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
48 int ischflags, ischown, ischgrp, ischmod;
49 extern char *__progname;
50
51 gid_t a_gid(const char *);
52 uid_t a_uid(const char *, int);
53 static void __dead usage(void);
54
55 int
main(int argc,char * argv[])56 main(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 }
124 done:
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 */
293 uid_t
a_uid(const char * s,int silent)294 a_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 */
322 gid_t
a_gid(const char * s)323 a_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
343 static void __dead
usage(void)344 usage(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