2 * Copyright 2001-2010 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.
20 * Shibboleth ISAPI filter.
24 #include "config_win32.h"
26 #define _CRT_NONSTDC_NO_DEPRECATE 1
27 #define _CRT_SECURE_NO_DEPRECATE 1
30 #include <shibsp/exceptions.h>
31 #include <shibsp/AbstractSPRequest.h>
32 #include <shibsp/SPConfig.h>
33 #include <shibsp/ServiceProvider.h>
40 #include <xmltooling/unicode.h>
41 #include <xmltooling/XMLToolingConfig.h>
42 #include <xmltooling/util/NDC.h>
43 #include <xmltooling/util/XMLConstants.h>
44 #include <xmltooling/util/XMLHelper.h>
45 #include <xercesc/util/Base64.hpp>
46 #include <xercesc/util/XMLUniDefs.hpp>
52 using namespace shibsp;
53 using namespace xmltooling;
54 using namespace xercesc;
59 static const XMLCh path[] = UNICODE_LITERAL_4(p,a,t,h);
60 static const XMLCh validate[] = UNICODE_LITERAL_8(v,a,l,i,d,a,t,e);
61 static const XMLCh name[] = UNICODE_LITERAL_4(n,a,m,e);
62 static const XMLCh port[] = UNICODE_LITERAL_4(p,o,r,t);
63 static const XMLCh sslport[] = UNICODE_LITERAL_7(s,s,l,p,o,r,t);
64 static const XMLCh scheme[] = UNICODE_LITERAL_6(s,c,h,e,m,e);
65 static const XMLCh id[] = UNICODE_LITERAL_2(i,d);
66 static const XMLCh Alias[] = UNICODE_LITERAL_5(A,l,i,a,s);
67 static const XMLCh Site[] = UNICODE_LITERAL_4(S,i,t,e);
70 site_t(const DOMElement* e)
72 auto_ptr_char n(e->getAttributeNS(nullptr,name));
73 auto_ptr_char s(e->getAttributeNS(nullptr,scheme));
74 auto_ptr_char p(e->getAttributeNS(nullptr,port));
75 auto_ptr_char p2(e->getAttributeNS(nullptr,sslport));
76 if (n.get()) m_name=n.get();
77 if (s.get()) m_scheme=s.get();
78 if (p.get()) m_port=p.get();
79 if (p2.get()) m_sslport=p2.get();
80 e = XMLHelper::getFirstChildElement(e, Alias);
82 if (e->hasChildNodes()) {
83 auto_ptr_char alias(e->getFirstChild()->getNodeValue());
84 m_aliases.insert(alias.get());
86 e = XMLHelper::getNextSiblingElement(e, Alias);
89 string m_scheme,m_port,m_sslport,m_name;
90 set<string> m_aliases;
94 SPConfig* g_Config = nullptr;
95 map<string,site_t> g_Sites;
96 bool g_bNormalizeRequest = true;
97 string g_unsetHeaderValue,g_spoofKey;
98 bool g_checkSpoofing = true;
99 bool g_catchAll = false;
100 bool g_bSafeHeaderNames = false;
101 vector<string> g_NoCerts;
105 LPCSTR lpUNCServerName,
111 LPCSTR messages[] = {message, nullptr};
113 HANDLE hElog = RegisterEventSource(lpUNCServerName, "Shibboleth ISAPI Filter");
114 BOOL res = ReportEvent(hElog, wType, 0, dwEventID, lpUserSid, 1, 0, messages, nullptr);
115 return (DeregisterEventSource(hElog) && res);
118 void _my_invalid_parameter_handler(
119 const wchar_t * expression,
120 const wchar_t * function,
121 const wchar_t * file,
129 extern "C" __declspec(dllexport) BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID)
131 if (fdwReason==DLL_PROCESS_ATTACH)
136 extern "C" BOOL WINAPI GetExtensionVersion(HSE_VERSION_INFO* pVer)
142 LogEvent(nullptr, EVENTLOG_ERROR_TYPE, 2100, nullptr,
143 "Extension mode startup not possible, is the DLL loaded as a filter?");
147 pVer->dwExtensionVersion=HSE_VERSION;
148 strncpy(pVer->lpszExtensionDesc,"Shibboleth ISAPI Extension",HSE_MAX_EXT_DLL_NAME_LEN-1);
152 extern "C" BOOL WINAPI TerminateExtension(DWORD)
154 return TRUE; // cleanup should happen when filter unloads
157 extern "C" BOOL WINAPI GetFilterVersion(PHTTP_FILTER_VERSION pVer)
162 LogEvent(nullptr, EVENTLOG_ERROR_TYPE, 2100, nullptr,
163 "Reentrant filter initialization, ignoring...");
167 g_Config=&SPConfig::getConfig();
168 g_Config->setFeatures(
171 SPConfig::RequestMapping |
172 SPConfig::InProcess |
176 if (!g_Config->init()) {
178 LogEvent(nullptr, EVENTLOG_ERROR_TYPE, 2100, nullptr,
179 "Filter startup failed during library initialization, check native log for help.");
184 if (!g_Config->instantiate(nullptr, true))
185 throw runtime_error("unknown error");
187 catch (exception& ex) {
190 LogEvent(nullptr, EVENTLOG_ERROR_TYPE, 2100, nullptr, ex.what());
191 LogEvent(nullptr, EVENTLOG_ERROR_TYPE, 2100, nullptr,
192 "Filter startup failed to load configuration, check native log for details.");
196 // Access implementation-specifics and site mappings.
197 ServiceProvider* sp=g_Config->getServiceProvider();
199 const PropertySet* props=sp->getPropertySet("InProcess");
201 pair<bool,bool> flag=props->getBool("checkSpoofing");
202 g_checkSpoofing = !flag.first || flag.second;
203 flag=props->getBool("catchAll");
204 g_catchAll = flag.first && flag.second;
206 pair<bool,const char*> unsetValue=props->getString("unsetHeaderValue");
207 if (unsetValue.first)
208 g_unsetHeaderValue = unsetValue.second;
209 if (g_checkSpoofing) {
210 unsetValue = props->getString("spoofKey");
211 if (unsetValue.first)
212 g_spoofKey = unsetValue.second;
214 _invalid_parameter_handler old = _set_invalid_parameter_handler(_my_invalid_parameter_handler);
215 unsigned int randkey=0,randkey2=0,randkey3=0,randkey4=0;
216 if (rand_s(&randkey) == 0 && rand_s(&randkey2) == 0 && rand_s(&randkey3) == 0 && rand_s(&randkey4) == 0) {
217 _set_invalid_parameter_handler(old);
218 ostringstream keystr;
219 keystr << randkey << randkey2 << randkey3 << randkey4;
220 g_spoofKey = keystr.str();
223 _set_invalid_parameter_handler(old);
224 LogEvent(nullptr, EVENTLOG_ERROR_TYPE, 2100, nullptr,
225 "Filter failed to generate a random anti-spoofing key (if this is Windows 2000 set one manually).");
226 locker.assign(); // pops lock on SP config
234 props = props->getPropertySet("ISAPI");
236 flag = props->getBool("normalizeRequest");
237 g_bNormalizeRequest = !flag.first || flag.second;
238 flag = props->getBool("safeHeaderNames");
239 g_bSafeHeaderNames = flag.first && flag.second;
240 const DOMElement* child = XMLHelper::getFirstChildElement(props->getElement(),Site);
242 auto_ptr_char id(child->getAttributeNS(nullptr,id));
244 g_Sites.insert(pair<string,site_t>(id.get(),site_t(child)));
245 child=XMLHelper::getNextSiblingElement(child,Site);
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(nullptr, EVENTLOG_INFORMATION_TYPE, 7701, nullptr, "Filter initialized...");
261 extern "C" BOOL WINAPI TerminateFilter(DWORD)
266 LogEvent(nullptr, EVENTLOG_INFORMATION_TYPE, 7701, nullptr, "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=nullptr; 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==0 || s==nullptr)
315 return (buflen==0 && s==nullptr);
317 return strcmp(bufptr,s)==0;
320 void GetServerVariable(PHTTP_FILTER_CONTEXT pfc, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
326 while (!pfc->GetServerVariable(pfc,lpszVariable,s,&size)) {
327 // Grumble. Check the error.
328 DWORD e=GetLastError();
329 if (e==ERROR_INSUFFICIENT_BUFFER)
334 if (bRequired && s.empty())
338 void GetServerVariable(LPEXTENSION_CONTROL_BLOCK lpECB, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
344 while (!lpECB->GetServerVariable(lpECB->ConnID,lpszVariable,s,&size)) {
345 // Grumble. Check the error.
346 DWORD e=GetLastError();
347 if (e==ERROR_INSUFFICIENT_BUFFER)
352 if (bRequired && s.empty())
356 void GetHeader(PHTTP_FILTER_PREPROC_HEADERS pn, PHTTP_FILTER_CONTEXT pfc,
357 LPSTR lpszName, dynabuf& s, DWORD size=80, bool bRequired=true)
363 while (!pn->GetHeader(pfc,lpszName,s,&size)) {
364 // Grumble. Check the error.
365 DWORD e=GetLastError();
366 if (e==ERROR_INSUFFICIENT_BUFFER)
371 if (bRequired && s.empty())
375 /****************************************************************************/
378 class ShibTargetIsapiF : public AbstractSPRequest
380 PHTTP_FILTER_CONTEXT m_pfc;
381 PHTTP_FILTER_PREPROC_HEADERS m_pn;
382 multimap<string,string> m_headers;
384 string m_scheme,m_hostname;
385 mutable string m_remote_addr,m_content_type,m_method;
390 ShibTargetIsapiF(PHTTP_FILTER_CONTEXT pfc, PHTTP_FILTER_PREPROC_HEADERS pn, const site_t& site)
391 : AbstractSPRequest(SHIBSP_LOGCAT".ISAPI"), m_pfc(pfc), m_pn(pn), m_allhttp(4096), m_firsttime(true) {
393 // URL path always come from IIS.
395 GetHeader(pn,pfc,"url",var,256,false);
398 // Port may come from IIS or from site def.
399 if (!g_bNormalizeRequest || (pfc->fIsSecurePort && site.m_sslport.empty()) || (!pfc->fIsSecurePort && site.m_port.empty())) {
400 GetServerVariable(pfc,"SERVER_PORT",var,10);
403 else if (pfc->fIsSecurePort) {
404 m_port = atoi(site.m_sslport.c_str());
407 m_port = atoi(site.m_port.c_str());
410 // Scheme may come from site def or be derived from IIS.
411 m_scheme=site.m_scheme;
412 if (m_scheme.empty() || !g_bNormalizeRequest)
413 m_scheme=pfc->fIsSecurePort ? "https" : "http";
415 GetServerVariable(pfc,"SERVER_NAME",var,32);
417 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
419 if (site.m_name!=m_hostname && site.m_aliases.find(m_hostname)==site.m_aliases.end())
420 m_hostname=site.m_name;
422 if (!g_spoofKey.empty()) {
423 GetHeader(pn, pfc, "ShibSpoofCheck:", var, 32, false);
424 if (!var.empty() && g_spoofKey == (char*)var)
429 log(SPDebug, "ISAPI filter running more than once");
431 ~ShibTargetIsapiF() { }
433 const char* getScheme() const {
434 return m_scheme.c_str();
436 const char* getHostname() const {
437 return m_hostname.c_str();
439 int getPort() const {
442 const char* getQueryString() const {
443 const char* uri = getRequestURI();
444 uri = (uri ? strchr(uri, '?') : nullptr);
445 return uri ? (uri + 1) : nullptr;
447 const char* getMethod() const {
448 if (m_method.empty()) {
450 GetServerVariable(m_pfc,"HTTP_METHOD",var,5,false);
454 return m_method.c_str();
456 string getContentType() const {
457 if (m_content_type.empty()) {
459 GetServerVariable(m_pfc,"HTTP_CONTENT_TYPE",var,32,false);
461 m_content_type = var;
463 return m_content_type;
465 string getRemoteAddr() const {
466 m_remote_addr = AbstractSPRequest::getRemoteAddr();
467 if (m_remote_addr.empty()) {
469 GetServerVariable(m_pfc,"REMOTE_ADDR",var,16,false);
473 return m_remote_addr;
475 void log(SPLogLevel level, const string& msg) {
476 AbstractSPRequest::log(level,msg);
478 LogEvent(nullptr, EVENTLOG_ERROR_TYPE, 2100, nullptr, msg.c_str());
480 string makeSafeHeader(const char* rawname) const {
482 for (; *rawname; ++rawname) {
483 if (isalnum(*rawname))
488 void clearHeader(const char* rawname, const char* cginame) {
489 if (g_checkSpoofing && m_firsttime) {
490 if (m_allhttp.empty())
491 GetServerVariable(m_pfc, "ALL_HTTP", m_allhttp, 4096);
492 string hdr = g_bSafeHeaderNames ? ("HTTP_" + makeSafeHeader(cginame + 5)) : (string(cginame) + ':');
493 if (strstr(m_allhttp, hdr.c_str()))
494 throw opensaml::SecurityPolicyException("Attempt to spoof header ($1) was detected.", params(1, hdr.c_str()));
496 if (g_bSafeHeaderNames) {
497 string hdr = makeSafeHeader(rawname);
498 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), const_cast<char*>(g_unsetHeaderValue.c_str()));
500 else if (!strcmp(rawname,"REMOTE_USER")) {
501 m_pn->SetHeader(m_pfc, "remote-user:", const_cast<char*>(g_unsetHeaderValue.c_str()));
502 m_pn->SetHeader(m_pfc, "remote_user:", const_cast<char*>(g_unsetHeaderValue.c_str()));
505 string hdr = string(rawname) + ':';
506 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), const_cast<char*>(g_unsetHeaderValue.c_str()));
509 void setHeader(const char* name, const char* value) {
510 string hdr = g_bSafeHeaderNames ? makeSafeHeader(name) : (string(name) + ':');
511 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), const_cast<char*>(value));
513 string getSecureHeader(const char* name) const {
514 string hdr = g_bSafeHeaderNames ? makeSafeHeader(name) : (string(name) + ':');
516 GetHeader(m_pn, m_pfc, const_cast<char*>(hdr.c_str()), buf, 256, false);
517 return string(buf.empty() ? "" : buf);
519 string getHeader(const char* name) const {
523 GetHeader(m_pn, m_pfc, const_cast<char*>(hdr.c_str()), buf, 256, false);
524 return string(buf.empty() ? "" : buf);
526 void setRemoteUser(const char* user) {
527 setHeader("remote-user", user);
529 m_pfc->pFilterContext = nullptr;
530 else if (m_pfc->pFilterContext = m_pfc->AllocMem(m_pfc, sizeof(char) * (strlen(user) + 1), 0))
531 strcpy(reinterpret_cast<char*>(m_pfc->pFilterContext), user);
533 string getRemoteUser() const {
534 return getSecureHeader("remote-user");
536 void setResponseHeader(const char* name, const char* value) {
537 HTTPResponse::setResponseHeader(name, value);
540 m_headers.insert(make_pair(name,value));
542 m_headers.erase(name);
544 long sendResponse(istream& in, long status) {
545 string hdr = string("Connection: close\r\n");
546 for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
547 hdr += i->first + ": " + i->second + "\r\n";
549 const char* codestr="200 OK";
551 case XMLTOOLING_HTTP_STATUS_NOTMODIFIED: codestr="304 Not Modified"; break;
552 case XMLTOOLING_HTTP_STATUS_UNAUTHORIZED: codestr="401 Authorization Required"; break;
553 case XMLTOOLING_HTTP_STATUS_FORBIDDEN: codestr="403 Forbidden"; break;
554 case XMLTOOLING_HTTP_STATUS_NOTFOUND: codestr="404 Not Found"; break;
555 case XMLTOOLING_HTTP_STATUS_ERROR: codestr="500 Server Error"; break;
557 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, (void*)codestr, (DWORD)hdr.c_str(), 0);
561 DWORD resplen = in.gcount();
562 m_pfc->WriteClient(m_pfc, buf, &resplen, 0);
564 return SF_STATUS_REQ_FINISHED;
566 long sendRedirect(const char* url) {
567 HTTPResponse::sendRedirect(url);
568 string hdr=string("Location: ") + url + "\r\n"
569 "Content-Type: text/html\r\n"
570 "Content-Length: 40\r\n"
571 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
572 "Cache-Control: private,no-store,no-cache\r\n";
573 for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
574 hdr += i->first + ": " + i->second + "\r\n";
576 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, "302 Please Wait", (DWORD)hdr.c_str(), 0);
577 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
579 m_pfc->WriteClient(m_pfc, (LPVOID)redmsg, &resplen, 0);
580 return SF_STATUS_REQ_FINISHED;
582 long returnDecline() {
583 return SF_STATUS_REQ_NEXT_NOTIFICATION;
586 return SF_STATUS_REQ_NEXT_NOTIFICATION;
589 const vector<string>& getClientCertificates() const {
593 // The filter never processes the POST, so stub these methods.
594 long getContentLength() const { throw IOException("The request's Content-Length is not available to an ISAPI filter."); }
595 const char* getRequestBody() const { throw IOException("The request body is not available to an ISAPI filter."); }
598 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const char* msg)
600 LogEvent(nullptr, EVENTLOG_ERROR_TYPE, 2100, nullptr, msg);
601 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
602 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)ctype,0);
603 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Filter Error</TITLE></HEAD><BODY>"
604 "<H1>Shibboleth Filter Error</H1>";
605 DWORD resplen=strlen(xmsg);
606 pfc->WriteClient(pfc,(LPVOID)xmsg,&resplen,0);
608 pfc->WriteClient(pfc,(LPVOID)msg,&resplen,0);
609 static const char* xmsg2="</BODY></HTML>";
610 resplen=strlen(xmsg2);
611 pfc->WriteClient(pfc,(LPVOID)xmsg2,&resplen,0);
612 return SF_STATUS_REQ_FINISHED;
615 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
617 // Is this a log notification?
618 if (notificationType==SF_NOTIFY_LOG) {
619 if (pfc->pFilterContext)
620 ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=reinterpret_cast<char*>(pfc->pFilterContext);
621 return SF_STATUS_REQ_NEXT_NOTIFICATION;
624 PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
627 // Determine web site number. This can't really fail, I don't think.
629 GetServerVariable(pfc,"INSTANCE_ID",buf,10);
631 // Match site instance to host name, skip if no match.
632 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
633 if (map_i==g_Sites.end())
634 return SF_STATUS_REQ_NEXT_NOTIFICATION;
636 ostringstream threadid;
637 threadid << "[" << getpid() << "] isapi_shib" << '\0';
638 xmltooling::NDC ndc(threadid.str().c_str());
640 ShibTargetIsapiF stf(pfc, pn, map_i->second);
642 // "false" because we don't override the Shib settings
643 pair<bool,long> res = stf.getServiceProvider().doAuthentication(stf);
644 if (!g_spoofKey.empty())
645 pn->SetHeader(pfc, "ShibSpoofCheck:", const_cast<char*>(g_spoofKey.c_str()));
646 if (res.first) return res.second;
648 // "false" because we don't override the Shib settings
649 res = stf.getServiceProvider().doExport(stf);
650 if (res.first) return res.second;
652 res = stf.getServiceProvider().doAuthorization(stf);
653 if (res.first) return res.second;
655 return SF_STATUS_REQ_NEXT_NOTIFICATION;
658 return WriteClientError(pfc,"Out of Memory");
661 if (e==ERROR_NO_DATA)
662 return WriteClientError(pfc,"A required variable or header was empty.");
664 return WriteClientError(pfc,"Shibboleth Filter detected unexpected IIS error.");
666 catch (exception& e) {
667 LogEvent(nullptr, EVENTLOG_ERROR_TYPE, 2100, nullptr, e.what());
668 return WriteClientError(pfc,"Shibboleth Filter caught an exception, check Event Log for details.");
671 LogEvent(nullptr, EVENTLOG_ERROR_TYPE, 2100, nullptr, "Shibboleth Filter threw an unknown exception.");
673 return WriteClientError(pfc,"Shibboleth Filter threw an unknown exception.");
677 return WriteClientError(pfc,"Shibboleth Filter reached unreachable code, save my walrus!");
681 /****************************************************************************/
684 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* msg)
686 LogEvent(nullptr, EVENTLOG_ERROR_TYPE, 2100, nullptr, msg);
687 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
688 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
689 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Error</TITLE></HEAD><BODY><H1>Shibboleth Error</H1>";
690 DWORD resplen=strlen(xmsg);
691 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg,&resplen,HSE_IO_SYNC);
693 lpECB->WriteClient(lpECB->ConnID,(LPVOID)msg,&resplen,HSE_IO_SYNC);
694 static const char* xmsg2="</BODY></HTML>";
695 resplen=strlen(xmsg2);
696 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg2,&resplen,HSE_IO_SYNC);
697 return HSE_STATUS_SUCCESS;
701 class ShibTargetIsapiE : public AbstractSPRequest
703 LPEXTENSION_CONTROL_BLOCK m_lpECB;
704 multimap<string,string> m_headers;
705 mutable vector<string> m_certs;
706 mutable string m_body;
707 mutable bool m_gotBody;
709 string m_scheme,m_hostname,m_uri;
710 mutable string m_remote_addr,m_remote_user;
713 ShibTargetIsapiE(LPEXTENSION_CONTROL_BLOCK lpECB, const site_t& site)
714 : AbstractSPRequest(SHIBSP_LOGCAT".ISAPI"), m_lpECB(lpECB), m_gotBody(false) {
716 GetServerVariable(lpECB,"HTTPS",ssl,5);
717 bool SSL=(ssl=="on" || ssl=="ON");
719 // Scheme may come from site def or be derived from IIS.
720 m_scheme=site.m_scheme;
721 if (m_scheme.empty() || !g_bNormalizeRequest)
722 m_scheme = SSL ? "https" : "http";
724 // URL path always come from IIS.
726 GetServerVariable(lpECB,"URL",url,255);
728 // Port may come from IIS or from site def.
730 if (!g_bNormalizeRequest || (SSL && site.m_sslport.empty()) || (!SSL && site.m_port.empty()))
731 GetServerVariable(lpECB,"SERVER_PORT",port,10);
733 strncpy(port,site.m_sslport.c_str(),10);
734 static_cast<char*>(port)[10]=0;
737 strncpy(port,site.m_port.c_str(),10);
738 static_cast<char*>(port)[10]=0;
743 GetServerVariable(lpECB, "SERVER_NAME", var, 32);
745 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
747 if (site.m_name!=m_hostname && site.m_aliases.find(m_hostname)==site.m_aliases.end())
748 m_hostname=site.m_name;
751 * IIS screws us over on PATH_INFO (the hits keep on coming). We need to figure out if
752 * the server is set up for proper PATH_INFO handling, or "IIS sucks rabid weasels mode",
753 * which is the default. No perfect way to tell, but we can take a good guess by checking
754 * whether the URL is a substring of the PATH_INFO:
756 * e.g. for /Shibboleth.sso/SAML/POST
758 * Bad mode (default):
759 * URL: /Shibboleth.sso
760 * PathInfo: /Shibboleth.sso/SAML/POST
763 * URL: /Shibboleth.sso
764 * PathInfo: /SAML/POST
769 // Clearly we're only in bad mode if path info exists at all.
770 if (lpECB->lpszPathInfo && *(lpECB->lpszPathInfo)) {
771 if (strstr(lpECB->lpszPathInfo,url))
772 // Pretty good chance we're in bad mode, unless the PathInfo repeats the path itself.
773 uri = lpECB->lpszPathInfo;
776 uri += lpECB->lpszPathInfo;
783 // For consistency with Apache, let's add the query string.
784 if (lpECB->lpszQueryString && *(lpECB->lpszQueryString)) {
786 uri += lpECB->lpszQueryString;
789 setRequestURI(uri.c_str());
791 ~ShibTargetIsapiE() { }
793 const char* getScheme() const {
794 return m_scheme.c_str();
796 const char* getHostname() const {
797 return m_hostname.c_str();
799 int getPort() const {
802 const char* getMethod() const {
803 return m_lpECB->lpszMethod;
805 string getContentType() const {
806 return m_lpECB->lpszContentType ? m_lpECB->lpszContentType : "";
808 long getContentLength() const {
809 return m_lpECB->cbTotalBytes;
811 string getRemoteUser() const {
812 if (m_remote_user.empty()) {
814 GetServerVariable(m_lpECB, "REMOTE_USER", var, 32, false);
818 return m_remote_user;
820 string getRemoteAddr() const {
821 m_remote_addr = AbstractSPRequest::getRemoteAddr();
822 if (m_remote_addr.empty()) {
824 GetServerVariable(m_lpECB, "REMOTE_ADDR", var, 16, false);
828 return m_remote_addr;
830 void log(SPLogLevel level, const string& msg) const {
831 AbstractSPRequest::log(level,msg);
833 LogEvent(nullptr, EVENTLOG_ERROR_TYPE, 2100, nullptr, msg.c_str());
835 string getHeader(const char* name) const {
837 for (; *name; ++name) {
841 hdr += toupper(*name);
844 GetServerVariable(m_lpECB, const_cast<char*>(hdr.c_str()), buf, 128, false);
845 return buf.empty() ? "" : buf;
847 void setResponseHeader(const char* name, const char* value) {
848 HTTPResponse::setResponseHeader(name, value);
851 m_headers.insert(make_pair(name,value));
853 m_headers.erase(name);
855 const char* getQueryString() const {
856 return m_lpECB->lpszQueryString;
858 const char* getRequestBody() const {
860 return m_body.c_str();
861 if (m_lpECB->cbTotalBytes > 1024*1024) // 1MB?
862 throw opensaml::SecurityPolicyException("Size of request body exceeded 1M size limit.");
863 else if (m_lpECB->cbTotalBytes > m_lpECB->cbAvailable) {
865 DWORD datalen=m_lpECB->cbTotalBytes;
866 if (m_lpECB->cbAvailable > 0) {
867 m_body.assign(reinterpret_cast<char*>(m_lpECB->lpbData),m_lpECB->cbAvailable);
868 datalen-=m_lpECB->cbAvailable;
873 BOOL ret = m_lpECB->ReadClient(m_lpECB->ConnID, buf, &buflen);
876 _snprintf(message, 64, "Error reading request body from browser (%x).", GetLastError());
877 throw IOException(message);
880 throw IOException("Socket closed while reading request body from browser.");
881 m_body.append(buf, buflen);
885 else if (m_lpECB->cbAvailable) {
887 m_body.assign(reinterpret_cast<char*>(m_lpECB->lpbData),m_lpECB->cbAvailable);
889 return m_body.c_str();
891 long sendResponse(istream& in, long status) {
892 string hdr = string("Connection: close\r\n");
893 for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
894 hdr += i->first + ": " + i->second + "\r\n";
896 const char* codestr="200 OK";
898 case XMLTOOLING_HTTP_STATUS_NOTMODIFIED: codestr="304 Not Modified"; break;
899 case XMLTOOLING_HTTP_STATUS_UNAUTHORIZED: codestr="401 Authorization Required"; break;
900 case XMLTOOLING_HTTP_STATUS_FORBIDDEN: codestr="403 Forbidden"; break;
901 case XMLTOOLING_HTTP_STATUS_NOTFOUND: codestr="404 Not Found"; break;
902 case XMLTOOLING_HTTP_STATUS_ERROR: codestr="500 Server Error"; break;
904 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, (void*)codestr, 0, (LPDWORD)hdr.c_str());
908 DWORD resplen = in.gcount();
909 m_lpECB->WriteClient(m_lpECB->ConnID, buf, &resplen, HSE_IO_SYNC);
911 return HSE_STATUS_SUCCESS;
913 long sendRedirect(const char* url) {
914 HTTPResponse::sendRedirect(url);
915 string hdr=string("Location: ") + url + "\r\n"
916 "Content-Type: text/html\r\n"
917 "Content-Length: 40\r\n"
918 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
919 "Cache-Control: private,no-store,no-cache\r\n";
920 for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
921 hdr += i->first + ": " + i->second + "\r\n";
923 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, "302 Moved", 0, (LPDWORD)hdr.c_str());
924 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
926 m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)redmsg, &resplen, HSE_IO_SYNC);
927 return HSE_STATUS_SUCCESS;
929 // Decline happens in the POST processor if this isn't the shire url
930 // Note that it can also happen with HTAccess, but we don't support that, yet.
931 long returnDecline() {
932 return WriteClientError(
934 "ISAPI extension can only be invoked to process Shibboleth protocol requests."
935 "Make sure the mapped file extension doesn't match actual content."
939 return HSE_STATUS_SUCCESS;
942 const vector<string>& getClientCertificates() const {
943 if (m_certs.empty()) {
944 char CertificateBuf[8192];
945 CERT_CONTEXT_EX ccex;
946 ccex.cbAllocated = sizeof(CertificateBuf);
947 ccex.CertContext.pbCertEncoded = (BYTE*)CertificateBuf;
948 DWORD dwSize = sizeof(ccex);
950 if (m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_GET_CERT_INFO_EX, (LPVOID)&ccex, (LPDWORD)dwSize, nullptr)) {
951 if (ccex.CertContext.cbCertEncoded) {
953 XMLByte* serialized = Base64::encode(reinterpret_cast<XMLByte*>(CertificateBuf), ccex.CertContext.cbCertEncoded, &outlen);
954 m_certs.push_back(reinterpret_cast<char*>(serialized));
955 #ifdef SHIBSP_XERCESC_HAS_XMLBYTE_RELEASE
956 XMLString::release(&serialized);
958 XMLString::release((char**)&serialized);
966 // Not used in the extension.
967 void clearHeader(const char* rawname, const char* cginame) { throw runtime_error("clearHeader not implemented"); }
968 void setHeader(const char* name, const char* value) { throw runtime_error("setHeader not implemented"); }
969 void setRemoteUser(const char* user) { throw runtime_error("setRemoteUser not implemented"); }
972 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
975 ostringstream threadid;
976 threadid << "[" << getpid() << "] isapi_shib_extension" << '\0';
977 xmltooling::NDC ndc(threadid.str().c_str());
979 // Determine web site number. This can't really fail, I don't think.
981 GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
983 // Match site instance to host name, skip if no match.
984 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
985 if (map_i==g_Sites.end())
986 return WriteClientError(lpECB, "Shibboleth Extension not configured for web site (check ISAPI mappings in SP configuration).");
988 ShibTargetIsapiE ste(lpECB, map_i->second);
989 pair<bool,long> res = ste.getServiceProvider().doHandler(ste);
990 if (res.first) return res.second;
992 return WriteClientError(lpECB, "Shibboleth Extension failed to process request");
996 return WriteClientError(lpECB,"Out of Memory");
999 if (e==ERROR_NO_DATA)
1000 return WriteClientError(lpECB,"A required variable or header was empty.");
1002 return WriteClientError(lpECB,"Server detected unexpected IIS error.");
1004 catch (exception& e) {
1005 LogEvent(nullptr, EVENTLOG_ERROR_TYPE, 2100, nullptr, e.what());
1006 return WriteClientError(lpECB,"Shibboleth Extension caught an exception, check Event Log for details.");
1009 LogEvent(nullptr, EVENTLOG_ERROR_TYPE, 2100, nullptr, "Shibboleth Extension threw an unknown exception.");
1011 return WriteClientError(lpECB,"Shibboleth Extension threw an unknown exception.");
1015 // If we get here we've got an error.
1016 return HSE_STATUS_ERROR;