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