diff options
Diffstat (limited to 'source3/libads/sasl.c')
-rw-r--r-- | source3/libads/sasl.c | 1127 |
1 files changed, 1127 insertions, 0 deletions
diff --git a/source3/libads/sasl.c b/source3/libads/sasl.c new file mode 100644 index 0000000000..55bc16a1be --- /dev/null +++ b/source3/libads/sasl.c @@ -0,0 +1,1127 @@ +/* + 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" + +#ifdef HAVE_LDAP + +static ADS_STATUS ads_sasl_ntlmssp_wrap(ADS_STRUCT *ads, uint8 *buf, uint32 len) +{ + struct ntlmssp_state *ntlmssp_state = + (struct ntlmssp_state *)ads->ldap.wrap_private_data; + ADS_STATUS status; + NTSTATUS nt_status; + DATA_BLOB sig; + uint8 *dptr = ads->ldap.out.buf + (4 + NTLMSSP_SIG_SIZE); + + /* copy the data to the right location */ + memcpy(dptr, buf, len); + + /* create the signature and may encrypt the data */ + if (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_SEAL) { + nt_status = ntlmssp_seal_packet(ntlmssp_state, + dptr, len, + dptr, len, + &sig); + } else { + nt_status = ntlmssp_sign_packet(ntlmssp_state, + dptr, len, + dptr, len, + &sig); + } + status = ADS_ERROR_NT(nt_status); + if (!ADS_ERR_OK(status)) return status; + + /* copy the signature to the right location */ + memcpy(ads->ldap.out.buf + 4, + sig.data, NTLMSSP_SIG_SIZE); + + data_blob_free(&sig); + + /* set how many bytes must be written to the underlying socket */ + ads->ldap.out.left = 4 + NTLMSSP_SIG_SIZE + len; + + return ADS_SUCCESS; +} + +static ADS_STATUS ads_sasl_ntlmssp_unwrap(ADS_STRUCT *ads) +{ + struct ntlmssp_state *ntlmssp_state = + (struct ntlmssp_state *)ads->ldap.wrap_private_data; + ADS_STATUS status; + NTSTATUS nt_status; + DATA_BLOB sig; + uint8 *dptr = ads->ldap.in.buf + (4 + NTLMSSP_SIG_SIZE); + uint32 dlen = ads->ldap.in.ofs - (4 + NTLMSSP_SIG_SIZE); + + /* wrap the signature into a DATA_BLOB */ + sig = data_blob_const(ads->ldap.in.buf + 4, NTLMSSP_SIG_SIZE); + + /* verify the signature and maybe decrypt the data */ + if (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_SEAL) { + nt_status = ntlmssp_unseal_packet(ntlmssp_state, + dptr, dlen, + dptr, dlen, + &sig); + } else { + nt_status = ntlmssp_check_packet(ntlmssp_state, + dptr, dlen, + dptr, dlen, + &sig); + } + status = ADS_ERROR_NT(nt_status); + if (!ADS_ERR_OK(status)) return status; + + /* set the amount of bytes for the upper layer and set the ofs to the data */ + ads->ldap.in.left = dlen; + ads->ldap.in.ofs = 4 + NTLMSSP_SIG_SIZE; + + return ADS_SUCCESS; +} + +static void ads_sasl_ntlmssp_disconnect(ADS_STRUCT *ads) +{ + struct ntlmssp_state *ntlmssp_state = + (struct ntlmssp_state *)ads->ldap.wrap_private_data; + + ntlmssp_end(&ntlmssp_state); + + 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; + uint32 features = 0; + + struct ntlmssp_state *ntlmssp_state; + + if (!NT_STATUS_IS_OK(nt_status = ntlmssp_client_start(&ntlmssp_state))) { + return ADS_ERROR_NT(nt_status); + } + ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_SIGN; + + if (!NT_STATUS_IS_OK(nt_status = ntlmssp_set_username(ntlmssp_state, ads->auth.user_name))) { + return ADS_ERROR_NT(nt_status); + } + if (!NT_STATUS_IS_OK(nt_status = ntlmssp_set_domain(ntlmssp_state, ads->auth.realm))) { + return ADS_ERROR_NT(nt_status); + } + if (!NT_STATUS_IS_OK(nt_status = ntlmssp_set_password(ntlmssp_state, ads->auth.password))) { + return ADS_ERROR_NT(nt_status); + } + + switch (ads->ldap.wrap_type) { + case ADS_SASLWRAP_TYPE_SEAL: + features = NTLMSSP_FEATURE_SIGN | NTLMSSP_FEATURE_SEAL; + break; + case ADS_SASLWRAP_TYPE_SIGN: + if (ads->auth.flags & ADS_AUTH_SASL_FORCE) { + features = NTLMSSP_FEATURE_SIGN; + } else { + /* + * windows servers are broken with sign only, + * so we need to use seal here too + */ + features = NTLMSSP_FEATURE_SIGN | NTLMSSP_FEATURE_SEAL; + ads->ldap.wrap_type = ADS_SASLWRAP_TYPE_SEAL; + } + break; + case ADS_SASLWRAP_TYPE_PLAIN: + break; + } + + ntlmssp_want_feature(ntlmssp_state, features); + + blob_in = data_blob_null; + + do { + nt_status = ntlmssp_update(ntlmssp_state, + 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) { + /* and wrap it in a SPNEGO wrapper */ + msg1 = gen_negTokenInit(OID_NTLMSSP, blob_out); + } else { + /* wrap it in SPNEGO */ + msg1 = spnego_gen_auth(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); + } + + ntlmssp_end(&ntlmssp_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 { + + ntlmssp_end(&ntlmssp_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(blob, &blob_in, + &tmp_blob)) { + + ntlmssp_end(&ntlmssp_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(blob, nt_status, OID_NTLMSSP, + &blob_in)) { + + ntlmssp_end(&ntlmssp_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)); + + /* we have a reference conter on ntlmssp_state, if we are signing + then the state will be kept by the signing engine */ + + if (ads->ldap.wrap_type > ADS_SASLWRAP_TYPE_PLAIN) { + ads->ldap.out.max_unwrapped = ADS_SASL_WRAPPING_OUT_MAX_WRAPPED - NTLMSSP_SIG_SIZE; + ads->ldap.out.sig_size = NTLMSSP_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, ntlmssp_state); + if (!ADS_ERR_OK(status)) { + DEBUG(0, ("ads_setup_sasl_wrapping() failed: %s\n", + ads_errstr(status))); + ntlmssp_end(&ntlmssp_state); + return status; + } + } else { + ntlmssp_end(&ntlmssp_state); + } + + return ADS_ERROR(rc); +} + +#ifdef HAVE_GSSAPI +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_OID_desc krb5_mech_type = + {9, CONST_DISCARD(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; + + 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_C_NO_CREDENTIAL, + &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 = gen_negTokenTarg(spnego_mechs, unwrapped); + 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(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_C_NO_CREDENTIAL, + &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 (context_handle != GSS_C_NO_CONTEXT) + gss_delete_sec_context(&minor_status, &context_handle, GSS_C_NO_BUFFER); + return status; +} + +#endif /* HAVE_GSSAPI */ + +#ifdef HAVE_KRB5 +struct ads_service_principal { + char *string; +#ifdef HAVE_GSSAPI + gss_name_t name; +#endif +}; + +static void ads_free_service_principal(struct ads_service_principal *p) +{ + SAFE_FREE(p->string); + +#ifdef HAVE_GSSAPI + if (p->name) { + uint32 minor_status; + gss_release_name(&minor_status, &p->name); + } +#endif + ZERO_STRUCTP(p); +} + +static ADS_STATUS ads_generate_service_principal(ADS_STRUCT *ads, + const char *given_principal, + struct ads_service_principal *p) +{ + ADS_STATUS status; +#ifdef HAVE_GSSAPI + gss_buffer_desc input_name; + /* GSS_KRB5_NT_PRINCIPAL_NAME */ + gss_OID_desc nt_principal = + {10, CONST_DISCARD(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 - gd */ + + if (!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_GSSAPI + 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_negTokenTarg(principal, ads->auth.time_offset, &blob, &session_key, 0, + &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_GSSAPI + /* + * 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(blob, OIDs, &given_principal)) { + 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 + 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); + SAFE_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 + { + SAFE_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_GSSAPI +#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_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; + + /* + * 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_C_NO_CREDENTIAL, + &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); + 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, &conf_state, + &input_token); + if (gss_rc) { + status = ADS_ERROR_GSS(gss_rc, minor_status); + goto failed; + } + + free(output_token.value); + + 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 (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_GSSAPI */ + +/* 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_GSSAPI + {"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])); + status = sasl_mechanisms[i].fn(ads); + 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 */ + |