Home | History | Annotate | Download | only in pmodes
      1 /*
      2  * CDDL HEADER START
      3  *
      4  * The contents of this file are subject to the terms of the
      5  * Common Development and Distribution License (the "License").
      6  * You may not use this file except in compliance with the License.
      7  *
      8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
      9  * or http://www.opensolaris.org/os/licensing.
     10  * See the License for the specific language governing permissions
     11  * and limitations under the License.
     12  *
     13  * When distributing Covered Code, include this CDDL HEADER in each
     14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
     15  * If applicable, add the following below this CDDL HEADER, with the
     16  * fields enclosed by brackets "[]" replaced with your own identifying
     17  * information: Portions Copyright [yyyy] [name of copyright owner]
     18  *
     19  * CDDL HEADER END
     20  */
     21 /*
     22  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
     23  * Use is subject to license terms.
     24  *
     25  * $Id: pmodes.c,v 1.23 1999/03/22 14:51:16 casper Exp $
     26  *
     27  *
     28  * Program to list files from packages with modes that are to
     29  * permissive.  Usage:
     30  *
     31  *	pmodes [options] pkgdir ...
     32  *
     33  * Pmodes currently has 4 types of modes that are changed:
     34  *
     35  *	m	remove group/other write permissions of all files,
     36  *		except those in the exceptions list.
     37  *	w	remove user write permission for executables that
     38  *		are not root owned.
     39  *	s	remove g/o read permission for set-uid/set-gid executables
     40  *	o	change the owner of files/directories that can be safely
     41  *		chowned to root.
     42  *
     43  *	Any combination of changes can be switched of by specifying -X
     44  *
     45  *	The -n option will create a "FILE.new" file for all changed
     46  *	pkgmap/prototype files.
     47  *	The -D option will limit changes to directories only.
     48  *
     49  * output:
     50  *
     51  * d m oldmode -> newmode pathname
     52  * | ^ whether the file/dir is group writable or even world writable
     53  * > type of file.
     54  * d o owner -> newowner pathname [mode]
     55  *
     56  *
     57  * Casper Dik (Casper.Dik (at) Holland.Sun.COM)
     58  */
     59 
     60 #pragma ident	"%Z%%M%	%I%	%E% SMI"
     61 
     62 #include <stdio.h>
     63 #include <unistd.h>
     64 #include <string.h>
     65 #include <ctype.h>
     66 #include <dirent.h>
     67 #include <stdlib.h>
     68 #include <errno.h>
     69 #include <sys/param.h>
     70 #include <sys/stat.h>
     71 #include "binsearch.h"
     72 
     73 static char *exceptions[] = {
     74 #include "exceptions.h"
     75 };
     76 
     77 static char *exempt_pkgs[] = {
     78 	"SUNWSMSdf",	/* "data files" package for SMS */
     79 	"SUNWSMSr",	/* "root" package for SMS */
     80 	"SUNWSMSsu",	/* "user" package for SMS */
     81 	"SUNWnethackr",	/* "root" package for nethack */
     82 };
     83 
     84 #define	NEXEMPT	(sizeof (exempt_pkgs) / sizeof (char *))
     85 
     86 #define	PROTO "prototype_"
     87 
     88 #define	DEFAULT_SU 0
     89 #define	DEFAULT_OWNER 1
     90 #define	DEFAULT_MODES 1
     91 #define	DEFAULT_USERWRITE 1
     92 #define	DEFAULT_DIRSONLY 0
     93 #define	DEFAULT_EDITABLE 1
     94 
     95 static int nexceptions = sizeof (exceptions)/sizeof (char *);
     96 static int dosu = DEFAULT_SU;
     97 static int doowner = DEFAULT_OWNER;
     98 static int domodes = DEFAULT_MODES;
     99 static int douserwrite = DEFAULT_USERWRITE;
    100 static int dirsonly = DEFAULT_DIRSONLY;
    101 static int editable = DEFAULT_EDITABLE;
    102 static int makenew = 0;
    103 static int installnew = 0;
    104 static int diffout = 0;
    105 static int proto = 0;
    106 static int verbose = 0;
    107 static int quiet = 0;
    108 static int errors = 0;
    109 
    110 static void update_map(char *, char *, int);
    111 
    112 static char *program;
    113 
    114 itemlist restrictto = NULL;
    115 
    116 static void
    117 usage(void) {
    118 	(void) fprintf(stderr,
    119 	    "Usage: %s [-DowsnNmdePvq] [-r file] pkgdir ...\n", program);
    120 	exit(1);
    121 }
    122 
    123 int
    124 main(int argc, char **argv)
    125 {
    126 	char buf[8192];
    127 	int c;
    128 	extern int optind, opterr;
    129 
    130 	opterr = 0;
    131 
    132 	program = argv[0];
    133 
    134 	while ((c = getopt(argc, argv, "eDowsnNmdPvqr:")) != EOF) {
    135 		switch (c) {
    136 		case 's': dosu = !DEFAULT_SU; break;
    137 		case 'o': doowner = !DEFAULT_OWNER; break;
    138 		case 'm': domodes = !DEFAULT_MODES; break;
    139 		case 'w': douserwrite = !DEFAULT_USERWRITE; break;
    140 		case 'D': dirsonly = !DEFAULT_DIRSONLY; break;
    141 		case 'e': editable = !DEFAULT_EDITABLE; break;
    142 		case 'N': installnew = 1; /* FALLTHROUGH */
    143 		case 'n': makenew = 1; break;
    144 		case 'd': diffout = 1; break;
    145 		case 'P': proto = 1; break;
    146 		case 'v': verbose = 1; break;
    147 		case 'q': quiet = 1; break;
    148 		case 'r':
    149 			if (restrictto == NULL)
    150 				restrictto = new_itemlist();
    151 			if (item_addfile(restrictto, optarg) != 0) {
    152 				perror(optarg);
    153 				exit(1);
    154 			}
    155 			break;
    156 		default:
    157 		case '?': usage(); break;
    158 		}
    159 	}
    160 	argc -= optind;
    161 	argv += optind;
    162 
    163 	if (argc < 1)
    164 		usage();
    165 
    166 	for (; *argv; argv++) {
    167 		FILE *info;
    168 		char name[MAXPATHLEN];
    169 		char basedir[MAXPATHLEN] = "/";
    170 		int basedir_len;
    171 		struct stat stb;
    172 		int isfile = 0;
    173 		boolean_t exempt = B_FALSE;
    174 
    175 		/*
    176 		 * If a plain file is passed on the command line, we assume
    177 		 * it's a prototype or pkgmap file and try to find the matching
    178 		 * pkginfo file
    179 		 */
    180 		if (lstat(*argv, &stb) == 0 && S_ISREG(stb.st_mode)) {
    181 			char *lastslash = strrchr(*argv, '/');
    182 
    183 			if (lastslash != NULL)
    184 				*lastslash = '\0';
    185 			(void) sprintf(name, "%s/pkginfo", *argv);
    186 			if (lastslash != NULL)
    187 				*lastslash = '/';
    188 			isfile = 1;
    189 		} else
    190 			(void) sprintf(name, "%s/pkginfo", *argv);
    191 
    192 		/* if there's no pkginfo file, it could be a prototype area */
    193 
    194 		if (access(name, R_OK) != 0)
    195 			(void) strcat(name, ".tmpl");
    196 
    197 		info = fopen(name, "r");
    198 		if (info == 0) {
    199 			if (!quiet)
    200 				(void) fprintf(stderr,
    201 				    "Can't open pkginfo file %s\n", name);
    202 			continue;
    203 		}
    204 
    205 		while (fgets(buf, sizeof (buf), info) != NULL && !exempt) {
    206 			if (strncmp(buf, "BASEDIR=", 8) == 0) {
    207 				(void) strcpy(basedir, buf+8);
    208 				basedir[strlen(basedir)-1] = '\0';
    209 			} else if (strncmp(buf, "PKG=", 4) == 0) {
    210 				int i;
    211 				char *str;
    212 
    213 				str = buf + sizeof ("PKG=") - 1;
    214 				str[strlen(str)-1] = '\0';
    215 				if (str[0] == '"')
    216 					str++;
    217 				if (str[strlen(str)-1] == '"')
    218 					str[strlen(str)-1] = '\0';
    219 				for (i = 0; i < NEXEMPT; i++) {
    220 					if (strcmp(exempt_pkgs[i], str) == 0) {
    221 						exempt = B_TRUE;
    222 						break;
    223 					}
    224 				}
    225 			}
    226 		}
    227 
    228 		(void) fclose(info);
    229 
    230 		/* exempt package */
    231 		if (exempt)
    232 			continue;
    233 
    234 		basedir_len = strlen(basedir);
    235 		if (basedir_len != 1)
    236 			basedir[basedir_len++] = '/';
    237 
    238 		(void) sprintf(name, "%s/pkgmap", *argv);
    239 		if (isfile)
    240 			update_map(*argv, basedir, basedir_len);
    241 		else if (!proto && access(name, R_OK) == 0)
    242 			update_map(name, basedir, basedir_len);
    243 		else {
    244 			DIR *d = opendir(*argv);
    245 			struct dirent *de;
    246 
    247 			if (d == NULL) {
    248 				(void) fprintf(stderr,
    249 					"Can't read directory \"%s\"\n", *argv);
    250 				continue;
    251 			}
    252 			while (de = readdir(d)) {
    253 				/* Skip files with .old or .new suffix */
    254 				if (strstr(de->d_name, PROTO) != NULL &&
    255 				    strncmp(de->d_name, ".del-", 5) != 0 &&
    256 				    strstr(de->d_name, ".old") == NULL &&
    257 				    strstr(de->d_name, ".new") == NULL) {
    258 					(void) sprintf(name, "%s/%s", *argv,
    259 					    de->d_name);
    260 					update_map(name, basedir, basedir_len);
    261 				}
    262 			}
    263 			(void) closedir(d);
    264 		}
    265 	}
    266 	return (errors != 0);
    267 }
    268 
    269 #define	NEXTWORD(tmp, end, warnme) \
    270 	do { \
    271 		tmp = strpbrk(tmp, "\t ");\
    272 		if (!tmp) {\
    273 			if (warnme)\
    274 				warn(name, lineno);\
    275 			return (LINE_IGNORE);\
    276 		}\
    277 		end = tmp++;\
    278 		while (*tmp && isspace(*tmp)) tmp++;\
    279 	} while (0)
    280 
    281 static void
    282 warn(const char *file, int line)
    283 {
    284 	(void) fprintf(stderr, "pmodes: %s, line %d: unexpected format\n",
    285 		file, line);
    286 }
    287 
    288 struct parsed_line {
    289 	char *start;		/* buffer start */
    290 	char *rest;		/* buffer after owner */
    291 	char *owner;		/* same size as ut_user */
    292 	char *old_owner;	/* same size as ut_user */
    293 	char group[16];		/* whatever */
    294 	int  modelen;		/* number of mode bytes (3 or 4); */
    295 	int mode;		/* the complete file mode */
    296 	char path[MAXPATHLEN];	/* NUL terminated pathname */
    297 	char type;		/* */
    298 	char realtype;		/* */
    299 };
    300 
    301 #define	LINE_OK		0
    302 #define	LINE_IGNORE	1
    303 #define	LINE_ERROR	2
    304 
    305 static void
    306 put_line(FILE *f, struct parsed_line *line)
    307 {
    308 	if (f != NULL)
    309 		if (line->rest)
    310 			(void) fprintf(f, "%s%.*o %s %s", line->start,
    311 			    line->modelen, line->mode, line->owner, line->rest);
    312 		else
    313 			(void) fputs(line->start, f);
    314 }
    315 
    316 /*
    317  * the first field is the path, the second the type, the
    318  * third the class, the fourth the mode, when appropriate.
    319  * We're interested in
    320  *		f (file)
    321  *		e (edited file)
    322  *		v (volatile file)
    323  *		d (directory)
    324  *		c (character devices)
    325  *		b (block devices)
    326  */
    327 
    328 static int
    329 parse_line(struct parsed_line *parse, char *buf, const char *name, int lineno)
    330 {
    331 	char *tmp;
    332 	char *p = buf;
    333 	char *end, *q;
    334 
    335 	parse->start = buf;
    336 	parse->rest = 0;		/* makes put_line work */
    337 
    338 	/* Trim trailing spaces */
    339 	end = buf + strlen(buf);
    340 	while (end > buf+1 && isspace(end[-2])) {
    341 		end -= 1;
    342 		end[-1] = end[0];
    343 		end[0] = '\0';
    344 	}
    345 
    346 	while (*p && isspace(*p))
    347 		p++;
    348 
    349 	if (*p == '#' || *p == ':' || *p == '\0')
    350 		return (LINE_IGNORE);
    351 
    352 	/*
    353 	 * Special directives; we really should follow the include
    354 	 * directives but we certainly need to look at default
    355 	 */
    356 	if (*p == '!') {
    357 		p++;
    358 		while (*p && isspace(*p))
    359 			p++;
    360 
    361 		if (!*p || *p == '\n')
    362 			return (LINE_IGNORE);
    363 
    364 		if (strncmp(p, "default", 7) == 0) {
    365 			NEXTWORD(p, end, 1);
    366 			parse->type = 'f';
    367 			parse->realtype = 'D';
    368 			strcpy(parse->path, "(default)");
    369 			tmp = p;
    370 			NEXTWORD(p, end, 1);
    371 			goto domode;
    372 		} else if (strncmp(p, "include", 7) == 0) {
    373 			NEXTWORD(p, end, 1);
    374 			if (strstr(p, PROTO) == NULL)
    375 				fprintf(stderr, "including file %s", p);
    376 		}
    377 		return (LINE_IGNORE);
    378 	}
    379 
    380 	/*
    381 	 * Parse the pkgmap line:
    382 	 * [<number>] <type> <class> <path> [<major> <minor>]
    383 	 * [ <mode> <owner> <group> .... ]
    384 	 */
    385 
    386 	/* Skip first column for non-prototype (i.e., pkgmap) files */
    387 	if (isdigit(*p))
    388 		NEXTWORD(p, end, 1);
    389 
    390 	parse->realtype = parse->type = *p;
    391 
    392 	switch (parse->type) {
    393 	case 'i': case 's': case 'l':
    394 		return (LINE_IGNORE);
    395 	}
    396 
    397 	NEXTWORD(p, end, 1);
    398 
    399 	/* skip class */
    400 	NEXTWORD(p, end, 1);
    401 
    402 	/*
    403 	 * p now points to pathname
    404 	 * At this point, we could have no mode because we are
    405 	 * using a default.
    406 	 */
    407 	tmp = p;
    408 	NEXTWORD(p, end, 0);
    409 
    410 	/* end points to space after name */
    411 	(void) strncpy(parse->path, tmp, end - tmp);
    412 	parse->path[end - tmp] = '\0';
    413 
    414 	switch (parse->type) {
    415 	case 'e':
    416 	case 'v':
    417 		/* type 'e' and 'v' are files, just like 'f', use 'f' in out */
    418 		parse->type = 'f';
    419 		/* FALLTHROUGH */
    420 	case 'f':
    421 	case 'd':
    422 	case 'p': /* FIFO - assume mode is sensible, don't treat as file */
    423 		break;
    424 
    425 	case 'x': /* Exclusive directory */
    426 		parse->type = 'd';
    427 		break;
    428 
    429 	/* device files have class major minor, skip */
    430 	case 'c':
    431 	case 'b':
    432 		NEXTWORD(p, end, 1); NEXTWORD(p, end, 1);
    433 		break;
    434 
    435 	default:
    436 		(void) fprintf(stderr, "Unknown type '%c', %s:%d\n",
    437 			    parse->type, name, lineno);
    438 		return (LINE_ERROR);
    439 	}
    440 	tmp = p;
    441 	NEXTWORD(p, end, 1);
    442 
    443 domode:
    444 	/*
    445 	 * the mode is either a 4 digit number (file is sticky/set-uid or
    446 	 * set-gid or the mode has a leading 0) or a three digit number
    447 	 * mode has all the mode bits, mode points to the three least
    448 	 * significant bit so fthe mode
    449 	 */
    450 	parse->mode = 0;
    451 	for (q = tmp; q < end; q++) {
    452 		if (!isdigit(*q) || *q > '7') {
    453 			(void) fprintf(stderr,
    454 			    "Warning: Unparseble mode \"%.*s\" at %s:%d\n",
    455 			    end-tmp, tmp, name, lineno);
    456 			return (LINE_IGNORE);
    457 		}
    458 		parse->mode <<= 3;
    459 		parse->mode += *q - '0';
    460 	}
    461 	parse->modelen = end - tmp;
    462 	tmp[0] = '\0';
    463 
    464 	parse->old_owner = parse->owner = p;
    465 
    466 	NEXTWORD(p, end, 1);
    467 
    468 	parse->rest = end+1;
    469 	*end = '\0';
    470 
    471 	(void) memset(parse->group, 0, sizeof (parse->group));
    472 	(void) strncpy(parse->group, end+1, strcspn(end+1, " \t\n"));
    473 
    474 	return (LINE_OK);
    475 }
    476 
    477 static void
    478 update_map(char *name, char *basedir, int basedir_len)
    479 {
    480 	char buf[8192];
    481 	int i;
    482 	FILE *map, *newmap;
    483 	char newname[MAXPATHLEN];
    484 	int nchanges = 0;
    485 	unsigned int lineno = 0;
    486 	struct parsed_line line;
    487 	char *fname;
    488 
    489 	map = fopen(name, "r");
    490 	if (map == 0) {
    491 		(void) fprintf(stderr, "Can't open \"%s\"\n", name);
    492 		return;
    493 	}
    494 	(void) strcpy(newname, name);
    495 	(void) strcat(newname, ".new");
    496 	if (makenew) {
    497 		newmap = fopen(newname, "w");
    498 		if (newmap == 0)
    499 			(void) fprintf(stderr, "Can't open %s for writing\n",
    500 			    name);
    501 	} else
    502 		newmap = 0;
    503 
    504 	/* Get last one or two components non-trivial of pathname */
    505 	if (verbose) {
    506 		char *tmp = name + strlen(name);
    507 		int cnt = 0, first = 0;
    508 
    509 		while (--tmp > name && cnt < 2) {
    510 			if (*tmp == '/') {
    511 				if (++cnt == 1)
    512 					first = tmp - name;
    513 				else  {
    514 					fname = tmp + 1;
    515 					/* Triviality check */
    516 					if (tmp - name > first - 4)
    517 						cnt--;
    518 				}
    519 			}
    520 		}
    521 		if (cnt < 2)
    522 			fname = name;
    523 	}
    524 
    525 	nchanges = 0;
    526 
    527 	for (; fgets(buf, sizeof (buf), map) != 0; put_line(newmap, &line)) {
    528 
    529 		int root_owner, mode_diff = 0;
    530 		int changed = 0;
    531 
    532 		lineno ++;
    533 
    534 		switch (parse_line(&line, buf, name, lineno)) {
    535 		case LINE_IGNORE:
    536 			continue;
    537 		case LINE_ERROR:
    538 			errors++;
    539 			continue;
    540 		}
    541 
    542 		if (restrictto) {
    543 			char nbuf[MAXPATHLEN];
    544 			snprintf(nbuf, sizeof (nbuf), "%.*s%s", basedir_len,
    545 			    basedir, line.path);
    546 
    547 			if (item_search(restrictto, nbuf) == -1)
    548 				continue;
    549 		}
    550 
    551 		if (dirsonly && line.type != 'd')
    552 			continue;
    553 
    554 		root_owner = strcmp(line.owner, "root") == 0;
    555 		if (dosu && line.type == 'f' && (line.mode & (S_ISUID|S_ISGID)))
    556 			mode_diff = line.mode & (S_IRGRP|S_IROTH);
    557 
    558 		/*
    559 		 * The following heuristics are used to determine whether a file
    560 		 * can be safely chown'ed to root:
    561 		 *	- it's not set-uid.
    562 		 *	and one of the following applies:
    563 		 *	    - it's not writable by the current owner and is
    564 		 *	    group/world readable
    565 		 *	    - it's world executable and a file
    566 		 *	    - owner, group and world permissions are identical
    567 		 *	    - it's a bin owned directory or a "non-volatile"
    568 		 *	    file (any owner) for which group and other r-x
    569 		 *	    permissions are identical, or it's a bin owned
    570 		 *	    executable or it's a /etc/security/dev/ device
    571 		 */
    572 
    573 		if (doowner && !(line.mode & S_ISUID) &&
    574 		    !root_owner &&
    575 		    ((!(line.mode & S_IWUSR) &&
    576 			(line.mode&(S_IRGRP|S_IROTH)) == (S_IRGRP|S_IROTH)) ||
    577 			(line.type == 'f' && (line.mode & S_IXOTH)) ||
    578 			((line.mode & 07) == ((line.mode>>3) & 07) &&
    579 			    (line.mode & 07) == ((line.mode>>6) & 07) &&
    580 			    strcmp(line.owner, "uucp") != 0) ||
    581 			((line.type == 'd' && strcmp(line.owner, "bin") == 0 ||
    582 			    (editable && strcmp(line.owner, "bin") == 0  ?
    583 				    line.type : line.realtype)  == 'f') &&
    584 			    ((line.mode & 05) == ((line.mode>>3) & 05) ||
    585 				(line.mode & 0100) &&
    586 				strcmp(line.owner, "bin") == 0) &&
    587 			    ((line.mode & 0105) != 0 ||
    588 				basedir_len < 18 &&
    589 				strncmp(basedir, "/etc/security/dev/",
    590 				    basedir_len) == 0 &&
    591 				strncmp(line.path, "/etc/security/dev/"
    592 				    + basedir_len, 18 - basedir_len) == 0)))) {
    593 			if (!diffout) {
    594 				if (!changed && verbose && !nchanges)
    595 					(void) printf("%s:\n", fname);
    596 				(void) printf("%c o %s -> root %s%s [%.*o]\n",
    597 					line.realtype, line.owner, basedir,
    598 					line.path, line.modelen, line.mode);
    599 			}
    600 			line.owner = "root";
    601 			root_owner = 1;
    602 			changed = 1;
    603 		}
    604 		/*
    605 		 * Strip user write bit if owner != root and executable by user.
    606 		 * root can write even if no write bits set
    607 		 * Could prevent  executables from being overwritten.
    608 		 */
    609 		if (douserwrite && line.type == 'f' && !root_owner &&
    610 		    (line.mode & (S_IWUSR|S_IXUSR)) == (S_IWUSR|S_IXUSR))
    611 			mode_diff |= S_IWUSR;
    612 
    613 
    614 		if (domodes && (line.mode & (S_IWGRP|S_IWOTH)) != 0 &&
    615 		    (line.mode & S_ISVTX) == 0) {
    616 			if (basedir_len <= 1) { /* root dir */
    617 				for (i = 0; i < nexceptions; i++) {
    618 					if (strcmp(line.path,
    619 					    exceptions[i]+basedir_len) == 0)
    620 						break;
    621 				}
    622 			} else {
    623 				for (i = 0; i < nexceptions; i++) {
    624 					if (strncmp(basedir, exceptions[i],
    625 						basedir_len) == 0 &&
    626 					    strcmp(line.path,
    627 						exceptions[i]+basedir_len) == 0)
    628 						break;
    629 				}
    630 			}
    631 			if (i == nexceptions)
    632 				mode_diff |= line.mode & (S_IWGRP|S_IWOTH);
    633 		}
    634 
    635 		if (mode_diff) {
    636 			int oldmode = line.mode;
    637 
    638 			line.mode &= ~mode_diff;
    639 
    640 			if (line.mode != oldmode) {
    641 				if (!diffout) {
    642 					if (!changed && verbose && !nchanges)
    643 						(void) printf("%s:\n", fname);
    644 					printf("%c %c %04o -> %04o %s%s\n",
    645 					    line.realtype,
    646 					    (mode_diff & (S_IRGRP|S_IROTH)) ?
    647 						's' : 'm',
    648 						oldmode, line.mode, basedir,
    649 						line.path);
    650 				}
    651 				changed = 1;
    652 			}
    653 		}
    654 		nchanges += changed;
    655 		if (diffout && changed) {
    656 			if (nchanges == 1 && verbose)
    657 				(void) printf("%s:\n", fname);
    658 
    659 			(void) printf("< %c %04o %s %s %s%s\n", line.realtype,
    660 			    line.mode | mode_diff, line.old_owner, line.group,
    661 			    basedir, line.path);
    662 			(void) printf("> %c %04o %s %s %s%s\n", line.realtype,
    663 			    line.mode, line.owner, line.group, basedir,
    664 			    line.path);
    665 		}
    666 	}
    667 	(void) fclose(map);
    668 
    669 	if (newmap != NULL) {
    670 		(void) fflush(newmap);
    671 		if (ferror(newmap)) {
    672 			(void) fprintf(stderr, "Error writing %s\n", name);
    673 			return;
    674 		}
    675 		(void) fclose(newmap);
    676 		if (nchanges == 0)
    677 			(void) unlink(newname);
    678 		else if (installnew) {
    679 			char oldname[MAXPATHLEN];
    680 
    681 			(void) strcpy(oldname, name);
    682 			(void) strcat(oldname, ".old");
    683 			if (rename(name, oldname) == -1 ||
    684 			    rename(newname, name) == -1)
    685 				(void) fprintf(stderr,
    686 				    "Couldn't install %s: %s\n",
    687 				    newname, strerror(errno));
    688 		}
    689 	}
    690 }
    691