Pull nsapi_shib up to HEAD.
authorwarlord <warlord@cb58f699-b61c-0410-a6fe-9272a202ed29>
Fri, 1 Apr 2005 16:07:33 +0000 (16:07 +0000)
committerwarlord <warlord@cb58f699-b61c-0410-a6fe-9272a202ed29>
Fri, 1 Apr 2005 16:07:33 +0000 (16:07 +0000)
git-svn-id: https://svn.middleware.georgetown.edu/cpp-sp/trunk@1471 cb58f699-b61c-0410-a6fe-9272a202ed29

nsapi_shib/.cvsignore [new file with mode: 0644]
nsapi_shib/Makefile.am [new file with mode: 0644]
nsapi_shib/nsapi_shib.cpp [new file with mode: 0644]
nsapi_shib/nsapi_shib.dsp [new file with mode: 0644]
nsapi_shib/nsapi_shib.rc [new file with mode: 0644]
nsapi_shib/resource.h [new file with mode: 0644]

diff --git a/nsapi_shib/.cvsignore b/nsapi_shib/.cvsignore
new file mode 100644 (file)
index 0000000..df1b5b9
--- /dev/null
@@ -0,0 +1,6 @@
+*.plg
+Release
+Debug
+*.dep
+*.mak
+*.aps
diff --git a/nsapi_shib/Makefile.am b/nsapi_shib/Makefile.am
new file mode 100644 (file)
index 0000000..d82a617
--- /dev/null
@@ -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 (file)
index 0000000..102b5f4
--- /dev/null
@@ -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
+ * <http://www.ucaid.edu>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 <saml/saml.h>
+#include <shib/shib.h>
+#include <shib/shib-threads.h>
+#include <shib-target/shib-target.h>
+
+#include <log4cpp/Category.hh>
+
+#include <ctime>
+#include <fstream>
+#include <sstream>
+#include <stdexcept>
+
+#ifdef WIN32
+# define XP_WIN32
+#else
+# define XP_UNIX
+#endif
+
+#define MCC_HTTPD
+#define NET_SSL
+
+extern "C"
+{
+#include <nsapi.h>
+}
+
+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<bool,const char*> 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<char*>(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<char*>(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<bool,const char*> 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<bool,bool> 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<const char*,const char*> shib_cookie=shire.getCookieNameProps();
+        pair<bool,bool> httpRedirects=application->getPropertySet("Sessions")->getBool("httpRedirects");
+        pair<bool,const char*> 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("<HTML><BODY>Redirecting...</BODY></HTML>");
+                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("<HTML><BODY>Redirecting...</BODY></HTML>");
+                    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<SAMLAssertion*> 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<IAAP*> provs=application->getAAPProviders();
+    
+        // Clear out the list of mapped attributes
+        while (provs.hasNext()) {
+            IAAP* aap=provs.next();
+            aap->lock();
+            try {
+                Iterator<const IAttributeRule*> 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<bool,bool> 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<SAMLAssertion*> a_iter(assertions);
+        while (a_iter.hasNext()) {
+            SAMLAssertion* assert=a_iter.next();
+            Iterator<SAMLStatement*> statements=assert->getStatements();
+            while (statements.hasNext()) {
+                SAMLAttributeStatement* astate=dynamic_cast<SAMLAttributeStatement*>(statements.next());
+                if (!astate)
+                    continue;
+                Iterator<SAMLAttribute*> 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<string> vals=attr->getSingleByteValues();
+                    if (!strcmp(wrapper->getHeader(),"REMOTE_USER") && vals.hasNext()) {
+                        char* principal=const_cast<char*>(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<bool,const char*> 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<const char*,const char*> shib_cookie=shire.getCookieNameProps();
+
+        // Make sure this is SSL, if it should be
+        pair<bool,bool> 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<bool,bool> httpRedirects=sessionProps->getBool("httpRedirects");
+        pair<bool,const char*> 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("<HTML><BODY>Redirecting...</BODY></HTML>");
+                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<const char*,const char*> elements=pair<const char*,const char*>(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("<HTML><BODY>Redirecting...</BODY></HTML>");
+                    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("<HTML><BODY>Redirecting...</BODY></HTML>");
+            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 (file)
index 0000000..337c1be
--- /dev/null
@@ -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 (file)
index 0000000..0a41683
--- /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
+
+#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 (file)
index 0000000..70749e6
--- /dev/null
@@ -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