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