r23779: Change from v2 or later to v3 or later.
[samba.git] / source3 / libads / kerberos_verify.c
index dc2f2a1e788acfcfc6677b0c8ac5b5958b32e171..12e8ee9955b68b8f8ba3cc0281fc902323a4264c 100644 (file)
@@ -7,10 +7,11 @@
    Copyright (C) Guenther Deschner 2003, 2005
    Copyright (C) Jim McDonough (jmcd@us.ibm.com) 2003
    Copyright (C) Andrew Bartlett <abartlet@samba.org> 2004-2005
+   Copyright (C) Jeremy Allison 2007
    
    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 2 of the License, or
+   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,
@@ -37,9 +38,12 @@ const krb5_data *krb5_princ_component(krb5_context, krb5_principal, int );
  ads_keytab_add_entry function for details.
 ***********************************************************************************/
 
-static BOOL ads_keytab_verify_ticket(krb5_context context, krb5_auth_context auth_context,
-                       const DATA_BLOB *ticket, krb5_data *p_packet, krb5_ticket **pp_tkt, 
-                       krb5_keyblock **keyblock)
+static BOOL ads_keytab_verify_ticket(krb5_context context,
+                                       krb5_auth_context auth_context,
+                                       const DATA_BLOB *ticket,
+                                       krb5_ticket **pp_tkt,
+                                       krb5_keyblock **keyblock,
+                                       krb5_error_code *perr)
 {
        krb5_error_code ret = 0;
        BOOL auth_ok = False;
@@ -51,6 +55,11 @@ static BOOL ads_keytab_verify_ticket(krb5_context context, krb5_auth_context aut
        fstring my_name, my_fqdn;
        int i;
        int number_matched_principals = 0;
+       krb5_data packet;
+
+       *pp_tkt = NULL;
+       *keyblock = NULL;
+       *perr = 0;
 
        /* Generate the list of principal names which we expect
         * clients might want to use for authenticating to the file
@@ -72,9 +81,9 @@ static BOOL ads_keytab_verify_ticket(krb5_context context, krb5_auth_context aut
        ZERO_STRUCT(kt_entry);
        ZERO_STRUCT(kt_cursor);
 
-       ret = krb5_kt_default(context, &keytab);
+       ret = smb_krb5_open_keytab(context, NULL, False, &keytab);
        if (ret) {
-               DEBUG(1, ("ads_keytab_verify_ticket: krb5_kt_default failed (%s)\n", error_message(ret)));
+               DEBUG(1, ("ads_keytab_verify_ticket: smb_krb5_open_keytab failed (%s)\n", error_message(ret)));
                goto out;
        }
 
@@ -88,71 +97,70 @@ static BOOL ads_keytab_verify_ticket(krb5_context context, krb5_auth_context aut
                goto out;
        }
   
-       if (ret != KRB5_KT_END && ret != ENOENT ) {
-               while (!auth_ok && (krb5_kt_next_entry(context, keytab, &kt_entry, &kt_cursor) == 0)) {
-                       ret = smb_krb5_unparse_name(context, kt_entry.principal, &entry_princ_s);
-                       if (ret) {
-                               DEBUG(1, ("ads_keytab_verify_ticket: smb_krb5_unparse_name failed (%s)\n",
-                                       error_message(ret)));
-                               goto out;
+       while (!auth_ok && (krb5_kt_next_entry(context, keytab, &kt_entry, &kt_cursor) == 0)) {
+               ret = smb_krb5_unparse_name(context, kt_entry.principal, &entry_princ_s);
+               if (ret) {
+                       DEBUG(1, ("ads_keytab_verify_ticket: smb_krb5_unparse_name failed (%s)\n",
+                               error_message(ret)));
+                       goto out;
+               }
+
+               for (i = 0; i < ARRAY_SIZE(valid_princ_formats); i++) {
+
+                       if (!strequal(entry_princ_s, valid_princ_formats[i])) {
+                               continue;
                        }
 
-                       for (i = 0; i < sizeof(valid_princ_formats) / sizeof(valid_princ_formats[0]); i++) {
-                               if (strequal(entry_princ_s, valid_princ_formats[i])) {
-                                       number_matched_principals++;
-                                       p_packet->length = ticket->length;
-                                       p_packet->data = (char *)ticket->data;
-                                       *pp_tkt = NULL;
-
-                                       ret = krb5_rd_req_return_keyblock_from_keytab(context, &auth_context, p_packet,
-                                                                                     kt_entry.principal, keytab,
-                                                                                     NULL, pp_tkt, keyblock);
-
-                                       if (ret) {
-                                               DEBUG(10,("ads_keytab_verify_ticket: "
-                                                       "krb5_rd_req_return_keyblock_from_keytab(%s) failed: %s\n",
-                                                       entry_princ_s, error_message(ret)));
-
-                                               /* workaround for MIT: 
-                                                * as krb5_ktfile_get_entry will
-                                                * explicitly close the
-                                                * krb5_keytab as soon as
-                                                * krb5_rd_req has sucessfully
-                                                * decrypted the ticket but the
-                                                * ticket is not valid yet (due
-                                                * to clockskew) there is no
-                                                * point in querying more
-                                                * keytab entries - Guenther */
-                                               
-                                               if (ret == KRB5KRB_AP_ERR_TKT_NYV || 
-                                                   ret == KRB5KRB_AP_ERR_TKT_EXPIRED) {
-                                                       break;
-                                               }
-                                       } else {
-                                               DEBUG(3,("ads_keytab_verify_ticket: "
-                                                       "krb5_rd_req_return_keyblock_from_keytab succeeded for principal %s\n",
-                                                       entry_princ_s));
-                                               auth_ok = True;
-                                               break;
-                                       }
+                       number_matched_principals++;
+                       packet.length = ticket->length;
+                       packet.data = (char *)ticket->data;
+                       *pp_tkt = NULL;
+
+                       ret = krb5_rd_req_return_keyblock_from_keytab(context, &auth_context, &packet,
+                                                                     kt_entry.principal, keytab,
+                                                                     NULL, pp_tkt, keyblock);
+
+                       if (ret) {
+                               DEBUG(10,("ads_keytab_verify_ticket: "
+                                       "krb5_rd_req_return_keyblock_from_keytab(%s) failed: %s\n",
+                                       entry_princ_s, error_message(ret)));
+
+                               /* workaround for MIT: 
+                               * as krb5_ktfile_get_entry will explicitly
+                               * close the krb5_keytab as soon as krb5_rd_req
+                               * has sucessfully decrypted the ticket but the
+                               * ticket is not valid yet (due to clockskew)
+                               * there is no point in querying more keytab
+                               * entries - Guenther */
+                                       
+                               if (ret == KRB5KRB_AP_ERR_TKT_NYV || 
+                                   ret == KRB5KRB_AP_ERR_TKT_EXPIRED ||
+                                   ret == KRB5KRB_AP_ERR_SKEW) {
+                                       break;
                                }
+                       } else {
+                               DEBUG(3,("ads_keytab_verify_ticket: "
+                                       "krb5_rd_req_return_keyblock_from_keytab succeeded for principal %s\n",
+                                       entry_princ_s));
+                               auth_ok = True;
+                               break;
                        }
+               }
 
-                       /* Free the name we parsed. */
-                       SAFE_FREE(entry_princ_s);
+               /* Free the name we parsed. */
+               SAFE_FREE(entry_princ_s);
 
-                       /* Free the entry we just read. */
-                       smb_krb5_kt_free_entry(context, &kt_entry);
-                       ZERO_STRUCT(kt_entry);
-               }
-               krb5_kt_end_seq_get(context, keytab, &kt_cursor);
+               /* Free the entry we just read. */
+               smb_krb5_kt_free_entry(context, &kt_entry);
+               ZERO_STRUCT(kt_entry);
        }
+       krb5_kt_end_seq_get(context, keytab, &kt_cursor);
 
        ZERO_STRUCT(kt_cursor);
 
   out:
        
-       for (i = 0; i < sizeof(valid_princ_formats) / sizeof(valid_princ_formats[0]); i++) {
+       for (i = 0; i < ARRAY_SIZE(valid_princ_formats); i++) {
                SAFE_FREE(valid_princ_formats[i]);
        }
        
@@ -186,6 +194,7 @@ static BOOL ads_keytab_verify_ticket(krb5_context context, krb5_auth_context aut
        if (keytab) {
                krb5_kt_close(context, keytab);
        }
+       *perr = ret;
        return auth_ok;
 }
 
@@ -193,32 +202,44 @@ static BOOL ads_keytab_verify_ticket(krb5_context context, krb5_auth_context aut
  Try to verify a ticket using the secrets.tdb.
 ***********************************************************************************/
 
-static BOOL ads_secrets_verify_ticket(krb5_context context, krb5_auth_context auth_context,
-                                     krb5_principal host_princ,
-                                     const DATA_BLOB *ticket, krb5_data *p_packet, krb5_ticket **pp_tkt,
-                                     krb5_keyblock **keyblock)
+static krb5_error_code ads_secrets_verify_ticket(krb5_context context,
+                                               krb5_auth_context auth_context,
+                                               krb5_principal host_princ,
+                                               const DATA_BLOB *ticket,
+                                               krb5_ticket **pp_tkt,
+                                               krb5_keyblock **keyblock,
+                                               krb5_error_code *perr)
 {
        krb5_error_code ret = 0;
        BOOL auth_ok = False;
        char *password_s = NULL;
        krb5_data password;
-       krb5_enctype enctypes[4] = { ENCTYPE_DES_CBC_CRC, ENCTYPE_DES_CBC_MD5, 0, 0 };
-       int i;
-
+       krb5_enctype enctypes[] = { 
 #if defined(ENCTYPE_ARCFOUR_HMAC)
-       enctypes[2] = ENCTYPE_ARCFOUR_HMAC;
+               ENCTYPE_ARCFOUR_HMAC,
 #endif
+               ENCTYPE_DES_CBC_CRC, 
+               ENCTYPE_DES_CBC_MD5, 
+               ENCTYPE_NULL
+       };
+       krb5_data packet;
+       int i;
+
+       *pp_tkt = NULL;
+       *keyblock = NULL;
+       *perr = 0;
 
-       ZERO_STRUCTP(keyblock);
 
        if (!secrets_init()) {
                DEBUG(1,("ads_secrets_verify_ticket: secrets_init failed\n"));
+               *perr = KRB5_CONFIG_CANTOPEN;
                return False;
        }
 
        password_s = secrets_fetch_machine_password(lp_workgroup(), NULL, NULL);
        if (!password_s) {
                DEBUG(1,("ads_secrets_verify_ticket: failed to fetch machine password\n"));
+               *perr = KRB5_LIBOS_CANTREADPWD;
                return False;
        }
 
@@ -227,14 +248,15 @@ static BOOL ads_secrets_verify_ticket(krb5_context context, krb5_auth_context au
 
        /* CIFS doesn't use addresses in tickets. This would break NAT. JRA */
 
-       p_packet->length = ticket->length;
-       p_packet->data = (char *)ticket->data;
+       packet.length = ticket->length;
+       packet.data = (char *)ticket->data;
 
        /* We need to setup a auth context with each possible encoding type in turn. */
        for (i=0;enctypes[i];i++) {
                krb5_keyblock *key = NULL;
 
                if (!(key = SMB_MALLOC_P(krb5_keyblock))) {
+                       ret = ENOMEM;
                        goto out;
                }
        
@@ -245,7 +267,7 @@ static BOOL ads_secrets_verify_ticket(krb5_context context, krb5_auth_context au
 
                krb5_auth_con_setuseruserkey(context, auth_context, key);
 
-               if (!(ret = krb5_rd_req(context, &auth_context, p_packet, 
+               if (!(ret = krb5_rd_req(context, &auth_context, &packet, 
                                        NULL,
                                        NULL, NULL, pp_tkt))) {
                        DEBUG(10,("ads_secrets_verify_ticket: enc type [%u] decrypted message !\n",
@@ -262,7 +284,8 @@ static BOOL ads_secrets_verify_ticket(krb5_context context, krb5_auth_context au
 
                /* successfully decrypted but ticket is just not valid at the moment */
                if (ret == KRB5KRB_AP_ERR_TKT_NYV || 
-                   ret == KRB5KRB_AP_ERR_TKT_EXPIRED) {
+                   ret == KRB5KRB_AP_ERR_TKT_EXPIRED ||
+                   ret == KRB5KRB_AP_ERR_SKEW) {
                        break;
                }
 
@@ -272,7 +295,7 @@ static BOOL ads_secrets_verify_ticket(krb5_context context, krb5_auth_context au
 
  out:
        SAFE_FREE(password_s);
-
+       *perr = ret;
        return auth_ok;
 }
 
@@ -282,11 +305,14 @@ static BOOL ads_secrets_verify_ticket(krb5_context context, krb5_auth_context au
 ***********************************************************************************/
 
 NTSTATUS ads_verify_ticket(TALLOC_CTX *mem_ctx,
-                          const char *realm, time_t time_offset,
-                          const DATA_BLOB *ticket, 
-                          char **principal, PAC_DATA **pac_data,
+                          const char *realm,
+                          time_t time_offset,
+                          const DATA_BLOB *ticket,
+                          char **principal,
+                          PAC_DATA **pac_data,
                           DATA_BLOB *ap_rep,
-                          DATA_BLOB *session_key)
+                          DATA_BLOB *session_key,
+                          BOOL use_replay_cache)
 {
        NTSTATUS sret = NT_STATUS_LOGON_FAILURE;
        NTSTATUS pac_ret;
@@ -298,20 +324,22 @@ NTSTATUS ads_verify_ticket(TALLOC_CTX *mem_ctx,
        krb5_rcache rcache = NULL;
        krb5_keyblock *keyblock = NULL;
        time_t authtime;
-       int ret;
-
+       krb5_error_code ret = 0;
+       int flags = 0;  
        krb5_principal host_princ = NULL;
        krb5_const_principal client_principal = NULL;
        char *host_princ_s = NULL;
-       BOOL got_replay_mutex = False;
-
        BOOL auth_ok = False;
+       BOOL got_replay_mutex = False;
        BOOL got_auth_data = False;
 
        ZERO_STRUCT(packet);
        ZERO_STRUCT(auth_data);
-       ZERO_STRUCTP(ap_rep);
-       ZERO_STRUCTP(session_key);
+
+       *principal = NULL;
+       *pac_data = NULL;
+       *ap_rep = data_blob_null;
+       *session_key = data_blob_null;
 
        initialize_krb5_error_table();
        ret = krb5_init_context(&context);
@@ -340,7 +368,18 @@ NTSTATUS ads_verify_ticket(TALLOC_CTX *mem_ctx,
                goto out;
        }
 
+       krb5_auth_con_getflags( context, auth_context, &flags );
+       if ( !use_replay_cache ) {
+               /* Disable default use of a replay cache */
+               flags &= ~KRB5_AUTH_CONTEXT_DO_TIME;
+               krb5_auth_con_setflags( context, auth_context, flags );
+       }
+
        asprintf(&host_princ_s, "%s$", global_myname());
+       if (!host_princ_s) {
+               goto out;
+       }
+
        strlower_m(host_princ_s);
        ret = smb_krb5_parse_name(context, host_princ_s, &host_princ);
        if (ret) {
@@ -350,53 +389,75 @@ NTSTATUS ads_verify_ticket(TALLOC_CTX *mem_ctx,
        }
 
 
-       /* Lock a mutex surrounding the replay as there is no locking in the MIT krb5
-        * code surrounding the replay cache... */
+       if ( use_replay_cache ) {
+               
+               /* Lock a mutex surrounding the replay as there is no 
+                  locking in the MIT krb5 code surrounding the replay 
+                  cache... */
 
-       if (!grab_server_mutex("replay cache mutex")) {
-               DEBUG(1,("ads_verify_ticket: unable to protect replay cache with mutex.\n"));
-               goto out;
-       }
-
-       got_replay_mutex = True;
+               if (!grab_server_mutex("replay cache mutex")) {
+                       DEBUG(1,("ads_verify_ticket: unable to protect "
+                                "replay cache with mutex.\n"));
+                       ret = KRB5_CC_IO;
+                       goto out;
+               }
 
-       /*
-        * JRA. We must set the rcache here. This will prevent replay attacks.
-        */
+               got_replay_mutex = True;
+
+               /* JRA. We must set the rcache here. This will prevent 
+                  replay attacks. */
+               
+               ret = krb5_get_server_rcache(context, 
+                                            krb5_princ_component(context, host_princ, 0), 
+                                            &rcache);
+               if (ret) {
+                       DEBUG(1,("ads_verify_ticket: krb5_get_server_rcache "
+                                "failed (%s)\n", error_message(ret)));
+                       goto out;
+               }
 
-       ret = krb5_get_server_rcache(context, krb5_princ_component(context, host_princ, 0), &rcache);
-       if (ret) {
-               DEBUG(1,("ads_verify_ticket: krb5_get_server_rcache failed (%s)\n", error_message(ret)));
-               goto out;
+               ret = krb5_auth_con_setrcache(context, auth_context, rcache);
+               if (ret) {
+                       DEBUG(1,("ads_verify_ticket: krb5_auth_con_setrcache "
+                                "failed (%s)\n", error_message(ret)));
+                       goto out;
+               }
        }
 
-       ret = krb5_auth_con_setrcache(context, auth_context, rcache);
-       if (ret) {
-               DEBUG(1,("ads_verify_ticket: krb5_auth_con_setrcache failed (%s)\n", error_message(ret)));
-               goto out;
-       }
+       /* Try secrets.tdb first and fallback to the krb5.keytab if
+          necessary */
 
-       if (lp_use_kerberos_keytab()) {
-               auth_ok = ads_keytab_verify_ticket(context, auth_context, ticket, &packet, &tkt, &keyblock);
-       }
-       if (!auth_ok) {
-               auth_ok = ads_secrets_verify_ticket(context, auth_context, host_princ,
-                                                   ticket, &packet, &tkt, &keyblock);
-       }
+        auth_ok = ads_secrets_verify_ticket(context, auth_context, host_princ,
+                                           ticket, &tkt, &keyblock, &ret);
 
-       release_server_mutex();
-       got_replay_mutex = False;
+       if (!auth_ok && lp_use_kerberos_keytab()) {
+               auth_ok = ads_keytab_verify_ticket(context, auth_context, 
+                                                  ticket, &tkt, &keyblock, &ret);
+       }
 
+       if ( use_replay_cache ) {               
+               release_server_mutex();
+               got_replay_mutex = False;
 #if 0
-       /* Heimdal leaks here, if we fix the leak, MIT crashes */
-       if (rcache) {
-               krb5_rc_close(context, rcache);
-       }
+               /* Heimdal leaks here, if we fix the leak, MIT crashes */
+               if (rcache) {
+                       krb5_rc_close(context, rcache);
+               }
 #endif
+       }       
 
        if (!auth_ok) {
                DEBUG(3,("ads_verify_ticket: krb5_rd_req with auth failed (%s)\n", 
                         error_message(ret)));
+               /* Try map the error return in case it's something like
+                * a clock skew error.
+                */
+               sret = krb5_to_nt_status(ret);
+               if (NT_STATUS_IS_OK(sret) || NT_STATUS_EQUAL(sret,NT_STATUS_UNSUCCESSFUL)) {
+                       sret = NT_STATUS_LOGON_FAILURE;
+               }
+               DEBUG(10,("ads_verify_ticket: returning error %s\n",
+                       nt_errstr(sret) ));
                goto out;
        } 
        
@@ -411,8 +472,10 @@ NTSTATUS ads_verify_ticket(TALLOC_CTX *mem_ctx,
        }
 
        *ap_rep = data_blob(packet.data, packet.length);
-       SAFE_FREE(packet.data);
-       packet.length = 0;
+       if (packet.data) {
+               kerberos_free_data_contents(context, &packet);
+               ZERO_STRUCT(packet);
+       }
 
        get_krb5_smb_session_key(context, auth_context, session_key, True);
        dump_data_pw("SMB session key (from ticket)\n", session_key->data, session_key->length);