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>
74 using namespace shibboleth;
75 using namespace shibtarget;
79 static const XMLCh name[] = { chLatin_n, chLatin_a, chLatin_m, chLatin_e, chNull };
80 static const XMLCh port[] = { chLatin_p, chLatin_o, chLatin_r, chLatin_t, chNull };
81 static const XMLCh sslport[] = { chLatin_s, chLatin_s, chLatin_l, chLatin_p, chLatin_o, chLatin_r, chLatin_t, chNull };
82 static const XMLCh scheme[] = { chLatin_s, chLatin_c, chLatin_h, chLatin_e, chLatin_m, chLatin_e, chNull };
83 static const XMLCh id[] = { chLatin_i, chLatin_d, chNull };
84 static const XMLCh Implementation[] =
85 { 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 };
86 static const XMLCh ISAPI[] = { chLatin_I, chLatin_S, chLatin_A, chLatin_P, chLatin_I, chNull };
87 static const XMLCh Alias[] = { chLatin_A, chLatin_l, chLatin_i, chLatin_a, chLatin_s, chNull };
88 static const XMLCh normalizeRequest[] =
89 { chLatin_n, chLatin_o, chLatin_r, chLatin_m, chLatin_a, chLatin_l, chLatin_i, chLatin_z, chLatin_e,
90 chLatin_R, chLatin_e, chLatin_q, chLatin_u, chLatin_e, chLatin_s, chLatin_t, chNull
92 static const XMLCh Site[] = { chLatin_S, chLatin_i, chLatin_t, chLatin_e, chNull };
95 site_t(const DOMElement* e)
97 auto_ptr_char n(e->getAttributeNS(NULL,name));
98 auto_ptr_char s(e->getAttributeNS(NULL,scheme));
99 auto_ptr_char p(e->getAttributeNS(NULL,port));
100 auto_ptr_char p2(e->getAttributeNS(NULL,sslport));
101 if (n.get()) m_name=n.get();
102 if (s.get()) m_scheme=s.get();
103 if (p.get()) m_port=p.get();
104 if (p2.get()) m_sslport=p2.get();
105 DOMNodeList* nlist=e->getElementsByTagNameNS(shibtarget::XML::SHIBTARGET_NS,Alias);
106 for (int i=0; nlist && i<nlist->getLength(); i++) {
107 if (nlist->item(i)->hasChildNodes()) {
108 auto_ptr_char alias(nlist->item(i)->getFirstChild()->getNodeValue());
109 m_aliases.insert(alias.get());
113 string m_scheme,m_port,m_sslport,m_name;
114 set<string> m_aliases;
117 HINSTANCE g_hinstDLL;
118 ShibTargetConfig* g_Config = NULL;
119 map<string,site_t> g_Sites;
120 bool g_bNormalizeRequest = true;
124 LPCSTR lpUNCServerName,
130 LPCSTR messages[] = {message, NULL};
132 HANDLE hElog = RegisterEventSource(lpUNCServerName, "Shibboleth ISAPI Filter");
133 BOOL res = ReportEvent(hElog, wType, 0, dwEventID, lpUserSid, 1, 0, messages, NULL);
134 return (DeregisterEventSource(hElog) && res);
137 extern "C" __declspec(dllexport) BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID)
139 if (fdwReason==DLL_PROCESS_ATTACH)
144 extern "C" BOOL WINAPI GetExtensionVersion(HSE_VERSION_INFO* pVer)
151 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
152 "Extension mode startup not possible, is the DLL loaded as a filter?");
156 pVer->dwExtensionVersion=HSE_VERSION;
157 strncpy(pVer->lpszExtensionDesc,"Shibboleth ISAPI Extension",HSE_MAX_EXT_DLL_NAME_LEN-1);
161 extern "C" BOOL WINAPI TerminateExtension(DWORD)
163 return TRUE; // cleanup should happen when filter unloads
166 extern "C" BOOL WINAPI GetFilterVersion(PHTTP_FILTER_VERSION pVer)
171 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
172 "Reentrant filter initialization, ignoring...");
180 LPCSTR schemadir=getenv("SHIBSCHEMAS");
182 schemadir=SHIB_SCHEMAS;
183 LPCSTR config=getenv("SHIBCONFIG");
186 g_Config=&ShibTargetConfig::getConfig();
187 g_Config->setFeatures(
188 ShibTargetConfig::Listener |
189 ShibTargetConfig::Metadata |
190 ShibTargetConfig::AAP |
191 ShibTargetConfig::RequestMapper |
192 ShibTargetConfig::LocalExtensions |
193 ShibTargetConfig::Logging
195 if (!g_Config->init(schemadir) || !g_Config->load(config)) {
197 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
198 "Filter startup failed during initialization, check shire log for help.");
202 // Access the implementation-specifics for site mappings.
203 IConfig* conf=g_Config->getINI();
205 const IPropertySet* props=conf->getPropertySet("Local");
207 const DOMElement* impl=saml::XML::getFirstChildElement(
208 props->getElement(),shibtarget::XML::SHIBTARGET_NS,Implementation
210 if (impl && (impl=saml::XML::getFirstChildElement(impl,shibtarget::XML::SHIBTARGET_NS,ISAPI))) {
211 const XMLCh* flag=impl->getAttributeNS(NULL,normalizeRequest);
212 g_bNormalizeRequest=(!flag || !*flag || *flag==chDigit_1 || *flag==chLatin_t);
213 impl=saml::XML::getFirstChildElement(impl,shibtarget::XML::SHIBTARGET_NS,Site);
215 auto_ptr_char id(impl->getAttributeNS(NULL,id));
217 g_Sites.insert(pair<string,site_t>(id.get(),site_t(impl)));
218 impl=saml::XML::getNextSiblingElement(impl,shibtarget::XML::SHIBTARGET_NS,Site);
226 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Filter startup failed with an exception.");
231 pVer->dwFilterVersion=HTTP_FILTER_REVISION;
232 strncpy(pVer->lpszFilterDesc,"Shibboleth ISAPI Filter",SF_MAX_FILTER_DESC_LEN);
233 pVer->dwFlags=(SF_NOTIFY_ORDER_HIGH |
234 SF_NOTIFY_SECURE_PORT |
235 SF_NOTIFY_NONSECURE_PORT |
236 SF_NOTIFY_PREPROC_HEADERS |
238 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter initialized...");
242 extern "C" BOOL WINAPI TerminateFilter(DWORD)
245 g_Config->shutdown();
247 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter shut down...");
251 /* Next up, some suck-free versions of various APIs.
253 You DON'T require people to guess the buffer size and THEN tell them the right size.
254 Returning an LPCSTR is apparently way beyond their ken. Not to mention the fact that
255 constant strings aren't typed as such, making it just that much harder. These versions
256 are now updated to use a special growable buffer object, modeled after the standard
257 string class. The standard string won't work because they left out the option to
258 pre-allocate a non-constant buffer.
264 dynabuf() { bufptr=NULL; buflen=0; }
265 dynabuf(size_t s) { bufptr=new char[buflen=s]; *bufptr=0; }
266 ~dynabuf() { delete[] bufptr; }
267 size_t length() const { return bufptr ? strlen(bufptr) : 0; }
268 size_t size() const { return buflen; }
269 bool empty() const { return length()==0; }
270 void reserve(size_t s, bool keep=false);
271 void erase() { if (bufptr) memset(bufptr,0,buflen); }
272 operator char*() { return bufptr; }
273 bool operator ==(const char* s) const;
274 bool operator !=(const char* s) const { return !(*this==s); }
280 void dynabuf::reserve(size_t s, bool keep)
287 p[buflen]=bufptr[buflen];
293 bool dynabuf::operator==(const char* s) const
295 if (buflen==NULL || s==NULL)
296 return (buflen==NULL && s==NULL);
298 return strcmp(bufptr,s)==0;
301 void GetServerVariable(PHTTP_FILTER_CONTEXT pfc, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
302 throw (bad_alloc, DWORD)
308 while (!pfc->GetServerVariable(pfc,lpszVariable,s,&size))
310 // Grumble. Check the error.
311 DWORD e=GetLastError();
312 if (e==ERROR_INSUFFICIENT_BUFFER)
317 if (bRequired && s.empty())
321 void GetServerVariable(LPEXTENSION_CONTROL_BLOCK lpECB, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
322 throw (bad_alloc, DWORD)
328 while (!lpECB->GetServerVariable(lpECB->ConnID,lpszVariable,s,&size))
330 // Grumble. Check the error.
331 DWORD e=GetLastError();
332 if (e==ERROR_INSUFFICIENT_BUFFER)
337 if (bRequired && s.empty())
341 void GetHeader(PHTTP_FILTER_PREPROC_HEADERS pn, PHTTP_FILTER_CONTEXT pfc,
342 LPSTR lpszName, dynabuf& s, DWORD size=80, bool bRequired=true)
343 throw (bad_alloc, DWORD)
349 while (!pn->GetHeader(pfc,lpszName,s,&size))
351 // Grumble. Check the error.
352 DWORD e=GetLastError();
353 if (e==ERROR_INSUFFICIENT_BUFFER)
358 if (bRequired && s.empty())
362 /****************************************************************************/
365 class ShibTargetIsapiF : public ShibTarget
367 PHTTP_FILTER_CONTEXT m_pfc;
368 PHTTP_FILTER_PREPROC_HEADERS m_pn;
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 GetServerVariable(pfc,"REQUEST_METHOD",method,5,false);
400 GetServerVariable(pfc,"CONTENT_TYPE",content_type,32,false);
402 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
403 const char* host=hostname;
404 if (site.m_name!=host && site.m_aliases.find(host)==site.m_aliases.end())
405 host=site.m_name.c_str();
407 init(scheme, host, atoi(port), url, content_type, remote_addr, method);
412 ~ShibTargetIsapiF() { }
414 virtual void log(ShibLogLevel level, const string &msg) {
415 ShibTarget::log(level,msg);
416 if (level == LogLevelError)
417 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
419 virtual string getCookies() const {
421 GetHeader(m_pn, m_pfc, "Cookie:", buf, 128, false);
422 return buf.empty() ? "" : buf;
425 virtual void clearHeader(const string &name) {
426 string hdr = name + ":";
427 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), "");
429 virtual void setHeader(const string &name, const string &value) {
430 string hdr = name + ":";
431 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()),
432 const_cast<char*>(value.c_str()));
434 virtual string getHeader(const string &name) {
435 string hdr = name + ":";
437 GetHeader(m_pn, m_pfc, const_cast<char*>(hdr.c_str()), buf, 1024, false);
440 virtual void setRemoteUser(const string &user) {
441 setHeader(string("remote-user"), user);
443 virtual string getRemoteUser(void) {
444 return getHeader(string("remote-user"));
446 virtual void* sendPage(
449 const string& content_type="text/html",
450 const Iterator<header_t>& headers=EMPTY(header_t)) {
451 string hdr = string ("Connection: close\r\nContent-type: ") + content_type + "\r\n";
452 while (headers.hasNext()) {
453 const header_t& h=headers.next();
454 hdr += h.first + ": " + h.second + "\r\n";
457 const char* codestr="200 OK";
459 case 403: codestr="403 Forbidden"; break;
460 case 404: codestr="404 Not Found"; break;
461 case 500: codestr="500 Server Error"; break;
463 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, (void*)codestr, (DWORD)hdr.c_str(), 0);
464 DWORD resplen = msg.size();
465 m_pfc->WriteClient(m_pfc, (LPVOID)msg.c_str(), &resplen, 0);
466 return (void*)SF_STATUS_REQ_FINISHED;
468 virtual void* sendRedirect(const string& url) {
469 // XXX: Don't support the httpRedirect option, yet.
470 string hdrs=m_cookie + string("Location: ") + url + "\r\n"
471 "Content-Type: text/html\r\n"
472 "Content-Length: 40\r\n"
473 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
474 "Cache-Control: private,no-store,no-cache\r\n\r\n";
475 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER,
476 "302 Please Wait", (DWORD)hdrs.c_str(), 0);
477 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
479 m_pfc->WriteClient(m_pfc, (LPVOID)redmsg, &resplen, 0);
480 return reinterpret_cast<void*>(SF_STATUS_REQ_FINISHED);
482 // XXX: We might not ever hit the 'decline' status in this filter.
483 //virtual void* returnDecline(void) { }
484 virtual void* returnOK(void) { return (void*) SF_STATUS_REQ_NEXT_NOTIFICATION; }
486 // The filter never processes the POST, so stub these methods.
487 virtual void setCookie(const string &name, const string &value) {
488 // Set the cookie for later. Use it during the redirect.
489 m_cookie += "Set-Cookie: " + name + "=" + value + "\r\n";
491 virtual string getArgs(void) { throw runtime_error("getArgs not implemented"); }
492 virtual string getPostData(void) { throw runtime_error("getPostData not implemented"); }
495 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const char* msg)
497 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
498 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
499 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)ctype,0);
500 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Filter Error</TITLE></HEAD><BODY>"
501 "<H1>Shibboleth Filter Error</H1>";
502 DWORD resplen=strlen(xmsg);
503 pfc->WriteClient(pfc,(LPVOID)xmsg,&resplen,0);
505 pfc->WriteClient(pfc,(LPVOID)msg,&resplen,0);
506 static const char* xmsg2="</BODY></HTML>";
507 resplen=strlen(xmsg2);
508 pfc->WriteClient(pfc,(LPVOID)xmsg2,&resplen,0);
509 return SF_STATUS_REQ_FINISHED;
512 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
514 // Is this a log notification?
515 if (notificationType==SF_NOTIFY_LOG)
517 if (pfc->pFilterContext)
518 ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=static_cast<LPCSTR>(pfc->pFilterContext);
519 return SF_STATUS_REQ_NEXT_NOTIFICATION;
522 PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
525 // Determine web site number. This can't really fail, I don't think.
527 GetServerVariable(pfc,"INSTANCE_ID",buf,10);
529 // Match site instance to host name, skip if no match.
530 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
531 if (map_i==g_Sites.end())
532 return SF_STATUS_REQ_NEXT_NOTIFICATION;
534 ostringstream threadid;
535 threadid << "[" << getpid() << "] isapi_shib" << '\0';
536 saml::NDC ndc(threadid.str().c_str());
538 ShibTargetIsapiF stf(pfc, pn, map_i->second);
540 // "false" because we don't override the Shib settings
541 pair<bool,void*> res = stf.doCheckAuthN();
542 if (res.first) return (DWORD)res.second;
544 // "false" because we don't override the Shib settings
545 res = stf.doExportAssertions();
546 if (res.first) return (DWORD)res.second;
548 res = stf.doCheckAuthZ();
549 if (res.first) return (DWORD)res.second;
551 return SF_STATUS_REQ_NEXT_NOTIFICATION;
554 return WriteClientError(pfc,"Out of Memory");
557 if (e==ERROR_NO_DATA)
558 return WriteClientError(pfc,"A required variable or header was empty.");
560 return WriteClientError(pfc,"Server detected unexpected IIS error.");
564 return WriteClientError(pfc,"Server caught an unknown exception.");
568 return WriteClientError(pfc,"Server reached unreachable code, save my walrus!");
573 IRequestMapper::Settings map_request(
574 PHTTP_FILTER_CONTEXT pfc, PHTTP_FILTER_PREPROC_HEADERS pn, IRequestMapper* mapper, const site_t& site, string& target
577 // URL path always come from IIS.
579 GetHeader(pn,pfc,"url",url,256,false);
581 // Port may come from IIS or from site def.
583 if (site.m_port.empty() || !g_bNormalizeRequest)
584 GetServerVariable(pfc,"SERVER_PORT",port,10);
586 strncpy(port,site.m_port.c_str(),10);
587 static_cast<char*>(port)[10]=0;
590 // Scheme may come from site def or be derived from IIS.
591 const char* scheme=site.m_scheme.c_str();
592 if (!scheme || !*scheme || !g_bNormalizeRequest)
593 scheme=pfc->fIsSecurePort ? "https" : "http";
595 // Start with scheme and hostname.
596 if (g_bNormalizeRequest) {
597 target = string(scheme) + "://" + site.m_name;
601 GetServerVariable(pfc,"SERVER_NAME",name,64);
602 target = string(scheme) + "://" + static_cast<char*>(name);
605 // If port is non-default, append it.
606 if ((!strcmp(scheme,"http") && port!="80") || (!strcmp(scheme,"https") && port!="443"))
607 target = target + ':' + static_cast<char*>(port);
611 target+=static_cast<char*>(url);
613 return mapper->getSettingsFromParsedURL(scheme,site.m_name.c_str(),strtoul(port,NULL,10),url);
616 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const IApplication* app, const char* page, ShibMLP& mlp)
618 const IPropertySet* props=app->getPropertySet("Errors");
620 pair<bool,const char*> p=props->getString(page);
622 ifstream infile(p.second);
623 if (!infile.fail()) {
624 const char* res = mlp.run(infile,props);
626 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
627 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)ctype,0);
628 DWORD resplen=strlen(res);
629 pfc->WriteClient(pfc,(LPVOID)res,&resplen,0);
630 return SF_STATUS_REQ_FINISHED;
636 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Filter unable to open error template.");
637 return WriteClientError(pfc,"Unable to open error template, check settings.");
640 DWORD WriteRedirectPage(PHTTP_FILTER_CONTEXT pfc, const IApplication* app, const char* file, ShibMLP& mlp, const char* headers=NULL)
642 ifstream infile(file);
643 if (!infile.fail()) {
644 const char* res = mlp.run(infile,app->getPropertySet("Errors"));
647 sprintf(buf,"Content-Length: %u\r\nContent-Type: text/html\r\n\r\n",strlen(res));
651 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)h.c_str(),0);
654 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)buf,0);
655 DWORD resplen=strlen(res);
656 pfc->WriteClient(pfc,(LPVOID)res,&resplen,0);
657 return SF_STATUS_REQ_FINISHED;
660 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Extension unable to open redirect template.");
661 return WriteClientError(pfc,"Unable to open redirect template, check settings.");
664 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
666 // Is this a log notification?
667 if (notificationType==SF_NOTIFY_LOG)
669 if (pfc->pFilterContext)
670 ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=static_cast<LPCSTR>(pfc->pFilterContext);
671 return SF_STATUS_REQ_NEXT_NOTIFICATION;
674 PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
677 // Determine web site number. This can't really fail, I don't think.
679 GetServerVariable(pfc,"INSTANCE_ID",buf,10);
681 // Match site instance to host name, skip if no match.
682 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
683 if (map_i==g_Sites.end())
684 return SF_STATUS_REQ_NEXT_NOTIFICATION;
686 ostringstream threadid;
687 threadid << "[" << getpid() << "] isapi_shib" << '\0';
688 saml::NDC ndc(threadid.str().c_str());
690 // We lock the configuration system for the duration.
691 IConfig* conf=g_Config->getINI();
694 // Map request to application and content settings.
696 IRequestMapper* mapper=conf->getRequestMapper();
697 Locker locker2(mapper);
698 IRequestMapper::Settings settings=map_request(pfc,pn,mapper,map_i->second,targeturl);
699 pair<bool,const char*> application_id=settings.first->getString("applicationId");
700 const IApplication* application=conf->getApplication(application_id.second);
702 return WriteClientError(pfc,"Unable to map request to application settings, check configuration.");
704 // Declare SHIRE object for this request.
705 SHIRE shire(application);
707 const char* shireURL=shire.getShireURL(targeturl.c_str());
709 return WriteClientError(pfc,"Unable to map request to proper shireURL setting, check configuration.");
711 // If the user is accessing the SHIRE acceptance point, pass it on.
712 if (targeturl.find(shireURL)!=string::npos)
713 return SF_STATUS_REQ_NEXT_NOTIFICATION;
715 // Now check the policy for this request.
716 pair<bool,bool> requireSession=settings.first->getBool("requireSession");
717 pair<const char*,const char*> shib_cookie=shire.getCookieNameProps();
718 pair<bool,bool> httpRedirects=application->getPropertySet("Sessions")->getBool("httpRedirects");
719 pair<bool,const char*> redirectPage=application->getPropertySet("Sessions")->getString("redirectPage");
720 if (httpRedirects.first && !httpRedirects.second && !redirectPage.first)
721 return WriteClientError(pfc,"HTML-based redirection requires a redirectPage property.");
723 // Check for session cookie.
724 const char* session_id=NULL;
725 GetHeader(pn,pfc,"Cookie:",buf,128,false);
726 Category::getInstance("isapi_shib.HttpFilterProc").debug("cookie header is {%s}",(const char*)buf);
727 if (!buf.empty() && (session_id=strstr(buf,shib_cookie.first))) {
728 session_id+=strlen(shib_cookie.first) + 1; /* Skip over the '=' */
729 char* cookieend=strchr(session_id,';');
731 *cookieend = '\0'; /* Ignore anyting after a ; */
734 if (!session_id || !*session_id) {
735 // If no session required, bail now.
736 if (!requireSession.second)
737 return SF_STATUS_REQ_NEXT_NOTIFICATION;
739 // No acceptable cookie, and we require a session. Generate an AuthnRequest.
740 const char* areq = shire.getAuthnRequest(targeturl.c_str());
741 if (!httpRedirects.first || httpRedirects.second) {
742 string hdrs=string("Location: ") + areq + "\r\n"
743 "Content-Type: text/html\r\n"
744 "Content-Length: 40\r\n"
745 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
746 "Cache-Control: private,no-store,no-cache\r\n\r\n";
747 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"302 Please Wait",(DWORD)hdrs.c_str(),0);
748 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
750 pfc->WriteClient(pfc,(LPVOID)redmsg,&resplen,0);
751 return SF_STATUS_REQ_FINISHED;
754 ShibMLP markupProcessor;
755 markupProcessor.insert("requestURL",areq);
756 return WriteRedirectPage(pfc, application, redirectPage.second, markupProcessor);
760 // Make sure this session is still valid.
761 RPCError* status = NULL;
762 ShibMLP markupProcessor;
763 markupProcessor.insert("requestURL", targeturl);
766 GetServerVariable(pfc,"REMOTE_ADDR",abuf,16);
768 status = shire.sessionIsValid(session_id, abuf);
770 catch (ShibTargetException &e) {
771 markupProcessor.insert("errorType", "Session Processing Error");
772 markupProcessor.insert("errorText", e.what());
773 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
774 return WriteClientError(pfc, application, "shire", markupProcessor);
778 markupProcessor.insert("errorType", "Session Processing Error");
779 markupProcessor.insert("errorText", "Unexpected Exception");
780 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
781 return WriteClientError(pfc, application, "shire", markupProcessor);
786 if (status->isError()) {
787 if (!requireSession.second)
788 return SF_STATUS_REQ_NEXT_NOTIFICATION;
789 else if (status->isRetryable()) {
790 // Oops, session is invalid. Generate AuthnRequest.
792 const char* areq = shire.getAuthnRequest(targeturl.c_str());
793 if (!httpRedirects.first || httpRedirects.second) {
794 string hdrs=string("Location: ") + areq + "\r\n"
795 "Content-Type: text/html\r\n"
796 "Content-Length: 40\r\n"
797 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
798 "Cache-Control: private,no-store,no-cache\r\n\r\n";
799 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"302 Please Wait",(DWORD)hdrs.c_str(),0);
800 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
802 pfc->WriteClient(pfc,(LPVOID)redmsg,&resplen,0);
803 return SF_STATUS_REQ_FINISHED;
806 markupProcessor.insert("requestURL",areq);
807 return WriteRedirectPage(pfc, application, redirectPage.second, markupProcessor);
811 // return the error page to the user
812 markupProcessor.insert(*status);
814 return WriteClientError(pfc, application, "shire", markupProcessor);
821 vector<SAMLAssertion*> assertions;
822 SAMLAuthenticationStatement* sso_statement=NULL;
825 status = rm.getAssertions(session_id, abuf, assertions, &sso_statement);
827 catch (ShibTargetException &e) {
828 markupProcessor.insert("errorType", "Attribute Processing Error");
829 markupProcessor.insert("errorText", e.what());
830 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
831 return WriteClientError(pfc, application, "rm", markupProcessor);
835 markupProcessor.insert("errorType", "Attribute Processing Error");
836 markupProcessor.insert("errorText", "Unexpected Exception");
837 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
838 return WriteClientError(pfc, application, "rm", markupProcessor);
842 if (status->isError()) {
843 markupProcessor.insert(*status);
845 return WriteClientError(pfc, application, "rm", markupProcessor);
849 // Do we have an access control plugin?
850 if (settings.second) {
851 Locker acllock(settings.second);
852 if (!settings.second->authorized(*sso_statement,assertions)) {
853 for (int k = 0; k < assertions.size(); k++)
854 delete assertions[k];
855 delete sso_statement;
856 return WriteClientError(pfc, application, "access", markupProcessor);
860 // Get the AAP providers, which contain the attribute policy info.
861 Iterator<IAAP*> provs=application->getAAPProviders();
863 // Clear out the list of mapped attributes
864 while (provs.hasNext()) {
865 IAAP* aap=provs.next();
868 Iterator<const IAttributeRule*> rules=aap->getAttributeRules();
869 while (rules.hasNext()) {
870 const char* header=rules.next()->getHeader();
872 string hname=string(header) + ':';
873 pn->SetHeader(pfc,const_cast<char*>(hname.c_str()),"");
879 for (int k = 0; k < assertions.size(); k++)
880 delete assertions[k];
881 delete sso_statement;
882 markupProcessor.insert("errorType", "Attribute Processing Error");
883 markupProcessor.insert("errorText", "Unexpected Exception");
884 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
885 return WriteClientError(pfc, application, "rm", markupProcessor);
891 // Maybe export the first assertion.
892 pn->SetHeader(pfc,"remote-user:","");
893 pn->SetHeader(pfc,"Shib-Attributes:","");
894 pair<bool,bool> exp=settings.first->getBool("exportAssertion");
895 if (exp.first && exp.second && assertions.size()) {
897 RM::serialize(*(assertions[0]), assertion);
898 string::size_type lfeed;
899 while ((lfeed=assertion.find('\n'))!=string::npos)
900 assertion.erase(lfeed,1);
901 pn->SetHeader(pfc,"Shib-Attributes:",const_cast<char*>(assertion.c_str()));
904 pn->SetHeader(pfc,"Shib-Origin-Site:","");
905 pn->SetHeader(pfc,"Shib-Authentication-Method:","");
906 pn->SetHeader(pfc,"Shib-NameIdentifier-Format:","");
908 // Export the SAML AuthnMethod and the origin site name.
909 auto_ptr_char os(sso_statement->getSubject()->getNameIdentifier()->getNameQualifier());
910 auto_ptr_char am(sso_statement->getAuthMethod());
911 pn->SetHeader(pfc,"Shib-Origin-Site:", const_cast<char*>(os.get()));
912 pn->SetHeader(pfc,"Shib-Authentication-Method:", const_cast<char*>(am.get()));
915 AAP wrapper(provs,sso_statement->getSubject()->getNameIdentifier()->getFormat(),Constants::SHIB_ATTRIBUTE_NAMESPACE_URI);
916 if (!wrapper.fail() && wrapper->getHeader()) {
917 auto_ptr_char form(sso_statement->getSubject()->getNameIdentifier()->getFormat());
918 auto_ptr_char nameid(sso_statement->getSubject()->getNameIdentifier()->getName());
919 pn->SetHeader(pfc,"Shib-NameIdentifier-Format:",const_cast<char*>(form.get()));
920 if (!strcmp(wrapper->getHeader(),"REMOTE_USER")) {
921 char* principal=const_cast<char*>(nameid.get());
922 pn->SetHeader(pfc,"remote-user:",principal);
923 pfc->pFilterContext=pfc->AllocMem(pfc,strlen(principal)+1,0);
924 if (pfc->pFilterContext)
925 strcpy(static_cast<char*>(pfc->pFilterContext),principal);
928 string hname=string(wrapper->getHeader()) + ':';
929 pn->SetHeader(pfc,const_cast<char*>(wrapper->getHeader()),const_cast<char*>(nameid.get()));
933 pn->SetHeader(pfc,"Shib-Application-ID:","");
934 pn->SetHeader(pfc,"Shib-Application-ID:",const_cast<char*>(application_id.second));
936 // Export the attributes.
937 Iterator<SAMLAssertion*> a_iter(assertions);
938 while (a_iter.hasNext()) {
939 SAMLAssertion* assert=a_iter.next();
940 Iterator<SAMLStatement*> statements=assert->getStatements();
941 while (statements.hasNext()) {
942 SAMLAttributeStatement* astate=dynamic_cast<SAMLAttributeStatement*>(statements.next());
945 Iterator<SAMLAttribute*> attrs=astate->getAttributes();
946 while (attrs.hasNext()) {
947 SAMLAttribute* attr=attrs.next();
949 // Are we supposed to export it?
950 AAP wrapper(provs,attr->getName(),attr->getNamespace());
951 if (wrapper.fail() || !wrapper->getHeader())
954 Iterator<string> vals=attr->getSingleByteValues();
955 if (!strcmp(wrapper->getHeader(),"REMOTE_USER") && vals.hasNext()) {
956 char* principal=const_cast<char*>(vals.next().c_str());
957 pn->SetHeader(pfc,"remote-user:",principal);
958 pfc->pFilterContext=pfc->AllocMem(pfc,strlen(principal)+1,0);
959 if (pfc->pFilterContext)
960 strcpy(static_cast<char*>(pfc->pFilterContext),principal);
965 string hname=string(wrapper->getHeader()) + ':';
966 GetHeader(pn,pfc,const_cast<char*>(hname.c_str()),buf,256,false);
971 for (; vals.hasNext(); it++) {
972 string value = vals.next();
973 for (string::size_type pos = value.find_first_of(";", string::size_type(0));
975 pos = value.find_first_of(";", pos)) {
976 value.insert(pos, "\\");
982 header=header + ';' + value;
984 pn->SetHeader(pfc,const_cast<char*>(hname.c_str()),const_cast<char*>(header.c_str()));
991 for (int k = 0; k < assertions.size(); k++)
992 delete assertions[k];
993 delete sso_statement;
995 return SF_STATUS_REQ_NEXT_NOTIFICATION;
998 return WriteClientError(pfc,"Out of Memory");
1001 if (e==ERROR_NO_DATA)
1002 return WriteClientError(pfc,"A required variable or header was empty.");
1004 return WriteClientError(pfc,"Server detected unexpected IIS error.");
1008 return WriteClientError(pfc,"Server caught an unknown exception.");
1012 return WriteClientError(pfc,"Server reached unreachable code, save my walrus!");
1016 /****************************************************************************/
1019 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* msg)
1021 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
1022 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
1023 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
1024 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Error</TITLE></HEAD><BODY><H1>Shibboleth Error</H1>";
1025 DWORD resplen=strlen(xmsg);
1026 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg,&resplen,HSE_IO_SYNC);
1027 resplen=strlen(msg);
1028 lpECB->WriteClient(lpECB->ConnID,(LPVOID)msg,&resplen,HSE_IO_SYNC);
1029 static const char* xmsg2="</BODY></HTML>";
1030 resplen=strlen(xmsg2);
1031 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg2,&resplen,HSE_IO_SYNC);
1032 return HSE_STATUS_SUCCESS;
1036 class ShibTargetIsapiE : public ShibTarget
1038 LPEXTENSION_CONTROL_BLOCK m_lpECB;
1042 ShibTargetIsapiE(LPEXTENSION_CONTROL_BLOCK lpECB, const site_t& site) {
1044 GetServerVariable(lpECB,"HTTPS",ssl,5);
1045 bool SSL=(ssl=="on" || ssl=="ON");
1047 // URL path always come from IIS.
1049 GetServerVariable(lpECB,"URL",url,255);
1051 // Port may come from IIS or from site def.
1053 if (!g_bNormalizeRequest || (SSL && site.m_sslport.empty()) || (!SSL && site.m_port.empty()))
1054 GetServerVariable(lpECB,"SERVER_PORT",port,10);
1056 strncpy(port,site.m_sslport.c_str(),10);
1057 static_cast<char*>(port)[10]=0;
1060 strncpy(port,site.m_port.c_str(),10);
1061 static_cast<char*>(port)[10]=0;
1064 // Scheme may come from site def or be derived from IIS.
1065 const char* scheme=site.m_scheme.c_str();
1066 if (!scheme || !*scheme || !g_bNormalizeRequest) {
1067 scheme = SSL ? "https" : "http";
1070 // Get the other server variables.
1071 dynabuf remote_addr(16),hostname(32);
1072 GetServerVariable(lpECB, "REMOTE_ADDR", remote_addr, 16);
1073 GetServerVariable(lpECB, "SERVER_NAME", hostname, 32);
1075 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
1076 const char* host=hostname;
1077 if (site.m_name!=host && site.m_aliases.find(host)==site.m_aliases.end())
1078 host=site.m_name.c_str();
1081 * IIS screws us over on PATH_INFO (the hits keep on coming). We need to figure out if
1082 * the server is set up for proper PATH_INFO handling, or "IIS sucks rabid weasels mode",
1083 * which is the default. No perfect way to tell, but we can take a good guess by checking
1084 * whether the URL is a substring of the PATH_INFO:
1086 * e.g. for /Shibboleth.sso/SAML/POST
1088 * Bad mode (default):
1089 * URL: /Shibboleth.sso
1090 * PathInfo: /Shibboleth.sso/SAML/POST
1093 * URL: /Shibboleth.sso
1094 * PathInfo: /SAML/POST
1099 // Clearly we're only in bad mode if path info exists at all.
1100 if (lpECB->lpszPathInfo && *(lpECB->lpszPathInfo)) {
1101 if (strstr(lpECB->lpszPathInfo,url))
1102 // Pretty good chance we're in bad mode, unless the PathInfo repeats the path itself.
1103 fullurl=lpECB->lpszPathInfo;
1106 fullurl+=lpECB->lpszPathInfo;
1110 // For consistency with Apache, let's add the query string.
1111 if (lpECB->lpszQueryString && *(lpECB->lpszQueryString)) {
1113 fullurl+=lpECB->lpszQueryString;
1115 init(scheme, host, atoi(port), fullurl.c_str(), lpECB->lpszContentType, remote_addr, lpECB->lpszMethod);
1119 ~ShibTargetIsapiE() { }
1121 virtual void log(ShibLogLevel level, const string &msg) {
1122 ShibTarget::log(level,msg);
1123 if (level == LogLevelError)
1124 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
1126 virtual string getCookies() const {
1128 GetServerVariable(m_lpECB, "HTTP_COOKIE", buf, 128, false);
1129 return buf.empty() ? "" : buf;
1131 virtual void setCookie(const string &name, const string &value) {
1132 // Set the cookie for later. Use it during the redirect.
1133 m_cookie += "Set-Cookie: " + name + "=" + value + "\r\n";
1135 virtual string getArgs(void) {
1136 return string(m_lpECB->lpszQueryString ? m_lpECB->lpszQueryString : "");
1138 virtual string getPostData(void) {
1139 if (m_lpECB->cbTotalBytes > 1024*1024) // 1MB?
1140 throw FatalProfileException("Blocked too-large a submission to profile endpoint.");
1141 else if (m_lpECB->cbTotalBytes != m_lpECB->cbAvailable) {
1144 DWORD datalen=m_lpECB->cbTotalBytes;
1147 BOOL ret = m_lpECB->ReadClient(m_lpECB->ConnID, buf, &buflen);
1148 if (!ret || !buflen)
1149 throw FatalProfileException("Error reading profile submission from browser.");
1150 cgistr.append(buf, buflen);
1156 return string(reinterpret_cast<char*>(m_lpECB->lpbData),m_lpECB->cbAvailable);
1158 virtual void* sendPage(
1161 const string& content_type="text/html",
1162 const Iterator<header_t>& headers=EMPTY(header_t)) {
1163 string hdr = string ("Connection: close\r\nContent-type: ") + content_type + "\r\n";
1164 for (int k = 0; k < headers.size(); k++) {
1165 hdr += headers[k].first + ": " + headers[k].second + "\r\n";
1168 const char* codestr="200 OK";
1170 case 403: codestr="403 Forbidden"; break;
1171 case 404: codestr="404 Not Found"; break;
1172 case 500: codestr="500 Server Error"; break;
1174 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, (void*)codestr, 0, (LPDWORD)hdr.c_str());
1175 DWORD resplen = msg.size();
1176 m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)msg.c_str(), &resplen, HSE_IO_SYNC);
1177 return (void*)HSE_STATUS_SUCCESS;
1179 virtual void* sendRedirect(const string& url) {
1180 // XXX: Don't support the httpRedirect option, yet.
1181 string hdrs = m_cookie + "Location: " + url + "\r\n"
1182 "Content-Type: text/html\r\n"
1183 "Content-Length: 40\r\n"
1184 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
1185 "Cache-Control: private,no-store,no-cache\r\n\r\n";
1186 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER,
1187 "302 Moved", 0, (LPDWORD)hdrs.c_str());
1188 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
1190 m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)redmsg, &resplen, HSE_IO_SYNC);
1191 return (void*)HSE_STATUS_SUCCESS;
1193 // Decline happens in the POST processor if this isn't the shire url
1194 // Note that it can also happen with HTAccess, but we don't support that, yet.
1195 virtual void* returnDecline(void) {
1197 WriteClientError(m_lpECB, "ISAPI extension can only be invoked to process Shibboleth protocol requests."
1198 "Make sure the mapped file extension doesn't match actual content.");
1200 virtual void* returnOK(void) { return (void*) HSE_STATUS_SUCCESS; }
1202 // Not used in the extension.
1203 virtual void clearHeader(const string &name) { throw runtime_error("clearHeader not implemented"); }
1204 virtual void setHeader(const string &name, const string &value) { throw runtime_error("setHeader not implemented"); }
1205 virtual string getHeader(const string &name) { throw runtime_error("getHeader not implemented"); }
1206 virtual void setRemoteUser(const string &user) { throw runtime_error("setRemoteUser not implemented"); }
1207 virtual string getRemoteUser(void) { throw runtime_error("getRemoteUser not implemented"); }
1210 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
1213 const IApplication* application=NULL;
1215 ostringstream threadid;
1216 threadid << "[" << getpid() << "] isapi_shib_extension" << '\0';
1217 saml::NDC ndc(threadid.str().c_str());
1219 // Determine web site number. This can't really fail, I don't think.
1221 GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
1223 // Match site instance to host name, skip if no match.
1224 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
1225 if (map_i==g_Sites.end())
1226 return WriteClientError(lpECB, "Shibboleth Extension not configured for this web site.");
1228 ShibTargetIsapiE ste(lpECB, map_i->second);
1229 pair<bool,void*> res = ste.doHandler();
1230 if (res.first) return (DWORD)res.second;
1232 return WriteClientError(lpECB, "Shibboleth Extension failed to process request");
1236 return WriteClientError(lpECB,"Out of Memory");
1239 if (e==ERROR_NO_DATA)
1240 return WriteClientError(lpECB,"A required variable or header was empty.");
1242 return WriteClientError(lpECB,"Server detected unexpected IIS error.");
1246 return WriteClientError(lpECB,"Server caught an unknown exception.");
1250 // If we get here we've got an error.
1251 return HSE_STATUS_ERROR;
1255 IRequestMapper::Settings map_request(
1256 LPEXTENSION_CONTROL_BLOCK lpECB, IRequestMapper* mapper, const site_t& site, string& target
1260 GetServerVariable(lpECB,"HTTPS",ssl,5);
1261 bool SSL=(ssl=="on" || ssl=="ON");
1263 // URL path always come from IIS.
1265 GetServerVariable(lpECB,"URL",url,255);
1267 // Port may come from IIS or from site def.
1269 if (site.m_port.empty() || !g_bNormalizeRequest)
1270 GetServerVariable(lpECB,"SERVER_PORT",port,10);
1272 strncpy(port,site.m_port.c_str(),10);
1273 static_cast<char*>(port)[10]=0;
1276 // Scheme may come from site def or be derived from IIS.
1277 const char* scheme=site.m_scheme.c_str();
1278 if (!scheme || !*scheme || !g_bNormalizeRequest) {
1279 scheme = SSL ? "https" : "http";
1282 // Start with scheme and hostname.
1283 if (g_bNormalizeRequest) {
1284 target = string(scheme) + "://" + site.m_name;
1288 GetServerVariable(lpECB,"SERVER_NAME",name,64);
1289 target = string(scheme) + "://" + static_cast<char*>(name);
1292 // If port is non-default, append it.
1293 if ((!strcmp(scheme,"http") && port!="80") || (!strcmp(scheme,"https") && port!="443"))
1294 target = target + ':' + static_cast<char*>(port);
1298 target+=static_cast<char*>(url);
1300 return mapper->getSettingsFromParsedURL(scheme,site.m_name.c_str(),strtoul(port,NULL,10),url);
1303 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const IApplication* app, const char* page, ShibMLP& mlp)
1305 const IPropertySet* props=app->getPropertySet("Errors");
1307 pair<bool,const char*> p=props->getString(page);
1309 ifstream infile(p.second);
1310 if (!infile.fail()) {
1311 const char* res = mlp.run(infile,props);
1313 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
1314 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
1315 DWORD resplen=strlen(res);
1316 lpECB->WriteClient(lpECB->ConnID,(LPVOID)res,&resplen,0);
1317 return HSE_STATUS_SUCCESS;
1322 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Extension unable to open error template.");
1323 return WriteClientError(lpECB,"Unable to open error template, check settings.");
1326 DWORD WriteRedirectPage(LPEXTENSION_CONTROL_BLOCK lpECB, const IApplication* app, const char* file, ShibMLP& mlp, const char* headers=NULL)
1328 ifstream infile(file);
1329 if (!infile.fail()) {
1330 const char* res = mlp.run(infile,app->getPropertySet("Errors"));
1333 sprintf(buf,"Content-Length: %u\r\nContent-Type: text/html\r\n\r\n",strlen(res));
1337 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)h.c_str());
1340 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)buf);
1341 DWORD resplen=strlen(res);
1342 lpECB->WriteClient(lpECB->ConnID,(LPVOID)res,&resplen,0);
1343 return HSE_STATUS_SUCCESS;
1346 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Extension unable to open redirect template.");
1347 return WriteClientError(lpECB,"Unable to open redirect template, check settings.");
1350 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
1353 const IApplication* application=NULL;
1356 ostringstream threadid;
1357 threadid << "[" << getpid() << "] shire_handler" << '\0';
1358 saml::NDC ndc(threadid.str().c_str());
1360 // Determine web site number. This can't really fail, I don't think.
1362 GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
1364 // Match site instance to host name, skip if no match.
1365 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
1366 if (map_i==g_Sites.end())
1367 return WriteClientError(lpECB,"Shibboleth filter not configured for this web site.");
1369 // We lock the configuration system for the duration.
1370 IConfig* conf=g_Config->getINI();
1371 Locker locker(conf);
1373 // Map request to application and content settings.
1374 IRequestMapper* mapper=conf->getRequestMapper();
1375 Locker locker2(mapper);
1376 IRequestMapper::Settings settings=map_request(lpECB,mapper,map_i->second,targeturl);
1377 pair<bool,const char*> application_id=settings.first->getString("applicationId");
1378 application=conf->getApplication(application_id.second);
1379 const IPropertySet* sessionProps=application ? application->getPropertySet("Sessions") : NULL;
1380 if (!application || !sessionProps)
1381 return WriteClientError(lpECB,"Unable to map request to application session settings, check configuration.");
1383 SHIRE shire(application);
1385 const char* shireURL=shire.getShireURL(targeturl.c_str());
1387 return WriteClientError(lpECB,"Unable to map request to proper shireURL setting, check configuration.");
1389 // Make sure we only process the SHIRE requests.
1390 if (!strstr(targeturl.c_str(),shireURL))
1391 return WriteClientError(lpECB,"ISAPI extension can only be invoked to process incoming sessions."
1392 "Make sure the mapped file extension doesn't match actual content.");
1394 pair<const char*,const char*> shib_cookie=shire.getCookieNameProps();
1396 // Make sure this is SSL, if it should be
1397 pair<bool,bool> shireSSL=sessionProps->getBool("shireSSL");
1398 if (!shireSSL.first || shireSSL.second) {
1399 GetServerVariable(lpECB,"HTTPS",buf,10);
1401 throw ShibTargetException(SHIBRPC_OK,"blocked non-SSL access to SHIRE POST processor");
1404 pair<bool,bool> httpRedirects=sessionProps->getBool("httpRedirects");
1405 pair<bool,const char*> redirectPage=sessionProps->getString("redirectPage");
1406 if (httpRedirects.first && !httpRedirects.second && !redirectPage.first)
1407 return WriteClientError(lpECB,"HTML-based redirection requires a redirectPage property.");
1409 // Check for Mac web browser
1413 GetServerVariable(lpECB,"HTTP_USER_AGENT",agent,64);
1414 if (strstr(agent,"AppleWebKit/"))
1418 // If this is a GET, we manufacture an AuthnRequest.
1419 if (!stricmp(lpECB->lpszMethod,"GET")) {
1420 const char* areq=lpECB->lpszQueryString ? shire.getLazyAuthnRequest(lpECB->lpszQueryString) : NULL;
1422 throw ShibTargetException(SHIBRPC_OK, "malformed arguments to request a new session");
1423 if (!httpRedirects.first || httpRedirects.second) {
1424 string hdrs=string("Location: ") + areq + "\r\n"
1425 "Content-Type: text/html\r\n"
1426 "Content-Length: 40\r\n"
1427 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
1428 "Cache-Control: private,no-store,no-cache\r\n\r\n";
1429 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"302 Moved",0,(LPDWORD)hdrs.c_str());
1430 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
1432 lpECB->WriteClient(lpECB->ConnID,(LPVOID)redmsg,&resplen,HSE_IO_SYNC);
1433 return HSE_STATUS_SUCCESS;
1436 ShibMLP markupProcessor;
1437 markupProcessor.insert("requestURL",areq);
1438 return WriteRedirectPage(lpECB, application, redirectPage.second, markupProcessor);
1441 else if (stricmp(lpECB->lpszMethod,"POST"))
1442 throw ShibTargetException(SHIBRPC_OK,"blocked non-POST to SHIRE POST processor");
1444 // Sure sure this POST is an appropriate content type
1445 if (!lpECB->lpszContentType || stricmp(lpECB->lpszContentType,"application/x-www-form-urlencoded"))
1446 throw ShibTargetException(SHIBRPC_OK,"blocked bad content-type to SHIRE POST processor");
1449 pair<const char*,const char*> elements=pair<const char*,const char*>(NULL,NULL);
1450 if (lpECB->cbTotalBytes > 1024*1024) // 1MB?
1451 throw ShibTargetException(SHIBRPC_OK,"blocked too-large a post to SHIRE POST processor");
1452 else if (lpECB->cbTotalBytes!=lpECB->cbAvailable) {
1455 DWORD datalen=lpECB->cbTotalBytes;
1458 BOOL ret=lpECB->ReadClient(lpECB->ConnID,buf,&buflen);
1459 if (!ret || !buflen)
1460 throw ShibTargetException(SHIBRPC_OK,"error reading POST data from browser");
1461 cgistr.append(buf,buflen);
1464 elements=shire.getFormSubmission(cgistr.c_str(),cgistr.length());
1467 elements=shire.getFormSubmission(reinterpret_cast<char*>(lpECB->lpbData),lpECB->cbAvailable);
1469 // Make sure the SAML Response parameter exists
1470 if (!elements.first || !*elements.first)
1471 throw ShibTargetException(SHIBRPC_OK, "SHIRE POST failed to find SAMLResponse form element");
1473 // Make sure the target parameter exists
1474 if (!elements.second || !*elements.second)
1475 throw ShibTargetException(SHIBRPC_OK, "SHIRE POST failed to find TARGET form element");
1477 GetServerVariable(lpECB,"REMOTE_ADDR",buf,16);
1479 // Process the post.
1481 RPCError* status=NULL;
1482 ShibMLP markupProcessor;
1483 markupProcessor.insert("requestURL", targeturl.c_str());
1485 status = shire.sessionCreate(elements.first,buf,cookie);
1487 catch (ShibTargetException &e) {
1488 markupProcessor.insert("errorType", "Session Creation Service Error");
1489 markupProcessor.insert("errorText", e.what());
1490 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
1491 return WriteClientError(lpECB, application, "shire", markupProcessor);
1495 markupProcessor.insert("errorType", "Session Creation Service Error");
1496 markupProcessor.insert("errorText", "Unexpected Exception");
1497 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
1498 return WriteClientError(lpECB, application, "shire", markupProcessor);
1502 if (status->isError()) {
1503 if (status->isRetryable()) {
1505 const char* loc=shire.getAuthnRequest(elements.second);
1506 if (!httpRedirects.first || httpRedirects.second) {
1507 string hdrs=string("Location: ") + loc + "\r\n"
1508 "Content-Type: text/html\r\n"
1509 "Content-Length: 40\r\n"
1510 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
1511 "Cache-Control: private,no-store,no-cache\r\n\r\n";
1512 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"302 Moved",0,(LPDWORD)hdrs.c_str());
1513 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
1515 lpECB->WriteClient(lpECB->ConnID,(LPVOID)redmsg,&resplen,HSE_IO_SYNC);
1516 return HSE_STATUS_SUCCESS;
1519 markupProcessor.insert("requestURL",loc);
1520 return WriteRedirectPage(lpECB, application, redirectPage.second, markupProcessor);
1524 // Return this error to the user.
1525 markupProcessor.insert(*status);
1527 return WriteClientError(lpECB,application,"shire",markupProcessor);
1531 // We've got a good session, set the cookie and redirect to target.
1532 cookie = string("Set-Cookie: ") + shib_cookie.first + '=' + cookie + shib_cookie.second + "\r\n"
1533 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
1534 "Cache-Control: private,no-store,no-cache\r\n";
1535 if (!httpRedirects.first || httpRedirects.second) {
1536 cookie=cookie + "Content-Type: text/html\r\nLocation: " + elements.second + "\r\nContent-Length: 40\r\n\r\n";
1537 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"302 Moved",0,(LPDWORD)cookie.c_str());
1538 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
1540 lpECB->WriteClient(lpECB->ConnID,(LPVOID)redmsg,&resplen,HSE_IO_SYNC);
1541 return HSE_STATUS_SUCCESS;
1544 markupProcessor.insert("requestURL",elements.second);
1545 return WriteRedirectPage(lpECB, application, redirectPage.second, markupProcessor, cookie.c_str());
1548 catch (ShibTargetException &e) {
1550 ShibMLP markupProcessor;
1551 markupProcessor.insert("requestURL", targeturl.c_str());
1552 markupProcessor.insert("errorType", "Session Creation Service Error");
1553 markupProcessor.insert("errorText", e.what());
1554 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
1555 return WriteClientError(lpECB,application,"shire",markupProcessor);
1561 ShibMLP markupProcessor;
1562 markupProcessor.insert("requestURL", targeturl.c_str());
1563 markupProcessor.insert("errorType", "Session Creation Service Error");
1564 markupProcessor.insert("errorText", "Unexpected Exception");
1565 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
1566 return WriteClientError(lpECB,application,"shire",markupProcessor);
1571 return HSE_STATUS_ERROR;