xref: /openbsd-src/usr.sbin/smtpd/to.c (revision 0dcffd0d27e0f4753ef0d585a243805b3e68ce8d)
1*0dcffd0dSop /*	$OpenBSD: to.c,v 1.50 2023/05/31 16:51:46 op Exp $	*/
265c4fdfbSgilles 
365c4fdfbSgilles /*
465c4fdfbSgilles  * Copyright (c) 2009 Jacek Masiulaniec <jacekm@dobremiasto.net>
565c4fdfbSgilles  * Copyright (c) 2012 Eric Faurot <eric@openbsd.org>
665c4fdfbSgilles  * Copyright (c) 2012 Gilles Chehade <gilles@poolp.org>
765c4fdfbSgilles  *
865c4fdfbSgilles  * Permission to use, copy, modify, and distribute this software for any
965c4fdfbSgilles  * purpose with or without fee is hereby granted, provided that the above
1065c4fdfbSgilles  * copyright notice and this permission notice appear in all copies.
1165c4fdfbSgilles  *
1265c4fdfbSgilles  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
1365c4fdfbSgilles  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
1465c4fdfbSgilles  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
1565c4fdfbSgilles  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
1665c4fdfbSgilles  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
1765c4fdfbSgilles  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
1865c4fdfbSgilles  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1965c4fdfbSgilles  */
2065c4fdfbSgilles 
2165c4fdfbSgilles #include <arpa/inet.h>
2265c4fdfbSgilles #include <ctype.h>
2365c4fdfbSgilles #include <errno.h>
2465c4fdfbSgilles #include <stdlib.h>
2565c4fdfbSgilles #include <string.h>
26*0dcffd0dSop #include <time.h>
27eed85469Seric #if IO_TLS
28eed85469Seric #include <tls.h>
29eed85469Seric #endif
3065c4fdfbSgilles 
3165c4fdfbSgilles #include "smtpd.h"
3265c4fdfbSgilles #include "log.h"
3365c4fdfbSgilles 
3465c4fdfbSgilles static int alias_is_filter(struct expandnode *, const char *, size_t);
3565c4fdfbSgilles static int alias_is_username(struct expandnode *, const char *, size_t);
3665c4fdfbSgilles static int alias_is_address(struct expandnode *, const char *, size_t);
3765c4fdfbSgilles static int alias_is_filename(struct expandnode *, const char *, size_t);
3865c4fdfbSgilles static int alias_is_include(struct expandnode *, const char *, size_t);
39299c4efeSeric static int alias_is_error(struct expandnode *, const char *, size_t);
4065c4fdfbSgilles 
4165c4fdfbSgilles const char *
sockaddr_to_text(const struct sockaddr * sa)42ad380068Sclaudio sockaddr_to_text(const struct sockaddr *sa)
4365c4fdfbSgilles {
4465c4fdfbSgilles 	static char	buf[NI_MAXHOST];
4565c4fdfbSgilles 
4665c4fdfbSgilles 	if (getnameinfo(sa, sa->sa_len, buf, sizeof(buf), NULL, 0,
4765c4fdfbSgilles 	    NI_NUMERICHOST))
4865c4fdfbSgilles 		return ("(unknown)");
4965c4fdfbSgilles 	else
5065c4fdfbSgilles 		return (buf);
5165c4fdfbSgilles }
5265c4fdfbSgilles 
5365c4fdfbSgilles int
text_to_mailaddr(struct mailaddr * maddr,const char * email)5465c4fdfbSgilles text_to_mailaddr(struct mailaddr *maddr, const char *email)
5565c4fdfbSgilles {
5665c4fdfbSgilles 	char *username;
5765c4fdfbSgilles 	char *hostname;
58953aae25Sderaadt 	char  buffer[LINE_MAX];
5965c4fdfbSgilles 
6065c4fdfbSgilles 	if (strlcpy(buffer, email, sizeof buffer) >= sizeof buffer)
6165c4fdfbSgilles 		return 0;
6265c4fdfbSgilles 
63c1392a69Seric 	memset(maddr, 0, sizeof *maddr);
6465c4fdfbSgilles 
6565c4fdfbSgilles 	username = buffer;
6665c4fdfbSgilles 	hostname = strrchr(username, '@');
6765c4fdfbSgilles 
6865c4fdfbSgilles 	if (hostname == NULL) {
6965c4fdfbSgilles 		if (strlcpy(maddr->user, username, sizeof maddr->user)
7065c4fdfbSgilles 		    >= sizeof maddr->user)
7165c4fdfbSgilles 			return 0;
7265c4fdfbSgilles 	}
7365c4fdfbSgilles 	else if (username == hostname) {
7465c4fdfbSgilles 		*hostname++ = '\0';
7565c4fdfbSgilles 		if (strlcpy(maddr->domain, hostname, sizeof maddr->domain)
7665c4fdfbSgilles 		    >= sizeof maddr->domain)
7765c4fdfbSgilles 			return 0;
7865c4fdfbSgilles 	}
7965c4fdfbSgilles 	else {
8065c4fdfbSgilles 		*hostname++ = '\0';
8165c4fdfbSgilles 		if (strlcpy(maddr->user, username, sizeof maddr->user)
8265c4fdfbSgilles 		    >= sizeof maddr->user)
8365c4fdfbSgilles 			return 0;
8465c4fdfbSgilles 		if (strlcpy(maddr->domain, hostname, sizeof maddr->domain)
8565c4fdfbSgilles 		    >= sizeof maddr->domain)
8665c4fdfbSgilles 			return 0;
8765c4fdfbSgilles 	}
8865c4fdfbSgilles 
8965c4fdfbSgilles 	return 1;
9065c4fdfbSgilles }
9165c4fdfbSgilles 
9265c4fdfbSgilles const char *
mailaddr_to_text(const struct mailaddr * maddr)9365c4fdfbSgilles mailaddr_to_text(const struct mailaddr *maddr)
9465c4fdfbSgilles {
95953aae25Sderaadt 	static char  buffer[LINE_MAX];
9665c4fdfbSgilles 
97f58aa1bbSgilles 	(void)strlcpy(buffer, maddr->user, sizeof buffer);
98f58aa1bbSgilles 	(void)strlcat(buffer, "@", sizeof buffer);
9965c4fdfbSgilles 	if (strlcat(buffer, maddr->domain, sizeof buffer) >= sizeof buffer)
10065c4fdfbSgilles 		return NULL;
10165c4fdfbSgilles 
10265c4fdfbSgilles 	return buffer;
10365c4fdfbSgilles }
10465c4fdfbSgilles 
10565c4fdfbSgilles 
10665c4fdfbSgilles const char *
sa_to_text(const struct sockaddr * sa)10765c4fdfbSgilles sa_to_text(const struct sockaddr *sa)
10865c4fdfbSgilles {
10965c4fdfbSgilles 	static char	 buf[NI_MAXHOST + 5];
11065c4fdfbSgilles 	char		*p;
11165c4fdfbSgilles 
11265c4fdfbSgilles 	buf[0] = '\0';
11365c4fdfbSgilles 	p = buf;
11465c4fdfbSgilles 
11565c4fdfbSgilles 	if (sa->sa_family == AF_LOCAL)
116f58aa1bbSgilles 		(void)strlcpy(buf, "local", sizeof buf);
11765c4fdfbSgilles 	else if (sa->sa_family == AF_INET) {
11865c4fdfbSgilles 		in_addr_t addr;
11965c4fdfbSgilles 
12065c4fdfbSgilles 		addr = ((const struct sockaddr_in *)sa)->sin_addr.s_addr;
12165c4fdfbSgilles 		addr = ntohl(addr);
122f58aa1bbSgilles 		(void)bsnprintf(p, NI_MAXHOST, "%d.%d.%d.%d",
12365c4fdfbSgilles 		    (addr >> 24) & 0xff, (addr >> 16) & 0xff,
12465c4fdfbSgilles 		    (addr >> 8) & 0xff, addr & 0xff);
12565c4fdfbSgilles 	}
12665c4fdfbSgilles 	else if (sa->sa_family == AF_INET6) {
127ad380068Sclaudio 		(void)bsnprintf(p, NI_MAXHOST, "[%s]", sockaddr_to_text(sa));
12865c4fdfbSgilles 	}
12965c4fdfbSgilles 
13065c4fdfbSgilles 	return (buf);
13165c4fdfbSgilles }
13265c4fdfbSgilles 
13365c4fdfbSgilles const char *
ss_to_text(const struct sockaddr_storage * ss)13465c4fdfbSgilles ss_to_text(const struct sockaddr_storage *ss)
13565c4fdfbSgilles {
13665c4fdfbSgilles 	return (sa_to_text((const struct sockaddr*)ss));
13765c4fdfbSgilles }
13865c4fdfbSgilles 
13965c4fdfbSgilles const char *
time_to_text(time_t when)14065c4fdfbSgilles time_to_text(time_t when)
14165c4fdfbSgilles {
14265c4fdfbSgilles 	struct tm *lt;
14365c4fdfbSgilles 	static char buf[40];
1444d138b8fSop 	const char *day[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
1454d138b8fSop 	const char *month[] = {"Jan","Feb","Mar","Apr","May","Jun",
14665c4fdfbSgilles 			 "Jul","Aug","Sep","Oct","Nov","Dec"};
1474d138b8fSop 	const char *tz;
1482d6a6d31Sgilles 	long offset;
14965c4fdfbSgilles 
15065c4fdfbSgilles 	lt = localtime(&when);
15165c4fdfbSgilles 	if (lt == NULL || when == 0)
15265c4fdfbSgilles 		fatalx("time_to_text: localtime");
15365c4fdfbSgilles 
1542d6a6d31Sgilles 	offset = lt->tm_gmtoff;
1552d6a6d31Sgilles 	tz = lt->tm_zone;
1562d6a6d31Sgilles 
15765c4fdfbSgilles 	/* We do not use strftime because it is subject to locale substitution*/
15865c4fdfbSgilles 	if (!bsnprintf(buf, sizeof(buf),
15965c4fdfbSgilles 	    "%s, %d %s %d %02d:%02d:%02d %c%02d%02d (%s)",
16065c4fdfbSgilles 	    day[lt->tm_wday], lt->tm_mday, month[lt->tm_mon],
16165c4fdfbSgilles 	    lt->tm_year + 1900,
16265c4fdfbSgilles 	    lt->tm_hour, lt->tm_min, lt->tm_sec,
1632d6a6d31Sgilles 	    offset >= 0 ? '+' : '-',
1642d6a6d31Sgilles 	    abs((int)offset / 3600),
1652d6a6d31Sgilles 	    abs((int)offset % 3600) / 60,
1662d6a6d31Sgilles 	    tz))
16765c4fdfbSgilles 		fatalx("time_to_text: bsnprintf");
16865c4fdfbSgilles 
16965c4fdfbSgilles 	return buf;
17065c4fdfbSgilles }
17165c4fdfbSgilles 
17265c4fdfbSgilles const char *
duration_to_text(time_t t)17365c4fdfbSgilles duration_to_text(time_t t)
17465c4fdfbSgilles {
17565c4fdfbSgilles 	static char	dst[64];
17665c4fdfbSgilles 	char		buf[64];
1778b40001bSgilles 	int		h, m, s;
1788b40001bSgilles 	long long	d;
17965c4fdfbSgilles 
18065c4fdfbSgilles 	if (t == 0) {
181f58aa1bbSgilles 		(void)strlcpy(dst, "0s", sizeof dst);
18265c4fdfbSgilles 		return (dst);
18365c4fdfbSgilles 	}
18465c4fdfbSgilles 
18565c4fdfbSgilles 	dst[0] = '\0';
18665c4fdfbSgilles 	if (t < 0) {
187f58aa1bbSgilles 		(void)strlcpy(dst, "-", sizeof dst);
18865c4fdfbSgilles 		t = -t;
18965c4fdfbSgilles 	}
19065c4fdfbSgilles 
19165c4fdfbSgilles 	s = t % 60;
19265c4fdfbSgilles 	t /= 60;
19365c4fdfbSgilles 	m = t % 60;
19465c4fdfbSgilles 	t /= 60;
19565c4fdfbSgilles 	h = t % 24;
19665c4fdfbSgilles 	d = t / 24;
19765c4fdfbSgilles 
19865c4fdfbSgilles 	if (d) {
199f58aa1bbSgilles 		(void)snprintf(buf, sizeof buf, "%lldd", d);
200f58aa1bbSgilles 		(void)strlcat(dst, buf, sizeof dst);
20165c4fdfbSgilles 	}
20265c4fdfbSgilles 	if (h) {
203f58aa1bbSgilles 		(void)snprintf(buf, sizeof buf, "%dh", h);
204f58aa1bbSgilles 		(void)strlcat(dst, buf, sizeof dst);
20565c4fdfbSgilles 	}
20665c4fdfbSgilles 	if (m) {
207f58aa1bbSgilles 		(void)snprintf(buf, sizeof buf, "%dm", m);
208f58aa1bbSgilles 		(void)strlcat(dst, buf, sizeof dst);
20965c4fdfbSgilles 	}
21065c4fdfbSgilles 	if (s) {
211f58aa1bbSgilles 		(void)snprintf(buf, sizeof buf, "%ds", s);
212f58aa1bbSgilles 		(void)strlcat(dst, buf, sizeof dst);
21365c4fdfbSgilles 	}
21465c4fdfbSgilles 
21565c4fdfbSgilles 	return (dst);
21665c4fdfbSgilles }
21765c4fdfbSgilles 
21865c4fdfbSgilles int
text_to_netaddr(struct netaddr * netaddr,const char * s)21965c4fdfbSgilles text_to_netaddr(struct netaddr *netaddr, const char *s)
22065c4fdfbSgilles {
22165c4fdfbSgilles 	struct sockaddr_storage	ss;
22265c4fdfbSgilles 	struct sockaddr_in	ssin;
22365c4fdfbSgilles 	struct sockaddr_in6	ssin6;
22465c4fdfbSgilles 	int			bits;
22591affd4dSgilles 	char			buf[NI_MAXHOST];
22691affd4dSgilles 	size_t			len;
22765c4fdfbSgilles 
228c1392a69Seric 	memset(&ssin, 0, sizeof(struct sockaddr_in));
229c1392a69Seric 	memset(&ssin6, 0, sizeof(struct sockaddr_in6));
2301c6ac251Seric 
231299c4efeSeric 	if (strncasecmp("IPv6:", s, 5) == 0)
23265c4fdfbSgilles 		s += 5;
23365c4fdfbSgilles 
23465c4fdfbSgilles 	bits = inet_net_pton(AF_INET, s, &ssin.sin_addr,
23565c4fdfbSgilles 	    sizeof(struct in_addr));
23665c4fdfbSgilles 	if (bits != -1) {
23765c4fdfbSgilles 		ssin.sin_family = AF_INET;
23865c4fdfbSgilles 		memcpy(&ss, &ssin, sizeof(ssin));
23965c4fdfbSgilles 		ss.ss_len = sizeof(struct sockaddr_in);
240e4912120Smpi 	} else {
24191affd4dSgilles 		if (s[0] != '[') {
24291affd4dSgilles 			if ((len = strlcpy(buf, s, sizeof buf)) >= sizeof buf)
24391affd4dSgilles 				return 0;
24491affd4dSgilles 		}
24591affd4dSgilles 		else {
24691affd4dSgilles 			s++;
24791affd4dSgilles 			if (strncasecmp("IPv6:", s, 5) == 0)
24891affd4dSgilles 				s += 5;
24991affd4dSgilles 			if ((len = strlcpy(buf, s, sizeof buf)) >= sizeof buf)
25091affd4dSgilles 				return 0;
25191affd4dSgilles 			if (buf[len-1] != ']')
25291affd4dSgilles 				return 0;
25391affd4dSgilles 			buf[len-1] = 0;
25491affd4dSgilles 		}
25591affd4dSgilles 		bits = inet_net_pton(AF_INET6, buf, &ssin6.sin6_addr,
25665c4fdfbSgilles 		    sizeof(struct in6_addr));
257e4912120Smpi 		if (bits == -1)
25865c4fdfbSgilles 			return 0;
25965c4fdfbSgilles 		ssin6.sin6_family = AF_INET6;
26065c4fdfbSgilles 		memcpy(&ss, &ssin6, sizeof(ssin6));
26165c4fdfbSgilles 		ss.ss_len = sizeof(struct sockaddr_in6);
26265c4fdfbSgilles 	}
26365c4fdfbSgilles 
26465c4fdfbSgilles 	netaddr->ss   = ss;
26565c4fdfbSgilles 	netaddr->bits = bits;
26665c4fdfbSgilles 	return 1;
26765c4fdfbSgilles }
26865c4fdfbSgilles 
26965c4fdfbSgilles int
text_to_relayhost(struct relayhost * relay,const char * s)27065c4fdfbSgilles text_to_relayhost(struct relayhost *relay, const char *s)
27165c4fdfbSgilles {
27265c4fdfbSgilles 	static const struct schema {
27365c4fdfbSgilles 		const char	*name;
2744c503616Seric 		int		 tls;
275cc81b7c6Seric 		uint16_t	 flags;
2761fa40e15Skn 		uint16_t	 port;
27765c4fdfbSgilles 	} schemas [] = {
278cc81b7c6Seric 		/*
279cc81b7c6Seric 		 * new schemas should be *appended* otherwise the default
280cc81b7c6Seric 		 * schema index needs to be updated later in this function.
281cc81b7c6Seric 		 */
2821fa40e15Skn 		{ "smtp://",		RELAY_TLS_OPPORTUNISTIC, 0,		25 },
2831fa40e15Skn 		{ "smtp+tls://",	RELAY_TLS_STARTTLS,	 0,		25 },
2841fa40e15Skn 		{ "smtp+notls://",	RELAY_TLS_NO,		 0,		25 },
2851fa40e15Skn 		/* need to specify an explicit port for LMTP */
2861fa40e15Skn 		{ "lmtp://",		RELAY_TLS_NO,		 RELAY_LMTP,	0 },
2871fa40e15Skn 		{ "smtps://",		RELAY_TLS_SMTPS,	 0,		465 }
28865c4fdfbSgilles 	};
28965c4fdfbSgilles 	const char     *errstr = NULL;
29065c4fdfbSgilles 	char	       *p, *q;
29165c4fdfbSgilles 	char		buffer[1024];
292e8f79b56Sgilles 	char	       *beg, *end;
29365c4fdfbSgilles 	size_t		i;
294e8f79b56Sgilles 	size_t		len;
29565c4fdfbSgilles 
296c1392a69Seric 	memset(buffer, 0, sizeof buffer);
29765c4fdfbSgilles 	if (strlcpy(buffer, s, sizeof buffer) >= sizeof buffer)
29865c4fdfbSgilles 		return 0;
29965c4fdfbSgilles 
30065c4fdfbSgilles 	for (i = 0; i < nitems(schemas); ++i)
30165c4fdfbSgilles 		if (strncasecmp(schemas[i].name, s,
30265c4fdfbSgilles 		    strlen(schemas[i].name)) == 0)
30365c4fdfbSgilles 			break;
30465c4fdfbSgilles 
30565c4fdfbSgilles 	if (i == nitems(schemas)) {
30665c4fdfbSgilles 		/* there is a schema, but it's not recognized */
30765c4fdfbSgilles 		if (strstr(buffer, "://"))
30865c4fdfbSgilles 			return 0;
30965c4fdfbSgilles 
3108117b31eSeric 		/* no schema, default to smtp:// */
3118117b31eSeric 		i = 0;
31265c4fdfbSgilles 		p = buffer;
31365c4fdfbSgilles 	}
31465c4fdfbSgilles 	else
31565c4fdfbSgilles 		p = buffer + strlen(schemas[i].name);
31665c4fdfbSgilles 
3174c503616Seric 	relay->tls = schemas[i].tls;
31865c4fdfbSgilles 	relay->flags = schemas[i].flags;
3191fa40e15Skn 	relay->port = schemas[i].port;
3200faa9237Seric 
321e8f79b56Sgilles 	/* first, we extract the label if any */
322e8f79b56Sgilles 	if ((q = strchr(p, '@')) != NULL) {
32365c4fdfbSgilles 		*q = 0;
32465c4fdfbSgilles 		if (strlcpy(relay->authlabel, p, sizeof (relay->authlabel))
32565c4fdfbSgilles 		    >= sizeof (relay->authlabel))
32665c4fdfbSgilles 			return 0;
327e8f79b56Sgilles 		p = q + 1;
328e8f79b56Sgilles 	}
329e8f79b56Sgilles 
330e8f79b56Sgilles 	/* then, we extract the mail exchanger */
331e8f79b56Sgilles 	beg = end = p;
332e8f79b56Sgilles 	if (*beg == '[') {
333e8f79b56Sgilles 		if ((end = strchr(beg, ']')) == NULL)
334e8f79b56Sgilles 			return 0;
335e8f79b56Sgilles 		/* skip ']', it has to be included in the relay hostname */
336e8f79b56Sgilles 		++end;
337e8f79b56Sgilles 		len = end - beg;
338e8f79b56Sgilles 	}
339e8f79b56Sgilles 	else {
340e8f79b56Sgilles 		for (end = beg; *end; ++end)
341e8f79b56Sgilles 			if (!isalnum((unsigned char)*end) &&
342e8f79b56Sgilles 			    *end != '_' && *end != '.' && *end != '-')
343e8f79b56Sgilles 				break;
344e8f79b56Sgilles 		len = end - beg;
345e8f79b56Sgilles 	}
346e8f79b56Sgilles 	if (len >= sizeof relay->hostname)
347e8f79b56Sgilles 		return 0;
348e8f79b56Sgilles 	for (i = 0; i < len; ++i)
349e8f79b56Sgilles 		relay->hostname[i] = beg[i];
350e8f79b56Sgilles 	relay->hostname[i] = 0;
351e8f79b56Sgilles 
352e8f79b56Sgilles 	/* finally, we extract the port */
353e8f79b56Sgilles 	p = beg + len;
354e8f79b56Sgilles 	if (*p == ':') {
355de5139d8Skn 		relay->port = strtonum(p+1, 1, IPPORT_HILASTAUTO, &errstr);
356e8f79b56Sgilles 		if (errstr)
35765c4fdfbSgilles 			return 0;
35865c4fdfbSgilles 	}
359e8f79b56Sgilles 
360e8f79b56Sgilles 	if (!valid_domainpart(relay->hostname))
361e8f79b56Sgilles 		return 0;
36288981a9fSeric 	if ((relay->flags & RELAY_LMTP) && (relay->port == 0))
363e8f79b56Sgilles 		return 0;
3648117b31eSeric 	if (relay->authlabel[0]) {
3658117b31eSeric 		/* disallow auth on non-tls scheme. */
3664c503616Seric 		if (relay->tls != RELAY_TLS_STARTTLS &&
3674c503616Seric 		    relay->tls != RELAY_TLS_SMTPS)
368e8f79b56Sgilles 			return 0;
3698117b31eSeric 		relay->flags |= RELAY_AUTH;
3708117b31eSeric 	}
3718117b31eSeric 
37265c4fdfbSgilles 	return 1;
37365c4fdfbSgilles }
37465c4fdfbSgilles 
37565c4fdfbSgilles uint64_t
text_to_evpid(const char * s)37665c4fdfbSgilles text_to_evpid(const char *s)
37765c4fdfbSgilles {
37865c4fdfbSgilles 	uint64_t ulval;
37965c4fdfbSgilles 	char	 *ep;
38065c4fdfbSgilles 
38165c4fdfbSgilles 	errno = 0;
38265c4fdfbSgilles 	ulval = strtoull(s, &ep, 16);
38365c4fdfbSgilles 	if (s[0] == '\0' || *ep != '\0')
38465c4fdfbSgilles 		return 0;
38565c4fdfbSgilles 	if (errno == ERANGE && ulval == ULLONG_MAX)
38665c4fdfbSgilles 		return 0;
38765c4fdfbSgilles 	if (ulval == 0)
38865c4fdfbSgilles 		return 0;
38965c4fdfbSgilles 	return (ulval);
39065c4fdfbSgilles }
39165c4fdfbSgilles 
39265c4fdfbSgilles uint32_t
text_to_msgid(const char * s)39365c4fdfbSgilles text_to_msgid(const char *s)
39465c4fdfbSgilles {
39565c4fdfbSgilles 	uint64_t ulval;
39665c4fdfbSgilles 	char	 *ep;
39765c4fdfbSgilles 
39865c4fdfbSgilles 	errno = 0;
39965c4fdfbSgilles 	ulval = strtoull(s, &ep, 16);
40065c4fdfbSgilles 	if (s[0] == '\0' || *ep != '\0')
40165c4fdfbSgilles 		return 0;
40265c4fdfbSgilles 	if (errno == ERANGE && ulval == ULLONG_MAX)
40365c4fdfbSgilles 		return 0;
40465c4fdfbSgilles 	if (ulval == 0)
40565c4fdfbSgilles 		return 0;
40665c4fdfbSgilles 	if (ulval > 0xffffffff)
40765c4fdfbSgilles 		return 0;
40865c4fdfbSgilles 	return (ulval & 0xffffffff);
40965c4fdfbSgilles }
41065c4fdfbSgilles 
41165c4fdfbSgilles const char *
rule_to_text(struct rule * r)41265c4fdfbSgilles rule_to_text(struct rule *r)
41365c4fdfbSgilles {
41465c4fdfbSgilles 	static char buf[4096];
41565c4fdfbSgilles 
416c1392a69Seric 	memset(buf, 0, sizeof buf);
417a8e22235Sgilles 	(void)strlcpy(buf, "match", sizeof buf);
418a8e22235Sgilles 	if (r->flag_tag) {
419a8e22235Sgilles 		if (r->flag_tag < 0)
420f58aa1bbSgilles 			(void)strlcat(buf, " !", sizeof buf);
421a8e22235Sgilles 		(void)strlcat(buf, " tag ", sizeof buf);
422a8e22235Sgilles 		(void)strlcat(buf, r->table_tag, sizeof buf);
42365c4fdfbSgilles 	}
42465c4fdfbSgilles 
425a8e22235Sgilles 	if (r->flag_from) {
426a8e22235Sgilles 		if (r->flag_from < 0)
427a8e22235Sgilles 			(void)strlcat(buf, " !", sizeof buf);
42857bda1deSgilles 		if (r->flag_from_socket)
42957bda1deSgilles 			(void)strlcat(buf, " from socket", sizeof buf);
4305845f8e6Sgilles 		else if (r->flag_from_rdns) {
431a9b72af9Sgilles 			(void)strlcat(buf, " from rdns", sizeof buf);
432a9b72af9Sgilles 			if (r->table_from) {
433a9b72af9Sgilles 				(void)strlcat(buf, " ", sizeof buf);
43456d38692Sgilles 				(void)strlcat(buf, r->table_from, sizeof buf);
435a9b72af9Sgilles 			}
436a9b72af9Sgilles 		}
43757bda1deSgilles 		else if (strcmp(r->table_from, "<anyhost>") == 0)
438a8e22235Sgilles 			(void)strlcat(buf, " from any", sizeof buf);
439a8e22235Sgilles 		else if (strcmp(r->table_from, "<localhost>") == 0)
440a8e22235Sgilles 			(void)strlcat(buf, " from local", sizeof buf);
441a8e22235Sgilles 		else {
442a8e22235Sgilles 			(void)strlcat(buf, " from src ", sizeof buf);
443a8e22235Sgilles 			(void)strlcat(buf, r->table_from, sizeof buf);
444a8e22235Sgilles 		}
44565c4fdfbSgilles 	}
44665c4fdfbSgilles 
447a8e22235Sgilles 	if (r->flag_for) {
448a8e22235Sgilles 		if (r->flag_for < 0)
449a8e22235Sgilles 			(void)strlcat(buf, " !", sizeof buf);
450a8e22235Sgilles 		if (strcmp(r->table_for, "<anydestination>") == 0)
451a8e22235Sgilles 			(void)strlcat(buf, " for any", sizeof buf);
452a8e22235Sgilles 		else if (strcmp(r->table_for, "<localnames>") == 0)
453a8e22235Sgilles 			(void)strlcat(buf, " for local", sizeof buf);
454a8e22235Sgilles 		else {
455a8e22235Sgilles 			(void)strlcat(buf, " for domain ", sizeof buf);
456a8e22235Sgilles 			(void)strlcat(buf, r->table_for, sizeof buf);
457a8e22235Sgilles 		}
458a8e22235Sgilles 	}
459a8e22235Sgilles 
460a8e22235Sgilles 	if (r->flag_smtp_helo) {
461a8e22235Sgilles 		if (r->flag_smtp_helo < 0)
462a8e22235Sgilles 			(void)strlcat(buf, " !", sizeof buf);
463a8e22235Sgilles 		(void)strlcat(buf, " helo ", sizeof buf);
464a8e22235Sgilles 		(void)strlcat(buf, r->table_smtp_helo, sizeof buf);
465a8e22235Sgilles 	}
466a8e22235Sgilles 
467a8e22235Sgilles 	if (r->flag_smtp_auth) {
468a8e22235Sgilles 		if (r->flag_smtp_auth < 0)
469a8e22235Sgilles 			(void)strlcat(buf, " !", sizeof buf);
470a8e22235Sgilles 		(void)strlcat(buf, " auth", sizeof buf);
471a8e22235Sgilles 		if (r->table_smtp_auth) {
472a8e22235Sgilles 			(void)strlcat(buf, " ", sizeof buf);
47356d38692Sgilles 			(void)strlcat(buf, r->table_smtp_auth, sizeof buf);
474a8e22235Sgilles 		}
475a8e22235Sgilles 	}
476a8e22235Sgilles 
477a8e22235Sgilles 	if (r->flag_smtp_starttls) {
478a8e22235Sgilles 		if (r->flag_smtp_starttls < 0)
479a8e22235Sgilles 			(void)strlcat(buf, " !", sizeof buf);
480ec43f4a8Sgilles 		(void)strlcat(buf, " tls", sizeof buf);
481a8e22235Sgilles 	}
482a8e22235Sgilles 
483a8e22235Sgilles 	if (r->flag_smtp_mail_from) {
484a8e22235Sgilles 		if (r->flag_smtp_mail_from < 0)
485a8e22235Sgilles 			(void)strlcat(buf, " !", sizeof buf);
486a8e22235Sgilles 		(void)strlcat(buf, " mail-from ", sizeof buf);
487a8e22235Sgilles 		(void)strlcat(buf, r->table_smtp_mail_from, sizeof buf);
488a8e22235Sgilles 	}
489a8e22235Sgilles 
490a8e22235Sgilles 	if (r->flag_smtp_rcpt_to) {
491a8e22235Sgilles 		if (r->flag_smtp_rcpt_to < 0)
492a8e22235Sgilles 			(void)strlcat(buf, " !", sizeof buf);
493a8e22235Sgilles 		(void)strlcat(buf, " rcpt-to ", sizeof buf);
494a8e22235Sgilles 		(void)strlcat(buf, r->table_smtp_rcpt_to, sizeof buf);
495a8e22235Sgilles 	}
49656d38692Sgilles 	(void)strlcat(buf, " action ", sizeof buf);
497a8e22235Sgilles 	if (r->reject)
498a8e22235Sgilles 		(void)strlcat(buf, "reject", sizeof buf);
499a8e22235Sgilles 	else
500a8e22235Sgilles 		(void)strlcat(buf, r->dispatcher, sizeof buf);
50165c4fdfbSgilles 	return buf;
50265c4fdfbSgilles }
50365c4fdfbSgilles 
504a8e22235Sgilles 
50565c4fdfbSgilles int
text_to_userinfo(struct userinfo * userinfo,const char * s)50665c4fdfbSgilles text_to_userinfo(struct userinfo *userinfo, const char *s)
50765c4fdfbSgilles {
508953aae25Sderaadt 	char		buf[PATH_MAX];
50965c4fdfbSgilles 	char	       *p;
51065c4fdfbSgilles 	const char     *errstr;
51165c4fdfbSgilles 
512c1392a69Seric 	memset(buf, 0, sizeof buf);
51365c4fdfbSgilles 	p = buf;
51465c4fdfbSgilles 	while (*s && *s != ':')
51565c4fdfbSgilles 		*p++ = *s++;
51665c4fdfbSgilles 	if (*s++ != ':')
51765c4fdfbSgilles 		goto error;
51865c4fdfbSgilles 
51965c4fdfbSgilles 	if (strlcpy(userinfo->username, buf,
52065c4fdfbSgilles 		sizeof userinfo->username) >= sizeof userinfo->username)
52165c4fdfbSgilles 		goto error;
52265c4fdfbSgilles 
523c1392a69Seric 	memset(buf, 0, sizeof buf);
52465c4fdfbSgilles 	p = buf;
52565c4fdfbSgilles 	while (*s && *s != ':')
52665c4fdfbSgilles 		*p++ = *s++;
52765c4fdfbSgilles 	if (*s++ != ':')
52865c4fdfbSgilles 		goto error;
52965c4fdfbSgilles 	userinfo->uid = strtonum(buf, 0, UID_MAX, &errstr);
53065c4fdfbSgilles 	if (errstr)
53165c4fdfbSgilles 		goto error;
53265c4fdfbSgilles 
533c1392a69Seric 	memset(buf, 0, sizeof buf);
53465c4fdfbSgilles 	p = buf;
53565c4fdfbSgilles 	while (*s && *s != ':')
53665c4fdfbSgilles 		*p++ = *s++;
53765c4fdfbSgilles 	if (*s++ != ':')
53865c4fdfbSgilles 		goto error;
53965c4fdfbSgilles 	userinfo->gid = strtonum(buf, 0, GID_MAX, &errstr);
54065c4fdfbSgilles 	if (errstr)
54165c4fdfbSgilles 		goto error;
54265c4fdfbSgilles 
54365c4fdfbSgilles 	if (strlcpy(userinfo->directory, s,
54465c4fdfbSgilles 		sizeof userinfo->directory) >= sizeof userinfo->directory)
54565c4fdfbSgilles 		goto error;
54665c4fdfbSgilles 
54765c4fdfbSgilles 	return 1;
54865c4fdfbSgilles 
54965c4fdfbSgilles error:
55065c4fdfbSgilles 	return 0;
55165c4fdfbSgilles }
55265c4fdfbSgilles 
55365c4fdfbSgilles int
text_to_credentials(struct credentials * creds,const char * s)55465c4fdfbSgilles text_to_credentials(struct credentials *creds, const char *s)
55565c4fdfbSgilles {
55665c4fdfbSgilles 	char   *p;
557953aae25Sderaadt 	char	buffer[LINE_MAX];
55865c4fdfbSgilles 	size_t	offset;
55965c4fdfbSgilles 
56065c4fdfbSgilles 	p = strchr(s, ':');
56165c4fdfbSgilles 	if (p == NULL) {
56265c4fdfbSgilles 		creds->username[0] = '\0';
56365c4fdfbSgilles 		if (strlcpy(creds->password, s, sizeof creds->password)
56465c4fdfbSgilles 		    >= sizeof creds->password)
56565c4fdfbSgilles 			return 0;
56665c4fdfbSgilles 		return 1;
56765c4fdfbSgilles 	}
56865c4fdfbSgilles 
56965c4fdfbSgilles 	offset = p - s;
57065c4fdfbSgilles 
571c1392a69Seric 	memset(buffer, 0, sizeof buffer);
57265c4fdfbSgilles 	if (strlcpy(buffer, s, sizeof buffer) >= sizeof buffer)
57365c4fdfbSgilles 		return 0;
57465c4fdfbSgilles 	p = buffer + offset;
57565c4fdfbSgilles 	*p = '\0';
57665c4fdfbSgilles 
57765c4fdfbSgilles 	if (strlcpy(creds->username, buffer, sizeof creds->username)
57865c4fdfbSgilles 	    >= sizeof creds->username)
57965c4fdfbSgilles 		return 0;
58065c4fdfbSgilles 	if (strlcpy(creds->password, p+1, sizeof creds->password)
58165c4fdfbSgilles 	    >= sizeof creds->password)
58265c4fdfbSgilles 		return 0;
58365c4fdfbSgilles 
58465c4fdfbSgilles 	return 1;
58565c4fdfbSgilles }
58665c4fdfbSgilles 
58765c4fdfbSgilles int
text_to_expandnode(struct expandnode * expandnode,const char * s)58865c4fdfbSgilles text_to_expandnode(struct expandnode *expandnode, const char *s)
58965c4fdfbSgilles {
59065c4fdfbSgilles 	size_t	l;
59165c4fdfbSgilles 
59265c4fdfbSgilles 	l = strlen(s);
593299c4efeSeric 	if (alias_is_error(expandnode, s, l) ||
594299c4efeSeric 	    alias_is_include(expandnode, s, l) ||
59565c4fdfbSgilles 	    alias_is_filter(expandnode, s, l) ||
59665c4fdfbSgilles 	    alias_is_filename(expandnode, s, l) ||
59765c4fdfbSgilles 	    alias_is_address(expandnode, s, l) ||
59865c4fdfbSgilles 	    alias_is_username(expandnode, s, l))
59965c4fdfbSgilles 		return (1);
60065c4fdfbSgilles 
60165c4fdfbSgilles 	return (0);
60265c4fdfbSgilles }
60365c4fdfbSgilles 
60459a46edcSgilles const char *
expandnode_to_text(struct expandnode * expandnode)60559a46edcSgilles expandnode_to_text(struct expandnode *expandnode)
60659a46edcSgilles {
60759a46edcSgilles 	switch (expandnode->type) {
60859a46edcSgilles 	case EXPAND_FILTER:
60959a46edcSgilles 	case EXPAND_FILENAME:
61059a46edcSgilles 	case EXPAND_INCLUDE:
611299c4efeSeric 	case EXPAND_ERROR:
61259a46edcSgilles 	case EXPAND_USERNAME:
61359a46edcSgilles 		return expandnode->u.user;
61459a46edcSgilles 	case EXPAND_ADDRESS:
61559a46edcSgilles 		return mailaddr_to_text(&expandnode->u.mailaddr);
61659a46edcSgilles 	case EXPAND_INVALID:
61759a46edcSgilles 		break;
61859a46edcSgilles 	}
61959a46edcSgilles 
62059a46edcSgilles 	return NULL;
62159a46edcSgilles }
62259a46edcSgilles 
623e8f79b56Sgilles /******/
624934f8fd8Sgilles static int
alias_is_filter(struct expandnode * alias,const char * line,size_t len)62565c4fdfbSgilles alias_is_filter(struct expandnode *alias, const char *line, size_t len)
62665c4fdfbSgilles {
627d699b7bdSeric 	int	v = 0;
628d699b7bdSeric 
629d699b7bdSeric 	if (*line == '"')
630d699b7bdSeric 		v = 1;
631d699b7bdSeric 	if (*(line+v) == '|') {
632d699b7bdSeric 		if (strlcpy(alias->u.buffer, line + v + 1,
63365c4fdfbSgilles 		    sizeof(alias->u.buffer)) >= sizeof(alias->u.buffer))
63465c4fdfbSgilles 			return 0;
635d699b7bdSeric 		if (v) {
636d699b7bdSeric 			v = strlen(alias->u.buffer);
637d699b7bdSeric 			if (v == 0)
638d699b7bdSeric 				return (0);
639d699b7bdSeric 			if (alias->u.buffer[v-1] != '"')
640d699b7bdSeric 				return (0);
641d699b7bdSeric 			alias->u.buffer[v-1] = '\0';
64265c4fdfbSgilles 		}
643d699b7bdSeric 		alias->type = EXPAND_FILTER;
644d699b7bdSeric 		return (1);
645d699b7bdSeric 	}
646d699b7bdSeric 	return (0);
64765c4fdfbSgilles }
64865c4fdfbSgilles 
64965c4fdfbSgilles static int
alias_is_username(struct expandnode * alias,const char * line,size_t len)65065c4fdfbSgilles alias_is_username(struct expandnode *alias, const char *line, size_t len)
65165c4fdfbSgilles {
652c1392a69Seric 	memset(alias, 0, sizeof *alias);
65365c4fdfbSgilles 
65465c4fdfbSgilles 	if (strlcpy(alias->u.user, line,
65565c4fdfbSgilles 	    sizeof(alias->u.user)) >= sizeof(alias->u.user))
65665c4fdfbSgilles 		return 0;
65765c4fdfbSgilles 
65865c4fdfbSgilles 	while (*line) {
659fc3a8311Seric 		if (!isalnum((unsigned char)*line) &&
660e8f79b56Sgilles 		    *line != '_' && *line != '.' && *line != '-' && *line != '+')
66165c4fdfbSgilles 			return 0;
66265c4fdfbSgilles 		++line;
66365c4fdfbSgilles 	}
66465c4fdfbSgilles 
66565c4fdfbSgilles 	alias->type = EXPAND_USERNAME;
66665c4fdfbSgilles 	return 1;
66765c4fdfbSgilles }
66865c4fdfbSgilles 
66965c4fdfbSgilles static int
alias_is_address(struct expandnode * alias,const char * line,size_t len)67065c4fdfbSgilles alias_is_address(struct expandnode *alias, const char *line, size_t len)
67165c4fdfbSgilles {
67265c4fdfbSgilles 	char *domain;
67365c4fdfbSgilles 
674c1392a69Seric 	memset(alias, 0, sizeof *alias);
67565c4fdfbSgilles 
67665c4fdfbSgilles 	if (len < 3)	/* x@y */
67765c4fdfbSgilles 		return 0;
67865c4fdfbSgilles 
67965c4fdfbSgilles 	domain = strchr(line, '@');
68065c4fdfbSgilles 	if (domain == NULL)
68165c4fdfbSgilles 		return 0;
68265c4fdfbSgilles 
68365c4fdfbSgilles 	/* @ cannot start or end an address */
68465c4fdfbSgilles 	if (domain == line || domain == line + len - 1)
68565c4fdfbSgilles 		return 0;
68665c4fdfbSgilles 
68765c4fdfbSgilles 	/* scan pre @ for disallowed chars */
68865c4fdfbSgilles 	*domain++ = '\0';
689f58aa1bbSgilles 	(void)strlcpy(alias->u.mailaddr.user, line, sizeof(alias->u.mailaddr.user));
690f58aa1bbSgilles 	(void)strlcpy(alias->u.mailaddr.domain, domain,
69165c4fdfbSgilles 	    sizeof(alias->u.mailaddr.domain));
69265c4fdfbSgilles 
69365c4fdfbSgilles 	while (*line) {
69465c4fdfbSgilles 		char allowedset[] = "!#$%*/?|^{}`~&'+-=_.";
695fc3a8311Seric 		if (!isalnum((unsigned char)*line) &&
69665c4fdfbSgilles 		    strchr(allowedset, *line) == NULL)
69765c4fdfbSgilles 			return 0;
69865c4fdfbSgilles 		++line;
69965c4fdfbSgilles 	}
70065c4fdfbSgilles 
70165c4fdfbSgilles 	while (*domain) {
70265c4fdfbSgilles 		char allowedset[] = "-.";
703fc3a8311Seric 		if (!isalnum((unsigned char)*domain) &&
70465c4fdfbSgilles 		    strchr(allowedset, *domain) == NULL)
70565c4fdfbSgilles 			return 0;
70665c4fdfbSgilles 		++domain;
70765c4fdfbSgilles 	}
70865c4fdfbSgilles 
70965c4fdfbSgilles 	alias->type = EXPAND_ADDRESS;
71065c4fdfbSgilles 	return 1;
71165c4fdfbSgilles }
71265c4fdfbSgilles 
71365c4fdfbSgilles static int
alias_is_filename(struct expandnode * alias,const char * line,size_t len)71465c4fdfbSgilles alias_is_filename(struct expandnode *alias, const char *line, size_t len)
71565c4fdfbSgilles {
716c1392a69Seric 	memset(alias, 0, sizeof *alias);
71765c4fdfbSgilles 
71865c4fdfbSgilles 	if (*line != '/')
71965c4fdfbSgilles 		return 0;
72065c4fdfbSgilles 
72165c4fdfbSgilles 	if (strlcpy(alias->u.buffer, line,
72265c4fdfbSgilles 	    sizeof(alias->u.buffer)) >= sizeof(alias->u.buffer))
72365c4fdfbSgilles 		return 0;
72465c4fdfbSgilles 	alias->type = EXPAND_FILENAME;
72565c4fdfbSgilles 	return 1;
72665c4fdfbSgilles }
72765c4fdfbSgilles 
72865c4fdfbSgilles static int
alias_is_include(struct expandnode * alias,const char * line,size_t len)72965c4fdfbSgilles alias_is_include(struct expandnode *alias, const char *line, size_t len)
73065c4fdfbSgilles {
73165c4fdfbSgilles 	size_t skip;
73265c4fdfbSgilles 
733c1392a69Seric 	memset(alias, 0, sizeof *alias);
73465c4fdfbSgilles 
73565c4fdfbSgilles 	if (strncasecmp(":include:", line, 9) == 0)
73665c4fdfbSgilles 		skip = 9;
73765c4fdfbSgilles 	else if (strncasecmp("include:", line, 8) == 0)
73865c4fdfbSgilles 		skip = 8;
73965c4fdfbSgilles 	else
74065c4fdfbSgilles 		return 0;
74165c4fdfbSgilles 
74265c4fdfbSgilles 	if (!alias_is_filename(alias, line + skip, len - skip))
74365c4fdfbSgilles 		return 0;
74465c4fdfbSgilles 
74565c4fdfbSgilles 	alias->type = EXPAND_INCLUDE;
74665c4fdfbSgilles 	return 1;
74765c4fdfbSgilles }
748299c4efeSeric 
749299c4efeSeric static int
alias_is_error(struct expandnode * alias,const char * line,size_t len)750299c4efeSeric alias_is_error(struct expandnode *alias, const char *line, size_t len)
751299c4efeSeric {
752299c4efeSeric 	size_t	skip;
753299c4efeSeric 
754c1392a69Seric 	memset(alias, 0, sizeof *alias);
755299c4efeSeric 
756299c4efeSeric 	if (strncasecmp(":error:", line, 7) == 0)
757299c4efeSeric 		skip = 7;
758299c4efeSeric 	else if (strncasecmp("error:", line, 6) == 0)
759299c4efeSeric 		skip = 6;
760299c4efeSeric 	else
761299c4efeSeric 		return 0;
762299c4efeSeric 
763299c4efeSeric 	if (strlcpy(alias->u.buffer, line + skip,
764299c4efeSeric 	    sizeof(alias->u.buffer)) >= sizeof(alias->u.buffer))
765299c4efeSeric 		return 0;
766299c4efeSeric 
767299c4efeSeric 	if (strlen(alias->u.buffer) < 5)
768299c4efeSeric 		return 0;
769299c4efeSeric 
770299c4efeSeric 	/* [45][0-9]{2} [a-zA-Z0-9].* */
771fc3a8311Seric 	if (alias->u.buffer[3] != ' ' ||
772fc3a8311Seric 	    !isalnum((unsigned char)alias->u.buffer[4]) ||
773299c4efeSeric 	    (alias->u.buffer[0] != '4' && alias->u.buffer[0] != '5') ||
774fc3a8311Seric 	    !isdigit((unsigned char)alias->u.buffer[1]) ||
775fc3a8311Seric 	    !isdigit((unsigned char)alias->u.buffer[2]))
776299c4efeSeric 		return 0;
777299c4efeSeric 
778299c4efeSeric 	alias->type = EXPAND_ERROR;
779299c4efeSeric 	return 1;
780299c4efeSeric }
781eed85469Seric 
782eed85469Seric #if IO_TLS
783eed85469Seric const char *
tls_to_text(struct tls * tls)784eed85469Seric tls_to_text(struct tls *tls)
785eed85469Seric {
786eed85469Seric 	static char buf[256];
787eed85469Seric 
788eed85469Seric 	(void)snprintf(buf, sizeof buf, "%s:%s:%d",
789eed85469Seric 	    tls_conn_version(tls),
790eed85469Seric 	    tls_conn_cipher(tls),
791eed85469Seric 	    tls_conn_cipher_strength(tls));
792eed85469Seric 
793eed85469Seric 	return (buf);
794eed85469Seric }
795eed85469Seric #endif
796