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