makemap.c revision 363466
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
16SM_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
23SM_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
42uid_t	RealUid;
43gid_t	RealGid;
44char	*RealUserName;
45uid_t	RunAsUid;
46gid_t	RunAsGid;
47char	*RunAsUserName;
48int	Verbose = 2;
49bool	DontInitGroups = false;
50uid_t	TrustedUid = 0;
51BITMAP256 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
57static void usage __P((const char *));
58static char *readcf __P((const char *, char *, bool));
59
60static void
61usage(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
92static char *
93readcf(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
251int
252main(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