TODO downgrade on LOGON_FAILURE??? gensec: move event-using code to gensec_update...
[metze/samba/wip.git] / source4 / auth / gensec / gensec_krb5.c
index ff26018ae233924b2cbb2cbd05b8ce4e77e3e973..f17245ccec0d83b95e094aca3d6989a74d54a652 100644 (file)
 #include "lib/tsocket/tsocket.h"
 #include "librpc/rpc/dcerpc.h"
 #include "auth/credentials/credentials.h"
+#include "auth/credentials/credentials_krb5.h"
+#include "auth/kerberos/kerberos_credentials.h"
 #include "auth/gensec/gensec.h"
 #include "auth/gensec/gensec_proto.h"
+#include "auth/gensec/gensec_toplevel_proto.h"
 #include "param/param.h"
 #include "auth/auth_sam_reply.h"
+#include "lib/util/util_net.h"
+
+_PUBLIC_ NTSTATUS gensec_krb5_init(void);
 
 enum GENSEC_KRB5_STATE {
        GENSEC_KRB5_SERVER_START,
@@ -44,8 +50,6 @@ enum GENSEC_KRB5_STATE {
 };
 
 struct gensec_krb5_state {
-       DATA_BLOB session_key;
-       DATA_BLOB pac;
        enum GENSEC_KRB5_STATE state_position;
        struct smb_krb5_context *smb_krb5_context;
        krb5_auth_context auth_context;
@@ -90,8 +94,7 @@ static NTSTATUS gensec_krb5_start(struct gensec_security *gensec_security, bool
        krb5_error_code ret;
        struct gensec_krb5_state *gensec_krb5_state;
        struct cli_credentials *creds;
-       const struct socket_address *peer_addr;
-       const struct tsocket_address *tlocal_addr;
+       const struct tsocket_address *tlocal_addr, *tremote_addr;
        krb5_address my_krb5_addr, peer_krb5_addr;
        
        creds = gensec_get_credentials(gensec_security);
@@ -110,14 +113,11 @@ static NTSTATUS gensec_krb5_start(struct gensec_security *gensec_security, bool
        gensec_krb5_state->ticket = NULL;
        ZERO_STRUCT(gensec_krb5_state->enc_ticket);
        gensec_krb5_state->keyblock = NULL;
-       gensec_krb5_state->session_key = data_blob(NULL, 0);
-       gensec_krb5_state->pac = data_blob(NULL, 0);
        gensec_krb5_state->gssapi = gssapi;
 
        talloc_set_destructor(gensec_krb5_state, gensec_krb5_destroy); 
 
        if (cli_credentials_get_krb5_context(creds, 
-                                            gensec_security->event_ctx, 
                                             gensec_security->settings->lp_ctx, &gensec_krb5_state->smb_krb5_context)) {
                talloc_free(gensec_krb5_state);
                return NT_STATUS_INTERNAL_ERROR;
@@ -152,6 +152,7 @@ static NTSTATUS gensec_krb5_start(struct gensec_security *gensec_security, bool
                                (struct sockaddr *) &ss,
                                sizeof(struct sockaddr_storage));
                if (socklen < 0) {
+                       talloc_free(gensec_krb5_state);
                        return NT_STATUS_INTERNAL_ERROR;
                }
                ret = krb5_sockaddr2address(gensec_krb5_state->smb_krb5_context->krb5_context,
@@ -165,10 +166,20 @@ static NTSTATUS gensec_krb5_start(struct gensec_security *gensec_security, bool
                }
        }
 
-       peer_addr = gensec_get_peer_addr(gensec_security);
-       if (peer_addr && peer_addr->sockaddr) {
-               ret = krb5_sockaddr2address(gensec_krb5_state->smb_krb5_context->krb5_context, 
-                                           peer_addr->sockaddr, &peer_krb5_addr);
+       tremote_addr = gensec_get_remote_address(gensec_security);
+       if (tremote_addr) {
+               ssize_t socklen;
+               struct sockaddr_storage ss;
+
+               socklen = tsocket_address_bsd_sockaddr(tremote_addr,
+                               (struct sockaddr *) &ss,
+                               sizeof(struct sockaddr_storage));
+               if (socklen < 0) {
+                       talloc_free(gensec_krb5_state);
+                       return NT_STATUS_INTERNAL_ERROR;
+               }
+               ret = krb5_sockaddr2address(gensec_krb5_state->smb_krb5_context->krb5_context,
+                               (const struct sockaddr *) &ss, &peer_krb5_addr);
                if (ret) {
                        DEBUG(1,("gensec_krb5_start: krb5_sockaddr2address (local) failed (%s)\n", 
                                 smb_get_krb5_error_message(gensec_krb5_state->smb_krb5_context->krb5_context, 
@@ -181,7 +192,7 @@ static NTSTATUS gensec_krb5_start(struct gensec_security *gensec_security, bool
        ret = krb5_auth_con_setaddrs(gensec_krb5_state->smb_krb5_context->krb5_context, 
                                     gensec_krb5_state->auth_context,
                                     tlocal_addr ? &my_krb5_addr : NULL,
-                                    peer_addr ? &peer_krb5_addr : NULL);
+                                    tremote_addr ? &peer_krb5_addr : NULL);
        if (ret) {
                DEBUG(1,("gensec_krb5_start: krb5_auth_con_setaddrs failed (%s)\n", 
                         smb_get_krb5_error_message(gensec_krb5_state->smb_krb5_context->krb5_context, 
@@ -221,15 +232,9 @@ static NTSTATUS gensec_fake_gssapi_krb5_server_start(struct gensec_security *gen
 
 static NTSTATUS gensec_krb5_common_client_start(struct gensec_security *gensec_security, bool gssapi)
 {
+       const char *hostname;
        struct gensec_krb5_state *gensec_krb5_state;
-       krb5_error_code ret;
        NTSTATUS nt_status;
-       struct ccache_container *ccache_container;
-       const char *hostname;
-
-       const char *principal;
-       krb5_data in_data;
-
        hostname = gensec_get_target_hostname(gensec_security);
        if (!hostname) {
                DEBUG(1, ("Could not determine hostname for target computer, cannot use kerberos\n"));
@@ -264,27 +269,54 @@ static NTSTATUS gensec_krb5_common_client_start(struct gensec_security *gensec_s
                        gensec_krb5_state->ap_req_options |= AP_OPTS_MUTUAL_REQUIRED;
                }
        }
+       return NT_STATUS_OK;
+}
+
+static NTSTATUS gensec_krb5_common_client_creds(struct gensec_security *gensec_security, bool gssapi)
+{
+       struct gensec_krb5_state *gensec_krb5_state;
+       krb5_error_code ret;
+       struct ccache_container *ccache_container;
+       const char *error_string;
+       const char *principal;
+       const char *hostname;
+       krb5_data in_data;
+       struct tevent_context *previous_ev;
+
+       gensec_krb5_state = (struct gensec_krb5_state *)gensec_security->private_data;
 
        principal = gensec_get_target_principal(gensec_security);
+       hostname = gensec_get_target_hostname(gensec_security);
 
        ret = cli_credentials_get_ccache(gensec_get_credentials(gensec_security), 
                                         gensec_security->event_ctx, 
-                                        gensec_security->settings->lp_ctx, &ccache_container);
+                                        gensec_security->settings->lp_ctx, &ccache_container, &error_string);
        switch (ret) {
        case 0:
                break;
        case KRB5KDC_ERR_PREAUTH_FAILED:
+       case KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN:
                return NT_STATUS_LOGON_FAILURE;
        case KRB5_KDC_UNREACH:
-               DEBUG(3, ("Cannot reach a KDC we require to contact %s\n", principal));
+               DEBUG(3, ("Cannot reach a KDC we require to contact %s: %s\n", principal, error_string));
+               return NT_STATUS_INVALID_PARAMETER; /* Make SPNEGO ignore us, we can't go any further here */
+       case KRB5_CC_NOTFOUND:
+       case KRB5_CC_END:
+               DEBUG(3, ("Error preparing credentials we require to contact %s : %s\n", principal, error_string));
                return NT_STATUS_INVALID_PARAMETER; /* Make SPNEGO ignore us, we can't go any further here */
        default:
-               DEBUG(1, ("gensec_krb5_start: Aquiring initiator credentials failed: %s\n", error_message(ret)));
+               DEBUG(1, ("gensec_krb5_start: Aquiring initiator credentials failed: %s\n", error_string));
                return NT_STATUS_UNSUCCESSFUL;
        }
        in_data.length = 0;
        
-       if (principal && lp_client_use_spnego_principal(gensec_security->settings->lp_ctx)) {
+       /* Do this every time, in case we have weird recursive issues here */
+       ret = smb_krb5_context_set_event_ctx(gensec_krb5_state->smb_krb5_context, gensec_security->event_ctx, &previous_ev);
+       if (ret != 0) {
+               DEBUG(1, ("gensec_krb5_start: Setting event context failed\n"));
+               return NT_STATUS_NO_MEMORY;
+       }
+       if (principal) {
                krb5_principal target_principal;
                ret = krb5_parse_name(gensec_krb5_state->smb_krb5_context->krb5_context, principal,
                                      &target_principal);
@@ -307,6 +339,9 @@ static NTSTATUS gensec_krb5_common_client_start(struct gensec_security *gensec_s
                                  &in_data, ccache_container->ccache, 
                                  &gensec_krb5_state->enc_ticket);
        }
+
+       smb_krb5_context_remove_event_ctx(gensec_krb5_state->smb_krb5_context, previous_ev, gensec_security->event_ctx);
+
        switch (ret) {
        case 0:
                return NT_STATUS_OK;
@@ -399,6 +434,11 @@ static NTSTATUS gensec_krb5_update(struct gensec_security *gensec_security,
        {
                DATA_BLOB unwrapped_out;
                
+               nt_status = gensec_krb5_common_client_creds(gensec_security, gensec_krb5_state->gssapi);
+               if (!NT_STATUS_IS_OK(nt_status)) {
+                       return nt_status;
+               }
+
                if (gensec_krb5_state->gssapi) {
                        unwrapped_out = data_blob_talloc(out_mem_ctx, gensec_krb5_state->enc_ticket.data, gensec_krb5_state->enc_ticket.length);
                        
@@ -464,6 +504,8 @@ static NTSTATUS gensec_krb5_update(struct gensec_security *gensec_security,
                uint8_t tok_id[2];
                struct keytab_container *keytab;
                krb5_principal server_in_keytab;
+               const char *error_string;
+               enum credentials_obtained obtained;
 
                if (!in.data) {
                        return NT_STATUS_INVALID_PARAMETER;
@@ -471,7 +513,6 @@ static NTSTATUS gensec_krb5_update(struct gensec_security *gensec_security,
 
                /* Grab the keytab, however generated */
                ret = cli_credentials_get_keytab(gensec_get_credentials(gensec_security), 
-                                                gensec_security->event_ctx, 
                                                 gensec_security->settings->lp_ctx, &keytab);
                if (ret) {
                        return NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
@@ -480,9 +521,10 @@ static NTSTATUS gensec_krb5_update(struct gensec_security *gensec_security,
                /* This ensures we lookup the correct entry in that keytab */
                ret = principal_from_credentials(out_mem_ctx, gensec_get_credentials(gensec_security), 
                                                 gensec_krb5_state->smb_krb5_context, 
-                                                &server_in_keytab);
+                                                &server_in_keytab, &obtained, &error_string);
 
                if (ret) {
+                       DEBUG(2,("Failed to make credentials from principal: %s\n", error_string));
                        return NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
                }
 
@@ -527,6 +569,7 @@ static NTSTATUS gensec_krb5_update(struct gensec_security *gensec_security,
 }
 
 static NTSTATUS gensec_krb5_session_key(struct gensec_security *gensec_security, 
+                                       TALLOC_CTX *mem_ctx,
                                        DATA_BLOB *session_key) 
 {
        struct gensec_krb5_state *gensec_krb5_state = (struct gensec_krb5_state *)gensec_security->private_data;
@@ -539,11 +582,6 @@ static NTSTATUS gensec_krb5_session_key(struct gensec_security *gensec_security,
                return NT_STATUS_NO_USER_SESSION_KEY;
        }
 
-       if (gensec_krb5_state->session_key.data) {
-               *session_key = gensec_krb5_state->session_key;
-               return NT_STATUS_OK;
-       }
-
        switch (gensec_security->gensec_role) {
        case GENSEC_CLIENT:
                err = krb5_auth_con_getlocalsubkey(context, auth_context, &skey);
@@ -555,9 +593,8 @@ static NTSTATUS gensec_krb5_session_key(struct gensec_security *gensec_security,
        if (err == 0 && skey != NULL) {
                DEBUG(10, ("Got KRB5 session key of length %d\n",  
                           (int)KRB5_KEY_LENGTH(skey)));
-               gensec_krb5_state->session_key = data_blob_talloc(gensec_krb5_state, 
-                                               KRB5_KEY_DATA(skey), KRB5_KEY_LENGTH(skey));
-               *session_key = gensec_krb5_state->session_key;
+               *session_key = data_blob_talloc(mem_ctx,
+                                              KRB5_KEY_DATA(skey), KRB5_KEY_LENGTH(skey));
                dump_data_pw("KRB5 Session Key:\n", session_key->data, session_key->length);
 
                krb5_free_keyblock(context, skey);
@@ -569,12 +606,13 @@ static NTSTATUS gensec_krb5_session_key(struct gensec_security *gensec_security,
 }
 
 static NTSTATUS gensec_krb5_session_info(struct gensec_security *gensec_security,
+                                        TALLOC_CTX *mem_ctx_out,
                                         struct auth_session_info **_session_info) 
 {
        NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL;
        struct gensec_krb5_state *gensec_krb5_state = (struct gensec_krb5_state *)gensec_security->private_data;
        krb5_context context = gensec_krb5_state->smb_krb5_context->krb5_context;
-       struct auth_serversupplied_info *server_info = NULL;
+       struct auth_user_info_dc *user_info_dc = NULL;
        struct auth_session_info *session_info = NULL;
        struct PAC_LOGON_INFO *logon_info;
 
@@ -586,7 +624,7 @@ static NTSTATUS gensec_krb5_session_info(struct gensec_security *gensec_security
 
        krb5_error_code ret;
 
-       TALLOC_CTX *mem_ctx = talloc_new(gensec_security);
+       TALLOC_CTX *mem_ctx = talloc_new(mem_ctx_out);
        if (!mem_ctx) {
                return NT_STATUS_NO_MEMORY;
        }
@@ -606,6 +644,7 @@ static NTSTATUS gensec_krb5_session_info(struct gensec_security *gensec_security
                DEBUG(1, ("Unable to parse client principal: %s\n",
                          smb_get_krb5_error_message(context, 
                                                     ret, mem_ctx)));
+               krb5_free_principal(context, client_principal);
                talloc_free(mem_ctx);
                return NT_STATUS_NO_MEMORY;
        }
@@ -619,8 +658,9 @@ static NTSTATUS gensec_krb5_session_info(struct gensec_security *gensec_security
                          principal_string,
                          smb_get_krb5_error_message(context, 
                                                     ret, mem_ctx)));
-               krb5_free_principal(context, client_principal);
                free(principal_string);
+               krb5_free_principal(context, client_principal);
+               talloc_free(mem_ctx);
                return NT_STATUS_ACCESS_DENIED;
        } else if (ret) {
                /* NO pac */
@@ -632,34 +672,31 @@ static NTSTATUS gensec_krb5_session_info(struct gensec_security *gensec_security
                        DEBUG(1, ("Unable to find PAC for %s, resorting to local user lookup: %s",
                                  principal_string, smb_get_krb5_error_message(context, 
                                                     ret, mem_ctx)));
-                       nt_status = gensec_security->auth_context->get_server_info_principal(mem_ctx, 
+                       nt_status = gensec_security->auth_context->get_user_info_dc_principal(mem_ctx,
                                                                                             gensec_security->auth_context, 
                                                                                             principal_string,
-                                                                                            &server_info);
+                                                                                            NULL, &user_info_dc);
                        if (!NT_STATUS_IS_OK(nt_status)) {
+                               free(principal_string);
+                               krb5_free_principal(context, client_principal);
                                talloc_free(mem_ctx);
                                return nt_status;
                        }
                } else {
                        DEBUG(1, ("Unable to find PAC in ticket from %s, failing to allow access\n",
                                  principal_string));
-                       return NT_STATUS_ACCESS_DENIED;
-               }
-
-               krb5_free_principal(context, client_principal);
-               free(principal_string);
-               
-               if (!NT_STATUS_IS_OK(nt_status)) {
+                       free(principal_string);
+                       krb5_free_principal(context, client_principal);
                        talloc_free(mem_ctx);
-                       return nt_status;
+                       return NT_STATUS_ACCESS_DENIED;
                }
        } else {
                /* Found pac */
                union netr_Validation validation;
-               free(principal_string);
 
                pac = data_blob_talloc(mem_ctx, pac_data.data, pac_data.length);
                if (!pac.data) {
+                       free(principal_string);
                        krb5_free_principal(context, client_principal);
                        talloc_free(mem_ctx);
                        return NT_STATUS_NO_MEMORY;
@@ -667,48 +704,53 @@ static NTSTATUS gensec_krb5_session_info(struct gensec_security *gensec_security
 
                /* decode and verify the pac */
                nt_status = kerberos_pac_logon_info(gensec_krb5_state, 
-                                                   gensec_security->settings->iconv_convenience,
-                                                   &logon_info, pac,
+                                                   pac,
                                                    gensec_krb5_state->smb_krb5_context->krb5_context,
                                                    NULL, gensec_krb5_state->keyblock,
                                                    client_principal,
-                                                   gensec_krb5_state->ticket->ticket.authtime, NULL);
-               krb5_free_principal(context, client_principal);
+                                                   gensec_krb5_state->ticket->ticket.authtime, &logon_info);
 
                if (!NT_STATUS_IS_OK(nt_status)) {
+                       free(principal_string);
+                       krb5_free_principal(context, client_principal);
                        talloc_free(mem_ctx);
                        return nt_status;
                }
 
                validation.sam3 = &logon_info->info3;
-               nt_status = make_server_info_netlogon_validation(mem_ctx, 
+               nt_status = make_user_info_dc_netlogon_validation(mem_ctx,
                                                                 NULL,
                                                                 3, &validation,
-                                                                &server_info); 
+                                                                 true, /* This user was authenticated */
+                                                                &user_info_dc);
                if (!NT_STATUS_IS_OK(nt_status)) {
+                       free(principal_string);
+                       krb5_free_principal(context, client_principal);
                        talloc_free(mem_ctx);
                        return nt_status;
                }
        }
 
-       /* references the server_info into the session_info */
-       nt_status = auth_generate_session_info(mem_ctx, gensec_security->event_ctx, gensec_security->settings->lp_ctx, server_info, &session_info);
+       free(principal_string);
+       krb5_free_principal(context, client_principal);
+
+       /* references the user_info_dc into the session_info */
+       nt_status = gensec_generate_session_info(mem_ctx, gensec_security, user_info_dc, &session_info);
 
        if (!NT_STATUS_IS_OK(nt_status)) {
                talloc_free(mem_ctx);
                return nt_status;
        }
 
-       nt_status = gensec_krb5_session_key(gensec_security, &session_info->session_key);
+       nt_status = gensec_krb5_session_key(gensec_security, session_info, &session_info->session_key);
 
        if (!NT_STATUS_IS_OK(nt_status)) {
                talloc_free(mem_ctx);
                return nt_status;
        }
 
-       *_session_info = session_info;
+       *_session_info = talloc_steal(mem_ctx_out, session_info);
 
-       talloc_steal(gensec_krb5_state, session_info);
        talloc_free(mem_ctx);
        return NT_STATUS_OK;
 }