test_acl_freebsd_posix1e.c revision 306379
1/*-
2 * Copyright (c) 2003-2008 Tim Kientzle
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(S) ``AS IS'' AND ANY EXPRESS OR
15 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
16 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
17 * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
18 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
19 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
20 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
21 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
23 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25#include "test.h"
26__FBSDID("$FreeBSD: head/lib/libarchive/test/test_acl_freebsd.c 189427 2009-03-06 04:21:23Z kientzle $");
27
28#if defined(__FreeBSD__) && __FreeBSD__ > 4
29#include <sys/acl.h>
30
31struct myacl_t {
32	int type;  /* Type of ACL: "access" or "default" */
33	int permset; /* Permissions for this class of users. */
34	int tag; /* Owner, User, Owning group, group, other, etc. */
35	int qual; /* GID or UID of user/group, depending on tag. */
36	const char *name; /* Name of user/group, depending on tag. */
37};
38
39static struct myacl_t acls2[] = {
40	{ ARCHIVE_ENTRY_ACL_TYPE_ACCESS, ARCHIVE_ENTRY_ACL_EXECUTE | ARCHIVE_ENTRY_ACL_READ,
41	  ARCHIVE_ENTRY_ACL_USER_OBJ, -1, "" },
42	{ ARCHIVE_ENTRY_ACL_TYPE_ACCESS, ARCHIVE_ENTRY_ACL_READ,
43	  ARCHIVE_ENTRY_ACL_USER, 77, "user77" },
44	{ ARCHIVE_ENTRY_ACL_TYPE_ACCESS, 0,
45	  ARCHIVE_ENTRY_ACL_USER, 78, "user78" },
46	{ ARCHIVE_ENTRY_ACL_TYPE_ACCESS, ARCHIVE_ENTRY_ACL_READ,
47	  ARCHIVE_ENTRY_ACL_GROUP_OBJ, -1, "" },
48	{ ARCHIVE_ENTRY_ACL_TYPE_ACCESS, 0007,
49	  ARCHIVE_ENTRY_ACL_GROUP, 78, "group78" },
50	{ ARCHIVE_ENTRY_ACL_TYPE_ACCESS,
51	  ARCHIVE_ENTRY_ACL_WRITE | ARCHIVE_ENTRY_ACL_EXECUTE,
52	  ARCHIVE_ENTRY_ACL_OTHER, -1, "" },
53	{ ARCHIVE_ENTRY_ACL_TYPE_ACCESS,
54	  ARCHIVE_ENTRY_ACL_WRITE | ARCHIVE_ENTRY_ACL_READ | ARCHIVE_ENTRY_ACL_EXECUTE,
55	  ARCHIVE_ENTRY_ACL_MASK, -1, "" },
56	{ 0, 0, 0, 0, NULL }
57};
58
59static void
60set_acls(struct archive_entry *ae, struct myacl_t *acls)
61{
62	int i;
63
64	archive_entry_acl_clear(ae);
65	for (i = 0; acls[i].name != NULL; i++) {
66		archive_entry_acl_add_entry(ae,
67		    acls[i].type, acls[i].permset, acls[i].tag, acls[i].qual,
68		    acls[i].name);
69	}
70}
71
72static int
73acl_entry_get_perm(acl_entry_t aclent) {
74	int permset = 0;
75	acl_permset_t opaque_ps;
76
77	/* translate the silly opaque permset to a bitmap */
78	acl_get_permset(aclent, &opaque_ps);
79	if (acl_get_perm_np(opaque_ps, ACL_EXECUTE))
80		permset |= ARCHIVE_ENTRY_ACL_EXECUTE;
81	if (acl_get_perm_np(opaque_ps, ACL_WRITE))
82		permset |= ARCHIVE_ENTRY_ACL_WRITE;
83	if (acl_get_perm_np(opaque_ps, ACL_READ))
84		permset |= ARCHIVE_ENTRY_ACL_READ;
85	return permset;
86}
87
88#if 0
89static int
90acl_get_specific_entry(acl_t acl, acl_tag_t requested_tag_type, int requested_tag) {
91	int entry_id = ACL_FIRST_ENTRY;
92	acl_entry_t acl_entry;
93	acl_tag_t acl_tag_type;
94
95	while (1 == acl_get_entry(acl, entry_id, &acl_entry)) {
96		/* After the first time... */
97		entry_id = ACL_NEXT_ENTRY;
98
99		/* If this matches, return perm mask */
100		acl_get_tag_type(acl_entry, &acl_tag_type);
101		if (acl_tag_type == requested_tag_type) {
102			switch (acl_tag_type) {
103			case ACL_USER_OBJ:
104				if ((uid_t)requested_tag == *(uid_t *)(acl_get_qualifier(acl_entry))) {
105					return acl_entry_get_perm(acl_entry);
106				}
107				break;
108			case ACL_GROUP_OBJ:
109				if ((gid_t)requested_tag == *(gid_t *)(acl_get_qualifier(acl_entry))) {
110					return acl_entry_get_perm(acl_entry);
111				}
112				break;
113			case ACL_USER:
114			case ACL_GROUP:
115			case ACL_OTHER:
116				return acl_entry_get_perm(acl_entry);
117			default:
118				failure("Unexpected ACL tag type");
119				assert(0);
120			}
121		}
122
123
124	}
125	return -1;
126}
127#endif
128
129static int
130acl_match(acl_entry_t aclent, struct myacl_t *myacl)
131{
132	gid_t g, *gp;
133	uid_t u, *up;
134	acl_tag_t tag_type;
135
136	if (myacl->permset != acl_entry_get_perm(aclent))
137		return (0);
138
139	acl_get_tag_type(aclent, &tag_type);
140	switch (tag_type) {
141	case ACL_USER_OBJ:
142		if (myacl->tag != ARCHIVE_ENTRY_ACL_USER_OBJ) return (0);
143		break;
144	case ACL_USER:
145		if (myacl->tag != ARCHIVE_ENTRY_ACL_USER)
146			return (0);
147		up = acl_get_qualifier(aclent);
148		u = *up;
149		acl_free(up);
150		if ((uid_t)myacl->qual != u)
151			return (0);
152		break;
153	case ACL_GROUP_OBJ:
154		if (myacl->tag != ARCHIVE_ENTRY_ACL_GROUP_OBJ) return (0);
155		break;
156	case ACL_GROUP:
157		if (myacl->tag != ARCHIVE_ENTRY_ACL_GROUP)
158			return (0);
159		gp = acl_get_qualifier(aclent);
160		g = *gp;
161		acl_free(gp);
162		if ((gid_t)myacl->qual != g)
163			return (0);
164		break;
165	case ACL_MASK:
166		if (myacl->tag != ARCHIVE_ENTRY_ACL_MASK) return (0);
167		break;
168	case ACL_OTHER:
169		if (myacl->tag != ARCHIVE_ENTRY_ACL_OTHER) return (0);
170		break;
171	}
172	return (1);
173}
174
175static void
176compare_acls(acl_t acl, struct myacl_t *myacls)
177{
178	int *marker;
179	int entry_id = ACL_FIRST_ENTRY;
180	int matched;
181	int i, n;
182	acl_entry_t acl_entry;
183
184	/* Count ACL entries in myacls array and allocate an indirect array. */
185	for (n = 0; myacls[n].name != NULL; ++n)
186		continue;
187	if (n) {
188		marker = malloc(sizeof(marker[0]) * n);
189		if (marker == NULL)
190			return;
191		for (i = 0; i < n; i++)
192			marker[i] = i;
193	} else
194		marker = NULL;
195
196	/*
197	 * Iterate over acls in system acl object, try to match each
198	 * one with an item in the myacls array.
199	 */
200	while (1 == acl_get_entry(acl, entry_id, &acl_entry)) {
201		/* After the first time... */
202		entry_id = ACL_NEXT_ENTRY;
203
204		/* Search for a matching entry (tag and qualifier) */
205		for (i = 0, matched = 0; i < n && !matched; i++) {
206			if (acl_match(acl_entry, &myacls[marker[i]])) {
207				/* We found a match; remove it. */
208				marker[i] = marker[n - 1];
209				n--;
210				matched = 1;
211			}
212		}
213
214		/* TODO: Print out more details in this case. */
215		failure("ACL entry on file that shouldn't be there");
216		assert(matched == 1);
217	}
218
219	/* Dump entries in the myacls array that weren't in the system acl. */
220	for (i = 0; i < n; ++i) {
221		failure(" ACL entry missing from file: "
222		    "type=%d,permset=%d,tag=%d,qual=%d,name=``%s''\n",
223		    myacls[marker[i]].type, myacls[marker[i]].permset,
224		    myacls[marker[i]].tag, myacls[marker[i]].qual,
225		    myacls[marker[i]].name);
226		assert(0); /* Record this as a failure. */
227	}
228	free(marker);
229}
230
231#endif
232
233
234/*
235 * Verify ACL restore-to-disk.  This test is FreeBSD-specific.
236 */
237
238DEFINE_TEST(test_acl_freebsd_posix1e_restore)
239{
240#if !defined(__FreeBSD__)
241	skipping("FreeBSD-specific ACL restore test");
242#elif __FreeBSD__ < 5
243	skipping("ACL restore supported only on FreeBSD 5.0 and later");
244#else
245	struct stat st;
246	struct archive *a;
247	struct archive_entry *ae;
248	int n, fd;
249	acl_t acl;
250
251	/*
252	 * First, do a quick manual set/read of ACL data to
253	 * verify that the local filesystem does support ACLs.
254	 * If it doesn't, we'll simply skip the remaining tests.
255	 */
256	acl = acl_from_text("u::rwx,u:1:rw,g::rwx,g:15:rx,o::rwx,m::rwx");
257	assert((void *)acl != NULL);
258	/* Create a test file and try to set an ACL on it. */
259	fd = open("pretest", O_WRONLY | O_CREAT | O_EXCL, 0777);
260	failure("Could not create test file?!");
261	if (!assert(fd >= 0)) {
262		acl_free(acl);
263		return;
264	}
265
266	n = acl_set_fd(fd, acl);
267	acl_free(acl);
268	if (n != 0 && errno == EOPNOTSUPP) {
269		close(fd);
270		skipping("ACL tests require that ACL support be enabled on the filesystem");
271		return;
272	}
273	if (n != 0 && errno == EINVAL) {
274		close(fd);
275		skipping("This filesystem does not support POSIX.1e ACLs");
276		return;
277	}
278	failure("acl_set_fd(): errno = %d (%s)",
279	    errno, strerror(errno));
280	assertEqualInt(0, n);
281	close(fd);
282
283	/* Create a write-to-disk object. */
284	assert(NULL != (a = archive_write_disk_new()));
285	archive_write_disk_set_options(a,
286	    ARCHIVE_EXTRACT_TIME | ARCHIVE_EXTRACT_PERM | ARCHIVE_EXTRACT_ACL);
287
288	/* Populate an archive entry with some metadata, including ACL info */
289	ae = archive_entry_new();
290	assert(ae != NULL);
291	archive_entry_set_pathname(ae, "test0");
292	archive_entry_set_mtime(ae, 123456, 7890);
293	archive_entry_set_size(ae, 0);
294	set_acls(ae, acls2);
295	assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae));
296	archive_entry_free(ae);
297
298	/* Close the archive. */
299	assertEqualIntA(a, ARCHIVE_OK, archive_write_close(a));
300	assertEqualInt(ARCHIVE_OK, archive_write_free(a));
301
302	/* Verify the data on disk. */
303	assertEqualInt(0, stat("test0", &st));
304	assertEqualInt(st.st_mtime, 123456);
305	acl = acl_get_file("test0", ACL_TYPE_ACCESS);
306	assert(acl != (acl_t)NULL);
307	compare_acls(acl, acls2);
308	acl_free(acl);
309#endif
310}
311
312/*
313 * Verify ACL reaed-from-disk.  This test is FreeBSD-specific.
314 */
315DEFINE_TEST(test_acl_freebsd_posix1e_read)
316{
317#if !defined(__FreeBSD__)
318	skipping("FreeBSD-specific ACL read test");
319#elif __FreeBSD__ < 5
320	skipping("ACL read supported only on FreeBSD 5.0 and later");
321#else
322	struct archive *a;
323	struct archive_entry *ae;
324	int n, fd;
325	const char *acl1_text, *acl2_text;
326	acl_t acl1, acl2;
327
328	/*
329	 * Manually construct a directory and two files with
330	 * different ACLs.  This also serves to verify that ACLs
331	 * are supported on the local filesystem.
332	 */
333
334	/* Create a test file f1 with acl1 */
335	acl1_text = "user::rwx,group::rwx,other::rwx,user:1:rw-,group:15:r-x,mask::rwx";
336	acl1 = acl_from_text(acl1_text);
337	assert((void *)acl1 != NULL);
338	fd = open("f1", O_WRONLY | O_CREAT | O_EXCL, 0777);
339	failure("Could not create test file?!");
340	if (!assert(fd >= 0)) {
341		acl_free(acl1);
342		return;
343	}
344	n = acl_set_fd(fd, acl1);
345	acl_free(acl1);
346	if (n != 0 && errno == EOPNOTSUPP) {
347		close(fd);
348		skipping("ACL tests require that ACL support be enabled on the filesystem");
349		return;
350	}
351	if (n != 0 && errno == EINVAL) {
352		close(fd);
353		skipping("This filesystem does not support POSIX.1e ACLs");
354		return;
355	}
356	failure("acl_set_fd(): errno = %d (%s)",
357	    errno, strerror(errno));
358	assertEqualInt(0, n);
359	close(fd);
360
361	assertMakeDir("d", 0700);
362
363	/*
364	 * Create file d/f1 with acl2
365	 *
366	 * This differs from acl1 in the u:1: and g:15: permissions.
367	 *
368	 * This file deliberately has the same name but a different ACL.
369	 * Github Issue #777 explains how libarchive's directory traversal
370	 * did not always correctly enter directories before attempting
371	 * to read ACLs, resulting in reading the ACL from a like-named
372	 * file in the wrong directory.
373	 */
374	acl2_text = "user::rwx,group::rwx,other::---,user:1:r--,group:15:r--,mask::rwx";
375	acl2 = acl_from_text(acl2_text);
376	assert((void *)acl2 != NULL);
377	fd = open("d/f1", O_WRONLY | O_CREAT | O_EXCL, 0777);
378	failure("Could not create test file?!");
379	if (!assert(fd >= 0)) {
380		acl_free(acl2);
381		return;
382	}
383	n = acl_set_fd(fd, acl2);
384	acl_free(acl2);
385	if (n != 0 && errno == EOPNOTSUPP) {
386		close(fd);
387		skipping("ACL tests require that ACL support be enabled on the filesystem");
388		return;
389	}
390	if (n != 0 && errno == EINVAL) {
391		close(fd);
392		skipping("This filesystem does not support POSIX.1e ACLs");
393		return;
394	}
395	failure("acl_set_fd(): errno = %d (%s)",
396	    errno, strerror(errno));
397	assertEqualInt(0, n);
398	close(fd);
399
400	/* Create a read-from-disk object. */
401	assert(NULL != (a = archive_read_disk_new()));
402	assertEqualIntA(a, ARCHIVE_OK, archive_read_disk_open(a, "."));
403	assert(NULL != (ae = archive_entry_new()));
404
405	/* Walk the dir until we see both of the files */
406	while (ARCHIVE_OK == archive_read_next_header2(a, ae)) {
407		archive_read_disk_descend(a);
408		if (strcmp(archive_entry_pathname(ae), "./f1") == 0) {
409			assertEqualString(archive_entry_acl_text(ae, ARCHIVE_ENTRY_ACL_TYPE_ACCESS), acl1_text);
410
411		} else if (strcmp(archive_entry_pathname(ae), "./d/f1") == 0) {
412			assertEqualString(archive_entry_acl_text(ae, ARCHIVE_ENTRY_ACL_TYPE_ACCESS), acl2_text);
413		}
414	}
415
416	archive_free(a);
417#endif
418}
419