2 * Licensed to the University Corporation for Advanced Internet
3 * Development, Inc. (UCAID) under one or more contributor license
4 * agreements. See the NOTICE file distributed with this work for
5 * additional information regarding copyright ownership.
7 * UCAID licenses this file to you under the Apache License,
8 * Version 2.0 (the "License"); you may not use this file except
9 * in compliance with the License. You may obtain a copy of the
12 * http://www.apache.org/licenses/LICENSE-2.0
14 * Unless required by applicable law or agreed to in writing,
15 * software distributed under the License is distributed on an
16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
17 * either express or implied. See the License for the specific
18 * language governing permissions and limitations under the License.
24 * Shibboleth ISAPI filter.
28 #include "config_win32.h"
30 #define _CRT_NONSTDC_NO_DEPRECATE 1
31 #define _CRT_SECURE_NO_DEPRECATE 1
34 #include <shibsp/exceptions.h>
35 #include <shibsp/AbstractSPRequest.h>
36 #include <shibsp/SPConfig.h>
37 #include <shibsp/ServiceProvider.h>
44 #include <xmltooling/unicode.h>
45 #include <xmltooling/XMLToolingConfig.h>
46 #include <xmltooling/util/NDC.h>
47 #include <xmltooling/util/XMLConstants.h>
48 #include <xmltooling/util/XMLHelper.h>
49 #include <xercesc/util/Base64.hpp>
50 #include <xercesc/util/XMLUniDefs.hpp>
56 using namespace shibsp;
57 using namespace xmltooling;
58 using namespace xercesc;
63 static const XMLCh path[] = UNICODE_LITERAL_4(p,a,t,h);
64 static const XMLCh validate[] = UNICODE_LITERAL_8(v,a,l,i,d,a,t,e);
65 static const XMLCh name[] = UNICODE_LITERAL_4(n,a,m,e);
66 static const XMLCh port[] = UNICODE_LITERAL_4(p,o,r,t);
67 static const XMLCh sslport[] = UNICODE_LITERAL_7(s,s,l,p,o,r,t);
68 static const XMLCh scheme[] = UNICODE_LITERAL_6(s,c,h,e,m,e);
69 static const XMLCh id[] = UNICODE_LITERAL_2(i,d);
70 static const XMLCh Alias[] = UNICODE_LITERAL_5(A,l,i,a,s);
71 static const XMLCh Site[] = UNICODE_LITERAL_4(S,i,t,e);
74 site_t(const DOMElement* e)
76 auto_ptr_char n(e->getAttributeNS(nullptr,name));
77 auto_ptr_char s(e->getAttributeNS(nullptr,scheme));
78 auto_ptr_char p(e->getAttributeNS(nullptr,port));
79 auto_ptr_char p2(e->getAttributeNS(nullptr,sslport));
80 if (n.get()) m_name=n.get();
81 if (s.get()) m_scheme=s.get();
82 if (p.get()) m_port=p.get();
83 if (p2.get()) m_sslport=p2.get();
84 e = XMLHelper::getFirstChildElement(e, Alias);
86 if (e->hasChildNodes()) {
87 auto_ptr_char alias(e->getFirstChild()->getNodeValue());
88 m_aliases.insert(alias.get());
90 e = XMLHelper::getNextSiblingElement(e, Alias);
93 string m_scheme,m_port,m_sslport,m_name;
94 set<string> m_aliases;
98 SPConfig* g_Config = nullptr;
99 map<string,site_t> g_Sites;
100 bool g_bNormalizeRequest = true;
101 string g_unsetHeaderValue,g_spoofKey;
102 bool g_checkSpoofing = true;
103 bool g_catchAll = false;
104 bool g_bSafeHeaderNames = false;
105 vector<string> g_NoCerts;
109 LPCSTR lpUNCServerName,
115 LPCSTR messages[] = {message, nullptr};
117 HANDLE hElog = RegisterEventSource(lpUNCServerName, "Shibboleth ISAPI Filter");
118 BOOL res = ReportEvent(hElog, wType, 0, dwEventID, lpUserSid, 1, 0, messages, nullptr);
119 return (DeregisterEventSource(hElog) && res);
122 void _my_invalid_parameter_handler(
123 const wchar_t * expression,
124 const wchar_t * function,
125 const wchar_t * file,
133 extern "C" __declspec(dllexport) BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID)
135 if (fdwReason==DLL_PROCESS_ATTACH)
140 extern "C" BOOL WINAPI GetExtensionVersion(HSE_VERSION_INFO* pVer)
146 LogEvent(nullptr, EVENTLOG_ERROR_TYPE, 2100, nullptr,
147 "Extension mode startup not possible, is the DLL loaded as a filter?");
151 pVer->dwExtensionVersion=HSE_VERSION;
152 strncpy(pVer->lpszExtensionDesc,"Shibboleth ISAPI Extension",HSE_MAX_EXT_DLL_NAME_LEN-1);
156 extern "C" BOOL WINAPI TerminateExtension(DWORD)
158 return TRUE; // cleanup should happen when filter unloads
161 extern "C" BOOL WINAPI GetFilterVersion(PHTTP_FILTER_VERSION pVer)
166 LogEvent(nullptr, EVENTLOG_ERROR_TYPE, 2100, nullptr,
167 "Reentrant filter initialization, ignoring...");
171 g_Config=&SPConfig::getConfig();
172 g_Config->setFeatures(
175 SPConfig::RequestMapping |
176 SPConfig::InProcess |
180 if (!g_Config->init()) {
182 LogEvent(nullptr, EVENTLOG_ERROR_TYPE, 2100, nullptr,
183 "Filter startup failed during library initialization, check native log for help.");
188 if (!g_Config->instantiate(nullptr, true))
189 throw runtime_error("unknown error");
191 catch (exception& ex) {
194 LogEvent(nullptr, EVENTLOG_ERROR_TYPE, 2100, nullptr, ex.what());
195 LogEvent(nullptr, EVENTLOG_ERROR_TYPE, 2100, nullptr,
196 "Filter startup failed to load configuration, check native log for details.");
200 // Access implementation-specifics and site mappings.
201 ServiceProvider* sp=g_Config->getServiceProvider();
203 const PropertySet* props=sp->getPropertySet("InProcess");
205 pair<bool,bool> flag=props->getBool("checkSpoofing");
206 g_checkSpoofing = !flag.first || flag.second;
207 flag=props->getBool("catchAll");
208 g_catchAll = flag.first && flag.second;
210 pair<bool,const char*> unsetValue=props->getString("unsetHeaderValue");
211 if (unsetValue.first)
212 g_unsetHeaderValue = unsetValue.second;
213 if (g_checkSpoofing) {
214 unsetValue = props->getString("spoofKey");
215 if (unsetValue.first)
216 g_spoofKey = unsetValue.second;
218 _invalid_parameter_handler old = _set_invalid_parameter_handler(_my_invalid_parameter_handler);
219 unsigned int randkey=0,randkey2=0,randkey3=0,randkey4=0;
220 if (rand_s(&randkey) == 0 && rand_s(&randkey2) == 0 && rand_s(&randkey3) == 0 && rand_s(&randkey4) == 0) {
221 _set_invalid_parameter_handler(old);
222 ostringstream keystr;
223 keystr << randkey << randkey2 << randkey3 << randkey4;
224 g_spoofKey = keystr.str();
227 _set_invalid_parameter_handler(old);
228 LogEvent(nullptr, EVENTLOG_ERROR_TYPE, 2100, nullptr,
229 "Filter failed to generate a random anti-spoofing key (if this is Windows 2000 set one manually).");
230 locker.assign(); // pops lock on SP config
238 props = props->getPropertySet("ISAPI");
240 flag = props->getBool("normalizeRequest");
241 g_bNormalizeRequest = !flag.first || flag.second;
242 flag = props->getBool("safeHeaderNames");
243 g_bSafeHeaderNames = flag.first && flag.second;
244 const DOMElement* child = XMLHelper::getFirstChildElement(props->getElement(),Site);
246 auto_ptr_char id(child->getAttributeNS(nullptr,id));
248 g_Sites.insert(pair<string,site_t>(id.get(),site_t(child)));
249 child=XMLHelper::getNextSiblingElement(child,Site);
254 pVer->dwFilterVersion=HTTP_FILTER_REVISION;
255 strncpy(pVer->lpszFilterDesc,"Shibboleth ISAPI Filter",SF_MAX_FILTER_DESC_LEN);
256 pVer->dwFlags=(SF_NOTIFY_ORDER_HIGH |
257 SF_NOTIFY_SECURE_PORT |
258 SF_NOTIFY_NONSECURE_PORT |
259 SF_NOTIFY_PREPROC_HEADERS |
261 LogEvent(nullptr, EVENTLOG_INFORMATION_TYPE, 7701, nullptr, "Filter initialized...");
265 extern "C" BOOL WINAPI TerminateFilter(DWORD)
270 LogEvent(nullptr, EVENTLOG_INFORMATION_TYPE, 7701, nullptr, "Filter shut down...");
274 /* Next up, some suck-free versions of various APIs.
276 You DON'T require people to guess the buffer size and THEN tell them the right size.
277 Returning an LPCSTR is apparently way beyond their ken. Not to mention the fact that
278 constant strings aren't typed as such, making it just that much harder. These versions
279 are now updated to use a special growable buffer object, modeled after the standard
280 string class. The standard string won't work because they left out the option to
281 pre-allocate a non-constant buffer.
287 dynabuf() { bufptr=nullptr; buflen=0; }
288 dynabuf(size_t s) { bufptr=new char[buflen=s]; *bufptr=0; }
289 ~dynabuf() { delete[] bufptr; }
290 size_t length() const { return bufptr ? strlen(bufptr) : 0; }
291 size_t size() const { return buflen; }
292 bool empty() const { return length()==0; }
293 void reserve(size_t s, bool keep=false);
294 void erase() { if (bufptr) memset(bufptr,0,buflen); }
295 operator char*() { return bufptr; }
296 bool operator ==(const char* s) const;
297 bool operator !=(const char* s) const { return !(*this==s); }
303 void dynabuf::reserve(size_t s, bool keep)
310 p[buflen]=bufptr[buflen];
316 bool dynabuf::operator==(const char* s) const
318 if (buflen==0 || s==nullptr)
319 return (buflen==0 && s==nullptr);
321 return strcmp(bufptr,s)==0;
324 void GetServerVariable(PHTTP_FILTER_CONTEXT pfc, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
330 while (!pfc->GetServerVariable(pfc,lpszVariable,s,&size)) {
331 // Grumble. Check the error.
332 DWORD e=GetLastError();
333 if (e==ERROR_INSUFFICIENT_BUFFER)
338 if (bRequired && s.empty())
342 void GetServerVariable(LPEXTENSION_CONTROL_BLOCK lpECB, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
348 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)
367 while (!pn->GetHeader(pfc,lpszName,s,&size)) {
368 // Grumble. Check the error.
369 DWORD e=GetLastError();
370 if (e==ERROR_INSUFFICIENT_BUFFER)
375 if (bRequired && s.empty())
379 /****************************************************************************/
382 class ShibTargetIsapiF : public AbstractSPRequest
384 PHTTP_FILTER_CONTEXT m_pfc;
385 PHTTP_FILTER_PREPROC_HEADERS m_pn;
386 multimap<string,string> m_headers;
388 string m_scheme,m_hostname;
389 mutable string m_remote_addr,m_content_type,m_method;
394 ShibTargetIsapiF(PHTTP_FILTER_CONTEXT pfc, PHTTP_FILTER_PREPROC_HEADERS pn, const site_t& site)
395 : AbstractSPRequest(SHIBSP_LOGCAT".ISAPI"), m_pfc(pfc), m_pn(pn), m_allhttp(4096), m_firsttime(true) {
397 // URL path always come from IIS.
399 GetHeader(pn,pfc,"url",var,256,false);
402 // Port may come from IIS or from site def.
403 if (!g_bNormalizeRequest || (pfc->fIsSecurePort && site.m_sslport.empty()) || (!pfc->fIsSecurePort && site.m_port.empty())) {
404 GetServerVariable(pfc,"SERVER_PORT",var,10);
407 else if (pfc->fIsSecurePort) {
408 m_port = atoi(site.m_sslport.c_str());
411 m_port = atoi(site.m_port.c_str());
414 // Scheme may come from site def or be derived from IIS.
415 m_scheme=site.m_scheme;
416 if (m_scheme.empty() || !g_bNormalizeRequest)
417 m_scheme=pfc->fIsSecurePort ? "https" : "http";
419 GetServerVariable(pfc,"SERVER_NAME",var,32);
421 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
423 if (site.m_name!=m_hostname && site.m_aliases.find(m_hostname)==site.m_aliases.end())
424 m_hostname=site.m_name;
426 if (!g_spoofKey.empty()) {
427 GetHeader(pn, pfc, "ShibSpoofCheck:", var, 32, false);
428 if (!var.empty() && g_spoofKey == (char*)var)
433 log(SPDebug, "ISAPI filter running more than once");
435 ~ShibTargetIsapiF() { }
437 const char* getScheme() const {
438 return m_scheme.c_str();
440 const char* getHostname() const {
441 return m_hostname.c_str();
443 int getPort() const {
446 const char* getQueryString() const {
447 const char* uri = getRequestURI();
448 uri = (uri ? strchr(uri, '?') : nullptr);
449 return uri ? (uri + 1) : nullptr;
451 const char* getMethod() const {
452 if (m_method.empty()) {
454 GetServerVariable(m_pfc,"HTTP_METHOD",var,5,false);
458 return m_method.c_str();
460 string getContentType() const {
461 if (m_content_type.empty()) {
463 GetServerVariable(m_pfc,"HTTP_CONTENT_TYPE",var,32,false);
465 m_content_type = var;
467 return m_content_type;
469 string getRemoteAddr() const {
470 m_remote_addr = AbstractSPRequest::getRemoteAddr();
471 if (m_remote_addr.empty()) {
473 GetServerVariable(m_pfc,"REMOTE_ADDR",var,16,false);
477 return m_remote_addr;
479 void log(SPLogLevel level, const string& msg) {
480 AbstractSPRequest::log(level,msg);
482 LogEvent(nullptr, EVENTLOG_ERROR_TYPE, 2100, nullptr, msg.c_str());
484 string makeSafeHeader(const char* rawname) const {
486 for (; *rawname; ++rawname) {
487 if (isalnum(*rawname))
492 void clearHeader(const char* rawname, const char* cginame) {
493 if (g_checkSpoofing && m_firsttime) {
494 if (m_allhttp.empty())
495 GetServerVariable(m_pfc, "ALL_HTTP", m_allhttp, 4096);
496 string hdr = g_bSafeHeaderNames ? ("HTTP_" + makeSafeHeader(cginame + 5)) : (string(cginame) + ':');
497 if (strstr(m_allhttp, hdr.c_str()))
498 throw opensaml::SecurityPolicyException("Attempt to spoof header ($1) was detected.", params(1, hdr.c_str()));
500 if (g_bSafeHeaderNames) {
501 string hdr = makeSafeHeader(rawname);
502 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), const_cast<char*>(g_unsetHeaderValue.c_str()));
504 else if (!strcmp(rawname,"REMOTE_USER")) {
505 m_pn->SetHeader(m_pfc, "remote-user:", const_cast<char*>(g_unsetHeaderValue.c_str()));
506 m_pn->SetHeader(m_pfc, "remote_user:", const_cast<char*>(g_unsetHeaderValue.c_str()));
509 string hdr = string(rawname) + ':';
510 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), const_cast<char*>(g_unsetHeaderValue.c_str()));
513 void setHeader(const char* name, const char* value) {
514 string hdr = g_bSafeHeaderNames ? makeSafeHeader(name) : (string(name) + ':');
515 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), const_cast<char*>(value));
517 string getSecureHeader(const char* name) const {
518 string hdr = g_bSafeHeaderNames ? makeSafeHeader(name) : (string(name) + ':');
520 GetHeader(m_pn, m_pfc, const_cast<char*>(hdr.c_str()), buf, 256, false);
521 return string(buf.empty() ? "" : buf);
523 string getHeader(const char* name) const {
527 GetHeader(m_pn, m_pfc, const_cast<char*>(hdr.c_str()), buf, 256, false);
528 return string(buf.empty() ? "" : buf);
530 void setRemoteUser(const char* user) {
531 setHeader("remote-user", user);
533 m_pfc->pFilterContext = nullptr;
534 else if (m_pfc->pFilterContext = m_pfc->AllocMem(m_pfc, sizeof(char) * (strlen(user) + 1), 0))
535 strcpy(reinterpret_cast<char*>(m_pfc->pFilterContext), user);
537 string getRemoteUser() const {
538 return getSecureHeader("remote-user");
540 void setResponseHeader(const char* name, const char* value) {
541 HTTPResponse::setResponseHeader(name, value);
544 m_headers.insert(make_pair(name,value));
546 m_headers.erase(name);
548 long sendResponse(istream& in, long status) {
549 string hdr = string("Connection: close\r\n");
550 for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
551 hdr += i->first + ": " + i->second + "\r\n";
553 const char* codestr="200 OK";
555 case XMLTOOLING_HTTP_STATUS_NOTMODIFIED: codestr="304 Not Modified"; break;
556 case XMLTOOLING_HTTP_STATUS_UNAUTHORIZED: codestr="401 Authorization Required"; break;
557 case XMLTOOLING_HTTP_STATUS_FORBIDDEN: codestr="403 Forbidden"; break;
558 case XMLTOOLING_HTTP_STATUS_NOTFOUND: codestr="404 Not Found"; break;
559 case XMLTOOLING_HTTP_STATUS_ERROR: codestr="500 Server Error"; break;
561 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, (void*)codestr, (DWORD)hdr.c_str(), 0);
565 DWORD resplen = in.gcount();
566 m_pfc->WriteClient(m_pfc, buf, &resplen, 0);
568 return SF_STATUS_REQ_FINISHED;
570 long sendRedirect(const char* url) {
571 HTTPResponse::sendRedirect(url);
572 string hdr=string("Location: ") + url + "\r\n"
573 "Content-Type: text/html\r\n"
574 "Content-Length: 40\r\n"
575 "Expires: Wed, 01 Jan 1997 12:00:00 GMT\r\n"
576 "Cache-Control: private,no-store,no-cache,max-age=0\r\n";
577 for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
578 hdr += i->first + ": " + i->second + "\r\n";
580 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, "302 Please Wait", (DWORD)hdr.c_str(), 0);
581 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
583 m_pfc->WriteClient(m_pfc, (LPVOID)redmsg, &resplen, 0);
584 return SF_STATUS_REQ_FINISHED;
586 long returnDecline() {
587 return SF_STATUS_REQ_NEXT_NOTIFICATION;
590 return SF_STATUS_REQ_NEXT_NOTIFICATION;
593 const vector<string>& getClientCertificates() const {
597 // The filter never processes the POST, so stub these methods.
598 long getContentLength() const { throw IOException("The request's Content-Length is not available to an ISAPI filter."); }
599 const char* getRequestBody() const { throw IOException("The request body is not available to an ISAPI filter."); }
602 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const char* msg)
604 LogEvent(nullptr, EVENTLOG_ERROR_TYPE, 2100, nullptr, msg);
605 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
606 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)ctype,0);
607 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Filter Error</TITLE></HEAD><BODY>"
608 "<H1>Shibboleth Filter Error</H1>";
609 DWORD resplen=strlen(xmsg);
610 pfc->WriteClient(pfc,(LPVOID)xmsg,&resplen,0);
612 pfc->WriteClient(pfc,(LPVOID)msg,&resplen,0);
613 static const char* xmsg2="</BODY></HTML>";
614 resplen=strlen(xmsg2);
615 pfc->WriteClient(pfc,(LPVOID)xmsg2,&resplen,0);
616 return SF_STATUS_REQ_FINISHED;
619 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
621 // Is this a log notification?
622 if (notificationType==SF_NOTIFY_LOG) {
623 if (pfc->pFilterContext)
624 ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=reinterpret_cast<char*>(pfc->pFilterContext);
625 return SF_STATUS_REQ_NEXT_NOTIFICATION;
628 PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
631 // Determine web site number. This can't really fail, I don't think.
633 GetServerVariable(pfc,"INSTANCE_ID",buf,10);
635 // Match site instance to host name, skip if no match.
636 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
637 if (map_i==g_Sites.end())
638 return SF_STATUS_REQ_NEXT_NOTIFICATION;
640 ostringstream threadid;
641 threadid << "[" << getpid() << "] isapi_shib" << '\0';
642 xmltooling::NDC ndc(threadid.str().c_str());
644 ShibTargetIsapiF stf(pfc, pn, map_i->second);
646 // "false" because we don't override the Shib settings
647 pair<bool,long> res = stf.getServiceProvider().doAuthentication(stf);
648 if (!g_spoofKey.empty())
649 pn->SetHeader(pfc, "ShibSpoofCheck:", const_cast<char*>(g_spoofKey.c_str()));
650 if (res.first) return res.second;
652 // "false" because we don't override the Shib settings
653 res = stf.getServiceProvider().doExport(stf);
654 if (res.first) return res.second;
656 res = stf.getServiceProvider().doAuthorization(stf);
657 if (res.first) return res.second;
659 return SF_STATUS_REQ_NEXT_NOTIFICATION;
662 return WriteClientError(pfc,"Out of Memory");
665 if (e==ERROR_NO_DATA)
666 return WriteClientError(pfc,"A required variable or header was empty.");
668 return WriteClientError(pfc,"Shibboleth Filter detected unexpected IIS error.");
670 catch (exception& e) {
671 LogEvent(nullptr, EVENTLOG_ERROR_TYPE, 2100, nullptr, e.what());
672 return WriteClientError(pfc,"Shibboleth Filter caught an exception, check Event Log for details.");
675 LogEvent(nullptr, EVENTLOG_ERROR_TYPE, 2100, nullptr, "Shibboleth Filter threw an unknown exception.");
677 return WriteClientError(pfc,"Shibboleth Filter threw an unknown exception.");
681 return WriteClientError(pfc,"Shibboleth Filter reached unreachable code, save my walrus!");
685 /****************************************************************************/
688 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* msg)
690 LogEvent(nullptr, EVENTLOG_ERROR_TYPE, 2100, nullptr, msg);
691 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
692 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
693 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Error</TITLE></HEAD><BODY><H1>Shibboleth Error</H1>";
694 DWORD resplen=strlen(xmsg);
695 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg,&resplen,HSE_IO_SYNC);
697 lpECB->WriteClient(lpECB->ConnID,(LPVOID)msg,&resplen,HSE_IO_SYNC);
698 static const char* xmsg2="</BODY></HTML>";
699 resplen=strlen(xmsg2);
700 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg2,&resplen,HSE_IO_SYNC);
701 return HSE_STATUS_SUCCESS;
705 class ShibTargetIsapiE : public AbstractSPRequest
707 LPEXTENSION_CONTROL_BLOCK m_lpECB;
708 multimap<string,string> m_headers;
709 mutable vector<string> m_certs;
710 mutable string m_body;
711 mutable bool m_gotBody;
713 string m_scheme,m_hostname,m_uri;
714 mutable string m_remote_addr,m_remote_user;
717 ShibTargetIsapiE(LPEXTENSION_CONTROL_BLOCK lpECB, const site_t& site)
718 : AbstractSPRequest(SHIBSP_LOGCAT".ISAPI"), m_lpECB(lpECB), m_gotBody(false) {
720 GetServerVariable(lpECB,"HTTPS",ssl,5);
721 bool SSL=(ssl=="on" || ssl=="ON");
723 // Scheme may come from site def or be derived from IIS.
724 m_scheme=site.m_scheme;
725 if (m_scheme.empty() || !g_bNormalizeRequest)
726 m_scheme = SSL ? "https" : "http";
728 // URL path always come from IIS.
730 GetServerVariable(lpECB,"URL",url,255);
732 // Port may come from IIS or from site def.
734 if (!g_bNormalizeRequest || (SSL && site.m_sslport.empty()) || (!SSL && site.m_port.empty()))
735 GetServerVariable(lpECB,"SERVER_PORT",port,10);
737 strncpy(port,site.m_sslport.c_str(),10);
738 static_cast<char*>(port)[10]=0;
741 strncpy(port,site.m_port.c_str(),10);
742 static_cast<char*>(port)[10]=0;
747 GetServerVariable(lpECB, "SERVER_NAME", var, 32);
749 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
751 if (site.m_name!=m_hostname && site.m_aliases.find(m_hostname)==site.m_aliases.end())
752 m_hostname=site.m_name;
755 * IIS screws us over on PATH_INFO (the hits keep on coming). We need to figure out if
756 * the server is set up for proper PATH_INFO handling, or "IIS sucks rabid weasels mode",
757 * which is the default. No perfect way to tell, but we can take a good guess by checking
758 * whether the URL is a substring of the PATH_INFO:
760 * e.g. for /Shibboleth.sso/SAML/POST
762 * Bad mode (default):
763 * URL: /Shibboleth.sso
764 * PathInfo: /Shibboleth.sso/SAML/POST
767 * URL: /Shibboleth.sso
768 * PathInfo: /SAML/POST
773 // Clearly we're only in bad mode if path info exists at all.
774 if (lpECB->lpszPathInfo && *(lpECB->lpszPathInfo)) {
775 if (strstr(lpECB->lpszPathInfo,url))
776 // Pretty good chance we're in bad mode, unless the PathInfo repeats the path itself.
777 uri = lpECB->lpszPathInfo;
780 uri += lpECB->lpszPathInfo;
787 // For consistency with Apache, let's add the query string.
788 if (lpECB->lpszQueryString && *(lpECB->lpszQueryString)) {
790 uri += lpECB->lpszQueryString;
793 setRequestURI(uri.c_str());
795 ~ShibTargetIsapiE() { }
797 const char* getScheme() const {
798 return m_scheme.c_str();
800 const char* getHostname() const {
801 return m_hostname.c_str();
803 int getPort() const {
806 const char* getMethod() const {
807 return m_lpECB->lpszMethod;
809 string getContentType() const {
810 return m_lpECB->lpszContentType ? m_lpECB->lpszContentType : "";
812 long getContentLength() const {
813 return m_lpECB->cbTotalBytes;
815 string getRemoteUser() const {
816 if (m_remote_user.empty()) {
818 GetServerVariable(m_lpECB, "REMOTE_USER", var, 32, false);
822 return m_remote_user;
824 string getRemoteAddr() const {
825 m_remote_addr = AbstractSPRequest::getRemoteAddr();
826 if (m_remote_addr.empty()) {
828 GetServerVariable(m_lpECB, "REMOTE_ADDR", var, 16, false);
832 return m_remote_addr;
834 void log(SPLogLevel level, const string& msg) const {
835 AbstractSPRequest::log(level,msg);
837 LogEvent(nullptr, EVENTLOG_ERROR_TYPE, 2100, nullptr, msg.c_str());
839 string getHeader(const char* name) const {
841 for (; *name; ++name) {
845 hdr += toupper(*name);
848 GetServerVariable(m_lpECB, const_cast<char*>(hdr.c_str()), buf, 128, false);
849 return buf.empty() ? "" : buf;
851 void setResponseHeader(const char* name, const char* value) {
852 HTTPResponse::setResponseHeader(name, value);
855 m_headers.insert(make_pair(name,value));
857 m_headers.erase(name);
859 const char* getQueryString() const {
860 return m_lpECB->lpszQueryString;
862 const char* getRequestBody() const {
864 return m_body.c_str();
865 if (m_lpECB->cbTotalBytes > 1024*1024) // 1MB?
866 throw opensaml::SecurityPolicyException("Size of request body exceeded 1M size limit.");
867 else if (m_lpECB->cbTotalBytes > m_lpECB->cbAvailable) {
869 DWORD datalen=m_lpECB->cbTotalBytes;
870 if (m_lpECB->cbAvailable > 0) {
871 m_body.assign(reinterpret_cast<char*>(m_lpECB->lpbData),m_lpECB->cbAvailable);
872 datalen-=m_lpECB->cbAvailable;
877 BOOL ret = m_lpECB->ReadClient(m_lpECB->ConnID, buf, &buflen);
880 _snprintf(message, 64, "Error reading request body from browser (%x).", GetLastError());
881 throw IOException(message);
884 throw IOException("Socket closed while reading request body from browser.");
885 m_body.append(buf, buflen);
889 else if (m_lpECB->cbAvailable) {
891 m_body.assign(reinterpret_cast<char*>(m_lpECB->lpbData),m_lpECB->cbAvailable);
893 return m_body.c_str();
895 long sendResponse(istream& in, long status) {
896 string hdr = string("Connection: close\r\n");
897 for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
898 hdr += i->first + ": " + i->second + "\r\n";
900 const char* codestr="200 OK";
902 case XMLTOOLING_HTTP_STATUS_NOTMODIFIED: codestr="304 Not Modified"; break;
903 case XMLTOOLING_HTTP_STATUS_UNAUTHORIZED: codestr="401 Authorization Required"; break;
904 case XMLTOOLING_HTTP_STATUS_FORBIDDEN: codestr="403 Forbidden"; break;
905 case XMLTOOLING_HTTP_STATUS_NOTFOUND: codestr="404 Not Found"; break;
906 case XMLTOOLING_HTTP_STATUS_ERROR: codestr="500 Server Error"; break;
908 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, (void*)codestr, 0, (LPDWORD)hdr.c_str());
912 DWORD resplen = in.gcount();
913 m_lpECB->WriteClient(m_lpECB->ConnID, buf, &resplen, HSE_IO_SYNC);
915 return HSE_STATUS_SUCCESS;
917 long sendRedirect(const char* url) {
918 HTTPResponse::sendRedirect(url);
919 string hdr=string("Location: ") + url + "\r\n"
920 "Content-Type: text/html\r\n"
921 "Content-Length: 40\r\n"
922 "Expires: Wed, 01 Jan 1997 12:00:00 GMT\r\n"
923 "Cache-Control: private,no-store,no-cache,max-age=0\r\n";
924 for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
925 hdr += i->first + ": " + i->second + "\r\n";
927 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, "302 Moved", 0, (LPDWORD)hdr.c_str());
928 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
930 m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)redmsg, &resplen, HSE_IO_SYNC);
931 return HSE_STATUS_SUCCESS;
933 // Decline happens in the POST processor if this isn't the shire url
934 // Note that it can also happen with HTAccess, but we don't support that, yet.
935 long returnDecline() {
936 return WriteClientError(
938 "ISAPI extension can only be invoked to process Shibboleth protocol requests."
939 "Make sure the mapped file extension doesn't match actual content."
943 return HSE_STATUS_SUCCESS;
946 const vector<string>& getClientCertificates() const {
947 if (m_certs.empty()) {
948 char CertificateBuf[8192];
949 CERT_CONTEXT_EX ccex;
950 ccex.cbAllocated = sizeof(CertificateBuf);
951 ccex.CertContext.pbCertEncoded = (BYTE*)CertificateBuf;
952 DWORD dwSize = sizeof(ccex);
954 if (m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_GET_CERT_INFO_EX, (LPVOID)&ccex, (LPDWORD)dwSize, nullptr)) {
955 if (ccex.CertContext.cbCertEncoded) {
957 XMLByte* serialized = Base64::encode(reinterpret_cast<XMLByte*>(CertificateBuf), ccex.CertContext.cbCertEncoded, &outlen);
958 m_certs.push_back(reinterpret_cast<char*>(serialized));
959 #ifdef SHIBSP_XERCESC_HAS_XMLBYTE_RELEASE
960 XMLString::release(&serialized);
962 XMLString::release((char**)&serialized);
970 // Not used in the extension.
971 void clearHeader(const char* rawname, const char* cginame) { throw runtime_error("clearHeader not implemented"); }
972 void setHeader(const char* name, const char* value) { throw runtime_error("setHeader not implemented"); }
973 void setRemoteUser(const char* user) { throw runtime_error("setRemoteUser not implemented"); }
976 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
979 ostringstream threadid;
980 threadid << "[" << getpid() << "] isapi_shib_extension" << '\0';
981 xmltooling::NDC ndc(threadid.str().c_str());
983 // Determine web site number. This can't really fail, I don't think.
985 GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
987 // Match site instance to host name, skip if no match.
988 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
989 if (map_i==g_Sites.end())
990 return WriteClientError(lpECB, "Shibboleth Extension not configured for web site (check ISAPI mappings in SP configuration).");
992 ShibTargetIsapiE ste(lpECB, map_i->second);
993 pair<bool,long> res = ste.getServiceProvider().doHandler(ste);
994 if (res.first) return res.second;
996 return WriteClientError(lpECB, "Shibboleth Extension failed to process request");
1000 return WriteClientError(lpECB,"Out of Memory");
1003 if (e==ERROR_NO_DATA)
1004 return WriteClientError(lpECB,"A required variable or header was empty.");
1006 return WriteClientError(lpECB,"Server detected unexpected IIS error.");
1008 catch (exception& e) {
1009 LogEvent(nullptr, EVENTLOG_ERROR_TYPE, 2100, nullptr, e.what());
1010 return WriteClientError(lpECB,"Shibboleth Extension caught an exception, check Event Log for details.");
1013 LogEvent(nullptr, EVENTLOG_ERROR_TYPE, 2100, nullptr, "Shibboleth Extension threw an unknown exception.");
1015 return WriteClientError(lpECB,"Shibboleth Extension threw an unknown exception.");
1019 // If we get here we've got an error.
1020 return HSE_STATUS_ERROR;