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 correctly 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
);
240 return NT_STATUS_ACCESS_DENIED
;
243 status
= schannel_fetch_session_key_tdb(db_sc
, tmpctx
,
244 computer_name
, &creds
);
245 if (NT_STATUS_IS_OK(status
)) {
246 *_creds
= talloc_steal(mem_ctx
, creds
);
248 status
= NT_STATUS_NO_MEMORY
;
256 /******************************************************************************
257 Wrapper around schannel_store_session_key_tdb()
258 Note we must be root here.
259 *******************************************************************************/
261 NTSTATUS
schannel_save_creds_state(TALLOC_CTX
*mem_ctx
,
262 struct loadparm_context
*lp_ctx
,
263 struct netlogon_creds_CredentialState
*creds
)
266 struct db_context
*db_sc
;
269 tmpctx
= talloc_named(mem_ctx
, 0, "schannel_save_creds_state");
271 return NT_STATUS_NO_MEMORY
;
274 db_sc
= open_schannel_session_store(tmpctx
, lp_ctx
);
276 status
= NT_STATUS_ACCESS_DENIED
;
280 status
= schannel_store_session_key_tdb(db_sc
, tmpctx
, creds
);
289 * Create a very lossy hash of the computer name.
291 * The idea here is to compress the computer name into small space so
292 * that malicious clients cannot fill the database with junk, as only a
293 * maximum of 16k of entries are possible.
295 * Collisions are certainly possible, and the design behaves in the
296 * same way as when the hostname is reused, but clients that use the
297 * same connection do not go via the cache, and the cache only needs
298 * to function between the ReqChallenge and ServerAuthenticate
301 static void hash_computer_name(const char *computer_name
,
305 TDB_DATA computer_tdb_data
= {
306 .dptr
= (uint8_t *)discard_const_p(char, computer_name
),
307 .dsize
= strlen(computer_name
)
309 hash
= tdb_jenkins_hash(&computer_tdb_data
);
311 /* we are using 14 bits of the digest to index our connections, so
312 that we use at most 16,384 buckets.*/
313 snprintf(keystr
, 15, "CHALLENGE/%x%x", hash
& 0xFF,
314 (hash
& 0xFF00 >> 8) & 0x3f);
320 NTSTATUS
schannel_store_challenge_tdb(struct db_context
*db_sc
,
322 const struct netr_Credential
*client_challenge
,
323 const struct netr_Credential
*server_challenge
,
324 const char *computer_name
)
326 enum ndr_err_code ndr_err
;
329 char *name_upper
= NULL
;
331 char keystr
[16] = { 0, };
332 struct netlogon_cache_entry cache_entry
;
334 if (strlen(computer_name
) > 255) {
336 * We don't make this a limit at 15 chars as Samba has
337 * a test showing this can be longer :-(
339 return STATUS_BUFFER_OVERFLOW
;
342 name_upper
= strupper_talloc(mem_ctx
, computer_name
);
343 if (name_upper
== NULL
) {
344 return NT_STATUS_NO_MEMORY
;
347 hash_computer_name(name_upper
, keystr
);
349 cache_entry
.computer_name
= name_upper
;
350 cache_entry
.client_challenge
= *client_challenge
;
351 cache_entry
.server_challenge
= *server_challenge
;
353 ndr_err
= ndr_push_struct_blob(&blob
, mem_ctx
, &cache_entry
,
354 (ndr_push_flags_fn_t
)ndr_push_netlogon_cache_entry
);
355 if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err
)) {
356 return NT_STATUS_UNSUCCESSFUL
;
359 value
.dptr
= blob
.data
;
360 value
.dsize
= blob
.length
;
362 status
= dbwrap_store_bystring(db_sc
, keystr
, value
, TDB_REPLACE
);
363 if (!NT_STATUS_IS_OK(status
)) {
364 DEBUG(0,("%s: failed to stored challenge info for '%s' "
365 "with key %s - %s\n",
366 __func__
, cache_entry
.computer_name
, keystr
,
371 DEBUG(3,("%s: stored challenge info for '%s' with key %s\n",
372 __func__
, cache_entry
.computer_name
, keystr
));
374 if (DEBUGLEVEL
>= 10) {
375 NDR_PRINT_DEBUG(netlogon_cache_entry
, &cache_entry
);
381 /********************************************************************
382 Fetch a single challenge from the TDB.
383 ********************************************************************/
386 NTSTATUS
schannel_fetch_challenge_tdb(struct db_context
*db_sc
,
388 struct netr_Credential
*client_challenge
,
389 struct netr_Credential
*server_challenge
,
390 const char *computer_name
)
394 enum ndr_err_code ndr_err
;
396 char keystr
[16] = { 0, };
397 struct netlogon_cache_entry cache_entry
;
398 char *name_upper
= NULL
;
400 name_upper
= strupper_talloc(mem_ctx
, computer_name
);
401 if (name_upper
== NULL
) {
402 return NT_STATUS_NO_MEMORY
;
405 hash_computer_name(name_upper
, keystr
);
407 status
= dbwrap_fetch_bystring(db_sc
, mem_ctx
, keystr
, &value
);
408 if (!NT_STATUS_IS_OK(status
)) {
409 DEBUG(3,("%s: Failed to find entry for %s with key %s - %s\n",
410 __func__
, name_upper
, keystr
, nt_errstr(status
)));
414 blob
= data_blob_const(value
.dptr
, value
.dsize
);
416 ndr_err
= ndr_pull_struct_blob_all(&blob
, mem_ctx
, &cache_entry
,
417 (ndr_pull_flags_fn_t
)ndr_pull_netlogon_cache_entry
);
418 if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err
)) {
419 status
= ndr_map_error2ntstatus(ndr_err
);
420 DEBUG(3,("%s: Failed to parse entry for %s with key %s - %s\n",
421 __func__
, name_upper
, keystr
, nt_errstr(status
)));
425 if (DEBUGLEVEL
>= 10) {
426 NDR_PRINT_DEBUG(netlogon_cache_entry
, &cache_entry
);
429 if (strcmp(cache_entry
.computer_name
, name_upper
) != 0) {
430 status
= NT_STATUS_NOT_FOUND
;
432 DEBUG(1, ("%s: HASH COLLISION with key %s ! "
433 "Wanted to fetch record for %s but got %s.",
434 __func__
, keystr
, name_upper
,
435 cache_entry
.computer_name
));
438 DEBUG(3,("%s: restored key %s for %s\n",
439 __func__
, keystr
, cache_entry
.computer_name
));
441 *client_challenge
= cache_entry
.client_challenge
;
442 *server_challenge
= cache_entry
.server_challenge
;
446 if (!NT_STATUS_IS_OK(status
)) {
453 /******************************************************************************
454 Wrapper around schannel_fetch_challenge_tdb()
455 Note we must be root here.
457 *******************************************************************************/
459 NTSTATUS
schannel_get_challenge(struct loadparm_context
*lp_ctx
,
460 struct netr_Credential
*client_challenge
,
461 struct netr_Credential
*server_challenge
,
462 const char *computer_name
)
464 TALLOC_CTX
*frame
= talloc_stackframe();
465 struct db_context
*db_sc
;
468 db_sc
= open_schannel_session_store(frame
, lp_ctx
);
471 return NT_STATUS_ACCESS_DENIED
;
474 status
= schannel_fetch_challenge_tdb(db_sc
, frame
,
482 /******************************************************************************
483 Wrapper around dbwrap_delete_bystring()
484 Note we must be root here.
486 This allows the challenge to be removed from the TDB, which should be
487 as soon as the TDB or in-memory copy it is used, to avoid reuse.
488 *******************************************************************************/
490 NTSTATUS
schannel_delete_challenge(struct loadparm_context
*lp_ctx
,
491 const char *computer_name
)
493 TALLOC_CTX
*frame
= talloc_stackframe();
494 struct db_context
*db_sc
;
496 char keystr
[16] = { 0, };
498 db_sc
= open_schannel_session_store(frame
, lp_ctx
);
501 return NT_STATUS_ACCESS_DENIED
;
504 name_upper
= strupper_talloc(frame
, computer_name
);
507 return NT_STATUS_NO_MEMORY
;
510 hash_computer_name(name_upper
, keystr
);
512 /* Now delete it, we do not want to permit fetch of this twice */
513 dbwrap_delete_bystring(db_sc
, keystr
);
519 /******************************************************************************
520 Wrapper around schannel_store_session_key_tdb()
521 Note we must be root here.
522 *******************************************************************************/
524 NTSTATUS
schannel_save_challenge(struct loadparm_context
*lp_ctx
,
525 const struct netr_Credential
*client_challenge
,
526 const struct netr_Credential
*server_challenge
,
527 const char *computer_name
)
529 TALLOC_CTX
*frame
= talloc_stackframe();
530 struct db_context
*db_sc
;
533 db_sc
= open_schannel_session_store(frame
, lp_ctx
);
536 return NT_STATUS_ACCESS_DENIED
;
539 status
= schannel_store_challenge_tdb(db_sc
, frame
,
548 /********************************************************************
549 Validate an incoming authenticator against the credentials for the
550 remote machine stored in the schannel database.
552 The credentials are (re)read and from the schannel database, and
553 written back after the calculations are performed.
555 If the creds_out parameter is not NULL returns the credentials.
556 ********************************************************************/
558 NTSTATUS
schannel_check_creds_state(TALLOC_CTX
*mem_ctx
,
559 struct loadparm_context
*lp_ctx
,
560 const char *computer_name
,
561 struct netr_Authenticator
*received_authenticator
,
562 struct netr_Authenticator
*return_authenticator
,
563 struct netlogon_creds_CredentialState
**creds_out
)
566 struct db_context
*db_sc
;
567 struct netlogon_creds_CredentialState
*creds
;
569 char *name_upper
= NULL
;
571 struct db_record
*record
;
574 if (creds_out
!= NULL
) {
578 tmpctx
= talloc_named(mem_ctx
, 0, "schannel_check_creds_state");
580 return NT_STATUS_NO_MEMORY
;
583 name_upper
= strupper_talloc(tmpctx
, computer_name
);
585 status
= NT_STATUS_NO_MEMORY
;
589 keystr
= talloc_asprintf(tmpctx
, "%s/%s",
590 SECRETS_SCHANNEL_STATE
, name_upper
);
592 status
= NT_STATUS_NO_MEMORY
;
596 key
= string_term_tdb_data(keystr
);
598 db_sc
= open_schannel_session_store(tmpctx
, lp_ctx
);
600 status
= NT_STATUS_ACCESS_DENIED
;
604 record
= dbwrap_fetch_locked(db_sc
, tmpctx
, key
);
606 status
= NT_STATUS_INTERNAL_DB_CORRUPTION
;
610 /* Because this is a shared structure (even across
611 * disconnects) we must update the database every time we
612 * update the structure */
614 status
= schannel_fetch_session_key_tdb(db_sc
, tmpctx
,
615 computer_name
, &creds
);
616 if (!NT_STATUS_IS_OK(status
)) {
620 status
= netlogon_creds_server_step_check(creds
,
621 received_authenticator
,
622 return_authenticator
);
623 if (!NT_STATUS_IS_OK(status
)) {
627 status
= schannel_store_session_key_tdb(db_sc
, tmpctx
, creds
);
628 if (!NT_STATUS_IS_OK(status
)) {
633 *creds_out
= talloc_steal(mem_ctx
, creds
);
635 status
= NT_STATUS_NO_MEMORY
;
640 status
= NT_STATUS_OK
;