/* handler for completion of a smbcli_request sub-request */ static void request_handler(struct smbcli_request *req) { struct composite_context *c = (struct composite_context *)req->async.private_data; struct sesssetup_state *state = talloc_get_type(c->private_data, struct sesssetup_state); struct smbcli_session *session = req->session; DATA_BLOB session_key = data_blob(NULL, 0); DATA_BLOB null_data_blob = data_blob(NULL, 0); NTSTATUS session_key_err, nt_status; struct smbcli_request *check_req = NULL; const char *os = NULL; const char *lanman = NULL; if (req->sign_caller_checks) { req->do_not_free = true; check_req = req; } state->remote_status = smb_raw_sesssetup_recv(req, state, &state->setup); c->status = state->remote_status; state->req = NULL; /* * we only need to check the signature if the * NT_STATUS_OK is returned */ if (!NT_STATUS_IS_OK(state->remote_status)) { talloc_free(check_req); check_req = NULL; } switch (state->setup.old.level) { case RAW_SESSSETUP_OLD: state->io->out.vuid = state->setup.old.out.vuid; /* This doesn't work, as this only happens on old * protocols, where this comparison won't match. */ if (NT_STATUS_EQUAL(c->status, NT_STATUS_LOGON_FAILURE)) { /* we neet to reset the vuid for a new try */ session->vuid = 0; if (cli_credentials_wrong_password(state->io->in.credentials)) { nt_status = session_setup_old(c, session, state->io, &state->req); if (NT_STATUS_IS_OK(nt_status)) { talloc_free(check_req); c->status = nt_status; composite_continue_smb(c, state->req, request_handler, c); return; } } } os = state->setup.old.out.os; lanman = state->setup.old.out.lanman; break; case RAW_SESSSETUP_NT1: state->io->out.vuid = state->setup.nt1.out.vuid; if (NT_STATUS_EQUAL(c->status, NT_STATUS_LOGON_FAILURE)) { /* we neet to reset the vuid for a new try */ session->vuid = 0; if (cli_credentials_wrong_password(state->io->in.credentials)) { nt_status = session_setup_nt1(c, session, state->io, &state->req); if (NT_STATUS_IS_OK(nt_status)) { talloc_free(check_req); c->status = nt_status; composite_continue_smb(c, state->req, request_handler, c); return; } } } os = state->setup.nt1.out.os; lanman = state->setup.nt1.out.lanman; break; case RAW_SESSSETUP_SPNEGO: state->io->out.vuid = state->setup.spnego.out.vuid; if (NT_STATUS_EQUAL(c->status, NT_STATUS_LOGON_FAILURE)) { /* we need to reset the vuid for a new try */ session->vuid = 0; if (cli_credentials_wrong_password(state->io->in.credentials)) { nt_status = session_setup_spnego(c, session, state->io, &state->req); if (NT_STATUS_IS_OK(nt_status)) { talloc_free(check_req); c->status = nt_status; composite_continue_smb(c, state->req, request_handler, c); return; } } } if (!NT_STATUS_EQUAL(c->status, NT_STATUS_MORE_PROCESSING_REQUIRED) && !NT_STATUS_IS_OK(c->status)) { break; } if (NT_STATUS_EQUAL(state->gensec_status, NT_STATUS_MORE_PROCESSING_REQUIRED)) { /* The status value here, from the earlier pass at GENSEC is * vital to the security of the system. Even if the other end * accepts, if GENSEC claims 'MORE_PROCESSING_REQUIRED' then * you must keep feeding it blobs, or else the remote * host/attacker might avoid mutal authentication * requirements */ state->gensec_status = gensec_update(session->gensec, state, state->setup.spnego.out.secblob, &state->setup.spnego.in.secblob); c->status = state->gensec_status; if (!NT_STATUS_EQUAL(c->status, NT_STATUS_MORE_PROCESSING_REQUIRED) && !NT_STATUS_IS_OK(c->status)) { break; } } else { state->setup.spnego.in.secblob = data_blob(NULL, 0); } if (NT_STATUS_IS_OK(state->remote_status)) { if (state->setup.spnego.in.secblob.length) { c->status = NT_STATUS_INTERNAL_ERROR; break; } session_key_err = gensec_session_key(session->gensec, session, &session->user_session_key); if (NT_STATUS_IS_OK(session_key_err)) { smbcli_transport_simple_set_signing(session->transport, session->user_session_key, null_data_blob); } } if (state->setup.spnego.in.secblob.length) { /* * set the session->vuid value only for calling * smb_raw_sesssetup_send() */ uint16_t vuid = session->vuid; session->vuid = state->io->out.vuid; state->req = smb_raw_sesssetup_send(session, &state->setup); session->vuid = vuid; if (state->req) { state->req->sign_caller_checks = true; } composite_continue_smb(c, state->req, request_handler, c); return; } os = state->setup.spnego.out.os; lanman = state->setup.spnego.out.lanman; break; case RAW_SESSSETUP_SMB2: c->status = NT_STATUS_INTERNAL_ERROR; break; } if (check_req) { check_req->sign_caller_checks = false; if (!smbcli_request_check_sign_mac(check_req)) { c->status = NT_STATUS_ACCESS_DENIED; } talloc_free(check_req); check_req = NULL; } /* enforce the local signing required flag */ if (NT_STATUS_IS_OK(c->status) && !cli_credentials_is_anonymous(state->io->in.credentials)) { if (!session->transport->negotiate.sign_info.doing_signing && session->transport->negotiate.sign_info.mandatory_signing) { DEBUG(0, ("SMB signing required, but server does not support it\n")); c->status = NT_STATUS_ACCESS_DENIED; } } if (!NT_STATUS_IS_OK(c->status)) { composite_error(c, c->status); return; } if (os) { session->os = talloc_strdup(session, os); if (composite_nomem(session->os, c)) return; } else { session->os = NULL; } if (lanman) { session->lanman = talloc_strdup(session, lanman); if (composite_nomem(session->lanman, c)) return; } else { session->lanman = NULL; } composite_done(c); }
/* handler for completion of a smbcli_request sub-request */ static void request_handler(struct smbcli_request *req) { struct composite_context *c = (struct composite_context *)req->async.private_data; struct sesssetup_state *state = talloc_get_type(c->private_data, struct sesssetup_state); struct smbcli_session *session = req->session; DATA_BLOB null_data_blob = data_blob(NULL, 0); NTSTATUS session_key_err, nt_status; struct smbcli_request *check_req = NULL; const char *os = NULL; const char *lanman = NULL; if (req->sign_caller_checks) { req->do_not_free = true; check_req = req; } state->remote_status = smb_raw_sesssetup_recv(req, state, &state->setup); c->status = state->remote_status; state->req = NULL; /* * we only need to check the signature if the * NT_STATUS_OK is returned */ if (!NT_STATUS_IS_OK(state->remote_status)) { talloc_free(check_req); check_req = NULL; } switch (state->setup.old.level) { case RAW_SESSSETUP_OLD: state->io->out.vuid = state->setup.old.out.vuid; /* This doesn't work, as this only happens on old * protocols, where this comparison won't match. */ if (NT_STATUS_EQUAL(c->status, NT_STATUS_LOGON_FAILURE)) { /* we neet to reset the vuid for a new try */ session->vuid = 0; if (cli_credentials_wrong_password(state->io->in.credentials)) { nt_status = session_setup_old(c, session, state->io, &state->req); if (NT_STATUS_IS_OK(nt_status)) { talloc_free(check_req); c->status = nt_status; composite_continue_smb(c, state->req, request_handler, c); return; } } } os = state->setup.old.out.os; lanman = state->setup.old.out.lanman; break; case RAW_SESSSETUP_NT1: state->io->out.vuid = state->setup.nt1.out.vuid; if (NT_STATUS_EQUAL(c->status, NT_STATUS_LOGON_FAILURE)) { /* we need to reset the vuid for a new try */ session->vuid = 0; if (cli_credentials_wrong_password(state->io->in.credentials)) { nt_status = session_setup_nt1(c, session, state->io, &state->req); if (NT_STATUS_IS_OK(nt_status)) { talloc_free(check_req); c->status = nt_status; composite_continue_smb(c, state->req, request_handler, c); return; } } } os = state->setup.nt1.out.os; lanman = state->setup.nt1.out.lanman; break; case RAW_SESSSETUP_SPNEGO: state->io->out.vuid = state->setup.spnego.out.vuid; if (NT_STATUS_EQUAL(c->status, NT_STATUS_LOGON_FAILURE)) { const char *principal; /* we need to reset the vuid for a new try */ session->vuid = 0; principal = gensec_get_target_principal(session->gensec); if (principal == NULL) { const char *hostname = gensec_get_target_hostname(session->gensec); const char *service = gensec_get_target_service(session->gensec); if (hostname != NULL && service != NULL) { principal = talloc_asprintf(state, "%s/%s", service, hostname); } } if (cli_credentials_failed_kerberos_login(state->io->in.credentials, principal, &state->logon_retries) || cli_credentials_wrong_password(state->io->in.credentials)) { nt_status = session_setup_spnego(c, session, state->io, &state->req); if (NT_STATUS_IS_OK(nt_status)) { talloc_free(check_req); c->status = nt_status; composite_continue_smb(c, state->req, request_handler, c); return; } } } if (!NT_STATUS_EQUAL(c->status, NT_STATUS_MORE_PROCESSING_REQUIRED) && !NT_STATUS_IS_OK(c->status)) { break; } if (NT_STATUS_EQUAL(state->gensec_status, NT_STATUS_MORE_PROCESSING_REQUIRED)) { /* The status value here, from the earlier pass at GENSEC is * vital to the security of the system. Even if the other end * accepts, if GENSEC claims 'MORE_PROCESSING_REQUIRED' then * you must keep feeding it blobs, or else the remote * host/attacker might avoid mutal authentication * requirements */ state->gensec_status = gensec_update(session->gensec, state, c->event_ctx, state->setup.spnego.out.secblob, &state->setup.spnego.in.secblob); c->status = state->gensec_status; if (!NT_STATUS_EQUAL(c->status, NT_STATUS_MORE_PROCESSING_REQUIRED) && !NT_STATUS_IS_OK(c->status)) { break; } } else { state->setup.spnego.in.secblob = data_blob(NULL, 0); } if (NT_STATUS_IS_OK(state->remote_status)) { DATA_BLOB session_key; if (state->setup.spnego.in.secblob.length) { c->status = NT_STATUS_INTERNAL_ERROR; break; } session_key_err = gensec_session_key(session->gensec, session, &session_key); if (NT_STATUS_IS_OK(session_key_err)) { smb1cli_conn_activate_signing(session->transport->conn, session_key, null_data_blob); } c->status = smb1cli_session_set_session_key(session->smbXcli, session_key); data_blob_free(&session_key); if (!NT_STATUS_IS_OK(c->status)) { break; } } if (state->setup.spnego.in.secblob.length) { /* * set the session->vuid value only for calling * smb_raw_sesssetup_send() */ uint16_t vuid = session->vuid; session->vuid = state->io->out.vuid; state->req = smb_raw_sesssetup_send(session, &state->setup); session->vuid = vuid; if (state->req && !smb1cli_conn_signing_is_active(state->req->transport->conn)) { state->req->sign_caller_checks = true; } composite_continue_smb(c, state->req, request_handler, c); return; } os = state->setup.spnego.out.os; lanman = state->setup.spnego.out.lanman; break; case RAW_SESSSETUP_SMB2: c->status = NT_STATUS_INTERNAL_ERROR; break; } if (check_req) { bool ok; check_req->sign_caller_checks = false; ok = smb1cli_conn_check_signing(check_req->transport->conn, check_req->in.buffer, 1); if (!ok) { c->status = NT_STATUS_ACCESS_DENIED; } talloc_free(check_req); check_req = NULL; } if (!NT_STATUS_IS_OK(c->status)) { composite_error(c, c->status); return; } if (os) { session->os = talloc_strdup(session, os); if (composite_nomem(session->os, c)) return; } else { session->os = NULL; } if (lanman) { session->lanman = talloc_strdup(session, lanman); if (composite_nomem(session->lanman, c)) return; } else { session->lanman = NULL; } composite_done(c); }
krb5_error_code kinit_to_ccache(TALLOC_CTX *parent_ctx, struct cli_credentials *credentials, struct smb_krb5_context *smb_krb5_context, struct tevent_context *event_ctx, krb5_ccache ccache, enum credentials_obtained *obtained, const char **error_string) { krb5_error_code ret; const char *password; #ifdef SAMBA4_USES_HEIMDAL const char *self_service; #endif const char *target_service; time_t kdc_time = 0; krb5_principal princ; krb5_principal impersonate_principal; int tries; TALLOC_CTX *mem_ctx = talloc_new(parent_ctx); krb5_get_init_creds_opt *krb_options; if (!mem_ctx) { (*error_string) = strerror(ENOMEM); return ENOMEM; } ret = principal_from_credentials(mem_ctx, credentials, smb_krb5_context, &princ, obtained, error_string); if (ret) { talloc_free(mem_ctx); return ret; } if (princ == NULL) { (*error_string) = talloc_asprintf(credentials, "principal, username or realm was not specified in the credentials"); talloc_free(mem_ctx); return KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN; } ret = impersonate_principal_from_credentials(mem_ctx, credentials, smb_krb5_context, &impersonate_principal, error_string); if (ret) { talloc_free(mem_ctx); return ret; } #ifdef SAMBA4_USES_HEIMDAL self_service = cli_credentials_get_self_service(credentials); #endif target_service = cli_credentials_get_target_service(credentials); password = cli_credentials_get_password(credentials); /* setup the krb5 options we want */ if ((ret = krb5_get_init_creds_opt_alloc(smb_krb5_context->krb5_context, &krb_options))) { (*error_string) = talloc_asprintf(credentials, "krb5_get_init_creds_opt_alloc failed (%s)\n", smb_get_krb5_error_message(smb_krb5_context->krb5_context, ret, mem_ctx)); talloc_free(mem_ctx); return ret; } #ifdef SAMBA4_USES_HEIMDAL /* Disable for now MIT reads defaults when needed */ /* get the defaults */ krb5_get_init_creds_opt_set_default_flags(smb_krb5_context->krb5_context, NULL, NULL, krb_options); #endif /* set if we want a forwardable ticket */ switch (cli_credentials_get_krb_forwardable(credentials)) { case CRED_AUTO_KRB_FORWARDABLE: break; case CRED_NO_KRB_FORWARDABLE: krb5_get_init_creds_opt_set_forwardable(krb_options, FALSE); break; case CRED_FORCE_KRB_FORWARDABLE: krb5_get_init_creds_opt_set_forwardable(krb_options, TRUE); break; } #ifdef SAMBA4_USES_HEIMDAL /* FIXME: MIT does not have this yet */ /* * In order to work against windows KDCs even if we use * the netbios domain name as realm, we need to add the following * flags: * KRB5_INIT_CREDS_NO_C_CANON_CHECK; * KRB5_INIT_CREDS_NO_C_NO_EKU_CHECK; * * On MIT: Set pkinit_eku_checking to none */ krb5_get_init_creds_opt_set_win2k(smb_krb5_context->krb5_context, krb_options, true); #else /* MIT */ krb5_get_init_creds_opt_set_canonicalize(krb_options, true); #endif tries = 2; while (tries--) { #ifdef SAMBA4_USES_HEIMDAL struct tevent_context *previous_ev; /* Do this every time, in case we have weird recursive issues here */ ret = smb_krb5_context_set_event_ctx(smb_krb5_context, event_ctx, &previous_ev); if (ret) { talloc_free(mem_ctx); return ret; } #endif if (password) { if (impersonate_principal) { #ifdef SAMBA4_USES_HEIMDAL ret = kerberos_kinit_s4u2_cc( smb_krb5_context->krb5_context, ccache, princ, password, impersonate_principal, self_service, target_service, krb_options, NULL, &kdc_time); #else talloc_free(mem_ctx); (*error_string) = "INTERNAL error: s4u2 ops " "are not supported with MIT build yet"; return EINVAL; #endif } else { ret = kerberos_kinit_password_cc( smb_krb5_context->krb5_context, ccache, princ, password, target_service, krb_options, NULL, &kdc_time); } } else if (impersonate_principal) { talloc_free(mem_ctx); (*error_string) = "INTERNAL error: Cannot impersonate principal with just a keyblock. A password must be specified in the credentials"; return EINVAL; } else { /* No password available, try to use a keyblock instead */ krb5_keyblock keyblock; const struct samr_Password *mach_pwd; mach_pwd = cli_credentials_get_nt_hash(credentials, mem_ctx); if (!mach_pwd) { talloc_free(mem_ctx); (*error_string) = "kinit_to_ccache: No password available for kinit\n"; krb5_get_init_creds_opt_free(smb_krb5_context->krb5_context, krb_options); #ifdef SAMBA4_USES_HEIMDAL smb_krb5_context_remove_event_ctx(smb_krb5_context, previous_ev, event_ctx); #endif return EINVAL; } ret = smb_krb5_keyblock_init_contents(smb_krb5_context->krb5_context, ENCTYPE_ARCFOUR_HMAC, mach_pwd->hash, sizeof(mach_pwd->hash), &keyblock); if (ret == 0) { ret = kerberos_kinit_keyblock_cc(smb_krb5_context->krb5_context, ccache, princ, &keyblock, target_service, krb_options, NULL, &kdc_time); krb5_free_keyblock_contents(smb_krb5_context->krb5_context, &keyblock); } } #ifdef SAMBA4_USES_HEIMDAL smb_krb5_context_remove_event_ctx(smb_krb5_context, previous_ev, event_ctx); #endif if (ret == KRB5KRB_AP_ERR_SKEW || ret == KRB5_KDCREP_SKEW) { /* Perhaps we have been given an invalid skew, so try again without it */ time_t t = time(NULL); krb5_set_real_time(smb_krb5_context->krb5_context, t, 0); } else { /* not a skew problem */ break; } } krb5_get_init_creds_opt_free(smb_krb5_context->krb5_context, krb_options); if (ret == KRB5KRB_AP_ERR_SKEW || ret == KRB5_KDCREP_SKEW) { (*error_string) = talloc_asprintf(credentials, "kinit for %s failed (%s)\n", cli_credentials_get_principal(credentials, mem_ctx), smb_get_krb5_error_message(smb_krb5_context->krb5_context, ret, mem_ctx)); talloc_free(mem_ctx); return ret; } /* cope with ticket being in the future due to clock skew */ if ((unsigned)kdc_time > time(NULL)) { time_t t = time(NULL); int time_offset =(unsigned)kdc_time-t; DEBUG(4,("Advancing clock by %d seconds to cope with clock skew\n", time_offset)); krb5_set_real_time(smb_krb5_context->krb5_context, t + time_offset + 1, 0); } if (ret == KRB5KDC_ERR_PREAUTH_FAILED && cli_credentials_wrong_password(credentials)) { ret = kinit_to_ccache(parent_ctx, credentials, smb_krb5_context, event_ctx, ccache, obtained, error_string); } if (ret) { (*error_string) = talloc_asprintf(credentials, "kinit for %s failed (%s)\n", cli_credentials_get_principal(credentials, mem_ctx), smb_get_krb5_error_message(smb_krb5_context->krb5_context, ret, mem_ctx)); talloc_free(mem_ctx); return ret; } DEBUG(10,("kinit for %s succeeded\n", cli_credentials_get_principal(credentials, mem_ctx))); talloc_free(mem_ctx); return 0; }
/* perform a sasl bind using the given credentials */ _PUBLIC_ NTSTATUS ldap_bind_sasl(struct ldap_connection *conn, struct cli_credentials *creds, struct loadparm_context *lp_ctx) { NTSTATUS status; TALLOC_CTX *tmp_ctx = NULL; DATA_BLOB input = data_blob(NULL, 0); DATA_BLOB output = data_blob(NULL, 0); struct ldap_message **sasl_mechs_msgs; struct ldap_SearchResEntry *search; int count, i; const char **sasl_names; uint32_t old_gensec_features; static const char *supported_sasl_mech_attrs[] = { "supportedSASLMechanisms", NULL }; unsigned int logon_retries = 0; status = ildap_search(conn, "", LDAP_SEARCH_SCOPE_BASE, "", supported_sasl_mech_attrs, false, NULL, NULL, &sasl_mechs_msgs); if (!NT_STATUS_IS_OK(status)) { DEBUG(1, ("Failed to inquire of target's available sasl mechs in rootdse search: %s\n", nt_errstr(status))); goto failed; } count = ildap_count_entries(conn, sasl_mechs_msgs); if (count != 1) { DEBUG(1, ("Failed to inquire of target's available sasl mechs in rootdse search: wrong number of replies: %d\n", count)); goto failed; } tmp_ctx = talloc_new(conn); if (tmp_ctx == NULL) goto failed; search = &sasl_mechs_msgs[0]->r.SearchResultEntry; if (search->num_attributes != 1) { DEBUG(1, ("Failed to inquire of target's available sasl mechs in rootdse search: wrong number of attributes: %d != 1\n", search->num_attributes)); goto failed; } sasl_names = talloc_array(tmp_ctx, const char *, search->attributes[0].num_values + 1); if (!sasl_names) { DEBUG(1, ("talloc_arry(char *, %d) failed\n", count)); goto failed; } for (i=0; i<search->attributes[0].num_values; i++) { sasl_names[i] = (const char *)search->attributes[0].values[i].data; } sasl_names[i] = NULL; gensec_init(); try_logon_again: /* we loop back here on a logon failure, and re-create the gensec session. The logon_retries counter ensures we don't loop forever. */ status = gensec_client_start(conn, &conn->gensec, lpcfg_gensec_settings(conn, lp_ctx)); if (!NT_STATUS_IS_OK(status)) { DEBUG(0, ("Failed to start GENSEC engine (%s)\n", nt_errstr(status))); goto failed; } /* require Kerberos SIGN/SEAL only if we don't use SSL * Windows seem not to like double encryption */ old_gensec_features = cli_credentials_get_gensec_features(creds); if (tls_enabled(conn->sock)) { cli_credentials_set_gensec_features(creds, old_gensec_features & ~(GENSEC_FEATURE_SIGN|GENSEC_FEATURE_SEAL)); } /* this call also sets the gensec_want_features */ status = gensec_set_credentials(conn->gensec, creds); if (!NT_STATUS_IS_OK(status)) { DEBUG(1, ("Failed to set GENSEC creds: %s\n", nt_errstr(status))); goto failed; } /* reset the original gensec_features (on the credentials * context, so we don't tatoo it ) */ cli_credentials_set_gensec_features(creds, old_gensec_features); if (conn->host) { status = gensec_set_target_hostname(conn->gensec, conn->host); if (!NT_STATUS_IS_OK(status)) { DEBUG(1, ("Failed to set GENSEC target hostname: %s\n", nt_errstr(status))); goto failed; } } status = gensec_set_target_service(conn->gensec, "ldap"); if (!NT_STATUS_IS_OK(status)) { DEBUG(1, ("Failed to set GENSEC target service: %s\n", nt_errstr(status))); goto failed; } status = gensec_start_mech_by_sasl_list(conn->gensec, sasl_names); if (!NT_STATUS_IS_OK(status)) { DEBUG(1, ("None of the %d proposed SASL mechs were acceptable: %s\n", count, nt_errstr(status))); goto failed; } while (1) { NTSTATUS gensec_status; struct ldap_message *response; struct ldap_message *msg; struct ldap_request *req; int result = LDAP_OTHER; status = gensec_update(conn->gensec, tmp_ctx, conn->event.event_ctx, input, &output); /* The status value here, from GENSEC is vital to the security * of the system. Even if the other end accepts, if GENSEC * claims 'MORE_PROCESSING_REQUIRED' then you must keep * feeding it blobs, or else the remote host/attacker might * avoid mutal authentication requirements. * * Likewise, you must not feed GENSEC too much (after the OK), * it doesn't like that either */ gensec_status = status; if (!NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED) && !NT_STATUS_IS_OK(status)) { break; } if (NT_STATUS_IS_OK(status) && output.length == 0) { break; } /* Perhaps we should make gensec_start_mech_by_sasl_list() return the name we got? */ msg = new_ldap_sasl_bind_msg(tmp_ctx, conn->gensec->ops->sasl_name, (output.data?&output:NULL)); if (msg == NULL) { status = NT_STATUS_NO_MEMORY; goto failed; } req = ldap_request_send(conn, msg); if (req == NULL) { status = NT_STATUS_NO_MEMORY; goto failed; } talloc_reparent(conn, tmp_ctx, req); status = ldap_result_n(req, 0, &response); if (!NT_STATUS_IS_OK(status)) { goto failed; } if (response->type != LDAP_TAG_BindResponse) { status = NT_STATUS_UNEXPECTED_NETWORK_ERROR; goto failed; } result = response->r.BindResponse.response.resultcode; if (result == LDAP_INVALID_CREDENTIALS) { /* try a second time on invalid credentials, to give the user a chance to re-enter the password and to handle the case where our kerberos ticket is invalid as the server password has changed */ const char *principal; principal = gensec_get_target_principal(conn->gensec); if (principal == NULL) { const char *hostname = gensec_get_target_hostname(conn->gensec); const char *service = gensec_get_target_service(conn->gensec); if (hostname != NULL && service != NULL) { principal = talloc_asprintf(tmp_ctx, "%s/%s", service, hostname); } } if (cli_credentials_failed_kerberos_login(creds, principal, &logon_retries) || cli_credentials_wrong_password(creds)) { /* destroy our gensec session and loop back up to the top to retry, offering the user a chance to enter new credentials, or get a new ticket if using kerberos */ talloc_free(conn->gensec); conn->gensec = NULL; goto try_logon_again; } } if (result != LDAP_SUCCESS && result != LDAP_SASL_BIND_IN_PROGRESS) { status = ldap_check_response(conn, &response->r.BindResponse.response); break; } /* This is where we check if GENSEC wanted to be fed more data */ if (!NT_STATUS_EQUAL(gensec_status, NT_STATUS_MORE_PROCESSING_REQUIRED)) { break; } if (response->r.BindResponse.SASL.secblob) { input = *response->r.BindResponse.SASL.secblob; } else { input = data_blob(NULL, 0); } } talloc_free(tmp_ctx); if (NT_STATUS_IS_OK(status)) { struct socket_context *sasl_socket; status = gensec_socket_init(conn->gensec, conn, conn->sock, conn->event.event_ctx, ldap_read_io_handler, conn, &sasl_socket); if (!NT_STATUS_IS_OK(status)) goto failed; conn->sock = sasl_socket; packet_set_socket(conn->packet, conn->sock); conn->bind.type = LDAP_BIND_SASL; conn->bind.creds = creds; } return status; failed: talloc_free(tmp_ctx); talloc_free(conn->gensec); conn->gensec = NULL; return status; }
krb5_error_code kinit_to_ccache(TALLOC_CTX *parent_ctx, struct cli_credentials *credentials, struct smb_krb5_context *smb_krb5_context, krb5_ccache ccache) { krb5_error_code ret; const char *password; time_t kdc_time = 0; krb5_principal princ; int tries; TALLOC_CTX *mem_ctx = talloc_new(parent_ctx); if (!mem_ctx) { return ENOMEM; } ret = principal_from_credentials(mem_ctx, credentials, smb_krb5_context, &princ); if (ret) { talloc_free(mem_ctx); return ret; } password = cli_credentials_get_password(credentials); tries = 2; while (tries--) { if (password) { ret = kerberos_kinit_password_cc(smb_krb5_context->krb5_context, ccache, princ, password, NULL, &kdc_time); } else { /* No password available, try to use a keyblock instead */ krb5_keyblock keyblock; const struct samr_Password *mach_pwd; mach_pwd = cli_credentials_get_nt_hash(credentials, mem_ctx); if (!mach_pwd) { talloc_free(mem_ctx); DEBUG(1, ("kinit_to_ccache: No password available for kinit\n")); return EINVAL; } ret = krb5_keyblock_init(smb_krb5_context->krb5_context, ENCTYPE_ARCFOUR_HMAC, mach_pwd->hash, sizeof(mach_pwd->hash), &keyblock); if (ret == 0) { ret = kerberos_kinit_keyblock_cc(smb_krb5_context->krb5_context, ccache, princ, &keyblock, NULL, &kdc_time); krb5_free_keyblock_contents(smb_krb5_context->krb5_context, &keyblock); } } if (ret == KRB5KRB_AP_ERR_SKEW || ret == KRB5_KDCREP_SKEW) { /* Perhaps we have been given an invalid skew, so try again without it */ time_t t = time(NULL); krb5_set_real_time(smb_krb5_context->krb5_context, t, 0); } else { /* not a skew problem */ break; } } if (ret == KRB5KRB_AP_ERR_SKEW || ret == KRB5_KDCREP_SKEW) { DEBUG(1,("kinit for %s failed (%s)\n", cli_credentials_get_principal(credentials, mem_ctx), smb_get_krb5_error_message(smb_krb5_context->krb5_context, ret, mem_ctx))); talloc_free(mem_ctx); return ret; } /* cope with ticket being in the future due to clock skew */ if ((unsigned)kdc_time > time(NULL)) { time_t t = time(NULL); int time_offset =(unsigned)kdc_time-t; DEBUG(4,("Advancing clock by %d seconds to cope with clock skew\n", time_offset)); krb5_set_real_time(smb_krb5_context->krb5_context, t + time_offset + 1, 0); } if (ret == KRB5KDC_ERR_PREAUTH_FAILED && cli_credentials_wrong_password(credentials)) { ret = kinit_to_ccache(parent_ctx, credentials, smb_krb5_context, ccache); } if (ret) { DEBUG(1,("kinit for %s failed (%s)\n", cli_credentials_get_principal(credentials, mem_ctx), smb_get_krb5_error_message(smb_krb5_context->krb5_context, ret, mem_ctx))); talloc_free(mem_ctx); return ret; } talloc_free(mem_ctx); return 0; }
/* perform a sasl bind using the given credentials */ _PUBLIC_ NTSTATUS ldap_bind_sasl(struct ldap_connection *conn, struct cli_credentials *creds, struct loadparm_context *lp_ctx) { NTSTATUS status; TALLOC_CTX *tmp_ctx = NULL; DATA_BLOB input = data_blob(NULL, 0); DATA_BLOB output = data_blob(NULL, 0); struct ldap_message **sasl_mechs_msgs; struct ldap_SearchResEntry *search; int count, i; bool first = true; int wrap_flags = 0; const char **sasl_names; uint32_t old_gensec_features; static const char *supported_sasl_mech_attrs[] = { "supportedSASLMechanisms", NULL }; unsigned int logon_retries = 0; size_t queue_length; if (conn->sockets.active == NULL) { status = NT_STATUS_CONNECTION_DISCONNECTED; goto failed; } queue_length = tevent_queue_length(conn->sockets.send_queue); if (queue_length != 0) { status = NT_STATUS_INVALID_PARAMETER_MIX; DEBUG(1, ("SASL bind triggered with non empty send_queue[%zu]: %s\n", queue_length, nt_errstr(status))); goto failed; } if (conn->pending != NULL) { status = NT_STATUS_INVALID_PARAMETER_MIX; DEBUG(1, ("SASL bind triggered with pending requests: %s\n", nt_errstr(status))); goto failed; } status = ildap_search(conn, "", LDAP_SEARCH_SCOPE_BASE, "", supported_sasl_mech_attrs, false, NULL, NULL, &sasl_mechs_msgs); if (!NT_STATUS_IS_OK(status)) { DEBUG(1, ("Failed to inquire of target's available sasl mechs in rootdse search: %s\n", nt_errstr(status))); goto failed; } count = ildap_count_entries(conn, sasl_mechs_msgs); if (count != 1) { DEBUG(1, ("Failed to inquire of target's available sasl mechs in rootdse search: wrong number of replies: %d\n", count)); goto failed; } tmp_ctx = talloc_new(conn); if (tmp_ctx == NULL) goto failed; search = &sasl_mechs_msgs[0]->r.SearchResultEntry; if (search->num_attributes != 1) { DEBUG(1, ("Failed to inquire of target's available sasl mechs in rootdse search: wrong number of attributes: %d != 1\n", search->num_attributes)); goto failed; } sasl_names = talloc_array(tmp_ctx, const char *, search->attributes[0].num_values + 1); if (!sasl_names) { DEBUG(1, ("talloc_arry(char *, %d) failed\n", count)); goto failed; } for (i=0; i<search->attributes[0].num_values; i++) { sasl_names[i] = (const char *)search->attributes[0].values[i].data; } sasl_names[i] = NULL; gensec_init(); if (conn->sockets.active == conn->sockets.tls) { /* * require Kerberos SIGN/SEAL only if we don't use SSL * Windows seem not to like double encryption */ wrap_flags = 0; } else if (cli_credentials_is_anonymous(creds)) { /* * anonymous isn't protected */ wrap_flags = 0; } else { wrap_flags = lpcfg_client_ldap_sasl_wrapping(lp_ctx); } try_logon_again: /* we loop back here on a logon failure, and re-create the gensec session. The logon_retries counter ensures we don't loop forever. */ data_blob_free(&input); TALLOC_FREE(conn->gensec); status = gensec_client_start(conn, &conn->gensec, lpcfg_gensec_settings(conn, lp_ctx)); if (!NT_STATUS_IS_OK(status)) { DEBUG(0, ("Failed to start GENSEC engine (%s)\n", nt_errstr(status))); goto failed; } old_gensec_features = cli_credentials_get_gensec_features(creds); if (wrap_flags == 0) { cli_credentials_set_gensec_features(creds, old_gensec_features & ~(GENSEC_FEATURE_SIGN|GENSEC_FEATURE_SEAL)); } /* this call also sets the gensec_want_features */ status = gensec_set_credentials(conn->gensec, creds); if (!NT_STATUS_IS_OK(status)) { DEBUG(1, ("Failed to set GENSEC creds: %s\n", nt_errstr(status))); goto failed; } /* reset the original gensec_features (on the credentials * context, so we don't tatoo it ) */ cli_credentials_set_gensec_features(creds, old_gensec_features); if (wrap_flags & ADS_AUTH_SASL_SEAL) { gensec_want_feature(conn->gensec, GENSEC_FEATURE_SIGN); gensec_want_feature(conn->gensec, GENSEC_FEATURE_SEAL); } if (wrap_flags & ADS_AUTH_SASL_SIGN) { gensec_want_feature(conn->gensec, GENSEC_FEATURE_SIGN); } /* * This is an indication for the NTLMSSP backend to * also encrypt when only GENSEC_FEATURE_SIGN is requested * in gensec_[un]wrap(). */ gensec_want_feature(conn->gensec, GENSEC_FEATURE_LDAP_STYLE); if (conn->host) { status = gensec_set_target_hostname(conn->gensec, conn->host); if (!NT_STATUS_IS_OK(status)) { DEBUG(1, ("Failed to set GENSEC target hostname: %s\n", nt_errstr(status))); goto failed; } } status = gensec_set_target_service(conn->gensec, "ldap"); if (!NT_STATUS_IS_OK(status)) { DEBUG(1, ("Failed to set GENSEC target service: %s\n", nt_errstr(status))); goto failed; } status = gensec_start_mech_by_sasl_list(conn->gensec, sasl_names); if (!NT_STATUS_IS_OK(status)) { DEBUG(1, ("None of the %d proposed SASL mechs were acceptable: %s\n", count, nt_errstr(status))); goto failed; } while (1) { NTSTATUS gensec_status; struct ldap_message *response; struct ldap_message *msg; struct ldap_request *req; int result = LDAP_OTHER; status = gensec_update_ev(conn->gensec, tmp_ctx, conn->event.event_ctx, input, &output); /* The status value here, from GENSEC is vital to the security * of the system. Even if the other end accepts, if GENSEC * claims 'MORE_PROCESSING_REQUIRED' then you must keep * feeding it blobs, or else the remote host/attacker might * avoid mutal authentication requirements. * * Likewise, you must not feed GENSEC too much (after the OK), * it doesn't like that either. * * For SASL/EXTERNAL, there is no data to send, but we still * must send the actual Bind request the first time around. * Otherwise, a result of NT_STATUS_OK with 0 output means the * end of a multi-step authentication, and no message must be * sent. */ gensec_status = status; if (!NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED) && !NT_STATUS_IS_OK(status)) { break; } if (NT_STATUS_IS_OK(status) && output.length == 0) { if (!first) break; } first = false; /* Perhaps we should make gensec_start_mech_by_sasl_list() return the name we got? */ msg = new_ldap_sasl_bind_msg(tmp_ctx, conn->gensec->ops->sasl_name, (output.data?&output:NULL)); if (msg == NULL) { status = NT_STATUS_NO_MEMORY; goto failed; } req = ldap_request_send(conn, msg); if (req == NULL) { status = NT_STATUS_NO_MEMORY; goto failed; } talloc_reparent(conn, tmp_ctx, req); status = ldap_result_n(req, 0, &response); if (!NT_STATUS_IS_OK(status)) { goto failed; } if (response->type != LDAP_TAG_BindResponse) { status = NT_STATUS_UNEXPECTED_NETWORK_ERROR; goto failed; } result = response->r.BindResponse.response.resultcode; if (result == LDAP_STRONG_AUTH_REQUIRED) { if (wrap_flags == 0) { wrap_flags = ADS_AUTH_SASL_SIGN; goto try_logon_again; } } if (result == LDAP_INVALID_CREDENTIALS) { /* try a second time on invalid credentials, to give the user a chance to re-enter the password and to handle the case where our kerberos ticket is invalid as the server password has changed */ const char *principal; principal = gensec_get_target_principal(conn->gensec); if (principal == NULL) { const char *hostname = gensec_get_target_hostname(conn->gensec); const char *service = gensec_get_target_service(conn->gensec); if (hostname != NULL && service != NULL) { principal = talloc_asprintf(tmp_ctx, "%s/%s", service, hostname); } } if (cli_credentials_failed_kerberos_login(creds, principal, &logon_retries) || cli_credentials_wrong_password(creds)) { /* destroy our gensec session and loop back up to the top to retry, offering the user a chance to enter new credentials, or get a new ticket if using kerberos */ goto try_logon_again; } } if (result != LDAP_SUCCESS && result != LDAP_SASL_BIND_IN_PROGRESS) { status = ldap_check_response(conn, &response->r.BindResponse.response); break; } /* This is where we check if GENSEC wanted to be fed more data */ if (!NT_STATUS_EQUAL(gensec_status, NT_STATUS_MORE_PROCESSING_REQUIRED)) { break; } if (response->r.BindResponse.SASL.secblob) { input = *response->r.BindResponse.SASL.secblob; } else { input = data_blob(NULL, 0); } } TALLOC_FREE(tmp_ctx); if (!NT_STATUS_IS_OK(status)) { goto failed; } conn->bind.type = LDAP_BIND_SASL; conn->bind.creds = creds; if (wrap_flags & ADS_AUTH_SASL_SEAL) { if (!gensec_have_feature(conn->gensec, GENSEC_FEATURE_SIGN)) { return NT_STATUS_INVALID_NETWORK_RESPONSE; } if (!gensec_have_feature(conn->gensec, GENSEC_FEATURE_SEAL)) { return NT_STATUS_INVALID_NETWORK_RESPONSE; } } else if (wrap_flags & ADS_AUTH_SASL_SIGN) { if (!gensec_have_feature(conn->gensec, GENSEC_FEATURE_SIGN)) { return NT_STATUS_INVALID_NETWORK_RESPONSE; } } if (!gensec_have_feature(conn->gensec, GENSEC_FEATURE_SIGN) && !gensec_have_feature(conn->gensec, GENSEC_FEATURE_SEAL)) { return NT_STATUS_OK; } status = gensec_create_tstream(conn->sockets.raw, conn->gensec, conn->sockets.raw, &conn->sockets.sasl); if (!NT_STATUS_IS_OK(status)) { goto failed; } conn->sockets.active = conn->sockets.sasl; return NT_STATUS_OK; failed: talloc_free(tmp_ctx); talloc_free(conn->gensec); conn->gensec = NULL; return status; }
static PyObject *py_creds_wrong_password(PyObject *self, PyObject *unused) { return PyBool_FromLong(cli_credentials_wrong_password(PyCredentials_AsCliCredentials(self))); }