]> git.ipfire.org Git - thirdparty/squid.git/commitdiff
Author: Markus Moeller <huaraz@moeller.plus.com>
authorAmos Jeffries <squid3@treenet.co.nz>
Fri, 4 Sep 2009 11:38:28 +0000 (23:38 +1200)
committerAmos Jeffries <squid3@treenet.co.nz>
Fri, 4 Sep 2009 11:38:28 +0000 (23:38 +1200)
Support Kerberos login to peers

In some setups the upstream proxy requires a secue authentication method
(Negotiate, NTLM). Allow this with Negotiate/Kerberos.

configure.in
src/HttpRequest.h
src/Makefile.am
src/cf.data.pre
src/http.cc
src/peer_proxy_negotiate_auth.cc [new file with mode: 0644]
src/protos.h
src/tunnel.cc

index 339894c21bea5630d62b38f62e1749b0d8d61eec..d9bd589b341a95bdf1a8f047f903bd7bec8d9df5 100644 (file)
@@ -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.h>
+                ],
+                [ 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<krb5.h>
+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 <gssapi/gssapi.h>
+#elif HAVE_GSSAPI_H
+#include <gssapi.h>
+#endif
+
+#ifdef HAVE_GSSAPI_GSSAPI_EXT_H
+#include <gssapi/gssapi_ext.h>
+#endif
+
+#ifdef HAVE_GSSAPI_GSSAPI_KRB5_H
+#include <gssapi/gssapi_krb5.h>
+#endif
+
+#ifdef HAVE_GSSAPI_GSSAPI_GENERIC_H
+#include <gssapi/gssapi_generic.h>
+#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 <gssapi/gssapi.h>
+#elif defined(HAVE_GSSAPI_H)
+#include <gssapi.h>
+#endif
+#else
+#ifdef HAVE_GSSAPI_GSSAPI_H
+#include <gssapi/gssapi.h>
+#elif defined(HAVE_GSSAPI_H)
+#include <gssapi.h>
+#endif
+#ifdef HAVE_GSSAPI_GSSAPI_KRB5_H
+#include <gssapi/gssapi_krb5.h>
+#endif
+#ifdef HAVE_GSSAPI_GSSAPI_GENERIC_H
+#include <gssapi/gssapi_generic.h>
+#endif
+#endif
+#include <string.h>
+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;i<gss_mech_set->count;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 <krb5.h>
+#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
index 98d98716aa6e5651f528d380d2fb3e2825cf549b..3b241ec23a1c5b7d5026c7038206bb97ef37336e 100644 (file)
@@ -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 */
index 4db4ec7b78d8114d47ac74c4ecf74c23c3c54772..f4874bfb57b1bf6ae6a53a0db252f2d124bbcd00 100644 (file)
@@ -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@ \
index c27c9a29eec0ce6070b624799af94d167f35bcc5..4de0b74cfe39260f36162614ad302e5235e8bbdd 100644 (file)
@@ -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
index 24b3ee8ed5aa08c3a6482e02b329bc511d91e0ec..dca3be0c1326a0274e7ff88c4dacebfa321de90c 100644 (file)
  */
 
 #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 (file)
index 0000000..8457a22
--- /dev/null
@@ -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 <squid.h>
+
+#if HAVE_KRB5 && HAVE_GSSAPI
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#if HAVE_PROFILE_H
+#include <profile.h>
+#endif                         /* HAVE_PROFILE_H */
+#if HAVE_KRB5_H
+#include <krb5.h>
+#endif                         /* HAVE_KRB5_H */
+#if HAVE_COM_ERR_H
+#include <com_err.h>
+#endif                         /* HAVE_COM_ERR_H */
+
+#if HAVE_GSSAPI_GSSAPI_H
+#include <gssapi/gssapi.h>
+#elif HAVE_GSSAPI_H
+#include <gssapi.h>
+#endif                         /* HAVE_GSSAPI_H */
+#if HAVE_GSSAPI_GSSAPI_EXT_H
+#include <gssapi/gssapi_ext.h>
+#endif                         /* HAVE_GSSAPI_GSSAPI_EXT_H */
+#if HAVE_GSSAPI_GSSAPI_KRB5_H
+#include <gssapi/gssapi_krb5.h>
+#endif                         /* HAVE_GSSAPI_GSSAPI_KRB5_H */
+#if HAVE_GSSAPI_GSSAPI_GENERIC_H
+#include <gssapi/gssapi_generic.h>
+#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 <ibm_svc/krb5_svc.h>
+    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 */
index 459820d3bc0766cbb8620526a10e49fc2af91e85..e56f6065cdca0e85a0f873155989c00199fd14c1 100644 (file)
@@ -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 */
index a6bab679f4142e72a0c06261b8877dcd8a52c9f8..8d74df00b92d28572b5f1d3c10b9971ee7b8a666 100644 (file)
@@ -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;