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, Version 1.0 only 6 * (the "License"). You may not use this file except in compliance 7 * with the License. 8 * 9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 10 * or http://www.opensolaris.org/os/licensing. 11 * See the License for the specific language governing permissions 12 * and limitations under the License. 13 * 14 * When distributing Covered Code, include this CDDL HEADER in each 15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 16 * If applicable, add the following below this CDDL HEADER, with the 17 * fields enclosed by brackets "[]" replaced with your own identifying 18 * information: Portions Copyright [yyyy] [name of copyright owner] 19 * 20 * CDDL HEADER END 21 */ 22 /* 23 * Copyright 2004 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 #pragma ident "%Z%%M% %I% %E% SMI" 62 63 #include <stdio.h> 64 #include <unistd.h> 65 #include <string.h> 66 #include <ctype.h> 67 #include <dirent.h> 68 #include <stdlib.h> 69 #include <errno.h> 70 #include <sys/param.h> 71 #include <sys/stat.h> 72 #include "binsearch.h" 73 74 static char *exceptions[] = { 75 #include "exceptions.h" 76 }; 77 78 static char *exempt_pkgs[] = { 79 "SUNWSMSdf", /* "data files" package for SMS */ 80 "SUNWSMSr", /* "root" package for SMS */ 81 "SUNWSMSsu", /* "user" package for SMS */ 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 for (i = 0; i < NEXEMPT; i++) { 216 if (strcmp(exempt_pkgs[i], str) == 0) { 217 exempt = B_TRUE; 218 break; 219 } 220 } 221 } 222 } 223 224 (void) fclose(info); 225 226 /* exempt package */ 227 if (exempt) 228 continue; 229 230 basedir_len = strlen(basedir); 231 if (basedir_len != 1) 232 basedir[basedir_len++] = '/'; 233 234 (void) sprintf(name, "%s/pkgmap", *argv); 235 if (isfile) 236 update_map(*argv, basedir, basedir_len); 237 else if (!proto && access(name, R_OK) == 0) 238 update_map(name, basedir, basedir_len); 239 else { 240 DIR *d = opendir(*argv); 241 struct dirent *de; 242 243 if (d == NULL) { 244 (void) fprintf(stderr, 245 "Can't read directory \"%s\"\n", *argv); 246 continue; 247 } 248 while (de = readdir(d)) { 249 /* Skip files with .old or .new suffix */ 250 if (strstr(de->d_name, PROTO) != NULL && 251 strncmp(de->d_name, ".del-", 5) != 0 && 252 strstr(de->d_name, ".old") == NULL && 253 strstr(de->d_name, ".new") == NULL) { 254 (void) sprintf(name, "%s/%s", *argv, 255 de->d_name); 256 update_map(name, basedir, basedir_len); 257 } 258 } 259 (void) closedir(d); 260 } 261 } 262 return (errors != 0); 263 } 264 265 #define NEXTWORD(tmp, end, warnme) \ 266 do { \ 267 tmp = strpbrk(tmp, "\t ");\ 268 if (!tmp) {\ 269 if (warnme)\ 270 warn(name, lineno);\ 271 return (LINE_IGNORE);\ 272 }\ 273 end = tmp++;\ 274 while (*tmp && isspace(*tmp)) tmp++;\ 275 } while (0) 276 277 static void 278 warn(const char *file, int line) 279 { 280 (void) fprintf(stderr, "pmodes: %s, line %d: unexpected format\n", 281 file, line); 282 } 283 284 struct parsed_line { 285 char *start; /* buffer start */ 286 char *rest; /* buffer after owner */ 287 char *owner; /* same size as ut_user */ 288 char *old_owner; /* same size as ut_user */ 289 char group[16]; /* whatever */ 290 int modelen; /* number of mode bytes (3 or 4); */ 291 int mode; /* the complete file mode */ 292 char path[MAXPATHLEN]; /* NUL terminated pathname */ 293 char type; /* */ 294 char realtype; /* */ 295 }; 296 297 #define LINE_OK 0 298 #define LINE_IGNORE 1 299 #define LINE_ERROR 2 300 301 static void 302 put_line(FILE *f, struct parsed_line *line) 303 { 304 if (f != NULL) 305 if (line->rest) 306 (void) fprintf(f, "%s%.*o %s %s", line->start, 307 line->modelen, line->mode, line->owner, line->rest); 308 else 309 (void) fputs(line->start, f); 310 } 311 312 /* 313 * the first field is the path, the second the type, the 314 * third the class, the fourth the mode, when appropriate. 315 * We're interested in 316 * f (file) 317 * e (edited file) 318 * v (volatile file) 319 * d (directory) 320 * c (character devices) 321 * b (block devices) 322 */ 323 324 static int 325 parse_line(struct parsed_line *parse, char *buf, const char *name, int lineno) 326 { 327 char *tmp; 328 char *p = buf; 329 char *end, *q; 330 331 parse->start = buf; 332 parse->rest = 0; /* makes put_line work */ 333 334 /* Trim trailing spaces */ 335 end = buf + strlen(buf); 336 while (end > buf+1 && isspace(end[-2])) { 337 end -= 1; 338 end[-1] = end[0]; 339 end[0] = '\0'; 340 } 341 342 while (*p && isspace(*p)) 343 p++; 344 345 if (*p == '#' || *p == ':' || *p == '\0') 346 return (LINE_IGNORE); 347 348 /* 349 * Special directives; we really should follow the include 350 * directives but we certainly need to look at default 351 */ 352 if (*p == '!') { 353 p++; 354 while (*p && isspace(*p)) 355 p++; 356 357 if (!*p || *p == '\n') 358 return (LINE_IGNORE); 359 360 if (strncmp(p, "default", 7) == 0) { 361 NEXTWORD(p, end, 1); 362 parse->type = 'f'; 363 parse->realtype = 'D'; 364 strcpy(parse->path, "(default)"); 365 tmp = p; 366 NEXTWORD(p, end, 1); 367 goto domode; 368 } else if (strncmp(p, "include", 7) == 0) { 369 NEXTWORD(p, end, 1); 370 if (strstr(p, PROTO) == NULL) 371 fprintf(stderr, "including file %s", p); 372 } 373 return (LINE_IGNORE); 374 } 375 376 /* 377 * Parse the pkgmap line: 378 * [<number>] <type> <class> <path> [<major> <minor>] 379 * [ <mode> <owner> <group> .... ] 380 */ 381 382 /* Skip first column for non-prototype (i.e., pkgmap) files */ 383 if (isdigit(*p)) 384 NEXTWORD(p, end, 1); 385 386 parse->realtype = parse->type = *p; 387 388 switch (parse->type) { 389 case 'i': case 's': case 'l': 390 return (LINE_IGNORE); 391 } 392 393 NEXTWORD(p, end, 1); 394 395 /* skip class */ 396 NEXTWORD(p, end, 1); 397 398 /* 399 * p now points to pathname 400 * At this point, we could have no mode because we are 401 * using a default. 402 */ 403 tmp = p; 404 NEXTWORD(p, end, 0); 405 406 /* end points to space after name */ 407 (void) strncpy(parse->path, tmp, end - tmp); 408 parse->path[end - tmp] = '\0'; 409 410 switch (parse->type) { 411 case 'e': 412 case 'v': 413 /* type 'e' and 'v' are files, just like 'f', use 'f' in out */ 414 parse->type = 'f'; 415 /* FALLTHROUGH */ 416 case 'f': 417 case 'd': 418 case 'p': /* FIFO - assume mode is sensible, don't treat as file */ 419 break; 420 421 case 'x': /* Exclusive directory */ 422 parse->type = 'd'; 423 break; 424 425 /* device files have class major minor, skip */ 426 case 'c': 427 case 'b': 428 NEXTWORD(p, end, 1); NEXTWORD(p, end, 1); 429 break; 430 431 default: 432 (void) fprintf(stderr, "Unknown type '%c', %s:%d\n", 433 parse->type, name, lineno); 434 return (LINE_ERROR); 435 } 436 tmp = p; 437 NEXTWORD(p, end, 1); 438 439 domode: 440 /* 441 * the mode is either a 4 digit number (file is sticky/set-uid or 442 * set-gid or the mode has a leading 0) or a three digit number 443 * mode has all the mode bits, mode points to the three least 444 * significant bit so fthe mode 445 */ 446 parse->mode = 0; 447 for (q = tmp; q < end; q++) { 448 if (!isdigit(*q) || *q > '7') { 449 (void) fprintf(stderr, 450 "Warning: Unparseble mode \"%.*s\" at %s:%d\n", 451 end-tmp, tmp, name, lineno); 452 return (LINE_IGNORE); 453 } 454 parse->mode <<= 3; 455 parse->mode += *q - '0'; 456 } 457 parse->modelen = end - tmp; 458 tmp[0] = '\0'; 459 460 parse->old_owner = parse->owner = p; 461 462 NEXTWORD(p, end, 1); 463 464 parse->rest = end+1; 465 *end = '\0'; 466 467 (void) memset(parse->group, 0, sizeof (parse->group)); 468 (void) strncpy(parse->group, end+1, strcspn(end+1, " \t\n")); 469 470 return (LINE_OK); 471 } 472 473 static void 474 update_map(char *name, char *basedir, int basedir_len) 475 { 476 char buf[8192]; 477 int i; 478 FILE *map, *newmap; 479 char newname[MAXPATHLEN]; 480 int nchanges = 0; 481 unsigned int lineno = 0; 482 struct parsed_line line; 483 char *fname; 484 485 map = fopen(name, "r"); 486 if (map == 0) { 487 (void) fprintf(stderr, "Can't open \"%s\"\n", name); 488 return; 489 } 490 (void) strcpy(newname, name); 491 (void) strcat(newname, ".new"); 492 if (makenew) { 493 newmap = fopen(newname, "w"); 494 if (newmap == 0) 495 (void) fprintf(stderr, "Can't open %s for writing\n", 496 name); 497 } else 498 newmap = 0; 499 500 /* Get last one or two components non-trivial of pathname */ 501 if (verbose) { 502 char *tmp = name + strlen(name); 503 int cnt = 0, first = 0; 504 505 while (--tmp > name && cnt < 2) { 506 if (*tmp == '/') { 507 if (++cnt == 1) 508 first = tmp - name; 509 else { 510 fname = tmp + 1; 511 /* Triviality check */ 512 if (tmp - name > first - 4) 513 cnt--; 514 } 515 } 516 } 517 if (cnt < 2) 518 fname = name; 519 } 520 521 nchanges = 0; 522 523 for (; fgets(buf, sizeof (buf), map) != 0; put_line(newmap, &line)) { 524 525 int root_owner, mode_diff = 0; 526 int changed = 0; 527 528 lineno ++; 529 530 switch (parse_line(&line, buf, name, lineno)) { 531 case LINE_IGNORE: 532 continue; 533 case LINE_ERROR: 534 errors++; 535 continue; 536 } 537 538 if (restrictto) { 539 char nbuf[MAXPATHLEN]; 540 snprintf(nbuf, sizeof (nbuf), "%.*s%s", basedir_len, 541 basedir, line.path); 542 543 if (item_search(restrictto, nbuf) == -1) 544 continue; 545 } 546 547 if (dirsonly && line.type != 'd') 548 continue; 549 550 root_owner = strcmp(line.owner, "root") == 0; 551 if (dosu && line.type == 'f' && (line.mode & (S_ISUID|S_ISGID))) 552 mode_diff = line.mode & (S_IRGRP|S_IROTH); 553 554 /* 555 * The following heuristics are used to determine whether a file 556 * can be safely chown'ed to root: 557 * - it's not set-uid. 558 * and one of the following applies: 559 * - it's not writable by the current owner and is 560 * group/world readable 561 * - it's world executable and a file 562 * - owner, group and world permissions are identical 563 * - it's a bin owned directory or a "non-volatile" 564 * file (any owner) for which group and other r-x 565 * permissions are identical, or it's a bin owned 566 * executable or it's a /etc/security/dev/ device 567 */ 568 569 if (doowner && !(line.mode & S_ISUID) && 570 !root_owner && 571 ((!(line.mode & S_IWUSR) && 572 (line.mode&(S_IRGRP|S_IROTH)) == (S_IRGRP|S_IROTH)) || 573 (line.type == 'f' && (line.mode & S_IXOTH)) || 574 ((line.mode & 07) == ((line.mode>>3) & 07) && 575 (line.mode & 07) == ((line.mode>>6) & 07) && 576 strcmp(line.owner, "uucp") != 0) || 577 ((line.type == 'd' && strcmp(line.owner, "bin") == 0 || 578 (editable && strcmp(line.owner, "bin") == 0 ? 579 line.type : line.realtype) == 'f') && 580 ((line.mode & 05) == ((line.mode>>3) & 05) || 581 (line.mode & 0100) && 582 strcmp(line.owner, "bin") == 0) && 583 ((line.mode & 0105) != 0 || 584 basedir_len < 18 && 585 strncmp(basedir, "/etc/security/dev/", 586 basedir_len) == 0 && 587 strncmp(line.path, "/etc/security/dev/" 588 + basedir_len, 18 - basedir_len) == 0)))) { 589 if (!diffout) { 590 if (!changed && verbose && !nchanges) 591 (void) printf("%s:\n", fname); 592 (void) printf("%c o %s -> root %s%s [%.*o]\n", 593 line.realtype, line.owner, basedir, 594 line.path, line.modelen, line.mode); 595 } 596 line.owner = "root"; 597 root_owner = 1; 598 changed = 1; 599 } 600 /* 601 * Strip user write bit if owner != root and executable by user. 602 * root can write even if no write bits set 603 * Could prevent executables from being overwritten. 604 */ 605 if (douserwrite && line.type == 'f' && !root_owner && 606 (line.mode & (S_IWUSR|S_IXUSR)) == (S_IWUSR|S_IXUSR)) 607 mode_diff |= S_IWUSR; 608 609 610 if (domodes && (line.mode & (S_IWGRP|S_IWOTH)) != 0 && 611 (line.mode & S_ISVTX) == 0) { 612 if (basedir_len <= 1) { /* root dir */ 613 for (i = 0; i < nexceptions; i++) { 614 if (strcmp(line.path, 615 exceptions[i]+basedir_len) == 0) 616 break; 617 } 618 } else { 619 for (i = 0; i < nexceptions; i++) { 620 if (strncmp(basedir, exceptions[i], 621 basedir_len) == 0 && 622 strcmp(line.path, 623 exceptions[i]+basedir_len) == 0) 624 break; 625 } 626 } 627 if (i == nexceptions) 628 mode_diff |= line.mode & (S_IWGRP|S_IWOTH); 629 } 630 631 if (mode_diff) { 632 int oldmode = line.mode; 633 634 line.mode &= ~mode_diff; 635 636 if (line.mode != oldmode) { 637 if (!diffout) { 638 if (!changed && verbose && !nchanges) 639 (void) printf("%s:\n", fname); 640 printf("%c %c %04o -> %04o %s%s\n", 641 line.realtype, 642 (mode_diff & (S_IRGRP|S_IROTH)) ? 643 's' : 'm', 644 oldmode, line.mode, basedir, 645 line.path); 646 } 647 changed = 1; 648 } 649 } 650 nchanges += changed; 651 if (diffout && changed) { 652 if (nchanges == 1 && verbose) 653 (void) printf("%s:\n", fname); 654 655 (void) printf("< %c %04o %s %s %s%s\n", line.realtype, 656 line.mode | mode_diff, line.old_owner, line.group, 657 basedir, line.path); 658 (void) printf("> %c %04o %s %s %s%s\n", line.realtype, 659 line.mode, line.owner, line.group, basedir, 660 line.path); 661 } 662 } 663 (void) fclose(map); 664 665 if (newmap != NULL) { 666 (void) fflush(newmap); 667 if (ferror(newmap)) { 668 (void) fprintf(stderr, "Error writing %s\n", name); 669 return; 670 } 671 (void) fclose(newmap); 672 if (nchanges == 0) 673 (void) unlink(newname); 674 else if (installnew) { 675 char oldname[MAXPATHLEN]; 676 677 (void) strcpy(oldname, name); 678 (void) strcat(oldname, ".old"); 679 if (rename(name, oldname) == -1 || 680 rename(newname, name) == -1) 681 (void) fprintf(stderr, 682 "Couldn't install %s: %s\n", 683 newname, strerror(errno)); 684 } 685 } 686 } 687