2 Unix SMB/CIFS implementation.
4 Winbind daemon - cached credentials funcions
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
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.
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.
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/>.
27 #include "auth/gensec/gensec.h"
28 #include "auth_generic.h"
31 #define DBGC_CLASS DBGC_WINBIND
33 static bool client_can_access_ccache_entry(uid_t client_uid,
34 struct WINBINDD_MEMORY_CREDS *entry)
36 if (client_uid == entry->uid || client_uid == 0) {
37 DEBUG(10, ("Access granted to uid %u\n", (unsigned int)client_uid));
41 DEBUG(1, ("Access denied to uid %u (expected %u)\n",
42 (unsigned int)client_uid, (unsigned int)entry->uid));
46 static NTSTATUS do_ntlm_auth_with_stored_pw(const char *username,
49 const DATA_BLOB initial_msg,
50 const DATA_BLOB challenge_msg,
52 uint8_t session_key[16])
55 struct auth_generic_state *auth_generic_state = NULL;
56 DATA_BLOB dummy_msg, reply, session_key_blob;
58 status = auth_generic_client_prepare(NULL, &auth_generic_state);
60 if (!NT_STATUS_IS_OK(status)) {
61 DEBUG(1, ("Could not start NTLMSSP client: %s\n",
66 status = auth_generic_set_username(auth_generic_state, username);
68 if (!NT_STATUS_IS_OK(status)) {
69 DEBUG(1, ("Could not set username: %s\n",
74 status = auth_generic_set_domain(auth_generic_state, domain);
76 if (!NT_STATUS_IS_OK(status)) {
77 DEBUG(1, ("Could not set domain: %s\n",
82 status = auth_generic_set_password(auth_generic_state, password);
84 if (!NT_STATUS_IS_OK(status)) {
85 DEBUG(1, ("Could not set password: %s\n",
90 gensec_want_feature(auth_generic_state->gensec_security, GENSEC_FEATURE_SESSION_KEY);
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",
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.
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);
115 if (!NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
116 DEBUG(1, ("Failed to create initial message! [%s]\n",
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",
127 data_blob_free(&reply);
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",
136 data_blob_free(&reply);
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);
146 memcpy(session_key, session_key_blob.data, 16);
147 data_blob_free(&session_key_blob);
149 status = NT_STATUS_OK;
152 TALLOC_FREE(auth_generic_state);
156 static bool check_client_uid(struct winbindd_cli_state *state, uid_t uid)
163 ret = sys_getpeereid(state->sock, &ret_uid);
165 DEBUG(1, ("check_client_uid: Could not get socket peer uid: %s; "
166 "denying access\n", strerror(errno)));
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));
180 void winbindd_ccache_ntlm_auth(struct winbindd_cli_state *state)
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;
189 /* Ensure null termination */
190 state->request->data.ccache_ntlm_auth.user[
191 sizeof(state->request->data.ccache_ntlm_auth.user)-1]='\0';
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));
196 /* Parse domain and username */
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);
206 domain = find_auth_domain(state->request->flags, name_domain);
208 if (domain == NULL) {
209 DEBUG(5,("winbindd_ccache_ntlm_auth: can't get domain [%s]\n",
211 request_error(state);
215 if (!check_client_uid(state, state->request->data.ccache_ntlm_auth.uid)) {
216 request_error(state);
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;
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) {
230 DEBUG(10,("winbindd_dual_ccache_ntlm_auth: blob lengths overrun "
231 "or wrap. Buffer [%d+%d > %d]\n",
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));
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));
254 DEBUG(10,("winbindd_dual_ccache_ntlm_auth: found ccache [%s]\n", entry->username));
256 if (!client_can_access_ccache_entry(state->request->data.ccache_ntlm_auth.uid, entry)) {
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;
267 initial = data_blob_const(state->request->extra_data.data,
269 challenge = data_blob_const(
270 state->request->extra_data.data + initial_blob_len,
271 state->request->data.ccache_ntlm_auth.challenge_blob_len);
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);
278 if (!NT_STATUS_IS_OK(result)) {
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;
288 state->response->length += auth.length;
289 state->response->data.ccache_ntlm_auth.auth_blob_len = auth.length;
291 data_blob_free(&auth);
294 if (!NT_STATUS_IS_OK(result)) {
295 request_error(state);
301 void winbindd_ccache_save(struct winbindd_cli_state *state)
303 struct winbindd_domain *domain;
304 fstring name_domain, name_user;
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';
313 DEBUG(3, ("[%5lu]: save password of user %s\n",
314 (unsigned long)state->pid,
315 state->request->data.ccache_save.user));
317 /* Parse domain and username */
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 "
323 state->request->data.ccache_save.user));
324 request_error(state);
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.
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",
341 request_error(state);
345 if (!check_client_uid(state, state->request->data.ccache_save.uid)) {
346 request_error(state);
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);
355 if (!NT_STATUS_IS_OK(status)) {
356 DEBUG(1, ("winbindd_add_memory_creds failed %s\n",
358 request_error(state);