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