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>
75 using namespace shibboleth;
76 using namespace shibtarget;
80 static const XMLCh name[] = { chLatin_n, chLatin_a, chLatin_m, chLatin_e, chNull };
81 static const XMLCh port[] = { chLatin_p, chLatin_o, chLatin_r, chLatin_t, chNull };
82 static const XMLCh sslport[] = { chLatin_s, chLatin_s, chLatin_l, chLatin_p, chLatin_o, chLatin_r, chLatin_t, chNull };
83 static const XMLCh scheme[] = { chLatin_s, chLatin_c, chLatin_h, chLatin_e, chLatin_m, chLatin_e, chNull };
84 static const XMLCh id[] = { chLatin_i, chLatin_d, chNull };
85 static const XMLCh Implementation[] =
86 { 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 };
87 static const XMLCh ISAPI[] = { chLatin_I, chLatin_S, chLatin_A, chLatin_P, chLatin_I, chNull };
88 static const XMLCh Alias[] = { chLatin_A, chLatin_l, chLatin_i, chLatin_a, chLatin_s, chNull };
89 static const XMLCh normalizeRequest[] =
90 { chLatin_n, chLatin_o, chLatin_r, chLatin_m, chLatin_a, chLatin_l, chLatin_i, chLatin_z, chLatin_e,
91 chLatin_R, chLatin_e, chLatin_q, chLatin_u, chLatin_e, chLatin_s, chLatin_t, chNull
93 static const XMLCh Site[] = { chLatin_S, chLatin_i, chLatin_t, chLatin_e, chNull };
96 site_t(const DOMElement* e)
98 auto_ptr_char n(e->getAttributeNS(NULL,name));
99 auto_ptr_char s(e->getAttributeNS(NULL,scheme));
100 auto_ptr_char p(e->getAttributeNS(NULL,port));
101 auto_ptr_char p2(e->getAttributeNS(NULL,sslport));
102 if (n.get()) m_name=n.get();
103 if (s.get()) m_scheme=s.get();
104 if (p.get()) m_port=p.get();
105 if (p2.get()) m_sslport=p2.get();
106 DOMNodeList* nlist=e->getElementsByTagNameNS(shibtarget::XML::SHIBTARGET_NS,Alias);
107 for (int i=0; nlist && i<nlist->getLength(); i++) {
108 if (nlist->item(i)->hasChildNodes()) {
109 auto_ptr_char alias(nlist->item(i)->getFirstChild()->getNodeValue());
110 m_aliases.insert(alias.get());
114 string m_scheme,m_port,m_sslport,m_name;
115 set<string> m_aliases;
118 HINSTANCE g_hinstDLL;
119 ShibTargetConfig* g_Config = NULL;
120 map<string,site_t> g_Sites;
121 bool g_bNormalizeRequest = true;
125 LPCSTR lpUNCServerName,
131 LPCSTR messages[] = {message, NULL};
133 HANDLE hElog = RegisterEventSource(lpUNCServerName, "Shibboleth ISAPI Filter");
134 BOOL res = ReportEvent(hElog, wType, 0, dwEventID, lpUserSid, 1, 0, messages, NULL);
135 return (DeregisterEventSource(hElog) && res);
138 extern "C" __declspec(dllexport) BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID)
140 if (fdwReason==DLL_PROCESS_ATTACH)
145 extern "C" BOOL WINAPI GetExtensionVersion(HSE_VERSION_INFO* pVer)
152 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
153 "Extension mode startup not possible, is the DLL loaded as a filter?");
157 pVer->dwExtensionVersion=HSE_VERSION;
158 strncpy(pVer->lpszExtensionDesc,"Shibboleth ISAPI Extension",HSE_MAX_EXT_DLL_NAME_LEN-1);
162 extern "C" BOOL WINAPI TerminateExtension(DWORD)
164 return TRUE; // cleanup should happen when filter unloads
167 extern "C" BOOL WINAPI GetFilterVersion(PHTTP_FILTER_VERSION pVer)
172 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
173 "Reentrant filter initialization, ignoring...");
181 LPCSTR schemadir=getenv("SHIBSCHEMAS");
183 schemadir=SHIB_SCHEMAS;
184 LPCSTR config=getenv("SHIBCONFIG");
187 g_Config=&ShibTargetConfig::getConfig();
188 g_Config->setFeatures(
189 ShibTargetConfig::Listener |
190 ShibTargetConfig::Metadata |
191 ShibTargetConfig::AAP |
192 ShibTargetConfig::RequestMapper |
193 ShibTargetConfig::LocalExtensions |
194 ShibTargetConfig::Logging
196 if (!g_Config->init(schemadir)) {
198 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
199 "Filter startup failed during library initialization, check native log for help.");
202 else if (!g_Config->load(config)) {
204 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
205 "Filter startup failed to load configuration, check native log for help.");
209 // Access the implementation-specifics for site mappings.
210 IConfig* conf=g_Config->getINI();
212 const IPropertySet* props=conf->getPropertySet("Local");
214 const DOMElement* impl=saml::XML::getFirstChildElement(
215 props->getElement(),shibtarget::XML::SHIBTARGET_NS,Implementation
217 if (impl && (impl=saml::XML::getFirstChildElement(impl,shibtarget::XML::SHIBTARGET_NS,ISAPI))) {
218 const XMLCh* flag=impl->getAttributeNS(NULL,normalizeRequest);
219 g_bNormalizeRequest=(!flag || !*flag || *flag==chDigit_1 || *flag==chLatin_t);
220 impl=saml::XML::getFirstChildElement(impl,shibtarget::XML::SHIBTARGET_NS,Site);
222 auto_ptr_char id(impl->getAttributeNS(NULL,id));
224 g_Sites.insert(pair<string,site_t>(id.get(),site_t(impl)));
225 impl=saml::XML::getNextSiblingElement(impl,shibtarget::XML::SHIBTARGET_NS,Site);
233 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Filter startup failed with an exception.");
238 pVer->dwFilterVersion=HTTP_FILTER_REVISION;
239 strncpy(pVer->lpszFilterDesc,"Shibboleth ISAPI Filter",SF_MAX_FILTER_DESC_LEN);
240 pVer->dwFlags=(SF_NOTIFY_ORDER_HIGH |
241 SF_NOTIFY_SECURE_PORT |
242 SF_NOTIFY_NONSECURE_PORT |
243 SF_NOTIFY_PREPROC_HEADERS |
245 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter initialized...");
249 extern "C" BOOL WINAPI TerminateFilter(DWORD)
252 g_Config->shutdown();
254 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter shut down...");
258 /* Next up, some suck-free versions of various APIs.
260 You DON'T require people to guess the buffer size and THEN tell them the right size.
261 Returning an LPCSTR is apparently way beyond their ken. Not to mention the fact that
262 constant strings aren't typed as such, making it just that much harder. These versions
263 are now updated to use a special growable buffer object, modeled after the standard
264 string class. The standard string won't work because they left out the option to
265 pre-allocate a non-constant buffer.
271 dynabuf() { bufptr=NULL; buflen=0; }
272 dynabuf(size_t s) { bufptr=new char[buflen=s]; *bufptr=0; }
273 ~dynabuf() { delete[] bufptr; }
274 size_t length() const { return bufptr ? strlen(bufptr) : 0; }
275 size_t size() const { return buflen; }
276 bool empty() const { return length()==0; }
277 void reserve(size_t s, bool keep=false);
278 void erase() { if (bufptr) memset(bufptr,0,buflen); }
279 operator char*() { return bufptr; }
280 bool operator ==(const char* s) const;
281 bool operator !=(const char* s) const { return !(*this==s); }
287 void dynabuf::reserve(size_t s, bool keep)
294 p[buflen]=bufptr[buflen];
300 bool dynabuf::operator==(const char* s) const
302 if (buflen==NULL || s==NULL)
303 return (buflen==NULL && s==NULL);
305 return strcmp(bufptr,s)==0;
308 void GetServerVariable(PHTTP_FILTER_CONTEXT pfc, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
309 throw (bad_alloc, DWORD)
315 while (!pfc->GetServerVariable(pfc,lpszVariable,s,&size))
317 // Grumble. Check the error.
318 DWORD e=GetLastError();
319 if (e==ERROR_INSUFFICIENT_BUFFER)
324 if (bRequired && s.empty())
328 void GetServerVariable(LPEXTENSION_CONTROL_BLOCK lpECB, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
329 throw (bad_alloc, DWORD)
335 while (!lpECB->GetServerVariable(lpECB->ConnID,lpszVariable,s,&size))
337 // Grumble. Check the error.
338 DWORD e=GetLastError();
339 if (e==ERROR_INSUFFICIENT_BUFFER)
344 if (bRequired && s.empty())
348 void GetHeader(PHTTP_FILTER_PREPROC_HEADERS pn, PHTTP_FILTER_CONTEXT pfc,
349 LPSTR lpszName, dynabuf& s, DWORD size=80, bool bRequired=true)
350 throw (bad_alloc, DWORD)
356 while (!pn->GetHeader(pfc,lpszName,s,&size))
358 // Grumble. Check the error.
359 DWORD e=GetLastError();
360 if (e==ERROR_INSUFFICIENT_BUFFER)
365 if (bRequired && s.empty())
369 /****************************************************************************/
372 class ShibTargetIsapiF : public ShibTarget
374 PHTTP_FILTER_CONTEXT m_pfc;
375 PHTTP_FILTER_PREPROC_HEADERS m_pn;
378 ShibTargetIsapiF(PHTTP_FILTER_CONTEXT pfc, PHTTP_FILTER_PREPROC_HEADERS pn, const site_t& site) {
380 // URL path always come from IIS.
382 GetHeader(pn,pfc,"url",url,256,false);
384 // Port may come from IIS or from site def.
386 if (!g_bNormalizeRequest || (pfc->fIsSecurePort && site.m_sslport.empty()) || (!pfc->fIsSecurePort && site.m_port.empty()))
387 GetServerVariable(pfc,"SERVER_PORT",port,10);
388 else if (pfc->fIsSecurePort) {
389 strncpy(port,site.m_sslport.c_str(),10);
390 static_cast<char*>(port)[10]=0;
393 strncpy(port,site.m_port.c_str(),10);
394 static_cast<char*>(port)[10]=0;
397 // Scheme may come from site def or be derived from IIS.
398 const char* scheme=site.m_scheme.c_str();
399 if (!scheme || !*scheme || !g_bNormalizeRequest)
400 scheme=pfc->fIsSecurePort ? "https" : "http";
402 // Get the rest of the server variables.
403 dynabuf remote_addr(16),method(5),content_type(32),hostname(32);
404 GetServerVariable(pfc,"SERVER_NAME",hostname,32);
405 GetServerVariable(pfc,"REMOTE_ADDR",remote_addr,16);
406 GetServerVariable(pfc,"REQUEST_METHOD",method,5,false);
407 GetServerVariable(pfc,"CONTENT_TYPE",content_type,32,false);
409 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
410 const char* host=hostname;
411 if (site.m_name!=host && site.m_aliases.find(host)==site.m_aliases.end())
412 host=site.m_name.c_str();
414 init(scheme, host, atoi(port), url, content_type, remote_addr, method);
419 ~ShibTargetIsapiF() { }
421 virtual void log(ShibLogLevel level, const string &msg) {
422 ShibTarget::log(level,msg);
423 if (level == LogLevelError)
424 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
426 virtual string getCookies() const {
428 GetHeader(m_pn, m_pfc, "Cookie:", buf, 128, false);
429 return buf.empty() ? "" : buf;
432 virtual void clearHeader(const string &name) {
433 string hdr = name + ":";
434 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), "");
436 virtual void setHeader(const string &name, const string &value) {
437 string hdr = name + ":";
438 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()),
439 const_cast<char*>(value.c_str()));
441 virtual string getHeader(const string &name) {
442 string hdr = name + ":";
444 GetHeader(m_pn, m_pfc, const_cast<char*>(hdr.c_str()), buf, 1024, false);
447 virtual void setRemoteUser(const string &user) {
448 setHeader(string("remote-user"), user);
450 virtual string getRemoteUser(void) {
451 return getHeader(string("remote-user"));
453 virtual void* sendPage(
456 const string& content_type="text/html",
457 const Iterator<header_t>& headers=EMPTY(header_t)) {
458 string hdr = string ("Connection: close\r\nContent-type: ") + content_type + "\r\n";
459 while (headers.hasNext()) {
460 const header_t& h=headers.next();
461 hdr += h.first + ": " + h.second + "\r\n";
464 const char* codestr="200 OK";
466 case 403: codestr="403 Forbidden"; break;
467 case 404: codestr="404 Not Found"; break;
468 case 500: codestr="500 Server Error"; break;
470 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, (void*)codestr, (DWORD)hdr.c_str(), 0);
471 DWORD resplen = msg.size();
472 m_pfc->WriteClient(m_pfc, (LPVOID)msg.c_str(), &resplen, 0);
473 return (void*)SF_STATUS_REQ_FINISHED;
475 virtual void* sendRedirect(const string& url) {
476 // XXX: Don't support the httpRedirect option, yet.
477 string hdrs=m_cookie + string("Location: ") + url + "\r\n"
478 "Content-Type: text/html\r\n"
479 "Content-Length: 40\r\n"
480 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
481 "Cache-Control: private,no-store,no-cache\r\n\r\n";
482 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER,
483 "302 Please Wait", (DWORD)hdrs.c_str(), 0);
484 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
486 m_pfc->WriteClient(m_pfc, (LPVOID)redmsg, &resplen, 0);
487 return reinterpret_cast<void*>(SF_STATUS_REQ_FINISHED);
489 // XXX: We might not ever hit the 'decline' status in this filter.
490 //virtual void* returnDecline(void) { }
491 virtual void* returnOK(void) { return (void*) SF_STATUS_REQ_NEXT_NOTIFICATION; }
493 // The filter never processes the POST, so stub these methods.
494 virtual void setCookie(const string &name, const string &value) {
495 // Set the cookie for later. Use it during the redirect.
496 m_cookie += "Set-Cookie: " + name + "=" + value + "\r\n";
498 virtual string getArgs(void) { throw runtime_error("getArgs not implemented"); }
499 virtual string getPostData(void) { throw runtime_error("getPostData not implemented"); }
502 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const char* msg)
504 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
505 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
506 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)ctype,0);
507 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Filter Error</TITLE></HEAD><BODY>"
508 "<H1>Shibboleth Filter Error</H1>";
509 DWORD resplen=strlen(xmsg);
510 pfc->WriteClient(pfc,(LPVOID)xmsg,&resplen,0);
512 pfc->WriteClient(pfc,(LPVOID)msg,&resplen,0);
513 static const char* xmsg2="</BODY></HTML>";
514 resplen=strlen(xmsg2);
515 pfc->WriteClient(pfc,(LPVOID)xmsg2,&resplen,0);
516 return SF_STATUS_REQ_FINISHED;
519 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
521 // Is this a log notification?
522 if (notificationType==SF_NOTIFY_LOG)
524 if (pfc->pFilterContext)
525 ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=static_cast<LPCSTR>(pfc->pFilterContext);
526 return SF_STATUS_REQ_NEXT_NOTIFICATION;
529 PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
532 // Determine web site number. This can't really fail, I don't think.
534 GetServerVariable(pfc,"INSTANCE_ID",buf,10);
536 // Match site instance to host name, skip if no match.
537 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
538 if (map_i==g_Sites.end())
539 return SF_STATUS_REQ_NEXT_NOTIFICATION;
541 ostringstream threadid;
542 threadid << "[" << getpid() << "] isapi_shib" << '\0';
543 saml::NDC ndc(threadid.str().c_str());
545 ShibTargetIsapiF stf(pfc, pn, map_i->second);
547 // "false" because we don't override the Shib settings
548 pair<bool,void*> res = stf.doCheckAuthN();
549 if (res.first) return (DWORD)res.second;
551 // "false" because we don't override the Shib settings
552 res = stf.doExportAssertions();
553 if (res.first) return (DWORD)res.second;
555 res = stf.doCheckAuthZ();
556 if (res.first) return (DWORD)res.second;
558 return SF_STATUS_REQ_NEXT_NOTIFICATION;
561 return WriteClientError(pfc,"Out of Memory");
564 if (e==ERROR_NO_DATA)
565 return WriteClientError(pfc,"A required variable or header was empty.");
567 return WriteClientError(pfc,"Server detected unexpected IIS error.");
571 return WriteClientError(pfc,"Server caught an unknown exception.");
575 return WriteClientError(pfc,"Server reached unreachable code, save my walrus!");
580 IRequestMapper::Settings map_request(
581 PHTTP_FILTER_CONTEXT pfc, PHTTP_FILTER_PREPROC_HEADERS pn, IRequestMapper* mapper, const site_t& site, string& target
584 // URL path always come from IIS.
586 GetHeader(pn,pfc,"url",url,256,false);
588 // Port may come from IIS or from site def.
590 if (site.m_port.empty() || !g_bNormalizeRequest)
591 GetServerVariable(pfc,"SERVER_PORT",port,10);
593 strncpy(port,site.m_port.c_str(),10);
594 static_cast<char*>(port)[10]=0;
597 // Scheme may come from site def or be derived from IIS.
598 const char* scheme=site.m_scheme.c_str();
599 if (!scheme || !*scheme || !g_bNormalizeRequest)
600 scheme=pfc->fIsSecurePort ? "https" : "http";
602 // Start with scheme and hostname.
603 if (g_bNormalizeRequest) {
604 target = string(scheme) + "://" + site.m_name;
608 GetServerVariable(pfc,"SERVER_NAME",name,64);
609 target = string(scheme) + "://" + static_cast<char*>(name);
612 // If port is non-default, append it.
613 if ((!strcmp(scheme,"http") && port!="80") || (!strcmp(scheme,"https") && port!="443"))
614 target = target + ':' + static_cast<char*>(port);
618 target+=static_cast<char*>(url);
620 return mapper->getSettingsFromParsedURL(scheme,site.m_name.c_str(),strtoul(port,NULL,10),url);
623 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const IApplication* app, const char* page, ShibMLP& mlp)
625 const IPropertySet* props=app->getPropertySet("Errors");
627 pair<bool,const char*> p=props->getString(page);
629 ifstream infile(p.second);
630 if (!infile.fail()) {
631 const char* res = mlp.run(infile,props);
633 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
634 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)ctype,0);
635 DWORD resplen=strlen(res);
636 pfc->WriteClient(pfc,(LPVOID)res,&resplen,0);
637 return SF_STATUS_REQ_FINISHED;
643 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Filter unable to open error template.");
644 return WriteClientError(pfc,"Unable to open error template, check settings.");
647 DWORD WriteRedirectPage(PHTTP_FILTER_CONTEXT pfc, const IApplication* app, const char* file, ShibMLP& mlp, const char* headers=NULL)
649 ifstream infile(file);
650 if (!infile.fail()) {
651 const char* res = mlp.run(infile,app->getPropertySet("Errors"));
654 sprintf(buf,"Content-Length: %u\r\nContent-Type: text/html\r\n\r\n",strlen(res));
658 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)h.c_str(),0);
661 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)buf,0);
662 DWORD resplen=strlen(res);
663 pfc->WriteClient(pfc,(LPVOID)res,&resplen,0);
664 return SF_STATUS_REQ_FINISHED;
667 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Extension unable to open redirect template.");
668 return WriteClientError(pfc,"Unable to open redirect template, check settings.");
671 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
673 // Is this a log notification?
674 if (notificationType==SF_NOTIFY_LOG)
676 if (pfc->pFilterContext)
677 ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=static_cast<LPCSTR>(pfc->pFilterContext);
678 return SF_STATUS_REQ_NEXT_NOTIFICATION;
681 PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
684 // Determine web site number. This can't really fail, I don't think.
686 GetServerVariable(pfc,"INSTANCE_ID",buf,10);
688 // Match site instance to host name, skip if no match.
689 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
690 if (map_i==g_Sites.end())
691 return SF_STATUS_REQ_NEXT_NOTIFICATION;
693 ostringstream threadid;
694 threadid << "[" << getpid() << "] isapi_shib" << '\0';
695 saml::NDC ndc(threadid.str().c_str());
697 // We lock the configuration system for the duration.
698 IConfig* conf=g_Config->getINI();
701 // Map request to application and content settings.
703 IRequestMapper* mapper=conf->getRequestMapper();
704 Locker locker2(mapper);
705 IRequestMapper::Settings settings=map_request(pfc,pn,mapper,map_i->second,targeturl);
706 pair<bool,const char*> application_id=settings.first->getString("applicationId");
707 const IApplication* application=conf->getApplication(application_id.second);
709 return WriteClientError(pfc,"Unable to map request to application settings, check configuration.");
711 // Declare SHIRE object for this request.
712 SHIRE shire(application);
714 const char* shireURL=shire.getShireURL(targeturl.c_str());
716 return WriteClientError(pfc,"Unable to map request to proper shireURL setting, check configuration.");
718 // If the user is accessing the SHIRE acceptance point, pass it on.
719 if (targeturl.find(shireURL)!=string::npos)
720 return SF_STATUS_REQ_NEXT_NOTIFICATION;
722 // Now check the policy for this request.
723 pair<bool,bool> requireSession=settings.first->getBool("requireSession");
724 pair<const char*,const char*> shib_cookie=shire.getCookieNameProps();
725 pair<bool,bool> httpRedirects=application->getPropertySet("Sessions")->getBool("httpRedirects");
726 pair<bool,const char*> redirectPage=application->getPropertySet("Sessions")->getString("redirectPage");
727 if (httpRedirects.first && !httpRedirects.second && !redirectPage.first)
728 return WriteClientError(pfc,"HTML-based redirection requires a redirectPage property.");
730 // Check for session cookie.
731 const char* session_id=NULL;
732 GetHeader(pn,pfc,"Cookie:",buf,128,false);
733 Category::getInstance("isapi_shib.HttpFilterProc").debug("cookie header is {%s}",(const char*)buf);
734 if (!buf.empty() && (session_id=strstr(buf,shib_cookie.first))) {
735 session_id+=strlen(shib_cookie.first) + 1; /* Skip over the '=' */
736 char* cookieend=strchr(session_id,';');
738 *cookieend = '\0'; /* Ignore anyting after a ; */
741 if (!session_id || !*session_id) {
742 // If no session required, bail now.
743 if (!requireSession.second)
744 return SF_STATUS_REQ_NEXT_NOTIFICATION;
746 // No acceptable cookie, and we require a session. Generate an AuthnRequest.
747 const char* areq = shire.getAuthnRequest(targeturl.c_str());
748 if (!httpRedirects.first || httpRedirects.second) {
749 string hdrs=string("Location: ") + areq + "\r\n"
750 "Content-Type: text/html\r\n"
751 "Content-Length: 40\r\n"
752 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
753 "Cache-Control: private,no-store,no-cache\r\n\r\n";
754 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"302 Please Wait",(DWORD)hdrs.c_str(),0);
755 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
757 pfc->WriteClient(pfc,(LPVOID)redmsg,&resplen,0);
758 return SF_STATUS_REQ_FINISHED;
761 ShibMLP markupProcessor;
762 markupProcessor.insert("requestURL",areq);
763 return WriteRedirectPage(pfc, application, redirectPage.second, markupProcessor);
767 // Make sure this session is still valid.
768 RPCError* status = NULL;
769 ShibMLP markupProcessor;
770 markupProcessor.insert("requestURL", targeturl);
773 GetServerVariable(pfc,"REMOTE_ADDR",abuf,16);
775 status = shire.sessionIsValid(session_id, abuf);
777 catch (ShibTargetException &e) {
778 markupProcessor.insert("errorType", "Session Processing Error");
779 markupProcessor.insert("errorText", e.what());
780 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
781 return WriteClientError(pfc, application, "shire", markupProcessor);
785 markupProcessor.insert("errorType", "Session Processing Error");
786 markupProcessor.insert("errorText", "Unexpected Exception");
787 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
788 return WriteClientError(pfc, application, "shire", markupProcessor);
793 if (status->isError()) {
794 if (!requireSession.second)
795 return SF_STATUS_REQ_NEXT_NOTIFICATION;
796 else if (status->isRetryable()) {
797 // Oops, session is invalid. Generate AuthnRequest.
799 const char* areq = shire.getAuthnRequest(targeturl.c_str());
800 if (!httpRedirects.first || httpRedirects.second) {
801 string hdrs=string("Location: ") + areq + "\r\n"
802 "Content-Type: text/html\r\n"
803 "Content-Length: 40\r\n"
804 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
805 "Cache-Control: private,no-store,no-cache\r\n\r\n";
806 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"302 Please Wait",(DWORD)hdrs.c_str(),0);
807 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
809 pfc->WriteClient(pfc,(LPVOID)redmsg,&resplen,0);
810 return SF_STATUS_REQ_FINISHED;
813 markupProcessor.insert("requestURL",areq);
814 return WriteRedirectPage(pfc, application, redirectPage.second, markupProcessor);
818 // return the error page to the user
819 markupProcessor.insert(*status);
821 return WriteClientError(pfc, application, "shire", markupProcessor);
828 vector<SAMLAssertion*> assertions;
829 SAMLAuthenticationStatement* sso_statement=NULL;
832 status = rm.getAssertions(session_id, abuf, assertions, &sso_statement);
834 catch (ShibTargetException &e) {
835 markupProcessor.insert("errorType", "Attribute Processing Error");
836 markupProcessor.insert("errorText", e.what());
837 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
838 return WriteClientError(pfc, application, "rm", markupProcessor);
842 markupProcessor.insert("errorType", "Attribute Processing Error");
843 markupProcessor.insert("errorText", "Unexpected Exception");
844 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
845 return WriteClientError(pfc, application, "rm", markupProcessor);
849 if (status->isError()) {
850 markupProcessor.insert(*status);
852 return WriteClientError(pfc, application, "rm", markupProcessor);
856 // Do we have an access control plugin?
857 if (settings.second) {
858 Locker acllock(settings.second);
859 if (!settings.second->authorized(*sso_statement,assertions)) {
860 for (int k = 0; k < assertions.size(); k++)
861 delete assertions[k];
862 delete sso_statement;
863 return WriteClientError(pfc, application, "access", markupProcessor);
867 // Get the AAP providers, which contain the attribute policy info.
868 Iterator<IAAP*> provs=application->getAAPProviders();
870 // Clear out the list of mapped attributes
871 while (provs.hasNext()) {
872 IAAP* aap=provs.next();
875 Iterator<const IAttributeRule*> rules=aap->getAttributeRules();
876 while (rules.hasNext()) {
877 const char* header=rules.next()->getHeader();
879 string hname=string(header) + ':';
880 pn->SetHeader(pfc,const_cast<char*>(hname.c_str()),"");
886 for (int k = 0; k < assertions.size(); k++)
887 delete assertions[k];
888 delete sso_statement;
889 markupProcessor.insert("errorType", "Attribute Processing Error");
890 markupProcessor.insert("errorText", "Unexpected Exception");
891 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
892 return WriteClientError(pfc, application, "rm", markupProcessor);
898 // Maybe export the first assertion.
899 pn->SetHeader(pfc,"remote-user:","");
900 pn->SetHeader(pfc,"Shib-Attributes:","");
901 pair<bool,bool> exp=settings.first->getBool("exportAssertion");
902 if (exp.first && exp.second && assertions.size()) {
904 RM::serialize(*(assertions[0]), assertion);
905 string::size_type lfeed;
906 while ((lfeed=assertion.find('\n'))!=string::npos)
907 assertion.erase(lfeed,1);
908 pn->SetHeader(pfc,"Shib-Attributes:",const_cast<char*>(assertion.c_str()));
911 pn->SetHeader(pfc,"Shib-Origin-Site:","");
912 pn->SetHeader(pfc,"Shib-Authentication-Method:","");
913 pn->SetHeader(pfc,"Shib-NameIdentifier-Format:","");
915 // Export the SAML AuthnMethod and the origin site name.
916 auto_ptr_char os(sso_statement->getSubject()->getNameIdentifier()->getNameQualifier());
917 auto_ptr_char am(sso_statement->getAuthMethod());
918 pn->SetHeader(pfc,"Shib-Origin-Site:", const_cast<char*>(os.get()));
919 pn->SetHeader(pfc,"Shib-Authentication-Method:", const_cast<char*>(am.get()));
922 AAP wrapper(provs,sso_statement->getSubject()->getNameIdentifier()->getFormat(),Constants::SHIB_ATTRIBUTE_NAMESPACE_URI);
923 if (!wrapper.fail() && wrapper->getHeader()) {
924 auto_ptr_char form(sso_statement->getSubject()->getNameIdentifier()->getFormat());
925 auto_ptr_char nameid(sso_statement->getSubject()->getNameIdentifier()->getName());
926 pn->SetHeader(pfc,"Shib-NameIdentifier-Format:",const_cast<char*>(form.get()));
927 if (!strcmp(wrapper->getHeader(),"REMOTE_USER")) {
928 char* principal=const_cast<char*>(nameid.get());
929 pn->SetHeader(pfc,"remote-user:",principal);
930 pfc->pFilterContext=pfc->AllocMem(pfc,strlen(principal)+1,0);
931 if (pfc->pFilterContext)
932 strcpy(static_cast<char*>(pfc->pFilterContext),principal);
935 string hname=string(wrapper->getHeader()) + ':';
936 pn->SetHeader(pfc,const_cast<char*>(wrapper->getHeader()),const_cast<char*>(nameid.get()));
940 pn->SetHeader(pfc,"Shib-Application-ID:","");
941 pn->SetHeader(pfc,"Shib-Application-ID:",const_cast<char*>(application_id.second));
943 // Export the attributes.
944 Iterator<SAMLAssertion*> a_iter(assertions);
945 while (a_iter.hasNext()) {
946 SAMLAssertion* assert=a_iter.next();
947 Iterator<SAMLStatement*> statements=assert->getStatements();
948 while (statements.hasNext()) {
949 SAMLAttributeStatement* astate=dynamic_cast<SAMLAttributeStatement*>(statements.next());
952 Iterator<SAMLAttribute*> attrs=astate->getAttributes();
953 while (attrs.hasNext()) {
954 SAMLAttribute* attr=attrs.next();
956 // Are we supposed to export it?
957 AAP wrapper(provs,attr->getName(),attr->getNamespace());
958 if (wrapper.fail() || !wrapper->getHeader())
961 Iterator<string> vals=attr->getSingleByteValues();
962 if (!strcmp(wrapper->getHeader(),"REMOTE_USER") && vals.hasNext()) {
963 char* principal=const_cast<char*>(vals.next().c_str());
964 pn->SetHeader(pfc,"remote-user:",principal);
965 pfc->pFilterContext=pfc->AllocMem(pfc,strlen(principal)+1,0);
966 if (pfc->pFilterContext)
967 strcpy(static_cast<char*>(pfc->pFilterContext),principal);
972 string hname=string(wrapper->getHeader()) + ':';
973 GetHeader(pn,pfc,const_cast<char*>(hname.c_str()),buf,256,false);
978 for (; vals.hasNext(); it++) {
979 string value = vals.next();
980 for (string::size_type pos = value.find_first_of(";", string::size_type(0));
982 pos = value.find_first_of(";", pos)) {
983 value.insert(pos, "\\");
989 header=header + ';' + value;
991 pn->SetHeader(pfc,const_cast<char*>(hname.c_str()),const_cast<char*>(header.c_str()));
998 for (int k = 0; k < assertions.size(); k++)
999 delete assertions[k];
1000 delete sso_statement;
1002 return SF_STATUS_REQ_NEXT_NOTIFICATION;
1005 return WriteClientError(pfc,"Out of Memory");
1008 if (e==ERROR_NO_DATA)
1009 return WriteClientError(pfc,"A required variable or header was empty.");
1011 return WriteClientError(pfc,"Server detected unexpected IIS error.");
1015 return WriteClientError(pfc,"Server caught an unknown exception.");
1019 return WriteClientError(pfc,"Server reached unreachable code, save my walrus!");
1023 /****************************************************************************/
1026 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* msg)
1028 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
1029 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
1030 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
1031 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Error</TITLE></HEAD><BODY><H1>Shibboleth Error</H1>";
1032 DWORD resplen=strlen(xmsg);
1033 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg,&resplen,HSE_IO_SYNC);
1034 resplen=strlen(msg);
1035 lpECB->WriteClient(lpECB->ConnID,(LPVOID)msg,&resplen,HSE_IO_SYNC);
1036 static const char* xmsg2="</BODY></HTML>";
1037 resplen=strlen(xmsg2);
1038 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg2,&resplen,HSE_IO_SYNC);
1039 return HSE_STATUS_SUCCESS;
1043 class ShibTargetIsapiE : public ShibTarget
1045 LPEXTENSION_CONTROL_BLOCK m_lpECB;
1049 ShibTargetIsapiE(LPEXTENSION_CONTROL_BLOCK lpECB, const site_t& site) {
1051 GetServerVariable(lpECB,"HTTPS",ssl,5);
1052 bool SSL=(ssl=="on" || ssl=="ON");
1054 // URL path always come from IIS.
1056 GetServerVariable(lpECB,"URL",url,255);
1058 // Port may come from IIS or from site def.
1060 if (!g_bNormalizeRequest || (SSL && site.m_sslport.empty()) || (!SSL && site.m_port.empty()))
1061 GetServerVariable(lpECB,"SERVER_PORT",port,10);
1063 strncpy(port,site.m_sslport.c_str(),10);
1064 static_cast<char*>(port)[10]=0;
1067 strncpy(port,site.m_port.c_str(),10);
1068 static_cast<char*>(port)[10]=0;
1071 // Scheme may come from site def or be derived from IIS.
1072 const char* scheme=site.m_scheme.c_str();
1073 if (!scheme || !*scheme || !g_bNormalizeRequest) {
1074 scheme = SSL ? "https" : "http";
1077 // Get the other server variables.
1078 dynabuf remote_addr(16),hostname(32);
1079 GetServerVariable(lpECB, "REMOTE_ADDR", remote_addr, 16);
1080 GetServerVariable(lpECB, "SERVER_NAME", hostname, 32);
1082 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
1083 const char* host=hostname;
1084 if (site.m_name!=host && site.m_aliases.find(host)==site.m_aliases.end())
1085 host=site.m_name.c_str();
1088 * IIS screws us over on PATH_INFO (the hits keep on coming). We need to figure out if
1089 * the server is set up for proper PATH_INFO handling, or "IIS sucks rabid weasels mode",
1090 * which is the default. No perfect way to tell, but we can take a good guess by checking
1091 * whether the URL is a substring of the PATH_INFO:
1093 * e.g. for /Shibboleth.sso/SAML/POST
1095 * Bad mode (default):
1096 * URL: /Shibboleth.sso
1097 * PathInfo: /Shibboleth.sso/SAML/POST
1100 * URL: /Shibboleth.sso
1101 * PathInfo: /SAML/POST
1106 // Clearly we're only in bad mode if path info exists at all.
1107 if (lpECB->lpszPathInfo && *(lpECB->lpszPathInfo)) {
1108 if (strstr(lpECB->lpszPathInfo,url))
1109 // Pretty good chance we're in bad mode, unless the PathInfo repeats the path itself.
1110 fullurl=lpECB->lpszPathInfo;
1113 fullurl+=lpECB->lpszPathInfo;
1117 // For consistency with Apache, let's add the query string.
1118 if (lpECB->lpszQueryString && *(lpECB->lpszQueryString)) {
1120 fullurl+=lpECB->lpszQueryString;
1122 init(scheme, host, atoi(port), fullurl.c_str(), lpECB->lpszContentType, remote_addr, lpECB->lpszMethod);
1126 ~ShibTargetIsapiE() { }
1128 virtual void log(ShibLogLevel level, const string &msg) {
1129 ShibTarget::log(level,msg);
1130 if (level == LogLevelError)
1131 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
1133 virtual string getCookies() const {
1135 GetServerVariable(m_lpECB, "HTTP_COOKIE", buf, 128, false);
1136 return buf.empty() ? "" : buf;
1138 virtual void setCookie(const string &name, const string &value) {
1139 // Set the cookie for later. Use it during the redirect.
1140 m_cookie += "Set-Cookie: " + name + "=" + value + "\r\n";
1142 virtual string getArgs(void) {
1143 return string(m_lpECB->lpszQueryString ? m_lpECB->lpszQueryString : "");
1145 virtual string getPostData(void) {
1146 if (m_lpECB->cbTotalBytes > 1024*1024) // 1MB?
1147 throw FatalProfileException("Blocked too-large a submission to profile endpoint.");
1148 else if (m_lpECB->cbTotalBytes != m_lpECB->cbAvailable) {
1151 DWORD datalen=m_lpECB->cbTotalBytes;
1154 BOOL ret = m_lpECB->ReadClient(m_lpECB->ConnID, buf, &buflen);
1155 if (!ret || !buflen)
1156 throw FatalProfileException("Error reading profile submission from browser.");
1157 cgistr.append(buf, buflen);
1163 return string(reinterpret_cast<char*>(m_lpECB->lpbData),m_lpECB->cbAvailable);
1165 virtual void* sendPage(
1168 const string& content_type="text/html",
1169 const Iterator<header_t>& headers=EMPTY(header_t)) {
1170 string hdr = string ("Connection: close\r\nContent-type: ") + content_type + "\r\n";
1171 for (int k = 0; k < headers.size(); k++) {
1172 hdr += headers[k].first + ": " + headers[k].second + "\r\n";
1175 const char* codestr="200 OK";
1177 case 403: codestr="403 Forbidden"; break;
1178 case 404: codestr="404 Not Found"; break;
1179 case 500: codestr="500 Server Error"; break;
1181 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, (void*)codestr, 0, (LPDWORD)hdr.c_str());
1182 DWORD resplen = msg.size();
1183 m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)msg.c_str(), &resplen, HSE_IO_SYNC);
1184 return (void*)HSE_STATUS_SUCCESS;
1186 virtual void* sendRedirect(const string& url) {
1187 // XXX: Don't support the httpRedirect option, yet.
1188 string hdrs = m_cookie + "Location: " + url + "\r\n"
1189 "Content-Type: text/html\r\n"
1190 "Content-Length: 40\r\n"
1191 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
1192 "Cache-Control: private,no-store,no-cache\r\n\r\n";
1193 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER,
1194 "302 Moved", 0, (LPDWORD)hdrs.c_str());
1195 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
1197 m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)redmsg, &resplen, HSE_IO_SYNC);
1198 return (void*)HSE_STATUS_SUCCESS;
1200 // Decline happens in the POST processor if this isn't the shire url
1201 // Note that it can also happen with HTAccess, but we don't support that, yet.
1202 virtual void* returnDecline(void) {
1204 WriteClientError(m_lpECB, "ISAPI extension can only be invoked to process Shibboleth protocol requests."
1205 "Make sure the mapped file extension doesn't match actual content.");
1207 virtual void* returnOK(void) { return (void*) HSE_STATUS_SUCCESS; }
1209 // Not used in the extension.
1210 virtual void clearHeader(const string &name) { throw runtime_error("clearHeader not implemented"); }
1211 virtual void setHeader(const string &name, const string &value) { throw runtime_error("setHeader not implemented"); }
1212 virtual string getHeader(const string &name) { throw runtime_error("getHeader not implemented"); }
1213 virtual void setRemoteUser(const string &user) { throw runtime_error("setRemoteUser not implemented"); }
1214 virtual string getRemoteUser(void) { throw runtime_error("getRemoteUser not implemented"); }
1217 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
1220 const IApplication* application=NULL;
1222 ostringstream threadid;
1223 threadid << "[" << getpid() << "] isapi_shib_extension" << '\0';
1224 saml::NDC ndc(threadid.str().c_str());
1226 // Determine web site number. This can't really fail, I don't think.
1228 GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
1230 // Match site instance to host name, skip if no match.
1231 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
1232 if (map_i==g_Sites.end())
1233 return WriteClientError(lpECB, "Shibboleth Extension not configured for this web site.");
1235 ShibTargetIsapiE ste(lpECB, map_i->second);
1236 pair<bool,void*> res = ste.doHandler();
1237 if (res.first) return (DWORD)res.second;
1239 return WriteClientError(lpECB, "Shibboleth Extension failed to process request");
1243 return WriteClientError(lpECB,"Out of Memory");
1246 if (e==ERROR_NO_DATA)
1247 return WriteClientError(lpECB,"A required variable or header was empty.");
1249 return WriteClientError(lpECB,"Server detected unexpected IIS error.");
1253 return WriteClientError(lpECB,"Server caught an unknown exception.");
1257 // If we get here we've got an error.
1258 return HSE_STATUS_ERROR;
1262 IRequestMapper::Settings map_request(
1263 LPEXTENSION_CONTROL_BLOCK lpECB, IRequestMapper* mapper, const site_t& site, string& target
1267 GetServerVariable(lpECB,"HTTPS",ssl,5);
1268 bool SSL=(ssl=="on" || ssl=="ON");
1270 // URL path always come from IIS.
1272 GetServerVariable(lpECB,"URL",url,255);
1274 // Port may come from IIS or from site def.
1276 if (site.m_port.empty() || !g_bNormalizeRequest)
1277 GetServerVariable(lpECB,"SERVER_PORT",port,10);
1279 strncpy(port,site.m_port.c_str(),10);
1280 static_cast<char*>(port)[10]=0;
1283 // Scheme may come from site def or be derived from IIS.
1284 const char* scheme=site.m_scheme.c_str();
1285 if (!scheme || !*scheme || !g_bNormalizeRequest) {
1286 scheme = SSL ? "https" : "http";
1289 // Start with scheme and hostname.
1290 if (g_bNormalizeRequest) {
1291 target = string(scheme) + "://" + site.m_name;
1295 GetServerVariable(lpECB,"SERVER_NAME",name,64);
1296 target = string(scheme) + "://" + static_cast<char*>(name);
1299 // If port is non-default, append it.
1300 if ((!strcmp(scheme,"http") && port!="80") || (!strcmp(scheme,"https") && port!="443"))
1301 target = target + ':' + static_cast<char*>(port);
1305 target+=static_cast<char*>(url);
1307 return mapper->getSettingsFromParsedURL(scheme,site.m_name.c_str(),strtoul(port,NULL,10),url);
1310 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const IApplication* app, const char* page, ShibMLP& mlp)
1312 const IPropertySet* props=app->getPropertySet("Errors");
1314 pair<bool,const char*> p=props->getString(page);
1316 ifstream infile(p.second);
1317 if (!infile.fail()) {
1318 const char* res = mlp.run(infile,props);
1320 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
1321 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
1322 DWORD resplen=strlen(res);
1323 lpECB->WriteClient(lpECB->ConnID,(LPVOID)res,&resplen,0);
1324 return HSE_STATUS_SUCCESS;
1329 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Extension unable to open error template.");
1330 return WriteClientError(lpECB,"Unable to open error template, check settings.");
1333 DWORD WriteRedirectPage(LPEXTENSION_CONTROL_BLOCK lpECB, const IApplication* app, const char* file, ShibMLP& mlp, const char* headers=NULL)
1335 ifstream infile(file);
1336 if (!infile.fail()) {
1337 const char* res = mlp.run(infile,app->getPropertySet("Errors"));
1340 sprintf(buf,"Content-Length: %u\r\nContent-Type: text/html\r\n\r\n",strlen(res));
1344 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)h.c_str());
1347 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)buf);
1348 DWORD resplen=strlen(res);
1349 lpECB->WriteClient(lpECB->ConnID,(LPVOID)res,&resplen,0);
1350 return HSE_STATUS_SUCCESS;
1353 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Extension unable to open redirect template.");
1354 return WriteClientError(lpECB,"Unable to open redirect template, check settings.");
1357 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
1360 const IApplication* application=NULL;
1363 ostringstream threadid;
1364 threadid << "[" << getpid() << "] shire_handler" << '\0';
1365 saml::NDC ndc(threadid.str().c_str());
1367 // Determine web site number. This can't really fail, I don't think.
1369 GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
1371 // Match site instance to host name, skip if no match.
1372 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
1373 if (map_i==g_Sites.end())
1374 return WriteClientError(lpECB,"Shibboleth filter not configured for this web site.");
1376 // We lock the configuration system for the duration.
1377 IConfig* conf=g_Config->getINI();
1378 Locker locker(conf);
1380 // Map request to application and content settings.
1381 IRequestMapper* mapper=conf->getRequestMapper();
1382 Locker locker2(mapper);
1383 IRequestMapper::Settings settings=map_request(lpECB,mapper,map_i->second,targeturl);
1384 pair<bool,const char*> application_id=settings.first->getString("applicationId");
1385 application=conf->getApplication(application_id.second);
1386 const IPropertySet* sessionProps=application ? application->getPropertySet("Sessions") : NULL;
1387 if (!application || !sessionProps)
1388 return WriteClientError(lpECB,"Unable to map request to application session settings, check configuration.");
1390 SHIRE shire(application);
1392 const char* shireURL=shire.getShireURL(targeturl.c_str());
1394 return WriteClientError(lpECB,"Unable to map request to proper shireURL setting, check configuration.");
1396 // Make sure we only process the SHIRE requests.
1397 if (!strstr(targeturl.c_str(),shireURL))
1398 return WriteClientError(lpECB,"ISAPI extension can only be invoked to process incoming sessions."
1399 "Make sure the mapped file extension doesn't match actual content.");
1401 pair<const char*,const char*> shib_cookie=shire.getCookieNameProps();
1403 // Make sure this is SSL, if it should be
1404 pair<bool,bool> shireSSL=sessionProps->getBool("shireSSL");
1405 if (!shireSSL.first || shireSSL.second) {
1406 GetServerVariable(lpECB,"HTTPS",buf,10);
1408 throw ShibTargetException(SHIBRPC_OK,"blocked non-SSL access to SHIRE POST processor");
1411 pair<bool,bool> httpRedirects=sessionProps->getBool("httpRedirects");
1412 pair<bool,const char*> redirectPage=sessionProps->getString("redirectPage");
1413 if (httpRedirects.first && !httpRedirects.second && !redirectPage.first)
1414 return WriteClientError(lpECB,"HTML-based redirection requires a redirectPage property.");
1416 // Check for Mac web browser
1420 GetServerVariable(lpECB,"HTTP_USER_AGENT",agent,64);
1421 if (strstr(agent,"AppleWebKit/"))
1425 // If this is a GET, we manufacture an AuthnRequest.
1426 if (!stricmp(lpECB->lpszMethod,"GET")) {
1427 const char* areq=lpECB->lpszQueryString ? shire.getLazyAuthnRequest(lpECB->lpszQueryString) : NULL;
1429 throw ShibTargetException(SHIBRPC_OK, "malformed arguments to request a new session");
1430 if (!httpRedirects.first || httpRedirects.second) {
1431 string hdrs=string("Location: ") + areq + "\r\n"
1432 "Content-Type: text/html\r\n"
1433 "Content-Length: 40\r\n"
1434 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
1435 "Cache-Control: private,no-store,no-cache\r\n\r\n";
1436 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"302 Moved",0,(LPDWORD)hdrs.c_str());
1437 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
1439 lpECB->WriteClient(lpECB->ConnID,(LPVOID)redmsg,&resplen,HSE_IO_SYNC);
1440 return HSE_STATUS_SUCCESS;
1443 ShibMLP markupProcessor;
1444 markupProcessor.insert("requestURL",areq);
1445 return WriteRedirectPage(lpECB, application, redirectPage.second, markupProcessor);
1448 else if (stricmp(lpECB->lpszMethod,"POST"))
1449 throw ShibTargetException(SHIBRPC_OK,"blocked non-POST to SHIRE POST processor");
1451 // Sure sure this POST is an appropriate content type
1452 if (!lpECB->lpszContentType || stricmp(lpECB->lpszContentType,"application/x-www-form-urlencoded"))
1453 throw ShibTargetException(SHIBRPC_OK,"blocked bad content-type to SHIRE POST processor");
1456 pair<const char*,const char*> elements=pair<const char*,const char*>(NULL,NULL);
1457 if (lpECB->cbTotalBytes > 1024*1024) // 1MB?
1458 throw ShibTargetException(SHIBRPC_OK,"blocked too-large a post to SHIRE POST processor");
1459 else if (lpECB->cbTotalBytes!=lpECB->cbAvailable) {
1462 DWORD datalen=lpECB->cbTotalBytes;
1465 BOOL ret=lpECB->ReadClient(lpECB->ConnID,buf,&buflen);
1466 if (!ret || !buflen)
1467 throw ShibTargetException(SHIBRPC_OK,"error reading POST data from browser");
1468 cgistr.append(buf,buflen);
1471 elements=shire.getFormSubmission(cgistr.c_str(),cgistr.length());
1474 elements=shire.getFormSubmission(reinterpret_cast<char*>(lpECB->lpbData),lpECB->cbAvailable);
1476 // Make sure the SAML Response parameter exists
1477 if (!elements.first || !*elements.first)
1478 throw ShibTargetException(SHIBRPC_OK, "SHIRE POST failed to find SAMLResponse form element");
1480 // Make sure the target parameter exists
1481 if (!elements.second || !*elements.second)
1482 throw ShibTargetException(SHIBRPC_OK, "SHIRE POST failed to find TARGET form element");
1484 GetServerVariable(lpECB,"REMOTE_ADDR",buf,16);
1486 // Process the post.
1488 RPCError* status=NULL;
1489 ShibMLP markupProcessor;
1490 markupProcessor.insert("requestURL", targeturl.c_str());
1492 status = shire.sessionCreate(elements.first,buf,cookie);
1494 catch (ShibTargetException &e) {
1495 markupProcessor.insert("errorType", "Session Creation Service Error");
1496 markupProcessor.insert("errorText", e.what());
1497 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
1498 return WriteClientError(lpECB, application, "shire", markupProcessor);
1502 markupProcessor.insert("errorType", "Session Creation Service Error");
1503 markupProcessor.insert("errorText", "Unexpected Exception");
1504 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
1505 return WriteClientError(lpECB, application, "shire", markupProcessor);
1509 if (status->isError()) {
1510 if (status->isRetryable()) {
1512 const char* loc=shire.getAuthnRequest(elements.second);
1513 if (!httpRedirects.first || httpRedirects.second) {
1514 string hdrs=string("Location: ") + loc + "\r\n"
1515 "Content-Type: text/html\r\n"
1516 "Content-Length: 40\r\n"
1517 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
1518 "Cache-Control: private,no-store,no-cache\r\n\r\n";
1519 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"302 Moved",0,(LPDWORD)hdrs.c_str());
1520 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
1522 lpECB->WriteClient(lpECB->ConnID,(LPVOID)redmsg,&resplen,HSE_IO_SYNC);
1523 return HSE_STATUS_SUCCESS;
1526 markupProcessor.insert("requestURL",loc);
1527 return WriteRedirectPage(lpECB, application, redirectPage.second, markupProcessor);
1531 // Return this error to the user.
1532 markupProcessor.insert(*status);
1534 return WriteClientError(lpECB,application,"shire",markupProcessor);
1538 // We've got a good session, set the cookie and redirect to target.
1539 cookie = string("Set-Cookie: ") + shib_cookie.first + '=' + cookie + shib_cookie.second + "\r\n"
1540 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
1541 "Cache-Control: private,no-store,no-cache\r\n";
1542 if (!httpRedirects.first || httpRedirects.second) {
1543 cookie=cookie + "Content-Type: text/html\r\nLocation: " + elements.second + "\r\nContent-Length: 40\r\n\r\n";
1544 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"302 Moved",0,(LPDWORD)cookie.c_str());
1545 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
1547 lpECB->WriteClient(lpECB->ConnID,(LPVOID)redmsg,&resplen,HSE_IO_SYNC);
1548 return HSE_STATUS_SUCCESS;
1551 markupProcessor.insert("requestURL",elements.second);
1552 return WriteRedirectPage(lpECB, application, redirectPage.second, markupProcessor, cookie.c_str());
1555 catch (ShibTargetException &e) {
1557 ShibMLP markupProcessor;
1558 markupProcessor.insert("requestURL", targeturl.c_str());
1559 markupProcessor.insert("errorType", "Session Creation Service Error");
1560 markupProcessor.insert("errorText", e.what());
1561 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
1562 return WriteClientError(lpECB,application,"shire",markupProcessor);
1568 ShibMLP markupProcessor;
1569 markupProcessor.insert("requestURL", targeturl.c_str());
1570 markupProcessor.insert("errorType", "Session Creation Service Error");
1571 markupProcessor.insert("errorText", "Unexpected Exception");
1572 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
1573 return WriteClientError(lpECB,application,"shire",markupProcessor);
1578 return HSE_STATUS_ERROR;