[prev in list] [next in list] [prev in thread] [next in thread] 

List:       kfm-devel
Subject:    Negotiate authentication for HTTP
From:       Karsten =?iso-8859-1?q?K=FCnne?= <kuenne () rentec ! com>
Date:       2004-07-07 23:41:42
Message-ID: 200407071941.49059.kuenne () rentec ! com
[Download RAW message or body]

[Attachment #2 (multipart/mixed)]


Somebody on kde-devel suggested that I forward this to kfm-devel.

----------  Forwarded Message  ----------

Subject: Negotiate authentication for HTTP
Date: Wednesday 07 July 2004 00:33
From: Karsten Künne <kuenne@rentec.com>
To: kde-devel@mail.kde.org

Hi,

After Mozilla came out with support for negotiate authentication and we try
 to deploy it for our internal webservice I couldn't let Konqueror stand in
 the dark and be abandoned by our users. So I hacked together a patch for
 kioslave/http in order to support negotiate authentication. This patch works
 fine with apache and mod_auth_kerb-5.0-rc5 and heimdal on the server side. I
 tested it on a SuSE 9.0 system with KDE 3.2.3 but the diff is against KDE
 CVS from yesterday (there aren't many changes in this area between 3.2.3 and
 CVS). I don't know whether I got the autoconf magic right, I'm not an expert
 on that. Also, this implementation will most likely NOT work with IIS and
 SPNEGO and it doesn't support mutual authentication as it does not support
 multiple roundtrips between client and server. I have no way of testing
 against IIS so I can't work on this. But if you're using apache and
mod_auth_kerb-5.0 it should work fine. It also supports multiple
WWW-Authenticate headers, for instance "Negotiate" and "Basic" (this is what
our server sends). If "Negotiate" repeatedly fails it falls back to the next
lower authentication (in our case "Basic"). This is the behavior we want at
our site. All the changes in http.cc and http.h are ifdef'd with
HAVE_LIBGSSAPI so it should be transparent if this is not defined.

Please have a look and let me know whether this is useful for inclusion into
kdelibs.


Karsten.
--
The Psblurtex is an 18-inch long anaconda that hides in the gentlemen's
outfitting departments of Amazonian stores and is often bought by
mistake since its colors are those of the London Reform Club.  Once
tied around its victim's neck, it strangles him gently and then claims
the insurance before running off to Germany where it lives in hiding.
		-- Mike Harding, "The Armchair Anarchist's Almanac"

-------------------------------------------------------


Karsten.
-- 
We are all worms.  But I do believe I am a glowworm.
		-- Winston Churchill

["gssapi.diff" (text/x-diff)]

diff -Naur http.orig/Makefile.am http/Makefile.am
--- http.orig/Makefile.am	2003-10-09 17:38:24.000000000 -0400
+++ http/Makefile.am	2004-07-06 22:09:26.000000000 -0400
@@ -3,8 +3,8 @@
 
 SUBDIRS = kcookiejar
 
-INCLUDES= -I$(top_srcdir)/interfaces -I$(top_srcdir)/kio/httpfilter $(all_includes)
-AM_LDFLAGS = $(all_libraries)
+INCLUDES= -I$(top_srcdir)/interfaces -I$(top_srcdir)/kio/httpfilter $(all_includes) \
$(GSSAPI_INCS) +AM_LDFLAGS = $(all_libraries) $(GSSAPI_RPATH)
 
 ####### Files
 
@@ -15,8 +15,8 @@
 
 kio_http_la_SOURCES = http.cc
 kio_http_la_METASOURCES = AUTO
-kio_http_la_LIBADD = $(LIB_KIO) $(top_builddir)/kio/httpfilter/libhttpfilter.la
-kio_http_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN)
+kio_http_la_LIBADD = $(LIB_KIO) $(top_builddir)/kio/httpfilter/libhttpfilter.la \
$(GSSAPI_LIBS) +kio_http_la_LDFLAGS = $(all_libraries) $(GSSAPI_RPATH) -module \
$(KDE_PLUGIN)  
 kio_http_cache_cleaner_la_SOURCES = http_cache_cleaner.cpp
 kio_http_cache_cleaner_la_LIBADD  = $(LIB_KIO)
diff -Naur http.orig/configure.in.in http/configure.in.in
--- http.orig/configure.in.in	1969-12-31 19:00:00.000000000 -0500
+++ http/configure.in.in	2004-07-06 23:49:22.000000000 -0400
@@ -0,0 +1,92 @@
+AC_MSG_CHECKING(whether to enable GSSAPI support)
+AC_ARG_WITH(gssapi,
+[  --with-gssapi=PATH      Set path for GSSAPI files [default=check]],
+[ case "$withval" in
+  yes)
+    with_gssapi=CHECK
+    ;;
+  esac ],
+[ with_gssapi=CHECK ]
+)dnl
+
+if test "x$with_gssapi" = "xCHECK" ; then
+  with_gssapi=NOTFOUND
+  KDE_FIND_PATH(krb5-config, KRB5_CONFIG, [${prefix}/bin ${exec_prefix}/bin /usr/bin \
/usr/local/bin /opt/local/bin], [ +    AC_MSG_WARN([Could not find krb5-config])
+  ])
+
+  if test -n "$KRB5_CONFIG"; then
+    GSSAPI_INCS="`$KRB5_CONFIG --cflags gssapi`"
+    GSSAPI_LIBS="`$KRB5_CONFIG --libs gssapi`"
+    if test "$USE_RPATH" = yes; then
+      for args in $GSSAPI_LIBS; do
+        case $args in
+          -L*)
+             GSSAPI_RPATH="$GSSAPI_RPATH $args"
+             ;;
+        esac
+      done
+      GSSAPI_RPATH=`echo $GSSAPI_RPATH | sed -e "s/-L/-R/g"`
+    fi
+    gssapi_incdir="$GSSAPI_INCS"
+    gssapi_libdir="$GSSAPI_LIBS"
+    with_gssapi=FOUND
+  else
+    search_incs="$kde_includes /usr/include /usr/local/include"
+    AC_FIND_FILE(gssapi.h, $search_incs, gssapi_incdir)
+    if test -r $gssapi_incdir/gssapi.h ; then
+      test "x$gssapi_incdir" != "x/usr/include" && GSSAPI_INCS="-I$gssapi_incdir"
+      with_gssapi=FOUND
+    fi
+    if test $with_gssapi = FOUND ; then
+      with_gssapi=NOTFOUND
+      for ext in la so sl a dylib ; do
+        AC_FIND_FILE(libgssapi.$ext, $kde_libraries /usr/lib /usr/local/lib,
+          gssapi_libdir)
+        if test -r $gssapi_libdir/libgssapi.$ext ; then
+          if test "x$gssapi_libdir" != "x/usr/lib" ; then
+            GSSAPI_LIBS="-L$gssapi_libdir "
+            test "$USE_RPATH" = yes && GSSAPI_RPATH="-R $gssapi_libdir"
+          fi
+          GSSAPI_LIBS="${GSSAPI_LIBS}-lgssapi -lkrb5 -lasn1 -lcrypto -lroken -lcrypt \
${LIBRESOLV}" +          with_gssapi=FOUND
+          break
+        fi
+      done
+    fi
+  fi
+fi
+
+case "$with_gssapi" in
+no) AC_MSG_RESULT(no) ;;
+framework)
+  GSSAPI_LIBS="-Xlinker -framework -Xlinker GSSAPI"
+  AC_DEFINE_UNQUOTED(HAVE_LIBGSSAPI, 1, [Define if you have GSSAPI libraries])
+  GSSAPI_SUBDIR="gssapi"
+  AC_MSG_RESULT(Apple framework)
+  ;;
+NOTFOUND) AC_MSG_RESULT(searched but not found) ;;
+*)
+  if test "x$with_gssapi" = "xFOUND" ; then
+    msg="incs=$gssapi_incdir libs=$gssapi_libdir"
+  else
+    msg="$with_gssapi"
+    GSSAPI_ROOT="$with_gssapi"
+    if test "x$GSSAPI_ROOT" != "x/usr" ; then
+      GSSAPI_INCS="-I${GSSAPI_ROOT}/include"
+      GSSAPI_LIBS="-L${GSSAPI_ROOT}/lib "
+      if test "$USE_RPATH" = "yes" ; then
+        GSSAPI_RPATH="-R ${GSSAPI_ROOT}/lib"
+      fi
+    fi
+    GSSAPI_LIBS="${GSSAPI_LIBS}-lgssapi -lkrb5 -lasn1 -lcrypto -lroken -lcrypt \
${LIBRESOLV}" +  fi
+  AC_DEFINE_UNQUOTED(HAVE_LIBGSSAPI, 1, [Define if you have GSSAPI libraries])
+  AC_MSG_RESULT($msg)
+  ;;
+esac
+
+AC_SUBST(GSSAPI_INCS)
+AC_SUBST(GSSAPI_LIBS)
+AC_SUBST(GSSAPI_RPATH)
+
diff -Naur http.orig/http.cc http/http.cc
--- http.orig/http.cc	2004-06-16 09:16:48.000000000 -0400
+++ http/http.cc	2004-07-06 22:09:26.000000000 -0400
@@ -69,6 +69,10 @@
 #include "httpfilter.h"
 #include "http.h"
 
+#ifdef HAVE_LIBGSSAPI
+#include <gssapi.h>
+#endif
+
 using namespace KIO;
 
 extern "C" {
@@ -347,6 +351,9 @@
   m_strRealm = QString::null;
   m_strAuthorization = QString::null;
   Authentication = AUTH_None;
+#ifdef HAVE_LIBGSSAPI
+  PrevAuthentication = AUTH_None;
+#endif
 
   // Obtain the proxy and remote server timeout values
   m_proxyConnTimeout = proxyConnectTimeout();
@@ -2350,7 +2357,12 @@
 
     // Only check for a cached copy if the previous
     // response was NOT a 401 or 407.
+#ifdef HAVE_LIBGSSAPI
+    // no caching for Negotiate auth.
+    if ( !m_request.bNoAuth && m_responseCode != 401 && m_responseCode != 407 && \
Authentication != AUTH_Negotiate ) +#else
     if ( !m_request.bNoAuth && m_responseCode != 401 && m_responseCode != 407 )
+#endif
     {
       kdDebug(7113) << "(" << m_pid << ") Calling checkCachedAuthentication " << \
endl;  AuthInfo info;
@@ -2380,6 +2392,11 @@
       case AUTH_Digest:
           header += createDigestAuth();
           break;
+#ifdef HAVE_LIBGSSAPI
+      case AUTH_Negotiate:
+          header += createNegotiateAuth();
+          break;
+#endif
       case AUTH_None:
       default:
           break;
@@ -2739,6 +2756,9 @@
         m_bUnauthorized = true;
         m_request.bCachedWrite = false; // Don't put in cache
         mayCache = false;
+#ifdef HAVE_LIBGSSAPI
+        PrevAuthentication = AUTH_None;
+#endif
       }
       //
       else if (m_responseCode == 416) // Range not supported
@@ -4771,23 +4791,62 @@
 
   if ( strncasecmp( p, "Basic", 5 ) == 0 )
   {
-    f = AUTH_Basic;
-    p += 5;
-    strAuth = "Basic"; // Correct for upper-case variations.
+#ifdef HAVE_LIBGSSAPI
+    if ( !b && PrevAuthentication < AUTH_Basic )
+    {
+#endif
+      f = AUTH_Basic;
+      p += 5;
+      strAuth = "Basic"; // Correct for upper-case variations.
+#ifdef HAVE_LIBGSSAPI
+      PrevAuthentication = AUTH_Basic;
+    } else f = PrevAuthentication;
+#endif
   }
   else if ( strncasecmp (p, "Digest", 6) == 0 )
   {
-    f = AUTH_Digest;
-    memcpy((void *)p, "Digest", 6); // Correct for upper-case variations.
-    p += 6;
+#ifdef HAVE_LIBGSSAPI
+    if ( !b && PrevAuthentication < AUTH_Digest )
+    {
+#endif
+      f = AUTH_Digest;
+      memcpy((void *)p, "Digest", 6); // Correct for upper-case variations.
+      p += 6;
+#ifdef HAVE_LIBGSSAPI
+      PrevAuthentication = AUTH_Digest;
+    } else f = PrevAuthentication;
+#endif
   }
   else if (strncasecmp( p, "MBS_PWD_COOKIE", 14 ) == 0)
   {
-    // Found on http://www.webscription.net/baen/default.asp
-    f = AUTH_Basic;
-    p += 14;
-    strAuth = "Basic";
+#ifdef HAVE_LIBGSSAPI
+    if ( !b && PrevAuthentication < AUTH_Basic )
+    {
+#endif
+      // Found on http://www.webscription.net/baen/default.asp
+      f = AUTH_Basic;
+      p += 14;
+      strAuth = "Basic";
+#ifdef HAVE_LIBGSSAPI
+      PrevAuthentication = AUTH_Basic;
+    } else f = PrevAuthentication;
+#endif
+  }
+#ifdef HAVE_LIBGSSAPI
+  else if ( strncasecmp( p, "Negotiate", 9 ) == 0 )
+  {
+    // if we get two 401 in a row let's assume for now that
+    // Negotiate isn't working and ignore it
+    if ( !b && PrevAuthentication < AUTH_Negotiate
+            && !(m_responseCode == 401 && m_prevResponseCode == 401) )
+    {
+      f = AUTH_Negotiate;
+      memcpy((void *)p, "Negotiate", 9); // Correct for upper-case variations.
+      p += 9;
+      PrevAuthentication = AUTH_Negotiate;
+    } else f = PrevAuthentication;
   }
+#endif
   else
   {
     kdWarning(7113) << "(" << m_pid << ") Unsupported or invalid authorization "
@@ -4816,9 +4875,11 @@
         ProxyAuthentication = f;
       m_iProxyAuthCount++;
     }
+#if !defined(HAVE_LIBGSSAPI)
     else
       m_iWWWAuthCount++;
     return;
+#endif
   }
 
   while (*p)
@@ -5063,7 +5124,16 @@
       result = true;
     else
     {
+#ifdef HAVE_LIBGSSAPI
+      if (Authentication == AUTH_Negotiate)
+      {
+        if (!repeatFailure)
+          result = true;
+      }
+      else if ( m_request.disablePassDlg == false )
+#else
       if ( m_request.disablePassDlg == false )
+#endif
       {
         kdDebug( 7113 ) << "(" << m_pid << ") Prompting the user for \
authorization..." << endl;  promptInfo( info );
@@ -5127,6 +5197,100 @@
   }
 }
 
+#ifdef HAVE_LIBGSSAPI
+QCString HTTPProtocol::gssError( int major_status, int minor_status )
+{
+  OM_uint32 new_status;
+  OM_uint32 msg_ctx = 0;
+  gss_buffer_desc major_string;
+  gss_buffer_desc minor_string;
+  OM_uint32 ret;
+  QCString errorstr;
+
+  errorstr = "";
+
+  do {
+    ret = gss_display_status(&new_status, major_status, GSS_C_GSS_CODE, \
GSS_C_NULL_OID, &msg_ctx, &major_string); +    errorstr += (const char \
*)major_string.value; +    errorstr += " ";
+    ret = gss_display_status(&new_status, minor_status, GSS_C_MECH_CODE, \
GSS_C_NULL_OID, &msg_ctx, &minor_string); +    errorstr += (const char \
*)minor_string.value; +    errorstr += " ";
+  } while (!GSS_ERROR(ret) && msg_ctx != 0);
+
+  return errorstr;
+}
+
+QString HTTPProtocol::createNegotiateAuth()
+{
+  QString auth;
+  QCString servicename;
+  QByteArray input;
+  OM_uint32 major_status, minor_status;
+  OM_uint32 req_flags = 0;
+  gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER;
+  gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
+  gss_name_t server;
+  gss_ctx_id_t ctx;
+  gss_OID mech_oid;
+  static gss_OID_desc krb5_oid_desc = {9, (void *) \
"\x2a\x86\x48\x86\xf7\x12\x01\x02\x02"}; +
+  ctx = GSS_C_NO_CONTEXT;
+  mech_oid = &krb5_oid_desc;
+
+  // the service name is "HTTP/f.q.d.n"
+  servicename = "HTTP@";
+  servicename += m_state.hostname.ascii();
+
+  input_token.value = (void *)servicename.data();
+  input_token.length = servicename.length() + 1;
+
+  major_status = gss_import_name(&minor_status, &input_token,
+                                 GSS_C_NT_HOSTBASED_SERVICE, &server);
+
+  input_token.value = NULL;
+  input_token.length = 0;
+
+  if (GSS_ERROR(major_status)) {
+    kdWarning(7113) << "(" << m_pid << ") gss_import_name failed: " << \
gssError(major_status, minor_status) << endl; +    return QString::null;
+  }
+
+  major_status = gss_init_sec_context(&minor_status, GSS_C_NO_CREDENTIAL,
+                                      &ctx, server, mech_oid,
+                                      req_flags, GSS_C_INDEFINITE,
+                                      GSS_C_NO_CHANNEL_BINDINGS,
+                                      GSS_C_NO_BUFFER, NULL, &output_token,
+                                      NULL, NULL);
+
+
+  if (GSS_ERROR(major_status) || (output_token.length == 0)) {
+    kdWarning(7113) << "(" << m_pid << ") gss_init_sec_context failed: " << \
gssError(major_status, minor_status) << endl; +    gss_release_name(&minor_status, \
&server); +    if (ctx != GSS_C_NO_CONTEXT) {
+      gss_delete_sec_context(&minor_status, &ctx, GSS_C_NO_BUFFER);
+      ctx = GSS_C_NO_CONTEXT;
+    }
+    return QString::null;
+  }
+
+  input.duplicate((const char *)output_token.value, output_token.length);
+  auth = "Authorization: Negotiate ";
+  auth += KCodecs::base64Encode( input );
+  auth += "\r\n";
+
+  // free everything
+  gss_release_name(&minor_status, &server);
+  if (ctx != GSS_C_NO_CONTEXT) {
+    gss_delete_sec_context(&minor_status, &ctx, GSS_C_NO_BUFFER);
+    ctx = GSS_C_NO_CONTEXT;
+  }
+  gss_release_buffer(&minor_status, &output_token);
+
+  return auth;
+}
+#endif
+
 QString HTTPProtocol::createBasicAuth( bool isForProxy )
 {
   QString auth;
diff -Naur http.orig/http.h http/http.h
--- http.orig/http.h	2004-06-16 09:16:48.000000000 -0400
+++ http/http.h	2004-07-06 22:09:26.000000000 -0400
@@ -58,7 +58,11 @@
   enum HTTP_REV    {HTTP_None, HTTP_Unknown, HTTP_10, HTTP_11, SHOUTCAST};
 
   /** Authorization method used **/
+#ifdef HAVE_LIBGSSAPI
+  enum HTTP_AUTH   {AUTH_None, AUTH_Basic, AUTH_Digest, AUTH_Negotiate};
+#else
   enum HTTP_AUTH   {AUTH_None, AUTH_Basic, AUTH_Digest};
+#endif
 
   /** HTTP / DAV method **/
   // Removed to interfaces/kio/http.h
@@ -433,6 +437,18 @@
    */
   QString createDigestAuth( bool isForProxy = false );
 
+#ifdef HAVE_LIBGSSAPI
+  /**
+   * Creates the entity-header for Negotiate authentication.
+   */
+  QString createNegotiateAuth();
+
+  /**
+   * create GSS error string
+   */
+  QCString gssError( int major_status, int minor_status );
+#endif
+
   /**
    * Calcualtes the message digest response based on RFC 2617.
    */
@@ -526,6 +542,9 @@
   QString m_strAuthorization;
   QString m_strProxyAuthorization;
   HTTP_AUTH Authentication;
+#ifdef HAVE_LIBGSSAPI
+  HTTP_AUTH PrevAuthentication;
+#endif
   HTTP_AUTH ProxyAuthentication;
   bool m_bUnauthorized;
   short unsigned int m_iProxyAuthCount;


[Attachment #6 (application/pgp-signature)]

[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic