credentials: Simplify cli_credentials_get_server_gss_creds()
[metze/samba/wip.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_internal.h"
30 #include "auth/credentials/credentials_proto.h"
31 #include "auth/credentials/credentials_krb5.h"
32 #include "auth/kerberos/kerberos_credentials.h"
33 #include "auth/kerberos/kerberos_srv_keytab.h"
34 #include "auth/kerberos/kerberos_util.h"
35 #include "auth/kerberos/pac_utils.h"
36 #include "param/param.h"
37
38 static void cli_credentials_invalidate_client_gss_creds(
39                                         struct cli_credentials *cred,
40                                         enum credentials_obtained obtained);
41
42 /* Free a memory ccache */
43 static int free_mccache(struct ccache_container *ccc)
44 {
45         if (ccc->ccache != NULL) {
46                 krb5_cc_destroy(ccc->smb_krb5_context->krb5_context,
47                                 ccc->ccache);
48                 ccc->ccache = NULL;
49         }
50
51         return 0;
52 }
53
54 /* Free a disk-based ccache */
55 static int free_dccache(struct ccache_container *ccc)
56 {
57         if (ccc->ccache != NULL) {
58                 krb5_cc_close(ccc->smb_krb5_context->krb5_context,
59                               ccc->ccache);
60                 ccc->ccache = NULL;
61         }
62
63         return 0;
64 }
65
66 static uint32_t smb_gss_krb5_copy_ccache(uint32_t *min_stat,
67                                          gss_cred_id_t cred,
68                                          struct ccache_container *ccc)
69 {
70 #ifndef SAMBA4_USES_HEIMDAL /* MIT 1.10 */
71         krb5_context context = ccc->smb_krb5_context->krb5_context;
72         krb5_ccache dummy_ccache = NULL;
73         krb5_creds creds = {0};
74         krb5_cc_cursor cursor = NULL;
75         krb5_principal princ = NULL;
76         krb5_error_code code;
77         char *dummy_name;
78         uint32_t maj_stat = GSS_S_FAILURE;
79
80         dummy_name = talloc_asprintf(ccc,
81                                      "MEMORY:gss_krb5_copy_ccache-%p",
82                                      &ccc->ccache);
83         if (dummy_name == NULL) {
84                 *min_stat = ENOMEM;
85                 return GSS_S_FAILURE;
86         }
87
88         /*
89          * Create a dummy ccache, so we can iterate over the credentials
90          * and find the default principal for the ccache we want to
91          * copy. The new ccache needs to be initialized with this
92          * principal.
93          */
94         code = krb5_cc_resolve(context, dummy_name, &dummy_ccache);
95         TALLOC_FREE(dummy_name);
96         if (code != 0) {
97                 *min_stat = code;
98                 return GSS_S_FAILURE;
99         }
100
101         /*
102          * We do not need set a default principal on the temporary dummy
103          * ccache, as we do consume it at all in this function.
104          */
105         maj_stat = gss_krb5_copy_ccache(min_stat, cred, dummy_ccache);
106         if (maj_stat != 0) {
107                 krb5_cc_close(context, dummy_ccache);
108                 return maj_stat;
109         }
110
111         code = krb5_cc_start_seq_get(context, dummy_ccache, &cursor);
112         if (code != 0) {
113                 krb5_cc_close(context, dummy_ccache);
114                 *min_stat = EINVAL;
115                 return GSS_S_FAILURE;
116         }
117
118         code = krb5_cc_next_cred(context,
119                                  dummy_ccache,
120                                  &cursor,
121                                  &creds);
122         if (code != 0) {
123                 krb5_cc_close(context, dummy_ccache);
124                 *min_stat = EINVAL;
125                 return GSS_S_FAILURE;
126         }
127
128         do {
129                 if (creds.ticket_flags & TKT_FLG_PRE_AUTH) {
130                         krb5_data *tgs;
131
132                         tgs = krb5_princ_component(context,
133                                                    creds.server,
134                                                    0);
135                         if (tgs != NULL && tgs->length >= 1) {
136                                 int cmp;
137
138                                 cmp = memcmp(tgs->data,
139                                              KRB5_TGS_NAME,
140                                              tgs->length);
141                                 if (cmp == 0 && creds.client != NULL) {
142                                         princ = creds.client;
143                                         code = KRB5_CC_END;
144                                         break;
145                                 }
146                         }
147                 }
148
149                 krb5_free_cred_contents(context, &creds);
150
151                 code = krb5_cc_next_cred(context,
152                                          dummy_ccache,
153                                          &cursor,
154                                          &creds);
155         } while (code == 0);
156
157         if (code == KRB5_CC_END) {
158                 krb5_cc_end_seq_get(context, dummy_ccache, &cursor);
159                 code = 0;
160         }
161         krb5_cc_close(context, dummy_ccache);
162
163         if (code != 0 || princ == NULL) {
164                 krb5_free_cred_contents(context, &creds);
165                 *min_stat = EINVAL;
166                 return GSS_S_FAILURE;
167         }
168
169         /*
170          * Set the default principal for the cache we copy
171          * into. This is needed to be able that other calls
172          * can read it with e.g. gss_acquire_cred() or
173          * krb5_cc_get_principal().
174          */
175         code = krb5_cc_initialize(context, ccc->ccache, princ);
176         if (code != 0) {
177                 krb5_free_cred_contents(context, &creds);
178                 *min_stat = EINVAL;
179                 return GSS_S_FAILURE;
180         }
181         krb5_free_cred_contents(context, &creds);
182
183 #endif /* SAMBA4_USES_HEIMDAL */
184
185         return gss_krb5_copy_ccache(min_stat,
186                                     cred,
187                                     ccc->ccache);
188 }
189
190 _PUBLIC_ int cli_credentials_get_krb5_context(struct cli_credentials *cred, 
191                                      struct loadparm_context *lp_ctx,
192                                      struct smb_krb5_context **smb_krb5_context) 
193 {
194         int ret;
195         if (cred->smb_krb5_context) {
196                 *smb_krb5_context = cred->smb_krb5_context;
197                 return 0;
198         }
199
200         ret = smb_krb5_init_context(cred, lp_ctx,
201                                     &cred->smb_krb5_context);
202         if (ret) {
203                 cred->smb_krb5_context = NULL;
204                 return ret;
205         }
206         *smb_krb5_context = cred->smb_krb5_context;
207         return 0;
208 }
209
210 /* For most predictable behaviour, this needs to be called directly after the cli_credentials_init(),
211  * otherwise we may still have references to the old smb_krb5_context in a credential cache etc
212  */
213 _PUBLIC_ NTSTATUS cli_credentials_set_krb5_context(struct cli_credentials *cred, 
214                                           struct smb_krb5_context *smb_krb5_context)
215 {
216         if (smb_krb5_context == NULL) {
217                 talloc_unlink(cred, cred->smb_krb5_context);
218                 cred->smb_krb5_context = NULL;
219                 return NT_STATUS_OK;
220         }
221
222         if (!talloc_reference(cred, smb_krb5_context)) {
223                 return NT_STATUS_NO_MEMORY;
224         }
225         cred->smb_krb5_context = smb_krb5_context;
226         return NT_STATUS_OK;
227 }
228
229 static int cli_credentials_set_from_ccache(struct cli_credentials *cred, 
230                                            struct ccache_container *ccache,
231                                            enum credentials_obtained obtained,
232                                            const char **error_string)
233 {
234         bool ok;
235         char *realm;
236         krb5_principal princ;
237         krb5_error_code ret;
238         char *name;
239
240         if (cred->ccache_obtained > obtained) {
241                 return 0;
242         }
243
244         ret = krb5_cc_get_principal(ccache->smb_krb5_context->krb5_context, 
245                                     ccache->ccache, &princ);
246
247         if (ret) {
248                 (*error_string) = talloc_asprintf(cred, "failed to get principal from ccache: %s\n",
249                                                   smb_get_krb5_error_message(ccache->smb_krb5_context->krb5_context,
250                                                                              ret, cred));
251                 return ret;
252         }
253         
254         ret = krb5_unparse_name(ccache->smb_krb5_context->krb5_context, princ, &name);
255         if (ret) {
256                 (*error_string) = talloc_asprintf(cred, "failed to unparse principal from ccache: %s\n",
257                                                   smb_get_krb5_error_message(ccache->smb_krb5_context->krb5_context,
258                                                                              ret, cred));
259                 return ret;
260         }
261
262         ok = cli_credentials_set_principal(cred, name, obtained);
263         krb5_free_unparsed_name(ccache->smb_krb5_context->krb5_context, name);
264         if (!ok) {
265                 krb5_free_principal(ccache->smb_krb5_context->krb5_context, princ);
266                 return ENOMEM;
267         }
268
269         realm = smb_krb5_principal_get_realm(ccache->smb_krb5_context->krb5_context,
270                                              princ);
271         krb5_free_principal(ccache->smb_krb5_context->krb5_context, princ);
272         if (realm == NULL) {
273                 return ENOMEM;
274         }
275         ok = cli_credentials_set_realm(cred, realm, obtained);
276         SAFE_FREE(realm);
277         if (!ok) {
278                 return ENOMEM;
279         }
280
281         /* set the ccache_obtained here, as it just got set to UNINITIALISED by the calls above */
282         cred->ccache_obtained = obtained;
283
284         return 0;
285 }
286
287 _PUBLIC_ int cli_credentials_set_ccache(struct cli_credentials *cred, 
288                                         struct loadparm_context *lp_ctx,
289                                         const char *name,
290                                         enum credentials_obtained obtained,
291                                         const char **error_string)
292 {
293         krb5_error_code ret;
294         krb5_principal princ;
295         struct ccache_container *ccc;
296         if (cred->ccache_obtained > obtained) {
297                 return 0;
298         }
299
300         ccc = talloc(cred, struct ccache_container);
301         if (!ccc) {
302                 (*error_string) = error_message(ENOMEM);
303                 return ENOMEM;
304         }
305
306         ret = cli_credentials_get_krb5_context(cred, lp_ctx,
307                                                &ccc->smb_krb5_context);
308         if (ret) {
309                 (*error_string) = error_message(ret);
310                 talloc_free(ccc);
311                 return ret;
312         }
313         if (!talloc_reference(ccc, ccc->smb_krb5_context)) {
314                 talloc_free(ccc);
315                 (*error_string) = error_message(ENOMEM);
316                 return ENOMEM;
317         }
318
319         if (name) {
320                 ret = krb5_cc_resolve(ccc->smb_krb5_context->krb5_context, name, &ccc->ccache);
321                 if (ret) {
322                         (*error_string) = talloc_asprintf(cred, "failed to read krb5 ccache: %s: %s\n",
323                                                           name,
324                                                           smb_get_krb5_error_message(ccc->smb_krb5_context->krb5_context,
325                                                                                      ret, ccc));
326                         talloc_free(ccc);
327                         return ret;
328                 }
329         } else {
330                 ret = krb5_cc_default(ccc->smb_krb5_context->krb5_context, &ccc->ccache);
331                 if (ret) {
332                         (*error_string) = talloc_asprintf(cred, "failed to read default krb5 ccache: %s\n",
333                                                           smb_get_krb5_error_message(ccc->smb_krb5_context->krb5_context,
334                                                                                      ret, ccc));
335                         talloc_free(ccc);
336                         return ret;
337                 }
338         }
339
340         talloc_set_destructor(ccc, free_dccache);
341
342         ret = krb5_cc_get_principal(ccc->smb_krb5_context->krb5_context, ccc->ccache, &princ);
343
344         if (ret == 0) {
345                 krb5_free_principal(ccc->smb_krb5_context->krb5_context, princ);
346                 ret = cli_credentials_set_from_ccache(cred, ccc, obtained, error_string);
347
348                 if (ret) {
349                         (*error_string) = error_message(ret);
350                         return ret;
351                 }
352
353                 cred->ccache = ccc;
354                 cred->ccache_obtained = obtained;
355                 talloc_steal(cred, ccc);
356
357                 cli_credentials_invalidate_client_gss_creds(cred, cred->ccache_obtained);
358                 return 0;
359         }
360         return 0;
361 }
362
363 /*
364  * Indicate the we failed to log in to this service/host with these
365  * credentials.  The caller passes an unsigned int which they
366  * initialise to the number of times they would like to retry.
367  *
368  * This method is used to support re-trying with freshly fetched
369  * credentials in case a server is rebuilt while clients have
370  * non-expired tickets. When the client code gets a logon failure they
371  * throw away the existing credentials for the server and retry.
372  */
373 _PUBLIC_ bool cli_credentials_failed_kerberos_login(struct cli_credentials *cred,
374                                                     const char *principal,
375                                                     unsigned int *count)
376 {
377         struct ccache_container *ccc;
378         krb5_creds creds, creds2;
379         int ret;
380
381         if (principal == NULL) {
382                 /* no way to delete if we don't know the principal */
383                 return false;
384         }
385
386         ccc = cred->ccache;
387         if (ccc == NULL) {
388                 /* not a kerberos connection */
389                 return false;
390         }
391
392         if (*count > 0) {
393                 /* We have already tried discarding the credentials */
394                 return false;
395         }
396         (*count)++;
397
398         ZERO_STRUCT(creds);
399         ret = krb5_parse_name(ccc->smb_krb5_context->krb5_context, principal, &creds.server);
400         if (ret != 0) {
401                 return false;
402         }
403
404         ret = krb5_cc_retrieve_cred(ccc->smb_krb5_context->krb5_context, ccc->ccache, KRB5_TC_MATCH_SRV_NAMEONLY, &creds, &creds2);
405         if (ret != 0) {
406                 /* don't retry - we didn't find these credentials to remove */
407                 krb5_free_cred_contents(ccc->smb_krb5_context->krb5_context, &creds);
408                 return false;
409         }
410
411         ret = krb5_cc_remove_cred(ccc->smb_krb5_context->krb5_context, ccc->ccache, KRB5_TC_MATCH_SRV_NAMEONLY, &creds);
412         krb5_free_cred_contents(ccc->smb_krb5_context->krb5_context, &creds);
413         krb5_free_cred_contents(ccc->smb_krb5_context->krb5_context, &creds2);
414         if (ret != 0) {
415                 /* don't retry - we didn't find these credentials to
416                  * remove. Note that with the current backend this
417                  * never happens, as it always returns 0 even if the
418                  * creds don't exist, which is why we do a separate
419                  * krb5_cc_retrieve_cred() above.
420                  */
421                 return false;
422         }
423         return true;
424 }
425
426
427 static int cli_credentials_new_ccache(struct cli_credentials *cred, 
428                                       struct loadparm_context *lp_ctx,
429                                       char *ccache_name,
430                                       struct ccache_container **_ccc,
431                                       const char **error_string)
432 {
433         bool must_free_cc_name = false;
434         krb5_error_code ret;
435         struct ccache_container *ccc = talloc(cred, struct ccache_container);
436         if (!ccc) {
437                 return ENOMEM;
438         }
439
440         ret = cli_credentials_get_krb5_context(cred, lp_ctx,
441                                                &ccc->smb_krb5_context);
442         if (ret) {
443                 talloc_free(ccc);
444                 (*error_string) = talloc_asprintf(cred, "Failed to get krb5_context: %s",
445                                                   error_message(ret));
446                 return ret;
447         }
448         if (!talloc_reference(ccc, ccc->smb_krb5_context)) {
449                 talloc_free(ccc);
450                 (*error_string) = strerror(ENOMEM);
451                 return ENOMEM;
452         }
453
454         if (!ccache_name) {
455                 must_free_cc_name = true;
456
457                 if (lpcfg_parm_bool(lp_ctx, NULL, "credentials", "krb5_cc_file", false)) {
458                         ccache_name = talloc_asprintf(ccc, "FILE:/tmp/krb5_cc_samba_%u_%p", 
459                                                       (unsigned int)getpid(), ccc);
460                 } else {
461                         ccache_name = talloc_asprintf(ccc, "MEMORY:%p", 
462                                                       ccc);
463                 }
464
465                 if (!ccache_name) {
466                         talloc_free(ccc);
467                         (*error_string) = strerror(ENOMEM);
468                         return ENOMEM;
469                 }
470         }
471
472         ret = krb5_cc_resolve(ccc->smb_krb5_context->krb5_context, ccache_name, 
473                               &ccc->ccache);
474         if (ret) {
475                 (*error_string) = talloc_asprintf(cred, "failed to resolve a krb5 ccache (%s): %s\n",
476                                                   ccache_name,
477                                                   smb_get_krb5_error_message(ccc->smb_krb5_context->krb5_context,
478                                                                              ret, ccc));
479                 talloc_free(ccache_name);
480                 talloc_free(ccc);
481                 return ret;
482         }
483
484         if (strncasecmp(ccache_name, "MEMORY:", 7) == 0) {
485                 talloc_set_destructor(ccc, free_mccache);
486         } else {
487                 talloc_set_destructor(ccc, free_dccache);
488         }
489
490         if (must_free_cc_name) {
491                 talloc_free(ccache_name);
492         }
493
494         *_ccc = ccc;
495
496         return 0;
497 }
498
499 _PUBLIC_ int cli_credentials_get_named_ccache(struct cli_credentials *cred, 
500                                               struct tevent_context *event_ctx,
501                                               struct loadparm_context *lp_ctx,
502                                               char *ccache_name,
503                                               struct ccache_container **ccc,
504                                               const char **error_string)
505 {
506         krb5_error_code ret;
507         enum credentials_obtained obtained;
508         
509         if (cred->machine_account_pending) {
510                 cli_credentials_set_machine_account(cred, lp_ctx);
511         }
512
513         if (cred->ccache_obtained >= cred->ccache_threshold && 
514             cred->ccache_obtained > CRED_UNINITIALISED) {
515                 time_t lifetime;
516                 bool expired = false;
517                 ret = smb_krb5_cc_get_lifetime(cred->ccache->smb_krb5_context->krb5_context,
518                                                cred->ccache->ccache, &lifetime);
519                 if (ret == KRB5_CC_END) {
520                         /* If we have a particular ccache set, without
521                          * an initial ticket, then assume there is a
522                          * good reason */
523                 } else if (ret == 0) {
524                         if (lifetime == 0) {
525                                 DEBUG(3, ("Ticket in credentials cache for %s expired, will refresh\n",
526                                           cli_credentials_get_principal(cred, cred)));
527                                 expired = true;
528                         } else if (lifetime < 300) {
529                                 DEBUG(3, ("Ticket in credentials cache for %s will shortly expire (%u secs), will refresh\n", 
530                                           cli_credentials_get_principal(cred, cred), (unsigned int)lifetime));
531                                 expired = true;
532                         }
533                 } else {
534                         (*error_string) = talloc_asprintf(cred, "failed to get ccache lifetime: %s\n",
535                                                           smb_get_krb5_error_message(cred->ccache->smb_krb5_context->krb5_context,
536                                                                                      ret, cred));
537                         return ret;
538                 }
539
540                 DEBUG(5, ("Ticket in credentials cache for %s will expire in %u secs\n", 
541                           cli_credentials_get_principal(cred, cred), (unsigned int)lifetime));
542                 
543                 if (!expired) {
544                         *ccc = cred->ccache;
545                         return 0;
546                 }
547         }
548         if (cli_credentials_is_anonymous(cred)) {
549                 (*error_string) = "Cannot get anonymous kerberos credentials";
550                 return EINVAL;
551         }
552
553         ret = cli_credentials_new_ccache(cred, lp_ctx, ccache_name, ccc, error_string);
554         if (ret) {
555                 return ret;
556         }
557
558         ret = kinit_to_ccache(cred, cred, (*ccc)->smb_krb5_context, event_ctx, (*ccc)->ccache, &obtained, error_string);
559         if (ret) {
560                 return ret;
561         }
562
563         ret = cli_credentials_set_from_ccache(cred, *ccc, 
564                                               obtained, error_string);
565         
566         cred->ccache = *ccc;
567         cred->ccache_obtained = cred->principal_obtained;
568         if (ret) {
569                 return ret;
570         }
571         cli_credentials_invalidate_client_gss_creds(cred, cred->ccache_obtained);
572         return 0;
573 }
574
575 _PUBLIC_ int cli_credentials_get_ccache(struct cli_credentials *cred, 
576                                         struct tevent_context *event_ctx,
577                                         struct loadparm_context *lp_ctx,
578                                         struct ccache_container **ccc,
579                                         const char **error_string)
580 {
581         return cli_credentials_get_named_ccache(cred, event_ctx, lp_ctx, NULL, ccc, error_string);
582 }
583
584 /* We have good reason to think the ccache in these credentials is invalid - blow it away */
585 static void cli_credentials_unconditionally_invalidate_client_gss_creds(struct cli_credentials *cred)
586 {
587         if (cred->client_gss_creds_obtained > CRED_UNINITIALISED) {
588                 talloc_unlink(cred, cred->client_gss_creds);
589                 cred->client_gss_creds = NULL;
590         }
591         cred->client_gss_creds_obtained = CRED_UNINITIALISED;
592 }
593
594 void cli_credentials_invalidate_client_gss_creds(struct cli_credentials *cred, 
595                                                  enum credentials_obtained obtained)
596 {
597         /* If the caller just changed the username/password etc, then
598          * any cached credentials are now invalid */
599         if (obtained >= cred->client_gss_creds_obtained) {
600                 if (cred->client_gss_creds_obtained > CRED_UNINITIALISED) {
601                         talloc_unlink(cred, cred->client_gss_creds);
602                         cred->client_gss_creds = NULL;
603                 }
604                 cred->client_gss_creds_obtained = CRED_UNINITIALISED;
605         }
606         /* Now that we know that the data is 'this specified', then
607          * don't allow something less 'known' to be returned as a
608          * ccache.  Ie, if the username is on the command line, we
609          * don't want to later guess to use a file-based ccache */
610         if (obtained > cred->client_gss_creds_threshold) {
611                 cred->client_gss_creds_threshold = obtained;
612         }
613 }
614
615 /* We have good reason to think this CCACHE is invalid.  Blow it away */
616 static void cli_credentials_unconditionally_invalidate_ccache(struct cli_credentials *cred)
617 {
618         if (cred->ccache_obtained > CRED_UNINITIALISED) {
619                 talloc_unlink(cred, cred->ccache);
620                 cred->ccache = NULL;
621         }
622         cred->ccache_obtained = CRED_UNINITIALISED;
623
624         cli_credentials_unconditionally_invalidate_client_gss_creds(cred);
625 }
626
627 _PUBLIC_ void cli_credentials_invalidate_ccache(struct cli_credentials *cred, 
628                                        enum credentials_obtained obtained)
629 {
630         /* If the caller just changed the username/password etc, then
631          * any cached credentials are now invalid */
632         if (obtained >= cred->ccache_obtained) {
633                 if (cred->ccache_obtained > CRED_UNINITIALISED) {
634                         talloc_unlink(cred, cred->ccache);
635                         cred->ccache = NULL;
636                 }
637                 cred->ccache_obtained = CRED_UNINITIALISED;
638         }
639         /* Now that we know that the data is 'this specified', then
640          * don't allow something less 'known' to be returned as a
641          * ccache.  i.e, if the username is on the command line, we
642          * don't want to later guess to use a file-based ccache */
643         if (obtained > cred->ccache_threshold) {
644                 cred->ccache_threshold  = obtained;
645         }
646
647         cli_credentials_invalidate_client_gss_creds(cred, 
648                                                     obtained);
649 }
650
651 static int free_gssapi_creds(struct gssapi_creds_container *gcc)
652 {
653         OM_uint32 min_stat;
654         (void)gss_release_cred(&min_stat, &gcc->creds);
655         return 0;
656 }
657
658 _PUBLIC_ int cli_credentials_get_client_gss_creds(struct cli_credentials *cred, 
659                                                   struct tevent_context *event_ctx,
660                                                   struct loadparm_context *lp_ctx,
661                                                   struct gssapi_creds_container **_gcc,
662                                                   const char **error_string)
663 {
664         int ret = 0;
665         OM_uint32 maj_stat, min_stat;
666         struct gssapi_creds_container *gcc;
667         struct ccache_container *ccache;
668 #ifdef HAVE_GSS_KRB5_CRED_NO_CI_FLAGS_X
669         gss_buffer_desc empty_buffer = GSS_C_EMPTY_BUFFER;
670         gss_OID oid = discard_const(GSS_KRB5_CRED_NO_CI_FLAGS_X);
671 #endif
672         krb5_enctype *etypes = NULL;
673
674         if (cred->client_gss_creds_obtained >= cred->client_gss_creds_threshold && 
675             cred->client_gss_creds_obtained > CRED_UNINITIALISED) {
676                 bool expired = false;
677                 OM_uint32 lifetime = 0;
678                 gss_cred_usage_t usage = 0;
679                 maj_stat = gss_inquire_cred(&min_stat, cred->client_gss_creds->creds, 
680                                             NULL, &lifetime, &usage, NULL);
681                 if (maj_stat == GSS_S_CREDENTIALS_EXPIRED) {
682                         DEBUG(3, ("Credentials for %s expired, must refresh credentials cache\n", cli_credentials_get_principal(cred, cred)));
683                         expired = true;
684                 } else if (maj_stat == GSS_S_COMPLETE && lifetime < 300) {
685                         DEBUG(3, ("Credentials for %s will expire shortly (%u sec), must refresh credentials cache\n", cli_credentials_get_principal(cred, cred), lifetime));
686                         expired = true;
687                 } else if (maj_stat != GSS_S_COMPLETE) {
688                         *error_string = talloc_asprintf(cred, "inquiry of credential lifefime via GSSAPI gss_inquire_cred failed: %s\n",
689                                                         gssapi_error_string(cred, maj_stat, min_stat, NULL));
690                         return EINVAL;
691                 }
692                 if (expired) {
693                         cli_credentials_unconditionally_invalidate_client_gss_creds(cred);
694                 } else {
695                         DEBUG(5, ("GSSAPI credentials for %s will expire in %u secs\n", 
696                                   cli_credentials_get_principal(cred, cred), (unsigned int)lifetime));
697                 
698                         *_gcc = cred->client_gss_creds;
699                         return 0;
700                 }
701         }
702
703         ret = cli_credentials_get_ccache(cred, event_ctx, lp_ctx,
704                                          &ccache, error_string);
705         if (ret) {
706                 if (cli_credentials_get_kerberos_state(cred) == CRED_MUST_USE_KERBEROS) {
707                         DEBUG(1, ("Failed to get kerberos credentials (kerberos required): %s\n", *error_string));
708                 } else {
709                         DEBUG(4, ("Failed to get kerberos credentials: %s\n", *error_string));
710                 }
711                 return ret;
712         }
713
714         gcc = talloc(cred, struct gssapi_creds_container);
715         if (!gcc) {
716                 (*error_string) = error_message(ENOMEM);
717                 return ENOMEM;
718         }
719
720         maj_stat = smb_gss_krb5_import_cred(&min_stat, ccache->smb_krb5_context->krb5_context,
721                                             ccache->ccache, NULL, NULL,
722                                             &gcc->creds);
723         if ((maj_stat == GSS_S_FAILURE) &&
724             (min_stat == (OM_uint32)KRB5_CC_END ||
725              min_stat == (OM_uint32)KRB5_CC_NOTFOUND ||
726              min_stat == (OM_uint32)KRB5_FCC_NOFILE))
727         {
728                 /* This CCACHE is no good.  Ensure we don't use it again */
729                 cli_credentials_unconditionally_invalidate_ccache(cred);
730
731                 /* Now try again to get a ccache */
732                 ret = cli_credentials_get_ccache(cred, event_ctx, lp_ctx,
733                                                  &ccache, error_string);
734                 if (ret) {
735                         DEBUG(1, ("Failed to re-get CCACHE for GSSAPI client: %s\n", error_message(ret)));
736                         return ret;
737                 }
738
739                 maj_stat = smb_gss_krb5_import_cred(&min_stat, ccache->smb_krb5_context->krb5_context,
740                                                     ccache->ccache, NULL, NULL,
741                                                     &gcc->creds);
742
743         }
744
745         if (maj_stat) {
746                 talloc_free(gcc);
747                 if (min_stat) {
748                         ret = min_stat;
749                 } else {
750                         ret = EINVAL;
751                 }
752                 (*error_string) = talloc_asprintf(cred, "smb_gss_krb5_import_cred failed: %s", error_message(ret));
753                 return ret;
754         }
755
756
757         /*
758          * transfer the enctypes from the smb_krb5_context to the gssapi layer
759          *
760          * We use 'our' smb_krb5_context to do the AS-REQ and it is possible
761          * to configure the enctypes via the krb5.conf.
762          *
763          * And the gss_init_sec_context() creates it's own krb5_context and
764          * the TGS-REQ had all enctypes in it and only the ones configured
765          * and used for the AS-REQ, so it wasn't possible to disable the usage
766          * of AES keys.
767          */
768         min_stat = smb_krb5_get_allowed_etypes(ccache->smb_krb5_context->krb5_context,
769                                                &etypes);
770         if (min_stat == 0) {
771                 OM_uint32 num_ktypes;
772
773                 for (num_ktypes = 0; etypes[num_ktypes]; num_ktypes++);
774
775                 maj_stat = gss_krb5_set_allowable_enctypes(&min_stat, gcc->creds,
776                                                            num_ktypes,
777                                                            (int32_t *) etypes);
778                 SAFE_FREE(etypes);
779                 if (maj_stat) {
780                         talloc_free(gcc);
781                         if (min_stat) {
782                                 ret = min_stat;
783                         } else {
784                                 ret = EINVAL;
785                         }
786                         (*error_string) = talloc_asprintf(cred, "gss_krb5_set_allowable_enctypes failed: %s", error_message(ret));
787                         return ret;
788                 }
789         }
790
791 #ifdef HAVE_GSS_KRB5_CRED_NO_CI_FLAGS_X
792         /*
793          * Don't force GSS_C_CONF_FLAG and GSS_C_INTEG_FLAG.
794          *
795          * This allows us to disable SIGN and SEAL on a TLS connection with
796          * GSS-SPNENO. For example ldaps:// connections.
797          *
798          * https://groups.yahoo.com/neo/groups/cat-ietf/conversations/topics/575
799          * http://krbdev.mit.edu/rt/Ticket/Display.html?id=6938
800          */
801         maj_stat = gss_set_cred_option(&min_stat, &gcc->creds,
802                                        oid,
803                                        &empty_buffer);
804         if (maj_stat) {
805                 talloc_free(gcc);
806                 if (min_stat) {
807                         ret = min_stat;
808                 } else {
809                         ret = EINVAL;
810                 }
811                 (*error_string) = talloc_asprintf(cred, "gss_set_cred_option failed: %s", error_message(ret));
812                 return ret;
813         }
814 #endif
815         cred->client_gss_creds_obtained = cred->ccache_obtained;
816         talloc_set_destructor(gcc, free_gssapi_creds);
817         cred->client_gss_creds = gcc;
818         *_gcc = gcc;
819         return 0;
820 }
821
822 /**
823    Set a gssapi cred_id_t into the credentials system. (Client case)
824
825    This grabs the credentials both 'intact' and getting the krb5
826    ccache out of it.  This routine can be generalised in future for
827    the case where we deal with GSSAPI mechs other than krb5.  
828
829    On sucess, the caller must not free gssapi_cred, as it now belongs
830    to the credentials system.
831 */
832
833  int cli_credentials_set_client_gss_creds(struct cli_credentials *cred, 
834                                           struct loadparm_context *lp_ctx,
835                                           gss_cred_id_t gssapi_cred,
836                                           enum credentials_obtained obtained,
837                                           const char **error_string)
838 {
839         int ret;
840         OM_uint32 maj_stat, min_stat;
841         struct ccache_container *ccc = NULL;
842         struct gssapi_creds_container *gcc = NULL;
843         if (cred->client_gss_creds_obtained > obtained) {
844                 return 0;
845         }
846
847         gcc = talloc(cred, struct gssapi_creds_container);
848         if (!gcc) {
849                 (*error_string) = error_message(ENOMEM);
850                 return ENOMEM;
851         }
852
853         ret = cli_credentials_new_ccache(cred, lp_ctx, NULL, &ccc, error_string);
854         if (ret != 0) {
855                 return ret;
856         }
857
858         maj_stat = smb_gss_krb5_copy_ccache(&min_stat,
859                                             gssapi_cred,
860                                             ccc);
861         if (maj_stat) {
862                 if (min_stat) {
863                         ret = min_stat;
864                 } else {
865                         ret = EINVAL;
866                 }
867                 if (ret) {
868                         (*error_string) = error_message(ENOMEM);
869                 }
870         }
871
872         if (ret == 0) {
873                 ret = cli_credentials_set_from_ccache(cred, ccc, obtained, error_string);
874         }
875         cred->ccache = ccc;
876         cred->ccache_obtained = obtained;
877         if (ret == 0) {
878                 gcc->creds = gssapi_cred;
879                 talloc_set_destructor(gcc, free_gssapi_creds);
880                 
881                 /* set the clinet_gss_creds_obtained here, as it just 
882                    got set to UNINITIALISED by the calls above */
883                 cred->client_gss_creds_obtained = obtained;
884                 cred->client_gss_creds = gcc;
885         }
886         return ret;
887 }
888
889 static int cli_credentials_shallow_ccache(struct cli_credentials *cred)
890 {
891         krb5_error_code ret;
892         const struct ccache_container *old_ccc = NULL;
893         struct ccache_container *ccc = NULL;
894         char *ccache_name = NULL;
895
896         old_ccc = cred->ccache;
897         if (old_ccc == NULL) {
898                 return 0;
899         }
900
901         ccc = talloc(cred, struct ccache_container);
902         if (ccc == NULL) {
903                 return ENOMEM;
904         }
905         *ccc = *old_ccc;
906         ccc->ccache = NULL;
907
908         ccache_name = talloc_asprintf(ccc, "MEMORY:%p", ccc);
909
910         ret = krb5_cc_resolve(ccc->smb_krb5_context->krb5_context,
911                               ccache_name, &ccc->ccache);
912         if (ret != 0) {
913                 TALLOC_FREE(ccc);
914                 return ret;
915         }
916
917         talloc_set_destructor(ccc, free_mccache);
918
919         TALLOC_FREE(ccache_name);
920
921         ret = smb_krb5_cc_copy_creds(ccc->smb_krb5_context->krb5_context,
922                                      old_ccc->ccache, ccc->ccache);
923         if (ret != 0) {
924                 TALLOC_FREE(ccc);
925                 return ret;
926         }
927
928         cred->ccache = ccc;
929         cred->client_gss_creds = NULL;
930         cred->client_gss_creds_obtained = CRED_UNINITIALISED;
931         return ret;
932 }
933
934 _PUBLIC_ struct cli_credentials *cli_credentials_shallow_copy(TALLOC_CTX *mem_ctx,
935                                                 struct cli_credentials *src)
936 {
937         struct cli_credentials *dst;
938         int ret;
939
940         dst = talloc(mem_ctx, struct cli_credentials);
941         if (dst == NULL) {
942                 return NULL;
943         }
944
945         *dst = *src;
946
947         ret = cli_credentials_shallow_ccache(dst);
948         if (ret != 0) {
949                 TALLOC_FREE(dst);
950                 return NULL;
951         }
952
953         return dst;
954 }
955
956 /* Get the keytab (actually, a container containing the krb5_keytab)
957  * attached to this context.  If this hasn't been done or set before,
958  * it will be generated from the password.
959  */
960 _PUBLIC_ int cli_credentials_get_keytab(struct cli_credentials *cred, 
961                                         struct loadparm_context *lp_ctx,
962                                         struct keytab_container **_ktc)
963 {
964         krb5_error_code ret;
965         struct keytab_container *ktc;
966         struct smb_krb5_context *smb_krb5_context;
967         const char *keytab_name;
968         krb5_keytab keytab;
969         TALLOC_CTX *mem_ctx;
970         const char *username = cli_credentials_get_username(cred);
971         const char *upn = NULL;
972         const char *realm = cli_credentials_get_realm(cred);
973         char *salt_principal = NULL;
974         bool is_computer = false;
975
976         if (cred->keytab_obtained >= (MAX(cred->principal_obtained, 
977                                           cred->username_obtained))) {
978                 *_ktc = cred->keytab;
979                 return 0;
980         }
981
982         if (cli_credentials_is_anonymous(cred)) {
983                 return EINVAL;
984         }
985
986         ret = cli_credentials_get_krb5_context(cred, lp_ctx,
987                                                &smb_krb5_context);
988         if (ret) {
989                 return ret;
990         }
991
992         mem_ctx = talloc_new(cred);
993         if (!mem_ctx) {
994                 return ENOMEM;
995         }
996
997         switch (cred->secure_channel_type) {
998         case SEC_CHAN_WKSTA:
999         case SEC_CHAN_BDC:
1000         case SEC_CHAN_RODC:
1001                 is_computer = true;
1002                 break;
1003         default:
1004                 upn = cli_credentials_get_principal(cred, mem_ctx);
1005                 if (upn == NULL) {
1006                         TALLOC_FREE(mem_ctx);
1007                         return ENOMEM;
1008                 }
1009                 break;
1010         }
1011
1012         ret = smb_krb5_salt_principal(realm,
1013                                       username, /* sAMAccountName */
1014                                       upn, /* userPrincipalName */
1015                                       is_computer,
1016                                       mem_ctx,
1017                                       &salt_principal);
1018         if (ret) {
1019                 talloc_free(mem_ctx);
1020                 return ret;
1021         }
1022
1023         ret = smb_krb5_create_memory_keytab(mem_ctx,
1024                                             smb_krb5_context->krb5_context,
1025                                             cli_credentials_get_password(cred),
1026                                             username,
1027                                             realm,
1028                                             salt_principal,
1029                                             cli_credentials_get_kvno(cred),
1030                                             &keytab,
1031                                             &keytab_name);
1032         if (ret) {
1033                 talloc_free(mem_ctx);
1034                 return ret;
1035         }
1036
1037         ret = smb_krb5_get_keytab_container(mem_ctx, smb_krb5_context,
1038                                             keytab, keytab_name, &ktc);
1039         if (ret) {
1040                 talloc_free(mem_ctx);
1041                 return ret;
1042         }
1043
1044         cred->keytab_obtained = (MAX(cred->principal_obtained, 
1045                                      cred->username_obtained));
1046
1047         /* We make this keytab up based on a password.  Therefore
1048          * match-by-key is acceptable, we can't match on the wrong
1049          * principal */
1050         ktc->password_based = true;
1051
1052         talloc_steal(cred, ktc);
1053         cred->keytab = ktc;
1054         *_ktc = cred->keytab;
1055         talloc_free(mem_ctx);
1056         return ret;
1057 }
1058
1059 /* Given the name of a keytab (presumably in the format
1060  * FILE:/etc/krb5.keytab), open it and attach it */
1061
1062 _PUBLIC_ int cli_credentials_set_keytab_name(struct cli_credentials *cred, 
1063                                              struct loadparm_context *lp_ctx,
1064                                              const char *keytab_name,
1065                                              enum credentials_obtained obtained)
1066 {
1067         krb5_error_code ret;
1068         struct keytab_container *ktc;
1069         struct smb_krb5_context *smb_krb5_context;
1070         TALLOC_CTX *mem_ctx;
1071
1072         if (cred->keytab_obtained >= obtained) {
1073                 return 0;
1074         }
1075
1076         ret = cli_credentials_get_krb5_context(cred, lp_ctx, &smb_krb5_context);
1077         if (ret) {
1078                 return ret;
1079         }
1080
1081         mem_ctx = talloc_new(cred);
1082         if (!mem_ctx) {
1083                 return ENOMEM;
1084         }
1085
1086         ret = smb_krb5_get_keytab_container(mem_ctx, smb_krb5_context,
1087                                             NULL, keytab_name, &ktc);
1088         if (ret) {
1089                 return ret;
1090         }
1091
1092         cred->keytab_obtained = obtained;
1093
1094         talloc_steal(cred, ktc);
1095         cred->keytab = ktc;
1096         talloc_free(mem_ctx);
1097
1098         return ret;
1099 }
1100
1101 /* Get server gss credentials (in gsskrb5, this means the keytab) */
1102
1103 _PUBLIC_ int cli_credentials_get_server_gss_creds(struct cli_credentials *cred, 
1104                                                   struct loadparm_context *lp_ctx,
1105                                                   struct gssapi_creds_container **_gcc)
1106 {
1107         int ret = 0;
1108         OM_uint32 maj_stat, min_stat;
1109         struct gssapi_creds_container *gcc;
1110         struct keytab_container *ktc;
1111         struct smb_krb5_context *smb_krb5_context;
1112         TALLOC_CTX *mem_ctx;
1113         krb5_principal princ;
1114         const char *error_string;
1115         enum credentials_obtained obtained;
1116
1117         mem_ctx = talloc_new(cred);
1118         if (!mem_ctx) {
1119                 return ENOMEM;
1120         }
1121
1122         ret = cli_credentials_get_krb5_context(cred, lp_ctx, &smb_krb5_context);
1123         if (ret) {
1124                 return ret;
1125         }
1126
1127         ret = principal_from_credentials(mem_ctx, cred, smb_krb5_context, &princ, &obtained, &error_string);
1128         if (ret) {
1129                 DEBUG(1,("cli_credentials_get_server_gss_creds: making krb5 principal failed (%s)\n",
1130                          error_string));
1131                 talloc_free(mem_ctx);
1132                 return ret;
1133         }
1134
1135         if (cred->server_gss_creds_obtained >= (MAX(cred->keytab_obtained, obtained))) {
1136                 talloc_free(mem_ctx);
1137                 *_gcc = cred->server_gss_creds;
1138                 return 0;
1139         }
1140
1141         ret = cli_credentials_get_keytab(cred, lp_ctx, &ktc);
1142         if (ret) {
1143                 DEBUG(1, ("Failed to get keytab for GSSAPI server: %s\n", error_message(ret)));
1144                 return ret;
1145         }
1146
1147         gcc = talloc(cred, struct gssapi_creds_container);
1148         if (!gcc) {
1149                 talloc_free(mem_ctx);
1150                 return ENOMEM;
1151         }
1152
1153         if (ktc->password_based || obtained < CRED_SPECIFIED) {
1154                 /*
1155                  * This creates a GSSAPI cred_id_t for match-by-key with only
1156                  * the keytab set
1157                  */
1158                 princ = NULL;
1159         }
1160         maj_stat = smb_gss_krb5_import_cred(&min_stat,
1161                                             smb_krb5_context->krb5_context,
1162                                             NULL, princ,
1163                                             ktc->keytab,
1164                                             &gcc->creds);
1165         if (maj_stat) {
1166                 if (min_stat) {
1167                         ret = min_stat;
1168                 } else {
1169                         ret = EINVAL;
1170                 }
1171         }
1172         if (ret == 0) {
1173                 cred->server_gss_creds_obtained = cred->keytab_obtained;
1174                 talloc_set_destructor(gcc, free_gssapi_creds);
1175                 cred->server_gss_creds = gcc;
1176                 *_gcc = gcc;
1177         }
1178         talloc_free(mem_ctx);
1179         return ret;
1180 }
1181
1182 /** 
1183  * Set Kerberos KVNO
1184  */
1185
1186 _PUBLIC_ void cli_credentials_set_kvno(struct cli_credentials *cred,
1187                               int kvno)
1188 {
1189         cred->kvno = kvno;
1190 }
1191
1192 /**
1193  * Return Kerberos KVNO
1194  */
1195
1196 _PUBLIC_ int cli_credentials_get_kvno(struct cli_credentials *cred)
1197 {
1198         return cred->kvno;
1199 }
1200
1201
1202 const char *cli_credentials_get_salt_principal(struct cli_credentials *cred) 
1203 {
1204         return cred->salt_principal;
1205 }
1206
1207 _PUBLIC_ void cli_credentials_set_salt_principal(struct cli_credentials *cred, const char *principal) 
1208 {
1209         talloc_free(cred->salt_principal);
1210         cred->salt_principal = talloc_strdup(cred, principal);
1211 }
1212
1213 /* The 'impersonate_principal' is used to allow one Kerberos principal
1214  * (and it's associated keytab etc) to impersonate another.  The
1215  * ability to do this is controlled by the KDC, but it is generally
1216  * permitted to impersonate anyone to yourself.  This allows any
1217  * member of the domain to get the groups of a user.  This is also
1218  * known as S4U2Self */
1219
1220 _PUBLIC_ const char *cli_credentials_get_impersonate_principal(struct cli_credentials *cred)
1221 {
1222         return cred->impersonate_principal;
1223 }
1224
1225 /*
1226  * The 'self_service' is the service principal that
1227  * represents the same object (by its objectSid)
1228  * as the client principal (typically our machine account).
1229  * When trying to impersonate 'impersonate_principal' with
1230  * S4U2Self.
1231  */
1232 _PUBLIC_ const char *cli_credentials_get_self_service(struct cli_credentials *cred)
1233 {
1234         return cred->self_service;
1235 }
1236
1237 _PUBLIC_ void cli_credentials_set_impersonate_principal(struct cli_credentials *cred,
1238                                                         const char *principal,
1239                                                         const char *self_service)
1240 {
1241         talloc_free(cred->impersonate_principal);
1242         cred->impersonate_principal = talloc_strdup(cred, principal);
1243         talloc_free(cred->self_service);
1244         cred->self_service = talloc_strdup(cred, self_service);
1245         cli_credentials_set_kerberos_state(cred, CRED_MUST_USE_KERBEROS);
1246 }
1247
1248 /*
1249  * when impersonating for S4U2proxy we need to set the target principal.
1250  * Similarly, we may only be authorized to do general impersonation to
1251  * some particular services.
1252  *
1253  * Likewise, password changes typically require a ticket to kpasswd/realm directly, not via a TGT
1254  *
1255  * NULL means that tickets will be obtained for the krbtgt service.
1256 */
1257
1258 const char *cli_credentials_get_target_service(struct cli_credentials *cred)
1259 {
1260         return cred->target_service;
1261 }
1262
1263 _PUBLIC_ void cli_credentials_set_target_service(struct cli_credentials *cred, const char *target_service)
1264 {
1265         talloc_free(cred->target_service);
1266         cred->target_service = talloc_strdup(cred, target_service);
1267 }
1268