Rewrite krb5_parse_name
authorGreg Hudson <ghudson@mit.edu>
Thu, 10 May 2012 17:34:07 +0000 (17:34 +0000)
committerGreg Hudson <ghudson@mit.edu>
Thu, 10 May 2012 17:34:07 +0000 (17:34 +0000)
krb5_parse_name started out a bit unwieldy, and has become more so
with the introduction of flags.  Rewrite it using two passes (allocate
and fill), each broken out into its own helper, and a wrapper which
handles the realm flags.

git-svn-id: svn://anonsvn.mit.edu/krb5/trunk@25861 dc483132-0cff-0310-8789-dd5450dbe970

src/include/krb5/krb5.hin
src/lib/krb5/krb/parse.c

index f319136ecb6bf60eaa177ea0babe584d745681c6..284e6b327c0b53c6752fef471f73bd85ba960ac1 100644 (file)
@@ -3374,7 +3374,7 @@ krb5_rd_priv(krb5_context context, krb5_auth_context auth_context,
  *
  * @param [in]  context         Library context
  * @param [in]  name            String representation of a principal name
- * @param [out] nprincipal      Principal
+ * @param [out] principal_out   New principal
  *
  * Convert a string representation of a principal name to a krb5_principal
  * structure.
@@ -3393,7 +3393,8 @@ krb5_rd_priv(krb5_context context, krb5_auth_context auth_context,
  * @note The realm in a Kerberos @a name cannot contain slash, colon,
  * or NULL characters.
  *
- * Use krb5_free_principal() to free @a nprincipal when it is no longer needed.
+ * Use krb5_free_principal() to free @a principal_out when it is no longer
+ * needed.
  *
  * @retval
  * 0 Success
@@ -3401,7 +3402,8 @@ krb5_rd_priv(krb5_context context, krb5_auth_context auth_context,
  * Kerberos error codes
  */
 krb5_error_code KRB5_CALLCONV
-krb5_parse_name(krb5_context context, const char *name, krb5_principal *nprincipal);
+krb5_parse_name(krb5_context context, const char *name,
+                krb5_principal *principal_out);
 
 #define KRB5_PRINCIPAL_PARSE_NO_REALM      0x1 /**< Error if realm is present */
 #define KRB5_PRINCIPAL_PARSE_REQUIRE_REALM 0x2 /**< Error if realm is not present */
@@ -3409,12 +3411,12 @@ krb5_parse_name(krb5_context context, const char *name, krb5_principal *nprincip
                                                   enterprise principle */
 
 /**
- * Convert a string principal name to a krb5_principal with flags
+ * Convert a string principal name to a krb5_principal with flags.
  *
  * @param [in]  context         Library context
  * @param [in]  name            String representation of a principal name
  * @param [in]  flags           Flag
- * @param [out] nprincipal      Principal
+ * @param [out] principal_out   New principal
  *
  * Similar to krb5_parse_name(), this function converts a single-string
  * representation of a principal name to a krb5_principal structure.
@@ -3425,7 +3427,8 @@ krb5_parse_name(krb5_context context, const char *name, krb5_principal *nprincip
  * @li #KRB5_PRINCIPAL_PARSE_ENTERPRISE - create single-component enterprise
  *                                        principal
  *
- * Use krb5_free_principal() to free @a nprincipal when it is no longer needed.
+ * Use krb5_free_principal() to free @a principal_out when it is no longer
+ * needed.
  *
  * @retval
  * 0 Success
@@ -3434,7 +3437,7 @@ krb5_parse_name(krb5_context context, const char *name, krb5_principal *nprincip
  */
 krb5_error_code KRB5_CALLCONV
 krb5_parse_name_flags(krb5_context context, const char *name,
-                      int flags, krb5_principal *nprincipal);
+                      int flags, krb5_principal *principal_out);
 
 /**
  * Convert a krb5_principal structure to a string representation.
index 9e1f6ee62ac6e5c48402b9a9c91db44cfef2c15e..fb28229a35e4139a1912b905d2ac4e8225a50e79 100644 (file)
@@ -1,7 +1,7 @@
 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
-/* lib/krb5/krb/parse.c */
+/* lib/krb5/krb/parse.c - Parse strings into krb5_principals */
 /*
- * Copyright 1990,1991,2008 by the Massachusetts Institute of Technology.
+ * Copyright 1990,1991,2008,2012 by the Massachusetts Institute of Technology.
  * All Rights Reserved.
  *
  * Export of this software from the United States of America may
  * or implied warranty.
  */
 
-/*
- * krb5_parse_name() routine.
- *
- * Rewritten by Theodore Ts'o to properly handle arbitrary quoted
- * characters in the principal name.
- */
-
-
 #include "k5-int.h"
 
 /*
- * converts a single-string representation of the name to the
- * multi-part principal format used in the protocols.
- *
- * principal will point to allocated storage which should be freed by
- * the caller (using krb5_free_principal) after use.
- *
- * Conventions:  / is used to separate components.  If @ is present in the
- * string, then the rest of the string after it represents the realm name.
- * Otherwise the local realm name is used.
- *
- * error return:
- *      KRB5_PARSE_MALFORMED    badly formatted string
- *
- * also returns system errors:
- *      ENOMEM  malloc failed/out of memory
- *
- * get_default_realm() is called; it may return other errors.
- */
-
-#define REALM_SEP       '@'
-#define COMPONENT_SEP   '/'
-#define QUOTECHAR       '\\'
-
-#define FCOMPNUM        10
-
-/*
- * May the fleas of a thousand camels infest the ISO, they who think
- * that arbitrarily large multi-component names are a Good Thing.....
+ * Scan name and allocate a shell principal with enough space in each field.
+ * If enterprise is true, use enterprise principal parsing rules.  Return
+ * KRB5_PARSE_MALFORMED if name is malformed.  Set *has_realm_out according to
+ * whether name contains a realm separator.
  */
 static krb5_error_code
-k5_parse_name(krb5_context context, const char *name,
-              int flags, krb5_principal *nprincipal)
+allocate_princ(krb5_context context, const char *name, krb5_boolean enterprise,
+               krb5_principal *princ_out, krb5_boolean *has_realm_out)
 {
-    register const char     *cp;
-    register char   *q;
-    register int    i,c,size;
-    int             components = 0;
-    const char      *parsed_realm = NULL;
-    int             fcompsize[FCOMPNUM];
-    unsigned int    realmsize = 0;
-    char            *default_realm = NULL;
-    int             default_realm_size = 0;
-    char            *tmpdata;
-    krb5_principal  principal;
-    krb5_error_code retval;
-    unsigned int    enterprise = (flags & KRB5_PRINCIPAL_PARSE_ENTERPRISE);
-    int             first_at;
-
-    *nprincipal = NULL;
-
-    /*
-     * Pass 1.  Find out how many components there are to the name,
-     * and get string sizes for the first FCOMPNUM components. For
-     * enterprise principal names (UPNs), there is only a single
-     * component.
-     */
-    size = 0;
-    for (i=0,cp = name, first_at = 1; (c = *cp); cp++) {
-        if (c == QUOTECHAR) {
-            cp++;
-            if (!(c = *cp))
-                /*
-                 * QUOTECHAR can't be at the last
-                 * character of the name!
-                 */
-                return(KRB5_PARSE_MALFORMED);
-            size++;
-            continue;
-        } else if (c == COMPONENT_SEP && !enterprise) {
-            if (parsed_realm)
-                /*
-                 * Shouldn't see a component separator
-                 * after we've parsed out the realm name!
-                 */
-                return(KRB5_PARSE_MALFORMED);
-            if (i < FCOMPNUM) {
-                fcompsize[i] = size;
+    krb5_error_code ret;
+    const char *p;
+    krb5_boolean first_at = TRUE;
+    krb5_principal princ = NULL;
+    krb5_data *cur_data, *new_comps;
+    krb5_int32 i;
+
+    *princ_out = NULL;
+    *has_realm_out = FALSE;
+
+    /* Allocate a starting principal with one component. */
+    princ = k5alloc(sizeof(*princ), &ret);
+    if (princ == NULL)
+        goto cleanup;
+    princ->data = k5alloc(sizeof(*princ->data), &ret);
+    if (princ->data == NULL)
+        goto cleanup;
+    princ->realm = empty_data();
+    princ->data[0] = empty_data();
+    princ->length = 1;
+
+    cur_data = &princ->data[0];
+    for (p = name; *p != '\0'; p++) {
+        if (*p == '/' && !enterprise) {
+            /* Component separator (for non-enterprise principals).  We
+             * shouldn't see this in the realm name. */
+            if (cur_data == &princ->realm) {
+                ret = KRB5_PARSE_MALFORMED;
+                goto cleanup;
             }
-            size = 0;
-            i++;
-        } else if (c == REALM_SEP && (!enterprise || !first_at)) {
-            if (parsed_realm)
-                /*
-                 * Multiple realm separaters
-                 * not allowed; zero-length realms are.
-                 */
-                return(KRB5_PARSE_MALFORMED);
-            parsed_realm = cp + 1;
-            if (i < FCOMPNUM) {
-                fcompsize[i] = size;
+            new_comps = realloc(princ->data,
+                                (princ->length + 1) * sizeof(*princ->data));
+            if (new_comps == NULL) {
+                ret = ENOMEM;
+                goto cleanup;
             }
-            size = 0;
+            princ->data = new_comps;
+            princ->length++;
+            cur_data = &princ->data[princ->length - 1];
+            *cur_data = empty_data();
+        } else if (*p == '@' && (!enterprise || !first_at)) {
+            /* Realm separator.  In enterprise principals, the first one of
+             * these we see is part of the component. */
+            cur_data = &princ->realm;
         } else {
-            if (c == REALM_SEP && enterprise && first_at)
-                first_at = 0;
+            /* Component or realm character, possibly quoted.  Make note if
+             * we're seeing the first '@' in an enterprise principal. */
+            cur_data->length++;
+            if (*p == '@' && enterprise)
+                first_at = FALSE;
+            if (*p == '\\') {
+                /* Quote character can't be the last character of the name. */
+                if (*++p == '\0') {
+                    ret = KRB5_PARSE_MALFORMED;
+                    goto cleanup;
+                }
+            }
+        }
+    }
 
-            size++;
+    /* Allocate space for each non-empty component and the realm. */
+    for (i = 0; i < princ->length; i++) {
+        if (princ->data[i].length > 0) {
+            princ->data[i].data = k5alloc(princ->data[i].length, &ret);
+            if (princ->data[i].data == NULL)
+                goto cleanup;
         }
     }
-    if (parsed_realm != NULL)
-        realmsize = size;
-    else if (i < FCOMPNUM)
-        fcompsize[i] = size;
-    components = i + 1;
-    /*
-     * Now, we allocate the principal structure and all of its
-     * component pieces
-     */
-    principal = (krb5_principal)malloc(sizeof(krb5_principal_data));
-    if (principal == NULL) {
-        return(ENOMEM);
+    if (princ->realm.length > 0) {
+        princ->realm.data = k5alloc(princ->realm.length, &ret);
+        if (princ->realm.data == NULL)
+            goto cleanup;
     }
-    principal->data = (krb5_data *) malloc(sizeof(krb5_data) * components);
-    if (principal->data == NULL) {
-        free(principal);
-        return ENOMEM;
+
+    *princ_out = princ;
+    *has_realm_out = (cur_data == &princ->realm);
+    princ = NULL;
+cleanup:
+    krb5_free_principal(context, princ);
+    return ret;
+}
+
+/*
+ * Parse name into princ, assuming that name is correctly formed and that all
+ * principal fields are allocated to the correct length.  If enterprise is
+ * true, use enterprise principal parsing rules.
+ */
+static void
+parse_name_into_princ(const char *name, krb5_boolean enterprise,
+                      krb5_principal princ)
+{
+    const char *p;
+    char c;
+    krb5_boolean first_at = TRUE;
+    krb5_data *cur_data = princ->data;
+    unsigned int pos = 0;
+
+    for (p = name; *p != '\0'; p++) {
+        if (*p == '/' && !enterprise) {
+            /* Advance to the next component. */
+            assert(pos == cur_data->length);
+            assert(cur_data != &princ->realm);
+            assert(cur_data - princ->data + 1 < princ->length);
+            cur_data++;
+            pos = 0;
+        } else if (*p == '@' && (!enterprise || !first_at)) {
+            /* Advance to the realm. */
+            assert(pos == cur_data->length);
+            cur_data = &princ->realm;
+            pos = 0;
+        } else {
+            /* Add to the current component or to the realm. */
+            if (*p == '@' && enterprise)
+                first_at = FALSE;
+            c = *p;
+            if (c == '\\') {
+                c = *++p;
+                if (c == 'n')
+                    c = '\n';
+                else if (c == 't')
+                    c = '\t';
+                else if (c == 'b')
+                    c = '\b';
+                else if (c == '0')
+                    c = '\0';
+            }
+            assert(pos < cur_data->length);
+            cur_data->data[pos++] = c;
+        }
     }
-    principal->length = components;
+    assert(pos == cur_data->length);
+}
+
+krb5_error_code KRB5_CALLCONV
+krb5_parse_name_flags(krb5_context context, const char *name,
+                      int flags, krb5_principal *principal_out)
+{
+    krb5_error_code ret;
+    krb5_principal princ = NULL;
+    char *default_realm;
+    krb5_boolean has_realm;
+    krb5_boolean enterprise = (flags & KRB5_PRINCIPAL_PARSE_ENTERPRISE);
+    krb5_boolean require_realm = (flags & KRB5_PRINCIPAL_PARSE_REQUIRE_REALM);
+    krb5_boolean no_realm = (flags & KRB5_PRINCIPAL_PARSE_NO_REALM);
+
+    *principal_out = NULL;
+
+    ret = allocate_princ(context, name, enterprise, &princ, &has_realm);
+    if (ret)
+        goto cleanup;
+    parse_name_into_princ(name, enterprise, princ);
 
     /*
      * If a realm was not found, then use the default realm, unless
      * KRB5_PRINCIPAL_PARSE_NO_REALM was specified in which case the
      * realm will be empty.
      */
-    if (!parsed_realm) {
-        if (flags & KRB5_PRINCIPAL_PARSE_REQUIRE_REALM) {
-            krb5_set_error_message(context, KRB5_PARSE_MALFORMED,
+    if (!has_realm) {
+        if (require_realm) {
+            ret = KRB5_PARSE_MALFORMED;
+            krb5_set_error_message(context, ret,
                                    _("Principal %s is missing required realm"),
                                    name);
-            free(principal->data);
-            free(principal);
-            return KRB5_PARSE_MALFORMED;
+            goto cleanup;
         }
-        if (!default_realm && (flags & KRB5_PRINCIPAL_PARSE_NO_REALM) == 0) {
-            retval = krb5_get_default_realm(context, &default_realm);
-            if (retval) {
-                free(principal->data);
-                free(principal);
-                return(retval);
-            }
-            default_realm_size = strlen(default_realm);
+        if (!no_realm) {
+            ret = krb5_get_default_realm(context, &default_realm);
+            if (ret)
+                goto cleanup;
+            princ->realm = string2data(default_realm);
         }
-        realmsize = default_realm_size;
-    } else if (flags & KRB5_PRINCIPAL_PARSE_NO_REALM) {
-        krb5_set_error_message(context, KRB5_PARSE_MALFORMED,
+    } else if (no_realm) {
+        ret = KRB5_PARSE_MALFORMED;
+        krb5_set_error_message(context, ret,
                                _("Principal %s has realm present"), name);
-        free(principal->data);
-        free(principal);
-        return KRB5_PARSE_MALFORMED;
-    }
-
-    /*
-     * Pass 2.  Happens only if there were more than FCOMPNUM
-     * component; if this happens, someone should be shot
-     * immediately.  Nevertheless, we will attempt to handle said
-     * case..... <martyred sigh>
-     */
-    if (components >= FCOMPNUM) {
-        size = 0;
-        parsed_realm = NULL;
-        for (i=0,cp = name; (c = *cp); cp++) {
-            if (c == QUOTECHAR) {
-                cp++;
-                size++;
-            } else if (c == COMPONENT_SEP) {
-                if (krb5_princ_size(context, principal) > i)
-                    krb5_princ_component(context, principal, i)->length = size;
-                size = 0;
-                i++;
-            } else if (c == REALM_SEP) {
-                if (krb5_princ_size(context, principal) > i)
-                    krb5_princ_component(context, principal, i)->length = size;
-                size = 0;
-                parsed_realm = cp+1;
-            } else
-                size++;
-        }
-        if (parsed_realm)
-            krb5_princ_realm(context, principal)->length = size;
-        else
-            if (krb5_princ_size(context, principal) > i)
-                krb5_princ_component(context, principal, i)->length = size;
-        if (i + 1 != components) {
-#if !defined(_WIN32)
-            fprintf(stderr,
-                    "Programming error in krb5_parse_name!");
-#endif
-            assert(i + 1 == components);
-            abort();
-        }
-    } else {
-        /*
-         * If there were fewer than FCOMPSIZE components (the
-         * usual case), then just copy the sizes to the
-         * principal structure
-         */
-        for (i=0; i < components; i++)
-            krb5_princ_component(context, principal, i)->length = fcompsize[i];
-    }
-    /*
-     * Now, we need to allocate the space for the strings themselves.....
-     */
-    tmpdata = malloc(realmsize + 1);
-    if (tmpdata == 0) {
-        free(principal->data);
-        free(principal);
-        free(default_realm);
-        return ENOMEM;
-    }
-    krb5_princ_set_realm_length(context, principal, realmsize);
-    krb5_princ_set_realm_data(context, principal, tmpdata);
-    for (i=0; i < components; i++) {
-        char *tmpdata2 =
-            malloc(krb5_princ_component(context, principal, i)->length + 1);
-        if (tmpdata2 == NULL) {
-            for (i--; i >= 0; i--)
-                free(krb5_princ_component(context, principal, i)->data);
-            free(krb5_princ_realm(context, principal)->data);
-            free(principal->data);
-            free(principal);
-            free(default_realm);
-            return(ENOMEM);
-        }
-        krb5_princ_component(context, principal, i)->data = tmpdata2;
-        krb5_princ_component(context, principal, i)->magic = KV5M_DATA;
+        goto cleanup;
     }
 
-    /*
-     * Pass 3.  Now we go through the string a *third* time, this
-     * time filling in the krb5_principal structure which we just
-     * allocated.
-     */
-    q = krb5_princ_component(context, principal, 0)->data;
-    for (i=0,cp = name, first_at = 1; (c = *cp); cp++) {
-        if (c == QUOTECHAR) {
-            cp++;
-            switch (c = *cp) {
-            case 'n':
-                *q++ = '\n';
-                break;
-            case 't':
-                *q++ = '\t';
-                break;
-            case 'b':
-                *q++ = '\b';
-                break;
-            case '0':
-                *q++ = '\0';
-                break;
-            default:
-                *q++ = c;
-                break;
-            }
-        } else if (c == COMPONENT_SEP && !enterprise) {
-            i++;
-            *q++ = '\0';
-            q = krb5_princ_component(context, principal, i)->data;
-        } else if (c == REALM_SEP && (!enterprise || !first_at)) {
-            i++;
-            *q++ = '\0';
-            q = krb5_princ_realm(context, principal)->data;
-        } else {
-            if (c == REALM_SEP && enterprise && first_at)
-                first_at = 0;
+    princ->type = (enterprise) ? KRB5_NT_ENTERPRISE_PRINCIPAL :
+        KRB5_NT_PRINCIPAL;
+    princ->magic = KV5M_PRINCIPAL;
+    *principal_out = princ;
+    princ = NULL;
 
-            *q++ = c;
-        }
-    }
-    *q++ = '\0';
-    if (!parsed_realm) {
-        if (flags & KRB5_PRINCIPAL_PARSE_NO_REALM)
-            (krb5_princ_realm(context, principal)->data)[0] = '\0';
-        else
-            strlcpy(krb5_princ_realm(context, principal)->data, default_realm, realmsize+1);
-    }
-    /*
-     * Alright, we're done.  Now stuff a pointer to this monstrosity
-     * into the return variable, and let's get out of here.
-     */
-    if (enterprise)
-        krb5_princ_type(context, principal) = KRB5_NT_ENTERPRISE_PRINCIPAL;
-    else
-        krb5_princ_type(context, principal) = KRB5_NT_PRINCIPAL;
-    principal->magic = KV5M_PRINCIPAL;
-    principal->realm.magic = KV5M_DATA;
-    *nprincipal = principal;
-
-    if (default_realm != NULL)
-        free(default_realm);
-
-    return(0);
-}
-
-krb5_error_code KRB5_CALLCONV
-krb5_parse_name(krb5_context context, const char *name, krb5_principal *nprincipal)
-{
-    return k5_parse_name(context, name, 0, nprincipal);
+cleanup:
+    krb5_free_principal(context, princ);
+    return ret;
 }
 
 krb5_error_code KRB5_CALLCONV
-krb5_parse_name_flags(krb5_context context, const char *name,
-                      int flags, krb5_principal *nprincipal)
+krb5_parse_name(krb5_context context, const char *name,
+                krb5_principal *principal_out)
 {
-    return k5_parse_name(context, name, flags, nprincipal);
+    return krb5_parse_name_flags(context, name, 0, principal_out);
 }