Home | History | Annotate | Download | only in chown
      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