r26399: Use -O option for SWIG (less evil generated code).
[ab/samba.git/.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 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 #include "param/param.h"
30
31 int cli_credentials_get_krb5_context(struct cli_credentials *cred, 
32                                      struct loadparm_context *lp_ctx,
33                                      struct smb_krb5_context **smb_krb5_context) 
34 {
35         int ret;
36         if (cred->smb_krb5_context) {
37                 *smb_krb5_context = cred->smb_krb5_context;
38                 return 0;
39         }
40
41         ret = smb_krb5_init_context(cred, cli_credentials_get_event_context(cred), 
42                                     lp_ctx, &cred->smb_krb5_context);
43         if (ret) {
44                 cred->smb_krb5_context = NULL;
45                 return ret;
46         }
47         *smb_krb5_context = cred->smb_krb5_context;
48         return 0;
49 }
50
51 /* This needs to be called directly after the cli_credentials_init(),
52  * otherwise we might have problems with the krb5 context already
53  * being here.
54  */
55 NTSTATUS cli_credentials_set_krb5_context(struct cli_credentials *cred, 
56                                           struct smb_krb5_context *smb_krb5_context)
57 {
58         if (!talloc_reference(cred, smb_krb5_context)) {
59                 return NT_STATUS_NO_MEMORY;
60         }
61         cred->smb_krb5_context = smb_krb5_context;
62         return NT_STATUS_OK;
63 }
64
65 static int cli_credentials_set_from_ccache(struct cli_credentials *cred, 
66                                     struct ccache_container *ccache,
67                                     enum credentials_obtained obtained)
68 {
69         
70         krb5_principal princ;
71         krb5_error_code ret;
72         char *name;
73         char **realm;
74
75         if (cred->ccache_obtained > obtained) {
76                 return 0;
77         }
78
79         ret = krb5_cc_get_principal(ccache->smb_krb5_context->krb5_context, 
80                                     ccache->ccache, &princ);
81
82         if (ret) {
83                 char *err_mess = smb_get_krb5_error_message(ccache->smb_krb5_context->krb5_context, 
84                                                             ret, cred);
85                 DEBUG(1,("failed to get principal from ccache: %s\n", 
86                          err_mess));
87                 talloc_free(err_mess);
88                 return ret;
89         }
90         
91         ret = krb5_unparse_name(ccache->smb_krb5_context->krb5_context, princ, &name);
92         if (ret) {
93                 char *err_mess = smb_get_krb5_error_message(ccache->smb_krb5_context->krb5_context, ret, cred);
94                 DEBUG(1,("failed to unparse principal from ccache: %s\n", 
95                          err_mess));
96                 talloc_free(err_mess);
97                 return ret;
98         }
99
100         realm = krb5_princ_realm(ccache->smb_krb5_context->krb5_context, princ);
101
102         cli_credentials_set_principal(cred, name, obtained);
103
104         free(name);
105
106         krb5_free_principal(ccache->smb_krb5_context->krb5_context, princ);
107
108         /* set the ccache_obtained here, as it just got set to UNINITIALISED by the calls above */
109         cred->ccache_obtained = obtained;
110
111         return 0;
112 }
113
114 /* Free a memory ccache */
115 static int free_mccache(struct ccache_container *ccc)
116 {
117         krb5_cc_destroy(ccc->smb_krb5_context->krb5_context, ccc->ccache);
118
119         return 0;
120 }
121
122 /* Free a disk-based ccache */
123 static int free_dccache(struct ccache_container *ccc) {
124         krb5_cc_close(ccc->smb_krb5_context->krb5_context, ccc->ccache);
125
126         return 0;
127 }
128
129 int cli_credentials_set_ccache(struct cli_credentials *cred, 
130                                const char *name, 
131                                enum credentials_obtained obtained)
132 {
133         krb5_error_code ret;
134         krb5_principal princ;
135         struct ccache_container *ccc;
136         if (cred->ccache_obtained > obtained) {
137                 return 0;
138         }
139
140         ccc = talloc(cred, struct ccache_container);
141         if (!ccc) {
142                 return ENOMEM;
143         }
144
145         ret = cli_credentials_get_krb5_context(cred, global_loadparm, 
146                                                &ccc->smb_krb5_context);
147         if (ret) {
148                 talloc_free(ccc);
149                 return ret;
150         }
151         if (!talloc_reference(ccc, ccc->smb_krb5_context)) {
152                 talloc_free(ccc);
153                 return ENOMEM;
154         }
155
156         if (name) {
157                 ret = krb5_cc_resolve(ccc->smb_krb5_context->krb5_context, name, &ccc->ccache);
158                 if (ret) {
159                         DEBUG(1,("failed to read krb5 ccache: %s: %s\n", 
160                                  name, 
161                                  smb_get_krb5_error_message(ccc->smb_krb5_context->krb5_context, ret, ccc)));
162                         talloc_free(ccc);
163                         return ret;
164                 }
165         } else {
166                 ret = krb5_cc_default(ccc->smb_krb5_context->krb5_context, &ccc->ccache);
167                 if (ret) {
168                         DEBUG(3,("failed to read default krb5 ccache: %s\n", 
169                                  smb_get_krb5_error_message(ccc->smb_krb5_context->krb5_context, ret, ccc)));
170                         talloc_free(ccc);
171                         return ret;
172                 }
173         }
174
175         talloc_set_destructor(ccc, free_dccache);
176
177         ret = krb5_cc_get_principal(ccc->smb_krb5_context->krb5_context, ccc->ccache, &princ);
178
179         if (ret) {
180                 DEBUG(3,("failed to get principal from default ccache: %s\n", 
181                          smb_get_krb5_error_message(ccc->smb_krb5_context->krb5_context, ret, ccc)));
182                 talloc_free(ccc);               
183                 return ret;
184         }
185
186         krb5_free_principal(ccc->smb_krb5_context->krb5_context, princ);
187
188         ret = cli_credentials_set_from_ccache(cred, ccc, obtained);
189
190         if (ret) {
191                 return ret;
192         }
193
194         cred->ccache = ccc;
195         cred->ccache_obtained = obtained;
196         talloc_steal(cred, ccc);
197
198         cli_credentials_invalidate_client_gss_creds(cred, cred->ccache_obtained);
199         return 0;
200 }
201
202
203 static int cli_credentials_new_ccache(struct cli_credentials *cred, 
204                                       struct ccache_container **_ccc)
205 {
206         krb5_error_code ret;
207         struct ccache_container *ccc = talloc(cred, struct ccache_container);
208         char *ccache_name;
209         if (!ccc) {
210                 return ENOMEM;
211         }
212
213         ccache_name = talloc_asprintf(ccc, "MEMORY:%p", 
214                                       ccc);
215
216         if (!ccache_name) {
217                 talloc_free(ccc);
218                 return ENOMEM;
219         }
220
221         ret = cli_credentials_get_krb5_context(cred, global_loadparm, 
222                                                &ccc->smb_krb5_context);
223         if (ret) {
224                 talloc_free(ccc);
225                 return ret;
226         }
227         if (!talloc_reference(ccc, ccc->smb_krb5_context)) {
228                 talloc_free(ccc);
229                 return ENOMEM;
230         }
231
232         ret = krb5_cc_resolve(ccc->smb_krb5_context->krb5_context, ccache_name, 
233                               &ccc->ccache);
234         if (ret) {
235                 DEBUG(1,("failed to generate a new krb5 ccache (%s): %s\n", 
236                          ccache_name,
237                          smb_get_krb5_error_message(ccc->smb_krb5_context->krb5_context, ret, ccc)));
238                 talloc_free(ccache_name);
239                 talloc_free(ccc);
240                 return ret;
241         }
242
243         talloc_set_destructor(ccc, free_mccache);
244
245         talloc_free(ccache_name);
246
247         *_ccc = ccc;
248
249         return ret;
250 }
251
252 int cli_credentials_get_ccache(struct cli_credentials *cred, 
253                                struct ccache_container **ccc)
254 {
255         krb5_error_code ret;
256         
257         if (cred->machine_account_pending) {
258                 cli_credentials_set_machine_account(cred);
259         }
260
261         if (cred->ccache_obtained >= cred->ccache_threshold && 
262             cred->ccache_obtained > CRED_UNINITIALISED) {
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_unlink(cred, 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_unlink(cred, 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             cred->client_gss_creds_obtained > CRED_UNINITIALISED) {
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 credentials 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, global_loadparm, 
472                                                &smb_krb5_context);
473         if (ret) {
474                 return ret;
475         }
476
477         mem_ctx = talloc_new(cred);
478         if (!mem_ctx) {
479                 return ENOMEM;
480         }
481
482         enctype_strings = cli_credentials_get_enctype_strings(cred);
483         
484         ret = smb_krb5_create_memory_keytab(mem_ctx, cred, 
485                                             smb_krb5_context, 
486                                             enctype_strings, &ktc);
487         if (ret) {
488                 talloc_free(mem_ctx);
489                 return ret;
490         }
491
492         cred->keytab_obtained = (MAX(cred->principal_obtained, 
493                                      cred->username_obtained));
494
495         talloc_steal(cred, ktc);
496         cred->keytab = ktc;
497         *_ktc = cred->keytab;
498         talloc_free(mem_ctx);
499         return ret;
500 }
501
502 /* Given the name of a keytab (presumably in the format
503  * FILE:/etc/krb5.keytab), open it and attach it */
504
505 int cli_credentials_set_keytab_name(struct cli_credentials *cred, 
506                                     const char *keytab_name, 
507                                     enum credentials_obtained obtained) 
508 {
509         krb5_error_code ret;
510         struct keytab_container *ktc;
511         struct smb_krb5_context *smb_krb5_context;
512         TALLOC_CTX *mem_ctx;
513
514         if (cred->keytab_obtained >= obtained) {
515                 return 0;
516         }
517
518         ret = cli_credentials_get_krb5_context(cred, global_loadparm, &smb_krb5_context);
519         if (ret) {
520                 return ret;
521         }
522
523         mem_ctx = talloc_new(cred);
524         if (!mem_ctx) {
525                 return ENOMEM;
526         }
527
528         ret = smb_krb5_open_keytab(mem_ctx, smb_krb5_context, 
529                                    keytab_name, &ktc);
530         if (ret) {
531                 return ret;
532         }
533
534         cred->keytab_obtained = obtained;
535
536         talloc_steal(cred, ktc);
537         cred->keytab = ktc;
538         talloc_free(mem_ctx);
539
540         return ret;
541 }
542
543 int cli_credentials_update_keytab(struct cli_credentials *cred) 
544 {
545         krb5_error_code ret;
546         struct keytab_container *ktc;
547         struct smb_krb5_context *smb_krb5_context;
548         const char **enctype_strings;
549         TALLOC_CTX *mem_ctx;
550         
551         mem_ctx = talloc_new(cred);
552         if (!mem_ctx) {
553                 return ENOMEM;
554         }
555
556         ret = cli_credentials_get_krb5_context(cred, global_loadparm, &smb_krb5_context);
557         if (ret) {
558                 talloc_free(mem_ctx);
559                 return ret;
560         }
561
562         enctype_strings = cli_credentials_get_enctype_strings(cred);
563         
564         ret = cli_credentials_get_keytab(cred, &ktc);
565         if (ret != 0) {
566                 talloc_free(mem_ctx);
567                 return ret;
568         }
569
570         ret = smb_krb5_update_keytab(mem_ctx, cred, smb_krb5_context, enctype_strings, ktc);
571
572         talloc_free(mem_ctx);
573         return ret;
574 }
575
576 /* Get server gss credentials (in gsskrb5, this means the keytab) */
577
578 int cli_credentials_get_server_gss_creds(struct cli_credentials *cred, 
579                                          struct gssapi_creds_container **_gcc) 
580 {
581         int ret = 0;
582         OM_uint32 maj_stat, min_stat;
583         struct gssapi_creds_container *gcc;
584         struct keytab_container *ktc;
585         struct smb_krb5_context *smb_krb5_context;
586         TALLOC_CTX *mem_ctx;
587         krb5_principal princ;
588
589         if (cred->server_gss_creds_obtained >= (MAX(cred->keytab_obtained, 
590                                                     MAX(cred->principal_obtained, 
591                                                         cred->username_obtained)))) {
592                 *_gcc = cred->server_gss_creds;
593                 return 0;
594         }
595
596         ret = cli_credentials_get_krb5_context(cred, global_loadparm, &smb_krb5_context);
597         if (ret) {
598                 return ret;
599         }
600
601         ret = cli_credentials_get_keytab(cred, 
602                                          &ktc);
603         if (ret) {
604                 DEBUG(1, ("Failed to get keytab for GSSAPI server: %s\n", error_message(ret)));
605                 return ret;
606         }
607
608         mem_ctx = talloc_new(cred);
609         if (!mem_ctx) {
610                 return ENOMEM;
611         }
612
613         ret = principal_from_credentials(mem_ctx, cred, smb_krb5_context, &princ);
614         if (ret) {
615                 DEBUG(1,("cli_credentials_get_server_gss_creds: makeing krb5 principal failed (%s)\n",
616                          smb_get_krb5_error_message(smb_krb5_context->krb5_context, 
617                                                     ret, mem_ctx)));
618                 talloc_free(mem_ctx);
619                 return ret;
620         }
621
622         gcc = talloc(cred, struct gssapi_creds_container);
623         if (!gcc) {
624                 talloc_free(mem_ctx);
625                 return ENOMEM;
626         }
627
628         /* This creates a GSSAPI cred_id_t with the principal and keytab set */
629         maj_stat = gss_krb5_import_cred(&min_stat, NULL, princ, ktc->keytab, 
630                                         &gcc->creds);
631         if (maj_stat) {
632                 if (min_stat) {
633                         ret = min_stat;
634                 } else {
635                         ret = EINVAL;
636                 }
637         }
638         if (ret == 0) {
639                 cred->server_gss_creds_obtained = cred->keytab_obtained;
640                 talloc_set_destructor(gcc, free_gssapi_creds);
641                 cred->server_gss_creds = gcc;
642                 *_gcc = gcc;
643         }
644         talloc_free(mem_ctx);
645         return ret;
646 }
647
648 /** 
649  * Set Kerberos KVNO
650  */
651
652 void cli_credentials_set_kvno(struct cli_credentials *cred,
653                               int kvno)
654 {
655         cred->kvno = kvno;
656 }
657
658 /**
659  * Return Kerberos KVNO
660  */
661
662 int cli_credentials_get_kvno(struct cli_credentials *cred)
663 {
664         return cred->kvno;
665 }
666
667
668 const char **cli_credentials_get_enctype_strings(struct cli_credentials *cred) 
669 {
670         /* If this is ever made user-configurable, we need to add code
671          * to remove/hide the other entries from the generated
672          * keytab */
673         static const char *default_enctypes[] = {
674                 "des-cbc-md5",
675                 "aes256-cts-hmac-sha1-96",
676                 "des3-cbc-sha1",
677                 "arcfour-hmac-md5",
678                 NULL
679         };
680         return default_enctypes;
681 }
682
683 const char *cli_credentials_get_salt_principal(struct cli_credentials *cred) 
684 {
685         return cred->salt_principal;
686 }
687
688 void cli_credentials_set_salt_principal(struct cli_credentials *cred, const char *principal) 
689 {
690         cred->salt_principal = talloc_strdup(cred, principal);
691 }
692
693