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