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