lib: Fix a typo
[sfrench/samba-autobuild/.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
46         if (!fname) {
47                 return NULL;
48         }
49
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);
54
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;
60         }
61
62         TALLOC_FREE(fname);
63
64         return db_sc;
65 }
66
67 /********************************************************************
68  ********************************************************************/
69
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)
74 {
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;
81
82         if (strlen(creds->computer_name) > 15) {
83                 /*
84                  * We may want to check for a completely
85                  * valid netbios name.
86                  */
87                 return STATUS_BUFFER_OVERFLOW;
88         }
89
90         name_upper = strupper_talloc(mem_ctx, creds->computer_name);
91         if (!name_upper) {
92                 return NT_STATUS_NO_MEMORY;
93         }
94
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;
100         }
101
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);
107         }
108
109         value.dptr = blob.data;
110         value.dsize = blob.length;
111
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;
118         }
119
120         DEBUG(3,("schannel_store_session_key_tdb: stored schannel info with key %s\n",
121                 keystr));
122
123         if (DEBUGLEVEL >= 10) {
124                 NDR_PRINT_DEBUG(netlogon_creds_CredentialState, creds);
125         }
126
127         talloc_free(keystr);
128
129         return NT_STATUS_OK;
130 }
131
132 /********************************************************************
133  ********************************************************************/
134
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)
140 {
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;
148
149         *pcreds = NULL;
150
151         name_upper = strupper_talloc(mem_ctx, computer_name);
152         if (!name_upper) {
153                 return NT_STATUS_NO_MEMORY;
154         }
155
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;
161         }
162
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;
168         }
169
170         creds = talloc_zero(mem_ctx, struct netlogon_creds_CredentialState);
171         if (!creds) {
172                 status = NT_STATUS_NO_MEMORY;
173                 goto done;
174         }
175
176         blob = data_blob_const(value.dptr, value.dsize);
177
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;
183         }
184
185         if (DEBUGLEVEL >= 10) {
186                 NDR_PRINT_DEBUG(netlogon_creds_CredentialState, creds);
187         }
188
189         DEBUG(3,("schannel_fetch_session_key_tdb: restored schannel info key %s\n",
190                 keystr));
191
192         status = NT_STATUS_OK;
193
194  done:
195
196         talloc_free(keystr);
197
198         if (!NT_STATUS_IS_OK(status)) {
199                 talloc_free(creds);
200                 return status;
201         }
202
203         *pcreds = creds;
204
205         return NT_STATUS_OK;
206 }
207
208 /******************************************************************************
209  Wrapper around schannel_fetch_session_key_tdb()
210  Note we must be root here.
211 *******************************************************************************/
212
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)
217 {
218         TALLOC_CTX *tmpctx;
219         struct db_context *db_sc;
220         struct netlogon_creds_CredentialState *creds;
221         NTSTATUS status;
222
223         tmpctx = talloc_named(mem_ctx, 0, "schannel_get_creds_state");
224         if (!tmpctx) {
225                 return NT_STATUS_NO_MEMORY;
226         }
227
228         db_sc = open_schannel_session_store(tmpctx, lp_ctx);
229         if (!db_sc) {
230                 return NT_STATUS_ACCESS_DENIED;
231         }
232
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;
239                 }
240         }
241
242         talloc_free(tmpctx);
243         return status;
244 }
245
246 /******************************************************************************
247  Wrapper around schannel_store_session_key_tdb()
248  Note we must be root here.
249 *******************************************************************************/
250
251 NTSTATUS schannel_save_creds_state(TALLOC_CTX *mem_ctx,
252                                    struct loadparm_context *lp_ctx,
253                                    struct netlogon_creds_CredentialState *creds)
254 {
255         TALLOC_CTX *tmpctx;
256         struct db_context *db_sc;
257         NTSTATUS status;
258
259         tmpctx = talloc_named(mem_ctx, 0, "schannel_save_creds_state");
260         if (!tmpctx) {
261                 return NT_STATUS_NO_MEMORY;
262         }
263
264         db_sc = open_schannel_session_store(tmpctx, lp_ctx);
265         if (!db_sc) {
266                 return NT_STATUS_ACCESS_DENIED;
267         }
268
269         status = schannel_store_session_key_tdb(db_sc, tmpctx, creds);
270
271         talloc_free(tmpctx);
272         return status;
273 }
274
275
276 /*
277  * Create a very lossy hash of the computer name.
278  *
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.
282  *
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.
288  */
289 static void hash_computer_name(const char *computer_name,
290                                char keystr[16])
291 {
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)
296         };
297         hash = tdb_jenkins_hash(&computer_tdb_data);
298
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;
304 }
305
306
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)
313 {
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;
321
322         if (strlen(computer_name) > 255) {
323                 /*
324                  * We don't make this a limit at 15 chars as Samba has
325                  * a test showing this can be longer :-(
326                  */
327                 return STATUS_BUFFER_OVERFLOW;
328         }
329
330         name_upper = strupper_talloc(mem_ctx, computer_name);
331         if (name_upper == NULL) {
332                 return NT_STATUS_NO_MEMORY;
333         }
334
335         hash_computer_name(name_upper, keystr);
336
337         cache_entry.computer_name = name_upper;
338         cache_entry.client_challenge = *client_challenge;
339         cache_entry.server_challenge = *server_challenge;
340
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;
345         }
346
347         value.dptr = blob.data;
348         value.dsize = blob.length;
349
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;
357         }
358
359         DEBUG(3,("%s: stored challenge info for '%s' with key %s\n",
360                 __func__, cache_entry.computer_name, keystr));
361
362         if (DEBUGLEVEL >= 10) {
363                 NDR_PRINT_DEBUG(netlogon_cache_entry, &cache_entry);
364         }
365
366         return NT_STATUS_OK;
367 }
368
369 /********************************************************************
370  Fetch a single challenge from the TDB.
371  ********************************************************************/
372
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)
379 {
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;
387
388         name_upper = strupper_talloc(mem_ctx, computer_name);
389         if (name_upper == NULL) {
390                 return NT_STATUS_NO_MEMORY;
391         }
392
393         hash_computer_name(name_upper, keystr);
394
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;
400         }
401
402         blob = data_blob_const(value.dptr, value.dsize);
403
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;
411         }
412
413         if (DEBUGLEVEL >= 10) {
414                 NDR_PRINT_DEBUG(netlogon_cache_entry, &cache_entry);
415         }
416
417         if (strcmp(cache_entry.computer_name, name_upper) != 0) {
418                 status = NT_STATUS_NOT_FOUND;
419
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 {
425
426                 DEBUG(3,("%s: restored key %s for %s\n",
427                          __func__, keystr, cache_entry.computer_name));
428
429                 *client_challenge = cache_entry.client_challenge;
430                 *server_challenge = cache_entry.server_challenge;
431         }
432  done:
433
434         if (!NT_STATUS_IS_OK(status)) {
435                 return status;
436         }
437
438         return NT_STATUS_OK;
439 }
440
441 /******************************************************************************
442  Wrapper around schannel_fetch_challenge_tdb()
443  Note we must be root here.
444
445 *******************************************************************************/
446
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)
451 {
452         TALLOC_CTX *frame = talloc_stackframe();
453         struct db_context *db_sc;
454         NTSTATUS status;
455
456         db_sc = open_schannel_session_store(frame, lp_ctx);
457         if (!db_sc) {
458                 TALLOC_FREE(frame);
459                 return NT_STATUS_ACCESS_DENIED;
460         }
461
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;
468 }
469
470 /******************************************************************************
471  Wrapper around dbwrap_delete_bystring()
472  Note we must be root here.
473
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 *******************************************************************************/
477
478 NTSTATUS schannel_delete_challenge(struct loadparm_context *lp_ctx,
479                                    const char *computer_name)
480 {
481         TALLOC_CTX *frame = talloc_stackframe();
482         struct db_context *db_sc;
483         char *name_upper;
484         char keystr[16] = { 0, };
485
486         db_sc = open_schannel_session_store(frame, lp_ctx);
487         if (!db_sc) {
488                 TALLOC_FREE(frame);
489                 return NT_STATUS_ACCESS_DENIED;
490         }
491
492         name_upper = strupper_talloc(frame, computer_name);
493         if (!name_upper) {
494                 TALLOC_FREE(frame);
495                 return NT_STATUS_NO_MEMORY;
496         }
497
498         hash_computer_name(name_upper, keystr);
499
500         /* Now delete it, we do not want to permit fetch of this twice */
501         dbwrap_delete_bystring(db_sc, keystr);
502
503         TALLOC_FREE(frame);
504         return NT_STATUS_OK;
505 }
506
507 /******************************************************************************
508  Wrapper around schannel_store_session_key_tdb()
509  Note we must be root here.
510 *******************************************************************************/
511
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)
516 {
517         TALLOC_CTX *frame = talloc_stackframe();
518         struct db_context *db_sc;
519         NTSTATUS status;
520
521         db_sc = open_schannel_session_store(frame, lp_ctx);
522         if (!db_sc) {
523                 TALLOC_FREE(frame);
524                 return NT_STATUS_ACCESS_DENIED;
525         }
526
527         status = schannel_store_challenge_tdb(db_sc, frame,
528                                               client_challenge,
529                                               server_challenge,
530                                               computer_name);
531
532         TALLOC_FREE(frame);
533         return status;
534 }
535
536 /********************************************************************
537  Validate an incoming authenticator against the credentials for the
538  remote machine stored in the schannel database.
539
540  The credentials are (re)read and from the schannel database, and
541  written back after the caclulations are performed.
542
543  If the creds_out parameter is not NULL returns the credentials.
544  ********************************************************************/
545
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)
552 {
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;
561
562         if (creds_out != NULL) {
563                 *creds_out = NULL;
564         }
565
566         tmpctx = talloc_named(mem_ctx, 0, "schannel_check_creds_state");
567         if (!tmpctx) {
568                 return NT_STATUS_NO_MEMORY;
569         }
570
571         name_upper = strupper_talloc(tmpctx, computer_name);
572         if (!name_upper) {
573                 status = NT_STATUS_NO_MEMORY;
574                 goto done;
575         }
576
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;
582         }
583
584         key = string_term_tdb_data(keystr);
585
586         db_sc = open_schannel_session_store(tmpctx, lp_ctx);
587         if (!db_sc) {
588                 status = NT_STATUS_ACCESS_DENIED;
589                 goto done;
590         }
591
592         record = dbwrap_fetch_locked(db_sc, tmpctx, key);
593         if (!record) {
594                 status = NT_STATUS_INTERNAL_DB_CORRUPTION;
595                 goto done;
596         }
597
598         /* Because this is a shared structure (even across
599          * disconnects) we must update the database every time we
600          * update the structure */
601
602         status = schannel_fetch_session_key_tdb(db_sc, tmpctx,
603                                                 computer_name, &creds);
604         if (!NT_STATUS_IS_OK(status)) {
605                 goto done;
606         }
607
608         status = netlogon_creds_server_step_check(creds,
609                                                   received_authenticator,
610                                                   return_authenticator);
611         if (!NT_STATUS_IS_OK(status)) {
612                 goto done;
613         }
614
615         status = schannel_store_session_key_tdb(db_sc, tmpctx, creds);
616         if (!NT_STATUS_IS_OK(status)) {
617                 goto done;
618         }
619
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;
625                 }
626         }
627
628         status = NT_STATUS_OK;
629
630 done:
631         talloc_free(tmpctx);
632         return status;
633 }
634