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