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