2 * -----------------------------------------------------------------------------
4 * Author: Markus Moeller (markus_moeller at compuserve.com)
6 * Copyright (C) 2007 Markus Moeller. All rights reserved.
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
22 * -----------------------------------------------------------------------------
25 * Hosted at http://sourceforge.net/projects/squidkerbauth
32 #if HAVE_KRB5 && HAVE_GSSAPI
39 #endif /* HAVE_PROFILE_H */
41 #if HAVE_BROKEN_SOLARIS_KRB5_H
42 #if defined(__cplusplus)
43 #define KRB5INT_BEGIN_DECLS extern "C" {
44 #define KRB5INT_END_DECLS
49 #elif HAVE_ET_COM_ERR_H
50 #include <et/com_err.h>
51 #endif /* HAVE_COM_ERR_H */
54 #endif /* HAVE_COM_ERR_H */
56 #if HAVE_GSSAPI_GSSAPI_H
57 #include <gssapi/gssapi.h>
60 #endif /* HAVE_GSSAPI_H */
61 #if HAVE_GSSAPI_GSSAPI_EXT_H
62 #include <gssapi/gssapi_ext.h>
63 #endif /* HAVE_GSSAPI_GSSAPI_EXT_H */
64 #if HAVE_GSSAPI_GSSAPI_KRB5_H
65 #include <gssapi/gssapi_krb5.h>
66 #endif /* HAVE_GSSAPI_GSSAPI_KRB5_H */
67 #if HAVE_GSSAPI_GSSAPI_GENERIC_H
68 #include <gssapi/gssapi_generic.h>
69 #endif /* HAVE_GSSAPI_GSSAPI_GENERIC_H */
71 #ifndef gss_nt_service_name
72 #define gss_nt_service_name GSS_C_NT_HOSTBASED_SERVICE
75 #if !HAVE_ERROR_MESSAGE && HAVE_KRB5_GET_ERR_TEXT
76 #define error_message(code) krb5_get_err_text(kparam.context,code)
77 #elif !HAVE_ERROR_MESSAGE && HAVE_KRB5_GET_ERROR_MESSAGE
78 #define error_message(code) krb5_get_error_message(kparam.context,code)
79 #elif !HAVE_ERROR_MESSAGE
80 static char err_code
[17];
81 const char *KRB5_CALLCONV
82 error_message(long code
) {
83 snprintf(err_code
,16,"%ld",code
);
88 #ifndef gss_mech_spnego
89 static gss_OID_desc _gss_mech_spnego
=
90 { 6, (void *) "\x2b\x06\x01\x05\x05\x02" };
91 gss_OID gss_mech_spnego
= &_gss_mech_spnego
;
95 #include <ibm_svc/krb5_svc.h>
96 const char *KRB5_CALLCONV
error_message(long code
) {
98 krb5_svc_get_msg(code
, &msg
);
104 * Kerberos context and cache structure
105 * Caches authentication details to reduce
106 * number of authentication requests to kdc
108 static struct kstruct
{
109 krb5_context context
;
115 * krb5_create_cache creates a Kerberos file credential cache or a memory
116 * credential cache if supported. The initial key for the principal
117 * principal_name is extracted from the keytab keytab_filename.
119 * If keytab_filename is NULL the default will be used.
120 * If principal_name is NULL the first working entry of the keytab will be used.
122 int krb5_create_cache(char *keytab_filename
, char *principal_name
);
125 * krb5_cleanup clears used Keberos memory
127 void krb5_cleanup(void);
130 * check_gss_err checks for gssapi error codes, extracts the error message
133 int check_gss_err(OM_uint32 major_status
, OM_uint32 minor_status
,
134 const char *function
);
136 int check_gss_err(OM_uint32 major_status
, OM_uint32 minor_status
,
137 const char *function
) {
138 if (GSS_ERROR(major_status
)) {
139 OM_uint32 maj_stat
, min_stat
;
140 OM_uint32 msg_ctx
= 0;
141 gss_buffer_desc status_string
;
148 /* convert major status code (GSS-API error) to text */
149 maj_stat
= gss_display_status(&min_stat
, major_status
,
150 GSS_C_GSS_CODE
, GSS_C_NULL_OID
, &msg_ctx
, &status_string
);
151 if (maj_stat
== GSS_S_COMPLETE
) {
152 if (sizeof(buf
) > len
+ status_string
.length
+ 1) {
153 memcpy(buf
+ len
, status_string
.value
,
154 status_string
.length
);
155 len
+= status_string
.length
;
157 gss_release_buffer(&min_stat
, &status_string
);
160 gss_release_buffer(&min_stat
, &status_string
);
162 if (sizeof(buf
) > len
+ 2) {
163 strcpy(buf
+ len
, ". ");
168 /* convert minor status code (underlying routine error) to text */
169 maj_stat
= gss_display_status(&min_stat
, minor_status
,
170 GSS_C_MECH_CODE
, GSS_C_NULL_OID
, &msg_ctx
, &status_string
);
171 if (maj_stat
== GSS_S_COMPLETE
) {
172 if (sizeof(buf
) > len
+ status_string
.length
) {
173 memcpy(buf
+ len
, status_string
.value
,
174 status_string
.length
);
175 len
+= status_string
.length
;
177 gss_release_buffer(&min_stat
, &status_string
);
180 gss_release_buffer(&min_stat
, &status_string
);
182 debugs(11, 5, HERE
<< function
<< "failed: " << buf
);
188 void krb5_cleanup() {
189 debugs(11, 5, HERE
<< "Cleanup kerberos context");
190 if (kparam
.context
) {
192 krb5_cc_destroy(kparam
.context
, kparam
.cc
);
194 krb5_free_context(kparam
.context
);
195 kparam
.context
= NULL
;
199 int krb5_create_cache(char *kf
, char *pn
) {
201 #define KT_PATH_MAX 256
202 #define MAX_RENEW_TIME "365d"
203 #define DEFAULT_SKEW (krb5_deltat) 600
205 static char *keytab_filename
= NULL
, *principal_name
= NULL
;
206 static krb5_keytab keytab
= 0;
207 static krb5_keytab_entry entry
;
208 static krb5_kt_cursor cursor
;
209 static krb5_creds
*creds
= NULL
;
210 #if HAVE_HEIMDAL_KERBEROS
211 static krb5_creds creds2
;
213 static krb5_principal principal
= NULL
;
214 static krb5_deltat skew
;
216 krb5_get_init_creds_opt options
;
217 krb5_error_code code
= 0;
219 #if HAVE_PROFILE_H && HAVE_KRB5_GET_PROFILE && HAVE_PROFILE_GET_INTEGER && HAVE_PROFILE_RELEASE
222 #if HAVE_HEIMDAL_KERBEROS
223 krb5_kdc_flags flags
;
224 krb5_realm
*client_realm
;
230 * Check if credentials need to be renewed
233 (creds
->times
.endtime
- time(0) > skew
) &&
234 (creds
->times
.renew_till
- time(0) > 2 * skew
)) {
235 if (creds
->times
.endtime
- time(0) < 2 * skew
) {
236 #if !HAVE_HEIMDAL_KERBEROS
239 krb5_get_renewed_creds(kparam
.context
, creds
, principal
,
244 flags
.b
.renewable
= flags
.b
.renew
= 1;
247 krb5_cc_get_principal(kparam
.context
, kparam
.cc
,
252 "Error while getting principal from credential cache : "
253 << error_message(code
));
256 client_realm
= krb5_princ_realm(kparam
.context
, creds2
.client
);
258 krb5_make_principal(kparam
.context
, &creds2
.server
,
259 *client_realm
, KRB5_TGS_NAME
, *client_realm
, NULL
);
262 HERE
<< "Error while getting krbtgt principal : " <<
263 error_message(code
));
267 krb5_get_kdc_cred(kparam
.context
, kparam
.cc
, flags
, NULL
,
268 NULL
, &creds2
, &creds
);
269 krb5_free_creds(kparam
.context
, &creds2
);
272 if (code
== KRB5KRB_AP_ERR_TKT_EXPIRED
) {
273 krb5_free_creds(kparam
.context
, creds
);
275 /* this can happen because of clock skew */
279 HERE
<< "Error while get credentials : " <<
280 error_message(code
));
286 if (!kparam
.context
) {
287 code
= krb5_init_context(&kparam
.context
);
290 HERE
<< "Error while initialising Kerberos library : "
291 << error_message(code
));
295 #if HAVE_PROFILE_H && HAVE_KRB5_GET_PROFILE && HAVE_PROFILE_GET_INTEGER && HAVE_PROFILE_RELEASE
296 code
= krb5_get_profile(kparam
.context
, &profile
);
299 profile_release(profile
);
301 HERE
<< "Error while getting profile : " <<
302 error_message(code
));
306 profile_get_integer(profile
, "libdefaults", "clockskew", 0,
309 profile_release(profile
);
312 HERE
<< "Error while getting clockskew : " <<
313 error_message(code
));
316 #elif HAVE_KRB5_GET_MAX_TIME_SKEW && HAVE_HEIMDAL_KERBEROS
317 skew
= krb5_get_max_time_skew(kparam
.context
);
318 #elif HAVE_MAX_SKEW_IN_KRB5_CONTEXT && HAVE_HEIMDAL_KERBEROS
319 skew
= kparam
.context
->max_skew
;
325 char buf
[KT_PATH_MAX
], *p
;
327 krb5_kt_default_name(kparam
.context
, buf
, KT_PATH_MAX
);
328 p
= strchr(buf
, ':');
332 xfree(keytab_filename
);
333 keytab_filename
= xstrdup(p
? p
: buf
);
335 keytab_filename
= xstrdup(kf
);
338 code
= krb5_kt_resolve(kparam
.context
, keytab_filename
, &keytab
);
341 HERE
<< "Error while resolving keytab filename " <<
342 keytab_filename
<< " : " << error_message(code
));
347 code
= krb5_kt_start_seq_get(kparam
.context
, keytab
, &cursor
);
350 HERE
<< "Error while starting keytab scan : " <<
351 error_message(code
));
355 krb5_kt_next_entry(kparam
.context
, keytab
, &entry
, &cursor
);
356 krb5_copy_principal(kparam
.context
, entry
.principal
,
358 if (code
&& code
!= KRB5_KT_END
) {
360 HERE
<< "Error while scanning keytab : " <<
361 error_message(code
));
365 code
= krb5_kt_end_seq_get(kparam
.context
, keytab
, &cursor
);
368 HERE
<< "Error while ending keytab scan : " <<
369 error_message(code
));
372 #if HAVE_HEIMDAL_KERBEROS || ( HAVE_KRB5_KT_FREE_ENTRY && HAVE_DECL_KRB5_KT_FREE_ENTRY)
373 code
= krb5_kt_free_entry(kparam
.context
, &entry
);
375 code
= krb5_free_keytab_entry_contents(kparam
.context
, &entry
);
379 HERE
<< "Error while freeing keytab entry : " <<
380 error_message(code
));
385 principal_name
= xstrdup(pn
);
390 krb5_parse_name(kparam
.context
, principal_name
, &principal
);
393 HERE
<< "Error while parsing principal name " <<
394 principal_name
<< " : " << error_message(code
));
399 creds
= (krb5_creds
*) xmalloc(sizeof(*creds
));
400 memset(creds
, 0, sizeof(*creds
));
401 krb5_get_init_creds_opt_init(&options
);
402 code
= krb5_string_to_deltat((char *) MAX_RENEW_TIME
, &rlife
);
403 if (code
!= 0 || rlife
== 0) {
405 HERE
<< "Error bad lifetime value " << MAX_RENEW_TIME
<<
406 " : " << error_message(code
));
409 krb5_get_init_creds_opt_set_renew_life(&options
, rlife
);
412 krb5_get_init_creds_keytab(kparam
.context
, creds
, principal
,
413 keytab
, 0, NULL
, &options
);
417 "Error while initializing credentials from keytab : " <<
418 error_message(code
));
421 #if !HAVE_KRB5_MEMORY_CACHE
423 (char *) xmalloc(strlen("FILE:/tmp/peer_proxy_negotiate_auth_")
426 strlen("FILE:/tmp/peer_proxy_negotiate_auth_") + 16,
427 "FILE:/tmp/peer_proxy_negotiate_auth_%d", (int) getpid());
430 (char *) xmalloc(strlen("MEMORY:peer_proxy_negotiate_auth_") +
433 strlen("MEMORY:peer_proxy_negotiate_auth_") + 16,
434 "MEMORY:peer_proxy_negotiate_auth_%d", (int) getpid());
437 setenv("KRB5CCNAME", mem_cache
, 1);
438 code
= krb5_cc_resolve(kparam
.context
, mem_cache
, &kparam
.cc
);
443 HERE
<< "Error while resolving memory credential cache : "
444 << error_message(code
));
447 code
= krb5_cc_initialize(kparam
.context
, kparam
.cc
, principal
);
451 "Error while initializing memory credential cache : " <<
452 error_message(code
));
455 code
= krb5_cc_store_cred(kparam
.context
, kparam
.cc
, creds
);
458 HERE
<< "Error while storing credentials : " <<
459 error_message(code
));
463 if (!creds
->times
.starttime
)
464 creds
->times
.starttime
= creds
->times
.authtime
;
470 * peer_proxy_negotiate_auth gets a GSSAPI token for principal_name
471 * and base64 encodes it.
473 char *peer_proxy_negotiate_auth(char *principal_name
, char *proxy
) {
475 OM_uint32 major_status
, minor_status
;
476 gss_ctx_id_t gss_context
= GSS_C_NO_CONTEXT
;
477 gss_name_t server_name
= GSS_C_NO_NAME
;
478 gss_buffer_desc service
= GSS_C_EMPTY_BUFFER
;
479 gss_buffer_desc input_token
= GSS_C_EMPTY_BUFFER
;
480 gss_buffer_desc output_token
= GSS_C_EMPTY_BUFFER
;
483 setbuf(stdout
, NULL
);
487 debugs(11, 5, HERE
<< "Error : No proxy server name");
493 HERE
<< "Creating credential cache for " << principal_name
);
495 debugs(11, 5, HERE
<< "Creating credential cache");
496 rc
= krb5_create_cache(NULL
, principal_name
);
498 debugs(11, 5, HERE
<< "Error : Failed to create Kerberos cache");
503 service
.value
= (void *) xmalloc(strlen("HTTP") + strlen(proxy
) + 2);
504 snprintf((char *) service
.value
, strlen("HTTP") + strlen(proxy
) + 2,
505 "%s@%s", "HTTP", proxy
);
506 service
.length
= strlen((char *) service
.value
);
508 debugs(11, 5, HERE
<< "Import gss name");
509 major_status
= gss_import_name(&minor_status
, &service
,
510 gss_nt_service_name
, &server_name
);
512 if (check_gss_err(major_status
, minor_status
, "gss_import_name()"))
515 debugs(11, 5, HERE
<< "Initialize gss security context");
516 major_status
= gss_init_sec_context(&minor_status
,
523 GSS_C_NO_CHANNEL_BINDINGS
,
524 &input_token
, NULL
, &output_token
, NULL
, NULL
);
526 if (check_gss_err(major_status
, minor_status
, "gss_init_sec_context()"))
529 debugs(11, 5, HERE
<< "Got token with length " << output_token
.length
);
530 if (output_token
.length
) {
533 (char *) base64_encode_bin((const char *) output_token
.value
,
534 output_token
.length
);
539 gss_delete_sec_context(&minor_status
, &gss_context
, NULL
);
540 gss_release_buffer(&minor_status
, &service
);
541 gss_release_buffer(&minor_status
, &input_token
);
542 gss_release_buffer(&minor_status
, &output_token
);
543 gss_release_name(&minor_status
, &server_name
);
551 #endif /* HAVE_KRB5 && HAVE_GSSAPI */