s3:smbspool: Fix cmdline argument handling
[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                 return NT_STATUS_ACCESS_DENIED;
240         }
241
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;
248                 }
249         }
250
251         talloc_free(tmpctx);
252         return status;
253 }
254
255 /******************************************************************************
256  Wrapper around schannel_store_session_key_tdb()
257  Note we must be root here.
258 *******************************************************************************/
259
260 NTSTATUS schannel_save_creds_state(TALLOC_CTX *mem_ctx,
261                                    struct loadparm_context *lp_ctx,
262                                    struct netlogon_creds_CredentialState *creds)
263 {
264         TALLOC_CTX *tmpctx;
265         struct db_context *db_sc;
266         NTSTATUS status;
267
268         tmpctx = talloc_named(mem_ctx, 0, "schannel_save_creds_state");
269         if (!tmpctx) {
270                 return NT_STATUS_NO_MEMORY;
271         }
272
273         db_sc = open_schannel_session_store(tmpctx, lp_ctx);
274         if (!db_sc) {
275                 status = NT_STATUS_ACCESS_DENIED;
276                 goto fail;
277         }
278
279         status = schannel_store_session_key_tdb(db_sc, tmpctx, creds);
280
281 fail:
282         talloc_free(tmpctx);
283         return status;
284 }
285
286
287 /*
288  * Create a very lossy hash of the computer name.
289  *
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.
293  *
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.
299  */
300 static void hash_computer_name(const char *computer_name,
301                                char keystr[16])
302 {
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)
307         };
308         hash = tdb_jenkins_hash(&computer_tdb_data);
309
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;
315 }
316
317
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)
324 {
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;
332
333         if (strlen(computer_name) > 255) {
334                 /*
335                  * We don't make this a limit at 15 chars as Samba has
336                  * a test showing this can be longer :-(
337                  */
338                 return STATUS_BUFFER_OVERFLOW;
339         }
340
341         name_upper = strupper_talloc(mem_ctx, computer_name);
342         if (name_upper == NULL) {
343                 return NT_STATUS_NO_MEMORY;
344         }
345
346         hash_computer_name(name_upper, keystr);
347
348         cache_entry.computer_name = name_upper;
349         cache_entry.client_challenge = *client_challenge;
350         cache_entry.server_challenge = *server_challenge;
351
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;
356         }
357
358         value.dptr = blob.data;
359         value.dsize = blob.length;
360
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;
368         }
369
370         DEBUG(3,("%s: stored challenge info for '%s' with key %s\n",
371                 __func__, cache_entry.computer_name, keystr));
372
373         if (DEBUGLEVEL >= 10) {
374                 NDR_PRINT_DEBUG(netlogon_cache_entry, &cache_entry);
375         }
376
377         return NT_STATUS_OK;
378 }
379
380 /********************************************************************
381  Fetch a single challenge from the TDB.
382  ********************************************************************/
383
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)
390 {
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;
398
399         name_upper = strupper_talloc(mem_ctx, computer_name);
400         if (name_upper == NULL) {
401                 return NT_STATUS_NO_MEMORY;
402         }
403
404         hash_computer_name(name_upper, keystr);
405
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;
411         }
412
413         blob = data_blob_const(value.dptr, value.dsize);
414
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;
422         }
423
424         if (DEBUGLEVEL >= 10) {
425                 NDR_PRINT_DEBUG(netlogon_cache_entry, &cache_entry);
426         }
427
428         if (strcmp(cache_entry.computer_name, name_upper) != 0) {
429                 status = NT_STATUS_NOT_FOUND;
430
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 {
436
437                 DEBUG(3,("%s: restored key %s for %s\n",
438                          __func__, keystr, cache_entry.computer_name));
439
440                 *client_challenge = cache_entry.client_challenge;
441                 *server_challenge = cache_entry.server_challenge;
442         }
443  done:
444
445         if (!NT_STATUS_IS_OK(status)) {
446                 return status;
447         }
448
449         return NT_STATUS_OK;
450 }
451
452 /******************************************************************************
453  Wrapper around schannel_fetch_challenge_tdb()
454  Note we must be root here.
455
456 *******************************************************************************/
457
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)
462 {
463         TALLOC_CTX *frame = talloc_stackframe();
464         struct db_context *db_sc;
465         NTSTATUS status;
466
467         db_sc = open_schannel_session_store(frame, lp_ctx);
468         if (!db_sc) {
469                 TALLOC_FREE(frame);
470                 return NT_STATUS_ACCESS_DENIED;
471         }
472
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;
479 }
480
481 /******************************************************************************
482  Wrapper around dbwrap_delete_bystring()
483  Note we must be root here.
484
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 *******************************************************************************/
488
489 NTSTATUS schannel_delete_challenge(struct loadparm_context *lp_ctx,
490                                    const char *computer_name)
491 {
492         TALLOC_CTX *frame = talloc_stackframe();
493         struct db_context *db_sc;
494         char *name_upper;
495         char keystr[16] = { 0, };
496
497         db_sc = open_schannel_session_store(frame, lp_ctx);
498         if (!db_sc) {
499                 TALLOC_FREE(frame);
500                 return NT_STATUS_ACCESS_DENIED;
501         }
502
503         name_upper = strupper_talloc(frame, computer_name);
504         if (!name_upper) {
505                 TALLOC_FREE(frame);
506                 return NT_STATUS_NO_MEMORY;
507         }
508
509         hash_computer_name(name_upper, keystr);
510
511         /* Now delete it, we do not want to permit fetch of this twice */
512         dbwrap_delete_bystring(db_sc, keystr);
513
514         TALLOC_FREE(frame);
515         return NT_STATUS_OK;
516 }
517
518 /******************************************************************************
519  Wrapper around schannel_store_session_key_tdb()
520  Note we must be root here.
521 *******************************************************************************/
522
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)
527 {
528         TALLOC_CTX *frame = talloc_stackframe();
529         struct db_context *db_sc;
530         NTSTATUS status;
531
532         db_sc = open_schannel_session_store(frame, lp_ctx);
533         if (!db_sc) {
534                 TALLOC_FREE(frame);
535                 return NT_STATUS_ACCESS_DENIED;
536         }
537
538         status = schannel_store_challenge_tdb(db_sc, frame,
539                                               client_challenge,
540                                               server_challenge,
541                                               computer_name);
542
543         TALLOC_FREE(frame);
544         return status;
545 }
546
547 /********************************************************************
548  Validate an incoming authenticator against the credentials for the
549  remote machine stored in the schannel database.
550
551  The credentials are (re)read and from the schannel database, and
552  written back after the caclulations are performed.
553
554  If the creds_out parameter is not NULL returns the credentials.
555  ********************************************************************/
556
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)
563 {
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;
572
573         if (creds_out != NULL) {
574                 *creds_out = NULL;
575         }
576
577         tmpctx = talloc_named(mem_ctx, 0, "schannel_check_creds_state");
578         if (!tmpctx) {
579                 return NT_STATUS_NO_MEMORY;
580         }
581
582         name_upper = strupper_talloc(tmpctx, computer_name);
583         if (!name_upper) {
584                 status = NT_STATUS_NO_MEMORY;
585                 goto done;
586         }
587
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;
593         }
594
595         key = string_term_tdb_data(keystr);
596
597         db_sc = open_schannel_session_store(tmpctx, lp_ctx);
598         if (!db_sc) {
599                 status = NT_STATUS_ACCESS_DENIED;
600                 goto done;
601         }
602
603         record = dbwrap_fetch_locked(db_sc, tmpctx, key);
604         if (!record) {
605                 status = NT_STATUS_INTERNAL_DB_CORRUPTION;
606                 goto done;
607         }
608
609         /* Because this is a shared structure (even across
610          * disconnects) we must update the database every time we
611          * update the structure */
612
613         status = schannel_fetch_session_key_tdb(db_sc, tmpctx,
614                                                 computer_name, &creds);
615         if (!NT_STATUS_IS_OK(status)) {
616                 goto done;
617         }
618
619         status = netlogon_creds_server_step_check(creds,
620                                                   received_authenticator,
621                                                   return_authenticator);
622         if (!NT_STATUS_IS_OK(status)) {
623                 goto done;
624         }
625
626         status = schannel_store_session_key_tdb(db_sc, tmpctx, creds);
627         if (!NT_STATUS_IS_OK(status)) {
628                 goto done;
629         }
630
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;
636                 }
637         }
638
639         status = NT_STATUS_OK;
640
641 done:
642         talloc_free(tmpctx);
643         return status;
644 }
645