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 scheme[] = { chLatin_s, chLatin_c, chLatin_h, chLatin_e, chLatin_m, chLatin_e, chNull };
85 static const XMLCh id[] = { chLatin_i, chLatin_d, chNull };
86 static const XMLCh Implementation[] =
87 { 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 };
88 static const XMLCh ISAPI[] = { chLatin_I, chLatin_S, chLatin_A, chLatin_P, chLatin_I, chNull };
89 static const XMLCh normalizeRequest[] =
90 { chLatin_n, chLatin_o, chLatin_r, chLatin_m, chLatin_a, chLatin_l, chLatin_i, chLatin_z, chLatin_e,
91 chLatin_R, chLatin_e, chLatin_q, chLatin_u, chLatin_e, chLatin_s, chLatin_t, chNull
93 static const XMLCh Site[] = { chLatin_S, chLatin_i, chLatin_t, chLatin_e, chNull };
96 site_t(const DOMElement* e)
98 auto_ptr_char n(e->getAttributeNS(NULL,name));
99 auto_ptr_char s(e->getAttributeNS(NULL,scheme));
100 auto_ptr_char p(e->getAttributeNS(NULL,port));
101 if (n.get()) m_name=n.get();
102 if (s.get()) m_scheme=s.get();
103 if (p.get()) m_port=p.get();
105 string m_scheme,m_name,m_port;
108 HINSTANCE g_hinstDLL;
109 ShibTargetConfig* g_Config = NULL;
110 map<string,site_t> g_Sites;
111 bool g_bNormalizeRequest = true;
115 LPCSTR lpUNCServerName,
121 LPCSTR messages[] = {message, NULL};
123 HANDLE hElog = RegisterEventSource(lpUNCServerName, "Shibboleth ISAPI Filter");
124 BOOL res = ReportEvent(hElog, wType, 0, dwEventID, lpUserSid, 1, 0, messages, NULL);
125 return (DeregisterEventSource(hElog) && res);
128 extern "C" __declspec(dllexport) BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID)
130 if (fdwReason==DLL_PROCESS_ATTACH)
135 extern "C" BOOL WINAPI GetExtensionVersion(HSE_VERSION_INFO* pVer)
142 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
143 "Extension mode startup not possible, is the DLL loaded as a filter?");
147 pVer->dwExtensionVersion=HSE_VERSION;
148 strncpy(pVer->lpszExtensionDesc,"Shibboleth ISAPI Extension",HSE_MAX_EXT_DLL_NAME_LEN-1);
152 extern "C" BOOL WINAPI TerminateExtension(DWORD)
154 return TRUE; // cleanup should happen when filter unloads
157 extern "C" BOOL WINAPI GetFilterVersion(PHTTP_FILTER_VERSION pVer)
162 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
163 "Reentrant filter initialization, ignoring...");
171 LPCSTR schemadir=getenv("SHIBSCHEMAS");
173 schemadir=SHIB_SCHEMAS;
174 LPCSTR config=getenv("SHIBCONFIG");
177 g_Config=&ShibTargetConfig::getConfig();
178 g_Config->setFeatures(
179 ShibTargetConfig::Listener |
180 ShibTargetConfig::Metadata |
181 ShibTargetConfig::AAP |
182 ShibTargetConfig::RequestMapper |
183 ShibTargetConfig::LocalExtensions |
184 ShibTargetConfig::Logging
186 if (!g_Config->init(schemadir,config)) {
188 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
189 "Filter startup failed during initialization, check shire log for help.");
193 // Access the implementation-specifics for site mappings.
194 IConfig* conf=g_Config->getINI();
196 const IPropertySet* props=conf->getPropertySet("SHIRE");
198 const DOMElement* impl=saml::XML::getFirstChildElement(
199 props->getElement(),ShibTargetConfig::SHIBTARGET_NS,Implementation
201 if (impl && (impl=saml::XML::getFirstChildElement(impl,ShibTargetConfig::SHIBTARGET_NS,ISAPI))) {
202 const XMLCh* flag=impl->getAttributeNS(NULL,normalizeRequest);
203 g_bNormalizeRequest=(!flag || !*flag || *flag==chDigit_1 || *flag==chLatin_t);
204 impl=saml::XML::getFirstChildElement(impl,ShibTargetConfig::SHIBTARGET_NS,Site);
206 auto_ptr_char id(impl->getAttributeNS(NULL,id));
208 g_Sites.insert(pair<string,site_t>(id.get(),site_t(impl)));
209 impl=saml::XML::getNextSiblingElement(impl,ShibTargetConfig::SHIBTARGET_NS,Site);
217 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Filter startup failed with an exception.");
222 pVer->dwFilterVersion=HTTP_FILTER_REVISION;
223 strncpy(pVer->lpszFilterDesc,"Shibboleth ISAPI Filter",SF_MAX_FILTER_DESC_LEN);
224 pVer->dwFlags=(SF_NOTIFY_ORDER_HIGH |
225 SF_NOTIFY_SECURE_PORT |
226 SF_NOTIFY_NONSECURE_PORT |
227 SF_NOTIFY_PREPROC_HEADERS |
229 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter initialized...");
233 extern "C" BOOL WINAPI TerminateFilter(DWORD)
236 g_Config->shutdown();
238 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter shut down...");
242 /* Next up, some suck-free versions of various APIs.
244 You DON'T require people to guess the buffer size and THEN tell them the right size.
245 Returning an LPCSTR is apparently way beyond their ken. Not to mention the fact that
246 constant strings aren't typed as such, making it just that much harder. These versions
247 are now updated to use a special growable buffer object, modeled after the standard
248 string class. The standard string won't work because they left out the option to
249 pre-allocate a non-constant buffer.
255 dynabuf() { bufptr=NULL; buflen=0; }
256 dynabuf(size_t s) { bufptr=new char[buflen=s]; *bufptr=0; }
257 ~dynabuf() { delete[] bufptr; }
258 size_t length() const { return bufptr ? strlen(bufptr) : 0; }
259 size_t size() const { return buflen; }
260 bool empty() const { return length()==0; }
261 void reserve(size_t s, bool keep=false);
262 void erase() { if (bufptr) memset(bufptr,0,buflen); }
263 operator char*() { return bufptr; }
264 bool operator ==(const char* s) const;
265 bool operator !=(const char* s) const { return !(*this==s); }
271 void dynabuf::reserve(size_t s, bool keep)
278 p[buflen]=bufptr[buflen];
284 bool dynabuf::operator==(const char* s) const
286 if (buflen==NULL || s==NULL)
287 return (buflen==NULL && s==NULL);
289 return strcmp(bufptr,s)==0;
292 void GetServerVariable(PHTTP_FILTER_CONTEXT pfc, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
293 throw (bad_alloc, DWORD)
299 while (!pfc->GetServerVariable(pfc,lpszVariable,s,&size))
301 // Grumble. Check the error.
302 DWORD e=GetLastError();
303 if (e==ERROR_INSUFFICIENT_BUFFER)
308 if (bRequired && s.empty())
312 void GetServerVariable(LPEXTENSION_CONTROL_BLOCK lpECB, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
313 throw (bad_alloc, DWORD)
319 while (lpECB->GetServerVariable(lpECB->ConnID,lpszVariable,s,&size))
321 // Grumble. Check the error.
322 DWORD e=GetLastError();
323 if (e==ERROR_INSUFFICIENT_BUFFER)
328 if (bRequired && s.empty())
332 void GetHeader(PHTTP_FILTER_PREPROC_HEADERS pn, PHTTP_FILTER_CONTEXT pfc,
333 LPSTR lpszName, dynabuf& s, DWORD size=80, bool bRequired=true)
334 throw (bad_alloc, DWORD)
340 while (!pn->GetHeader(pfc,lpszName,s,&size))
342 // Grumble. Check the error.
343 DWORD e=GetLastError();
344 if (e==ERROR_INSUFFICIENT_BUFFER)
349 if (bRequired && s.empty())
353 /****************************************************************************/
356 class ShibTargetIsapiF : public ShibTarget
359 ShibTargetIsapiF(PHTTP_FILTER_CONTEXT pfc, PHTTP_FILTER_PREPROC_HEADERS pn,
360 const site_t& site) {
362 // URL path always come from IIS.
364 GetHeader(pn,pfc,"url",url,256,false);
366 // Port may come from IIS or from site def.
368 if (site.m_port.empty() || !g_bNormalizeRequest)
369 GetServerVariable(pfc,"SERVER_PORT",port,10);
371 strncpy(port,site.m_port.c_str(),10);
372 static_cast<char*>(port)[10]=0;
375 // Scheme may come from site def or be derived from IIS.
376 const char* scheme=site.m_scheme.c_str();
377 if (!scheme || !*scheme || !g_bNormalizeRequest)
378 scheme=pfc->fIsSecurePort ? "https" : "http";
380 // Get the remote address
381 dynabuf remote_addr(16);
382 GetServerVariable(pfc,"REMOTE_ADDR",remote_addr,16);
384 // XXX: How do I get the content type and HTTP Method from this context?
386 // TODO: Need to allow for use of SERVER_NAME
388 init(g_Config, string(scheme), site.m_name, atoi(port),
389 string(url), string(""), // XXX: content type
390 string(remote_addr), string("") // XXX: http method
396 ~ShibTargetIsapiF() { }
398 virtual void log(ShibLogLevel level, const string &msg) {
399 LogEvent(NULL, (level == LogLevelDebug ? EVENTLOG_INFORMATION_TYPE :
400 (level == LogLevelInfo ? EVENTLOG_INFORMATION_TYPE :
401 (level == LogLevelWarn ? EVENTLOG_WARNING_TYPE : EVENTLOG_ERROR_TYPE))),
402 2100, NULL, msg.c_str());
404 virtual string getCookies(void) {
406 GetHeader(m_pn, m_pfc, "Cookie:", buf, 128, false);
407 return buf.empty() ? "" : buf;
410 virtual void clearHeader(const string &name) {
411 string hdr = name + ":";
412 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), "");
414 virtual void setHeader(const string &name, const string &value) {
415 string hdr = name + ":";
416 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()),
417 const_cast<char*>(value.c_str()));
419 virtual string getHeader(const string &name) {
420 string hdr = name + ":";
422 GetHeader(m_pn, m_pfc, const_cast<char*>(hdr.c_str()), buf, 1024, false);
425 virtual void setRemoteUser(const string &user) {
426 setHeader(string("remote-user"), user);
428 virtual string getRemoteUser(void) {
429 return getHeader(string("remote-user"));
431 virtual void* sendPage(const string &msg, const string content_type,
432 const Iterator<header_t>& headers=EMPTY(header_t), int code=200) {
433 string hdr = string ("Connection: close\r\nContent-type: ") + content_type + "\r\n";
434 while (headers.hasNext()) {
435 const header_t& h=headers.next();
436 hdr += h.first + ": " + h.second + "\r\n";
439 // XXX Need to handle "code"
440 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, "200 OK", (DWORD)hdr.c_str(), 0);
441 DWORD resplen = msg.size();
442 m_pfc->WriteClient(m_pfc, (LPVOID)msg.c_str(), &resplen, 0);
443 return (void*)SF_STATUS_REQ_FINISHED;
445 virtual void* sendRedirect(const string url) {
446 // XXX: Don't support the httpRedirect option, yet.
447 string hdrs=string("Location: ") + url + "\r\n"
448 "Content-Type: text/html\r\n"
449 "Content-Length: 40\r\n"
450 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
451 "Cache-Control: private,no-store,no-cache\r\n\r\n";
452 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER,
453 "302 Please Wait", (DWORD)hdrs.c_str(), 0);
454 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
456 m_pfc->WriteClient(m_pfc, (LPVOID)redmsg, &resplen, 0);
457 return reinterpret_cast<void*>(SF_STATUS_REQ_FINISHED);
459 // XXX: We might not ever hit the 'decline' status in this filter.
460 //virtual void* returnDecline(void) { }
461 virtual void* returnOK(void) { return (void*) SF_STATUS_REQ_NEXT_NOTIFICATION; }
463 // The filter never processes the POST, so stub these methods.
464 virtual void setCookie(const string &name, const string &value) { throw runtime_error("setCookie not implemented"); }
465 virtual string getArgs(void) { throw runtime_error("getArgs not implemented"); }
466 virtual string getPostData(void) { throw runtime_error("getPostData not implemented"); }
468 PHTTP_FILTER_CONTEXT m_pfc;
469 PHTTP_FILTER_PREPROC_HEADERS m_pn;
472 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const char* msg)
474 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
475 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
476 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)ctype,0);
477 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Filter Error</TITLE></HEAD><BODY>"
478 "<H1>Shibboleth Filter Error</H1>";
479 DWORD resplen=strlen(xmsg);
480 pfc->WriteClient(pfc,(LPVOID)xmsg,&resplen,0);
482 pfc->WriteClient(pfc,(LPVOID)msg,&resplen,0);
483 static const char* xmsg2="</BODY></HTML>";
484 resplen=strlen(xmsg2);
485 pfc->WriteClient(pfc,(LPVOID)xmsg2,&resplen,0);
486 return SF_STATUS_REQ_FINISHED;
489 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
491 // Is this a log notification?
492 if (notificationType==SF_NOTIFY_LOG)
494 if (pfc->pFilterContext)
495 ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=static_cast<LPCSTR>(pfc->pFilterContext);
496 return SF_STATUS_REQ_NEXT_NOTIFICATION;
499 PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
502 // Determine web site number. This can't really fail, I don't think.
504 GetServerVariable(pfc,"INSTANCE_ID",buf,10);
506 // Match site instance to host name, skip if no match.
507 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
508 if (map_i==g_Sites.end())
509 return SF_STATUS_REQ_NEXT_NOTIFICATION;
511 ostringstream threadid;
512 threadid << "[" << getpid() << "] isapi_shib" << '\0';
513 saml::NDC ndc(threadid.str().c_str());
515 ShibTargetIsapiF stf(pfc, pn, map_i->second);
517 // "false" because we don't override the Shib settings
518 pair<bool,void*> res = stf.doCheckAuthN();
519 if (res.first) return (DWORD)res.second;
521 // "false" because we don't override the Shib settings
522 res = stf.doExportAssertions();
523 if (res.first) return (DWORD)res.second;
525 res = stf.doCheckAuthZ();
526 if (res.first) return (DWORD)res.second;
528 return SF_STATUS_REQ_NEXT_NOTIFICATION;
531 return WriteClientError(pfc,"Out of Memory");
534 if (e==ERROR_NO_DATA)
535 return WriteClientError(pfc,"A required variable or header was empty.");
537 return WriteClientError(pfc,"Server detected unexpected IIS error.");
541 return WriteClientError(pfc,"Server caught an unknown exception.");
545 return WriteClientError(pfc,"Server reached unreachable code, save my walrus!");
550 IRequestMapper::Settings map_request(
551 PHTTP_FILTER_CONTEXT pfc, PHTTP_FILTER_PREPROC_HEADERS pn, IRequestMapper* mapper, const site_t& site, string& target
554 // URL path always come from IIS.
556 GetHeader(pn,pfc,"url",url,256,false);
558 // Port may come from IIS or from site def.
560 if (site.m_port.empty() || !g_bNormalizeRequest)
561 GetServerVariable(pfc,"SERVER_PORT",port,10);
563 strncpy(port,site.m_port.c_str(),10);
564 static_cast<char*>(port)[10]=0;
567 // Scheme may come from site def or be derived from IIS.
568 const char* scheme=site.m_scheme.c_str();
569 if (!scheme || !*scheme || !g_bNormalizeRequest)
570 scheme=pfc->fIsSecurePort ? "https" : "http";
572 // Start with scheme and hostname.
573 if (g_bNormalizeRequest) {
574 target = string(scheme) + "://" + site.m_name;
578 GetServerVariable(pfc,"SERVER_NAME",name,64);
579 target = string(scheme) + "://" + static_cast<char*>(name);
582 // If port is non-default, append it.
583 if ((!strcmp(scheme,"http") && port!="80") || (!strcmp(scheme,"https") && port!="443"))
584 target = target + ':' + static_cast<char*>(port);
588 target+=static_cast<char*>(url);
590 return mapper->getSettingsFromParsedURL(scheme,site.m_name.c_str(),strtoul(port,NULL,10),url);
593 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const IApplication* app, const char* page, ShibMLP& mlp)
595 const IPropertySet* props=app->getPropertySet("Errors");
597 pair<bool,const char*> p=props->getString(page);
599 ifstream infile(p.second);
600 if (!infile.fail()) {
601 const char* res = mlp.run(infile,props);
603 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
604 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)ctype,0);
605 DWORD resplen=strlen(res);
606 pfc->WriteClient(pfc,(LPVOID)res,&resplen,0);
607 return SF_STATUS_REQ_FINISHED;
613 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Filter unable to open error template.");
614 return WriteClientError(pfc,"Unable to open error template, check settings.");
617 DWORD WriteRedirectPage(PHTTP_FILTER_CONTEXT pfc, const IApplication* app, const char* file, ShibMLP& mlp, const char* headers=NULL)
619 ifstream infile(file);
620 if (!infile.fail()) {
621 const char* res = mlp.run(infile,app->getPropertySet("Errors"));
624 sprintf(buf,"Content-Length: %u\r\nContent-Type: text/html\r\n\r\n",strlen(res));
628 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)h.c_str(),0);
631 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)buf,0);
632 DWORD resplen=strlen(res);
633 pfc->WriteClient(pfc,(LPVOID)res,&resplen,0);
634 return SF_STATUS_REQ_FINISHED;
637 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Extension unable to open redirect template.");
638 return WriteClientError(pfc,"Unable to open redirect template, check settings.");
641 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
643 // Is this a log notification?
644 if (notificationType==SF_NOTIFY_LOG)
646 if (pfc->pFilterContext)
647 ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=static_cast<LPCSTR>(pfc->pFilterContext);
648 return SF_STATUS_REQ_NEXT_NOTIFICATION;
651 PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
654 // Determine web site number. This can't really fail, I don't think.
656 GetServerVariable(pfc,"INSTANCE_ID",buf,10);
658 // Match site instance to host name, skip if no match.
659 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
660 if (map_i==g_Sites.end())
661 return SF_STATUS_REQ_NEXT_NOTIFICATION;
663 ostringstream threadid;
664 threadid << "[" << getpid() << "] isapi_shib" << '\0';
665 saml::NDC ndc(threadid.str().c_str());
667 // We lock the configuration system for the duration.
668 IConfig* conf=g_Config->getINI();
671 // Map request to application and content settings.
673 IRequestMapper* mapper=conf->getRequestMapper();
674 Locker locker2(mapper);
675 IRequestMapper::Settings settings=map_request(pfc,pn,mapper,map_i->second,targeturl);
676 pair<bool,const char*> application_id=settings.first->getString("applicationId");
677 const IApplication* application=conf->getApplication(application_id.second);
679 return WriteClientError(pfc,"Unable to map request to application settings, check configuration.");
681 // Declare SHIRE object for this request.
682 SHIRE shire(application);
684 const char* shireURL=shire.getShireURL(targeturl.c_str());
686 return WriteClientError(pfc,"Unable to map request to proper shireURL setting, check configuration.");
688 // If the user is accessing the SHIRE acceptance point, pass it on.
689 if (targeturl.find(shireURL)!=string::npos)
690 return SF_STATUS_REQ_NEXT_NOTIFICATION;
692 // Now check the policy for this request.
693 pair<bool,bool> requireSession=settings.first->getBool("requireSession");
694 pair<const char*,const char*> shib_cookie=shire.getCookieNameProps();
695 pair<bool,bool> httpRedirects=application->getPropertySet("Sessions")->getBool("httpRedirects");
696 pair<bool,const char*> redirectPage=application->getPropertySet("Sessions")->getString("redirectPage");
697 if (httpRedirects.first && !httpRedirects.second && !redirectPage.first)
698 return WriteClientError(pfc,"HTML-based redirection requires a redirectPage property.");
700 // Check for session cookie.
701 const char* session_id=NULL;
702 GetHeader(pn,pfc,"Cookie:",buf,128,false);
703 Category::getInstance("isapi_shib.HttpFilterProc").debug("cookie header is {%s}",(const char*)buf);
704 if (!buf.empty() && (session_id=strstr(buf,shib_cookie.first))) {
705 session_id+=strlen(shib_cookie.first) + 1; /* Skip over the '=' */
706 char* cookieend=strchr(session_id,';');
708 *cookieend = '\0'; /* Ignore anyting after a ; */
711 if (!session_id || !*session_id) {
712 // If no session required, bail now.
713 if (!requireSession.second)
714 return SF_STATUS_REQ_NEXT_NOTIFICATION;
716 // No acceptable cookie, and we require a session. Generate an AuthnRequest.
717 const char* areq = shire.getAuthnRequest(targeturl.c_str());
718 if (!httpRedirects.first || httpRedirects.second) {
719 string hdrs=string("Location: ") + areq + "\r\n"
720 "Content-Type: text/html\r\n"
721 "Content-Length: 40\r\n"
722 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
723 "Cache-Control: private,no-store,no-cache\r\n\r\n";
724 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"302 Please Wait",(DWORD)hdrs.c_str(),0);
725 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
727 pfc->WriteClient(pfc,(LPVOID)redmsg,&resplen,0);
728 return SF_STATUS_REQ_FINISHED;
731 ShibMLP markupProcessor;
732 markupProcessor.insert("requestURL",areq);
733 return WriteRedirectPage(pfc, application, redirectPage.second, markupProcessor);
737 // Make sure this session is still valid.
738 RPCError* status = NULL;
739 ShibMLP markupProcessor;
740 markupProcessor.insert("requestURL", targeturl);
743 GetServerVariable(pfc,"REMOTE_ADDR",abuf,16);
745 status = shire.sessionIsValid(session_id, abuf);
747 catch (ShibTargetException &e) {
748 markupProcessor.insert("errorType", "Session Processing Error");
749 markupProcessor.insert("errorText", e.what());
750 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
751 return WriteClientError(pfc, application, "shire", markupProcessor);
755 markupProcessor.insert("errorType", "Session Processing Error");
756 markupProcessor.insert("errorText", "Unexpected Exception");
757 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
758 return WriteClientError(pfc, application, "shire", markupProcessor);
763 if (status->isError()) {
764 if (!requireSession.second)
765 return SF_STATUS_REQ_NEXT_NOTIFICATION;
766 else if (status->isRetryable()) {
767 // Oops, session is invalid. Generate AuthnRequest.
769 const char* areq = shire.getAuthnRequest(targeturl.c_str());
770 if (!httpRedirects.first || httpRedirects.second) {
771 string hdrs=string("Location: ") + areq + "\r\n"
772 "Content-Type: text/html\r\n"
773 "Content-Length: 40\r\n"
774 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
775 "Cache-Control: private,no-store,no-cache\r\n\r\n";
776 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"302 Please Wait",(DWORD)hdrs.c_str(),0);
777 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
779 pfc->WriteClient(pfc,(LPVOID)redmsg,&resplen,0);
780 return SF_STATUS_REQ_FINISHED;
783 markupProcessor.insert("requestURL",areq);
784 return WriteRedirectPage(pfc, application, redirectPage.second, markupProcessor);
788 // return the error page to the user
789 markupProcessor.insert(*status);
791 return WriteClientError(pfc, application, "shire", markupProcessor);
798 vector<SAMLAssertion*> assertions;
799 SAMLAuthenticationStatement* sso_statement=NULL;
802 status = rm.getAssertions(session_id, abuf, assertions, &sso_statement);
804 catch (ShibTargetException &e) {
805 markupProcessor.insert("errorType", "Attribute Processing Error");
806 markupProcessor.insert("errorText", e.what());
807 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
808 return WriteClientError(pfc, application, "rm", markupProcessor);
812 markupProcessor.insert("errorType", "Attribute Processing Error");
813 markupProcessor.insert("errorText", "Unexpected Exception");
814 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
815 return WriteClientError(pfc, application, "rm", markupProcessor);
819 if (status->isError()) {
820 markupProcessor.insert(*status);
822 return WriteClientError(pfc, application, "rm", markupProcessor);
826 // Do we have an access control plugin?
827 if (settings.second) {
828 Locker acllock(settings.second);
829 if (!settings.second->authorized(*sso_statement,assertions)) {
830 for (int k = 0; k < assertions.size(); k++)
831 delete assertions[k];
832 delete sso_statement;
833 return WriteClientError(pfc, application, "access", markupProcessor);
837 // Get the AAP providers, which contain the attribute policy info.
838 Iterator<IAAP*> provs=application->getAAPProviders();
840 // Clear out the list of mapped attributes
841 while (provs.hasNext()) {
842 IAAP* aap=provs.next();
845 Iterator<const IAttributeRule*> rules=aap->getAttributeRules();
846 while (rules.hasNext()) {
847 const char* header=rules.next()->getHeader();
849 string hname=string(header) + ':';
850 pn->SetHeader(pfc,const_cast<char*>(hname.c_str()),"");
856 for (int k = 0; k < assertions.size(); k++)
857 delete assertions[k];
858 delete sso_statement;
859 markupProcessor.insert("errorType", "Attribute Processing Error");
860 markupProcessor.insert("errorText", "Unexpected Exception");
861 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
862 return WriteClientError(pfc, application, "rm", markupProcessor);
868 // Maybe export the first assertion.
869 pn->SetHeader(pfc,"remote-user:","");
870 pn->SetHeader(pfc,"Shib-Attributes:","");
871 pair<bool,bool> exp=settings.first->getBool("exportAssertion");
872 if (exp.first && exp.second && assertions.size()) {
874 RM::serialize(*(assertions[0]), assertion);
875 string::size_type lfeed;
876 while ((lfeed=assertion.find('\n'))!=string::npos)
877 assertion.erase(lfeed,1);
878 pn->SetHeader(pfc,"Shib-Attributes:",const_cast<char*>(assertion.c_str()));
881 pn->SetHeader(pfc,"Shib-Origin-Site:","");
882 pn->SetHeader(pfc,"Shib-Authentication-Method:","");
883 pn->SetHeader(pfc,"Shib-NameIdentifier-Format:","");
885 // Export the SAML AuthnMethod and the origin site name.
886 auto_ptr_char os(sso_statement->getSubject()->getNameIdentifier()->getNameQualifier());
887 auto_ptr_char am(sso_statement->getAuthMethod());
888 pn->SetHeader(pfc,"Shib-Origin-Site:", const_cast<char*>(os.get()));
889 pn->SetHeader(pfc,"Shib-Authentication-Method:", const_cast<char*>(am.get()));
892 AAP wrapper(provs,sso_statement->getSubject()->getNameIdentifier()->getFormat(),Constants::SHIB_ATTRIBUTE_NAMESPACE_URI);
893 if (!wrapper.fail() && wrapper->getHeader()) {
894 auto_ptr_char form(sso_statement->getSubject()->getNameIdentifier()->getFormat());
895 auto_ptr_char nameid(sso_statement->getSubject()->getNameIdentifier()->getName());
896 pn->SetHeader(pfc,"Shib-NameIdentifier-Format:",const_cast<char*>(form.get()));
897 if (!strcmp(wrapper->getHeader(),"REMOTE_USER")) {
898 char* principal=const_cast<char*>(nameid.get());
899 pn->SetHeader(pfc,"remote-user:",principal);
900 pfc->pFilterContext=pfc->AllocMem(pfc,strlen(principal)+1,0);
901 if (pfc->pFilterContext)
902 strcpy(static_cast<char*>(pfc->pFilterContext),principal);
905 string hname=string(wrapper->getHeader()) + ':';
906 pn->SetHeader(pfc,const_cast<char*>(wrapper->getHeader()),const_cast<char*>(nameid.get()));
910 pn->SetHeader(pfc,"Shib-Application-ID:","");
911 pn->SetHeader(pfc,"Shib-Application-ID:",const_cast<char*>(application_id.second));
913 // Export the attributes.
914 Iterator<SAMLAssertion*> a_iter(assertions);
915 while (a_iter.hasNext()) {
916 SAMLAssertion* assert=a_iter.next();
917 Iterator<SAMLStatement*> statements=assert->getStatements();
918 while (statements.hasNext()) {
919 SAMLAttributeStatement* astate=dynamic_cast<SAMLAttributeStatement*>(statements.next());
922 Iterator<SAMLAttribute*> attrs=astate->getAttributes();
923 while (attrs.hasNext()) {
924 SAMLAttribute* attr=attrs.next();
926 // Are we supposed to export it?
927 AAP wrapper(provs,attr->getName(),attr->getNamespace());
928 if (wrapper.fail() || !wrapper->getHeader())
931 Iterator<string> vals=attr->getSingleByteValues();
932 if (!strcmp(wrapper->getHeader(),"REMOTE_USER") && vals.hasNext()) {
933 char* principal=const_cast<char*>(vals.next().c_str());
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);
942 string hname=string(wrapper->getHeader()) + ':';
943 GetHeader(pn,pfc,const_cast<char*>(hname.c_str()),buf,256,false);
948 for (; vals.hasNext(); it++) {
949 string value = vals.next();
950 for (string::size_type pos = value.find_first_of(";", string::size_type(0));
952 pos = value.find_first_of(";", pos)) {
953 value.insert(pos, "\\");
959 header=header + ';' + value;
961 pn->SetHeader(pfc,const_cast<char*>(hname.c_str()),const_cast<char*>(header.c_str()));
968 for (int k = 0; k < assertions.size(); k++)
969 delete assertions[k];
970 delete sso_statement;
972 return SF_STATUS_REQ_NEXT_NOTIFICATION;
975 return WriteClientError(pfc,"Out of Memory");
978 if (e==ERROR_NO_DATA)
979 return WriteClientError(pfc,"A required variable or header was empty.");
981 return WriteClientError(pfc,"Server detected unexpected IIS error.");
985 return WriteClientError(pfc,"Server caught an unknown exception.");
989 return WriteClientError(pfc,"Server reached unreachable code, save my walrus!");
993 /****************************************************************************/
996 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* msg)
998 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
999 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
1000 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
1001 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Error</TITLE></HEAD><BODY><H1>Shibboleth Error</H1>";
1002 DWORD resplen=strlen(xmsg);
1003 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg,&resplen,HSE_IO_SYNC);
1004 resplen=strlen(msg);
1005 lpECB->WriteClient(lpECB->ConnID,(LPVOID)msg,&resplen,HSE_IO_SYNC);
1006 static const char* xmsg2="</BODY></HTML>";
1007 resplen=strlen(xmsg2);
1008 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg2,&resplen,HSE_IO_SYNC);
1009 return HSE_STATUS_SUCCESS;
1013 class ShibTargetIsapiE : public ShibTarget
1016 ShibTargetIsapiE(LPEXTENSION_CONTROL_BLOCK lpECB, const site_t& site) :
1020 GetServerVariable(lpECB,"HTTPS",ssl,5);
1021 bool SSL=(ssl=="on" || ssl=="ON");
1023 // URL path always come from IIS.
1025 GetServerVariable(lpECB,"URL",url,255);
1027 // Port may come from IIS or from site def.
1029 if (site.m_port.empty() || !g_bNormalizeRequest)
1030 GetServerVariable(lpECB,"SERVER_PORT",port,10);
1032 strncpy(port,site.m_port.c_str(),10);
1033 static_cast<char*>(port)[10]=0;
1036 // Scheme may come from site def or be derived from IIS.
1037 const char* scheme=site.m_scheme.c_str();
1038 if (!scheme || !*scheme || !g_bNormalizeRequest) {
1039 scheme = SSL ? "https" : "http";
1042 // Get the remote address
1043 dynabuf remote_addr(16);
1044 GetServerVariable(lpECB, "REMOTE_ADDR", remote_addr, 16);
1046 init(g_Config, string(scheme), site.m_name, atoi(port),
1047 string(url), string(lpECB->lpszContentType ? lpECB->lpszContentType : ""),
1048 string(remote_addr), string(lpECB->lpszMethod)
1053 ~ShibTargetIsapiE() { }
1055 virtual void log(ShibLogLevel level, const string &msg) {
1056 LogEvent(NULL, (level == LogLevelDebug ? EVENTLOG_INFORMATION_TYPE :
1057 (level == LogLevelInfo ? EVENTLOG_INFORMATION_TYPE :
1058 (level == LogLevelWarn ? EVENTLOG_WARNING_TYPE : EVENTLOG_ERROR_TYPE))),
1059 2100, NULL, msg.c_str());
1061 virtual void setCookie(const string &name, const string &value) {
1062 // Set the cookie for later. Use it during the redirect.
1063 m_cookie += "Set-Cookie: " + name + "=" + value + "\r\n";
1065 virtual string getArgs(void) {
1066 return string(m_lpECB->lpszQueryString ? m_lpECB->lpszQueryString : "");
1068 virtual string getPostData(void) {
1069 if (m_lpECB->cbTotalBytes > 1024*1024) // 1MB?
1070 throw ShibTargetException(SHIBRPC_OK,
1071 "blocked too-large a post to SHIRE POST processor");
1072 else if (m_lpECB->cbTotalBytes != m_lpECB->cbAvailable) {
1075 DWORD datalen=m_lpECB->cbTotalBytes;
1078 BOOL ret = m_lpECB->ReadClient(m_lpECB->ConnID, buf, &buflen);
1079 if (!ret || !buflen)
1080 throw ShibTargetException(SHIBRPC_OK,
1081 "error reading POST data from browser");
1082 cgistr.append(buf, buflen);
1088 return string(reinterpret_cast<char*>(m_lpECB->lpbData),m_lpECB->cbAvailable);
1090 virtual void* sendPage(const string &msg, const string content_type,
1091 const Iterator<header_t>& headers=EMPTY(header_t), int code=200) {
1092 string hdr = string ("Connection: close\r\nContent-type: ") + content_type + "\r\n";
1093 for (int k = 0; k < headers.size(); k++) {
1094 hdr += headers[k].first + ": " + headers[k].second + "\r\n";
1097 // XXX Need to handle "code"
1098 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER,
1099 "200 OK", 0, (LPDWORD)hdr.c_str());
1100 DWORD resplen = msg.size();
1101 m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)msg.c_str(), &resplen, HSE_IO_SYNC);
1102 return (void*)HSE_STATUS_SUCCESS;
1104 virtual void* sendRedirect(const string url) {
1105 // XXX: Don't support the httpRedirect option, yet.
1106 string hdrs = m_cookie + "Location: " + url + "\r\n"
1107 "Content-Type: text/html\r\n"
1108 "Content-Length: 40\r\n"
1109 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
1110 "Cache-Control: private,no-store,no-cache\r\n\r\n";
1111 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER,
1112 "302 Moved", 0, (LPDWORD)hdrs.c_str());
1113 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
1115 m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)redmsg, &resplen, HSE_IO_SYNC);
1116 return (void*)HSE_STATUS_SUCCESS;
1118 // Decline happens in the POST processor if this isn't the shire url
1119 // Note that it can also happen with HTAccess, but we don't suppor that, yet.
1120 virtual void* returnDecline(void) {
1122 WriteClientError(m_lpECB, "UISAPA extension can only be unvoked to process incoming sessions."
1123 "Make sure the mapped file extension doesn't match actual content.");
1125 virtual void* returnOK(void) { return (void*) HSE_STATUS_SUCCESS; }
1127 // Not used in the extension.
1128 virtual string getCookies(void) { throw runtime_error("getCookies not implemented"); }
1129 virtual void clearHeader(const string &name) { throw runtime_error("clearHeader not implemented"); }
1130 virtual void setHeader(const string &name, const string &value) { throw runtime_error("setHeader not implemented"); }
1131 virtual string getHeader(const string &name) { throw runtime_error("getHeader not implemented"); }
1132 virtual void setRemoteUser(const string &user) { throw runtime_error("setRemoteUser not implemented"); }
1133 virtual string getRemoteUser(void) { throw runtime_error("getRemoteUser not implemented"); }
1135 LPEXTENSION_CONTROL_BLOCK m_lpECB;
1139 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
1142 const IApplication* application=NULL;
1145 ostringstream threadid;
1146 threadid << "[" << getpid() << "] shire_handler" << '\0';
1147 saml::NDC ndc(threadid.str().c_str());
1149 // Determine web site number. This can't really fail, I don't think.
1151 GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
1153 // Match site instance to host name, skip if no match.
1154 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
1155 if (map_i==g_Sites.end())
1156 return WriteClientError(lpECB, "Shibboleth Extension not configured for this web site.");
1158 ShibTargetIsapiE ste(lpECB, map_i->second);
1159 pair<bool,void*> res = ste.doHandleProfile();
1160 if (res.first) return (DWORD)res.second;
1162 return WriteClientError(lpECB, "Shibboleth Extension failed to process POST");
1165 return WriteClientError(lpECB,
1166 "Shibboleth Extension caught an unknown error. "
1170 // If we get here we've got an error.
1171 return HSE_STATUS_ERROR;
1175 IRequestMapper::Settings map_request(
1176 LPEXTENSION_CONTROL_BLOCK lpECB, IRequestMapper* mapper, const site_t& site, string& target
1180 GetServerVariable(lpECB,"HTTPS",ssl,5);
1181 bool SSL=(ssl=="on" || ssl=="ON");
1183 // URL path always come from IIS.
1185 GetServerVariable(lpECB,"URL",url,255);
1187 // Port may come from IIS or from site def.
1189 if (site.m_port.empty() || !g_bNormalizeRequest)
1190 GetServerVariable(lpECB,"SERVER_PORT",port,10);
1192 strncpy(port,site.m_port.c_str(),10);
1193 static_cast<char*>(port)[10]=0;
1196 // Scheme may come from site def or be derived from IIS.
1197 const char* scheme=site.m_scheme.c_str();
1198 if (!scheme || !*scheme || !g_bNormalizeRequest) {
1199 scheme = SSL ? "https" : "http";
1202 // Start with scheme and hostname.
1203 if (g_bNormalizeRequest) {
1204 target = string(scheme) + "://" + site.m_name;
1208 GetServerVariable(lpECB,"SERVER_NAME",name,64);
1209 target = string(scheme) + "://" + static_cast<char*>(name);
1212 // If port is non-default, append it.
1213 if ((!strcmp(scheme,"http") && port!="80") || (!strcmp(scheme,"https") && port!="443"))
1214 target = target + ':' + static_cast<char*>(port);
1218 target+=static_cast<char*>(url);
1220 return mapper->getSettingsFromParsedURL(scheme,site.m_name.c_str(),strtoul(port,NULL,10),url);
1223 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const IApplication* app, const char* page, ShibMLP& mlp)
1225 const IPropertySet* props=app->getPropertySet("Errors");
1227 pair<bool,const char*> p=props->getString(page);
1229 ifstream infile(p.second);
1230 if (!infile.fail()) {
1231 const char* res = mlp.run(infile,props);
1233 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
1234 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
1235 DWORD resplen=strlen(res);
1236 lpECB->WriteClient(lpECB->ConnID,(LPVOID)res,&resplen,0);
1237 return HSE_STATUS_SUCCESS;
1242 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Extension unable to open error template.");
1243 return WriteClientError(lpECB,"Unable to open error template, check settings.");
1246 DWORD WriteRedirectPage(LPEXTENSION_CONTROL_BLOCK lpECB, const IApplication* app, const char* file, ShibMLP& mlp, const char* headers=NULL)
1248 ifstream infile(file);
1249 if (!infile.fail()) {
1250 const char* res = mlp.run(infile,app->getPropertySet("Errors"));
1253 sprintf(buf,"Content-Length: %u\r\nContent-Type: text/html\r\n\r\n",strlen(res));
1257 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)h.c_str());
1260 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)buf);
1261 DWORD resplen=strlen(res);
1262 lpECB->WriteClient(lpECB->ConnID,(LPVOID)res,&resplen,0);
1263 return HSE_STATUS_SUCCESS;
1266 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Extension unable to open redirect template.");
1267 return WriteClientError(lpECB,"Unable to open redirect template, check settings.");
1270 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
1273 const IApplication* application=NULL;
1276 ostringstream threadid;
1277 threadid << "[" << getpid() << "] shire_handler" << '\0';
1278 saml::NDC ndc(threadid.str().c_str());
1280 // Determine web site number. This can't really fail, I don't think.
1282 GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
1284 // Match site instance to host name, skip if no match.
1285 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
1286 if (map_i==g_Sites.end())
1287 return WriteClientError(lpECB,"Shibboleth filter not configured for this web site.");
1289 // We lock the configuration system for the duration.
1290 IConfig* conf=g_Config->getINI();
1291 Locker locker(conf);
1293 // Map request to application and content settings.
1294 IRequestMapper* mapper=conf->getRequestMapper();
1295 Locker locker2(mapper);
1296 IRequestMapper::Settings settings=map_request(lpECB,mapper,map_i->second,targeturl);
1297 pair<bool,const char*> application_id=settings.first->getString("applicationId");
1298 application=conf->getApplication(application_id.second);
1299 const IPropertySet* sessionProps=application ? application->getPropertySet("Sessions") : NULL;
1300 if (!application || !sessionProps)
1301 return WriteClientError(lpECB,"Unable to map request to application session settings, check configuration.");
1303 SHIRE shire(application);
1305 const char* shireURL=shire.getShireURL(targeturl.c_str());
1307 return WriteClientError(lpECB,"Unable to map request to proper shireURL setting, check configuration.");
1309 // Make sure we only process the SHIRE requests.
1310 if (!strstr(targeturl.c_str(),shireURL))
1311 return WriteClientError(lpECB,"ISAPI extension can only be invoked to process incoming sessions."
1312 "Make sure the mapped file extension doesn't match actual content.");
1314 pair<const char*,const char*> shib_cookie=shire.getCookieNameProps();
1316 // Make sure this is SSL, if it should be
1317 pair<bool,bool> shireSSL=sessionProps->getBool("shireSSL");
1318 if (!shireSSL.first || shireSSL.second) {
1319 GetServerVariable(lpECB,"HTTPS",buf,10);
1321 throw ShibTargetException(SHIBRPC_OK,"blocked non-SSL access to SHIRE POST processor");
1324 pair<bool,bool> httpRedirects=sessionProps->getBool("httpRedirects");
1325 pair<bool,const char*> redirectPage=sessionProps->getString("redirectPage");
1326 if (httpRedirects.first && !httpRedirects.second && !redirectPage.first)
1327 return WriteClientError(lpECB,"HTML-based redirection requires a redirectPage property.");
1329 // Check for Mac web browser
1333 GetServerVariable(lpECB,"HTTP_USER_AGENT",agent,64);
1334 if (strstr(agent,"AppleWebKit/"))
1338 // If this is a GET, we manufacture an AuthnRequest.
1339 if (!stricmp(lpECB->lpszMethod,"GET")) {
1340 const char* areq=lpECB->lpszQueryString ? shire.getLazyAuthnRequest(lpECB->lpszQueryString) : NULL;
1342 throw ShibTargetException(SHIBRPC_OK, "malformed arguments to request a new session");
1343 if (!httpRedirects.first || httpRedirects.second) {
1344 string hdrs=string("Location: ") + areq + "\r\n"
1345 "Content-Type: text/html\r\n"
1346 "Content-Length: 40\r\n"
1347 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
1348 "Cache-Control: private,no-store,no-cache\r\n\r\n";
1349 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"302 Moved",0,(LPDWORD)hdrs.c_str());
1350 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
1352 lpECB->WriteClient(lpECB->ConnID,(LPVOID)redmsg,&resplen,HSE_IO_SYNC);
1353 return HSE_STATUS_SUCCESS;
1356 ShibMLP markupProcessor;
1357 markupProcessor.insert("requestURL",areq);
1358 return WriteRedirectPage(lpECB, application, redirectPage.second, markupProcessor);
1361 else if (stricmp(lpECB->lpszMethod,"POST"))
1362 throw ShibTargetException(SHIBRPC_OK,"blocked non-POST to SHIRE POST processor");
1364 // Sure sure this POST is an appropriate content type
1365 if (!lpECB->lpszContentType || stricmp(lpECB->lpszContentType,"application/x-www-form-urlencoded"))
1366 throw ShibTargetException(SHIBRPC_OK,"blocked bad content-type to SHIRE POST processor");
1369 pair<const char*,const char*> elements=pair<const char*,const char*>(NULL,NULL);
1370 if (lpECB->cbTotalBytes > 1024*1024) // 1MB?
1371 throw ShibTargetException(SHIBRPC_OK,"blocked too-large a post to SHIRE POST processor");
1372 else if (lpECB->cbTotalBytes!=lpECB->cbAvailable) {
1375 DWORD datalen=lpECB->cbTotalBytes;
1378 BOOL ret=lpECB->ReadClient(lpECB->ConnID,buf,&buflen);
1379 if (!ret || !buflen)
1380 throw ShibTargetException(SHIBRPC_OK,"error reading POST data from browser");
1381 cgistr.append(buf,buflen);
1384 elements=shire.getFormSubmission(cgistr.c_str(),cgistr.length());
1387 elements=shire.getFormSubmission(reinterpret_cast<char*>(lpECB->lpbData),lpECB->cbAvailable);
1389 // Make sure the SAML Response parameter exists
1390 if (!elements.first || !*elements.first)
1391 throw ShibTargetException(SHIBRPC_OK, "SHIRE POST failed to find SAMLResponse form element");
1393 // Make sure the target parameter exists
1394 if (!elements.second || !*elements.second)
1395 throw ShibTargetException(SHIBRPC_OK, "SHIRE POST failed to find TARGET form element");
1397 GetServerVariable(lpECB,"REMOTE_ADDR",buf,16);
1399 // Process the post.
1401 RPCError* status=NULL;
1402 ShibMLP markupProcessor;
1403 markupProcessor.insert("requestURL", targeturl.c_str());
1405 status = shire.sessionCreate(elements.first,buf,cookie);
1407 catch (ShibTargetException &e) {
1408 markupProcessor.insert("errorType", "Session Creation Service Error");
1409 markupProcessor.insert("errorText", e.what());
1410 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
1411 return WriteClientError(lpECB, application, "shire", markupProcessor);
1415 markupProcessor.insert("errorType", "Session Creation Service Error");
1416 markupProcessor.insert("errorText", "Unexpected Exception");
1417 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
1418 return WriteClientError(lpECB, application, "shire", markupProcessor);
1422 if (status->isError()) {
1423 if (status->isRetryable()) {
1425 const char* loc=shire.getAuthnRequest(elements.second);
1426 if (!httpRedirects.first || httpRedirects.second) {
1427 string hdrs=string("Location: ") + loc + "\r\n"
1428 "Content-Type: text/html\r\n"
1429 "Content-Length: 40\r\n"
1430 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
1431 "Cache-Control: private,no-store,no-cache\r\n\r\n";
1432 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"302 Moved",0,(LPDWORD)hdrs.c_str());
1433 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
1435 lpECB->WriteClient(lpECB->ConnID,(LPVOID)redmsg,&resplen,HSE_IO_SYNC);
1436 return HSE_STATUS_SUCCESS;
1439 markupProcessor.insert("requestURL",loc);
1440 return WriteRedirectPage(lpECB, application, redirectPage.second, markupProcessor);
1444 // Return this error to the user.
1445 markupProcessor.insert(*status);
1447 return WriteClientError(lpECB,application,"shire",markupProcessor);
1451 // We've got a good session, set the cookie and redirect to target.
1452 cookie = string("Set-Cookie: ") + shib_cookie.first + '=' + cookie + shib_cookie.second + "\r\n"
1453 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
1454 "Cache-Control: private,no-store,no-cache\r\n";
1455 if (!httpRedirects.first || httpRedirects.second) {
1456 cookie=cookie + "Content-Type: text/html\r\nLocation: " + elements.second + "\r\nContent-Length: 40\r\n\r\n";
1457 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"302 Moved",0,(LPDWORD)cookie.c_str());
1458 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
1460 lpECB->WriteClient(lpECB->ConnID,(LPVOID)redmsg,&resplen,HSE_IO_SYNC);
1461 return HSE_STATUS_SUCCESS;
1464 markupProcessor.insert("requestURL",elements.second);
1465 return WriteRedirectPage(lpECB, application, redirectPage.second, markupProcessor, cookie.c_str());
1468 catch (ShibTargetException &e) {
1470 ShibMLP markupProcessor;
1471 markupProcessor.insert("requestURL", targeturl.c_str());
1472 markupProcessor.insert("errorType", "Session Creation Service Error");
1473 markupProcessor.insert("errorText", e.what());
1474 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
1475 return WriteClientError(lpECB,application,"shire",markupProcessor);
1481 ShibMLP markupProcessor;
1482 markupProcessor.insert("requestURL", targeturl.c_str());
1483 markupProcessor.insert("errorType", "Session Creation Service Error");
1484 markupProcessor.insert("errorText", "Unexpected Exception");
1485 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
1486 return WriteClientError(lpECB,application,"shire",markupProcessor);
1491 return HSE_STATUS_ERROR;