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