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;
83 ShibTargetConfig* g_Config = NULL;
84 map<string,string> g_Sites;
85 bool g_bNormalizeRequest = true;
89 LPCSTR lpUNCServerName,
95 LPCSTR messages[] = {message, NULL};
97 HANDLE hElog = RegisterEventSource(lpUNCServerName, "Shibboleth ISAPI Filter");
98 BOOL res = ReportEvent(hElog, wType, 0, dwEventID, lpUserSid, 1, 0, messages, NULL);
99 return (DeregisterEventSource(hElog) && res);
102 extern "C" __declspec(dllexport) BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID)
104 if (fdwReason==DLL_PROCESS_ATTACH)
109 extern "C" BOOL WINAPI GetExtensionVersion(HSE_VERSION_INFO* pVer)
116 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
117 "Extension mode startup not possible, is the DLL loaded as a filter?");
121 pVer->dwExtensionVersion=HSE_VERSION;
122 strncpy(pVer->lpszExtensionDesc,"Shibboleth ISAPI Extension",HSE_MAX_EXT_DLL_NAME_LEN-1);
126 extern "C" BOOL WINAPI TerminateExtension(DWORD)
128 return TRUE; // cleanup should happen when filter unloads
131 static const XMLCh host[] = { chLatin_h, chLatin_o, chLatin_s, chLatin_t, chNull };
132 static const XMLCh id[] = { chLatin_i, chLatin_d, chNull };
133 static const XMLCh Implementation[] =
134 { 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 };
135 static const XMLCh ISAPI[] = { chLatin_I, chLatin_S, chLatin_A, chLatin_P, chLatin_I, chNull };
136 static const XMLCh normalizeRequest[] =
137 { chLatin_n, chLatin_o, chLatin_r, chLatin_m, chLatin_a, chLatin_l, chLatin_i, chLatin_z, chLatin_e,
138 chLatin_R, chLatin_e, chLatin_q, chLatin_u, chLatin_e, chLatin_s, chLatin_t, chNull
140 static const XMLCh Site[] = { chLatin_S, chLatin_i, chLatin_t, chLatin_e, chNull };
142 extern "C" BOOL WINAPI GetFilterVersion(PHTTP_FILTER_VERSION pVer)
149 LPCSTR schemadir=getenv("SHIBSCHEMAS");
151 schemadir=SHIB_SCHEMAS;
152 LPCSTR config=getenv("SHIBCONFIG");
155 g_Config=&ShibTargetConfig::getConfig();
156 g_Config->setFeatures(
157 ShibTargetConfig::Listener |
158 ShibTargetConfig::Metadata |
159 ShibTargetConfig::AAP |
160 ShibTargetConfig::RequestMapper |
161 ShibTargetConfig::SHIREExtensions
163 if (!g_Config->init(schemadir,config)) {
164 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
165 "Filter startup failed during initialization, check shire log for help.");
169 // Access the implementation-specifics for site mappings.
170 IConfig* conf=g_Config->getINI();
172 const IPropertySet* props=conf->getPropertySet("SHIRE");
174 const DOMElement* impl=saml::XML::getFirstChildElement(
175 props->getElement(),ShibTargetConfig::SHIBTARGET_NS,Implementation
177 if (impl && (impl=saml::XML::getFirstChildElement(impl,ShibTargetConfig::SHIBTARGET_NS,ISAPI))) {
178 const XMLCh* flag=impl->getAttributeNS(NULL,normalizeRequest);
179 g_bNormalizeRequest=(!flag || !*flag || *flag==chDigit_1 || *flag==chLatin_t);
180 impl=saml::XML::getFirstChildElement(impl,ShibTargetConfig::SHIBTARGET_NS,Site);
182 auto_ptr_char id(impl->getAttributeNS(NULL,id));
183 auto_ptr_char host(impl->getAttributeNS(NULL,host));
184 if (id.get() && host.get())
185 g_Sites[id.get()]=host.get();
186 impl=saml::XML::getNextSiblingElement(impl,ShibTargetConfig::SHIBTARGET_NS,Site);
193 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Filter startup failed with an exception.");
197 pVer->dwFilterVersion=HTTP_FILTER_REVISION;
198 strncpy(pVer->lpszFilterDesc,"Shibboleth ISAPI Filter",SF_MAX_FILTER_DESC_LEN);
199 pVer->dwFlags=(SF_NOTIFY_ORDER_HIGH |
200 SF_NOTIFY_SECURE_PORT |
201 SF_NOTIFY_NONSECURE_PORT |
202 SF_NOTIFY_PREPROC_HEADERS |
204 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter initialized...");
208 extern "C" BOOL WINAPI TerminateFilter(DWORD)
211 g_Config->shutdown();
213 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter shut down...");
217 /* Next up, some suck-free versions of various APIs.
219 You DON'T require people to guess the buffer size and THEN tell them the right size.
220 Returning an LPCSTR is apparently way beyond their ken. Not to mention the fact that
221 constant strings aren't typed as such, making it just that much harder. These versions
222 are now updated to use a special growable buffer object, modeled after the standard
223 string class. The standard string won't work because they left out the option to
224 pre-allocate a non-constant buffer.
230 dynabuf() { bufptr=NULL; buflen=0; }
231 dynabuf(size_t s) { bufptr=new char[buflen=s]; *bufptr=0; }
232 ~dynabuf() { delete[] bufptr; }
233 size_t length() const { return bufptr ? strlen(bufptr) : 0; }
234 size_t size() const { return buflen; }
235 bool empty() const { return length()==0; }
236 void reserve(size_t s, bool keep=false);
237 void erase() { if (bufptr) memset(bufptr,0,buflen); }
238 operator char*() { return bufptr; }
239 bool operator ==(const char* s) const;
240 bool operator !=(const char* s) const { return !(*this==s); }
246 void dynabuf::reserve(size_t s, bool keep)
253 p[buflen]=bufptr[buflen];
259 bool dynabuf::operator==(const char* s) const
261 if (buflen==NULL || s==NULL)
262 return (buflen==NULL && s==NULL);
264 return strcmp(bufptr,s)==0;
267 void GetServerVariable(PHTTP_FILTER_CONTEXT pfc, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
268 throw (bad_alloc, DWORD)
274 while (!pfc->GetServerVariable(pfc,lpszVariable,s,&size))
276 // Grumble. Check the error.
277 DWORD e=GetLastError();
278 if (e==ERROR_INSUFFICIENT_BUFFER)
283 if (bRequired && s.empty())
287 void GetServerVariable(LPEXTENSION_CONTROL_BLOCK lpECB, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
288 throw (bad_alloc, DWORD)
294 while (lpECB->GetServerVariable(lpECB->ConnID,lpszVariable,s,&size))
296 // Grumble. Check the error.
297 DWORD e=GetLastError();
298 if (e==ERROR_INSUFFICIENT_BUFFER)
303 if (bRequired && s.empty())
307 void GetHeader(PHTTP_FILTER_PREPROC_HEADERS pn, PHTTP_FILTER_CONTEXT pfc,
308 LPSTR lpszName, dynabuf& s, DWORD size=80, bool bRequired=true)
309 throw (bad_alloc, DWORD)
315 while (!pn->GetHeader(pfc,lpszName,s,&size))
317 // Grumble. Check the error.
318 DWORD e=GetLastError();
319 if (e==ERROR_INSUFFICIENT_BUFFER)
324 if (bRequired && s.empty())
328 IRequestMapper::Settings map_request(
329 PHTTP_FILTER_CONTEXT pfc, PHTTP_FILTER_PREPROC_HEADERS pn, IRequestMapper* mapper, const char* hostname, string& target
335 GetServerVariable(pfc,"REQUEST_METHOD",method,10);
336 GetServerVariable(pfc,"SERVER_PORT",port,10);
337 GetHeader(pn,pfc,"url",url,256,false);
340 target=static_cast<char*>(url);
341 if (port!=(pfc->fIsSecurePort ? "443" : "80"))
342 target = ':' + static_cast<char*>(port) + target;
344 if (g_bNormalizeRequest) {
345 target = string(pfc->fIsSecurePort ? "https://" : "http://") + hostname + target;
346 return mapper->getSettingsFromParsedURL(method,hostname,strtoul(port,NULL,10),url);
350 GetServerVariable(pfc,"SERVER_NAME",name,64);
351 target = string(pfc->fIsSecurePort ? "https://" : "http://") + static_cast<char*>(name) + target;
352 return mapper->getSettingsFromParsedURL(method,name,strtoul(port,NULL,10),url);
356 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const char* msg)
358 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
359 static const char* ctype="Content-Type: text/html\r\n";
360 pfc->AddResponseHeaders(pfc,const_cast<char*>(ctype),0);
361 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",0,0);
362 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Filter Error</TITLE></HEAD><BODY>"
363 "<H1>Shibboleth Filter Error</H1>";
364 DWORD resplen=strlen(xmsg);
365 pfc->WriteClient(pfc,(LPVOID)xmsg,&resplen,0);
367 pfc->WriteClient(pfc,(LPVOID)msg,&resplen,0);
368 static const char* xmsg2="</BODY></HTML>";
369 resplen=strlen(xmsg2);
370 pfc->WriteClient(pfc,(LPVOID)xmsg2,&resplen,0);
371 return SF_STATUS_REQ_FINISHED;
374 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const IApplication* app, const char* page, ShibMLP& mlp)
376 const IPropertySet* props=app->getPropertySet("Errors");
378 pair<bool,const char*> p=props->getString(page);
380 ifstream infile(p.second);
381 if (!infile.fail()) {
382 const char* res = mlp.run(infile);
384 static const char* ctype="Content-Type: text/html\r\n";
385 pfc->AddResponseHeaders(pfc,const_cast<char*>(ctype),0);
386 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",0,0);
387 DWORD resplen=strlen(res);
388 pfc->WriteClient(pfc,(LPVOID)res,&resplen,0);
389 return SF_STATUS_REQ_FINISHED;
395 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Filter unable to open error template.");
396 return WriteClientError(pfc,"Unable to open error template, check settings.");
399 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
401 // Is this a log notification?
402 if (notificationType==SF_NOTIFY_LOG)
404 if (pfc->pFilterContext)
405 ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=static_cast<LPCSTR>(pfc->pFilterContext);
406 return SF_STATUS_REQ_NEXT_NOTIFICATION;
409 PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
412 // Determine web site number. This can't really fail, I don't think.
414 GetServerVariable(pfc,"INSTANCE_ID",buf,10);
416 // Match site instance to host name, skip if no match.
417 map<string,string>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
418 if (map_i==g_Sites.end())
419 return SF_STATUS_REQ_NEXT_NOTIFICATION;
421 const string& site=map_i->second;
423 ostringstream threadid;
424 threadid << "[" << getpid() << "] isapi_shib" << '\0';
425 saml::NDC ndc(threadid.str().c_str());
427 // We lock the configuration system for the duration.
428 IConfig* conf=g_Config->getINI();
431 // Map request to application and content settings.
433 IRequestMapper* mapper=conf->getRequestMapper();
434 Locker locker2(mapper);
435 IRequestMapper::Settings settings=map_request(pfc,pn,mapper,site.c_str(),targeturl);
436 pair<bool,const char*> application_id=settings.first->getString("applicationId");
437 const IApplication* application=conf->getApplication(application_id.second);
438 const IPropertySet* sessionProps=application ? application->getPropertySet("Sessions") : NULL;
439 if (!application || !sessionProps)
440 return WriteClientError(pfc,"Unable to map request to application session settings, check configuration.");
442 // Declare SHIRE object for this request.
443 SHIRE shire(application);
445 // If the user is accessing the SHIRE acceptance point, pass it on.
446 if (targeturl.find(shire.getShireURL(targeturl.c_str()))!=string::npos)
447 return SF_STATUS_REQ_NEXT_NOTIFICATION;
449 // Now check the policy for this request.
450 pair<bool,bool> requireSession=settings.first->getBool("requireSession");
451 pair<bool,const char*> shib_cookie=sessionProps->getString("cookieName");
452 if (!shib_cookie.first)
453 return WriteClientError(pfc,"No session cookie name defined for this application, check configuration.");
455 // Check for session cookie.
456 const char* session_id=NULL;
457 GetHeader(pn,pfc,"Cookie:",buf,128,false);
458 Category::getInstance("isapi_shib.HttpFilterProc").debug("cookie header is {%s}",(const char*)buf);
459 if (!buf.empty() && (session_id=strstr(buf,shib_cookie.second))) {
460 session_id+=strlen(shib_cookie.second) + 1; /* Skip over the '=' */
461 char* cookieend=strchr(session_id,';');
463 *cookieend = '\0'; /* Ignore anyting after a ; */
466 if (!session_id || !*session_id) {
467 // If no session required, bail now.
468 if (!requireSession.second)
469 return SF_STATUS_REQ_NEXT_NOTIFICATION;
471 // No acceptable cookie, and we require a session. Generate an AuthnRequest.
472 string loc("Location: ");
473 loc+=shire.getAuthnRequest(targeturl.c_str());
474 pfc->AddResponseHeaders(pfc,const_cast<char*>(loc.c_str()),0);
475 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"302 Please Wait",0,0);
476 return SF_STATUS_REQ_FINISHED;
479 // Make sure this session is still valid.
480 RPCError* status = NULL;
481 ShibMLP markupProcessor(application);
482 markupProcessor.insert("requestURL", targeturl);
485 GetServerVariable(pfc,"REMOTE_ADDR",abuf,16);
487 status = shire.sessionIsValid(session_id, abuf);
489 catch (ShibTargetException &e) {
490 markupProcessor.insert("errorType", "Session Processing Error");
491 markupProcessor.insert("errorText", e.what());
492 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
493 return WriteClientError(pfc, application, "shire", markupProcessor);
497 markupProcessor.insert("errorType", "Session Processing Error");
498 markupProcessor.insert("errorText", "Unexpected Exception");
499 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
500 return WriteClientError(pfc, application, "shire", markupProcessor);
505 if (status->isError()) {
506 if (!requireSession.second)
507 return SF_STATUS_REQ_NEXT_NOTIFICATION;
508 else if (status->isRetryable()) {
509 // Oops, session is invalid. Generate AuthnRequest.
511 string loc("Location: ");
512 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 // return the error page to the user
519 markupProcessor.insert(*status);
521 return WriteClientError(pfc, application, "shire", markupProcessor);
528 vector<SAMLAssertion*> assertions;
529 SAMLAuthenticationStatement* sso_statement=NULL;
532 status = rm.getAssertions(session_id, abuf, assertions, &sso_statement);
534 catch (ShibTargetException &e) {
535 markupProcessor.insert("errorType", "Attribute Processing Error");
536 markupProcessor.insert("errorText", e.what());
537 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
538 return WriteClientError(pfc, application, "rm", markupProcessor);
542 markupProcessor.insert("errorType", "Attribute Processing Error");
543 markupProcessor.insert("errorText", "Unexpected Exception");
544 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
545 return WriteClientError(pfc, application, "rm", markupProcessor);
549 if (status->isError()) {
550 markupProcessor.insert(*status);
552 return WriteClientError(pfc, application, "rm", markupProcessor);
556 // Do we have an access control plugin?
557 if (settings.second) {
558 Locker acllock(settings.second);
559 if (!settings.second->authorized(assertions)) {
560 for (int k = 0; k < assertions.size(); k++)
561 delete assertions[k];
562 delete sso_statement;
563 return WriteClientError(pfc, application, "access", markupProcessor);
567 // Get the AAP providers, which contain the attribute policy info.
568 Iterator<IAAP*> provs=application->getAAPProviders();
570 // Clear out the list of mapped attributes
571 while (provs.hasNext()) {
572 IAAP* aap=provs.next();
575 Iterator<const IAttributeRule*> rules=aap->getAttributeRules();
576 while (rules.hasNext()) {
577 const char* header=rules.next()->getHeader();
579 pn->SetHeader(pfc,const_cast<char*>(header),"");
584 for (int k = 0; k < assertions.size(); k++)
585 delete assertions[k];
586 delete sso_statement;
587 markupProcessor.insert("errorType", "Attribute Processing Error");
588 markupProcessor.insert("errorText", "Unexpected Exception");
589 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
590 return WriteClientError(pfc, application, "rm", markupProcessor);
596 // Maybe export the first assertion.
597 pn->SetHeader(pfc,"remote-user:","");
598 pn->SetHeader(pfc,"Shib-Attributes:","");
599 pair<bool,bool> exp=settings.first->getBool("exportAssertion");
600 if (exp.first && exp.second && assertions.size()) {
602 RM::serialize(*(assertions[0]), assertion);
603 string::size_type lfeed;
604 while ((lfeed=assertion.find('\n'))!=string::npos)
605 assertion.erase(lfeed,1);
606 pn->SetHeader(pfc,"Shib-Attributes:",const_cast<char*>(assertion.c_str()));
609 pn->SetHeader(pfc,"Shib-Origin-Site:","");
610 pn->SetHeader(pfc,"Shib-Authentication-Method:","");
612 // Export the SAML AuthnMethod and the origin site name.
614 auto_ptr_char os(sso_statement->getSubject()->getNameQualifier());
615 auto_ptr_char am(sso_statement->getAuthMethod());
616 pn->SetHeader(pfc,"Shib-Origin-Site:", const_cast<char*>(os.get()));
617 pn->SetHeader(pfc,"Shib-Authentication-Method:", const_cast<char*>(am.get()));
620 pn->SetHeader(pfc,"Shib-Application-ID:","");
621 pn->SetHeader(pfc,"Shib-Application-ID:",const_cast<char*>(application_id.second));
623 // Export the attributes.
624 Iterator<SAMLAssertion*> a_iter(assertions);
625 while (a_iter.hasNext()) {
626 SAMLAssertion* assert=a_iter.next();
627 Iterator<SAMLStatement*> statements=assert->getStatements();
628 while (statements.hasNext()) {
629 SAMLAttributeStatement* astate=dynamic_cast<SAMLAttributeStatement*>(statements.next());
632 Iterator<SAMLAttribute*> attrs=astate->getAttributes();
633 while (attrs.hasNext()) {
634 SAMLAttribute* attr=attrs.next();
636 // Are we supposed to export it?
637 AAP wrapper(application->getAAPProviders(),attr->getName(),attr->getNamespace());
641 Iterator<string> vals=attr->getSingleByteValues();
642 if (!strcmp(wrapper->getHeader(),"REMOTE_USER") && vals.hasNext()) {
643 char* principal=const_cast<char*>(vals.next().c_str());
644 pn->SetHeader(pfc,"remote-user:",principal);
645 pfc->pFilterContext=pfc->AllocMem(pfc,strlen(principal)+1,0);
646 if (pfc->pFilterContext)
647 strcpy(static_cast<char*>(pfc->pFilterContext),principal);
652 string hname=string(wrapper->getHeader()) + ':';
653 GetHeader(pn,pfc,const_cast<char*>(hname.c_str()),buf,256,false);
658 for (; vals.hasNext(); it++) {
659 string value = vals.next();
660 for (string::size_type pos = value.find_first_of(";", string::size_type(0));
662 pos = value.find_first_of(";", pos)) {
663 value.insert(pos, "\\");
669 header=header + ';' + value;
671 pn->SetHeader(pfc,const_cast<char*>(hname.c_str()),const_cast<char*>(header.c_str()));
678 for (int k = 0; k < assertions.size(); k++)
679 delete assertions[k];
680 delete sso_statement;
682 return SF_STATUS_REQ_NEXT_NOTIFICATION;
685 return WriteClientError(pfc,"Out of Memory");
688 if (e==ERROR_NO_DATA)
689 return WriteClientError(pfc,"A required variable or header was empty.");
691 return WriteClientError(pfc,"Server detected unexpected IIS error.");
694 return WriteClientError(pfc,"Server caught an unknown exception.");
697 return WriteClientError(pfc,"Server reached unreachable code, save my walrus!");
700 IRequestMapper::Settings map_request(
701 LPEXTENSION_CONTROL_BLOCK lpECB, IRequestMapper* mapper, const char* hostname, string& target
707 GetServerVariable(lpECB,"HTTPS",ssl,5);
708 GetServerVariable(lpECB,"SERVER_PORT",port,10);
709 GetServerVariable(lpECB,"URL",url,255);
710 bool SSL=(ssl=="on");
713 target=static_cast<char*>(url);
714 if (port!=(SSL ? "443" : "80"))
715 target = ':' + static_cast<char*>(port) + target;
717 if (g_bNormalizeRequest) {
718 target = string(SSL ? "https://" : "http://") + hostname + target;
719 return mapper->getSettingsFromParsedURL(lpECB->lpszMethod,hostname,strtoul(port,NULL,10),url);
723 GetServerVariable(lpECB,"SERVER_NAME",name,64);
724 target = string(SSL ? "https://" : "http://") + static_cast<char*>(name) + target;
725 return mapper->getSettingsFromParsedURL(lpECB->lpszMethod,name,strtoul(port,NULL,10),url);
729 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* msg)
731 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
732 static const char* ctype="Content-Type: text/html\r\n";
733 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
734 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Error</TITLE></HEAD><BODY><H1>Shibboleth Error</H1>";
735 DWORD resplen=strlen(xmsg);
736 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg,&resplen,HSE_IO_SYNC);
738 lpECB->WriteClient(lpECB->ConnID,(LPVOID)msg,&resplen,HSE_IO_SYNC);
739 static const char* xmsg2="</BODY></HTML>";
740 resplen=strlen(xmsg2);
741 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg2,&resplen,HSE_IO_SYNC);
742 return HSE_STATUS_SUCCESS;
745 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const IApplication* app, const char* page, ShibMLP& mlp)
747 const IPropertySet* props=app->getPropertySet("Errors");
749 pair<bool,const char*> p=props->getString(page);
751 ifstream infile(p.second);
752 if (!infile.fail()) {
753 const char* res = mlp.run(infile);
755 static const char* ctype="Content-Type: text/html\r\n";
756 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
757 DWORD resplen=strlen(res);
758 lpECB->WriteClient(lpECB->ConnID,(LPVOID)res,&resplen,0);
759 return HSE_STATUS_SUCCESS;
764 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Extension unable to open error template.");
765 return WriteClientError(lpECB,"Unable to open error template, check settings.");
768 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
770 const IApplication* application=NULL;
773 ostringstream threadid;
774 threadid << "[" << getpid() << "] shire_handler" << '\0';
775 saml::NDC ndc(threadid.str().c_str());
777 // Determine web site number. This can't really fail, I don't think.
779 GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
781 // Match site instance to host name, skip if no match.
782 map<string,string>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
783 if (map_i==g_Sites.end())
784 return WriteClientError(lpECB,"Shibboleth filter not configured for this web site.");
786 const string& site=map_i->second;
788 // We lock the configuration system for the duration.
789 IConfig* conf=g_Config->getINI();
792 // Map request to application and content settings.
794 IRequestMapper* mapper=conf->getRequestMapper();
795 Locker locker2(mapper);
796 IRequestMapper::Settings settings=map_request(lpECB,mapper,site.c_str(),targeturl);
797 pair<bool,const char*> application_id=settings.first->getString("applicationId");
798 application=conf->getApplication(application_id.second);
799 const IPropertySet* sessionProps=application ? application->getPropertySet("Sessions") : NULL;
800 if (!application || !sessionProps)
801 return WriteClientError(lpECB,"Unable to map request to application session settings, check configuration.");
803 SHIRE shire(application);
805 // Make sure we only process the SHIRE requests.
806 if (!strstr(targeturl.c_str(),shire.getShireURL(targeturl.c_str())))
807 return WriteClientError(lpECB,"The request's application and associated shireURL setting are inconsistent.");;
809 pair<bool,const char*> shib_cookie=sessionProps->getString("cookieName");
810 pair<bool,const char*> shib_cookie_props=sessionProps->getString("cookieProps");
811 if (!shib_cookie.first)
812 return WriteClientError(lpECB,"No session cookie name defined for this application, check configuration.");
814 ShibMLP markupProcessor(application);
815 markupProcessor.insert("requestURL", targeturl.c_str());
817 // Make sure this is SSL, if it should be
818 pair<bool,bool> shireSSL=sessionProps->getBool("shireSSL");
819 if (!shireSSL.first || shireSSL.second) {
820 GetServerVariable(lpECB,"HTTPS",buf,10);
822 throw ShibTargetException(SHIBRPC_OK,"blocked non-SSL access to SHIRE POST processor");
825 // If this is a GET, we manufacture an AuthnRequest.
826 if (!stricmp(lpECB->lpszMethod,"GET")) {
827 const char* areq=lpECB->lpszQueryString ? shire.getLazyAuthnRequest(lpECB->lpszQueryString) : NULL;
829 throw ShibTargetException(SHIBRPC_OK, "malformed arguments to request a new session");
830 targeturl = string("Location: ") + areq + "\r\n"
831 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
832 "Cache-Control: private,no-store,no-cache\r\n"
833 "Connection: close\r\n";
834 HSE_SEND_HEADER_EX_INFO hinfo;
835 hinfo.pszStatus="302 Moved";
836 hinfo.pszHeader=targeturl.c_str();
838 hinfo.cchHeader=targeturl.length();
839 hinfo.fKeepConn=FALSE;
840 if (lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER_EX,&hinfo,0,0))
841 return HSE_STATUS_SUCCESS;
842 return HSE_STATUS_ERROR;
844 else if (stricmp(lpECB->lpszMethod,"POST"))
845 throw ShibTargetException(SHIBRPC_OK,"blocked non-POST to SHIRE POST processor");
847 // Sure sure this POST is an appropriate content type
848 if (!lpECB->lpszContentType || stricmp(lpECB->lpszContentType,"application/x-www-form-urlencoded"))
849 throw ShibTargetException(SHIBRPC_OK,"blocked bad content-type to SHIRE POST processor");
852 pair<const char*,const char*> elements=pair<const char*,const char*>(NULL,NULL);
853 if (lpECB->cbTotalBytes > 1024*1024) // 1MB?
854 throw ShibTargetException(SHIBRPC_OK,"blocked too-large a post to SHIRE POST processor");
855 else if (lpECB->cbTotalBytes!=lpECB->cbAvailable) {
858 DWORD datalen=lpECB->cbTotalBytes;
861 BOOL ret=lpECB->ReadClient(lpECB->ConnID,buf,&buflen);
863 throw ShibTargetException(SHIBRPC_OK,"error reading POST data from browser");
864 cgistr.append(buf,buflen);
867 elements=shire.getFormSubmission(cgistr.c_str(),cgistr.length());
870 elements=shire.getFormSubmission(reinterpret_cast<char*>(lpECB->lpbData),lpECB->cbAvailable);
872 // Make sure the SAML Response parameter exists
873 if (!elements.first || !*elements.first)
874 throw ShibTargetException(SHIBRPC_OK, "SHIRE POST failed to find SAMLResponse form element");
876 // Make sure the target parameter exists
877 if (!elements.second || !*elements.second)
878 throw ShibTargetException(SHIBRPC_OK, "SHIRE POST failed to find TARGET form element");
880 GetServerVariable(lpECB,"REMOTE_ADDR",buf,16);
884 RPCError* status = shire.sessionCreate(elements.first,buf,cookie);
886 if (status->isError()) {
887 if (status->isRetryable()) {
889 const char* loc=shire.getAuthnRequest(elements.second);
890 DWORD len=strlen(loc);
891 if (lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_URL_REDIRECT_RESP,(LPVOID)loc,&len,0))
892 return HSE_STATUS_SUCCESS;
893 return HSE_STATUS_ERROR;
896 // Return this error to the user.
897 markupProcessor.insert(*status);
899 return WriteClientError(lpECB,application,"shire",markupProcessor);
903 // We've got a good session, set the cookie and redirect to target.
904 cookie = string("Set-Cookie: ") + shib_cookie.second + '=' + cookie +
905 (shib_cookie_props.first ? shib_cookie_props.second : "; path=/") + "\r\n"
906 "Location: " + elements.second + "\r\n"
907 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
908 "Cache-Control: private,no-store,no-cache\r\n"
909 "Connection: close\r\n";
910 HSE_SEND_HEADER_EX_INFO hinfo;
911 hinfo.pszStatus="302 Moved";
912 hinfo.pszHeader=cookie.c_str();
914 hinfo.cchHeader=cookie.length();
915 hinfo.fKeepConn=FALSE;
916 if (lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER_EX,&hinfo,0,0))
917 return HSE_STATUS_SUCCESS;
919 catch (ShibTargetException &e) {
921 ShibMLP markupProcessor(application);
922 markupProcessor.insert("errorType", "Session Creation Service Error");
923 markupProcessor.insert("errorText", e.what());
924 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
925 return WriteClientError(lpECB,application,"shire",markupProcessor);
931 ShibMLP markupProcessor(application);
932 markupProcessor.insert("errorType", "Session Creation Service Error");
933 markupProcessor.insert("errorText", "Unexpected Exception");
934 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
935 return WriteClientError(lpECB,application,"shire",markupProcessor);
940 return HSE_STATUS_ERROR;