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)) {
165 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
166 "Filter startup failed during initialization, check shire log for help.");
170 // Access the implementation-specifics for site mappings.
171 IConfig* conf=g_Config->getINI();
173 const IPropertySet* props=conf->getPropertySet("SHIRE");
175 const DOMElement* impl=saml::XML::getFirstChildElement(
176 props->getElement(),ShibTargetConfig::SHIBTARGET_NS,Implementation
178 if (impl && (impl=saml::XML::getFirstChildElement(impl,ShibTargetConfig::SHIBTARGET_NS,ISAPI))) {
179 const XMLCh* flag=impl->getAttributeNS(NULL,normalizeRequest);
180 g_bNormalizeRequest=(!flag || !*flag || *flag==chDigit_1 || *flag==chLatin_t);
181 impl=saml::XML::getFirstChildElement(impl,ShibTargetConfig::SHIBTARGET_NS,Site);
183 auto_ptr_char id(impl->getAttributeNS(NULL,id));
184 auto_ptr_char host(impl->getAttributeNS(NULL,host));
185 if (id.get() && host.get())
186 g_Sites[id.get()]=host.get();
187 impl=saml::XML::getNextSiblingElement(impl,ShibTargetConfig::SHIBTARGET_NS,Site);
194 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Filter startup failed with an exception.");
198 pVer->dwFilterVersion=HTTP_FILTER_REVISION;
199 strncpy(pVer->lpszFilterDesc,"Shibboleth ISAPI Filter",SF_MAX_FILTER_DESC_LEN);
200 pVer->dwFlags=(SF_NOTIFY_ORDER_HIGH |
201 SF_NOTIFY_SECURE_PORT |
202 SF_NOTIFY_NONSECURE_PORT |
203 SF_NOTIFY_PREPROC_HEADERS |
205 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter initialized...");
209 extern "C" BOOL WINAPI TerminateFilter(DWORD)
212 g_Config->shutdown();
214 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter shut down...");
218 /* Next up, some suck-free versions of various APIs.
220 You DON'T require people to guess the buffer size and THEN tell them the right size.
221 Returning an LPCSTR is apparently way beyond their ken. Not to mention the fact that
222 constant strings aren't typed as such, making it just that much harder. These versions
223 are now updated to use a special growable buffer object, modeled after the standard
224 string class. The standard string won't work because they left out the option to
225 pre-allocate a non-constant buffer.
231 dynabuf() { bufptr=NULL; buflen=0; }
232 dynabuf(size_t s) { bufptr=new char[buflen=s]; *bufptr=0; }
233 ~dynabuf() { delete[] bufptr; }
234 size_t length() const { return bufptr ? strlen(bufptr) : 0; }
235 size_t size() const { return buflen; }
236 bool empty() const { return length()==0; }
237 void reserve(size_t s, bool keep=false);
238 void erase() { if (bufptr) memset(bufptr,0,buflen); }
239 operator char*() { return bufptr; }
240 bool operator ==(const char* s) const;
241 bool operator !=(const char* s) const { return !(*this==s); }
247 void dynabuf::reserve(size_t s, bool keep)
254 p[buflen]=bufptr[buflen];
260 bool dynabuf::operator==(const char* s) const
262 if (buflen==NULL || s==NULL)
263 return (buflen==NULL && s==NULL);
265 return strcmp(bufptr,s)==0;
268 void GetServerVariable(PHTTP_FILTER_CONTEXT pfc, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
269 throw (bad_alloc, DWORD)
275 while (!pfc->GetServerVariable(pfc,lpszVariable,s,&size))
277 // Grumble. Check the error.
278 DWORD e=GetLastError();
279 if (e==ERROR_INSUFFICIENT_BUFFER)
284 if (bRequired && s.empty())
288 void GetServerVariable(LPEXTENSION_CONTROL_BLOCK lpECB, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
289 throw (bad_alloc, DWORD)
295 while (lpECB->GetServerVariable(lpECB->ConnID,lpszVariable,s,&size))
297 // Grumble. Check the error.
298 DWORD e=GetLastError();
299 if (e==ERROR_INSUFFICIENT_BUFFER)
304 if (bRequired && s.empty())
308 void GetHeader(PHTTP_FILTER_PREPROC_HEADERS pn, PHTTP_FILTER_CONTEXT pfc,
309 LPSTR lpszName, dynabuf& s, DWORD size=80, bool bRequired=true)
310 throw (bad_alloc, DWORD)
316 while (!pn->GetHeader(pfc,lpszName,s,&size))
318 // Grumble. Check the error.
319 DWORD e=GetLastError();
320 if (e==ERROR_INSUFFICIENT_BUFFER)
325 if (bRequired && s.empty())
329 IRequestMapper::Settings map_request(
330 PHTTP_FILTER_CONTEXT pfc, PHTTP_FILTER_PREPROC_HEADERS pn, IRequestMapper* mapper, const char* hostname, string& target
335 GetServerVariable(pfc,"SERVER_PORT",port,10);
336 GetHeader(pn,pfc,"url",url,256,false);
339 target=static_cast<char*>(url);
340 if (port!=(pfc->fIsSecurePort ? "443" : "80"))
341 target = ':' + static_cast<char*>(port) + target;
343 if (g_bNormalizeRequest) {
344 target = string(pfc->fIsSecurePort ? "https://" : "http://") + hostname + target;
348 GetServerVariable(pfc,"SERVER_NAME",name,64);
349 target = string(pfc->fIsSecurePort ? "https://" : "http://") + static_cast<char*>(name) + target;
351 return mapper->getSettingsFromParsedURL((pfc->fIsSecurePort ? "https" : "http"),hostname,strtoul(port,NULL,10),url);
354 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const char* msg)
356 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
357 static const char* ctype="Content-Type: text/html\r\n";
358 pfc->AddResponseHeaders(pfc,const_cast<char*>(ctype),0);
359 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",0,0);
360 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Filter Error</TITLE></HEAD><BODY>"
361 "<H1>Shibboleth Filter Error</H1>";
362 DWORD resplen=strlen(xmsg);
363 pfc->WriteClient(pfc,(LPVOID)xmsg,&resplen,0);
365 pfc->WriteClient(pfc,(LPVOID)msg,&resplen,0);
366 static const char* xmsg2="</BODY></HTML>";
367 resplen=strlen(xmsg2);
368 pfc->WriteClient(pfc,(LPVOID)xmsg2,&resplen,0);
369 return SF_STATUS_REQ_FINISHED;
372 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const IApplication* app, const char* page, ShibMLP& mlp)
374 const IPropertySet* props=app->getPropertySet("Errors");
376 pair<bool,const char*> p=props->getString(page);
378 ifstream infile(p.second);
379 if (!infile.fail()) {
380 const char* res = mlp.run(infile);
382 static const char* ctype="Content-Type: text/html\r\n";
383 pfc->AddResponseHeaders(pfc,const_cast<char*>(ctype),0);
384 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",0,0);
385 DWORD resplen=strlen(res);
386 pfc->WriteClient(pfc,(LPVOID)res,&resplen,0);
387 return SF_STATUS_REQ_FINISHED;
393 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Filter unable to open error template.");
394 return WriteClientError(pfc,"Unable to open error template, check settings.");
397 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
399 // Is this a log notification?
400 if (notificationType==SF_NOTIFY_LOG)
402 if (pfc->pFilterContext)
403 ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=static_cast<LPCSTR>(pfc->pFilterContext);
404 return SF_STATUS_REQ_NEXT_NOTIFICATION;
407 PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
410 // Determine web site number. This can't really fail, I don't think.
412 GetServerVariable(pfc,"INSTANCE_ID",buf,10);
414 // Match site instance to host name, skip if no match.
415 map<string,string>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
416 if (map_i==g_Sites.end())
417 return SF_STATUS_REQ_NEXT_NOTIFICATION;
419 const string& site=map_i->second;
421 ostringstream threadid;
422 threadid << "[" << getpid() << "] isapi_shib" << '\0';
423 saml::NDC ndc(threadid.str().c_str());
425 // We lock the configuration system for the duration.
426 IConfig* conf=g_Config->getINI();
429 // Map request to application and content settings.
431 IRequestMapper* mapper=conf->getRequestMapper();
432 Locker locker2(mapper);
433 IRequestMapper::Settings settings=map_request(pfc,pn,mapper,site.c_str(),targeturl);
434 pair<bool,const char*> application_id=settings.first->getString("applicationId");
435 const IApplication* application=conf->getApplication(application_id.second);
436 const IPropertySet* sessionProps=application ? application->getPropertySet("Sessions") : NULL;
437 if (!application || !sessionProps)
438 return WriteClientError(pfc,"Unable to map request to application session settings, check configuration.");
440 // Declare SHIRE object for this request.
441 SHIRE shire(application);
443 // If the user is accessing the SHIRE acceptance point, pass it on.
444 if (targeturl.find(shire.getShireURL(targeturl.c_str()))!=string::npos)
445 return SF_STATUS_REQ_NEXT_NOTIFICATION;
447 // Now check the policy for this request.
448 pair<bool,bool> requireSession=settings.first->getBool("requireSession");
449 pair<bool,const char*> shib_cookie=sessionProps->getString("cookieName");
450 if (!shib_cookie.first)
451 return WriteClientError(pfc,"No session cookie name defined for this application, check configuration.");
453 // Check for session cookie.
454 const char* session_id=NULL;
455 GetHeader(pn,pfc,"Cookie:",buf,128,false);
456 Category::getInstance("isapi_shib.HttpFilterProc").debug("cookie header is {%s}",(const char*)buf);
457 if (!buf.empty() && (session_id=strstr(buf,shib_cookie.second))) {
458 session_id+=strlen(shib_cookie.second) + 1; /* Skip over the '=' */
459 char* cookieend=strchr(session_id,';');
461 *cookieend = '\0'; /* Ignore anyting after a ; */
464 if (!session_id || !*session_id) {
465 // If no session required, bail now.
466 if (!requireSession.second)
467 return SF_STATUS_REQ_NEXT_NOTIFICATION;
469 // No acceptable cookie, and we require a session. Generate an AuthnRequest.
470 string loc("Location: ");
471 loc+=shire.getAuthnRequest(targeturl.c_str());
472 pfc->AddResponseHeaders(pfc,const_cast<char*>(loc.c_str()),0);
473 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"302 Please Wait",0,0);
474 return SF_STATUS_REQ_FINISHED;
477 // Make sure this session is still valid.
478 RPCError* status = NULL;
479 ShibMLP markupProcessor(application);
480 markupProcessor.insert("requestURL", targeturl);
483 GetServerVariable(pfc,"REMOTE_ADDR",abuf,16);
485 status = shire.sessionIsValid(session_id, abuf);
487 catch (ShibTargetException &e) {
488 markupProcessor.insert("errorType", "Session Processing Error");
489 markupProcessor.insert("errorText", e.what());
490 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
491 return WriteClientError(pfc, application, "shire", markupProcessor);
495 markupProcessor.insert("errorType", "Session Processing Error");
496 markupProcessor.insert("errorText", "Unexpected Exception");
497 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
498 return WriteClientError(pfc, application, "shire", markupProcessor);
503 if (status->isError()) {
504 if (!requireSession.second)
505 return SF_STATUS_REQ_NEXT_NOTIFICATION;
506 else if (status->isRetryable()) {
507 // Oops, session is invalid. Generate AuthnRequest.
509 string loc("Location: ");
510 loc+=shire.getAuthnRequest(targeturl.c_str());
511 pfc->AddResponseHeaders(pfc,const_cast<char*>(loc.c_str()),0);
512 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"302 Please Wait",0,0);
513 return SF_STATUS_REQ_FINISHED;
516 // return the error page to the user
517 markupProcessor.insert(*status);
519 return WriteClientError(pfc, application, "shire", markupProcessor);
526 vector<SAMLAssertion*> assertions;
527 SAMLAuthenticationStatement* sso_statement=NULL;
530 status = rm.getAssertions(session_id, abuf, assertions, &sso_statement);
532 catch (ShibTargetException &e) {
533 markupProcessor.insert("errorType", "Attribute Processing Error");
534 markupProcessor.insert("errorText", e.what());
535 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
536 return WriteClientError(pfc, application, "rm", markupProcessor);
540 markupProcessor.insert("errorType", "Attribute Processing Error");
541 markupProcessor.insert("errorText", "Unexpected Exception");
542 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
543 return WriteClientError(pfc, application, "rm", markupProcessor);
547 if (status->isError()) {
548 markupProcessor.insert(*status);
550 return WriteClientError(pfc, application, "rm", markupProcessor);
554 // Do we have an access control plugin?
555 if (settings.second) {
556 Locker acllock(settings.second);
557 if (!settings.second->authorized(assertions)) {
558 for (int k = 0; k < assertions.size(); k++)
559 delete assertions[k];
560 delete sso_statement;
561 return WriteClientError(pfc, application, "access", markupProcessor);
565 // Get the AAP providers, which contain the attribute policy info.
566 Iterator<IAAP*> provs=application->getAAPProviders();
568 // Clear out the list of mapped attributes
569 while (provs.hasNext()) {
570 IAAP* aap=provs.next();
573 Iterator<const IAttributeRule*> rules=aap->getAttributeRules();
574 while (rules.hasNext()) {
575 const char* header=rules.next()->getHeader();
577 pn->SetHeader(pfc,const_cast<char*>(header),"");
582 for (int k = 0; k < assertions.size(); k++)
583 delete assertions[k];
584 delete sso_statement;
585 markupProcessor.insert("errorType", "Attribute Processing Error");
586 markupProcessor.insert("errorText", "Unexpected Exception");
587 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
588 return WriteClientError(pfc, application, "rm", markupProcessor);
594 // Maybe export the first assertion.
595 pn->SetHeader(pfc,"remote-user:","");
596 pn->SetHeader(pfc,"Shib-Attributes:","");
597 pair<bool,bool> exp=settings.first->getBool("exportAssertion");
598 if (exp.first && exp.second && assertions.size()) {
600 RM::serialize(*(assertions[0]), assertion);
601 string::size_type lfeed;
602 while ((lfeed=assertion.find('\n'))!=string::npos)
603 assertion.erase(lfeed,1);
604 pn->SetHeader(pfc,"Shib-Attributes:",const_cast<char*>(assertion.c_str()));
607 pn->SetHeader(pfc,"Shib-Origin-Site:","");
608 pn->SetHeader(pfc,"Shib-Authentication-Method:","");
610 // Export the SAML AuthnMethod and the origin site name.
612 auto_ptr_char os(sso_statement->getSubject()->getNameQualifier());
613 auto_ptr_char am(sso_statement->getAuthMethod());
614 pn->SetHeader(pfc,"Shib-Origin-Site:", const_cast<char*>(os.get()));
615 pn->SetHeader(pfc,"Shib-Authentication-Method:", const_cast<char*>(am.get()));
618 pn->SetHeader(pfc,"Shib-Application-ID:","");
619 pn->SetHeader(pfc,"Shib-Application-ID:",const_cast<char*>(application_id.second));
621 // Export the attributes.
622 Iterator<SAMLAssertion*> a_iter(assertions);
623 while (a_iter.hasNext()) {
624 SAMLAssertion* assert=a_iter.next();
625 Iterator<SAMLStatement*> statements=assert->getStatements();
626 while (statements.hasNext()) {
627 SAMLAttributeStatement* astate=dynamic_cast<SAMLAttributeStatement*>(statements.next());
630 Iterator<SAMLAttribute*> attrs=astate->getAttributes();
631 while (attrs.hasNext()) {
632 SAMLAttribute* attr=attrs.next();
634 // Are we supposed to export it?
635 AAP wrapper(application->getAAPProviders(),attr->getName(),attr->getNamespace());
639 Iterator<string> vals=attr->getSingleByteValues();
640 if (!strcmp(wrapper->getHeader(),"REMOTE_USER") && vals.hasNext()) {
641 char* principal=const_cast<char*>(vals.next().c_str());
642 pn->SetHeader(pfc,"remote-user:",principal);
643 pfc->pFilterContext=pfc->AllocMem(pfc,strlen(principal)+1,0);
644 if (pfc->pFilterContext)
645 strcpy(static_cast<char*>(pfc->pFilterContext),principal);
650 string hname=string(wrapper->getHeader()) + ':';
651 GetHeader(pn,pfc,const_cast<char*>(hname.c_str()),buf,256,false);
656 for (; vals.hasNext(); it++) {
657 string value = vals.next();
658 for (string::size_type pos = value.find_first_of(";", string::size_type(0));
660 pos = value.find_first_of(";", pos)) {
661 value.insert(pos, "\\");
667 header=header + ';' + value;
669 pn->SetHeader(pfc,const_cast<char*>(hname.c_str()),const_cast<char*>(header.c_str()));
676 for (int k = 0; k < assertions.size(); k++)
677 delete assertions[k];
678 delete sso_statement;
680 return SF_STATUS_REQ_NEXT_NOTIFICATION;
683 return WriteClientError(pfc,"Out of Memory");
686 if (e==ERROR_NO_DATA)
687 return WriteClientError(pfc,"A required variable or header was empty.");
689 return WriteClientError(pfc,"Server detected unexpected IIS error.");
693 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((SSL ? "https" : "http"),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)
771 const IApplication* application=NULL;
774 ostringstream threadid;
775 threadid << "[" << getpid() << "] shire_handler" << '\0';
776 saml::NDC ndc(threadid.str().c_str());
778 // Determine web site number. This can't really fail, I don't think.
780 GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
782 // Match site instance to host name, skip if no match.
783 map<string,string>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
784 if (map_i==g_Sites.end())
785 return WriteClientError(lpECB,"Shibboleth filter not configured for this web site.");
787 const string& site=map_i->second;
789 // We lock the configuration system for the duration.
790 IConfig* conf=g_Config->getINI();
793 // Map request to application and content settings.
795 IRequestMapper* mapper=conf->getRequestMapper();
796 Locker locker2(mapper);
797 IRequestMapper::Settings settings=map_request(lpECB,mapper,site.c_str(),targeturl);
798 pair<bool,const char*> application_id=settings.first->getString("applicationId");
799 application=conf->getApplication(application_id.second);
800 const IPropertySet* sessionProps=application ? application->getPropertySet("Sessions") : NULL;
801 if (!application || !sessionProps)
802 return WriteClientError(lpECB,"Unable to map request to application session settings, check configuration.");
804 SHIRE shire(application);
806 // Make sure we only process the SHIRE requests.
807 if (!strstr(targeturl.c_str(),shire.getShireURL(targeturl.c_str())))
808 return WriteClientError(lpECB,"The request's application and associated shireURL setting are inconsistent.");;
810 pair<bool,const char*> shib_cookie=sessionProps->getString("cookieName");
811 pair<bool,const char*> shib_cookie_props=sessionProps->getString("cookieProps");
812 if (!shib_cookie.first)
813 return WriteClientError(lpECB,"No session cookie name defined for this application, check configuration.");
815 // Make sure this is SSL, if it should be
816 pair<bool,bool> shireSSL=sessionProps->getBool("shireSSL");
817 if (!shireSSL.first || shireSSL.second) {
818 GetServerVariable(lpECB,"HTTPS",buf,10);
820 throw ShibTargetException(SHIBRPC_OK,"blocked non-SSL access to SHIRE POST processor");
823 // If this is a GET, we manufacture an AuthnRequest.
824 if (!stricmp(lpECB->lpszMethod,"GET")) {
825 const char* areq=lpECB->lpszQueryString ? shire.getLazyAuthnRequest(lpECB->lpszQueryString) : NULL;
827 throw ShibTargetException(SHIBRPC_OK, "malformed arguments to request a new session");
828 targeturl = string("Location: ") + areq + "\r\n"
829 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
830 "Cache-Control: private,no-store,no-cache\r\n"
831 "Connection: close\r\n";
832 HSE_SEND_HEADER_EX_INFO hinfo;
833 hinfo.pszStatus="302 Moved";
834 hinfo.pszHeader=targeturl.c_str();
836 hinfo.cchHeader=targeturl.length();
837 hinfo.fKeepConn=FALSE;
838 if (lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER_EX,&hinfo,0,0))
839 return HSE_STATUS_SUCCESS;
840 return HSE_STATUS_ERROR;
842 else if (stricmp(lpECB->lpszMethod,"POST"))
843 throw ShibTargetException(SHIBRPC_OK,"blocked non-POST to SHIRE POST processor");
845 // Sure sure this POST is an appropriate content type
846 if (!lpECB->lpszContentType || stricmp(lpECB->lpszContentType,"application/x-www-form-urlencoded"))
847 throw ShibTargetException(SHIBRPC_OK,"blocked bad content-type to SHIRE POST processor");
850 pair<const char*,const char*> elements=pair<const char*,const char*>(NULL,NULL);
851 if (lpECB->cbTotalBytes > 1024*1024) // 1MB?
852 throw ShibTargetException(SHIBRPC_OK,"blocked too-large a post to SHIRE POST processor");
853 else if (lpECB->cbTotalBytes!=lpECB->cbAvailable) {
856 DWORD datalen=lpECB->cbTotalBytes;
859 BOOL ret=lpECB->ReadClient(lpECB->ConnID,buf,&buflen);
861 throw ShibTargetException(SHIBRPC_OK,"error reading POST data from browser");
862 cgistr.append(buf,buflen);
865 elements=shire.getFormSubmission(cgistr.c_str(),cgistr.length());
868 elements=shire.getFormSubmission(reinterpret_cast<char*>(lpECB->lpbData),lpECB->cbAvailable);
870 // Make sure the SAML Response parameter exists
871 if (!elements.first || !*elements.first)
872 throw ShibTargetException(SHIBRPC_OK, "SHIRE POST failed to find SAMLResponse form element");
874 // Make sure the target parameter exists
875 if (!elements.second || !*elements.second)
876 throw ShibTargetException(SHIBRPC_OK, "SHIRE POST failed to find TARGET form element");
878 GetServerVariable(lpECB,"REMOTE_ADDR",buf,16);
882 RPCError* status=NULL;
883 ShibMLP markupProcessor(application);
884 markupProcessor.insert("requestURL", targeturl.c_str());
886 status = shire.sessionCreate(elements.first,buf,cookie);
888 catch (ShibTargetException &e) {
889 markupProcessor.insert("errorType", "Session Creation Service Error");
890 markupProcessor.insert("errorText", e.what());
891 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
892 return WriteClientError(lpECB, application, "shire", markupProcessor);
896 markupProcessor.insert("errorType", "Session Creation Service Error");
897 markupProcessor.insert("errorText", "Unexpected Exception");
898 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
899 return WriteClientError(lpECB, application, "shire", markupProcessor);
903 if (status->isError()) {
904 if (status->isRetryable()) {
906 const char* loc=shire.getAuthnRequest(elements.second);
907 DWORD len=strlen(loc);
908 if (lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_URL_REDIRECT_RESP,(LPVOID)loc,&len,0))
909 return HSE_STATUS_SUCCESS;
910 return HSE_STATUS_ERROR;
913 // Return this error to the user.
914 markupProcessor.insert(*status);
916 return WriteClientError(lpECB,application,"shire",markupProcessor);
920 // We've got a good session, set the cookie and redirect to target.
921 cookie = string("Set-Cookie: ") + shib_cookie.second + '=' + cookie +
922 (shib_cookie_props.first ? shib_cookie_props.second : "; path=/") + "\r\n"
923 "Location: " + elements.second + "\r\n"
924 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
925 "Cache-Control: private,no-store,no-cache\r\n"
926 "Connection: close\r\n";
927 HSE_SEND_HEADER_EX_INFO hinfo;
928 hinfo.pszStatus="302 Moved";
929 hinfo.pszHeader=cookie.c_str();
931 hinfo.cchHeader=cookie.length();
932 hinfo.fKeepConn=FALSE;
933 if (lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER_EX,&hinfo,0,0))
934 return HSE_STATUS_SUCCESS;
936 catch (ShibTargetException &e) {
938 ShibMLP markupProcessor(application);
939 markupProcessor.insert("requestURL", targeturl.c_str());
940 markupProcessor.insert("errorType", "Session Creation Service Error");
941 markupProcessor.insert("errorText", e.what());
942 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
943 return WriteClientError(lpECB,application,"shire",markupProcessor);
949 ShibMLP markupProcessor(application);
950 markupProcessor.insert("requestURL", targeturl.c_str());
951 markupProcessor.insert("errorType", "Session Creation Service Error");
952 markupProcessor.insert("errorText", "Unexpected Exception");
953 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
954 return WriteClientError(lpECB,application,"shire",markupProcessor);
959 return HSE_STATUS_ERROR;