xref: /openbsd-src/usr.sbin/smtpd/makemap.c (revision 5a38ef86d0b61900239c7913d24a05e7b88a58f0)
1 /*	$OpenBSD: makemap.c,v 1.75 2021/06/14 17:58:15 eric 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	 parse_entry(DB *, int *, char *, size_t, size_t);
41 static int	 parse_mapentry(DB *, int *, char *, size_t, size_t);
42 static int	 parse_setentry(DB *, int *, char *, size_t, size_t);
43 static int	 make_plain(DBT *, char *);
44 static int	 make_aliases(DBT *, char *);
45 static char	*conf_aliases(char *);
46 static int	 dump_db(const char *, DBTYPE);
47 
48 char		*source;
49 static int	 mode;
50 
51 enum output_type {
52 	T_PLAIN,
53 	T_ALIASES,
54 	T_SET
55 } type;
56 
57 /*
58  * Stub functions so that makemap compiles using minimum object files.
59  */
60 int
61 fork_proc_backend(const char *backend, const char *conf, const char *procname)
62 {
63 	return (-1);
64 }
65 
66 int
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
247 parse_map(DB *db, int *dbputs, char *filename)
248 {
249 	FILE	*fp;
250 	char	*line;
251 	size_t	 len;
252 	size_t	 lineno = 0;
253 
254 	if (strcmp(filename, "-") == 0)
255 		fp = fdopen(0, "r");
256 	else
257 		fp = fopen(filename, "r");
258 	if (fp == NULL) {
259 		warn("%s", filename);
260 		return 0;
261 	}
262 
263 	if (!isatty(fileno(fp)) && flock(fileno(fp), LOCK_SH|LOCK_NB) == -1) {
264 		if (errno == EWOULDBLOCK)
265 			warnx("%s is locked", filename);
266 		else
267 			warn("%s: flock", filename);
268 		fclose(fp);
269 		return 0;
270 	}
271 
272 	while ((line = fparseln(fp, &len, &lineno,
273 	    NULL, FPARSELN_UNESCCOMM)) != NULL) {
274 		if (!parse_entry(db, dbputs, line, len, lineno)) {
275 			free(line);
276 			fclose(fp);
277 			return 0;
278 		}
279 		free(line);
280 	}
281 
282 	fclose(fp);
283 	return 1;
284 }
285 
286 static int
287 parse_entry(DB *db, int *dbputs, char *line, size_t len, size_t lineno)
288 {
289 	switch (type) {
290 	case T_PLAIN:
291 	case T_ALIASES:
292 		return parse_mapentry(db, dbputs, line, len, lineno);
293 	case T_SET:
294 		return parse_setentry(db, dbputs, line, len, lineno);
295 	}
296 	return 0;
297 }
298 
299 static int
300 parse_mapentry(DB *db, int *dbputs, char *line, size_t len, size_t lineno)
301 {
302 	DBT	 key;
303 	DBT	 val;
304 	char	*keyp;
305 	char	*valp;
306 
307 	keyp = line;
308 	while (isspace((unsigned char)*keyp))
309 		keyp++;
310 	if (*keyp == '\0')
311 		return 1;
312 
313 	valp = keyp;
314 	strsep(&valp, " \t:");
315 	if (valp == NULL || valp == keyp)
316 		goto bad;
317 	while (*valp == ':' || isspace((unsigned char)*valp))
318 		valp++;
319 	if (*valp == '\0')
320 		goto bad;
321 
322 	/* Check for dups. */
323 	key.data = keyp;
324 	key.size = strlen(keyp) + 1;
325 
326 	xlowercase(key.data, key.data, strlen(key.data) + 1);
327 	if (db->get(db, &key, &val, 0) == 0) {
328 		warnx("%s:%zd: duplicate entry for %s", source, lineno, keyp);
329 		return 0;
330 	}
331 
332 	if (type == T_PLAIN) {
333 		if (!make_plain(&val, valp))
334 			goto bad;
335 	}
336 	else if (type == T_ALIASES) {
337 		if (!make_aliases(&val, valp))
338 			goto bad;
339 	}
340 
341 	if (db->put(db, &key, &val, 0) == -1) {
342 		warn("dbput");
343 		return 0;
344 	}
345 
346 	(*dbputs)++;
347 
348 	free(val.data);
349 
350 	return 1;
351 
352 bad:
353 	warnx("%s:%zd: invalid entry", source, lineno);
354 	return 0;
355 }
356 
357 static int
358 parse_setentry(DB *db, int *dbputs, char *line, size_t len, size_t lineno)
359 {
360 	DBT	 key;
361 	DBT	 val;
362 	char	*keyp;
363 
364 	keyp = line;
365 	while (isspace((unsigned char)*keyp))
366 		keyp++;
367 	if (*keyp == '\0')
368 		return 1;
369 
370 	val.data  = "<set>";
371 	val.size = strlen(val.data) + 1;
372 
373 	/* Check for dups. */
374 	key.data = keyp;
375 	key.size = strlen(keyp) + 1;
376 	xlowercase(key.data, key.data, strlen(key.data) + 1);
377 	if (db->get(db, &key, &val, 0) == 0) {
378 		warnx("%s:%zd: duplicate entry for %s", source, lineno, keyp);
379 		return 0;
380 	}
381 
382 	if (db->put(db, &key, &val, 0) == -1) {
383 		warn("dbput");
384 		return 0;
385 	}
386 
387 	(*dbputs)++;
388 
389 	return 1;
390 }
391 
392 static int
393 make_plain(DBT *val, char *text)
394 {
395 	val->data = xstrdup(text);
396 	val->size = strlen(text) + 1;
397 
398 	return (val->size);
399 }
400 
401 static int
402 make_aliases(DBT *val, char *text)
403 {
404 	struct expandnode	xn;
405 	char		       *subrcpt;
406 	char		       *origtext;
407 
408 	val->data = NULL;
409 	val->size = 0;
410 
411 	origtext = xstrdup(text);
412 
413 	while ((subrcpt = strsep(&text, ",")) != NULL) {
414 		/* subrcpt: strip initial and trailing whitespace. */
415 		subrcpt = strip(subrcpt);
416 		if (*subrcpt == '\0')
417 			goto error;
418 
419 		if (!text_to_expandnode(&xn, subrcpt))
420 			goto error;
421 	}
422 
423 	val->data = origtext;
424 	val->size = strlen(origtext) + 1;
425 	return (val->size);
426 
427 error:
428 	free(origtext);
429 
430 	return 0;
431 }
432 
433 static char *
434 conf_aliases(char *cfgpath)
435 {
436 	struct table	*table;
437 	char		*path;
438 	char		*p;
439 
440 	if (parse_config(env, cfgpath, 0))
441 		exit(1);
442 
443 	table = table_find(env, "aliases");
444 	if (table == NULL)
445 		return (PATH_ALIASES);
446 
447 	path = xstrdup(table->t_config);
448 	p = strstr(path, ".db");
449 	if (p == NULL || strcmp(p, ".db") != 0) {
450 		return (path);
451 	}
452 	*p = '\0';
453 	return (path);
454 }
455 
456 static int
457 dump_db(const char *dbname, DBTYPE dbtype)
458 {
459 	DB	*db;
460 	DBT	 key, val;
461 	char	*keystr, *valstr;
462 	int	 r;
463 
464 	db = dbopen(dbname, O_RDONLY, 0644, dbtype, NULL);
465 	if (db == NULL)
466 		err(1, "dbopen: %s", dbname);
467 
468 	for (r = db->seq(db, &key, &val, R_FIRST); r == 0;
469 	    r = db->seq(db, &key, &val, R_NEXT)) {
470 		keystr = key.data;
471 		valstr = val.data;
472 		if (keystr[key.size - 1] == '\0')
473 			key.size--;
474 		if (valstr[val.size - 1] == '\0')
475 			val.size--;
476 		printf("%.*s\t%.*s\n", (int)key.size, keystr,
477 		    (int)val.size, valstr);
478 	}
479 	if (r == -1)
480 		err(1, "db->seq: %s", dbname);
481 
482 	if (db->close(db) == -1)
483 		err(1, "dbclose: %s", dbname);
484 
485 	return 0;
486 }
487 
488 static void
489 usage(void)
490 {
491 	if (mode == P_NEWALIASES)
492 		fprintf(stderr, "usage: newaliases [-f file]\n");
493 	else
494 		fprintf(stderr, "usage: makemap [-U] [-d dbtype] [-o dbfile] "
495 		    "[-t type] file\n");
496 	exit(1);
497 }
498