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