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 |
162 ShibTargetConfig::Logging
164 if (!g_Config->init(schemadir,config)) {
166 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
167 "Filter startup failed during initialization, check shire log for help.");
171 // Access the implementation-specifics for site mappings.
172 IConfig* conf=g_Config->getINI();
174 const IPropertySet* props=conf->getPropertySet("SHIRE");
176 const DOMElement* impl=saml::XML::getFirstChildElement(
177 props->getElement(),ShibTargetConfig::SHIBTARGET_NS,Implementation
179 if (impl && (impl=saml::XML::getFirstChildElement(impl,ShibTargetConfig::SHIBTARGET_NS,ISAPI))) {
180 const XMLCh* flag=impl->getAttributeNS(NULL,normalizeRequest);
181 g_bNormalizeRequest=(!flag || !*flag || *flag==chDigit_1 || *flag==chLatin_t);
182 impl=saml::XML::getFirstChildElement(impl,ShibTargetConfig::SHIBTARGET_NS,Site);
184 auto_ptr_char id(impl->getAttributeNS(NULL,id));
185 auto_ptr_char host(impl->getAttributeNS(NULL,host));
186 if (id.get() && host.get())
187 g_Sites[id.get()]=host.get();
188 impl=saml::XML::getNextSiblingElement(impl,ShibTargetConfig::SHIBTARGET_NS,Site);
195 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Filter startup failed with an exception.");
199 pVer->dwFilterVersion=HTTP_FILTER_REVISION;
200 strncpy(pVer->lpszFilterDesc,"Shibboleth ISAPI Filter",SF_MAX_FILTER_DESC_LEN);
201 pVer->dwFlags=(SF_NOTIFY_ORDER_HIGH |
202 SF_NOTIFY_SECURE_PORT |
203 SF_NOTIFY_NONSECURE_PORT |
204 SF_NOTIFY_PREPROC_HEADERS |
206 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter initialized...");
210 extern "C" BOOL WINAPI TerminateFilter(DWORD)
213 g_Config->shutdown();
215 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter shut down...");
219 /* Next up, some suck-free versions of various APIs.
221 You DON'T require people to guess the buffer size and THEN tell them the right size.
222 Returning an LPCSTR is apparently way beyond their ken. Not to mention the fact that
223 constant strings aren't typed as such, making it just that much harder. These versions
224 are now updated to use a special growable buffer object, modeled after the standard
225 string class. The standard string won't work because they left out the option to
226 pre-allocate a non-constant buffer.
232 dynabuf() { bufptr=NULL; buflen=0; }
233 dynabuf(size_t s) { bufptr=new char[buflen=s]; *bufptr=0; }
234 ~dynabuf() { delete[] bufptr; }
235 size_t length() const { return bufptr ? strlen(bufptr) : 0; }
236 size_t size() const { return buflen; }
237 bool empty() const { return length()==0; }
238 void reserve(size_t s, bool keep=false);
239 void erase() { if (bufptr) memset(bufptr,0,buflen); }
240 operator char*() { return bufptr; }
241 bool operator ==(const char* s) const;
242 bool operator !=(const char* s) const { return !(*this==s); }
248 void dynabuf::reserve(size_t s, bool keep)
255 p[buflen]=bufptr[buflen];
261 bool dynabuf::operator==(const char* s) const
263 if (buflen==NULL || s==NULL)
264 return (buflen==NULL && s==NULL);
266 return strcmp(bufptr,s)==0;
269 void GetServerVariable(PHTTP_FILTER_CONTEXT pfc, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
270 throw (bad_alloc, DWORD)
276 while (!pfc->GetServerVariable(pfc,lpszVariable,s,&size))
278 // Grumble. Check the error.
279 DWORD e=GetLastError();
280 if (e==ERROR_INSUFFICIENT_BUFFER)
285 if (bRequired && s.empty())
289 void GetServerVariable(LPEXTENSION_CONTROL_BLOCK lpECB, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
290 throw (bad_alloc, DWORD)
296 while (lpECB->GetServerVariable(lpECB->ConnID,lpszVariable,s,&size))
298 // Grumble. Check the error.
299 DWORD e=GetLastError();
300 if (e==ERROR_INSUFFICIENT_BUFFER)
305 if (bRequired && s.empty())
309 void GetHeader(PHTTP_FILTER_PREPROC_HEADERS pn, PHTTP_FILTER_CONTEXT pfc,
310 LPSTR lpszName, dynabuf& s, DWORD size=80, bool bRequired=true)
311 throw (bad_alloc, DWORD)
317 while (!pn->GetHeader(pfc,lpszName,s,&size))
319 // Grumble. Check the error.
320 DWORD e=GetLastError();
321 if (e==ERROR_INSUFFICIENT_BUFFER)
326 if (bRequired && s.empty())
330 IRequestMapper::Settings map_request(
331 PHTTP_FILTER_CONTEXT pfc, PHTTP_FILTER_PREPROC_HEADERS pn, IRequestMapper* mapper, const char* hostname, string& target
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;
349 GetServerVariable(pfc,"SERVER_NAME",name,64);
350 target = string(pfc->fIsSecurePort ? "https://" : "http://") + static_cast<char*>(name) + target;
352 return mapper->getSettingsFromParsedURL((pfc->fIsSecurePort ? "https" : "http"),hostname,strtoul(port,NULL,10),url);
355 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const char* msg)
357 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
358 static const char* ctype="Content-Type: text/html\r\n";
359 pfc->AddResponseHeaders(pfc,const_cast<char*>(ctype),0);
360 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",0,0);
361 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Filter Error</TITLE></HEAD><BODY>"
362 "<H1>Shibboleth Filter Error</H1>";
363 DWORD resplen=strlen(xmsg);
364 pfc->WriteClient(pfc,(LPVOID)xmsg,&resplen,0);
366 pfc->WriteClient(pfc,(LPVOID)msg,&resplen,0);
367 static const char* xmsg2="</BODY></HTML>";
368 resplen=strlen(xmsg2);
369 pfc->WriteClient(pfc,(LPVOID)xmsg2,&resplen,0);
370 return SF_STATUS_REQ_FINISHED;
373 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const IApplication* app, const char* page, ShibMLP& mlp)
375 const IPropertySet* props=app->getPropertySet("Errors");
377 pair<bool,const char*> p=props->getString(page);
379 ifstream infile(p.second);
380 if (!infile.fail()) {
381 const char* res = mlp.run(infile,props);
383 static const char* ctype="Content-Type: text/html\r\n";
384 pfc->AddResponseHeaders(pfc,const_cast<char*>(ctype),0);
385 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",0,0);
386 DWORD resplen=strlen(res);
387 pfc->WriteClient(pfc,(LPVOID)res,&resplen,0);
388 return SF_STATUS_REQ_FINISHED;
394 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Filter unable to open error template.");
395 return WriteClientError(pfc,"Unable to open error template, check settings.");
398 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
400 // Is this a log notification?
401 if (notificationType==SF_NOTIFY_LOG)
403 if (pfc->pFilterContext)
404 ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=static_cast<LPCSTR>(pfc->pFilterContext);
405 return SF_STATUS_REQ_NEXT_NOTIFICATION;
408 PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
411 // Determine web site number. This can't really fail, I don't think.
413 GetServerVariable(pfc,"INSTANCE_ID",buf,10);
415 // Match site instance to host name, skip if no match.
416 map<string,string>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
417 if (map_i==g_Sites.end())
418 return SF_STATUS_REQ_NEXT_NOTIFICATION;
420 const string& site=map_i->second;
422 ostringstream threadid;
423 threadid << "[" << getpid() << "] isapi_shib" << '\0';
424 saml::NDC ndc(threadid.str().c_str());
426 // We lock the configuration system for the duration.
427 IConfig* conf=g_Config->getINI();
430 // Map request to application and content settings.
432 IRequestMapper* mapper=conf->getRequestMapper();
433 Locker locker2(mapper);
434 IRequestMapper::Settings settings=map_request(pfc,pn,mapper,site.c_str(),targeturl);
435 pair<bool,const char*> application_id=settings.first->getString("applicationId");
436 const IApplication* application=conf->getApplication(application_id.second);
438 return WriteClientError(pfc,"Unable to map request to application 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<const char*,const char*> shib_cookie=shire.getCookieNameProps();
451 // Check for session cookie.
452 const char* session_id=NULL;
453 GetHeader(pn,pfc,"Cookie:",buf,128,false);
454 Category::getInstance("isapi_shib.HttpFilterProc").debug("cookie header is {%s}",(const char*)buf);
455 if (!buf.empty() && (session_id=strstr(buf,shib_cookie.first))) {
456 session_id+=strlen(shib_cookie.first) + 1; /* Skip over the '=' */
457 char* cookieend=strchr(session_id,';');
459 *cookieend = '\0'; /* Ignore anyting after a ; */
462 if (!session_id || !*session_id) {
463 // If no session required, bail now.
464 if (!requireSession.second)
465 return SF_STATUS_REQ_NEXT_NOTIFICATION;
467 // No acceptable cookie, and we require a session. Generate an AuthnRequest.
468 string loc("Location: ");
469 loc+=shire.getAuthnRequest(targeturl.c_str());
470 pfc->AddResponseHeaders(pfc,const_cast<char*>(loc.c_str()),0);
471 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"302 Please Wait",0,0);
472 return SF_STATUS_REQ_FINISHED;
475 // Make sure this session is still valid.
476 RPCError* status = NULL;
477 ShibMLP markupProcessor;
478 markupProcessor.insert("requestURL", targeturl);
481 GetServerVariable(pfc,"REMOTE_ADDR",abuf,16);
483 status = shire.sessionIsValid(session_id, abuf);
485 catch (ShibTargetException &e) {
486 markupProcessor.insert("errorType", "Session Processing Error");
487 markupProcessor.insert("errorText", e.what());
488 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
489 return WriteClientError(pfc, application, "shire", markupProcessor);
493 markupProcessor.insert("errorType", "Session Processing Error");
494 markupProcessor.insert("errorText", "Unexpected Exception");
495 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
496 return WriteClientError(pfc, application, "shire", markupProcessor);
501 if (status->isError()) {
502 if (!requireSession.second)
503 return SF_STATUS_REQ_NEXT_NOTIFICATION;
504 else if (status->isRetryable()) {
505 // Oops, session is invalid. Generate AuthnRequest.
507 string loc("Location: ");
508 loc+=shire.getAuthnRequest(targeturl.c_str());
509 pfc->AddResponseHeaders(pfc,const_cast<char*>(loc.c_str()),0);
510 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"302 Please Wait",0,0);
511 return SF_STATUS_REQ_FINISHED;
514 // return the error page to the user
515 markupProcessor.insert(*status);
517 return WriteClientError(pfc, application, "shire", markupProcessor);
524 vector<SAMLAssertion*> assertions;
525 SAMLAuthenticationStatement* sso_statement=NULL;
528 status = rm.getAssertions(session_id, abuf, assertions, &sso_statement);
530 catch (ShibTargetException &e) {
531 markupProcessor.insert("errorType", "Attribute Processing Error");
532 markupProcessor.insert("errorText", e.what());
533 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
534 return WriteClientError(pfc, application, "rm", markupProcessor);
538 markupProcessor.insert("errorType", "Attribute Processing Error");
539 markupProcessor.insert("errorText", "Unexpected Exception");
540 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
541 return WriteClientError(pfc, application, "rm", markupProcessor);
545 if (status->isError()) {
546 markupProcessor.insert(*status);
548 return WriteClientError(pfc, application, "rm", markupProcessor);
552 // Do we have an access control plugin?
553 if (settings.second) {
554 Locker acllock(settings.second);
555 if (!settings.second->authorized(assertions)) {
556 for (int k = 0; k < assertions.size(); k++)
557 delete assertions[k];
558 delete sso_statement;
559 return WriteClientError(pfc, application, "access", markupProcessor);
563 // Get the AAP providers, which contain the attribute policy info.
564 Iterator<IAAP*> provs=application->getAAPProviders();
566 // Clear out the list of mapped attributes
567 while (provs.hasNext()) {
568 IAAP* aap=provs.next();
571 Iterator<const IAttributeRule*> rules=aap->getAttributeRules();
572 while (rules.hasNext()) {
573 const char* header=rules.next()->getHeader();
575 string hname=string(header) + ':';
576 pn->SetHeader(pfc,const_cast<char*>(hname.c_str()),"");
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:","");
609 pn->SetHeader(pfc,"Shib-NameIdentifier-Format:","");
611 // Export the SAML AuthnMethod and the origin site name.
613 auto_ptr_char os(sso_statement->getSubject()->getNameIdentifier()->getNameQualifier());
614 auto_ptr_char am(sso_statement->getAuthMethod());
615 pn->SetHeader(pfc,"Shib-Origin-Site:", const_cast<char*>(os.get()));
616 pn->SetHeader(pfc,"Shib-Authentication-Method:", const_cast<char*>(am.get()));
619 AAP wrapper(provs,sso_statement->getSubject()->getNameIdentifier()->getFormat(),Constants::SHIB_ATTRIBUTE_NAMESPACE_URI);
620 if (!wrapper.fail() && wrapper->getHeader()) {
621 auto_ptr_char form(sso_statement->getSubject()->getNameIdentifier()->getFormat());
622 auto_ptr_char nameid(sso_statement->getSubject()->getNameIdentifier()->getName());
623 pn->SetHeader(pfc,"Shib-NameIdentifier-Format:",const_cast<char*>(form.get()));
624 if (!strcmp(wrapper->getHeader(),"REMOTE_USER")) {
625 char* principal=const_cast<char*>(nameid.get());
626 pn->SetHeader(pfc,"remote-user:",principal);
627 pfc->pFilterContext=pfc->AllocMem(pfc,strlen(principal)+1,0);
628 if (pfc->pFilterContext)
629 strcpy(static_cast<char*>(pfc->pFilterContext),principal);
632 string hname=string(wrapper->getHeader()) + ':';
633 pn->SetHeader(pfc,const_cast<char*>(wrapper->getHeader()),const_cast<char*>(nameid.get()));
638 pn->SetHeader(pfc,"Shib-Application-ID:","");
639 pn->SetHeader(pfc,"Shib-Application-ID:",const_cast<char*>(application_id.second));
641 // Export the attributes.
642 Iterator<SAMLAssertion*> a_iter(assertions);
643 while (a_iter.hasNext()) {
644 SAMLAssertion* assert=a_iter.next();
645 Iterator<SAMLStatement*> statements=assert->getStatements();
646 while (statements.hasNext()) {
647 SAMLAttributeStatement* astate=dynamic_cast<SAMLAttributeStatement*>(statements.next());
650 Iterator<SAMLAttribute*> attrs=astate->getAttributes();
651 while (attrs.hasNext()) {
652 SAMLAttribute* attr=attrs.next();
654 // Are we supposed to export it?
655 AAP wrapper(provs,attr->getName(),attr->getNamespace());
656 if (wrapper.fail() || !wrapper->getHeader())
659 Iterator<string> vals=attr->getSingleByteValues();
660 if (!strcmp(wrapper->getHeader(),"REMOTE_USER") && vals.hasNext()) {
661 char* principal=const_cast<char*>(vals.next().c_str());
662 pn->SetHeader(pfc,"remote-user:",principal);
663 pfc->pFilterContext=pfc->AllocMem(pfc,strlen(principal)+1,0);
664 if (pfc->pFilterContext)
665 strcpy(static_cast<char*>(pfc->pFilterContext),principal);
670 string hname=string(wrapper->getHeader()) + ':';
671 GetHeader(pn,pfc,const_cast<char*>(hname.c_str()),buf,256,false);
676 for (; vals.hasNext(); it++) {
677 string value = vals.next();
678 for (string::size_type pos = value.find_first_of(";", string::size_type(0));
680 pos = value.find_first_of(";", pos)) {
681 value.insert(pos, "\\");
687 header=header + ';' + value;
689 pn->SetHeader(pfc,const_cast<char*>(hname.c_str()),const_cast<char*>(header.c_str()));
696 for (int k = 0; k < assertions.size(); k++)
697 delete assertions[k];
698 delete sso_statement;
700 return SF_STATUS_REQ_NEXT_NOTIFICATION;
703 return WriteClientError(pfc,"Out of Memory");
706 if (e==ERROR_NO_DATA)
707 return WriteClientError(pfc,"A required variable or header was empty.");
709 return WriteClientError(pfc,"Server detected unexpected IIS error.");
713 return WriteClientError(pfc,"Server caught an unknown exception.");
717 return WriteClientError(pfc,"Server reached unreachable code, save my walrus!");
720 IRequestMapper::Settings map_request(
721 LPEXTENSION_CONTROL_BLOCK lpECB, IRequestMapper* mapper, const char* hostname, string& target
727 GetServerVariable(lpECB,"HTTPS",ssl,5);
728 GetServerVariable(lpECB,"SERVER_PORT",port,10);
729 GetServerVariable(lpECB,"URL",url,255);
730 bool SSL=(ssl=="on");
733 target=static_cast<char*>(url);
734 if (port!=(SSL ? "443" : "80"))
735 target = ':' + static_cast<char*>(port) + target;
737 if (g_bNormalizeRequest) {
738 target = string(SSL ? "https://" : "http://") + hostname + target;
739 return mapper->getSettingsFromParsedURL(lpECB->lpszMethod,hostname,strtoul(port,NULL,10),url);
743 GetServerVariable(lpECB,"SERVER_NAME",name,64);
744 target = string(SSL ? "https://" : "http://") + static_cast<char*>(name) + target;
745 return mapper->getSettingsFromParsedURL((SSL ? "https" : "http"),name,strtoul(port,NULL,10),url);
749 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* msg)
751 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
752 static const char* ctype="Content-Type: text/html\r\n";
753 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
754 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Error</TITLE></HEAD><BODY><H1>Shibboleth Error</H1>";
755 DWORD resplen=strlen(xmsg);
756 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg,&resplen,HSE_IO_SYNC);
758 lpECB->WriteClient(lpECB->ConnID,(LPVOID)msg,&resplen,HSE_IO_SYNC);
759 static const char* xmsg2="</BODY></HTML>";
760 resplen=strlen(xmsg2);
761 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg2,&resplen,HSE_IO_SYNC);
762 return HSE_STATUS_SUCCESS;
765 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const IApplication* app, const char* page, ShibMLP& mlp)
767 const IPropertySet* props=app->getPropertySet("Errors");
769 pair<bool,const char*> p=props->getString(page);
771 ifstream infile(p.second);
772 if (!infile.fail()) {
773 const char* res = mlp.run(infile,props);
775 static const char* ctype="Content-Type: text/html\r\n";
776 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
777 DWORD resplen=strlen(res);
778 lpECB->WriteClient(lpECB->ConnID,(LPVOID)res,&resplen,0);
779 return HSE_STATUS_SUCCESS;
784 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Extension unable to open error template.");
785 return WriteClientError(lpECB,"Unable to open error template, check settings.");
788 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
791 const IApplication* application=NULL;
794 ostringstream threadid;
795 threadid << "[" << getpid() << "] shire_handler" << '\0';
796 saml::NDC ndc(threadid.str().c_str());
798 // Determine web site number. This can't really fail, I don't think.
800 GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
802 // Match site instance to host name, skip if no match.
803 map<string,string>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
804 if (map_i==g_Sites.end())
805 return WriteClientError(lpECB,"Shibboleth filter not configured for this web site.");
807 const string& site=map_i->second;
809 // We lock the configuration system for the duration.
810 IConfig* conf=g_Config->getINI();
813 // Map request to application and content settings.
815 IRequestMapper* mapper=conf->getRequestMapper();
816 Locker locker2(mapper);
817 IRequestMapper::Settings settings=map_request(lpECB,mapper,site.c_str(),targeturl);
818 pair<bool,const char*> application_id=settings.first->getString("applicationId");
819 application=conf->getApplication(application_id.second);
820 const IPropertySet* sessionProps=application ? application->getPropertySet("Sessions") : NULL;
821 if (!application || !sessionProps)
822 return WriteClientError(lpECB,"Unable to map request to application session settings, check configuration.");
824 SHIRE shire(application);
826 // Make sure we only process the SHIRE requests.
827 if (!strstr(targeturl.c_str(),shire.getShireURL(targeturl.c_str())))
828 return WriteClientError(lpECB,"The request's application and associated shireURL setting are inconsistent.");;
830 pair<const char*,const char*> shib_cookie=shire.getCookieNameProps();
832 // Make sure this is SSL, if it should be
833 pair<bool,bool> shireSSL=sessionProps->getBool("shireSSL");
834 if (!shireSSL.first || shireSSL.second) {
835 GetServerVariable(lpECB,"HTTPS",buf,10);
837 throw ShibTargetException(SHIBRPC_OK,"blocked non-SSL access to SHIRE POST processor");
840 // If this is a GET, we manufacture an AuthnRequest.
841 if (!stricmp(lpECB->lpszMethod,"GET")) {
842 const char* areq=lpECB->lpszQueryString ? shire.getLazyAuthnRequest(lpECB->lpszQueryString) : NULL;
844 throw ShibTargetException(SHIBRPC_OK, "malformed arguments to request a new session");
845 targeturl = string("Location: ") + areq + "\r\n"
846 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
847 "Cache-Control: private,no-store,no-cache\r\n"
848 "Connection: close\r\n";
849 HSE_SEND_HEADER_EX_INFO hinfo;
850 hinfo.pszStatus="302 Moved";
851 hinfo.pszHeader=targeturl.c_str();
853 hinfo.cchHeader=targeturl.length();
854 hinfo.fKeepConn=FALSE;
855 if (lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER_EX,&hinfo,0,0))
856 return HSE_STATUS_SUCCESS;
857 return HSE_STATUS_ERROR;
859 else if (stricmp(lpECB->lpszMethod,"POST"))
860 throw ShibTargetException(SHIBRPC_OK,"blocked non-POST to SHIRE POST processor");
862 // Sure sure this POST is an appropriate content type
863 if (!lpECB->lpszContentType || stricmp(lpECB->lpszContentType,"application/x-www-form-urlencoded"))
864 throw ShibTargetException(SHIBRPC_OK,"blocked bad content-type to SHIRE POST processor");
867 pair<const char*,const char*> elements=pair<const char*,const char*>(NULL,NULL);
868 if (lpECB->cbTotalBytes > 1024*1024) // 1MB?
869 throw ShibTargetException(SHIBRPC_OK,"blocked too-large a post to SHIRE POST processor");
870 else if (lpECB->cbTotalBytes!=lpECB->cbAvailable) {
873 DWORD datalen=lpECB->cbTotalBytes;
876 BOOL ret=lpECB->ReadClient(lpECB->ConnID,buf,&buflen);
878 throw ShibTargetException(SHIBRPC_OK,"error reading POST data from browser");
879 cgistr.append(buf,buflen);
882 elements=shire.getFormSubmission(cgistr.c_str(),cgistr.length());
885 elements=shire.getFormSubmission(reinterpret_cast<char*>(lpECB->lpbData),lpECB->cbAvailable);
887 // Make sure the SAML Response parameter exists
888 if (!elements.first || !*elements.first)
889 throw ShibTargetException(SHIBRPC_OK, "SHIRE POST failed to find SAMLResponse form element");
891 // Make sure the target parameter exists
892 if (!elements.second || !*elements.second)
893 throw ShibTargetException(SHIBRPC_OK, "SHIRE POST failed to find TARGET form element");
895 GetServerVariable(lpECB,"REMOTE_ADDR",buf,16);
899 RPCError* status=NULL;
900 ShibMLP markupProcessor;
901 markupProcessor.insert("requestURL", targeturl.c_str());
903 status = shire.sessionCreate(elements.first,buf,cookie);
905 catch (ShibTargetException &e) {
906 markupProcessor.insert("errorType", "Session Creation Service Error");
907 markupProcessor.insert("errorText", e.what());
908 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
909 return WriteClientError(lpECB, application, "shire", markupProcessor);
913 markupProcessor.insert("errorType", "Session Creation Service Error");
914 markupProcessor.insert("errorText", "Unexpected Exception");
915 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
916 return WriteClientError(lpECB, application, "shire", markupProcessor);
920 if (status->isError()) {
921 if (status->isRetryable()) {
923 const char* loc=shire.getAuthnRequest(elements.second);
924 DWORD len=strlen(loc);
925 if (lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_URL_REDIRECT_RESP,(LPVOID)loc,&len,0))
926 return HSE_STATUS_SUCCESS;
927 return HSE_STATUS_ERROR;
930 // Return this error to the user.
931 markupProcessor.insert(*status);
933 return WriteClientError(lpECB,application,"shire",markupProcessor);
937 // We've got a good session, set the cookie and redirect to target.
938 cookie = string("Set-Cookie: ") + shib_cookie.first + '=' + cookie + shib_cookie.second + "\r\n"
939 "Location: " + elements.second + "\r\n"
940 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
941 "Cache-Control: private,no-store,no-cache\r\n"
942 "Connection: close\r\n";
943 HSE_SEND_HEADER_EX_INFO hinfo;
944 hinfo.pszStatus="302 Moved";
945 hinfo.pszHeader=cookie.c_str();
947 hinfo.cchHeader=cookie.length();
948 hinfo.fKeepConn=FALSE;
949 if (lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER_EX,&hinfo,0,0))
950 return HSE_STATUS_SUCCESS;
952 catch (ShibTargetException &e) {
954 ShibMLP markupProcessor;
955 markupProcessor.insert("requestURL", targeturl.c_str());
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);
965 ShibMLP markupProcessor;
966 markupProcessor.insert("requestURL", targeturl.c_str());
967 markupProcessor.insert("errorType", "Session Creation Service Error");
968 markupProcessor.insert("errorText", "Unexpected Exception");
969 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
970 return WriteClientError(lpECB,application,"shire",markupProcessor);
975 return HSE_STATUS_ERROR;