Rewrite krb5_parse_name
[krb5.git] / src / lib / krb5 / krb / parse.c
1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /* lib/krb5/krb/parse.c - Parse strings into krb5_principals */
3 /*
4  * Copyright 1990,1991,2008,2012 by the Massachusetts Institute of Technology.
5  * All Rights Reserved.
6  *
7  * Export of this software from the United States of America may
8  *   require a specific license from the United States Government.
9  *   It is the responsibility of any person or organization contemplating
10  *   export to obtain such a license before exporting.
11  *
12  * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
13  * distribute this software and its documentation for any purpose and
14  * without fee is hereby granted, provided that the above copyright
15  * notice appear in all copies and that both that copyright notice and
16  * this permission notice appear in supporting documentation, and that
17  * the name of M.I.T. not be used in advertising or publicity pertaining
18  * to distribution of the software without specific, written prior
19  * permission.  Furthermore if you modify this software you must label
20  * your software as modified software and not distribute it in such a
21  * fashion that it might be confused with the original M.I.T. software.
22  * M.I.T. makes no representations about the suitability of
23  * this software for any purpose.  It is provided "as is" without express
24  * or implied warranty.
25  */
26
27 #include "k5-int.h"
28
29 /*
30  * Scan name and allocate a shell principal with enough space in each field.
31  * If enterprise is true, use enterprise principal parsing rules.  Return
32  * KRB5_PARSE_MALFORMED if name is malformed.  Set *has_realm_out according to
33  * whether name contains a realm separator.
34  */
35 static krb5_error_code
36 allocate_princ(krb5_context context, const char *name, krb5_boolean enterprise,
37                krb5_principal *princ_out, krb5_boolean *has_realm_out)
38 {
39     krb5_error_code ret;
40     const char *p;
41     krb5_boolean first_at = TRUE;
42     krb5_principal princ = NULL;
43     krb5_data *cur_data, *new_comps;
44     krb5_int32 i;
45
46     *princ_out = NULL;
47     *has_realm_out = FALSE;
48
49     /* Allocate a starting principal with one component. */
50     princ = k5alloc(sizeof(*princ), &ret);
51     if (princ == NULL)
52         goto cleanup;
53     princ->data = k5alloc(sizeof(*princ->data), &ret);
54     if (princ->data == NULL)
55         goto cleanup;
56     princ->realm = empty_data();
57     princ->data[0] = empty_data();
58     princ->length = 1;
59
60     cur_data = &princ->data[0];
61     for (p = name; *p != '\0'; p++) {
62         if (*p == '/' && !enterprise) {
63             /* Component separator (for non-enterprise principals).  We
64              * shouldn't see this in the realm name. */
65             if (cur_data == &princ->realm) {
66                 ret = KRB5_PARSE_MALFORMED;
67                 goto cleanup;
68             }
69             new_comps = realloc(princ->data,
70                                 (princ->length + 1) * sizeof(*princ->data));
71             if (new_comps == NULL) {
72                 ret = ENOMEM;
73                 goto cleanup;
74             }
75             princ->data = new_comps;
76             princ->length++;
77             cur_data = &princ->data[princ->length - 1];
78             *cur_data = empty_data();
79         } else if (*p == '@' && (!enterprise || !first_at)) {
80             /* Realm separator.  In enterprise principals, the first one of
81              * these we see is part of the component. */
82             cur_data = &princ->realm;
83         } else {
84             /* Component or realm character, possibly quoted.  Make note if
85              * we're seeing the first '@' in an enterprise principal. */
86             cur_data->length++;
87             if (*p == '@' && enterprise)
88                 first_at = FALSE;
89             if (*p == '\\') {
90                 /* Quote character can't be the last character of the name. */
91                 if (*++p == '\0') {
92                     ret = KRB5_PARSE_MALFORMED;
93                     goto cleanup;
94                 }
95             }
96         }
97     }
98
99     /* Allocate space for each non-empty component and the realm. */
100     for (i = 0; i < princ->length; i++) {
101         if (princ->data[i].length > 0) {
102             princ->data[i].data = k5alloc(princ->data[i].length, &ret);
103             if (princ->data[i].data == NULL)
104                 goto cleanup;
105         }
106     }
107     if (princ->realm.length > 0) {
108         princ->realm.data = k5alloc(princ->realm.length, &ret);
109         if (princ->realm.data == NULL)
110             goto cleanup;
111     }
112
113     *princ_out = princ;
114     *has_realm_out = (cur_data == &princ->realm);
115     princ = NULL;
116 cleanup:
117     krb5_free_principal(context, princ);
118     return ret;
119 }
120
121 /*
122  * Parse name into princ, assuming that name is correctly formed and that all
123  * principal fields are allocated to the correct length.  If enterprise is
124  * true, use enterprise principal parsing rules.
125  */
126 static void
127 parse_name_into_princ(const char *name, krb5_boolean enterprise,
128                       krb5_principal princ)
129 {
130     const char *p;
131     char c;
132     krb5_boolean first_at = TRUE;
133     krb5_data *cur_data = princ->data;
134     unsigned int pos = 0;
135
136     for (p = name; *p != '\0'; p++) {
137         if (*p == '/' && !enterprise) {
138             /* Advance to the next component. */
139             assert(pos == cur_data->length);
140             assert(cur_data != &princ->realm);
141             assert(cur_data - princ->data + 1 < princ->length);
142             cur_data++;
143             pos = 0;
144         } else if (*p == '@' && (!enterprise || !first_at)) {
145             /* Advance to the realm. */
146             assert(pos == cur_data->length);
147             cur_data = &princ->realm;
148             pos = 0;
149         } else {
150             /* Add to the current component or to the realm. */
151             if (*p == '@' && enterprise)
152                 first_at = FALSE;
153             c = *p;
154             if (c == '\\') {
155                 c = *++p;
156                 if (c == 'n')
157                     c = '\n';
158                 else if (c == 't')
159                     c = '\t';
160                 else if (c == 'b')
161                     c = '\b';
162                 else if (c == '0')
163                     c = '\0';
164             }
165             assert(pos < cur_data->length);
166             cur_data->data[pos++] = c;
167         }
168     }
169     assert(pos == cur_data->length);
170 }
171
172 krb5_error_code KRB5_CALLCONV
173 krb5_parse_name_flags(krb5_context context, const char *name,
174                       int flags, krb5_principal *principal_out)
175 {
176     krb5_error_code ret;
177     krb5_principal princ = NULL;
178     char *default_realm;
179     krb5_boolean has_realm;
180     krb5_boolean enterprise = (flags & KRB5_PRINCIPAL_PARSE_ENTERPRISE);
181     krb5_boolean require_realm = (flags & KRB5_PRINCIPAL_PARSE_REQUIRE_REALM);
182     krb5_boolean no_realm = (flags & KRB5_PRINCIPAL_PARSE_NO_REALM);
183
184     *principal_out = NULL;
185
186     ret = allocate_princ(context, name, enterprise, &princ, &has_realm);
187     if (ret)
188         goto cleanup;
189     parse_name_into_princ(name, enterprise, princ);
190
191     /*
192      * If a realm was not found, then use the default realm, unless
193      * KRB5_PRINCIPAL_PARSE_NO_REALM was specified in which case the
194      * realm will be empty.
195      */
196     if (!has_realm) {
197         if (require_realm) {
198             ret = KRB5_PARSE_MALFORMED;
199             krb5_set_error_message(context, ret,
200                                    _("Principal %s is missing required realm"),
201                                    name);
202             goto cleanup;
203         }
204         if (!no_realm) {
205             ret = krb5_get_default_realm(context, &default_realm);
206             if (ret)
207                 goto cleanup;
208             princ->realm = string2data(default_realm);
209         }
210     } else if (no_realm) {
211         ret = KRB5_PARSE_MALFORMED;
212         krb5_set_error_message(context, ret,
213                                _("Principal %s has realm present"), name);
214         goto cleanup;
215     }
216
217     princ->type = (enterprise) ? KRB5_NT_ENTERPRISE_PRINCIPAL :
218         KRB5_NT_PRINCIPAL;
219     princ->magic = KV5M_PRINCIPAL;
220     *principal_out = princ;
221     princ = NULL;
222
223 cleanup:
224     krb5_free_principal(context, princ);
225     return ret;
226 }
227
228 krb5_error_code KRB5_CALLCONV
229 krb5_parse_name(krb5_context context, const char *name,
230                 krb5_principal *principal_out)
231 {
232     return krb5_parse_name_flags(context, name, 0, principal_out);
233 }