1/*
2 * Copyright (c) 1989, 1993, 1994
3 *	The Regents of the University of California.  All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 * 3. All advertising materials mentioning features or use of this software
14 *    must display the following acknowledgement:
15 *	This product includes software developed by the University of
16 *	California, Berkeley and its contributors.
17 * 4. Neither the name of the University nor the names of its contributors
18 *    may be used to endorse or promote products derived from this software
19 *    without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 * SUCH DAMAGE.
32 */
33
34#include <sys/cdefs.h>
35#ifndef lint
36__used static char const copyright[] =
37"@(#) Copyright (c) 1989, 1993, 1994\n\
38	The Regents of the University of California.  All rights reserved.\n";
39#endif /* not lint */
40
41#ifndef lint
42#if 0
43static char sccsid[] = "@(#)chmod.c	8.8 (Berkeley) 4/1/94";
44#endif
45#endif /* not lint */
46#include <sys/cdefs.h>
47__RCSID("$FreeBSD: src/bin/chmod/chmod.c,v 1.27 2002/08/04 05:29:13 obrien Exp $");
48
49#include <sys/types.h>
50#include <sys/stat.h>
51
52#include <err.h>
53#include <errno.h>
54#include <fts.h>
55#include <limits.h>
56#include <stdio.h>
57#include <stdlib.h>
58#include <string.h>
59#include <unistd.h>
60
61#ifdef __APPLE__
62#include "chmod_acl.h"
63
64#endif /*__APPLE__*/
65
66int fflag = 0;
67
68int main(int, char *[]);
69void usage(void);
70
71int
72main(int argc, char *argv[])
73{
74	FTS *ftsp = NULL;
75	FTSENT *p = NULL;
76	mode_t *set = NULL;
77	long val = 0;
78	int oct = 0;
79	int Hflag, Lflag, Pflag, Rflag, ch, fts_options, hflag, rval;
80	int vflag;
81	char *ep, *mode;
82	mode_t newmode, omode;
83#ifdef __APPLE__
84	unsigned int acloptflags = 0;
85	long aclpos = -1;
86	int inheritance_level = 0;
87	int index = 0;
88	size_t acloptlen = 0;
89	int ace_arg_not_required = 0;
90	acl_t acl_input = NULL;
91#endif /* __APPLE__*/
92	int (*change_mode)(const char *, mode_t);
93
94	set = NULL;
95	omode = 0;
96	Hflag = Lflag = Pflag = Rflag = fflag = hflag = vflag = 0;
97#ifndef __APPLE__
98	while ((ch = getopt(argc, argv, "HLPRXfghorstuvwx")) != -1)
99#else
100	while ((ch = getopt(argc, argv, "ACEHILNPRVXafghinorstuvwx")) != -1)
101#endif
102		switch (ch) {
103		case 'H':
104			Hflag = 1;
105			Lflag = 0;
106			Pflag = 0;
107			break;
108		case 'L':
109			Lflag = 1;
110			Hflag = 0;
111			Pflag = 0;
112			break;
113		case 'P':
114			Hflag = Lflag = 0;
115			Pflag = 1;
116			break;
117		case 'R':
118			Rflag = 1;
119			break;
120		case 'f':
121			fflag = 1;
122			break;
123		case 'h':
124			/*
125			 * In System V (and probably POSIX.2) the -h option
126			 * causes chmod to change the mode of the symbolic
127			 * link.  4.4BSD's symbolic links didn't have modes,
128			 * so it was an undocumented noop.  In FreeBSD 3.0,
129			 * lchmod(2) is introduced and this option does real
130			 * work.
131			 */
132			hflag = 1;
133			break;
134#ifdef __APPLE__
135		case 'a':
136			if (argv[optind - 1][0] == '-' &&
137			    argv[optind - 1][1] == ch)
138				--optind;
139			goto done;
140		case 'A':
141//			acloptflags |= ACL_FLAG | ACL_TO_STDOUT;
142//			ace_arg_not_required = 1;
143			errx(1, "-A not implemented");
144			goto done;
145		case 'E':
146			acloptflags |= ACL_FLAG | ACL_FROM_STDIN;
147			goto done;
148		case 'C':
149			acloptflags |= ACL_FLAG | ACL_CHECK_CANONICITY;
150			ace_arg_not_required = 1;
151			goto done;
152		case 'i':
153			acloptflags |= ACL_FLAG | ACL_REMOVE_INHERIT_FLAG;
154			ace_arg_not_required = 1;
155			goto done;
156		case 'I':
157			acloptflags |= ACL_FLAG | ACL_REMOVE_INHERITED_ENTRIES;
158			ace_arg_not_required = 1;
159			goto done;
160		case 'n':
161			acloptflags |= ACL_FLAG | ACL_NO_TRANSLATE;
162			break;
163		case 'N':
164			acloptflags |= ACL_FLAG | ACL_CLEAR_FLAG;
165			ace_arg_not_required = 1;
166			goto done;
167		case 'V':
168//			acloptflags |= ACL_FLAG | ACL_INVOKE_EDITOR;
169//			ace_arg_not_required = 1;
170			errx(1, "-V not implemented");
171			goto done;
172#endif /* __APPLE__ */
173		/*
174		 * XXX
175		 * "-[rwx]" are valid mode commands.  If they are the entire
176		 * argument, getopt has moved past them, so decrement optind.
177		 * Regardless, we're done argument processing.
178		 */
179		case 'g': case 'o': case 'r': case 's':
180		case 't': case 'u': case 'w': case 'X': case 'x':
181			if (argv[optind - 1][0] == '-' &&
182			    argv[optind - 1][1] == ch &&
183			    argv[optind - 1][2] == '\0')
184				--optind;
185			goto done;
186		case 'v':
187			vflag++;
188			break;
189		case '?':
190		default:
191			usage();
192		}
193done:	argv += optind;
194	argc -= optind;
195
196#ifdef __APPLE__
197	if (argc < ((acloptflags & ACL_FLAG) ? 1 : 2))
198		usage();
199	if (!Rflag && (Hflag || Lflag || Pflag))
200		warnx("options -H, -L, -P only useful with -R");
201#else  /* !__APPLE__ */
202	if (argc < 2)
203		usage();
204#endif	/* __APPLE__ */
205
206#ifdef __APPLE__
207	if (!(acloptflags & ACL_FLAG) && ((acloptlen = strlen(argv[0])) > 1) && (argv[0][1] == 'a')) {
208		acloptflags |= ACL_FLAG;
209		switch (argv[0][0]) {
210		case '+':
211			acloptflags |= ACL_SET_FLAG;
212			break;
213		case '-':
214			acloptflags |= ACL_DELETE_FLAG;
215			break;
216		case '=':
217			acloptflags |= ACL_REWRITE_FLAG;
218			break;
219		default:
220			acloptflags &= ~ACL_FLAG;
221			goto apnoacl;
222		}
223
224		if (argc < 3)
225			usage();
226
227		if (acloptlen > 2) {
228			for (index = 2; index < acloptlen; index++) {
229				switch (argv[0][index]) {
230				case '#':
231					acloptflags |= ACL_ORDER_FLAG;
232
233					if (argc < ((acloptflags & ACL_DELETE_FLAG)
234						    ? 3 : 4))
235						usage();
236					argv++;
237					argc--;
238					errno = 0;
239					aclpos = strtol(argv[0], &ep, 0);
240
241					if (aclpos > ACL_MAX_ENTRIES
242					    || aclpos < 0)
243						errno = ERANGE;
244					if (errno || *ep)
245						errx(1, "Invalid ACL entry number: %ld", aclpos);
246					if (acloptflags & ACL_DELETE_FLAG)
247						ace_arg_not_required = 1;
248
249					goto apdone;
250				case 'i':
251					acloptflags |= ACL_INHERIT_FLAG;
252					/* The +aii.. syntax to specify
253					 * inheritance level is rather unwieldy,
254					 * find an alternative.
255					 */
256					inheritance_level++;
257					if (inheritance_level > 1)
258						warnx("Inheritance across more than one generation is not currently supported");
259					if (inheritance_level >= MAX_INHERITANCE_LEVEL)
260						goto apdone;
261					break;
262				default:
263					errno = EINVAL;
264					usage();
265				}
266			}
267		}
268apdone:
269		argv++;
270		argc--;
271	}
272apnoacl:
273#endif /*__APPLE__*/
274
275	if (Rflag) {
276		fts_options = FTS_PHYSICAL;
277		if (hflag)
278			errx(1,
279		"the -R and -h options may not be specified together.");
280		if (Hflag)
281			fts_options |= FTS_COMFOLLOW;
282		if (Lflag) {
283			fts_options &= ~FTS_PHYSICAL;
284			fts_options |= FTS_LOGICAL;
285		}
286	} else
287		fts_options = hflag ? FTS_PHYSICAL : FTS_LOGICAL;
288
289	if (hflag)
290		change_mode = lchmod;
291	else
292		change_mode = chmod;
293#ifdef __APPLE__
294	if (acloptflags & ACL_FROM_STDIN) {
295		ssize_t readval = 0;
296		size_t readtotal = 0;
297
298		mode = (char *) malloc(MAX_ACL_TEXT_SIZE);
299
300		if (mode == NULL)
301			err(1, "Unable to allocate mode string");
302		/* Read the ACEs from STDIN */
303		do {
304			readtotal += readval;
305			readval = read(STDIN_FILENO, mode + readtotal,
306				       MAX_ACL_TEXT_SIZE);
307		} while ((readval > 0) && (readtotal <= MAX_ACL_TEXT_SIZE));
308
309		if (0 == readtotal)
310			errx(1, "-E specified, but read from STDIN failed");
311		else
312			mode[readtotal - 1] = '\0';
313		--argv;
314	}
315	else
316#endif /* __APPLE */
317		mode = *argv;
318
319#ifdef __APPLE__
320	if ((acloptflags & ACL_FLAG)) {
321
322		/* Are we deleting by entry number, verifying
323		 * canonicity or performing some other operation that
324		 * does not require an input entry? If so, there's no
325		 * entry to convert.
326		 */
327		if (ace_arg_not_required) {
328			--argv;
329		}
330		else {
331                        /* Parse the text into an ACL*/
332			acl_input = parse_acl_entries(mode);
333			if (acl_input == NULL) {
334				errx(1, "Invalid ACL specification: %s", mode);
335			}
336		}
337	}
338	else {
339#endif /* __APPLE__*/
340		if (*mode >= '0' && *mode <= '7') {
341			errno = 0;
342			val = strtol(mode, &ep, 8);
343			if (val > USHRT_MAX || val < 0)
344				errno = ERANGE;
345			if (errno)
346				err(1, "Invalid file mode: %s", mode);
347			if (*ep)
348				errx(1, "Invalid file mode: %s", mode);
349			omode = (mode_t)val;
350			oct = 1;
351		} else {
352			if ((set = setmode(mode)) == NULL)
353				errx(1, "Invalid file mode: %s", mode);
354			oct = 0;
355		}
356#ifdef __APPLE__
357	}
358#endif /* __APPLE__*/
359	if ((ftsp = fts_open(++argv, fts_options, 0)) == NULL)
360		err(1, "fts_open");
361	for (rval = 0; (p = fts_read(ftsp)) != NULL;) {
362		switch (p->fts_info) {
363		case FTS_D:
364			if (!Rflag)
365				(void)fts_set(ftsp, p, FTS_SKIP);
366			break;
367		case FTS_DNR:			/* Warn, chmod, continue. */
368			warnx("%s: %s", p->fts_path, strerror(p->fts_errno));
369			rval = 1;
370			break;
371		case FTS_DP:			/* Already changed at FTS_D. */
372			continue;
373		case FTS_NS:
374			if (acloptflags & ACL_FLAG) /* don't need stat for -N */
375				break;
376		case FTS_ERR:			/* Warn, continue. */
377			warnx("%s: %s", p->fts_path, strerror(p->fts_errno));
378			rval = 1;
379			continue;
380		case FTS_SL:			/* Ignore. */
381		case FTS_SLNONE:
382			/*
383			 * The only symlinks that end up here are ones that
384			 * don't point to anything and ones that we found
385			 * doing a physical walk.
386			 */
387			if (!hflag)
388				continue;
389			/* else */
390			/* FALLTHROUGH */
391		default:
392			break;
393		}
394#ifdef __APPLE__
395/* If an ACL manipulation option was specified, manipulate */
396		if (acloptflags & ACL_FLAG)	{
397			if (0 != modify_file_acl(acloptflags, p->fts_accpath, acl_input, (int)aclpos, inheritance_level, !hflag))
398				rval = 1;
399		}
400		else {
401#endif /* __APPLE__ */
402			newmode = oct ? omode : getmode(set, p->fts_statp->st_mode);
403			if ((newmode & ALLPERMS) == (p->fts_statp->st_mode & ALLPERMS))
404				continue;
405			if ((*change_mode)(p->fts_accpath, newmode) && !fflag) {
406				warn("Unable to change file mode on %s", p->fts_path);
407				rval = 1;
408			} else {
409				if (vflag) {
410					(void)printf("%s", p->fts_accpath);
411
412					if (vflag > 1) {
413						char m1[12], m2[12];
414
415						strmode(p->fts_statp->st_mode, m1);
416						strmode((p->fts_statp->st_mode &
417							 S_IFMT) | newmode, m2);
418
419						(void)printf(": 0%o [%s] -> 0%o [%s]",
420							     p->fts_statp->st_mode, m1,
421					    (p->fts_statp->st_mode & S_IFMT) |
422							     newmode, m2);
423					}
424					(void)printf("\n");
425				}
426
427			}
428#ifdef __APPLE__
429		}
430#endif /* __APPLE__*/
431	}
432	if (errno)
433		err(1, "fts_read");
434#ifdef __APPLE__
435	if (acl_input)
436		acl_free(acl_input);
437	if (mode && (acloptflags & ACL_FROM_STDIN))
438		free(mode);
439
440#endif /* __APPLE__ */
441	if (set)
442		free(set);
443	exit(rval);
444}
445
446void
447usage(void)
448{
449#ifdef __APPLE__
450	(void)fprintf(stderr,
451		      "usage:\tchmod [-fhv] [-R [-H | -L | -P]] [-a | +a | =a  [i][# [ n]]] mode|entry file ...\n"
452		      "\tchmod [-fhv] [-R [-H | -L | -P]] [-E | -C | -N | -i | -I] file ...\n"); /* add -A and -V when implemented */
453#else
454	(void)fprintf(stderr,
455	    "usage: chmod [-fhv] [-R [-H | -L | -P]] mode file ...\n");
456#endif /* __APPLE__ */
457	exit(1);
458}
459