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, Version 1.0 only 6 * (the "License"). You may not use this file except in compliance 7 * with the License. 8 * 9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 10 * or http://www.opensolaris.org/os/licensing. 11 * See the License for the specific language governing permissions 12 * and limitations under the License. 13 * 14 * When distributing Covered Code, include this CDDL HEADER in each 15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 16 * If applicable, add the following below this CDDL HEADER, with the 17 * fields enclosed by brackets "[]" replaced with your own identifying 18 * information: Portions Copyright [yyyy] [name of copyright owner] 19 * 20 * CDDL HEADER END 21 */ 22 /* 23 * Copyright 2005 Sun Microsystems, Inc. All rights reserved. 24 * Use is subject to license terms. 25 */ 26 27 /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ 28 /* All Rights Reserved */ 29 /* */ 30 31 /* 32 * University Copyright- Copyright (c) 1982, 1986, 1988 33 * The Regents of the University of California 34 * All Rights Reserved 35 * 36 * University Acknowledgment- Portions of this document are derived from 37 * software developed by the University of California, Berkeley, and its 38 * contributors. 39 */ 40 41 #pragma ident "%Z%%M% %I% %E% SMI" 42 43 /* 44 * chmod option mode files 45 * where 46 * mode is [ugoa][+-=][rwxXlstugo] or an octal number 47 * mode is [<+|->A[# <number] ]<aclspec> 48 * option is -R and -f 49 */ 50 51 /* 52 * Note that many convolutions are necessary 53 * due to the re-use of bits between locking 54 * and setgid 55 */ 56 57 #include <unistd.h> 58 #include <stdlib.h> 59 #include <stdio.h> 60 #include <sys/types.h> 61 #include <sys/stat.h> 62 #include <dirent.h> 63 #include <locale.h> 64 #include <string.h> /* strerror() */ 65 #include <stdarg.h> 66 #include <limits.h> 67 #include <ctype.h> 68 #include <errno.h> 69 #include <sys/acl.h> 70 #include <aclutils.h> 71 72 static int rflag; 73 static int fflag; 74 75 extern int optind; 76 extern int errno; 77 78 static int mac; /* Alternate to argc (for parseargs) */ 79 static char **mav; /* Alternate to argv (for parseargs) */ 80 81 static char *ms; /* Points to the mode argument */ 82 83 #define ACL_ADD 1 84 #define ACL_DELETE 2 85 #define ACL_SLOT_DELETE 3 86 #define ACL_REPLACE 4 87 #define ACL_STRIP 5 88 89 typedef struct acl_args { 90 acl_t *acl_aclp; 91 int acl_slot; 92 int acl_action; 93 } acl_args_t; 94 95 extern mode_t 96 newmode_common(char *ms, mode_t new_mode, mode_t umsk, char *file, char *path, 97 o_mode_t *group_clear_bits, o_mode_t *group_set_bits); 98 99 static int 100 dochmod(char *name, char *path, mode_t umsk, acl_args_t *aclp), 101 chmodr(char *dir, char *path, mode_t mode, mode_t umsk, acl_args_t *aclp); 102 static int doacl(char *file, struct stat *st, acl_args_t *aclp); 103 104 static void handle_acl(char *name, o_mode_t group_clear_bits, 105 o_mode_t group_set_bits); 106 107 static void usage(void); 108 109 void errmsg(int severity, int code, char *format, ...); 110 111 static void parseargs(int ac, char *av[]); 112 113 int 114 parse_acl_args(char *arg, acl_args_t **acl_args); 115 116 int 117 main(int argc, char *argv[]) 118 { 119 int i, c; 120 int status = 0; 121 mode_t umsk; 122 acl_args_t *acl_args = NULL; 123 124 (void) setlocale(LC_ALL, ""); 125 #if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */ 126 #define TEXT_DOMAIN "SYS_TEST" /* Use this only if it weren't */ 127 #endif 128 (void) textdomain(TEXT_DOMAIN); 129 130 parseargs(argc, argv); 131 132 while ((c = getopt(mac, mav, "Rf")) != EOF) { 133 switch (c) { 134 case 'R': 135 rflag++; 136 break; 137 case 'f': 138 fflag++; 139 break; 140 case '?': 141 usage(); 142 exit(2); 143 } 144 } 145 146 /* 147 * Check for sufficient arguments 148 * or a usage error. 149 */ 150 151 mac -= optind; 152 mav += optind; 153 154 if (mac >= 2 && (mav[0][0] == 'A')) { 155 if (parse_acl_args(*mav, &acl_args)) { 156 usage(); 157 exit(2); 158 } 159 } else { 160 if (mac < 2) { 161 usage(); 162 exit(2); 163 } 164 } 165 166 ms = mav[0]; 167 168 umsk = umask(0); 169 (void) umask(umsk); 170 171 for (i = 1; i < mac; i++) { 172 status += dochmod(mav[i], mav[i], umsk, acl_args); 173 } 174 175 return (fflag ? 0 : status); 176 } 177 178 static int 179 dochmod(char *name, char *path, mode_t umsk, acl_args_t *aclp) 180 { 181 static struct stat st; 182 int linkflg = 0; 183 o_mode_t group_clear_bits, group_set_bits; 184 185 if (lstat(name, &st) < 0) { 186 errmsg(2, 0, gettext("can't access %s\n"), path); 187 return (1); 188 } 189 190 if ((st.st_mode & S_IFMT) == S_IFLNK) { 191 linkflg = 1; 192 if (stat(name, &st) < 0) { 193 errmsg(2, 0, gettext("can't access %s\n"), path); 194 return (1); 195 } 196 } 197 198 /* Do not recurse if directory is object of symbolic link */ 199 if (rflag && ((st.st_mode & S_IFMT) == S_IFDIR) && !linkflg) 200 return (chmodr(name, path, st.st_mode, umsk, aclp)); 201 202 if (aclp) { 203 return (doacl(name, &st, aclp)); 204 } else if (chmod(name, newmode_common(ms, st.st_mode, umsk, name, path, 205 &group_clear_bits, &group_set_bits)) == -1) { 206 errmsg(2, 0, gettext("can't change %s\n"), path); 207 return (1); 208 } 209 210 /* 211 * If the group permissions of the file are being modified, 212 * make sure that the file's ACL (if it has one) is 213 * modified also, since chmod is supposed to apply group 214 * permissions changes to both the acl mask and the 215 * general group permissions. 216 */ 217 if (group_clear_bits || group_set_bits) 218 handle_acl(name, group_clear_bits, group_set_bits); 219 220 return (0); 221 } 222 223 224 static int 225 chmodr(char *dir, char *path, mode_t mode, mode_t umsk, acl_args_t *aclp) 226 { 227 228 DIR *dirp; 229 struct dirent *dp; 230 char savedir[PATH_MAX]; /* dir name to restore */ 231 char currdir[PATH_MAX+1]; /* current dir name + '/' */ 232 char parentdir[PATH_MAX+1]; /* parent dir name + '/' */ 233 int ecode; 234 struct stat st; 235 o_mode_t group_clear_bits, group_set_bits; 236 237 if (getcwd(savedir, PATH_MAX) == 0) 238 errmsg(2, 255, gettext("chmod: could not getcwd %s\n"), 239 savedir); 240 241 /* 242 * Change what we are given before doing it's contents 243 */ 244 if (aclp) { 245 if (lstat(dir, &st) < 0) { 246 errmsg(2, 0, gettext("can't access %s\n"), path); 247 return (1); 248 } 249 if (doacl(dir, &st, aclp) != 0) 250 return (1); 251 } else if (chmod(dir, newmode_common(ms, mode, umsk, dir, path, 252 &group_clear_bits, &group_set_bits)) < 0) { 253 errmsg(2, 0, gettext("can't change %s\n"), path); 254 return (1); 255 } 256 257 /* 258 * If the group permissions of the file are being modified, 259 * make sure that the file's ACL (if it has one) is 260 * modified also, since chmod is supposed to apply group 261 * permissions changes to both the acl mask and the 262 * general group permissions. 263 */ 264 265 if (aclp == NULL) { /* only necessary when not setting ACL */ 266 if (group_clear_bits || group_set_bits) 267 handle_acl(dir, group_clear_bits, group_set_bits); 268 } 269 270 if (chdir(dir) < 0) { 271 errmsg(2, 0, "%s/%s: %s\n", savedir, dir, strerror(errno)); 272 return (1); 273 } 274 if ((dirp = opendir(".")) == NULL) { 275 errmsg(2, 0, "%s\n", strerror(errno)); 276 return (1); 277 } 278 dp = readdir(dirp); 279 dp = readdir(dirp); /* read "." and ".." */ 280 ecode = 0; 281 282 /* 283 * Save parent directory path before recursive chmod. 284 * We'll need this for error printing purposes. Add 285 * a trailing '/' to the path except in the case where 286 * the path is just '/' 287 */ 288 289 (void) strcpy(parentdir, path); 290 if (strcmp(path, "/") != 0) 291 (void) strcat(parentdir, "/"); 292 293 for (dp = readdir(dirp); dp != NULL; dp = readdir(dirp)) { 294 (void) strcpy(currdir, parentdir); 295 (void) strcat(currdir, dp->d_name); 296 ecode += dochmod(dp->d_name, currdir, umsk, aclp); 297 } 298 (void) closedir(dirp); 299 if (chdir(savedir) < 0) { 300 errmsg(2, 255, gettext("can't change back to %s\n"), savedir); 301 } 302 return (ecode ? 1 : 0); 303 } 304 305 /* PRINTFLIKE3 */ 306 void 307 errmsg(int severity, int code, char *format, ...) 308 { 309 va_list ap; 310 static char *msg[] = { 311 "", 312 "ERROR", 313 "WARNING", 314 "" 315 }; 316 317 va_start(ap, format); 318 319 /* 320 * Always print error message if this is a fatal error (code == 0); 321 * otherwise, print message if fflag == 0 (no -f option specified) 322 */ 323 if (!fflag || (code != 0)) { 324 (void) fprintf(stderr, 325 "chmod: %s: ", gettext(msg[severity])); 326 (void) vfprintf(stderr, format, ap); 327 } 328 329 va_end(ap); 330 331 if (code != 0) 332 exit(fflag ? 0 : code); 333 } 334 335 static void 336 usage(void) 337 { 338 (void) fprintf(stderr, gettext( 339 "usage:\tchmod [-fR] <absolute-mode> file ...\n")); 340 341 (void) fprintf(stderr, gettext( 342 "\tchmod [-fR] <ACL-operation> file ...\n")); 343 344 (void) fprintf(stderr, gettext( 345 "\tchmod [-fR] <symbolic-mode-list> file ...\n")); 346 347 348 (void) fprintf(stderr, gettext( 349 "where \t<symbolic-mode-list> is a comma-separated list of\n")); 350 351 (void) fprintf(stderr, gettext( 352 "\t[ugoa]{+|-|=}[rwxXlstugo]\n")); 353 354 (void) fprintf(stderr, gettext( 355 "where \t<ACL-operation> is one of the following\n")); 356 (void) fprintf(stderr, gettext("\tA-<acl_specification>\n")); 357 (void) fprintf(stderr, gettext("\tA[number]-\n")); 358 (void) fprintf(stderr, gettext( 359 "\tA[number]{+|=}<acl_specification>\n")); 360 (void) fprintf(stderr, gettext( 361 "where \t<acl-specification> is a comma-separated list of ACEs\n")); 362 } 363 364 /* 365 * parseargs - generate getopt-friendly argument list for backwards 366 * compatibility with earlier Solaris usage (eg, chmod -w 367 * foo). 368 * 369 * assumes the existence of a static set of alternates to argc and argv, 370 * (namely, mac, and mav[]). 371 * 372 */ 373 374 static void 375 parseargs(int ac, char *av[]) 376 { 377 int i; /* current argument */ 378 int fflag; /* arg list contains "--" */ 379 size_t mav_num; /* number of entries in mav[] */ 380 381 /* 382 * We add an extra argument slot, in case we need to jam a "--" 383 * argument into the list. 384 */ 385 386 mav_num = (size_t)ac+2; 387 388 if ((mav = calloc(mav_num, sizeof (char *))) == NULL) { 389 perror("chmod"); 390 exit(2); 391 } 392 393 /* scan for the use of "--" in the argument list */ 394 395 for (fflag = i = 0; i < ac; i ++) { 396 if (strcmp(av[i], "--") == 0) 397 fflag = 1; 398 } 399 400 /* process the arguments */ 401 402 for (i = mac = 0; 403 (av[i] != (char *)NULL) && (av[i][0] != (char)NULL); 404 i++) { 405 if (!fflag && av[i][0] == '-') { 406 /* 407 * If there is not already a "--" argument specified, 408 * and the argument starts with '-' but does not 409 * contain any of the official option letters, then it 410 * is probably a mode argument beginning with '-'. 411 * Force a "--" into the argument stream in front of 412 * it. 413 */ 414 415 if ((strchr(av[i], 'R') == NULL && 416 strchr(av[i], 'f') == NULL)) { 417 mav[mac++] = strdup("--"); 418 } 419 } 420 421 mav[mac++] = strdup(av[i]); 422 } 423 424 mav[mac] = (char *)NULL; 425 } 426 427 int 428 parse_acl_args(char *arg, acl_args_t **acl_args) 429 { 430 acl_t *new_acl = NULL; 431 int slot; 432 int error; 433 int len; 434 int action; 435 acl_args_t *new_acl_args; 436 char *acl_spec = NULL; 437 char *end; 438 439 if (arg[0] != 'A') 440 return (1); 441 442 slot = strtol(&arg[1], &end, 10); 443 444 len = strlen(arg); 445 switch (*end) { 446 case '+': 447 action = ACL_ADD; 448 acl_spec = ++end; 449 break; 450 case '-': 451 if (len == 2 && arg[0] == 'A' && arg[1] == '-') 452 action = ACL_STRIP; 453 else 454 action = ACL_DELETE; 455 if (action != ACL_STRIP) { 456 acl_spec = ++end; 457 if (acl_spec[0] == '\0') { 458 action = ACL_SLOT_DELETE; 459 acl_spec = NULL; 460 } else if (arg[1] != '-') 461 return (1); 462 } 463 break; 464 case '=': 465 /* 466 * Was slot specified? 467 */ 468 if (arg[1] == '=') 469 slot = -1; 470 action = ACL_REPLACE; 471 acl_spec = ++end; 472 break; 473 default: 474 return (1); 475 } 476 477 if ((action == ACL_REPLACE || action == ACL_ADD) && acl_spec[0] == '\0') 478 return (1); 479 480 if (acl_spec) { 481 if (error = acl_fromtext(acl_spec, &new_acl)) { 482 errmsg(1, 1, "%s\n", acl_strerror(error)); 483 return (1); 484 } 485 } 486 487 new_acl_args = malloc(sizeof (acl_args_t)); 488 if (new_acl_args == NULL) 489 return (1); 490 491 new_acl_args->acl_aclp = new_acl; 492 new_acl_args->acl_slot = slot; 493 new_acl_args->acl_action = action; 494 495 *acl_args = new_acl_args; 496 497 return (0); 498 } 499 500 /* 501 * This function is called whenever the group permissions of a file 502 * is being modified. According to the chmod(1) manpage, any 503 * change made to the group permissions must be applied to both 504 * the acl mask and the acl's GROUP_OBJ. The chmod(2) already 505 * set the mask, so this routine needs to make the same change 506 * to the GROUP_OBJ. 507 */ 508 static void 509 handle_acl(char *name, o_mode_t group_clear_bits, o_mode_t group_set_bits) 510 { 511 int aclcnt, n; 512 aclent_t *aclp, *tp; 513 o_mode_t newperm; 514 515 /* 516 * if this file system support ace_t acl's 517 * then simply return since we don't have an 518 * acl mask to deal with 519 */ 520 if (pathconf(name, _PC_ACL_ENABLED) == _ACL_ACE_ENABLED) 521 return; 522 523 if ((aclcnt = acl(name, GETACLCNT, 0, NULL)) <= MIN_ACL_ENTRIES) 524 return; /* it's just a trivial acl; no need to change it */ 525 526 if ((aclp = (aclent_t *)malloc((sizeof (aclent_t)) * aclcnt)) 527 == NULL) { 528 perror("chmod"); 529 exit(2); 530 } 531 532 if (acl(name, GETACL, aclcnt, aclp) < 0) { 533 free(aclp); 534 (void) fprintf(stderr, "chmod: "); 535 perror(name); 536 return; 537 } 538 539 for (tp = aclp, n = aclcnt; n--; tp++) { 540 if (tp->a_type == GROUP_OBJ) { 541 newperm = tp->a_perm; 542 if (group_clear_bits != 0) 543 newperm &= ~group_clear_bits; 544 if (group_set_bits != 0) 545 newperm |= group_set_bits; 546 if (newperm != tp->a_perm) { 547 tp->a_perm = newperm; 548 if (acl(name, SETACL, aclcnt, aclp) 549 < 0) { 550 (void) fprintf(stderr, "chmod: "); 551 perror(name); 552 } 553 } 554 break; 555 } 556 } 557 free(aclp); 558 } 559 560 static int 561 doacl(char *file, struct stat *st, acl_args_t *acl_args) 562 { 563 acl_t *aclp; 564 acl_t *set_aclp; 565 int error = 0; 566 void *to, *from; 567 int len; 568 int isdir; 569 570 isdir = S_ISDIR(st->st_mode); 571 572 error = acl_get(file, 0, &aclp); 573 574 if (error != 0) { 575 errmsg(1, 1, "%s\n", acl_strerror(error)); 576 return (1); 577 } 578 579 switch (acl_args->acl_action) { 580 case ACL_ADD: 581 if ((error = acl_addentries(aclp, 582 acl_args->acl_aclp, acl_args->acl_slot)) != 0) { 583 errmsg(1, 1, "%s\n", acl_strerror(error)); 584 acl_free(aclp); 585 return (1); 586 } 587 set_aclp = aclp; 588 break; 589 case ACL_SLOT_DELETE: 590 591 if (acl_args->acl_slot + 1 > aclp->acl_cnt) { 592 errmsg(1, 1, 593 gettext("Invalid slot specified for removal\n")); 594 acl_free(aclp); 595 return (1); 596 } 597 598 if (acl_args->acl_slot == 0 && aclp->acl_cnt == 1) { 599 errmsg(1, 1, 600 gettext("Can't remove all ACL " 601 "entries from a file\n")); 602 acl_free(aclp); 603 return (1); 604 } 605 606 /* 607 * remove a single entry 608 * 609 * if last entry just adjust acl_cnt 610 */ 611 612 if ((acl_args->acl_slot + 1) == aclp->acl_cnt) 613 aclp->acl_cnt--; 614 else { 615 to = (char *)aclp->acl_aclp + 616 (acl_args->acl_slot * aclp->acl_entry_size); 617 from = (char *)to + aclp->acl_entry_size; 618 len = (aclp->acl_cnt - acl_args->acl_slot - 1) * 619 aclp->acl_entry_size; 620 (void) memmove(to, from, len); 621 aclp->acl_cnt--; 622 } 623 set_aclp = aclp; 624 break; 625 626 case ACL_DELETE: 627 if ((error = acl_removeentries(aclp, acl_args->acl_aclp, 628 acl_args->acl_slot, ACL_REMOVE_ALL)) != 0) { 629 errmsg(1, 1, "%s\n", acl_strerror(error)); 630 acl_free(aclp); 631 return (1); 632 } 633 634 if (aclp->acl_cnt == 0) { 635 errmsg(1, 1, 636 gettext("Can't remove all ACL " 637 "entries from a file\n")); 638 acl_free(aclp); 639 return (1); 640 } 641 642 set_aclp = aclp; 643 break; 644 case ACL_REPLACE: 645 if (acl_args->acl_slot >= 0) { 646 error = acl_modifyentries(aclp, acl_args->acl_aclp, 647 acl_args->acl_slot); 648 if (error) { 649 errmsg(1, 1, "%s\n", acl_strerror(error)); 650 acl_free(aclp); 651 return (1); 652 } 653 set_aclp = aclp; 654 } else { 655 set_aclp = acl_args->acl_aclp; 656 } 657 break; 658 case ACL_STRIP: 659 error = acl_strip(file, st->st_uid, st->st_gid, st->st_mode); 660 if (error) { 661 errmsg(1, 1, "%s\n", acl_strerror(error)); 662 return (1); 663 } 664 acl_free(aclp); 665 return (0); 666 /*NOTREACHED*/ 667 default: 668 errmsg(1, 0, gettext("Unknown ACL action requested\n")); 669 return (1); 670 break; 671 } 672 673 error = acl_check(set_aclp, isdir); 674 675 if (error) { 676 errmsg(1, 0, "%s\n%s", acl_strerror(error), 677 gettext("See chmod(1) for more information on " 678 "valid ACL syntax\n")); 679 return (1); 680 } 681 if ((error = acl_set(file, set_aclp)) != 0) { 682 errmsg(1, 0, gettext("Failed to set ACL: %s\n"), 683 acl_strerror(error)); 684 acl_free(aclp); 685 return (1); 686 } 687 acl_free(aclp); 688 return (0); 689 } 690