/* Copyright (C) 2011 the GSS-PROXY contributors, see COPYING for license */ #include "gssapi_gpm.h" #include struct gpm_mech_info { gss_OID mech; gss_OID_set name_types; gss_OID_set mech_attrs; gss_OID_set known_mech_attrs; gss_OID_set cred_options; gss_OID_set sec_ctx_options; gss_buffer_t saslname_sasl_mech_name; gss_buffer_t saslname_mech_name; gss_buffer_t saslname_mech_desc; }; struct gpm_mech_attr { gss_OID attr; gss_buffer_t name; gss_buffer_t short_desc; gss_buffer_t long_desc; }; struct gpm_mechs { bool initialized; gss_OID_set mech_set; size_t info_len; struct gpm_mech_info *info; size_t desc_len; struct gpm_mech_attr *desc; }; pthread_mutex_t global_mechs_lock = PTHREAD_MUTEX_INITIALIZER; pthread_once_t indicate_mechs_once = PTHREAD_ONCE_INIT; struct gpm_mechs global_mechs = { .initialized = false, .mech_set = GSS_C_NO_OID_SET, .info_len = 0, .info = NULL, .desc_len = 0, .desc = NULL, }; static uint32_t gpm_copy_gss_OID_set(uint32_t *minor_status, gss_OID_set oldset, gss_OID_set *newset) { gss_OID_set n; uint32_t ret_maj; uint32_t ret_min; int i; ret_maj = gss_create_empty_oid_set(&ret_min, &n); if (ret_maj) { *minor_status = ret_min; return ret_maj; } for (i = 0; i < oldset->count; i++) { ret_maj = gss_add_oid_set_member(&ret_min, &oldset->elements[i], &n); if (ret_maj) { *minor_status = ret_min; gss_release_oid_set(&ret_min, &n); return ret_maj; } } *newset = n; *minor_status = 0; return GSS_S_COMPLETE; } static uint32_t gpm_copy_gss_buffer(uint32_t *minor_status, gss_buffer_t oldbuf, gss_buffer_t newbuf) { if (!oldbuf || oldbuf->length == 0) { newbuf->value = NULL; newbuf->length = 0; *minor_status = 0; return GSS_S_COMPLETE; } newbuf->value = malloc(oldbuf->length); if (!newbuf->value) { *minor_status = ENOMEM; return GSS_S_FAILURE; } memcpy(newbuf->value, oldbuf->value, oldbuf->length); newbuf->length = oldbuf->length; *minor_status = 0; return GSS_S_COMPLETE; } static bool gpm_equal_oids(gss_const_OID a, gss_const_OID b) { int ret; if (a->length == b->length) { ret = memcmp(a->elements, b->elements, a->length); if (ret == 0) { return true; } } return false; } static void gpmint_indicate_mechs(void) { union gp_rpc_arg uarg; union gp_rpc_res ures; gssx_arg_indicate_mechs *arg = &uarg.indicate_mechs; gssx_res_indicate_mechs *res = &ures.indicate_mechs; struct gpm_mech_info *gi; struct gpm_mech_attr *ga; gssx_mech_info *mi; gssx_mech_attr *ma; uint32_t discard; uint32_t ret_min; uint32_t ret_maj = 0; int ret = 0; int i; memset(arg, 0, sizeof(gssx_arg_indicate_mechs)); memset(res, 0, sizeof(gssx_res_indicate_mechs)); /* ignore call_ctx for now */ /* execute proxy request */ ret = gpm_make_call(GSSX_INDICATE_MECHS, &uarg, &ures); if (ret) { goto done; } if (res->status.major_status) { gpm_save_status(&res->status); ret_min = res->status.minor_status; ret_maj = res->status.major_status; ret = 0; goto done; } ret_maj = gss_create_empty_oid_set(&ret_min, &global_mechs.mech_set); if (ret_maj) { goto done; } global_mechs.info = calloc(res->mechs.mechs_len, sizeof(struct gpm_mech_info)); if (!global_mechs.info) { ret_maj = GSS_S_FAILURE; ret_min = ENOMEM; goto done; } for (i = 0; i < res->mechs.mechs_len; i++) { mi = &res->mechs.mechs_val[i]; gi = &global_mechs.info[i]; ret = gp_conv_gssx_to_oid_alloc(&mi->mech, &gi->mech); if (ret) { goto done; } ret_maj = gss_add_oid_set_member(&ret_min, gi->mech, &global_mechs.mech_set); if (ret_maj) { goto done; } ret = gp_conv_gssx_to_oid_set(&mi->name_types, &gi->name_types); if (ret) { goto done; } ret = gp_conv_gssx_to_oid_set(&mi->mech_attrs, &gi->mech_attrs); if (ret) { goto done; } ret = gp_conv_gssx_to_oid_set(&mi->known_mech_attrs, &gi->known_mech_attrs); if (ret) { goto done; } ret = gp_conv_gssx_to_oid_set(&mi->cred_options, &gi->cred_options); if (ret) { goto done; } ret = gp_conv_gssx_to_oid_set(&mi->sec_ctx_options, &gi->sec_ctx_options); if (ret) { goto done; } ret = gp_conv_gssx_to_buffer_alloc(&mi->saslname_sasl_mech_name, &gi->saslname_sasl_mech_name); if (ret) { goto done; } ret = gp_conv_gssx_to_buffer_alloc(&mi->saslname_mech_name, &gi->saslname_mech_name); if (ret) { goto done; } ret = gp_conv_gssx_to_buffer_alloc(&mi->saslname_mech_desc, &gi->saslname_mech_desc); if (ret) { goto done; } } global_mechs.info_len = res->mechs.mechs_len; global_mechs.desc = calloc(res->mech_attr_descs.mech_attr_descs_len, sizeof(struct gpm_mech_attr)); if (!global_mechs.desc) { goto done; } for (i = 0; i < res->mech_attr_descs.mech_attr_descs_len; i++) { ma = &res->mech_attr_descs.mech_attr_descs_val[i]; ga = &global_mechs.desc[i]; ret = gp_conv_gssx_to_oid_alloc(&ma->attr, &ga->attr); if (ret) { goto done; } ret = gp_conv_gssx_to_buffer_alloc(&ma->name, &ga->name); if (ret) { goto done; } ret = gp_conv_gssx_to_buffer_alloc(&ma->short_desc, &ga->short_desc); if (ret) { goto done; } ret = gp_conv_gssx_to_buffer_alloc(&ma->long_desc, &ga->long_desc); if (ret) { goto done; } } global_mechs.desc_len = res->mech_attr_descs.mech_attr_descs_len; global_mechs.initialized = true; done: if (ret || ret_maj) { for (i = 0; i < global_mechs.desc_len; i++) { ga = &global_mechs.desc[i]; gss_release_oid(&discard, &ga->attr); gss_release_buffer(&discard, ga->name); gss_release_buffer(&discard, ga->short_desc); gss_release_buffer(&discard, ga->long_desc); } free(global_mechs.desc); global_mechs.desc = NULL; for (i = 0; i < global_mechs.info_len; i++) { gi = &global_mechs.info[i]; gss_release_oid(&discard, &gi->mech); gss_release_oid_set(&discard, &gi->name_types); gss_release_oid_set(&discard, &gi->mech_attrs); gss_release_oid_set(&discard, &gi->known_mech_attrs); gss_release_oid_set(&discard, &gi->cred_options); gss_release_oid_set(&discard, &gi->sec_ctx_options); gss_release_buffer(&discard, gi->saslname_sasl_mech_name); gss_release_buffer(&discard, gi->saslname_mech_name); gss_release_buffer(&discard, gi->saslname_mech_desc); } free(global_mechs.info); global_mechs.info = NULL; gss_release_oid_set(&discard, &global_mechs.mech_set); } gpm_free_xdrs(GSSX_INDICATE_MECHS, &uarg, &ures); } static int gpmint_init_global_mechs(void) { pthread_once(&indicate_mechs_once, gpmint_indicate_mechs); if (!global_mechs.initialized) { /* this is quite a corner case. It means the pthread_once() call * failed for some reason. In this case we need to use a mutex */ pthread_mutex_lock(&global_mechs_lock); /* need to recheck once we acquired the lock, to avoid redoing * if we were stuck after another thread that already did it */ if (!global_mechs.initialized) { gpmint_indicate_mechs(); } pthread_mutex_unlock(&global_mechs_lock); if (!global_mechs.initialized) { /* if still it is not initialized, give up */ return EIO; } } return 0; } OM_uint32 gpm_indicate_mechs(OM_uint32 *minor_status, gss_OID_set *mech_set) { uint32_t ret_min; uint32_t ret_maj; int ret; if (!minor_status) { return GSS_S_CALL_INACCESSIBLE_WRITE; } if (!mech_set) { *minor_status = 0; return GSS_S_CALL_INACCESSIBLE_WRITE; } ret= gpmint_init_global_mechs(); if (ret) { *minor_status = ret; return GSS_S_FAILURE; } ret_maj = gpm_copy_gss_OID_set(&ret_min, global_mechs.mech_set, mech_set); *minor_status = ret_min; return ret_maj; } OM_uint32 gpm_inquire_names_for_mech(OM_uint32 *minor_status, gss_OID mech_type, gss_OID_set *mech_names) { uint32_t ret_min; uint32_t ret_maj; int i; if (!minor_status) { return GSS_S_CALL_INACCESSIBLE_WRITE; } if (!mech_names) { *minor_status = 0; return GSS_S_CALL_INACCESSIBLE_WRITE; } ret_min = gpmint_init_global_mechs(); if (ret_min) { *minor_status = ret_min; return GSS_S_FAILURE; } for (i = 0; i < global_mechs.info_len; i++) { if (!gpm_equal_oids(global_mechs.info[i].mech, mech_type)) { continue; } ret_maj = gpm_copy_gss_OID_set(&ret_min, global_mechs.info[i].name_types, mech_names); *minor_status = ret_min; return ret_maj; } *minor_status = 0; return GSS_S_BAD_MECH; } OM_uint32 gpm_inquire_mechs_for_name(OM_uint32 *minor_status, gssx_name *input_name, gss_OID_set *mech_types) { uint32_t ret_min; uint32_t ret_maj; uint32_t discard; gss_OID name_type = GSS_C_NO_OID; int present; int i; if (!minor_status) { return GSS_S_CALL_INACCESSIBLE_WRITE; } if (!input_name || !mech_types) { *minor_status = 0; return GSS_S_CALL_INACCESSIBLE_WRITE; } ret_min = gpmint_init_global_mechs(); if (ret_min) { *minor_status = ret_min; return GSS_S_FAILURE; } ret_min = gp_conv_gssx_to_oid_alloc(&input_name->name_type, &name_type); if (ret_min) { ret_maj = GSS_S_FAILURE; goto done; } ret_maj = gss_create_empty_oid_set(&ret_min, mech_types); if (ret_maj) { goto done; } for (i = 0; i < global_mechs.info_len; i++) { ret_maj = gss_test_oid_set_member(&ret_min, name_type, global_mechs.info[i].name_types, &present); if (ret_maj) { /* skip on error */ continue; } if (present) { ret_maj = gss_add_oid_set_member(&ret_min, global_mechs.info[i].mech, mech_types); } if (ret_maj) { goto done; } } done: gss_release_oid(&discard, &name_type); if (ret_maj) { gss_release_oid_set(&discard, mech_types); *minor_status = ret_min; return ret_maj; } *minor_status = 0; return GSS_S_COMPLETE; } OM_uint32 gpm_inquire_attrs_for_mech(OM_uint32 *minor_status, gss_OID mech, gss_OID_set *mech_attrs, gss_OID_set *known_mech_attrs) { uint32_t ret_min; uint32_t ret_maj; uint32_t discard; int i; if (!minor_status) { return GSS_S_CALL_INACCESSIBLE_WRITE; } if (!mech_attrs || !known_mech_attrs) { *minor_status = 0; return GSS_S_CALL_INACCESSIBLE_WRITE; } ret_min = gpmint_init_global_mechs(); if (ret_min) { *minor_status = ret_min; return GSS_S_FAILURE; } for (i = 0; i < global_mechs.info_len; i++) { if (!gpm_equal_oids(global_mechs.info[i].mech, mech)) { continue; } ret_maj = gpm_copy_gss_OID_set(&ret_min, global_mechs.info[i].mech_attrs, mech_attrs); if (ret_maj) { *minor_status = ret_min; return ret_maj; } ret_maj = gpm_copy_gss_OID_set(&ret_min, global_mechs.info[i].known_mech_attrs, known_mech_attrs); if (ret_maj) { gss_release_oid_set(&discard, known_mech_attrs); } *minor_status = ret_min; return ret_maj; } *minor_status = 0; return GSS_S_BAD_MECH; } OM_uint32 gpm_inquire_saslname_for_mech(OM_uint32 *minor_status, const gss_OID desired_mech, gss_buffer_t sasl_mech_name, gss_buffer_t mech_name, gss_buffer_t mech_description) { uint32_t ret_min; uint32_t ret_maj; uint32_t discard; int i; if (!minor_status) { return GSS_S_CALL_INACCESSIBLE_WRITE; } if (!sasl_mech_name || !mech_name || !mech_description) { *minor_status = 0; return GSS_S_CALL_INACCESSIBLE_WRITE; } ret_min = gpmint_init_global_mechs(); if (ret_min) { *minor_status = ret_min; return GSS_S_FAILURE; } for (i = 0; i < global_mechs.info_len; i++) { if (!gpm_equal_oids(global_mechs.info[i].mech, desired_mech)) { continue; } ret_maj = gpm_copy_gss_buffer(&ret_min, global_mechs.info[i].saslname_sasl_mech_name, sasl_mech_name); if (ret_maj) { *minor_status = ret_min; return ret_maj; } ret_maj = gpm_copy_gss_buffer(&ret_min, global_mechs.info[i].saslname_mech_name, mech_name); if (ret_maj) { gss_release_buffer(&discard, sasl_mech_name); *minor_status = ret_min; return ret_maj; } ret_maj = gpm_copy_gss_buffer(&ret_min, global_mechs.info[i].saslname_mech_desc, mech_description); if (ret_maj) { gss_release_buffer(&discard, sasl_mech_name); gss_release_buffer(&discard, mech_name); } *minor_status = ret_min; return ret_maj; } *minor_status = 0; return GSS_S_BAD_MECH; } OM_uint32 gpm_display_mech_attr(OM_uint32 *minor_status, gss_const_OID mech_attr, gss_buffer_t name, gss_buffer_t short_desc, gss_buffer_t long_desc) { uint32_t ret_min; uint32_t ret_maj; uint32_t discard; int i; if (!minor_status) { return GSS_S_CALL_INACCESSIBLE_WRITE; } if (!name || !short_desc || !long_desc) { *minor_status = 0; return GSS_S_CALL_INACCESSIBLE_WRITE; } ret_min = gpmint_init_global_mechs(); if (ret_min) { *minor_status = ret_min; return GSS_S_FAILURE; } for (i = 0; i < global_mechs.desc_len; i++) { if (!gpm_equal_oids(global_mechs.desc[i].attr, mech_attr)) { continue; } ret_maj = gpm_copy_gss_buffer(&ret_min, global_mechs.desc[i].name, name); if (ret_maj) { *minor_status = ret_min; return ret_maj; } ret_maj = gpm_copy_gss_buffer(&ret_min, global_mechs.desc[i].short_desc, short_desc); if (ret_maj) { gss_release_buffer(&discard, name); *minor_status = ret_min; return ret_maj; } ret_maj = gpm_copy_gss_buffer(&ret_min, global_mechs.desc[i].long_desc, long_desc); if (ret_maj) { gss_release_buffer(&discard, name); gss_release_buffer(&discard, short_desc); } *minor_status = ret_min; return ret_maj; } *minor_status = 0; return GSS_S_BAD_MECH; } OM_uint32 gpm_indicate_mechs_by_attrs(OM_uint32 *minor_status, gss_const_OID_set desired_mech_attrs, gss_const_OID_set except_mech_attrs, gss_const_OID_set critical_mech_attrs, gss_OID_set *mechs) { uint32_t ret_min; uint32_t ret_maj; uint32_t discard; int present; int i, j; if (!minor_status) { return GSS_S_CALL_INACCESSIBLE_WRITE; } if (!mechs) { *minor_status = 0; return GSS_S_CALL_INACCESSIBLE_WRITE; } ret_min = gpmint_init_global_mechs(); if (ret_min) { *minor_status = ret_min; return GSS_S_FAILURE; } ret_maj = gss_create_empty_oid_set(&ret_min, mechs); if (ret_maj) { *minor_status = ret_min; return ret_maj; } for (i = 0; i < global_mechs.info_len; i++) { if (desired_mech_attrs != GSS_C_NO_OID_SET) { for (j = 0; j < desired_mech_attrs->count; j++) { ret_maj = gss_test_oid_set_member(&ret_min, &desired_mech_attrs->elements[j], global_mechs.info[i].mech_attrs, &present); if (ret_maj) { /* skip in case of errors */ break; } if (!present) { break; } } /* if not desired skip */ if (j != desired_mech_attrs->count) { continue; } } if (except_mech_attrs != GSS_C_NO_OID_SET) { for (j = 0; j < except_mech_attrs->count; j++) { ret_maj = gss_test_oid_set_member(&ret_min, &except_mech_attrs->elements[j], global_mechs.info[i].mech_attrs, &present); if (ret_maj) { /* continue in case of errors */ continue; } if (present) { break; } } /* if excepted skip */ if (j == except_mech_attrs->count) { continue; } } if (critical_mech_attrs != GSS_C_NO_OID_SET) { for (j = 0; j < critical_mech_attrs->count; j++) { ret_maj = gss_test_oid_set_member(&ret_min, &critical_mech_attrs->elements[j], global_mechs.info[i].known_mech_attrs, &present); if (ret_maj) { /* skip in case of errors */ break; } if (!present) { break; } } /* if not known skip */ if (j != critical_mech_attrs->count) { continue; } } /* passes all tests, add to list */ ret_maj = gss_add_oid_set_member(&ret_min, global_mechs.info[i].mech, mechs); if (ret_maj) { goto done; } } done: if (ret_maj) { gss_release_oid_set(&discard, mechs); *minor_status = ret_min; return ret_maj; } *minor_status = 0; return GSS_S_COMPLETE; }