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