Fix krb5_samba.c build
[nivanova/samba-autobuild/.git] / lib / krb5_wrap / krb5_samba.c
1 /*
2    Unix SMB/CIFS implementation.
3    simple kerberos5 routines for active directory
4    Copyright (C) Andrew Tridgell 2001
5    Copyright (C) Luke Howard 2002-2003
6    Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005
7    Copyright (C) Guenther Deschner 2005-2009
8
9    This program is free software; you can redistribute it and/or modify
10    it under the terms of the GNU General Public License as published by
11    the Free Software Foundation; either version 3 of the License, or
12    (at your option) any later version.
13
14    This program is distributed in the hope that it will be useful,
15    but WITHOUT ANY WARRANTY; without even the implied warranty of
16    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17    GNU General Public License for more details.
18
19    You should have received a copy of the GNU General Public License
20    along with this program.  If not, see <http://www.gnu.org/licenses/>.
21 */
22
23 #include "includes.h"
24 #include "krb5_samba.h"
25 #include "librpc/gen_ndr/krb5pac.h"
26 #include "lib/util/asn1.h"
27
28 #ifndef KRB5_AUTHDATA_WIN2K_PAC
29 #define KRB5_AUTHDATA_WIN2K_PAC 128
30 #endif
31
32 #ifndef KRB5_AUTHDATA_IF_RELEVANT
33 #define KRB5_AUTHDATA_IF_RELEVANT 1
34 #endif
35
36 #ifdef HAVE_KRB5
37
38 #define GSSAPI_CHECKSUM      0x8003             /* Checksum type value for Kerberos */
39 #define GSSAPI_BNDLENGTH     16                 /* Bind Length (rfc-1964 pg.3) */
40 #define GSSAPI_CHECKSUM_SIZE (4+GSSAPI_BNDLENGTH+4) /* Length of bind length,
41                                                         bind field, flags field. */
42 #define GSS_C_DELEG_FLAG 1
43
44 /* MIT krb5 1.7beta3 (in Ubuntu Karmic) is missing the prototype,
45    but still has the symbol */
46 #if !HAVE_DECL_KRB5_AUTH_CON_SET_REQ_CKSUMTYPE
47 krb5_error_code krb5_auth_con_set_req_cksumtype(
48         krb5_context     context,
49         krb5_auth_context      auth_context,
50         krb5_cksumtype     cksumtype);
51 #endif
52
53 #if !defined(SMB_MALLOC)
54 #undef malloc
55 #define SMB_MALLOC(s) malloc((s))
56 #endif
57
58 #ifndef SMB_STRDUP
59 #define SMB_STRDUP(s) strdup(s)
60 #endif
61
62 #if !defined(HAVE_KRB5_SET_DEFAULT_TGS_KTYPES)
63
64 #if defined(HAVE_KRB5_SET_DEFAULT_TGS_ENCTYPES)
65
66 /* With MIT kerberos, we should use krb5_set_default_tgs_enctypes in preference
67  * to krb5_set_default_tgs_ktypes. See
68  *         http://lists.samba.org/archive/samba-technical/2006-July/048271.html
69  *
70  * If the MIT libraries are not exporting internal symbols, we will end up in
71  * this branch, which is correct. Otherwise we will continue to use the
72  * internal symbol
73  */
74  krb5_error_code krb5_set_default_tgs_ktypes(krb5_context ctx, const krb5_enctype *enc)
75 {
76     return krb5_set_default_tgs_enctypes(ctx, enc);
77 }
78
79 #elif defined(HAVE_KRB5_SET_DEFAULT_IN_TKT_ETYPES)
80
81 /* Heimdal */
82  krb5_error_code krb5_set_default_tgs_ktypes(krb5_context ctx, const krb5_enctype *enc)
83 {
84         return krb5_set_default_in_tkt_etypes(ctx, enc);
85 }
86
87 #endif /* HAVE_KRB5_SET_DEFAULT_TGS_ENCTYPES */
88
89 #endif /* HAVE_KRB5_SET_DEFAULT_TGS_KTYPES */
90
91 #if defined(HAVE_ADDR_TYPE_IN_KRB5_ADDRESS)
92 /* HEIMDAL */
93  bool setup_kaddr( krb5_address *pkaddr, struct sockaddr_storage *paddr)
94 {
95         memset(pkaddr, '\0', sizeof(krb5_address));
96 #if defined(HAVE_IPV6) && defined(KRB5_ADDRESS_INET6)
97         if (paddr->ss_family == AF_INET6) {
98                 pkaddr->addr_type = KRB5_ADDRESS_INET6;
99                 pkaddr->address.length = sizeof(((struct sockaddr_in6 *)paddr)->sin6_addr);
100                 pkaddr->address.data = (char *)&(((struct sockaddr_in6 *)paddr)->sin6_addr);
101                 return true;
102         }
103 #endif
104         if (paddr->ss_family == AF_INET) {
105                 pkaddr->addr_type = KRB5_ADDRESS_INET;
106                 pkaddr->address.length = sizeof(((struct sockaddr_in *)paddr)->sin_addr);
107                 pkaddr->address.data = (char *)&(((struct sockaddr_in *)paddr)->sin_addr);
108                 return true;
109         }
110         return false;
111 }
112 #elif defined(HAVE_ADDRTYPE_IN_KRB5_ADDRESS)
113 /* MIT */
114 bool setup_kaddr( krb5_address *pkaddr, struct sockaddr_storage *paddr)
115 {
116         memset(pkaddr, '\0', sizeof(krb5_address));
117 #if defined(HAVE_IPV6) && defined(ADDRTYPE_INET6)
118         if (paddr->ss_family == AF_INET6) {
119                 pkaddr->addrtype = ADDRTYPE_INET6;
120                 pkaddr->length = sizeof(((struct sockaddr_in6 *)paddr)->sin6_addr);
121                 pkaddr->contents = (krb5_octet *)&(((struct sockaddr_in6 *)paddr)->sin6_addr);
122                 return true;
123         }
124 #endif
125         if (paddr->ss_family == AF_INET) {
126                 pkaddr->addrtype = ADDRTYPE_INET;
127                 pkaddr->length = sizeof(((struct sockaddr_in *)paddr)->sin_addr);
128                 pkaddr->contents = (krb5_octet *)&(((struct sockaddr_in *)paddr)->sin_addr);
129                 return true;
130         }
131         return false;
132 }
133 #else
134 #error UNKNOWN_ADDRTYPE
135 #endif
136
137 #if defined(HAVE_KRB5_PRINCIPAL2SALT) && defined(HAVE_KRB5_C_STRING_TO_KEY)
138 /* MIT */
139 int create_kerberos_key_from_string_direct(krb5_context context,
140                                                   krb5_principal host_princ,
141                                                   krb5_data *password,
142                                                   krb5_keyblock *key,
143                                                   krb5_enctype enctype)
144 {
145         int ret = 0;
146         krb5_data salt;
147
148         ret = krb5_principal2salt(context, host_princ, &salt);
149         if (ret) {
150                 DEBUG(1,("krb5_principal2salt failed (%s)\n", error_message(ret)));
151                 return ret;
152         }
153         ret = krb5_c_string_to_key(context, enctype, password, &salt, key);
154         SAFE_FREE(salt.data);
155
156         return ret;
157 }
158 #elif defined(HAVE_KRB5_GET_PW_SALT) && defined(HAVE_KRB5_STRING_TO_KEY_SALT)
159 /* Heimdal */
160 int create_kerberos_key_from_string_direct(krb5_context context,
161                                                   krb5_principal host_princ,
162                                                   krb5_data *password,
163                                                   krb5_keyblock *key,
164                                                   krb5_enctype enctype)
165 {
166         int ret;
167         krb5_salt salt;
168
169         ret = krb5_get_pw_salt(context, host_princ, &salt);
170         if (ret) {
171                 DEBUG(1,("krb5_get_pw_salt failed (%s)\n", error_message(ret)));
172                 return ret;
173         }
174
175         ret = krb5_string_to_key_salt(context, enctype, (const char *)password->data, salt, key);
176         krb5_free_salt(context, salt);
177
178         return ret;
179 }
180 #else
181 #error UNKNOWN_CREATE_KEY_FUNCTIONS
182 #endif
183
184 #if defined(HAVE_KRB5_GET_PERMITTED_ENCTYPES)
185  krb5_error_code get_kerberos_allowed_etypes(krb5_context context,
186                                             krb5_enctype **enctypes)
187 {
188         return krb5_get_permitted_enctypes(context, enctypes);
189 }
190 #elif defined(HAVE_KRB5_GET_DEFAULT_IN_TKT_ETYPES)
191  krb5_error_code get_kerberos_allowed_etypes(krb5_context context,
192                                             krb5_enctype **enctypes)
193 {
194 #ifdef HAVE_KRB5_PDU_NONE_DECL
195         return krb5_get_default_in_tkt_etypes(context, KRB5_PDU_NONE, enctypes);
196 #else
197         return krb5_get_default_in_tkt_etypes(context, enctypes);
198 #endif
199 }
200 #else
201 #error UNKNOWN_GET_ENCTYPES_FUNCTIONS
202 #endif
203
204 #if defined(HAVE_KRB5_AUTH_CON_SETKEY) && !defined(HAVE_KRB5_AUTH_CON_SETUSERUSERKEY)
205  krb5_error_code krb5_auth_con_setuseruserkey(krb5_context context,
206                                         krb5_auth_context auth_context,
207                                         krb5_keyblock *keyblock)
208 {
209         return krb5_auth_con_setkey(context, auth_context, keyblock);
210 }
211 #endif
212
213 bool unwrap_edata_ntstatus(TALLOC_CTX *mem_ctx,
214                            DATA_BLOB *edata,
215                            DATA_BLOB *edata_out)
216 {
217         DATA_BLOB edata_contents;
218         ASN1_DATA *data;
219         int edata_type;
220
221         if (!edata->length) {
222                 return false;
223         }
224
225         data = asn1_init(mem_ctx);
226         if (data == NULL) {
227                 return false;
228         }
229
230         asn1_load(data, *edata);
231         asn1_start_tag(data, ASN1_SEQUENCE(0));
232         asn1_start_tag(data, ASN1_CONTEXT(1));
233         asn1_read_Integer(data, &edata_type);
234
235         if (edata_type != KRB5_PADATA_PW_SALT) {
236                 DEBUG(0,("edata is not of required type %d but of type %d\n",
237                         KRB5_PADATA_PW_SALT, edata_type));
238                 asn1_free(data);
239                 return false;
240         }
241
242         asn1_start_tag(data, ASN1_CONTEXT(2));
243         asn1_read_OctetString(data, talloc_tos(), &edata_contents);
244         asn1_end_tag(data);
245         asn1_end_tag(data);
246         asn1_end_tag(data);
247         asn1_free(data);
248
249         *edata_out = data_blob_talloc(mem_ctx, edata_contents.data, edata_contents.length);
250
251         data_blob_free(&edata_contents);
252
253         return true;
254 }
255
256
257 static bool ads_cleanup_expired_creds(krb5_context context,
258                                       krb5_ccache  ccache,
259                                       krb5_creds  *credsp)
260 {
261         krb5_error_code retval;
262         const char *cc_type = krb5_cc_get_type(context, ccache);
263
264         DEBUG(3, ("ads_cleanup_expired_creds: Ticket in ccache[%s:%s] expiration %s\n",
265                   cc_type, krb5_cc_get_name(context, ccache),
266                   http_timestring(talloc_tos(), credsp->times.endtime)));
267
268         /* we will probably need new tickets if the current ones
269            will expire within 10 seconds.
270         */
271         if (credsp->times.endtime >= (time(NULL) + 10))
272                 return false;
273
274         /* heimdal won't remove creds from a file ccache, and
275            perhaps we shouldn't anyway, since internally we
276            use memory ccaches, and a FILE one probably means that
277            we're using creds obtained outside of our exectuable
278         */
279         if (strequal(cc_type, "FILE")) {
280                 DEBUG(5, ("ads_cleanup_expired_creds: We do not remove creds from a %s ccache\n", cc_type));
281                 return false;
282         }
283
284         retval = krb5_cc_remove_cred(context, ccache, 0, credsp);
285         if (retval) {
286                 DEBUG(1, ("ads_cleanup_expired_creds: krb5_cc_remove_cred failed, err %s\n",
287                           error_message(retval)));
288                 /* If we have an error in this, we want to display it,
289                    but continue as though we deleted it */
290         }
291         return true;
292 }
293
294 /* Allocate and setup the auth context into the state we need. */
295
296 static krb5_error_code setup_auth_context(krb5_context context,
297                         krb5_auth_context *auth_context)
298 {
299         krb5_error_code retval;
300
301         retval = krb5_auth_con_init(context, auth_context );
302         if (retval) {
303                 DEBUG(1,("krb5_auth_con_init failed (%s)\n",
304                         error_message(retval)));
305                 return retval;
306         }
307
308         /* Ensure this is an addressless ticket. */
309         retval = krb5_auth_con_setaddrs(context, *auth_context, NULL, NULL);
310         if (retval) {
311                 DEBUG(1,("krb5_auth_con_setaddrs failed (%s)\n",
312                         error_message(retval)));
313         }
314
315         return retval;
316 }
317
318 #if defined(TKT_FLG_OK_AS_DELEGATE ) && defined(HAVE_KRB5_AUTH_CON_SETUSERUSERKEY) && defined(KRB5_AUTH_CONTEXT_USE_SUBKEY) && defined(HAVE_KRB5_AUTH_CON_SET_REQ_CKSUMTYPE)
319 static krb5_error_code create_gss_checksum(krb5_data *in_data, /* [inout] */
320                                                 uint32_t gss_flags)
321 {
322         unsigned int orig_length = in_data->length;
323         unsigned int base_cksum_size = GSSAPI_CHECKSUM_SIZE;
324         char *gss_cksum = NULL;
325
326         if (orig_length) {
327                 /* Extra length field for delgated ticket. */
328                 base_cksum_size += 4;
329         }
330
331         if ((unsigned int)base_cksum_size + orig_length <
332                         (unsigned int)base_cksum_size) {
333                 return EINVAL;
334         }
335
336         gss_cksum = (char *)SMB_MALLOC(base_cksum_size + orig_length);
337         if (gss_cksum == NULL) {
338                 return ENOMEM;
339         }
340
341         memset(gss_cksum, '\0', base_cksum_size + orig_length);
342         SIVAL(gss_cksum, 0, GSSAPI_BNDLENGTH);
343
344         /*
345          * GSS_C_NO_CHANNEL_BINDINGS means 16 zero bytes.
346          * This matches the behavior of heimdal and mit.
347          *
348          * And it is needed to work against some closed source
349          * SMB servers.
350          *
351          * See bug #7883
352          */
353         memset(&gss_cksum[4], 0x00, GSSAPI_BNDLENGTH);
354
355         SIVAL(gss_cksum, 20, gss_flags);
356
357         if (orig_length) {
358                 SSVAL(gss_cksum, 24, 1); /* The Delegation Option identifier */
359                 SSVAL(gss_cksum, 26, orig_length);
360                 /* Copy the kerberos KRB_CRED data */
361                 memcpy(gss_cksum + 28, in_data->data, orig_length);
362                 free(in_data->data);
363                 in_data->data = NULL;
364                 in_data->length = 0;
365         }
366         in_data->data = gss_cksum;
367         in_data->length = base_cksum_size + orig_length;
368         return 0;
369 }
370 #endif
371
372 /**************************************************************
373  krb5_parse_name that takes a UNIX charset.
374 **************************************************************/
375
376 krb5_error_code smb_krb5_parse_name(krb5_context context,
377                                 const char *name, /* in unix charset */
378                                 krb5_principal *principal)
379 {
380         krb5_error_code ret;
381         char *utf8_name;
382         size_t converted_size;
383         TALLOC_CTX *frame = talloc_stackframe();
384
385         if (!push_utf8_talloc(frame, &utf8_name, name, &converted_size)) {
386                 talloc_free(frame);
387                 return ENOMEM;
388         }
389
390         ret = krb5_parse_name(context, utf8_name, principal);
391         TALLOC_FREE(frame);
392         return ret;
393 }
394
395 #if !defined(HAVE_KRB5_FREE_UNPARSED_NAME)
396 void krb5_free_unparsed_name(krb5_context context, char *val)
397 {
398         SAFE_FREE(val);
399 }
400 #endif
401
402 /**************************************************************
403  krb5_parse_name that returns a UNIX charset name. Must
404  be freed with talloc_free() call.
405 **************************************************************/
406
407 krb5_error_code smb_krb5_unparse_name(TALLOC_CTX *mem_ctx,
408                                       krb5_context context,
409                                       krb5_const_principal principal,
410                                       char **unix_name)
411 {
412         krb5_error_code ret;
413         char *utf8_name;
414         size_t converted_size;
415
416         *unix_name = NULL;
417         ret = krb5_unparse_name(context, principal, &utf8_name);
418         if (ret) {
419                 return ret;
420         }
421
422         if (!pull_utf8_talloc(mem_ctx, unix_name, utf8_name, &converted_size)) {
423                 krb5_free_unparsed_name(context, utf8_name);
424                 return ENOMEM;
425         }
426         krb5_free_unparsed_name(context, utf8_name);
427         return 0;
428 }
429
430 krb5_error_code smb_krb5_parse_name_norealm(krb5_context context, 
431                                             const char *name, 
432                                             krb5_principal *principal)
433 {
434         /* we are cheating here because parse_name will in fact set the realm.
435          * We don't care as the only caller of smb_krb5_parse_name_norealm
436          * ignores the realm anyway when calling
437          * smb_krb5_principal_compare_any_realm later - Guenther */
438
439         return smb_krb5_parse_name(context, name, principal);
440 }
441
442 bool smb_krb5_principal_compare_any_realm(krb5_context context, 
443                                           krb5_const_principal princ1, 
444                                           krb5_const_principal princ2)
445 {
446         return krb5_principal_compare_any_realm(context, princ1, princ2);
447 }
448
449 /*
450   we can't use krb5_mk_req because w2k wants the service to be in a particular format
451 */
452 static krb5_error_code ads_krb5_mk_req(krb5_context context,
453                                        krb5_auth_context *auth_context,
454                                        const krb5_flags ap_req_options,
455                                        const char *principal,
456                                        krb5_ccache ccache,
457                                        krb5_data *outbuf,
458                                        time_t *expire_time,
459                                        const char *impersonate_princ_s)
460 {
461         krb5_error_code           retval;
462         krb5_principal    server;
463         krb5_principal impersonate_princ = NULL;
464         krb5_creds              * credsp;
465         krb5_creds                creds;
466         krb5_data in_data;
467         bool creds_ready = false;
468         int i = 0, maxtries = 3;
469
470         ZERO_STRUCT(in_data);
471
472         retval = smb_krb5_parse_name(context, principal, &server);
473         if (retval) {
474                 DEBUG(1,("ads_krb5_mk_req: Failed to parse principal %s\n", principal));
475                 return retval;
476         }
477
478         if (impersonate_princ_s) {
479                 retval = smb_krb5_parse_name(context, impersonate_princ_s,
480                                              &impersonate_princ);
481                 if (retval) {
482                         DEBUG(1,("ads_krb5_mk_req: Failed to parse principal %s\n", impersonate_princ_s));
483                         goto cleanup_princ;
484                 }
485         }
486
487         /* obtain ticket & session key */
488         ZERO_STRUCT(creds);
489         if ((retval = krb5_copy_principal(context, server, &creds.server))) {
490                 DEBUG(1,("ads_krb5_mk_req: krb5_copy_principal failed (%s)\n",
491                          error_message(retval)));
492                 goto cleanup_princ;
493         }
494
495         if ((retval = krb5_cc_get_principal(context, ccache, &creds.client))) {
496                 /* This can commonly fail on smbd startup with no ticket in the cache.
497                  * Report at higher level than 1. */
498                 DEBUG(3,("ads_krb5_mk_req: krb5_cc_get_principal failed (%s)\n",
499                          error_message(retval)));
500                 goto cleanup_creds;
501         }
502
503         while (!creds_ready && (i < maxtries)) {
504
505                 if ((retval = smb_krb5_get_credentials(context, ccache,
506                                                        creds.client,
507                                                        creds.server,
508                                                        impersonate_princ,
509                                                        &credsp))) {
510                         DEBUG(1,("ads_krb5_mk_req: smb_krb5_get_credentials failed for %s (%s)\n",
511                                 principal, error_message(retval)));
512                         goto cleanup_creds;
513                 }
514
515                 /* cope with ticket being in the future due to clock skew */
516                 if ((unsigned)credsp->times.starttime > time(NULL)) {
517                         time_t t = time(NULL);
518                         int time_offset =(int)((unsigned)credsp->times.starttime-t);
519                         DEBUG(4,("ads_krb5_mk_req: Advancing clock by %d seconds to cope with clock skew\n", time_offset));
520                         krb5_set_real_time(context, t + time_offset + 1, 0);
521                 }
522
523                 if (!ads_cleanup_expired_creds(context, ccache, credsp)) {
524                         creds_ready = true;
525                 }
526
527                 i++;
528         }
529
530         DEBUG(10,("ads_krb5_mk_req: Ticket (%s) in ccache (%s:%s) is valid until: (%s - %u)\n",
531                   principal, krb5_cc_get_type(context, ccache), krb5_cc_get_name(context, ccache),
532                   http_timestring(talloc_tos(), (unsigned)credsp->times.endtime), 
533                   (unsigned)credsp->times.endtime));
534
535         if (expire_time) {
536                 *expire_time = (time_t)credsp->times.endtime;
537         }
538
539         /* Allocate the auth_context. */
540         retval = setup_auth_context(context, auth_context);
541         if (retval) {
542                 DEBUG(1,("setup_auth_context failed (%s)\n",
543                         error_message(retval)));
544                 goto cleanup_creds;
545         }
546
547 #if defined(TKT_FLG_OK_AS_DELEGATE ) && defined(HAVE_KRB5_AUTH_CON_SETUSERUSERKEY) && defined(KRB5_AUTH_CONTEXT_USE_SUBKEY) && defined(HAVE_KRB5_AUTH_CON_SET_REQ_CKSUMTYPE)
548         {
549                 uint32_t gss_flags = 0;
550
551                 if( credsp->ticket_flags & TKT_FLG_OK_AS_DELEGATE ) {
552                         /* Fetch a forwarded TGT from the KDC so that we can hand off a 2nd ticket
553                          as part of the kerberos exchange. */
554
555                         DEBUG( 3, ("ads_krb5_mk_req: server marked as OK to delegate to, building forwardable TGT\n")  );
556
557                         retval = krb5_auth_con_setuseruserkey(context,
558                                         *auth_context,
559                                         &credsp->keyblock );
560                         if (retval) {
561                                 DEBUG(1,("krb5_auth_con_setuseruserkey failed (%s)\n",
562                                         error_message(retval)));
563                                 goto cleanup_creds;
564                         }
565
566                         /* Must use a subkey for forwarded tickets. */
567                         retval = krb5_auth_con_setflags(context,
568                                 *auth_context,
569                                 KRB5_AUTH_CONTEXT_USE_SUBKEY);
570                         if (retval) {
571                                 DEBUG(1,("krb5_auth_con_setflags failed (%s)\n",
572                                         error_message(retval)));
573                                 goto cleanup_creds;
574                         }
575
576                         retval = krb5_fwd_tgt_creds(context,/* Krb5 context [in] */
577                                 *auth_context,  /* Authentication context [in] */
578                                 discard_const_p(char, KRB5_TGS_NAME),  /* Ticket service name ("krbtgt") [in] */
579                                 credsp->client, /* Client principal for the tgt [in] */
580                                 credsp->server, /* Server principal for the tgt [in] */
581                                 ccache,         /* Credential cache to use for storage [in] */
582                                 1,              /* Turn on for "Forwardable ticket" [in] */
583                                 &in_data );     /* Resulting response [out] */
584
585                         if (retval) {
586                                 DEBUG( 3, ("krb5_fwd_tgt_creds failed (%s)\n",
587                                            error_message( retval ) ) );
588
589                                 /*
590                                  * This is not fatal. Delete the *auth_context and continue
591                                  * with krb5_mk_req_extended to get a non-forwardable ticket.
592                                  */
593
594                                 if (in_data.data) {
595                                         free( in_data.data );
596                                         in_data.data = NULL;
597                                         in_data.length = 0;
598                                 }
599                                 krb5_auth_con_free(context, *auth_context);
600                                 *auth_context = NULL;
601                                 retval = setup_auth_context(context, auth_context);
602                                 if (retval) {
603                                         DEBUG(1,("setup_auth_context failed (%s)\n",
604                                                 error_message(retval)));
605                                         goto cleanup_creds;
606                                 }
607                         } else {
608                                 /* We got a delegated ticket. */
609                                 gss_flags |= GSS_C_DELEG_FLAG;
610                         }
611                 }
612
613                 /* Frees and reallocates in_data into a GSS checksum blob. */
614                 retval = create_gss_checksum(&in_data, gss_flags);
615                 if (retval) {
616                         goto cleanup_data;
617                 }
618
619                 /* We always want GSS-checksum types. */
620                 retval = krb5_auth_con_set_req_cksumtype(context, *auth_context, GSSAPI_CHECKSUM );
621                 if (retval) {
622                         DEBUG(1,("krb5_auth_con_set_req_cksumtype failed (%s)\n",
623                                 error_message(retval)));
624                         goto cleanup_data;
625                 }
626         }
627 #endif
628
629         retval = krb5_mk_req_extended(context, auth_context, ap_req_options,
630                                       &in_data, credsp, outbuf);
631         if (retval) {
632                 DEBUG(1,("ads_krb5_mk_req: krb5_mk_req_extended failed (%s)\n", 
633                          error_message(retval)));
634         }
635
636 #if defined(TKT_FLG_OK_AS_DELEGATE ) && defined(HAVE_KRB5_AUTH_CON_SETUSERUSERKEY) && defined(KRB5_AUTH_CONTEXT_USE_SUBKEY) && defined(HAVE_KRB5_AUTH_CON_SET_REQ_CKSUMTYPE)
637 cleanup_data:
638 #endif
639
640         if (in_data.data) {
641                 free( in_data.data );
642                 in_data.length = 0;
643         }
644
645         krb5_free_creds(context, credsp);
646
647 cleanup_creds:
648         krb5_free_cred_contents(context, &creds);
649
650 cleanup_princ:
651         krb5_free_principal(context, server);
652         if (impersonate_princ) {
653                 krb5_free_principal(context, impersonate_princ);
654         }
655
656         return retval;
657 }
658
659 void kerberos_free_data_contents(krb5_context context, krb5_data *pdata)
660 {
661 #if defined(HAVE_KRB5_FREE_DATA_CONTENTS)
662         if (pdata->data) {
663                 krb5_free_data_contents(context, pdata);
664         }
665 #elif defined(HAVE_KRB5_DATA_FREE)
666         krb5_data_free(context, pdata);
667 #else
668         SAFE_FREE(pdata->data);
669 #endif
670 }
671
672 /*
673   get a kerberos5 ticket for the given service
674 */
675 int cli_krb5_get_ticket(TALLOC_CTX *mem_ctx,
676                         const char *principal, time_t time_offset,
677                         DATA_BLOB *ticket, DATA_BLOB *session_key_krb5,
678                         uint32_t extra_ap_opts, const char *ccname,
679                         time_t *tgs_expire,
680                         const char *impersonate_princ_s)
681
682 {
683         krb5_error_code retval;
684         krb5_data packet;
685         krb5_context context = NULL;
686         krb5_ccache ccdef = NULL;
687         krb5_auth_context auth_context = NULL;
688         krb5_enctype enc_types[] = {
689                 ENCTYPE_ARCFOUR_HMAC,
690                 ENCTYPE_DES_CBC_MD5,
691                 ENCTYPE_DES_CBC_CRC,
692                 ENCTYPE_NULL};
693
694         initialize_krb5_error_table();
695         retval = krb5_init_context(&context);
696         if (retval) {
697                 DEBUG(1, ("krb5_init_context failed (%s)\n",
698                          error_message(retval)));
699                 goto failed;
700         }
701
702         if (time_offset != 0) {
703                 krb5_set_real_time(context, time(NULL) + time_offset, 0);
704         }
705
706         if ((retval = krb5_cc_resolve(context, ccname ?
707                         ccname : krb5_cc_default_name(context), &ccdef))) {
708                 DEBUG(1, ("krb5_cc_default failed (%s)\n",
709                          error_message(retval)));
710                 goto failed;
711         }
712
713         if ((retval = krb5_set_default_tgs_ktypes(context, enc_types))) {
714                 DEBUG(1, ("krb5_set_default_tgs_ktypes failed (%s)\n",
715                          error_message(retval)));
716                 goto failed;
717         }
718
719         retval = ads_krb5_mk_req(context, &auth_context,
720                                 AP_OPTS_USE_SUBKEY | (krb5_flags)extra_ap_opts,
721                                 principal, ccdef, &packet,
722                                 tgs_expire, impersonate_princ_s);
723         if (retval) {
724                 goto failed;
725         }
726
727         get_krb5_smb_session_key(mem_ctx, context, auth_context,
728                                  session_key_krb5, false);
729
730         *ticket = data_blob_talloc(mem_ctx, packet.data, packet.length);
731
732         kerberos_free_data_contents(context, &packet);
733
734 failed:
735
736         if (context) {
737                 if (ccdef)
738                         krb5_cc_close(context, ccdef);
739                 if (auth_context)
740                         krb5_auth_con_free(context, auth_context);
741                 krb5_free_context(context);
742         }
743
744         return retval;
745 }
746
747 bool get_krb5_smb_session_key(TALLOC_CTX *mem_ctx,
748                               krb5_context context,
749                               krb5_auth_context auth_context,
750                               DATA_BLOB *session_key, bool remote)
751 {
752         krb5_keyblock *skey = NULL;
753         krb5_error_code err = 0;
754         bool ret = false;
755
756         if (remote) {
757                 err = krb5_auth_con_getremotesubkey(context,
758                                                     auth_context, &skey);
759         } else {
760                 err = krb5_auth_con_getlocalsubkey(context,
761                                                    auth_context, &skey);
762         }
763
764         if (err || skey == NULL) {
765                 DEBUG(10, ("KRB5 error getting session key %d\n", err));
766                 goto done;
767         }
768
769         DEBUG(10, ("Got KRB5 session key of length %d\n",
770                    (int)KRB5_KEY_LENGTH(skey)));
771
772         *session_key = data_blob_talloc(mem_ctx,
773                                          KRB5_KEY_DATA(skey),
774                                          KRB5_KEY_LENGTH(skey));
775         dump_data_pw("KRB5 Session Key:\n",
776                      session_key->data,
777                      session_key->length);
778
779         ret = true;
780
781 done:
782         if (skey) {
783                 krb5_free_keyblock(context, skey);
784         }
785
786         return ret;
787 }
788
789
790 #if defined(HAVE_KRB5_PRINCIPAL_GET_COMP_STRING) && !defined(HAVE_KRB5_PRINC_COMPONENT)
791  const krb5_data *krb5_princ_component(krb5_context context, krb5_principal principal, int i );
792
793  const krb5_data *krb5_princ_component(krb5_context context, krb5_principal principal, int i )
794 {
795         static krb5_data kdata;
796
797         kdata.data = discard_const_p(char, krb5_principal_get_comp_string(context, principal, i));
798         kdata.length = strlen((const char *)kdata.data);
799         return &kdata;
800 }
801 #endif
802
803 /* Prototypes */
804
805  krb5_error_code smb_krb5_renew_ticket(const char *ccache_string,       /* FILE:/tmp/krb5cc_0 */
806                                        const char *client_string,       /* gd@BER.SUSE.DE */
807                                        const char *service_string,      /* krbtgt/BER.SUSE.DE@BER.SUSE.DE */
808                                        time_t *expire_time)
809 {
810         krb5_error_code ret;
811         krb5_context context = NULL;
812         krb5_ccache ccache = NULL;
813         krb5_principal client = NULL;
814         krb5_creds creds, creds_in, *creds_out = NULL;
815
816         ZERO_STRUCT(creds);
817         ZERO_STRUCT(creds_in);
818
819         initialize_krb5_error_table();
820         ret = krb5_init_context(&context);
821         if (ret) {
822                 goto done;
823         }
824
825         if (!ccache_string) {
826                 ccache_string = krb5_cc_default_name(context);
827         }
828
829         if (!ccache_string) {
830                 ret = EINVAL;
831                 goto done;
832         }
833
834         DEBUG(10,("smb_krb5_renew_ticket: using %s as ccache\n", ccache_string));
835
836         /* FIXME: we should not fall back to defaults */
837         ret = krb5_cc_resolve(context, discard_const_p(char, ccache_string), &ccache);
838         if (ret) {
839                 goto done;
840         }
841
842         if (client_string) {
843                 ret = smb_krb5_parse_name(context, client_string, &client);
844                 if (ret) {
845                         goto done;
846                 }
847         } else {
848                 ret = krb5_cc_get_principal(context, ccache, &client);
849                 if (ret) {
850                         goto done;
851                 }
852         }
853
854         ret = krb5_get_renewed_creds(context, &creds, client, ccache, discard_const_p(char, service_string));
855         if (ret) {
856                 DEBUG(10,("smb_krb5_renew_ticket: krb5_get_kdc_cred failed: %s\n", error_message(ret)));
857                 goto done;
858         }
859
860         /* hm, doesn't that create a new one if the old one wasn't there? - Guenther */
861         ret = krb5_cc_initialize(context, ccache, client);
862         if (ret) {
863                 goto done;
864         }
865
866         ret = krb5_cc_store_cred(context, ccache, &creds);
867
868         if (expire_time) {
869                 *expire_time = (time_t) creds.times.endtime;
870         }
871
872 done:
873         krb5_free_cred_contents(context, &creds_in);
874
875         if (creds_out) {
876                 krb5_free_creds(context, creds_out);
877         } else {
878                 krb5_free_cred_contents(context, &creds);
879         }
880
881         if (client) {
882                 krb5_free_principal(context, client);
883         }
884         if (ccache) {
885                 krb5_cc_close(context, ccache);
886         }
887         if (context) {
888                 krb5_free_context(context);
889         }
890
891         return ret;
892 }
893
894  krb5_error_code smb_krb5_free_addresses(krb5_context context, smb_krb5_addresses *addr)
895 {
896         krb5_error_code ret = 0;
897         if (addr == NULL) {
898                 return ret;
899         }
900 #if defined(HAVE_MAGIC_IN_KRB5_ADDRESS) && defined(HAVE_ADDRTYPE_IN_KRB5_ADDRESS) /* MIT */
901         krb5_free_addresses(context, addr->addrs);
902 #elif defined(HAVE_ADDR_TYPE_IN_KRB5_ADDRESS) /* Heimdal */
903         ret = krb5_free_addresses(context, addr->addrs);
904         SAFE_FREE(addr->addrs);
905 #endif
906         SAFE_FREE(addr);
907         addr = NULL;
908         return ret;
909 }
910
911 #define MAX_NETBIOSNAME_LEN 16
912  krb5_error_code smb_krb5_gen_netbios_krb5_address(smb_krb5_addresses **kerb_addr,
913                                                    const char *netbios_name)
914 {
915         krb5_error_code ret = 0;
916         char buf[MAX_NETBIOSNAME_LEN];
917         int len;
918 #if defined(HAVE_MAGIC_IN_KRB5_ADDRESS) && defined(HAVE_ADDRTYPE_IN_KRB5_ADDRESS) /* MIT */
919         krb5_address **addrs = NULL;
920 #elif defined(HAVE_ADDR_TYPE_IN_KRB5_ADDRESS) /* Heimdal */
921         krb5_addresses *addrs = NULL;
922 #endif
923
924         *kerb_addr = (smb_krb5_addresses *)SMB_MALLOC(sizeof(smb_krb5_addresses));
925         if (*kerb_addr == NULL) {
926                 return ENOMEM;
927         }
928
929         /* temporarily duplicate put_name() code here to avoid dependency
930          * issues for a 5 lines function */
931         len = strlen(netbios_name);
932         memcpy(buf, netbios_name,
933                 (len < MAX_NETBIOSNAME_LEN) ? len : MAX_NETBIOSNAME_LEN - 1);
934         if (len < MAX_NETBIOSNAME_LEN - 1) {
935                 memset(buf + len, ' ', MAX_NETBIOSNAME_LEN - 1 - len);
936         }
937         buf[MAX_NETBIOSNAME_LEN - 1] = 0x20;
938
939 #if defined(HAVE_MAGIC_IN_KRB5_ADDRESS) && defined(HAVE_ADDRTYPE_IN_KRB5_ADDRESS) /* MIT */
940         {
941                 int num_addr = 2;
942
943                 addrs = (krb5_address **)SMB_MALLOC(sizeof(krb5_address *) * num_addr);
944                 if (addrs == NULL) {
945                         SAFE_FREE(*kerb_addr);
946                         return ENOMEM;
947                 }
948
949                 memset(addrs, 0, sizeof(krb5_address *) * num_addr);
950
951                 addrs[0] = (krb5_address *)SMB_MALLOC(sizeof(krb5_address));
952                 if (addrs[0] == NULL) {
953                         SAFE_FREE(addrs);
954                         SAFE_FREE(*kerb_addr);
955                         return ENOMEM;
956                 }
957
958                 addrs[0]->magic = KV5M_ADDRESS;
959                 addrs[0]->addrtype = KRB5_ADDR_NETBIOS;
960                 addrs[0]->length = MAX_NETBIOSNAME_LEN;
961                 addrs[0]->contents = (unsigned char *)SMB_MALLOC(addrs[0]->length);
962                 if (addrs[0]->contents == NULL) {
963                         SAFE_FREE(addrs[0]);
964                         SAFE_FREE(addrs);
965                         SAFE_FREE(*kerb_addr);
966                         return ENOMEM;
967                 }
968
969                 memcpy(addrs[0]->contents, buf, addrs[0]->length);
970
971                 addrs[1] = NULL;
972         }
973 #elif defined(HAVE_ADDR_TYPE_IN_KRB5_ADDRESS) /* Heimdal */
974         {
975                 addrs = (krb5_addresses *)SMB_MALLOC(sizeof(krb5_addresses));
976                 if (addrs == NULL) {
977                         SAFE_FREE(*kerb_addr);
978                         return ENOMEM;
979                 }
980
981                 memset(addrs, 0, sizeof(krb5_addresses));
982
983                 addrs->len = 1;
984                 addrs->val = (krb5_address *)SMB_MALLOC(sizeof(krb5_address));
985                 if (addrs->val == NULL) {
986                         SAFE_FREE(addrs);
987                         SAFE_FREE(kerb_addr);
988                         return ENOMEM;
989                 }
990
991                 addrs->val[0].addr_type = KRB5_ADDR_NETBIOS;
992                 addrs->val[0].address.length = MAX_NETBIOSNAME_LEN;
993                 addrs->val[0].address.data = (unsigned char *)SMB_MALLOC(addrs->val[0].address.length);
994                 if (addrs->val[0].address.data == NULL) {
995                         SAFE_FREE(addrs->val);
996                         SAFE_FREE(addrs);
997                         SAFE_FREE(*kerb_addr);
998                         return ENOMEM;
999                 }
1000
1001                 memcpy(addrs->val[0].address.data, buf, addrs->val[0].address.length);
1002         }
1003 #else
1004 #error UNKNOWN_KRB5_ADDRESS_FORMAT
1005 #endif
1006         (*kerb_addr)->addrs = addrs;
1007
1008         return ret;
1009 }
1010
1011  void smb_krb5_free_error(krb5_context context, krb5_error *krberror)
1012 {
1013 #ifdef HAVE_KRB5_FREE_ERROR_CONTENTS /* Heimdal */
1014         krb5_free_error_contents(context, krberror);
1015 #else /* MIT */
1016         krb5_free_error(context, krberror);
1017 #endif
1018 }
1019
1020  krb5_error_code handle_krberror_packet(krb5_context context,
1021                                         krb5_data *packet)
1022 {
1023         krb5_error_code ret;
1024         bool got_error_code = false;
1025
1026         DEBUG(10,("handle_krberror_packet: got error packet\n"));
1027
1028 #ifdef HAVE_E_DATA_POINTER_IN_KRB5_ERROR /* Heimdal */
1029         {
1030                 krb5_error krberror;
1031
1032                 if ((ret = krb5_rd_error(context, packet, &krberror))) {
1033                         DEBUG(10,("handle_krberror_packet: krb5_rd_error failed with: %s\n", 
1034                                 error_message(ret)));
1035                         return ret;
1036                 }
1037
1038                 if (krberror.e_data == NULL || krberror.e_data->data == NULL) {
1039                         ret = (krb5_error_code) krberror.error_code;
1040                         got_error_code = true;
1041                 }
1042
1043                 smb_krb5_free_error(context, &krberror);
1044         }
1045 #else /* MIT */
1046         {
1047                 krb5_error *krberror;
1048
1049                 if ((ret = krb5_rd_error(context, packet, &krberror))) {
1050                         DEBUG(10,("handle_krberror_packet: krb5_rd_error failed with: %s\n", 
1051                                 error_message(ret)));
1052                         return ret;
1053                 }
1054
1055                 if (krberror->e_data.data == NULL) {
1056 #if defined(ERROR_TABLE_BASE_krb5)
1057                         ret = ERROR_TABLE_BASE_krb5 + (krb5_error_code) krberror->error;
1058 #else
1059                         ret = (krb5_error_code)krberror->error;
1060 #endif
1061                         got_error_code = true;
1062                 }
1063                 smb_krb5_free_error(context, krberror);
1064         }
1065 #endif
1066         if (got_error_code) {
1067                 DEBUG(5,("handle_krberror_packet: got KERBERR from kpasswd: %s (%d)\n", 
1068                         error_message(ret), ret));
1069         }
1070         return ret;
1071 }
1072
1073 krb5_error_code smb_krb5_get_init_creds_opt_alloc(krb5_context context,
1074                                             krb5_get_init_creds_opt **opt)
1075 {
1076         /* Heimdal or modern MIT version */
1077         return krb5_get_init_creds_opt_alloc(context, opt);
1078 }
1079
1080 void smb_krb5_get_init_creds_opt_free(krb5_context context,
1081                                 krb5_get_init_creds_opt *opt)
1082 {
1083         /* Modern MIT or Heimdal version */
1084         krb5_get_init_creds_opt_free(context, opt);
1085 }
1086
1087 krb5_enctype smb_get_enctype_from_kt_entry(krb5_keytab_entry *kt_entry)
1088 {
1089         return KRB5_KEY_TYPE(KRB5_KT_KEY(kt_entry));
1090 }
1091
1092 krb5_error_code smb_krb5_kt_free_entry(krb5_context context,
1093                                         krb5_keytab_entry *kt_entry)
1094 {
1095 /* Try krb5_free_keytab_entry_contents first, since
1096  * MIT Kerberos >= 1.7 has both krb5_free_keytab_entry_contents and
1097  * krb5_kt_free_entry but only has a prototype for the first, while the
1098  * second is considered private.
1099  */
1100 #if defined(HAVE_KRB5_FREE_KEYTAB_ENTRY_CONTENTS)
1101         return krb5_free_keytab_entry_contents(context, kt_entry);
1102 #elif defined(HAVE_KRB5_KT_FREE_ENTRY)
1103         return krb5_kt_free_entry(context, kt_entry);
1104 #else
1105 #error UNKNOWN_KT_FREE_FUNCTION
1106 #endif
1107 }
1108
1109
1110 /* caller needs to free etype_s */
1111 krb5_error_code smb_krb5_enctype_to_string(krb5_context context,
1112                                            krb5_enctype enctype,
1113                                            char **etype_s)
1114 {
1115 #ifdef HAVE_KRB5_ENCTYPE_TO_STRING_WITH_KRB5_CONTEXT_ARG
1116         return krb5_enctype_to_string(context, enctype, etype_s); /* Heimdal */
1117 #elif defined(HAVE_KRB5_ENCTYPE_TO_STRING_WITH_SIZE_T_ARG)
1118         char buf[256];
1119         krb5_error_code ret = krb5_enctype_to_string(enctype, buf, 256); /* MIT */
1120         if (ret) {
1121                 return ret;
1122         }
1123         *etype_s = SMB_STRDUP(buf);
1124         if (!*etype_s) {
1125                 return ENOMEM;
1126         }
1127         return ret;
1128 #else
1129 #error UNKNOWN_KRB5_ENCTYPE_TO_STRING_FUNCTION
1130 #endif
1131 }
1132
1133 /**********************************************************************
1134  * Open a krb5 keytab with flags, handles readonly or readwrite access and
1135  * allows to process non-default keytab names.
1136  * @param context krb5_context
1137  * @param keytab_name_req string
1138  * @param write_access bool if writable keytab is required
1139  * @param krb5_keytab pointer to krb5_keytab (close with krb5_kt_close())
1140  * @return krb5_error_code
1141 **********************************************************************/
1142
1143 /* This MAX_NAME_LEN is a constant defined in krb5.h */
1144 #ifndef MAX_KEYTAB_NAME_LEN
1145 #define MAX_KEYTAB_NAME_LEN 1100
1146 #endif
1147
1148 krb5_error_code smb_krb5_open_keytab(krb5_context context,
1149                                      const char *keytab_name_req,
1150                                      bool write_access,
1151                                      krb5_keytab *keytab)
1152 {
1153         krb5_error_code ret = 0;
1154         TALLOC_CTX *mem_ctx;
1155         char keytab_string[MAX_KEYTAB_NAME_LEN];
1156         char *kt_str = NULL;
1157         bool found_valid_name = false;
1158         const char *pragma = "FILE";
1159         const char *tmp = NULL;
1160
1161         if (!write_access && !keytab_name_req) {
1162                 /* caller just wants to read the default keytab readonly, so be it */
1163                 return krb5_kt_default(context, keytab);
1164         }
1165
1166         mem_ctx = talloc_init("smb_krb5_open_keytab");
1167         if (!mem_ctx) {
1168                 return ENOMEM;
1169         }
1170
1171 #ifdef HAVE_WRFILE_KEYTAB
1172         if (write_access) {
1173                 pragma = "WRFILE";
1174         }
1175 #endif
1176
1177         if (keytab_name_req) {
1178
1179                 if (strlen(keytab_name_req) > MAX_KEYTAB_NAME_LEN) {
1180                         ret = KRB5_CONFIG_NOTENUFSPACE;
1181                         goto out;
1182                 }
1183
1184                 if ((strncmp(keytab_name_req, "WRFILE:/", 8) == 0) ||
1185                     (strncmp(keytab_name_req, "FILE:/", 6) == 0)) {
1186                         tmp = keytab_name_req;
1187                         goto resolve;
1188                 }
1189
1190                 if (keytab_name_req[0] != '/') {
1191                         ret = KRB5_KT_BADNAME;
1192                         goto out;
1193                 }
1194
1195                 tmp = talloc_asprintf(mem_ctx, "%s:%s", pragma, keytab_name_req);
1196                 if (!tmp) {
1197                         ret = ENOMEM;
1198                         goto out;
1199                 }
1200
1201                 goto resolve;
1202         }
1203
1204         /* we need to handle more complex keytab_strings, like:
1205          * "ANY:FILE:/etc/krb5.keytab,krb4:/etc/srvtab" */
1206
1207         ret = krb5_kt_default_name(context, &keytab_string[0], MAX_KEYTAB_NAME_LEN - 2);
1208         if (ret) {
1209                 goto out;
1210         }
1211
1212         DEBUG(10,("smb_krb5_open_keytab: krb5_kt_default_name returned %s\n", keytab_string));
1213
1214         tmp = talloc_strdup(mem_ctx, keytab_string);
1215         if (!tmp) {
1216                 ret = ENOMEM;
1217                 goto out;
1218         }
1219
1220         if (strncmp(tmp, "ANY:", 4) == 0) {
1221                 tmp += 4;
1222         }
1223
1224         memset(&keytab_string, '\0', sizeof(keytab_string));
1225
1226         while (next_token_talloc(mem_ctx, &tmp, &kt_str, ",")) {
1227                 if (strncmp(kt_str, "WRFILE:", 7) == 0) {
1228                         found_valid_name = true;
1229                         tmp = kt_str;
1230                         tmp += 7;
1231                 }
1232
1233                 if (strncmp(kt_str, "FILE:", 5) == 0) {
1234                         found_valid_name = true;
1235                         tmp = kt_str;
1236                         tmp += 5;
1237                 }
1238
1239                 if (tmp[0] == '/') {
1240                         /* Treat as a FILE: keytab definition. */
1241                         found_valid_name = true;
1242                 }
1243
1244                 if (found_valid_name) {
1245                         if (tmp[0] != '/') {
1246                                 ret = KRB5_KT_BADNAME;
1247                                 goto out;
1248                         }
1249
1250                         tmp = talloc_asprintf(mem_ctx, "%s:%s", pragma, tmp);
1251                         if (!tmp) {
1252                                 ret = ENOMEM;
1253                                 goto out;
1254                         }
1255                         break;
1256                 }
1257         }
1258
1259         if (!found_valid_name) {
1260                 ret = KRB5_KT_UNKNOWN_TYPE;
1261                 goto out;
1262         }
1263
1264  resolve:
1265         DEBUG(10,("smb_krb5_open_keytab: resolving: %s\n", tmp));
1266         ret = krb5_kt_resolve(context, tmp, keytab);
1267
1268  out:
1269         TALLOC_FREE(mem_ctx);
1270         return ret;
1271 }
1272
1273 krb5_error_code smb_krb5_keytab_name(TALLOC_CTX *mem_ctx,
1274                                      krb5_context context,
1275                                      krb5_keytab keytab,
1276                                      const char **keytab_name)
1277 {
1278         char keytab_string[MAX_KEYTAB_NAME_LEN];
1279         krb5_error_code ret = 0;
1280
1281         ret = krb5_kt_get_name(context, keytab,
1282                                keytab_string, MAX_KEYTAB_NAME_LEN - 2);
1283         if (ret) {
1284                 return ret;
1285         }
1286
1287         *keytab_name = talloc_strdup(mem_ctx, keytab_string);
1288         if (!*keytab_name) {
1289                 return ENOMEM;
1290         }
1291
1292         return ret;
1293 }
1294
1295 #if defined(HAVE_KRB5_GET_CREDS_OPT_SET_IMPERSONATE) && \
1296     defined(HAVE_KRB5_GET_CREDS_OPT_ALLOC) && \
1297     defined(HAVE_KRB5_GET_CREDS)
1298 static krb5_error_code smb_krb5_get_credentials_for_user_opt(krb5_context context,
1299                                                              krb5_ccache ccache,
1300                                                              krb5_principal me,
1301                                                              krb5_principal server,
1302                                                              krb5_principal impersonate_princ,
1303                                                              krb5_creds **out_creds)
1304 {
1305         krb5_error_code ret;
1306         krb5_get_creds_opt opt;
1307
1308         ret = krb5_get_creds_opt_alloc(context, &opt);
1309         if (ret) {
1310                 goto done;
1311         }
1312         krb5_get_creds_opt_add_options(context, opt, KRB5_GC_FORWARDABLE);
1313
1314         if (impersonate_princ) {
1315                 ret = krb5_get_creds_opt_set_impersonate(context, opt,
1316                                                          impersonate_princ);
1317                 if (ret) {
1318                         goto done;
1319                 }
1320         }
1321
1322         ret = krb5_get_creds(context, opt, ccache, server, out_creds);
1323         if (ret) {
1324                 goto done;
1325         }
1326
1327  done:
1328         if (opt) {
1329                 krb5_get_creds_opt_free(context, opt);
1330         }
1331         return ret;
1332 }
1333 #endif /* HAVE_KRB5_GET_CREDS_OPT_SET_IMPERSONATE */
1334
1335 #ifdef HAVE_KRB5_GET_CREDENTIALS_FOR_USER
1336 static krb5_error_code smb_krb5_get_credentials_for_user(krb5_context context,
1337                                                          krb5_ccache ccache,
1338                                                          krb5_principal me,
1339                                                          krb5_principal server,
1340                                                          krb5_principal impersonate_princ,
1341                                                          krb5_creds **out_creds)
1342 {
1343         krb5_error_code ret;
1344         krb5_creds in_creds;
1345
1346 #if !HAVE_DECL_KRB5_GET_CREDENTIALS_FOR_USER
1347 krb5_error_code KRB5_CALLCONV
1348 krb5_get_credentials_for_user(krb5_context context, krb5_flags options,
1349                               krb5_ccache ccache, krb5_creds *in_creds,
1350                               krb5_data *subject_cert,
1351                               krb5_creds **out_creds);
1352 #endif /* !HAVE_DECL_KRB5_GET_CREDENTIALS_FOR_USER */
1353
1354         ZERO_STRUCT(in_creds);
1355
1356         if (impersonate_princ) {
1357
1358                 in_creds.server = me;
1359                 in_creds.client = impersonate_princ;
1360
1361                 ret = krb5_get_credentials_for_user(context,
1362                                                     0, /* krb5_flags options */
1363                                                     ccache,
1364                                                     &in_creds,
1365                                                     NULL, /* krb5_data *subject_cert */
1366                                                     out_creds);
1367         } else {
1368                 in_creds.client = me;
1369                 in_creds.server = server;
1370
1371                 ret = krb5_get_credentials(context, 0, ccache,
1372                                            &in_creds, out_creds);
1373         }
1374
1375         return ret;
1376 }
1377 #endif /* HAVE_KRB5_GET_CREDENTIALS_FOR_USER */
1378
1379 /*
1380  * smb_krb5_get_credentials
1381  *
1382  * @brief Get krb5 credentials for a server
1383  *
1384  * @param[in] context           An initialized krb5_context
1385  * @param[in] ccache            An initialized krb5_ccache
1386  * @param[in] me                The krb5_principal of the caller
1387  * @param[in] server            The krb5_principal of the requested service
1388  * @param[in] impersonate_princ The krb5_principal of a user to impersonate as (optional)
1389  * @param[out] out_creds        The returned krb5_creds structure
1390  * @return krb5_error_code
1391  *
1392  */
1393 krb5_error_code smb_krb5_get_credentials(krb5_context context,
1394                                          krb5_ccache ccache,
1395                                          krb5_principal me,
1396                                          krb5_principal server,
1397                                          krb5_principal impersonate_princ,
1398                                          krb5_creds **out_creds)
1399 {
1400         krb5_error_code ret;
1401         krb5_creds *creds = NULL;
1402
1403         *out_creds = NULL;
1404
1405         if (impersonate_princ) {
1406 #ifdef HAVE_KRB5_GET_CREDS_OPT_SET_IMPERSONATE /* Heimdal */
1407                 ret = smb_krb5_get_credentials_for_user_opt(context, ccache, me, server, impersonate_princ, &creds);
1408 #elif defined(HAVE_KRB5_GET_CREDENTIALS_FOR_USER) /* MIT */
1409                 ret = smb_krb5_get_credentials_for_user(context, ccache, me, server, impersonate_princ, &creds);
1410 #else
1411                 ret = ENOTSUP;
1412 #endif
1413         } else {
1414                 krb5_creds in_creds;
1415
1416                 ZERO_STRUCT(in_creds);
1417
1418                 in_creds.client = me;
1419                 in_creds.server = server;
1420
1421                 ret = krb5_get_credentials(context, 0, ccache,
1422                                            &in_creds, &creds);
1423         }
1424         if (ret) {
1425                 goto done;
1426         }
1427
1428         if (out_creds) {
1429                 *out_creds = creds;
1430         }
1431
1432  done:
1433         if (creds && ret) {
1434                 krb5_free_creds(context, creds);
1435         }
1436
1437         return ret;
1438 }
1439
1440 /*
1441  * smb_krb5_get_creds
1442  *
1443  * @brief Get krb5 credentials for a server
1444  *
1445  * @param[in] server_s          The string name of the service
1446  * @param[in] time_offset       The offset to the KDCs time in seconds (optional)
1447  * @param[in] cc                The krb5 credential cache string name (optional)
1448  * @param[in] impersonate_princ_s The string principal name to impersonate (optional)
1449  * @param[out] creds_p          The returned krb5_creds structure
1450  * @return krb5_error_code
1451  *
1452  */
1453 krb5_error_code smb_krb5_get_creds(const char *server_s,
1454                                    time_t time_offset,
1455                                    const char *cc,
1456                                    const char *impersonate_princ_s,
1457                                    krb5_creds **creds_p)
1458 {
1459         krb5_error_code ret;
1460         krb5_context context = NULL;
1461         krb5_principal me = NULL;
1462         krb5_principal server = NULL;
1463         krb5_principal impersonate_princ = NULL;
1464         krb5_creds *creds = NULL;
1465         krb5_ccache ccache = NULL;
1466
1467         *creds_p = NULL;
1468
1469         initialize_krb5_error_table();
1470         ret = krb5_init_context(&context);
1471         if (ret) {
1472                 goto done;
1473         }
1474
1475         if (time_offset != 0) {
1476                 krb5_set_real_time(context, time(NULL) + time_offset, 0);
1477         }
1478
1479         ret = krb5_cc_resolve(context, cc ? cc :
1480                 krb5_cc_default_name(context), &ccache);
1481         if (ret) {
1482                 goto done;
1483         }
1484
1485         ret = krb5_cc_get_principal(context, ccache, &me);
1486         if (ret) {
1487                 goto done;
1488         }
1489
1490         ret = smb_krb5_parse_name(context, server_s, &server);
1491         if (ret) {
1492                 goto done;
1493         }
1494
1495         if (impersonate_princ_s) {
1496                 ret = smb_krb5_parse_name(context, impersonate_princ_s,
1497                                           &impersonate_princ);
1498                 if (ret) {
1499                         goto done;
1500                 }
1501         }
1502
1503         ret = smb_krb5_get_credentials(context, ccache,
1504                                        me, server, impersonate_princ,
1505                                        &creds);
1506         if (ret) {
1507                 goto done;
1508         }
1509
1510         ret = krb5_cc_store_cred(context, ccache, creds);
1511         if (ret) {
1512                 goto done;
1513         }
1514
1515         if (creds_p) {
1516                 *creds_p = creds;
1517         }
1518
1519         DEBUG(1,("smb_krb5_get_creds: got ticket for %s\n",
1520                 server_s));
1521
1522         if (impersonate_princ_s) {
1523                 char *client = NULL;
1524
1525                 ret = smb_krb5_unparse_name(talloc_tos(), context, creds->client, &client);
1526                 if (ret) {
1527                         goto done;
1528                 }
1529                 DEBUGADD(1,("smb_krb5_get_creds: using S4U2SELF impersonation as %s\n",
1530                         client));
1531                 TALLOC_FREE(client);
1532         }
1533
1534  done:
1535         if (!context) {
1536                 return ret;
1537         }
1538
1539         if (creds && ret) {
1540                 krb5_free_creds(context, creds);
1541         }
1542         if (server) {
1543                 krb5_free_principal(context, server);
1544         }
1545         if (me) {
1546                 krb5_free_principal(context, me);
1547         }
1548         if (impersonate_princ) {
1549                 krb5_free_principal(context, impersonate_princ);
1550         }
1551         if (ccache) {
1552                 krb5_cc_close(context, ccache);
1553         }
1554         krb5_free_context(context);
1555
1556         return ret;
1557 }
1558
1559 /*
1560  * smb_krb5_principal_get_realm
1561  *
1562  * @brief Get realm of a principal
1563  *
1564  * @param[in] context           The krb5_context
1565  * @param[in] principal         The principal
1566  * @return pointer to the realm
1567  *
1568  */
1569
1570 char *smb_krb5_principal_get_realm(krb5_context context,
1571                                    krb5_principal principal)
1572 {
1573 #ifdef HAVE_KRB5_PRINCIPAL_GET_REALM /* Heimdal */
1574         return discard_const_p(char, krb5_principal_get_realm(context, principal));
1575 #elif defined(krb5_princ_realm) /* MIT */
1576         krb5_data *realm;
1577         realm = krb5_princ_realm(context, principal);
1578         return discard_const_p(char, realm->data);
1579 #else
1580         return NULL;
1581 #endif
1582 }
1583
1584 /************************************************************************
1585  Routine to get the default realm from the kerberos credentials cache.
1586  Caller must free if the return value is not NULL.
1587 ************************************************************************/
1588
1589 static char *smb_krb5_get_default_realm_from_ccache(TALLOC_CTX *mem_ctx)
1590 {
1591         char *realm = NULL;
1592         krb5_context ctx = NULL;
1593         krb5_ccache cc = NULL;
1594         krb5_principal princ = NULL;
1595
1596         initialize_krb5_error_table();
1597         if (krb5_init_context(&ctx)) {
1598                 return NULL;
1599         }
1600
1601         DEBUG(5,("kerberos_get_default_realm_from_ccache: "
1602                 "Trying to read krb5 cache: %s\n",
1603                 krb5_cc_default_name(ctx)));
1604         if (krb5_cc_default(ctx, &cc)) {
1605                 DEBUG(0,("kerberos_get_default_realm_from_ccache: "
1606                         "failed to read default cache\n"));
1607                 goto out;
1608         }
1609         if (krb5_cc_get_principal(ctx, cc, &princ)) {
1610                 DEBUG(0,("kerberos_get_default_realm_from_ccache: "
1611                         "failed to get default principal\n"));
1612                 goto out;
1613         }
1614
1615 #if defined(HAVE_KRB5_PRINCIPAL_GET_REALM)
1616         realm = talloc_strdup(mem_ctx, krb5_principal_get_realm(ctx, princ));
1617 #elif defined(HAVE_KRB5_PRINC_REALM)
1618         {
1619                 krb5_data *realm_data = krb5_princ_realm(ctx, princ);
1620                 realm = talloc_strndup(mem_ctx, realm_data->data, realm_data->length);
1621         }
1622 #endif
1623
1624   out:
1625
1626         if (ctx) {
1627                 if (princ) {
1628                         krb5_free_principal(ctx, princ);
1629                 }
1630                 if (cc) {
1631                         krb5_cc_close(ctx, cc);
1632                 }
1633                 krb5_free_context(ctx);
1634         }
1635
1636         return realm;
1637 }
1638
1639 /************************************************************************
1640  Routine to get the realm from a given DNS name.
1641 ************************************************************************/
1642
1643 static char *smb_krb5_get_realm_from_hostname(TALLOC_CTX *mem_ctx,
1644                                                 const char *hostname)
1645 {
1646 #if defined(HAVE_KRB5_REALM_TYPE)
1647         /* Heimdal. */
1648         krb5_realm *realm_list = NULL;
1649 #else
1650         /* MIT */
1651         char **realm_list = NULL;
1652 #endif
1653         char *realm = NULL;
1654         krb5_error_code kerr;
1655         krb5_context ctx = NULL;
1656
1657         initialize_krb5_error_table();
1658         if (krb5_init_context(&ctx)) {
1659                 return NULL;
1660         }
1661
1662         kerr = krb5_get_host_realm(ctx, hostname, &realm_list);
1663         if (kerr != 0) {
1664                 DEBUG(3,("kerberos_get_realm_from_hostname %s: "
1665                         "failed %s\n",
1666                         hostname ? hostname : "(NULL)",
1667                         error_message(kerr) ));
1668                 goto out;
1669         }
1670
1671         if (realm_list && realm_list[0]) {
1672                 realm = talloc_strdup(mem_ctx, realm_list[0]);
1673         }
1674
1675   out:
1676
1677         if (ctx) {
1678                 if (realm_list) {
1679                         krb5_free_host_realm(ctx, realm_list);
1680                         realm_list = NULL;
1681                 }
1682                 krb5_free_context(ctx);
1683                 ctx = NULL;
1684         }
1685         return realm;
1686 }
1687
1688 char *kerberos_get_principal_from_service_hostname(TALLOC_CTX *mem_ctx,
1689                                                    const char *service,
1690                                                    const char *remote_name,
1691                                                    const char *default_realm)
1692 {
1693         char *realm = NULL;
1694         char *host = NULL;
1695         char *principal;
1696         host = strchr_m(remote_name, '.');
1697         if (host) {
1698                 /* DNS name. */
1699                 realm = smb_krb5_get_realm_from_hostname(talloc_tos(),
1700                                                          remote_name);
1701         } else {
1702                 /* NetBIOS name - use our realm. */
1703                 realm = smb_krb5_get_default_realm_from_ccache(talloc_tos());
1704         }
1705
1706         if (realm == NULL || *realm == '\0') {
1707                 realm = talloc_strdup(talloc_tos(), default_realm);
1708                 if (!realm) {
1709                         return NULL;
1710                 }
1711                 DEBUG(3,("kerberos_get_principal_from_service_hostname: "
1712                          "cannot get realm from, "
1713                          "desthost %s or default ccache. Using default "
1714                          "smb.conf realm %s\n",
1715                          remote_name,
1716                          realm));
1717         }
1718
1719         principal = talloc_asprintf(mem_ctx,
1720                                     "%s/%s@%s",
1721                                     service, remote_name,
1722                                     realm);
1723         TALLOC_FREE(realm);
1724         return principal;
1725 }
1726
1727 char *smb_get_krb5_error_message(krb5_context context,
1728                                  krb5_error_code code,
1729                                  TALLOC_CTX *mem_ctx)
1730 {
1731         char *ret;
1732
1733 #if defined(HAVE_KRB5_GET_ERROR_MESSAGE) && defined(HAVE_KRB5_FREE_ERROR_MESSAGE)
1734         const char *context_error = krb5_get_error_message(context, code);
1735         if (context_error) {
1736                 ret = talloc_asprintf(mem_ctx, "%s: %s",
1737                                         error_message(code), context_error);
1738                 krb5_free_error_message(context, context_error);
1739                 return ret;
1740         }
1741 #endif
1742         ret = talloc_strdup(mem_ctx, error_message(code));
1743         return ret;
1744 }
1745
1746 #else /* HAVE_KRB5 */
1747  /* this saves a few linking headaches */
1748  int cli_krb5_get_ticket(TALLOC_CTX *mem_ctx,
1749                         const char *principal, time_t time_offset,
1750                         DATA_BLOB *ticket, DATA_BLOB *session_key_krb5,
1751                         uint32_t extra_ap_opts,
1752                         const char *ccname, time_t *tgs_expire,
1753                         const char *impersonate_princ_s)
1754 {
1755          DEBUG(0,("NO KERBEROS SUPPORT\n"));
1756          return 1;
1757 }
1758
1759 #endif /* HAVE_KRB5 */