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