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;
205 pair<bool,const char*> unsetValue=props->getString("unsetHeaderValue");
206 if (unsetValue.first)
207 g_unsetHeaderValue = unsetValue.second;
208 if (g_checkSpoofing) {
209 unsetValue = props->getString("spoofKey");
210 if (unsetValue.first)
211 g_spoofKey = unsetValue.second;
213 _invalid_parameter_handler old = _set_invalid_parameter_handler(_my_invalid_parameter_handler);
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 _set_invalid_parameter_handler(old);
217 ostringstream keystr;
218 keystr << randkey << randkey2 << randkey3 << randkey4;
219 g_spoofKey = keystr.str();
222 _set_invalid_parameter_handler(old);
223 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
224 "Filter failed to generate a random anti-spoofing key (if this is Windows 2000 set one manually).");
225 locker.assign(); // pops lock on SP config
233 props = props->getPropertySet("ISAPI");
235 flag = props->getBool("normalizeRequest");
236 g_bNormalizeRequest = !flag.first || flag.second;
237 flag = props->getBool("safeHeaderNames");
238 g_bSafeHeaderNames = flag.first && flag.second;
239 const DOMElement* child = XMLHelper::getFirstChildElement(props->getElement(),Site);
241 auto_ptr_char id(child->getAttributeNS(NULL,id));
243 g_Sites.insert(pair<string,site_t>(id.get(),site_t(child)));
244 child=XMLHelper::getNextSiblingElement(child,Site);
249 pVer->dwFilterVersion=HTTP_FILTER_REVISION;
250 strncpy(pVer->lpszFilterDesc,"Shibboleth ISAPI Filter",SF_MAX_FILTER_DESC_LEN);
251 pVer->dwFlags=(SF_NOTIFY_ORDER_HIGH |
252 SF_NOTIFY_SECURE_PORT |
253 SF_NOTIFY_NONSECURE_PORT |
254 SF_NOTIFY_PREPROC_HEADERS |
256 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter initialized...");
260 extern "C" BOOL WINAPI TerminateFilter(DWORD)
265 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter shut down...");
269 /* Next up, some suck-free versions of various APIs.
271 You DON'T require people to guess the buffer size and THEN tell them the right size.
272 Returning an LPCSTR is apparently way beyond their ken. Not to mention the fact that
273 constant strings aren't typed as such, making it just that much harder. These versions
274 are now updated to use a special growable buffer object, modeled after the standard
275 string class. The standard string won't work because they left out the option to
276 pre-allocate a non-constant buffer.
282 dynabuf() { bufptr=NULL; buflen=0; }
283 dynabuf(size_t s) { bufptr=new char[buflen=s]; *bufptr=0; }
284 ~dynabuf() { delete[] bufptr; }
285 size_t length() const { return bufptr ? strlen(bufptr) : 0; }
286 size_t size() const { return buflen; }
287 bool empty() const { return length()==0; }
288 void reserve(size_t s, bool keep=false);
289 void erase() { if (bufptr) memset(bufptr,0,buflen); }
290 operator char*() { return bufptr; }
291 bool operator ==(const char* s) const;
292 bool operator !=(const char* s) const { return !(*this==s); }
298 void dynabuf::reserve(size_t s, bool keep)
305 p[buflen]=bufptr[buflen];
311 bool dynabuf::operator==(const char* s) const
313 if (buflen==NULL || s==NULL)
314 return (buflen==NULL && s==NULL);
316 return strcmp(bufptr,s)==0;
319 void GetServerVariable(PHTTP_FILTER_CONTEXT pfc, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
325 while (!pfc->GetServerVariable(pfc,lpszVariable,s,&size)) {
326 // Grumble. Check the error.
327 DWORD e=GetLastError();
328 if (e==ERROR_INSUFFICIENT_BUFFER)
333 if (bRequired && s.empty())
337 void GetServerVariable(LPEXTENSION_CONTROL_BLOCK lpECB, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
343 while (!lpECB->GetServerVariable(lpECB->ConnID,lpszVariable,s,&size)) {
344 // Grumble. Check the error.
345 DWORD e=GetLastError();
346 if (e==ERROR_INSUFFICIENT_BUFFER)
351 if (bRequired && s.empty())
355 void GetHeader(PHTTP_FILTER_PREPROC_HEADERS pn, PHTTP_FILTER_CONTEXT pfc,
356 LPSTR lpszName, dynabuf& s, DWORD size=80, bool bRequired=true)
362 while (!pn->GetHeader(pfc,lpszName,s,&size)) {
363 // Grumble. Check the error.
364 DWORD e=GetLastError();
365 if (e==ERROR_INSUFFICIENT_BUFFER)
370 if (bRequired && s.empty())
374 /****************************************************************************/
377 class ShibTargetIsapiF : public AbstractSPRequest
379 PHTTP_FILTER_CONTEXT m_pfc;
380 PHTTP_FILTER_PREPROC_HEADERS m_pn;
381 multimap<string,string> m_headers;
383 string m_scheme,m_hostname;
384 mutable string m_remote_addr,m_content_type,m_method;
389 ShibTargetIsapiF(PHTTP_FILTER_CONTEXT pfc, PHTTP_FILTER_PREPROC_HEADERS pn, const site_t& site)
390 : AbstractSPRequest(SHIBSP_LOGCAT".ISAPI"), m_pfc(pfc), m_pn(pn), m_allhttp(4096), m_firsttime(true) {
392 // URL path always come from IIS.
394 GetHeader(pn,pfc,"url",var,256,false);
397 // Port may come from IIS or from site def.
398 if (!g_bNormalizeRequest || (pfc->fIsSecurePort && site.m_sslport.empty()) || (!pfc->fIsSecurePort && site.m_port.empty())) {
399 GetServerVariable(pfc,"SERVER_PORT",var,10);
402 else if (pfc->fIsSecurePort) {
403 m_port = atoi(site.m_sslport.c_str());
406 m_port = atoi(site.m_port.c_str());
409 // Scheme may come from site def or be derived from IIS.
410 m_scheme=site.m_scheme;
411 if (m_scheme.empty() || !g_bNormalizeRequest)
412 m_scheme=pfc->fIsSecurePort ? "https" : "http";
414 GetServerVariable(pfc,"SERVER_NAME",var,32);
416 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
418 if (site.m_name!=m_hostname && site.m_aliases.find(m_hostname)==site.m_aliases.end())
419 m_hostname=site.m_name;
421 if (!g_spoofKey.empty()) {
422 GetHeader(pn, pfc, "ShibSpoofCheck:", var, 32, false);
423 if (!var.empty() && g_spoofKey == (char*)var)
428 log(SPDebug, "ISAPI filter running more than once");
430 ~ShibTargetIsapiF() { }
432 const char* getScheme() const {
433 return m_scheme.c_str();
435 const char* getHostname() const {
436 return m_hostname.c_str();
438 int getPort() const {
441 const char* getQueryString() const {
442 const char* uri = getRequestURI();
443 uri = (uri ? strchr(uri, '?') : NULL);
444 return uri ? (uri + 1) : NULL;
446 const char* getMethod() const {
447 if (m_method.empty()) {
449 GetServerVariable(m_pfc,"HTTP_METHOD",var,5,false);
453 return m_method.c_str();
455 string getContentType() const {
456 if (m_content_type.empty()) {
458 GetServerVariable(m_pfc,"HTTP_CONTENT_TYPE",var,32,false);
460 m_content_type = var;
462 return m_content_type;
464 string getRemoteAddr() const {
465 m_remote_addr = AbstractSPRequest::getRemoteAddr();
466 if (m_remote_addr.empty()) {
468 GetServerVariable(m_pfc,"REMOTE_ADDR",var,16,false);
472 return m_remote_addr;
474 void log(SPLogLevel level, const string& msg) {
475 AbstractSPRequest::log(level,msg);
477 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
479 string makeSafeHeader(const char* rawname) const {
481 for (; *rawname; ++rawname) {
482 if (isalnum(*rawname))
487 void clearHeader(const char* rawname, const char* cginame) {
488 if (g_checkSpoofing && m_firsttime) {
489 if (m_allhttp.empty())
490 GetServerVariable(m_pfc, "ALL_HTTP", m_allhttp, 4096);
491 string hdr = g_bSafeHeaderNames ? ("HTTP_" + makeSafeHeader(cginame + 5)) : (string(cginame) + ':');
492 if (strstr(m_allhttp, hdr.c_str()))
493 throw opensaml::SecurityPolicyException("Attempt to spoof header ($1) was detected.", params(1, hdr.c_str()));
495 if (g_bSafeHeaderNames) {
496 string hdr = makeSafeHeader(rawname);
497 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), const_cast<char*>(g_unsetHeaderValue.c_str()));
499 else if (!strcmp(rawname,"REMOTE_USER")) {
500 m_pn->SetHeader(m_pfc, "remote-user:", const_cast<char*>(g_unsetHeaderValue.c_str()));
501 m_pn->SetHeader(m_pfc, "remote_user:", const_cast<char*>(g_unsetHeaderValue.c_str()));
504 string hdr = string(rawname) + ':';
505 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), const_cast<char*>(g_unsetHeaderValue.c_str()));
508 void setHeader(const char* name, const char* value) {
509 string hdr = g_bSafeHeaderNames ? makeSafeHeader(name) : (string(name) + ':');
510 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), const_cast<char*>(value));
512 string getSecureHeader(const char* name) const {
513 string hdr = g_bSafeHeaderNames ? makeSafeHeader(name) : (string(name) + ':');
515 GetHeader(m_pn, m_pfc, const_cast<char*>(hdr.c_str()), buf, 256, false);
516 return string(buf.empty() ? "" : buf);
518 string getHeader(const char* name) const {
522 GetHeader(m_pn, m_pfc, const_cast<char*>(hdr.c_str()), buf, 256, false);
523 return string(buf.empty() ? "" : buf);
525 void setRemoteUser(const char* user) {
526 setHeader("remote-user", user);
528 m_pfc->pFilterContext = NULL;
529 else if (m_pfc->pFilterContext = m_pfc->AllocMem(m_pfc, sizeof(char) * (strlen(user) + 1), NULL))
530 strcpy(reinterpret_cast<char*>(m_pfc->pFilterContext), user);
532 string getRemoteUser() const {
533 return getSecureHeader("remote-user");
535 void setResponseHeader(const char* name, const char* value) {
538 m_headers.insert(make_pair(name,value));
540 m_headers.erase(name);
542 long sendResponse(istream& in, long status) {
543 string hdr = string("Connection: close\r\n");
544 for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
545 hdr += i->first + ": " + i->second + "\r\n";
547 const char* codestr="200 OK";
549 case XMLTOOLING_HTTP_STATUS_UNAUTHORIZED: codestr="401 Authorization Required"; break;
550 case XMLTOOLING_HTTP_STATUS_FORBIDDEN: codestr="403 Forbidden"; break;
551 case XMLTOOLING_HTTP_STATUS_NOTFOUND: codestr="404 Not Found"; break;
552 case XMLTOOLING_HTTP_STATUS_ERROR: codestr="500 Server Error"; break;
554 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, (void*)codestr, (DWORD)hdr.c_str(), 0);
558 DWORD resplen = in.gcount();
559 m_pfc->WriteClient(m_pfc, buf, &resplen, 0);
561 return SF_STATUS_REQ_FINISHED;
563 long sendRedirect(const char* url) {
564 // XXX: Don't support the httpRedirect option, yet.
565 string hdr=string("Location: ") + url + "\r\n"
566 "Content-Type: text/html\r\n"
567 "Content-Length: 40\r\n"
568 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
569 "Cache-Control: private,no-store,no-cache\r\n";
570 for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
571 hdr += i->first + ": " + i->second + "\r\n";
573 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, "302 Please Wait", (DWORD)hdr.c_str(), 0);
574 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
576 m_pfc->WriteClient(m_pfc, (LPVOID)redmsg, &resplen, 0);
577 return SF_STATUS_REQ_FINISHED;
579 long returnDecline() {
580 return SF_STATUS_REQ_NEXT_NOTIFICATION;
583 return SF_STATUS_REQ_NEXT_NOTIFICATION;
586 const vector<string>& getClientCertificates() const {
590 // The filter never processes the POST, so stub these methods.
591 long getContentLength() const { throw IOException("The request's Content-Length is not available to an ISAPI filter."); }
592 const char* getRequestBody() const { throw IOException("The request body is not available to an ISAPI filter."); }
595 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const char* msg)
597 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
598 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
599 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)ctype,0);
600 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Filter Error</TITLE></HEAD><BODY>"
601 "<H1>Shibboleth Filter Error</H1>";
602 DWORD resplen=strlen(xmsg);
603 pfc->WriteClient(pfc,(LPVOID)xmsg,&resplen,0);
605 pfc->WriteClient(pfc,(LPVOID)msg,&resplen,0);
606 static const char* xmsg2="</BODY></HTML>";
607 resplen=strlen(xmsg2);
608 pfc->WriteClient(pfc,(LPVOID)xmsg2,&resplen,0);
609 return SF_STATUS_REQ_FINISHED;
612 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
614 // Is this a log notification?
615 if (notificationType==SF_NOTIFY_LOG) {
616 if (pfc->pFilterContext)
617 ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=reinterpret_cast<char*>(pfc->pFilterContext);
618 return SF_STATUS_REQ_NEXT_NOTIFICATION;
621 PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
624 // Determine web site number. This can't really fail, I don't think.
626 GetServerVariable(pfc,"INSTANCE_ID",buf,10);
628 // Match site instance to host name, skip if no match.
629 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
630 if (map_i==g_Sites.end())
631 return SF_STATUS_REQ_NEXT_NOTIFICATION;
633 ostringstream threadid;
634 threadid << "[" << getpid() << "] isapi_shib" << '\0';
635 xmltooling::NDC ndc(threadid.str().c_str());
637 ShibTargetIsapiF stf(pfc, pn, map_i->second);
639 // "false" because we don't override the Shib settings
640 pair<bool,long> res = stf.getServiceProvider().doAuthentication(stf);
641 if (!g_spoofKey.empty())
642 pn->SetHeader(pfc, "ShibSpoofCheck:", const_cast<char*>(g_spoofKey.c_str()));
643 if (res.first) return res.second;
645 // "false" because we don't override the Shib settings
646 res = stf.getServiceProvider().doExport(stf);
647 if (res.first) return res.second;
649 res = stf.getServiceProvider().doAuthorization(stf);
650 if (res.first) return res.second;
652 return SF_STATUS_REQ_NEXT_NOTIFICATION;
655 return WriteClientError(pfc,"Out of Memory");
658 if (e==ERROR_NO_DATA)
659 return WriteClientError(pfc,"A required variable or header was empty.");
661 return WriteClientError(pfc,"Shibboleth Filter detected unexpected IIS error.");
663 catch (exception& e) {
664 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
665 return WriteClientError(pfc,"Shibboleth Filter caught an exception, check Event Log for details.");
668 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Shibboleth Filter threw an unknown exception.");
670 return WriteClientError(pfc,"Shibboleth Filter threw an unknown exception.");
674 return WriteClientError(pfc,"Shibboleth Filter reached unreachable code, save my walrus!");
678 /****************************************************************************/
681 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* msg)
683 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
684 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
685 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
686 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Error</TITLE></HEAD><BODY><H1>Shibboleth Error</H1>";
687 DWORD resplen=strlen(xmsg);
688 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg,&resplen,HSE_IO_SYNC);
690 lpECB->WriteClient(lpECB->ConnID,(LPVOID)msg,&resplen,HSE_IO_SYNC);
691 static const char* xmsg2="</BODY></HTML>";
692 resplen=strlen(xmsg2);
693 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg2,&resplen,HSE_IO_SYNC);
694 return HSE_STATUS_SUCCESS;
698 class ShibTargetIsapiE : public AbstractSPRequest
700 LPEXTENSION_CONTROL_BLOCK m_lpECB;
701 multimap<string,string> m_headers;
702 mutable vector<string> m_certs;
703 mutable string m_body;
704 mutable bool m_gotBody;
706 string m_scheme,m_hostname,m_uri;
707 mutable string m_remote_addr,m_remote_user;
710 ShibTargetIsapiE(LPEXTENSION_CONTROL_BLOCK lpECB, const site_t& site)
711 : AbstractSPRequest(SHIBSP_LOGCAT".ISAPI"), m_lpECB(lpECB), m_gotBody(false) {
713 GetServerVariable(lpECB,"HTTPS",ssl,5);
714 bool SSL=(ssl=="on" || ssl=="ON");
716 // Scheme may come from site def or be derived from IIS.
717 m_scheme=site.m_scheme;
718 if (m_scheme.empty() || !g_bNormalizeRequest)
719 m_scheme = SSL ? "https" : "http";
721 // URL path always come from IIS.
723 GetServerVariable(lpECB,"URL",url,255);
725 // Port may come from IIS or from site def.
727 if (!g_bNormalizeRequest || (SSL && site.m_sslport.empty()) || (!SSL && site.m_port.empty()))
728 GetServerVariable(lpECB,"SERVER_PORT",port,10);
730 strncpy(port,site.m_sslport.c_str(),10);
731 static_cast<char*>(port)[10]=0;
734 strncpy(port,site.m_port.c_str(),10);
735 static_cast<char*>(port)[10]=0;
740 GetServerVariable(lpECB, "SERVER_NAME", var, 32);
742 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
744 if (site.m_name!=m_hostname && site.m_aliases.find(m_hostname)==site.m_aliases.end())
745 m_hostname=site.m_name;
748 * IIS screws us over on PATH_INFO (the hits keep on coming). We need to figure out if
749 * the server is set up for proper PATH_INFO handling, or "IIS sucks rabid weasels mode",
750 * which is the default. No perfect way to tell, but we can take a good guess by checking
751 * whether the URL is a substring of the PATH_INFO:
753 * e.g. for /Shibboleth.sso/SAML/POST
755 * Bad mode (default):
756 * URL: /Shibboleth.sso
757 * PathInfo: /Shibboleth.sso/SAML/POST
760 * URL: /Shibboleth.sso
761 * PathInfo: /SAML/POST
766 // Clearly we're only in bad mode if path info exists at all.
767 if (lpECB->lpszPathInfo && *(lpECB->lpszPathInfo)) {
768 if (strstr(lpECB->lpszPathInfo,url))
769 // Pretty good chance we're in bad mode, unless the PathInfo repeats the path itself.
770 uri = lpECB->lpszPathInfo;
773 uri += lpECB->lpszPathInfo;
780 // For consistency with Apache, let's add the query string.
781 if (lpECB->lpszQueryString && *(lpECB->lpszQueryString)) {
783 uri += lpECB->lpszQueryString;
786 setRequestURI(uri.c_str());
788 ~ShibTargetIsapiE() { }
790 const char* getScheme() const {
791 return m_scheme.c_str();
793 const char* getHostname() const {
794 return m_hostname.c_str();
796 int getPort() const {
799 const char* getMethod() const {
800 return m_lpECB->lpszMethod;
802 string getContentType() const {
803 return m_lpECB->lpszContentType ? m_lpECB->lpszContentType : "";
805 long getContentLength() const {
806 return m_lpECB->cbTotalBytes;
808 string getRemoteUser() const {
809 if (m_remote_user.empty()) {
811 GetServerVariable(m_lpECB, "REMOTE_USER", var, 32, false);
815 return m_remote_user;
817 string getRemoteAddr() const {
818 m_remote_addr = AbstractSPRequest::getRemoteAddr();
819 if (m_remote_addr.empty()) {
821 GetServerVariable(m_lpECB, "REMOTE_ADDR", var, 16, false);
825 return m_remote_addr;
827 void log(SPLogLevel level, const string& msg) const {
828 AbstractSPRequest::log(level,msg);
830 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
832 string getHeader(const char* name) const {
834 for (; *name; ++name) {
838 hdr += toupper(*name);
841 GetServerVariable(m_lpECB, const_cast<char*>(hdr.c_str()), buf, 128, false);
842 return buf.empty() ? "" : buf;
844 void setResponseHeader(const char* name, const char* value) {
847 m_headers.insert(make_pair(name,value));
849 m_headers.erase(name);
851 const char* getQueryString() const {
852 return m_lpECB->lpszQueryString;
854 const char* getRequestBody() const {
856 return m_body.c_str();
857 if (m_lpECB->cbTotalBytes > 1024*1024) // 1MB?
858 throw opensaml::SecurityPolicyException("Size of request body exceeded 1M size limit.");
859 else if (m_lpECB->cbTotalBytes > m_lpECB->cbAvailable) {
861 DWORD datalen=m_lpECB->cbTotalBytes;
862 if (m_lpECB->cbAvailable > 0) {
863 m_body.assign(reinterpret_cast<char*>(m_lpECB->lpbData),m_lpECB->cbAvailable);
864 datalen-=m_lpECB->cbAvailable;
869 BOOL ret = m_lpECB->ReadClient(m_lpECB->ConnID, buf, &buflen);
871 throw IOException("Error reading request body from browser.");
873 throw IOException("Socket closed while reading request body from browser.");
874 m_body.append(buf, buflen);
878 else if (m_lpECB->cbAvailable) {
880 m_body.assign(reinterpret_cast<char*>(m_lpECB->lpbData),m_lpECB->cbAvailable);
882 return m_body.c_str();
884 long sendResponse(istream& in, long status) {
885 string hdr = string("Connection: close\r\n");
886 for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
887 hdr += i->first + ": " + i->second + "\r\n";
889 const char* codestr="200 OK";
891 case XMLTOOLING_HTTP_STATUS_UNAUTHORIZED: codestr="401 Authorization Required"; break;
892 case XMLTOOLING_HTTP_STATUS_FORBIDDEN: codestr="403 Forbidden"; break;
893 case XMLTOOLING_HTTP_STATUS_NOTFOUND: codestr="404 Not Found"; break;
894 case XMLTOOLING_HTTP_STATUS_ERROR: codestr="500 Server Error"; break;
896 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, (void*)codestr, 0, (LPDWORD)hdr.c_str());
900 DWORD resplen = in.gcount();
901 m_lpECB->WriteClient(m_lpECB->ConnID, buf, &resplen, HSE_IO_SYNC);
903 return HSE_STATUS_SUCCESS;
905 long sendRedirect(const char* url) {
906 string hdr=string("Location: ") + url + "\r\n"
907 "Content-Type: text/html\r\n"
908 "Content-Length: 40\r\n"
909 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
910 "Cache-Control: private,no-store,no-cache\r\n";
911 for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
912 hdr += i->first + ": " + i->second + "\r\n";
914 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, "302 Moved", 0, (LPDWORD)hdr.c_str());
915 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
917 m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)redmsg, &resplen, HSE_IO_SYNC);
918 return HSE_STATUS_SUCCESS;
920 // Decline happens in the POST processor if this isn't the shire url
921 // Note that it can also happen with HTAccess, but we don't support that, yet.
922 long returnDecline() {
923 return WriteClientError(
925 "ISAPI extension can only be invoked to process Shibboleth protocol requests."
926 "Make sure the mapped file extension doesn't match actual content."
930 return HSE_STATUS_SUCCESS;
933 const vector<string>& getClientCertificates() const {
934 if (m_certs.empty()) {
935 char CertificateBuf[8192];
936 CERT_CONTEXT_EX ccex;
937 ccex.cbAllocated = sizeof(CertificateBuf);
938 ccex.CertContext.pbCertEncoded = (BYTE*)CertificateBuf;
939 DWORD dwSize = sizeof(ccex);
941 if (m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_GET_CERT_INFO_EX, (LPVOID)&ccex, (LPDWORD)dwSize, NULL)) {
942 if (ccex.CertContext.cbCertEncoded) {
944 XMLByte* serialized = Base64::encode(reinterpret_cast<XMLByte*>(CertificateBuf), ccex.CertContext.cbCertEncoded, &outlen);
945 m_certs.push_back(reinterpret_cast<char*>(serialized));
946 #ifdef SHIBSP_XERCESC_HAS_XMLBYTE_RELEASE
947 XMLString::release(&serialized);
949 XMLString::release((char**)&serialized);
957 // Not used in the extension.
958 void clearHeader(const char* rawname, const char* cginame) { throw runtime_error("clearHeader not implemented"); }
959 void setHeader(const char* name, const char* value) { throw runtime_error("setHeader not implemented"); }
960 void setRemoteUser(const char* user) { throw runtime_error("setRemoteUser not implemented"); }
963 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
966 ostringstream threadid;
967 threadid << "[" << getpid() << "] isapi_shib_extension" << '\0';
968 xmltooling::NDC ndc(threadid.str().c_str());
970 // Determine web site number. This can't really fail, I don't think.
972 GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
974 // Match site instance to host name, skip if no match.
975 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
976 if (map_i==g_Sites.end())
977 return WriteClientError(lpECB, "Shibboleth Extension not configured for web site (check <ISAPI> mappings in configuration).");
979 ShibTargetIsapiE ste(lpECB, map_i->second);
980 pair<bool,long> res = ste.getServiceProvider().doHandler(ste);
981 if (res.first) return res.second;
983 return WriteClientError(lpECB, "Shibboleth Extension failed to process request");
987 return WriteClientError(lpECB,"Out of Memory");
990 if (e==ERROR_NO_DATA)
991 return WriteClientError(lpECB,"A required variable or header was empty.");
993 return WriteClientError(lpECB,"Server detected unexpected IIS error.");
995 catch (exception& e) {
996 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
997 return WriteClientError(lpECB,"Shibboleth Extension caught an exception, check Event Log for details.");
1000 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Shibboleth Extension threw an unknown exception.");
1002 return WriteClientError(lpECB,"Shibboleth Extension threw an unknown exception.");
1006 // If we get here we've got an error.
1007 return HSE_STATUS_ERROR;