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>
43 #include <boost/lexical_cast.hpp>
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;
59 using namespace boost;
64 static const XMLCh path[] = UNICODE_LITERAL_4(p,a,t,h);
65 static const XMLCh validate[] = UNICODE_LITERAL_8(v,a,l,i,d,a,t,e);
66 static const XMLCh name[] = UNICODE_LITERAL_4(n,a,m,e);
67 static const XMLCh port[] = UNICODE_LITERAL_4(p,o,r,t);
68 static const XMLCh sslport[] = UNICODE_LITERAL_7(s,s,l,p,o,r,t);
69 static const XMLCh scheme[] = UNICODE_LITERAL_6(s,c,h,e,m,e);
70 static const XMLCh id[] = UNICODE_LITERAL_2(i,d);
71 static const XMLCh Alias[] = UNICODE_LITERAL_5(A,l,i,a,s);
72 static const XMLCh Site[] = UNICODE_LITERAL_4(S,i,t,e);
75 site_t(const DOMElement* e)
76 : m_name(XMLHelper::getAttrString(e, "", name)),
77 m_scheme(XMLHelper::getAttrString(e, "", scheme)),
78 m_port(XMLHelper::getAttrString(e, "", port)),
79 m_sslport(XMLHelper::getAttrString(e, "", sslport))
81 e = XMLHelper::getFirstChildElement(e, Alias);
83 if (e->hasChildNodes()) {
84 auto_ptr_char alias(e->getTextContent());
85 m_aliases.insert(alias.get());
87 e = XMLHelper::getNextSiblingElement(e, Alias);
90 string m_scheme,m_port,m_sslport,m_name;
91 set<string> m_aliases;
95 SPConfig* g_Config = nullptr;
96 map<string,site_t> g_Sites;
97 bool g_bNormalizeRequest = true;
98 string g_unsetHeaderValue,g_spoofKey;
99 bool g_checkSpoofing = true;
100 bool g_catchAll = false;
101 bool g_bSafeHeaderNames = false;
102 vector<string> g_NoCerts;
106 LPCSTR lpUNCServerName,
112 LPCSTR messages[] = {message, nullptr};
114 HANDLE hElog = RegisterEventSource(lpUNCServerName, "Shibboleth ISAPI Filter");
115 BOOL res = ReportEvent(hElog, wType, 0, dwEventID, lpUserSid, 1, 0, messages, nullptr);
116 return (DeregisterEventSource(hElog) && res);
119 void _my_invalid_parameter_handler(
120 const wchar_t * expression,
121 const wchar_t * function,
122 const wchar_t * file,
130 extern "C" __declspec(dllexport) BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID)
132 if (fdwReason == DLL_PROCESS_ATTACH)
133 g_hinstDLL = hinstDLL;
137 extern "C" BOOL WINAPI GetExtensionVersion(HSE_VERSION_INFO* pVer)
143 LogEvent(nullptr, EVENTLOG_ERROR_TYPE, 2100, nullptr,
144 "Extension mode startup not possible, is the DLL loaded as a filter?");
148 pVer->dwExtensionVersion = HSE_VERSION;
149 strncpy(pVer->lpszExtensionDesc,"Shibboleth ISAPI Extension",HSE_MAX_EXT_DLL_NAME_LEN-1);
153 extern "C" BOOL WINAPI TerminateExtension(DWORD)
155 return TRUE; // cleanup should happen when filter unloads
158 extern "C" BOOL WINAPI GetFilterVersion(PHTTP_FILTER_VERSION pVer)
163 LogEvent(nullptr, EVENTLOG_ERROR_TYPE, 2100, nullptr,
164 "Reentrant filter initialization, ignoring...");
168 g_Config = &SPConfig::getConfig();
169 g_Config->setFeatures(
172 SPConfig::RequestMapping |
173 SPConfig::InProcess |
177 if (!g_Config->init()) {
179 LogEvent(nullptr, EVENTLOG_ERROR_TYPE, 2100, nullptr,
180 "Filter startup failed during library initialization, check native log for help.");
185 if (!g_Config->instantiate(nullptr, true))
186 throw runtime_error("unknown error");
188 catch (std::exception& ex) {
191 LogEvent(nullptr, EVENTLOG_ERROR_TYPE, 2100, nullptr, ex.what());
192 LogEvent(nullptr, EVENTLOG_ERROR_TYPE, 2100, nullptr,
193 "Filter startup failed to load configuration, check native log for details.");
197 // Access implementation-specifics and site mappings.
198 ServiceProvider* sp = g_Config->getServiceProvider();
200 const PropertySet* props = sp->getPropertySet("InProcess");
202 pair<bool,bool> flag = props->getBool("checkSpoofing");
203 g_checkSpoofing = !flag.first || flag.second;
204 flag = props->getBool("catchAll");
205 g_catchAll = flag.first && flag.second;
207 pair<bool,const char*> unsetValue = props->getString("unsetHeaderValue");
208 if (unsetValue.first)
209 g_unsetHeaderValue = unsetValue.second;
210 if (g_checkSpoofing) {
211 unsetValue = props->getString("spoofKey");
212 if (unsetValue.first)
213 g_spoofKey = unsetValue.second;
215 _invalid_parameter_handler old = _set_invalid_parameter_handler(_my_invalid_parameter_handler);
216 unsigned int randkey=0,randkey2=0,randkey3=0,randkey4=0;
217 if (rand_s(&randkey) == 0 && rand_s(&randkey2) == 0 && rand_s(&randkey3) == 0 && rand_s(&randkey4) == 0) {
218 _set_invalid_parameter_handler(old);
219 g_spoofKey = lexical_cast<string>(randkey) + lexical_cast<string>(randkey2) +
220 lexical_cast<string>(randkey3) + lexical_cast<string>(randkey4);
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 string id(XMLHelper::getAttrString(child, "", id));
244 g_Sites.insert(make_pair(id, 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 /****************************************************************************/
323 class ShibTargetIsapiF : public AbstractSPRequest
325 PHTTP_FILTER_CONTEXT m_pfc;
326 PHTTP_FILTER_PREPROC_HEADERS m_pn;
327 multimap<string,string> m_headers;
329 string m_scheme,m_hostname;
330 mutable string m_remote_addr,m_content_type,m_method;
335 ShibTargetIsapiF(PHTTP_FILTER_CONTEXT pfc, PHTTP_FILTER_PREPROC_HEADERS pn, const site_t& site)
336 : AbstractSPRequest(SHIBSP_LOGCAT ".ISAPI"), m_pfc(pfc), m_pn(pn), m_allhttp(4096), m_firsttime(true) {
338 // URL path always come from IIS.
340 GetHeader("url",var,256,false);
343 // Port may come from IIS or from site def.
344 if (!g_bNormalizeRequest || (pfc->fIsSecurePort && site.m_sslport.empty()) || (!pfc->fIsSecurePort && site.m_port.empty())) {
345 GetServerVariable("SERVER_PORT",var,10);
347 m_port = pfc->fIsSecurePort ? 443 : 80;
353 else if (pfc->fIsSecurePort) {
354 m_port = atoi(site.m_sslport.c_str());
357 m_port = atoi(site.m_port.c_str());
360 // Scheme may come from site def or be derived from IIS.
361 m_scheme=site.m_scheme;
362 if (m_scheme.empty() || !g_bNormalizeRequest)
363 m_scheme=pfc->fIsSecurePort ? "https" : "http";
365 GetServerVariable("SERVER_NAME",var,32);
367 // Make sure SERVER_NAME is "authorized" for use on this site. If not, or empty, set to canonical name.
369 m_hostname = site.m_name;
373 if (site.m_name != m_hostname && site.m_aliases.find(m_hostname) == site.m_aliases.end())
374 m_hostname = site.m_name;
377 if (!g_spoofKey.empty()) {
378 GetHeader("ShibSpoofCheck:", var, 32, false);
379 if (!var.empty() && g_spoofKey == (char*)var)
384 log(SPDebug, "ISAPI filter running more than once");
386 ~ShibTargetIsapiF() { }
388 const char* getScheme() const {
389 return m_scheme.c_str();
391 const char* getHostname() const {
392 return m_hostname.c_str();
394 int getPort() const {
397 const char* getQueryString() const {
398 const char* uri = getRequestURI();
399 uri = (uri ? strchr(uri, '?') : nullptr);
400 return uri ? (uri + 1) : nullptr;
402 const char* getMethod() const {
403 if (m_method.empty()) {
405 GetServerVariable("HTTP_METHOD",var,5,false);
409 return m_method.c_str();
411 string getContentType() const {
412 if (m_content_type.empty()) {
414 GetServerVariable("HTTP_CONTENT_TYPE",var,32,false);
416 m_content_type = var;
418 return m_content_type;
420 string getRemoteAddr() const {
421 m_remote_addr = AbstractSPRequest::getRemoteAddr();
422 if (m_remote_addr.empty()) {
424 GetServerVariable("REMOTE_ADDR",var,16,false);
428 return m_remote_addr;
430 void log(SPLogLevel level, const string& msg) const {
431 AbstractSPRequest::log(level,msg);
433 LogEvent(nullptr, EVENTLOG_ERROR_TYPE, 2100, nullptr, msg.c_str());
435 string makeSafeHeader(const char* rawname) const {
437 for (; *rawname; ++rawname) {
438 if (isalnum(*rawname))
443 void clearHeader(const char* rawname, const char* cginame) {
444 if (g_checkSpoofing && m_firsttime) {
445 if (m_allhttp.empty())
446 GetServerVariable( "ALL_HTTP", m_allhttp, 4096, false);
447 if (!m_allhttp.empty()) {
448 string hdr = g_bSafeHeaderNames ? ("HTTP_" + makeSafeHeader(cginame + 5)) : (string(cginame) + ':');
449 if (strstr(m_allhttp, hdr.c_str()))
450 throw opensaml::SecurityPolicyException("Attempt to spoof header ($1) was detected.", params(1, hdr.c_str()));
453 if (g_bSafeHeaderNames) {
454 string hdr = makeSafeHeader(rawname);
455 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), const_cast<char*>(g_unsetHeaderValue.c_str()));
457 else if (!strcmp(rawname,"REMOTE_USER")) {
458 m_pn->SetHeader(m_pfc, "remote-user:", const_cast<char*>(g_unsetHeaderValue.c_str()));
459 m_pn->SetHeader(m_pfc, "remote_user:", const_cast<char*>(g_unsetHeaderValue.c_str()));
462 string hdr = string(rawname) + ':';
463 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), const_cast<char*>(g_unsetHeaderValue.c_str()));
466 void setHeader(const char* name, const char* value) {
467 string hdr = g_bSafeHeaderNames ? makeSafeHeader(name) : (string(name) + ':');
468 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), const_cast<char*>(value));
470 string getSecureHeader(const char* name) const {
471 string hdr = g_bSafeHeaderNames ? makeSafeHeader(name) : (string(name) + ':');
473 GetHeader(const_cast<char*>(hdr.c_str()), buf, 256, false);
474 return string(buf.empty() ? "" : buf);
476 string getHeader(const char* name) const {
480 GetHeader(const_cast<char*>(hdr.c_str()), buf, 256, false);
481 return string(buf.empty() ? "" : buf);
483 void setRemoteUser(const char* user) {
484 setHeader("remote-user", user);
486 m_pfc->pFilterContext = nullptr;
487 else if (m_pfc->pFilterContext = m_pfc->AllocMem(m_pfc, sizeof(char) * (strlen(user) + 1), 0))
488 strcpy(reinterpret_cast<char*>(m_pfc->pFilterContext), user);
490 string getRemoteUser() const {
491 return getSecureHeader("remote-user");
493 void setResponseHeader(const char* name, const char* value) {
494 HTTPResponse::setResponseHeader(name, value);
498 m_headers.insert(make_pair(name,value));
500 m_headers.erase(name);
503 long sendResponse(istream& in, long status) {
504 string hdr = string("Connection: close\r\n");
505 for (multimap<string,string>::const_iterator i = m_headers.begin(); i != m_headers.end(); ++i)
506 hdr += i->first + ": " + i->second + "\r\n";
508 const char* codestr="200 OK";
510 case XMLTOOLING_HTTP_STATUS_NOTMODIFIED: codestr="304 Not Modified"; break;
511 case XMLTOOLING_HTTP_STATUS_UNAUTHORIZED: codestr="401 Authorization Required"; break;
512 case XMLTOOLING_HTTP_STATUS_FORBIDDEN: codestr="403 Forbidden"; break;
513 case XMLTOOLING_HTTP_STATUS_NOTFOUND: codestr="404 Not Found"; break;
514 case XMLTOOLING_HTTP_STATUS_ERROR: codestr="500 Server Error"; break;
516 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, (void*)codestr, (ULONG_PTR)hdr.c_str(), 0);
520 DWORD resplen = in.gcount();
521 m_pfc->WriteClient(m_pfc, buf, &resplen, 0);
523 return SF_STATUS_REQ_FINISHED;
525 long sendRedirect(const char* url) {
526 HTTPResponse::sendRedirect(url);
527 string hdr=string("Location: ") + url + "\r\n"
528 "Content-Type: text/html\r\n"
529 "Content-Length: 40\r\n"
530 "Expires: Wed, 01 Jan 1997 12:00:00 GMT\r\n"
531 "Cache-Control: private,no-store,no-cache,max-age=0\r\n";
532 for (multimap<string,string>::const_iterator i = m_headers.begin(); i != m_headers.end(); ++i)
533 hdr += i->first + ": " + i->second + "\r\n";
535 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, "302 Please Wait", (ULONG_PTR)hdr.c_str(), 0);
536 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
538 m_pfc->WriteClient(m_pfc, (LPVOID)redmsg, &resplen, 0);
539 return SF_STATUS_REQ_FINISHED;
541 long returnDecline() {
542 return SF_STATUS_REQ_NEXT_NOTIFICATION;
545 return SF_STATUS_REQ_NEXT_NOTIFICATION;
548 const vector<string>& getClientCertificates() const {
552 // The filter never processes the POST, so stub these methods.
553 long getContentLength() const { throw IOException("The request's Content-Length is not available to an ISAPI filter."); }
554 const char* getRequestBody() const { throw IOException("The request body is not available to an ISAPI filter."); }
556 void GetServerVariable(LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true) const {
561 while (!m_pfc->GetServerVariable(m_pfc,lpszVariable,s,&size)) {
562 // Grumble. Check the error.
563 DWORD e = GetLastError();
564 if (e == ERROR_INSUFFICIENT_BUFFER)
569 if (bRequired && s.empty())
570 log(SPRequest::SPError, string("missing required server variable: ") + lpszVariable);
573 void GetHeader(LPSTR lpszName, dynabuf& s, DWORD size=80, bool bRequired=true) const {
578 while (!m_pn->GetHeader(m_pfc,lpszName,s,&size)) {
579 // Grumble. Check the error.
580 DWORD e = GetLastError();
581 if (e == ERROR_INSUFFICIENT_BUFFER)
586 if (bRequired && s.empty())
587 log(SPRequest::SPError, string("missing required header: ") + lpszName);
591 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const char* msg)
593 LogEvent(nullptr, EVENTLOG_ERROR_TYPE, 2100, nullptr, msg);
594 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
595 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(ULONG_PTR)ctype,0);
596 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Filter Error</TITLE></HEAD><BODY>"
597 "<H1>Shibboleth Filter Error</H1>";
598 DWORD resplen=strlen(xmsg);
599 pfc->WriteClient(pfc,(LPVOID)xmsg,&resplen,0);
601 pfc->WriteClient(pfc,(LPVOID)msg,&resplen,0);
602 static const char* xmsg2="</BODY></HTML>";
603 resplen=strlen(xmsg2);
604 pfc->WriteClient(pfc,(LPVOID)xmsg2,&resplen,0);
605 return SF_STATUS_REQ_FINISHED;
608 void GetServerVariable(PHTTP_FILTER_CONTEXT pfc, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
614 while (!pfc->GetServerVariable(pfc,lpszVariable,s,&size)) {
615 // Grumble. Check the error.
616 DWORD e=GetLastError();
617 if (e==ERROR_INSUFFICIENT_BUFFER)
622 if (bRequired && s.empty()) {
623 string msg = string("Missing required server variable: ") + lpszVariable;
624 LogEvent(nullptr, EVENTLOG_ERROR_TYPE, 2100, nullptr, msg.c_str());
629 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
631 // Is this a log notification?
632 if (notificationType == SF_NOTIFY_LOG) {
633 if (pfc->pFilterContext)
634 ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName = reinterpret_cast<char*>(pfc->pFilterContext);
635 return SF_STATUS_REQ_NEXT_NOTIFICATION;
638 PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
640 // Determine web site number. This can't really fail, I don't think.
642 GetServerVariable(pfc,"INSTANCE_ID",buf,10);
644 return WriteClientError(pfc, "Shibboleth Filter failed to obtain INSTANCE_ID server variable.");
646 // Match site instance to host name, skip if no match.
647 map<string,site_t>::const_iterator map_i = g_Sites.find(static_cast<char*>(buf));
648 if (map_i == g_Sites.end())
649 return SF_STATUS_REQ_NEXT_NOTIFICATION;
651 string threadid("[");
652 threadid += lexical_cast<string>(getpid()) + "] isapi_shib";
653 xmltooling::NDC ndc(threadid.c_str());
655 ShibTargetIsapiF stf(pfc, pn, map_i->second);
657 pair<bool,long> res = stf.getServiceProvider().doAuthentication(stf);
658 if (!g_spoofKey.empty())
659 pn->SetHeader(pfc, "ShibSpoofCheck:", const_cast<char*>(g_spoofKey.c_str()));
660 if (res.first) return res.second;
662 res = stf.getServiceProvider().doExport(stf);
663 if (res.first) return res.second;
665 res = stf.getServiceProvider().doAuthorization(stf);
666 if (res.first) return res.second;
668 return SF_STATUS_REQ_NEXT_NOTIFICATION;
671 return WriteClientError(pfc, "Out of Memory");
674 if (e==ERROR_NO_DATA)
675 return WriteClientError(pfc, "A required variable or header was empty.");
677 return WriteClientError(pfc, "Shibboleth Filter detected unexpected IIS error.");
679 catch (std::exception& e) {
680 LogEvent(nullptr, EVENTLOG_ERROR_TYPE, 2100, nullptr, e.what());
681 return WriteClientError(pfc, "Shibboleth Filter caught an exception, check Event Log for details.");
684 LogEvent(nullptr, EVENTLOG_ERROR_TYPE, 2100, nullptr, "Shibboleth Filter threw an unknown exception.");
686 return WriteClientError(pfc, "Shibboleth Filter threw an unknown exception.");
690 return WriteClientError(pfc, "Shibboleth Filter reached unreachable code, save my walrus!");
694 /****************************************************************************/
697 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* msg)
699 LogEvent(nullptr, EVENTLOG_ERROR_TYPE, 2100, nullptr, msg);
700 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
701 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
702 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Error</TITLE></HEAD><BODY><H1>Shibboleth Error</H1>";
703 DWORD resplen=strlen(xmsg);
704 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg,&resplen,HSE_IO_SYNC);
706 lpECB->WriteClient(lpECB->ConnID,(LPVOID)msg,&resplen,HSE_IO_SYNC);
707 static const char* xmsg2="</BODY></HTML>";
708 resplen=strlen(xmsg2);
709 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg2,&resplen,HSE_IO_SYNC);
710 return HSE_STATUS_SUCCESS;
714 class ShibTargetIsapiE : public AbstractSPRequest
716 LPEXTENSION_CONTROL_BLOCK m_lpECB;
717 multimap<string,string> m_headers;
718 mutable vector<string> m_certs;
719 mutable string m_body;
720 mutable bool m_gotBody;
722 string m_scheme,m_hostname,m_uri;
723 mutable string m_remote_addr,m_remote_user;
726 ShibTargetIsapiE(LPEXTENSION_CONTROL_BLOCK lpECB, const site_t& site)
727 : AbstractSPRequest(SHIBSP_LOGCAT ".ISAPI"), m_lpECB(lpECB), m_gotBody(false) {
729 GetServerVariable("HTTPS",ssl,5);
730 bool SSL=(ssl=="on" || ssl=="ON");
732 // Scheme may come from site def or be derived from IIS.
733 m_scheme = site.m_scheme;
734 if (m_scheme.empty() || !g_bNormalizeRequest)
735 m_scheme = SSL ? "https" : "http";
737 // URL path always come from IIS.
739 GetServerVariable("URL",url,255);
741 // Port may come from IIS or from site def.
742 if (!g_bNormalizeRequest || (SSL && site.m_sslport.empty()) || (!SSL && site.m_port.empty())) {
744 GetServerVariable("SERVER_PORT",port,10);
746 m_port = SSL ? 443 : 80;
753 m_port = atoi(site.m_sslport.c_str());
756 m_port = atoi(site.m_port.c_str());
760 GetServerVariable("SERVER_NAME", var, 32);
762 m_hostname = site.m_name;
765 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
767 if (site.m_name != m_hostname && site.m_aliases.find(m_hostname) == site.m_aliases.end())
768 m_hostname = site.m_name;
772 * IIS screws us over on PATH_INFO (the hits keep on coming). We need to figure out if
773 * the server is set up for proper PATH_INFO handling, or "IIS sucks rabid weasels mode",
774 * which is the default. No perfect way to tell, but we can take a good guess by checking
775 * whether the URL is a substring of the PATH_INFO:
777 * e.g. for /Shibboleth.sso/SAML/POST
779 * Bad mode (default):
780 * URL: /Shibboleth.sso
781 * PathInfo: /Shibboleth.sso/SAML/POST
784 * URL: /Shibboleth.sso
785 * PathInfo: /SAML/POST
790 // Clearly we're only in bad mode if path info exists at all.
791 if (lpECB->lpszPathInfo && *(lpECB->lpszPathInfo)) {
792 if (strstr(lpECB->lpszPathInfo,url))
793 // Pretty good chance we're in bad mode, unless the PathInfo repeats the path itself.
794 uri = lpECB->lpszPathInfo;
798 uri += lpECB->lpszPathInfo;
801 else if (!url.empty()) {
805 // For consistency with Apache, let's add the query string.
806 if (lpECB->lpszQueryString && *(lpECB->lpszQueryString)) {
808 uri += lpECB->lpszQueryString;
811 setRequestURI(uri.c_str());
813 ~ShibTargetIsapiE() {}
815 const char* getScheme() const {
816 return m_scheme.c_str();
818 const char* getHostname() const {
819 return m_hostname.c_str();
821 int getPort() const {
824 const char* getMethod() const {
825 return m_lpECB->lpszMethod;
827 string getContentType() const {
828 return m_lpECB->lpszContentType ? m_lpECB->lpszContentType : "";
830 long getContentLength() const {
831 return m_lpECB->cbTotalBytes;
833 string getRemoteUser() const {
834 if (m_remote_user.empty()) {
836 GetServerVariable("REMOTE_USER", var, 32, false);
840 return m_remote_user;
842 string getRemoteAddr() const {
843 m_remote_addr = AbstractSPRequest::getRemoteAddr();
844 if (m_remote_addr.empty()) {
846 GetServerVariable("REMOTE_ADDR", var, 16, false);
850 return m_remote_addr;
852 void log(SPLogLevel level, const string& msg) const {
853 AbstractSPRequest::log(level,msg);
855 LogEvent(nullptr, EVENTLOG_ERROR_TYPE, 2100, nullptr, msg.c_str());
857 string getHeader(const char* name) const {
859 for (; *name; ++name) {
863 hdr += toupper(*name);
866 GetServerVariable(const_cast<char*>(hdr.c_str()), buf, 128, false);
867 return buf.empty() ? "" : buf;
869 void setResponseHeader(const char* name, const char* value) {
870 HTTPResponse::setResponseHeader(name, value);
874 m_headers.insert(make_pair(name,value));
876 m_headers.erase(name);
879 const char* getQueryString() const {
880 return m_lpECB->lpszQueryString;
882 const char* getRequestBody() const {
884 return m_body.c_str();
885 if (m_lpECB->cbTotalBytes > 1024*1024) // 1MB?
886 throw opensaml::SecurityPolicyException("Size of request body exceeded 1M size limit.");
887 else if (m_lpECB->cbTotalBytes > m_lpECB->cbAvailable) {
889 DWORD datalen=m_lpECB->cbTotalBytes;
890 if (m_lpECB->cbAvailable > 0) {
891 m_body.assign(reinterpret_cast<char*>(m_lpECB->lpbData),m_lpECB->cbAvailable);
892 datalen-=m_lpECB->cbAvailable;
897 BOOL ret = m_lpECB->ReadClient(m_lpECB->ConnID, buf, &buflen);
900 _snprintf(message, 64, "Error reading request body from browser (%x).", GetLastError());
901 throw IOException(message);
904 throw IOException("Socket closed while reading request body from browser.");
905 m_body.append(buf, buflen);
909 else if (m_lpECB->cbAvailable) {
911 m_body.assign(reinterpret_cast<char*>(m_lpECB->lpbData),m_lpECB->cbAvailable);
913 return m_body.c_str();
915 long sendResponse(istream& in, long status) {
916 string hdr = string("Connection: close\r\n");
917 for (multimap<string,string>::const_iterator i = m_headers.begin(); i != m_headers.end(); ++i)
918 hdr += i->first + ": " + i->second + "\r\n";
920 const char* codestr="200 OK";
922 case XMLTOOLING_HTTP_STATUS_NOTMODIFIED: codestr="304 Not Modified"; break;
923 case XMLTOOLING_HTTP_STATUS_UNAUTHORIZED: codestr="401 Authorization Required"; break;
924 case XMLTOOLING_HTTP_STATUS_FORBIDDEN: codestr="403 Forbidden"; break;
925 case XMLTOOLING_HTTP_STATUS_NOTFOUND: codestr="404 Not Found"; break;
926 case XMLTOOLING_HTTP_STATUS_ERROR: codestr="500 Server Error"; break;
928 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, (void*)codestr, 0, (LPDWORD)hdr.c_str());
932 DWORD resplen = in.gcount();
933 m_lpECB->WriteClient(m_lpECB->ConnID, buf, &resplen, HSE_IO_SYNC);
935 return HSE_STATUS_SUCCESS;
937 long sendRedirect(const char* url) {
938 HTTPResponse::sendRedirect(url);
939 string hdr=string("Location: ") + url + "\r\n"
940 "Content-Type: text/html\r\n"
941 "Content-Length: 40\r\n"
942 "Expires: Wed, 01 Jan 1997 12:00:00 GMT\r\n"
943 "Cache-Control: private,no-store,no-cache,max-age=0\r\n";
944 for (multimap<string,string>::const_iterator i = m_headers.begin(); i != m_headers.end(); ++i)
945 hdr += i->first + ": " + i->second + "\r\n";
947 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, "302 Moved", 0, (LPDWORD)hdr.c_str());
948 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
950 m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)redmsg, &resplen, HSE_IO_SYNC);
951 return HSE_STATUS_SUCCESS;
953 // Decline happens in the POST processor if this isn't the handler url
954 // Note that it can also happen with HTAccess, but we don't support that, yet.
955 long returnDecline() {
956 return WriteClientError(
958 "ISAPI extension can only be invoked to process Shibboleth protocol requests."
959 "Make sure the mapped file extension doesn't match actual content."
963 return HSE_STATUS_SUCCESS;
966 const vector<string>& getClientCertificates() const {
967 if (m_certs.empty()) {
968 char CertificateBuf[8192];
969 CERT_CONTEXT_EX ccex;
970 ccex.cbAllocated = sizeof(CertificateBuf);
971 ccex.CertContext.pbCertEncoded = (BYTE*)CertificateBuf;
972 DWORD dwSize = sizeof(ccex);
974 if (m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_GET_CERT_INFO_EX, (LPVOID)&ccex, (LPDWORD)dwSize, nullptr)) {
975 if (ccex.CertContext.cbCertEncoded) {
977 XMLByte* serialized = Base64::encode(reinterpret_cast<XMLByte*>(CertificateBuf), ccex.CertContext.cbCertEncoded, &outlen);
978 m_certs.push_back(reinterpret_cast<char*>(serialized));
979 #ifdef SHIBSP_XERCESC_HAS_XMLBYTE_RELEASE
980 XMLString::release(&serialized);
982 XMLString::release((char**)&serialized);
990 // Not used in the extension.
991 void clearHeader(const char* rawname, const char* cginame) { throw runtime_error("clearHeader not implemented"); }
992 void setHeader(const char* name, const char* value) { throw runtime_error("setHeader not implemented"); }
993 void setRemoteUser(const char* user) { throw runtime_error("setRemoteUser not implemented"); }
995 void GetServerVariable(LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true) const {
1000 while (!m_lpECB->GetServerVariable(m_lpECB->ConnID,lpszVariable,s,&size)) {
1001 // Grumble. Check the error.
1002 DWORD e=GetLastError();
1003 if (e==ERROR_INSUFFICIENT_BUFFER)
1008 if (bRequired && s.empty())
1009 log(SPRequest::SPError, string("missing required server variable: ") + lpszVariable);
1013 void GetServerVariable(LPEXTENSION_CONTROL_BLOCK lpECB, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
1019 while (!lpECB->GetServerVariable(lpECB->ConnID,lpszVariable,s,&size)) {
1020 // Grumble. Check the error.
1021 DWORD e=GetLastError();
1022 if (e==ERROR_INSUFFICIENT_BUFFER)
1027 if (bRequired && s.empty()) {
1028 string msg = string("Missing required server variable: ") + lpszVariable;
1029 LogEvent(nullptr, EVENTLOG_ERROR_TYPE, 2100, nullptr, msg.c_str());
1033 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
1036 string threadid("[");
1037 threadid += lexical_cast<string>(getpid()) + "] isapi_shib_extension";
1038 xmltooling::NDC ndc(threadid.c_str());
1040 // Determine web site number. This can't really fail, I don't think.
1042 GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
1044 return WriteClientError(lpECB, "Shibboleth Extension failed to obtain INSTANCE_ID server variable.");
1046 // Match site instance to host name, skip if no match.
1047 map<string,site_t>::const_iterator map_i = g_Sites.find(static_cast<char*>(buf));
1048 if (map_i == g_Sites.end())
1049 return WriteClientError(lpECB, "Shibboleth Extension not configured for web site (check ISAPI mappings in SP configuration).");
1051 ShibTargetIsapiE ste(lpECB, map_i->second);
1052 pair<bool,long> res = ste.getServiceProvider().doHandler(ste);
1053 if (res.first) return res.second;
1055 return WriteClientError(lpECB, "Shibboleth Extension failed to process request");
1059 return WriteClientError(lpECB, "Out of Memory");
1062 if (e==ERROR_NO_DATA)
1063 return WriteClientError(lpECB, "A required variable or header was empty.");
1065 return WriteClientError(lpECB, "Server detected unexpected IIS error.");
1067 catch (std::exception& e) {
1068 LogEvent(nullptr, EVENTLOG_ERROR_TYPE, 2100, nullptr, e.what());
1069 return WriteClientError(lpECB, "Shibboleth Extension caught an exception, check Event Log for details.");
1072 LogEvent(nullptr, EVENTLOG_ERROR_TYPE, 2100, nullptr, "Shibboleth Extension threw an unknown exception.");
1074 return WriteClientError(lpECB, "Shibboleth Extension threw an unknown exception.");
1078 // If we get here we've got an error.
1079 return HSE_STATUS_ERROR;