2 * The Shibboleth License, Version 1.
4 * University Corporation for Advanced Internet Development, Inc.
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions are met:
11 * Redistributions of source code must retain the above copyright notice, this
12 * list of conditions and the following disclaimer.
14 * Redistributions in binary form must reproduce the above copyright notice,
15 * this list of conditions and the following disclaimer in the documentation
16 * and/or other materials provided with the distribution, if any, must include
17 * the following acknowledgment: "This product includes software developed by
18 * the University Corporation for Advanced Internet Development
19 * <http://www.ucaid.edu>Internet2 Project. Alternately, this acknowledegement
20 * may appear in the software itself, if and wherever such third-party
21 * acknowledgments normally appear.
23 * Neither the name of Shibboleth nor the names of its contributors, nor
24 * Internet2, nor the University Corporation for Advanced Internet Development,
25 * Inc., nor UCAID may be used to endorse or promote products derived from this
26 * software without specific prior written permission. For written permission,
27 * please contact shibboleth@shibboleth.org
29 * Products derived from this software may not be called Shibboleth, Internet2,
30 * UCAID, or the University Corporation for Advanced Internet Development, nor
31 * may Shibboleth appear in their name, without prior written permission of the
32 * University Corporation for Advanced Internet Development.
35 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
36 * AND WITH ALL FAULTS. ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
37 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
38 * PARTICULAR PURPOSE, AND NON-INFRINGEMENT ARE DISCLAIMED AND THE ENTIRE RISK
39 * OF SATISFACTORY QUALITY, PERFORMANCE, ACCURACY, AND EFFORT IS WITH LICENSEE.
40 * IN NO EVENT SHALL THE COPYRIGHT OWNER, CONTRIBUTORS OR THE UNIVERSITY
41 * CORPORATION FOR ADVANCED INTERNET DEVELOPMENT, INC. BE LIABLE FOR ANY DIRECT,
42 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
43 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
44 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
45 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
46 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
47 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
50 /* isapi_shib.cpp - Shibboleth ISAPI filter
56 #include "config_win32.h"
59 #include <saml/saml.h>
60 #include <shib/shib.h>
61 #include <shib/shib-threads.h>
62 #include <shib-target/shib-target.h>
64 #include <log4cpp/Category.hh>
75 using namespace log4cpp;
77 using namespace shibboleth;
78 using namespace shibtarget;
82 static const XMLCh name[] = { chLatin_n, chLatin_a, chLatin_m, chLatin_e, chNull };
83 static const XMLCh port[] = { chLatin_p, chLatin_o, chLatin_r, chLatin_t, chNull };
84 static const XMLCh sslport[] = { chLatin_s, chLatin_s, chLatin_l, chLatin_p, chLatin_o, chLatin_r, chLatin_t, chNull };
85 static const XMLCh scheme[] = { chLatin_s, chLatin_c, chLatin_h, chLatin_e, chLatin_m, chLatin_e, chNull };
86 static const XMLCh id[] = { chLatin_i, chLatin_d, chNull };
87 static const XMLCh Implementation[] =
88 { chLatin_I, chLatin_m, chLatin_p, chLatin_l, chLatin_e, chLatin_m, chLatin_e, chLatin_n, chLatin_t, chLatin_a, chLatin_t, chLatin_i, chLatin_o, chLatin_n, chNull };
89 static const XMLCh ISAPI[] = { chLatin_I, chLatin_S, chLatin_A, chLatin_P, chLatin_I, chNull };
90 static const XMLCh Alias[] = { chLatin_A, chLatin_l, chLatin_i, chLatin_a, chLatin_s, chNull };
91 static const XMLCh normalizeRequest[] =
92 { chLatin_n, chLatin_o, chLatin_r, chLatin_m, chLatin_a, chLatin_l, chLatin_i, chLatin_z, chLatin_e,
93 chLatin_R, chLatin_e, chLatin_q, chLatin_u, chLatin_e, chLatin_s, chLatin_t, chNull
95 static const XMLCh Site[] = { chLatin_S, chLatin_i, chLatin_t, chLatin_e, chNull };
98 site_t(const DOMElement* e)
100 auto_ptr_char n(e->getAttributeNS(NULL,name));
101 auto_ptr_char s(e->getAttributeNS(NULL,scheme));
102 auto_ptr_char p(e->getAttributeNS(NULL,port));
103 auto_ptr_char p2(e->getAttributeNS(NULL,sslport));
104 if (n.get()) m_name=n.get();
105 if (s.get()) m_scheme=s.get();
106 if (p.get()) m_port=p.get();
107 if (p2.get()) m_sslport=p2.get();
108 DOMNodeList* nlist=e->getElementsByTagNameNS(ShibTargetConfig::SHIBTARGET_NS,Alias);
109 for (int i=0; nlist && i<nlist->getLength(); i++) {
110 if (nlist->item(i)->hasChildNodes()) {
111 auto_ptr_char alias(nlist->item(i)->getFirstChild()->getNodeValue());
112 m_aliases.insert(alias.get());
116 string m_scheme,m_port,m_sslport,m_name;
117 set<string> m_aliases;
120 HINSTANCE g_hinstDLL;
121 ShibTargetConfig* g_Config = NULL;
122 map<string,site_t> g_Sites;
123 bool g_bNormalizeRequest = true;
127 LPCSTR lpUNCServerName,
133 LPCSTR messages[] = {message, NULL};
135 HANDLE hElog = RegisterEventSource(lpUNCServerName, "Shibboleth ISAPI Filter");
136 BOOL res = ReportEvent(hElog, wType, 0, dwEventID, lpUserSid, 1, 0, messages, NULL);
137 return (DeregisterEventSource(hElog) && res);
140 extern "C" __declspec(dllexport) BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID)
142 if (fdwReason==DLL_PROCESS_ATTACH)
147 extern "C" BOOL WINAPI GetExtensionVersion(HSE_VERSION_INFO* pVer)
154 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
155 "Extension mode startup not possible, is the DLL loaded as a filter?");
159 pVer->dwExtensionVersion=HSE_VERSION;
160 strncpy(pVer->lpszExtensionDesc,"Shibboleth ISAPI Extension",HSE_MAX_EXT_DLL_NAME_LEN-1);
164 extern "C" BOOL WINAPI TerminateExtension(DWORD)
166 return TRUE; // cleanup should happen when filter unloads
169 extern "C" BOOL WINAPI GetFilterVersion(PHTTP_FILTER_VERSION pVer)
174 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
175 "Reentrant filter initialization, ignoring...");
183 LPCSTR schemadir=getenv("SHIBSCHEMAS");
185 schemadir=SHIB_SCHEMAS;
186 LPCSTR config=getenv("SHIBCONFIG");
189 g_Config=&ShibTargetConfig::getConfig();
190 g_Config->setFeatures(
191 ShibTargetConfig::Listener |
192 ShibTargetConfig::Metadata |
193 ShibTargetConfig::AAP |
194 ShibTargetConfig::RequestMapper |
195 ShibTargetConfig::LocalExtensions |
196 ShibTargetConfig::Logging
198 if (!g_Config->init(schemadir,config)) {
200 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
201 "Filter startup failed during initialization, check shire log for help.");
205 // Access the implementation-specifics for site mappings.
206 IConfig* conf=g_Config->getINI();
208 const IPropertySet* props=conf->getPropertySet("Local");
210 const DOMElement* impl=saml::XML::getFirstChildElement(
211 props->getElement(),ShibTargetConfig::SHIBTARGET_NS,Implementation
213 if (impl && (impl=saml::XML::getFirstChildElement(impl,ShibTargetConfig::SHIBTARGET_NS,ISAPI))) {
214 const XMLCh* flag=impl->getAttributeNS(NULL,normalizeRequest);
215 g_bNormalizeRequest=(!flag || !*flag || *flag==chDigit_1 || *flag==chLatin_t);
216 impl=saml::XML::getFirstChildElement(impl,ShibTargetConfig::SHIBTARGET_NS,Site);
218 auto_ptr_char id(impl->getAttributeNS(NULL,id));
220 g_Sites.insert(pair<string,site_t>(id.get(),site_t(impl)));
221 impl=saml::XML::getNextSiblingElement(impl,ShibTargetConfig::SHIBTARGET_NS,Site);
229 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Filter startup failed with an exception.");
234 pVer->dwFilterVersion=HTTP_FILTER_REVISION;
235 strncpy(pVer->lpszFilterDesc,"Shibboleth ISAPI Filter",SF_MAX_FILTER_DESC_LEN);
236 pVer->dwFlags=(SF_NOTIFY_ORDER_HIGH |
237 SF_NOTIFY_SECURE_PORT |
238 SF_NOTIFY_NONSECURE_PORT |
239 SF_NOTIFY_PREPROC_HEADERS |
241 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter initialized...");
245 extern "C" BOOL WINAPI TerminateFilter(DWORD)
248 g_Config->shutdown();
250 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter shut down...");
254 /* Next up, some suck-free versions of various APIs.
256 You DON'T require people to guess the buffer size and THEN tell them the right size.
257 Returning an LPCSTR is apparently way beyond their ken. Not to mention the fact that
258 constant strings aren't typed as such, making it just that much harder. These versions
259 are now updated to use a special growable buffer object, modeled after the standard
260 string class. The standard string won't work because they left out the option to
261 pre-allocate a non-constant buffer.
267 dynabuf() { bufptr=NULL; buflen=0; }
268 dynabuf(size_t s) { bufptr=new char[buflen=s]; *bufptr=0; }
269 ~dynabuf() { delete[] bufptr; }
270 size_t length() const { return bufptr ? strlen(bufptr) : 0; }
271 size_t size() const { return buflen; }
272 bool empty() const { return length()==0; }
273 void reserve(size_t s, bool keep=false);
274 void erase() { if (bufptr) memset(bufptr,0,buflen); }
275 operator char*() { return bufptr; }
276 bool operator ==(const char* s) const;
277 bool operator !=(const char* s) const { return !(*this==s); }
283 void dynabuf::reserve(size_t s, bool keep)
290 p[buflen]=bufptr[buflen];
296 bool dynabuf::operator==(const char* s) const
298 if (buflen==NULL || s==NULL)
299 return (buflen==NULL && s==NULL);
301 return strcmp(bufptr,s)==0;
304 void GetServerVariable(PHTTP_FILTER_CONTEXT pfc, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
305 throw (bad_alloc, DWORD)
311 while (!pfc->GetServerVariable(pfc,lpszVariable,s,&size))
313 // Grumble. Check the error.
314 DWORD e=GetLastError();
315 if (e==ERROR_INSUFFICIENT_BUFFER)
320 if (bRequired && s.empty())
324 void GetServerVariable(LPEXTENSION_CONTROL_BLOCK lpECB, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
325 throw (bad_alloc, DWORD)
331 while (lpECB->GetServerVariable(lpECB->ConnID,lpszVariable,s,&size))
333 // Grumble. Check the error.
334 DWORD e=GetLastError();
335 if (e==ERROR_INSUFFICIENT_BUFFER)
340 if (bRequired && s.empty())
344 void GetHeader(PHTTP_FILTER_PREPROC_HEADERS pn, PHTTP_FILTER_CONTEXT pfc,
345 LPSTR lpszName, dynabuf& s, DWORD size=80, bool bRequired=true)
346 throw (bad_alloc, DWORD)
352 while (!pn->GetHeader(pfc,lpszName,s,&size))
354 // Grumble. Check the error.
355 DWORD e=GetLastError();
356 if (e==ERROR_INSUFFICIENT_BUFFER)
361 if (bRequired && s.empty())
365 /****************************************************************************/
368 class ShibTargetIsapiF : public ShibTarget
371 ShibTargetIsapiF(PHTTP_FILTER_CONTEXT pfc, PHTTP_FILTER_PREPROC_HEADERS pn, const site_t& site) {
373 // URL path always come from IIS.
375 GetHeader(pn,pfc,"url",url,256,false);
377 // Port may come from IIS or from site def.
379 if (!g_bNormalizeRequest || (pfc->fIsSecurePort && site.m_sslport.empty()) || (!pfc->fIsSecurePort && site.m_port.empty()))
380 GetServerVariable(pfc,"SERVER_PORT",port,10);
381 else if (pfc->fIsSecurePort) {
382 strncpy(port,site.m_sslport.c_str(),10);
383 static_cast<char*>(port)[10]=0;
386 strncpy(port,site.m_port.c_str(),10);
387 static_cast<char*>(port)[10]=0;
390 // Scheme may come from site def or be derived from IIS.
391 const char* scheme=site.m_scheme.c_str();
392 if (!scheme || !*scheme || !g_bNormalizeRequest)
393 scheme=pfc->fIsSecurePort ? "https" : "http";
395 // Get the rest of the server variables.
396 dynabuf remote_addr(16),method(5),content_type(32),hostname(32);
397 GetServerVariable(pfc,"SERVER_NAME",hostname,32);
398 GetServerVariable(pfc,"REMOTE_ADDR",remote_addr,16);
399 // The last two appear to be unavailable to this filter hook, but we don't need them.
400 GetServerVariable(pfc,"REQUEST_METHOD",method,5,false);
401 GetServerVariable(pfc,"CONTENT_TYPE",content_type,32,false);
403 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
404 const char* host=hostname;
405 if (site.m_name!=host && site.m_aliases.find(host)==site.m_aliases.end())
406 host=site.m_name.c_str();
408 init(g_Config, scheme, host, atoi(port), url, content_type, remote_addr, method);
413 ~ShibTargetIsapiF() { }
415 virtual void log(ShibLogLevel level, const string &msg) {
416 LogEvent(NULL, (level == LogLevelDebug ? EVENTLOG_INFORMATION_TYPE :
417 (level == LogLevelInfo ? EVENTLOG_INFORMATION_TYPE :
418 (level == LogLevelWarn ? EVENTLOG_WARNING_TYPE : EVENTLOG_ERROR_TYPE))),
419 2100, NULL, msg.c_str());
421 virtual string getCookies(void) {
423 GetHeader(m_pn, m_pfc, "Cookie:", buf, 128, false);
424 return buf.empty() ? "" : buf;
427 virtual void clearHeader(const string &name) {
428 string hdr = name + ":";
429 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), "");
431 virtual void setHeader(const string &name, const string &value) {
432 string hdr = name + ":";
433 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()),
434 const_cast<char*>(value.c_str()));
436 virtual string getHeader(const string &name) {
437 string hdr = name + ":";
439 GetHeader(m_pn, m_pfc, const_cast<char*>(hdr.c_str()), buf, 1024, false);
442 virtual void setRemoteUser(const string &user) {
443 setHeader(string("remote-user"), user);
445 virtual string getRemoteUser(void) {
446 return getHeader(string("remote-user"));
448 virtual void* sendPage(const string& msg, const string& content_type,
449 const Iterator<header_t>& headers=EMPTY(header_t), int code=200) {
450 string hdr = string ("Connection: close\r\nContent-type: ") + content_type + "\r\n";
451 while (headers.hasNext()) {
452 const header_t& h=headers.next();
453 hdr += h.first + ": " + h.second + "\r\n";
456 // XXX Need to handle "code"
457 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, "200 OK", (DWORD)hdr.c_str(), 0);
458 DWORD resplen = msg.size();
459 m_pfc->WriteClient(m_pfc, (LPVOID)msg.c_str(), &resplen, 0);
460 return (void*)SF_STATUS_REQ_FINISHED;
462 virtual void* sendRedirect(const string& url) {
463 // XXX: Don't support the httpRedirect option, yet.
464 string hdrs=m_cookie + string("Location: ") + url + "\r\n"
465 "Content-Type: text/html\r\n"
466 "Content-Length: 40\r\n"
467 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
468 "Cache-Control: private,no-store,no-cache\r\n\r\n";
469 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER,
470 "302 Please Wait", (DWORD)hdrs.c_str(), 0);
471 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
473 m_pfc->WriteClient(m_pfc, (LPVOID)redmsg, &resplen, 0);
474 return reinterpret_cast<void*>(SF_STATUS_REQ_FINISHED);
476 // XXX: We might not ever hit the 'decline' status in this filter.
477 //virtual void* returnDecline(void) { }
478 virtual void* returnOK(void) { return (void*) SF_STATUS_REQ_NEXT_NOTIFICATION; }
480 // The filter never processes the POST, so stub these methods.
481 virtual void setCookie(const string &name, const string &value) {
482 // Set the cookie for later. Use it during the redirect.
483 m_cookie += "Set-Cookie: " + name + "=" + value + "\r\n";
485 virtual string getArgs(void) { throw runtime_error("getArgs not implemented"); }
486 virtual string getPostData(void) { throw runtime_error("getPostData not implemented"); }
488 PHTTP_FILTER_CONTEXT m_pfc;
489 PHTTP_FILTER_PREPROC_HEADERS m_pn;
493 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const char* msg)
495 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
496 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
497 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)ctype,0);
498 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Filter Error</TITLE></HEAD><BODY>"
499 "<H1>Shibboleth Filter Error</H1>";
500 DWORD resplen=strlen(xmsg);
501 pfc->WriteClient(pfc,(LPVOID)xmsg,&resplen,0);
503 pfc->WriteClient(pfc,(LPVOID)msg,&resplen,0);
504 static const char* xmsg2="</BODY></HTML>";
505 resplen=strlen(xmsg2);
506 pfc->WriteClient(pfc,(LPVOID)xmsg2,&resplen,0);
507 return SF_STATUS_REQ_FINISHED;
510 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
512 // Is this a log notification?
513 if (notificationType==SF_NOTIFY_LOG)
515 if (pfc->pFilterContext)
516 ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=static_cast<LPCSTR>(pfc->pFilterContext);
517 return SF_STATUS_REQ_NEXT_NOTIFICATION;
520 PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
523 // Determine web site number. This can't really fail, I don't think.
525 GetServerVariable(pfc,"INSTANCE_ID",buf,10);
527 // Match site instance to host name, skip if no match.
528 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
529 if (map_i==g_Sites.end())
530 return SF_STATUS_REQ_NEXT_NOTIFICATION;
532 ostringstream threadid;
533 threadid << "[" << getpid() << "] isapi_shib" << '\0';
534 saml::NDC ndc(threadid.str().c_str());
536 ShibTargetIsapiF stf(pfc, pn, map_i->second);
538 // "false" because we don't override the Shib settings
539 pair<bool,void*> res = stf.doCheckAuthN();
540 if (res.first) return (DWORD)res.second;
542 // "false" because we don't override the Shib settings
543 res = stf.doExportAssertions();
544 if (res.first) return (DWORD)res.second;
546 res = stf.doCheckAuthZ();
547 if (res.first) return (DWORD)res.second;
549 return SF_STATUS_REQ_NEXT_NOTIFICATION;
552 return WriteClientError(pfc,"Out of Memory");
555 if (e==ERROR_NO_DATA)
556 return WriteClientError(pfc,"A required variable or header was empty.");
558 return WriteClientError(pfc,"Server detected unexpected IIS error.");
562 return WriteClientError(pfc,"Server caught an unknown exception.");
566 return WriteClientError(pfc,"Server reached unreachable code, save my walrus!");
571 IRequestMapper::Settings map_request(
572 PHTTP_FILTER_CONTEXT pfc, PHTTP_FILTER_PREPROC_HEADERS pn, IRequestMapper* mapper, const site_t& site, string& target
575 // URL path always come from IIS.
577 GetHeader(pn,pfc,"url",url,256,false);
579 // Port may come from IIS or from site def.
581 if (site.m_port.empty() || !g_bNormalizeRequest)
582 GetServerVariable(pfc,"SERVER_PORT",port,10);
584 strncpy(port,site.m_port.c_str(),10);
585 static_cast<char*>(port)[10]=0;
588 // Scheme may come from site def or be derived from IIS.
589 const char* scheme=site.m_scheme.c_str();
590 if (!scheme || !*scheme || !g_bNormalizeRequest)
591 scheme=pfc->fIsSecurePort ? "https" : "http";
593 // Start with scheme and hostname.
594 if (g_bNormalizeRequest) {
595 target = string(scheme) + "://" + site.m_name;
599 GetServerVariable(pfc,"SERVER_NAME",name,64);
600 target = string(scheme) + "://" + static_cast<char*>(name);
603 // If port is non-default, append it.
604 if ((!strcmp(scheme,"http") && port!="80") || (!strcmp(scheme,"https") && port!="443"))
605 target = target + ':' + static_cast<char*>(port);
609 target+=static_cast<char*>(url);
611 return mapper->getSettingsFromParsedURL(scheme,site.m_name.c_str(),strtoul(port,NULL,10),url);
614 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const IApplication* app, const char* page, ShibMLP& mlp)
616 const IPropertySet* props=app->getPropertySet("Errors");
618 pair<bool,const char*> p=props->getString(page);
620 ifstream infile(p.second);
621 if (!infile.fail()) {
622 const char* res = mlp.run(infile,props);
624 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
625 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)ctype,0);
626 DWORD resplen=strlen(res);
627 pfc->WriteClient(pfc,(LPVOID)res,&resplen,0);
628 return SF_STATUS_REQ_FINISHED;
634 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Filter unable to open error template.");
635 return WriteClientError(pfc,"Unable to open error template, check settings.");
638 DWORD WriteRedirectPage(PHTTP_FILTER_CONTEXT pfc, const IApplication* app, const char* file, ShibMLP& mlp, const char* headers=NULL)
640 ifstream infile(file);
641 if (!infile.fail()) {
642 const char* res = mlp.run(infile,app->getPropertySet("Errors"));
645 sprintf(buf,"Content-Length: %u\r\nContent-Type: text/html\r\n\r\n",strlen(res));
649 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)h.c_str(),0);
652 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)buf,0);
653 DWORD resplen=strlen(res);
654 pfc->WriteClient(pfc,(LPVOID)res,&resplen,0);
655 return SF_STATUS_REQ_FINISHED;
658 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Extension unable to open redirect template.");
659 return WriteClientError(pfc,"Unable to open redirect template, check settings.");
662 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
664 // Is this a log notification?
665 if (notificationType==SF_NOTIFY_LOG)
667 if (pfc->pFilterContext)
668 ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=static_cast<LPCSTR>(pfc->pFilterContext);
669 return SF_STATUS_REQ_NEXT_NOTIFICATION;
672 PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
675 // Determine web site number. This can't really fail, I don't think.
677 GetServerVariable(pfc,"INSTANCE_ID",buf,10);
679 // Match site instance to host name, skip if no match.
680 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
681 if (map_i==g_Sites.end())
682 return SF_STATUS_REQ_NEXT_NOTIFICATION;
684 ostringstream threadid;
685 threadid << "[" << getpid() << "] isapi_shib" << '\0';
686 saml::NDC ndc(threadid.str().c_str());
688 // We lock the configuration system for the duration.
689 IConfig* conf=g_Config->getINI();
692 // Map request to application and content settings.
694 IRequestMapper* mapper=conf->getRequestMapper();
695 Locker locker2(mapper);
696 IRequestMapper::Settings settings=map_request(pfc,pn,mapper,map_i->second,targeturl);
697 pair<bool,const char*> application_id=settings.first->getString("applicationId");
698 const IApplication* application=conf->getApplication(application_id.second);
700 return WriteClientError(pfc,"Unable to map request to application settings, check configuration.");
702 // Declare SHIRE object for this request.
703 SHIRE shire(application);
705 const char* shireURL=shire.getShireURL(targeturl.c_str());
707 return WriteClientError(pfc,"Unable to map request to proper shireURL setting, check configuration.");
709 // If the user is accessing the SHIRE acceptance point, pass it on.
710 if (targeturl.find(shireURL)!=string::npos)
711 return SF_STATUS_REQ_NEXT_NOTIFICATION;
713 // Now check the policy for this request.
714 pair<bool,bool> requireSession=settings.first->getBool("requireSession");
715 pair<const char*,const char*> shib_cookie=shire.getCookieNameProps();
716 pair<bool,bool> httpRedirects=application->getPropertySet("Sessions")->getBool("httpRedirects");
717 pair<bool,const char*> redirectPage=application->getPropertySet("Sessions")->getString("redirectPage");
718 if (httpRedirects.first && !httpRedirects.second && !redirectPage.first)
719 return WriteClientError(pfc,"HTML-based redirection requires a redirectPage property.");
721 // Check for session cookie.
722 const char* session_id=NULL;
723 GetHeader(pn,pfc,"Cookie:",buf,128,false);
724 Category::getInstance("isapi_shib.HttpFilterProc").debug("cookie header is {%s}",(const char*)buf);
725 if (!buf.empty() && (session_id=strstr(buf,shib_cookie.first))) {
726 session_id+=strlen(shib_cookie.first) + 1; /* Skip over the '=' */
727 char* cookieend=strchr(session_id,';');
729 *cookieend = '\0'; /* Ignore anyting after a ; */
732 if (!session_id || !*session_id) {
733 // If no session required, bail now.
734 if (!requireSession.second)
735 return SF_STATUS_REQ_NEXT_NOTIFICATION;
737 // No acceptable cookie, and we require a session. Generate an AuthnRequest.
738 const char* areq = shire.getAuthnRequest(targeturl.c_str());
739 if (!httpRedirects.first || httpRedirects.second) {
740 string hdrs=string("Location: ") + areq + "\r\n"
741 "Content-Type: text/html\r\n"
742 "Content-Length: 40\r\n"
743 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
744 "Cache-Control: private,no-store,no-cache\r\n\r\n";
745 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"302 Please Wait",(DWORD)hdrs.c_str(),0);
746 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
748 pfc->WriteClient(pfc,(LPVOID)redmsg,&resplen,0);
749 return SF_STATUS_REQ_FINISHED;
752 ShibMLP markupProcessor;
753 markupProcessor.insert("requestURL",areq);
754 return WriteRedirectPage(pfc, application, redirectPage.second, markupProcessor);
758 // Make sure this session is still valid.
759 RPCError* status = NULL;
760 ShibMLP markupProcessor;
761 markupProcessor.insert("requestURL", targeturl);
764 GetServerVariable(pfc,"REMOTE_ADDR",abuf,16);
766 status = shire.sessionIsValid(session_id, abuf);
768 catch (ShibTargetException &e) {
769 markupProcessor.insert("errorType", "Session Processing Error");
770 markupProcessor.insert("errorText", e.what());
771 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
772 return WriteClientError(pfc, application, "shire", markupProcessor);
776 markupProcessor.insert("errorType", "Session Processing Error");
777 markupProcessor.insert("errorText", "Unexpected Exception");
778 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
779 return WriteClientError(pfc, application, "shire", markupProcessor);
784 if (status->isError()) {
785 if (!requireSession.second)
786 return SF_STATUS_REQ_NEXT_NOTIFICATION;
787 else if (status->isRetryable()) {
788 // Oops, session is invalid. Generate AuthnRequest.
790 const char* areq = shire.getAuthnRequest(targeturl.c_str());
791 if (!httpRedirects.first || httpRedirects.second) {
792 string hdrs=string("Location: ") + areq + "\r\n"
793 "Content-Type: text/html\r\n"
794 "Content-Length: 40\r\n"
795 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
796 "Cache-Control: private,no-store,no-cache\r\n\r\n";
797 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"302 Please Wait",(DWORD)hdrs.c_str(),0);
798 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
800 pfc->WriteClient(pfc,(LPVOID)redmsg,&resplen,0);
801 return SF_STATUS_REQ_FINISHED;
804 markupProcessor.insert("requestURL",areq);
805 return WriteRedirectPage(pfc, application, redirectPage.second, markupProcessor);
809 // return the error page to the user
810 markupProcessor.insert(*status);
812 return WriteClientError(pfc, application, "shire", markupProcessor);
819 vector<SAMLAssertion*> assertions;
820 SAMLAuthenticationStatement* sso_statement=NULL;
823 status = rm.getAssertions(session_id, abuf, assertions, &sso_statement);
825 catch (ShibTargetException &e) {
826 markupProcessor.insert("errorType", "Attribute Processing Error");
827 markupProcessor.insert("errorText", e.what());
828 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
829 return WriteClientError(pfc, application, "rm", markupProcessor);
833 markupProcessor.insert("errorType", "Attribute Processing Error");
834 markupProcessor.insert("errorText", "Unexpected Exception");
835 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
836 return WriteClientError(pfc, application, "rm", markupProcessor);
840 if (status->isError()) {
841 markupProcessor.insert(*status);
843 return WriteClientError(pfc, application, "rm", markupProcessor);
847 // Do we have an access control plugin?
848 if (settings.second) {
849 Locker acllock(settings.second);
850 if (!settings.second->authorized(*sso_statement,assertions)) {
851 for (int k = 0; k < assertions.size(); k++)
852 delete assertions[k];
853 delete sso_statement;
854 return WriteClientError(pfc, application, "access", markupProcessor);
858 // Get the AAP providers, which contain the attribute policy info.
859 Iterator<IAAP*> provs=application->getAAPProviders();
861 // Clear out the list of mapped attributes
862 while (provs.hasNext()) {
863 IAAP* aap=provs.next();
866 Iterator<const IAttributeRule*> rules=aap->getAttributeRules();
867 while (rules.hasNext()) {
868 const char* header=rules.next()->getHeader();
870 string hname=string(header) + ':';
871 pn->SetHeader(pfc,const_cast<char*>(hname.c_str()),"");
877 for (int k = 0; k < assertions.size(); k++)
878 delete assertions[k];
879 delete sso_statement;
880 markupProcessor.insert("errorType", "Attribute Processing Error");
881 markupProcessor.insert("errorText", "Unexpected Exception");
882 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
883 return WriteClientError(pfc, application, "rm", markupProcessor);
889 // Maybe export the first assertion.
890 pn->SetHeader(pfc,"remote-user:","");
891 pn->SetHeader(pfc,"Shib-Attributes:","");
892 pair<bool,bool> exp=settings.first->getBool("exportAssertion");
893 if (exp.first && exp.second && assertions.size()) {
895 RM::serialize(*(assertions[0]), assertion);
896 string::size_type lfeed;
897 while ((lfeed=assertion.find('\n'))!=string::npos)
898 assertion.erase(lfeed,1);
899 pn->SetHeader(pfc,"Shib-Attributes:",const_cast<char*>(assertion.c_str()));
902 pn->SetHeader(pfc,"Shib-Origin-Site:","");
903 pn->SetHeader(pfc,"Shib-Authentication-Method:","");
904 pn->SetHeader(pfc,"Shib-NameIdentifier-Format:","");
906 // Export the SAML AuthnMethod and the origin site name.
907 auto_ptr_char os(sso_statement->getSubject()->getNameIdentifier()->getNameQualifier());
908 auto_ptr_char am(sso_statement->getAuthMethod());
909 pn->SetHeader(pfc,"Shib-Origin-Site:", const_cast<char*>(os.get()));
910 pn->SetHeader(pfc,"Shib-Authentication-Method:", const_cast<char*>(am.get()));
913 AAP wrapper(provs,sso_statement->getSubject()->getNameIdentifier()->getFormat(),Constants::SHIB_ATTRIBUTE_NAMESPACE_URI);
914 if (!wrapper.fail() && wrapper->getHeader()) {
915 auto_ptr_char form(sso_statement->getSubject()->getNameIdentifier()->getFormat());
916 auto_ptr_char nameid(sso_statement->getSubject()->getNameIdentifier()->getName());
917 pn->SetHeader(pfc,"Shib-NameIdentifier-Format:",const_cast<char*>(form.get()));
918 if (!strcmp(wrapper->getHeader(),"REMOTE_USER")) {
919 char* principal=const_cast<char*>(nameid.get());
920 pn->SetHeader(pfc,"remote-user:",principal);
921 pfc->pFilterContext=pfc->AllocMem(pfc,strlen(principal)+1,0);
922 if (pfc->pFilterContext)
923 strcpy(static_cast<char*>(pfc->pFilterContext),principal);
926 string hname=string(wrapper->getHeader()) + ':';
927 pn->SetHeader(pfc,const_cast<char*>(wrapper->getHeader()),const_cast<char*>(nameid.get()));
931 pn->SetHeader(pfc,"Shib-Application-ID:","");
932 pn->SetHeader(pfc,"Shib-Application-ID:",const_cast<char*>(application_id.second));
934 // Export the attributes.
935 Iterator<SAMLAssertion*> a_iter(assertions);
936 while (a_iter.hasNext()) {
937 SAMLAssertion* assert=a_iter.next();
938 Iterator<SAMLStatement*> statements=assert->getStatements();
939 while (statements.hasNext()) {
940 SAMLAttributeStatement* astate=dynamic_cast<SAMLAttributeStatement*>(statements.next());
943 Iterator<SAMLAttribute*> attrs=astate->getAttributes();
944 while (attrs.hasNext()) {
945 SAMLAttribute* attr=attrs.next();
947 // Are we supposed to export it?
948 AAP wrapper(provs,attr->getName(),attr->getNamespace());
949 if (wrapper.fail() || !wrapper->getHeader())
952 Iterator<string> vals=attr->getSingleByteValues();
953 if (!strcmp(wrapper->getHeader(),"REMOTE_USER") && vals.hasNext()) {
954 char* principal=const_cast<char*>(vals.next().c_str());
955 pn->SetHeader(pfc,"remote-user:",principal);
956 pfc->pFilterContext=pfc->AllocMem(pfc,strlen(principal)+1,0);
957 if (pfc->pFilterContext)
958 strcpy(static_cast<char*>(pfc->pFilterContext),principal);
963 string hname=string(wrapper->getHeader()) + ':';
964 GetHeader(pn,pfc,const_cast<char*>(hname.c_str()),buf,256,false);
969 for (; vals.hasNext(); it++) {
970 string value = vals.next();
971 for (string::size_type pos = value.find_first_of(";", string::size_type(0));
973 pos = value.find_first_of(";", pos)) {
974 value.insert(pos, "\\");
980 header=header + ';' + value;
982 pn->SetHeader(pfc,const_cast<char*>(hname.c_str()),const_cast<char*>(header.c_str()));
989 for (int k = 0; k < assertions.size(); k++)
990 delete assertions[k];
991 delete sso_statement;
993 return SF_STATUS_REQ_NEXT_NOTIFICATION;
996 return WriteClientError(pfc,"Out of Memory");
999 if (e==ERROR_NO_DATA)
1000 return WriteClientError(pfc,"A required variable or header was empty.");
1002 return WriteClientError(pfc,"Server detected unexpected IIS error.");
1006 return WriteClientError(pfc,"Server caught an unknown exception.");
1010 return WriteClientError(pfc,"Server reached unreachable code, save my walrus!");
1014 /****************************************************************************/
1017 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* msg)
1019 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
1020 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
1021 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
1022 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Error</TITLE></HEAD><BODY><H1>Shibboleth Error</H1>";
1023 DWORD resplen=strlen(xmsg);
1024 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg,&resplen,HSE_IO_SYNC);
1025 resplen=strlen(msg);
1026 lpECB->WriteClient(lpECB->ConnID,(LPVOID)msg,&resplen,HSE_IO_SYNC);
1027 static const char* xmsg2="</BODY></HTML>";
1028 resplen=strlen(xmsg2);
1029 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg2,&resplen,HSE_IO_SYNC);
1030 return HSE_STATUS_SUCCESS;
1034 class ShibTargetIsapiE : public ShibTarget
1037 ShibTargetIsapiE(LPEXTENSION_CONTROL_BLOCK lpECB, const site_t& site)
1040 GetServerVariable(lpECB,"HTTPS",ssl,5);
1041 bool SSL=(ssl=="on" || ssl=="ON");
1043 // URL path always come from IIS.
1045 GetServerVariable(lpECB,"URL",url,255);
1047 // Port may come from IIS or from site def.
1049 if (!g_bNormalizeRequest || (SSL && site.m_sslport.empty()) || (!SSL && site.m_port.empty()))
1050 GetServerVariable(lpECB,"SERVER_PORT",port,10);
1052 strncpy(port,site.m_sslport.c_str(),10);
1053 static_cast<char*>(port)[10]=0;
1056 strncpy(port,site.m_port.c_str(),10);
1057 static_cast<char*>(port)[10]=0;
1060 // Scheme may come from site def or be derived from IIS.
1061 const char* scheme=site.m_scheme.c_str();
1062 if (!scheme || !*scheme || !g_bNormalizeRequest) {
1063 scheme = SSL ? "https" : "http";
1066 // Get the other server variables.
1067 dynabuf remote_addr(16),hostname(32);
1068 GetServerVariable(lpECB, "REMOTE_ADDR", remote_addr, 16);
1069 GetServerVariable(lpECB, "SERVER_NAME", hostname, 32);
1071 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
1072 const char* host=hostname;
1073 if (site.m_name!=host && site.m_aliases.find(host)==site.m_aliases.end())
1074 host=site.m_name.c_str();
1076 init(g_Config, scheme, host, atoi(port), url, lpECB->lpszContentType, remote_addr, lpECB->lpszMethod);
1080 ~ShibTargetIsapiE() { }
1082 virtual void log(ShibLogLevel level, const string &msg) {
1083 LogEvent(NULL, (level == LogLevelDebug ? EVENTLOG_INFORMATION_TYPE :
1084 (level == LogLevelInfo ? EVENTLOG_INFORMATION_TYPE :
1085 (level == LogLevelWarn ? EVENTLOG_WARNING_TYPE : EVENTLOG_ERROR_TYPE))),
1086 2100, NULL, msg.c_str());
1088 virtual void setCookie(const string &name, const string &value) {
1089 // Set the cookie for later. Use it during the redirect.
1090 m_cookie += "Set-Cookie: " + name + "=" + value + "\r\n";
1092 virtual string getArgs(void) {
1093 return string(m_lpECB->lpszQueryString ? m_lpECB->lpszQueryString : "");
1095 virtual string getPostData(void) {
1096 if (m_lpECB->cbTotalBytes > 1024*1024) // 1MB?
1097 throw FatalProfileException("Blocked too-large a submission to profile endpoint.");
1098 else if (m_lpECB->cbTotalBytes != m_lpECB->cbAvailable) {
1101 DWORD datalen=m_lpECB->cbTotalBytes;
1104 BOOL ret = m_lpECB->ReadClient(m_lpECB->ConnID, buf, &buflen);
1105 if (!ret || !buflen)
1106 throw FatalProfileException("Error reading profile submission from browser.");
1107 cgistr.append(buf, buflen);
1113 return string(reinterpret_cast<char*>(m_lpECB->lpbData),m_lpECB->cbAvailable);
1115 virtual void* sendPage(const string &msg, const string& content_type,
1116 const Iterator<header_t>& headers=EMPTY(header_t), int code=200) {
1117 string hdr = string ("Connection: close\r\nContent-type: ") + content_type + "\r\n";
1118 for (int k = 0; k < headers.size(); k++) {
1119 hdr += headers[k].first + ": " + headers[k].second + "\r\n";
1122 // XXX Need to handle "code"
1123 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER,
1124 "200 OK", 0, (LPDWORD)hdr.c_str());
1125 DWORD resplen = msg.size();
1126 m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)msg.c_str(), &resplen, HSE_IO_SYNC);
1127 return (void*)HSE_STATUS_SUCCESS;
1129 virtual void* sendRedirect(const string& url) {
1130 // XXX: Don't support the httpRedirect option, yet.
1131 string hdrs = m_cookie + "Location: " + url + "\r\n"
1132 "Content-Type: text/html\r\n"
1133 "Content-Length: 40\r\n"
1134 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
1135 "Cache-Control: private,no-store,no-cache\r\n\r\n";
1136 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER,
1137 "302 Moved", 0, (LPDWORD)hdrs.c_str());
1138 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
1140 m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)redmsg, &resplen, HSE_IO_SYNC);
1141 return (void*)HSE_STATUS_SUCCESS;
1143 // Decline happens in the POST processor if this isn't the shire url
1144 // Note that it can also happen with HTAccess, but we don't support that, yet.
1145 virtual void* returnDecline(void) {
1147 WriteClientError(m_lpECB, "ISAPA extension can only be invoked to process incoming sessions."
1148 "Make sure the mapped file extension doesn't match actual content.");
1150 virtual void* returnOK(void) { return (void*) HSE_STATUS_SUCCESS; }
1152 virtual string getCookies(void) {
1154 GetServerVariable(m_lpECB, "HTTP_COOKIE", buf, 128, false);
1155 return buf.empty() ? "" : buf;
1158 // Not used in the extension.
1159 virtual void clearHeader(const string &name) { throw runtime_error("clearHeader not implemented"); }
1160 virtual void setHeader(const string &name, const string &value) { throw runtime_error("setHeader not implemented"); }
1161 virtual string getHeader(const string &name) { throw runtime_error("getHeader not implemented"); }
1162 virtual void setRemoteUser(const string &user) { throw runtime_error("setRemoteUser not implemented"); }
1163 virtual string getRemoteUser(void) { throw runtime_error("getRemoteUser not implemented"); }
1165 LPEXTENSION_CONTROL_BLOCK m_lpECB;
1169 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
1172 const IApplication* application=NULL;
1174 ostringstream threadid;
1175 threadid << "[" << getpid() << "] shire_handler" << '\0';
1176 saml::NDC ndc(threadid.str().c_str());
1178 // Determine web site number. This can't really fail, I don't think.
1180 GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
1182 // Match site instance to host name, skip if no match.
1183 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
1184 if (map_i==g_Sites.end())
1185 return WriteClientError(lpECB, "Shibboleth Extension not configured for this web site.");
1187 ShibTargetIsapiE ste(lpECB, map_i->second);
1188 pair<bool,void*> res = ste.doHandleProfile();
1189 if (res.first) return (DWORD)res.second;
1191 return WriteClientError(lpECB, "Shibboleth Extension failed to process request");
1195 return WriteClientError(lpECB,"Out of Memory");
1198 if (e==ERROR_NO_DATA)
1199 return WriteClientError(lpECB,"A required variable or header was empty.");
1201 return WriteClientError(lpECB,"Server detected unexpected IIS error.");
1205 return WriteClientError(lpECB,"Server caught an unknown exception.");
1209 // If we get here we've got an error.
1210 return HSE_STATUS_ERROR;
1214 IRequestMapper::Settings map_request(
1215 LPEXTENSION_CONTROL_BLOCK lpECB, IRequestMapper* mapper, const site_t& site, string& target
1219 GetServerVariable(lpECB,"HTTPS",ssl,5);
1220 bool SSL=(ssl=="on" || ssl=="ON");
1222 // URL path always come from IIS.
1224 GetServerVariable(lpECB,"URL",url,255);
1226 // Port may come from IIS or from site def.
1228 if (site.m_port.empty() || !g_bNormalizeRequest)
1229 GetServerVariable(lpECB,"SERVER_PORT",port,10);
1231 strncpy(port,site.m_port.c_str(),10);
1232 static_cast<char*>(port)[10]=0;
1235 // Scheme may come from site def or be derived from IIS.
1236 const char* scheme=site.m_scheme.c_str();
1237 if (!scheme || !*scheme || !g_bNormalizeRequest) {
1238 scheme = SSL ? "https" : "http";
1241 // Start with scheme and hostname.
1242 if (g_bNormalizeRequest) {
1243 target = string(scheme) + "://" + site.m_name;
1247 GetServerVariable(lpECB,"SERVER_NAME",name,64);
1248 target = string(scheme) + "://" + static_cast<char*>(name);
1251 // If port is non-default, append it.
1252 if ((!strcmp(scheme,"http") && port!="80") || (!strcmp(scheme,"https") && port!="443"))
1253 target = target + ':' + static_cast<char*>(port);
1257 target+=static_cast<char*>(url);
1259 return mapper->getSettingsFromParsedURL(scheme,site.m_name.c_str(),strtoul(port,NULL,10),url);
1262 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const IApplication* app, const char* page, ShibMLP& mlp)
1264 const IPropertySet* props=app->getPropertySet("Errors");
1266 pair<bool,const char*> p=props->getString(page);
1268 ifstream infile(p.second);
1269 if (!infile.fail()) {
1270 const char* res = mlp.run(infile,props);
1272 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
1273 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
1274 DWORD resplen=strlen(res);
1275 lpECB->WriteClient(lpECB->ConnID,(LPVOID)res,&resplen,0);
1276 return HSE_STATUS_SUCCESS;
1281 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Extension unable to open error template.");
1282 return WriteClientError(lpECB,"Unable to open error template, check settings.");
1285 DWORD WriteRedirectPage(LPEXTENSION_CONTROL_BLOCK lpECB, const IApplication* app, const char* file, ShibMLP& mlp, const char* headers=NULL)
1287 ifstream infile(file);
1288 if (!infile.fail()) {
1289 const char* res = mlp.run(infile,app->getPropertySet("Errors"));
1292 sprintf(buf,"Content-Length: %u\r\nContent-Type: text/html\r\n\r\n",strlen(res));
1296 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)h.c_str());
1299 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)buf);
1300 DWORD resplen=strlen(res);
1301 lpECB->WriteClient(lpECB->ConnID,(LPVOID)res,&resplen,0);
1302 return HSE_STATUS_SUCCESS;
1305 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Extension unable to open redirect template.");
1306 return WriteClientError(lpECB,"Unable to open redirect template, check settings.");
1309 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
1312 const IApplication* application=NULL;
1315 ostringstream threadid;
1316 threadid << "[" << getpid() << "] shire_handler" << '\0';
1317 saml::NDC ndc(threadid.str().c_str());
1319 // Determine web site number. This can't really fail, I don't think.
1321 GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
1323 // Match site instance to host name, skip if no match.
1324 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
1325 if (map_i==g_Sites.end())
1326 return WriteClientError(lpECB,"Shibboleth filter not configured for this web site.");
1328 // We lock the configuration system for the duration.
1329 IConfig* conf=g_Config->getINI();
1330 Locker locker(conf);
1332 // Map request to application and content settings.
1333 IRequestMapper* mapper=conf->getRequestMapper();
1334 Locker locker2(mapper);
1335 IRequestMapper::Settings settings=map_request(lpECB,mapper,map_i->second,targeturl);
1336 pair<bool,const char*> application_id=settings.first->getString("applicationId");
1337 application=conf->getApplication(application_id.second);
1338 const IPropertySet* sessionProps=application ? application->getPropertySet("Sessions") : NULL;
1339 if (!application || !sessionProps)
1340 return WriteClientError(lpECB,"Unable to map request to application session settings, check configuration.");
1342 SHIRE shire(application);
1344 const char* shireURL=shire.getShireURL(targeturl.c_str());
1346 return WriteClientError(lpECB,"Unable to map request to proper shireURL setting, check configuration.");
1348 // Make sure we only process the SHIRE requests.
1349 if (!strstr(targeturl.c_str(),shireURL))
1350 return WriteClientError(lpECB,"ISAPI extension can only be invoked to process incoming sessions."
1351 "Make sure the mapped file extension doesn't match actual content.");
1353 pair<const char*,const char*> shib_cookie=shire.getCookieNameProps();
1355 // Make sure this is SSL, if it should be
1356 pair<bool,bool> shireSSL=sessionProps->getBool("shireSSL");
1357 if (!shireSSL.first || shireSSL.second) {
1358 GetServerVariable(lpECB,"HTTPS",buf,10);
1360 throw ShibTargetException(SHIBRPC_OK,"blocked non-SSL access to SHIRE POST processor");
1363 pair<bool,bool> httpRedirects=sessionProps->getBool("httpRedirects");
1364 pair<bool,const char*> redirectPage=sessionProps->getString("redirectPage");
1365 if (httpRedirects.first && !httpRedirects.second && !redirectPage.first)
1366 return WriteClientError(lpECB,"HTML-based redirection requires a redirectPage property.");
1368 // Check for Mac web browser
1372 GetServerVariable(lpECB,"HTTP_USER_AGENT",agent,64);
1373 if (strstr(agent,"AppleWebKit/"))
1377 // If this is a GET, we manufacture an AuthnRequest.
1378 if (!stricmp(lpECB->lpszMethod,"GET")) {
1379 const char* areq=lpECB->lpszQueryString ? shire.getLazyAuthnRequest(lpECB->lpszQueryString) : NULL;
1381 throw ShibTargetException(SHIBRPC_OK, "malformed arguments to request a new session");
1382 if (!httpRedirects.first || httpRedirects.second) {
1383 string hdrs=string("Location: ") + areq + "\r\n"
1384 "Content-Type: text/html\r\n"
1385 "Content-Length: 40\r\n"
1386 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
1387 "Cache-Control: private,no-store,no-cache\r\n\r\n";
1388 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"302 Moved",0,(LPDWORD)hdrs.c_str());
1389 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
1391 lpECB->WriteClient(lpECB->ConnID,(LPVOID)redmsg,&resplen,HSE_IO_SYNC);
1392 return HSE_STATUS_SUCCESS;
1395 ShibMLP markupProcessor;
1396 markupProcessor.insert("requestURL",areq);
1397 return WriteRedirectPage(lpECB, application, redirectPage.second, markupProcessor);
1400 else if (stricmp(lpECB->lpszMethod,"POST"))
1401 throw ShibTargetException(SHIBRPC_OK,"blocked non-POST to SHIRE POST processor");
1403 // Sure sure this POST is an appropriate content type
1404 if (!lpECB->lpszContentType || stricmp(lpECB->lpszContentType,"application/x-www-form-urlencoded"))
1405 throw ShibTargetException(SHIBRPC_OK,"blocked bad content-type to SHIRE POST processor");
1408 pair<const char*,const char*> elements=pair<const char*,const char*>(NULL,NULL);
1409 if (lpECB->cbTotalBytes > 1024*1024) // 1MB?
1410 throw ShibTargetException(SHIBRPC_OK,"blocked too-large a post to SHIRE POST processor");
1411 else if (lpECB->cbTotalBytes!=lpECB->cbAvailable) {
1414 DWORD datalen=lpECB->cbTotalBytes;
1417 BOOL ret=lpECB->ReadClient(lpECB->ConnID,buf,&buflen);
1418 if (!ret || !buflen)
1419 throw ShibTargetException(SHIBRPC_OK,"error reading POST data from browser");
1420 cgistr.append(buf,buflen);
1423 elements=shire.getFormSubmission(cgistr.c_str(),cgistr.length());
1426 elements=shire.getFormSubmission(reinterpret_cast<char*>(lpECB->lpbData),lpECB->cbAvailable);
1428 // Make sure the SAML Response parameter exists
1429 if (!elements.first || !*elements.first)
1430 throw ShibTargetException(SHIBRPC_OK, "SHIRE POST failed to find SAMLResponse form element");
1432 // Make sure the target parameter exists
1433 if (!elements.second || !*elements.second)
1434 throw ShibTargetException(SHIBRPC_OK, "SHIRE POST failed to find TARGET form element");
1436 GetServerVariable(lpECB,"REMOTE_ADDR",buf,16);
1438 // Process the post.
1440 RPCError* status=NULL;
1441 ShibMLP markupProcessor;
1442 markupProcessor.insert("requestURL", targeturl.c_str());
1444 status = shire.sessionCreate(elements.first,buf,cookie);
1446 catch (ShibTargetException &e) {
1447 markupProcessor.insert("errorType", "Session Creation Service Error");
1448 markupProcessor.insert("errorText", e.what());
1449 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
1450 return WriteClientError(lpECB, application, "shire", markupProcessor);
1454 markupProcessor.insert("errorType", "Session Creation Service Error");
1455 markupProcessor.insert("errorText", "Unexpected Exception");
1456 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
1457 return WriteClientError(lpECB, application, "shire", markupProcessor);
1461 if (status->isError()) {
1462 if (status->isRetryable()) {
1464 const char* loc=shire.getAuthnRequest(elements.second);
1465 if (!httpRedirects.first || httpRedirects.second) {
1466 string hdrs=string("Location: ") + loc + "\r\n"
1467 "Content-Type: text/html\r\n"
1468 "Content-Length: 40\r\n"
1469 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
1470 "Cache-Control: private,no-store,no-cache\r\n\r\n";
1471 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"302 Moved",0,(LPDWORD)hdrs.c_str());
1472 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
1474 lpECB->WriteClient(lpECB->ConnID,(LPVOID)redmsg,&resplen,HSE_IO_SYNC);
1475 return HSE_STATUS_SUCCESS;
1478 markupProcessor.insert("requestURL",loc);
1479 return WriteRedirectPage(lpECB, application, redirectPage.second, markupProcessor);
1483 // Return this error to the user.
1484 markupProcessor.insert(*status);
1486 return WriteClientError(lpECB,application,"shire",markupProcessor);
1490 // We've got a good session, set the cookie and redirect to target.
1491 cookie = string("Set-Cookie: ") + shib_cookie.first + '=' + cookie + shib_cookie.second + "\r\n"
1492 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
1493 "Cache-Control: private,no-store,no-cache\r\n";
1494 if (!httpRedirects.first || httpRedirects.second) {
1495 cookie=cookie + "Content-Type: text/html\r\nLocation: " + elements.second + "\r\nContent-Length: 40\r\n\r\n";
1496 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"302 Moved",0,(LPDWORD)cookie.c_str());
1497 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
1499 lpECB->WriteClient(lpECB->ConnID,(LPVOID)redmsg,&resplen,HSE_IO_SYNC);
1500 return HSE_STATUS_SUCCESS;
1503 markupProcessor.insert("requestURL",elements.second);
1504 return WriteRedirectPage(lpECB, application, redirectPage.second, markupProcessor, cookie.c_str());
1507 catch (ShibTargetException &e) {
1509 ShibMLP markupProcessor;
1510 markupProcessor.insert("requestURL", targeturl.c_str());
1511 markupProcessor.insert("errorType", "Session Creation Service Error");
1512 markupProcessor.insert("errorText", e.what());
1513 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
1514 return WriteClientError(lpECB,application,"shire",markupProcessor);
1520 ShibMLP markupProcessor;
1521 markupProcessor.insert("requestURL", targeturl.c_str());
1522 markupProcessor.insert("errorType", "Session Creation Service Error");
1523 markupProcessor.insert("errorText", "Unexpected Exception");
1524 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
1525 return WriteClientError(lpECB,application,"shire",markupProcessor);
1530 return HSE_STATUS_ERROR;