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