xref: /openbsd-src/usr.sbin/smtpd/makemap.c (revision 50b7afb2c2c0993b0894d4e34bf857cb13ed9c80)
1 /*	$OpenBSD: makemap.c,v 1.49 2014/07/08 13:49:09 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/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 <util.h>
38 
39 #include "smtpd.h"
40 #include "log.h"
41 
42 #define	PATH_ALIASES	"/etc/mail/aliases"
43 
44 extern char *__progname;
45 
46 __dead void	usage(void);
47 static int parse_map(char *);
48 static int parse_entry(char *, size_t, size_t);
49 static int parse_mapentry(char *, size_t, size_t);
50 static int parse_setentry(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 
55 DB	*db;
56 char	*source;
57 char	*oflag;
58 int	 dbputs;
59 
60 struct smtpd	smtpd;
61 struct smtpd	*env = &smtpd;
62 
63 enum program {
64 	P_MAKEMAP,
65 	P_NEWALIASES
66 } mode;
67 
68 enum output_type {
69 	T_PLAIN,
70 	T_ALIASES,
71 	T_SET
72 } type;
73 
74 /*
75  * Stub functions so that makemap compiles using minimum object files.
76  */
77 void
78 purge_config(uint8_t what)
79 {
80 	memset(env, 0, sizeof(struct smtpd));
81 }
82 
83 int
84 fork_proc_backend(const char *backend, const char *conf, const char *procname)
85 {
86 	return (-1);
87 }
88 
89 int
90 main(int argc, char *argv[])
91 {
92 	struct stat	 sb;
93 	char		 dbname[SMTPD_MAXPATHLEN];
94 	char		*opts;
95 	char		*conf;
96 	int		 ch;
97 	DBTYPE		 dbtype = DB_HASH;
98 	char		*p;
99 	mode_t		 omode;
100 
101 	log_init(1);
102 
103 	mode = strcmp(__progname, "newaliases") ? P_MAKEMAP : P_NEWALIASES;
104 	conf = CONF_FILE;
105 	type = T_PLAIN;
106 	opts = "ho:t:d:";
107 	if (mode == P_NEWALIASES)
108 		opts = "f:h";
109 
110 	while ((ch = getopt(argc, argv, opts)) != -1) {
111 		switch (ch) {
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 if (strcmp(optarg, "dbm") == 0)
118 				dbtype = DB_RECNO;
119 			else
120 				errx(1, "unsupported DB type '%s'", optarg);
121 			break;
122 		case 'f':
123 			conf = optarg;
124 			break;
125 		case 'o':
126 			oflag = optarg;
127 			break;
128 		case 't':
129 			if (strcmp(optarg, "aliases") == 0)
130 				type = T_ALIASES;
131 			else if (strcmp(optarg, "set") == 0)
132 				type = T_SET;
133 			else
134 				errx(1, "unsupported type '%s'", optarg);
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 		execlp("makemap", "makemap", "-d", argv[0], "-o", dbname, "-",
161 		    NULL);
162 		err(1, "execlp");
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 		source = conf_aliases(conf);
172 	} else {
173 		if (argc != 1)
174 			usage();
175 		source = argv[0];
176 	}
177 
178 	if (oflag == NULL && asprintf(&oflag, "%s.db", source) == -1)
179 		err(1, "asprintf");
180 
181 	if (strcmp(source, "-") != 0)
182 		if (stat(source, &sb) == -1)
183 			err(1, "stat: %s", source);
184 
185 	if (! bsnprintf(dbname, sizeof(dbname), "%s.XXXXXXXXXXX", oflag))
186 		errx(1, "path too long");
187 	omode = umask(7077);
188 	if (mkstemp(dbname) == -1)
189 		err(1, "mkstemp");
190 	umask(omode);
191 
192 	db = dbopen(dbname, O_EXLOCK|O_RDWR|O_SYNC, 0644, dbtype, NULL);
193 	if (db == NULL) {
194 		warn("dbopen: %s", dbname);
195 		goto bad;
196 	}
197 
198 	if (strcmp(source, "-") != 0)
199 		if (fchmod(db->fd(db), sb.st_mode) == -1 ||
200 		    fchown(db->fd(db), sb.st_uid, sb.st_gid) == -1) {
201 			warn("couldn't carry ownership and perms to %s",
202 			    dbname);
203 			goto bad;
204 		}
205 
206 	if (! parse_map(source))
207 		goto bad;
208 
209 	if (db->close(db) == -1) {
210 		warn("dbclose: %s", dbname);
211 		goto bad;
212 	}
213 
214 	if (rename(dbname, oflag) == -1) {
215 		warn("rename");
216 		goto bad;
217 	}
218 
219 	if (mode == P_NEWALIASES)
220 		printf("%s: %d aliases\n", source, dbputs);
221 	else if (dbputs == 0)
222 		warnx("warning: empty map created: %s", oflag);
223 
224 	return 0;
225 bad:
226 	unlink(dbname);
227 	return 1;
228 }
229 
230 int
231 parse_map(char *filename)
232 {
233 	FILE	*fp;
234 	char	*line;
235 	size_t	 len;
236 	size_t	 lineno = 0;
237 	char	 delim[] = { '\\', 0, 0 };
238 
239 	if (strcmp(filename, "-") == 0)
240 		fp = fdopen(0, "r");
241 	else
242 		fp = fopen(filename, "r");
243 	if (fp == NULL) {
244 		warn("%s", filename);
245 		return 0;
246 	}
247 
248 	if (!isatty(fileno(fp)) && flock(fileno(fp), LOCK_SH|LOCK_NB) == -1) {
249 		if (errno == EWOULDBLOCK)
250 			warnx("%s is locked", filename);
251 		else
252 			warn("%s: flock", filename);
253 		fclose(fp);
254 		return 0;
255 	}
256 
257 	while ((line = fparseln(fp, &len, &lineno, delim, 0)) != NULL) {
258 		if (! parse_entry(line, len, lineno)) {
259 			free(line);
260 			fclose(fp);
261 			return 0;
262 		}
263 		free(line);
264 	}
265 
266 	fclose(fp);
267 	return 1;
268 }
269 
270 int
271 parse_entry(char *line, size_t len, size_t lineno)
272 {
273 	switch (type) {
274 	case T_PLAIN:
275 	case T_ALIASES:
276 		return parse_mapentry(line, len, lineno);
277 	case T_SET:
278 		return parse_setentry(line, len, lineno);
279 	}
280 	return 0;
281 }
282 
283 int
284 parse_mapentry(char *line, size_t len, size_t lineno)
285 {
286 	DBT	 key;
287 	DBT	 val;
288 	char	*keyp;
289 	char	*valp;
290 
291 	keyp = line;
292 	while (isspace((unsigned char)*keyp))
293 		keyp++;
294 	if (*keyp == '\0' || *keyp == '#')
295 		return 1;
296 
297 	valp = keyp;
298 	strsep(&valp, " \t:");
299 	if (valp == NULL || valp == keyp)
300 		goto bad;
301 	while (*valp == ':' || isspace((unsigned char)*valp))
302 		valp++;
303 	if (*valp == '\0' || *valp == '#')
304 		goto bad;
305 
306 	/* Check for dups. */
307 	key.data = keyp;
308 	key.size = strlen(keyp) + 1;
309 
310 	xlowercase(key.data, key.data, strlen(key.data) + 1);
311 	if (db->get(db, &key, &val, 0) == 0) {
312 		warnx("%s:%zd: duplicate entry for %s", source, lineno, keyp);
313 		return 0;
314 	}
315 
316 	if (type == T_PLAIN) {
317 		if (! make_plain(&val, valp))
318 			goto bad;
319 	}
320 	else if (type == T_ALIASES) {
321 		if (! make_aliases(&val, valp))
322 			goto bad;
323 	}
324 
325 	if (db->put(db, &key, &val, 0) == -1) {
326 		warn("dbput");
327 		return 0;
328 	}
329 
330 	dbputs++;
331 
332 	free(val.data);
333 
334 	return 1;
335 
336 bad:
337 	warnx("%s:%zd: invalid entry", source, lineno);
338 	return 0;
339 }
340 
341 int
342 parse_setentry(char *line, size_t len, size_t lineno)
343 {
344 	DBT	 key;
345 	DBT	 val;
346 	char	*keyp;
347 
348 	keyp = line;
349 	while (isspace((unsigned char)*keyp))
350 		keyp++;
351 	if (*keyp == '\0' || *keyp == '#')
352 		return 1;
353 
354 	val.data  = "<set>";
355 	val.size = strlen(val.data) + 1;
356 
357 	/* Check for dups. */
358 	key.data = keyp;
359 	key.size = strlen(keyp) + 1;
360 	xlowercase(key.data, key.data, strlen(key.data) + 1);
361 	if (db->get(db, &key, &val, 0) == 0) {
362 		warnx("%s:%zd: duplicate entry for %s", source, lineno, keyp);
363 		return 0;
364 	}
365 
366 	if (db->put(db, &key, &val, 0) == -1) {
367 		warn("dbput");
368 		return 0;
369 	}
370 
371 	dbputs++;
372 
373 	return 1;
374 }
375 
376 int
377 make_plain(DBT *val, char *text)
378 {
379 	val->data = xstrdup(text, "make_plain");
380 	val->size = strlen(text) + 1;
381 
382 	return (val->size);
383 }
384 
385 int
386 make_aliases(DBT *val, char *text)
387 {
388 	struct expandnode	xn;
389 	char		       *subrcpt;
390 	char		       *endp;
391 	char		       *origtext;
392 
393 	val->data = NULL;
394 	val->size = 0;
395 
396 	origtext = xstrdup(text, "make_aliases");
397 
398 	while ((subrcpt = strsep(&text, ",")) != NULL) {
399 		/* subrcpt: strip initial whitespace. */
400 		while (isspace((unsigned char)*subrcpt))
401 			++subrcpt;
402 		if (*subrcpt == '\0')
403 			goto error;
404 
405 		/* subrcpt: strip trailing whitespace. */
406 		endp = subrcpt + strlen(subrcpt) - 1;
407 		while (subrcpt < endp && isspace((unsigned char)*endp))
408 			*endp-- = '\0';
409 
410 		if (! text_to_expandnode(&xn, subrcpt))
411 			goto error;
412 	}
413 
414 	val->data = origtext;
415 	val->size = strlen(origtext) + 1;
416 	return (val->size);
417 
418 error:
419 	free(origtext);
420 
421 	return 0;
422 }
423 
424 char *
425 conf_aliases(char *cfgpath)
426 {
427 	struct table	*table;
428 	char		*path;
429 	char		*p;
430 
431 	if (parse_config(env, cfgpath, 0))
432 		exit(1);
433 
434 	table = table_find("aliases", NULL);
435 	if (table == NULL)
436 		return (PATH_ALIASES);
437 
438 	path = xstrdup(table->t_config, "conf_aliases");
439 	p = strstr(path, ".db");
440 	if (p == NULL || strcmp(p, ".db") != 0) {
441 		return (path);
442 	}
443 	*p = '\0';
444 	return (path);
445 }
446 
447 void
448 usage(void)
449 {
450 	if (mode == P_NEWALIASES)
451 		fprintf(stderr, "usage: %s [-f file]\n", __progname);
452 	else
453 		fprintf(stderr, "usage: %s [-d dbtype] [-o dbfile] "
454 		    "[-t type] file\n", __progname);
455 	exit(1);
456 }
457