xref: /openbsd-src/usr.sbin/smtpd/util.c (revision c90a81c56dcebd6a1b73fe4aff9b03385b8e63b3)
1 /*	$OpenBSD: util.c,v 1.138 2018/10/31 16:32:12 gilles 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 == -1 || ret >= (int)size)
180 		return 0;
181 
182 	return 1;
183 }
184 
185 
186 static int
187 mkdirs_component(char *path, mode_t mode)
188 {
189 	struct stat	sb;
190 
191 	if (stat(path, &sb) == -1) {
192 		if (errno != ENOENT)
193 			return 0;
194 		if (mkdir(path, mode | S_IWUSR | S_IXUSR) == -1)
195 			return 0;
196 	}
197 	else if (!S_ISDIR(sb.st_mode))
198 		return 0;
199 
200 	return 1;
201 }
202 
203 int
204 mkdirs(char *path, mode_t mode)
205 {
206 	char	 buf[PATH_MAX];
207 	int	 i = 0;
208 	int	 done = 0;
209 	char	*p;
210 
211 	/* absolute path required */
212 	if (*path != '/')
213 		return 0;
214 
215 	/* make sure we don't exceed PATH_MAX */
216 	if (strlen(path) >= sizeof buf)
217 		return 0;
218 
219 	memset(buf, 0, sizeof buf);
220 	for (p = path; *p; p++) {
221 		if (*p == '/') {
222 			if (buf[0] != '\0')
223 				if (!mkdirs_component(buf, mode))
224 					return 0;
225 			while (*p == '/')
226 				p++;
227 			buf[i++] = '/';
228 			buf[i++] = *p;
229 			if (*p == '\0' && ++done)
230 				break;
231 			continue;
232 		}
233 		buf[i++] = *p;
234 	}
235 	if (!done)
236 		if (!mkdirs_component(buf, mode))
237 			return 0;
238 
239 	if (chmod(path, mode) == -1)
240 		return 0;
241 
242 	return 1;
243 }
244 
245 int
246 ckdir(const char *path, mode_t mode, uid_t owner, gid_t group, int create)
247 {
248 	char		mode_str[12];
249 	int		ret;
250 	struct stat	sb;
251 
252 	if (stat(path, &sb) == -1) {
253 		if (errno != ENOENT || create == 0) {
254 			log_warn("stat: %s", path);
255 			return (0);
256 		}
257 
258 		/* chmod is deferred to avoid umask effect */
259 		if (mkdir(path, 0) == -1) {
260 			log_warn("mkdir: %s", path);
261 			return (0);
262 		}
263 
264 		if (chown(path, owner, group) == -1) {
265 			log_warn("chown: %s", path);
266 			return (0);
267 		}
268 
269 		if (chmod(path, mode) == -1) {
270 			log_warn("chmod: %s", path);
271 			return (0);
272 		}
273 
274 		if (stat(path, &sb) == -1) {
275 			log_warn("stat: %s", path);
276 			return (0);
277 		}
278 	}
279 
280 	ret = 1;
281 
282 	/* check if it's a directory */
283 	if (!S_ISDIR(sb.st_mode)) {
284 		ret = 0;
285 		log_warnx("%s is not a directory", path);
286 	}
287 
288 	/* check that it is owned by owner/group */
289 	if (sb.st_uid != owner) {
290 		ret = 0;
291 		log_warnx("%s is not owned by uid %d", path, owner);
292 	}
293 	if (sb.st_gid != group) {
294 		ret = 0;
295 		log_warnx("%s is not owned by gid %d", path, group);
296 	}
297 
298 	/* check permission */
299 	if ((sb.st_mode & 07777) != mode) {
300 		ret = 0;
301 		strmode(mode, mode_str);
302 		mode_str[10] = '\0';
303 		log_warnx("%s must be %s (%o)", path, mode_str + 1, mode);
304 	}
305 
306 	return ret;
307 }
308 
309 int
310 rmtree(char *path, int keepdir)
311 {
312 	char		*path_argv[2];
313 	FTS		*fts;
314 	FTSENT		*e;
315 	int		 ret, depth;
316 
317 	path_argv[0] = path;
318 	path_argv[1] = NULL;
319 	ret = 0;
320 	depth = 0;
321 
322 	fts = fts_open(path_argv, FTS_PHYSICAL | FTS_NOCHDIR, NULL);
323 	if (fts == NULL) {
324 		log_warn("fts_open: %s", path);
325 		return (-1);
326 	}
327 
328 	while ((e = fts_read(fts)) != NULL) {
329 		switch (e->fts_info) {
330 		case FTS_D:
331 			depth++;
332 			break;
333 		case FTS_DP:
334 		case FTS_DNR:
335 			depth--;
336 			if (keepdir && depth == 0)
337 				continue;
338 			if (rmdir(e->fts_path) == -1) {
339 				log_warn("rmdir: %s", e->fts_path);
340 				ret = -1;
341 			}
342 			break;
343 
344 		case FTS_F:
345 			if (unlink(e->fts_path) == -1) {
346 				log_warn("unlink: %s", e->fts_path);
347 				ret = -1;
348 			}
349 		}
350 	}
351 
352 	fts_close(fts);
353 
354 	return (ret);
355 }
356 
357 int
358 mvpurge(char *from, char *to)
359 {
360 	size_t		 n;
361 	int		 retry;
362 	const char	*sep;
363 	char		 buf[PATH_MAX];
364 
365 	if ((n = strlen(to)) == 0)
366 		fatalx("to is empty");
367 
368 	sep = (to[n - 1] == '/') ? "" : "/";
369 	retry = 0;
370 
371 again:
372 	(void)snprintf(buf, sizeof buf, "%s%s%u", to, sep, arc4random());
373 	if (rename(from, buf) == -1) {
374 		/* ENOTDIR has actually 2 meanings, and incorrect input
375 		 * could lead to an infinite loop. Consider that after
376 		 * 20 tries something is hopelessly wrong.
377 		 */
378 		if (errno == ENOTEMPTY || errno == EISDIR || errno == ENOTDIR) {
379 			if ((retry++) >= 20)
380 				return (-1);
381 			goto again;
382 		}
383 		return -1;
384 	}
385 
386 	return 0;
387 }
388 
389 
390 int
391 mktmpfile(void)
392 {
393 	char		path[PATH_MAX];
394 	int		fd;
395 
396 	if (!bsnprintf(path, sizeof(path), "%s/smtpd.XXXXXXXXXX",
397 		PATH_TEMPORARY)) {
398 		log_warn("snprintf");
399 		fatal("exiting");
400 	}
401 
402 	if ((fd = mkstemp(path)) == -1) {
403 		log_warn("cannot create temporary file %s", path);
404 		fatal("exiting");
405 	}
406 	unlink(path);
407 	return (fd);
408 }
409 
410 
411 /* Close file, signifying temporary error condition (if any) to the caller. */
412 int
413 safe_fclose(FILE *fp)
414 {
415 	if (ferror(fp)) {
416 		fclose(fp);
417 		return 0;
418 	}
419 	if (fflush(fp)) {
420 		fclose(fp);
421 		if (errno == ENOSPC)
422 			return 0;
423 		fatal("safe_fclose: fflush");
424 	}
425 	if (fsync(fileno(fp)))
426 		fatal("safe_fclose: fsync");
427 	if (fclose(fp))
428 		fatal("safe_fclose: fclose");
429 
430 	return 1;
431 }
432 
433 int
434 hostname_match(const char *hostname, const char *pattern)
435 {
436 	while (*pattern != '\0' && *hostname != '\0') {
437 		if (*pattern == '*') {
438 			while (*pattern == '*')
439 				pattern++;
440 			while (*hostname != '\0' &&
441 			    tolower((unsigned char)*hostname) !=
442 			    tolower((unsigned char)*pattern))
443 				hostname++;
444 			continue;
445 		}
446 
447 		if (tolower((unsigned char)*pattern) !=
448 		    tolower((unsigned char)*hostname))
449 			return 0;
450 		pattern++;
451 		hostname++;
452 	}
453 
454 	return (*hostname == '\0' && *pattern == '\0');
455 }
456 
457 int
458 mailaddr_match(const struct mailaddr *maddr1, const struct mailaddr *maddr2)
459 {
460 	struct mailaddr m1 = *maddr1;
461 	struct mailaddr m2 = *maddr2;
462 	char	       *p;
463 
464 	/* catchall */
465 	if (m2.user[0] == '\0' && m2.domain[0] == '\0')
466 		return 1;
467 
468 	if (m2.domain[0] && !hostname_match(m1.domain, m2.domain))
469 		return 0;
470 
471 	if (m2.user[0]) {
472 		/* if address from table has a tag, we must respect it */
473 		if (strchr(m2.user, *env->sc_subaddressing_delim) == NULL) {
474 			/* otherwise, strip tag from session address if any */
475 			p = strchr(m1.user, *env->sc_subaddressing_delim);
476 			if (p)
477 				*p = '\0';
478 		}
479 		if (strcasecmp(m1.user, m2.user))
480 			return 0;
481 	}
482 	return 1;
483 }
484 
485 int
486 valid_localpart(const char *s)
487 {
488 #define IS_ATEXT(c) (isalnum((unsigned char)(c)) || strchr(MAILADDR_ALLOWED, (c)))
489 nextatom:
490 	if (!IS_ATEXT(*s) || *s == '\0')
491 		return 0;
492 	while (*(++s) != '\0') {
493 		if (*s == '.')
494 			break;
495 		if (IS_ATEXT(*s))
496 			continue;
497 		return 0;
498 	}
499 	if (*s == '.') {
500 		s++;
501 		goto nextatom;
502 	}
503 	return 1;
504 }
505 
506 int
507 valid_domainpart(const char *s)
508 {
509 	struct in_addr	 ina;
510 	struct in6_addr	 ina6;
511 	char		*c, domain[SMTPD_MAXDOMAINPARTSIZE];
512 	const char	*p;
513 
514 	if (*s == '[') {
515 		if (strncasecmp("[IPv6:", s, 6) == 0)
516 			p = s + 6;
517 		else
518 			p = s + 1;
519 
520 		if (strlcpy(domain, p, sizeof domain) >= sizeof domain)
521 			return 0;
522 
523 		c = strchr(domain, (int)']');
524 		if (!c || c[1] != '\0')
525 			return 0;
526 
527 		*c = '\0';
528 
529 		if (inet_pton(AF_INET6, domain, &ina6) == 1)
530 			return 1;
531 		if (inet_pton(AF_INET, domain, &ina) == 1)
532 			return 1;
533 
534 		return 0;
535 	}
536 
537 	if (*s == '\0')
538 		return 0;
539 
540 	return res_hnok(s);
541 }
542 
543 int
544 valid_smtp_response(const char *s)
545 {
546 	if (strlen(s) < 5)
547 		return 0;
548 
549 	if ((s[0] < '2' || s[0] > '5') ||
550 	    (s[1] < '0' || s[1] > '9') ||
551 	    (s[2] < '0' || s[2] > '9') ||
552 	    (s[3] != ' '))
553 		return 0;
554 
555 	return 1;
556 }
557 
558 int
559 secure_file(int fd, char *path, char *userdir, uid_t uid, int mayread)
560 {
561 	char		 buf[PATH_MAX];
562 	char		 homedir[PATH_MAX];
563 	struct stat	 st;
564 	char		*cp;
565 
566 	if (realpath(path, buf) == NULL)
567 		return 0;
568 
569 	if (realpath(userdir, homedir) == NULL)
570 		homedir[0] = '\0';
571 
572 	/* Check the open file to avoid races. */
573 	if (fstat(fd, &st) < 0 ||
574 	    !S_ISREG(st.st_mode) ||
575 	    st.st_uid != uid ||
576 	    (st.st_mode & (mayread ? 022 : 066)) != 0)
577 		return 0;
578 
579 	/* For each component of the canonical path, walking upwards. */
580 	for (;;) {
581 		if ((cp = dirname(buf)) == NULL)
582 			return 0;
583 		(void)strlcpy(buf, cp, sizeof(buf));
584 
585 		if (stat(buf, &st) < 0 ||
586 		    (st.st_uid != 0 && st.st_uid != uid) ||
587 		    (st.st_mode & 022) != 0)
588 			return 0;
589 
590 		/* We can stop checking after reaching homedir level. */
591 		if (strcmp(homedir, buf) == 0)
592 			break;
593 
594 		/*
595 		 * dirname should always complete with a "/" path,
596 		 * but we can be paranoid and check for "." too
597 		 */
598 		if ((strcmp("/", buf) == 0) || (strcmp(".", buf) == 0))
599 			break;
600 	}
601 
602 	return 1;
603 }
604 
605 void
606 addargs(arglist *args, char *fmt, ...)
607 {
608 	va_list ap;
609 	char *cp;
610 	uint nalloc;
611 	int r;
612 	char	**tmp;
613 
614 	va_start(ap, fmt);
615 	r = vasprintf(&cp, fmt, ap);
616 	va_end(ap);
617 	if (r == -1)
618 		fatal("addargs: argument too long");
619 
620 	nalloc = args->nalloc;
621 	if (args->list == NULL) {
622 		nalloc = 32;
623 		args->num = 0;
624 	} else if (args->num+2 >= nalloc)
625 		nalloc *= 2;
626 
627 	tmp = reallocarray(args->list, nalloc, sizeof(char *));
628 	if (tmp == NULL)
629 		fatal("addargs: reallocarray");
630 	args->list = tmp;
631 	args->nalloc = nalloc;
632 	args->list[args->num++] = cp;
633 	args->list[args->num] = NULL;
634 }
635 
636 int
637 lowercase(char *buf, const char *s, size_t len)
638 {
639 	if (len == 0)
640 		return 0;
641 
642 	if (strlcpy(buf, s, len) >= len)
643 		return 0;
644 
645 	while (*buf != '\0') {
646 		*buf = tolower((unsigned char)*buf);
647 		buf++;
648 	}
649 
650 	return 1;
651 }
652 
653 int
654 uppercase(char *buf, const char *s, size_t len)
655 {
656 	if (len == 0)
657 		return 0;
658 
659 	if (strlcpy(buf, s, len) >= len)
660 		return 0;
661 
662 	while (*buf != '\0') {
663 		*buf = toupper((unsigned char)*buf);
664 		buf++;
665 	}
666 
667 	return 1;
668 }
669 
670 void
671 xlowercase(char *buf, const char *s, size_t len)
672 {
673 	if (len == 0)
674 		fatalx("lowercase: len == 0");
675 
676 	if (!lowercase(buf, s, len))
677 		fatalx("lowercase: truncation");
678 }
679 
680 uint64_t
681 generate_uid(void)
682 {
683 	static uint32_t id;
684 	static uint8_t	inited;
685 	uint64_t	uid;
686 
687 	if (!inited) {
688 		id = arc4random();
689 		inited = 1;
690 	}
691 	while ((uid = ((uint64_t)(id++) << 32 | arc4random())) == 0)
692 		;
693 
694 	return (uid);
695 }
696 
697 int
698 session_socket_error(int fd)
699 {
700 	int		error;
701 	socklen_t	len;
702 
703 	len = sizeof(error);
704 	if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &len) == -1)
705 		fatal("session_socket_error: getsockopt");
706 
707 	return (error);
708 }
709 
710 const char *
711 parse_smtp_response(char *line, size_t len, char **msg, int *cont)
712 {
713 	if (len >= LINE_MAX)
714 		return "line too long";
715 
716 	if (len > 3) {
717 		if (msg)
718 			*msg = line + 4;
719 		if (cont)
720 			*cont = (line[3] == '-');
721 	} else if (len == 3) {
722 		if (msg)
723 			*msg = line + 3;
724 		if (cont)
725 			*cont = 0;
726 	} else
727 		return "line too short";
728 
729 	/* validate reply code */
730 	if (line[0] < '2' || line[0] > '5' || !isdigit((unsigned char)line[1]) ||
731 	    !isdigit((unsigned char)line[2]))
732 		return "reply code out of range";
733 
734 	return NULL;
735 }
736 
737 static int
738 parse_mailname_file(char *hostname, size_t len)
739 {
740 	FILE	*fp;
741 	char	*buf = NULL;
742 	size_t	 bufsz = 0;
743 	ssize_t	 buflen;
744 
745 	if ((fp = fopen(MAILNAME_FILE, "r")) == NULL)
746 		return 1;
747 
748 	if ((buflen = getline(&buf, &bufsz, fp)) == -1)
749 		goto error;
750 
751 	if (buf[buflen - 1] == '\n')
752 		buf[buflen - 1] = '\0';
753 
754 	if (strlcpy(hostname, buf, len) >= len) {
755 		fprintf(stderr, MAILNAME_FILE " entry too long");
756 		goto error;
757 	}
758 
759 	return 0;
760 error:
761 	fclose(fp);
762 	free(buf);
763 	return 1;
764 }
765 
766 int
767 getmailname(char *hostname, size_t len)
768 {
769 	struct addrinfo	 hints, *res = NULL;
770 	int		 error;
771 
772 	/* Try MAILNAME_FILE first */
773 	if (parse_mailname_file(hostname, len) == 0)
774 		return 0;
775 
776 	/* Next, gethostname(3) */
777 	if (gethostname(hostname, len) == -1) {
778 		fprintf(stderr, "getmailname: gethostname() failed\n");
779 		return -1;
780 	}
781 
782 	if (strchr(hostname, '.') != NULL)
783 		return 0;
784 
785 	/* Canonicalize if domain part is missing */
786 	memset(&hints, 0, sizeof hints);
787 	hints.ai_family = PF_UNSPEC;
788 	hints.ai_flags = AI_CANONNAME;
789 	error = getaddrinfo(hostname, NULL, &hints, &res);
790 	if (error)
791 		return 0; /* Continue with non-canon hostname */
792 
793 	if (strlcpy(hostname, res->ai_canonname, len) >= len) {
794 		fprintf(stderr, "hostname too long");
795 		return -1;
796 	}
797 
798 	freeaddrinfo(res);
799 	return 0;
800 }
801 
802 int
803 base64_encode(unsigned char const *src, size_t srclen,
804 	      char *dest, size_t destsize)
805 {
806 	return __b64_ntop(src, srclen, dest, destsize);
807 }
808 
809 int
810 base64_decode(char const *src, unsigned char *dest, size_t destsize)
811 {
812 	return __b64_pton(src, dest, destsize);
813 }
814 
815 void
816 log_trace(int mask, const char *emsg, ...)
817 {
818 	va_list	 ap;
819 
820 	if (tracing & mask) {
821 		va_start(ap, emsg);
822 		vlog(LOG_DEBUG, emsg, ap);
823 		va_end(ap);
824 	}
825 }
826 
827 void
828 log_trace_verbose(int v)
829 {
830 	tracing = v;
831 
832 	/* Set debug logging in log.c */
833 	log_setverbose(v & TRACE_DEBUG ? 2 : foreground_log);
834 }
835