2 Unix SMB/CIFS implementation.
3 Authenticate against a remote domain
4 Copyright (C) Andrew Tridgell 1992-1998
5 Copyright (C) Andrew Bartlett 2001
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (at your option) any later version.
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with this program; if not, write to the Free Software
19 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
25 #define DBGC_CLASS DBGC_AUTH
27 BOOL global_machine_password_needs_changing
= False
;
29 extern pstring global_myname
;
30 extern userdom_struct current_user_info
;
33 * Connect to a remote server for domain security authenticaion.
35 * @param cli the cli to return containing the active connection
36 * @param server either a machine name or text IP address to
38 * @param trust_password the trust password to establish the
43 static NTSTATUS
connect_to_domain_password_server(struct cli_state
**cli
,
45 const char *setup_creds_as
,
47 const unsigned char *trust_passwd
)
49 struct in_addr dest_ip
;
50 fstring remote_machine
;
53 if (is_ipaddress(server
)) {
56 /* we shouldn't have 255.255.255.255 forthe IP address of
57 a password server anyways */
58 if ((to_ip
.s_addr
=inet_addr(server
)) == 0xFFFFFFFF) {
59 DEBUG (0,("connect_to_domain_password_server: inet_addr(%s) returned 0xFFFFFFFF!\n", server
));
60 return NT_STATUS_NO_LOGON_SERVERS
;
63 if (!name_status_find("*", 0x20, 0x20, to_ip
, remote_machine
)) {
64 DEBUG(0, ("connect_to_domain_password_server: Can't "
65 "resolve name for IP %s\n", server
));
66 return NT_STATUS_NO_LOGON_SERVERS
;
69 fstrcpy(remote_machine
, server
);
72 standard_sub_basic(current_user_info
.smb_name
, remote_machine
, sizeof(remote_machine
));
73 strupper(remote_machine
);
75 if(!resolve_name( remote_machine
, &dest_ip
, 0x20)) {
76 DEBUG(1,("connect_to_domain_password_server: Can't resolve address for %s\n", remote_machine
));
77 return NT_STATUS_NO_LOGON_SERVERS
;
80 if (ismyip(dest_ip
)) {
81 DEBUG(1,("connect_to_domain_password_server: Password server loop - not using password server %s\n",
83 return NT_STATUS_NO_LOGON_SERVERS
;
86 /* TODO: Send a SAMLOGON request to determine whether this is a valid
87 logonserver. We can avoid a 30-second timeout if the DC is down
88 if the SAMLOGON request fails as it is only over UDP. */
90 /* we use a mutex to prevent two connections at once - when a
91 Win2k PDC get two connections where one hasn't completed a
92 session setup yet it will send a TCP reset to the first
93 connection (tridge) */
96 * With NT4.x DC's *all* authentication must be serialized to avoid
97 * ACCESS_DENIED errors if 2 auths are done from the same machine. JRA.
100 if (!grab_server_mutex(server
))
101 return NT_STATUS_NO_LOGON_SERVERS
;
103 /* Attempt connection */
104 result
= cli_full_connection(cli
, global_myname
, server
,
105 &dest_ip
, 0, "IPC$", "IPC", "", "", "", 0);
107 if (!NT_STATUS_IS_OK(result
)) {
108 release_server_mutex();
113 * We now have an anonymous connection to IPC$ on the domain password server.
117 * Even if the connect succeeds we need to setup the netlogon
118 * pipe here. We do this as we may just have changed the domain
119 * account password on the PDC and yet we may be talking to
120 * a BDC that doesn't have this replicated yet. In this case
121 * a successful connect to a DC needs to take the netlogon connect
122 * into account also. This patch from "Bjart Kvarme" <bjart.kvarme@usit.uio.no>.
125 if(cli_nt_session_open(*cli
, PIPE_NETLOGON
) == False
) {
126 DEBUG(0,("connect_to_domain_password_server: unable to open the domain client session to \
127 machine %s. Error was : %s.\n", remote_machine
, cli_errstr(*cli
)));
128 cli_nt_session_close(*cli
);
131 release_server_mutex();
132 return NT_STATUS_NO_LOGON_SERVERS
;
135 snprintf((*cli
)->mach_acct
, sizeof((*cli
)->mach_acct
) - 1, "%s$", setup_creds_as
);
137 if (!(*cli
)->mach_acct
) {
138 release_server_mutex();
139 return NT_STATUS_NO_MEMORY
;
142 result
= cli_nt_setup_creds(*cli
, sec_chan
, trust_passwd
);
144 if (!NT_STATUS_IS_OK(result
)) {
145 DEBUG(0,("connect_to_domain_password_server: unable to setup the PDC credentials to machine \
146 %s. Error was : %s.\n", remote_machine
, nt_errstr(result
)));
147 cli_nt_session_close(*cli
);
150 release_server_mutex();
154 /* We exit here with the mutex *locked*. JRA */
159 /***********************************************************************
160 Utility function to attempt a connection to an IP address of a DC.
161 ************************************************************************/
163 static NTSTATUS
attempt_connect_to_dc(struct cli_state
**cli
,
166 const char *setup_creds_as
,
168 const unsigned char *trust_passwd
)
173 * Ignore addresses we have already tried.
177 return NT_STATUS_NO_LOGON_SERVERS
;
179 if (!lookup_dc_name(global_myname
, domain
, ip
, dc_name
))
180 return NT_STATUS_NO_LOGON_SERVERS
;
182 return connect_to_domain_password_server(cli
, dc_name
, setup_creds_as
, sec_chan
, trust_passwd
);
185 /***********************************************************************
186 We have been asked to dynamcially determine the IP addresses of
187 the PDC and BDC's for DOMAIN, and query them in turn.
188 ************************************************************************/
189 static NTSTATUS
find_connect_pdc(struct cli_state
**cli
,
191 const char *setup_creds_as
,
193 unsigned char *trust_passwd
,
194 time_t last_change_time
)
196 struct in_addr
*ip_list
= NULL
;
199 NTSTATUS nt_status
= NT_STATUS_NO_LOGON_SERVERS
;
200 time_t time_now
= time(NULL
);
201 BOOL use_pdc_only
= False
;
204 * If the time the machine password has changed
205 * was less than an hour ago then we need to contact
206 * the PDC only, as we cannot be sure domain replication
207 * has yet taken place. Bug found by Gerald (way to go
211 if (time_now
- last_change_time
< 3600)
214 if (!get_dc_list(use_pdc_only
, domain
, &ip_list
, &count
))
215 return NT_STATUS_NO_LOGON_SERVERS
;
218 * Firstly try and contact a PDC/BDC who has the same
219 * network address as any of our interfaces.
221 for(i
= 0; i
< count
; i
++) {
222 if(!is_local_net(ip_list
[i
]))
225 if(NT_STATUS_IS_OK(nt_status
=
226 attempt_connect_to_dc(cli
, domain
,
227 &ip_list
[i
], setup_creds_as
,
228 sec_chan
, trust_passwd
)))
231 zero_ip(&ip_list
[i
]); /* Tried and failed. */
235 * Secondly try and contact a random PDC/BDC.
237 if(!NT_STATUS_IS_OK(nt_status
)) {
238 i
= (sys_random() % count
);
240 if (!is_zero_ip(ip_list
[i
])) {
241 if (!NT_STATUS_IS_OK(nt_status
=
242 attempt_connect_to_dc(cli
, domain
,
243 &ip_list
[i
], setup_creds_as
,
244 sec_chan
, trust_passwd
)))
245 zero_ip(&ip_list
[i
]); /* Tried and failed. */
250 * Finally go through the IP list in turn, ignoring any addresses
251 * we have already tried.
253 if(!NT_STATUS_IS_OK(nt_status
)) {
255 * Try and connect to any of the other IP addresses in the PDC/BDC list.
256 * Note that from a WINS server the #1 IP address is the PDC.
258 for(i
= 0; i
< count
; i
++) {
259 if (is_zero_ip(ip_list
[i
]))
262 if (NT_STATUS_IS_OK(nt_status
= attempt_connect_to_dc(cli
, domain
,
263 &ip_list
[i
], setup_creds_as
, sec_chan
, trust_passwd
)))
272 /***********************************************************************
273 Do the same as security=server, but using NT Domain calls and a session
274 key from the machine password. If the server parameter is specified
275 use it, otherwise figure out a server from the 'password server' param.
276 ************************************************************************/
278 static NTSTATUS
domain_client_validate(TALLOC_CTX
*mem_ctx
,
279 const auth_usersupplied_info
*user_info
,
282 auth_serversupplied_info
**server_info
,
283 char *server
, char *setup_creds_as
,
285 unsigned char trust_passwd
[16],
286 time_t last_change_time
)
288 fstring remote_machine
;
289 NET_USER_INFO_3 info3
;
290 struct cli_state
*cli
= NULL
;
291 NTSTATUS nt_status
= NT_STATUS_NO_LOGON_SERVERS
;
294 * At this point, smb_apasswd points to the lanman response to
295 * the challenge in local_challenge, and smb_ntpasswd points to
296 * the NT response to the challenge in local_challenge. Ship
297 * these over the secure channel to a domain controller and
298 * see if they were valid.
301 while (!NT_STATUS_IS_OK(nt_status
) &&
302 next_token(&server
,remote_machine
,LIST_SEP
,sizeof(remote_machine
))) {
303 if(strequal(remote_machine
, "*")) {
304 nt_status
= find_connect_pdc(&cli
, domain
, setup_creds_as
, sec_chan
, trust_passwd
, last_change_time
);
306 nt_status
= connect_to_domain_password_server(&cli
, remote_machine
, setup_creds_as
, sec_chan
, trust_passwd
);
310 if (!NT_STATUS_IS_OK(nt_status
)) {
311 DEBUG(0,("domain_client_validate: Domain password server not available.\n"));
318 * If this call succeeds, we now have lots of info about the user
319 * in the info3 structure.
322 nt_status
= cli_netlogon_sam_network_logon(cli
, mem_ctx
,
323 user_info
->smb_name
.str
, user_info
->domain
.str
,
324 user_info
->wksta_name
.str
, chal
,
325 user_info
->lm_resp
, user_info
->nt_resp
,
328 if (!NT_STATUS_IS_OK(nt_status
)) {
329 DEBUG(0,("domain_client_validate: unable to validate password "
330 "for user %s in domain %s to Domain controller %s. "
331 "Error was %s.\n", user_info
->smb_name
.str
,
332 user_info
->domain
.str
, cli
->srv_name_slash
,
333 nt_errstr(nt_status
)));
335 nt_status
= make_server_info_info3(mem_ctx
, user_info
->internal_username
.str
,
336 user_info
->smb_name
.str
, domain
, server_info
, &info3
);
338 /* The stuff doesn't work right yet */
339 SMB_ASSERT(sizeof((*server_info
)->session_key
) == sizeof(info3
.user_sess_key
));
340 memcpy((*server_info
)->session_key
, info3
.user_sess_key
, sizeof((*server_info
)->session_key
)/* 16 */);
341 SamOEMhash((*server_info
)->session_key
, trust_passwd
, sizeof((*server_info
)->session_key
));
344 uni_group_cache_store_netlogon(mem_ctx
, &info3
);
349 * We don't actually need to do this - plus it fails currently with
350 * NT_STATUS_INVALID_INFO_CLASS - we need to know *exactly* what to
354 if (NT_STATUS_IS_OK(status
)) {
355 if(cli_nt_logoff(&cli
, &ctr
) == False
) {
356 DEBUG(0,("domain_client_validate: unable to log off user %s in domain \
357 %s to Domain controller %s. Error was %s.\n", user
, domain
, remote_machine
, cli_errstr(&cli
)));
358 nt_status
= NT_STATUS_LOGON_FAILURE
;
363 /* Note - once the cli stream is shutdown the mem_ctx used
364 to allocate the other_sids and gids structures has been deleted - so
365 these pointers are no longer valid..... */
367 cli_nt_session_close(cli
);
370 release_server_mutex();
374 /****************************************************************************
375 Check for a valid username and password in security=domain mode.
376 ****************************************************************************/
378 static NTSTATUS
check_ntdomain_security(const struct auth_context
*auth_context
,
379 void *my_private_data
,
381 const auth_usersupplied_info
*user_info
,
382 auth_serversupplied_info
**server_info
)
384 NTSTATUS nt_status
= NT_STATUS_LOGON_FAILURE
;
385 char *password_server
;
386 unsigned char trust_passwd
[16];
387 time_t last_change_time
;
388 char *domain
= lp_workgroup();
390 if (!user_info
|| !server_info
|| !auth_context
) {
391 DEBUG(1,("check_ntdomain_security: Critical variables not present. Failing.\n"));
392 return NT_STATUS_INVALID_PARAMETER
;
396 * Check that the requested domain is not our own machine name.
397 * If it is, we should never check the PDC here, we use our own local
401 if(is_netbios_alias_or_name(user_info
->domain
.str
)) {
402 DEBUG(3,("check_ntdomain_security: Requested domain was for this machine.\n"));
403 return NT_STATUS_LOGON_FAILURE
;
407 * Get the machine account password for our primary domain
408 * No need to become_root() as secrets_init() is done at startup.
411 if (!secrets_fetch_trust_account_password(domain
, trust_passwd
, &last_change_time
))
413 DEBUG(0, ("check_ntdomain_security: could not fetch trust account password for domain '%s'\n", domain
));
414 return NT_STATUS_CANT_ACCESS_DOMAIN_INFO
;
417 /* Test if machine password is expired and need to be changed */
418 if (time(NULL
) > last_change_time
+ lp_machine_password_timeout())
420 global_machine_password_needs_changing
= True
;
424 * Treat each name in the 'password server =' line as a potential
425 * PDC/BDC. Contact each in turn and try and authenticate.
428 password_server
= lp_passwordserver();
430 nt_status
= domain_client_validate(mem_ctx
, user_info
, domain
,
431 (uchar
*)auth_context
->challenge
.data
,
433 password_server
, global_myname
, SEC_CHAN_WKSTA
, trust_passwd
, last_change_time
);
437 /* module initialisation */
438 NTSTATUS
auth_init_ntdomain(struct auth_context
*auth_context
, const char* param
, auth_methods
**auth_method
)
440 if (!make_auth_methods(auth_context
, auth_method
)) {
441 return NT_STATUS_NO_MEMORY
;
444 (*auth_method
)->name
= "ntdomain";
445 (*auth_method
)->auth
= check_ntdomain_security
;
450 /****************************************************************************
451 Check for a valid username and password in a trusted domain
452 ****************************************************************************/
454 static NTSTATUS
check_trustdomain_security(const struct auth_context
*auth_context
,
455 void *my_private_data
,
457 const auth_usersupplied_info
*user_info
,
458 auth_serversupplied_info
**server_info
)
460 NTSTATUS nt_status
= NT_STATUS_LOGON_FAILURE
;
461 unsigned char trust_md4_password
[16];
462 char *trust_password
;
463 time_t last_change_time
;
466 if (!user_info
|| !server_info
|| !auth_context
) {
467 DEBUG(1,("check_trustdomain_security: Critical variables not present. Failing.\n"));
468 return NT_STATUS_INVALID_PARAMETER
;
472 * Check that the requested domain is not our own machine name.
473 * If it is, we should never check the PDC here, we use our own local
477 if(is_netbios_alias_or_name(user_info
->domain
.str
)) {
478 DEBUG(3,("check_trustdomain_security: Requested domain was for this machine.\n"));
479 return NT_STATUS_LOGON_FAILURE
;
483 * Check that the requested domain is not our own domain,
484 * If it is, we should use our own local password file.
487 if(strequal(lp_workgroup(), (user_info
->domain
.str
))) {
488 DEBUG(3,("check_trustdomain_security: Requested domain was for this domain.\n"));
489 return NT_STATUS_LOGON_FAILURE
;
493 * Get the trusted account password for the trusted domain
494 * No need to become_root() as secrets_init() is done at startup.
497 if (!secrets_fetch_trusted_domain_password(user_info
->domain
.str
, &trust_password
, &sid
, &last_change_time
))
499 DEBUG(0, ("check_trustdomain_security: could not fetch trust account password for domain %s\n", user_info
->domain
.str
));
500 return NT_STATUS_CANT_ACCESS_DOMAIN_INFO
;
503 #ifdef DEBUG_PASSWORD
504 DEBUG(100, ("Trust password for domain %s is %s\n", user_info
->domain
.str
, trust_password
));
506 E_md4hash(trust_password
, trust_md4_password
);
507 SAFE_FREE(trust_password
);
510 /* Test if machine password is expired and need to be changed */
511 if (time(NULL
) > last_change_time
+ lp_machine_password_timeout())
513 global_machine_password_needs_changing
= True
;
517 nt_status
= domain_client_validate(mem_ctx
, user_info
, user_info
->domain
.str
,
518 (uchar
*)auth_context
->challenge
.data
,
519 server_info
, "*" /* Do a lookup */,
520 lp_workgroup(), SEC_CHAN_DOMAIN
, trust_md4_password
, last_change_time
);
525 /* module initialisation */
526 NTSTATUS
auth_init_trustdomain(struct auth_context
*auth_context
, const char* param
, auth_methods
**auth_method
)
528 if (!make_auth_methods(auth_context
, auth_method
)) {
529 return NT_STATUS_NO_MEMORY
;
532 (*auth_method
)->name
= "trustdomain";
533 (*auth_method
)->auth
= check_trustdomain_security
;