/* * Naming extensions based local login authorization. */ static OM_uint32 attr_authorize_localname(OM_uint32 *minor, const gss_name_t name, const gss_union_name_t unionUser) { OM_uint32 major = GSS_S_UNAVAILABLE; /* attribute not present */ gss_buffer_t externalName; int more = -1; if (unionUser->name_type != GSS_C_NO_OID && !g_OID_equal(unionUser->name_type, GSS_C_NT_USER_NAME)) return (GSS_S_BAD_NAMETYPE); externalName = unionUser->external_name; assert(externalName != GSS_C_NO_BUFFER); while (more != 0 && major != GSS_S_COMPLETE) { OM_uint32 tmpMajor, tmpMinor; gss_buffer_desc value; gss_buffer_desc display_value; int authenticated = 0, complete = 0; tmpMajor = gss_get_name_attribute(minor, name, GSS_C_ATTR_LOCAL_LOGIN_USER, &authenticated, &complete, &value, &display_value, &more); if (GSS_ERROR(tmpMajor)) { major = tmpMajor; break; } if (authenticated && value.length == externalName->length && memcmp(value.value, externalName->value, externalName->length) == 0) major = GSS_S_COMPLETE; else major = GSS_S_UNAUTHORIZED; gss_release_buffer(&tmpMinor, &value); gss_release_buffer(&tmpMinor, &display_value); } return (major); }
OM_uint32 ntlm_gss_get_name_attribute(OM_uint32 *minor_status, gss_name_t name, gss_buffer_t attr, int *authenticated, int *complete, gss_buffer_t value, gss_buffer_t display_value, int *more) { OM_uint32 ret; ret = gss_get_name_attribute(minor_status, name, attr, authenticated, complete, value, display_value, more); return (ret); }
static void dump_attribute(gss_name_t name, gss_buffer_t attribute, int noisy) { OM_uint32 major, minor; gss_buffer_desc value; gss_buffer_desc display_value; int authenticated = 0; int complete = 0; int more = -1; unsigned int i; while (more != 0) { value.value = NULL; display_value.value = NULL; major = gss_get_name_attribute(&minor, name, attribute, &authenticated, &complete, &value, &display_value, &more); check_gsserr("gss_get_name_attribute", major, minor); printf("Attribute %.*s %s %s\n\n%.*s\n", (int)attribute->length, (char *)attribute->value, authenticated ? "Authenticated" : "", complete ? "Complete" : "", (int)display_value.length, (char *)display_value.value); if (noisy) { for (i = 0; i < value.length; i++) { if ((i % 32) == 0) printf("\n"); printf("%02x", ((char *)value.value)[i] & 0xFF); } printf("\n\n"); } (void)gss_release_buffer(&minor, &value); (void)gss_release_buffer(&minor, &display_value); } }
enum auth_stat _svcauth_gss(struct svc_req *req, struct rpc_msg *msg, bool *no_dispatch) { XDR xdrs[1]; SVCAUTH *auth; struct svc_rpc_gss_data *gd = NULL; struct rpc_gss_cred *gc = NULL; struct rpc_gss_init_res gr; int call_stat, offset; OM_uint32 min_stat; bool gd_locked = false; bool gd_hashed = false; /* Initialize reply. */ req->rq_verf = _null_auth; /* Unserialize client credentials. */ if (req->rq_cred.oa_length <= 0) svcauth_gss_return(AUTH_BADCRED); gc = (struct rpc_gss_cred *)req->rq_clntcred; memset(gc, 0, sizeof(struct rpc_gss_cred)); xdrmem_create(xdrs, req->rq_cred.oa_base, req->rq_cred.oa_length, XDR_DECODE); if (!xdr_rpc_gss_cred(xdrs, gc)) { XDR_DESTROY(xdrs); svcauth_gss_return(AUTH_BADCRED); } XDR_DESTROY(xdrs); /* Check version. */ if (gc->gc_v != RPCSEC_GSS_VERSION) svcauth_gss_return(AUTH_BADCRED); if (gc->gc_seq > RPCSEC_GSS_MAXSEQ) svcauth_gss_return(RPCSEC_GSS_CTXPROBLEM); if (gc->gc_proc > RPCSEC_GSS_MAXPROC) svcauth_gss_return(AUTH_BADCRED); /* Check RPCSEC_GSS service. */ if (gc->gc_svc != RPCSEC_GSS_SVC_NONE && gc->gc_svc != RPCSEC_GSS_SVC_INTEGRITY && gc->gc_svc != RPCSEC_GSS_SVC_PRIVACY) svcauth_gss_return(AUTH_BADCRED); /* Context lookup. */ if ((gc->gc_proc == RPCSEC_GSS_DATA) || (gc->gc_proc == RPCSEC_GSS_DESTROY)) { /* Per RFC 2203 5.3.3.3, if a valid security context * cannot be found to authorize a request, the * implementation returns RPCSEC_GSS_CREDPROBLEM. * N.B., we are explicitly allowed to discard contexts * for any reason (e.g., to save space). */ gd = authgss_ctx_hash_get(gc); if (!gd) svcauth_gss_return(RPCSEC_GSS_CREDPROBLEM); gd_hashed = true; if (gc->gc_svc != gd->sec.svc) gd->sec.svc = gc->gc_svc; } if (!gd) { /* Allocate and set up server auth handle. */ auth = mem_alloc(sizeof(SVCAUTH)); gd = alloc_svc_rpc_gss_data(); auth->svc_ah_ops = &svc_auth_gss_ops; auth->svc_ah_private = (caddr_t) gd; gd->auth = auth; } /* Serialize context. */ mutex_lock(&gd->lock); gd_locked = true; /* thread auth */ req->rq_auth = gd->auth; /* Check sequence number. */ if (gd->established) { if (get_time_fast() >= gd->endtime) { *no_dispatch = true; svcauth_gss_return(RPCSEC_GSS_CREDPROBLEM); } /* XXX implied serialization? or just fudging? advance if * greater? */ offset = gd->seqlast - gc->gc_seq; if (offset < 0) { gd->seqlast = gc->gc_seq; offset = 0 - offset; gd->seqmask <<= offset; offset = 0; } else if (offset >= gd->win || (gd->seqmask & (1 << offset))) { *no_dispatch = true; svcauth_gss_return(AUTH_OK); } gd->seqmask |= (1 << offset); /* XXX harmless */ req->rq_ap1 = (void *)(uintptr_t) gc->gc_seq; /* GCC casts */ req->rq_clntname = (char *) gd->client_name; req->rq_svcname = (char *) gd->ctx; } /* gd->established */ /* Handle RPCSEC_GSS control procedure. */ switch (gc->gc_proc) { case RPCSEC_GSS_INIT: case RPCSEC_GSS_CONTINUE_INIT: if (req->rq_proc != NULLPROC) svcauth_gss_return(AUTH_FAILED); /* XXX ? */ /* XXX why unconditionally acquire creds? */ if (!svcauth_gss_acquire_cred()) svcauth_gss_return(AUTH_FAILED); if (!svcauth_gss_accept_sec_context(req, gd, &gr)) svcauth_gss_return(AUTH_REJECTEDCRED); if (!svcauth_gss_nextverf(req, gd, htonl(gr.gr_win))) { /* XXX check */ gss_release_buffer(&min_stat, &gr.gr_token); mem_free(gr.gr_ctx.value, 0); svcauth_gss_return(AUTH_FAILED); } *no_dispatch = true; call_stat = svc_sendreply(req->rq_xprt, req, (xdrproc_t) xdr_rpc_gss_init_res, (caddr_t) &gr); /* XXX */ gss_release_buffer(&min_stat, &gr.gr_token); gss_release_buffer(&min_stat, &gd->checksum); mem_free(gr.gr_ctx.value, 0); if (!call_stat) svcauth_gss_return(AUTH_FAILED); if (gr.gr_major == GSS_S_COMPLETE) { gd->established = true; if (!gd_hashed) { /* krb5 pac -- try all that apply */ gss_buffer_desc attr, display_buffer; /* completely generic */ int auth = 1, comp = 0, more = -1; memset(&gd->pac.ms_pac, 0, sizeof(gss_buffer_desc)); memset(&display_buffer, 0, sizeof(gss_buffer_desc)); /* MS AD */ attr.value = "urn:mspac:"; attr.length = 10; gr.gr_major = gss_get_name_attribute(&gr.gr_minor, gd->client_name, &attr, &auth, &comp, &gd->pac.ms_pac, &display_buffer, &more); if (gr.gr_major == GSS_S_COMPLETE) { /* dont need it */ gss_release_buffer(&gr.gr_minor, &display_buffer); gd->flags |= SVC_RPC_GSS_FLAG_MSPAC; } (void)authgss_ctx_hash_set(gd); } } break; /* XXX next 2 cases: is it correct to leave gd in cache * after a validate or verf failure ? */ case RPCSEC_GSS_DATA: call_stat = svcauth_gss_validate(req, gd, msg); switch (call_stat) { default: svcauth_gss_return(RPCSEC_GSS_CREDPROBLEM); case 0: break; } if (!svcauth_gss_nextverf(req, gd, htonl(gc->gc_seq))) svcauth_gss_return(AUTH_FAILED); break; case RPCSEC_GSS_DESTROY: if (req->rq_proc != NULLPROC) svcauth_gss_return(AUTH_FAILED); /* XXX ? */ if (svcauth_gss_validate(req, gd, msg)) svcauth_gss_return(RPCSEC_GSS_CREDPROBLEM); if (!svcauth_gss_nextverf(req, gd, htonl(gc->gc_seq))) svcauth_gss_return(AUTH_FAILED); *no_dispatch = true; (void)authgss_ctx_hash_del(gd); /* avoid lock order reversal gd->lock, xprt->xp_lock */ mutex_unlock(&gd->lock); gd_locked = false; call_stat = svc_sendreply(req->rq_xprt, req, (xdrproc_t) xdr_void, (caddr_t) NULL); /* We acquired a reference on gd with authgss_ctx_hash_get * call. Time to release the reference as we don't need * gd anymore. */ unref_svc_rpc_gss_data(gd, SVC_RPC_GSS_FLAG_NONE); req->rq_auth = &svc_auth_none; break; default: svcauth_gss_return(AUTH_REJECTEDCRED); break; } svcauth_gss_return(AUTH_OK); }
static int gssattr_dynacl_mask( void *priv, Operation *op, Entry *target, AttributeDescription *desc, struct berval *val, int nmatch, regmatch_t *matches, slap_access_t *grant, slap_access_t *deny ) { gssattr_t *gssattr = (gssattr_t *)priv; sasl_conn_t *sasl_ctx = op->o_conn->c_sasl_authctx; gss_name_t gss_name = GSS_C_NO_NAME; OM_uint32 major, minor; int more = -1; int authenticated, complete; gss_buffer_desc attr = GSS_C_EMPTY_BUFFER; int granted = 0; ACL_INVALIDATE( *deny ); if ( sasl_ctx == NULL || sasl_getprop( sasl_ctx, SASL_GSS_PEER_NAME, (const void **)&gss_name) != 0 || gss_name == GSS_C_NO_NAME ) { return 0; } attr.length = gssattr->gssattr_name.bv_len; attr.value = gssattr->gssattr_name.bv_val; while ( more != 0 ) { AclRegexMatches amatches = { 0 }; gss_buffer_desc gss_value = GSS_C_EMPTY_BUFFER; gss_buffer_desc gss_display_value = GSS_C_EMPTY_BUFFER; struct berval bv_value; major = gss_get_name_attribute( &minor, gss_name, &attr, &authenticated, &complete, &gss_value, &gss_display_value, &more ); if ( GSS_ERROR( major ) ) { break; } else if ( authenticated == 0 ) { gss_release_buffer( &minor, &gss_value ); gss_release_buffer( &minor, &gss_display_value ); continue; } bv_value.bv_len = gss_value.length; bv_value.bv_val = (char *)gss_value.value; if ( !ber_bvccmp( &gssattr->gssattr_value, '*' ) ) { if ( gssattr->gssattr_style != ACL_STYLE_BASE ) { amatches.dn_count = nmatch; AC_MEMCPY( amatches.dn_data, matches, sizeof( amatches.dn_data ) ); } switch ( gssattr->gssattr_style ) { case ACL_STYLE_REGEX: /* XXX assumes value NUL terminated */ granted = regex_matches( &gssattr->gssattr_value, bv_value.bv_val, &target->e_nname, val, &amatches ); break; case ACL_STYLE_EXPAND: { struct berval bv; char buf[ACL_BUF_SIZE]; bv.bv_len = sizeof( buf ) - 1; bv.bv_val = buf; granted = ( acl_string_expand( &bv, &gssattr->gssattr_value, &target->e_nname, val, &amatches ) == 0 ) && ( ber_bvstrcmp( &bv, &bv_value) == 0 ); break; } case ACL_STYLE_BASE: granted = ( ber_bvstrcmp( &gssattr->gssattr_value, &bv_value ) == 0 ); break; default: assert(0); break; } } else { granted = 1; } gss_release_buffer( &minor, &gss_value ); gss_release_buffer( &minor, &gss_display_value ); if ( granted ) { break; } } if ( granted ) { ACL_LVL_ASSIGN_WRITE( *grant ); } return 0; }
NTSTATUS gssapi_obtain_pac_blob(TALLOC_CTX *mem_ctx, gss_ctx_id_t gssapi_context, gss_name_t gss_client_name, DATA_BLOB *pac_blob) { NTSTATUS status; OM_uint32 gss_maj, gss_min; #ifdef HAVE_GSS_GET_NAME_ATTRIBUTE /* * gss_get_name_attribute() in MIT krb5 1.10.0 can return unintialized pac_display_buffer * and later gss_release_buffer() will crash on attempting to release it. * * So always initialize the buffer descriptors. * * See following links for more details: * http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=658514 * http://krbdev.mit.edu/rt/Ticket/Display.html?user=guest&pass=guest&id=7087 */ gss_buffer_desc pac_buffer = { .value = NULL, .length = 0 }; gss_buffer_desc pac_display_buffer = { .value = NULL, .length = 0 }; gss_buffer_desc pac_name = { .value = discard_const("urn:mspac:"), .length = sizeof("urn:mspac:")-1 }; int more = -1; int authenticated = false; int complete = false; gss_maj = gss_get_name_attribute( &gss_min, gss_client_name, &pac_name, &authenticated, &complete, &pac_buffer, &pac_display_buffer, &more); if (gss_maj != 0) { gss_OID oid = discard_const(gss_mech_krb5); DBG_NOTICE("obtaining PAC via GSSAPI gss_get_name_attribute " "failed: %s\n", gssapi_error_string(mem_ctx, gss_maj, gss_min, oid)); return NT_STATUS_ACCESS_DENIED; } else if (authenticated && complete) { /* The PAC blob is returned directly */ *pac_blob = data_blob_talloc(mem_ctx, pac_buffer.value, pac_buffer.length); if (!pac_blob->data) { status = NT_STATUS_NO_MEMORY; } else { status = NT_STATUS_OK; } gss_maj = gss_release_buffer(&gss_min, &pac_buffer); gss_maj = gss_release_buffer(&gss_min, &pac_display_buffer); return status; } else { DEBUG(0, ("obtaining PAC via GSSAPI failed: authenticated: %s, complete: %s, more: %s\n", authenticated ? "true" : "false", complete ? "true" : "false", more ? "true" : "false")); return NT_STATUS_ACCESS_DENIED; } #elif defined(HAVE_GSS_INQUIRE_SEC_CONTEXT_BY_OID) gss_OID_desc pac_data_oid = { .elements = discard_const(EXTRACT_PAC_AUTHZ_DATA_FROM_SEC_CONTEXT_OID), .length = EXTRACT_PAC_AUTHZ_DATA_FROM_SEC_CONTEXT_OID_LENGTH }; gss_buffer_set_t set = GSS_C_NO_BUFFER_SET; /* If we didn't have the routine to get a verified, validated * PAC (supplied only by MIT at the time of writing), then try * with the Heimdal OID (fetches the PAC directly and always * validates) */ gss_maj = gss_inquire_sec_context_by_oid( &gss_min, gssapi_context, &pac_data_oid, &set); /* First check for the error MIT gives for an unknown OID */ if (gss_maj == GSS_S_UNAVAILABLE) { DEBUG(1, ("unable to obtain a PAC against this GSSAPI library. " "GSSAPI secured connections are available only with Heimdal or MIT Kerberos >= 1.8\n")); } else if (gss_maj != 0) { DEBUG(2, ("obtaining PAC via GSSAPI gss_inqiure_sec_context_by_oid (Heimdal OID) failed: %s\n", gssapi_error_string(mem_ctx, gss_maj, gss_min, gss_mech_krb5))); } else { if (set == GSS_C_NO_BUFFER_SET) { DEBUG(0, ("gss_inquire_sec_context_by_oid returned unknown " "data in results.\n")); return NT_STATUS_INTERNAL_ERROR; } /* The PAC blob is returned directly */ *pac_blob = data_blob_talloc(mem_ctx, set->elements[0].value, set->elements[0].length); if (!pac_blob->data) { status = NT_STATUS_NO_MEMORY; } else { status = NT_STATUS_OK; } gss_maj = gss_release_buffer_set(&gss_min, &set); return status; } #else DEBUG(1, ("unable to obtain a PAC against this GSSAPI library. " "GSSAPI secured connections are available only with Heimdal or MIT Kerberos >= 1.8\n")); #endif return NT_STATUS_ACCESS_DENIED; } NTSTATUS gssapi_get_session_key(TALLOC_CTX *mem_ctx, gss_ctx_id_t gssapi_context, DATA_BLOB *session_key, uint32_t *keytype) { OM_uint32 gss_min, gss_maj; gss_buffer_set_t set = GSS_C_NO_BUFFER_SET; gss_maj = gss_inquire_sec_context_by_oid( &gss_min, gssapi_context, &gse_sesskey_inq_oid, &set); if (gss_maj) { DEBUG(0, ("gss_inquire_sec_context_by_oid failed [%s]\n", gssapi_error_string(mem_ctx, gss_maj, gss_min, discard_const_p(struct gss_OID_desc_struct, gss_mech_krb5)))); return NT_STATUS_NO_USER_SESSION_KEY; } if ((set == GSS_C_NO_BUFFER_SET) || (set->count == 0)) { #ifdef HAVE_GSSKRB5_GET_SUBKEY krb5_keyblock *subkey; gss_maj = gsskrb5_get_subkey(&gss_min, gssapi_context, &subkey); if (gss_maj != 0) { DEBUG(1, ("NO session key for this mech\n")); return NT_STATUS_NO_USER_SESSION_KEY; } if (session_key) { *session_key = data_blob_talloc(mem_ctx, KRB5_KEY_DATA(subkey), KRB5_KEY_LENGTH(subkey)); } if (keytype) { *keytype = KRB5_KEY_TYPE(subkey); } krb5_free_keyblock(NULL /* should be krb5_context */, subkey); return NT_STATUS_OK; #else DEBUG(0, ("gss_inquire_sec_context_by_oid didn't return any session key (and no alternative method available)\n")); return NT_STATUS_NO_USER_SESSION_KEY; #endif } if (session_key) { *session_key = data_blob_talloc(mem_ctx, set->elements[0].value, set->elements[0].length); } if (keytype) { int diflen, i; const uint8_t *p; *keytype = 0; if (set->count < 2) { #ifdef HAVE_GSSKRB5_GET_SUBKEY krb5_keyblock *subkey; gss_maj = gsskrb5_get_subkey(&gss_min, gssapi_context, &subkey); if (gss_maj == 0) { *keytype = KRB5_KEY_TYPE(subkey); krb5_free_keyblock(NULL /* should be krb5_context */, subkey); } #endif gss_maj = gss_release_buffer_set(&gss_min, &set); return NT_STATUS_OK; } else if (memcmp(set->elements[1].value, gse_sesskeytype_oid.elements, gse_sesskeytype_oid.length) != 0) { /* Perhaps a non-krb5 session key */ gss_maj = gss_release_buffer_set(&gss_min, &set); return NT_STATUS_OK; } p = (const uint8_t *)set->elements[1].value + gse_sesskeytype_oid.length; diflen = set->elements[1].length - gse_sesskeytype_oid.length; if (diflen <= 0) { gss_maj = gss_release_buffer_set(&gss_min, &set); return NT_STATUS_INVALID_PARAMETER; } for (i = 0; i < diflen; i++) { *keytype = (*keytype << 7) | (p[i] & 0x7f); if (i + 1 != diflen && (p[i] & 0x80) == 0) { gss_maj = gss_release_buffer_set(&gss_min, &set); return NT_STATUS_INVALID_PARAMETER; } } } gss_maj = gss_release_buffer_set(&gss_min, &set); return NT_STATUS_OK; } char *gssapi_error_string(TALLOC_CTX *mem_ctx, OM_uint32 maj_stat, OM_uint32 min_stat, const gss_OID mech) { OM_uint32 disp_min_stat, disp_maj_stat; gss_buffer_desc maj_error_message; gss_buffer_desc min_error_message; char *maj_error_string, *min_error_string; OM_uint32 msg_ctx = 0; char *ret; maj_error_message.value = NULL; min_error_message.value = NULL; maj_error_message.length = 0; min_error_message.length = 0; disp_maj_stat = gss_display_status(&disp_min_stat, maj_stat, GSS_C_GSS_CODE, mech, &msg_ctx, &maj_error_message); if (disp_maj_stat != 0) { maj_error_message.value = NULL; maj_error_message.length = 0; } disp_maj_stat = gss_display_status(&disp_min_stat, min_stat, GSS_C_MECH_CODE, mech, &msg_ctx, &min_error_message); if (disp_maj_stat != 0) { min_error_message.value = NULL; min_error_message.length = 0; } maj_error_string = talloc_strndup(mem_ctx, (char *)maj_error_message.value, maj_error_message.length); min_error_string = talloc_strndup(mem_ctx, (char *)min_error_message.value, min_error_message.length); ret = talloc_asprintf(mem_ctx, "%s: %s", maj_error_string, min_error_string); talloc_free(maj_error_string); talloc_free(min_error_string); gss_release_buffer(&disp_min_stat, &maj_error_message); gss_release_buffer(&disp_min_stat, &min_error_message); return ret; }