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