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