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 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 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 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 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 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