dsdb: Avoid calculating the PSO multiple times
[samba.git] / source4 / auth / sam.c
index 56b64e50093b7d85ad1f4b14ba7f16e93d948591..07cfbd06b3363659504580ce7518062e63f7e383 100644 (file)
 #include "dsdb/common/util.h"
 #include "libcli/ldap/ldap_ndr.h"
 #include "param/param.h"
+#include "librpc/gen_ndr/ndr_winbind_c.h"
 
-#define KRBTGT_ATTRS \
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_AUTH
+
+#define KRBTGT_ATTRS                           \
        /* required for the krb5 kdc */         \
        "objectClass",                          \
        "sAMAccountName",                       \
@@ -64,6 +68,14 @@ const char *server_attrs[] = {
 };
 
 const char *user_attrs[] = {
+       /*
+        * This ordering (having msDS-ResultantPSO first) is
+        * important.  By processing this attribute first it is
+        * available in the operational module for the other PSO
+        * attribute calcuations to use.
+        */
+       "msDS-ResultantPSO",
+
        KRBTGT_ATTRS,
 
        "logonHours",
@@ -74,9 +86,14 @@ const char *user_attrs[] = {
         */
        "lockoutTime",
 
+       /*
+        * Needed for SendToSAM requests
+        */
+       "objectGUID",
+
        /* check 'allowed workstations' */
        "userWorkstations",
-                      
+
        /* required for user_info_dc, not access control: */
        "displayName",
        "scriptPath",
@@ -280,10 +297,46 @@ _PUBLIC_ NTSTATUS authsam_account_ok(TALLOC_CTX *mem_ctx,
        return NT_STATUS_OK;
 }
 
+static NTSTATUS authsam_domain_group_filter(TALLOC_CTX *mem_ctx,
+                                           char **_filter)
+{
+       char *filter = NULL;
+
+       *_filter = NULL;
+
+       filter = talloc_strdup(mem_ctx, "(&(objectClass=group)");
+       if (filter == NULL) {
+               return NT_STATUS_NO_MEMORY;
+       }
+
+       /*
+        * Skip all builtin groups, they're added later.
+        */
+       filter = talloc_asprintf_append_buffer(filter,
+                               "(!(groupType:1.2.840.113556.1.4.803:=%u))",
+                               GROUP_TYPE_BUILTIN_LOCAL_GROUP);
+       if (filter == NULL) {
+               return NT_STATUS_NO_MEMORY;
+       }
+       /*
+        * Only include security groups.
+        */
+       filter = talloc_asprintf_append_buffer(filter,
+                               "(groupType:1.2.840.113556.1.4.803:=%u))",
+                               GROUP_TYPE_SECURITY_ENABLED);
+       if (filter == NULL) {
+               return NT_STATUS_NO_MEMORY;
+       }
+
+       *_filter = filter;
+       return NT_STATUS_OK;
+}
+
 _PUBLIC_ NTSTATUS authsam_make_user_info_dc(TALLOC_CTX *mem_ctx,
                                           struct ldb_context *sam_ctx,
                                           const char *netbios_name,
                                           const char *domain_name,
+                                          const char *dns_domain_name,
                                           struct ldb_dn *domain_dn, 
                                           struct ldb_message *msg,
                                           DATA_BLOB user_sess_key,
@@ -293,7 +346,8 @@ _PUBLIC_ NTSTATUS authsam_make_user_info_dc(TALLOC_CTX *mem_ctx,
        NTSTATUS status;
        struct auth_user_info_dc *user_info_dc;
        struct auth_user_info *info;
-       const char *str, *filter;
+       const char *str = NULL;
+       char *filter = NULL;
        /* SIDs for the account and his primary group */
        struct dom_sid *account_sid;
        const char *primary_group_string;
@@ -339,13 +393,15 @@ _PUBLIC_ NTSTATUS authsam_make_user_info_dc(TALLOC_CTX *mem_ctx,
        sids[PRIMARY_GROUP_SID_INDEX] = *domain_sid;
        sid_append_rid(&sids[PRIMARY_GROUP_SID_INDEX], ldb_msg_find_attr_as_uint(msg, "primaryGroupID", ~0));
 
-       /* Filter out builtin groups from this token.  We will search
+       /*
+        * Filter out builtin groups from this token. We will search
         * for builtin groups later, and not include them in the PAC
-        * on SamLogon validation info */
-       filter = talloc_asprintf(tmp_ctx, "(&(objectClass=group)(!(groupType:1.2.840.113556.1.4.803:=%u))(groupType:1.2.840.113556.1.4.803:=%u))", GROUP_TYPE_BUILTIN_LOCAL_GROUP, GROUP_TYPE_SECURITY_ENABLED);
-       if (filter == NULL) {
+        * or SamLogon validation info.
+        */
+       status = authsam_domain_group_filter(tmp_ctx, &filter);
+       if (!NT_STATUS_IS_OK(status)) {
                TALLOC_FREE(user_info_dc);
-               return NT_STATUS_NO_MEMORY;
+               return status;
        }
 
        primary_group_string = dom_sid_string(tmp_ctx, &sids[PRIMARY_GROUP_SID_INDEX]);
@@ -401,12 +457,33 @@ _PUBLIC_ NTSTATUS authsam_make_user_info_dc(TALLOC_CTX *mem_ctx,
        info->account_name = talloc_steal(info,
                ldb_msg_find_attr_as_string(msg, "sAMAccountName", NULL));
 
+       info->user_principal_name = talloc_steal(info,
+               ldb_msg_find_attr_as_string(msg, "userPrincipalName", NULL));
+       if (info->user_principal_name == NULL && dns_domain_name != NULL) {
+               info->user_principal_name = talloc_asprintf(info, "%s@%s",
+                                       info->account_name,
+                                       dns_domain_name);
+               if (info->user_principal_name == NULL) {
+                       TALLOC_FREE(user_info_dc);
+                       return NT_STATUS_NO_MEMORY;
+               }
+               info->user_principal_constructed = true;
+       }
+
        info->domain_name = talloc_strdup(info, domain_name);
        if (info->domain_name == NULL) {
                TALLOC_FREE(user_info_dc);
                return NT_STATUS_NO_MEMORY;
        }
 
+       if (dns_domain_name != NULL) {
+               info->dns_domain_name = talloc_strdup(info, dns_domain_name);
+               if (info->dns_domain_name == NULL) {
+                       TALLOC_FREE(user_info_dc);
+                       return NT_STATUS_NO_MEMORY;
+               }
+       }
+
        str = ldb_msg_find_attr_as_string(msg, "displayName", "");
        info->full_name = talloc_strdup(info, str);
        if (info->full_name == NULL) {
@@ -523,6 +600,68 @@ _PUBLIC_ NTSTATUS authsam_make_user_info_dc(TALLOC_CTX *mem_ctx,
        return NT_STATUS_OK;
 }
 
+_PUBLIC_ NTSTATUS authsam_update_user_info_dc(TALLOC_CTX *mem_ctx,
+                       struct ldb_context *sam_ctx,
+                       struct auth_user_info_dc *user_info_dc)
+{
+       char *filter = NULL;
+       NTSTATUS status;
+       uint32_t i;
+       uint32_t n = 0;
+
+       /*
+        * This function exists to expand group memberships
+        * in the local domain (forest), as the token
+        * may come from a different domain.
+        */
+
+       /*
+        * Filter out builtin groups from this token. We will search
+        * for builtin groups later.
+        */
+       status = authsam_domain_group_filter(mem_ctx, &filter);
+       if (!NT_STATUS_IS_OK(status)) {
+               TALLOC_FREE(user_info_dc);
+               return status;
+       }
+
+       /*
+        * We loop only over the existing number of
+        * sids.
+        */
+       n = user_info_dc->num_sids;
+       for (i = 0; i < n; i++) {
+               struct dom_sid *sid = &user_info_dc->sids[i];
+               char sid_buf[DOM_SID_STR_BUFLEN] = {0,};
+               char dn_str[DOM_SID_STR_BUFLEN*2] = {0,};
+               DATA_BLOB dn_blob = data_blob_null;
+               int len;
+
+               len = dom_sid_string_buf(sid, sid_buf, sizeof(sid_buf));
+               if (len+1 > sizeof(sid_buf)) {
+                       return NT_STATUS_INVALID_SID;
+               }
+               snprintf(dn_str, sizeof(dn_str), "<SID=%s>", sid_buf);
+               dn_blob = data_blob_string_const(dn_str);
+
+               /*
+                * We already have the SID in the token, so set
+                * 'only childs' flag to true and add all
+                * groups which match the filter.
+                */
+               status = dsdb_expand_nested_groups(sam_ctx, &dn_blob,
+                                                  true, filter,
+                                                  user_info_dc,
+                                                  &user_info_dc->sids,
+                                                  &user_info_dc->num_sids);
+               if (!NT_STATUS_IS_OK(status)) {
+                       return status;
+               }
+       }
+
+       return NT_STATUS_OK;
+}
+
 NTSTATUS sam_get_results_principal(struct ldb_context *sam_ctx,
                                   TALLOC_CTX *mem_ctx, const char *principal,
                                   const char **attrs,
@@ -630,6 +769,7 @@ NTSTATUS authsam_get_user_info_dc_principal(TALLOC_CTX *mem_ctx,
        nt_status = authsam_make_user_info_dc(tmp_ctx, sam_ctx,
                                             lpcfg_netbios_name(lp_ctx),
                                             lpcfg_sam_name(lp_ctx),
+                                            lpcfg_sam_dnsname(lp_ctx),
                                             domain_dn,
                                             msg,
                                             user_sess_key, lm_sess_key,
@@ -645,6 +785,38 @@ NTSTATUS authsam_get_user_info_dc_principal(TALLOC_CTX *mem_ctx,
        return NT_STATUS_OK;
 }
 
+/*
+ * Returns the details for the Password Settings Object (PSO), if one applies
+ * the user.
+ */
+static int authsam_get_user_pso(struct ldb_context *sam_ctx,
+                               TALLOC_CTX *mem_ctx,
+                               struct ldb_message *user_msg,
+                               struct ldb_message **pso_msg)
+{
+       const char *attrs[] = { "msDS-LockoutThreshold",
+                               "msDS-LockoutObservationWindow",
+                               NULL };
+       struct ldb_dn *pso_dn = NULL;
+       struct ldb_result *res = NULL;
+       int ret;
+
+       /* check if the user has a PSO that applies to it */
+       pso_dn = ldb_msg_find_attr_as_dn(sam_ctx, mem_ctx, user_msg,
+                                        "msDS-ResultantPSO");
+
+       if (pso_dn != NULL) {
+               ret = dsdb_search_dn(sam_ctx, mem_ctx, &res, pso_dn, attrs, 0);
+               if (ret != LDB_SUCCESS) {
+                       return ret;
+               }
+
+               *pso_msg = res->msgs[0];
+       }
+
+       return LDB_SUCCESS;
+}
+
 NTSTATUS authsam_update_bad_pwd_count(struct ldb_context *sam_ctx,
                                      struct ldb_message *msg,
                                      struct ldb_dn *domain_dn)
@@ -658,6 +830,7 @@ NTSTATUS authsam_update_bad_pwd_count(struct ldb_context *sam_ctx,
        NTSTATUS status;
        struct ldb_result *domain_res;
        struct ldb_message *msg_mod = NULL;
+       struct ldb_message *pso_msg = NULL;
        TALLOC_CTX *mem_ctx;
 
        mem_ctx = talloc_new(msg);
@@ -671,21 +844,56 @@ NTSTATUS authsam_update_bad_pwd_count(struct ldb_context *sam_ctx,
                return NT_STATUS_INTERNAL_DB_CORRUPTION;
        }
 
+       ret = authsam_get_user_pso(sam_ctx, mem_ctx, msg, &pso_msg);
+       if (ret != LDB_SUCCESS) {
+
+               /*
+                * fallback to using the domain defaults so that we still
+                * record the bad password attempt
+                */
+               DBG_ERR("Error (%d) checking PSO for %s",
+                       ret, ldb_dn_get_linearized(msg->dn));
+       }
+
        status = dsdb_update_bad_pwd_count(mem_ctx, sam_ctx,
-                                          msg, domain_res->msgs[0], &msg_mod);
+                                          msg, domain_res->msgs[0], pso_msg,
+                                          &msg_mod);
        if (!NT_STATUS_IS_OK(status)) {
                TALLOC_FREE(mem_ctx);
                return status;
        }
 
        if (msg_mod != NULL) {
-               ret = dsdb_modify(sam_ctx, msg_mod, 0);
+               struct ldb_request *req;
+
+               ret = ldb_build_mod_req(&req, sam_ctx, sam_ctx,
+                                       msg_mod,
+                                       NULL,
+                                       NULL,
+                                       ldb_op_default_callback,
+                                       NULL);
                if (ret != LDB_SUCCESS) {
-                       DEBUG(0, ("Failed to update badPwdCount, badPasswordTime or set lockoutTime on %s: %s\n",
-                                 ldb_dn_get_linearized(msg_mod->dn), ldb_errstring(sam_ctx)));
-                       TALLOC_FREE(mem_ctx);
-                       return NT_STATUS_INTERNAL_ERROR;
+                       goto done;
                }
+
+               ret = ldb_request_add_control(req,
+                                             DSDB_CONTROL_FORCE_RODC_LOCAL_CHANGE,
+                                             false, NULL);
+               if (ret != LDB_SUCCESS) {
+                       talloc_free(req);
+                       goto done;
+               }
+
+               ret = dsdb_autotransaction_request(sam_ctx, req);
+               talloc_free(req);
+       }
+
+done:
+       if (ret != LDB_SUCCESS) {
+               DEBUG(0, ("Failed to update badPwdCount, badPasswordTime or set lockoutTime on %s: %s\n",
+                         ldb_dn_get_linearized(msg_mod->dn), ldb_errstring(sam_ctx)));
+               TALLOC_FREE(mem_ctx);
+               return NT_STATUS_INTERNAL_ERROR;
        }
 
        TALLOC_FREE(mem_ctx);
@@ -791,23 +999,54 @@ static NTSTATUS authsam_update_lastlogon_timestamp(struct ldb_context *sam_ctx,
        return NT_STATUS_OK;
 }
 
+/****************************************************************************
+ Look for the specified user in the sam, return ldb result structures
+****************************************************************************/
+
+NTSTATUS authsam_search_account(TALLOC_CTX *mem_ctx, struct ldb_context *sam_ctx,
+                                        const char *account_name,
+                                        struct ldb_dn *domain_dn,
+                                        struct ldb_message **ret_msg)
+{
+       int ret;
+
+       /* pull the user attributes */
+       ret = dsdb_search_one(sam_ctx, mem_ctx, ret_msg, domain_dn, LDB_SCOPE_SUBTREE,
+                             user_attrs,
+                             DSDB_SEARCH_SHOW_EXTENDED_DN,
+                             "(&(sAMAccountName=%s)(objectclass=user))",
+                             ldb_binary_encode_string(mem_ctx, account_name));
+       if (ret == LDB_ERR_NO_SUCH_OBJECT) {
+               DEBUG(3,("sam_search_user: Couldn't find user [%s] in samdb, under %s\n",
+                        account_name, ldb_dn_get_linearized(domain_dn)));
+               return NT_STATUS_NO_SUCH_USER;
+       }
+       if (ret != LDB_SUCCESS) {
+               return NT_STATUS_INTERNAL_DB_CORRUPTION;
+       }
+
+       return NT_STATUS_OK;
+}
 
 
 /* Reset the badPwdCount to zero and update the lastLogon time. */
 NTSTATUS authsam_logon_success_accounting(struct ldb_context *sam_ctx,
                                          const struct ldb_message *msg,
                                          struct ldb_dn *domain_dn,
-                                         bool interactive_or_kerberos)
+                                         bool interactive_or_kerberos,
+                                         struct netr_SendToSamBase **send_to_sam)
 {
        int ret;
        NTSTATUS status;
        int badPwdCount;
+       int dbBadPwdCount;
        int64_t lockoutTime;
        struct ldb_message *msg_mod;
        TALLOC_CTX *mem_ctx;
        struct timeval tv_now;
        NTTIME now;
        NTTIME lastLogonTimestamp;
+       bool am_rodc = false;
 
        mem_ctx = talloc_new(msg);
        if (mem_ctx == NULL) {
@@ -815,8 +1054,9 @@ NTSTATUS authsam_logon_success_accounting(struct ldb_context *sam_ctx,
        }
 
        lockoutTime = ldb_msg_find_attr_as_int64(msg, "lockoutTime", 0);
+       dbBadPwdCount = ldb_msg_find_attr_as_int(msg, "badPwdCount", 0);
        if (interactive_or_kerberos) {
-               badPwdCount = ldb_msg_find_attr_as_int(msg, "badPwdCount", 0);
+               badPwdCount = dbBadPwdCount;
        } else {
                badPwdCount = samdb_result_effective_badPwdCount(sam_ctx, mem_ctx,
                                                                 domain_dn, msg);
@@ -877,27 +1117,87 @@ NTSTATUS authsam_logon_success_accounting(struct ldb_context *sam_ctx,
                        TALLOC_FREE(mem_ctx);
                        return NT_STATUS_NO_MEMORY;
                }
+       } else {
+               /* Set an unset logonCount to 0 on first successful login */
+               if (ldb_msg_find_ldb_val(msg, "logonCount") == NULL) {
+                       ret = samdb_msg_add_int(sam_ctx, msg_mod, msg_mod,
+                                               "logonCount", 0);
+                       if (ret != LDB_SUCCESS) {
+                               TALLOC_FREE(mem_ctx);
+                               return NT_STATUS_NO_MEMORY;
+                       }
+               }
        }
 
-       status = authsam_update_lastlogon_timestamp(sam_ctx, msg_mod, domain_dn,
-                                                   lastLogonTimestamp, now);
-       if (!NT_STATUS_IS_OK(status)) {
+       ret = samdb_rodc(sam_ctx, &am_rodc);
+       if (ret != LDB_SUCCESS) {
                TALLOC_FREE(mem_ctx);
-               return NT_STATUS_NO_MEMORY;
+               return NT_STATUS_INTERNAL_ERROR;
+       }
+
+       if (!am_rodc) {
+               status = authsam_update_lastlogon_timestamp(sam_ctx, msg_mod, domain_dn,
+                                                           lastLogonTimestamp, now);
+               if (!NT_STATUS_IS_OK(status)) {
+                       TALLOC_FREE(mem_ctx);
+                       return NT_STATUS_NO_MEMORY;
+               }
+       } else {
+               /* Perform the (async) SendToSAM calls for MS-SAMS */
+               if (dbBadPwdCount != 0 && send_to_sam != NULL) {
+                       struct netr_SendToSamBase *base_msg;
+                       struct GUID guid = samdb_result_guid(msg, "objectGUID");
+                       base_msg = talloc_zero(msg, struct netr_SendToSamBase);
+
+                       base_msg->message_type = SendToSamResetBadPasswordCount;
+                       base_msg->message_size = 16;
+                       base_msg->message.reset_bad_password.guid = guid;
+                       *send_to_sam = base_msg;
+               }
        }
 
        if (msg_mod->num_elements > 0) {
-               ret = dsdb_replace(sam_ctx, msg_mod, 0);
+               unsigned int i;
+               struct ldb_request *req;
+
+               /* mark all the message elements as LDB_FLAG_MOD_REPLACE */
+               for (i=0;i<msg_mod->num_elements;i++) {
+                       msg_mod->elements[i].flags = LDB_FLAG_MOD_REPLACE;
+               }
+
+               ret = ldb_build_mod_req(&req, sam_ctx, sam_ctx,
+                                       msg_mod,
+                                       NULL,
+                                       NULL,
+                                       ldb_op_default_callback,
+                                       NULL);
                if (ret != LDB_SUCCESS) {
-                       DEBUG(0, ("Failed to set badPwdCount and lockoutTime "
-                                 "to 0 and/or  lastlogon to now (%lld) "
-                                 "%s: %s\n", (long long int)now,
-                                 ldb_dn_get_linearized(msg_mod->dn),
-                                 ldb_errstring(sam_ctx)));
-                       TALLOC_FREE(mem_ctx);
-                       return NT_STATUS_INTERNAL_ERROR;
+                       goto done;
                }
+
+               ret = ldb_request_add_control(req,
+                                             DSDB_CONTROL_FORCE_RODC_LOCAL_CHANGE,
+                                             false, NULL);
+               if (ret != LDB_SUCCESS) {
+                       talloc_free(req);
+                       goto done;
+               }
+
+               ret = dsdb_autotransaction_request(sam_ctx, req);
+               talloc_free(req);
+       }
+
+done:
+       if (ret != LDB_SUCCESS) {
+               DEBUG(0, ("Failed to set badPwdCount and lockoutTime "
+                         "to 0 and/or  lastlogon to now (%lld) "
+                         "%s: %s\n", (long long int)now,
+                         ldb_dn_get_linearized(msg_mod->dn),
+                         ldb_errstring(sam_ctx)));
+               TALLOC_FREE(mem_ctx);
+               return NT_STATUS_INTERNAL_ERROR;
        }
+
        TALLOC_FREE(mem_ctx);
        return NT_STATUS_OK;
 }