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 status
= NT_STATUS_ACCESS_DENIED
;
270 status
= schannel_store_session_key_tdb(db_sc
, tmpctx
, creds
);
279 * Create a very lossy hash of the computer name.
281 * The idea here is to compress the computer name into small space so
282 * that malicious clients cannot fill the database with junk, as only a
283 * maximum of 16k of entries are possible.
285 * Collisions are certainly possible, and the design behaves in the
286 * same way as when the hostname is reused, but clients that use the
287 * same connection do not go via the cache, and the cache only needs
288 * to function between the ReqChallenge and ServerAuthenticate
291 static void hash_computer_name(const char *computer_name
,
295 TDB_DATA computer_tdb_data
= {
296 .dptr
= (uint8_t *)discard_const_p(char, computer_name
),
297 .dsize
= strlen(computer_name
)
299 hash
= tdb_jenkins_hash(&computer_tdb_data
);
301 /* we are using 14 bits of the digest to index our connections, so
302 that we use at most 16,384 buckets.*/
303 snprintf(keystr
, 15, "CHALLENGE/%x%x", hash
& 0xFF,
304 (hash
& 0xFF00 >> 8) & 0x3f);
310 NTSTATUS
schannel_store_challenge_tdb(struct db_context
*db_sc
,
312 const struct netr_Credential
*client_challenge
,
313 const struct netr_Credential
*server_challenge
,
314 const char *computer_name
)
316 enum ndr_err_code ndr_err
;
319 char *name_upper
= NULL
;
321 char keystr
[16] = { 0, };
322 struct netlogon_cache_entry cache_entry
;
324 if (strlen(computer_name
) > 255) {
326 * We don't make this a limit at 15 chars as Samba has
327 * a test showing this can be longer :-(
329 return STATUS_BUFFER_OVERFLOW
;
332 name_upper
= strupper_talloc(mem_ctx
, computer_name
);
333 if (name_upper
== NULL
) {
334 return NT_STATUS_NO_MEMORY
;
337 hash_computer_name(name_upper
, keystr
);
339 cache_entry
.computer_name
= name_upper
;
340 cache_entry
.client_challenge
= *client_challenge
;
341 cache_entry
.server_challenge
= *server_challenge
;
343 ndr_err
= ndr_push_struct_blob(&blob
, mem_ctx
, &cache_entry
,
344 (ndr_push_flags_fn_t
)ndr_push_netlogon_cache_entry
);
345 if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err
)) {
346 return NT_STATUS_UNSUCCESSFUL
;
349 value
.dptr
= blob
.data
;
350 value
.dsize
= blob
.length
;
352 status
= dbwrap_store_bystring(db_sc
, keystr
, value
, TDB_REPLACE
);
353 if (!NT_STATUS_IS_OK(status
)) {
354 DEBUG(0,("%s: failed to stored challenge info for '%s' "
355 "with key %s - %s\n",
356 __func__
, cache_entry
.computer_name
, keystr
,
361 DEBUG(3,("%s: stored challenge info for '%s' with key %s\n",
362 __func__
, cache_entry
.computer_name
, keystr
));
364 if (DEBUGLEVEL
>= 10) {
365 NDR_PRINT_DEBUG(netlogon_cache_entry
, &cache_entry
);
371 /********************************************************************
372 Fetch a single challenge from the TDB.
373 ********************************************************************/
376 NTSTATUS
schannel_fetch_challenge_tdb(struct db_context
*db_sc
,
378 struct netr_Credential
*client_challenge
,
379 struct netr_Credential
*server_challenge
,
380 const char *computer_name
)
384 enum ndr_err_code ndr_err
;
386 char keystr
[16] = { 0, };
387 struct netlogon_cache_entry cache_entry
;
388 char *name_upper
= NULL
;
390 name_upper
= strupper_talloc(mem_ctx
, computer_name
);
391 if (name_upper
== NULL
) {
392 return NT_STATUS_NO_MEMORY
;
395 hash_computer_name(name_upper
, keystr
);
397 status
= dbwrap_fetch_bystring(db_sc
, mem_ctx
, keystr
, &value
);
398 if (!NT_STATUS_IS_OK(status
)) {
399 DEBUG(3,("%s: Failed to find entry for %s with key %s - %s\n",
400 __func__
, name_upper
, keystr
, nt_errstr(status
)));
404 blob
= data_blob_const(value
.dptr
, value
.dsize
);
406 ndr_err
= ndr_pull_struct_blob_all(&blob
, mem_ctx
, &cache_entry
,
407 (ndr_pull_flags_fn_t
)ndr_pull_netlogon_cache_entry
);
408 if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err
)) {
409 status
= ndr_map_error2ntstatus(ndr_err
);
410 DEBUG(3,("%s: Failed to parse entry for %s with key %s - %s\n",
411 __func__
, name_upper
, keystr
, nt_errstr(status
)));
415 if (DEBUGLEVEL
>= 10) {
416 NDR_PRINT_DEBUG(netlogon_cache_entry
, &cache_entry
);
419 if (strcmp(cache_entry
.computer_name
, name_upper
) != 0) {
420 status
= NT_STATUS_NOT_FOUND
;
422 DEBUG(1, ("%s: HASH COLLISION with key %s ! "
423 "Wanted to fetch record for %s but got %s.",
424 __func__
, keystr
, name_upper
,
425 cache_entry
.computer_name
));
428 DEBUG(3,("%s: restored key %s for %s\n",
429 __func__
, keystr
, cache_entry
.computer_name
));
431 *client_challenge
= cache_entry
.client_challenge
;
432 *server_challenge
= cache_entry
.server_challenge
;
436 if (!NT_STATUS_IS_OK(status
)) {
443 /******************************************************************************
444 Wrapper around schannel_fetch_challenge_tdb()
445 Note we must be root here.
447 *******************************************************************************/
449 NTSTATUS
schannel_get_challenge(struct loadparm_context
*lp_ctx
,
450 struct netr_Credential
*client_challenge
,
451 struct netr_Credential
*server_challenge
,
452 const char *computer_name
)
454 TALLOC_CTX
*frame
= talloc_stackframe();
455 struct db_context
*db_sc
;
458 db_sc
= open_schannel_session_store(frame
, lp_ctx
);
461 return NT_STATUS_ACCESS_DENIED
;
464 status
= schannel_fetch_challenge_tdb(db_sc
, frame
,
472 /******************************************************************************
473 Wrapper around dbwrap_delete_bystring()
474 Note we must be root here.
476 This allows the challenge to be removed from the TDB, which should be
477 as soon as the TDB or in-memory copy it is used, to avoid reuse.
478 *******************************************************************************/
480 NTSTATUS
schannel_delete_challenge(struct loadparm_context
*lp_ctx
,
481 const char *computer_name
)
483 TALLOC_CTX
*frame
= talloc_stackframe();
484 struct db_context
*db_sc
;
486 char keystr
[16] = { 0, };
488 db_sc
= open_schannel_session_store(frame
, lp_ctx
);
491 return NT_STATUS_ACCESS_DENIED
;
494 name_upper
= strupper_talloc(frame
, computer_name
);
497 return NT_STATUS_NO_MEMORY
;
500 hash_computer_name(name_upper
, keystr
);
502 /* Now delete it, we do not want to permit fetch of this twice */
503 dbwrap_delete_bystring(db_sc
, keystr
);
509 /******************************************************************************
510 Wrapper around schannel_store_session_key_tdb()
511 Note we must be root here.
512 *******************************************************************************/
514 NTSTATUS
schannel_save_challenge(struct loadparm_context
*lp_ctx
,
515 const struct netr_Credential
*client_challenge
,
516 const struct netr_Credential
*server_challenge
,
517 const char *computer_name
)
519 TALLOC_CTX
*frame
= talloc_stackframe();
520 struct db_context
*db_sc
;
523 db_sc
= open_schannel_session_store(frame
, lp_ctx
);
526 return NT_STATUS_ACCESS_DENIED
;
529 status
= schannel_store_challenge_tdb(db_sc
, frame
,
538 /********************************************************************
539 Validate an incoming authenticator against the credentials for the
540 remote machine stored in the schannel database.
542 The credentials are (re)read and from the schannel database, and
543 written back after the caclulations are performed.
545 If the creds_out parameter is not NULL returns the credentials.
546 ********************************************************************/
548 NTSTATUS
schannel_check_creds_state(TALLOC_CTX
*mem_ctx
,
549 struct loadparm_context
*lp_ctx
,
550 const char *computer_name
,
551 struct netr_Authenticator
*received_authenticator
,
552 struct netr_Authenticator
*return_authenticator
,
553 struct netlogon_creds_CredentialState
**creds_out
)
556 struct db_context
*db_sc
;
557 struct netlogon_creds_CredentialState
*creds
;
559 char *name_upper
= NULL
;
561 struct db_record
*record
;
564 if (creds_out
!= NULL
) {
568 tmpctx
= talloc_named(mem_ctx
, 0, "schannel_check_creds_state");
570 return NT_STATUS_NO_MEMORY
;
573 name_upper
= strupper_talloc(tmpctx
, computer_name
);
575 status
= NT_STATUS_NO_MEMORY
;
579 keystr
= talloc_asprintf(tmpctx
, "%s/%s",
580 SECRETS_SCHANNEL_STATE
, name_upper
);
582 status
= NT_STATUS_NO_MEMORY
;
586 key
= string_term_tdb_data(keystr
);
588 db_sc
= open_schannel_session_store(tmpctx
, lp_ctx
);
590 status
= NT_STATUS_ACCESS_DENIED
;
594 record
= dbwrap_fetch_locked(db_sc
, tmpctx
, key
);
596 status
= NT_STATUS_INTERNAL_DB_CORRUPTION
;
600 /* Because this is a shared structure (even across
601 * disconnects) we must update the database every time we
602 * update the structure */
604 status
= schannel_fetch_session_key_tdb(db_sc
, tmpctx
,
605 computer_name
, &creds
);
606 if (!NT_STATUS_IS_OK(status
)) {
610 status
= netlogon_creds_server_step_check(creds
,
611 received_authenticator
,
612 return_authenticator
);
613 if (!NT_STATUS_IS_OK(status
)) {
617 status
= schannel_store_session_key_tdb(db_sc
, tmpctx
, creds
);
618 if (!NT_STATUS_IS_OK(status
)) {
623 *creds_out
= talloc_steal(mem_ctx
, creds
);
625 status
= NT_STATUS_NO_MEMORY
;
630 status
= NT_STATUS_OK
;