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