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