e0ac8a378a0ab63d3ca0ed70cdaae89a3c71d5bc
[samba.git] / libcli / auth / schannel_state_tdb.c
1 /*
2    Unix SMB/CIFS implementation.
3
4    module to store/fetch session keys for the schannel server
5
6    Copyright (C) Andrew Tridgell 2004
7    Copyright (C) Andrew Bartlett <abartlet@samba.org> 2006-2009
8    Copyright (C) Guenther Deschner 2009
9
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.
14
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.
19
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/>.
22 */
23
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"
32
33 #define SECRETS_SCHANNEL_STATE "SECRETS/SCHANNEL"
34
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 *******************************************************************************/
39
40 struct db_context *open_schannel_session_store(TALLOC_CTX *mem_ctx,
41                                                struct loadparm_context *lp_ctx)
42 {
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;
46
47         if (!fname) {
48                 return NULL;
49         }
50
51         hash_size = lpcfg_tdb_hash_size(lp_ctx, fname);
52         tdb_flags = lpcfg_tdb_flags(lp_ctx, TDB_CLEAR_IF_FIRST|TDB_NOSYNC);
53
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);
63
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;
69         }
70
71         TALLOC_FREE(fname);
72
73         return db_sc;
74 }
75
76 /********************************************************************
77  ********************************************************************/
78
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)
83 {
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;
90
91         if (strlen(creds->computer_name) > 15) {
92                 /*
93                  * We may want to check for a completely
94                  * valid netbios name.
95                  */
96                 return STATUS_BUFFER_OVERFLOW;
97         }
98
99         name_upper = strupper_talloc(mem_ctx, creds->computer_name);
100         if (!name_upper) {
101                 return NT_STATUS_NO_MEMORY;
102         }
103
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;
109         }
110
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);
116         }
117
118         value.dptr = blob.data;
119         value.dsize = blob.length;
120
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;
127         }
128
129         DEBUG(3,("schannel_store_session_key_tdb: stored schannel info with key %s\n",
130                 keystr));
131
132         if (DEBUGLEVEL >= 10) {
133                 NDR_PRINT_DEBUG(netlogon_creds_CredentialState, creds);
134         }
135
136         talloc_free(keystr);
137
138         return NT_STATUS_OK;
139 }
140
141 /********************************************************************
142  ********************************************************************/
143
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)
149 {
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;
157
158         *pcreds = NULL;
159
160         name_upper = strupper_talloc(mem_ctx, computer_name);
161         if (!name_upper) {
162                 return NT_STATUS_NO_MEMORY;
163         }
164
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;
170         }
171
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;
177         }
178
179         creds = talloc_zero(mem_ctx, struct netlogon_creds_CredentialState);
180         if (!creds) {
181                 status = NT_STATUS_NO_MEMORY;
182                 goto done;
183         }
184
185         blob = data_blob_const(value.dptr, value.dsize);
186
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;
192         }
193
194         if (DEBUGLEVEL >= 10) {
195                 NDR_PRINT_DEBUG(netlogon_creds_CredentialState, creds);
196         }
197
198         DEBUG(3,("schannel_fetch_session_key_tdb: restored schannel info key %s\n",
199                 keystr));
200
201         status = NT_STATUS_OK;
202
203  done:
204
205         talloc_free(keystr);
206
207         if (!NT_STATUS_IS_OK(status)) {
208                 talloc_free(creds);
209                 return status;
210         }
211
212         *pcreds = creds;
213
214         return NT_STATUS_OK;
215 }
216
217 /******************************************************************************
218  Wrapper around schannel_fetch_session_key_tdb()
219  Note we must be root here.
220 *******************************************************************************/
221
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)
226 {
227         TALLOC_CTX *tmpctx;
228         struct db_context *db_sc;
229         struct netlogon_creds_CredentialState *creds;
230         NTSTATUS status;
231
232         tmpctx = talloc_named(mem_ctx, 0, "schannel_get_creds_state");
233         if (!tmpctx) {
234                 return NT_STATUS_NO_MEMORY;
235         }
236
237         db_sc = open_schannel_session_store(tmpctx, lp_ctx);
238         if (!db_sc) {
239                 TALLOC_FREE(tmpctx);
240                 return NT_STATUS_ACCESS_DENIED;
241         }
242
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;
249                 }
250         }
251
252         talloc_free(tmpctx);
253         return status;
254 }
255
256 /******************************************************************************
257  Wrapper around schannel_store_session_key_tdb()
258  Note we must be root here.
259 *******************************************************************************/
260
261 NTSTATUS schannel_save_creds_state(TALLOC_CTX *mem_ctx,
262                                    struct loadparm_context *lp_ctx,
263                                    struct netlogon_creds_CredentialState *creds)
264 {
265         TALLOC_CTX *tmpctx;
266         struct db_context *db_sc;
267         NTSTATUS status;
268
269         tmpctx = talloc_named(mem_ctx, 0, "schannel_save_creds_state");
270         if (!tmpctx) {
271                 return NT_STATUS_NO_MEMORY;
272         }
273
274         db_sc = open_schannel_session_store(tmpctx, lp_ctx);
275         if (!db_sc) {
276                 status = NT_STATUS_ACCESS_DENIED;
277                 goto fail;
278         }
279
280         status = schannel_store_session_key_tdb(db_sc, tmpctx, creds);
281
282 fail:
283         talloc_free(tmpctx);
284         return status;
285 }
286
287
288 /*
289  * Create a very lossy hash of the computer name.
290  *
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.
294  *
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.
300  */
301 static void hash_computer_name(const char *computer_name,
302                                char keystr[16])
303 {
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)
308         };
309         hash = tdb_jenkins_hash(&computer_tdb_data);
310
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;
316 }
317
318
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)
325 {
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;
333
334         if (strlen(computer_name) > 255) {
335                 /*
336                  * We don't make this a limit at 15 chars as Samba has
337                  * a test showing this can be longer :-(
338                  */
339                 return STATUS_BUFFER_OVERFLOW;
340         }
341
342         name_upper = strupper_talloc(mem_ctx, computer_name);
343         if (name_upper == NULL) {
344                 return NT_STATUS_NO_MEMORY;
345         }
346
347         hash_computer_name(name_upper, keystr);
348
349         cache_entry.computer_name = name_upper;
350         cache_entry.client_challenge = *client_challenge;
351         cache_entry.server_challenge = *server_challenge;
352
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;
357         }
358
359         value.dptr = blob.data;
360         value.dsize = blob.length;
361
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;
369         }
370
371         DEBUG(3,("%s: stored challenge info for '%s' with key %s\n",
372                 __func__, cache_entry.computer_name, keystr));
373
374         if (DEBUGLEVEL >= 10) {
375                 NDR_PRINT_DEBUG(netlogon_cache_entry, &cache_entry);
376         }
377
378         return NT_STATUS_OK;
379 }
380
381 /********************************************************************
382  Fetch a single challenge from the TDB.
383  ********************************************************************/
384
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)
391 {
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;
399
400         name_upper = strupper_talloc(mem_ctx, computer_name);
401         if (name_upper == NULL) {
402                 return NT_STATUS_NO_MEMORY;
403         }
404
405         hash_computer_name(name_upper, keystr);
406
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;
412         }
413
414         blob = data_blob_const(value.dptr, value.dsize);
415
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;
423         }
424
425         if (DEBUGLEVEL >= 10) {
426                 NDR_PRINT_DEBUG(netlogon_cache_entry, &cache_entry);
427         }
428
429         if (strcmp(cache_entry.computer_name, name_upper) != 0) {
430                 status = NT_STATUS_NOT_FOUND;
431
432                 DEBUG(1, ("%s: HASH COLLISION with key %s ! "
433                           "Wanted to fetch record for %s but got %s.",
434                           __func__, keystr, name_upper,
435                           cache_entry.computer_name));
436         } else {
437
438                 DEBUG(3,("%s: restored key %s for %s\n",
439                          __func__, keystr, cache_entry.computer_name));
440
441                 *client_challenge = cache_entry.client_challenge;
442                 *server_challenge = cache_entry.server_challenge;
443         }
444  done:
445
446         if (!NT_STATUS_IS_OK(status)) {
447                 return status;
448         }
449
450         return NT_STATUS_OK;
451 }
452
453 /******************************************************************************
454  Wrapper around schannel_fetch_challenge_tdb()
455  Note we must be root here.
456
457 *******************************************************************************/
458
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)
463 {
464         TALLOC_CTX *frame = talloc_stackframe();
465         struct db_context *db_sc;
466         NTSTATUS status;
467
468         db_sc = open_schannel_session_store(frame, lp_ctx);
469         if (!db_sc) {
470                 TALLOC_FREE(frame);
471                 return NT_STATUS_ACCESS_DENIED;
472         }
473
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;
480 }
481
482 /******************************************************************************
483  Wrapper around dbwrap_delete_bystring()
484  Note we must be root here.
485
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 *******************************************************************************/
489
490 NTSTATUS schannel_delete_challenge(struct loadparm_context *lp_ctx,
491                                    const char *computer_name)
492 {
493         TALLOC_CTX *frame = talloc_stackframe();
494         struct db_context *db_sc;
495         char *name_upper;
496         char keystr[16] = { 0, };
497
498         db_sc = open_schannel_session_store(frame, lp_ctx);
499         if (!db_sc) {
500                 TALLOC_FREE(frame);
501                 return NT_STATUS_ACCESS_DENIED;
502         }
503
504         name_upper = strupper_talloc(frame, computer_name);
505         if (!name_upper) {
506                 TALLOC_FREE(frame);
507                 return NT_STATUS_NO_MEMORY;
508         }
509
510         hash_computer_name(name_upper, keystr);
511
512         /* Now delete it, we do not want to permit fetch of this twice */
513         dbwrap_delete_bystring(db_sc, keystr);
514
515         TALLOC_FREE(frame);
516         return NT_STATUS_OK;
517 }
518
519 /******************************************************************************
520  Wrapper around schannel_store_session_key_tdb()
521  Note we must be root here.
522 *******************************************************************************/
523
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)
528 {
529         TALLOC_CTX *frame = talloc_stackframe();
530         struct db_context *db_sc;
531         NTSTATUS status;
532
533         db_sc = open_schannel_session_store(frame, lp_ctx);
534         if (!db_sc) {
535                 TALLOC_FREE(frame);
536                 return NT_STATUS_ACCESS_DENIED;
537         }
538
539         status = schannel_store_challenge_tdb(db_sc, frame,
540                                               client_challenge,
541                                               server_challenge,
542                                               computer_name);
543
544         TALLOC_FREE(frame);
545         return status;
546 }
547
548 /********************************************************************
549  Validate an incoming authenticator against the credentials for the
550  remote machine stored in the schannel database.
551
552  The credentials are (re)read and from the schannel database, and
553  written back after the caclulations are performed.
554
555  If the creds_out parameter is not NULL returns the credentials.
556  ********************************************************************/
557
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)
564 {
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;
573
574         if (creds_out != NULL) {
575                 *creds_out = NULL;
576         }
577
578         tmpctx = talloc_named(mem_ctx, 0, "schannel_check_creds_state");
579         if (!tmpctx) {
580                 return NT_STATUS_NO_MEMORY;
581         }
582
583         name_upper = strupper_talloc(tmpctx, computer_name);
584         if (!name_upper) {
585                 status = NT_STATUS_NO_MEMORY;
586                 goto done;
587         }
588
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;
594         }
595
596         key = string_term_tdb_data(keystr);
597
598         db_sc = open_schannel_session_store(tmpctx, lp_ctx);
599         if (!db_sc) {
600                 status = NT_STATUS_ACCESS_DENIED;
601                 goto done;
602         }
603
604         record = dbwrap_fetch_locked(db_sc, tmpctx, key);
605         if (!record) {
606                 status = NT_STATUS_INTERNAL_DB_CORRUPTION;
607                 goto done;
608         }
609
610         /* Because this is a shared structure (even across
611          * disconnects) we must update the database every time we
612          * update the structure */
613
614         status = schannel_fetch_session_key_tdb(db_sc, tmpctx,
615                                                 computer_name, &creds);
616         if (!NT_STATUS_IS_OK(status)) {
617                 goto done;
618         }
619
620         status = netlogon_creds_server_step_check(creds,
621                                                   received_authenticator,
622                                                   return_authenticator);
623         if (!NT_STATUS_IS_OK(status)) {
624                 goto done;
625         }
626
627         status = schannel_store_session_key_tdb(db_sc, tmpctx, creds);
628         if (!NT_STATUS_IS_OK(status)) {
629                 goto done;
630         }
631
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;
637                 }
638         }
639
640         status = NT_STATUS_OK;
641
642 done:
643         talloc_free(tmpctx);
644         return status;
645 }
646