credentials: Create a smb_gss_krb5_copy_ccache() function
[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         if (!ok) {
264                 krb5_free_principal(ccache->smb_krb5_context->krb5_context, princ);
265                 return ENOMEM;
266         }
267         free(name);
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 = gss_krb5_import_cred(&min_stat, ccache->ccache, NULL, NULL, 
721                                         &gcc->creds);
722         if ((maj_stat == GSS_S_FAILURE) &&
723             (min_stat == (OM_uint32)KRB5_CC_END ||
724              min_stat == (OM_uint32)KRB5_CC_NOTFOUND ||
725              min_stat == (OM_uint32)KRB5_FCC_NOFILE))
726         {
727                 /* This CCACHE is no good.  Ensure we don't use it again */
728                 cli_credentials_unconditionally_invalidate_ccache(cred);
729
730                 /* Now try again to get a ccache */
731                 ret = cli_credentials_get_ccache(cred, event_ctx, lp_ctx,
732                                                  &ccache, error_string);
733                 if (ret) {
734                         DEBUG(1, ("Failed to re-get CCACHE for GSSAPI client: %s\n", error_message(ret)));
735                         return ret;
736                 }
737
738                 maj_stat = gss_krb5_import_cred(&min_stat, ccache->ccache, NULL, NULL,
739                                                 &gcc->creds);
740
741         }
742
743         if (maj_stat) {
744                 talloc_free(gcc);
745                 if (min_stat) {
746                         ret = min_stat;
747                 } else {
748                         ret = EINVAL;
749                 }
750                 (*error_string) = talloc_asprintf(cred, "gss_krb5_import_cred failed: %s", error_message(ret));
751                 return ret;
752         }
753
754
755         /*
756          * transfer the enctypes from the smb_krb5_context to the gssapi layer
757          *
758          * We use 'our' smb_krb5_context to do the AS-REQ and it is possible
759          * to configure the enctypes via the krb5.conf.
760          *
761          * And the gss_init_sec_context() creates it's own krb5_context and
762          * the TGS-REQ had all enctypes in it and only the ones configured
763          * and used for the AS-REQ, so it wasn't possible to disable the usage
764          * of AES keys.
765          */
766         min_stat = smb_krb5_get_allowed_etypes(ccache->smb_krb5_context->krb5_context,
767                                                &etypes);
768         if (min_stat == 0) {
769                 OM_uint32 num_ktypes;
770
771                 for (num_ktypes = 0; etypes[num_ktypes]; num_ktypes++);
772
773                 maj_stat = gss_krb5_set_allowable_enctypes(&min_stat, gcc->creds,
774                                                            num_ktypes,
775                                                            (int32_t *) etypes);
776                 SAFE_FREE(etypes);
777                 if (maj_stat) {
778                         talloc_free(gcc);
779                         if (min_stat) {
780                                 ret = min_stat;
781                         } else {
782                                 ret = EINVAL;
783                         }
784                         (*error_string) = talloc_asprintf(cred, "gss_krb5_set_allowable_enctypes failed: %s", error_message(ret));
785                         return ret;
786                 }
787         }
788
789 #ifdef HAVE_GSS_KRB5_CRED_NO_CI_FLAGS_X
790         /*
791          * Don't force GSS_C_CONF_FLAG and GSS_C_INTEG_FLAG.
792          *
793          * This allows us to disable SIGN and SEAL on a TLS connection with
794          * GSS-SPNENO. For example ldaps:// connections.
795          *
796          * https://groups.yahoo.com/neo/groups/cat-ietf/conversations/topics/575
797          * http://krbdev.mit.edu/rt/Ticket/Display.html?id=6938
798          */
799         maj_stat = gss_set_cred_option(&min_stat, &gcc->creds,
800                                        oid,
801                                        &empty_buffer);
802         if (maj_stat) {
803                 talloc_free(gcc);
804                 if (min_stat) {
805                         ret = min_stat;
806                 } else {
807                         ret = EINVAL;
808                 }
809                 (*error_string) = talloc_asprintf(cred, "gss_set_cred_option failed: %s", error_message(ret));
810                 return ret;
811         }
812 #endif
813         cred->client_gss_creds_obtained = cred->ccache_obtained;
814         talloc_set_destructor(gcc, free_gssapi_creds);
815         cred->client_gss_creds = gcc;
816         *_gcc = gcc;
817         return 0;
818 }
819
820 /**
821    Set a gssapi cred_id_t into the credentials system. (Client case)
822
823    This grabs the credentials both 'intact' and getting the krb5
824    ccache out of it.  This routine can be generalised in future for
825    the case where we deal with GSSAPI mechs other than krb5.  
826
827    On sucess, the caller must not free gssapi_cred, as it now belongs
828    to the credentials system.
829 */
830
831  int cli_credentials_set_client_gss_creds(struct cli_credentials *cred, 
832                                           struct loadparm_context *lp_ctx,
833                                           gss_cred_id_t gssapi_cred,
834                                           enum credentials_obtained obtained,
835                                           const char **error_string)
836 {
837         int ret;
838         OM_uint32 maj_stat, min_stat;
839         struct ccache_container *ccc = NULL;
840         struct gssapi_creds_container *gcc = NULL;
841         if (cred->client_gss_creds_obtained > obtained) {
842                 return 0;
843         }
844
845         gcc = talloc(cred, struct gssapi_creds_container);
846         if (!gcc) {
847                 (*error_string) = error_message(ENOMEM);
848                 return ENOMEM;
849         }
850
851         ret = cli_credentials_new_ccache(cred, lp_ctx, NULL, &ccc, error_string);
852         if (ret != 0) {
853                 return ret;
854         }
855
856         maj_stat = smb_gss_krb5_copy_ccache(&min_stat,
857                                             gssapi_cred,
858                                             ccc);
859         if (maj_stat) {
860                 if (min_stat) {
861                         ret = min_stat;
862                 } else {
863                         ret = EINVAL;
864                 }
865                 if (ret) {
866                         (*error_string) = error_message(ENOMEM);
867                 }
868         }
869
870         if (ret == 0) {
871                 ret = cli_credentials_set_from_ccache(cred, ccc, obtained, error_string);
872         }
873         cred->ccache = ccc;
874         cred->ccache_obtained = obtained;
875         if (ret == 0) {
876                 gcc->creds = gssapi_cred;
877                 talloc_set_destructor(gcc, free_gssapi_creds);
878                 
879                 /* set the clinet_gss_creds_obtained here, as it just 
880                    got set to UNINITIALISED by the calls above */
881                 cred->client_gss_creds_obtained = obtained;
882                 cred->client_gss_creds = gcc;
883         }
884         return ret;
885 }
886
887 static int cli_credentials_shallow_ccache(struct cli_credentials *cred)
888 {
889         krb5_error_code ret;
890         const struct ccache_container *old_ccc = NULL;
891         struct ccache_container *ccc = NULL;
892         char *ccache_name = NULL;
893
894         old_ccc = cred->ccache;
895         if (old_ccc == NULL) {
896                 return 0;
897         }
898
899         ccc = talloc(cred, struct ccache_container);
900         if (ccc == NULL) {
901                 return ENOMEM;
902         }
903         *ccc = *old_ccc;
904         ccc->ccache = NULL;
905
906         ccache_name = talloc_asprintf(ccc, "MEMORY:%p", ccc);
907
908         ret = krb5_cc_resolve(ccc->smb_krb5_context->krb5_context,
909                               ccache_name, &ccc->ccache);
910         if (ret != 0) {
911                 TALLOC_FREE(ccc);
912                 return ret;
913         }
914
915         talloc_set_destructor(ccc, free_mccache);
916
917         TALLOC_FREE(ccache_name);
918
919         ret = smb_krb5_cc_copy_creds(ccc->smb_krb5_context->krb5_context,
920                                      old_ccc->ccache, ccc->ccache);
921         if (ret != 0) {
922                 TALLOC_FREE(ccc);
923                 return ret;
924         }
925
926         cred->ccache = ccc;
927         cred->client_gss_creds = NULL;
928         cred->client_gss_creds_obtained = CRED_UNINITIALISED;
929         return ret;
930 }
931
932 _PUBLIC_ struct cli_credentials *cli_credentials_shallow_copy(TALLOC_CTX *mem_ctx,
933                                                 struct cli_credentials *src)
934 {
935         struct cli_credentials *dst;
936         int ret;
937
938         dst = talloc(mem_ctx, struct cli_credentials);
939         if (dst == NULL) {
940                 return NULL;
941         }
942
943         *dst = *src;
944
945         ret = cli_credentials_shallow_ccache(dst);
946         if (ret != 0) {
947                 TALLOC_FREE(dst);
948                 return NULL;
949         }
950
951         return dst;
952 }
953
954 static int smb_krb5_create_salt_principal(TALLOC_CTX *mem_ctx,
955                                           const char *samAccountName,
956                                           const char *realm,
957                                           const char **salt_principal,
958                                           const char **error_string)
959 {
960         char *machine_username;
961         bool is_machine_account = false;
962         char *upper_realm;
963         TALLOC_CTX *tmp_ctx;
964         int rc = -1;
965
966         if (samAccountName == NULL) {
967                 *error_string = "Cannot determine salt principal, no "
968                                 "saltPrincipal or samAccountName specified";
969                 return rc;
970         }
971
972         if (realm == NULL) {
973                 *error_string = "Cannot make principal without a realm";
974                 return rc;
975         }
976
977         tmp_ctx = talloc_new(mem_ctx);
978         if (tmp_ctx == NULL) {
979                 *error_string = "Cannot allocate talloc context";
980                 return rc;
981         }
982
983         upper_realm = strupper_talloc(tmp_ctx, realm);
984         if (upper_realm == NULL) {
985                 *error_string = "Cannot allocate to upper case realm";
986                 goto out;
987         }
988
989         machine_username = strlower_talloc(tmp_ctx, samAccountName);
990         if (!machine_username) {
991                 *error_string = "Cannot duplicate samAccountName";
992                 goto out;
993         }
994
995         if (machine_username[strlen(machine_username) - 1] == '$') {
996                 machine_username[strlen(machine_username) - 1] = '\0';
997                 is_machine_account = true;
998         }
999
1000         if (is_machine_account) {
1001                 char *lower_realm;
1002
1003                 lower_realm = strlower_talloc(tmp_ctx, realm);
1004                 if (lower_realm == NULL) {
1005                         *error_string = "Cannot allocate to lower case realm";
1006                         goto out;
1007                 }
1008
1009                 *salt_principal = talloc_asprintf(mem_ctx,
1010                                                   "host/%s.%s@%s",
1011                                                   machine_username,
1012                                                   lower_realm,
1013                                                   upper_realm);
1014         } else {
1015                 *salt_principal = talloc_asprintf(mem_ctx,
1016                                                   "%s@%s",
1017                                                   machine_username,
1018                                                   upper_realm);
1019         }
1020         if (*salt_principal == NULL) {
1021                 *error_string = "Cannot create salt principal";
1022                 goto out;
1023         }
1024
1025         rc = 0;
1026 out:
1027         talloc_free(tmp_ctx);
1028         return rc;
1029 }
1030
1031 /* Get the keytab (actually, a container containing the krb5_keytab)
1032  * attached to this context.  If this hasn't been done or set before,
1033  * it will be generated from the password.
1034  */
1035 _PUBLIC_ int cli_credentials_get_keytab(struct cli_credentials *cred, 
1036                                         struct loadparm_context *lp_ctx,
1037                                         struct keytab_container **_ktc)
1038 {
1039         krb5_error_code ret;
1040         struct keytab_container *ktc;
1041         struct smb_krb5_context *smb_krb5_context;
1042         const char *keytab_name;
1043         krb5_keytab keytab;
1044         TALLOC_CTX *mem_ctx;
1045         const char *username = cli_credentials_get_username(cred);
1046         const char *realm = cli_credentials_get_realm(cred);
1047         const char *error_string;
1048         const char *salt_principal;
1049
1050         if (cred->keytab_obtained >= (MAX(cred->principal_obtained, 
1051                                           cred->username_obtained))) {
1052                 *_ktc = cred->keytab;
1053                 return 0;
1054         }
1055
1056         if (cli_credentials_is_anonymous(cred)) {
1057                 return EINVAL;
1058         }
1059
1060         ret = cli_credentials_get_krb5_context(cred, lp_ctx,
1061                                                &smb_krb5_context);
1062         if (ret) {
1063                 return ret;
1064         }
1065
1066         mem_ctx = talloc_new(cred);
1067         if (!mem_ctx) {
1068                 return ENOMEM;
1069         }
1070
1071         /*
1072          * FIXME: Currently there is no better way than to create the correct
1073          * salt principal by checking if the username ends with a '$'. It would
1074          * be better if it is part of the credentials.
1075          */
1076         ret = smb_krb5_create_salt_principal(mem_ctx,
1077                                              username,
1078                                              realm,
1079                                              &salt_principal,
1080                                              &error_string);
1081         if (ret) {
1082                 talloc_free(mem_ctx);
1083                 return ret;
1084         }
1085
1086         ret = smb_krb5_create_memory_keytab(mem_ctx,
1087                                             smb_krb5_context->krb5_context,
1088                                             cli_credentials_get_password(cred),
1089                                             username,
1090                                             realm,
1091                                             salt_principal,
1092                                             cli_credentials_get_kvno(cred),
1093                                             &keytab,
1094                                             &keytab_name);
1095         if (ret) {
1096                 talloc_free(mem_ctx);
1097                 return ret;
1098         }
1099
1100         ret = smb_krb5_get_keytab_container(mem_ctx, smb_krb5_context,
1101                                             keytab, keytab_name, &ktc);
1102         if (ret) {
1103                 talloc_free(mem_ctx);
1104                 return ret;
1105         }
1106
1107         cred->keytab_obtained = (MAX(cred->principal_obtained, 
1108                                      cred->username_obtained));
1109
1110         /* We make this keytab up based on a password.  Therefore
1111          * match-by-key is acceptable, we can't match on the wrong
1112          * principal */
1113         ktc->password_based = true;
1114
1115         talloc_steal(cred, ktc);
1116         cred->keytab = ktc;
1117         *_ktc = cred->keytab;
1118         talloc_free(mem_ctx);
1119         return ret;
1120 }
1121
1122 /* Given the name of a keytab (presumably in the format
1123  * FILE:/etc/krb5.keytab), open it and attach it */
1124
1125 _PUBLIC_ int cli_credentials_set_keytab_name(struct cli_credentials *cred, 
1126                                              struct loadparm_context *lp_ctx,
1127                                              const char *keytab_name,
1128                                              enum credentials_obtained obtained)
1129 {
1130         krb5_error_code ret;
1131         struct keytab_container *ktc;
1132         struct smb_krb5_context *smb_krb5_context;
1133         TALLOC_CTX *mem_ctx;
1134
1135         if (cred->keytab_obtained >= obtained) {
1136                 return 0;
1137         }
1138
1139         ret = cli_credentials_get_krb5_context(cred, lp_ctx, &smb_krb5_context);
1140         if (ret) {
1141                 return ret;
1142         }
1143
1144         mem_ctx = talloc_new(cred);
1145         if (!mem_ctx) {
1146                 return ENOMEM;
1147         }
1148
1149         ret = smb_krb5_get_keytab_container(mem_ctx, smb_krb5_context,
1150                                             NULL, keytab_name, &ktc);
1151         if (ret) {
1152                 return ret;
1153         }
1154
1155         cred->keytab_obtained = obtained;
1156
1157         talloc_steal(cred, ktc);
1158         cred->keytab = ktc;
1159         talloc_free(mem_ctx);
1160
1161         return ret;
1162 }
1163
1164 /* Get server gss credentials (in gsskrb5, this means the keytab) */
1165
1166 _PUBLIC_ int cli_credentials_get_server_gss_creds(struct cli_credentials *cred, 
1167                                                   struct loadparm_context *lp_ctx,
1168                                                   struct gssapi_creds_container **_gcc)
1169 {
1170         int ret = 0;
1171         OM_uint32 maj_stat, min_stat;
1172         struct gssapi_creds_container *gcc;
1173         struct keytab_container *ktc;
1174         struct smb_krb5_context *smb_krb5_context;
1175         TALLOC_CTX *mem_ctx;
1176         krb5_principal princ;
1177         const char *error_string;
1178         enum credentials_obtained obtained;
1179
1180         mem_ctx = talloc_new(cred);
1181         if (!mem_ctx) {
1182                 return ENOMEM;
1183         }
1184
1185         ret = cli_credentials_get_krb5_context(cred, lp_ctx, &smb_krb5_context);
1186         if (ret) {
1187                 return ret;
1188         }
1189
1190         ret = principal_from_credentials(mem_ctx, cred, smb_krb5_context, &princ, &obtained, &error_string);
1191         if (ret) {
1192                 DEBUG(1,("cli_credentials_get_server_gss_creds: making krb5 principal failed (%s)\n",
1193                          error_string));
1194                 talloc_free(mem_ctx);
1195                 return ret;
1196         }
1197
1198         if (cred->server_gss_creds_obtained >= (MAX(cred->keytab_obtained, obtained))) {
1199                 talloc_free(mem_ctx);
1200                 *_gcc = cred->server_gss_creds;
1201                 return 0;
1202         }
1203
1204         ret = cli_credentials_get_keytab(cred, lp_ctx, &ktc);
1205         if (ret) {
1206                 DEBUG(1, ("Failed to get keytab for GSSAPI server: %s\n", error_message(ret)));
1207                 return ret;
1208         }
1209
1210         gcc = talloc(cred, struct gssapi_creds_container);
1211         if (!gcc) {
1212                 talloc_free(mem_ctx);
1213                 return ENOMEM;
1214         }
1215
1216         if (ktc->password_based || obtained < CRED_SPECIFIED) {
1217                 /* This creates a GSSAPI cred_id_t for match-by-key with only the keytab set */
1218                 maj_stat = gss_krb5_import_cred(&min_stat, NULL, NULL, ktc->keytab,
1219                                                 &gcc->creds);
1220         } else {
1221                 /* This creates a GSSAPI cred_id_t with the principal and keytab set, matching by name */
1222                 maj_stat = gss_krb5_import_cred(&min_stat, NULL, princ, ktc->keytab,
1223                                                 &gcc->creds);
1224         }
1225         if (maj_stat) {
1226                 if (min_stat) {
1227                         ret = min_stat;
1228                 } else {
1229                         ret = EINVAL;
1230                 }
1231         }
1232         if (ret == 0) {
1233                 cred->server_gss_creds_obtained = cred->keytab_obtained;
1234                 talloc_set_destructor(gcc, free_gssapi_creds);
1235                 cred->server_gss_creds = gcc;
1236                 *_gcc = gcc;
1237         }
1238         talloc_free(mem_ctx);
1239         return ret;
1240 }
1241
1242 /** 
1243  * Set Kerberos KVNO
1244  */
1245
1246 _PUBLIC_ void cli_credentials_set_kvno(struct cli_credentials *cred,
1247                               int kvno)
1248 {
1249         cred->kvno = kvno;
1250 }
1251
1252 /**
1253  * Return Kerberos KVNO
1254  */
1255
1256 _PUBLIC_ int cli_credentials_get_kvno(struct cli_credentials *cred)
1257 {
1258         return cred->kvno;
1259 }
1260
1261
1262 const char *cli_credentials_get_salt_principal(struct cli_credentials *cred) 
1263 {
1264         return cred->salt_principal;
1265 }
1266
1267 _PUBLIC_ void cli_credentials_set_salt_principal(struct cli_credentials *cred, const char *principal) 
1268 {
1269         talloc_free(cred->salt_principal);
1270         cred->salt_principal = talloc_strdup(cred, principal);
1271 }
1272
1273 /* The 'impersonate_principal' is used to allow one Kerberos principal
1274  * (and it's associated keytab etc) to impersonate another.  The
1275  * ability to do this is controlled by the KDC, but it is generally
1276  * permitted to impersonate anyone to yourself.  This allows any
1277  * member of the domain to get the groups of a user.  This is also
1278  * known as S4U2Self */
1279
1280 _PUBLIC_ const char *cli_credentials_get_impersonate_principal(struct cli_credentials *cred)
1281 {
1282         return cred->impersonate_principal;
1283 }
1284
1285 /*
1286  * The 'self_service' is the service principal that
1287  * represents the same object (by its objectSid)
1288  * as the client principal (typically our machine account).
1289  * When trying to impersonate 'impersonate_principal' with
1290  * S4U2Self.
1291  */
1292 _PUBLIC_ const char *cli_credentials_get_self_service(struct cli_credentials *cred)
1293 {
1294         return cred->self_service;
1295 }
1296
1297 _PUBLIC_ void cli_credentials_set_impersonate_principal(struct cli_credentials *cred,
1298                                                         const char *principal,
1299                                                         const char *self_service)
1300 {
1301         talloc_free(cred->impersonate_principal);
1302         cred->impersonate_principal = talloc_strdup(cred, principal);
1303         talloc_free(cred->self_service);
1304         cred->self_service = talloc_strdup(cred, self_service);
1305         cli_credentials_set_kerberos_state(cred, CRED_MUST_USE_KERBEROS);
1306 }
1307
1308 /*
1309  * when impersonating for S4U2proxy we need to set the target principal.
1310  * Similarly, we may only be authorized to do general impersonation to
1311  * some particular services.
1312  *
1313  * Likewise, password changes typically require a ticket to kpasswd/realm directly, not via a TGT
1314  *
1315  * NULL means that tickets will be obtained for the krbtgt service.
1316 */
1317
1318 const char *cli_credentials_get_target_service(struct cli_credentials *cred)
1319 {
1320         return cred->target_service;
1321 }
1322
1323 _PUBLIC_ void cli_credentials_set_target_service(struct cli_credentials *cred, const char *target_service)
1324 {
1325         talloc_free(cred->target_service);
1326         cred->target_service = talloc_strdup(cred, target_service);
1327 }
1328