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