r23792: convert Samba4 to GPLv3
[jelmer/samba4-debian.git] / source / auth / credentials / credentials_krb5.c
1 /* 
2    Unix SMB/CIFS implementation.
3
4    Handle user credentials (as regards krb5)
5
6    Copyright (C) Jelmer Vernooij 2005
7    Copyright (C) Tim Potter 2001
8    Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005
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/kerberos.h"
26 #include "auth/kerberos/kerberos.h"
27 #include "auth/credentials/credentials.h"
28 #include "auth/credentials/credentials_krb5.h"
29
30 int cli_credentials_get_krb5_context(struct cli_credentials *cred, 
31                                      struct smb_krb5_context **smb_krb5_context) 
32 {
33         int ret;
34         if (cred->smb_krb5_context) {
35                 *smb_krb5_context = cred->smb_krb5_context;
36                 return 0;
37         }
38
39         ret = smb_krb5_init_context(cred, cli_credentials_get_event_context(cred), 
40                                     &cred->smb_krb5_context);
41         if (ret) {
42                 return ret;
43         }
44         *smb_krb5_context = cred->smb_krb5_context;
45         return 0;
46 }
47
48 /* This needs to be called directly after the cli_credentials_init(),
49  * otherwise we might have problems with the krb5 context already
50  * being here.
51  */
52 NTSTATUS cli_credentials_set_krb5_context(struct cli_credentials *cred, 
53                                           struct smb_krb5_context *smb_krb5_context)
54 {
55         if (!talloc_reference(cred, smb_krb5_context)) {
56                 return NT_STATUS_NO_MEMORY;
57         }
58         cred->smb_krb5_context = smb_krb5_context;
59         return NT_STATUS_OK;
60 }
61
62 static int cli_credentials_set_from_ccache(struct cli_credentials *cred, 
63                                     struct ccache_container *ccache,
64                                     enum credentials_obtained obtained)
65 {
66         
67         krb5_principal princ;
68         krb5_error_code ret;
69         char *name;
70         char **realm;
71
72         if (cred->ccache_obtained > obtained) {
73                 return 0;
74         }
75
76         ret = krb5_cc_get_principal(ccache->smb_krb5_context->krb5_context, 
77                                     ccache->ccache, &princ);
78
79         if (ret) {
80                 char *err_mess = smb_get_krb5_error_message(ccache->smb_krb5_context->krb5_context, 
81                                                             ret, cred);
82                 DEBUG(1,("failed to get principal from ccache: %s\n", 
83                          err_mess));
84                 talloc_free(err_mess);
85                 return ret;
86         }
87         
88         ret = krb5_unparse_name(ccache->smb_krb5_context->krb5_context, princ, &name);
89         if (ret) {
90                 char *err_mess = smb_get_krb5_error_message(ccache->smb_krb5_context->krb5_context, ret, cred);
91                 DEBUG(1,("failed to unparse principal from ccache: %s\n", 
92                          err_mess));
93                 talloc_free(err_mess);
94                 return ret;
95         }
96
97         realm = krb5_princ_realm(ccache->smb_krb5_context->krb5_context, princ);
98
99         cli_credentials_set_principal(cred, name, obtained);
100
101         free(name);
102
103         krb5_free_principal(ccache->smb_krb5_context->krb5_context, princ);
104
105         /* set the ccache_obtained here, as it just got set to UNINITIALISED by the calls above */
106         cred->ccache_obtained = obtained;
107
108         return 0;
109 }
110
111 /* Free a memory ccache */
112 static int free_mccache(struct ccache_container *ccc)
113 {
114         krb5_cc_destroy(ccc->smb_krb5_context->krb5_context, ccc->ccache);
115
116         return 0;
117 }
118
119 /* Free a disk-based ccache */
120 static int free_dccache(struct ccache_container *ccc) {
121         krb5_cc_close(ccc->smb_krb5_context->krb5_context, ccc->ccache);
122
123         return 0;
124 }
125
126 int cli_credentials_set_ccache(struct cli_credentials *cred, 
127                                const char *name, 
128                                enum credentials_obtained obtained)
129 {
130         krb5_error_code ret;
131         krb5_principal princ;
132         struct ccache_container *ccc;
133         if (cred->ccache_obtained > obtained) {
134                 return 0;
135         }
136
137         ccc = talloc(cred, struct ccache_container);
138         if (!ccc) {
139                 return ENOMEM;
140         }
141
142         ret = cli_credentials_get_krb5_context(cred, &ccc->smb_krb5_context);
143         if (ret) {
144                 talloc_free(ccc);
145                 return ret;
146         }
147         if (!talloc_reference(ccc, ccc->smb_krb5_context)) {
148                 talloc_free(ccc);
149                 return ENOMEM;
150         }
151
152         if (name) {
153                 ret = krb5_cc_resolve(ccc->smb_krb5_context->krb5_context, name, &ccc->ccache);
154                 if (ret) {
155                         DEBUG(1,("failed to read krb5 ccache: %s: %s\n", 
156                                  name, 
157                                  smb_get_krb5_error_message(ccc->smb_krb5_context->krb5_context, ret, ccc)));
158                         talloc_free(ccc);
159                         return ret;
160                 }
161         } else {
162                 ret = krb5_cc_default(ccc->smb_krb5_context->krb5_context, &ccc->ccache);
163                 if (ret) {
164                         DEBUG(3,("failed to read default krb5 ccache: %s\n", 
165                                  smb_get_krb5_error_message(ccc->smb_krb5_context->krb5_context, ret, ccc)));
166                         talloc_free(ccc);
167                         return ret;
168                 }
169         }
170
171         talloc_set_destructor(ccc, free_dccache);
172
173         ret = krb5_cc_get_principal(ccc->smb_krb5_context->krb5_context, ccc->ccache, &princ);
174
175         if (ret) {
176                 DEBUG(3,("failed to get principal from default ccache: %s\n", 
177                          smb_get_krb5_error_message(ccc->smb_krb5_context->krb5_context, ret, ccc)));
178                 talloc_free(ccc);               
179                 return ret;
180         }
181
182         krb5_free_principal(ccc->smb_krb5_context->krb5_context, princ);
183
184         ret = cli_credentials_set_from_ccache(cred, ccc, obtained);
185
186         if (ret) {
187                 return ret;
188         }
189
190         cred->ccache = ccc;
191         cred->ccache_obtained = obtained;
192         talloc_steal(cred, ccc);
193
194         cli_credentials_invalidate_client_gss_creds(cred, cred->ccache_obtained);
195         return 0;
196 }
197
198
199 static int cli_credentials_new_ccache(struct cli_credentials *cred, struct ccache_container **_ccc)
200 {
201         krb5_error_code ret;
202         char *rand_string;
203         struct ccache_container *ccc = talloc(cred, struct ccache_container);
204         char *ccache_name;
205         if (!ccc) {
206                 return ENOMEM;
207         }
208
209         rand_string = generate_random_str(NULL, 16);
210         if (!rand_string) {
211                 talloc_free(ccc);
212                 return ENOMEM;
213         }
214
215         ccache_name = talloc_asprintf(ccc, "MEMORY:%s", 
216                                       rand_string);
217         talloc_free(rand_string);
218
219         if (!ccache_name) {
220                 talloc_free(ccc);
221                 return ENOMEM;
222         }
223
224         ret = cli_credentials_get_krb5_context(cred, &ccc->smb_krb5_context);
225         if (ret) {
226                 talloc_free(ccc);
227                 return ret;
228         }
229         if (!talloc_reference(ccc, ccc->smb_krb5_context)) {
230                 talloc_free(ccc);
231                 return ENOMEM;
232         }
233
234         ret = krb5_cc_resolve(ccc->smb_krb5_context->krb5_context, ccache_name, &ccc->ccache);
235         if (ret) {
236                 DEBUG(1,("failed to generate a new krb5 ccache (%s): %s\n", 
237                          ccache_name,
238                          smb_get_krb5_error_message(ccc->smb_krb5_context->krb5_context, ret, ccc)));
239                 talloc_free(ccache_name);
240                 talloc_free(ccc);
241                 return ret;
242         }
243
244         talloc_set_destructor(ccc, free_mccache);
245
246         talloc_free(ccache_name);
247
248         *_ccc = ccc;
249
250         return ret;
251 }
252
253 int cli_credentials_get_ccache(struct cli_credentials *cred, 
254                                struct ccache_container **ccc)
255 {
256         krb5_error_code ret;
257         
258         if (cred->machine_account_pending) {
259                 cli_credentials_set_machine_account(cred);
260         }
261
262         if (cred->ccache_obtained >= cred->ccache_threshold) {
263                 *ccc = cred->ccache;
264                 return 0;
265         }
266         if (cli_credentials_is_anonymous(cred)) {
267                 return EINVAL;
268         }
269
270         ret = cli_credentials_new_ccache(cred, ccc);
271         if (ret) {
272                 return ret;
273         }
274
275         ret = kinit_to_ccache(cred, cred, (*ccc)->smb_krb5_context, (*ccc)->ccache);
276         if (ret) {
277                 return ret;
278         }
279
280         ret = cli_credentials_set_from_ccache(cred, *ccc, 
281                                               (MAX(MAX(cred->principal_obtained, 
282                                                        cred->username_obtained), 
283                                                    cred->password_obtained)));
284         
285         cred->ccache = *ccc;
286         cred->ccache_obtained = cred->principal_obtained;
287         if (ret) {
288                 return ret;
289         }
290         cli_credentials_invalidate_client_gss_creds(cred, cred->ccache_obtained);
291         return ret;
292 }
293
294 void cli_credentials_invalidate_client_gss_creds(struct cli_credentials *cred, 
295                                                  enum credentials_obtained obtained)
296 {
297         /* If the caller just changed the username/password etc, then
298          * any cached credentials are now invalid */
299         if (obtained >= cred->client_gss_creds_obtained) {
300                 if (cred->client_gss_creds_obtained > CRED_UNINITIALISED) {
301                         talloc_free(cred->client_gss_creds);
302                         cred->client_gss_creds = NULL;
303                 }
304                 cred->client_gss_creds_obtained = CRED_UNINITIALISED;
305         }
306         /* Now that we know that the data is 'this specified', then
307          * don't allow something less 'known' to be returned as a
308          * ccache.  Ie, if the username is on the commmand line, we
309          * don't want to later guess to use a file-based ccache */
310         if (obtained > cred->client_gss_creds_threshold) {
311                 cred->client_gss_creds_threshold = obtained;
312         }
313 }
314
315 void cli_credentials_invalidate_ccache(struct cli_credentials *cred, 
316                                        enum credentials_obtained obtained)
317 {
318         /* If the caller just changed the username/password etc, then
319          * any cached credentials are now invalid */
320         if (obtained >= cred->ccache_obtained) {
321                 if (cred->ccache_obtained > CRED_UNINITIALISED) {
322                         talloc_free(cred->ccache);
323                         cred->ccache = NULL;
324                 }
325                 cred->ccache_obtained = CRED_UNINITIALISED;
326         }
327         /* Now that we know that the data is 'this specified', then
328          * don't allow something less 'known' to be returned as a
329          * ccache.  Ie, if the username is on the commmand line, we
330          * don't want to later guess to use a file-based ccache */
331         if (obtained > cred->ccache_threshold) {
332                 cred->ccache_threshold  = obtained;
333         }
334
335         cli_credentials_invalidate_client_gss_creds(cred, 
336                                                     obtained);
337 }
338
339 static int free_gssapi_creds(struct gssapi_creds_container *gcc)
340 {
341         OM_uint32 min_stat, maj_stat;
342         maj_stat = gss_release_cred(&min_stat, &gcc->creds);
343         return 0;
344 }
345
346 int cli_credentials_get_client_gss_creds(struct cli_credentials *cred, 
347                                          struct gssapi_creds_container **_gcc) 
348 {
349         int ret = 0;
350         OM_uint32 maj_stat, min_stat;
351         struct gssapi_creds_container *gcc;
352         struct ccache_container *ccache;
353         if (cred->client_gss_creds_obtained >= cred->client_gss_creds_threshold) {
354                 *_gcc = cred->client_gss_creds;
355                 return 0;
356         }
357         ret = cli_credentials_get_ccache(cred, 
358                                          &ccache);
359         if (ret) {
360                 DEBUG(1, ("Failed to get CCACHE for GSSAPI client: %s\n", error_message(ret)));
361                 return ret;
362         }
363
364         gcc = talloc(cred, struct gssapi_creds_container);
365         if (!gcc) {
366                 return ENOMEM;
367         }
368
369         maj_stat = gss_krb5_import_cred(&min_stat, ccache->ccache, NULL, NULL, 
370                                         &gcc->creds);
371         if (maj_stat) {
372                 if (min_stat) {
373                         ret = min_stat;
374                 } else {
375                         ret = EINVAL;
376                 }
377         }
378         if (ret == 0) {
379                 cred->client_gss_creds_obtained = cred->ccache_obtained;
380                 talloc_set_destructor(gcc, free_gssapi_creds);
381                 cred->client_gss_creds = gcc;
382                 *_gcc = gcc;
383         }
384         return ret;
385 }
386
387 /**
388    Set a gssapi cred_id_t into the credentails system. (Client case)
389
390    This grabs the credentials both 'intact' and getting the krb5
391    ccache out of it.  This routine can be generalised in future for
392    the case where we deal with GSSAPI mechs other than krb5.  
393
394    On sucess, the caller must not free gssapi_cred, as it now belongs
395    to the credentials system.
396 */
397
398 int cli_credentials_set_client_gss_creds(struct cli_credentials *cred, 
399                                          gss_cred_id_t gssapi_cred,
400                                          enum credentials_obtained obtained) 
401 {
402         int ret;
403         OM_uint32 maj_stat, min_stat;
404         struct ccache_container *ccc;
405         struct gssapi_creds_container *gcc;
406         if (cred->client_gss_creds_obtained > obtained) {
407                 return 0;
408         }
409
410         gcc = talloc(cred, struct gssapi_creds_container);
411         if (!gcc) {
412                 return ENOMEM;
413         }
414
415         ret = cli_credentials_new_ccache(cred, &ccc);
416         if (ret != 0) {
417                 return ret;
418         }
419
420         maj_stat = gss_krb5_copy_ccache(&min_stat, 
421                                         gssapi_cred, ccc->ccache);
422         if (maj_stat) {
423                 if (min_stat) {
424                         ret = min_stat;
425                 } else {
426                         ret = EINVAL;
427                 }
428         }
429
430         if (ret == 0) {
431                 ret = cli_credentials_set_from_ccache(cred, ccc, obtained);
432         }
433         cred->ccache = ccc;
434         cred->ccache_obtained = obtained;
435         if (ret == 0) {
436                 gcc->creds = gssapi_cred;
437                 talloc_set_destructor(gcc, free_gssapi_creds);
438                 
439                 /* set the clinet_gss_creds_obtained here, as it just 
440                    got set to UNINITIALISED by the calls above */
441                 cred->client_gss_creds_obtained = obtained;
442                 cred->client_gss_creds = gcc;
443         }
444         return ret;
445 }
446
447 /* Get the keytab (actually, a container containing the krb5_keytab)
448  * attached to this context.  If this hasn't been done or set before,
449  * it will be generated from the password.
450  */
451 int cli_credentials_get_keytab(struct cli_credentials *cred, 
452                                struct keytab_container **_ktc)
453 {
454         krb5_error_code ret;
455         struct keytab_container *ktc;
456         struct smb_krb5_context *smb_krb5_context;
457         const char **enctype_strings;
458         TALLOC_CTX *mem_ctx;
459
460         if (cred->keytab_obtained >= (MAX(cred->principal_obtained, 
461                                           cred->username_obtained))) {
462                 *_ktc = cred->keytab;
463                 return 0;
464         }
465
466         if (cli_credentials_is_anonymous(cred)) {
467                 return EINVAL;
468         }
469
470         ret = cli_credentials_get_krb5_context(cred, &smb_krb5_context);
471         if (ret) {
472                 return ret;
473         }
474
475         mem_ctx = talloc_new(cred);
476         if (!mem_ctx) {
477                 return ENOMEM;
478         }
479
480         enctype_strings = cli_credentials_get_enctype_strings(cred);
481         
482         ret = smb_krb5_create_memory_keytab(mem_ctx, cred, 
483                                             smb_krb5_context, 
484                                             enctype_strings, &ktc);
485         if (ret) {
486                 talloc_free(mem_ctx);
487                 return ret;
488         }
489
490         cred->keytab_obtained = (MAX(cred->principal_obtained, 
491                                      cred->username_obtained));
492
493         talloc_steal(cred, ktc);
494         cred->keytab = ktc;
495         *_ktc = cred->keytab;
496         talloc_free(mem_ctx);
497         return ret;
498 }
499
500 /* Given the name of a keytab (presumably in the format
501  * FILE:/etc/krb5.keytab), open it and attach it */
502
503 int cli_credentials_set_keytab_name(struct cli_credentials *cred, 
504                                     const char *keytab_name, 
505                                     enum credentials_obtained obtained) 
506 {
507         krb5_error_code ret;
508         struct keytab_container *ktc;
509         struct smb_krb5_context *smb_krb5_context;
510         TALLOC_CTX *mem_ctx;
511
512         if (cred->keytab_obtained >= obtained) {
513                 return 0;
514         }
515
516         ret = cli_credentials_get_krb5_context(cred, &smb_krb5_context);
517         if (ret) {
518                 return ret;
519         }
520
521         mem_ctx = talloc_new(cred);
522         if (!mem_ctx) {
523                 return ENOMEM;
524         }
525
526         ret = smb_krb5_open_keytab(mem_ctx, smb_krb5_context, 
527                                    keytab_name, &ktc);
528         if (ret) {
529                 return ret;
530         }
531
532         cred->keytab_obtained = obtained;
533
534         talloc_steal(cred, ktc);
535         cred->keytab = ktc;
536         talloc_free(mem_ctx);
537
538         return ret;
539 }
540
541 int cli_credentials_update_keytab(struct cli_credentials *cred) 
542 {
543         krb5_error_code ret;
544         struct keytab_container *ktc;
545         struct smb_krb5_context *smb_krb5_context;
546         const char **enctype_strings;
547         TALLOC_CTX *mem_ctx;
548         
549         mem_ctx = talloc_new(cred);
550         if (!mem_ctx) {
551                 return ENOMEM;
552         }
553
554         ret = cli_credentials_get_krb5_context(cred, &smb_krb5_context);
555         if (ret) {
556                 talloc_free(mem_ctx);
557                 return ret;
558         }
559
560         enctype_strings = cli_credentials_get_enctype_strings(cred);
561         
562         ret = cli_credentials_get_keytab(cred, &ktc);
563         if (ret != 0) {
564                 talloc_free(mem_ctx);
565                 return ret;
566         }
567
568         ret = smb_krb5_update_keytab(mem_ctx, cred, smb_krb5_context, enctype_strings, ktc);
569
570         talloc_free(mem_ctx);
571         return ret;
572 }
573
574 /* Get server gss credentials (in gsskrb5, this means the keytab) */
575
576 int cli_credentials_get_server_gss_creds(struct cli_credentials *cred, 
577                                          struct gssapi_creds_container **_gcc) 
578 {
579         int ret = 0;
580         OM_uint32 maj_stat, min_stat;
581         struct gssapi_creds_container *gcc;
582         struct keytab_container *ktc;
583         struct smb_krb5_context *smb_krb5_context;
584         TALLOC_CTX *mem_ctx;
585         krb5_principal princ;
586
587         if (cred->server_gss_creds_obtained >= (MAX(cred->keytab_obtained, 
588                                                     MAX(cred->principal_obtained, 
589                                                         cred->username_obtained)))) {
590                 *_gcc = cred->server_gss_creds;
591                 return 0;
592         }
593
594         ret = cli_credentials_get_krb5_context(cred, &smb_krb5_context);
595         if (ret) {
596                 return ret;
597         }
598
599         ret = cli_credentials_get_keytab(cred, 
600                                          &ktc);
601         if (ret) {
602                 DEBUG(1, ("Failed to get keytab for GSSAPI server: %s\n", error_message(ret)));
603                 return ret;
604         }
605
606         mem_ctx = talloc_new(cred);
607         if (!mem_ctx) {
608                 return ENOMEM;
609         }
610
611         ret = principal_from_credentials(mem_ctx, cred, smb_krb5_context, &princ);
612         if (ret) {
613                 DEBUG(1,("cli_credentials_get_server_gss_creds: makeing krb5 principal failed (%s)\n",
614                          smb_get_krb5_error_message(smb_krb5_context->krb5_context, 
615                                                     ret, mem_ctx)));
616                 talloc_free(mem_ctx);
617                 return ret;
618         }
619
620         gcc = talloc(cred, struct gssapi_creds_container);
621         if (!gcc) {
622                 talloc_free(mem_ctx);
623                 return ENOMEM;
624         }
625
626         /* This creates a GSSAPI cred_id_t with the principal and keytab set */
627         maj_stat = gss_krb5_import_cred(&min_stat, NULL, princ, ktc->keytab, 
628                                         &gcc->creds);
629         if (maj_stat) {
630                 if (min_stat) {
631                         ret = min_stat;
632                 } else {
633                         ret = EINVAL;
634                 }
635         }
636         if (ret == 0) {
637                 cred->server_gss_creds_obtained = cred->keytab_obtained;
638                 talloc_set_destructor(gcc, free_gssapi_creds);
639                 cred->server_gss_creds = gcc;
640                 *_gcc = gcc;
641         }
642         talloc_free(mem_ctx);
643         return ret;
644 }
645
646 /** 
647  * Set Kerberos KVNO
648  */
649
650 void cli_credentials_set_kvno(struct cli_credentials *cred,
651                               int kvno)
652 {
653         cred->kvno = kvno;
654 }
655
656 /**
657  * Return Kerberos KVNO
658  */
659
660 int cli_credentials_get_kvno(struct cli_credentials *cred)
661 {
662         return cred->kvno;
663 }
664
665
666 const char **cli_credentials_get_enctype_strings(struct cli_credentials *cred) 
667 {
668         /* If this is ever made user-configurable, we need to add code
669          * to remove/hide the other entries from the generated
670          * keytab */
671         static const char *default_enctypes[] = {
672                 "des-cbc-md5",
673                 "aes256-cts-hmac-sha1-96",
674                 "des3-cbc-sha1",
675                 "arcfour-hmac-md5",
676                 NULL
677         };
678         return default_enctypes;
679 }
680
681 const char *cli_credentials_get_salt_principal(struct cli_credentials *cred) 
682 {
683         return cred->salt_principal;
684 }
685
686 void cli_credentials_set_salt_principal(struct cli_credentials *cred, const char *principal) 
687 {
688         cred->salt_principal = talloc_strdup(cred, principal);
689 }
690
691