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(*sso_statement,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.
612 auto_ptr_char os(sso_statement->getSubject()->getNameIdentifier()->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 AAP wrapper(provs,sso_statement->getSubject()->getNameIdentifier()->getFormat(),Constants::SHIB_ATTRIBUTE_NAMESPACE_URI);
619 if (!wrapper.fail() && wrapper->getHeader()) {
620 auto_ptr_char form(sso_statement->getSubject()->getNameIdentifier()->getFormat());
621 auto_ptr_char nameid(sso_statement->getSubject()->getNameIdentifier()->getName());
622 pn->SetHeader(pfc,"Shib-NameIdentifier-Format:",const_cast<char*>(form.get()));
623 if (!strcmp(wrapper->getHeader(),"REMOTE_USER")) {
624 char* principal=const_cast<char*>(nameid.get());
625 pn->SetHeader(pfc,"remote-user:",principal);
626 pfc->pFilterContext=pfc->AllocMem(pfc,strlen(principal)+1,0);
627 if (pfc->pFilterContext)
628 strcpy(static_cast<char*>(pfc->pFilterContext),principal);
631 string hname=string(wrapper->getHeader()) + ':';
632 pn->SetHeader(pfc,const_cast<char*>(wrapper->getHeader()),const_cast<char*>(nameid.get()));
636 pn->SetHeader(pfc,"Shib-Application-ID:","");
637 pn->SetHeader(pfc,"Shib-Application-ID:",const_cast<char*>(application_id.second));
639 // Export the attributes.
640 Iterator<SAMLAssertion*> a_iter(assertions);
641 while (a_iter.hasNext()) {
642 SAMLAssertion* assert=a_iter.next();
643 Iterator<SAMLStatement*> statements=assert->getStatements();
644 while (statements.hasNext()) {
645 SAMLAttributeStatement* astate=dynamic_cast<SAMLAttributeStatement*>(statements.next());
648 Iterator<SAMLAttribute*> attrs=astate->getAttributes();
649 while (attrs.hasNext()) {
650 SAMLAttribute* attr=attrs.next();
652 // Are we supposed to export it?
653 AAP wrapper(provs,attr->getName(),attr->getNamespace());
654 if (wrapper.fail() || !wrapper->getHeader())
657 Iterator<string> vals=attr->getSingleByteValues();
658 if (!strcmp(wrapper->getHeader(),"REMOTE_USER") && vals.hasNext()) {
659 char* principal=const_cast<char*>(vals.next().c_str());
660 pn->SetHeader(pfc,"remote-user:",principal);
661 pfc->pFilterContext=pfc->AllocMem(pfc,strlen(principal)+1,0);
662 if (pfc->pFilterContext)
663 strcpy(static_cast<char*>(pfc->pFilterContext),principal);
668 string hname=string(wrapper->getHeader()) + ':';
669 GetHeader(pn,pfc,const_cast<char*>(hname.c_str()),buf,256,false);
674 for (; vals.hasNext(); it++) {
675 string value = vals.next();
676 for (string::size_type pos = value.find_first_of(";", string::size_type(0));
678 pos = value.find_first_of(";", pos)) {
679 value.insert(pos, "\\");
685 header=header + ';' + value;
687 pn->SetHeader(pfc,const_cast<char*>(hname.c_str()),const_cast<char*>(header.c_str()));
694 for (int k = 0; k < assertions.size(); k++)
695 delete assertions[k];
696 delete sso_statement;
698 return SF_STATUS_REQ_NEXT_NOTIFICATION;
701 return WriteClientError(pfc,"Out of Memory");
704 if (e==ERROR_NO_DATA)
705 return WriteClientError(pfc,"A required variable or header was empty.");
707 return WriteClientError(pfc,"Server detected unexpected IIS error.");
711 return WriteClientError(pfc,"Server caught an unknown exception.");
715 return WriteClientError(pfc,"Server reached unreachable code, save my walrus!");
718 IRequestMapper::Settings map_request(
719 LPEXTENSION_CONTROL_BLOCK lpECB, IRequestMapper* mapper, const char* hostname, string& target
725 GetServerVariable(lpECB,"HTTPS",ssl,5);
726 GetServerVariable(lpECB,"SERVER_PORT",port,10);
727 GetServerVariable(lpECB,"URL",url,255);
728 bool SSL=(ssl=="on");
731 target=static_cast<char*>(url);
732 if (port!=(SSL ? "443" : "80"))
733 target = ':' + static_cast<char*>(port) + target;
735 if (g_bNormalizeRequest) {
736 target = string(SSL ? "https://" : "http://") + hostname + target;
737 return mapper->getSettingsFromParsedURL(lpECB->lpszMethod,hostname,strtoul(port,NULL,10),url);
741 GetServerVariable(lpECB,"SERVER_NAME",name,64);
742 target = string(SSL ? "https://" : "http://") + static_cast<char*>(name) + target;
743 return mapper->getSettingsFromParsedURL((SSL ? "https" : "http"),name,strtoul(port,NULL,10),url);
747 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* msg)
749 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
750 static const char* ctype="Content-Type: text/html\r\n";
751 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
752 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Error</TITLE></HEAD><BODY><H1>Shibboleth Error</H1>";
753 DWORD resplen=strlen(xmsg);
754 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg,&resplen,HSE_IO_SYNC);
756 lpECB->WriteClient(lpECB->ConnID,(LPVOID)msg,&resplen,HSE_IO_SYNC);
757 static const char* xmsg2="</BODY></HTML>";
758 resplen=strlen(xmsg2);
759 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg2,&resplen,HSE_IO_SYNC);
760 return HSE_STATUS_SUCCESS;
763 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const IApplication* app, const char* page, ShibMLP& mlp)
765 const IPropertySet* props=app->getPropertySet("Errors");
767 pair<bool,const char*> p=props->getString(page);
769 ifstream infile(p.second);
770 if (!infile.fail()) {
771 const char* res = mlp.run(infile,props);
773 static const char* ctype="Content-Type: text/html\r\n";
774 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
775 DWORD resplen=strlen(res);
776 lpECB->WriteClient(lpECB->ConnID,(LPVOID)res,&resplen,0);
777 return HSE_STATUS_SUCCESS;
782 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Extension unable to open error template.");
783 return WriteClientError(lpECB,"Unable to open error template, check settings.");
786 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
789 const IApplication* application=NULL;
792 ostringstream threadid;
793 threadid << "[" << getpid() << "] shire_handler" << '\0';
794 saml::NDC ndc(threadid.str().c_str());
796 // Determine web site number. This can't really fail, I don't think.
798 GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
800 // Match site instance to host name, skip if no match.
801 map<string,string>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
802 if (map_i==g_Sites.end())
803 return WriteClientError(lpECB,"Shibboleth filter not configured for this web site.");
805 const string& site=map_i->second;
807 // We lock the configuration system for the duration.
808 IConfig* conf=g_Config->getINI();
811 // Map request to application and content settings.
813 IRequestMapper* mapper=conf->getRequestMapper();
814 Locker locker2(mapper);
815 IRequestMapper::Settings settings=map_request(lpECB,mapper,site.c_str(),targeturl);
816 pair<bool,const char*> application_id=settings.first->getString("applicationId");
817 application=conf->getApplication(application_id.second);
818 const IPropertySet* sessionProps=application ? application->getPropertySet("Sessions") : NULL;
819 if (!application || !sessionProps)
820 return WriteClientError(lpECB,"Unable to map request to application session settings, check configuration.");
822 SHIRE shire(application);
824 // Make sure we only process the SHIRE requests.
825 if (!strstr(targeturl.c_str(),shire.getShireURL(targeturl.c_str())))
826 return WriteClientError(lpECB,"The request's application and associated shireURL setting are inconsistent.");;
828 pair<const char*,const char*> shib_cookie=shire.getCookieNameProps();
830 // Make sure this is SSL, if it should be
831 pair<bool,bool> shireSSL=sessionProps->getBool("shireSSL");
832 if (!shireSSL.first || shireSSL.second) {
833 GetServerVariable(lpECB,"HTTPS",buf,10);
835 throw ShibTargetException(SHIBRPC_OK,"blocked non-SSL access to SHIRE POST processor");
838 // If this is a GET, we manufacture an AuthnRequest.
839 if (!stricmp(lpECB->lpszMethod,"GET")) {
840 const char* areq=lpECB->lpszQueryString ? shire.getLazyAuthnRequest(lpECB->lpszQueryString) : NULL;
842 throw ShibTargetException(SHIBRPC_OK, "malformed arguments to request a new session");
843 targeturl = string("Location: ") + areq + "\r\n"
844 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
845 "Cache-Control: private,no-store,no-cache\r\n"
846 "Connection: close\r\n";
847 HSE_SEND_HEADER_EX_INFO hinfo;
848 hinfo.pszStatus="302 Moved";
849 hinfo.pszHeader=targeturl.c_str();
851 hinfo.cchHeader=targeturl.length();
852 hinfo.fKeepConn=FALSE;
853 if (lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER_EX,&hinfo,0,0))
854 return HSE_STATUS_SUCCESS;
855 return HSE_STATUS_ERROR;
857 else if (stricmp(lpECB->lpszMethod,"POST"))
858 throw ShibTargetException(SHIBRPC_OK,"blocked non-POST to SHIRE POST processor");
860 // Sure sure this POST is an appropriate content type
861 if (!lpECB->lpszContentType || stricmp(lpECB->lpszContentType,"application/x-www-form-urlencoded"))
862 throw ShibTargetException(SHIBRPC_OK,"blocked bad content-type to SHIRE POST processor");
865 pair<const char*,const char*> elements=pair<const char*,const char*>(NULL,NULL);
866 if (lpECB->cbTotalBytes > 1024*1024) // 1MB?
867 throw ShibTargetException(SHIBRPC_OK,"blocked too-large a post to SHIRE POST processor");
868 else if (lpECB->cbTotalBytes!=lpECB->cbAvailable) {
871 DWORD datalen=lpECB->cbTotalBytes;
874 BOOL ret=lpECB->ReadClient(lpECB->ConnID,buf,&buflen);
876 throw ShibTargetException(SHIBRPC_OK,"error reading POST data from browser");
877 cgistr.append(buf,buflen);
880 elements=shire.getFormSubmission(cgistr.c_str(),cgistr.length());
883 elements=shire.getFormSubmission(reinterpret_cast<char*>(lpECB->lpbData),lpECB->cbAvailable);
885 // Make sure the SAML Response parameter exists
886 if (!elements.first || !*elements.first)
887 throw ShibTargetException(SHIBRPC_OK, "SHIRE POST failed to find SAMLResponse form element");
889 // Make sure the target parameter exists
890 if (!elements.second || !*elements.second)
891 throw ShibTargetException(SHIBRPC_OK, "SHIRE POST failed to find TARGET form element");
893 GetServerVariable(lpECB,"REMOTE_ADDR",buf,16);
897 RPCError* status=NULL;
898 ShibMLP markupProcessor;
899 markupProcessor.insert("requestURL", targeturl.c_str());
901 status = shire.sessionCreate(elements.first,buf,cookie);
903 catch (ShibTargetException &e) {
904 markupProcessor.insert("errorType", "Session Creation Service Error");
905 markupProcessor.insert("errorText", e.what());
906 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
907 return WriteClientError(lpECB, application, "shire", markupProcessor);
911 markupProcessor.insert("errorType", "Session Creation Service Error");
912 markupProcessor.insert("errorText", "Unexpected Exception");
913 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
914 return WriteClientError(lpECB, application, "shire", markupProcessor);
918 if (status->isError()) {
919 if (status->isRetryable()) {
921 const char* loc=shire.getAuthnRequest(elements.second);
922 DWORD len=strlen(loc);
923 if (lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_URL_REDIRECT_RESP,(LPVOID)loc,&len,0))
924 return HSE_STATUS_SUCCESS;
925 return HSE_STATUS_ERROR;
928 // Return this error to the user.
929 markupProcessor.insert(*status);
931 return WriteClientError(lpECB,application,"shire",markupProcessor);
935 // We've got a good session, set the cookie and redirect to target.
936 cookie = string("Set-Cookie: ") + shib_cookie.first + '=' + cookie + shib_cookie.second + "\r\n"
937 "Location: " + elements.second + "\r\n"
938 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
939 "Cache-Control: private,no-store,no-cache\r\n"
940 "Connection: close\r\n";
941 HSE_SEND_HEADER_EX_INFO hinfo;
942 hinfo.pszStatus="302 Moved";
943 hinfo.pszHeader=cookie.c_str();
945 hinfo.cchHeader=cookie.length();
946 hinfo.fKeepConn=FALSE;
947 if (lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER_EX,&hinfo,0,0))
948 return HSE_STATUS_SUCCESS;
950 catch (ShibTargetException &e) {
952 ShibMLP markupProcessor;
953 markupProcessor.insert("requestURL", targeturl.c_str());
954 markupProcessor.insert("errorType", "Session Creation Service Error");
955 markupProcessor.insert("errorText", e.what());
956 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
957 return WriteClientError(lpECB,application,"shire",markupProcessor);
963 ShibMLP markupProcessor;
964 markupProcessor.insert("requestURL", targeturl.c_str());
965 markupProcessor.insert("errorType", "Session Creation Service Error");
966 markupProcessor.insert("errorText", "Unexpected Exception");
967 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
968 return WriteClientError(lpECB,application,"shire",markupProcessor);
973 return HSE_STATUS_ERROR;