xref: /openbsd-src/usr.sbin/smtpd/makemap.c (revision d13be5d47e4149db2549a9828e244d59dbc43f15)
1 /*	$OpenBSD: makemap.c,v 1.32 2011/05/16 21:27:38 jasper Exp $	*/
2 
3 /*
4  * Copyright (c) 2008 Gilles Chehade <gilles@openbsd.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	*env = NULL;
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(u_int8_t what)
79 {
80 	bzero(env, sizeof(struct smtpd));
81 }
82 
83 int
84 ssl_load_certfile(const char *name, u_int8_t flags)
85 {
86 	return (0);
87 }
88 
89 int
90 main(int argc, char *argv[])
91 {
92 	struct stat	 sb;
93 	char		 dbname[MAXPATHLEN];
94 	char		*opts;
95 	char		*conf;
96 	int		 ch;
97 	struct smtpd	 smtpd;
98 
99 	env = &smtpd;
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:";
107 	if (mode == P_NEWALIASES)
108 		opts = "f:h";
109 
110 	while ((ch = getopt(argc, argv, opts)) != -1) {
111 		switch (ch) {
112 		case 'f':
113 			conf = optarg;
114 			break;
115 		case 'o':
116 			oflag = optarg;
117 			break;
118 		case 't':
119 			if (strcmp(optarg, "aliases") == 0)
120 				type = T_ALIASES;
121 			else if (strcmp(optarg, "set") == 0)
122 				type = T_SET;
123 			else
124 				errx(1, "unsupported type '%s'", optarg);
125 			break;
126 		default:
127 			usage();
128 		}
129 	}
130 	argc -= optind;
131 	argv += optind;
132 
133 	if (mode == P_NEWALIASES) {
134 		if (geteuid())
135 			errx(1, "need root privileges");
136 		if (argc != 0)
137 			usage();
138 		type = T_ALIASES;
139 		source = conf_aliases(conf);
140 	} else {
141 		if (argc != 1)
142 			usage();
143 		source = argv[0];
144 	}
145 
146 	if (oflag == NULL && asprintf(&oflag, "%s.db", source) == -1)
147 		err(1, "asprintf");
148 
149 	if (stat(source, &sb) == -1)
150 		err(1, "stat: %s", source);
151 
152 	if (! bsnprintf(dbname, sizeof(dbname), "%s.XXXXXXXXXXX", oflag))
153 		errx(1, "path too long");
154 	if (mkstemp(dbname) == -1)
155 		err(1, "mkstemp");
156 
157 	db = dbopen(dbname, O_EXLOCK|O_RDWR|O_SYNC, 0644, DB_HASH, NULL);
158 	if (db == NULL) {
159 		warn("dbopen: %s", dbname);
160 		goto bad;
161 	}
162 
163 	if (fchmod(db->fd(db), sb.st_mode) == -1 ||
164 	    fchown(db->fd(db), sb.st_uid, sb.st_gid) == -1) {
165 		warn("couldn't carry ownership and perms to %s", dbname);
166 		goto bad;
167 	}
168 
169 	if (! parse_map(source))
170 		goto bad;
171 
172 	if (db->close(db) == -1) {
173 		warn("dbclose: %s", dbname);
174 		goto bad;
175 	}
176 
177 	if (rename(dbname, oflag) == -1) {
178 		warn("rename");
179 		goto bad;
180 	}
181 
182 	if (mode == P_NEWALIASES)
183 		printf("%s: %d aliases\n", source, dbputs);
184 	else if (dbputs == 0)
185 		warnx("warning: empty map created: %s", oflag);
186 
187 	return 0;
188 bad:
189 	unlink(dbname);
190 	return 1;
191 }
192 
193 int
194 parse_map(char *filename)
195 {
196 	FILE	*fp;
197 	char	*line;
198 	size_t	 len;
199 	size_t	 lineno = 0;
200 	char	 delim[] = { '\\', 0, 0 };
201 
202 	fp = fopen(filename, "r");
203 	if (fp == NULL) {
204 		warn("%s", filename);
205 		return 0;
206 	}
207 
208 	if (flock(fileno(fp), LOCK_SH|LOCK_NB) == -1) {
209 		if (errno == EWOULDBLOCK)
210 			warnx("%s is locked", filename);
211 		else
212 			warn("%s: flock", filename);
213 		fclose(fp);
214 		return 0;
215 	}
216 
217 	while ((line = fparseln(fp, &len, &lineno, delim, 0)) != NULL) {
218 		if (! parse_entry(line, len, lineno)) {
219 			free(line);
220 			fclose(fp);
221 			return 0;
222 		}
223 		free(line);
224 	}
225 
226 	fclose(fp);
227 	return 1;
228 }
229 
230 int
231 parse_entry(char *line, size_t len, size_t lineno)
232 {
233 	switch (type) {
234 	case T_PLAIN:
235 	case T_ALIASES:
236 		return parse_mapentry(line, len, lineno);
237 	case T_SET:
238 		return parse_setentry(line, len, lineno);
239 	}
240 	return 0;
241 }
242 
243 int
244 parse_mapentry(char *line, size_t len, size_t lineno)
245 {
246 	DBT	 key;
247 	DBT	 val;
248 	char	*keyp;
249 	char	*valp;
250 
251 	keyp = line;
252 	while (isspace((int)*keyp))
253 		keyp++;
254 	if (*keyp == '\0' || *keyp == '#')
255 		return 1;
256 
257 	valp = keyp;
258 	strsep(&valp, " \t:");
259 	if (valp == NULL || valp == keyp)
260 		goto bad;
261 	while (*valp == ':' || isspace((int)*valp))
262 		valp++;
263 	if (*valp == '\0' || *valp == '#')
264 		goto bad;
265 
266 	/* Check for dups. */
267 	key.data = keyp;
268 	key.size = strlen(keyp) + 1;
269 	if (db->get(db, &key, &val, 0) == 0) {
270 		warnx("%s:%zd: duplicate entry for %s", source, lineno, keyp);
271 		return 0;
272 	}
273 
274 	if (type == T_PLAIN) {
275 		if (! make_plain(&val, valp))
276 			goto bad;
277 	}
278 	else if (type == T_ALIASES) {
279 		lowercase(key.data, key.data, strlen(key.data) + 1);
280 		if (! make_aliases(&val, valp))
281 			goto bad;
282 	}
283 
284 	if (db->put(db, &key, &val, 0) == -1) {
285 		warn("dbput");
286 		return 0;
287 	}
288 
289 	dbputs++;
290 
291 	free(val.data);
292 
293 	return 1;
294 
295 bad:
296 	warnx("%s:%zd: invalid entry", source, lineno);
297 	return 0;
298 }
299 
300 int
301 parse_setentry(char *line, size_t len, size_t lineno)
302 {
303 	DBT	 key;
304 	DBT	 val;
305 	char	*keyp;
306 
307 	keyp = line;
308 	while (isspace((int)*keyp))
309 		keyp++;
310 	if (*keyp == '\0' || *keyp == '#')
311 		return 1;
312 
313 	val.data  = "<set>";
314 	val.size = strlen(val.data) + 1;
315 
316 	/* Check for dups. */
317 	key.data = keyp;
318 	key.size = strlen(keyp) + 1;
319 	if (db->get(db, &key, &val, 0) == 0) {
320 		warnx("%s:%zd: duplicate entry for %s", source, lineno, keyp);
321 		return 0;
322 	}
323 
324 	if (db->put(db, &key, &val, 0) == -1) {
325 		warn("dbput");
326 		return 0;
327 	}
328 
329 	dbputs++;
330 
331 	return 1;
332 }
333 
334 int
335 make_plain(DBT *val, char *text)
336 {
337 	val->data = strdup(text);
338 	if (val->data == NULL)
339 		err(1, "malloc");
340 
341 	val->size = strlen(text) + 1;
342 
343 	return (val->size);
344 }
345 
346 int
347 make_aliases(DBT *val, char *text)
348 {
349 	struct expandnode	expnode;
350 	char	       	*subrcpt;
351 	char	       	*endp;
352 	char		*origtext;
353 
354 	val->data = NULL;
355 	val->size = 0;
356 
357 	origtext = strdup(text);
358 	if (origtext == NULL)
359 		fatal("strdup");
360 
361 	while ((subrcpt = strsep(&text, ",")) != NULL) {
362 		/* subrcpt: strip initial whitespace. */
363 		while (isspace((int)*subrcpt))
364 			++subrcpt;
365 		if (*subrcpt == '\0')
366 			goto error;
367 
368 		/* subrcpt: strip trailing whitespace. */
369 		endp = subrcpt + strlen(subrcpt) - 1;
370 		while (subrcpt < endp && isspace((int)*endp))
371 			*endp-- = '\0';
372 
373 		bzero(&expnode, sizeof(struct expandnode));
374 		if (! alias_parse(&expnode, subrcpt))
375 			goto error;
376 	}
377 
378 	val->data = origtext;
379 	val->size = strlen(origtext) + 1;
380 	return (val->size);
381 
382 error:
383 	free(origtext);
384 
385 	return 0;
386 }
387 
388 char *
389 conf_aliases(char *cfgpath)
390 {
391 	struct map	*map;
392 	char		*path;
393 	char		*p;
394 
395 	if (parse_config(env, cfgpath, 0))
396 		exit(1);
397 
398 	map = map_findbyname("aliases");
399 	if (map == NULL)
400 		return (PATH_ALIASES);
401 
402 	path = strdup(map->m_config);
403 	if (path == NULL)
404 		err(1, NULL);
405 	p = strstr(path, ".db");
406 	if (p == NULL || p[3] != '\0')
407 		errx(1, "%s: %s: no .db suffix present", cfgpath, path);
408 	*p = '\0';
409 
410 	return (path);
411 }
412 
413 void
414 usage(void)
415 {
416 	if (mode == P_NEWALIASES)
417 		fprintf(stderr, "usage: %s [-f file]\n", __progname);
418 	else
419 		fprintf(stderr, "usage: %s [-o dbfile] [-t type] file\n",
420 		    __progname);
421 	exit(1);
422 }
423