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" || 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 = SSL ? "https" : "http";
789 // Start with scheme and hostname.
790 if (g_bNormalizeRequest) {
791 target = string(scheme) + "://" + site.m_name;
795 GetServerVariable(lpECB,"SERVER_NAME",name,64);
796 target = string(scheme) + "://" + static_cast<char*>(name);
799 // If port is non-default, append it.
800 if ((!strcmp(scheme,"http") && port!="80") || (!strcmp(scheme,"https") && port!="443"))
801 target = target + ':' + static_cast<char*>(port);
805 target+=static_cast<char*>(url);
807 return mapper->getSettingsFromParsedURL(scheme,site.m_name.c_str(),strtoul(port,NULL,10),url);
810 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* msg)
812 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
813 static const char* ctype="Content-Type: text/html\r\n";
814 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
815 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Error</TITLE></HEAD><BODY><H1>Shibboleth Error</H1>";
816 DWORD resplen=strlen(xmsg);
817 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg,&resplen,HSE_IO_SYNC);
819 lpECB->WriteClient(lpECB->ConnID,(LPVOID)msg,&resplen,HSE_IO_SYNC);
820 static const char* xmsg2="</BODY></HTML>";
821 resplen=strlen(xmsg2);
822 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg2,&resplen,HSE_IO_SYNC);
823 return HSE_STATUS_SUCCESS;
826 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const IApplication* app, const char* page, ShibMLP& mlp)
828 const IPropertySet* props=app->getPropertySet("Errors");
830 pair<bool,const char*> p=props->getString(page);
832 ifstream infile(p.second);
833 if (!infile.fail()) {
834 const char* res = mlp.run(infile,props);
836 static const char* ctype="Content-Type: text/html\r\n";
837 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
838 DWORD resplen=strlen(res);
839 lpECB->WriteClient(lpECB->ConnID,(LPVOID)res,&resplen,0);
840 return HSE_STATUS_SUCCESS;
845 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Extension unable to open error template.");
846 return WriteClientError(lpECB,"Unable to open error template, check settings.");
849 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
852 const IApplication* application=NULL;
855 ostringstream threadid;
856 threadid << "[" << getpid() << "] shire_handler" << '\0';
857 saml::NDC ndc(threadid.str().c_str());
859 // Determine web site number. This can't really fail, I don't think.
861 GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
863 // Match site instance to host name, skip if no match.
864 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
865 if (map_i==g_Sites.end())
866 return WriteClientError(lpECB,"Shibboleth filter not configured for this web site.");
868 // We lock the configuration system for the duration.
869 IConfig* conf=g_Config->getINI();
872 // Map request to application and content settings.
874 IRequestMapper* mapper=conf->getRequestMapper();
875 Locker locker2(mapper);
876 IRequestMapper::Settings settings=map_request(lpECB,mapper,map_i->second,targeturl);
877 pair<bool,const char*> application_id=settings.first->getString("applicationId");
878 application=conf->getApplication(application_id.second);
879 const IPropertySet* sessionProps=application ? application->getPropertySet("Sessions") : NULL;
880 if (!application || !sessionProps)
881 return WriteClientError(lpECB,"Unable to map request to application session settings, check configuration.");
883 SHIRE shire(application);
885 const char* shireURL=shire.getShireURL(targeturl.c_str());
887 return WriteClientError(lpECB,"Unable to map request to proper shireURL setting, check configuration.");
889 // Make sure we only process the SHIRE requests.
890 if (!strstr(targeturl.c_str(),shireURL))
891 return WriteClientError(lpECB,"ISAPI extension can only be invoked to process incoming sessions."
892 "Make sure the mapped file extension doesn't match actual content.");
894 pair<const char*,const char*> shib_cookie=shire.getCookieNameProps();
896 // Make sure this is SSL, if it should be
897 pair<bool,bool> shireSSL=sessionProps->getBool("shireSSL");
898 if (!shireSSL.first || shireSSL.second) {
899 GetServerVariable(lpECB,"HTTPS",buf,10);
901 throw ShibTargetException(SHIBRPC_OK,"blocked non-SSL access to SHIRE POST processor");
904 // If this is a GET, we manufacture an AuthnRequest.
905 if (!stricmp(lpECB->lpszMethod,"GET")) {
906 const char* areq=lpECB->lpszQueryString ? shire.getLazyAuthnRequest(lpECB->lpszQueryString) : NULL;
908 throw ShibTargetException(SHIBRPC_OK, "malformed arguments to request a new session");
909 targeturl = string("Location: ") + areq + "\r\n"
910 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
911 "Cache-Control: private,no-store,no-cache\r\n"
912 "Connection: close\r\n";
913 HSE_SEND_HEADER_EX_INFO hinfo;
914 hinfo.pszStatus="302 Moved";
915 hinfo.pszHeader=targeturl.c_str();
917 hinfo.cchHeader=targeturl.length();
918 hinfo.fKeepConn=FALSE;
919 if (lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER_EX,&hinfo,0,0))
920 return HSE_STATUS_SUCCESS;
921 return HSE_STATUS_ERROR;
923 else if (stricmp(lpECB->lpszMethod,"POST"))
924 throw ShibTargetException(SHIBRPC_OK,"blocked non-POST to SHIRE POST processor");
926 // Sure sure this POST is an appropriate content type
927 if (!lpECB->lpszContentType || stricmp(lpECB->lpszContentType,"application/x-www-form-urlencoded"))
928 throw ShibTargetException(SHIBRPC_OK,"blocked bad content-type to SHIRE POST processor");
931 pair<const char*,const char*> elements=pair<const char*,const char*>(NULL,NULL);
932 if (lpECB->cbTotalBytes > 1024*1024) // 1MB?
933 throw ShibTargetException(SHIBRPC_OK,"blocked too-large a post to SHIRE POST processor");
934 else if (lpECB->cbTotalBytes!=lpECB->cbAvailable) {
937 DWORD datalen=lpECB->cbTotalBytes;
940 BOOL ret=lpECB->ReadClient(lpECB->ConnID,buf,&buflen);
942 throw ShibTargetException(SHIBRPC_OK,"error reading POST data from browser");
943 cgistr.append(buf,buflen);
946 elements=shire.getFormSubmission(cgistr.c_str(),cgistr.length());
949 elements=shire.getFormSubmission(reinterpret_cast<char*>(lpECB->lpbData),lpECB->cbAvailable);
951 // Make sure the SAML Response parameter exists
952 if (!elements.first || !*elements.first)
953 throw ShibTargetException(SHIBRPC_OK, "SHIRE POST failed to find SAMLResponse form element");
955 // Make sure the target parameter exists
956 if (!elements.second || !*elements.second)
957 throw ShibTargetException(SHIBRPC_OK, "SHIRE POST failed to find TARGET form element");
959 GetServerVariable(lpECB,"REMOTE_ADDR",buf,16);
963 RPCError* status=NULL;
964 ShibMLP markupProcessor;
965 markupProcessor.insert("requestURL", targeturl.c_str());
967 status = shire.sessionCreate(elements.first,buf,cookie);
969 catch (ShibTargetException &e) {
970 markupProcessor.insert("errorType", "Session Creation Service Error");
971 markupProcessor.insert("errorText", e.what());
972 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
973 return WriteClientError(lpECB, application, "shire", markupProcessor);
977 markupProcessor.insert("errorType", "Session Creation Service Error");
978 markupProcessor.insert("errorText", "Unexpected Exception");
979 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
980 return WriteClientError(lpECB, application, "shire", markupProcessor);
984 if (status->isError()) {
985 if (status->isRetryable()) {
987 const char* loc=shire.getAuthnRequest(elements.second);
988 DWORD len=strlen(loc);
989 if (lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_URL_REDIRECT_RESP,(LPVOID)loc,&len,0))
990 return HSE_STATUS_SUCCESS;
991 return HSE_STATUS_ERROR;
994 // Return this error to the user.
995 markupProcessor.insert(*status);
997 return WriteClientError(lpECB,application,"shire",markupProcessor);
1001 // We've got a good session, set the cookie and redirect to target.
1002 cookie = string("Set-Cookie: ") + shib_cookie.first + '=' + cookie + shib_cookie.second + "\r\n"
1003 "Location: " + elements.second + "\r\n"
1004 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
1005 "Cache-Control: private,no-store,no-cache\r\n"
1006 "Connection: close\r\n";
1007 HSE_SEND_HEADER_EX_INFO hinfo;
1008 hinfo.pszStatus="302 Moved";
1009 hinfo.pszHeader=cookie.c_str();
1011 hinfo.cchHeader=cookie.length();
1012 hinfo.fKeepConn=FALSE;
1013 if (lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER_EX,&hinfo,0,0))
1014 return HSE_STATUS_SUCCESS;
1016 catch (ShibTargetException &e) {
1018 ShibMLP markupProcessor;
1019 markupProcessor.insert("requestURL", targeturl.c_str());
1020 markupProcessor.insert("errorType", "Session Creation Service Error");
1021 markupProcessor.insert("errorText", e.what());
1022 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
1023 return WriteClientError(lpECB,application,"shire",markupProcessor);
1029 ShibMLP markupProcessor;
1030 markupProcessor.insert("requestURL", targeturl.c_str());
1031 markupProcessor.insert("errorType", "Session Creation Service Error");
1032 markupProcessor.insert("errorText", "Unexpected Exception");
1033 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
1034 return WriteClientError(lpECB,application,"shire",markupProcessor);
1039 return HSE_STATUS_ERROR;