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