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