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("Local");
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)
1019 GetServerVariable(lpECB,"HTTPS",ssl,5);
1020 bool SSL=(ssl=="on" || ssl=="ON");
1022 // URL path always come from IIS.
1024 GetServerVariable(lpECB,"URL",url,255);
1026 // Port may come from IIS or from site def.
1028 if (site.m_port.empty() || !g_bNormalizeRequest)
1029 GetServerVariable(lpECB,"SERVER_PORT",port,10);
1031 strncpy(port,site.m_port.c_str(),10);
1032 static_cast<char*>(port)[10]=0;
1035 // Scheme may come from site def or be derived from IIS.
1036 const char* scheme=site.m_scheme.c_str();
1037 if (!scheme || !*scheme || !g_bNormalizeRequest) {
1038 scheme = SSL ? "https" : "http";
1041 // Get the remote address
1042 dynabuf remote_addr(16);
1043 GetServerVariable(lpECB, "REMOTE_ADDR", remote_addr, 16);
1045 init(g_Config, string(scheme), site.m_name, atoi(port),
1046 string(url), string(lpECB->lpszContentType ? lpECB->lpszContentType : ""),
1047 string(remote_addr), string(lpECB->lpszMethod)
1052 ~ShibTargetIsapiE() { }
1054 virtual void log(ShibLogLevel level, const string &msg) {
1055 LogEvent(NULL, (level == LogLevelDebug ? EVENTLOG_INFORMATION_TYPE :
1056 (level == LogLevelInfo ? EVENTLOG_INFORMATION_TYPE :
1057 (level == LogLevelWarn ? EVENTLOG_WARNING_TYPE : EVENTLOG_ERROR_TYPE))),
1058 2100, NULL, msg.c_str());
1060 virtual void setCookie(const string &name, const string &value) {
1061 // Set the cookie for later. Use it during the redirect.
1062 m_cookie += "Set-Cookie: " + name + "=" + value + "\r\n";
1064 virtual string getArgs(void) {
1065 return string(m_lpECB->lpszQueryString ? m_lpECB->lpszQueryString : "");
1067 virtual string getPostData(void) {
1068 if (m_lpECB->cbTotalBytes > 1024*1024) // 1MB?
1069 throw FatalProfileException("Blocked too-large a submission to profile endpoint.");
1070 else if (m_lpECB->cbTotalBytes != m_lpECB->cbAvailable) {
1073 DWORD datalen=m_lpECB->cbTotalBytes;
1076 BOOL ret = m_lpECB->ReadClient(m_lpECB->ConnID, buf, &buflen);
1077 if (!ret || !buflen)
1078 throw FatalProfileException("Error reading profile submission from browser.");
1079 cgistr.append(buf, buflen);
1085 return string(reinterpret_cast<char*>(m_lpECB->lpbData),m_lpECB->cbAvailable);
1087 virtual void* sendPage(const string &msg, const string content_type,
1088 const Iterator<header_t>& headers=EMPTY(header_t), int code=200) {
1089 string hdr = string ("Connection: close\r\nContent-type: ") + content_type + "\r\n";
1090 for (int k = 0; k < headers.size(); k++) {
1091 hdr += headers[k].first + ": " + headers[k].second + "\r\n";
1094 // XXX Need to handle "code"
1095 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER,
1096 "200 OK", 0, (LPDWORD)hdr.c_str());
1097 DWORD resplen = msg.size();
1098 m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)msg.c_str(), &resplen, HSE_IO_SYNC);
1099 return (void*)HSE_STATUS_SUCCESS;
1101 virtual void* sendRedirect(const string url) {
1102 // XXX: Don't support the httpRedirect option, yet.
1103 string hdrs = m_cookie + "Location: " + url + "\r\n"
1104 "Content-Type: text/html\r\n"
1105 "Content-Length: 40\r\n"
1106 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
1107 "Cache-Control: private,no-store,no-cache\r\n\r\n";
1108 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER,
1109 "302 Moved", 0, (LPDWORD)hdrs.c_str());
1110 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
1112 m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)redmsg, &resplen, HSE_IO_SYNC);
1113 return (void*)HSE_STATUS_SUCCESS;
1115 // Decline happens in the POST processor if this isn't the shire url
1116 // Note that it can also happen with HTAccess, but we don't suppor that, yet.
1117 virtual void* returnDecline(void) {
1119 WriteClientError(m_lpECB, "UISAPA extension can only be unvoked to process incoming sessions."
1120 "Make sure the mapped file extension doesn't match actual content.");
1122 virtual void* returnOK(void) { return (void*) HSE_STATUS_SUCCESS; }
1124 // Not used in the extension.
1125 virtual string getCookies(void) { throw runtime_error("getCookies not implemented"); }
1126 virtual void clearHeader(const string &name) { throw runtime_error("clearHeader not implemented"); }
1127 virtual void setHeader(const string &name, const string &value) { throw runtime_error("setHeader not implemented"); }
1128 virtual string getHeader(const string &name) { throw runtime_error("getHeader not implemented"); }
1129 virtual void setRemoteUser(const string &user) { throw runtime_error("setRemoteUser not implemented"); }
1130 virtual string getRemoteUser(void) { throw runtime_error("getRemoteUser not implemented"); }
1132 LPEXTENSION_CONTROL_BLOCK m_lpECB;
1136 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
1139 const IApplication* application=NULL;
1142 ostringstream threadid;
1143 threadid << "[" << getpid() << "] shire_handler" << '\0';
1144 saml::NDC ndc(threadid.str().c_str());
1146 // Determine web site number. This can't really fail, I don't think.
1148 GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
1150 // Match site instance to host name, skip if no match.
1151 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
1152 if (map_i==g_Sites.end())
1153 return WriteClientError(lpECB, "Shibboleth Extension not configured for this web site.");
1155 ShibTargetIsapiE ste(lpECB, map_i->second);
1156 pair<bool,void*> res = ste.doHandleProfile();
1157 if (res.first) return (DWORD)res.second;
1159 return WriteClientError(lpECB, "Shibboleth Extension failed to process request");
1163 return WriteClientError(lpECB,
1164 "Shibboleth Extension caught an unknown error.");
1167 // If we get here we've got an error.
1168 return HSE_STATUS_ERROR;
1172 IRequestMapper::Settings map_request(
1173 LPEXTENSION_CONTROL_BLOCK lpECB, IRequestMapper* mapper, const site_t& site, string& target
1177 GetServerVariable(lpECB,"HTTPS",ssl,5);
1178 bool SSL=(ssl=="on" || ssl=="ON");
1180 // URL path always come from IIS.
1182 GetServerVariable(lpECB,"URL",url,255);
1184 // Port may come from IIS or from site def.
1186 if (site.m_port.empty() || !g_bNormalizeRequest)
1187 GetServerVariable(lpECB,"SERVER_PORT",port,10);
1189 strncpy(port,site.m_port.c_str(),10);
1190 static_cast<char*>(port)[10]=0;
1193 // Scheme may come from site def or be derived from IIS.
1194 const char* scheme=site.m_scheme.c_str();
1195 if (!scheme || !*scheme || !g_bNormalizeRequest) {
1196 scheme = SSL ? "https" : "http";
1199 // Start with scheme and hostname.
1200 if (g_bNormalizeRequest) {
1201 target = string(scheme) + "://" + site.m_name;
1205 GetServerVariable(lpECB,"SERVER_NAME",name,64);
1206 target = string(scheme) + "://" + static_cast<char*>(name);
1209 // If port is non-default, append it.
1210 if ((!strcmp(scheme,"http") && port!="80") || (!strcmp(scheme,"https") && port!="443"))
1211 target = target + ':' + static_cast<char*>(port);
1215 target+=static_cast<char*>(url);
1217 return mapper->getSettingsFromParsedURL(scheme,site.m_name.c_str(),strtoul(port,NULL,10),url);
1220 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const IApplication* app, const char* page, ShibMLP& mlp)
1222 const IPropertySet* props=app->getPropertySet("Errors");
1224 pair<bool,const char*> p=props->getString(page);
1226 ifstream infile(p.second);
1227 if (!infile.fail()) {
1228 const char* res = mlp.run(infile,props);
1230 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
1231 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
1232 DWORD resplen=strlen(res);
1233 lpECB->WriteClient(lpECB->ConnID,(LPVOID)res,&resplen,0);
1234 return HSE_STATUS_SUCCESS;
1239 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Extension unable to open error template.");
1240 return WriteClientError(lpECB,"Unable to open error template, check settings.");
1243 DWORD WriteRedirectPage(LPEXTENSION_CONTROL_BLOCK lpECB, const IApplication* app, const char* file, ShibMLP& mlp, const char* headers=NULL)
1245 ifstream infile(file);
1246 if (!infile.fail()) {
1247 const char* res = mlp.run(infile,app->getPropertySet("Errors"));
1250 sprintf(buf,"Content-Length: %u\r\nContent-Type: text/html\r\n\r\n",strlen(res));
1254 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)h.c_str());
1257 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)buf);
1258 DWORD resplen=strlen(res);
1259 lpECB->WriteClient(lpECB->ConnID,(LPVOID)res,&resplen,0);
1260 return HSE_STATUS_SUCCESS;
1263 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Extension unable to open redirect template.");
1264 return WriteClientError(lpECB,"Unable to open redirect template, check settings.");
1267 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
1270 const IApplication* application=NULL;
1273 ostringstream threadid;
1274 threadid << "[" << getpid() << "] shire_handler" << '\0';
1275 saml::NDC ndc(threadid.str().c_str());
1277 // Determine web site number. This can't really fail, I don't think.
1279 GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
1281 // Match site instance to host name, skip if no match.
1282 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
1283 if (map_i==g_Sites.end())
1284 return WriteClientError(lpECB,"Shibboleth filter not configured for this web site.");
1286 // We lock the configuration system for the duration.
1287 IConfig* conf=g_Config->getINI();
1288 Locker locker(conf);
1290 // Map request to application and content settings.
1291 IRequestMapper* mapper=conf->getRequestMapper();
1292 Locker locker2(mapper);
1293 IRequestMapper::Settings settings=map_request(lpECB,mapper,map_i->second,targeturl);
1294 pair<bool,const char*> application_id=settings.first->getString("applicationId");
1295 application=conf->getApplication(application_id.second);
1296 const IPropertySet* sessionProps=application ? application->getPropertySet("Sessions") : NULL;
1297 if (!application || !sessionProps)
1298 return WriteClientError(lpECB,"Unable to map request to application session settings, check configuration.");
1300 SHIRE shire(application);
1302 const char* shireURL=shire.getShireURL(targeturl.c_str());
1304 return WriteClientError(lpECB,"Unable to map request to proper shireURL setting, check configuration.");
1306 // Make sure we only process the SHIRE requests.
1307 if (!strstr(targeturl.c_str(),shireURL))
1308 return WriteClientError(lpECB,"ISAPI extension can only be invoked to process incoming sessions."
1309 "Make sure the mapped file extension doesn't match actual content.");
1311 pair<const char*,const char*> shib_cookie=shire.getCookieNameProps();
1313 // Make sure this is SSL, if it should be
1314 pair<bool,bool> shireSSL=sessionProps->getBool("shireSSL");
1315 if (!shireSSL.first || shireSSL.second) {
1316 GetServerVariable(lpECB,"HTTPS",buf,10);
1318 throw ShibTargetException(SHIBRPC_OK,"blocked non-SSL access to SHIRE POST processor");
1321 pair<bool,bool> httpRedirects=sessionProps->getBool("httpRedirects");
1322 pair<bool,const char*> redirectPage=sessionProps->getString("redirectPage");
1323 if (httpRedirects.first && !httpRedirects.second && !redirectPage.first)
1324 return WriteClientError(lpECB,"HTML-based redirection requires a redirectPage property.");
1326 // Check for Mac web browser
1330 GetServerVariable(lpECB,"HTTP_USER_AGENT",agent,64);
1331 if (strstr(agent,"AppleWebKit/"))
1335 // If this is a GET, we manufacture an AuthnRequest.
1336 if (!stricmp(lpECB->lpszMethod,"GET")) {
1337 const char* areq=lpECB->lpszQueryString ? shire.getLazyAuthnRequest(lpECB->lpszQueryString) : NULL;
1339 throw ShibTargetException(SHIBRPC_OK, "malformed arguments to request a new session");
1340 if (!httpRedirects.first || httpRedirects.second) {
1341 string hdrs=string("Location: ") + areq + "\r\n"
1342 "Content-Type: text/html\r\n"
1343 "Content-Length: 40\r\n"
1344 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
1345 "Cache-Control: private,no-store,no-cache\r\n\r\n";
1346 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"302 Moved",0,(LPDWORD)hdrs.c_str());
1347 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
1349 lpECB->WriteClient(lpECB->ConnID,(LPVOID)redmsg,&resplen,HSE_IO_SYNC);
1350 return HSE_STATUS_SUCCESS;
1353 ShibMLP markupProcessor;
1354 markupProcessor.insert("requestURL",areq);
1355 return WriteRedirectPage(lpECB, application, redirectPage.second, markupProcessor);
1358 else if (stricmp(lpECB->lpszMethod,"POST"))
1359 throw ShibTargetException(SHIBRPC_OK,"blocked non-POST to SHIRE POST processor");
1361 // Sure sure this POST is an appropriate content type
1362 if (!lpECB->lpszContentType || stricmp(lpECB->lpszContentType,"application/x-www-form-urlencoded"))
1363 throw ShibTargetException(SHIBRPC_OK,"blocked bad content-type to SHIRE POST processor");
1366 pair<const char*,const char*> elements=pair<const char*,const char*>(NULL,NULL);
1367 if (lpECB->cbTotalBytes > 1024*1024) // 1MB?
1368 throw ShibTargetException(SHIBRPC_OK,"blocked too-large a post to SHIRE POST processor");
1369 else if (lpECB->cbTotalBytes!=lpECB->cbAvailable) {
1372 DWORD datalen=lpECB->cbTotalBytes;
1375 BOOL ret=lpECB->ReadClient(lpECB->ConnID,buf,&buflen);
1376 if (!ret || !buflen)
1377 throw ShibTargetException(SHIBRPC_OK,"error reading POST data from browser");
1378 cgistr.append(buf,buflen);
1381 elements=shire.getFormSubmission(cgistr.c_str(),cgistr.length());
1384 elements=shire.getFormSubmission(reinterpret_cast<char*>(lpECB->lpbData),lpECB->cbAvailable);
1386 // Make sure the SAML Response parameter exists
1387 if (!elements.first || !*elements.first)
1388 throw ShibTargetException(SHIBRPC_OK, "SHIRE POST failed to find SAMLResponse form element");
1390 // Make sure the target parameter exists
1391 if (!elements.second || !*elements.second)
1392 throw ShibTargetException(SHIBRPC_OK, "SHIRE POST failed to find TARGET form element");
1394 GetServerVariable(lpECB,"REMOTE_ADDR",buf,16);
1396 // Process the post.
1398 RPCError* status=NULL;
1399 ShibMLP markupProcessor;
1400 markupProcessor.insert("requestURL", targeturl.c_str());
1402 status = shire.sessionCreate(elements.first,buf,cookie);
1404 catch (ShibTargetException &e) {
1405 markupProcessor.insert("errorType", "Session Creation Service Error");
1406 markupProcessor.insert("errorText", e.what());
1407 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
1408 return WriteClientError(lpECB, application, "shire", markupProcessor);
1412 markupProcessor.insert("errorType", "Session Creation Service Error");
1413 markupProcessor.insert("errorText", "Unexpected Exception");
1414 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
1415 return WriteClientError(lpECB, application, "shire", markupProcessor);
1419 if (status->isError()) {
1420 if (status->isRetryable()) {
1422 const char* loc=shire.getAuthnRequest(elements.second);
1423 if (!httpRedirects.first || httpRedirects.second) {
1424 string hdrs=string("Location: ") + loc + "\r\n"
1425 "Content-Type: text/html\r\n"
1426 "Content-Length: 40\r\n"
1427 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
1428 "Cache-Control: private,no-store,no-cache\r\n\r\n";
1429 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"302 Moved",0,(LPDWORD)hdrs.c_str());
1430 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
1432 lpECB->WriteClient(lpECB->ConnID,(LPVOID)redmsg,&resplen,HSE_IO_SYNC);
1433 return HSE_STATUS_SUCCESS;
1436 markupProcessor.insert("requestURL",loc);
1437 return WriteRedirectPage(lpECB, application, redirectPage.second, markupProcessor);
1441 // Return this error to the user.
1442 markupProcessor.insert(*status);
1444 return WriteClientError(lpECB,application,"shire",markupProcessor);
1448 // We've got a good session, set the cookie and redirect to target.
1449 cookie = string("Set-Cookie: ") + shib_cookie.first + '=' + cookie + shib_cookie.second + "\r\n"
1450 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
1451 "Cache-Control: private,no-store,no-cache\r\n";
1452 if (!httpRedirects.first || httpRedirects.second) {
1453 cookie=cookie + "Content-Type: text/html\r\nLocation: " + elements.second + "\r\nContent-Length: 40\r\n\r\n";
1454 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"302 Moved",0,(LPDWORD)cookie.c_str());
1455 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
1457 lpECB->WriteClient(lpECB->ConnID,(LPVOID)redmsg,&resplen,HSE_IO_SYNC);
1458 return HSE_STATUS_SUCCESS;
1461 markupProcessor.insert("requestURL",elements.second);
1462 return WriteRedirectPage(lpECB, application, redirectPage.second, markupProcessor, cookie.c_str());
1465 catch (ShibTargetException &e) {
1467 ShibMLP markupProcessor;
1468 markupProcessor.insert("requestURL", targeturl.c_str());
1469 markupProcessor.insert("errorType", "Session Creation Service Error");
1470 markupProcessor.insert("errorText", e.what());
1471 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
1472 return WriteClientError(lpECB,application,"shire",markupProcessor);
1478 ShibMLP markupProcessor;
1479 markupProcessor.insert("requestURL", targeturl.c_str());
1480 markupProcessor.insert("errorType", "Session Creation Service Error");
1481 markupProcessor.insert("errorText", "Unexpected Exception");
1482 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
1483 return WriteClientError(lpECB,application,"shire",markupProcessor);
1488 return HSE_STATUS_ERROR;