ntlmssp: Move ntlmssp code to auth/ntlmssp
[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 "../auth/ntlmssp/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                                       lp_netbios_name(),
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         TALLOC_FREE(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         NTSTATUS result = NT_STATUS_NOT_SUPPORTED;
172         struct WINBINDD_MEMORY_CREDS *entry;
173         DATA_BLOB initial, challenge, auth;
174         uint32 initial_blob_len, challenge_blob_len, extra_len;
175
176         /* Ensure null termination */
177         state->request->data.ccache_ntlm_auth.user[
178                         sizeof(state->request->data.ccache_ntlm_auth.user)-1]='\0';
179
180         DEBUG(3, ("[%5lu]: perform NTLM auth on behalf of user %s\n", (unsigned long)state->pid,
181                 state->request->data.ccache_ntlm_auth.user));
182
183         /* Parse domain and username */
184
185         if (!canonicalize_username(state->request->data.ccache_ntlm_auth.user,
186                                 name_domain, name_user)) {
187                 DEBUG(5,("winbindd_ccache_ntlm_auth: cannot parse domain and user from name [%s]\n",
188                         state->request->data.ccache_ntlm_auth.user));
189                 request_error(state);
190                 return;
191         }
192
193         domain = find_auth_domain(state->request->flags, name_domain);
194
195         if (domain == NULL) {
196                 DEBUG(5,("winbindd_ccache_ntlm_auth: can't get domain [%s]\n",
197                         name_domain));
198                 request_error(state);
199                 return;
200         }
201
202         if (!check_client_uid(state, state->request->data.ccache_ntlm_auth.uid)) {
203                 request_error(state);
204                 return;
205         }
206
207         /* validate blob lengths */
208         initial_blob_len = state->request->data.ccache_ntlm_auth.initial_blob_len;
209         challenge_blob_len = state->request->data.ccache_ntlm_auth.challenge_blob_len;
210         extra_len = state->request->extra_len;
211
212         if (initial_blob_len > extra_len || challenge_blob_len > extra_len ||
213                 initial_blob_len + challenge_blob_len > extra_len ||
214                 initial_blob_len + challenge_blob_len < initial_blob_len ||
215                 initial_blob_len + challenge_blob_len < challenge_blob_len) {
216
217                 DEBUG(10,("winbindd_dual_ccache_ntlm_auth: blob lengths overrun "
218                         "or wrap. Buffer [%d+%d > %d]\n",
219                         initial_blob_len,
220                         challenge_blob_len,
221                         extra_len));
222                 goto process_result;
223         }
224
225         /* Parse domain and username */
226         if (!parse_domain_user(state->request->data.ccache_ntlm_auth.user, name_domain, name_user)) {
227                 DEBUG(10,("winbindd_dual_ccache_ntlm_auth: cannot parse "
228                         "domain and user from name [%s]\n",
229                         state->request->data.ccache_ntlm_auth.user));
230                 goto process_result;
231         }
232
233         entry = find_memory_creds_by_name(state->request->data.ccache_ntlm_auth.user);
234         if (entry == NULL || entry->nt_hash == NULL || entry->lm_hash == NULL) {
235                 DEBUG(10,("winbindd_dual_ccache_ntlm_auth: could not find "
236                         "credentials for user %s\n", 
237                         state->request->data.ccache_ntlm_auth.user));
238                 goto process_result;
239         }
240
241         DEBUG(10,("winbindd_dual_ccache_ntlm_auth: found ccache [%s]\n", entry->username));
242
243         if (!client_can_access_ccache_entry(state->request->data.ccache_ntlm_auth.uid, entry)) {
244                 goto process_result;
245         }
246
247         if (initial_blob_len == 0 && challenge_blob_len == 0) {
248                 /* this is just a probe to see if credentials are available. */
249                 result = NT_STATUS_OK;
250                 state->response->data.ccache_ntlm_auth.auth_blob_len = 0;
251                 goto process_result;
252         }
253
254         initial = data_blob_const(state->request->extra_data.data,
255                                   initial_blob_len);
256         challenge = data_blob_const(
257                 state->request->extra_data.data + initial_blob_len,
258                 state->request->data.ccache_ntlm_auth.challenge_blob_len);
259
260         result = do_ntlm_auth_with_hashes(
261                 name_user, name_domain, entry->lm_hash, entry->nt_hash,
262                 initial, challenge, &auth,
263                 state->response->data.ccache_ntlm_auth.session_key);
264
265         if (!NT_STATUS_IS_OK(result)) {
266                 goto process_result;
267         }
268
269         state->response->extra_data.data = talloc_memdup(
270                 state->mem_ctx, auth.data, auth.length);
271         if (!state->response->extra_data.data) {
272                 result = NT_STATUS_NO_MEMORY;
273                 goto process_result;
274         }
275         state->response->length += auth.length;
276         state->response->data.ccache_ntlm_auth.auth_blob_len = auth.length;
277
278         data_blob_free(&auth);
279
280   process_result:
281         if (!NT_STATUS_IS_OK(result)) {
282                 request_error(state);
283                 return;
284         }
285         request_ok(state);
286 }
287
288 void winbindd_ccache_save(struct winbindd_cli_state *state)
289 {
290         struct winbindd_domain *domain;
291         fstring name_domain, name_user;
292         NTSTATUS status;
293
294         /* Ensure null termination */
295         state->request->data.ccache_save.user[
296                 sizeof(state->request->data.ccache_save.user)-1]='\0';
297         state->request->data.ccache_save.pass[
298                 sizeof(state->request->data.ccache_save.pass)-1]='\0';
299
300         DEBUG(3, ("[%5lu]: save password of user %s\n",
301                   (unsigned long)state->pid,
302                   state->request->data.ccache_save.user));
303
304         /* Parse domain and username */
305
306         if (!canonicalize_username(state->request->data.ccache_save.user,
307                                    name_domain, name_user)) {
308                 DEBUG(5,("winbindd_ccache_save: cannot parse domain and user "
309                          "from name [%s]\n",
310                          state->request->data.ccache_save.user));
311                 request_error(state);
312                 return;
313         }
314
315         /*
316          * The domain is checked here only for compatibility
317          * reasons. We used to do the winbindd memory ccache for
318          * ntlm_auth in the domain child. With that code, we had to
319          * make sure that we do have a domain around to send this
320          * to. Now we do the memory cache in the parent winbindd,
321          * where it would not matter if we have a domain or not.
322          */
323
324         domain = find_auth_domain(state->request->flags, name_domain);
325         if (domain == NULL) {
326                 DEBUG(5, ("winbindd_ccache_save: can't get domain [%s]\n",
327                           name_domain));
328                 request_error(state);
329                 return;
330         }
331
332         if (!check_client_uid(state, state->request->data.ccache_save.uid)) {
333                 request_error(state);
334                 return;
335         }
336
337         status = winbindd_add_memory_creds(
338                 state->request->data.ccache_save.user,
339                 state->request->data.ccache_save.uid,
340                 state->request->data.ccache_save.pass);
341
342         if (!NT_STATUS_IS_OK(status)) {
343                 DEBUG(1, ("winbindd_add_memory_creds failed %s\n",
344                           nt_errstr(status)));
345                 request_error(state);
346                 return;
347         }
348         request_ok(state);
349 }