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