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
dict_proxy_handshake(VSTREAM * stream)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
dict_proxy_sequence(DICT * dict,int function,const char ** key,const char ** value)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
dict_proxy_lookup(DICT * dict,const char * key)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
dict_proxy_update(DICT * dict,const char * key,const char * value)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
dict_proxy_delete(DICT * dict,const char * key)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
dict_proxy_close(DICT * dict)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
dict_proxy_open(const char * map,int open_flags,int dict_flags)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