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