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,props);
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);
437 return WriteClientError(pfc,"Unable to map request to application settings, check configuration.");
439 // Declare SHIRE object for this request.
440 SHIRE shire(application);
442 // If the user is accessing the SHIRE acceptance point, pass it on.
443 if (targeturl.find(shire.getShireURL(targeturl.c_str()))!=string::npos)
444 return SF_STATUS_REQ_NEXT_NOTIFICATION;
446 // Now check the policy for this request.
447 pair<bool,bool> requireSession=settings.first->getBool("requireSession");
448 pair<const char*,const char*> shib_cookie=shire.getCookieNameProps();
450 // Check for session cookie.
451 const char* session_id=NULL;
452 GetHeader(pn,pfc,"Cookie:",buf,128,false);
453 Category::getInstance("isapi_shib.HttpFilterProc").debug("cookie header is {%s}",(const char*)buf);
454 if (!buf.empty() && (session_id=strstr(buf,shib_cookie.first))) {
455 session_id+=strlen(shib_cookie.first) + 1; /* Skip over the '=' */
456 char* cookieend=strchr(session_id,';');
458 *cookieend = '\0'; /* Ignore anyting after a ; */
461 if (!session_id || !*session_id) {
462 // If no session required, bail now.
463 if (!requireSession.second)
464 return SF_STATUS_REQ_NEXT_NOTIFICATION;
466 // No acceptable cookie, and we require a session. Generate an AuthnRequest.
467 string loc("Location: ");
468 loc+=shire.getAuthnRequest(targeturl.c_str());
469 pfc->AddResponseHeaders(pfc,const_cast<char*>(loc.c_str()),0);
470 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"302 Please Wait",0,0);
471 return SF_STATUS_REQ_FINISHED;
474 // Make sure this session is still valid.
475 RPCError* status = NULL;
476 ShibMLP markupProcessor;
477 markupProcessor.insert("requestURL", targeturl);
480 GetServerVariable(pfc,"REMOTE_ADDR",abuf,16);
482 status = shire.sessionIsValid(session_id, abuf);
484 catch (ShibTargetException &e) {
485 markupProcessor.insert("errorType", "Session Processing Error");
486 markupProcessor.insert("errorText", e.what());
487 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
488 return WriteClientError(pfc, application, "shire", markupProcessor);
492 markupProcessor.insert("errorType", "Session Processing Error");
493 markupProcessor.insert("errorText", "Unexpected Exception");
494 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
495 return WriteClientError(pfc, application, "shire", markupProcessor);
500 if (status->isError()) {
501 if (!requireSession.second)
502 return SF_STATUS_REQ_NEXT_NOTIFICATION;
503 else if (status->isRetryable()) {
504 // Oops, session is invalid. Generate AuthnRequest.
506 string loc("Location: ");
507 loc+=shire.getAuthnRequest(targeturl.c_str());
508 pfc->AddResponseHeaders(pfc,const_cast<char*>(loc.c_str()),0);
509 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"302 Please Wait",0,0);
510 return SF_STATUS_REQ_FINISHED;
513 // return the error page to the user
514 markupProcessor.insert(*status);
516 return WriteClientError(pfc, application, "shire", markupProcessor);
523 vector<SAMLAssertion*> assertions;
524 SAMLAuthenticationStatement* sso_statement=NULL;
527 status = rm.getAssertions(session_id, abuf, assertions, &sso_statement);
529 catch (ShibTargetException &e) {
530 markupProcessor.insert("errorType", "Attribute Processing Error");
531 markupProcessor.insert("errorText", e.what());
532 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
533 return WriteClientError(pfc, application, "rm", markupProcessor);
537 markupProcessor.insert("errorType", "Attribute Processing Error");
538 markupProcessor.insert("errorText", "Unexpected Exception");
539 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
540 return WriteClientError(pfc, application, "rm", markupProcessor);
544 if (status->isError()) {
545 markupProcessor.insert(*status);
547 return WriteClientError(pfc, application, "rm", markupProcessor);
551 // Do we have an access control plugin?
552 if (settings.second) {
553 Locker acllock(settings.second);
554 if (!settings.second->authorized(assertions)) {
555 for (int k = 0; k < assertions.size(); k++)
556 delete assertions[k];
557 delete sso_statement;
558 return WriteClientError(pfc, application, "access", markupProcessor);
562 // Get the AAP providers, which contain the attribute policy info.
563 Iterator<IAAP*> provs=application->getAAPProviders();
565 // Clear out the list of mapped attributes
566 while (provs.hasNext()) {
567 IAAP* aap=provs.next();
570 Iterator<const IAttributeRule*> rules=aap->getAttributeRules();
571 while (rules.hasNext()) {
572 const char* header=rules.next()->getHeader();
574 string hname=string(header) + ':';
575 pn->SetHeader(pfc,const_cast<char*>(hname.c_str()),"");
581 for (int k = 0; k < assertions.size(); k++)
582 delete assertions[k];
583 delete sso_statement;
584 markupProcessor.insert("errorType", "Attribute Processing Error");
585 markupProcessor.insert("errorText", "Unexpected Exception");
586 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
587 return WriteClientError(pfc, application, "rm", markupProcessor);
593 // Maybe export the first assertion.
594 pn->SetHeader(pfc,"remote-user:","");
595 pn->SetHeader(pfc,"Shib-Attributes:","");
596 pair<bool,bool> exp=settings.first->getBool("exportAssertion");
597 if (exp.first && exp.second && assertions.size()) {
599 RM::serialize(*(assertions[0]), assertion);
600 string::size_type lfeed;
601 while ((lfeed=assertion.find('\n'))!=string::npos)
602 assertion.erase(lfeed,1);
603 pn->SetHeader(pfc,"Shib-Attributes:",const_cast<char*>(assertion.c_str()));
606 pn->SetHeader(pfc,"Shib-Origin-Site:","");
607 pn->SetHeader(pfc,"Shib-Authentication-Method:","");
608 pn->SetHeader(pfc,"Shib-NameIdentifier-Format:","");
610 // 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()));
637 pn->SetHeader(pfc,"Shib-Application-ID:","");
638 pn->SetHeader(pfc,"Shib-Application-ID:",const_cast<char*>(application_id.second));
640 // Export the attributes.
641 Iterator<SAMLAssertion*> a_iter(assertions);
642 while (a_iter.hasNext()) {
643 SAMLAssertion* assert=a_iter.next();
644 Iterator<SAMLStatement*> statements=assert->getStatements();
645 while (statements.hasNext()) {
646 SAMLAttributeStatement* astate=dynamic_cast<SAMLAttributeStatement*>(statements.next());
649 Iterator<SAMLAttribute*> attrs=astate->getAttributes();
650 while (attrs.hasNext()) {
651 SAMLAttribute* attr=attrs.next();
653 // Are we supposed to export it?
654 AAP wrapper(provs,attr->getName(),attr->getNamespace());
655 if (wrapper.fail() || !wrapper->getHeader())
658 Iterator<string> vals=attr->getSingleByteValues();
659 if (!strcmp(wrapper->getHeader(),"REMOTE_USER") && vals.hasNext()) {
660 char* principal=const_cast<char*>(vals.next().c_str());
661 pn->SetHeader(pfc,"remote-user:",principal);
662 pfc->pFilterContext=pfc->AllocMem(pfc,strlen(principal)+1,0);
663 if (pfc->pFilterContext)
664 strcpy(static_cast<char*>(pfc->pFilterContext),principal);
669 string hname=string(wrapper->getHeader()) + ':';
670 GetHeader(pn,pfc,const_cast<char*>(hname.c_str()),buf,256,false);
675 for (; vals.hasNext(); it++) {
676 string value = vals.next();
677 for (string::size_type pos = value.find_first_of(";", string::size_type(0));
679 pos = value.find_first_of(";", pos)) {
680 value.insert(pos, "\\");
686 header=header + ';' + value;
688 pn->SetHeader(pfc,const_cast<char*>(hname.c_str()),const_cast<char*>(header.c_str()));
695 for (int k = 0; k < assertions.size(); k++)
696 delete assertions[k];
697 delete sso_statement;
699 return SF_STATUS_REQ_NEXT_NOTIFICATION;
702 return WriteClientError(pfc,"Out of Memory");
705 if (e==ERROR_NO_DATA)
706 return WriteClientError(pfc,"A required variable or header was empty.");
708 return WriteClientError(pfc,"Server detected unexpected IIS error.");
712 return WriteClientError(pfc,"Server caught an unknown exception.");
716 return WriteClientError(pfc,"Server reached unreachable code, save my walrus!");
719 IRequestMapper::Settings map_request(
720 LPEXTENSION_CONTROL_BLOCK lpECB, IRequestMapper* mapper, const char* hostname, string& target
726 GetServerVariable(lpECB,"HTTPS",ssl,5);
727 GetServerVariable(lpECB,"SERVER_PORT",port,10);
728 GetServerVariable(lpECB,"URL",url,255);
729 bool SSL=(ssl=="on");
732 target=static_cast<char*>(url);
733 if (port!=(SSL ? "443" : "80"))
734 target = ':' + static_cast<char*>(port) + target;
736 if (g_bNormalizeRequest) {
737 target = string(SSL ? "https://" : "http://") + hostname + target;
738 return mapper->getSettingsFromParsedURL(lpECB->lpszMethod,hostname,strtoul(port,NULL,10),url);
742 GetServerVariable(lpECB,"SERVER_NAME",name,64);
743 target = string(SSL ? "https://" : "http://") + static_cast<char*>(name) + target;
744 return mapper->getSettingsFromParsedURL((SSL ? "https" : "http"),name,strtoul(port,NULL,10),url);
748 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* msg)
750 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
751 static const char* ctype="Content-Type: text/html\r\n";
752 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
753 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Error</TITLE></HEAD><BODY><H1>Shibboleth Error</H1>";
754 DWORD resplen=strlen(xmsg);
755 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg,&resplen,HSE_IO_SYNC);
757 lpECB->WriteClient(lpECB->ConnID,(LPVOID)msg,&resplen,HSE_IO_SYNC);
758 static const char* xmsg2="</BODY></HTML>";
759 resplen=strlen(xmsg2);
760 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg2,&resplen,HSE_IO_SYNC);
761 return HSE_STATUS_SUCCESS;
764 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const IApplication* app, const char* page, ShibMLP& mlp)
766 const IPropertySet* props=app->getPropertySet("Errors");
768 pair<bool,const char*> p=props->getString(page);
770 ifstream infile(p.second);
771 if (!infile.fail()) {
772 const char* res = mlp.run(infile,props);
774 static const char* ctype="Content-Type: text/html\r\n";
775 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
776 DWORD resplen=strlen(res);
777 lpECB->WriteClient(lpECB->ConnID,(LPVOID)res,&resplen,0);
778 return HSE_STATUS_SUCCESS;
783 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Extension unable to open error template.");
784 return WriteClientError(lpECB,"Unable to open error template, check settings.");
787 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
790 const IApplication* application=NULL;
793 ostringstream threadid;
794 threadid << "[" << getpid() << "] shire_handler" << '\0';
795 saml::NDC ndc(threadid.str().c_str());
797 // Determine web site number. This can't really fail, I don't think.
799 GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
801 // Match site instance to host name, skip if no match.
802 map<string,string>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
803 if (map_i==g_Sites.end())
804 return WriteClientError(lpECB,"Shibboleth filter not configured for this web site.");
806 const string& site=map_i->second;
808 // We lock the configuration system for the duration.
809 IConfig* conf=g_Config->getINI();
812 // Map request to application and content settings.
814 IRequestMapper* mapper=conf->getRequestMapper();
815 Locker locker2(mapper);
816 IRequestMapper::Settings settings=map_request(lpECB,mapper,site.c_str(),targeturl);
817 pair<bool,const char*> application_id=settings.first->getString("applicationId");
818 application=conf->getApplication(application_id.second);
819 const IPropertySet* sessionProps=application ? application->getPropertySet("Sessions") : NULL;
820 if (!application || !sessionProps)
821 return WriteClientError(lpECB,"Unable to map request to application session settings, check configuration.");
823 SHIRE shire(application);
825 // Make sure we only process the SHIRE requests.
826 if (!strstr(targeturl.c_str(),shire.getShireURL(targeturl.c_str())))
827 return WriteClientError(lpECB,"The request's application and associated shireURL setting are inconsistent.");;
829 pair<const char*,const char*> shib_cookie=shire.getCookieNameProps();
831 // Make sure this is SSL, if it should be
832 pair<bool,bool> shireSSL=sessionProps->getBool("shireSSL");
833 if (!shireSSL.first || shireSSL.second) {
834 GetServerVariable(lpECB,"HTTPS",buf,10);
836 throw ShibTargetException(SHIBRPC_OK,"blocked non-SSL access to SHIRE POST processor");
839 // If this is a GET, we manufacture an AuthnRequest.
840 if (!stricmp(lpECB->lpszMethod,"GET")) {
841 const char* areq=lpECB->lpszQueryString ? shire.getLazyAuthnRequest(lpECB->lpszQueryString) : NULL;
843 throw ShibTargetException(SHIBRPC_OK, "malformed arguments to request a new session");
844 targeturl = string("Location: ") + areq + "\r\n"
845 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
846 "Cache-Control: private,no-store,no-cache\r\n"
847 "Connection: close\r\n";
848 HSE_SEND_HEADER_EX_INFO hinfo;
849 hinfo.pszStatus="302 Moved";
850 hinfo.pszHeader=targeturl.c_str();
852 hinfo.cchHeader=targeturl.length();
853 hinfo.fKeepConn=FALSE;
854 if (lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER_EX,&hinfo,0,0))
855 return HSE_STATUS_SUCCESS;
856 return HSE_STATUS_ERROR;
858 else if (stricmp(lpECB->lpszMethod,"POST"))
859 throw ShibTargetException(SHIBRPC_OK,"blocked non-POST to SHIRE POST processor");
861 // Sure sure this POST is an appropriate content type
862 if (!lpECB->lpszContentType || stricmp(lpECB->lpszContentType,"application/x-www-form-urlencoded"))
863 throw ShibTargetException(SHIBRPC_OK,"blocked bad content-type to SHIRE POST processor");
866 pair<const char*,const char*> elements=pair<const char*,const char*>(NULL,NULL);
867 if (lpECB->cbTotalBytes > 1024*1024) // 1MB?
868 throw ShibTargetException(SHIBRPC_OK,"blocked too-large a post to SHIRE POST processor");
869 else if (lpECB->cbTotalBytes!=lpECB->cbAvailable) {
872 DWORD datalen=lpECB->cbTotalBytes;
875 BOOL ret=lpECB->ReadClient(lpECB->ConnID,buf,&buflen);
877 throw ShibTargetException(SHIBRPC_OK,"error reading POST data from browser");
878 cgistr.append(buf,buflen);
881 elements=shire.getFormSubmission(cgistr.c_str(),cgistr.length());
884 elements=shire.getFormSubmission(reinterpret_cast<char*>(lpECB->lpbData),lpECB->cbAvailable);
886 // Make sure the SAML Response parameter exists
887 if (!elements.first || !*elements.first)
888 throw ShibTargetException(SHIBRPC_OK, "SHIRE POST failed to find SAMLResponse form element");
890 // Make sure the target parameter exists
891 if (!elements.second || !*elements.second)
892 throw ShibTargetException(SHIBRPC_OK, "SHIRE POST failed to find TARGET form element");
894 GetServerVariable(lpECB,"REMOTE_ADDR",buf,16);
898 RPCError* status=NULL;
899 ShibMLP markupProcessor;
900 markupProcessor.insert("requestURL", targeturl.c_str());
902 status = shire.sessionCreate(elements.first,buf,cookie);
904 catch (ShibTargetException &e) {
905 markupProcessor.insert("errorType", "Session Creation Service Error");
906 markupProcessor.insert("errorText", e.what());
907 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
908 return WriteClientError(lpECB, application, "shire", markupProcessor);
912 markupProcessor.insert("errorType", "Session Creation Service Error");
913 markupProcessor.insert("errorText", "Unexpected Exception");
914 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
915 return WriteClientError(lpECB, application, "shire", markupProcessor);
919 if (status->isError()) {
920 if (status->isRetryable()) {
922 const char* loc=shire.getAuthnRequest(elements.second);
923 DWORD len=strlen(loc);
924 if (lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_URL_REDIRECT_RESP,(LPVOID)loc,&len,0))
925 return HSE_STATUS_SUCCESS;
926 return HSE_STATUS_ERROR;
929 // Return this error to the user.
930 markupProcessor.insert(*status);
932 return WriteClientError(lpECB,application,"shire",markupProcessor);
936 // We've got a good session, set the cookie and redirect to target.
937 cookie = string("Set-Cookie: ") + shib_cookie.first + '=' + cookie + shib_cookie.second + "\r\n"
938 "Location: " + elements.second + "\r\n"
939 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
940 "Cache-Control: private,no-store,no-cache\r\n"
941 "Connection: close\r\n";
942 HSE_SEND_HEADER_EX_INFO hinfo;
943 hinfo.pszStatus="302 Moved";
944 hinfo.pszHeader=cookie.c_str();
946 hinfo.cchHeader=cookie.length();
947 hinfo.fKeepConn=FALSE;
948 if (lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER_EX,&hinfo,0,0))
949 return HSE_STATUS_SUCCESS;
951 catch (ShibTargetException &e) {
953 ShibMLP markupProcessor;
954 markupProcessor.insert("requestURL", targeturl.c_str());
955 markupProcessor.insert("errorType", "Session Creation Service Error");
956 markupProcessor.insert("errorText", e.what());
957 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
958 return WriteClientError(lpECB,application,"shire",markupProcessor);
964 ShibMLP markupProcessor;
965 markupProcessor.insert("requestURL", targeturl.c_str());
966 markupProcessor.insert("errorType", "Session Creation Service Error");
967 markupProcessor.insert("errorText", "Unexpected Exception");
968 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
969 return WriteClientError(lpECB,application,"shire",markupProcessor);
974 return HSE_STATUS_ERROR;