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