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);
497 m_headers.insert(make_pair(name,value));
499 m_headers.erase(name);
501 long sendResponse(istream& in, long status) {
502 string hdr = string("Connection: close\r\n");
503 for (multimap<string,string>::const_iterator i = m_headers.begin(); i != m_headers.end(); ++i)
504 hdr += i->first + ": " + i->second + "\r\n";
506 const char* codestr="200 OK";
508 case XMLTOOLING_HTTP_STATUS_NOTMODIFIED: codestr="304 Not Modified"; break;
509 case XMLTOOLING_HTTP_STATUS_UNAUTHORIZED: codestr="401 Authorization Required"; break;
510 case XMLTOOLING_HTTP_STATUS_FORBIDDEN: codestr="403 Forbidden"; break;
511 case XMLTOOLING_HTTP_STATUS_NOTFOUND: codestr="404 Not Found"; break;
512 case XMLTOOLING_HTTP_STATUS_ERROR: codestr="500 Server Error"; break;
514 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, (void*)codestr, (ULONG_PTR)hdr.c_str(), 0);
518 DWORD resplen = in.gcount();
519 m_pfc->WriteClient(m_pfc, buf, &resplen, 0);
521 return SF_STATUS_REQ_FINISHED;
523 long sendRedirect(const char* url) {
524 HTTPResponse::sendRedirect(url);
525 string hdr=string("Location: ") + url + "\r\n"
526 "Content-Type: text/html\r\n"
527 "Content-Length: 40\r\n"
528 "Expires: Wed, 01 Jan 1997 12:00:00 GMT\r\n"
529 "Cache-Control: private,no-store,no-cache,max-age=0\r\n";
530 for (multimap<string,string>::const_iterator i = m_headers.begin(); i != m_headers.end(); ++i)
531 hdr += i->first + ": " + i->second + "\r\n";
533 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, "302 Please Wait", (ULONG_PTR)hdr.c_str(), 0);
534 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
536 m_pfc->WriteClient(m_pfc, (LPVOID)redmsg, &resplen, 0);
537 return SF_STATUS_REQ_FINISHED;
539 long returnDecline() {
540 return SF_STATUS_REQ_NEXT_NOTIFICATION;
543 return SF_STATUS_REQ_NEXT_NOTIFICATION;
546 const vector<string>& getClientCertificates() const {
550 // The filter never processes the POST, so stub these methods.
551 long getContentLength() const { throw IOException("The request's Content-Length is not available to an ISAPI filter."); }
552 const char* getRequestBody() const { throw IOException("The request body is not available to an ISAPI filter."); }
554 void GetServerVariable(LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true) const {
559 while (!m_pfc->GetServerVariable(m_pfc,lpszVariable,s,&size)) {
560 // Grumble. Check the error.
561 DWORD e = GetLastError();
562 if (e == ERROR_INSUFFICIENT_BUFFER)
567 if (bRequired && s.empty())
568 log(SPRequest::SPError, string("missing required server variable: ") + lpszVariable);
571 void GetHeader(LPSTR lpszName, dynabuf& s, DWORD size=80, bool bRequired=true) const {
576 while (!m_pn->GetHeader(m_pfc,lpszName,s,&size)) {
577 // Grumble. Check the error.
578 DWORD e = GetLastError();
579 if (e == ERROR_INSUFFICIENT_BUFFER)
584 if (bRequired && s.empty())
585 log(SPRequest::SPError, string("missing required header: ") + lpszName);
589 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const char* msg)
591 LogEvent(nullptr, EVENTLOG_ERROR_TYPE, 2100, nullptr, msg);
592 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
593 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(ULONG_PTR)ctype,0);
594 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Filter Error</TITLE></HEAD><BODY>"
595 "<H1>Shibboleth Filter Error</H1>";
596 DWORD resplen=strlen(xmsg);
597 pfc->WriteClient(pfc,(LPVOID)xmsg,&resplen,0);
599 pfc->WriteClient(pfc,(LPVOID)msg,&resplen,0);
600 static const char* xmsg2="</BODY></HTML>";
601 resplen=strlen(xmsg2);
602 pfc->WriteClient(pfc,(LPVOID)xmsg2,&resplen,0);
603 return SF_STATUS_REQ_FINISHED;
606 void GetServerVariable(PHTTP_FILTER_CONTEXT pfc, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
612 while (!pfc->GetServerVariable(pfc,lpszVariable,s,&size)) {
613 // Grumble. Check the error.
614 DWORD e=GetLastError();
615 if (e==ERROR_INSUFFICIENT_BUFFER)
620 if (bRequired && s.empty()) {
621 string msg = string("Missing required server variable: ") + lpszVariable;
622 LogEvent(nullptr, EVENTLOG_ERROR_TYPE, 2100, nullptr, msg.c_str());
627 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
629 // Is this a log notification?
630 if (notificationType == SF_NOTIFY_LOG) {
631 if (pfc->pFilterContext)
632 ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName = reinterpret_cast<char*>(pfc->pFilterContext);
633 return SF_STATUS_REQ_NEXT_NOTIFICATION;
636 PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
638 // Determine web site number. This can't really fail, I don't think.
640 GetServerVariable(pfc,"INSTANCE_ID",buf,10);
642 return WriteClientError(pfc, "Shibboleth Filter failed to obtain INSTANCE_ID server variable.");
644 // Match site instance to host name, skip if no match.
645 map<string,site_t>::const_iterator map_i = g_Sites.find(static_cast<char*>(buf));
646 if (map_i == g_Sites.end())
647 return SF_STATUS_REQ_NEXT_NOTIFICATION;
649 string threadid("[");
650 threadid += lexical_cast<string>(getpid()) + "] isapi_shib";
651 xmltooling::NDC ndc(threadid.c_str());
653 ShibTargetIsapiF stf(pfc, pn, map_i->second);
655 pair<bool,long> res = stf.getServiceProvider().doAuthentication(stf);
656 if (!g_spoofKey.empty())
657 pn->SetHeader(pfc, "ShibSpoofCheck:", const_cast<char*>(g_spoofKey.c_str()));
658 if (res.first) return res.second;
660 res = stf.getServiceProvider().doExport(stf);
661 if (res.first) return res.second;
663 res = stf.getServiceProvider().doAuthorization(stf);
664 if (res.first) return res.second;
666 return SF_STATUS_REQ_NEXT_NOTIFICATION;
669 return WriteClientError(pfc, "Out of Memory");
672 if (e==ERROR_NO_DATA)
673 return WriteClientError(pfc, "A required variable or header was empty.");
675 return WriteClientError(pfc, "Shibboleth Filter detected unexpected IIS error.");
677 catch (std::exception& e) {
678 LogEvent(nullptr, EVENTLOG_ERROR_TYPE, 2100, nullptr, e.what());
679 return WriteClientError(pfc, "Shibboleth Filter caught an exception, check Event Log for details.");
682 LogEvent(nullptr, EVENTLOG_ERROR_TYPE, 2100, nullptr, "Shibboleth Filter threw an unknown exception.");
684 return WriteClientError(pfc, "Shibboleth Filter threw an unknown exception.");
688 return WriteClientError(pfc, "Shibboleth Filter reached unreachable code, save my walrus!");
692 /****************************************************************************/
695 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* msg)
697 LogEvent(nullptr, EVENTLOG_ERROR_TYPE, 2100, nullptr, msg);
698 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
699 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
700 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Error</TITLE></HEAD><BODY><H1>Shibboleth Error</H1>";
701 DWORD resplen=strlen(xmsg);
702 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg,&resplen,HSE_IO_SYNC);
704 lpECB->WriteClient(lpECB->ConnID,(LPVOID)msg,&resplen,HSE_IO_SYNC);
705 static const char* xmsg2="</BODY></HTML>";
706 resplen=strlen(xmsg2);
707 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg2,&resplen,HSE_IO_SYNC);
708 return HSE_STATUS_SUCCESS;
712 class ShibTargetIsapiE : public AbstractSPRequest
714 LPEXTENSION_CONTROL_BLOCK m_lpECB;
715 multimap<string,string> m_headers;
716 mutable vector<string> m_certs;
717 mutable string m_body;
718 mutable bool m_gotBody;
720 string m_scheme,m_hostname,m_uri;
721 mutable string m_remote_addr,m_remote_user;
724 ShibTargetIsapiE(LPEXTENSION_CONTROL_BLOCK lpECB, const site_t& site)
725 : AbstractSPRequest(SHIBSP_LOGCAT".ISAPI"), m_lpECB(lpECB), m_gotBody(false) {
727 GetServerVariable("HTTPS",ssl,5);
728 bool SSL=(ssl=="on" || ssl=="ON");
730 // Scheme may come from site def or be derived from IIS.
731 m_scheme = site.m_scheme;
732 if (m_scheme.empty() || !g_bNormalizeRequest)
733 m_scheme = SSL ? "https" : "http";
735 // URL path always come from IIS.
737 GetServerVariable("URL",url,255);
739 // Port may come from IIS or from site def.
740 if (!g_bNormalizeRequest || (SSL && site.m_sslport.empty()) || (!SSL && site.m_port.empty())) {
742 GetServerVariable("SERVER_PORT",port,10);
744 m_port = SSL ? 443 : 80;
751 m_port = atoi(site.m_sslport.c_str());
754 m_port = atoi(site.m_port.c_str());
758 GetServerVariable("SERVER_NAME", var, 32);
760 m_hostname = site.m_name;
763 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
765 if (site.m_name != m_hostname && site.m_aliases.find(m_hostname) == site.m_aliases.end())
766 m_hostname = site.m_name;
770 * IIS screws us over on PATH_INFO (the hits keep on coming). We need to figure out if
771 * the server is set up for proper PATH_INFO handling, or "IIS sucks rabid weasels mode",
772 * which is the default. No perfect way to tell, but we can take a good guess by checking
773 * whether the URL is a substring of the PATH_INFO:
775 * e.g. for /Shibboleth.sso/SAML/POST
777 * Bad mode (default):
778 * URL: /Shibboleth.sso
779 * PathInfo: /Shibboleth.sso/SAML/POST
782 * URL: /Shibboleth.sso
783 * PathInfo: /SAML/POST
788 // Clearly we're only in bad mode if path info exists at all.
789 if (lpECB->lpszPathInfo && *(lpECB->lpszPathInfo)) {
790 if (strstr(lpECB->lpszPathInfo,url))
791 // Pretty good chance we're in bad mode, unless the PathInfo repeats the path itself.
792 uri = lpECB->lpszPathInfo;
796 uri += lpECB->lpszPathInfo;
799 else if (!url.empty()) {
803 // For consistency with Apache, let's add the query string.
804 if (lpECB->lpszQueryString && *(lpECB->lpszQueryString)) {
806 uri += lpECB->lpszQueryString;
809 setRequestURI(uri.c_str());
811 ~ShibTargetIsapiE() {}
813 const char* getScheme() const {
814 return m_scheme.c_str();
816 const char* getHostname() const {
817 return m_hostname.c_str();
819 int getPort() const {
822 const char* getMethod() const {
823 return m_lpECB->lpszMethod;
825 string getContentType() const {
826 return m_lpECB->lpszContentType ? m_lpECB->lpszContentType : "";
828 long getContentLength() const {
829 return m_lpECB->cbTotalBytes;
831 string getRemoteUser() const {
832 if (m_remote_user.empty()) {
834 GetServerVariable("REMOTE_USER", var, 32, false);
838 return m_remote_user;
840 string getRemoteAddr() const {
841 m_remote_addr = AbstractSPRequest::getRemoteAddr();
842 if (m_remote_addr.empty()) {
844 GetServerVariable("REMOTE_ADDR", var, 16, false);
848 return m_remote_addr;
850 void log(SPLogLevel level, const string& msg) const {
851 AbstractSPRequest::log(level,msg);
853 LogEvent(nullptr, EVENTLOG_ERROR_TYPE, 2100, nullptr, msg.c_str());
855 string getHeader(const char* name) const {
857 for (; *name; ++name) {
861 hdr += toupper(*name);
864 GetServerVariable(const_cast<char*>(hdr.c_str()), buf, 128, false);
865 return buf.empty() ? "" : buf;
867 void setResponseHeader(const char* name, const char* value) {
868 HTTPResponse::setResponseHeader(name, value);
871 m_headers.insert(make_pair(name,value));
873 m_headers.erase(name);
875 const char* getQueryString() const {
876 return m_lpECB->lpszQueryString;
878 const char* getRequestBody() const {
880 return m_body.c_str();
881 if (m_lpECB->cbTotalBytes > 1024*1024) // 1MB?
882 throw opensaml::SecurityPolicyException("Size of request body exceeded 1M size limit.");
883 else if (m_lpECB->cbTotalBytes > m_lpECB->cbAvailable) {
885 DWORD datalen=m_lpECB->cbTotalBytes;
886 if (m_lpECB->cbAvailable > 0) {
887 m_body.assign(reinterpret_cast<char*>(m_lpECB->lpbData),m_lpECB->cbAvailable);
888 datalen-=m_lpECB->cbAvailable;
893 BOOL ret = m_lpECB->ReadClient(m_lpECB->ConnID, buf, &buflen);
896 _snprintf(message, 64, "Error reading request body from browser (%x).", GetLastError());
897 throw IOException(message);
900 throw IOException("Socket closed while reading request body from browser.");
901 m_body.append(buf, buflen);
905 else if (m_lpECB->cbAvailable) {
907 m_body.assign(reinterpret_cast<char*>(m_lpECB->lpbData),m_lpECB->cbAvailable);
909 return m_body.c_str();
911 long sendResponse(istream& in, long status) {
912 string hdr = string("Connection: close\r\n");
913 for (multimap<string,string>::const_iterator i = m_headers.begin(); i != m_headers.end(); ++i)
914 hdr += i->first + ": " + i->second + "\r\n";
916 const char* codestr="200 OK";
918 case XMLTOOLING_HTTP_STATUS_NOTMODIFIED: codestr="304 Not Modified"; break;
919 case XMLTOOLING_HTTP_STATUS_UNAUTHORIZED: codestr="401 Authorization Required"; break;
920 case XMLTOOLING_HTTP_STATUS_FORBIDDEN: codestr="403 Forbidden"; break;
921 case XMLTOOLING_HTTP_STATUS_NOTFOUND: codestr="404 Not Found"; break;
922 case XMLTOOLING_HTTP_STATUS_ERROR: codestr="500 Server Error"; break;
924 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, (void*)codestr, 0, (LPDWORD)hdr.c_str());
928 DWORD resplen = in.gcount();
929 m_lpECB->WriteClient(m_lpECB->ConnID, buf, &resplen, HSE_IO_SYNC);
931 return HSE_STATUS_SUCCESS;
933 long sendRedirect(const char* url) {
934 HTTPResponse::sendRedirect(url);
935 string hdr=string("Location: ") + url + "\r\n"
936 "Content-Type: text/html\r\n"
937 "Content-Length: 40\r\n"
938 "Expires: Wed, 01 Jan 1997 12:00:00 GMT\r\n"
939 "Cache-Control: private,no-store,no-cache,max-age=0\r\n";
940 for (multimap<string,string>::const_iterator i = m_headers.begin(); i != m_headers.end(); ++i)
941 hdr += i->first + ": " + i->second + "\r\n";
943 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, "302 Moved", 0, (LPDWORD)hdr.c_str());
944 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
946 m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)redmsg, &resplen, HSE_IO_SYNC);
947 return HSE_STATUS_SUCCESS;
949 // Decline happens in the POST processor if this isn't the handler url
950 // Note that it can also happen with HTAccess, but we don't support that, yet.
951 long returnDecline() {
952 return WriteClientError(
954 "ISAPI extension can only be invoked to process Shibboleth protocol requests."
955 "Make sure the mapped file extension doesn't match actual content."
959 return HSE_STATUS_SUCCESS;
962 const vector<string>& getClientCertificates() const {
963 if (m_certs.empty()) {
964 char CertificateBuf[8192];
965 CERT_CONTEXT_EX ccex;
966 ccex.cbAllocated = sizeof(CertificateBuf);
967 ccex.CertContext.pbCertEncoded = (BYTE*)CertificateBuf;
968 DWORD dwSize = sizeof(ccex);
970 if (m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_GET_CERT_INFO_EX, (LPVOID)&ccex, (LPDWORD)dwSize, nullptr)) {
971 if (ccex.CertContext.cbCertEncoded) {
973 XMLByte* serialized = Base64::encode(reinterpret_cast<XMLByte*>(CertificateBuf), ccex.CertContext.cbCertEncoded, &outlen);
974 m_certs.push_back(reinterpret_cast<char*>(serialized));
975 #ifdef SHIBSP_XERCESC_HAS_XMLBYTE_RELEASE
976 XMLString::release(&serialized);
978 XMLString::release((char**)&serialized);
986 // Not used in the extension.
987 void clearHeader(const char* rawname, const char* cginame) { throw runtime_error("clearHeader not implemented"); }
988 void setHeader(const char* name, const char* value) { throw runtime_error("setHeader not implemented"); }
989 void setRemoteUser(const char* user) { throw runtime_error("setRemoteUser not implemented"); }
991 void GetServerVariable(LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true) const {
996 while (!m_lpECB->GetServerVariable(m_lpECB->ConnID,lpszVariable,s,&size)) {
997 // Grumble. Check the error.
998 DWORD e=GetLastError();
999 if (e==ERROR_INSUFFICIENT_BUFFER)
1004 if (bRequired && s.empty())
1005 log(SPRequest::SPError, string("missing required server variable: ") + lpszVariable);
1009 void GetServerVariable(LPEXTENSION_CONTROL_BLOCK lpECB, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
1015 while (!lpECB->GetServerVariable(lpECB->ConnID,lpszVariable,s,&size)) {
1016 // Grumble. Check the error.
1017 DWORD e=GetLastError();
1018 if (e==ERROR_INSUFFICIENT_BUFFER)
1023 if (bRequired && s.empty()) {
1024 string msg = string("Missing required server variable: ") + lpszVariable;
1025 LogEvent(nullptr, EVENTLOG_ERROR_TYPE, 2100, nullptr, msg.c_str());
1029 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
1032 string threadid("[");
1033 threadid += lexical_cast<string>(getpid()) + "] isapi_shib_extension";
1034 xmltooling::NDC ndc(threadid.c_str());
1036 // Determine web site number. This can't really fail, I don't think.
1038 GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
1040 return WriteClientError(lpECB, "Shibboleth Extension failed to obtain INSTANCE_ID server variable.");
1042 // Match site instance to host name, skip if no match.
1043 map<string,site_t>::const_iterator map_i = g_Sites.find(static_cast<char*>(buf));
1044 if (map_i == g_Sites.end())
1045 return WriteClientError(lpECB, "Shibboleth Extension not configured for web site (check ISAPI mappings in SP configuration).");
1047 ShibTargetIsapiE ste(lpECB, map_i->second);
1048 pair<bool,long> res = ste.getServiceProvider().doHandler(ste);
1049 if (res.first) return res.second;
1051 return WriteClientError(lpECB, "Shibboleth Extension failed to process request");
1055 return WriteClientError(lpECB, "Out of Memory");
1058 if (e==ERROR_NO_DATA)
1059 return WriteClientError(lpECB, "A required variable or header was empty.");
1061 return WriteClientError(lpECB, "Server detected unexpected IIS error.");
1063 catch (std::exception& e) {
1064 LogEvent(nullptr, EVENTLOG_ERROR_TYPE, 2100, nullptr, e.what());
1065 return WriteClientError(lpECB, "Shibboleth Extension caught an exception, check Event Log for details.");
1068 LogEvent(nullptr, EVENTLOG_ERROR_TYPE, 2100, nullptr, "Shibboleth Extension threw an unknown exception.");
1070 return WriteClientError(lpECB, "Shibboleth Extension threw an unknown exception.");
1074 // If we get here we've got an error.
1075 return HSE_STATUS_ERROR;