2 * The Shibboleth License, Version 1.
4 * University Corporation for Advanced Internet Development, Inc.
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions are met:
11 * Redistributions of source code must retain the above copyright notice, this
12 * list of conditions and the following disclaimer.
14 * Redistributions in binary form must reproduce the above copyright notice,
15 * this list of conditions and the following disclaimer in the documentation
16 * and/or other materials provided with the distribution, if any, must include
17 * the following acknowledgment: "This product includes software developed by
18 * the University Corporation for Advanced Internet Development
19 * <http://www.ucaid.edu>Internet2 Project. Alternately, this acknowledegement
20 * may appear in the software itself, if and wherever such third-party
21 * acknowledgments normally appear.
23 * Neither the name of Shibboleth nor the names of its contributors, nor
24 * Internet2, nor the University Corporation for Advanced Internet Development,
25 * Inc., nor UCAID may be used to endorse or promote products derived from this
26 * software without specific prior written permission. For written permission,
27 * please contact shibboleth@shibboleth.org
29 * Products derived from this software may not be called Shibboleth, Internet2,
30 * UCAID, or the University Corporation for Advanced Internet Development, nor
31 * may Shibboleth appear in their name, without prior written permission of the
32 * University Corporation for Advanced Internet Development.
35 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
36 * AND WITH ALL FAULTS. ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
37 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
38 * PARTICULAR PURPOSE, AND NON-INFRINGEMENT ARE DISCLAIMED AND THE ENTIRE RISK
39 * OF SATISFACTORY QUALITY, PERFORMANCE, ACCURACY, AND EFFORT IS WITH LICENSEE.
40 * IN NO EVENT SHALL THE COPYRIGHT OWNER, CONTRIBUTORS OR THE UNIVERSITY
41 * CORPORATION FOR ADVANCED INTERNET DEVELOPMENT, INC. BE LIABLE FOR ANY DIRECT,
42 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
43 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
44 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
45 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
46 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
47 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
50 /* isapi_shib.cpp - Shibboleth ISAPI filter
56 #include "config_win32.h"
59 #include <saml/saml.h>
60 #include <shib/shib.h>
61 #include <shib/shib-threads.h>
62 #include <shib-target/shib-target.h>
64 #include <log4cpp/Category.hh>
75 using namespace log4cpp;
77 using namespace shibboleth;
78 using namespace shibtarget;
82 static const XMLCh name[] = { chLatin_n, chLatin_a, chLatin_m, chLatin_e, chNull };
83 static const XMLCh port[] = { chLatin_p, chLatin_o, chLatin_r, chLatin_t, chNull };
84 static const XMLCh scheme[] = { chLatin_s, chLatin_c, chLatin_h, chLatin_e, chLatin_m, chLatin_e, chNull };
85 static const XMLCh id[] = { chLatin_i, chLatin_d, chNull };
86 static const XMLCh Implementation[] =
87 { chLatin_I, chLatin_m, chLatin_p, chLatin_l, chLatin_e, chLatin_m, chLatin_e, chLatin_n, chLatin_t, chLatin_a, chLatin_t, chLatin_i, chLatin_o, chLatin_n, chNull };
88 static const XMLCh ISAPI[] = { chLatin_I, chLatin_S, chLatin_A, chLatin_P, chLatin_I, chNull };
89 static const XMLCh normalizeRequest[] =
90 { chLatin_n, chLatin_o, chLatin_r, chLatin_m, chLatin_a, chLatin_l, chLatin_i, chLatin_z, chLatin_e,
91 chLatin_R, chLatin_e, chLatin_q, chLatin_u, chLatin_e, chLatin_s, chLatin_t, chNull
93 static const XMLCh Site[] = { chLatin_S, chLatin_i, chLatin_t, chLatin_e, chNull };
96 site_t(const DOMElement* e)
98 auto_ptr_char n(e->getAttributeNS(NULL,name));
99 auto_ptr_char s(e->getAttributeNS(NULL,scheme));
100 auto_ptr_char p(e->getAttributeNS(NULL,port));
101 if (n.get()) m_name=n.get();
102 if (s.get()) m_scheme=s.get();
103 if (p.get()) m_port=p.get();
105 string m_scheme,m_name,m_port;
108 HINSTANCE g_hinstDLL;
109 ShibTargetConfig* g_Config = NULL;
110 map<string,site_t> g_Sites;
111 bool g_bNormalizeRequest = true;
115 LPCSTR lpUNCServerName,
121 LPCSTR messages[] = {message, NULL};
123 HANDLE hElog = RegisterEventSource(lpUNCServerName, "Shibboleth ISAPI Filter");
124 BOOL res = ReportEvent(hElog, wType, 0, dwEventID, lpUserSid, 1, 0, messages, NULL);
125 return (DeregisterEventSource(hElog) && res);
128 extern "C" __declspec(dllexport) BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID)
130 if (fdwReason==DLL_PROCESS_ATTACH)
135 extern "C" BOOL WINAPI GetExtensionVersion(HSE_VERSION_INFO* pVer)
142 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
143 "Extension mode startup not possible, is the DLL loaded as a filter?");
147 pVer->dwExtensionVersion=HSE_VERSION;
148 strncpy(pVer->lpszExtensionDesc,"Shibboleth ISAPI Extension",HSE_MAX_EXT_DLL_NAME_LEN-1);
152 extern "C" BOOL WINAPI TerminateExtension(DWORD)
154 return TRUE; // cleanup should happen when filter unloads
157 extern "C" BOOL WINAPI GetFilterVersion(PHTTP_FILTER_VERSION pVer)
162 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
163 "Reentrant filter initialization, ignoring...");
169 LPCSTR schemadir=getenv("SHIBSCHEMAS");
171 schemadir=SHIB_SCHEMAS;
172 LPCSTR config=getenv("SHIBCONFIG");
175 g_Config=&ShibTargetConfig::getConfig();
176 g_Config->setFeatures(
177 ShibTargetConfig::Listener |
178 ShibTargetConfig::Metadata |
179 ShibTargetConfig::AAP |
180 ShibTargetConfig::RequestMapper |
181 ShibTargetConfig::SHIREExtensions |
182 ShibTargetConfig::Logging
184 if (!g_Config->init(schemadir,config)) {
186 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
187 "Filter startup failed during initialization, check shire log for help.");
191 // Access the implementation-specifics for site mappings.
192 IConfig* conf=g_Config->getINI();
194 const IPropertySet* props=conf->getPropertySet("SHIRE");
196 const DOMElement* impl=saml::XML::getFirstChildElement(
197 props->getElement(),ShibTargetConfig::SHIBTARGET_NS,Implementation
199 if (impl && (impl=saml::XML::getFirstChildElement(impl,ShibTargetConfig::SHIBTARGET_NS,ISAPI))) {
200 const XMLCh* flag=impl->getAttributeNS(NULL,normalizeRequest);
201 g_bNormalizeRequest=(!flag || !*flag || *flag==chDigit_1 || *flag==chLatin_t);
202 impl=saml::XML::getFirstChildElement(impl,ShibTargetConfig::SHIBTARGET_NS,Site);
204 auto_ptr_char id(impl->getAttributeNS(NULL,id));
206 g_Sites.insert(pair<string,site_t>(id.get(),site_t(impl)));
207 impl=saml::XML::getNextSiblingElement(impl,ShibTargetConfig::SHIBTARGET_NS,Site);
214 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Filter startup failed with an exception.");
221 pVer->dwFilterVersion=HTTP_FILTER_REVISION;
222 strncpy(pVer->lpszFilterDesc,"Shibboleth ISAPI Filter",SF_MAX_FILTER_DESC_LEN);
223 pVer->dwFlags=(SF_NOTIFY_ORDER_HIGH |
224 SF_NOTIFY_SECURE_PORT |
225 SF_NOTIFY_NONSECURE_PORT |
226 SF_NOTIFY_PREPROC_HEADERS |
228 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter initialized...");
232 extern "C" BOOL WINAPI TerminateFilter(DWORD)
235 g_Config->shutdown();
237 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter shut down...");
241 /* Next up, some suck-free versions of various APIs.
243 You DON'T require people to guess the buffer size and THEN tell them the right size.
244 Returning an LPCSTR is apparently way beyond their ken. Not to mention the fact that
245 constant strings aren't typed as such, making it just that much harder. These versions
246 are now updated to use a special growable buffer object, modeled after the standard
247 string class. The standard string won't work because they left out the option to
248 pre-allocate a non-constant buffer.
254 dynabuf() { bufptr=NULL; buflen=0; }
255 dynabuf(size_t s) { bufptr=new char[buflen=s]; *bufptr=0; }
256 ~dynabuf() { delete[] bufptr; }
257 size_t length() const { return bufptr ? strlen(bufptr) : 0; }
258 size_t size() const { return buflen; }
259 bool empty() const { return length()==0; }
260 void reserve(size_t s, bool keep=false);
261 void erase() { if (bufptr) memset(bufptr,0,buflen); }
262 operator char*() { return bufptr; }
263 bool operator ==(const char* s) const;
264 bool operator !=(const char* s) const { return !(*this==s); }
270 void dynabuf::reserve(size_t s, bool keep)
277 p[buflen]=bufptr[buflen];
283 bool dynabuf::operator==(const char* s) const
285 if (buflen==NULL || s==NULL)
286 return (buflen==NULL && s==NULL);
288 return strcmp(bufptr,s)==0;
291 void GetServerVariable(PHTTP_FILTER_CONTEXT pfc, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
292 throw (bad_alloc, DWORD)
298 while (!pfc->GetServerVariable(pfc,lpszVariable,s,&size))
300 // Grumble. Check the error.
301 DWORD e=GetLastError();
302 if (e==ERROR_INSUFFICIENT_BUFFER)
307 if (bRequired && s.empty())
311 void GetServerVariable(LPEXTENSION_CONTROL_BLOCK lpECB, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
312 throw (bad_alloc, DWORD)
318 while (lpECB->GetServerVariable(lpECB->ConnID,lpszVariable,s,&size))
320 // Grumble. Check the error.
321 DWORD e=GetLastError();
322 if (e==ERROR_INSUFFICIENT_BUFFER)
327 if (bRequired && s.empty())
331 void GetHeader(PHTTP_FILTER_PREPROC_HEADERS pn, PHTTP_FILTER_CONTEXT pfc,
332 LPSTR lpszName, dynabuf& s, DWORD size=80, bool bRequired=true)
333 throw (bad_alloc, DWORD)
339 while (!pn->GetHeader(pfc,lpszName,s,&size))
341 // Grumble. Check the error.
342 DWORD e=GetLastError();
343 if (e==ERROR_INSUFFICIENT_BUFFER)
348 if (bRequired && s.empty())
352 IRequestMapper::Settings map_request(
353 PHTTP_FILTER_CONTEXT pfc, PHTTP_FILTER_PREPROC_HEADERS pn, IRequestMapper* mapper, const site_t& site, string& target
356 // URL path always come from IIS.
358 GetHeader(pn,pfc,"url",url,256,false);
360 // Port may come from IIS or from site def.
362 if (site.m_port.empty() || !g_bNormalizeRequest)
363 GetServerVariable(pfc,"SERVER_PORT",port,10);
365 strncpy(port,site.m_port.c_str(),10);
366 static_cast<char*>(port)[10]=0;
369 // Scheme may come from site def or be derived from IIS.
370 const char* scheme=site.m_scheme.c_str();
371 if (!scheme || !*scheme || !g_bNormalizeRequest)
372 scheme=pfc->fIsSecurePort ? "https" : "http";
374 // Start with scheme and hostname.
375 if (g_bNormalizeRequest) {
376 target = string(scheme) + "://" + site.m_name;
380 GetServerVariable(pfc,"SERVER_NAME",name,64);
381 target = string(scheme) + "://" + static_cast<char*>(name);
384 // If port is non-default, append it.
385 if ((!strcmp(scheme,"http") && port!="80") || (!strcmp(scheme,"https") && port!="443"))
386 target = target + ':' + static_cast<char*>(port);
390 target+=static_cast<char*>(url);
392 return mapper->getSettingsFromParsedURL(scheme,site.m_name.c_str(),strtoul(port,NULL,10),url);
395 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const char* msg)
397 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
398 static const char* ctype="Content-Type: text/html\r\n";
399 pfc->AddResponseHeaders(pfc,const_cast<char*>(ctype),0);
400 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",0,0);
401 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Filter Error</TITLE></HEAD><BODY>"
402 "<H1>Shibboleth Filter Error</H1>";
403 DWORD resplen=strlen(xmsg);
404 pfc->WriteClient(pfc,(LPVOID)xmsg,&resplen,0);
406 pfc->WriteClient(pfc,(LPVOID)msg,&resplen,0);
407 static const char* xmsg2="</BODY></HTML>";
408 resplen=strlen(xmsg2);
409 pfc->WriteClient(pfc,(LPVOID)xmsg2,&resplen,0);
410 return SF_STATUS_REQ_FINISHED;
413 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const IApplication* app, const char* page, ShibMLP& mlp)
415 const IPropertySet* props=app->getPropertySet("Errors");
417 pair<bool,const char*> p=props->getString(page);
419 ifstream infile(p.second);
420 if (!infile.fail()) {
421 const char* res = mlp.run(infile,props);
423 static const char* ctype="Content-Type: text/html\r\n";
424 pfc->AddResponseHeaders(pfc,const_cast<char*>(ctype),0);
425 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",0,0);
426 DWORD resplen=strlen(res);
427 pfc->WriteClient(pfc,(LPVOID)res,&resplen,0);
428 return SF_STATUS_REQ_FINISHED;
434 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Filter unable to open error template.");
435 return WriteClientError(pfc,"Unable to open error template, check settings.");
438 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
440 // Is this a log notification?
441 if (notificationType==SF_NOTIFY_LOG)
443 if (pfc->pFilterContext)
444 ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=static_cast<LPCSTR>(pfc->pFilterContext);
445 return SF_STATUS_REQ_NEXT_NOTIFICATION;
448 PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
451 // Determine web site number. This can't really fail, I don't think.
453 GetServerVariable(pfc,"INSTANCE_ID",buf,10);
455 // Match site instance to host name, skip if no match.
456 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
457 if (map_i==g_Sites.end())
458 return SF_STATUS_REQ_NEXT_NOTIFICATION;
460 ostringstream threadid;
461 threadid << "[" << getpid() << "] isapi_shib" << '\0';
462 saml::NDC ndc(threadid.str().c_str());
464 // We lock the configuration system for the duration.
465 IConfig* conf=g_Config->getINI();
468 // Map request to application and content settings.
470 IRequestMapper* mapper=conf->getRequestMapper();
471 Locker locker2(mapper);
472 IRequestMapper::Settings settings=map_request(pfc,pn,mapper,map_i->second,targeturl);
473 pair<bool,const char*> application_id=settings.first->getString("applicationId");
474 const IApplication* application=conf->getApplication(application_id.second);
476 return WriteClientError(pfc,"Unable to map request to application settings, check configuration.");
478 // Declare SHIRE object for this request.
479 SHIRE shire(application);
481 const char* shireURL=shire.getShireURL(targeturl.c_str());
483 return WriteClientError(pfc,"Unable to map request to proper shireURL setting, check configuration.");
485 // If the user is accessing the SHIRE acceptance point, pass it on.
486 if (targeturl.find(shireURL)!=string::npos)
487 return SF_STATUS_REQ_NEXT_NOTIFICATION;
489 // Now check the policy for this request.
490 pair<bool,bool> requireSession=settings.first->getBool("requireSession");
491 pair<const char*,const char*> shib_cookie=shire.getCookieNameProps();
493 // Check for session cookie.
494 const char* session_id=NULL;
495 GetHeader(pn,pfc,"Cookie:",buf,128,false);
496 Category::getInstance("isapi_shib.HttpFilterProc").debug("cookie header is {%s}",(const char*)buf);
497 if (!buf.empty() && (session_id=strstr(buf,shib_cookie.first))) {
498 session_id+=strlen(shib_cookie.first) + 1; /* Skip over the '=' */
499 char* cookieend=strchr(session_id,';');
501 *cookieend = '\0'; /* Ignore anyting after a ; */
504 if (!session_id || !*session_id) {
505 // If no session required, bail now.
506 if (!requireSession.second)
507 return SF_STATUS_REQ_NEXT_NOTIFICATION;
509 // No acceptable cookie, and we require a session. Generate an AuthnRequest.
510 string loc("Location: ");
511 loc+=shire.getAuthnRequest(targeturl.c_str());
513 pfc->AddResponseHeaders(pfc,const_cast<char*>(loc.c_str()),0);
514 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"302 Please Wait",0,0);
515 return SF_STATUS_REQ_FINISHED;
518 // Make sure this session is still valid.
519 RPCError* status = NULL;
520 ShibMLP markupProcessor;
521 markupProcessor.insert("requestURL", targeturl);
524 GetServerVariable(pfc,"REMOTE_ADDR",abuf,16);
526 status = shire.sessionIsValid(session_id, abuf);
528 catch (ShibTargetException &e) {
529 markupProcessor.insert("errorType", "Session Processing Error");
530 markupProcessor.insert("errorText", e.what());
531 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
532 return WriteClientError(pfc, application, "shire", markupProcessor);
536 markupProcessor.insert("errorType", "Session Processing Error");
537 markupProcessor.insert("errorText", "Unexpected Exception");
538 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
539 return WriteClientError(pfc, application, "shire", markupProcessor);
544 if (status->isError()) {
545 if (!requireSession.second)
546 return SF_STATUS_REQ_NEXT_NOTIFICATION;
547 else if (status->isRetryable()) {
548 // Oops, session is invalid. Generate AuthnRequest.
550 string loc("Location: ");
551 loc+=shire.getAuthnRequest(targeturl.c_str());
553 pfc->AddResponseHeaders(pfc,const_cast<char*>(loc.c_str()),0);
554 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"302 Please Wait",0,0);
555 return SF_STATUS_REQ_FINISHED;
558 // return the error page to the user
559 markupProcessor.insert(*status);
561 return WriteClientError(pfc, application, "shire", markupProcessor);
568 vector<SAMLAssertion*> assertions;
569 SAMLAuthenticationStatement* sso_statement=NULL;
572 status = rm.getAssertions(session_id, abuf, assertions, &sso_statement);
574 catch (ShibTargetException &e) {
575 markupProcessor.insert("errorType", "Attribute Processing Error");
576 markupProcessor.insert("errorText", e.what());
577 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
578 return WriteClientError(pfc, application, "rm", markupProcessor);
582 markupProcessor.insert("errorType", "Attribute Processing Error");
583 markupProcessor.insert("errorText", "Unexpected Exception");
584 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
585 return WriteClientError(pfc, application, "rm", markupProcessor);
589 if (status->isError()) {
590 markupProcessor.insert(*status);
592 return WriteClientError(pfc, application, "rm", markupProcessor);
596 // Do we have an access control plugin?
597 if (settings.second) {
598 Locker acllock(settings.second);
599 if (!settings.second->authorized(*sso_statement,assertions)) {
600 for (int k = 0; k < assertions.size(); k++)
601 delete assertions[k];
602 delete sso_statement;
603 return WriteClientError(pfc, application, "access", markupProcessor);
607 // Get the AAP providers, which contain the attribute policy info.
608 Iterator<IAAP*> provs=application->getAAPProviders();
610 // Clear out the list of mapped attributes
611 while (provs.hasNext()) {
612 IAAP* aap=provs.next();
615 Iterator<const IAttributeRule*> rules=aap->getAttributeRules();
616 while (rules.hasNext()) {
617 const char* header=rules.next()->getHeader();
619 string hname=string(header) + ':';
620 pn->SetHeader(pfc,const_cast<char*>(hname.c_str()),"");
626 for (int k = 0; k < assertions.size(); k++)
627 delete assertions[k];
628 delete sso_statement;
629 markupProcessor.insert("errorType", "Attribute Processing Error");
630 markupProcessor.insert("errorText", "Unexpected Exception");
631 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
632 return WriteClientError(pfc, application, "rm", markupProcessor);
638 // Maybe export the first assertion.
639 pn->SetHeader(pfc,"remote-user:","");
640 pn->SetHeader(pfc,"Shib-Attributes:","");
641 pair<bool,bool> exp=settings.first->getBool("exportAssertion");
642 if (exp.first && exp.second && assertions.size()) {
644 RM::serialize(*(assertions[0]), assertion);
645 string::size_type lfeed;
646 while ((lfeed=assertion.find('\n'))!=string::npos)
647 assertion.erase(lfeed,1);
648 pn->SetHeader(pfc,"Shib-Attributes:",const_cast<char*>(assertion.c_str()));
651 pn->SetHeader(pfc,"Shib-Origin-Site:","");
652 pn->SetHeader(pfc,"Shib-Authentication-Method:","");
653 pn->SetHeader(pfc,"Shib-NameIdentifier-Format:","");
655 // Export the SAML AuthnMethod and the origin site name.
656 auto_ptr_char os(sso_statement->getSubject()->getNameIdentifier()->getNameQualifier());
657 auto_ptr_char am(sso_statement->getAuthMethod());
658 pn->SetHeader(pfc,"Shib-Origin-Site:", const_cast<char*>(os.get()));
659 pn->SetHeader(pfc,"Shib-Authentication-Method:", const_cast<char*>(am.get()));
662 AAP wrapper(provs,sso_statement->getSubject()->getNameIdentifier()->getFormat(),Constants::SHIB_ATTRIBUTE_NAMESPACE_URI);
663 if (!wrapper.fail() && wrapper->getHeader()) {
664 auto_ptr_char form(sso_statement->getSubject()->getNameIdentifier()->getFormat());
665 auto_ptr_char nameid(sso_statement->getSubject()->getNameIdentifier()->getName());
666 pn->SetHeader(pfc,"Shib-NameIdentifier-Format:",const_cast<char*>(form.get()));
667 if (!strcmp(wrapper->getHeader(),"REMOTE_USER")) {
668 char* principal=const_cast<char*>(nameid.get());
669 pn->SetHeader(pfc,"remote-user:",principal);
670 pfc->pFilterContext=pfc->AllocMem(pfc,strlen(principal)+1,0);
671 if (pfc->pFilterContext)
672 strcpy(static_cast<char*>(pfc->pFilterContext),principal);
675 string hname=string(wrapper->getHeader()) + ':';
676 pn->SetHeader(pfc,const_cast<char*>(wrapper->getHeader()),const_cast<char*>(nameid.get()));
680 pn->SetHeader(pfc,"Shib-Application-ID:","");
681 pn->SetHeader(pfc,"Shib-Application-ID:",const_cast<char*>(application_id.second));
683 // Export the attributes.
684 Iterator<SAMLAssertion*> a_iter(assertions);
685 while (a_iter.hasNext()) {
686 SAMLAssertion* assert=a_iter.next();
687 Iterator<SAMLStatement*> statements=assert->getStatements();
688 while (statements.hasNext()) {
689 SAMLAttributeStatement* astate=dynamic_cast<SAMLAttributeStatement*>(statements.next());
692 Iterator<SAMLAttribute*> attrs=astate->getAttributes();
693 while (attrs.hasNext()) {
694 SAMLAttribute* attr=attrs.next();
696 // Are we supposed to export it?
697 AAP wrapper(provs,attr->getName(),attr->getNamespace());
698 if (wrapper.fail() || !wrapper->getHeader())
701 Iterator<string> vals=attr->getSingleByteValues();
702 if (!strcmp(wrapper->getHeader(),"REMOTE_USER") && vals.hasNext()) {
703 char* principal=const_cast<char*>(vals.next().c_str());
704 pn->SetHeader(pfc,"remote-user:",principal);
705 pfc->pFilterContext=pfc->AllocMem(pfc,strlen(principal)+1,0);
706 if (pfc->pFilterContext)
707 strcpy(static_cast<char*>(pfc->pFilterContext),principal);
712 string hname=string(wrapper->getHeader()) + ':';
713 GetHeader(pn,pfc,const_cast<char*>(hname.c_str()),buf,256,false);
718 for (; vals.hasNext(); it++) {
719 string value = vals.next();
720 for (string::size_type pos = value.find_first_of(";", string::size_type(0));
722 pos = value.find_first_of(";", pos)) {
723 value.insert(pos, "\\");
729 header=header + ';' + value;
731 pn->SetHeader(pfc,const_cast<char*>(hname.c_str()),const_cast<char*>(header.c_str()));
738 for (int k = 0; k < assertions.size(); k++)
739 delete assertions[k];
740 delete sso_statement;
742 return SF_STATUS_REQ_NEXT_NOTIFICATION;
745 return WriteClientError(pfc,"Out of Memory");
748 if (e==ERROR_NO_DATA)
749 return WriteClientError(pfc,"A required variable or header was empty.");
751 return WriteClientError(pfc,"Server detected unexpected IIS error.");
755 return WriteClientError(pfc,"Server caught an unknown exception.");
759 return WriteClientError(pfc,"Server reached unreachable code, save my walrus!");
762 IRequestMapper::Settings map_request(
763 LPEXTENSION_CONTROL_BLOCK lpECB, IRequestMapper* mapper, const site_t& site, string& target
767 GetServerVariable(lpECB,"HTTPS",ssl,5);
768 bool SSL=(ssl=="on");
770 // URL path always come from IIS.
772 GetServerVariable(lpECB,"URL",url,255);
774 // Port may come from IIS or from site def.
776 if (site.m_port.empty() || !g_bNormalizeRequest)
777 GetServerVariable(lpECB,"SERVER_PORT",port,10);
779 strncpy(port,site.m_port.c_str(),10);
780 static_cast<char*>(port)[10]=0;
783 // Scheme may come from site def or be derived from IIS.
784 const char* scheme=site.m_scheme.c_str();
785 if (!scheme || !*scheme || !g_bNormalizeRequest)
786 scheme=lpECB->lpszMethod;
788 // Start with scheme and hostname.
789 if (g_bNormalizeRequest) {
790 target = string(scheme) + "://" + site.m_name;
794 GetServerVariable(lpECB,"SERVER_NAME",name,64);
795 target = string(scheme) + "://" + static_cast<char*>(name);
798 // If port is non-default, append it.
799 if ((!strcmp(scheme,"http") && port!="80") || (!strcmp(scheme,"https") && port!="443"))
800 target = target + ':' + static_cast<char*>(port);
804 target+=static_cast<char*>(url);
806 return mapper->getSettingsFromParsedURL(scheme,site.m_name.c_str(),strtoul(port,NULL,10),url);
809 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* msg)
811 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
812 static const char* ctype="Content-Type: text/html\r\n";
813 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
814 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Error</TITLE></HEAD><BODY><H1>Shibboleth Error</H1>";
815 DWORD resplen=strlen(xmsg);
816 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg,&resplen,HSE_IO_SYNC);
818 lpECB->WriteClient(lpECB->ConnID,(LPVOID)msg,&resplen,HSE_IO_SYNC);
819 static const char* xmsg2="</BODY></HTML>";
820 resplen=strlen(xmsg2);
821 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg2,&resplen,HSE_IO_SYNC);
822 return HSE_STATUS_SUCCESS;
825 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const IApplication* app, const char* page, ShibMLP& mlp)
827 const IPropertySet* props=app->getPropertySet("Errors");
829 pair<bool,const char*> p=props->getString(page);
831 ifstream infile(p.second);
832 if (!infile.fail()) {
833 const char* res = mlp.run(infile,props);
835 static const char* ctype="Content-Type: text/html\r\n";
836 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
837 DWORD resplen=strlen(res);
838 lpECB->WriteClient(lpECB->ConnID,(LPVOID)res,&resplen,0);
839 return HSE_STATUS_SUCCESS;
844 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Extension unable to open error template.");
845 return WriteClientError(lpECB,"Unable to open error template, check settings.");
848 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
851 const IApplication* application=NULL;
854 ostringstream threadid;
855 threadid << "[" << getpid() << "] shire_handler" << '\0';
856 saml::NDC ndc(threadid.str().c_str());
858 // Determine web site number. This can't really fail, I don't think.
860 GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
862 // Match site instance to host name, skip if no match.
863 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
864 if (map_i==g_Sites.end())
865 return WriteClientError(lpECB,"Shibboleth filter not configured for this web site.");
867 // We lock the configuration system for the duration.
868 IConfig* conf=g_Config->getINI();
871 // Map request to application and content settings.
873 IRequestMapper* mapper=conf->getRequestMapper();
874 Locker locker2(mapper);
875 IRequestMapper::Settings settings=map_request(lpECB,mapper,map_i->second,targeturl);
876 pair<bool,const char*> application_id=settings.first->getString("applicationId");
877 application=conf->getApplication(application_id.second);
878 const IPropertySet* sessionProps=application ? application->getPropertySet("Sessions") : NULL;
879 if (!application || !sessionProps)
880 return WriteClientError(lpECB,"Unable to map request to application session settings, check configuration.");
882 SHIRE shire(application);
884 const char* shireURL=shire.getShireURL(targeturl.c_str());
886 return WriteClientError(lpECB,"Unable to map request to proper shireURL setting, check configuration.");
888 // Make sure we only process the SHIRE requests.
889 if (!strstr(targeturl.c_str(),shireURL))
890 return WriteClientError(lpECB,"The request's application and associated shireURL setting are inconsistent.");;
892 pair<const char*,const char*> shib_cookie=shire.getCookieNameProps();
894 // Make sure this is SSL, if it should be
895 pair<bool,bool> shireSSL=sessionProps->getBool("shireSSL");
896 if (!shireSSL.first || shireSSL.second) {
897 GetServerVariable(lpECB,"HTTPS",buf,10);
899 throw ShibTargetException(SHIBRPC_OK,"blocked non-SSL access to SHIRE POST processor");
902 // If this is a GET, we manufacture an AuthnRequest.
903 if (!stricmp(lpECB->lpszMethod,"GET")) {
904 const char* areq=lpECB->lpszQueryString ? shire.getLazyAuthnRequest(lpECB->lpszQueryString) : NULL;
906 throw ShibTargetException(SHIBRPC_OK, "malformed arguments to request a new session");
907 targeturl = string("Location: ") + areq + "\r\n"
908 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
909 "Cache-Control: private,no-store,no-cache\r\n"
910 "Connection: close\r\n";
911 HSE_SEND_HEADER_EX_INFO hinfo;
912 hinfo.pszStatus="302 Moved";
913 hinfo.pszHeader=targeturl.c_str();
915 hinfo.cchHeader=targeturl.length();
916 hinfo.fKeepConn=FALSE;
917 if (lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER_EX,&hinfo,0,0))
918 return HSE_STATUS_SUCCESS;
919 return HSE_STATUS_ERROR;
921 else if (stricmp(lpECB->lpszMethod,"POST"))
922 throw ShibTargetException(SHIBRPC_OK,"blocked non-POST to SHIRE POST processor");
924 // Sure sure this POST is an appropriate content type
925 if (!lpECB->lpszContentType || stricmp(lpECB->lpszContentType,"application/x-www-form-urlencoded"))
926 throw ShibTargetException(SHIBRPC_OK,"blocked bad content-type to SHIRE POST processor");
929 pair<const char*,const char*> elements=pair<const char*,const char*>(NULL,NULL);
930 if (lpECB->cbTotalBytes > 1024*1024) // 1MB?
931 throw ShibTargetException(SHIBRPC_OK,"blocked too-large a post to SHIRE POST processor");
932 else if (lpECB->cbTotalBytes!=lpECB->cbAvailable) {
935 DWORD datalen=lpECB->cbTotalBytes;
938 BOOL ret=lpECB->ReadClient(lpECB->ConnID,buf,&buflen);
940 throw ShibTargetException(SHIBRPC_OK,"error reading POST data from browser");
941 cgistr.append(buf,buflen);
944 elements=shire.getFormSubmission(cgistr.c_str(),cgistr.length());
947 elements=shire.getFormSubmission(reinterpret_cast<char*>(lpECB->lpbData),lpECB->cbAvailable);
949 // Make sure the SAML Response parameter exists
950 if (!elements.first || !*elements.first)
951 throw ShibTargetException(SHIBRPC_OK, "SHIRE POST failed to find SAMLResponse form element");
953 // Make sure the target parameter exists
954 if (!elements.second || !*elements.second)
955 throw ShibTargetException(SHIBRPC_OK, "SHIRE POST failed to find TARGET form element");
957 GetServerVariable(lpECB,"REMOTE_ADDR",buf,16);
961 RPCError* status=NULL;
962 ShibMLP markupProcessor;
963 markupProcessor.insert("requestURL", targeturl.c_str());
965 status = shire.sessionCreate(elements.first,buf,cookie);
967 catch (ShibTargetException &e) {
968 markupProcessor.insert("errorType", "Session Creation Service Error");
969 markupProcessor.insert("errorText", e.what());
970 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
971 return WriteClientError(lpECB, application, "shire", markupProcessor);
975 markupProcessor.insert("errorType", "Session Creation Service Error");
976 markupProcessor.insert("errorText", "Unexpected Exception");
977 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
978 return WriteClientError(lpECB, application, "shire", markupProcessor);
982 if (status->isError()) {
983 if (status->isRetryable()) {
985 const char* loc=shire.getAuthnRequest(elements.second);
986 DWORD len=strlen(loc);
987 if (lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_URL_REDIRECT_RESP,(LPVOID)loc,&len,0))
988 return HSE_STATUS_SUCCESS;
989 return HSE_STATUS_ERROR;
992 // Return this error to the user.
993 markupProcessor.insert(*status);
995 return WriteClientError(lpECB,application,"shire",markupProcessor);
999 // We've got a good session, set the cookie and redirect to target.
1000 cookie = string("Set-Cookie: ") + shib_cookie.first + '=' + cookie + shib_cookie.second + "\r\n"
1001 "Location: " + elements.second + "\r\n"
1002 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
1003 "Cache-Control: private,no-store,no-cache\r\n"
1004 "Connection: close\r\n";
1005 HSE_SEND_HEADER_EX_INFO hinfo;
1006 hinfo.pszStatus="302 Moved";
1007 hinfo.pszHeader=cookie.c_str();
1009 hinfo.cchHeader=cookie.length();
1010 hinfo.fKeepConn=FALSE;
1011 if (lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER_EX,&hinfo,0,0))
1012 return HSE_STATUS_SUCCESS;
1014 catch (ShibTargetException &e) {
1016 ShibMLP markupProcessor;
1017 markupProcessor.insert("requestURL", targeturl.c_str());
1018 markupProcessor.insert("errorType", "Session Creation Service Error");
1019 markupProcessor.insert("errorText", e.what());
1020 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
1021 return WriteClientError(lpECB,application,"shire",markupProcessor);
1027 ShibMLP markupProcessor;
1028 markupProcessor.insert("requestURL", targeturl.c_str());
1029 markupProcessor.insert("errorType", "Session Creation Service Error");
1030 markupProcessor.insert("errorText", "Unexpected Exception");
1031 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
1032 return WriteClientError(lpECB,application,"shire",markupProcessor);
1037 return HSE_STATUS_ERROR;