1 /* $NetBSD: dict_proxy.c,v 1.3 2022/10/08 16:12:45 christos Exp $ */ 2 3 /*++ 4 /* NAME 5 /* dict_proxy 3 6 /* SUMMARY 7 /* generic dictionary proxy client 8 /* SYNOPSIS 9 /* #include <dict_proxy.h> 10 /* 11 /* DICT *dict_proxy_open(map, open_flags, dict_flags) 12 /* const char *map; 13 /* int open_flags; 14 /* int dict_flags; 15 /* DESCRIPTION 16 /* dict_proxy_open() relays read-only or read-write operations 17 /* through the Postfix proxymap server. 18 /* 19 /* The \fIopen_flags\fR argument must specify O_RDONLY 20 /* or O_RDWR. Depending on this, the client 21 /* connects to the proxymap multiserver or to the 22 /* proxywrite single updater. 23 /* 24 /* The connection to the Postfix proxymap server is automatically 25 /* closed after $ipc_idle seconds of idle time, or after $ipc_ttl 26 /* seconds of activity. 27 /* SECURITY 28 /* The proxy map server is not meant to be a trusted process. Proxy 29 /* maps must not be used to look up security sensitive information 30 /* such as user/group IDs, output files, or external commands. 31 /* SEE ALSO 32 /* dict(3) generic dictionary manager 33 /* clnt_stream(3) client endpoint connection management 34 /* DIAGNOSTICS 35 /* Fatal errors: out of memory, unimplemented operation, 36 /* bad request parameter, map not approved for proxy access. 37 /* LICENSE 38 /* .ad 39 /* .fi 40 /* The Secure Mailer license must be distributed with this software. 41 /* AUTHOR(S) 42 /* Wietse Venema 43 /* IBM T.J. Watson Research 44 /* P.O. Box 704 45 /* Yorktown Heights, NY 10598, USA 46 /* 47 /* Wietse Venema 48 /* Google, Inc. 49 /* 111 8th Avenue 50 /* New York, NY 10011, USA 51 /*--*/ 52 53 /* System library. */ 54 55 #include <sys_defs.h> 56 #include <errno.h> 57 #include <unistd.h> 58 59 /* Utility library. */ 60 61 #include <msg.h> 62 #include <mymalloc.h> 63 #include <stringops.h> 64 #include <vstring.h> 65 #include <vstream.h> 66 #include <attr.h> 67 #include <dict.h> 68 69 /* Global library. */ 70 71 #include <mail_proto.h> 72 #include <mail_params.h> 73 #include <clnt_stream.h> 74 #include <dict_proxy.h> 75 76 /* Application-specific. */ 77 78 typedef struct { 79 DICT dict; /* generic members */ 80 CLNT_STREAM *clnt; /* client handle (shared) */ 81 const char *service; /* service name */ 82 int inst_flags; /* saved dict flags */ 83 VSTRING *reskey; /* result key storage */ 84 VSTRING *result; /* storage */ 85 } DICT_PROXY; 86 87 /* 88 * SLMs. 89 */ 90 #define STR(x) vstring_str(x) 91 #define VSTREQ(v,s) (strcmp(STR(v),s) == 0) 92 93 /* 94 * All proxied maps of the same type share the same query/reply socket. 95 */ 96 static CLNT_STREAM *proxymap_stream; /* read-only maps */ 97 static CLNT_STREAM *proxywrite_stream; /* read-write maps */ 98 99 /* dict_proxy_handshake - receive server protocol announcement */ 100 101 static int dict_proxy_handshake(VSTREAM *stream) 102 { 103 return (attr_scan(stream, ATTR_FLAG_STRICT, 104 RECV_ATTR_STREQ(MAIL_ATTR_PROTO, MAIL_ATTR_PROTO_PROXYMAP), 105 ATTR_TYPE_END)); 106 } 107 108 /* dict_proxy_sequence - find first/next entry */ 109 110 static int dict_proxy_sequence(DICT *dict, int function, 111 const char **key, const char **value) 112 { 113 const char *myname = "dict_proxy_sequence"; 114 DICT_PROXY *dict_proxy = (DICT_PROXY *) dict; 115 VSTREAM *stream; 116 int status; 117 int count = 0; 118 int request_flags; 119 120 /* 121 * The client and server live in separate processes that may start and 122 * terminate independently. We cannot rely on a persistent connection, 123 * let alone on persistent state (such as a specific open table) that is 124 * associated with a specific connection. Each lookup needs to specify 125 * the table and the flags that were specified to dict_proxy_open(). 126 */ 127 VSTRING_RESET(dict_proxy->reskey); 128 VSTRING_TERMINATE(dict_proxy->reskey); 129 VSTRING_RESET(dict_proxy->result); 130 VSTRING_TERMINATE(dict_proxy->result); 131 request_flags = dict_proxy->inst_flags 132 | (dict->flags & DICT_FLAG_RQST_MASK); 133 for (;;) { 134 stream = clnt_stream_access(dict_proxy->clnt); 135 errno = 0; 136 count += 1; 137 if (stream == 0 138 || attr_print(stream, ATTR_FLAG_NONE, 139 SEND_ATTR_STR(MAIL_ATTR_REQ, PROXY_REQ_SEQUENCE), 140 SEND_ATTR_STR(MAIL_ATTR_TABLE, dict->name), 141 SEND_ATTR_INT(MAIL_ATTR_FLAGS, request_flags), 142 SEND_ATTR_INT(MAIL_ATTR_FUNC, function), 143 ATTR_TYPE_END) != 0 144 || vstream_fflush(stream) 145 || attr_scan(stream, ATTR_FLAG_STRICT, 146 RECV_ATTR_INT(MAIL_ATTR_STATUS, &status), 147 RECV_ATTR_STR(MAIL_ATTR_KEY, dict_proxy->reskey), 148 RECV_ATTR_STR(MAIL_ATTR_VALUE, dict_proxy->result), 149 ATTR_TYPE_END) != 3) { 150 if (msg_verbose || count > 1 || (errno && errno != EPIPE && errno != ENOENT)) 151 msg_warn("%s: service %s: %m", myname, dict_proxy->service); 152 } else { 153 if (msg_verbose) 154 msg_info("%s: table=%s flags=%s func=%d -> status=%d key=%s val=%s", 155 myname, dict->name, dict_flags_str(request_flags), 156 function, status, STR(dict_proxy->reskey), 157 STR(dict_proxy->result)); 158 switch (status) { 159 case PROXY_STAT_BAD: 160 msg_fatal("%s sequence failed for table \"%s\" function %d: " 161 "invalid request", 162 dict_proxy->service, dict->name, function); 163 case PROXY_STAT_DENY: 164 msg_fatal("%s service is not configured for table \"%s\"", 165 dict_proxy->service, dict->name); 166 case PROXY_STAT_OK: 167 *key = STR(dict_proxy->reskey); 168 *value = STR(dict_proxy->result); 169 DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE, DICT_STAT_SUCCESS); 170 case PROXY_STAT_NOKEY: 171 *key = *value = 0; 172 DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE, DICT_STAT_FAIL); 173 case PROXY_STAT_RETRY: 174 *key = *value = 0; 175 DICT_ERR_VAL_RETURN(dict, DICT_ERR_RETRY, DICT_STAT_ERROR); 176 case PROXY_STAT_CONFIG: 177 *key = *value = 0; 178 DICT_ERR_VAL_RETURN(dict, DICT_ERR_CONFIG, DICT_STAT_ERROR); 179 default: 180 msg_warn("%s sequence failed for table \"%s\" function %d: " 181 "unexpected reply status %d", 182 dict_proxy->service, dict->name, function, status); 183 } 184 } 185 clnt_stream_recover(dict_proxy->clnt); 186 sleep(1); /* XXX make configurable */ 187 } 188 } 189 190 /* dict_proxy_lookup - find table entry */ 191 192 static const char *dict_proxy_lookup(DICT *dict, const char *key) 193 { 194 const char *myname = "dict_proxy_lookup"; 195 DICT_PROXY *dict_proxy = (DICT_PROXY *) dict; 196 VSTREAM *stream; 197 int status; 198 int count = 0; 199 int request_flags; 200 201 /* 202 * The client and server live in separate processes that may start and 203 * terminate independently. We cannot rely on a persistent connection, 204 * let alone on persistent state (such as a specific open table) that is 205 * associated with a specific connection. Each lookup needs to specify 206 * the table and the flags that were specified to dict_proxy_open(). 207 */ 208 VSTRING_RESET(dict_proxy->result); 209 VSTRING_TERMINATE(dict_proxy->result); 210 request_flags = dict_proxy->inst_flags 211 | (dict->flags & DICT_FLAG_RQST_MASK); 212 for (;;) { 213 stream = clnt_stream_access(dict_proxy->clnt); 214 errno = 0; 215 count += 1; 216 if (stream == 0 217 || attr_print(stream, ATTR_FLAG_NONE, 218 SEND_ATTR_STR(MAIL_ATTR_REQ, PROXY_REQ_LOOKUP), 219 SEND_ATTR_STR(MAIL_ATTR_TABLE, dict->name), 220 SEND_ATTR_INT(MAIL_ATTR_FLAGS, request_flags), 221 SEND_ATTR_STR(MAIL_ATTR_KEY, key), 222 ATTR_TYPE_END) != 0 223 || vstream_fflush(stream) 224 || attr_scan(stream, ATTR_FLAG_STRICT, 225 RECV_ATTR_INT(MAIL_ATTR_STATUS, &status), 226 RECV_ATTR_STR(MAIL_ATTR_VALUE, dict_proxy->result), 227 ATTR_TYPE_END) != 2) { 228 if (msg_verbose || count > 1 || (errno && errno != EPIPE && errno != ENOENT)) 229 msg_warn("%s: service %s: %m", myname, dict_proxy->service); 230 } else { 231 if (msg_verbose) 232 msg_info("%s: table=%s flags=%s key=%s -> status=%d result=%s", 233 myname, dict->name, 234 dict_flags_str(request_flags), key, 235 status, STR(dict_proxy->result)); 236 switch (status) { 237 case PROXY_STAT_BAD: 238 msg_fatal("%s lookup failed for table \"%s\" key \"%s\": " 239 "invalid request", 240 dict_proxy->service, dict->name, key); 241 case PROXY_STAT_DENY: 242 msg_fatal("%s service is not configured for table \"%s\"", 243 dict_proxy->service, dict->name); 244 case PROXY_STAT_OK: 245 DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE, STR(dict_proxy->result)); 246 case PROXY_STAT_NOKEY: 247 DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE, (char *) 0); 248 case PROXY_STAT_RETRY: 249 DICT_ERR_VAL_RETURN(dict, DICT_ERR_RETRY, (char *) 0); 250 case PROXY_STAT_CONFIG: 251 DICT_ERR_VAL_RETURN(dict, DICT_ERR_CONFIG, (char *) 0); 252 default: 253 msg_warn("%s lookup failed for table \"%s\" key \"%s\": " 254 "unexpected reply status %d", 255 dict_proxy->service, dict->name, key, status); 256 } 257 } 258 clnt_stream_recover(dict_proxy->clnt); 259 sleep(1); /* XXX make configurable */ 260 } 261 } 262 263 /* dict_proxy_update - update table entry */ 264 265 static int dict_proxy_update(DICT *dict, const char *key, const char *value) 266 { 267 const char *myname = "dict_proxy_update"; 268 DICT_PROXY *dict_proxy = (DICT_PROXY *) dict; 269 VSTREAM *stream; 270 int status; 271 int count = 0; 272 int request_flags; 273 274 /* 275 * The client and server live in separate processes that may start and 276 * terminate independently. We cannot rely on a persistent connection, 277 * let alone on persistent state (such as a specific open table) that is 278 * associated with a specific connection. Each lookup needs to specify 279 * the table and the flags that were specified to dict_proxy_open(). 280 */ 281 request_flags = dict_proxy->inst_flags 282 | (dict->flags & DICT_FLAG_RQST_MASK); 283 for (;;) { 284 stream = clnt_stream_access(dict_proxy->clnt); 285 errno = 0; 286 count += 1; 287 if (stream == 0 288 || attr_print(stream, ATTR_FLAG_NONE, 289 SEND_ATTR_STR(MAIL_ATTR_REQ, PROXY_REQ_UPDATE), 290 SEND_ATTR_STR(MAIL_ATTR_TABLE, dict->name), 291 SEND_ATTR_INT(MAIL_ATTR_FLAGS, request_flags), 292 SEND_ATTR_STR(MAIL_ATTR_KEY, key), 293 SEND_ATTR_STR(MAIL_ATTR_VALUE, value), 294 ATTR_TYPE_END) != 0 295 || vstream_fflush(stream) 296 || attr_scan(stream, ATTR_FLAG_STRICT, 297 RECV_ATTR_INT(MAIL_ATTR_STATUS, &status), 298 ATTR_TYPE_END) != 1) { 299 if (msg_verbose || count > 1 || (errno && errno != EPIPE && errno != ENOENT)) 300 msg_warn("%s: service %s: %m", myname, dict_proxy->service); 301 } else { 302 if (msg_verbose) 303 msg_info("%s: table=%s flags=%s key=%s value=%s -> status=%d", 304 myname, dict->name, dict_flags_str(request_flags), 305 key, value, status); 306 switch (status) { 307 case PROXY_STAT_BAD: 308 msg_fatal("%s update failed for table \"%s\" key \"%s\": " 309 "invalid request", 310 dict_proxy->service, dict->name, key); 311 case PROXY_STAT_DENY: 312 msg_fatal("%s update access is not configured for table \"%s\"", 313 dict_proxy->service, dict->name); 314 case PROXY_STAT_OK: 315 DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE, DICT_STAT_SUCCESS); 316 case PROXY_STAT_NOKEY: 317 DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE, DICT_STAT_FAIL); 318 case PROXY_STAT_RETRY: 319 DICT_ERR_VAL_RETURN(dict, DICT_ERR_RETRY, DICT_STAT_ERROR); 320 case PROXY_STAT_CONFIG: 321 DICT_ERR_VAL_RETURN(dict, DICT_ERR_CONFIG, DICT_STAT_ERROR); 322 default: 323 msg_warn("%s update failed for table \"%s\" key \"%s\": " 324 "unexpected reply status %d", 325 dict_proxy->service, dict->name, key, status); 326 } 327 } 328 clnt_stream_recover(dict_proxy->clnt); 329 sleep(1); /* XXX make configurable */ 330 } 331 } 332 333 /* dict_proxy_delete - delete table entry */ 334 335 static int dict_proxy_delete(DICT *dict, const char *key) 336 { 337 const char *myname = "dict_proxy_delete"; 338 DICT_PROXY *dict_proxy = (DICT_PROXY *) dict; 339 VSTREAM *stream; 340 int status; 341 int count = 0; 342 int request_flags; 343 344 /* 345 * The client and server live in separate processes that may start and 346 * terminate independently. We cannot rely on a persistent connection, 347 * let alone on persistent state (such as a specific open table) that is 348 * associated with a specific connection. Each lookup needs to specify 349 * the table and the flags that were specified to dict_proxy_open(). 350 */ 351 request_flags = dict_proxy->inst_flags 352 | (dict->flags & DICT_FLAG_RQST_MASK); 353 for (;;) { 354 stream = clnt_stream_access(dict_proxy->clnt); 355 errno = 0; 356 count += 1; 357 if (stream == 0 358 || attr_print(stream, ATTR_FLAG_NONE, 359 SEND_ATTR_STR(MAIL_ATTR_REQ, PROXY_REQ_DELETE), 360 SEND_ATTR_STR(MAIL_ATTR_TABLE, dict->name), 361 SEND_ATTR_INT(MAIL_ATTR_FLAGS, request_flags), 362 SEND_ATTR_STR(MAIL_ATTR_KEY, key), 363 ATTR_TYPE_END) != 0 364 || vstream_fflush(stream) 365 || attr_scan(stream, ATTR_FLAG_STRICT, 366 RECV_ATTR_INT(MAIL_ATTR_STATUS, &status), 367 ATTR_TYPE_END) != 1) { 368 if (msg_verbose || count > 1 || (errno && errno != EPIPE && errno != 369 ENOENT)) 370 msg_warn("%s: service %s: %m", myname, dict_proxy->service); 371 } else { 372 if (msg_verbose) 373 msg_info("%s: table=%s flags=%s key=%s -> status=%d", 374 myname, dict->name, dict_flags_str(request_flags), 375 key, status); 376 switch (status) { 377 case PROXY_STAT_BAD: 378 msg_fatal("%s delete failed for table \"%s\" key \"%s\": " 379 "invalid request", 380 dict_proxy->service, dict->name, key); 381 case PROXY_STAT_DENY: 382 msg_fatal("%s update access is not configured for table \"%s\"", 383 dict_proxy->service, dict->name); 384 case PROXY_STAT_OK: 385 DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE, DICT_STAT_SUCCESS); 386 case PROXY_STAT_NOKEY: 387 DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE, DICT_STAT_FAIL); 388 case PROXY_STAT_RETRY: 389 DICT_ERR_VAL_RETURN(dict, DICT_ERR_RETRY, DICT_STAT_ERROR); 390 case PROXY_STAT_CONFIG: 391 DICT_ERR_VAL_RETURN(dict, DICT_ERR_CONFIG, DICT_STAT_ERROR); 392 default: 393 msg_warn("%s delete failed for table \"%s\" key \"%s\": " 394 "unexpected reply status %d", 395 dict_proxy->service, dict->name, key, status); 396 } 397 } 398 clnt_stream_recover(dict_proxy->clnt); 399 sleep(1); /* XXX make configurable */ 400 } 401 } 402 403 /* dict_proxy_close - disconnect */ 404 405 static void dict_proxy_close(DICT *dict) 406 { 407 DICT_PROXY *dict_proxy = (DICT_PROXY *) dict; 408 409 vstring_free(dict_proxy->reskey); 410 vstring_free(dict_proxy->result); 411 dict_free(dict); 412 } 413 414 /* dict_proxy_open - open remote map */ 415 416 DICT *dict_proxy_open(const char *map, int open_flags, int dict_flags) 417 { 418 const char *myname = "dict_proxy_open"; 419 DICT_PROXY *dict_proxy; 420 VSTREAM *stream; 421 int server_flags; 422 int status; 423 const char *service; 424 char *relative_path; 425 char *kludge = 0; 426 char *prefix; 427 CLNT_STREAM **pstream; 428 429 /* 430 * If this map can't be proxied then we silently do a direct open. This 431 * allows sites to benefit from proxying the virtual mailbox maps without 432 * unnecessary pain. 433 */ 434 if (dict_flags & DICT_FLAG_NO_PROXY) 435 return (dict_open(map, open_flags, dict_flags)); 436 437 /* 438 * Use a shared stream for proxied table lookups of the same type. 439 * 440 * XXX A complete implementation would also allow O_RDWR without O_CREAT. 441 * But we must not pass on every possible set of flags to the proxy 442 * server; only sets that make sense. For now, the flags are passed 443 * implicitly by choosing between the proxymap or proxywrite service. 444 * 445 * XXX Use absolute pathname to make this work from non-daemon processes. 446 */ 447 if (open_flags == O_RDONLY) { 448 pstream = &proxymap_stream; 449 service = var_proxymap_service; 450 } else if ((open_flags & O_RDWR) == O_RDWR) { 451 pstream = &proxywrite_stream; 452 service = var_proxywrite_service; 453 } else 454 msg_fatal("%s: %s map open requires O_RDONLY or O_RDWR mode", 455 map, DICT_TYPE_PROXY); 456 457 if (*pstream == 0) { 458 relative_path = concatenate(MAIL_CLASS_PRIVATE "/", 459 service, (char *) 0); 460 if (access(relative_path, F_OK) == 0) 461 prefix = MAIL_CLASS_PRIVATE; 462 else 463 prefix = kludge = concatenate(var_queue_dir, "/", 464 MAIL_CLASS_PRIVATE, (char *) 0); 465 *pstream = clnt_stream_create(prefix, service, var_ipc_idle_limit, 466 var_ipc_ttl_limit, 467 dict_proxy_handshake); 468 if (kludge) 469 myfree(kludge); 470 myfree(relative_path); 471 } 472 473 /* 474 * Local initialization. 475 */ 476 dict_proxy = (DICT_PROXY *) 477 dict_alloc(DICT_TYPE_PROXY, map, sizeof(*dict_proxy)); 478 dict_proxy->dict.lookup = dict_proxy_lookup; 479 dict_proxy->dict.update = dict_proxy_update; 480 dict_proxy->dict.delete = dict_proxy_delete; 481 dict_proxy->dict.sequence = dict_proxy_sequence; 482 dict_proxy->dict.close = dict_proxy_close; 483 dict_proxy->inst_flags = (dict_flags & DICT_FLAG_INST_MASK); 484 dict_proxy->reskey = vstring_alloc(10); 485 dict_proxy->result = vstring_alloc(10); 486 dict_proxy->clnt = *pstream; 487 dict_proxy->service = service; 488 489 /* 490 * Establish initial contact and get the map type specific flags. 491 * 492 * XXX Should retrieve flags from local instance. 493 */ 494 for (;;) { 495 stream = clnt_stream_access(dict_proxy->clnt); 496 errno = 0; 497 if (stream == 0 498 || attr_print(stream, ATTR_FLAG_NONE, 499 SEND_ATTR_STR(MAIL_ATTR_REQ, PROXY_REQ_OPEN), 500 SEND_ATTR_STR(MAIL_ATTR_TABLE, dict_proxy->dict.name), 501 SEND_ATTR_INT(MAIL_ATTR_FLAGS, dict_proxy->inst_flags), 502 ATTR_TYPE_END) != 0 503 || vstream_fflush(stream) 504 || attr_scan(stream, ATTR_FLAG_STRICT, 505 RECV_ATTR_INT(MAIL_ATTR_STATUS, &status), 506 RECV_ATTR_INT(MAIL_ATTR_FLAGS, &server_flags), 507 ATTR_TYPE_END) != 2) { 508 if (msg_verbose || (errno != EPIPE && errno != ENOENT)) 509 msg_warn("%s: service %s: %m", myname, dict_proxy->service); 510 } else { 511 if (msg_verbose) 512 msg_info("%s: connect to map=%s status=%d server_flags=%s", 513 myname, dict_proxy->dict.name, status, 514 dict_flags_str(server_flags)); 515 switch (status) { 516 case PROXY_STAT_BAD: 517 msg_fatal("%s open failed for table \"%s\": invalid request", 518 dict_proxy->service, dict_proxy->dict.name); 519 case PROXY_STAT_DENY: 520 msg_fatal("%s service is not configured for table \"%s\"", 521 dict_proxy->service, dict_proxy->dict.name); 522 case PROXY_STAT_OK: 523 dict_proxy->dict.flags = (dict_flags & ~DICT_FLAG_IMPL_MASK) 524 | (server_flags & DICT_FLAG_IMPL_MASK); 525 return (DICT_DEBUG (&dict_proxy->dict)); 526 default: 527 msg_warn("%s open failed for table \"%s\": unexpected status %d", 528 dict_proxy->service, dict_proxy->dict.name, status); 529 } 530 } 531 clnt_stream_recover(dict_proxy->clnt); 532 sleep(1); /* XXX make configurable */ 533 } 534 } 535