Initial ADFS check in, compiles, but not tested.
authorScott Cantor <cantor.2@osu.edu>
Tue, 18 Oct 2005 21:27:51 +0000 (21:27 +0000)
committerScott Cantor <cantor.2@osu.edu>
Tue, 18 Oct 2005 21:27:51 +0000 (21:27 +0000)
adfs/.gitignore [new file with mode: 0644]
adfs/Makefile.am [new file with mode: 0644]
adfs/XML.cpp [new file with mode: 0644]
adfs/adfs.cpp [new file with mode: 0644]
adfs/adfs.dsp [new file with mode: 0644]
adfs/adfs.rc [new file with mode: 0644]
adfs/handlers.cpp [new file with mode: 0644]
adfs/internal.h [new file with mode: 0644]
adfs/listener.cpp [new file with mode: 0644]
adfs/resource.h [new file with mode: 0644]

diff --git a/adfs/.gitignore b/adfs/.gitignore
new file mode 100644 (file)
index 0000000..db379c7
--- /dev/null
@@ -0,0 +1,12 @@
+/Makefile.in
+/Makefile
+/.libs
+/.deps
+/*.lo
+/*.la
+/Debug
+/*.plg
+/Release
+/*.dep
+/*.mak
+/*.aps
\ No newline at end of file
diff --git a/adfs/Makefile.am b/adfs/Makefile.am
new file mode 100644 (file)
index 0000000..a1fda6a
--- /dev/null
@@ -0,0 +1,26 @@
+## $Id$
+
+AUTOMAKE_OPTIONS = foreign
+
+plugindir = $(libexecdir)
+plugin_LTLIBRARIES = adfs.la
+
+noinst_HEADERS = internal.h
+
+adfs_la_LIBADD = \
+       ../shib/libshib.la \
+       ../shib-target/libshib-target.la
+
+adfs_la_SOURCES = \
+       adfs.cpp \
+       listener.cpp \
+       handlers.cpp \
+       XML.cpp
+
+
+adfs_la_LDFLAGS = -module -avoid-version
+
+install-exec-hook:
+       for la in $(plugin_LTLIBRARIES) ; do rm -f $(DESTDIR)$(plugindir)/$$la ; done
+
+EXTRA_DIST = adfs.dsp resource.h adfs.rc
diff --git a/adfs/XML.cpp b/adfs/XML.cpp
new file mode 100644 (file)
index 0000000..ea8653a
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ *  Copyright 2001-2005 Internet2
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* XML.cpp - XML constants
+
+   Scott Cantor
+   6/4/02
+
+   $History:$
+*/
+
+#include "internal.h"
+
+// Namespace and schema string literals
+
+const XMLCh adfs::XML::WSFED_NS[] = // http://schemas.xmlsoap.org/ws/2003/07/secext
+{ chLatin_h, chLatin_t, chLatin_t, chLatin_p, chColon, chForwardSlash, chForwardSlash,
+  chLatin_s, chLatin_c, chLatin_h, chLatin_e, chLatin_m, chLatin_a, chLatin_s, chPeriod,
+  chLatin_x, chLatin_m, chLatin_l, chLatin_s, chLatin_o, chLatin_a, chLatin_p, chPeriod,
+  chLatin_o, chLatin_r, chLatin_g, chForwardSlash, chLatin_w, chLatin_s, chForwardSlash,
+  chDigit_2, chDigit_0, chDigit_0, chDigit_3, chForwardSlash, chDigit_0, chDigit_7, chForwardSlash,
+  chLatin_s, chLatin_e, chLatin_c, chLatin_e, chLatin_x, chLatin_t, chNull
+};
+
+const XMLCh adfs::XML::WSTRUST_NS[] = // http://schemas.xmlsoap.org/ws/2005/02/trust
+{ chLatin_h, chLatin_t, chLatin_t, chLatin_p, chColon, chForwardSlash, chForwardSlash,
+  chLatin_s, chLatin_c, chLatin_h, chLatin_e, chLatin_m, chLatin_a, chLatin_s, chPeriod,
+  chLatin_x, chLatin_m, chLatin_l, chLatin_s, chLatin_o, chLatin_a, chLatin_p, chPeriod,
+  chLatin_o, chLatin_r, chLatin_g, chForwardSlash, chLatin_w, chLatin_s, chForwardSlash,
+  chDigit_2, chDigit_0, chDigit_0, chDigit_5, chForwardSlash, chDigit_0, chDigit_2, chForwardSlash,
+  chLatin_t, chLatin_r, chLatin_u, chLatin_s, chLatin_t, chNull
+};
+
+const XMLCh adfs::XML::WSTRUST_SCHEMA_ID[] =
+{ chLatin_W, chLatin_S, chDash, chLatin_T, chLatin_r, chLatin_u, chLatin_s, chLatin_t, chPeriod, chLatin_x, chLatin_s, chLatin_d, chNull
+};
+
+const XMLCh adfs::XML::Literals::RequestedSecurityToken[] =
+{ chLatin_R, chLatin_e, chLatin_q, chLatin_u, chLatin_e, chLatin_s, chLatin_t, chLatin_e, chLatin_d,
+  chLatin_S, chLatin_e, chLatin_c, chLatin_u, chLatin_r, chLatin_i, chLatin_t, chLatin_y,
+  chLatin_T, chLatin_o, chLatin_k, chLatin_e, chLatin_n, chNull
+};
+
+const XMLCh adfs::XML::Literals::RequestSecurityTokenResponse[] =
+{ chLatin_R, chLatin_e, chLatin_q, chLatin_u, chLatin_e, chLatin_s, chLatin_t,
+  chLatin_S, chLatin_e, chLatin_c, chLatin_u, chLatin_r, chLatin_i, chLatin_t, chLatin_y,
+  chLatin_T, chLatin_o, chLatin_k, chLatin_e, chLatin_n,
+  chLatin_R, chLatin_e, chLatin_s, chLatin_p, chLatin_o, chLatin_n, chLatin_s, chLatin_e, chNull
+};
diff --git a/adfs/adfs.cpp b/adfs/adfs.cpp
new file mode 100644 (file)
index 0000000..2f35433
--- /dev/null
@@ -0,0 +1,329 @@
+/*
+ *  Copyright 2001-2005 Internet2
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* adfs.cpp - bootstraps the ADFS extension library
+
+   Scott Cantor
+   10/10/05
+*/
+
+#ifdef WIN32
+# define ADFS_EXPORTS __declspec(dllexport)
+#else
+# define ADFS_EXPORTS
+#endif
+
+#include "internal.h"
+
+#include <xercesc/util/Base64.hpp>
+
+
+using namespace std;
+using namespace saml;
+using namespace shibboleth;
+using namespace adfs;
+using namespace log4cpp;
+
+// Plugin Factories
+PlugManager::Factory ADFSListenerFactory;
+PlugManager::Factory ADFSSessionInitiatorFactory;
+PlugManager::Factory ADFSHandlerFactory;
+
+
+extern "C" int ADFS_EXPORTS saml_extension_init(void*)
+{
+    // Register extension schema.
+    saml::XML::registerSchema(adfs::XML::WSTRUST_NS,adfs::XML::WSTRUST_SCHEMA_ID);
+
+    // Register plugin factories (some override existing Shib functionality).
+    SAMLConfig& conf=SAMLConfig::getConfig();
+    conf.getPlugMgr().regFactory(shibtarget::XML::MemoryListenerType,&ADFSListenerFactory);
+
+    auto_ptr_char temp1(Constants::SHIB_SESSIONINIT_PROFILE_URI);
+    conf.getPlugMgr().regFactory(temp1.get(),&ADFSSessionInitiatorFactory);
+
+    auto_ptr_char temp2(adfs::XML::WSFED_NS);
+    conf.getPlugMgr().regFactory(temp2.get(),&ADFSHandlerFactory);
+
+    return 0;
+}
+
+extern "C" void ADFS_EXPORTS saml_extension_term()
+{
+    // Unregister metadata factories
+    SAMLConfig& conf=SAMLConfig::getConfig();
+    conf.getPlugMgr().unregFactory(shibtarget::XML::MemoryListenerType);
+    
+    auto_ptr_char temp1(Constants::SHIB_SESSIONINIT_PROFILE_URI);
+    conf.getPlugMgr().unregFactory(temp1.get());
+    
+    auto_ptr_char temp2(adfs::XML::WSFED_NS);
+    conf.getPlugMgr().unregFactory(temp2.get());
+}
+
+// For now, we'll just put the meat of the profile here.
+
+SAMLAuthenticationStatement* adfs::checkAssertionProfile(const SAMLAssertion* a)
+{
+    // Is it signed?
+    if (!a->isSigned())
+        throw FatalProfileException("rejected unsigned ADFS assertion");
+    
+    // Is it valid?
+    time_t now=time(NULL);
+    SAMLConfig& config=SAMLConfig::getConfig();
+    if (a->getIssueInstant()->getEpoch() < now-(2*config.clock_skew_secs))
+        throw ExpiredAssertionException("rejected expired ADFS assertion");
+
+    const SAMLDateTime* notBefore=a->getNotBefore();
+    const SAMLDateTime* notOnOrAfter=a->getNotOnOrAfter();
+    if (!notBefore || !notOnOrAfter)
+        throw ExpiredAssertionException("rejected ADFS assertion without time conditions");
+    if (now+config.clock_skew_secs < notBefore->getEpoch())
+        throw ExpiredAssertionException("rejected ADFS assertion that is not yet valid");
+    if (notOnOrAfter->getEpoch() <= now-config.clock_skew_secs)
+        throw ExpiredAssertionException("rejected expired ADFS assertion");
+
+    // Look for an authentication statement.
+    SAMLAuthenticationStatement* as=NULL;
+    for (Iterator<SAMLStatement*> statements=a->getStatements(); !as && statements.hasNext();)
+        as=dynamic_cast<SAMLAuthenticationStatement*>(statements.next());
+    if (!as)
+        throw FatalProfileException("rejecting ADFS assertion without authentication statement");
+
+    return as;
+}
+
+/*************************************************************************
+ * CGI Parser implementation
+ */
+
+CgiParse::CgiParse(const char* data, unsigned int len)
+{
+    const char* pch = data;
+    unsigned int cl = len;
+        
+    while (cl && pch) {
+        char *name;
+        char *value;
+        value=fmakeword('&',&cl,&pch);
+        plustospace(value);
+        url_decode(value);
+        name=makeword(value,'=');
+        kvp_map[name]=value;
+        free(name);
+    }
+}
+
+CgiParse::~CgiParse()
+{
+    for (map<string,char*>::iterator i=kvp_map.begin(); i!=kvp_map.end(); i++)
+        free(i->second);
+}
+
+const char*
+CgiParse::get_value(const char* name) const
+{
+    map<string,char*>::const_iterator i=kvp_map.find(name);
+    if (i==kvp_map.end())
+        return NULL;
+    return i->second;
+}
+
+/* Parsing routines modified from NCSA source. */
+char *
+CgiParse::makeword(char *line, char stop)
+{
+    int x = 0,y;
+    char *word = (char *) malloc(sizeof(char) * (strlen(line) + 1));
+
+    for(x=0;((line[x]) && (line[x] != stop));x++)
+        word[x] = line[x];
+
+    word[x] = '\0';
+    if(line[x])
+        ++x;
+    y=0;
+
+    while(line[x])
+      line[y++] = line[x++];
+    line[y] = '\0';
+    return word;
+}
+
+char *
+CgiParse::fmakeword(char stop, unsigned int *cl, const char** ppch)
+{
+    int wsize;
+    char *word;
+    int ll;
+
+    wsize = 1024;
+    ll=0;
+    word = (char *) malloc(sizeof(char) * (wsize + 1));
+
+    while(1)
+    {
+        word[ll] = *((*ppch)++);
+        if(ll==wsize-1)
+        {
+            word[ll+1] = '\0';
+            wsize+=1024;
+            word = (char *)realloc(word,sizeof(char)*(wsize+1));
+        }
+        --(*cl);
+        if((word[ll] == stop) || word[ll] == EOF || (!(*cl)))
+        {
+            if(word[ll] != stop)
+                ll++;
+            word[ll] = '\0';
+            return word;
+        }
+        ++ll;
+    }
+}
+
+void
+CgiParse::plustospace(char *str)
+{
+    register int x;
+
+    for(x=0;str[x];x++)
+        if(str[x] == '+') str[x] = ' ';
+}
+
+char
+CgiParse::x2c(char *what)
+{
+    register char digit;
+
+    digit = (what[0] >= 'A' ? ((what[0] & 0xdf) - 'A')+10 : (what[0] - '0'));
+    digit *= 16;
+    digit += (what[1] >= 'A' ? ((what[1] & 0xdf) - 'A')+10 : (what[1] - '0'));
+    return(digit);
+}
+
+void
+CgiParse::url_decode(char *url)
+{
+    register int x,y;
+
+    for(x=0,y=0;url[y];++x,++y)
+    {
+        if((url[x] = url[y]) == '%')
+        {
+            url[x] = x2c(&url[y+1]);
+            y+=2;
+        }
+    }
+    url[x] = '\0';
+}
+
+static inline char hexchar(unsigned short s)
+{
+    return (s<=9) ? ('0' + s) : ('A' + s - 10);
+}
+
+string CgiParse::url_encode(const char* s)
+{
+    static char badchars[]="\"\\+<>#%{}|^~[]`;/?:@=&";
+
+    string ret;
+    for (; *s; s++) {
+        if (strchr(badchars,*s) || *s<=0x20 || *s>=0x7F) {
+            ret+='%';
+        ret+=hexchar(*s >> 4);
+        ret+=hexchar(*s & 0x0F);
+        }
+        else
+            ret+=*s;
+    }
+    return ret;
+}
+
+// CDC implementation
+
+const char CommonDomainCookie::CDCName[] = "_saml_idp";
+
+CommonDomainCookie::CommonDomainCookie(const char* cookie)
+{
+    if (!cookie)
+        return;
+
+    Category& log=Category::getInstance(ADFS_LOGCAT".CommonDomainCookie");
+
+    // Copy it so we can URL-decode it.
+    char* b64=strdup(cookie);
+    CgiParse::url_decode(b64);
+
+    // Chop it up and save off elements.
+    vector<string> templist;
+    char* ptr=b64;
+    while (*ptr) {
+        while (*ptr && isspace(*ptr)) ptr++;
+        char* end=ptr;
+        while (*end && !isspace(*end)) end++;
+        templist.push_back(string(ptr,end-ptr));
+        ptr=end;
+    }
+    free(b64);
+
+    // Now Base64 decode the list.
+    for (vector<string>::iterator i=templist.begin(); i!=templist.end(); i++) {
+        unsigned int len;
+        XMLByte* decoded=Base64::decode(reinterpret_cast<const XMLByte*>(i->c_str()),&len);
+        if (decoded && *decoded) {
+            m_list.push_back(reinterpret_cast<char*>(decoded));
+            XMLString::release(&decoded);
+        }
+        else
+            log.warn("cookie element does not appear to be base64-encoded");
+    }
+}
+
+const char* CommonDomainCookie::set(const char* providerId)
+{
+    // First scan the list for this IdP.
+    for (vector<string>::iterator i=m_list.begin(); i!=m_list.end(); i++) {
+        if (*i == providerId) {
+            m_list.erase(i);
+            break;
+        }
+    }
+    
+    // Append it to the end.
+    m_list.push_back(providerId);
+    
+    // Now rebuild the delimited list.
+    string delimited;
+    for (vector<string>::const_iterator j=m_list.begin(); j!=m_list.end(); j++) {
+        if (!delimited.empty()) delimited += ' ';
+        
+        unsigned int len;
+        XMLByte* b64=Base64::encode(reinterpret_cast<const XMLByte*>(j->c_str()),j->length(),&len);
+        XMLByte *pos, *pos2;
+        for (pos=b64, pos2=b64; *pos2; pos2++)
+            if (isgraph(*pos2))
+                *pos++=*pos2;
+        *pos=0;
+        
+        delimited += reinterpret_cast<char*>(b64);
+        XMLString::release(&b64);
+    }
+    
+    m_encoded=CgiParse::url_encode(delimited.c_str());
+    return m_encoded.c_str();
+}
diff --git a/adfs/adfs.dsp b/adfs/adfs.dsp
new file mode 100644 (file)
index 0000000..78f7d36
--- /dev/null
@@ -0,0 +1,119 @@
+# Microsoft Developer Studio Project File - Name="adfs" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=adfs - Win32 Debug
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE 
+!MESSAGE NMAKE /f "adfs.mak".
+!MESSAGE 
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE 
+!MESSAGE NMAKE /f "adfs.mak" CFG="adfs - Win32 Debug"
+!MESSAGE 
+!MESSAGE Possible choices for configuration are:
+!MESSAGE 
+!MESSAGE "adfs - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "adfs - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE 
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF  "$(CFG)" == "adfs - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "ADFS_EXPORTS" /YX /FD /c
+# ADD CPP /nologo /MD /W3 /GR /GX /O2 /I ".." /I "..\..\..\opensaml\c" /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /d "NDEBUG"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /machine:I386
+# ADD LINK32 log4cpp.lib xerces-c_2.lib saml_5.lib /nologo /dll /machine:I386 /out:"Release/adfs.so" /libpath:"..\..\..\opensaml\c\saml\Release"
+
+!ELSEIF  "$(CFG)" == "adfs - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "ADFS_EXPORTS" /YX /FD /GZ /c
+# ADD CPP /nologo /MDd /W3 /Gm /GR /GX /ZI /Od /I ".." /I "..\..\..\opensaml\c" /D "_WINDOWS" /D "_WINDLL" /D "WIN32" /D "_DEBUG" /D "_MBCS" /FR /YX /FD /GZ /c
+# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /d "_DEBUG"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /debug /machine:I386 /pdbtype:sept
+# ADD LINK32 log4cppD.lib xerces-c_2D.lib saml_5D.lib /nologo /dll /debug /machine:I386 /out:"Debug/adfs.so" /pdbtype:sept /libpath:"..\..\..\opensaml\c\saml\Debug"
+
+!ENDIF 
+
+# Begin Target
+
+# Name "adfs - Win32 Release"
+# Name "adfs - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\adfs.cpp
+# End Source File
+# Begin Source File
+
+SOURCE=.\adfs.rc
+# End Source File
+# Begin Source File
+
+SOURCE=.\handlers.cpp
+# End Source File
+# Begin Source File
+
+SOURCE=.\internal.h
+# End Source File
+# Begin Source File
+
+SOURCE=.\listener.cpp
+# End Source File
+# Begin Source File
+
+SOURCE=.\resource.h
+# End Source File
+# Begin Source File
+
+SOURCE=.\XML.cpp
+# End Source File
+# End Target
+# End Project
diff --git a/adfs/adfs.rc b/adfs/adfs.rc
new file mode 100644 (file)
index 0000000..d1b2bca
--- /dev/null
@@ -0,0 +1,109 @@
+//Microsoft Developer Studio generated resource script.
+//
+#include "resource.h"
+
+#define APSTUDIO_READONLY_SYMBOLS
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 2 resource.
+//
+#include "afxres.h"
+
+/////////////////////////////////////////////////////////////////////////////
+#undef APSTUDIO_READONLY_SYMBOLS
+
+/////////////////////////////////////////////////////////////////////////////
+// English (U.S.) resources
+
+#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
+#ifdef _WIN32
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+#pragma code_page(1252)
+#endif //_WIN32
+
+#ifdef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// TEXTINCLUDE
+//
+
+1 TEXTINCLUDE DISCARDABLE 
+BEGIN
+    "resource.h\0"
+END
+
+2 TEXTINCLUDE DISCARDABLE 
+BEGIN
+    "#include ""afxres.h""\r\n"
+    "\0"
+END
+
+3 TEXTINCLUDE DISCARDABLE 
+BEGIN
+    "\r\n"
+    "\0"
+END
+
+#endif    // APSTUDIO_INVOKED
+
+
+#ifndef _MAC
+/////////////////////////////////////////////////////////////////////////////
+//
+// Version
+//
+
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION 1,3,0,0
+ PRODUCTVERSION 1,3,0,0
+ FILEFLAGSMASK 0x3fL
+#ifdef _DEBUG
+ FILEFLAGS 0x1L
+#else
+ FILEFLAGS 0x0L
+#endif
+ FILEOS 0x40004L
+ FILETYPE 0x2L
+ FILESUBTYPE 0x0L
+BEGIN
+    BLOCK "StringFileInfo"
+    BEGIN
+        BLOCK "040904b0"
+        BEGIN
+            VALUE "Comments", "\0"
+            VALUE "CompanyName", "Internet2\0"
+            VALUE "FileDescription", "Shibboleth ADFS Plugins\0"
+            VALUE "FileVersion", "1, 3, 0, 0\0"
+            VALUE "InternalName", "adfs\0"
+            VALUE "LegalCopyright", "Copyright Â© 2005 Internet2\0"
+            VALUE "LegalTrademarks", "\0"
+            VALUE "OriginalFilename", "adfs.so\0"
+            VALUE "PrivateBuild", "\0"
+            VALUE "ProductName", "Shibboleth 1.3\0"
+            VALUE "ProductVersion", "1, 3, 0, 0\0"
+            VALUE "SpecialBuild", "\0"
+        END
+    END
+    BLOCK "VarFileInfo"
+    BEGIN
+        VALUE "Translation", 0x409, 1200
+    END
+END
+
+#endif    // !_MAC
+
+#endif    // English (U.S.) resources
+/////////////////////////////////////////////////////////////////////////////
+
+
+
+#ifndef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 3 resource.
+//
+
+
+/////////////////////////////////////////////////////////////////////////////
+#endif    // not APSTUDIO_INVOKED
+
diff --git a/adfs/handlers.cpp b/adfs/handlers.cpp
new file mode 100644 (file)
index 0000000..d23b044
--- /dev/null
@@ -0,0 +1,479 @@
+/*
+ *  Copyright 2001-2005 Internet2
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * handlers.cpp -- ADFS-aware profile handlers that plug into SP
+ *
+ * Scott Cantor
+ * 10/10/2005
+ */
+
+#include "internal.h"
+
+#ifndef HAVE_STRCASECMP
+# define strcasecmp stricmp
+#endif
+
+using namespace std;
+using namespace saml;
+using namespace shibboleth;
+using namespace shibtarget;
+using namespace adfs;
+using namespace log4cpp;
+
+namespace {
+  
+  // TODO: Refactor/extend API so I don't have to cut/paste this code out of libshib-target
+  class SessionInitiator : virtual public IHandler
+  {
+  public:
+    SessionInitiator(const DOMElement* e) {}
+    ~SessionInitiator() {}
+    pair<bool,void*> run(ShibTarget* st, const IPropertySet* handler, bool isHandler=true);
+  
+  private:
+    const IPropertySet* getCompatibleACS(const IApplication* app, const vector<ShibProfile>& profiles);
+    pair<bool,void*> ShibAuthnRequest(
+        ShibTarget* st,
+        const IPropertySet* shire,
+        const char* dest,
+        const char* target,
+        const char* providerId
+        );
+    pair<bool,void*> ADFSAuthnRequest(
+        ShibTarget* st,
+        const IPropertySet* shire,
+        const char* dest,
+        const char* target,
+        const char* providerId
+        );
+  };
+
+  class ADFSHandler : virtual public IHandler
+  {
+  public:
+    ADFSHandler(const DOMElement* e) {}
+    ~ADFSHandler() {}
+    pair<bool,void*> run(ShibTarget* st, const IPropertySet* handler, bool isHandler=true);
+  };
+}
+
+
+IPlugIn* ADFSSessionInitiatorFactory(const DOMElement* e)
+{
+    return new SessionInitiator(e);
+}
+
+IPlugIn* ADFSHandlerFactory(const DOMElement* e)
+{
+    return new ADFSHandler(e);
+}
+
+pair<bool,void*> SessionInitiator::run(ShibTarget* st, const IPropertySet* handler, bool isHandler)
+{
+    string dupresource;
+    const char* resource=NULL;
+    const IPropertySet* ACS=NULL;
+    const IApplication* app=st->getApplication();
+    
+    if (isHandler) {
+        /* 
+         * Binding is CGI query string with:
+         *  target      the resource to direct back to later
+         *  acsIndex    optional index of an ACS to use on the way back in
+         *  providerId  optional direct invocation of a specific IdP
+         */
+        string query=st->getArgs();
+        CgiParse parser(query.c_str(),query.length());
+
+        const char* option=parser.get_value("acsIndex");
+        if (option)
+            ACS=app->getAssertionConsumerServiceByIndex(atoi(option));
+        option=parser.get_value("providerId");
+        
+        resource=parser.get_value("target");
+        if (!resource || !*resource) {
+            pair<bool,const char*> home=app->getString("homeURL");
+            if (home.first)
+                resource=home.second;
+            else
+                throw FatalProfileException("Session initiator requires a target parameter or a homeURL application property.");
+        }
+        else if (!option) {
+            dupresource=resource;
+            resource=dupresource.c_str();
+        }
+        
+        if (option) {
+            // Here we actually use metadata to invoke the SSO service directly.
+            Metadata m(app->getMetadataProviders());
+            const IEntityDescriptor* entity=m.lookup(option);
+            if (!entity)
+                throw MetadataException("Session initiator unable to locate metadata for provider ($1).", params(1,option));
+
+            // Look for an IdP role with Shib support.
+            const IIDPSSODescriptor* role=entity->getIDPSSODescriptor(Constants::SHIB_NS);
+            if (role) {
+                // Look for a SSO endpoint with Shib support.
+                const IEndpointManager* SSO=role->getSingleSignOnServiceManager();
+                const IEndpoint* ep=SSO->getEndpointByBinding(Constants::SHIB_AUTHNREQUEST_PROFILE_URI);
+                if (ep) {
+                    if (!ACS) {
+                        // Look for an ACS with SAML support.
+                        vector<ShibProfile> v;
+                        v.push_back(SAML11_POST);
+                        v.push_back(SAML11_ARTIFACT);
+                        v.push_back(SAML10_ARTIFACT);
+                        v.push_back(SAML10_POST);
+                        ACS=getCompatibleACS(app,v);
+                    }
+                    auto_ptr_char dest(ep->getLocation());
+                    return ShibAuthnRequest(
+                        st,ACS ? ACS : app->getDefaultAssertionConsumerService(),dest.get(),resource,app->getString("providerId").second
+                        );
+                }
+            }
+            // Look for an IdP role with ADFS support.
+            role=entity->getIDPSSODescriptor(adfs::XML::WSFED_NS);
+            if (role) {
+                // Finally, look for a SSO endpoint with ADFS support.
+                const IEndpointManager* SSO=role->getSingleSignOnServiceManager();
+                const IEndpoint* ep=SSO->getEndpointByBinding(adfs::XML::WSFED_NS);
+                if (ep) {
+                    if (!ACS) {
+                        // Look for an ACS with ADFS support.
+                        vector<ShibProfile> v;
+                        v.push_back(ADFS_SSO);
+                        ACS=getCompatibleACS(app,v);
+                    }
+                    auto_ptr_char dest(ep->getLocation());
+                    return ADFSAuthnRequest(
+                        st,ACS ? ACS : app->getDefaultAssertionConsumerService(),dest.get(),resource,app->getString("providerId").second
+                        );
+                }
+            }
+
+            throw MetadataException(
+                "Session initiator unable to locate a compatible identity provider SSO endpoint for provider ($1).",
+                params(1,option)
+                );
+        }
+    }
+    else {
+        // We're running as a "virtual handler" from within the filter.
+        // The target resource is the current one and everything else is defaulted.
+        resource=st->getRequestURL();
+    }
+    
+    // For now, we only support external session initiation via a wayfURL
+    pair<bool,const char*> wayfURL=handler->getString("wayfURL");
+    if (!wayfURL.first)
+        throw ConfigurationException("Session initiator is missing wayfURL property.");
+
+    pair<bool,const XMLCh*> wayfBinding=handler->getXMLString("wayfBinding");
+    if (!wayfBinding.first || !XMLString::compareString(wayfBinding.second,Constants::SHIB_AUTHNREQUEST_PROFILE_URI))
+        // Standard Shib 1.x
+        return ShibAuthnRequest(st,ACS,wayfURL.second,resource,app->getString("providerId").second);
+    else if (!XMLString::compareString(wayfBinding.second,Constants::SHIB_LEGACY_AUTHNREQUEST_PROFILE_URI))
+        // Shib pre-1.2
+        return ShibAuthnRequest(st,ACS,wayfURL.second,resource,NULL);
+    else if (!strcmp(handler->getString("wayfBinding").second,"urn:mace:shibboleth:1.0:profiles:EAuth")) {
+        pair<bool,bool> localRelayState=st->getConfig()->getPropertySet("Local")->getBool("localRelayState");
+        if (!localRelayState.first || !localRelayState.second)
+            throw ConfigurationException("E-Authn requests cannot include relay state, so localRelayState must be enabled.");
+
+        // Here we store the state in a cookie.
+        pair<string,const char*> shib_cookie=st->getCookieNameProps("_shibstate_");
+        st->setCookie(shib_cookie.first,CgiParse::url_encode(resource) + shib_cookie.second);
+        return make_pair(true, st->sendRedirect(wayfURL.second));
+    }
+    else if (!XMLString::compareString(wayfBinding.second,adfs::XML::WSFED_NS))
+        return ADFSAuthnRequest(st,ACS,wayfURL.second,resource,app->getString("providerId").second);
+   
+    throw UnsupportedProfileException("Unsupported WAYF binding ($1).", params(1,handler->getString("wayfBinding").second));
+}
+
+// Get an ACS that can handle one of the desired profiles
+const IPropertySet* SessionInitiator::getCompatibleACS(const IApplication* app, const vector<ShibProfile>& profiles)
+{
+    // This isn't going to be very efficient until I can revise the IApplication API to
+    // support ACS lookup by profile.
+    
+    int mask=0;
+    for (vector<ShibProfile>::const_iterator p=profiles.begin(); p!=profiles.end(); p++)
+        mask+=*p;
+    
+    // See if the default is acceptable.
+    const IPropertySet* ACS=app->getDefaultAssertionConsumerService();
+    pair<bool,const XMLCh*> binding=ACS ? ACS->getXMLString("Binding") : pair<bool,const XMLCh*>(false,NULL);
+    if (!ACS || !binding.first || !XMLString::compareString(binding.second,SAMLBrowserProfile::BROWSER_POST)) {
+        pair<bool,unsigned int> version =
+            ACS ? ACS->getUnsignedInt("MinorVersion","urn:oasis:names:tc:SAML:1.0:protocol") : pair<bool,unsigned int>(false,1);
+        if (!version.first)
+            version.second=1;
+        if (mask & (version.second==1 ? SAML11_POST : SAML10_POST))
+            return ACS;
+    }
+    else if (!XMLString::compareString(binding.second,SAMLBrowserProfile::BROWSER_ARTIFACT)) {
+        pair<bool,unsigned int> version=ACS->getUnsignedInt("MinorVersion","urn:oasis:names:tc:SAML:1.0:protocol");
+        if (!version.first)
+            version.second=1;
+        if (mask & (version.second==1 ? SAML11_ARTIFACT : SAML10_ARTIFACT))
+            return ACS;
+    }
+    else if (!XMLString::compareString(binding.second,adfs::XML::WSFED_NS)) {
+        if (mask & ADFS_SSO)
+            return ACS;
+    }
+    
+    // If not, iterate by profile.
+    for (vector<ShibProfile>::const_iterator i=profiles.begin(); i!=profiles.end(); i++) {
+        for (unsigned short j=0; j<=65535; j++) {
+            ACS=app->getAssertionConsumerServiceByIndex(j);
+            if (!ACS && j)
+                break;  // we're past 0 and didn't get a hit, so we'll bail
+            else if (ACS) {
+                binding=ACS->getXMLString("Binding");
+                pair<bool,unsigned int> version=ACS->getUnsignedInt("MinorVersion","urn:oasis:names:tc:SAML:1.0:protocol");
+                if (!version.first)
+                    version.second=1;
+                switch (*i) {
+                    case SAML11_POST:
+                        if (version.second==1 && (!binding.first || !XMLString::compareString(binding.second,SAMLBrowserProfile::BROWSER_POST)))
+                            return ACS;
+                        break;
+                    case SAML11_ARTIFACT:
+                        if (version.second==1 && !XMLString::compareString(binding.second,SAMLBrowserProfile::BROWSER_ARTIFACT))
+                            return ACS;
+                        break;
+                    case ADFS_SSO:
+                        if (!XMLString::compareString(binding.second,adfs::XML::WSFED_NS))
+                            return ACS;
+                        break;
+                    case SAML10_POST:
+                        if (version.second==0 && (!binding.first || !XMLString::compareString(binding.second,SAMLBrowserProfile::BROWSER_POST)))
+                            return ACS;
+                        break;
+                    case SAML10_ARTIFACT:
+                        if (version.second==0 && !XMLString::compareString(binding.second,SAMLBrowserProfile::BROWSER_ARTIFACT))
+                            return ACS;
+                        break;
+                }
+            }
+        }
+    }
+    
+    return NULL;
+}
+
+// Handles Shib 1.x AuthnRequest profile.
+pair<bool,void*> SessionInitiator::ShibAuthnRequest(
+    ShibTarget* st,
+    const IPropertySet* shire,
+    const char* dest,
+    const char* target,
+    const char* providerId
+    )
+{
+    if (!shire) {
+        // Look for an ACS with SAML support.
+        vector<ShibProfile> v;
+        v.push_back(SAML11_POST);
+        v.push_back(SAML11_ARTIFACT);
+        v.push_back(SAML10_ARTIFACT);
+        v.push_back(SAML10_POST);
+        shire=getCompatibleACS(st->getApplication(),v);
+    }
+    if (!shire)
+        shire=st->getApplication()->getDefaultAssertionConsumerService();
+    
+    // Compute the ACS URL. We add the ACS location to the handler baseURL.
+    // Legacy configs will not have an ACS specified, so no suffix will be added.
+    string ACSloc=st->getHandlerURL(target);
+    if (shire) ACSloc+=shire->getString("Location").second;
+    
+    char timebuf[16];
+    sprintf(timebuf,"%u",time(NULL));
+    string req=string(dest) + "?shire=" + CgiParse::url_encode(ACSloc.c_str()) + "&time=" + timebuf;
+
+    // How should the resource value be preserved?
+    pair<bool,bool> localRelayState=st->getConfig()->getPropertySet("Local")->getBool("localRelayState");
+    if (!localRelayState.first || !localRelayState.second) {
+        // The old way, just send it along.
+        req+="&target=" + CgiParse::url_encode(target);
+    }
+    else {
+        // Here we store the state in a cookie and send a fixed
+        // value to the IdP so we can recognize it on the way back.
+        pair<string,const char*> shib_cookie=st->getCookieNameProps("_shibstate_");
+        st->setCookie(shib_cookie.first,CgiParse::url_encode(target) + shib_cookie.second);
+        req+="&target=cookie";
+    }
+    
+    // Only omitted for 1.1 style requests.
+    if (providerId)
+        req+="&providerId=" + CgiParse::url_encode(providerId);
+
+    return make_pair(true, st->sendRedirect(req));
+}
+
+// Handles ADFS token request profile.
+pair<bool,void*> SessionInitiator::ADFSAuthnRequest(
+    ShibTarget* st,
+    const IPropertySet* shire,
+    const char* dest,
+    const char* target,
+    const char* providerId
+    )
+{
+    if (!shire) {
+        // Look for an ACS with ADFS support.
+        vector<ShibProfile> v;
+        v.push_back(ADFS_SSO);
+        shire=getCompatibleACS(st->getApplication(),v);
+    }
+    if (!shire)
+        shire=st->getApplication()->getDefaultAssertionConsumerService();
+
+    // Compute the ACS URL. We add the ACS location to the handler baseURL.
+    // Legacy configs will not have an ACS specified, so no suffix will be added.
+    string ACSloc=st->getHandlerURL(target);
+    if (shire) ACSloc+=shire->getString("Location").second;
+    
+    // UTC timestamp
+#ifndef HAVE_GMTIME_R
+    time_t epoch=time(NULL);
+    struct tm* ptime=gmtime(&epoch);
+#else
+    struct tm res;
+    struct tm* ptime=gmtime_r(&epoch,&res);
+#endif
+    char timebuf[32];
+    strftime(timebuf,32,"%Y-%m-%dT%H:%M:%SZ",ptime);
+
+    string req=string(dest) + "?wa=wsignin1.0&wreply=" + CgiParse::url_encode(ACSloc.c_str()) + "&wct=" + CgiParse::url_encode(timebuf);
+
+    // How should the resource value be preserved?
+    pair<bool,bool> localRelayState=st->getConfig()->getPropertySet("Local")->getBool("localRelayState");
+    if (!localRelayState.first || !localRelayState.second) {
+        // The old way, just send it along.
+        req+="&wctx=" + CgiParse::url_encode(target);
+    }
+    else {
+        // Here we store the state in a cookie and send a fixed
+        // value to the IdP so we can recognize it on the way back.
+        pair<string,const char*> shib_cookie=st->getCookieNameProps("_shibstate_");
+        st->setCookie(shib_cookie.first,CgiParse::url_encode(target) + shib_cookie.second);
+        req+="&wctx=cookie";
+    }
+    
+    req+="&wtrealm=" + CgiParse::url_encode(providerId);
+
+    return make_pair(true, st->sendRedirect(req));
+}
+
+pair<bool,void*> ADFSHandler::run(ShibTarget* st, const IPropertySet* handler, bool isHandler)
+{
+    const IApplication* app=st->getApplication();
+    
+    if (strcasecmp(st->getRequestMethod(), "POST"))
+        throw FatalProfileException(
+            "ADFS protocol handler does not support HTTP method ($1).", params(1,st->getRequestMethod())
+            );
+    
+    if (!st->getContentType() || strcasecmp(st->getContentType(),"application/x-www-form-urlencoded"))
+        throw FatalProfileException(
+            "Blocked invalid content-type ($1) submitted to ADFS protocol handler.", params(1,st->getContentType())
+            );
+
+    string input=st->getPostData();
+    if (input.empty())
+        throw FatalProfileException("ADFS protocol handler received no data from browser.");
+
+    ShibProfile profile=ADFS_SSO;
+    string cookie,target,providerId;
+    
+    string hURL=st->getHandlerURL(st->getRequestURL());
+    pair<bool,const char*> loc=handler->getString("Location");
+    string recipient=loc.first ? hURL + loc.second : hURL;
+    st->getConfig()->getListener()->sessionNew(
+        app,
+        profile,
+        recipient.c_str(),
+        input.c_str(),
+        st->getRemoteAddr(),
+        target,
+        cookie,
+        providerId
+        );
+
+    st->log(ShibTarget::LogLevelDebug, string("profile processing succeeded, new session created (") + cookie + ")");
+
+    if (target=="default") {
+        pair<bool,const char*> homeURL=app->getString("homeURL");
+        target=homeURL.first ? homeURL.second : "/";
+    }
+    else if (target=="cookie" || target.empty()) {
+        // Pull the target value from the "relay state" cookie.
+        pair<string,const char*> relay_cookie = st->getCookieNameProps("_shibstate_");
+        const char* relay_state = st->getCookie(relay_cookie.first);
+        if (!relay_state || !*relay_state) {
+            // No apparent relay state value to use, so fall back on the default.
+            pair<bool,const char*> homeURL=app->getString("homeURL");
+            target=homeURL.first ? homeURL.second : "/";
+        }
+        else {
+            char* rscopy=strdup(relay_state);
+            CgiParse::url_decode(rscopy);
+            target=rscopy;
+            free(rscopy);
+        }
+    }
+
+    // We've got a good session, set the session cookie.
+    pair<string,const char*> shib_cookie=st->getCookieNameProps("_shibsession_");
+    st->setCookie(shib_cookie.first, cookie + shib_cookie.second);
+
+    const IPropertySet* sessionProps=app->getPropertySet("Sessions");
+    pair<bool,bool> idpHistory=sessionProps->getBool("idpHistory");
+    if (!idpHistory.first || idpHistory.second) {
+        // Set an IdP history cookie locally (essentially just a CDC).
+        CommonDomainCookie cdc(st->getCookie(CommonDomainCookie::CDCName));
+
+        // Either leave in memory or set an expiration.
+        pair<bool,unsigned int> days=sessionProps->getUnsignedInt("idpHistoryDays");
+            if (!days.first || days.second==0)
+                st->setCookie(CommonDomainCookie::CDCName,string(cdc.set(providerId.c_str())) + shib_cookie.second);
+            else {
+                time_t now=time(NULL) + (days.second * 24 * 60 * 60);
+#ifdef HAVE_GMTIME_R
+                struct tm res;
+                struct tm* ptime=gmtime_r(&now,&res);
+#else
+                struct tm* ptime=gmtime(&now);
+#endif
+                char timebuf[64];
+                strftime(timebuf,64,"%a, %d %b %Y %H:%M:%S GMT",ptime);
+                st->setCookie(
+                    CommonDomainCookie::CDCName,
+                    string(cdc.set(providerId.c_str())) + shib_cookie.second + "; expires=" + timebuf
+                    );
+        }
+    }
+
+    // Now redirect to the target.
+    return make_pair(true, st->sendRedirect(target));
+}
diff --git a/adfs/internal.h b/adfs/internal.h
new file mode 100644 (file)
index 0000000..6afade2
--- /dev/null
@@ -0,0 +1,92 @@
+/*
+ *  Copyright 2001-2005 Internet2
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* internal.h
+
+   Scott Cantor
+   2/14/04
+
+   $History:$
+*/
+
+#ifndef __internal_h__
+#define __internal_h__
+
+#include <saml/saml.h>
+#include <shib/shib.h>
+#include <shib-target/shib-target.h>
+#include <shib-target/hresult.h>
+
+#include <log4cpp/Category.hh>
+
+#define ADFS_LOGCAT "shibtarget"
+#define SHIBTRAN_LOGCAT "Shibboleth-TRANSACTION"
+#define ADFS_L(s) adfs::XML::Literals::s
+
+namespace adfs {
+
+    class XML
+    {
+    public:
+        // URI constants
+        static const XMLCh WSFED_NS[];          // http://schemas.xmlsoap.org/ws/2003/07/secext
+        static const XMLCh WSTRUST_NS[];        // http://schemas.xmlsoap.org/ws/2005/02/trust
+        static const XMLCh WSTRUST_SCHEMA_ID[];
+        
+        struct Literals
+        {
+            static const XMLCh RequestedSecurityToken[];
+            static const XMLCh RequestSecurityTokenResponse[];
+        };
+    };
+
+    // TODO: Publish these classes for reuse by extensions.
+    class CgiParse
+    {
+    public:
+        CgiParse(const char* data, unsigned int len);
+        ~CgiParse();
+        const char* get_value(const char* name) const;
+        
+        static char x2c(char *what);
+        static void url_decode(char *url);
+        static std::string url_encode(const char* s);
+    private:
+        char * fmakeword(char stop, unsigned int *cl, const char** ppch);
+        char * makeword(char *line, char stop);
+        void plustospace(char *str);
+    
+        std::map<std::string,char*> kvp_map;
+    };
+
+    // Helper class for SAML 2.0 Common Domain Cookie operations
+    class CommonDomainCookie
+    {
+    public:
+        CommonDomainCookie(const char* cookie);
+        ~CommonDomainCookie() {}
+        saml::Iterator<std::string> get() {return m_list;}
+        const char* set(const char* providerId);
+        static const char CDCName[];
+    private:
+        std::string m_encoded;
+        std::vector<std::string> m_list;
+    };
+    
+    saml::SAMLAuthenticationStatement* checkAssertionProfile(const saml::SAMLAssertion* a);
+}
+
+#endif
diff --git a/adfs/listener.cpp b/adfs/listener.cpp
new file mode 100644 (file)
index 0000000..3fd02dc
--- /dev/null
@@ -0,0 +1,539 @@
+/*
+ *  Copyright 2001-2005 Internet2
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * listener.cpp -- implementation of IListener functional methods that includes ADFS support
+ *
+ * Scott Cantor
+ * 10/10/05
+ *
+ */
+
+#include "internal.h"
+
+#include <xercesc/framework/MemBufInputSource.hpp>
+
+using namespace std;
+using namespace log4cpp;
+using namespace saml;
+using namespace shibboleth;
+using namespace shibtarget;
+using namespace adfs;
+
+namespace {
+    class ADFSListener : public virtual IListener
+    {
+    public:
+        ADFSListener(const DOMElement* e) : log(&Category::getInstance(ADFS_LOGCAT".Listener")) {}
+        ~ADFSListener() {}
+
+        bool create(ShibSocket& s) const {return true;}
+        bool bind(ShibSocket& s, bool force=false) const {return true;}
+        bool connect(ShibSocket& s) const {return true;}
+        bool close(ShibSocket& s) const {return true;}
+        bool accept(ShibSocket& listener, ShibSocket& s) const {return true;}
+
+        void sessionNew(
+            const IApplication* application,
+            int supported_profiles,
+            const char* recipient,
+            const char* packet,
+            const char* ip,
+            std::string& target,
+            std::string& cookie,
+            std::string& provider_id
+            ) const;
+    
+        void sessionGet(
+            const IApplication* application,
+            const char* cookie,
+            const char* ip,
+            ISessionCacheEntry** pentry
+            ) const;
+    
+        void sessionEnd(
+            const IApplication* application,
+            const char* cookie
+        ) const;
+        
+        void ping(int& i) const;
+
+    private:
+        Category* log;
+    };
+}
+
+IPlugIn* ADFSListenerFactory(const DOMElement* e)
+{
+    return new ADFSListener(e);
+}
+
+void ADFSListener::sessionNew(
+    const IApplication* app,
+    int supported_profiles,
+    const char* recipient,
+    const char* packet,
+    const char* ip,
+    string& target,
+    string& cookie,
+    string& provider_id
+    ) const
+{
+#ifdef _DEBUG
+    saml::NDC ndc("sessionNew");
+#endif
+
+    log->debug("creating session for %s", ip);
+    log->debug("recipient: %s", recipient);
+    log->debug("application: %s", app->getId());
+
+    auto_ptr_XMLCh wrecipient(recipient);
+
+    // Access the application config. It's already locked behind us.
+    ShibTargetConfig& stc=ShibTargetConfig::getConfig();
+    IConfig* conf=stc.getINI();
+
+    bool checkIPAddress=true;
+    const IPropertySet* props=app->getPropertySet("Sessions");
+    if (props) {
+        pair<bool,bool> pcheck=props->getBool("checkAddress");
+        if (pcheck.first)
+            checkIPAddress = pcheck.second;
+    }
+
+    pair<bool,bool> checkReplay=pair<bool,bool>(false,false);
+    props=app->getPropertySet("Sessions");
+    if (props)
+        checkReplay=props->getBool("checkReplay");
+    const IRoleDescriptor* role=NULL;
+    Metadata m(app->getMetadataProviders());
+
+    bool bADFS = false;
+    SAMLBrowserProfile::BrowserProfileResponse bpr;
+
+    // For now, just branch off to handle ADFS inline, I'll wrap all this up later.
+    if (supported_profiles & ADFS_SSO) {
+        log->debug("executing ADFS profile...");
+        CgiParse parser(packet,strlen(packet));
+        const char* param=parser.get_value("wa");
+        if (param && !strcmp(param,"wsignin1.0")) {
+            bADFS=true;
+            param=parser.get_value("wresult");
+            if (!param)
+                throw FatalProfileException("ADFS profile required wresult parameter not found");
+            
+            log->debug("decoded ADFS Token response:\n%s",param);
+            // wresult should carry an wst:RequestSecurityTokenResponse message so we parse it manually
+            DOMDocument* rdoc=NULL;
+            try {
+                saml::XML::Parser p;
+                static const XMLCh systemId[]={chLatin_W, chLatin_S, chDash, chLatin_T, chLatin_r, chLatin_u, chLatin_s, chLatin_t, chNull};
+                MemBufInputSource membufsrc(reinterpret_cast<const XMLByte*>(param),strlen(param),systemId,false);
+                Wrapper4InputSource dsrc(&membufsrc,false);
+                rdoc=p.parse(dsrc);
+        
+                // Process the wrapper and extract the assertion.
+                if (saml::XML::isElementNamed(rdoc->getDocumentElement(),adfs::XML::WSTRUST_NS,ADFS_L(RequestSecurityTokenResponse))) {
+                    DOMElement* e=
+                        saml::XML::getFirstChildElement(rdoc->getDocumentElement(),adfs::XML::WSTRUST_NS,ADFS_L(RequestedSecurityToken));
+                    if (e) {
+                        e=saml::XML::getFirstChildElement(e,saml::XML::SAML_NS,L(Assertion));
+                        if (e) {
+                            auto_ptr<SAMLAssertion> assertion(new SAMLAssertion(e));
+                            
+                            // Try and map to metadata.
+                            const IEntityDescriptor* provider=m.lookup(assertion->getIssuer());
+                            if (provider)
+                                role=provider->getIDPSSODescriptor(adfs::XML::WSFED_NS);
+                            if (!role) {
+                                MetadataException ex("unable to locate role-specific metadata for identity provider.");
+                                annotateException(&ex,provider); // throws it
+                            }
+                            
+                            try {
+                                // Check over the assertion.
+                                SAMLAuthenticationStatement* authnStatement=checkAssertionProfile(assertion.get());
+                                
+                                // Check signature.
+                                log->debug("passing signed ADFS assertion to trust layer");
+                                Trust t(app->getTrustProviders());
+                                if (!t.validate(*(assertion.get()),role)) {
+                                    log->error("unable to verify signed authentication assertion");
+                                    throw TrustException("unable to verify signed authentication assertion");
+                                }
+                                
+                                // Wrap the assertion in a dummy samlp:Response for subsequent processing.
+                                // Generate the Response DOM using the assertion's document and then
+                                // transfer ownership of the tree to the Response.
+                                auto_ptr<SAMLResponse> response(new SAMLResponse());
+                                response->addAssertion(assertion.release());
+                                response->toDOM(rdoc);
+                                response->setDocument(rdoc);
+                                rdoc=NULL;
+                                
+                                // Now dummy up the SAML profile response wrapper.
+                                param=parser.get_value("wctx");
+                                if (param)
+                                    bpr.TARGET=param;
+                                bpr.profile=SAMLBrowserProfile::Post;   // not really, but...
+                                bpr.response=response.release();
+                                bpr.assertion=response->getAssertions().next();
+                                bpr.authnStatement=authnStatement;
+                            }
+                            catch (SAMLException& ex) {
+                                annotateException(&ex,role); // throws it
+                            }
+                        }
+                    }
+                }
+                if (rdoc) {
+                    rdoc->release();
+                    rdoc=NULL;
+                }
+            }
+            catch(...) {
+                if (rdoc) rdoc->release();
+                throw;
+            }
+        }
+        if (bADFS && !bpr.response)
+            throw FatalProfileException("ADFS profile was indicated, but processing was unsuccesful");
+    }
+    
+    // If ADFS wasn't used, proceed to SAML processing up until we reach a common point.
+    int minorVersion = 1;
+    try {
+        if (!bADFS) {
+            int allowed = 0;
+            if (supported_profiles & SAML11_POST || supported_profiles & SAML10_POST)
+                allowed |= SAMLBrowserProfile::Post;
+            if (supported_profiles & SAML11_ARTIFACT || supported_profiles & SAML10_ARTIFACT)
+                allowed |= SAMLBrowserProfile::Artifact;
+            minorVersion=(supported_profiles & SAML11_ARTIFACT || supported_profiles & SAML11_POST) ? 1 : 0;
+    
+            auto_ptr<SAMLBrowserProfile::ArtifactMapper> artifactMapper(app->getArtifactMapper());
+      
+            // Try and run the profile.
+            log->debug("executing browser profile...");
+            bpr=app->getBrowserProfile()->receive(
+                packet,
+                wrecipient.get(),
+                allowed,
+                (!checkReplay.first || checkReplay.second) ? conf->getReplayCache() : NULL,
+                artifactMapper.get(),
+                minorVersion
+                );
+    
+            // Blow it away to clear any locks that might be held.
+            delete artifactMapper.release();
+    
+            // Try and map to metadata (again).
+            // Once the metadata layer is in the SAML core, the repetition should be fixed.
+            const IEntityDescriptor* provider=m.lookup(bpr.assertion->getIssuer());
+            if (!provider && bpr.authnStatement->getSubject()->getNameIdentifier() &&
+                    bpr.authnStatement->getSubject()->getNameIdentifier()->getNameQualifier())
+                provider=m.lookup(bpr.authnStatement->getSubject()->getNameIdentifier()->getNameQualifier());
+            if (provider) {
+                const IIDPSSODescriptor* IDP=provider->getIDPSSODescriptor(
+                    minorVersion==1 ? saml::XML::SAML11_PROTOCOL_ENUM : saml::XML::SAML10_PROTOCOL_ENUM
+                    );
+                role=IDP;
+            }
+            
+            // This isn't likely, since the profile must have found a role.
+            if (!role) {
+                MetadataException ex("Unable to locate role-specific metadata for identity provider.");
+                annotateException(&ex,provider); // throws it
+            }
+        }
+        
+        // At this point, we link back up and do the same work for ADFS and SAML.
+        
+        // Maybe verify the origin address....
+        if (checkIPAddress) {
+            log->debug("verifying client address");
+            // Verify the client address exists
+            const XMLCh* wip = bpr.authnStatement->getSubjectIP();
+            if (wip && *wip) {
+                // Verify the client address matches authentication
+                auto_ptr_char this_ip(ip);
+                if (strcmp(ip, this_ip.get())) {
+                    FatalProfileException ex(
+                        SESSION_E_ADDRESSMISMATCH,
+                       "Your client's current address ($1) differs from the one used when you authenticated "
+                        "to your identity provider. To correct this problem, you may need to bypass a proxy server. "
+                        "Please contact your local support staff or help desk for assistance.",
+                        params(1,ip)
+                        );
+                    annotateException(&ex,role); // throws it
+                }
+            }
+        }
+      
+        // Verify condition(s) on authentication assertion.
+        // Attribute assertions get filtered later by the AAP.
+        Iterator<SAMLCondition*> conditions=bpr.assertion->getConditions();
+        while (conditions.hasNext()) {
+            SAMLCondition* cond=conditions.next();
+            const SAMLAudienceRestrictionCondition* ac=dynamic_cast<const SAMLAudienceRestrictionCondition*>(cond);
+            if (!ac) {
+                ostringstream os;
+                os << *cond;
+                log->error("Unrecognized Condition in authentication assertion (%s), tossing it.",os.str().c_str());
+                FatalProfileException ex("unable to create session due to unrecognized condition in authentication assertion.");
+                annotateException(&ex,role); // throws it
+            }
+            else if (!ac->eval(app->getAudiences())) {
+                ostringstream os;
+                os << *ac;
+                log->error("Unacceptable AudienceRestrictionCondition in authentication assertion (%s), tossing it.",os.str().c_str());
+                FatalProfileException ex("unable to create session due to unacceptable AudienceRestrictionCondition in authentication assertion.");
+                annotateException(&ex,role); // throws it
+            }
+        }
+    }
+    catch (SAMLException&) {
+        bpr.clear();
+        throw;
+    }
+    catch (...) {
+        log->error("caught unknown exception");
+        bpr.clear();
+#ifdef _DEBUG
+        throw;
+#else
+        SAMLException e("An unexpected error occurred while creating your session.");
+        annotateException(&e,role);
+#endif
+    }
+
+    // It passes all our tests -- create a new session.
+    log->info("creating new session");
+
+    // Are attributes present?
+    bool attributesPushed=false;
+    Iterator<SAMLAssertion*> assertions=bpr.response->getAssertions();
+    while (!attributesPushed && assertions.hasNext()) {
+        Iterator<SAMLStatement*> statements=assertions.next()->getStatements();
+        while (!attributesPushed && statements.hasNext()) {
+            if (dynamic_cast<SAMLAttributeStatement*>(statements.next()))
+                attributesPushed=true;
+        }
+    }
+
+    auto_ptr_char oname(role->getEntityDescriptor()->getId());
+    auto_ptr_char hname(bpr.authnStatement->getSubject()->getNameIdentifier()->getName());
+
+    try {
+        // Create a new session key.
+        cookie = conf->getSessionCache()->generateKey();
+
+        // Insert into cache.
+        auto_ptr<SAMLAuthenticationStatement> as(static_cast<SAMLAuthenticationStatement*>(bpr.authnStatement->clone()));
+        conf->getSessionCache()->insert(
+            cookie.c_str(),
+            app,
+            ip,
+            (bADFS ? ADFS_SSO :
+                ((bpr.profile==SAMLBrowserProfile::Post) ?
+                    (minorVersion==1 ? SAML11_POST : SAML10_POST) : (minorVersion==1 ? SAML11_ARTIFACT : SAML10_ARTIFACT))),
+            oname.get(),
+            as.get(),
+            (attributesPushed ? bpr.response : NULL),
+            role
+            );
+        as.release();   // owned by cache now
+    }
+    catch (SAMLException&) {
+        bpr.clear();
+        throw;
+    }
+    catch (...) {
+        log->error("caught unknown exception");
+        bpr.clear();
+#ifdef _DEBUG
+        throw;
+#else
+        SAMLException e("An unexpected error occurred while creating your session.");
+        annotateException(&e,role);
+#endif
+    }
+
+    target = bpr.TARGET;
+    provider_id = oname.get();
+
+    // Maybe delete the response...
+    if (!attributesPushed)
+        bpr.clear();
+
+    log->debug("new session id: %s", cookie.c_str());
+  
+    // Transaction Logging
+    Category::getInstance(SHIBTRAN_LOGCAT).infoStream() <<
+        "New session (ID: " <<
+            cookie <<
+        ") with (applicationId: " <<
+            app->getId() <<
+        ") for principal from (IdP: " <<
+            provider_id <<
+        ") at (ClientAddress: " <<
+            ip <<
+        ") with (NameIdentifier: " <<
+            hname.get() <<
+        ")";
+    //stc.releaseTransactionLog();
+}
+
+void ADFSListener::sessionGet(
+    const IApplication* app,
+    const char* cookie,
+    const char* ip,
+    ISessionCacheEntry** pentry
+    ) const
+{
+#ifdef _DEBUG
+    saml::NDC ndc("sessionGet");
+#endif
+
+    *pentry=NULL;
+    log->debug("checking for session: %s@%s", cookie, ip);
+
+    // See if the session exists...
+
+    ShibTargetConfig& stc=ShibTargetConfig::getConfig();
+    IConfig* conf=stc.getINI();
+    log->debug("application: %s", app->getId());
+
+    bool checkIPAddress=true;
+    int lifetime=0,timeout=0;
+    const IPropertySet* props=app->getPropertySet("Sessions");
+    if (props) {
+        pair<bool,unsigned int> p=props->getUnsignedInt("lifetime");
+        if (p.first)
+            lifetime = p.second;
+        p=props->getUnsignedInt("timeout");
+        if (p.first)
+            timeout = p.second;
+        pair<bool,bool> pcheck=props->getBool("checkAddress");
+        if (pcheck.first)
+            checkIPAddress = pcheck.second;
+    }
+    
+    *pentry = conf->getSessionCache()->find(cookie,app);
+
+    // If not, leave now..
+    if (!*pentry) {
+        log->debug("session not found");
+        throw InvalidSessionException("No session exists for key value ($session_id)",namedparams(1,"session_id",cookie));
+    }
+
+    // TEST the session...
+    try {
+        // Verify the address is the same
+        if (checkIPAddress) {
+            log->debug("Checking address against %s", (*pentry)->getClientAddress());
+            if (strcmp(ip, (*pentry)->getClientAddress())) {
+                log->debug("client address mismatch");
+                InvalidSessionException ex(
+                    SESSION_E_ADDRESSMISMATCH,
+                    "Your IP address (%1) does not match the address recorded at the time the session was established.",
+                    params(1,ip)
+                    );
+                Metadata m(app->getMetadataProviders());
+                annotateException(&ex,m.lookup((*pentry)->getProviderId())); // throws it
+            }
+        }
+
+        // and that the session is still valid...
+        if (!(*pentry)->isValid(lifetime,timeout)) {
+            log->debug("session expired");
+            InvalidSessionException ex(SESSION_E_EXPIRED, "Your session has expired, and you must re-authenticate.");
+            Metadata m(app->getMetadataProviders());
+            annotateException(&ex,m.lookup((*pentry)->getProviderId())); // throws it
+        }
+    }
+    catch (SAMLException&) {
+        (*pentry)->unlock();
+        *pentry=NULL;
+        conf->getSessionCache()->remove(cookie);
+      
+        // Transaction Logging
+        Category::getInstance(SHIBTRAN_LOGCAT).infoStream() <<
+            "Destroyed invalid session (ID: " <<
+                cookie <<
+            ") with (applicationId: " <<
+                app->getId() <<
+            "), request was from (ClientAddress: " <<
+                ip <<
+            ")";
+        //stc.releaseTransactionLog();
+        throw;
+    }
+    catch (...) {
+        log->error("caught unknown exception");
+#ifndef _DEBUG
+        InvalidSessionException ex("An unexpected error occurred while validating your session, and you must re-authenticate.");
+        Metadata m(app->getMetadataProviders());
+        annotateException(&ex,m.lookup((*pentry)->getProviderId()),false);
+#endif
+        (*pentry)->unlock();
+        *pentry=NULL;
+        conf->getSessionCache()->remove(cookie);
+
+        // Transaction Logging
+        Category::getInstance(SHIBTRAN_LOGCAT).infoStream() <<
+            "Destroyed invalid session (ID: " <<
+                cookie <<
+            ") with (applicationId: " <<
+                app->getId() <<
+            "), request was from (ClientAddress: " <<
+                ip <<
+            ")";
+        //stc.releaseTransactionLog();
+#ifdef _DEBUG
+        throw;
+#else
+        ex.raise();
+#endif
+    }
+
+    log->debug("session ok");
+}
+
+void ADFSListener::sessionEnd(
+    const IApplication* application,
+    const char* cookie
+    ) const
+{
+#ifdef _DEBUG
+    saml::NDC ndc("sessionEnd");
+#endif
+
+    log->debug("removing session: %s", cookie);
+
+    ShibTargetConfig& stc=ShibTargetConfig::getConfig();
+    stc.getINI()->getSessionCache()->remove(cookie);
+  
+    // Transaction Logging
+    Category::getInstance(SHIBTRAN_LOGCAT).infoStream() << "Destroyed session (ID: " << cookie << ")";
+    //stc.releaseTransactionLog();
+}
+
+void ADFSListener::ping(int& i) const
+{
+    i++;
+}
diff --git a/adfs/resource.h b/adfs/resource.h
new file mode 100644 (file)
index 0000000..3ba1cce
--- /dev/null
@@ -0,0 +1,15 @@
+//{{NO_DEPENDENCIES}}
+// Microsoft Developer Studio generated include file.
+// Used by adfs.rc
+//
+
+// Next default values for new objects
+// 
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE        101
+#define _APS_NEXT_COMMAND_VALUE         40001
+#define _APS_NEXT_CONTROL_VALUE         1000
+#define _APS_NEXT_SYMED_VALUE           101
+#endif
+#endif