xref: /openbsd/usr.sbin/smtpd/to.c (revision 3cab2bb3)
1 /*	$OpenBSD: to.c,v 1.44 2019/11/12 20:21:46 gilles Exp $	*/
2 
3 /*
4  * Copyright (c) 2009 Jacek Masiulaniec <jacekm@dobremiasto.net>
5  * Copyright (c) 2012 Eric Faurot <eric@openbsd.org>
6  * Copyright (c) 2012 Gilles Chehade <gilles@poolp.org>
7  *
8  * Permission to use, copy, modify, and distribute this software for any
9  * purpose with or without fee is hereby granted, provided that the above
10  * copyright notice and this permission notice appear in all copies.
11  *
12  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
13  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
14  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
15  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
16  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
17  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
18  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
19  */
20 
21 #include <sys/types.h>
22 #include <sys/queue.h>
23 #include <sys/tree.h>
24 #include <sys/socket.h>
25 #include <sys/stat.h>
26 #include <sys/resource.h>
27 
28 #include <netinet/in.h>
29 #include <arpa/inet.h>
30 
31 #include <ctype.h>
32 #include <err.h>
33 #include <errno.h>
34 #include <event.h>
35 #include <fcntl.h>
36 #include <imsg.h>
37 #include <limits.h>
38 #include <inttypes.h>
39 #include <netdb.h>
40 #include <pwd.h>
41 #include <stdarg.h>
42 #include <stdio.h>
43 #include <stdlib.h>
44 #include <string.h>
45 #include <time.h>
46 #include <unistd.h>
47 
48 #include "smtpd.h"
49 #include "log.h"
50 
51 static const char *in6addr_to_text(const struct in6_addr *);
52 static int alias_is_filter(struct expandnode *, const char *, size_t);
53 static int alias_is_username(struct expandnode *, const char *, size_t);
54 static int alias_is_address(struct expandnode *, const char *, size_t);
55 static int alias_is_filename(struct expandnode *, const char *, size_t);
56 static int alias_is_include(struct expandnode *, const char *, size_t);
57 static int alias_is_error(struct expandnode *, const char *, size_t);
58 
59 const char *
60 sockaddr_to_text(struct sockaddr *sa)
61 {
62 	static char	buf[NI_MAXHOST];
63 
64 	if (getnameinfo(sa, sa->sa_len, buf, sizeof(buf), NULL, 0,
65 	    NI_NUMERICHOST))
66 		return ("(unknown)");
67 	else
68 		return (buf);
69 }
70 
71 static const char *
72 in6addr_to_text(const struct in6_addr *addr)
73 {
74 	struct sockaddr_in6	sa_in6;
75 	uint16_t		tmp16;
76 
77 	memset(&sa_in6, 0, sizeof(sa_in6));
78 	sa_in6.sin6_len = sizeof(sa_in6);
79 	sa_in6.sin6_family = AF_INET6;
80 	memcpy(&sa_in6.sin6_addr, addr, sizeof(sa_in6.sin6_addr));
81 
82 	/* XXX thanks, KAME, for this ugliness... adopted from route/show.c */
83 	if (IN6_IS_ADDR_LINKLOCAL(&sa_in6.sin6_addr) ||
84 	    IN6_IS_ADDR_MC_LINKLOCAL(&sa_in6.sin6_addr)) {
85 		memcpy(&tmp16, &sa_in6.sin6_addr.s6_addr[2], sizeof(tmp16));
86 		sa_in6.sin6_scope_id = ntohs(tmp16);
87 		sa_in6.sin6_addr.s6_addr[2] = 0;
88 		sa_in6.sin6_addr.s6_addr[3] = 0;
89 	}
90 
91 	return (sockaddr_to_text((struct sockaddr *)&sa_in6));
92 }
93 
94 int
95 text_to_mailaddr(struct mailaddr *maddr, const char *email)
96 {
97 	char *username;
98 	char *hostname;
99 	char  buffer[LINE_MAX];
100 
101 	if (strlcpy(buffer, email, sizeof buffer) >= sizeof buffer)
102 		return 0;
103 
104 	memset(maddr, 0, sizeof *maddr);
105 
106 	username = buffer;
107 	hostname = strrchr(username, '@');
108 
109 	if (hostname == NULL) {
110 		if (strlcpy(maddr->user, username, sizeof maddr->user)
111 		    >= sizeof maddr->user)
112 			return 0;
113 	}
114 	else if (username == hostname) {
115 		*hostname++ = '\0';
116 		if (strlcpy(maddr->domain, hostname, sizeof maddr->domain)
117 		    >= sizeof maddr->domain)
118 			return 0;
119 	}
120 	else {
121 		*hostname++ = '\0';
122 		if (strlcpy(maddr->user, username, sizeof maddr->user)
123 		    >= sizeof maddr->user)
124 			return 0;
125 		if (strlcpy(maddr->domain, hostname, sizeof maddr->domain)
126 		    >= sizeof maddr->domain)
127 			return 0;
128 	}
129 
130 	return 1;
131 }
132 
133 const char *
134 mailaddr_to_text(const struct mailaddr *maddr)
135 {
136 	static char  buffer[LINE_MAX];
137 
138 	(void)strlcpy(buffer, maddr->user, sizeof buffer);
139 	(void)strlcat(buffer, "@", sizeof buffer);
140 	if (strlcat(buffer, maddr->domain, sizeof buffer) >= sizeof buffer)
141 		return NULL;
142 
143 	return buffer;
144 }
145 
146 
147 const char *
148 sa_to_text(const struct sockaddr *sa)
149 {
150 	static char	 buf[NI_MAXHOST + 5];
151 	char		*p;
152 
153 	buf[0] = '\0';
154 	p = buf;
155 
156 	if (sa->sa_family == AF_LOCAL)
157 		(void)strlcpy(buf, "local", sizeof buf);
158 	else if (sa->sa_family == AF_INET) {
159 		in_addr_t addr;
160 
161 		addr = ((const struct sockaddr_in *)sa)->sin_addr.s_addr;
162 		addr = ntohl(addr);
163 		(void)bsnprintf(p, NI_MAXHOST, "%d.%d.%d.%d",
164 		    (addr >> 24) & 0xff, (addr >> 16) & 0xff,
165 		    (addr >> 8) & 0xff, addr & 0xff);
166 	}
167 	else if (sa->sa_family == AF_INET6) {
168 		const struct sockaddr_in6 *in6;
169 		const struct in6_addr	*in6_addr;
170 
171 		in6 = (const struct sockaddr_in6 *)sa;
172 		p = buf;
173 		in6_addr = &in6->sin6_addr;
174 		(void)bsnprintf(p, NI_MAXHOST, "[%s]", in6addr_to_text(in6_addr));
175 	}
176 
177 	return (buf);
178 }
179 
180 const char *
181 ss_to_text(const struct sockaddr_storage *ss)
182 {
183 	return (sa_to_text((const struct sockaddr*)ss));
184 }
185 
186 const char *
187 time_to_text(time_t when)
188 {
189 	struct tm *lt;
190 	static char buf[40];
191 	char *day[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
192 	char *month[] = {"Jan","Feb","Mar","Apr","May","Jun",
193 			 "Jul","Aug","Sep","Oct","Nov","Dec"};
194 	char *tz;
195 	long offset;
196 
197 	lt = localtime(&when);
198 	if (lt == NULL || when == 0)
199 		fatalx("time_to_text: localtime");
200 
201 	offset = lt->tm_gmtoff;
202 	tz = lt->tm_zone;
203 
204 	/* We do not use strftime because it is subject to locale substitution*/
205 	if (!bsnprintf(buf, sizeof(buf),
206 	    "%s, %d %s %d %02d:%02d:%02d %c%02d%02d (%s)",
207 	    day[lt->tm_wday], lt->tm_mday, month[lt->tm_mon],
208 	    lt->tm_year + 1900,
209 	    lt->tm_hour, lt->tm_min, lt->tm_sec,
210 	    offset >= 0 ? '+' : '-',
211 	    abs((int)offset / 3600),
212 	    abs((int)offset % 3600) / 60,
213 	    tz))
214 		fatalx("time_to_text: bsnprintf");
215 
216 	return buf;
217 }
218 
219 const char *
220 duration_to_text(time_t t)
221 {
222 	static char	dst[64];
223 	char		buf[64];
224 	int		h, m, s;
225 	long long	d;
226 
227 	if (t == 0) {
228 		(void)strlcpy(dst, "0s", sizeof dst);
229 		return (dst);
230 	}
231 
232 	dst[0] = '\0';
233 	if (t < 0) {
234 		(void)strlcpy(dst, "-", sizeof dst);
235 		t = -t;
236 	}
237 
238 	s = t % 60;
239 	t /= 60;
240 	m = t % 60;
241 	t /= 60;
242 	h = t % 24;
243 	d = t / 24;
244 
245 	if (d) {
246 		(void)snprintf(buf, sizeof buf, "%lldd", d);
247 		(void)strlcat(dst, buf, sizeof dst);
248 	}
249 	if (h) {
250 		(void)snprintf(buf, sizeof buf, "%dh", h);
251 		(void)strlcat(dst, buf, sizeof dst);
252 	}
253 	if (m) {
254 		(void)snprintf(buf, sizeof buf, "%dm", m);
255 		(void)strlcat(dst, buf, sizeof dst);
256 	}
257 	if (s) {
258 		(void)snprintf(buf, sizeof buf, "%ds", s);
259 		(void)strlcat(dst, buf, sizeof dst);
260 	}
261 
262 	return (dst);
263 }
264 
265 int
266 text_to_netaddr(struct netaddr *netaddr, const char *s)
267 {
268 	struct sockaddr_storage	ss;
269 	struct sockaddr_in	ssin;
270 	struct sockaddr_in6	ssin6;
271 	int			bits;
272 	char			buf[NI_MAXHOST];
273 	size_t			len;
274 
275 	memset(&ssin, 0, sizeof(struct sockaddr_in));
276 	memset(&ssin6, 0, sizeof(struct sockaddr_in6));
277 
278 	if (strncasecmp("IPv6:", s, 5) == 0)
279 		s += 5;
280 
281 	bits = inet_net_pton(AF_INET, s, &ssin.sin_addr,
282 	    sizeof(struct in_addr));
283 	if (bits != -1) {
284 		ssin.sin_family = AF_INET;
285 		memcpy(&ss, &ssin, sizeof(ssin));
286 		ss.ss_len = sizeof(struct sockaddr_in);
287 	} else {
288 		if (s[0] != '[') {
289 			if ((len = strlcpy(buf, s, sizeof buf)) >= sizeof buf)
290 				return 0;
291 		}
292 		else {
293 			s++;
294 			if (strncasecmp("IPv6:", s, 5) == 0)
295 				s += 5;
296 			if ((len = strlcpy(buf, s, sizeof buf)) >= sizeof buf)
297 				return 0;
298 			if (buf[len-1] != ']')
299 				return 0;
300 			buf[len-1] = 0;
301 		}
302 		bits = inet_net_pton(AF_INET6, buf, &ssin6.sin6_addr,
303 		    sizeof(struct in6_addr));
304 		if (bits == -1)
305 			return 0;
306 		ssin6.sin6_family = AF_INET6;
307 		memcpy(&ss, &ssin6, sizeof(ssin6));
308 		ss.ss_len = sizeof(struct sockaddr_in6);
309 	}
310 
311 	netaddr->ss   = ss;
312 	netaddr->bits = bits;
313 	return 1;
314 }
315 
316 int
317 text_to_relayhost(struct relayhost *relay, const char *s)
318 {
319 	static const struct schema {
320 		const char	*name;
321 		int		 tls;
322 		uint16_t	 flags;
323 		uint16_t	 port;
324 	} schemas [] = {
325 		/*
326 		 * new schemas should be *appended* otherwise the default
327 		 * schema index needs to be updated later in this function.
328 		 */
329 		{ "smtp://",		RELAY_TLS_OPPORTUNISTIC, 0,		25 },
330 		{ "smtp+tls://",	RELAY_TLS_STARTTLS,	 0,		25 },
331 		{ "smtp+notls://",	RELAY_TLS_NO,		 0,		25 },
332 		/* need to specify an explicit port for LMTP */
333 		{ "lmtp://",		RELAY_TLS_NO,		 RELAY_LMTP,	0 },
334 		{ "smtps://",		RELAY_TLS_SMTPS,	 0,		465 }
335 	};
336 	const char     *errstr = NULL;
337 	char	       *p, *q;
338 	char		buffer[1024];
339 	char	       *beg, *end;
340 	size_t		i;
341 	size_t		len;
342 
343 	memset(buffer, 0, sizeof buffer);
344 	if (strlcpy(buffer, s, sizeof buffer) >= sizeof buffer)
345 		return 0;
346 
347 	for (i = 0; i < nitems(schemas); ++i)
348 		if (strncasecmp(schemas[i].name, s,
349 		    strlen(schemas[i].name)) == 0)
350 			break;
351 
352 	if (i == nitems(schemas)) {
353 		/* there is a schema, but it's not recognized */
354 		if (strstr(buffer, "://"))
355 			return 0;
356 
357 		/* no schema, default to smtp:// */
358 		i = 0;
359 		p = buffer;
360 	}
361 	else
362 		p = buffer + strlen(schemas[i].name);
363 
364 	relay->tls = schemas[i].tls;
365 	relay->flags = schemas[i].flags;
366 	relay->port = schemas[i].port;
367 
368 	/* first, we extract the label if any */
369 	if ((q = strchr(p, '@')) != NULL) {
370 		*q = 0;
371 		if (strlcpy(relay->authlabel, p, sizeof (relay->authlabel))
372 		    >= sizeof (relay->authlabel))
373 			return 0;
374 		p = q + 1;
375 	}
376 
377 	/* then, we extract the mail exchanger */
378 	beg = end = p;
379 	if (*beg == '[') {
380 		if ((end = strchr(beg, ']')) == NULL)
381 			return 0;
382 		/* skip ']', it has to be included in the relay hostname */
383 		++end;
384 		len = end - beg;
385 	}
386 	else {
387 		for (end = beg; *end; ++end)
388 			if (!isalnum((unsigned char)*end) &&
389 			    *end != '_' && *end != '.' && *end != '-')
390 				break;
391 		len = end - beg;
392 	}
393 	if (len >= sizeof relay->hostname)
394 		return 0;
395 	for (i = 0; i < len; ++i)
396 		relay->hostname[i] = beg[i];
397 	relay->hostname[i] = 0;
398 
399 	/* finally, we extract the port */
400 	p = beg + len;
401 	if (*p == ':') {
402 		relay->port = strtonum(p+1, 1, IPPORT_HILASTAUTO, &errstr);
403 		if (errstr)
404 			return 0;
405 	}
406 
407 	if (!valid_domainpart(relay->hostname))
408 		return 0;
409 	if ((relay->flags & RELAY_LMTP) && (relay->port == 0))
410 		return 0;
411 	if (relay->authlabel[0]) {
412 		/* disallow auth on non-tls scheme. */
413 		if (relay->tls != RELAY_TLS_STARTTLS &&
414 		    relay->tls != RELAY_TLS_SMTPS)
415 			return 0;
416 		relay->flags |= RELAY_AUTH;
417 	}
418 
419 	return 1;
420 }
421 
422 uint64_t
423 text_to_evpid(const char *s)
424 {
425 	uint64_t ulval;
426 	char	 *ep;
427 
428 	errno = 0;
429 	ulval = strtoull(s, &ep, 16);
430 	if (s[0] == '\0' || *ep != '\0')
431 		return 0;
432 	if (errno == ERANGE && ulval == ULLONG_MAX)
433 		return 0;
434 	if (ulval == 0)
435 		return 0;
436 	return (ulval);
437 }
438 
439 uint32_t
440 text_to_msgid(const char *s)
441 {
442 	uint64_t ulval;
443 	char	 *ep;
444 
445 	errno = 0;
446 	ulval = strtoull(s, &ep, 16);
447 	if (s[0] == '\0' || *ep != '\0')
448 		return 0;
449 	if (errno == ERANGE && ulval == ULLONG_MAX)
450 		return 0;
451 	if (ulval == 0)
452 		return 0;
453 	if (ulval > 0xffffffff)
454 		return 0;
455 	return (ulval & 0xffffffff);
456 }
457 
458 const char *
459 rule_to_text(struct rule *r)
460 {
461 	static char buf[4096];
462 
463 	memset(buf, 0, sizeof buf);
464 	(void)strlcpy(buf, "match", sizeof buf);
465 	if (r->flag_tag) {
466 		if (r->flag_tag < 0)
467 			(void)strlcat(buf, " !", sizeof buf);
468 		(void)strlcat(buf, " tag ", sizeof buf);
469 		(void)strlcat(buf, r->table_tag, sizeof buf);
470 	}
471 
472 	if (r->flag_from) {
473 		if (r->flag_from < 0)
474 			(void)strlcat(buf, " !", sizeof buf);
475 		if (r->flag_from_socket)
476 			(void)strlcat(buf, " from socket", sizeof buf);
477 		else if (r->flag_from_rdns) {
478 			(void)strlcat(buf, " from rdns", sizeof buf);
479 			if (r->table_from) {
480 				(void)strlcat(buf, " ", sizeof buf);
481 				(void)strlcat(buf, r->table_from, sizeof buf);
482 			}
483 		}
484 		else if (strcmp(r->table_from, "<anyhost>") == 0)
485 			(void)strlcat(buf, " from any", sizeof buf);
486 		else if (strcmp(r->table_from, "<localhost>") == 0)
487 			(void)strlcat(buf, " from local", sizeof buf);
488 		else {
489 			(void)strlcat(buf, " from src ", sizeof buf);
490 			(void)strlcat(buf, r->table_from, sizeof buf);
491 		}
492 	}
493 
494 	if (r->flag_for) {
495 		if (r->flag_for < 0)
496 			(void)strlcat(buf, " !", sizeof buf);
497 		if (strcmp(r->table_for, "<anydestination>") == 0)
498 			(void)strlcat(buf, " for any", sizeof buf);
499 		else if (strcmp(r->table_for, "<localnames>") == 0)
500 			(void)strlcat(buf, " for local", sizeof buf);
501 		else {
502 			(void)strlcat(buf, " for domain ", sizeof buf);
503 			(void)strlcat(buf, r->table_for, sizeof buf);
504 		}
505 	}
506 
507 	if (r->flag_smtp_helo) {
508 		if (r->flag_smtp_helo < 0)
509 			(void)strlcat(buf, " !", sizeof buf);
510 		(void)strlcat(buf, " helo ", sizeof buf);
511 		(void)strlcat(buf, r->table_smtp_helo, sizeof buf);
512 	}
513 
514 	if (r->flag_smtp_auth) {
515 		if (r->flag_smtp_auth < 0)
516 			(void)strlcat(buf, " !", sizeof buf);
517 		(void)strlcat(buf, " auth", sizeof buf);
518 		if (r->table_smtp_auth) {
519 			(void)strlcat(buf, " ", sizeof buf);
520 			(void)strlcat(buf, r->table_smtp_auth, sizeof buf);
521 		}
522 	}
523 
524 	if (r->flag_smtp_starttls) {
525 		if (r->flag_smtp_starttls < 0)
526 			(void)strlcat(buf, " !", sizeof buf);
527 		(void)strlcat(buf, " tls", sizeof buf);
528 	}
529 
530 	if (r->flag_smtp_mail_from) {
531 		if (r->flag_smtp_mail_from < 0)
532 			(void)strlcat(buf, " !", sizeof buf);
533 		(void)strlcat(buf, " mail-from ", sizeof buf);
534 		(void)strlcat(buf, r->table_smtp_mail_from, sizeof buf);
535 	}
536 
537 	if (r->flag_smtp_rcpt_to) {
538 		if (r->flag_smtp_rcpt_to < 0)
539 			(void)strlcat(buf, " !", sizeof buf);
540 		(void)strlcat(buf, " rcpt-to ", sizeof buf);
541 		(void)strlcat(buf, r->table_smtp_rcpt_to, sizeof buf);
542 	}
543 	(void)strlcat(buf, " action ", sizeof buf);
544 	if (r->reject)
545 		(void)strlcat(buf, "reject", sizeof buf);
546 	else
547 		(void)strlcat(buf, r->dispatcher, sizeof buf);
548 	return buf;
549 }
550 
551 
552 int
553 text_to_userinfo(struct userinfo *userinfo, const char *s)
554 {
555 	char		buf[PATH_MAX];
556 	char	       *p;
557 	const char     *errstr;
558 
559 	memset(buf, 0, sizeof buf);
560 	p = buf;
561 	while (*s && *s != ':')
562 		*p++ = *s++;
563 	if (*s++ != ':')
564 		goto error;
565 
566 	if (strlcpy(userinfo->username, buf,
567 		sizeof userinfo->username) >= sizeof userinfo->username)
568 		goto error;
569 
570 	memset(buf, 0, sizeof buf);
571 	p = buf;
572 	while (*s && *s != ':')
573 		*p++ = *s++;
574 	if (*s++ != ':')
575 		goto error;
576 	userinfo->uid = strtonum(buf, 0, UID_MAX, &errstr);
577 	if (errstr)
578 		goto error;
579 
580 	memset(buf, 0, sizeof buf);
581 	p = buf;
582 	while (*s && *s != ':')
583 		*p++ = *s++;
584 	if (*s++ != ':')
585 		goto error;
586 	userinfo->gid = strtonum(buf, 0, GID_MAX, &errstr);
587 	if (errstr)
588 		goto error;
589 
590 	if (strlcpy(userinfo->directory, s,
591 		sizeof userinfo->directory) >= sizeof userinfo->directory)
592 		goto error;
593 
594 	return 1;
595 
596 error:
597 	return 0;
598 }
599 
600 int
601 text_to_credentials(struct credentials *creds, const char *s)
602 {
603 	char   *p;
604 	char	buffer[LINE_MAX];
605 	size_t	offset;
606 
607 	p = strchr(s, ':');
608 	if (p == NULL) {
609 		creds->username[0] = '\0';
610 		if (strlcpy(creds->password, s, sizeof creds->password)
611 		    >= sizeof creds->password)
612 			return 0;
613 		return 1;
614 	}
615 
616 	offset = p - s;
617 
618 	memset(buffer, 0, sizeof buffer);
619 	if (strlcpy(buffer, s, sizeof buffer) >= sizeof buffer)
620 		return 0;
621 	p = buffer + offset;
622 	*p = '\0';
623 
624 	if (strlcpy(creds->username, buffer, sizeof creds->username)
625 	    >= sizeof creds->username)
626 		return 0;
627 	if (strlcpy(creds->password, p+1, sizeof creds->password)
628 	    >= sizeof creds->password)
629 		return 0;
630 
631 	return 1;
632 }
633 
634 int
635 text_to_expandnode(struct expandnode *expandnode, const char *s)
636 {
637 	size_t	l;
638 
639 	l = strlen(s);
640 	if (alias_is_error(expandnode, s, l) ||
641 	    alias_is_include(expandnode, s, l) ||
642 	    alias_is_filter(expandnode, s, l) ||
643 	    alias_is_filename(expandnode, s, l) ||
644 	    alias_is_address(expandnode, s, l) ||
645 	    alias_is_username(expandnode, s, l))
646 		return (1);
647 
648 	return (0);
649 }
650 
651 const char *
652 expandnode_to_text(struct expandnode *expandnode)
653 {
654 	switch (expandnode->type) {
655 	case EXPAND_FILTER:
656 	case EXPAND_FILENAME:
657 	case EXPAND_INCLUDE:
658 	case EXPAND_ERROR:
659 	case EXPAND_USERNAME:
660 		return expandnode->u.user;
661 	case EXPAND_ADDRESS:
662 		return mailaddr_to_text(&expandnode->u.mailaddr);
663 	case EXPAND_INVALID:
664 		break;
665 	}
666 
667 	return NULL;
668 }
669 
670 /******/
671 static int
672 alias_is_filter(struct expandnode *alias, const char *line, size_t len)
673 {
674 	int	v = 0;
675 
676 	if (*line == '"')
677 		v = 1;
678 	if (*(line+v) == '|') {
679 		if (strlcpy(alias->u.buffer, line + v + 1,
680 		    sizeof(alias->u.buffer)) >= sizeof(alias->u.buffer))
681 			return 0;
682 		if (v) {
683 			v = strlen(alias->u.buffer);
684 			if (v == 0)
685 				return (0);
686 			if (alias->u.buffer[v-1] != '"')
687 				return (0);
688 			alias->u.buffer[v-1] = '\0';
689 		}
690 		alias->type = EXPAND_FILTER;
691 		return (1);
692 	}
693 	return (0);
694 }
695 
696 static int
697 alias_is_username(struct expandnode *alias, const char *line, size_t len)
698 {
699 	memset(alias, 0, sizeof *alias);
700 
701 	if (strlcpy(alias->u.user, line,
702 	    sizeof(alias->u.user)) >= sizeof(alias->u.user))
703 		return 0;
704 
705 	while (*line) {
706 		if (!isalnum((unsigned char)*line) &&
707 		    *line != '_' && *line != '.' && *line != '-' && *line != '+')
708 			return 0;
709 		++line;
710 	}
711 
712 	alias->type = EXPAND_USERNAME;
713 	return 1;
714 }
715 
716 static int
717 alias_is_address(struct expandnode *alias, const char *line, size_t len)
718 {
719 	char *domain;
720 
721 	memset(alias, 0, sizeof *alias);
722 
723 	if (len < 3)	/* x@y */
724 		return 0;
725 
726 	domain = strchr(line, '@');
727 	if (domain == NULL)
728 		return 0;
729 
730 	/* @ cannot start or end an address */
731 	if (domain == line || domain == line + len - 1)
732 		return 0;
733 
734 	/* scan pre @ for disallowed chars */
735 	*domain++ = '\0';
736 	(void)strlcpy(alias->u.mailaddr.user, line, sizeof(alias->u.mailaddr.user));
737 	(void)strlcpy(alias->u.mailaddr.domain, domain,
738 	    sizeof(alias->u.mailaddr.domain));
739 
740 	while (*line) {
741 		char allowedset[] = "!#$%*/?|^{}`~&'+-=_.";
742 		if (!isalnum((unsigned char)*line) &&
743 		    strchr(allowedset, *line) == NULL)
744 			return 0;
745 		++line;
746 	}
747 
748 	while (*domain) {
749 		char allowedset[] = "-.";
750 		if (!isalnum((unsigned char)*domain) &&
751 		    strchr(allowedset, *domain) == NULL)
752 			return 0;
753 		++domain;
754 	}
755 
756 	alias->type = EXPAND_ADDRESS;
757 	return 1;
758 }
759 
760 static int
761 alias_is_filename(struct expandnode *alias, const char *line, size_t len)
762 {
763 	memset(alias, 0, sizeof *alias);
764 
765 	if (*line != '/')
766 		return 0;
767 
768 	if (strlcpy(alias->u.buffer, line,
769 	    sizeof(alias->u.buffer)) >= sizeof(alias->u.buffer))
770 		return 0;
771 	alias->type = EXPAND_FILENAME;
772 	return 1;
773 }
774 
775 static int
776 alias_is_include(struct expandnode *alias, const char *line, size_t len)
777 {
778 	size_t skip;
779 
780 	memset(alias, 0, sizeof *alias);
781 
782 	if (strncasecmp(":include:", line, 9) == 0)
783 		skip = 9;
784 	else if (strncasecmp("include:", line, 8) == 0)
785 		skip = 8;
786 	else
787 		return 0;
788 
789 	if (!alias_is_filename(alias, line + skip, len - skip))
790 		return 0;
791 
792 	alias->type = EXPAND_INCLUDE;
793 	return 1;
794 }
795 
796 static int
797 alias_is_error(struct expandnode *alias, const char *line, size_t len)
798 {
799 	size_t	skip;
800 
801 	memset(alias, 0, sizeof *alias);
802 
803 	if (strncasecmp(":error:", line, 7) == 0)
804 		skip = 7;
805 	else if (strncasecmp("error:", line, 6) == 0)
806 		skip = 6;
807 	else
808 		return 0;
809 
810 	if (strlcpy(alias->u.buffer, line + skip,
811 	    sizeof(alias->u.buffer)) >= sizeof(alias->u.buffer))
812 		return 0;
813 
814 	if (strlen(alias->u.buffer) < 5)
815 		return 0;
816 
817 	/* [45][0-9]{2} [a-zA-Z0-9].* */
818 	if (alias->u.buffer[3] != ' ' ||
819 	    !isalnum((unsigned char)alias->u.buffer[4]) ||
820 	    (alias->u.buffer[0] != '4' && alias->u.buffer[0] != '5') ||
821 	    !isdigit((unsigned char)alias->u.buffer[1]) ||
822 	    !isdigit((unsigned char)alias->u.buffer[2]))
823 		return 0;
824 
825 	alias->type = EXPAND_ERROR;
826 	return 1;
827 }
828