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 #ifdef XPG4
82 /*
83 * Check to see if we are to follow symlinks specified on the command line.
84 * This assumes we've already checked to make sure neither -h or -P was
85 * specified, so we are just looking to see if -R -H, or -R -L was specified,
86 * or, since -R has the same behavior as -R -L, if -R was specified by itself.
87 * Therefore, all we really need to check for is if -R was specified.
88 */
89 #define FOLLOW_CL_LINKS (rflag)
90 #else
91 /*
92 * Check to see if we are to follow symlinks specified on the command line.
93 * This assumes we've already checked to make sure neither -h or -P was
94 * specified, so we are just looking to see if -R -H, or -R -L was specified.
95 * Note: -R by itself will change the ownership of a directory referenced by a
96 * symlink however it will now follow the symlink to any other part of the
97 * file hierarchy.
98 */
99 #define FOLLOW_CL_LINKS (rflag && (Hflag || Lflag))
100 #endif
101
102 #ifdef XPG4
103 /*
104 * Follow symlinks when traversing directories. Since -R behaves the
105 * same as -R -L, we always want to follow symlinks to other parts
106 * of the file hierarchy unless -H was specified.
107 */
108 #define FOLLOW_D_LINKS (!Hflag)
109 #else
110 /*
111 * Follow symlinks when traversing directories. Only follow symlinks
112 * to other parts of the file hierarchy if -L was specified.
113 */
114 #define FOLLOW_D_LINKS (Lflag)
115 #endif
116
117 #define CHOWN(f, u, g) if (chown(f, u, g) < 0) { \
118 status += Perror(f); \
119 }
120 #define LCHOWN(f, u, g) if (lchown(f, u, g) < 0) { \
121 status += Perror(f); \
122 }
123
124
125 int
126 main(int argc, char *argv[])
127 {
128 int c;
129 int ch;
130 char *grpp; /* pointer to group name arg */
131 extern int optind;
132 int errflg = 0;
133
134 (void) setlocale(LC_ALL, "");
135 #if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */
136 #define TEXT_DOMAIN "SYS_TEST" /* Use this only if it weren't */
137 #endif
138 (void) textdomain(TEXT_DOMAIN);
139
140 while ((ch = getopt(argc, argv, "hRfHLPs")) != EOF) {
141 switch (ch) {
142 case 'h':
143 hflag++;
144 break;
145
146 case 'R':
147 rflag++;
148 break;
149
150 case 'f':
151 fflag++;
152 break;
153
154 case 'H':
155 /*
156 * If more than one of -H, -L, and -P
157 * are specified, only the last option
158 * specified determines the behavior of
159 * chown.
160 */
161 Lflag = Pflag = 0;
162 Hflag++;
163 break;
164
165 case 'L':
166 Hflag = Pflag = 0;
167 Lflag++;
168 break;
169
170 case 'P':
171 Hflag = Lflag = 0;
172 Pflag++;
173 break;
174
175 case 's':
176 sflag++;
177 break;
178
179 default:
180 errflg++;
181 break;
182 }
183 }
184 /*
185 * Check for sufficient arguments
186 * or a usage error.
187 */
188
189 argc -= optind;
190 argv = &argv[optind];
191
192 if (errflg || (argc < 2) ||
193 ((Hflag || Lflag || Pflag) && !rflag) ||
194 ((Hflag || Lflag || Pflag) && hflag)) {
195 usage();
196 }
197
198 /*
199 * POSIX.2
200 * Check for owner[:group]
201 */
202 if ((grpp = strchr(argv[0], ':')) != NULL) {
203 *grpp++ = 0;
204
205 if (sflag) {
206 if (sid_to_id(grpp, B_FALSE, &gid)) {
207 (void) fprintf(stderr, gettext(
208 "chown: invalid owning group sid %s\n"),
209 grpp);
210 exit(2);
211 }
212 } else if ((grp = getgrnam(grpp)) != NULL) {
213 gid = grp->gr_gid;
214 } else {
215 if (isnumber(grpp)) {
216 errno = 0;
217 gid = (gid_t)strtoul(grpp, NULL, 10);
218 if (errno != 0) {
219 if (errno == ERANGE) {
220 (void) fprintf(stderr, gettext(
221 "chown: group id too large\n"));
222 exit(2);
223 } else {
224 (void) fprintf(stderr, gettext(
225 "chown: invalid group id\n"));
226 exit(2);
227 }
228 }
229 } else {
230 (void) fprintf(stderr, gettext(
231 "chown: unknown group id %s\n"), grpp);
232 exit(2);
233 }
234 }
235 }
236
237 if (sflag) {
238 if (sid_to_id(argv[0], B_TRUE, &uid)) {
239 (void) fprintf(stderr, gettext(
240 "chown: invalid owner sid %s\n"), argv[0]);
241 exit(2);
242 }
243 } else if ((pwd = getpwnam(argv[0])) != NULL) {
244 uid = pwd->pw_uid;
245 } else {
246 if (isnumber(argv[0])) {
247 errno = 0;
248 uid = (uid_t)strtoul(argv[0], NULL, 10);
249 if (errno != 0) {
250 if (errno == ERANGE) {
251 (void) fprintf(stderr, gettext(
252 "chown: user id too large\n"));
253 exit(2);
254 } else {
255 (void) fprintf(stderr, gettext(
256 "chown: invalid user id\n"));
257 exit(2);
258 }
259 }
260 } else {
261 (void) fprintf(stderr, gettext(
262 "chown: unknown user id %s\n"), argv[0]);
263 exit(2);
264 }
265 }
266
267 for (c = 1; c < argc; c++) {
268 tree = NULL;
269 if (lstat(argv[c], &stbuf) < 0) {
270 status += Perror(argv[c]);
271 continue;
272 }
273 if (rflag && ((stbuf.st_mode & S_IFMT) == S_IFLNK)) {
274 if (hflag || Pflag) {
275 /*
276 * Change the ownership of the symlink
277 * specified on the command line.
278 * Don't follow the symbolic link to
279 * any other part of the file hierarchy.
280 */
281 LCHOWN(argv[c], uid, gid);
282 } else {
283 struct stat stbuf2;
284 if (stat(argv[c], &stbuf2) < 0) {
285 status += Perror(argv[c]);
286 continue;
287 }
288 /*
289 * We know that we are to change the
290 * ownership of the file referenced by the
291 * symlink specified on the command line.
292 * Now check to see if we are to follow
293 * the symlink to any other part of the
294 * file hierarchy.
295 */
296 if (FOLLOW_CL_LINKS) {
297 if ((stbuf2.st_mode & S_IFMT)
298 == S_IFDIR) {
299 /*
300 * We are following symlinks so
301 * traverse into the directory.
302 * Add this node to the search
303 * tree so we don't get into an
304 * endless loop.
305 */
306 if (add_tnode(&tree,
307 stbuf2.st_dev,
308 stbuf2.st_ino) == 1) {
309 chownr(argv[c],
310 uid, gid);
311 } else {
312 /*
313 * Error occurred.
314 * rc can't be 0
315 * as this is the first
316 * node to be added to
317 * the search tree.
318 */
319 status += Perror(
320 argv[c]);
321 }
322 } else {
323 /*
324 * Change the user ID of the
325 * file referenced by the
326 * symlink.
327 */
328 CHOWN(argv[c], uid, gid);
329 }
330 } else {
331 /*
332 * Change the user ID of the file
333 * referenced by the symbolic link.
334 */
335 CHOWN(argv[c], uid, gid);
336 }
337 }
338 } else if (rflag && ((stbuf.st_mode & S_IFMT) == S_IFDIR)) {
339 /*
340 * Add this node to the search tree so we don't
341 * get into a endless loop.
342 */
343 if (add_tnode(&tree, stbuf.st_dev,
344 stbuf.st_ino) == 1) {
345 chownr(argv[c], uid, gid);
346 } else {
347 /*
348 * An error occurred while trying
349 * to add the node to the tree.
350 * Continue on with next file
351 * specified. Note: rc shouldn't
352 * be 0 as this was the first node
353 * being added to the search tree.
354 */
355 status += Perror(argv[c]);
356 }
357 } else if (hflag || Pflag) {
358 LCHOWN(argv[c], uid, gid);
359 } else {
360 CHOWN(argv[c], uid, gid);
361 }
362 }
363 return (status);
364 }
365
366 /*
367 * chownr() - recursive chown()
368 *
369 * Recursively chowns the input directory then its contents. rflag must
370 * have been set if chownr() is called. The input directory should not
371 * be a sym link (this is handled in the calling routine). In
372 * addition, the calling routine should have already added the input
373 * directory to the search tree so we do not get into endless loops.
374 * Note: chownr() doesn't need a return value as errors are reported
375 * through the global "status" variable.
376 */
377 static void
378 chownr(char *dir, uid_t uid, gid_t gid)
379 {
380 DIR *dirp;
381 struct dirent *dp;
382 struct stat st, st2;
383 char savedir[1024];
384
385 if (getcwd(savedir, 1024) == (char *)0) {
386 (void) Perror("getcwd");
387 exit(255);
388 }
389
390 /*
391 * Attempt to chown the directory, however don't return if we
392 * can't as we still may be able to chown the contents of the
393 * directory. Note: the calling routine resets the SUID bits
394 * on this directory so we don't have to perform an extra 'stat'.
395 */
396 CHOWN(dir, uid, gid);
397
398 if (chdir(dir) < 0) {
399 status += Perror(dir);
400 return;
401 }
402 if ((dirp = opendir(".")) == NULL) {
403 status += Perror(dir);
404 return;
405 }
406 for (dp = readdir(dirp); dp != NULL; dp = readdir(dirp)) {
407 if (strcmp(dp->d_name, ".") == 0 || /* skip . and .. */
408 strcmp(dp->d_name, "..") == 0) {
409 continue;
410 }
411 if (lstat(dp->d_name, &st) < 0) {
412 status += Perror(dp->d_name);
413 continue;
414 }
415 if ((st.st_mode & S_IFMT) == S_IFLNK) {
416 if (hflag || Pflag) {
417 /*
418 * Change the ownership of the symbolic link
419 * encountered while traversing the
420 * directory. Don't follow the symbolic
421 * link to any other part of the file
422 * hierarchy.
423 */
424 LCHOWN(dp->d_name, uid, gid);
425 } else {
426 if (stat(dp->d_name, &st2) < 0) {
427 status += Perror(dp->d_name);
428 continue;
429 }
430 /*
431 * We know that we are to change the
432 * ownership of the file referenced by the
433 * symlink encountered while traversing
434 * the directory. Now check to see if we
435 * are to follow the symlink to any other
436 * part of the file hierarchy.
437 */
438 if (FOLLOW_D_LINKS) {
439 if ((st2.st_mode & S_IFMT) == S_IFDIR) {
440 /*
441 * We are following symlinks so
442 * traverse into the directory.
443 * Add this node to the search
444 * tree so we don't get into an
445 * endless loop.
446 */
447 int rc;
448 if ((rc = add_tnode(&tree,
449 st2.st_dev,
450 st2.st_ino)) == 1) {
451 chownr(dp->d_name,
452 uid, gid);
453 } else if (rc == 0) {
454 /* already visited */
455 continue;
456 } else {
457 /*
458 * An error occurred
459 * while trying to add
460 * the node to the tree.
461 */
462 status += Perror(
463 dp->d_name);
464 continue;
465 }
466 } else {
467 /*
468 * Change the user id of the
469 * file referenced by the
470 * symbolic link.
471 */
472 CHOWN(dp->d_name, uid, gid);
473 }
474 } else {
475 /*
476 * Change the user id of the file
477 * referenced by the symbolic link.
478 */
479 CHOWN(dp->d_name, uid, gid);
480 }
481 }
482 } else if ((st.st_mode & S_IFMT) == S_IFDIR) {
483 /*
484 * Add this node to the search tree so we don't
485 * get into a endless loop.
486 */
487 int rc;
488 if ((rc = add_tnode(&tree, st.st_dev,
489 st.st_ino)) == 1) {
490 chownr(dp->d_name, uid, gid);
491 } else if (rc == 0) {
492 /* already visited */
493 continue;
494 } else {
495 /*
496 * An error occurred while trying
497 * to add the node to the search tree.
498 */
499 status += Perror(dp->d_name);
500 continue;
501 }
502 } else {
503 CHOWN(dp->d_name, uid, gid);
504 }
505 }
506
507 (void) closedir(dirp);
508 if (chdir(savedir) < 0) {
509 (void) fprintf(stderr, gettext(
510 "chown: can't change back to %s\n"), savedir);
511 exit(255);
512 }
513 }
514
515 static int
516 isnumber(char *s)
517 {
518 int c;
519
520 while ((c = *s++) != '\0')
521 if (!isdigit(c))
522 return (0);
523 return (1);
524 }
525
526 static int
527 Perror(char *s)
528 {
529 if (!fflag) {
530 (void) fprintf(stderr, "chown: ");
531 perror(s);
532 }
533 return (!fflag);
534 }
535
536 static void
537 usage()
538 {
539 (void) fprintf(stderr, gettext(
540 "usage:\n"
541 "\tchown [-fhR] owner[:group] file...\n"
542 "\tchown -R [-f] [-H|-L|-P] owner[:group] file...\n"
543 "\tchown -s [-fhR] ownersid[:groupsid] file...\n"
544 "\tchown -s -R [-f] [-H|-L|-P] ownersid[:groupsid] file...\n"));
545 exit(2);
546 }