xref: /illumos-gate/usr/src/cmd/chown/chown.c (revision 533affcbc7fc4d0c8132976ea454aaa715fe2307)
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
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
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
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
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
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