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