and so it begins....
[abartlet/samba.git/.git] / source3 / nsswitch / winbindd_pam.c
1 /*
2    Unix SMB/CIFS implementation.
3
4    Winbind daemon - pam auth funcions
5
6    Copyright (C) Andrew Tridgell 2000
7    Copyright (C) Tim Potter 2001
8    Copyright (C) Andrew Bartlett 2001-2002
9    
10    This program is free software; you can redistribute it and/or modify
11    it under the terms of the GNU General Public License as published by
12    the Free Software Foundation; either version 2 of the License, or
13    (at your option) any later version.
14    
15    This program is distributed in the hope that it will be useful,
16    but WITHOUT ANY WARRANTY; without even the implied warranty of
17    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18    GNU General Public License for more details.
19    
20    You should have received a copy of the GNU General Public License
21    along with this program; if not, write to the Free Software
22    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
23 */
24
25 #include "winbindd.h"
26 #undef DBGC_CLASS
27 #define DBGC_CLASS DBGC_WINBIND
28
29
30 static NTSTATUS append_info3_as_ndr(TALLOC_CTX *mem_ctx, 
31                                     struct winbindd_cli_state *state, 
32                                     NET_USER_INFO_3 *info3) 
33 {
34         prs_struct ps;
35         uint32 size;
36         if (!prs_init(&ps, 256 /* Random, non-zero number */, mem_ctx, MARSHALL)) {
37                 return NT_STATUS_NO_MEMORY;
38         }
39         if (!net_io_user_info3("", info3, &ps, 1, 3)) {
40                 prs_mem_free(&ps);
41                 return NT_STATUS_UNSUCCESSFUL;
42         }
43
44         size = prs_data_size(&ps);
45         state->response.extra_data = malloc(size);
46         if (!state->response.extra_data) {
47                 prs_mem_free(&ps);
48                 return NT_STATUS_NO_MEMORY;
49         }
50         prs_copy_all_data_out(state->response.extra_data, &ps);
51         state->response.length += size;
52         prs_mem_free(&ps);
53         return NT_STATUS_OK;
54 }
55
56 /*******************************************************************
57  wrapper around retreiving the trsut account password 
58 *******************************************************************/
59
60 static BOOL get_trust_pw(const char *domain, uint8 ret_pwd[16],
61                           time_t *pass_last_set_time, uint32 *channel)
62 {
63         DOM_SID sid;
64         char *pwd;
65
66         /* if we are a DC and this is not our domain, then lookup an account
67            for the domain trust */
68            
69         if ( IS_DC && !strequal(domain, lp_workgroup()) && lp_allow_trusted_domains() ) 
70         {
71                 if ( !secrets_fetch_trusted_domain_password(domain, &pwd, &sid, 
72                         pass_last_set_time) ) 
73                 {
74                         DEBUG(0, ("get_trust_pw: could not fetch trust account "
75                                   "password for trusted domain %s\n", domain));
76                         return False;
77                 }
78                 
79                 *channel = SEC_CHAN_DOMAIN;
80                 E_md4hash(pwd, ret_pwd);
81                 SAFE_FREE(pwd);
82
83                 return True;
84         }
85         else    /* just get the account for our domain (covers 
86                    ROLE_DOMAIN_MEMBER as well */
87         {
88                 /* get the machine trust account for our domain */
89
90                 if ( !secrets_fetch_trust_account_password (lp_workgroup(), ret_pwd,
91                         pass_last_set_time, channel) ) 
92                 {
93                         DEBUG(0, ("get_trust_pw: could not fetch trust account "
94                                   "password for my domain %s\n", domain));
95                         return False;
96                 }
97                 
98                 return True;
99         }
100         
101         /* Failure */
102         return False;
103 }
104
105 /**********************************************************************
106  Authenticate a user with a clear test password
107 **********************************************************************/
108
109 enum winbindd_result winbindd_pam_auth(struct winbindd_cli_state *state) 
110 {
111         NTSTATUS result;
112         fstring name_domain, name_user;
113         unsigned char trust_passwd[16];
114         time_t last_change_time;
115         uint32 sec_channel_type;
116         NET_USER_INFO_3 info3;
117         struct cli_state *cli = NULL;
118         uchar chal[8];
119         TALLOC_CTX *mem_ctx = NULL;
120         DATA_BLOB lm_resp;
121         DATA_BLOB nt_resp;
122         DOM_CRED ret_creds;
123         int attempts = 0;
124         unsigned char local_lm_response[24];
125         unsigned char local_nt_response[24];
126         const char *contact_domain;
127
128         /* Ensure null termination */
129         state->request.data.auth.user[sizeof(state->request.data.auth.user)-1]='\0';
130
131         /* Ensure null termination */
132         state->request.data.auth.pass[sizeof(state->request.data.auth.pass)-1]='\0';
133
134         DEBUG(3, ("[%5d]: pam auth %s\n", state->pid,
135                   state->request.data.auth.user));
136
137         if (!(mem_ctx = talloc_init("winbind pam auth for %s", state->request.data.auth.user))) {
138                 DEBUG(0, ("winbindd_pam_auth: could not talloc_init()!\n"));
139                 result = NT_STATUS_NO_MEMORY;
140                 goto done;
141         }
142
143         /* Parse domain and username */
144         
145         if (!parse_domain_user(state->request.data.auth.user, name_domain, 
146                                name_user)) {
147                 DEBUG(5,("no domain separator (%s) in username (%s) - failing auth\n", lp_winbind_separator(), state->request.data.auth.user));
148                 result = NT_STATUS_INVALID_PARAMETER;
149                 goto done;
150         }
151
152         /* do password magic */
153         
154         generate_random_buffer(chal, 8, False);
155         SMBencrypt(state->request.data.auth.pass, chal, local_lm_response);
156                 
157         SMBNTencrypt(state->request.data.auth.pass, chal, local_nt_response);
158
159         lm_resp = data_blob_talloc(mem_ctx, local_lm_response, sizeof(local_lm_response));
160         nt_resp = data_blob_talloc(mem_ctx, local_nt_response, sizeof(local_nt_response));
161         
162         if ( !get_trust_pw(name_domain, trust_passwd, &last_change_time, &sec_channel_type) ) {
163                 result = NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
164                 goto done;
165         }
166
167         /* what domain should we contact? */
168         
169         if ( IS_DC )
170                 contact_domain = name_domain;
171         else
172                 contact_domain = lp_workgroup();
173                 
174         /* check authentication loop */
175
176         do {
177                 ZERO_STRUCT(info3);
178                 ZERO_STRUCT(ret_creds);
179         
180                 /* Don't shut this down - it belongs to the connection cache code */
181                 result = cm_get_netlogon_cli(contact_domain, trust_passwd, 
182                                              sec_channel_type, False, &cli);
183
184                 if (!NT_STATUS_IS_OK(result)) {
185                         DEBUG(3, ("could not open handle to NETLOGON pipe\n"));
186                         goto done;
187                 }
188
189                 result = cli_netlogon_sam_network_logon(cli, mem_ctx,
190                                                         &ret_creds,
191                                                         name_user, name_domain, 
192                                                         global_myname(), chal, 
193                                                         lm_resp, nt_resp,
194                                                         &info3);
195                 attempts += 1;
196                 
197                 /* if we get access denied, a possible cuase was that we had and open
198                    connection to the DC, but someone changed our machine accoutn password
199                    out from underneath us using 'net rpc changetrustpw' */
200                    
201                 if ( NT_STATUS_V(result) == NT_STATUS_V(NT_STATUS_ACCESS_DENIED) ) {
202                         DEBUG(3,("winbindd_pam_auth: sam_logon returned ACCESS_DENIED.  Maybe the trust account "
203                                 "password was changed and we didn't know it.  Killing connections to domain %s\n",
204                                 name_domain));
205                         winbindd_cm_flush();
206                         cli->fd = -1;
207                 } 
208                 
209                 /* We have to try a second time as cm_get_netlogon_cli
210                    might not yet have noticed that the DC has killed
211                    our connection. */
212
213         } while ( (attempts < 2) && (cli->fd == -1) );
214
215         
216         clnt_deal_with_creds(cli->sess_key, &(cli->clnt_cred), &ret_creds);
217         
218         if (NT_STATUS_IS_OK(result)) {
219                 netsamlogon_cache_store( cli->mem_ctx, &info3 );
220                 wcache_invalidate_samlogon(find_domain_from_name(name_domain), &info3);
221         }
222         
223         
224 done:
225         
226         /* give us a more useful (more correct?) error code */
227         if ((NT_STATUS_EQUAL(result, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND) || (NT_STATUS_EQUAL(result, NT_STATUS_UNSUCCESSFUL)))) {
228                 result = NT_STATUS_NO_LOGON_SERVERS;
229         }
230         
231         state->response.data.auth.nt_status = NT_STATUS_V(result);
232         fstrcpy(state->response.data.auth.nt_status_string, nt_errstr(result));
233         fstrcpy(state->response.data.auth.error_string, get_friendly_nt_error_msg(result));
234         state->response.data.auth.pam_error = nt_status_to_pam(result);
235
236         DEBUG(NT_STATUS_IS_OK(result) ? 5 : 2, ("Plain-text authentication for user %s returned %s (PAM: %d)\n", 
237               state->request.data.auth.user, 
238               state->response.data.auth.nt_status_string,
239               state->response.data.auth.pam_error));          
240
241         if (mem_ctx) 
242                 talloc_destroy(mem_ctx);
243         
244         return NT_STATUS_IS_OK(result) ? WINBINDD_OK : WINBINDD_ERROR;
245 }
246
247 /**********************************************************************
248  Challenge Response Authentication Protocol 
249 **********************************************************************/
250
251 enum winbindd_result winbindd_pam_auth_crap(struct winbindd_cli_state *state) 
252 {
253         NTSTATUS result;
254         unsigned char trust_passwd[16];
255         time_t last_change_time;
256         uint32 sec_channel_type;
257         NET_USER_INFO_3 info3;
258         struct cli_state *cli = NULL;
259         TALLOC_CTX *mem_ctx = NULL;
260         char *user = NULL;
261         const char *domain = NULL;
262         const char *workstation;
263         const char *contact_domain;
264         DOM_CRED ret_creds;
265         int attempts = 0;
266
267         DATA_BLOB lm_resp, nt_resp;
268
269         if (!state->privileged) {
270                 DEBUG(2, ("winbindd_pam_auth_crap: non-privileged access denied!\n"));
271                 /* send a better message than ACCESS_DENIED */
272                 push_utf8_fstring(state->response.data.auth.error_string, "winbind client not authorized to use winbindd_pam_auth_crap");
273                 result =  NT_STATUS_ACCESS_DENIED;
274                 goto done;
275         }
276
277         /* Ensure null termination */
278         state->request.data.auth_crap.user[sizeof(state->request.data.auth_crap.user)-1]='\0';
279
280         /* Ensure null termination */
281         state->request.data.auth_crap.domain[sizeof(state->request.data.auth_crap.domain)-1]='\0';
282
283         if (!(mem_ctx = talloc_init("winbind pam auth crap for (utf8) %s", state->request.data.auth_crap.user))) {
284                 DEBUG(0, ("winbindd_pam_auth_crap: could not talloc_init()!\n"));
285                 result = NT_STATUS_NO_MEMORY;
286                 goto done;
287         }
288
289         if (pull_utf8_talloc(mem_ctx, &user, state->request.data.auth_crap.user) == (size_t)-1) {
290                 DEBUG(0, ("winbindd_pam_auth_crap: pull_utf8_talloc failed!\n"));
291         }
292
293         if (*state->request.data.auth_crap.domain) {
294                 char *dom = NULL;
295                 if (pull_utf8_talloc(mem_ctx, &dom, state->request.data.auth_crap.domain) == (size_t)-1) {
296                         DEBUG(0, ("winbindd_pam_auth_crap: pull_utf8_talloc failed!\n"));
297                 }
298                 domain = dom;
299         } else if (lp_winbind_use_default_domain()) {
300                 domain = lp_workgroup();
301         } else {
302                 DEBUG(5,("no domain specified with username (%s) - failing auth\n", 
303                          user));
304                 result = NT_STATUS_INVALID_PARAMETER;
305                 goto done;
306         }
307
308         DEBUG(3, ("[%5d]: pam auth crap domain: %s user: %s\n", state->pid,
309                   domain, user));
310            
311         if ( !get_trust_pw(domain, trust_passwd, &last_change_time, &sec_channel_type) ) {
312                 result = NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
313                 goto done;
314         }
315
316         if (*state->request.data.auth_crap.workstation) {
317                 char *wrk = NULL;
318                 if (pull_utf8_talloc(mem_ctx, &wrk, state->request.data.auth_crap.workstation) == (size_t)-1) {
319                         DEBUG(0, ("winbindd_pam_auth_crap: pull_utf8_talloc failed!\n"));
320                 }
321                 workstation = wrk;
322         } else {
323                 workstation = global_myname();
324         }
325
326         if (state->request.data.auth_crap.lm_resp_len > sizeof(state->request.data.auth_crap.lm_resp)
327                 || state->request.data.auth_crap.nt_resp_len > sizeof(state->request.data.auth_crap.nt_resp)) {
328                 DEBUG(0, ("winbindd_pam_auth_crap: invalid password length %u/%u\n", 
329                           state->request.data.auth_crap.lm_resp_len, 
330                           state->request.data.auth_crap.nt_resp_len));
331                 result = NT_STATUS_INVALID_PARAMETER;
332                 goto done;
333         }
334
335         lm_resp = data_blob_talloc(mem_ctx, state->request.data.auth_crap.lm_resp, state->request.data.auth_crap.lm_resp_len);
336         nt_resp = data_blob_talloc(mem_ctx, state->request.data.auth_crap.nt_resp, state->request.data.auth_crap.nt_resp_len);
337         
338         /* what domain should we contact? */
339         
340         if ( IS_DC )
341                 contact_domain = domain;
342         else
343                 contact_domain = lp_workgroup();
344         
345         do {
346                 ZERO_STRUCT(info3);
347                 ZERO_STRUCT(ret_creds);
348
349                 /* Don't shut this down - it belongs to the connection cache code */
350                 result = cm_get_netlogon_cli(contact_domain, trust_passwd, sec_channel_type, False, &cli);
351
352                 if (!NT_STATUS_IS_OK(result)) {
353                         DEBUG(3, ("could not open handle to NETLOGON pipe (error: %s)\n",
354                                   nt_errstr(result)));
355                         goto done;
356                 }
357
358                 result = cli_netlogon_sam_network_logon(cli, mem_ctx,
359                                                         &ret_creds,
360                                                         user, domain,
361                                                         workstation,
362                                                         state->request.data.auth_crap.chal, 
363                                                         lm_resp, nt_resp, 
364                                                         &info3);
365
366                 attempts += 1;
367
368                 /* if we get access denied, a possible cuase was that we had and open
369                    connection to the DC, but someone changed our machine accoutn password
370                    out from underneath us using 'net rpc changetrustpw' */
371                    
372                 if ( NT_STATUS_V(result) == NT_STATUS_V(NT_STATUS_ACCESS_DENIED) ) {
373                         DEBUG(3,("winbindd_pam_auth_crap: sam_logon returned ACCESS_DENIED.  Maybe the trust account "
374                                 "password was changed and we didn't know it.  Killing connections to domain %s\n",
375                                 domain));
376                         winbindd_cm_flush();
377                         cli->fd = -1;
378                 } 
379                 
380                 /* We have to try a second time as cm_get_netlogon_cli
381                    might not yet have noticed that the DC has killed
382                    our connection. */
383
384         } while ( (attempts < 2) && (cli->fd == -1) );
385
386         clnt_deal_with_creds(cli->sess_key, &(cli->clnt_cred), &ret_creds);
387         
388         if (NT_STATUS_IS_OK(result)) {
389                 netsamlogon_cache_store( cli->mem_ctx, &info3 );
390                 wcache_invalidate_samlogon(find_domain_from_name(domain), &info3);
391                 
392                 if (state->request.flags & WBFLAG_PAM_INFO3_NDR) {
393                         result = append_info3_as_ndr(mem_ctx, state, &info3);
394                 }
395                 
396                 if (state->request.flags & WBFLAG_PAM_NTKEY) {
397                         memcpy(state->response.data.auth.nt_session_key, info3.user_sess_key, sizeof(state->response.data.auth.nt_session_key) /* 16 */);
398                 }
399                 if (state->request.flags & WBFLAG_PAM_LMKEY) {
400                         memcpy(state->response.data.auth.first_8_lm_hash, info3.padding, sizeof(state->response.data.auth.first_8_lm_hash) /* 8 */);
401                 }
402         }
403
404 done:
405
406         /* give us a more useful (more correct?) error code */
407         if ((NT_STATUS_EQUAL(result, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND) || (NT_STATUS_EQUAL(result, NT_STATUS_UNSUCCESSFUL)))) {
408                 result = NT_STATUS_NO_LOGON_SERVERS;
409         }
410         
411         state->response.data.auth.nt_status = NT_STATUS_V(result);
412         push_utf8_fstring(state->response.data.auth.nt_status_string, nt_errstr(result));
413         if (!*state->response.data.auth.error_string) 
414                 push_utf8_fstring(state->response.data.auth.error_string, get_friendly_nt_error_msg(result));
415         state->response.data.auth.pam_error = nt_status_to_pam(result);
416
417         DEBUG(NT_STATUS_IS_OK(result) ? 5 : 2, 
418               ("NTLM CRAP authentication for user [%s]\\[%s] returned %s (PAM: %d)\n", 
419                domain,
420                user,
421                state->response.data.auth.nt_status_string,
422                state->response.data.auth.pam_error));         
423
424         if (mem_ctx) 
425                 talloc_destroy(mem_ctx);
426         
427         return NT_STATUS_IS_OK(result) ? WINBINDD_OK : WINBINDD_ERROR;
428 }
429
430 /* Change a user password */
431
432 enum winbindd_result winbindd_pam_chauthtok(struct winbindd_cli_state *state)
433 {
434         NTSTATUS result;
435         char *oldpass, *newpass;
436         fstring domain, user;
437         CLI_POLICY_HND *hnd;
438
439         DEBUG(3, ("[%5d]: pam chauthtok %s\n", state->pid,
440                 state->request.data.chauthtok.user));
441
442         /* Setup crap */
443
444         if (state == NULL)
445                 return WINBINDD_ERROR;
446
447         if (!parse_domain_user(state->request.data.chauthtok.user, domain, 
448                                user)) {
449                 result = NT_STATUS_INVALID_PARAMETER;
450                 goto done;
451         }
452
453         /* Change password */
454
455         oldpass = state->request.data.chauthtok.oldpass;
456         newpass = state->request.data.chauthtok.newpass;
457
458         /* Get sam handle */
459
460         if ( NT_STATUS_IS_ERR(result = cm_get_sam_handle(domain, &hnd)) ) {
461                 DEBUG(1, ("could not get SAM handle on DC for %s\n", domain));
462                 goto done;
463         }
464
465         if (!cli_oem_change_password(hnd->cli, user, newpass, oldpass)) {
466                 DEBUG(1, ("password change failed for user %s/%s\n", domain, 
467                           user));
468                 result = NT_STATUS_WRONG_PASSWORD;
469         } else {
470                 result = NT_STATUS_OK;
471         }
472
473 done:    
474         state->response.data.auth.nt_status = NT_STATUS_V(result);
475         fstrcpy(state->response.data.auth.nt_status_string, nt_errstr(result));
476         fstrcpy(state->response.data.auth.error_string, nt_errstr(result));
477         state->response.data.auth.pam_error = nt_status_to_pam(result);
478
479         DEBUG(NT_STATUS_IS_OK(result) ? 5 : 2, 
480               ("Password change for user [%s]\\[%s] returned %s (PAM: %d)\n", 
481                domain,
482                user,
483                state->response.data.auth.nt_status_string,
484                state->response.data.auth.pam_error));         
485
486         return NT_STATUS_IS_OK(result) ? WINBINDD_OK : WINBINDD_ERROR;
487 }