1/*	$OpenBSD: chmod.c,v 1.43 2018/09/16 02:44:06 millert Exp $	*/
2/*	$NetBSD: chmod.c,v 1.12 1995/03/21 09:02:09 cgd Exp $	*/
3
4/*
5 * Copyright (c) 1989, 1993, 1994
6 *	The Regents of the University of California.  All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 *    notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 *    notice, this list of conditions and the following disclaimer in the
15 *    documentation and/or other materials provided with the distribution.
16 * 3. Neither the name of the University nor the names of its contributors
17 *    may be used to endorse or promote products derived from this software
18 *    without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
21 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
24 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30 * SUCH DAMAGE.
31 */
32
33#include <sys/types.h>
34#include <sys/stat.h>
35
36#include <err.h>
37#include <errno.h>
38#include <fcntl.h>
39#include <fts.h>
40#include <grp.h>
41#include <limits.h>
42#include <pwd.h>
43#include <stdio.h>
44#include <stdlib.h>
45#include <string.h>
46#include <unistd.h>
47
48int ischflags, ischown, ischgrp, ischmod;
49extern char *__progname;
50
51gid_t a_gid(const char *);
52uid_t a_uid(const char *, int);
53static void __dead usage(void);
54
55int
56main(int argc, char *argv[])
57{
58	FTS *ftsp;
59	FTSENT *p;
60	void *set;
61	unsigned long val;
62	int oct;
63	mode_t omode;
64	int Hflag, Lflag, Rflag, ch, fflag, fts_options, hflag, rval, atflags;
65	uid_t uid;
66	gid_t gid;
67	u_int32_t fclear, fset;
68	char *ep, *mode, *cp, *flags;
69
70	if (strlen(__progname) > 2) {
71		ischown = __progname[2] == 'o';
72		ischgrp = __progname[2] == 'g';
73		ischmod = __progname[2] == 'm';
74		ischflags = __progname[2] == 'f';
75	}
76
77	uid = (uid_t)-1;
78	gid = (gid_t)-1;
79	Hflag = Lflag = Rflag = fflag = hflag = 0;
80	while ((ch = getopt(argc, argv, "HLPRXfghorstuwx")) != -1)
81		switch (ch) {
82		case 'H':
83			Hflag = 1;
84			Lflag = 0;
85			break;
86		case 'L':
87			Lflag = 1;
88			Hflag = 0;
89			break;
90		case 'P':
91			Hflag = Lflag = 0;
92			break;
93		case 'R':
94			Rflag = 1;
95			break;
96		case 'f':		/* no longer documented. */
97			fflag = 1;
98			break;
99		case 'h':
100			hflag = 1;
101			break;
102		/*
103		 * If this is a symbolic mode argument rather than
104		 * an option, we are done with option processing.
105		 */
106		case 'g': case 'o': case 'r': case 's':
107		case 't': case 'u': case 'w': case 'X': case 'x':
108			if (!ischmod)
109				usage();
110			/*
111			 * If getopt() moved past the argument, back up.
112			 * If the argument contains option letters before
113			 * mode letters, setmode() will catch them.
114			 */
115			if (optind > 1) {
116				cp = argv[optind - 1];
117				if (cp[strlen(cp) - 1] == ch)
118					--optind;
119			}
120			goto done;
121		default:
122			usage();
123		}
124done:
125	argv += optind;
126	argc -= optind;
127
128	if (argc < 2)
129		usage();
130
131	/*
132	 * We alter the symlink itself if doing -h or -RP, or
133	 * if doing -RH and the symlink wasn't a command line arg.
134	 */
135	atflags = AT_SYMLINK_NOFOLLOW;
136
137	fts_options = FTS_PHYSICAL;
138	if (Rflag) {
139		if (hflag)
140			errx(1,
141		"the -R and -h options may not be specified together.");
142		if (Hflag)
143			fts_options |= FTS_COMFOLLOW;
144		if (Lflag) {
145			fts_options &= ~FTS_PHYSICAL;
146			fts_options |= FTS_LOGICAL;
147			atflags = 0;
148		}
149	} else if (!hflag) {
150		fts_options |= FTS_COMFOLLOW;
151		atflags = 0;
152	}
153
154	if (ischflags) {
155		if (pledge("stdio rpath fattr", NULL) == -1)
156			err(1, "pledge");
157
158		flags = *argv;
159		if (*flags >= '0' && *flags <= '7') {
160			errno = 0;
161			val = strtoul(flags, &ep, 8);
162			if (val > UINT_MAX)
163				errno = ERANGE;
164			if (errno)
165				err(1, "invalid flags: %s", flags);
166			if (*ep)
167				errx(1, "invalid flags: %s", flags);
168			fset = val;
169			oct = 1;
170		} else {
171			if (strtofflags(&flags, &fset, &fclear))
172				errx(1, "invalid flag: %s", flags);
173			fclear = ~fclear;
174			oct = 0;
175		}
176	} else if (ischmod) {
177		mode = *argv;
178		if (*mode >= '0' && *mode <= '7') {
179			errno = 0;
180			val = strtoul(mode, &ep, 8);
181			if (val > INT_MAX)
182				errno = ERANGE;
183			if (errno)
184				err(1, "invalid file mode: %s", mode);
185			if (*ep)
186				errx(1, "invalid file mode: %s", mode);
187			omode = val;
188			oct = 1;
189		} else {
190			if ((set = setmode(mode)) == NULL)
191				errx(1, "invalid file mode: %s", mode);
192			oct = 0;
193		}
194	} else if (ischown) {
195		/* Both UID and GID are given. */
196		if ((cp = strchr(*argv, ':')) != NULL) {
197			*cp++ = '\0';
198			gid = a_gid(cp);
199		}
200		/*
201		 * UID and GID are separated by a dot and UID exists.
202		 * required for backwards compatibility pre-dating POSIX.2
203		 * likely to stay here forever
204		 */
205		else if ((cp = strchr(*argv, '.')) != NULL &&
206		    (uid = a_uid(*argv, 1)) == (uid_t)-1) {
207			*cp++ = '\0';
208			gid = a_gid(cp);
209		}
210		if (uid == (uid_t)-1)
211			uid = a_uid(*argv, 0);
212	} else
213		gid = a_gid(*argv);
214
215	if ((ftsp = fts_open(++argv, fts_options, 0)) == NULL)
216		err(1, NULL);
217	for (rval = 0; (p = fts_read(ftsp)) != NULL;) {
218		switch (p->fts_info) {
219		case FTS_D:
220			if (!Rflag)
221				fts_set(ftsp, p, FTS_SKIP);
222			if (ischmod)
223				break;
224			else
225				continue;
226		case FTS_DNR:			/* Warn, chmod, continue. */
227			warnc(p->fts_errno, "%s", p->fts_path);
228			rval = 1;
229			break;
230		case FTS_DP:			/* Already changed at FTS_D. */
231			if (ischmod)
232				continue;
233			else
234				break;
235		case FTS_ERR:			/* Warn, continue. */
236		case FTS_NS:
237			warnc(p->fts_errno, "%s", p->fts_path);
238			rval = 1;
239			continue;
240		case FTS_SL:			/* Ignore. */
241		case FTS_SLNONE:
242			/*
243			 * The only symlinks that end up here are ones that
244			 * don't point to anything or that loop and ones
245			 * that we found doing a physical walk.
246			 */
247			if (!hflag && (fts_options & FTS_LOGICAL))
248				continue;
249			break;
250		default:
251			break;
252		}
253
254		/*
255		 * For -RH, the decision of how to handle symlinks depends
256		 * on the level: follow it iff it's a command line arg.
257		 */
258		if (fts_options & FTS_COMFOLLOW) {
259			atflags = p->fts_level == FTS_ROOTLEVEL ? 0 :
260			    AT_SYMLINK_NOFOLLOW;
261		}
262
263		if (ischmod) {
264			if (!fchmodat(AT_FDCWD, p->fts_accpath, oct ? omode :
265			    getmode(set, p->fts_statp->st_mode), atflags)
266			    || fflag)
267				continue;
268		} else if (!ischflags) {
269			if (!fchownat(AT_FDCWD, p->fts_accpath, uid, gid,
270			    atflags) || fflag)
271				continue;
272		} else {
273			if (!chflagsat(AT_FDCWD, p->fts_accpath, oct ? fset :
274			    (p->fts_statp->st_flags | fset) & fclear, atflags))
275				continue;
276		}
277
278		/* error case */
279		warn("%s", p->fts_path);
280		rval = 1;
281	}
282	if (errno)
283		err(1, "fts_read");
284	fts_close(ftsp);
285	return (rval);
286}
287
288/*
289 * Given a UID or user name in a string, return the UID.  If an empty string
290 * was given, returns -1.  If silent is 0, exits on invalid user names/UIDs;
291 * otherwise, returns -1.
292 */
293uid_t
294a_uid(const char *s, int silent)
295{
296	const char *errstr;
297	uid_t uid;
298
299	if (*s == '\0')			/* Argument was "[:.]gid". */
300		return ((uid_t)-1);
301
302	/* User name was given. */
303	if (uid_from_user(s, &uid) != -1)
304		return (uid);
305
306	/* UID was given. */
307	uid = (uid_t)strtonum(s, 0, UID_MAX, &errstr);
308	if (errstr) {
309		if (silent)
310			return ((uid_t)-1);
311		else
312			errx(1, "user is %s: %s", errstr, s);
313	}
314
315	return (uid);
316}
317
318/*
319 * Given a GID or group name in a string, return the GID.  If an empty string
320 * was given, returns -1.  Exits on invalid user names/UIDs.
321 */
322gid_t
323a_gid(const char *s)
324{
325	const char *errstr;
326	gid_t gid;
327
328	if (*s == '\0')			/* Argument was "uid[:.]". */
329		return ((gid_t)-1);
330
331	/* Group name was given. */
332	if (gid_from_group(s, &gid) != -1)
333		return (gid);
334
335	/* GID was given. */
336	gid = (gid_t)strtonum(s, 0, GID_MAX, &errstr);
337	if (errstr)
338		errx(1, "group is %s: %s", errstr, s);
339
340	return (gid);
341}
342
343static void __dead
344usage(void)
345{
346	fprintf(stderr,
347	    "usage: %s [-h] [-R [-H | -L | -P]] %s file ...\n",
348	    __progname, ischmod ? "mode" : ischflags ? "flags" :
349	    ischown ? "owner[:group]" : "group");
350	if (ischown)
351		fprintf(stderr,
352		    "       %s [-h] [-R [-H | -L | -P]] :group file ...\n",
353		    __progname);
354	exit(1);
355}
356