xref: /openbsd-src/usr.sbin/smtpd/util.c (revision 91ef716ecec67e8f64eb87412ef2176eeb02b9c3)
1*91ef716eSjsg /*	$OpenBSD: util.c,v 1.159 2024/06/02 23:26:39 jsg Exp $	*/
2939984b2Sgilles 
3939984b2Sgilles /*
430183bdbSjacekm  * Copyright (c) 2000,2001 Markus Friedl.  All rights reserved.
565c4fdfbSgilles  * Copyright (c) 2008 Gilles Chehade <gilles@poolp.org>
6e5b07014Sgilles  * Copyright (c) 2009 Jacek Masiulaniec <jacekm@dobremiasto.net>
772bc847cSeric  * Copyright (c) 2012 Eric Faurot <eric@openbsd.org>
8939984b2Sgilles  *
9939984b2Sgilles  * Permission to use, copy, modify, and distribute this software for any
10939984b2Sgilles  * purpose with or without fee is hereby granted, provided that the above
11939984b2Sgilles  * copyright notice and this permission notice appear in all copies.
12939984b2Sgilles  *
13939984b2Sgilles  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
14939984b2Sgilles  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
15939984b2Sgilles  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
16939984b2Sgilles  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
17939984b2Sgilles  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
18939984b2Sgilles  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
19939984b2Sgilles  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
20939984b2Sgilles  */
21939984b2Sgilles 
2244307885Sjacekm #include <sys/stat.h>
23939984b2Sgilles 
2425080696Sgilles #include <netinet/in.h>
2525080696Sgilles 
26d3140113Seric #include <arpa/inet.h>
272b90997cSgilles #include <ctype.h>
28939984b2Sgilles #include <errno.h>
29591aa05bSeric #include <fts.h>
3044307885Sjacekm #include <libgen.h>
31254aed36Seric #include <resolv.h>
32378b56faSgilles #include <stdarg.h>
3344307885Sjacekm #include <stdlib.h>
34939984b2Sgilles #include <string.h>
35ccfb4053Seric #include <syslog.h>
36939984b2Sgilles #include <unistd.h>
37939984b2Sgilles 
38939984b2Sgilles #include "smtpd.h"
395eb8dddaSgilles #include "log.h"
40939984b2Sgilles 
4136e884f4Ssunil static int  parse_mailname_file(char *, size_t);
4225080696Sgilles 
43f24248b7Sreyk int	tracing = 0;
44f24248b7Sreyk int	foreground_log = 0;
45f24248b7Sreyk 
4672bc847cSeric void *
xmalloc(size_t size)47118c16f3Sgilles xmalloc(size_t size)
4872bc847cSeric {
4972bc847cSeric 	void	*r;
5072bc847cSeric 
51118c16f3Sgilles 	if ((r = malloc(size)) == NULL)
52118c16f3Sgilles 		fatal("malloc");
5372bc847cSeric 
5472bc847cSeric 	return (r);
5572bc847cSeric }
5672bc847cSeric 
5772bc847cSeric void *
xcalloc(size_t nmemb,size_t size)58118c16f3Sgilles xcalloc(size_t nmemb, size_t size)
5972bc847cSeric {
6072bc847cSeric 	void	*r;
6172bc847cSeric 
62118c16f3Sgilles 	if ((r = calloc(nmemb, size)) == NULL)
63118c16f3Sgilles 		fatal("calloc");
6472bc847cSeric 
6572bc847cSeric 	return (r);
6672bc847cSeric }
6772bc847cSeric 
6872bc847cSeric char *
xstrdup(const char * str)69118c16f3Sgilles xstrdup(const char *str)
7072bc847cSeric {
7172bc847cSeric 	char	*r;
7272bc847cSeric 
73118c16f3Sgilles 	if ((r = strdup(str)) == NULL)
74118c16f3Sgilles 		fatal("strdup");
7572bc847cSeric 
7672bc847cSeric 	return (r);
7772bc847cSeric }
7872bc847cSeric 
79591e1804Seric void *
xmemdup(const void * ptr,size_t size)80118c16f3Sgilles xmemdup(const void *ptr, size_t size)
81591e1804Seric {
82591e1804Seric 	void	*r;
83591e1804Seric 
84118c16f3Sgilles 	if ((r = malloc(size)) == NULL)
85118c16f3Sgilles 		fatal("malloc");
86118c16f3Sgilles 
87591e1804Seric 	memmove(r, ptr, size);
88591e1804Seric 
89591e1804Seric 	return (r);
90591e1804Seric }
91591e1804Seric 
92118c16f3Sgilles int
xasprintf(char ** ret,const char * format,...)93118c16f3Sgilles xasprintf(char **ret, const char *format, ...)
942df8b630Sgilles {
95118c16f3Sgilles 	int r;
962df8b630Sgilles 	va_list ap;
972df8b630Sgilles 
982df8b630Sgilles 	va_start(ap, format);
99118c16f3Sgilles 	r = vasprintf(ret, format, ap);
1002df8b630Sgilles 	va_end(ap);
101118c16f3Sgilles 	if (r == -1)
102118c16f3Sgilles 		fatal("vasprintf");
103118c16f3Sgilles 
104118c16f3Sgilles 	return (r);
1052df8b630Sgilles }
1062df8b630Sgilles 
1072df8b630Sgilles 
10893f98431Schl #if !defined(NO_IO)
10966802da1Seric int
io_xprintf(struct io * io,const char * fmt,...)11066802da1Seric io_xprintf(struct io *io, const char *fmt, ...)
11166802da1Seric {
11266802da1Seric 	va_list	ap;
11366802da1Seric 	int len;
11466802da1Seric 
11566802da1Seric 	va_start(ap, fmt);
11666802da1Seric 	len = io_vprintf(io, fmt, ap);
11766802da1Seric 	va_end(ap);
11866802da1Seric 	if (len == -1)
11966802da1Seric 		fatal("io_xprintf(%p, %s, ...)", io, fmt);
12066802da1Seric 
12166802da1Seric 	return len;
12266802da1Seric }
12366802da1Seric 
12466802da1Seric int
io_xprint(struct io * io,const char * str)12566802da1Seric io_xprint(struct io *io, const char *str)
12666802da1Seric {
12766802da1Seric 	int len;
12866802da1Seric 
12966802da1Seric 	len = io_print(io, str);
13066802da1Seric 	if (len == -1)
13166802da1Seric 		fatal("io_xprint(%p, %s, ...)", io, str);
13266802da1Seric 
13366802da1Seric 	return len;
13466802da1Seric }
13593f98431Schl #endif
13693f98431Schl 
13765c4fdfbSgilles char *
strip(char * s)13865c4fdfbSgilles strip(char *s)
13965c4fdfbSgilles {
14065c4fdfbSgilles 	size_t	 l;
14165c4fdfbSgilles 
142b2232dc0Sgilles 	while (isspace((unsigned char)*s))
14365c4fdfbSgilles 		s++;
14465c4fdfbSgilles 
14565c4fdfbSgilles 	for (l = strlen(s); l; l--) {
146b2232dc0Sgilles 		if (!isspace((unsigned char)s[l-1]))
14765c4fdfbSgilles 			break;
14865c4fdfbSgilles 		s[l-1] = '\0';
14965c4fdfbSgilles 	}
15065c4fdfbSgilles 
15165c4fdfbSgilles 	return (s);
15265c4fdfbSgilles }
15365c4fdfbSgilles 
154939984b2Sgilles int
bsnprintf(char * str,size_t size,const char * format,...)155939984b2Sgilles bsnprintf(char *str, size_t size, const char *format, ...)
156939984b2Sgilles {
157939984b2Sgilles 	int ret;
158939984b2Sgilles 	va_list ap;
159939984b2Sgilles 
160939984b2Sgilles 	va_start(ap, format);
161939984b2Sgilles 	ret = vsnprintf(str, size, format, ap);
162939984b2Sgilles 	va_end(ap);
16396051a29Stb 	if (ret < 0 || (size_t)ret >= size)
164939984b2Sgilles 		return 0;
165939984b2Sgilles 
166939984b2Sgilles 	return 1;
167939984b2Sgilles }
168a933d224Sjacekm 
169fc240c15Sgilles 
1707a71916fSeric int
ckdir(const char * path,mode_t mode,uid_t owner,gid_t group,int create)1717a71916fSeric ckdir(const char *path, mode_t mode, uid_t owner, gid_t group, int create)
1727a71916fSeric {
1737a71916fSeric 	char		mode_str[12];
1747a71916fSeric 	int		ret;
1757a71916fSeric 	struct stat	sb;
1767a71916fSeric 
1777a71916fSeric 	if (stat(path, &sb) == -1) {
1787a71916fSeric 		if (errno != ENOENT || create == 0) {
179b832907bSeric 			log_warn("stat: %s", path);
1807a71916fSeric 			return (0);
1817a71916fSeric 		}
1827a71916fSeric 
1837a71916fSeric 		/* chmod is deferred to avoid umask effect */
1847a71916fSeric 		if (mkdir(path, 0) == -1) {
185b832907bSeric 			log_warn("mkdir: %s", path);
1867a71916fSeric 			return (0);
1877a71916fSeric 		}
1887a71916fSeric 
1897a71916fSeric 		if (chown(path, owner, group) == -1) {
190b832907bSeric 			log_warn("chown: %s", path);
1917a71916fSeric 			return (0);
1927a71916fSeric 		}
1937a71916fSeric 
1947a71916fSeric 		if (chmod(path, mode) == -1) {
195b832907bSeric 			log_warn("chmod: %s", path);
1967a71916fSeric 			return (0);
1977a71916fSeric 		}
1987a71916fSeric 
1997a71916fSeric 		if (stat(path, &sb) == -1) {
200b832907bSeric 			log_warn("stat: %s", path);
2017a71916fSeric 			return (0);
2027a71916fSeric 		}
2037a71916fSeric 	}
2047a71916fSeric 
2057a71916fSeric 	ret = 1;
2067a71916fSeric 
2077a71916fSeric 	/* check if it's a directory */
2087a71916fSeric 	if (!S_ISDIR(sb.st_mode)) {
2097a71916fSeric 		ret = 0;
210b832907bSeric 		log_warnx("%s is not a directory", path);
2117a71916fSeric 	}
2127a71916fSeric 
2137a71916fSeric 	/* check that it is owned by owner/group */
2147a71916fSeric 	if (sb.st_uid != owner) {
2157a71916fSeric 		ret = 0;
216b832907bSeric 		log_warnx("%s is not owned by uid %d", path, owner);
2177a71916fSeric 	}
2187a71916fSeric 	if (sb.st_gid != group) {
2197a71916fSeric 		ret = 0;
220b832907bSeric 		log_warnx("%s is not owned by gid %d", path, group);
2217a71916fSeric 	}
2227a71916fSeric 
2237a71916fSeric 	/* check permission */
2247a71916fSeric 	if ((sb.st_mode & 07777) != mode) {
2257a71916fSeric 		ret = 0;
2267a71916fSeric 		strmode(mode, mode_str);
2277a71916fSeric 		mode_str[10] = '\0';
228b832907bSeric 		log_warnx("%s must be %s (%o)", path, mode_str + 1, mode);
2297a71916fSeric 	}
2307a71916fSeric 
2317a71916fSeric 	return ret;
2327a71916fSeric }
2337a71916fSeric 
234591aa05bSeric int
rmtree(char * path,int keepdir)235591aa05bSeric rmtree(char *path, int keepdir)
236591aa05bSeric {
237591aa05bSeric 	char		*path_argv[2];
238591aa05bSeric 	FTS		*fts;
239591aa05bSeric 	FTSENT		*e;
240591aa05bSeric 	int		 ret, depth;
241591aa05bSeric 
242591aa05bSeric 	path_argv[0] = path;
243591aa05bSeric 	path_argv[1] = NULL;
244591aa05bSeric 	ret = 0;
24565c4fdfbSgilles 	depth = 0;
246591aa05bSeric 
24765c4fdfbSgilles 	fts = fts_open(path_argv, FTS_PHYSICAL | FTS_NOCHDIR, NULL);
24865c4fdfbSgilles 	if (fts == NULL) {
249b832907bSeric 		log_warn("fts_open: %s", path);
250591aa05bSeric 		return (-1);
251591aa05bSeric 	}
252591aa05bSeric 
253591aa05bSeric 	while ((e = fts_read(fts)) != NULL) {
25465c4fdfbSgilles 		switch (e->fts_info) {
25565c4fdfbSgilles 		case FTS_D:
25665c4fdfbSgilles 			depth++;
25765c4fdfbSgilles 			break;
25865c4fdfbSgilles 		case FTS_DP:
25965c4fdfbSgilles 		case FTS_DNR:
260591aa05bSeric 			depth--;
26165c4fdfbSgilles 			if (keepdir && depth == 0)
262591aa05bSeric 				continue;
263591aa05bSeric 			if (rmdir(e->fts_path) == -1) {
264b832907bSeric 				log_warn("rmdir: %s", e->fts_path);
265591aa05bSeric 				ret = -1;
266591aa05bSeric 			}
26765c4fdfbSgilles 			break;
268591aa05bSeric 
26965c4fdfbSgilles 		case FTS_F:
270591aa05bSeric 			if (unlink(e->fts_path) == -1) {
271b832907bSeric 				log_warn("unlink: %s", e->fts_path);
272591aa05bSeric 				ret = -1;
273591aa05bSeric 			}
274591aa05bSeric 		}
27565c4fdfbSgilles 	}
276591aa05bSeric 
277591aa05bSeric 	fts_close(fts);
278591aa05bSeric 
279591aa05bSeric 	return (ret);
280591aa05bSeric }
281591aa05bSeric 
282591aa05bSeric int
mvpurge(char * from,char * to)283591aa05bSeric mvpurge(char *from, char *to)
284591aa05bSeric {
285591aa05bSeric 	size_t		 n;
286591aa05bSeric 	int		 retry;
287591aa05bSeric 	const char	*sep;
288953aae25Sderaadt 	char		 buf[PATH_MAX];
289591aa05bSeric 
290591aa05bSeric 	if ((n = strlen(to)) == 0)
291591aa05bSeric 		fatalx("to is empty");
292591aa05bSeric 
293591aa05bSeric 	sep = (to[n - 1] == '/') ? "" : "/";
294591aa05bSeric 	retry = 0;
295591aa05bSeric 
296591aa05bSeric again:
2979b886139Sgilles 	(void)snprintf(buf, sizeof buf, "%s%s%u", to, sep, arc4random());
298591aa05bSeric 	if (rename(from, buf) == -1) {
299591aa05bSeric 		/* ENOTDIR has actually 2 meanings, and incorrect input
300591aa05bSeric 		 * could lead to an infinite loop. Consider that after
301591aa05bSeric 		 * 20 tries something is hopelessly wrong.
302591aa05bSeric 		 */
303591aa05bSeric 		if (errno == ENOTEMPTY || errno == EISDIR || errno == ENOTDIR) {
304591aa05bSeric 			if ((retry++) >= 20)
305591aa05bSeric 				return (-1);
306591aa05bSeric 			goto again;
307591aa05bSeric 		}
308591aa05bSeric 		return -1;
309591aa05bSeric 	}
310591aa05bSeric 
311591aa05bSeric 	return 0;
312591aa05bSeric }
313591aa05bSeric 
3147a71916fSeric 
3150e8cc8ecSchl int
mktmpfile(void)3160e8cc8ecSchl mktmpfile(void)
3170e8cc8ecSchl {
318953aae25Sderaadt 	char		path[PATH_MAX];
3190e8cc8ecSchl 	int		fd;
3200e8cc8ecSchl 
3217bdbba2fSeric 	if (!bsnprintf(path, sizeof(path), "%s/smtpd.XXXXXXXXXX",
322b832907bSeric 		PATH_TEMPORARY)) {
323b832907bSeric 		log_warn("snprintf");
324b832907bSeric 		fatal("exiting");
325b832907bSeric 	}
3260e8cc8ecSchl 
327b832907bSeric 	if ((fd = mkstemp(path)) == -1) {
328b832907bSeric 		log_warn("cannot create temporary file %s", path);
329b832907bSeric 		fatal("exiting");
330b832907bSeric 	}
3310e8cc8ecSchl 	unlink(path);
3320e8cc8ecSchl 	return (fd);
3330e8cc8ecSchl }
3340e8cc8ecSchl 
3350e8cc8ecSchl 
336e5b07014Sgilles /* Close file, signifying temporary error condition (if any) to the caller. */
337e5b07014Sgilles int
safe_fclose(FILE * fp)338e5b07014Sgilles safe_fclose(FILE *fp)
339e5b07014Sgilles {
340e5b07014Sgilles 	if (ferror(fp)) {
341e5b07014Sgilles 		fclose(fp);
342e5b07014Sgilles 		return 0;
343e5b07014Sgilles 	}
344e5b07014Sgilles 	if (fflush(fp)) {
345e5b07014Sgilles 		fclose(fp);
346e5b07014Sgilles 		if (errno == ENOSPC)
347e5b07014Sgilles 			return 0;
348e5b07014Sgilles 		fatal("safe_fclose: fflush");
349e5b07014Sgilles 	}
350e5b07014Sgilles 	if (fsync(fileno(fp)))
351e5b07014Sgilles 		fatal("safe_fclose: fsync");
352e5b07014Sgilles 	if (fclose(fp))
353e5b07014Sgilles 		fatal("safe_fclose: fclose");
354e5b07014Sgilles 
355e5b07014Sgilles 	return 1;
356e5b07014Sgilles }
357e5b07014Sgilles 
3582b90997cSgilles int
hostname_match(const char * hostname,const char * pattern)3597791da2bSeric hostname_match(const char *hostname, const char *pattern)
3602b90997cSgilles {
3612b90997cSgilles 	while (*pattern != '\0' && *hostname != '\0') {
3622b90997cSgilles 		if (*pattern == '*') {
3632b90997cSgilles 			while (*pattern == '*')
3642b90997cSgilles 				pattern++;
3652b90997cSgilles 			while (*hostname != '\0' &&
366fc3a8311Seric 			    tolower((unsigned char)*hostname) !=
367fc3a8311Seric 			    tolower((unsigned char)*pattern))
3682b90997cSgilles 				hostname++;
3692b90997cSgilles 			continue;
3702b90997cSgilles 		}
3712b90997cSgilles 
372fc3a8311Seric 		if (tolower((unsigned char)*pattern) !=
373fc3a8311Seric 		    tolower((unsigned char)*hostname))
3742b90997cSgilles 			return 0;
3752b90997cSgilles 		pattern++;
3762b90997cSgilles 		hostname++;
3772b90997cSgilles 	}
3782b90997cSgilles 
3792b90997cSgilles 	return (*hostname == '\0' && *pattern == '\0');
3802b90997cSgilles }
38177a2e6faSgilles 
38277a2e6faSgilles int
mailaddr_match(const struct mailaddr * maddr1,const struct mailaddr * maddr2)3832b4f6ebbSgilles mailaddr_match(const struct mailaddr *maddr1, const struct mailaddr *maddr2)
3842b4f6ebbSgilles {
3852b4f6ebbSgilles 	struct mailaddr m1 = *maddr1;
3862b4f6ebbSgilles 	struct mailaddr m2 = *maddr2;
3872b4f6ebbSgilles 	char	       *p;
3882b4f6ebbSgilles 
3892b4f6ebbSgilles 	/* catchall */
3902b4f6ebbSgilles 	if (m2.user[0] == '\0' && m2.domain[0] == '\0')
3912b4f6ebbSgilles 		return 1;
3922b4f6ebbSgilles 
39369479998Sgilles 	if (m2.domain[0] && !hostname_match(m1.domain, m2.domain))
3942b4f6ebbSgilles 		return 0;
3952b4f6ebbSgilles 
3962b4f6ebbSgilles 	if (m2.user[0]) {
3972b4f6ebbSgilles 		/* if address from table has a tag, we must respect it */
39837e5d029Sgilles 		if (strchr(m2.user, *env->sc_subaddressing_delim) == NULL) {
3992b4f6ebbSgilles 			/* otherwise, strip tag from session address if any */
40037e5d029Sgilles 			p = strchr(m1.user, *env->sc_subaddressing_delim);
4012b4f6ebbSgilles 			if (p)
4022b4f6ebbSgilles 				*p = '\0';
4032b4f6ebbSgilles 		}
4042b4f6ebbSgilles 		if (strcasecmp(m1.user, m2.user))
4052b4f6ebbSgilles 			return 0;
4062b4f6ebbSgilles 	}
4072b4f6ebbSgilles 	return 1;
4082b4f6ebbSgilles }
4092b4f6ebbSgilles 
4102b4f6ebbSgilles int
valid_localpart(const char * s)41122507732Schl valid_localpart(const char *s)
41271442faaSjacekm {
413b15221a5Seric #define IS_ATEXT(c) (isalnum((unsigned char)(c)) || strchr(MAILADDR_ALLOWED, (c)))
41471442faaSjacekm nextatom:
41571442faaSjacekm 	if (!IS_ATEXT(*s) || *s == '\0')
41671442faaSjacekm 		return 0;
41771442faaSjacekm 	while (*(++s) != '\0') {
41871442faaSjacekm 		if (*s == '.')
41971442faaSjacekm 			break;
42071442faaSjacekm 		if (IS_ATEXT(*s))
42171442faaSjacekm 			continue;
42271442faaSjacekm 		return 0;
42371442faaSjacekm 	}
42471442faaSjacekm 	if (*s == '.') {
42571442faaSjacekm 		s++;
42671442faaSjacekm 		goto nextatom;
42771442faaSjacekm 	}
42871442faaSjacekm 	return 1;
42971442faaSjacekm }
43071442faaSjacekm 
43171442faaSjacekm int
valid_domainpart(const char * s)43222507732Schl valid_domainpart(const char *s)
43371442faaSjacekm {
43422507732Schl 	struct in_addr	 ina;
43522507732Schl 	struct in6_addr	 ina6;
436299c4efeSeric 	char		*c, domain[SMTPD_MAXDOMAINPARTSIZE];
437299c4efeSeric 	const char	*p;
4383d2f1f8eSeric 	size_t		 dlen;
43922507732Schl 
44022507732Schl 	if (*s == '[') {
441299c4efeSeric 		if (strncasecmp("[IPv6:", s, 6) == 0)
442299c4efeSeric 			p = s + 6;
443299c4efeSeric 		else
444299c4efeSeric 			p = s + 1;
445299c4efeSeric 
446299c4efeSeric 		if (strlcpy(domain, p, sizeof domain) >= sizeof domain)
447299c4efeSeric 			return 0;
44822507732Schl 
449000eaaf0Smillert 		c = strchr(domain, ']');
45022507732Schl 		if (!c || c[1] != '\0')
45122507732Schl 			return 0;
45222507732Schl 
45322507732Schl 		*c = '\0';
45422507732Schl 
45522507732Schl 		if (inet_pton(AF_INET6, domain, &ina6) == 1)
45622507732Schl 			return 1;
45722507732Schl 		if (inet_pton(AF_INET, domain, &ina) == 1)
45822507732Schl 			return 1;
45922507732Schl 
46022507732Schl 		return 0;
46122507732Schl 	}
46222507732Schl 
4633d2f1f8eSeric 	if (*s == '\0')
4643d2f1f8eSeric 		return 0;
4653d2f1f8eSeric 
4663d2f1f8eSeric 	dlen = strlen(s);
4673d2f1f8eSeric 	if (dlen >= sizeof domain)
4683d2f1f8eSeric 		return 0;
4693d2f1f8eSeric 
4703d2f1f8eSeric 	if (s[dlen - 1] == '.')
4713d2f1f8eSeric 		return 0;
4723d2f1f8eSeric 
4733d2f1f8eSeric 	return res_hnok(s);
4740264247fSeric }
4750264247fSeric 
476000eaaf0Smillert #define LABELCHR(c) ((c) == '-' || (c) == '_' || isalpha((unsigned char)(c)) || isdigit((unsigned char)(c)))
4779ac382e5Seric #define LABELMAX 63
4789ac382e5Seric #define DNAMEMAX 253
4790264247fSeric 
4800264247fSeric int
valid_domainname(const char * str)4819ac382e5Seric valid_domainname(const char *str)
4820264247fSeric {
4839ac382e5Seric 	const char *label, *s;
4840264247fSeric 
4859ac382e5Seric 	/*
4869ac382e5Seric 	 * Expect a sequence of dot-separated labels, possibly with a trailing
4879ac382e5Seric 	 * dot. The empty string is rejected, as well a single dot.
4889ac382e5Seric 	 */
4899ac382e5Seric 	for (s = str; *s; s++) {
4900264247fSeric 
4919ac382e5Seric 		/* Start of a new label. */
4920264247fSeric 		label = s;
4939ac382e5Seric 		while (LABELCHR(*s))
4940264247fSeric 			s++;
4950264247fSeric 
4969ac382e5Seric 		/* Must have at least one char and at most LABELMAX. */
4979ac382e5Seric 		if (s == label || s - label > LABELMAX)
4985f2c0e3cSgilles 			return 0;
4995f2c0e3cSgilles 
5009ac382e5Seric 		/* If last label, stop here. */
5019ac382e5Seric 		if (*s == '\0')
5029ac382e5Seric 			break;
5039ac382e5Seric 
5049ac382e5Seric 		/* Expect a dot as label separator or last char. */
5050264247fSeric 		if (*s != '.')
506fad1310eSgilles 			return 0;
5070264247fSeric 	}
5089ac382e5Seric 
5099ac382e5Seric 	/* Must have at leat one label and no more than DNAMEMAX chars. */
5109ac382e5Seric 	if (s == str || s - str > DNAMEMAX)
5119ac382e5Seric 		return 0;
5129ac382e5Seric 
5139ac382e5Seric 	return 1;
51471442faaSjacekm }
5152697a4d9Sjacekm 
51644307885Sjacekm int
valid_smtp_response(const char * s)5177ebec07bSgilles valid_smtp_response(const char *s)
5187ebec07bSgilles {
5197ebec07bSgilles 	if (strlen(s) < 5)
5207ebec07bSgilles 		return 0;
5217ebec07bSgilles 
5227ebec07bSgilles 	if ((s[0] < '2' || s[0] > '5') ||
5237ebec07bSgilles 	    (s[1] < '0' || s[1] > '9') ||
5247ebec07bSgilles 	    (s[2] < '0' || s[2] > '9') ||
5257ebec07bSgilles 	    (s[3] != ' '))
5267ebec07bSgilles 		return 0;
5277ebec07bSgilles 
5287ebec07bSgilles 	return 1;
5297ebec07bSgilles }
5307ebec07bSgilles 
5317ebec07bSgilles int
valid_xtext(const char * s)532cd8603dbSop valid_xtext(const char *s)
533cd8603dbSop {
534cd8603dbSop 	for (; *s != '\0'; ++s) {
535cd8603dbSop 		if (*s < '!' || *s > '~' || *s == '=')
536cd8603dbSop 			return 0;
537cd8603dbSop 
538cd8603dbSop 		if (*s != '+')
539cd8603dbSop 			continue;
540cd8603dbSop 
541cd8603dbSop 		s++;
542cd8603dbSop 		if (!isdigit((unsigned char)*s) &&
543cd8603dbSop 		    !(*s >= 'A' && *s <= 'F'))
544cd8603dbSop 			return 0;
545cd8603dbSop 
546cd8603dbSop 		s++;
547cd8603dbSop 		if (!isdigit((unsigned char)*s) &&
548cd8603dbSop 		    !(*s >= 'A' && *s <= 'F'))
549cd8603dbSop 			return 0;
550cd8603dbSop 	}
551cd8603dbSop 
552cd8603dbSop 	return 1;
553cd8603dbSop }
554cd8603dbSop 
555cd8603dbSop int
secure_file(int fd,char * path,char * userdir,uid_t uid,int mayread)5562ddfddf2Sgilles secure_file(int fd, char *path, char *userdir, uid_t uid, int mayread)
55744307885Sjacekm {
558299c4efeSeric 	char		 buf[PATH_MAX];
559299c4efeSeric 	char		 homedir[PATH_MAX];
56044307885Sjacekm 	struct stat	 st;
56144307885Sjacekm 	char		*cp;
56244307885Sjacekm 
56344307885Sjacekm 	if (realpath(path, buf) == NULL)
56444307885Sjacekm 		return 0;
56544307885Sjacekm 
5662ddfddf2Sgilles 	if (realpath(userdir, homedir) == NULL)
56744307885Sjacekm 		homedir[0] = '\0';
56844307885Sjacekm 
56944307885Sjacekm 	/* Check the open file to avoid races. */
570df69c215Sderaadt 	if (fstat(fd, &st) == -1 ||
57144307885Sjacekm 	    !S_ISREG(st.st_mode) ||
5720fe61fc9Sgilles 	    st.st_uid != uid ||
573d0e4aaa3Sjacekm 	    (st.st_mode & (mayread ? 022 : 066)) != 0)
57444307885Sjacekm 		return 0;
57544307885Sjacekm 
57644307885Sjacekm 	/* For each component of the canonical path, walking upwards. */
57744307885Sjacekm 	for (;;) {
57844307885Sjacekm 		if ((cp = dirname(buf)) == NULL)
57944307885Sjacekm 			return 0;
5809b886139Sgilles 		(void)strlcpy(buf, cp, sizeof(buf));
58144307885Sjacekm 
582df69c215Sderaadt 		if (stat(buf, &st) == -1 ||
5832ddfddf2Sgilles 		    (st.st_uid != 0 && st.st_uid != uid) ||
58444307885Sjacekm 		    (st.st_mode & 022) != 0)
58544307885Sjacekm 			return 0;
58644307885Sjacekm 
58744307885Sjacekm 		/* We can stop checking after reaching homedir level. */
58844307885Sjacekm 		if (strcmp(homedir, buf) == 0)
58944307885Sjacekm 			break;
59044307885Sjacekm 
59144307885Sjacekm 		/*
59244307885Sjacekm 		 * dirname should always complete with a "/" path,
59344307885Sjacekm 		 * but we can be paranoid and check for "." too
59444307885Sjacekm 		 */
59544307885Sjacekm 		if ((strcmp("/", buf) == 0) || (strcmp(".", buf) == 0))
59644307885Sjacekm 			break;
59744307885Sjacekm 	}
59844307885Sjacekm 
59944307885Sjacekm 	return 1;
60044307885Sjacekm }
601f1e656c3Sjacekm 
602f1e656c3Sjacekm void
addargs(arglist * args,char * fmt,...)603f1e656c3Sjacekm addargs(arglist *args, char *fmt, ...)
604f1e656c3Sjacekm {
605f1e656c3Sjacekm 	va_list ap;
606f1e656c3Sjacekm 	char *cp;
607d2241734Schl 	uint nalloc;
608f1e656c3Sjacekm 	int r;
609483e8451Sgilles 	char	**tmp;
610f1e656c3Sjacekm 
611f1e656c3Sjacekm 	va_start(ap, fmt);
612f1e656c3Sjacekm 	r = vasprintf(&cp, fmt, ap);
613f1e656c3Sjacekm 	va_end(ap);
614f1e656c3Sjacekm 	if (r == -1)
615f1e656c3Sjacekm 		fatal("addargs: argument too long");
616f1e656c3Sjacekm 
617f1e656c3Sjacekm 	nalloc = args->nalloc;
618f1e656c3Sjacekm 	if (args->list == NULL) {
619f1e656c3Sjacekm 		nalloc = 32;
620f1e656c3Sjacekm 		args->num = 0;
621f1e656c3Sjacekm 	} else if (args->num+2 >= nalloc)
622f1e656c3Sjacekm 		nalloc *= 2;
623f1e656c3Sjacekm 
624483e8451Sgilles 	tmp = reallocarray(args->list, nalloc, sizeof(char *));
625483e8451Sgilles 	if (tmp == NULL)
6265ce25fe6Sespie 		fatal("addargs: reallocarray");
627483e8451Sgilles 	args->list = tmp;
628f1e656c3Sjacekm 	args->nalloc = nalloc;
629f1e656c3Sjacekm 	args->list[args->num++] = cp;
630f1e656c3Sjacekm 	args->list[args->num] = NULL;
631f1e656c3Sjacekm }
63222d2befeSjacekm 
633aac96740Sgilles int
lowercase(char * buf,const char * s,size_t len)634abf71fdfSeric lowercase(char *buf, const char *s, size_t len)
63522d2befeSjacekm {
63622d2befeSjacekm 	if (len == 0)
637aac96740Sgilles 		return 0;
63822d2befeSjacekm 
63922d2befeSjacekm 	if (strlcpy(buf, s, len) >= len)
640aac96740Sgilles 		return 0;
64122d2befeSjacekm 
64222d2befeSjacekm 	while (*buf != '\0') {
643fc3a8311Seric 		*buf = tolower((unsigned char)*buf);
64422d2befeSjacekm 		buf++;
64522d2befeSjacekm 	}
646aac96740Sgilles 
647aac96740Sgilles 	return 1;
648aac96740Sgilles }
649aac96740Sgilles 
650ce4786e4Seric int
uppercase(char * buf,const char * s,size_t len)651ce4786e4Seric uppercase(char *buf, const char *s, size_t len)
652ce4786e4Seric {
653ce4786e4Seric 	if (len == 0)
654ce4786e4Seric 		return 0;
655ce4786e4Seric 
656ce4786e4Seric 	if (strlcpy(buf, s, len) >= len)
657ce4786e4Seric 		return 0;
658ce4786e4Seric 
659ce4786e4Seric 	while (*buf != '\0') {
660fc3a8311Seric 		*buf = toupper((unsigned char)*buf);
661ce4786e4Seric 		buf++;
662ce4786e4Seric 	}
663ce4786e4Seric 
664ce4786e4Seric 	return 1;
665ce4786e4Seric }
666ce4786e4Seric 
667aac96740Sgilles void
xlowercase(char * buf,const char * s,size_t len)668abf71fdfSeric xlowercase(char *buf, const char *s, size_t len)
669aac96740Sgilles {
670aac96740Sgilles 	if (len == 0)
671aac96740Sgilles 		fatalx("lowercase: len == 0");
672aac96740Sgilles 
673aac96740Sgilles 	if (!lowercase(buf, s, len))
674aac96740Sgilles 		fatalx("lowercase: truncation");
67522d2befeSjacekm }
676378b56faSgilles 
677d2241734Schl uint64_t
generate_uid(void)678e8664bd6Sgilles generate_uid(void)
679e8664bd6Sgilles {
68026897f9dSeric 	static uint32_t id;
681081a0b33Seric 	static uint8_t	inited;
68226897f9dSeric 	uint64_t	uid;
683e8664bd6Sgilles 
684081a0b33Seric 	if (!inited) {
685081a0b33Seric 		id = arc4random();
686081a0b33Seric 		inited = 1;
687081a0b33Seric 	}
68826897f9dSeric 	while ((uid = ((uint64_t)(id++) << 32 | arc4random())) == 0)
68926897f9dSeric 		;
69026897f9dSeric 
69126897f9dSeric 	return (uid);
692e8664bd6Sgilles }
693cb5c228eSjacekm 
694f4ef9244Sjacekm int
session_socket_error(int fd)695f4ef9244Sjacekm session_socket_error(int fd)
696f4ef9244Sjacekm {
697d10e47d4Sgilles 	int		error;
698d10e47d4Sgilles 	socklen_t	len;
699f4ef9244Sjacekm 
700f4ef9244Sjacekm 	len = sizeof(error);
701f4ef9244Sjacekm 	if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &len) == -1)
702f4ef9244Sjacekm 		fatal("session_socket_error: getsockopt");
703f4ef9244Sjacekm 
704f4ef9244Sjacekm 	return (error);
705f4ef9244Sjacekm }
70625080696Sgilles 
70725080696Sgilles const char *
parse_smtp_response(char * line,size_t len,char ** msg,int * cont)7088a99bc94Seric parse_smtp_response(char *line, size_t len, char **msg, int *cont)
7098a99bc94Seric {
710953aae25Sderaadt 	if (len >= LINE_MAX)
7118a99bc94Seric 		return "line too long";
7128a99bc94Seric 
7138a99bc94Seric 	if (len > 3) {
7148a99bc94Seric 		if (msg)
7158a99bc94Seric 			*msg = line + 4;
7168a99bc94Seric 		if (cont)
7178a99bc94Seric 			*cont = (line[3] == '-');
7188a99bc94Seric 	} else if (len == 3) {
7198a99bc94Seric 		if (msg)
7208a99bc94Seric 			*msg = line + 3;
7218a99bc94Seric 		if (cont)
7228a99bc94Seric 			*cont = 0;
7238a99bc94Seric 	} else
7248a99bc94Seric 		return "line too short";
7258a99bc94Seric 
7268a99bc94Seric 	/* validate reply code */
727b70263c5Sderaadt 	if (line[0] < '2' || line[0] > '5' || !isdigit((unsigned char)line[1]) ||
728b70263c5Sderaadt 	    !isdigit((unsigned char)line[2]))
7298a99bc94Seric 		return "reply code out of range";
7308a99bc94Seric 
7318a99bc94Seric 	return NULL;
7328a99bc94Seric }
733f70d44e6Seric 
73436e884f4Ssunil static int
parse_mailname_file(char * hostname,size_t len)73536e884f4Ssunil parse_mailname_file(char *hostname, size_t len)
736f70d44e6Seric {
737f70d44e6Seric 	FILE	*fp;
7382b86a358Sjung 	char	*buf = NULL;
7392b86a358Sjung 	size_t	 bufsz = 0;
7402b86a358Sjung 	ssize_t	 buflen;
741f70d44e6Seric 
7428b79b54eSeric 	if ((fp = fopen(MAILNAME_FILE, "r")) == NULL)
74336e884f4Ssunil 		return 1;
744f70d44e6Seric 
7458e996a8eSjsg 	buflen = getline(&buf, &bufsz, fp);
7468e996a8eSjsg 	fclose(fp);
7478e996a8eSjsg 	if (buflen == -1) {
7488e996a8eSjsg 		free(buf);
7498e996a8eSjsg 		return 1;
7508e996a8eSjsg 	}
751f70d44e6Seric 
752f70d44e6Seric 	if (buf[buflen - 1] == '\n')
753f70d44e6Seric 		buf[buflen - 1] = '\0';
754f70d44e6Seric 
7558e996a8eSjsg 	bufsz = strlcpy(hostname, buf, len);
7568e996a8eSjsg 	free(buf);
7578e996a8eSjsg 	if (bufsz >= len) {
7588b79b54eSeric 		fprintf(stderr, MAILNAME_FILE " entry too long");
7598e996a8eSjsg 		return 1;
760f70d44e6Seric 	}
761f70d44e6Seric 
76236e884f4Ssunil 	return 0;
76336e884f4Ssunil }
76436e884f4Ssunil 
76536e884f4Ssunil int
getmailname(char * hostname,size_t len)76636e884f4Ssunil getmailname(char *hostname, size_t len)
76736e884f4Ssunil {
76836e884f4Ssunil 	struct addrinfo	 hints, *res = NULL;
76936e884f4Ssunil 	int		 error;
77036e884f4Ssunil 
77136e884f4Ssunil 	/* Try MAILNAME_FILE first */
77236e884f4Ssunil 	if (parse_mailname_file(hostname, len) == 0)
77336e884f4Ssunil 		return 0;
77436e884f4Ssunil 
77536e884f4Ssunil 	/* Next, gethostname(3) */
776f70d44e6Seric 	if (gethostname(hostname, len) == -1) {
77736e884f4Ssunil 		fprintf(stderr, "getmailname: gethostname() failed\n");
77836e884f4Ssunil 		return -1;
779f70d44e6Seric 	}
780f70d44e6Seric 
78136e884f4Ssunil 	if (strchr(hostname, '.') != NULL)
78236e884f4Ssunil 		return 0;
78336e884f4Ssunil 
78436e884f4Ssunil 	/* Canonicalize if domain part is missing */
785f70d44e6Seric 	memset(&hints, 0, sizeof hints);
786f70d44e6Seric 	hints.ai_family = PF_UNSPEC;
787f70d44e6Seric 	hints.ai_flags = AI_CANONNAME;
788f70d44e6Seric 	error = getaddrinfo(hostname, NULL, &hints, &res);
78936e884f4Ssunil 	if (error)
79036e884f4Ssunil 		return 0; /* Continue with non-canon hostname */
791f70d44e6Seric 
792f70d44e6Seric 	if (strlcpy(hostname, res->ai_canonname, len) >= len) {
793f70d44e6Seric 		fprintf(stderr, "hostname too long");
794272e865cSgilles 		freeaddrinfo(res);
79536e884f4Ssunil 		return -1;
796f70d44e6Seric 	}
797f70d44e6Seric 
798f70d44e6Seric 	freeaddrinfo(res);
79936e884f4Ssunil 	return 0;
800f70d44e6Seric }
801254aed36Seric 
802254aed36Seric int
base64_encode(unsigned char const * src,size_t srclen,char * dest,size_t destsize)803254aed36Seric base64_encode(unsigned char const *src, size_t srclen,
804254aed36Seric 	      char *dest, size_t destsize)
805254aed36Seric {
806254aed36Seric 	return __b64_ntop(src, srclen, dest, destsize);
807254aed36Seric }
808254aed36Seric 
809254aed36Seric int
base64_decode(char const * src,unsigned char * dest,size_t destsize)810254aed36Seric base64_decode(char const *src, unsigned char *dest, size_t destsize)
811254aed36Seric {
812254aed36Seric 	return __b64_pton(src, dest, destsize);
813254aed36Seric }
814f24248b7Sreyk 
8158cfe1040Sgilles int
base64_encode_rfc3548(unsigned char const * src,size_t srclen,char * dest,size_t destsize)8168cfe1040Sgilles base64_encode_rfc3548(unsigned char const *src, size_t srclen,
8178cfe1040Sgilles 	      char *dest, size_t destsize)
8188cfe1040Sgilles {
8198cfe1040Sgilles 	size_t i;
8208cfe1040Sgilles 	int ret;
8218cfe1040Sgilles 
8228cfe1040Sgilles 	if ((ret = base64_encode(src, srclen, dest, destsize)) == -1)
8238cfe1040Sgilles 		return -1;
8248cfe1040Sgilles 
8258cfe1040Sgilles 	for (i = 0; i < destsize; ++i) {
8268cfe1040Sgilles 		if (dest[i] == '/')
8278cfe1040Sgilles 			dest[i] = '_';
8288cfe1040Sgilles 		else if (dest[i] == '+')
8298cfe1040Sgilles 			dest[i] = '-';
8308cfe1040Sgilles 	}
8318cfe1040Sgilles 
8328cfe1040Sgilles 	return ret;
8338cfe1040Sgilles }
8348cfe1040Sgilles 
835f24248b7Sreyk void
log_trace0(const char * emsg,...)83682e75344Seric log_trace0(const char *emsg, ...)
837f24248b7Sreyk {
838f24248b7Sreyk 	va_list	 ap;
839f24248b7Sreyk 
840f24248b7Sreyk 	va_start(ap, emsg);
841f24248b7Sreyk 	vlog(LOG_DEBUG, emsg, ap);
842f24248b7Sreyk 	va_end(ap);
843f24248b7Sreyk }
844f24248b7Sreyk 
845f24248b7Sreyk void
log_trace_verbose(int v)846f24248b7Sreyk log_trace_verbose(int v)
847f24248b7Sreyk {
848f24248b7Sreyk 	tracing = v;
849f24248b7Sreyk 
850f24248b7Sreyk 	/* Set debug logging in log.c */
851871fc12cSreyk 	log_setverbose(v & TRACE_DEBUG ? 2 : foreground_log);
852f24248b7Sreyk }
853d7df8c18Sop 
854d7df8c18Sop int
parse_table_line(FILE * fp,char ** line,size_t * linesize,int * type,char ** key,char ** val,int * malformed)855d7df8c18Sop parse_table_line(FILE *fp, char **line, size_t *linesize,
856d7df8c18Sop     int *type, char **key, char **val, int *malformed)
857d7df8c18Sop {
8586d4e484eSop 	char	*keyp, *valp;
859d7df8c18Sop 	ssize_t	 linelen;
860d7df8c18Sop 
861d7df8c18Sop 	*key = NULL;
862d7df8c18Sop 	*val = NULL;
863d7df8c18Sop 	*malformed = 0;
864d7df8c18Sop 
865d7df8c18Sop 	if ((linelen = getline(line, linesize, fp)) == -1)
866d7df8c18Sop 		return (-1);
867d7df8c18Sop 
868d7df8c18Sop 	keyp = *line;
869d7df8c18Sop 	while (isspace((unsigned char)*keyp)) {
870d7df8c18Sop 		++keyp;
871d7df8c18Sop 		--linelen;
872d7df8c18Sop 	}
873d7df8c18Sop 	if (*keyp == '\0')
874d7df8c18Sop 		return 0;
875d7df8c18Sop 	while (linelen > 0 && isspace((unsigned char)keyp[linelen - 1]))
876d7df8c18Sop 		keyp[--linelen] = '\0';
877d7df8c18Sop 	if (*keyp == '#') {
878d7df8c18Sop 		if (*type == T_NONE) {
879d7df8c18Sop 			keyp++;
880d7df8c18Sop 			while (isspace((unsigned char)*keyp))
881d7df8c18Sop 				++keyp;
882d7df8c18Sop 			if (!strcmp(keyp, "@list"))
883d7df8c18Sop 				*type = T_LIST;
884d7df8c18Sop 		}
885d7df8c18Sop 		return 0;
886d7df8c18Sop 	}
887d7df8c18Sop 
8886d4e484eSop 	if (*keyp == '[') {
8896d4e484eSop 		if ((valp = strchr(keyp, ']')) == NULL) {
8906d4e484eSop 			*malformed = 1;
8916d4e484eSop 			return (0);
892d7df8c18Sop 		}
8936d4e484eSop 		valp++;
8946d4e484eSop 	} else
8956d4e484eSop 		valp = keyp + strcspn(keyp, " \t:");
8966d4e484eSop 
897d7df8c18Sop 	if (*type == T_NONE)
8986d4e484eSop 		*type = (*valp == '\0') ? T_LIST : T_HASH;
899d7df8c18Sop 
900d7df8c18Sop 	if (*type == T_LIST) {
901d7df8c18Sop 		*key = keyp;
902d7df8c18Sop 		return (0);
903d7df8c18Sop 	}
904d7df8c18Sop 
905d7df8c18Sop 	/* T_HASH */
9066d4e484eSop 	if (*valp != '\0') {
9076d4e484eSop 		*valp++ = '\0';
9086d4e484eSop 		valp += strspn(valp, " \t");
909d7df8c18Sop 	}
910d7df8c18Sop 	if (*valp == '\0')
911d7df8c18Sop 		*malformed = 1;
912d7df8c18Sop 
913d7df8c18Sop 	*key = keyp;
914d7df8c18Sop 	*val = valp;
915d7df8c18Sop 	return (0);
916d7df8c18Sop }
917