1/*++ 2/* NAME 3/* postmap 1 4/* SUMMARY 5/* Postfix lookup table management 6/* SYNOPSIS 7/* .fi 8/* \fBpostmap\fR [\fB-Nbfhimnoprsvw\fR] [\fB-c \fIconfig_dir\fR] 9/* [\fB-d \fIkey\fR] [\fB-q \fIkey\fR] 10/* [\fIfile_type\fR:]\fIfile_name\fR ... 11/* DESCRIPTION 12/* The \fBpostmap\fR(1) command creates or queries one or more Postfix 13/* lookup tables, or updates an existing one. The input and output 14/* file formats are expected to be compatible with: 15/* 16/* .nf 17/* \fBmakemap \fIfile_type\fR \fIfile_name\fR < \fIfile_name\fR 18/* .fi 19/* 20/* If the result files do not exist they will be created with the 21/* same group and other read permissions as their source file. 22/* 23/* While the table update is in progress, signal delivery is 24/* postponed, and an exclusive, advisory, lock is placed on the 25/* entire table, in order to avoid surprises in spectator 26/* processes. 27/* INPUT FILE FORMAT 28/* .ad 29/* .fi 30/* The format of a lookup table input file is as follows: 31/* .IP \(bu 32/* A table entry has the form 33/* .sp 34/* .nf 35/* \fIkey\fR whitespace \fIvalue\fR 36/* .fi 37/* .IP \(bu 38/* Empty lines and whitespace-only lines are ignored, as 39/* are lines whose first non-whitespace character is a `#'. 40/* .IP \(bu 41/* A logical line starts with non-whitespace text. A line that 42/* starts with whitespace continues a logical line. 43/* .PP 44/* The \fIkey\fR and \fIvalue\fR are processed as is, except that 45/* surrounding white space is stripped off. Unlike with Postfix alias 46/* databases, quotes cannot be used to protect lookup keys that contain 47/* special characters such as `#' or whitespace. 48/* 49/* By default the lookup key is mapped to lowercase to make 50/* the lookups case insensitive; as of Postfix 2.3 this case 51/* folding happens only with tables whose lookup keys are 52/* fixed-case strings such as btree:, dbm: or hash:. With 53/* earlier versions, the lookup key is folded even with tables 54/* where a lookup field can match both upper and lower case 55/* text, such as regexp: and pcre:. This resulted in loss of 56/* information with $\fInumber\fR substitutions. 57/* COMMAND-LINE ARGUMENTS 58/* .ad 59/* .fi 60/* .IP \fB-b\fR 61/* Enable message body query mode. When reading lookup keys 62/* from standard input with "\fB-q -\fR", process the input 63/* as if it is an email message in RFC 2822 format. Each line 64/* of body content becomes one lookup key. 65/* .sp 66/* By default, the \fB-b\fR option starts generating lookup 67/* keys at the first non-header line, and stops when the end 68/* of the message is reached. 69/* To simulate \fBbody_checks\fR(5) processing, enable MIME 70/* parsing with \fB-m\fR. With this, the \fB-b\fR option 71/* generates no body-style lookup keys for attachment MIME 72/* headers and for attached message/* headers. 73/* .sp 74/* This feature is available in Postfix version 2.6 and later. 75/* .IP "\fB-c \fIconfig_dir\fR" 76/* Read the \fBmain.cf\fR configuration file in the named directory 77/* instead of the default configuration directory. 78/* .IP "\fB-d \fIkey\fR" 79/* Search the specified maps for \fIkey\fR and remove one entry per map. 80/* The exit status is zero when the requested information was found. 81/* 82/* If a key value of \fB-\fR is specified, the program reads key 83/* values from the standard input stream. The exit status is zero 84/* when at least one of the requested keys was found. 85/* .IP \fB-f\fR 86/* Do not fold the lookup key to lower case while creating or querying 87/* a table. 88/* 89/* With Postfix version 2.3 and later, this option has no 90/* effect for regular expression tables. There, case folding 91/* is controlled by appending a flag to a pattern. 92/* .IP \fB-h\fR 93/* Enable message header query mode. When reading lookup keys 94/* from standard input with "\fB-q -\fR", process the input 95/* as if it is an email message in RFC 2822 format. Each 96/* logical header line becomes one lookup key. A multi-line 97/* header becomes one lookup key with one or more embedded 98/* newline characters. 99/* .sp 100/* By default, the \fB-h\fR option generates lookup keys until 101/* the first non-header line is reached. 102/* To simulate \fBheader_checks\fR(5) processing, enable MIME 103/* parsing with \fB-m\fR. With this, the \fB-h\fR option also 104/* generates header-style lookup keys for attachment MIME 105/* headers and for attached message/* headers. 106/* .sp 107/* This feature is available in Postfix version 2.6 and later. 108/* .IP \fB-i\fR 109/* Incremental mode. Read entries from standard input and do not 110/* truncate an existing database. By default, \fBpostmap\fR(1) creates 111/* a new database from the entries in \fBfile_name\fR. 112/* .IP \fB-m\fR 113/* Enable MIME parsing with "\fB-b\fR" and "\fB-h\fR". 114/* .sp 115/* This feature is available in Postfix version 2.6 and later. 116/* .IP \fB-N\fR 117/* Include the terminating null character that terminates lookup keys 118/* and values. By default, \fBpostmap\fR(1) does whatever is 119/* the default for 120/* the host operating system. 121/* .IP \fB-n\fR 122/* Don't include the terminating null character that terminates lookup 123/* keys and values. By default, \fBpostmap\fR(1) does whatever 124/* is the default for 125/* the host operating system. 126/* .IP \fB-o\fR 127/* Do not release root privileges when processing a non-root 128/* input file. By default, \fBpostmap\fR(1) drops root privileges 129/* and runs as the source file owner instead. 130/* .IP \fB-p\fR 131/* Do not inherit the file access permissions from the input file 132/* when creating a new file. Instead, create a new file with default 133/* access permissions (mode 0644). 134/* .IP "\fB-q \fIkey\fR" 135/* Search the specified maps for \fIkey\fR and write the first value 136/* found to the standard output stream. The exit status is zero 137/* when the requested information was found. 138/* 139/* If a key value of \fB-\fR is specified, the program reads key 140/* values from the standard input stream and writes one line of 141/* \fIkey value\fR output for each key that was found. The exit 142/* status is zero when at least one of the requested keys was found. 143/* .IP \fB-r\fR 144/* When updating a table, do not complain about attempts to update 145/* existing entries, and make those updates anyway. 146/* .IP \fB-s\fR 147/* Retrieve all database elements, and write one line of 148/* \fIkey value\fR output for each element. The elements are 149/* printed in database order, which is not necessarily the same 150/* as the original input order. 151/* .sp 152/* This feature is available in Postfix version 2.2 and later, 153/* and is not available for all database types. 154/* .IP \fB-v\fR 155/* Enable verbose logging for debugging purposes. Multiple \fB-v\fR 156/* options make the software increasingly verbose. 157/* .IP \fB-w\fR 158/* When updating a table, do not complain about attempts to update 159/* existing entries, and ignore those attempts. 160/* .PP 161/* Arguments: 162/* .IP \fIfile_type\fR 163/* The database type. To find out what types are supported, use 164/* the "\fBpostconf -m\fR" command. 165/* 166/* The \fBpostmap\fR(1) command can query any supported file type, 167/* but it can create only the following file types: 168/* .RS 169/* .IP \fBbtree\fR 170/* The output file is a btree file, named \fIfile_name\fB.db\fR. 171/* This is available on systems with support for \fBdb\fR databases. 172/* .IP \fBcdb\fR 173/* The output consists of one file, named \fIfile_name\fB.cdb\fR. 174/* This is available on systems with support for \fBcdb\fR databases. 175/* .IP \fBdbm\fR 176/* The output consists of two files, named \fIfile_name\fB.pag\fR and 177/* \fIfile_name\fB.dir\fR. 178/* This is available on systems with support for \fBdbm\fR databases. 179/* .IP \fBhash\fR 180/* The output file is a hashed file, named \fIfile_name\fB.db\fR. 181/* This is available on systems with support for \fBdb\fR databases. 182/* .IP \fBfail\fR 183/* A table that reliably fails all requests. The lookup table 184/* name is used for logging only. This table exists to simplify 185/* Postfix error tests. 186/* .IP \fBsdbm\fR 187/* The output consists of two files, named \fIfile_name\fB.pag\fR and 188/* \fIfile_name\fB.dir\fR. 189/* This is available on systems with support for \fBsdbm\fR databases. 190/* .PP 191/* When no \fIfile_type\fR is specified, the software uses the database 192/* type specified via the \fBdefault_database_type\fR configuration 193/* parameter. 194/* .RE 195/* .IP \fIfile_name\fR 196/* The name of the lookup table source file when rebuilding a database. 197/* DIAGNOSTICS 198/* Problems are logged to the standard error stream and to 199/* \fBsyslogd\fR(8). 200/* No output means that no problems were detected. Duplicate entries are 201/* skipped and are flagged with a warning. 202/* 203/* \fBpostmap\fR(1) terminates with zero exit status in case of success 204/* (including successful "\fBpostmap -q\fR" lookup) and terminates 205/* with non-zero exit status in case of failure. 206/* ENVIRONMENT 207/* .ad 208/* .fi 209/* .IP \fBMAIL_CONFIG\fR 210/* Directory with Postfix configuration files. 211/* .IP \fBMAIL_VERBOSE\fR 212/* Enable verbose logging for debugging purposes. 213/* CONFIGURATION PARAMETERS 214/* .ad 215/* .fi 216/* The following \fBmain.cf\fR parameters are especially relevant to 217/* this program. 218/* The text below provides only a parameter summary. See 219/* \fBpostconf\fR(5) for more details including examples. 220/* .IP "\fBberkeley_db_create_buffer_size (16777216)\fR" 221/* The per-table I/O buffer size for programs that create Berkeley DB 222/* hash or btree tables. 223/* .IP "\fBberkeley_db_read_buffer_size (131072)\fR" 224/* The per-table I/O buffer size for programs that read Berkeley DB 225/* hash or btree tables. 226/* .IP "\fBconfig_directory (see 'postconf -d' output)\fR" 227/* The default location of the Postfix main.cf and master.cf 228/* configuration files. 229/* .IP "\fBdefault_database_type (see 'postconf -d' output)\fR" 230/* The default database type for use in \fBnewaliases\fR(1), \fBpostalias\fR(1) 231/* and \fBpostmap\fR(1) commands. 232/* .IP "\fBsyslog_facility (mail)\fR" 233/* The syslog facility of Postfix logging. 234/* .IP "\fBsyslog_name (see 'postconf -d' output)\fR" 235/* The mail system name that is prepended to the process name in syslog 236/* records, so that "smtpd" becomes, for example, "postfix/smtpd". 237/* SEE ALSO 238/* postalias(1), create/update/query alias database 239/* postconf(1), supported database types 240/* postconf(5), configuration parameters 241/* syslogd(8), system logging 242/* README FILES 243/* .ad 244/* .fi 245/* Use "\fBpostconf readme_directory\fR" or 246/* "\fBpostconf html_directory\fR" to locate this information. 247/* .na 248/* .nf 249/* DATABASE_README, Postfix lookup table overview 250/* LICENSE 251/* .ad 252/* .fi 253/* The Secure Mailer license must be distributed with this software. 254/* AUTHOR(S) 255/* Wietse Venema 256/* IBM T.J. Watson Research 257/* P.O. Box 704 258/* Yorktown Heights, NY 10598, USA 259/*--*/ 260 261/* System library. */ 262 263#include <sys_defs.h> 264#include <sys/stat.h> 265#include <stdlib.h> 266#include <unistd.h> 267#include <fcntl.h> 268#include <ctype.h> 269#include <string.h> 270 271/* Utility library. */ 272 273#include <msg.h> 274#include <mymalloc.h> 275#include <vstring.h> 276#include <vstream.h> 277#include <msg_vstream.h> 278#include <msg_syslog.h> 279#include <readlline.h> 280#include <stringops.h> 281#include <split_at.h> 282#include <vstring_vstream.h> 283#include <set_eugid.h> 284#include <warn_stat.h> 285 286/* Global library. */ 287 288#include <mail_conf.h> 289#include <mail_dict.h> 290#include <mail_params.h> 291#include <mail_version.h> 292#include <mkmap.h> 293#include <mail_task.h> 294#include <dict_proxy.h> 295#include <mime_state.h> 296#include <rec_type.h> 297 298/* Application-specific. */ 299 300#define STR vstring_str 301#define LEN VSTRING_LEN 302 303#define POSTMAP_FLAG_AS_OWNER (1<<0) /* open dest as owner of source */ 304#define POSTMAP_FLAG_SAVE_PERM (1<<1) /* copy access permission from source */ 305#define POSTMAP_FLAG_HEADER_KEY (1<<2) /* apply to header text */ 306#define POSTMAP_FLAG_BODY_KEY (1<<3) /* apply to body text */ 307#define POSTMAP_FLAG_MIME_KEY (1<<4) /* enable MIME parsing */ 308 309#define POSTMAP_FLAG_HB_KEY (POSTMAP_FLAG_HEADER_KEY | POSTMAP_FLAG_BODY_KEY) 310#define POSTMAP_FLAG_FULL_KEY (POSTMAP_FLAG_BODY_KEY | POSTMAP_FLAG_MIME_KEY) 311#define POSTMAP_FLAG_ANY_KEY (POSTMAP_FLAG_HB_KEY | POSTMAP_FLAG_MIME_KEY) 312 313 /* 314 * MIME Engine call-back state for generating lookup keys from an email 315 * message read from standard input. 316 */ 317typedef struct { 318 DICT **dicts; /* map handles */ 319 char **maps; /* map names */ 320 int map_count; /* yes, indeed */ 321 int dict_flags; /* query flags */ 322 int header_done; /* past primary header */ 323 int found; /* result */ 324} POSTMAP_KEY_STATE; 325 326/* postmap - create or update mapping database */ 327 328static void postmap(char *map_type, char *path_name, int postmap_flags, 329 int open_flags, int dict_flags) 330{ 331 VSTREAM *NOCLOBBER source_fp; 332 VSTRING *line_buffer; 333 MKMAP *mkmap; 334 int lineno; 335 char *key; 336 char *value; 337 struct stat st; 338 mode_t saved_mask; 339 340 /* 341 * Initialize. 342 */ 343 line_buffer = vstring_alloc(100); 344 if ((open_flags & O_TRUNC) == 0) { 345 /* Incremental mode. */ 346 source_fp = VSTREAM_IN; 347 vstream_control(source_fp, VSTREAM_CTL_PATH, "stdin", VSTREAM_CTL_END); 348 } else { 349 /* Create database. */ 350 if (strcmp(map_type, DICT_TYPE_PROXY) == 0) 351 msg_fatal("can't create maps via the proxy service"); 352 dict_flags |= DICT_FLAG_BULK_UPDATE; 353 if ((source_fp = vstream_fopen(path_name, O_RDONLY, 0)) == 0) 354 msg_fatal("open %s: %m", path_name); 355 } 356 if (fstat(vstream_fileno(source_fp), &st) < 0) 357 msg_fatal("fstat %s: %m", path_name); 358 359 /* 360 * Turn off group/other read permissions as indicated in the source file. 361 */ 362 if ((postmap_flags & POSTMAP_FLAG_SAVE_PERM) && S_ISREG(st.st_mode)) 363 saved_mask = umask(022 | (~st.st_mode & 077)); 364 365 /* 366 * If running as root, run as the owner of the source file, so that the 367 * result shows proper ownership, and so that a bug in postmap does not 368 * allow privilege escalation. 369 */ 370 if ((postmap_flags & POSTMAP_FLAG_AS_OWNER) && getuid() == 0 371 && (st.st_uid != geteuid() || st.st_gid != getegid())) 372 set_eugid(st.st_uid, st.st_gid); 373 374 /* 375 * Open the database, optionally create it when it does not exist, 376 * optionally truncate it when it does exist, and lock out any 377 * spectators. 378 */ 379 mkmap = mkmap_open(map_type, path_name, open_flags, dict_flags); 380 381 /* 382 * And restore the umask, in case it matters. 383 */ 384 if ((postmap_flags & POSTMAP_FLAG_SAVE_PERM) && S_ISREG(st.st_mode)) 385 umask(saved_mask); 386 387 /* 388 * Trap "exceptions" so that we can restart a bulk-mode update after a 389 * recoverable error. 390 */ 391 for (;;) { 392 if (dict_isjmp(mkmap->dict) != 0 393 && dict_setjmp(mkmap->dict) != 0 394 && vstream_fseek(source_fp, SEEK_SET, 0) < 0) 395 msg_fatal("seek %s: %m", VSTREAM_PATH(source_fp)); 396 397 /* 398 * Add records to the database. 399 */ 400 lineno = 0; 401 while (readlline(line_buffer, source_fp, &lineno)) { 402 403 /* 404 * Split on the first whitespace character, then trim leading and 405 * trailing whitespace from key and value. 406 */ 407 key = STR(line_buffer); 408 value = key + strcspn(key, " \t\r\n"); 409 if (*value) 410 *value++ = 0; 411 while (ISSPACE(*value)) 412 value++; 413 trimblanks(key, 0)[0] = 0; 414 trimblanks(value, 0)[0] = 0; 415 416 /* 417 * Enforce the "key whitespace value" format. Disallow missing 418 * keys or missing values. 419 */ 420 if (*key == 0 || *value == 0) { 421 msg_warn("%s, line %d: expected format: key whitespace value", 422 VSTREAM_PATH(source_fp), lineno); 423 continue; 424 } 425 if (key[strlen(key) - 1] == ':') 426 msg_warn("%s, line %d: record is in \"key: value\" format; is this an alias file?", 427 VSTREAM_PATH(source_fp), lineno); 428 429 /* 430 * Store the value under a case-insensitive key. 431 */ 432 mkmap_append(mkmap, key, value); 433 if (mkmap->dict->error) 434 msg_fatal("table %s:%s: write error: %m", 435 mkmap->dict->type, mkmap->dict->name); 436 } 437 break; 438 } 439 440 /* 441 * Close the mapping database, and release the lock. 442 */ 443 mkmap_close(mkmap); 444 445 /* 446 * Cleanup. We're about to terminate, but it is a good sanity check. 447 */ 448 vstring_free(line_buffer); 449 if (source_fp != VSTREAM_IN) 450 vstream_fclose(source_fp); 451} 452 453/* postmap_body - MIME engine body call-back routine */ 454 455static void postmap_body(void *ptr, int unused_rec_type, 456 const char *keybuf, 457 ssize_t unused_len, 458 off_t unused_offset) 459{ 460 POSTMAP_KEY_STATE *state = (POSTMAP_KEY_STATE *) ptr; 461 DICT **dicts = state->dicts; 462 char **maps = state->maps; 463 int map_count = state->map_count; 464 int dict_flags = state->dict_flags; 465 const char *map_name; 466 const char *value; 467 int n; 468 469 for (n = 0; n < map_count; n++) { 470 if (dicts[n] == 0) 471 dicts[n] = ((map_name = split_at(maps[n], ':')) != 0 ? 472 dict_open3(maps[n], map_name, O_RDONLY, dict_flags) : 473 dict_open3(var_db_type, maps[n], O_RDONLY, dict_flags)); 474 if ((value = dict_get(dicts[n], keybuf)) != 0) { 475 if (*value == 0) { 476 msg_warn("table %s:%s: key %s: empty string result is not allowed", 477 dicts[n]->type, dicts[n]->name, keybuf); 478 msg_warn("table %s:%s should return NO RESULT in case of NOT FOUND", 479 dicts[n]->type, dicts[n]->name); 480 } 481 vstream_printf("%s %s\n", keybuf, value); 482 state->found = 1; 483 break; 484 } 485 if (dicts[n]->error) 486 msg_fatal("table %s:%s: query error: %m", 487 dicts[n]->type, dicts[n]->name); 488 } 489} 490 491/* postmap_header - MIME engine header call-back routine */ 492 493static void postmap_header(void *ptr, int unused_header_class, 494 const HEADER_OPTS *unused_header_info, 495 VSTRING *header_buf, 496 off_t offset) 497{ 498 499 /* 500 * Don't re-invent an already working wheel. 501 */ 502 postmap_body(ptr, 0, STR(header_buf), LEN(header_buf), offset); 503} 504 505/* postmap_head_end - MIME engine end-of-header call-back routine */ 506 507static void postmap_head_end(void *ptr) 508{ 509 POSTMAP_KEY_STATE *state = (POSTMAP_KEY_STATE *) ptr; 510 511 /* 512 * Don't process the message body when we only examine primary headers. 513 */ 514 state->header_done = 1; 515} 516 517/* postmap_queries - apply multiple requests from stdin */ 518 519static int postmap_queries(VSTREAM *in, char **maps, const int map_count, 520 const int postmap_flags, 521 const int dict_flags) 522{ 523 int found = 0; 524 VSTRING *keybuf = vstring_alloc(100); 525 DICT **dicts; 526 const char *map_name; 527 const char *value; 528 int n; 529 530 /* 531 * Sanity check. 532 */ 533 if (map_count <= 0) 534 msg_panic("postmap_queries: bad map count"); 535 536 /* 537 * Prepare to open maps lazily. 538 */ 539 dicts = (DICT **) mymalloc(sizeof(*dicts) * map_count); 540 for (n = 0; n < map_count; n++) 541 dicts[n] = 0; 542 543 /* 544 * Perform all queries. Open maps on the fly, to avoid opening unecessary 545 * maps. 546 */ 547 if ((postmap_flags & POSTMAP_FLAG_HB_KEY) == 0) { 548 while (vstring_get_nonl(keybuf, in) != VSTREAM_EOF) { 549 for (n = 0; n < map_count; n++) { 550 if (dicts[n] == 0) 551 dicts[n] = ((map_name = split_at(maps[n], ':')) != 0 ? 552 dict_open3(maps[n], map_name, O_RDONLY, dict_flags) : 553 dict_open3(var_db_type, maps[n], O_RDONLY, dict_flags)); 554 if ((value = dict_get(dicts[n], STR(keybuf))) != 0) { 555 if (*value == 0) { 556 msg_warn("table %s:%s: key %s: empty string result is not allowed", 557 dicts[n]->type, dicts[n]->name, STR(keybuf)); 558 msg_warn("table %s:%s should return NO RESULT in case of NOT FOUND", 559 dicts[n]->type, dicts[n]->name); 560 } 561 vstream_printf("%s %s\n", STR(keybuf), value); 562 found = 1; 563 break; 564 } 565 if (dicts[n]->error) 566 msg_fatal("table %s:%s: query error: %m", 567 dicts[n]->type, dicts[n]->name); 568 } 569 } 570 } else { 571 POSTMAP_KEY_STATE key_state; 572 MIME_STATE *mime_state; 573 int mime_errs = 0; 574 575 /* 576 * Bundle up the request and instantiate a MIME parsing engine. 577 */ 578 key_state.dicts = dicts; 579 key_state.maps = maps; 580 key_state.map_count = map_count; 581 key_state.dict_flags = dict_flags; 582 key_state.header_done = 0; 583 key_state.found = 0; 584 mime_state = 585 mime_state_alloc((postmap_flags & POSTMAP_FLAG_MIME_KEY) ? 586 0 : MIME_OPT_DISABLE_MIME, 587 (postmap_flags & POSTMAP_FLAG_HEADER_KEY) ? 588 postmap_header : (MIME_STATE_HEAD_OUT) 0, 589 (postmap_flags & POSTMAP_FLAG_FULL_KEY) ? 590 (MIME_STATE_ANY_END) 0 : postmap_head_end, 591 (postmap_flags & POSTMAP_FLAG_BODY_KEY) ? 592 postmap_body : (MIME_STATE_BODY_OUT) 0, 593 (MIME_STATE_ANY_END) 0, 594 (MIME_STATE_ERR_PRINT) 0, 595 (void *) &key_state); 596 597 /* 598 * Process the input message. 599 */ 600 while (vstring_get_nonl(keybuf, in) != VSTREAM_EOF 601 && key_state.header_done == 0 && mime_errs == 0) 602 mime_errs = mime_state_update(mime_state, REC_TYPE_NORM, 603 STR(keybuf), LEN(keybuf)); 604 605 /* 606 * Flush the MIME engine output buffer and tidy up loose ends. 607 */ 608 if (mime_errs == 0) 609 mime_errs = mime_state_update(mime_state, REC_TYPE_END, "", 0); 610 if (mime_errs) 611 msg_fatal("message format error: %s", 612 mime_state_detail(mime_errs)->text); 613 mime_state_free(mime_state); 614 found = key_state.found; 615 } 616 if (found) 617 vstream_fflush(VSTREAM_OUT); 618 619 /* 620 * Cleanup. 621 */ 622 for (n = 0; n < map_count; n++) 623 if (dicts[n]) 624 dict_close(dicts[n]); 625 myfree((char *) dicts); 626 vstring_free(keybuf); 627 628 return (found); 629} 630 631/* postmap_query - query a map and print the result to stdout */ 632 633static int postmap_query(const char *map_type, const char *map_name, 634 const char *key, int dict_flags) 635{ 636 DICT *dict; 637 const char *value; 638 639 dict = dict_open3(map_type, map_name, O_RDONLY, dict_flags); 640 if ((value = dict_get(dict, key)) != 0) { 641 if (*value == 0) { 642 msg_warn("table %s:%s: key %s: empty string result is not allowed", 643 map_type, map_name, key); 644 msg_warn("table %s:%s should return NO RESULT in case of NOT FOUND", 645 map_type, map_name); 646 } 647 vstream_printf("%s\n", value); 648 } 649 if (dict->error) 650 msg_fatal("table %s:%s: query error: %m", dict->type, dict->name); 651 vstream_fflush(VSTREAM_OUT); 652 dict_close(dict); 653 return (value != 0); 654} 655 656/* postmap_deletes - apply multiple requests from stdin */ 657 658static int postmap_deletes(VSTREAM *in, char **maps, const int map_count, 659 int dict_flags) 660{ 661 int found = 0; 662 VSTRING *keybuf = vstring_alloc(100); 663 DICT **dicts; 664 const char *map_name; 665 int n; 666 int open_flags; 667 668 /* 669 * Sanity check. 670 */ 671 if (map_count <= 0) 672 msg_panic("postmap_deletes: bad map count"); 673 674 /* 675 * Open maps ahead of time. 676 */ 677 dicts = (DICT **) mymalloc(sizeof(*dicts) * map_count); 678 for (n = 0; n < map_count; n++) { 679 map_name = split_at(maps[n], ':'); 680 if (map_name && strcmp(maps[n], DICT_TYPE_PROXY) == 0) 681 open_flags = O_RDWR | O_CREAT; /* XXX */ 682 else 683 open_flags = O_RDWR; 684 dicts[n] = (map_name != 0 ? 685 dict_open3(maps[n], map_name, open_flags, dict_flags) : 686 dict_open3(var_db_type, maps[n], open_flags, dict_flags)); 687 } 688 689 /* 690 * Perform all requests. 691 */ 692 while (vstring_get_nonl(keybuf, in) != VSTREAM_EOF) { 693 for (n = 0; n < map_count; n++) { 694 found |= (dict_del(dicts[n], STR(keybuf)) == 0); 695 if (dicts[n]->error) 696 msg_fatal("table %s:%s: delete error: %m", 697 dicts[n]->type, dicts[n]->name); 698 } 699 } 700 701 /* 702 * Cleanup. 703 */ 704 for (n = 0; n < map_count; n++) 705 if (dicts[n]) 706 dict_close(dicts[n]); 707 myfree((char *) dicts); 708 vstring_free(keybuf); 709 710 return (found); 711} 712 713/* postmap_delete - delete a (key, value) pair from a map */ 714 715static int postmap_delete(const char *map_type, const char *map_name, 716 const char *key, int dict_flags) 717{ 718 DICT *dict; 719 int status; 720 int open_flags; 721 722 if (strcmp(map_type, DICT_TYPE_PROXY) == 0) 723 open_flags = O_RDWR | O_CREAT; /* XXX */ 724 else 725 open_flags = O_RDWR; 726 dict = dict_open3(map_type, map_name, open_flags, dict_flags); 727 status = dict_del(dict, key); 728 if (dict->error) 729 msg_fatal("table %s:%s: delete error: %m", dict->type, dict->name); 730 dict_close(dict); 731 return (status == 0); 732} 733 734/* postmap_seq - print all map entries to stdout */ 735 736static void postmap_seq(const char *map_type, const char *map_name, 737 int dict_flags) 738{ 739 DICT *dict; 740 const char *key; 741 const char *value; 742 int func; 743 744 if (strcmp(map_type, DICT_TYPE_PROXY) == 0) 745 msg_fatal("can't sequence maps via the proxy service"); 746 dict = dict_open3(map_type, map_name, O_RDONLY, dict_flags); 747 for (func = DICT_SEQ_FUN_FIRST; /* void */ ; func = DICT_SEQ_FUN_NEXT) { 748 if (dict_seq(dict, func, &key, &value) != 0) 749 break; 750 if (*key == 0) { 751 msg_warn("table %s:%s: empty lookup key value is not allowed", 752 map_type, map_name); 753 } else if (*value == 0) { 754 msg_warn("table %s:%s: key %s: empty string result is not allowed", 755 map_type, map_name, key); 756 msg_warn("table %s:%s should return NO RESULT in case of NOT FOUND", 757 map_type, map_name); 758 } 759 vstream_printf("%s %s\n", key, value); 760 } 761 if (dict->error) 762 msg_fatal("table %s:%s: sequence error: %m", dict->type, dict->name); 763 vstream_fflush(VSTREAM_OUT); 764 dict_close(dict); 765} 766 767/* usage - explain */ 768 769static NORETURN usage(char *myname) 770{ 771 msg_fatal("usage: %s [-Nfinoprsvw] [-c config_dir] [-d key] [-q key] [map_type:]file...", 772 myname); 773} 774 775MAIL_VERSION_STAMP_DECLARE; 776 777int main(int argc, char **argv) 778{ 779 char *path_name; 780 int ch; 781 int fd; 782 char *slash; 783 struct stat st; 784 int postmap_flags = POSTMAP_FLAG_AS_OWNER | POSTMAP_FLAG_SAVE_PERM; 785 int open_flags = O_RDWR | O_CREAT | O_TRUNC; 786 int dict_flags = DICT_FLAG_DUP_WARN | DICT_FLAG_FOLD_FIX; 787 char *query = 0; 788 char *delkey = 0; 789 int sequence = 0; 790 int found; 791 792 /* 793 * Fingerprint executables and core dumps. 794 */ 795 MAIL_VERSION_STAMP_ALLOCATE; 796 797 /* 798 * Be consistent with file permissions. 799 */ 800 umask(022); 801 802 /* 803 * To minimize confusion, make sure that the standard file descriptors 804 * are open before opening anything else. XXX Work around for 44BSD where 805 * fstat can return EBADF on an open file descriptor. 806 */ 807 for (fd = 0; fd < 3; fd++) 808 if (fstat(fd, &st) == -1 809 && (close(fd), open("/dev/null", O_RDWR, 0)) != fd) 810 msg_fatal("open /dev/null: %m"); 811 812 /* 813 * Process environment options as early as we can. We are not set-uid, 814 * and we are supposed to be running in a controlled environment. 815 */ 816 if (getenv(CONF_ENV_VERB)) 817 msg_verbose = 1; 818 819 /* 820 * Initialize. Set up logging, read the global configuration file and 821 * extract configuration information. 822 */ 823 if ((slash = strrchr(argv[0], '/')) != 0 && slash[1]) 824 argv[0] = slash + 1; 825 msg_vstream_init(argv[0], VSTREAM_ERR); 826 msg_syslog_init(mail_task(argv[0]), LOG_PID, LOG_FACILITY); 827 828 /* 829 * Check the Postfix library version as soon as we enable logging. 830 */ 831 MAIL_VERSION_CHECK; 832 833 /* 834 * Parse JCL. 835 */ 836 while ((ch = GETOPT(argc, argv, "Nbc:d:fhimnopq:rsvw")) > 0) { 837 switch (ch) { 838 default: 839 usage(argv[0]); 840 break; 841 case 'N': 842 dict_flags |= DICT_FLAG_TRY1NULL; 843 dict_flags &= ~DICT_FLAG_TRY0NULL; 844 break; 845 case 'b': 846 postmap_flags |= POSTMAP_FLAG_BODY_KEY; 847 break; 848 case 'c': 849 if (setenv(CONF_ENV_PATH, optarg, 1) < 0) 850 msg_fatal("out of memory"); 851 break; 852 case 'd': 853 if (sequence || query || delkey) 854 msg_fatal("specify only one of -s -q or -d"); 855 delkey = optarg; 856 break; 857 case 'f': 858 dict_flags &= ~DICT_FLAG_FOLD_FIX; 859 break; 860 case 'h': 861 postmap_flags |= POSTMAP_FLAG_HEADER_KEY; 862 break; 863 case 'i': 864 open_flags &= ~O_TRUNC; 865 break; 866 case 'm': 867 postmap_flags |= POSTMAP_FLAG_MIME_KEY; 868 break; 869 case 'n': 870 dict_flags |= DICT_FLAG_TRY0NULL; 871 dict_flags &= ~DICT_FLAG_TRY1NULL; 872 break; 873 case 'o': 874 postmap_flags &= ~POSTMAP_FLAG_AS_OWNER; 875 break; 876 case 'p': 877 postmap_flags &= ~POSTMAP_FLAG_SAVE_PERM; 878 break; 879 case 'q': 880 if (sequence || query || delkey) 881 msg_fatal("specify only one of -s -q or -d"); 882 query = optarg; 883 break; 884 case 'r': 885 dict_flags &= ~(DICT_FLAG_DUP_WARN | DICT_FLAG_DUP_IGNORE); 886 dict_flags |= DICT_FLAG_DUP_REPLACE; 887 break; 888 case 's': 889 if (query || delkey) 890 msg_fatal("specify only one of -s or -q or -d"); 891 sequence = 1; 892 break; 893 case 'v': 894 msg_verbose++; 895 break; 896 case 'w': 897 dict_flags &= ~(DICT_FLAG_DUP_WARN | DICT_FLAG_DUP_REPLACE); 898 dict_flags |= DICT_FLAG_DUP_IGNORE; 899 break; 900 } 901 } 902 mail_conf_read(); 903 if (strcmp(var_syslog_name, DEF_SYSLOG_NAME) != 0) 904 msg_syslog_init(mail_task(argv[0]), LOG_PID, LOG_FACILITY); 905 mail_dict_init(); 906 if ((query == 0 || strcmp(query, "-") != 0) 907 && (postmap_flags & POSTMAP_FLAG_ANY_KEY)) 908 msg_fatal("specify -b -h or -m only with \"-q -\""); 909 if ((postmap_flags & POSTMAP_FLAG_ANY_KEY) != 0 910 && (postmap_flags & POSTMAP_FLAG_ANY_KEY) 911 == (postmap_flags & POSTMAP_FLAG_MIME_KEY)) 912 msg_warn("ignoring -m option without -b or -h"); 913 914 /* 915 * Use the map type specified by the user, or fall back to a default 916 * database type. 917 */ 918 if (delkey) { /* remove entry */ 919 if (optind + 1 > argc) 920 usage(argv[0]); 921 if (strcmp(delkey, "-") == 0) 922 exit(postmap_deletes(VSTREAM_IN, argv + optind, argc - optind, 923 dict_flags | DICT_FLAG_LOCK) == 0); 924 found = 0; 925 while (optind < argc) { 926 if ((path_name = split_at(argv[optind], ':')) != 0) { 927 found |= postmap_delete(argv[optind], path_name, delkey, 928 dict_flags | DICT_FLAG_LOCK); 929 } else { 930 found |= postmap_delete(var_db_type, argv[optind], delkey, 931 dict_flags | DICT_FLAG_LOCK); 932 } 933 optind++; 934 } 935 exit(found ? 0 : 1); 936 } else if (query) { /* query map(s) */ 937 if (optind + 1 > argc) 938 usage(argv[0]); 939 if (strcmp(query, "-") == 0) 940 exit(postmap_queries(VSTREAM_IN, argv + optind, argc - optind, 941 postmap_flags, dict_flags | DICT_FLAG_LOCK) == 0); 942 while (optind < argc) { 943 if ((path_name = split_at(argv[optind], ':')) != 0) { 944 found = postmap_query(argv[optind], path_name, query, 945 dict_flags | DICT_FLAG_LOCK); 946 } else { 947 found = postmap_query(var_db_type, argv[optind], query, 948 dict_flags | DICT_FLAG_LOCK); 949 } 950 if (found) 951 exit(0); 952 optind++; 953 } 954 exit(1); 955 } else if (sequence) { 956 while (optind < argc) { 957 if ((path_name = split_at(argv[optind], ':')) != 0) { 958 postmap_seq(argv[optind], path_name, 959 dict_flags | DICT_FLAG_LOCK); 960 } else { 961 postmap_seq(var_db_type, argv[optind], 962 dict_flags | DICT_FLAG_LOCK); 963 } 964 exit(0); 965 } 966 exit(1); 967 } else { /* create/update map(s) */ 968 if (optind + 1 > argc) 969 usage(argv[0]); 970 while (optind < argc) { 971 if ((path_name = split_at(argv[optind], ':')) != 0) { 972 postmap(argv[optind], path_name, postmap_flags, 973 open_flags, dict_flags); 974 } else { 975 postmap(var_db_type, argv[optind], postmap_flags, 976 open_flags, dict_flags); 977 } 978 optind++; 979 } 980 exit(0); 981 } 982} 983