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