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
46 static acl_tag_t acl_string_to_tag(char *tag, char *qualifier);
47
48 int _nfs4_acl_entry_from_text(acl_t aclp, char *entry);
49 int _text_could_be_nfs4_acl(const char *entry);
50
51 static acl_tag_t
acl_string_to_tag(char * tag,char * qualifier)52 acl_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
80 static int
_posix1e_acl_entry_from_text(acl_t aclp,char * entry)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
166 static int
_text_is_nfs4_entry(const char * entry)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 */
190 acl_t
acl_from_text(const char * buf_p)191 acl_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(¬comment, ","))) {
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
260 error_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 */
272 int
_acl_name_to_id(acl_tag_t tag,char * name,uid_t * id)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