xref: /netbsd-src/external/ibm-public/postfix/dist/src/global/dict_proxy.c (revision 67b9b338a7386232ac596b5fd0cd5a9cc8a03c71)
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