selftest: temporary skip samba.blackbox.pdbtest.s4winbind
[Samba.git] / libcli / auth / schannel_state_tdb.c
blobd884279bdb2b847f53c9223f79643ee98d72c5df
1 /*
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/>.
24 #include "includes.h"
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");
46 if (!fname) {
47 return NULL;
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,
53 DBWRAP_FLAG_NONE);
55 if (!db_sc) {
56 DEBUG(0,("open_schannel_session_store: Failed to open %s - %s\n",
57 fname, strerror(errno)));
58 TALLOC_FREE(fname);
59 return NULL;
62 TALLOC_FREE(fname);
64 return db_sc;
67 /********************************************************************
68 ********************************************************************/
70 static
71 NTSTATUS schannel_store_session_key_tdb(struct db_context *db_sc,
72 TALLOC_CTX *mem_ctx,
73 struct netlogon_creds_CredentialState *creds)
75 enum ndr_err_code ndr_err;
76 DATA_BLOB blob;
77 TDB_DATA value;
78 char *keystr;
79 char *name_upper;
80 NTSTATUS status;
82 if (strlen(creds->computer_name) > 15) {
84 * We may want to check for a completely
85 * valid netbios name.
87 return STATUS_BUFFER_OVERFLOW;
90 name_upper = strupper_talloc(mem_ctx, creds->computer_name);
91 if (!name_upper) {
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);
98 if (!keystr) {
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)) {
105 talloc_free(keystr);
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)));
116 talloc_free(keystr);
117 return status;
120 DEBUG(3,("schannel_store_session_key_tdb: stored schannel info with key %s\n",
121 keystr));
123 if (DEBUGLEVEL >= 10) {
124 NDR_PRINT_DEBUG(netlogon_creds_CredentialState, creds);
127 talloc_free(keystr);
129 return NT_STATUS_OK;
132 /********************************************************************
133 ********************************************************************/
135 static
136 NTSTATUS schannel_fetch_session_key_tdb(struct db_context *db_sc,
137 TALLOC_CTX *mem_ctx,
138 const char *computer_name,
139 struct netlogon_creds_CredentialState **pcreds)
141 NTSTATUS status;
142 TDB_DATA value;
143 enum ndr_err_code ndr_err;
144 DATA_BLOB blob;
145 struct netlogon_creds_CredentialState *creds = NULL;
146 char *keystr = NULL;
147 char *name_upper;
149 *pcreds = NULL;
151 name_upper = strupper_talloc(mem_ctx, computer_name);
152 if (!name_upper) {
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);
159 if (!keystr) {
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",
166 keystr ));
167 goto done;
170 creds = talloc_zero(mem_ctx, struct netlogon_creds_CredentialState);
171 if (!creds) {
172 status = NT_STATUS_NO_MEMORY;
173 goto done;
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);
182 goto done;
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",
190 keystr));
192 status = NT_STATUS_OK;
194 done:
196 talloc_free(keystr);
198 if (!NT_STATUS_IS_OK(status)) {
199 talloc_free(creds);
200 return status;
203 *pcreds = creds;
205 return NT_STATUS_OK;
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)
218 TALLOC_CTX *tmpctx;
219 struct db_context *db_sc;
220 struct netlogon_creds_CredentialState *creds;
221 NTSTATUS status;
223 tmpctx = talloc_named(mem_ctx, 0, "schannel_get_creds_state");
224 if (!tmpctx) {
225 return NT_STATUS_NO_MEMORY;
228 db_sc = open_schannel_session_store(tmpctx, lp_ctx);
229 if (!db_sc) {
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);
237 if (!*_creds) {
238 status = NT_STATUS_NO_MEMORY;
242 talloc_free(tmpctx);
243 return status;
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)
255 TALLOC_CTX *tmpctx;
256 struct db_context *db_sc;
257 NTSTATUS status;
259 tmpctx = talloc_named(mem_ctx, 0, "schannel_save_creds_state");
260 if (!tmpctx) {
261 return NT_STATUS_NO_MEMORY;
264 db_sc = open_schannel_session_store(tmpctx, lp_ctx);
265 if (!db_sc) {
266 return NT_STATUS_ACCESS_DENIED;
269 status = schannel_store_session_key_tdb(db_sc, tmpctx, creds);
271 talloc_free(tmpctx);
272 return status;
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
287 * packets.
289 static void hash_computer_name(const char *computer_name,
290 char keystr[16])
292 unsigned int hash;
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);
303 return;
307 static
308 NTSTATUS schannel_store_challenge_tdb(struct db_context *db_sc,
309 TALLOC_CTX *mem_ctx,
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;
315 DATA_BLOB blob;
316 TDB_DATA value;
317 char *name_upper = NULL;
318 NTSTATUS status;
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,
355 nt_errstr(status)));
356 return status;
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);
366 return NT_STATUS_OK;
369 /********************************************************************
370 Fetch a single challenge from the TDB.
371 ********************************************************************/
373 static
374 NTSTATUS schannel_fetch_challenge_tdb(struct db_context *db_sc,
375 TALLOC_CTX *mem_ctx,
376 struct netr_Credential *client_challenge,
377 struct netr_Credential *server_challenge,
378 const char *computer_name)
380 NTSTATUS status;
381 TDB_DATA value;
382 enum ndr_err_code ndr_err;
383 DATA_BLOB blob;
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)));
399 goto done;
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)));
410 goto done;
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));
424 } else {
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;
432 done:
434 if (!NT_STATUS_IS_OK(status)) {
435 return status;
438 return NT_STATUS_OK;
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;
454 NTSTATUS status;
456 db_sc = open_schannel_session_store(frame, lp_ctx);
457 if (!db_sc) {
458 TALLOC_FREE(frame);
459 return NT_STATUS_ACCESS_DENIED;
462 status = schannel_fetch_challenge_tdb(db_sc, frame,
463 client_challenge,
464 server_challenge,
465 computer_name);
466 TALLOC_FREE(frame);
467 return status;
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;
483 char *name_upper;
484 char keystr[16] = { 0, };
486 db_sc = open_schannel_session_store(frame, lp_ctx);
487 if (!db_sc) {
488 TALLOC_FREE(frame);
489 return NT_STATUS_ACCESS_DENIED;
492 name_upper = strupper_talloc(frame, computer_name);
493 if (!name_upper) {
494 TALLOC_FREE(frame);
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);
503 TALLOC_FREE(frame);
504 return NT_STATUS_OK;
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;
519 NTSTATUS status;
521 db_sc = open_schannel_session_store(frame, lp_ctx);
522 if (!db_sc) {
523 TALLOC_FREE(frame);
524 return NT_STATUS_ACCESS_DENIED;
527 status = schannel_store_challenge_tdb(db_sc, frame,
528 client_challenge,
529 server_challenge,
530 computer_name);
532 TALLOC_FREE(frame);
533 return status;
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)
553 TALLOC_CTX *tmpctx;
554 struct db_context *db_sc;
555 struct netlogon_creds_CredentialState *creds;
556 NTSTATUS status;
557 char *name_upper = NULL;
558 char *keystr = NULL;
559 struct db_record *record;
560 TDB_DATA key;
562 if (creds_out != NULL) {
563 *creds_out = NULL;
566 tmpctx = talloc_named(mem_ctx, 0, "schannel_check_creds_state");
567 if (!tmpctx) {
568 return NT_STATUS_NO_MEMORY;
571 name_upper = strupper_talloc(tmpctx, computer_name);
572 if (!name_upper) {
573 status = NT_STATUS_NO_MEMORY;
574 goto done;
577 keystr = talloc_asprintf(tmpctx, "%s/%s",
578 SECRETS_SCHANNEL_STATE, name_upper);
579 if (!keystr) {
580 status = NT_STATUS_NO_MEMORY;
581 goto done;
584 key = string_term_tdb_data(keystr);
586 db_sc = open_schannel_session_store(tmpctx, lp_ctx);
587 if (!db_sc) {
588 status = NT_STATUS_ACCESS_DENIED;
589 goto done;
592 record = dbwrap_fetch_locked(db_sc, tmpctx, key);
593 if (!record) {
594 status = NT_STATUS_INTERNAL_DB_CORRUPTION;
595 goto done;
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)) {
605 goto done;
608 status = netlogon_creds_server_step_check(creds,
609 received_authenticator,
610 return_authenticator);
611 if (!NT_STATUS_IS_OK(status)) {
612 goto done;
615 status = schannel_store_session_key_tdb(db_sc, tmpctx, creds);
616 if (!NT_STATUS_IS_OK(status)) {
617 goto done;
620 if (creds_out) {
621 *creds_out = talloc_steal(mem_ctx, creds);
622 if (!*creds_out) {
623 status = NT_STATUS_NO_MEMORY;
624 goto done;
628 status = NT_STATUS_OK;
630 done:
631 talloc_free(tmpctx);
632 return status;