xref: /openbsd/usr.sbin/smtpd/util.c (revision 097a140d)
1 /*	$OpenBSD: util.c,v 1.153 2021/03/31 19:09:19 eric Exp $	*/
2 
3 /*
4  * Copyright (c) 2000,2001 Markus Friedl.  All rights reserved.
5  * Copyright (c) 2008 Gilles Chehade <gilles@poolp.org>
6  * Copyright (c) 2009 Jacek Masiulaniec <jacekm@dobremiasto.net>
7  * Copyright (c) 2012 Eric Faurot <eric@openbsd.org>
8  *
9  * Permission to use, copy, modify, and distribute this software for any
10  * purpose with or without fee is hereby granted, provided that the above
11  * copyright notice and this permission notice appear in all copies.
12  *
13  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
14  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
15  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
16  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
17  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
18  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
19  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
20  */
21 
22 #include <sys/types.h>
23 #include <sys/queue.h>
24 #include <sys/tree.h>
25 #include <sys/socket.h>
26 #include <sys/stat.h>
27 #include <sys/resource.h>
28 
29 #include <netinet/in.h>
30 #include <arpa/inet.h>
31 
32 #include <ctype.h>
33 #include <errno.h>
34 #include <event.h>
35 #include <fcntl.h>
36 #include <fts.h>
37 #include <imsg.h>
38 #include <inttypes.h>
39 #include <libgen.h>
40 #include <netdb.h>
41 #include <pwd.h>
42 #include <limits.h>
43 #include <resolv.h>
44 #include <stdarg.h>
45 #include <stdio.h>
46 #include <stdlib.h>
47 #include <string.h>
48 #include <syslog.h>
49 #include <time.h>
50 #include <unistd.h>
51 
52 #include "smtpd.h"
53 #include "log.h"
54 
55 const char *log_in6addr(const struct in6_addr *);
56 const char *log_sockaddr(struct sockaddr *);
57 static int  parse_mailname_file(char *, size_t);
58 
59 int	tracing = 0;
60 int	foreground_log = 0;
61 
62 void *
63 xmalloc(size_t size)
64 {
65 	void	*r;
66 
67 	if ((r = malloc(size)) == NULL)
68 		fatal("malloc");
69 
70 	return (r);
71 }
72 
73 void *
74 xcalloc(size_t nmemb, size_t size)
75 {
76 	void	*r;
77 
78 	if ((r = calloc(nmemb, size)) == NULL)
79 		fatal("calloc");
80 
81 	return (r);
82 }
83 
84 char *
85 xstrdup(const char *str)
86 {
87 	char	*r;
88 
89 	if ((r = strdup(str)) == NULL)
90 		fatal("strdup");
91 
92 	return (r);
93 }
94 
95 void *
96 xmemdup(const void *ptr, size_t size)
97 {
98 	void	*r;
99 
100 	if ((r = malloc(size)) == NULL)
101 		fatal("malloc");
102 
103 	memmove(r, ptr, size);
104 
105 	return (r);
106 }
107 
108 int
109 xasprintf(char **ret, const char *format, ...)
110 {
111 	int r;
112 	va_list ap;
113 
114 	va_start(ap, format);
115 	r = vasprintf(ret, format, ap);
116 	va_end(ap);
117 	if (r == -1)
118 		fatal("vasprintf");
119 
120 	return (r);
121 }
122 
123 
124 #if !defined(NO_IO)
125 int
126 io_xprintf(struct io *io, const char *fmt, ...)
127 {
128 	va_list	ap;
129 	int len;
130 
131 	va_start(ap, fmt);
132 	len = io_vprintf(io, fmt, ap);
133 	va_end(ap);
134 	if (len == -1)
135 		fatal("io_xprintf(%p, %s, ...)", io, fmt);
136 
137 	return len;
138 }
139 
140 int
141 io_xprint(struct io *io, const char *str)
142 {
143 	int len;
144 
145 	len = io_print(io, str);
146 	if (len == -1)
147 		fatal("io_xprint(%p, %s, ...)", io, str);
148 
149 	return len;
150 }
151 #endif
152 
153 char *
154 strip(char *s)
155 {
156 	size_t	 l;
157 
158 	while (isspace((unsigned char)*s))
159 		s++;
160 
161 	for (l = strlen(s); l; l--) {
162 		if (!isspace((unsigned char)s[l-1]))
163 			break;
164 		s[l-1] = '\0';
165 	}
166 
167 	return (s);
168 }
169 
170 int
171 bsnprintf(char *str, size_t size, const char *format, ...)
172 {
173 	int ret;
174 	va_list ap;
175 
176 	va_start(ap, format);
177 	ret = vsnprintf(str, size, format, ap);
178 	va_end(ap);
179 	if (ret < 0 || (size_t)ret >= size)
180 		return 0;
181 
182 	return 1;
183 }
184 
185 
186 int
187 ckdir(const char *path, mode_t mode, uid_t owner, gid_t group, int create)
188 {
189 	char		mode_str[12];
190 	int		ret;
191 	struct stat	sb;
192 
193 	if (stat(path, &sb) == -1) {
194 		if (errno != ENOENT || create == 0) {
195 			log_warn("stat: %s", path);
196 			return (0);
197 		}
198 
199 		/* chmod is deferred to avoid umask effect */
200 		if (mkdir(path, 0) == -1) {
201 			log_warn("mkdir: %s", path);
202 			return (0);
203 		}
204 
205 		if (chown(path, owner, group) == -1) {
206 			log_warn("chown: %s", path);
207 			return (0);
208 		}
209 
210 		if (chmod(path, mode) == -1) {
211 			log_warn("chmod: %s", path);
212 			return (0);
213 		}
214 
215 		if (stat(path, &sb) == -1) {
216 			log_warn("stat: %s", path);
217 			return (0);
218 		}
219 	}
220 
221 	ret = 1;
222 
223 	/* check if it's a directory */
224 	if (!S_ISDIR(sb.st_mode)) {
225 		ret = 0;
226 		log_warnx("%s is not a directory", path);
227 	}
228 
229 	/* check that it is owned by owner/group */
230 	if (sb.st_uid != owner) {
231 		ret = 0;
232 		log_warnx("%s is not owned by uid %d", path, owner);
233 	}
234 	if (sb.st_gid != group) {
235 		ret = 0;
236 		log_warnx("%s is not owned by gid %d", path, group);
237 	}
238 
239 	/* check permission */
240 	if ((sb.st_mode & 07777) != mode) {
241 		ret = 0;
242 		strmode(mode, mode_str);
243 		mode_str[10] = '\0';
244 		log_warnx("%s must be %s (%o)", path, mode_str + 1, mode);
245 	}
246 
247 	return ret;
248 }
249 
250 int
251 rmtree(char *path, int keepdir)
252 {
253 	char		*path_argv[2];
254 	FTS		*fts;
255 	FTSENT		*e;
256 	int		 ret, depth;
257 
258 	path_argv[0] = path;
259 	path_argv[1] = NULL;
260 	ret = 0;
261 	depth = 0;
262 
263 	fts = fts_open(path_argv, FTS_PHYSICAL | FTS_NOCHDIR, NULL);
264 	if (fts == NULL) {
265 		log_warn("fts_open: %s", path);
266 		return (-1);
267 	}
268 
269 	while ((e = fts_read(fts)) != NULL) {
270 		switch (e->fts_info) {
271 		case FTS_D:
272 			depth++;
273 			break;
274 		case FTS_DP:
275 		case FTS_DNR:
276 			depth--;
277 			if (keepdir && depth == 0)
278 				continue;
279 			if (rmdir(e->fts_path) == -1) {
280 				log_warn("rmdir: %s", e->fts_path);
281 				ret = -1;
282 			}
283 			break;
284 
285 		case FTS_F:
286 			if (unlink(e->fts_path) == -1) {
287 				log_warn("unlink: %s", e->fts_path);
288 				ret = -1;
289 			}
290 		}
291 	}
292 
293 	fts_close(fts);
294 
295 	return (ret);
296 }
297 
298 int
299 mvpurge(char *from, char *to)
300 {
301 	size_t		 n;
302 	int		 retry;
303 	const char	*sep;
304 	char		 buf[PATH_MAX];
305 
306 	if ((n = strlen(to)) == 0)
307 		fatalx("to is empty");
308 
309 	sep = (to[n - 1] == '/') ? "" : "/";
310 	retry = 0;
311 
312 again:
313 	(void)snprintf(buf, sizeof buf, "%s%s%u", to, sep, arc4random());
314 	if (rename(from, buf) == -1) {
315 		/* ENOTDIR has actually 2 meanings, and incorrect input
316 		 * could lead to an infinite loop. Consider that after
317 		 * 20 tries something is hopelessly wrong.
318 		 */
319 		if (errno == ENOTEMPTY || errno == EISDIR || errno == ENOTDIR) {
320 			if ((retry++) >= 20)
321 				return (-1);
322 			goto again;
323 		}
324 		return -1;
325 	}
326 
327 	return 0;
328 }
329 
330 
331 int
332 mktmpfile(void)
333 {
334 	char		path[PATH_MAX];
335 	int		fd;
336 
337 	if (!bsnprintf(path, sizeof(path), "%s/smtpd.XXXXXXXXXX",
338 		PATH_TEMPORARY)) {
339 		log_warn("snprintf");
340 		fatal("exiting");
341 	}
342 
343 	if ((fd = mkstemp(path)) == -1) {
344 		log_warn("cannot create temporary file %s", path);
345 		fatal("exiting");
346 	}
347 	unlink(path);
348 	return (fd);
349 }
350 
351 
352 /* Close file, signifying temporary error condition (if any) to the caller. */
353 int
354 safe_fclose(FILE *fp)
355 {
356 	if (ferror(fp)) {
357 		fclose(fp);
358 		return 0;
359 	}
360 	if (fflush(fp)) {
361 		fclose(fp);
362 		if (errno == ENOSPC)
363 			return 0;
364 		fatal("safe_fclose: fflush");
365 	}
366 	if (fsync(fileno(fp)))
367 		fatal("safe_fclose: fsync");
368 	if (fclose(fp))
369 		fatal("safe_fclose: fclose");
370 
371 	return 1;
372 }
373 
374 int
375 hostname_match(const char *hostname, const char *pattern)
376 {
377 	while (*pattern != '\0' && *hostname != '\0') {
378 		if (*pattern == '*') {
379 			while (*pattern == '*')
380 				pattern++;
381 			while (*hostname != '\0' &&
382 			    tolower((unsigned char)*hostname) !=
383 			    tolower((unsigned char)*pattern))
384 				hostname++;
385 			continue;
386 		}
387 
388 		if (tolower((unsigned char)*pattern) !=
389 		    tolower((unsigned char)*hostname))
390 			return 0;
391 		pattern++;
392 		hostname++;
393 	}
394 
395 	return (*hostname == '\0' && *pattern == '\0');
396 }
397 
398 int
399 mailaddr_match(const struct mailaddr *maddr1, const struct mailaddr *maddr2)
400 {
401 	struct mailaddr m1 = *maddr1;
402 	struct mailaddr m2 = *maddr2;
403 	char	       *p;
404 
405 	/* catchall */
406 	if (m2.user[0] == '\0' && m2.domain[0] == '\0')
407 		return 1;
408 
409 	if (m2.domain[0] && !hostname_match(m1.domain, m2.domain))
410 		return 0;
411 
412 	if (m2.user[0]) {
413 		/* if address from table has a tag, we must respect it */
414 		if (strchr(m2.user, *env->sc_subaddressing_delim) == NULL) {
415 			/* otherwise, strip tag from session address if any */
416 			p = strchr(m1.user, *env->sc_subaddressing_delim);
417 			if (p)
418 				*p = '\0';
419 		}
420 		if (strcasecmp(m1.user, m2.user))
421 			return 0;
422 	}
423 	return 1;
424 }
425 
426 int
427 valid_localpart(const char *s)
428 {
429 #define IS_ATEXT(c) (isalnum((unsigned char)(c)) || strchr(MAILADDR_ALLOWED, (c)))
430 nextatom:
431 	if (!IS_ATEXT(*s) || *s == '\0')
432 		return 0;
433 	while (*(++s) != '\0') {
434 		if (*s == '.')
435 			break;
436 		if (IS_ATEXT(*s))
437 			continue;
438 		return 0;
439 	}
440 	if (*s == '.') {
441 		s++;
442 		goto nextatom;
443 	}
444 	return 1;
445 }
446 
447 int
448 valid_domainpart(const char *s)
449 {
450 	struct in_addr	 ina;
451 	struct in6_addr	 ina6;
452 	char		*c, domain[SMTPD_MAXDOMAINPARTSIZE];
453 	const char	*p;
454 	size_t		 dlen;
455 
456 	if (*s == '[') {
457 		if (strncasecmp("[IPv6:", s, 6) == 0)
458 			p = s + 6;
459 		else
460 			p = s + 1;
461 
462 		if (strlcpy(domain, p, sizeof domain) >= sizeof domain)
463 			return 0;
464 
465 		c = strchr(domain, ']');
466 		if (!c || c[1] != '\0')
467 			return 0;
468 
469 		*c = '\0';
470 
471 		if (inet_pton(AF_INET6, domain, &ina6) == 1)
472 			return 1;
473 		if (inet_pton(AF_INET, domain, &ina) == 1)
474 			return 1;
475 
476 		return 0;
477 	}
478 
479 	if (*s == '\0')
480 		return 0;
481 
482 	dlen = strlen(s);
483 	if (dlen >= sizeof domain)
484 		return 0;
485 
486 	if (s[dlen - 1] == '.')
487 		return 0;
488 
489 	return res_hnok(s);
490 }
491 
492 #define LABELCHR(c) ((c) == '-' || (c) == '_' || isalpha((unsigned char)(c)) || isdigit((unsigned char)(c)))
493 #define LABELMAX 63
494 #define DNAMEMAX 253
495 
496 int
497 valid_domainname(const char *str)
498 {
499 	const char *label, *s;
500 
501 	/*
502 	 * Expect a sequence of dot-separated labels, possibly with a trailing
503 	 * dot. The empty string is rejected, as well a single dot.
504 	 */
505 	for (s = str; *s; s++) {
506 
507 		/* Start of a new label. */
508 		label = s;
509 		while (LABELCHR(*s))
510 			s++;
511 
512 		/* Must have at least one char and at most LABELMAX. */
513 		if (s == label || s - label > LABELMAX)
514 			return 0;
515 
516 		/* If last label, stop here. */
517 		if (*s == '\0')
518 			break;
519 
520 		/* Expect a dot as label separator or last char. */
521 		if (*s != '.')
522 			return 0;
523 	}
524 
525 	/* Must have at leat one label and no more than DNAMEMAX chars. */
526 	if (s == str || s - str > DNAMEMAX)
527 		return 0;
528 
529 	return 1;
530 }
531 
532 int
533 valid_smtp_response(const char *s)
534 {
535 	if (strlen(s) < 5)
536 		return 0;
537 
538 	if ((s[0] < '2' || s[0] > '5') ||
539 	    (s[1] < '0' || s[1] > '9') ||
540 	    (s[2] < '0' || s[2] > '9') ||
541 	    (s[3] != ' '))
542 		return 0;
543 
544 	return 1;
545 }
546 
547 int
548 secure_file(int fd, char *path, char *userdir, uid_t uid, int mayread)
549 {
550 	char		 buf[PATH_MAX];
551 	char		 homedir[PATH_MAX];
552 	struct stat	 st;
553 	char		*cp;
554 
555 	if (realpath(path, buf) == NULL)
556 		return 0;
557 
558 	if (realpath(userdir, homedir) == NULL)
559 		homedir[0] = '\0';
560 
561 	/* Check the open file to avoid races. */
562 	if (fstat(fd, &st) == -1 ||
563 	    !S_ISREG(st.st_mode) ||
564 	    st.st_uid != uid ||
565 	    (st.st_mode & (mayread ? 022 : 066)) != 0)
566 		return 0;
567 
568 	/* For each component of the canonical path, walking upwards. */
569 	for (;;) {
570 		if ((cp = dirname(buf)) == NULL)
571 			return 0;
572 		(void)strlcpy(buf, cp, sizeof(buf));
573 
574 		if (stat(buf, &st) == -1 ||
575 		    (st.st_uid != 0 && st.st_uid != uid) ||
576 		    (st.st_mode & 022) != 0)
577 			return 0;
578 
579 		/* We can stop checking after reaching homedir level. */
580 		if (strcmp(homedir, buf) == 0)
581 			break;
582 
583 		/*
584 		 * dirname should always complete with a "/" path,
585 		 * but we can be paranoid and check for "." too
586 		 */
587 		if ((strcmp("/", buf) == 0) || (strcmp(".", buf) == 0))
588 			break;
589 	}
590 
591 	return 1;
592 }
593 
594 void
595 addargs(arglist *args, char *fmt, ...)
596 {
597 	va_list ap;
598 	char *cp;
599 	uint nalloc;
600 	int r;
601 	char	**tmp;
602 
603 	va_start(ap, fmt);
604 	r = vasprintf(&cp, fmt, ap);
605 	va_end(ap);
606 	if (r == -1)
607 		fatal("addargs: argument too long");
608 
609 	nalloc = args->nalloc;
610 	if (args->list == NULL) {
611 		nalloc = 32;
612 		args->num = 0;
613 	} else if (args->num+2 >= nalloc)
614 		nalloc *= 2;
615 
616 	tmp = reallocarray(args->list, nalloc, sizeof(char *));
617 	if (tmp == NULL)
618 		fatal("addargs: reallocarray");
619 	args->list = tmp;
620 	args->nalloc = nalloc;
621 	args->list[args->num++] = cp;
622 	args->list[args->num] = NULL;
623 }
624 
625 int
626 lowercase(char *buf, const char *s, size_t len)
627 {
628 	if (len == 0)
629 		return 0;
630 
631 	if (strlcpy(buf, s, len) >= len)
632 		return 0;
633 
634 	while (*buf != '\0') {
635 		*buf = tolower((unsigned char)*buf);
636 		buf++;
637 	}
638 
639 	return 1;
640 }
641 
642 int
643 uppercase(char *buf, const char *s, size_t len)
644 {
645 	if (len == 0)
646 		return 0;
647 
648 	if (strlcpy(buf, s, len) >= len)
649 		return 0;
650 
651 	while (*buf != '\0') {
652 		*buf = toupper((unsigned char)*buf);
653 		buf++;
654 	}
655 
656 	return 1;
657 }
658 
659 void
660 xlowercase(char *buf, const char *s, size_t len)
661 {
662 	if (len == 0)
663 		fatalx("lowercase: len == 0");
664 
665 	if (!lowercase(buf, s, len))
666 		fatalx("lowercase: truncation");
667 }
668 
669 uint64_t
670 generate_uid(void)
671 {
672 	static uint32_t id;
673 	static uint8_t	inited;
674 	uint64_t	uid;
675 
676 	if (!inited) {
677 		id = arc4random();
678 		inited = 1;
679 	}
680 	while ((uid = ((uint64_t)(id++) << 32 | arc4random())) == 0)
681 		;
682 
683 	return (uid);
684 }
685 
686 int
687 session_socket_error(int fd)
688 {
689 	int		error;
690 	socklen_t	len;
691 
692 	len = sizeof(error);
693 	if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &len) == -1)
694 		fatal("session_socket_error: getsockopt");
695 
696 	return (error);
697 }
698 
699 const char *
700 parse_smtp_response(char *line, size_t len, char **msg, int *cont)
701 {
702 	if (len >= LINE_MAX)
703 		return "line too long";
704 
705 	if (len > 3) {
706 		if (msg)
707 			*msg = line + 4;
708 		if (cont)
709 			*cont = (line[3] == '-');
710 	} else if (len == 3) {
711 		if (msg)
712 			*msg = line + 3;
713 		if (cont)
714 			*cont = 0;
715 	} else
716 		return "line too short";
717 
718 	/* validate reply code */
719 	if (line[0] < '2' || line[0] > '5' || !isdigit((unsigned char)line[1]) ||
720 	    !isdigit((unsigned char)line[2]))
721 		return "reply code out of range";
722 
723 	return NULL;
724 }
725 
726 static int
727 parse_mailname_file(char *hostname, size_t len)
728 {
729 	FILE	*fp;
730 	char	*buf = NULL;
731 	size_t	 bufsz = 0;
732 	ssize_t	 buflen;
733 
734 	if ((fp = fopen(MAILNAME_FILE, "r")) == NULL)
735 		return 1;
736 
737 	if ((buflen = getline(&buf, &bufsz, fp)) == -1)
738 		goto error;
739 
740 	if (buf[buflen - 1] == '\n')
741 		buf[buflen - 1] = '\0';
742 
743 	if (strlcpy(hostname, buf, len) >= len) {
744 		fprintf(stderr, MAILNAME_FILE " entry too long");
745 		goto error;
746 	}
747 
748 	return 0;
749 error:
750 	fclose(fp);
751 	free(buf);
752 	return 1;
753 }
754 
755 int
756 getmailname(char *hostname, size_t len)
757 {
758 	struct addrinfo	 hints, *res = NULL;
759 	int		 error;
760 
761 	/* Try MAILNAME_FILE first */
762 	if (parse_mailname_file(hostname, len) == 0)
763 		return 0;
764 
765 	/* Next, gethostname(3) */
766 	if (gethostname(hostname, len) == -1) {
767 		fprintf(stderr, "getmailname: gethostname() failed\n");
768 		return -1;
769 	}
770 
771 	if (strchr(hostname, '.') != NULL)
772 		return 0;
773 
774 	/* Canonicalize if domain part is missing */
775 	memset(&hints, 0, sizeof hints);
776 	hints.ai_family = PF_UNSPEC;
777 	hints.ai_flags = AI_CANONNAME;
778 	error = getaddrinfo(hostname, NULL, &hints, &res);
779 	if (error)
780 		return 0; /* Continue with non-canon hostname */
781 
782 	if (strlcpy(hostname, res->ai_canonname, len) >= len) {
783 		fprintf(stderr, "hostname too long");
784 		freeaddrinfo(res);
785 		return -1;
786 	}
787 
788 	freeaddrinfo(res);
789 	return 0;
790 }
791 
792 int
793 base64_encode(unsigned char const *src, size_t srclen,
794 	      char *dest, size_t destsize)
795 {
796 	return __b64_ntop(src, srclen, dest, destsize);
797 }
798 
799 int
800 base64_decode(char const *src, unsigned char *dest, size_t destsize)
801 {
802 	return __b64_pton(src, dest, destsize);
803 }
804 
805 int
806 base64_encode_rfc3548(unsigned char const *src, size_t srclen,
807 	      char *dest, size_t destsize)
808 {
809 	size_t i;
810 	int ret;
811 
812 	if ((ret = base64_encode(src, srclen, dest, destsize)) == -1)
813 		return -1;
814 
815 	for (i = 0; i < destsize; ++i) {
816 		if (dest[i] == '/')
817 			dest[i] = '_';
818 		else if (dest[i] == '+')
819 			dest[i] = '-';
820 	}
821 
822 	return ret;
823 }
824 
825 void
826 log_trace0(const char *emsg, ...)
827 {
828 	va_list	 ap;
829 
830 	va_start(ap, emsg);
831 	vlog(LOG_DEBUG, emsg, ap);
832 	va_end(ap);
833 }
834 
835 void
836 log_trace_verbose(int v)
837 {
838 	tracing = v;
839 
840 	/* Set debug logging in log.c */
841 	log_setverbose(v & TRACE_DEBUG ? 2 : foreground_log);
842 }
843