xref: /freebsd/bin/chflags/chflags.c (revision 22649d4dba730d46244fd2dff4fd174903c8379f)
1 /*-
2  * SPDX-License-Identifier: BSD-3-Clause
3  *
4  * Copyright (c) 1992, 1993, 1994
5  *	The Regents of the University of California.  All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  * 3. Neither the name of the University nor the names of its contributors
16  *    may be used to endorse or promote products derived from this software
17  *    without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29  * SUCH DAMAGE.
30  */
31 
32 #include <sys/types.h>
33 #include <sys/capsicum.h>
34 #include <sys/stat.h>
35 
36 #include <assert.h>
37 #include <capsicum_helpers.h>
38 #include <err.h>
39 #include <errno.h>
40 #include <fcntl.h>
41 #include <fts.h>
42 #include <getopt.h>
43 #include <libgen.h>
44 #include <limits.h>
45 #include <signal.h>
46 #include <stdbool.h>
47 #include <stdio.h>
48 #include <stdlib.h>
49 #include <string.h>
50 #include <unistd.h>
51 
52 static volatile sig_atomic_t siginfo;
53 
54 #define	OPT_DEREF_UNSAFE	(CHAR_MAX + 1)
55 
56 static void usage(void) __dead2;
57 
58 static void
59 siginfo_handler(int sig __unused)
60 {
61 
62 	siginfo = 1;
63 }
64 
65 /*
66  * A path needs its own pre-opened directory descriptor unless it is a
67  * single path component that can be resolved directly relative to the
68  * base directory descriptor.  Anything containing a '/' (an absolute
69  * path, or a relative path with a directory component) has its parent
70  * directory opened separately, so that in capability mode the final
71  * component is always reached relative to its immediate parent.
72  */
73 static bool
74 needs_own_fd(const char *path)
75 {
76 
77 	return (strchr(path, '/') != NULL);
78 }
79 
80 /*
81  * Open a directory descriptor for the parent of "path", and return in
82  * "*base" a pointer to the final path component (relative to that
83  * descriptor).  "*base" points into the storage of "path".
84  */
85 static int
86 open_base(char *path, char **base)
87 {
88 	char *dir, *bn, *pathcopy;
89 	int fd;
90 
91 	/*
92 	 * dirname() and basename() may modify their argument and may
93 	 * return a pointer to internal storage, so operate on copies and
94 	 * duplicate basename()'s result for the caller.
95 	 */
96 	if ((pathcopy = strdup(path)) == NULL)
97 		err(1, "strdup");
98 	dir = dirname(pathcopy);
99 	fd = open(dir, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
100 	free(pathcopy);
101 
102 	if ((pathcopy = strdup(path)) == NULL)
103 		err(1, "strdup");
104 	bn = basename(pathcopy);
105 	if ((*base = strdup(bn)) == NULL)
106 		err(1, "strdup");
107 	free(pathcopy);
108 
109 	return (fd);
110 }
111 
112 static int
113 chflags_fts(int dirfd, char **paths, int fts_options, u_long set, u_long clear,
114     int oct, int Rflag, int fflag, int vflag)
115 {
116 	FTS *ftsp;
117 	FTSENT *p;
118 	u_long newflags;
119 	int e, rval;
120 
121 	if ((ftsp = fts_openat(dirfd, paths, fts_options, NULL)) == NULL)
122 		err(1, NULL);
123 
124 	for (rval = 0; errno = 0, (p = fts_read(ftsp)) != NULL;) {
125 		int atflag;
126 
127 		if ((fts_options & FTS_LOGICAL) ||
128 		    ((fts_options & FTS_COMFOLLOW) &&
129 		    p->fts_level == FTS_ROOTLEVEL))
130 			atflag = 0;
131 		else
132 			atflag = AT_SYMLINK_NOFOLLOW;
133 
134 		switch (p->fts_info) {
135 		case FTS_D:		/* Change it at FTS_DP if we're recursive. */
136 			if (!Rflag)
137 				fts_set(ftsp, p, FTS_SKIP);
138 			continue;
139 		case FTS_DNR:			/* Warn, chflags. */
140 			warnx("%s: %s", p->fts_path, strerror(p->fts_errno));
141 			rval = 1;
142 			break;
143 		case FTS_ERR:			/* Warn, continue. */
144 		case FTS_NS:
145 			warnx("%s: %s", p->fts_path, strerror(p->fts_errno));
146 			rval = 1;
147 			continue;
148 		default:
149 			break;
150 		}
151 		if (oct)
152 			newflags = set;
153 		else
154 			newflags = (p->fts_statp->st_flags | set) & clear;
155 		if (newflags == p->fts_statp->st_flags)
156 			continue;
157 		if (chflagsat(p->fts_parent->fts_dirfd, p->fts_name, newflags,
158 		    atflag) == -1) {
159 			e = errno;
160 			if (!fflag) {
161 				warnc(e, "%s", p->fts_path);
162 				rval = 1;
163 			}
164 			if (siginfo) {
165 				(void)printf("%s: %s\n", p->fts_path,
166 				    strerror(e));
167 				siginfo = 0;
168 			}
169 		} else if (vflag || siginfo) {
170 			(void)printf("%s", p->fts_path);
171 			if (vflag > 1 || siginfo)
172 				(void)printf(": 0%lo -> 0%lo",
173 				    (u_long)p->fts_statp->st_flags,
174 				    newflags);
175 			(void)printf("\n");
176 			siginfo = 0;
177 		}
178 	}
179 	if (errno)
180 		err(1, "fts_read");
181 	(void)fts_close(ftsp);
182 	return (rval);
183 }
184 
185 int
186 main(int argc, char *argv[])
187 {
188 	static const struct option longopts[] = {
189 		{ "dereference-links-unsafely", no_argument, NULL,
190 		    OPT_DEREF_UNSAFE },
191 		{ NULL, 0, NULL, 0 }
192 	};
193 	u_long clear, set;
194 	long val;
195 	int Hflag, Lflag, Rflag, fflag, hflag, vflag, xflag, unsafe;
196 	int ch, fts_options, oct, rval;
197 	int cwd_fd, i, nrel, nown;
198 	int *ownfd;
199 	char **ownbase;
200 	char *flags, *ep;
201 	char **relpaths, *twopath[2];
202 
203 	Hflag = Lflag = Rflag = fflag = hflag = vflag = xflag = unsafe = 0;
204 	while ((ch = getopt_long(argc, argv, "HLPRfhvx", longopts,
205 	    NULL)) != -1)
206 		switch (ch) {
207 		case 'H':
208 			Hflag = 1;
209 			Lflag = 0;
210 			break;
211 		case 'L':
212 			Lflag = 1;
213 			Hflag = 0;
214 			break;
215 		case 'P':
216 			Hflag = Lflag = 0;
217 			break;
218 		case 'R':
219 			Rflag = 1;
220 			break;
221 		case 'f':
222 			fflag = 1;
223 			break;
224 		case 'h':
225 			hflag = 1;
226 			break;
227 		case 'v':
228 			vflag++;
229 			break;
230 		case 'x':
231 			xflag = 1;
232 			break;
233 		case OPT_DEREF_UNSAFE:
234 			unsafe = 1;
235 			break;
236 		case '?':
237 		default:
238 			usage();
239 		}
240 	argv += optind;
241 	argc -= optind;
242 
243 	if (argc < 2)
244 		usage();
245 
246 	(void)signal(SIGINFO, siginfo_handler);
247 
248 	if (Rflag) {
249 		if (hflag)
250 			errx(1, "the -R and -h options may not be "
251 			    "specified together.");
252 		if (Lflag) {
253 			fts_options = FTS_LOGICAL;
254 		} else {
255 			fts_options = FTS_PHYSICAL;
256 
257 			if (Hflag) {
258 				fts_options |= FTS_COMFOLLOW;
259 			}
260 		}
261 	} else if (hflag) {
262 		fts_options = FTS_PHYSICAL;
263 	} else {
264 		fts_options = FTS_LOGICAL;
265 	}
266 	if (xflag)
267 		fts_options |= FTS_XDEV;
268 
269 	flags = *argv;
270 	if (*flags >= '0' && *flags <= '7') {
271 		errno = 0;
272 		val = strtol(flags, &ep, 8);
273 		if (val < 0)
274 			errno = ERANGE;
275 		if (errno)
276 			err(1, "invalid flags: %s", flags);
277 		if (*ep)
278 			errx(1, "invalid flags: %s", flags);
279 		set = val;
280 		oct = 1;
281 	} else {
282 		if (strtofflags(&flags, &set, &clear))
283 			errx(1, "invalid flag: %s", flags);
284 		clear = ~clear;
285 		oct = 0;
286 	}
287 
288 	argv++;
289 	argc--;
290 
291 	/*
292 	 * Pre-open a directory descriptor for every path argument, so the
293 	 * traversal runs through fd-relative operations.  Plain relative
294 	 * arguments share a descriptor for the current directory; absolute
295 	 * paths and paths containing ".." cannot be resolved relative to
296 	 * another descriptor in capability mode, so each gets its own
297 	 * parent descriptor.
298 	 */
299 	if ((cwd_fd = open(".", O_RDONLY | O_DIRECTORY | O_CLOEXEC)) < 0)
300 		err(1, ".");
301 
302 	relpaths = calloc(argc + 1, sizeof(*relpaths));
303 	ownfd = calloc(argc, sizeof(*ownfd));
304 	ownbase = calloc(argc, sizeof(*ownbase));
305 	if (relpaths == NULL || ownfd == NULL || ownbase == NULL)
306 		err(1, "calloc");
307 	nrel = 0;
308 	nown = 0;
309 	rval = 0;
310 
311 	for (i = 0; i < argc; i++) {
312 		char *arg = argv[i];
313 		char resolved[PATH_MAX];
314 		struct stat sb;
315 
316 		/*
317 		 * A symbolic link named on the command line is followed
318 		 * unless -h was given (or a purely physical walk was
319 		 * requested).  Its target may lie outside the link's parent
320 		 * directory, which capability mode could not reach, so
321 		 * resolve the link now and operate relative to the target's
322 		 * own parent.
323 		 */
324 		if ((fts_options & (FTS_LOGICAL | FTS_COMFOLLOW)) &&
325 		    lstat(arg, &sb) == 0 && S_ISLNK(sb.st_mode)) {
326 			if (realpath(arg, resolved) == NULL) {
327 				warn("%s", arg);
328 				rval = 1;
329 				continue;
330 			}
331 			arg = resolved;
332 		}
333 
334 		if (needs_own_fd(arg)) {
335 			int fd = open_base(arg, &ownbase[nown]);
336 			if (fd < 0) {
337 				warn("%s", argv[i]);
338 				rval = 1;
339 				continue;
340 			}
341 			ownfd[nown] = fd;
342 			nown++;
343 		} else {
344 			/*
345 			 * A resolved symlink is always absolute and thus
346 			 * takes the branch above; only an unmodified argument
347 			 * reaches this point.  Store argv[i], which outlives
348 			 * the on-stack resolved[] buffer.
349 			 */
350 			assert(arg != resolved);
351 			relpaths[nrel++] = argv[i];
352 		}
353 	}
354 	relpaths[nrel] = NULL;
355 
356 	if (caph_limit_stdio() < 0)
357 		err(1, "caph_limit_stdio");
358 	/*
359 	 * With --dereference-links-unsafely the traversal may follow a
360 	 * symlink to a file outside the hierarchy named on the command
361 	 * line, which capability mode would block, so skip caph_enter() in
362 	 * that case.
363 	 */
364 	if (!unsafe && caph_enter() < 0)
365 		err(1, "caph_enter");
366 
367 	/* Process all plain relative paths together under cwd_fd. */
368 	if (nrel > 0)
369 		rval |= chflags_fts(cwd_fd, relpaths, fts_options, set, clear,
370 		    oct, Rflag, fflag, vflag);
371 
372 	/* Process each absolute / ".."-containing path under its own fd. */
373 	for (i = 0; i < nown; i++) {
374 		twopath[0] = ownbase[i];
375 		twopath[1] = NULL;
376 		rval |= chflags_fts(ownfd[i], twopath, fts_options, set,
377 		    clear, oct, Rflag, fflag, vflag);
378 		free(ownbase[i]);
379 	}
380 	free(relpaths);
381 	free(ownfd);
382 	free(ownbase);
383 	exit(rval);
384 }
385 
386 static void
387 usage(void)
388 {
389 	(void)fprintf(stderr,
390 	    "usage: chflags [-fhvx] [-R [-H | -L | -P]] "
391 	    "[--dereference-links-unsafely] flags file ...\n");
392 	exit(1);
393 }
394