@@ -, +, @@ --- auth/credentials/credentials.h | 3 ++ auth/credentials/credentials_krb5.c | 61 +++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) --- a/auth/credentials/credentials.h +++ a/auth/credentials/credentials.h @@ -182,6 +182,9 @@ int cli_credentials_get_named_ccache(struct cli_credentials *cred, struct loadparm_context *lp_ctx, char *ccache_name, struct ccache_container **ccc, const char **error_string); +bool cli_credentials_failed_kerberos_login(struct cli_credentials *cred, + const char *principal, + unsigned int *count); int cli_credentials_get_keytab(struct cli_credentials *cred, struct loadparm_context *lp_ctx, struct keytab_container **_ktc); --- a/auth/credentials/credentials_krb5.c +++ a/auth/credentials/credentials_krb5.c @@ -212,6 +212,67 @@ _PUBLIC_ int cli_credentials_set_ccache(struct cli_credentials *cred, return 0; } +/* + * Indicate the we failed to log in to this service/host with these + * credentials. The caller passes an unsigned int which they + * initialise to the number of times they would like to retry. + * + * This method is used to support re-trying with freshly fetched + * credentials in case a server is rebuilt while clients have + * non-expired tickets. When the client code gets a logon failure they + * throw away the existing credentials for the server and retry. + */ +_PUBLIC_ bool cli_credentials_failed_kerberos_login(struct cli_credentials *cred, + const char *principal, + unsigned int *count) +{ + struct ccache_container *ccc; + krb5_creds creds, creds2; + int ret; + + if (principal == NULL) { + /* no way to delete if we don't know the principal */ + return false; + } + + ccc = cred->ccache; + if (ccc == NULL) { + /* not a kerberos connection */ + return false; + } + + if (*count > 0) { + /* We have already tried discarding the credentials */ + return false; + } + (*count)++; + + ZERO_STRUCT(creds); + ret = krb5_parse_name(ccc->smb_krb5_context->krb5_context, principal, &creds.server); + if (ret != 0) { + return false; + } + + ret = krb5_cc_retrieve_cred(ccc->smb_krb5_context->krb5_context, ccc->ccache, KRB5_TC_MATCH_SRV_NAMEONLY, &creds, &creds2); + if (ret != 0) { + /* don't retry - we didn't find these credentials to remove */ + return false; + } + + ret = krb5_cc_remove_cred(ccc->smb_krb5_context->krb5_context, ccc->ccache, KRB5_TC_MATCH_SRV_NAMEONLY, &creds); + krb5_free_cred_contents(ccc->smb_krb5_context->krb5_context, &creds2); + if (ret != 0) { + /* don't retry - we didn't find these credentials to + * remove. Note that with the current backend this + * never happens, as it always returns 0 even if the + * creds don't exist, which is why we do a separate + * krb5_cc_retrieve_cred() above. + */ + return false; + } + return true; +} + static int cli_credentials_new_ccache(struct cli_credentials *cred, struct loadparm_context *lp_ctx, -- cope with server changes --- source4/libcli/smb_composite/sesssetup.c | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) --- a/source4/libcli/smb_composite/sesssetup.c +++ a/source4/libcli/smb_composite/sesssetup.c @@ -39,6 +39,7 @@ struct sesssetup_state { NTSTATUS gensec_status; struct smb_composite_sesssetup *io; struct smbcli_request *req; + unsigned int logon_retries; }; static int sesssetup_state_destructor(struct sesssetup_state *state) @@ -123,7 +124,7 @@ static void request_handler(struct smbcli_request *req) 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 */ + /* 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, @@ -144,9 +145,21 @@ static void request_handler(struct smbcli_request *req) 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; - if (cli_credentials_wrong_password(state->io->in.credentials)) { + + 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); -- --- testprogs/blackbox/test_chgdcpass.sh | 5 +++++ 1 file changed, 5 insertions(+) --- a/testprogs/blackbox/test_chgdcpass.sh +++ a/testprogs/blackbox/test_chgdcpass.sh @@ -59,6 +59,11 @@ testit "change dc password" $samba4srcdir/scripting/devel/chgtdcpass -s $PROVDIR #This is important because it shows that the old ticket remains valid (as it must) for incoming connections after the DC password is changed test_smbclient "Test login with kerberos ccache after password change" 'ls' -k yes || failed=`expr $failed + 1` +testit "change dc password (2nd time)" $samba4srcdir/scripting/devel/chgtdcpass -s $PROVDIR/etc/smb.conf || failed=`expr $failed + 1` + +#This is important because it shows that the old ticket remains valid (as it must) for incoming connections after the DC pass +test_smbclient "Test login with kerberos ccache after 2nd password change" 'ls' -k yes || failed=`expr $failed + 1` + #This confirms that the DC password is valid for a kinit too testit "kinit with keytab" $samba4kinit $enctype -t $PROVDIR/private/secrets.keytab --use-keytab $USERNAME || failed=`expr $failed + 1` test_smbclient "Test login with kerberos ccache with fresh kinit" 'ls' -k yes || failed=`expr $failed + 1` -- to cope with stale tickets --- source4/librpc/rpc/dcerpc_util.c | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) --- a/source4/librpc/rpc/dcerpc_util.c +++ a/source4/librpc/rpc/dcerpc_util.c @@ -30,6 +30,7 @@ #include "librpc/gen_ndr/ndr_misc.h" #include "librpc/rpc/dcerpc_proto.h" #include "auth/credentials/credentials.h" +#include "auth/gensec/gensec.h" #include "param/param.h" #include "librpc/rpc/rpc_common.h" @@ -335,6 +336,7 @@ struct pipe_auth_state { const struct ndr_interface_table *table; struct loadparm_context *lp_ctx; struct cli_credentials *credentials; + unsigned int logon_retries; }; @@ -395,7 +397,19 @@ static void continue_auth_auto(struct composite_context *ctx) composite_continue(c, sec_conn_req, continue_ntlmssp_connection, c); return; } else if (NT_STATUS_EQUAL(c->status, NT_STATUS_LOGON_FAILURE)) { - if (cli_credentials_wrong_password(s->credentials)) { + const char *principal; + + principal = gensec_get_target_principal(s->pipe->conn->security_state.generic_state); + if (principal == NULL) { + const char *hostname = gensec_get_target_hostname(s->pipe->conn->security_state.generic_state); + const char *service = gensec_get_target_service(s->pipe->conn->security_state.generic_state); + if (hostname != NULL && service != NULL) { + principal = talloc_asprintf(c, "%s/%s", service, hostname); + } + } + + if (cli_credentials_failed_kerberos_login(s->credentials, principal, &s->logon_retries) || + cli_credentials_wrong_password(s->credentials)) { /* * Retry SPNEGO with a better password * send a request for secondary rpc connection -- RPC --- testprogs/blackbox/test_chgdcpass.sh | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) --- a/testprogs/blackbox/test_chgdcpass.sh +++ a/testprogs/blackbox/test_chgdcpass.sh @@ -45,6 +45,21 @@ test_smbclient() { return $status } +test_drsbind() { + name="$1" + shift + echo "test: $name" + echo $VALGRIND $samba4bindir/samba-tool drs bind $SERVER -k yes $@ + $VALGRIND $samba4bindir/samba-tool drs bind $SERVER -k yes $@ + status=$? + if [ x$status = x0 ]; then + echo "success: $name" + else + echo "failure: $name" + fi + return $status +} + enctype="-e $ENCTYPE" KRB5CCNAME="$PREFIX/tmpccache" @@ -54,16 +69,26 @@ testit "kinit with keytab" $samba4kinit $enctype -t $PROVDIR/private/secrets.key #This is important because it puts the ticket for the old KVNO and password into a local ccache test_smbclient "Test login with kerberos ccache before password change" 'ls' -k yes || failed=`expr $failed + 1` + +#check that drs options works before we change the password (prime the ccache) +test_drsbind "Test drs options with with kerberos ccache" || failed=`expr $failed + 1` + testit "change dc password" $samba4srcdir/scripting/devel/chgtdcpass -s $PROVDIR/etc/smb.conf || failed=`expr $failed + 1` #This is important because it shows that the old ticket remains valid (as it must) for incoming connections after the DC password is changed test_smbclient "Test login with kerberos ccache after password change" 'ls' -k yes || failed=`expr $failed + 1` +#check that drs options works after we change the password +test_drsbind "Test drs options with new password" || failed=`expr $failed + 1` + testit "change dc password (2nd time)" $samba4srcdir/scripting/devel/chgtdcpass -s $PROVDIR/etc/smb.conf || failed=`expr $failed + 1` #This is important because it shows that the old ticket remains valid (as it must) for incoming connections after the DC pass test_smbclient "Test login with kerberos ccache after 2nd password change" 'ls' -k yes || failed=`expr $failed + 1` +#check that drs options works after we change the password a 2nd time +test_drsbind "Test drs options after 2nd password change" || failed=`expr $failed + 1` + #This confirms that the DC password is valid for a kinit too testit "kinit with keytab" $samba4kinit $enctype -t $PROVDIR/private/secrets.keytab --use-keytab $USERNAME || failed=`expr $failed + 1` test_smbclient "Test login with kerberos ccache with fresh kinit" 'ls' -k yes || failed=`expr $failed + 1` -- --- source4/librpc/rpc/dcerpc_util.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) --- a/source4/librpc/rpc/dcerpc_util.c +++ a/source4/librpc/rpc/dcerpc_util.c @@ -396,7 +396,13 @@ static void continue_auth_auto(struct composite_context *ctx) s->binding); composite_continue(c, sec_conn_req, continue_ntlmssp_connection, c); return; - } else if (NT_STATUS_EQUAL(c->status, NT_STATUS_LOGON_FAILURE)) { + } else if (NT_STATUS_EQUAL(c->status, NT_STATUS_LOGON_FAILURE) || + NT_STATUS_EQUAL(c->status, NT_STATUS_UNSUCCESSFUL)) { + /* + try a second time on any error. We don't just do it + on LOGON_FAILURE as some servers will give a + NT_STATUS_UNSUCCESSFUL on a authentication error on RPC + */ const char *principal; principal = gensec_get_target_principal(s->pipe->conn->security_state.generic_state); @@ -408,8 +414,9 @@ static void continue_auth_auto(struct composite_context *ctx) } } - if (cli_credentials_failed_kerberos_login(s->credentials, principal, &s->logon_retries) || - cli_credentials_wrong_password(s->credentials)) { + if ((cli_credentials_failed_kerberos_login(s->credentials, principal, &s->logon_retries) || + cli_credentials_wrong_password(s->credentials)) && + s->binding->endpoint != NULL) { /* * Retry SPNEGO with a better password * send a request for secondary rpc connection -- connection --- source4/scripting/python/samba/netcmd/drs.py | 1 - 1 file changed, 1 deletion(-) --- a/source4/scripting/python/samba/netcmd/drs.py +++ a/source4/scripting/python/samba/netcmd/drs.py @@ -361,7 +361,6 @@ class cmd_drs_bind(Command): self.creds = credopts.get_credentials(self.lp, fallback_machine=True) drsuapi_connect(self) - samdb_connect(self) bind_info = drsuapi.DsBindInfoCtr() bind_info.length = 28 -- fault with access denied --- source4/librpc/rpc/dcerpc.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) --- a/source4/librpc/rpc/dcerpc.c +++ a/source4/librpc/rpc/dcerpc.c @@ -2148,8 +2148,13 @@ static void dcerpc_alter_context_recv_handler(struct rpc_request *subreq, if (pkt->ptype == DCERPC_PKT_FAULT) { DEBUG(5,("dcerpc: alter_resp - rpc fault: %s\n", dcerpc_errstr(state, pkt->u.fault.status))); - state->p->last_fault_code = pkt->u.fault.status; - tevent_req_nterror(req, NT_STATUS_NET_WRITE_FAULT); + if (pkt->u.fault.status == DCERPC_FAULT_ACCESS_DENIED) { + state->p->last_fault_code = pkt->u.fault.status; + tevent_req_nterror(req, NT_STATUS_LOGON_FAILURE); + } else { + state->p->last_fault_code = pkt->u.fault.status; + tevent_req_nterror(req, NT_STATUS_NET_WRITE_FAULT); + } return; } -- --- source4/libcli/ldap/ldap_bind.c | 116 +++++++++++++++++++++++++++------------- 1 file changed, 79 insertions(+), 37 deletions(-) --- a/source4/libcli/ldap/ldap_bind.c +++ a/source4/libcli/ldap/ldap_bind.c @@ -221,9 +221,54 @@ _PUBLIC_ NTSTATUS ldap_bind_sasl(struct ldap_connection *conn, "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; iattributes[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)) { @@ -266,43 +311,6 @@ _PUBLIC_ NTSTATUS ldap_bind_sasl(struct ldap_connection *conn, 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; iattributes[0].num_values; i++) { - sasl_names[i] = (const char *)search->attributes[0].values[i].data; - } - sasl_names[i] = NULL; - 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", @@ -367,6 +375,40 @@ _PUBLIC_ NTSTATUS ldap_bind_sasl(struct ldap_connection *conn, 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); -- change --- testprogs/blackbox/test_chgdcpass.sh | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) --- a/testprogs/blackbox/test_chgdcpass.sh +++ a/testprogs/blackbox/test_chgdcpass.sh @@ -45,12 +45,14 @@ test_smbclient() { return $status } -test_drsbind() { - name="$1" +test_drs() { + function="$1" + name="$2" + shift shift echo "test: $name" - echo $VALGRIND $samba4bindir/samba-tool drs bind $SERVER -k yes $@ - $VALGRIND $samba4bindir/samba-tool drs bind $SERVER -k yes $@ + echo $VALGRIND $samba4bindir/samba-tool drs $function $SERVER -k yes $@ + $VALGRIND $samba4bindir/samba-tool drs $function $SERVER -k yes $@ status=$? if [ x$status = x0 ]; then echo "success: $name" @@ -70,24 +72,33 @@ testit "kinit with keytab" $samba4kinit $enctype -t $PROVDIR/private/secrets.key #This is important because it puts the ticket for the old KVNO and password into a local ccache test_smbclient "Test login with kerberos ccache before password change" 'ls' -k yes || failed=`expr $failed + 1` +#check that drs bind works before we change the password (prime the ccache) +test_drs bind "Test drs bind with with kerberos ccache" || failed=`expr $failed + 1` + #check that drs options works before we change the password (prime the ccache) -test_drsbind "Test drs options with with kerberos ccache" || failed=`expr $failed + 1` +test_drs options "Test drs options with with kerberos ccache" || failed=`expr $failed + 1` testit "change dc password" $samba4srcdir/scripting/devel/chgtdcpass -s $PROVDIR/etc/smb.conf || failed=`expr $failed + 1` #This is important because it shows that the old ticket remains valid (as it must) for incoming connections after the DC password is changed test_smbclient "Test login with kerberos ccache after password change" 'ls' -k yes || failed=`expr $failed + 1` +#check that drs bind works after we change the password +test_drs bind "Test drs bind with new password" || failed=`expr $failed + 1` + #check that drs options works after we change the password -test_drsbind "Test drs options with new password" || failed=`expr $failed + 1` +test_drs options "Test drs options with new password" || failed=`expr $failed + 1` testit "change dc password (2nd time)" $samba4srcdir/scripting/devel/chgtdcpass -s $PROVDIR/etc/smb.conf || failed=`expr $failed + 1` #This is important because it shows that the old ticket remains valid (as it must) for incoming connections after the DC pass test_smbclient "Test login with kerberos ccache after 2nd password change" 'ls' -k yes || failed=`expr $failed + 1` +#check that drs bind works after we change the password a 2nd time +test_drs bind "Test drs bind after 2nd password change" || failed=`expr $failed + 1` + #check that drs options works after we change the password a 2nd time -test_drsbind "Test drs options after 2nd password change" || failed=`expr $failed + 1` +test_drs options "Test drs options after 2nd password change" || failed=`expr $failed + 1` #This confirms that the DC password is valid for a kinit too testit "kinit with keytab" $samba4kinit $enctype -t $PROVDIR/private/secrets.keytab --use-keytab $USERNAME || failed=`expr $failed + 1` --