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 (c) 1988, 2010, Oracle and/or its affiliates. All rights reserved. 23 * Copyright 2011 Nexenta Systems, Inc. All rights reserved. 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 /* 41 * chmod option mode files 42 * where 43 * mode is [ugoa][+-=][rwxXlstugo] or an octal number 44 * mode is [<+|->A[# <number] ]<aclspec> 45 * mode is S<attrspec> 46 * option is -R, -f, and -@ 47 */ 48 49 /* 50 * Note that many convolutions are necessary 51 * due to the re-use of bits between locking 52 * and setgid 53 */ 54 55 #include <unistd.h> 56 #include <stdlib.h> 57 #include <stdio.h> 58 #include <sys/types.h> 59 #include <sys/stat.h> 60 #include <fcntl.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 #include <libnvpair.h> 71 #include <libcmdutils.h> 72 #include <libgen.h> 73 #include <attr.h> 74 75 static int rflag; 76 static int fflag; 77 78 extern int optind; 79 extern int errno; 80 81 static int mac; /* Alternate to argc (for parseargs) */ 82 static char **mav; /* Alternate to argv (for parseargs) */ 83 84 static char *ms; /* Points to the mode argument */ 85 86 #define ACL_ADD 1 87 #define ACL_DELETE 2 88 #define ACL_SLOT_DELETE 3 89 #define ACL_REPLACE 4 90 #define ACL_STRIP 5 91 92 #define LEFTBRACE '{' 93 #define RIGHTBRACE '}' 94 #define A_SEP ',' 95 #define A_SEP_TOK "," 96 97 #define A_COMPACT_TYPE 'c' 98 #define A_VERBOSE_TYPE 'v' 99 #define A_ALLATTRS_TYPE 'a' 100 101 #define A_SET_OP '+' 102 #define A_INVERSE_OP '-' 103 #define A_REPLACE_OP '=' 104 #define A_UNDEF_OP '\0' 105 106 #define A_SET_TEXT "set" 107 #define A_INVERSE_TEXT "clear" 108 109 #define A_SET_VAL B_TRUE 110 #define A_CLEAR_VAL B_FALSE 111 112 #define ATTR_OPTS 0 113 #define ATTR_NAMES 1 114 115 #define sec_acls secptr.acls 116 #define sec_attrs secptr.attrs 117 118 typedef struct acl_args { 119 acl_t *acl_aclp; 120 int acl_slot; 121 int acl_action; 122 } acl_args_t; 123 124 typedef enum { 125 SEC_ACL, 126 SEC_ATTR 127 } chmod_sec_t; 128 129 typedef struct { 130 chmod_sec_t sec_type; 131 union { 132 acl_args_t *acls; 133 nvlist_t *attrs; 134 } secptr; 135 } sec_args_t; 136 137 typedef struct attr_name { 138 char *name; 139 struct attr_name *next; 140 } attr_name_t; 141 142 143 extern mode_t newmode_common(char *ms, mode_t new_mode, mode_t umsk, 144 char *file, char *path, o_mode_t *group_clear_bits, 145 o_mode_t *group_set_bits); 146 147 static int chmodr(char *dir, char *path, mode_t mode, mode_t umsk, 148 sec_args_t *secp, attr_name_t *attrname); 149 static int doacl(char *file, struct stat *st, acl_args_t *aclp); 150 static int dochmod(char *name, char *path, mode_t umsk, sec_args_t *secp, 151 attr_name_t *attrnames); 152 static void handle_acl(char *name, o_mode_t group_clear_bits, 153 o_mode_t group_set_bits); 154 void errmsg(int severity, int code, char *format, ...); 155 static void free_attr_names(attr_name_t *attrnames); 156 static void parseargs(int ac, char *av[]); 157 static int parse_acl_args(char *arg, sec_args_t **sec_args); 158 static int parse_attr_args(char *arg, sec_args_t **sec_args); 159 static void print_attrs(int flag); 160 static int set_attrs(char *file, attr_name_t *attrnames, nvlist_t *attr_nvlist); 161 static void usage(void); 162 163 int 164 main(int argc, char *argv[]) 165 { 166 int i, c; 167 int status = 0; 168 mode_t umsk; 169 sec_args_t *sec_args = NULL; 170 attr_name_t *attrnames = NULL; 171 attr_name_t *attrend = NULL; 172 attr_name_t *tattr; 173 174 (void) setlocale(LC_ALL, ""); 175 #if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */ 176 #define TEXT_DOMAIN "SYS_TEST" /* Use this only if it weren't */ 177 #endif 178 (void) textdomain(TEXT_DOMAIN); 179 180 parseargs(argc, argv); 181 182 while ((c = getopt(mac, mav, "Rf@:")) != EOF) { 183 switch (c) { 184 case 'R': 185 rflag++; 186 break; 187 case 'f': 188 fflag++; 189 break; 190 case '@': 191 if (((tattr = malloc(sizeof (attr_name_t))) == NULL) || 192 ((tattr->name = strdup(optarg)) == NULL)) { 193 perror("chmod"); 194 exit(2); 195 } 196 if (attrnames == NULL) { 197 attrnames = tattr; 198 attrnames->next = NULL; 199 } else { 200 attrend->next = tattr; 201 } 202 attrend = tattr; 203 break; 204 case '?': 205 usage(); 206 exit(2); 207 } 208 } 209 210 /* 211 * Check for sufficient arguments 212 * or a usage error. 213 */ 214 215 mac -= optind; 216 mav += optind; 217 if ((mac >= 2) && (mav[0][0] == 'A')) { 218 if (attrnames != NULL) { 219 free_attr_names(attrnames); 220 attrnames = NULL; 221 } 222 if (parse_acl_args(*mav, &sec_args)) { 223 usage(); 224 exit(2); 225 } 226 } else if ((mac >= 2) && (mav[0][0] == 'S')) { 227 if (parse_attr_args(*mav, &sec_args)) { 228 usage(); 229 exit(2); 230 231 /* A no-op attribute operation was specified. */ 232 } else if (sec_args->sec_attrs == NULL) { 233 exit(0); 234 } 235 } else { 236 if (mac < 2) { 237 usage(); 238 exit(2); 239 } 240 if (attrnames != NULL) { 241 free_attr_names(attrnames); 242 attrnames = NULL; 243 } 244 } 245 246 ms = mav[0]; 247 248 umsk = umask(0); 249 (void) umask(umsk); 250 251 for (i = 1; i < mac; i++) { 252 status += dochmod(mav[i], mav[i], umsk, sec_args, attrnames); 253 } 254 255 return (fflag ? 0 : status); 256 } 257 258 static void 259 free_attr_names(attr_name_t *attrnames) 260 { 261 attr_name_t *attrnamesptr = attrnames; 262 attr_name_t *tptr; 263 264 while (attrnamesptr != NULL) { 265 tptr = attrnamesptr->next; 266 if (attrnamesptr->name != NULL) { 267 free(attrnamesptr->name); 268 } 269 attrnamesptr = tptr; 270 } 271 } 272 273 static int 274 dochmod(char *name, char *path, mode_t umsk, sec_args_t *secp, 275 attr_name_t *attrnames) 276 { 277 static struct stat st; 278 int linkflg = 0; 279 o_mode_t group_clear_bits, group_set_bits; 280 281 if (lstat(name, &st) < 0) { 282 errmsg(2, 0, gettext("can't access %s\n"), path); 283 return (1); 284 } 285 286 if ((st.st_mode & S_IFMT) == S_IFLNK) { 287 linkflg = 1; 288 if (stat(name, &st) < 0) { 289 errmsg(2, 0, gettext("can't access %s\n"), path); 290 return (1); 291 } 292 } 293 294 /* Do not recurse if directory is object of symbolic link */ 295 if (rflag && ((st.st_mode & S_IFMT) == S_IFDIR) && !linkflg) { 296 return (chmodr(name, path, st.st_mode, umsk, secp, attrnames)); 297 } 298 299 if (secp != NULL) { 300 if (secp->sec_type == SEC_ACL) { 301 return (doacl(name, &st, secp->sec_acls)); 302 } else if (secp->sec_type == SEC_ATTR) { 303 return (set_attrs(name, attrnames, secp->sec_attrs)); 304 } else { 305 return (1); 306 } 307 } else { 308 if (chmod(name, newmode_common(ms, st.st_mode, umsk, name, path, 309 &group_clear_bits, &group_set_bits)) == -1) { 310 errmsg(2, 0, gettext("can't change %s\n"), path); 311 return (1); 312 } 313 } 314 315 /* 316 * If the group permissions of the file are being modified, 317 * make sure that the file's ACL (if it has one) is 318 * modified also, since chmod is supposed to apply group 319 * permissions changes to both the acl mask and the 320 * general group permissions. 321 */ 322 if (group_clear_bits || group_set_bits) 323 handle_acl(name, group_clear_bits, group_set_bits); 324 325 return (0); 326 } 327 328 static int 329 chmodr(char *dir, char *path, mode_t mode, mode_t umsk, sec_args_t *secp, 330 attr_name_t *attrnames) 331 { 332 333 DIR *dirp; 334 struct dirent *dp; 335 char savedir[PATH_MAX]; /* dir name to restore */ 336 char currdir[PATH_MAX+1]; /* current dir name + '/' */ 337 char parentdir[PATH_MAX+1]; /* parent dir name + '/' */ 338 int ecode; 339 struct stat st; 340 o_mode_t group_clear_bits, group_set_bits; 341 342 if (getcwd(savedir, PATH_MAX) == 0) 343 errmsg(2, 255, gettext("chmod: could not getcwd %s\n"), 344 savedir); 345 346 /* 347 * Change what we are given before doing it's contents 348 */ 349 if (secp != NULL) { 350 if (lstat(dir, &st) < 0) { 351 errmsg(2, 0, gettext("can't access %s\n"), path); 352 return (1); 353 } 354 if (secp->sec_type == SEC_ACL) { 355 (void) doacl(dir, &st, secp->sec_acls); 356 } else if (secp->sec_type == SEC_ATTR) { 357 (void) set_attrs(dir, attrnames, secp->sec_attrs); 358 } else { 359 return (1); 360 } 361 } else if (chmod(dir, newmode_common(ms, mode, umsk, dir, path, 362 &group_clear_bits, &group_set_bits)) < 0) { 363 errmsg(2, 0, gettext("can't change %s\n"), path); 364 } 365 366 /* 367 * If the group permissions of the file are being modified, 368 * make sure that the file's ACL (if it has one) is 369 * modified also, since chmod is supposed to apply group 370 * permissions changes to both the acl mask and the 371 * general group permissions. 372 */ 373 374 if (secp != NULL) { 375 /* only necessary when not setting ACL or system attributes */ 376 if (group_clear_bits || group_set_bits) 377 handle_acl(dir, group_clear_bits, group_set_bits); 378 } 379 380 if (chdir(dir) < 0) { 381 errmsg(2, 0, "%s/%s: %s\n", savedir, dir, strerror(errno)); 382 return (1); 383 } 384 if ((dirp = opendir(".")) == NULL) { 385 errmsg(2, 0, "%s\n", strerror(errno)); 386 return (1); 387 } 388 ecode = 0; 389 390 /* 391 * Save parent directory path before recursive chmod. 392 * We'll need this for error printing purposes. Add 393 * a trailing '/' to the path except in the case where 394 * the path is just '/' 395 */ 396 397 if (strlcpy(parentdir, path, PATH_MAX + 1) >= PATH_MAX + 1) { 398 errmsg(2, 0, gettext("directory path name too long: %s\n"), 399 path); 400 return (1); 401 } 402 if (strcmp(path, "/") != 0) 403 if (strlcat(parentdir, "/", PATH_MAX + 1) >= PATH_MAX + 1) { 404 errmsg(2, 0, 405 gettext("directory path name too long: %s/\n"), 406 parentdir); 407 return (1); 408 } 409 410 411 for (dp = readdir(dirp); dp != NULL; dp = readdir(dirp)) { 412 413 if (strcmp(dp->d_name, ".") == 0 || /* skip . and .. */ 414 strcmp(dp->d_name, "..") == 0) { 415 continue; 416 } 417 if (strlcpy(currdir, parentdir, PATH_MAX + 1) >= PATH_MAX + 1) { 418 errmsg(2, 0, 419 gettext("directory path name too long: %s\n"), 420 parentdir); 421 return (1); 422 } 423 if (strlcat(currdir, dp->d_name, PATH_MAX + 1) 424 >= PATH_MAX + 1) { 425 errmsg(2, 0, 426 gettext("directory path name too long: %s%s\n"), 427 currdir, dp->d_name); 428 return (1); 429 } 430 ecode += dochmod(dp->d_name, currdir, umsk, secp, attrnames); 431 } 432 (void) closedir(dirp); 433 if (chdir(savedir) < 0) { 434 errmsg(2, 255, gettext("can't change back to %s\n"), savedir); 435 } 436 return (ecode ? 1 : 0); 437 } 438 439 /* PRINTFLIKE3 */ 440 void 441 errmsg(int severity, int code, char *format, ...) 442 { 443 va_list ap; 444 static char *msg[] = { 445 "", 446 "ERROR", 447 "WARNING", 448 "" 449 }; 450 451 va_start(ap, format); 452 453 /* 454 * Always print error message if this is a fatal error (code != 0); 455 * otherwise, print message if fflag == 0 (no -f option specified) 456 */ 457 if (!fflag || (code != 0)) { 458 (void) fprintf(stderr, 459 "chmod: %s: ", gettext(msg[severity])); 460 (void) vfprintf(stderr, format, ap); 461 } 462 463 va_end(ap); 464 465 if (code != 0) 466 exit(fflag ? 0 : code); 467 } 468 469 static void 470 usage(void) 471 { 472 (void) fprintf(stderr, gettext( 473 "usage:\tchmod [-fR] <absolute-mode> file ...\n")); 474 475 (void) fprintf(stderr, gettext( 476 "\tchmod [-fR] [-@ attribute] ... " 477 "S<attribute-operation> file ...\n")); 478 479 (void) fprintf(stderr, gettext( 480 "\tchmod [-fR] <ACL-operation> file ...\n")); 481 482 (void) fprintf(stderr, gettext( 483 "\tchmod [-fR] <symbolic-mode-list> file ...\n\n")); 484 485 (void) fprintf(stderr, gettext( 486 "where \t<symbolic-mode-list> is a comma-separated list of\n")); 487 (void) fprintf(stderr, gettext( 488 "\t[ugoa]{+|-|=}[rwxXlstugo]\n\n")); 489 490 (void) fprintf(stderr, gettext( 491 "where \t<attribute-operation> is a comma-separated list of\n" 492 "\tone or more of the following\n")); 493 (void) fprintf(stderr, gettext( 494 "\t[+|-|=]c[<compact-attribute-list>|{<compact-attribute-list>}]\n" 495 "\t[+|-|=]v[<verbose-attribute-setting>|" 496 "\'{\'<verbose-attribute-setting-list>\'}\']\n" 497 "\t[+|-|=]a\n")); 498 (void) fprintf(stderr, gettext( 499 "where \t<compact-attribute-list> is a list of zero or more of\n")); 500 print_attrs(ATTR_OPTS); 501 (void) fprintf(stderr, gettext( 502 "where \t<verbose-attribute-setting> is one of\n")); 503 print_attrs(ATTR_NAMES); 504 (void) fprintf(stderr, gettext( 505 "\tand can be, optionally, immediately preceded by \"no\"\n\n")); 506 507 (void) fprintf(stderr, gettext( 508 "where \t<ACL-operation> is one of the following\n")); 509 (void) fprintf(stderr, gettext("\tA-<acl_specification>\n")); 510 (void) fprintf(stderr, gettext("\tA[number]-\n")); 511 (void) fprintf(stderr, gettext( 512 "\tA[number]{+|=}<acl_specification>\n")); 513 (void) fprintf(stderr, gettext( 514 "where \t<acl-specification> is a comma-separated list of ACEs\n")); 515 } 516 517 /* 518 * parseargs - generate getopt-friendly argument list for backwards 519 * compatibility with earlier Solaris usage (eg, chmod -w 520 * foo). 521 * 522 * assumes the existence of a static set of alternates to argc and argv, 523 * (namely, mac, and mav[]). 524 * 525 */ 526 527 static void 528 parseargs(int ac, char *av[]) 529 { 530 int i; /* current argument */ 531 int fflag; /* arg list contains "--" */ 532 size_t mav_num; /* number of entries in mav[] */ 533 534 /* 535 * We add an extra argument slot, in case we need to jam a "--" 536 * argument into the list. 537 */ 538 539 mav_num = (size_t)ac+2; 540 541 if ((mav = calloc(mav_num, sizeof (char *))) == NULL) { 542 perror("chmod"); 543 exit(2); 544 } 545 546 /* scan for the use of "--" in the argument list */ 547 548 for (fflag = i = 0; i < ac; i ++) { 549 if (strcmp(av[i], "--") == 0) 550 fflag = 1; 551 } 552 553 /* process the arguments */ 554 555 for (i = mac = 0; 556 (av[i] != NULL) && (av[i][0] != '\0'); 557 i++) { 558 if (!fflag && av[i][0] == '-') { 559 /* 560 * If there is not already a "--" argument specified, 561 * and the argument starts with '-' but does not 562 * contain any of the official option letters, then it 563 * is probably a mode argument beginning with '-'. 564 * Force a "--" into the argument stream in front of 565 * it. 566 */ 567 568 if ((strchr(av[i], 'R') == NULL && 569 strchr(av[i], 'f') == NULL) && 570 strchr(av[i], '@') == NULL) { 571 if ((mav[mac++] = strdup("--")) == NULL) { 572 perror("chmod"); 573 exit(2); 574 } 575 } 576 } 577 578 if ((mav[mac++] = strdup(av[i])) == NULL) { 579 perror("chmod"); 580 exit(2); 581 } 582 } 583 584 mav[mac] = (char *)NULL; 585 } 586 587 static int 588 parse_acl_args(char *arg, sec_args_t **sec_args) 589 { 590 acl_t *new_acl = NULL; 591 int slot; 592 int len; 593 int action; 594 acl_args_t *new_acl_args; 595 char *acl_spec = NULL; 596 char *end; 597 598 if (arg[0] != 'A') 599 return (1); 600 601 slot = strtol(&arg[1], &end, 10); 602 603 len = strlen(arg); 604 switch (*end) { 605 case '+': 606 action = ACL_ADD; 607 acl_spec = ++end; 608 break; 609 case '-': 610 if (len == 2 && arg[0] == 'A' && arg[1] == '-') 611 action = ACL_STRIP; 612 else 613 action = ACL_DELETE; 614 if (action != ACL_STRIP) { 615 acl_spec = ++end; 616 if (acl_spec[0] == '\0') { 617 action = ACL_SLOT_DELETE; 618 acl_spec = NULL; 619 } else if (arg[1] != '-') 620 return (1); 621 } 622 break; 623 case '=': 624 /* 625 * Was slot specified? 626 */ 627 if (arg[1] == '=') 628 slot = -1; 629 action = ACL_REPLACE; 630 acl_spec = ++end; 631 break; 632 default: 633 return (1); 634 } 635 636 if ((action == ACL_REPLACE || action == ACL_ADD) && acl_spec[0] == '\0') 637 return (1); 638 639 if (acl_spec) { 640 if (acl_parse(acl_spec, &new_acl)) { 641 exit(1); 642 } 643 } 644 645 new_acl_args = malloc(sizeof (acl_args_t)); 646 if (new_acl_args == NULL) 647 return (1); 648 649 new_acl_args->acl_aclp = new_acl; 650 new_acl_args->acl_slot = slot; 651 new_acl_args->acl_action = action; 652 653 if ((*sec_args = malloc(sizeof (sec_args_t))) == NULL) { 654 perror("chmod"); 655 exit(2); 656 } 657 (*sec_args)->sec_type = SEC_ACL; 658 (*sec_args)->sec_acls = new_acl_args; 659 660 return (0); 661 } 662 663 /* 664 * This function is called whenever the group permissions of a file 665 * is being modified. According to the chmod(1) manpage, any 666 * change made to the group permissions must be applied to both 667 * the acl mask and the acl's GROUP_OBJ. The chmod(2) already 668 * set the mask, so this routine needs to make the same change 669 * to the GROUP_OBJ. 670 */ 671 static void 672 handle_acl(char *name, o_mode_t group_clear_bits, o_mode_t group_set_bits) 673 { 674 int aclcnt, n; 675 aclent_t *aclp, *tp; 676 o_mode_t newperm; 677 /* 678 * if this file system support ace_t acl's 679 * then simply return since we don't have an 680 * acl mask to deal with 681 */ 682 if (pathconf(name, _PC_ACL_ENABLED) == _ACL_ACE_ENABLED) 683 return; 684 if ((aclcnt = acl(name, GETACLCNT, 0, NULL)) <= MIN_ACL_ENTRIES) 685 return; /* it's just a trivial acl; no need to change it */ 686 if ((aclp = (aclent_t *)malloc((sizeof (aclent_t)) * aclcnt)) 687 == NULL) { 688 perror("chmod"); 689 exit(2); 690 } 691 692 if (acl(name, GETACL, aclcnt, aclp) < 0) { 693 free(aclp); 694 (void) fprintf(stderr, "chmod: "); 695 perror(name); 696 return; 697 } 698 for (tp = aclp, n = aclcnt; n--; tp++) { 699 if (tp->a_type == GROUP_OBJ) { 700 newperm = tp->a_perm; 701 if (group_clear_bits != 0) 702 newperm &= ~group_clear_bits; 703 if (group_set_bits != 0) 704 newperm |= group_set_bits; 705 if (newperm != tp->a_perm) { 706 tp->a_perm = newperm; 707 if (acl(name, SETACL, aclcnt, aclp) 708 < 0) { 709 (void) fprintf(stderr, "chmod: "); 710 perror(name); 711 } 712 } 713 break; 714 } 715 } 716 free(aclp); 717 } 718 719 static int 720 doacl(char *file, struct stat *st, acl_args_t *acl_args) 721 { 722 acl_t *aclp; 723 acl_t *set_aclp; 724 int error = 0; 725 void *to, *from; 726 int len; 727 int isdir; 728 isdir = S_ISDIR(st->st_mode); 729 730 error = acl_get(file, 0, &aclp); 731 732 if (error != 0) { 733 errmsg(1, 0, "%s\n", acl_strerror(error)); 734 return (1); 735 } 736 switch (acl_args->acl_action) { 737 case ACL_ADD: 738 if ((error = acl_addentries(aclp, 739 acl_args->acl_aclp, acl_args->acl_slot)) != 0) { 740 errmsg(1, 0, "%s\n", acl_strerror(error)); 741 acl_free(aclp); 742 return (1); 743 } 744 set_aclp = aclp; 745 break; 746 case ACL_SLOT_DELETE: 747 if (acl_args->acl_slot + 1 > aclp->acl_cnt) { 748 errmsg(1, 0, 749 gettext("Invalid slot specified for removal\n")); 750 acl_free(aclp); 751 return (1); 752 } 753 754 if (acl_args->acl_slot == 0 && aclp->acl_cnt == 1) { 755 errmsg(1, 0, 756 gettext("Can't remove all ACL " 757 "entries from a file\n")); 758 acl_free(aclp); 759 return (1); 760 } 761 762 /* 763 * remove a single entry 764 * 765 * if last entry just adjust acl_cnt 766 */ 767 768 if ((acl_args->acl_slot + 1) == aclp->acl_cnt) 769 aclp->acl_cnt--; 770 else { 771 to = (char *)aclp->acl_aclp + 772 (acl_args->acl_slot * aclp->acl_entry_size); 773 from = (char *)to + aclp->acl_entry_size; 774 len = (aclp->acl_cnt - acl_args->acl_slot - 1) * 775 aclp->acl_entry_size; 776 (void) memmove(to, from, len); 777 aclp->acl_cnt--; 778 } 779 set_aclp = aclp; 780 break; 781 782 case ACL_DELETE: 783 if ((error = acl_removeentries(aclp, acl_args->acl_aclp, 784 acl_args->acl_slot, ACL_REMOVE_ALL)) != 0) { 785 errmsg(1, 0, "%s\n", acl_strerror(error)); 786 acl_free(aclp); 787 return (1); 788 } 789 790 if (aclp->acl_cnt == 0) { 791 errmsg(1, 0, 792 gettext("Can't remove all ACL " 793 "entries from a file\n")); 794 acl_free(aclp); 795 return (1); 796 } 797 798 set_aclp = aclp; 799 break; 800 case ACL_REPLACE: 801 if (acl_args->acl_slot >= 0) { 802 error = acl_modifyentries(aclp, acl_args->acl_aclp, 803 acl_args->acl_slot); 804 if (error) { 805 errmsg(1, 0, "%s\n", acl_strerror(error)); 806 acl_free(aclp); 807 return (1); 808 } 809 set_aclp = aclp; 810 } else { 811 set_aclp = acl_args->acl_aclp; 812 } 813 break; 814 case ACL_STRIP: 815 error = acl_strip(file, st->st_uid, st->st_gid, st->st_mode); 816 if (error) { 817 errmsg(1, 0, "%s\n", acl_strerror(error)); 818 acl_free(aclp); 819 return (1); 820 } 821 acl_free(aclp); 822 return (0); 823 /*NOTREACHED*/ 824 default: 825 errmsg(1, 2, gettext("Unknown ACL action requested\n")); 826 /*NOTREACHED*/ 827 } 828 error = acl_check(set_aclp, isdir); 829 830 if (error) { 831 errmsg(1, 2, "%s\n%s", acl_strerror(error), 832 gettext("See chmod(1) for more information on " 833 "valid ACL syntax\n")); 834 } 835 if ((error = acl_set(file, set_aclp)) != 0) { 836 errmsg(1, 0, gettext("Failed to set ACL: %s\n"), 837 acl_strerror(error)); 838 acl_free(aclp); 839 return (1); 840 } 841 acl_free(aclp); 842 return (0); 843 } 844 845 /* 846 * Prints out the attributes in their verbose form: 847 * '{'[["no"]<attribute-name>][,["no"]<attribute-name>]...'}' 848 * similar to output of ls -/v. 849 */ 850 static void 851 print_nvlist(nvlist_t *attr_nvlist) 852 { 853 int firsttime = 1; 854 boolean_t value; 855 nvlist_t *lptr = attr_nvlist; 856 nvpair_t *pair = NULL; 857 858 (void) fprintf(stderr, "\t%c", LEFTBRACE); 859 while (pair = nvlist_next_nvpair(lptr, pair)) { 860 if (nvpair_value_boolean_value(pair, &value) == 0) { 861 (void) fprintf(stderr, "%s%s%s", 862 firsttime ? "" : A_SEP_TOK, 863 (value == A_SET_VAL) ? "" : "no", 864 nvpair_name(pair)); 865 firsttime = 0; 866 } else { 867 (void) fprintf(stderr, gettext( 868 "<error retrieving attributes: %s>"), 869 strerror(errno)); 870 break; 871 } 872 } 873 (void) fprintf(stderr, "%c\n", RIGHTBRACE); 874 } 875 876 /* 877 * Add an attribute name and boolean value to an nvlist if an action is to be 878 * performed for that attribute. The nvlist will be used later to set all the 879 * attributes in the nvlist in one operation through a call to setattrat(). 880 * 881 * If a set operation ('+') was specified, then a boolean representation of the 882 * attribute's value will be added to the nvlist for that attribute name. If an 883 * inverse operation ('-') was specified, then a boolean representation of the 884 * inverse of the attribute's value will be added to the nvlist for that 885 * attribute name. 886 * 887 * Returns an nvlist of attribute name and boolean value pairs if there are 888 * attribute actions to be performed, otherwise returns NULL. 889 */ 890 static nvlist_t * 891 set_attrs_nvlist(char *attractptr, int numofattrs) 892 { 893 int attribute_set = 0; 894 f_attr_t i; 895 nvlist_t *attr_nvlist; 896 897 if (nvlist_alloc(&attr_nvlist, NV_UNIQUE_NAME, 0) != 0) { 898 perror("chmod"); 899 exit(2); 900 } 901 902 for (i = 0; i < numofattrs; i++) { 903 if (attractptr[i] != '\0') { 904 if ((nvlist_add_boolean_value(attr_nvlist, 905 attr_to_name(i), 906 (attractptr[i] == A_SET_OP))) != 0) { 907 errmsg(1, 2, gettext( 908 "unable to propagate attribute names and" 909 "values: %s\n"), strerror(errno)); 910 } else { 911 attribute_set = 1; 912 } 913 } 914 } 915 return (attribute_set ? attr_nvlist : NULL); 916 } 917 918 /* 919 * Set the attributes of file, or if specified, of the named attribute file, 920 * attrname. Build an nvlist of attribute names and values and call setattrat() 921 * to set the attributes in one operation. 922 * 923 * Returns 0 if successful, otherwise returns 1. 924 */ 925 static int 926 set_file_attrs(char *file, char *attrname, nvlist_t *attr_nvlist) 927 { 928 int rc; 929 char *filename; 930 931 if (attrname != NULL) { 932 filename = attrname; 933 } else { 934 filename = basename(file); 935 } 936 937 if ((rc = setattrat(AT_FDCWD, XATTR_VIEW_READWRITE, filename, 938 attr_nvlist)) != 0) { 939 char *emsg; 940 switch (errno) { 941 case EINVAL: 942 emsg = gettext("not supported"); 943 break; 944 case EPERM: 945 emsg = gettext("not privileged"); 946 break; 947 default: 948 emsg = strerror(rc); 949 } 950 errmsg(1, 0, gettext( 951 "cannot set the following attributes on " 952 "%s%s%s%s: %s\n"), 953 (attrname == NULL) ? "" : gettext("attribute "), 954 (attrname == NULL) ? "" : attrname, 955 (attrname == NULL) ? "" : gettext(" of "), 956 file, emsg); 957 print_nvlist(attr_nvlist); 958 } 959 960 return (rc); 961 } 962 963 static int 964 save_cwd(void) 965 { 966 return (open(".", O_RDONLY)); 967 } 968 969 static void 970 rest_cwd(int cwd) 971 { 972 if (cwd != -1) { 973 if (fchdir(cwd) != 0) { 974 errmsg(1, 1, gettext( 975 "can't change to current working directory\n")); 976 } 977 (void) close(cwd); 978 } 979 } 980 981 /* 982 * Returns 1 if filename is a system attribute file, otherwise 983 * returns 0. 984 */ 985 static int 986 is_sattr(char *filename) 987 { 988 return (sysattr_type(filename) != _NOT_SATTR); 989 } 990 991 /* 992 * Perform the action on the specified named attribute file for the file 993 * associated with the input file descriptor. If the named attribute file 994 * is "*", then the action is to be performed on all the named attribute files 995 * of the file associated with the input file descriptor. 996 */ 997 static int 998 set_named_attrs(char *file, int parentfd, char *attrname, nvlist_t *attr_nvlist) 999 { 1000 int dirfd; 1001 int error = 0; 1002 DIR *dirp = NULL; 1003 struct dirent *dp; 1004 struct stat st; 1005 1006 if ((attrname == NULL) || (strcmp(attrname, "*") != 0)) { 1007 /* 1008 * Make sure the named attribute exists and extended system 1009 * attributes are supported on the underlying file system. 1010 */ 1011 if (attrname != NULL) { 1012 if (fstatat(parentfd, attrname, &st, 1013 AT_SYMLINK_NOFOLLOW) < 0) { 1014 errmsg(2, 0, gettext( 1015 "can't access attribute %s of %s\n"), 1016 attrname, file); 1017 return (1); 1018 } 1019 if (sysattr_support(attrname, _PC_SATTR_ENABLED) != 1) { 1020 errmsg(1, 0, gettext( 1021 "extended system attributes not supported " 1022 "for attribute %s of %s\n"), 1023 attrname, file); 1024 return (1); 1025 } 1026 } 1027 1028 error = set_file_attrs(file, attrname, attr_nvlist); 1029 1030 } else { 1031 if (((dirfd = dup(parentfd)) == -1) || 1032 ((dirp = fdopendir(dirfd)) == NULL)) { 1033 errmsg(1, 0, gettext( 1034 "cannot open dir pointer of file %s\n"), file); 1035 if (dirfd > 0) { 1036 (void) close(dirfd); 1037 } 1038 return (1); 1039 } 1040 1041 while (dp = readdir(dirp)) { 1042 /* 1043 * Process all extended attribute files except 1044 * ".", "..", and extended system attribute files. 1045 */ 1046 if ((strcmp(dp->d_name, ".") == 0) || 1047 (strcmp(dp->d_name, "..") == 0) || 1048 is_sattr(dp->d_name)) { 1049 continue; 1050 } 1051 1052 if (set_named_attrs(file, parentfd, dp->d_name, 1053 attr_nvlist) != 0) { 1054 error++; 1055 } 1056 } 1057 if (dirp != NULL) { 1058 (void) closedir(dirp); 1059 } 1060 } 1061 1062 return ((error == 0) ? 0 : 1); 1063 } 1064 1065 /* 1066 * Set the attributes of the specified file, or if specified with -@ on the 1067 * command line, the specified named attributes of the specified file. 1068 * 1069 * Returns 0 if successful, otherwise returns 1. 1070 */ 1071 static int 1072 set_attrs(char *file, attr_name_t *attrnames, nvlist_t *attr_nvlist) 1073 { 1074 char *parentd; 1075 char *tpath = NULL; 1076 int cwd; 1077 int error = 0; 1078 int parentfd; 1079 attr_name_t *tattr = attrnames; 1080 1081 if (attr_nvlist == NULL) { 1082 return (0); 1083 } 1084 1085 if (sysattr_support(file, _PC_SATTR_ENABLED) != 1) { 1086 errmsg(1, 0, gettext( 1087 "extended system attributes not supported for %s\n"), file); 1088 return (1); 1089 } 1090 1091 /* 1092 * Open the parent directory and change into it before attempting 1093 * to set the attributes of the file. 1094 */ 1095 if (attrnames == NULL) { 1096 tpath = strdup(file); 1097 parentd = dirname(tpath); 1098 parentfd = open(parentd, O_RDONLY); 1099 } else { 1100 parentfd = attropen(file, ".", O_RDONLY); 1101 } 1102 if (parentfd == -1) { 1103 errmsg(1, 0, gettext( 1104 "cannot open attribute directory of %s\n"), file); 1105 if (tpath != NULL) { 1106 free(tpath); 1107 } 1108 return (1); 1109 } 1110 1111 if ((cwd = save_cwd()) < 0) { 1112 errmsg(1, 1, gettext( 1113 "can't get current working directory\n")); 1114 } 1115 if (fchdir(parentfd) != 0) { 1116 errmsg(1, 0, gettext( 1117 "can't change to parent %sdirectory of %s\n"), 1118 (attrnames == NULL) ? "" : gettext("attribute "), file); 1119 (void) close(cwd); 1120 (void) close(parentfd); 1121 if (tpath != NULL) { 1122 free(tpath); 1123 } 1124 return (1); 1125 } 1126 1127 /* 1128 * If no named attribute file names were provided on the command line 1129 * then set the attributes of the base file, otherwise, set the 1130 * attributes for each of the named attribute files specified. 1131 */ 1132 if (attrnames == NULL) { 1133 error = set_named_attrs(file, parentfd, NULL, attr_nvlist); 1134 free(tpath); 1135 } else { 1136 while (tattr != NULL) { 1137 if (set_named_attrs(file, parentfd, tattr->name, 1138 attr_nvlist) != 0) { 1139 error++; 1140 } 1141 tattr = tattr->next; 1142 } 1143 } 1144 (void) close(parentfd); 1145 rest_cwd(cwd); 1146 1147 return ((error == 0) ? 0 : 1); 1148 } 1149 1150 /* 1151 * Prints the attributes in either the compact or verbose form indicated 1152 * by flag. 1153 */ 1154 static void 1155 print_attrs(int flag) 1156 { 1157 f_attr_t i; 1158 static int numofattrs; 1159 int firsttime = 1; 1160 1161 numofattrs = attr_count(); 1162 1163 (void) fprintf(stderr, gettext("\t[")); 1164 for (i = 0; i < numofattrs; i++) { 1165 if ((attr_to_xattr_view(i) != XATTR_VIEW_READWRITE) || 1166 (attr_to_data_type(i) != DATA_TYPE_BOOLEAN_VALUE)) { 1167 continue; 1168 } 1169 (void) fprintf(stderr, "%s%s", 1170 (firsttime == 1) ? "" : gettext("|"), 1171 (flag == ATTR_OPTS) ? attr_to_option(i) : attr_to_name(i)); 1172 firsttime = 0; 1173 } 1174 (void) fprintf(stderr, gettext("]\n")); 1175 } 1176 1177 /* 1178 * Record what action should be taken on the specified attribute. Only boolean 1179 * read-write attributes can be manipulated. 1180 * 1181 * Returns 0 if successful, otherwise returns 1. 1182 */ 1183 static int 1184 set_attr_args(f_attr_t attr, char action, char *attractptr) 1185 { 1186 if ((attr_to_xattr_view(attr) == XATTR_VIEW_READWRITE) && 1187 (attr_to_data_type(attr) == DATA_TYPE_BOOLEAN_VALUE)) { 1188 attractptr[attr] = action; 1189 return (0); 1190 } 1191 return (1); 1192 } 1193 1194 /* 1195 * Parses the entry and assigns the appropriate action (either '+' or '-' in 1196 * attribute's position in the character array pointed to by attractptr, where 1197 * upon exit, attractptr is positional and the value of each character specifies 1198 * whether to set (a '+'), clear (a '-'), or leave untouched (a '\0') the 1199 * attribute value. 1200 * 1201 * If the entry is an attribute name, then the A_SET_OP action is to be 1202 * performed for this attribute. If the entry is an attribute name proceeded 1203 * with "no", then the A_INVERSE_OP action is to be performed for this 1204 * attribute. If the entry is one or more attribute option letters, then step 1205 * through each of the option letters marking the action to be performed for 1206 * each of the attributes associated with the letter as A_SET_OP. 1207 * 1208 * Returns 0 if the entry was a valid attribute(s) and the action to be 1209 * performed on that attribute(s) has been recorded, otherwise returns 1. 1210 */ 1211 static int 1212 parse_entry(char *entry, char action, char atype, int len, char *attractptr) 1213 { 1214 char aopt[2] = {'\0', '\0'}; 1215 char *aptr; 1216 f_attr_t attr; 1217 1218 if (atype == A_VERBOSE_TYPE) { 1219 if ((attr = name_to_attr(entry)) != F_ATTR_INVAL) { 1220 return (set_attr_args(attr, 1221 (action == A_REPLACE_OP) ? A_SET_OP : action, 1222 attractptr)); 1223 } else if ((len > 2) && (strncmp(entry, "no", 2) == 0) && 1224 ((attr = name_to_attr(entry + 2)) != F_ATTR_INVAL)) { 1225 return (set_attr_args(attr, ((action == A_REPLACE_OP) || 1226 (action == A_SET_OP)) ? A_INVERSE_OP : A_SET_OP, 1227 attractptr)); 1228 } else { 1229 return (1); 1230 } 1231 } else if (atype == A_COMPACT_TYPE) { 1232 for (aptr = entry; *aptr != '\0'; aptr++) { 1233 *aopt = *aptr; 1234 /* 1235 * The output of 'ls' can be used as the attribute mode 1236 * specification for chmod. This output can contain a 1237 * hypen ('-') for each attribute that is not set. If 1238 * so, ignore them. If a replace action is being 1239 * performed, then all attributes that don't have an 1240 * action set here, will be cleared down the line. 1241 */ 1242 if (*aptr == '-') { 1243 continue; 1244 } 1245 if (set_attr_args(option_to_attr(aopt), 1246 (action == A_REPLACE_OP) ? A_SET_OP : action, 1247 attractptr) != 0) { 1248 return (1); 1249 } 1250 } 1251 return (0); 1252 } 1253 return (1); 1254 } 1255 1256 /* 1257 * Parse the attribute specification, aoptsstr. Upon completion, attr_nvlist 1258 * will point to an nvlist which contains pairs of attribute names and values 1259 * to be set; attr_nvlist will be NULL if it is a no-op. 1260 * 1261 * The attribute specification format is 1262 * S[oper]attr_type[attribute_list] 1263 * where oper is 1264 * + set operation of specified attributes in attribute list. 1265 * This is the default operation. 1266 * - inverse operation of specified attributes in attribute list 1267 * = replace operation of all attributes. All attribute operations 1268 * depend on those specified in the attribute list. Attributes 1269 * not specified in the attribute list will be cleared. 1270 * where attr_type is 1271 * c compact type. Each entry in the attribute list is a character 1272 * option representing an associated attribute name. 1273 * v verbose type. Each entry in the attribute list is an 1274 * an attribute name which can optionally be preceeded with "no" 1275 * (to imply the attribute should be cleared). 1276 * a all attributes type. The oper should be applied to all 1277 * read-write boolean system attributes. No attribute list should 1278 * be specified after an 'a' attribute type. 1279 * 1280 * Returns 0 if aoptsstr contained a valid attribute specification, 1281 * otherwise, returns 1. 1282 */ 1283 static int 1284 parse_attr_args(char *aoptsstr, sec_args_t **sec_args) 1285 { 1286 char action; 1287 char *attractptr; 1288 char atype; 1289 char *entry; 1290 char *eptr; 1291 char *nextattr; 1292 char *nextentry; 1293 char *subentry; 1294 char *teptr; 1295 char tok[] = {'\0', '\0'}; 1296 int len; 1297 f_attr_t i; 1298 int numofattrs; 1299 1300 if ((*aoptsstr != 'S') || (*(aoptsstr + 1) == '\0')) { 1301 return (1); 1302 } 1303 1304 if ((eptr = strdup(aoptsstr + 1)) == NULL) { 1305 perror("chmod"); 1306 exit(2); 1307 } 1308 entry = eptr; 1309 1310 /* 1311 * Create a positional character array to determine a single attribute 1312 * operation to be performed, where each index represents the system 1313 * attribute affected, and it's value in the array represents the action 1314 * to be performed, i.e., a value of '+' means to set the attribute, a 1315 * value of '-' means to clear the attribute, and a value of '\0' means 1316 * to leave the attribute untouched. Initially, this positional 1317 * character array is all '\0's, representing a no-op. 1318 */ 1319 if ((numofattrs = attr_count()) < 1) { 1320 errmsg(1, 1, gettext("system attributes not supported\n")); 1321 } 1322 1323 if ((attractptr = calloc(numofattrs, sizeof (char))) == NULL) { 1324 perror("chmod"); 1325 exit(2); 1326 } 1327 1328 if ((*sec_args = malloc(sizeof (sec_args_t))) == NULL) { 1329 perror("chmod"); 1330 exit(2); 1331 } 1332 (*sec_args)->sec_type = SEC_ATTR; 1333 (*sec_args)->sec_attrs = NULL; 1334 1335 /* Parse each attribute operation within the attribute specification. */ 1336 while ((entry != NULL) && (*entry != '\0')) { 1337 action = A_SET_OP; 1338 atype = '\0'; 1339 1340 /* Get the operator. */ 1341 switch (*entry) { 1342 case A_SET_OP: 1343 case A_INVERSE_OP: 1344 case A_REPLACE_OP: 1345 action = *entry++; 1346 break; 1347 case A_COMPACT_TYPE: 1348 case A_VERBOSE_TYPE: 1349 case A_ALLATTRS_TYPE: 1350 atype = *entry++; 1351 action = A_SET_OP; 1352 break; 1353 default: 1354 break; 1355 } 1356 1357 /* An attribute type must be specified. */ 1358 if (atype == '\0') { 1359 if ((*entry == A_COMPACT_TYPE) || 1360 (*entry == A_VERBOSE_TYPE) || 1361 (*entry == A_ALLATTRS_TYPE)) { 1362 atype = *entry++; 1363 } else { 1364 return (1); 1365 } 1366 } 1367 1368 /* Get the attribute specification separator. */ 1369 if (*entry == LEFTBRACE) { 1370 *tok = RIGHTBRACE; 1371 entry++; 1372 } else { 1373 *tok = A_SEP; 1374 } 1375 1376 /* Get the attribute operation */ 1377 if ((nextentry = strpbrk(entry, tok)) != NULL) { 1378 *nextentry = '\0'; 1379 nextentry++; 1380 } 1381 1382 /* Check for a no-op */ 1383 if ((*entry == '\0') && (atype != A_ALLATTRS_TYPE) && 1384 (action != A_REPLACE_OP)) { 1385 entry = nextentry; 1386 continue; 1387 } 1388 1389 /* 1390 * Step through the attribute operation, setting the 1391 * appropriate values for the specified attributes in the 1392 * character array, attractptr. A value of '+' will mean the 1393 * attribute is to be set, and a value of '-' will mean the 1394 * attribute is to be cleared. If the value of an attribute 1395 * remains '\0', then no action is to be taken on that 1396 * attribute. As multiple operations specified are 1397 * accumulated, a single attribute setting operation is 1398 * represented in attractptr. 1399 */ 1400 len = strlen(entry); 1401 if ((*tok == RIGHTBRACE) || (action == A_REPLACE_OP) || 1402 (atype == A_ALLATTRS_TYPE)) { 1403 1404 if ((action == A_REPLACE_OP) || 1405 (atype == A_ALLATTRS_TYPE)) { 1406 (void) memset(attractptr, '\0', numofattrs); 1407 } 1408 1409 if (len > 0) { 1410 if ((teptr = strdup(entry)) == NULL) { 1411 perror("chmod"); 1412 exit(2); 1413 } 1414 subentry = teptr; 1415 while (subentry != NULL) { 1416 if ((nextattr = strpbrk(subentry, 1417 A_SEP_TOK)) != NULL) { 1418 *nextattr = '\0'; 1419 nextattr++; 1420 } 1421 if (parse_entry(subentry, action, 1422 atype, len, attractptr) != 0) { 1423 return (1); 1424 } 1425 subentry = nextattr; 1426 } 1427 free(teptr); 1428 } 1429 1430 /* 1431 * If performing the replace action, record the 1432 * attributes and values for the rest of the 1433 * attributes that have not already been recorded, 1434 * otherwise record the specified action for all 1435 * attributes. Note: set_attr_args() will only record 1436 * the attribute and action if it is a boolean 1437 * read-write attribute so we don't need to worry 1438 * about checking it here. 1439 */ 1440 if ((action == A_REPLACE_OP) || 1441 (atype == A_ALLATTRS_TYPE)) { 1442 for (i = 0; i < numofattrs; i++) { 1443 if (attractptr[i] == A_UNDEF_OP) { 1444 (void) set_attr_args(i, 1445 (action == A_SET_OP) ? 1446 A_SET_OP : A_INVERSE_OP, 1447 attractptr); 1448 } 1449 } 1450 } 1451 1452 } else { 1453 if (parse_entry(entry, action, atype, len, 1454 attractptr) != 0) { 1455 return (1); 1456 } 1457 } 1458 entry = nextentry; 1459 } 1460 1461 /* 1462 * Populate an nvlist with attribute name and boolean value pairs 1463 * using the single attribute operation. 1464 */ 1465 (*sec_args)->sec_attrs = set_attrs_nvlist(attractptr, numofattrs); 1466 free(attractptr); 1467 free(eptr); 1468 1469 return (0); 1470 } 1471