5
/* Postfix lookup table management
8
/* \fBpostmap\fR [\fB-Nfinoprvw\fR] [\fB-c \fIconfig_dir\fR]
9
/* [\fB-d \fIkey\fR] [\fB-q \fIkey\fR]
10
/* [\fIfile_type\fR:]\fIfile_name\fR ...
12
/* The \fBpostmap\fR command creates or queries one or more Postfix
13
/* lookup tables, or updates an existing one. The input and output
14
/* file formats are expected to be compatible with:
17
/* \fBmakemap \fIfile_type\fR \fIfile_name\fR < \fIfile_name\fR
19
/* If the result files do not exist they will be created with the
20
/* same group and other read permissions as the source file.
22
/* While the table update is in progress, signal delivery is
23
/* postponed, and an exclusive, advisory, lock is placed on the
24
/* entire table, in order to avoid surprises in spectator
29
/* The format of a lookup table input file is as follows:
31
/* A table entry has the form
34
/* \fIkey\fR whitespace \fIvalue\fR
36
/* Empty lines and whitespace-only lines are ignored, as
37
/* are lines whose first non-whitespace character is a `#'.
39
/* A logical line starts with non-whitespace text. A line that
40
/* starts with whitespace continues a logical line.
42
/* The \fIkey\fR and \fIvalue\fR are processed as is, except that
43
/* surrounding white space is stripped off. Unlike with Postfix alias
44
/* databases, quotes cannot be used to protect lookup keys that contain
45
/* special characters such as `#' or whitespace. The \fIkey\fR is mapped
46
/* to lowercase to make mapping lookups case insensitive.
47
/* COMMAND-LINE ARGUMENTS
50
/* .IP "\fB-c \fIconfig_dir\fR"
51
/* Read the \fBmain.cf\fR configuration file in the named directory
52
/* instead of the default configuration directory.
53
/* .IP "\fB-d \fIkey\fR"
54
/* Search the specified maps for \fIkey\fR and remove one entry per map.
55
/* The exit status is zero when the requested information was found.
57
/* If a key value of \fB-\fR is specified, the program reads key
58
/* values from the standard input stream. The exit status is zero
59
/* when at least one of the requested keys was found.
61
/* Do not fold the lookup key to lower case while creating or querying
64
/* Incremental mode. Read entries from standard input and do not
65
/* truncate an existing database. By default, \fBpostmap\fR creates
66
/* a new database from the entries in \fBfile_name\fR.
68
/* Include the terminating null character that terminates lookup keys
69
/* and values. By default, Postfix does whatever is the default for
70
/* the host operating system.
72
/* Don't include the terminating null character that terminates lookup
73
/* keys and values. By default, Postfix does whatever is the default for
74
/* the host operating system.
76
/* Do not release root privileges when processing a non-root
77
/* input file. By default, \fBpostmap\fR drops root privileges
78
/* and runs as the source file owner instead.
80
/* Do not inherit the file access permissions from the input file
81
/* when creating a new file. Instead, create a new file with default
82
/* access permissions (mode 0644).
83
/* .IP "\fB-q \fIkey\fR"
84
/* Search the specified maps for \fIkey\fR and write the first value
85
/* found to the standard output stream. The exit status is zero
86
/* when the requested information was found.
88
/* If a key value of \fB-\fR is specified, the program reads key
89
/* values from the standard input stream and writes one line of
90
/* \fIkey value\fR output for each key that was found. The exit
91
/* status is zero when at least one of the requested keys was found.
93
/* When updating a table, do not warn about duplicate entries; silently
96
/* Enable verbose logging for debugging purposes. Multiple \fB-v\fR
97
/* options make the software increasingly verbose.
99
/* When updating a table, do not warn about duplicate entries; silently
103
/* .IP \fIfile_type\fR
104
/* The database type. To find out what types are supported, use
105
/* the "\fBpostconf -m" command.
107
/* The \fBpostmap\fR command can query any supported file type,
108
/* but it can create only the following file types:
111
/* The output file is a btree file, named \fIfile_name\fB.db\fR.
112
/* This is available only on systems with support for \fBdb\fR databases.
114
/* The output consists of two files, named \fIfile_name\fB.pag\fR and
115
/* \fIfile_name\fB.dir\fR.
116
/* This is available only on systems with support for \fBdbm\fR databases.
118
/* The output file is a hashed file, named \fIfile_name\fB.db\fR.
119
/* This is available only on systems with support for \fBdb\fR databases.
121
/* Use the command \fBpostconf -m\fR to find out what types of database
122
/* your Postfix installation can support.
124
/* When no \fIfile_type\fR is specified, the software uses the database
125
/* type specified via the \fBdefault_database_type\fR configuration
128
/* .IP \fIfile_name\fR
129
/* The name of the lookup table source file when rebuilding a database.
131
/* Problems are logged to the standard error stream and to
133
/* No output means that no problems were detected. Duplicate entries are
134
/* skipped and are flagged with a warning.
136
/* \fBpostmap\fR terminates with zero exit status in case of success
137
/* (including successful \fBpostmap -q\fR lookup) and terminates
138
/* with non-zero exit status in case of failure.
142
/* .IP \fBMAIL_CONFIG\fR
143
/* Directory with Postfix configuration files.
144
/* .IP \fBMAIL_VERBOSE\fR
145
/* Enable verbose logging for debugging purposes.
146
/* CONFIGURATION PARAMETERS
149
/* The following \fBmain.cf\fR parameters are especially relevant to
151
/* The text below provides only a parameter summary. See
152
/* postconf(5) for more details including examples.
153
/* .IP "\fBberkeley_db_create_buffer_size (16777216)\fR"
154
/* The per-table I/O buffer size for programs that create Berkeley DB
155
/* hash or btree tables.
156
/* .IP "\fBberkeley_db_read_buffer_size (131072)\fR"
157
/* The per-table I/O buffer size for programs that read Berkeley DB
158
/* hash or btree tables.
159
/* .IP "\fBconfig_directory (see 'postconf -d' output)\fR"
160
/* The default location of the Postfix main.cf and master.cf
161
/* configuration files.
162
/* .IP "\fBdefault_database_type (see 'postconf -d' output)\fR"
163
/* The default database type for use in newaliases(1), postalias(1)
164
/* and postmap(1) commands.
165
/* .IP "\fBsyslog_facility (mail)\fR"
166
/* The syslog facility of Postfix logging.
167
/* .IP "\fBsyslog_name (postfix)\fR"
168
/* The mail system name that is prepended to the process name in syslog
169
/* records, so that "smtpd" becomes, for example, "postfix/smtpd".
171
/* postalias(1), create/update/query alias database
172
/* postconf(1), supported database types
173
/* postconf(5), configuration parameters
174
/* syslogd(8), system logging
178
/* Use "\fBpostconf readme_directory\fR" or
179
/* "\fBpostconf html_directory\fR" to locate this information.
182
/* DATABASE_README, Postfix lookup table overview
186
/* The Secure Mailer license must be distributed with this software.
189
/* IBM T.J. Watson Research
191
/* Yorktown Heights, NY 10598, USA
194
/* System library. */
196
#include <sys_defs.h>
197
#include <sys/stat.h>
204
/* Utility library. */
207
#include <mymalloc.h>
210
#include <msg_vstream.h>
211
#include <msg_syslog.h>
212
#include <readlline.h>
213
#include <stringops.h>
214
#include <split_at.h>
215
#include <vstring_vstream.h>
216
#include <set_eugid.h>
218
/* Global library. */
220
#include <mail_conf.h>
221
#include <mail_dict.h>
222
#include <mail_params.h>
224
#include <mail_task.h>
226
/* Application-specific. */
228
#define STR vstring_str
230
#define POSTMAP_FLAG_AS_OWNER (1<<0) /* open dest as owner of source */
231
#define POSTMAP_FLAG_SAVE_PERM (1<<1) /* copy access permission from source */
233
/* postmap - create or update mapping database */
235
static void postmap(char *map_type, char *path_name, int postmap_flags,
236
int open_flags, int dict_flags)
239
VSTRING *line_buffer;
250
line_buffer = vstring_alloc(100);
251
if ((open_flags & O_TRUNC) == 0) {
252
source_fp = VSTREAM_IN;
253
vstream_control(source_fp, VSTREAM_CTL_PATH, "stdin", VSTREAM_CTL_END);
254
} else if ((source_fp = vstream_fopen(path_name, O_RDONLY, 0)) == 0) {
255
msg_fatal("open %s: %m", path_name);
257
if (fstat(vstream_fileno(source_fp), &st) < 0)
258
msg_fatal("fstat %s: %m", path_name);
261
* Turn off group/other read permissions as indicated in the source file.
263
if ((postmap_flags & POSTMAP_FLAG_SAVE_PERM) && S_ISREG(st.st_mode))
264
saved_mask = umask(022 | (~st.st_mode & 077));
267
* If running as root, run as the owner of the source file, so that the
268
* result shows proper ownership, and so that a bug in postmap does not
269
* allow privilege escalation.
271
if ((postmap_flags & POSTMAP_FLAG_AS_OWNER) && getuid() == 0
272
&& (st.st_uid != geteuid() || st.st_gid != getegid()))
273
set_eugid(st.st_uid, st.st_gid);
276
* Open the database, optionally create it when it does not exist,
277
* optionally truncate it when it does exist, and lock out any
280
mkmap = mkmap_open(map_type, path_name, open_flags, dict_flags);
283
* And restore the umask, in case it matters.
285
if ((postmap_flags & POSTMAP_FLAG_SAVE_PERM) && S_ISREG(st.st_mode))
289
* Add records to the database.
292
while (readlline(line_buffer, source_fp, &lineno)) {
295
* Split on the first whitespace character, then trim leading and
296
* trailing whitespace from key and value.
298
key = STR(line_buffer);
299
value = key + strcspn(key, " \t\r\n");
302
while (ISSPACE(*value))
304
trimblanks(key, 0)[0] = 0;
305
trimblanks(value, 0)[0] = 0;
308
* Enforce the "key whitespace value" format. Disallow missing keys
311
if (*key == 0 || *value == 0) {
312
msg_warn("%s, line %d: expected format: key whitespace value",
313
VSTREAM_PATH(source_fp), lineno);
316
if (key[strlen(key) - 1] == ':')
317
msg_warn("%s, line %d: record is in \"key: value\" format; is this an alias file?",
318
VSTREAM_PATH(source_fp), lineno);
321
* Store the value under a case-insensitive key.
323
if (dict_flags & DICT_FLAG_FOLD_KEY)
325
mkmap_append(mkmap, key, value);
329
* Close the mapping database, and release the lock.
334
* Cleanup. We're about to terminate, but it is a good sanity check.
336
vstring_free(line_buffer);
337
if (source_fp != VSTREAM_IN)
338
vstream_fclose(source_fp);
341
/* postmap_queries - apply multiple requests from stdin */
343
static int postmap_queries(VSTREAM *in, char **maps, const int map_count,
344
const int dict_flags)
347
VSTRING *keybuf = vstring_alloc(100);
349
const char *map_name;
357
msg_panic("postmap_queries: bad map count");
360
* Prepare to open maps lazily.
362
dicts = (DICT **) mymalloc(sizeof(*dicts) * map_count);
363
for (n = 0; n < map_count; n++)
367
* Perform all queries. Open maps on the fly, to avoid opening unecessary
370
while (vstring_get_nonl(keybuf, in) != VSTREAM_EOF) {
371
if (dict_flags & DICT_FLAG_FOLD_KEY)
372
lowercase(STR(keybuf));
373
for (n = 0; n < map_count; n++) {
375
dicts[n] = ((map_name = split_at(maps[n], ':')) != 0 ?
376
dict_open3(maps[n], map_name, O_RDONLY, DICT_FLAG_LOCK) :
377
dict_open3(var_db_type, maps[n], O_RDONLY, DICT_FLAG_LOCK));
378
if ((value = dict_get(dicts[n], STR(keybuf))) != 0) {
380
msg_warn("table %s:%s: key %s: empty string result is not allowed",
381
dicts[n]->type, dicts[n]->name, STR(keybuf));
382
msg_warn("table %s:%s should return NO RESULT in case of NOT FOUND",
383
dicts[n]->type, dicts[n]->name);
385
vstream_printf("%s %s\n", STR(keybuf), value);
392
vstream_fflush(VSTREAM_OUT);
397
for (n = 0; n < map_count; n++)
399
dict_close(dicts[n]);
400
myfree((char *) dicts);
401
vstring_free(keybuf);
406
/* postmap_query - query a map and print the result to stdout */
408
static int postmap_query(const char *map_type, const char *map_name,
414
dict = dict_open3(map_type, map_name, O_RDONLY, DICT_FLAG_LOCK);
415
if ((value = dict_get(dict, key)) != 0) {
417
msg_warn("table %s:%s: key %s: empty string result is not allowed",
418
map_type, map_name, key);
419
msg_warn("table %s:%s should return NO RESULT in case of NOT FOUND",
422
vstream_printf("%s\n", value);
423
vstream_fflush(VSTREAM_OUT);
429
/* postmap_deletes - apply multiple requests from stdin */
431
static int postmap_deletes(VSTREAM *in, char **maps, const int map_count)
434
VSTRING *keybuf = vstring_alloc(100);
436
const char *map_name;
443
msg_panic("postmap_deletes: bad map count");
446
* Open maps ahead of time.
448
dicts = (DICT **) mymalloc(sizeof(*dicts) * map_count);
449
for (n = 0; n < map_count; n++)
450
dicts[n] = ((map_name = split_at(maps[n], ':')) != 0 ?
451
dict_open3(maps[n], map_name, O_RDWR, DICT_FLAG_LOCK) :
452
dict_open3(var_db_type, maps[n], O_RDWR, DICT_FLAG_LOCK));
455
* Perform all requests.
457
while (vstring_get_nonl(keybuf, in) != VSTREAM_EOF)
458
for (n = 0; n < map_count; n++)
459
found |= (dict_del(dicts[n], STR(keybuf)) == 0);
464
for (n = 0; n < map_count; n++)
466
dict_close(dicts[n]);
467
myfree((char *) dicts);
468
vstring_free(keybuf);
473
/* postmap_delete - delete a (key, value) pair from a map */
475
static int postmap_delete(const char *map_type, const char *map_name,
481
dict = dict_open3(map_type, map_name, O_RDWR, DICT_FLAG_LOCK);
482
status = dict_del(dict, key);
484
return (status == 0);
487
/* usage - explain */
489
static NORETURN usage(char *myname)
491
msg_fatal("usage: %s [-Nfinorvw] [-c config_dir] [-d key] [-q key] [map_type:]file...",
495
int main(int argc, char **argv)
502
int postmap_flags = POSTMAP_FLAG_AS_OWNER | POSTMAP_FLAG_SAVE_PERM;
503
int open_flags = O_RDWR | O_CREAT | O_TRUNC;
504
int dict_flags = DICT_FLAG_DUP_WARN | DICT_FLAG_FOLD_KEY;
510
* Be consistent with file permissions.
515
* To minimize confusion, make sure that the standard file descriptors
516
* are open before opening anything else. XXX Work around for 44BSD where
517
* fstat can return EBADF on an open file descriptor.
519
for (fd = 0; fd < 3; fd++)
520
if (fstat(fd, &st) == -1
521
&& (close(fd), open("/dev/null", O_RDWR, 0)) != fd)
522
msg_fatal("open /dev/null: %m");
525
* Process environment options as early as we can. We are not set-uid,
526
* and we are supposed to be running in a controlled environment.
528
if (getenv(CONF_ENV_VERB))
532
* Initialize. Set up logging, read the global configuration file and
533
* extract configuration information.
535
if ((slash = strrchr(argv[0], '/')) != 0 && slash[1])
537
msg_vstream_init(argv[0], VSTREAM_ERR);
538
msg_syslog_init(mail_task(argv[0]), LOG_PID, LOG_FACILITY);
543
while ((ch = GETOPT(argc, argv, "Nc:d:finopq:rvw")) > 0) {
549
dict_flags |= DICT_FLAG_TRY1NULL;
550
dict_flags &= ~DICT_FLAG_TRY0NULL;
553
if (setenv(CONF_ENV_PATH, optarg, 1) < 0)
554
msg_fatal("out of memory");
558
msg_fatal("specify only one of -q or -d");
562
dict_flags &= ~DICT_FLAG_FOLD_KEY;
565
open_flags &= ~O_TRUNC;
568
dict_flags |= DICT_FLAG_TRY0NULL;
569
dict_flags &= ~DICT_FLAG_TRY1NULL;
572
postmap_flags &= ~POSTMAP_FLAG_AS_OWNER;
575
postmap_flags &= ~POSTMAP_FLAG_SAVE_PERM;
579
msg_fatal("specify only one of -q or -d");
583
dict_flags &= ~(DICT_FLAG_DUP_WARN | DICT_FLAG_DUP_IGNORE);
584
dict_flags |= DICT_FLAG_DUP_REPLACE;
590
dict_flags &= ~(DICT_FLAG_DUP_WARN | DICT_FLAG_DUP_REPLACE);
591
dict_flags |= DICT_FLAG_DUP_IGNORE;
599
* Use the map type specified by the user, or fall back to a default
602
if (delkey) { /* remove entry */
603
if (optind + 1 > argc)
605
if (strcmp(delkey, "-") == 0)
606
exit(postmap_deletes(VSTREAM_IN, argv + optind, argc - optind) == 0);
608
while (optind < argc) {
609
if ((path_name = split_at(argv[optind], ':')) != 0) {
610
found |= postmap_delete(argv[optind], path_name, delkey);
612
found |= postmap_delete(var_db_type, argv[optind], delkey);
617
} else if (query) { /* query map(s) */
618
if (optind + 1 > argc)
620
if (strcmp(query, "-") == 0)
621
exit(postmap_queries(VSTREAM_IN, argv + optind, argc - optind,
623
if (dict_flags & DICT_FLAG_FOLD_KEY)
625
while (optind < argc) {
626
if ((path_name = split_at(argv[optind], ':')) != 0) {
627
found = postmap_query(argv[optind], path_name, query);
629
found = postmap_query(var_db_type, argv[optind], query);
636
} else { /* create/update map(s) */
637
if (optind + 1 > argc)
639
while (optind < argc) {
640
if ((path_name = split_at(argv[optind], ':')) != 0) {
641
postmap(argv[optind], path_name, postmap_flags,
642
open_flags, dict_flags);
644
postmap(var_db_type, argv[optind], postmap_flags,
645
open_flags, dict_flags);