2 Unix SMB/CIFS implementation.
4 module to store/fetch session keys for the schannel server
6 Copyright (C) Andrew Tridgell 2004
7 Copyright (C) Andrew Bartlett <abartlet@samba.org> 2006-2009
8 Copyright (C) Guenther Deschner 2009
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/>.
25 #include "system/filesys.h"
26 #include "../lib/tdb/include/tdb.h"
27 #include "../lib/util/util_tdb.h"
28 #include "../lib/param/param.h"
29 #include "../libcli/auth/schannel.h"
30 #include "../librpc/gen_ndr/ndr_schannel.h"
31 #include "lib/dbwrap/dbwrap.h"
33 #define SECRETS_SCHANNEL_STATE "SECRETS/SCHANNEL"
35 /******************************************************************************
36 Open or create the schannel session store tdb. Non-static so it can
37 be called from parent processes to corectly handle TDB_CLEAR_IF_FIRST
38 *******************************************************************************/
40 struct db_context
*open_schannel_session_store(TALLOC_CTX
*mem_ctx
,
41 struct loadparm_context
*lp_ctx
)
43 struct db_context
*db_sc
= NULL
;
44 char *fname
= lpcfg_private_db_path(mem_ctx
, lp_ctx
, "schannel_store");
50 db_sc
= dbwrap_local_open(mem_ctx
, lp_ctx
, fname
, 0,
51 TDB_CLEAR_IF_FIRST
|TDB_NOSYNC
, O_RDWR
|O_CREAT
,
52 0600, DBWRAP_LOCK_ORDER_NONE
,
56 DEBUG(0,("open_schannel_session_store: Failed to open %s - %s\n",
57 fname
, strerror(errno
)));
67 /********************************************************************
68 ********************************************************************/
71 NTSTATUS
schannel_store_session_key_tdb(struct db_context
*db_sc
,
73 struct netlogon_creds_CredentialState
*creds
)
75 enum ndr_err_code ndr_err
;
82 if (strlen(creds
->computer_name
) > 15) {
84 * We may want to check for a completely
87 return STATUS_BUFFER_OVERFLOW
;
90 name_upper
= strupper_talloc(mem_ctx
, creds
->computer_name
);
92 return NT_STATUS_NO_MEMORY
;
95 keystr
= talloc_asprintf(mem_ctx
, "%s/%s",
96 SECRETS_SCHANNEL_STATE
, name_upper
);
97 TALLOC_FREE(name_upper
);
99 return NT_STATUS_NO_MEMORY
;
102 ndr_err
= ndr_push_struct_blob(&blob
, mem_ctx
, creds
,
103 (ndr_push_flags_fn_t
)ndr_push_netlogon_creds_CredentialState
);
104 if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err
)) {
106 return ndr_map_error2ntstatus(ndr_err
);
109 value
.dptr
= blob
.data
;
110 value
.dsize
= blob
.length
;
112 status
= dbwrap_store_bystring(db_sc
, keystr
, value
, TDB_REPLACE
);
113 if (!NT_STATUS_IS_OK(status
)) {
114 DEBUG(0,("Unable to add %s to session key db - %s\n",
115 keystr
, nt_errstr(status
)));
120 DEBUG(3,("schannel_store_session_key_tdb: stored schannel info with key %s\n",
123 if (DEBUGLEVEL
>= 10) {
124 NDR_PRINT_DEBUG(netlogon_creds_CredentialState
, creds
);
132 /********************************************************************
133 ********************************************************************/
136 NTSTATUS
schannel_fetch_session_key_tdb(struct db_context
*db_sc
,
138 const char *computer_name
,
139 struct netlogon_creds_CredentialState
**pcreds
)
143 enum ndr_err_code ndr_err
;
145 struct netlogon_creds_CredentialState
*creds
= NULL
;
151 name_upper
= strupper_talloc(mem_ctx
, computer_name
);
153 return NT_STATUS_NO_MEMORY
;
156 keystr
= talloc_asprintf(mem_ctx
, "%s/%s",
157 SECRETS_SCHANNEL_STATE
, name_upper
);
158 TALLOC_FREE(name_upper
);
160 return NT_STATUS_NO_MEMORY
;
163 status
= dbwrap_fetch_bystring(db_sc
, keystr
, keystr
, &value
);
164 if (!NT_STATUS_IS_OK(status
)) {
165 DEBUG(10,("schannel_fetch_session_key_tdb: Failed to find entry with key %s\n",
170 creds
= talloc_zero(mem_ctx
, struct netlogon_creds_CredentialState
);
172 status
= NT_STATUS_NO_MEMORY
;
176 blob
= data_blob_const(value
.dptr
, value
.dsize
);
178 ndr_err
= ndr_pull_struct_blob(&blob
, creds
, creds
,
179 (ndr_pull_flags_fn_t
)ndr_pull_netlogon_creds_CredentialState
);
180 if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err
)) {
181 status
= ndr_map_error2ntstatus(ndr_err
);
185 if (DEBUGLEVEL
>= 10) {
186 NDR_PRINT_DEBUG(netlogon_creds_CredentialState
, creds
);
189 DEBUG(3,("schannel_fetch_session_key_tdb: restored schannel info key %s\n",
192 status
= NT_STATUS_OK
;
198 if (!NT_STATUS_IS_OK(status
)) {
208 /******************************************************************************
209 Wrapper around schannel_fetch_session_key_tdb()
210 Note we must be root here.
211 *******************************************************************************/
213 NTSTATUS
schannel_get_creds_state(TALLOC_CTX
*mem_ctx
,
214 struct loadparm_context
*lp_ctx
,
215 const char *computer_name
,
216 struct netlogon_creds_CredentialState
**_creds
)
219 struct db_context
*db_sc
;
220 struct netlogon_creds_CredentialState
*creds
;
223 tmpctx
= talloc_named(mem_ctx
, 0, "schannel_get_creds_state");
225 return NT_STATUS_NO_MEMORY
;
228 db_sc
= open_schannel_session_store(tmpctx
, lp_ctx
);
230 return NT_STATUS_ACCESS_DENIED
;
233 status
= schannel_fetch_session_key_tdb(db_sc
, tmpctx
,
234 computer_name
, &creds
);
235 if (NT_STATUS_IS_OK(status
)) {
236 *_creds
= talloc_steal(mem_ctx
, creds
);
238 status
= NT_STATUS_NO_MEMORY
;
246 /******************************************************************************
247 Wrapper around schannel_store_session_key_tdb()
248 Note we must be root here.
249 *******************************************************************************/
251 NTSTATUS
schannel_save_creds_state(TALLOC_CTX
*mem_ctx
,
252 struct loadparm_context
*lp_ctx
,
253 struct netlogon_creds_CredentialState
*creds
)
256 struct db_context
*db_sc
;
259 tmpctx
= talloc_named(mem_ctx
, 0, "schannel_save_creds_state");
261 return NT_STATUS_NO_MEMORY
;
264 db_sc
= open_schannel_session_store(tmpctx
, lp_ctx
);
266 return NT_STATUS_ACCESS_DENIED
;
269 status
= schannel_store_session_key_tdb(db_sc
, tmpctx
, creds
);
277 * Create a very lossy hash of the computer name.
279 * The idea here is to compress the computer name into small space so
280 * that malicious clients cannot fill the database with junk, as only a
281 * maximum of 16k of entries are possible.
283 * Collisions are certainly possible, and the design behaves in the
284 * same way as when the hostname is reused, but clients that use the
285 * same connection do not go via the cache, and the cache only needs
286 * to function between the ReqChallenge and ServerAuthenticate
289 static void hash_computer_name(const char *computer_name
,
293 TDB_DATA computer_tdb_data
= {
294 .dptr
= (uint8_t *)discard_const_p(char, computer_name
),
295 .dsize
= strlen(computer_name
)
297 hash
= tdb_jenkins_hash(&computer_tdb_data
);
299 /* we are using 14 bits of the digest to index our connections, so
300 that we use at most 16,384 buckets.*/
301 snprintf(keystr
, 15, "CHALLENGE/%x%x", hash
& 0xFF,
302 (hash
& 0xFF00 >> 8) & 0x3f);
308 NTSTATUS
schannel_store_challenge_tdb(struct db_context
*db_sc
,
310 const struct netr_Credential
*client_challenge
,
311 const struct netr_Credential
*server_challenge
,
312 const char *computer_name
)
314 enum ndr_err_code ndr_err
;
317 char *name_upper
= NULL
;
319 char keystr
[16] = { 0, };
320 struct netlogon_cache_entry cache_entry
;
322 if (strlen(computer_name
) > 255) {
324 * We don't make this a limit at 15 chars as Samba has
325 * a test showing this can be longer :-(
327 return STATUS_BUFFER_OVERFLOW
;
330 name_upper
= strupper_talloc(mem_ctx
, computer_name
);
331 if (name_upper
== NULL
) {
332 return NT_STATUS_NO_MEMORY
;
335 hash_computer_name(name_upper
, keystr
);
337 cache_entry
.computer_name
= name_upper
;
338 cache_entry
.client_challenge
= *client_challenge
;
339 cache_entry
.server_challenge
= *server_challenge
;
341 ndr_err
= ndr_push_struct_blob(&blob
, mem_ctx
, &cache_entry
,
342 (ndr_push_flags_fn_t
)ndr_push_netlogon_cache_entry
);
343 if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err
)) {
344 return NT_STATUS_UNSUCCESSFUL
;
347 value
.dptr
= blob
.data
;
348 value
.dsize
= blob
.length
;
350 status
= dbwrap_store_bystring(db_sc
, keystr
, value
, TDB_REPLACE
);
351 if (!NT_STATUS_IS_OK(status
)) {
352 DEBUG(0,("%s: failed to stored challenge info for '%s' "
353 "with key %s - %s\n",
354 __func__
, cache_entry
.computer_name
, keystr
,
359 DEBUG(3,("%s: stored challenge info for '%s' with key %s\n",
360 __func__
, cache_entry
.computer_name
, keystr
));
362 if (DEBUGLEVEL
>= 10) {
363 NDR_PRINT_DEBUG(netlogon_cache_entry
, &cache_entry
);
369 /********************************************************************
370 Fetch a single challenge from the TDB.
371 ********************************************************************/
374 NTSTATUS
schannel_fetch_challenge_tdb(struct db_context
*db_sc
,
376 struct netr_Credential
*client_challenge
,
377 struct netr_Credential
*server_challenge
,
378 const char *computer_name
)
382 enum ndr_err_code ndr_err
;
384 char keystr
[16] = { 0, };
385 struct netlogon_cache_entry cache_entry
;
386 char *name_upper
= NULL
;
388 name_upper
= strupper_talloc(mem_ctx
, computer_name
);
389 if (name_upper
== NULL
) {
390 return NT_STATUS_NO_MEMORY
;
393 hash_computer_name(name_upper
, keystr
);
395 status
= dbwrap_fetch_bystring(db_sc
, mem_ctx
, keystr
, &value
);
396 if (!NT_STATUS_IS_OK(status
)) {
397 DEBUG(3,("%s: Failed to find entry for %s with key %s - %s\n",
398 __func__
, name_upper
, keystr
, nt_errstr(status
)));
402 blob
= data_blob_const(value
.dptr
, value
.dsize
);
404 ndr_err
= ndr_pull_struct_blob_all(&blob
, mem_ctx
, &cache_entry
,
405 (ndr_pull_flags_fn_t
)ndr_pull_netlogon_cache_entry
);
406 if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err
)) {
407 status
= ndr_map_error2ntstatus(ndr_err
);
408 DEBUG(3,("%s: Failed to parse entry for %s with key %s - %s\n",
409 __func__
, name_upper
, keystr
, nt_errstr(status
)));
413 if (DEBUGLEVEL
>= 10) {
414 NDR_PRINT_DEBUG(netlogon_cache_entry
, &cache_entry
);
417 if (strcmp(cache_entry
.computer_name
, name_upper
) != 0) {
418 status
= NT_STATUS_NOT_FOUND
;
420 DEBUG(1, ("%s: HASH COLLISION with key %s ! "
421 "Wanted to fetch record for %s but got %s.",
422 __func__
, keystr
, name_upper
,
423 cache_entry
.computer_name
));
426 DEBUG(3,("%s: restored key %s for %s\n",
427 __func__
, keystr
, cache_entry
.computer_name
));
429 *client_challenge
= cache_entry
.client_challenge
;
430 *server_challenge
= cache_entry
.server_challenge
;
434 if (!NT_STATUS_IS_OK(status
)) {
441 /******************************************************************************
442 Wrapper around schannel_fetch_session_key_tdb()
443 Note we must be root here.
445 *******************************************************************************/
447 NTSTATUS
schannel_get_challenge(struct loadparm_context
*lp_ctx
,
448 struct netr_Credential
*client_challenge
,
449 struct netr_Credential
*server_challenge
,
450 const char *computer_name
)
452 TALLOC_CTX
*frame
= talloc_stackframe();
453 struct db_context
*db_sc
;
456 db_sc
= open_schannel_session_store(frame
, lp_ctx
);
459 return NT_STATUS_ACCESS_DENIED
;
462 status
= schannel_fetch_challenge_tdb(db_sc
, frame
,
470 /******************************************************************************
471 Wrapper around dbwrap_delete_bystring()
472 Note we must be root here.
474 This allows the challenge to be removed from the TDB, which should be
475 as soon as the TDB or in-memory copy it is used, to avoid reuse.
476 *******************************************************************************/
478 NTSTATUS
schannel_delete_challenge(struct loadparm_context
*lp_ctx
,
479 const char *computer_name
)
481 TALLOC_CTX
*frame
= talloc_stackframe();
482 struct db_context
*db_sc
;
484 char keystr
[16] = { 0, };
486 db_sc
= open_schannel_session_store(frame
, lp_ctx
);
489 return NT_STATUS_ACCESS_DENIED
;
492 name_upper
= strupper_talloc(frame
, computer_name
);
495 return NT_STATUS_NO_MEMORY
;
498 hash_computer_name(name_upper
, keystr
);
500 /* Now delete it, we do not want to permit fetch of this twice */
501 dbwrap_delete_bystring(db_sc
, keystr
);
507 /******************************************************************************
508 Wrapper around schannel_store_session_key_tdb()
509 Note we must be root here.
510 *******************************************************************************/
512 NTSTATUS
schannel_save_challenge(struct loadparm_context
*lp_ctx
,
513 const struct netr_Credential
*client_challenge
,
514 const struct netr_Credential
*server_challenge
,
515 const char *computer_name
)
517 TALLOC_CTX
*frame
= talloc_stackframe();
518 struct db_context
*db_sc
;
521 db_sc
= open_schannel_session_store(frame
, lp_ctx
);
524 return NT_STATUS_ACCESS_DENIED
;
527 status
= schannel_store_challenge_tdb(db_sc
, frame
,
536 /********************************************************************
537 Validate an incoming authenticator against the credentials for the
538 remote machine stored in the schannel database.
540 The credentials are (re)read and from the schannel database, and
541 written back after the caclulations are performed.
543 If the creds_out parameter is not NULL returns the credentials.
544 ********************************************************************/
546 NTSTATUS
schannel_check_creds_state(TALLOC_CTX
*mem_ctx
,
547 struct loadparm_context
*lp_ctx
,
548 const char *computer_name
,
549 struct netr_Authenticator
*received_authenticator
,
550 struct netr_Authenticator
*return_authenticator
,
551 struct netlogon_creds_CredentialState
**creds_out
)
554 struct db_context
*db_sc
;
555 struct netlogon_creds_CredentialState
*creds
;
557 char *name_upper
= NULL
;
559 struct db_record
*record
;
562 if (creds_out
!= NULL
) {
566 tmpctx
= talloc_named(mem_ctx
, 0, "schannel_check_creds_state");
568 return NT_STATUS_NO_MEMORY
;
571 name_upper
= strupper_talloc(tmpctx
, computer_name
);
573 status
= NT_STATUS_NO_MEMORY
;
577 keystr
= talloc_asprintf(tmpctx
, "%s/%s",
578 SECRETS_SCHANNEL_STATE
, name_upper
);
580 status
= NT_STATUS_NO_MEMORY
;
584 key
= string_term_tdb_data(keystr
);
586 db_sc
= open_schannel_session_store(tmpctx
, lp_ctx
);
588 status
= NT_STATUS_ACCESS_DENIED
;
592 record
= dbwrap_fetch_locked(db_sc
, tmpctx
, key
);
594 status
= NT_STATUS_INTERNAL_DB_CORRUPTION
;
598 /* Because this is a shared structure (even across
599 * disconnects) we must update the database every time we
600 * update the structure */
602 status
= schannel_fetch_session_key_tdb(db_sc
, tmpctx
,
603 computer_name
, &creds
);
604 if (!NT_STATUS_IS_OK(status
)) {
608 status
= netlogon_creds_server_step_check(creds
,
609 received_authenticator
,
610 return_authenticator
);
611 if (!NT_STATUS_IS_OK(status
)) {
615 status
= schannel_store_session_key_tdb(db_sc
, tmpctx
, creds
);
616 if (!NT_STATUS_IS_OK(status
)) {
621 *creds_out
= talloc_steal(mem_ctx
, creds
);
623 status
= NT_STATUS_NO_MEMORY
;
628 status
= NT_STATUS_OK
;