auth/credentials: don't ignore "client use kerberos" and --use-kerberos for machine...
[samba.git] / source3 / winbindd / winbindd_cred_cache.c
index e63e73221e2cffe0b4c5294b9c9c29707a14a38c..a73afe483fb30624b3d53eb448e0c30c1544e3b5 100644 (file)
@@ -25,6 +25,8 @@
 #include "winbindd.h"
 #include "../libcli/auth/libcli_auth.h"
 #include "smb_krb5.h"
+#include "libads/kerberos_proto.h"
+#include "lib/global_contexts.h"
 
 #undef DBGC_CLASS
 #define DBGC_CLASS DBGC_WINBIND
@@ -37,8 +39,8 @@
 #define MAX_CCACHES 100
 
 static struct WINBINDD_CCACHE_ENTRY *ccache_list;
-static void krb5_ticket_gain_handler(struct event_context *,
-                                    struct timed_event *,
+static void krb5_ticket_gain_handler(struct tevent_context *,
+                                    struct tevent_timer *,
                                     struct timeval,
                                     void *);
 static void add_krb5_ticket_gain_handler_event(struct WINBINDD_CCACHE_ENTRY *,
@@ -47,7 +49,12 @@ static void add_krb5_ticket_gain_handler_event(struct WINBINDD_CCACHE_ENTRY *,
 /* The Krb5 ticket refresh handler should be scheduled
    at one-half of the period from now till the tkt
    expiration */
-#define KRB5_EVENT_REFRESH_TIME(x) ((x) - (((x) - time(NULL))/2))
+
+static time_t krb5_event_refresh_time(time_t end_time)
+{
+       time_t rest = end_time - time(NULL);
+       return end_time - rest/2;
+}
 
 /****************************************************************
  Find an entry by name.
@@ -98,8 +105,8 @@ void ccache_remove_all_after_fork(void)
  Do the work of refreshing the ticket.
 ****************************************************************/
 
-static void krb5_ticket_refresh_handler(struct event_context *event_ctx,
-                                       struct timed_event *te,
+static void krb5_ticket_refresh_handler(struct tevent_context *event_ctx,
+                                       struct tevent_timer *te,
                                        struct timeval now,
                                        void *private_data)
 {
@@ -112,16 +119,15 @@ static void krb5_ticket_refresh_handler(struct event_context *event_ctx,
        struct WINBINDD_MEMORY_CREDS *cred_ptr = entry->cred_ptr;
 #endif
 
-       DEBUG(10,("krb5_ticket_refresh_handler called\n"));
-       DEBUGADD(10,("event called for: %s, %s\n",
-               entry->ccname, entry->username));
+       DBG_DEBUG("event called for: %s, %s\n",
+                 entry->ccname, entry->username);
 
        TALLOC_FREE(entry->event);
 
 #ifdef HAVE_KRB5
 
        /* Kinit again if we have the user password and we can't renew the old
-        * tgt anymore 
+        * tgt anymore
         * NB
         * This happens when machine are put to sleep for a very long time. */
 
@@ -140,6 +146,9 @@ rekinit:
                                                          False, /* no PAC required anymore */
                                                          True,
                                                          WINBINDD_PAM_AUTH_KRB5_RENEW_TIME,
+                                                         NULL,
+                                                         NULL,
+                                                         NULL,
                                                          NULL);
                        gain_root_privilege();
 
@@ -151,10 +160,10 @@ rekinit:
                                 * it, ignore error here */
                                ads_kdestroy(entry->ccname);
 
-                               /* Don't break the ticket refresh chain: retry 
-                                * refreshing ticket sometime later when KDC is 
+                               /* Don't break the ticket refresh chain: retry
+                                * refreshing ticket sometime later when KDC is
                                 * unreachable -- BoYang. More error code handling
-                                * here? 
+                                * here?
                                 * */
 
                                if ((ret == KRB5_KDC_UNREACH)
@@ -165,8 +174,10 @@ rekinit:
                                        new_start = time(NULL) +
                                                    MAX(30, lp_winbind_cache_time());
 #endif
-                                       add_krb5_ticket_gain_handler_event(entry,
-                                                       timeval_set(new_start, 0));
+                                       add_krb5_ticket_gain_handler_event(
+                                               entry,
+                                               tevent_timeval_set(new_start,
+                                                                  0));
                                        return;
                                }
                                TALLOC_FREE(entry->event);
@@ -183,13 +194,13 @@ rekinit:
                        /* The tkt should be refreshed at one-half the period
                           from now to the expiration time */
                        expire_time = entry->refresh_time;
-                       new_start = KRB5_EVENT_REFRESH_TIME(entry->refresh_time);
+                       new_start = krb5_event_refresh_time(entry->refresh_time);
 #endif
                        goto done;
                } else {
-                               /* can this happen? 
+                               /* can this happen?
                                 * No cached credentials
-                                * destroy ticket and refresh chain 
+                                * destroy ticket and refresh chain
                                 * */
                                ads_kdestroy(entry->ccname);
                                TALLOC_FREE(entry->event);
@@ -200,14 +211,14 @@ rekinit:
        set_effective_uid(entry->uid);
 
        ret = smb_krb5_renew_ticket(entry->ccname,
-                                   entry->principal_name,
+                                   entry->canon_principal,
                                    entry->service,
                                    &new_start);
 #if defined(DEBUG_KRB5_TKT_RENEWAL)
        new_start = time(NULL) + 30;
 #else
        expire_time = new_start;
-       new_start = KRB5_EVENT_REFRESH_TIME(new_start);
+       new_start = krb5_event_refresh_time(new_start);
 #endif
 
        gain_root_privilege();
@@ -220,18 +231,18 @@ rekinit:
 
                /* evil rises here, we refresh ticket failed,
                 * but the ticket might be expired. Therefore,
-                * When we refresh ticket failed, destory the 
+                * When we refresh ticket failed, destroy the
                 * ticket */
 
                ads_kdestroy(entry->ccname);
 
                /* avoid breaking the renewal chain: retry in
                 * lp_winbind_cache_time() seconds when the KDC was not
-                * available right now. 
-                * the return code can be KRB5_REALM_CANT_RESOLVE. 
+                * available right now.
+                * the return code can be KRB5_REALM_CANT_RESOLVE.
                 * More error code handling here? */
 
-               if ((ret == KRB5_KDC_UNREACH) 
+               if ((ret == KRB5_KDC_UNREACH)
                    || (ret == KRB5_REALM_CANT_RESOLVE)) {
 #if defined(DEBUG_KRB5_TKT_RENEWAL)
                        new_start = time(NULL) + 30;
@@ -241,14 +252,14 @@ rekinit:
 #endif
                        /* ticket is destroyed here, we have to regain it
                         * if it is possible */
-                       add_krb5_ticket_gain_handler_event(entry,
-                                               timeval_set(new_start, 0));
+                       add_krb5_ticket_gain_handler_event(
+                               entry, tevent_timeval_set(new_start, 0));
                        return;
                }
 
                /* This is evil, if the ticket was already expired.
                 * renew ticket function returns KRB5KRB_AP_ERR_TKT_EXPIRED.
-                * But there is still a chance that we can rekinit it. 
+                * But there is still a chance that we can rekinit it.
                 *
                 * This happens when user login in online mode, and then network
                 * down or something cause winbind goes offline for a very long time,
@@ -265,24 +276,25 @@ rekinit:
        }
 
 done:
-       /* in cases that ticket will be unrenewable soon, we don't try to renew ticket 
+       /* in cases that ticket will be unrenewable soon, we don't try to renew ticket
         * but try to regain ticket if it is possible */
        if (entry->renew_until && expire_time
             && (entry->renew_until <= expire_time)) {
-               /* try to regain ticket 10 seconds beforre expiration */
+               /* try to regain ticket 10 seconds before expiration */
                expire_time -= 10;
-               add_krb5_ticket_gain_handler_event(entry,
-                                       timeval_set(expire_time, 0));
+               add_krb5_ticket_gain_handler_event(
+                       entry, tevent_timeval_set(expire_time, 0));
                return;
        }
 
        if (entry->refresh_time == 0) {
                entry->refresh_time = new_start;
        }
-       entry->event = event_add_timed(winbind_event_context(), entry,
-                                      timeval_set(new_start, 0),
-                                      krb5_ticket_refresh_handler,
-                                      entry);
+       entry->event = tevent_add_timer(global_event_context(),
+                                       entry,
+                                       tevent_timeval_set(new_start, 0),
+                                       krb5_ticket_refresh_handler,
+                                       entry);
 
 #endif
 }
@@ -291,8 +303,8 @@ done:
  Do the work of regaining a ticket when coming from offline auth.
 ****************************************************************/
 
-static void krb5_ticket_gain_handler(struct event_context *event_ctx,
-                                    struct timed_event *te,
+static void krb5_ticket_gain_handler(struct tevent_context *event_ctx,
+                                    struct tevent_timer *te,
                                     struct timeval now,
                                     void *private_data)
 {
@@ -305,9 +317,8 @@ static void krb5_ticket_gain_handler(struct event_context *event_ctx,
        struct winbindd_domain *domain = NULL;
 #endif
 
-       DEBUG(10,("krb5_ticket_gain_handler called\n"));
-       DEBUGADD(10,("event called for: %s, %s\n",
-               entry->ccname, entry->username));
+       DBG_DEBUG("event called for: %s, %s\n",
+                 entry->ccname, entry->username);
 
        TALLOC_FREE(entry->event);
 
@@ -338,6 +349,9 @@ static void krb5_ticket_gain_handler(struct event_context *event_ctx,
                                          False, /* no PAC required anymore */
                                          True,
                                          WINBINDD_PAM_AUTH_KRB5_RENEW_TIME,
+                                         NULL,
+                                         NULL,
+                                         NULL,
                                          NULL);
        gain_root_privilege();
 
@@ -345,7 +359,7 @@ static void krb5_ticket_gain_handler(struct event_context *event_ctx,
                DEBUG(3,("krb5_ticket_gain_handler: "
                        "could not kinit: %s\n",
                        error_message(ret)));
-               /* evil. If we cannot do it, destroy any the __maybe__ 
+               /* evil. If we cannot do it, destroy any the __maybe__
                 * __existing__ ticket */
                ads_kdestroy(entry->ccname);
                goto retry_later;
@@ -358,9 +372,9 @@ static void krb5_ticket_gain_handler(struct event_context *event_ctx,
        goto got_ticket;
 
   retry_later:
-#if defined(DEBUG_KRB5_TKT_REGAIN)
-       t = timeval_set(time(NULL) + 30, 0);
+
+#if defined(DEBUG_KRB5_TKT_RENEWAL)
+       t = tevent_timeval_set(time(NULL) + 30, 0);
 #else
        t = timeval_current_ofs(MAX(30, lp_winbind_cache_time()), 0);
 #endif
@@ -371,15 +385,15 @@ static void krb5_ticket_gain_handler(struct event_context *event_ctx,
   got_ticket:
 
 #if defined(DEBUG_KRB5_TKT_RENEWAL)
-       t = timeval_set(time(NULL) + 30, 0);
+       t = tevent_timeval_set(time(NULL) + 30, 0);
 #else
-       t = timeval_set(KRB5_EVENT_REFRESH_TIME(entry->refresh_time), 0);
+       t = tevent_timeval_set(krb5_event_refresh_time(entry->refresh_time), 0);
 #endif
 
        if (entry->refresh_time == 0) {
                entry->refresh_time = t.tv_sec;
        }
-       entry->event = event_add_timed(winbind_event_context(),
+       entry->event = tevent_add_timer(global_event_context(),
                                       entry,
                                       t,
                                       krb5_ticket_refresh_handler,
@@ -398,7 +412,7 @@ static void add_krb5_ticket_gain_handler_event(struct WINBINDD_CCACHE_ENTRY *ent
                                     struct timeval t)
 {
        entry->refresh_time = 0;
-       entry->event = event_add_timed(winbind_event_context(),
+       entry->event = tevent_add_timer(global_event_context(),
                                       entry,
                                       t,
                                       krb5_ticket_gain_handler,
@@ -411,20 +425,20 @@ void ccache_regain_all_now(void)
        struct timeval t = timeval_current();
 
        for (cur = ccache_list; cur; cur = cur->next) {
-               struct timed_event *new_event;
+               struct tevent_timer *new_event;
 
                /*
                 * if refresh_time is 0, we know that the
                 * the event has the krb5_ticket_gain_handler
                 */
                if (cur->refresh_time == 0) {
-                       new_event = event_add_timed(winbind_event_context(),
+                       new_event = tevent_add_timer(global_event_context(),
                                                    cur,
                                                    t,
                                                    krb5_ticket_gain_handler,
                                                    cur);
                } else {
-                       new_event = event_add_timed(winbind_event_context(),
+                       new_event = tevent_add_timer(global_event_context(),
                                                    cur,
                                                    t,
                                                    krb5_ticket_refresh_handler,
@@ -482,24 +496,23 @@ bool ccache_entry_identical(const char *username,
 
 NTSTATUS add_ccache_to_list(const char *princ_name,
                            const char *ccname,
-                           const char *service,
                            const char *username,
+                           const char *pass,
                            const char *realm,
                            uid_t uid,
                            time_t create_time,
                            time_t ticket_end,
                            time_t renew_until,
-                           bool postponed_request)
+                           bool postponed_request,
+                           const char *canon_principal,
+                           const char *canon_realm)
 {
        struct WINBINDD_CCACHE_ENTRY *entry = NULL;
        struct timeval t;
        NTSTATUS ntret;
-#ifdef HAVE_KRB5
-       int ret;
-#endif
 
        if ((username == NULL && princ_name == NULL) ||
-           ccname == NULL || uid < 0) {
+           ccname == NULL || uid == (uid_t)-1) {
                return NT_STATUS_INVALID_PARAMETER;
        }
 
@@ -509,28 +522,6 @@ NTSTATUS add_ccache_to_list(const char *princ_name,
                return NT_STATUS_NO_MORE_ENTRIES;
        }
 
-       /* If it is cached login, destroy krb5 ticket
-        * to avoid surprise. */
-#ifdef HAVE_KRB5
-       if (postponed_request) {
-               /* ignore KRB5_FCC_NOFILE error here */
-               ret = ads_kdestroy(ccname);
-               if (ret == KRB5_FCC_NOFILE) {
-                       ret = 0;
-               }
-               if (ret) {
-                       DEBUG(0, ("add_ccache_to_list: failed to destroy "
-                                  "user krb5 ccache %s with %s\n", ccname,
-                                  error_message(ret)));
-                       return krb5_to_nt_status(ret);
-               } else {
-                       DEBUG(10, ("add_ccache_to_list: successfully destroyed "
-                                  "krb5 ccache %s for user %s\n", ccname,
-                                  username));
-               }
-       }
-#endif
-
        /* Reference count old entries */
        entry = get_ccache_by_username(username);
        if (entry) {
@@ -545,11 +536,11 @@ NTSTATUS add_ccache_to_list(const char *princ_name,
                /* FIXME: in this case we still might want to have a krb5 cred
                 * event handler created - gd
                 * Add ticket refresh handler here */
-               
+
                if (!lp_winbind_refresh_tickets() || renew_until <= 0) {
                        return NT_STATUS_OK;
                }
-               
+
                if (!entry->event) {
                        if (postponed_request) {
                                t = timeval_current_ofs(MAX(30, lp_winbind_cache_time()), 0);
@@ -557,14 +548,16 @@ NTSTATUS add_ccache_to_list(const char *princ_name,
                        } else {
                                /* Renew at 1/2 the ticket expiration time */
 #if defined(DEBUG_KRB5_TKT_RENEWAL)
-                               t = timeval_set(time(NULL)+30, 0);
+                               t = tevent_timeval_set(time(NULL) + 30, 0);
 #else
-                               t = timeval_set(KRB5_EVENT_REFRESH_TIME(ticket_end), 0);
+                               t = tevent_timeval_set(krb5_event_refresh_time(
+                                                              ticket_end),
+                                                      0);
 #endif
                                if (!entry->refresh_time) {
                                        entry->refresh_time = t.tv_sec;
                                }
-                               entry->event = event_add_timed(winbind_event_context(),
+                               entry->event = tevent_add_timer(global_event_context(),
                                                               entry,
                                                               t,
                                                               krb5_ticket_refresh_handler,
@@ -585,12 +578,26 @@ NTSTATUS add_ccache_to_list(const char *princ_name,
                        }
 
                        DEBUG(10,("add_ccache_to_list: added krb5_ticket handler\n"));
+
                }
-                
+
+               /*
+                * If we're set up to renew our krb5 tickets, we must
+                * cache the credentials in memory for the ticket
+                * renew function (or increase the reference count
+                * if we're logging in more than once). Fix inspired
+                * by patch from Ian Gordon <ian.gordon@strath.ac.uk>
+                * for bugid #9098.
+                */
+
+               ntret = winbindd_add_memory_creds(username, uid, pass);
+               DEBUG(10, ("winbindd_add_memory_creds returned: %s\n",
+                       nt_errstr(ntret)));
+
                return NT_STATUS_OK;
        }
 
-       entry = TALLOC_P(NULL, struct WINBINDD_CCACHE_ENTRY);
+       entry = talloc(NULL, struct WINBINDD_CCACHE_ENTRY);
        if (!entry) {
                return NT_STATUS_NO_MEMORY;
        }
@@ -609,9 +616,15 @@ NTSTATUS add_ccache_to_list(const char *princ_name,
                        goto no_mem;
                }
        }
-       if (service) {
-               entry->service = talloc_strdup(entry, service);
-               if (!entry->service) {
+       if (canon_principal != NULL) {
+               entry->canon_principal = talloc_strdup(entry, canon_principal);
+               if (entry->canon_principal == NULL) {
+                       goto no_mem;
+               }
+       }
+       if (canon_realm != NULL) {
+               entry->canon_realm = talloc_strdup(entry, canon_realm);
+               if (entry->canon_realm == NULL) {
                        goto no_mem;
                }
        }
@@ -626,6 +639,15 @@ NTSTATUS add_ccache_to_list(const char *princ_name,
                goto no_mem;
        }
 
+       entry->service = talloc_asprintf(entry,
+                                        "%s/%s@%s",
+                                        KRB5_TGS_NAME,
+                                        canon_realm,
+                                        canon_realm);
+       if (entry->service == NULL) {
+               goto no_mem;
+       }
+
        entry->create_time = create_time;
        entry->renew_until = renew_until;
        entry->uid = uid;
@@ -641,14 +663,14 @@ NTSTATUS add_ccache_to_list(const char *princ_name,
        } else {
                /* Renew at 1/2 the ticket expiration time */
 #if defined(DEBUG_KRB5_TKT_RENEWAL)
-               t = timeval_set(time(NULL)+30, 0);
+               t = tevent_timeval_set(time(NULL) + 30, 0);
 #else
-               t = timeval_set(KRB5_EVENT_REFRESH_TIME(ticket_end), 0);
+               t = tevent_timeval_set(krb5_event_refresh_time(ticket_end), 0);
 #endif
                if (entry->refresh_time == 0) {
                        entry->refresh_time = t.tv_sec;
                }
-               entry->event = event_add_timed(winbind_event_context(),
+               entry->event = tevent_add_timer(global_event_context(),
                                               entry,
                                               t,
                                               krb5_ticket_refresh_handler,
@@ -665,9 +687,22 @@ NTSTATUS add_ccache_to_list(const char *princ_name,
 
        DLIST_ADD(ccache_list, entry);
 
-       DEBUG(10,("add_ccache_to_list: "
-               "added ccache [%s] for user [%s] to the list\n",
-               ccname, username));
+       DBG_DEBUG("Added ccache [%s] for user [%s] and service [%s]\n",
+                 entry->ccname, entry->username, entry->service);
+
+       if (entry->event) {
+               /*
+                * If we're set up to renew our krb5 tickets, we must
+                * cache the credentials in memory for the ticket
+                * renew function. Fix inspired by patch from
+                * Ian Gordon <ian.gordon@strath.ac.uk> for
+                * bugid #9098.
+                */
+
+               ntret = winbindd_add_memory_creds(username, uid, pass);
+               DEBUG(10, ("winbindd_add_memory_creds returned: %s\n",
+                       nt_errstr(ntret)));
+       }
 
        return NT_STATUS_OK;
 
@@ -686,7 +721,7 @@ NTSTATUS remove_ccache(const char *username)
 {
        struct WINBINDD_CCACHE_ENTRY *entry = get_ccache_by_username(username);
        NTSTATUS status = NT_STATUS_OK;
-       #ifdef HAVE_KRB5
+#ifdef HAVE_KRB5
        krb5_error_code ret;
 #endif
 
@@ -811,11 +846,11 @@ static NTSTATUS store_memory_creds(struct WINBINDD_MEMORY_CREDS *memcredp,
        DEBUG(10,("mlocked memory: %p\n", memcredp->nt_hash));
 #endif
 
-       /* Create and store the password hashes. */
-       E_md4hash(pass, memcredp->nt_hash);
-       E_deshash(pass, memcredp->lm_hash);
-
        if (pass) {
+               /* Create and store the password hashes. */
+               E_md4hash(pass, memcredp->nt_hash);
+               E_deshash(pass, memcredp->lm_hash);
+
                memcredp->pass = (char *)memcredp->lm_hash + LM_HASH_LEN;
                memcpy(memcredp->pass, pass,
                       memcredp->len - NT_HASH_LEN - LM_HASH_LEN);
@@ -902,7 +937,7 @@ static NTSTATUS winbindd_add_memory_creds_internal(const char *username,
                return winbindd_replace_memory_creds_internal(memcredp, pass);
        }
 
-       memcredp = TALLOC_ZERO_P(NULL, struct WINBINDD_MEMORY_CREDS);
+       memcredp = talloc_zero(NULL, struct WINBINDD_MEMORY_CREDS);
        if (!memcredp) {
                return NT_STATUS_NO_MEMORY;
        }