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