s3:ntlmssp: pass names and use_ntlmv2 to ntlmssp_client_start() and store them
[amitay/samba.git] / source3 / winbindd / winbindd_ccache_access.c
1 /*
2    Unix SMB/CIFS implementation.
3
4    Winbind daemon - cached credentials funcions
5
6    Copyright (C) Robert O'Callahan 2006
7    Copyright (C) Jeremy Allison 2006 (minor fixes to fit into Samba and
8                                       protect against integer wrap).
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 3 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, see <http://www.gnu.org/licenses/>.
22 */
23
24 #include "includes.h"
25 #include "winbindd.h"
26 #include "ntlmssp.h"
27
28 #undef DBGC_CLASS
29 #define DBGC_CLASS DBGC_WINBIND
30
31 static bool client_can_access_ccache_entry(uid_t client_uid,
32                                         struct WINBINDD_MEMORY_CREDS *entry)
33 {
34         if (client_uid == entry->uid || client_uid == 0) {
35                 DEBUG(10, ("Access granted to uid %u\n", (unsigned int)client_uid));
36                 return True;
37         }
38
39         DEBUG(1, ("Access denied to uid %u (expected %u)\n",
40                 (unsigned int)client_uid, (unsigned int)entry->uid));
41         return False;
42 }
43
44 static NTSTATUS do_ntlm_auth_with_hashes(const char *username,
45                                         const char *domain,
46                                         const unsigned char lm_hash[LM_HASH_LEN],
47                                         const unsigned char nt_hash[NT_HASH_LEN],
48                                         const DATA_BLOB initial_msg,
49                                         const DATA_BLOB challenge_msg,
50                                         DATA_BLOB *auth_msg,
51                                         uint8_t session_key[16])
52 {
53         NTSTATUS status;
54         struct ntlmssp_state *ntlmssp_state = NULL;
55         DATA_BLOB dummy_msg, reply;
56
57         status = ntlmssp_client_start(NULL,
58                                       global_myname(),
59                                       lp_workgroup(),
60                                       lp_client_ntlmv2_auth(),
61                                       &ntlmssp_state);
62
63         if (!NT_STATUS_IS_OK(status)) {
64                 DEBUG(1, ("Could not start NTLMSSP client: %s\n",
65                         nt_errstr(status)));
66                 goto done;
67         }
68
69         status = ntlmssp_set_username(ntlmssp_state, username);
70
71         if (!NT_STATUS_IS_OK(status)) {
72                 DEBUG(1, ("Could not set username: %s\n",
73                         nt_errstr(status)));
74                 goto done;
75         }
76
77         status = ntlmssp_set_domain(ntlmssp_state, domain);
78
79         if (!NT_STATUS_IS_OK(status)) {
80                 DEBUG(1, ("Could not set domain: %s\n",
81                         nt_errstr(status)));
82                 goto done;
83         }
84
85         status = ntlmssp_set_hashes(ntlmssp_state, lm_hash, nt_hash);
86
87         if (!NT_STATUS_IS_OK(status)) {
88                 DEBUG(1, ("Could not set hashes: %s\n",
89                         nt_errstr(status)));
90                 goto done;
91         }
92
93         ntlmssp_want_feature(ntlmssp_state, NTLMSSP_FEATURE_SESSION_KEY);
94
95         /* We need to get our protocol handler into the right state. So first
96            we ask it to generate the initial message. Actually the client has already
97            sent its own initial message, so we're going to drop this one on the floor.
98            The client might have sent a different message, for example with different
99            negotiation options, but as far as I can tell this won't hurt us. (Unless
100            the client sent a different username or domain, in which case that's their
101            problem for telling us the wrong username or domain.)
102            Since we have a copy of the initial message that the client sent, we could
103            resolve any discrepancies if we had to.
104         */
105         dummy_msg = data_blob_null;
106         reply = data_blob_null;
107         status = ntlmssp_update(ntlmssp_state, dummy_msg, &reply);
108         data_blob_free(&dummy_msg);
109         data_blob_free(&reply);
110
111         if (!NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
112                 DEBUG(1, ("Failed to create initial message! [%s]\n",
113                         nt_errstr(status)));
114                 goto done;
115         }
116
117         /* Now we are ready to handle the server's actual response. */
118         status = ntlmssp_update(ntlmssp_state, challenge_msg, &reply);
119
120         if (!NT_STATUS_EQUAL(status, NT_STATUS_OK)) {
121                 DEBUG(1, ("We didn't get a response to the challenge! [%s]\n",
122                         nt_errstr(status)));
123                 data_blob_free(&reply);
124                 goto done;
125         }
126
127         if (ntlmssp_state->session_key.length != 16) {
128                 DEBUG(1, ("invalid session key length %d\n",
129                           (int)ntlmssp_state->session_key.length));
130                 data_blob_free(&reply);
131                 goto done;
132         }
133
134         *auth_msg = data_blob(reply.data, reply.length);
135         memcpy(session_key, ntlmssp_state->session_key.data, 16);
136         status = NT_STATUS_OK;
137
138 done:
139         ntlmssp_end(&ntlmssp_state);
140         return status;
141 }
142
143 static bool check_client_uid(struct winbindd_cli_state *state, uid_t uid)
144 {
145         int ret;
146         uid_t ret_uid;
147
148         ret_uid = (uid_t)-1;
149
150         ret = sys_getpeereid(state->sock, &ret_uid);
151         if (ret != 0) {
152                 DEBUG(1, ("check_client_uid: Could not get socket peer uid: %s; "
153                         "denying access\n", strerror(errno)));
154                 return False;
155         }
156
157         if (uid != ret_uid) {
158                 DEBUG(1, ("check_client_uid: Client lied about its uid: said %u, "
159                         "actually was %u; denying access\n",
160                         (unsigned int)uid, (unsigned int)ret_uid));
161                 return False;
162         }
163
164         return True;
165 }
166
167 void winbindd_ccache_ntlm_auth(struct winbindd_cli_state *state)
168 {
169         struct winbindd_domain *domain;
170         fstring name_domain, name_user;
171
172         /* Ensure null termination */
173         state->request->data.ccache_ntlm_auth.user[
174                         sizeof(state->request->data.ccache_ntlm_auth.user)-1]='\0';
175
176         DEBUG(3, ("[%5lu]: perform NTLM auth on behalf of user %s\n", (unsigned long)state->pid,
177                 state->request->data.ccache_ntlm_auth.user));
178
179         /* Parse domain and username */
180
181         if (!canonicalize_username(state->request->data.ccache_ntlm_auth.user,
182                                 name_domain, name_user)) {
183                 DEBUG(5,("winbindd_ccache_ntlm_auth: cannot parse domain and user from name [%s]\n",
184                         state->request->data.ccache_ntlm_auth.user));
185                 request_error(state);
186                 return;
187         }
188
189         domain = find_auth_domain(state->request->flags, name_domain);
190
191         if (domain == NULL) {
192                 DEBUG(5,("winbindd_ccache_ntlm_auth: can't get domain [%s]\n",
193                         name_domain));
194                 request_error(state);
195                 return;
196         }
197
198         if (!check_client_uid(state, state->request->data.ccache_ntlm_auth.uid)) {
199                 request_error(state);
200                 return;
201         }
202
203         sendto_domain(state, domain);
204 }
205
206 enum winbindd_result winbindd_dual_ccache_ntlm_auth(struct winbindd_domain *domain,
207                                                 struct winbindd_cli_state *state)
208 {
209         NTSTATUS result = NT_STATUS_NOT_SUPPORTED;
210         struct WINBINDD_MEMORY_CREDS *entry;
211         DATA_BLOB initial, challenge, auth;
212         fstring name_domain, name_user;
213         uint32 initial_blob_len, challenge_blob_len, extra_len;
214
215         /* Ensure null termination */
216         state->request->data.ccache_ntlm_auth.user[
217                 sizeof(state->request->data.ccache_ntlm_auth.user)-1]='\0';
218
219         DEBUG(3, ("winbindd_dual_ccache_ntlm_auth: [%5lu]: perform NTLM auth on "
220                 "behalf of user %s (dual)\n", (unsigned long)state->pid,
221                 state->request->data.ccache_ntlm_auth.user));
222
223         /* validate blob lengths */
224         initial_blob_len = state->request->data.ccache_ntlm_auth.initial_blob_len;
225         challenge_blob_len = state->request->data.ccache_ntlm_auth.challenge_blob_len;
226         extra_len = state->request->extra_len;
227
228         if (initial_blob_len > extra_len || challenge_blob_len > extra_len ||
229                 initial_blob_len + challenge_blob_len > extra_len ||
230                 initial_blob_len + challenge_blob_len < initial_blob_len ||
231                 initial_blob_len + challenge_blob_len < challenge_blob_len) {
232
233                 DEBUG(10,("winbindd_dual_ccache_ntlm_auth: blob lengths overrun "
234                         "or wrap. Buffer [%d+%d > %d]\n",
235                         initial_blob_len,
236                         challenge_blob_len,
237                         extra_len));
238                 goto process_result;
239         }
240
241         /* Parse domain and username */
242         if (!parse_domain_user(state->request->data.ccache_ntlm_auth.user, name_domain, name_user)) {
243                 DEBUG(10,("winbindd_dual_ccache_ntlm_auth: cannot parse "
244                         "domain and user from name [%s]\n",
245                         state->request->data.ccache_ntlm_auth.user));
246                 goto process_result;
247         }
248
249         entry = find_memory_creds_by_name(state->request->data.ccache_ntlm_auth.user);
250         if (entry == NULL || entry->nt_hash == NULL || entry->lm_hash == NULL) {
251                 DEBUG(10,("winbindd_dual_ccache_ntlm_auth: could not find "
252                         "credentials for user %s\n", 
253                         state->request->data.ccache_ntlm_auth.user));
254                 goto process_result;
255         }
256
257         DEBUG(10,("winbindd_dual_ccache_ntlm_auth: found ccache [%s]\n", entry->username));
258
259         if (!client_can_access_ccache_entry(state->request->data.ccache_ntlm_auth.uid, entry)) {
260                 goto process_result;
261         }
262
263         if (initial_blob_len == 0 && challenge_blob_len == 0) {
264                 /* this is just a probe to see if credentials are available. */
265                 result = NT_STATUS_OK;
266                 state->response->data.ccache_ntlm_auth.auth_blob_len = 0;
267                 goto process_result;
268         }
269
270         initial = data_blob_const(state->request->extra_data.data,
271                                   initial_blob_len);
272         challenge = data_blob_const(
273                 state->request->extra_data.data + initial_blob_len,
274                 state->request->data.ccache_ntlm_auth.challenge_blob_len);
275
276         result = do_ntlm_auth_with_hashes(
277                 name_user, name_domain, entry->lm_hash, entry->nt_hash,
278                 initial, challenge, &auth,
279                 state->response->data.ccache_ntlm_auth.session_key);
280
281         if (!NT_STATUS_IS_OK(result)) {
282                 goto process_result;
283         }
284
285         state->response->extra_data.data = talloc_memdup(
286                 state->mem_ctx, auth.data, auth.length);
287         if (!state->response->extra_data.data) {
288                 result = NT_STATUS_NO_MEMORY;
289                 goto process_result;
290         }
291         state->response->length += auth.length;
292         state->response->data.ccache_ntlm_auth.auth_blob_len = auth.length;
293
294         data_blob_free(&auth);
295
296   process_result:
297         return NT_STATUS_IS_OK(result) ? WINBINDD_OK : WINBINDD_ERROR;
298 }
299
300 void winbindd_ccache_save(struct winbindd_cli_state *state)
301 {
302         struct winbindd_domain *domain;
303         fstring name_domain, name_user;
304
305         /* Ensure null termination */
306         state->request->data.ccache_save.user[
307                 sizeof(state->request->data.ccache_save.user)-1]='\0';
308         state->request->data.ccache_save.pass[
309                 sizeof(state->request->data.ccache_save.pass)-1]='\0';
310
311         DEBUG(3, ("[%5lu]: save password of user %s\n",
312                   (unsigned long)state->pid,
313                   state->request->data.ccache_save.user));
314
315         /* Parse domain and username */
316
317         if (!canonicalize_username(state->request->data.ccache_ntlm_auth.user,
318                                    name_domain, name_user)) {
319                 DEBUG(5,("winbindd_ccache_save: cannot parse domain and user "
320                          "from name [%s]\n",
321                          state->request->data.ccache_save.user));
322                 request_error(state);
323                 return;
324         }
325
326         domain = find_auth_domain(state->request->flags, name_domain);
327
328         if (domain == NULL) {
329                 DEBUG(5, ("winbindd_ccache_save: can't get domain [%s]\n",
330                           name_domain));
331                 request_error(state);
332                 return;
333         }
334
335         if (!check_client_uid(state, state->request->data.ccache_save.uid)) {
336                 request_error(state);
337                 return;
338         }
339
340         sendto_domain(state, domain);
341 }
342
343 enum winbindd_result winbindd_dual_ccache_save(
344         struct winbindd_domain *domain, struct winbindd_cli_state *state)
345 {
346         NTSTATUS status = NT_STATUS_NOT_SUPPORTED;
347
348         /* Ensure null termination */
349         state->request->data.ccache_save.user[
350                 sizeof(state->request->data.ccache_save.user)-1]='\0';
351         state->request->data.ccache_save.pass[
352                 sizeof(state->request->data.ccache_save.pass)-1]='\0';
353
354         DEBUG(3, ("winbindd_dual_ccache_save: [%5lu]: save password of user "
355                   "%s\n", (unsigned long)state->pid,
356                   state->request->data.ccache_save.user));
357
358         status = winbindd_add_memory_creds(
359                 state->request->data.ccache_save.user,
360                 state->request->data.ccache_save.uid,
361                 state->request->data.ccache_save.pass);
362
363         if (!NT_STATUS_IS_OK(status)) {
364                 DEBUG(1, ("winbindd_add_memory_creds failed %s\n",
365                           nt_errstr(status)));
366                 return WINBINDD_ERROR;
367         }
368
369         return WINBINDD_OK;
370 }