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 const char* shireURL=shire.getShireURL(targeturl.c_str());
473 return WriteClientError(pfc,"Unable to map request to proper shireURL setting, check configuration.");
475 // If the user is accessing the SHIRE acceptance point, pass it on.
476 if (targeturl.find(shireURL)!=string::npos)
477 return SF_STATUS_REQ_NEXT_NOTIFICATION;
479 // Now check the policy for this request.
480 pair<bool,bool> requireSession=settings.first->getBool("requireSession");
481 pair<const char*,const char*> shib_cookie=shire.getCookieNameProps();
483 // Check for session cookie.
484 const char* session_id=NULL;
485 GetHeader(pn,pfc,"Cookie:",buf,128,false);
486 Category::getInstance("isapi_shib.HttpFilterProc").debug("cookie header is {%s}",(const char*)buf);
487 if (!buf.empty() && (session_id=strstr(buf,shib_cookie.first))) {
488 session_id+=strlen(shib_cookie.first) + 1; /* Skip over the '=' */
489 char* cookieend=strchr(session_id,';');
491 *cookieend = '\0'; /* Ignore anyting after a ; */
494 if (!session_id || !*session_id) {
495 // If no session required, bail now.
496 if (!requireSession.second)
497 return SF_STATUS_REQ_NEXT_NOTIFICATION;
499 // No acceptable cookie, and we require a session. Generate an AuthnRequest.
500 string loc("Location: ");
501 loc+=shire.getAuthnRequest(targeturl.c_str());
503 pfc->AddResponseHeaders(pfc,const_cast<char*>(loc.c_str()),0);
504 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"302 Please Wait",0,0);
505 return SF_STATUS_REQ_FINISHED;
508 // Make sure this session is still valid.
509 RPCError* status = NULL;
510 ShibMLP markupProcessor;
511 markupProcessor.insert("requestURL", targeturl);
514 GetServerVariable(pfc,"REMOTE_ADDR",abuf,16);
516 status = shire.sessionIsValid(session_id, abuf);
518 catch (ShibTargetException &e) {
519 markupProcessor.insert("errorType", "Session Processing Error");
520 markupProcessor.insert("errorText", e.what());
521 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
522 return WriteClientError(pfc, application, "shire", markupProcessor);
526 markupProcessor.insert("errorType", "Session Processing Error");
527 markupProcessor.insert("errorText", "Unexpected Exception");
528 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
529 return WriteClientError(pfc, application, "shire", markupProcessor);
534 if (status->isError()) {
535 if (!requireSession.second)
536 return SF_STATUS_REQ_NEXT_NOTIFICATION;
537 else if (status->isRetryable()) {
538 // Oops, session is invalid. Generate AuthnRequest.
540 string loc("Location: ");
541 loc+=shire.getAuthnRequest(targeturl.c_str());
543 pfc->AddResponseHeaders(pfc,const_cast<char*>(loc.c_str()),0);
544 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"302 Please Wait",0,0);
545 return SF_STATUS_REQ_FINISHED;
548 // return the error page to the user
549 markupProcessor.insert(*status);
551 return WriteClientError(pfc, application, "shire", markupProcessor);
558 vector<SAMLAssertion*> assertions;
559 SAMLAuthenticationStatement* sso_statement=NULL;
562 status = rm.getAssertions(session_id, abuf, assertions, &sso_statement);
564 catch (ShibTargetException &e) {
565 markupProcessor.insert("errorType", "Attribute Processing Error");
566 markupProcessor.insert("errorText", e.what());
567 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
568 return WriteClientError(pfc, application, "rm", markupProcessor);
572 markupProcessor.insert("errorType", "Attribute Processing Error");
573 markupProcessor.insert("errorText", "Unexpected Exception");
574 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
575 return WriteClientError(pfc, application, "rm", markupProcessor);
579 if (status->isError()) {
580 markupProcessor.insert(*status);
582 return WriteClientError(pfc, application, "rm", markupProcessor);
586 // Do we have an access control plugin?
587 if (settings.second) {
588 Locker acllock(settings.second);
589 if (!settings.second->authorized(*sso_statement,assertions)) {
590 for (int k = 0; k < assertions.size(); k++)
591 delete assertions[k];
592 delete sso_statement;
593 return WriteClientError(pfc, application, "access", markupProcessor);
597 // Get the AAP providers, which contain the attribute policy info.
598 Iterator<IAAP*> provs=application->getAAPProviders();
600 // Clear out the list of mapped attributes
601 while (provs.hasNext()) {
602 IAAP* aap=provs.next();
605 Iterator<const IAttributeRule*> rules=aap->getAttributeRules();
606 while (rules.hasNext()) {
607 const char* header=rules.next()->getHeader();
609 string hname=string(header) + ':';
610 pn->SetHeader(pfc,const_cast<char*>(hname.c_str()),"");
616 for (int k = 0; k < assertions.size(); k++)
617 delete assertions[k];
618 delete sso_statement;
619 markupProcessor.insert("errorType", "Attribute Processing Error");
620 markupProcessor.insert("errorText", "Unexpected Exception");
621 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
622 return WriteClientError(pfc, application, "rm", markupProcessor);
628 // Maybe export the first assertion.
629 pn->SetHeader(pfc,"remote-user:","");
630 pn->SetHeader(pfc,"Shib-Attributes:","");
631 pair<bool,bool> exp=settings.first->getBool("exportAssertion");
632 if (exp.first && exp.second && assertions.size()) {
634 RM::serialize(*(assertions[0]), assertion);
635 string::size_type lfeed;
636 while ((lfeed=assertion.find('\n'))!=string::npos)
637 assertion.erase(lfeed,1);
638 pn->SetHeader(pfc,"Shib-Attributes:",const_cast<char*>(assertion.c_str()));
641 pn->SetHeader(pfc,"Shib-Origin-Site:","");
642 pn->SetHeader(pfc,"Shib-Authentication-Method:","");
643 pn->SetHeader(pfc,"Shib-NameIdentifier-Format:","");
645 // Export the SAML AuthnMethod and the origin site name.
646 auto_ptr_char os(sso_statement->getSubject()->getNameIdentifier()->getNameQualifier());
647 auto_ptr_char am(sso_statement->getAuthMethod());
648 pn->SetHeader(pfc,"Shib-Origin-Site:", const_cast<char*>(os.get()));
649 pn->SetHeader(pfc,"Shib-Authentication-Method:", const_cast<char*>(am.get()));
652 AAP wrapper(provs,sso_statement->getSubject()->getNameIdentifier()->getFormat(),Constants::SHIB_ATTRIBUTE_NAMESPACE_URI);
653 if (!wrapper.fail() && wrapper->getHeader()) {
654 auto_ptr_char form(sso_statement->getSubject()->getNameIdentifier()->getFormat());
655 auto_ptr_char nameid(sso_statement->getSubject()->getNameIdentifier()->getName());
656 pn->SetHeader(pfc,"Shib-NameIdentifier-Format:",const_cast<char*>(form.get()));
657 if (!strcmp(wrapper->getHeader(),"REMOTE_USER")) {
658 char* principal=const_cast<char*>(nameid.get());
659 pn->SetHeader(pfc,"remote-user:",principal);
660 pfc->pFilterContext=pfc->AllocMem(pfc,strlen(principal)+1,0);
661 if (pfc->pFilterContext)
662 strcpy(static_cast<char*>(pfc->pFilterContext),principal);
665 string hname=string(wrapper->getHeader()) + ':';
666 pn->SetHeader(pfc,const_cast<char*>(wrapper->getHeader()),const_cast<char*>(nameid.get()));
670 pn->SetHeader(pfc,"Shib-Application-ID:","");
671 pn->SetHeader(pfc,"Shib-Application-ID:",const_cast<char*>(application_id.second));
673 // Export the attributes.
674 Iterator<SAMLAssertion*> a_iter(assertions);
675 while (a_iter.hasNext()) {
676 SAMLAssertion* assert=a_iter.next();
677 Iterator<SAMLStatement*> statements=assert->getStatements();
678 while (statements.hasNext()) {
679 SAMLAttributeStatement* astate=dynamic_cast<SAMLAttributeStatement*>(statements.next());
682 Iterator<SAMLAttribute*> attrs=astate->getAttributes();
683 while (attrs.hasNext()) {
684 SAMLAttribute* attr=attrs.next();
686 // Are we supposed to export it?
687 AAP wrapper(provs,attr->getName(),attr->getNamespace());
688 if (wrapper.fail() || !wrapper->getHeader())
691 Iterator<string> vals=attr->getSingleByteValues();
692 if (!strcmp(wrapper->getHeader(),"REMOTE_USER") && vals.hasNext()) {
693 char* principal=const_cast<char*>(vals.next().c_str());
694 pn->SetHeader(pfc,"remote-user:",principal);
695 pfc->pFilterContext=pfc->AllocMem(pfc,strlen(principal)+1,0);
696 if (pfc->pFilterContext)
697 strcpy(static_cast<char*>(pfc->pFilterContext),principal);
702 string hname=string(wrapper->getHeader()) + ':';
703 GetHeader(pn,pfc,const_cast<char*>(hname.c_str()),buf,256,false);
708 for (; vals.hasNext(); it++) {
709 string value = vals.next();
710 for (string::size_type pos = value.find_first_of(";", string::size_type(0));
712 pos = value.find_first_of(";", pos)) {
713 value.insert(pos, "\\");
719 header=header + ';' + value;
721 pn->SetHeader(pfc,const_cast<char*>(hname.c_str()),const_cast<char*>(header.c_str()));
728 for (int k = 0; k < assertions.size(); k++)
729 delete assertions[k];
730 delete sso_statement;
732 return SF_STATUS_REQ_NEXT_NOTIFICATION;
735 return WriteClientError(pfc,"Out of Memory");
738 if (e==ERROR_NO_DATA)
739 return WriteClientError(pfc,"A required variable or header was empty.");
741 return WriteClientError(pfc,"Server detected unexpected IIS error.");
745 return WriteClientError(pfc,"Server caught an unknown exception.");
749 return WriteClientError(pfc,"Server reached unreachable code, save my walrus!");
752 IRequestMapper::Settings map_request(
753 LPEXTENSION_CONTROL_BLOCK lpECB, IRequestMapper* mapper, const site_t& site, string& target
757 GetServerVariable(lpECB,"HTTPS",ssl,5);
758 bool SSL=(ssl=="on");
760 // URL path always come from IIS.
762 GetServerVariable(lpECB,"URL",url,255);
764 // Port may come from IIS or from site def.
766 if (site.m_port.empty() || !g_bNormalizeRequest)
767 GetServerVariable(lpECB,"SERVER_PORT",port,10);
769 strncpy(port,site.m_port.c_str(),10);
770 static_cast<char*>(port)[10]=0;
773 // Scheme may come from site def or be derived from IIS.
774 const char* scheme=site.m_scheme.c_str();
775 if (!scheme || !*scheme || !g_bNormalizeRequest)
776 scheme=lpECB->lpszMethod;
780 target=static_cast<char*>(url);
782 // If port is non-default, prepend it.
783 if ((!strcmp(scheme,"http") && port!="80") || (!strcmp(scheme,"https") && port!="443"))
784 target = ':' + static_cast<char*>(port) + target;
786 if (g_bNormalizeRequest) {
787 target = string(scheme) + "://" + site.m_name + target;
791 GetServerVariable(lpECB,"SERVER_NAME",name,64);
792 target = string(scheme) + "://" + static_cast<char*>(name) + target;
794 return mapper->getSettingsFromParsedURL(scheme,site.m_name.c_str(),strtoul(port,NULL,10),url);
797 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* msg)
799 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
800 static const char* ctype="Content-Type: text/html\r\n";
801 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
802 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Error</TITLE></HEAD><BODY><H1>Shibboleth Error</H1>";
803 DWORD resplen=strlen(xmsg);
804 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg,&resplen,HSE_IO_SYNC);
806 lpECB->WriteClient(lpECB->ConnID,(LPVOID)msg,&resplen,HSE_IO_SYNC);
807 static const char* xmsg2="</BODY></HTML>";
808 resplen=strlen(xmsg2);
809 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg2,&resplen,HSE_IO_SYNC);
810 return HSE_STATUS_SUCCESS;
813 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const IApplication* app, const char* page, ShibMLP& mlp)
815 const IPropertySet* props=app->getPropertySet("Errors");
817 pair<bool,const char*> p=props->getString(page);
819 ifstream infile(p.second);
820 if (!infile.fail()) {
821 const char* res = mlp.run(infile,props);
823 static const char* ctype="Content-Type: text/html\r\n";
824 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
825 DWORD resplen=strlen(res);
826 lpECB->WriteClient(lpECB->ConnID,(LPVOID)res,&resplen,0);
827 return HSE_STATUS_SUCCESS;
832 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Extension unable to open error template.");
833 return WriteClientError(lpECB,"Unable to open error template, check settings.");
836 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
839 const IApplication* application=NULL;
842 ostringstream threadid;
843 threadid << "[" << getpid() << "] shire_handler" << '\0';
844 saml::NDC ndc(threadid.str().c_str());
846 // Determine web site number. This can't really fail, I don't think.
848 GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
850 // Match site instance to host name, skip if no match.
851 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
852 if (map_i==g_Sites.end())
853 return WriteClientError(lpECB,"Shibboleth filter not configured for this web site.");
855 // We lock the configuration system for the duration.
856 IConfig* conf=g_Config->getINI();
859 // Map request to application and content settings.
861 IRequestMapper* mapper=conf->getRequestMapper();
862 Locker locker2(mapper);
863 IRequestMapper::Settings settings=map_request(lpECB,mapper,map_i->second,targeturl);
864 pair<bool,const char*> application_id=settings.first->getString("applicationId");
865 application=conf->getApplication(application_id.second);
866 const IPropertySet* sessionProps=application ? application->getPropertySet("Sessions") : NULL;
867 if (!application || !sessionProps)
868 return WriteClientError(lpECB,"Unable to map request to application session settings, check configuration.");
870 SHIRE shire(application);
872 const char* shireURL=shire.getShireURL(targeturl.c_str());
874 return WriteClientError(lpECB,"Unable to map request to proper shireURL setting, check configuration.");
876 // Make sure we only process the SHIRE requests.
877 if (!strstr(targeturl.c_str(),shireURL))
878 return WriteClientError(lpECB,"The request's application and associated shireURL setting are inconsistent.");;
880 pair<const char*,const char*> shib_cookie=shire.getCookieNameProps();
882 // Make sure this is SSL, if it should be
883 pair<bool,bool> shireSSL=sessionProps->getBool("shireSSL");
884 if (!shireSSL.first || shireSSL.second) {
885 GetServerVariable(lpECB,"HTTPS",buf,10);
887 throw ShibTargetException(SHIBRPC_OK,"blocked non-SSL access to SHIRE POST processor");
890 // If this is a GET, we manufacture an AuthnRequest.
891 if (!stricmp(lpECB->lpszMethod,"GET")) {
892 const char* areq=lpECB->lpszQueryString ? shire.getLazyAuthnRequest(lpECB->lpszQueryString) : NULL;
894 throw ShibTargetException(SHIBRPC_OK, "malformed arguments to request a new session");
895 targeturl = string("Location: ") + areq + "\r\n"
896 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
897 "Cache-Control: private,no-store,no-cache\r\n"
898 "Connection: close\r\n";
899 HSE_SEND_HEADER_EX_INFO hinfo;
900 hinfo.pszStatus="302 Moved";
901 hinfo.pszHeader=targeturl.c_str();
903 hinfo.cchHeader=targeturl.length();
904 hinfo.fKeepConn=FALSE;
905 if (lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER_EX,&hinfo,0,0))
906 return HSE_STATUS_SUCCESS;
907 return HSE_STATUS_ERROR;
909 else if (stricmp(lpECB->lpszMethod,"POST"))
910 throw ShibTargetException(SHIBRPC_OK,"blocked non-POST to SHIRE POST processor");
912 // Sure sure this POST is an appropriate content type
913 if (!lpECB->lpszContentType || stricmp(lpECB->lpszContentType,"application/x-www-form-urlencoded"))
914 throw ShibTargetException(SHIBRPC_OK,"blocked bad content-type to SHIRE POST processor");
917 pair<const char*,const char*> elements=pair<const char*,const char*>(NULL,NULL);
918 if (lpECB->cbTotalBytes > 1024*1024) // 1MB?
919 throw ShibTargetException(SHIBRPC_OK,"blocked too-large a post to SHIRE POST processor");
920 else if (lpECB->cbTotalBytes!=lpECB->cbAvailable) {
923 DWORD datalen=lpECB->cbTotalBytes;
926 BOOL ret=lpECB->ReadClient(lpECB->ConnID,buf,&buflen);
928 throw ShibTargetException(SHIBRPC_OK,"error reading POST data from browser");
929 cgistr.append(buf,buflen);
932 elements=shire.getFormSubmission(cgistr.c_str(),cgistr.length());
935 elements=shire.getFormSubmission(reinterpret_cast<char*>(lpECB->lpbData),lpECB->cbAvailable);
937 // Make sure the SAML Response parameter exists
938 if (!elements.first || !*elements.first)
939 throw ShibTargetException(SHIBRPC_OK, "SHIRE POST failed to find SAMLResponse form element");
941 // Make sure the target parameter exists
942 if (!elements.second || !*elements.second)
943 throw ShibTargetException(SHIBRPC_OK, "SHIRE POST failed to find TARGET form element");
945 GetServerVariable(lpECB,"REMOTE_ADDR",buf,16);
949 RPCError* status=NULL;
950 ShibMLP markupProcessor;
951 markupProcessor.insert("requestURL", targeturl.c_str());
953 status = shire.sessionCreate(elements.first,buf,cookie);
955 catch (ShibTargetException &e) {
956 markupProcessor.insert("errorType", "Session Creation Service Error");
957 markupProcessor.insert("errorText", e.what());
958 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
959 return WriteClientError(lpECB, application, "shire", markupProcessor);
963 markupProcessor.insert("errorType", "Session Creation Service Error");
964 markupProcessor.insert("errorText", "Unexpected Exception");
965 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
966 return WriteClientError(lpECB, application, "shire", markupProcessor);
970 if (status->isError()) {
971 if (status->isRetryable()) {
973 const char* loc=shire.getAuthnRequest(elements.second);
974 DWORD len=strlen(loc);
975 if (lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_URL_REDIRECT_RESP,(LPVOID)loc,&len,0))
976 return HSE_STATUS_SUCCESS;
977 return HSE_STATUS_ERROR;
980 // Return this error to the user.
981 markupProcessor.insert(*status);
983 return WriteClientError(lpECB,application,"shire",markupProcessor);
987 // We've got a good session, set the cookie and redirect to target.
988 cookie = string("Set-Cookie: ") + shib_cookie.first + '=' + cookie + shib_cookie.second + "\r\n"
989 "Location: " + elements.second + "\r\n"
990 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
991 "Cache-Control: private,no-store,no-cache\r\n"
992 "Connection: close\r\n";
993 HSE_SEND_HEADER_EX_INFO hinfo;
994 hinfo.pszStatus="302 Moved";
995 hinfo.pszHeader=cookie.c_str();
997 hinfo.cchHeader=cookie.length();
998 hinfo.fKeepConn=FALSE;
999 if (lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER_EX,&hinfo,0,0))
1000 return HSE_STATUS_SUCCESS;
1002 catch (ShibTargetException &e) {
1004 ShibMLP markupProcessor;
1005 markupProcessor.insert("requestURL", targeturl.c_str());
1006 markupProcessor.insert("errorType", "Session Creation Service Error");
1007 markupProcessor.insert("errorText", e.what());
1008 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
1009 return WriteClientError(lpECB,application,"shire",markupProcessor);
1015 ShibMLP markupProcessor;
1016 markupProcessor.insert("requestURL", targeturl.c_str());
1017 markupProcessor.insert("errorType", "Session Creation Service Error");
1018 markupProcessor.insert("errorText", "Unexpected Exception");
1019 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
1020 return WriteClientError(lpECB,application,"shire",markupProcessor);
1025 return HSE_STATUS_ERROR;