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
371 ShibTargetIsapiF(PHTTP_FILTER_CONTEXT pfc, PHTTP_FILTER_PREPROC_HEADERS pn, const site_t& site) {
373 // URL path always come from IIS.
375 GetHeader(pn,pfc,"url",url,256,false);
377 // Port may come from IIS or from site def.
379 if (!g_bNormalizeRequest || (pfc->fIsSecurePort && site.m_sslport.empty()) || (!pfc->fIsSecurePort && site.m_port.empty()))
380 GetServerVariable(pfc,"SERVER_PORT",port,10);
381 else if (pfc->fIsSecurePort) {
382 strncpy(port,site.m_sslport.c_str(),10);
383 static_cast<char*>(port)[10]=0;
386 strncpy(port,site.m_port.c_str(),10);
387 static_cast<char*>(port)[10]=0;
390 // Scheme may come from site def or be derived from IIS.
391 const char* scheme=site.m_scheme.c_str();
392 if (!scheme || !*scheme || !g_bNormalizeRequest)
393 scheme=pfc->fIsSecurePort ? "https" : "http";
395 // Get the rest of the server variables.
396 dynabuf remote_addr(16),method(5),content_type(32),hostname(32);
397 GetServerVariable(pfc,"SERVER_NAME",hostname,32);
398 GetServerVariable(pfc,"REMOTE_ADDR",remote_addr,16);
399 // The last two appear to be unavailable to this filter hook, but we don't need them.
400 GetServerVariable(pfc,"REQUEST_METHOD",method,5,false);
401 GetServerVariable(pfc,"CONTENT_TYPE",content_type,32,false);
403 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
404 const char* host=hostname;
405 if (site.m_name!=host && site.m_aliases.find(host)==site.m_aliases.end())
406 host=site.m_name.c_str();
408 init(g_Config, scheme, host, atoi(port), url, content_type, remote_addr, method);
413 ~ShibTargetIsapiF() { }
415 virtual void log(ShibLogLevel level, const string &msg) {
416 LogEvent(NULL, (level == LogLevelDebug ? EVENTLOG_INFORMATION_TYPE :
417 (level == LogLevelInfo ? EVENTLOG_INFORMATION_TYPE :
418 (level == LogLevelWarn ? EVENTLOG_WARNING_TYPE : EVENTLOG_ERROR_TYPE))),
419 2100, NULL, msg.c_str());
421 virtual string getCookies(void) {
423 GetHeader(m_pn, m_pfc, "Cookie:", buf, 128, false);
424 return buf.empty() ? "" : buf;
427 virtual void clearHeader(const string &name) {
428 string hdr = name + ":";
429 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), "");
431 virtual void setHeader(const string &name, const string &value) {
432 string hdr = name + ":";
433 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()),
434 const_cast<char*>(value.c_str()));
436 virtual string getHeader(const string &name) {
437 string hdr = name + ":";
439 GetHeader(m_pn, m_pfc, const_cast<char*>(hdr.c_str()), buf, 1024, false);
442 virtual void setRemoteUser(const string &user) {
443 setHeader(string("remote-user"), user);
445 virtual string getRemoteUser(void) {
446 return getHeader(string("remote-user"));
448 virtual void* sendPage(
451 const string& content_type="text/html",
452 const Iterator<header_t>& headers=EMPTY(header_t)) {
453 string hdr = string ("Connection: close\r\nContent-type: ") + content_type + "\r\n";
454 while (headers.hasNext()) {
455 const header_t& h=headers.next();
456 hdr += h.first + ": " + h.second + "\r\n";
459 const char* codestr="200 OK";
461 case 403: codestr="403 Forbidden"; break;
462 case 404: codestr="404 Not Found"; break;
463 case 500: codestr="500 Server Error"; break;
465 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, (void*)codestr, (DWORD)hdr.c_str(), 0);
466 DWORD resplen = msg.size();
467 m_pfc->WriteClient(m_pfc, (LPVOID)msg.c_str(), &resplen, 0);
468 return (void*)SF_STATUS_REQ_FINISHED;
470 virtual void* sendRedirect(const string& url) {
471 // XXX: Don't support the httpRedirect option, yet.
472 string hdrs=m_cookie + string("Location: ") + url + "\r\n"
473 "Content-Type: text/html\r\n"
474 "Content-Length: 40\r\n"
475 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
476 "Cache-Control: private,no-store,no-cache\r\n\r\n";
477 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER,
478 "302 Please Wait", (DWORD)hdrs.c_str(), 0);
479 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
481 m_pfc->WriteClient(m_pfc, (LPVOID)redmsg, &resplen, 0);
482 return reinterpret_cast<void*>(SF_STATUS_REQ_FINISHED);
484 // XXX: We might not ever hit the 'decline' status in this filter.
485 //virtual void* returnDecline(void) { }
486 virtual void* returnOK(void) { return (void*) SF_STATUS_REQ_NEXT_NOTIFICATION; }
488 // The filter never processes the POST, so stub these methods.
489 virtual void setCookie(const string &name, const string &value) {
490 // Set the cookie for later. Use it during the redirect.
491 m_cookie += "Set-Cookie: " + name + "=" + value + "\r\n";
493 virtual string getArgs(void) { throw runtime_error("getArgs not implemented"); }
494 virtual string getPostData(void) { throw runtime_error("getPostData not implemented"); }
496 PHTTP_FILTER_CONTEXT m_pfc;
497 PHTTP_FILTER_PREPROC_HEADERS m_pn;
501 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const char* msg)
503 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
504 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
505 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)ctype,0);
506 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Filter Error</TITLE></HEAD><BODY>"
507 "<H1>Shibboleth Filter Error</H1>";
508 DWORD resplen=strlen(xmsg);
509 pfc->WriteClient(pfc,(LPVOID)xmsg,&resplen,0);
511 pfc->WriteClient(pfc,(LPVOID)msg,&resplen,0);
512 static const char* xmsg2="</BODY></HTML>";
513 resplen=strlen(xmsg2);
514 pfc->WriteClient(pfc,(LPVOID)xmsg2,&resplen,0);
515 return SF_STATUS_REQ_FINISHED;
518 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
520 // Is this a log notification?
521 if (notificationType==SF_NOTIFY_LOG)
523 if (pfc->pFilterContext)
524 ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=static_cast<LPCSTR>(pfc->pFilterContext);
525 return SF_STATUS_REQ_NEXT_NOTIFICATION;
528 PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
531 // Determine web site number. This can't really fail, I don't think.
533 GetServerVariable(pfc,"INSTANCE_ID",buf,10);
535 // Match site instance to host name, skip if no match.
536 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
537 if (map_i==g_Sites.end())
538 return SF_STATUS_REQ_NEXT_NOTIFICATION;
540 ostringstream threadid;
541 threadid << "[" << getpid() << "] isapi_shib" << '\0';
542 saml::NDC ndc(threadid.str().c_str());
544 ShibTargetIsapiF stf(pfc, pn, map_i->second);
546 // "false" because we don't override the Shib settings
547 pair<bool,void*> res = stf.doCheckAuthN();
548 if (res.first) return (DWORD)res.second;
550 // "false" because we don't override the Shib settings
551 res = stf.doExportAssertions();
552 if (res.first) return (DWORD)res.second;
554 res = stf.doCheckAuthZ();
555 if (res.first) return (DWORD)res.second;
557 return SF_STATUS_REQ_NEXT_NOTIFICATION;
560 return WriteClientError(pfc,"Out of Memory");
563 if (e==ERROR_NO_DATA)
564 return WriteClientError(pfc,"A required variable or header was empty.");
566 return WriteClientError(pfc,"Server detected unexpected IIS error.");
570 return WriteClientError(pfc,"Server caught an unknown exception.");
574 return WriteClientError(pfc,"Server reached unreachable code, save my walrus!");
579 IRequestMapper::Settings map_request(
580 PHTTP_FILTER_CONTEXT pfc, PHTTP_FILTER_PREPROC_HEADERS pn, IRequestMapper* mapper, const site_t& site, string& target
583 // URL path always come from IIS.
585 GetHeader(pn,pfc,"url",url,256,false);
587 // Port may come from IIS or from site def.
589 if (site.m_port.empty() || !g_bNormalizeRequest)
590 GetServerVariable(pfc,"SERVER_PORT",port,10);
592 strncpy(port,site.m_port.c_str(),10);
593 static_cast<char*>(port)[10]=0;
596 // Scheme may come from site def or be derived from IIS.
597 const char* scheme=site.m_scheme.c_str();
598 if (!scheme || !*scheme || !g_bNormalizeRequest)
599 scheme=pfc->fIsSecurePort ? "https" : "http";
601 // Start with scheme and hostname.
602 if (g_bNormalizeRequest) {
603 target = string(scheme) + "://" + site.m_name;
607 GetServerVariable(pfc,"SERVER_NAME",name,64);
608 target = string(scheme) + "://" + static_cast<char*>(name);
611 // If port is non-default, append it.
612 if ((!strcmp(scheme,"http") && port!="80") || (!strcmp(scheme,"https") && port!="443"))
613 target = target + ':' + static_cast<char*>(port);
617 target+=static_cast<char*>(url);
619 return mapper->getSettingsFromParsedURL(scheme,site.m_name.c_str(),strtoul(port,NULL,10),url);
622 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const IApplication* app, const char* page, ShibMLP& mlp)
624 const IPropertySet* props=app->getPropertySet("Errors");
626 pair<bool,const char*> p=props->getString(page);
628 ifstream infile(p.second);
629 if (!infile.fail()) {
630 const char* res = mlp.run(infile,props);
632 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
633 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)ctype,0);
634 DWORD resplen=strlen(res);
635 pfc->WriteClient(pfc,(LPVOID)res,&resplen,0);
636 return SF_STATUS_REQ_FINISHED;
642 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Filter unable to open error template.");
643 return WriteClientError(pfc,"Unable to open error template, check settings.");
646 DWORD WriteRedirectPage(PHTTP_FILTER_CONTEXT pfc, const IApplication* app, const char* file, ShibMLP& mlp, const char* headers=NULL)
648 ifstream infile(file);
649 if (!infile.fail()) {
650 const char* res = mlp.run(infile,app->getPropertySet("Errors"));
653 sprintf(buf,"Content-Length: %u\r\nContent-Type: text/html\r\n\r\n",strlen(res));
657 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)h.c_str(),0);
660 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)buf,0);
661 DWORD resplen=strlen(res);
662 pfc->WriteClient(pfc,(LPVOID)res,&resplen,0);
663 return SF_STATUS_REQ_FINISHED;
666 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Extension unable to open redirect template.");
667 return WriteClientError(pfc,"Unable to open redirect template, check settings.");
670 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
672 // Is this a log notification?
673 if (notificationType==SF_NOTIFY_LOG)
675 if (pfc->pFilterContext)
676 ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=static_cast<LPCSTR>(pfc->pFilterContext);
677 return SF_STATUS_REQ_NEXT_NOTIFICATION;
680 PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
683 // Determine web site number. This can't really fail, I don't think.
685 GetServerVariable(pfc,"INSTANCE_ID",buf,10);
687 // Match site instance to host name, skip if no match.
688 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
689 if (map_i==g_Sites.end())
690 return SF_STATUS_REQ_NEXT_NOTIFICATION;
692 ostringstream threadid;
693 threadid << "[" << getpid() << "] isapi_shib" << '\0';
694 saml::NDC ndc(threadid.str().c_str());
696 // We lock the configuration system for the duration.
697 IConfig* conf=g_Config->getINI();
700 // Map request to application and content settings.
702 IRequestMapper* mapper=conf->getRequestMapper();
703 Locker locker2(mapper);
704 IRequestMapper::Settings settings=map_request(pfc,pn,mapper,map_i->second,targeturl);
705 pair<bool,const char*> application_id=settings.first->getString("applicationId");
706 const IApplication* application=conf->getApplication(application_id.second);
708 return WriteClientError(pfc,"Unable to map request to application settings, check configuration.");
710 // Declare SHIRE object for this request.
711 SHIRE shire(application);
713 const char* shireURL=shire.getShireURL(targeturl.c_str());
715 return WriteClientError(pfc,"Unable to map request to proper shireURL setting, check configuration.");
717 // If the user is accessing the SHIRE acceptance point, pass it on.
718 if (targeturl.find(shireURL)!=string::npos)
719 return SF_STATUS_REQ_NEXT_NOTIFICATION;
721 // Now check the policy for this request.
722 pair<bool,bool> requireSession=settings.first->getBool("requireSession");
723 pair<const char*,const char*> shib_cookie=shire.getCookieNameProps();
724 pair<bool,bool> httpRedirects=application->getPropertySet("Sessions")->getBool("httpRedirects");
725 pair<bool,const char*> redirectPage=application->getPropertySet("Sessions")->getString("redirectPage");
726 if (httpRedirects.first && !httpRedirects.second && !redirectPage.first)
727 return WriteClientError(pfc,"HTML-based redirection requires a redirectPage property.");
729 // Check for session cookie.
730 const char* session_id=NULL;
731 GetHeader(pn,pfc,"Cookie:",buf,128,false);
732 Category::getInstance("isapi_shib.HttpFilterProc").debug("cookie header is {%s}",(const char*)buf);
733 if (!buf.empty() && (session_id=strstr(buf,shib_cookie.first))) {
734 session_id+=strlen(shib_cookie.first) + 1; /* Skip over the '=' */
735 char* cookieend=strchr(session_id,';');
737 *cookieend = '\0'; /* Ignore anyting after a ; */
740 if (!session_id || !*session_id) {
741 // If no session required, bail now.
742 if (!requireSession.second)
743 return SF_STATUS_REQ_NEXT_NOTIFICATION;
745 // No acceptable cookie, and we require a session. Generate an AuthnRequest.
746 const char* areq = shire.getAuthnRequest(targeturl.c_str());
747 if (!httpRedirects.first || httpRedirects.second) {
748 string hdrs=string("Location: ") + areq + "\r\n"
749 "Content-Type: text/html\r\n"
750 "Content-Length: 40\r\n"
751 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
752 "Cache-Control: private,no-store,no-cache\r\n\r\n";
753 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"302 Please Wait",(DWORD)hdrs.c_str(),0);
754 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
756 pfc->WriteClient(pfc,(LPVOID)redmsg,&resplen,0);
757 return SF_STATUS_REQ_FINISHED;
760 ShibMLP markupProcessor;
761 markupProcessor.insert("requestURL",areq);
762 return WriteRedirectPage(pfc, application, redirectPage.second, markupProcessor);
766 // Make sure this session is still valid.
767 RPCError* status = NULL;
768 ShibMLP markupProcessor;
769 markupProcessor.insert("requestURL", targeturl);
772 GetServerVariable(pfc,"REMOTE_ADDR",abuf,16);
774 status = shire.sessionIsValid(session_id, abuf);
776 catch (ShibTargetException &e) {
777 markupProcessor.insert("errorType", "Session Processing Error");
778 markupProcessor.insert("errorText", e.what());
779 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
780 return WriteClientError(pfc, application, "shire", markupProcessor);
784 markupProcessor.insert("errorType", "Session Processing Error");
785 markupProcessor.insert("errorText", "Unexpected Exception");
786 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
787 return WriteClientError(pfc, application, "shire", markupProcessor);
792 if (status->isError()) {
793 if (!requireSession.second)
794 return SF_STATUS_REQ_NEXT_NOTIFICATION;
795 else if (status->isRetryable()) {
796 // Oops, session is invalid. Generate AuthnRequest.
798 const char* areq = shire.getAuthnRequest(targeturl.c_str());
799 if (!httpRedirects.first || httpRedirects.second) {
800 string hdrs=string("Location: ") + areq + "\r\n"
801 "Content-Type: text/html\r\n"
802 "Content-Length: 40\r\n"
803 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
804 "Cache-Control: private,no-store,no-cache\r\n\r\n";
805 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"302 Please Wait",(DWORD)hdrs.c_str(),0);
806 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
808 pfc->WriteClient(pfc,(LPVOID)redmsg,&resplen,0);
809 return SF_STATUS_REQ_FINISHED;
812 markupProcessor.insert("requestURL",areq);
813 return WriteRedirectPage(pfc, application, redirectPage.second, markupProcessor);
817 // return the error page to the user
818 markupProcessor.insert(*status);
820 return WriteClientError(pfc, application, "shire", markupProcessor);
827 vector<SAMLAssertion*> assertions;
828 SAMLAuthenticationStatement* sso_statement=NULL;
831 status = rm.getAssertions(session_id, abuf, assertions, &sso_statement);
833 catch (ShibTargetException &e) {
834 markupProcessor.insert("errorType", "Attribute Processing Error");
835 markupProcessor.insert("errorText", e.what());
836 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
837 return WriteClientError(pfc, application, "rm", markupProcessor);
841 markupProcessor.insert("errorType", "Attribute Processing Error");
842 markupProcessor.insert("errorText", "Unexpected Exception");
843 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
844 return WriteClientError(pfc, application, "rm", markupProcessor);
848 if (status->isError()) {
849 markupProcessor.insert(*status);
851 return WriteClientError(pfc, application, "rm", markupProcessor);
855 // Do we have an access control plugin?
856 if (settings.second) {
857 Locker acllock(settings.second);
858 if (!settings.second->authorized(*sso_statement,assertions)) {
859 for (int k = 0; k < assertions.size(); k++)
860 delete assertions[k];
861 delete sso_statement;
862 return WriteClientError(pfc, application, "access", markupProcessor);
866 // Get the AAP providers, which contain the attribute policy info.
867 Iterator<IAAP*> provs=application->getAAPProviders();
869 // Clear out the list of mapped attributes
870 while (provs.hasNext()) {
871 IAAP* aap=provs.next();
874 Iterator<const IAttributeRule*> rules=aap->getAttributeRules();
875 while (rules.hasNext()) {
876 const char* header=rules.next()->getHeader();
878 string hname=string(header) + ':';
879 pn->SetHeader(pfc,const_cast<char*>(hname.c_str()),"");
885 for (int k = 0; k < assertions.size(); k++)
886 delete assertions[k];
887 delete sso_statement;
888 markupProcessor.insert("errorType", "Attribute Processing Error");
889 markupProcessor.insert("errorText", "Unexpected Exception");
890 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
891 return WriteClientError(pfc, application, "rm", markupProcessor);
897 // Maybe export the first assertion.
898 pn->SetHeader(pfc,"remote-user:","");
899 pn->SetHeader(pfc,"Shib-Attributes:","");
900 pair<bool,bool> exp=settings.first->getBool("exportAssertion");
901 if (exp.first && exp.second && assertions.size()) {
903 RM::serialize(*(assertions[0]), assertion);
904 string::size_type lfeed;
905 while ((lfeed=assertion.find('\n'))!=string::npos)
906 assertion.erase(lfeed,1);
907 pn->SetHeader(pfc,"Shib-Attributes:",const_cast<char*>(assertion.c_str()));
910 pn->SetHeader(pfc,"Shib-Origin-Site:","");
911 pn->SetHeader(pfc,"Shib-Authentication-Method:","");
912 pn->SetHeader(pfc,"Shib-NameIdentifier-Format:","");
914 // Export the SAML AuthnMethod and the origin site name.
915 auto_ptr_char os(sso_statement->getSubject()->getNameIdentifier()->getNameQualifier());
916 auto_ptr_char am(sso_statement->getAuthMethod());
917 pn->SetHeader(pfc,"Shib-Origin-Site:", const_cast<char*>(os.get()));
918 pn->SetHeader(pfc,"Shib-Authentication-Method:", const_cast<char*>(am.get()));
921 AAP wrapper(provs,sso_statement->getSubject()->getNameIdentifier()->getFormat(),Constants::SHIB_ATTRIBUTE_NAMESPACE_URI);
922 if (!wrapper.fail() && wrapper->getHeader()) {
923 auto_ptr_char form(sso_statement->getSubject()->getNameIdentifier()->getFormat());
924 auto_ptr_char nameid(sso_statement->getSubject()->getNameIdentifier()->getName());
925 pn->SetHeader(pfc,"Shib-NameIdentifier-Format:",const_cast<char*>(form.get()));
926 if (!strcmp(wrapper->getHeader(),"REMOTE_USER")) {
927 char* principal=const_cast<char*>(nameid.get());
928 pn->SetHeader(pfc,"remote-user:",principal);
929 pfc->pFilterContext=pfc->AllocMem(pfc,strlen(principal)+1,0);
930 if (pfc->pFilterContext)
931 strcpy(static_cast<char*>(pfc->pFilterContext),principal);
934 string hname=string(wrapper->getHeader()) + ':';
935 pn->SetHeader(pfc,const_cast<char*>(wrapper->getHeader()),const_cast<char*>(nameid.get()));
939 pn->SetHeader(pfc,"Shib-Application-ID:","");
940 pn->SetHeader(pfc,"Shib-Application-ID:",const_cast<char*>(application_id.second));
942 // Export the attributes.
943 Iterator<SAMLAssertion*> a_iter(assertions);
944 while (a_iter.hasNext()) {
945 SAMLAssertion* assert=a_iter.next();
946 Iterator<SAMLStatement*> statements=assert->getStatements();
947 while (statements.hasNext()) {
948 SAMLAttributeStatement* astate=dynamic_cast<SAMLAttributeStatement*>(statements.next());
951 Iterator<SAMLAttribute*> attrs=astate->getAttributes();
952 while (attrs.hasNext()) {
953 SAMLAttribute* attr=attrs.next();
955 // Are we supposed to export it?
956 AAP wrapper(provs,attr->getName(),attr->getNamespace());
957 if (wrapper.fail() || !wrapper->getHeader())
960 Iterator<string> vals=attr->getSingleByteValues();
961 if (!strcmp(wrapper->getHeader(),"REMOTE_USER") && vals.hasNext()) {
962 char* principal=const_cast<char*>(vals.next().c_str());
963 pn->SetHeader(pfc,"remote-user:",principal);
964 pfc->pFilterContext=pfc->AllocMem(pfc,strlen(principal)+1,0);
965 if (pfc->pFilterContext)
966 strcpy(static_cast<char*>(pfc->pFilterContext),principal);
971 string hname=string(wrapper->getHeader()) + ':';
972 GetHeader(pn,pfc,const_cast<char*>(hname.c_str()),buf,256,false);
977 for (; vals.hasNext(); it++) {
978 string value = vals.next();
979 for (string::size_type pos = value.find_first_of(";", string::size_type(0));
981 pos = value.find_first_of(";", pos)) {
982 value.insert(pos, "\\");
988 header=header + ';' + value;
990 pn->SetHeader(pfc,const_cast<char*>(hname.c_str()),const_cast<char*>(header.c_str()));
997 for (int k = 0; k < assertions.size(); k++)
998 delete assertions[k];
999 delete sso_statement;
1001 return SF_STATUS_REQ_NEXT_NOTIFICATION;
1004 return WriteClientError(pfc,"Out of Memory");
1007 if (e==ERROR_NO_DATA)
1008 return WriteClientError(pfc,"A required variable or header was empty.");
1010 return WriteClientError(pfc,"Server detected unexpected IIS error.");
1014 return WriteClientError(pfc,"Server caught an unknown exception.");
1018 return WriteClientError(pfc,"Server reached unreachable code, save my walrus!");
1022 /****************************************************************************/
1025 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* msg)
1027 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
1028 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
1029 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
1030 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Error</TITLE></HEAD><BODY><H1>Shibboleth Error</H1>";
1031 DWORD resplen=strlen(xmsg);
1032 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg,&resplen,HSE_IO_SYNC);
1033 resplen=strlen(msg);
1034 lpECB->WriteClient(lpECB->ConnID,(LPVOID)msg,&resplen,HSE_IO_SYNC);
1035 static const char* xmsg2="</BODY></HTML>";
1036 resplen=strlen(xmsg2);
1037 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg2,&resplen,HSE_IO_SYNC);
1038 return HSE_STATUS_SUCCESS;
1042 class ShibTargetIsapiE : public ShibTarget
1045 ShibTargetIsapiE(LPEXTENSION_CONTROL_BLOCK lpECB, const site_t& site)
1048 GetServerVariable(lpECB,"HTTPS",ssl,5);
1049 bool SSL=(ssl=="on" || ssl=="ON");
1051 // URL path always come from IIS.
1053 GetServerVariable(lpECB,"URL",url,255);
1055 // Port may come from IIS or from site def.
1057 if (!g_bNormalizeRequest || (SSL && site.m_sslport.empty()) || (!SSL && site.m_port.empty()))
1058 GetServerVariable(lpECB,"SERVER_PORT",port,10);
1060 strncpy(port,site.m_sslport.c_str(),10);
1061 static_cast<char*>(port)[10]=0;
1064 strncpy(port,site.m_port.c_str(),10);
1065 static_cast<char*>(port)[10]=0;
1068 // Scheme may come from site def or be derived from IIS.
1069 const char* scheme=site.m_scheme.c_str();
1070 if (!scheme || !*scheme || !g_bNormalizeRequest) {
1071 scheme = SSL ? "https" : "http";
1074 // Get the other server variables.
1075 dynabuf remote_addr(16),hostname(32);
1076 GetServerVariable(lpECB, "REMOTE_ADDR", remote_addr, 16);
1077 GetServerVariable(lpECB, "SERVER_NAME", hostname, 32);
1079 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
1080 const char* host=hostname;
1081 if (site.m_name!=host && site.m_aliases.find(host)==site.m_aliases.end())
1082 host=site.m_name.c_str();
1084 init(g_Config, scheme, host, atoi(port), url, lpECB->lpszContentType, remote_addr, lpECB->lpszMethod);
1088 ~ShibTargetIsapiE() { }
1090 virtual void log(ShibLogLevel level, const string &msg) {
1091 LogEvent(NULL, (level == LogLevelDebug ? EVENTLOG_INFORMATION_TYPE :
1092 (level == LogLevelInfo ? EVENTLOG_INFORMATION_TYPE :
1093 (level == LogLevelWarn ? EVENTLOG_WARNING_TYPE : EVENTLOG_ERROR_TYPE))),
1094 2100, NULL, msg.c_str());
1096 virtual void setCookie(const string &name, const string &value) {
1097 // Set the cookie for later. Use it during the redirect.
1098 m_cookie += "Set-Cookie: " + name + "=" + value + "\r\n";
1100 virtual string getArgs(void) {
1101 return string(m_lpECB->lpszQueryString ? m_lpECB->lpszQueryString : "");
1103 virtual string getPostData(void) {
1104 if (m_lpECB->cbTotalBytes > 1024*1024) // 1MB?
1105 throw FatalProfileException("Blocked too-large a submission to profile endpoint.");
1106 else if (m_lpECB->cbTotalBytes != m_lpECB->cbAvailable) {
1109 DWORD datalen=m_lpECB->cbTotalBytes;
1112 BOOL ret = m_lpECB->ReadClient(m_lpECB->ConnID, buf, &buflen);
1113 if (!ret || !buflen)
1114 throw FatalProfileException("Error reading profile submission from browser.");
1115 cgistr.append(buf, buflen);
1121 return string(reinterpret_cast<char*>(m_lpECB->lpbData),m_lpECB->cbAvailable);
1123 virtual void* sendPage(
1126 const string& content_type="text/html",
1127 const Iterator<header_t>& headers=EMPTY(header_t)) {
1128 string hdr = string ("Connection: close\r\nContent-type: ") + content_type + "\r\n";
1129 for (int k = 0; k < headers.size(); k++) {
1130 hdr += headers[k].first + ": " + headers[k].second + "\r\n";
1133 const char* codestr="200 OK";
1135 case 403: codestr="403 Forbidden"; break;
1136 case 404: codestr="404 Not Found"; break;
1137 case 500: codestr="500 Server Error"; break;
1139 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, (void*)codestr, 0, (LPDWORD)hdr.c_str());
1140 DWORD resplen = msg.size();
1141 m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)msg.c_str(), &resplen, HSE_IO_SYNC);
1142 return (void*)HSE_STATUS_SUCCESS;
1144 virtual void* sendRedirect(const string& url) {
1145 // XXX: Don't support the httpRedirect option, yet.
1146 string hdrs = m_cookie + "Location: " + url + "\r\n"
1147 "Content-Type: text/html\r\n"
1148 "Content-Length: 40\r\n"
1149 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
1150 "Cache-Control: private,no-store,no-cache\r\n\r\n";
1151 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER,
1152 "302 Moved", 0, (LPDWORD)hdrs.c_str());
1153 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
1155 m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)redmsg, &resplen, HSE_IO_SYNC);
1156 return (void*)HSE_STATUS_SUCCESS;
1158 // Decline happens in the POST processor if this isn't the shire url
1159 // Note that it can also happen with HTAccess, but we don't support that, yet.
1160 virtual void* returnDecline(void) {
1162 WriteClientError(m_lpECB, "ISAPA extension can only be invoked to process incoming sessions."
1163 "Make sure the mapped file extension doesn't match actual content.");
1165 virtual void* returnOK(void) { return (void*) HSE_STATUS_SUCCESS; }
1167 virtual string getCookies(void) {
1169 GetServerVariable(m_lpECB, "HTTP_COOKIE", buf, 128, false);
1170 return buf.empty() ? "" : buf;
1173 // Not used in the extension.
1174 virtual void clearHeader(const string &name) { throw runtime_error("clearHeader not implemented"); }
1175 virtual void setHeader(const string &name, const string &value) { throw runtime_error("setHeader not implemented"); }
1176 virtual string getHeader(const string &name) { throw runtime_error("getHeader not implemented"); }
1177 virtual void setRemoteUser(const string &user) { throw runtime_error("setRemoteUser not implemented"); }
1178 virtual string getRemoteUser(void) { throw runtime_error("getRemoteUser not implemented"); }
1180 LPEXTENSION_CONTROL_BLOCK m_lpECB;
1184 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
1187 const IApplication* application=NULL;
1189 ostringstream threadid;
1190 threadid << "[" << getpid() << "] shire_handler" << '\0';
1191 saml::NDC ndc(threadid.str().c_str());
1193 // Determine web site number. This can't really fail, I don't think.
1195 GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
1197 // Match site instance to host name, skip if no match.
1198 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
1199 if (map_i==g_Sites.end())
1200 return WriteClientError(lpECB, "Shibboleth Extension not configured for this web site.");
1202 ShibTargetIsapiE ste(lpECB, map_i->second);
1203 pair<bool,void*> res = ste.doHandleProfile();
1204 if (res.first) return (DWORD)res.second;
1206 return WriteClientError(lpECB, "Shibboleth Extension failed to process request");
1210 return WriteClientError(lpECB,"Out of Memory");
1213 if (e==ERROR_NO_DATA)
1214 return WriteClientError(lpECB,"A required variable or header was empty.");
1216 return WriteClientError(lpECB,"Server detected unexpected IIS error.");
1220 return WriteClientError(lpECB,"Server caught an unknown exception.");
1224 // If we get here we've got an error.
1225 return HSE_STATUS_ERROR;
1229 IRequestMapper::Settings map_request(
1230 LPEXTENSION_CONTROL_BLOCK lpECB, IRequestMapper* mapper, const site_t& site, string& target
1234 GetServerVariable(lpECB,"HTTPS",ssl,5);
1235 bool SSL=(ssl=="on" || ssl=="ON");
1237 // URL path always come from IIS.
1239 GetServerVariable(lpECB,"URL",url,255);
1241 // Port may come from IIS or from site def.
1243 if (site.m_port.empty() || !g_bNormalizeRequest)
1244 GetServerVariable(lpECB,"SERVER_PORT",port,10);
1246 strncpy(port,site.m_port.c_str(),10);
1247 static_cast<char*>(port)[10]=0;
1250 // Scheme may come from site def or be derived from IIS.
1251 const char* scheme=site.m_scheme.c_str();
1252 if (!scheme || !*scheme || !g_bNormalizeRequest) {
1253 scheme = SSL ? "https" : "http";
1256 // Start with scheme and hostname.
1257 if (g_bNormalizeRequest) {
1258 target = string(scheme) + "://" + site.m_name;
1262 GetServerVariable(lpECB,"SERVER_NAME",name,64);
1263 target = string(scheme) + "://" + static_cast<char*>(name);
1266 // If port is non-default, append it.
1267 if ((!strcmp(scheme,"http") && port!="80") || (!strcmp(scheme,"https") && port!="443"))
1268 target = target + ':' + static_cast<char*>(port);
1272 target+=static_cast<char*>(url);
1274 return mapper->getSettingsFromParsedURL(scheme,site.m_name.c_str(),strtoul(port,NULL,10),url);
1277 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const IApplication* app, const char* page, ShibMLP& mlp)
1279 const IPropertySet* props=app->getPropertySet("Errors");
1281 pair<bool,const char*> p=props->getString(page);
1283 ifstream infile(p.second);
1284 if (!infile.fail()) {
1285 const char* res = mlp.run(infile,props);
1287 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
1288 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
1289 DWORD resplen=strlen(res);
1290 lpECB->WriteClient(lpECB->ConnID,(LPVOID)res,&resplen,0);
1291 return HSE_STATUS_SUCCESS;
1296 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Extension unable to open error template.");
1297 return WriteClientError(lpECB,"Unable to open error template, check settings.");
1300 DWORD WriteRedirectPage(LPEXTENSION_CONTROL_BLOCK lpECB, const IApplication* app, const char* file, ShibMLP& mlp, const char* headers=NULL)
1302 ifstream infile(file);
1303 if (!infile.fail()) {
1304 const char* res = mlp.run(infile,app->getPropertySet("Errors"));
1307 sprintf(buf,"Content-Length: %u\r\nContent-Type: text/html\r\n\r\n",strlen(res));
1311 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)h.c_str());
1314 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)buf);
1315 DWORD resplen=strlen(res);
1316 lpECB->WriteClient(lpECB->ConnID,(LPVOID)res,&resplen,0);
1317 return HSE_STATUS_SUCCESS;
1320 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Extension unable to open redirect template.");
1321 return WriteClientError(lpECB,"Unable to open redirect template, check settings.");
1324 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
1327 const IApplication* application=NULL;
1330 ostringstream threadid;
1331 threadid << "[" << getpid() << "] shire_handler" << '\0';
1332 saml::NDC ndc(threadid.str().c_str());
1334 // Determine web site number. This can't really fail, I don't think.
1336 GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
1338 // Match site instance to host name, skip if no match.
1339 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
1340 if (map_i==g_Sites.end())
1341 return WriteClientError(lpECB,"Shibboleth filter not configured for this web site.");
1343 // We lock the configuration system for the duration.
1344 IConfig* conf=g_Config->getINI();
1345 Locker locker(conf);
1347 // Map request to application and content settings.
1348 IRequestMapper* mapper=conf->getRequestMapper();
1349 Locker locker2(mapper);
1350 IRequestMapper::Settings settings=map_request(lpECB,mapper,map_i->second,targeturl);
1351 pair<bool,const char*> application_id=settings.first->getString("applicationId");
1352 application=conf->getApplication(application_id.second);
1353 const IPropertySet* sessionProps=application ? application->getPropertySet("Sessions") : NULL;
1354 if (!application || !sessionProps)
1355 return WriteClientError(lpECB,"Unable to map request to application session settings, check configuration.");
1357 SHIRE shire(application);
1359 const char* shireURL=shire.getShireURL(targeturl.c_str());
1361 return WriteClientError(lpECB,"Unable to map request to proper shireURL setting, check configuration.");
1363 // Make sure we only process the SHIRE requests.
1364 if (!strstr(targeturl.c_str(),shireURL))
1365 return WriteClientError(lpECB,"ISAPI extension can only be invoked to process incoming sessions."
1366 "Make sure the mapped file extension doesn't match actual content.");
1368 pair<const char*,const char*> shib_cookie=shire.getCookieNameProps();
1370 // Make sure this is SSL, if it should be
1371 pair<bool,bool> shireSSL=sessionProps->getBool("shireSSL");
1372 if (!shireSSL.first || shireSSL.second) {
1373 GetServerVariable(lpECB,"HTTPS",buf,10);
1375 throw ShibTargetException(SHIBRPC_OK,"blocked non-SSL access to SHIRE POST processor");
1378 pair<bool,bool> httpRedirects=sessionProps->getBool("httpRedirects");
1379 pair<bool,const char*> redirectPage=sessionProps->getString("redirectPage");
1380 if (httpRedirects.first && !httpRedirects.second && !redirectPage.first)
1381 return WriteClientError(lpECB,"HTML-based redirection requires a redirectPage property.");
1383 // Check for Mac web browser
1387 GetServerVariable(lpECB,"HTTP_USER_AGENT",agent,64);
1388 if (strstr(agent,"AppleWebKit/"))
1392 // If this is a GET, we manufacture an AuthnRequest.
1393 if (!stricmp(lpECB->lpszMethod,"GET")) {
1394 const char* areq=lpECB->lpszQueryString ? shire.getLazyAuthnRequest(lpECB->lpszQueryString) : NULL;
1396 throw ShibTargetException(SHIBRPC_OK, "malformed arguments to request a new session");
1397 if (!httpRedirects.first || httpRedirects.second) {
1398 string hdrs=string("Location: ") + areq + "\r\n"
1399 "Content-Type: text/html\r\n"
1400 "Content-Length: 40\r\n"
1401 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
1402 "Cache-Control: private,no-store,no-cache\r\n\r\n";
1403 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"302 Moved",0,(LPDWORD)hdrs.c_str());
1404 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
1406 lpECB->WriteClient(lpECB->ConnID,(LPVOID)redmsg,&resplen,HSE_IO_SYNC);
1407 return HSE_STATUS_SUCCESS;
1410 ShibMLP markupProcessor;
1411 markupProcessor.insert("requestURL",areq);
1412 return WriteRedirectPage(lpECB, application, redirectPage.second, markupProcessor);
1415 else if (stricmp(lpECB->lpszMethod,"POST"))
1416 throw ShibTargetException(SHIBRPC_OK,"blocked non-POST to SHIRE POST processor");
1418 // Sure sure this POST is an appropriate content type
1419 if (!lpECB->lpszContentType || stricmp(lpECB->lpszContentType,"application/x-www-form-urlencoded"))
1420 throw ShibTargetException(SHIBRPC_OK,"blocked bad content-type to SHIRE POST processor");
1423 pair<const char*,const char*> elements=pair<const char*,const char*>(NULL,NULL);
1424 if (lpECB->cbTotalBytes > 1024*1024) // 1MB?
1425 throw ShibTargetException(SHIBRPC_OK,"blocked too-large a post to SHIRE POST processor");
1426 else if (lpECB->cbTotalBytes!=lpECB->cbAvailable) {
1429 DWORD datalen=lpECB->cbTotalBytes;
1432 BOOL ret=lpECB->ReadClient(lpECB->ConnID,buf,&buflen);
1433 if (!ret || !buflen)
1434 throw ShibTargetException(SHIBRPC_OK,"error reading POST data from browser");
1435 cgistr.append(buf,buflen);
1438 elements=shire.getFormSubmission(cgistr.c_str(),cgistr.length());
1441 elements=shire.getFormSubmission(reinterpret_cast<char*>(lpECB->lpbData),lpECB->cbAvailable);
1443 // Make sure the SAML Response parameter exists
1444 if (!elements.first || !*elements.first)
1445 throw ShibTargetException(SHIBRPC_OK, "SHIRE POST failed to find SAMLResponse form element");
1447 // Make sure the target parameter exists
1448 if (!elements.second || !*elements.second)
1449 throw ShibTargetException(SHIBRPC_OK, "SHIRE POST failed to find TARGET form element");
1451 GetServerVariable(lpECB,"REMOTE_ADDR",buf,16);
1453 // Process the post.
1455 RPCError* status=NULL;
1456 ShibMLP markupProcessor;
1457 markupProcessor.insert("requestURL", targeturl.c_str());
1459 status = shire.sessionCreate(elements.first,buf,cookie);
1461 catch (ShibTargetException &e) {
1462 markupProcessor.insert("errorType", "Session Creation Service Error");
1463 markupProcessor.insert("errorText", e.what());
1464 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
1465 return WriteClientError(lpECB, application, "shire", markupProcessor);
1469 markupProcessor.insert("errorType", "Session Creation Service Error");
1470 markupProcessor.insert("errorText", "Unexpected Exception");
1471 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
1472 return WriteClientError(lpECB, application, "shire", markupProcessor);
1476 if (status->isError()) {
1477 if (status->isRetryable()) {
1479 const char* loc=shire.getAuthnRequest(elements.second);
1480 if (!httpRedirects.first || httpRedirects.second) {
1481 string hdrs=string("Location: ") + loc + "\r\n"
1482 "Content-Type: text/html\r\n"
1483 "Content-Length: 40\r\n"
1484 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
1485 "Cache-Control: private,no-store,no-cache\r\n\r\n";
1486 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"302 Moved",0,(LPDWORD)hdrs.c_str());
1487 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
1489 lpECB->WriteClient(lpECB->ConnID,(LPVOID)redmsg,&resplen,HSE_IO_SYNC);
1490 return HSE_STATUS_SUCCESS;
1493 markupProcessor.insert("requestURL",loc);
1494 return WriteRedirectPage(lpECB, application, redirectPage.second, markupProcessor);
1498 // Return this error to the user.
1499 markupProcessor.insert(*status);
1501 return WriteClientError(lpECB,application,"shire",markupProcessor);
1505 // We've got a good session, set the cookie and redirect to target.
1506 cookie = string("Set-Cookie: ") + shib_cookie.first + '=' + cookie + shib_cookie.second + "\r\n"
1507 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
1508 "Cache-Control: private,no-store,no-cache\r\n";
1509 if (!httpRedirects.first || httpRedirects.second) {
1510 cookie=cookie + "Content-Type: text/html\r\nLocation: " + elements.second + "\r\nContent-Length: 40\r\n\r\n";
1511 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"302 Moved",0,(LPDWORD)cookie.c_str());
1512 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
1514 lpECB->WriteClient(lpECB->ConnID,(LPVOID)redmsg,&resplen,HSE_IO_SYNC);
1515 return HSE_STATUS_SUCCESS;
1518 markupProcessor.insert("requestURL",elements.second);
1519 return WriteRedirectPage(lpECB, application, redirectPage.second, markupProcessor, cookie.c_str());
1522 catch (ShibTargetException &e) {
1524 ShibMLP markupProcessor;
1525 markupProcessor.insert("requestURL", targeturl.c_str());
1526 markupProcessor.insert("errorType", "Session Creation Service Error");
1527 markupProcessor.insert("errorText", e.what());
1528 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
1529 return WriteClientError(lpECB,application,"shire",markupProcessor);
1535 ShibMLP markupProcessor;
1536 markupProcessor.insert("requestURL", targeturl.c_str());
1537 markupProcessor.insert("errorType", "Session Creation Service Error");
1538 markupProcessor.insert("errorText", "Unexpected Exception");
1539 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
1540 return WriteClientError(lpECB,application,"shire",markupProcessor);
1545 return HSE_STATUS_ERROR;