This commit was manufactured by cvs2svn to create branch 'SAMBA_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 /* Return a password structure from a username.  */
57
58 enum winbindd_result winbindd_pam_auth(struct winbindd_cli_state *state) 
59 {
60         NTSTATUS result;
61         fstring name_domain, name_user;
62         unsigned char trust_passwd[16];
63         time_t last_change_time;
64         uint32 sec_channel_type;
65         NET_USER_INFO_3 info3;
66         struct cli_state *cli = NULL;
67         uchar chal[8];
68         TALLOC_CTX *mem_ctx = NULL;
69         DATA_BLOB lm_resp;
70         DATA_BLOB nt_resp;
71         DOM_CRED ret_creds;
72         int attempts = 0;
73
74         /* Ensure null termination */
75         state->request.data.auth.user[sizeof(state->request.data.auth.user)-1]='\0';
76
77         /* Ensure null termination */
78         state->request.data.auth.pass[sizeof(state->request.data.auth.pass)-1]='\0';
79
80         DEBUG(3, ("[%5d]: pam auth %s\n", state->pid,
81                   state->request.data.auth.user));
82
83         if (!(mem_ctx = talloc_init("winbind pam auth for %s", state->request.data.auth.user))) {
84                 DEBUG(0, ("winbindd_pam_auth: could not talloc_init()!\n"));
85                 result = NT_STATUS_NO_MEMORY;
86                 goto done;
87         }
88
89         /* Parse domain and username */
90         
91         if (!parse_domain_user(state->request.data.auth.user, name_domain, 
92                                name_user)) {
93                 DEBUG(5,("no domain separator (%s) in username (%s) - failing auth\n", lp_winbind_separator(), state->request.data.auth.user));
94                 result = NT_STATUS_INVALID_PARAMETER;
95                 goto done;
96         }
97
98         {
99                 unsigned char local_lm_response[24];
100                 unsigned char local_nt_response[24];
101                 
102                 generate_random_buffer(chal, 8, False);
103                 SMBencrypt(state->request.data.auth.pass, chal, local_lm_response);
104                 
105                 SMBNTencrypt(state->request.data.auth.pass, chal, local_nt_response);
106
107                 lm_resp = data_blob_talloc(mem_ctx, local_lm_response, sizeof(local_lm_response));
108                 nt_resp = data_blob_talloc(mem_ctx, local_nt_response, sizeof(local_nt_response));
109         }
110         
111         /*
112          * Get the machine account password for our primary domain
113          */
114
115         if (!secrets_fetch_trust_account_password(
116                 lp_workgroup(), trust_passwd, &last_change_time,
117                 &sec_channel_type)) {
118                 DEBUG(0, ("winbindd_pam_auth: could not fetch trust account "
119                           "password for domain %s\n", lp_workgroup()));
120                 result = NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
121                 goto done;
122         }
123
124         do {
125                 ZERO_STRUCT(info3);
126                 ZERO_STRUCT(ret_creds);
127         
128                 /* Don't shut this down - it belongs to the connection cache code */
129                 result = cm_get_netlogon_cli(lp_workgroup(), trust_passwd, 
130                                              sec_channel_type, False, &cli);
131
132                 if (!NT_STATUS_IS_OK(result)) {
133                         DEBUG(3, ("could not open handle to NETLOGON pipe\n"));
134                         goto done;
135                 }
136
137                 result = cli_netlogon_sam_network_logon(cli, mem_ctx,
138                                                         &ret_creds,
139                                                         name_user, name_domain, 
140                                                         global_myname(), chal, 
141                                                         lm_resp, nt_resp,
142                                                         &info3);
143                 attempts += 1;
144
145                 /* We have to try a second time as cm_get_netlogon_cli
146                    might not yet have noticed that the DC has killed
147                    our connection. */
148
149         } while ( (attempts < 2) && (cli->fd == -1) );
150
151         
152         clnt_deal_with_creds(cli->sess_key, &(cli->clnt_cred), &ret_creds);
153         
154         uni_group_cache_store_netlogon(mem_ctx, &info3);
155 done:
156         
157         /* give us a more useful (more correct?) error code */
158         if ((NT_STATUS_EQUAL(result, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND) || (NT_STATUS_EQUAL(result, NT_STATUS_UNSUCCESSFUL)))) {
159                 result = NT_STATUS_NO_LOGON_SERVERS;
160         }
161         
162         state->response.data.auth.nt_status = NT_STATUS_V(result);
163         fstrcpy(state->response.data.auth.nt_status_string, nt_errstr(result));
164         fstrcpy(state->response.data.auth.error_string, get_friendly_nt_error_msg(result));
165         state->response.data.auth.pam_error = nt_status_to_pam(result);
166
167         DEBUG(NT_STATUS_IS_OK(result) ? 5 : 2, ("Plain-text authentication for user %s returned %s (PAM: %d)\n", 
168               state->request.data.auth.user, 
169               state->response.data.auth.nt_status_string,
170               state->response.data.auth.pam_error));          
171
172         if (mem_ctx) 
173                 talloc_destroy(mem_ctx);
174         
175         return NT_STATUS_IS_OK(result) ? WINBINDD_OK : WINBINDD_ERROR;
176 }
177         
178 /* Challenge Response Authentication Protocol */
179
180 enum winbindd_result winbindd_pam_auth_crap(struct winbindd_cli_state *state) 
181 {
182         NTSTATUS result;
183         unsigned char trust_passwd[16];
184         time_t last_change_time;
185         uint32 sec_channel_type;
186         NET_USER_INFO_3 info3;
187         struct cli_state *cli = NULL;
188         TALLOC_CTX *mem_ctx = NULL;
189         char *user = NULL;
190         const char *domain = NULL;
191         const char *contact_domain;
192         const char *workstation;
193         DOM_CRED ret_creds;
194         int attempts = 0;
195
196         DATA_BLOB lm_resp, nt_resp;
197
198         if (!state->privileged) {
199                 DEBUG(2, ("winbindd_pam_auth_crap: non-privileged access denied!\n"));
200                 /* send a better message than ACCESS_DENIED */
201                 push_utf8_fstring(state->response.data.auth.error_string, "winbind client not authorized to use winbindd_pam_auth_crap");
202                 result =  NT_STATUS_ACCESS_DENIED;
203                 goto done;
204         }
205
206         /* Ensure null termination */
207         state->request.data.auth_crap.user[sizeof(state->request.data.auth_crap.user)-1]='\0';
208
209         /* Ensure null termination */
210         state->request.data.auth_crap.domain[sizeof(state->request.data.auth_crap.domain)-1]='\0';
211
212         if (!(mem_ctx = talloc_init("winbind pam auth crap for (utf8) %s", state->request.data.auth_crap.user))) {
213                 DEBUG(0, ("winbindd_pam_auth_crap: could not talloc_init()!\n"));
214                 result = NT_STATUS_NO_MEMORY;
215                 goto done;
216         }
217
218         if (pull_utf8_talloc(mem_ctx, &user, state->request.data.auth_crap.user) == (size_t)-1) {
219                 DEBUG(0, ("winbindd_pam_auth_crap: pull_utf8_talloc failed!\n"));
220         }
221
222         if (*state->request.data.auth_crap.domain) {
223                 char *dom = NULL;
224                 if (pull_utf8_talloc(mem_ctx, &dom, state->request.data.auth_crap.domain) == (size_t)-1) {
225                         DEBUG(0, ("winbindd_pam_auth_crap: pull_utf8_talloc failed!\n"));
226                 }
227                 domain = dom;
228         } else if (lp_winbind_use_default_domain()) {
229                 domain = lp_workgroup();
230         } else {
231                 DEBUG(5,("no domain specified with username (%s) - failing auth\n", 
232                          user));
233                 result = NT_STATUS_INVALID_PARAMETER;
234                 goto done;
235         }
236
237         DEBUG(3, ("[%5d]: pam auth crap domain: %s user: %s\n", state->pid,
238                   domain, user));
239
240         if (lp_allow_trusted_domains() && (state->request.data.auth_crap.flags & WINBIND_PAM_CONTACT_TRUSTDOM)) {
241                 contact_domain = domain;
242         } else {
243                 contact_domain = lp_workgroup();
244         }
245
246         if (*state->request.data.auth_crap.workstation) {
247                 char *wrk = NULL;
248                 if (pull_utf8_talloc(mem_ctx, &wrk, state->request.data.auth_crap.workstation) == (size_t)-1) {
249                         DEBUG(0, ("winbindd_pam_auth_crap: pull_utf8_talloc failed!\n"));
250                 }
251                 workstation = wrk;
252         } else {
253                 workstation = global_myname();
254         }
255
256         if (state->request.data.auth_crap.lm_resp_len > sizeof(state->request.data.auth_crap.lm_resp)
257                 || state->request.data.auth_crap.nt_resp_len > sizeof(state->request.data.auth_crap.nt_resp)) {
258                 DEBUG(0, ("winbindd_pam_auth_crap: invalid password length %u/%u\n", 
259                           state->request.data.auth_crap.lm_resp_len, 
260                           state->request.data.auth_crap.nt_resp_len));
261                 result = NT_STATUS_INVALID_PARAMETER;
262                 goto done;
263         }
264
265         lm_resp = data_blob_talloc(mem_ctx, state->request.data.auth_crap.lm_resp, state->request.data.auth_crap.lm_resp_len);
266         nt_resp = data_blob_talloc(mem_ctx, state->request.data.auth_crap.nt_resp, state->request.data.auth_crap.nt_resp_len);
267         
268         /*
269          * Get the machine account password for the domain to contact.
270          * This is either our own domain for a workstation, or possibly
271          * any domain for a PDC with trusted domains.
272          */
273
274         if (!secrets_fetch_trust_account_password (
275                 contact_domain, trust_passwd, &last_change_time,
276                 &sec_channel_type)) {
277                 DEBUG(0, ("winbindd_pam_auth: could not fetch trust account "
278                           "password for domain %s\n", contact_domain));
279                 result = NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
280                 goto done;
281         }
282
283         do {
284                 ZERO_STRUCT(info3);
285                 ZERO_STRUCT(ret_creds);
286
287                 /* Don't shut this down - it belongs to the connection cache code */
288                 result = cm_get_netlogon_cli(contact_domain, trust_passwd,
289                                              sec_channel_type, False, &cli);
290
291                 if (!NT_STATUS_IS_OK(result)) {
292                         DEBUG(3, ("could not open handle to NETLOGON pipe (error: %s)\n",
293                                   nt_errstr(result)));
294                         goto done;
295                 }
296
297                 result = cli_netlogon_sam_network_logon(cli, mem_ctx,
298                                                         &ret_creds,
299                                                         user, domain,
300                                                         workstation,
301                                                         state->request.data.auth_crap.chal, 
302                                                         lm_resp, nt_resp, 
303                                                         &info3);
304
305                 attempts += 1;
306
307                 /* We have to try a second time as cm_get_netlogon_cli
308                    might not yet have noticed that the DC has killed
309                    our connection. */
310
311         } while ( (attempts < 2) && (cli->fd == -1) );
312
313         clnt_deal_with_creds(cli->sess_key, &(cli->clnt_cred), &ret_creds);
314         
315         if (NT_STATUS_IS_OK(result)) {
316                 uni_group_cache_store_netlogon(mem_ctx, &info3);
317                 if (state->request.data.auth_crap.flags & WINBIND_PAM_INFO3_NDR) {
318                         result = append_info3_as_ndr(mem_ctx, state, &info3);
319                 }
320
321                 if (state->request.data.auth_crap.flags & WINBIND_PAM_NTKEY) {
322                         memcpy(state->response.data.auth.nt_session_key, info3.user_sess_key, sizeof(state->response.data.auth.nt_session_key) /* 16 */);
323                 }
324                 if (state->request.data.auth_crap.flags & WINBIND_PAM_LMKEY) {
325                         memcpy(state->response.data.auth.first_8_lm_hash, info3.padding, sizeof(state->response.data.auth.first_8_lm_hash) /* 8 */);
326                 }
327         }
328
329 done:
330
331         /* give us a more useful (more correct?) error code */
332         if ((NT_STATUS_EQUAL(result, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND) || (NT_STATUS_EQUAL(result, NT_STATUS_UNSUCCESSFUL)))) {
333                 result = NT_STATUS_NO_LOGON_SERVERS;
334         }
335         
336         state->response.data.auth.nt_status = NT_STATUS_V(result);
337         push_utf8_fstring(state->response.data.auth.nt_status_string, nt_errstr(result));
338         if (!*state->response.data.auth.error_string) 
339                 push_utf8_fstring(state->response.data.auth.error_string, get_friendly_nt_error_msg(result));
340         state->response.data.auth.pam_error = nt_status_to_pam(result);
341
342         DEBUG(NT_STATUS_IS_OK(result) ? 5 : 2, 
343               ("NTLM CRAP authentication for user [%s]\\[%s] returned %s (PAM: %d)\n", 
344                domain,
345                user,
346                state->response.data.auth.nt_status_string,
347                state->response.data.auth.pam_error));         
348
349         if (mem_ctx) 
350                 talloc_destroy(mem_ctx);
351         
352         return NT_STATUS_IS_OK(result) ? WINBINDD_OK : WINBINDD_ERROR;
353 }
354
355 /* Change a user password */
356
357 enum winbindd_result winbindd_pam_chauthtok(struct winbindd_cli_state *state)
358 {
359         NTSTATUS result;
360         char *oldpass, *newpass;
361         fstring domain, user;
362         CLI_POLICY_HND *hnd;
363
364         DEBUG(3, ("[%5d]: pam chauthtok %s\n", state->pid,
365                 state->request.data.chauthtok.user));
366
367         /* Setup crap */
368
369         if (state == NULL)
370                 return WINBINDD_ERROR;
371
372         if (!parse_domain_user(state->request.data.chauthtok.user, domain, 
373                                user)) {
374                 result = NT_STATUS_INVALID_PARAMETER;
375                 goto done;
376         }
377
378         /* Change password */
379
380         oldpass = state->request.data.chauthtok.oldpass;
381         newpass = state->request.data.chauthtok.newpass;
382
383         /* Get sam handle */
384
385         if (!(hnd = cm_get_sam_handle(domain))) {
386                 DEBUG(1, ("could not get SAM handle on DC for %s\n", domain));
387                 result = NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND;
388                 goto done;
389         }
390
391         if (!cli_oem_change_password(hnd->cli, user, newpass, oldpass)) {
392                 DEBUG(1, ("password change failed for user %s/%s\n", domain, 
393                           user));
394                 result = NT_STATUS_WRONG_PASSWORD;
395         } else {
396                 result = NT_STATUS_OK;
397         }
398
399 done:    
400         state->response.data.auth.nt_status = NT_STATUS_V(result);
401         fstrcpy(state->response.data.auth.nt_status_string, nt_errstr(result));
402         fstrcpy(state->response.data.auth.error_string, nt_errstr(result));
403         state->response.data.auth.pam_error = nt_status_to_pam(result);
404
405         DEBUG(NT_STATUS_IS_OK(result) ? 5 : 2, 
406               ("Password change for user [%s]\\[%s] returned %s (PAM: %d)\n", 
407                domain,
408                user,
409                state->response.data.auth.nt_status_string,
410                state->response.data.auth.pam_error));         
411
412         return NT_STATUS_IS_OK(result) ? WINBINDD_OK : WINBINDD_ERROR;
413 }