2 * Copyright 2001-2009 Internet2
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 /* isapi_shib.cpp - Shibboleth ISAPI filter
23 #include "config_win32.h"
26 #include <saml/saml.h>
27 #include <shib/shib.h>
28 #include <shib/shib-threads.h>
29 #include <shib-target/shib-target.h>
42 using namespace shibboleth;
43 using namespace shibtarget;
47 static const XMLCh name[] = { chLatin_n, chLatin_a, chLatin_m, chLatin_e, chNull };
48 static const XMLCh port[] = { chLatin_p, chLatin_o, chLatin_r, chLatin_t, chNull };
49 static const XMLCh sslport[] = { chLatin_s, chLatin_s, chLatin_l, chLatin_p, chLatin_o, chLatin_r, chLatin_t, chNull };
50 static const XMLCh scheme[] = { chLatin_s, chLatin_c, chLatin_h, chLatin_e, chLatin_m, chLatin_e, chNull };
51 static const XMLCh id[] = { chLatin_i, chLatin_d, chNull };
52 static const XMLCh Implementation[] =
53 { 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 };
54 static const XMLCh ISAPI[] = { chLatin_I, chLatin_S, chLatin_A, chLatin_P, chLatin_I, chNull };
55 static const XMLCh Alias[] = { chLatin_A, chLatin_l, chLatin_i, chLatin_a, chLatin_s, chNull };
56 static const XMLCh normalizeRequest[] =
57 { chLatin_n, chLatin_o, chLatin_r, chLatin_m, chLatin_a, chLatin_l, chLatin_i, chLatin_z, chLatin_e,
58 chLatin_R, chLatin_e, chLatin_q, chLatin_u, chLatin_e, chLatin_s, chLatin_t, chNull
60 static const XMLCh safeHeaderNames[] =
61 { chLatin_s, chLatin_a, chLatin_f, chLatin_e, chLatin_H, chLatin_e, chLatin_a, chLatin_d, chLatin_e, chLatin_r,
62 chLatin_N, chLatin_a, chLatin_m, chLatin_e, chLatin_s, chNull
64 static const XMLCh Site[] = { chLatin_S, chLatin_i, chLatin_t, chLatin_e, chNull };
67 site_t(const DOMElement* e)
69 auto_ptr_char n(e->getAttributeNS(NULL,name));
70 auto_ptr_char s(e->getAttributeNS(NULL,scheme));
71 auto_ptr_char p(e->getAttributeNS(NULL,port));
72 auto_ptr_char p2(e->getAttributeNS(NULL,sslport));
73 if (n.get()) m_name=n.get();
74 if (s.get()) m_scheme=s.get();
75 if (p.get()) m_port=p.get();
76 if (p2.get()) m_sslport=p2.get();
77 DOMNodeList* nlist=e->getElementsByTagNameNS(shibtarget::XML::SHIBTARGET_NS,Alias);
78 for (int i=0; nlist && i<nlist->getLength(); i++) {
79 if (nlist->item(i)->hasChildNodes()) {
80 auto_ptr_char alias(nlist->item(i)->getFirstChild()->getNodeValue());
81 m_aliases.insert(alias.get());
85 string m_scheme,m_port,m_sslport,m_name;
86 set<string> m_aliases;
90 ShibTargetConfig* g_Config = NULL;
91 map<string,site_t> g_Sites;
92 bool g_bNormalizeRequest = true;
93 string g_unsetHeaderValue,g_spoofKey;
94 set<string> g_allowedSchemes;
95 bool g_checkSpoofing = true;
96 bool g_catchAll = true;
97 bool g_bSafeHeaderNames = false;
101 LPCSTR lpUNCServerName,
107 LPCSTR messages[] = {message, NULL};
109 HANDLE hElog = RegisterEventSource(lpUNCServerName, "Shibboleth ISAPI Filter");
110 BOOL res = ReportEvent(hElog, wType, 0, dwEventID, lpUserSid, 1, 0, messages, NULL);
111 return (DeregisterEventSource(hElog) && res);
114 extern "C" __declspec(dllexport) BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID)
116 if (fdwReason==DLL_PROCESS_ATTACH)
121 extern "C" BOOL WINAPI GetExtensionVersion(HSE_VERSION_INFO* pVer)
128 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
129 "Extension mode startup not possible, is the DLL loaded as a filter?");
133 pVer->dwExtensionVersion=HSE_VERSION;
134 strncpy(pVer->lpszExtensionDesc,"Shibboleth ISAPI Extension",HSE_MAX_EXT_DLL_NAME_LEN-1);
138 extern "C" BOOL WINAPI TerminateExtension(DWORD)
140 return TRUE; // cleanup should happen when filter unloads
143 extern "C" BOOL WINAPI GetFilterVersion(PHTTP_FILTER_VERSION pVer)
148 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
149 "Reentrant filter initialization, ignoring...");
154 LPCSTR schemadir=getenv("SHIBSCHEMAS");
156 schemadir=SHIB_SCHEMAS;
157 LPCSTR config=getenv("SHIBCONFIG");
160 g_Config=&ShibTargetConfig::getConfig();
161 g_Config->setFeatures(
162 ShibTargetConfig::Listener |
163 ShibTargetConfig::Metadata |
164 ShibTargetConfig::AAP |
165 ShibTargetConfig::RequestMapper |
166 ShibTargetConfig::LocalExtensions |
167 ShibTargetConfig::Logging
169 if (!g_Config->init(schemadir)) {
171 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
172 "Filter startup failed during library initialization, check native log for help.");
175 else if (!g_Config->load(config)) {
177 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
178 "Filter startup failed to load configuration, check native log for help.");
182 // Access the implementation-specifics for site mappings.
183 IConfig* conf=g_Config->getINI();
185 const IPropertySet* props=conf->getPropertySet("Local");
187 pair<bool,bool> flag=props->getBool("checkSpoofing");
188 g_checkSpoofing = !flag.first || flag.second;
189 flag=props->getBool("catchAll");
190 g_catchAll = !flag.first || flag.second;
192 pair<bool,const char*> str=props->getString("unsetHeaderValue");
194 g_unsetHeaderValue = str.second;
196 str=props->getString("allowedSchemes");
198 string schemes=str.second;
200 for (unsigned int i=0; i < schemes.length(); i++) {
201 if (schemes.at(i)==' ') {
202 g_allowedSchemes.insert(schemes.substr(j, i-j));
206 g_allowedSchemes.insert(schemes.substr(j, schemes.length()-j));
209 if (g_checkSpoofing) {
210 str = props->getString("spoofKey");
212 g_spoofKey = str.second;
214 LogEvent(NULL, EVENTLOG_WARNING_TYPE, 2100, NULL,
215 "Filter generating a pseudorandom anti-spoofing key, consider setting spoofKey yourself.");
217 ostringstream keystr;
218 keystr << rand() << rand() << rand() << rand();
219 g_spoofKey = keystr.str();
223 const DOMElement* impl=saml::XML::getFirstChildElement(
224 props->getElement(),shibtarget::XML::SHIBTARGET_NS,Implementation
226 if (impl && (impl=saml::XML::getFirstChildElement(impl,shibtarget::XML::SHIBTARGET_NS,ISAPI))) {
227 const XMLCh* ch=impl->getAttributeNS(NULL,normalizeRequest);
228 g_bNormalizeRequest=(!ch || !*ch || *ch==chDigit_1 || *ch==chLatin_t);
229 ch=impl->getAttributeNS(NULL,safeHeaderNames);
230 g_bSafeHeaderNames=(ch && (*ch==chDigit_1 || *ch==chLatin_t));
231 impl=saml::XML::getFirstChildElement(impl,shibtarget::XML::SHIBTARGET_NS,Site);
233 auto_ptr_char id(impl->getAttributeNS(NULL,id));
235 g_Sites.insert(pair<string,site_t>(id.get(),site_t(impl)));
236 impl=saml::XML::getNextSiblingElement(impl,shibtarget::XML::SHIBTARGET_NS,Site);
240 if (g_allowedSchemes.empty()) {
241 g_allowedSchemes.insert("https");
242 g_allowedSchemes.insert("http");
246 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Filter startup failed with an exception.");
250 pVer->dwFilterVersion=HTTP_FILTER_REVISION;
251 strncpy(pVer->lpszFilterDesc,"Shibboleth ISAPI Filter",SF_MAX_FILTER_DESC_LEN);
252 pVer->dwFlags=(SF_NOTIFY_ORDER_HIGH |
253 SF_NOTIFY_SECURE_PORT |
254 SF_NOTIFY_NONSECURE_PORT |
255 SF_NOTIFY_PREPROC_HEADERS |
257 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter initialized...");
261 extern "C" BOOL WINAPI TerminateFilter(DWORD)
264 g_Config->shutdown();
266 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter shut down...");
270 /* Next up, some suck-free versions of various APIs.
272 You DON'T require people to guess the buffer size and THEN tell them the right size.
273 Returning an LPCSTR is apparently way beyond their ken. Not to mention the fact that
274 constant strings aren't typed as such, making it just that much harder. These versions
275 are now updated to use a special growable buffer object, modeled after the standard
276 string class. The standard string won't work because they left out the option to
277 pre-allocate a non-constant buffer.
283 dynabuf() { bufptr=NULL; buflen=0; }
284 dynabuf(size_t s) { bufptr=new char[buflen=s]; *bufptr=0; }
285 ~dynabuf() { delete[] bufptr; }
286 size_t length() const { return bufptr ? strlen(bufptr) : 0; }
287 size_t size() const { return buflen; }
288 bool empty() const { return length()==0; }
289 void reserve(size_t s, bool keep=false);
290 void erase() { if (bufptr) memset(bufptr,0,buflen); }
291 operator char*() { return bufptr; }
292 bool operator ==(const char* s) const;
293 bool operator !=(const char* s) const { return !(*this==s); }
299 void dynabuf::reserve(size_t s, bool keep)
306 p[buflen]=bufptr[buflen];
312 bool dynabuf::operator==(const char* s) const
314 if (buflen==NULL || s==NULL)
315 return (buflen==NULL && s==NULL);
317 return strcmp(bufptr,s)==0;
320 void GetServerVariable(PHTTP_FILTER_CONTEXT pfc, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
321 throw (bad_alloc, DWORD)
327 while (!pfc->GetServerVariable(pfc,lpszVariable,s,&size))
329 // Grumble. Check the error.
330 DWORD e=GetLastError();
331 if (e==ERROR_INSUFFICIENT_BUFFER)
336 if (bRequired && s.empty())
340 void GetServerVariable(LPEXTENSION_CONTROL_BLOCK lpECB, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
341 throw (bad_alloc, DWORD)
347 while (!lpECB->GetServerVariable(lpECB->ConnID,lpszVariable,s,&size))
349 // Grumble. Check the error.
350 DWORD e=GetLastError();
351 if (e==ERROR_INSUFFICIENT_BUFFER)
356 if (bRequired && s.empty())
360 void GetHeader(PHTTP_FILTER_PREPROC_HEADERS pn, PHTTP_FILTER_CONTEXT pfc,
361 LPSTR lpszName, dynabuf& s, DWORD size=80, bool bRequired=true)
362 throw (bad_alloc, DWORD)
368 while (!pn->GetHeader(pfc,lpszName,s,&size))
370 // Grumble. Check the error.
371 DWORD e=GetLastError();
372 if (e==ERROR_INSUFFICIENT_BUFFER)
377 if (bRequired && s.empty())
381 /****************************************************************************/
384 class ShibTargetIsapiF : public ShibTarget
386 PHTTP_FILTER_CONTEXT m_pfc;
387 PHTTP_FILTER_PREPROC_HEADERS m_pn;
392 void checkString(const string& s, const char* msg) {
393 string::const_iterator e = s.end();
394 for (string::const_iterator i=s.begin(); i!=e; ++i) {
396 throw FatalProfileException(msg);
401 ShibTargetIsapiF(PHTTP_FILTER_CONTEXT pfc, PHTTP_FILTER_PREPROC_HEADERS pn, const site_t& site)
402 : m_pfc(pfc), m_pn(pn), m_allhttp(4096), m_firsttime(true) {
404 // URL path always come from IIS.
406 GetHeader(pn,pfc,"url",url,256,false);
408 // Port may come from IIS or from site def.
410 if (!g_bNormalizeRequest || (pfc->fIsSecurePort && site.m_sslport.empty()) || (!pfc->fIsSecurePort && site.m_port.empty()))
411 GetServerVariable(pfc,"SERVER_PORT",port,10);
412 else if (pfc->fIsSecurePort) {
413 strncpy(port,site.m_sslport.c_str(),10);
414 static_cast<char*>(port)[10]=0;
417 strncpy(port,site.m_port.c_str(),10);
418 static_cast<char*>(port)[10]=0;
421 // Scheme may come from site def or be derived from IIS.
422 const char* scheme=site.m_scheme.c_str();
423 if (!scheme || !*scheme || !g_bNormalizeRequest)
424 scheme=pfc->fIsSecurePort ? "https" : "http";
426 // Get the rest of the server variables.
427 dynabuf remote_addr(16),method(5),content_type(32),hostname(32);
428 GetServerVariable(pfc,"SERVER_NAME",hostname,32);
429 GetServerVariable(pfc,"REMOTE_ADDR",remote_addr,16);
430 GetServerVariable(pfc,"REQUEST_METHOD",method,5,false);
431 GetServerVariable(pfc,"CONTENT_TYPE",content_type,32,false);
433 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
434 const char* host=hostname;
435 if (site.m_name!=host && site.m_aliases.find(host)==site.m_aliases.end())
436 host=site.m_name.c_str();
438 init(scheme, host, atoi(port), url, content_type, remote_addr, method);
440 if (!g_spoofKey.empty()) {
441 GetHeader(pn, pfc, "ShibSpoofCheck:", url, 32, false);
442 if (!url.empty() && g_spoofKey == (char*)url)
447 log(LogLevelDebug, "ISAPI filter running more than once");
449 ~ShibTargetIsapiF() {}
451 virtual void log(ShibLogLevel level, const string &msg) {
452 ShibTarget::log(level,msg);
454 virtual string getCookies() const {
456 GetHeader(m_pn, m_pfc, "Cookie:", buf, 128, false);
457 return buf.empty() ? "" : buf;
459 string makeSafeHeader(const char* rawname) const {
461 for (; *rawname; ++rawname) {
462 if (isalnum(*rawname))
467 virtual void clearHeader(const string &name) {
468 if (g_checkSpoofing && m_firsttime) {
469 if (m_allhttp.empty())
470 GetServerVariable(m_pfc,"ALL_HTTP",m_allhttp,4096);
472 // Map to the expected CGI variable name.
473 string transformed("HTTP_");
474 if (g_bSafeHeaderNames) {
475 string temp = makeSafeHeader(name.c_str());
476 for (const char* pch = temp.c_str(); *pch; ++pch) {
478 transformed += toupper(*pch);
482 for (const char* pch = name.c_str(); *pch; ++pch)
483 transformed += (isalnum(*pch) ? toupper(*pch) : '_');
487 if (strstr(m_allhttp, transformed.c_str()))
488 throw SAMLException("Attempt to spoof header ($1) was detected.", params(1, transformed.c_str()));
490 if (g_bSafeHeaderNames) {
491 string hdr = makeSafeHeader(name.c_str());
492 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), const_cast<char*>(g_unsetHeaderValue.c_str()));
494 else if (name == "REMOTE_USER") {
495 m_pn->SetHeader(m_pfc, "remote-user:", const_cast<char*>(g_unsetHeaderValue.c_str()));
496 m_pn->SetHeader(m_pfc, "remote_user:", const_cast<char*>(g_unsetHeaderValue.c_str()));
499 string hdr = name + ':';
500 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), const_cast<char*>(g_unsetHeaderValue.c_str()));
503 virtual void setHeader(const string &name, const string &value) {
504 string hdr = g_bSafeHeaderNames ? makeSafeHeader(name.c_str()) : (name + ':');
505 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), const_cast<char*>(value.c_str()));
507 virtual string getHeader(const string &name) {
508 string hdr = g_bSafeHeaderNames ? makeSafeHeader(name.c_str()) : (name + ':');
510 GetHeader(m_pn, m_pfc, const_cast<char*>(hdr.c_str()), buf, 1024, false);
511 return string(buf.empty() ? "" : buf);
513 virtual void setRemoteUser(const string &user) {
514 setHeader(string("remote-user"), user);
515 if (m_pfc->pFilterContext) {
517 m_pfc->pFilterContext = NULL;
518 else if (m_pfc->pFilterContext = m_pfc->AllocMem(m_pfc, sizeof(char) * (user.length() + 1), NULL))
519 strcpy(reinterpret_cast<char*>(m_pfc->pFilterContext), user.c_str());
522 virtual string getRemoteUser(void) {
523 return getHeader(string("remote-user"));
525 virtual void* sendPage(
528 const string& content_type="text/html",
529 const Iterator<header_t>& headers=EMPTY(header_t)) {
530 checkString(content_type, "Detected control character in a response header.");
531 string hdr = string ("Connection: close\r\nContent-type: ") + content_type + "\r\n";
532 while (headers.hasNext()) {
533 const header_t& h=headers.next();
534 checkString(h.first, "Detected control character in a response header.");
535 checkString(h.second, "Detected control character in a response header.");
536 hdr += h.first + ": " + h.second + "\r\n";
539 const char* codestr="200 OK";
541 case 403: codestr="403 Forbidden"; break;
542 case 404: codestr="404 Not Found"; break;
543 case 500: codestr="500 Server Error"; break;
545 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, (void*)codestr, (DWORD)hdr.c_str(), 0);
546 DWORD resplen = msg.size();
547 m_pfc->WriteClient(m_pfc, (LPVOID)msg.c_str(), &resplen, 0);
548 return (void*)SF_STATUS_REQ_FINISHED;
550 virtual void* sendRedirect(const string& url) {
551 checkString(url, "Detected control character in an attempted redirect.");
552 if (g_allowedSchemes.find(url.substr(0, url.find(':'))) == g_allowedSchemes.end())
553 throw FatalProfileException("Invalid scheme in attempted redirect.");
554 // XXX: Don't support the httpRedirect option, yet.
555 string hdrs=m_cookie + string("Location: ") + url + "\r\n"
556 "Content-Type: text/html\r\n"
557 "Content-Length: 40\r\n"
558 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
559 "Cache-Control: private,no-store,no-cache\r\n\r\n";
560 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER,
561 "302 Please Wait", (DWORD)hdrs.c_str(), 0);
562 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
564 m_pfc->WriteClient(m_pfc, (LPVOID)redmsg, &resplen, 0);
565 return reinterpret_cast<void*>(SF_STATUS_REQ_FINISHED);
567 // XXX: We might not ever hit the 'decline' status in this filter.
568 //virtual void* returnDecline(void) { }
569 virtual void* returnOK(void) { return (void*) SF_STATUS_REQ_NEXT_NOTIFICATION; }
571 // The filter never processes the POST, so stub these methods.
572 virtual void setCookie(const string &name, const string &value) {
573 // Set the cookie for later. Use it during the redirect.
574 m_cookie += "Set-Cookie: " + name + "=" + value + "\r\n";
576 virtual string getArgs(void) { throw runtime_error("getArgs not implemented"); }
577 virtual string getPostData(void) { throw runtime_error("getPostData not implemented"); }
580 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const char* msg)
582 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
583 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
584 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)ctype,0);
585 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Filter Error</TITLE></HEAD><BODY>"
586 "<H1>Shibboleth Filter Error</H1>";
587 DWORD resplen=strlen(xmsg);
588 pfc->WriteClient(pfc,(LPVOID)xmsg,&resplen,0);
590 pfc->WriteClient(pfc,(LPVOID)msg,&resplen,0);
591 static const char* xmsg2="</BODY></HTML>";
592 resplen=strlen(xmsg2);
593 pfc->WriteClient(pfc,(LPVOID)xmsg2,&resplen,0);
594 return SF_STATUS_REQ_FINISHED;
597 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
599 // Is this a log notification?
600 if (notificationType==SF_NOTIFY_LOG) {
601 if (pfc->pFilterContext)
602 ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=reinterpret_cast<char*>(pfc->pFilterContext);
603 return SF_STATUS_REQ_NEXT_NOTIFICATION;
606 PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
608 // Determine web site number. This can't really fail, I don't think.
610 GetServerVariable(pfc,"INSTANCE_ID",buf,10);
612 // Match site instance to host name, skip if no match.
613 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
614 if (map_i==g_Sites.end())
615 return SF_STATUS_REQ_NEXT_NOTIFICATION;
617 ostringstream threadid;
618 threadid << "[" << getpid() << "] isapi_shib" << '\0';
619 saml::NDC ndc(threadid.str().c_str());
621 ShibTargetIsapiF stf(pfc, pn, map_i->second);
623 // "false" because we don't override the Shib settings
624 pair<bool,void*> res = stf.doCheckAuthN();
625 if (!g_spoofKey.empty())
626 pn->SetHeader(pfc, "ShibSpoofCheck:", const_cast<char*>(g_spoofKey.c_str()));
627 if (res.first) return (DWORD)res.second;
629 // "false" because we don't override the Shib settings
630 res = stf.doExportAssertions();
631 if (res.first) return (DWORD)res.second;
633 res = stf.doCheckAuthZ();
634 if (res.first) return (DWORD)res.second;
636 return SF_STATUS_REQ_NEXT_NOTIFICATION;
639 return WriteClientError(pfc,"Out of Memory");
642 if (e==ERROR_NO_DATA)
643 return WriteClientError(pfc,"A required variable or header was empty.");
645 return WriteClientError(pfc,"Shibboleth Filter detected unexpected IIS error.");
647 catch (exception& e) {
648 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
649 return WriteClientError(pfc,"Shibboleth Filter caught an exception, ask administrator to check Event Log for details.");
653 return WriteClientError(pfc,"Shibboleth Filter caught an unknown exception.");
657 return WriteClientError(pfc,"Shibboleth Filter reached unreachable code, save my walrus!");
661 /****************************************************************************/
664 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* msg)
666 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
667 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
668 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
669 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Error</TITLE></HEAD><BODY><H1>Shibboleth Error</H1>";
670 DWORD resplen=strlen(xmsg);
671 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg,&resplen,HSE_IO_SYNC);
673 lpECB->WriteClient(lpECB->ConnID,(LPVOID)msg,&resplen,HSE_IO_SYNC);
674 static const char* xmsg2="</BODY></HTML>";
675 resplen=strlen(xmsg2);
676 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg2,&resplen,HSE_IO_SYNC);
677 return HSE_STATUS_SUCCESS;
681 class ShibTargetIsapiE : public ShibTarget
683 LPEXTENSION_CONTROL_BLOCK m_lpECB;
686 void checkString(const string& s, const char* msg) {
687 string::const_iterator e = s.end();
688 for (string::const_iterator i=s.begin(); i!=e; ++i) {
690 throw FatalProfileException(msg);
695 ShibTargetIsapiE(LPEXTENSION_CONTROL_BLOCK lpECB, const site_t& site) {
697 GetServerVariable(lpECB,"HTTPS",ssl,5);
698 bool SSL=(ssl=="on" || ssl=="ON");
700 // URL path always come from IIS.
702 GetServerVariable(lpECB,"URL",url,255);
704 // Port may come from IIS or from site def.
706 if (!g_bNormalizeRequest || (SSL && site.m_sslport.empty()) || (!SSL && site.m_port.empty()))
707 GetServerVariable(lpECB,"SERVER_PORT",port,10);
709 strncpy(port,site.m_sslport.c_str(),10);
710 static_cast<char*>(port)[10]=0;
713 strncpy(port,site.m_port.c_str(),10);
714 static_cast<char*>(port)[10]=0;
717 // Scheme may come from site def or be derived from IIS.
718 const char* scheme=site.m_scheme.c_str();
719 if (!scheme || !*scheme || !g_bNormalizeRequest) {
720 scheme = SSL ? "https" : "http";
723 // Get the other server variables.
724 dynabuf remote_addr(16),hostname(32);
725 GetServerVariable(lpECB, "REMOTE_ADDR", remote_addr, 16);
726 GetServerVariable(lpECB, "SERVER_NAME", hostname, 32);
728 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
729 const char* host=hostname;
730 if (site.m_name!=host && site.m_aliases.find(host)==site.m_aliases.end())
731 host=site.m_name.c_str();
734 * IIS screws us over on PATH_INFO (the hits keep on coming). We need to figure out if
735 * the server is set up for proper PATH_INFO handling, or "IIS sucks rabid weasels mode",
736 * which is the default. No perfect way to tell, but we can take a good guess by checking
737 * whether the URL is a substring of the PATH_INFO:
739 * e.g. for /Shibboleth.sso/SAML/POST
741 * Bad mode (default):
742 * URL: /Shibboleth.sso
743 * PathInfo: /Shibboleth.sso/SAML/POST
746 * URL: /Shibboleth.sso
747 * PathInfo: /SAML/POST
752 // Clearly we're only in bad mode if path info exists at all.
753 if (lpECB->lpszPathInfo && *(lpECB->lpszPathInfo)) {
754 if (strstr(lpECB->lpszPathInfo,url))
755 // Pretty good chance we're in bad mode, unless the PathInfo repeats the path itself.
756 fullurl=lpECB->lpszPathInfo;
759 fullurl+=lpECB->lpszPathInfo;
766 // For consistency with Apache, let's add the query string.
767 if (lpECB->lpszQueryString && *(lpECB->lpszQueryString)) {
769 fullurl+=lpECB->lpszQueryString;
771 init(scheme, host, atoi(port), fullurl.c_str(), lpECB->lpszContentType, remote_addr, lpECB->lpszMethod);
775 ~ShibTargetIsapiE() { }
777 virtual void log(ShibLogLevel level, const string &msg) {
778 ShibTarget::log(level,msg);
780 virtual string getCookies() const {
782 GetServerVariable(m_lpECB, "HTTP_COOKIE", buf, 128, false);
783 return buf.empty() ? "" : buf;
785 virtual void setCookie(const string &name, const string &value) {
786 // Set the cookie for later. Use it during the redirect.
787 m_cookie += "Set-Cookie: " + name + "=" + value + "\r\n";
789 virtual string getArgs(void) {
790 return string(m_lpECB->lpszQueryString ? m_lpECB->lpszQueryString : "");
792 virtual string getPostData(void) {
793 if (m_lpECB->cbTotalBytes > 1024*1024) // 1MB?
794 throw FatalProfileException("Blocked too-large a submission to profile endpoint.");
795 else if (m_lpECB->cbTotalBytes > m_lpECB->cbAvailable) {
796 string cgistr(reinterpret_cast<char*>(m_lpECB->lpbData),m_lpECB->cbAvailable);
798 DWORD datalen=m_lpECB->cbTotalBytes - m_lpECB->cbAvailable;
801 BOOL ret = m_lpECB->ReadClient(m_lpECB->ConnID, buf, &buflen);
803 throw FatalProfileException("Error reading profile submission from browser.");
804 cgistr.append(buf, buflen);
810 return string(reinterpret_cast<char*>(m_lpECB->lpbData),m_lpECB->cbAvailable);
813 virtual void* sendPage(
816 const string& content_type="text/html",
817 const Iterator<header_t>& headers=EMPTY(header_t)) {
818 checkString(content_type, "Detected control character in a response header.");
819 string hdr = m_cookie + "Connection: close\r\nContent-type: " + content_type + "\r\n";
820 for (int k = 0; k < headers.size(); k++) {
821 checkString(headers[k].first, "Detected control character in a response header.");
822 checkString(headers[k].second, "Detected control character in a response header.");
823 hdr += headers[k].first + ": " + headers[k].second + "\r\n";
826 const char* codestr="200 OK";
828 case 403: codestr="403 Forbidden"; break;
829 case 404: codestr="404 Not Found"; break;
830 case 500: codestr="500 Server Error"; break;
832 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, (void*)codestr, 0, (LPDWORD)hdr.c_str());
833 DWORD resplen = msg.size();
834 m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)msg.c_str(), &resplen, HSE_IO_SYNC);
835 return (void*)HSE_STATUS_SUCCESS;
837 virtual void* sendRedirect(const string& url) {
838 // XXX: Don't support the httpRedirect option, yet.
839 checkString(url, "Detected control character in an attempted redirect.");
840 if (g_allowedSchemes.find(url.substr(0, url.find(':'))) == g_allowedSchemes.end())
841 throw FatalProfileException("Invalid scheme in attempted redirect.");
842 string hdrs = m_cookie + "Location: " + url + "\r\n"
843 "Content-Type: text/html\r\n"
844 "Content-Length: 40\r\n"
845 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
846 "Cache-Control: private,no-store,no-cache\r\n\r\n";
847 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER,
848 "302 Moved", 0, (LPDWORD)hdrs.c_str());
849 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
851 m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)redmsg, &resplen, HSE_IO_SYNC);
852 return (void*)HSE_STATUS_SUCCESS;
854 // Decline happens in the POST processor if this isn't the shire url
855 // Note that it can also happen with HTAccess, but we don't support that, yet.
856 virtual void* returnDecline(void) {
858 WriteClientError(m_lpECB, "ISAPI extension can only be invoked to process Shibboleth protocol requests."
859 "Make sure the mapped file extension doesn't match actual content.");
861 virtual void* returnOK(void) { return (void*) HSE_STATUS_SUCCESS; }
863 // Not used in the extension.
864 virtual void clearHeader(const string &name) { throw runtime_error("clearHeader not implemented"); }
865 virtual void setHeader(const string &name, const string &value) { throw runtime_error("setHeader not implemented"); }
866 virtual string getHeader(const string &name) { throw runtime_error("getHeader not implemented"); }
867 virtual void setRemoteUser(const string &user) { throw runtime_error("setRemoteUser not implemented"); }
868 virtual string getRemoteUser(void) { throw runtime_error("getRemoteUser not implemented"); }
871 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
874 ostringstream threadid;
875 threadid << "[" << getpid() << "] isapi_shib_extension" << '\0';
876 saml::NDC ndc(threadid.str().c_str());
878 // Determine web site number. This can't really fail, I don't think.
880 GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
882 // Match site instance to host name, skip if no match.
883 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
884 if (map_i==g_Sites.end())
885 return WriteClientError(lpECB, "Shibboleth Extension not configured for this web site.");
887 ShibTargetIsapiE ste(lpECB, map_i->second);
888 pair<bool,void*> res = ste.doHandler();
889 if (res.first) return (DWORD)res.second;
891 return WriteClientError(lpECB, "Shibboleth Extension failed to process request");
895 return WriteClientError(lpECB,"Out of Memory");
898 if (e==ERROR_NO_DATA)
899 return WriteClientError(lpECB,"A required variable or header was empty.");
901 return WriteClientError(lpECB,"Server detected unexpected IIS error.");
903 catch (exception& e) {
904 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
905 return WriteClientError(lpECB,"Shibboleth Extension caught an exception, check Event Log for details.");
909 return WriteClientError(lpECB,"Shibboleth Extension caught an unknown exception.");
913 // If we get here we've got an error.
914 return HSE_STATUS_ERROR;