/* Copyright (C) 2012 the GSS-PROXY contributors, see COPYING for license */ #include "gss_plugin.h" #include #include #include #define KRB5_OID_LEN 9 #define KRB5_OID "\052\206\110\206\367\022\001\002\002" #define KRB5_OLD_OID_LEN 5 #define KRB5_OLD_OID "\053\005\001\005\002" /* Incorrect krb5 mech OID emitted by MS. */ #define KRB5_WRONG_OID_LEN 9 #define KRB5_WRONG_OID "\052\206\110\202\367\022\001\002\002" #define IAKERB_OID_LEN 6 #define IAKERB_OID "\053\006\001\005\002\005" const gss_OID_desc gpoid_krb5 = { .length = KRB5_OID_LEN, .elements = KRB5_OID }; const gss_OID_desc gpoid_krb5_old = { .length = KRB5_OLD_OID_LEN, .elements = KRB5_OLD_OID }; const gss_OID_desc gpoid_krb5_wrong = { .length = KRB5_WRONG_OID_LEN, .elements = KRB5_WRONG_OID }; const gss_OID_desc gpoid_iakerb = { .length = IAKERB_OID_LEN, .elements = IAKERB_OID }; enum gpp_behavior gpp_get_behavior(void) { static enum gpp_behavior behavior = GPP_UNINITIALIZED; char *envval; if (behavior == GPP_UNINITIALIZED) { envval = gp_getenv("GSSPROXY_BEHAVIOR"); if (envval) { if (strcmp(envval, "LOCAL_ONLY") == 0) { behavior = GPP_LOCAL_ONLY; } else if (strcmp(envval, "LOCAL_FIRST") == 0) { behavior = GPP_LOCAL_FIRST; } else if (strcmp(envval, "REMOTE_FIRST") == 0) { behavior = GPP_REMOTE_FIRST; } else if (strcmp(envval, "REMOTE_ONLY") == 0) { behavior = GPP_REMOTE_ONLY; } else { /* unknown setting, default to what has been configured * (by default local first) */ behavior = GPP_DEFAULT_BEHAVIOR; } } else { /* default to what has been configured (by default local only) */ behavior = GPP_DEFAULT_BEHAVIOR; } } return behavior; } /* 2.16.840.1.113730.3.8.15.1 */ const gss_OID_desc gssproxy_mech_interposer = { .length = 11, .elements = "\140\206\110\001\206\370\102\003\010\017\001" }; gss_OID_set gss_mech_interposer(gss_OID mech_type) { gss_OID_set interposed_mechs; OM_uint32 maj, min; char *envval; /* avoid looping in the gssproxy daemon by avoiding to interpose * any mechanism */ envval = gp_getenv("GSS_USE_PROXY"); if (!envval) { return NULL; } if (!gp_boolean_is_true(envval)) { return NULL; } interposed_mechs = NULL; maj = 0; if (gss_oid_equal(&gssproxy_mech_interposer, mech_type)) { maj = gss_create_empty_oid_set(&min, &interposed_mechs); if (maj != 0) { return NULL; } maj = gss_add_oid_set_member(&min, no_const(&gpoid_krb5), &interposed_mechs); if (maj != 0) { goto done; } maj = gss_add_oid_set_member(&min, no_const(&gpoid_krb5_old), &interposed_mechs); if (maj != 0) { goto done; } maj = gss_add_oid_set_member(&min, no_const(&gpoid_krb5_wrong), &interposed_mechs); if (maj != 0) { goto done; } maj = gss_add_oid_set_member(&min, no_const(&gpoid_iakerb), &interposed_mechs); if (maj != 0) { goto done; } } /* while there also initiaize special_mechs */ (void)gpp_special_available_mechs(interposed_mechs); done: if (maj != 0) { (void)gss_release_oid_set(&min, &interposed_mechs); interposed_mechs = NULL; } return interposed_mechs; } bool gpp_is_special_oid(const gss_OID mech_type) { if (mech_type != GSS_C_NO_OID && mech_type->length >= gssproxy_mech_interposer.length && memcmp(gssproxy_mech_interposer.elements, mech_type->elements, gssproxy_mech_interposer.length) == 0) { return true; } return false; } static bool gpp_special_equal(const gss_OID s, const gss_OID n) { int base_len = gssproxy_mech_interposer.length; if (s->length - base_len == n->length && memcmp(s->elements + base_len, n->elements, n->length) == 0) { return true; } return false; } struct gpp_special_oid_list { gss_OID_desc regular_oid; gss_OID_desc special_oid; struct gpp_special_oid_list *next; sig_atomic_t next_is_set; }; /* This is an ADD-ONLY list, and the pointer to next is updated * atomically so that we can avoid using mutexes for mere access * to the list. */ static struct gpp_special_oid_list *gpp_s_mechs; static sig_atomic_t gpp_s_mechs_is_set; static inline struct gpp_special_oid_list *gpp_get_special_oids(void) { int is_set; is_set = gpp_s_mechs_is_set; __sync_synchronize(); if (is_set != 0) { return gpp_s_mechs; } return NULL; } static inline struct gpp_special_oid_list *gpp_next_special_oids( struct gpp_special_oid_list *item) { int is_set; is_set = item->next_is_set; __sync_synchronize(); if (is_set != 0) { return item->next; } return NULL; } static inline struct gpp_special_oid_list *gpp_last_special_oids( struct gpp_special_oid_list *list) { struct gpp_special_oid_list *item; item = list; while (item && item->next_is_set) { item = item->next; } return item; } static inline void gpp_add_special_oids(struct gpp_special_oid_list *item) { struct gpp_special_oid_list *list, *last; list = gpp_get_special_oids(); if (list == NULL) { gpp_s_mechs = item; __sync_synchronize(); gpp_s_mechs_is_set = 1; } else { last = gpp_last_special_oids(list); last->next = item; __sync_synchronize(); last->next_is_set = 1; } } static const gss_OID gpp_new_special_mech(const gss_OID n) { gss_const_OID base = &gssproxy_mech_interposer; struct gpp_special_oid_list *item; item = calloc(1, sizeof(struct gpp_special_oid_list)); if (!item) { return GSS_C_NO_OID; } item->regular_oid.length = n->length; item->regular_oid.elements = malloc(n->length); item->special_oid.length = base->length + n->length; item->special_oid.elements = malloc(item->special_oid.length); if (!item->regular_oid.elements || !item->special_oid.elements) { free(item->regular_oid.elements); free(item->special_oid.elements); free(item); return GSS_C_NO_OID; } memcpy(item->regular_oid.elements, n->elements, n->length); memcpy(item->special_oid.elements, base->elements, base->length); memcpy(item->special_oid.elements + base->length, n->elements, n->length); gpp_add_special_oids(item); return (const gss_OID)&item->special_oid; } const gss_OID gpp_special_mech(const gss_OID mech_type) { struct gpp_special_oid_list *item = NULL; if (gpp_is_special_oid(mech_type)) { return mech_type; } item = gpp_get_special_oids(); if (mech_type == GSS_C_NO_OID) { /* return the first special one if none specified */ if (item) { return (const gss_OID)&item->special_oid; } return GSS_C_NO_OID; } while (item) { if (gpp_special_equal(&item->special_oid, mech_type)) { return (const gss_OID)&item->special_oid; } item = gpp_next_special_oids(item); } /* none matched, add new special oid to the set */ return gpp_new_special_mech(mech_type); } const gss_OID gpp_unspecial_mech(const gss_OID mech_type) { struct gpp_special_oid_list *item = NULL; if (!gpp_is_special_oid(mech_type)) { return mech_type; } item = gpp_get_special_oids(); while (item) { if (gss_oid_equal(&item->special_oid, mech_type)) { return (const gss_OID)&item->regular_oid; } item = gpp_next_special_oids(item); } /* none matched */ return mech_type; } gss_OID_set gpp_special_available_mechs(const gss_OID_set mechs) { gss_OID_set amechs = GSS_C_NO_OID_SET; struct gpp_special_oid_list *item; gss_OID n; uint32_t maj, min; int i; item = gpp_get_special_oids(); maj = gss_create_empty_oid_set(&min, &amechs); if (maj) { return GSS_C_NO_OID_SET; } for (i = 0; i < mechs->count; i++) { while (item) { if (gpp_is_special_oid(&mechs->elements[i])) { maj = gss_add_oid_set_member(&min, &mechs->elements[i], &amechs); if (maj != GSS_S_COMPLETE) { goto done; } break; } if (gpp_special_equal(&item->special_oid, &mechs->elements[i])) { maj = gss_add_oid_set_member(&min, &item->special_oid, &amechs); if (maj != GSS_S_COMPLETE) { goto done; } break; } item = gpp_next_special_oids(item); } if (item == NULL) { /* not found, add to static list */ n = gpp_new_special_mech(&mechs->elements[i]); if (n == GSS_C_NO_OID) { maj = GSS_S_FAILURE; } else { maj = gss_add_oid_set_member(&min, n, &amechs); } if (maj != GSS_S_COMPLETE) { goto done; } } } done: if (maj != GSS_S_COMPLETE || amechs->count == 0) { (void)gss_release_oid_set(&min, &amechs); } return amechs; } OM_uint32 gssi_internal_release_oid(OM_uint32 *minor_status, gss_OID *oid) { struct gpp_special_oid_list *item = NULL; *minor_status = 0; if (&gssproxy_mech_interposer == *oid) { *oid = GSS_C_NO_OID; return GSS_S_COMPLETE; } item = gpp_get_special_oids(); while (item) { if ((&item->regular_oid == *oid) || (&item->special_oid == *oid)) { *oid = GSS_C_NO_OID; return GSS_S_COMPLETE; } item = gpp_next_special_oids(item); } /* none matched, it's not ours */ return GSS_S_CONTINUE_NEEDED; } #define MAP_ERROR_BASE 0x04200000 uint32_t gpp_map_error(uint32_t err) { /* placeholder, * we will need an actual map but to speed up testing just make a sum with * a special base and hope no conflicts will happen in the mechglue */ if (err) { err += MAP_ERROR_BASE; } return err; } uint32_t gpp_unmap_error(uint32_t err) { /* placeholder, * we will need an actual map but to speed up testing just make a sum with * a special base and hope no conflicts will happen in the mechglue */ if (err) { err -= MAP_ERROR_BASE; } return err; } uint32_t gpp_wrap_sec_ctx_token(uint32_t *minor, gss_OID mech_type, gss_buffer_t token, gss_buffer_t wrap_token) { gss_OID spmech; uint32_t len; spmech = gpp_special_mech(mech_type); if (spmech == GSS_C_NO_OID) { return GSS_S_FAILURE; } wrap_token->length = sizeof(uint32_t) + spmech->length + token->length; wrap_token->value = malloc(wrap_token->length); if (!wrap_token->value) { wrap_token->length = 0; return GSS_S_FAILURE; } len = htobe32(spmech->length); memcpy(wrap_token->value, &len, sizeof(uint32_t)); memcpy(wrap_token->value + sizeof(uint32_t), spmech->elements, spmech->length); memcpy(wrap_token->value + sizeof(uint32_t) + spmech->length, token->value, token->length); return GSS_S_COMPLETE; } uint32_t gpp_remote_to_local_ctx(uint32_t *minor, gssx_ctx **remote_ctx, gss_ctx_id_t *local_ctx) { gss_buffer_desc wrap_token = {0}; gss_buffer_desc token; gss_OID_desc mech; uint32_t hlen, len; uint32_t maj, min; gp_conv_gssx_to_buffer(&(*remote_ctx)->exported_context_token, &token); /* To get a local context we need to call import_sec_context with a token * wrapping that uses the special mech oid. Otherwise the mechglue will * give us back an interposed context. */ if (token.length <= sizeof(uint32_t)) { return GSS_S_FAILURE; } memcpy(&len, token.value, sizeof(uint32_t)); mech.length = be32toh(len); hlen = sizeof(uint32_t) + mech.length; if (token.length <= hlen) { return GSS_S_FAILURE; } mech.elements = malloc(mech.length); if (!mech.elements) { return GSS_S_FAILURE; } memcpy(mech.elements, token.value + sizeof(uint32_t), mech.length); token.length -= hlen; token.value += hlen; maj = gpp_wrap_sec_ctx_token(&min, &mech, &token, &wrap_token); if (maj != GSS_S_COMPLETE) { free(mech.elements); return maj; } maj = gss_import_sec_context(minor, &wrap_token, local_ctx); free(mech.elements); (void)gss_release_buffer(&min, &wrap_token); xdr_free((xdrproc_t)xdr_gssx_ctx, (char *)(*remote_ctx)); *remote_ctx = NULL; return maj; } uint32_t gpp_name_to_local(uint32_t *minor, gssx_name *name, gss_OID mech_type, gss_name_t *mech_name) { uint32_t maj, min; gss_buffer_desc display_name_buffer = GSS_C_EMPTY_BUFFER; gss_OID display_name_type = GSS_C_NO_OID; gss_name_t tmpname = NULL; maj = gpm_display_name(minor, name, &display_name_buffer, &display_name_type); if (maj) { return maj; } maj = gss_import_name(minor, &display_name_buffer, display_name_type, &tmpname); (void)gss_release_buffer(&min, &display_name_buffer); (void)gss_release_oid(&min, &display_name_type); if (maj) { return maj; } if (mech_type != GSS_C_NO_OID) { /* name for specific mech requested */ maj = gss_canonicalize_name(minor, tmpname, gpp_special_mech(mech_type), NULL); } *mech_name = tmpname; return maj; } uint32_t gpp_local_to_name(uint32_t *minor, gss_name_t local_name, gssx_name **name) { uint32_t maj, min; gss_buffer_desc display_name_buffer = GSS_C_EMPTY_BUFFER; gss_OID display_name_type = GSS_C_NO_OID; maj = gss_display_name(minor, local_name, &display_name_buffer, &display_name_type); if (maj) { return maj; } maj = gpm_import_name(minor, &display_name_buffer, display_name_type, name); (void)gss_release_buffer(&min, &display_name_buffer); (void)gss_release_oid(&min, &display_name_type); return maj; } uint32_t gpp_copy_oid(uint32_t *minor, gss_OID in, gss_OID *out) { gss_OID c; c = calloc(1, sizeof(gss_OID_desc)); if (!c) { *minor = ENOMEM; return GSS_S_FAILURE; } c->length = in->length; c->elements = malloc(in->length); if (!c->elements) { free(c); *minor = ENOMEM; return GSS_S_FAILURE; } memcpy(c->elements, in->elements, in->length); *out = c; *minor = 0; return GSS_S_COMPLETE; } bool gpp_is_krb5_oid(const gss_OID mech) { if (gss_oid_equal(&gpoid_krb5, mech)) { return true; } else if (gss_oid_equal(&gpoid_krb5_old, mech)) { return true; } else if (gss_oid_equal(&gpoid_krb5_wrong, mech)) { return true; } else if (gss_oid_equal(&gpoid_iakerb, mech)) { return true; } return false; }