1/*
2 * Copyright (c) 2004, 2010, 2011 Apple Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24#include <sys/appleapiopts.h>
25#include <sys/types.h>
26#include <sys/acl.h>
27#include <sys/fcntl.h>
28#include <errno.h>
29#include <stdio.h>
30#include <stdarg.h>
31#include <stdlib.h>
32#include <string.h>
33#include <strings.h>
34#include <membership.h>
35#include <membershipPriv.h>
36#include <pwd.h>
37#include <grp.h>
38
39#include <libkern/OSByteOrder.h>
40
41#include "aclvar.h"
42
43/*
44 * NOTE: the copy_int/copy_ext functions are duplicated here, one version of each for
45 * each of native and portable endianity.  A more elegant solution might be called for
46 * if the functions become much more complicated.
47 */
48
49/*
50 * acl_t -> external representation, portable endianity
51 */
52ssize_t
53acl_copy_ext(void *buf, acl_t acl, ssize_t size)
54{
55	struct kauth_filesec *ext = (struct kauth_filesec *)buf;
56	ssize_t		reqsize;
57	int		i;
58
59	/* validate arguments, compute required size */
60	reqsize = acl_size(acl);
61	if (reqsize < 0)
62		return(-1);
63	if (reqsize > size) {
64		errno = ERANGE;
65		return(-1);
66	}
67
68	bzero(ext, reqsize);
69	ext->fsec_magic = OSSwapHostToBigInt32(KAUTH_FILESEC_MAGIC);
70
71	/* special case for _FILESEC_REMOVE_ACL */
72	if (acl == (acl_t)_FILESEC_REMOVE_ACL) {
73		ext->fsec_entrycount = OSSwapHostToBigInt32(KAUTH_FILESEC_NOACL);
74		return(reqsize);
75	}
76
77	/* export the header */
78	ext->fsec_entrycount = OSSwapHostToBigInt32(acl->a_entries);
79	ext->fsec_flags = OSSwapHostToBigInt32(acl->a_flags);
80
81	/* copy ACEs */
82	for (i = 0; i < acl->a_entries; i++) {
83		/* ACE contents are almost identical */
84		ext->fsec_ace[i].ace_applicable = acl->a_ace[i].ae_applicable;
85		ext->fsec_ace[i].ace_flags =
86		    OSSwapHostToBigInt32((acl->a_ace[i].ae_tag & KAUTH_ACE_KINDMASK) | (acl->a_ace[i].ae_flags & ~KAUTH_ACE_KINDMASK));
87		ext->fsec_ace[i].ace_rights = OSSwapHostToBigInt32(acl->a_ace[i].ae_perms);
88	}
89
90	return(reqsize);
91}
92
93/*
94 * acl_t -> external representation, native system endianity
95 */
96ssize_t
97acl_copy_ext_native(void *buf, acl_t acl, ssize_t size)
98{
99	struct kauth_filesec *ext = (struct kauth_filesec *)buf;
100	ssize_t		reqsize;
101	int		i;
102
103	/* validate arguments, compute required size */
104	reqsize = acl_size(acl);
105	if (reqsize < 0)
106		return(-1);
107	if (reqsize > size) {
108		errno = ERANGE;
109		return(-1);
110	}
111
112	bzero(ext, reqsize);
113	ext->fsec_magic = KAUTH_FILESEC_MAGIC;
114
115	/* special case for _FILESEC_REMOVE_ACL */
116	if (acl == (acl_t)_FILESEC_REMOVE_ACL) {
117		ext->fsec_entrycount = KAUTH_FILESEC_NOACL;
118		return(reqsize);
119	}
120
121	/* export the header */
122	ext->fsec_entrycount = acl->a_entries;
123	ext->fsec_flags = acl->a_flags;
124
125	/* copy ACEs */
126	for (i = 0; i < acl->a_entries; i++) {
127		/* ACE contents are almost identical */
128		ext->fsec_ace[i].ace_applicable = acl->a_ace[i].ae_applicable;
129		ext->fsec_ace[i].ace_flags =
130		    (acl->a_ace[i].ae_tag & KAUTH_ACE_KINDMASK) |
131		    (acl->a_ace[i].ae_flags & ~KAUTH_ACE_KINDMASK);
132		ext->fsec_ace[i].ace_rights = acl->a_ace[i].ae_perms;
133	}
134
135	return(reqsize);
136}
137
138/*
139 * external representation, portable system endianity -> acl_t
140 *
141 * Unlike acl_copy_ext, we can't mung the buffer as it doesn't belong to us.
142 */
143acl_t
144acl_copy_int(const void *buf)
145{
146	struct kauth_filesec *ext = (struct kauth_filesec *)buf;
147	acl_t		ap;
148	int		i;
149
150	if (ext->fsec_magic != OSSwapHostToBigInt32(KAUTH_FILESEC_MAGIC)) {
151		errno = EINVAL;
152		return(NULL);
153	}
154
155	if ((ap = acl_init(OSSwapBigToHostInt32(ext->fsec_entrycount))) != NULL) {
156		/* copy useful header fields */
157		ap->a_flags = OSSwapBigToHostInt32(ext->fsec_flags);
158		ap->a_entries = OSSwapBigToHostInt32(ext->fsec_entrycount);
159		/* copy ACEs */
160		for (i = 0; i < ap->a_entries; i++) {
161			/* ACE contents are literally identical */
162			ap->a_ace[i].ae_magic = _ACL_ENTRY_MAGIC;
163			ap->a_ace[i].ae_applicable = ext->fsec_ace[i].ace_applicable;
164			ap->a_ace[i].ae_flags = OSSwapBigToHostInt32(ext->fsec_ace[i].ace_flags) & ~KAUTH_ACE_KINDMASK;
165			ap->a_ace[i].ae_tag = OSSwapBigToHostInt32(ext->fsec_ace[i].ace_flags) & KAUTH_ACE_KINDMASK;
166			ap->a_ace[i].ae_perms = OSSwapBigToHostInt32(ext->fsec_ace[i].ace_rights);
167		}
168	}
169	return(ap);
170}
171
172/*
173 * external representation, native system endianity -> acl_t
174 */
175acl_t
176acl_copy_int_native(const void *buf)
177{
178	struct kauth_filesec *ext = (struct kauth_filesec *)buf;
179	acl_t		ap;
180	int		i;
181
182	if (ext->fsec_magic != KAUTH_FILESEC_MAGIC) {
183		errno = EINVAL;
184		return(NULL);
185	}
186
187	if ((ap = acl_init(ext->fsec_entrycount)) != NULL) {
188		/* copy useful header fields */
189		ap->a_flags = ext->fsec_flags;
190		ap->a_entries = ext->fsec_entrycount;
191		/* copy ACEs */
192		for (i = 0; i < ap->a_entries; i++) {
193			/* ACE contents are literally identical */
194			ap->a_ace[i].ae_magic = _ACL_ENTRY_MAGIC;
195			ap->a_ace[i].ae_applicable = ext->fsec_ace[i].ace_applicable;
196			ap->a_ace[i].ae_flags = ext->fsec_ace[i].ace_flags & ~KAUTH_ACE_KINDMASK;
197			ap->a_ace[i].ae_tag = ext->fsec_ace[i].ace_flags & KAUTH_ACE_KINDMASK;
198			ap->a_ace[i].ae_perms = ext->fsec_ace[i].ace_rights;
199		}
200	}
201	return(ap);
202}
203
204#define ACL_TYPE_DIR	(1<<0)
205#define ACL_TYPE_FILE	(1<<1)
206#define ACL_TYPE_ACL	(1<<2)
207
208static struct {
209	acl_perm_t	perm;
210	char		*name;
211	int		type;
212} acl_perms[] = {
213	{ACL_READ_DATA,		"read",		ACL_TYPE_FILE},
214//	{ACL_LIST_DIRECTORY,	"list",		ACL_TYPE_DIR},
215	{ACL_WRITE_DATA,	"write",	ACL_TYPE_FILE},
216//	{ACL_ADD_FILE,		"add_file",	ACL_TYPE_DIR},
217	{ACL_EXECUTE,		"execute",	ACL_TYPE_FILE},
218//	{ACL_SEARCH,		"search",	ACL_TYPE_DIR},
219	{ACL_DELETE,		"delete",	ACL_TYPE_FILE | ACL_TYPE_DIR},
220	{ACL_APPEND_DATA,	"append",	ACL_TYPE_FILE},
221//	{ACL_ADD_SUBDIRECTORY,	"add_subdirectory", ACL_TYPE_DIR},
222	{ACL_DELETE_CHILD,	"delete_child",	ACL_TYPE_DIR},
223	{ACL_READ_ATTRIBUTES,	"readattr",	ACL_TYPE_FILE | ACL_TYPE_DIR},
224	{ACL_WRITE_ATTRIBUTES,	"writeattr",	ACL_TYPE_FILE | ACL_TYPE_DIR},
225	{ACL_READ_EXTATTRIBUTES, "readextattr",	ACL_TYPE_FILE | ACL_TYPE_DIR},
226	{ACL_WRITE_EXTATTRIBUTES, "writeextattr", ACL_TYPE_FILE | ACL_TYPE_DIR},
227	{ACL_READ_SECURITY,	"readsecurity",	ACL_TYPE_FILE | ACL_TYPE_DIR},
228	{ACL_WRITE_SECURITY,	"writesecurity", ACL_TYPE_FILE | ACL_TYPE_DIR},
229	{ACL_CHANGE_OWNER,	"chown",	ACL_TYPE_FILE | ACL_TYPE_DIR},
230	{0, NULL, 0}
231};
232
233static struct {
234	acl_flag_t	flag;
235	char		*name;
236	int		type;
237} acl_flags[] = {
238	{ACL_ENTRY_INHERITED,		"inherited",		ACL_TYPE_FILE | ACL_TYPE_DIR},
239	{ACL_FLAG_DEFER_INHERIT,	"defer_inherit",	ACL_TYPE_ACL},
240	{ACL_ENTRY_FILE_INHERIT,	"file_inherit",		ACL_TYPE_DIR},
241	{ACL_ENTRY_DIRECTORY_INHERIT,	"directory_inherit",	ACL_TYPE_DIR},
242	{ACL_ENTRY_LIMIT_INHERIT,	"limit_inherit",	ACL_TYPE_FILE | ACL_TYPE_DIR},
243	{ACL_ENTRY_ONLY_INHERIT,	"only_inherit",		ACL_TYPE_DIR},
244	{ACL_FLAG_NO_INHERIT,		"no_inherit",		ACL_TYPE_ACL},
245	{0, NULL, 0}
246};
247
248/*
249 * reallocing snprintf with offset
250 */
251
252static int
253raosnprintf(char **buf, size_t *size, ssize_t *offset, char *fmt, ...)
254{
255    va_list ap;
256    int ret;
257
258    do
259    {
260	if (*offset < *size)
261	{
262	    va_start(ap, fmt);
263	    ret = vsnprintf(*buf + *offset, *size - *offset, fmt, ap);
264	    va_end(ap);
265	    if (ret < (*size - *offset))
266	    {
267		*offset += ret;
268		return ret;
269	    }
270	}
271	*buf = reallocf(*buf, (*size *= 2));
272    } while (*buf);
273
274    //warn("reallocf failure");
275    return 0;
276}
277
278static char *
279uuid_to_name(uuid_t *uu, uid_t *id, int *isgid)
280{
281    struct group *tgrp = NULL;
282    struct passwd *tpass = NULL;
283
284    if (0 == mbr_uuid_to_id(*uu, id, isgid))
285    {
286	switch (*isgid)
287	{
288	    case ID_TYPE_UID:
289		if (!(tpass = getpwuid(*id)))
290		    goto errout;
291		return strdup(tpass->pw_name);
292		break;
293	    case ID_TYPE_GID:
294		if (!(tgrp = getgrgid((gid_t) *id)))
295		    goto errout;
296		return strdup(tgrp->gr_name);
297		break;
298	    default:
299errout:		;    //warn("Unable to translate qualifier on ACL\n");
300	}
301    }
302    return NULL;
303}
304
305acl_t
306acl_from_text(const char *buf_p)
307{
308    int i, error = 0, need_tag, ug_tag;
309    char *buf, *orig_buf;
310    char *entry, *field, *sub;
311    uuid_t *uu = NULL;
312    struct passwd *tpass = NULL;
313    struct group *tgrp = NULL;
314    acl_entry_t acl_entry;
315    acl_flagset_t flags = NULL;
316    acl_permset_t perms = NULL;
317    acl_tag_t tag;
318    acl_t acl_ret;
319
320    if (buf_p == NULL)
321    {
322	errno = EINVAL;
323	return NULL;
324    }
325
326    if ((buf = strdup(buf_p)) == NULL)
327	return NULL;
328
329    if ((acl_ret = acl_init(1)) == NULL)
330	return NULL;
331
332    orig_buf = buf;
333
334    /* global acl flags
335     * format: !#acl <version> [<flags>]
336     */
337    if ((entry = strsep(&buf, "\n")) != NULL && *entry)
338    {
339	/* field 1: !#acl */
340	field = strsep(&entry, " ");
341	if (*field && strncmp(field, "!#acl", strlen("!#acl")))
342	{
343	    error = EINVAL;
344	    goto exit;
345	}
346
347	/* field 2: <version>
348	 * currently only accepts 1
349	 */
350	field = strsep(&entry, " ");
351	errno = 0;
352	if (!*field || strtol(field, NULL, 0) != 1)
353	{
354	    error = EINVAL;
355	    goto exit;
356	}
357
358	/* field 3: <flags>
359	 * optional
360	 */
361	if((field = strsep(&entry, " ")) != NULL && *field)
362	{
363	    acl_get_flagset_np(acl_ret, &flags);
364	    while ((sub = strsep(&field, ",")) && *sub)
365	    {
366		for (i = 0; acl_flags[i].name != NULL; ++i)
367		{
368		    if (acl_flags[i].type & ACL_TYPE_ACL
369			    && !strcmp(acl_flags[i].name, sub))
370		    {
371			acl_add_flag_np(flags, acl_flags[i].flag);
372			break;
373		    }
374		}
375		if (acl_flags[i].name == NULL)
376		{
377		    /* couldn't find flag */
378		    error = EINVAL;
379		    goto exit;
380		}
381	    }
382	}
383    } else {
384	error = EINVAL;
385	goto exit;
386    }
387
388    /* parse each acl line
389     * format: <user|group>:
390     *	    [<uuid>]:
391     *	    [<user|group>]:
392     *	    [<uid|gid>]:
393     *	    <allow|deny>[,<flags>]
394     *	    [:<permissions>[,<permissions>]]
395     *
396     * only one of the user/group identifies is required
397     * the first one found is used
398     */
399    while ((entry = strsep(&buf, "\n")) && *entry)
400    {
401	need_tag = 1;
402	ug_tag = -1;
403
404	/* field 1: <user|group> */
405	field = strsep(&entry, ":");
406
407	if(uu)
408	    bzero(uu, sizeof(uuid_t));
409	else if((uu = calloc(1, sizeof(uuid_t))) == NULL)
410	{
411	    error = errno;
412	    goto exit;
413	}
414
415	if(acl_create_entry(&acl_ret, &acl_entry))
416	{
417	    error = errno;
418	    goto exit;
419	}
420
421	if (-1 == acl_get_flagset_np(acl_entry, &flags)
422	 || -1 == acl_get_permset(acl_entry, &perms))
423	{
424	    error = errno;
425	    goto exit;
426	}
427
428	switch(*field)
429	{
430	    case 'u':
431		if(!strcmp(field, "user"))
432		    ug_tag = ID_TYPE_UID;
433		break;
434	    case 'g':
435		if(!strcmp(field, "group"))
436		    ug_tag = ID_TYPE_GID;
437		break;
438	    default:
439		error = EINVAL;
440		goto exit;
441	}
442
443	/* field 2: <uuid> */
444	if ((field = strsep(&entry, ":")) != NULL && *field)
445	{
446	    uuid_parse(field, *uu);
447	    need_tag = 0;
448	}
449
450	/* field 3: <username|groupname> */
451	if ((field = strsep(&entry, ":")) != NULL && *field && need_tag)
452	{
453	    switch(ug_tag)
454	    {
455		case ID_TYPE_UID:
456		    if((tpass = getpwnam(field)) != NULL)
457			if (mbr_uid_to_uuid(tpass->pw_uid, *uu) != 0)
458			{
459			    error = EINVAL;
460			    goto exit;
461			}
462		    break;
463		case ID_TYPE_GID:
464		    if ((tgrp = getgrnam(field)) != NULL)
465			if (mbr_gid_to_uuid(tgrp->gr_gid, *uu) != 0)
466			{
467			    error = EINVAL;
468			    goto exit;
469			}
470		    break;
471		default:
472		    error = EINVAL;
473		    goto exit;
474	    }
475	    need_tag = 0;
476	}
477
478	/* field 4: <uid|gid> */
479	if ((field = strsep(&entry, ":")) != NULL && *field && need_tag)
480	{
481	    uid_t id;
482	    error = 0;
483
484	    if((id = strtol(field, NULL, 10)) == 0 && error)
485	    {
486		error = EINVAL;
487		goto exit;
488	    }
489
490	    switch(ug_tag)
491	    {
492		case ID_TYPE_UID:
493		    if((tpass = getpwuid((uid_t)id)) != NULL)
494			if (mbr_uid_to_uuid(tpass->pw_uid, *uu) != 0)
495			{
496			    error = EINVAL;
497			    goto exit;
498			}
499		    break;
500		case ID_TYPE_GID:
501		    if ((tgrp = getgrgid((gid_t)id)) != NULL)
502			if (mbr_gid_to_uuid(tgrp->gr_gid, *uu) != 0)
503			{
504			    error = EINVAL;
505			    goto exit;
506			}
507		    break;
508	    }
509	    need_tag = 0;
510	}
511
512	/* sanity check: nothing set as qualifier */
513	if (need_tag)
514	{
515	    error = EINVAL;
516	    goto exit;
517	}
518
519	/* field 5: <flags> */
520	if((field = strsep(&entry, ":")) == NULL || !*field)
521	{
522	    error = EINVAL;
523	    goto exit;
524	}
525
526	for (tag = 0; (sub = strsep(&field, ",")) && *sub;)
527	{
528	    if (!tag)
529	    {
530		if (!strcmp(sub, "allow"))
531		    tag = ACL_EXTENDED_ALLOW;
532		else if (!strcmp(sub, "deny"))
533		    tag = ACL_EXTENDED_DENY;
534		else {
535		    error = EINVAL;
536		    goto exit;
537		}
538		continue;
539	    }
540
541	    for (i = 0; acl_flags[i].name != NULL; ++i)
542	    {
543		if (acl_flags[i].type & (ACL_TYPE_FILE | ACL_TYPE_DIR)
544			&& !strcmp(acl_flags[i].name, sub))
545		{
546		    acl_add_flag_np(flags, acl_flags[i].flag);
547		    break;
548		}
549	    }
550	    if (acl_flags[i].name == NULL)
551	    {
552		/* couldn't find perm */
553		error = EINVAL;
554		goto exit;
555	    }
556	}
557
558	/* field 6: <perms> (can be empty) */
559	if((field = strsep(&entry, ":")) != NULL && *field)
560	{
561	    while ((sub = strsep(&field, ",")) && *sub)
562	    {
563		for (i = 0; acl_perms[i].name != NULL; i++)
564		{
565		    if (acl_perms[i].type & (ACL_TYPE_FILE | ACL_TYPE_DIR)
566			    && !strcmp(acl_perms[i].name, sub))
567		    {
568			acl_add_perm(perms, acl_perms[i].perm);
569			break;
570		    }
571		}
572		if (acl_perms[i].name == NULL)
573		{
574		    /* couldn't find perm */
575		    error = EINVAL;
576		    goto exit;
577		}
578	    }
579	}
580	acl_set_tag_type(acl_entry, tag);
581	acl_set_qualifier(acl_entry, *uu);
582    }
583exit:
584    if(uu)
585	free(uu);
586    free(orig_buf);
587    if (error)
588    {
589	acl_free(acl_ret);
590	acl_ret = NULL;
591	errno = error;
592    }
593    return acl_ret;
594}
595
596char *
597acl_to_text(acl_t acl, ssize_t *len_p)
598{
599	acl_tag_t tag;
600	acl_entry_t entry = NULL;
601	acl_flagset_t flags;
602	acl_permset_t perms;
603	uid_t id;
604	int i, first;
605	int isgid;
606	size_t bufsize = 1024;
607	char *buf = NULL;
608
609	if (!_ACL_VALID_ACL(acl)) {
610		errno = EINVAL;
611		return NULL;
612	}
613
614	if (len_p == NULL)
615	    if ((len_p = alloca(sizeof(ssize_t))) == NULL)
616		goto err_nomem;
617
618	*len_p = 0;
619
620	if ((buf = malloc(bufsize)) == NULL)
621	    goto err_nomem;
622
623	if (!raosnprintf(&buf, &bufsize, len_p, "!#acl %d", 1))
624	    goto err_nomem;
625
626	if (acl_get_flagset_np(acl, &flags) == 0)
627	{
628	    for (i = 0, first = 0; acl_flags[i].name != NULL; ++i)
629	    {
630		if (acl_flags[i].type & ACL_TYPE_ACL
631			&& acl_get_flag_np(flags, acl_flags[i].flag) != 0)
632		{
633		    if(!raosnprintf(&buf, &bufsize, len_p, "%s%s",
634			    first++ ? "," : " ", acl_flags[i].name))
635			goto err_nomem;
636		}
637	    }
638	}
639	for (;acl_get_entry(acl,
640		    entry == NULL ? ACL_FIRST_ENTRY : ACL_NEXT_ENTRY, &entry) == 0;)
641	{
642	    int valid;
643	    uuid_t *uu;
644	    char *str, uu_str[37];
645
646	    if (((uu = (uuid_t *) acl_get_qualifier(entry)) == NULL)
647		|| (acl_get_tag_type(entry, &tag) != 0)
648		|| (acl_get_flagset_np(entry, &flags) != 0)
649		|| (acl_get_permset(entry, &perms) != 0)) {
650		if (uu != NULL) acl_free(uu);
651		continue;
652	    }
653
654	    uuid_unparse_upper(*uu, uu_str);
655
656	    if ((str = uuid_to_name(uu, &id, &isgid)) != NULL) {
657		valid = raosnprintf(&buf, &bufsize, len_p, "\n%s:%s:%s:%d:%s",
658		    isgid ? "group" : "user",
659		    uu_str,
660		    str,
661		    id,
662		    (tag == ACL_EXTENDED_ALLOW) ? "allow" : "deny");
663	    } else {
664		valid = raosnprintf(&buf, &bufsize, len_p, "\nuser:%s:::%s",
665		    uu_str,
666		    (tag == ACL_EXTENDED_ALLOW) ? "allow" : "deny");
667	    }
668
669	    free(str);
670	    acl_free(uu);
671
672	    if (!valid)
673		goto err_nomem;
674
675	    for (i = 0; acl_flags[i].name != NULL; ++i)
676	    {
677		if (acl_flags[i].type & (ACL_TYPE_DIR | ACL_TYPE_FILE))
678		{
679		    if(acl_get_flag_np(flags, acl_flags[i].flag) != 0)
680		    {
681			if(!raosnprintf(&buf, &bufsize, len_p, ",%s",
682			    acl_flags[i].name))
683			    goto err_nomem;
684		    }
685		}
686	    }
687
688	    for (i = 0, first = 0; acl_perms[i].name != NULL; ++i)
689	    {
690		if (acl_perms[i].type & (ACL_TYPE_DIR | ACL_TYPE_FILE))
691		{
692		    if(acl_get_perm_np(perms, acl_perms[i].perm) != 0)
693		    {
694			if(!raosnprintf(&buf, &bufsize, len_p, "%s%s",
695			    first++ ? "," : ":", acl_perms[i].name))
696			    goto err_nomem;
697		    }
698		}
699	    }
700	}
701	buf[(*len_p)++] = '\n';
702	buf[(*len_p)] = 0;
703	return buf;
704
705err_nomem:
706	if (buf != NULL)
707	    free(buf);
708
709	errno = ENOMEM;
710	return NULL;
711}
712
713ssize_t
714acl_size(acl_t acl)
715{
716	/* special case for _FILESEC_REMOVE_ACL */
717	if (acl == (acl_t)_FILESEC_REMOVE_ACL)
718		return KAUTH_FILESEC_SIZE(0);
719
720	_ACL_VALIDATE_ACL(acl);
721
722	return(KAUTH_FILESEC_SIZE(acl->a_entries));
723}
724