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