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