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
usage(progname)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 *
readcf(cfile,mapfile,fullpath)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
main(argc,argv)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