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>
75 using namespace shibboleth;
76 using namespace shibtarget;
80 static const XMLCh name[] = { chLatin_n, chLatin_a, chLatin_m, chLatin_e, chNull };
81 static const XMLCh port[] = { chLatin_p, chLatin_o, chLatin_r, chLatin_t, chNull };
82 static const XMLCh sslport[] = { chLatin_s, chLatin_s, chLatin_l, chLatin_p, chLatin_o, chLatin_r, chLatin_t, chNull };
83 static const XMLCh scheme[] = { chLatin_s, chLatin_c, chLatin_h, chLatin_e, chLatin_m, chLatin_e, chNull };
84 static const XMLCh id[] = { chLatin_i, chLatin_d, chNull };
85 static const XMLCh Implementation[] =
86 { 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 };
87 static const XMLCh ISAPI[] = { chLatin_I, chLatin_S, chLatin_A, chLatin_P, chLatin_I, chNull };
88 static const XMLCh Alias[] = { chLatin_A, chLatin_l, chLatin_i, chLatin_a, chLatin_s, chNull };
89 static const XMLCh normalizeRequest[] =
90 { chLatin_n, chLatin_o, chLatin_r, chLatin_m, chLatin_a, chLatin_l, chLatin_i, chLatin_z, chLatin_e,
91 chLatin_R, chLatin_e, chLatin_q, chLatin_u, chLatin_e, chLatin_s, chLatin_t, chNull
93 static const XMLCh Site[] = { chLatin_S, chLatin_i, chLatin_t, chLatin_e, chNull };
96 site_t(const DOMElement* e)
98 auto_ptr_char n(e->getAttributeNS(NULL,name));
99 auto_ptr_char s(e->getAttributeNS(NULL,scheme));
100 auto_ptr_char p(e->getAttributeNS(NULL,port));
101 auto_ptr_char p2(e->getAttributeNS(NULL,sslport));
102 if (n.get()) m_name=n.get();
103 if (s.get()) m_scheme=s.get();
104 if (p.get()) m_port=p.get();
105 if (p2.get()) m_sslport=p2.get();
106 DOMNodeList* nlist=e->getElementsByTagNameNS(shibtarget::XML::SHIBTARGET_NS,Alias);
107 for (int i=0; nlist && i<nlist->getLength(); i++) {
108 if (nlist->item(i)->hasChildNodes()) {
109 auto_ptr_char alias(nlist->item(i)->getFirstChild()->getNodeValue());
110 m_aliases.insert(alias.get());
114 string m_scheme,m_port,m_sslport,m_name;
115 set<string> m_aliases;
118 HINSTANCE g_hinstDLL;
119 ShibTargetConfig* g_Config = NULL;
120 map<string,site_t> g_Sites;
121 bool g_bNormalizeRequest = true;
125 LPCSTR lpUNCServerName,
131 LPCSTR messages[] = {message, NULL};
133 HANDLE hElog = RegisterEventSource(lpUNCServerName, "Shibboleth ISAPI Filter");
134 BOOL res = ReportEvent(hElog, wType, 0, dwEventID, lpUserSid, 1, 0, messages, NULL);
135 return (DeregisterEventSource(hElog) && res);
138 extern "C" __declspec(dllexport) BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID)
140 if (fdwReason==DLL_PROCESS_ATTACH)
145 extern "C" BOOL WINAPI GetExtensionVersion(HSE_VERSION_INFO* pVer)
152 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
153 "Extension mode startup not possible, is the DLL loaded as a filter?");
157 pVer->dwExtensionVersion=HSE_VERSION;
158 strncpy(pVer->lpszExtensionDesc,"Shibboleth ISAPI Extension",HSE_MAX_EXT_DLL_NAME_LEN-1);
162 extern "C" BOOL WINAPI TerminateExtension(DWORD)
164 return TRUE; // cleanup should happen when filter unloads
167 extern "C" BOOL WINAPI GetFilterVersion(PHTTP_FILTER_VERSION pVer)
172 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
173 "Reentrant filter initialization, ignoring...");
181 LPCSTR schemadir=getenv("SHIBSCHEMAS");
183 schemadir=SHIB_SCHEMAS;
184 LPCSTR config=getenv("SHIBCONFIG");
187 g_Config=&ShibTargetConfig::getConfig();
188 g_Config->setFeatures(
189 ShibTargetConfig::Listener |
190 ShibTargetConfig::Metadata |
191 ShibTargetConfig::AAP |
192 ShibTargetConfig::RequestMapper |
193 ShibTargetConfig::LocalExtensions |
194 ShibTargetConfig::Logging
196 if (!g_Config->init(schemadir)) {
198 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
199 "Filter startup failed during library initialization, check native log for help.");
202 else if (!g_Config->load(config)) {
204 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
205 "Filter startup failed to load configuration, check native log for help.");
209 // Access the implementation-specifics for site mappings.
210 IConfig* conf=g_Config->getINI();
212 const IPropertySet* props=conf->getPropertySet("Local");
214 const DOMElement* impl=saml::XML::getFirstChildElement(
215 props->getElement(),shibtarget::XML::SHIBTARGET_NS,Implementation
217 if (impl && (impl=saml::XML::getFirstChildElement(impl,shibtarget::XML::SHIBTARGET_NS,ISAPI))) {
218 const XMLCh* flag=impl->getAttributeNS(NULL,normalizeRequest);
219 g_bNormalizeRequest=(!flag || !*flag || *flag==chDigit_1 || *flag==chLatin_t);
220 impl=saml::XML::getFirstChildElement(impl,shibtarget::XML::SHIBTARGET_NS,Site);
222 auto_ptr_char id(impl->getAttributeNS(NULL,id));
224 g_Sites.insert(pair<string,site_t>(id.get(),site_t(impl)));
225 impl=saml::XML::getNextSiblingElement(impl,shibtarget::XML::SHIBTARGET_NS,Site);
233 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Filter startup failed with an exception.");
238 pVer->dwFilterVersion=HTTP_FILTER_REVISION;
239 strncpy(pVer->lpszFilterDesc,"Shibboleth ISAPI Filter",SF_MAX_FILTER_DESC_LEN);
240 pVer->dwFlags=(SF_NOTIFY_ORDER_HIGH |
241 SF_NOTIFY_SECURE_PORT |
242 SF_NOTIFY_NONSECURE_PORT |
243 SF_NOTIFY_PREPROC_HEADERS |
245 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter initialized...");
249 extern "C" BOOL WINAPI TerminateFilter(DWORD)
252 g_Config->shutdown();
254 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter shut down...");
258 /* Next up, some suck-free versions of various APIs.
260 You DON'T require people to guess the buffer size and THEN tell them the right size.
261 Returning an LPCSTR is apparently way beyond their ken. Not to mention the fact that
262 constant strings aren't typed as such, making it just that much harder. These versions
263 are now updated to use a special growable buffer object, modeled after the standard
264 string class. The standard string won't work because they left out the option to
265 pre-allocate a non-constant buffer.
271 dynabuf() { bufptr=NULL; buflen=0; }
272 dynabuf(size_t s) { bufptr=new char[buflen=s]; *bufptr=0; }
273 ~dynabuf() { delete[] bufptr; }
274 size_t length() const { return bufptr ? strlen(bufptr) : 0; }
275 size_t size() const { return buflen; }
276 bool empty() const { return length()==0; }
277 void reserve(size_t s, bool keep=false);
278 void erase() { if (bufptr) memset(bufptr,0,buflen); }
279 operator char*() { return bufptr; }
280 bool operator ==(const char* s) const;
281 bool operator !=(const char* s) const { return !(*this==s); }
287 void dynabuf::reserve(size_t s, bool keep)
294 p[buflen]=bufptr[buflen];
300 bool dynabuf::operator==(const char* s) const
302 if (buflen==NULL || s==NULL)
303 return (buflen==NULL && s==NULL);
305 return strcmp(bufptr,s)==0;
308 void GetServerVariable(PHTTP_FILTER_CONTEXT pfc, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
309 throw (bad_alloc, DWORD)
315 while (!pfc->GetServerVariable(pfc,lpszVariable,s,&size))
317 // Grumble. Check the error.
318 DWORD e=GetLastError();
319 if (e==ERROR_INSUFFICIENT_BUFFER)
324 if (bRequired && s.empty())
328 void GetServerVariable(LPEXTENSION_CONTROL_BLOCK lpECB, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
329 throw (bad_alloc, DWORD)
335 while (!lpECB->GetServerVariable(lpECB->ConnID,lpszVariable,s,&size))
337 // Grumble. Check the error.
338 DWORD e=GetLastError();
339 if (e==ERROR_INSUFFICIENT_BUFFER)
344 if (bRequired && s.empty())
348 void GetHeader(PHTTP_FILTER_PREPROC_HEADERS pn, PHTTP_FILTER_CONTEXT pfc,
349 LPSTR lpszName, dynabuf& s, DWORD size=80, bool bRequired=true)
350 throw (bad_alloc, DWORD)
356 while (!pn->GetHeader(pfc,lpszName,s,&size))
358 // Grumble. Check the error.
359 DWORD e=GetLastError();
360 if (e==ERROR_INSUFFICIENT_BUFFER)
365 if (bRequired && s.empty())
369 /****************************************************************************/
372 class ShibTargetIsapiF : public ShibTarget
374 PHTTP_FILTER_CONTEXT m_pfc;
375 PHTTP_FILTER_PREPROC_HEADERS m_pn;
378 ShibTargetIsapiF(PHTTP_FILTER_CONTEXT pfc, PHTTP_FILTER_PREPROC_HEADERS pn, const site_t& site) {
380 // URL path always come from IIS.
382 GetHeader(pn,pfc,"url",url,256,false);
384 // Port may come from IIS or from site def.
386 if (!g_bNormalizeRequest || (pfc->fIsSecurePort && site.m_sslport.empty()) || (!pfc->fIsSecurePort && site.m_port.empty()))
387 GetServerVariable(pfc,"SERVER_PORT",port,10);
388 else if (pfc->fIsSecurePort) {
389 strncpy(port,site.m_sslport.c_str(),10);
390 static_cast<char*>(port)[10]=0;
393 strncpy(port,site.m_port.c_str(),10);
394 static_cast<char*>(port)[10]=0;
397 // Scheme may come from site def or be derived from IIS.
398 const char* scheme=site.m_scheme.c_str();
399 if (!scheme || !*scheme || !g_bNormalizeRequest)
400 scheme=pfc->fIsSecurePort ? "https" : "http";
402 // Get the rest of the server variables.
403 dynabuf remote_addr(16),method(5),content_type(32),hostname(32);
404 GetServerVariable(pfc,"SERVER_NAME",hostname,32);
405 GetServerVariable(pfc,"REMOTE_ADDR",remote_addr,16);
406 GetServerVariable(pfc,"REQUEST_METHOD",method,5,false);
407 GetServerVariable(pfc,"CONTENT_TYPE",content_type,32,false);
409 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
410 const char* host=hostname;
411 if (site.m_name!=host && site.m_aliases.find(host)==site.m_aliases.end())
412 host=site.m_name.c_str();
414 init(scheme, host, atoi(port), url, content_type, remote_addr, method);
419 ~ShibTargetIsapiF() { }
421 virtual void log(ShibLogLevel level, const string &msg) {
422 ShibTarget::log(level,msg);
423 if (level == LogLevelError)
424 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
426 virtual string getCookies() const {
428 GetHeader(m_pn, m_pfc, "Cookie:", buf, 128, false);
429 return buf.empty() ? "" : buf;
432 virtual void clearHeader(const string &name) {
433 string hdr = name + ":";
434 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), "");
436 virtual void setHeader(const string &name, const string &value) {
437 string hdr = name + ":";
438 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()),
439 const_cast<char*>(value.c_str()));
441 virtual string getHeader(const string &name) {
442 string hdr = name + ":";
444 GetHeader(m_pn, m_pfc, const_cast<char*>(hdr.c_str()), buf, 1024, false);
447 virtual void setRemoteUser(const string &user) {
448 setHeader(string("remote-user"), user);
450 virtual string getRemoteUser(void) {
451 return getHeader(string("remote-user"));
453 virtual void* sendPage(
456 const string& content_type="text/html",
457 const Iterator<header_t>& headers=EMPTY(header_t)) {
458 string hdr = string ("Connection: close\r\nContent-type: ") + content_type + "\r\n";
459 while (headers.hasNext()) {
460 const header_t& h=headers.next();
461 hdr += h.first + ": " + h.second + "\r\n";
464 const char* codestr="200 OK";
466 case 403: codestr="403 Forbidden"; break;
467 case 404: codestr="404 Not Found"; break;
468 case 500: codestr="500 Server Error"; break;
470 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, (void*)codestr, (DWORD)hdr.c_str(), 0);
471 DWORD resplen = msg.size();
472 m_pfc->WriteClient(m_pfc, (LPVOID)msg.c_str(), &resplen, 0);
473 return (void*)SF_STATUS_REQ_FINISHED;
475 virtual void* sendRedirect(const string& url) {
476 // XXX: Don't support the httpRedirect option, yet.
477 string hdrs=m_cookie + string("Location: ") + url + "\r\n"
478 "Content-Type: text/html\r\n"
479 "Content-Length: 40\r\n"
480 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
481 "Cache-Control: private,no-store,no-cache\r\n\r\n";
482 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER,
483 "302 Please Wait", (DWORD)hdrs.c_str(), 0);
484 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
486 m_pfc->WriteClient(m_pfc, (LPVOID)redmsg, &resplen, 0);
487 return reinterpret_cast<void*>(SF_STATUS_REQ_FINISHED);
489 // XXX: We might not ever hit the 'decline' status in this filter.
490 //virtual void* returnDecline(void) { }
491 virtual void* returnOK(void) { return (void*) SF_STATUS_REQ_NEXT_NOTIFICATION; }
493 // The filter never processes the POST, so stub these methods.
494 virtual void setCookie(const string &name, const string &value) {
495 // Set the cookie for later. Use it during the redirect.
496 m_cookie += "Set-Cookie: " + name + "=" + value + "\r\n";
498 virtual string getArgs(void) { throw runtime_error("getArgs not implemented"); }
499 virtual string getPostData(void) { throw runtime_error("getPostData not implemented"); }
502 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const char* msg)
504 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
505 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
506 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)ctype,0);
507 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Filter Error</TITLE></HEAD><BODY>"
508 "<H1>Shibboleth Filter Error</H1>";
509 DWORD resplen=strlen(xmsg);
510 pfc->WriteClient(pfc,(LPVOID)xmsg,&resplen,0);
512 pfc->WriteClient(pfc,(LPVOID)msg,&resplen,0);
513 static const char* xmsg2="</BODY></HTML>";
514 resplen=strlen(xmsg2);
515 pfc->WriteClient(pfc,(LPVOID)xmsg2,&resplen,0);
516 return SF_STATUS_REQ_FINISHED;
519 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
521 // Is this a log notification?
522 if (notificationType==SF_NOTIFY_LOG)
524 if (pfc->pFilterContext)
525 ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=static_cast<LPCSTR>(pfc->pFilterContext);
526 return SF_STATUS_REQ_NEXT_NOTIFICATION;
529 PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
532 // Determine web site number. This can't really fail, I don't think.
534 GetServerVariable(pfc,"INSTANCE_ID",buf,10);
536 // Match site instance to host name, skip if no match.
537 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
538 if (map_i==g_Sites.end())
539 return SF_STATUS_REQ_NEXT_NOTIFICATION;
541 ostringstream threadid;
542 threadid << "[" << getpid() << "] isapi_shib" << '\0';
543 saml::NDC ndc(threadid.str().c_str());
545 ShibTargetIsapiF stf(pfc, pn, map_i->second);
547 // "false" because we don't override the Shib settings
548 pair<bool,void*> res = stf.doCheckAuthN();
549 if (res.first) return (DWORD)res.second;
551 // "false" because we don't override the Shib settings
552 res = stf.doExportAssertions();
553 if (res.first) return (DWORD)res.second;
555 res = stf.doCheckAuthZ();
556 if (res.first) return (DWORD)res.second;
558 return SF_STATUS_REQ_NEXT_NOTIFICATION;
561 return WriteClientError(pfc,"Out of Memory");
564 if (e==ERROR_NO_DATA)
565 return WriteClientError(pfc,"A required variable or header was empty.");
567 return WriteClientError(pfc,"Server detected unexpected IIS error.");
571 return WriteClientError(pfc,"Server caught an unknown exception.");
575 return WriteClientError(pfc,"Server reached unreachable code, save my walrus!");
579 /****************************************************************************/
582 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* msg)
584 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
585 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
586 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
587 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Error</TITLE></HEAD><BODY><H1>Shibboleth Error</H1>";
588 DWORD resplen=strlen(xmsg);
589 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg,&resplen,HSE_IO_SYNC);
591 lpECB->WriteClient(lpECB->ConnID,(LPVOID)msg,&resplen,HSE_IO_SYNC);
592 static const char* xmsg2="</BODY></HTML>";
593 resplen=strlen(xmsg2);
594 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg2,&resplen,HSE_IO_SYNC);
595 return HSE_STATUS_SUCCESS;
599 class ShibTargetIsapiE : public ShibTarget
601 LPEXTENSION_CONTROL_BLOCK m_lpECB;
605 ShibTargetIsapiE(LPEXTENSION_CONTROL_BLOCK lpECB, const site_t& site) {
607 GetServerVariable(lpECB,"HTTPS",ssl,5);
608 bool SSL=(ssl=="on" || ssl=="ON");
610 // URL path always come from IIS.
612 GetServerVariable(lpECB,"URL",url,255);
614 // Port may come from IIS or from site def.
616 if (!g_bNormalizeRequest || (SSL && site.m_sslport.empty()) || (!SSL && site.m_port.empty()))
617 GetServerVariable(lpECB,"SERVER_PORT",port,10);
619 strncpy(port,site.m_sslport.c_str(),10);
620 static_cast<char*>(port)[10]=0;
623 strncpy(port,site.m_port.c_str(),10);
624 static_cast<char*>(port)[10]=0;
627 // Scheme may come from site def or be derived from IIS.
628 const char* scheme=site.m_scheme.c_str();
629 if (!scheme || !*scheme || !g_bNormalizeRequest) {
630 scheme = SSL ? "https" : "http";
633 // Get the other server variables.
634 dynabuf remote_addr(16),hostname(32);
635 GetServerVariable(lpECB, "REMOTE_ADDR", remote_addr, 16);
636 GetServerVariable(lpECB, "SERVER_NAME", hostname, 32);
638 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
639 const char* host=hostname;
640 if (site.m_name!=host && site.m_aliases.find(host)==site.m_aliases.end())
641 host=site.m_name.c_str();
644 * IIS screws us over on PATH_INFO (the hits keep on coming). We need to figure out if
645 * the server is set up for proper PATH_INFO handling, or "IIS sucks rabid weasels mode",
646 * which is the default. No perfect way to tell, but we can take a good guess by checking
647 * whether the URL is a substring of the PATH_INFO:
649 * e.g. for /Shibboleth.sso/SAML/POST
651 * Bad mode (default):
652 * URL: /Shibboleth.sso
653 * PathInfo: /Shibboleth.sso/SAML/POST
656 * URL: /Shibboleth.sso
657 * PathInfo: /SAML/POST
662 // Clearly we're only in bad mode if path info exists at all.
663 if (lpECB->lpszPathInfo && *(lpECB->lpszPathInfo)) {
664 if (strstr(lpECB->lpszPathInfo,url))
665 // Pretty good chance we're in bad mode, unless the PathInfo repeats the path itself.
666 fullurl=lpECB->lpszPathInfo;
669 fullurl+=lpECB->lpszPathInfo;
673 // For consistency with Apache, let's add the query string.
674 if (lpECB->lpszQueryString && *(lpECB->lpszQueryString)) {
676 fullurl+=lpECB->lpszQueryString;
678 init(scheme, host, atoi(port), fullurl.c_str(), lpECB->lpszContentType, remote_addr, lpECB->lpszMethod);
682 ~ShibTargetIsapiE() { }
684 virtual void log(ShibLogLevel level, const string &msg) {
685 ShibTarget::log(level,msg);
686 if (level == LogLevelError)
687 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
689 virtual string getCookies() const {
691 GetServerVariable(m_lpECB, "HTTP_COOKIE", buf, 128, false);
692 return buf.empty() ? "" : buf;
694 virtual void setCookie(const string &name, const string &value) {
695 // Set the cookie for later. Use it during the redirect.
696 m_cookie += "Set-Cookie: " + name + "=" + value + "\r\n";
698 virtual string getArgs(void) {
699 return string(m_lpECB->lpszQueryString ? m_lpECB->lpszQueryString : "");
701 virtual string getPostData(void) {
702 if (m_lpECB->cbTotalBytes > 1024*1024) // 1MB?
703 throw FatalProfileException("Blocked too-large a submission to profile endpoint.");
704 else if (m_lpECB->cbTotalBytes != m_lpECB->cbAvailable) {
707 DWORD datalen=m_lpECB->cbTotalBytes;
710 BOOL ret = m_lpECB->ReadClient(m_lpECB->ConnID, buf, &buflen);
712 throw FatalProfileException("Error reading profile submission from browser.");
713 cgistr.append(buf, buflen);
719 return string(reinterpret_cast<char*>(m_lpECB->lpbData),m_lpECB->cbAvailable);
721 virtual void* sendPage(
724 const string& content_type="text/html",
725 const Iterator<header_t>& headers=EMPTY(header_t)) {
726 string hdr = string ("Connection: close\r\nContent-type: ") + content_type + "\r\n";
727 for (int k = 0; k < headers.size(); k++) {
728 hdr += headers[k].first + ": " + headers[k].second + "\r\n";
731 const char* codestr="200 OK";
733 case 403: codestr="403 Forbidden"; break;
734 case 404: codestr="404 Not Found"; break;
735 case 500: codestr="500 Server Error"; break;
737 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, (void*)codestr, 0, (LPDWORD)hdr.c_str());
738 DWORD resplen = msg.size();
739 m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)msg.c_str(), &resplen, HSE_IO_SYNC);
740 return (void*)HSE_STATUS_SUCCESS;
742 virtual void* sendRedirect(const string& url) {
743 // XXX: Don't support the httpRedirect option, yet.
744 string hdrs = m_cookie + "Location: " + url + "\r\n"
745 "Content-Type: text/html\r\n"
746 "Content-Length: 40\r\n"
747 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
748 "Cache-Control: private,no-store,no-cache\r\n\r\n";
749 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER,
750 "302 Moved", 0, (LPDWORD)hdrs.c_str());
751 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
753 m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)redmsg, &resplen, HSE_IO_SYNC);
754 return (void*)HSE_STATUS_SUCCESS;
756 // Decline happens in the POST processor if this isn't the shire url
757 // Note that it can also happen with HTAccess, but we don't support that, yet.
758 virtual void* returnDecline(void) {
760 WriteClientError(m_lpECB, "ISAPI extension can only be invoked to process Shibboleth protocol requests."
761 "Make sure the mapped file extension doesn't match actual content.");
763 virtual void* returnOK(void) { return (void*) HSE_STATUS_SUCCESS; }
765 // Not used in the extension.
766 virtual void clearHeader(const string &name) { throw runtime_error("clearHeader not implemented"); }
767 virtual void setHeader(const string &name, const string &value) { throw runtime_error("setHeader not implemented"); }
768 virtual string getHeader(const string &name) { throw runtime_error("getHeader not implemented"); }
769 virtual void setRemoteUser(const string &user) { throw runtime_error("setRemoteUser not implemented"); }
770 virtual string getRemoteUser(void) { throw runtime_error("getRemoteUser not implemented"); }
773 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
776 const IApplication* application=NULL;
778 ostringstream threadid;
779 threadid << "[" << getpid() << "] isapi_shib_extension" << '\0';
780 saml::NDC ndc(threadid.str().c_str());
782 // Determine web site number. This can't really fail, I don't think.
784 GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
786 // Match site instance to host name, skip if no match.
787 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
788 if (map_i==g_Sites.end())
789 return WriteClientError(lpECB, "Shibboleth Extension not configured for this web site.");
791 ShibTargetIsapiE ste(lpECB, map_i->second);
792 pair<bool,void*> res = ste.doHandler();
793 if (res.first) return (DWORD)res.second;
795 return WriteClientError(lpECB, "Shibboleth Extension failed to process request");
799 return WriteClientError(lpECB,"Out of Memory");
802 if (e==ERROR_NO_DATA)
803 return WriteClientError(lpECB,"A required variable or header was empty.");
805 return WriteClientError(lpECB,"Server detected unexpected IIS error.");
809 return WriteClientError(lpECB,"Server caught an unknown exception.");
813 // If we get here we've got an error.
814 return HSE_STATUS_ERROR;