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)
164 LPCSTR schemadir=getenv("SHIBSCHEMAS");
166 schemadir=SHIB_SCHEMAS;
167 LPCSTR config=getenv("SHIBCONFIG");
170 g_Config=&ShibTargetConfig::getConfig();
171 g_Config->setFeatures(
172 ShibTargetConfig::Listener |
173 ShibTargetConfig::Metadata |
174 ShibTargetConfig::AAP |
175 ShibTargetConfig::RequestMapper |
176 ShibTargetConfig::SHIREExtensions |
177 ShibTargetConfig::Logging
179 if (!g_Config->init(schemadir,config)) {
181 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
182 "Filter startup failed during initialization, check shire log for help.");
186 // Access the implementation-specifics for site mappings.
187 IConfig* conf=g_Config->getINI();
189 const IPropertySet* props=conf->getPropertySet("SHIRE");
191 const DOMElement* impl=saml::XML::getFirstChildElement(
192 props->getElement(),ShibTargetConfig::SHIBTARGET_NS,Implementation
194 if (impl && (impl=saml::XML::getFirstChildElement(impl,ShibTargetConfig::SHIBTARGET_NS,ISAPI))) {
195 const XMLCh* flag=impl->getAttributeNS(NULL,normalizeRequest);
196 g_bNormalizeRequest=(!flag || !*flag || *flag==chDigit_1 || *flag==chLatin_t);
197 impl=saml::XML::getFirstChildElement(impl,ShibTargetConfig::SHIBTARGET_NS,Site);
199 auto_ptr_char id(impl->getAttributeNS(NULL,id));
201 g_Sites.insert(pair<string,site_t>(id.get(),site_t(impl)));
202 impl=saml::XML::getNextSiblingElement(impl,ShibTargetConfig::SHIBTARGET_NS,Site);
209 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Filter startup failed with an exception.");
213 pVer->dwFilterVersion=HTTP_FILTER_REVISION;
214 strncpy(pVer->lpszFilterDesc,"Shibboleth ISAPI Filter",SF_MAX_FILTER_DESC_LEN);
215 pVer->dwFlags=(SF_NOTIFY_ORDER_HIGH |
216 SF_NOTIFY_SECURE_PORT |
217 SF_NOTIFY_NONSECURE_PORT |
218 SF_NOTIFY_PREPROC_HEADERS |
220 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter initialized...");
224 extern "C" BOOL WINAPI TerminateFilter(DWORD)
227 g_Config->shutdown();
229 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter shut down...");
233 /* Next up, some suck-free versions of various APIs.
235 You DON'T require people to guess the buffer size and THEN tell them the right size.
236 Returning an LPCSTR is apparently way beyond their ken. Not to mention the fact that
237 constant strings aren't typed as such, making it just that much harder. These versions
238 are now updated to use a special growable buffer object, modeled after the standard
239 string class. The standard string won't work because they left out the option to
240 pre-allocate a non-constant buffer.
246 dynabuf() { bufptr=NULL; buflen=0; }
247 dynabuf(size_t s) { bufptr=new char[buflen=s]; *bufptr=0; }
248 ~dynabuf() { delete[] bufptr; }
249 size_t length() const { return bufptr ? strlen(bufptr) : 0; }
250 size_t size() const { return buflen; }
251 bool empty() const { return length()==0; }
252 void reserve(size_t s, bool keep=false);
253 void erase() { if (bufptr) memset(bufptr,0,buflen); }
254 operator char*() { return bufptr; }
255 bool operator ==(const char* s) const;
256 bool operator !=(const char* s) const { return !(*this==s); }
262 void dynabuf::reserve(size_t s, bool keep)
269 p[buflen]=bufptr[buflen];
275 bool dynabuf::operator==(const char* s) const
277 if (buflen==NULL || s==NULL)
278 return (buflen==NULL && s==NULL);
280 return strcmp(bufptr,s)==0;
283 void GetServerVariable(PHTTP_FILTER_CONTEXT pfc, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
284 throw (bad_alloc, DWORD)
290 while (!pfc->GetServerVariable(pfc,lpszVariable,s,&size))
292 // Grumble. Check the error.
293 DWORD e=GetLastError();
294 if (e==ERROR_INSUFFICIENT_BUFFER)
299 if (bRequired && s.empty())
303 void GetServerVariable(LPEXTENSION_CONTROL_BLOCK lpECB, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
304 throw (bad_alloc, DWORD)
310 while (lpECB->GetServerVariable(lpECB->ConnID,lpszVariable,s,&size))
312 // Grumble. Check the error.
313 DWORD e=GetLastError();
314 if (e==ERROR_INSUFFICIENT_BUFFER)
319 if (bRequired && s.empty())
323 void GetHeader(PHTTP_FILTER_PREPROC_HEADERS pn, PHTTP_FILTER_CONTEXT pfc,
324 LPSTR lpszName, dynabuf& s, DWORD size=80, bool bRequired=true)
325 throw (bad_alloc, DWORD)
331 while (!pn->GetHeader(pfc,lpszName,s,&size))
333 // Grumble. Check the error.
334 DWORD e=GetLastError();
335 if (e==ERROR_INSUFFICIENT_BUFFER)
340 if (bRequired && s.empty())
344 IRequestMapper::Settings map_request(
345 PHTTP_FILTER_CONTEXT pfc, PHTTP_FILTER_PREPROC_HEADERS pn, IRequestMapper* mapper, const site_t& site, string& target
348 // URL path always come from IIS.
350 GetHeader(pn,pfc,"url",url,256,false);
352 // Port may come from IIS or from site def.
354 if (site.m_port.empty() || !g_bNormalizeRequest)
355 GetServerVariable(pfc,"SERVER_PORT",port,10);
357 strncpy(port,site.m_port.c_str(),10);
358 static_cast<char*>(port)[10]=0;
361 // Scheme may come from site def or be derived from IIS.
362 const char* scheme=site.m_scheme.c_str();
363 if (!scheme || !*scheme || !g_bNormalizeRequest)
364 scheme=pfc->fIsSecurePort ? "https" : "http";
368 target=static_cast<char*>(url);
370 // If port is non-default, prepend it.
371 if ((!strcmp(scheme,"http") && port!="80") || (!strcmp(scheme,"https") && port!="443"))
372 target = ':' + static_cast<char*>(port) + target;
374 if (g_bNormalizeRequest) {
375 target = string(scheme) + "://" + site.m_name + target;
379 GetServerVariable(pfc,"SERVER_NAME",name,64);
380 target = string(scheme) + "://" + static_cast<char*>(name) + target;
382 return mapper->getSettingsFromParsedURL(scheme,site.m_name.c_str(),strtoul(port,NULL,10),url);
385 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const char* msg)
387 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
388 static const char* ctype="Content-Type: text/html\r\n";
389 pfc->AddResponseHeaders(pfc,const_cast<char*>(ctype),0);
390 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",0,0);
391 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Filter Error</TITLE></HEAD><BODY>"
392 "<H1>Shibboleth Filter Error</H1>";
393 DWORD resplen=strlen(xmsg);
394 pfc->WriteClient(pfc,(LPVOID)xmsg,&resplen,0);
396 pfc->WriteClient(pfc,(LPVOID)msg,&resplen,0);
397 static const char* xmsg2="</BODY></HTML>";
398 resplen=strlen(xmsg2);
399 pfc->WriteClient(pfc,(LPVOID)xmsg2,&resplen,0);
400 return SF_STATUS_REQ_FINISHED;
403 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const IApplication* app, const char* page, ShibMLP& mlp)
405 const IPropertySet* props=app->getPropertySet("Errors");
407 pair<bool,const char*> p=props->getString(page);
409 ifstream infile(p.second);
410 if (!infile.fail()) {
411 const char* res = mlp.run(infile,props);
413 static const char* ctype="Content-Type: text/html\r\n";
414 pfc->AddResponseHeaders(pfc,const_cast<char*>(ctype),0);
415 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",0,0);
416 DWORD resplen=strlen(res);
417 pfc->WriteClient(pfc,(LPVOID)res,&resplen,0);
418 return SF_STATUS_REQ_FINISHED;
424 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Filter unable to open error template.");
425 return WriteClientError(pfc,"Unable to open error template, check settings.");
428 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
430 // Is this a log notification?
431 if (notificationType==SF_NOTIFY_LOG)
433 if (pfc->pFilterContext)
434 ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=static_cast<LPCSTR>(pfc->pFilterContext);
435 return SF_STATUS_REQ_NEXT_NOTIFICATION;
438 PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
441 // Determine web site number. This can't really fail, I don't think.
443 GetServerVariable(pfc,"INSTANCE_ID",buf,10);
445 // Match site instance to host name, skip if no match.
446 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
447 if (map_i==g_Sites.end())
448 return SF_STATUS_REQ_NEXT_NOTIFICATION;
450 ostringstream threadid;
451 threadid << "[" << getpid() << "] isapi_shib" << '\0';
452 saml::NDC ndc(threadid.str().c_str());
454 // We lock the configuration system for the duration.
455 IConfig* conf=g_Config->getINI();
458 // Map request to application and content settings.
460 IRequestMapper* mapper=conf->getRequestMapper();
461 Locker locker2(mapper);
462 IRequestMapper::Settings settings=map_request(pfc,pn,mapper,map_i->second,targeturl);
463 pair<bool,const char*> application_id=settings.first->getString("applicationId");
464 const IApplication* application=conf->getApplication(application_id.second);
466 return WriteClientError(pfc,"Unable to map request to application settings, check configuration.");
468 // Declare SHIRE object for this request.
469 SHIRE shire(application);
471 // If the user is accessing the SHIRE acceptance point, pass it on.
472 if (targeturl.find(shire.getShireURL(targeturl.c_str()))!=string::npos)
473 return SF_STATUS_REQ_NEXT_NOTIFICATION;
475 // Now check the policy for this request.
476 pair<bool,bool> requireSession=settings.first->getBool("requireSession");
477 pair<const char*,const char*> shib_cookie=shire.getCookieNameProps();
479 // Check for session cookie.
480 const char* session_id=NULL;
481 GetHeader(pn,pfc,"Cookie:",buf,128,false);
482 Category::getInstance("isapi_shib.HttpFilterProc").debug("cookie header is {%s}",(const char*)buf);
483 if (!buf.empty() && (session_id=strstr(buf,shib_cookie.first))) {
484 session_id+=strlen(shib_cookie.first) + 1; /* Skip over the '=' */
485 char* cookieend=strchr(session_id,';');
487 *cookieend = '\0'; /* Ignore anyting after a ; */
490 if (!session_id || !*session_id) {
491 // If no session required, bail now.
492 if (!requireSession.second)
493 return SF_STATUS_REQ_NEXT_NOTIFICATION;
495 // No acceptable cookie, and we require a session. Generate an AuthnRequest.
496 string loc("Location: ");
497 loc+=shire.getAuthnRequest(targeturl.c_str());
498 pfc->AddResponseHeaders(pfc,const_cast<char*>(loc.c_str()),0);
499 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"302 Please Wait",0,0);
500 return SF_STATUS_REQ_FINISHED;
503 // Make sure this session is still valid.
504 RPCError* status = NULL;
505 ShibMLP markupProcessor;
506 markupProcessor.insert("requestURL", targeturl);
509 GetServerVariable(pfc,"REMOTE_ADDR",abuf,16);
511 status = shire.sessionIsValid(session_id, abuf);
513 catch (ShibTargetException &e) {
514 markupProcessor.insert("errorType", "Session Processing Error");
515 markupProcessor.insert("errorText", e.what());
516 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
517 return WriteClientError(pfc, application, "shire", markupProcessor);
521 markupProcessor.insert("errorType", "Session Processing Error");
522 markupProcessor.insert("errorText", "Unexpected Exception");
523 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
524 return WriteClientError(pfc, application, "shire", markupProcessor);
529 if (status->isError()) {
530 if (!requireSession.second)
531 return SF_STATUS_REQ_NEXT_NOTIFICATION;
532 else if (status->isRetryable()) {
533 // Oops, session is invalid. Generate AuthnRequest.
535 string loc("Location: ");
536 loc+=shire.getAuthnRequest(targeturl.c_str());
537 pfc->AddResponseHeaders(pfc,const_cast<char*>(loc.c_str()),0);
538 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"302 Please Wait",0,0);
539 return SF_STATUS_REQ_FINISHED;
542 // return the error page to the user
543 markupProcessor.insert(*status);
545 return WriteClientError(pfc, application, "shire", markupProcessor);
552 vector<SAMLAssertion*> assertions;
553 SAMLAuthenticationStatement* sso_statement=NULL;
556 status = rm.getAssertions(session_id, abuf, assertions, &sso_statement);
558 catch (ShibTargetException &e) {
559 markupProcessor.insert("errorType", "Attribute Processing Error");
560 markupProcessor.insert("errorText", e.what());
561 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
562 return WriteClientError(pfc, application, "rm", markupProcessor);
566 markupProcessor.insert("errorType", "Attribute Processing Error");
567 markupProcessor.insert("errorText", "Unexpected Exception");
568 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
569 return WriteClientError(pfc, application, "rm", markupProcessor);
573 if (status->isError()) {
574 markupProcessor.insert(*status);
576 return WriteClientError(pfc, application, "rm", markupProcessor);
580 // Do we have an access control plugin?
581 if (settings.second) {
582 Locker acllock(settings.second);
583 if (!settings.second->authorized(*sso_statement,assertions)) {
584 for (int k = 0; k < assertions.size(); k++)
585 delete assertions[k];
586 delete sso_statement;
587 return WriteClientError(pfc, application, "access", markupProcessor);
591 // Get the AAP providers, which contain the attribute policy info.
592 Iterator<IAAP*> provs=application->getAAPProviders();
594 // Clear out the list of mapped attributes
595 while (provs.hasNext()) {
596 IAAP* aap=provs.next();
599 Iterator<const IAttributeRule*> rules=aap->getAttributeRules();
600 while (rules.hasNext()) {
601 const char* header=rules.next()->getHeader();
603 string hname=string(header) + ':';
604 pn->SetHeader(pfc,const_cast<char*>(hname.c_str()),"");
610 for (int k = 0; k < assertions.size(); k++)
611 delete assertions[k];
612 delete sso_statement;
613 markupProcessor.insert("errorType", "Attribute Processing Error");
614 markupProcessor.insert("errorText", "Unexpected Exception");
615 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
616 return WriteClientError(pfc, application, "rm", markupProcessor);
622 // Maybe export the first assertion.
623 pn->SetHeader(pfc,"remote-user:","");
624 pn->SetHeader(pfc,"Shib-Attributes:","");
625 pair<bool,bool> exp=settings.first->getBool("exportAssertion");
626 if (exp.first && exp.second && assertions.size()) {
628 RM::serialize(*(assertions[0]), assertion);
629 string::size_type lfeed;
630 while ((lfeed=assertion.find('\n'))!=string::npos)
631 assertion.erase(lfeed,1);
632 pn->SetHeader(pfc,"Shib-Attributes:",const_cast<char*>(assertion.c_str()));
635 pn->SetHeader(pfc,"Shib-Origin-Site:","");
636 pn->SetHeader(pfc,"Shib-Authentication-Method:","");
637 pn->SetHeader(pfc,"Shib-NameIdentifier-Format:","");
639 // Export the SAML AuthnMethod and the origin site name.
640 auto_ptr_char os(sso_statement->getSubject()->getNameIdentifier()->getNameQualifier());
641 auto_ptr_char am(sso_statement->getAuthMethod());
642 pn->SetHeader(pfc,"Shib-Origin-Site:", const_cast<char*>(os.get()));
643 pn->SetHeader(pfc,"Shib-Authentication-Method:", const_cast<char*>(am.get()));
646 AAP wrapper(provs,sso_statement->getSubject()->getNameIdentifier()->getFormat(),Constants::SHIB_ATTRIBUTE_NAMESPACE_URI);
647 if (!wrapper.fail() && wrapper->getHeader()) {
648 auto_ptr_char form(sso_statement->getSubject()->getNameIdentifier()->getFormat());
649 auto_ptr_char nameid(sso_statement->getSubject()->getNameIdentifier()->getName());
650 pn->SetHeader(pfc,"Shib-NameIdentifier-Format:",const_cast<char*>(form.get()));
651 if (!strcmp(wrapper->getHeader(),"REMOTE_USER")) {
652 char* principal=const_cast<char*>(nameid.get());
653 pn->SetHeader(pfc,"remote-user:",principal);
654 pfc->pFilterContext=pfc->AllocMem(pfc,strlen(principal)+1,0);
655 if (pfc->pFilterContext)
656 strcpy(static_cast<char*>(pfc->pFilterContext),principal);
659 string hname=string(wrapper->getHeader()) + ':';
660 pn->SetHeader(pfc,const_cast<char*>(wrapper->getHeader()),const_cast<char*>(nameid.get()));
664 pn->SetHeader(pfc,"Shib-Application-ID:","");
665 pn->SetHeader(pfc,"Shib-Application-ID:",const_cast<char*>(application_id.second));
667 // Export the attributes.
668 Iterator<SAMLAssertion*> a_iter(assertions);
669 while (a_iter.hasNext()) {
670 SAMLAssertion* assert=a_iter.next();
671 Iterator<SAMLStatement*> statements=assert->getStatements();
672 while (statements.hasNext()) {
673 SAMLAttributeStatement* astate=dynamic_cast<SAMLAttributeStatement*>(statements.next());
676 Iterator<SAMLAttribute*> attrs=astate->getAttributes();
677 while (attrs.hasNext()) {
678 SAMLAttribute* attr=attrs.next();
680 // Are we supposed to export it?
681 AAP wrapper(provs,attr->getName(),attr->getNamespace());
682 if (wrapper.fail() || !wrapper->getHeader())
685 Iterator<string> vals=attr->getSingleByteValues();
686 if (!strcmp(wrapper->getHeader(),"REMOTE_USER") && vals.hasNext()) {
687 char* principal=const_cast<char*>(vals.next().c_str());
688 pn->SetHeader(pfc,"remote-user:",principal);
689 pfc->pFilterContext=pfc->AllocMem(pfc,strlen(principal)+1,0);
690 if (pfc->pFilterContext)
691 strcpy(static_cast<char*>(pfc->pFilterContext),principal);
696 string hname=string(wrapper->getHeader()) + ':';
697 GetHeader(pn,pfc,const_cast<char*>(hname.c_str()),buf,256,false);
702 for (; vals.hasNext(); it++) {
703 string value = vals.next();
704 for (string::size_type pos = value.find_first_of(";", string::size_type(0));
706 pos = value.find_first_of(";", pos)) {
707 value.insert(pos, "\\");
713 header=header + ';' + value;
715 pn->SetHeader(pfc,const_cast<char*>(hname.c_str()),const_cast<char*>(header.c_str()));
722 for (int k = 0; k < assertions.size(); k++)
723 delete assertions[k];
724 delete sso_statement;
726 return SF_STATUS_REQ_NEXT_NOTIFICATION;
729 return WriteClientError(pfc,"Out of Memory");
732 if (e==ERROR_NO_DATA)
733 return WriteClientError(pfc,"A required variable or header was empty.");
735 return WriteClientError(pfc,"Server detected unexpected IIS error.");
739 return WriteClientError(pfc,"Server caught an unknown exception.");
743 return WriteClientError(pfc,"Server reached unreachable code, save my walrus!");
746 IRequestMapper::Settings map_request(
747 LPEXTENSION_CONTROL_BLOCK lpECB, IRequestMapper* mapper, const site_t& site, string& target
751 GetServerVariable(lpECB,"HTTPS",ssl,5);
752 bool SSL=(ssl=="on");
754 // URL path always come from IIS.
756 GetServerVariable(lpECB,"URL",url,255);
758 // Port may come from IIS or from site def.
760 if (site.m_port.empty() || !g_bNormalizeRequest)
761 GetServerVariable(lpECB,"SERVER_PORT",port,10);
763 strncpy(port,site.m_port.c_str(),10);
764 static_cast<char*>(port)[10]=0;
767 // Scheme may come from site def or be derived from IIS.
768 const char* scheme=site.m_scheme.c_str();
769 if (!scheme || !*scheme || !g_bNormalizeRequest)
770 scheme=lpECB->lpszMethod;
774 target=static_cast<char*>(url);
776 // If port is non-default, prepend it.
777 if ((!strcmp(scheme,"http") && port!="80") || (!strcmp(scheme,"https") && port!="443"))
778 target = ':' + static_cast<char*>(port) + target;
780 if (g_bNormalizeRequest) {
781 target = string(scheme) + "://" + site.m_name + target;
785 GetServerVariable(lpECB,"SERVER_NAME",name,64);
786 target = string(scheme) + "://" + static_cast<char*>(name) + target;
788 return mapper->getSettingsFromParsedURL(scheme,site.m_name.c_str(),strtoul(port,NULL,10),url);
791 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* msg)
793 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
794 static const char* ctype="Content-Type: text/html\r\n";
795 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
796 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Error</TITLE></HEAD><BODY><H1>Shibboleth Error</H1>";
797 DWORD resplen=strlen(xmsg);
798 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg,&resplen,HSE_IO_SYNC);
800 lpECB->WriteClient(lpECB->ConnID,(LPVOID)msg,&resplen,HSE_IO_SYNC);
801 static const char* xmsg2="</BODY></HTML>";
802 resplen=strlen(xmsg2);
803 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg2,&resplen,HSE_IO_SYNC);
804 return HSE_STATUS_SUCCESS;
807 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const IApplication* app, const char* page, ShibMLP& mlp)
809 const IPropertySet* props=app->getPropertySet("Errors");
811 pair<bool,const char*> p=props->getString(page);
813 ifstream infile(p.second);
814 if (!infile.fail()) {
815 const char* res = mlp.run(infile,props);
817 static const char* ctype="Content-Type: text/html\r\n";
818 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
819 DWORD resplen=strlen(res);
820 lpECB->WriteClient(lpECB->ConnID,(LPVOID)res,&resplen,0);
821 return HSE_STATUS_SUCCESS;
826 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Extension unable to open error template.");
827 return WriteClientError(lpECB,"Unable to open error template, check settings.");
830 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
833 const IApplication* application=NULL;
836 ostringstream threadid;
837 threadid << "[" << getpid() << "] shire_handler" << '\0';
838 saml::NDC ndc(threadid.str().c_str());
840 // Determine web site number. This can't really fail, I don't think.
842 GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
844 // Match site instance to host name, skip if no match.
845 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
846 if (map_i==g_Sites.end())
847 return WriteClientError(lpECB,"Shibboleth filter not configured for this web site.");
849 // We lock the configuration system for the duration.
850 IConfig* conf=g_Config->getINI();
853 // Map request to application and content settings.
855 IRequestMapper* mapper=conf->getRequestMapper();
856 Locker locker2(mapper);
857 IRequestMapper::Settings settings=map_request(lpECB,mapper,map_i->second,targeturl);
858 pair<bool,const char*> application_id=settings.first->getString("applicationId");
859 application=conf->getApplication(application_id.second);
860 const IPropertySet* sessionProps=application ? application->getPropertySet("Sessions") : NULL;
861 if (!application || !sessionProps)
862 return WriteClientError(lpECB,"Unable to map request to application session settings, check configuration.");
864 SHIRE shire(application);
866 // Make sure we only process the SHIRE requests.
867 if (!strstr(targeturl.c_str(),shire.getShireURL(targeturl.c_str())))
868 return WriteClientError(lpECB,"The request's application and associated shireURL setting are inconsistent.");;
870 pair<const char*,const char*> shib_cookie=shire.getCookieNameProps();
872 // Make sure this is SSL, if it should be
873 pair<bool,bool> shireSSL=sessionProps->getBool("shireSSL");
874 if (!shireSSL.first || shireSSL.second) {
875 GetServerVariable(lpECB,"HTTPS",buf,10);
877 throw ShibTargetException(SHIBRPC_OK,"blocked non-SSL access to SHIRE POST processor");
880 // If this is a GET, we manufacture an AuthnRequest.
881 if (!stricmp(lpECB->lpszMethod,"GET")) {
882 const char* areq=lpECB->lpszQueryString ? shire.getLazyAuthnRequest(lpECB->lpszQueryString) : NULL;
884 throw ShibTargetException(SHIBRPC_OK, "malformed arguments to request a new session");
885 targeturl = string("Location: ") + areq + "\r\n"
886 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
887 "Cache-Control: private,no-store,no-cache\r\n"
888 "Connection: close\r\n";
889 HSE_SEND_HEADER_EX_INFO hinfo;
890 hinfo.pszStatus="302 Moved";
891 hinfo.pszHeader=targeturl.c_str();
893 hinfo.cchHeader=targeturl.length();
894 hinfo.fKeepConn=FALSE;
895 if (lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER_EX,&hinfo,0,0))
896 return HSE_STATUS_SUCCESS;
897 return HSE_STATUS_ERROR;
899 else if (stricmp(lpECB->lpszMethod,"POST"))
900 throw ShibTargetException(SHIBRPC_OK,"blocked non-POST to SHIRE POST processor");
902 // Sure sure this POST is an appropriate content type
903 if (!lpECB->lpszContentType || stricmp(lpECB->lpszContentType,"application/x-www-form-urlencoded"))
904 throw ShibTargetException(SHIBRPC_OK,"blocked bad content-type to SHIRE POST processor");
907 pair<const char*,const char*> elements=pair<const char*,const char*>(NULL,NULL);
908 if (lpECB->cbTotalBytes > 1024*1024) // 1MB?
909 throw ShibTargetException(SHIBRPC_OK,"blocked too-large a post to SHIRE POST processor");
910 else if (lpECB->cbTotalBytes!=lpECB->cbAvailable) {
913 DWORD datalen=lpECB->cbTotalBytes;
916 BOOL ret=lpECB->ReadClient(lpECB->ConnID,buf,&buflen);
918 throw ShibTargetException(SHIBRPC_OK,"error reading POST data from browser");
919 cgistr.append(buf,buflen);
922 elements=shire.getFormSubmission(cgistr.c_str(),cgistr.length());
925 elements=shire.getFormSubmission(reinterpret_cast<char*>(lpECB->lpbData),lpECB->cbAvailable);
927 // Make sure the SAML Response parameter exists
928 if (!elements.first || !*elements.first)
929 throw ShibTargetException(SHIBRPC_OK, "SHIRE POST failed to find SAMLResponse form element");
931 // Make sure the target parameter exists
932 if (!elements.second || !*elements.second)
933 throw ShibTargetException(SHIBRPC_OK, "SHIRE POST failed to find TARGET form element");
935 GetServerVariable(lpECB,"REMOTE_ADDR",buf,16);
939 RPCError* status=NULL;
940 ShibMLP markupProcessor;
941 markupProcessor.insert("requestURL", targeturl.c_str());
943 status = shire.sessionCreate(elements.first,buf,cookie);
945 catch (ShibTargetException &e) {
946 markupProcessor.insert("errorType", "Session Creation Service Error");
947 markupProcessor.insert("errorText", e.what());
948 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
949 return WriteClientError(lpECB, application, "shire", markupProcessor);
953 markupProcessor.insert("errorType", "Session Creation Service Error");
954 markupProcessor.insert("errorText", "Unexpected Exception");
955 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
956 return WriteClientError(lpECB, application, "shire", markupProcessor);
960 if (status->isError()) {
961 if (status->isRetryable()) {
963 const char* loc=shire.getAuthnRequest(elements.second);
964 DWORD len=strlen(loc);
965 if (lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_URL_REDIRECT_RESP,(LPVOID)loc,&len,0))
966 return HSE_STATUS_SUCCESS;
967 return HSE_STATUS_ERROR;
970 // Return this error to the user.
971 markupProcessor.insert(*status);
973 return WriteClientError(lpECB,application,"shire",markupProcessor);
977 // We've got a good session, set the cookie and redirect to target.
978 cookie = string("Set-Cookie: ") + shib_cookie.first + '=' + cookie + shib_cookie.second + "\r\n"
979 "Location: " + elements.second + "\r\n"
980 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
981 "Cache-Control: private,no-store,no-cache\r\n"
982 "Connection: close\r\n";
983 HSE_SEND_HEADER_EX_INFO hinfo;
984 hinfo.pszStatus="302 Moved";
985 hinfo.pszHeader=cookie.c_str();
987 hinfo.cchHeader=cookie.length();
988 hinfo.fKeepConn=FALSE;
989 if (lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER_EX,&hinfo,0,0))
990 return HSE_STATUS_SUCCESS;
992 catch (ShibTargetException &e) {
994 ShibMLP markupProcessor;
995 markupProcessor.insert("requestURL", targeturl.c_str());
996 markupProcessor.insert("errorType", "Session Creation Service Error");
997 markupProcessor.insert("errorText", e.what());
998 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
999 return WriteClientError(lpECB,application,"shire",markupProcessor);
1005 ShibMLP markupProcessor;
1006 markupProcessor.insert("requestURL", targeturl.c_str());
1007 markupProcessor.insert("errorType", "Session Creation Service Error");
1008 markupProcessor.insert("errorText", "Unexpected Exception");
1009 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
1010 return WriteClientError(lpECB,application,"shire",markupProcessor);
1015 return HSE_STATUS_ERROR;