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 }