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(), 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
, 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
)
164 ret
= getpeereid(state
->sock
, &ret_uid
, &ret_gid
);
166 DEBUG(1, ("check_client_uid: Could not get socket peer uid: %s; "
167 "denying access\n", strerror(errno
)));
171 if (uid
!= ret_uid
&& ret_uid
!= sec_initial_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
));
181 void winbindd_ccache_ntlm_auth(struct winbindd_cli_state
*state
)
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
;
190 /* Ensure null termination */
191 state
->request
->data
.ccache_ntlm_auth
.user
[
192 sizeof(state
->request
->data
.ccache_ntlm_auth
.user
)-1]='\0';
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
));
197 /* Parse domain and username */
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
);
207 domain
= find_auth_domain(state
->request
->flags
, name_domain
);
209 if (domain
== NULL
) {
210 DEBUG(5,("winbindd_ccache_ntlm_auth: can't get domain [%s]\n",
212 request_error(state
);
216 if (!check_client_uid(state
, state
->request
->data
.ccache_ntlm_auth
.uid
)) {
217 request_error(state
);
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
;
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
) {
231 DEBUG(10,("winbindd_dual_ccache_ntlm_auth: blob lengths overrun "
232 "or wrap. Buffer [%d+%d > %d]\n",
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
));
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
));
255 DEBUG(10,("winbindd_dual_ccache_ntlm_auth: found ccache [%s]\n", entry
->username
));
257 if (!client_can_access_ccache_entry(state
->request
->data
.ccache_ntlm_auth
.uid
, entry
)) {
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;
268 initial
= data_blob_const(state
->request
->extra_data
.data
,
270 challenge
= data_blob_const(
271 state
->request
->extra_data
.data
+ initial_blob_len
,
272 state
->request
->data
.ccache_ntlm_auth
.challenge_blob_len
);
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
);
279 if (!NT_STATUS_IS_OK(result
)) {
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
;
289 state
->response
->length
+= auth
.length
;
290 state
->response
->data
.ccache_ntlm_auth
.auth_blob_len
= auth
.length
;
292 data_blob_free(&auth
);
295 if (!NT_STATUS_IS_OK(result
)) {
296 request_error(state
);
302 void winbindd_ccache_save(struct winbindd_cli_state
*state
)
304 struct winbindd_domain
*domain
;
305 fstring name_domain
, name_user
;
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';
314 DEBUG(3, ("[%5lu]: save password of user %s\n",
315 (unsigned long)state
->pid
,
316 state
->request
->data
.ccache_save
.user
));
318 /* Parse domain and username */
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 "
324 state
->request
->data
.ccache_save
.user
));
325 request_error(state
);
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.
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",
342 request_error(state
);
346 if (!check_client_uid(state
, state
->request
->data
.ccache_save
.uid
)) {
347 request_error(state
);
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
);
356 if (!NT_STATUS_IS_OK(status
)) {
357 DEBUG(1, ("winbindd_add_memory_creds failed %s\n",
359 request_error(state
);