74dbb0a5d8c8f44c6b59e5c1248562e9fd585217
[kai/samba-autobuild/.git] / 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_proto.h"
29 #include "auth/credentials/credentials_krb5.h"
30 #include "auth/kerberos/kerberos_credentials.h"
31 #include "auth/kerberos/kerberos_srv_keytab.h"
32 #include "auth/kerberos/kerberos_util.h"
33 #include "param/param.h"
34
35 static void cli_credentials_invalidate_client_gss_creds(
36                                         struct cli_credentials *cred,
37                                         enum credentials_obtained obtained);
38
39 _PUBLIC_ int cli_credentials_get_krb5_context(struct cli_credentials *cred, 
40                                      struct loadparm_context *lp_ctx,
41                                      struct smb_krb5_context **smb_krb5_context) 
42 {
43         int ret;
44         if (cred->smb_krb5_context) {
45                 *smb_krb5_context = cred->smb_krb5_context;
46                 return 0;
47         }
48
49         ret = smb_krb5_init_context(cred, NULL, lp_ctx,
50                                     &cred->smb_krb5_context);
51         if (ret) {
52                 cred->smb_krb5_context = NULL;
53                 return ret;
54         }
55         *smb_krb5_context = cred->smb_krb5_context;
56         return 0;
57 }
58
59 /* For most predictable behaviour, this needs to be called directly after the cli_credentials_init(),
60  * otherwise we may still have references to the old smb_krb5_context in a credential cache etc
61  */
62 _PUBLIC_ NTSTATUS cli_credentials_set_krb5_context(struct cli_credentials *cred, 
63                                           struct smb_krb5_context *smb_krb5_context)
64 {
65         if (smb_krb5_context == NULL) {
66                 talloc_unlink(cred, cred->smb_krb5_context);
67                 cred->smb_krb5_context = NULL;
68                 return NT_STATUS_OK;
69         }
70
71         if (!talloc_reference(cred, smb_krb5_context)) {
72                 return NT_STATUS_NO_MEMORY;
73         }
74         cred->smb_krb5_context = smb_krb5_context;
75         return NT_STATUS_OK;
76 }
77
78 static int cli_credentials_set_from_ccache(struct cli_credentials *cred, 
79                                            struct ccache_container *ccache,
80                                            enum credentials_obtained obtained,
81                                            const char **error_string)
82 {
83         
84         krb5_principal princ;
85         krb5_error_code ret;
86         char *name;
87
88         if (cred->ccache_obtained > obtained) {
89                 return 0;
90         }
91
92         ret = krb5_cc_get_principal(ccache->smb_krb5_context->krb5_context, 
93                                     ccache->ccache, &princ);
94
95         if (ret) {
96                 (*error_string) = talloc_asprintf(cred, "failed to get principal from ccache: %s\n",
97                                                   smb_get_krb5_error_message(ccache->smb_krb5_context->krb5_context,
98                                                                              ret, cred));
99                 return ret;
100         }
101         
102         ret = krb5_unparse_name(ccache->smb_krb5_context->krb5_context, princ, &name);
103         if (ret) {
104                 (*error_string) = talloc_asprintf(cred, "failed to unparse principal from ccache: %s\n",
105                                                   smb_get_krb5_error_message(ccache->smb_krb5_context->krb5_context,
106                                                                              ret, cred));
107                 return ret;
108         }
109
110         cli_credentials_set_principal(cred, name, obtained);
111
112         free(name);
113
114         krb5_free_principal(ccache->smb_krb5_context->krb5_context, princ);
115
116         /* set the ccache_obtained here, as it just got set to UNINITIALISED by the calls above */
117         cred->ccache_obtained = obtained;
118
119         return 0;
120 }
121
122 /* Free a memory ccache */
123 static int free_mccache(struct ccache_container *ccc)
124 {
125         krb5_cc_destroy(ccc->smb_krb5_context->krb5_context, ccc->ccache);
126
127         return 0;
128 }
129
130 /* Free a disk-based ccache */
131 static int free_dccache(struct ccache_container *ccc) {
132         krb5_cc_close(ccc->smb_krb5_context->krb5_context, ccc->ccache);
133
134         return 0;
135 }
136
137 _PUBLIC_ int cli_credentials_set_ccache(struct cli_credentials *cred, 
138                                         struct loadparm_context *lp_ctx,
139                                         const char *name,
140                                         enum credentials_obtained obtained,
141                                         const char **error_string)
142 {
143         krb5_error_code ret;
144         krb5_principal princ;
145         struct ccache_container *ccc;
146         if (cred->ccache_obtained > obtained) {
147                 return 0;
148         }
149
150         ccc = talloc(cred, struct ccache_container);
151         if (!ccc) {
152                 (*error_string) = error_message(ENOMEM);
153                 return ENOMEM;
154         }
155
156         ret = cli_credentials_get_krb5_context(cred, lp_ctx,
157                                                &ccc->smb_krb5_context);
158         if (ret) {
159                 (*error_string) = error_message(ret);
160                 talloc_free(ccc);
161                 return ret;
162         }
163         if (!talloc_reference(ccc, ccc->smb_krb5_context)) {
164                 talloc_free(ccc);
165                 (*error_string) = error_message(ENOMEM);
166                 return ENOMEM;
167         }
168
169         if (name) {
170                 ret = krb5_cc_resolve(ccc->smb_krb5_context->krb5_context, name, &ccc->ccache);
171                 if (ret) {
172                         (*error_string) = talloc_asprintf(cred, "failed to read krb5 ccache: %s: %s\n",
173                                                           name,
174                                                           smb_get_krb5_error_message(ccc->smb_krb5_context->krb5_context,
175                                                                                      ret, ccc));
176                         talloc_free(ccc);
177                         return ret;
178                 }
179         } else {
180                 ret = krb5_cc_default(ccc->smb_krb5_context->krb5_context, &ccc->ccache);
181                 if (ret) {
182                         (*error_string) = talloc_asprintf(cred, "failed to read default krb5 ccache: %s\n",
183                                                           smb_get_krb5_error_message(ccc->smb_krb5_context->krb5_context,
184                                                                                      ret, ccc));
185                         talloc_free(ccc);
186                         return ret;
187                 }
188         }
189
190         talloc_set_destructor(ccc, free_dccache);
191
192         ret = krb5_cc_get_principal(ccc->smb_krb5_context->krb5_context, ccc->ccache, &princ);
193
194         if (ret == 0) {
195                 krb5_free_principal(ccc->smb_krb5_context->krb5_context, princ);
196                 ret = cli_credentials_set_from_ccache(cred, ccc, obtained, error_string);
197
198                 if (ret) {
199                         (*error_string) = error_message(ret);
200                         return ret;
201                 }
202
203                 cred->ccache = ccc;
204                 cred->ccache_obtained = obtained;
205                 talloc_steal(cred, ccc);
206
207                 cli_credentials_invalidate_client_gss_creds(cred, cred->ccache_obtained);
208                 return 0;
209         }
210         return 0;
211 }
212
213
214 static int cli_credentials_new_ccache(struct cli_credentials *cred, 
215                                       struct loadparm_context *lp_ctx,
216                                       char *ccache_name,
217                                       struct ccache_container **_ccc,
218                                       const char **error_string)
219 {
220         bool must_free_cc_name = false;
221         krb5_error_code ret;
222         struct ccache_container *ccc = talloc(cred, struct ccache_container);
223         if (!ccc) {
224                 return ENOMEM;
225         }
226
227         ret = cli_credentials_get_krb5_context(cred, lp_ctx,
228                                                &ccc->smb_krb5_context);
229         if (ret) {
230                 talloc_free(ccc);
231                 (*error_string) = talloc_asprintf(cred, "Failed to get krb5_context: %s",
232                                                   error_message(ret));
233                 return ret;
234         }
235         if (!talloc_reference(ccc, ccc->smb_krb5_context)) {
236                 talloc_free(ccc);
237                 (*error_string) = strerror(ENOMEM);
238                 return ENOMEM;
239         }
240
241         if (!ccache_name) {
242                 must_free_cc_name = true;
243
244                 if (lpcfg_parm_bool(lp_ctx, NULL, "credentials", "krb5_cc_file", false)) {
245                         ccache_name = talloc_asprintf(ccc, "FILE:/tmp/krb5_cc_samba_%u_%p", 
246                                                       (unsigned int)getpid(), ccc);
247                 } else {
248                         ccache_name = talloc_asprintf(ccc, "MEMORY:%p", 
249                                                       ccc);
250                 }
251
252                 if (!ccache_name) {
253                         talloc_free(ccc);
254                         (*error_string) = strerror(ENOMEM);
255                         return ENOMEM;
256                 }
257         }
258
259         ret = krb5_cc_resolve(ccc->smb_krb5_context->krb5_context, ccache_name, 
260                               &ccc->ccache);
261         if (ret) {
262                 (*error_string) = talloc_asprintf(cred, "failed to resolve a krb5 ccache (%s): %s\n",
263                                                   ccache_name,
264                                                   smb_get_krb5_error_message(ccc->smb_krb5_context->krb5_context,
265                                                                              ret, ccc));
266                 talloc_free(ccache_name);
267                 talloc_free(ccc);
268                 return ret;
269         }
270
271         if (strncasecmp(ccache_name, "MEMORY:", 7) == 0) {
272                 talloc_set_destructor(ccc, free_mccache);
273         } else {
274                 talloc_set_destructor(ccc, free_dccache);
275         }
276
277         if (must_free_cc_name) {
278                 talloc_free(ccache_name);
279         }
280
281         *_ccc = ccc;
282
283         return 0;
284 }
285
286 _PUBLIC_ int cli_credentials_get_named_ccache(struct cli_credentials *cred, 
287                                               struct tevent_context *event_ctx,
288                                               struct loadparm_context *lp_ctx,
289                                               char *ccache_name,
290                                               struct ccache_container **ccc,
291                                               const char **error_string)
292 {
293         krb5_error_code ret;
294         enum credentials_obtained obtained;
295         
296         if (cred->machine_account_pending) {
297                 cli_credentials_set_machine_account(cred, lp_ctx);
298         }
299
300         if (cred->ccache_obtained >= cred->ccache_threshold && 
301             cred->ccache_obtained > CRED_UNINITIALISED) {
302                 time_t lifetime;
303                 bool expired = false;
304                 ret = krb5_cc_get_lifetime(cred->ccache->smb_krb5_context->krb5_context, 
305                                            cred->ccache->ccache, &lifetime);
306                 if (ret == KRB5_CC_END) {
307                         /* If we have a particular ccache set, without
308                          * an initial ticket, then assume there is a
309                          * good reason */
310                 } else if (ret == 0) {
311                         if (lifetime == 0) {
312                                 DEBUG(3, ("Ticket in credentials cache for %s expired, will refresh\n",
313                                           cli_credentials_get_principal(cred, cred)));
314                                 expired = true;
315                         } else if (lifetime < 300) {
316                                 DEBUG(3, ("Ticket in credentials cache for %s will shortly expire (%u secs), will refresh\n", 
317                                           cli_credentials_get_principal(cred, cred), (unsigned int)lifetime));
318                                 expired = true;
319                         }
320                 } else {
321                         (*error_string) = talloc_asprintf(cred, "failed to get ccache lifetime: %s\n",
322                                                           smb_get_krb5_error_message(cred->ccache->smb_krb5_context->krb5_context,
323                                                                                      ret, cred));
324                         return ret;
325                 }
326
327                 DEBUG(5, ("Ticket in credentials cache for %s will expire in %u secs\n", 
328                           cli_credentials_get_principal(cred, cred), (unsigned int)lifetime));
329                 
330                 if (!expired) {
331                         *ccc = cred->ccache;
332                         return 0;
333                 }
334         }
335         if (cli_credentials_is_anonymous(cred)) {
336                 (*error_string) = "Cannot get anonymous kerberos credentials";
337                 return EINVAL;
338         }
339
340         ret = cli_credentials_new_ccache(cred, lp_ctx, ccache_name, ccc, error_string);
341         if (ret) {
342                 return ret;
343         }
344
345         ret = kinit_to_ccache(cred, cred, (*ccc)->smb_krb5_context, event_ctx, (*ccc)->ccache, &obtained, error_string);
346         if (ret) {
347                 return ret;
348         }
349
350         ret = cli_credentials_set_from_ccache(cred, *ccc, 
351                                               obtained, error_string);
352         
353         cred->ccache = *ccc;
354         cred->ccache_obtained = cred->principal_obtained;
355         if (ret) {
356                 return ret;
357         }
358         cli_credentials_invalidate_client_gss_creds(cred, cred->ccache_obtained);
359         return 0;
360 }
361
362 _PUBLIC_ int cli_credentials_get_ccache(struct cli_credentials *cred, 
363                                         struct tevent_context *event_ctx,
364                                         struct loadparm_context *lp_ctx,
365                                         struct ccache_container **ccc,
366                                         const char **error_string)
367 {
368         return cli_credentials_get_named_ccache(cred, event_ctx, lp_ctx, NULL, ccc, error_string);
369 }
370
371 /* We have good reason to think the ccache in these credentials is invalid - blow it away */
372 static void cli_credentials_unconditionally_invalidate_client_gss_creds(struct cli_credentials *cred)
373 {
374         if (cred->client_gss_creds_obtained > CRED_UNINITIALISED) {
375                 talloc_unlink(cred, cred->client_gss_creds);
376                 cred->client_gss_creds = NULL;
377         }
378         cred->client_gss_creds_obtained = CRED_UNINITIALISED;
379 }
380
381 void cli_credentials_invalidate_client_gss_creds(struct cli_credentials *cred, 
382                                                  enum credentials_obtained obtained)
383 {
384         /* If the caller just changed the username/password etc, then
385          * any cached credentials are now invalid */
386         if (obtained >= cred->client_gss_creds_obtained) {
387                 if (cred->client_gss_creds_obtained > CRED_UNINITIALISED) {
388                         talloc_unlink(cred, cred->client_gss_creds);
389                         cred->client_gss_creds = NULL;
390                 }
391                 cred->client_gss_creds_obtained = CRED_UNINITIALISED;
392         }
393         /* Now that we know that the data is 'this specified', then
394          * don't allow something less 'known' to be returned as a
395          * ccache.  Ie, if the username is on the command line, we
396          * don't want to later guess to use a file-based ccache */
397         if (obtained > cred->client_gss_creds_threshold) {
398                 cred->client_gss_creds_threshold = obtained;
399         }
400 }
401
402 /* We have good reason to think this CCACHE is invalid.  Blow it away */
403 static void cli_credentials_unconditionally_invalidate_ccache(struct cli_credentials *cred)
404 {
405         if (cred->ccache_obtained > CRED_UNINITIALISED) {
406                 talloc_unlink(cred, cred->ccache);
407                 cred->ccache = NULL;
408         }
409         cred->ccache_obtained = CRED_UNINITIALISED;
410
411         cli_credentials_unconditionally_invalidate_client_gss_creds(cred);
412 }
413
414 _PUBLIC_ void cli_credentials_invalidate_ccache(struct cli_credentials *cred, 
415                                        enum credentials_obtained obtained)
416 {
417         /* If the caller just changed the username/password etc, then
418          * any cached credentials are now invalid */
419         if (obtained >= cred->ccache_obtained) {
420                 if (cred->ccache_obtained > CRED_UNINITIALISED) {
421                         talloc_unlink(cred, cred->ccache);
422                         cred->ccache = NULL;
423                 }
424                 cred->ccache_obtained = CRED_UNINITIALISED;
425         }
426         /* Now that we know that the data is 'this specified', then
427          * don't allow something less 'known' to be returned as a
428          * ccache.  i.e, if the username is on the command line, we
429          * don't want to later guess to use a file-based ccache */
430         if (obtained > cred->ccache_threshold) {
431                 cred->ccache_threshold  = obtained;
432         }
433
434         cli_credentials_invalidate_client_gss_creds(cred, 
435                                                     obtained);
436 }
437
438 static int free_gssapi_creds(struct gssapi_creds_container *gcc)
439 {
440         OM_uint32 min_stat;
441         (void)gss_release_cred(&min_stat, &gcc->creds);
442         return 0;
443 }
444
445 _PUBLIC_ int cli_credentials_get_client_gss_creds(struct cli_credentials *cred, 
446                                                   struct tevent_context *event_ctx,
447                                                   struct loadparm_context *lp_ctx,
448                                                   struct gssapi_creds_container **_gcc,
449                                                   const char **error_string)
450 {
451         int ret = 0;
452         OM_uint32 maj_stat, min_stat;
453         struct gssapi_creds_container *gcc;
454         struct ccache_container *ccache;
455         gss_buffer_desc empty_buffer = GSS_C_EMPTY_BUFFER;
456         krb5_enctype *etypes = NULL;
457
458         if (cred->client_gss_creds_obtained >= cred->client_gss_creds_threshold && 
459             cred->client_gss_creds_obtained > CRED_UNINITIALISED) {
460                 bool expired = false;
461                 OM_uint32 lifetime = 0;
462                 gss_cred_usage_t usage = 0;
463                 maj_stat = gss_inquire_cred(&min_stat, cred->client_gss_creds->creds, 
464                                             NULL, &lifetime, &usage, NULL);
465                 if (maj_stat == GSS_S_CREDENTIALS_EXPIRED) {
466                         DEBUG(3, ("Credentials for %s expired, must refresh credentials cache\n", cli_credentials_get_principal(cred, cred)));
467                         expired = true;
468                 } else if (maj_stat == GSS_S_COMPLETE && lifetime < 300) {
469                         DEBUG(3, ("Credentials for %s will expire shortly (%u sec), must refresh credentials cache\n", cli_credentials_get_principal(cred, cred), lifetime));
470                         expired = true;
471                 } else if (maj_stat != GSS_S_COMPLETE) {
472                         *error_string = talloc_asprintf(cred, "inquiry of credential lifefime via GSSAPI gss_inquire_cred failed: %s\n",
473                                                         gssapi_error_string(cred, maj_stat, min_stat, NULL));
474                         return EINVAL;
475                 }
476                 if (expired) {
477                         cli_credentials_unconditionally_invalidate_client_gss_creds(cred);
478                 } else {
479                         DEBUG(5, ("GSSAPI credentials for %s will expire in %u secs\n", 
480                                   cli_credentials_get_principal(cred, cred), (unsigned int)lifetime));
481                 
482                         *_gcc = cred->client_gss_creds;
483                         return 0;
484                 }
485         }
486
487         ret = cli_credentials_get_ccache(cred, event_ctx, lp_ctx,
488                                          &ccache, error_string);
489         if (ret) {
490                 if (cli_credentials_get_kerberos_state(cred) == CRED_MUST_USE_KERBEROS) {
491                         DEBUG(1, ("Failed to get kerberos credentials (kerberos required): %s\n", *error_string));
492                 } else {
493                         DEBUG(4, ("Failed to get kerberos credentials: %s\n", *error_string));
494                 }
495                 return ret;
496         }
497
498         gcc = talloc(cred, struct gssapi_creds_container);
499         if (!gcc) {
500                 (*error_string) = error_message(ENOMEM);
501                 return ENOMEM;
502         }
503
504         maj_stat = gss_krb5_import_cred(&min_stat, ccache->ccache, NULL, NULL, 
505                                         &gcc->creds);
506         if ((maj_stat == GSS_S_FAILURE) && (min_stat == (OM_uint32)KRB5_CC_END || min_stat == (OM_uint32) KRB5_CC_NOTFOUND)) {
507                 /* This CCACHE is no good.  Ensure we don't use it again */
508                 cli_credentials_unconditionally_invalidate_ccache(cred);
509
510                 /* Now try again to get a ccache */
511                 ret = cli_credentials_get_ccache(cred, event_ctx, lp_ctx,
512                                                  &ccache, error_string);
513                 if (ret) {
514                         DEBUG(1, ("Failed to re-get CCACHE for GSSAPI client: %s\n", error_message(ret)));
515                         return ret;
516                 }
517
518                 maj_stat = gss_krb5_import_cred(&min_stat, ccache->ccache, NULL, NULL,
519                                                 &gcc->creds);
520
521         }
522
523         if (maj_stat) {
524                 talloc_free(gcc);
525                 if (min_stat) {
526                         ret = min_stat;
527                 } else {
528                         ret = EINVAL;
529                 }
530                 (*error_string) = talloc_asprintf(cred, "gss_krb5_import_cred failed: %s", error_message(ret));
531                 return ret;
532         }
533
534         /*
535          * transfer the enctypes from the smb_krb5_context to the gssapi layer
536          *
537          * We use 'our' smb_krb5_context to do the AS-REQ and it is possible
538          * to configure the enctypes via the krb5.conf.
539          *
540          * And the gss_init_sec_context() creates it's own krb5_context and
541          * the TGS-REQ had all enctypes in it and only the ones configured
542          * and used for the AS-REQ, so it wasn't possible to disable the usage
543          * of AES keys.
544          */
545         min_stat = krb5_get_default_in_tkt_etypes(ccache->smb_krb5_context->krb5_context,
546                                                   KRB5_PDU_NONE,
547                                                   &etypes);
548         if (min_stat == 0) {
549                 OM_uint32 num_ktypes;
550
551                 for (num_ktypes = 0; etypes[num_ktypes]; num_ktypes++);
552
553                 maj_stat = gss_krb5_set_allowable_enctypes(&min_stat, gcc->creds,
554                                                            num_ktypes,
555                                                            (int32_t *) etypes);
556                 krb5_xfree (etypes);
557                 if (maj_stat) {
558                         talloc_free(gcc);
559                         if (min_stat) {
560                                 ret = min_stat;
561                         } else {
562                                 ret = EINVAL;
563                         }
564                         (*error_string) = talloc_asprintf(cred, "gss_krb5_set_allowable_enctypes failed: %s", error_message(ret));
565                         return ret;
566                 }
567         }
568
569         /* don't force GSS_C_CONF_FLAG and GSS_C_INTEG_FLAG */
570         maj_stat = gss_set_cred_option(&min_stat, &gcc->creds,
571                                        GSS_KRB5_CRED_NO_CI_FLAGS_X,
572                                        &empty_buffer);
573         if (maj_stat) {
574                 talloc_free(gcc);
575                 if (min_stat) {
576                         ret = min_stat;
577                 } else {
578                         ret = EINVAL;
579                 }
580                 (*error_string) = talloc_asprintf(cred, "gss_set_cred_option failed: %s", error_message(ret));
581                 return ret;
582         }
583
584         cred->client_gss_creds_obtained = cred->ccache_obtained;
585         talloc_set_destructor(gcc, free_gssapi_creds);
586         cred->client_gss_creds = gcc;
587         *_gcc = gcc;
588         return 0;
589 }
590
591 /**
592    Set a gssapi cred_id_t into the credentials system. (Client case)
593
594    This grabs the credentials both 'intact' and getting the krb5
595    ccache out of it.  This routine can be generalised in future for
596    the case where we deal with GSSAPI mechs other than krb5.  
597
598    On sucess, the caller must not free gssapi_cred, as it now belongs
599    to the credentials system.
600 */
601
602  int cli_credentials_set_client_gss_creds(struct cli_credentials *cred, 
603                                           struct loadparm_context *lp_ctx,
604                                           gss_cred_id_t gssapi_cred,
605                                           enum credentials_obtained obtained,
606                                           const char **error_string)
607 {
608         int ret;
609         OM_uint32 maj_stat, min_stat;
610         struct ccache_container *ccc;
611         struct gssapi_creds_container *gcc;
612         if (cred->client_gss_creds_obtained > obtained) {
613                 return 0;
614         }
615
616         gcc = talloc(cred, struct gssapi_creds_container);
617         if (!gcc) {
618                 (*error_string) = error_message(ENOMEM);
619                 return ENOMEM;
620         }
621
622         ret = cli_credentials_new_ccache(cred, lp_ctx, NULL, &ccc, error_string);
623         if (ret != 0) {
624                 return ret;
625         }
626
627         maj_stat = gss_krb5_copy_ccache(&min_stat, 
628                                         gssapi_cred, ccc->ccache);
629         if (maj_stat) {
630                 if (min_stat) {
631                         ret = min_stat;
632                 } else {
633                         ret = EINVAL;
634                 }
635                 if (ret) {
636                         (*error_string) = error_message(ENOMEM);
637                 }
638         }
639
640         if (ret == 0) {
641                 ret = cli_credentials_set_from_ccache(cred, ccc, obtained, error_string);
642         }
643         cred->ccache = ccc;
644         cred->ccache_obtained = obtained;
645         if (ret == 0) {
646                 gcc->creds = gssapi_cred;
647                 talloc_set_destructor(gcc, free_gssapi_creds);
648                 
649                 /* set the clinet_gss_creds_obtained here, as it just 
650                    got set to UNINITIALISED by the calls above */
651                 cred->client_gss_creds_obtained = obtained;
652                 cred->client_gss_creds = gcc;
653         }
654         return ret;
655 }
656
657 /* Get the keytab (actually, a container containing the krb5_keytab)
658  * attached to this context.  If this hasn't been done or set before,
659  * it will be generated from the password.
660  */
661 _PUBLIC_ int cli_credentials_get_keytab(struct cli_credentials *cred, 
662                                         struct loadparm_context *lp_ctx,
663                                         struct keytab_container **_ktc)
664 {
665         krb5_error_code ret;
666         struct keytab_container *ktc;
667         struct smb_krb5_context *smb_krb5_context;
668         const char *keytab_name;
669         krb5_keytab keytab;
670         TALLOC_CTX *mem_ctx;
671
672         if (cred->keytab_obtained >= (MAX(cred->principal_obtained, 
673                                           cred->username_obtained))) {
674                 *_ktc = cred->keytab;
675                 return 0;
676         }
677
678         if (cli_credentials_is_anonymous(cred)) {
679                 return EINVAL;
680         }
681
682         ret = cli_credentials_get_krb5_context(cred, lp_ctx,
683                                                &smb_krb5_context);
684         if (ret) {
685                 return ret;
686         }
687
688         mem_ctx = talloc_new(cred);
689         if (!mem_ctx) {
690                 return ENOMEM;
691         }
692
693         ret = smb_krb5_create_memory_keytab(mem_ctx, smb_krb5_context,
694                                         cli_credentials_get_password(cred),
695                                         cli_credentials_get_username(cred),
696                                         cli_credentials_get_realm(cred),
697                                         cli_credentials_get_kvno(cred),
698                                         &keytab, &keytab_name);
699         if (ret) {
700                 talloc_free(mem_ctx);
701                 return ret;
702         }
703
704         ret = smb_krb5_get_keytab_container(mem_ctx, smb_krb5_context,
705                                             keytab, keytab_name, &ktc);
706         if (ret) {
707                 talloc_free(mem_ctx);
708                 return ret;
709         }
710
711         cred->keytab_obtained = (MAX(cred->principal_obtained, 
712                                      cred->username_obtained));
713
714         talloc_steal(cred, ktc);
715         cred->keytab = ktc;
716         *_ktc = cred->keytab;
717         talloc_free(mem_ctx);
718         return ret;
719 }
720
721 /* Given the name of a keytab (presumably in the format
722  * FILE:/etc/krb5.keytab), open it and attach it */
723
724 _PUBLIC_ int cli_credentials_set_keytab_name(struct cli_credentials *cred, 
725                                              struct loadparm_context *lp_ctx,
726                                              const char *keytab_name,
727                                              enum credentials_obtained obtained)
728 {
729         krb5_error_code ret;
730         struct keytab_container *ktc;
731         struct smb_krb5_context *smb_krb5_context;
732         TALLOC_CTX *mem_ctx;
733
734         if (cred->keytab_obtained >= obtained) {
735                 return 0;
736         }
737
738         ret = cli_credentials_get_krb5_context(cred, lp_ctx, &smb_krb5_context);
739         if (ret) {
740                 return ret;
741         }
742
743         mem_ctx = talloc_new(cred);
744         if (!mem_ctx) {
745                 return ENOMEM;
746         }
747
748         ret = smb_krb5_get_keytab_container(mem_ctx, smb_krb5_context,
749                                             NULL, keytab_name, &ktc);
750         if (ret) {
751                 return ret;
752         }
753
754         cred->keytab_obtained = obtained;
755
756         talloc_steal(cred, ktc);
757         cred->keytab = ktc;
758         talloc_free(mem_ctx);
759
760         return ret;
761 }
762
763 /* Get server gss credentials (in gsskrb5, this means the keytab) */
764
765 _PUBLIC_ int cli_credentials_get_server_gss_creds(struct cli_credentials *cred, 
766                                                   struct loadparm_context *lp_ctx,
767                                                   struct gssapi_creds_container **_gcc)
768 {
769         int ret = 0;
770         OM_uint32 maj_stat, min_stat;
771         struct gssapi_creds_container *gcc;
772         struct keytab_container *ktc;
773         struct smb_krb5_context *smb_krb5_context;
774         TALLOC_CTX *mem_ctx;
775         krb5_principal princ;
776         const char *error_string;
777         enum credentials_obtained obtained;
778
779         mem_ctx = talloc_new(cred);
780         if (!mem_ctx) {
781                 return ENOMEM;
782         }
783
784         ret = cli_credentials_get_krb5_context(cred, lp_ctx, &smb_krb5_context);
785         if (ret) {
786                 return ret;
787         }
788
789         ret = principal_from_credentials(mem_ctx, cred, smb_krb5_context, &princ, &obtained, &error_string);
790         if (ret) {
791                 DEBUG(1,("cli_credentials_get_server_gss_creds: making krb5 principal failed (%s)\n",
792                          error_string));
793                 talloc_free(mem_ctx);
794                 return ret;
795         }
796
797         if (cred->server_gss_creds_obtained >= (MAX(cred->keytab_obtained, obtained))) {
798                 talloc_free(mem_ctx);
799                 *_gcc = cred->server_gss_creds;
800                 return 0;
801         }
802
803         ret = cli_credentials_get_keytab(cred, lp_ctx, &ktc);
804         if (ret) {
805                 DEBUG(1, ("Failed to get keytab for GSSAPI server: %s\n", error_message(ret)));
806                 return ret;
807         }
808
809         gcc = talloc(cred, struct gssapi_creds_container);
810         if (!gcc) {
811                 talloc_free(mem_ctx);
812                 return ENOMEM;
813         }
814
815         if (obtained < CRED_SPECIFIED) {
816                 /* This creates a GSSAPI cred_id_t with the principal and keytab set */
817                 maj_stat = gss_krb5_import_cred(&min_stat, NULL, NULL, ktc->keytab,
818                                                 &gcc->creds);
819         } else {
820                 /* This creates a GSSAPI cred_id_t with the principal and keytab set */
821                 maj_stat = gss_krb5_import_cred(&min_stat, NULL, princ, ktc->keytab,
822                                                 &gcc->creds);
823         }
824         if (maj_stat) {
825                 if (min_stat) {
826                         ret = min_stat;
827                 } else {
828                         ret = EINVAL;
829                 }
830         }
831         if (ret == 0) {
832                 cred->server_gss_creds_obtained = cred->keytab_obtained;
833                 talloc_set_destructor(gcc, free_gssapi_creds);
834                 cred->server_gss_creds = gcc;
835                 *_gcc = gcc;
836         }
837         talloc_free(mem_ctx);
838         return ret;
839 }
840
841 /** 
842  * Set Kerberos KVNO
843  */
844
845 _PUBLIC_ void cli_credentials_set_kvno(struct cli_credentials *cred,
846                               int kvno)
847 {
848         cred->kvno = kvno;
849 }
850
851 /**
852  * Return Kerberos KVNO
853  */
854
855 _PUBLIC_ int cli_credentials_get_kvno(struct cli_credentials *cred)
856 {
857         return cred->kvno;
858 }
859
860
861 const char *cli_credentials_get_salt_principal(struct cli_credentials *cred) 
862 {
863         return cred->salt_principal;
864 }
865
866 _PUBLIC_ void cli_credentials_set_salt_principal(struct cli_credentials *cred, const char *principal) 
867 {
868         talloc_free(cred->salt_principal);
869         cred->salt_principal = talloc_strdup(cred, principal);
870 }
871
872 /* The 'impersonate_principal' is used to allow one Kerberos principal
873  * (and it's associated keytab etc) to impersonate another.  The
874  * ability to do this is controlled by the KDC, but it is generally
875  * permitted to impersonate anyone to yourself.  This allows any
876  * member of the domain to get the groups of a user.  This is also
877  * known as S4U2Self */
878
879 _PUBLIC_ const char *cli_credentials_get_impersonate_principal(struct cli_credentials *cred)
880 {
881         return cred->impersonate_principal;
882 }
883
884 /*
885  * The 'self_service' is the service principal that
886  * represents the same object (by its objectSid)
887  * as the client principal (typically our machine account).
888  * When trying to impersonate 'impersonate_principal' with
889  * S4U2Self.
890  */
891 _PUBLIC_ const char *cli_credentials_get_self_service(struct cli_credentials *cred)
892 {
893         return cred->self_service;
894 }
895
896 _PUBLIC_ void cli_credentials_set_impersonate_principal(struct cli_credentials *cred,
897                                                         const char *principal,
898                                                         const char *self_service)
899 {
900         talloc_free(cred->impersonate_principal);
901         cred->impersonate_principal = talloc_strdup(cred, principal);
902         talloc_free(cred->self_service);
903         cred->self_service = talloc_strdup(cred, self_service);
904         cli_credentials_set_kerberos_state(cred, CRED_MUST_USE_KERBEROS);
905 }
906
907 /*
908  * when impersonating for S4U2proxy we need to set the target principal.
909  * Similarly, we may only be authorized to do general impersonation to
910  * some particular services.
911  *
912  * Likewise, password changes typically require a ticket to kpasswd/realm directly, not via a TGT
913  *
914  * NULL means that tickets will be obtained for the krbtgt service.
915 */
916
917 const char *cli_credentials_get_target_service(struct cli_credentials *cred)
918 {
919         return cred->target_service;
920 }
921
922 _PUBLIC_ void cli_credentials_set_target_service(struct cli_credentials *cred, const char *target_service)
923 {
924         talloc_free(cred->target_service);
925         cred->target_service = talloc_strdup(cred, target_service);
926 }
927