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