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