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