/* Unix SMB/CIFS implementation. ads sasl code Copyright (C) Andrew Tridgell 2001 This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ #include "includes.h" #include "../libcli/auth/spnego.h" #include "auth/gensec/gensec.h" #include "auth_generic.h" #include "ads.h" #include "smb_krb5.h" #include "system/gssapi.h" #include "lib/param/loadparm.h" #ifdef HAVE_LDAP static ADS_STATUS ads_sasl_ntlmssp_wrap(ADS_STRUCT *ads, uint8 *buf, uint32 len) { struct gensec_security *gensec_security = talloc_get_type_abort(ads->ldap.wrap_private_data, struct gensec_security); NTSTATUS nt_status; DATA_BLOB unwrapped, wrapped; TALLOC_CTX *frame = talloc_stackframe(); unwrapped = data_blob_const(buf, len); nt_status = gensec_wrap(gensec_security, frame, &unwrapped, &wrapped); if (!NT_STATUS_IS_OK(nt_status)) { TALLOC_FREE(frame); return ADS_ERROR_NT(nt_status); } if ((ads->ldap.out.size - 4) < wrapped.length) { return ADS_ERROR_NT(NT_STATUS_INTERNAL_ERROR); } /* copy the wrapped blob to the right location */ memcpy(ads->ldap.out.buf + 4, wrapped.data, wrapped.length); /* set how many bytes must be written to the underlying socket */ ads->ldap.out.left = 4 + wrapped.length; TALLOC_FREE(frame); return ADS_SUCCESS; } static ADS_STATUS ads_sasl_ntlmssp_unwrap(ADS_STRUCT *ads) { struct gensec_security *gensec_security = talloc_get_type_abort(ads->ldap.wrap_private_data, struct gensec_security); NTSTATUS nt_status; DATA_BLOB unwrapped, wrapped; TALLOC_CTX *frame = talloc_stackframe(); wrapped = data_blob_const(ads->ldap.in.buf + 4, ads->ldap.in.ofs - 4); nt_status = gensec_unwrap(gensec_security, frame, &wrapped, &unwrapped); if (!NT_STATUS_IS_OK(nt_status)) { TALLOC_FREE(frame); return ADS_ERROR_NT(nt_status); } if (wrapped.length < unwrapped.length) { TALLOC_FREE(frame); return ADS_ERROR_NT(NT_STATUS_INTERNAL_ERROR); } /* copy the wrapped blob to the right location */ memcpy(ads->ldap.in.buf + 4, unwrapped.data, unwrapped.length); /* set how many bytes must be written to the underlying socket */ ads->ldap.in.left = unwrapped.length; ads->ldap.in.ofs = 4; TALLOC_FREE(frame); return ADS_SUCCESS; } static void ads_sasl_ntlmssp_disconnect(ADS_STRUCT *ads) { struct gensec_security *gensec_security = talloc_get_type_abort(ads->ldap.wrap_private_data, struct gensec_security); TALLOC_FREE(gensec_security); ads->ldap.wrap_ops = NULL; ads->ldap.wrap_private_data = NULL; } static const struct ads_saslwrap_ops ads_sasl_ntlmssp_ops = { .name = "ntlmssp", .wrap = ads_sasl_ntlmssp_wrap, .unwrap = ads_sasl_ntlmssp_unwrap, .disconnect = ads_sasl_ntlmssp_disconnect }; /* perform a LDAP/SASL/SPNEGO/NTLMSSP bind (just how many layers can we fit on one socket??) */ static ADS_STATUS ads_sasl_spnego_ntlmssp_bind(ADS_STRUCT *ads) { DATA_BLOB msg1 = data_blob_null; DATA_BLOB blob = data_blob_null; DATA_BLOB blob_in = data_blob_null; DATA_BLOB blob_out = data_blob_null; struct berval cred, *scred = NULL; int rc; NTSTATUS nt_status; ADS_STATUS status; int turn = 1; struct auth_generic_state *auth_generic_state; nt_status = auth_generic_client_prepare(NULL, &auth_generic_state); if (!NT_STATUS_IS_OK(nt_status)) { return ADS_ERROR_NT(nt_status); } if (!NT_STATUS_IS_OK(nt_status = auth_generic_set_username(auth_generic_state, ads->auth.user_name))) { return ADS_ERROR_NT(nt_status); } if (!NT_STATUS_IS_OK(nt_status = auth_generic_set_domain(auth_generic_state, ads->auth.realm))) { return ADS_ERROR_NT(nt_status); } if (!NT_STATUS_IS_OK(nt_status = auth_generic_set_password(auth_generic_state, ads->auth.password))) { return ADS_ERROR_NT(nt_status); } switch (ads->ldap.wrap_type) { case ADS_SASLWRAP_TYPE_SEAL: gensec_want_feature(auth_generic_state->gensec_security, GENSEC_FEATURE_SIGN); gensec_want_feature(auth_generic_state->gensec_security, GENSEC_FEATURE_SEAL); break; case ADS_SASLWRAP_TYPE_SIGN: if (ads->auth.flags & ADS_AUTH_SASL_FORCE) { gensec_want_feature(auth_generic_state->gensec_security, GENSEC_FEATURE_SIGN); } else { /* * windows servers are broken with sign only, * so we need to use seal here too */ gensec_want_feature(auth_generic_state->gensec_security, GENSEC_FEATURE_SIGN); gensec_want_feature(auth_generic_state->gensec_security, GENSEC_FEATURE_SEAL); ads->ldap.wrap_type = ADS_SASLWRAP_TYPE_SEAL; } break; case ADS_SASLWRAP_TYPE_PLAIN: break; } nt_status = auth_generic_client_start(auth_generic_state, GENSEC_OID_NTLMSSP); if (!NT_STATUS_IS_OK(nt_status)) { return ADS_ERROR_NT(nt_status); } blob_in = data_blob_null; do { nt_status = gensec_update(auth_generic_state->gensec_security, talloc_tos(), NULL, blob_in, &blob_out); data_blob_free(&blob_in); if ((NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED) || NT_STATUS_IS_OK(nt_status)) && blob_out.length) { if (turn == 1) { const char *OIDs_ntlm[] = {OID_NTLMSSP, NULL}; /* and wrap it in a SPNEGO wrapper */ msg1 = spnego_gen_negTokenInit(talloc_tos(), OIDs_ntlm, &blob_out, NULL); } else { /* wrap it in SPNEGO */ msg1 = spnego_gen_auth(talloc_tos(), blob_out); } data_blob_free(&blob_out); cred.bv_val = (char *)msg1.data; cred.bv_len = msg1.length; scred = NULL; rc = ldap_sasl_bind_s(ads->ldap.ld, NULL, "GSS-SPNEGO", &cred, NULL, NULL, &scred); data_blob_free(&msg1); if ((rc != LDAP_SASL_BIND_IN_PROGRESS) && (rc != 0)) { if (scred) { ber_bvfree(scred); } TALLOC_FREE(auth_generic_state); return ADS_ERROR(rc); } if (scred) { blob = data_blob(scred->bv_val, scred->bv_len); ber_bvfree(scred); } else { blob = data_blob_null; } } else { TALLOC_FREE(auth_generic_state); data_blob_free(&blob_out); return ADS_ERROR_NT(nt_status); } if ((turn == 1) && (rc == LDAP_SASL_BIND_IN_PROGRESS)) { DATA_BLOB tmp_blob = data_blob_null; /* the server might give us back two challenges */ if (!spnego_parse_challenge(talloc_tos(), blob, &blob_in, &tmp_blob)) { TALLOC_FREE(auth_generic_state); data_blob_free(&blob); DEBUG(3,("Failed to parse challenges\n")); return ADS_ERROR_NT(NT_STATUS_INVALID_PARAMETER); } data_blob_free(&tmp_blob); } else if (rc == LDAP_SASL_BIND_IN_PROGRESS) { if (!spnego_parse_auth_response(talloc_tos(), blob, nt_status, OID_NTLMSSP, &blob_in)) { TALLOC_FREE(auth_generic_state); data_blob_free(&blob); DEBUG(3,("Failed to parse auth response\n")); return ADS_ERROR_NT(NT_STATUS_INVALID_PARAMETER); } } data_blob_free(&blob); data_blob_free(&blob_out); turn++; } while (rc == LDAP_SASL_BIND_IN_PROGRESS && !NT_STATUS_IS_OK(nt_status)); if (ads->ldap.wrap_type > ADS_SASLWRAP_TYPE_PLAIN) { uint32_t sig_size = gensec_sig_size(auth_generic_state->gensec_security, 0); ads->ldap.out.max_unwrapped = ADS_SASL_WRAPPING_OUT_MAX_WRAPPED - sig_size; ads->ldap.out.sig_size = sig_size; ads->ldap.in.min_wrapped = ads->ldap.out.sig_size; ads->ldap.in.max_wrapped = ADS_SASL_WRAPPING_IN_MAX_WRAPPED; status = ads_setup_sasl_wrapping(ads, &ads_sasl_ntlmssp_ops, auth_generic_state->gensec_security); if (!ADS_ERR_OK(status)) { DEBUG(0, ("ads_setup_sasl_wrapping() failed: %s\n", ads_errstr(status))); TALLOC_FREE(auth_generic_state); return status; } /* Only keep the gensec_security element around long-term */ talloc_steal(NULL, auth_generic_state->gensec_security); } TALLOC_FREE(auth_generic_state); return ADS_ERROR(rc); } #ifdef HAVE_KRB5 static ADS_STATUS ads_init_gssapi_cred(ADS_STRUCT *ads, gss_cred_id_t *cred) { ADS_STATUS status; krb5_context kctx; krb5_error_code kerr; krb5_ccache kccache = NULL; uint32_t maj, min; *cred = GSS_C_NO_CREDENTIAL; if (!ads->auth.ccache_name) { return ADS_SUCCESS; } kerr = krb5_init_context(&kctx); if (kerr) { return ADS_ERROR_KRB5(kerr); } #ifdef HAVE_GSS_KRB5_IMPORT_CRED kerr = krb5_cc_resolve(kctx, ads->auth.ccache_name, &kccache); if (kerr) { status = ADS_ERROR_KRB5(kerr); goto done; } maj = gss_krb5_import_cred(&min, kccache, NULL, NULL, cred); if (maj != GSS_S_COMPLETE) { status = ADS_ERROR_GSS(maj, min); goto done; } #else /* We need to fallback to overriding the default creds. * This operation is not thread safe as it changes the process * environment variable, but we do not have any better option * with older kerberos libraries */ { const char *oldccname = NULL; oldccname = getenv("KRB5CCNAME"); setenv("KRB5CCNAME", ads->auth.ccache_name, 1); maj = gss_acquire_cred(&min, GSS_C_NO_NAME, GSS_C_INDEFINITE, NULL, GSS_C_INITIATE, cred, NULL, NULL); if (oldccname) { setenv("KRB5CCNAME", oldccname, 1); } else { unsetenv("KRB5CCNAME"); } if (maj != GSS_S_COMPLETE) { status = ADS_ERROR_GSS(maj, min); goto done; } } #endif status = ADS_SUCCESS; done: if (!ADS_ERR_OK(status) && kccache != NULL) { krb5_cc_close(kctx, kccache); } krb5_free_context(kctx); return status; } static ADS_STATUS ads_sasl_gssapi_wrap(ADS_STRUCT *ads, uint8 *buf, uint32 len) { gss_ctx_id_t context_handle = (gss_ctx_id_t)ads->ldap.wrap_private_data; ADS_STATUS status; int gss_rc; uint32 minor_status; gss_buffer_desc unwrapped, wrapped; int conf_req_flag, conf_state; unwrapped.value = buf; unwrapped.length = len; /* for now request sign and seal */ conf_req_flag = (ads->ldap.wrap_type == ADS_SASLWRAP_TYPE_SEAL); gss_rc = gss_wrap(&minor_status, context_handle, conf_req_flag, GSS_C_QOP_DEFAULT, &unwrapped, &conf_state, &wrapped); status = ADS_ERROR_GSS(gss_rc, minor_status); if (!ADS_ERR_OK(status)) return status; if (conf_req_flag && conf_state == 0) { return ADS_ERROR_NT(NT_STATUS_ACCESS_DENIED); } if ((ads->ldap.out.size - 4) < wrapped.length) { return ADS_ERROR_NT(NT_STATUS_INTERNAL_ERROR); } /* copy the wrapped blob to the right location */ memcpy(ads->ldap.out.buf + 4, wrapped.value, wrapped.length); /* set how many bytes must be written to the underlying socket */ ads->ldap.out.left = 4 + wrapped.length; gss_release_buffer(&minor_status, &wrapped); return ADS_SUCCESS; } static ADS_STATUS ads_sasl_gssapi_unwrap(ADS_STRUCT *ads) { gss_ctx_id_t context_handle = (gss_ctx_id_t)ads->ldap.wrap_private_data; ADS_STATUS status; int gss_rc; uint32 minor_status; gss_buffer_desc unwrapped, wrapped; int conf_state; wrapped.value = ads->ldap.in.buf + 4; wrapped.length = ads->ldap.in.ofs - 4; gss_rc = gss_unwrap(&minor_status, context_handle, &wrapped, &unwrapped, &conf_state, GSS_C_QOP_DEFAULT); status = ADS_ERROR_GSS(gss_rc, minor_status); if (!ADS_ERR_OK(status)) return status; if (ads->ldap.wrap_type == ADS_SASLWRAP_TYPE_SEAL && conf_state == 0) { return ADS_ERROR_NT(NT_STATUS_ACCESS_DENIED); } if (wrapped.length < unwrapped.length) { return ADS_ERROR_NT(NT_STATUS_INTERNAL_ERROR); } /* copy the wrapped blob to the right location */ memcpy(ads->ldap.in.buf + 4, unwrapped.value, unwrapped.length); /* set how many bytes must be written to the underlying socket */ ads->ldap.in.left = unwrapped.length; ads->ldap.in.ofs = 4; gss_release_buffer(&minor_status, &unwrapped); return ADS_SUCCESS; } static void ads_sasl_gssapi_disconnect(ADS_STRUCT *ads) { gss_ctx_id_t context_handle = (gss_ctx_id_t)ads->ldap.wrap_private_data; uint32 minor_status; gss_delete_sec_context(&minor_status, &context_handle, GSS_C_NO_BUFFER); ads->ldap.wrap_ops = NULL; ads->ldap.wrap_private_data = NULL; } static const struct ads_saslwrap_ops ads_sasl_gssapi_ops = { .name = "gssapi", .wrap = ads_sasl_gssapi_wrap, .unwrap = ads_sasl_gssapi_unwrap, .disconnect = ads_sasl_gssapi_disconnect }; /* 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; } #endif /* HAVE_KRB5 */ #ifdef HAVE_KRB5 struct ads_service_principal { char *string; #ifdef HAVE_KRB5 gss_name_t name; #endif }; static void ads_free_service_principal(struct ads_service_principal *p) { SAFE_FREE(p->string); #ifdef HAVE_KRB5 if (p->name) { uint32 minor_status; gss_release_name(&minor_status, &p->name); } #endif ZERO_STRUCTP(p); } static ADS_STATUS ads_guess_service_principal(ADS_STRUCT *ads, char **returned_principal) { char *princ = NULL; if (ads->server.realm && ads->server.ldap_server) { char *server, *server_realm; server = SMB_STRDUP(ads->server.ldap_server); server_realm = SMB_STRDUP(ads->server.realm); if (!server || !server_realm) { SAFE_FREE(server); SAFE_FREE(server_realm); return ADS_ERROR(LDAP_NO_MEMORY); } if (!strlower_m(server)) { SAFE_FREE(server); SAFE_FREE(server_realm); return ADS_ERROR(LDAP_NO_MEMORY); } if (!strupper_m(server_realm)) { SAFE_FREE(server); SAFE_FREE(server_realm); return ADS_ERROR(LDAP_NO_MEMORY); } if (asprintf(&princ, "ldap/%s@%s", server, server_realm) == -1) { SAFE_FREE(server); SAFE_FREE(server_realm); return ADS_ERROR(LDAP_NO_MEMORY); } SAFE_FREE(server); SAFE_FREE(server_realm); if (!princ) { return ADS_ERROR(LDAP_NO_MEMORY); } } else if (ads->config.realm && ads->config.ldap_server_name) { char *server, *server_realm; server = SMB_STRDUP(ads->config.ldap_server_name); server_realm = SMB_STRDUP(ads->config.realm); if (!server || !server_realm) { SAFE_FREE(server); SAFE_FREE(server_realm); return ADS_ERROR(LDAP_NO_MEMORY); } if (!strlower_m(server)) { SAFE_FREE(server); SAFE_FREE(server_realm); return ADS_ERROR(LDAP_NO_MEMORY); } if (!strupper_m(server_realm)) { SAFE_FREE(server); SAFE_FREE(server_realm); return ADS_ERROR(LDAP_NO_MEMORY); } if (asprintf(&princ, "ldap/%s@%s", server, server_realm) == -1) { SAFE_FREE(server); SAFE_FREE(server_realm); return ADS_ERROR(LDAP_NO_MEMORY); } SAFE_FREE(server); SAFE_FREE(server_realm); if (!princ) { return ADS_ERROR(LDAP_NO_MEMORY); } } if (!princ) { return ADS_ERROR(LDAP_PARAM_ERROR); } *returned_principal = princ; return ADS_SUCCESS; } static ADS_STATUS ads_generate_service_principal(ADS_STRUCT *ads, const char *given_principal, struct ads_service_principal *p) { ADS_STATUS status; #ifdef HAVE_KRB5 gss_buffer_desc input_name; /* GSS_KRB5_NT_PRINCIPAL_NAME */ gss_OID_desc nt_principal = {10, discard_const_p(char, "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02\x01")}; uint32 minor_status; int gss_rc; #endif ZERO_STRUCTP(p); /* I've seen a child Windows 2000 domain not send the principal name back in the first round of the SASL bind reply. So we guess based on server name and realm. --jerry */ /* Also try best guess when we get the w2k8 ignore principal back, or when we are configured to ignore it - gd, abartlet */ if (!lp_client_use_spnego_principal() || !given_principal || strequal(given_principal, ADS_IGNORE_PRINCIPAL)) { status = ads_guess_service_principal(ads, &p->string); if (!ADS_ERR_OK(status)) { return status; } } else { p->string = SMB_STRDUP(given_principal); if (!p->string) { return ADS_ERROR(LDAP_NO_MEMORY); } } #ifdef HAVE_KRB5 input_name.value = p->string; input_name.length = strlen(p->string); gss_rc = gss_import_name(&minor_status, &input_name, &nt_principal, &p->name); if (gss_rc) { ads_free_service_principal(p); return ADS_ERROR_GSS(gss_rc, minor_status); } #endif return ADS_SUCCESS; } /* perform a LDAP/SASL/SPNEGO/KRB5 bind */ static ADS_STATUS ads_sasl_spnego_rawkrb5_bind(ADS_STRUCT *ads, const char *principal) { DATA_BLOB blob = data_blob_null; struct berval cred, *scred = NULL; DATA_BLOB session_key = data_blob_null; int rc; if (ads->ldap.wrap_type > ADS_SASLWRAP_TYPE_PLAIN) { return ADS_ERROR_NT(NT_STATUS_NOT_SUPPORTED); } rc = spnego_gen_krb5_negTokenInit(talloc_tos(), principal, ads->auth.time_offset, &blob, &session_key, 0, ads->auth.ccache_name, &ads->auth.tgs_expire); if (rc) { return ADS_ERROR_KRB5(rc); } /* now send the auth packet and we should be done */ cred.bv_val = (char *)blob.data; cred.bv_len = blob.length; rc = ldap_sasl_bind_s(ads->ldap.ld, NULL, "GSS-SPNEGO", &cred, NULL, NULL, &scred); data_blob_free(&blob); data_blob_free(&session_key); if(scred) ber_bvfree(scred); return ADS_ERROR(rc); } static ADS_STATUS ads_sasl_spnego_krb5_bind(ADS_STRUCT *ads, struct ads_service_principal *p) { #ifdef HAVE_KRB5 /* * we only use the gsskrb5 based implementation * when sasl sign or seal is requested. * * This has the following reasons: * - it's likely that the gssapi krb5 mech implementation * doesn't support to negotiate plain connections * - the ads_sasl_spnego_rawkrb5_bind is more robust * against clock skew errors */ if (ads->ldap.wrap_type > ADS_SASLWRAP_TYPE_PLAIN) { return ads_sasl_spnego_gsskrb5_bind(ads, p->name); } #endif return ads_sasl_spnego_rawkrb5_bind(ads, p->string); } #endif /* HAVE_KRB5 */ /* this performs a SASL/SPNEGO bind */ static ADS_STATUS ads_sasl_spnego_bind(ADS_STRUCT *ads) { struct berval *scred=NULL; int rc, i; ADS_STATUS status; DATA_BLOB blob; char *given_principal = NULL; char *OIDs[ASN1_MAX_OIDS]; #ifdef HAVE_KRB5 bool got_kerberos_mechanism = False; #endif rc = ldap_sasl_bind_s(ads->ldap.ld, NULL, "GSS-SPNEGO", NULL, NULL, NULL, &scred); if (rc != LDAP_SASL_BIND_IN_PROGRESS) { status = ADS_ERROR(rc); goto failed; } blob = data_blob(scred->bv_val, scred->bv_len); ber_bvfree(scred); #if 0 file_save("sasl_spnego.dat", blob.data, blob.length); #endif /* the server sent us the first part of the SPNEGO exchange in the negprot reply */ if (!spnego_parse_negTokenInit(talloc_tos(), blob, OIDs, &given_principal, NULL) || OIDs[0] == NULL) { data_blob_free(&blob); status = ADS_ERROR(LDAP_OPERATIONS_ERROR); goto failed; } data_blob_free(&blob); /* make sure the server understands kerberos */ for (i=0;OIDs[i];i++) { DEBUG(3,("ads_sasl_spnego_bind: got OID=%s\n", OIDs[i])); #ifdef HAVE_KRB5 if (strcmp(OIDs[i], OID_KERBEROS5_OLD) == 0 || strcmp(OIDs[i], OID_KERBEROS5) == 0) { got_kerberos_mechanism = True; } #endif talloc_free(OIDs[i]); } DEBUG(3,("ads_sasl_spnego_bind: got server principal name = %s\n", given_principal)); #ifdef HAVE_KRB5 if (!(ads->auth.flags & ADS_AUTH_DISABLE_KERBEROS) && got_kerberos_mechanism) { struct ads_service_principal p; status = ads_generate_service_principal(ads, given_principal, &p); TALLOC_FREE(given_principal); if (!ADS_ERR_OK(status)) { return status; } status = ads_sasl_spnego_krb5_bind(ads, &p); if (ADS_ERR_OK(status)) { ads_free_service_principal(&p); return status; } DEBUG(10,("ads_sasl_spnego_krb5_bind failed with: %s, " "calling kinit\n", ads_errstr(status))); status = ADS_ERROR_KRB5(ads_kinit_password(ads)); if (ADS_ERR_OK(status)) { status = ads_sasl_spnego_krb5_bind(ads, &p); if (!ADS_ERR_OK(status)) { DEBUG(0,("kinit succeeded but " "ads_sasl_spnego_krb5_bind failed: %s\n", ads_errstr(status))); } } ads_free_service_principal(&p); /* only fallback to NTLMSSP if allowed */ if (ADS_ERR_OK(status) || !(ads->auth.flags & ADS_AUTH_ALLOW_NTLMSSP)) { return status; } } else #endif { TALLOC_FREE(given_principal); } /* lets do NTLMSSP ... this has the big advantage that we don't need to sync clocks, and we don't rely on special versions of the krb5 library for HMAC_MD4 encryption */ return ads_sasl_spnego_ntlmssp_bind(ads); failed: return status; } #ifdef HAVE_KRB5 #define MAX_GSS_PASSES 3 /* 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 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 req_flags, ret_flags; int conf_state; struct berval cred; struct berval *scred = NULL; int i=0; int gss_rc, rc; uint8 *p; uint32 max_msg_size = ADS_SASL_WRAPPING_OUT_MAX_WRAPPED; uint8 wrap_type = ADS_SASLWRAP_TYPE_PLAIN; ADS_STATUS status; 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 *)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 & ads->ldap.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", ads->ldap.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 *)output_token.value; RSIVAL(p,0,max_msg_size); SCVAL(p,0,ads->ldap.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 (ads->ldap.wrap_type > ADS_SASLWRAP_TYPE_PLAIN) { 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; } 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; } static ADS_STATUS ads_sasl_gssapi_bind(ADS_STRUCT *ads) { ADS_STATUS status; struct ads_service_principal p; status = ads_generate_service_principal(ads, NULL, &p); if (!ADS_ERR_OK(status)) { return status; } status = ads_sasl_gssapi_do_bind(ads, p.name); if (ADS_ERR_OK(status)) { ads_free_service_principal(&p); return status; } DEBUG(10,("ads_sasl_gssapi_do_bind failed with: %s, " "calling kinit\n", ads_errstr(status))); status = ADS_ERROR_KRB5(ads_kinit_password(ads)); if (ADS_ERR_OK(status)) { status = ads_sasl_gssapi_do_bind(ads, p.name); } ads_free_service_principal(&p); return status; } #endif /* HAVE_KRB5 */ /* mapping between SASL mechanisms and functions */ static struct { const char *name; ADS_STATUS (*fn)(ADS_STRUCT *); } sasl_mechanisms[] = { {"GSS-SPNEGO", ads_sasl_spnego_bind}, #ifdef HAVE_KRB5 {"GSSAPI", ads_sasl_gssapi_bind}, /* doesn't work with .NET RC1. No idea why */ #endif {NULL, NULL} }; ADS_STATUS ads_sasl_bind(ADS_STRUCT *ads) { const char *attrs[] = {"supportedSASLMechanisms", NULL}; char **values; ADS_STATUS status; int i, j; LDAPMessage *res; /* get a list of supported SASL mechanisms */ status = ads_do_search(ads, "", LDAP_SCOPE_BASE, "(objectclass=*)", attrs, &res); if (!ADS_ERR_OK(status)) return status; values = ldap_get_values(ads->ldap.ld, res, "supportedSASLMechanisms"); if (ads->auth.flags & ADS_AUTH_SASL_SEAL) { ads->ldap.wrap_type = ADS_SASLWRAP_TYPE_SEAL; } else if (ads->auth.flags & ADS_AUTH_SASL_SIGN) { ads->ldap.wrap_type = ADS_SASLWRAP_TYPE_SIGN; } else { ads->ldap.wrap_type = ADS_SASLWRAP_TYPE_PLAIN; } /* try our supported mechanisms in order */ for (i=0;sasl_mechanisms[i].name;i++) { /* see if the server supports it */ for (j=0;values && values[j];j++) { if (strcmp(values[j], sasl_mechanisms[i].name) == 0) { DEBUG(4,("Found SASL mechanism %s\n", values[j])); retry: status = sasl_mechanisms[i].fn(ads); if (status.error_type == ENUM_ADS_ERROR_LDAP && status.err.rc == LDAP_STRONG_AUTH_REQUIRED && ads->ldap.wrap_type == ADS_SASLWRAP_TYPE_PLAIN) { DEBUG(3,("SASL bin got LDAP_STRONG_AUTH_REQUIRED " "retrying with signing enabled\n")); ads->ldap.wrap_type = ADS_SASLWRAP_TYPE_SIGN; goto retry; } ldap_value_free(values); ldap_msgfree(res); return status; } } } ldap_value_free(values); ldap_msgfree(res); return ADS_ERROR(LDAP_AUTH_METHOD_NOT_SUPPORTED); } #endif /* HAVE_LDAP */