7 /* generic dictionary proxy client
9 /* #include <dict_proxy.h>
11 /* DICT *dict_proxy_open(map, open_flags, dict_flags)
16 /* dict_proxy_open() relays read-only operations through
17 /* the Postfix proxymap server.
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.
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.
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.
32 /* dict(3) generic dictionary manager
33 /* clnt_stream(3) client endpoint connection management
35 /* Fatal errors: out of memory, unimplemented operation,
36 /* bad request parameter, map not approved for proxy access.
40 /* The Secure Mailer license must be distributed with this software.
43 /* IBM T.J. Watson Research
45 /* Yorktown Heights, NY 10598, USA
54 /* Utility library. */
58 #include <stringops.h>
66 #include <mail_proto.h>
67 #include <mail_params.h>
68 #include <clnt_stream.h>
69 #include <dict_proxy.h>
71 /* Application-specific. */
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 */
84 #define STR(x) vstring_str(x)
85 #define VSTREQ(v,s) (strcmp(STR(v),s) == 0)
88 * All proxied maps of the same type share the same query/reply socket.
90 static CLNT_STREAM
*proxymap_stream
; /* read-only maps */
91 static CLNT_STREAM
*proxywrite_stream
; /* read-write maps */
93 /* dict_proxy_lookup - find table entry */
95 static const char *dict_proxy_lookup(DICT
*dict
, const char *key
)
97 const char *myname
= "dict_proxy_lookup";
98 DICT_PROXY
*dict_proxy
= (DICT_PROXY
*) dict
;
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().
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
);
116 stream
= clnt_stream_access(dict_proxy
->clnt
);
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
,
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
));
134 msg_info("%s: table=%s flags=%s key=%s -> status=%d result=%s",
136 dict_flags_str(request_flags
), key
,
137 status
, STR(dict_proxy
->result
));
140 msg_fatal("%s lookup failed for table \"%s\" key \"%s\": "
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
);
147 return (STR(dict_proxy
->result
));
148 case PROXY_STAT_NOKEY
:
151 case PROXY_STAT_RETRY
:
152 dict_errno
= DICT_ERR_RETRY
;
155 msg_warn("%s lookup failed for table \"%s\" key \"%s\": "
156 "unexpected reply status %d",
157 dict_proxy
->service
, dict
->name
, key
, status
);
160 clnt_stream_recover(dict_proxy
->clnt
);
161 sleep(1); /* XXX make configurable */
165 /* dict_proxy_update - update table entry */
167 static void dict_proxy_update(DICT
*dict
, const char *key
, const char *value
)
169 const char *myname
= "dict_proxy_update";
170 DICT_PROXY
*dict_proxy
= (DICT_PROXY
*) dict
;
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().
183 request_flags
= (dict_proxy
->in_flags
& DICT_FLAG_RQST_MASK
)
184 | (dict
->flags
& DICT_FLAG_RQST_MASK
);
186 stream
= clnt_stream_access(dict_proxy
->clnt
);
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
,
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
));
204 msg_info("%s: table=%s flags=%s key=%s value=%s -> status=%d",
205 myname
, dict
->name
, dict_flags_str(request_flags
),
209 msg_fatal("%s update failed for table \"%s\" key \"%s\": "
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
);
218 msg_warn("%s update failed for table \"%s\" key \"%s\": "
219 "unexpected reply status %d",
220 dict_proxy
->service
, dict
->name
, key
, status
);
223 clnt_stream_recover(dict_proxy
->clnt
);
224 sleep(1); /* XXX make configurable */
228 /* dict_proxy_delete - delete table entry */
230 static int dict_proxy_delete(DICT
*dict
, const char *key
)
232 const char *myname
= "dict_proxy_delete";
233 DICT_PROXY
*dict_proxy
= (DICT_PROXY
*) dict
;
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().
246 request_flags
= (dict_proxy
->in_flags
& DICT_FLAG_RQST_MASK
)
247 | (dict
->flags
& DICT_FLAG_RQST_MASK
);
249 stream
= clnt_stream_access(dict_proxy
->clnt
);
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
,
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
!=
264 msg_warn("%s: service %s: %m", myname
, VSTREAM_PATH(stream
));
267 msg_info("%s: table=%s flags=%s key=%s -> status=%d",
268 myname
, dict
->name
, dict_flags_str(request_flags
),
272 msg_fatal("%s delete failed for table \"%s\" key \"%s\": "
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
);
280 case PROXY_STAT_NOKEY
:
283 msg_warn("%s delete failed for table \"%s\" key \"%s\": "
284 "unexpected reply status %d",
285 dict_proxy
->service
, dict
->name
, key
, status
);
288 clnt_stream_recover(dict_proxy
->clnt
);
289 sleep(1); /* XXX make configurable */
293 /* dict_proxy_close - disconnect */
295 static void dict_proxy_close(DICT
*dict
)
297 DICT_PROXY
*dict_proxy
= (DICT_PROXY
*) dict
;
299 vstring_free(dict_proxy
->result
);
303 /* dict_proxy_open - open remote map */
305 DICT
*dict_proxy_open(const char *map
, int open_flags
, int dict_flags
)
307 const char *myname
= "dict_proxy_open";
308 DICT_PROXY
*dict_proxy
;
316 CLNT_STREAM
**pstream
;
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
323 if (dict_flags
& DICT_FLAG_NO_PROXY
)
324 return (dict_open(map
, open_flags
, dict_flags
));
327 * Use a shared stream for proxied table lookups of the same type.
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.
334 * XXX Use absolute pathname to make this work from non-daemon processes.
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
;
343 msg_fatal("%s: %s map open requires O_RDONLY or O_RDWR|O_CREAT mode",
344 map
, DICT_TYPE_PROXY
);
347 relative_path
= concatenate(MAIL_CLASS_PRIVATE
"/",
348 service
, (char *) 0);
349 if (access(relative_path
, F_OK
) == 0)
350 prefix
= MAIL_CLASS_PRIVATE
;
352 prefix
= kludge
= concatenate(var_queue_dir
, "/",
353 MAIL_CLASS_PRIVATE
, (char *) 0);
354 *pstream
= clnt_stream_create(prefix
, service
, var_ipc_idle_limit
,
358 myfree(relative_path
);
362 * Local initialization.
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
;
376 * Establish initial contact and get the map type specific flags.
378 * XXX Should retrieve flags from local instance.
381 stream
= clnt_stream_access(dict_proxy
->clnt
);
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
,
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
);
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
));
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
);
408 dict_proxy
->dict
.flags
= dict_proxy
->in_flags
409 | (server_flags
& DICT_FLAG_IMPL_MASK
);
410 return (DICT_DEBUG (&dict_proxy
->dict
));
412 msg_warn("%s open failed for table \"%s\": unexpected status %d",
413 dict_proxy
->service
, dict_proxy
->dict
.name
, status
);
416 clnt_stream_recover(dict_proxy
->clnt
);
417 sleep(1); /* XXX make configurable */