From: Amos Jeffries Date: Fri, 4 Sep 2009 11:38:28 +0000 (+1200) Subject: Author: Markus Moeller X-Git-Tag: SQUID_3_2_0_1~742 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=9ca29d23;p=thirdparty%2Fsquid.git Author: Markus Moeller Support Kerberos login to peers In some setups the upstream proxy requires a secue authentication method (Negotiate, NTLM). Allow this with Negotiate/Kerberos. --- diff --git a/configure.in b/configure.in index 339894c21b..d9bd589b34 100644 --- a/configure.in +++ b/configure.in @@ -1805,6 +1805,201 @@ if test -n "$DIGEST_AUTH_HELPERS"; then fi AC_SUBST(DIGEST_AUTH_HELPERS) +dnl Check Kerberos +SAVED_CPPFLAGS=$CPPFLAGS +SAVED_LIBS=$LIBS +AC_ARG_WITH(krb5-config, + [ --with-krb5-config=PATH specify path to krb5-config @<:@default=detect@:>@], +[ if test "$withval" = "yes"; then + unset krb5confpath + elif test "$withval" != "no"; then + krb5confpath=$withval + else + krb5confpath=no + fi +]) +if test x"$krb5confpath" != xno; then + if test x"$krb5confpath" != x; then + if ! test -x "$krb5confpath"; then + AC_MSG_WARN([krb5-config '$krb5confpath' not executable, ignoring]) + AC_CHECK_PROG(ac_krb5_config, krb5-config, yes, no) + krb5confpath=krb5-config + fi + krb5_config_path=`dirname $krb5confpath` + AC_CHECK_PROG(ac_krb5_config, krb5-config, yes, no, $krb5_config_path) + else + AC_CHECK_PROG(ac_krb5_config,krb5-config,yes,no) + krb5confpath=krb5-config + fi +fi +if test "$ac_krb5_config" = "yes" ; then + ac_heimdal=`$krb5confpath --version 2>/dev/null | grep -i heimdal` + ac_solaris=`$krb5confpath --version 2>/dev/null | grep -i solaris` + if test "x$ac_heimdal" != "x" ; then + AC_DEFINE(HAVE_HEIMDAL_KERBEROS,1,[Define to 1 if you have Heimdal Kerberos]) + else + AC_DEFINE(HAVE_MIT_KERBEROS,1,[Define to 1 if you have MIT Kerberos]) + fi + if test "$ac_solaris" != "" ; then + KRB5INCS=`$krb5confpath --cflags krb5 2>/dev/null` + KRB5LIBS=`$krb5confpath --libs krb5 2>/dev/null` + KRB5INCS="-I/usr/include/gssapi $KRB5INCS" + KRB5LIBS="-L/usr/lib -R/usr/lib -lgss -lresolv -lsocket -lnsl $KRB5LIBS" + else + KRB5INCS=`$krb5confpath --cflags krb5 2>/dev/null` + KRB5LIBS=`$krb5confpath --libs krb5 2>/dev/null` + KRB5INCS="`$krb5confpath --cflags gssapi 2>/dev/null` $KRB5INCS" + KRB5LIBS="`$krb5confpath --libs gssapi 2>/dev/null` $KRB5LIBS" + fi + CPPFLAGS="$CPPFLAGS $KRB5INCS" + LIBS="$LIBS $KRB5LIBS" + AC_CHECK_HEADERS(gssapi.h gssapi/gssapi.h gssapi/gssapi_krb5.h) + if test "x$ac_heimdal" == "x" ; then + AC_CHECK_HEADERS(gssapi/gssapi_generic.h) + fi + AC_CHECK_HEADERS(krb5.h com_err.h) + AC_MSG_CHECKING([for max_skew in struct krb5_context]) +AC_TRY_COMPILE([ +#include + ], + [ krb5_context kc; kc->max_skew = 1; ], + [ AC_DEFINE(HAVE_MAX_SKEW_IN_KRB5_CONTEXT, 1, [Define to 1 if max_skew in struct krb5_context]) + AC_MSG_RESULT(yes) ], + [ AC_MSG_RESULT(no) ] + ) + + if test "x$ac_heimdal" == "x" ; then + AC_CHECK_HEADERS(profile.h) + fi + AC_CHECK_LIB(krb5,krb5_kt_free_entry, + AC_DEFINE(HAVE_KRB5_KT_FREE_ENTRY,1,[Define to 1 if you have krb5_kt_free_entry]),) + AC_CHECK_LIB(krb5,krb5_get_init_creds_keytab, + AC_DEFINE(HAVE_GET_INIT_CREDS_KEYTAB,1,[Define to 1 if you have krb5_get_init_creds_keytab]),) + AC_CHECK_LIB(krb5,krb5_get_max_time_skew, + AC_DEFINE(HAVE_KRB5_GET_MAX_TIME_SKEW,1,[Define to 1 if you have krb5_get_max_time_skew]),) + AC_CHECK_LIB(krb5,krb5_get_profile, + AC_DEFINE(HAVE_KRB5_GET_PROFILE,1,[Define to 1 if you have krb5_get_profile]),) + AC_CHECK_LIB(krb5,profile_get_integer, + AC_DEFINE(HAVE_PROFILE_GET_INTEGER,1,[Define to 1 if you have profile_get_integer]),) + AC_CHECK_LIB(krb5,profile_release, + AC_DEFINE(HAVE_PROFILE_RELEASE,1,[Define to 1 if you have profile_release]),) + AC_MSG_CHECKING([for memory cache]) + AC_TRY_RUN([ +#include +main() +{ + krb5_context context; + krb5_ccache cc; + + krb5_init_context(&context); + return krb5_cc_resolve(context, "MEMORY:test_cache", &cc); +}], + [AC_DEFINE(HAVE_KRB5_MEMORY_CACHE,1, [Define to 1 if you have MEMORY: cache support]) + AC_MSG_RESULT(yes)], + AC_MSG_RESULT(no)) + + AC_MSG_CHECKING([for working gssapi]) + AC_TRY_RUN([ +#ifdef HAVE_GSSAPI_GSSAPI_H +#include +#elif HAVE_GSSAPI_H +#include +#endif + +#ifdef HAVE_GSSAPI_GSSAPI_EXT_H +#include +#endif + +#ifdef HAVE_GSSAPI_GSSAPI_KRB5_H +#include +#endif + +#ifdef HAVE_GSSAPI_GSSAPI_GENERIC_H +#include +#endif +int +main(void) +{ + OM_uint32 val; + gss_OID_set set; + + gss_create_empty_oid_set(&val, &set); + + return 0; +} +], [AC_DEFINE(HAVE_GSSAPI, 1, [GSSAPI support]) + AC_MSG_RESULT(yes)], + AC_MSG_RESULT(no)) + AC_MSG_CHECKING([for spnego support]) + AC_TRY_RUN([ +#ifdef HAVE_HEIMDAL_KERBEROS +#ifdef HAVE_GSSAPI_GSSAPI_H +#include +#elif defined(HAVE_GSSAPI_H) +#include +#endif +#else +#ifdef HAVE_GSSAPI_GSSAPI_H +#include +#elif defined(HAVE_GSSAPI_H) +#include +#endif +#ifdef HAVE_GSSAPI_GSSAPI_KRB5_H +#include +#endif +#ifdef HAVE_GSSAPI_GSSAPI_GENERIC_H +#include +#endif +#endif +#include +int main(int argc, char *argv[]) { + OM_uint32 major_status,minor_status; + gss_OID_set gss_mech_set; + int i; + +static gss_OID_desc _gss_mech_spnego = {6, (void *)"\x2b\x06\x01\x05\x05\x02"}; +gss_OID gss_mech_spnego = &_gss_mech_spnego; + + major_status = gss_indicate_mechs( &minor_status, &gss_mech_set); + + for (i=0;icount;i++) { + if (!memcmp(gss_mech_set->elements[i].elements,gss_mech_spnego->elements,gss_mech_set->elements[i].length)) { + return 0; + } + } + + return 1; +}], + [ac_cv_have_spnego=yes + AC_DEFINE(HAVE_SPNEGO,1, [Define to 1 if you have SPNEGO support]) + AC_MSG_RESULT(yes)], + [ac_cv_have_spnego=no + AC_MSG_RESULT(no)]) + AC_MSG_CHECKING([for working krb5]) + AC_TRY_RUN([ +#ifdef HAVE_KRB5_H +#include +#endif + +int +main(void) +{ + krb5_context context; + + krb5_init_context(&context); + + return 0; +} +], [AC_DEFINE(HAVE_KRB5, 1, [KRB5 support]) + AC_MSG_RESULT(yes)], + AC_MSG_RESULT(no)) + LIBS=$SAVED_LIBS + CPPFLAGS=$SAVED_CPPFLAGS + AC_SUBST(KRB5INCS) + AC_SUBST(KRB5LIBS) +fi +AM_CONDITIONAL(HAVE_SPNEGO, test x"$ac_cv_have_spnego" = x"yes" ) + dnl Enable "NTLM fail open" AC_ARG_ENABLE(ntlm-fail-open, AS_HELP_STRING([--enable-ntlm-fail-open],[Enable NTLM fail open, where a helper that fails one of the diff --git a/src/HttpRequest.h b/src/HttpRequest.h index 98d98716aa..3b241ec23a 100644 --- a/src/HttpRequest.h +++ b/src/HttpRequest.h @@ -175,6 +175,8 @@ public: char *peer_login; /* Configured peer login:password */ + char *peer_host; /* Selected peer host*/ + time_t lastmod; /* Used on refreshes */ const char *vary_headers; /* Used when varying entities are detected. Changes how the store key is calculated */ diff --git a/src/Makefile.am b/src/Makefile.am index 4db4ec7b78..f4874bfb57 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -392,6 +392,7 @@ squid_SOURCES = \ pconn.h \ PeerDigest.h \ peer_digest.cc \ + peer_proxy_negotiate_auth.cc \ peer_select.cc \ peer_sourcehash.cc \ peer_userhash.cc \ @@ -543,7 +544,8 @@ squid_LDADD = \ @SSLLIB@ \ @XTRA_LIBS@ \ @EPOLL_LIBS@ \ - @MINGW_LIBS@ + @MINGW_LIBS@ \ + @KRB5LIBS@ squid_DEPENDENCIES = $(top_builddir)/lib/libmiscutil.a \ @DISK_LIBS@ \ @DISK_LINKOBJS@ \ @@ -565,6 +567,9 @@ squid_LDFLAGS = \ # endif +## Kerberos libraries require their include path... +INCLUDES += @KRB5INCS@ + unlinkd_SOURCES = unlinkd_daemon.cc SquidNew.cc @@ -629,7 +634,7 @@ ufsdump_LDADD = \ -L$(top_builddir)/lib -lmiscutil \ @XTRA_LIBS@ \ @EPOLL_LIBS@ \ - @MINGW_LIBS@ + @MINGW_LIBS@ ufsdump_DEPENDENCIES = $(top_builddir)/lib/libmiscutil.a \ $(COMMON_LIBS) \ @DISK_LIBS@ \ @@ -1143,6 +1148,7 @@ tests_testCacheManager_SOURCES = \ Parsing.cc \ pconn.cc \ peer_digest.cc \ + peer_proxy_negotiate_auth.cc \ peer_select.cc \ peer_sourcehash.cc \ peer_userhash.cc \ @@ -1203,7 +1209,8 @@ tests_testCacheManager_LDADD = \ @SQUID_CPPUNIT_LIBS@ \ @SQUID_CPPUNIT_LA@ \ @SSLLIB@ \ - @XTRA_LIBS@ + @XTRA_LIBS@ \ + @KRB5LIBS@ tests_testCacheManager_LDFLAGS = $(LIBADD_DL) tests_testCacheManager_DEPENDENCIES = $(top_builddir)/lib/libmiscutil.a \ @REPL_OBJS@ \ @@ -1313,6 +1320,7 @@ tests_testEvent_SOURCES = \ Parsing.cc \ pconn.cc \ peer_digest.cc \ + peer_proxy_negotiate_auth.cc \ peer_select.cc \ peer_sourcehash.cc \ peer_userhash.cc \ @@ -1372,7 +1380,8 @@ tests_testEvent_LDADD = \ @SQUID_CPPUNIT_LIBS@ \ @SQUID_CPPUNIT_LA@ \ @SSLLIB@ \ - @XTRA_LIBS@ + @XTRA_LIBS@ \ + @KRB5LIBS@ tests_testEvent_LDFLAGS = $(LIBADD_DL) tests_testEvent_DEPENDENCIES = $(top_builddir)/lib/libmiscutil.a \ @REPL_OBJS@ \ @@ -1460,6 +1469,7 @@ tests_testEventLoop_SOURCES = \ Parsing.cc \ pconn.cc \ peer_digest.cc \ + peer_proxy_negotiate_auth.cc \ peer_select.cc \ peer_sourcehash.cc \ peer_userhash.cc \ @@ -1519,7 +1529,8 @@ tests_testEventLoop_LDADD = \ @SQUID_CPPUNIT_LIBS@ \ @SQUID_CPPUNIT_LA@ \ @SSLLIB@ \ - @XTRA_LIBS@ + @XTRA_LIBS@ \ + @KRB5LIBS@ tests_testEventLoop_LDFLAGS = $(LIBADD_DL) tests_testEventLoop_DEPENDENCIES = $(top_builddir)/lib/libmiscutil.a \ @REPL_OBJS@ \ @@ -1596,6 +1607,7 @@ tests_test_http_range_SOURCES = \ neighbors.cc \ Parsing.cc \ peer_digest.cc \ + peer_proxy_negotiate_auth.cc \ peer_select.cc \ peer_sourcehash.cc \ peer_userhash.cc \ @@ -1661,7 +1673,8 @@ tests_test_http_range_LDADD = \ @SQUID_CPPUNIT_LIBS@ \ @SQUID_CPPUNIT_LA@ \ @SSLLIB@ \ - @XTRA_LIBS@ + @XTRA_LIBS@ \ + @KRB5LIBS@ tests_test_http_range_LDFLAGS = $(LIBADD_DL) tests_test_http_range_DEPENDENCIES = \ @SQUID_CPPUNIT_LA@ @@ -1748,6 +1761,7 @@ tests_testHttpRequest_SOURCES = \ Parsing.cc \ pconn.cc \ peer_digest.cc \ + peer_proxy_negotiate_auth.cc \ peer_select.cc \ peer_sourcehash.cc \ peer_userhash.cc \ @@ -1808,7 +1822,8 @@ tests_testHttpRequest_LDADD = \ @SQUID_CPPUNIT_LIBS@ \ @SQUID_CPPUNIT_LA@ \ @SSLLIB@ \ - @XTRA_LIBS@ + @XTRA_LIBS@ \ + @KRB5LIBS@ tests_testHttpRequest_LDFLAGS = $(LIBADD_DL) tests_testHttpRequest_DEPENDENCIES = $(top_builddir)/lib/libmiscutil.a \ @REPL_OBJS@ \ @@ -2097,6 +2112,7 @@ tests_testURL_SOURCES = \ Parsing.cc \ pconn.cc \ peer_digest.cc \ + peer_proxy_negotiate_auth.cc \ peer_select.cc \ peer_sourcehash.cc \ peer_userhash.cc \ @@ -2154,7 +2170,8 @@ tests_testURL_LDADD = \ @SQUID_CPPUNIT_LIBS@ \ @SQUID_CPPUNIT_LA@ \ @SSLLIB@ \ - @XTRA_LIBS@ + @XTRA_LIBS@ \ + @KRB5LIBS@ tests_testURL_LDFLAGS = $(LIBADD_DL) tests_testURL_DEPENDENCIES = $(top_builddir)/lib/libmiscutil.a \ @REPL_OBJS@ \ diff --git a/src/cf.data.pre b/src/cf.data.pre index c27c9a29ee..4de0b74cfe 100644 --- a/src/cf.data.pre +++ b/src/cf.data.pre @@ -1822,6 +1822,19 @@ DOC_START be used to identify this proxy to the peer, similar to the login=username:password option above. + login=NEGOTIATE + If this is a personal/workgroup proxy and your parent + requires a secure proxy authentication. + The first principal from the default keytab or defined by + the environment variable KRB5_KTNAME will be used. + + login=NEGOTIATE:principal_name + If this is a personal/workgroup proxy and your parent + requires a secure proxy authentication. + The principal principal_name from the default keytab or + defined by the environment variable KRB5_KTNAME will be + used. + connection-auth=on|off Tell Squid that this peer does or not support Microsoft connection oriented authentication, and any such diff --git a/src/http.cc b/src/http.cc index 24b3ee8ed5..dca3be0c13 100644 --- a/src/http.cc +++ b/src/http.cc @@ -39,25 +39,28 @@ */ #include "squid.h" + +#include "acl/FilledChecklist.h" +#include "auth/UserRequest.h" +#if DELAY_POOLS +#include "DelayPools.h" +#endif #include "errorpage.h" -#include "MemBuf.h" +#include "fde.h" #include "http.h" -#include "auth/UserRequest.h" -#include "Store.h" -#include "HttpReply.h" -#include "HttpRequest.h" -#include "MemObject.h" #include "HttpHdrContRange.h" #include "HttpHdrSc.h" #include "HttpHdrScTarget.h" -#include "acl/FilledChecklist.h" -#include "fde.h" -#if DELAY_POOLS -#include "DelayPools.h" -#endif +#include "HttpReply.h" +#include "HttpRequest.h" +#include "MemBuf.h" +#include "MemObject.h" +#include "protos.h" #include "SquidTime.h" +#include "Store.h" #include "TextException.h" + #define SQUID_ENTER_THROWING_CODE() try { #define SQUID_EXIT_THROWING_CODE(status) \ status = true; \ @@ -1511,6 +1514,22 @@ httpFixupAuthentication(HttpRequest * request, HttpRequest * orig_request, const return; } + /* Kerberos login to peer */ +#if HAVE_KRB5 && HAVE_GSSAPI + if (strncmp(orig_request->peer_login, "NEGOTIATE",strlen("NEGOTIATE")) == 0) { + char *Token=NULL; + char *PrincipalName=NULL,*p; + if ((p=strchr(orig_request->peer_login,':')) != NULL ) { + PrincipalName=++p; + } + Token = peer_proxy_negotiate_auth(PrincipalName,request->peer_host); + if (Token) { + httpHeaderPutStrf(hdr_out, HDR_PROXY_AUTHORIZATION, "Negotiate %s",Token); + } + return; + } +#endif /* HAVE_KRB5 && HAVE_GSSAPI */ + httpHeaderPutStrf(hdr_out, header, "Basic %s", base64_encode(orig_request->peer_login)); return; @@ -1990,6 +2009,7 @@ HttpStateData::sendRequest() } mb.init(); + request->peer_host=_peer?_peer->host:NULL; buildRequestPrefix(request, orig_request, entry, &mb, flags); debugs(11, 6, "httpSendRequest: FD " << fd << ":\n" << mb.buf); comm_write_mbuf(fd, &mb, requestSender); diff --git a/src/peer_proxy_negotiate_auth.cc b/src/peer_proxy_negotiate_auth.cc new file mode 100644 index 0000000000..8457a22bf8 --- /dev/null +++ b/src/peer_proxy_negotiate_auth.cc @@ -0,0 +1,538 @@ +/* + * ----------------------------------------------------------------------------- + * + * Author: Markus Moeller (markus_moeller at compuserve.com) + * + * Copyright (C) 2007 Markus Moeller. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. + * + * ----------------------------------------------------------------------------- + */ +/* + * Hosted at http://sourceforge.net/projects/squidkerbauth + */ + +#include + +#if HAVE_KRB5 && HAVE_GSSAPI +#ifdef __cplusplus +extern "C" +{ +#endif + +#if HAVE_PROFILE_H +#include +#endif /* HAVE_PROFILE_H */ +#if HAVE_KRB5_H +#include +#endif /* HAVE_KRB5_H */ +#if HAVE_COM_ERR_H +#include +#endif /* HAVE_COM_ERR_H */ + +#if HAVE_GSSAPI_GSSAPI_H +#include +#elif HAVE_GSSAPI_H +#include +#endif /* HAVE_GSSAPI_H */ +#if HAVE_GSSAPI_GSSAPI_EXT_H +#include +#endif /* HAVE_GSSAPI_GSSAPI_EXT_H */ +#if HAVE_GSSAPI_GSSAPI_KRB5_H +#include +#endif /* HAVE_GSSAPI_GSSAPI_KRB5_H */ +#if HAVE_GSSAPI_GSSAPI_GENERIC_H +#include +#endif /* HAVE_GSSAPI_GSSAPI_GENERIC_H */ + +#ifndef gss_nt_service_name +#define gss_nt_service_name GSS_C_NT_HOSTBASED_SERVICE +#endif + +#if HAVE_HEIMDAL_KERBEROS +#define error_message(code) krb5_get_err_text(kparam.context,code) +#endif + +#ifndef gss_mech_spnego + static gss_OID_desc _gss_mech_spnego = + { 6, (void *) "\x2b\x06\x01\x05\x05\x02" }; + gss_OID gss_mech_spnego = &_gss_mech_spnego; +#endif + +#if HAVE_NAS_KERBEROS +#include + const char *KRB5_CALLCONV error_message(long code) + { + char *msg = NULL; + krb5_svc_get_msg(code, &msg); + return msg; + } +#endif + +/* + * Kerberos context and cache structure + * Caches authentication details to reduce + * number of authentication requests to kdc + */ + static struct kstruct + { + krb5_context context; + krb5_ccache cc; + } kparam = { + NULL, NULL}; + +/* + * krb5_create_cache creates a Kerberos file credential cache or a memory + * credential cache if supported. The initial key for the principal + * principal_name is extracted from the keytab keytab_filename. + * + * If keytab_filename is NULL the default will be used. + * If principal_name is NULL the first working entry of the keytab will be used. + */ + int krb5_create_cache(char *keytab_filename, char *principal_name); + +/* + * krb5_cleanup clears used Keberos memory + */ + void krb5_cleanup(void); + +/* + * check_gss_err checks for gssapi error codes, extracts the error message + * and prints it. + */ + int check_gss_err(OM_uint32 major_status, OM_uint32 minor_status, + const char *function); + + int check_gss_err(OM_uint32 major_status, OM_uint32 minor_status, + const char *function) + { + if (GSS_ERROR(major_status)) { + OM_uint32 maj_stat, min_stat; + OM_uint32 msg_ctx = 0; + gss_buffer_desc status_string; + char buf[1024]; + size_t len; + + len = 0; + msg_ctx = 0; + while (!msg_ctx) { + /* convert major status code (GSS-API error) to text */ + maj_stat = gss_display_status(&min_stat, major_status, + GSS_C_GSS_CODE, GSS_C_NULL_OID, &msg_ctx, &status_string); + if (maj_stat == GSS_S_COMPLETE) { + if (sizeof(buf) > len + status_string.length + 1) { + memcpy(buf + len, status_string.value, + status_string.length); + len += status_string.length; + } + gss_release_buffer(&min_stat, &status_string); + break; + } + gss_release_buffer(&min_stat, &status_string); + } + if (sizeof(buf) > len + 2) { + strcpy(buf + len, ". "); + len += 2; + } + msg_ctx = 0; + while (!msg_ctx) { + /* convert minor status code (underlying routine error) to text */ + maj_stat = gss_display_status(&min_stat, minor_status, + GSS_C_MECH_CODE, GSS_C_NULL_OID, &msg_ctx, &status_string); + if (maj_stat == GSS_S_COMPLETE) { + if (sizeof(buf) > len + status_string.length) { + memcpy(buf + len, status_string.value, + status_string.length); + len += status_string.length; + } + gss_release_buffer(&min_stat, &status_string); + break; + } + gss_release_buffer(&min_stat, &status_string); + } + debugs(11, 5, HERE << function << "failed: " << buf); + return (1); + } + return (0); + } + + void krb5_cleanup() + { + debugs(11, 5, HERE << "Cleanup kerberos context"); + if (kparam.context) { + if (kparam.cc) + krb5_cc_destroy(kparam.context, kparam.cc); + kparam.cc = NULL; + krb5_free_context(kparam.context); + kparam.context = NULL; + } + } + + int krb5_create_cache(char *kf, char *pn) + { + +#define KT_PATH_MAX 256 +#define MAX_RENEW_TIME "365d" +#define DEFAULT_SKEW (krb5_deltat) 600 + + static char *keytab_filename = NULL, *principal_name = NULL; + static krb5_keytab keytab = 0; + static krb5_keytab_entry entry; + static krb5_kt_cursor cursor; + static krb5_creds *creds = NULL; +#if HAVE_HEIMDAL_KERBEROS + static krb5_creds creds2; +#endif + static krb5_principal principal = NULL; + static krb5_deltat skew; + + krb5_get_init_creds_opt options; + krb5_error_code code = 0; + krb5_deltat rlife; +#if HAVE_PROFILE_H && HAVE_KRB5_GET_PROFILE && HAVE_PROFILE_GET_INTEGER && HAVE_PROFILE_RELEASE + profile_t profile; +#endif +#if HAVE_HEIMDAL_KERBEROS + krb5_kdc_flags flags; + krb5_realm *client_realm; +#endif + char *mem_cache; + + restart: +/* + * Check if credentials need to be renewed + */ + if (creds && + (creds->times.endtime - time(0) > skew) && + (creds->times.renew_till - time(0) > 2 * skew)) { + if (creds->times.endtime - time(0) < 2 * skew) { +#if !HAVE_HEIMDAL_KERBEROS + /* renew ticket */ + code = + krb5_get_renewed_creds(kparam.context, creds, principal, + kparam.cc, NULL); +#else + /* renew ticket */ + flags.i = 0; + flags.b.renewable = flags.b.renew = 1; + + code = + krb5_cc_get_principal(kparam.context, kparam.cc, + &creds2.client); + if (code) { + debugs(11, 5, + HERE << + "Error while getting principal from credential cache : " + << error_message(code)); + return (1); + } + client_realm = krb5_princ_realm(kparam.context, creds2.client); + code = + krb5_make_principal(kparam.context, &creds2.server, + *client_realm, KRB5_TGS_NAME, *client_realm, NULL); + if (code) { + debugs(11, 5, + HERE << "Error while getting krbtgt principal : " << + error_message(code)); + return (1); + } + code = + krb5_get_kdc_cred(kparam.context, kparam.cc, flags, NULL, + NULL, &creds2, &creds); + krb5_free_creds(kparam.context, &creds2); +#endif + if (code) { + if (code == KRB5KRB_AP_ERR_TKT_EXPIRED) { + krb5_free_creds(kparam.context, creds); + creds = NULL; + /* this can happen because of clock skew */ + goto restart; + } + debugs(11, 5, + HERE << "Error while get credentials : " << + error_message(code)); + return (1); + } + } + } else { + /* reinit */ + if (!kparam.context) { + code = krb5_init_context(&kparam.context); + if (code) { + debugs(11, 5, + HERE << "Error while initialising Kerberos library : " + << error_message(code)); + return (1); + } + } +#if HAVE_PROFILE_H && HAVE_KRB5_GET_PROFILE && HAVE_PROFILE_GET_INTEGER && HAVE_PROFILE_RELEASE + code = krb5_get_profile(kparam.context, &profile); + if (code) { + if (profile) + profile_release(profile); + debugs(11, 5, + HERE << "Error while getting profile : " << + error_message(code)); + return (1); + } + code = + profile_get_integer(profile, "libdefaults", "clockskew", 0, + 5 * 60, &skew); + if (profile) + profile_release(profile); + if (code) { + debugs(11, 5, + HERE << "Error while getting clockskew : " << + error_message(code)); + return (1); + } +#elif HAVE_KRB5_GET_MAX_TIME_SKEW && HAVE_HEIMDAL_KERBEROS + skew = krb5_get_max_time_skew(kparam.context); +#elif HAVE_MAX_SKEW_IN_KRB5_CONTEXT && HAVE_HEIMDAL_KERBEROS + skew = kparam.context->max_skew; +#else + skew = DEFAULT_SKEW; +#endif + + if (!kf) { + char buf[KT_PATH_MAX], *p; + + krb5_kt_default_name(kparam.context, buf, KT_PATH_MAX); + p = strchr(buf, ':'); + if (p) + p++; + if (keytab_filename) + xfree(keytab_filename); + keytab_filename = xstrdup(p ? p : buf); + } else { + keytab_filename = xstrdup(kf); + } + + code = krb5_kt_resolve(kparam.context, keytab_filename, &keytab); + if (code) { + debugs(11, 5, + HERE << "Error while resolving keytab filename " << + keytab_filename << " : " << error_message(code)); + return (1); + } + + if (!pn) { + code = krb5_kt_start_seq_get(kparam.context, keytab, &cursor); + if (code) { + debugs(11, 5, + HERE << "Error while starting keytab scan : " << + error_message(code)); + return (1); + } + code = + krb5_kt_next_entry(kparam.context, keytab, &entry, &cursor); + krb5_copy_principal(kparam.context, entry.principal, + &principal); + if (code && code != KRB5_KT_END) { + debugs(11, 5, + HERE << "Error while scanning keytab : " << + error_message(code)); + return (1); + } + + code = krb5_kt_end_seq_get(kparam.context, keytab, &cursor); + if (code) { + debugs(11, 5, + HERE << "Error while ending keytab scan : " << + error_message(code)); + return (1); + } +#if HAVE_HEIMDAL_KERBEROS || ( HAVE_KRB5_KT_FREE_ENTRY && HAVE_DECL_KRB5_KT_FREE_ENTRY) + code = krb5_kt_free_entry(kparam.context, &entry); +#else + code = krb5_free_keytab_entry_contents(kparam.context, &entry); +#endif + if (code) { + debugs(11, 5, + HERE << "Error while freeing keytab entry : " << + error_message(code)); + return (1); + } + + } else { + principal_name = xstrdup(pn); + } + + if (!principal) { + code = + krb5_parse_name(kparam.context, principal_name, &principal); + if (code) { + debugs(11, 5, + HERE << "Error while parsing principal name " << + principal_name << " : " << error_message(code)); + return (1); + } + } + + creds = (krb5_creds *) xmalloc(sizeof(*creds)); + memset(creds, 0, sizeof(*creds)); + krb5_get_init_creds_opt_init(&options); + code = krb5_string_to_deltat((char *) MAX_RENEW_TIME, &rlife); + if (code != 0 || rlife == 0) { + debugs(11, 5, + HERE << "Error bad lifetime value " << MAX_RENEW_TIME << + " : " << error_message(code)); + return (1); + } + krb5_get_init_creds_opt_set_renew_life(&options, rlife); + + code = + krb5_get_init_creds_keytab(kparam.context, creds, principal, + keytab, 0, NULL, &options); + if (code) { + debugs(11, 5, + HERE << + "Error while initializing credentials from keytab : " << + error_message(code)); + return (1); + } +#if !HAVE_KRB5_MEMORY_CACHE + mem_cache = + (char *) xmalloc(strlen("FILE:/tmp/peer_proxy_negotiate_auth_") + + 16); + snprintf(mem_cache, + strlen("FILE:/tmp/peer_proxy_negotiate_auth_") + 16, + "FILE:/tmp/peer_proxy_negotiate_auth_%d", (int) getpid()); +#else + mem_cache = + (char *) xmalloc(strlen("MEMORY:peer_proxy_negotiate_auth_") + + 16); + snprintf(mem_cache, + strlen("MEMORY:peer_proxy_negotiate_auth_") + 16, + "MEMORY:peer_proxy_negotiate_auth_%d", (int) getpid()); +#endif + + setenv("KRB5CCNAME", mem_cache, 1); + code = krb5_cc_resolve(kparam.context, mem_cache, &kparam.cc); + if (mem_cache) + xfree(mem_cache); + if (code) { + debugs(11, 5, + HERE << "Error while resolving memory credential cache : " + << error_message(code)); + return (1); + } + code = krb5_cc_initialize(kparam.context, kparam.cc, principal); + if (code) { + debugs(11, 5, + HERE << + "Error while initializing memory credential cache : " << + error_message(code)); + return (1); + } + code = krb5_cc_store_cred(kparam.context, kparam.cc, creds); + if (code) { + debugs(11, 5, + HERE << "Error while storing credentials : " << + error_message(code)); + return (1); + } + + if (!creds->times.starttime) + creds->times.starttime = creds->times.authtime; + } + return (0); + } + +/* + * peer_proxy_negotiate_auth gets a GSSAPI token for principal_name + * and base64 encodes it. + */ + char *peer_proxy_negotiate_auth(char *principal_name, char *proxy) + { + int rc = 0; + OM_uint32 major_status, minor_status; + gss_ctx_id_t gss_context = GSS_C_NO_CONTEXT; + gss_name_t server_name = GSS_C_NO_NAME; + gss_buffer_desc service = GSS_C_EMPTY_BUFFER; + gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER; + gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER; + char *token = NULL; + + setbuf(stdout, NULL); + setbuf(stdin, NULL); + + if (!proxy) { + debugs(11, 5, HERE << "Error : No proxy server name"); + return NULL; + } + + if (principal_name) + debugs(11, 5, + HERE << "Creating credential cache for " << principal_name); + else + debugs(11, 5, HERE << "Creating credential cache"); + rc = krb5_create_cache(NULL, principal_name); + if (rc) { + debugs(11, 5, HERE << "Error : Failed to create Kerberos cache"); + krb5_cleanup(); + return NULL; + } + + service.value = (void *) xmalloc(strlen("HTTP") + strlen(proxy) + 2); + snprintf((char *) service.value, strlen("HTTP") + strlen(proxy) + 2, + "%s@%s", "HTTP", proxy); + service.length = strlen((char *) service.value); + + debugs(11, 5, HERE << "Import gss name"); + major_status = gss_import_name(&minor_status, &service, + gss_nt_service_name, &server_name); + + if (check_gss_err(major_status, minor_status, "gss_import_name()")) + goto cleanup; + + debugs(11, 5, HERE << "Initialize gss security context"); + major_status = gss_init_sec_context(&minor_status, + GSS_C_NO_CREDENTIAL, + &gss_context, + server_name, + gss_mech_spnego, + 0, + 0, + GSS_C_NO_CHANNEL_BINDINGS, + &input_token, NULL, &output_token, NULL, NULL); + + if (check_gss_err(major_status, minor_status, "gss_init_sec_context()")) + goto cleanup; + + debugs(11, 5, HERE << "Got token with length " << output_token.length); + if (output_token.length) { + + token = + (char *) base64_encode_bin((const char *) output_token.value, + output_token.length); + } + + + cleanup: + gss_delete_sec_context(&minor_status, &gss_context, NULL); + gss_release_buffer(&minor_status, &service); + gss_release_buffer(&minor_status, &input_token); + gss_release_buffer(&minor_status, &output_token); + gss_release_name(&minor_status, &server_name); + + return token; + } + +#ifdef __cplusplus +} +#endif +#endif /* HAVE_KRB5 && HAVE_GSSAPI */ diff --git a/src/protos.h b/src/protos.h index 459820d3bc..e56f6065cd 100644 --- a/src/protos.h +++ b/src/protos.h @@ -797,4 +797,8 @@ class external_acl; #endif +#if HAVE_KRB5 && HAVE_GSSAPI + /* upstream proxy authentication */ + SQUIDCEXTERN char *peer_proxy_negotiate_auth(char *principal_name, char *proxy); +#endif #endif /* SQUID_PROTOS_H */ diff --git a/src/tunnel.cc b/src/tunnel.cc index a6bab679f4..8d74df00b9 100644 --- a/src/tunnel.cc +++ b/src/tunnel.cc @@ -745,6 +745,7 @@ tunnelPeerSelectComplete(FwdServer * fs, void *data) tunnelState->servers = fs; tunnelState->host = fs->_peer ? fs->_peer->host : xstrdup(request->GetHost()); + request->peer_host = fs->_peer ? fs->_peer->host : NULL; if (fs->_peer == NULL) { tunnelState->port = request->port;