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