1/* $NetBSD$ */ 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 73typedef 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 */ 90static CLNT_STREAM *proxymap_stream; /* read-only maps */ 91static CLNT_STREAM *proxywrite_stream; /* read-write maps */ 92 93/* dict_proxy_lookup - find table entry */ 94 95static 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 167static 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 230static 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 295static 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 305DICT *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