1 /* 2 * Copyright (c) 1998-2002, 2004, 2008, 2020 Proofpoint, Inc. and its suppliers. 3 * All rights reserved. 4 * Copyright (c) 1992 Eric P. Allman. All rights reserved. 5 * Copyright (c) 1992, 1993 6 * The Regents of the University of California. All rights reserved. 7 * 8 * By using this file, you agree to the terms and conditions set 9 * forth in the LICENSE file which can be found at the top level of 10 * the sendmail distribution. 11 * 12 */ 13 14 #include <sm/gen.h> 15 16 SM_IDSTR(copyright, 17 "@(#) Copyright (c) 1998-2002, 2004 Proofpoint, Inc. and its suppliers.\n\ 18 All rights reserved.\n\ 19 Copyright (c) 1992 Eric P. Allman. All rights reserved.\n\ 20 Copyright (c) 1992, 1993\n\ 21 The Regents of the University of California. All rights reserved.\n") 22 23 SM_IDSTR(id, "@(#)$Id: makemap.c,v 8.183 2013-11-22 20:51:52 ca Exp $") 24 25 26 #include <sys/types.h> 27 #ifndef ISC_UNIX 28 # include <sys/file.h> 29 #endif 30 #include <ctype.h> 31 #include <stdlib.h> 32 #include <unistd.h> 33 #ifdef EX_OK 34 # undef EX_OK /* unistd.h may have another use for this */ 35 #endif 36 #include <sysexits.h> 37 #include <sendmail/sendmail.h> 38 #include <sm/path.h> 39 #include <sendmail/pathnames.h> 40 #include <libsmdb/smdb.h> 41 #if USE_EAI 42 # include <sm/ixlen.h> 43 #endif 44 45 uid_t RealUid; 46 gid_t RealGid; 47 char *RealUserName; 48 uid_t RunAsUid; 49 gid_t RunAsGid; 50 char *RunAsUserName; 51 int Verbose = 2; 52 bool DontInitGroups = false; 53 uid_t TrustedUid = 0; 54 BITMAP256 DontBlameSendmail; 55 56 #define BUFSIZE 1024 57 #define ISASCII(c) isascii((unsigned char)(c)) 58 #define ISSEP(c) (sep == '\0' ? ISASCII(c) && isspace(c) : (c) == sep) 59 60 static void usage __P((const char *)); 61 static char *readcf __P((const char *, char *, bool)); 62 63 static void 64 usage(progname) 65 const char *progname; 66 { 67 sm_io_fprintf(smioerr, SM_TIME_DEFAULT, 68 "Usage: %s [-C cffile] [-N] [-c cachesize] [-D commentchar]\n", 69 progname); 70 sm_io_fprintf(smioerr, SM_TIME_DEFAULT, 71 " %*s [-d] [-e] [-f] [-l] [-o] [-r] [-s] [-t delimiter]\n", 72 (int) strlen(progname), ""); 73 #if _FFR_TESTS 74 sm_io_fprintf(smioerr, SM_TIME_DEFAULT, 75 " %*s [-S n]\n", 76 (int) strlen(progname), ""); 77 #endif 78 sm_io_fprintf(smioerr, SM_TIME_DEFAULT, 79 " %*s [-u] [-v] type mapname\n", 80 (int) strlen(progname), ""); 81 exit(EX_USAGE); 82 } 83 84 /* 85 ** READCF -- read some settings from configuration file. 86 ** 87 ** Parameters: 88 ** cfile -- configuration file name. 89 ** mapfile -- file name of map to look up (if not NULL/empty) 90 ** Note: this finds the first match, so in case someone 91 ** uses the same map file for different maps, they are 92 ** hopefully using the same map type. 93 ** fullpath -- compare the full paths or just the "basename"s? 94 ** (even excluding any .ext !) 95 ** 96 ** Returns: 97 ** pointer to map class name (static!) 98 */ 99 100 static char * 101 readcf(cfile, mapfile, fullpath) 102 const char *cfile; 103 char *mapfile; 104 bool fullpath; 105 { 106 SM_FILE_T *cfp; 107 char buf[MAXLINE]; 108 static char classbuf[MAXLINE]; 109 char *classname; 110 char *p; 111 112 if ((cfp = sm_io_open(SmFtStdio, SM_TIME_DEFAULT, cfile, 113 SM_IO_RDONLY, NULL)) == NULL) 114 { 115 sm_io_fprintf(smioerr, SM_TIME_DEFAULT, 116 "makemap: %s: %s\n", 117 cfile, sm_errstring(errno)); 118 exit(EX_NOINPUT); 119 } 120 classname = NULL; 121 classbuf[0] = '\0'; 122 123 if (!fullpath && mapfile != NULL) 124 { 125 p = strrchr(mapfile, '/'); 126 if (p != NULL) 127 mapfile = ++p; 128 p = strrchr(mapfile, '.'); 129 if (p != NULL) 130 *p = '\0'; 131 } 132 133 while (sm_io_fgets(cfp, SM_TIME_DEFAULT, buf, sizeof(buf)) >= 0) 134 { 135 char *b; 136 137 if ((b = strchr(buf, '\n')) != NULL) 138 *b = '\0'; 139 140 b = buf; 141 switch (*b++) 142 { 143 case 'O': /* option */ 144 #if HASFCHOWN 145 if (strncasecmp(b, " TrustedUser", 12) == 0 && 146 !(ISASCII(b[12]) && isalnum(b[12]))) 147 { 148 b = strchr(b, '='); 149 if (b == NULL) 150 continue; 151 while (ISASCII(*++b) && isspace(*b)) 152 continue; 153 if (ISASCII(*b) && isdigit(*b)) 154 TrustedUid = atoi(b); 155 else 156 { 157 struct passwd *pw; 158 159 TrustedUid = 0; 160 pw = getpwnam(b); 161 if (pw == NULL) 162 (void) sm_io_fprintf(smioerr, 163 SM_TIME_DEFAULT, 164 "TrustedUser: unknown user %s\n", b); 165 else 166 TrustedUid = pw->pw_uid; 167 } 168 169 # ifdef UID_MAX 170 if (TrustedUid > UID_MAX) 171 { 172 (void) sm_io_fprintf(smioerr, 173 SM_TIME_DEFAULT, 174 "TrustedUser: uid value (%ld) > UID_MAX (%ld)", 175 (long) TrustedUid, 176 (long) UID_MAX); 177 TrustedUid = 0; 178 } 179 # endif /* UID_MAX */ 180 } 181 #endif /* HASFCHOWN */ 182 break; 183 184 case 'K': /* Keyfile (map) */ 185 if (classname != NULL) /* found it already */ 186 continue; 187 if (mapfile == NULL || *mapfile == '\0') 188 continue; 189 190 /* cut off trailing spaces */ 191 for (p = buf + strlen(buf) - 1; ISASCII(*p) && isspace(*p) && p > buf; p--) 192 *p = '\0'; 193 194 /* find the last argument */ 195 p = strrchr(buf, ' '); 196 if (p == NULL) 197 continue; 198 b = strstr(p, mapfile); 199 if (b == NULL) 200 continue; 201 if (b <= buf) 202 continue; 203 if (!fullpath) 204 { 205 p = strrchr(b, '.'); 206 if (p != NULL) 207 *p = '\0'; 208 } 209 210 /* allow trailing white space? */ 211 if (strcmp(mapfile, b) != 0) 212 continue; 213 /* SM_ASSERT(b > buf); */ 214 --b; 215 if (!ISASCII(*b)) 216 continue; 217 if (!isspace(*b) && fullpath) 218 continue; 219 if (!fullpath && !(SM_IS_DIR_DELIM(*b) || isspace(*b))) 220 continue; 221 222 /* basically from readcf.c */ 223 for (b = buf + 1; ISASCII(*b) && isspace(*b); b++) 224 ; 225 if (!(ISASCII(*b) && isalnum(*b))) 226 { 227 /* syserr("readcf: config K line: no map name"); */ 228 return NULL; 229 } 230 231 while ((ISASCII(*++b) && isalnum(*b)) || *b == '_' || *b == '.') 232 ; 233 if (*b != '\0') 234 *b++ = '\0'; 235 while (ISASCII(*b) && isspace(*b)) 236 b++; 237 if (!(ISASCII(*b) && isalnum(*b))) 238 { 239 /* syserr("readcf: config K line, map %s: no map class", b); */ 240 return NULL; 241 } 242 classname = b; 243 while (ISASCII(*++b) && isalnum(*b)) 244 ; 245 if (*b != '\0') 246 *b++ = '\0'; 247 (void) sm_strlcpy(classbuf, classname, sizeof classbuf); 248 break; 249 250 default: 251 continue; 252 } 253 } 254 (void) sm_io_close(cfp, SM_TIME_DEFAULT); 255 256 return classbuf; 257 } 258 259 int 260 main(argc, argv) 261 int argc; 262 char **argv; 263 { 264 char *progname; 265 char *cfile; 266 bool inclnull = false; 267 bool notrunc = false; 268 bool allowreplace = false; 269 bool allowempty = false; 270 bool verbose = false; 271 bool foldcase = true; 272 bool unmake = false; 273 bool didreadcf = false; 274 char sep = '\0'; 275 char comment = '#'; 276 int exitstat; 277 int opt; 278 char *typename = NULL; 279 char *fallback = NULL; 280 char *mapname = NULL; 281 unsigned int lineno; 282 int st; 283 int mode; 284 int smode; 285 int putflags = 0; 286 long sff = SFF_ROOTOK|SFF_REGONLY; 287 struct passwd *pw; 288 SMDB_DATABASE *database; 289 SMDB_CURSOR *cursor; 290 SMDB_DBENT db_key, db_val; 291 SMDB_DBPARAMS params; 292 SMDB_USER_INFO user_info; 293 char ibuf[BUFSIZE]; 294 static char rnamebuf[MAXNAME]; /* holds RealUserName */ 295 extern char *optarg; 296 extern int optind; 297 #if USE_EAI 298 bool ascii = true; 299 #endif 300 #if _FFR_TESTS 301 int slp = 0; 302 #endif 303 304 memset(¶ms, '\0', sizeof params); 305 params.smdbp_cache_size = 1024 * 1024; 306 307 progname = strrchr(argv[0], '/'); 308 if (progname != NULL) 309 progname++; 310 else 311 progname = argv[0]; 312 cfile = getcfname(0, 0, SM_GET_SENDMAIL_CF, NULL); 313 314 clrbitmap(DontBlameSendmail); 315 RunAsUid = RealUid = getuid(); 316 RunAsGid = RealGid = getgid(); 317 pw = getpwuid(RealUid); 318 if (pw != NULL) 319 (void) sm_strlcpy(rnamebuf, pw->pw_name, sizeof rnamebuf); 320 else 321 (void) sm_snprintf(rnamebuf, sizeof rnamebuf, 322 "Unknown UID %d", (int) RealUid); 323 RunAsUserName = RealUserName = rnamebuf; 324 user_info.smdbu_id = RunAsUid; 325 user_info.smdbu_group_id = RunAsGid; 326 (void) sm_strlcpy(user_info.smdbu_name, RunAsUserName, 327 SMDB_MAX_USER_NAME_LEN); 328 329 #define OPTIONS "C:D:Nc:defi:Llorst:uvx" 330 #if _FFR_TESTS 331 # define X_OPTIONS "S:" 332 #else 333 # define X_OPTIONS 334 #endif 335 while ((opt = getopt(argc, argv, OPTIONS X_OPTIONS)) != -1) 336 { 337 switch (opt) 338 { 339 case 'C': 340 cfile = optarg; 341 break; 342 343 case 'N': 344 inclnull = true; 345 break; 346 347 case 'c': 348 params.smdbp_cache_size = atol(optarg); 349 break; 350 351 case 'd': 352 params.smdbp_allow_dup = true; 353 break; 354 355 case 'e': 356 allowempty = true; 357 break; 358 359 case 'f': 360 foldcase = false; 361 break; 362 363 case 'i': 364 fallback =optarg; 365 break; 366 367 case 'D': 368 comment = *optarg; 369 break; 370 371 case 'L': 372 smdb_print_available_types(false); 373 sm_io_fprintf(smioout, SM_TIME_DEFAULT, 374 "cf\nCF\n"); 375 exit(EX_OK); 376 break; 377 378 case 'l': 379 smdb_print_available_types(false); 380 exit(EX_OK); 381 break; 382 383 case 'o': 384 notrunc = true; 385 break; 386 387 case 'r': 388 allowreplace = true; 389 break; 390 391 #if _FFR_TESTS 392 case 'S': 393 slp = atoi(optarg); 394 break; 395 #endif 396 397 case 's': 398 setbitn(DBS_MAPINUNSAFEDIRPATH, DontBlameSendmail); 399 setbitn(DBS_WRITEMAPTOHARDLINK, DontBlameSendmail); 400 setbitn(DBS_WRITEMAPTOSYMLINK, DontBlameSendmail); 401 setbitn(DBS_LINKEDMAPINWRITABLEDIR, DontBlameSendmail); 402 break; 403 404 case 't': 405 if (optarg == NULL || *optarg == '\0') 406 { 407 sm_io_fprintf(smioerr, SM_TIME_DEFAULT, 408 "Invalid separator\n"); 409 break; 410 } 411 sep = *optarg; 412 break; 413 414 case 'u': 415 unmake = true; 416 break; 417 418 case 'v': 419 verbose = true; 420 break; 421 422 case 'x': 423 smdb_print_available_types(true); 424 exit(EX_OK); 425 break; 426 427 default: 428 usage(progname); 429 /* NOTREACHED */ 430 } 431 } 432 433 if (!bitnset(DBS_WRITEMAPTOSYMLINK, DontBlameSendmail)) 434 sff |= SFF_NOSLINK; 435 if (!bitnset(DBS_WRITEMAPTOHARDLINK, DontBlameSendmail)) 436 sff |= SFF_NOHLINK; 437 if (!bitnset(DBS_LINKEDMAPINWRITABLEDIR, DontBlameSendmail)) 438 sff |= SFF_NOWLINK; 439 440 argc -= optind; 441 argv += optind; 442 if (argc != 2) 443 { 444 usage(progname); 445 /* NOTREACHED */ 446 } 447 else 448 { 449 typename = argv[0]; 450 mapname = argv[1]; 451 } 452 453 #define TYPEFROMCF (strcasecmp(typename, "cf") == 0) 454 #define FULLPATHFROMCF (strcmp(typename, "cf") == 0) 455 456 #if HASFCHOWN 457 if (geteuid() == 0) 458 { 459 if (TYPEFROMCF) 460 typename = readcf(cfile, mapname, FULLPATHFROMCF); 461 else 462 (void) readcf(cfile, NULL, false); 463 didreadcf = true; 464 } 465 #endif /* HASFCHOWN */ 466 467 if (!params.smdbp_allow_dup && !allowreplace) 468 putflags = SMDBF_NO_OVERWRITE; 469 470 if (unmake) 471 { 472 mode = O_RDONLY; 473 smode = S_IRUSR; 474 } 475 else 476 { 477 mode = O_RDWR; 478 if (!notrunc) 479 { 480 mode |= O_CREAT|O_TRUNC; 481 sff |= SFF_CREAT; 482 } 483 smode = S_IWUSR; 484 } 485 486 params.smdbp_num_elements = 4096; 487 488 if (!didreadcf && TYPEFROMCF) 489 { 490 typename = readcf(cfile, mapname, FULLPATHFROMCF); 491 didreadcf = true; 492 } 493 if (didreadcf && (typename == NULL || *typename == '\0')) 494 { 495 if (fallback != NULL && *fallback != '\0') 496 { 497 typename = fallback; 498 if (verbose) 499 (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT, 500 "%s: mapfile %s: not found in %s, using fallback %s\n", 501 progname, mapname, cfile, fallback); 502 } 503 else 504 { 505 (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT, 506 "%s: mapfile %s: not found in %s\n", 507 progname, mapname, cfile); 508 exit(EX_DATAERR); 509 } 510 } 511 512 /* 513 ** Note: if "implicit" is selected it does not work like 514 ** sendmail: it will just use the first available DB type, 515 ** it won't try several (for -u) to find one that "works". 516 */ 517 518 errno = smdb_open_database(&database, mapname, mode, smode, sff, 519 typename, &user_info, ¶ms); 520 if (errno != SMDBE_OK) 521 { 522 char *hint; 523 524 if (errno == SMDBE_UNSUPPORTED_DB_TYPE && 525 (hint = smdb_db_definition(typename)) != NULL) 526 (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT, 527 "%s: Need to recompile with -D%s for %s support\n", 528 progname, hint, typename); 529 else 530 (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT, 531 "%s: error opening type %s map %s: %s\n", 532 progname, typename, mapname, 533 sm_errstring(errno)); 534 exit(EX_CANTCREAT); 535 } 536 537 (void) database->smdb_sync(database, 0); 538 539 if (!unmake && geteuid() == 0 && TrustedUid != 0) 540 { 541 errno = database->smdb_set_owner(database, TrustedUid, -1); 542 if (errno != SMDBE_OK) 543 { 544 (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT, 545 "WARNING: ownership change on %s failed %s", 546 mapname, sm_errstring(errno)); 547 } 548 } 549 550 /* 551 ** Copy the data 552 */ 553 554 exitstat = EX_OK; 555 if (unmake) 556 { 557 errno = database->smdb_cursor(database, &cursor, 0); 558 if (errno != SMDBE_OK) 559 { 560 561 (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT, 562 "%s: cannot make cursor for type %s map %s\n", 563 progname, typename, mapname); 564 exit(EX_SOFTWARE); 565 } 566 567 memset(&db_key, '\0', sizeof db_key); 568 memset(&db_val, '\0', sizeof db_val); 569 570 for (lineno = 0; ; lineno++) 571 { 572 errno = cursor->smdbc_get(cursor, &db_key, &db_val, 573 SMDB_CURSOR_GET_NEXT); 574 if (errno != SMDBE_OK) 575 break; 576 577 (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, 578 "%.*s%c%.*s\n", 579 (int) db_key.size, 580 (char *) db_key.data, 581 (sep != '\0') ? sep : '\t', 582 (int) db_val.size, 583 (char *)db_val.data); 584 585 } 586 (void) cursor->smdbc_close(cursor); 587 } 588 else 589 { 590 lineno = 0; 591 while (sm_io_fgets(smioin, SM_TIME_DEFAULT, ibuf, sizeof ibuf) 592 >= 0) 593 { 594 register char *p; 595 596 lineno++; 597 598 /* 599 ** Parse the line. 600 */ 601 602 p = strchr(ibuf, '\n'); 603 if (p != NULL) 604 *p = '\0'; 605 else if (!sm_io_eof(smioin)) 606 { 607 (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT, 608 "%s: %s: line %u: line too long (%ld bytes max)\n", 609 progname, mapname, lineno, 610 (long) sizeof ibuf); 611 exitstat = EX_DATAERR; 612 continue; 613 } 614 615 if (ibuf[0] == '\0' || ibuf[0] == comment) 616 continue; 617 if (sep == '\0' && ISASCII(ibuf[0]) && isspace(ibuf[0])) 618 { 619 (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT, 620 "%s: %s: line %u: syntax error (leading space)\n", 621 progname, mapname, lineno); 622 exitstat = EX_DATAERR; 623 continue; 624 } 625 626 memset(&db_key, '\0', sizeof db_key); 627 memset(&db_val, '\0', sizeof db_val); 628 db_key.data = ibuf; 629 630 #if USE_EAI 631 db_key.size = 0; 632 if (foldcase) 633 { 634 for (p = ibuf; *p != '\0' && !ISSEP(*p); p++) 635 { 636 if (!ISASCII(*p)) 637 ascii = false; 638 } 639 if (!ascii) 640 { 641 char sep; 642 char *lkey; 643 644 sep = *p; 645 *p = '\0'; 646 647 lkey = sm_lowercase(ibuf); 648 db_key.data = lkey; 649 db_key.size = strlen(lkey); 650 *p = sep; 651 } 652 } 653 if (ascii) 654 #endif /* USE_EAI */ 655 /* NOTE: see if () above! */ 656 for (p = ibuf; *p != '\0' && !ISSEP(*p); p++) 657 { 658 if (foldcase && ISASCII(*p) && isupper(*p)) 659 *p = tolower(*p); 660 } 661 #if USE_EAI 662 if (0 == db_key.size) 663 #endif 664 db_key.size = p - ibuf; 665 if (inclnull) 666 db_key.size++; 667 668 if (*p != '\0') 669 *p++ = '\0'; 670 while (*p != '\0' && ISSEP(*p)) 671 p++; 672 if (!allowempty && *p == '\0') 673 { 674 (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT, 675 "%s: %s: line %u: no RHS for LHS %s\n", 676 progname, mapname, lineno, 677 (char *) db_key.data); 678 exitstat = EX_DATAERR; 679 continue; 680 } 681 682 db_val.data = p; 683 db_val.size = strlen(p); 684 if (inclnull) 685 db_val.size++; 686 687 /* 688 ** Do the database insert. 689 */ 690 691 if (verbose) 692 { 693 (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, 694 "key=`%s', val=`%s'\n", 695 (char *) db_key.data, 696 (char *) db_val.data); 697 } 698 699 errno = database->smdb_put(database, &db_key, &db_val, 700 putflags); 701 switch (errno) 702 { 703 case SMDBE_KEY_EXIST: 704 st = 1; 705 break; 706 707 case 0: 708 st = 0; 709 break; 710 711 default: 712 st = -1; 713 break; 714 } 715 716 if (st < 0) 717 { 718 (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT, 719 "%s: %s: line %u: key %s: put error: %s\n", 720 progname, mapname, lineno, 721 (char *) db_key.data, 722 sm_errstring(errno)); 723 exitstat = EX_IOERR; 724 } 725 else if (st > 0) 726 { 727 (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT, 728 "%s: %s: line %u: key %s: duplicate key\n", 729 progname, mapname, 730 lineno, 731 (char *) db_key.data); 732 exitstat = EX_DATAERR; 733 } 734 } 735 } 736 737 #if _FFR_TESTS 738 if (slp > 0) 739 sleep(slp); 740 #endif 741 742 /* 743 ** Now close the database. 744 */ 745 746 errno = database->smdb_close(database); 747 if (errno != SMDBE_OK) 748 { 749 (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT, 750 "%s: close(%s): %s\n", 751 progname, mapname, sm_errstring(errno)); 752 exitstat = EX_IOERR; 753 } 754 smdb_free_database(database); 755 756 exit(exitstat); 757 758 /* NOTREACHED */ 759 return exitstat; 760 } 761