xref: /freebsd/lib/libc/posix1e/acl_from_text.c (revision 1669d8afc64812c8d2d1d147ae1fd42ff441e1b1)
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 
44 #include "acl_support.h"
45 
46 static int _posix1e_acl_name_to_id(acl_tag_t tag, char *name, uid_t *id);
47 static acl_tag_t acl_string_to_tag(char *tag, char *qualifier);
48 static char *string_skip_whitespace(char *string);
49 static void string_trim_trailing_whitespace(char *string);
50 
51 static char *
52 string_skip_whitespace(char *string)
53 {
54 
55 	while (*string && ((*string == ' ') || (*string == '\t'))) {
56 		string++;
57 	}
58 	return (string);
59 }
60 
61 static void
62 string_trim_trailing_whitespace(char *string)
63 {
64 	char	*end;
65 
66 	if (*string == '\0')
67 		return;
68 
69 	end = string + strlen(string) - 1;
70 
71 	while (end != string) {
72 		if ((*end == ' ') || (*end == '\t')) {
73 			*end = '\0';
74 			end--;
75 		} else {
76 			return;
77 		}
78 	}
79 
80 	return;
81 }
82 
83 static acl_tag_t
84 acl_string_to_tag(char *tag, char *qualifier)
85 {
86 
87 	if (*qualifier == '\0') {
88 		if ((!strcmp(tag, "user")) || (!strcmp(tag, "u"))) {
89 			return (ACL_USER_OBJ);
90 		} else
91 		if ((!strcmp(tag, "group")) || (!strcmp(tag, "g"))) {
92 			return (ACL_GROUP_OBJ);
93 		} else
94 		if ((!strcmp(tag, "mask")) || (!strcmp(tag, "m"))) {
95 			return (ACL_MASK);
96 		} else
97 		if ((!strcmp(tag, "other")) || (!strcmp(tag, "o"))) {
98 			return (ACL_OTHER);
99 		} else
100 			return(-1);
101 	} else {
102 		if ((!strcmp(tag, "user")) || (!strcmp(tag, "u"))) {
103 			return(ACL_USER);
104 		} else
105 		if ((!strcmp(tag, "group")) || (!strcmp(tag, "g"))) {
106 			return(ACL_GROUP);
107 		} else
108 			return(-1);
109 	}
110 }
111 
112 /*
113  * acl_from_text -- Convert a string into an ACL.
114  * Postpone most validity checking until the end and call acl_valid() to do
115  * that.
116  */
117 acl_t
118 acl_from_text(const char *buf_p)
119 {
120 	acl_tag_t	 t;
121 	acl_perm_t	 p;
122 	acl_t		 acl;
123 	char		*mybuf_p, *line, *cur, *notcomment, *comment, *entry;
124 	char		*tag, *qualifier, *permission;
125 	int		 error;
126 	uid_t		 id;
127 
128 	/* Local copy we can mess up. */
129 	mybuf_p = strdup(buf_p);
130 	if (mybuf_p == NULL)
131 		return(NULL);
132 
133 	acl = acl_init(3);
134 	if (acl == NULL) {
135 		free(mybuf_p);
136 		return(NULL);
137 	}
138 
139 	/* Outer loop: delimit at \n boundaries. */
140 	cur = mybuf_p;
141 	while ((line = strsep(&cur, "\n"))) {
142 		/* Now split the line on the first # to strip out comments. */
143 		comment = line;
144 		notcomment = strsep(&comment, "#");
145 
146 		/* Inner loop: delimit at ',' boundaries. */
147 		while ((entry = strsep(&notcomment, ","))) {
148 			/* Now split into three ':' delimited fields. */
149 			tag = strsep(&entry, ":");
150 			if (tag == NULL) {
151 				errno = EINVAL;
152 				goto error_label;
153 			}
154 			tag = string_skip_whitespace(tag);
155 			if ((*tag == '\0') && (!entry)) {
156 				/*
157 				 * Is an entirely comment line, skip to next
158 				 * comma.
159 				 */
160 				continue;
161 			}
162 			string_trim_trailing_whitespace(tag);
163 
164 			qualifier = strsep(&entry, ":");
165 			if (qualifier == NULL) {
166 				errno = EINVAL;
167 				goto error_label;
168 			}
169 			qualifier = string_skip_whitespace(qualifier);
170 			string_trim_trailing_whitespace(qualifier);
171 
172 			permission = strsep(&entry, ":");
173 			if (permission == NULL || entry) {
174 				errno = EINVAL;
175 				goto error_label;
176 			}
177 			permission = string_skip_whitespace(permission);
178 			string_trim_trailing_whitespace(permission);
179 
180 			t = acl_string_to_tag(tag, qualifier);
181 			if (t == -1) {
182 				errno = EINVAL;
183 				goto error_label;
184 			}
185 
186 			error = _posix1e_acl_string_to_perm(permission, &p);
187 			if (error == -1) {
188 				errno = EINVAL;
189 				goto error_label;
190 			}
191 
192 			switch(t) {
193 			case ACL_USER_OBJ:
194 			case ACL_GROUP_OBJ:
195 			case ACL_MASK:
196 			case ACL_OTHER:
197 				if (*qualifier != '\0') {
198 					errno = EINVAL;
199 					goto error_label;
200 				}
201 				id = 0;
202 				break;
203 
204 			case ACL_USER:
205 			case ACL_GROUP:
206 				error = _posix1e_acl_name_to_id(t, qualifier,
207 				    &id);
208 				if (error == -1)
209 					goto error_label;
210 				break;
211 
212 			default:
213 				errno = EINVAL;
214 				goto error_label;
215 			}
216 
217 			error = _posix1e_acl_add_entry(acl, t, id, p);
218 			if (error == -1)
219 				goto error_label;
220 		}
221 	}
222 
223 #if 0
224 	/* XXX Should we only return ACLs valid according to acl_valid? */
225 	/* Verify validity of the ACL we read in. */
226 	if (acl_valid(acl) == -1) {
227 		errno = EINVAL;
228 		goto error_label;
229 	}
230 #endif
231 
232 	return(acl);
233 
234 error_label:
235 	acl_free(acl);
236 	free(mybuf_p);
237 	return(NULL);
238 }
239 
240 /*
241  * Given a username/groupname from a text form of an ACL, return the uid/gid
242  * XXX NOT THREAD SAFE, RELIES ON GETPWNAM, GETGRNAM
243  * XXX USES *PW* AND *GR* WHICH ARE STATEFUL AND THEREFORE THIS ROUTINE
244  * MAY HAVE SIDE-EFFECTS
245  *
246  * XXX currently doesn't deal correctly with a numeric uid being passed
247  * instead of a username.  What is correct behavior here?  Check chown.
248  */
249 static int
250 _posix1e_acl_name_to_id(acl_tag_t tag, char *name, uid_t *id)
251 {
252 	struct group	*g;
253 	struct passwd	*p;
254 	unsigned long	l;
255 	char 		*endp;
256 
257 	switch(tag) {
258 	case ACL_USER:
259 		p = getpwnam(name);
260 		if (p == NULL) {
261 			l = strtoul(name, &endp, 0);
262 			if (*endp != '\0' || l != (unsigned long)(uid_t)l) {
263 				errno = EINVAL;
264 				return (-1);
265 			}
266 			*id = (uid_t)l;
267 			return (0);
268 		}
269 		*id = p->pw_uid;
270 		return (0);
271 
272 	case ACL_GROUP:
273 		g = getgrnam(name);
274 		if (g == NULL) {
275 			l = strtoul(name, &endp, 0);
276 			if (*endp != '\0' || l != (unsigned long)(gid_t)l) {
277 				errno = EINVAL;
278 				return (-1);
279 			}
280 			*id = (gid_t)l;
281 			return (0);
282 		}
283 		*id = g->gr_gid;
284 		return (0);
285 
286 	default:
287 		return (EINVAL);
288 	}
289 }
290