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 2007 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 = (uid_t)-1; 60 static gid_t gid = (gid_t)-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 * Check for sufficient arguments 176 * or a usage error. 177 */ 178 179 argc -= optind; 180 argv = &argv[optind]; 181 182 if (errflg || (argc < 2) || 183 ((Hflag || Lflag || Pflag) && !rflag) || 184 ((Hflag || Lflag || Pflag) && hflag)) { 185 usage(); 186 } 187 188 /* 189 * POSIX.2 190 * Check for owner[:group] 191 */ 192 if ((grpp = strchr(argv[0], ':')) != NULL) { 193 *grpp++ = 0; 194 if ((grp = getgrnam(grpp)) != NULL) { 195 gid = grp->gr_gid; 196 } else { 197 if (isnumber(grpp)) { 198 errno = 0; 199 gid = (gid_t)strtoul(grpp, NULL, 10); 200 if (errno != 0) { 201 if (errno == ERANGE) { 202 (void) fprintf(stderr, gettext( 203 "chown: group id too large\n")); 204 exit(2); 205 } else { 206 (void) fprintf(stderr, gettext( 207 "chown: invalid group id\n")); 208 exit(2); 209 } 210 } 211 } else { 212 (void) fprintf(stderr, gettext( 213 "chown: unknown group id %s\n"), grpp); 214 exit(2); 215 } 216 } 217 } 218 219 if ((pwd = getpwnam(argv[0])) != NULL) { 220 uid = pwd->pw_uid; 221 } else { 222 if (isnumber(argv[0])) { 223 errno = 0; 224 uid = (uid_t)strtoul(argv[0], NULL, 10); 225 if (errno != 0) { 226 if (errno == ERANGE) { 227 (void) fprintf(stderr, gettext( 228 "chown: user id too large\n")); 229 exit(2); 230 } else { 231 (void) fprintf(stderr, gettext( 232 "chown: invalid user id\n")); 233 exit(2); 234 } 235 } 236 } else { 237 (void) fprintf(stderr, gettext( 238 "chown: unknown user id %s\n"), argv[0]); 239 exit(2); 240 } 241 } 242 243 for (c = 1; c < argc; c++) { 244 tree = NULL; 245 if (lstat(argv[c], &stbuf) < 0) { 246 status += Perror(argv[c]); 247 continue; 248 } 249 if (rflag && ((stbuf.st_mode & S_IFMT) == S_IFLNK)) { 250 if (hflag || Pflag) { 251 /* 252 * Change the ownership of the symlink 253 * specified on the command line. 254 * Don't follow the symbolic link to 255 * any other part of the file hierarchy. 256 */ 257 LCHOWN(argv[c], uid, gid); 258 } else { 259 struct stat stbuf2; 260 if (stat(argv[c], &stbuf2) < 0) { 261 status += Perror(argv[c]); 262 continue; 263 } 264 /* 265 * We know that we are to change the 266 * ownership of the file referenced by the 267 * symlink specified on the command line. 268 * Now check to see if we are to follow 269 * the symlink to any other part of the 270 * file hierarchy. 271 */ 272 if (FOLLOW_CL_LINKS) { 273 if ((stbuf2.st_mode & S_IFMT) 274 == S_IFDIR) { 275 /* 276 * We are following symlinks so 277 * traverse into the directory. 278 * Add this node to the search 279 * tree so we don't get into an 280 * endless loop. 281 */ 282 if (add_tnode(&tree, 283 stbuf2.st_dev, 284 stbuf2.st_ino) == 1) { 285 chownr(argv[c], 286 uid, gid); 287 } else { 288 /* 289 * Error occurred. 290 * rc can't be 0 291 * as this is the first 292 * node to be added to 293 * the search tree. 294 */ 295 status += Perror( 296 argv[c]); 297 } 298 } else { 299 /* 300 * Change the user ID of the 301 * file referenced by the 302 * symlink. 303 */ 304 CHOWN(argv[c], uid, gid); 305 } 306 } else { 307 /* 308 * Change the user ID of the file 309 * referenced by the symbolic link. 310 */ 311 CHOWN(argv[c], uid, gid); 312 } 313 } 314 } else if (rflag && ((stbuf.st_mode & S_IFMT) == S_IFDIR)) { 315 /* 316 * Add this node to the search tree so we don't 317 * get into a endless loop. 318 */ 319 if (add_tnode(&tree, stbuf.st_dev, 320 stbuf.st_ino) == 1) { 321 chownr(argv[c], uid, gid); 322 } else { 323 /* 324 * An error occurred while trying 325 * to add the node to the tree. 326 * Continue on with next file 327 * specified. Note: rc shouldn't 328 * be 0 as this was the first node 329 * being added to the search tree. 330 */ 331 status += Perror(argv[c]); 332 } 333 } else if (hflag || Pflag) { 334 LCHOWN(argv[c], uid, gid); 335 } else { 336 CHOWN(argv[c], uid, gid); 337 } 338 } 339 return (status); 340 } 341 342 /* 343 * chownr() - recursive chown() 344 * 345 * Recursively chowns the input directory then its contents. rflag must 346 * have been set if chownr() is called. The input directory should not 347 * be a sym link (this is handled in the calling routine). In 348 * addition, the calling routine should have already added the input 349 * directory to the search tree so we do not get into endless loops. 350 * Note: chownr() doesn't need a return value as errors are reported 351 * through the global "status" variable. 352 */ 353 static void 354 chownr(char *dir, uid_t uid, gid_t gid) 355 { 356 DIR *dirp; 357 struct dirent *dp; 358 struct stat st, st2; 359 char savedir[1024]; 360 361 if (getcwd(savedir, 1024) == (char *)0) { 362 (void) Perror("getcwd"); 363 exit(255); 364 } 365 366 /* 367 * Attempt to chown the directory, however don't return if we 368 * can't as we still may be able to chown the contents of the 369 * directory. Note: the calling routine resets the SUID bits 370 * on this directory so we don't have to perform an extra 'stat'. 371 */ 372 CHOWN(dir, uid, gid); 373 374 if (chdir(dir) < 0) { 375 status += Perror(dir); 376 return; 377 } 378 if ((dirp = opendir(".")) == NULL) { 379 status += Perror(dir); 380 return; 381 } 382 for (dp = readdir(dirp); dp != NULL; dp = readdir(dirp)) { 383 if (strcmp(dp->d_name, ".") == 0 || /* skip . and .. */ 384 strcmp(dp->d_name, "..") == 0) { 385 continue; 386 } 387 if (lstat(dp->d_name, &st) < 0) { 388 status += Perror(dp->d_name); 389 continue; 390 } 391 if ((st.st_mode & S_IFMT) == S_IFLNK) { 392 if (hflag || Pflag) { 393 /* 394 * Change the ownership of the symbolic link 395 * encountered while traversing the 396 * directory. Don't follow the symbolic 397 * link to any other part of the file 398 * hierarchy. 399 */ 400 LCHOWN(dp->d_name, uid, gid); 401 } else { 402 if (stat(dp->d_name, &st2) < 0) { 403 status += Perror(dp->d_name); 404 continue; 405 } 406 /* 407 * We know that we are to change the 408 * ownership of the file referenced by the 409 * symlink encountered while traversing 410 * the directory. Now check to see if we 411 * are to follow the symlink to any other 412 * part of the file hierarchy. 413 */ 414 if (FOLLOW_D_LINKS) { 415 if ((st2.st_mode & S_IFMT) == S_IFDIR) { 416 /* 417 * We are following symlinks so 418 * traverse into the directory. 419 * Add this node to the search 420 * tree so we don't get into an 421 * endless loop. 422 */ 423 int rc; 424 if ((rc = add_tnode(&tree, 425 st2.st_dev, 426 st2.st_ino)) == 1) { 427 chownr(dp->d_name, 428 uid, gid); 429 } else if (rc == 0) { 430 /* already visited */ 431 continue; 432 } else { 433 /* 434 * An error occurred 435 * while trying to add 436 * the node to the tree. 437 */ 438 status += Perror( 439 dp->d_name); 440 continue; 441 } 442 } else { 443 /* 444 * Change the user id of the 445 * file referenced by the 446 * symbolic link. 447 */ 448 CHOWN(dp->d_name, uid, gid); 449 } 450 } else { 451 /* 452 * Change the user id of the file 453 * referenced by the symbolic link. 454 */ 455 CHOWN(dp->d_name, uid, gid); 456 } 457 } 458 } else if ((st.st_mode & S_IFMT) == S_IFDIR) { 459 /* 460 * Add this node to the search tree so we don't 461 * get into a endless loop. 462 */ 463 int rc; 464 if ((rc = add_tnode(&tree, st.st_dev, 465 st.st_ino)) == 1) { 466 chownr(dp->d_name, uid, gid); 467 } else if (rc == 0) { 468 /* already visited */ 469 continue; 470 } else { 471 /* 472 * An error occurred while trying 473 * to add the node to the search tree. 474 */ 475 status += Perror(dp->d_name); 476 continue; 477 } 478 } else { 479 CHOWN(dp->d_name, uid, gid); 480 } 481 } 482 483 (void) closedir(dirp); 484 if (chdir(savedir) < 0) { 485 (void) fprintf(stderr, gettext( 486 "chown: can't change back to %s\n"), savedir); 487 exit(255); 488 } 489 } 490 491 static int 492 isnumber(char *s) 493 { 494 int c; 495 496 while ((c = *s++) != '\0') 497 if (!isdigit(c)) 498 return (0); 499 return (1); 500 } 501 502 static int 503 Perror(char *s) 504 { 505 if (!fflag) { 506 (void) fprintf(stderr, "chown: "); 507 perror(s); 508 } 509 return (!fflag); 510 } 511 512 static void 513 usage() 514 { 515 (void) fprintf(stderr, gettext( 516 "usage:\n" 517 "\tchown [-fhR] owner[:group] file...\n" 518 "\tchown -R [-f] [-H|-L|-P] owner[:group] file...\n")); 519 exit(2); 520 } 521