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 /*
  23  * Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
  24  * Use is subject to license terms.
  25  *
  26  * $Id: pmodes.c,v 1.23 1999/03/22 14:51:16 casper Exp $
  27  *
  28  *
  29  * Program to list files from packages with modes that are to
  30  * permissive.  Usage:
  31  *
  32  *      pmodes [options] pkgdir ...
  33  *
  34  * Pmodes currently has 4 types of modes that are changed:
  35  *
  36  *      m       remove group/other write permissions of all files,
  37  *              except those in the exceptions list.
  38  *      w       remove user write permission for executables that
  39  *              are not root owned.
  40  *      s       remove g/o read permission for set-uid/set-gid executables
  41  *      o       change the owner of files/directories that can be safely
  42  *              chowned to root.
  43  *
  44  *      Any combination of changes can be switched of by specifying -X
  45  *
  46  *      The -n option will create a "FILE.new" file for all changed
  47  *      pkgmap/prototype files.
  48  *      The -D option will limit changes to directories only.
  49  *
  50  * output:
  51  *
  52  * d m oldmode -> newmode pathname
  53  * | ^ whether the file/dir is group writable or even world writable
  54  * > type of file.
  55  * d o owner -> newowner pathname [mode]
  56  *
  57  *
  58  * Casper Dik (Casper.Dik@Holland.Sun.COM)
  59  */
  60 
  61 #include <stdio.h>
  62 #include <unistd.h>
  63 #include <string.h>
  64 #include <ctype.h>
  65 #include <dirent.h>
  66 #include <stdlib.h>
  67 #include <errno.h>
  68 #include <sys/param.h>
  69 #include <sys/stat.h>
  70 #include "binsearch.h"
  71 
  72 static char *exceptions[] = {
  73         "/etc/lp",
  74         "/var/cache/cups",
  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 }