1 /* $OpenBSD: makemap.c,v 1.77 2024/05/07 12:10:06 op Exp $ */
2
3 /*
4 * Copyright (c) 2008 Gilles Chehade <gilles@poolp.org>
5 * Copyright (c) 2008-2009 Jacek Masiulaniec <jacekm@dobremiasto.net>
6 *
7 * Permission to use, copy, modify, and distribute this software for any
8 * purpose with or without fee is hereby granted, provided that the above
9 * copyright notice and this permission notice appear in all copies.
10 *
11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 */
19
20 #include <sys/stat.h>
21
22 #include <ctype.h>
23 #include <db.h>
24 #include <err.h>
25 #include <errno.h>
26 #include <fcntl.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <syslog.h>
30 #include <unistd.h>
31 #include <util.h>
32
33 #include "smtpd.h"
34 #include "log.h"
35
36 #define PATH_ALIASES "/etc/mail/aliases"
37
38 static void usage(void);
39 static int parse_map(DB *, int *, char *);
40 static int add_mapentry(DB *, int *, char *, char *, size_t);
41 static int add_setentry(DB *, int *, char *, size_t);
42 static int make_plain(DBT *, char *);
43 static int make_aliases(DBT *, char *);
44 static char *conf_aliases(char *);
45 static int dump_db(const char *, DBTYPE);
46
47 char *source;
48 static int mode;
49
50 enum output_type {
51 T_PLAIN,
52 T_ALIASES,
53 T_SET
54 } type;
55
56 /*
57 * Stub functions so that makemap compiles using minimum object files.
58 */
59 int
fork_proc_backend(const char * backend,const char * conf,const char * procname,int do_stdout)60 fork_proc_backend(const char *backend, const char *conf, const char *procname,
61 int do_stdout)
62 {
63 return (-1);
64 }
65
66 int
makemap(int prog_mode,int argc,char * argv[])67 makemap(int prog_mode, int argc, char *argv[])
68 {
69 struct stat sb;
70 char dbname[PATH_MAX];
71 DB *db;
72 const char *opts;
73 char *conf, *oflag = NULL;
74 int ch, dbputs = 0, Uflag = 0;
75 DBTYPE dbtype = DB_HASH;
76 char *p;
77 gid_t gid;
78 int fd = -1;
79
80 gid = getgid();
81 if (setresgid(gid, gid, gid) == -1)
82 err(1, "setresgid");
83
84 if ((env = config_default()) == NULL)
85 err(1, NULL);
86
87 log_init(1, LOG_MAIL);
88
89 mode = prog_mode;
90 conf = CONF_FILE;
91 type = T_PLAIN;
92 opts = "b:C:d:ho:O:t:U";
93 if (mode == P_NEWALIASES)
94 opts = "f:h";
95
96 while ((ch = getopt(argc, argv, opts)) != -1) {
97 switch (ch) {
98 case 'b':
99 if (optarg && strcmp(optarg, "i") == 0)
100 mode = P_NEWALIASES;
101 break;
102 case 'C':
103 break; /* for compatibility */
104 case 'd':
105 if (strcmp(optarg, "hash") == 0)
106 dbtype = DB_HASH;
107 else if (strcmp(optarg, "btree") == 0)
108 dbtype = DB_BTREE;
109 else
110 errx(1, "unsupported DB type '%s'", optarg);
111 break;
112 case 'f':
113 conf = optarg;
114 break;
115 case 'o':
116 oflag = optarg;
117 break;
118 case 'O':
119 if (strncmp(optarg, "AliasFile=", 10) != 0)
120 break;
121 type = T_ALIASES;
122 p = strchr(optarg, '=');
123 source = ++p;
124 break;
125 case 't':
126 if (strcmp(optarg, "aliases") == 0)
127 type = T_ALIASES;
128 else if (strcmp(optarg, "set") == 0)
129 type = T_SET;
130 else
131 errx(1, "unsupported type '%s'", optarg);
132 break;
133 case 'U':
134 Uflag = 1;
135 break;
136 default:
137 usage();
138 }
139 }
140 argc -= optind;
141 argv += optind;
142
143 /* sendmail-compat makemap ... re-execute using proper interface */
144 if (argc == 2) {
145 if (oflag)
146 usage();
147
148 p = strstr(argv[1], ".db");
149 if (p == NULL || strcmp(p, ".db") != 0) {
150 if (!bsnprintf(dbname, sizeof dbname, "%s.db",
151 argv[1]))
152 errx(1, "database name too long");
153 }
154 else {
155 if (strlcpy(dbname, argv[1], sizeof dbname)
156 >= sizeof dbname)
157 errx(1, "database name too long");
158 }
159
160 execl(PATH_MAKEMAP, "makemap", "-d", argv[0], "-o", dbname,
161 "-", (char *)NULL);
162 err(1, "execl");
163 }
164
165 if (mode == P_NEWALIASES) {
166 if (geteuid())
167 errx(1, "need root privileges");
168 if (argc != 0)
169 usage();
170 type = T_ALIASES;
171 if (source == NULL)
172 source = conf_aliases(conf);
173 } else {
174 if (argc != 1)
175 usage();
176 source = argv[0];
177 }
178
179 if (Uflag)
180 return dump_db(source, dbtype);
181
182 if (oflag == NULL && asprintf(&oflag, "%s.db", source) == -1)
183 err(1, "asprintf");
184
185 if (strcmp(source, "-") != 0)
186 if (stat(source, &sb) == -1)
187 err(1, "stat: %s", source);
188
189 if (!bsnprintf(dbname, sizeof(dbname), "%s.XXXXXXXXXXX", oflag))
190 errx(1, "path too long");
191 if ((fd = mkstemp(dbname)) == -1)
192 err(1, "mkstemp");
193
194 db = dbopen(dbname, O_TRUNC|O_RDWR, 0644, dbtype, NULL);
195 if (db == NULL) {
196 warn("dbopen: %s", dbname);
197 goto bad;
198 }
199
200 if (strcmp(source, "-") != 0)
201 if (fchmod(db->fd(db), sb.st_mode) == -1 ||
202 fchown(db->fd(db), sb.st_uid, sb.st_gid) == -1) {
203 warn("couldn't carry ownership and perms to %s",
204 dbname);
205 goto bad;
206 }
207
208 if (!parse_map(db, &dbputs, source))
209 goto bad;
210
211 if (db->close(db) == -1) {
212 warn("dbclose: %s", dbname);
213 goto bad;
214 }
215
216 /* force to disk before renaming over an existing file */
217 if (fsync(fd) == -1) {
218 warn("fsync: %s", dbname);
219 goto bad;
220 }
221 if (close(fd) == -1) {
222 fd = -1;
223 warn("close: %s", dbname);
224 goto bad;
225 }
226 fd = -1;
227
228 if (rename(dbname, oflag) == -1) {
229 warn("rename");
230 goto bad;
231 }
232
233 if (mode == P_NEWALIASES)
234 printf("%s: %d aliases\n", source, dbputs);
235 else if (dbputs == 0)
236 warnx("warning: empty map created: %s", oflag);
237
238 return 0;
239 bad:
240 if (fd != -1)
241 close(fd);
242 unlink(dbname);
243 return 1;
244 }
245
246 static int
parse_map(DB * db,int * dbputs,char * filename)247 parse_map(DB *db, int *dbputs, char *filename)
248 {
249 FILE *fp;
250 char *key, *val, *line = NULL;
251 size_t linesize = 0;
252 size_t lineno = 0;
253 int malformed, table_type, r;
254
255 if (strcmp(filename, "-") == 0)
256 fp = fdopen(0, "r");
257 else
258 fp = fopen(filename, "r");
259 if (fp == NULL) {
260 warn("%s", filename);
261 return 0;
262 }
263
264 if (!isatty(fileno(fp)) && flock(fileno(fp), LOCK_SH|LOCK_NB) == -1) {
265 if (errno == EWOULDBLOCK)
266 warnx("%s is locked", filename);
267 else
268 warn("%s: flock", filename);
269 fclose(fp);
270 return 0;
271 }
272
273 table_type = (type == T_SET) ? T_LIST : T_HASH;
274 while (parse_table_line(fp, &line, &linesize, &table_type,
275 &key, &val, &malformed) != -1) {
276 lineno++;
277 if (malformed) {
278 warnx("%s:%zd: invalid entry", source, lineno);
279 free(line);
280 fclose(fp);
281 return 0;
282 }
283 if (key == NULL)
284 continue;
285
286 switch (type) {
287 case T_PLAIN:
288 case T_ALIASES:
289 r = add_mapentry(db, dbputs, key, val, lineno);
290 break;
291 case T_SET:
292 r = add_setentry(db, dbputs, key, lineno);
293 break;
294 }
295
296 if (!r) {
297 free(line);
298 fclose(fp);
299 return 0;
300 }
301 }
302
303 free(line);
304 fclose(fp);
305 return 1;
306 }
307
308 static int
add_mapentry(DB * db,int * dbputs,char * keyp,char * valp,size_t lineno)309 add_mapentry(DB *db, int *dbputs, char *keyp, char *valp, size_t lineno)
310 {
311 DBT key;
312 DBT val;
313
314 /* Check for dups. */
315 key.data = keyp;
316 key.size = strlen(keyp) + 1;
317
318 xlowercase(key.data, key.data, strlen(key.data) + 1);
319 if (db->get(db, &key, &val, 0) == 0) {
320 warnx("%s:%zd: duplicate entry for %s", source, lineno, keyp);
321 return 0;
322 }
323
324 if (type == T_PLAIN) {
325 if (!make_plain(&val, valp))
326 goto bad;
327 }
328 else if (type == T_ALIASES) {
329 if (!make_aliases(&val, valp))
330 goto bad;
331 }
332
333 if (db->put(db, &key, &val, 0) == -1) {
334 warn("dbput");
335 return 0;
336 }
337
338 (*dbputs)++;
339
340 free(val.data);
341
342 return 1;
343
344 bad:
345 warnx("%s:%zd: invalid entry", source, lineno);
346 return 0;
347 }
348
349 static int
add_setentry(DB * db,int * dbputs,char * keyp,size_t lineno)350 add_setentry(DB *db, int *dbputs, char *keyp, size_t lineno)
351 {
352 DBT key;
353 DBT val;
354
355 val.data = "<set>";
356 val.size = strlen(val.data) + 1;
357
358 /* Check for dups. */
359 key.data = keyp;
360 key.size = strlen(keyp) + 1;
361 xlowercase(key.data, key.data, strlen(key.data) + 1);
362 if (db->get(db, &key, &val, 0) == 0) {
363 warnx("%s:%zd: duplicate entry for %s", source, lineno, keyp);
364 return 0;
365 }
366
367 if (db->put(db, &key, &val, 0) == -1) {
368 warn("dbput");
369 return 0;
370 }
371
372 (*dbputs)++;
373
374 return 1;
375 }
376
377 static int
make_plain(DBT * val,char * text)378 make_plain(DBT *val, char *text)
379 {
380 val->data = xstrdup(text);
381 val->size = strlen(text) + 1;
382
383 return (val->size);
384 }
385
386 static int
make_aliases(DBT * val,char * text)387 make_aliases(DBT *val, char *text)
388 {
389 struct expandnode xn;
390 char *subrcpt;
391 char *origtext;
392
393 val->data = NULL;
394 val->size = 0;
395
396 origtext = xstrdup(text);
397
398 while ((subrcpt = strsep(&text, ",")) != NULL) {
399 /* subrcpt: strip initial and trailing whitespace. */
400 subrcpt = strip(subrcpt);
401 if (*subrcpt == '\0')
402 goto error;
403
404 if (!text_to_expandnode(&xn, subrcpt))
405 goto error;
406 }
407
408 val->data = origtext;
409 val->size = strlen(origtext) + 1;
410 return (val->size);
411
412 error:
413 free(origtext);
414
415 return 0;
416 }
417
418 static char *
conf_aliases(char * cfgpath)419 conf_aliases(char *cfgpath)
420 {
421 struct table *table;
422 char *path;
423 char *p;
424
425 if (parse_config(env, cfgpath, 0))
426 exit(1);
427
428 table = table_find(env, "aliases");
429 if (table == NULL)
430 return (PATH_ALIASES);
431
432 path = xstrdup(table->t_config);
433 p = strstr(path, ".db");
434 if (p == NULL || strcmp(p, ".db") != 0) {
435 return (path);
436 }
437 *p = '\0';
438 return (path);
439 }
440
441 static int
dump_db(const char * dbname,DBTYPE dbtype)442 dump_db(const char *dbname, DBTYPE dbtype)
443 {
444 DB *db;
445 DBT key, val;
446 char *keystr, *valstr;
447 int r;
448
449 db = dbopen(dbname, O_RDONLY, 0644, dbtype, NULL);
450 if (db == NULL)
451 err(1, "dbopen: %s", dbname);
452
453 for (r = db->seq(db, &key, &val, R_FIRST); r == 0;
454 r = db->seq(db, &key, &val, R_NEXT)) {
455 keystr = key.data;
456 valstr = val.data;
457 if (keystr[key.size - 1] == '\0')
458 key.size--;
459 if (valstr[val.size - 1] == '\0')
460 val.size--;
461 printf("%.*s\t%.*s\n", (int)key.size, keystr,
462 (int)val.size, valstr);
463 }
464 if (r == -1)
465 err(1, "db->seq: %s", dbname);
466
467 if (db->close(db) == -1)
468 err(1, "dbclose: %s", dbname);
469
470 return 0;
471 }
472
473 static void
usage(void)474 usage(void)
475 {
476 if (mode == P_NEWALIASES)
477 fprintf(stderr, "usage: newaliases [-f file]\n");
478 else
479 fprintf(stderr, "usage: makemap [-U] [-d dbtype] [-o dbfile] "
480 "[-t type] file\n");
481 exit(1);
482 }
483