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