1 /*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21 /*
22 * Copyright 2008 Sun Microsystems, Inc. All rights reserved.
23 * Use is subject to license terms.
24 */
25
26 /* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T */
27 /* All Rights Reserved */
28
29 /*
30 * Portions of this source code were derived from Berkeley 4.3 BSD
31 * under license from the Regents of the University of California.
32 */
33
34 /*
35 * Copyright (c) 2018, Joyent, Inc.
36 */
37
38 /*
39 * chown [-fhR] uid[:gid] file ...
40 * chown -R [-f] [-H|-L|-P] uid[:gid] file ...
41 * chown -s [-fhR] ownersid[:groupsid] file ...
42 * chown -s -R [-f] [-H|-L|-P] ownersid[:groupsid] file ...
43 */
44
45 #include <stdio.h>
46 #include <stdlib.h>
47 #include <ctype.h>
48 #include <sys/types.h>
49 #include <dirent.h>
50 #include <string.h>
51 #include <sys/stat.h>
52 #include <sys/avl.h>
53 #include <pwd.h>
54 #include <grp.h>
55 #include <unistd.h>
56 #include <locale.h>
57 #include <errno.h>
58 #include <libcmdutils.h>
59 #include <aclutils.h>
60
61 static struct passwd *pwd;
62 static struct group *grp;
63 static struct stat stbuf;
64 static uid_t uid = (uid_t)-1;
65 static gid_t gid = (gid_t)-1;
66 static int status = 0; /* total number of errors received */
67 static int hflag = 0,
68 rflag = 0,
69 fflag = 0,
70 Hflag = 0,
71 Lflag = 0,
72 Pflag = 0,
73 sflag = 0;
74 static avl_tree_t *tree;
75
76 static int Perror(char *);
77 static int isnumber(char *);
78 static void chownr(char *, uid_t, gid_t);
79 static void usage();
80
81 /*
82 * Check to see if we are to follow symlinks specified on the command line.
83 * This assumes we've already checked to make sure neither -h or -P was
84 * specified, so we are just looking to see if -R -H, or -R -L was specified.
85 */
86 #define FOLLOW_CL_LINKS (rflag && (Hflag || Lflag))
87
88 /*
89 * Follow symlinks when traversing directories. Only follow symlinks
90 * to other parts of the file hierarchy if -L was specified.
91 */
92 #define FOLLOW_D_LINKS (Lflag)
93
94 #define CHOWN(f, u, g) if (chown(f, u, g) < 0) { \
95 status += Perror(f); \
96 }
97 #define LCHOWN(f, u, g) if (lchown(f, u, g) < 0) { \
98 status += Perror(f); \
99 }
100
101
102 int
main(int argc,char * argv[])103 main(int argc, char *argv[])
104 {
105 int c;
106 int ch;
107 char *grpp; /* pointer to group name arg */
108 extern int optind;
109 int errflg = 0;
110
111 (void) setlocale(LC_ALL, "");
112 #if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */
113 #define TEXT_DOMAIN "SYS_TEST" /* Use this only if it weren't */
114 #endif
115 (void) textdomain(TEXT_DOMAIN);
116
117 while ((ch = getopt(argc, argv, "hRfHLPs")) != EOF) {
118 switch (ch) {
119 case 'h':
120 hflag++;
121 break;
122
123 case 'R':
124 rflag++;
125 break;
126
127 case 'f':
128 fflag++;
129 break;
130
131 case 'H':
132 /*
133 * If more than one of -H, -L, and -P
134 * are specified, only the last option
135 * specified determines the behavior of
136 * chown.
137 */
138 Lflag = Pflag = 0;
139 Hflag++;
140 break;
141
142 case 'L':
143 Hflag = Pflag = 0;
144 Lflag++;
145 break;
146
147 case 'P':
148 Hflag = Lflag = 0;
149 Pflag++;
150 break;
151
152 case 's':
153 sflag++;
154 break;
155
156 default:
157 errflg++;
158 break;
159 }
160 }
161 /*
162 * Set Pflag by default for recursive operations
163 * if no other options were specified.
164 */
165 if (rflag && !(Lflag || Hflag || Pflag || hflag)) {
166 Pflag = 1;
167 }
168 /*
169 * Check for sufficient arguments
170 * or a usage error.
171 */
172
173 argc -= optind;
174 argv = &argv[optind];
175
176 if (errflg || (argc < 2) ||
177 ((Hflag || Lflag || Pflag) && !rflag) ||
178 ((Hflag || Lflag || Pflag) && hflag)) {
179 usage();
180 }
181
182 /*
183 * POSIX.2
184 * Check for owner[:group]
185 */
186 if ((grpp = strchr(argv[0], ':')) != NULL) {
187 *grpp++ = 0;
188
189 if (sflag) {
190 if (sid_to_id(grpp, B_FALSE, &gid)) {
191 (void) fprintf(stderr, gettext(
192 "chown: invalid owning group sid %s\n"),
193 grpp);
194 exit(2);
195 }
196 } else if ((grp = getgrnam(grpp)) != NULL) {
197 gid = grp->gr_gid;
198 } else {
199 if (isnumber(grpp)) {
200 errno = 0;
201 gid = (gid_t)strtoul(grpp, NULL, 10);
202 if (errno != 0) {
203 if (errno == ERANGE) {
204 (void) fprintf(stderr, gettext(
205 "chown: group id too large\n"));
206 exit(2);
207 } else {
208 (void) fprintf(stderr, gettext(
209 "chown: invalid group id\n"));
210 exit(2);
211 }
212 }
213 } else {
214 (void) fprintf(stderr, gettext(
215 "chown: unknown group id %s\n"), grpp);
216 exit(2);
217 }
218 }
219 }
220
221 if (sflag) {
222 if (sid_to_id(argv[0], B_TRUE, &uid)) {
223 (void) fprintf(stderr, gettext(
224 "chown: invalid owner sid %s\n"), argv[0]);
225 exit(2);
226 }
227 } else if ((pwd = getpwnam(argv[0])) != NULL) {
228 uid = pwd->pw_uid;
229 } else {
230 if (isnumber(argv[0])) {
231 errno = 0;
232 uid = (uid_t)strtoul(argv[0], NULL, 10);
233 if (errno != 0) {
234 if (errno == ERANGE) {
235 (void) fprintf(stderr, gettext(
236 "chown: user id too large\n"));
237 exit(2);
238 } else {
239 (void) fprintf(stderr, gettext(
240 "chown: invalid user id\n"));
241 exit(2);
242 }
243 }
244 } else {
245 (void) fprintf(stderr, gettext(
246 "chown: unknown user id %s\n"), argv[0]);
247 exit(2);
248 }
249 }
250
251 for (c = 1; c < argc; c++) {
252 tree = NULL;
253 if (lstat(argv[c], &stbuf) < 0) {
254 status += Perror(argv[c]);
255 continue;
256 }
257 if (rflag && ((stbuf.st_mode & S_IFMT) == S_IFLNK)) {
258 if (hflag || Pflag) {
259 /*
260 * Change the ownership of the symlink
261 * specified on the command line.
262 * Don't follow the symbolic link to
263 * any other part of the file hierarchy.
264 */
265 LCHOWN(argv[c], uid, gid);
266 } else {
267 struct stat stbuf2;
268 if (stat(argv[c], &stbuf2) < 0) {
269 status += Perror(argv[c]);
270 continue;
271 }
272 /*
273 * We know that we are to change the
274 * ownership of the file referenced by the
275 * symlink specified on the command line.
276 * Now check to see if we are to follow
277 * the symlink to any other part of the
278 * file hierarchy.
279 */
280 if (FOLLOW_CL_LINKS) {
281 if ((stbuf2.st_mode & S_IFMT)
282 == S_IFDIR) {
283 /*
284 * We are following symlinks so
285 * traverse into the directory.
286 * Add this node to the search
287 * tree so we don't get into an
288 * endless loop.
289 */
290 if (add_tnode(&tree,
291 stbuf2.st_dev,
292 stbuf2.st_ino) == 1) {
293 chownr(argv[c],
294 uid, gid);
295 } else {
296 /*
297 * Error occurred.
298 * rc can't be 0
299 * as this is the first
300 * node to be added to
301 * the search tree.
302 */
303 status += Perror(
304 argv[c]);
305 }
306 } else {
307 /*
308 * Change the user ID of the
309 * file referenced by the
310 * symlink.
311 */
312 CHOWN(argv[c], uid, gid);
313 }
314 } else {
315 /*
316 * Change the user ID of the file
317 * referenced by the symbolic link.
318 */
319 CHOWN(argv[c], uid, gid);
320 }
321 }
322 } else if (rflag && ((stbuf.st_mode & S_IFMT) == S_IFDIR)) {
323 /*
324 * Add this node to the search tree so we don't
325 * get into a endless loop.
326 */
327 if (add_tnode(&tree, stbuf.st_dev,
328 stbuf.st_ino) == 1) {
329 chownr(argv[c], uid, gid);
330 } else {
331 /*
332 * An error occurred while trying
333 * to add the node to the tree.
334 * Continue on with next file
335 * specified. Note: rc shouldn't
336 * be 0 as this was the first node
337 * being added to the search tree.
338 */
339 status += Perror(argv[c]);
340 }
341 } else if (hflag || Pflag) {
342 LCHOWN(argv[c], uid, gid);
343 } else {
344 CHOWN(argv[c], uid, gid);
345 }
346 }
347 return (status);
348 }
349
350 /*
351 * chownr() - recursive chown()
352 *
353 * Recursively chowns the input directory then its contents. rflag must
354 * have been set if chownr() is called. The input directory should not
355 * be a sym link (this is handled in the calling routine). In
356 * addition, the calling routine should have already added the input
357 * directory to the search tree so we do not get into endless loops.
358 * Note: chownr() doesn't need a return value as errors are reported
359 * through the global "status" variable.
360 */
361 static void
chownr(char * dir,uid_t uid,gid_t gid)362 chownr(char *dir, uid_t uid, gid_t gid)
363 {
364 DIR *dirp;
365 struct dirent *dp;
366 struct stat st, st2;
367 char savedir[1024];
368
369 if (getcwd(savedir, 1024) == (char *)0) {
370 (void) Perror("getcwd");
371 exit(255);
372 }
373
374 /*
375 * Attempt to chown the directory, however don't return if we
376 * can't as we still may be able to chown the contents of the
377 * directory. Note: the calling routine resets the SUID bits
378 * on this directory so we don't have to perform an extra 'stat'.
379 */
380 CHOWN(dir, uid, gid);
381
382 if (chdir(dir) < 0) {
383 status += Perror(dir);
384 return;
385 }
386 if ((dirp = opendir(".")) == NULL) {
387 status += Perror(dir);
388 return;
389 }
390 for (dp = readdir(dirp); dp != NULL; dp = readdir(dirp)) {
391 if (strcmp(dp->d_name, ".") == 0 || /* skip . and .. */
392 strcmp(dp->d_name, "..") == 0) {
393 continue;
394 }
395 if (lstat(dp->d_name, &st) < 0) {
396 status += Perror(dp->d_name);
397 continue;
398 }
399 if ((st.st_mode & S_IFMT) == S_IFLNK) {
400 if (hflag || Pflag) {
401 /*
402 * Change the ownership of the symbolic link
403 * encountered while traversing the
404 * directory. Don't follow the symbolic
405 * link to any other part of the file
406 * hierarchy.
407 */
408 LCHOWN(dp->d_name, uid, gid);
409 } else {
410 if (stat(dp->d_name, &st2) < 0) {
411 status += Perror(dp->d_name);
412 continue;
413 }
414 /*
415 * We know that we are to change the
416 * ownership of the file referenced by the
417 * symlink encountered while traversing
418 * the directory. Now check to see if we
419 * are to follow the symlink to any other
420 * part of the file hierarchy.
421 */
422 if (FOLLOW_D_LINKS) {
423 if ((st2.st_mode & S_IFMT) == S_IFDIR) {
424 /*
425 * We are following symlinks so
426 * traverse into the directory.
427 * Add this node to the search
428 * tree so we don't get into an
429 * endless loop.
430 */
431 int rc;
432 if ((rc = add_tnode(&tree,
433 st2.st_dev,
434 st2.st_ino)) == 1) {
435 chownr(dp->d_name,
436 uid, gid);
437 } else if (rc == 0) {
438 /* already visited */
439 continue;
440 } else {
441 /*
442 * An error occurred
443 * while trying to add
444 * the node to the tree.
445 */
446 status += Perror(
447 dp->d_name);
448 continue;
449 }
450 } else {
451 /*
452 * Change the user id of the
453 * file referenced by the
454 * symbolic link.
455 */
456 CHOWN(dp->d_name, uid, gid);
457 }
458 } else {
459 /*
460 * Change the user id of the file
461 * referenced by the symbolic link.
462 */
463 CHOWN(dp->d_name, uid, gid);
464 }
465 }
466 } else if ((st.st_mode & S_IFMT) == S_IFDIR) {
467 /*
468 * Add this node to the search tree so we don't
469 * get into a endless loop.
470 */
471 int rc;
472 if ((rc = add_tnode(&tree, st.st_dev,
473 st.st_ino)) == 1) {
474 chownr(dp->d_name, uid, gid);
475 } else if (rc == 0) {
476 /* already visited */
477 continue;
478 } else {
479 /*
480 * An error occurred while trying
481 * to add the node to the search tree.
482 */
483 status += Perror(dp->d_name);
484 continue;
485 }
486 } else {
487 CHOWN(dp->d_name, uid, gid);
488 }
489 }
490
491 (void) closedir(dirp);
492 if (chdir(savedir) < 0) {
493 (void) fprintf(stderr, gettext(
494 "chown: can't change back to %s\n"), savedir);
495 exit(255);
496 }
497 }
498
499 static int
isnumber(char * s)500 isnumber(char *s)
501 {
502 int c;
503
504 while ((c = *s++) != '\0')
505 if (!isdigit(c))
506 return (0);
507 return (1);
508 }
509
510 static int
Perror(char * s)511 Perror(char *s)
512 {
513 if (!fflag) {
514 (void) fprintf(stderr, "chown: ");
515 perror(s);
516 }
517 return (!fflag);
518 }
519
520 static void
usage()521 usage()
522 {
523 (void) fprintf(stderr, gettext(
524 "usage:\n"
525 "\tchown [-fhR] owner[:group] file...\n"
526 "\tchown -R [-f] [-H|-L|-P] owner[:group] file...\n"
527 "\tchown -s [-fhR] ownersid[:groupsid] file...\n"
528 "\tchown -s -R [-f] [-H|-L|-P] ownersid[:groupsid] file...\n"));
529 exit(2);
530 }
531