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;
82 static const XMLCh name[] = { chLatin_n, chLatin_a, chLatin_m, chLatin_e, chNull };
83 static const XMLCh port[] = { chLatin_p, chLatin_o, chLatin_r, chLatin_t, chNull };
84 static const XMLCh sslport[] = { chLatin_s, chLatin_s, chLatin_l, chLatin_p, chLatin_o, chLatin_r, chLatin_t, chNull };
85 static const XMLCh scheme[] = { chLatin_s, chLatin_c, chLatin_h, chLatin_e, chLatin_m, chLatin_e, chNull };
86 static const XMLCh id[] = { chLatin_i, chLatin_d, chNull };
87 static const XMLCh Implementation[] =
88 { 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 };
89 static const XMLCh ISAPI[] = { chLatin_I, chLatin_S, chLatin_A, chLatin_P, chLatin_I, chNull };
90 static const XMLCh Alias[] = { chLatin_A, chLatin_l, chLatin_i, chLatin_a, chLatin_s, chNull };
91 static const XMLCh normalizeRequest[] =
92 { chLatin_n, chLatin_o, chLatin_r, chLatin_m, chLatin_a, chLatin_l, chLatin_i, chLatin_z, chLatin_e,
93 chLatin_R, chLatin_e, chLatin_q, chLatin_u, chLatin_e, chLatin_s, chLatin_t, chNull
95 static const XMLCh Site[] = { chLatin_S, chLatin_i, chLatin_t, chLatin_e, chNull };
98 site_t(const DOMElement* e)
100 auto_ptr_char n(e->getAttributeNS(NULL,name));
101 auto_ptr_char s(e->getAttributeNS(NULL,scheme));
102 auto_ptr_char p(e->getAttributeNS(NULL,port));
103 auto_ptr_char p2(e->getAttributeNS(NULL,sslport));
104 if (n.get()) m_name=n.get();
105 if (s.get()) m_scheme=s.get();
106 if (p.get()) m_port=p.get();
107 if (p2.get()) m_sslport=p2.get();
108 DOMNodeList* nlist=e->getElementsByTagNameNS(shibtarget::XML::SHIBTARGET_NS,Alias);
109 for (int i=0; nlist && i<nlist->getLength(); i++) {
110 if (nlist->item(i)->hasChildNodes()) {
111 auto_ptr_char alias(nlist->item(i)->getFirstChild()->getNodeValue());
112 m_aliases.insert(alias.get());
116 string m_scheme,m_port,m_sslport,m_name;
117 set<string> m_aliases;
120 HINSTANCE g_hinstDLL;
121 ShibTargetConfig* g_Config = NULL;
122 map<string,site_t> g_Sites;
123 bool g_bNormalizeRequest = true;
127 LPCSTR lpUNCServerName,
133 LPCSTR messages[] = {message, NULL};
135 HANDLE hElog = RegisterEventSource(lpUNCServerName, "Shibboleth ISAPI Filter");
136 BOOL res = ReportEvent(hElog, wType, 0, dwEventID, lpUserSid, 1, 0, messages, NULL);
137 return (DeregisterEventSource(hElog) && res);
140 extern "C" __declspec(dllexport) BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID)
142 if (fdwReason==DLL_PROCESS_ATTACH)
147 extern "C" BOOL WINAPI GetExtensionVersion(HSE_VERSION_INFO* pVer)
154 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
155 "Extension mode startup not possible, is the DLL loaded as a filter?");
159 pVer->dwExtensionVersion=HSE_VERSION;
160 strncpy(pVer->lpszExtensionDesc,"Shibboleth ISAPI Extension",HSE_MAX_EXT_DLL_NAME_LEN-1);
164 extern "C" BOOL WINAPI TerminateExtension(DWORD)
166 return TRUE; // cleanup should happen when filter unloads
169 extern "C" BOOL WINAPI GetFilterVersion(PHTTP_FILTER_VERSION pVer)
174 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
175 "Reentrant filter initialization, ignoring...");
183 LPCSTR schemadir=getenv("SHIBSCHEMAS");
185 schemadir=SHIB_SCHEMAS;
186 LPCSTR config=getenv("SHIBCONFIG");
189 g_Config=&ShibTargetConfig::getConfig();
190 g_Config->setFeatures(
191 ShibTargetConfig::Listener |
192 ShibTargetConfig::Metadata |
193 ShibTargetConfig::AAP |
194 ShibTargetConfig::RequestMapper |
195 ShibTargetConfig::LocalExtensions |
196 ShibTargetConfig::Logging
198 if (!g_Config->init(schemadir) || !g_Config->load(config)) {
200 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
201 "Filter startup failed during initialization, check shire log for help.");
205 // Access the implementation-specifics for site mappings.
206 IConfig* conf=g_Config->getINI();
208 const IPropertySet* props=conf->getPropertySet("Local");
210 const DOMElement* impl=saml::XML::getFirstChildElement(
211 props->getElement(),shibtarget::XML::SHIBTARGET_NS,Implementation
213 if (impl && (impl=saml::XML::getFirstChildElement(impl,shibtarget::XML::SHIBTARGET_NS,ISAPI))) {
214 const XMLCh* flag=impl->getAttributeNS(NULL,normalizeRequest);
215 g_bNormalizeRequest=(!flag || !*flag || *flag==chDigit_1 || *flag==chLatin_t);
216 impl=saml::XML::getFirstChildElement(impl,shibtarget::XML::SHIBTARGET_NS,Site);
218 auto_ptr_char id(impl->getAttributeNS(NULL,id));
220 g_Sites.insert(pair<string,site_t>(id.get(),site_t(impl)));
221 impl=saml::XML::getNextSiblingElement(impl,shibtarget::XML::SHIBTARGET_NS,Site);
229 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Filter startup failed with an exception.");
234 pVer->dwFilterVersion=HTTP_FILTER_REVISION;
235 strncpy(pVer->lpszFilterDesc,"Shibboleth ISAPI Filter",SF_MAX_FILTER_DESC_LEN);
236 pVer->dwFlags=(SF_NOTIFY_ORDER_HIGH |
237 SF_NOTIFY_SECURE_PORT |
238 SF_NOTIFY_NONSECURE_PORT |
239 SF_NOTIFY_PREPROC_HEADERS |
241 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter initialized...");
245 extern "C" BOOL WINAPI TerminateFilter(DWORD)
248 g_Config->shutdown();
250 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter shut down...");
254 /* Next up, some suck-free versions of various APIs.
256 You DON'T require people to guess the buffer size and THEN tell them the right size.
257 Returning an LPCSTR is apparently way beyond their ken. Not to mention the fact that
258 constant strings aren't typed as such, making it just that much harder. These versions
259 are now updated to use a special growable buffer object, modeled after the standard
260 string class. The standard string won't work because they left out the option to
261 pre-allocate a non-constant buffer.
267 dynabuf() { bufptr=NULL; buflen=0; }
268 dynabuf(size_t s) { bufptr=new char[buflen=s]; *bufptr=0; }
269 ~dynabuf() { delete[] bufptr; }
270 size_t length() const { return bufptr ? strlen(bufptr) : 0; }
271 size_t size() const { return buflen; }
272 bool empty() const { return length()==0; }
273 void reserve(size_t s, bool keep=false);
274 void erase() { if (bufptr) memset(bufptr,0,buflen); }
275 operator char*() { return bufptr; }
276 bool operator ==(const char* s) const;
277 bool operator !=(const char* s) const { return !(*this==s); }
283 void dynabuf::reserve(size_t s, bool keep)
290 p[buflen]=bufptr[buflen];
296 bool dynabuf::operator==(const char* s) const
298 if (buflen==NULL || s==NULL)
299 return (buflen==NULL && s==NULL);
301 return strcmp(bufptr,s)==0;
304 void GetServerVariable(PHTTP_FILTER_CONTEXT pfc, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
305 throw (bad_alloc, DWORD)
311 while (!pfc->GetServerVariable(pfc,lpszVariable,s,&size))
313 // Grumble. Check the error.
314 DWORD e=GetLastError();
315 if (e==ERROR_INSUFFICIENT_BUFFER)
320 if (bRequired && s.empty())
324 void GetServerVariable(LPEXTENSION_CONTROL_BLOCK lpECB, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
325 throw (bad_alloc, DWORD)
331 while (!lpECB->GetServerVariable(lpECB->ConnID,lpszVariable,s,&size))
333 // Grumble. Check the error.
334 DWORD e=GetLastError();
335 if (e==ERROR_INSUFFICIENT_BUFFER)
340 if (bRequired && s.empty())
344 void GetHeader(PHTTP_FILTER_PREPROC_HEADERS pn, PHTTP_FILTER_CONTEXT pfc,
345 LPSTR lpszName, dynabuf& s, DWORD size=80, bool bRequired=true)
346 throw (bad_alloc, DWORD)
352 while (!pn->GetHeader(pfc,lpszName,s,&size))
354 // Grumble. Check the error.
355 DWORD e=GetLastError();
356 if (e==ERROR_INSUFFICIENT_BUFFER)
361 if (bRequired && s.empty())
365 /****************************************************************************/
368 class ShibTargetIsapiF : public ShibTarget
370 PHTTP_FILTER_CONTEXT m_pfc;
371 PHTTP_FILTER_PREPROC_HEADERS m_pn;
375 ShibTargetIsapiF(PHTTP_FILTER_CONTEXT pfc, PHTTP_FILTER_PREPROC_HEADERS pn, const site_t& site)
376 : m_log(&Category::getInstance("isapi_shib"))
379 // URL path always come from IIS.
381 GetHeader(pn,pfc,"url",url,256,false);
383 // Port may come from IIS or from site def.
385 if (!g_bNormalizeRequest || (pfc->fIsSecurePort && site.m_sslport.empty()) || (!pfc->fIsSecurePort && site.m_port.empty()))
386 GetServerVariable(pfc,"SERVER_PORT",port,10);
387 else if (pfc->fIsSecurePort) {
388 strncpy(port,site.m_sslport.c_str(),10);
389 static_cast<char*>(port)[10]=0;
392 strncpy(port,site.m_port.c_str(),10);
393 static_cast<char*>(port)[10]=0;
396 // Scheme may come from site def or be derived from IIS.
397 const char* scheme=site.m_scheme.c_str();
398 if (!scheme || !*scheme || !g_bNormalizeRequest)
399 scheme=pfc->fIsSecurePort ? "https" : "http";
401 // Get the rest of the server variables.
402 dynabuf remote_addr(16),method(5),content_type(32),hostname(32);
403 GetServerVariable(pfc,"SERVER_NAME",hostname,32);
404 GetServerVariable(pfc,"REMOTE_ADDR",remote_addr,16);
405 GetServerVariable(pfc,"REQUEST_METHOD",method,5,false);
406 GetServerVariable(pfc,"CONTENT_TYPE",content_type,32,false);
408 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
409 const char* host=hostname;
410 if (site.m_name!=host && site.m_aliases.find(host)==site.m_aliases.end())
411 host=site.m_name.c_str();
413 init(scheme, host, atoi(port), url, content_type, remote_addr, method);
418 ~ShibTargetIsapiF() { }
420 virtual void log(ShibLogLevel level, const string &msg) {
421 if (level == LogLevelError)
422 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
424 (level == LogLevelDebug ? Priority::DEBUG :
425 (level == LogLevelInfo ? Priority::INFO :
426 (level == LogLevelWarn ? Priority::WARN : Priority::ERROR))),
430 virtual string getCookies() const {
432 GetHeader(m_pn, m_pfc, "Cookie:", buf, 128, false);
433 return buf.empty() ? "" : buf;
436 virtual void clearHeader(const string &name) {
437 string hdr = name + ":";
438 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), "");
440 virtual void setHeader(const string &name, const string &value) {
441 string hdr = name + ":";
442 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()),
443 const_cast<char*>(value.c_str()));
445 virtual string getHeader(const string &name) {
446 string hdr = name + ":";
448 GetHeader(m_pn, m_pfc, const_cast<char*>(hdr.c_str()), buf, 1024, false);
451 virtual void setRemoteUser(const string &user) {
452 setHeader(string("remote-user"), user);
454 virtual string getRemoteUser(void) {
455 return getHeader(string("remote-user"));
457 virtual void* sendPage(
460 const string& content_type="text/html",
461 const Iterator<header_t>& headers=EMPTY(header_t)) {
462 string hdr = string ("Connection: close\r\nContent-type: ") + content_type + "\r\n";
463 while (headers.hasNext()) {
464 const header_t& h=headers.next();
465 hdr += h.first + ": " + h.second + "\r\n";
468 const char* codestr="200 OK";
470 case 403: codestr="403 Forbidden"; break;
471 case 404: codestr="404 Not Found"; break;
472 case 500: codestr="500 Server Error"; break;
474 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, (void*)codestr, (DWORD)hdr.c_str(), 0);
475 DWORD resplen = msg.size();
476 m_pfc->WriteClient(m_pfc, (LPVOID)msg.c_str(), &resplen, 0);
477 return (void*)SF_STATUS_REQ_FINISHED;
479 virtual void* sendRedirect(const string& url) {
480 // XXX: Don't support the httpRedirect option, yet.
481 string hdrs=m_cookie + string("Location: ") + url + "\r\n"
482 "Content-Type: text/html\r\n"
483 "Content-Length: 40\r\n"
484 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
485 "Cache-Control: private,no-store,no-cache\r\n\r\n";
486 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER,
487 "302 Please Wait", (DWORD)hdrs.c_str(), 0);
488 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
490 m_pfc->WriteClient(m_pfc, (LPVOID)redmsg, &resplen, 0);
491 return reinterpret_cast<void*>(SF_STATUS_REQ_FINISHED);
493 // XXX: We might not ever hit the 'decline' status in this filter.
494 //virtual void* returnDecline(void) { }
495 virtual void* returnOK(void) { return (void*) SF_STATUS_REQ_NEXT_NOTIFICATION; }
497 // The filter never processes the POST, so stub these methods.
498 virtual void setCookie(const string &name, const string &value) {
499 // Set the cookie for later. Use it during the redirect.
500 m_cookie += "Set-Cookie: " + name + "=" + value + "\r\n";
502 virtual string getArgs(void) { throw runtime_error("getArgs not implemented"); }
503 virtual string getPostData(void) { throw runtime_error("getPostData not implemented"); }
506 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const char* msg)
508 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
509 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
510 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)ctype,0);
511 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Filter Error</TITLE></HEAD><BODY>"
512 "<H1>Shibboleth Filter Error</H1>";
513 DWORD resplen=strlen(xmsg);
514 pfc->WriteClient(pfc,(LPVOID)xmsg,&resplen,0);
516 pfc->WriteClient(pfc,(LPVOID)msg,&resplen,0);
517 static const char* xmsg2="</BODY></HTML>";
518 resplen=strlen(xmsg2);
519 pfc->WriteClient(pfc,(LPVOID)xmsg2,&resplen,0);
520 return SF_STATUS_REQ_FINISHED;
523 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
525 // Is this a log notification?
526 if (notificationType==SF_NOTIFY_LOG)
528 if (pfc->pFilterContext)
529 ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=static_cast<LPCSTR>(pfc->pFilterContext);
530 return SF_STATUS_REQ_NEXT_NOTIFICATION;
533 PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
536 // Determine web site number. This can't really fail, I don't think.
538 GetServerVariable(pfc,"INSTANCE_ID",buf,10);
540 // Match site instance to host name, skip if no match.
541 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
542 if (map_i==g_Sites.end())
543 return SF_STATUS_REQ_NEXT_NOTIFICATION;
545 ostringstream threadid;
546 threadid << "[" << getpid() << "] isapi_shib" << '\0';
547 saml::NDC ndc(threadid.str().c_str());
549 ShibTargetIsapiF stf(pfc, pn, map_i->second);
551 // "false" because we don't override the Shib settings
552 pair<bool,void*> res = stf.doCheckAuthN();
553 if (res.first) return (DWORD)res.second;
555 // "false" because we don't override the Shib settings
556 res = stf.doExportAssertions();
557 if (res.first) return (DWORD)res.second;
559 res = stf.doCheckAuthZ();
560 if (res.first) return (DWORD)res.second;
562 return SF_STATUS_REQ_NEXT_NOTIFICATION;
565 return WriteClientError(pfc,"Out of Memory");
568 if (e==ERROR_NO_DATA)
569 return WriteClientError(pfc,"A required variable or header was empty.");
571 return WriteClientError(pfc,"Server detected unexpected IIS error.");
575 return WriteClientError(pfc,"Server caught an unknown exception.");
579 return WriteClientError(pfc,"Server reached unreachable code, save my walrus!");
584 IRequestMapper::Settings map_request(
585 PHTTP_FILTER_CONTEXT pfc, PHTTP_FILTER_PREPROC_HEADERS pn, IRequestMapper* mapper, const site_t& site, string& target
588 // URL path always come from IIS.
590 GetHeader(pn,pfc,"url",url,256,false);
592 // Port may come from IIS or from site def.
594 if (site.m_port.empty() || !g_bNormalizeRequest)
595 GetServerVariable(pfc,"SERVER_PORT",port,10);
597 strncpy(port,site.m_port.c_str(),10);
598 static_cast<char*>(port)[10]=0;
601 // Scheme may come from site def or be derived from IIS.
602 const char* scheme=site.m_scheme.c_str();
603 if (!scheme || !*scheme || !g_bNormalizeRequest)
604 scheme=pfc->fIsSecurePort ? "https" : "http";
606 // Start with scheme and hostname.
607 if (g_bNormalizeRequest) {
608 target = string(scheme) + "://" + site.m_name;
612 GetServerVariable(pfc,"SERVER_NAME",name,64);
613 target = string(scheme) + "://" + static_cast<char*>(name);
616 // If port is non-default, append it.
617 if ((!strcmp(scheme,"http") && port!="80") || (!strcmp(scheme,"https") && port!="443"))
618 target = target + ':' + static_cast<char*>(port);
622 target+=static_cast<char*>(url);
624 return mapper->getSettingsFromParsedURL(scheme,site.m_name.c_str(),strtoul(port,NULL,10),url);
627 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const IApplication* app, const char* page, ShibMLP& mlp)
629 const IPropertySet* props=app->getPropertySet("Errors");
631 pair<bool,const char*> p=props->getString(page);
633 ifstream infile(p.second);
634 if (!infile.fail()) {
635 const char* res = mlp.run(infile,props);
637 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
638 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)ctype,0);
639 DWORD resplen=strlen(res);
640 pfc->WriteClient(pfc,(LPVOID)res,&resplen,0);
641 return SF_STATUS_REQ_FINISHED;
647 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Filter unable to open error template.");
648 return WriteClientError(pfc,"Unable to open error template, check settings.");
651 DWORD WriteRedirectPage(PHTTP_FILTER_CONTEXT pfc, const IApplication* app, const char* file, ShibMLP& mlp, const char* headers=NULL)
653 ifstream infile(file);
654 if (!infile.fail()) {
655 const char* res = mlp.run(infile,app->getPropertySet("Errors"));
658 sprintf(buf,"Content-Length: %u\r\nContent-Type: text/html\r\n\r\n",strlen(res));
662 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)h.c_str(),0);
665 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)buf,0);
666 DWORD resplen=strlen(res);
667 pfc->WriteClient(pfc,(LPVOID)res,&resplen,0);
668 return SF_STATUS_REQ_FINISHED;
671 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Extension unable to open redirect template.");
672 return WriteClientError(pfc,"Unable to open redirect template, check settings.");
675 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
677 // Is this a log notification?
678 if (notificationType==SF_NOTIFY_LOG)
680 if (pfc->pFilterContext)
681 ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=static_cast<LPCSTR>(pfc->pFilterContext);
682 return SF_STATUS_REQ_NEXT_NOTIFICATION;
685 PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
688 // Determine web site number. This can't really fail, I don't think.
690 GetServerVariable(pfc,"INSTANCE_ID",buf,10);
692 // Match site instance to host name, skip if no match.
693 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
694 if (map_i==g_Sites.end())
695 return SF_STATUS_REQ_NEXT_NOTIFICATION;
697 ostringstream threadid;
698 threadid << "[" << getpid() << "] isapi_shib" << '\0';
699 saml::NDC ndc(threadid.str().c_str());
701 // We lock the configuration system for the duration.
702 IConfig* conf=g_Config->getINI();
705 // Map request to application and content settings.
707 IRequestMapper* mapper=conf->getRequestMapper();
708 Locker locker2(mapper);
709 IRequestMapper::Settings settings=map_request(pfc,pn,mapper,map_i->second,targeturl);
710 pair<bool,const char*> application_id=settings.first->getString("applicationId");
711 const IApplication* application=conf->getApplication(application_id.second);
713 return WriteClientError(pfc,"Unable to map request to application settings, check configuration.");
715 // Declare SHIRE object for this request.
716 SHIRE shire(application);
718 const char* shireURL=shire.getShireURL(targeturl.c_str());
720 return WriteClientError(pfc,"Unable to map request to proper shireURL setting, check configuration.");
722 // If the user is accessing the SHIRE acceptance point, pass it on.
723 if (targeturl.find(shireURL)!=string::npos)
724 return SF_STATUS_REQ_NEXT_NOTIFICATION;
726 // Now check the policy for this request.
727 pair<bool,bool> requireSession=settings.first->getBool("requireSession");
728 pair<const char*,const char*> shib_cookie=shire.getCookieNameProps();
729 pair<bool,bool> httpRedirects=application->getPropertySet("Sessions")->getBool("httpRedirects");
730 pair<bool,const char*> redirectPage=application->getPropertySet("Sessions")->getString("redirectPage");
731 if (httpRedirects.first && !httpRedirects.second && !redirectPage.first)
732 return WriteClientError(pfc,"HTML-based redirection requires a redirectPage property.");
734 // Check for session cookie.
735 const char* session_id=NULL;
736 GetHeader(pn,pfc,"Cookie:",buf,128,false);
737 Category::getInstance("isapi_shib.HttpFilterProc").debug("cookie header is {%s}",(const char*)buf);
738 if (!buf.empty() && (session_id=strstr(buf,shib_cookie.first))) {
739 session_id+=strlen(shib_cookie.first) + 1; /* Skip over the '=' */
740 char* cookieend=strchr(session_id,';');
742 *cookieend = '\0'; /* Ignore anyting after a ; */
745 if (!session_id || !*session_id) {
746 // If no session required, bail now.
747 if (!requireSession.second)
748 return SF_STATUS_REQ_NEXT_NOTIFICATION;
750 // No acceptable cookie, and we require a session. Generate an AuthnRequest.
751 const char* areq = shire.getAuthnRequest(targeturl.c_str());
752 if (!httpRedirects.first || httpRedirects.second) {
753 string hdrs=string("Location: ") + areq + "\r\n"
754 "Content-Type: text/html\r\n"
755 "Content-Length: 40\r\n"
756 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
757 "Cache-Control: private,no-store,no-cache\r\n\r\n";
758 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"302 Please Wait",(DWORD)hdrs.c_str(),0);
759 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
761 pfc->WriteClient(pfc,(LPVOID)redmsg,&resplen,0);
762 return SF_STATUS_REQ_FINISHED;
765 ShibMLP markupProcessor;
766 markupProcessor.insert("requestURL",areq);
767 return WriteRedirectPage(pfc, application, redirectPage.second, markupProcessor);
771 // Make sure this session is still valid.
772 RPCError* status = NULL;
773 ShibMLP markupProcessor;
774 markupProcessor.insert("requestURL", targeturl);
777 GetServerVariable(pfc,"REMOTE_ADDR",abuf,16);
779 status = shire.sessionIsValid(session_id, abuf);
781 catch (ShibTargetException &e) {
782 markupProcessor.insert("errorType", "Session Processing Error");
783 markupProcessor.insert("errorText", e.what());
784 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
785 return WriteClientError(pfc, application, "shire", markupProcessor);
789 markupProcessor.insert("errorType", "Session Processing Error");
790 markupProcessor.insert("errorText", "Unexpected Exception");
791 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
792 return WriteClientError(pfc, application, "shire", markupProcessor);
797 if (status->isError()) {
798 if (!requireSession.second)
799 return SF_STATUS_REQ_NEXT_NOTIFICATION;
800 else if (status->isRetryable()) {
801 // Oops, session is invalid. Generate AuthnRequest.
803 const char* areq = shire.getAuthnRequest(targeturl.c_str());
804 if (!httpRedirects.first || httpRedirects.second) {
805 string hdrs=string("Location: ") + areq + "\r\n"
806 "Content-Type: text/html\r\n"
807 "Content-Length: 40\r\n"
808 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
809 "Cache-Control: private,no-store,no-cache\r\n\r\n";
810 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"302 Please Wait",(DWORD)hdrs.c_str(),0);
811 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
813 pfc->WriteClient(pfc,(LPVOID)redmsg,&resplen,0);
814 return SF_STATUS_REQ_FINISHED;
817 markupProcessor.insert("requestURL",areq);
818 return WriteRedirectPage(pfc, application, redirectPage.second, markupProcessor);
822 // return the error page to the user
823 markupProcessor.insert(*status);
825 return WriteClientError(pfc, application, "shire", markupProcessor);
832 vector<SAMLAssertion*> assertions;
833 SAMLAuthenticationStatement* sso_statement=NULL;
836 status = rm.getAssertions(session_id, abuf, assertions, &sso_statement);
838 catch (ShibTargetException &e) {
839 markupProcessor.insert("errorType", "Attribute Processing Error");
840 markupProcessor.insert("errorText", e.what());
841 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
842 return WriteClientError(pfc, application, "rm", markupProcessor);
846 markupProcessor.insert("errorType", "Attribute Processing Error");
847 markupProcessor.insert("errorText", "Unexpected Exception");
848 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
849 return WriteClientError(pfc, application, "rm", markupProcessor);
853 if (status->isError()) {
854 markupProcessor.insert(*status);
856 return WriteClientError(pfc, application, "rm", markupProcessor);
860 // Do we have an access control plugin?
861 if (settings.second) {
862 Locker acllock(settings.second);
863 if (!settings.second->authorized(*sso_statement,assertions)) {
864 for (int k = 0; k < assertions.size(); k++)
865 delete assertions[k];
866 delete sso_statement;
867 return WriteClientError(pfc, application, "access", markupProcessor);
871 // Get the AAP providers, which contain the attribute policy info.
872 Iterator<IAAP*> provs=application->getAAPProviders();
874 // Clear out the list of mapped attributes
875 while (provs.hasNext()) {
876 IAAP* aap=provs.next();
879 Iterator<const IAttributeRule*> rules=aap->getAttributeRules();
880 while (rules.hasNext()) {
881 const char* header=rules.next()->getHeader();
883 string hname=string(header) + ':';
884 pn->SetHeader(pfc,const_cast<char*>(hname.c_str()),"");
890 for (int k = 0; k < assertions.size(); k++)
891 delete assertions[k];
892 delete sso_statement;
893 markupProcessor.insert("errorType", "Attribute Processing Error");
894 markupProcessor.insert("errorText", "Unexpected Exception");
895 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
896 return WriteClientError(pfc, application, "rm", markupProcessor);
902 // Maybe export the first assertion.
903 pn->SetHeader(pfc,"remote-user:","");
904 pn->SetHeader(pfc,"Shib-Attributes:","");
905 pair<bool,bool> exp=settings.first->getBool("exportAssertion");
906 if (exp.first && exp.second && assertions.size()) {
908 RM::serialize(*(assertions[0]), assertion);
909 string::size_type lfeed;
910 while ((lfeed=assertion.find('\n'))!=string::npos)
911 assertion.erase(lfeed,1);
912 pn->SetHeader(pfc,"Shib-Attributes:",const_cast<char*>(assertion.c_str()));
915 pn->SetHeader(pfc,"Shib-Origin-Site:","");
916 pn->SetHeader(pfc,"Shib-Authentication-Method:","");
917 pn->SetHeader(pfc,"Shib-NameIdentifier-Format:","");
919 // Export the SAML AuthnMethod and the origin site name.
920 auto_ptr_char os(sso_statement->getSubject()->getNameIdentifier()->getNameQualifier());
921 auto_ptr_char am(sso_statement->getAuthMethod());
922 pn->SetHeader(pfc,"Shib-Origin-Site:", const_cast<char*>(os.get()));
923 pn->SetHeader(pfc,"Shib-Authentication-Method:", const_cast<char*>(am.get()));
926 AAP wrapper(provs,sso_statement->getSubject()->getNameIdentifier()->getFormat(),Constants::SHIB_ATTRIBUTE_NAMESPACE_URI);
927 if (!wrapper.fail() && wrapper->getHeader()) {
928 auto_ptr_char form(sso_statement->getSubject()->getNameIdentifier()->getFormat());
929 auto_ptr_char nameid(sso_statement->getSubject()->getNameIdentifier()->getName());
930 pn->SetHeader(pfc,"Shib-NameIdentifier-Format:",const_cast<char*>(form.get()));
931 if (!strcmp(wrapper->getHeader(),"REMOTE_USER")) {
932 char* principal=const_cast<char*>(nameid.get());
933 pn->SetHeader(pfc,"remote-user:",principal);
934 pfc->pFilterContext=pfc->AllocMem(pfc,strlen(principal)+1,0);
935 if (pfc->pFilterContext)
936 strcpy(static_cast<char*>(pfc->pFilterContext),principal);
939 string hname=string(wrapper->getHeader()) + ':';
940 pn->SetHeader(pfc,const_cast<char*>(wrapper->getHeader()),const_cast<char*>(nameid.get()));
944 pn->SetHeader(pfc,"Shib-Application-ID:","");
945 pn->SetHeader(pfc,"Shib-Application-ID:",const_cast<char*>(application_id.second));
947 // Export the attributes.
948 Iterator<SAMLAssertion*> a_iter(assertions);
949 while (a_iter.hasNext()) {
950 SAMLAssertion* assert=a_iter.next();
951 Iterator<SAMLStatement*> statements=assert->getStatements();
952 while (statements.hasNext()) {
953 SAMLAttributeStatement* astate=dynamic_cast<SAMLAttributeStatement*>(statements.next());
956 Iterator<SAMLAttribute*> attrs=astate->getAttributes();
957 while (attrs.hasNext()) {
958 SAMLAttribute* attr=attrs.next();
960 // Are we supposed to export it?
961 AAP wrapper(provs,attr->getName(),attr->getNamespace());
962 if (wrapper.fail() || !wrapper->getHeader())
965 Iterator<string> vals=attr->getSingleByteValues();
966 if (!strcmp(wrapper->getHeader(),"REMOTE_USER") && vals.hasNext()) {
967 char* principal=const_cast<char*>(vals.next().c_str());
968 pn->SetHeader(pfc,"remote-user:",principal);
969 pfc->pFilterContext=pfc->AllocMem(pfc,strlen(principal)+1,0);
970 if (pfc->pFilterContext)
971 strcpy(static_cast<char*>(pfc->pFilterContext),principal);
976 string hname=string(wrapper->getHeader()) + ':';
977 GetHeader(pn,pfc,const_cast<char*>(hname.c_str()),buf,256,false);
982 for (; vals.hasNext(); it++) {
983 string value = vals.next();
984 for (string::size_type pos = value.find_first_of(";", string::size_type(0));
986 pos = value.find_first_of(";", pos)) {
987 value.insert(pos, "\\");
993 header=header + ';' + value;
995 pn->SetHeader(pfc,const_cast<char*>(hname.c_str()),const_cast<char*>(header.c_str()));
1002 for (int k = 0; k < assertions.size(); k++)
1003 delete assertions[k];
1004 delete sso_statement;
1006 return SF_STATUS_REQ_NEXT_NOTIFICATION;
1009 return WriteClientError(pfc,"Out of Memory");
1012 if (e==ERROR_NO_DATA)
1013 return WriteClientError(pfc,"A required variable or header was empty.");
1015 return WriteClientError(pfc,"Server detected unexpected IIS error.");
1019 return WriteClientError(pfc,"Server caught an unknown exception.");
1023 return WriteClientError(pfc,"Server reached unreachable code, save my walrus!");
1027 /****************************************************************************/
1030 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* msg)
1032 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
1033 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
1034 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
1035 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Error</TITLE></HEAD><BODY><H1>Shibboleth Error</H1>";
1036 DWORD resplen=strlen(xmsg);
1037 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg,&resplen,HSE_IO_SYNC);
1038 resplen=strlen(msg);
1039 lpECB->WriteClient(lpECB->ConnID,(LPVOID)msg,&resplen,HSE_IO_SYNC);
1040 static const char* xmsg2="</BODY></HTML>";
1041 resplen=strlen(xmsg2);
1042 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg2,&resplen,HSE_IO_SYNC);
1043 return HSE_STATUS_SUCCESS;
1047 class ShibTargetIsapiE : public ShibTarget
1049 LPEXTENSION_CONTROL_BLOCK m_lpECB;
1054 ShibTargetIsapiE(LPEXTENSION_CONTROL_BLOCK lpECB, const site_t& site)
1055 : m_log(&Category::getInstance("isapi_shib_filter"))
1058 GetServerVariable(lpECB,"HTTPS",ssl,5);
1059 bool SSL=(ssl=="on" || ssl=="ON");
1061 // URL path always come from IIS.
1063 GetServerVariable(lpECB,"URL",url,255);
1065 // Port may come from IIS or from site def.
1067 if (!g_bNormalizeRequest || (SSL && site.m_sslport.empty()) || (!SSL && site.m_port.empty()))
1068 GetServerVariable(lpECB,"SERVER_PORT",port,10);
1070 strncpy(port,site.m_sslport.c_str(),10);
1071 static_cast<char*>(port)[10]=0;
1074 strncpy(port,site.m_port.c_str(),10);
1075 static_cast<char*>(port)[10]=0;
1078 // Scheme may come from site def or be derived from IIS.
1079 const char* scheme=site.m_scheme.c_str();
1080 if (!scheme || !*scheme || !g_bNormalizeRequest) {
1081 scheme = SSL ? "https" : "http";
1084 // Get the other server variables.
1085 dynabuf remote_addr(16),hostname(32);
1086 GetServerVariable(lpECB, "REMOTE_ADDR", remote_addr, 16);
1087 GetServerVariable(lpECB, "SERVER_NAME", hostname, 32);
1089 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
1090 const char* host=hostname;
1091 if (site.m_name!=host && site.m_aliases.find(host)==site.m_aliases.end())
1092 host=site.m_name.c_str();
1095 * IIS screws us over on PATH_INFO (the hits keep on coming). We need to figure out if
1096 * the server is set up for proper PATH_INFO handling, or "IIS sucks rabid weasels mode",
1097 * which is the default. No perfect way to tell, but we can take a good guess by checking
1098 * whether the URL is a substring of the PATH_INFO:
1100 * e.g. for /Shibboleth.sso/SAML/POST
1102 * Bad mode (default):
1103 * URL: /Shibboleth.sso
1104 * PathInfo: /Shibboleth.sso/SAML/POST
1107 * URL: /Shibboleth.sso
1108 * PathInfo: /SAML/POST
1113 // Clearly we're only in bad mode if path info exists at all.
1114 if (lpECB->lpszPathInfo && *(lpECB->lpszPathInfo)) {
1115 if (strstr(lpECB->lpszPathInfo,url))
1116 // Pretty good chance we're in bad mode, unless the PathInfo repeats the path itself.
1117 fullurl=lpECB->lpszPathInfo;
1120 fullurl+=lpECB->lpszPathInfo;
1124 // For consistency with Apache, let's add the query string.
1125 if (lpECB->lpszQueryString && *(lpECB->lpszQueryString)) {
1127 fullurl+=lpECB->lpszQueryString;
1129 init(scheme, host, atoi(port), fullurl.c_str(), lpECB->lpszContentType, remote_addr, lpECB->lpszMethod);
1133 ~ShibTargetIsapiE() { }
1135 virtual void log(ShibLogLevel level, const string &msg) {
1136 if (level == LogLevelError)
1137 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
1139 (level == LogLevelDebug ? Priority::DEBUG :
1140 (level == LogLevelInfo ? Priority::INFO :
1141 (level == LogLevelWarn ? Priority::WARN : Priority::ERROR))),
1145 virtual string getCookies() const {
1147 GetServerVariable(m_lpECB, "HTTP_COOKIE", buf, 128, false);
1148 return buf.empty() ? "" : buf;
1150 virtual void setCookie(const string &name, const string &value) {
1151 // Set the cookie for later. Use it during the redirect.
1152 m_cookie += "Set-Cookie: " + name + "=" + value + "\r\n";
1154 virtual string getArgs(void) {
1155 return string(m_lpECB->lpszQueryString ? m_lpECB->lpszQueryString : "");
1157 virtual string getPostData(void) {
1158 if (m_lpECB->cbTotalBytes > 1024*1024) // 1MB?
1159 throw FatalProfileException("Blocked too-large a submission to profile endpoint.");
1160 else if (m_lpECB->cbTotalBytes != m_lpECB->cbAvailable) {
1163 DWORD datalen=m_lpECB->cbTotalBytes;
1166 BOOL ret = m_lpECB->ReadClient(m_lpECB->ConnID, buf, &buflen);
1167 if (!ret || !buflen)
1168 throw FatalProfileException("Error reading profile submission from browser.");
1169 cgistr.append(buf, buflen);
1175 return string(reinterpret_cast<char*>(m_lpECB->lpbData),m_lpECB->cbAvailable);
1177 virtual void* sendPage(
1180 const string& content_type="text/html",
1181 const Iterator<header_t>& headers=EMPTY(header_t)) {
1182 string hdr = string ("Connection: close\r\nContent-type: ") + content_type + "\r\n";
1183 for (int k = 0; k < headers.size(); k++) {
1184 hdr += headers[k].first + ": " + headers[k].second + "\r\n";
1187 const char* codestr="200 OK";
1189 case 403: codestr="403 Forbidden"; break;
1190 case 404: codestr="404 Not Found"; break;
1191 case 500: codestr="500 Server Error"; break;
1193 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, (void*)codestr, 0, (LPDWORD)hdr.c_str());
1194 DWORD resplen = msg.size();
1195 m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)msg.c_str(), &resplen, HSE_IO_SYNC);
1196 return (void*)HSE_STATUS_SUCCESS;
1198 virtual void* sendRedirect(const string& url) {
1199 // XXX: Don't support the httpRedirect option, yet.
1200 string hdrs = m_cookie + "Location: " + url + "\r\n"
1201 "Content-Type: text/html\r\n"
1202 "Content-Length: 40\r\n"
1203 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
1204 "Cache-Control: private,no-store,no-cache\r\n\r\n";
1205 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER,
1206 "302 Moved", 0, (LPDWORD)hdrs.c_str());
1207 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
1209 m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)redmsg, &resplen, HSE_IO_SYNC);
1210 return (void*)HSE_STATUS_SUCCESS;
1212 // Decline happens in the POST processor if this isn't the shire url
1213 // Note that it can also happen with HTAccess, but we don't support that, yet.
1214 virtual void* returnDecline(void) {
1216 WriteClientError(m_lpECB, "ISAPI extension can only be invoked to process Shibboleth protocol requests."
1217 "Make sure the mapped file extension doesn't match actual content.");
1219 virtual void* returnOK(void) { return (void*) HSE_STATUS_SUCCESS; }
1221 // Not used in the extension.
1222 virtual void clearHeader(const string &name) { throw runtime_error("clearHeader not implemented"); }
1223 virtual void setHeader(const string &name, const string &value) { throw runtime_error("setHeader not implemented"); }
1224 virtual string getHeader(const string &name) { throw runtime_error("getHeader not implemented"); }
1225 virtual void setRemoteUser(const string &user) { throw runtime_error("setRemoteUser not implemented"); }
1226 virtual string getRemoteUser(void) { throw runtime_error("getRemoteUser not implemented"); }
1229 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
1232 const IApplication* application=NULL;
1234 ostringstream threadid;
1235 threadid << "[" << getpid() << "] isapi_shib_extension" << '\0';
1236 saml::NDC ndc(threadid.str().c_str());
1238 // Determine web site number. This can't really fail, I don't think.
1240 GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
1242 // Match site instance to host name, skip if no match.
1243 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
1244 if (map_i==g_Sites.end())
1245 return WriteClientError(lpECB, "Shibboleth Extension not configured for this web site.");
1247 ShibTargetIsapiE ste(lpECB, map_i->second);
1248 pair<bool,void*> res = ste.doHandler();
1249 if (res.first) return (DWORD)res.second;
1251 return WriteClientError(lpECB, "Shibboleth Extension failed to process request");
1255 return WriteClientError(lpECB,"Out of Memory");
1258 if (e==ERROR_NO_DATA)
1259 return WriteClientError(lpECB,"A required variable or header was empty.");
1261 return WriteClientError(lpECB,"Server detected unexpected IIS error.");
1265 return WriteClientError(lpECB,"Server caught an unknown exception.");
1269 // If we get here we've got an error.
1270 return HSE_STATUS_ERROR;
1274 IRequestMapper::Settings map_request(
1275 LPEXTENSION_CONTROL_BLOCK lpECB, IRequestMapper* mapper, const site_t& site, string& target
1279 GetServerVariable(lpECB,"HTTPS",ssl,5);
1280 bool SSL=(ssl=="on" || ssl=="ON");
1282 // URL path always come from IIS.
1284 GetServerVariable(lpECB,"URL",url,255);
1286 // Port may come from IIS or from site def.
1288 if (site.m_port.empty() || !g_bNormalizeRequest)
1289 GetServerVariable(lpECB,"SERVER_PORT",port,10);
1291 strncpy(port,site.m_port.c_str(),10);
1292 static_cast<char*>(port)[10]=0;
1295 // Scheme may come from site def or be derived from IIS.
1296 const char* scheme=site.m_scheme.c_str();
1297 if (!scheme || !*scheme || !g_bNormalizeRequest) {
1298 scheme = SSL ? "https" : "http";
1301 // Start with scheme and hostname.
1302 if (g_bNormalizeRequest) {
1303 target = string(scheme) + "://" + site.m_name;
1307 GetServerVariable(lpECB,"SERVER_NAME",name,64);
1308 target = string(scheme) + "://" + static_cast<char*>(name);
1311 // If port is non-default, append it.
1312 if ((!strcmp(scheme,"http") && port!="80") || (!strcmp(scheme,"https") && port!="443"))
1313 target = target + ':' + static_cast<char*>(port);
1317 target+=static_cast<char*>(url);
1319 return mapper->getSettingsFromParsedURL(scheme,site.m_name.c_str(),strtoul(port,NULL,10),url);
1322 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const IApplication* app, const char* page, ShibMLP& mlp)
1324 const IPropertySet* props=app->getPropertySet("Errors");
1326 pair<bool,const char*> p=props->getString(page);
1328 ifstream infile(p.second);
1329 if (!infile.fail()) {
1330 const char* res = mlp.run(infile,props);
1332 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
1333 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
1334 DWORD resplen=strlen(res);
1335 lpECB->WriteClient(lpECB->ConnID,(LPVOID)res,&resplen,0);
1336 return HSE_STATUS_SUCCESS;
1341 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Extension unable to open error template.");
1342 return WriteClientError(lpECB,"Unable to open error template, check settings.");
1345 DWORD WriteRedirectPage(LPEXTENSION_CONTROL_BLOCK lpECB, const IApplication* app, const char* file, ShibMLP& mlp, const char* headers=NULL)
1347 ifstream infile(file);
1348 if (!infile.fail()) {
1349 const char* res = mlp.run(infile,app->getPropertySet("Errors"));
1352 sprintf(buf,"Content-Length: %u\r\nContent-Type: text/html\r\n\r\n",strlen(res));
1356 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)h.c_str());
1359 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)buf);
1360 DWORD resplen=strlen(res);
1361 lpECB->WriteClient(lpECB->ConnID,(LPVOID)res,&resplen,0);
1362 return HSE_STATUS_SUCCESS;
1365 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Extension unable to open redirect template.");
1366 return WriteClientError(lpECB,"Unable to open redirect template, check settings.");
1369 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
1372 const IApplication* application=NULL;
1375 ostringstream threadid;
1376 threadid << "[" << getpid() << "] shire_handler" << '\0';
1377 saml::NDC ndc(threadid.str().c_str());
1379 // Determine web site number. This can't really fail, I don't think.
1381 GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
1383 // Match site instance to host name, skip if no match.
1384 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
1385 if (map_i==g_Sites.end())
1386 return WriteClientError(lpECB,"Shibboleth filter not configured for this web site.");
1388 // We lock the configuration system for the duration.
1389 IConfig* conf=g_Config->getINI();
1390 Locker locker(conf);
1392 // Map request to application and content settings.
1393 IRequestMapper* mapper=conf->getRequestMapper();
1394 Locker locker2(mapper);
1395 IRequestMapper::Settings settings=map_request(lpECB,mapper,map_i->second,targeturl);
1396 pair<bool,const char*> application_id=settings.first->getString("applicationId");
1397 application=conf->getApplication(application_id.second);
1398 const IPropertySet* sessionProps=application ? application->getPropertySet("Sessions") : NULL;
1399 if (!application || !sessionProps)
1400 return WriteClientError(lpECB,"Unable to map request to application session settings, check configuration.");
1402 SHIRE shire(application);
1404 const char* shireURL=shire.getShireURL(targeturl.c_str());
1406 return WriteClientError(lpECB,"Unable to map request to proper shireURL setting, check configuration.");
1408 // Make sure we only process the SHIRE requests.
1409 if (!strstr(targeturl.c_str(),shireURL))
1410 return WriteClientError(lpECB,"ISAPI extension can only be invoked to process incoming sessions."
1411 "Make sure the mapped file extension doesn't match actual content.");
1413 pair<const char*,const char*> shib_cookie=shire.getCookieNameProps();
1415 // Make sure this is SSL, if it should be
1416 pair<bool,bool> shireSSL=sessionProps->getBool("shireSSL");
1417 if (!shireSSL.first || shireSSL.second) {
1418 GetServerVariable(lpECB,"HTTPS",buf,10);
1420 throw ShibTargetException(SHIBRPC_OK,"blocked non-SSL access to SHIRE POST processor");
1423 pair<bool,bool> httpRedirects=sessionProps->getBool("httpRedirects");
1424 pair<bool,const char*> redirectPage=sessionProps->getString("redirectPage");
1425 if (httpRedirects.first && !httpRedirects.second && !redirectPage.first)
1426 return WriteClientError(lpECB,"HTML-based redirection requires a redirectPage property.");
1428 // Check for Mac web browser
1432 GetServerVariable(lpECB,"HTTP_USER_AGENT",agent,64);
1433 if (strstr(agent,"AppleWebKit/"))
1437 // If this is a GET, we manufacture an AuthnRequest.
1438 if (!stricmp(lpECB->lpszMethod,"GET")) {
1439 const char* areq=lpECB->lpszQueryString ? shire.getLazyAuthnRequest(lpECB->lpszQueryString) : NULL;
1441 throw ShibTargetException(SHIBRPC_OK, "malformed arguments to request a new session");
1442 if (!httpRedirects.first || httpRedirects.second) {
1443 string hdrs=string("Location: ") + areq + "\r\n"
1444 "Content-Type: text/html\r\n"
1445 "Content-Length: 40\r\n"
1446 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
1447 "Cache-Control: private,no-store,no-cache\r\n\r\n";
1448 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"302 Moved",0,(LPDWORD)hdrs.c_str());
1449 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
1451 lpECB->WriteClient(lpECB->ConnID,(LPVOID)redmsg,&resplen,HSE_IO_SYNC);
1452 return HSE_STATUS_SUCCESS;
1455 ShibMLP markupProcessor;
1456 markupProcessor.insert("requestURL",areq);
1457 return WriteRedirectPage(lpECB, application, redirectPage.second, markupProcessor);
1460 else if (stricmp(lpECB->lpszMethod,"POST"))
1461 throw ShibTargetException(SHIBRPC_OK,"blocked non-POST to SHIRE POST processor");
1463 // Sure sure this POST is an appropriate content type
1464 if (!lpECB->lpszContentType || stricmp(lpECB->lpszContentType,"application/x-www-form-urlencoded"))
1465 throw ShibTargetException(SHIBRPC_OK,"blocked bad content-type to SHIRE POST processor");
1468 pair<const char*,const char*> elements=pair<const char*,const char*>(NULL,NULL);
1469 if (lpECB->cbTotalBytes > 1024*1024) // 1MB?
1470 throw ShibTargetException(SHIBRPC_OK,"blocked too-large a post to SHIRE POST processor");
1471 else if (lpECB->cbTotalBytes!=lpECB->cbAvailable) {
1474 DWORD datalen=lpECB->cbTotalBytes;
1477 BOOL ret=lpECB->ReadClient(lpECB->ConnID,buf,&buflen);
1478 if (!ret || !buflen)
1479 throw ShibTargetException(SHIBRPC_OK,"error reading POST data from browser");
1480 cgistr.append(buf,buflen);
1483 elements=shire.getFormSubmission(cgistr.c_str(),cgistr.length());
1486 elements=shire.getFormSubmission(reinterpret_cast<char*>(lpECB->lpbData),lpECB->cbAvailable);
1488 // Make sure the SAML Response parameter exists
1489 if (!elements.first || !*elements.first)
1490 throw ShibTargetException(SHIBRPC_OK, "SHIRE POST failed to find SAMLResponse form element");
1492 // Make sure the target parameter exists
1493 if (!elements.second || !*elements.second)
1494 throw ShibTargetException(SHIBRPC_OK, "SHIRE POST failed to find TARGET form element");
1496 GetServerVariable(lpECB,"REMOTE_ADDR",buf,16);
1498 // Process the post.
1500 RPCError* status=NULL;
1501 ShibMLP markupProcessor;
1502 markupProcessor.insert("requestURL", targeturl.c_str());
1504 status = shire.sessionCreate(elements.first,buf,cookie);
1506 catch (ShibTargetException &e) {
1507 markupProcessor.insert("errorType", "Session Creation Service Error");
1508 markupProcessor.insert("errorText", e.what());
1509 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
1510 return WriteClientError(lpECB, application, "shire", markupProcessor);
1514 markupProcessor.insert("errorType", "Session Creation Service Error");
1515 markupProcessor.insert("errorText", "Unexpected Exception");
1516 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
1517 return WriteClientError(lpECB, application, "shire", markupProcessor);
1521 if (status->isError()) {
1522 if (status->isRetryable()) {
1524 const char* loc=shire.getAuthnRequest(elements.second);
1525 if (!httpRedirects.first || httpRedirects.second) {
1526 string hdrs=string("Location: ") + loc + "\r\n"
1527 "Content-Type: text/html\r\n"
1528 "Content-Length: 40\r\n"
1529 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
1530 "Cache-Control: private,no-store,no-cache\r\n\r\n";
1531 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"302 Moved",0,(LPDWORD)hdrs.c_str());
1532 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
1534 lpECB->WriteClient(lpECB->ConnID,(LPVOID)redmsg,&resplen,HSE_IO_SYNC);
1535 return HSE_STATUS_SUCCESS;
1538 markupProcessor.insert("requestURL",loc);
1539 return WriteRedirectPage(lpECB, application, redirectPage.second, markupProcessor);
1543 // Return this error to the user.
1544 markupProcessor.insert(*status);
1546 return WriteClientError(lpECB,application,"shire",markupProcessor);
1550 // We've got a good session, set the cookie and redirect to target.
1551 cookie = string("Set-Cookie: ") + shib_cookie.first + '=' + cookie + shib_cookie.second + "\r\n"
1552 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
1553 "Cache-Control: private,no-store,no-cache\r\n";
1554 if (!httpRedirects.first || httpRedirects.second) {
1555 cookie=cookie + "Content-Type: text/html\r\nLocation: " + elements.second + "\r\nContent-Length: 40\r\n\r\n";
1556 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"302 Moved",0,(LPDWORD)cookie.c_str());
1557 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
1559 lpECB->WriteClient(lpECB->ConnID,(LPVOID)redmsg,&resplen,HSE_IO_SYNC);
1560 return HSE_STATUS_SUCCESS;
1563 markupProcessor.insert("requestURL",elements.second);
1564 return WriteRedirectPage(lpECB, application, redirectPage.second, markupProcessor, cookie.c_str());
1567 catch (ShibTargetException &e) {
1569 ShibMLP markupProcessor;
1570 markupProcessor.insert("requestURL", targeturl.c_str());
1571 markupProcessor.insert("errorType", "Session Creation Service Error");
1572 markupProcessor.insert("errorText", e.what());
1573 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
1574 return WriteClientError(lpECB,application,"shire",markupProcessor);
1580 ShibMLP markupProcessor;
1581 markupProcessor.insert("requestURL", targeturl.c_str());
1582 markupProcessor.insert("errorType", "Session Creation Service Error");
1583 markupProcessor.insert("errorText", "Unexpected Exception");
1584 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
1585 return WriteClientError(lpECB,application,"shire",markupProcessor);
1590 return HSE_STATUS_ERROR;