wafsamba: remove unused Build.BuildContext.pre_build overload
[Samba.git] / libcli / auth / schannel_state_tdb.c
blobce20012ac225d728195a5204aa00ef17b1b3101e
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");
45 int hash_size, tdb_flags;
47 if (!fname) {
48 return NULL;
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(
55 mem_ctx,
56 fname,
57 hash_size,
58 tdb_flags,
59 O_RDWR|O_CREAT,
60 0600,
61 DBWRAP_LOCK_ORDER_NONE,
62 DBWRAP_FLAG_NONE);
64 if (!db_sc) {
65 DEBUG(0,("open_schannel_session_store: Failed to open %s - %s\n",
66 fname, strerror(errno)));
67 TALLOC_FREE(fname);
68 return NULL;
71 TALLOC_FREE(fname);
73 return db_sc;
76 /********************************************************************
77 ********************************************************************/
79 static
80 NTSTATUS schannel_store_session_key_tdb(struct db_context *db_sc,
81 TALLOC_CTX *mem_ctx,
82 struct netlogon_creds_CredentialState *creds)
84 enum ndr_err_code ndr_err;
85 DATA_BLOB blob;
86 TDB_DATA value;
87 char *keystr;
88 char *name_upper;
89 NTSTATUS status;
91 if (strlen(creds->computer_name) > 15) {
93 * We may want to check for a completely
94 * valid netbios name.
96 return STATUS_BUFFER_OVERFLOW;
99 name_upper = strupper_talloc(mem_ctx, creds->computer_name);
100 if (!name_upper) {
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);
107 if (!keystr) {
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)) {
114 talloc_free(keystr);
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)));
125 talloc_free(keystr);
126 return status;
129 DEBUG(3,("schannel_store_session_key_tdb: stored schannel info with key %s\n",
130 keystr));
132 if (DEBUGLEVEL >= 10) {
133 NDR_PRINT_DEBUG(netlogon_creds_CredentialState, creds);
136 talloc_free(keystr);
138 return NT_STATUS_OK;
141 /********************************************************************
142 ********************************************************************/
144 static
145 NTSTATUS schannel_fetch_session_key_tdb(struct db_context *db_sc,
146 TALLOC_CTX *mem_ctx,
147 const char *computer_name,
148 struct netlogon_creds_CredentialState **pcreds)
150 NTSTATUS status;
151 TDB_DATA value;
152 enum ndr_err_code ndr_err;
153 DATA_BLOB blob;
154 struct netlogon_creds_CredentialState *creds = NULL;
155 char *keystr = NULL;
156 char *name_upper;
158 *pcreds = NULL;
160 name_upper = strupper_talloc(mem_ctx, computer_name);
161 if (!name_upper) {
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);
168 if (!keystr) {
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",
175 keystr ));
176 goto done;
179 creds = talloc_zero(mem_ctx, struct netlogon_creds_CredentialState);
180 if (!creds) {
181 status = NT_STATUS_NO_MEMORY;
182 goto done;
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);
191 goto done;
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",
199 keystr));
201 status = NT_STATUS_OK;
203 done:
205 talloc_free(keystr);
207 if (!NT_STATUS_IS_OK(status)) {
208 talloc_free(creds);
209 return status;
212 *pcreds = creds;
214 return NT_STATUS_OK;
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)
227 TALLOC_CTX *tmpctx;
228 struct db_context *db_sc;
229 struct netlogon_creds_CredentialState *creds;
230 NTSTATUS status;
232 tmpctx = talloc_named(mem_ctx, 0, "schannel_get_creds_state");
233 if (!tmpctx) {
234 return NT_STATUS_NO_MEMORY;
237 db_sc = open_schannel_session_store(tmpctx, lp_ctx);
238 if (!db_sc) {
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);
246 if (!*_creds) {
247 status = NT_STATUS_NO_MEMORY;
251 talloc_free(tmpctx);
252 return status;
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)
264 TALLOC_CTX *tmpctx;
265 struct db_context *db_sc;
266 NTSTATUS status;
268 tmpctx = talloc_named(mem_ctx, 0, "schannel_save_creds_state");
269 if (!tmpctx) {
270 return NT_STATUS_NO_MEMORY;
273 db_sc = open_schannel_session_store(tmpctx, lp_ctx);
274 if (!db_sc) {
275 status = NT_STATUS_ACCESS_DENIED;
276 goto fail;
279 status = schannel_store_session_key_tdb(db_sc, tmpctx, creds);
281 fail:
282 talloc_free(tmpctx);
283 return status;
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
298 * packets.
300 static void hash_computer_name(const char *computer_name,
301 char keystr[16])
303 unsigned int hash;
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);
314 return;
318 static
319 NTSTATUS schannel_store_challenge_tdb(struct db_context *db_sc,
320 TALLOC_CTX *mem_ctx,
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;
326 DATA_BLOB blob;
327 TDB_DATA value;
328 char *name_upper = NULL;
329 NTSTATUS status;
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,
366 nt_errstr(status)));
367 return status;
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);
377 return NT_STATUS_OK;
380 /********************************************************************
381 Fetch a single challenge from the TDB.
382 ********************************************************************/
384 static
385 NTSTATUS schannel_fetch_challenge_tdb(struct db_context *db_sc,
386 TALLOC_CTX *mem_ctx,
387 struct netr_Credential *client_challenge,
388 struct netr_Credential *server_challenge,
389 const char *computer_name)
391 NTSTATUS status;
392 TDB_DATA value;
393 enum ndr_err_code ndr_err;
394 DATA_BLOB blob;
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)));
410 goto done;
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)));
421 goto done;
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));
435 } else {
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;
443 done:
445 if (!NT_STATUS_IS_OK(status)) {
446 return status;
449 return NT_STATUS_OK;
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;
465 NTSTATUS status;
467 db_sc = open_schannel_session_store(frame, lp_ctx);
468 if (!db_sc) {
469 TALLOC_FREE(frame);
470 return NT_STATUS_ACCESS_DENIED;
473 status = schannel_fetch_challenge_tdb(db_sc, frame,
474 client_challenge,
475 server_challenge,
476 computer_name);
477 TALLOC_FREE(frame);
478 return status;
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;
494 char *name_upper;
495 char keystr[16] = { 0, };
497 db_sc = open_schannel_session_store(frame, lp_ctx);
498 if (!db_sc) {
499 TALLOC_FREE(frame);
500 return NT_STATUS_ACCESS_DENIED;
503 name_upper = strupper_talloc(frame, computer_name);
504 if (!name_upper) {
505 TALLOC_FREE(frame);
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);
514 TALLOC_FREE(frame);
515 return NT_STATUS_OK;
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;
530 NTSTATUS status;
532 db_sc = open_schannel_session_store(frame, lp_ctx);
533 if (!db_sc) {
534 TALLOC_FREE(frame);
535 return NT_STATUS_ACCESS_DENIED;
538 status = schannel_store_challenge_tdb(db_sc, frame,
539 client_challenge,
540 server_challenge,
541 computer_name);
543 TALLOC_FREE(frame);
544 return status;
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)
564 TALLOC_CTX *tmpctx;
565 struct db_context *db_sc;
566 struct netlogon_creds_CredentialState *creds;
567 NTSTATUS status;
568 char *name_upper = NULL;
569 char *keystr = NULL;
570 struct db_record *record;
571 TDB_DATA key;
573 if (creds_out != NULL) {
574 *creds_out = NULL;
577 tmpctx = talloc_named(mem_ctx, 0, "schannel_check_creds_state");
578 if (!tmpctx) {
579 return NT_STATUS_NO_MEMORY;
582 name_upper = strupper_talloc(tmpctx, computer_name);
583 if (!name_upper) {
584 status = NT_STATUS_NO_MEMORY;
585 goto done;
588 keystr = talloc_asprintf(tmpctx, "%s/%s",
589 SECRETS_SCHANNEL_STATE, name_upper);
590 if (!keystr) {
591 status = NT_STATUS_NO_MEMORY;
592 goto done;
595 key = string_term_tdb_data(keystr);
597 db_sc = open_schannel_session_store(tmpctx, lp_ctx);
598 if (!db_sc) {
599 status = NT_STATUS_ACCESS_DENIED;
600 goto done;
603 record = dbwrap_fetch_locked(db_sc, tmpctx, key);
604 if (!record) {
605 status = NT_STATUS_INTERNAL_DB_CORRUPTION;
606 goto done;
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)) {
616 goto done;
619 status = netlogon_creds_server_step_check(creds,
620 received_authenticator,
621 return_authenticator);
622 if (!NT_STATUS_IS_OK(status)) {
623 goto done;
626 status = schannel_store_session_key_tdb(db_sc, tmpctx, creds);
627 if (!NT_STATUS_IS_OK(status)) {
628 goto done;
631 if (creds_out) {
632 *creds_out = talloc_steal(mem_ctx, creds);
633 if (!*creds_out) {
634 status = NT_STATUS_NO_MEMORY;
635 goto done;
639 status = NT_STATUS_OK;
641 done:
642 talloc_free(tmpctx);
643 return status;