s4:kdc Allow a password change when the password is expired
authorAndrew Bartlett <abartlet@samba.org>
Thu, 18 Jun 2009 01:08:46 +0000 (11:08 +1000)
committerAndrew Bartlett <abartlet@samba.org>
Thu, 18 Jun 2009 03:49:30 +0000 (13:49 +1000)
This requires a rework on Heimdal's windc plugin layer, as we want
full control over what tickets Heimdal will issue.  (In particular, in
case our requirements become more complex in future).

The original problem was that Heimdal's check would permit the ticket,
but Samba would then deny it, not knowing it was for kadmin/changepw

Also (in hdb-samba4) be a bit more careful on what entries we will
make the 'change_pw' service mark that this depends on.

Andrew Bartlett

12 files changed:
source4/auth/auth.h
source4/auth/ntlm/auth_sam.c
source4/auth/sam.c
source4/heimdal/kdc/headers.h
source4/heimdal/kdc/kdc_locl.h
source4/heimdal/kdc/kerberos5.c
source4/heimdal/kdc/krb5tgs.c
source4/heimdal/kdc/windc.c
source4/heimdal/kdc/windc_plugin.h
source4/kdc/hdb-samba4.c
source4/kdc/kdc.h
source4/kdc/pac-glue.c

index f6d739325da54f2901924912c76aa32f405fefd6..6bad0178629f46bb749d0e8e3986d5069b2aa04d 100644 (file)
@@ -232,7 +232,8 @@ NTSTATUS authsam_account_ok(TALLOC_CTX *mem_ctx,
                            struct ldb_message *msg,
                            const char *logon_workstation,
                            const char *name_for_logs,
-                           bool allow_domain_trust);
+                           bool allow_domain_trust,
+                           bool password_change);
 struct auth_session_info *system_session(TALLOC_CTX *mem_ctx, struct loadparm_context *lp_ctx);
 NTSTATUS authsam_make_server_info(TALLOC_CTX *mem_ctx, struct ldb_context *sam_ctx,
                                           const char *netbios_name,
index 0bb79e234ce4e0e205532a72118b61022434a740..253ddf2286d039fa12a3de327bafbbb1f91bed7b 100644 (file)
@@ -175,7 +175,7 @@ static NTSTATUS authsam_authenticate(struct auth_context *auth_context,
                                       msg,
                                       user_info->workstation_name,
                                       user_info->mapped.account_name,
-                                      false);
+                                      false, false);
 
        return nt_status;
 }
index 68eaacf25505b1ddb8be6c88ee7f5973c91d0e3b..acbd50c3fa028dfce5918b27a85f1635fa0f13d1 100644 (file)
@@ -147,7 +147,8 @@ _PUBLIC_ NTSTATUS authsam_account_ok(TALLOC_CTX *mem_ctx,
                                     struct ldb_message *msg,
                                     const char *logon_workstation,
                                     const char *name_for_logs,
-                                    bool allow_domain_trust)
+                                    bool allow_domain_trust,
+                                    bool password_change)
 {
        uint16_t acct_flags;
        const char *workstation_list;
@@ -189,15 +190,15 @@ _PUBLIC_ NTSTATUS authsam_account_ok(TALLOC_CTX *mem_ctx,
                return NT_STATUS_ACCOUNT_EXPIRED;
        }
 
-       /* check for immediate expiry "must change at next logon" */
-       if (must_change_time == 0) {
+       /* check for immediate expiry "must change at next logon" (but not if this is a password change request) */
+       if ((must_change_time == 0) && !password_change) {
                DEBUG(1,("sam_account_ok: Account for user '%s' password must change!.\n", 
                         name_for_logs));
                return NT_STATUS_PASSWORD_MUST_CHANGE;
        }
 
-       /* check for expired password */
-       if (must_change_time < now) {
+       /* check for expired password (but not if this is a password change request) */
+       if ((must_change_time < now) && !password_change) {
                DEBUG(1,("sam_account_ok: Account for user '%s' password expired!.\n", 
                         name_for_logs));
                DEBUG(1,("sam_account_ok: Password expired at '%s' unix time.\n", 
index 2240336e31f3789aea4e38a31185da69937452af..b9a828852a6abff52561ecade6eedddd4e52553d 100644 (file)
 #ifndef NO_NTLM
 #include <heimntlm.h>
 #endif
+#include <kdc.h>
 #include <windc_plugin.h>
 
 #undef ALLOC
index 9b291ac896511314b51cd1cdbde617c4a10098f7..daf155839cd691e9b4fd97c81514ec429ca86f55 100644 (file)
@@ -39,7 +39,6 @@
 #define __KDC_LOCL_H__
 
 #include "headers.h"
-#include "kdc.h"
 
 typedef struct pk_client_params pk_client_params;
 struct DigestREQ;
index 941a2e0572e39be2de90228bea4c165e43627cc3..ac495b1ac7b318708ccc4c81b87f7732ddff655e 100644 (file)
@@ -668,11 +668,11 @@ log_as_req(krb5_context context,
  */
 
 krb5_error_code
-_kdc_check_flags(krb5_context context,
-                krb5_kdc_configuration *config,
-                hdb_entry_ex *client_ex, const char *client_name,
-                hdb_entry_ex *server_ex, const char *server_name,
-                krb5_boolean is_as_req)
+kdc_check_flags(krb5_context context,
+               krb5_kdc_configuration *config,
+               hdb_entry_ex *client_ex, const char *client_name,
+               hdb_entry_ex *server_ex, const char *server_name,
+               krb5_boolean is_as_req)
 {
     if(client_ex != NULL) {
        hdb_entry *client = &client_ex->entry;
@@ -921,7 +921,6 @@ _kdc_as_rep(krb5_context context,
                "AS-REQ malformed server name from %s", from);
        goto out;
     }
-
     if(b->cname == NULL){
        ret = KRB5KRB_ERR_GENERIC;
        e_text = "No client in request";
@@ -1345,14 +1344,9 @@ _kdc_as_rep(krb5_context context,
      * with in a preauth mech.
      */
 
-    ret = _kdc_check_flags(context, config,
-                          client, client_name,
-                          server, server_name,
-                          TRUE);
-    if(ret)
-       goto out;
-
-    ret = _kdc_windc_client_access(context, client, req, &e_data);
+    ret = _kdc_check_access(context, config, client, client_name,
+                           server, server_name,
+                           req, &e_data);
     if(ret)
        goto out;
 
index 3abdb18ae487a53b521c6a401b7354c515036967..59104da3d60ec74657e332a91b86872b5fba107a 100644 (file)
@@ -1860,10 +1860,10 @@ server_lookup:
      * Check flags
      */
 
-    ret = _kdc_check_flags(context, config,
-                          client, cpn,
-                          server, spn,
-                          FALSE);
+    ret = kdc_check_flags(context, config,
+                         client, cpn,
+                         server, spn,
+                         FALSE);
     if(ret)
        goto out;
 
index fe3cd997e76a6f5748374f45bbb28d7fbc14750f..9d7fa52ceab498c9635f4873e5b06bec26d3a764 100644 (file)
@@ -99,12 +99,22 @@ _kdc_pac_verify(krb5_context context,
 }
 
 krb5_error_code
-_kdc_windc_client_access(krb5_context context,
-                        struct hdb_entry_ex *client,
-                        KDC_REQ *req,
-                        krb5_data *e_data)
+_kdc_check_access(krb5_context context,
+                 krb5_kdc_configuration *config,
+                 hdb_entry_ex *client_ex, const char *client_name,
+                 hdb_entry_ex *server_ex, const char *server_name,
+                 KDC_REQ *req,
+                 krb5_data *e_data)
 {
     if (windcft == NULL)
-       return 0;
-    return (windcft->client_access)(windcctx, context, client, req, e_data);
+           return kdc_check_flags(context, config,
+                                  client_ex, client_name,
+                                  server_ex, server_name,
+                                  req->msg_type == krb_as_req);
+
+    return (windcft->client_access)(windcctx, 
+                                   context, config, 
+                                   client_ex, client_name, 
+                                   server_ex, server_name, 
+                                   req, e_data);
 }
index 34016694b2571f9acf0d2bdc659025c1923b4db8..c7efb7b852478978e46ef23390fa1222429f4c50 100644 (file)
@@ -64,10 +64,14 @@ typedef krb5_error_code
 
 typedef krb5_error_code
 (*krb5plugin_windc_client_access)(
-    void *, krb5_context, struct hdb_entry_ex *, KDC_REQ *, krb5_data *);
+       void *, krb5_context, 
+       krb5_kdc_configuration *config,
+       hdb_entry_ex *, const char *, 
+       hdb_entry_ex *, const char *, 
+       KDC_REQ *, krb5_data *);
 
 
-#define KRB5_WINDC_PLUGING_MINOR               3
+#define KRB5_WINDC_PLUGING_MINOR               4
 
 typedef struct krb5plugin_windc_ftable {
     int                        minor_version;
index c0fa5132d16f912015f82c754640e950614c443d..eda7867bb5ea1a135829ffaa23acfdac8ecc1909 100644 (file)
@@ -627,7 +627,18 @@ static krb5_error_code LDB_message2entry(krb5_context context, HDB *db,
 
                entry_ex->entry.flags.invalid = 0;
                entry_ex->entry.flags.server = 1;
-               entry_ex->entry.flags.change_pw = 1;
+
+               /* Don't mark all requests for the krbtgt/realm as
+                * 'change password', as otherwise we could get into
+                * trouble, and not enforce the password expirty.
+                * Instead, only do it when request is for the kpasswd service */
+               if (ent_type == HDB_SAMBA4_ENT_TYPE_SERVER
+                   && principal->name.name_string.len == 2
+                   && (strcmp(principal->name.name_string.val[0], "kadmin") == 0)
+                   && (strcmp(principal->name.name_string.val[1], "changepw") == 0)
+                   && lp_is_my_domain_or_realm(lp_ctx, principal->realm)) {
+                       entry_ex->entry.flags.change_pw = 1;
+               }
                entry_ex->entry.flags.client = 0;
                entry_ex->entry.flags.forwardable = 1;
                entry_ex->entry.flags.ok_as_delegate = 1;
index a281e1d9c9ebea47fead2a448c16bb6df630394d..d37a32ebbfc595556cfa1faf7e2051749aecb01b 100644 (file)
@@ -22,8 +22,8 @@
 
 #include "system/kerberos.h"
 #include "auth/kerberos/kerberos.h"
-#include <kdc.h>
 #include <hdb.h>
+#include <kdc.h>
 #include <krb5/windc_plugin.h>
 #include "kdc/pac_glue.h"
 
index 411e752c04c2281b769af30ab1d455d81e1c2ab2..5bd4cb10c7345ce05982f71dcb7447d8f9816b0f 100644 (file)
@@ -231,28 +231,29 @@ static void samba_kdc_build_edata_reply(TALLOC_CTX *tmp_ctx, krb5_data *e_data,
 
 
 krb5_error_code samba_kdc_check_client_access(void *priv, 
-                                             krb5_context context, hdb_entry_ex *entry_ex, 
+                                             krb5_context context, 
+                                             krb5_kdc_configuration *config,
+                                             hdb_entry_ex *client_ex, const char *client_name,
+                                             hdb_entry_ex *server_ex, const char *server_name,
                                              KDC_REQ *req,
                                              krb5_data *e_data)
 {
        krb5_error_code ret;
        NTSTATUS nt_status;
-       TALLOC_CTX *tmp_ctx = talloc_new(entry_ex->ctx);
-       struct hdb_ldb_private *p = talloc_get_type(entry_ex->ctx, struct hdb_ldb_private);
-       char *name, *workstation = NULL;
+       TALLOC_CTX *tmp_ctx;
+       struct hdb_ldb_private *p;
+       char *workstation = NULL;
        HostAddresses *addresses = req->req_body.addresses;
        int i;
+       bool password_change;
+
+       tmp_ctx = talloc_new(client_ex->ctx);
+       p = talloc_get_type(client_ex->ctx, struct hdb_ldb_private);
 
        if (!tmp_ctx) {
                return ENOMEM;
        }
        
-       ret = krb5_unparse_name(context, entry_ex->entry.principal, &name);
-       if (ret != 0) {
-               talloc_free(tmp_ctx);
-               return ret;
-       }
-
        if (addresses) {
                for (i=0; i < addresses->len; i++) {
                        if (addresses->val->addr_type == KRB5_ADDRESS_NETBIOS) {
@@ -272,6 +273,8 @@ krb5_error_code samba_kdc_check_client_access(void *priv,
                }
        }
 
+       password_change = (server_ex && server_ex->entry.flags.change_pw);
+
        /* we allow all kinds of trusts here */
        nt_status = authsam_account_ok(tmp_ctx, 
                                       p->samdb,
@@ -279,30 +282,34 @@ krb5_error_code samba_kdc_check_client_access(void *priv,
                                       p->realm_dn,
                                       p->msg,
                                       workstation,
-                                      name, true);
-       free(name);
-
-       if (NT_STATUS_IS_OK(nt_status))
-               return 0;
-
-       if (NT_STATUS_EQUAL(nt_status, NT_STATUS_PASSWORD_MUST_CHANGE))
-               ret = KRB5KDC_ERR_KEY_EXPIRED;
-       else if (NT_STATUS_EQUAL(nt_status, NT_STATUS_PASSWORD_EXPIRED))
-               ret = KRB5KDC_ERR_KEY_EXPIRED;
-       else if (NT_STATUS_EQUAL(nt_status, NT_STATUS_ACCOUNT_EXPIRED))
-               ret = KRB5KDC_ERR_CLIENT_REVOKED;
-       else if (NT_STATUS_EQUAL(nt_status, NT_STATUS_ACCOUNT_DISABLED))
-               ret = KRB5KDC_ERR_CLIENT_REVOKED;
-       else if (NT_STATUS_EQUAL(nt_status, NT_STATUS_INVALID_LOGON_HOURS))
-               ret = KRB5KDC_ERR_CLIENT_REVOKED;
-       else if (NT_STATUS_EQUAL(nt_status, NT_STATUS_ACCOUNT_LOCKED_OUT))
-               ret = KRB5KDC_ERR_CLIENT_REVOKED;
-       else if (NT_STATUS_EQUAL(nt_status, NT_STATUS_INVALID_WORKSTATION))
-               ret = KRB5KDC_ERR_POLICY;
-       else
-               ret = KRB5KDC_ERR_POLICY;
-
-       samba_kdc_build_edata_reply(tmp_ctx, e_data, nt_status);
+                                      client_name, true, password_change);
+
+       if (NT_STATUS_IS_OK(nt_status)) {
+               /* Now do the standard Heimdal check */
+               ret = kdc_check_flags(context, config,
+                                     client_ex, client_name,
+                                     server_ex, server_name,
+                                     req->msg_type == krb_as_req);
+       } else {
+               if (NT_STATUS_EQUAL(nt_status, NT_STATUS_PASSWORD_MUST_CHANGE))
+                       ret = KRB5KDC_ERR_KEY_EXPIRED;
+               else if (NT_STATUS_EQUAL(nt_status, NT_STATUS_PASSWORD_EXPIRED))
+                       ret = KRB5KDC_ERR_KEY_EXPIRED;
+               else if (NT_STATUS_EQUAL(nt_status, NT_STATUS_ACCOUNT_EXPIRED))
+                       ret = KRB5KDC_ERR_CLIENT_REVOKED;
+               else if (NT_STATUS_EQUAL(nt_status, NT_STATUS_ACCOUNT_DISABLED))
+                       ret = KRB5KDC_ERR_CLIENT_REVOKED;
+               else if (NT_STATUS_EQUAL(nt_status, NT_STATUS_INVALID_LOGON_HOURS))
+                       ret = KRB5KDC_ERR_CLIENT_REVOKED;
+               else if (NT_STATUS_EQUAL(nt_status, NT_STATUS_ACCOUNT_LOCKED_OUT))
+                       ret = KRB5KDC_ERR_CLIENT_REVOKED;
+               else if (NT_STATUS_EQUAL(nt_status, NT_STATUS_INVALID_WORKSTATION))
+                       ret = KRB5KDC_ERR_POLICY;
+               else
+                       ret = KRB5KDC_ERR_POLICY;
+
+               samba_kdc_build_edata_reply(tmp_ctx, e_data, nt_status);
+       }
 
        return ret;
 }