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_PREPRC_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_DEBUG_TYPE ?
397 (level == LogLevelInfo : EVENTLOG_INFORMATION_TYPE ?
398 (level == LogLevelWarn : EVENTLOG_WARNING_TYPE ?
399 EVENTLOG_ERROR_TYPE))),
400 2100, NULL, msg.c_str());
402 virtual string getCookies(void) {
404 GetHeader(m_pn, m_pfc, "Cookie:", buf, 128, false);
406 // XXX: the filter never processes the POST.
407 //virtual void setCookie(const string &name, const string &value) { }
408 //virtual string getArgs(void) { }
409 //virtual string getPostData(void) { }
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, 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 pair<string, string> headers[], int code) {
433 string hdr = string ("Connection: close\r\nContent-type: ") + content_type + "\r\n";
434 for (int k = 0; k < headers.size(); k++) {
435 hdr += headers[k].first + ": " + headers[k].second + "\r\n";
438 // XXX Need to handle "code"
439 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, "200 OK",
440 (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 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 PHTTP_FILTER_CONTEXT m_pfc;
464 PHTTP_FILTER_PREPRC_HEADERS m_pn
467 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const char* msg)
469 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
470 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
471 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)ctype,0);
472 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Filter Error</TITLE></HEAD><BODY>"
473 "<H1>Shibboleth Filter Error</H1>";
474 DWORD resplen=strlen(xmsg);
475 pfc->WriteClient(pfc,(LPVOID)xmsg,&resplen,0);
477 pfc->WriteClient(pfc,(LPVOID)msg,&resplen,0);
478 static const char* xmsg2="</BODY></HTML>";
479 resplen=strlen(xmsg2);
480 pfc->WriteClient(pfc,(LPVOID)xmsg2,&resplen,0);
481 return SF_STATUS_REQ_FINISHED;
484 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
486 // Is this a log notification?
487 if (notificationType==SF_NOTIFY_LOG)
489 if (pfc->pFilterContext)
490 ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=static_cast<LPCSTR>(pfc->pFilterContext);
491 return SF_STATUS_REQ_NEXT_NOTIFICATION;
494 PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
497 // Determine web site number. This can't really fail, I don't think.
499 GetServerVariable(pfc,"INSTANCE_ID",buf,10);
501 // Match site instance to host name, skip if no match.
502 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
503 if (map_i==g_Sites.end())
504 return SF_STATUS_REQ_NEXT_NOTIFICATION;
506 ostringstream threadid;
507 threadid << "[" << getpid() << "] isapi_shib" << '\0';
508 saml::NDC ndc(threadid.str().c_str());
510 ShibTargetIsapiF stf(pfc, pn, map_i->second);
512 // "false" because we don't override the Shib settings
513 pair<bool,void*> res = ste.doCheckAuthN();
514 if (res.first) return (DWORD)res.second;
516 // "false" because we don't override the Shib settings
517 res = ste.doExportAssertions();
518 if (res.first) return (DWORD)res.second;
520 res = ste.doCheckAuthZ();
521 if (res.first) return (DWORD)res.second;
523 return SF_STATUS_REQ_NEXT_NOTIFICATION;
526 return WriteClientError(pfc,"Out of Memory");
529 if (e==ERROR_NO_DATA)
530 return WriteClientError(pfc,"A required variable or header was empty.");
532 return WriteClientError(pfc,"Server detected unexpected IIS error.");
536 return WriteClientError(pfc,"Server caught an unknown exception.");
540 return WriteClientError(pfc,"Server reached unreachable code, save my walrus!");
545 IRequestMapper::Settings map_request(
546 PHTTP_FILTER_CONTEXT pfc, PHTTP_FILTER_PREPROC_HEADERS pn, IRequestMapper* mapper, const site_t& site, string& target
549 // URL path always come from IIS.
551 GetHeader(pn,pfc,"url",url,256,false);
553 // Port may come from IIS or from site def.
555 if (site.m_port.empty() || !g_bNormalizeRequest)
556 GetServerVariable(pfc,"SERVER_PORT",port,10);
558 strncpy(port,site.m_port.c_str(),10);
559 static_cast<char*>(port)[10]=0;
562 // Scheme may come from site def or be derived from IIS.
563 const char* scheme=site.m_scheme.c_str();
564 if (!scheme || !*scheme || !g_bNormalizeRequest)
565 scheme=pfc->fIsSecurePort ? "https" : "http";
567 // Start with scheme and hostname.
568 if (g_bNormalizeRequest) {
569 target = string(scheme) + "://" + site.m_name;
573 GetServerVariable(pfc,"SERVER_NAME",name,64);
574 target = string(scheme) + "://" + static_cast<char*>(name);
577 // If port is non-default, append it.
578 if ((!strcmp(scheme,"http") && port!="80") || (!strcmp(scheme,"https") && port!="443"))
579 target = target + ':' + static_cast<char*>(port);
583 target+=static_cast<char*>(url);
585 return mapper->getSettingsFromParsedURL(scheme,site.m_name.c_str(),strtoul(port,NULL,10),url);
588 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const IApplication* app, const char* page, ShibMLP& mlp)
590 const IPropertySet* props=app->getPropertySet("Errors");
592 pair<bool,const char*> p=props->getString(page);
594 ifstream infile(p.second);
595 if (!infile.fail()) {
596 const char* res = mlp.run(infile,props);
598 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
599 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)ctype,0);
600 DWORD resplen=strlen(res);
601 pfc->WriteClient(pfc,(LPVOID)res,&resplen,0);
602 return SF_STATUS_REQ_FINISHED;
608 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Filter unable to open error template.");
609 return WriteClientError(pfc,"Unable to open error template, check settings.");
612 DWORD WriteRedirectPage(PHTTP_FILTER_CONTEXT pfc, const IApplication* app, const char* file, ShibMLP& mlp, const char* headers=NULL)
614 ifstream infile(file);
615 if (!infile.fail()) {
616 const char* res = mlp.run(infile,app->getPropertySet("Errors"));
619 sprintf(buf,"Content-Length: %u\r\nContent-Type: text/html\r\n\r\n",strlen(res));
623 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)h.c_str(),0);
626 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)buf,0);
627 DWORD resplen=strlen(res);
628 pfc->WriteClient(pfc,(LPVOID)res,&resplen,0);
629 return SF_STATUS_REQ_FINISHED;
632 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Extension unable to open redirect template.");
633 return WriteClientError(pfc,"Unable to open redirect template, check settings.");
636 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
638 // Is this a log notification?
639 if (notificationType==SF_NOTIFY_LOG)
641 if (pfc->pFilterContext)
642 ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=static_cast<LPCSTR>(pfc->pFilterContext);
643 return SF_STATUS_REQ_NEXT_NOTIFICATION;
646 PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
649 // Determine web site number. This can't really fail, I don't think.
651 GetServerVariable(pfc,"INSTANCE_ID",buf,10);
653 // Match site instance to host name, skip if no match.
654 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
655 if (map_i==g_Sites.end())
656 return SF_STATUS_REQ_NEXT_NOTIFICATION;
658 ostringstream threadid;
659 threadid << "[" << getpid() << "] isapi_shib" << '\0';
660 saml::NDC ndc(threadid.str().c_str());
662 // We lock the configuration system for the duration.
663 IConfig* conf=g_Config->getINI();
666 // Map request to application and content settings.
668 IRequestMapper* mapper=conf->getRequestMapper();
669 Locker locker2(mapper);
670 IRequestMapper::Settings settings=map_request(pfc,pn,mapper,map_i->second,targeturl);
671 pair<bool,const char*> application_id=settings.first->getString("applicationId");
672 const IApplication* application=conf->getApplication(application_id.second);
674 return WriteClientError(pfc,"Unable to map request to application settings, check configuration.");
676 // Declare SHIRE object for this request.
677 SHIRE shire(application);
679 const char* shireURL=shire.getShireURL(targeturl.c_str());
681 return WriteClientError(pfc,"Unable to map request to proper shireURL setting, check configuration.");
683 // If the user is accessing the SHIRE acceptance point, pass it on.
684 if (targeturl.find(shireURL)!=string::npos)
685 return SF_STATUS_REQ_NEXT_NOTIFICATION;
687 // Now check the policy for this request.
688 pair<bool,bool> requireSession=settings.first->getBool("requireSession");
689 pair<const char*,const char*> shib_cookie=shire.getCookieNameProps();
690 pair<bool,bool> httpRedirects=application->getPropertySet("Sessions")->getBool("httpRedirects");
691 pair<bool,const char*> redirectPage=application->getPropertySet("Sessions")->getString("redirectPage");
692 if (httpRedirects.first && !httpRedirects.second && !redirectPage.first)
693 return WriteClientError(pfc,"HTML-based redirection requires a redirectPage property.");
695 // Check for session cookie.
696 const char* session_id=NULL;
697 GetHeader(pn,pfc,"Cookie:",buf,128,false);
698 Category::getInstance("isapi_shib.HttpFilterProc").debug("cookie header is {%s}",(const char*)buf);
699 if (!buf.empty() && (session_id=strstr(buf,shib_cookie.first))) {
700 session_id+=strlen(shib_cookie.first) + 1; /* Skip over the '=' */
701 char* cookieend=strchr(session_id,';');
703 *cookieend = '\0'; /* Ignore anyting after a ; */
706 if (!session_id || !*session_id) {
707 // If no session required, bail now.
708 if (!requireSession.second)
709 return SF_STATUS_REQ_NEXT_NOTIFICATION;
711 // No acceptable cookie, and we require a session. Generate an AuthnRequest.
712 const char* areq = shire.getAuthnRequest(targeturl.c_str());
713 if (!httpRedirects.first || httpRedirects.second) {
714 string hdrs=string("Location: ") + areq + "\r\n"
715 "Content-Type: text/html\r\n"
716 "Content-Length: 40\r\n"
717 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
718 "Cache-Control: private,no-store,no-cache\r\n\r\n";
719 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"302 Please Wait",(DWORD)hdrs.c_str(),0);
720 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
722 pfc->WriteClient(pfc,(LPVOID)redmsg,&resplen,0);
723 return SF_STATUS_REQ_FINISHED;
726 ShibMLP markupProcessor;
727 markupProcessor.insert("requestURL",areq);
728 return WriteRedirectPage(pfc, application, redirectPage.second, markupProcessor);
732 // Make sure this session is still valid.
733 RPCError* status = NULL;
734 ShibMLP markupProcessor;
735 markupProcessor.insert("requestURL", targeturl);
738 GetServerVariable(pfc,"REMOTE_ADDR",abuf,16);
740 status = shire.sessionIsValid(session_id, abuf);
742 catch (ShibTargetException &e) {
743 markupProcessor.insert("errorType", "Session Processing Error");
744 markupProcessor.insert("errorText", e.what());
745 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
746 return WriteClientError(pfc, application, "shire", markupProcessor);
750 markupProcessor.insert("errorType", "Session Processing Error");
751 markupProcessor.insert("errorText", "Unexpected Exception");
752 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
753 return WriteClientError(pfc, application, "shire", markupProcessor);
758 if (status->isError()) {
759 if (!requireSession.second)
760 return SF_STATUS_REQ_NEXT_NOTIFICATION;
761 else if (status->isRetryable()) {
762 // Oops, session is invalid. Generate AuthnRequest.
764 const char* areq = shire.getAuthnRequest(targeturl.c_str());
765 if (!httpRedirects.first || httpRedirects.second) {
766 string hdrs=string("Location: ") + areq + "\r\n"
767 "Content-Type: text/html\r\n"
768 "Content-Length: 40\r\n"
769 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
770 "Cache-Control: private,no-store,no-cache\r\n\r\n";
771 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"302 Please Wait",(DWORD)hdrs.c_str(),0);
772 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
774 pfc->WriteClient(pfc,(LPVOID)redmsg,&resplen,0);
775 return SF_STATUS_REQ_FINISHED;
778 markupProcessor.insert("requestURL",areq);
779 return WriteRedirectPage(pfc, application, redirectPage.second, markupProcessor);
783 // return the error page to the user
784 markupProcessor.insert(*status);
786 return WriteClientError(pfc, application, "shire", markupProcessor);
793 vector<SAMLAssertion*> assertions;
794 SAMLAuthenticationStatement* sso_statement=NULL;
797 status = rm.getAssertions(session_id, abuf, assertions, &sso_statement);
799 catch (ShibTargetException &e) {
800 markupProcessor.insert("errorType", "Attribute Processing Error");
801 markupProcessor.insert("errorText", e.what());
802 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
803 return WriteClientError(pfc, application, "rm", markupProcessor);
807 markupProcessor.insert("errorType", "Attribute Processing Error");
808 markupProcessor.insert("errorText", "Unexpected Exception");
809 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
810 return WriteClientError(pfc, application, "rm", markupProcessor);
814 if (status->isError()) {
815 markupProcessor.insert(*status);
817 return WriteClientError(pfc, application, "rm", markupProcessor);
821 // Do we have an access control plugin?
822 if (settings.second) {
823 Locker acllock(settings.second);
824 if (!settings.second->authorized(*sso_statement,assertions)) {
825 for (int k = 0; k < assertions.size(); k++)
826 delete assertions[k];
827 delete sso_statement;
828 return WriteClientError(pfc, application, "access", markupProcessor);
832 // Get the AAP providers, which contain the attribute policy info.
833 Iterator<IAAP*> provs=application->getAAPProviders();
835 // Clear out the list of mapped attributes
836 while (provs.hasNext()) {
837 IAAP* aap=provs.next();
840 Iterator<const IAttributeRule*> rules=aap->getAttributeRules();
841 while (rules.hasNext()) {
842 const char* header=rules.next()->getHeader();
844 string hname=string(header) + ':';
845 pn->SetHeader(pfc,const_cast<char*>(hname.c_str()),"");
851 for (int k = 0; k < assertions.size(); k++)
852 delete assertions[k];
853 delete sso_statement;
854 markupProcessor.insert("errorType", "Attribute Processing Error");
855 markupProcessor.insert("errorText", "Unexpected Exception");
856 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
857 return WriteClientError(pfc, application, "rm", markupProcessor);
863 // Maybe export the first assertion.
864 pn->SetHeader(pfc,"remote-user:","");
865 pn->SetHeader(pfc,"Shib-Attributes:","");
866 pair<bool,bool> exp=settings.first->getBool("exportAssertion");
867 if (exp.first && exp.second && assertions.size()) {
869 RM::serialize(*(assertions[0]), assertion);
870 string::size_type lfeed;
871 while ((lfeed=assertion.find('\n'))!=string::npos)
872 assertion.erase(lfeed,1);
873 pn->SetHeader(pfc,"Shib-Attributes:",const_cast<char*>(assertion.c_str()));
876 pn->SetHeader(pfc,"Shib-Origin-Site:","");
877 pn->SetHeader(pfc,"Shib-Authentication-Method:","");
878 pn->SetHeader(pfc,"Shib-NameIdentifier-Format:","");
880 // Export the SAML AuthnMethod and the origin site name.
881 auto_ptr_char os(sso_statement->getSubject()->getNameIdentifier()->getNameQualifier());
882 auto_ptr_char am(sso_statement->getAuthMethod());
883 pn->SetHeader(pfc,"Shib-Origin-Site:", const_cast<char*>(os.get()));
884 pn->SetHeader(pfc,"Shib-Authentication-Method:", const_cast<char*>(am.get()));
887 AAP wrapper(provs,sso_statement->getSubject()->getNameIdentifier()->getFormat(),Constants::SHIB_ATTRIBUTE_NAMESPACE_URI);
888 if (!wrapper.fail() && wrapper->getHeader()) {
889 auto_ptr_char form(sso_statement->getSubject()->getNameIdentifier()->getFormat());
890 auto_ptr_char nameid(sso_statement->getSubject()->getNameIdentifier()->getName());
891 pn->SetHeader(pfc,"Shib-NameIdentifier-Format:",const_cast<char*>(form.get()));
892 if (!strcmp(wrapper->getHeader(),"REMOTE_USER")) {
893 char* principal=const_cast<char*>(nameid.get());
894 pn->SetHeader(pfc,"remote-user:",principal);
895 pfc->pFilterContext=pfc->AllocMem(pfc,strlen(principal)+1,0);
896 if (pfc->pFilterContext)
897 strcpy(static_cast<char*>(pfc->pFilterContext),principal);
900 string hname=string(wrapper->getHeader()) + ':';
901 pn->SetHeader(pfc,const_cast<char*>(wrapper->getHeader()),const_cast<char*>(nameid.get()));
905 pn->SetHeader(pfc,"Shib-Application-ID:","");
906 pn->SetHeader(pfc,"Shib-Application-ID:",const_cast<char*>(application_id.second));
908 // Export the attributes.
909 Iterator<SAMLAssertion*> a_iter(assertions);
910 while (a_iter.hasNext()) {
911 SAMLAssertion* assert=a_iter.next();
912 Iterator<SAMLStatement*> statements=assert->getStatements();
913 while (statements.hasNext()) {
914 SAMLAttributeStatement* astate=dynamic_cast<SAMLAttributeStatement*>(statements.next());
917 Iterator<SAMLAttribute*> attrs=astate->getAttributes();
918 while (attrs.hasNext()) {
919 SAMLAttribute* attr=attrs.next();
921 // Are we supposed to export it?
922 AAP wrapper(provs,attr->getName(),attr->getNamespace());
923 if (wrapper.fail() || !wrapper->getHeader())
926 Iterator<string> vals=attr->getSingleByteValues();
927 if (!strcmp(wrapper->getHeader(),"REMOTE_USER") && vals.hasNext()) {
928 char* principal=const_cast<char*>(vals.next().c_str());
929 pn->SetHeader(pfc,"remote-user:",principal);
930 pfc->pFilterContext=pfc->AllocMem(pfc,strlen(principal)+1,0);
931 if (pfc->pFilterContext)
932 strcpy(static_cast<char*>(pfc->pFilterContext),principal);
937 string hname=string(wrapper->getHeader()) + ':';
938 GetHeader(pn,pfc,const_cast<char*>(hname.c_str()),buf,256,false);
943 for (; vals.hasNext(); it++) {
944 string value = vals.next();
945 for (string::size_type pos = value.find_first_of(";", string::size_type(0));
947 pos = value.find_first_of(";", pos)) {
948 value.insert(pos, "\\");
954 header=header + ';' + value;
956 pn->SetHeader(pfc,const_cast<char*>(hname.c_str()),const_cast<char*>(header.c_str()));
963 for (int k = 0; k < assertions.size(); k++)
964 delete assertions[k];
965 delete sso_statement;
967 return SF_STATUS_REQ_NEXT_NOTIFICATION;
970 return WriteClientError(pfc,"Out of Memory");
973 if (e==ERROR_NO_DATA)
974 return WriteClientError(pfc,"A required variable or header was empty.");
976 return WriteClientError(pfc,"Server detected unexpected IIS error.");
980 return WriteClientError(pfc,"Server caught an unknown exception.");
984 return WriteClientError(pfc,"Server reached unreachable code, save my walrus!");
988 /****************************************************************************/
991 class ShibTargetIsapiE : public ShibTarget
994 ShibTargetIsapiE(LPEXTENSION_CONTROL_BLOCK lpECB, const site_t& site) :
998 GetServerVariable(lpECB,"HTTPS",ssl,5);
999 bool SSL=(ssl=="on" || ssl=="ON");
1001 // URL path always come from IIS.
1003 GetServerVariable(lpECB,"URL",url,255);
1005 // Port may come from IIS or from site def.
1007 if (site.m_port.empty() || !g_bNormalizeRequest)
1008 GetServerVariable(lpECB,"SERVER_PORT",port,10);
1010 strncpy(port,site.m_port.c_str(),10);
1011 static_cast<char*>(port)[10]=0;
1014 // Scheme may come from site def or be derived from IIS.
1015 const char* scheme=site.m_scheme.c_str();
1016 if (!scheme || !*scheme || !g_bNormalizeRequest) {
1017 scheme = SSL ? "https" : "http";
1020 // Get the remote address
1021 dynabuf remote_addr(16);
1022 GetServerVariable(lpECB, "REMOTE_ADDR", remote_addr, 16);
1024 init(g_Config, string(scheme), site.m_name, atoi(port),
1025 string(url), string(lpECB->lpszContentType ? lpECB->lpszContentType : ""),
1026 string(remote_addr), string(lpECB->lpszMethod)
1031 ~ShibTargetIsapiE() { }
1033 virtual void log(ShibLogLevel level, const string &msg) {
1034 LogEvent(NULL, (level == LogLevelDebug : EVENTLOG_DEBUG_TYPE ?
1035 (level == LogLevelInfo : EVENTLOG_INFORMATION_TYPE ?
1036 (level == LogLevelWarn : EVENTLOG_WARNING_TYPE ?
1037 EVENTLOG_ERROR_TYPE))),
1038 2100, NULL, msg.c_str());
1040 // Not used in the extension.
1041 //virtual string getCookies(void) { }
1042 virtual void setCookie(const string &name, const string &value) {
1043 // Set the cookie for later. Use it during the redirect.
1044 m_cookie += "Set-Cookie: " + name + "=" + value + "\r\n";
1046 virtual string getArgs(void) {
1047 return string(m_lpECB->lpszQueryString ? m_lpECB->lpszQueryString : "");
1049 virtual string getPostData(void) {
1050 if (m_lpECB->cbTotalBytes > 1024*1024) // 1MB?
1051 throw ShibTargetException(SHIBRPC_OK,
1052 "blocked too-large a post to SHIRE POST processor");
1053 else if (m_lpECB->cbTotalBytes != lpECB->cbAvailable) {
1056 DWORD datalen=m_lpECB->cbTotalBytes;
1059 BOOL ret = m_lpECB->ReadClient(m_lpECB->ConnID, buf, &buflen);
1060 if (!ret || !buflen)
1061 throw ShibTargetException(SHIBRPC_OK,
1062 "error reading POST data from browser");
1063 cgistr.append(buf, buflen);
1069 return string(reinterpret_cast<char*>(m_lpECB->lpbData),m_lpECB->cbAvailable);
1071 // Not used in the Extension
1072 //virtual void clearHeader(const string &name) { }
1073 //virtual void setHeader(const string &name, const string &value) { }
1074 //virtual string getHeader(const string &name) { }
1075 //virtual void setRemoteUser(const string &user) { }
1076 //virtual string getRemoteUser(void) { }
1077 virtual void* sendPage(const string &msg, const string content_type,
1078 const pair<string, string> headers[], int code) {
1079 string hdr = string ("Connection: close\r\nContent-type: ") + content_type + "\r\n";
1080 for (int k = 0; k < headers.size(); k++) {
1081 hdr += headers[k].first + ": " + headers[k].second + "\r\n";
1084 // XXX Need to handle "code"
1085 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER,
1086 "200 OK", (LPDWORD)hdr.c_str());
1087 DWORD resplen = msg.size();
1088 m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)msg.c_str(), &resplen, HSE_IO_SYNC);
1089 return (void*)HSE_STATUS_SUCCESS;
1091 virtual void* sendRedirect(const string url) {
1092 // XXX: Don't support the httpRedirect option, yet.
1093 string hdrs = m_cookie + "Location: " + url + "\r\n"
1094 "Content-Type: text/html\r\n"
1095 "Content-Length: 40\r\n"
1096 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
1097 "Cache-Control: private,no-store,no-cache\r\n\r\n";
1098 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER,
1099 "302 Moved", 0, (LPDWORD)hdrs.c_str());
1100 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
1102 m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)redmsg, &resplen, HSE_IO_SYNC);
1103 return (void*)HSE_STATUS_SUCCESS;
1105 // Decline happens in the POST processor if this isn't the shire url
1106 // Note that it can also happen with HTAccess, but we don't suppor that, yet.
1107 virtual void* returnDecline(void) {
1109 WriteClientError(m_lpECB, "UISAPA extension can only be unvoked to process incoming sessions."
1110 "Make sure the mapped file extension doesn't match actual content.");
1112 virtual void* returnOK(void) { return (void*) HSE_STATUS_SUCCESS; }
1114 LPEXTENSION_CONTROL_BLOCK m_lpECB;
1118 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* msg)
1120 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
1121 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
1122 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
1123 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Error</TITLE></HEAD><BODY><H1>Shibboleth Error</H1>";
1124 DWORD resplen=strlen(xmsg);
1125 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg,&resplen,HSE_IO_SYNC);
1126 resplen=strlen(msg);
1127 lpECB->WriteClient(lpECB->ConnID,(LPVOID)msg,&resplen,HSE_IO_SYNC);
1128 static const char* xmsg2="</BODY></HTML>";
1129 resplen=strlen(xmsg2);
1130 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg2,&resplen,HSE_IO_SYNC);
1131 return HSE_STATUS_SUCCESS;
1134 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
1137 const IApplication* application=NULL;
1140 ostringstream threadid;
1141 threadid << "[" << getpid() << "] shire_handler" << '\0';
1142 saml::NDC ndc(threadid.str().c_str());
1144 // Determine web site number. This can't really fail, I don't think.
1146 GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
1148 // Match site instance to host name, skip if no match.
1149 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
1150 if (map_i==g_Sites.end())
1151 return WriteClientError(lpECB, "Shibboleth Extension not configured for this web site.");
1153 ShibTargetIsapiE ste(lpECB, map_i->second);
1154 pair<bool,void*> res = ste.doHandlePOST();
1155 if (res.first) return (DWORD)res.second;
1157 return WriteClientError(lpECB, "Shibboleth Extension failed to process POST");
1160 return WriteClientError(lpECB,
1161 "Shibboleth Extension caught an unknown error. "
1165 // If we get here we've got an error.
1166 return HSE_STATUS_ERROR;
1170 IRequestMapper::Settings map_request(
1171 LPEXTENSION_CONTROL_BLOCK lpECB, IRequestMapper* mapper, const site_t& site, string& target
1175 GetServerVariable(lpECB,"HTTPS",ssl,5);
1176 bool SSL=(ssl=="on" || ssl=="ON");
1178 // URL path always come from IIS.
1180 GetServerVariable(lpECB,"URL",url,255);
1182 // Port may come from IIS or from site def.
1184 if (site.m_port.empty() || !g_bNormalizeRequest)
1185 GetServerVariable(lpECB,"SERVER_PORT",port,10);
1187 strncpy(port,site.m_port.c_str(),10);
1188 static_cast<char*>(port)[10]=0;
1191 // Scheme may come from site def or be derived from IIS.
1192 const char* scheme=site.m_scheme.c_str();
1193 if (!scheme || !*scheme || !g_bNormalizeRequest) {
1194 scheme = SSL ? "https" : "http";
1197 // Start with scheme and hostname.
1198 if (g_bNormalizeRequest) {
1199 target = string(scheme) + "://" + site.m_name;
1203 GetServerVariable(lpECB,"SERVER_NAME",name,64);
1204 target = string(scheme) + "://" + static_cast<char*>(name);
1207 // If port is non-default, append it.
1208 if ((!strcmp(scheme,"http") && port!="80") || (!strcmp(scheme,"https") && port!="443"))
1209 target = target + ':' + static_cast<char*>(port);
1213 target+=static_cast<char*>(url);
1215 return mapper->getSettingsFromParsedURL(scheme,site.m_name.c_str(),strtoul(port,NULL,10),url);
1218 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const IApplication* app, const char* page, ShibMLP& mlp)
1220 const IPropertySet* props=app->getPropertySet("Errors");
1222 pair<bool,const char*> p=props->getString(page);
1224 ifstream infile(p.second);
1225 if (!infile.fail()) {
1226 const char* res = mlp.run(infile,props);
1228 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
1229 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
1230 DWORD resplen=strlen(res);
1231 lpECB->WriteClient(lpECB->ConnID,(LPVOID)res,&resplen,0);
1232 return HSE_STATUS_SUCCESS;
1237 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Extension unable to open error template.");
1238 return WriteClientError(lpECB,"Unable to open error template, check settings.");
1241 DWORD WriteRedirectPage(LPEXTENSION_CONTROL_BLOCK lpECB, const IApplication* app, const char* file, ShibMLP& mlp, const char* headers=NULL)
1243 ifstream infile(file);
1244 if (!infile.fail()) {
1245 const char* res = mlp.run(infile,app->getPropertySet("Errors"));
1248 sprintf(buf,"Content-Length: %u\r\nContent-Type: text/html\r\n\r\n",strlen(res));
1252 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)h.c_str());
1255 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)buf);
1256 DWORD resplen=strlen(res);
1257 lpECB->WriteClient(lpECB->ConnID,(LPVOID)res,&resplen,0);
1258 return HSE_STATUS_SUCCESS;
1261 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Extension unable to open redirect template.");
1262 return WriteClientError(lpECB,"Unable to open redirect template, check settings.");
1265 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
1268 const IApplication* application=NULL;
1271 ostringstream threadid;
1272 threadid << "[" << getpid() << "] shire_handler" << '\0';
1273 saml::NDC ndc(threadid.str().c_str());
1275 // Determine web site number. This can't really fail, I don't think.
1277 GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
1279 // Match site instance to host name, skip if no match.
1280 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
1281 if (map_i==g_Sites.end())
1282 return WriteClientError(lpECB,"Shibboleth filter not configured for this web site.");
1284 // We lock the configuration system for the duration.
1285 IConfig* conf=g_Config->getINI();
1286 Locker locker(conf);
1288 // Map request to application and content settings.
1289 IRequestMapper* mapper=conf->getRequestMapper();
1290 Locker locker2(mapper);
1291 IRequestMapper::Settings settings=map_request(lpECB,mapper,map_i->second,targeturl);
1292 pair<bool,const char*> application_id=settings.first->getString("applicationId");
1293 application=conf->getApplication(application_id.second);
1294 const IPropertySet* sessionProps=application ? application->getPropertySet("Sessions") : NULL;
1295 if (!application || !sessionProps)
1296 return WriteClientError(lpECB,"Unable to map request to application session settings, check configuration.");
1298 SHIRE shire(application);
1300 const char* shireURL=shire.getShireURL(targeturl.c_str());
1302 return WriteClientError(lpECB,"Unable to map request to proper shireURL setting, check configuration.");
1304 // Make sure we only process the SHIRE requests.
1305 if (!strstr(targeturl.c_str(),shireURL))
1306 return WriteClientError(lpECB,"ISAPI extension can only be invoked to process incoming sessions."
1307 "Make sure the mapped file extension doesn't match actual content.");
1309 pair<const char*,const char*> shib_cookie=shire.getCookieNameProps();
1311 // Make sure this is SSL, if it should be
1312 pair<bool,bool> shireSSL=sessionProps->getBool("shireSSL");
1313 if (!shireSSL.first || shireSSL.second) {
1314 GetServerVariable(lpECB,"HTTPS",buf,10);
1316 throw ShibTargetException(SHIBRPC_OK,"blocked non-SSL access to SHIRE POST processor");
1319 pair<bool,bool> httpRedirects=sessionProps->getBool("httpRedirects");
1320 pair<bool,const char*> redirectPage=sessionProps->getString("redirectPage");
1321 if (httpRedirects.first && !httpRedirects.second && !redirectPage.first)
1322 return WriteClientError(lpECB,"HTML-based redirection requires a redirectPage property.");
1324 // Check for Mac web browser
1328 GetServerVariable(lpECB,"HTTP_USER_AGENT",agent,64);
1329 if (strstr(agent,"AppleWebKit/"))
1333 // If this is a GET, we manufacture an AuthnRequest.
1334 if (!stricmp(lpECB->lpszMethod,"GET")) {
1335 const char* areq=lpECB->lpszQueryString ? shire.getLazyAuthnRequest(lpECB->lpszQueryString) : NULL;
1337 throw ShibTargetException(SHIBRPC_OK, "malformed arguments to request a new session");
1338 if (!httpRedirects.first || httpRedirects.second) {
1339 string hdrs=string("Location: ") + areq + "\r\n"
1340 "Content-Type: text/html\r\n"
1341 "Content-Length: 40\r\n"
1342 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
1343 "Cache-Control: private,no-store,no-cache\r\n\r\n";
1344 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"302 Moved",0,(LPDWORD)hdrs.c_str());
1345 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
1347 lpECB->WriteClient(lpECB->ConnID,(LPVOID)redmsg,&resplen,HSE_IO_SYNC);
1348 return HSE_STATUS_SUCCESS;
1351 ShibMLP markupProcessor;
1352 markupProcessor.insert("requestURL",areq);
1353 return WriteRedirectPage(lpECB, application, redirectPage.second, markupProcessor);
1356 else if (stricmp(lpECB->lpszMethod,"POST"))
1357 throw ShibTargetException(SHIBRPC_OK,"blocked non-POST to SHIRE POST processor");
1359 // Sure sure this POST is an appropriate content type
1360 if (!lpECB->lpszContentType || stricmp(lpECB->lpszContentType,"application/x-www-form-urlencoded"))
1361 throw ShibTargetException(SHIBRPC_OK,"blocked bad content-type to SHIRE POST processor");
1364 pair<const char*,const char*> elements=pair<const char*,const char*>(NULL,NULL);
1365 if (lpECB->cbTotalBytes > 1024*1024) // 1MB?
1366 throw ShibTargetException(SHIBRPC_OK,"blocked too-large a post to SHIRE POST processor");
1367 else if (lpECB->cbTotalBytes!=lpECB->cbAvailable) {
1370 DWORD datalen=lpECB->cbTotalBytes;
1373 BOOL ret=lpECB->ReadClient(lpECB->ConnID,buf,&buflen);
1374 if (!ret || !buflen)
1375 throw ShibTargetException(SHIBRPC_OK,"error reading POST data from browser");
1376 cgistr.append(buf,buflen);
1379 elements=shire.getFormSubmission(cgistr.c_str(),cgistr.length());
1382 elements=shire.getFormSubmission(reinterpret_cast<char*>(lpECB->lpbData),lpECB->cbAvailable);
1384 // Make sure the SAML Response parameter exists
1385 if (!elements.first || !*elements.first)
1386 throw ShibTargetException(SHIBRPC_OK, "SHIRE POST failed to find SAMLResponse form element");
1388 // Make sure the target parameter exists
1389 if (!elements.second || !*elements.second)
1390 throw ShibTargetException(SHIBRPC_OK, "SHIRE POST failed to find TARGET form element");
1392 GetServerVariable(lpECB,"REMOTE_ADDR",buf,16);
1394 // Process the post.
1396 RPCError* status=NULL;
1397 ShibMLP markupProcessor;
1398 markupProcessor.insert("requestURL", targeturl.c_str());
1400 status = shire.sessionCreate(elements.first,buf,cookie);
1402 catch (ShibTargetException &e) {
1403 markupProcessor.insert("errorType", "Session Creation Service Error");
1404 markupProcessor.insert("errorText", e.what());
1405 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
1406 return WriteClientError(lpECB, application, "shire", markupProcessor);
1410 markupProcessor.insert("errorType", "Session Creation Service Error");
1411 markupProcessor.insert("errorText", "Unexpected Exception");
1412 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
1413 return WriteClientError(lpECB, application, "shire", markupProcessor);
1417 if (status->isError()) {
1418 if (status->isRetryable()) {
1420 const char* loc=shire.getAuthnRequest(elements.second);
1421 if (!httpRedirects.first || httpRedirects.second) {
1422 string hdrs=string("Location: ") + loc + "\r\n"
1423 "Content-Type: text/html\r\n"
1424 "Content-Length: 40\r\n"
1425 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
1426 "Cache-Control: private,no-store,no-cache\r\n\r\n";
1427 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"302 Moved",0,(LPDWORD)hdrs.c_str());
1428 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
1430 lpECB->WriteClient(lpECB->ConnID,(LPVOID)redmsg,&resplen,HSE_IO_SYNC);
1431 return HSE_STATUS_SUCCESS;
1434 markupProcessor.insert("requestURL",loc);
1435 return WriteRedirectPage(lpECB, application, redirectPage.second, markupProcessor);
1439 // Return this error to the user.
1440 markupProcessor.insert(*status);
1442 return WriteClientError(lpECB,application,"shire",markupProcessor);
1446 // We've got a good session, set the cookie and redirect to target.
1447 cookie = string("Set-Cookie: ") + shib_cookie.first + '=' + cookie + shib_cookie.second + "\r\n"
1448 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
1449 "Cache-Control: private,no-store,no-cache\r\n";
1450 if (!httpRedirects.first || httpRedirects.second) {
1451 cookie=cookie + "Content-Type: text/html\r\nLocation: " + elements.second + "\r\nContent-Length: 40\r\n\r\n";
1452 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"302 Moved",0,(LPDWORD)cookie.c_str());
1453 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
1455 lpECB->WriteClient(lpECB->ConnID,(LPVOID)redmsg,&resplen,HSE_IO_SYNC);
1456 return HSE_STATUS_SUCCESS;
1459 markupProcessor.insert("requestURL",elements.second);
1460 return WriteRedirectPage(lpECB, application, redirectPage.second, markupProcessor, cookie.c_str());
1463 catch (ShibTargetException &e) {
1465 ShibMLP markupProcessor;
1466 markupProcessor.insert("requestURL", targeturl.c_str());
1467 markupProcessor.insert("errorType", "Session Creation Service Error");
1468 markupProcessor.insert("errorText", e.what());
1469 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
1470 return WriteClientError(lpECB,application,"shire",markupProcessor);
1476 ShibMLP markupProcessor;
1477 markupProcessor.insert("requestURL", targeturl.c_str());
1478 markupProcessor.insert("errorType", "Session Creation Service Error");
1479 markupProcessor.insert("errorText", "Unexpected Exception");
1480 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
1481 return WriteClientError(lpECB,application,"shire",markupProcessor);
1486 return HSE_STATUS_ERROR;