diff options
Diffstat (limited to 'src/lib/krb5/krb/authdata.c')
| -rw-r--r-- | src/lib/krb5/krb/authdata.c | 1311 | 
1 files changed, 1311 insertions, 0 deletions
| diff --git a/src/lib/krb5/krb/authdata.c b/src/lib/krb5/krb/authdata.c new file mode 100644 index 000000000000..abb2ab9e255e --- /dev/null +++ b/src/lib/krb5/krb/authdata.c @@ -0,0 +1,1311 @@ +/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* + * Copyright 2009 by the Massachusetts Institute of Technology.  All + * Rights Reserved. + * + * Export of this software from the United States of America may + *   require a specific license from the United States Government. + *   It is the responsibility of any person or organization contemplating + *   export to obtain such a license before exporting. + * + * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and + * distribute this software and its documentation for any purpose and + * without fee is hereby granted, provided that the above copyright + * notice appear in all copies and that both that copyright notice and + * this permission notice appear in supporting documentation, and that + * the name of M.I.T. not be used in advertising or publicity pertaining + * to distribution of the software without specific, written prior + * permission.  Furthermore if you modify this software you must label + * your software as modified software and not distribute it in such a + * fashion that it might be confused with the original M.I.T. software. + * M.I.T. makes no representations about the suitability of + * this software for any purpose.  It is provided "as is" without express + * or implied warranty. + * + */ + +#include "k5-int.h" +#include "authdata.h" +#include "auth_con.h" +#include "int-proto.h" + +/* Loosely based on preauth2.c */ + +#define IS_PRIMARY_INSTANCE(_module) ((_module)->client_req_init != NULL) + +static const char *objdirs[] = { +#if TARGET_OS_MAC +    KRB5_AUTHDATA_PLUGIN_BUNDLE_DIR, +#endif +    LIBDIR "/krb5/plugins/authdata", +    NULL +}; /* should be a list */ + +/* Internal authdata systems */ +static krb5plugin_authdata_client_ftable_v0 *authdata_systems[] = { +    &k5_mspac_ad_client_ftable, +    &k5_s4u2proxy_ad_client_ftable, +    &k5_authind_ad_client_ftable, +    NULL +}; + +static inline int +k5_ad_module_count(krb5plugin_authdata_client_ftable_v0 *table) +{ +    int i; + +    if (table->ad_type_list == NULL) +        return 0; + +    for (i = 0; table->ad_type_list[i]; i++) +        ; + +    return i; +} + +static krb5_error_code +k5_ad_init_modules(krb5_context kcontext, +                   krb5_authdata_context context, +                   krb5plugin_authdata_client_ftable_v0 *table, +                   int *module_count) +{ +    int j, k = *module_count; +    krb5_error_code code; +    void *plugin_context = NULL; +    void **rcpp = NULL; + +    if (table->ad_type_list == NULL) { +#ifdef DEBUG +        fprintf(stderr, "warning: module \"%s\" does not advertise " +                "any AD types\n", table->name); +#endif +        return ENOENT; +    } + +    if (table->init == NULL) +        return ENOSYS; + +    code = (*table->init)(kcontext, &plugin_context); +    if (code != 0) { +#ifdef DEBUG +        fprintf(stderr, "warning: skipping module \"%s\" which " +                "failed to initialize\n", table->name); +#endif +        return code; +    } + +    for (j = 0; table->ad_type_list[j] != 0; j++) { +        context->modules[k].ad_type = table->ad_type_list[j]; +        context->modules[k].plugin_context = plugin_context; +        if (j == 0) +            context->modules[k].client_fini = table->fini; +        else +            context->modules[k].client_fini = NULL; +        context->modules[k].ftable = table; +        context->modules[k].name = table->name; +        if (table->flags != NULL) { +            (*table->flags)(kcontext, plugin_context, +                            context->modules[k].ad_type, +                            &context->modules[k].flags); +        } else { +            context->modules[k].flags = 0; +        } +        context->modules[k].request_context = NULL; +        if (j == 0) { +            context->modules[k].client_req_init = table->request_init; +            context->modules[k].client_req_fini = table->request_fini; +            rcpp = &context->modules[k].request_context; + +            /* For now, single request per context. That may change */ +            code = (*table->request_init)(kcontext, +                                          context, +                                          plugin_context, +                                          rcpp); +            if ((code != 0 && code != ENOMEM) && +                (context->modules[k].flags & AD_INFORMATIONAL)) +                code = 0; +            if (code != 0) +                break; +        } else { +            context->modules[k].client_req_init = NULL; +            context->modules[k].client_req_fini = NULL; +        } +        context->modules[k].request_context_pp = rcpp; + +#ifdef DEBUG +        fprintf(stderr, "init module \"%s\", ad_type %d, flags %08x\n", +                context->modules[k].name, +                context->modules[k].ad_type, +                context->modules[k].flags); +#endif +        k++; +    } +    *module_count = k; + +    return code; +} + +/* + * Determine size of to-be-externalized authdata context, for + * modules that match given flags mask. Note that this size + * does not include the magic identifier/trailer. + */ +static krb5_error_code +k5_ad_size(krb5_context kcontext, +           krb5_authdata_context context, +           krb5_flags flags, +           size_t *sizep) +{ +    int i; +    krb5_error_code code = 0; + +    *sizep += sizeof(krb5_int32); /* count */ + +    for (i = 0; i < context->n_modules; i++) { +        struct _krb5_authdata_context_module *module = &context->modules[i]; +        size_t size; + +        if ((module->flags & flags) == 0) +            continue; + +        /* externalize request context for the first instance only */ +        if (!IS_PRIMARY_INSTANCE(module)) +            continue; + +        if (module->ftable->size == NULL) +            continue; + +        assert(module->ftable->externalize != NULL); + +        size = sizeof(krb5_int32) /* namelen */ + strlen(module->name); + +        code = (*module->ftable->size)(kcontext, +                                       context, +                                       module->plugin_context, +                                       *(module->request_context_pp), +                                       &size); +        if (code != 0) +            break; + +        *sizep += size; +    } + +    return code; +} + +/* + * Externalize authdata context, for modules that match given flags + * mask. Note that the magic identifier/trailer is not included. + */ +static krb5_error_code +k5_ad_externalize(krb5_context kcontext, +                  krb5_authdata_context context, +                  krb5_flags flags, +                  krb5_octet **buffer, +                  size_t *lenremain) +{ +    int i; +    krb5_error_code code; +    krb5_int32 ad_count = 0; +    krb5_octet *bp; +    size_t remain; + +    bp = *buffer; +    remain = *lenremain; + +    /* placeholder for count */ +    code = krb5_ser_pack_int32(0, &bp, &remain); +    if (code != 0) +        return code; + +    for (i = 0; i < context->n_modules; i++) { +        struct _krb5_authdata_context_module *module = &context->modules[i]; +        size_t namelen; + +        if ((module->flags & flags) == 0) +            continue; + +        /* externalize request context for the first instance only */ +        if (!IS_PRIMARY_INSTANCE(module)) +            continue; + +        if (module->ftable->externalize == NULL) +            continue; + +        /* +         * We use the module name rather than the authdata type, because +         * there may be multiple modules for a particular authdata type. +         */ +        namelen = strlen(module->name); + +        code = krb5_ser_pack_int32((krb5_int32)namelen, &bp, &remain); +        if (code != 0) +            break; + +        code = krb5_ser_pack_bytes((krb5_octet *)module->name, +                                   namelen, &bp, &remain); +        if (code != 0) +            break; + +        code = (*module->ftable->externalize)(kcontext, +                                              context, +                                              module->plugin_context, +                                              *(module->request_context_pp), +                                              &bp, +                                              &remain); +        if (code != 0) +            break; + +        ad_count++; +    } + +    if (code == 0) { +        /* store actual count */ +        krb5_ser_pack_int32(ad_count, buffer, lenremain); + +        *buffer = bp; +        *lenremain = remain; +    } + +    return code; +} + +/* + * Find authdata module for authdata type that matches flag mask + */ +static struct _krb5_authdata_context_module * +k5_ad_find_module(krb5_context kcontext, +                  krb5_authdata_context context, +                  krb5_flags flags, +                  const krb5_data *name) +{ +    int i; +    struct _krb5_authdata_context_module *ret = NULL; + +    for (i = 0; i < context->n_modules; i++) { +        struct _krb5_authdata_context_module *module = &context->modules[i]; + +        if ((module->flags & flags) == 0) +            continue; + +        /* internalize request context for the first instance only */ +        if (!IS_PRIMARY_INSTANCE(module)) +            continue; + +        /* check for name match */ +        if (!data_eq_string(*name, module->name)) +            continue; + +        ret = module; +        break; +    } + +    return ret; +} + +/* + * In-place internalize authdata context, for modules that match given + * flags mask. The magic identifier/trailer is not expected by this. + */ +static krb5_error_code +k5_ad_internalize(krb5_context kcontext, +                  krb5_authdata_context context, +                  krb5_flags flags, +                  krb5_octet **buffer, +                  size_t *lenremain) +{ +    krb5_error_code code = 0; +    krb5_int32 i, count; +    krb5_octet *bp; +    size_t remain; + +    bp = *buffer; +    remain = *lenremain; + +    code = krb5_ser_unpack_int32(&count, &bp, &remain); +    if (code != 0) +        return code; + +    for (i = 0; i < count; i++) { +        struct _krb5_authdata_context_module *module; +        krb5_int32 namelen; +        krb5_data name; + +        code = krb5_ser_unpack_int32(&namelen, &bp, &remain); +        if (code != 0) +            break; + +        if (remain < (size_t)namelen) { +            code = ENOMEM; +            break; +        } + +        name.length = namelen; +        name.data = (char *)bp; + +        module = k5_ad_find_module(kcontext, context, flags, &name); +        if (module == NULL || module->ftable->internalize == NULL) { +            code = EINVAL; +            break; +        } + +        bp += namelen; +        remain -= namelen; + +        code = (*module->ftable->internalize)(kcontext, +                                              context, +                                              module->plugin_context, +                                              *(module->request_context_pp), +                                              &bp, +                                              &remain); +        if (code != 0) +            break; +    } + +    if (code == 0) { +        *buffer = bp; +        *lenremain = remain; +    } + +    return code; +} + +krb5_error_code KRB5_CALLCONV +krb5_authdata_context_init(krb5_context kcontext, +                           krb5_authdata_context *pcontext) +{ +    int n_modules, n_tables, i, k; +    void **tables = NULL; +    krb5plugin_authdata_client_ftable_v0 *table; +    krb5_authdata_context context = NULL; +    int internal_count = 0; +    struct plugin_dir_handle plugins; +    krb5_error_code code; + +    *pcontext = NULL; +    memset(&plugins, 0, sizeof(plugins)); + +    n_modules = 0; +    for (n_tables = 0; authdata_systems[n_tables] != NULL; n_tables++) { +        n_modules += k5_ad_module_count(authdata_systems[n_tables]); +    } +    internal_count = n_tables; + +    if (PLUGIN_DIR_OPEN(&plugins) == 0 && +        krb5int_open_plugin_dirs(objdirs, NULL, +                                 &plugins, +                                 &kcontext->err) == 0 && +        krb5int_get_plugin_dir_data(&plugins, +                                    "authdata_client_0", +                                    &tables, +                                    &kcontext->err) == 0 && +        tables != NULL) +    { +        for (; tables[n_tables - internal_count] != NULL; n_tables++) { +            table = tables[n_tables - internal_count]; +            n_modules += k5_ad_module_count(table); +        } +    } + +    context = calloc(1, sizeof(*context)); +    if (context == NULL) { +        code = ENOMEM; +        goto cleanup; +    } +    context->magic = KV5M_AUTHDATA_CONTEXT; +    context->modules = calloc(n_modules, sizeof(context->modules[0])); +    if (context->modules == NULL) { +        code = ENOMEM; +        goto cleanup; +    } +    context->n_modules = n_modules; + +    /* fill in the structure */ +    for (i = 0, k = 0, code = 0; i < n_tables - internal_count; i++) { +        code = k5_ad_init_modules(kcontext, context, tables[i], &k); +        if (code != 0) +            goto cleanup; +    } + +    for (i = 0; i < internal_count; i++) { +        code = k5_ad_init_modules(kcontext, context, authdata_systems[i], &k); +        if (code != 0) +            goto cleanup; +    } + +    context->plugins = plugins; + +cleanup: +    if (tables != NULL) +        krb5int_free_plugin_dir_data(tables); + +    if (code != 0) { +        krb5int_close_plugin_dirs(&plugins); +        krb5_authdata_context_free(kcontext, context); +    } else { +        /* plugins is owned by context now */ +        *pcontext = context; +    } + +    return code; +} + +void KRB5_CALLCONV +krb5_authdata_context_free(krb5_context kcontext, +                           krb5_authdata_context context) +{ +    int i; + +    if (context == NULL) +        return; + +    for (i = 0; i < context->n_modules; i++) { +        struct _krb5_authdata_context_module *module = &context->modules[i]; + +        if (module->client_req_fini != NULL && +            module->request_context != NULL) +            (*module->client_req_fini)(kcontext, +                                       context, +                                       module->plugin_context, +                                       module->request_context); + +        if (module->client_fini != NULL) +            (*module->client_fini)(kcontext, module->plugin_context); + +        memset(module, 0, sizeof(*module)); +    } + +    if (context->modules != NULL) { +        free(context->modules); +        context->modules = NULL; +    } +    krb5int_close_plugin_dirs(&context->plugins); +    zapfree(context, sizeof(*context)); +} + +krb5_error_code KRB5_CALLCONV +krb5_authdata_import_attributes(krb5_context kcontext, +                                krb5_authdata_context context, +                                krb5_flags usage, +                                const krb5_data *attrs) +{ +    krb5_octet *bp; +    size_t remain; + +    bp = (krb5_octet *)attrs->data; +    remain = attrs->length; + +    return k5_ad_internalize(kcontext, context, usage, &bp, &remain); +} + +/* Return 0 with *kdc_issued_authdata == NULL on verification failure. */ +static krb5_error_code +k5_get_kdc_issued_authdata(krb5_context kcontext, +                           const krb5_ap_req *ap_req, +                           krb5_principal *kdc_issuer, +                           krb5_authdata ***kdc_issued_authdata) +{ +    krb5_error_code code; +    krb5_authdata **authdata; +    krb5_authdata **ticket_authdata; + +    *kdc_issuer = NULL; +    *kdc_issued_authdata = NULL; + +    ticket_authdata = ap_req->ticket->enc_part2->authorization_data; + +    code = krb5_find_authdata(kcontext, ticket_authdata, NULL, +                              KRB5_AUTHDATA_KDC_ISSUED, &authdata); +    if (code != 0 || authdata == NULL) +        return code; + +    /* +     * Note: a module must still implement a verify_authdata +     * method, even it is a NOOP that simply records the value +     * of the kdc_issued_flag. +     */ +    code = krb5_verify_authdata_kdc_issued(kcontext, +                                           ap_req->ticket->enc_part2->session, +                                           authdata[0], +                                           kdc_issuer, +                                           kdc_issued_authdata); + +    if (code == KRB5KRB_AP_ERR_BAD_INTEGRITY || +        code == KRB5KRB_AP_ERR_INAPP_CKSUM || +        code == KRB5_BAD_ENCTYPE || code == KRB5_BAD_MSIZE) +        code = 0; + +    krb5_free_authdata(kcontext, authdata); + +    return code; +} + +/* Decode and verify each CAMMAC and collect the resulting authdata, + * ignoring those that failed verification. */ +static krb5_error_code +extract_cammacs(krb5_context kcontext, krb5_authdata **cammacs, +                const krb5_keyblock *key, krb5_authdata ***ad_out) +{ +    krb5_error_code ret; +    krb5_authdata **list = NULL, **elements = NULL, **new_list; +    size_t i, n_elements, count = 0; + +    *ad_out = NULL; + +    for (i = 0; cammacs != NULL && cammacs[i] != NULL; i++) { +        ret = k5_unwrap_cammac_svc(kcontext, cammacs[i], key, &elements); +        if (ret && ret != KRB5KRB_AP_ERR_BAD_INTEGRITY) +            goto cleanup; +        ret = 0; + +        /* Add the verified elements to list and free the container array. */ +        for (n_elements = 0; elements[n_elements] != NULL; n_elements++); +        new_list = realloc(list, (count + n_elements + 1) * sizeof(*list)); +        if (new_list == NULL) { +            ret = ENOMEM; +            goto cleanup; +        } +        list = new_list; +        memcpy(list + count, elements, n_elements * sizeof(*list)); +        count += n_elements; +        list[count] = NULL; +        free(elements); +        elements = NULL; +    } + +    *ad_out = list; +    list = NULL; + +cleanup: +    krb5_free_authdata(kcontext, list); +    krb5_free_authdata(kcontext, elements); +    return ret; +} + +/* Retrieve verified CAMMAC contained elements. */ +static krb5_error_code +get_cammac_authdata(krb5_context kcontext, const krb5_ap_req *ap_req, +                    const krb5_keyblock *key, krb5_authdata ***elems_out) +{ +    krb5_error_code ret = 0; +    krb5_authdata **ticket_authdata, **cammacs, **elements; + +    *elems_out = NULL; + +    ticket_authdata = ap_req->ticket->enc_part2->authorization_data; +    ret = krb5_find_authdata(kcontext, ticket_authdata, NULL, +                             KRB5_AUTHDATA_CAMMAC, &cammacs); +    if (ret || cammacs == NULL) +        return ret; + +    ret = extract_cammacs(kcontext, cammacs, key, &elements); +    if (!ret) +        *elems_out = elements; + +    krb5_free_authdata(kcontext, cammacs); +    return ret; +} + +krb5_error_code +krb5int_authdata_verify(krb5_context kcontext, +                        krb5_authdata_context context, +                        krb5_flags usage, +                        const krb5_auth_context *auth_context, +                        const krb5_keyblock *key, +                        const krb5_ap_req *ap_req) +{ +    int i; +    krb5_error_code code = 0; +    krb5_authdata **authen_authdata; +    krb5_authdata **ticket_authdata; +    krb5_principal kdc_issuer = NULL; +    krb5_authdata **kdc_issued_authdata = NULL; +    krb5_authdata **cammac_authdata = NULL; + +    authen_authdata = (*auth_context)->authentp->authorization_data; +    ticket_authdata = ap_req->ticket->enc_part2->authorization_data; + +    code = k5_get_kdc_issued_authdata(kcontext, ap_req, &kdc_issuer, +                                      &kdc_issued_authdata); +    if (code) +        goto cleanup; + +    code = get_cammac_authdata(kcontext, ap_req, key, &cammac_authdata); +    if (code) +        goto cleanup; + +    for (i = 0; i < context->n_modules; i++) { +        struct _krb5_authdata_context_module *module = &context->modules[i]; +        krb5_authdata **authdata = NULL; +        krb5_boolean kdc_issued_flag = FALSE; + +        if ((module->flags & usage) == 0) +            continue; + +        if (module->ftable->import_authdata == NULL) +            continue; + +        if (kdc_issued_authdata != NULL && +            (module->flags & AD_USAGE_KDC_ISSUED)) { +            code = krb5_find_authdata(kcontext, kdc_issued_authdata, NULL, +                                      module->ad_type, &authdata); +            if (code != 0) +                break; + +            kdc_issued_flag = TRUE; +        } + +        if (cammac_authdata != NULL && (module->flags & AD_CAMMAC_PROTECTED)) { +            code = krb5_find_authdata(kcontext, cammac_authdata, NULL, +                                      module->ad_type, &authdata); +            if (code) +                break; + +            kdc_issued_flag = TRUE; +        } + +        if (authdata == NULL) { +            krb5_boolean ticket_usage = FALSE; +            krb5_boolean authen_usage = FALSE; + +            /* +             * Determine which authdata sources to interrogate based on the +             * module's usage. This is important if the authdata is signed +             * by the KDC with the TGT key (as the user can forge that in +             * the AP-REQ). +             */ +            if (module->flags & (AD_USAGE_AS_REQ | AD_USAGE_TGS_REQ)) +                ticket_usage = TRUE; +            if (module->flags & AD_USAGE_AP_REQ) +                authen_usage = TRUE; + +            code = krb5_find_authdata(kcontext, +                                      ticket_usage ? ticket_authdata : NULL, +                                      authen_usage ? authen_authdata : NULL, +                                      module->ad_type, &authdata); +            if (code != 0) +                break; +        } + +        if (authdata == NULL) +            continue; + +        assert(authdata[0] != NULL); + +        code = (*module->ftable->import_authdata)(kcontext, +                                                  context, +                                                  module->plugin_context, +                                                  *(module->request_context_pp), +                                                  authdata, +                                                  kdc_issued_flag, +                                                  kdc_issuer); +        if (code == 0 && module->ftable->verify != NULL) { +            code = (*module->ftable->verify)(kcontext, +                                             context, +                                             module->plugin_context, +                                             *(module->request_context_pp), +                                             auth_context, +                                             key, +                                             ap_req); +        } +        if (code != 0 && (module->flags & AD_INFORMATIONAL)) +            code = 0; +        krb5_free_authdata(kcontext, authdata); +        if (code != 0) +            break; +    } + +cleanup: +    krb5_free_principal(kcontext, kdc_issuer); +    krb5_free_authdata(kcontext, kdc_issued_authdata); +    krb5_free_authdata(kcontext, cammac_authdata); + +    return code; +} + +static krb5_error_code +k5_merge_data_list(krb5_data **dst, krb5_data *src, unsigned int *len) +{ +    unsigned int i; +    krb5_data *d; + +    if (src == NULL) +        return 0; + +    for (i = 0; src[i].data != NULL; i++) +        ; + +    d = realloc(*dst, (*len + i + 1) * sizeof(krb5_data)); +    if (d == NULL) +        return ENOMEM; + +    memcpy(&d[*len], src, i * sizeof(krb5_data)); + +    *len += i; + +    d[*len].data = NULL; +    d[*len].length = 0; + +    *dst = d; + +    return 0; +} + +krb5_error_code KRB5_CALLCONV +krb5_authdata_get_attribute_types(krb5_context kcontext, +                                  krb5_authdata_context context, +                                  krb5_data **out_attrs) +{ +    int i; +    krb5_error_code code = 0; +    krb5_data *attrs = NULL; +    unsigned int attrs_len = 0; + +    for (i = 0; i < context->n_modules; i++) { +        struct _krb5_authdata_context_module *module = &context->modules[i]; +        krb5_data *attrs2 = NULL; + +        if (module->ftable->get_attribute_types == NULL) +            continue; + +        if ((*module->ftable->get_attribute_types)(kcontext, +                                                   context, +                                                   module->plugin_context, +                                                   *(module->request_context_pp), +                                                   &attrs2)) +            continue; + +        code = k5_merge_data_list(&attrs, attrs2, &attrs_len); +        if (code != 0) { +            krb5int_free_data_list(kcontext, attrs2); +            break; +        } +        if (attrs2 != NULL) +            free(attrs2); +    } + +    if (code != 0) { +        krb5int_free_data_list(kcontext, attrs); +        attrs = NULL; +    } + +    *out_attrs = attrs; + +    return code; +} + +krb5_error_code KRB5_CALLCONV +krb5_authdata_get_attribute(krb5_context kcontext, +                            krb5_authdata_context context, +                            const krb5_data *attribute, +                            krb5_boolean *authenticated, +                            krb5_boolean *complete, +                            krb5_data *value, +                            krb5_data *display_value, +                            int *more) +{ +    int i; +    krb5_error_code code = ENOENT; + +    *authenticated = FALSE; +    *complete = FALSE; + +    value->data = NULL; +    value->length = 0; + +    display_value->data = NULL; +    display_value->length = 0; + +    /* +     * NB at present a module is presumed to be authoritative for +     * an attribute; not sure how to federate "more" across module +     * yet +     */ +    for (i = 0; i < context->n_modules; i++) { +        struct _krb5_authdata_context_module *module = &context->modules[i]; + +        if (module->ftable->get_attribute == NULL) +            continue; + +        code = (*module->ftable->get_attribute)(kcontext, +                                                context, +                                                module->plugin_context, +                                                *(module->request_context_pp), +                                                attribute, +                                                authenticated, +                                                complete, +                                                value, +                                                display_value, +                                                more); +        if (code == 0) +            break; +    } + +    if (code != 0) +        *more = 0; + +    return code; +} + +krb5_error_code KRB5_CALLCONV +krb5_authdata_set_attribute(krb5_context kcontext, +                            krb5_authdata_context context, +                            krb5_boolean complete, +                            const krb5_data *attribute, +                            const krb5_data *value) +{ +    int i; +    krb5_error_code code = 0; +    int found = 0; + +    for (i = 0; i < context->n_modules; i++) { +        struct _krb5_authdata_context_module *module = &context->modules[i]; + +        if (module->ftable->set_attribute == NULL) +            continue; + +        code = (*module->ftable->set_attribute)(kcontext, +                                                context, +                                                module->plugin_context, +                                                *(module->request_context_pp), +                                                complete, +                                                attribute, +                                                value); +        if (code == ENOENT) +            code = 0; +        else if (code == 0) +            found++; +        else +            break; +    } + +    if (code == 0 && found == 0) +        code = ENOENT; + +    return code; +} + +krb5_error_code KRB5_CALLCONV +krb5_authdata_delete_attribute(krb5_context kcontext, +                               krb5_authdata_context context, +                               const krb5_data *attribute) +{ +    int i; +    krb5_error_code code = ENOENT; +    int found = 0; + +    for (i = 0; i < context->n_modules; i++) { +        struct _krb5_authdata_context_module *module = &context->modules[i]; + +        if (module->ftable->delete_attribute == NULL) +            continue; + +        code = (*module->ftable->delete_attribute)(kcontext, +                                                   context, +                                                   module->plugin_context, +                                                   *(module->request_context_pp), +                                                   attribute); +        if (code == ENOENT) +            code = 0; +        else if (code == 0) +            found++; +        else +            break; +    } + +    if (code == 0 && found == 0) +        code = ENOENT; + +    return code; +} + +krb5_error_code KRB5_CALLCONV +krb5_authdata_export_attributes(krb5_context kcontext, +                                krb5_authdata_context context, +                                krb5_flags flags, +                                krb5_data **attrsp) +{ +    krb5_error_code code; +    size_t required = 0; +    krb5_octet *bp; +    size_t remain; +    krb5_data *attrs; + +    code = k5_ad_size(kcontext, context, AD_USAGE_MASK, &required); +    if (code != 0) +        return code; + +    attrs = malloc(sizeof(*attrs)); +    if (attrs == NULL) +        return ENOMEM; + +    attrs->magic = KV5M_DATA; +    attrs->length = 0; +    attrs->data = malloc(required); +    if (attrs->data == NULL) { +        free(attrs); +        return ENOMEM; +    } + +    bp = (krb5_octet *)attrs->data; +    remain = required; + +    code = k5_ad_externalize(kcontext, context, AD_USAGE_MASK, &bp, &remain); +    if (code != 0) { +        krb5_free_data(kcontext, attrs); +        return code; +    } + +    attrs->length = (bp - (krb5_octet *)attrs->data); + +    *attrsp = attrs; + +    return 0; +} + +krb5_error_code KRB5_CALLCONV +krb5_authdata_export_internal(krb5_context kcontext, +                              krb5_authdata_context context, +                              krb5_boolean restrict_authenticated, +                              const char *module_name, +                              void **ptr) +{ +    krb5_error_code code; +    krb5_data name; +    struct _krb5_authdata_context_module *module; + +    *ptr = NULL; + +    name.length = strlen(module_name); +    name.data = (char *)module_name; + +    module = k5_ad_find_module(kcontext, context, AD_USAGE_MASK, &name); +    if (module == NULL) +        return ENOENT; + +    if (module->ftable->export_internal == NULL) +        return ENOENT; + +    code = (*module->ftable->export_internal)(kcontext, +                                              context, +                                              module->plugin_context, +                                              *(module->request_context_pp), +                                              restrict_authenticated, +                                              ptr); + +    return code; +} + +krb5_error_code KRB5_CALLCONV +krb5_authdata_free_internal(krb5_context kcontext, +                            krb5_authdata_context context, +                            const char *module_name, +                            void *ptr) +{ +    krb5_data name; +    struct _krb5_authdata_context_module *module; + +    name.length = strlen(module_name); +    name.data = (char *)module_name; + +    module = k5_ad_find_module(kcontext, context, AD_USAGE_MASK, &name); +    if (module == NULL) +        return ENOENT; + +    if (module->ftable->free_internal == NULL) +        return ENOENT; + +    (*module->ftable->free_internal)(kcontext, +                                     context, +                                     module->plugin_context, +                                     *(module->request_context_pp), +                                     ptr); + +    return 0; +} + +static krb5_error_code +k5_copy_ad_module_data(krb5_context kcontext, +                       krb5_authdata_context context, +                       struct _krb5_authdata_context_module *src_module, +                       krb5_authdata_context dst) +{ +    int i; +    krb5_error_code code; +    struct _krb5_authdata_context_module *dst_module = NULL; + +    for (i = 0; i < dst->n_modules; i++) { +        struct _krb5_authdata_context_module *module = &dst->modules[i]; + +        if (module->ftable == src_module->ftable) { +            /* XXX is this safe to assume these pointers are interned? */ +            dst_module = module; +            break; +        } +    } + +    if (dst_module == NULL) +        return ENOENT; + +    /* copy request context for the first instance only */ +    if (!IS_PRIMARY_INSTANCE(dst_module)) +        return 0; + +    assert(strcmp(dst_module->name, src_module->name) == 0); + +    /* If copy is unimplemented, externalize/internalize */ +    if (src_module->ftable->copy == NULL) { +        size_t size = 0, remain; +        krb5_octet *contents, *bp; + +        assert(src_module->ftable->size != NULL); +        assert(src_module->ftable->externalize != NULL); +        assert(dst_module->ftable->internalize != NULL); + +        code = (*src_module->ftable->size)(kcontext, +                                           context, +                                           src_module->plugin_context, +                                           src_module->request_context, +                                           &size); +        if (code != 0) +            return code; + +        contents = malloc(size); +        if (contents == NULL) +            return ENOMEM; + +        bp = contents; +        remain = size; + +        code = (*src_module->ftable->externalize)(kcontext, +                                                  context, +                                                  src_module->plugin_context, +                                                  *(src_module->request_context_pp), +                                                  &bp, +                                                  &remain); +        if (code != 0) { +            free(contents); +            return code; +        } + +        remain = (bp - contents); +        bp = contents; + +        code = (*dst_module->ftable->internalize)(kcontext, +                                                  context, +                                                  dst_module->plugin_context, +                                                  *(dst_module->request_context_pp), +                                                  &bp, +                                                  &remain); +        if (code != 0) { +            free(contents); +            return code; +        } + +        free(contents); +    } else { +        assert(src_module->request_context_pp == &src_module->request_context); +        assert(dst_module->request_context_pp == &dst_module->request_context); + +        code = (*src_module->ftable->copy)(kcontext, +                                           context, +                                           src_module->plugin_context, +                                           src_module->request_context, +                                           dst_module->plugin_context, +                                           dst_module->request_context); +    } + +    return code; +} + +krb5_error_code KRB5_CALLCONV +krb5_authdata_context_copy(krb5_context kcontext, +                           krb5_authdata_context src, +                           krb5_authdata_context *pdst) +{ +    int i; +    krb5_error_code code; +    krb5_authdata_context dst; + +    /* XXX we need to init a new context because we can't copy plugins */ +    code = krb5_authdata_context_init(kcontext, &dst); +    if (code != 0) +        return code; + +    for (i = 0; i < src->n_modules; i++) { +        struct _krb5_authdata_context_module *module = &src->modules[i]; + +        code = k5_copy_ad_module_data(kcontext, src, module, dst); +        if (code != 0) +            break; +    } + +    if (code != 0) { +        krb5_authdata_context_free(kcontext, dst); +        return code; +    } + +    *pdst = dst; + +    return 0; +} + +/* + * Calculate size of to-be-externalized authdata context. + */ +static krb5_error_code +krb5_authdata_context_size(krb5_context kcontext, +                           krb5_pointer ptr, +                           size_t *sizep) +{ +    krb5_error_code code; +    krb5_authdata_context context = (krb5_authdata_context)ptr; + +    code = k5_ad_size(kcontext, context, AD_USAGE_MASK, sizep); +    if (code != 0) +        return code; + +    *sizep += 2 * sizeof(krb5_int32); /* identifier/trailer */ + +    return 0; +} + +/* + * Externalize an authdata context. + */ +static krb5_error_code +krb5_authdata_context_externalize(krb5_context kcontext, +                                  krb5_pointer ptr, +                                  krb5_octet **buffer, +                                  size_t *lenremain) +{ +    krb5_error_code code; +    krb5_authdata_context context = (krb5_authdata_context)ptr; +    krb5_octet *bp; +    size_t remain; + +    bp = *buffer; +    remain = *lenremain; + +    /* Our identifier */ +    code = krb5_ser_pack_int32(KV5M_AUTHDATA_CONTEXT, &bp, &remain); +    if (code != 0) +        return code; + +    /* The actual context data */ +    code = k5_ad_externalize(kcontext, context, AD_USAGE_MASK, +                             &bp, &remain); +    if (code != 0) +        return code; + +    /* Our trailer */ +    code = krb5_ser_pack_int32(KV5M_AUTHDATA_CONTEXT, &bp, &remain); +    if (code != 0) +        return code; + +    *buffer = bp; +    *lenremain = remain; + +    return 0; +} + +/* + * Internalize an authdata context. + */ +static krb5_error_code +krb5_authdata_context_internalize(krb5_context kcontext, +                                  krb5_pointer *ptr, +                                  krb5_octet **buffer, +                                  size_t *lenremain) +{ +    krb5_error_code code; +    krb5_authdata_context context; +    krb5_int32 ibuf; +    krb5_octet *bp; +    size_t remain; + +    bp = *buffer; +    remain = *lenremain; + +    code = krb5_ser_unpack_int32(&ibuf, &bp, &remain); +    if (code != 0) +        return code; + +    if (ibuf != KV5M_AUTHDATA_CONTEXT) +        return EINVAL; + +    code = krb5_authdata_context_init(kcontext, &context); +    if (code != 0) +        return code; + +    code = k5_ad_internalize(kcontext, context, AD_USAGE_MASK, +                             &bp, &remain); +    if (code != 0) { +        krb5_authdata_context_free(kcontext, context); +        return code; +    } + +    code = krb5_ser_unpack_int32(&ibuf, &bp, &remain); +    if (code != 0) +        return code; + +    if (ibuf != KV5M_AUTHDATA_CONTEXT) { +        krb5_authdata_context_free(kcontext, context); +        return EINVAL; +    } + +    *buffer = bp; +    *lenremain = remain; +    *ptr = context; + +    return 0; +} + +static const krb5_ser_entry krb5_authdata_context_ser_entry = { +    KV5M_AUTHDATA_CONTEXT, +    krb5_authdata_context_size, +    krb5_authdata_context_externalize, +    krb5_authdata_context_internalize +}; + +/* + * Register the authdata context serializer. + */ +krb5_error_code +krb5_ser_authdata_context_init(krb5_context kcontext) +{ +    return krb5_register_serializer(kcontext, +                                    &krb5_authdata_context_ser_entry); +} + +krb5_error_code +krb5int_copy_authdatum(krb5_context context, +                       const krb5_authdata *inad, krb5_authdata **outad) +{ +    krb5_authdata *tmpad; + +    if (!(tmpad = (krb5_authdata *)malloc(sizeof(*tmpad)))) +        return ENOMEM; +    *tmpad = *inad; +    if (!(tmpad->contents = (krb5_octet *)malloc(inad->length))) { +        free(tmpad); +        return ENOMEM; +    } +    memcpy(tmpad->contents, inad->contents, inad->length); +    *outad = tmpad; +    return 0; +} + +void KRB5_CALLCONV +krb5_free_authdata(krb5_context context, krb5_authdata **val) +{ +    register krb5_authdata **temp; + +    if (val == NULL) +        return; +    for (temp = val; *temp; temp++) { +        free((*temp)->contents); +        free(*temp); +    } +    free(val); +} | 
