(merge from 3.0)
[tprouty/samba.git] / source / 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  Authenticate a user with a clear test password
58 **********************************************************************/
59
60 enum winbindd_result winbindd_pam_auth(struct winbindd_cli_state *state) 
61 {
62         NTSTATUS result;
63         fstring name_domain, name_user;
64         unsigned char trust_passwd[16];
65         time_t last_change_time;
66         uint32 sec_channel_type;
67         NET_USER_INFO_3 info3;
68         struct cli_state *cli = NULL;
69         uchar chal[8];
70         TALLOC_CTX *mem_ctx = NULL;
71         DATA_BLOB lm_resp;
72         DATA_BLOB nt_resp;
73         DOM_CRED ret_creds;
74         int attempts = 0;
75         unsigned char local_lm_response[24];
76         unsigned char local_nt_response[24];
77         struct winbindd_domain *contact_domain;
78         BOOL retry;
79
80         /* Ensure null termination */
81         state->request.data.auth.user[sizeof(state->request.data.auth.user)-1]='\0';
82
83         /* Ensure null termination */
84         state->request.data.auth.pass[sizeof(state->request.data.auth.pass)-1]='\0';
85
86         DEBUG(3, ("[%5lu]: pam auth %s\n", (unsigned long)state->pid,
87                   state->request.data.auth.user));
88
89         if (!(mem_ctx = talloc_init("winbind pam auth for %s", state->request.data.auth.user))) {
90                 DEBUG(0, ("winbindd_pam_auth: could not talloc_init()!\n"));
91                 result = NT_STATUS_NO_MEMORY;
92                 goto done;
93         }
94
95         /* Parse domain and username */
96         
97         parse_domain_user(state->request.data.auth.user, name_domain, name_user);
98
99         /* do password magic */
100         
101         generate_random_buffer(chal, 8, False);
102         SMBencrypt(state->request.data.auth.pass, chal, local_lm_response);
103                 
104         SMBNTencrypt(state->request.data.auth.pass, chal, local_nt_response);
105
106         lm_resp = data_blob_talloc(mem_ctx, local_lm_response, sizeof(local_lm_response));
107         nt_resp = data_blob_talloc(mem_ctx, local_nt_response, sizeof(local_nt_response));
108         
109         /* what domain should we contact? */
110         
111         if ( IS_DC ) {
112                 if (!(contact_domain = find_domain_from_name(name_domain))) {
113                         DEBUG(3, ("Authentication for domain for [%s] -> [%s]\\[%s] failed as %s is not a trusted domain\n", 
114                                   state->request.data.auth.user, name_domain, name_user, name_domain)); 
115                         result = NT_STATUS_NO_SUCH_USER;
116                         goto done;
117                 }
118                 
119         } else {
120                 if (is_myname(name_domain)) {
121                         DEBUG(3, ("Authentication for domain %s (local domain to this server) not supported at this stage\n", name_domain));
122                         result =  NT_STATUS_NO_SUCH_USER;
123                         goto done;
124                 }
125
126                 if (!(contact_domain = find_our_domain())) {
127                         DEBUG(1, ("Authenticatoin for [%s] -> [%s]\\[%s] in our domain failed - we can't find our domain!\n", 
128                                   state->request.data.auth.user, name_domain, name_user)); 
129                         result = NT_STATUS_NO_SUCH_USER;
130                         goto done;
131                 }
132         }
133
134         if ( !get_trust_pw(contact_domain->name, trust_passwd, &last_change_time, &sec_channel_type) ) {
135                 result = NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
136                 goto done;
137         }
138
139         /* check authentication loop */
140
141         do {
142                 ZERO_STRUCT(info3);
143                 ZERO_STRUCT(ret_creds);
144                 retry = False;
145         
146                 /* Don't shut this down - it belongs to the connection cache code */
147                 result = cm_get_netlogon_cli(contact_domain, trust_passwd, 
148                                              sec_channel_type, False, &cli);
149
150                 if (!NT_STATUS_IS_OK(result)) {
151                         DEBUG(3, ("could not open handle to NETLOGON pipe\n"));
152                         goto done;
153                 }
154
155                 result = cli_netlogon_sam_network_logon(cli, mem_ctx,
156                                                         &ret_creds,
157                                                         name_user, name_domain, 
158                                                         global_myname(), chal, 
159                                                         lm_resp, nt_resp,
160                                                         &info3);
161                 attempts += 1;
162                 
163                 /* We have to try a second time as cm_get_netlogon_cli
164                    might not yet have noticed that the DC has killed
165                    our connection. */
166
167                 if ( cli->fd == -1 ) {
168                         retry = True;
169                         continue;
170                 } 
171                 
172                 /* if we get access denied, a possible cuase was that we had and open
173                    connection to the DC, but someone changed our machine account password
174                    out from underneath us using 'net rpc changetrustpw' */
175                    
176                 if ( NT_STATUS_V(result) == NT_STATUS_V(NT_STATUS_ACCESS_DENIED) ) {
177                         DEBUG(3,("winbindd_pam_auth: sam_logon returned ACCESS_DENIED.  Maybe the trust account "
178                                 "password was changed and we didn't know it.  Killing connections to domain %s\n",
179                                 name_domain));
180                         winbindd_cm_flush();
181                         retry = True;
182                         cli = NULL;
183                 } 
184                 
185         } while ( (attempts < 2) && retry );
186         
187         clnt_deal_with_creds(cli->sess_key, &(cli->clnt_cred), &ret_creds);
188         
189         if (NT_STATUS_IS_OK(result)) {
190                 netsamlogon_cache_store( cli->mem_ctx, &info3 );
191                 wcache_invalidate_samlogon(find_domain_from_name(name_domain), &info3);
192         }
193    
194 done:
195         /* give us a more useful (more correct?) error code */
196         if ((NT_STATUS_EQUAL(result, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND) || (NT_STATUS_EQUAL(result, NT_STATUS_UNSUCCESSFUL)))) {
197                 result = NT_STATUS_NO_LOGON_SERVERS;
198         }
199         
200         state->response.data.auth.nt_status = NT_STATUS_V(result);
201         fstrcpy(state->response.data.auth.nt_status_string, nt_errstr(result));
202
203         /* we might have given a more useful error above */
204         if (!*state->response.data.auth.error_string) 
205                 fstrcpy(state->response.data.auth.error_string, get_friendly_nt_error_msg(result));
206         state->response.data.auth.pam_error = nt_status_to_pam(result);
207
208         DEBUG(NT_STATUS_IS_OK(result) ? 5 : 2, ("Plain-text authentication for user %s returned %s (PAM: %d)\n", 
209               state->request.data.auth.user, 
210               state->response.data.auth.nt_status_string,
211               state->response.data.auth.pam_error));          
212
213         if (mem_ctx) 
214                 talloc_destroy(mem_ctx);
215         
216         return NT_STATUS_IS_OK(result) ? WINBINDD_OK : WINBINDD_ERROR;
217 }
218
219 /**********************************************************************
220  Challenge Response Authentication Protocol 
221 **********************************************************************/
222
223 enum winbindd_result winbindd_pam_auth_crap(struct winbindd_cli_state *state) 
224 {
225         NTSTATUS result;
226         unsigned char trust_passwd[16];
227         time_t last_change_time;
228         uint32 sec_channel_type;
229         NET_USER_INFO_3 info3;
230         struct cli_state *cli = NULL;
231         TALLOC_CTX *mem_ctx = NULL;
232         char *name_user = NULL;
233         const char *name_domain = NULL;
234         const char *workstation;
235         struct winbindd_domain *contact_domain;
236         DOM_CRED ret_creds;
237         int attempts = 0;
238         BOOL retry;
239
240         DATA_BLOB lm_resp, nt_resp;
241
242         if (!state->privileged) {
243                 char *error_string = NULL;
244                 DEBUG(2, ("winbindd_pam_auth_crap: non-privileged access denied.  !\n"));
245                 DEBUGADD(2, ("winbindd_pam_auth_crap: Ensure permissions on %s are set correctly.\n", 
246                              get_winbind_priv_pipe_dir()));
247                 /* send a better message than ACCESS_DENIED */
248                 asprintf(&error_string, "winbind client not authorized to use winbindd_pam_auth_crap.  Ensure permissions on %s are set correctly.",
249                          get_winbind_priv_pipe_dir());
250                 push_utf8_fstring(state->response.data.auth.error_string, error_string);
251                 SAFE_FREE(error_string);
252                 result =  NT_STATUS_ACCESS_DENIED;
253                 goto done;
254         }
255
256         /* Ensure null termination */
257         state->request.data.auth_crap.user[sizeof(state->request.data.auth_crap.user)-1]=0;
258         state->request.data.auth_crap.domain[sizeof(state->request.data.auth_crap.domain)-1]=0;
259
260         if (!(mem_ctx = talloc_init("winbind pam auth crap for (utf8) %s", state->request.data.auth_crap.user))) {
261                 DEBUG(0, ("winbindd_pam_auth_crap: could not talloc_init()!\n"));
262                 result = NT_STATUS_NO_MEMORY;
263                 goto done;
264         }
265
266         if (pull_utf8_talloc(mem_ctx, &name_user, state->request.data.auth_crap.user) == (size_t)-1) {
267                 DEBUG(0, ("winbindd_pam_auth_crap: pull_utf8_talloc failed!\n"));
268                 result = NT_STATUS_UNSUCCESSFUL;
269                 goto done;
270         }
271
272         if (*state->request.data.auth_crap.domain) {
273                 char *dom = NULL;
274                 if (pull_utf8_talloc(mem_ctx, &dom, state->request.data.auth_crap.domain) == (size_t)-1) {
275                         DEBUG(0, ("winbindd_pam_auth_crap: pull_utf8_talloc failed!\n"));
276                         result = NT_STATUS_UNSUCCESSFUL;
277                         goto done;
278                 }
279                 name_domain = dom;
280         } else if (lp_winbind_use_default_domain()) {
281                 name_domain = lp_workgroup();
282         } else {
283                 DEBUG(5,("no domain specified with username (%s) - failing auth\n", 
284                          name_user));
285                 result = NT_STATUS_NO_SUCH_USER;
286                 goto done;
287         }
288
289         DEBUG(3, ("[%5lu]: pam auth crap domain: %s user: %s\n", (unsigned long)state->pid,
290                   name_domain, name_user));
291            
292         if (*state->request.data.auth_crap.workstation) {
293                 char *wrk = NULL;
294                 if (pull_utf8_talloc(mem_ctx, &wrk, state->request.data.auth_crap.workstation) == (size_t)-1) {
295                         DEBUG(0, ("winbindd_pam_auth_crap: pull_utf8_talloc failed!\n"));
296                         result = NT_STATUS_UNSUCCESSFUL;
297                         goto done;
298                 }
299                 workstation = wrk;
300         } else {
301                 workstation = global_myname();
302         }
303
304         if (state->request.data.auth_crap.lm_resp_len > sizeof(state->request.data.auth_crap.lm_resp)
305                 || state->request.data.auth_crap.nt_resp_len > sizeof(state->request.data.auth_crap.nt_resp)) {
306                 DEBUG(0, ("winbindd_pam_auth_crap: invalid password length %u/%u\n", 
307                           state->request.data.auth_crap.lm_resp_len, 
308                           state->request.data.auth_crap.nt_resp_len));
309                 result = NT_STATUS_INVALID_PARAMETER;
310                 goto done;
311         }
312
313         lm_resp = data_blob_talloc(mem_ctx, state->request.data.auth_crap.lm_resp, state->request.data.auth_crap.lm_resp_len);
314         nt_resp = data_blob_talloc(mem_ctx, state->request.data.auth_crap.nt_resp, state->request.data.auth_crap.nt_resp_len);
315         
316         /* what domain should we contact? */
317         
318
319         /* what domain should we contact? */
320         
321         if ( IS_DC ) {
322                 if (!(contact_domain = find_domain_from_name(name_domain))) {
323                         DEBUG(3, ("Authentication for domain for [%s] -> [%s]\\[%s] failed as %s is not a trusted domain\n", 
324                                   state->request.data.auth.user, name_domain, name_user, name_domain)); 
325                         result = NT_STATUS_NO_SUCH_USER;
326                         goto done;
327                 }
328                 
329         } else {
330                 if (is_myname(name_domain)) {
331                         DEBUG(3, ("Authentication for domain %s (local domain to this server) not supported at this stage\n", name_domain));
332                         result =  NT_STATUS_NO_SUCH_USER;
333                         goto done;
334                 }
335
336                 if (!(contact_domain = find_our_domain())) {
337                         DEBUG(1, ("Authenticatoin for [%s] -> [%s]\\[%s] in our domain failed - we can't find our domain!\n", 
338                                   state->request.data.auth.user, name_domain, name_user)); 
339                         result = NT_STATUS_NO_SUCH_USER;
340                         goto done;
341                 }
342         }
343                 
344         if ( !get_trust_pw(contact_domain->name, trust_passwd, &last_change_time, &sec_channel_type) ) {
345                 result = NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
346                 goto done;
347         }
348
349         do {
350                 ZERO_STRUCT(info3);
351                 ZERO_STRUCT(ret_creds);
352                 retry = False;
353
354                 /* Don't shut this down - it belongs to the connection cache code */
355                 result = cm_get_netlogon_cli(contact_domain, trust_passwd, sec_channel_type, False, &cli);
356
357                 if (!NT_STATUS_IS_OK(result)) {
358                         DEBUG(3, ("could not open handle to NETLOGON pipe (error: %s)\n",
359                                   nt_errstr(result)));
360                         goto done;
361                 }
362
363                 result = cli_netlogon_sam_network_logon(cli, mem_ctx,
364                                                         &ret_creds,
365                                                         name_user, name_domain,
366                                                         workstation,
367                                                         state->request.data.auth_crap.chal, 
368                                                         lm_resp, nt_resp, 
369                                                         &info3);
370
371                 attempts += 1;
372
373                 /* We have to try a second time as cm_get_netlogon_cli
374                    might not yet have noticed that the DC has killed
375                    our connection. */
376
377                 if ( cli->fd == -1 ) {
378                         retry = True;
379                         continue;
380                 } 
381
382                 /* if we get access denied, a possible cause was that we had and open
383                    connection to the DC, but someone changed our machine account password
384                    out from underneath us using 'net rpc changetrustpw' */
385                    
386                 if ( NT_STATUS_V(result) == NT_STATUS_V(NT_STATUS_ACCESS_DENIED) ) {
387                         DEBUG(3,("winbindd_pam_auth_crap: sam_logon returned ACCESS_DENIED.  Maybe the trust account "
388                                 "password was changed and we didn't know it.  Killing connections to domain %s\n",
389                                 contact_domain->name));
390                         winbindd_cm_flush();
391                         retry = True;
392                         cli = NULL;
393                 } 
394                 
395         } while ( (attempts < 2) && retry );
396
397         clnt_deal_with_creds(cli->sess_key, &(cli->clnt_cred), &ret_creds);
398         
399         if (NT_STATUS_IS_OK(result)) {
400                 netsamlogon_cache_store( cli->mem_ctx, &info3 );
401                 wcache_invalidate_samlogon(find_domain_from_name(name_domain), &info3);
402                 
403                 if (state->request.flags & WBFLAG_PAM_INFO3_NDR) {
404                         result = append_info3_as_ndr(mem_ctx, state, &info3);
405                 } else if (state->request.flags & WBFLAG_PAM_UNIX_NAME) {
406                         /* ntlm_auth should return the unix username, per 
407                            'winbind use default domain' settings and the like */
408                         
409                         fstring username_out;
410                         const char *nt_username, *nt_domain;
411                         if (!(nt_username = unistr2_tdup(mem_ctx, &(info3.uni_user_name)))) {
412                                 /* If the server didn't give us one, just use the one we sent them */
413                                 nt_username = name_user;
414                         }
415                         
416                         if (!(nt_domain = unistr2_tdup(mem_ctx, &(info3.uni_logon_dom)))) {
417                                 /* If the server didn't give us one, just use the one we sent them */
418                                 nt_domain = name_domain;
419                         }
420
421                         fill_domain_username(username_out, nt_domain, nt_username);
422
423                         DEBUG(5, ("Setting unix username to [%s]\n", username_out));
424
425                         /* this interface is in UTF8 */
426                         if (push_utf8_allocate((char **)&state->response.extra_data, username_out) == -1) {
427                                 result = NT_STATUS_NO_MEMORY;
428                                 goto done;
429                         }
430                         state->response.length +=  strlen(state->response.extra_data)+1;
431                 }
432                 
433                 if (state->request.flags & WBFLAG_PAM_NTKEY) {
434                         memcpy(state->response.data.auth.nt_session_key, info3.user_sess_key, sizeof(state->response.data.auth.nt_session_key) /* 16 */);
435                 }
436                 if (state->request.flags & WBFLAG_PAM_LMKEY) {
437                         memcpy(state->response.data.auth.first_8_lm_hash, info3.padding, sizeof(state->response.data.auth.first_8_lm_hash) /* 8 */);
438                 }
439         }
440
441 done:
442         /* give us a more useful (more correct?) error code */
443         if ((NT_STATUS_EQUAL(result, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND) || (NT_STATUS_EQUAL(result, NT_STATUS_UNSUCCESSFUL)))) {
444                 result = NT_STATUS_NO_LOGON_SERVERS;
445         }
446         
447         state->response.data.auth.nt_status = NT_STATUS_V(result);
448         push_utf8_fstring(state->response.data.auth.nt_status_string, nt_errstr(result));
449         
450         /* we might have given a more useful error above */
451         if (!*state->response.data.auth.error_string) 
452                 push_utf8_fstring(state->response.data.auth.error_string, get_friendly_nt_error_msg(result));
453         state->response.data.auth.pam_error = nt_status_to_pam(result);
454
455         DEBUG(NT_STATUS_IS_OK(result) ? 5 : 2, 
456               ("NTLM CRAP authentication for user [%s]\\[%s] returned %s (PAM: %d)\n", 
457                name_domain,
458                name_user,
459                state->response.data.auth.nt_status_string,
460                state->response.data.auth.pam_error));         
461
462         if (mem_ctx) 
463                 talloc_destroy(mem_ctx);
464         
465         return NT_STATUS_IS_OK(result) ? WINBINDD_OK : WINBINDD_ERROR;
466 }
467
468 /* Change a user password */
469
470 enum winbindd_result winbindd_pam_chauthtok(struct winbindd_cli_state *state)
471 {
472         NTSTATUS result;
473         char *oldpass, *newpass;
474         fstring domain, user;
475         CLI_POLICY_HND *hnd;
476         TALLOC_CTX *mem_ctx;
477         struct winbindd_domain *contact_domain;
478
479         DEBUG(3, ("[%5lu]: pam chauthtok %s\n", (unsigned long)state->pid,
480                 state->request.data.chauthtok.user));
481
482         if (!(mem_ctx = talloc_init("winbind password change for (utf8) %s", 
483                                     state->request.data.chauthtok.user))) {
484                 DEBUG(0, ("winbindd_pam_auth_crap: could not talloc_init()!\n"));
485                 result = NT_STATUS_NO_MEMORY;
486                 goto done;
487         }
488
489         /* Setup crap */
490
491         if (state == NULL)
492                 return WINBINDD_ERROR;
493
494         parse_domain_user(state->request.data.chauthtok.user, domain, user);
495
496         if (!(contact_domain = find_domain_from_name(domain))) {
497                 DEBUG(3, ("Cannot change password for [%s] -> [%s]\\[%s] as %s is not a trusted domain\n", 
498                           state->request.data.chauthtok.user, domain, user, domain)); 
499                 result = NT_STATUS_NO_SUCH_USER;
500                 goto done;
501         }
502
503         /* Change password */
504
505         oldpass = state->request.data.chauthtok.oldpass;
506         newpass = state->request.data.chauthtok.newpass;
507
508         /* Get sam handle */
509
510         if ( NT_STATUS_IS_ERR(result = cm_get_sam_handle(contact_domain, &hnd)) ) {
511                 DEBUG(1, ("could not get SAM handle on DC for %s\n", domain));
512                 goto done;
513         }
514
515         if (!cli_oem_change_password(hnd->cli, user, newpass, oldpass)) {
516                 DEBUG(1, ("password change failed for user %s/%s\n", domain, 
517                           user));
518                 result = NT_STATUS_WRONG_PASSWORD;
519         } else {
520                 result = NT_STATUS_OK;
521         }
522
523 done:    
524         state->response.data.auth.nt_status = NT_STATUS_V(result);
525         fstrcpy(state->response.data.auth.nt_status_string, nt_errstr(result));
526         fstrcpy(state->response.data.auth.error_string, nt_errstr(result));
527         state->response.data.auth.pam_error = nt_status_to_pam(result);
528
529         DEBUG(NT_STATUS_IS_OK(result) ? 5 : 2, 
530               ("Password change for user [%s]\\[%s] returned %s (PAM: %d)\n", 
531                domain,
532                user,
533                state->response.data.auth.nt_status_string,
534                state->response.data.auth.pam_error));         
535
536         if (mem_ctx)
537                 talloc_destroy(mem_ctx);
538
539         return NT_STATUS_IS_OK(result) ? WINBINDD_OK : WINBINDD_ERROR;
540 }