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