1/*
2  File: do_set.c
3  (Linux Access Control List Management)
4
5  Copyright (C) 1999, 2000
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 <stdio.h>
24#include <errno.h>
25#include <sys/acl.h>
26#include <acl/libacl.h>
27
28#include <stdlib.h>
29#include <string.h>
30#include <getopt.h>
31#include <sys/types.h>
32#include <sys/stat.h>
33#include <unistd.h>
34#include <dirent.h>
35#include <ftw.h>
36#include "sequence.h"
37#include "parse.h"
38
39#include <libintl.h>
40
41
42extern const char *progname;
43extern int opt_recursive;
44extern int opt_recalculate;
45extern int opt_test;
46extern int print_options;
47
48acl_entry_t
49find_entry(
50	acl_t acl,
51	acl_tag_t type,
52	id_t id)
53{
54	acl_entry_t ent;
55	acl_tag_t e_type;
56	id_t *e_id_p;
57
58	if (acl_get_entry(acl, ACL_FIRST_ENTRY, &ent) != 1)
59		return NULL;
60
61	for(;;) {
62		acl_get_tag_type(ent, &e_type);
63		if (type == e_type) {
64			if (id != ACL_UNDEFINED_ID) {
65				e_id_p = acl_get_qualifier(ent);
66				if (e_id_p == NULL)
67					return NULL;
68				if (*e_id_p == id) {
69					acl_free(e_id_p);
70					return ent;
71				}
72				acl_free(e_id_p);
73			} else {
74				return ent;
75			}
76		}
77		if (acl_get_entry(acl, ACL_NEXT_ENTRY, &ent) != 1)
78			return NULL;
79	}
80}
81
82int
83has_execute_perms(
84	acl_t acl)
85{
86	acl_entry_t ent;
87
88	if (acl_get_entry(acl, ACL_FIRST_ENTRY, &ent) != 1)
89		return 0;
90
91	for(;;) {
92		acl_permset_t permset;
93
94		acl_get_permset(ent, &permset);
95		if (acl_get_perm(permset, ACL_EXECUTE) != 0)
96			return 1;
97
98		if (acl_get_entry(acl, ACL_NEXT_ENTRY, &ent) != 1)
99			return 0;
100	}
101}
102
103
104int
105clone_entry(
106	acl_t from_acl,
107	acl_tag_t from_type,
108	acl_t *to_acl,
109	acl_tag_t to_type)
110{
111	acl_entry_t from_entry, to_entry;
112	from_entry = find_entry(from_acl, from_type, ACL_UNDEFINED_ID);
113	if (from_entry) {
114		if (acl_create_entry(to_acl, &to_entry) != 0)
115			return -1;
116		acl_copy_entry(to_entry, from_entry);
117		acl_set_tag_type(to_entry, to_type);
118		return 0;
119	} else {
120		return 1;
121	}
122}
123
124
125void
126print_test(
127	FILE *file,
128	const char *path_p,
129	const struct stat *st,
130	const acl_t acl,
131	const acl_t default_acl)
132{
133	char *acl_text, *default_acl_text;
134
135	acl_text = acl_to_any_text(acl, NULL, ',', TEXT_ABBREVIATE);
136	default_acl_text =
137		acl_to_any_text(default_acl, "d:", ',', TEXT_ABBREVIATE);
138	fprintf(file, "%s: %s,%s\n", path_p,
139		acl_text ? acl_text : "*",
140		default_acl_text ? default_acl_text : "*");
141	acl_free(acl_text);
142	acl_free(default_acl_text);
143}
144
145
146static void
147set_perm(
148	acl_entry_t ent,
149	mode_t add,
150	mode_t remove)
151{
152	acl_permset_t set;
153
154	acl_get_permset(ent, &set);
155	if (remove & CMD_PERM_READ)
156		acl_delete_perm(set, ACL_READ);
157	if (remove & CMD_PERM_WRITE)
158		acl_delete_perm(set, ACL_WRITE);
159	if (remove & CMD_PERM_EXECUTE)
160		acl_delete_perm(set, ACL_EXECUTE);
161	if (add & CMD_PERM_READ)
162		acl_add_perm(set, ACL_READ);
163	if (add & CMD_PERM_WRITE)
164		acl_add_perm(set, ACL_WRITE);
165	if (add & CMD_PERM_EXECUTE)
166		acl_add_perm(set, ACL_EXECUTE);
167}
168
169
170static int
171retrieve_acl(
172	const char *path_p,
173	acl_type_t type,
174	const struct stat *st,
175	acl_t *old_acl,
176	acl_t *acl)
177{
178	if (*acl)
179		return 0;
180	*acl = NULL;
181	if (type == ACL_TYPE_ACCESS || S_ISDIR(st->st_mode)) {
182		*old_acl = acl_get_file(path_p, type);
183		if (*old_acl == NULL && (errno == ENOSYS || errno == ENOTSUP)) {
184			if (type == ACL_TYPE_DEFAULT)
185				*old_acl = acl_init(0);
186			else
187				*old_acl = acl_from_mode(st->st_mode);
188		}
189	} else
190		*old_acl = acl_init(0);
191	if (*old_acl == NULL)
192		return -1;
193	*acl = acl_dup(*old_acl);
194	if (*acl == NULL)
195		return -1;
196	return 0;
197}
198
199
200static int
201remove_extended_entries(
202	acl_t acl)
203{
204	acl_entry_t ent, group_obj;
205	acl_permset_t mask_permset, group_obj_permset;
206	acl_tag_t tag;
207	int error;
208
209	/*
210	 * Removing the ACL_MASK entry from the ACL results in
211	 * increased permissions for the owning group if the
212	 * ACL_GROUP_OBJ entry contains permissions not contained
213	 * in the ACL_MASK entry. We remove these permissions from
214	 * the ACL_GROUP_OBJ entry to avoid that.
215	 *
216	 * After removing the ACL, the file owner and the owning group
217	 * therefore have the same permissions as before.
218	 */
219
220	ent = find_entry(acl, ACL_MASK, ACL_UNDEFINED_ID);
221	group_obj = find_entry(acl, ACL_GROUP_OBJ, ACL_UNDEFINED_ID);
222	if (ent && group_obj) {
223		if (!acl_get_permset(ent, &mask_permset) &&
224		    !acl_get_permset(group_obj, &group_obj_permset)) {
225			if (!acl_get_perm(mask_permset, ACL_READ))
226				acl_delete_perm(group_obj_permset, ACL_READ);
227			if (!acl_get_perm(mask_permset, ACL_WRITE))
228				acl_delete_perm(group_obj_permset, ACL_WRITE);
229			if (!acl_get_perm(mask_permset, ACL_EXECUTE))
230				acl_delete_perm(group_obj_permset, ACL_EXECUTE);
231		}
232	}
233
234	error = acl_get_entry(acl, ACL_FIRST_ENTRY, &ent);
235	while (error == 1) {
236		acl_get_tag_type(ent, &tag);
237		switch(tag) {
238			case ACL_USER:
239			case ACL_GROUP:
240			case ACL_MASK:
241				acl_delete_entry(acl, ent);
242				break;
243			default:
244				break;
245		}
246
247		error = acl_get_entry(acl, ACL_NEXT_ENTRY, &ent);
248	}
249	if (error < 0)
250		return -1;
251	return 0;
252}
253
254
255#define RETRIEVE_ACL(type) do { \
256	error = retrieve_acl(path_p, type, st, old_xacl, xacl); \
257	if (error) \
258		goto fail; \
259	} while(0)
260
261int
262do_set(
263	const char *path_p,
264	const struct stat *st,
265	const seq_t seq)
266{
267	acl_t old_acl = NULL, old_default_acl = NULL;
268	acl_t acl = NULL, default_acl = NULL;
269	acl_t *xacl, *old_xacl;
270	acl_entry_t ent;
271	cmd_t cmd;
272	int which_entry;
273	int errors = 0, error;
274	char *acl_text;
275	int acl_modified = 0, default_acl_modified = 0;
276	int acl_mask_provided = 0, default_acl_mask_provided = 0;
277
278	/* Execute the commands in seq (read ACLs on demand) */
279	error = seq_get_cmd(seq, SEQ_FIRST_CMD, &cmd);
280	if (error == 0)
281		return 0;
282	while (error == 1) {
283		if (cmd->c_type == ACL_TYPE_ACCESS) {
284			xacl = &acl;
285			old_xacl = &old_acl;
286			acl_modified = 1;
287			if (cmd->c_tag == ACL_MASK)
288				acl_mask_provided = 1;
289		} else {
290			xacl = &default_acl;
291			old_xacl = &old_default_acl;
292			default_acl_modified = 1;
293			if (cmd->c_tag == ACL_MASK)
294				default_acl_mask_provided = 1;
295		}
296
297		RETRIEVE_ACL(cmd->c_type);
298
299		/* Check for `X', and replace with `x' as appropriate. */
300		if (cmd->c_perm & CMD_PERM_COND_EXECUTE) {
301			cmd->c_perm &= ~CMD_PERM_COND_EXECUTE;
302			if (S_ISDIR(st->st_mode) || has_execute_perms(*xacl))
303				cmd->c_perm |= CMD_PERM_EXECUTE;
304		}
305
306		switch(cmd->c_cmd) {
307			case CMD_ENTRY_REPLACE:
308				ent = find_entry(*xacl, cmd->c_tag, cmd->c_id);
309				if (!ent) {
310					if (acl_create_entry(xacl, &ent) != 0)
311						goto fail;
312					acl_set_tag_type(ent, cmd->c_tag);
313					if (cmd->c_id != ACL_UNDEFINED_ID)
314						acl_set_qualifier(ent,
315								  &cmd->c_id);
316				}
317				set_perm(ent, cmd->c_perm, ~cmd->c_perm);
318				break;
319
320			case CMD_ENTRY_ADD:
321				ent = find_entry(*xacl, cmd->c_tag, cmd->c_id);
322				if (ent)
323					set_perm(ent, cmd->c_perm, 0);
324				break;
325
326			case CMD_ENTRY_SUBTRACT:
327				ent = find_entry(*xacl, cmd->c_tag, cmd->c_id);
328				if (ent)
329					set_perm(ent, 0, cmd->c_perm);
330				break;
331
332			case CMD_REMOVE_ENTRY:
333				ent = find_entry(*xacl, cmd->c_tag, cmd->c_id);
334				if (ent)
335					acl_delete_entry(*xacl, ent);
336				else
337					/* ignore */;
338				break;
339
340			case CMD_REMOVE_EXTENDED_ACL:
341				remove_extended_entries(acl);
342				break;
343
344			case CMD_REMOVE_ACL:
345				acl_free(*xacl);
346				*xacl = acl_init(5);
347				if (!*xacl)
348					goto fail;
349				break;
350
351			default:
352				errno = EINVAL;
353				goto fail;
354		}
355
356		error = seq_get_cmd(seq, SEQ_NEXT_CMD, &cmd);
357	}
358
359	if (error < 0)
360		goto fail;
361
362	/* Try to fill in missing entries */
363	if (default_acl && acl_entries(default_acl) != 0) {
364		xacl = &acl;
365		old_xacl = &old_acl;
366
367		if (!find_entry(default_acl, ACL_USER_OBJ, ACL_UNDEFINED_ID)) {
368			if (!acl)
369				RETRIEVE_ACL(ACL_TYPE_ACCESS);
370			clone_entry(acl, ACL_USER_OBJ,
371			            &default_acl, ACL_USER_OBJ);
372		}
373		if (!find_entry(default_acl, ACL_GROUP_OBJ, ACL_UNDEFINED_ID)) {
374			if (!acl)
375				RETRIEVE_ACL(ACL_TYPE_ACCESS);
376			clone_entry(acl, ACL_GROUP_OBJ,
377			            &default_acl, ACL_GROUP_OBJ);
378		}
379		if (!find_entry(default_acl, ACL_OTHER, ACL_UNDEFINED_ID)) {
380			if (!acl)
381				RETRIEVE_ACL(ACL_TYPE_ACCESS);
382			clone_entry(acl, ACL_OTHER,
383			            &default_acl, ACL_OTHER);
384		}
385	}
386
387	/* update mask entries and check if ACLs are valid */
388	if (acl && acl_modified) {
389		if (acl_equiv_mode(acl, NULL) != 0) {
390			if (!acl_mask_provided &&
391			    !find_entry(acl, ACL_MASK, ACL_UNDEFINED_ID))
392				clone_entry(acl, ACL_GROUP_OBJ,
393				            &acl, ACL_MASK);
394			if (opt_recalculate != -1 &&
395			    (!acl_mask_provided || opt_recalculate == 1))
396				acl_calc_mask(&acl);
397		}
398
399		error = acl_check(acl, &which_entry);
400		if (error < 0)
401			goto fail;
402		if (error > 0) {
403			acl_text = acl_to_any_text(acl, NULL, ',', 0);
404			fprintf(stderr, gettext("%s: %s: Malformed access ACL "
405				"`%s': %s at entry %d\n"), progname, path_p,
406				acl_text, acl_error(error), which_entry+1);
407			acl_free(acl_text);
408			errors++;
409			goto cleanup;
410		}
411	}
412
413	if (default_acl && acl_entries(default_acl) != 0 &&
414	    default_acl_modified) {
415		if (acl_equiv_mode(default_acl, NULL) != 0) {
416			if (!default_acl_mask_provided &&
417			    !find_entry(default_acl,ACL_MASK,ACL_UNDEFINED_ID))
418				clone_entry(default_acl, ACL_GROUP_OBJ,
419				            &default_acl, ACL_MASK);
420			if (opt_recalculate != -1 &&
421			    (!default_acl_mask_provided ||
422			     opt_recalculate == 1))
423				acl_calc_mask(&default_acl);
424		}
425
426		error = acl_check(default_acl, &which_entry);
427		if (error < 0)
428			goto fail;
429		if (error > 0) {
430			acl_text = acl_to_any_text(default_acl, NULL, ',', 0);
431			fprintf(stderr, gettext("%s: %s: Malformed default ACL "
432			                  "`%s': %s at entry %d\n"),
433				progname, path_p, acl_text,
434				acl_error(error), which_entry+1);
435			acl_free(acl_text);
436			errors++;
437			goto cleanup;
438		}
439	}
440
441	/* Only directores can have default ACLs */
442	if (default_acl && !S_ISDIR(st->st_mode) && opt_recursive) {
443		/* In recursive mode, ignore default ACLs for files */
444		acl_free(default_acl);
445		default_acl = NULL;
446	}
447
448	/* check which ACLs have changed */
449	if (acl && old_acl && acl_cmp(old_acl, acl) == 0) {
450		acl_free(acl);
451		acl = NULL;
452	}
453	if ((default_acl && old_default_acl &&
454	    acl_cmp(old_default_acl, default_acl) == 0)) {
455		acl_free(default_acl);
456		default_acl = NULL;
457	}
458
459	/* update the file system */
460	if (opt_test) {
461		print_test(stdout, path_p, st,
462		           acl, default_acl);
463		goto cleanup;
464	}
465	if (acl) {
466		if (acl_set_file(path_p, ACL_TYPE_ACCESS, acl) != 0) {
467			if (errno == ENOSYS || errno == ENOTSUP) {
468				int saved_errno = errno;
469				mode_t mode;
470
471				if (acl_equiv_mode(acl, &mode) != 0) {
472					errno = saved_errno;
473					goto fail;
474				} else if (chmod(path_p, mode) != 0)
475					goto fail;
476			} else
477				goto fail;
478		}
479	}
480	if (default_acl) {
481		if (S_ISDIR(st->st_mode)) {
482			if (acl_entries(default_acl) == 0) {
483				if (acl_delete_def_file(path_p) != 0 &&
484				    errno != ENOSYS && errno != ENOTSUP)
485					goto fail;
486			} else {
487				if (acl_set_file(path_p, ACL_TYPE_DEFAULT,
488						 default_acl) != 0)
489					goto fail;
490			}
491		} else {
492			if (acl_entries(default_acl) != 0) {
493				fprintf(stderr, gettext(
494						"%s: %s: Only directories "
495						"can have default ACLs\n"),
496					progname, path_p);
497				errors++;
498				goto cleanup;
499			}
500		}
501	}
502
503	error = 0;
504
505cleanup:
506	if (acl)
507		acl_free(acl);
508	if (old_acl)
509		acl_free(old_acl);
510	if (default_acl)
511		acl_free(default_acl);
512	if (old_default_acl)
513		acl_free(old_default_acl);
514	return errors;
515
516fail:
517	fprintf(stderr, "%s: %s: %s\n", progname, path_p, strerror(errno));
518	errors++;
519	goto cleanup;
520}
521
522