1 /* $NetBSD: dict_sockmap.c,v 1.5 2017/02/14 01:16:49 christos Exp $ */ 2 3 /*++ 4 /* NAME 5 /* dict_sockmap 3 6 /* SUMMARY 7 /* dictionary manager interface to Sendmail-style socketmap server 8 /* SYNOPSIS 9 /* #include <dict_sockmap.h> 10 /* 11 /* DICT *dict_sockmap_open(map, open_flags, dict_flags) 12 /* const char *map; 13 /* int open_flags; 14 /* int dict_flags; 15 /* DESCRIPTION 16 /* dict_sockmap_open() makes a Sendmail-style socketmap server 17 /* accessible via the generic dictionary operations described 18 /* in dict_open(3). The only implemented operation is dictionary 19 /* lookup. This map type can be useful for simulating a dynamic 20 /* lookup table. 21 /* 22 /* Postfix socketmap names have the form inet:host:port:socketmap-name 23 /* or unix:pathname:socketmap-name, where socketmap-name 24 /* specifies the socketmap name that the socketmap server uses. 25 /* 26 /* To test this module, build the netstring and dict_open test 27 /* programs. Run "./netstring nc -l portnumber" as the server, 28 /* and "./dict_open socketmap:127.0.0.1:portnumber:socketmapname" 29 /* as the client. 30 /* PROTOCOL 31 /* .ad 32 /* .fi 33 /* The socketmap class implements a simple protocol: the client 34 /* sends one request, and the server sends one reply. 35 /* ENCODING 36 /* .ad 37 /* .fi 38 /* Each request and reply are sent as one netstring object. 39 /* REQUEST FORMAT 40 /* .ad 41 /* .fi 42 /* .IP "<mapname> <space> <key>" 43 /* Search the specified socketmap under the specified key. 44 /* REPLY FORMAT 45 /* .ad 46 /* .fi 47 /* Replies must be no longer than 100000 characters (not including 48 /* the netstring encapsulation), and must have the following 49 /* form: 50 /* .IP "OK <space> <data>" 51 /* The requested data was found. 52 /* .IP "NOTFOUND <space>" 53 /* The requested data was not found. 54 /* .IP "TEMP <space> <reason>" 55 /* .IP "TIMEOUT <space> <reason>" 56 /* .IP "PERM <space> <reason>" 57 /* The request failed. The reason, if non-empty, is descriptive 58 /* text. 59 /* SECURITY 60 /* This map cannot be used for security-sensitive information, 61 /* because neither the connection nor the server are authenticated. 62 /* SEE ALSO 63 /* dict(3) generic dictionary manager 64 /* netstring(3) netstring stream I/O support 65 /* DIAGNOSTICS 66 /* Fatal errors: out of memory, unknown host or service name, 67 /* attempt to update or iterate over map. 68 /* BUGS 69 /* The protocol limits are not yet configurable. 70 /* LICENSE 71 /* .ad 72 /* .fi 73 /* The Secure Mailer license must be distributed with this software. 74 /* AUTHOR(S) 75 /* Wietse Venema 76 /* IBM T.J. Watson Research 77 /* P.O. Box 704 78 /* Yorktown Heights, NY 10598, USA 79 /*--*/ 80 81 /* 82 * System library. 83 */ 84 #include <sys_defs.h> 85 #include <errno.h> 86 #include <string.h> 87 #include <ctype.h> 88 89 /* 90 * Utility library. 91 */ 92 #include <mymalloc.h> 93 #include <msg.h> 94 #include <vstream.h> 95 #include <auto_clnt.h> 96 #include <netstring.h> 97 #include <split_at.h> 98 #include <stringops.h> 99 #include <htable.h> 100 #include <dict_sockmap.h> 101 102 /* 103 * Socket map data structure. 104 */ 105 typedef struct { 106 DICT dict; /* parent class */ 107 char *sockmap_name; /* on-the-wire socketmap name */ 108 VSTRING *rdwr_buf; /* read/write buffer */ 109 HTABLE_INFO *client_info; /* shared endpoint name and handle */ 110 } DICT_SOCKMAP; 111 112 /* 113 * Default limits. 114 */ 115 #define DICT_SOCKMAP_DEF_TIMEOUT 100 /* connect/read/write timeout */ 116 #define DICT_SOCKMAP_DEF_MAX_REPLY 100000 /* reply size limit */ 117 #define DICT_SOCKMAP_DEF_MAX_IDLE 10 /* close idle socket */ 118 #define DICT_SOCKMAP_DEF_MAX_TTL 100 /* close old socket */ 119 120 /* 121 * Class variables. 122 */ 123 static int dict_sockmap_timeout = DICT_SOCKMAP_DEF_TIMEOUT; 124 static int dict_sockmap_max_reply = DICT_SOCKMAP_DEF_MAX_REPLY; 125 static int dict_sockmap_max_idle = DICT_SOCKMAP_DEF_MAX_IDLE; 126 static int dict_sockmap_max_ttl = DICT_SOCKMAP_DEF_MAX_TTL; 127 128 /* 129 * The client handle is shared between socketmap instances that have the 130 * same inet:host:port or unix:pathame information. This could be factored 131 * out as a general module for reference-counted handles of any kind. 132 */ 133 static HTABLE *dict_sockmap_handles; /* shared handles */ 134 135 typedef struct { 136 AUTO_CLNT *client_handle; /* the client handle */ 137 int refcount; /* the reference count */ 138 } DICT_SOCKMAP_REFC_HANDLE; 139 140 #define DICT_SOCKMAP_RH_NAME(ht) (ht)->key 141 #define DICT_SOCKMAP_RH_HANDLE(ht) \ 142 ((DICT_SOCKMAP_REFC_HANDLE *) (ht)->value)->client_handle 143 #define DICT_SOCKMAP_RH_REFCOUNT(ht) \ 144 ((DICT_SOCKMAP_REFC_HANDLE *) (ht)->value)->refcount 145 146 /* 147 * Socketmap protocol elements. 148 */ 149 #define DICT_SOCKMAP_PROT_OK "OK" 150 #define DICT_SOCKMAP_PROT_NOTFOUND "NOTFOUND" 151 #define DICT_SOCKMAP_PROT_TEMP "TEMP" 152 #define DICT_SOCKMAP_PROT_TIMEOUT "TIMEOUT" 153 #define DICT_SOCKMAP_PROT_PERM "PERM" 154 155 /* 156 * SLMs. 157 */ 158 #define STR(x) vstring_str(x) 159 #define LEN(x) VSTRING_LEN(x) 160 161 /* dict_sockmap_lookup - socket map lookup */ 162 163 static const char *dict_sockmap_lookup(DICT *dict, const char *key) 164 { 165 const char *myname = "dict_sockmap_lookup"; 166 DICT_SOCKMAP *dp = (DICT_SOCKMAP *) dict; 167 AUTO_CLNT *sockmap_clnt = DICT_SOCKMAP_RH_HANDLE(dp->client_info); 168 VSTREAM *fp; 169 int netstring_err; 170 char *reply_payload; 171 int except_count; 172 const char *error_class; 173 174 if (msg_verbose) 175 msg_info("%s: key %s", myname, key); 176 177 /* 178 * Optionally fold the key. 179 */ 180 if (dict->flags & DICT_FLAG_FOLD_MUL) { 181 if (dict->fold_buf == 0) 182 dict->fold_buf = vstring_alloc(100); 183 vstring_strcpy(dict->fold_buf, key); 184 key = lowercase(STR(dict->fold_buf)); 185 } 186 187 /* 188 * We retry connection-level errors once, to make server restarts 189 * transparent. 190 */ 191 for (except_count = 0; /* see below */ ; except_count++) { 192 193 /* 194 * Look up the stream. 195 */ 196 if ((fp = auto_clnt_access(sockmap_clnt)) == 0) { 197 msg_warn("table %s:%s lookup error: %m", dict->type, dict->name); 198 dict->error = DICT_ERR_RETRY; 199 return (0); 200 } 201 202 /* 203 * Set up an exception handler. 204 */ 205 netstring_setup(fp, dict_sockmap_timeout); 206 if ((netstring_err = vstream_setjmp(fp)) == 0) { 207 208 /* 209 * Send the query. This may raise an exception. 210 */ 211 vstring_sprintf(dp->rdwr_buf, "%s %s", dp->sockmap_name, key); 212 NETSTRING_PUT_BUF(fp, dp->rdwr_buf); 213 214 /* 215 * Receive the response. This may raise an exception. 216 */ 217 netstring_get(fp, dp->rdwr_buf, dict_sockmap_max_reply); 218 219 /* 220 * If we got here, then no exception was raised. 221 */ 222 break; 223 } 224 225 /* 226 * Handle exceptions. 227 */ 228 else { 229 230 /* 231 * We retry a broken connection only once. 232 */ 233 if (except_count == 0 && netstring_err == NETSTRING_ERR_EOF 234 && errno != ETIMEDOUT) { 235 auto_clnt_recover(sockmap_clnt); 236 continue; 237 } 238 239 /* 240 * We do not retry other errors. 241 */ 242 else { 243 msg_warn("table %s:%s lookup error: %s", 244 dict->type, dict->name, 245 netstring_strerror(netstring_err)); 246 dict->error = DICT_ERR_RETRY; 247 return (0); 248 } 249 } 250 } 251 252 /* 253 * Parse the reply. 254 */ 255 VSTRING_TERMINATE(dp->rdwr_buf); 256 reply_payload = split_at(STR(dp->rdwr_buf), ' '); 257 if (strcmp(STR(dp->rdwr_buf), DICT_SOCKMAP_PROT_OK) == 0) { 258 dict->error = 0; 259 return (reply_payload); 260 } else if (strcmp(STR(dp->rdwr_buf), DICT_SOCKMAP_PROT_NOTFOUND) == 0) { 261 dict->error = 0; 262 return (0); 263 } 264 /* We got no definitive reply. */ 265 if (strcmp(STR(dp->rdwr_buf), DICT_SOCKMAP_PROT_TEMP) == 0) { 266 error_class = "temporary"; 267 dict->error = DICT_ERR_RETRY; 268 } else if (strcmp(STR(dp->rdwr_buf), DICT_SOCKMAP_PROT_TIMEOUT) == 0) { 269 error_class = "timeout"; 270 dict->error = DICT_ERR_RETRY; 271 } else if (strcmp(STR(dp->rdwr_buf), DICT_SOCKMAP_PROT_PERM) == 0) { 272 error_class = "permanent"; 273 dict->error = DICT_ERR_CONFIG; 274 } else { 275 error_class = "unknown"; 276 dict->error = DICT_ERR_RETRY; 277 } 278 while (reply_payload && ISSPACE(*reply_payload)) 279 reply_payload++; 280 msg_warn("%s:%s socketmap server %s error%s%.200s", 281 dict->type, dict->name, error_class, 282 reply_payload && *reply_payload ? ": " : "", 283 reply_payload && *reply_payload ? 284 printable(reply_payload, '?') : ""); 285 return (0); 286 } 287 288 /* dict_sockmap_close - close socket map */ 289 290 static void dict_sockmap_close(DICT *dict) 291 { 292 const char *myname = "dict_sockmap_close"; 293 DICT_SOCKMAP *dp = (DICT_SOCKMAP *) dict; 294 295 if (dict_sockmap_handles == 0 || dict_sockmap_handles->used == 0) 296 msg_panic("%s: attempt to close a non-existent map", myname); 297 vstring_free(dp->rdwr_buf); 298 myfree(dp->sockmap_name); 299 if (--DICT_SOCKMAP_RH_REFCOUNT(dp->client_info) == 0) { 300 auto_clnt_free(DICT_SOCKMAP_RH_HANDLE(dp->client_info)); 301 htable_delete(dict_sockmap_handles, 302 DICT_SOCKMAP_RH_NAME(dp->client_info), myfree); 303 } 304 if (dict->fold_buf) 305 vstring_free(dict->fold_buf); 306 dict_free(dict); 307 } 308 309 /* dict_sockmap_open - open socket map */ 310 311 DICT *dict_sockmap_open(const char *mapname, int open_flags, int dict_flags) 312 { 313 DICT_SOCKMAP *dp; 314 char *saved_name = 0; 315 char *sockmap; 316 DICT_SOCKMAP_REFC_HANDLE *ref_handle; 317 HTABLE_INFO *client_info; 318 319 /* 320 * Let the optimizer worry about eliminating redundant code. 321 */ 322 #define DICT_SOCKMAP_OPEN_RETURN(d) do { \ 323 DICT *__d = (d); \ 324 if (saved_name != 0) \ 325 myfree(saved_name); \ 326 return (__d); \ 327 } while (0) 328 329 /* 330 * Sanity checks. 331 */ 332 if (open_flags != O_RDONLY) 333 DICT_SOCKMAP_OPEN_RETURN(dict_surrogate(DICT_TYPE_SOCKMAP, mapname, 334 open_flags, dict_flags, 335 "%s:%s map requires O_RDONLY access mode", 336 DICT_TYPE_SOCKMAP, mapname)); 337 if (dict_flags & DICT_FLAG_NO_UNAUTH) 338 DICT_SOCKMAP_OPEN_RETURN(dict_surrogate(DICT_TYPE_SOCKMAP, mapname, 339 open_flags, dict_flags, 340 "%s:%s map is not allowed for security-sensitive data", 341 DICT_TYPE_SOCKMAP, mapname)); 342 343 /* 344 * Separate the socketmap name from the socketmap server name. 345 */ 346 saved_name = mystrdup(mapname); 347 if ((sockmap = split_at_right(saved_name, ':')) == 0) 348 DICT_SOCKMAP_OPEN_RETURN(dict_surrogate(DICT_TYPE_SOCKMAP, mapname, 349 open_flags, dict_flags, 350 "%s requires server:socketmap argument", 351 DICT_TYPE_SOCKMAP)); 352 353 /* 354 * Use one reference-counted client handle for all socketmaps with the 355 * same inet:host:port or unix:pathname information. 356 * 357 * XXX Todo: graceful degradation after endpoint syntax error. 358 */ 359 if (dict_sockmap_handles == 0) 360 dict_sockmap_handles = htable_create(1); 361 if ((client_info = htable_locate(dict_sockmap_handles, saved_name)) == 0) { 362 ref_handle = (DICT_SOCKMAP_REFC_HANDLE *) mymalloc(sizeof(*ref_handle)); 363 client_info = htable_enter(dict_sockmap_handles, 364 saved_name, (void *) ref_handle); 365 /* XXX Late initialization, so we can reuse macros for consistency. */ 366 DICT_SOCKMAP_RH_REFCOUNT(client_info) = 1; 367 DICT_SOCKMAP_RH_HANDLE(client_info) = 368 auto_clnt_create(saved_name, dict_sockmap_timeout, 369 dict_sockmap_max_idle, dict_sockmap_max_ttl); 370 } else 371 DICT_SOCKMAP_RH_REFCOUNT(client_info) += 1; 372 373 /* 374 * Instantiate a socket map handle. 375 */ 376 dp = (DICT_SOCKMAP *) dict_alloc(DICT_TYPE_SOCKMAP, mapname, sizeof(*dp)); 377 dp->rdwr_buf = vstring_alloc(100); 378 dp->sockmap_name = mystrdup(sockmap); 379 dp->client_info = client_info; 380 dp->dict.lookup = dict_sockmap_lookup; 381 dp->dict.close = dict_sockmap_close; 382 /* Don't look up parent domains or network superblocks. */ 383 dp->dict.flags = dict_flags | DICT_FLAG_PATTERN; 384 385 DICT_SOCKMAP_OPEN_RETURN(DICT_DEBUG (&dp->dict)); 386 } 387