1/*-
2 * Copyright (c) 1999, 2000, 2001 Robert N. M. Watson
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 CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 */
26/*
27 * acl_from_text: Convert a text-form ACL from a string to an acl_t.
28 */
29
30#include <sys/cdefs.h>
31__FBSDID("$FreeBSD$");
32
33#include <sys/types.h>
34#include "namespace.h"
35#include <sys/acl.h>
36#include "un-namespace.h"
37#include <sys/errno.h>
38#include <grp.h>
39#include <pwd.h>
40#include <stdio.h>
41#include <stdlib.h>
42#include <string.h>
43#include <assert.h>
44
45#include "acl_support.h"
46
47static acl_tag_t acl_string_to_tag(char *tag, char *qualifier);
48
49int _nfs4_acl_entry_from_text(acl_t aclp, char *entry);
50int _text_could_be_nfs4_acl(const char *entry);
51
52static acl_tag_t
53acl_string_to_tag(char *tag, char *qualifier)
54{
55
56	if (*qualifier == '\0') {
57		if ((!strcmp(tag, "user")) || (!strcmp(tag, "u"))) {
58			return (ACL_USER_OBJ);
59		} else
60		if ((!strcmp(tag, "group")) || (!strcmp(tag, "g"))) {
61			return (ACL_GROUP_OBJ);
62		} else
63		if ((!strcmp(tag, "mask")) || (!strcmp(tag, "m"))) {
64			return (ACL_MASK);
65		} else
66		if ((!strcmp(tag, "other")) || (!strcmp(tag, "o"))) {
67			return (ACL_OTHER);
68		} else
69			return(-1);
70	} else {
71		if ((!strcmp(tag, "user")) || (!strcmp(tag, "u"))) {
72			return(ACL_USER);
73		} else
74		if ((!strcmp(tag, "group")) || (!strcmp(tag, "g"))) {
75			return(ACL_GROUP);
76		} else
77			return(-1);
78	}
79}
80
81static int
82_posix1e_acl_entry_from_text(acl_t aclp, char *entry)
83{
84	acl_tag_t	 t;
85	acl_perm_t	 p;
86	char		*tag, *qualifier, *permission;
87	uid_t		 id;
88	int		 error;
89
90	assert(_acl_brand(aclp) == ACL_BRAND_POSIX);
91
92	/* Split into three ':' delimited fields. */
93	tag = strsep(&entry, ":");
94	if (tag == NULL) {
95		errno = EINVAL;
96		return (-1);
97	}
98	tag = string_skip_whitespace(tag);
99	if ((*tag == '\0') && (!entry)) {
100		/*
101		 * Is an entirely comment line, skip to next
102		 * comma.
103		 */
104		return (0);
105	}
106	string_trim_trailing_whitespace(tag);
107
108	qualifier = strsep(&entry, ":");
109	if (qualifier == NULL) {
110		errno = EINVAL;
111		return (-1);
112	}
113	qualifier = string_skip_whitespace(qualifier);
114	string_trim_trailing_whitespace(qualifier);
115
116	permission = strsep(&entry, ":");
117	if (permission == NULL || entry) {
118		errno = EINVAL;
119		return (-1);
120	}
121	permission = string_skip_whitespace(permission);
122	string_trim_trailing_whitespace(permission);
123
124	t = acl_string_to_tag(tag, qualifier);
125	if (t == -1) {
126		errno = EINVAL;
127		return (-1);
128	}
129
130	error = _posix1e_acl_string_to_perm(permission, &p);
131	if (error == -1) {
132		errno = EINVAL;
133		return (-1);
134	}
135
136	switch(t) {
137		case ACL_USER_OBJ:
138		case ACL_GROUP_OBJ:
139		case ACL_MASK:
140		case ACL_OTHER:
141			if (*qualifier != '\0') {
142				errno = EINVAL;
143				return (-1);
144			}
145			id = 0;
146			break;
147
148		case ACL_USER:
149		case ACL_GROUP:
150			error = _acl_name_to_id(t, qualifier, &id);
151			if (error == -1)
152				return (-1);
153			break;
154
155		default:
156			errno = EINVAL;
157			return (-1);
158	}
159
160	error = _posix1e_acl_add_entry(aclp, t, id, p);
161	if (error == -1)
162		return (-1);
163
164	return (0);
165}
166
167static int
168_text_is_nfs4_entry(const char *entry)
169{
170	int count = 0;
171
172	assert(strlen(entry) > 0);
173
174	while (*entry != '\0') {
175		if (*entry == ':' || *entry == '@')
176			count++;
177		entry++;
178	}
179
180	if (count <= 2)
181		return (0);
182
183	return (1);
184}
185
186/*
187 * acl_from_text -- Convert a string into an ACL.
188 * Postpone most validity checking until the end and call acl_valid() to do
189 * that.
190 */
191acl_t
192acl_from_text(const char *buf_p)
193{
194	acl_t		 acl;
195	char		*mybuf_p, *line, *cur, *notcomment, *comment, *entry;
196	int		 error;
197
198	/* Local copy we can mess up. */
199	mybuf_p = strdup(buf_p);
200	if (mybuf_p == NULL)
201		return(NULL);
202
203	acl = acl_init(3); /* XXX: WTF, 3? */
204	if (acl == NULL) {
205		free(mybuf_p);
206		return(NULL);
207	}
208
209	/* Outer loop: delimit at \n boundaries. */
210	cur = mybuf_p;
211	while ((line = strsep(&cur, "\n"))) {
212		/* Now split the line on the first # to strip out comments. */
213		comment = line;
214		notcomment = strsep(&comment, "#");
215
216		/* Inner loop: delimit at ',' boundaries. */
217		while ((entry = strsep(&notcomment, ","))) {
218
219			/* Skip empty lines. */
220			if (strlen(string_skip_whitespace(entry)) == 0)
221				continue;
222
223			if (_acl_brand(acl) == ACL_BRAND_UNKNOWN) {
224				if (_text_is_nfs4_entry(entry))
225					_acl_brand_as(acl, ACL_BRAND_NFS4);
226				else
227					_acl_brand_as(acl, ACL_BRAND_POSIX);
228			}
229
230			switch (_acl_brand(acl)) {
231			case ACL_BRAND_NFS4:
232				error = _nfs4_acl_entry_from_text(acl, entry);
233				break;
234
235			case ACL_BRAND_POSIX:
236				error = _posix1e_acl_entry_from_text(acl, entry);
237				break;
238
239			default:
240				error = EINVAL;
241				break;
242			}
243
244			if (error)
245				goto error_label;
246		}
247	}
248
249#if 0
250	/* XXX Should we only return ACLs valid according to acl_valid? */
251	/* Verify validity of the ACL we read in. */
252	if (acl_valid(acl) == -1) {
253		errno = EINVAL;
254		goto error_label;
255	}
256#endif
257
258	free(mybuf_p);
259	return(acl);
260
261error_label:
262	acl_free(acl);
263	free(mybuf_p);
264	return(NULL);
265}
266
267/*
268 * Given a username/groupname from a text form of an ACL, return the uid/gid
269 * XXX NOT THREAD SAFE, RELIES ON GETPWNAM, GETGRNAM
270 * XXX USES *PW* AND *GR* WHICH ARE STATEFUL AND THEREFORE THIS ROUTINE
271 * MAY HAVE SIDE-EFFECTS
272 */
273int
274_acl_name_to_id(acl_tag_t tag, char *name, uid_t *id)
275{
276	struct group	*g;
277	struct passwd	*p;
278	unsigned long	l;
279	char 		*endp;
280
281	switch(tag) {
282	case ACL_USER:
283		p = getpwnam(name);
284		if (p == NULL) {
285			l = strtoul(name, &endp, 0);
286			if (*endp != '\0' || l != (unsigned long)(uid_t)l) {
287				errno = EINVAL;
288				return (-1);
289			}
290			*id = (uid_t)l;
291			return (0);
292		}
293		*id = p->pw_uid;
294		return (0);
295
296	case ACL_GROUP:
297		g = getgrnam(name);
298		if (g == NULL) {
299			l = strtoul(name, &endp, 0);
300			if (*endp != '\0' || l != (unsigned long)(gid_t)l) {
301				errno = EINVAL;
302				return (-1);
303			}
304			*id = (gid_t)l;
305			return (0);
306		}
307		*id = g->gr_gid;
308		return (0);
309
310	default:
311		return (EINVAL);
312	}
313}
314