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