Add clock skew handling to our kerberos code. This allows us to cope with
authorAndrew Tridgell <tridge@samba.org>
Tue, 17 Sep 2002 12:12:50 +0000 (12:12 +0000)
committerAndrew Tridgell <tridge@samba.org>
Tue, 17 Sep 2002 12:12:50 +0000 (12:12 +0000)
the DC being out of sync with the local machine.

12 files changed:
source/auth/auth_domain.c
source/include/ads.h
source/libads/kerberos.c
source/libads/krb5_setpw.c
source/libads/ldap.c
source/libads/sasl.c
source/libads/util.c
source/libsmb/cliconnect.c
source/libsmb/clikrb5.c
source/libsmb/clispnego.c
source/nsswitch/winbindd_cm.c
source/utils/net_ads.c

index e8f11bb3d5a39abd23f70226a021d1dbb36d0b67..2e51a852816474dcdcd247bcc29b17ee984cec8f 100644 (file)
@@ -48,7 +48,7 @@ static NTSTATUS ads_resolve_dc(fstring remote_machine,
 
        DEBUG(4,("ads_resolve_dc: realm=%s\n", ads->config.realm));
 
-       ads->auth.no_bind = 1;
+       ads->auth.flags |= ADS_AUTH_NO_BIND;
 
 #ifdef HAVE_ADS
        /* a full ads_connect() is actually overkill, as we don't srictly need
index 6106eb6b409de29cdeee7abf7f669542870d3321..875b895e49351d2df652354f6ff05d298e69de87 100644 (file)
@@ -24,7 +24,8 @@ typedef struct {
                char *password;
                char *user_name;
                char *kdc_server;
-               int no_bind;
+               unsigned flags;
+               int time_offset;
        } auth;
 
        /* info derived from the servers config */
@@ -32,6 +33,7 @@ typedef struct {
                char *realm;
                char *bind_path;
                char *ldap_server_name;
+               time_t current_time;
        } config;
 } ADS_STRUCT;
 
@@ -249,3 +251,8 @@ typedef void **ADS_MODLIST;
 /* DomainCntrollerAddressType */
 #define ADS_INET_ADDRESS      0x00000001
 #define ADS_NETBIOS_ADDRESS   0x00000002
+
+
+/* ads auth control flags */
+#define ADS_AUTH_DISABLE_KERBEROS 1
+#define ADS_AUTH_NO_BIND 2
index 9a486237c9fe9a65c198bd924f96aab0b8163a50..a80837cf4df478a45247888171e3ad7606acbd06 100644 (file)
@@ -50,7 +50,7 @@ kerb_prompter(krb5_context ctx, void *data,
   simulate a kinit, putting the tgt in the default cache location
   remus@snapserver.com
 */
-int kerberos_kinit_password(const char *principal, const char *password)
+int kerberos_kinit_password(const char *principal, const char *password, int time_offset)
 {
        krb5_context ctx;
        krb5_error_code code = 0;
@@ -60,6 +60,10 @@ int kerberos_kinit_password(const char *principal, const char *password)
 
        if ((code = krb5_init_context(&ctx)))
                return code;
+
+       if (time_offset != 0) {
+               krb5_set_real_time(ctx, time(NULL) + time_offset, 0);
+       }
        
        if ((code = krb5_cc_default(ctx, &cc))) {
                krb5_free_context(ctx);
@@ -111,7 +115,7 @@ int ads_kinit_password(ADS_STRUCT *ads)
        int ret;
 
        asprintf(&s, "%s@%s", ads->auth.user_name, ads->auth.realm);
-       ret = kerberos_kinit_password(s, ads->auth.password);
+       ret = kerberos_kinit_password(s, ads->auth.password, ads->auth.time_offset);
 
        if (ret) {
                DEBUG(0,("kerberos_kinit_password %s failed: %s\n", 
index ec79a8658fe721e6accbf05ad64534738ea0a0b3..a49b6cbe3b0ee8a01ed6ec30286544d0e175921c 100644 (file)
@@ -248,7 +248,8 @@ static krb5_error_code parse_setpw_reply(krb5_context context,
        return 0;
 }
 
-ADS_STATUS krb5_set_password(const char *kdc_host, const char *princ, const char *newpw)
+ADS_STATUS krb5_set_password(const char *kdc_host, const char *princ, const char *newpw, 
+                            int time_offset)
 {
        krb5_context context;
        krb5_auth_context auth_context = NULL;
@@ -268,6 +269,10 @@ ADS_STATUS krb5_set_password(const char *kdc_host, const char *princ, const char
                return ADS_ERROR_KRB5(ret);
        }
        
+       if (time_offset != 0) {
+               krb5_set_real_time(context, time(NULL) + time_offset, 0);
+       }
+
        ret = krb5_cc_default(context, &ccache);
        if (ret) {
                krb5_free_context(context);
@@ -452,16 +457,17 @@ ADS_STATUS krb5_set_password(const char *kdc_host, const char *princ, const char
 
 ADS_STATUS kerberos_set_password(const char *kpasswd_server, 
                                 const char *auth_principal, const char *auth_password,
-                                const char *target_principal, const char *new_password)
+                                const char *target_principal, const char *new_password,
+                                int time_offset)
 {
     int ret;
 
-    if ((ret = kerberos_kinit_password(auth_principal, auth_password))) {
+    if ((ret = kerberos_kinit_password(auth_principal, auth_password, time_offset))) {
        DEBUG(1,("Failed kinit for principal %s (%s)\n", auth_principal, error_message(ret)));
        return ADS_ERROR_KRB5(ret);
     }
 
-    return krb5_set_password(kpasswd_server, target_principal, new_password);
+    return krb5_set_password(kpasswd_server, target_principal, new_password, time_offset);
 }
 
 
index 2f70d3a285416b85e992d41ae944a9f035d674d0..385a9bd93f90e326d4ab79d410c11485363f1009 100644 (file)
@@ -63,6 +63,7 @@ static BOOL ads_try_connect(ADS_STRUCT *ads, const char *server, unsigned port)
        ads->ldap_port = port;
        ads->ldap_ip = *interpret_addr2(srv);
        free(srv);
+
        return True;
 }
 
@@ -204,7 +205,6 @@ static BOOL ads_try_netbios(ADS_STRUCT *ads)
 ADS_STATUS ads_connect(ADS_STRUCT *ads)
 {
        int version = LDAP_VERSION3;
-       int code;
        ADS_STATUS status;
 
        ads->last_attempt = time(NULL);
@@ -274,7 +274,7 @@ got_connection:
        }
 #endif
 
-       if (ads->auth.no_bind) {
+       if (ads->auth.flags & ADS_AUTH_NO_BIND) {
                return ADS_SUCCESS;
        }
 
@@ -1416,7 +1416,7 @@ ADS_STATUS ads_set_machine_password(ADS_STRUCT *ads,
         */
        asprintf(&principal, "%s$@%s", host, ads->auth.realm);
        
-       status = krb5_set_password(ads->auth.kdc_server, principal, password);
+       status = krb5_set_password(ads->auth.kdc_server, principal, password, ads->auth.time_offset);
        
        free(host);
        free(principal);
@@ -1622,6 +1622,26 @@ ADS_STATUS ads_USN(ADS_STRUCT *ads, uint32 *usn)
        return ADS_SUCCESS;
 }
 
+/* parse a ADS timestring - typical string is
+   '20020917091222.0Z0' which means 09:12.22 17th September
+   2002, timezone 0 */
+static time_t ads_parse_time(const char *str)
+{
+       struct tm tm;
+
+       ZERO_STRUCT(tm);
+
+       if (sscanf(str, "%4d%2d%2d%2d%2d%2d", 
+                  &tm.tm_year, &tm.tm_mon, &tm.tm_mday, 
+                  &tm.tm_hour, &tm.tm_min, &tm.tm_sec) != 6) {
+               return 0;
+       }
+       tm.tm_year -= 1900;
+       tm.tm_mon -= 1;
+
+       return timegm(&tm);
+}
+
 
 /**
  * Find the servers name and realm - this can be done before authentication 
@@ -1632,22 +1652,36 @@ ADS_STATUS ads_USN(ADS_STRUCT *ads, uint32 *usn)
  **/
 ADS_STATUS ads_server_info(ADS_STRUCT *ads)
 {
-       const char *attrs[] = {"ldapServiceName", NULL};
+       const char *attrs[] = {"ldapServiceName", "currentTime", NULL};
        ADS_STATUS status;
        void *res;
-       char **values;
+       char *value;
        char *p;
+       char *timestr;
+       TALLOC_CTX *ctx;
+
+       if (!(ctx = talloc_init())) {
+               return ADS_ERROR(LDAP_NO_MEMORY);
+       }
 
        status = ads_do_search(ads, "", LDAP_SCOPE_BASE, "(objectclass=*)", attrs, &res);
        if (!ADS_ERR_OK(status)) return status;
 
-       values = ldap_get_values(ads->ld, res, "ldapServiceName");
-       if (!values || !values[0]) return ADS_ERROR(LDAP_NO_RESULTS_RETURNED);
+       value = ads_pull_string(ads, ctx, res, "ldapServiceName");
+       if (!value) {
+               return ADS_ERROR(LDAP_NO_RESULTS_RETURNED);
+       }
+
+       timestr = ads_pull_string(ads, ctx, res, "currentTime");
+       if (!timestr) {
+               return ADS_ERROR(LDAP_NO_RESULTS_RETURNED);
+       }
 
-       p = strchr(values[0], ':');
+       ldap_msgfree(res);
+
+       p = strchr(value, ':');
        if (!p) {
-               ldap_value_free(values);
-               ldap_msgfree(res);
+               talloc_destroy(ctx);
                DEBUG(1, ("ads_server_info: returned ldap server name did not contain a ':' so was deemed invalid\n"));
                return ADS_ERROR(LDAP_DECODING_ERROR);
        }
@@ -1657,8 +1691,7 @@ ADS_STATUS ads_server_info(ADS_STRUCT *ads)
        ads->config.ldap_server_name = strdup(p+1);
        p = strchr(ads->config.ldap_server_name, '$');
        if (!p || p[1] != '@') {
-               ldap_value_free(values);
-               ldap_msgfree(res);
+               talloc_destroy(ctx);
                SAFE_FREE(ads->config.ldap_server_name);
                DEBUG(1, ("ads_server_info: returned ldap server name did not contain '$@' so was deemed invalid\n"));
                return ADS_ERROR(LDAP_DECODING_ERROR);
@@ -1675,6 +1708,15 @@ ADS_STATUS ads_server_info(ADS_STRUCT *ads)
        DEBUG(3,("got ldap server name %s@%s\n", 
                 ads->config.ldap_server_name, ads->config.realm));
 
+       ads->config.current_time = ads_parse_time(timestr);
+
+       if (ads->config.current_time != 0) {
+               ads->auth.time_offset = ads->config.current_time - time(NULL);
+               DEBUG(4,("time offset is %d seconds\n", ads->auth.time_offset));
+       }
+
+       talloc_destroy(ctx);
+
        return ADS_SUCCESS;
 }
 
index 12a5722319feff66b9f9dd321679ae6c902bfc07..c110c1d2cd7d15b19dd8c494a8784cff1af51e24 100644 (file)
@@ -122,7 +122,7 @@ static ADS_STATUS ads_sasl_spnego_krb5_bind(ADS_STRUCT *ads, const char *princip
        struct berval cred, *scred;
        int rc;
 
-       blob = spnego_gen_negTokenTarg(principal);
+       blob = spnego_gen_negTokenTarg(principal, ads->auth.time_offset);
 
        if (!blob.data) {
                return ADS_ERROR(LDAP_OPERATIONS_ERROR);
@@ -144,7 +144,7 @@ static ADS_STATUS ads_sasl_spnego_krb5_bind(ADS_STRUCT *ads, const char *princip
 */
 static ADS_STATUS ads_sasl_spnego_bind(ADS_STRUCT *ads)
 {
-       struct berval *scred;
+       struct berval *scred=NULL;
        int rc, i;
        ADS_STATUS status;
        DATA_BLOB blob;
@@ -185,7 +185,8 @@ static ADS_STATUS ads_sasl_spnego_bind(ADS_STRUCT *ads)
        }
        DEBUG(3,("got principal=%s\n", principal));
 
-       if (got_kerberos_mechanism && ads_kinit_password(ads) == 0) {
+       if (!(ads->auth.flags & ADS_AUTH_DISABLE_KERBEROS) &&
+           got_kerberos_mechanism && ads_kinit_password(ads) == 0) {
                return ads_sasl_spnego_krb5_bind(ads, principal);
        }
 
index b10b130a313bea604c90aca6cabea30d4b69f83f..021f2d93e4aad7039beb1d7524513e8645d19f47 100644 (file)
@@ -40,7 +40,7 @@ ADS_STATUS ads_change_trust_account_password(ADS_STRUCT *ads, char *host_princip
     asprintf(&service_principal, "HOST/%s", host_principal);
     
     ret = kerberos_set_password(ads->auth.kdc_server, host_principal, password, 
-                               service_principal, new_password);
+                               service_principal, new_password, ads->auth.time_offset);
 
     if (!secrets_store_machine_password(new_password)) {
            DEBUG(1,("Failed to save machine password\n"));
index 6c5c5e0b0e76bd3e3ed9e86867149b868ad8b56d..298b1e52b6186fa4153fcbca4264e9245f1031f5 100644 (file)
@@ -431,7 +431,7 @@ static BOOL cli_session_setup_kerberos(struct cli_state *cli, char *principal, c
        DEBUG(2,("Doing kerberos session setup\n"));
 
        /* generate the encapsulated kerberos5 ticket */
-       negTokenTarg = spnego_gen_negTokenTarg(principal);
+       negTokenTarg = spnego_gen_negTokenTarg(principal, 0);
 
        if (!negTokenTarg.data) return False;
 
index 1fc400edb0e349d1140e707aa5bd34e49544d671..22bfdc046330cd621130f3fe3b1b0ed147e28e68 100644 (file)
@@ -64,6 +64,14 @@ static krb5_error_code krb5_mk_req2(krb5_context context,
                goto cleanup_creds;
        }
 
+       /* cope with the ticket being in the future due to clock skew */
+       if ((unsigned)credsp->times.starttime > time(NULL)) {
+               time_t t = time(NULL);
+               int time_offset = (unsigned)credsp->times.starttime - t;
+               DEBUG(4,("Advancing clock by %d seconds to cope with clock skew\n", time_offset));
+               krb5_set_real_time(context, t + time_offset + 1, 0);
+       }
+
        in_data.length = 0;
        retval = krb5_mk_req_extended(context, auth_context, ap_req_options, 
                                      &in_data, credsp, outbuf);
@@ -86,7 +94,7 @@ cleanup_princ:
 /*
   get a kerberos5 ticket for the given service 
 */
-DATA_BLOB krb5_get_ticket(char *principal)
+DATA_BLOB krb5_get_ticket(char *principal, time_t time_offset)
 {
        krb5_error_code retval;
        krb5_data packet;
@@ -108,6 +116,10 @@ DATA_BLOB krb5_get_ticket(char *principal)
                goto failed;
        }
 
+       if (time_offset != 0) {
+               krb5_set_real_time(context, time(NULL) + time_offset, 0);
+       }
+
        if ((retval = krb5_cc_default(context, &ccdef))) {
                DEBUG(1,("krb5_cc_default failed (%s)\n",
                         error_message(retval)));
index 04ec6ed39ec59dc290a667f04ec74b7f6b7c6e7b..55f49c5987111af4c1e8f1f9eb1fda6691b1a2c0 100644 (file)
@@ -321,13 +321,13 @@ BOOL spnego_parse_krb5_wrap(DATA_BLOB blob, DATA_BLOB *ticket)
    generate a SPNEGO negTokenTarg packet, ready for a EXTENDED_SECURITY
    kerberos session setup 
 */
-DATA_BLOB spnego_gen_negTokenTarg(const char *principal)
+DATA_BLOB spnego_gen_negTokenTarg(const char *principal, int time_offset)
 {
        DATA_BLOB tkt, tkt_wrapped, targ;
        const char *krb_mechs[] = {OID_KERBEROS5_OLD, OID_NTLMSSP, NULL};
 
        /* get a kerberos ticket for the service */
-       tkt = krb5_get_ticket(principal);
+       tkt = krb5_get_ticket(principal, time_offset);
 
        /* wrap that up in a nice GSS-API wrapping */
        tkt_wrapped = spnego_gen_krb5_wrap(tkt);
index 9ac392a6ba092e49d3813ba6ddd7a3af6a266787..0b9e38eb1f2511121fd74e1ceaf685ca8f85e8e9 100644 (file)
@@ -109,7 +109,7 @@ static BOOL cm_ads_find_dc(const char *domain, struct in_addr *dc_ip, fstring sr
        }
 
        /* we don't need to bind, just connect */
-       ads->auth.no_bind = 1;
+       ads->auth.flags |= ADS_AUTH_NO_BIND;
 
        DEBUG(4,("cm_ads_find_dc: domain=%s\n", domain));
 
index 8c85bd82f9824ed2c565f43c31900cc8ac5c8d5d..af290ce83c6bc239a60310d8ac8709a3664d28a5 100644 (file)
@@ -66,7 +66,7 @@ static int net_ads_lookup(int argc, const char **argv)
 
        ads = ads_init(NULL, NULL, opt_host);
        if (ads) {
-               ads->auth.no_bind = 1;
+               ads->auth.flags |= ADS_AUTH_NO_BIND;
        }
 
        ads_connect(ads);
@@ -88,7 +88,7 @@ static int net_ads_info(int argc, const char **argv)
        ads = ads_init(NULL, NULL, opt_host);
 
        if (ads) {
-               ads->auth.no_bind = 1;
+               ads->auth.flags |= ADS_AUTH_NO_BIND;
        }
 
        ads_connect(ads);
@@ -103,6 +103,7 @@ static int net_ads_info(int argc, const char **argv)
        d_printf("Realm: %s\n", ads->config.realm);
        d_printf("Bind Path: %s\n", ads->config.bind_path);
        d_printf("LDAP port: %d\n", ads->ldap_port);
+       d_printf("Server time: %s\n", http_timestring(ads->config.current_time));
 
        return 0;
 }
@@ -199,7 +200,7 @@ static int net_ads_workgroup(int argc, const char **argv)
 
 
 
-static void usergrp_display(char *field, void **values, void *data_area)
+static BOOL usergrp_display(char *field, void **values, void *data_area)
 {
        char **disp_fields = (char **) data_area;
 
@@ -213,15 +214,16 @@ static void usergrp_display(char *field, void **values, void *data_area)
                }
                SAFE_FREE(disp_fields[0]);
                SAFE_FREE(disp_fields[1]);
-               return;
+               return True;
        }
        if (!values) /* must be new field, indicate string field */
-               return;
+               return True;
        if (StrCaseCmp(field, "sAMAccountName") == 0) {
                disp_fields[0] = strdup((char *) values[0]);
        }
        if (StrCaseCmp(field, "description") == 0)
                disp_fields[1] = strdup((char *) values[0]);
+       return True;
 }
 
 static int net_ads_user_usage(int argc, const char **argv)
@@ -270,7 +272,7 @@ static int ads_user_add(int argc, const char **argv)
 
        /* try setting the password */
        asprintf(&upn, "%s@%s", argv[0], ads->config.realm);
-       status = krb5_set_password(ads->auth.kdc_server, upn, argv[1]);
+       status = krb5_set_password(ads->auth.kdc_server, upn, argv[1], ads->auth.time_offset);
        safe_free(upn);
        if (ADS_ERR_OK(status)) {
                d_printf("User %s added\n", argv[0]);
@@ -653,7 +655,9 @@ int net_ads_join(int argc, const char **argv)
                return -1;
        }
 
-       if (ads_kinit_password(ads)) {
+       rc = ads_domain_sid(ads, &dom_sid);
+       if (!ADS_ERR_OK(rc)) {
+               d_printf("ads_domain_sid: %s\n", ads_errstr(rc));
                return -1;
        }
 
@@ -663,12 +667,6 @@ int net_ads_join(int argc, const char **argv)
                return -1;
        }
 
-       rc = ads_domain_sid(ads, &dom_sid);
-       if (!ADS_ERR_OK(rc)) {
-               d_printf("ads_domain_sid: %s\n", ads_errstr(rc));
-               return -1;
-       }
-
        if (!secrets_store_domain_sid(lp_workgroup(), &dom_sid)) {
                DEBUG(1,("Failed to save domain sid\n"));
                return -1;
@@ -885,7 +883,7 @@ static int net_ads_password(int argc, const char **argv)
     new_password = getpass(prompt);
 
     ret = kerberos_set_password(ads->auth.kdc_server, auth_principal, 
-                               auth_password, argv[0], new_password);
+                               auth_password, argv[0], new_password, ads->auth.time_offset);
     if (!ADS_ERR_OK(ret)) {
        d_printf("Password change failed :-( ...\n");
        ads_destroy(&ads);