From 107aa0ce6433826ba116c3f65fa803a216c7b8cd Mon Sep 17 00:00:00 2001 From: Derek Atkins Date: Fri, 1 Apr 2005 16:07:33 +0000 Subject: [PATCH] Pull nsapi_shib up to HEAD. --- nsapi_shib/.gitignore | 6 + nsapi_shib/Makefile.am | 23 ++ nsapi_shib/nsapi_shib.cpp | 822 ++++++++++++++++++++++++++++++++++++++++++++++ nsapi_shib/nsapi_shib.dsp | 103 ++++++ nsapi_shib/nsapi_shib.rc | 109 ++++++ nsapi_shib/resource.h | 15 + 6 files changed, 1078 insertions(+) create mode 100644 nsapi_shib/.gitignore create mode 100644 nsapi_shib/Makefile.am create mode 100644 nsapi_shib/nsapi_shib.cpp create mode 100644 nsapi_shib/nsapi_shib.dsp create mode 100644 nsapi_shib/nsapi_shib.rc create mode 100644 nsapi_shib/resource.h diff --git a/nsapi_shib/.gitignore b/nsapi_shib/.gitignore new file mode 100644 index 0000000..058c8a6 --- /dev/null +++ b/nsapi_shib/.gitignore @@ -0,0 +1,6 @@ +/*.plg +/Release +/Debug +/*.dep +/*.mak +/*.aps \ No newline at end of file diff --git a/nsapi_shib/Makefile.am b/nsapi_shib/Makefile.am new file mode 100644 index 0000000..d82a617 --- /dev/null +++ b/nsapi_shib/Makefile.am @@ -0,0 +1,23 @@ +AUTOMAKE_OPTIONS = foreign + +if USE_OUR_ONCRPC +RPC_CFLAGS = -I${top_srcdir}/oncrpc +endif + +if BUILD_NSAPI +nsapi_shibdir = $(libexecdir) +nsapi_shib_LTLIBRARIES = nsapi_shib.la +nsapi_shib_la_SOURCES = nsapi_shib.cpp +nsapi_shib_la_CXXFLAGS = $(NSAPI_INCLUDE) $(RPC_CFLAGS) +nsapi_shib_la_LDFLAGS = -module -avoid-version +nsapi_shib_la_LIBADD = \ + $(top_builddir)/shib/libshib.la \ + $(top_builddir)/shib-target/libshib-target.la + +install-exec-hook: + for la in $(nsapi_shib_LTLIBRARIES) ; do rm -f $(DESTDIR)$(nsapi_shibdir)/$$la ; done + +endif + +EXTRA_DIST = nsapi_shib.dsp resource.h nsapi_shib.rc nsapi_shib.cpp + diff --git a/nsapi_shib/nsapi_shib.cpp b/nsapi_shib/nsapi_shib.cpp new file mode 100644 index 0000000..102b5f4 --- /dev/null +++ b/nsapi_shib/nsapi_shib.cpp @@ -0,0 +1,822 @@ +/* + * The Shibboleth License, Version 1. + * Copyright (c) 2002 + * University Corporation for Advanced Internet Development, Inc. + * All rights reserved + * + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution, if any, must include + * the following acknowledgment: "This product includes software developed by + * the University Corporation for Advanced Internet Development + * Internet2 Project. Alternately, this acknowledegement + * may appear in the software itself, if and wherever such third-party + * acknowledgments normally appear. + * + * Neither the name of Shibboleth nor the names of its contributors, nor + * Internet2, nor the University Corporation for Advanced Internet Development, + * Inc., nor UCAID may be used to endorse or promote products derived from this + * software without specific prior written permission. For written permission, + * please contact shibboleth@shibboleth.org + * + * Products derived from this software may not be called Shibboleth, Internet2, + * UCAID, or the University Corporation for Advanced Internet Development, nor + * may Shibboleth appear in their name, without prior written permission of the + * University Corporation for Advanced Internet Development. + * + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND WITH ALL FAULTS. ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A + * PARTICULAR PURPOSE, AND NON-INFRINGEMENT ARE DISCLAIMED AND THE ENTIRE RISK + * OF SATISFACTORY QUALITY, PERFORMANCE, ACCURACY, AND EFFORT IS WITH LICENSEE. + * IN NO EVENT SHALL THE COPYRIGHT OWNER, CONTRIBUTORS OR THE UNIVERSITY + * CORPORATION FOR ADVANCED INTERNET DEVELOPMENT, INC. BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* nsapi_shib.cpp - Shibboleth NSAPI filter + + Scott Cantor + 12/13/04 +*/ + +#include "config_win32.h" + +// SAML Runtime +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#ifdef WIN32 +# define XP_WIN32 +#else +# define XP_UNIX +#endif + +#define MCC_HTTPD +#define NET_SSL + +extern "C" +{ +#include +} + +using namespace std; +using namespace log4cpp; +using namespace saml; +using namespace shibboleth; +using namespace shibtarget; + +// macros to output text to client +#define NET_WRITE(str) \ + if (IO_ERROR==net_write(sn->csd,str,strlen(str))) return REQ_EXIT + +#define NET_WRITE1(buf,fmstr,param) \ + do { sprintf(buf,fmstr,param); NET_WRITE(buf); } while(0) + +#define NET_WRITE2(buf,fmstr,param1,param2) \ + do { sprintf(buf,fmstr,param1,param2); NET_WRITE(buf); } while(0) + +#define NET_WRITE3(buf,fmstr,param1,param2,param3) \ + do { sprintf(buf,fmstr,param1,param2,param3); NET_WRITE(buf); } while(0) + +#define NET_WRITE4(buf,fmstr,param1,param2,param3,param4) \ + do { sprintf(buf,fmstr,param1,param2,param3,param4); NET_WRITE(buf); } while(0) + +namespace { + ShibTargetConfig* g_Config=NULL; + string g_ServerName; + string g_ServerScheme; +} + +extern "C" NSAPI_PUBLIC void nsapi_shib_exit(void*) +{ + if (g_Config) + g_Config->shutdown(); + g_Config = NULL; +} + +extern "C" NSAPI_PUBLIC int nsapi_shib_init(pblock* pb, Session* sn, Request* rq) +{ + // Save off a default hostname for this virtual server. + char* name=pblock_findval("server-name",pb); + if (name) + g_ServerName=name; + else { + name=server_hostname; + if (name) + g_ServerName=name; + else { + name=util_hostname(); + if (name) { + g_ServerName=name; + FREE(name); + } + else { + pblock_nvinsert("error","unable to determine web server hostname",pb); + return REQ_ABORTED; + } + } + } + name=pblock_findval("server-scheme",pb); + if (name) + g_ServerScheme=name; + + log_error(LOG_INFORM,"nsapi_shib_init",sn,rq,"nsapi_shib loaded for host (%s)",g_ServerName.c_str()); + + try + { + const char* schemadir=pblock_findval("shib-schemas",pb); + if (!schemadir) + schemadir=getenv("SHIBSCHEMAS"); + if (!schemadir) + schemadir=SHIB_SCHEMAS; + const char* config=pblock_findval("shib-config",pb); + if (!config) + config=getenv("SHIBCONFIG"); + if (!config) + config=SHIB_CONFIG; + g_Config=&ShibTargetConfig::getConfig(); + g_Config->setFeatures( + ShibTargetConfig::Listener | + ShibTargetConfig::Metadata | + ShibTargetConfig::AAP | + ShibTargetConfig::RequestMapper | + ShibTargetConfig::SHIREExtensions | + ShibTargetConfig::Logging + ); + if (!g_Config->init(schemadir,config)) { + g_Config=NULL; + pblock_nvinsert("error","unable to initialize Shibboleth libraries",pb); + return REQ_ABORTED; + } + + daemon_atrestart(nsapi_shib_exit,NULL); + } + catch (...) + { +#ifdef _DEBUG + throw; +#endif + g_Config=NULL; + pblock_nvinsert("error","caught exception, unable to initialize Shibboleth libraries",pb); + return REQ_ABORTED; + } + return REQ_PROCEED; +} + +IRequestMapper::Settings map_request(pblock* pb, Session* sn, Request* rq, IRequestMapper* mapper, string& target) +{ + // Get everything but hostname... + const char* uri=pblock_findval("uri",rq->reqpb); + const char* qstr=pblock_findval("query",rq->reqpb); + int port=server_portnum; + const char* scheme=security_active ? "https" : "http"; + const char* host=NULL; + + string url; + if (uri) + url=uri; + if (qstr) + url=url + '?' + qstr; + +#ifdef vs_is_default_vs + // This is 6.0 or later, so we can distinguish requests to name-based vhosts. + if (!vs_is_default_vs) + // The beauty here is, a non-default vhost can *only* be accessed if the client + // specified the exact name in the Host header. So we can trust the Host header. + host=pblock_findval("host", rq->headers); + else +#endif + // In other cases, we're going to rely on the initialization process... + host=g_ServerName.c_str(); + + target=(g_ServerScheme.empty() ? string(scheme) : g_ServerScheme) + "://" + host; + + // If port is non-default, append it. + if ((!security_active && port!=80) || (security_active && port!=443)) { + char portbuf[10]; + util_snprintf(portbuf,9,"%d",port); + target = target + ':' + portbuf; + } + + target+=url; + + return mapper->getSettingsFromParsedURL(scheme,host,port,url.c_str()); +} + +int WriteClientError(Session* sn, Request* rq, char* func, char* msg) +{ + log_error(LOG_FAILURE,func,sn,rq,msg); + protocol_status(sn,rq,PROTOCOL_SERVER_ERROR,msg); + return REQ_ABORTED; +} + +int WriteClientError(Session* sn, Request* rq, const IApplication* app, const char* page, ShibMLP& mlp) +{ + const IPropertySet* props=app->getPropertySet("Errors"); + if (props) { + pair p=props->getString(page); + if (p.first) { + ifstream infile(p.second); + if (!infile.fail()) { + const char* res = mlp.run(infile,props); + if (res) { + pblock_nvinsert("Content-Type","text/html",rq->srvhdrs); + pblock_nninsert("Content-Length",strlen(res),rq->srvhdrs); + pblock_nvinsert("Connection","close",rq->srvhdrs); + protocol_status(sn,rq,PROTOCOL_OK,NULL); + NET_WRITE(const_cast(res)); + return REQ_EXIT; + } + } + } + } + + log_error(LOG_FAILURE,"WriteClientError",sn,rq,"Unable to open error template, check settings."); + protocol_status(sn,rq,PROTOCOL_SERVER_ERROR,"Unable to open error template, check settings."); + return REQ_ABORTED; +} + +int WriteRedirectPage(Session* sn, Request* rq, const IApplication* app, const char* file, ShibMLP& mlp) +{ + ifstream infile(file); + if (!infile.fail()) { + const char* res = mlp.run(infile,app->getPropertySet("Errors")); + if (res) { + pblock_nvinsert("Content-Type","text/html",rq->srvhdrs); + pblock_nninsert("Content-Length",strlen(res),rq->srvhdrs); + protocol_status(sn,rq,PROTOCOL_OK,NULL); + NET_WRITE(const_cast(res)); + return REQ_EXIT; + } + } + log_error(LOG_FAILURE,"WriteRedirectPage",sn,rq,"Unable to open redirect template, check settings."); + protocol_status(sn,rq,PROTOCOL_SERVER_ERROR,"Unable to open redirect template, check settings."); + return REQ_ABORTED; +} + +#undef FUNC +#define FUNC "shibboleth" +extern "C" NSAPI_PUBLIC int nsapi_shib(pblock* pb, Session* sn, Request* rq) +{ + try + { + ostringstream threadid; + threadid << "[" << getpid() << "] nsapi_shib" << '\0'; + saml::NDC ndc(threadid.str().c_str()); + + // We lock the configuration system for the duration. + IConfig* conf=g_Config->getINI(); + Locker locker(conf); + + // Map request to application and content settings. + string targeturl; + IRequestMapper* mapper=conf->getRequestMapper(); + Locker locker2(mapper); + IRequestMapper::Settings settings=map_request(pb,sn,rq,mapper,targeturl); + pair application_id=settings.first->getString("applicationId"); + const IApplication* application=conf->getApplication(application_id.second); + if (!application) + return WriteClientError(sn,rq,FUNC,"Unable to map request to application settings, check configuration."); + + // Declare SHIRE object for this request. + SHIRE shire(application); + + const char* shireURL=shire.getShireURL(targeturl.c_str()); + if (!shireURL) + return WriteClientError(sn,rq,FUNC,"Unable to map request to proper shireURL setting, check configuration."); + + // If the user is accessing the SHIRE acceptance point, pass it on. + if (targeturl.find(shireURL)!=string::npos) + return REQ_PROCEED; + + // Now check the policy for this request. + pair requireSession=settings.first->getBool("requireSession"); + if (!requireSession.first || !requireSession.second) { + const char* param=pblock_findval("require-session",pb); + if (param && (!strcmp(param,"1") || !strcasecmp(param,"true"))) + requireSession.second=true; + } + pair shib_cookie=shire.getCookieNameProps(); + pair httpRedirects=application->getPropertySet("Sessions")->getBool("httpRedirects"); + pair redirectPage=application->getPropertySet("Sessions")->getString("redirectPage"); + if (httpRedirects.first && !httpRedirects.second && !redirectPage.first) + return WriteClientError(sn,rq,FUNC,"HTML-based redirection requires a redirectPage property."); + + // Check for session cookie. + const char* session_id=NULL; + string cookie; + if (request_header("cookie",(char**)&session_id,sn,rq)==REQ_ABORTED) + return WriteClientError(sn,rq,FUNC,"error accessing cookie header"); + + Category::getInstance("nsapi_shib."FUNC).debug("cookie header is {%s}",session_id ? session_id : "NULL"); + if (session_id && (session_id=strstr(session_id,shib_cookie.first))) { + session_id+=strlen(shib_cookie.first) + 1; /* Skip over the '=' */ + char* cookieend=strchr(session_id,';'); + if (cookieend) { + // Chop out just the value portion. + cookie.assign(session_id,cookieend-session_id-1); + session_id=cookie.c_str(); + } + } + + if (!session_id || !*session_id) { + // If no session required, bail now. + if (!requireSession.second) + return REQ_PROCEED; + + // No acceptable cookie, and we require a session. Generate an AuthnRequest. + const char* areq = shire.getAuthnRequest(targeturl.c_str()); + if (!httpRedirects.first || httpRedirects.second) { + pblock_nvinsert("Content-Type","text/html",rq->srvhdrs); + pblock_nvinsert("Content-Length","40",rq->srvhdrs); + pblock_nvinsert("Expires","01-Jan-1997 12:00:00 GMT",rq->srvhdrs); + pblock_nvinsert("Cache-Control","private,no-store,no-cache",rq->srvhdrs); + pblock_nvinsert("Location",areq,rq->srvhdrs); + protocol_status(sn,rq,PROTOCOL_REDIRECT,"302 Please wait"); + protocol_start_response(sn,rq); + NET_WRITE("Redirecting..."); + return REQ_EXIT; + } + else { + ShibMLP markupProcessor; + markupProcessor.insert("requestURL",areq); + return WriteRedirectPage(sn, rq, application, redirectPage.second, markupProcessor); + } + } + + // Make sure this session is still valid. + RPCError* status = NULL; + ShibMLP markupProcessor; + markupProcessor.insert("requestURL", targeturl); + + try { + status = shire.sessionIsValid(session_id, pblock_findval("ip",sn->client)); + } + catch (ShibTargetException &e) { + markupProcessor.insert("errorType", "Session Processing Error"); + markupProcessor.insert("errorText", e.what()); + markupProcessor.insert("errorDesc", "An error occurred while processing your request."); + return WriteClientError(sn, rq, application, "shire", markupProcessor); + } +#ifndef _DEBUG + catch (...) { + markupProcessor.insert("errorType", "Session Processing Error"); + markupProcessor.insert("errorText", "Unexpected Exception"); + markupProcessor.insert("errorDesc", "An error occurred while processing your request."); + return WriteClientError(sn, rq, application, "shire", markupProcessor); + } +#endif + + // Check the status + if (status->isError()) { + if (!requireSession.second) + return REQ_PROCEED; + else if (status->isRetryable()) { + // Oops, session is invalid. Generate AuthnRequest. + delete status; + const char* areq = shire.getAuthnRequest(targeturl.c_str()); + if (!httpRedirects.first || httpRedirects.second) { + pblock_nvinsert("Content-Type","text/html",rq->srvhdrs); + pblock_nvinsert("Content-Length","40",rq->srvhdrs); + pblock_nvinsert("Expires","01-Jan-1997 12:00:00 GMT",rq->srvhdrs); + pblock_nvinsert("Cache-Control","private,no-store,no-cache",rq->srvhdrs); + pblock_nvinsert("Location",areq,rq->srvhdrs); + protocol_status(sn,rq,PROTOCOL_REDIRECT,"302 Please wait"); + protocol_start_response(sn,rq); + NET_WRITE("Redirecting..."); + return REQ_EXIT; + } + else { + markupProcessor.insert("requestURL",areq); + return WriteRedirectPage(sn, rq, application, redirectPage.second, markupProcessor); + } + } + else { + // return the error page to the user + markupProcessor.insert(*status); + delete status; + return WriteClientError(sn, rq, application, "shire", markupProcessor); + } + } + delete status; + + // Move to RM phase. + RM rm(application); + vector assertions; + SAMLAuthenticationStatement* sso_statement=NULL; + + try { + status = rm.getAssertions(session_id, pblock_findval("ip",sn->client), assertions, &sso_statement); + } + catch (ShibTargetException &e) { + markupProcessor.insert("errorType", "Attribute Processing Error"); + markupProcessor.insert("errorText", e.what()); + markupProcessor.insert("errorDesc", "An error occurred while processing your request."); + return WriteClientError(sn, rq, application, "rm", markupProcessor); + } + #ifndef _DEBUG + catch (...) { + markupProcessor.insert("errorType", "Attribute Processing Error"); + markupProcessor.insert("errorText", "Unexpected Exception"); + markupProcessor.insert("errorDesc", "An error occurred while processing your request."); + return WriteClientError(sn, rq, application, "rm", markupProcessor); + } + #endif + + if (status->isError()) { + markupProcessor.insert(*status); + delete status; + return WriteClientError(sn, rq, application, "rm", markupProcessor); + } + delete status; + + // Do we have an access control plugin? + if (settings.second) { + Locker acllock(settings.second); + if (!settings.second->authorized(*sso_statement,assertions)) { + for (int k = 0; k < assertions.size(); k++) + delete assertions[k]; + delete sso_statement; + return WriteClientError(sn, rq, application, "access", markupProcessor); + } + } + + // Get the AAP providers, which contain the attribute policy info. + Iterator provs=application->getAAPProviders(); + + // Clear out the list of mapped attributes + while (provs.hasNext()) { + IAAP* aap=provs.next(); + aap->lock(); + try { + Iterator rules=aap->getAttributeRules(); + while (rules.hasNext()) { + const char* header=rules.next()->getHeader(); + if (header) + param_free(pblock_remove(header,rq->headers)); + } + } + catch(...) { + aap->unlock(); + for (int k = 0; k < assertions.size(); k++) + delete assertions[k]; + delete sso_statement; + markupProcessor.insert("errorType", "Attribute Processing Error"); + markupProcessor.insert("errorText", "Unexpected Exception"); + markupProcessor.insert("errorDesc", "An error occurred while processing your request."); + return WriteClientError(sn, rq, application, "rm", markupProcessor); + } + aap->unlock(); + } + provs.reset(); + + // Maybe export the first assertion. + param_free(pblock_remove("remote-user",rq->headers)); + param_free(pblock_remove("auth-user",rq->vars)); + param_free(pblock_remove("Shib-Attributes",rq->headers)); + pair exp=settings.first->getBool("exportAssertion"); + if (!exp.first || !exp.second) { + const char* param=pblock_findval("export-assertion",pb); + if (param && (!strcmp(param,"1") || !strcasecmp(param,"true"))) + exp.second=true; + } + if (exp.second && assertions.size()) { + string assertion; + RM::serialize(*(assertions[0]), assertion); + string::size_type lfeed; + while ((lfeed=assertion.find('\n'))!=string::npos) + assertion.erase(lfeed,1); + pblock_nvinsert("Shib-Attributes",assertion.c_str(),rq->headers); + } + + pblock_nvinsert("auth-type","shibboleth",rq->vars); + param_free(pblock_remove("Shib-Origin-Site",rq->headers)); + param_free(pblock_remove("Shib-Authentication-Method",rq->headers)); + param_free(pblock_remove("Shib-NameIdentifier-Format",rq->headers)); + + // Export the SAML AuthnMethod and the origin site name. + auto_ptr_char os(sso_statement->getSubject()->getNameIdentifier()->getNameQualifier()); + auto_ptr_char am(sso_statement->getAuthMethod()); + pblock_nvinsert("Shib-Origin-Site",os.get(),rq->headers); + pblock_nvinsert("Shib-Authentication-Method",am.get(),rq->headers); + + // Export NameID? + AAP wrapper(provs,sso_statement->getSubject()->getNameIdentifier()->getFormat(),Constants::SHIB_ATTRIBUTE_NAMESPACE_URI); + if (!wrapper.fail() && wrapper->getHeader()) { + auto_ptr_char form(sso_statement->getSubject()->getNameIdentifier()->getFormat()); + auto_ptr_char nameid(sso_statement->getSubject()->getNameIdentifier()->getName()); + pblock_nvinsert("Shib-NameIdentifier-Format",form.get(),pb); + if (!strcmp(wrapper->getHeader(),"REMOTE_USER")) { + pblock_nvinsert("remote-user",nameid.get(),rq->headers); + pblock_nvinsert("auth-user",nameid.get(),rq->vars); + } + else { + pblock_nvinsert(wrapper->getHeader(),nameid.get(),rq->headers); + } + } + + param_free(pblock_remove("Shib-Application-ID",rq->headers)); + pblock_nvinsert("Shib-Application-ID",application_id.second,rq->headers); + + // Export the attributes. + Iterator a_iter(assertions); + while (a_iter.hasNext()) { + SAMLAssertion* assert=a_iter.next(); + Iterator statements=assert->getStatements(); + while (statements.hasNext()) { + SAMLAttributeStatement* astate=dynamic_cast(statements.next()); + if (!astate) + continue; + Iterator attrs=astate->getAttributes(); + while (attrs.hasNext()) { + SAMLAttribute* attr=attrs.next(); + + // Are we supposed to export it? + AAP wrapper(provs,attr->getName(),attr->getNamespace()); + if (wrapper.fail() || !wrapper->getHeader()) + continue; + + Iterator vals=attr->getSingleByteValues(); + if (!strcmp(wrapper->getHeader(),"REMOTE_USER") && vals.hasNext()) { + char* principal=const_cast(vals.next().c_str()); + pblock_nvinsert("remote-user",principal,rq->headers); + pblock_nvinsert("auth-user",principal,rq->vars); + } + else { + int it=0; + string header; + const char* h=pblock_findval(wrapper->getHeader(),rq->headers); + if (h) { + header=h; + param_free(pblock_remove(wrapper->getHeader(),rq->headers)); + it++; + } + for (; vals.hasNext(); it++) { + string value = vals.next(); + for (string::size_type pos = value.find_first_of(";", string::size_type(0)); + pos != string::npos; + pos = value.find_first_of(";", pos)) { + value.insert(pos, "\\"); + pos += 2; + } + if (it == 0) + header=value; + else + header=header + ';' + value; + } + pblock_nvinsert(wrapper->getHeader(),header.c_str(),rq->headers); + } + } + } + } + + // clean up memory + for (int k = 0; k < assertions.size(); k++) + delete assertions[k]; + delete sso_statement; + + return REQ_PROCEED; + } + catch(bad_alloc) { + return WriteClientError(sn, rq, FUNC,"Out of Memory"); + } +#ifndef _DEBUG + catch(...) { + return WriteClientError(sn, rq, FUNC,"Server caught an unknown exception."); + } +#endif + + return WriteClientError(sn, rq, FUNC,"Server reached unreachable code, save my walrus!"); +} + +#undef FUNC +#define FUNC "shib_handler" +extern "C" NSAPI_PUBLIC int shib_handler(pblock* pb, Session* sn, Request* rq) +{ + string targeturl; + const IApplication* application=NULL; + try + { + ostringstream threadid; + threadid << "[" << getpid() << "] shib_handler" << '\0'; + saml::NDC ndc(threadid.str().c_str()); + + // We lock the configuration system for the duration. + IConfig* conf=g_Config->getINI(); + Locker locker(conf); + + // Map request to application and content settings. + IRequestMapper* mapper=conf->getRequestMapper(); + Locker locker2(mapper); + IRequestMapper::Settings settings=map_request(pb,sn,rq,mapper,targeturl); + pair application_id=settings.first->getString("applicationId"); + application=conf->getApplication(application_id.second); + const IPropertySet* sessionProps=application ? application->getPropertySet("Sessions") : NULL; + if (!application || !sessionProps) + return WriteClientError(sn,rq,FUNC,"Unable to map request to application settings, check configuration."); + + SHIRE shire(application); + + const char* shireURL=shire.getShireURL(targeturl.c_str()); + if (!shireURL) + return WriteClientError(sn,rq,FUNC,"Unable to map request to proper shireURL setting, check configuration."); + + // Make sure we only process the SHIRE requests. + if (!strstr(targeturl.c_str(),shireURL)) + return WriteClientError(sn,rq,FUNC,"NSAPI service function can only be invoked to process incoming sessions." + "Make sure the mapped file extension or URL doesn't match actual content."); + + pair shib_cookie=shire.getCookieNameProps(); + + // Make sure this is SSL, if it should be + pair shireSSL=sessionProps->getBool("shireSSL"); + if (!shireSSL.first || shireSSL.second) { + if (!security_active) + throw ShibTargetException(SHIBRPC_OK,"blocked non-SSL access to Shibboleth session processor"); + } + + pair httpRedirects=sessionProps->getBool("httpRedirects"); + pair redirectPage=sessionProps->getString("redirectPage"); + if (httpRedirects.first && !httpRedirects.second && !redirectPage.first) + return WriteClientError(sn,rq,FUNC,"HTML-based redirection requires a redirectPage property."); + + // If this is a GET, we manufacture an AuthnRequest. + if (!strcasecmp(pblock_findval("method",rq->reqpb),"GET")) { + const char* areq=pblock_findval("query",rq->reqpb) ? shire.getLazyAuthnRequest(pblock_findval("query",rq->reqpb)) : NULL; + if (!areq) + throw ShibTargetException(SHIBRPC_OK, "malformed arguments to request a new session"); + if (!httpRedirects.first || httpRedirects.second) { + pblock_nvinsert("Content-Type","text/html",rq->srvhdrs); + pblock_nvinsert("Content-Length","40",rq->srvhdrs); + pblock_nvinsert("Expires","01-Jan-1997 12:00:00 GMT",rq->srvhdrs); + pblock_nvinsert("Cache-Control","private,no-store,no-cache",rq->srvhdrs); + pblock_nvinsert("Location",areq,rq->srvhdrs); + protocol_status(sn,rq,PROTOCOL_REDIRECT,"302 Please wait"); + protocol_start_response(sn,rq); + NET_WRITE("Redirecting..."); + return REQ_EXIT; + } + else { + ShibMLP markupProcessor; + markupProcessor.insert("requestURL",areq); + return WriteRedirectPage(sn, rq, application, redirectPage.second, markupProcessor); + } + } + else if (strcasecmp(pblock_findval("method",rq->reqpb),"POST")) + throw ShibTargetException(SHIBRPC_OK,"blocked non-POST to Shibboleth session processor"); + + // Make sure this POST is an appropriate content type + char* content_type=NULL; + if (request_header("content-type",&content_type,sn,rq)!=REQ_PROCEED || + !content_type || strcasecmp(content_type,"application/x-www-form-urlencoded")) + throw ShibTargetException(SHIBRPC_OK,"blocked bad content-type to Shibboleth session processor"); + + // Read the data. + pair elements=pair(NULL,NULL); + char* content_length=NULL; + if (request_header("content-length",&content_length,sn,rq)!=REQ_PROCEED || + atoi(content_length) > 1024*1024) // 1MB? + throw ShibTargetException(SHIBRPC_OK,"blocked too-large a post to Shibboleth session processor"); + else { + char ch=IO_EOF+1; + int cl=atoi(content_length); + string cgistr; + while (cl && ch!=IO_EOF) { + ch=netbuf_getc(sn->inbuf); + + // Check for error. + if(ch==IO_ERROR) + break; + cgistr+=ch; + cl--; + } + if (cl) + throw ShibTargetException(SHIBRPC_OK,"error reading POST data from browser"); + elements=shire.getFormSubmission(cgistr.c_str(),cgistr.length()); + } + + // Make sure the SAML Response parameter exists + if (!elements.first || !*elements.first) + throw ShibTargetException(SHIBRPC_OK, "Shibboleth POST failed to find SAMLResponse form element"); + + // Make sure the target parameter exists + if (!elements.second || !*elements.second) + throw ShibTargetException(SHIBRPC_OK, "Shibboleth POST failed to find TARGET form element"); + + // Process the post. + string cookie; + RPCError* status=NULL; + ShibMLP markupProcessor; + markupProcessor.insert("requestURL", targeturl.c_str()); + try { + status = shire.sessionCreate(elements.first,pblock_findval("ip",sn->client),cookie); + } + catch (ShibTargetException &e) { + markupProcessor.insert("errorType", "Session Creation Service Error"); + markupProcessor.insert("errorText", e.what()); + markupProcessor.insert("errorDesc", "An error occurred while processing your request."); + return WriteClientError(sn, rq, application, "shire", markupProcessor); + } +#ifndef _DEBUG + catch (...) { + markupProcessor.insert("errorType", "Session Creation Service Error"); + markupProcessor.insert("errorText", "Unexpected Exception"); + markupProcessor.insert("errorDesc", "An error occurred while processing your request."); + return WriteClientError(sn, rq, application, "shire", markupProcessor); + } +#endif + + if (status->isError()) { + if (status->isRetryable()) { + delete status; + const char* loc=shire.getAuthnRequest(elements.second); + if (!httpRedirects.first || httpRedirects.second) { + pblock_nvinsert("Content-Type","text/html",rq->srvhdrs); + pblock_nvinsert("Content-Length","40",rq->srvhdrs); + pblock_nvinsert("Expires","01-Jan-1997 12:00:00 GMT",rq->srvhdrs); + pblock_nvinsert("Cache-Control","private,no-store,no-cache",rq->srvhdrs); + pblock_nvinsert("Location",loc,rq->srvhdrs); + protocol_status(sn,rq,PROTOCOL_REDIRECT,"302 Please wait"); + protocol_start_response(sn,rq); + NET_WRITE("Redirecting..."); + return REQ_EXIT; + } + else { + markupProcessor.insert("requestURL",loc); + return WriteRedirectPage(sn, rq, application, redirectPage.second, markupProcessor); + } + } + + // Return this error to the user. + markupProcessor.insert(*status); + delete status; + return WriteClientError(sn,rq,application,"shire",markupProcessor); + } + delete status; + + // We've got a good session, set the cookie and redirect to target. + cookie = string(shib_cookie.first) + '=' + cookie + shib_cookie.second; + pblock_nvinsert("Set-Cookie",cookie.c_str(),rq->srvhdrs); + if (!httpRedirects.first || httpRedirects.second) { + pblock_nvinsert("Content-Type","text/html",rq->srvhdrs); + pblock_nvinsert("Content-Length","40",rq->srvhdrs); + pblock_nvinsert("Expires","01-Jan-1997 12:00:00 GMT",rq->srvhdrs); + pblock_nvinsert("Cache-Control","private,no-store,no-cache",rq->srvhdrs); + pblock_nvinsert("Location",elements.second,rq->srvhdrs); + protocol_status(sn,rq,PROTOCOL_REDIRECT,"302 Please wait"); + protocol_start_response(sn,rq); + NET_WRITE("Redirecting..."); + return REQ_EXIT; + } + else { + markupProcessor.insert("requestURL",elements.second); + return WriteRedirectPage(sn, rq, application, redirectPage.second, markupProcessor); + } + } + catch (ShibTargetException &e) { + if (application) { + ShibMLP markupProcessor; + markupProcessor.insert("requestURL", targeturl.c_str()); + markupProcessor.insert("errorType", "Session Creation Service Error"); + markupProcessor.insert("errorText", e.what()); + markupProcessor.insert("errorDesc", "An error occurred while processing your request."); + return WriteClientError(sn,rq,application,"shire",markupProcessor); + } + } +#ifndef _DEBUG + catch (...) { + if (application) { + ShibMLP markupProcessor; + markupProcessor.insert("requestURL", targeturl.c_str()); + markupProcessor.insert("errorType", "Session Creation Service Error"); + markupProcessor.insert("errorText", "Unexpected Exception"); + markupProcessor.insert("errorDesc", "An error occurred while processing your request."); + return WriteClientError(sn,rq,application,"shire",markupProcessor); + } + } +#endif + return REQ_EXIT; +} diff --git a/nsapi_shib/nsapi_shib.dsp b/nsapi_shib/nsapi_shib.dsp new file mode 100644 index 0000000..337c1be --- /dev/null +++ b/nsapi_shib/nsapi_shib.dsp @@ -0,0 +1,103 @@ +# Microsoft Developer Studio Project File - Name="nsapi_shib" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102 + +CFG=nsapi_shib - 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 "nsapi_shib.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 "nsapi_shib.mak" CFG="nsapi_shib - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "nsapi_shib - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "nsapi_shib - 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)" == "nsapi_shib - 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 "NSAPI_SHIB_EXPORTS" /YX /FD /c +# ADD CPP /nologo /MD /W3 /GR /GX /O2 /I ".." /I "..\..\..\opensaml\c" /I "..\oncrpc" /I "." /D "NDEBUG" /D "_WINDOWS" /D "WIN32" /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 xsec_1.lib saml_4.lib ns-httpd30.lib /nologo /dll /machine:I386 /libpath:"..\..\..\opensaml\c\saml\Release" /libpath:"\\KRAMER\iPlanet\plugins\lib" + +!ELSEIF "$(CFG)" == "nsapi_shib - 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 "NSAPI_SHIB_EXPORTS" /YX /FD /GZ /c +# ADD CPP /nologo /MDd /W3 /Gm /GR /GX /ZI /Od /I ".." /I "..\..\..\opensaml\c" /I "..\oncrpc" /I "." /D "_DEBUG" /D "_AFXDLL" /D "_WINDOWS" /D "WIN32" /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 xsec_1D.lib saml_4D.lib ns-httpd30.lib /nologo /dll /debug /machine:I386 /pdbtype:sept /libpath:"..\..\..\opensaml\c\saml\Debug" /libpath:"\\KRAMER\iPlanet\plugins\lib" + +!ENDIF + +# Begin Target + +# Name "nsapi_shib - Win32 Release" +# Name "nsapi_shib - Win32 Debug" +# Begin Source File + +SOURCE=.\nsapi_shib.cpp +# End Source File +# Begin Source File + +SOURCE=.\nsapi_shib.rc +# End Source File +# Begin Source File + +SOURCE=.\resource.h +# End Source File +# End Target +# End Project diff --git a/nsapi_shib/nsapi_shib.rc b/nsapi_shib/nsapi_shib.rc new file mode 100644 index 0000000..0a41683 --- /dev/null +++ b/nsapi_shib/nsapi_shib.rc @@ -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 + +#ifndef _MAC +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,2,1,0 + PRODUCTVERSION 1,2,1,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", "UCAID\0" + VALUE "FileDescription", "Shibboleth NSAPI Extension\0" + VALUE "FileVersion", "1, 2, 1, 0\0" + VALUE "InternalName", "nsapi_shib\0" + VALUE "LegalCopyright", "Copyright © 2004 UCAID\0" + VALUE "LegalTrademarks", "\0" + VALUE "OriginalFilename", "nsapi_shib.dll\0" + VALUE "PrivateBuild", "\0" + VALUE "ProductName", "Shibboleth\0" + VALUE "ProductVersion", "1, 2, 1, 0\0" + VALUE "SpecialBuild", "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + +#endif // !_MAC + + +#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 + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/nsapi_shib/resource.h b/nsapi_shib/resource.h new file mode 100644 index 0000000..70749e6 --- /dev/null +++ b/nsapi_shib/resource.h @@ -0,0 +1,15 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Developer Studio generated include file. +// Used by nsapi_shib.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 -- 2.1.4