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");
45 int hash_size
, tdb_flags
;
51 hash_size
= lpcfg_tdb_hash_size(lp_ctx
, fname
);
52 tdb_flags
= lpcfg_tdb_flags(lp_ctx
, TDB_CLEAR_IF_FIRST
|TDB_NOSYNC
);
54 db_sc
= dbwrap_local_open(
61 DBWRAP_LOCK_ORDER_NONE
,
65 DEBUG(0,("open_schannel_session_store: Failed to open %s - %s\n",
66 fname
, strerror(errno
)));
76 /********************************************************************
77 ********************************************************************/
80 NTSTATUS
schannel_store_session_key_tdb(struct db_context
*db_sc
,
82 struct netlogon_creds_CredentialState
*creds
)
84 enum ndr_err_code ndr_err
;
91 if (strlen(creds
->computer_name
) > 15) {
93 * We may want to check for a completely
96 return STATUS_BUFFER_OVERFLOW
;
99 name_upper
= strupper_talloc(mem_ctx
, creds
->computer_name
);
101 return NT_STATUS_NO_MEMORY
;
104 keystr
= talloc_asprintf(mem_ctx
, "%s/%s",
105 SECRETS_SCHANNEL_STATE
, name_upper
);
106 TALLOC_FREE(name_upper
);
108 return NT_STATUS_NO_MEMORY
;
111 ndr_err
= ndr_push_struct_blob(&blob
, mem_ctx
, creds
,
112 (ndr_push_flags_fn_t
)ndr_push_netlogon_creds_CredentialState
);
113 if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err
)) {
115 return ndr_map_error2ntstatus(ndr_err
);
118 value
.dptr
= blob
.data
;
119 value
.dsize
= blob
.length
;
121 status
= dbwrap_store_bystring(db_sc
, keystr
, value
, TDB_REPLACE
);
122 if (!NT_STATUS_IS_OK(status
)) {
123 DEBUG(0,("Unable to add %s to session key db - %s\n",
124 keystr
, nt_errstr(status
)));
129 DEBUG(3,("schannel_store_session_key_tdb: stored schannel info with key %s\n",
132 if (DEBUGLEVEL
>= 10) {
133 NDR_PRINT_DEBUG(netlogon_creds_CredentialState
, creds
);
141 /********************************************************************
142 ********************************************************************/
145 NTSTATUS
schannel_fetch_session_key_tdb(struct db_context
*db_sc
,
147 const char *computer_name
,
148 struct netlogon_creds_CredentialState
**pcreds
)
152 enum ndr_err_code ndr_err
;
154 struct netlogon_creds_CredentialState
*creds
= NULL
;
160 name_upper
= strupper_talloc(mem_ctx
, computer_name
);
162 return NT_STATUS_NO_MEMORY
;
165 keystr
= talloc_asprintf(mem_ctx
, "%s/%s",
166 SECRETS_SCHANNEL_STATE
, name_upper
);
167 TALLOC_FREE(name_upper
);
169 return NT_STATUS_NO_MEMORY
;
172 status
= dbwrap_fetch_bystring(db_sc
, keystr
, keystr
, &value
);
173 if (!NT_STATUS_IS_OK(status
)) {
174 DEBUG(10,("schannel_fetch_session_key_tdb: Failed to find entry with key %s\n",
179 creds
= talloc_zero(mem_ctx
, struct netlogon_creds_CredentialState
);
181 status
= NT_STATUS_NO_MEMORY
;
185 blob
= data_blob_const(value
.dptr
, value
.dsize
);
187 ndr_err
= ndr_pull_struct_blob(&blob
, creds
, creds
,
188 (ndr_pull_flags_fn_t
)ndr_pull_netlogon_creds_CredentialState
);
189 if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err
)) {
190 status
= ndr_map_error2ntstatus(ndr_err
);
194 if (DEBUGLEVEL
>= 10) {
195 NDR_PRINT_DEBUG(netlogon_creds_CredentialState
, creds
);
198 DEBUG(3,("schannel_fetch_session_key_tdb: restored schannel info key %s\n",
201 status
= NT_STATUS_OK
;
207 if (!NT_STATUS_IS_OK(status
)) {
217 /******************************************************************************
218 Wrapper around schannel_fetch_session_key_tdb()
219 Note we must be root here.
220 *******************************************************************************/
222 NTSTATUS
schannel_get_creds_state(TALLOC_CTX
*mem_ctx
,
223 struct loadparm_context
*lp_ctx
,
224 const char *computer_name
,
225 struct netlogon_creds_CredentialState
**_creds
)
228 struct db_context
*db_sc
;
229 struct netlogon_creds_CredentialState
*creds
;
232 tmpctx
= talloc_named(mem_ctx
, 0, "schannel_get_creds_state");
234 return NT_STATUS_NO_MEMORY
;
237 db_sc
= open_schannel_session_store(tmpctx
, lp_ctx
);
239 return NT_STATUS_ACCESS_DENIED
;
242 status
= schannel_fetch_session_key_tdb(db_sc
, tmpctx
,
243 computer_name
, &creds
);
244 if (NT_STATUS_IS_OK(status
)) {
245 *_creds
= talloc_steal(mem_ctx
, creds
);
247 status
= NT_STATUS_NO_MEMORY
;
255 /******************************************************************************
256 Wrapper around schannel_store_session_key_tdb()
257 Note we must be root here.
258 *******************************************************************************/
260 NTSTATUS
schannel_save_creds_state(TALLOC_CTX
*mem_ctx
,
261 struct loadparm_context
*lp_ctx
,
262 struct netlogon_creds_CredentialState
*creds
)
265 struct db_context
*db_sc
;
268 tmpctx
= talloc_named(mem_ctx
, 0, "schannel_save_creds_state");
270 return NT_STATUS_NO_MEMORY
;
273 db_sc
= open_schannel_session_store(tmpctx
, lp_ctx
);
275 status
= NT_STATUS_ACCESS_DENIED
;
279 status
= schannel_store_session_key_tdb(db_sc
, tmpctx
, creds
);
288 * Create a very lossy hash of the computer name.
290 * The idea here is to compress the computer name into small space so
291 * that malicious clients cannot fill the database with junk, as only a
292 * maximum of 16k of entries are possible.
294 * Collisions are certainly possible, and the design behaves in the
295 * same way as when the hostname is reused, but clients that use the
296 * same connection do not go via the cache, and the cache only needs
297 * to function between the ReqChallenge and ServerAuthenticate
300 static void hash_computer_name(const char *computer_name
,
304 TDB_DATA computer_tdb_data
= {
305 .dptr
= (uint8_t *)discard_const_p(char, computer_name
),
306 .dsize
= strlen(computer_name
)
308 hash
= tdb_jenkins_hash(&computer_tdb_data
);
310 /* we are using 14 bits of the digest to index our connections, so
311 that we use at most 16,384 buckets.*/
312 snprintf(keystr
, 15, "CHALLENGE/%x%x", hash
& 0xFF,
313 (hash
& 0xFF00 >> 8) & 0x3f);
319 NTSTATUS
schannel_store_challenge_tdb(struct db_context
*db_sc
,
321 const struct netr_Credential
*client_challenge
,
322 const struct netr_Credential
*server_challenge
,
323 const char *computer_name
)
325 enum ndr_err_code ndr_err
;
328 char *name_upper
= NULL
;
330 char keystr
[16] = { 0, };
331 struct netlogon_cache_entry cache_entry
;
333 if (strlen(computer_name
) > 255) {
335 * We don't make this a limit at 15 chars as Samba has
336 * a test showing this can be longer :-(
338 return STATUS_BUFFER_OVERFLOW
;
341 name_upper
= strupper_talloc(mem_ctx
, computer_name
);
342 if (name_upper
== NULL
) {
343 return NT_STATUS_NO_MEMORY
;
346 hash_computer_name(name_upper
, keystr
);
348 cache_entry
.computer_name
= name_upper
;
349 cache_entry
.client_challenge
= *client_challenge
;
350 cache_entry
.server_challenge
= *server_challenge
;
352 ndr_err
= ndr_push_struct_blob(&blob
, mem_ctx
, &cache_entry
,
353 (ndr_push_flags_fn_t
)ndr_push_netlogon_cache_entry
);
354 if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err
)) {
355 return NT_STATUS_UNSUCCESSFUL
;
358 value
.dptr
= blob
.data
;
359 value
.dsize
= blob
.length
;
361 status
= dbwrap_store_bystring(db_sc
, keystr
, value
, TDB_REPLACE
);
362 if (!NT_STATUS_IS_OK(status
)) {
363 DEBUG(0,("%s: failed to stored challenge info for '%s' "
364 "with key %s - %s\n",
365 __func__
, cache_entry
.computer_name
, keystr
,
370 DEBUG(3,("%s: stored challenge info for '%s' with key %s\n",
371 __func__
, cache_entry
.computer_name
, keystr
));
373 if (DEBUGLEVEL
>= 10) {
374 NDR_PRINT_DEBUG(netlogon_cache_entry
, &cache_entry
);
380 /********************************************************************
381 Fetch a single challenge from the TDB.
382 ********************************************************************/
385 NTSTATUS
schannel_fetch_challenge_tdb(struct db_context
*db_sc
,
387 struct netr_Credential
*client_challenge
,
388 struct netr_Credential
*server_challenge
,
389 const char *computer_name
)
393 enum ndr_err_code ndr_err
;
395 char keystr
[16] = { 0, };
396 struct netlogon_cache_entry cache_entry
;
397 char *name_upper
= NULL
;
399 name_upper
= strupper_talloc(mem_ctx
, computer_name
);
400 if (name_upper
== NULL
) {
401 return NT_STATUS_NO_MEMORY
;
404 hash_computer_name(name_upper
, keystr
);
406 status
= dbwrap_fetch_bystring(db_sc
, mem_ctx
, keystr
, &value
);
407 if (!NT_STATUS_IS_OK(status
)) {
408 DEBUG(3,("%s: Failed to find entry for %s with key %s - %s\n",
409 __func__
, name_upper
, keystr
, nt_errstr(status
)));
413 blob
= data_blob_const(value
.dptr
, value
.dsize
);
415 ndr_err
= ndr_pull_struct_blob_all(&blob
, mem_ctx
, &cache_entry
,
416 (ndr_pull_flags_fn_t
)ndr_pull_netlogon_cache_entry
);
417 if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err
)) {
418 status
= ndr_map_error2ntstatus(ndr_err
);
419 DEBUG(3,("%s: Failed to parse entry for %s with key %s - %s\n",
420 __func__
, name_upper
, keystr
, nt_errstr(status
)));
424 if (DEBUGLEVEL
>= 10) {
425 NDR_PRINT_DEBUG(netlogon_cache_entry
, &cache_entry
);
428 if (strcmp(cache_entry
.computer_name
, name_upper
) != 0) {
429 status
= NT_STATUS_NOT_FOUND
;
431 DEBUG(1, ("%s: HASH COLLISION with key %s ! "
432 "Wanted to fetch record for %s but got %s.",
433 __func__
, keystr
, name_upper
,
434 cache_entry
.computer_name
));
437 DEBUG(3,("%s: restored key %s for %s\n",
438 __func__
, keystr
, cache_entry
.computer_name
));
440 *client_challenge
= cache_entry
.client_challenge
;
441 *server_challenge
= cache_entry
.server_challenge
;
445 if (!NT_STATUS_IS_OK(status
)) {
452 /******************************************************************************
453 Wrapper around schannel_fetch_challenge_tdb()
454 Note we must be root here.
456 *******************************************************************************/
458 NTSTATUS
schannel_get_challenge(struct loadparm_context
*lp_ctx
,
459 struct netr_Credential
*client_challenge
,
460 struct netr_Credential
*server_challenge
,
461 const char *computer_name
)
463 TALLOC_CTX
*frame
= talloc_stackframe();
464 struct db_context
*db_sc
;
467 db_sc
= open_schannel_session_store(frame
, lp_ctx
);
470 return NT_STATUS_ACCESS_DENIED
;
473 status
= schannel_fetch_challenge_tdb(db_sc
, frame
,
481 /******************************************************************************
482 Wrapper around dbwrap_delete_bystring()
483 Note we must be root here.
485 This allows the challenge to be removed from the TDB, which should be
486 as soon as the TDB or in-memory copy it is used, to avoid reuse.
487 *******************************************************************************/
489 NTSTATUS
schannel_delete_challenge(struct loadparm_context
*lp_ctx
,
490 const char *computer_name
)
492 TALLOC_CTX
*frame
= talloc_stackframe();
493 struct db_context
*db_sc
;
495 char keystr
[16] = { 0, };
497 db_sc
= open_schannel_session_store(frame
, lp_ctx
);
500 return NT_STATUS_ACCESS_DENIED
;
503 name_upper
= strupper_talloc(frame
, computer_name
);
506 return NT_STATUS_NO_MEMORY
;
509 hash_computer_name(name_upper
, keystr
);
511 /* Now delete it, we do not want to permit fetch of this twice */
512 dbwrap_delete_bystring(db_sc
, keystr
);
518 /******************************************************************************
519 Wrapper around schannel_store_session_key_tdb()
520 Note we must be root here.
521 *******************************************************************************/
523 NTSTATUS
schannel_save_challenge(struct loadparm_context
*lp_ctx
,
524 const struct netr_Credential
*client_challenge
,
525 const struct netr_Credential
*server_challenge
,
526 const char *computer_name
)
528 TALLOC_CTX
*frame
= talloc_stackframe();
529 struct db_context
*db_sc
;
532 db_sc
= open_schannel_session_store(frame
, lp_ctx
);
535 return NT_STATUS_ACCESS_DENIED
;
538 status
= schannel_store_challenge_tdb(db_sc
, frame
,
547 /********************************************************************
548 Validate an incoming authenticator against the credentials for the
549 remote machine stored in the schannel database.
551 The credentials are (re)read and from the schannel database, and
552 written back after the caclulations are performed.
554 If the creds_out parameter is not NULL returns the credentials.
555 ********************************************************************/
557 NTSTATUS
schannel_check_creds_state(TALLOC_CTX
*mem_ctx
,
558 struct loadparm_context
*lp_ctx
,
559 const char *computer_name
,
560 struct netr_Authenticator
*received_authenticator
,
561 struct netr_Authenticator
*return_authenticator
,
562 struct netlogon_creds_CredentialState
**creds_out
)
565 struct db_context
*db_sc
;
566 struct netlogon_creds_CredentialState
*creds
;
568 char *name_upper
= NULL
;
570 struct db_record
*record
;
573 if (creds_out
!= NULL
) {
577 tmpctx
= talloc_named(mem_ctx
, 0, "schannel_check_creds_state");
579 return NT_STATUS_NO_MEMORY
;
582 name_upper
= strupper_talloc(tmpctx
, computer_name
);
584 status
= NT_STATUS_NO_MEMORY
;
588 keystr
= talloc_asprintf(tmpctx
, "%s/%s",
589 SECRETS_SCHANNEL_STATE
, name_upper
);
591 status
= NT_STATUS_NO_MEMORY
;
595 key
= string_term_tdb_data(keystr
);
597 db_sc
= open_schannel_session_store(tmpctx
, lp_ctx
);
599 status
= NT_STATUS_ACCESS_DENIED
;
603 record
= dbwrap_fetch_locked(db_sc
, tmpctx
, key
);
605 status
= NT_STATUS_INTERNAL_DB_CORRUPTION
;
609 /* Because this is a shared structure (even across
610 * disconnects) we must update the database every time we
611 * update the structure */
613 status
= schannel_fetch_session_key_tdb(db_sc
, tmpctx
,
614 computer_name
, &creds
);
615 if (!NT_STATUS_IS_OK(status
)) {
619 status
= netlogon_creds_server_step_check(creds
,
620 received_authenticator
,
621 return_authenticator
);
622 if (!NT_STATUS_IS_OK(status
)) {
626 status
= schannel_store_session_key_tdb(db_sc
, tmpctx
, creds
);
627 if (!NT_STATUS_IS_OK(status
)) {
632 *creds_out
= talloc_steal(mem_ctx
, creds
);
634 status
= NT_STATUS_NO_MEMORY
;
639 status
= NT_STATUS_OK
;