2 Unix SMB/CIFS implementation.
3 Password and authentication handling
4 Copyright (C) Andrew Bartlett <abartlet@samba.org> 2001-2004
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/>.
24 #include "system/time.h"
25 #include "auth/auth.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"
32 #define KRBTGT_ATTRS \
33 /* required for the krb5 kdc */ \
36 "userPrincipalName", \
37 "servicePrincipalName", \
38 "msDS-KeyVersionNumber", \
39 "msDS-SecondaryKrbTgtNumber", \
40 "msDS-SupportedEncryptionTypes", \
41 "supplementalCredentials", \
47 "userAccountControl", \
53 const char *krbtgt_attrs
[] = {
57 const char *server_attrs
[] = {
61 const char *user_attrs
[] = {
66 /* check 'allowed workstations' */
69 /* required for server_info, not access control: */
85 /****************************************************************************
86 Check if a user is allowed to logon at this time. Note this is the
87 servers local time, as logon hours are just specified as a weekly
89 ****************************************************************************/
91 static bool logon_hours_ok(struct ldb_message
*msg
, const char *name_for_logs
)
93 /* In logon hours first bit is Sunday from 12AM to 1AM */
94 const struct ldb_val
*hours
;
98 uint8_t bitmask
, bitpos
;
100 hours
= ldb_msg_find_ldb_val(msg
, "logonHours");
102 DEBUG(5,("logon_hours_ok: No hours restrictions for user %s\n", name_for_logs
));
106 if (hours
->length
!= 168/8) {
107 DEBUG(5,("logon_hours_ok: malformed logon hours restrictions for user %s\n", name_for_logs
));
111 lasttime
= time(NULL
);
112 utctime
= gmtime(&lasttime
);
114 DEBUG(1, ("logon_hours_ok: failed to get gmtime. Failing logon for user %s\n",
119 /* find the corresponding byte and bit */
120 bitpos
= (utctime
->tm_wday
* 24 + utctime
->tm_hour
) % 168;
121 bitmask
= 1 << (bitpos
% 8);
123 if (! (hours
->data
[bitpos
/8] & bitmask
)) {
124 struct tm
*t
= localtime(&lasttime
);
126 asct
= "INVALID TIME";
130 asct
= "INVALID TIME";
134 DEBUG(1, ("logon_hours_ok: Account for user %s not allowed to "
135 "logon at this time (%s).\n",
136 name_for_logs
, asct
));
140 asct
= asctime(utctime
);
141 DEBUG(5,("logon_hours_ok: user %s allowed to logon at this time (%s)\n",
142 name_for_logs
, asct
? asct
: "UNKNOWN TIME" ));
147 /****************************************************************************
148 Do a specific test for a SAM_ACCOUNT being valid for this connection
149 (ie not disabled, expired and the like).
150 ****************************************************************************/
151 _PUBLIC_ NTSTATUS
authsam_account_ok(TALLOC_CTX
*mem_ctx
,
152 struct ldb_context
*sam_ctx
,
153 uint32_t logon_parameters
,
154 struct ldb_dn
*domain_dn
,
155 struct ldb_message
*msg
,
156 const char *logon_workstation
,
157 const char *name_for_logs
,
158 bool allow_domain_trust
,
159 bool password_change
)
162 const char *workstation_list
;
164 NTTIME must_change_time
;
167 DEBUG(4,("authsam_account_ok: Checking SMB password for user %s\n", name_for_logs
));
169 acct_flags
= samdb_result_acct_flags(sam_ctx
, mem_ctx
, msg
, domain_dn
);
171 acct_expiry
= samdb_result_account_expires(msg
);
173 /* Check for when we must change this password, taking the
174 * userAccountControl flags into account */
175 must_change_time
= samdb_result_force_password_change(sam_ctx
, mem_ctx
,
178 workstation_list
= ldb_msg_find_attr_as_string(msg
, "userWorkstations", NULL
);
180 /* Quit if the account was disabled. */
181 if (acct_flags
& ACB_DISABLED
) {
182 DEBUG(2,("authsam_account_ok: Account for user '%s' was disabled.\n", name_for_logs
));
183 return NT_STATUS_ACCOUNT_DISABLED
;
186 /* Quit if the account was locked out. */
187 if (acct_flags
& ACB_AUTOLOCK
) {
188 DEBUG(2,("authsam_account_ok: Account for user %s was locked out.\n", name_for_logs
));
189 return NT_STATUS_ACCOUNT_LOCKED_OUT
;
192 /* Test account expire time */
193 unix_to_nt_time(&now
, time(NULL
));
194 if (now
> acct_expiry
) {
195 DEBUG(2,("authsam_account_ok: Account for user '%s' has expired.\n", name_for_logs
));
196 DEBUG(3,("authsam_account_ok: Account expired at '%s'.\n",
197 nt_time_string(mem_ctx
, acct_expiry
)));
198 return NT_STATUS_ACCOUNT_EXPIRED
;
201 /* check for immediate expiry "must change at next logon" (but not if this is a password change request) */
202 if ((must_change_time
== 0) && !password_change
) {
203 DEBUG(2,("sam_account_ok: Account for user '%s' password must change!.\n",
205 return NT_STATUS_PASSWORD_MUST_CHANGE
;
208 /* check for expired password (but not if this is a password change request) */
209 if ((must_change_time
< now
) && !password_change
) {
210 DEBUG(2,("sam_account_ok: Account for user '%s' password expired!.\n",
212 DEBUG(2,("sam_account_ok: Password expired at '%s' unix time.\n",
213 nt_time_string(mem_ctx
, must_change_time
)));
214 return NT_STATUS_PASSWORD_EXPIRED
;
217 /* Test workstation. Workstation list is comma separated. */
218 if (logon_workstation
&& workstation_list
&& *workstation_list
) {
219 bool invalid_ws
= true;
221 const char **workstations
= (const char **)str_list_make(mem_ctx
, workstation_list
, ",");
223 for (i
= 0; workstations
&& workstations
[i
]; i
++) {
224 DEBUG(10,("sam_account_ok: checking for workstation match '%s' and '%s'\n",
225 workstations
[i
], logon_workstation
));
227 if (strequal(workstations
[i
], logon_workstation
)) {
233 talloc_free(workstations
);
236 return NT_STATUS_INVALID_WORKSTATION
;
240 if (!logon_hours_ok(msg
, name_for_logs
)) {
241 return NT_STATUS_INVALID_LOGON_HOURS
;
244 if (!allow_domain_trust
) {
245 if (acct_flags
& ACB_DOMTRUST
) {
246 DEBUG(2,("sam_account_ok: Domain trust account %s denied by server\n", name_for_logs
));
247 return NT_STATUS_NOLOGON_INTERDOMAIN_TRUST_ACCOUNT
;
250 if (!(logon_parameters
& MSV1_0_ALLOW_SERVER_TRUST_ACCOUNT
)) {
251 if (acct_flags
& ACB_SVRTRUST
) {
252 DEBUG(2,("sam_account_ok: Server trust account %s denied by server\n", name_for_logs
));
253 return NT_STATUS_NOLOGON_SERVER_TRUST_ACCOUNT
;
256 if (!(logon_parameters
& MSV1_0_ALLOW_WORKSTATION_TRUST_ACCOUNT
)) {
257 /* TODO: this fails with current solaris client. We
258 need to work with Gordon to work out why */
259 if (acct_flags
& ACB_WSTRUST
) {
260 DEBUG(4,("sam_account_ok: Wksta trust account %s denied by server\n", name_for_logs
));
261 return NT_STATUS_NOLOGON_WORKSTATION_TRUST_ACCOUNT
;
268 /* This function tests if a SID structure "sids" contains the SID "sid" */
269 static bool sids_contains_sid(const struct dom_sid
**sids
,
270 const unsigned int num_sids
,
271 const struct dom_sid
*sid
)
275 for (i
= 0; i
< num_sids
; i
++) {
276 if (dom_sid_equal(sids
[i
], sid
))
284 * This function generates the transitive closure of a given SAM object "dn_val"
285 * (it basically expands nested memberships).
286 * If the object isn't located in the "res_sids" structure yet and the
287 * "only_childs" flag is false, we add it to "res_sids".
288 * Then we've always to consider the "memberOf" attributes. We invoke the
289 * function recursively on each of it with the "only_childs" flag set to
291 * The "only_childs" flag is particularly useful if you have a user object and
292 * want to include all it's groups (referenced with "memberOf") but not itself
293 * or considering if that object matches the filter.
295 * At the beginning "res_sids" should reference to a NULL pointer.
297 NTSTATUS
authsam_expand_nested_groups(struct ldb_context
*sam_ctx
,
298 struct ldb_val
*dn_val
, const bool only_childs
, const char *filter
,
299 TALLOC_CTX
*res_sids_ctx
, struct dom_sid
***res_sids
,
300 unsigned int *num_res_sids
)
302 const char * const attrs
[] = { "memberOf", NULL
};
309 struct ldb_result
*res
;
311 const struct ldb_message_element
*el
;
313 if (*res_sids
== NULL
) {
317 tmp_ctx
= talloc_new(res_sids_ctx
);
319 dn
= ldb_dn_from_ldb_val(tmp_ctx
, sam_ctx
, dn_val
);
321 talloc_free(tmp_ctx
);
322 DEBUG(0, (__location__
": we failed parsing DN %*.*s, so we cannot calculate the group token\n",
323 (int)dn_val
->length
, (int)dn_val
->length
, dn_val
->data
));
324 return NT_STATUS_INTERNAL_DB_CORRUPTION
;
327 status
= dsdb_get_extended_dn_sid(dn
, &sid
, "SID");
328 if (!NT_STATUS_IS_OK(status
)) {
329 /* If we fail finding a SID then this is no error since it could
330 * be a non SAM object - e.g. a group with object class
332 talloc_free(tmp_ctx
);
337 DEBUG(0, ("No SAM available, cannot determine local groups\n"));
338 talloc_free(tmp_ctx
);
339 return NT_STATUS_INVALID_SYSTEM_SERVICE
;
343 ret
= dsdb_search_dn(sam_ctx
, tmp_ctx
, &res
, dn
, attrs
,
344 DSDB_SEARCH_SHOW_EXTENDED_DN
);
346 /* This is an O(n^2) linear search */
347 already_there
= sids_contains_sid((const struct dom_sid
**) *res_sids
,
348 *num_res_sids
, &sid
);
353 ret
= dsdb_search(sam_ctx
, tmp_ctx
, &res
, dn
, LDB_SCOPE_BASE
,
354 attrs
, DSDB_SEARCH_SHOW_EXTENDED_DN
, "%s",
358 if (ret
== LDB_ERR_NO_SUCH_OBJECT
) {
359 talloc_free(tmp_ctx
);
363 if (ret
!= LDB_SUCCESS
) {
364 DEBUG(1, (__location__
": dsdb_search for %s failed: %s\n",
365 ldb_dn_get_extended_linearized(tmp_ctx
, dn
, 1),
366 ldb_errstring(sam_ctx
)));
367 talloc_free(tmp_ctx
);
368 return NT_STATUS_INTERNAL_DB_CORRUPTION
;
371 /* We may get back 0 results, if the SID didn't match the filter - such as it wasn't a domain group, for example */
372 if (res
->count
!= 1) {
373 talloc_free(tmp_ctx
);
377 /* We only apply this test once we know the SID matches the filter */
379 *res_sids
= talloc_realloc(res_sids_ctx
, *res_sids
,
380 struct dom_sid
*, *num_res_sids
+ 1);
381 NT_STATUS_HAVE_NO_MEMORY_AND_FREE(*res_sids
, tmp_ctx
);
382 (*res_sids
)[*num_res_sids
] = dom_sid_dup(*res_sids
, &sid
);
383 NT_STATUS_HAVE_NO_MEMORY_AND_FREE((*res_sids
)[*num_res_sids
], tmp_ctx
);
387 el
= ldb_msg_find_element(res
->msgs
[0], "memberOf");
389 for (i
= 0; el
&& i
< el
->num_values
; i
++) {
390 status
= authsam_expand_nested_groups(sam_ctx
, &el
->values
[i
],
391 false, filter
, res_sids_ctx
, res_sids
, num_res_sids
);
392 if (!NT_STATUS_IS_OK(status
)) {
394 talloc_free(tmp_ctx
);
399 talloc_free(tmp_ctx
);
404 _PUBLIC_ NTSTATUS
authsam_make_server_info(TALLOC_CTX
*mem_ctx
,
405 struct ldb_context
*sam_ctx
,
406 const char *netbios_name
,
407 const char *domain_name
,
408 struct ldb_dn
*domain_dn
,
409 struct ldb_message
*msg
,
410 DATA_BLOB user_sess_key
,
411 DATA_BLOB lm_sess_key
,
412 struct auth_serversupplied_info
**_server_info
)
415 struct auth_serversupplied_info
*server_info
;
416 const char *str
, *filter
;
417 /* SIDs for the account and his primary group */
418 struct dom_sid
*account_sid
;
419 struct dom_sid
*primary_group_sid
;
420 const char *primary_group_string
;
421 const char *primary_group_dn
;
422 DATA_BLOB primary_group_blob
;
423 /* SID structures for the expanded group memberships */
424 struct dom_sid
**groupSIDs
= NULL
;
425 unsigned int num_groupSIDs
= 0, i
;
426 struct dom_sid
*domain_sid
;
428 struct ldb_message_element
*el
;
430 server_info
= talloc(mem_ctx
, struct auth_serversupplied_info
);
431 NT_STATUS_HAVE_NO_MEMORY(server_info
);
433 tmp_ctx
= talloc_new(server_info
);
434 NT_STATUS_HAVE_NO_MEMORY_AND_FREE(server_info
, server_info
);
436 account_sid
= samdb_result_dom_sid(server_info
, msg
, "objectSid");
437 NT_STATUS_HAVE_NO_MEMORY_AND_FREE(account_sid
, server_info
);
439 status
= dom_sid_split_rid(tmp_ctx
, account_sid
, &domain_sid
, NULL
);
440 if (!NT_STATUS_IS_OK(status
)) {
441 talloc_free(server_info
);
445 primary_group_sid
= dom_sid_add_rid(server_info
,
447 ldb_msg_find_attr_as_uint(msg
, "primaryGroupID", ~0));
448 NT_STATUS_HAVE_NO_MEMORY_AND_FREE(primary_group_sid
, server_info
);
450 /* Filter out builtin groups from this token. We will search
451 * for builtin groups later, and not include them in the PAC
452 * on SamLogon validation info */
453 filter
= talloc_asprintf(tmp_ctx
, "(&(objectClass=group)(!(groupType:1.2.840.113556.1.4.803:=%u))(groupType:1.2.840.113556.1.4.803:=%u))", GROUP_TYPE_BUILTIN_LOCAL_GROUP
, GROUP_TYPE_SECURITY_ENABLED
);
454 NT_STATUS_HAVE_NO_MEMORY_AND_FREE(filter
, server_info
);
456 primary_group_string
= dom_sid_string(tmp_ctx
, primary_group_sid
);
457 NT_STATUS_HAVE_NO_MEMORY_AND_FREE(primary_group_string
, server_info
);
459 primary_group_dn
= talloc_asprintf(tmp_ctx
, "<SID=%s>", primary_group_string
);
460 NT_STATUS_HAVE_NO_MEMORY_AND_FREE(primary_group_dn
, server_info
);
462 primary_group_blob
= data_blob_string_const(primary_group_dn
);
464 /* Expands the primary group - this function takes in
465 * memberOf-like values, so we fake one up with the
466 * <SID=S-...> format of DN and then let it expand
467 * them, as long as they meet the filter - so only
468 * domain groups, not builtin groups
470 * The primary group is still treated specially, so we set the
471 * 'only childs' flag to true
473 status
= authsam_expand_nested_groups(sam_ctx
, &primary_group_blob
, true, filter
,
474 server_info
, &groupSIDs
, &num_groupSIDs
);
475 if (!NT_STATUS_IS_OK(status
)) {
476 talloc_free(server_info
);
480 /* Expands the additional groups */
481 el
= ldb_msg_find_element(msg
, "memberOf");
482 for (i
= 0; el
&& i
< el
->num_values
; i
++) {
483 /* This function takes in memberOf values and expands
484 * them, as long as they meet the filter - so only
485 * domain groups, not builtin groups */
486 status
= authsam_expand_nested_groups(sam_ctx
, &el
->values
[i
], false, filter
,
487 server_info
, &groupSIDs
, &num_groupSIDs
);
488 if (!NT_STATUS_IS_OK(status
)) {
489 talloc_free(server_info
);
494 server_info
->account_sid
= account_sid
;
495 server_info
->primary_group_sid
= primary_group_sid
;
497 server_info
->domain_groups
= groupSIDs
;
498 server_info
->n_domain_groups
= num_groupSIDs
;
500 server_info
->account_name
= talloc_steal(server_info
,
501 ldb_msg_find_attr_as_string(msg
, "sAMAccountName", NULL
));
503 server_info
->domain_name
= talloc_strdup(server_info
, domain_name
);
504 NT_STATUS_HAVE_NO_MEMORY_AND_FREE(server_info
->domain_name
,
507 str
= ldb_msg_find_attr_as_string(msg
, "displayName", "");
508 server_info
->full_name
= talloc_strdup(server_info
, str
);
509 NT_STATUS_HAVE_NO_MEMORY_AND_FREE(server_info
->full_name
, server_info
);
511 str
= ldb_msg_find_attr_as_string(msg
, "scriptPath", "");
512 server_info
->logon_script
= talloc_strdup(server_info
, str
);
513 NT_STATUS_HAVE_NO_MEMORY_AND_FREE(server_info
->logon_script
,
516 str
= ldb_msg_find_attr_as_string(msg
, "profilePath", "");
517 server_info
->profile_path
= talloc_strdup(server_info
, str
);
518 NT_STATUS_HAVE_NO_MEMORY_AND_FREE(server_info
->profile_path
,
521 str
= ldb_msg_find_attr_as_string(msg
, "homeDirectory", "");
522 server_info
->home_directory
= talloc_strdup(server_info
, str
);
523 NT_STATUS_HAVE_NO_MEMORY_AND_FREE(server_info
->home_directory
,
526 str
= ldb_msg_find_attr_as_string(msg
, "homeDrive", "");
527 server_info
->home_drive
= talloc_strdup(server_info
, str
);
528 NT_STATUS_HAVE_NO_MEMORY_AND_FREE(server_info
->home_drive
, server_info
);
530 server_info
->logon_server
= talloc_strdup(server_info
, netbios_name
);
531 NT_STATUS_HAVE_NO_MEMORY_AND_FREE(server_info
->logon_server
,
534 server_info
->last_logon
= samdb_result_nttime(msg
, "lastLogon", 0);
535 server_info
->last_logoff
= samdb_result_last_logoff(msg
);
536 server_info
->acct_expiry
= samdb_result_account_expires(msg
);
537 server_info
->last_password_change
= samdb_result_nttime(msg
,
539 server_info
->allow_password_change
540 = samdb_result_allow_password_change(sam_ctx
, mem_ctx
,
541 domain_dn
, msg
, "pwdLastSet");
542 server_info
->force_password_change
543 = samdb_result_force_password_change(sam_ctx
, mem_ctx
,
545 server_info
->logon_count
= ldb_msg_find_attr_as_uint(msg
, "logonCount", 0);
546 server_info
->bad_password_count
= ldb_msg_find_attr_as_uint(msg
, "badPwdCount",
549 server_info
->acct_flags
= samdb_result_acct_flags(sam_ctx
, mem_ctx
,
552 server_info
->user_session_key
= data_blob_talloc(server_info
,
554 user_sess_key
.length
);
555 if (user_sess_key
.data
) {
556 NT_STATUS_HAVE_NO_MEMORY_AND_FREE(server_info
->user_session_key
.data
,
559 server_info
->lm_session_key
= data_blob_talloc(server_info
,
562 if (lm_sess_key
.data
) {
563 NT_STATUS_HAVE_NO_MEMORY_AND_FREE(server_info
->lm_session_key
.data
,
567 if (server_info
->acct_flags
& ACB_SVRTRUST
) {
568 /* the SID_NT_ENTERPRISE_DCS SID gets added into the
570 server_info
->domain_groups
= talloc_realloc(server_info
,
571 server_info
->domain_groups
,
573 server_info
->n_domain_groups
+1);
574 NT_STATUS_HAVE_NO_MEMORY_AND_FREE(server_info
->domain_groups
, server_info
);
575 server_info
->domain_groups
[server_info
->n_domain_groups
] =
576 dom_sid_parse_talloc(server_info
->domain_groups
,
577 SID_NT_ENTERPRISE_DCS
);
578 NT_STATUS_HAVE_NO_MEMORY_AND_FREE(server_info
->domain_groups
[server_info
->n_domain_groups
],
580 server_info
->n_domain_groups
++;
583 if ((server_info
->acct_flags
& (ACB_PARTIAL_SECRETS_ACCOUNT
| ACB_WSTRUST
)) ==
584 (ACB_PARTIAL_SECRETS_ACCOUNT
| ACB_WSTRUST
)) {
585 /* the DOMAIN_RID_ENTERPRISE_READONLY_DCS PAC */
586 server_info
->domain_groups
= talloc_realloc(server_info
,
587 server_info
->domain_groups
,
589 server_info
->n_domain_groups
+1);
590 NT_STATUS_HAVE_NO_MEMORY_AND_FREE(server_info
->domain_groups
, server_info
);
591 server_info
->domain_groups
[server_info
->n_domain_groups
] =
592 dom_sid_add_rid(server_info
->domain_groups
, domain_sid
,
593 DOMAIN_RID_ENTERPRISE_READONLY_DCS
);
594 NT_STATUS_HAVE_NO_MEMORY_AND_FREE(server_info
->domain_groups
[server_info
->n_domain_groups
],
596 server_info
->n_domain_groups
++;
599 server_info
->authenticated
= true;
601 talloc_free(tmp_ctx
);
602 *_server_info
= server_info
;
607 NTSTATUS
sam_get_results_principal(struct ldb_context
*sam_ctx
,
608 TALLOC_CTX
*mem_ctx
, const char *principal
,
610 struct ldb_dn
**domain_dn
,
611 struct ldb_message
**msg
)
613 struct ldb_dn
*user_dn
;
615 TALLOC_CTX
*tmp_ctx
= talloc_new(mem_ctx
);
619 return NT_STATUS_NO_MEMORY
;
622 nt_status
= crack_user_principal_name(sam_ctx
, tmp_ctx
, principal
,
623 &user_dn
, domain_dn
);
624 if (!NT_STATUS_IS_OK(nt_status
)) {
625 talloc_free(tmp_ctx
);
629 /* pull the user attributes */
630 ret
= dsdb_search_one(sam_ctx
, tmp_ctx
, msg
, user_dn
,
631 LDB_SCOPE_BASE
, attrs
, DSDB_SEARCH_SHOW_EXTENDED_DN
, "(objectClass=*)");
632 if (ret
!= LDB_SUCCESS
) {
633 talloc_free(tmp_ctx
);
634 return NT_STATUS_INTERNAL_DB_CORRUPTION
;
636 talloc_steal(mem_ctx
, *msg
);
637 talloc_steal(mem_ctx
, *domain_dn
);
638 talloc_free(tmp_ctx
);