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