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