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