xref: /netbsd-src/crypto/external/bsd/heimdal/dist/lib/krb5/acl.c (revision afab4e300d3a9fb07dd8c80daf53d0feb3345706)
1*afab4e30Schristos /*	$NetBSD: acl.c,v 1.3 2023/06/19 21:41:44 christos Exp $	*/
2ca1c9b0cSelric 
3ca1c9b0cSelric /*
4ca1c9b0cSelric  * Copyright (c) 2000 - 2002, 2004 Kungliga Tekniska Högskolan
5ca1c9b0cSelric  * (Royal Institute of Technology, Stockholm, Sweden).
6ca1c9b0cSelric  * All rights reserved.
7ca1c9b0cSelric  *
8ca1c9b0cSelric  * Redistribution and use in source and binary forms, with or without
9ca1c9b0cSelric  * modification, are permitted provided that the following conditions
10ca1c9b0cSelric  * are met:
11ca1c9b0cSelric  *
12ca1c9b0cSelric  * 1. Redistributions of source code must retain the above copyright
13ca1c9b0cSelric  *    notice, this list of conditions and the following disclaimer.
14ca1c9b0cSelric  *
15ca1c9b0cSelric  * 2. Redistributions in binary form must reproduce the above copyright
16ca1c9b0cSelric  *    notice, this list of conditions and the following disclaimer in the
17ca1c9b0cSelric  *    documentation and/or other materials provided with the distribution.
18ca1c9b0cSelric  *
19ca1c9b0cSelric  * 3. Neither the name of the Institute nor the names of its contributors
20ca1c9b0cSelric  *    may be used to endorse or promote products derived from this software
21ca1c9b0cSelric  *    without specific prior written permission.
22ca1c9b0cSelric  *
23ca1c9b0cSelric  * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
24ca1c9b0cSelric  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25ca1c9b0cSelric  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26ca1c9b0cSelric  * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
27ca1c9b0cSelric  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28ca1c9b0cSelric  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29ca1c9b0cSelric  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30ca1c9b0cSelric  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31ca1c9b0cSelric  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32ca1c9b0cSelric  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33ca1c9b0cSelric  * SUCH DAMAGE.
34ca1c9b0cSelric  */
35ca1c9b0cSelric 
36ca1c9b0cSelric #include "krb5_locl.h"
37ca1c9b0cSelric #include <fnmatch.h>
38ca1c9b0cSelric 
39ca1c9b0cSelric struct acl_field {
40ca1c9b0cSelric     enum { acl_string, acl_fnmatch, acl_retval } type;
41ca1c9b0cSelric     union {
42ca1c9b0cSelric 	const char *cstr;
43ca1c9b0cSelric 	char **retv;
44ca1c9b0cSelric     } u;
45ca1c9b0cSelric     struct acl_field *next, **last;
46ca1c9b0cSelric };
47ca1c9b0cSelric 
48ca1c9b0cSelric static void
free_retv(struct acl_field * acl)49ca1c9b0cSelric free_retv(struct acl_field *acl)
50ca1c9b0cSelric {
51ca1c9b0cSelric     while(acl != NULL) {
52ca1c9b0cSelric 	if (acl->type == acl_retval) {
53ca1c9b0cSelric 	    if (*acl->u.retv)
54ca1c9b0cSelric 		free(*acl->u.retv);
55ca1c9b0cSelric 	    *acl->u.retv = NULL;
56ca1c9b0cSelric 	}
57ca1c9b0cSelric 	acl = acl->next;
58ca1c9b0cSelric     }
59ca1c9b0cSelric }
60ca1c9b0cSelric 
61ca1c9b0cSelric static void
acl_free_list(struct acl_field * acl,int retv)62ca1c9b0cSelric acl_free_list(struct acl_field *acl, int retv)
63ca1c9b0cSelric {
64ca1c9b0cSelric     struct acl_field *next;
65ca1c9b0cSelric     if (retv)
66ca1c9b0cSelric 	free_retv(acl);
67ca1c9b0cSelric     while(acl != NULL) {
68ca1c9b0cSelric 	next = acl->next;
69ca1c9b0cSelric 	free(acl);
70ca1c9b0cSelric 	acl = next;
71ca1c9b0cSelric     }
72ca1c9b0cSelric }
73ca1c9b0cSelric 
74ca1c9b0cSelric static krb5_error_code
acl_parse_format(krb5_context context,struct acl_field ** acl_ret,const char * format,va_list ap)75ca1c9b0cSelric acl_parse_format(krb5_context context,
76ca1c9b0cSelric 		 struct acl_field **acl_ret,
77ca1c9b0cSelric 		 const char *format,
78ca1c9b0cSelric 		 va_list ap)
79ca1c9b0cSelric {
80ca1c9b0cSelric     const char *p;
81ca1c9b0cSelric     struct acl_field *acl = NULL, *tmp;
82ca1c9b0cSelric 
83ca1c9b0cSelric     for(p = format; *p != '\0'; p++) {
84ca1c9b0cSelric 	tmp = malloc(sizeof(*tmp));
85ca1c9b0cSelric 	if(tmp == NULL) {
86ca1c9b0cSelric 	    acl_free_list(acl, 0);
87b9d004c6Schristos 	    return krb5_enomem(context);
88ca1c9b0cSelric 	}
89ca1c9b0cSelric 	if(*p == 's') {
90ca1c9b0cSelric 	    tmp->type = acl_string;
91ca1c9b0cSelric 	    tmp->u.cstr = va_arg(ap, const char*);
92ca1c9b0cSelric 	} else if(*p == 'f') {
93ca1c9b0cSelric 	    tmp->type = acl_fnmatch;
94ca1c9b0cSelric 	    tmp->u.cstr = va_arg(ap, const char*);
95ca1c9b0cSelric 	} else if(*p == 'r') {
96ca1c9b0cSelric 	    tmp->type = acl_retval;
97ca1c9b0cSelric 	    tmp->u.retv = va_arg(ap, char **);
98ca1c9b0cSelric 	    *tmp->u.retv = NULL;
99ca1c9b0cSelric 	} else {
100ca1c9b0cSelric 	    krb5_set_error_message(context, EINVAL,
101ca1c9b0cSelric 				   N_("Unknown format specifier %c while "
102ca1c9b0cSelric 				     "parsing ACL", "specifier"), *p);
103ca1c9b0cSelric 	    acl_free_list(acl, 0);
104ca1c9b0cSelric 	    free(tmp);
105ca1c9b0cSelric 	    return EINVAL;
106ca1c9b0cSelric 	}
107ca1c9b0cSelric 	tmp->next = NULL;
108ca1c9b0cSelric 	if(acl == NULL)
109ca1c9b0cSelric 	    acl = tmp;
110ca1c9b0cSelric 	else
111ca1c9b0cSelric 	    *acl->last = tmp;
112ca1c9b0cSelric 	acl->last = &tmp->next;
113ca1c9b0cSelric     }
114ca1c9b0cSelric     *acl_ret = acl;
115ca1c9b0cSelric     return 0;
116ca1c9b0cSelric }
117ca1c9b0cSelric 
118ca1c9b0cSelric static krb5_boolean
acl_match_field(krb5_context context,const char * string,struct acl_field * field)119ca1c9b0cSelric acl_match_field(krb5_context context,
120ca1c9b0cSelric 		const char *string,
121ca1c9b0cSelric 		struct acl_field *field)
122ca1c9b0cSelric {
123ca1c9b0cSelric     if(field->type == acl_string) {
124ca1c9b0cSelric 	return !strcmp(field->u.cstr, string);
125ca1c9b0cSelric     } else if(field->type == acl_fnmatch) {
126ca1c9b0cSelric 	return !fnmatch(field->u.cstr, string, 0);
127ca1c9b0cSelric     } else if(field->type == acl_retval) {
128ca1c9b0cSelric 	*field->u.retv = strdup(string);
129ca1c9b0cSelric 	return TRUE;
130ca1c9b0cSelric     }
131ca1c9b0cSelric     return FALSE;
132ca1c9b0cSelric }
133ca1c9b0cSelric 
134ca1c9b0cSelric static krb5_boolean
acl_match_acl(krb5_context context,struct acl_field * acl,const char * string)135ca1c9b0cSelric acl_match_acl(krb5_context context,
136ca1c9b0cSelric 	      struct acl_field *acl,
137ca1c9b0cSelric 	      const char *string)
138ca1c9b0cSelric {
139ca1c9b0cSelric     char buf[256];
140ca1c9b0cSelric     while(strsep_copy(&string, " \t", buf, sizeof(buf)) != -1) {
141ca1c9b0cSelric 	if(buf[0] == '\0')
142ca1c9b0cSelric 	    continue; /* skip ws */
143ca1c9b0cSelric 	if (acl == NULL)
144ca1c9b0cSelric 	    return FALSE;
145ca1c9b0cSelric 	if(!acl_match_field(context, buf, acl)) {
146ca1c9b0cSelric 	    return FALSE;
147ca1c9b0cSelric 	}
148ca1c9b0cSelric 	acl = acl->next;
149ca1c9b0cSelric     }
150ca1c9b0cSelric     if (acl)
151ca1c9b0cSelric 	return FALSE;
152ca1c9b0cSelric     return TRUE;
153ca1c9b0cSelric }
154ca1c9b0cSelric 
155ca1c9b0cSelric /**
156ca1c9b0cSelric  * krb5_acl_match_string matches ACL format against a string.
157ca1c9b0cSelric  *
158ca1c9b0cSelric  * The ACL format has three format specifiers: s, f, and r.  Each
159ca1c9b0cSelric  * specifier will retrieve one argument from the variable arguments
160ca1c9b0cSelric  * for either matching or storing data.  The input string is split up
161ca1c9b0cSelric  * using " " (space) and "\t" (tab) as a delimiter; multiple and "\t"
162ca1c9b0cSelric  * in a row are considered to be the same.
163ca1c9b0cSelric  *
164ca1c9b0cSelric  * List of format specifiers:
165ca1c9b0cSelric  * - s Matches a string using strcmp(3) (case sensitive).
166ca1c9b0cSelric  * - f Matches the string with fnmatch(3). Theflags
167ca1c9b0cSelric  *     argument (the last argument) passed to the fnmatch function is 0.
168ca1c9b0cSelric  * - r Returns a copy of the string in the char ** passed in; the copy
169ca1c9b0cSelric  *     must be freed with free(3). There is no need to free(3) the
170ca1c9b0cSelric  *     string on error: the function will clean up and set the pointer
171ca1c9b0cSelric  *     to NULL.
172ca1c9b0cSelric  *
173ca1c9b0cSelric  * @param context Kerberos 5 context
174ca1c9b0cSelric  * @param string string to match with
175ca1c9b0cSelric  * @param format format to match
176ca1c9b0cSelric  * @param ... parameter to format string
177ca1c9b0cSelric  *
178ca1c9b0cSelric  * @return Return an error code or 0.
179ca1c9b0cSelric  *
180ca1c9b0cSelric  *
181ca1c9b0cSelric  * @code
182ca1c9b0cSelric  * char *s;
183ca1c9b0cSelric  *
184ca1c9b0cSelric  * ret = krb5_acl_match_string(context, "foo", "s", "foo");
185ca1c9b0cSelric  * if (ret)
186ca1c9b0cSelric  *     krb5_errx(context, 1, "acl didn't match");
187ca1c9b0cSelric  * ret = krb5_acl_match_string(context, "foo foo baz/kaka",
188ca1c9b0cSelric  *     "ss", "foo", &s, "foo/\\*");
189ca1c9b0cSelric  * if (ret) {
190ca1c9b0cSelric  *     // no need to free(s) on error
191ca1c9b0cSelric  *     assert(s == NULL);
192ca1c9b0cSelric  *     krb5_errx(context, 1, "acl didn't match");
193ca1c9b0cSelric  * }
194ca1c9b0cSelric  * free(s);
195ca1c9b0cSelric  * @endcode
196ca1c9b0cSelric  *
197ca1c9b0cSelric  * @sa krb5_acl_match_file
198ca1c9b0cSelric  * @ingroup krb5_support
199ca1c9b0cSelric  */
200ca1c9b0cSelric 
201ca1c9b0cSelric KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
krb5_acl_match_string(krb5_context context,const char * string,const char * format,...)202ca1c9b0cSelric krb5_acl_match_string(krb5_context context,
203ca1c9b0cSelric 		      const char *string,
204ca1c9b0cSelric 		      const char *format,
205ca1c9b0cSelric 		      ...)
206ca1c9b0cSelric {
207ca1c9b0cSelric     krb5_error_code ret;
208ca1c9b0cSelric     krb5_boolean found;
209ca1c9b0cSelric     struct acl_field *acl;
210ca1c9b0cSelric 
211ca1c9b0cSelric     va_list ap;
212ca1c9b0cSelric     va_start(ap, format);
213ca1c9b0cSelric     ret = acl_parse_format(context, &acl, format, ap);
214ca1c9b0cSelric     va_end(ap);
215ca1c9b0cSelric     if(ret)
216ca1c9b0cSelric 	return ret;
217ca1c9b0cSelric 
218ca1c9b0cSelric     found = acl_match_acl(context, acl, string);
219ca1c9b0cSelric     acl_free_list(acl, !found);
220ca1c9b0cSelric     if (found) {
221ca1c9b0cSelric 	return 0;
222ca1c9b0cSelric     } else {
223ca1c9b0cSelric 	krb5_set_error_message(context, EACCES, N_("ACL did not match", ""));
224ca1c9b0cSelric 	return EACCES;
225ca1c9b0cSelric     }
226ca1c9b0cSelric }
227ca1c9b0cSelric 
228ca1c9b0cSelric /**
229ca1c9b0cSelric  * krb5_acl_match_file matches ACL format against each line in a file
230ca1c9b0cSelric  * using krb5_acl_match_string(). Lines starting with # are treated
231ca1c9b0cSelric  * like comments and ignored.
232ca1c9b0cSelric  *
233ca1c9b0cSelric  * @param context Kerberos 5 context.
234ca1c9b0cSelric  * @param file file with acl listed in the file.
235ca1c9b0cSelric  * @param format format to match.
236ca1c9b0cSelric  * @param ... parameter to format string.
237ca1c9b0cSelric  *
238ca1c9b0cSelric  * @return Return an error code or 0.
239ca1c9b0cSelric  *
240ca1c9b0cSelric  * @sa krb5_acl_match_string
241ca1c9b0cSelric  * @ingroup krb5_support
242ca1c9b0cSelric  */
243ca1c9b0cSelric 
244ca1c9b0cSelric KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
krb5_acl_match_file(krb5_context context,const char * file,const char * format,...)245ca1c9b0cSelric krb5_acl_match_file(krb5_context context,
246ca1c9b0cSelric 		    const char *file,
247ca1c9b0cSelric 		    const char *format,
248ca1c9b0cSelric 		    ...)
249ca1c9b0cSelric {
250ca1c9b0cSelric     krb5_error_code ret;
251*afab4e30Schristos     struct acl_field *acl = NULL;
252ca1c9b0cSelric     char buf[256];
253ca1c9b0cSelric     va_list ap;
254ca1c9b0cSelric     FILE *f;
255ca1c9b0cSelric     krb5_boolean found;
256ca1c9b0cSelric 
257ca1c9b0cSelric     f = fopen(file, "r");
258ca1c9b0cSelric     if(f == NULL) {
259ca1c9b0cSelric 	int save_errno = errno;
260ca1c9b0cSelric 	rk_strerror_r(save_errno, buf, sizeof(buf));
261ca1c9b0cSelric 	krb5_set_error_message(context, save_errno,
262ca1c9b0cSelric 			       N_("open(%s): %s", "file, errno"),
263ca1c9b0cSelric 			       file, buf);
264ca1c9b0cSelric 	return save_errno;
265ca1c9b0cSelric     }
266ca1c9b0cSelric     rk_cloexec_file(f);
267ca1c9b0cSelric 
268ca1c9b0cSelric     va_start(ap, format);
269ca1c9b0cSelric     ret = acl_parse_format(context, &acl, format, ap);
270ca1c9b0cSelric     va_end(ap);
271ca1c9b0cSelric     if(ret) {
272ca1c9b0cSelric 	fclose(f);
273ca1c9b0cSelric 	return ret;
274ca1c9b0cSelric     }
275ca1c9b0cSelric 
276ca1c9b0cSelric     found = FALSE;
277ca1c9b0cSelric     while(fgets(buf, sizeof(buf), f)) {
278ca1c9b0cSelric 	if(buf[0] == '#')
279ca1c9b0cSelric 	    continue;
280ca1c9b0cSelric 	if(acl_match_acl(context, acl, buf)) {
281ca1c9b0cSelric 	    found = TRUE;
282ca1c9b0cSelric 	    break;
283ca1c9b0cSelric 	}
284ca1c9b0cSelric 	free_retv(acl);
285ca1c9b0cSelric     }
286ca1c9b0cSelric 
287ca1c9b0cSelric     fclose(f);
288ca1c9b0cSelric     acl_free_list(acl, !found);
289ca1c9b0cSelric     if (found) {
290ca1c9b0cSelric 	return 0;
291ca1c9b0cSelric     } else {
292ca1c9b0cSelric 	krb5_set_error_message(context, EACCES, N_("ACL did not match", ""));
293ca1c9b0cSelric 	return EACCES;
294ca1c9b0cSelric     }
295ca1c9b0cSelric }
296