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
usage(void)117 usage(void) {
118 (void) fprintf(stderr,
119 "Usage: %s [-DowsnNmdePvq] [-r file] pkgdir ...\n", program);
120 exit(1);
121 }
122
123 int
main(int argc,char ** argv)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
warn(const char * file,int line)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
put_line(FILE * f,struct parsed_line * line)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
parse_line(struct parsed_line * parse,char * buf,const char * name,int lineno)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
update_map(char * name,char * basedir,int basedir_len)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