merge.c revision 201016
1/*-
2 * Copyright (c) 2001 Chris D. Faulhaber
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR THE VOICES IN HIS HEAD BE
18 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
22 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
23 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
24 * POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#include <sys/cdefs.h>
28__FBSDID("$FreeBSD: head/bin/setfacl/merge.c 201016 2009-12-26 10:06:45Z trasz $");
29
30#include <sys/types.h>
31#include <sys/acl.h>
32#include <sys/stat.h>
33
34#include <err.h>
35#include <stdio.h>
36
37#include "setfacl.h"
38
39static int merge_user_group(acl_entry_t *entry, acl_entry_t *entry_new,
40    int acl_brand);
41
42static int
43merge_user_group(acl_entry_t *entry, acl_entry_t *entry_new, int acl_brand)
44{
45	acl_permset_t permset;
46	acl_entry_type_t entry_type;
47	acl_flagset_t flagset;
48	int have_entry;
49	uid_t *id, *id_new;
50
51	have_entry = 0;
52
53	id = acl_get_qualifier(*entry);
54	if (id == NULL)
55		err(1, "acl_get_qualifier() failed");
56	id_new = acl_get_qualifier(*entry_new);
57	if (id_new == NULL)
58		err(1, "acl_get_qualifier() failed");
59	if (*id == *id_new) {
60		/* any other matches */
61		if (acl_get_permset(*entry, &permset) == -1)
62			err(1, "acl_get_permset() failed");
63		if (acl_set_permset(*entry_new, permset) == -1)
64			err(1, "acl_set_permset() failed");
65
66		if (acl_brand == ACL_BRAND_NFS4) {
67			if (acl_get_entry_type_np(*entry, &entry_type))
68				err(1, "acl_get_entry_type_np() failed");
69			if (acl_set_entry_type_np(*entry_new, entry_type))
70				err(1, "acl_set_entry_type_np() failed");
71			if (acl_get_flagset_np(*entry, &flagset))
72				err(1, "acl_get_flagset_np() failed");
73			if (acl_set_flagset_np(*entry_new, flagset))
74				err(1, "acl_set_flagset_np() failed");
75		}
76
77		have_entry = 1;
78	}
79	acl_free(id);
80	acl_free(id_new);
81
82	return (have_entry);
83}
84
85/*
86 * merge an ACL into existing file's ACL
87 */
88int
89merge_acl(acl_t acl, acl_t *prev_acl, const char *filename)
90{
91	acl_entry_t entry, entry_new;
92	acl_permset_t permset;
93	acl_t acl_new;
94	acl_tag_t tag, tag_new;
95	acl_entry_type_t entry_type, entry_type_new;
96	acl_flagset_t flagset;
97	int entry_id, entry_id_new, have_entry, entry_number = 0;
98	int acl_brand, prev_acl_brand;
99
100	acl_get_brand_np(acl, &acl_brand);
101	acl_get_brand_np(*prev_acl, &prev_acl_brand);
102
103	if (branding_mismatch(acl_brand, prev_acl_brand)) {
104		warnx("%s: branding mismatch; existing ACL is %s, "
105		    "entry to be merged is %s", filename,
106		    brand_name(prev_acl_brand), brand_name(acl_brand));
107		return (-1);
108	}
109
110	acl_new = acl_dup(*prev_acl);
111	if (acl_new == NULL)
112		err(1, "%s: acl_dup() failed", filename);
113
114	entry_id = ACL_FIRST_ENTRY;
115
116	while (acl_get_entry(acl, entry_id, &entry) == 1) {
117		entry_id = ACL_NEXT_ENTRY;
118		have_entry = 0;
119
120		/* keep track of existing ACL_MASK entries */
121		if (acl_get_tag_type(entry, &tag) == -1)
122			err(1, "%s: acl_get_tag_type() failed - "
123			    "invalid ACL entry", filename);
124		if (tag == ACL_MASK)
125			have_mask = 1;
126
127		/* check against the existing ACL entries */
128		entry_id_new = ACL_FIRST_ENTRY;
129		while (acl_get_entry(acl_new, entry_id_new, &entry_new) == 1) {
130			entry_id_new = ACL_NEXT_ENTRY;
131
132			if (acl_get_tag_type(entry, &tag) == -1)
133				err(1, "%s: acl_get_tag_type() failed",
134				    filename);
135			if (acl_get_tag_type(entry_new, &tag_new) == -1)
136				err(1, "%s: acl_get_tag_type() failed",
137				    filename);
138			if (tag != tag_new)
139				continue;
140
141			/*
142			 * For NFSv4, in addition to "tag" and "id" we also
143			 * compare "entry_type".
144			 */
145			if (acl_brand == ACL_BRAND_NFS4) {
146				if (acl_get_entry_type_np(entry, &entry_type))
147					err(1, "%s: acl_get_entry_type_np() "
148					    "failed", filename);
149				if (acl_get_entry_type_np(entry_new, &entry_type_new))
150					err(1, "%s: acl_get_entry_type_np() "
151					    "failed", filename);
152				if (entry_type != entry_type_new)
153					continue;
154			}
155
156			switch(tag) {
157			case ACL_USER:
158			case ACL_GROUP:
159				have_entry = merge_user_group(&entry,
160				    &entry_new, acl_brand);
161				if (have_entry == 0)
162					break;
163				/* FALLTHROUGH */
164			case ACL_USER_OBJ:
165			case ACL_GROUP_OBJ:
166			case ACL_OTHER:
167			case ACL_MASK:
168			case ACL_EVERYONE:
169				if (acl_get_permset(entry, &permset) == -1)
170					err(1, "%s: acl_get_permset() failed",
171					    filename);
172				if (acl_set_permset(entry_new, permset) == -1)
173					err(1, "%s: acl_set_permset() failed",
174					    filename);
175
176				if (acl_brand == ACL_BRAND_NFS4) {
177					if (acl_get_entry_type_np(entry, &entry_type))
178						err(1, "%s: acl_get_entry_type_np() failed",
179						    filename);
180					if (acl_set_entry_type_np(entry_new, entry_type))
181						err(1, "%s: acl_set_entry_type_np() failed",
182						    filename);
183					if (acl_get_flagset_np(entry, &flagset))
184						err(1, "%s: acl_get_flagset_np() failed",
185						    filename);
186					if (acl_set_flagset_np(entry_new, flagset))
187						err(1, "%s: acl_set_flagset_np() failed",
188						    filename);
189				}
190				have_entry = 1;
191				break;
192			default:
193				/* should never be here */
194				errx(1, "%s: invalid tag type: %i", filename, tag);
195				break;
196			}
197		}
198
199		/* if this entry has not been found, it must be new */
200		if (have_entry == 0) {
201
202			/*
203			 * NFSv4 ACL entries must be prepended to the ACL.
204			 * Appending them at the end makes no sense, since
205			 * in most cases they wouldn't even get evaluated.
206			 */
207			if (acl_brand == ACL_BRAND_NFS4) {
208				if (acl_create_entry_np(&acl_new, &entry_new, entry_number) == -1) {
209					warn("%s: acl_create_entry_np() failed", filename);
210					acl_free(acl_new);
211					return (-1);
212				}
213				/*
214				 * Without this increment, adding several
215				 * entries at once, for example
216				 * "setfacl -m user:1:r:allow,user:2:r:allow",
217				 * would make them appear in reverse order.
218				 */
219				entry_number++;
220			} else {
221				if (acl_create_entry(&acl_new, &entry_new) == -1) {
222					warn("%s: acl_create_entry() failed", filename);
223					acl_free(acl_new);
224					return (-1);
225				}
226			}
227			if (acl_copy_entry(entry_new, entry) == -1)
228				err(1, "%s: acl_copy_entry() failed", filename);
229		}
230	}
231
232	acl_free(*prev_acl);
233	*prev_acl = acl_new;
234
235	return (0);
236}
237
238int
239add_acl(acl_t acl, uint entry_number, acl_t *prev_acl, const char *filename)
240{
241	acl_entry_t entry, entry_new;
242	acl_t acl_new;
243	int entry_id, acl_brand, prev_acl_brand;
244
245	acl_get_brand_np(acl, &acl_brand);
246	acl_get_brand_np(*prev_acl, &prev_acl_brand);
247
248	if (prev_acl_brand != ACL_BRAND_NFS4) {
249		warnx("%s: the '-a' option is only applicable to NFSv4 ACLs",
250		    filename);
251		return (-1);
252	}
253
254	if (branding_mismatch(acl_brand, ACL_BRAND_NFS4)) {
255		warnx("%s: branding mismatch; existing ACL is NFSv4, "
256		    "entry to be added is %s", filename,
257		    brand_name(acl_brand));
258		return (-1);
259	}
260
261	acl_new = acl_dup(*prev_acl);
262	if (acl_new == NULL)
263		err(1, "%s: acl_dup() failed", filename);
264
265	entry_id = ACL_FIRST_ENTRY;
266
267	while (acl_get_entry(acl, entry_id, &entry) == 1) {
268		entry_id = ACL_NEXT_ENTRY;
269
270		if (acl_create_entry_np(&acl_new, &entry_new, entry_number) == -1) {
271			warn("%s: acl_create_entry_np() failed", filename);
272			acl_free(acl_new);
273			return (-1);
274		}
275
276		/*
277		 * Without this increment, adding several
278		 * entries at once, for example
279		 * "setfacl -m user:1:r:allow,user:2:r:allow",
280		 * would make them appear in reverse order.
281		 */
282		entry_number++;
283
284		if (acl_copy_entry(entry_new, entry) == -1)
285			err(1, "%s: acl_copy_entry() failed", filename);
286	}
287
288	acl_free(*prev_acl);
289	*prev_acl = acl_new;
290
291	return (0);
292}
293