/src/samba/source4/auth/sam.c
Line | Count | Source |
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 |
8 | | |
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. |
13 | | |
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. |
18 | | |
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/>. |
21 | | */ |
22 | | |
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" |
34 | | #include "lib/dbwrap/dbwrap.h" |
35 | | #include "cluster/cluster.h" |
36 | | |
37 | | #undef DBGC_CLASS |
38 | 0 | #define DBGC_CLASS DBGC_AUTH |
39 | | |
40 | | #define KRBTGT_ATTRS \ |
41 | | /* required for the krb5 kdc */ \ |
42 | | "objectClass", \ |
43 | | "sAMAccountName", \ |
44 | | "userPrincipalName", \ |
45 | | "servicePrincipalName", \ |
46 | | "msDS-KeyVersionNumber", \ |
47 | | "msDS-SecondaryKrbTgtNumber", \ |
48 | | "msDS-SupportedEncryptionTypes", \ |
49 | | "supplementalCredentials", \ |
50 | | "msDS-AllowedToDelegateTo", \ |
51 | | "msDS-AllowedToActOnBehalfOfOtherIdentity", \ |
52 | | \ |
53 | | /* passwords */ \ |
54 | | "unicodePwd", \ |
55 | | \ |
56 | | "userAccountControl", \ |
57 | | "msDS-User-Account-Control-Computed", \ |
58 | | "objectSid", \ |
59 | | \ |
60 | | "pwdLastSet", \ |
61 | | "msDS-UserPasswordExpiryTimeComputed", \ |
62 | | "accountExpires", \ |
63 | | \ |
64 | | /* Needed for RODC rule processing */ \ |
65 | | "msDS-KrbTgtLinkBL", \ |
66 | | \ |
67 | | /* Required for Group Managed Service Accounts. */ \ |
68 | | "msDS-ManagedPasswordId", \ |
69 | | "msDS-ManagedPasswordInterval", \ |
70 | | "whenCreated", \ |
71 | | /* Required for Key Trust authentication */\ |
72 | | "msDS-KeyCredentialLink", \ |
73 | | /* Required for certificate mapping */ \ |
74 | | "altSecurityIdentities" |
75 | | |
76 | | #define AUTHN_POLICY_ATTRS \ |
77 | | /* Required for authentication policies / silos */ \ |
78 | | "msDS-AssignedAuthNPolicy", \ |
79 | | "msDS-AssignedAuthNPolicySilo" |
80 | | |
81 | | const char *krbtgt_attrs[] = { |
82 | | /* |
83 | | * Authentication policies will not be enforced on the TGS |
84 | | * account. Don’t include the relevant attributes in the account search. |
85 | | */ |
86 | | KRBTGT_ATTRS, NULL |
87 | | }; |
88 | | |
89 | | const char *server_attrs[] = { |
90 | | KRBTGT_ATTRS, |
91 | | AUTHN_POLICY_ATTRS, |
92 | | NULL |
93 | | }; |
94 | | |
95 | | const char *user_attrs[] = { |
96 | | /* |
97 | | * This ordering (having msDS-ResultantPSO first) is |
98 | | * important. By processing this attribute first it is |
99 | | * available in the operational module for the other PSO |
100 | | * attribute calculations to use. |
101 | | */ |
102 | | "msDS-ResultantPSO", |
103 | | |
104 | | KRBTGT_ATTRS, |
105 | | AUTHN_POLICY_ATTRS, |
106 | | |
107 | | "logonHours", |
108 | | |
109 | | /* |
110 | | * To allow us to zero the badPwdCount and lockoutTime on |
111 | | * successful logon, without database churn |
112 | | */ |
113 | | "lockoutTime", |
114 | | |
115 | | /* |
116 | | * Needed for SendToSAM requests |
117 | | */ |
118 | | "objectGUID", |
119 | | |
120 | | /* check 'allowed workstations' */ |
121 | | "userWorkstations", |
122 | | |
123 | | /* required for user_info_dc, not access control: */ |
124 | | "displayName", |
125 | | "scriptPath", |
126 | | "profilePath", |
127 | | "homeDirectory", |
128 | | "homeDrive", |
129 | | "lastLogon", |
130 | | "lastLogonTimestamp", |
131 | | "lastLogoff", |
132 | | "accountExpires", |
133 | | "badPwdCount", |
134 | | "logonCount", |
135 | | "primaryGroupID", |
136 | | "memberOf", |
137 | | "badPasswordTime", |
138 | | "lmPwdHistory", |
139 | | "ntPwdHistory", |
140 | | NULL, |
141 | | }; |
142 | | |
143 | | /**************************************************************************** |
144 | | Check if a user is allowed to logon at this time. Note this is the |
145 | | servers local time, as logon hours are just specified as a weekly |
146 | | bitmask. |
147 | | ****************************************************************************/ |
148 | | |
149 | | static bool logon_hours_ok(struct ldb_message *msg, const char *name_for_logs) |
150 | 0 | { |
151 | | /* In logon hours first bit is Sunday from 12AM to 1AM */ |
152 | 0 | const struct ldb_val *hours; |
153 | 0 | struct tm *utctime; |
154 | 0 | time_t lasttime; |
155 | 0 | const char *asct; |
156 | 0 | uint8_t bitmask, bitpos; |
157 | |
|
158 | 0 | hours = ldb_msg_find_ldb_val(msg, "logonHours"); |
159 | 0 | if (!hours) { |
160 | 0 | DEBUG(5,("logon_hours_ok: No hours restrictions for user %s\n", name_for_logs)); |
161 | 0 | return true; |
162 | 0 | } |
163 | | |
164 | 0 | if (hours->length != 168/8) { |
165 | 0 | DEBUG(5,("logon_hours_ok: malformed logon hours restrictions for user %s\n", name_for_logs)); |
166 | 0 | return true; |
167 | 0 | } |
168 | | |
169 | 0 | lasttime = time(NULL); |
170 | 0 | utctime = gmtime(&lasttime); |
171 | 0 | if (!utctime) { |
172 | 0 | DEBUG(1, ("logon_hours_ok: failed to get gmtime. Failing logon for user %s\n", |
173 | 0 | name_for_logs)); |
174 | 0 | return false; |
175 | 0 | } |
176 | | |
177 | | /* find the corresponding byte and bit */ |
178 | 0 | bitpos = (utctime->tm_wday * 24 + utctime->tm_hour) % 168; |
179 | 0 | bitmask = 1 << (bitpos % 8); |
180 | |
|
181 | 0 | if (! (hours->data[bitpos/8] & bitmask)) { |
182 | 0 | struct tm *t = localtime(&lasttime); |
183 | 0 | if (!t) { |
184 | 0 | asct = "INVALID TIME"; |
185 | 0 | } else { |
186 | 0 | asct = asctime(t); |
187 | 0 | if (!asct) { |
188 | 0 | asct = "INVALID TIME"; |
189 | 0 | } |
190 | 0 | } |
191 | |
|
192 | 0 | DEBUG(1, ("logon_hours_ok: Account for user %s not allowed to " |
193 | 0 | "logon at this time (%s).\n", |
194 | 0 | name_for_logs, asct )); |
195 | 0 | return false; |
196 | 0 | } |
197 | | |
198 | 0 | asct = asctime(utctime); |
199 | 0 | DEBUG(5,("logon_hours_ok: user %s allowed to logon at this time (%s)\n", |
200 | 0 | name_for_logs, asct ? asct : "UNKNOWN TIME" )); |
201 | |
|
202 | 0 | return true; |
203 | 0 | } |
204 | | |
205 | | /**************************************************************************** |
206 | | Do a specific test for a SAM_ACCOUNT being valid for this connection |
207 | | (ie not disabled, expired and the like). |
208 | | ****************************************************************************/ |
209 | | _PUBLIC_ NTSTATUS authsam_account_ok(TALLOC_CTX *mem_ctx, |
210 | | struct ldb_context *sam_ctx, |
211 | | NTTIME now, |
212 | | uint32_t logon_parameters, |
213 | | struct ldb_dn *domain_dn, |
214 | | struct ldb_message *msg, |
215 | | const char *logon_workstation, |
216 | | const char *name_for_logs, |
217 | | bool allow_domain_trust, |
218 | | bool password_change) |
219 | 0 | { |
220 | 0 | uint32_t acct_flags; |
221 | 0 | const char *workstation_list; |
222 | 0 | NTTIME acct_expiry; |
223 | 0 | NTTIME must_change_time; |
224 | |
|
225 | 0 | DEBUG(4,("authsam_account_ok: Checking SMB password for user %s\n", name_for_logs)); |
226 | |
|
227 | 0 | acct_flags = samdb_result_acct_flags(msg, "msDS-User-Account-Control-Computed"); |
228 | |
|
229 | 0 | acct_expiry = samdb_result_account_expires(msg); |
230 | | |
231 | | /* Check for when we must change this password, taking the |
232 | | * userAccountControl flags into account */ |
233 | 0 | must_change_time = samdb_result_nttime(msg, |
234 | 0 | "msDS-UserPasswordExpiryTimeComputed", 0); |
235 | |
|
236 | 0 | workstation_list = ldb_msg_find_attr_as_string(msg, "userWorkstations", NULL); |
237 | | |
238 | | /* Quit if the account was disabled. */ |
239 | 0 | if (acct_flags & ACB_DISABLED) { |
240 | 0 | DEBUG(2,("authsam_account_ok: Account for user '%s' was disabled.\n", name_for_logs)); |
241 | 0 | return NT_STATUS_ACCOUNT_DISABLED; |
242 | 0 | } |
243 | | |
244 | | /* Quit if the account was locked out. */ |
245 | 0 | if (acct_flags & ACB_AUTOLOCK) { |
246 | 0 | DEBUG(2,("authsam_account_ok: Account for user %s was locked out.\n", name_for_logs)); |
247 | 0 | return NT_STATUS_ACCOUNT_LOCKED_OUT; |
248 | 0 | } |
249 | | |
250 | | /* Test account expire time */ |
251 | 0 | if (now > acct_expiry) { |
252 | 0 | DEBUG(2,("authsam_account_ok: Account for user '%s' has expired.\n", name_for_logs)); |
253 | 0 | DEBUG(3,("authsam_account_ok: Account expired at '%s'.\n", |
254 | 0 | nt_time_string(mem_ctx, acct_expiry))); |
255 | 0 | return NT_STATUS_ACCOUNT_EXPIRED; |
256 | 0 | } |
257 | | |
258 | | /* check for immediate expiry "must change at next logon" (but not if this is a password change request) */ |
259 | 0 | if ((must_change_time == 0) && !password_change) { |
260 | 0 | DEBUG(2,("sam_account_ok: Account for user '%s' password must change!.\n", |
261 | 0 | name_for_logs)); |
262 | 0 | return NT_STATUS_PASSWORD_MUST_CHANGE; |
263 | 0 | } |
264 | | |
265 | | /* check for expired password (but not if this is a password change request) */ |
266 | 0 | if ((acct_flags & ACB_PW_EXPIRED) && !password_change) { |
267 | 0 | DEBUG(2,("sam_account_ok: Account for user '%s' password expired!.\n", |
268 | 0 | name_for_logs)); |
269 | 0 | DEBUG(2,("sam_account_ok: Password expired at '%s' unix time.\n", |
270 | 0 | nt_time_string(mem_ctx, must_change_time))); |
271 | 0 | return NT_STATUS_PASSWORD_EXPIRED; |
272 | 0 | } |
273 | | |
274 | | /* Test workstation. Workstation list is comma separated. */ |
275 | 0 | if (logon_workstation && workstation_list && *workstation_list) { |
276 | 0 | bool invalid_ws = true; |
277 | 0 | int i; |
278 | 0 | char **workstations = str_list_make(mem_ctx, workstation_list, ","); |
279 | |
|
280 | 0 | for (i = 0; workstations && workstations[i]; i++) { |
281 | 0 | DEBUG(10,("sam_account_ok: checking for workstation match '%s' and '%s'\n", |
282 | 0 | workstations[i], logon_workstation)); |
283 | |
|
284 | 0 | if (strequal(workstations[i], logon_workstation)) { |
285 | 0 | invalid_ws = false; |
286 | 0 | break; |
287 | 0 | } |
288 | 0 | } |
289 | |
|
290 | 0 | talloc_free(workstations); |
291 | |
|
292 | 0 | if (invalid_ws) { |
293 | 0 | return NT_STATUS_INVALID_WORKSTATION; |
294 | 0 | } |
295 | 0 | } |
296 | | |
297 | 0 | if (!logon_hours_ok(msg, name_for_logs)) { |
298 | 0 | return NT_STATUS_INVALID_LOGON_HOURS; |
299 | 0 | } |
300 | | |
301 | 0 | if (!allow_domain_trust) { |
302 | 0 | if (acct_flags & ACB_DOMTRUST) { |
303 | 0 | DEBUG(2,("sam_account_ok: Domain trust account %s denied by server\n", name_for_logs)); |
304 | 0 | return NT_STATUS_NOLOGON_INTERDOMAIN_TRUST_ACCOUNT; |
305 | 0 | } |
306 | 0 | } |
307 | 0 | if (!(logon_parameters & MSV1_0_ALLOW_SERVER_TRUST_ACCOUNT)) { |
308 | 0 | if (acct_flags & ACB_SVRTRUST) { |
309 | 0 | DEBUG(2,("sam_account_ok: Server trust account %s denied by server\n", name_for_logs)); |
310 | 0 | return NT_STATUS_NOLOGON_SERVER_TRUST_ACCOUNT; |
311 | 0 | } |
312 | 0 | } |
313 | 0 | if (!(logon_parameters & MSV1_0_ALLOW_WORKSTATION_TRUST_ACCOUNT)) { |
314 | | /* TODO: this fails with current solaris client. We |
315 | | need to work with Gordon to work out why */ |
316 | 0 | if (acct_flags & ACB_WSTRUST) { |
317 | 0 | DEBUG(4,("sam_account_ok: Wksta trust account %s denied by server\n", name_for_logs)); |
318 | 0 | return NT_STATUS_NOLOGON_WORKSTATION_TRUST_ACCOUNT; |
319 | 0 | } |
320 | 0 | } |
321 | | |
322 | 0 | return NT_STATUS_OK; |
323 | 0 | } |
324 | | |
325 | | static NTSTATUS authsam_domain_group_filter(TALLOC_CTX *mem_ctx, |
326 | | char **_filter) |
327 | 0 | { |
328 | 0 | char *filter = NULL; |
329 | |
|
330 | 0 | *_filter = NULL; |
331 | |
|
332 | 0 | filter = talloc_strdup(mem_ctx, "(&(objectClass=group)"); |
333 | 0 | if (filter == NULL) { |
334 | 0 | return NT_STATUS_NO_MEMORY; |
335 | 0 | } |
336 | | |
337 | | /* |
338 | | * Skip all builtin groups, they're added later. |
339 | | */ |
340 | 0 | talloc_asprintf_addbuf(&filter, |
341 | 0 | "(!(groupType:"LDB_OID_COMPARATOR_AND":=%u))", |
342 | 0 | GROUP_TYPE_BUILTIN_LOCAL_GROUP); |
343 | 0 | if (filter == NULL) { |
344 | 0 | return NT_STATUS_NO_MEMORY; |
345 | 0 | } |
346 | | /* |
347 | | * Only include security groups. |
348 | | */ |
349 | 0 | talloc_asprintf_addbuf(&filter, |
350 | 0 | "(groupType:"LDB_OID_COMPARATOR_AND":=%u))", |
351 | 0 | GROUP_TYPE_SECURITY_ENABLED); |
352 | 0 | if (filter == NULL) { |
353 | 0 | return NT_STATUS_NO_MEMORY; |
354 | 0 | } |
355 | | |
356 | 0 | *_filter = filter; |
357 | 0 | return NT_STATUS_OK; |
358 | 0 | } |
359 | | |
360 | | _PUBLIC_ NTSTATUS authsam_make_user_info_dc(TALLOC_CTX *mem_ctx, |
361 | | struct ldb_context *sam_ctx, |
362 | | const char *netbios_name, |
363 | | const char *domain_name, |
364 | | const char *dns_domain_name, |
365 | | struct ldb_dn *domain_dn, |
366 | | const struct ldb_message *msg, |
367 | | DATA_BLOB user_sess_key, |
368 | | DATA_BLOB lm_sess_key, |
369 | | struct auth_user_info_dc **_user_info_dc) |
370 | 0 | { |
371 | 0 | NTSTATUS status; |
372 | 0 | int ret; |
373 | 0 | struct auth_user_info_dc *user_info_dc; |
374 | 0 | struct auth_user_info *info; |
375 | 0 | const char *str = NULL; |
376 | 0 | char *filter = NULL; |
377 | | /* SIDs for the account and his primary group */ |
378 | 0 | struct dom_sid *account_sid; |
379 | 0 | struct dom_sid_buf buf; |
380 | 0 | const char *primary_group_dn_str = NULL; |
381 | 0 | DATA_BLOB primary_group_blob; |
382 | 0 | struct ldb_dn *primary_group_dn = NULL; |
383 | 0 | struct ldb_message *primary_group_msg = NULL; |
384 | 0 | unsigned primary_group_type; |
385 | | /* SID structures for the expanded group memberships */ |
386 | 0 | struct auth_SidAttr *sids = NULL; |
387 | 0 | uint32_t num_sids = 0; |
388 | 0 | unsigned int i; |
389 | 0 | struct dom_sid *domain_sid; |
390 | 0 | uint32_t group_rid; |
391 | 0 | struct dom_sid groupsid = {}; |
392 | 0 | TALLOC_CTX *tmp_ctx; |
393 | 0 | struct ldb_message_element *el; |
394 | 0 | static const char * const group_type_attrs[] = { "groupType", NULL }; |
395 | |
|
396 | 0 | if (msg == NULL) { |
397 | 0 | return NT_STATUS_INVALID_PARAMETER; |
398 | 0 | } |
399 | | |
400 | 0 | user_info_dc = talloc_zero(mem_ctx, struct auth_user_info_dc); |
401 | 0 | NT_STATUS_HAVE_NO_MEMORY(user_info_dc); |
402 | | |
403 | 0 | tmp_ctx = talloc_new(user_info_dc); |
404 | 0 | if (tmp_ctx == NULL) { |
405 | 0 | TALLOC_FREE(user_info_dc); |
406 | 0 | return NT_STATUS_NO_MEMORY; |
407 | 0 | } |
408 | | |
409 | | /* |
410 | | * We'll typically store three SIDs: the SID of the user, the SID of the |
411 | | * primary group, and a copy of the latter if it's not a resource |
412 | | * group. Allocate enough memory for these three SIDs. |
413 | | */ |
414 | 0 | sids = talloc_zero_array(user_info_dc, struct auth_SidAttr, 3); |
415 | 0 | if (sids == NULL) { |
416 | 0 | TALLOC_FREE(user_info_dc); |
417 | 0 | return NT_STATUS_NO_MEMORY; |
418 | 0 | } |
419 | | |
420 | 0 | num_sids = 2; |
421 | |
|
422 | 0 | account_sid = samdb_result_dom_sid(tmp_ctx, msg, "objectSid"); |
423 | 0 | if (account_sid == NULL) { |
424 | 0 | TALLOC_FREE(user_info_dc); |
425 | 0 | return NT_STATUS_NO_MEMORY; |
426 | 0 | } |
427 | | |
428 | 0 | status = dom_sid_split_rid(tmp_ctx, account_sid, &domain_sid, NULL); |
429 | 0 | if (!NT_STATUS_IS_OK(status)) { |
430 | 0 | talloc_free(user_info_dc); |
431 | 0 | return status; |
432 | 0 | } |
433 | | |
434 | 0 | group_rid = ldb_msg_find_attr_as_uint(msg, "primaryGroupID", ~0); |
435 | 0 | groupsid = *domain_sid; |
436 | 0 | sid_append_rid(&groupsid, group_rid); |
437 | |
|
438 | 0 | sids[PRIMARY_USER_SID_INDEX] = (struct auth_SidAttr) { |
439 | 0 | .sid = *account_sid, |
440 | 0 | .attrs = SE_GROUP_DEFAULT_FLAGS, |
441 | 0 | }; |
442 | |
|
443 | 0 | sids[PRIMARY_GROUP_SID_INDEX] = (struct auth_SidAttr) { |
444 | 0 | .sid = groupsid, |
445 | 0 | .attrs = SE_GROUP_DEFAULT_FLAGS, |
446 | 0 | }; |
447 | | |
448 | | /* |
449 | | * Filter out builtin groups from this token. We will search |
450 | | * for builtin groups later, and not include them in the PAC |
451 | | * or SamLogon validation info. |
452 | | */ |
453 | 0 | status = authsam_domain_group_filter(tmp_ctx, &filter); |
454 | 0 | if (!NT_STATUS_IS_OK(status)) { |
455 | 0 | TALLOC_FREE(user_info_dc); |
456 | 0 | return status; |
457 | 0 | } |
458 | | |
459 | 0 | primary_group_dn_str = talloc_asprintf( |
460 | 0 | tmp_ctx, |
461 | 0 | "<SID=%s>", |
462 | 0 | dom_sid_str_buf(&sids[PRIMARY_GROUP_SID_INDEX].sid, &buf)); |
463 | 0 | if (primary_group_dn_str == NULL) { |
464 | 0 | TALLOC_FREE(user_info_dc); |
465 | 0 | return NT_STATUS_NO_MEMORY; |
466 | 0 | } |
467 | | |
468 | | /* Get the DN of the primary group. */ |
469 | 0 | primary_group_dn = ldb_dn_new(tmp_ctx, sam_ctx, primary_group_dn_str); |
470 | 0 | if (primary_group_dn == NULL) { |
471 | 0 | TALLOC_FREE(user_info_dc); |
472 | 0 | return NT_STATUS_NO_MEMORY; |
473 | 0 | } |
474 | | |
475 | | /* |
476 | | * Do a search for the primary group, for the purpose of checking |
477 | | * whether it's a resource group. |
478 | | */ |
479 | 0 | ret = dsdb_search_one(sam_ctx, tmp_ctx, |
480 | 0 | &primary_group_msg, |
481 | 0 | primary_group_dn, |
482 | 0 | LDB_SCOPE_BASE, |
483 | 0 | group_type_attrs, |
484 | 0 | 0, |
485 | 0 | NULL); |
486 | 0 | if (ret != LDB_SUCCESS) { |
487 | 0 | talloc_free(user_info_dc); |
488 | 0 | return NT_STATUS_INTERNAL_DB_CORRUPTION; |
489 | 0 | } |
490 | | |
491 | | /* Check the type of the primary group. */ |
492 | 0 | primary_group_type = ldb_msg_find_attr_as_uint(primary_group_msg, "groupType", 0); |
493 | 0 | if (primary_group_type & GROUP_TYPE_RESOURCE_GROUP) { |
494 | | /* |
495 | | * If it's a resource group, we might as well indicate that in |
496 | | * its attributes. At any rate, the primary group's attributes |
497 | | * are unlikely to be used in the code, as there's nowhere to |
498 | | * store them. |
499 | | */ |
500 | 0 | sids[PRIMARY_GROUP_SID_INDEX].attrs |= SE_GROUP_RESOURCE; |
501 | 0 | } else { |
502 | | /* |
503 | | * The primary group is not a resource group. Make a copy of its |
504 | | * SID to ensure it is added to the Base SIDs in the PAC. |
505 | | */ |
506 | 0 | sids[REMAINING_SIDS_INDEX] = sids[PRIMARY_GROUP_SID_INDEX]; |
507 | 0 | ++num_sids; |
508 | 0 | } |
509 | |
|
510 | 0 | primary_group_blob = data_blob_string_const(primary_group_dn_str); |
511 | | |
512 | | /* Expands the primary group - this function takes in |
513 | | * memberOf-like values, so we fake one up with the |
514 | | * <SID=S-...> format of DN and then let it expand |
515 | | * them, as long as they meet the filter - so only |
516 | | * domain groups, not builtin groups |
517 | | * |
518 | | * The primary group is still treated specially, so we set the |
519 | | * 'only childs' flag to true |
520 | | */ |
521 | 0 | status = dsdb_expand_nested_groups(sam_ctx, &primary_group_blob, true, filter, |
522 | 0 | user_info_dc, &sids, &num_sids); |
523 | 0 | if (!NT_STATUS_IS_OK(status)) { |
524 | 0 | talloc_free(user_info_dc); |
525 | 0 | return status; |
526 | 0 | } |
527 | | |
528 | | /* Expands the additional groups */ |
529 | 0 | el = ldb_msg_find_element(msg, "memberOf"); |
530 | 0 | for (i = 0; el && i < el->num_values; i++) { |
531 | | /* This function takes in memberOf values and expands |
532 | | * them, as long as they meet the filter - so only |
533 | | * domain groups, not builtin groups */ |
534 | 0 | status = dsdb_expand_nested_groups(sam_ctx, &el->values[i], false, filter, |
535 | 0 | user_info_dc, &sids, &num_sids); |
536 | 0 | if (!NT_STATUS_IS_OK(status)) { |
537 | 0 | talloc_free(user_info_dc); |
538 | 0 | return status; |
539 | 0 | } |
540 | 0 | } |
541 | | |
542 | 0 | user_info_dc->sids = sids; |
543 | 0 | user_info_dc->num_sids = num_sids; |
544 | |
|
545 | 0 | user_info_dc->info = info = talloc_zero(user_info_dc, struct auth_user_info); |
546 | 0 | if (user_info_dc->info == NULL) { |
547 | 0 | talloc_free(user_info_dc); |
548 | 0 | return NT_STATUS_NO_MEMORY; |
549 | 0 | } |
550 | | |
551 | 0 | str = ldb_msg_find_attr_as_string(msg, "sAMAccountName", NULL); |
552 | 0 | info->account_name = talloc_strdup(info, str); |
553 | 0 | if (info->account_name == NULL) { |
554 | 0 | TALLOC_FREE(user_info_dc); |
555 | 0 | return NT_STATUS_NO_MEMORY; |
556 | 0 | } |
557 | | |
558 | 0 | str = ldb_msg_find_attr_as_string(msg, "userPrincipalName", NULL); |
559 | 0 | if (str == NULL && dns_domain_name != NULL) { |
560 | 0 | info->user_principal_name = talloc_asprintf(info, "%s@%s", |
561 | 0 | info->account_name, |
562 | 0 | dns_domain_name); |
563 | 0 | if (info->user_principal_name == NULL) { |
564 | 0 | TALLOC_FREE(user_info_dc); |
565 | 0 | return NT_STATUS_NO_MEMORY; |
566 | 0 | } |
567 | 0 | info->user_principal_constructed = true; |
568 | 0 | } else if (str != NULL) { |
569 | 0 | info->user_principal_name = talloc_strdup(info, str); |
570 | 0 | if (info->user_principal_name == NULL) { |
571 | 0 | TALLOC_FREE(user_info_dc); |
572 | 0 | return NT_STATUS_NO_MEMORY; |
573 | 0 | } |
574 | 0 | } |
575 | | |
576 | 0 | info->domain_name = talloc_strdup(info, domain_name); |
577 | 0 | if (info->domain_name == NULL) { |
578 | 0 | TALLOC_FREE(user_info_dc); |
579 | 0 | return NT_STATUS_NO_MEMORY; |
580 | 0 | } |
581 | | |
582 | 0 | if (dns_domain_name != NULL) { |
583 | 0 | info->dns_domain_name = talloc_strdup(info, dns_domain_name); |
584 | 0 | if (info->dns_domain_name == NULL) { |
585 | 0 | TALLOC_FREE(user_info_dc); |
586 | 0 | return NT_STATUS_NO_MEMORY; |
587 | 0 | } |
588 | 0 | } |
589 | | |
590 | 0 | str = ldb_msg_find_attr_as_string(msg, "displayName", ""); |
591 | 0 | info->full_name = talloc_strdup(info, str); |
592 | 0 | if (info->full_name == NULL) { |
593 | 0 | TALLOC_FREE(user_info_dc); |
594 | 0 | return NT_STATUS_NO_MEMORY; |
595 | 0 | } |
596 | | |
597 | 0 | str = ldb_msg_find_attr_as_string(msg, "scriptPath", ""); |
598 | 0 | info->logon_script = talloc_strdup(info, str); |
599 | 0 | if (info->logon_script == NULL) { |
600 | 0 | TALLOC_FREE(user_info_dc); |
601 | 0 | return NT_STATUS_NO_MEMORY; |
602 | 0 | } |
603 | | |
604 | 0 | str = ldb_msg_find_attr_as_string(msg, "profilePath", ""); |
605 | 0 | info->profile_path = talloc_strdup(info, str); |
606 | 0 | if (info->profile_path == NULL) { |
607 | 0 | TALLOC_FREE(user_info_dc); |
608 | 0 | return NT_STATUS_NO_MEMORY; |
609 | 0 | } |
610 | | |
611 | 0 | str = ldb_msg_find_attr_as_string(msg, "homeDirectory", ""); |
612 | 0 | info->home_directory = talloc_strdup(info, str); |
613 | 0 | if (info->home_directory == NULL) { |
614 | 0 | TALLOC_FREE(user_info_dc); |
615 | 0 | return NT_STATUS_NO_MEMORY; |
616 | 0 | } |
617 | | |
618 | 0 | str = ldb_msg_find_attr_as_string(msg, "homeDrive", ""); |
619 | 0 | info->home_drive = talloc_strdup(info, str); |
620 | 0 | if (info->home_drive == NULL) { |
621 | 0 | TALLOC_FREE(user_info_dc); |
622 | 0 | return NT_STATUS_NO_MEMORY; |
623 | 0 | } |
624 | | |
625 | 0 | info->logon_server = talloc_strdup(info, netbios_name); |
626 | 0 | if (info->logon_server == NULL) { |
627 | 0 | TALLOC_FREE(user_info_dc); |
628 | 0 | return NT_STATUS_NO_MEMORY; |
629 | 0 | } |
630 | | |
631 | 0 | info->last_logon = samdb_result_nttime(msg, "lastLogon", 0); |
632 | 0 | info->last_logoff = samdb_result_last_logoff(msg); |
633 | 0 | info->acct_expiry = samdb_result_account_expires(msg); |
634 | 0 | info->last_password_change = samdb_result_nttime(msg, |
635 | 0 | "pwdLastSet", 0); |
636 | 0 | info->allow_password_change |
637 | 0 | = samdb_result_allow_password_change(sam_ctx, mem_ctx, |
638 | 0 | domain_dn, msg, "pwdLastSet"); |
639 | 0 | info->force_password_change = samdb_result_nttime(msg, |
640 | 0 | "msDS-UserPasswordExpiryTimeComputed", 0); |
641 | 0 | info->logon_count = ldb_msg_find_attr_as_uint(msg, "logonCount", 0); |
642 | 0 | info->bad_password_count = ldb_msg_find_attr_as_uint(msg, "badPwdCount", |
643 | 0 | 0); |
644 | |
|
645 | 0 | info->acct_flags = samdb_result_acct_flags(msg, "msDS-User-Account-Control-Computed"); |
646 | |
|
647 | 0 | user_info_dc->user_session_key = data_blob_talloc(user_info_dc, |
648 | 0 | user_sess_key.data, |
649 | 0 | user_sess_key.length); |
650 | 0 | if (user_sess_key.data) { |
651 | 0 | if (user_info_dc->user_session_key.data == NULL) { |
652 | 0 | TALLOC_FREE(user_info_dc); |
653 | 0 | return NT_STATUS_NO_MEMORY; |
654 | 0 | } |
655 | 0 | } |
656 | 0 | user_info_dc->lm_session_key = data_blob_talloc(user_info_dc, |
657 | 0 | lm_sess_key.data, |
658 | 0 | lm_sess_key.length); |
659 | 0 | if (lm_sess_key.data) { |
660 | 0 | if (user_info_dc->lm_session_key.data == NULL) { |
661 | 0 | TALLOC_FREE(user_info_dc); |
662 | 0 | return NT_STATUS_NO_MEMORY; |
663 | 0 | } |
664 | 0 | } |
665 | | |
666 | 0 | if (info->acct_flags & ACB_SVRTRUST) { |
667 | | /* the SID_NT_ENTERPRISE_DCS SID gets added into the |
668 | | PAC */ |
669 | 0 | user_info_dc->sids = talloc_realloc(user_info_dc, |
670 | 0 | user_info_dc->sids, |
671 | 0 | struct auth_SidAttr, |
672 | 0 | user_info_dc->num_sids+1); |
673 | 0 | if (user_info_dc->sids == NULL) { |
674 | 0 | TALLOC_FREE(user_info_dc); |
675 | 0 | return NT_STATUS_NO_MEMORY; |
676 | 0 | } |
677 | | |
678 | 0 | user_info_dc->sids[user_info_dc->num_sids] = (struct auth_SidAttr) { |
679 | 0 | .sid = global_sid_Enterprise_DCs, |
680 | 0 | .attrs = SE_GROUP_DEFAULT_FLAGS, |
681 | 0 | }; |
682 | 0 | user_info_dc->num_sids++; |
683 | 0 | } |
684 | | |
685 | 0 | if ((info->acct_flags & (ACB_PARTIAL_SECRETS_ACCOUNT | ACB_WSTRUST)) == |
686 | 0 | (ACB_PARTIAL_SECRETS_ACCOUNT | ACB_WSTRUST)) { |
687 | 0 | struct dom_sid rodcsid = {}; |
688 | | |
689 | | /* the DOMAIN_RID_ENTERPRISE_READONLY_DCS PAC */ |
690 | 0 | user_info_dc->sids = talloc_realloc(user_info_dc, |
691 | 0 | user_info_dc->sids, |
692 | 0 | struct auth_SidAttr, |
693 | 0 | user_info_dc->num_sids+1); |
694 | 0 | if (user_info_dc->sids == NULL) { |
695 | 0 | TALLOC_FREE(user_info_dc); |
696 | 0 | return NT_STATUS_NO_MEMORY; |
697 | 0 | } |
698 | | |
699 | 0 | rodcsid = *domain_sid; |
700 | 0 | sid_append_rid(&rodcsid, DOMAIN_RID_ENTERPRISE_READONLY_DCS); |
701 | |
|
702 | 0 | user_info_dc->sids[user_info_dc->num_sids] = (struct auth_SidAttr) { |
703 | 0 | .sid = rodcsid, |
704 | 0 | .attrs = SE_GROUP_DEFAULT_FLAGS, |
705 | 0 | }; |
706 | 0 | user_info_dc->num_sids++; |
707 | 0 | } |
708 | | |
709 | 0 | info->user_flags = 0; |
710 | |
|
711 | 0 | talloc_free(tmp_ctx); |
712 | 0 | *_user_info_dc = user_info_dc; |
713 | |
|
714 | 0 | return NT_STATUS_OK; |
715 | 0 | } |
716 | | |
717 | | _PUBLIC_ NTSTATUS authsam_update_user_info_dc(TALLOC_CTX *mem_ctx, |
718 | | struct ldb_context *sam_ctx, |
719 | | struct auth_user_info_dc *user_info_dc) |
720 | 0 | { |
721 | 0 | char *filter = NULL; |
722 | 0 | NTSTATUS status; |
723 | 0 | uint32_t i; |
724 | 0 | uint32_t n = 0; |
725 | | |
726 | | /* |
727 | | * This function exists to expand group memberships |
728 | | * in the local domain (forest), as the token |
729 | | * may come from a different domain. |
730 | | */ |
731 | | |
732 | | /* |
733 | | * Filter out builtin groups from this token. We will search |
734 | | * for builtin groups later. |
735 | | */ |
736 | 0 | status = authsam_domain_group_filter(mem_ctx, &filter); |
737 | 0 | if (!NT_STATUS_IS_OK(status)) { |
738 | 0 | return status; |
739 | 0 | } |
740 | | |
741 | | /* |
742 | | * We loop only over the existing number of |
743 | | * sids. |
744 | | */ |
745 | 0 | n = user_info_dc->num_sids; |
746 | 0 | for (i = 0; i < n; i++) { |
747 | 0 | struct dom_sid *sid = &user_info_dc->sids[i].sid; |
748 | 0 | struct dom_sid_buf sid_buf; |
749 | 0 | char dn_str[sizeof(sid_buf.buf)*2]; |
750 | 0 | DATA_BLOB dn_blob = data_blob_null; |
751 | |
|
752 | 0 | snprintf(dn_str, |
753 | 0 | sizeof(dn_str), |
754 | 0 | "<SID=%s>", |
755 | 0 | dom_sid_str_buf(sid, &sid_buf)); |
756 | 0 | dn_blob = data_blob_string_const(dn_str); |
757 | | |
758 | | /* |
759 | | * We already have the SID in the token, so set |
760 | | * 'only childs' flag to true and add all |
761 | | * groups which match the filter. |
762 | | */ |
763 | 0 | status = dsdb_expand_nested_groups(sam_ctx, &dn_blob, |
764 | 0 | true, filter, |
765 | 0 | user_info_dc, |
766 | 0 | &user_info_dc->sids, |
767 | 0 | &user_info_dc->num_sids); |
768 | 0 | if (!NT_STATUS_IS_OK(status)) { |
769 | 0 | talloc_free(filter); |
770 | 0 | return status; |
771 | 0 | } |
772 | 0 | } |
773 | | |
774 | 0 | talloc_free(filter); |
775 | 0 | return NT_STATUS_OK; |
776 | 0 | } |
777 | | |
778 | | /* |
779 | | * Make a shallow copy of a talloc-allocated user_info_dc structure, holding a |
780 | | * reference to each of the original fields. |
781 | | */ |
782 | | NTSTATUS authsam_shallow_copy_user_info_dc(TALLOC_CTX *mem_ctx, |
783 | | const struct auth_user_info_dc *user_info_dc_in, |
784 | | struct auth_user_info_dc **user_info_dc_out) |
785 | 0 | { |
786 | 0 | struct auth_user_info_dc *user_info_dc = NULL; |
787 | 0 | NTSTATUS status = NT_STATUS_OK; |
788 | |
|
789 | 0 | if (user_info_dc_in == NULL) { |
790 | 0 | return NT_STATUS_INVALID_PARAMETER; |
791 | 0 | } |
792 | | |
793 | 0 | if (user_info_dc_out == NULL) { |
794 | 0 | return NT_STATUS_INVALID_PARAMETER; |
795 | 0 | } |
796 | | |
797 | 0 | user_info_dc = talloc_zero(mem_ctx, struct auth_user_info_dc); |
798 | 0 | if (user_info_dc == NULL) { |
799 | 0 | status = NT_STATUS_NO_MEMORY; |
800 | 0 | goto out; |
801 | 0 | } |
802 | | |
803 | 0 | *user_info_dc = *user_info_dc_in; |
804 | |
|
805 | 0 | if (user_info_dc->info != NULL) { |
806 | 0 | if (talloc_reference(user_info_dc, user_info_dc->info) == NULL) { |
807 | 0 | status = NT_STATUS_NO_MEMORY; |
808 | 0 | goto out; |
809 | 0 | } |
810 | 0 | } |
811 | | |
812 | 0 | if (user_info_dc->user_session_key.data != NULL) { |
813 | 0 | if (talloc_reference(user_info_dc, user_info_dc->user_session_key.data) == NULL) { |
814 | 0 | status = NT_STATUS_NO_MEMORY; |
815 | 0 | goto out; |
816 | 0 | } |
817 | 0 | } |
818 | | |
819 | 0 | if (user_info_dc->lm_session_key.data != NULL) { |
820 | 0 | if (talloc_reference(user_info_dc, user_info_dc->lm_session_key.data) == NULL) { |
821 | 0 | status = NT_STATUS_NO_MEMORY; |
822 | 0 | goto out; |
823 | 0 | } |
824 | 0 | } |
825 | | |
826 | 0 | if (user_info_dc->sids != NULL) { |
827 | | /* |
828 | | * Because we want to modify the SIDs in the user_info_dc |
829 | | * structure, adding various well-known SIDs such as Asserted |
830 | | * Identity or Claims Valid, make a copy of the SID array to |
831 | | * guard against modification of the original. |
832 | | * |
833 | | * It’s better not to make a reference, because anything that |
834 | | * tries to call talloc_realloc() on the original or the copy |
835 | | * will fail when called for any referenced talloc context. |
836 | | */ |
837 | 0 | user_info_dc->sids = talloc_memdup(user_info_dc, |
838 | 0 | user_info_dc->sids, |
839 | 0 | talloc_get_size(user_info_dc->sids)); |
840 | 0 | if (user_info_dc->sids == NULL) { |
841 | 0 | status = NT_STATUS_NO_MEMORY; |
842 | 0 | goto out; |
843 | 0 | } |
844 | 0 | } |
845 | | |
846 | 0 | *user_info_dc_out = user_info_dc; |
847 | 0 | user_info_dc = NULL; |
848 | |
|
849 | 0 | out: |
850 | 0 | talloc_free(user_info_dc); |
851 | 0 | return status; |
852 | 0 | } |
853 | | |
854 | | NTSTATUS sam_get_results_principal(struct ldb_context *sam_ctx, |
855 | | TALLOC_CTX *mem_ctx, const char *principal, |
856 | | const char **attrs, |
857 | | const uint32_t dsdb_flags, |
858 | | struct ldb_dn **domain_dn, |
859 | | struct ldb_message **msg) |
860 | 0 | { |
861 | 0 | struct ldb_dn *user_dn; |
862 | 0 | NTSTATUS nt_status; |
863 | 0 | TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx); |
864 | 0 | int ret; |
865 | |
|
866 | 0 | if (!tmp_ctx) { |
867 | 0 | return NT_STATUS_NO_MEMORY; |
868 | 0 | } |
869 | | |
870 | 0 | nt_status = crack_user_principal_name(sam_ctx, tmp_ctx, principal, |
871 | 0 | &user_dn, domain_dn); |
872 | 0 | if (!NT_STATUS_IS_OK(nt_status)) { |
873 | 0 | talloc_free(tmp_ctx); |
874 | 0 | return nt_status; |
875 | 0 | } |
876 | | |
877 | | /* pull the user attributes */ |
878 | 0 | ret = dsdb_search_one(sam_ctx, tmp_ctx, msg, user_dn, |
879 | 0 | LDB_SCOPE_BASE, attrs, |
880 | 0 | dsdb_flags | DSDB_SEARCH_SHOW_EXTENDED_DN | DSDB_SEARCH_NO_GLOBAL_CATALOG, |
881 | 0 | "(objectClass=*)"); |
882 | 0 | if (ret != LDB_SUCCESS) { |
883 | 0 | talloc_free(tmp_ctx); |
884 | 0 | return NT_STATUS_INTERNAL_DB_CORRUPTION; |
885 | 0 | } |
886 | 0 | talloc_steal(mem_ctx, *msg); |
887 | 0 | talloc_steal(mem_ctx, *domain_dn); |
888 | 0 | talloc_free(tmp_ctx); |
889 | |
|
890 | 0 | return NT_STATUS_OK; |
891 | 0 | } |
892 | | |
893 | | /* Used in the gensec_gssapi and gensec_krb5 server-side code, where the PAC isn't available, and for tokenGroups in the DSDB stack. |
894 | | |
895 | | Supply either a principal or a DN |
896 | | */ |
897 | | NTSTATUS authsam_get_user_info_dc_principal(TALLOC_CTX *mem_ctx, |
898 | | struct loadparm_context *lp_ctx, |
899 | | struct ldb_context *sam_ctx, |
900 | | const char *principal, |
901 | | struct ldb_dn *user_dn, |
902 | | struct auth_user_info_dc **user_info_dc) |
903 | 0 | { |
904 | 0 | NTSTATUS nt_status; |
905 | 0 | DATA_BLOB user_sess_key = data_blob(NULL, 0); |
906 | 0 | DATA_BLOB lm_sess_key = data_blob(NULL, 0); |
907 | |
|
908 | 0 | struct ldb_message *msg; |
909 | 0 | struct ldb_dn *domain_dn; |
910 | |
|
911 | 0 | TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx); |
912 | 0 | if (!tmp_ctx) { |
913 | 0 | return NT_STATUS_NO_MEMORY; |
914 | 0 | } |
915 | | |
916 | 0 | if (principal) { |
917 | 0 | nt_status = sam_get_results_principal(sam_ctx, tmp_ctx, principal, |
918 | 0 | user_attrs, DSDB_SEARCH_UPDATE_MANAGED_PASSWORDS, &domain_dn, &msg); |
919 | 0 | if (!NT_STATUS_IS_OK(nt_status)) { |
920 | 0 | talloc_free(tmp_ctx); |
921 | 0 | return nt_status; |
922 | 0 | } |
923 | 0 | } else if (user_dn) { |
924 | 0 | struct dom_sid *user_sid, *domain_sid; |
925 | 0 | int ret; |
926 | | /* pull the user attributes */ |
927 | 0 | ret = dsdb_search_one(sam_ctx, tmp_ctx, &msg, user_dn, |
928 | 0 | LDB_SCOPE_BASE, user_attrs, |
929 | 0 | DSDB_SEARCH_SHOW_EXTENDED_DN | DSDB_SEARCH_NO_GLOBAL_CATALOG | DSDB_SEARCH_UPDATE_MANAGED_PASSWORDS, |
930 | 0 | "(objectClass=*)"); |
931 | 0 | if (ret == LDB_ERR_NO_SUCH_OBJECT) { |
932 | 0 | talloc_free(tmp_ctx); |
933 | 0 | return NT_STATUS_NO_SUCH_USER; |
934 | 0 | } else if (ret != LDB_SUCCESS) { |
935 | 0 | talloc_free(tmp_ctx); |
936 | 0 | return NT_STATUS_INTERNAL_DB_CORRUPTION; |
937 | 0 | } |
938 | | |
939 | 0 | user_sid = samdb_result_dom_sid(msg, msg, "objectSid"); |
940 | |
|
941 | 0 | nt_status = dom_sid_split_rid(tmp_ctx, user_sid, &domain_sid, NULL); |
942 | 0 | if (!NT_STATUS_IS_OK(nt_status)) { |
943 | 0 | talloc_free(tmp_ctx); |
944 | 0 | return nt_status; |
945 | 0 | } |
946 | | |
947 | 0 | domain_dn = samdb_search_dn(sam_ctx, mem_ctx, NULL, |
948 | 0 | "(&(objectSid=%s)(objectClass=domain))", |
949 | 0 | ldap_encode_ndr_dom_sid(tmp_ctx, domain_sid)); |
950 | 0 | if (!domain_dn) { |
951 | 0 | struct dom_sid_buf buf; |
952 | 0 | DEBUG(3, ("authsam_get_user_info_dc_principal: Failed to find domain with: SID %s\n", |
953 | 0 | dom_sid_str_buf(domain_sid, &buf))); |
954 | 0 | talloc_free(tmp_ctx); |
955 | 0 | return NT_STATUS_NO_SUCH_USER; |
956 | 0 | } |
957 | |
|
958 | 0 | } else { |
959 | 0 | talloc_free(tmp_ctx); |
960 | 0 | return NT_STATUS_INVALID_PARAMETER; |
961 | 0 | } |
962 | | |
963 | 0 | nt_status = authsam_make_user_info_dc(tmp_ctx, sam_ctx, |
964 | 0 | lpcfg_netbios_name(lp_ctx), |
965 | 0 | lpcfg_sam_name(lp_ctx), |
966 | 0 | lpcfg_sam_dnsname(lp_ctx), |
967 | 0 | domain_dn, |
968 | 0 | msg, |
969 | 0 | user_sess_key, lm_sess_key, |
970 | 0 | user_info_dc); |
971 | 0 | if (!NT_STATUS_IS_OK(nt_status)) { |
972 | 0 | talloc_free(tmp_ctx); |
973 | 0 | return nt_status; |
974 | 0 | } |
975 | | |
976 | 0 | talloc_steal(mem_ctx, *user_info_dc); |
977 | 0 | talloc_free(tmp_ctx); |
978 | |
|
979 | 0 | return NT_STATUS_OK; |
980 | 0 | } |
981 | | |
982 | | /* |
983 | | * Returns the details for the Password Settings Object (PSO), if one applies |
984 | | * the user. |
985 | | */ |
986 | | static int authsam_get_user_pso(struct ldb_context *sam_ctx, |
987 | | TALLOC_CTX *mem_ctx, |
988 | | struct ldb_message *user_msg, |
989 | | struct ldb_message **pso_msg) |
990 | 0 | { |
991 | 0 | const char *attrs[] = { "msDS-LockoutThreshold", |
992 | 0 | "msDS-LockoutObservationWindow", |
993 | 0 | NULL }; |
994 | 0 | struct ldb_dn *pso_dn = NULL; |
995 | 0 | struct ldb_result *res = NULL; |
996 | 0 | int ret; |
997 | | |
998 | | /* check if the user has a PSO that applies to it */ |
999 | 0 | pso_dn = ldb_msg_find_attr_as_dn(sam_ctx, mem_ctx, user_msg, |
1000 | 0 | "msDS-ResultantPSO"); |
1001 | |
|
1002 | 0 | if (pso_dn != NULL) { |
1003 | 0 | ret = dsdb_search_dn(sam_ctx, mem_ctx, &res, pso_dn, attrs, 0); |
1004 | 0 | if (ret != LDB_SUCCESS) { |
1005 | 0 | return ret; |
1006 | 0 | } |
1007 | | |
1008 | 0 | *pso_msg = res->msgs[0]; |
1009 | 0 | } |
1010 | | |
1011 | 0 | return LDB_SUCCESS; |
1012 | 0 | } |
1013 | | |
1014 | | /* |
1015 | | * Re-read the bad password and successful logon data for a user. |
1016 | | * |
1017 | | * The DN in the passed user record should contain the "objectGUID" in case the |
1018 | | * object DN has changed. |
1019 | | */ |
1020 | | NTSTATUS authsam_reread_user_logon_data( |
1021 | | struct ldb_context *sam_ctx, |
1022 | | TALLOC_CTX *mem_ctx, |
1023 | | const struct ldb_message *user_msg, |
1024 | | struct ldb_message **current) |
1025 | 0 | { |
1026 | 0 | TALLOC_CTX *tmp_ctx = NULL; |
1027 | 0 | const struct ldb_val *v = NULL; |
1028 | 0 | struct ldb_result *res = NULL; |
1029 | 0 | uint32_t acct_flags = 0; |
1030 | 0 | const char *attr_name = "msDS-User-Account-Control-Computed"; |
1031 | 0 | NTSTATUS status = NT_STATUS_OK; |
1032 | 0 | int ret; |
1033 | |
|
1034 | 0 | tmp_ctx = talloc_new(mem_ctx); |
1035 | 0 | if (tmp_ctx == NULL) { |
1036 | 0 | status = NT_STATUS_NO_MEMORY; |
1037 | 0 | goto out; |
1038 | 0 | } |
1039 | | |
1040 | | /* |
1041 | | * Re-read the account details, using the GUID in case the DN |
1042 | | * is being changed (this is automatic in LDB because the |
1043 | | * original search also used DSDB_SEARCH_SHOW_EXTENDED_DN) |
1044 | | * |
1045 | | * We re read all the attributes in user_attrs, rather than using a |
1046 | | * subset to ensure that we can reuse existing validation code. |
1047 | | */ |
1048 | 0 | ret = dsdb_search_dn(sam_ctx, |
1049 | 0 | tmp_ctx, |
1050 | 0 | &res, |
1051 | 0 | user_msg->dn, |
1052 | 0 | user_attrs, |
1053 | 0 | DSDB_SEARCH_SHOW_EXTENDED_DN | DSDB_SEARCH_UPDATE_MANAGED_PASSWORDS); |
1054 | 0 | if (ret != LDB_SUCCESS) { |
1055 | 0 | DBG_ERR("Unable to re-read account control data for %s\n", |
1056 | 0 | ldb_dn_get_linearized(user_msg->dn)); |
1057 | 0 | status = NT_STATUS_INTERNAL_ERROR; |
1058 | 0 | goto out; |
1059 | 0 | } |
1060 | | |
1061 | | /* |
1062 | | * Ensure the account has not been locked out by another request |
1063 | | */ |
1064 | 0 | v = ldb_msg_find_ldb_val(res->msgs[0], attr_name); |
1065 | 0 | if (v == NULL || v->data == NULL) { |
1066 | 0 | DBG_ERR("No %s attribute for %s\n", |
1067 | 0 | attr_name, |
1068 | 0 | ldb_dn_get_linearized(user_msg->dn)); |
1069 | 0 | status = NT_STATUS_INTERNAL_ERROR; |
1070 | 0 | goto out; |
1071 | 0 | } |
1072 | 0 | acct_flags = samdb_result_acct_flags(res->msgs[0], attr_name); |
1073 | 0 | if (acct_flags & ACB_AUTOLOCK) { |
1074 | 0 | DBG_WARNING( |
1075 | 0 | "Account for user %s was locked out.\n", |
1076 | 0 | ldb_dn_get_linearized(user_msg->dn)); |
1077 | 0 | status = NT_STATUS_ACCOUNT_LOCKED_OUT; |
1078 | 0 | goto out; |
1079 | 0 | } |
1080 | 0 | *current = talloc_steal(mem_ctx, res->msgs[0]); |
1081 | 0 | out: |
1082 | 0 | TALLOC_FREE(tmp_ctx); |
1083 | 0 | return status; |
1084 | 0 | } |
1085 | | |
1086 | | static struct db_context *authsam_get_bad_password_db( |
1087 | | TALLOC_CTX *mem_ctx, |
1088 | | struct ldb_context *sam_ctx) |
1089 | 0 | { |
1090 | 0 | struct loadparm_context *lp_ctx = NULL; |
1091 | 0 | const char *db_name = "bad_password"; |
1092 | 0 | struct db_context *db_ctx = NULL; |
1093 | |
|
1094 | 0 | lp_ctx = ldb_get_opaque(sam_ctx, "loadparm"); |
1095 | 0 | if (lp_ctx == NULL) { |
1096 | 0 | DBG_ERR("Unable to get loadparm_context\n"); |
1097 | 0 | return NULL; |
1098 | 0 | } |
1099 | | |
1100 | 0 | db_ctx = cluster_db_tmp_open(mem_ctx, lp_ctx, db_name, TDB_DEFAULT); |
1101 | 0 | if (db_ctx == NULL) { |
1102 | 0 | DBG_ERR("Unable to open bad password attempts database\n"); |
1103 | 0 | return NULL; |
1104 | 0 | } |
1105 | 0 | return db_ctx; |
1106 | 0 | } |
1107 | | |
1108 | | static NTSTATUS get_object_sid_as_tdb_data( |
1109 | | TALLOC_CTX *mem_ctx, |
1110 | | const struct ldb_message *msg, |
1111 | | struct dom_sid_buf *buf, |
1112 | | TDB_DATA *key) |
1113 | 0 | { |
1114 | 0 | struct dom_sid *objectsid = NULL; |
1115 | | |
1116 | | /* |
1117 | | * Convert the objectSID to a human readable form to |
1118 | | * make debugging easier |
1119 | | */ |
1120 | 0 | objectsid = samdb_result_dom_sid(mem_ctx, msg, "objectSID"); |
1121 | 0 | if (objectsid == NULL) { |
1122 | 0 | DBG_ERR("Unable to extract objectSID\n"); |
1123 | 0 | return NT_STATUS_INTERNAL_ERROR; |
1124 | 0 | } |
1125 | 0 | dom_sid_str_buf(objectsid, buf); |
1126 | 0 | key->dptr = (unsigned char *)buf->buf; |
1127 | 0 | key->dsize = strlen(buf->buf); |
1128 | |
|
1129 | 0 | talloc_free(objectsid); |
1130 | |
|
1131 | 0 | return NT_STATUS_OK; |
1132 | 0 | } |
1133 | | |
1134 | | /* |
1135 | | * Add the users objectSID to the bad password attempt database |
1136 | | * to indicate that last authentication failed due to a bad password |
1137 | | */ |
1138 | | static NTSTATUS authsam_set_bad_password_indicator( |
1139 | | struct ldb_context *sam_ctx, |
1140 | | TALLOC_CTX *mem_ctx, |
1141 | | const struct ldb_message *msg) |
1142 | 0 | { |
1143 | 0 | NTSTATUS status = NT_STATUS_OK; |
1144 | 0 | struct dom_sid_buf buf; |
1145 | 0 | TDB_DATA key = {0}; |
1146 | 0 | TDB_DATA value = {0}; |
1147 | 0 | struct db_context *db = NULL; |
1148 | |
|
1149 | 0 | TALLOC_CTX *ctx = talloc_new(mem_ctx); |
1150 | 0 | if (ctx == NULL) { |
1151 | 0 | return NT_STATUS_NO_MEMORY; |
1152 | 0 | } |
1153 | | |
1154 | 0 | db = authsam_get_bad_password_db(ctx, sam_ctx); |
1155 | 0 | if (db == NULL) { |
1156 | 0 | status = NT_STATUS_INTERNAL_ERROR; |
1157 | 0 | goto exit; |
1158 | 0 | } |
1159 | | |
1160 | 0 | status = get_object_sid_as_tdb_data(ctx, msg, &buf, &key); |
1161 | 0 | if (!NT_STATUS_IS_OK(status)) { |
1162 | 0 | goto exit; |
1163 | 0 | } |
1164 | | |
1165 | 0 | status = dbwrap_store(db, key, value, 0); |
1166 | 0 | if (!NT_STATUS_IS_OK(status)) { |
1167 | 0 | DBG_ERR("Unable to store bad password indicator\n"); |
1168 | 0 | } |
1169 | 0 | exit: |
1170 | 0 | talloc_free(ctx); |
1171 | 0 | return status; |
1172 | 0 | } |
1173 | | |
1174 | | /* |
1175 | | * see if the users objectSID is in the bad password attempt database |
1176 | | */ |
1177 | | static NTSTATUS authsam_check_bad_password_indicator( |
1178 | | struct ldb_context *sam_ctx, |
1179 | | TALLOC_CTX *mem_ctx, |
1180 | | bool *exists, |
1181 | | const struct ldb_message *msg) |
1182 | 0 | { |
1183 | 0 | NTSTATUS status = NT_STATUS_OK; |
1184 | 0 | struct dom_sid_buf buf; |
1185 | 0 | TDB_DATA key = {0}; |
1186 | 0 | struct db_context *db = NULL; |
1187 | |
|
1188 | 0 | TALLOC_CTX *ctx = talloc_new(mem_ctx); |
1189 | 0 | if (ctx == NULL) { |
1190 | 0 | return NT_STATUS_NO_MEMORY; |
1191 | 0 | } |
1192 | | |
1193 | 0 | db = authsam_get_bad_password_db(ctx, sam_ctx); |
1194 | 0 | if (db == NULL) { |
1195 | 0 | status = NT_STATUS_INTERNAL_ERROR; |
1196 | 0 | goto exit; |
1197 | 0 | } |
1198 | | |
1199 | 0 | status = get_object_sid_as_tdb_data(ctx, msg, &buf, &key); |
1200 | 0 | if (!NT_STATUS_IS_OK(status)) { |
1201 | 0 | goto exit; |
1202 | 0 | } |
1203 | | |
1204 | 0 | *exists = dbwrap_exists(db, key); |
1205 | 0 | exit: |
1206 | 0 | talloc_free(ctx); |
1207 | 0 | return status; |
1208 | 0 | } |
1209 | | |
1210 | | /* |
1211 | | * Remove the users objectSID to the bad password attempt database |
1212 | | * to indicate that last authentication succeeded. |
1213 | | */ |
1214 | | static NTSTATUS authsam_clear_bad_password_indicator( |
1215 | | struct ldb_context *sam_ctx, |
1216 | | TALLOC_CTX *mem_ctx, |
1217 | | const struct ldb_message *msg) |
1218 | 0 | { |
1219 | 0 | NTSTATUS status = NT_STATUS_OK; |
1220 | 0 | struct dom_sid_buf buf; |
1221 | 0 | TDB_DATA key = {0}; |
1222 | 0 | struct db_context *db = NULL; |
1223 | |
|
1224 | 0 | TALLOC_CTX *ctx = talloc_new(mem_ctx); |
1225 | 0 | if (ctx == NULL) { |
1226 | 0 | return NT_STATUS_NO_MEMORY; |
1227 | 0 | } |
1228 | | |
1229 | 0 | db = authsam_get_bad_password_db(ctx, sam_ctx); |
1230 | 0 | if (db == NULL) { |
1231 | 0 | status = NT_STATUS_INTERNAL_ERROR; |
1232 | 0 | goto exit; |
1233 | 0 | } |
1234 | | |
1235 | 0 | status = get_object_sid_as_tdb_data(ctx, msg, &buf, &key); |
1236 | 0 | if (!NT_STATUS_IS_OK(status)) { |
1237 | 0 | goto exit; |
1238 | 0 | } |
1239 | | |
1240 | 0 | status = dbwrap_delete(db, key); |
1241 | 0 | if (NT_STATUS_EQUAL(NT_STATUS_NOT_FOUND, status)) { |
1242 | | /* |
1243 | | * Ok there was no bad password indicator this is expected |
1244 | | */ |
1245 | 0 | status = NT_STATUS_OK; |
1246 | 0 | } |
1247 | 0 | if (NT_STATUS_IS_ERR(status)) { |
1248 | 0 | DBG_ERR("Unable to delete bad password indicator, %s %s\n", |
1249 | 0 | nt_errstr(status), |
1250 | 0 | get_friendly_nt_error_msg(status)); |
1251 | 0 | } |
1252 | 0 | exit: |
1253 | 0 | talloc_free(ctx); |
1254 | 0 | return status; |
1255 | 0 | } |
1256 | | |
1257 | | NTSTATUS authsam_update_bad_pwd_count(struct ldb_context *sam_ctx, |
1258 | | struct ldb_message *msg, |
1259 | | struct ldb_dn *domain_dn) |
1260 | 0 | { |
1261 | 0 | const char *attrs[] = { "lockoutThreshold", |
1262 | 0 | "lockOutObservationWindow", |
1263 | 0 | "lockoutDuration", |
1264 | 0 | "pwdProperties", |
1265 | 0 | NULL }; |
1266 | 0 | int ret; |
1267 | 0 | NTSTATUS status; |
1268 | 0 | struct ldb_result *domain_res; |
1269 | 0 | struct ldb_message *msg_mod = NULL; |
1270 | 0 | struct ldb_message *current = NULL; |
1271 | 0 | struct ldb_message *pso_msg = NULL; |
1272 | 0 | bool txn_active = false; |
1273 | 0 | TALLOC_CTX *mem_ctx; |
1274 | |
|
1275 | 0 | mem_ctx = talloc_new(msg); |
1276 | 0 | if (mem_ctx == NULL) { |
1277 | 0 | return NT_STATUS_NO_MEMORY; |
1278 | 0 | } |
1279 | | |
1280 | 0 | ret = dsdb_search_dn(sam_ctx, mem_ctx, &domain_res, domain_dn, attrs, 0); |
1281 | 0 | if (ret != LDB_SUCCESS) { |
1282 | 0 | TALLOC_FREE(mem_ctx); |
1283 | 0 | return NT_STATUS_INTERNAL_DB_CORRUPTION; |
1284 | 0 | } |
1285 | | |
1286 | 0 | ret = authsam_get_user_pso(sam_ctx, mem_ctx, msg, &pso_msg); |
1287 | 0 | if (ret != LDB_SUCCESS) { |
1288 | | |
1289 | | /* |
1290 | | * fallback to using the domain defaults so that we still |
1291 | | * record the bad password attempt |
1292 | | */ |
1293 | 0 | DBG_ERR("Error (%d) checking PSO for %s\n", |
1294 | 0 | ret, ldb_dn_get_linearized(msg->dn)); |
1295 | 0 | } |
1296 | | |
1297 | | /* |
1298 | | * To ensure that the bad password count is updated atomically, |
1299 | | * we need to: |
1300 | | * begin a transaction |
1301 | | * re-read the account details, |
1302 | | * using the <GUID= part of the DN |
1303 | | * update the bad password count |
1304 | | * commit the transaction. |
1305 | | */ |
1306 | | |
1307 | | /* |
1308 | | * Start a new transaction |
1309 | | */ |
1310 | 0 | ret = ldb_transaction_start(sam_ctx); |
1311 | 0 | if (ret != LDB_SUCCESS) { |
1312 | 0 | status = NT_STATUS_INTERNAL_ERROR; |
1313 | 0 | goto error; |
1314 | 0 | } |
1315 | 0 | txn_active = true; |
1316 | | |
1317 | | /* |
1318 | | * Re-read the account details, using the GUID in case the DN |
1319 | | * is being changed. |
1320 | | */ |
1321 | 0 | status = authsam_reread_user_logon_data( |
1322 | 0 | sam_ctx, mem_ctx, msg, ¤t); |
1323 | 0 | if (!NT_STATUS_IS_OK(status)) { |
1324 | | /* The re-read can return account locked out, as well |
1325 | | * as an internal error |
1326 | | */ |
1327 | 0 | if (NT_STATUS_EQUAL(status, NT_STATUS_ACCOUNT_LOCKED_OUT)) { |
1328 | | /* |
1329 | | * For NT_STATUS_ACCOUNT_LOCKED_OUT we want to commit |
1330 | | * the transaction. Again to avoid cluttering the |
1331 | | * audit logs with spurious errors |
1332 | | */ |
1333 | 0 | goto exit; |
1334 | 0 | } |
1335 | 0 | goto error; |
1336 | 0 | } |
1337 | | |
1338 | | /* |
1339 | | * Update the bad password count and if required lock the account |
1340 | | */ |
1341 | 0 | status = dsdb_update_bad_pwd_count( |
1342 | 0 | mem_ctx, |
1343 | 0 | sam_ctx, |
1344 | 0 | current, |
1345 | 0 | domain_res->msgs[0], |
1346 | 0 | pso_msg, |
1347 | 0 | &msg_mod); |
1348 | 0 | if (!NT_STATUS_IS_OK(status)) { |
1349 | 0 | status = NT_STATUS_INTERNAL_ERROR; |
1350 | 0 | goto error; |
1351 | 0 | } |
1352 | | |
1353 | | /* |
1354 | | * Write the data back to disk if required. |
1355 | | */ |
1356 | 0 | if (msg_mod != NULL) { |
1357 | 0 | struct ldb_request *req; |
1358 | |
|
1359 | 0 | ret = ldb_build_mod_req(&req, sam_ctx, sam_ctx, |
1360 | 0 | msg_mod, |
1361 | 0 | NULL, |
1362 | 0 | NULL, |
1363 | 0 | ldb_op_default_callback, |
1364 | 0 | NULL); |
1365 | 0 | if (ret != LDB_SUCCESS) { |
1366 | 0 | TALLOC_FREE(msg_mod); |
1367 | 0 | status = NT_STATUS_INTERNAL_ERROR; |
1368 | 0 | goto error; |
1369 | 0 | } |
1370 | | |
1371 | 0 | ret = ldb_request_add_control(req, |
1372 | 0 | DSDB_CONTROL_FORCE_RODC_LOCAL_CHANGE, |
1373 | 0 | false, NULL); |
1374 | 0 | if (ret != LDB_SUCCESS) { |
1375 | 0 | talloc_free(req); |
1376 | 0 | status = NT_STATUS_INTERNAL_ERROR; |
1377 | 0 | goto error; |
1378 | 0 | } |
1379 | | |
1380 | | /* |
1381 | | * As we're in a transaction, make the ldb request directly |
1382 | | * to avoid the nested transaction that would result if we |
1383 | | * called dsdb_autotransaction_request |
1384 | | */ |
1385 | 0 | ret = ldb_request(sam_ctx, req); |
1386 | 0 | if (ret == LDB_SUCCESS) { |
1387 | 0 | ret = ldb_wait(req->handle, LDB_WAIT_ALL); |
1388 | 0 | } |
1389 | 0 | talloc_free(req); |
1390 | 0 | if (ret != LDB_SUCCESS) { |
1391 | 0 | status = NT_STATUS_INTERNAL_ERROR; |
1392 | 0 | goto error; |
1393 | 0 | } |
1394 | 0 | status = authsam_set_bad_password_indicator( |
1395 | 0 | sam_ctx, mem_ctx, msg); |
1396 | 0 | if (!NT_STATUS_IS_OK(status)) { |
1397 | 0 | goto error; |
1398 | 0 | } |
1399 | 0 | } |
1400 | | /* |
1401 | | * Note that we may not have updated the user record, but |
1402 | | * committing the transaction in that case is still the correct |
1403 | | * thing to do. |
1404 | | * If the transaction was cancelled, this would be logged by |
1405 | | * the dsdb audit log as a failure. When in fact it is expected |
1406 | | * behaviour. |
1407 | | */ |
1408 | 0 | exit: |
1409 | 0 | TALLOC_FREE(mem_ctx); |
1410 | 0 | ret = ldb_transaction_commit(sam_ctx); |
1411 | 0 | if (ret != LDB_SUCCESS) { |
1412 | 0 | DBG_ERR("Error (%d) %s, committing transaction," |
1413 | 0 | " while updating bad password count" |
1414 | 0 | " for (%s)\n", |
1415 | 0 | ret, |
1416 | 0 | ldb_errstring(sam_ctx), |
1417 | 0 | ldb_dn_get_linearized(msg->dn)); |
1418 | 0 | return NT_STATUS_INTERNAL_ERROR; |
1419 | 0 | } |
1420 | 0 | return status; |
1421 | | |
1422 | 0 | error: |
1423 | 0 | DBG_ERR("Failed to update badPwdCount, badPasswordTime or " |
1424 | 0 | "set lockoutTime on %s: %s\n", |
1425 | 0 | ldb_dn_get_linearized(msg->dn), |
1426 | 0 | ldb_errstring(sam_ctx) != NULL ? |
1427 | 0 | ldb_errstring(sam_ctx) :nt_errstr(status)); |
1428 | 0 | if (txn_active) { |
1429 | 0 | ret = ldb_transaction_cancel(sam_ctx); |
1430 | 0 | if (ret != LDB_SUCCESS) { |
1431 | 0 | DBG_ERR("Error rolling back transaction," |
1432 | 0 | " while updating bad password count" |
1433 | 0 | " on %s: %s\n", |
1434 | 0 | ldb_dn_get_linearized(msg->dn), |
1435 | 0 | ldb_errstring(sam_ctx)); |
1436 | 0 | } |
1437 | 0 | } |
1438 | 0 | TALLOC_FREE(mem_ctx); |
1439 | 0 | return status; |
1440 | |
|
1441 | 0 | } |
1442 | | |
1443 | | /* |
1444 | | * msDS-LogonTimeSyncInterval is an int32_t number of days. |
1445 | | * |
1446 | | * The docs say: "the initial update, after the domain functional |
1447 | | * level is raised to DS_BEHAVIOR_WIN2003 or higher, is calculated as |
1448 | | * 14 days minus a random percentage of 5 days", but we aren't doing |
1449 | | * that. The blogosphere seems to think that this randomised update |
1450 | | * happens every time, but [MS-ADA1] doesn't agree. |
1451 | | * |
1452 | | * Dochelp referred us to the following blog post: |
1453 | | * http://blogs.technet.com/b/askds/archive/2009/04/15/the-lastlogontimestamp-attribute-what-it-was-designed-for-and-how-it-works.aspx |
1454 | | * |
1455 | | * when msDS-LogonTimeSyncInterval is zero, the lastLogonTimestamp is |
1456 | | * not changed. |
1457 | | */ |
1458 | | |
1459 | | static NTSTATUS authsam_calculate_lastlogon_sync_interval( |
1460 | | struct ldb_context *sam_ctx, |
1461 | | TALLOC_CTX *ctx, |
1462 | | struct ldb_dn *domain_dn, |
1463 | | NTTIME *sync_interval_nt) |
1464 | 0 | { |
1465 | 0 | static const char *attrs[] = { "msDS-LogonTimeSyncInterval", |
1466 | 0 | NULL }; |
1467 | 0 | int ret; |
1468 | 0 | struct ldb_result *domain_res = NULL; |
1469 | 0 | TALLOC_CTX *mem_ctx = NULL; |
1470 | 0 | uint32_t sync_interval; |
1471 | |
|
1472 | 0 | mem_ctx = talloc_new(ctx); |
1473 | 0 | if (mem_ctx == NULL) { |
1474 | 0 | return NT_STATUS_NO_MEMORY; |
1475 | 0 | } |
1476 | | |
1477 | 0 | ret = dsdb_search_dn(sam_ctx, mem_ctx, &domain_res, domain_dn, attrs, |
1478 | 0 | 0); |
1479 | 0 | if (ret != LDB_SUCCESS || domain_res->count != 1) { |
1480 | 0 | TALLOC_FREE(mem_ctx); |
1481 | 0 | return NT_STATUS_INTERNAL_DB_CORRUPTION; |
1482 | 0 | } |
1483 | | |
1484 | 0 | sync_interval = ldb_msg_find_attr_as_int(domain_res->msgs[0], |
1485 | 0 | "msDS-LogonTimeSyncInterval", |
1486 | 0 | 14); |
1487 | 0 | DEBUG(5, ("sync interval is %d\n", sync_interval)); |
1488 | 0 | if (sync_interval >= 5){ |
1489 | | /* |
1490 | | * Subtract "a random percentage of 5" days. Presumably this |
1491 | | * percentage is between 0 and 100, and modulus is accurate |
1492 | | * enough. |
1493 | | */ |
1494 | 0 | uint32_t r = generate_random() % 6; |
1495 | 0 | sync_interval -= r; |
1496 | 0 | DBG_INFO("randomised sync interval is %d (-%d)\n", sync_interval, r); |
1497 | 0 | } |
1498 | | /* In the case where sync_interval < 5 there is no randomisation */ |
1499 | | |
1500 | | /* |
1501 | | * msDS-LogonTimeSyncInterval is an int32_t number of days, |
1502 | | * while lastLogonTimestamp (to be updated) is in the 64 bit |
1503 | | * 100ns NTTIME format so we must convert. |
1504 | | */ |
1505 | 0 | *sync_interval_nt = sync_interval * 24LL * 3600LL * 10000000LL; |
1506 | 0 | TALLOC_FREE(mem_ctx); |
1507 | 0 | return NT_STATUS_OK; |
1508 | 0 | } |
1509 | | |
1510 | | |
1511 | | /* |
1512 | | * We only set lastLogonTimestamp if the current value is older than |
1513 | | * now - msDS-LogonTimeSyncInterval days. |
1514 | | * |
1515 | | * lastLogonTimestamp is in the 64 bit 100ns NTTIME format |
1516 | | */ |
1517 | | static NTSTATUS authsam_update_lastlogon_timestamp(struct ldb_context *sam_ctx, |
1518 | | struct ldb_message *msg_mod, |
1519 | | struct ldb_dn *domain_dn, |
1520 | | NTTIME old_timestamp, |
1521 | | NTTIME now, |
1522 | | NTTIME sync_interval_nt) |
1523 | 0 | { |
1524 | 0 | int ret; |
1525 | 0 | DEBUG(5, ("old timestamp is %lld, threshold %lld, diff %lld\n", |
1526 | 0 | (long long int)old_timestamp, |
1527 | 0 | (long long int)(now - sync_interval_nt), |
1528 | 0 | (long long int)(old_timestamp - now + sync_interval_nt))); |
1529 | |
|
1530 | 0 | if (sync_interval_nt == 0){ |
1531 | | /* |
1532 | | * Setting msDS-LogonTimeSyncInterval to zero is how you ask |
1533 | | * that nothing happens here. |
1534 | | */ |
1535 | 0 | return NT_STATUS_OK; |
1536 | 0 | } |
1537 | 0 | if (old_timestamp > now){ |
1538 | 0 | DEBUG(0, ("lastLogonTimestamp is in the future! (%lld > %lld)\n", |
1539 | 0 | (long long int)old_timestamp, (long long int)now)); |
1540 | | /* then what? */ |
1541 | |
|
1542 | 0 | } else if (old_timestamp < now - sync_interval_nt){ |
1543 | 0 | DEBUG(5, ("updating lastLogonTimestamp to %lld\n", |
1544 | 0 | (long long int)now)); |
1545 | | |
1546 | | /* The time has come to update lastLogonTimestamp */ |
1547 | 0 | ret = samdb_msg_add_int64(sam_ctx, msg_mod, msg_mod, |
1548 | 0 | "lastLogonTimestamp", now); |
1549 | |
|
1550 | 0 | if (ret != LDB_SUCCESS) { |
1551 | 0 | return NT_STATUS_NO_MEMORY; |
1552 | 0 | } |
1553 | 0 | } |
1554 | 0 | return NT_STATUS_OK; |
1555 | 0 | } |
1556 | | |
1557 | | /**************************************************************************** |
1558 | | Look for the specified user in the sam, return ldb result structures |
1559 | | ****************************************************************************/ |
1560 | | |
1561 | | NTSTATUS authsam_search_account(TALLOC_CTX *mem_ctx, struct ldb_context *sam_ctx, |
1562 | | const char *account_name, |
1563 | | struct ldb_dn *domain_dn, |
1564 | | struct ldb_message **ret_msg) |
1565 | 0 | { |
1566 | 0 | int ret; |
1567 | 0 | char *account_name_encoded = NULL; |
1568 | |
|
1569 | 0 | account_name_encoded = ldb_binary_encode_string(mem_ctx, account_name); |
1570 | 0 | if (account_name_encoded == NULL) { |
1571 | 0 | return NT_STATUS_NO_MEMORY; |
1572 | 0 | } |
1573 | | |
1574 | | /* pull the user attributes */ |
1575 | 0 | ret = dsdb_search_one(sam_ctx, mem_ctx, ret_msg, domain_dn, LDB_SCOPE_SUBTREE, |
1576 | 0 | user_attrs, |
1577 | 0 | DSDB_SEARCH_SHOW_EXTENDED_DN | DSDB_SEARCH_UPDATE_MANAGED_PASSWORDS, |
1578 | 0 | "(&(sAMAccountName=%s)(objectclass=user))", |
1579 | 0 | account_name_encoded); |
1580 | 0 | if (ret == LDB_ERR_NO_SUCH_OBJECT) { |
1581 | 0 | DEBUG(3,("authsam_search_account: Couldn't find user [%s] in samdb, under %s\n", |
1582 | 0 | account_name, ldb_dn_get_linearized(domain_dn))); |
1583 | 0 | return NT_STATUS_NO_SUCH_USER; |
1584 | 0 | } |
1585 | 0 | if (ret != LDB_SUCCESS) { |
1586 | 0 | return NT_STATUS_INTERNAL_DB_CORRUPTION; |
1587 | 0 | } |
1588 | | |
1589 | 0 | return NT_STATUS_OK; |
1590 | 0 | } |
1591 | | |
1592 | | |
1593 | | /* Reset the badPwdCount to zero and update the lastLogon time. */ |
1594 | | NTSTATUS authsam_logon_success_accounting(struct ldb_context *sam_ctx, |
1595 | | const struct ldb_message *msg, |
1596 | | struct ldb_dn *domain_dn, |
1597 | | bool interactive_or_kerberos, |
1598 | | TALLOC_CTX *send_to_sam_mem_ctx, |
1599 | | struct netr_SendToSamBase **send_to_sam) |
1600 | 0 | { |
1601 | 0 | int ret; |
1602 | 0 | NTSTATUS status; |
1603 | 0 | int badPwdCount; |
1604 | 0 | int dbBadPwdCount; |
1605 | 0 | int64_t lockoutTime; |
1606 | 0 | struct ldb_message *msg_mod; |
1607 | 0 | TALLOC_CTX *mem_ctx; |
1608 | 0 | struct timeval tv_now; |
1609 | 0 | NTTIME now; |
1610 | 0 | NTTIME lastLogonTimestamp; |
1611 | 0 | int64_t lockOutObservationWindow; |
1612 | 0 | NTTIME sync_interval_nt = 0; |
1613 | 0 | bool am_rodc = false; |
1614 | 0 | bool txn_active = false; |
1615 | 0 | bool need_db_reread = false; |
1616 | |
|
1617 | 0 | mem_ctx = talloc_new(msg); |
1618 | 0 | if (mem_ctx == NULL) { |
1619 | 0 | return NT_STATUS_NO_MEMORY; |
1620 | 0 | } |
1621 | | |
1622 | | /* |
1623 | | * Any update of the last logon data, needs to be done inside a |
1624 | | * transaction. |
1625 | | * And the user data needs to be re-read, and the account re-checked |
1626 | | * for lockout. |
1627 | | * |
1628 | | * However we have long-running transactions like replication |
1629 | | * that could otherwise grind the system to a halt so we first |
1630 | | * determine if *this* account has seen a bad password, |
1631 | | * otherwise we only start a transaction if there was a need |
1632 | | * (because a change was to be made). |
1633 | | */ |
1634 | | |
1635 | 0 | status = authsam_check_bad_password_indicator( |
1636 | 0 | sam_ctx, mem_ctx, &need_db_reread, msg); |
1637 | 0 | if (!NT_STATUS_IS_OK(status)) { |
1638 | 0 | TALLOC_FREE(mem_ctx); |
1639 | 0 | return status; |
1640 | 0 | } |
1641 | | |
1642 | 0 | if (interactive_or_kerberos == false) { |
1643 | | /* |
1644 | | * Avoid calculating this twice, it reads the PSO. A |
1645 | | * race on this is unimportant. |
1646 | | */ |
1647 | 0 | lockOutObservationWindow |
1648 | 0 | = samdb_result_msds_LockoutObservationWindow( |
1649 | 0 | sam_ctx, mem_ctx, domain_dn, msg); |
1650 | 0 | } |
1651 | |
|
1652 | 0 | ret = samdb_rodc(sam_ctx, &am_rodc); |
1653 | 0 | if (ret != LDB_SUCCESS) { |
1654 | 0 | status = NT_STATUS_INTERNAL_ERROR; |
1655 | 0 | goto error; |
1656 | 0 | } |
1657 | | |
1658 | 0 | if (!am_rodc) { |
1659 | | /* |
1660 | | * Avoid reading the main domain DN twice. A race on |
1661 | | * this is unimportant. |
1662 | | */ |
1663 | 0 | status = authsam_calculate_lastlogon_sync_interval( |
1664 | 0 | sam_ctx, mem_ctx, domain_dn, &sync_interval_nt); |
1665 | |
|
1666 | 0 | if (!NT_STATUS_IS_OK(status)) { |
1667 | 0 | status = NT_STATUS_INTERNAL_ERROR; |
1668 | 0 | goto error; |
1669 | 0 | } |
1670 | 0 | } |
1671 | | |
1672 | 0 | get_transaction: |
1673 | |
|
1674 | 0 | if (need_db_reread) { |
1675 | 0 | struct ldb_message *current = NULL; |
1676 | | |
1677 | | /* |
1678 | | * Start a new transaction |
1679 | | */ |
1680 | 0 | ret = ldb_transaction_start(sam_ctx); |
1681 | 0 | if (ret != LDB_SUCCESS) { |
1682 | 0 | status = NT_STATUS_INTERNAL_ERROR; |
1683 | 0 | goto error; |
1684 | 0 | } |
1685 | | |
1686 | 0 | txn_active = true; |
1687 | | |
1688 | | /* |
1689 | | * Re-read the account details, using the GUID |
1690 | | * embedded in DN so this is safe against a race where |
1691 | | * it is being renamed. |
1692 | | */ |
1693 | 0 | status = authsam_reread_user_logon_data( |
1694 | 0 | sam_ctx, mem_ctx, msg, ¤t); |
1695 | 0 | if (!NT_STATUS_IS_OK(status)) { |
1696 | | /* |
1697 | | * The re-read can return account locked out, as well |
1698 | | * as an internal error |
1699 | | */ |
1700 | 0 | if (NT_STATUS_EQUAL(status, NT_STATUS_ACCOUNT_LOCKED_OUT)) { |
1701 | | /* |
1702 | | * For NT_STATUS_ACCOUNT_LOCKED_OUT we want to commit |
1703 | | * the transaction. Again to avoid cluttering the |
1704 | | * audit logs with spurious errors |
1705 | | */ |
1706 | 0 | goto exit; |
1707 | 0 | } |
1708 | 0 | goto error; |
1709 | 0 | } |
1710 | 0 | msg = current; |
1711 | 0 | } |
1712 | | |
1713 | 0 | lockoutTime = ldb_msg_find_attr_as_int64(msg, "lockoutTime", 0); |
1714 | 0 | dbBadPwdCount = ldb_msg_find_attr_as_int(msg, "badPwdCount", 0); |
1715 | 0 | tv_now = timeval_current(); |
1716 | 0 | now = timeval_to_nttime(&tv_now); |
1717 | |
|
1718 | 0 | if (interactive_or_kerberos) { |
1719 | 0 | badPwdCount = dbBadPwdCount; |
1720 | 0 | } else { |
1721 | | /* |
1722 | | * We get lockOutObservationWindow above, before the |
1723 | | * transaction |
1724 | | */ |
1725 | 0 | badPwdCount = dsdb_effective_badPwdCount( |
1726 | 0 | msg, lockOutObservationWindow, now); |
1727 | 0 | } |
1728 | 0 | lastLogonTimestamp = |
1729 | 0 | ldb_msg_find_attr_as_int64(msg, "lastLogonTimestamp", 0); |
1730 | |
|
1731 | 0 | DEBUG(5, ("lastLogonTimestamp is %lld\n", |
1732 | 0 | (long long int)lastLogonTimestamp)); |
1733 | |
|
1734 | 0 | msg_mod = ldb_msg_new(mem_ctx); |
1735 | 0 | if (msg_mod == NULL) { |
1736 | 0 | status = NT_STATUS_NO_MEMORY; |
1737 | 0 | goto error; |
1738 | 0 | } |
1739 | | |
1740 | | /* |
1741 | | * By using the DN from msg->dn directly, we allow LDB to |
1742 | | * prefer the embedded GUID form, so this is actually quite |
1743 | | * safe even in the case where DN has been changed |
1744 | | */ |
1745 | 0 | msg_mod->dn = msg->dn; |
1746 | |
|
1747 | 0 | if (lockoutTime != 0) { |
1748 | | /* |
1749 | | * This implies "badPwdCount" = 0, see samldb_lockout_time() |
1750 | | */ |
1751 | 0 | ret = samdb_msg_add_int(sam_ctx, msg_mod, msg_mod, "lockoutTime", 0); |
1752 | 0 | if (ret != LDB_SUCCESS) { |
1753 | 0 | status = NT_STATUS_NO_MEMORY; |
1754 | 0 | goto error; |
1755 | 0 | } |
1756 | 0 | } else if (badPwdCount != 0) { |
1757 | 0 | ret = samdb_msg_add_int(sam_ctx, msg_mod, msg_mod, "badPwdCount", 0); |
1758 | 0 | if (ret != LDB_SUCCESS) { |
1759 | 0 | status = NT_STATUS_NO_MEMORY; |
1760 | 0 | goto error; |
1761 | 0 | } |
1762 | 0 | } |
1763 | | |
1764 | 0 | if (interactive_or_kerberos || |
1765 | 0 | (badPwdCount != 0 && lockoutTime == 0)) { |
1766 | 0 | ret = samdb_msg_add_int64(sam_ctx, msg_mod, msg_mod, |
1767 | 0 | "lastLogon", now); |
1768 | 0 | if (ret != LDB_SUCCESS) { |
1769 | 0 | status = NT_STATUS_NO_MEMORY; |
1770 | 0 | goto error; |
1771 | 0 | } |
1772 | 0 | } |
1773 | | |
1774 | 0 | if (interactive_or_kerberos) { |
1775 | 0 | int logonCount; |
1776 | |
|
1777 | 0 | logonCount = ldb_msg_find_attr_as_int(msg, "logonCount", 0); |
1778 | |
|
1779 | 0 | logonCount += 1; |
1780 | |
|
1781 | 0 | ret = samdb_msg_add_int(sam_ctx, msg_mod, msg_mod, |
1782 | 0 | "logonCount", logonCount); |
1783 | 0 | if (ret != LDB_SUCCESS) { |
1784 | 0 | status = NT_STATUS_NO_MEMORY; |
1785 | 0 | goto error; |
1786 | 0 | } |
1787 | 0 | } else { |
1788 | | /* Set an unset logonCount to 0 on first successful login */ |
1789 | 0 | if (ldb_msg_find_ldb_val(msg, "logonCount") == NULL) { |
1790 | 0 | ret = samdb_msg_add_int(sam_ctx, msg_mod, msg_mod, |
1791 | 0 | "logonCount", 0); |
1792 | 0 | if (ret != LDB_SUCCESS) { |
1793 | 0 | TALLOC_FREE(mem_ctx); |
1794 | 0 | return NT_STATUS_NO_MEMORY; |
1795 | 0 | } |
1796 | 0 | } |
1797 | 0 | } |
1798 | | |
1799 | 0 | if (!am_rodc) { |
1800 | 0 | status = authsam_update_lastlogon_timestamp( |
1801 | 0 | sam_ctx, |
1802 | 0 | msg_mod, |
1803 | 0 | domain_dn, |
1804 | 0 | lastLogonTimestamp, |
1805 | 0 | now, |
1806 | 0 | sync_interval_nt); |
1807 | 0 | if (!NT_STATUS_IS_OK(status)) { |
1808 | 0 | status = NT_STATUS_NO_MEMORY; |
1809 | 0 | goto error; |
1810 | 0 | } |
1811 | 0 | } else { |
1812 | | /* Perform the (async) SendToSAM calls for MS-SAMS */ |
1813 | 0 | if (dbBadPwdCount != 0 && send_to_sam != NULL) { |
1814 | 0 | struct netr_SendToSamBase *base_msg; |
1815 | 0 | struct GUID guid = samdb_result_guid(msg, "objectGUID"); |
1816 | |
|
1817 | 0 | base_msg = talloc_zero(send_to_sam_mem_ctx, |
1818 | 0 | struct netr_SendToSamBase); |
1819 | 0 | if (base_msg == NULL) { |
1820 | 0 | status = NT_STATUS_NO_MEMORY; |
1821 | 0 | goto error; |
1822 | 0 | } |
1823 | | |
1824 | 0 | base_msg->message_type = SendToSamResetBadPasswordCount; |
1825 | 0 | base_msg->message_size = 16; |
1826 | 0 | base_msg->message.reset_bad_password.guid = guid; |
1827 | 0 | *send_to_sam = base_msg; |
1828 | 0 | } |
1829 | 0 | } |
1830 | | |
1831 | 0 | if (msg_mod->num_elements > 0) { |
1832 | 0 | unsigned int i; |
1833 | 0 | struct ldb_request *req; |
1834 | | |
1835 | | /* |
1836 | | * If it turns out we are going to update the DB, go |
1837 | | * back to the start, get a transaction and the |
1838 | | * current DB state and try again |
1839 | | */ |
1840 | 0 | if (txn_active == false) { |
1841 | 0 | need_db_reread = true; |
1842 | 0 | goto get_transaction; |
1843 | 0 | } |
1844 | | |
1845 | | /* mark all the message elements as LDB_FLAG_MOD_REPLACE */ |
1846 | 0 | for (i=0;i<msg_mod->num_elements;i++) { |
1847 | 0 | msg_mod->elements[i].flags = LDB_FLAG_MOD_REPLACE; |
1848 | 0 | } |
1849 | |
|
1850 | 0 | ret = ldb_build_mod_req(&req, sam_ctx, sam_ctx, |
1851 | 0 | msg_mod, |
1852 | 0 | NULL, |
1853 | 0 | NULL, |
1854 | 0 | ldb_op_default_callback, |
1855 | 0 | NULL); |
1856 | 0 | if (ret != LDB_SUCCESS) { |
1857 | 0 | status = NT_STATUS_INTERNAL_ERROR; |
1858 | 0 | goto error; |
1859 | 0 | } |
1860 | | |
1861 | 0 | ret = ldb_request_add_control(req, |
1862 | 0 | DSDB_CONTROL_FORCE_RODC_LOCAL_CHANGE, |
1863 | 0 | false, NULL); |
1864 | 0 | if (ret != LDB_SUCCESS) { |
1865 | 0 | TALLOC_FREE(req); |
1866 | 0 | status = NT_STATUS_INTERNAL_ERROR; |
1867 | 0 | goto error; |
1868 | 0 | } |
1869 | | /* |
1870 | | * As we're in a transaction, make the ldb request directly |
1871 | | * to avoid the nested transaction that would result if we |
1872 | | * called dsdb_autotransaction_request |
1873 | | */ |
1874 | 0 | ret = ldb_request(sam_ctx, req); |
1875 | 0 | if (ret == LDB_SUCCESS) { |
1876 | 0 | ret = ldb_wait(req->handle, LDB_WAIT_ALL); |
1877 | 0 | } |
1878 | 0 | TALLOC_FREE(req); |
1879 | 0 | if (ret != LDB_SUCCESS) { |
1880 | 0 | status = NT_STATUS_INTERNAL_ERROR; |
1881 | 0 | goto error; |
1882 | 0 | } |
1883 | 0 | } |
1884 | 0 | status = authsam_clear_bad_password_indicator(sam_ctx, mem_ctx, msg); |
1885 | 0 | if (!NT_STATUS_IS_OK(status)) { |
1886 | 0 | goto error; |
1887 | 0 | } |
1888 | | |
1889 | | /* |
1890 | | * Note that we may not have updated the user record, but |
1891 | | * committing the transaction in that case is still the correct |
1892 | | * thing to do. |
1893 | | * If the transaction was cancelled, this would be logged by |
1894 | | * the dsdb audit log as a failure. When in fact it is expected |
1895 | | * behaviour. |
1896 | | * |
1897 | | * Thankfully both TDB and LMDB seem to optimise for the empty |
1898 | | * transaction case |
1899 | | */ |
1900 | 0 | exit: |
1901 | 0 | TALLOC_FREE(mem_ctx); |
1902 | |
|
1903 | 0 | if (txn_active == false) { |
1904 | 0 | return status; |
1905 | 0 | } |
1906 | | |
1907 | 0 | ret = ldb_transaction_commit(sam_ctx); |
1908 | 0 | if (ret != LDB_SUCCESS) { |
1909 | 0 | DBG_ERR("Error (%d) %s, committing transaction," |
1910 | 0 | " while updating successful logon accounting" |
1911 | 0 | " for (%s)\n", |
1912 | 0 | ret, |
1913 | 0 | ldb_errstring(sam_ctx), |
1914 | 0 | ldb_dn_get_linearized(msg->dn)); |
1915 | 0 | return NT_STATUS_INTERNAL_ERROR; |
1916 | 0 | } |
1917 | 0 | return status; |
1918 | | |
1919 | 0 | error: |
1920 | 0 | DBG_ERR("Failed to update badPwdCount, badPasswordTime or " |
1921 | 0 | "set lockoutTime on %s: %s\n", |
1922 | 0 | ldb_dn_get_linearized(msg->dn), |
1923 | 0 | ldb_errstring(sam_ctx) != NULL ? |
1924 | 0 | ldb_errstring(sam_ctx) :nt_errstr(status)); |
1925 | 0 | if (txn_active) { |
1926 | 0 | ret = ldb_transaction_cancel(sam_ctx); |
1927 | 0 | if (ret != LDB_SUCCESS) { |
1928 | 0 | DBG_ERR("Error rolling back transaction," |
1929 | 0 | " while updating bad password count" |
1930 | 0 | " on %s: %s\n", |
1931 | 0 | ldb_dn_get_linearized(msg->dn), |
1932 | 0 | ldb_errstring(sam_ctx)); |
1933 | 0 | } |
1934 | 0 | } |
1935 | | TALLOC_FREE(mem_ctx); |
1936 | 0 | return status; |
1937 | 0 | } |