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(ShibTargetConfig::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,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(),ShibTargetConfig::SHIBTARGET_NS,Implementation
213 if (impl && (impl=saml::XML::getFirstChildElement(impl,ShibTargetConfig::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,ShibTargetConfig::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,ShibTargetConfig::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 // The last two appear to be unavailable to this filter hook, but we don't need them.
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(g_Config, scheme, host, atoi(port), url, content_type, remote_addr, method);
419 ~ShibTargetIsapiF() { }
421 virtual void log(ShibLogLevel level, const string &msg) {
422 if (level == LogLevelError)
423 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
425 (level == LogLevelDebug ? Priority::DEBUG :
426 (level == LogLevelInfo ? Priority::INFO :
427 (level == LogLevelWarn ? Priority::WARN : Priority::ERROR))),
431 virtual string getCookies() const {
433 GetHeader(m_pn, m_pfc, "Cookie:", buf, 128, false);
434 return buf.empty() ? "" : buf;
437 virtual void clearHeader(const string &name) {
438 string hdr = name + ":";
439 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), "");
441 virtual void setHeader(const string &name, const string &value) {
442 string hdr = name + ":";
443 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()),
444 const_cast<char*>(value.c_str()));
446 virtual string getHeader(const string &name) {
447 string hdr = name + ":";
449 GetHeader(m_pn, m_pfc, const_cast<char*>(hdr.c_str()), buf, 1024, false);
452 virtual void setRemoteUser(const string &user) {
453 setHeader(string("remote-user"), user);
455 virtual string getRemoteUser(void) {
456 return getHeader(string("remote-user"));
458 virtual void* sendPage(
461 const string& content_type="text/html",
462 const Iterator<header_t>& headers=EMPTY(header_t)) {
463 string hdr = string ("Connection: close\r\nContent-type: ") + content_type + "\r\n";
464 while (headers.hasNext()) {
465 const header_t& h=headers.next();
466 hdr += h.first + ": " + h.second + "\r\n";
469 const char* codestr="200 OK";
471 case 403: codestr="403 Forbidden"; break;
472 case 404: codestr="404 Not Found"; break;
473 case 500: codestr="500 Server Error"; break;
475 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, (void*)codestr, (DWORD)hdr.c_str(), 0);
476 DWORD resplen = msg.size();
477 m_pfc->WriteClient(m_pfc, (LPVOID)msg.c_str(), &resplen, 0);
478 return (void*)SF_STATUS_REQ_FINISHED;
480 virtual void* sendRedirect(const string& url) {
481 // XXX: Don't support the httpRedirect option, yet.
482 string hdrs=m_cookie + string("Location: ") + url + "\r\n"
483 "Content-Type: text/html\r\n"
484 "Content-Length: 40\r\n"
485 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
486 "Cache-Control: private,no-store,no-cache\r\n\r\n";
487 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER,
488 "302 Please Wait", (DWORD)hdrs.c_str(), 0);
489 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
491 m_pfc->WriteClient(m_pfc, (LPVOID)redmsg, &resplen, 0);
492 return reinterpret_cast<void*>(SF_STATUS_REQ_FINISHED);
494 // XXX: We might not ever hit the 'decline' status in this filter.
495 //virtual void* returnDecline(void) { }
496 virtual void* returnOK(void) { return (void*) SF_STATUS_REQ_NEXT_NOTIFICATION; }
498 // The filter never processes the POST, so stub these methods.
499 virtual void setCookie(const string &name, const string &value) {
500 // Set the cookie for later. Use it during the redirect.
501 m_cookie += "Set-Cookie: " + name + "=" + value + "\r\n";
503 virtual string getArgs(void) { throw runtime_error("getArgs not implemented"); }
504 virtual string getPostData(void) { throw runtime_error("getPostData not implemented"); }
507 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const char* msg)
509 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
510 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
511 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)ctype,0);
512 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Filter Error</TITLE></HEAD><BODY>"
513 "<H1>Shibboleth Filter Error</H1>";
514 DWORD resplen=strlen(xmsg);
515 pfc->WriteClient(pfc,(LPVOID)xmsg,&resplen,0);
517 pfc->WriteClient(pfc,(LPVOID)msg,&resplen,0);
518 static const char* xmsg2="</BODY></HTML>";
519 resplen=strlen(xmsg2);
520 pfc->WriteClient(pfc,(LPVOID)xmsg2,&resplen,0);
521 return SF_STATUS_REQ_FINISHED;
524 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
526 // Is this a log notification?
527 if (notificationType==SF_NOTIFY_LOG)
529 if (pfc->pFilterContext)
530 ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=static_cast<LPCSTR>(pfc->pFilterContext);
531 return SF_STATUS_REQ_NEXT_NOTIFICATION;
534 PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
537 // Determine web site number. This can't really fail, I don't think.
539 GetServerVariable(pfc,"INSTANCE_ID",buf,10);
541 // Match site instance to host name, skip if no match.
542 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
543 if (map_i==g_Sites.end())
544 return SF_STATUS_REQ_NEXT_NOTIFICATION;
546 ostringstream threadid;
547 threadid << "[" << getpid() << "] isapi_shib" << '\0';
548 saml::NDC ndc(threadid.str().c_str());
550 ShibTargetIsapiF stf(pfc, pn, map_i->second);
552 // "false" because we don't override the Shib settings
553 pair<bool,void*> res = stf.doCheckAuthN();
554 if (res.first) return (DWORD)res.second;
556 // "false" because we don't override the Shib settings
557 res = stf.doExportAssertions();
558 if (res.first) return (DWORD)res.second;
560 res = stf.doCheckAuthZ();
561 if (res.first) return (DWORD)res.second;
563 return SF_STATUS_REQ_NEXT_NOTIFICATION;
566 return WriteClientError(pfc,"Out of Memory");
569 if (e==ERROR_NO_DATA)
570 return WriteClientError(pfc,"A required variable or header was empty.");
572 return WriteClientError(pfc,"Server detected unexpected IIS error.");
576 return WriteClientError(pfc,"Server caught an unknown exception.");
580 return WriteClientError(pfc,"Server reached unreachable code, save my walrus!");
585 IRequestMapper::Settings map_request(
586 PHTTP_FILTER_CONTEXT pfc, PHTTP_FILTER_PREPROC_HEADERS pn, IRequestMapper* mapper, const site_t& site, string& target
589 // URL path always come from IIS.
591 GetHeader(pn,pfc,"url",url,256,false);
593 // Port may come from IIS or from site def.
595 if (site.m_port.empty() || !g_bNormalizeRequest)
596 GetServerVariable(pfc,"SERVER_PORT",port,10);
598 strncpy(port,site.m_port.c_str(),10);
599 static_cast<char*>(port)[10]=0;
602 // Scheme may come from site def or be derived from IIS.
603 const char* scheme=site.m_scheme.c_str();
604 if (!scheme || !*scheme || !g_bNormalizeRequest)
605 scheme=pfc->fIsSecurePort ? "https" : "http";
607 // Start with scheme and hostname.
608 if (g_bNormalizeRequest) {
609 target = string(scheme) + "://" + site.m_name;
613 GetServerVariable(pfc,"SERVER_NAME",name,64);
614 target = string(scheme) + "://" + static_cast<char*>(name);
617 // If port is non-default, append it.
618 if ((!strcmp(scheme,"http") && port!="80") || (!strcmp(scheme,"https") && port!="443"))
619 target = target + ':' + static_cast<char*>(port);
623 target+=static_cast<char*>(url);
625 return mapper->getSettingsFromParsedURL(scheme,site.m_name.c_str(),strtoul(port,NULL,10),url);
628 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const IApplication* app, const char* page, ShibMLP& mlp)
630 const IPropertySet* props=app->getPropertySet("Errors");
632 pair<bool,const char*> p=props->getString(page);
634 ifstream infile(p.second);
635 if (!infile.fail()) {
636 const char* res = mlp.run(infile,props);
638 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
639 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)ctype,0);
640 DWORD resplen=strlen(res);
641 pfc->WriteClient(pfc,(LPVOID)res,&resplen,0);
642 return SF_STATUS_REQ_FINISHED;
648 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Filter unable to open error template.");
649 return WriteClientError(pfc,"Unable to open error template, check settings.");
652 DWORD WriteRedirectPage(PHTTP_FILTER_CONTEXT pfc, const IApplication* app, const char* file, ShibMLP& mlp, const char* headers=NULL)
654 ifstream infile(file);
655 if (!infile.fail()) {
656 const char* res = mlp.run(infile,app->getPropertySet("Errors"));
659 sprintf(buf,"Content-Length: %u\r\nContent-Type: text/html\r\n\r\n",strlen(res));
663 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)h.c_str(),0);
666 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)buf,0);
667 DWORD resplen=strlen(res);
668 pfc->WriteClient(pfc,(LPVOID)res,&resplen,0);
669 return SF_STATUS_REQ_FINISHED;
672 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Extension unable to open redirect template.");
673 return WriteClientError(pfc,"Unable to open redirect template, check settings.");
676 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
678 // Is this a log notification?
679 if (notificationType==SF_NOTIFY_LOG)
681 if (pfc->pFilterContext)
682 ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=static_cast<LPCSTR>(pfc->pFilterContext);
683 return SF_STATUS_REQ_NEXT_NOTIFICATION;
686 PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
689 // Determine web site number. This can't really fail, I don't think.
691 GetServerVariable(pfc,"INSTANCE_ID",buf,10);
693 // Match site instance to host name, skip if no match.
694 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
695 if (map_i==g_Sites.end())
696 return SF_STATUS_REQ_NEXT_NOTIFICATION;
698 ostringstream threadid;
699 threadid << "[" << getpid() << "] isapi_shib" << '\0';
700 saml::NDC ndc(threadid.str().c_str());
702 // We lock the configuration system for the duration.
703 IConfig* conf=g_Config->getINI();
706 // Map request to application and content settings.
708 IRequestMapper* mapper=conf->getRequestMapper();
709 Locker locker2(mapper);
710 IRequestMapper::Settings settings=map_request(pfc,pn,mapper,map_i->second,targeturl);
711 pair<bool,const char*> application_id=settings.first->getString("applicationId");
712 const IApplication* application=conf->getApplication(application_id.second);
714 return WriteClientError(pfc,"Unable to map request to application settings, check configuration.");
716 // Declare SHIRE object for this request.
717 SHIRE shire(application);
719 const char* shireURL=shire.getShireURL(targeturl.c_str());
721 return WriteClientError(pfc,"Unable to map request to proper shireURL setting, check configuration.");
723 // If the user is accessing the SHIRE acceptance point, pass it on.
724 if (targeturl.find(shireURL)!=string::npos)
725 return SF_STATUS_REQ_NEXT_NOTIFICATION;
727 // Now check the policy for this request.
728 pair<bool,bool> requireSession=settings.first->getBool("requireSession");
729 pair<const char*,const char*> shib_cookie=shire.getCookieNameProps();
730 pair<bool,bool> httpRedirects=application->getPropertySet("Sessions")->getBool("httpRedirects");
731 pair<bool,const char*> redirectPage=application->getPropertySet("Sessions")->getString("redirectPage");
732 if (httpRedirects.first && !httpRedirects.second && !redirectPage.first)
733 return WriteClientError(pfc,"HTML-based redirection requires a redirectPage property.");
735 // Check for session cookie.
736 const char* session_id=NULL;
737 GetHeader(pn,pfc,"Cookie:",buf,128,false);
738 Category::getInstance("isapi_shib.HttpFilterProc").debug("cookie header is {%s}",(const char*)buf);
739 if (!buf.empty() && (session_id=strstr(buf,shib_cookie.first))) {
740 session_id+=strlen(shib_cookie.first) + 1; /* Skip over the '=' */
741 char* cookieend=strchr(session_id,';');
743 *cookieend = '\0'; /* Ignore anyting after a ; */
746 if (!session_id || !*session_id) {
747 // If no session required, bail now.
748 if (!requireSession.second)
749 return SF_STATUS_REQ_NEXT_NOTIFICATION;
751 // No acceptable cookie, and we require a session. Generate an AuthnRequest.
752 const char* areq = shire.getAuthnRequest(targeturl.c_str());
753 if (!httpRedirects.first || httpRedirects.second) {
754 string hdrs=string("Location: ") + areq + "\r\n"
755 "Content-Type: text/html\r\n"
756 "Content-Length: 40\r\n"
757 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
758 "Cache-Control: private,no-store,no-cache\r\n\r\n";
759 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"302 Please Wait",(DWORD)hdrs.c_str(),0);
760 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
762 pfc->WriteClient(pfc,(LPVOID)redmsg,&resplen,0);
763 return SF_STATUS_REQ_FINISHED;
766 ShibMLP markupProcessor;
767 markupProcessor.insert("requestURL",areq);
768 return WriteRedirectPage(pfc, application, redirectPage.second, markupProcessor);
772 // Make sure this session is still valid.
773 RPCError* status = NULL;
774 ShibMLP markupProcessor;
775 markupProcessor.insert("requestURL", targeturl);
778 GetServerVariable(pfc,"REMOTE_ADDR",abuf,16);
780 status = shire.sessionIsValid(session_id, abuf);
782 catch (ShibTargetException &e) {
783 markupProcessor.insert("errorType", "Session Processing Error");
784 markupProcessor.insert("errorText", e.what());
785 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
786 return WriteClientError(pfc, application, "shire", markupProcessor);
790 markupProcessor.insert("errorType", "Session Processing Error");
791 markupProcessor.insert("errorText", "Unexpected Exception");
792 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
793 return WriteClientError(pfc, application, "shire", markupProcessor);
798 if (status->isError()) {
799 if (!requireSession.second)
800 return SF_STATUS_REQ_NEXT_NOTIFICATION;
801 else if (status->isRetryable()) {
802 // Oops, session is invalid. Generate AuthnRequest.
804 const char* areq = shire.getAuthnRequest(targeturl.c_str());
805 if (!httpRedirects.first || httpRedirects.second) {
806 string hdrs=string("Location: ") + areq + "\r\n"
807 "Content-Type: text/html\r\n"
808 "Content-Length: 40\r\n"
809 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
810 "Cache-Control: private,no-store,no-cache\r\n\r\n";
811 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"302 Please Wait",(DWORD)hdrs.c_str(),0);
812 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
814 pfc->WriteClient(pfc,(LPVOID)redmsg,&resplen,0);
815 return SF_STATUS_REQ_FINISHED;
818 markupProcessor.insert("requestURL",areq);
819 return WriteRedirectPage(pfc, application, redirectPage.second, markupProcessor);
823 // return the error page to the user
824 markupProcessor.insert(*status);
826 return WriteClientError(pfc, application, "shire", markupProcessor);
833 vector<SAMLAssertion*> assertions;
834 SAMLAuthenticationStatement* sso_statement=NULL;
837 status = rm.getAssertions(session_id, abuf, assertions, &sso_statement);
839 catch (ShibTargetException &e) {
840 markupProcessor.insert("errorType", "Attribute Processing Error");
841 markupProcessor.insert("errorText", e.what());
842 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
843 return WriteClientError(pfc, application, "rm", markupProcessor);
847 markupProcessor.insert("errorType", "Attribute Processing Error");
848 markupProcessor.insert("errorText", "Unexpected Exception");
849 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
850 return WriteClientError(pfc, application, "rm", markupProcessor);
854 if (status->isError()) {
855 markupProcessor.insert(*status);
857 return WriteClientError(pfc, application, "rm", markupProcessor);
861 // Do we have an access control plugin?
862 if (settings.second) {
863 Locker acllock(settings.second);
864 if (!settings.second->authorized(*sso_statement,assertions)) {
865 for (int k = 0; k < assertions.size(); k++)
866 delete assertions[k];
867 delete sso_statement;
868 return WriteClientError(pfc, application, "access", markupProcessor);
872 // Get the AAP providers, which contain the attribute policy info.
873 Iterator<IAAP*> provs=application->getAAPProviders();
875 // Clear out the list of mapped attributes
876 while (provs.hasNext()) {
877 IAAP* aap=provs.next();
880 Iterator<const IAttributeRule*> rules=aap->getAttributeRules();
881 while (rules.hasNext()) {
882 const char* header=rules.next()->getHeader();
884 string hname=string(header) + ':';
885 pn->SetHeader(pfc,const_cast<char*>(hname.c_str()),"");
891 for (int k = 0; k < assertions.size(); k++)
892 delete assertions[k];
893 delete sso_statement;
894 markupProcessor.insert("errorType", "Attribute Processing Error");
895 markupProcessor.insert("errorText", "Unexpected Exception");
896 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
897 return WriteClientError(pfc, application, "rm", markupProcessor);
903 // Maybe export the first assertion.
904 pn->SetHeader(pfc,"remote-user:","");
905 pn->SetHeader(pfc,"Shib-Attributes:","");
906 pair<bool,bool> exp=settings.first->getBool("exportAssertion");
907 if (exp.first && exp.second && assertions.size()) {
909 RM::serialize(*(assertions[0]), assertion);
910 string::size_type lfeed;
911 while ((lfeed=assertion.find('\n'))!=string::npos)
912 assertion.erase(lfeed,1);
913 pn->SetHeader(pfc,"Shib-Attributes:",const_cast<char*>(assertion.c_str()));
916 pn->SetHeader(pfc,"Shib-Origin-Site:","");
917 pn->SetHeader(pfc,"Shib-Authentication-Method:","");
918 pn->SetHeader(pfc,"Shib-NameIdentifier-Format:","");
920 // Export the SAML AuthnMethod and the origin site name.
921 auto_ptr_char os(sso_statement->getSubject()->getNameIdentifier()->getNameQualifier());
922 auto_ptr_char am(sso_statement->getAuthMethod());
923 pn->SetHeader(pfc,"Shib-Origin-Site:", const_cast<char*>(os.get()));
924 pn->SetHeader(pfc,"Shib-Authentication-Method:", const_cast<char*>(am.get()));
927 AAP wrapper(provs,sso_statement->getSubject()->getNameIdentifier()->getFormat(),Constants::SHIB_ATTRIBUTE_NAMESPACE_URI);
928 if (!wrapper.fail() && wrapper->getHeader()) {
929 auto_ptr_char form(sso_statement->getSubject()->getNameIdentifier()->getFormat());
930 auto_ptr_char nameid(sso_statement->getSubject()->getNameIdentifier()->getName());
931 pn->SetHeader(pfc,"Shib-NameIdentifier-Format:",const_cast<char*>(form.get()));
932 if (!strcmp(wrapper->getHeader(),"REMOTE_USER")) {
933 char* principal=const_cast<char*>(nameid.get());
934 pn->SetHeader(pfc,"remote-user:",principal);
935 pfc->pFilterContext=pfc->AllocMem(pfc,strlen(principal)+1,0);
936 if (pfc->pFilterContext)
937 strcpy(static_cast<char*>(pfc->pFilterContext),principal);
940 string hname=string(wrapper->getHeader()) + ':';
941 pn->SetHeader(pfc,const_cast<char*>(wrapper->getHeader()),const_cast<char*>(nameid.get()));
945 pn->SetHeader(pfc,"Shib-Application-ID:","");
946 pn->SetHeader(pfc,"Shib-Application-ID:",const_cast<char*>(application_id.second));
948 // Export the attributes.
949 Iterator<SAMLAssertion*> a_iter(assertions);
950 while (a_iter.hasNext()) {
951 SAMLAssertion* assert=a_iter.next();
952 Iterator<SAMLStatement*> statements=assert->getStatements();
953 while (statements.hasNext()) {
954 SAMLAttributeStatement* astate=dynamic_cast<SAMLAttributeStatement*>(statements.next());
957 Iterator<SAMLAttribute*> attrs=astate->getAttributes();
958 while (attrs.hasNext()) {
959 SAMLAttribute* attr=attrs.next();
961 // Are we supposed to export it?
962 AAP wrapper(provs,attr->getName(),attr->getNamespace());
963 if (wrapper.fail() || !wrapper->getHeader())
966 Iterator<string> vals=attr->getSingleByteValues();
967 if (!strcmp(wrapper->getHeader(),"REMOTE_USER") && vals.hasNext()) {
968 char* principal=const_cast<char*>(vals.next().c_str());
969 pn->SetHeader(pfc,"remote-user:",principal);
970 pfc->pFilterContext=pfc->AllocMem(pfc,strlen(principal)+1,0);
971 if (pfc->pFilterContext)
972 strcpy(static_cast<char*>(pfc->pFilterContext),principal);
977 string hname=string(wrapper->getHeader()) + ':';
978 GetHeader(pn,pfc,const_cast<char*>(hname.c_str()),buf,256,false);
983 for (; vals.hasNext(); it++) {
984 string value = vals.next();
985 for (string::size_type pos = value.find_first_of(";", string::size_type(0));
987 pos = value.find_first_of(";", pos)) {
988 value.insert(pos, "\\");
994 header=header + ';' + value;
996 pn->SetHeader(pfc,const_cast<char*>(hname.c_str()),const_cast<char*>(header.c_str()));
1003 for (int k = 0; k < assertions.size(); k++)
1004 delete assertions[k];
1005 delete sso_statement;
1007 return SF_STATUS_REQ_NEXT_NOTIFICATION;
1010 return WriteClientError(pfc,"Out of Memory");
1013 if (e==ERROR_NO_DATA)
1014 return WriteClientError(pfc,"A required variable or header was empty.");
1016 return WriteClientError(pfc,"Server detected unexpected IIS error.");
1020 return WriteClientError(pfc,"Server caught an unknown exception.");
1024 return WriteClientError(pfc,"Server reached unreachable code, save my walrus!");
1028 /****************************************************************************/
1031 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* msg)
1033 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
1034 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
1035 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
1036 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Error</TITLE></HEAD><BODY><H1>Shibboleth Error</H1>";
1037 DWORD resplen=strlen(xmsg);
1038 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg,&resplen,HSE_IO_SYNC);
1039 resplen=strlen(msg);
1040 lpECB->WriteClient(lpECB->ConnID,(LPVOID)msg,&resplen,HSE_IO_SYNC);
1041 static const char* xmsg2="</BODY></HTML>";
1042 resplen=strlen(xmsg2);
1043 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg2,&resplen,HSE_IO_SYNC);
1044 return HSE_STATUS_SUCCESS;
1048 class ShibTargetIsapiE : public ShibTarget
1050 LPEXTENSION_CONTROL_BLOCK m_lpECB;
1055 ShibTargetIsapiE(LPEXTENSION_CONTROL_BLOCK lpECB, const site_t& site)
1056 : m_log(&Category::getInstance("isapi_shib"))
1059 GetServerVariable(lpECB,"HTTPS",ssl,5);
1060 bool SSL=(ssl=="on" || ssl=="ON");
1062 // URL path always come from IIS.
1064 GetServerVariable(lpECB,"URL",url,255);
1066 // Port may come from IIS or from site def.
1068 if (!g_bNormalizeRequest || (SSL && site.m_sslport.empty()) || (!SSL && site.m_port.empty()))
1069 GetServerVariable(lpECB,"SERVER_PORT",port,10);
1071 strncpy(port,site.m_sslport.c_str(),10);
1072 static_cast<char*>(port)[10]=0;
1075 strncpy(port,site.m_port.c_str(),10);
1076 static_cast<char*>(port)[10]=0;
1079 // Scheme may come from site def or be derived from IIS.
1080 const char* scheme=site.m_scheme.c_str();
1081 if (!scheme || !*scheme || !g_bNormalizeRequest) {
1082 scheme = SSL ? "https" : "http";
1085 // Get the other server variables.
1086 dynabuf remote_addr(16),hostname(32);
1087 GetServerVariable(lpECB, "REMOTE_ADDR", remote_addr, 16);
1088 GetServerVariable(lpECB, "SERVER_NAME", hostname, 32);
1090 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
1091 const char* host=hostname;
1092 if (site.m_name!=host && site.m_aliases.find(host)==site.m_aliases.end())
1093 host=site.m_name.c_str();
1095 init(g_Config, scheme, host, atoi(port), url, lpECB->lpszContentType, remote_addr, lpECB->lpszMethod);
1099 ~ShibTargetIsapiE() { }
1101 virtual void log(ShibLogLevel level, const string &msg) {
1102 if (level == LogLevelError)
1103 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
1105 (level == LogLevelDebug ? Priority::DEBUG :
1106 (level == LogLevelInfo ? Priority::INFO :
1107 (level == LogLevelWarn ? Priority::WARN : Priority::ERROR))),
1111 virtual string getCookies() const {
1113 GetServerVariable(m_lpECB, "HTTP_COOKIE", buf, 128, false);
1114 return buf.empty() ? "" : buf;
1116 virtual void setCookie(const string &name, const string &value) {
1117 // Set the cookie for later. Use it during the redirect.
1118 m_cookie += "Set-Cookie: " + name + "=" + value + "\r\n";
1120 virtual string getArgs(void) {
1121 return string(m_lpECB->lpszQueryString ? m_lpECB->lpszQueryString : "");
1123 virtual string getPostData(void) {
1124 if (m_lpECB->cbTotalBytes > 1024*1024) // 1MB?
1125 throw FatalProfileException("Blocked too-large a submission to profile endpoint.");
1126 else if (m_lpECB->cbTotalBytes != m_lpECB->cbAvailable) {
1129 DWORD datalen=m_lpECB->cbTotalBytes;
1132 BOOL ret = m_lpECB->ReadClient(m_lpECB->ConnID, buf, &buflen);
1133 if (!ret || !buflen)
1134 throw FatalProfileException("Error reading profile submission from browser.");
1135 cgistr.append(buf, buflen);
1141 return string(reinterpret_cast<char*>(m_lpECB->lpbData),m_lpECB->cbAvailable);
1143 virtual void* sendPage(
1146 const string& content_type="text/html",
1147 const Iterator<header_t>& headers=EMPTY(header_t)) {
1148 string hdr = string ("Connection: close\r\nContent-type: ") + content_type + "\r\n";
1149 for (int k = 0; k < headers.size(); k++) {
1150 hdr += headers[k].first + ": " + headers[k].second + "\r\n";
1153 const char* codestr="200 OK";
1155 case 403: codestr="403 Forbidden"; break;
1156 case 404: codestr="404 Not Found"; break;
1157 case 500: codestr="500 Server Error"; break;
1159 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, (void*)codestr, 0, (LPDWORD)hdr.c_str());
1160 DWORD resplen = msg.size();
1161 m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)msg.c_str(), &resplen, HSE_IO_SYNC);
1162 return (void*)HSE_STATUS_SUCCESS;
1164 virtual void* sendRedirect(const string& url) {
1165 // XXX: Don't support the httpRedirect option, yet.
1166 string hdrs = m_cookie + "Location: " + url + "\r\n"
1167 "Content-Type: text/html\r\n"
1168 "Content-Length: 40\r\n"
1169 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
1170 "Cache-Control: private,no-store,no-cache\r\n\r\n";
1171 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER,
1172 "302 Moved", 0, (LPDWORD)hdrs.c_str());
1173 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
1175 m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)redmsg, &resplen, HSE_IO_SYNC);
1176 return (void*)HSE_STATUS_SUCCESS;
1178 // Decline happens in the POST processor if this isn't the shire url
1179 // Note that it can also happen with HTAccess, but we don't support that, yet.
1180 virtual void* returnDecline(void) {
1182 WriteClientError(m_lpECB, "ISAPA extension can only be invoked to process incoming sessions."
1183 "Make sure the mapped file extension doesn't match actual content.");
1185 virtual void* returnOK(void) { return (void*) HSE_STATUS_SUCCESS; }
1187 // Not used in the extension.
1188 virtual void clearHeader(const string &name) { throw runtime_error("clearHeader not implemented"); }
1189 virtual void setHeader(const string &name, const string &value) { throw runtime_error("setHeader not implemented"); }
1190 virtual string getHeader(const string &name) { throw runtime_error("getHeader not implemented"); }
1191 virtual void setRemoteUser(const string &user) { throw runtime_error("setRemoteUser not implemented"); }
1192 virtual string getRemoteUser(void) { throw runtime_error("getRemoteUser not implemented"); }
1195 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
1198 const IApplication* application=NULL;
1200 ostringstream threadid;
1201 threadid << "[" << getpid() << "] shire_handler" << '\0';
1202 saml::NDC ndc(threadid.str().c_str());
1204 // Determine web site number. This can't really fail, I don't think.
1206 GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
1208 // Match site instance to host name, skip if no match.
1209 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
1210 if (map_i==g_Sites.end())
1211 return WriteClientError(lpECB, "Shibboleth Extension not configured for this web site.");
1213 ShibTargetIsapiE ste(lpECB, map_i->second);
1214 pair<bool,void*> res = ste.doHandleProfile();
1215 if (res.first) return (DWORD)res.second;
1217 return WriteClientError(lpECB, "Shibboleth Extension failed to process request");
1221 return WriteClientError(lpECB,"Out of Memory");
1224 if (e==ERROR_NO_DATA)
1225 return WriteClientError(lpECB,"A required variable or header was empty.");
1227 return WriteClientError(lpECB,"Server detected unexpected IIS error.");
1231 return WriteClientError(lpECB,"Server caught an unknown exception.");
1235 // If we get here we've got an error.
1236 return HSE_STATUS_ERROR;
1240 IRequestMapper::Settings map_request(
1241 LPEXTENSION_CONTROL_BLOCK lpECB, IRequestMapper* mapper, const site_t& site, string& target
1245 GetServerVariable(lpECB,"HTTPS",ssl,5);
1246 bool SSL=(ssl=="on" || ssl=="ON");
1248 // URL path always come from IIS.
1250 GetServerVariable(lpECB,"URL",url,255);
1252 // Port may come from IIS or from site def.
1254 if (site.m_port.empty() || !g_bNormalizeRequest)
1255 GetServerVariable(lpECB,"SERVER_PORT",port,10);
1257 strncpy(port,site.m_port.c_str(),10);
1258 static_cast<char*>(port)[10]=0;
1261 // Scheme may come from site def or be derived from IIS.
1262 const char* scheme=site.m_scheme.c_str();
1263 if (!scheme || !*scheme || !g_bNormalizeRequest) {
1264 scheme = SSL ? "https" : "http";
1267 // Start with scheme and hostname.
1268 if (g_bNormalizeRequest) {
1269 target = string(scheme) + "://" + site.m_name;
1273 GetServerVariable(lpECB,"SERVER_NAME",name,64);
1274 target = string(scheme) + "://" + static_cast<char*>(name);
1277 // If port is non-default, append it.
1278 if ((!strcmp(scheme,"http") && port!="80") || (!strcmp(scheme,"https") && port!="443"))
1279 target = target + ':' + static_cast<char*>(port);
1283 target+=static_cast<char*>(url);
1285 return mapper->getSettingsFromParsedURL(scheme,site.m_name.c_str(),strtoul(port,NULL,10),url);
1288 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const IApplication* app, const char* page, ShibMLP& mlp)
1290 const IPropertySet* props=app->getPropertySet("Errors");
1292 pair<bool,const char*> p=props->getString(page);
1294 ifstream infile(p.second);
1295 if (!infile.fail()) {
1296 const char* res = mlp.run(infile,props);
1298 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
1299 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
1300 DWORD resplen=strlen(res);
1301 lpECB->WriteClient(lpECB->ConnID,(LPVOID)res,&resplen,0);
1302 return HSE_STATUS_SUCCESS;
1307 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Extension unable to open error template.");
1308 return WriteClientError(lpECB,"Unable to open error template, check settings.");
1311 DWORD WriteRedirectPage(LPEXTENSION_CONTROL_BLOCK lpECB, const IApplication* app, const char* file, ShibMLP& mlp, const char* headers=NULL)
1313 ifstream infile(file);
1314 if (!infile.fail()) {
1315 const char* res = mlp.run(infile,app->getPropertySet("Errors"));
1318 sprintf(buf,"Content-Length: %u\r\nContent-Type: text/html\r\n\r\n",strlen(res));
1322 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)h.c_str());
1325 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)buf);
1326 DWORD resplen=strlen(res);
1327 lpECB->WriteClient(lpECB->ConnID,(LPVOID)res,&resplen,0);
1328 return HSE_STATUS_SUCCESS;
1331 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Extension unable to open redirect template.");
1332 return WriteClientError(lpECB,"Unable to open redirect template, check settings.");
1335 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
1338 const IApplication* application=NULL;
1341 ostringstream threadid;
1342 threadid << "[" << getpid() << "] shire_handler" << '\0';
1343 saml::NDC ndc(threadid.str().c_str());
1345 // Determine web site number. This can't really fail, I don't think.
1347 GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
1349 // Match site instance to host name, skip if no match.
1350 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
1351 if (map_i==g_Sites.end())
1352 return WriteClientError(lpECB,"Shibboleth filter not configured for this web site.");
1354 // We lock the configuration system for the duration.
1355 IConfig* conf=g_Config->getINI();
1356 Locker locker(conf);
1358 // Map request to application and content settings.
1359 IRequestMapper* mapper=conf->getRequestMapper();
1360 Locker locker2(mapper);
1361 IRequestMapper::Settings settings=map_request(lpECB,mapper,map_i->second,targeturl);
1362 pair<bool,const char*> application_id=settings.first->getString("applicationId");
1363 application=conf->getApplication(application_id.second);
1364 const IPropertySet* sessionProps=application ? application->getPropertySet("Sessions") : NULL;
1365 if (!application || !sessionProps)
1366 return WriteClientError(lpECB,"Unable to map request to application session settings, check configuration.");
1368 SHIRE shire(application);
1370 const char* shireURL=shire.getShireURL(targeturl.c_str());
1372 return WriteClientError(lpECB,"Unable to map request to proper shireURL setting, check configuration.");
1374 // Make sure we only process the SHIRE requests.
1375 if (!strstr(targeturl.c_str(),shireURL))
1376 return WriteClientError(lpECB,"ISAPI extension can only be invoked to process incoming sessions."
1377 "Make sure the mapped file extension doesn't match actual content.");
1379 pair<const char*,const char*> shib_cookie=shire.getCookieNameProps();
1381 // Make sure this is SSL, if it should be
1382 pair<bool,bool> shireSSL=sessionProps->getBool("shireSSL");
1383 if (!shireSSL.first || shireSSL.second) {
1384 GetServerVariable(lpECB,"HTTPS",buf,10);
1386 throw ShibTargetException(SHIBRPC_OK,"blocked non-SSL access to SHIRE POST processor");
1389 pair<bool,bool> httpRedirects=sessionProps->getBool("httpRedirects");
1390 pair<bool,const char*> redirectPage=sessionProps->getString("redirectPage");
1391 if (httpRedirects.first && !httpRedirects.second && !redirectPage.first)
1392 return WriteClientError(lpECB,"HTML-based redirection requires a redirectPage property.");
1394 // Check for Mac web browser
1398 GetServerVariable(lpECB,"HTTP_USER_AGENT",agent,64);
1399 if (strstr(agent,"AppleWebKit/"))
1403 // If this is a GET, we manufacture an AuthnRequest.
1404 if (!stricmp(lpECB->lpszMethod,"GET")) {
1405 const char* areq=lpECB->lpszQueryString ? shire.getLazyAuthnRequest(lpECB->lpszQueryString) : NULL;
1407 throw ShibTargetException(SHIBRPC_OK, "malformed arguments to request a new session");
1408 if (!httpRedirects.first || httpRedirects.second) {
1409 string hdrs=string("Location: ") + areq + "\r\n"
1410 "Content-Type: text/html\r\n"
1411 "Content-Length: 40\r\n"
1412 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
1413 "Cache-Control: private,no-store,no-cache\r\n\r\n";
1414 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"302 Moved",0,(LPDWORD)hdrs.c_str());
1415 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
1417 lpECB->WriteClient(lpECB->ConnID,(LPVOID)redmsg,&resplen,HSE_IO_SYNC);
1418 return HSE_STATUS_SUCCESS;
1421 ShibMLP markupProcessor;
1422 markupProcessor.insert("requestURL",areq);
1423 return WriteRedirectPage(lpECB, application, redirectPage.second, markupProcessor);
1426 else if (stricmp(lpECB->lpszMethod,"POST"))
1427 throw ShibTargetException(SHIBRPC_OK,"blocked non-POST to SHIRE POST processor");
1429 // Sure sure this POST is an appropriate content type
1430 if (!lpECB->lpszContentType || stricmp(lpECB->lpszContentType,"application/x-www-form-urlencoded"))
1431 throw ShibTargetException(SHIBRPC_OK,"blocked bad content-type to SHIRE POST processor");
1434 pair<const char*,const char*> elements=pair<const char*,const char*>(NULL,NULL);
1435 if (lpECB->cbTotalBytes > 1024*1024) // 1MB?
1436 throw ShibTargetException(SHIBRPC_OK,"blocked too-large a post to SHIRE POST processor");
1437 else if (lpECB->cbTotalBytes!=lpECB->cbAvailable) {
1440 DWORD datalen=lpECB->cbTotalBytes;
1443 BOOL ret=lpECB->ReadClient(lpECB->ConnID,buf,&buflen);
1444 if (!ret || !buflen)
1445 throw ShibTargetException(SHIBRPC_OK,"error reading POST data from browser");
1446 cgistr.append(buf,buflen);
1449 elements=shire.getFormSubmission(cgistr.c_str(),cgistr.length());
1452 elements=shire.getFormSubmission(reinterpret_cast<char*>(lpECB->lpbData),lpECB->cbAvailable);
1454 // Make sure the SAML Response parameter exists
1455 if (!elements.first || !*elements.first)
1456 throw ShibTargetException(SHIBRPC_OK, "SHIRE POST failed to find SAMLResponse form element");
1458 // Make sure the target parameter exists
1459 if (!elements.second || !*elements.second)
1460 throw ShibTargetException(SHIBRPC_OK, "SHIRE POST failed to find TARGET form element");
1462 GetServerVariable(lpECB,"REMOTE_ADDR",buf,16);
1464 // Process the post.
1466 RPCError* status=NULL;
1467 ShibMLP markupProcessor;
1468 markupProcessor.insert("requestURL", targeturl.c_str());
1470 status = shire.sessionCreate(elements.first,buf,cookie);
1472 catch (ShibTargetException &e) {
1473 markupProcessor.insert("errorType", "Session Creation Service Error");
1474 markupProcessor.insert("errorText", e.what());
1475 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
1476 return WriteClientError(lpECB, application, "shire", markupProcessor);
1480 markupProcessor.insert("errorType", "Session Creation Service Error");
1481 markupProcessor.insert("errorText", "Unexpected Exception");
1482 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
1483 return WriteClientError(lpECB, application, "shire", markupProcessor);
1487 if (status->isError()) {
1488 if (status->isRetryable()) {
1490 const char* loc=shire.getAuthnRequest(elements.second);
1491 if (!httpRedirects.first || httpRedirects.second) {
1492 string hdrs=string("Location: ") + loc + "\r\n"
1493 "Content-Type: text/html\r\n"
1494 "Content-Length: 40\r\n"
1495 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
1496 "Cache-Control: private,no-store,no-cache\r\n\r\n";
1497 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"302 Moved",0,(LPDWORD)hdrs.c_str());
1498 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
1500 lpECB->WriteClient(lpECB->ConnID,(LPVOID)redmsg,&resplen,HSE_IO_SYNC);
1501 return HSE_STATUS_SUCCESS;
1504 markupProcessor.insert("requestURL",loc);
1505 return WriteRedirectPage(lpECB, application, redirectPage.second, markupProcessor);
1509 // Return this error to the user.
1510 markupProcessor.insert(*status);
1512 return WriteClientError(lpECB,application,"shire",markupProcessor);
1516 // We've got a good session, set the cookie and redirect to target.
1517 cookie = string("Set-Cookie: ") + shib_cookie.first + '=' + cookie + shib_cookie.second + "\r\n"
1518 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
1519 "Cache-Control: private,no-store,no-cache\r\n";
1520 if (!httpRedirects.first || httpRedirects.second) {
1521 cookie=cookie + "Content-Type: text/html\r\nLocation: " + elements.second + "\r\nContent-Length: 40\r\n\r\n";
1522 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"302 Moved",0,(LPDWORD)cookie.c_str());
1523 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
1525 lpECB->WriteClient(lpECB->ConnID,(LPVOID)redmsg,&resplen,HSE_IO_SYNC);
1526 return HSE_STATUS_SUCCESS;
1529 markupProcessor.insert("requestURL",elements.second);
1530 return WriteRedirectPage(lpECB, application, redirectPage.second, markupProcessor, cookie.c_str());
1533 catch (ShibTargetException &e) {
1535 ShibMLP markupProcessor;
1536 markupProcessor.insert("requestURL", targeturl.c_str());
1537 markupProcessor.insert("errorType", "Session Creation Service Error");
1538 markupProcessor.insert("errorText", e.what());
1539 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
1540 return WriteClientError(lpECB,application,"shire",markupProcessor);
1546 ShibMLP markupProcessor;
1547 markupProcessor.insert("requestURL", targeturl.c_str());
1548 markupProcessor.insert("errorType", "Session Creation Service Error");
1549 markupProcessor.insert("errorText", "Unexpected Exception");
1550 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
1551 return WriteClientError(lpECB,application,"shire",markupProcessor);
1556 return HSE_STATUS_ERROR;