1 |
|
|
/* $OpenBSD: chmod.c,v 1.39 2015/12/31 23:38:16 guenther 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 <locale.h> |
43 |
|
|
#include <pwd.h> |
44 |
|
|
#include <stdio.h> |
45 |
|
|
#include <stdlib.h> |
46 |
|
|
#include <string.h> |
47 |
|
|
#include <unistd.h> |
48 |
|
|
|
49 |
|
|
int ischflags, ischown, ischgrp, ischmod; |
50 |
|
|
extern char *__progname; |
51 |
|
|
|
52 |
|
|
gid_t a_gid(const char *); |
53 |
|
|
uid_t a_uid(const char *, int); |
54 |
|
|
__dead void usage(void); |
55 |
|
|
|
56 |
|
|
int |
57 |
|
|
main(int argc, char *argv[]) |
58 |
|
379 |
{ |
59 |
|
|
FTS *ftsp; |
60 |
|
|
FTSENT *p; |
61 |
|
|
void *set; |
62 |
|
|
unsigned long val; |
63 |
|
|
int oct; |
64 |
|
|
mode_t omode; |
65 |
|
|
int Hflag, Lflag, Rflag, ch, fflag, fts_options, hflag, rval, atflags; |
66 |
|
|
uid_t uid; |
67 |
|
|
gid_t gid; |
68 |
|
|
u_int32_t fclear, fset; |
69 |
|
|
char *ep, *mode, *cp, *flags; |
70 |
|
|
|
71 |
|
379 |
setlocale(LC_ALL, ""); |
72 |
|
|
|
73 |
✓✗ |
379 |
if (strlen(__progname) > 2) { |
74 |
|
379 |
ischown = __progname[2] == 'o'; |
75 |
|
379 |
ischgrp = __progname[2] == 'g'; |
76 |
|
379 |
ischmod = __progname[2] == 'm'; |
77 |
|
379 |
ischflags = __progname[2] == 'f'; |
78 |
|
|
} |
79 |
|
|
|
80 |
|
379 |
uid = (uid_t)-1; |
81 |
|
379 |
gid = (gid_t)-1; |
82 |
|
379 |
Hflag = Lflag = Rflag = fflag = hflag = 0; |
83 |
✓✓ |
958 |
while ((ch = getopt(argc, argv, "HLPRXfghorstuwx")) != -1) |
84 |
✓✓✓✓ ✗✓✗✗
|
200 |
switch (ch) { |
85 |
|
|
case 'H': |
86 |
|
8 |
Hflag = 1; |
87 |
|
8 |
Lflag = 0; |
88 |
|
8 |
break; |
89 |
|
|
case 'L': |
90 |
|
32 |
Lflag = 1; |
91 |
|
32 |
Hflag = 0; |
92 |
|
32 |
break; |
93 |
|
|
case 'P': |
94 |
|
8 |
Hflag = Lflag = 0; |
95 |
|
8 |
break; |
96 |
|
|
case 'R': |
97 |
|
140 |
Rflag = 1; |
98 |
|
140 |
break; |
99 |
|
|
case 'f': /* no longer documented. */ |
100 |
|
|
fflag = 1; |
101 |
|
|
break; |
102 |
|
|
case 'h': |
103 |
|
12 |
hflag = 1; |
104 |
|
12 |
break; |
105 |
|
|
/* |
106 |
|
|
* If this is a symbolic mode argument rather than |
107 |
|
|
* an option, we are done with option processing. |
108 |
|
|
*/ |
109 |
|
|
case 'g': case 'o': case 'r': case 's': |
110 |
|
|
case 't': case 'u': case 'w': case 'X': case 'x': |
111 |
|
|
if (!ischmod) |
112 |
|
|
usage(); |
113 |
|
|
/* |
114 |
|
|
* If getopt() moved past the argument, back up. |
115 |
|
|
* If the argument contains option letters before |
116 |
|
|
* mode letters, setmode() will catch them. |
117 |
|
|
*/ |
118 |
|
|
if (optind > 1) { |
119 |
|
|
cp = argv[optind - 1]; |
120 |
|
|
if (cp[strlen(cp) - 1] == ch) |
121 |
|
|
--optind; |
122 |
|
|
} |
123 |
|
|
goto done; |
124 |
|
|
default: |
125 |
|
|
usage(); |
126 |
|
|
} |
127 |
|
379 |
done: |
128 |
|
379 |
argv += optind; |
129 |
|
379 |
argc -= optind; |
130 |
|
|
|
131 |
✗✓ |
379 |
if (argc < 2) |
132 |
|
|
usage(); |
133 |
|
|
|
134 |
|
|
/* |
135 |
|
|
* We alter the symlink itself if doing -h or -RP, or |
136 |
|
|
* if doing -RH and the symlink wasn't a command line arg. |
137 |
|
|
*/ |
138 |
|
379 |
atflags = AT_SYMLINK_NOFOLLOW; |
139 |
|
|
|
140 |
|
379 |
fts_options = FTS_PHYSICAL; |
141 |
✓✓ |
379 |
if (Rflag) { |
142 |
✗✓ |
140 |
if (hflag) |
143 |
|
|
errx(1, |
144 |
|
|
"the -R and -h options may not be specified together."); |
145 |
✓✓ |
140 |
if (Hflag) |
146 |
|
8 |
fts_options |= FTS_COMFOLLOW; |
147 |
✓✓ |
140 |
if (Lflag) { |
148 |
|
32 |
fts_options &= ~FTS_PHYSICAL; |
149 |
|
32 |
fts_options |= FTS_LOGICAL; |
150 |
|
32 |
atflags = 0; |
151 |
|
|
} |
152 |
✓✓ |
239 |
} else if (!hflag) |
153 |
|
227 |
atflags = 0; |
154 |
|
|
|
155 |
✓✓ |
379 |
if (ischflags) { |
156 |
✗✓ |
28 |
if (pledge("stdio rpath fattr wpath cpath", NULL) == -1) |
157 |
|
|
err(1, "pledge"); |
158 |
|
|
|
159 |
|
28 |
flags = *argv; |
160 |
✗✓ |
28 |
if (*flags >= '0' && *flags <= '7') { |
161 |
|
|
errno = 0; |
162 |
|
|
val = strtoul(flags, &ep, 8); |
163 |
|
|
if (val > UINT_MAX) |
164 |
|
|
errno = ERANGE; |
165 |
|
|
if (errno) |
166 |
|
|
err(1, "invalid flags: %s", flags); |
167 |
|
|
if (*ep) |
168 |
|
|
errx(1, "invalid flags: %s", flags); |
169 |
|
|
fset = val; |
170 |
|
|
oct = 1; |
171 |
|
|
} else { |
172 |
✓✓ |
28 |
if (strtofflags(&flags, &fset, &fclear)) |
173 |
|
2 |
errx(1, "invalid flag: %s", flags); |
174 |
|
26 |
fclear = ~fclear; |
175 |
|
26 |
oct = 0; |
176 |
|
|
} |
177 |
✓✓ |
351 |
} else if (ischmod) { |
178 |
|
275 |
mode = *argv; |
179 |
✓✓ |
275 |
if (*mode >= '0' && *mode <= '7') { |
180 |
|
259 |
errno = 0; |
181 |
|
259 |
val = strtoul(mode, &ep, 8); |
182 |
✗✓ |
259 |
if (val > INT_MAX) |
183 |
|
|
errno = ERANGE; |
184 |
✗✓ |
259 |
if (errno) |
185 |
|
|
err(1, "invalid file mode: %s", mode); |
186 |
✗✓ |
259 |
if (*ep) |
187 |
|
|
errx(1, "invalid file mode: %s", mode); |
188 |
|
259 |
omode = val; |
189 |
|
259 |
oct = 1; |
190 |
|
|
} else { |
191 |
✓✓ |
16 |
if ((set = setmode(mode)) == NULL) |
192 |
|
2 |
errx(1, "invalid file mode: %s", mode); |
193 |
|
14 |
oct = 0; |
194 |
|
|
} |
195 |
✓✓ |
76 |
} else if (ischown) { |
196 |
|
|
/* Both UID and GID are given. */ |
197 |
✓✓ |
58 |
if ((cp = strchr(*argv, ':')) != NULL) { |
198 |
|
40 |
*cp++ = '\0'; |
199 |
|
40 |
gid = a_gid(cp); |
200 |
|
|
} |
201 |
|
|
#ifdef SUPPORT_DOT |
202 |
|
|
/* UID and GID are separated by a dot and UID exists. */ |
203 |
✗✓✗✗
|
18 |
else if ((cp = strchr(*argv, '.')) != NULL && |
204 |
|
|
(uid = a_uid(*argv, 1)) == (uid_t)-1) { |
205 |
|
|
*cp++ = '\0'; |
206 |
|
|
gid = a_gid(cp); |
207 |
|
|
} |
208 |
|
|
#endif |
209 |
✓✗ |
58 |
if (uid == (uid_t)-1) |
210 |
|
58 |
uid = a_uid(*argv, 0); |
211 |
|
|
} else |
212 |
|
18 |
gid = a_gid(*argv); |
213 |
|
|
|
214 |
✗✓ |
371 |
if ((ftsp = fts_open(++argv, fts_options, 0)) == NULL) |
215 |
|
|
err(1, NULL); |
216 |
✓✓ |
2080 |
for (rval = 0; (p = fts_read(ftsp)) != NULL;) { |
217 |
✓✗✓✓ ✓✓ |
1338 |
switch (p->fts_info) { |
218 |
|
|
case FTS_D: |
219 |
✓✓ |
274 |
if (!Rflag) |
220 |
|
32 |
fts_set(ftsp, p, FTS_SKIP); |
221 |
✓✓ |
274 |
if (ischmod) |
222 |
|
178 |
break; |
223 |
|
|
else |
224 |
|
96 |
continue; |
225 |
|
|
case FTS_DNR: /* Warn, chmod, continue. */ |
226 |
|
|
warnc(p->fts_errno, "%s", p->fts_path); |
227 |
|
|
rval = 1; |
228 |
|
|
break; |
229 |
|
|
case FTS_DP: /* Already changed at FTS_D. */ |
230 |
✓✓ |
274 |
if (ischmod) |
231 |
|
178 |
continue; |
232 |
|
|
else |
233 |
|
96 |
break; |
234 |
|
|
case FTS_ERR: /* Warn, continue. */ |
235 |
|
|
case FTS_NS: |
236 |
|
1 |
warnc(p->fts_errno, "%s", p->fts_path); |
237 |
|
1 |
rval = 1; |
238 |
|
1 |
continue; |
239 |
|
|
case FTS_SL: /* Ignore. */ |
240 |
|
|
case FTS_SLNONE: |
241 |
|
|
/* |
242 |
|
|
* The only symlinks that end up here are ones that |
243 |
|
|
* don't point to anything or that loop and ones |
244 |
|
|
* that we found doing a physical walk. |
245 |
|
|
*/ |
246 |
✓✓✗✓
|
158 |
if (!hflag && (fts_options & FTS_LOGICAL)) |
247 |
|
|
continue; |
248 |
|
|
break; |
249 |
|
|
default: |
250 |
|
|
break; |
251 |
|
|
} |
252 |
|
|
|
253 |
|
|
/* |
254 |
|
|
* For -RH, the decision of how to handle symlinks depends |
255 |
|
|
* on the level: follow it iff it's a command line arg. |
256 |
|
|
*/ |
257 |
✓✓ |
1063 |
if (fts_options & FTS_COMFOLLOW) { |
258 |
✓✓ |
32 |
atflags = p->fts_level == FTS_ROOTLEVEL ? 0 : |
259 |
|
|
AT_SYMLINK_NOFOLLOW; |
260 |
|
|
} |
261 |
|
|
|
262 |
✓✓ |
1063 |
if (ischmod) { |
263 |
✓✓✗✓ ✗✗ |
677 |
if (!fchmodat(AT_FDCWD, p->fts_accpath, oct ? omode : |
264 |
|
|
getmode(set, p->fts_statp->st_mode), atflags) |
265 |
|
|
|| fflag) |
266 |
|
|
continue; |
267 |
✓✓ |
386 |
} else if (!ischflags) { |
268 |
✗✓✗✗
|
264 |
if (!fchownat(AT_FDCWD, p->fts_accpath, uid, gid, |
269 |
|
|
atflags) || fflag) |
270 |
|
|
continue; |
271 |
|
|
} else { |
272 |
✓✗✓✗
|
122 |
if (!chflagsat(AT_FDCWD, p->fts_accpath, oct ? fset : |
273 |
|
|
(p->fts_statp->st_flags | fset) & fclear, atflags)) |
274 |
|
122 |
continue; |
275 |
|
|
} |
276 |
|
|
|
277 |
|
|
/* error case */ |
278 |
|
|
warn("%s", p->fts_path); |
279 |
|
|
rval = 1; |
280 |
|
|
} |
281 |
✗✓ |
371 |
if (errno) |
282 |
|
|
err(1, "fts_read"); |
283 |
|
371 |
fts_close(ftsp); |
284 |
|
371 |
exit(rval); |
285 |
|
|
} |
286 |
|
|
|
287 |
|
|
/* |
288 |
|
|
* Given a UID or user name in a string, return the UID. If an empty string |
289 |
|
|
* was given, returns -1. If silent is 0, exits on invalid user names/UIDs; |
290 |
|
|
* otherwise, returns -1. |
291 |
|
|
*/ |
292 |
|
|
uid_t |
293 |
|
|
a_uid(const char *s, int silent) |
294 |
|
58 |
{ |
295 |
|
|
struct passwd *pw; |
296 |
|
|
const char *errstr; |
297 |
|
|
uid_t uid; |
298 |
|
|
|
299 |
✗✓ |
58 |
if (*s == '\0') /* Argument was "[:.]gid". */ |
300 |
|
|
return ((uid_t)-1); |
301 |
|
|
|
302 |
|
|
/* User name was given. */ |
303 |
✓✓ |
58 |
if ((pw = getpwnam(s)) != NULL) |
304 |
|
54 |
return (pw->pw_uid); |
305 |
|
|
|
306 |
|
|
/* UID was given. */ |
307 |
|
4 |
uid = (uid_t)strtonum(s, 0, UID_MAX, &errstr); |
308 |
✓✓ |
4 |
if (errstr) { |
309 |
✗✓ |
2 |
if (silent) |
310 |
|
|
return ((uid_t)-1); |
311 |
|
|
else |
312 |
|
2 |
errx(1, "user is %s: %s", errstr, s); |
313 |
|
|
} |
314 |
|
|
|
315 |
|
2 |
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 |
323 |
|
|
a_gid(const char *s) |
324 |
|
58 |
{ |
325 |
|
|
struct group *gr; |
326 |
|
|
const char *errstr; |
327 |
|
|
gid_t gid; |
328 |
|
|
|
329 |
✗✓ |
58 |
if (*s == '\0') /* Argument was "uid[:.]". */ |
330 |
|
|
return ((gid_t)-1); |
331 |
|
|
|
332 |
|
|
/* Group name was given. */ |
333 |
✓✓ |
58 |
if ((gr = getgrnam(s)) != NULL) |
334 |
|
54 |
return (gr->gr_gid); |
335 |
|
|
|
336 |
|
|
/* GID was given. */ |
337 |
|
4 |
gid = (gid_t)strtonum(s, 0, GID_MAX, &errstr); |
338 |
✓✓ |
4 |
if (errstr) |
339 |
|
2 |
errx(1, "group is %s: %s", errstr, s); |
340 |
|
|
|
341 |
|
2 |
return (gid); |
342 |
|
|
} |
343 |
|
|
|
344 |
|
|
void |
345 |
|
|
usage(void) |
346 |
|
|
{ |
347 |
|
|
fprintf(stderr, |
348 |
|
|
"usage: %s [-h] [-R [-H | -L | -P]] %s file ...\n", |
349 |
|
|
__progname, ischmod ? "mode" : ischflags ? "flags" : |
350 |
|
|
ischown ? "owner[:group]" : "group"); |
351 |
|
|
if (ischown) |
352 |
|
|
fprintf(stderr, |
353 |
|
|
" %s [-h] [-R [-H | -L | -P]] :group file ...\n", |
354 |
|
|
__progname); |
355 |
|
|
exit(1); |
356 |
|
|
} |