/* this performs a SASL/gssapi bind we avoid using cyrus-sasl to make Samba more robust. cyrus-sasl is very dependent on correctly configured DNS whereas this routine is much less fragile see RFC2078 and RFC2222 for details */ static ADS_STATUS ads_sasl_gssapi_do_bind(ADS_STRUCT *ads, const gss_name_t serv_name) { uint32_t minor_status; gss_cred_id_t gss_cred = GSS_C_NO_CREDENTIAL; gss_ctx_id_t context_handle = GSS_C_NO_CONTEXT; gss_OID mech_type = GSS_C_NULL_OID; gss_buffer_desc output_token, input_token; uint32_t req_flags, ret_flags; int conf_state; struct berval cred; struct berval *scred = NULL; int i=0; int gss_rc, rc; uint8_t *p; uint32_t max_msg_size = ADS_SASL_WRAPPING_OUT_MAX_WRAPPED; uint8_t wrap_type = ADS_SASLWRAP_TYPE_PLAIN; ADS_STATUS status; struct ads_saslwrap *wrap = &ads->ldap_wrap_data; input_token.value = NULL; input_token.length = 0; status = ads_init_gssapi_cred(ads, &gss_cred); if (!ADS_ERR_OK(status)) { goto failed; } /* * Note: here we always ask the gssapi for sign and seal * as this is negotiated later after the mutal * authentication */ req_flags = GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG | GSS_C_INTEG_FLAG | GSS_C_CONF_FLAG; for (i=0; i < MAX_GSS_PASSES; i++) { gss_rc = gss_init_sec_context(&minor_status, gss_cred, &context_handle, serv_name, mech_type, req_flags, 0, NULL, &input_token, NULL, &output_token, &ret_flags, NULL); if (scred) { ber_bvfree(scred); scred = NULL; } if (gss_rc && gss_rc != GSS_S_CONTINUE_NEEDED) { status = ADS_ERROR_GSS(gss_rc, minor_status); goto failed; } cred.bv_val = (char *)output_token.value; cred.bv_len = output_token.length; rc = ldap_sasl_bind_s(ads->ldap.ld, NULL, "GSSAPI", &cred, NULL, NULL, &scred); if (rc != LDAP_SASL_BIND_IN_PROGRESS) { status = ADS_ERROR(rc); goto failed; } if (output_token.value) { gss_release_buffer(&minor_status, &output_token); } if (scred) { input_token.value = scred->bv_val; input_token.length = scred->bv_len; } else { input_token.value = NULL; input_token.length = 0; } if (gss_rc == 0) break; } gss_rc = gss_unwrap(&minor_status,context_handle,&input_token,&output_token, &conf_state,NULL); if (scred) { ber_bvfree(scred); scred = NULL; } if (gss_rc) { status = ADS_ERROR_GSS(gss_rc, minor_status); goto failed; } p = (uint8_t *)output_token.value; #if 0 file_save("sasl_gssapi.dat", output_token.value, output_token.length); #endif if (p) { wrap_type = CVAL(p,0); SCVAL(p,0,0); max_msg_size = RIVAL(p,0); } gss_release_buffer(&minor_status, &output_token); if (!(wrap_type & wrap->wrap_type)) { /* * the server doesn't supports the wrap * type we want :-( */ DEBUG(0,("The ldap sasl wrap type doesn't match wanted[%d] server[%d]\n", wrap->wrap_type, wrap_type)); DEBUGADD(0,("You may want to set the 'client ldap sasl wrapping' option\n")); status = ADS_ERROR_NT(NT_STATUS_NOT_SUPPORTED); goto failed; } /* 0x58 is the minimum windows accepts */ if (max_msg_size < 0x58) { max_msg_size = 0x58; } output_token.length = 4; output_token.value = SMB_MALLOC(output_token.length); if (!output_token.value) { output_token.length = 0; status = ADS_ERROR_NT(NT_STATUS_NO_MEMORY); goto failed; } p = (uint8_t *)output_token.value; RSIVAL(p,0,max_msg_size); SCVAL(p,0,wrap->wrap_type); /* * we used to add sprintf("dn:%s", ads->config.bind_path) here. * but using ads->config.bind_path is the wrong! It should be * the DN of the user object! * * w2k3 gives an error when we send an incorrect DN, but sending nothing * is ok and matches the information flow used in GSS-SPNEGO. */ gss_rc = gss_wrap(&minor_status, context_handle,0,GSS_C_QOP_DEFAULT, &output_token, /* used as *input* here. */ &conf_state, &input_token); /* Used as *output* here. */ if (gss_rc) { status = ADS_ERROR_GSS(gss_rc, minor_status); output_token.length = 0; SAFE_FREE(output_token.value); goto failed; } /* We've finished with output_token. */ SAFE_FREE(output_token.value); output_token.length = 0; cred.bv_val = (char *)input_token.value; cred.bv_len = input_token.length; rc = ldap_sasl_bind_s(ads->ldap.ld, NULL, "GSSAPI", &cred, NULL, NULL, &scred); gss_release_buffer(&minor_status, &input_token); status = ADS_ERROR(rc); if (!ADS_ERR_OK(status)) { goto failed; } if (wrap->wrap_type > ADS_SASLWRAP_TYPE_PLAIN) { gss_rc = gss_wrap_size_limit(&minor_status, context_handle, (wrap->wrap_type == ADS_SASLWRAP_TYPE_SEAL), GSS_C_QOP_DEFAULT, max_msg_size, &wrap->out.max_unwrapped); if (gss_rc) { status = ADS_ERROR_GSS(gss_rc, minor_status); goto failed; } wrap->out.sig_size = max_msg_size - wrap->out.max_unwrapped; wrap->in.min_wrapped = 0x2C; /* taken from a capture with LDAP unbind */ wrap->in.max_wrapped = ADS_SASL_WRAPPING_IN_MAX_WRAPPED; status = ads_setup_sasl_wrapping(wrap->wrap_private_data, ads->ldap.ld, &ads_sasl_gssapi_ops, context_handle); if (!ADS_ERR_OK(status)) { DEBUG(0, ("ads_setup_sasl_wrapping() failed: %s\n", ads_errstr(status))); goto failed; } /* make sure we don't free context_handle */ context_handle = GSS_C_NO_CONTEXT; } failed: if (gss_cred != GSS_C_NO_CREDENTIAL) gss_release_cred(&minor_status, &gss_cred); if (context_handle != GSS_C_NO_CONTEXT) gss_delete_sec_context(&minor_status, &context_handle, GSS_C_NO_BUFFER); if(scred) ber_bvfree(scred); return status; }
/* perform a LDAP/SASL/SPNEGO/GSSKRB5 bind */ static ADS_STATUS ads_sasl_spnego_gsskrb5_bind(ADS_STRUCT *ads, const gss_name_t serv_name) { ADS_STATUS status; bool ok; uint32 minor_status; int gss_rc, rc; gss_cred_id_t gss_cred = GSS_C_NO_CREDENTIAL; gss_OID_desc krb5_mech_type = {9, discard_const_p(char, "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02") }; gss_OID mech_type = &krb5_mech_type; gss_OID actual_mech_type = GSS_C_NULL_OID; const char *spnego_mechs[] = {OID_KERBEROS5_OLD, OID_KERBEROS5, OID_NTLMSSP, NULL}; gss_ctx_id_t context_handle = GSS_C_NO_CONTEXT; gss_buffer_desc input_token, output_token; uint32 req_flags, ret_flags; uint32 req_tmp, ret_tmp; DATA_BLOB unwrapped; DATA_BLOB wrapped; struct berval cred, *scred = NULL; status = ads_init_gssapi_cred(ads, &gss_cred); if (!ADS_ERR_OK(status)) { goto failed; } input_token.value = NULL; input_token.length = 0; req_flags = GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG; switch (ads->ldap.wrap_type) { case ADS_SASLWRAP_TYPE_SEAL: req_flags |= GSS_C_INTEG_FLAG | GSS_C_CONF_FLAG; break; case ADS_SASLWRAP_TYPE_SIGN: req_flags |= GSS_C_INTEG_FLAG; break; case ADS_SASLWRAP_TYPE_PLAIN: break; } /* Note: here we explicit ask for the krb5 mech_type */ gss_rc = gss_init_sec_context(&minor_status, gss_cred, &context_handle, serv_name, mech_type, req_flags, 0, NULL, &input_token, &actual_mech_type, &output_token, &ret_flags, NULL); if (gss_rc && gss_rc != GSS_S_CONTINUE_NEEDED) { status = ADS_ERROR_GSS(gss_rc, minor_status); goto failed; } /* * As some gssapi krb5 mech implementations * automaticly add GSS_C_INTEG_FLAG and GSS_C_CONF_FLAG * to req_flags internaly, it's not possible to * use plain or signing only connection via * the gssapi interface. * * Because of this we need to check it the ret_flags * has more flags as req_flags and correct the value * of ads->ldap.wrap_type. * * I ads->auth.flags has ADS_AUTH_SASL_FORCE * we need to give an error. */ req_tmp = req_flags & (GSS_C_INTEG_FLAG | GSS_C_CONF_FLAG); ret_tmp = ret_flags & (GSS_C_INTEG_FLAG | GSS_C_CONF_FLAG); if (req_tmp == ret_tmp) { /* everythings fine... */ } else if (req_flags & GSS_C_CONF_FLAG) { /* * here we wanted sealing but didn't got it * from the gssapi library */ status = ADS_ERROR_NT(NT_STATUS_NOT_SUPPORTED); goto failed; } else if ((req_flags & GSS_C_INTEG_FLAG) && !(ret_flags & GSS_C_INTEG_FLAG)) { /* * here we wanted siging but didn't got it * from the gssapi library */ status = ADS_ERROR_NT(NT_STATUS_NOT_SUPPORTED); goto failed; } else if (ret_flags & GSS_C_CONF_FLAG) { /* * here we didn't want sealing * but the gssapi library forces it * so correct the needed wrap_type if * the caller didn't forced siging only */ if (ads->auth.flags & ADS_AUTH_SASL_FORCE) { status = ADS_ERROR_NT(NT_STATUS_NOT_SUPPORTED); goto failed; } ads->ldap.wrap_type = ADS_SASLWRAP_TYPE_SEAL; req_flags = ret_flags; } else if (ret_flags & GSS_C_INTEG_FLAG) { /* * here we didn't want signing * but the gssapi library forces it * so correct the needed wrap_type if * the caller didn't forced plain */ if (ads->auth.flags & ADS_AUTH_SASL_FORCE) { status = ADS_ERROR_NT(NT_STATUS_NOT_SUPPORTED); goto failed; } ads->ldap.wrap_type = ADS_SASLWRAP_TYPE_SIGN; req_flags = ret_flags; } else { /* * This could (should?) not happen */ status = ADS_ERROR_NT(NT_STATUS_INTERNAL_ERROR); goto failed; } /* and wrap that in a shiny SPNEGO wrapper */ unwrapped = data_blob_const(output_token.value, output_token.length); wrapped = spnego_gen_negTokenInit(talloc_tos(), spnego_mechs, &unwrapped, NULL); gss_release_buffer(&minor_status, &output_token); if (unwrapped.length > wrapped.length) { status = ADS_ERROR_NT(NT_STATUS_NO_MEMORY); goto failed; } cred.bv_val = (char *)wrapped.data; cred.bv_len = wrapped.length; rc = ldap_sasl_bind_s(ads->ldap.ld, NULL, "GSS-SPNEGO", &cred, NULL, NULL, &scred); data_blob_free(&wrapped); if (rc != LDAP_SUCCESS) { status = ADS_ERROR(rc); goto failed; } if (scred) { wrapped = data_blob_const(scred->bv_val, scred->bv_len); } else { wrapped = data_blob_null; } ok = spnego_parse_auth_response(talloc_tos(), wrapped, NT_STATUS_OK, OID_KERBEROS5_OLD, &unwrapped); if (scred) ber_bvfree(scred); if (!ok) { status = ADS_ERROR_NT(NT_STATUS_INVALID_NETWORK_RESPONSE); goto failed; } input_token.value = unwrapped.data; input_token.length = unwrapped.length; /* * As we asked for mutal authentication * we need to pass the servers response * to gssapi */ gss_rc = gss_init_sec_context(&minor_status, gss_cred, &context_handle, serv_name, mech_type, req_flags, 0, NULL, &input_token, &actual_mech_type, &output_token, &ret_flags, NULL); data_blob_free(&unwrapped); if (gss_rc) { status = ADS_ERROR_GSS(gss_rc, minor_status); goto failed; } gss_release_buffer(&minor_status, &output_token); /* * If we the sign and seal options * doesn't match after getting the response * from the server, we don't want to use the connection */ req_tmp = req_flags & (GSS_C_INTEG_FLAG | GSS_C_CONF_FLAG); ret_tmp = ret_flags & (GSS_C_INTEG_FLAG | GSS_C_CONF_FLAG); if (req_tmp != ret_tmp) { /* everythings fine... */ status = ADS_ERROR_NT(NT_STATUS_INVALID_NETWORK_RESPONSE); goto failed; } if (ads->ldap.wrap_type > ADS_SASLWRAP_TYPE_PLAIN) { uint32 max_msg_size = ADS_SASL_WRAPPING_OUT_MAX_WRAPPED; gss_rc = gss_wrap_size_limit(&minor_status, context_handle, (ads->ldap.wrap_type == ADS_SASLWRAP_TYPE_SEAL), GSS_C_QOP_DEFAULT, max_msg_size, &ads->ldap.out.max_unwrapped); if (gss_rc) { status = ADS_ERROR_GSS(gss_rc, minor_status); goto failed; } ads->ldap.out.sig_size = max_msg_size - ads->ldap.out.max_unwrapped; ads->ldap.in.min_wrapped = 0x2C; /* taken from a capture with LDAP unbind */ ads->ldap.in.max_wrapped = max_msg_size; status = ads_setup_sasl_wrapping(ads, &ads_sasl_gssapi_ops, context_handle); if (!ADS_ERR_OK(status)) { DEBUG(0, ("ads_setup_sasl_wrapping() failed: %s\n", ads_errstr(status))); goto failed; } /* make sure we don't free context_handle */ context_handle = GSS_C_NO_CONTEXT; } status = ADS_SUCCESS; failed: if (gss_cred != GSS_C_NO_CREDENTIAL) gss_release_cred(&minor_status, &gss_cred); if (context_handle != GSS_C_NO_CONTEXT) gss_delete_sec_context(&minor_status, &context_handle, GSS_C_NO_BUFFER); return status; }