2 * Copyright 2001-2009 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/AbstractSPRequest.h>
31 #include <shibsp/SPConfig.h>
32 #include <shibsp/ServiceProvider.h>
33 #include <xmltooling/unicode.h>
34 #include <xmltooling/XMLToolingConfig.h>
35 #include <xmltooling/util/NDC.h>
36 #include <xmltooling/util/XMLConstants.h>
37 #include <xmltooling/util/XMLHelper.h>
38 #include <xercesc/util/Base64.hpp>
39 #include <xercesc/util/XMLUniDefs.hpp>
51 using namespace shibsp;
52 using namespace xmltooling;
53 using namespace xercesc;
58 static const XMLCh path[] = UNICODE_LITERAL_4(p,a,t,h);
59 static const XMLCh validate[] = UNICODE_LITERAL_8(v,a,l,i,d,a,t,e);
60 static const XMLCh name[] = UNICODE_LITERAL_4(n,a,m,e);
61 static const XMLCh port[] = UNICODE_LITERAL_4(p,o,r,t);
62 static const XMLCh sslport[] = UNICODE_LITERAL_7(s,s,l,p,o,r,t);
63 static const XMLCh scheme[] = UNICODE_LITERAL_6(s,c,h,e,m,e);
64 static const XMLCh id[] = UNICODE_LITERAL_2(i,d);
65 static const XMLCh Alias[] = UNICODE_LITERAL_5(A,l,i,a,s);
66 static const XMLCh Site[] = UNICODE_LITERAL_4(S,i,t,e);
69 site_t(const DOMElement* e)
71 auto_ptr_char n(e->getAttributeNS(NULL,name));
72 auto_ptr_char s(e->getAttributeNS(NULL,scheme));
73 auto_ptr_char p(e->getAttributeNS(NULL,port));
74 auto_ptr_char p2(e->getAttributeNS(NULL,sslport));
75 if (n.get()) m_name=n.get();
76 if (s.get()) m_scheme=s.get();
77 if (p.get()) m_port=p.get();
78 if (p2.get()) m_sslport=p2.get();
79 e = XMLHelper::getFirstChildElement(e, Alias);
81 if (e->hasChildNodes()) {
82 auto_ptr_char alias(e->getFirstChild()->getNodeValue());
83 m_aliases.insert(alias.get());
85 e = XMLHelper::getNextSiblingElement(e, Alias);
88 string m_scheme,m_port,m_sslport,m_name;
89 set<string> m_aliases;
93 SPConfig* g_Config = NULL;
94 map<string,site_t> g_Sites;
95 bool g_bNormalizeRequest = true;
96 string g_unsetHeaderValue,g_spoofKey;
97 bool g_checkSpoofing = true;
98 bool g_catchAll = false;
99 bool g_bSafeHeaderNames = false;
100 vector<string> g_NoCerts;
104 LPCSTR lpUNCServerName,
110 LPCSTR messages[] = {message, NULL};
112 HANDLE hElog = RegisterEventSource(lpUNCServerName, "Shibboleth ISAPI Filter");
113 BOOL res = ReportEvent(hElog, wType, 0, dwEventID, lpUserSid, 1, 0, messages, NULL);
114 return (DeregisterEventSource(hElog) && res);
117 void _my_invalid_parameter_handler(
118 const wchar_t * expression,
119 const wchar_t * function,
120 const wchar_t * file,
128 extern "C" __declspec(dllexport) BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID)
130 if (fdwReason==DLL_PROCESS_ATTACH)
135 extern "C" BOOL WINAPI GetExtensionVersion(HSE_VERSION_INFO* pVer)
141 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
142 "Extension mode startup not possible, is the DLL loaded as a filter?");
146 pVer->dwExtensionVersion=HSE_VERSION;
147 strncpy(pVer->lpszExtensionDesc,"Shibboleth ISAPI Extension",HSE_MAX_EXT_DLL_NAME_LEN-1);
151 extern "C" BOOL WINAPI TerminateExtension(DWORD)
153 return TRUE; // cleanup should happen when filter unloads
156 extern "C" BOOL WINAPI GetFilterVersion(PHTTP_FILTER_VERSION pVer)
161 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
162 "Reentrant filter initialization, ignoring...");
166 g_Config=&SPConfig::getConfig();
167 g_Config->setFeatures(
170 SPConfig::RequestMapping |
171 SPConfig::InProcess |
175 if (!g_Config->init()) {
177 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
178 "Filter startup failed during library initialization, check native log for help.");
183 if (!g_Config->instantiate(NULL, true))
184 throw runtime_error("unknown error");
186 catch (exception& ex) {
189 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, ex.what());
190 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
191 "Filter startup failed to load configuration, check native log for details.");
195 // Access implementation-specifics and site mappings.
196 ServiceProvider* sp=g_Config->getServiceProvider();
198 const PropertySet* props=sp->getPropertySet("InProcess");
200 pair<bool,bool> flag=props->getBool("checkSpoofing");
201 g_checkSpoofing = !flag.first || flag.second;
202 flag=props->getBool("catchAll");
203 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 unsigned int randkey=0,randkey2=0,randkey3=0,randkey4=0;
215 if (rand_s(&randkey) == 0 && rand_s(&randkey2) == 0 && rand_s(&randkey3) == 0 && rand_s(&randkey4) == 0) {
216 ostringstream keystr;
217 keystr << randkey << randkey2 << randkey3 << randkey4;
218 g_spoofKey = keystr.str();
221 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
222 "Filter failed to generate a random anti-spoofing key (if this is Windows 2000 set one manually).");
231 pair<bool,const char*> unsetValue=props->getString("unsetHeaderValue");
232 if (unsetValue.first)
233 g_unsetHeaderValue = unsetValue.second;
234 if (g_checkSpoofing) {
235 unsetValue = props->getString("spoofKey");
236 if (unsetValue.first)
237 g_spoofKey = unsetValue.second;
239 _invalid_parameter_handler old = _set_invalid_parameter_handler(_my_invalid_parameter_handler);
240 unsigned int randkey=0,randkey2=0,randkey3=0,randkey4=0;
241 if (rand_s(&randkey) == 0 && rand_s(&randkey2) == 0 && rand_s(&randkey3) == 0 && rand_s(&randkey4) == 0) {
242 _set_invalid_parameter_handler(old);
243 ostringstream keystr;
244 keystr << randkey << randkey2 << randkey3 << randkey4;
245 g_spoofKey = keystr.str();
248 _set_invalid_parameter_handler(old);
249 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
250 "Filter failed to generate a random anti-spoofing key (if this is Windows 2000 set one manually).");
251 locker.assign(); // pops lock on SP config
260 props = props->getPropertySet("ISAPI");
262 flag = props->getBool("normalizeRequest");
263 g_bNormalizeRequest = !flag.first || flag.second;
264 flag = props->getBool("safeHeaderNames");
265 g_bSafeHeaderNames = flag.first && flag.second;
266 const DOMElement* child = XMLHelper::getFirstChildElement(props->getElement(),Site);
268 auto_ptr_char id(child->getAttributeNS(NULL,id));
270 g_Sites.insert(pair<string,site_t>(id.get(),site_t(child)));
271 child=XMLHelper::getNextSiblingElement(child,Site);
276 pVer->dwFilterVersion=HTTP_FILTER_REVISION;
277 strncpy(pVer->lpszFilterDesc,"Shibboleth ISAPI Filter",SF_MAX_FILTER_DESC_LEN);
278 pVer->dwFlags=(SF_NOTIFY_ORDER_HIGH |
279 SF_NOTIFY_SECURE_PORT |
280 SF_NOTIFY_NONSECURE_PORT |
281 SF_NOTIFY_PREPROC_HEADERS |
283 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter initialized...");
287 extern "C" BOOL WINAPI TerminateFilter(DWORD)
292 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter shut down...");
296 /* Next up, some suck-free versions of various APIs.
298 You DON'T require people to guess the buffer size and THEN tell them the right size.
299 Returning an LPCSTR is apparently way beyond their ken. Not to mention the fact that
300 constant strings aren't typed as such, making it just that much harder. These versions
301 are now updated to use a special growable buffer object, modeled after the standard
302 string class. The standard string won't work because they left out the option to
303 pre-allocate a non-constant buffer.
309 dynabuf() { bufptr=NULL; buflen=0; }
310 dynabuf(size_t s) { bufptr=new char[buflen=s]; *bufptr=0; }
311 ~dynabuf() { delete[] bufptr; }
312 size_t length() const { return bufptr ? strlen(bufptr) : 0; }
313 size_t size() const { return buflen; }
314 bool empty() const { return length()==0; }
315 void reserve(size_t s, bool keep=false);
316 void erase() { if (bufptr) memset(bufptr,0,buflen); }
317 operator char*() { return bufptr; }
318 bool operator ==(const char* s) const;
319 bool operator !=(const char* s) const { return !(*this==s); }
325 void dynabuf::reserve(size_t s, bool keep)
332 p[buflen]=bufptr[buflen];
338 bool dynabuf::operator==(const char* s) const
340 if (buflen==NULL || s==NULL)
341 return (buflen==NULL && s==NULL);
343 return strcmp(bufptr,s)==0;
346 void GetServerVariable(PHTTP_FILTER_CONTEXT pfc, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
352 while (!pfc->GetServerVariable(pfc,lpszVariable,s,&size)) {
353 // Grumble. Check the error.
354 DWORD e=GetLastError();
355 if (e==ERROR_INSUFFICIENT_BUFFER)
360 if (bRequired && s.empty())
364 void GetServerVariable(LPEXTENSION_CONTROL_BLOCK lpECB, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
370 while (!lpECB->GetServerVariable(lpECB->ConnID,lpszVariable,s,&size)) {
371 // Grumble. Check the error.
372 DWORD e=GetLastError();
373 if (e==ERROR_INSUFFICIENT_BUFFER)
378 if (bRequired && s.empty())
382 void GetHeader(PHTTP_FILTER_PREPROC_HEADERS pn, PHTTP_FILTER_CONTEXT pfc,
383 LPSTR lpszName, dynabuf& s, DWORD size=80, bool bRequired=true)
389 while (!pn->GetHeader(pfc,lpszName,s,&size)) {
390 // Grumble. Check the error.
391 DWORD e=GetLastError();
392 if (e==ERROR_INSUFFICIENT_BUFFER)
397 if (bRequired && s.empty())
401 /****************************************************************************/
404 class ShibTargetIsapiF : public AbstractSPRequest
406 PHTTP_FILTER_CONTEXT m_pfc;
407 PHTTP_FILTER_PREPROC_HEADERS m_pn;
408 multimap<string,string> m_headers;
410 string m_scheme,m_hostname;
411 mutable string m_remote_addr,m_content_type,m_method;
416 ShibTargetIsapiF(PHTTP_FILTER_CONTEXT pfc, PHTTP_FILTER_PREPROC_HEADERS pn, const site_t& site)
417 : AbstractSPRequest(SHIBSP_LOGCAT".ISAPI"), m_pfc(pfc), m_pn(pn), m_allhttp(4096), m_firsttime(true) {
419 // URL path always come from IIS.
421 GetHeader(pn,pfc,"url",var,256,false);
424 // Port may come from IIS or from site def.
425 if (!g_bNormalizeRequest || (pfc->fIsSecurePort && site.m_sslport.empty()) || (!pfc->fIsSecurePort && site.m_port.empty())) {
426 GetServerVariable(pfc,"SERVER_PORT",var,10);
429 else if (pfc->fIsSecurePort) {
430 m_port = atoi(site.m_sslport.c_str());
433 m_port = atoi(site.m_port.c_str());
436 // Scheme may come from site def or be derived from IIS.
437 m_scheme=site.m_scheme;
438 if (m_scheme.empty() || !g_bNormalizeRequest)
439 m_scheme=pfc->fIsSecurePort ? "https" : "http";
441 GetServerVariable(pfc,"SERVER_NAME",var,32);
443 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
445 if (site.m_name!=m_hostname && site.m_aliases.find(m_hostname)==site.m_aliases.end())
446 m_hostname=site.m_name;
448 if (!g_spoofKey.empty()) {
449 GetHeader(pn, pfc, "ShibSpoofCheck:", var, 32, false);
450 if (!var.empty() && g_spoofKey == (char*)var)
455 log(SPDebug, "ISAPI filter running more than once");
457 ~ShibTargetIsapiF() { }
459 const char* getScheme() const {
460 return m_scheme.c_str();
462 const char* getHostname() const {
463 return m_hostname.c_str();
465 int getPort() const {
468 const char* getQueryString() const {
469 const char* uri = getRequestURI();
470 uri = (uri ? strchr(uri, '?') : NULL);
471 return uri ? (uri + 1) : NULL;
473 const char* getMethod() const {
474 if (m_method.empty()) {
476 GetServerVariable(m_pfc,"HTTP_METHOD",var,5,false);
480 return m_method.c_str();
482 string getContentType() const {
483 if (m_content_type.empty()) {
485 GetServerVariable(m_pfc,"HTTP_CONTENT_TYPE",var,32,false);
487 m_content_type = var;
489 return m_content_type;
491 string getRemoteAddr() const {
492 m_remote_addr = AbstractSPRequest::getRemoteAddr();
493 if (m_remote_addr.empty()) {
495 GetServerVariable(m_pfc,"REMOTE_ADDR",var,16,false);
499 return m_remote_addr;
501 void log(SPLogLevel level, const string& msg) {
502 AbstractSPRequest::log(level,msg);
504 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
506 string makeSafeHeader(const char* rawname) const {
508 for (; *rawname; ++rawname) {
509 if (isalnum(*rawname))
514 void clearHeader(const char* rawname, const char* cginame) {
515 if (g_checkSpoofing && m_firsttime) {
516 if (m_allhttp.empty())
517 GetServerVariable(m_pfc, "ALL_HTTP", m_allhttp, 4096);
518 string hdr = g_bSafeHeaderNames ? ("HTTP_" + makeSafeHeader(cginame + 5)) : (string(cginame) + ':');
519 if (strstr(m_allhttp, hdr.c_str()))
520 throw opensaml::SecurityPolicyException("Attempt to spoof header ($1) was detected.", params(1, hdr.c_str()));
522 if (g_bSafeHeaderNames) {
523 string hdr = makeSafeHeader(rawname);
524 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), const_cast<char*>(g_unsetHeaderValue.c_str()));
526 else if (!strcmp(rawname,"REMOTE_USER")) {
527 m_pn->SetHeader(m_pfc, "remote-user:", const_cast<char*>(g_unsetHeaderValue.c_str()));
528 m_pn->SetHeader(m_pfc, "remote_user:", const_cast<char*>(g_unsetHeaderValue.c_str()));
531 string hdr = string(rawname) + ':';
532 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), const_cast<char*>(g_unsetHeaderValue.c_str()));
535 void setHeader(const char* name, const char* value) {
536 string hdr = g_bSafeHeaderNames ? makeSafeHeader(name) : (string(name) + ':');
537 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), const_cast<char*>(value));
539 string getSecureHeader(const char* name) const {
540 string hdr = g_bSafeHeaderNames ? makeSafeHeader(name) : (string(name) + ':');
542 GetHeader(m_pn, m_pfc, const_cast<char*>(hdr.c_str()), buf, 256, false);
543 return string(buf.empty() ? "" : buf);
545 string getHeader(const char* name) const {
549 GetHeader(m_pn, m_pfc, const_cast<char*>(hdr.c_str()), buf, 256, false);
550 return string(buf.empty() ? "" : buf);
552 void setRemoteUser(const char* user) {
553 setHeader("remote-user", user);
555 m_pfc->pFilterContext = NULL;
556 else if (m_pfc->pFilterContext = m_pfc->AllocMem(m_pfc, sizeof(char) * (strlen(user) + 1), NULL))
557 strcpy(reinterpret_cast<char*>(m_pfc->pFilterContext), user);
559 string getRemoteUser() const {
560 return getSecureHeader("remote-user");
562 void setResponseHeader(const char* name, const char* value) {
565 m_headers.insert(make_pair(name,value));
567 m_headers.erase(name);
569 long sendResponse(istream& in, long status) {
570 string hdr = string("Connection: close\r\n");
571 for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
572 hdr += i->first + ": " + i->second + "\r\n";
574 const char* codestr="200 OK";
576 case XMLTOOLING_HTTP_STATUS_UNAUTHORIZED: codestr="401 Authorization Required"; break;
577 case XMLTOOLING_HTTP_STATUS_FORBIDDEN: codestr="403 Forbidden"; break;
578 case XMLTOOLING_HTTP_STATUS_NOTFOUND: codestr="404 Not Found"; break;
579 case XMLTOOLING_HTTP_STATUS_ERROR: codestr="500 Server Error"; break;
581 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, (void*)codestr, (DWORD)hdr.c_str(), 0);
585 DWORD resplen = in.gcount();
586 m_pfc->WriteClient(m_pfc, buf, &resplen, 0);
588 return SF_STATUS_REQ_FINISHED;
590 long sendRedirect(const char* url) {
591 // XXX: Don't support the httpRedirect option, yet.
592 string hdr=string("Location: ") + url + "\r\n"
593 "Content-Type: text/html\r\n"
594 "Content-Length: 40\r\n"
595 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
596 "Cache-Control: private,no-store,no-cache\r\n";
597 for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
598 hdr += i->first + ": " + i->second + "\r\n";
600 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, "302 Please Wait", (DWORD)hdr.c_str(), 0);
601 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
603 m_pfc->WriteClient(m_pfc, (LPVOID)redmsg, &resplen, 0);
604 return SF_STATUS_REQ_FINISHED;
606 long returnDecline() {
607 return SF_STATUS_REQ_NEXT_NOTIFICATION;
610 return SF_STATUS_REQ_NEXT_NOTIFICATION;
613 const vector<string>& getClientCertificates() const {
617 // The filter never processes the POST, so stub these methods.
618 long getContentLength() const { throw IOException("The request's Content-Length is not available to an ISAPI filter."); }
619 const char* getRequestBody() const { throw IOException("The request body is not available to an ISAPI filter."); }
622 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const char* msg)
624 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
625 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
626 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)ctype,0);
627 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Filter Error</TITLE></HEAD><BODY>"
628 "<H1>Shibboleth Filter Error</H1>";
629 DWORD resplen=strlen(xmsg);
630 pfc->WriteClient(pfc,(LPVOID)xmsg,&resplen,0);
632 pfc->WriteClient(pfc,(LPVOID)msg,&resplen,0);
633 static const char* xmsg2="</BODY></HTML>";
634 resplen=strlen(xmsg2);
635 pfc->WriteClient(pfc,(LPVOID)xmsg2,&resplen,0);
636 return SF_STATUS_REQ_FINISHED;
639 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
641 // Is this a log notification?
642 if (notificationType==SF_NOTIFY_LOG) {
643 if (pfc->pFilterContext)
644 ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=reinterpret_cast<char*>(pfc->pFilterContext);
645 return SF_STATUS_REQ_NEXT_NOTIFICATION;
648 PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
651 // Determine web site number. This can't really fail, I don't think.
653 GetServerVariable(pfc,"INSTANCE_ID",buf,10);
655 // Match site instance to host name, skip if no match.
656 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
657 if (map_i==g_Sites.end())
658 return SF_STATUS_REQ_NEXT_NOTIFICATION;
660 ostringstream threadid;
661 threadid << "[" << getpid() << "] isapi_shib" << '\0';
662 xmltooling::NDC ndc(threadid.str().c_str());
664 ShibTargetIsapiF stf(pfc, pn, map_i->second);
666 // "false" because we don't override the Shib settings
667 pair<bool,long> res = stf.getServiceProvider().doAuthentication(stf);
668 if (!g_spoofKey.empty())
669 pn->SetHeader(pfc, "ShibSpoofCheck:", const_cast<char*>(g_spoofKey.c_str()));
670 if (res.first) return res.second;
672 // "false" because we don't override the Shib settings
673 res = stf.getServiceProvider().doExport(stf);
674 if (res.first) return res.second;
676 res = stf.getServiceProvider().doAuthorization(stf);
677 if (res.first) return res.second;
679 return SF_STATUS_REQ_NEXT_NOTIFICATION;
682 return WriteClientError(pfc,"Out of Memory");
685 if (e==ERROR_NO_DATA)
686 return WriteClientError(pfc,"A required variable or header was empty.");
688 return WriteClientError(pfc,"Shibboleth Filter detected unexpected IIS error.");
690 catch (exception& e) {
691 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
692 return WriteClientError(pfc,"Shibboleth Filter caught an exception, check Event Log for details.");
695 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Shibboleth Filter threw an unknown exception.");
697 return WriteClientError(pfc,"Shibboleth Filter threw an unknown exception.");
701 return WriteClientError(pfc,"Shibboleth Filter reached unreachable code, save my walrus!");
705 /****************************************************************************/
708 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* msg)
710 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
711 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
712 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
713 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Error</TITLE></HEAD><BODY><H1>Shibboleth Error</H1>";
714 DWORD resplen=strlen(xmsg);
715 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg,&resplen,HSE_IO_SYNC);
717 lpECB->WriteClient(lpECB->ConnID,(LPVOID)msg,&resplen,HSE_IO_SYNC);
718 static const char* xmsg2="</BODY></HTML>";
719 resplen=strlen(xmsg2);
720 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg2,&resplen,HSE_IO_SYNC);
721 return HSE_STATUS_SUCCESS;
725 class ShibTargetIsapiE : public AbstractSPRequest
727 LPEXTENSION_CONTROL_BLOCK m_lpECB;
728 multimap<string,string> m_headers;
729 mutable vector<string> m_certs;
730 mutable string m_body;
731 mutable bool m_gotBody;
733 string m_scheme,m_hostname,m_uri;
734 mutable string m_remote_addr,m_remote_user;
737 ShibTargetIsapiE(LPEXTENSION_CONTROL_BLOCK lpECB, const site_t& site)
738 : AbstractSPRequest(SHIBSP_LOGCAT".ISAPI"), m_lpECB(lpECB), m_gotBody(false) {
740 GetServerVariable(lpECB,"HTTPS",ssl,5);
741 bool SSL=(ssl=="on" || ssl=="ON");
743 // Scheme may come from site def or be derived from IIS.
744 m_scheme=site.m_scheme;
745 if (m_scheme.empty() || !g_bNormalizeRequest)
746 m_scheme = SSL ? "https" : "http";
748 // URL path always come from IIS.
750 GetServerVariable(lpECB,"URL",url,255);
752 // Port may come from IIS or from site def.
754 if (!g_bNormalizeRequest || (SSL && site.m_sslport.empty()) || (!SSL && site.m_port.empty()))
755 GetServerVariable(lpECB,"SERVER_PORT",port,10);
757 strncpy(port,site.m_sslport.c_str(),10);
758 static_cast<char*>(port)[10]=0;
761 strncpy(port,site.m_port.c_str(),10);
762 static_cast<char*>(port)[10]=0;
767 GetServerVariable(lpECB, "SERVER_NAME", var, 32);
769 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
771 if (site.m_name!=m_hostname && site.m_aliases.find(m_hostname)==site.m_aliases.end())
772 m_hostname=site.m_name;
775 * IIS screws us over on PATH_INFO (the hits keep on coming). We need to figure out if
776 * the server is set up for proper PATH_INFO handling, or "IIS sucks rabid weasels mode",
777 * which is the default. No perfect way to tell, but we can take a good guess by checking
778 * whether the URL is a substring of the PATH_INFO:
780 * e.g. for /Shibboleth.sso/SAML/POST
782 * Bad mode (default):
783 * URL: /Shibboleth.sso
784 * PathInfo: /Shibboleth.sso/SAML/POST
787 * URL: /Shibboleth.sso
788 * PathInfo: /SAML/POST
793 // Clearly we're only in bad mode if path info exists at all.
794 if (lpECB->lpszPathInfo && *(lpECB->lpszPathInfo)) {
795 if (strstr(lpECB->lpszPathInfo,url))
796 // Pretty good chance we're in bad mode, unless the PathInfo repeats the path itself.
797 uri = lpECB->lpszPathInfo;
800 uri += lpECB->lpszPathInfo;
807 // For consistency with Apache, let's add the query string.
808 if (lpECB->lpszQueryString && *(lpECB->lpszQueryString)) {
810 uri += lpECB->lpszQueryString;
813 setRequestURI(uri.c_str());
815 ~ShibTargetIsapiE() { }
817 const char* getScheme() const {
818 return m_scheme.c_str();
820 const char* getHostname() const {
821 return m_hostname.c_str();
823 int getPort() const {
826 const char* getMethod() const {
827 return m_lpECB->lpszMethod;
829 string getContentType() const {
830 return m_lpECB->lpszContentType ? m_lpECB->lpszContentType : "";
832 long getContentLength() const {
833 return m_lpECB->cbTotalBytes;
835 string getRemoteUser() const {
836 if (m_remote_user.empty()) {
838 GetServerVariable(m_lpECB, "REMOTE_USER", var, 32, false);
842 return m_remote_user;
844 string getRemoteAddr() const {
845 m_remote_addr = AbstractSPRequest::getRemoteAddr();
846 if (m_remote_addr.empty()) {
848 GetServerVariable(m_lpECB, "REMOTE_ADDR", var, 16, false);
852 return m_remote_addr;
854 void log(SPLogLevel level, const string& msg) const {
855 AbstractSPRequest::log(level,msg);
857 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
859 string getHeader(const char* name) const {
861 for (; *name; ++name) {
865 hdr += toupper(*name);
868 GetServerVariable(m_lpECB, const_cast<char*>(hdr.c_str()), buf, 128, false);
869 return buf.empty() ? "" : buf;
871 void setResponseHeader(const char* name, const char* value) {
874 m_headers.insert(make_pair(name,value));
876 m_headers.erase(name);
878 const char* getQueryString() const {
879 return m_lpECB->lpszQueryString;
881 const char* getRequestBody() const {
883 return m_body.c_str();
884 if (m_lpECB->cbTotalBytes > 1024*1024) // 1MB?
885 throw opensaml::SecurityPolicyException("Size of request body exceeded 1M size limit.");
886 else if (m_lpECB->cbTotalBytes > m_lpECB->cbAvailable) {
888 DWORD datalen=m_lpECB->cbTotalBytes;
889 if (m_lpECB->cbAvailable > 0) {
890 m_body.assign(reinterpret_cast<char*>(m_lpECB->lpbData),m_lpECB->cbAvailable);
891 datalen-=m_lpECB->cbAvailable;
896 BOOL ret = m_lpECB->ReadClient(m_lpECB->ConnID, buf, &buflen);
898 throw IOException("Error reading request body from browser.");
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_UNAUTHORIZED: codestr="401 Authorization Required"; break;
919 case XMLTOOLING_HTTP_STATUS_FORBIDDEN: codestr="403 Forbidden"; break;
920 case XMLTOOLING_HTTP_STATUS_NOTFOUND: codestr="404 Not Found"; break;
921 case XMLTOOLING_HTTP_STATUS_ERROR: codestr="500 Server Error"; break;
923 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, (void*)codestr, 0, (LPDWORD)hdr.c_str());
927 DWORD resplen = in.gcount();
928 m_lpECB->WriteClient(m_lpECB->ConnID, buf, &resplen, HSE_IO_SYNC);
930 return HSE_STATUS_SUCCESS;
932 long sendRedirect(const char* url) {
933 string hdr=string("Location: ") + url + "\r\n"
934 "Content-Type: text/html\r\n"
935 "Content-Length: 40\r\n"
936 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
937 "Cache-Control: private,no-store,no-cache\r\n";
938 for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
939 hdr += i->first + ": " + i->second + "\r\n";
941 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, "302 Moved", 0, (LPDWORD)hdr.c_str());
942 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
944 m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)redmsg, &resplen, HSE_IO_SYNC);
945 return HSE_STATUS_SUCCESS;
947 // Decline happens in the POST processor if this isn't the shire url
948 // Note that it can also happen with HTAccess, but we don't support that, yet.
949 long returnDecline() {
950 return WriteClientError(
952 "ISAPI extension can only be invoked to process Shibboleth protocol requests."
953 "Make sure the mapped file extension doesn't match actual content."
957 return HSE_STATUS_SUCCESS;
960 const vector<string>& getClientCertificates() const {
961 if (m_certs.empty()) {
962 char CertificateBuf[8192];
963 CERT_CONTEXT_EX ccex;
964 ccex.cbAllocated = sizeof(CertificateBuf);
965 ccex.CertContext.pbCertEncoded = (BYTE*)CertificateBuf;
966 DWORD dwSize = sizeof(ccex);
968 if (m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_GET_CERT_INFO_EX, (LPVOID)&ccex, (LPDWORD)dwSize, NULL)) {
969 if (ccex.CertContext.cbCertEncoded) {
971 XMLByte* serialized = Base64::encode(reinterpret_cast<XMLByte*>(CertificateBuf), ccex.CertContext.cbCertEncoded, &outlen);
972 m_certs.push_back(reinterpret_cast<char*>(serialized));
973 #ifdef SHIBSP_XERCESC_HAS_XMLBYTE_RELEASE
974 XMLString::release(&serialized);
976 XMLString::release((char**)&serialized);
984 // Not used in the extension.
985 void clearHeader(const char* rawname, const char* cginame) { throw runtime_error("clearHeader not implemented"); }
986 void setHeader(const char* name, const char* value) { throw runtime_error("setHeader not implemented"); }
987 void setRemoteUser(const char* user) { throw runtime_error("setRemoteUser not implemented"); }
990 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
993 ostringstream threadid;
994 threadid << "[" << getpid() << "] isapi_shib_extension" << '\0';
995 xmltooling::NDC ndc(threadid.str().c_str());
997 // Determine web site number. This can't really fail, I don't think.
999 GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
1001 // Match site instance to host name, skip if no match.
1002 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
1003 if (map_i==g_Sites.end())
1004 return WriteClientError(lpECB, "Shibboleth Extension not configured for web site (check <ISAPI> mappings in configuration).");
1006 ShibTargetIsapiE ste(lpECB, map_i->second);
1007 pair<bool,long> res = ste.getServiceProvider().doHandler(ste);
1008 if (res.first) return res.second;
1010 return WriteClientError(lpECB, "Shibboleth Extension failed to process request");
1014 return WriteClientError(lpECB,"Out of Memory");
1017 if (e==ERROR_NO_DATA)
1018 return WriteClientError(lpECB,"A required variable or header was empty.");
1020 return WriteClientError(lpECB,"Server detected unexpected IIS error.");
1022 catch (exception& e) {
1023 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
1024 return WriteClientError(lpECB,"Shibboleth Extension caught an exception, check Event Log for details.");
1027 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Shibboleth Extension threw an unknown exception.");
1029 return WriteClientError(lpECB,"Shibboleth Extension threw an unknown exception.");
1033 // If we get here we've got an error.
1034 return HSE_STATUS_ERROR;