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