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