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...");
169 LPCSTR schemadir=getenv("SHIBSCHEMAS");
171 schemadir=SHIB_SCHEMAS;
172 LPCSTR config=getenv("SHIBCONFIG");
175 g_Config=&ShibTargetConfig::getConfig();
176 g_Config->setFeatures(
177 ShibTargetConfig::Listener |
178 ShibTargetConfig::Metadata |
179 ShibTargetConfig::AAP |
180 ShibTargetConfig::RequestMapper |
181 ShibTargetConfig::SHIREExtensions |
182 ShibTargetConfig::Logging
184 if (!g_Config->init(schemadir,config)) {
186 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
187 "Filter startup failed during initialization, check shire log for help.");
191 // Access the implementation-specifics for site mappings.
192 IConfig* conf=g_Config->getINI();
194 const IPropertySet* props=conf->getPropertySet("SHIRE");
196 const DOMElement* impl=saml::XML::getFirstChildElement(
197 props->getElement(),ShibTargetConfig::SHIBTARGET_NS,Implementation
199 if (impl && (impl=saml::XML::getFirstChildElement(impl,ShibTargetConfig::SHIBTARGET_NS,ISAPI))) {
200 const XMLCh* flag=impl->getAttributeNS(NULL,normalizeRequest);
201 g_bNormalizeRequest=(!flag || !*flag || *flag==chDigit_1 || *flag==chLatin_t);
202 impl=saml::XML::getFirstChildElement(impl,ShibTargetConfig::SHIBTARGET_NS,Site);
204 auto_ptr_char id(impl->getAttributeNS(NULL,id));
206 g_Sites.insert(pair<string,site_t>(id.get(),site_t(impl)));
207 impl=saml::XML::getNextSiblingElement(impl,ShibTargetConfig::SHIBTARGET_NS,Site);
214 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Filter startup failed with an exception.");
221 pVer->dwFilterVersion=HTTP_FILTER_REVISION;
222 strncpy(pVer->lpszFilterDesc,"Shibboleth ISAPI Filter",SF_MAX_FILTER_DESC_LEN);
223 pVer->dwFlags=(SF_NOTIFY_ORDER_HIGH |
224 SF_NOTIFY_SECURE_PORT |
225 SF_NOTIFY_NONSECURE_PORT |
226 SF_NOTIFY_PREPROC_HEADERS |
228 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter initialized...");
232 extern "C" BOOL WINAPI TerminateFilter(DWORD)
235 g_Config->shutdown();
237 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter shut down...");
241 /* Next up, some suck-free versions of various APIs.
243 You DON'T require people to guess the buffer size and THEN tell them the right size.
244 Returning an LPCSTR is apparently way beyond their ken. Not to mention the fact that
245 constant strings aren't typed as such, making it just that much harder. These versions
246 are now updated to use a special growable buffer object, modeled after the standard
247 string class. The standard string won't work because they left out the option to
248 pre-allocate a non-constant buffer.
254 dynabuf() { bufptr=NULL; buflen=0; }
255 dynabuf(size_t s) { bufptr=new char[buflen=s]; *bufptr=0; }
256 ~dynabuf() { delete[] bufptr; }
257 size_t length() const { return bufptr ? strlen(bufptr) : 0; }
258 size_t size() const { return buflen; }
259 bool empty() const { return length()==0; }
260 void reserve(size_t s, bool keep=false);
261 void erase() { if (bufptr) memset(bufptr,0,buflen); }
262 operator char*() { return bufptr; }
263 bool operator ==(const char* s) const;
264 bool operator !=(const char* s) const { return !(*this==s); }
270 void dynabuf::reserve(size_t s, bool keep)
277 p[buflen]=bufptr[buflen];
283 bool dynabuf::operator==(const char* s) const
285 if (buflen==NULL || s==NULL)
286 return (buflen==NULL && s==NULL);
288 return strcmp(bufptr,s)==0;
291 void GetServerVariable(PHTTP_FILTER_CONTEXT pfc, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
292 throw (bad_alloc, DWORD)
298 while (!pfc->GetServerVariable(pfc,lpszVariable,s,&size))
300 // Grumble. Check the error.
301 DWORD e=GetLastError();
302 if (e==ERROR_INSUFFICIENT_BUFFER)
307 if (bRequired && s.empty())
311 void GetServerVariable(LPEXTENSION_CONTROL_BLOCK lpECB, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
312 throw (bad_alloc, DWORD)
318 while (lpECB->GetServerVariable(lpECB->ConnID,lpszVariable,s,&size))
320 // Grumble. Check the error.
321 DWORD e=GetLastError();
322 if (e==ERROR_INSUFFICIENT_BUFFER)
327 if (bRequired && s.empty())
331 void GetHeader(PHTTP_FILTER_PREPROC_HEADERS pn, PHTTP_FILTER_CONTEXT pfc,
332 LPSTR lpszName, dynabuf& s, DWORD size=80, bool bRequired=true)
333 throw (bad_alloc, DWORD)
339 while (!pn->GetHeader(pfc,lpszName,s,&size))
341 // Grumble. Check the error.
342 DWORD e=GetLastError();
343 if (e==ERROR_INSUFFICIENT_BUFFER)
348 if (bRequired && s.empty())
352 /****************************************************************************/
355 class ShibTargetIsapiF : public ShibTarget
358 ShibTargetIsapiF(PHTTP_FILTER_CONTEXT pfc, PHTTP_FILTER_PREPROC_HEADERS pn,
359 const site_t& site) {
361 // URL path always come from IIS.
363 GetHeader(pn,pfc,"url",url,256,false);
365 // Port may come from IIS or from site def.
367 if (site.m_port.empty() || !g_bNormalizeRequest)
368 GetServerVariable(pfc,"SERVER_PORT",port,10);
370 strncpy(port,site.m_port.c_str(),10);
371 static_cast<char*>(port)[10]=0;
374 // Scheme may come from site def or be derived from IIS.
375 const char* scheme=site.m_scheme.c_str();
376 if (!scheme || !*scheme || !g_bNormalizeRequest)
377 scheme=pfc->fIsSecurePort ? "https" : "http";
379 // Get the remote address
380 dynabuf remote_addr(16);
381 GetServerVariable(pfc,"REMOTE_ADDR",remote_addr,16);
383 // XXX: How do I get the content type and HTTP Method from this context?
385 init(g_Config, string(scheme), site.m_name, atoi(port),
386 string(url), string(""), // XXX: content type
387 string(remote_addr), string("") // XXX: http method
393 ~ShibTargetIsapiF() { }
395 virtual void log(ShibLogLevel level, const string &msg) {
396 LogEvent(NULL, (level == LogLevelDebug ? EVENTLOG_INFORMATION_TYPE :
397 (level == LogLevelInfo ? EVENTLOG_INFORMATION_TYPE :
398 (level == LogLevelWarn ? EVENTLOG_WARNING_TYPE : EVENTLOG_ERROR_TYPE))),
399 2100, NULL, msg.c_str());
401 virtual string getCookies(void) {
403 GetHeader(m_pn, m_pfc, "Cookie:", buf, 128, false);
404 return buf.empty() ? "" : buf;
407 virtual void clearHeader(const string &name) {
408 string hdr = name + ":";
409 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), "");
411 virtual void setHeader(const string &name, const string &value) {
412 string hdr = name + ":";
413 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()),
414 const_cast<char*>(value.c_str()));
416 virtual string getHeader(const string &name) {
417 string hdr = name + ":";
419 GetHeader(m_pn, m_pfc, const_cast<char*>(hdr.c_str()), buf, 1024, false);
422 virtual void setRemoteUser(const string &user) {
423 setHeader(string("remote-user"), user);
425 virtual string getRemoteUser(void) {
426 return getHeader(string("remote-user"));
428 virtual void* sendPage(const string &msg, const string content_type,
429 const Iterator<header_t>& headers=EMPTY(header_t), int code=200) {
430 string hdr = string ("Connection: close\r\nContent-type: ") + content_type + "\r\n";
431 while (headers.hasNext()) {
432 const header_t& h=headers.next();
433 hdr += h.first + ": " + h.second + "\r\n";
436 // XXX Need to handle "code"
437 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, "200 OK", (DWORD)hdr.c_str(), 0);
438 DWORD resplen = msg.size();
439 m_pfc->WriteClient(m_pfc, (LPVOID)msg.c_str(), &resplen, 0);
440 return (void*)SF_STATUS_REQ_FINISHED;
442 virtual void* sendRedirect(const string url) {
443 // XXX: Don't support the httpRedirect option, yet.
444 string hdrs=string("Location: ") + url + "\r\n"
445 "Content-Type: text/html\r\n"
446 "Content-Length: 40\r\n"
447 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
448 "Cache-Control: private,no-store,no-cache\r\n\r\n";
449 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER,
450 "302 Please Wait", (DWORD)hdrs.c_str(), 0);
451 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
453 m_pfc->WriteClient(m_pfc, (LPVOID)redmsg, &resplen, 0);
454 return reinterpret_cast<void*>(SF_STATUS_REQ_FINISHED);
456 // XXX: We might not ever hit the 'decline' status in this filter.
457 //virtual void* returnDecline(void) { }
458 virtual void* returnOK(void) { return (void*) SF_STATUS_REQ_NEXT_NOTIFICATION; }
460 // The filter never processes the POST, so stub these methods.
461 virtual void setCookie(const string &name, const string &value) { throw runtime_error("setCookie not implemented"); }
462 virtual string getArgs(void) { throw runtime_error("getArgs not implemented"); }
463 virtual string getPostData(void) { throw runtime_error("getPostData not implemented"); }
465 PHTTP_FILTER_CONTEXT m_pfc;
466 PHTTP_FILTER_PREPROC_HEADERS m_pn;
469 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const char* msg)
471 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
472 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
473 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)ctype,0);
474 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Filter Error</TITLE></HEAD><BODY>"
475 "<H1>Shibboleth Filter Error</H1>";
476 DWORD resplen=strlen(xmsg);
477 pfc->WriteClient(pfc,(LPVOID)xmsg,&resplen,0);
479 pfc->WriteClient(pfc,(LPVOID)msg,&resplen,0);
480 static const char* xmsg2="</BODY></HTML>";
481 resplen=strlen(xmsg2);
482 pfc->WriteClient(pfc,(LPVOID)xmsg2,&resplen,0);
483 return SF_STATUS_REQ_FINISHED;
486 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
488 // Is this a log notification?
489 if (notificationType==SF_NOTIFY_LOG)
491 if (pfc->pFilterContext)
492 ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=static_cast<LPCSTR>(pfc->pFilterContext);
493 return SF_STATUS_REQ_NEXT_NOTIFICATION;
496 PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
499 // Determine web site number. This can't really fail, I don't think.
501 GetServerVariable(pfc,"INSTANCE_ID",buf,10);
503 // Match site instance to host name, skip if no match.
504 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
505 if (map_i==g_Sites.end())
506 return SF_STATUS_REQ_NEXT_NOTIFICATION;
508 ostringstream threadid;
509 threadid << "[" << getpid() << "] isapi_shib" << '\0';
510 saml::NDC ndc(threadid.str().c_str());
512 ShibTargetIsapiF stf(pfc, pn, map_i->second);
514 // "false" because we don't override the Shib settings
515 pair<bool,void*> res = stf.doCheckAuthN();
516 if (res.first) return (DWORD)res.second;
518 // "false" because we don't override the Shib settings
519 res = stf.doExportAssertions();
520 if (res.first) return (DWORD)res.second;
522 res = stf.doCheckAuthZ();
523 if (res.first) return (DWORD)res.second;
525 return SF_STATUS_REQ_NEXT_NOTIFICATION;
528 return WriteClientError(pfc,"Out of Memory");
531 if (e==ERROR_NO_DATA)
532 return WriteClientError(pfc,"A required variable or header was empty.");
534 return WriteClientError(pfc,"Server detected unexpected IIS error.");
538 return WriteClientError(pfc,"Server caught an unknown exception.");
542 return WriteClientError(pfc,"Server reached unreachable code, save my walrus!");
547 IRequestMapper::Settings map_request(
548 PHTTP_FILTER_CONTEXT pfc, PHTTP_FILTER_PREPROC_HEADERS pn, IRequestMapper* mapper, const site_t& site, string& target
551 // URL path always come from IIS.
553 GetHeader(pn,pfc,"url",url,256,false);
555 // Port may come from IIS or from site def.
557 if (site.m_port.empty() || !g_bNormalizeRequest)
558 GetServerVariable(pfc,"SERVER_PORT",port,10);
560 strncpy(port,site.m_port.c_str(),10);
561 static_cast<char*>(port)[10]=0;
564 // Scheme may come from site def or be derived from IIS.
565 const char* scheme=site.m_scheme.c_str();
566 if (!scheme || !*scheme || !g_bNormalizeRequest)
567 scheme=pfc->fIsSecurePort ? "https" : "http";
569 // Start with scheme and hostname.
570 if (g_bNormalizeRequest) {
571 target = string(scheme) + "://" + site.m_name;
575 GetServerVariable(pfc,"SERVER_NAME",name,64);
576 target = string(scheme) + "://" + static_cast<char*>(name);
579 // If port is non-default, append it.
580 if ((!strcmp(scheme,"http") && port!="80") || (!strcmp(scheme,"https") && port!="443"))
581 target = target + ':' + static_cast<char*>(port);
585 target+=static_cast<char*>(url);
587 return mapper->getSettingsFromParsedURL(scheme,site.m_name.c_str(),strtoul(port,NULL,10),url);
590 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const IApplication* app, const char* page, ShibMLP& mlp)
592 const IPropertySet* props=app->getPropertySet("Errors");
594 pair<bool,const char*> p=props->getString(page);
596 ifstream infile(p.second);
597 if (!infile.fail()) {
598 const char* res = mlp.run(infile,props);
600 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
601 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)ctype,0);
602 DWORD resplen=strlen(res);
603 pfc->WriteClient(pfc,(LPVOID)res,&resplen,0);
604 return SF_STATUS_REQ_FINISHED;
610 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Filter unable to open error template.");
611 return WriteClientError(pfc,"Unable to open error template, check settings.");
614 DWORD WriteRedirectPage(PHTTP_FILTER_CONTEXT pfc, const IApplication* app, const char* file, ShibMLP& mlp, const char* headers=NULL)
616 ifstream infile(file);
617 if (!infile.fail()) {
618 const char* res = mlp.run(infile,app->getPropertySet("Errors"));
621 sprintf(buf,"Content-Length: %u\r\nContent-Type: text/html\r\n\r\n",strlen(res));
625 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)h.c_str(),0);
628 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)buf,0);
629 DWORD resplen=strlen(res);
630 pfc->WriteClient(pfc,(LPVOID)res,&resplen,0);
631 return SF_STATUS_REQ_FINISHED;
634 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Extension unable to open redirect template.");
635 return WriteClientError(pfc,"Unable to open redirect template, check settings.");
638 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
640 // Is this a log notification?
641 if (notificationType==SF_NOTIFY_LOG)
643 if (pfc->pFilterContext)
644 ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=static_cast<LPCSTR>(pfc->pFilterContext);
645 return SF_STATUS_REQ_NEXT_NOTIFICATION;
648 PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
651 // Determine web site number. This can't really fail, I don't think.
653 GetServerVariable(pfc,"INSTANCE_ID",buf,10);
655 // Match site instance to host name, skip if no match.
656 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
657 if (map_i==g_Sites.end())
658 return SF_STATUS_REQ_NEXT_NOTIFICATION;
660 ostringstream threadid;
661 threadid << "[" << getpid() << "] isapi_shib" << '\0';
662 saml::NDC ndc(threadid.str().c_str());
664 // We lock the configuration system for the duration.
665 IConfig* conf=g_Config->getINI();
668 // Map request to application and content settings.
670 IRequestMapper* mapper=conf->getRequestMapper();
671 Locker locker2(mapper);
672 IRequestMapper::Settings settings=map_request(pfc,pn,mapper,map_i->second,targeturl);
673 pair<bool,const char*> application_id=settings.first->getString("applicationId");
674 const IApplication* application=conf->getApplication(application_id.second);
676 return WriteClientError(pfc,"Unable to map request to application settings, check configuration.");
678 // Declare SHIRE object for this request.
679 SHIRE shire(application);
681 const char* shireURL=shire.getShireURL(targeturl.c_str());
683 return WriteClientError(pfc,"Unable to map request to proper shireURL setting, check configuration.");
685 // If the user is accessing the SHIRE acceptance point, pass it on.
686 if (targeturl.find(shireURL)!=string::npos)
687 return SF_STATUS_REQ_NEXT_NOTIFICATION;
689 // Now check the policy for this request.
690 pair<bool,bool> requireSession=settings.first->getBool("requireSession");
691 pair<const char*,const char*> shib_cookie=shire.getCookieNameProps();
692 pair<bool,bool> httpRedirects=application->getPropertySet("Sessions")->getBool("httpRedirects");
693 pair<bool,const char*> redirectPage=application->getPropertySet("Sessions")->getString("redirectPage");
694 if (httpRedirects.first && !httpRedirects.second && !redirectPage.first)
695 return WriteClientError(pfc,"HTML-based redirection requires a redirectPage property.");
697 // Check for session cookie.
698 const char* session_id=NULL;
699 GetHeader(pn,pfc,"Cookie:",buf,128,false);
700 Category::getInstance("isapi_shib.HttpFilterProc").debug("cookie header is {%s}",(const char*)buf);
701 if (!buf.empty() && (session_id=strstr(buf,shib_cookie.first))) {
702 session_id+=strlen(shib_cookie.first) + 1; /* Skip over the '=' */
703 char* cookieend=strchr(session_id,';');
705 *cookieend = '\0'; /* Ignore anyting after a ; */
708 if (!session_id || !*session_id) {
709 // If no session required, bail now.
710 if (!requireSession.second)
711 return SF_STATUS_REQ_NEXT_NOTIFICATION;
713 // No acceptable cookie, and we require a session. Generate an AuthnRequest.
714 const char* areq = shire.getAuthnRequest(targeturl.c_str());
715 if (!httpRedirects.first || httpRedirects.second) {
716 string hdrs=string("Location: ") + areq + "\r\n"
717 "Content-Type: text/html\r\n"
718 "Content-Length: 40\r\n"
719 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
720 "Cache-Control: private,no-store,no-cache\r\n\r\n";
721 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"302 Please Wait",(DWORD)hdrs.c_str(),0);
722 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
724 pfc->WriteClient(pfc,(LPVOID)redmsg,&resplen,0);
725 return SF_STATUS_REQ_FINISHED;
728 ShibMLP markupProcessor;
729 markupProcessor.insert("requestURL",areq);
730 return WriteRedirectPage(pfc, application, redirectPage.second, markupProcessor);
734 // Make sure this session is still valid.
735 RPCError* status = NULL;
736 ShibMLP markupProcessor;
737 markupProcessor.insert("requestURL", targeturl);
740 GetServerVariable(pfc,"REMOTE_ADDR",abuf,16);
742 status = shire.sessionIsValid(session_id, abuf);
744 catch (ShibTargetException &e) {
745 markupProcessor.insert("errorType", "Session Processing Error");
746 markupProcessor.insert("errorText", e.what());
747 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
748 return WriteClientError(pfc, application, "shire", markupProcessor);
752 markupProcessor.insert("errorType", "Session Processing Error");
753 markupProcessor.insert("errorText", "Unexpected Exception");
754 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
755 return WriteClientError(pfc, application, "shire", markupProcessor);
760 if (status->isError()) {
761 if (!requireSession.second)
762 return SF_STATUS_REQ_NEXT_NOTIFICATION;
763 else if (status->isRetryable()) {
764 // Oops, session is invalid. Generate AuthnRequest.
766 const char* areq = shire.getAuthnRequest(targeturl.c_str());
767 if (!httpRedirects.first || httpRedirects.second) {
768 string hdrs=string("Location: ") + areq + "\r\n"
769 "Content-Type: text/html\r\n"
770 "Content-Length: 40\r\n"
771 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
772 "Cache-Control: private,no-store,no-cache\r\n\r\n";
773 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"302 Please Wait",(DWORD)hdrs.c_str(),0);
774 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
776 pfc->WriteClient(pfc,(LPVOID)redmsg,&resplen,0);
777 return SF_STATUS_REQ_FINISHED;
780 markupProcessor.insert("requestURL",areq);
781 return WriteRedirectPage(pfc, application, redirectPage.second, markupProcessor);
785 // return the error page to the user
786 markupProcessor.insert(*status);
788 return WriteClientError(pfc, application, "shire", markupProcessor);
795 vector<SAMLAssertion*> assertions;
796 SAMLAuthenticationStatement* sso_statement=NULL;
799 status = rm.getAssertions(session_id, abuf, assertions, &sso_statement);
801 catch (ShibTargetException &e) {
802 markupProcessor.insert("errorType", "Attribute Processing Error");
803 markupProcessor.insert("errorText", e.what());
804 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
805 return WriteClientError(pfc, application, "rm", markupProcessor);
809 markupProcessor.insert("errorType", "Attribute Processing Error");
810 markupProcessor.insert("errorText", "Unexpected Exception");
811 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
812 return WriteClientError(pfc, application, "rm", markupProcessor);
816 if (status->isError()) {
817 markupProcessor.insert(*status);
819 return WriteClientError(pfc, application, "rm", markupProcessor);
823 // Do we have an access control plugin?
824 if (settings.second) {
825 Locker acllock(settings.second);
826 if (!settings.second->authorized(*sso_statement,assertions)) {
827 for (int k = 0; k < assertions.size(); k++)
828 delete assertions[k];
829 delete sso_statement;
830 return WriteClientError(pfc, application, "access", markupProcessor);
834 // Get the AAP providers, which contain the attribute policy info.
835 Iterator<IAAP*> provs=application->getAAPProviders();
837 // Clear out the list of mapped attributes
838 while (provs.hasNext()) {
839 IAAP* aap=provs.next();
842 Iterator<const IAttributeRule*> rules=aap->getAttributeRules();
843 while (rules.hasNext()) {
844 const char* header=rules.next()->getHeader();
846 string hname=string(header) + ':';
847 pn->SetHeader(pfc,const_cast<char*>(hname.c_str()),"");
853 for (int k = 0; k < assertions.size(); k++)
854 delete assertions[k];
855 delete sso_statement;
856 markupProcessor.insert("errorType", "Attribute Processing Error");
857 markupProcessor.insert("errorText", "Unexpected Exception");
858 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
859 return WriteClientError(pfc, application, "rm", markupProcessor);
865 // Maybe export the first assertion.
866 pn->SetHeader(pfc,"remote-user:","");
867 pn->SetHeader(pfc,"Shib-Attributes:","");
868 pair<bool,bool> exp=settings.first->getBool("exportAssertion");
869 if (exp.first && exp.second && assertions.size()) {
871 RM::serialize(*(assertions[0]), assertion);
872 string::size_type lfeed;
873 while ((lfeed=assertion.find('\n'))!=string::npos)
874 assertion.erase(lfeed,1);
875 pn->SetHeader(pfc,"Shib-Attributes:",const_cast<char*>(assertion.c_str()));
878 pn->SetHeader(pfc,"Shib-Origin-Site:","");
879 pn->SetHeader(pfc,"Shib-Authentication-Method:","");
880 pn->SetHeader(pfc,"Shib-NameIdentifier-Format:","");
882 // Export the SAML AuthnMethod and the origin site name.
883 auto_ptr_char os(sso_statement->getSubject()->getNameIdentifier()->getNameQualifier());
884 auto_ptr_char am(sso_statement->getAuthMethod());
885 pn->SetHeader(pfc,"Shib-Origin-Site:", const_cast<char*>(os.get()));
886 pn->SetHeader(pfc,"Shib-Authentication-Method:", const_cast<char*>(am.get()));
889 AAP wrapper(provs,sso_statement->getSubject()->getNameIdentifier()->getFormat(),Constants::SHIB_ATTRIBUTE_NAMESPACE_URI);
890 if (!wrapper.fail() && wrapper->getHeader()) {
891 auto_ptr_char form(sso_statement->getSubject()->getNameIdentifier()->getFormat());
892 auto_ptr_char nameid(sso_statement->getSubject()->getNameIdentifier()->getName());
893 pn->SetHeader(pfc,"Shib-NameIdentifier-Format:",const_cast<char*>(form.get()));
894 if (!strcmp(wrapper->getHeader(),"REMOTE_USER")) {
895 char* principal=const_cast<char*>(nameid.get());
896 pn->SetHeader(pfc,"remote-user:",principal);
897 pfc->pFilterContext=pfc->AllocMem(pfc,strlen(principal)+1,0);
898 if (pfc->pFilterContext)
899 strcpy(static_cast<char*>(pfc->pFilterContext),principal);
902 string hname=string(wrapper->getHeader()) + ':';
903 pn->SetHeader(pfc,const_cast<char*>(wrapper->getHeader()),const_cast<char*>(nameid.get()));
907 pn->SetHeader(pfc,"Shib-Application-ID:","");
908 pn->SetHeader(pfc,"Shib-Application-ID:",const_cast<char*>(application_id.second));
910 // Export the attributes.
911 Iterator<SAMLAssertion*> a_iter(assertions);
912 while (a_iter.hasNext()) {
913 SAMLAssertion* assert=a_iter.next();
914 Iterator<SAMLStatement*> statements=assert->getStatements();
915 while (statements.hasNext()) {
916 SAMLAttributeStatement* astate=dynamic_cast<SAMLAttributeStatement*>(statements.next());
919 Iterator<SAMLAttribute*> attrs=astate->getAttributes();
920 while (attrs.hasNext()) {
921 SAMLAttribute* attr=attrs.next();
923 // Are we supposed to export it?
924 AAP wrapper(provs,attr->getName(),attr->getNamespace());
925 if (wrapper.fail() || !wrapper->getHeader())
928 Iterator<string> vals=attr->getSingleByteValues();
929 if (!strcmp(wrapper->getHeader(),"REMOTE_USER") && vals.hasNext()) {
930 char* principal=const_cast<char*>(vals.next().c_str());
931 pn->SetHeader(pfc,"remote-user:",principal);
932 pfc->pFilterContext=pfc->AllocMem(pfc,strlen(principal)+1,0);
933 if (pfc->pFilterContext)
934 strcpy(static_cast<char*>(pfc->pFilterContext),principal);
939 string hname=string(wrapper->getHeader()) + ':';
940 GetHeader(pn,pfc,const_cast<char*>(hname.c_str()),buf,256,false);
945 for (; vals.hasNext(); it++) {
946 string value = vals.next();
947 for (string::size_type pos = value.find_first_of(";", string::size_type(0));
949 pos = value.find_first_of(";", pos)) {
950 value.insert(pos, "\\");
956 header=header + ';' + value;
958 pn->SetHeader(pfc,const_cast<char*>(hname.c_str()),const_cast<char*>(header.c_str()));
965 for (int k = 0; k < assertions.size(); k++)
966 delete assertions[k];
967 delete sso_statement;
969 return SF_STATUS_REQ_NEXT_NOTIFICATION;
972 return WriteClientError(pfc,"Out of Memory");
975 if (e==ERROR_NO_DATA)
976 return WriteClientError(pfc,"A required variable or header was empty.");
978 return WriteClientError(pfc,"Server detected unexpected IIS error.");
982 return WriteClientError(pfc,"Server caught an unknown exception.");
986 return WriteClientError(pfc,"Server reached unreachable code, save my walrus!");
990 /****************************************************************************/
993 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* msg)
995 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
996 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
997 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
998 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Error</TITLE></HEAD><BODY><H1>Shibboleth Error</H1>";
999 DWORD resplen=strlen(xmsg);
1000 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg,&resplen,HSE_IO_SYNC);
1001 resplen=strlen(msg);
1002 lpECB->WriteClient(lpECB->ConnID,(LPVOID)msg,&resplen,HSE_IO_SYNC);
1003 static const char* xmsg2="</BODY></HTML>";
1004 resplen=strlen(xmsg2);
1005 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg2,&resplen,HSE_IO_SYNC);
1006 return HSE_STATUS_SUCCESS;
1010 class ShibTargetIsapiE : public ShibTarget
1013 ShibTargetIsapiE(LPEXTENSION_CONTROL_BLOCK lpECB, const site_t& site) :
1017 GetServerVariable(lpECB,"HTTPS",ssl,5);
1018 bool SSL=(ssl=="on" || ssl=="ON");
1020 // URL path always come from IIS.
1022 GetServerVariable(lpECB,"URL",url,255);
1024 // Port may come from IIS or from site def.
1026 if (site.m_port.empty() || !g_bNormalizeRequest)
1027 GetServerVariable(lpECB,"SERVER_PORT",port,10);
1029 strncpy(port,site.m_port.c_str(),10);
1030 static_cast<char*>(port)[10]=0;
1033 // Scheme may come from site def or be derived from IIS.
1034 const char* scheme=site.m_scheme.c_str();
1035 if (!scheme || !*scheme || !g_bNormalizeRequest) {
1036 scheme = SSL ? "https" : "http";
1039 // Get the remote address
1040 dynabuf remote_addr(16);
1041 GetServerVariable(lpECB, "REMOTE_ADDR", remote_addr, 16);
1043 init(g_Config, string(scheme), site.m_name, atoi(port),
1044 string(url), string(lpECB->lpszContentType ? lpECB->lpszContentType : ""),
1045 string(remote_addr), string(lpECB->lpszMethod)
1050 ~ShibTargetIsapiE() { }
1052 virtual void log(ShibLogLevel level, const string &msg) {
1053 LogEvent(NULL, (level == LogLevelDebug ? EVENTLOG_INFORMATION_TYPE :
1054 (level == LogLevelInfo ? EVENTLOG_INFORMATION_TYPE :
1055 (level == LogLevelWarn ? EVENTLOG_WARNING_TYPE : EVENTLOG_ERROR_TYPE))),
1056 2100, NULL, msg.c_str());
1058 virtual void setCookie(const string &name, const string &value) {
1059 // Set the cookie for later. Use it during the redirect.
1060 m_cookie += "Set-Cookie: " + name + "=" + value + "\r\n";
1062 virtual string getArgs(void) {
1063 return string(m_lpECB->lpszQueryString ? m_lpECB->lpszQueryString : "");
1065 virtual string getPostData(void) {
1066 if (m_lpECB->cbTotalBytes > 1024*1024) // 1MB?
1067 throw ShibTargetException(SHIBRPC_OK,
1068 "blocked too-large a post to SHIRE POST processor");
1069 else if (m_lpECB->cbTotalBytes != m_lpECB->cbAvailable) {
1072 DWORD datalen=m_lpECB->cbTotalBytes;
1075 BOOL ret = m_lpECB->ReadClient(m_lpECB->ConnID, buf, &buflen);
1076 if (!ret || !buflen)
1077 throw ShibTargetException(SHIBRPC_OK,
1078 "error reading POST data 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 POST");
1162 return WriteClientError(lpECB,
1163 "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;