s3:printing: Allow to run samba-bgqd as a standalone systemd service
[Samba.git] / libcli / auth / schannel_state_tdb.c
blob2454a433819447795aca00423fdeee5e3ca36a88
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 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;
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 TALLOC_FREE(tmpctx);
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);
247 if (!*_creds) {
248 status = NT_STATUS_NO_MEMORY;
252 talloc_free(tmpctx);
253 return status;
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)
265 TALLOC_CTX *tmpctx;
266 struct db_context *db_sc;
267 NTSTATUS status;
269 tmpctx = talloc_named(mem_ctx, 0, "schannel_save_creds_state");
270 if (!tmpctx) {
271 return NT_STATUS_NO_MEMORY;
274 db_sc = open_schannel_session_store(tmpctx, lp_ctx);
275 if (!db_sc) {
276 status = NT_STATUS_ACCESS_DENIED;
277 goto fail;
280 status = schannel_store_session_key_tdb(db_sc, tmpctx, creds);
282 fail:
283 talloc_free(tmpctx);
284 return status;
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
299 * packets.
301 static void hash_computer_name(const char *computer_name,
302 char keystr[16])
304 unsigned int hash;
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);
315 return;
319 static
320 NTSTATUS schannel_store_challenge_tdb(struct db_context *db_sc,
321 TALLOC_CTX *mem_ctx,
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;
327 DATA_BLOB blob;
328 TDB_DATA value;
329 char *name_upper = NULL;
330 NTSTATUS status;
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,
367 nt_errstr(status)));
368 return status;
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);
378 return NT_STATUS_OK;
381 /********************************************************************
382 Fetch a single challenge from the TDB.
383 ********************************************************************/
385 static
386 NTSTATUS schannel_fetch_challenge_tdb(struct db_context *db_sc,
387 TALLOC_CTX *mem_ctx,
388 struct netr_Credential *client_challenge,
389 struct netr_Credential *server_challenge,
390 const char *computer_name)
392 NTSTATUS status;
393 TDB_DATA value;
394 enum ndr_err_code ndr_err;
395 DATA_BLOB blob;
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)));
411 goto done;
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)));
422 goto done;
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.\n",
434 __func__, keystr, name_upper,
435 cache_entry.computer_name));
436 } else {
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;
444 done:
446 if (!NT_STATUS_IS_OK(status)) {
447 return status;
450 return NT_STATUS_OK;
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;
466 NTSTATUS status;
468 db_sc = open_schannel_session_store(frame, lp_ctx);
469 if (!db_sc) {
470 TALLOC_FREE(frame);
471 return NT_STATUS_ACCESS_DENIED;
474 status = schannel_fetch_challenge_tdb(db_sc, frame,
475 client_challenge,
476 server_challenge,
477 computer_name);
478 TALLOC_FREE(frame);
479 return status;
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;
495 char *name_upper;
496 char keystr[16] = { 0, };
498 db_sc = open_schannel_session_store(frame, lp_ctx);
499 if (!db_sc) {
500 TALLOC_FREE(frame);
501 return NT_STATUS_ACCESS_DENIED;
504 name_upper = strupper_talloc(frame, computer_name);
505 if (!name_upper) {
506 TALLOC_FREE(frame);
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);
515 TALLOC_FREE(frame);
516 return NT_STATUS_OK;
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;
531 NTSTATUS status;
533 db_sc = open_schannel_session_store(frame, lp_ctx);
534 if (!db_sc) {
535 TALLOC_FREE(frame);
536 return NT_STATUS_ACCESS_DENIED;
539 status = schannel_store_challenge_tdb(db_sc, frame,
540 client_challenge,
541 server_challenge,
542 computer_name);
544 TALLOC_FREE(frame);
545 return status;
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)
565 TALLOC_CTX *tmpctx;
566 struct db_context *db_sc;
567 struct netlogon_creds_CredentialState *creds;
568 NTSTATUS status;
569 char *name_upper = NULL;
570 char *keystr = NULL;
571 struct db_record *record;
572 TDB_DATA key;
574 if (creds_out != NULL) {
575 *creds_out = NULL;
578 tmpctx = talloc_named(mem_ctx, 0, "schannel_check_creds_state");
579 if (!tmpctx) {
580 return NT_STATUS_NO_MEMORY;
583 name_upper = strupper_talloc(tmpctx, computer_name);
584 if (!name_upper) {
585 status = NT_STATUS_NO_MEMORY;
586 goto done;
589 keystr = talloc_asprintf(tmpctx, "%s/%s",
590 SECRETS_SCHANNEL_STATE, name_upper);
591 if (!keystr) {
592 status = NT_STATUS_NO_MEMORY;
593 goto done;
596 key = string_term_tdb_data(keystr);
598 db_sc = open_schannel_session_store(tmpctx, lp_ctx);
599 if (!db_sc) {
600 status = NT_STATUS_ACCESS_DENIED;
601 goto done;
604 record = dbwrap_fetch_locked(db_sc, tmpctx, key);
605 if (!record) {
606 status = NT_STATUS_INTERNAL_DB_CORRUPTION;
607 goto done;
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)) {
617 goto done;
620 status = netlogon_creds_server_step_check(creds,
621 received_authenticator,
622 return_authenticator);
623 if (!NT_STATUS_IS_OK(status)) {
624 goto done;
627 status = schannel_store_session_key_tdb(db_sc, tmpctx, creds);
628 if (!NT_STATUS_IS_OK(status)) {
629 goto done;
632 if (creds_out) {
633 *creds_out = talloc_steal(mem_ctx, creds);
634 if (!*creds_out) {
635 status = NT_STATUS_NO_MEMORY;
636 goto done;
640 status = NT_STATUS_OK;
642 done:
643 talloc_free(tmpctx);
644 return status;