1139969Simp/*-
274465Srwatson * Copyright (c) 2001 Chris D. Faulhaber
374465Srwatson * All rights reserved.
474465Srwatson *
574465Srwatson * Redistribution and use in source and binary forms, with or without
674465Srwatson * modification, are permitted provided that the following conditions
774465Srwatson * are met:
874465Srwatson * 1. Redistributions of source code must retain the above copyright
974465Srwatson *    notice, this list of conditions and the following disclaimer.
1074465Srwatson * 2. Redistributions in binary form must reproduce the above copyright
1174465Srwatson *    notice, this list of conditions and the following disclaimer in the
1274465Srwatson *    documentation and/or other materials provided with the distribution.
1374465Srwatson *
1474465Srwatson * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
1574465Srwatson * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
1674465Srwatson * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17204819Sjoel * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18204819Sjoel * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19204819Sjoel * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20204819Sjoel * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21204819Sjoel * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22204819Sjoel * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23204819Sjoel * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24204819Sjoel * SUCH DAMAGE.
2574465Srwatson */
2674465Srwatson
2799110Sobrien#include <sys/cdefs.h>
2899110Sobrien__FBSDID("$FreeBSD$");
2999110Sobrien
3074465Srwatson#include <sys/types.h>
3174465Srwatson#include <sys/acl.h>
3274465Srwatson#include <sys/stat.h>
3374465Srwatson
3474465Srwatson#include <err.h>
3574465Srwatson#include <stdio.h>
3674465Srwatson
3774465Srwatson#include "setfacl.h"
3874465Srwatson
39196936Straszstatic int merge_user_group(acl_entry_t *entry, acl_entry_t *entry_new,
40196936Strasz    int acl_brand);
4187260Sjedgar
4287260Sjedgarstatic int
43196936Straszmerge_user_group(acl_entry_t *entry, acl_entry_t *entry_new, int acl_brand)
4487260Sjedgar{
4587260Sjedgar	acl_permset_t permset;
46196936Strasz	acl_entry_type_t entry_type;
47196936Strasz	acl_flagset_t flagset;
4887260Sjedgar	int have_entry;
4987260Sjedgar	uid_t *id, *id_new;
5087260Sjedgar
5187260Sjedgar	have_entry = 0;
5287260Sjedgar
5387260Sjedgar	id = acl_get_qualifier(*entry);
5487260Sjedgar	if (id == NULL)
5587260Sjedgar		err(1, "acl_get_qualifier() failed");
5687260Sjedgar	id_new = acl_get_qualifier(*entry_new);
5787260Sjedgar	if (id_new == NULL)
5887260Sjedgar		err(1, "acl_get_qualifier() failed");
5987260Sjedgar	if (*id == *id_new) {
6087260Sjedgar		/* any other matches */
6187260Sjedgar		if (acl_get_permset(*entry, &permset) == -1)
6287260Sjedgar			err(1, "acl_get_permset() failed");
6387260Sjedgar		if (acl_set_permset(*entry_new, permset) == -1)
6487260Sjedgar			err(1, "acl_set_permset() failed");
65196936Strasz
66196936Strasz		if (acl_brand == ACL_BRAND_NFS4) {
67196936Strasz			if (acl_get_entry_type_np(*entry, &entry_type))
68196936Strasz				err(1, "acl_get_entry_type_np() failed");
69196936Strasz			if (acl_set_entry_type_np(*entry_new, entry_type))
70196936Strasz				err(1, "acl_set_entry_type_np() failed");
71196936Strasz			if (acl_get_flagset_np(*entry, &flagset))
72196936Strasz				err(1, "acl_get_flagset_np() failed");
73196936Strasz			if (acl_set_flagset_np(*entry_new, flagset))
74196936Strasz				err(1, "acl_set_flagset_np() failed");
75196936Strasz		}
76196936Strasz
7787260Sjedgar		have_entry = 1;
7887260Sjedgar	}
7987260Sjedgar	acl_free(id);
8087260Sjedgar	acl_free(id_new);
8187260Sjedgar
8287260Sjedgar	return (have_entry);
8387260Sjedgar}
8487260Sjedgar
8587254Sjedgar/*
8687254Sjedgar * merge an ACL into existing file's ACL
8787254Sjedgar */
8874465Srwatsonint
89196936Straszmerge_acl(acl_t acl, acl_t *prev_acl, const char *filename)
9074465Srwatson{
9175928Sjedgar	acl_entry_t entry, entry_new;
9275928Sjedgar	acl_permset_t permset;
9374465Srwatson	acl_t acl_new;
9475928Sjedgar	acl_tag_t tag, tag_new;
95196936Strasz	acl_entry_type_t entry_type, entry_type_new;
96196936Strasz	acl_flagset_t flagset;
97240087Strasz	int entry_id, entry_id_new, have_entry, had_entry, entry_number = 0;
98196936Strasz	int acl_brand, prev_acl_brand;
9974465Srwatson
100196936Strasz	acl_get_brand_np(acl, &acl_brand);
101196936Strasz	acl_get_brand_np(*prev_acl, &prev_acl_brand);
102196936Strasz
103201016Strasz	if (branding_mismatch(acl_brand, prev_acl_brand)) {
104196936Strasz		warnx("%s: branding mismatch; existing ACL is %s, "
105196936Strasz		    "entry to be merged is %s", filename,
106201016Strasz		    brand_name(prev_acl_brand), brand_name(acl_brand));
107196936Strasz		return (-1);
108196936Strasz	}
109196936Strasz
110196936Strasz	acl_new = acl_dup(*prev_acl);
11187254Sjedgar	if (acl_new == NULL)
112196936Strasz		err(1, "%s: acl_dup() failed", filename);
11374465Srwatson
11475928Sjedgar	entry_id = ACL_FIRST_ENTRY;
11575928Sjedgar
11675928Sjedgar	while (acl_get_entry(acl, entry_id, &entry) == 1) {
11775928Sjedgar		entry_id = ACL_NEXT_ENTRY;
11874465Srwatson		have_entry = 0;
119240087Strasz		had_entry = 0;
12074465Srwatson
12175928Sjedgar		/* keep track of existing ACL_MASK entries */
12275928Sjedgar		if (acl_get_tag_type(entry, &tag) == -1)
123196936Strasz			err(1, "%s: acl_get_tag_type() failed - "
124196936Strasz			    "invalid ACL entry", filename);
12575928Sjedgar		if (tag == ACL_MASK)
12674465Srwatson			have_mask = 1;
12774465Srwatson
12874465Srwatson		/* check against the existing ACL entries */
12975928Sjedgar		entry_id_new = ACL_FIRST_ENTRY;
130196936Strasz		while (acl_get_entry(acl_new, entry_id_new, &entry_new) == 1) {
13175928Sjedgar			entry_id_new = ACL_NEXT_ENTRY;
13275928Sjedgar
13375928Sjedgar			if (acl_get_tag_type(entry, &tag) == -1)
134196936Strasz				err(1, "%s: acl_get_tag_type() failed",
135196936Strasz				    filename);
13675928Sjedgar			if (acl_get_tag_type(entry_new, &tag_new) == -1)
137196936Strasz				err(1, "%s: acl_get_tag_type() failed",
138196936Strasz				    filename);
13975928Sjedgar			if (tag != tag_new)
14075928Sjedgar				continue;
14175928Sjedgar
142196936Strasz			/*
143196936Strasz			 * For NFSv4, in addition to "tag" and "id" we also
144196936Strasz			 * compare "entry_type".
145196936Strasz			 */
146196936Strasz			if (acl_brand == ACL_BRAND_NFS4) {
147196936Strasz				if (acl_get_entry_type_np(entry, &entry_type))
148196936Strasz					err(1, "%s: acl_get_entry_type_np() "
149196936Strasz					    "failed", filename);
150196936Strasz				if (acl_get_entry_type_np(entry_new, &entry_type_new))
151196936Strasz					err(1, "%s: acl_get_entry_type_np() "
152196936Strasz					    "failed", filename);
153196936Strasz				if (entry_type != entry_type_new)
154196936Strasz					continue;
155196936Strasz			}
156196936Strasz
15775928Sjedgar			switch(tag) {
15875928Sjedgar			case ACL_USER:
15975928Sjedgar			case ACL_GROUP:
16087260Sjedgar				have_entry = merge_user_group(&entry,
161196936Strasz				    &entry_new, acl_brand);
16287254Sjedgar				if (have_entry == 0)
16374465Srwatson					break;
16475928Sjedgar				/* FALLTHROUGH */
16575928Sjedgar			case ACL_USER_OBJ:
16675928Sjedgar			case ACL_GROUP_OBJ:
16775928Sjedgar			case ACL_OTHER:
16875928Sjedgar			case ACL_MASK:
169196936Strasz			case ACL_EVERYONE:
17075928Sjedgar				if (acl_get_permset(entry, &permset) == -1)
171196936Strasz					err(1, "%s: acl_get_permset() failed",
172196936Strasz					    filename);
17375928Sjedgar				if (acl_set_permset(entry_new, permset) == -1)
174196936Strasz					err(1, "%s: acl_set_permset() failed",
175196936Strasz					    filename);
176196936Strasz
177196936Strasz				if (acl_brand == ACL_BRAND_NFS4) {
178196936Strasz					if (acl_get_entry_type_np(entry, &entry_type))
179196936Strasz						err(1, "%s: acl_get_entry_type_np() failed",
180196936Strasz						    filename);
181196936Strasz					if (acl_set_entry_type_np(entry_new, entry_type))
182196936Strasz						err(1, "%s: acl_set_entry_type_np() failed",
183196936Strasz						    filename);
184196936Strasz					if (acl_get_flagset_np(entry, &flagset))
185196936Strasz						err(1, "%s: acl_get_flagset_np() failed",
186196936Strasz						    filename);
187196936Strasz					if (acl_set_flagset_np(entry_new, flagset))
188196936Strasz						err(1, "%s: acl_set_flagset_np() failed",
189196936Strasz						    filename);
190196936Strasz				}
191240087Strasz				had_entry = have_entry = 1;
19275928Sjedgar				break;
19375928Sjedgar			default:
19475928Sjedgar				/* should never be here */
195196936Strasz				errx(1, "%s: invalid tag type: %i", filename, tag);
19675928Sjedgar				break;
19774465Srwatson			}
19874465Srwatson		}
19974465Srwatson
20074465Srwatson		/* if this entry has not been found, it must be new */
201240087Strasz		if (had_entry == 0) {
202196936Strasz
203196936Strasz			/*
204196936Strasz			 * NFSv4 ACL entries must be prepended to the ACL.
205196936Strasz			 * Appending them at the end makes no sense, since
206196936Strasz			 * in most cases they wouldn't even get evaluated.
207196936Strasz			 */
208196936Strasz			if (acl_brand == ACL_BRAND_NFS4) {
209196936Strasz				if (acl_create_entry_np(&acl_new, &entry_new, entry_number) == -1) {
210196936Strasz					warn("%s: acl_create_entry_np() failed", filename);
211196936Strasz					acl_free(acl_new);
212196936Strasz					return (-1);
213196936Strasz				}
214196936Strasz				/*
215196936Strasz				 * Without this increment, adding several
216196936Strasz				 * entries at once, for example
217196936Strasz				 * "setfacl -m user:1:r:allow,user:2:r:allow",
218196936Strasz				 * would make them appear in reverse order.
219196936Strasz				 */
220196936Strasz				entry_number++;
221196936Strasz			} else {
222196936Strasz				if (acl_create_entry(&acl_new, &entry_new) == -1) {
223196936Strasz					warn("%s: acl_create_entry() failed", filename);
224196936Strasz					acl_free(acl_new);
225196936Strasz					return (-1);
226196936Strasz				}
22774465Srwatson			}
22875928Sjedgar			if (acl_copy_entry(entry_new, entry) == -1)
229196936Strasz				err(1, "%s: acl_copy_entry() failed", filename);
23074465Srwatson		}
23174465Srwatson	}
23274465Srwatson
233196936Strasz	acl_free(*prev_acl);
234196936Strasz	*prev_acl = acl_new;
235196936Strasz
236196936Strasz	return (0);
237196936Strasz}
238196936Strasz
239196936Straszint
240196936Straszadd_acl(acl_t acl, uint entry_number, acl_t *prev_acl, const char *filename)
241196936Strasz{
242196936Strasz	acl_entry_t entry, entry_new;
243196936Strasz	acl_t acl_new;
244196936Strasz	int entry_id, acl_brand, prev_acl_brand;
245196936Strasz
246196936Strasz	acl_get_brand_np(acl, &acl_brand);
247196936Strasz	acl_get_brand_np(*prev_acl, &prev_acl_brand);
248196936Strasz
249196936Strasz	if (prev_acl_brand != ACL_BRAND_NFS4) {
250196936Strasz		warnx("%s: the '-a' option is only applicable to NFSv4 ACLs",
251196936Strasz		    filename);
252196936Strasz		return (-1);
25375928Sjedgar	}
25475928Sjedgar
255201016Strasz	if (branding_mismatch(acl_brand, ACL_BRAND_NFS4)) {
256196936Strasz		warnx("%s: branding mismatch; existing ACL is NFSv4, "
257201016Strasz		    "entry to be added is %s", filename,
258201016Strasz		    brand_name(acl_brand));
259196936Strasz		return (-1);
260196936Strasz	}
261196936Strasz
262196936Strasz	acl_new = acl_dup(*prev_acl);
263196936Strasz	if (acl_new == NULL)
264196936Strasz		err(1, "%s: acl_dup() failed", filename);
265196936Strasz
266196936Strasz	entry_id = ACL_FIRST_ENTRY;
267196936Strasz
268196936Strasz	while (acl_get_entry(acl, entry_id, &entry) == 1) {
269196936Strasz		entry_id = ACL_NEXT_ENTRY;
270196936Strasz
271196936Strasz		if (acl_create_entry_np(&acl_new, &entry_new, entry_number) == -1) {
272196936Strasz			warn("%s: acl_create_entry_np() failed", filename);
273196936Strasz			acl_free(acl_new);
274196936Strasz			return (-1);
275196936Strasz		}
276196936Strasz
277196936Strasz		/*
278196936Strasz		 * Without this increment, adding several
279196936Strasz		 * entries at once, for example
280196936Strasz		 * "setfacl -m user:1:r:allow,user:2:r:allow",
281196936Strasz		 * would make them appear in reverse order.
282196936Strasz		 */
283196936Strasz		entry_number++;
284196936Strasz
285196936Strasz		if (acl_copy_entry(entry_new, entry) == -1)
286196936Strasz			err(1, "%s: acl_copy_entry() failed", filename);
287196936Strasz	}
288196936Strasz
289196936Strasz	acl_free(*prev_acl);
290196936Strasz	*prev_acl = acl_new;
291196936Strasz
29287254Sjedgar	return (0);
29374465Srwatson}
294