1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3  *
4  * Copyright (c) 2008, 2009 Edward Tomasz Napierała <trasz@FreeBSD.org>
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25  * SUCH DAMAGE.
26  */
27 
28 #include <sys/cdefs.h>
29 __FBSDID("$FreeBSD$");
30 
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <unistd.h>
34 #include <errno.h>
35 #include <assert.h>
36 #include <string.h>
37 #include <pwd.h>
38 #include <grp.h>
39 #include <ctype.h>
40 #include <err.h>
41 #include <sys/syscall.h>
42 #include <sys/types.h>
43 #include <sys/acl.h>
44 
45 #include "acl_support.h"
46 
47 #define MAX_ENTRY_LENGTH 512
48 
49 /*
50  * Parse the tag field of ACL entry passed as "str".  If qualifier
51  * needs to follow, then the variable referenced by "need_qualifier"
52  * is set to 1, otherwise it's set to 0.
53  */
54 static int
55 parse_tag(const char *str, acl_entry_t entry, int *need_qualifier)
56 {
57 
58 	assert(need_qualifier != NULL);
59 	*need_qualifier = 0;
60 
61 	if (strcmp(str, "owner@") == 0)
62 		return (acl_set_tag_type(entry, ACL_USER_OBJ));
63 	if (strcmp(str, "group@") == 0)
64 		return (acl_set_tag_type(entry, ACL_GROUP_OBJ));
65 	if (strcmp(str, "everyone@") == 0)
66 		return (acl_set_tag_type(entry, ACL_EVERYONE));
67 
68 	*need_qualifier = 1;
69 
70 	if (strcmp(str, "user") == 0 || strcmp(str, "u") == 0)
71 		return (acl_set_tag_type(entry, ACL_USER));
72 	if (strcmp(str, "group") == 0 || strcmp(str, "g") == 0)
73 		return (acl_set_tag_type(entry, ACL_GROUP));
74 
75 	warnx("malformed ACL: invalid \"tag\" field");
76 
77 	return (-1);
78 }
79 
80 /*
81  * Parse the qualifier field of ACL entry passed as "str".
82  * If user or group name cannot be resolved, then the variable
83  * referenced by "need_qualifier" is set to 1; it will be checked
84  * later to figure out whether the appended_id is required.
85  */
86 static int
87 parse_qualifier(char *str, acl_entry_t entry, int *need_qualifier)
88 {
89 	int qualifier_length, error;
90 	uid_t id;
91 	acl_tag_t tag;
92 
93 	assert(need_qualifier != NULL);
94 	*need_qualifier = 0;
95 
96 	qualifier_length = strlen(str);
97 
98 	if (qualifier_length == 0) {
99 		warnx("malformed ACL: empty \"qualifier\" field");
100 		return (-1);
101 	}
102 
103 	error = acl_get_tag_type(entry, &tag);
104 	if (error)
105 		return (error);
106 
107 	error = _acl_name_to_id(tag, str, &id);
108 	if (error) {
109 		*need_qualifier = 1;
110 		return (0);
111 	}
112 
113 	return (acl_set_qualifier(entry, &id));
114 }
115 
116 static int
117 parse_access_mask(char *str, acl_entry_t entry)
118 {
119 	int error;
120 	acl_perm_t perm;
121 
122 	error = _nfs4_parse_access_mask(str, &perm);
123 	if (error)
124 		return (error);
125 
126 	error = acl_set_permset(entry, &perm);
127 
128 	return (error);
129 }
130 
131 static int
132 parse_flags(char *str, acl_entry_t entry)
133 {
134 	int error;
135 	acl_flag_t flags;
136 
137 	error = _nfs4_parse_flags(str, &flags);
138 	if (error)
139 		return (error);
140 
141 	error = acl_set_flagset_np(entry, &flags);
142 
143 	return (error);
144 }
145 
146 static int
147 parse_entry_type(const char *str, acl_entry_t entry)
148 {
149 
150 	if (strcmp(str, "allow") == 0)
151 		return (acl_set_entry_type_np(entry, ACL_ENTRY_TYPE_ALLOW));
152 	if (strcmp(str, "deny") == 0)
153 		return (acl_set_entry_type_np(entry, ACL_ENTRY_TYPE_DENY));
154 	if (strcmp(str, "audit") == 0)
155 		return (acl_set_entry_type_np(entry, ACL_ENTRY_TYPE_AUDIT));
156 	if (strcmp(str, "alarm") == 0)
157 		return (acl_set_entry_type_np(entry, ACL_ENTRY_TYPE_ALARM));
158 
159 	warnx("malformed ACL: invalid \"type\" field");
160 
161 	return (-1);
162 }
163 
164 static int
165 parse_appended_id(char *str, acl_entry_t entry)
166 {
167 	int qualifier_length;
168 	char *end;
169 	id_t id;
170 
171 	qualifier_length = strlen(str);
172 	if (qualifier_length == 0) {
173 		warnx("malformed ACL: \"appended id\" field present, "
174 	           "but empty");
175 		return (-1);
176 	}
177 
178 	id = strtod(str, &end);
179 	if (end - str != qualifier_length) {
180 		warnx("malformed ACL: appended id is not a number");
181 		return (-1);
182 	}
183 
184 	return (acl_set_qualifier(entry, &id));
185 }
186 
187 static int
188 number_of_colons(const char *str)
189 {
190 	int count = 0;
191 
192 	while (*str != '\0') {
193 		if (*str == ':')
194 			count++;
195 
196 		str++;
197 	}
198 
199 	return (count);
200 }
201 
202 int
203 _nfs4_acl_entry_from_text(acl_t aclp, char *str)
204 {
205 	int error, need_qualifier;
206 	acl_entry_t entry;
207 	char *field, *qualifier_field;
208 
209 	error = acl_create_entry(&aclp, &entry);
210 	if (error)
211 		return (error);
212 
213 	assert(_entry_brand(entry) == ACL_BRAND_NFS4);
214 
215 	if (str == NULL)
216 		goto truncated_entry;
217 	field = strsep(&str, ":");
218 
219 	field = string_skip_whitespace(field);
220 	if ((*field == '\0') && (!str)) {
221 		/*
222 		 * Is an entirely comment line, skip to next
223 		 * comma.
224 		 */
225 		return (0);
226 	}
227 
228 	error = parse_tag(field, entry, &need_qualifier);
229 	if (error)
230 		goto malformed_field;
231 
232 	if (need_qualifier) {
233 		if (str == NULL)
234 			goto truncated_entry;
235 		qualifier_field = field = strsep(&str, ":");
236 		error = parse_qualifier(field, entry, &need_qualifier);
237 		if (error)
238 			goto malformed_field;
239 	}
240 
241 	if (str == NULL)
242 		goto truncated_entry;
243 	field = strsep(&str, ":");
244 	error = parse_access_mask(field, entry);
245 	if (error)
246 		goto malformed_field;
247 
248 	if (str == NULL)
249 		goto truncated_entry;
250 	/* Do we have "flags" field? */
251 	if (number_of_colons(str) > 0) {
252 		field = strsep(&str, ":");
253 		error = parse_flags(field, entry);
254 		if (error)
255 			goto malformed_field;
256 	}
257 
258 	if (str == NULL)
259 		goto truncated_entry;
260 	field = strsep(&str, ":");
261 	error = parse_entry_type(field, entry);
262 	if (error)
263 		goto malformed_field;
264 
265 	if (need_qualifier) {
266 		if (str == NULL) {
267 			warnx("malformed ACL: unknown user or group name "
268 			    "\"%s\"", qualifier_field);
269 			goto truncated_entry;
270 		}
271 
272 		error = parse_appended_id(str, entry);
273 		if (error)
274 			goto malformed_field;
275 	}
276 
277 	return (0);
278 
279 truncated_entry:
280 malformed_field:
281 	acl_delete_entry(aclp, entry);
282 	errno = EINVAL;
283 	return (-1);
284 }
285