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.
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.
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]
23 * Copyright 2010 Sun Microsystems, Inc. All rights reserved.
24 * Use is subject to license terms.
26 * $Id: pmodes.c,v 1.23 1999/03/22 14:51:16 casper Exp $
29 * Program to list files from packages with modes that are to
32 * pmodes [options] pkgdir ...
34 * Pmodes currently has 4 types of modes that are changed:
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
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
44 * Any combination of changes can be switched of by specifying -X
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.
52 * d m oldmode -> newmode pathname
53 * | ^ whether the file/dir is group writable or even world writable
55 * d o owner -> newowner pathname [mode]
58 * Casper Dik (Casper.Dik@Holland.Sun.COM)
68 #include <sys/param.h>
70 #include "binsearch.h"
72 static char *exceptions
[] = {
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 */
84 #define NEXEMPT (sizeof (exempt_pkgs) / sizeof (char *))
86 #define PROTO "prototype_"
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
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;
110 static void update_map(char *, char *, int);
112 static char *program
;
114 itemlist restrictto
= NULL
;
118 (void) fprintf(stderr
,
119 "Usage: %s [-DowsnNmdePvq] [-r file] pkgdir ...\n", program
);
124 main(int argc
, char **argv
)
128 extern int optind
, opterr
;
134 while ((c
= getopt(argc
, argv
, "eDowsnNmdPvqr:")) != EOF
) {
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;
149 if (restrictto
== NULL
)
150 restrictto
= new_itemlist();
151 if (item_addfile(restrictto
, optarg
) != 0) {
157 case '?': usage(); break;
166 for (; *argv
; argv
++) {
168 char name
[MAXPATHLEN
];
169 char basedir
[MAXPATHLEN
] = "/";
173 boolean_t exempt
= B_FALSE
;
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
180 if (lstat(*argv
, &stb
) == 0 && S_ISREG(stb
.st_mode
)) {
181 char *lastslash
= strrchr(*argv
, '/');
183 if (lastslash
!= NULL
)
185 (void) sprintf(name
, "%s/pkginfo", *argv
);
186 if (lastslash
!= NULL
)
190 (void) sprintf(name
, "%s/pkginfo", *argv
);
192 /* if there's no pkginfo file, it could be a prototype area */
194 if (access(name
, R_OK
) != 0)
195 (void) strcat(name
, ".tmpl");
197 info
= fopen(name
, "r");
200 (void) fprintf(stderr
,
201 "Can't open pkginfo file %s\n", name
);
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) {
213 str
= buf
+ sizeof ("PKG=") - 1;
214 str
[strlen(str
)-1] = '\0';
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) {
234 basedir_len
= strlen(basedir
);
235 if (basedir_len
!= 1)
236 basedir
[basedir_len
++] = '/';
238 (void) sprintf(name
, "%s/pkgmap", *argv
);
240 update_map(*argv
, basedir
, basedir_len
);
241 else if (!proto
&& access(name
, R_OK
) == 0)
242 update_map(name
, basedir
, basedir_len
);
244 DIR *d
= opendir(*argv
);
248 (void) fprintf(stderr
,
249 "Can't read directory \"%s\"\n", *argv
);
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
,
260 update_map(name
, basedir
, basedir_len
);
266 return (errors
!= 0);
269 #define NEXTWORD(tmp, end, warnme) \
271 tmp = strpbrk(tmp, "\t ");\
275 return (LINE_IGNORE);\
278 while (*tmp && isspace(*tmp)) tmp++;\
282 warn(const char *file
, int line
)
284 (void) fprintf(stderr
, "pmodes: %s, line %d: unexpected format\n",
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 */
302 #define LINE_IGNORE 1
306 put_line(FILE *f
, struct parsed_line
*line
)
310 (void) fprintf(f
, "%s%.*o %s %s", line
->start
,
311 line
->modelen
, line
->mode
, line
->owner
, line
->rest
);
313 (void) fputs(line
->start
, f
);
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
324 * c (character devices)
329 parse_line(struct parsed_line
*parse
, char *buf
, const char *name
, int lineno
)
336 parse
->rest
= 0; /* makes put_line work */
338 /* Trim trailing spaces */
339 end
= buf
+ strlen(buf
);
340 while (end
> buf
+1 && isspace(end
[-2])) {
346 while (*p
&& isspace(*p
))
349 if (*p
== '#' || *p
== ':' || *p
== '\0')
350 return (LINE_IGNORE
);
353 * Special directives; we really should follow the include
354 * directives but we certainly need to look at default
358 while (*p
&& isspace(*p
))
361 if (!*p
|| *p
== '\n')
362 return (LINE_IGNORE
);
364 if (strncmp(p
, "default", 7) == 0) {
367 parse
->realtype
= 'D';
368 strcpy(parse
->path
, "(default)");
372 } else if (strncmp(p
, "include", 7) == 0) {
374 if (strstr(p
, PROTO
) == NULL
)
375 fprintf(stderr
, "including file %s", p
);
377 return (LINE_IGNORE
);
381 * Parse the pkgmap line:
382 * [<number>] <type> <class> <path> [<major> <minor>]
383 * [ <mode> <owner> <group> .... ]
386 /* Skip first column for non-prototype (i.e., pkgmap) files */
390 parse
->realtype
= parse
->type
= *p
;
392 switch (parse
->type
) {
393 case 'i': case 's': case 'l':
394 return (LINE_IGNORE
);
403 * p now points to pathname
404 * At this point, we could have no mode because we are
410 /* end points to space after name */
411 (void) strncpy(parse
->path
, tmp
, end
- tmp
);
412 parse
->path
[end
- tmp
] = '\0';
414 switch (parse
->type
) {
417 /* type 'e' and 'v' are files, just like 'f', use 'f' in out */
422 case 'p': /* FIFO - assume mode is sensible, don't treat as file */
425 case 'x': /* Exclusive directory */
429 /* device files have class major minor, skip */
432 NEXTWORD(p
, end
, 1); NEXTWORD(p
, end
, 1);
436 (void) fprintf(stderr
, "Unknown type '%c', %s:%d\n",
437 parse
->type
, name
, lineno
);
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
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
);
459 parse
->mode
+= *q
- '0';
461 parse
->modelen
= end
- tmp
;
464 parse
->old_owner
= parse
->owner
= p
;
471 (void) memset(parse
->group
, 0, sizeof (parse
->group
));
472 (void) strncpy(parse
->group
, end
+1, strcspn(end
+1, " \t\n"));
478 update_map(char *name
, char *basedir
, int basedir_len
)
483 char newname
[MAXPATHLEN
];
485 unsigned int lineno
= 0;
486 struct parsed_line line
;
489 map
= fopen(name
, "r");
491 (void) fprintf(stderr
, "Can't open \"%s\"\n", name
);
494 (void) strcpy(newname
, name
);
495 (void) strcat(newname
, ".new");
497 newmap
= fopen(newname
, "w");
499 (void) fprintf(stderr
, "Can't open %s for writing\n",
504 /* Get last one or two components non-trivial of pathname */
506 char *tmp
= name
+ strlen(name
);
507 int cnt
= 0, first
= 0;
509 while (--tmp
> name
&& cnt
< 2) {
515 /* Triviality check */
516 if (tmp
- name
> first
- 4)
527 for (; fgets(buf
, sizeof (buf
), map
) != 0; put_line(newmap
, &line
)) {
529 int root_owner
, mode_diff
= 0;
534 switch (parse_line(&line
, buf
, name
, lineno
)) {
543 char nbuf
[MAXPATHLEN
];
544 snprintf(nbuf
, sizeof (nbuf
), "%.*s%s", basedir_len
,
547 if (item_search(restrictto
, nbuf
) == -1)
551 if (dirsonly
&& line
.type
!= 'd')
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
);
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
573 if (doowner
&& !(line
.mode
& S_ISUID
) &&
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 ||
589 strncmp(basedir
, "/etc/security/dev/",
591 strncmp(line
.path
, "/etc/security/dev/"
592 + basedir_len
, 18 - basedir_len
) == 0)))) {
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
);
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.
609 if (douserwrite
&& line
.type
== 'f' && !root_owner
&&
610 (line
.mode
& (S_IWUSR
|S_IXUSR
)) == (S_IWUSR
|S_IXUSR
))
611 mode_diff
|= S_IWUSR
;
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)
623 for (i
= 0; i
< nexceptions
; i
++) {
624 if (strncmp(basedir
, exceptions
[i
],
627 exceptions
[i
]+basedir_len
) == 0)
631 if (i
== nexceptions
)
632 mode_diff
|= line
.mode
& (S_IWGRP
|S_IWOTH
);
636 int oldmode
= line
.mode
;
638 line
.mode
&= ~mode_diff
;
640 if (line
.mode
!= oldmode
) {
642 if (!changed
&& verbose
&& !nchanges
)
643 (void) printf("%s:\n", fname
);
644 printf("%c %c %04o -> %04o %s%s\n",
646 (mode_diff
& (S_IRGRP
|S_IROTH
)) ?
648 oldmode
, line
.mode
, basedir
,
655 if (diffout
&& changed
) {
656 if (nchanges
== 1 && verbose
)
657 (void) printf("%s:\n", fname
);
659 (void) printf("< %c %04o %s %s %s%s\n", line
.realtype
,
660 line
.mode
| mode_diff
, line
.old_owner
, line
.group
,
662 (void) printf("> %c %04o %s %s %s%s\n", line
.realtype
,
663 line
.mode
, line
.owner
, line
.group
, basedir
,
669 if (newmap
!= NULL
) {
670 (void) fflush(newmap
);
671 if (ferror(newmap
)) {
672 (void) fprintf(stderr
, "Error writing %s\n", name
);
675 (void) fclose(newmap
);
677 (void) unlink(newname
);
678 else if (installnew
) {
679 char oldname
[MAXPATHLEN
];
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
));