2 * Copyright 2001-2010 Internet2
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
20 * Shibboleth ISAPI filter.
24 #include "config_win32.h"
26 #define _CRT_NONSTDC_NO_DEPRECATE 1
27 #define _CRT_SECURE_NO_DEPRECATE 1
30 #include <shibsp/exceptions.h>
31 #include <shibsp/AbstractSPRequest.h>
32 #include <shibsp/SPConfig.h>
33 #include <shibsp/ServiceProvider.h>
40 #include <xmltooling/unicode.h>
41 #include <xmltooling/XMLToolingConfig.h>
42 #include <xmltooling/util/NDC.h>
43 #include <xmltooling/util/XMLConstants.h>
44 #include <xmltooling/util/XMLHelper.h>
45 #include <xercesc/util/Base64.hpp>
46 #include <xercesc/util/XMLUniDefs.hpp>
52 using namespace shibsp;
53 using namespace xmltooling;
54 using namespace xercesc;
59 static const XMLCh path[] = UNICODE_LITERAL_4(p,a,t,h);
60 static const XMLCh validate[] = UNICODE_LITERAL_8(v,a,l,i,d,a,t,e);
61 static const XMLCh name[] = UNICODE_LITERAL_4(n,a,m,e);
62 static const XMLCh port[] = UNICODE_LITERAL_4(p,o,r,t);
63 static const XMLCh sslport[] = UNICODE_LITERAL_7(s,s,l,p,o,r,t);
64 static const XMLCh scheme[] = UNICODE_LITERAL_6(s,c,h,e,m,e);
65 static const XMLCh id[] = UNICODE_LITERAL_2(i,d);
66 static const XMLCh Alias[] = UNICODE_LITERAL_5(A,l,i,a,s);
67 static const XMLCh Site[] = UNICODE_LITERAL_4(S,i,t,e);
70 site_t(const DOMElement* e)
72 auto_ptr_char n(e->getAttributeNS(nullptr,name));
73 auto_ptr_char s(e->getAttributeNS(nullptr,scheme));
74 auto_ptr_char p(e->getAttributeNS(nullptr,port));
75 auto_ptr_char p2(e->getAttributeNS(nullptr,sslport));
76 if (n.get()) m_name=n.get();
77 if (s.get()) m_scheme=s.get();
78 if (p.get()) m_port=p.get();
79 if (p2.get()) m_sslport=p2.get();
80 e = XMLHelper::getFirstChildElement(e, Alias);
82 if (e->hasChildNodes()) {
83 auto_ptr_char alias(e->getFirstChild()->getNodeValue());
84 m_aliases.insert(alias.get());
86 e = XMLHelper::getNextSiblingElement(e, Alias);
89 string m_scheme,m_port,m_sslport,m_name;
90 set<string> m_aliases;
94 SPConfig* g_Config = nullptr;
95 map<string,site_t> g_Sites;
96 bool g_bNormalizeRequest = true;
97 string g_unsetHeaderValue,g_spoofKey;
98 bool g_checkSpoofing = true;
99 bool g_catchAll = false;
100 bool g_bSafeHeaderNames = false;
101 vector<string> g_NoCerts;
105 LPCSTR lpUNCServerName,
111 LPCSTR messages[] = {message, nullptr};
113 HANDLE hElog = RegisterEventSource(lpUNCServerName, "Shibboleth ISAPI Filter");
114 BOOL res = ReportEvent(hElog, wType, 0, dwEventID, lpUserSid, 1, 0, messages, nullptr);
115 return (DeregisterEventSource(hElog) && res);
118 void _my_invalid_parameter_handler(
119 const wchar_t * expression,
120 const wchar_t * function,
121 const wchar_t * file,
129 extern "C" __declspec(dllexport) BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID)
131 if (fdwReason==DLL_PROCESS_ATTACH)
136 extern "C" BOOL WINAPI GetExtensionVersion(HSE_VERSION_INFO* pVer)
142 LogEvent(nullptr, EVENTLOG_ERROR_TYPE, 2100, nullptr,
143 "Extension mode startup not possible, is the DLL loaded as a filter?");
147 pVer->dwExtensionVersion=HSE_VERSION;
148 strncpy(pVer->lpszExtensionDesc,"Shibboleth ISAPI Extension",HSE_MAX_EXT_DLL_NAME_LEN-1);
152 extern "C" BOOL WINAPI TerminateExtension(DWORD)
154 return TRUE; // cleanup should happen when filter unloads
157 extern "C" BOOL WINAPI GetFilterVersion(PHTTP_FILTER_VERSION pVer)
162 LogEvent(nullptr, EVENTLOG_ERROR_TYPE, 2100, nullptr,
163 "Reentrant filter initialization, ignoring...");
167 g_Config=&SPConfig::getConfig();
168 g_Config->setFeatures(
171 SPConfig::RequestMapping |
172 SPConfig::InProcess |
176 if (!g_Config->init()) {
178 LogEvent(nullptr, EVENTLOG_ERROR_TYPE, 2100, nullptr,
179 "Filter startup failed during library initialization, check native log for help.");
184 if (!g_Config->instantiate(nullptr, true))
185 throw runtime_error("unknown error");
187 catch (exception& ex) {
190 LogEvent(nullptr, EVENTLOG_ERROR_TYPE, 2100, nullptr, ex.what());
191 LogEvent(nullptr, EVENTLOG_ERROR_TYPE, 2100, nullptr,
192 "Filter startup failed to load configuration, check native log for details.");
196 // Access implementation-specifics and site mappings.
197 ServiceProvider* sp=g_Config->getServiceProvider();
199 const PropertySet* props=sp->getPropertySet("InProcess");
201 pair<bool,bool> flag=props->getBool("checkSpoofing");
202 g_checkSpoofing = !flag.first || flag.second;
203 flag=props->getBool("catchAll");
204 g_catchAll = flag.first && flag.second;
206 pair<bool,const char*> unsetValue=props->getString("unsetHeaderValue");
207 if (unsetValue.first)
208 g_unsetHeaderValue = unsetValue.second;
209 if (g_checkSpoofing) {
210 unsetValue = props->getString("spoofKey");
211 if (unsetValue.first)
212 g_spoofKey = unsetValue.second;
214 _invalid_parameter_handler old = _set_invalid_parameter_handler(_my_invalid_parameter_handler);
215 unsigned int randkey=0,randkey2=0,randkey3=0,randkey4=0;
216 if (rand_s(&randkey) == 0 && rand_s(&randkey2) == 0 && rand_s(&randkey3) == 0 && rand_s(&randkey4) == 0) {
217 _set_invalid_parameter_handler(old);
218 ostringstream keystr;
219 keystr << randkey << randkey2 << randkey3 << randkey4;
220 g_spoofKey = keystr.str();
223 _set_invalid_parameter_handler(old);
224 LogEvent(nullptr, EVENTLOG_ERROR_TYPE, 2100, nullptr,
225 "Filter failed to generate a random anti-spoofing key (if this is Windows 2000 set one manually).");
226 locker.assign(); // pops lock on SP config
234 props = props->getPropertySet("ISAPI");
236 flag = props->getBool("normalizeRequest");
237 g_bNormalizeRequest = !flag.first || flag.second;
238 flag = props->getBool("safeHeaderNames");
239 g_bSafeHeaderNames = flag.first && flag.second;
240 const DOMElement* child = XMLHelper::getFirstChildElement(props->getElement(),Site);
242 auto_ptr_char id(child->getAttributeNS(nullptr,id));
244 g_Sites.insert(pair<string,site_t>(id.get(),site_t(child)));
245 child=XMLHelper::getNextSiblingElement(child,Site);
250 pVer->dwFilterVersion=HTTP_FILTER_REVISION;
251 strncpy(pVer->lpszFilterDesc,"Shibboleth ISAPI Filter",SF_MAX_FILTER_DESC_LEN);
252 pVer->dwFlags=(SF_NOTIFY_ORDER_HIGH |
253 SF_NOTIFY_SECURE_PORT |
254 SF_NOTIFY_NONSECURE_PORT |
255 SF_NOTIFY_PREPROC_HEADERS |
257 LogEvent(nullptr, EVENTLOG_INFORMATION_TYPE, 7701, nullptr, "Filter initialized...");
261 extern "C" BOOL WINAPI TerminateFilter(DWORD)
266 LogEvent(nullptr, EVENTLOG_INFORMATION_TYPE, 7701, nullptr, "Filter shut down...");
270 /* Next up, some suck-free versions of various APIs.
272 You DON'T require people to guess the buffer size and THEN tell them the right size.
273 Returning an LPCSTR is apparently way beyond their ken. Not to mention the fact that
274 constant strings aren't typed as such, making it just that much harder. These versions
275 are now updated to use a special growable buffer object, modeled after the standard
276 string class. The standard string won't work because they left out the option to
277 pre-allocate a non-constant buffer.
283 dynabuf() { bufptr=nullptr; buflen=0; }
284 dynabuf(size_t s) { bufptr=new char[buflen=s]; *bufptr=0; }
285 ~dynabuf() { delete[] bufptr; }
286 size_t length() const { return bufptr ? strlen(bufptr) : 0; }
287 size_t size() const { return buflen; }
288 bool empty() const { return length()==0; }
289 void reserve(size_t s, bool keep=false);
290 void erase() { if (bufptr) memset(bufptr,0,buflen); }
291 operator char*() { return bufptr; }
292 bool operator ==(const char* s) const;
293 bool operator !=(const char* s) const { return !(*this==s); }
299 void dynabuf::reserve(size_t s, bool keep)
306 p[buflen]=bufptr[buflen];
312 bool dynabuf::operator==(const char* s) const
314 if (buflen==0 || s==nullptr)
315 return (buflen==0 && s==nullptr);
317 return strcmp(bufptr,s)==0;
320 void GetServerVariable(PHTTP_FILTER_CONTEXT pfc, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
326 while (!pfc->GetServerVariable(pfc,lpszVariable,s,&size)) {
327 // Grumble. Check the error.
328 DWORD e=GetLastError();
329 if (e==ERROR_INSUFFICIENT_BUFFER)
334 if (bRequired && s.empty())
338 void GetServerVariable(LPEXTENSION_CONTROL_BLOCK lpECB, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
344 while (!lpECB->GetServerVariable(lpECB->ConnID,lpszVariable,s,&size)) {
345 // Grumble. Check the error.
346 DWORD e=GetLastError();
347 if (e==ERROR_INSUFFICIENT_BUFFER)
352 if (bRequired && s.empty())
356 void GetHeader(PHTTP_FILTER_PREPROC_HEADERS pn, PHTTP_FILTER_CONTEXT pfc,
357 LPSTR lpszName, dynabuf& s, DWORD size=80, bool bRequired=true)
363 while (!pn->GetHeader(pfc,lpszName,s,&size)) {
364 // Grumble. Check the error.
365 DWORD e=GetLastError();
366 if (e==ERROR_INSUFFICIENT_BUFFER)
371 if (bRequired && s.empty())
375 /****************************************************************************/
378 class ShibTargetIsapiF : public AbstractSPRequest
380 PHTTP_FILTER_CONTEXT m_pfc;
381 PHTTP_FILTER_PREPROC_HEADERS m_pn;
382 multimap<string,string> m_headers;
384 string m_scheme,m_hostname;
385 mutable string m_remote_addr,m_content_type,m_method;
390 ShibTargetIsapiF(PHTTP_FILTER_CONTEXT pfc, PHTTP_FILTER_PREPROC_HEADERS pn, const site_t& site)
391 : AbstractSPRequest(SHIBSP_LOGCAT".ISAPI"), m_pfc(pfc), m_pn(pn), m_allhttp(4096), m_firsttime(true) {
393 // URL path always come from IIS.
395 GetHeader(pn,pfc,"url",var,256,false);
398 // Port may come from IIS or from site def.
399 if (!g_bNormalizeRequest || (pfc->fIsSecurePort && site.m_sslport.empty()) || (!pfc->fIsSecurePort && site.m_port.empty())) {
400 GetServerVariable(pfc,"SERVER_PORT",var,10);
403 else if (pfc->fIsSecurePort) {
404 m_port = atoi(site.m_sslport.c_str());
407 m_port = atoi(site.m_port.c_str());
410 // Scheme may come from site def or be derived from IIS.
411 m_scheme=site.m_scheme;
412 if (m_scheme.empty() || !g_bNormalizeRequest)
413 m_scheme=pfc->fIsSecurePort ? "https" : "http";
415 GetServerVariable(pfc,"SERVER_NAME",var,32);
417 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
419 if (site.m_name!=m_hostname && site.m_aliases.find(m_hostname)==site.m_aliases.end())
420 m_hostname=site.m_name;
422 if (!g_spoofKey.empty()) {
423 GetHeader(pn, pfc, "ShibSpoofCheck:", var, 32, false);
424 if (!var.empty() && g_spoofKey == (char*)var)
429 log(SPDebug, "ISAPI filter running more than once");
431 ~ShibTargetIsapiF() { }
433 const char* getScheme() const {
434 return m_scheme.c_str();
436 const char* getHostname() const {
437 return m_hostname.c_str();
439 int getPort() const {
442 const char* getQueryString() const {
443 const char* uri = getRequestURI();
444 uri = (uri ? strchr(uri, '?') : nullptr);
445 return uri ? (uri + 1) : nullptr;
447 const char* getMethod() const {
448 if (m_method.empty()) {
450 GetServerVariable(m_pfc,"HTTP_METHOD",var,5,false);
454 return m_method.c_str();
456 string getContentType() const {
457 if (m_content_type.empty()) {
459 GetServerVariable(m_pfc,"HTTP_CONTENT_TYPE",var,32,false);
461 m_content_type = var;
463 return m_content_type;
465 string getRemoteAddr() const {
466 m_remote_addr = AbstractSPRequest::getRemoteAddr();
467 if (m_remote_addr.empty()) {
469 GetServerVariable(m_pfc,"REMOTE_ADDR",var,16,false);
473 return m_remote_addr;
475 void log(SPLogLevel level, const string& msg) {
476 AbstractSPRequest::log(level,msg);
478 LogEvent(nullptr, EVENTLOG_ERROR_TYPE, 2100, nullptr, msg.c_str());
480 string makeSafeHeader(const char* rawname) const {
482 for (; *rawname; ++rawname) {
483 if (isalnum(*rawname))
488 void clearHeader(const char* rawname, const char* cginame) {
489 if (g_checkSpoofing && m_firsttime) {
490 if (m_allhttp.empty())
491 GetServerVariable(m_pfc, "ALL_HTTP", m_allhttp, 4096);
492 string hdr = g_bSafeHeaderNames ? ("HTTP_" + makeSafeHeader(cginame + 5)) : (string(cginame) + ':');
493 if (strstr(m_allhttp, hdr.c_str()))
494 throw opensaml::SecurityPolicyException("Attempt to spoof header ($1) was detected.", params(1, hdr.c_str()));
496 if (g_bSafeHeaderNames) {
497 string hdr = makeSafeHeader(rawname);
498 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), const_cast<char*>(g_unsetHeaderValue.c_str()));
500 else if (!strcmp(rawname,"REMOTE_USER")) {
501 m_pn->SetHeader(m_pfc, "remote-user:", const_cast<char*>(g_unsetHeaderValue.c_str()));
502 m_pn->SetHeader(m_pfc, "remote_user:", const_cast<char*>(g_unsetHeaderValue.c_str()));
505 string hdr = string(rawname) + ':';
506 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), const_cast<char*>(g_unsetHeaderValue.c_str()));
509 void setHeader(const char* name, const char* value) {
510 string hdr = g_bSafeHeaderNames ? makeSafeHeader(name) : (string(name) + ':');
511 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), const_cast<char*>(value));
513 string getSecureHeader(const char* name) const {
514 string hdr = g_bSafeHeaderNames ? makeSafeHeader(name) : (string(name) + ':');
516 GetHeader(m_pn, m_pfc, const_cast<char*>(hdr.c_str()), buf, 256, false);
517 return string(buf.empty() ? "" : buf);
519 string getHeader(const char* name) const {
523 GetHeader(m_pn, m_pfc, const_cast<char*>(hdr.c_str()), buf, 256, false);
524 return string(buf.empty() ? "" : buf);
526 void setRemoteUser(const char* user) {
527 setHeader("remote-user", user);
529 m_pfc->pFilterContext = nullptr;
530 else if (m_pfc->pFilterContext = m_pfc->AllocMem(m_pfc, sizeof(char) * (strlen(user) + 1), 0))
531 strcpy(reinterpret_cast<char*>(m_pfc->pFilterContext), user);
533 string getRemoteUser() const {
534 return getSecureHeader("remote-user");
536 void setResponseHeader(const char* name, const char* value) {
537 HTTPResponse::setResponseHeader(name, value);
540 m_headers.insert(make_pair(name,value));
542 m_headers.erase(name);
544 long sendResponse(istream& in, long status) {
545 string hdr = string("Connection: close\r\n");
546 for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
547 hdr += i->first + ": " + i->second + "\r\n";
549 const char* codestr="200 OK";
551 case XMLTOOLING_HTTP_STATUS_UNAUTHORIZED: codestr="401 Authorization Required"; break;
552 case XMLTOOLING_HTTP_STATUS_FORBIDDEN: codestr="403 Forbidden"; break;
553 case XMLTOOLING_HTTP_STATUS_NOTFOUND: codestr="404 Not Found"; break;
554 case XMLTOOLING_HTTP_STATUS_ERROR: codestr="500 Server Error"; break;
556 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, (void*)codestr, (DWORD)hdr.c_str(), 0);
560 DWORD resplen = in.gcount();
561 m_pfc->WriteClient(m_pfc, buf, &resplen, 0);
563 return SF_STATUS_REQ_FINISHED;
565 long sendRedirect(const char* url) {
566 HTTPResponse::sendRedirect(url);
567 string hdr=string("Location: ") + url + "\r\n"
568 "Content-Type: text/html\r\n"
569 "Content-Length: 40\r\n"
570 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
571 "Cache-Control: private,no-store,no-cache\r\n";
572 for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
573 hdr += i->first + ": " + i->second + "\r\n";
575 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, "302 Please Wait", (DWORD)hdr.c_str(), 0);
576 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
578 m_pfc->WriteClient(m_pfc, (LPVOID)redmsg, &resplen, 0);
579 return SF_STATUS_REQ_FINISHED;
581 long returnDecline() {
582 return SF_STATUS_REQ_NEXT_NOTIFICATION;
585 return SF_STATUS_REQ_NEXT_NOTIFICATION;
588 const vector<string>& getClientCertificates() const {
592 // The filter never processes the POST, so stub these methods.
593 long getContentLength() const { throw IOException("The request's Content-Length is not available to an ISAPI filter."); }
594 const char* getRequestBody() const { throw IOException("The request body is not available to an ISAPI filter."); }
597 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const char* msg)
599 LogEvent(nullptr, EVENTLOG_ERROR_TYPE, 2100, nullptr, msg);
600 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
601 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)ctype,0);
602 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Filter Error</TITLE></HEAD><BODY>"
603 "<H1>Shibboleth Filter Error</H1>";
604 DWORD resplen=strlen(xmsg);
605 pfc->WriteClient(pfc,(LPVOID)xmsg,&resplen,0);
607 pfc->WriteClient(pfc,(LPVOID)msg,&resplen,0);
608 static const char* xmsg2="</BODY></HTML>";
609 resplen=strlen(xmsg2);
610 pfc->WriteClient(pfc,(LPVOID)xmsg2,&resplen,0);
611 return SF_STATUS_REQ_FINISHED;
614 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
616 // Is this a log notification?
617 if (notificationType==SF_NOTIFY_LOG) {
618 if (pfc->pFilterContext)
619 ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=reinterpret_cast<char*>(pfc->pFilterContext);
620 return SF_STATUS_REQ_NEXT_NOTIFICATION;
623 PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
626 // Determine web site number. This can't really fail, I don't think.
628 GetServerVariable(pfc,"INSTANCE_ID",buf,10);
630 // Match site instance to host name, skip if no match.
631 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
632 if (map_i==g_Sites.end())
633 return SF_STATUS_REQ_NEXT_NOTIFICATION;
635 ostringstream threadid;
636 threadid << "[" << getpid() << "] isapi_shib" << '\0';
637 xmltooling::NDC ndc(threadid.str().c_str());
639 ShibTargetIsapiF stf(pfc, pn, map_i->second);
641 // "false" because we don't override the Shib settings
642 pair<bool,long> res = stf.getServiceProvider().doAuthentication(stf);
643 if (!g_spoofKey.empty())
644 pn->SetHeader(pfc, "ShibSpoofCheck:", const_cast<char*>(g_spoofKey.c_str()));
645 if (res.first) return res.second;
647 // "false" because we don't override the Shib settings
648 res = stf.getServiceProvider().doExport(stf);
649 if (res.first) return res.second;
651 res = stf.getServiceProvider().doAuthorization(stf);
652 if (res.first) return res.second;
654 return SF_STATUS_REQ_NEXT_NOTIFICATION;
657 return WriteClientError(pfc,"Out of Memory");
660 if (e==ERROR_NO_DATA)
661 return WriteClientError(pfc,"A required variable or header was empty.");
663 return WriteClientError(pfc,"Shibboleth Filter detected unexpected IIS error.");
665 catch (exception& e) {
666 LogEvent(nullptr, EVENTLOG_ERROR_TYPE, 2100, nullptr, e.what());
667 return WriteClientError(pfc,"Shibboleth Filter caught an exception, check Event Log for details.");
670 LogEvent(nullptr, EVENTLOG_ERROR_TYPE, 2100, nullptr, "Shibboleth Filter threw an unknown exception.");
672 return WriteClientError(pfc,"Shibboleth Filter threw an unknown exception.");
676 return WriteClientError(pfc,"Shibboleth Filter reached unreachable code, save my walrus!");
680 /****************************************************************************/
683 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* msg)
685 LogEvent(nullptr, EVENTLOG_ERROR_TYPE, 2100, nullptr, msg);
686 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
687 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
688 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Error</TITLE></HEAD><BODY><H1>Shibboleth Error</H1>";
689 DWORD resplen=strlen(xmsg);
690 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg,&resplen,HSE_IO_SYNC);
692 lpECB->WriteClient(lpECB->ConnID,(LPVOID)msg,&resplen,HSE_IO_SYNC);
693 static const char* xmsg2="</BODY></HTML>";
694 resplen=strlen(xmsg2);
695 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg2,&resplen,HSE_IO_SYNC);
696 return HSE_STATUS_SUCCESS;
700 class ShibTargetIsapiE : public AbstractSPRequest
702 LPEXTENSION_CONTROL_BLOCK m_lpECB;
703 multimap<string,string> m_headers;
704 mutable vector<string> m_certs;
705 mutable string m_body;
706 mutable bool m_gotBody;
708 string m_scheme,m_hostname,m_uri;
709 mutable string m_remote_addr,m_remote_user;
712 ShibTargetIsapiE(LPEXTENSION_CONTROL_BLOCK lpECB, const site_t& site)
713 : AbstractSPRequest(SHIBSP_LOGCAT".ISAPI"), m_lpECB(lpECB), m_gotBody(false) {
715 GetServerVariable(lpECB,"HTTPS",ssl,5);
716 bool SSL=(ssl=="on" || ssl=="ON");
718 // Scheme may come from site def or be derived from IIS.
719 m_scheme=site.m_scheme;
720 if (m_scheme.empty() || !g_bNormalizeRequest)
721 m_scheme = SSL ? "https" : "http";
723 // URL path always come from IIS.
725 GetServerVariable(lpECB,"URL",url,255);
727 // Port may come from IIS or from site def.
729 if (!g_bNormalizeRequest || (SSL && site.m_sslport.empty()) || (!SSL && site.m_port.empty()))
730 GetServerVariable(lpECB,"SERVER_PORT",port,10);
732 strncpy(port,site.m_sslport.c_str(),10);
733 static_cast<char*>(port)[10]=0;
736 strncpy(port,site.m_port.c_str(),10);
737 static_cast<char*>(port)[10]=0;
742 GetServerVariable(lpECB, "SERVER_NAME", var, 32);
744 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
746 if (site.m_name!=m_hostname && site.m_aliases.find(m_hostname)==site.m_aliases.end())
747 m_hostname=site.m_name;
750 * IIS screws us over on PATH_INFO (the hits keep on coming). We need to figure out if
751 * the server is set up for proper PATH_INFO handling, or "IIS sucks rabid weasels mode",
752 * which is the default. No perfect way to tell, but we can take a good guess by checking
753 * whether the URL is a substring of the PATH_INFO:
755 * e.g. for /Shibboleth.sso/SAML/POST
757 * Bad mode (default):
758 * URL: /Shibboleth.sso
759 * PathInfo: /Shibboleth.sso/SAML/POST
762 * URL: /Shibboleth.sso
763 * PathInfo: /SAML/POST
768 // Clearly we're only in bad mode if path info exists at all.
769 if (lpECB->lpszPathInfo && *(lpECB->lpszPathInfo)) {
770 if (strstr(lpECB->lpszPathInfo,url))
771 // Pretty good chance we're in bad mode, unless the PathInfo repeats the path itself.
772 uri = lpECB->lpszPathInfo;
775 uri += lpECB->lpszPathInfo;
782 // For consistency with Apache, let's add the query string.
783 if (lpECB->lpszQueryString && *(lpECB->lpszQueryString)) {
785 uri += lpECB->lpszQueryString;
788 setRequestURI(uri.c_str());
790 ~ShibTargetIsapiE() { }
792 const char* getScheme() const {
793 return m_scheme.c_str();
795 const char* getHostname() const {
796 return m_hostname.c_str();
798 int getPort() const {
801 const char* getMethod() const {
802 return m_lpECB->lpszMethod;
804 string getContentType() const {
805 return m_lpECB->lpszContentType ? m_lpECB->lpszContentType : "";
807 long getContentLength() const {
808 return m_lpECB->cbTotalBytes;
810 string getRemoteUser() const {
811 if (m_remote_user.empty()) {
813 GetServerVariable(m_lpECB, "REMOTE_USER", var, 32, false);
817 return m_remote_user;
819 string getRemoteAddr() const {
820 m_remote_addr = AbstractSPRequest::getRemoteAddr();
821 if (m_remote_addr.empty()) {
823 GetServerVariable(m_lpECB, "REMOTE_ADDR", var, 16, false);
827 return m_remote_addr;
829 void log(SPLogLevel level, const string& msg) const {
830 AbstractSPRequest::log(level,msg);
832 LogEvent(nullptr, EVENTLOG_ERROR_TYPE, 2100, nullptr, msg.c_str());
834 string getHeader(const char* name) const {
836 for (; *name; ++name) {
840 hdr += toupper(*name);
843 GetServerVariable(m_lpECB, const_cast<char*>(hdr.c_str()), buf, 128, false);
844 return buf.empty() ? "" : buf;
846 void setResponseHeader(const char* name, const char* value) {
847 HTTPResponse::setResponseHeader(name, value);
850 m_headers.insert(make_pair(name,value));
852 m_headers.erase(name);
854 const char* getQueryString() const {
855 return m_lpECB->lpszQueryString;
857 const char* getRequestBody() const {
859 return m_body.c_str();
860 if (m_lpECB->cbTotalBytes > 1024*1024) // 1MB?
861 throw opensaml::SecurityPolicyException("Size of request body exceeded 1M size limit.");
862 else if (m_lpECB->cbTotalBytes > m_lpECB->cbAvailable) {
864 DWORD datalen=m_lpECB->cbTotalBytes;
865 if (m_lpECB->cbAvailable > 0) {
866 m_body.assign(reinterpret_cast<char*>(m_lpECB->lpbData),m_lpECB->cbAvailable);
867 datalen-=m_lpECB->cbAvailable;
872 BOOL ret = m_lpECB->ReadClient(m_lpECB->ConnID, buf, &buflen);
874 throw IOException("Error reading request body from browser.");
876 throw IOException("Socket closed while reading request body from browser.");
877 m_body.append(buf, buflen);
881 else if (m_lpECB->cbAvailable) {
883 m_body.assign(reinterpret_cast<char*>(m_lpECB->lpbData),m_lpECB->cbAvailable);
885 return m_body.c_str();
887 long sendResponse(istream& in, long status) {
888 string hdr = string("Connection: close\r\n");
889 for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
890 hdr += i->first + ": " + i->second + "\r\n";
892 const char* codestr="200 OK";
894 case XMLTOOLING_HTTP_STATUS_UNAUTHORIZED: codestr="401 Authorization Required"; break;
895 case XMLTOOLING_HTTP_STATUS_FORBIDDEN: codestr="403 Forbidden"; break;
896 case XMLTOOLING_HTTP_STATUS_NOTFOUND: codestr="404 Not Found"; break;
897 case XMLTOOLING_HTTP_STATUS_ERROR: codestr="500 Server Error"; break;
899 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, (void*)codestr, 0, (LPDWORD)hdr.c_str());
903 DWORD resplen = in.gcount();
904 m_lpECB->WriteClient(m_lpECB->ConnID, buf, &resplen, HSE_IO_SYNC);
906 return HSE_STATUS_SUCCESS;
908 long sendRedirect(const char* url) {
909 HTTPResponse::sendRedirect(url);
910 string hdr=string("Location: ") + url + "\r\n"
911 "Content-Type: text/html\r\n"
912 "Content-Length: 40\r\n"
913 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
914 "Cache-Control: private,no-store,no-cache\r\n";
915 for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
916 hdr += i->first + ": " + i->second + "\r\n";
918 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, "302 Moved", 0, (LPDWORD)hdr.c_str());
919 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
921 m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)redmsg, &resplen, HSE_IO_SYNC);
922 return HSE_STATUS_SUCCESS;
924 // Decline happens in the POST processor if this isn't the shire url
925 // Note that it can also happen with HTAccess, but we don't support that, yet.
926 long returnDecline() {
927 return WriteClientError(
929 "ISAPI extension can only be invoked to process Shibboleth protocol requests."
930 "Make sure the mapped file extension doesn't match actual content."
934 return HSE_STATUS_SUCCESS;
937 const vector<string>& getClientCertificates() const {
938 if (m_certs.empty()) {
939 char CertificateBuf[8192];
940 CERT_CONTEXT_EX ccex;
941 ccex.cbAllocated = sizeof(CertificateBuf);
942 ccex.CertContext.pbCertEncoded = (BYTE*)CertificateBuf;
943 DWORD dwSize = sizeof(ccex);
945 if (m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_GET_CERT_INFO_EX, (LPVOID)&ccex, (LPDWORD)dwSize, nullptr)) {
946 if (ccex.CertContext.cbCertEncoded) {
948 XMLByte* serialized = Base64::encode(reinterpret_cast<XMLByte*>(CertificateBuf), ccex.CertContext.cbCertEncoded, &outlen);
949 m_certs.push_back(reinterpret_cast<char*>(serialized));
950 #ifdef SHIBSP_XERCESC_HAS_XMLBYTE_RELEASE
951 XMLString::release(&serialized);
953 XMLString::release((char**)&serialized);
961 // Not used in the extension.
962 void clearHeader(const char* rawname, const char* cginame) { throw runtime_error("clearHeader not implemented"); }
963 void setHeader(const char* name, const char* value) { throw runtime_error("setHeader not implemented"); }
964 void setRemoteUser(const char* user) { throw runtime_error("setRemoteUser not implemented"); }
967 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
970 ostringstream threadid;
971 threadid << "[" << getpid() << "] isapi_shib_extension" << '\0';
972 xmltooling::NDC ndc(threadid.str().c_str());
974 // Determine web site number. This can't really fail, I don't think.
976 GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
978 // Match site instance to host name, skip if no match.
979 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
980 if (map_i==g_Sites.end())
981 return WriteClientError(lpECB, "Shibboleth Extension not configured for web site (check ISAPI mappings in SP configuration).");
983 ShibTargetIsapiE ste(lpECB, map_i->second);
984 pair<bool,long> res = ste.getServiceProvider().doHandler(ste);
985 if (res.first) return res.second;
987 return WriteClientError(lpECB, "Shibboleth Extension failed to process request");
991 return WriteClientError(lpECB,"Out of Memory");
994 if (e==ERROR_NO_DATA)
995 return WriteClientError(lpECB,"A required variable or header was empty.");
997 return WriteClientError(lpECB,"Server detected unexpected IIS error.");
999 catch (exception& e) {
1000 LogEvent(nullptr, EVENTLOG_ERROR_TYPE, 2100, nullptr, e.what());
1001 return WriteClientError(lpECB,"Shibboleth Extension caught an exception, check Event Log for details.");
1004 LogEvent(nullptr, EVENTLOG_ERROR_TYPE, 2100, nullptr, "Shibboleth Extension threw an unknown exception.");
1006 return WriteClientError(lpECB,"Shibboleth Extension threw an unknown exception.");
1010 // If we get here we've got an error.
1011 return HSE_STATUS_ERROR;