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