xref: /freebsd/contrib/sendmail/makemap/makemap.c (revision a521f2116473fbd8c09db395518f060a27d02334)
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(&params, '\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, &params);
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