xref: /netbsd-src/external/ibm-public/postfix/dist/src/global/dict_proxy.c (revision cb861154c176d3dcc8ff846f449e3c16a5f5edb5)
1 /*	$NetBSD: dict_proxy.c,v 1.1.1.1 2009/06/23 10:08:45 tron 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 operations through
17 /*	the Postfix proxymap server.
18 /*
19 /*	The \fIopen_flags\fR argument must specify O_RDONLY
20 /*	or O_RDWR|O_CREAT. 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 
48 /* System library. */
49 
50 #include <sys_defs.h>
51 #include <errno.h>
52 #include <unistd.h>
53 
54 /* Utility library. */
55 
56 #include <msg.h>
57 #include <mymalloc.h>
58 #include <stringops.h>
59 #include <vstring.h>
60 #include <vstream.h>
61 #include <attr.h>
62 #include <dict.h>
63 
64 /* Global library. */
65 
66 #include <mail_proto.h>
67 #include <mail_params.h>
68 #include <clnt_stream.h>
69 #include <dict_proxy.h>
70 
71 /* Application-specific. */
72 
73 typedef struct {
74     DICT    dict;			/* generic members */
75     CLNT_STREAM *clnt;			/* client handle (shared) */
76     const char *service;		/* service name */
77     int     in_flags;			/* caller-specified flags */
78     VSTRING *result;			/* storage */
79 } DICT_PROXY;
80 
81  /*
82   * SLMs.
83   */
84 #define STR(x)		vstring_str(x)
85 #define VSTREQ(v,s)	(strcmp(STR(v),s) == 0)
86 
87  /*
88   * All proxied maps of the same type share the same query/reply socket.
89   */
90 static CLNT_STREAM *proxymap_stream;	/* read-only maps */
91 static CLNT_STREAM *proxywrite_stream;	/* read-write maps */
92 
93 /* dict_proxy_lookup - find table entry */
94 
95 static const char *dict_proxy_lookup(DICT *dict, const char *key)
96 {
97     const char *myname = "dict_proxy_lookup";
98     DICT_PROXY *dict_proxy = (DICT_PROXY *) dict;
99     VSTREAM *stream;
100     int     status;
101     int     count = 0;
102     int     request_flags;
103 
104     /*
105      * The client and server live in separate processes that may start and
106      * terminate independently. We cannot rely on a persistent connection,
107      * let alone on persistent state (such as a specific open table) that is
108      * associated with a specific connection. Each lookup needs to specify
109      * the table and the flags that were specified to dict_proxy_open().
110      */
111     VSTRING_RESET(dict_proxy->result);
112     VSTRING_TERMINATE(dict_proxy->result);
113     request_flags = (dict_proxy->in_flags & DICT_FLAG_RQST_MASK)
114 	| (dict->flags & DICT_FLAG_RQST_MASK);
115     for (;;) {
116 	stream = clnt_stream_access(dict_proxy->clnt);
117 	errno = 0;
118 	count += 1;
119 	if (attr_print(stream, ATTR_FLAG_NONE,
120 		       ATTR_TYPE_STR, MAIL_ATTR_REQ, PROXY_REQ_LOOKUP,
121 		       ATTR_TYPE_STR, MAIL_ATTR_TABLE, dict->name,
122 		       ATTR_TYPE_INT, MAIL_ATTR_FLAGS, request_flags,
123 		       ATTR_TYPE_STR, MAIL_ATTR_KEY, key,
124 		       ATTR_TYPE_END) != 0
125 	    || vstream_fflush(stream)
126 	    || attr_scan(stream, ATTR_FLAG_STRICT,
127 			 ATTR_TYPE_INT, MAIL_ATTR_STATUS, &status,
128 			 ATTR_TYPE_STR, MAIL_ATTR_VALUE, dict_proxy->result,
129 			 ATTR_TYPE_END) != 2) {
130 	    if (msg_verbose || count > 1 || (errno && errno != EPIPE && errno != ENOENT))
131 		msg_warn("%s: service %s: %m", myname, VSTREAM_PATH(stream));
132 	} else {
133 	    if (msg_verbose)
134 		msg_info("%s: table=%s flags=%s key=%s -> status=%d result=%s",
135 			 myname, dict->name,
136 			 dict_flags_str(request_flags), key,
137 			 status, STR(dict_proxy->result));
138 	    switch (status) {
139 	    case PROXY_STAT_BAD:
140 		msg_fatal("%s lookup failed for table \"%s\" key \"%s\": "
141 			  "invalid request",
142 			  dict_proxy->service, dict->name, key);
143 	    case PROXY_STAT_DENY:
144 		msg_fatal("%s service is not configured for table \"%s\"",
145 			  dict_proxy->service, dict->name);
146 	    case PROXY_STAT_OK:
147 		return (STR(dict_proxy->result));
148 	    case PROXY_STAT_NOKEY:
149 		dict_errno = 0;
150 		return (0);
151 	    case PROXY_STAT_RETRY:
152 		dict_errno = DICT_ERR_RETRY;
153 		return (0);
154 	    default:
155 		msg_warn("%s lookup failed for table \"%s\" key \"%s\": "
156 			 "unexpected reply status %d",
157 			 dict_proxy->service, dict->name, key, status);
158 	    }
159 	}
160 	clnt_stream_recover(dict_proxy->clnt);
161 	sleep(1);				/* XXX make configurable */
162     }
163 }
164 
165 /* dict_proxy_update - update table entry */
166 
167 static void dict_proxy_update(DICT *dict, const char *key, const char *value)
168 {
169     const char *myname = "dict_proxy_update";
170     DICT_PROXY *dict_proxy = (DICT_PROXY *) dict;
171     VSTREAM *stream;
172     int     status;
173     int     count = 0;
174     int     request_flags;
175 
176     /*
177      * The client and server live in separate processes that may start and
178      * terminate independently. We cannot rely on a persistent connection,
179      * let alone on persistent state (such as a specific open table) that is
180      * associated with a specific connection. Each lookup needs to specify
181      * the table and the flags that were specified to dict_proxy_open().
182      */
183     request_flags = (dict_proxy->in_flags & DICT_FLAG_RQST_MASK)
184 	| (dict->flags & DICT_FLAG_RQST_MASK);
185     for (;;) {
186 	stream = clnt_stream_access(dict_proxy->clnt);
187 	errno = 0;
188 	count += 1;
189 	if (attr_print(stream, ATTR_FLAG_NONE,
190 		       ATTR_TYPE_STR, MAIL_ATTR_REQ, PROXY_REQ_UPDATE,
191 		       ATTR_TYPE_STR, MAIL_ATTR_TABLE, dict->name,
192 		       ATTR_TYPE_INT, MAIL_ATTR_FLAGS, request_flags,
193 		       ATTR_TYPE_STR, MAIL_ATTR_KEY, key,
194 		       ATTR_TYPE_STR, MAIL_ATTR_VALUE, value,
195 		       ATTR_TYPE_END) != 0
196 	    || vstream_fflush(stream)
197 	    || attr_scan(stream, ATTR_FLAG_STRICT,
198 			 ATTR_TYPE_INT, MAIL_ATTR_STATUS, &status,
199 			 ATTR_TYPE_END) != 1) {
200 	    if (msg_verbose || count > 1 || (errno && errno != EPIPE && errno != ENOENT))
201 		msg_warn("%s: service %s: %m", myname, VSTREAM_PATH(stream));
202 	} else {
203 	    if (msg_verbose)
204 		msg_info("%s: table=%s flags=%s key=%s value=%s -> status=%d",
205 			 myname, dict->name, dict_flags_str(request_flags),
206 			 key, value, status);
207 	    switch (status) {
208 	    case PROXY_STAT_BAD:
209 		msg_fatal("%s update failed for table \"%s\" key \"%s\": "
210 			  "invalid request",
211 			  dict_proxy->service, dict->name, key);
212 	    case PROXY_STAT_DENY:
213 		msg_fatal("%s update access is not configured for table \"%s\"",
214 			  dict_proxy->service, dict->name);
215 	    case PROXY_STAT_OK:
216 		return;
217 	    default:
218 		msg_warn("%s update failed for table \"%s\" key \"%s\": "
219 			 "unexpected reply status %d",
220 			 dict_proxy->service, dict->name, key, status);
221 	    }
222 	}
223 	clnt_stream_recover(dict_proxy->clnt);
224 	sleep(1);				/* XXX make configurable */
225     }
226 }
227 
228 /* dict_proxy_delete - delete table entry */
229 
230 static int dict_proxy_delete(DICT *dict, const char *key)
231 {
232     const char *myname = "dict_proxy_delete";
233     DICT_PROXY *dict_proxy = (DICT_PROXY *) dict;
234     VSTREAM *stream;
235     int     status;
236     int     count = 0;
237     int     request_flags;
238 
239     /*
240      * The client and server live in separate processes that may start and
241      * terminate independently. We cannot rely on a persistent connection,
242      * let alone on persistent state (such as a specific open table) that is
243      * associated with a specific connection. Each lookup needs to specify
244      * the table and the flags that were specified to dict_proxy_open().
245      */
246     request_flags = (dict_proxy->in_flags & DICT_FLAG_RQST_MASK)
247 	| (dict->flags & DICT_FLAG_RQST_MASK);
248     for (;;) {
249 	stream = clnt_stream_access(dict_proxy->clnt);
250 	errno = 0;
251 	count += 1;
252 	if (attr_print(stream, ATTR_FLAG_NONE,
253 		       ATTR_TYPE_STR, MAIL_ATTR_REQ, PROXY_REQ_DELETE,
254 		       ATTR_TYPE_STR, MAIL_ATTR_TABLE, dict->name,
255 		       ATTR_TYPE_INT, MAIL_ATTR_FLAGS, request_flags,
256 		       ATTR_TYPE_STR, MAIL_ATTR_KEY, key,
257 		       ATTR_TYPE_END) != 0
258 	    || vstream_fflush(stream)
259 	    || attr_scan(stream, ATTR_FLAG_STRICT,
260 			 ATTR_TYPE_INT, MAIL_ATTR_STATUS, &status,
261 			 ATTR_TYPE_END) != 1) {
262 	    if (msg_verbose || count > 1 || (errno && errno != EPIPE && errno !=
263 					     ENOENT))
264 		msg_warn("%s: service %s: %m", myname, VSTREAM_PATH(stream));
265 	} else {
266 	    if (msg_verbose)
267 		msg_info("%s: table=%s flags=%s key=%s -> status=%d",
268 			 myname, dict->name, dict_flags_str(request_flags),
269 			 key, status);
270 	    switch (status) {
271 	    case PROXY_STAT_BAD:
272 		msg_fatal("%s delete failed for table \"%s\" key \"%s\": "
273 			  "invalid request",
274 			  dict_proxy->service, dict->name, key);
275 	    case PROXY_STAT_DENY:
276 		msg_fatal("%s update access is not configured for table \"%s\"",
277 			  dict_proxy->service, dict->name);
278 	    case PROXY_STAT_OK:
279 		return 0;
280 	    case PROXY_STAT_NOKEY:
281 		return 1;
282 	    default:
283 		msg_warn("%s delete failed for table \"%s\" key \"%s\": "
284 			 "unexpected reply status %d",
285 			 dict_proxy->service, dict->name, key, status);
286 	    }
287 	}
288 	clnt_stream_recover(dict_proxy->clnt);
289 	sleep(1);				/* XXX make configurable */
290     }
291 }
292 
293 /* dict_proxy_close - disconnect */
294 
295 static void dict_proxy_close(DICT *dict)
296 {
297     DICT_PROXY *dict_proxy = (DICT_PROXY *) dict;
298 
299     vstring_free(dict_proxy->result);
300     dict_free(dict);
301 }
302 
303 /* dict_proxy_open - open remote map */
304 
305 DICT   *dict_proxy_open(const char *map, int open_flags, int dict_flags)
306 {
307     const char *myname = "dict_proxy_open";
308     DICT_PROXY *dict_proxy;
309     VSTREAM *stream;
310     int     server_flags;
311     int     status;
312     const char *service;
313     char   *relative_path;
314     char   *kludge = 0;
315     char   *prefix;
316     CLNT_STREAM **pstream;
317 
318     /*
319      * If this map can't be proxied then we silently do a direct open. This
320      * allows sites to benefit from proxying the virtual mailbox maps without
321      * unnecessary pain.
322      */
323     if (dict_flags & DICT_FLAG_NO_PROXY)
324 	return (dict_open(map, open_flags, dict_flags));
325 
326     /*
327      * Use a shared stream for proxied table lookups of the same type.
328      *
329      * XXX A complete implementation would also allow O_RDWR without O_CREAT.
330      * But we must not pass on every possible set of flags to the proxy
331      * server; only sets that make sense. For now, the flags are passed
332      * implicitly by choosing between the proxymap or proxywrite service.
333      *
334      * XXX Use absolute pathname to make this work from non-daemon processes.
335      */
336     if (open_flags == O_RDONLY) {
337 	pstream = &proxymap_stream;
338 	service = var_proxymap_service;
339     } else if (open_flags == (O_RDWR | O_CREAT)) {
340 	pstream = &proxywrite_stream;
341 	service = var_proxywrite_service;
342     } else
343 	msg_fatal("%s: %s map open requires O_RDONLY or O_RDWR|O_CREAT mode",
344 		  map, DICT_TYPE_PROXY);
345 
346     if (*pstream == 0) {
347 	relative_path = concatenate(MAIL_CLASS_PRIVATE "/",
348 				    service, (char *) 0);
349 	if (access(relative_path, F_OK) == 0)
350 	    prefix = MAIL_CLASS_PRIVATE;
351 	else
352 	    prefix = kludge = concatenate(var_queue_dir, "/",
353 					  MAIL_CLASS_PRIVATE, (char *) 0);
354 	*pstream = clnt_stream_create(prefix, service, var_ipc_idle_limit,
355 				      var_ipc_ttl_limit);
356 	if (kludge)
357 	    myfree(kludge);
358 	myfree(relative_path);
359     }
360 
361     /*
362      * Local initialization.
363      */
364     dict_proxy = (DICT_PROXY *)
365 	dict_alloc(DICT_TYPE_PROXY, map, sizeof(*dict_proxy));
366     dict_proxy->dict.lookup = dict_proxy_lookup;
367     dict_proxy->dict.update = dict_proxy_update;
368     dict_proxy->dict.delete = dict_proxy_delete;
369     dict_proxy->dict.close = dict_proxy_close;
370     dict_proxy->in_flags = dict_flags;
371     dict_proxy->result = vstring_alloc(10);
372     dict_proxy->clnt = *pstream;
373     dict_proxy->service = service;
374 
375     /*
376      * Establish initial contact and get the map type specific flags.
377      *
378      * XXX Should retrieve flags from local instance.
379      */
380     for (;;) {
381 	stream = clnt_stream_access(dict_proxy->clnt);
382 	errno = 0;
383 	if (attr_print(stream, ATTR_FLAG_NONE,
384 		       ATTR_TYPE_STR, MAIL_ATTR_REQ, PROXY_REQ_OPEN,
385 		       ATTR_TYPE_STR, MAIL_ATTR_TABLE, dict_proxy->dict.name,
386 		       ATTR_TYPE_INT, MAIL_ATTR_FLAGS, dict_proxy->in_flags,
387 		       ATTR_TYPE_END) != 0
388 	    || vstream_fflush(stream)
389 	    || attr_scan(stream, ATTR_FLAG_STRICT,
390 			 ATTR_TYPE_INT, MAIL_ATTR_STATUS, &status,
391 			 ATTR_TYPE_INT, MAIL_ATTR_FLAGS, &server_flags,
392 			 ATTR_TYPE_END) != 2) {
393 	    if (msg_verbose || (errno != EPIPE && errno != ENOENT))
394 		msg_warn("%s: service %s: %m", VSTREAM_PATH(stream), myname);
395 	} else {
396 	    if (msg_verbose)
397 		msg_info("%s: connect to map=%s status=%d server_flags=%s",
398 			 myname, dict_proxy->dict.name, status,
399 			 dict_flags_str(server_flags));
400 	    switch (status) {
401 	    case PROXY_STAT_BAD:
402 		msg_fatal("%s open failed for table \"%s\": invalid request",
403 			  dict_proxy->service, dict_proxy->dict.name);
404 	    case PROXY_STAT_DENY:
405 		msg_fatal("%s service is not configured for table \"%s\"",
406 			  dict_proxy->service, dict_proxy->dict.name);
407 	    case PROXY_STAT_OK:
408 		dict_proxy->dict.flags = dict_proxy->in_flags
409 		    | (server_flags & DICT_FLAG_IMPL_MASK);
410 		return (DICT_DEBUG (&dict_proxy->dict));
411 	    default:
412 		msg_warn("%s open failed for table \"%s\": unexpected status %d",
413 			 dict_proxy->service, dict_proxy->dict.name, status);
414 	    }
415 	}
416 	clnt_stream_recover(dict_proxy->clnt);
417 	sleep(1);				/* XXX make configurable */
418     }
419 }
420