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).
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.
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.
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/>.
26 #include "../libcli/auth/ntlmssp.h"
29 #define DBGC_CLASS DBGC_WINBIND
31 static bool client_can_access_ccache_entry(uid_t client_uid
,
32 struct WINBINDD_MEMORY_CREDS
*entry
)
34 if (client_uid
== entry
->uid
|| client_uid
== 0) {
35 DEBUG(10, ("Access granted to uid %u\n", (unsigned int)client_uid
));
39 DEBUG(1, ("Access denied to uid %u (expected %u)\n",
40 (unsigned int)client_uid
, (unsigned int)entry
->uid
));
44 static NTSTATUS
do_ntlm_auth_with_hashes(const char *username
,
46 const unsigned char lm_hash
[LM_HASH_LEN
],
47 const unsigned char nt_hash
[NT_HASH_LEN
],
48 const DATA_BLOB initial_msg
,
49 const DATA_BLOB challenge_msg
,
51 uint8_t session_key
[16])
54 struct ntlmssp_state
*ntlmssp_state
= NULL
;
55 DATA_BLOB dummy_msg
, reply
;
57 status
= ntlmssp_client_start(NULL
,
60 lp_client_ntlmv2_auth(),
63 if (!NT_STATUS_IS_OK(status
)) {
64 DEBUG(1, ("Could not start NTLMSSP client: %s\n",
69 status
= ntlmssp_set_username(ntlmssp_state
, username
);
71 if (!NT_STATUS_IS_OK(status
)) {
72 DEBUG(1, ("Could not set username: %s\n",
77 status
= ntlmssp_set_domain(ntlmssp_state
, domain
);
79 if (!NT_STATUS_IS_OK(status
)) {
80 DEBUG(1, ("Could not set domain: %s\n",
85 status
= ntlmssp_set_hashes(ntlmssp_state
, lm_hash
, nt_hash
);
87 if (!NT_STATUS_IS_OK(status
)) {
88 DEBUG(1, ("Could not set hashes: %s\n",
93 ntlmssp_want_feature(ntlmssp_state
, NTLMSSP_FEATURE_SESSION_KEY
);
95 /* We need to get our protocol handler into the right state. So first
96 we ask it to generate the initial message. Actually the client has already
97 sent its own initial message, so we're going to drop this one on the floor.
98 The client might have sent a different message, for example with different
99 negotiation options, but as far as I can tell this won't hurt us. (Unless
100 the client sent a different username or domain, in which case that's their
101 problem for telling us the wrong username or domain.)
102 Since we have a copy of the initial message that the client sent, we could
103 resolve any discrepancies if we had to.
105 dummy_msg
= data_blob_null
;
106 reply
= data_blob_null
;
107 status
= ntlmssp_update(ntlmssp_state
, dummy_msg
, &reply
);
108 data_blob_free(&dummy_msg
);
109 data_blob_free(&reply
);
111 if (!NT_STATUS_EQUAL(status
, NT_STATUS_MORE_PROCESSING_REQUIRED
)) {
112 DEBUG(1, ("Failed to create initial message! [%s]\n",
117 /* Now we are ready to handle the server's actual response. */
118 status
= ntlmssp_update(ntlmssp_state
, challenge_msg
, &reply
);
120 if (!NT_STATUS_EQUAL(status
, NT_STATUS_OK
)) {
121 DEBUG(1, ("We didn't get a response to the challenge! [%s]\n",
123 data_blob_free(&reply
);
127 if (ntlmssp_state
->session_key
.length
!= 16) {
128 DEBUG(1, ("invalid session key length %d\n",
129 (int)ntlmssp_state
->session_key
.length
));
130 data_blob_free(&reply
);
134 *auth_msg
= data_blob(reply
.data
, reply
.length
);
135 memcpy(session_key
, ntlmssp_state
->session_key
.data
, 16);
136 status
= NT_STATUS_OK
;
139 ntlmssp_end(&ntlmssp_state
);
143 static bool check_client_uid(struct winbindd_cli_state
*state
, uid_t uid
)
150 ret
= sys_getpeereid(state
->sock
, &ret_uid
);
152 DEBUG(1, ("check_client_uid: Could not get socket peer uid: %s; "
153 "denying access\n", strerror(errno
)));
157 if (uid
!= ret_uid
) {
158 DEBUG(1, ("check_client_uid: Client lied about its uid: said %u, "
159 "actually was %u; denying access\n",
160 (unsigned int)uid
, (unsigned int)ret_uid
));
167 void winbindd_ccache_ntlm_auth(struct winbindd_cli_state
*state
)
169 struct winbindd_domain
*domain
;
170 fstring name_domain
, name_user
;
171 NTSTATUS result
= NT_STATUS_NOT_SUPPORTED
;
172 struct WINBINDD_MEMORY_CREDS
*entry
;
173 DATA_BLOB initial
, challenge
, auth
;
174 uint32 initial_blob_len
, challenge_blob_len
, extra_len
;
176 /* Ensure null termination */
177 state
->request
->data
.ccache_ntlm_auth
.user
[
178 sizeof(state
->request
->data
.ccache_ntlm_auth
.user
)-1]='\0';
180 DEBUG(3, ("[%5lu]: perform NTLM auth on behalf of user %s\n", (unsigned long)state
->pid
,
181 state
->request
->data
.ccache_ntlm_auth
.user
));
183 /* Parse domain and username */
185 if (!canonicalize_username(state
->request
->data
.ccache_ntlm_auth
.user
,
186 name_domain
, name_user
)) {
187 DEBUG(5,("winbindd_ccache_ntlm_auth: cannot parse domain and user from name [%s]\n",
188 state
->request
->data
.ccache_ntlm_auth
.user
));
189 request_error(state
);
193 domain
= find_auth_domain(state
->request
->flags
, name_domain
);
195 if (domain
== NULL
) {
196 DEBUG(5,("winbindd_ccache_ntlm_auth: can't get domain [%s]\n",
198 request_error(state
);
202 if (!check_client_uid(state
, state
->request
->data
.ccache_ntlm_auth
.uid
)) {
203 request_error(state
);
207 /* validate blob lengths */
208 initial_blob_len
= state
->request
->data
.ccache_ntlm_auth
.initial_blob_len
;
209 challenge_blob_len
= state
->request
->data
.ccache_ntlm_auth
.challenge_blob_len
;
210 extra_len
= state
->request
->extra_len
;
212 if (initial_blob_len
> extra_len
|| challenge_blob_len
> extra_len
||
213 initial_blob_len
+ challenge_blob_len
> extra_len
||
214 initial_blob_len
+ challenge_blob_len
< initial_blob_len
||
215 initial_blob_len
+ challenge_blob_len
< challenge_blob_len
) {
217 DEBUG(10,("winbindd_dual_ccache_ntlm_auth: blob lengths overrun "
218 "or wrap. Buffer [%d+%d > %d]\n",
225 /* Parse domain and username */
226 if (!parse_domain_user(state
->request
->data
.ccache_ntlm_auth
.user
, name_domain
, name_user
)) {
227 DEBUG(10,("winbindd_dual_ccache_ntlm_auth: cannot parse "
228 "domain and user from name [%s]\n",
229 state
->request
->data
.ccache_ntlm_auth
.user
));
233 entry
= find_memory_creds_by_name(state
->request
->data
.ccache_ntlm_auth
.user
);
234 if (entry
== NULL
|| entry
->nt_hash
== NULL
|| entry
->lm_hash
== NULL
) {
235 DEBUG(10,("winbindd_dual_ccache_ntlm_auth: could not find "
236 "credentials for user %s\n",
237 state
->request
->data
.ccache_ntlm_auth
.user
));
241 DEBUG(10,("winbindd_dual_ccache_ntlm_auth: found ccache [%s]\n", entry
->username
));
243 if (!client_can_access_ccache_entry(state
->request
->data
.ccache_ntlm_auth
.uid
, entry
)) {
247 if (initial_blob_len
== 0 && challenge_blob_len
== 0) {
248 /* this is just a probe to see if credentials are available. */
249 result
= NT_STATUS_OK
;
250 state
->response
->data
.ccache_ntlm_auth
.auth_blob_len
= 0;
254 initial
= data_blob_const(state
->request
->extra_data
.data
,
256 challenge
= data_blob_const(
257 state
->request
->extra_data
.data
+ initial_blob_len
,
258 state
->request
->data
.ccache_ntlm_auth
.challenge_blob_len
);
260 result
= do_ntlm_auth_with_hashes(
261 name_user
, name_domain
, entry
->lm_hash
, entry
->nt_hash
,
262 initial
, challenge
, &auth
,
263 state
->response
->data
.ccache_ntlm_auth
.session_key
);
265 if (!NT_STATUS_IS_OK(result
)) {
269 state
->response
->extra_data
.data
= talloc_memdup(
270 state
->mem_ctx
, auth
.data
, auth
.length
);
271 if (!state
->response
->extra_data
.data
) {
272 result
= NT_STATUS_NO_MEMORY
;
275 state
->response
->length
+= auth
.length
;
276 state
->response
->data
.ccache_ntlm_auth
.auth_blob_len
= auth
.length
;
278 data_blob_free(&auth
);
281 if (!NT_STATUS_IS_OK(result
)) {
282 request_error(state
);
288 void winbindd_ccache_save(struct winbindd_cli_state
*state
)
290 struct winbindd_domain
*domain
;
291 fstring name_domain
, name_user
;
294 /* Ensure null termination */
295 state
->request
->data
.ccache_save
.user
[
296 sizeof(state
->request
->data
.ccache_save
.user
)-1]='\0';
297 state
->request
->data
.ccache_save
.pass
[
298 sizeof(state
->request
->data
.ccache_save
.pass
)-1]='\0';
300 DEBUG(3, ("[%5lu]: save password of user %s\n",
301 (unsigned long)state
->pid
,
302 state
->request
->data
.ccache_save
.user
));
304 /* Parse domain and username */
306 if (!canonicalize_username(state
->request
->data
.ccache_save
.user
,
307 name_domain
, name_user
)) {
308 DEBUG(5,("winbindd_ccache_save: cannot parse domain and user "
310 state
->request
->data
.ccache_save
.user
));
311 request_error(state
);
316 * The domain is checked here only for compatibility
317 * reasons. We used to do the winbindd memory ccache for
318 * ntlm_auth in the domain child. With that code, we had to
319 * make sure that we do have a domain around to send this
320 * to. Now we do the memory cache in the parent winbindd,
321 * where it would not matter if we have a domain or not.
324 domain
= find_auth_domain(state
->request
->flags
, name_domain
);
325 if (domain
== NULL
) {
326 DEBUG(5, ("winbindd_ccache_save: can't get domain [%s]\n",
328 request_error(state
);
332 if (!check_client_uid(state
, state
->request
->data
.ccache_save
.uid
)) {
333 request_error(state
);
337 status
= winbindd_add_memory_creds(
338 state
->request
->data
.ccache_save
.user
,
339 state
->request
->data
.ccache_save
.uid
,
340 state
->request
->data
.ccache_save
.pass
);
342 if (!NT_STATUS_IS_OK(status
)) {
343 DEBUG(1, ("winbindd_add_memory_creds failed %s\n",
345 request_error(state
);