1/* 2 File: setfacl.c 3 (Linux Access Control List Management) 4 5 Copyright (C) 1999-2002 6 Andreas Gruenbacher, <a.gruenbacher@computer.org> 7 8 This program is free software; you can redistribute it and/or 9 modify it under the terms of the GNU Library General Public 10 License as published by the Free Software Foundation; either 11 version 2 of the License, or (at your option) any later version. 12 13 This program is distributed in the hope that it will be useful, 14 but WITHOUT ANY WARRANTY; without even the implied warranty of 15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 16 Library General Public License for more details. 17 18 You should have received a copy of the GNU Library General Public 19 License along with this library; if not, write to the Free Software 20 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 21*/ 22 23#include <limits.h> 24#include <stdio.h> 25#include <string.h> 26#include <unistd.h> 27#include <errno.h> 28#include <sys/stat.h> 29#include <dirent.h> 30#include <libgen.h> 31#include <ftw.h> 32#include <getopt.h> 33#include <locale.h> 34#include "config.h" 35#include "sequence.h" 36#include "parse.h" 37#include "misc.h" 38 39extern int 40do_set( 41 const char *path_p, 42 const struct stat *stat_p, 43 const seq_t seq); 44 45 46#define POSIXLY_CORRECT_STR "POSIXLY_CORRECT" 47 48/* '-' stands for `process non-option arguments in loop' */ 49#if !POSIXLY_CORRECT 50# define CMD_LINE_OPTIONS "-:bkndm:M:x:X:RLP" 51# define CMD_LINE_SPEC "[-bkndRLP] { -m|-M|-x|-X ... } file ..." 52#endif 53#define POSIXLY_CMD_LINE_OPTIONS "-:bkndm:M:x:X:" 54#define POSIXLY_CMD_LINE_SPEC "[-bknd] {-m|-M|-x|-X ... } file ..." 55 56struct option long_options[] = { 57#if !POSIXLY_CORRECT 58 { "set", 1, 0, 's' }, 59 { "set-file", 1, 0, 'S' }, 60 61 { "mask", 0, 0, 'r' }, 62 { "recursive", 0, 0, 'R' }, 63 { "logical", 0, 0, 'L' }, 64 { "physical", 0, 0, 'P' }, 65 { "restore", 1, 0, 'B' }, 66 { "test", 0, 0, 't' }, 67#endif 68 { "modify", 1, 0, 'm' }, 69 { "modify-file", 1, 0, 'M' }, 70 { "remove", 1, 0, 'x' }, 71 { "remove-file", 1, 0, 'X' }, 72 73 { "default", 0, 0, 'd' }, 74 { "no-mask", 0, 0, 'n' }, 75 { "remove-all", 0, 0, 'b' }, 76 { "remove-default", 0, 0, 'k' }, 77 { "version", 0, 0, 'v' }, 78 { "help", 0, 0, 'h' }, 79 { NULL, 0, 0, 0 }, 80}; 81 82const char *progname; 83const char *cmd_line_options, *cmd_line_spec; 84 85int opt_recursive; /* recurse into sub-directories? */ 86int opt_walk_logical; /* always follow symbolic links */ 87int opt_walk_physical; /* never follow symbolic links */ 88int opt_recalculate; /* recalculate mask entry (0=default, 1=yes, -1=no) */ 89int opt_promote; /* promote access ACL to default ACL */ 90int opt_test; /* do not write to the file system. 91 Print what would happen instead. */ 92#if POSIXLY_CORRECT 93const int posixly_correct = 1; /* Posix compatible behavior! */ 94#else 95int posixly_correct; /* Posix compatible behavior? */ 96#endif 97int chown_error; 98int promote_warning; 99 100 101static const char *xquote(const char *str) 102{ 103 const char *q = quote(str); 104 if (q == NULL) { 105 fprintf(stderr, "%s: %s\n", progname, strerror(errno)); 106 exit(1); 107 } 108 return q; 109} 110 111int 112has_any_of_type( 113 cmd_t cmd, 114 acl_type_t acl_type) 115{ 116 while (cmd) { 117 if (cmd->c_type == acl_type) 118 return 1; 119 cmd = cmd->c_next; 120 } 121 return 0; 122} 123 124 125#if !POSIXLY_CORRECT 126int 127restore( 128 FILE *file, 129 const char *filename) 130{ 131 char *path_p; 132 struct stat stat; 133 uid_t uid; 134 gid_t gid; 135 seq_t seq = NULL; 136 int line = 0, backup_line; 137 int error, status = 0; 138 139 memset(&stat, 0, sizeof(stat)); 140 141 for(;;) { 142 backup_line = line; 143 error = read_acl_comments(file, &line, &path_p, &uid, &gid); 144 if (error < 0) 145 goto fail; 146 if (error == 0) 147 return 0; 148 149 if (path_p == NULL) { 150 if (filename) { 151 fprintf(stderr, _("%s: %s: No filename found " 152 "in line %d, aborting\n"), 153 progname, xquote(filename), 154 backup_line); 155 } else { 156 fprintf(stderr, _("%s: No filename found in " 157 "line %d of standard input, " 158 "aborting\n"), 159 progname, backup_line); 160 } 161 goto getout; 162 } 163 164 if (!(seq = seq_init())) 165 goto fail; 166 if (seq_append_cmd(seq, CMD_REMOVE_ACL, ACL_TYPE_ACCESS) || 167 seq_append_cmd(seq, CMD_REMOVE_ACL, ACL_TYPE_DEFAULT)) 168 goto fail; 169 170 error = read_acl_seq(file, seq, CMD_ENTRY_REPLACE, 171 SEQ_PARSE_WITH_PERM | 172 SEQ_PARSE_NO_RELATIVE | 173 SEQ_PARSE_DEFAULT | 174 SEQ_PARSE_MULTI, 175 &line, NULL); 176 if (error != 0) { 177 fprintf(stderr, _("%s: %s: %s in line %d\n"), 178 progname, xquote(filename), strerror(errno), 179 line); 180 goto getout; 181 } 182 183 error = lstat(path_p, &stat); 184 if (opt_test && error != 0) { 185 fprintf(stderr, "%s: %s: %s\n", progname, 186 xquote(path_p), strerror(errno)); 187 status = 1; 188 } 189 stat.st_uid = uid; 190 stat.st_gid = gid; 191 192 error = do_set(path_p, &stat, seq); 193 if (error != 0) { 194 status = 1; 195 goto resume; 196 } 197 198 if (!opt_test && 199 (uid != ACL_UNDEFINED_ID || gid != ACL_UNDEFINED_ID)) { 200 if (chown(path_p, uid, gid) != 0) { 201 fprintf(stderr, _("%s: %s: Cannot change " 202 "owner/group: %s\n"), 203 progname, xquote(path_p), 204 strerror(errno)); 205 status = 1; 206 } 207 } 208resume: 209 if (path_p) { 210 free(path_p); 211 path_p = NULL; 212 } 213 if (seq) { 214 seq_free(seq); 215 seq = NULL; 216 } 217 } 218 219getout: 220 if (path_p) { 221 free(path_p); 222 path_p = NULL; 223 } 224 if (seq) { 225 seq_free(seq); 226 seq = NULL; 227 } 228 return status; 229 230fail: 231 fprintf(stderr, "%s: %s: %s\n", progname, xquote(filename), 232 strerror(errno)); 233 status = 1; 234 goto getout; 235} 236#endif 237 238 239void help(void) 240{ 241 printf(_("%s %s -- set file access control lists\n"), 242 progname, VERSION); 243 printf(_("Usage: %s %s\n"), 244 progname, cmd_line_spec); 245 printf(_( 246" -m, --modify=acl modify the current ACL(s) of file(s)\n" 247" -M, --modify-file=file read ACL entries to modify from file\n" 248" -x, --remove=acl remove entries from the ACL(s) of file(s)\n" 249" -X, --remove-file=file read ACL entries to remove from file\n" 250" -b, --remove-all remove all extended ACL entries\n" 251" -k, --remove-default remove the default ACL\n")); 252#if !POSIXLY_CORRECT 253 if (!posixly_correct) { 254 printf(_( 255" --set=acl set the ACL of file(s), replacing the current ACL\n" 256" --set-file=file read ACL entries to set from file\n" 257" --mask do recalculate the effective rights mask\n")); 258 } 259#endif 260 printf(_( 261" -n, --no-mask don't recalculate the effective rights mask\n" 262" -d, --default operations apply to the default ACL\n")); 263#if !POSIXLY_CORRECT 264 if (!posixly_correct) { 265 printf(_( 266" -R, --recursive recurse into subdirectories\n" 267" -L, --logical logical walk, follow symbolic links\n" 268" -P, --physical physical walk, do not follow symbolic links\n" 269" --restore=file restore ACLs (inverse of `getfacl -R')\n" 270" --test test mode (ACLs are not modified)\n")); 271 } 272#endif 273 printf(_( 274" --version print version and exit\n" 275" --help this help text\n")); 276} 277 278 279char *next_line(FILE *file) 280{ 281 static char line[_POSIX_PATH_MAX], *c; 282 if (!fgets(line, sizeof(line), file)) 283 return NULL; 284 285 c = strrchr(line, '\0'); 286 while (c > line && (*(c-1) == '\n' || 287 *(c-1) == '\r')) { 288 c--; 289 *c = '\0'; 290 } 291 return line; 292} 293 294 295 296static int __errors; 297static seq_t __seq; 298int __do_set(const char *file, const struct stat *stat, 299 int flag, struct FTW *ftw) 300{ 301 if (flag & FTW_DNR) { 302 /* Item is a directory which can't be read. */ 303 fprintf(stderr, "%s: %s: %s\n", 304 progname, file, strerror(errno)); 305 return 0; 306 } 307 308 /* Process the target of a symbolic link, and traverse the link, 309 only if doing a logical walk, or if the symbolic link was 310 specified on the command line. Always skip symbolic links if 311 doing a physical walk. */ 312 313 if (S_ISLNK(stat->st_mode) && 314 (opt_walk_physical || (ftw->level > 0 && !opt_walk_logical))) 315 return 0; 316 317 if (do_set(file, stat, __seq)) 318 __errors++; 319 320 /* We also get here in non-recursive mode. In that case, 321 return something != 0 to abort nftw. */ 322 323 if (!opt_recursive) 324 return 1; 325 326 return 0; 327} 328 329int walk_tree(const char *file, seq_t seq) 330{ 331 __errors = 0; 332 __seq = seq; 333 if (nftw(file, __do_set, 0, opt_walk_physical * FTW_PHYS) < 0) { 334 fprintf(stderr, "%s: %s: %s\n", progname, 335 xquote(file), strerror(errno)); 336 __errors++; 337 } 338 return __errors; 339} 340 341int next_file(const char *arg, seq_t seq) 342{ 343 char *line; 344 int errors = 0; 345 346 if (strcmp(arg, "-") == 0) { 347 while ((line = next_line(stdin))) 348 errors = walk_tree(line, seq); 349 } else { 350 errors = walk_tree(arg, seq); 351 } 352 return errors ? 1 : 0; 353} 354 355 356#define ERRNO_ERROR(s) \ 357 ({status = (s); goto errno_error; }) 358 359 360int main(int argc, char *argv[]) 361{ 362 int opt; 363 int saw_files = 0; 364 int status = 0; 365 FILE *file; 366 int which; 367 int lineno; 368 int error; 369 seq_t seq = NULL; 370 int seq_cmd, parse_mode; 371 372 progname = basename(argv[0]); 373 374#if POSIXLY_CORRECT 375 cmd_line_options = POSIXLY_CMD_LINE_OPTIONS; 376 cmd_line_spec = _(POSIXLY_CMD_LINE_SPEC); 377#else 378 if (getenv(POSIXLY_CORRECT_STR)) 379 posixly_correct = 1; 380 if (!posixly_correct) { 381 cmd_line_options = CMD_LINE_OPTIONS; 382 cmd_line_spec = _(CMD_LINE_SPEC); 383 } else { 384 cmd_line_options = POSIXLY_CMD_LINE_OPTIONS; 385 cmd_line_spec = _(POSIXLY_CMD_LINE_SPEC); 386 } 387#endif 388 389 setlocale(LC_CTYPE, ""); 390 setlocale(LC_MESSAGES, ""); 391 bindtextdomain(PACKAGE, LOCALEDIR); 392 textdomain(PACKAGE); 393 394 while ((opt = getopt_long(argc, argv, cmd_line_options, 395 long_options, NULL)) != -1) { 396 /* we remember the two REMOVE_ACL commands of the set 397 operations because we may later need to delete them. */ 398 cmd_t seq_remove_default_acl_cmd = NULL; 399 cmd_t seq_remove_acl_cmd = NULL; 400 401 if (opt != '\1' && saw_files) { 402 if (seq) { 403 seq_free(seq); 404 seq = NULL; 405 } 406 saw_files = 0; 407 } 408 if (seq == NULL) { 409 if (!(seq = seq_init())) 410 ERRNO_ERROR(1); 411 } 412 413 switch (opt) { 414 case 'b': /* remove all extended entries */ 415 if (seq_append_cmd(seq, CMD_REMOVE_EXTENDED_ACL, 416 ACL_TYPE_ACCESS) || 417 seq_append_cmd(seq, CMD_REMOVE_ACL, 418 ACL_TYPE_DEFAULT)) 419 ERRNO_ERROR(1); 420 break; 421 422 case 'k': /* remove default ACL */ 423 if (seq_append_cmd(seq, CMD_REMOVE_ACL, 424 ACL_TYPE_DEFAULT)) 425 ERRNO_ERROR(1); 426 break; 427 428 case 'n': /* do not recalculate mask */ 429 opt_recalculate = -1; 430 break; 431 432 case 'r': /* force recalculate mask */ 433 opt_recalculate = 1; 434 break; 435 436 case 'd': /* operations apply to default ACL */ 437 opt_promote = 1; 438 break; 439 440 case 's': /* set */ 441 if (seq_append_cmd(seq, CMD_REMOVE_ACL, 442 ACL_TYPE_ACCESS)) 443 ERRNO_ERROR(1); 444 seq_remove_acl_cmd = seq->s_last; 445 if (seq_append_cmd(seq, CMD_REMOVE_ACL, 446 ACL_TYPE_DEFAULT)) 447 ERRNO_ERROR(1); 448 seq_remove_default_acl_cmd = seq->s_last; 449 450 seq_cmd = CMD_ENTRY_REPLACE; 451 parse_mode = SEQ_PARSE_WITH_PERM | 452 SEQ_PARSE_NO_RELATIVE; 453 goto set_modify_delete; 454 455 case 'm': /* modify */ 456 seq_cmd = CMD_ENTRY_REPLACE; 457 parse_mode = SEQ_PARSE_WITH_PERM; 458#if POSIXLY_CORRECT || 1 459 parse_mode |= SEQ_PARSE_NO_RELATIVE; 460#else 461 if (posixly_correct) 462 parse_mode |= SEQ_PARSE_NO_RELATIVE; 463 else 464 parse_mode |= SEQ_PARSE_ANY_RELATIVE; 465#endif 466 goto set_modify_delete; 467 468 case 'x': /* delete */ 469 seq_cmd = CMD_REMOVE_ENTRY; 470 parse_mode = SEQ_PARSE_NO_RELATIVE; 471#if POSIXLY_CORRECT 472 parse_mode |= SEQ_PARSE_ANY_PERM; 473#else 474 if (posixly_correct) 475 parse_mode |= SEQ_PARSE_ANY_PERM; 476 else 477 parse_mode |= SEQ_PARSE_NO_PERM; 478#endif 479 goto set_modify_delete; 480 481 set_modify_delete: 482 if (!posixly_correct) 483 parse_mode |= SEQ_PARSE_DEFAULT; 484 if (opt_promote) 485 parse_mode |= SEQ_PROMOTE_ACL; 486 if (parse_acl_seq(seq, optarg, &which, 487 seq_cmd, parse_mode) != 0) { 488 if (which < 0 || 489 (size_t) which >= strlen(optarg)) { 490 fprintf(stderr, _( 491 "%s: Option " 492 "-%c incomplete\n"), 493 progname, opt); 494 } else { 495 fprintf(stderr, _( 496 "%s: Option " 497 "-%c: %s near " 498 "character %d\n"), 499 progname, opt, 500 strerror(errno), 501 which+1); 502 } 503 status = 2; 504 goto cleanup; 505 } 506 break; 507 508 case 'S': /* set from file */ 509 if (seq_append_cmd(seq, CMD_REMOVE_ACL, 510 ACL_TYPE_ACCESS)) 511 ERRNO_ERROR(1); 512 seq_remove_acl_cmd = seq->s_last; 513 if (seq_append_cmd(seq, CMD_REMOVE_ACL, 514 ACL_TYPE_DEFAULT)) 515 ERRNO_ERROR(1); 516 seq_remove_default_acl_cmd = seq->s_last; 517 518 seq_cmd = CMD_ENTRY_REPLACE; 519 parse_mode = SEQ_PARSE_WITH_PERM | 520 SEQ_PARSE_NO_RELATIVE; 521 goto set_modify_delete_from_file; 522 523 case 'M': /* modify from file */ 524 seq_cmd = CMD_ENTRY_REPLACE; 525 parse_mode = SEQ_PARSE_WITH_PERM; 526#if POSIXLY_CORRECT || 1 527 parse_mode |= SEQ_PARSE_NO_RELATIVE; 528#else 529 if (posixly_correct) 530 parse_mode |= SEQ_PARSE_NO_RELATIVE; 531 else 532 parse_mode |= SEQ_PARSE_ANY_RELATIVE; 533#endif 534 goto set_modify_delete_from_file; 535 536 case 'X': /* delete from file */ 537 seq_cmd = CMD_REMOVE_ENTRY; 538 parse_mode = SEQ_PARSE_NO_RELATIVE; 539#if POSIXLY_CORRECT 540 parse_mode |= SEQ_PARSE_ANY_PERM; 541#else 542 if (posixly_correct) 543 parse_mode |= SEQ_PARSE_ANY_PERM; 544 else 545 parse_mode |= SEQ_PARSE_NO_PERM; 546#endif 547 goto set_modify_delete_from_file; 548 549 set_modify_delete_from_file: 550 if (!posixly_correct) 551 parse_mode |= SEQ_PARSE_DEFAULT; 552 if (opt_promote) 553 parse_mode |= SEQ_PROMOTE_ACL; 554 if (strcmp(optarg, "-") == 0) { 555 file = stdin; 556 } else { 557 file = fopen(optarg, "r"); 558 if (file == NULL) { 559 fprintf(stderr, "%s: %s: %s\n", 560 progname, 561 xquote(optarg), 562 strerror(errno)); 563 status = 2; 564 goto cleanup; 565 } 566 } 567 568 lineno = 0; 569 error = read_acl_seq(file, seq, seq_cmd, 570 parse_mode, &lineno, NULL); 571 572 if (file != stdin) { 573 fclose(file); 574 } 575 576 if (error) { 577 if (!errno) 578 errno = EINVAL; 579 580 if (file != stdin) { 581 fprintf(stderr, _( 582 "%s: %s in line " 583 "%d of file %s\n"), 584 progname, 585 strerror(errno), 586 lineno, 587 xquote(optarg)); 588 } else { 589 fprintf(stderr, _( 590 "%s: %s in line " 591 "%d of standard " 592 "input\n"), progname, 593 strerror(errno), 594 lineno); 595 } 596 status = 2; 597 goto cleanup; 598 } 599 break; 600 601 602 case '\1': /* file argument */ 603 if (seq_empty(seq)) 604 goto synopsis; 605 saw_files = 1; 606 607 status = next_file(optarg, seq); 608 break; 609 610 case 'B': /* restore ACL backup */ 611 saw_files = 1; 612 613 if (strcmp(optarg, "-") == 0) 614 file = stdin; 615 else { 616 file = fopen(optarg, "r"); 617 if (file == NULL) { 618 fprintf(stderr, "%s: %s: %s\n", 619 progname, 620 xquote(optarg), 621 strerror(errno)); 622 status = 2; 623 goto cleanup; 624 } 625 } 626 627 status = restore(file, 628 (file == stdin) ? NULL : optarg); 629 630 if (file != stdin) 631 fclose(file); 632 if (status != 0) 633 goto cleanup; 634 break; 635 636 case 'R': /* recursive */ 637 opt_recursive = 1; 638 break; 639 640 case 'L': /* follow symlinks */ 641 opt_walk_logical = 1; 642 opt_walk_physical = 0; 643 break; 644 645 case 'P': /* do not follow symlinks */ 646 opt_walk_logical = 0; 647 opt_walk_physical = 1; 648 break; 649 650 case 't': /* test mode */ 651 opt_test = 1; 652 break; 653 654 case 'v': /* print version and exit */ 655 printf("%s " VERSION "\n", progname); 656 status = 0; 657 goto cleanup; 658 659 case 'h': /* help! */ 660 help(); 661 status = 0; 662 goto cleanup; 663 664 case ':': /* option missing */ 665 case '?': /* unknown option */ 666 default: 667 goto synopsis; 668 } 669 if (seq_remove_acl_cmd) { 670 /* This was a set operation. Check if there are 671 actually entries of ACL_TYPE_ACCESS; if there 672 are none, we need to remove this command! */ 673 if (!has_any_of_type(seq_remove_acl_cmd->c_next, 674 ACL_TYPE_ACCESS)) 675 seq_delete_cmd(seq, seq_remove_acl_cmd); 676 } 677 if (seq_remove_default_acl_cmd) { 678 /* This was a set operation. Check if there are 679 actually entries of ACL_TYPE_DEFAULT; if there 680 are none, we need to remove this command! */ 681 if (!has_any_of_type(seq_remove_default_acl_cmd->c_next, 682 ACL_TYPE_DEFAULT)) 683 seq_delete_cmd(seq, seq_remove_default_acl_cmd); 684 } 685 } 686 while (optind < argc) { 687 if (seq_empty(seq)) 688 goto synopsis; 689 saw_files = 1; 690 691 status = next_file(argv[optind++], seq); 692 } 693 if (!saw_files) 694 goto synopsis; 695 696 goto cleanup; 697 698synopsis: 699 fprintf(stderr, _("Usage: %s %s\n"), 700 progname, cmd_line_spec); 701 fprintf(stderr, _("Try `%s --help' for more information.\n"), 702 progname); 703 status = 2; 704 goto cleanup; 705 706errno_error: 707 fprintf(stderr, "%s: %s\n", progname, strerror(errno)); 708 goto cleanup; 709 710cleanup: 711 if (seq) 712 seq_free(seq); 713 return status; 714} 715 716