ddedf6a020802c3d78c022f268370280999d0e16
[kai/samba-autobuild/.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    Copyright (C) Andrew Bartlett 2011
10
11    This program is free software; you can redistribute it and/or modify
12    it under the terms of the GNU General Public License as published by
13    the Free Software Foundation; either version 3 of the License, or
14    (at your option) any later version.
15
16    This program is distributed in the hope that it will be useful,
17    but WITHOUT ANY WARRANTY; without even the implied warranty of
18    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19    GNU General Public License for more details.
20
21    You should have received a copy of the GNU General Public License
22    along with this program.  If not, see <http://www.gnu.org/licenses/>.
23 */
24
25 #include "includes.h"
26 #include "winbindd.h"
27 #include "auth/gensec/gensec.h"
28 #include "auth_generic.h"
29
30 #undef DBGC_CLASS
31 #define DBGC_CLASS DBGC_WINBIND
32
33 static bool client_can_access_ccache_entry(uid_t client_uid,
34                                         struct WINBINDD_MEMORY_CREDS *entry)
35 {
36         if (client_uid == entry->uid || client_uid == 0) {
37                 DEBUG(10, ("Access granted to uid %u\n", (unsigned int)client_uid));
38                 return True;
39         }
40
41         DEBUG(1, ("Access denied to uid %u (expected %u)\n",
42                 (unsigned int)client_uid, (unsigned int)entry->uid));
43         return False;
44 }
45
46 static NTSTATUS do_ntlm_auth_with_stored_pw(const char *username,
47                                             const char *domain,
48                                             const char *password,
49                                             const DATA_BLOB initial_msg,
50                                             const DATA_BLOB challenge_msg,
51                                             TALLOC_CTX *mem_ctx,
52                                             DATA_BLOB *auth_msg,
53                                             uint8_t session_key[16])
54 {
55         NTSTATUS status;
56         struct auth_generic_state *auth_generic_state = NULL;
57         DATA_BLOB reply, session_key_blob;
58
59         status = auth_generic_client_prepare(mem_ctx, &auth_generic_state);
60
61         if (!NT_STATUS_IS_OK(status)) {
62                 DEBUG(1, ("Could not start NTLMSSP client: %s\n",
63                         nt_errstr(status)));
64                 goto done;
65         }
66
67         status = auth_generic_set_username(auth_generic_state, username);
68
69         if (!NT_STATUS_IS_OK(status)) {
70                 DEBUG(1, ("Could not set username: %s\n",
71                         nt_errstr(status)));
72                 goto done;
73         }
74
75         status = auth_generic_set_domain(auth_generic_state, domain);
76
77         if (!NT_STATUS_IS_OK(status)) {
78                 DEBUG(1, ("Could not set domain: %s\n",
79                         nt_errstr(status)));
80                 goto done;
81         }
82
83         status = auth_generic_set_password(auth_generic_state, password);
84
85         if (!NT_STATUS_IS_OK(status)) {
86                 DEBUG(1, ("Could not set password: %s\n",
87                         nt_errstr(status)));
88                 goto done;
89         }
90
91         if (initial_msg.length == 0) {
92                 gensec_want_feature(auth_generic_state->gensec_security,
93                                     GENSEC_FEATURE_SESSION_KEY);
94         }
95
96         status = auth_generic_client_start_by_name(auth_generic_state,
97                                                    "ntlmssp_resume_ccache");
98         if (!NT_STATUS_IS_OK(status)) {
99                 DEBUG(1, ("Could not start NTLMSSP resume mech: %s\n",
100                         nt_errstr(status)));
101                 goto done;
102         }
103
104         /*
105          * We inject the inital NEGOTIATE message our caller used
106          * in order to get the state machine into the correct possition.
107          */
108         reply = data_blob_null;
109         status = gensec_update(auth_generic_state->gensec_security,
110                                talloc_tos(), initial_msg, &reply);
111         data_blob_free(&reply);
112
113         if (!NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
114                 DEBUG(1, ("Failed to create initial message! [%s]\n",
115                         nt_errstr(status)));
116                 goto done;
117         }
118
119         /* Now we are ready to handle the server's actual response. */
120         status = gensec_update(auth_generic_state->gensec_security,
121                                mem_ctx, challenge_msg, &reply);
122         if (!NT_STATUS_EQUAL(status, NT_STATUS_OK)) {
123                 DEBUG(1, ("We didn't get a response to the challenge! [%s]\n",
124                         nt_errstr(status)));
125                 data_blob_free(&reply);
126                 goto done;
127         }
128
129         status = gensec_session_key(auth_generic_state->gensec_security,
130                                     talloc_tos(), &session_key_blob);
131         if (!NT_STATUS_EQUAL(status, NT_STATUS_OK)) {
132                 DEBUG(1, ("We didn't get the session key we requested! [%s]\n",
133                         nt_errstr(status)));
134                 data_blob_free(&reply);
135                 goto done;
136         }
137
138         if (session_key_blob.length != 16) {
139                 DEBUG(1, ("invalid session key length %d\n",
140                           (int)session_key_blob.length));
141                 data_blob_free(&reply);
142                 goto done;
143         }
144         memcpy(session_key, session_key_blob.data, 16);
145         data_blob_free(&session_key_blob);
146         *auth_msg = reply;
147         status = NT_STATUS_OK;
148
149 done:
150         TALLOC_FREE(auth_generic_state);
151         return status;
152 }
153
154 static bool check_client_uid(struct winbindd_cli_state *state, uid_t uid)
155 {
156         int ret;
157         uid_t ret_uid;
158         gid_t ret_gid;
159
160         ret_uid = (uid_t)-1;
161
162         ret = getpeereid(state->sock, &ret_uid, &ret_gid);
163         if (ret != 0) {
164                 DEBUG(1, ("check_client_uid: Could not get socket peer uid: %s; "
165                         "denying access\n", strerror(errno)));
166                 return False;
167         }
168
169         if (uid != ret_uid && ret_uid != sec_initial_uid()) {
170                 DEBUG(1, ("check_client_uid: Client lied about its uid: said %u, "
171                         "actually was %u; denying access\n",
172                         (unsigned int)uid, (unsigned int)ret_uid));
173                 return False;
174         }
175
176         return True;
177 }
178
179 void winbindd_ccache_ntlm_auth(struct winbindd_cli_state *state)
180 {
181         struct winbindd_domain *domain;
182         fstring name_domain, name_user;
183         NTSTATUS result = NT_STATUS_NOT_SUPPORTED;
184         struct WINBINDD_MEMORY_CREDS *entry;
185         DATA_BLOB initial, challenge, auth;
186         uint32_t initial_blob_len, challenge_blob_len, extra_len;
187
188         /* Ensure null termination */
189         state->request->data.ccache_ntlm_auth.user[
190                         sizeof(state->request->data.ccache_ntlm_auth.user)-1]='\0';
191
192         DEBUG(3, ("[%5lu]: perform NTLM auth on behalf of user %s\n", (unsigned long)state->pid,
193                 state->request->data.ccache_ntlm_auth.user));
194
195         /* Parse domain and username */
196
197         if (!canonicalize_username(state->request->data.ccache_ntlm_auth.user,
198                                 name_domain, name_user)) {
199                 DEBUG(5,("winbindd_ccache_ntlm_auth: cannot parse domain and user from name [%s]\n",
200                         state->request->data.ccache_ntlm_auth.user));
201                 request_error(state);
202                 return;
203         }
204
205         domain = find_auth_domain(state->request->flags, name_domain);
206
207         if (domain == NULL) {
208                 DEBUG(5,("winbindd_ccache_ntlm_auth: can't get domain [%s]\n",
209                         name_domain));
210                 request_error(state);
211                 return;
212         }
213
214         if (!check_client_uid(state, state->request->data.ccache_ntlm_auth.uid)) {
215                 request_error(state);
216                 return;
217         }
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_stored_pw(
273                 name_user, name_domain, entry->pass,
274                 initial, challenge, talloc_tos(), &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         if (!NT_STATUS_IS_OK(result)) {
294                 request_error(state);
295                 return;
296         }
297         request_ok(state);
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         NTSTATUS status;
305
306         /* Ensure null termination */
307         state->request->data.ccache_save.user[
308                 sizeof(state->request->data.ccache_save.user)-1]='\0';
309         state->request->data.ccache_save.pass[
310                 sizeof(state->request->data.ccache_save.pass)-1]='\0';
311
312         DEBUG(3, ("[%5lu]: save password of user %s\n",
313                   (unsigned long)state->pid,
314                   state->request->data.ccache_save.user));
315
316         /* Parse domain and username */
317
318         if (!canonicalize_username(state->request->data.ccache_save.user,
319                                    name_domain, name_user)) {
320                 DEBUG(5,("winbindd_ccache_save: cannot parse domain and user "
321                          "from name [%s]\n",
322                          state->request->data.ccache_save.user));
323                 request_error(state);
324                 return;
325         }
326
327         /*
328          * The domain is checked here only for compatibility
329          * reasons. We used to do the winbindd memory ccache for
330          * ntlm_auth in the domain child. With that code, we had to
331          * make sure that we do have a domain around to send this
332          * to. Now we do the memory cache in the parent winbindd,
333          * where it would not matter if we have a domain or not.
334          */
335
336         domain = find_auth_domain(state->request->flags, name_domain);
337         if (domain == NULL) {
338                 DEBUG(5, ("winbindd_ccache_save: can't get domain [%s]\n",
339                           name_domain));
340                 request_error(state);
341                 return;
342         }
343
344         if (!check_client_uid(state, state->request->data.ccache_save.uid)) {
345                 request_error(state);
346                 return;
347         }
348
349         status = winbindd_add_memory_creds(
350                 state->request->data.ccache_save.user,
351                 state->request->data.ccache_save.uid,
352                 state->request->data.ccache_save.pass);
353
354         if (!NT_STATUS_IS_OK(status)) {
355                 DEBUG(1, ("winbindd_add_memory_creds failed %s\n",
356                           nt_errstr(status)));
357                 request_error(state);
358                 return;
359         }
360         request_ok(state);
361 }