CVE-2022-32746 ldb: Release LDB 2.3.4
[Samba.git] / source4 / auth / sam.c
blob8b233bab3ad8b2ea3ca8c58ec6323c7485059e78
1 /*
2 Unix SMB/CIFS implementation.
3 Password and authentication handling
4 Copyright (C) Andrew Bartlett <abartlet@samba.org> 2001-2010
5 Copyright (C) Gerald Carter 2003
6 Copyright (C) Stefan Metzmacher 2005
7 Copyright (C) Matthias Dieter Wallnöfer 2009
9 This program is free software; you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation; either version 3 of the License, or
12 (at your option) any later version.
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
19 You should have received a copy of the GNU General Public License
20 along with this program. If not, see <http://www.gnu.org/licenses/>.
23 #include "includes.h"
24 #include "system/time.h"
25 #include "auth/auth.h"
26 #include <ldb.h>
27 #include "dsdb/samdb/samdb.h"
28 #include "libcli/security/security.h"
29 #include "auth/auth_sam.h"
30 #include "dsdb/common/util.h"
31 #include "libcli/ldap/ldap_ndr.h"
32 #include "param/param.h"
33 #include "librpc/gen_ndr/ndr_winbind_c.h"
35 #undef DBGC_CLASS
36 #define DBGC_CLASS DBGC_AUTH
38 #define KRBTGT_ATTRS \
39 /* required for the krb5 kdc */ \
40 "objectClass", \
41 "sAMAccountName", \
42 "userPrincipalName", \
43 "servicePrincipalName", \
44 "msDS-KeyVersionNumber", \
45 "msDS-SecondaryKrbTgtNumber", \
46 "msDS-SupportedEncryptionTypes", \
47 "supplementalCredentials", \
48 "msDS-AllowedToDelegateTo", \
50 /* passwords */ \
51 "dBCSPwd", \
52 "unicodePwd", \
54 "userAccountControl", \
55 "msDS-User-Account-Control-Computed", \
56 "objectSid", \
58 "pwdLastSet", \
59 "msDS-UserPasswordExpiryTimeComputed", \
60 "accountExpires", \
62 /* Needed for RODC rule processing */ \
63 "msDS-KrbTgtLinkBL"
65 const char *krbtgt_attrs[] = {
66 KRBTGT_ATTRS, NULL
69 const char *server_attrs[] = {
70 KRBTGT_ATTRS, NULL
73 const char *user_attrs[] = {
75 * This ordering (having msDS-ResultantPSO first) is
76 * important. By processing this attribute first it is
77 * available in the operational module for the other PSO
78 * attribute calcuations to use.
80 "msDS-ResultantPSO",
82 KRBTGT_ATTRS,
84 "logonHours",
87 * To allow us to zero the badPwdCount and lockoutTime on
88 * successful logon, without database churn
90 "lockoutTime",
93 * Needed for SendToSAM requests
95 "objectGUID",
97 /* check 'allowed workstations' */
98 "userWorkstations",
100 /* required for user_info_dc, not access control: */
101 "displayName",
102 "scriptPath",
103 "profilePath",
104 "homeDirectory",
105 "homeDrive",
106 "lastLogon",
107 "lastLogonTimestamp",
108 "lastLogoff",
109 "accountExpires",
110 "badPwdCount",
111 "logonCount",
112 "primaryGroupID",
113 "memberOf",
114 "badPasswordTime",
115 "lmPwdHistory",
116 "ntPwdHistory",
117 NULL,
120 /****************************************************************************
121 Check if a user is allowed to logon at this time. Note this is the
122 servers local time, as logon hours are just specified as a weekly
123 bitmask.
124 ****************************************************************************/
126 static bool logon_hours_ok(struct ldb_message *msg, const char *name_for_logs)
128 /* In logon hours first bit is Sunday from 12AM to 1AM */
129 const struct ldb_val *hours;
130 struct tm *utctime;
131 time_t lasttime;
132 const char *asct;
133 uint8_t bitmask, bitpos;
135 hours = ldb_msg_find_ldb_val(msg, "logonHours");
136 if (!hours) {
137 DEBUG(5,("logon_hours_ok: No hours restrictions for user %s\n", name_for_logs));
138 return true;
141 if (hours->length != 168/8) {
142 DEBUG(5,("logon_hours_ok: malformed logon hours restrictions for user %s\n", name_for_logs));
143 return true;
146 lasttime = time(NULL);
147 utctime = gmtime(&lasttime);
148 if (!utctime) {
149 DEBUG(1, ("logon_hours_ok: failed to get gmtime. Failing logon for user %s\n",
150 name_for_logs));
151 return false;
154 /* find the corresponding byte and bit */
155 bitpos = (utctime->tm_wday * 24 + utctime->tm_hour) % 168;
156 bitmask = 1 << (bitpos % 8);
158 if (! (hours->data[bitpos/8] & bitmask)) {
159 struct tm *t = localtime(&lasttime);
160 if (!t) {
161 asct = "INVALID TIME";
162 } else {
163 asct = asctime(t);
164 if (!asct) {
165 asct = "INVALID TIME";
169 DEBUG(1, ("logon_hours_ok: Account for user %s not allowed to "
170 "logon at this time (%s).\n",
171 name_for_logs, asct ));
172 return false;
175 asct = asctime(utctime);
176 DEBUG(5,("logon_hours_ok: user %s allowed to logon at this time (%s)\n",
177 name_for_logs, asct ? asct : "UNKNOWN TIME" ));
179 return true;
182 /****************************************************************************
183 Do a specific test for a SAM_ACCOUNT being valid for this connection
184 (ie not disabled, expired and the like).
185 ****************************************************************************/
186 _PUBLIC_ NTSTATUS authsam_account_ok(TALLOC_CTX *mem_ctx,
187 struct ldb_context *sam_ctx,
188 uint32_t logon_parameters,
189 struct ldb_dn *domain_dn,
190 struct ldb_message *msg,
191 const char *logon_workstation,
192 const char *name_for_logs,
193 bool allow_domain_trust,
194 bool password_change)
196 uint16_t acct_flags;
197 const char *workstation_list;
198 NTTIME acct_expiry;
199 NTTIME must_change_time;
200 struct timeval tv_now = timeval_current();
201 NTTIME now = timeval_to_nttime(&tv_now);
203 DEBUG(4,("authsam_account_ok: Checking SMB password for user %s\n", name_for_logs));
205 acct_flags = samdb_result_acct_flags(msg, "msDS-User-Account-Control-Computed");
207 acct_expiry = samdb_result_account_expires(msg);
209 /* Check for when we must change this password, taking the
210 * userAccountControl flags into account */
211 must_change_time = samdb_result_nttime(msg,
212 "msDS-UserPasswordExpiryTimeComputed", 0);
214 workstation_list = ldb_msg_find_attr_as_string(msg, "userWorkstations", NULL);
216 /* Quit if the account was disabled. */
217 if (acct_flags & ACB_DISABLED) {
218 DEBUG(2,("authsam_account_ok: Account for user '%s' was disabled.\n", name_for_logs));
219 return NT_STATUS_ACCOUNT_DISABLED;
222 /* Quit if the account was locked out. */
223 if (acct_flags & ACB_AUTOLOCK) {
224 DEBUG(2,("authsam_account_ok: Account for user %s was locked out.\n", name_for_logs));
225 return NT_STATUS_ACCOUNT_LOCKED_OUT;
228 /* Test account expire time */
229 if (now > acct_expiry) {
230 DEBUG(2,("authsam_account_ok: Account for user '%s' has expired.\n", name_for_logs));
231 DEBUG(3,("authsam_account_ok: Account expired at '%s'.\n",
232 nt_time_string(mem_ctx, acct_expiry)));
233 return NT_STATUS_ACCOUNT_EXPIRED;
236 /* check for immediate expiry "must change at next logon" (but not if this is a password change request) */
237 if ((must_change_time == 0) && !password_change) {
238 DEBUG(2,("sam_account_ok: Account for user '%s' password must change!.\n",
239 name_for_logs));
240 return NT_STATUS_PASSWORD_MUST_CHANGE;
243 /* check for expired password (but not if this is a password change request) */
244 if ((must_change_time < now) && !password_change) {
245 DEBUG(2,("sam_account_ok: Account for user '%s' password expired!.\n",
246 name_for_logs));
247 DEBUG(2,("sam_account_ok: Password expired at '%s' unix time.\n",
248 nt_time_string(mem_ctx, must_change_time)));
249 return NT_STATUS_PASSWORD_EXPIRED;
252 /* Test workstation. Workstation list is comma separated. */
253 if (logon_workstation && workstation_list && *workstation_list) {
254 bool invalid_ws = true;
255 int i;
256 char **workstations = str_list_make(mem_ctx, workstation_list, ",");
258 for (i = 0; workstations && workstations[i]; i++) {
259 DEBUG(10,("sam_account_ok: checking for workstation match '%s' and '%s'\n",
260 workstations[i], logon_workstation));
262 if (strequal(workstations[i], logon_workstation)) {
263 invalid_ws = false;
264 break;
268 talloc_free(workstations);
270 if (invalid_ws) {
271 return NT_STATUS_INVALID_WORKSTATION;
275 if (!logon_hours_ok(msg, name_for_logs)) {
276 return NT_STATUS_INVALID_LOGON_HOURS;
279 if (!allow_domain_trust) {
280 if (acct_flags & ACB_DOMTRUST) {
281 DEBUG(2,("sam_account_ok: Domain trust account %s denied by server\n", name_for_logs));
282 return NT_STATUS_NOLOGON_INTERDOMAIN_TRUST_ACCOUNT;
285 if (!(logon_parameters & MSV1_0_ALLOW_SERVER_TRUST_ACCOUNT)) {
286 if (acct_flags & ACB_SVRTRUST) {
287 DEBUG(2,("sam_account_ok: Server trust account %s denied by server\n", name_for_logs));
288 return NT_STATUS_NOLOGON_SERVER_TRUST_ACCOUNT;
291 if (!(logon_parameters & MSV1_0_ALLOW_WORKSTATION_TRUST_ACCOUNT)) {
292 /* TODO: this fails with current solaris client. We
293 need to work with Gordon to work out why */
294 if (acct_flags & ACB_WSTRUST) {
295 DEBUG(4,("sam_account_ok: Wksta trust account %s denied by server\n", name_for_logs));
296 return NT_STATUS_NOLOGON_WORKSTATION_TRUST_ACCOUNT;
300 return NT_STATUS_OK;
303 static NTSTATUS authsam_domain_group_filter(TALLOC_CTX *mem_ctx,
304 char **_filter)
306 char *filter = NULL;
308 *_filter = NULL;
310 filter = talloc_strdup(mem_ctx, "(&(objectClass=group)");
311 if (filter == NULL) {
312 return NT_STATUS_NO_MEMORY;
316 * Skip all builtin groups, they're added later.
318 filter = talloc_asprintf_append_buffer(filter,
319 "(!(groupType:1.2.840.113556.1.4.803:=%u))",
320 GROUP_TYPE_BUILTIN_LOCAL_GROUP);
321 if (filter == NULL) {
322 return NT_STATUS_NO_MEMORY;
325 * Only include security groups.
327 filter = talloc_asprintf_append_buffer(filter,
328 "(groupType:1.2.840.113556.1.4.803:=%u))",
329 GROUP_TYPE_SECURITY_ENABLED);
330 if (filter == NULL) {
331 return NT_STATUS_NO_MEMORY;
334 *_filter = filter;
335 return NT_STATUS_OK;
338 _PUBLIC_ NTSTATUS authsam_make_user_info_dc(TALLOC_CTX *mem_ctx,
339 struct ldb_context *sam_ctx,
340 const char *netbios_name,
341 const char *domain_name,
342 const char *dns_domain_name,
343 struct ldb_dn *domain_dn,
344 struct ldb_message *msg,
345 DATA_BLOB user_sess_key,
346 DATA_BLOB lm_sess_key,
347 struct auth_user_info_dc **_user_info_dc)
349 NTSTATUS status;
350 struct auth_user_info_dc *user_info_dc;
351 struct auth_user_info *info;
352 const char *str = NULL;
353 char *filter = NULL;
354 /* SIDs for the account and his primary group */
355 struct dom_sid *account_sid;
356 struct dom_sid_buf buf;
357 const char *primary_group_dn;
358 DATA_BLOB primary_group_blob;
359 /* SID structures for the expanded group memberships */
360 struct dom_sid *sids = NULL;
361 unsigned int num_sids = 0, i;
362 struct dom_sid *domain_sid;
363 TALLOC_CTX *tmp_ctx;
364 struct ldb_message_element *el;
366 user_info_dc = talloc(mem_ctx, struct auth_user_info_dc);
367 NT_STATUS_HAVE_NO_MEMORY(user_info_dc);
369 tmp_ctx = talloc_new(user_info_dc);
370 if (tmp_ctx == NULL) {
371 TALLOC_FREE(user_info_dc);
372 return NT_STATUS_NO_MEMORY;
375 sids = talloc_array(user_info_dc, struct dom_sid, 2);
376 if (sids == NULL) {
377 TALLOC_FREE(user_info_dc);
378 return NT_STATUS_NO_MEMORY;
381 num_sids = 2;
383 account_sid = samdb_result_dom_sid(tmp_ctx, msg, "objectSid");
384 if (account_sid == NULL) {
385 TALLOC_FREE(user_info_dc);
386 return NT_STATUS_NO_MEMORY;
389 status = dom_sid_split_rid(tmp_ctx, account_sid, &domain_sid, NULL);
390 if (!NT_STATUS_IS_OK(status)) {
391 talloc_free(user_info_dc);
392 return status;
395 sids[PRIMARY_USER_SID_INDEX] = *account_sid;
396 sids[PRIMARY_GROUP_SID_INDEX] = *domain_sid;
397 sid_append_rid(&sids[PRIMARY_GROUP_SID_INDEX], ldb_msg_find_attr_as_uint(msg, "primaryGroupID", ~0));
400 * Filter out builtin groups from this token. We will search
401 * for builtin groups later, and not include them in the PAC
402 * or SamLogon validation info.
404 status = authsam_domain_group_filter(tmp_ctx, &filter);
405 if (!NT_STATUS_IS_OK(status)) {
406 TALLOC_FREE(user_info_dc);
407 return status;
410 primary_group_dn = talloc_asprintf(
411 tmp_ctx,
412 "<SID=%s>",
413 dom_sid_str_buf(&sids[PRIMARY_GROUP_SID_INDEX], &buf));
414 if (primary_group_dn == NULL) {
415 TALLOC_FREE(user_info_dc);
416 return NT_STATUS_NO_MEMORY;
419 primary_group_blob = data_blob_string_const(primary_group_dn);
421 /* Expands the primary group - this function takes in
422 * memberOf-like values, so we fake one up with the
423 * <SID=S-...> format of DN and then let it expand
424 * them, as long as they meet the filter - so only
425 * domain groups, not builtin groups
427 * The primary group is still treated specially, so we set the
428 * 'only childs' flag to true
430 status = dsdb_expand_nested_groups(sam_ctx, &primary_group_blob, true, filter,
431 user_info_dc, &sids, &num_sids);
432 if (!NT_STATUS_IS_OK(status)) {
433 talloc_free(user_info_dc);
434 return status;
437 /* Expands the additional groups */
438 el = ldb_msg_find_element(msg, "memberOf");
439 for (i = 0; el && i < el->num_values; i++) {
440 /* This function takes in memberOf values and expands
441 * them, as long as they meet the filter - so only
442 * domain groups, not builtin groups */
443 status = dsdb_expand_nested_groups(sam_ctx, &el->values[i], false, filter,
444 user_info_dc, &sids, &num_sids);
445 if (!NT_STATUS_IS_OK(status)) {
446 talloc_free(user_info_dc);
447 return status;
451 user_info_dc->sids = sids;
452 user_info_dc->num_sids = num_sids;
454 user_info_dc->info = info = talloc_zero(user_info_dc, struct auth_user_info);
455 NT_STATUS_HAVE_NO_MEMORY(user_info_dc->info);
457 str = ldb_msg_find_attr_as_string(msg, "sAMAccountName", NULL);
458 info->account_name = talloc_strdup(info, str);
459 if (info->account_name == NULL) {
460 TALLOC_FREE(user_info_dc);
461 return NT_STATUS_NO_MEMORY;
464 str = ldb_msg_find_attr_as_string(msg, "userPrincipalName", NULL);
465 if (str == NULL && dns_domain_name != NULL) {
466 info->user_principal_name = talloc_asprintf(info, "%s@%s",
467 info->account_name,
468 dns_domain_name);
469 if (info->user_principal_name == NULL) {
470 TALLOC_FREE(user_info_dc);
471 return NT_STATUS_NO_MEMORY;
473 info->user_principal_constructed = true;
474 } else if (str != NULL) {
475 info->user_principal_name = talloc_strdup(info, str);
476 if (info->user_principal_name == NULL) {
477 TALLOC_FREE(user_info_dc);
478 return NT_STATUS_NO_MEMORY;
482 info->domain_name = talloc_strdup(info, domain_name);
483 if (info->domain_name == NULL) {
484 TALLOC_FREE(user_info_dc);
485 return NT_STATUS_NO_MEMORY;
488 if (dns_domain_name != NULL) {
489 info->dns_domain_name = talloc_strdup(info, dns_domain_name);
490 if (info->dns_domain_name == NULL) {
491 TALLOC_FREE(user_info_dc);
492 return NT_STATUS_NO_MEMORY;
496 str = ldb_msg_find_attr_as_string(msg, "displayName", "");
497 info->full_name = talloc_strdup(info, str);
498 if (info->full_name == NULL) {
499 TALLOC_FREE(user_info_dc);
500 return NT_STATUS_NO_MEMORY;
503 str = ldb_msg_find_attr_as_string(msg, "scriptPath", "");
504 info->logon_script = talloc_strdup(info, str);
505 if (info->logon_script == NULL) {
506 TALLOC_FREE(user_info_dc);
507 return NT_STATUS_NO_MEMORY;
510 str = ldb_msg_find_attr_as_string(msg, "profilePath", "");
511 info->profile_path = talloc_strdup(info, str);
512 if (info->profile_path == NULL) {
513 TALLOC_FREE(user_info_dc);
514 return NT_STATUS_NO_MEMORY;
517 str = ldb_msg_find_attr_as_string(msg, "homeDirectory", "");
518 info->home_directory = talloc_strdup(info, str);
519 if (info->home_directory == NULL) {
520 TALLOC_FREE(user_info_dc);
521 return NT_STATUS_NO_MEMORY;
524 str = ldb_msg_find_attr_as_string(msg, "homeDrive", "");
525 info->home_drive = talloc_strdup(info, str);
526 if (info->home_drive == NULL) {
527 TALLOC_FREE(user_info_dc);
528 return NT_STATUS_NO_MEMORY;
531 info->logon_server = talloc_strdup(info, netbios_name);
532 if (info->logon_server == NULL) {
533 TALLOC_FREE(user_info_dc);
534 return NT_STATUS_NO_MEMORY;
537 info->last_logon = samdb_result_nttime(msg, "lastLogon", 0);
538 info->last_logoff = samdb_result_last_logoff(msg);
539 info->acct_expiry = samdb_result_account_expires(msg);
540 info->last_password_change = samdb_result_nttime(msg,
541 "pwdLastSet", 0);
542 info->allow_password_change
543 = samdb_result_allow_password_change(sam_ctx, mem_ctx,
544 domain_dn, msg, "pwdLastSet");
545 info->force_password_change = samdb_result_nttime(msg,
546 "msDS-UserPasswordExpiryTimeComputed", 0);
547 info->logon_count = ldb_msg_find_attr_as_uint(msg, "logonCount", 0);
548 info->bad_password_count = ldb_msg_find_attr_as_uint(msg, "badPwdCount",
551 info->acct_flags = samdb_result_acct_flags(msg, "msDS-User-Account-Control-Computed");
553 user_info_dc->user_session_key = data_blob_talloc(user_info_dc,
554 user_sess_key.data,
555 user_sess_key.length);
556 if (user_sess_key.data) {
557 if (user_info_dc->user_session_key.data == NULL) {
558 TALLOC_FREE(user_info_dc);
559 return NT_STATUS_NO_MEMORY;
562 user_info_dc->lm_session_key = data_blob_talloc(user_info_dc,
563 lm_sess_key.data,
564 lm_sess_key.length);
565 if (lm_sess_key.data) {
566 if (user_info_dc->lm_session_key.data == NULL) {
567 TALLOC_FREE(user_info_dc);
568 return NT_STATUS_NO_MEMORY;
572 if (info->acct_flags & ACB_SVRTRUST) {
573 /* the SID_NT_ENTERPRISE_DCS SID gets added into the
574 PAC */
575 user_info_dc->sids = talloc_realloc(user_info_dc,
576 user_info_dc->sids,
577 struct dom_sid,
578 user_info_dc->num_sids+1);
579 if (user_info_dc->sids == NULL) {
580 TALLOC_FREE(user_info_dc);
581 return NT_STATUS_NO_MEMORY;
583 user_info_dc->sids[user_info_dc->num_sids] = global_sid_Enterprise_DCs;
584 user_info_dc->num_sids++;
587 if ((info->acct_flags & (ACB_PARTIAL_SECRETS_ACCOUNT | ACB_WSTRUST)) ==
588 (ACB_PARTIAL_SECRETS_ACCOUNT | ACB_WSTRUST)) {
589 /* the DOMAIN_RID_ENTERPRISE_READONLY_DCS PAC */
590 user_info_dc->sids = talloc_realloc(user_info_dc,
591 user_info_dc->sids,
592 struct dom_sid,
593 user_info_dc->num_sids+1);
594 if (user_info_dc->sids == NULL) {
595 TALLOC_FREE(user_info_dc);
596 return NT_STATUS_NO_MEMORY;
598 user_info_dc->sids[user_info_dc->num_sids] = *domain_sid;
599 sid_append_rid(&user_info_dc->sids[user_info_dc->num_sids],
600 DOMAIN_RID_ENTERPRISE_READONLY_DCS);
601 user_info_dc->num_sids++;
604 info->authenticated = true;
606 talloc_free(tmp_ctx);
607 *_user_info_dc = user_info_dc;
609 return NT_STATUS_OK;
612 _PUBLIC_ NTSTATUS authsam_update_user_info_dc(TALLOC_CTX *mem_ctx,
613 struct ldb_context *sam_ctx,
614 struct auth_user_info_dc *user_info_dc)
616 char *filter = NULL;
617 NTSTATUS status;
618 uint32_t i;
619 uint32_t n = 0;
622 * This function exists to expand group memberships
623 * in the local domain (forest), as the token
624 * may come from a different domain.
628 * Filter out builtin groups from this token. We will search
629 * for builtin groups later.
631 status = authsam_domain_group_filter(mem_ctx, &filter);
632 if (!NT_STATUS_IS_OK(status)) {
633 TALLOC_FREE(user_info_dc);
634 return status;
638 * We loop only over the existing number of
639 * sids.
641 n = user_info_dc->num_sids;
642 for (i = 0; i < n; i++) {
643 struct dom_sid *sid = &user_info_dc->sids[i];
644 struct dom_sid_buf sid_buf;
645 char dn_str[sizeof(sid_buf.buf)*2];
646 DATA_BLOB dn_blob = data_blob_null;
648 snprintf(dn_str,
649 sizeof(dn_str),
650 "<SID=%s>",
651 dom_sid_str_buf(sid, &sid_buf));
652 dn_blob = data_blob_string_const(dn_str);
655 * We already have the SID in the token, so set
656 * 'only childs' flag to true and add all
657 * groups which match the filter.
659 status = dsdb_expand_nested_groups(sam_ctx, &dn_blob,
660 true, filter,
661 user_info_dc,
662 &user_info_dc->sids,
663 &user_info_dc->num_sids);
664 if (!NT_STATUS_IS_OK(status)) {
665 return status;
669 return NT_STATUS_OK;
672 NTSTATUS sam_get_results_principal(struct ldb_context *sam_ctx,
673 TALLOC_CTX *mem_ctx, const char *principal,
674 const char **attrs,
675 struct ldb_dn **domain_dn,
676 struct ldb_message **msg)
678 struct ldb_dn *user_dn;
679 NTSTATUS nt_status;
680 TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
681 int ret;
683 if (!tmp_ctx) {
684 return NT_STATUS_NO_MEMORY;
687 nt_status = crack_user_principal_name(sam_ctx, tmp_ctx, principal,
688 &user_dn, domain_dn);
689 if (!NT_STATUS_IS_OK(nt_status)) {
690 talloc_free(tmp_ctx);
691 return nt_status;
694 /* pull the user attributes */
695 ret = dsdb_search_one(sam_ctx, tmp_ctx, msg, user_dn,
696 LDB_SCOPE_BASE, attrs,
697 DSDB_SEARCH_SHOW_EXTENDED_DN | DSDB_SEARCH_NO_GLOBAL_CATALOG,
698 "(objectClass=*)");
699 if (ret != LDB_SUCCESS) {
700 talloc_free(tmp_ctx);
701 return NT_STATUS_INTERNAL_DB_CORRUPTION;
703 talloc_steal(mem_ctx, *msg);
704 talloc_steal(mem_ctx, *domain_dn);
705 talloc_free(tmp_ctx);
707 return NT_STATUS_OK;
710 /* Used in the gensec_gssapi and gensec_krb5 server-side code, where the PAC isn't available, and for tokenGroups in the DSDB stack.
712 Supply either a principal or a DN
714 NTSTATUS authsam_get_user_info_dc_principal(TALLOC_CTX *mem_ctx,
715 struct loadparm_context *lp_ctx,
716 struct ldb_context *sam_ctx,
717 const char *principal,
718 struct ldb_dn *user_dn,
719 struct auth_user_info_dc **user_info_dc)
721 NTSTATUS nt_status;
722 DATA_BLOB user_sess_key = data_blob(NULL, 0);
723 DATA_BLOB lm_sess_key = data_blob(NULL, 0);
725 struct ldb_message *msg;
726 struct ldb_dn *domain_dn;
728 TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
729 if (!tmp_ctx) {
730 return NT_STATUS_NO_MEMORY;
733 if (principal) {
734 nt_status = sam_get_results_principal(sam_ctx, tmp_ctx, principal,
735 user_attrs, &domain_dn, &msg);
736 if (!NT_STATUS_IS_OK(nt_status)) {
737 talloc_free(tmp_ctx);
738 return nt_status;
740 } else if (user_dn) {
741 struct dom_sid *user_sid, *domain_sid;
742 int ret;
743 /* pull the user attributes */
744 ret = dsdb_search_one(sam_ctx, tmp_ctx, &msg, user_dn,
745 LDB_SCOPE_BASE, user_attrs,
746 DSDB_SEARCH_SHOW_EXTENDED_DN | DSDB_SEARCH_NO_GLOBAL_CATALOG,
747 "(objectClass=*)");
748 if (ret == LDB_ERR_NO_SUCH_OBJECT) {
749 talloc_free(tmp_ctx);
750 return NT_STATUS_NO_SUCH_USER;
751 } else if (ret != LDB_SUCCESS) {
752 talloc_free(tmp_ctx);
753 return NT_STATUS_INTERNAL_DB_CORRUPTION;
756 user_sid = samdb_result_dom_sid(msg, msg, "objectSid");
758 nt_status = dom_sid_split_rid(tmp_ctx, user_sid, &domain_sid, NULL);
759 if (!NT_STATUS_IS_OK(nt_status)) {
760 return nt_status;
763 domain_dn = samdb_search_dn(sam_ctx, mem_ctx, NULL,
764 "(&(objectSid=%s)(objectClass=domain))",
765 ldap_encode_ndr_dom_sid(tmp_ctx, domain_sid));
766 if (!domain_dn) {
767 struct dom_sid_buf buf;
768 DEBUG(3, ("authsam_get_user_info_dc_principal: Failed to find domain with: SID %s\n",
769 dom_sid_str_buf(domain_sid, &buf)));
770 return NT_STATUS_NO_SUCH_USER;
773 } else {
774 return NT_STATUS_INVALID_PARAMETER;
777 nt_status = authsam_make_user_info_dc(tmp_ctx, sam_ctx,
778 lpcfg_netbios_name(lp_ctx),
779 lpcfg_sam_name(lp_ctx),
780 lpcfg_sam_dnsname(lp_ctx),
781 domain_dn,
782 msg,
783 user_sess_key, lm_sess_key,
784 user_info_dc);
785 if (!NT_STATUS_IS_OK(nt_status)) {
786 talloc_free(tmp_ctx);
787 return nt_status;
790 talloc_steal(mem_ctx, *user_info_dc);
791 talloc_free(tmp_ctx);
793 return NT_STATUS_OK;
797 * Returns the details for the Password Settings Object (PSO), if one applies
798 * the user.
800 static int authsam_get_user_pso(struct ldb_context *sam_ctx,
801 TALLOC_CTX *mem_ctx,
802 struct ldb_message *user_msg,
803 struct ldb_message **pso_msg)
805 const char *attrs[] = { "msDS-LockoutThreshold",
806 "msDS-LockoutObservationWindow",
807 NULL };
808 struct ldb_dn *pso_dn = NULL;
809 struct ldb_result *res = NULL;
810 int ret;
812 /* check if the user has a PSO that applies to it */
813 pso_dn = ldb_msg_find_attr_as_dn(sam_ctx, mem_ctx, user_msg,
814 "msDS-ResultantPSO");
816 if (pso_dn != NULL) {
817 ret = dsdb_search_dn(sam_ctx, mem_ctx, &res, pso_dn, attrs, 0);
818 if (ret != LDB_SUCCESS) {
819 return ret;
822 *pso_msg = res->msgs[0];
825 return LDB_SUCCESS;
828 NTSTATUS authsam_update_bad_pwd_count(struct ldb_context *sam_ctx,
829 struct ldb_message *msg,
830 struct ldb_dn *domain_dn)
832 const char *attrs[] = { "lockoutThreshold",
833 "lockOutObservationWindow",
834 "lockoutDuration",
835 "pwdProperties",
836 NULL };
837 int ret;
838 NTSTATUS status;
839 struct ldb_result *domain_res;
840 struct ldb_message *msg_mod = NULL;
841 struct ldb_message *pso_msg = NULL;
842 TALLOC_CTX *mem_ctx;
844 mem_ctx = talloc_new(msg);
845 if (mem_ctx == NULL) {
846 return NT_STATUS_NO_MEMORY;
849 ret = dsdb_search_dn(sam_ctx, mem_ctx, &domain_res, domain_dn, attrs, 0);
850 if (ret != LDB_SUCCESS) {
851 TALLOC_FREE(mem_ctx);
852 return NT_STATUS_INTERNAL_DB_CORRUPTION;
855 ret = authsam_get_user_pso(sam_ctx, mem_ctx, msg, &pso_msg);
856 if (ret != LDB_SUCCESS) {
859 * fallback to using the domain defaults so that we still
860 * record the bad password attempt
862 DBG_ERR("Error (%d) checking PSO for %s",
863 ret, ldb_dn_get_linearized(msg->dn));
866 status = dsdb_update_bad_pwd_count(mem_ctx, sam_ctx,
867 msg, domain_res->msgs[0], pso_msg,
868 &msg_mod);
869 if (!NT_STATUS_IS_OK(status)) {
870 TALLOC_FREE(mem_ctx);
871 return status;
874 if (msg_mod != NULL) {
875 struct ldb_request *req;
877 ret = ldb_build_mod_req(&req, sam_ctx, sam_ctx,
878 msg_mod,
879 NULL,
880 NULL,
881 ldb_op_default_callback,
882 NULL);
883 if (ret != LDB_SUCCESS) {
884 goto done;
887 ret = ldb_request_add_control(req,
888 DSDB_CONTROL_FORCE_RODC_LOCAL_CHANGE,
889 false, NULL);
890 if (ret != LDB_SUCCESS) {
891 talloc_free(req);
892 goto done;
895 ret = dsdb_autotransaction_request(sam_ctx, req);
896 talloc_free(req);
899 done:
900 if (ret != LDB_SUCCESS) {
901 DBG_ERR("Failed to update badPwdCount, badPasswordTime or "
902 "set lockoutTime on %s: %s\n",
903 ldb_dn_get_linearized(msg->dn),
904 ldb_errstring(sam_ctx));
905 TALLOC_FREE(mem_ctx);
906 return NT_STATUS_INTERNAL_ERROR;
909 TALLOC_FREE(mem_ctx);
910 return NT_STATUS_OK;
914 static NTSTATUS authsam_update_lastlogon_timestamp(struct ldb_context *sam_ctx,
915 struct ldb_message *msg_mod,
916 struct ldb_dn *domain_dn,
917 NTTIME old_timestamp,
918 NTTIME now)
921 * We only set lastLogonTimestamp if the current value is older than
922 * now - msDS-LogonTimeSyncInterval days.
924 * msDS-LogonTimeSyncInterval is an int32_t number of days, while
925 * lastLogonTimestamp is in the 64 bit 100ns NTTIME format.
927 * The docs say: "the initial update, after the domain functional
928 * level is raised to DS_BEHAVIOR_WIN2003 or higher, is calculated as
929 * 14 days minus a random percentage of 5 days", but we aren't doing
930 * that. The blogosphere seems to think that this randomised update
931 * happens everytime, but [MS-ADA1] doesn't agree.
933 * Dochelp referred us to the following blog post:
934 * http://blogs.technet.com/b/askds/archive/2009/04/15/the-lastlogontimestamp-attribute-what-it-was-designed-for-and-how-it-works.aspx
936 * en msDS-LogonTimeSyncInterval is zero, the lastLogonTimestamp is
937 * not changed.
939 static const char *attrs[] = { "msDS-LogonTimeSyncInterval",
940 NULL };
941 int ret;
942 struct ldb_result *domain_res = NULL;
943 TALLOC_CTX *mem_ctx = NULL;
944 int32_t sync_interval;
945 NTTIME sync_interval_nt;
947 mem_ctx = talloc_new(msg_mod);
948 if (mem_ctx == NULL) {
949 return NT_STATUS_NO_MEMORY;
952 ret = dsdb_search_dn(sam_ctx, mem_ctx, &domain_res, domain_dn, attrs,
954 if (ret != LDB_SUCCESS || domain_res->count != 1) {
955 TALLOC_FREE(mem_ctx);
956 return NT_STATUS_INTERNAL_DB_CORRUPTION;
959 sync_interval = ldb_msg_find_attr_as_int(domain_res->msgs[0],
960 "msDS-LogonTimeSyncInterval",
961 14);
962 DEBUG(5, ("sync interval is %d\n", sync_interval));
963 if (sync_interval == 0){
965 * Setting msDS-LogonTimeSyncInterval to zero is how you ask
966 * that nothing happens here.
968 TALLOC_FREE(mem_ctx);
969 return NT_STATUS_OK;
971 else if (sync_interval >= 5){
973 * Subtract "a random percentage of 5" days. Presumably this
974 * percentage is between 0 and 100, and modulus is accurate
975 * enough.
977 uint32_t r = generate_random() % 6;
978 sync_interval -= r;
979 DEBUG(5, ("randomised sync interval is %d (-%d)\n", sync_interval, r));
981 /* In the case where sync_interval < 5 there is no randomisation */
983 sync_interval_nt = sync_interval * 24LL * 3600LL * 10000000LL;
985 DEBUG(5, ("old timestamp is %lld, threshold %lld, diff %lld\n",
986 (long long int)old_timestamp,
987 (long long int)(now - sync_interval_nt),
988 (long long int)(old_timestamp - now + sync_interval_nt)));
990 if (old_timestamp > now){
991 DEBUG(0, ("lastLogonTimestamp is in the future! (%lld > %lld)\n",
992 (long long int)old_timestamp, (long long int)now));
993 /* then what? */
995 } else if (old_timestamp < now - sync_interval_nt){
996 DEBUG(5, ("updating lastLogonTimestamp to %lld\n",
997 (long long int)now));
999 /* The time has come to update lastLogonTimestamp */
1000 ret = samdb_msg_add_int64(sam_ctx, msg_mod, msg_mod,
1001 "lastLogonTimestamp", now);
1003 if (ret != LDB_SUCCESS) {
1004 TALLOC_FREE(mem_ctx);
1005 return NT_STATUS_NO_MEMORY;
1008 TALLOC_FREE(mem_ctx);
1009 return NT_STATUS_OK;
1012 /****************************************************************************
1013 Look for the specified user in the sam, return ldb result structures
1014 ****************************************************************************/
1016 NTSTATUS authsam_search_account(TALLOC_CTX *mem_ctx, struct ldb_context *sam_ctx,
1017 const char *account_name,
1018 struct ldb_dn *domain_dn,
1019 struct ldb_message **ret_msg)
1021 int ret;
1023 /* pull the user attributes */
1024 ret = dsdb_search_one(sam_ctx, mem_ctx, ret_msg, domain_dn, LDB_SCOPE_SUBTREE,
1025 user_attrs,
1026 DSDB_SEARCH_SHOW_EXTENDED_DN,
1027 "(&(sAMAccountName=%s)(objectclass=user))",
1028 ldb_binary_encode_string(mem_ctx, account_name));
1029 if (ret == LDB_ERR_NO_SUCH_OBJECT) {
1030 DEBUG(3,("sam_search_user: Couldn't find user [%s] in samdb, under %s\n",
1031 account_name, ldb_dn_get_linearized(domain_dn)));
1032 return NT_STATUS_NO_SUCH_USER;
1034 if (ret != LDB_SUCCESS) {
1035 return NT_STATUS_INTERNAL_DB_CORRUPTION;
1038 return NT_STATUS_OK;
1042 /* Reset the badPwdCount to zero and update the lastLogon time. */
1043 NTSTATUS authsam_logon_success_accounting(struct ldb_context *sam_ctx,
1044 const struct ldb_message *msg,
1045 struct ldb_dn *domain_dn,
1046 bool interactive_or_kerberos,
1047 struct netr_SendToSamBase **send_to_sam)
1049 int ret;
1050 NTSTATUS status;
1051 int badPwdCount;
1052 int dbBadPwdCount;
1053 int64_t lockoutTime;
1054 struct ldb_message *msg_mod;
1055 TALLOC_CTX *mem_ctx;
1056 struct timeval tv_now;
1057 NTTIME now;
1058 NTTIME lastLogonTimestamp;
1059 bool am_rodc = false;
1061 mem_ctx = talloc_new(msg);
1062 if (mem_ctx == NULL) {
1063 return NT_STATUS_NO_MEMORY;
1066 lockoutTime = ldb_msg_find_attr_as_int64(msg, "lockoutTime", 0);
1067 dbBadPwdCount = ldb_msg_find_attr_as_int(msg, "badPwdCount", 0);
1068 if (interactive_or_kerberos) {
1069 badPwdCount = dbBadPwdCount;
1070 } else {
1071 badPwdCount = samdb_result_effective_badPwdCount(sam_ctx, mem_ctx,
1072 domain_dn, msg);
1074 lastLogonTimestamp =
1075 ldb_msg_find_attr_as_int64(msg, "lastLogonTimestamp", 0);
1077 DEBUG(5, ("lastLogonTimestamp is %lld\n",
1078 (long long int)lastLogonTimestamp));
1080 msg_mod = ldb_msg_new(mem_ctx);
1081 if (msg_mod == NULL) {
1082 TALLOC_FREE(mem_ctx);
1083 return NT_STATUS_NO_MEMORY;
1085 msg_mod->dn = msg->dn;
1087 if (lockoutTime != 0) {
1089 * This implies "badPwdCount" = 0, see samldb_lockout_time()
1091 ret = samdb_msg_add_int(sam_ctx, msg_mod, msg_mod, "lockoutTime", 0);
1092 if (ret != LDB_SUCCESS) {
1093 TALLOC_FREE(mem_ctx);
1094 return NT_STATUS_NO_MEMORY;
1096 } else if (badPwdCount != 0) {
1097 ret = samdb_msg_add_int(sam_ctx, msg_mod, msg_mod, "badPwdCount", 0);
1098 if (ret != LDB_SUCCESS) {
1099 TALLOC_FREE(mem_ctx);
1100 return NT_STATUS_NO_MEMORY;
1104 tv_now = timeval_current();
1105 now = timeval_to_nttime(&tv_now);
1107 if (interactive_or_kerberos ||
1108 (badPwdCount != 0 && lockoutTime == 0)) {
1109 ret = samdb_msg_add_int64(sam_ctx, msg_mod, msg_mod,
1110 "lastLogon", now);
1111 if (ret != LDB_SUCCESS) {
1112 TALLOC_FREE(mem_ctx);
1113 return NT_STATUS_NO_MEMORY;
1117 if (interactive_or_kerberos) {
1118 int logonCount;
1120 logonCount = ldb_msg_find_attr_as_int(msg, "logonCount", 0);
1122 logonCount += 1;
1124 ret = samdb_msg_add_int(sam_ctx, msg_mod, msg_mod,
1125 "logonCount", logonCount);
1126 if (ret != LDB_SUCCESS) {
1127 TALLOC_FREE(mem_ctx);
1128 return NT_STATUS_NO_MEMORY;
1130 } else {
1131 /* Set an unset logonCount to 0 on first successful login */
1132 if (ldb_msg_find_ldb_val(msg, "logonCount") == NULL) {
1133 ret = samdb_msg_add_int(sam_ctx, msg_mod, msg_mod,
1134 "logonCount", 0);
1135 if (ret != LDB_SUCCESS) {
1136 TALLOC_FREE(mem_ctx);
1137 return NT_STATUS_NO_MEMORY;
1142 ret = samdb_rodc(sam_ctx, &am_rodc);
1143 if (ret != LDB_SUCCESS) {
1144 TALLOC_FREE(mem_ctx);
1145 return NT_STATUS_INTERNAL_ERROR;
1148 if (!am_rodc) {
1149 status = authsam_update_lastlogon_timestamp(sam_ctx, msg_mod, domain_dn,
1150 lastLogonTimestamp, now);
1151 if (!NT_STATUS_IS_OK(status)) {
1152 TALLOC_FREE(mem_ctx);
1153 return NT_STATUS_NO_MEMORY;
1155 } else {
1156 /* Perform the (async) SendToSAM calls for MS-SAMS */
1157 if (dbBadPwdCount != 0 && send_to_sam != NULL) {
1158 struct netr_SendToSamBase *base_msg;
1159 struct GUID guid = samdb_result_guid(msg, "objectGUID");
1160 base_msg = talloc_zero(msg, struct netr_SendToSamBase);
1162 base_msg->message_type = SendToSamResetBadPasswordCount;
1163 base_msg->message_size = 16;
1164 base_msg->message.reset_bad_password.guid = guid;
1165 *send_to_sam = base_msg;
1169 if (msg_mod->num_elements > 0) {
1170 unsigned int i;
1171 struct ldb_request *req;
1173 /* mark all the message elements as LDB_FLAG_MOD_REPLACE */
1174 for (i=0;i<msg_mod->num_elements;i++) {
1175 msg_mod->elements[i].flags = LDB_FLAG_MOD_REPLACE;
1178 ret = ldb_build_mod_req(&req, sam_ctx, sam_ctx,
1179 msg_mod,
1180 NULL,
1181 NULL,
1182 ldb_op_default_callback,
1183 NULL);
1184 if (ret != LDB_SUCCESS) {
1185 goto done;
1188 ret = ldb_request_add_control(req,
1189 DSDB_CONTROL_FORCE_RODC_LOCAL_CHANGE,
1190 false, NULL);
1191 if (ret != LDB_SUCCESS) {
1192 talloc_free(req);
1193 goto done;
1196 ret = dsdb_autotransaction_request(sam_ctx, req);
1197 talloc_free(req);
1200 done:
1201 if (ret != LDB_SUCCESS) {
1202 DEBUG(0, ("Failed to set badPwdCount and lockoutTime "
1203 "to 0 and/or lastlogon to now (%lld) "
1204 "%s: %s\n", (long long int)now,
1205 ldb_dn_get_linearized(msg_mod->dn),
1206 ldb_errstring(sam_ctx)));
1207 TALLOC_FREE(mem_ctx);
1208 return NT_STATUS_INTERNAL_ERROR;
1211 TALLOC_FREE(mem_ctx);
1212 return NT_STATUS_OK;