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