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