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