diff --git a/source4/auth/auth.h b/source4/auth/auth.h index f6d7393..6bad017 100644 --- a/source4/auth/auth.h +++ b/source4/auth/auth.h @@ -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, diff --git a/source4/auth/gensec/gensec_krb5.c b/source4/auth/gensec/gensec_krb5.c index b04abfc..09bdec5 100644 --- a/source4/auth/gensec/gensec_krb5.c +++ b/source4/auth/gensec/gensec_krb5.c @@ -609,7 +609,9 @@ static NTSTATUS gensec_krb5_session_info(struct gensec_security *gensec_security ret, mem_ctx))); if (gensec_security->auth_context && !gensec_setting_bool(gensec_security->settings, "gensec", "require_pac", false)) { - DEBUG(1, ("Unable to find PAC, resorting to local user lookup: %s")); + 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, gensec_security->auth_context, principal_string, diff --git a/source4/auth/ntlm/auth_sam.c b/source4/auth/ntlm/auth_sam.c index 0bb79e2..253ddf2 100644 --- a/source4/auth/ntlm/auth_sam.c +++ b/source4/auth/ntlm/auth_sam.c @@ -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; } diff --git a/source4/auth/sam.c b/source4/auth/sam.c index 68eaacf..acbd50c 100644 --- a/source4/auth/sam.c +++ b/source4/auth/sam.c @@ -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", diff --git a/source4/heimdal/kdc/headers.h b/source4/heimdal/kdc/headers.h index 2240336..b9a8288 100644 --- a/source4/heimdal/kdc/headers.h +++ b/source4/heimdal/kdc/headers.h @@ -104,6 +104,7 @@ #ifndef NO_NTLM #include #endif +#include #include #undef ALLOC diff --git a/source4/heimdal/kdc/kdc_locl.h b/source4/heimdal/kdc/kdc_locl.h index 9b291ac..daf1558 100644 --- a/source4/heimdal/kdc/kdc_locl.h +++ b/source4/heimdal/kdc/kdc_locl.h @@ -39,7 +39,6 @@ #define __KDC_LOCL_H__ #include "headers.h" -#include "kdc.h" typedef struct pk_client_params pk_client_params; struct DigestREQ; diff --git a/source4/heimdal/kdc/kerberos5.c b/source4/heimdal/kdc/kerberos5.c index 941a2e0..ac495b1 100644 --- a/source4/heimdal/kdc/kerberos5.c +++ b/source4/heimdal/kdc/kerberos5.c @@ -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; diff --git a/source4/heimdal/kdc/krb5tgs.c b/source4/heimdal/kdc/krb5tgs.c index 3abdb18..59104da 100644 --- a/source4/heimdal/kdc/krb5tgs.c +++ b/source4/heimdal/kdc/krb5tgs.c @@ -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; diff --git a/source4/heimdal/kdc/windc.c b/source4/heimdal/kdc/windc.c index fe3cd99..9d7fa52 100644 --- a/source4/heimdal/kdc/windc.c +++ b/source4/heimdal/kdc/windc.c @@ -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); } diff --git a/source4/heimdal/kdc/windc_plugin.h b/source4/heimdal/kdc/windc_plugin.h index 3401669..c7efb7b 100644 --- a/source4/heimdal/kdc/windc_plugin.h +++ b/source4/heimdal/kdc/windc_plugin.h @@ -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; diff --git a/source4/kdc/hdb-samba4.c b/source4/kdc/hdb-samba4.c index c0fa513..eda7867 100644 --- a/source4/kdc/hdb-samba4.c +++ b/source4/kdc/hdb-samba4.c @@ -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; diff --git a/source4/kdc/kdc.h b/source4/kdc/kdc.h index a281e1d..d37a32e 100644 --- a/source4/kdc/kdc.h +++ b/source4/kdc/kdc.h @@ -22,8 +22,8 @@ #include "system/kerberos.h" #include "auth/kerberos/kerberos.h" -#include #include +#include #include #include "kdc/pac_glue.h" diff --git a/source4/kdc/pac-glue.c b/source4/kdc/pac-glue.c index 411e752..5bd4cb1 100644 --- a/source4/kdc/pac-glue.c +++ b/source4/kdc/pac-glue.c @@ -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; } diff --git a/source4/scripting/python/samba/samdb.py b/source4/scripting/python/samba/samdb.py index 454a9d1..8ca4f65 100644 --- a/source4/scripting/python/samba/samdb.py +++ b/source4/scripting/python/samba/samdb.py @@ -152,7 +152,7 @@ userAccountControl: %u raise self.transaction_commit() - def setpassword(self, filter, password): + def setpassword(self, filter, password, must_change_at_next_login=False): """Set a password on a user record :param filter: LDAP filter to find the user (eg samccountname=name) @@ -184,6 +184,15 @@ userPassword:: %s self.modify_ldif(setpw) + if must_change_at_next_login: + mod = """ +dn: %s +changetype: modify +replace: pwdLastSet +pwdLastSet: 0 +""" % (user_dn) + self.modify_ldif(mod) + # modify the userAccountControl to remove the disabled bit self.enable_account(user_dn) except: @@ -212,7 +221,7 @@ userPassword:: %s glue.dsdb_set_ntds_invocation_id(self, invocation_id) def setexpiry(self, user, expiry_seconds, noexpiry): - """Set the password expiry for a user + """Set the account expiry for a user :param expiry_seconds: expiry time from now in seconds :param noexpiry: if set, then don't expire password @@ -246,3 +255,4 @@ accountExpires: %u self.transaction_cancel() raise self.transaction_commit(); + diff --git a/source4/setup/setpassword b/source4/setup/setpassword index 90a217f..d44f143 100755 --- a/source4/setup/setpassword +++ b/source4/setup/setpassword @@ -41,6 +41,7 @@ credopts = options.CredentialsOptions(parser) parser.add_option_group(credopts) parser.add_option("--filter", help="LDAP Filter to set password on", type=str) parser.add_option("--newpassword", help="Set password", type=str) +parser.add_option("--must-change-at-next-login", help="Force password to be changed on next login", action="store_true") opts, args = parser.parse_args() @@ -74,4 +75,5 @@ creds = credopts.get_credentials(lp) samdb = SamDB(url=lp.get("sam database"), session_info=system_session(), credentials=creds, lp=lp) -samdb.setpassword(filter, password) +samdb.setpassword(filter, password, must_change_at_next_login=opts.must_change_at_next_login) + diff --git a/source4/setup/tests/blackbox_setpassword.sh b/source4/setup/tests/blackbox_setpassword.sh index 89f1aa5..70061f6 100755 --- a/source4/setup/tests/blackbox_setpassword.sh +++ b/source4/setup/tests/blackbox_setpassword.sh @@ -18,4 +18,6 @@ testit "newuser" $PYTHON ./setup/newuser --configfile=$PREFIX/simple-dc/etc/smb. testit "setpassword" $PYTHON ./setup/setpassword --configfile=$PREFIX/simple-dc/etc/smb.conf testuser --newpassword=testpass +testit "setpassword" $PYTHON ./setup/setpassword --configfile=$PREFIX/simple-dc/etc/smb.conf testuser --newpassword=testpass --must-change-at-next-login + exit $failed