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