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