1 /* $NetBSD: dict_sockmap.c,v 1.7 2026/05/09 18:49:22 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 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 /* 202604 Claude: don't return NULL with dict->error==0. */ 260 return (reply_payload ? reply_payload : ""); 261 } else if (strcmp(STR(dp->rdwr_buf), DICT_SOCKMAP_PROT_NOTFOUND) == 0) { 262 dict->error = 0; 263 return (0); 264 } 265 /* We got no definitive reply. */ 266 if (strcmp(STR(dp->rdwr_buf), DICT_SOCKMAP_PROT_TEMP) == 0) { 267 error_class = "temporary"; 268 dict->error = DICT_ERR_RETRY; 269 } else if (strcmp(STR(dp->rdwr_buf), DICT_SOCKMAP_PROT_TIMEOUT) == 0) { 270 error_class = "timeout"; 271 dict->error = DICT_ERR_RETRY; 272 } else if (strcmp(STR(dp->rdwr_buf), DICT_SOCKMAP_PROT_PERM) == 0) { 273 error_class = "permanent"; 274 dict->error = DICT_ERR_CONFIG; 275 } else { 276 error_class = "unknown"; 277 dict->error = DICT_ERR_RETRY; 278 } 279 while (reply_payload && ISSPACE(*reply_payload)) 280 reply_payload++; 281 msg_warn("%s:%s socketmap server %s error%s%.200s", 282 dict->type, dict->name, error_class, 283 reply_payload && *reply_payload ? ": " : "", 284 reply_payload && *reply_payload ? 285 printable(reply_payload, '?') : ""); 286 return (0); 287 } 288 289 /* dict_sockmap_close - close socket map */ 290 291 static void dict_sockmap_close(DICT *dict) 292 { 293 const char *myname = "dict_sockmap_close"; 294 DICT_SOCKMAP *dp = (DICT_SOCKMAP *) dict; 295 296 if (dict_sockmap_handles == 0 || dict_sockmap_handles->used == 0) 297 msg_panic("%s: attempt to close a non-existent map", myname); 298 vstring_free(dp->rdwr_buf); 299 myfree(dp->sockmap_name); 300 if (--DICT_SOCKMAP_RH_REFCOUNT(dp->client_info) == 0) { 301 auto_clnt_free(DICT_SOCKMAP_RH_HANDLE(dp->client_info)); 302 htable_delete(dict_sockmap_handles, 303 DICT_SOCKMAP_RH_NAME(dp->client_info), myfree); 304 } 305 if (dict->fold_buf) 306 vstring_free(dict->fold_buf); 307 dict_free(dict); 308 } 309 310 /* dict_sockmap_open - open socket map */ 311 312 DICT *dict_sockmap_open(const char *mapname, int open_flags, int dict_flags) 313 { 314 DICT_SOCKMAP *dp; 315 char *saved_name = 0; 316 char *sockmap; 317 DICT_SOCKMAP_REFC_HANDLE *ref_handle; 318 HTABLE_INFO *client_info; 319 320 /* 321 * Let the optimizer worry about eliminating redundant code. 322 */ 323 #define DICT_SOCKMAP_OPEN_RETURN(d) do { \ 324 DICT *__d = (d); \ 325 if (saved_name != 0) \ 326 myfree(saved_name); \ 327 return (__d); \ 328 } while (0) 329 330 /* 331 * Sanity checks. 332 */ 333 if (open_flags != O_RDONLY) 334 DICT_SOCKMAP_OPEN_RETURN(dict_surrogate(DICT_TYPE_SOCKMAP, mapname, 335 open_flags, dict_flags, 336 "%s:%s map requires O_RDONLY access mode", 337 DICT_TYPE_SOCKMAP, mapname)); 338 if (dict_flags & DICT_FLAG_NO_UNAUTH) 339 DICT_SOCKMAP_OPEN_RETURN(dict_surrogate(DICT_TYPE_SOCKMAP, mapname, 340 open_flags, dict_flags, 341 "%s:%s map is not allowed for security-sensitive data", 342 DICT_TYPE_SOCKMAP, mapname)); 343 344 /* 345 * Separate the socketmap name from the socketmap server name. 346 */ 347 saved_name = mystrdup(mapname); 348 if ((sockmap = split_at_right(saved_name, ':')) == 0) 349 DICT_SOCKMAP_OPEN_RETURN(dict_surrogate(DICT_TYPE_SOCKMAP, mapname, 350 open_flags, dict_flags, 351 "%s requires server:socketmap argument", 352 DICT_TYPE_SOCKMAP)); 353 354 /* 355 * Use one reference-counted client handle for all socketmaps with the 356 * same inet:host:port or unix:pathname information. 357 * 358 * XXX Todo: graceful degradation after endpoint syntax error. 359 */ 360 if (dict_sockmap_handles == 0) 361 dict_sockmap_handles = htable_create(1); 362 if ((client_info = htable_locate(dict_sockmap_handles, saved_name)) == 0) { 363 ref_handle = (DICT_SOCKMAP_REFC_HANDLE *) mymalloc(sizeof(*ref_handle)); 364 client_info = htable_enter(dict_sockmap_handles, 365 saved_name, (void *) ref_handle); 366 /* XXX Late initialization, so we can reuse macros for consistency. */ 367 DICT_SOCKMAP_RH_REFCOUNT(client_info) = 1; 368 DICT_SOCKMAP_RH_HANDLE(client_info) = 369 auto_clnt_create(saved_name, dict_sockmap_timeout, 370 dict_sockmap_max_idle, dict_sockmap_max_ttl); 371 } else 372 DICT_SOCKMAP_RH_REFCOUNT(client_info) += 1; 373 374 /* 375 * Instantiate a socket map handle. 376 */ 377 dp = (DICT_SOCKMAP *) dict_alloc(DICT_TYPE_SOCKMAP, mapname, sizeof(*dp)); 378 dp->rdwr_buf = vstring_alloc(100); 379 dp->sockmap_name = mystrdup(sockmap); 380 dp->client_info = client_info; 381 dp->dict.lookup = dict_sockmap_lookup; 382 dp->dict.close = dict_sockmap_close; 383 /* Don't look up parent domains or network superblocks. */ 384 dp->dict.flags = dict_flags | DICT_FLAG_PATTERN; 385 386 DICT_SOCKMAP_OPEN_RETURN(&dp->dict); 387 } 388