lib: Fix an error path memleak
[Samba.git] / libcli / auth / schannel_state_tdb.c
blob6a19823f6393cfea7873da08656abd12b0d52a9e
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 status = NT_STATUS_ACCESS_DENIED;
267 goto fail;
270 status = schannel_store_session_key_tdb(db_sc, tmpctx, creds);
272 fail:
273 talloc_free(tmpctx);
274 return status;
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
289 * packets.
291 static void hash_computer_name(const char *computer_name,
292 char keystr[16])
294 unsigned int hash;
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);
305 return;
309 static
310 NTSTATUS schannel_store_challenge_tdb(struct db_context *db_sc,
311 TALLOC_CTX *mem_ctx,
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;
317 DATA_BLOB blob;
318 TDB_DATA value;
319 char *name_upper = NULL;
320 NTSTATUS status;
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,
357 nt_errstr(status)));
358 return status;
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);
368 return NT_STATUS_OK;
371 /********************************************************************
372 Fetch a single challenge from the TDB.
373 ********************************************************************/
375 static
376 NTSTATUS schannel_fetch_challenge_tdb(struct db_context *db_sc,
377 TALLOC_CTX *mem_ctx,
378 struct netr_Credential *client_challenge,
379 struct netr_Credential *server_challenge,
380 const char *computer_name)
382 NTSTATUS status;
383 TDB_DATA value;
384 enum ndr_err_code ndr_err;
385 DATA_BLOB blob;
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)));
401 goto done;
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)));
412 goto done;
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));
426 } else {
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;
434 done:
436 if (!NT_STATUS_IS_OK(status)) {
437 return status;
440 return NT_STATUS_OK;
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;
456 NTSTATUS status;
458 db_sc = open_schannel_session_store(frame, lp_ctx);
459 if (!db_sc) {
460 TALLOC_FREE(frame);
461 return NT_STATUS_ACCESS_DENIED;
464 status = schannel_fetch_challenge_tdb(db_sc, frame,
465 client_challenge,
466 server_challenge,
467 computer_name);
468 TALLOC_FREE(frame);
469 return status;
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;
485 char *name_upper;
486 char keystr[16] = { 0, };
488 db_sc = open_schannel_session_store(frame, lp_ctx);
489 if (!db_sc) {
490 TALLOC_FREE(frame);
491 return NT_STATUS_ACCESS_DENIED;
494 name_upper = strupper_talloc(frame, computer_name);
495 if (!name_upper) {
496 TALLOC_FREE(frame);
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);
505 TALLOC_FREE(frame);
506 return NT_STATUS_OK;
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;
521 NTSTATUS status;
523 db_sc = open_schannel_session_store(frame, lp_ctx);
524 if (!db_sc) {
525 TALLOC_FREE(frame);
526 return NT_STATUS_ACCESS_DENIED;
529 status = schannel_store_challenge_tdb(db_sc, frame,
530 client_challenge,
531 server_challenge,
532 computer_name);
534 TALLOC_FREE(frame);
535 return status;
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)
555 TALLOC_CTX *tmpctx;
556 struct db_context *db_sc;
557 struct netlogon_creds_CredentialState *creds;
558 NTSTATUS status;
559 char *name_upper = NULL;
560 char *keystr = NULL;
561 struct db_record *record;
562 TDB_DATA key;
564 if (creds_out != NULL) {
565 *creds_out = NULL;
568 tmpctx = talloc_named(mem_ctx, 0, "schannel_check_creds_state");
569 if (!tmpctx) {
570 return NT_STATUS_NO_MEMORY;
573 name_upper = strupper_talloc(tmpctx, computer_name);
574 if (!name_upper) {
575 status = NT_STATUS_NO_MEMORY;
576 goto done;
579 keystr = talloc_asprintf(tmpctx, "%s/%s",
580 SECRETS_SCHANNEL_STATE, name_upper);
581 if (!keystr) {
582 status = NT_STATUS_NO_MEMORY;
583 goto done;
586 key = string_term_tdb_data(keystr);
588 db_sc = open_schannel_session_store(tmpctx, lp_ctx);
589 if (!db_sc) {
590 status = NT_STATUS_ACCESS_DENIED;
591 goto done;
594 record = dbwrap_fetch_locked(db_sc, tmpctx, key);
595 if (!record) {
596 status = NT_STATUS_INTERNAL_DB_CORRUPTION;
597 goto done;
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)) {
607 goto done;
610 status = netlogon_creds_server_step_check(creds,
611 received_authenticator,
612 return_authenticator);
613 if (!NT_STATUS_IS_OK(status)) {
614 goto done;
617 status = schannel_store_session_key_tdb(db_sc, tmpctx, creds);
618 if (!NT_STATUS_IS_OK(status)) {
619 goto done;
622 if (creds_out) {
623 *creds_out = talloc_steal(mem_ctx, creds);
624 if (!*creds_out) {
625 status = NT_STATUS_NO_MEMORY;
626 goto done;
630 status = NT_STATUS_OK;
632 done:
633 talloc_free(tmpctx);
634 return status;