mit: make it possible to build with MIT kerberos and --picky-developer
[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 _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, 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                 krb5_free_cred_contents(ccc->smb_krb5_context->krb5_context, &creds);
261                 return false;
262         }
263
264         ret = krb5_cc_remove_cred(ccc->smb_krb5_context->krb5_context, ccc->ccache, KRB5_TC_MATCH_SRV_NAMEONLY, &creds);
265         krb5_free_cred_contents(ccc->smb_krb5_context->krb5_context, &creds);
266         krb5_free_cred_contents(ccc->smb_krb5_context->krb5_context, &creds2);
267         if (ret != 0) {
268                 /* don't retry - we didn't find these credentials to
269                  * remove. Note that with the current backend this
270                  * never happens, as it always returns 0 even if the
271                  * creds don't exist, which is why we do a separate
272                  * krb5_cc_retrieve_cred() above.
273                  */
274                 return false;
275         }
276         return true;
277 }
278
279
280 static int cli_credentials_new_ccache(struct cli_credentials *cred, 
281                                       struct loadparm_context *lp_ctx,
282                                       char *ccache_name,
283                                       struct ccache_container **_ccc,
284                                       const char **error_string)
285 {
286         bool must_free_cc_name = false;
287         krb5_error_code ret;
288         struct ccache_container *ccc = talloc(cred, struct ccache_container);
289         if (!ccc) {
290                 return ENOMEM;
291         }
292
293         ret = cli_credentials_get_krb5_context(cred, lp_ctx,
294                                                &ccc->smb_krb5_context);
295         if (ret) {
296                 talloc_free(ccc);
297                 (*error_string) = talloc_asprintf(cred, "Failed to get krb5_context: %s",
298                                                   error_message(ret));
299                 return ret;
300         }
301         if (!talloc_reference(ccc, ccc->smb_krb5_context)) {
302                 talloc_free(ccc);
303                 (*error_string) = strerror(ENOMEM);
304                 return ENOMEM;
305         }
306
307         if (!ccache_name) {
308                 must_free_cc_name = true;
309
310                 if (lpcfg_parm_bool(lp_ctx, NULL, "credentials", "krb5_cc_file", false)) {
311                         ccache_name = talloc_asprintf(ccc, "FILE:/tmp/krb5_cc_samba_%u_%p", 
312                                                       (unsigned int)getpid(), ccc);
313                 } else {
314                         ccache_name = talloc_asprintf(ccc, "MEMORY:%p", 
315                                                       ccc);
316                 }
317
318                 if (!ccache_name) {
319                         talloc_free(ccc);
320                         (*error_string) = strerror(ENOMEM);
321                         return ENOMEM;
322                 }
323         }
324
325         ret = krb5_cc_resolve(ccc->smb_krb5_context->krb5_context, ccache_name, 
326                               &ccc->ccache);
327         if (ret) {
328                 (*error_string) = talloc_asprintf(cred, "failed to resolve a krb5 ccache (%s): %s\n",
329                                                   ccache_name,
330                                                   smb_get_krb5_error_message(ccc->smb_krb5_context->krb5_context,
331                                                                              ret, ccc));
332                 talloc_free(ccache_name);
333                 talloc_free(ccc);
334                 return ret;
335         }
336
337         if (strncasecmp(ccache_name, "MEMORY:", 7) == 0) {
338                 talloc_set_destructor(ccc, free_mccache);
339         } else {
340                 talloc_set_destructor(ccc, free_dccache);
341         }
342
343         if (must_free_cc_name) {
344                 talloc_free(ccache_name);
345         }
346
347         *_ccc = ccc;
348
349         return 0;
350 }
351
352 _PUBLIC_ int cli_credentials_get_named_ccache(struct cli_credentials *cred, 
353                                               struct tevent_context *event_ctx,
354                                               struct loadparm_context *lp_ctx,
355                                               char *ccache_name,
356                                               struct ccache_container **ccc,
357                                               const char **error_string)
358 {
359         krb5_error_code ret;
360         enum credentials_obtained obtained;
361         
362         if (cred->machine_account_pending) {
363                 cli_credentials_set_machine_account(cred, lp_ctx);
364         }
365
366         if (cred->ccache_obtained >= cred->ccache_threshold && 
367             cred->ccache_obtained > CRED_UNINITIALISED) {
368                 time_t lifetime;
369                 bool expired = false;
370                 ret = smb_krb5_cc_get_lifetime(cred->ccache->smb_krb5_context->krb5_context,
371                                                cred->ccache->ccache, &lifetime);
372                 if (ret == KRB5_CC_END) {
373                         /* If we have a particular ccache set, without
374                          * an initial ticket, then assume there is a
375                          * good reason */
376                 } else if (ret == 0) {
377                         if (lifetime == 0) {
378                                 DEBUG(3, ("Ticket in credentials cache for %s expired, will refresh\n",
379                                           cli_credentials_get_principal(cred, cred)));
380                                 expired = true;
381                         } else if (lifetime < 300) {
382                                 DEBUG(3, ("Ticket in credentials cache for %s will shortly expire (%u secs), will refresh\n", 
383                                           cli_credentials_get_principal(cred, cred), (unsigned int)lifetime));
384                                 expired = true;
385                         }
386                 } else {
387                         (*error_string) = talloc_asprintf(cred, "failed to get ccache lifetime: %s\n",
388                                                           smb_get_krb5_error_message(cred->ccache->smb_krb5_context->krb5_context,
389                                                                                      ret, cred));
390                         return ret;
391                 }
392
393                 DEBUG(5, ("Ticket in credentials cache for %s will expire in %u secs\n", 
394                           cli_credentials_get_principal(cred, cred), (unsigned int)lifetime));
395                 
396                 if (!expired) {
397                         *ccc = cred->ccache;
398                         return 0;
399                 }
400         }
401         if (cli_credentials_is_anonymous(cred)) {
402                 (*error_string) = "Cannot get anonymous kerberos credentials";
403                 return EINVAL;
404         }
405
406         ret = cli_credentials_new_ccache(cred, lp_ctx, ccache_name, ccc, error_string);
407         if (ret) {
408                 return ret;
409         }
410
411         ret = kinit_to_ccache(cred, cred, (*ccc)->smb_krb5_context, event_ctx, (*ccc)->ccache, &obtained, error_string);
412         if (ret) {
413                 return ret;
414         }
415
416         ret = cli_credentials_set_from_ccache(cred, *ccc, 
417                                               obtained, error_string);
418         
419         cred->ccache = *ccc;
420         cred->ccache_obtained = cred->principal_obtained;
421         if (ret) {
422                 return ret;
423         }
424         cli_credentials_invalidate_client_gss_creds(cred, cred->ccache_obtained);
425         return 0;
426 }
427
428 _PUBLIC_ int cli_credentials_get_ccache(struct cli_credentials *cred, 
429                                         struct tevent_context *event_ctx,
430                                         struct loadparm_context *lp_ctx,
431                                         struct ccache_container **ccc,
432                                         const char **error_string)
433 {
434         return cli_credentials_get_named_ccache(cred, event_ctx, lp_ctx, NULL, ccc, error_string);
435 }
436
437 /* We have good reason to think the ccache in these credentials is invalid - blow it away */
438 static void cli_credentials_unconditionally_invalidate_client_gss_creds(struct cli_credentials *cred)
439 {
440         if (cred->client_gss_creds_obtained > CRED_UNINITIALISED) {
441                 talloc_unlink(cred, cred->client_gss_creds);
442                 cred->client_gss_creds = NULL;
443         }
444         cred->client_gss_creds_obtained = CRED_UNINITIALISED;
445 }
446
447 void cli_credentials_invalidate_client_gss_creds(struct cli_credentials *cred, 
448                                                  enum credentials_obtained obtained)
449 {
450         /* If the caller just changed the username/password etc, then
451          * any cached credentials are now invalid */
452         if (obtained >= cred->client_gss_creds_obtained) {
453                 if (cred->client_gss_creds_obtained > CRED_UNINITIALISED) {
454                         talloc_unlink(cred, cred->client_gss_creds);
455                         cred->client_gss_creds = NULL;
456                 }
457                 cred->client_gss_creds_obtained = CRED_UNINITIALISED;
458         }
459         /* Now that we know that the data is 'this specified', then
460          * don't allow something less 'known' to be returned as a
461          * ccache.  Ie, if the username is on the command line, we
462          * don't want to later guess to use a file-based ccache */
463         if (obtained > cred->client_gss_creds_threshold) {
464                 cred->client_gss_creds_threshold = obtained;
465         }
466 }
467
468 /* We have good reason to think this CCACHE is invalid.  Blow it away */
469 static void cli_credentials_unconditionally_invalidate_ccache(struct cli_credentials *cred)
470 {
471         if (cred->ccache_obtained > CRED_UNINITIALISED) {
472                 talloc_unlink(cred, cred->ccache);
473                 cred->ccache = NULL;
474         }
475         cred->ccache_obtained = CRED_UNINITIALISED;
476
477         cli_credentials_unconditionally_invalidate_client_gss_creds(cred);
478 }
479
480 _PUBLIC_ void cli_credentials_invalidate_ccache(struct cli_credentials *cred, 
481                                        enum credentials_obtained obtained)
482 {
483         /* If the caller just changed the username/password etc, then
484          * any cached credentials are now invalid */
485         if (obtained >= cred->ccache_obtained) {
486                 if (cred->ccache_obtained > CRED_UNINITIALISED) {
487                         talloc_unlink(cred, cred->ccache);
488                         cred->ccache = NULL;
489                 }
490                 cred->ccache_obtained = CRED_UNINITIALISED;
491         }
492         /* Now that we know that the data is 'this specified', then
493          * don't allow something less 'known' to be returned as a
494          * ccache.  i.e, if the username is on the command line, we
495          * don't want to later guess to use a file-based ccache */
496         if (obtained > cred->ccache_threshold) {
497                 cred->ccache_threshold  = obtained;
498         }
499
500         cli_credentials_invalidate_client_gss_creds(cred, 
501                                                     obtained);
502 }
503
504 static int free_gssapi_creds(struct gssapi_creds_container *gcc)
505 {
506         OM_uint32 min_stat;
507         (void)gss_release_cred(&min_stat, &gcc->creds);
508         return 0;
509 }
510
511 _PUBLIC_ int cli_credentials_get_client_gss_creds(struct cli_credentials *cred, 
512                                                   struct tevent_context *event_ctx,
513                                                   struct loadparm_context *lp_ctx,
514                                                   struct gssapi_creds_container **_gcc,
515                                                   const char **error_string)
516 {
517         int ret = 0;
518         OM_uint32 maj_stat, min_stat;
519         struct gssapi_creds_container *gcc;
520         struct ccache_container *ccache;
521 #ifdef HAVE_GSS_KRB5_CRED_NO_CI_FLAGS_X
522         gss_buffer_desc empty_buffer = GSS_C_EMPTY_BUFFER;
523         gss_OID oid = discard_const(GSS_KRB5_CRED_NO_CI_FLAGS_X);
524 #endif
525         krb5_enctype *etypes = NULL;
526
527         if (cred->client_gss_creds_obtained >= cred->client_gss_creds_threshold && 
528             cred->client_gss_creds_obtained > CRED_UNINITIALISED) {
529                 bool expired = false;
530                 OM_uint32 lifetime = 0;
531                 gss_cred_usage_t usage = 0;
532                 maj_stat = gss_inquire_cred(&min_stat, cred->client_gss_creds->creds, 
533                                             NULL, &lifetime, &usage, NULL);
534                 if (maj_stat == GSS_S_CREDENTIALS_EXPIRED) {
535                         DEBUG(3, ("Credentials for %s expired, must refresh credentials cache\n", cli_credentials_get_principal(cred, cred)));
536                         expired = true;
537                 } else if (maj_stat == GSS_S_COMPLETE && lifetime < 300) {
538                         DEBUG(3, ("Credentials for %s will expire shortly (%u sec), must refresh credentials cache\n", cli_credentials_get_principal(cred, cred), lifetime));
539                         expired = true;
540                 } else if (maj_stat != GSS_S_COMPLETE) {
541                         *error_string = talloc_asprintf(cred, "inquiry of credential lifefime via GSSAPI gss_inquire_cred failed: %s\n",
542                                                         gssapi_error_string(cred, maj_stat, min_stat, NULL));
543                         return EINVAL;
544                 }
545                 if (expired) {
546                         cli_credentials_unconditionally_invalidate_client_gss_creds(cred);
547                 } else {
548                         DEBUG(5, ("GSSAPI credentials for %s will expire in %u secs\n", 
549                                   cli_credentials_get_principal(cred, cred), (unsigned int)lifetime));
550                 
551                         *_gcc = cred->client_gss_creds;
552                         return 0;
553                 }
554         }
555
556         ret = cli_credentials_get_ccache(cred, event_ctx, lp_ctx,
557                                          &ccache, error_string);
558         if (ret) {
559                 if (cli_credentials_get_kerberos_state(cred) == CRED_MUST_USE_KERBEROS) {
560                         DEBUG(1, ("Failed to get kerberos credentials (kerberos required): %s\n", *error_string));
561                 } else {
562                         DEBUG(4, ("Failed to get kerberos credentials: %s\n", *error_string));
563                 }
564                 return ret;
565         }
566
567         gcc = talloc(cred, struct gssapi_creds_container);
568         if (!gcc) {
569                 (*error_string) = error_message(ENOMEM);
570                 return ENOMEM;
571         }
572
573         maj_stat = gss_krb5_import_cred(&min_stat, ccache->ccache, NULL, NULL, 
574                                         &gcc->creds);
575         if ((maj_stat == GSS_S_FAILURE) && (min_stat == (OM_uint32)KRB5_CC_END || min_stat == (OM_uint32) KRB5_CC_NOTFOUND)) {
576                 /* This CCACHE is no good.  Ensure we don't use it again */
577                 cli_credentials_unconditionally_invalidate_ccache(cred);
578
579                 /* Now try again to get a ccache */
580                 ret = cli_credentials_get_ccache(cred, event_ctx, lp_ctx,
581                                                  &ccache, error_string);
582                 if (ret) {
583                         DEBUG(1, ("Failed to re-get CCACHE for GSSAPI client: %s\n", error_message(ret)));
584                         return ret;
585                 }
586
587                 maj_stat = gss_krb5_import_cred(&min_stat, ccache->ccache, NULL, NULL,
588                                                 &gcc->creds);
589
590         }
591
592         if (maj_stat) {
593                 talloc_free(gcc);
594                 if (min_stat) {
595                         ret = min_stat;
596                 } else {
597                         ret = EINVAL;
598                 }
599                 (*error_string) = talloc_asprintf(cred, "gss_krb5_import_cred failed: %s", error_message(ret));
600                 return ret;
601         }
602
603
604         /*
605          * transfer the enctypes from the smb_krb5_context to the gssapi layer
606          *
607          * We use 'our' smb_krb5_context to do the AS-REQ and it is possible
608          * to configure the enctypes via the krb5.conf.
609          *
610          * And the gss_init_sec_context() creates it's own krb5_context and
611          * the TGS-REQ had all enctypes in it and only the ones configured
612          * and used for the AS-REQ, so it wasn't possible to disable the usage
613          * of AES keys.
614          */
615         min_stat = smb_krb5_get_allowed_etypes(ccache->smb_krb5_context->krb5_context,
616                                                &etypes);
617         if (min_stat == 0) {
618                 OM_uint32 num_ktypes;
619
620                 for (num_ktypes = 0; etypes[num_ktypes]; num_ktypes++);
621
622                 maj_stat = gss_krb5_set_allowable_enctypes(&min_stat, gcc->creds,
623                                                            num_ktypes,
624                                                            (int32_t *) etypes);
625                 SAFE_FREE(etypes);
626                 if (maj_stat) {
627                         talloc_free(gcc);
628                         if (min_stat) {
629                                 ret = min_stat;
630                         } else {
631                                 ret = EINVAL;
632                         }
633                         (*error_string) = talloc_asprintf(cred, "gss_krb5_set_allowable_enctypes failed: %s", error_message(ret));
634                         return ret;
635                 }
636         }
637
638 #ifdef HAVE_GSS_KRB5_CRED_NO_CI_FLAGS_X
639         /*
640          * Don't force GSS_C_CONF_FLAG and GSS_C_INTEG_FLAG.
641          *
642          * This allows us to disable SIGN and SEAL on a TLS connection with
643          * GSS-SPNENO. For example ldaps:// connections.
644          *
645          * https://groups.yahoo.com/neo/groups/cat-ietf/conversations/topics/575
646          * http://krbdev.mit.edu/rt/Ticket/Display.html?id=6938
647          */
648         maj_stat = gss_set_cred_option(&min_stat, &gcc->creds,
649                                        oid,
650                                        &empty_buffer);
651         if (maj_stat) {
652                 talloc_free(gcc);
653                 if (min_stat) {
654                         ret = min_stat;
655                 } else {
656                         ret = EINVAL;
657                 }
658                 (*error_string) = talloc_asprintf(cred, "gss_set_cred_option failed: %s", error_message(ret));
659                 return ret;
660         }
661 #endif
662         cred->client_gss_creds_obtained = cred->ccache_obtained;
663         talloc_set_destructor(gcc, free_gssapi_creds);
664         cred->client_gss_creds = gcc;
665         *_gcc = gcc;
666         return 0;
667 }
668
669 /**
670    Set a gssapi cred_id_t into the credentials system. (Client case)
671
672    This grabs the credentials both 'intact' and getting the krb5
673    ccache out of it.  This routine can be generalised in future for
674    the case where we deal with GSSAPI mechs other than krb5.  
675
676    On sucess, the caller must not free gssapi_cred, as it now belongs
677    to the credentials system.
678 */
679
680  int cli_credentials_set_client_gss_creds(struct cli_credentials *cred, 
681                                           struct loadparm_context *lp_ctx,
682                                           gss_cred_id_t gssapi_cred,
683                                           enum credentials_obtained obtained,
684                                           const char **error_string)
685 {
686         int ret;
687         OM_uint32 maj_stat, min_stat;
688         struct ccache_container *ccc;
689         struct gssapi_creds_container *gcc;
690         if (cred->client_gss_creds_obtained > obtained) {
691                 return 0;
692         }
693
694         gcc = talloc(cred, struct gssapi_creds_container);
695         if (!gcc) {
696                 (*error_string) = error_message(ENOMEM);
697                 return ENOMEM;
698         }
699
700         ret = cli_credentials_new_ccache(cred, lp_ctx, NULL, &ccc, error_string);
701         if (ret != 0) {
702                 return ret;
703         }
704
705         maj_stat = gss_krb5_copy_ccache(&min_stat, 
706                                         gssapi_cred, ccc->ccache);
707         if (maj_stat) {
708                 if (min_stat) {
709                         ret = min_stat;
710                 } else {
711                         ret = EINVAL;
712                 }
713                 if (ret) {
714                         (*error_string) = error_message(ENOMEM);
715                 }
716         }
717
718         if (ret == 0) {
719                 ret = cli_credentials_set_from_ccache(cred, ccc, obtained, error_string);
720         }
721         cred->ccache = ccc;
722         cred->ccache_obtained = obtained;
723         if (ret == 0) {
724                 gcc->creds = gssapi_cred;
725                 talloc_set_destructor(gcc, free_gssapi_creds);
726                 
727                 /* set the clinet_gss_creds_obtained here, as it just 
728                    got set to UNINITIALISED by the calls above */
729                 cred->client_gss_creds_obtained = obtained;
730                 cred->client_gss_creds = gcc;
731         }
732         return ret;
733 }
734
735 static int cli_credentials_shallow_ccache(struct cli_credentials *cred)
736 {
737         krb5_error_code ret;
738         const struct ccache_container *old_ccc = NULL;
739         struct ccache_container *ccc = NULL;
740         char *ccache_name = NULL;
741
742         old_ccc = cred->ccache;
743         if (old_ccc == NULL) {
744                 return 0;
745         }
746
747         ccc = talloc(cred, struct ccache_container);
748         if (ccc == NULL) {
749                 return ENOMEM;
750         }
751         *ccc = *old_ccc;
752         ccc->ccache = NULL;
753
754         ccache_name = talloc_asprintf(ccc, "MEMORY:%p", ccc);
755
756         ret = krb5_cc_resolve(ccc->smb_krb5_context->krb5_context,
757                               ccache_name, &ccc->ccache);
758         if (ret != 0) {
759                 TALLOC_FREE(ccc);
760                 return ret;
761         }
762
763         talloc_set_destructor(ccc, free_mccache);
764
765         TALLOC_FREE(ccache_name);
766
767         ret = smb_krb5_cc_copy_creds(ccc->smb_krb5_context->krb5_context,
768                                      old_ccc->ccache, ccc->ccache);
769         if (ret != 0) {
770                 TALLOC_FREE(ccc);
771                 return ret;
772         }
773
774         cred->ccache = ccc;
775         cred->client_gss_creds = NULL;
776         cred->client_gss_creds_obtained = CRED_UNINITIALISED;
777         return ret;
778 }
779
780 _PUBLIC_ struct cli_credentials *cli_credentials_shallow_copy(TALLOC_CTX *mem_ctx,
781                                                 struct cli_credentials *src)
782 {
783         struct cli_credentials *dst;
784         int ret;
785
786         dst = talloc(mem_ctx, struct cli_credentials);
787         if (dst == NULL) {
788                 return NULL;
789         }
790
791         *dst = *src;
792
793         ret = cli_credentials_shallow_ccache(dst);
794         if (ret != 0) {
795                 TALLOC_FREE(dst);
796                 return NULL;
797         }
798
799         return dst;
800 }
801
802 static int smb_krb5_create_salt_principal(TALLOC_CTX *mem_ctx,
803                                           const char *samAccountName,
804                                           const char *realm,
805                                           const char **salt_principal,
806                                           const char **error_string)
807 {
808         char *machine_username;
809         bool is_machine_account = false;
810         char *upper_realm;
811         TALLOC_CTX *tmp_ctx;
812         int rc = -1;
813
814         if (samAccountName == NULL) {
815                 *error_string = "Cannot determine salt principal, no "
816                                 "saltPrincipal or samAccountName specified";
817                 return rc;
818         }
819
820         if (realm == NULL) {
821                 *error_string = "Cannot make principal without a realm";
822                 return rc;
823         }
824
825         tmp_ctx = talloc_new(mem_ctx);
826         if (tmp_ctx == NULL) {
827                 *error_string = "Cannot allocate talloc context";
828                 return rc;
829         }
830
831         upper_realm = strupper_talloc(tmp_ctx, realm);
832         if (upper_realm == NULL) {
833                 *error_string = "Cannot allocate to upper case realm";
834                 goto out;
835         }
836
837         machine_username = strlower_talloc(tmp_ctx, samAccountName);
838         if (!machine_username) {
839                 *error_string = "Cannot duplicate samAccountName";
840                 goto out;
841         }
842
843         if (machine_username[strlen(machine_username) - 1] == '$') {
844                 machine_username[strlen(machine_username) - 1] = '\0';
845                 is_machine_account = true;
846         }
847
848         if (is_machine_account) {
849                 char *lower_realm;
850
851                 lower_realm = strlower_talloc(tmp_ctx, realm);
852                 if (lower_realm == NULL) {
853                         *error_string = "Cannot allocate to lower case realm";
854                         goto out;
855                 }
856
857                 *salt_principal = talloc_asprintf(mem_ctx,
858                                                   "host/%s.%s@%s",
859                                                   machine_username,
860                                                   lower_realm,
861                                                   upper_realm);
862         } else {
863                 *salt_principal = talloc_asprintf(mem_ctx,
864                                                   "%s@%s",
865                                                   machine_username,
866                                                   upper_realm);
867         }
868         if (*salt_principal == NULL) {
869                 *error_string = "Cannot create salt principal";
870                 goto out;
871         }
872
873         rc = 0;
874 out:
875         talloc_free(tmp_ctx);
876         return rc;
877 }
878
879 /* Get the keytab (actually, a container containing the krb5_keytab)
880  * attached to this context.  If this hasn't been done or set before,
881  * it will be generated from the password.
882  */
883 _PUBLIC_ int cli_credentials_get_keytab(struct cli_credentials *cred, 
884                                         struct loadparm_context *lp_ctx,
885                                         struct keytab_container **_ktc)
886 {
887         krb5_error_code ret;
888         struct keytab_container *ktc;
889         struct smb_krb5_context *smb_krb5_context;
890         const char *keytab_name;
891         krb5_keytab keytab;
892         TALLOC_CTX *mem_ctx;
893         const char *username = cli_credentials_get_username(cred);
894         const char *realm = cli_credentials_get_realm(cred);
895         const char *error_string;
896         const char *salt_principal;
897
898         if (cred->keytab_obtained >= (MAX(cred->principal_obtained, 
899                                           cred->username_obtained))) {
900                 *_ktc = cred->keytab;
901                 return 0;
902         }
903
904         if (cli_credentials_is_anonymous(cred)) {
905                 return EINVAL;
906         }
907
908         ret = cli_credentials_get_krb5_context(cred, lp_ctx,
909                                                &smb_krb5_context);
910         if (ret) {
911                 return ret;
912         }
913
914         mem_ctx = talloc_new(cred);
915         if (!mem_ctx) {
916                 return ENOMEM;
917         }
918
919         /*
920          * FIXME: Currently there is no better way than to create the correct
921          * salt principal by checking if the username ends with a '$'. It would
922          * be better if it is part of the credentials.
923          */
924         ret = smb_krb5_create_salt_principal(mem_ctx,
925                                              username,
926                                              realm,
927                                              &salt_principal,
928                                              &error_string);
929         if (ret) {
930                 talloc_free(mem_ctx);
931                 return ret;
932         }
933
934         ret = smb_krb5_create_memory_keytab(mem_ctx,
935                                             smb_krb5_context->krb5_context,
936                                             cli_credentials_get_password(cred),
937                                             username,
938                                             realm,
939                                             salt_principal,
940                                             cli_credentials_get_kvno(cred),
941                                             &keytab,
942                                             &keytab_name);
943         if (ret) {
944                 talloc_free(mem_ctx);
945                 return ret;
946         }
947
948         ret = smb_krb5_get_keytab_container(mem_ctx, smb_krb5_context,
949                                             keytab, keytab_name, &ktc);
950         if (ret) {
951                 talloc_free(mem_ctx);
952                 return ret;
953         }
954
955         cred->keytab_obtained = (MAX(cred->principal_obtained, 
956                                      cred->username_obtained));
957
958         /* We make this keytab up based on a password.  Therefore
959          * match-by-key is acceptable, we can't match on the wrong
960          * principal */
961         ktc->password_based = true;
962
963         talloc_steal(cred, ktc);
964         cred->keytab = ktc;
965         *_ktc = cred->keytab;
966         talloc_free(mem_ctx);
967         return ret;
968 }
969
970 /* Given the name of a keytab (presumably in the format
971  * FILE:/etc/krb5.keytab), open it and attach it */
972
973 _PUBLIC_ int cli_credentials_set_keytab_name(struct cli_credentials *cred, 
974                                              struct loadparm_context *lp_ctx,
975                                              const char *keytab_name,
976                                              enum credentials_obtained obtained)
977 {
978         krb5_error_code ret;
979         struct keytab_container *ktc;
980         struct smb_krb5_context *smb_krb5_context;
981         TALLOC_CTX *mem_ctx;
982
983         if (cred->keytab_obtained >= obtained) {
984                 return 0;
985         }
986
987         ret = cli_credentials_get_krb5_context(cred, lp_ctx, &smb_krb5_context);
988         if (ret) {
989                 return ret;
990         }
991
992         mem_ctx = talloc_new(cred);
993         if (!mem_ctx) {
994                 return ENOMEM;
995         }
996
997         ret = smb_krb5_get_keytab_container(mem_ctx, smb_krb5_context,
998                                             NULL, keytab_name, &ktc);
999         if (ret) {
1000                 return ret;
1001         }
1002
1003         cred->keytab_obtained = obtained;
1004
1005         talloc_steal(cred, ktc);
1006         cred->keytab = ktc;
1007         talloc_free(mem_ctx);
1008
1009         return ret;
1010 }
1011
1012 /* Get server gss credentials (in gsskrb5, this means the keytab) */
1013
1014 _PUBLIC_ int cli_credentials_get_server_gss_creds(struct cli_credentials *cred, 
1015                                                   struct loadparm_context *lp_ctx,
1016                                                   struct gssapi_creds_container **_gcc)
1017 {
1018         int ret = 0;
1019         OM_uint32 maj_stat, min_stat;
1020         struct gssapi_creds_container *gcc;
1021         struct keytab_container *ktc;
1022         struct smb_krb5_context *smb_krb5_context;
1023         TALLOC_CTX *mem_ctx;
1024         krb5_principal princ;
1025         const char *error_string;
1026         enum credentials_obtained obtained;
1027
1028         mem_ctx = talloc_new(cred);
1029         if (!mem_ctx) {
1030                 return ENOMEM;
1031         }
1032
1033         ret = cli_credentials_get_krb5_context(cred, lp_ctx, &smb_krb5_context);
1034         if (ret) {
1035                 return ret;
1036         }
1037
1038         ret = principal_from_credentials(mem_ctx, cred, smb_krb5_context, &princ, &obtained, &error_string);
1039         if (ret) {
1040                 DEBUG(1,("cli_credentials_get_server_gss_creds: making krb5 principal failed (%s)\n",
1041                          error_string));
1042                 talloc_free(mem_ctx);
1043                 return ret;
1044         }
1045
1046         if (cred->server_gss_creds_obtained >= (MAX(cred->keytab_obtained, obtained))) {
1047                 talloc_free(mem_ctx);
1048                 *_gcc = cred->server_gss_creds;
1049                 return 0;
1050         }
1051
1052         ret = cli_credentials_get_keytab(cred, lp_ctx, &ktc);
1053         if (ret) {
1054                 DEBUG(1, ("Failed to get keytab for GSSAPI server: %s\n", error_message(ret)));
1055                 return ret;
1056         }
1057
1058         gcc = talloc(cred, struct gssapi_creds_container);
1059         if (!gcc) {
1060                 talloc_free(mem_ctx);
1061                 return ENOMEM;
1062         }
1063
1064         if (ktc->password_based || obtained < CRED_SPECIFIED) {
1065                 /* This creates a GSSAPI cred_id_t for match-by-key with only the keytab set */
1066                 maj_stat = gss_krb5_import_cred(&min_stat, NULL, NULL, ktc->keytab,
1067                                                 &gcc->creds);
1068         } else {
1069                 /* This creates a GSSAPI cred_id_t with the principal and keytab set, matching by name */
1070                 maj_stat = gss_krb5_import_cred(&min_stat, NULL, princ, ktc->keytab,
1071                                                 &gcc->creds);
1072         }
1073         if (maj_stat) {
1074                 if (min_stat) {
1075                         ret = min_stat;
1076                 } else {
1077                         ret = EINVAL;
1078                 }
1079         }
1080         if (ret == 0) {
1081                 cred->server_gss_creds_obtained = cred->keytab_obtained;
1082                 talloc_set_destructor(gcc, free_gssapi_creds);
1083                 cred->server_gss_creds = gcc;
1084                 *_gcc = gcc;
1085         }
1086         talloc_free(mem_ctx);
1087         return ret;
1088 }
1089
1090 /** 
1091  * Set Kerberos KVNO
1092  */
1093
1094 _PUBLIC_ void cli_credentials_set_kvno(struct cli_credentials *cred,
1095                               int kvno)
1096 {
1097         cred->kvno = kvno;
1098 }
1099
1100 /**
1101  * Return Kerberos KVNO
1102  */
1103
1104 _PUBLIC_ int cli_credentials_get_kvno(struct cli_credentials *cred)
1105 {
1106         return cred->kvno;
1107 }
1108
1109
1110 const char *cli_credentials_get_salt_principal(struct cli_credentials *cred) 
1111 {
1112         return cred->salt_principal;
1113 }
1114
1115 _PUBLIC_ void cli_credentials_set_salt_principal(struct cli_credentials *cred, const char *principal) 
1116 {
1117         talloc_free(cred->salt_principal);
1118         cred->salt_principal = talloc_strdup(cred, principal);
1119 }
1120
1121 /* The 'impersonate_principal' is used to allow one Kerberos principal
1122  * (and it's associated keytab etc) to impersonate another.  The
1123  * ability to do this is controlled by the KDC, but it is generally
1124  * permitted to impersonate anyone to yourself.  This allows any
1125  * member of the domain to get the groups of a user.  This is also
1126  * known as S4U2Self */
1127
1128 _PUBLIC_ const char *cli_credentials_get_impersonate_principal(struct cli_credentials *cred)
1129 {
1130         return cred->impersonate_principal;
1131 }
1132
1133 /*
1134  * The 'self_service' is the service principal that
1135  * represents the same object (by its objectSid)
1136  * as the client principal (typically our machine account).
1137  * When trying to impersonate 'impersonate_principal' with
1138  * S4U2Self.
1139  */
1140 _PUBLIC_ const char *cli_credentials_get_self_service(struct cli_credentials *cred)
1141 {
1142         return cred->self_service;
1143 }
1144
1145 _PUBLIC_ void cli_credentials_set_impersonate_principal(struct cli_credentials *cred,
1146                                                         const char *principal,
1147                                                         const char *self_service)
1148 {
1149         talloc_free(cred->impersonate_principal);
1150         cred->impersonate_principal = talloc_strdup(cred, principal);
1151         talloc_free(cred->self_service);
1152         cred->self_service = talloc_strdup(cred, self_service);
1153         cli_credentials_set_kerberos_state(cred, CRED_MUST_USE_KERBEROS);
1154 }
1155
1156 /*
1157  * when impersonating for S4U2proxy we need to set the target principal.
1158  * Similarly, we may only be authorized to do general impersonation to
1159  * some particular services.
1160  *
1161  * Likewise, password changes typically require a ticket to kpasswd/realm directly, not via a TGT
1162  *
1163  * NULL means that tickets will be obtained for the krbtgt service.
1164 */
1165
1166 const char *cli_credentials_get_target_service(struct cli_credentials *cred)
1167 {
1168         return cred->target_service;
1169 }
1170
1171 _PUBLIC_ void cli_credentials_set_target_service(struct cli_credentials *cred, const char *target_service)
1172 {
1173         talloc_free(cred->target_service);
1174         cred->target_service = talloc_strdup(cred, target_service);
1175 }
1176