Imported Upstream version 1.17
[platform/upstream/krb5.git] / src / lib / krb5 / os / localauth_rule.c
1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /* lib/krb5/os/localauth_rule.c - rule localauth module */
3 /*
4  * Copyright (C) 1990,1991,2007,2008,2013 by the Massachusetts
5  * Institute of Technology.  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  *
11  * * Redistributions of source code must retain the above copyright
12  *   notice, this list of conditions and the following disclaimer.
13  *
14  * * Redistributions in binary form must reproduce the above copyright
15  *   notice, this list of conditions and the following disclaimer in
16  *   the documentation and/or other materials provided with the
17  *   distribution.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
22  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
23  * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
24  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
25  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
28  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
30  * OF THE POSSIBILITY OF SUCH DAMAGE.
31  */
32
33 /*
34  * This module implements the RULE type for auth_to_local processing.
35  *
36  * There are three parts to each rule.  The first part, if present, determines
37  * the selection string.  If this is not present, the selection string defaults
38  * to the unparsed principal name without realm (which can be dangerous in
39  * multi-realm environments, but is our historical behavior).  The selection
40  * string syntax is:
41  *
42  *     "[" <ncomps> ":" <format> "]"
43  *
44  *         <ncomps> is the number of expected components for this rule.  If the
45  *         principal does not have this many components, then this rule does
46  *         not apply.
47  *
48  *         <format> determines the selection string.  Within <format>, $0 will
49  *         be substituted with the principal's realm, $1 with its first
50  *         component, $2 with its second component, and so forth.
51  *
52  * The second part is an optional regular expression surrounded by parentheses.
53  * If present, the rule will only apply if the selection string matches the
54  * regular expression.  At present, the regular expression may not contain a
55  * ')' character.
56  *
57  * The third part is a sequence of zero or more transformation rules, using
58  * the syntax:
59  *
60  *     "s/" <regexp> "/" <text> "/" ["g"]
61  *
62  * No substitutions are allowed within <text>.  A "g" indicates that the
63  * substitution should be performed globally; otherwise it will be performed at
64  * most once.
65  */
66
67 #include "k5-int.h"
68 #include "os-proto.h"
69 #include <krb5/localauth_plugin.h>
70 #include <ctype.h>
71
72 #ifdef HAVE_REGEX_H
73 #include <regex.h>
74
75 /* Process the match portion of a rule and update *contextp.  Return
76  * KRB5_LNAME_NOTRANS if selstring doesn't match the regexp. */
77 static krb5_error_code
78 aname_do_match(const char *selstring, const char **contextp)
79 {
80     krb5_error_code ret;
81     const char *startp, *endp;
82     char *regstr;
83     regex_t re;
84     regmatch_t m;
85
86     /* If no regexp is present, leave *contextp alone and return success. */
87     if (**contextp != '(')
88         return 0;
89
90     /* Find the end of the regexp and make a copy of it. */
91     startp = *contextp + 1;
92     endp = strchr(startp, ')');
93     if (endp == NULL)
94         return KRB5_CONFIG_BADFORMAT;
95     regstr = k5memdup0(startp, endp - startp, &ret);
96     if (regstr == NULL)
97         return ret;
98
99     /* Perform the match. */
100     ret = (regcomp(&re, regstr, REG_EXTENDED) == 0 &&
101            regexec(&re, selstring, 1, &m, 0) == 0 &&
102            m.rm_so == 0 && (size_t)m.rm_eo == strlen(selstring)) ? 0 :
103         KRB5_LNAME_NOTRANS;
104     regfree(&re);
105     free(regstr);
106     *contextp = endp + 1;
107     return ret;
108 }
109
110 /* Replace regular expression matches of regstr with repl in instr, producing
111  * *outstr.  If doall is true, replace all matches for regstr. */
112 static krb5_error_code
113 do_replacement(const char *regstr, const char *repl, krb5_boolean doall,
114                const char *instr, char **outstr)
115 {
116     struct k5buf buf;
117     regex_t re;
118     regmatch_t m;
119
120     *outstr = NULL;
121     if (regcomp(&re, regstr, REG_EXTENDED))
122         return KRB5_LNAME_NOTRANS;
123     k5_buf_init_dynamic(&buf);
124     while (regexec(&re, instr, 1, &m, 0) == 0) {
125         k5_buf_add_len(&buf, instr, m.rm_so);
126         k5_buf_add(&buf, repl);
127         instr += m.rm_eo;
128         if (!doall)
129             break;
130     }
131     regfree(&re);
132     k5_buf_add(&buf, instr);
133     if (k5_buf_status(&buf) != 0)
134         return ENOMEM;
135     *outstr = buf.data;
136     return 0;
137 }
138
139 /*
140  * Perform any substitutions specified by *contextp, and advance *contextp past
141  * the substitution expressions.  Place the result of the substitutions in
142  * *result.
143  */
144 static krb5_error_code
145 aname_replacer(const char *string, const char **contextp, char **result)
146 {
147     krb5_error_code ret = 0;
148     const char *cp, *ep, *tp;
149     char *newstr, *rule = NULL, *repl = NULL, *current = NULL;
150     krb5_boolean doglobal;
151
152     *result = NULL;
153
154     current = strdup(string);
155     if (current == NULL)
156         return ENOMEM;
157
158     /* Iterate over replacement expressions, updating current for each one. */
159     cp = *contextp;
160     while (*cp != '\0') {
161         /* Skip leading whitespace */
162         while (isspace((unsigned char)*cp))
163             cp++;
164
165         /* Find the separators for an s/rule/repl/ expression. */
166         if (!(cp[0] == 's' && cp[1] == '/' && (ep = strchr(cp + 2, '/')) &&
167               (tp = strchr(ep + 1, '/')))) {
168             ret = KRB5_CONFIG_BADFORMAT;
169             goto cleanup;
170         }
171
172         /* Copy the rule and replacement strings. */
173         free(rule);
174         rule = k5memdup0(cp + 2, ep - (cp + 2), &ret);
175         if (rule == NULL)
176             goto cleanup;
177         free(repl);
178         repl = k5memdup0(ep + 1, tp - (ep + 1), &ret);
179         if (repl == NULL)
180             goto cleanup;
181
182         /* Advance past expression and check for trailing "g". */
183         cp = tp + 1;
184         doglobal = (*cp == 'g');
185         if (doglobal)
186             cp++;
187
188         ret = do_replacement(rule, repl, doglobal, current, &newstr);
189         if (ret)
190             goto cleanup;
191         free(current);
192         current = newstr;
193     }
194     *result = current;
195     current = NULL;
196
197 cleanup:
198     free(current);
199     free(repl);
200     free(rule);
201     return ret;
202 }
203
204 /*
205  * Compute selection string for RULE rules.  Advance *contextp to the string
206  * position after the selstring part if present, and set *result to the
207  * selection string.
208  */
209 static krb5_error_code
210 aname_get_selstring(krb5_context context, krb5_const_principal aname,
211                     const char **contextp, char **selstring_out)
212 {
213     const char *current;
214     char *end;
215     long num_comps, ind;
216     const krb5_data *datap;
217     struct k5buf selstring;
218     size_t nlit;
219
220     *selstring_out = NULL;
221     if (**contextp != '[') {
222         /*
223          * No selstring part; use the principal name without realm.  This is
224          * problematic in many multiple-realm environments, but is how we've
225          * historically done it.
226          */
227         return krb5_unparse_name_flags(context, aname,
228                                        KRB5_PRINCIPAL_UNPARSE_NO_REALM,
229                                        selstring_out);
230     }
231
232     /* Advance past the '[' and read the number of components. */
233     current = *contextp + 1;
234     errno = 0;
235     num_comps = strtol(current, &end, 10);
236     if (errno != 0 || num_comps < 0 || *end != ':')
237         return KRB5_CONFIG_BADFORMAT;
238     current = end;
239     if (num_comps != aname->length)
240         return KRB5_LNAME_NOTRANS;
241     current++;
242
243     k5_buf_init_dynamic(&selstring);
244     while (TRUE) {
245         /* Copy in literal characters up to the next $ or ]. */
246         nlit = strcspn(current, "$]");
247         k5_buf_add_len(&selstring, current, nlit);
248         current += nlit;
249         if (*current != '$')
250             break;
251
252         /* Expand $ substitution to a principal component. */
253         errno = 0;
254         ind = strtol(current + 1, &end, 10);
255         if (errno || ind > num_comps)
256             break;
257         current = end;
258         datap = ind > 0 ? &aname->data[ind - 1] : &aname->realm;
259         k5_buf_add_len(&selstring, datap->data, datap->length);
260     }
261
262     /* Check that we hit a ']' and not the end of the string. */
263     if (*current != ']') {
264         k5_buf_free(&selstring);
265         return KRB5_CONFIG_BADFORMAT;
266     }
267
268     if (k5_buf_status(&selstring) != 0)
269         return ENOMEM;
270
271     *contextp = current + 1;
272     *selstring_out = selstring.data;
273     return 0;
274 }
275
276 static krb5_error_code
277 an2ln_rule(krb5_context context, krb5_localauth_moddata data, const char *type,
278            const char *rule, krb5_const_principal aname, char **lname_out)
279 {
280     krb5_error_code ret;
281     const char *current;
282     char *selstring = NULL;
283
284     *lname_out = NULL;
285     if (rule == NULL)
286         return KRB5_CONFIG_BADFORMAT;
287
288     /* Compute the selection string. */
289     current = rule;
290     ret = aname_get_selstring(context, aname, &current, &selstring);
291     if (ret)
292         return ret;
293
294     /* Check the selection string against the regexp, if present. */
295     if (*current == '(') {
296         ret = aname_do_match(selstring, &current);
297         if (ret)
298             goto cleanup;
299     }
300
301     /* Perform the substitution. */
302     ret = aname_replacer(selstring, &current, lname_out);
303
304 cleanup:
305     free(selstring);
306     return ret;
307 }
308
309 #else /* HAVE_REGEX_H */
310
311 static krb5_error_code
312 an2ln_rule(krb5_context context, krb5_localauth_moddata data, const char *type,
313            const char *rule, krb5_const_principal aname, char **lname_out)
314 {
315     return KRB5_LNAME_NOTRANS;
316 }
317
318 #endif
319
320 static void
321 freestr(krb5_context context, krb5_localauth_moddata data, char *str)
322 {
323     free(str);
324 }
325
326 krb5_error_code
327 localauth_rule_initvt(krb5_context context, int maj_ver, int min_ver,
328                       krb5_plugin_vtable vtable)
329 {
330     krb5_localauth_vtable vt = (krb5_localauth_vtable)vtable;
331     static const char *types[] = { "RULE", NULL };
332
333     vt->name = "rule";
334     vt->an2ln_types = types;
335     vt->an2ln = an2ln_rule;
336     vt->free_string = freestr;
337     return 0;
338 }