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 extern "C" __declspec(dllexport) BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID)
119 if (fdwReason==DLL_PROCESS_ATTACH)
124 extern "C" BOOL WINAPI GetExtensionVersion(HSE_VERSION_INFO* pVer)
130 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
131 "Extension mode startup not possible, is the DLL loaded as a filter?");
135 pVer->dwExtensionVersion=HSE_VERSION;
136 strncpy(pVer->lpszExtensionDesc,"Shibboleth ISAPI Extension",HSE_MAX_EXT_DLL_NAME_LEN-1);
140 extern "C" BOOL WINAPI TerminateExtension(DWORD)
142 return TRUE; // cleanup should happen when filter unloads
145 extern "C" BOOL WINAPI GetFilterVersion(PHTTP_FILTER_VERSION pVer)
150 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
151 "Reentrant filter initialization, ignoring...");
155 g_Config=&SPConfig::getConfig();
156 g_Config->setFeatures(
159 SPConfig::RequestMapping |
160 SPConfig::InProcess |
164 if (!g_Config->init()) {
166 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
167 "Filter startup failed during library initialization, check native log for help.");
172 if (!g_Config->instantiate(NULL, true))
173 throw runtime_error("unknown error");
175 catch (exception& ex) {
178 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, ex.what());
179 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
180 "Filter startup failed to load configuration, check native log for details.");
184 // Access implementation-specifics and site mappings.
185 ServiceProvider* sp=g_Config->getServiceProvider();
187 const PropertySet* props=sp->getPropertySet("InProcess");
189 pair<bool,bool> flag=props->getBool("checkSpoofing");
190 g_checkSpoofing = !flag.first || flag.second;
191 flag=props->getBool("catchAll");
192 g_catchAll = flag.first && flag.second;
194 pair<bool,const char*> unsetValue=props->getString("unsetHeaderValue");
195 if (unsetValue.first)
196 g_unsetHeaderValue = unsetValue.second;
197 if (g_checkSpoofing) {
198 unsetValue = props->getString("spoofKey");
199 if (unsetValue.first)
200 g_spoofKey = unsetValue.second;
202 unsigned int randkey=0,randkey2=0,randkey3=0,randkey4=0;
203 if (rand_s(&randkey) == 0 && rand_s(&randkey2) == 0 && rand_s(&randkey3) == 0 && rand_s(&randkey4) == 0) {
204 ostringstream keystr;
205 keystr << randkey << randkey2 << randkey3 << randkey4;
206 g_spoofKey = keystr.str();
209 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
210 "Filter failed to generate a random anti-spoofing key (if this is Windows 2000 set one manually).");
218 props = props->getPropertySet("ISAPI");
220 flag = props->getBool("normalizeRequest");
221 g_bNormalizeRequest = !flag.first || flag.second;
222 flag = props->getBool("safeHeaderNames");
223 g_bSafeHeaderNames = flag.first && flag.second;
224 const DOMElement* child = XMLHelper::getFirstChildElement(props->getElement(),Site);
226 auto_ptr_char id(child->getAttributeNS(NULL,id));
228 g_Sites.insert(pair<string,site_t>(id.get(),site_t(child)));
229 child=XMLHelper::getNextSiblingElement(child,Site);
234 pVer->dwFilterVersion=HTTP_FILTER_REVISION;
235 strncpy(pVer->lpszFilterDesc,"Shibboleth ISAPI Filter",SF_MAX_FILTER_DESC_LEN);
236 pVer->dwFlags=(SF_NOTIFY_ORDER_HIGH |
237 SF_NOTIFY_SECURE_PORT |
238 SF_NOTIFY_NONSECURE_PORT |
239 SF_NOTIFY_PREPROC_HEADERS |
241 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter initialized...");
245 extern "C" BOOL WINAPI TerminateFilter(DWORD)
250 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter shut down...");
254 /* Next up, some suck-free versions of various APIs.
256 You DON'T require people to guess the buffer size and THEN tell them the right size.
257 Returning an LPCSTR is apparently way beyond their ken. Not to mention the fact that
258 constant strings aren't typed as such, making it just that much harder. These versions
259 are now updated to use a special growable buffer object, modeled after the standard
260 string class. The standard string won't work because they left out the option to
261 pre-allocate a non-constant buffer.
267 dynabuf() { bufptr=NULL; buflen=0; }
268 dynabuf(size_t s) { bufptr=new char[buflen=s]; *bufptr=0; }
269 ~dynabuf() { delete[] bufptr; }
270 size_t length() const { return bufptr ? strlen(bufptr) : 0; }
271 size_t size() const { return buflen; }
272 bool empty() const { return length()==0; }
273 void reserve(size_t s, bool keep=false);
274 void erase() { if (bufptr) memset(bufptr,0,buflen); }
275 operator char*() { return bufptr; }
276 bool operator ==(const char* s) const;
277 bool operator !=(const char* s) const { return !(*this==s); }
283 void dynabuf::reserve(size_t s, bool keep)
290 p[buflen]=bufptr[buflen];
296 bool dynabuf::operator==(const char* s) const
298 if (buflen==NULL || s==NULL)
299 return (buflen==NULL && s==NULL);
301 return strcmp(bufptr,s)==0;
304 void GetServerVariable(PHTTP_FILTER_CONTEXT pfc, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
310 while (!pfc->GetServerVariable(pfc,lpszVariable,s,&size)) {
311 // Grumble. Check the error.
312 DWORD e=GetLastError();
313 if (e==ERROR_INSUFFICIENT_BUFFER)
318 if (bRequired && s.empty())
322 void GetServerVariable(LPEXTENSION_CONTROL_BLOCK lpECB, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
328 while (!lpECB->GetServerVariable(lpECB->ConnID,lpszVariable,s,&size)) {
329 // Grumble. Check the error.
330 DWORD e=GetLastError();
331 if (e==ERROR_INSUFFICIENT_BUFFER)
336 if (bRequired && s.empty())
340 void GetHeader(PHTTP_FILTER_PREPROC_HEADERS pn, PHTTP_FILTER_CONTEXT pfc,
341 LPSTR lpszName, dynabuf& s, DWORD size=80, bool bRequired=true)
347 while (!pn->GetHeader(pfc,lpszName,s,&size)) {
348 // Grumble. Check the error.
349 DWORD e=GetLastError();
350 if (e==ERROR_INSUFFICIENT_BUFFER)
355 if (bRequired && s.empty())
359 /****************************************************************************/
362 class ShibTargetIsapiF : public AbstractSPRequest
364 PHTTP_FILTER_CONTEXT m_pfc;
365 PHTTP_FILTER_PREPROC_HEADERS m_pn;
366 multimap<string,string> m_headers;
368 string m_scheme,m_hostname;
369 mutable string m_remote_addr,m_content_type,m_method;
374 ShibTargetIsapiF(PHTTP_FILTER_CONTEXT pfc, PHTTP_FILTER_PREPROC_HEADERS pn, const site_t& site)
375 : AbstractSPRequest(SHIBSP_LOGCAT".ISAPI"), m_pfc(pfc), m_pn(pn), m_allhttp(4096), m_firsttime(true) {
377 // URL path always come from IIS.
379 GetHeader(pn,pfc,"url",var,256,false);
382 // Port may come from IIS or from site def.
383 if (!g_bNormalizeRequest || (pfc->fIsSecurePort && site.m_sslport.empty()) || (!pfc->fIsSecurePort && site.m_port.empty())) {
384 GetServerVariable(pfc,"SERVER_PORT",var,10);
387 else if (pfc->fIsSecurePort) {
388 m_port = atoi(site.m_sslport.c_str());
391 m_port = atoi(site.m_port.c_str());
394 // Scheme may come from site def or be derived from IIS.
395 m_scheme=site.m_scheme;
396 if (m_scheme.empty() || !g_bNormalizeRequest)
397 m_scheme=pfc->fIsSecurePort ? "https" : "http";
399 GetServerVariable(pfc,"SERVER_NAME",var,32);
401 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
403 if (site.m_name!=m_hostname && site.m_aliases.find(m_hostname)==site.m_aliases.end())
404 m_hostname=site.m_name;
406 if (!g_spoofKey.empty()) {
407 GetHeader(pn, pfc, "ShibSpoofCheck:", var, 32, false);
408 if (!var.empty() && g_spoofKey == (char*)var)
413 log(SPDebug, "ISAPI filter running more than once");
415 ~ShibTargetIsapiF() { }
417 const char* getScheme() const {
418 return m_scheme.c_str();
420 const char* getHostname() const {
421 return m_hostname.c_str();
423 int getPort() const {
426 const char* getQueryString() const {
427 const char* uri = getRequestURI();
428 uri = (uri ? strchr(uri, '?') : NULL);
429 return uri ? (uri + 1) : NULL;
431 const char* getMethod() const {
432 if (m_method.empty()) {
434 GetServerVariable(m_pfc,"HTTP_METHOD",var,5,false);
438 return m_method.c_str();
440 string getContentType() const {
441 if (m_content_type.empty()) {
443 GetServerVariable(m_pfc,"HTTP_CONTENT_TYPE",var,32,false);
445 m_content_type = var;
447 return m_content_type;
449 string getRemoteAddr() const {
450 m_remote_addr = AbstractSPRequest::getRemoteAddr();
451 if (m_remote_addr.empty()) {
453 GetServerVariable(m_pfc,"REMOTE_ADDR",var,16,false);
457 return m_remote_addr;
459 void log(SPLogLevel level, const string& msg) {
460 AbstractSPRequest::log(level,msg);
462 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
464 string makeSafeHeader(const char* rawname) const {
466 for (; *rawname; ++rawname) {
467 if (isalnum(*rawname))
472 void clearHeader(const char* rawname, const char* cginame) {
473 if (g_checkSpoofing && m_firsttime) {
474 if (m_allhttp.empty())
475 GetServerVariable(m_pfc, "ALL_HTTP", m_allhttp, 4096);
476 string hdr = g_bSafeHeaderNames ? ("HTTP_" + makeSafeHeader(cginame + 5)) : (string(cginame) + ':');
477 if (strstr(m_allhttp, hdr.c_str()))
478 throw opensaml::SecurityPolicyException("Attempt to spoof header ($1) was detected.", params(1, hdr.c_str()));
480 if (g_bSafeHeaderNames) {
481 string hdr = makeSafeHeader(rawname);
482 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), const_cast<char*>(g_unsetHeaderValue.c_str()));
484 else if (!strcmp(rawname,"REMOTE_USER")) {
485 m_pn->SetHeader(m_pfc, "remote-user:", const_cast<char*>(g_unsetHeaderValue.c_str()));
486 m_pn->SetHeader(m_pfc, "remote_user:", const_cast<char*>(g_unsetHeaderValue.c_str()));
489 string hdr = string(rawname) + ':';
490 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), const_cast<char*>(g_unsetHeaderValue.c_str()));
493 void setHeader(const char* name, const char* value) {
494 string hdr = g_bSafeHeaderNames ? makeSafeHeader(name) : (string(name) + ':');
495 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), const_cast<char*>(value));
497 string getSecureHeader(const char* name) const {
498 string hdr = g_bSafeHeaderNames ? makeSafeHeader(name) : (string(name) + ':');
500 GetHeader(m_pn, m_pfc, const_cast<char*>(hdr.c_str()), buf, 256, false);
501 return string(buf.empty() ? "" : buf);
503 string getHeader(const char* name) const {
507 GetHeader(m_pn, m_pfc, const_cast<char*>(hdr.c_str()), buf, 256, false);
508 return string(buf.empty() ? "" : buf);
510 void setRemoteUser(const char* user) {
511 setHeader("remote-user", user);
513 m_pfc->pFilterContext = NULL;
514 else if (m_pfc->pFilterContext = m_pfc->AllocMem(m_pfc, sizeof(char) * (strlen(user) + 1), NULL))
515 strcpy(reinterpret_cast<char*>(m_pfc->pFilterContext), user);
517 string getRemoteUser() const {
518 return getSecureHeader("remote-user");
520 void setResponseHeader(const char* name, const char* value) {
523 m_headers.insert(make_pair(name,value));
525 m_headers.erase(name);
527 long sendResponse(istream& in, long status) {
528 string hdr = string("Connection: close\r\n");
529 for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
530 hdr += i->first + ": " + i->second + "\r\n";
532 const char* codestr="200 OK";
534 case XMLTOOLING_HTTP_STATUS_UNAUTHORIZED: codestr="401 Authorization Required"; break;
535 case XMLTOOLING_HTTP_STATUS_FORBIDDEN: codestr="403 Forbidden"; break;
536 case XMLTOOLING_HTTP_STATUS_NOTFOUND: codestr="404 Not Found"; break;
537 case XMLTOOLING_HTTP_STATUS_ERROR: codestr="500 Server Error"; break;
539 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, (void*)codestr, (DWORD)hdr.c_str(), 0);
543 DWORD resplen = in.gcount();
544 m_pfc->WriteClient(m_pfc, buf, &resplen, 0);
546 return SF_STATUS_REQ_FINISHED;
548 long sendRedirect(const char* url) {
549 // XXX: Don't support the httpRedirect option, yet.
550 string hdr=string("Location: ") + url + "\r\n"
551 "Content-Type: text/html\r\n"
552 "Content-Length: 40\r\n"
553 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
554 "Cache-Control: private,no-store,no-cache\r\n";
555 for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
556 hdr += i->first + ": " + i->second + "\r\n";
558 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, "302 Please Wait", (DWORD)hdr.c_str(), 0);
559 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
561 m_pfc->WriteClient(m_pfc, (LPVOID)redmsg, &resplen, 0);
562 return SF_STATUS_REQ_FINISHED;
564 long returnDecline() {
565 return SF_STATUS_REQ_NEXT_NOTIFICATION;
568 return SF_STATUS_REQ_NEXT_NOTIFICATION;
571 const vector<string>& getClientCertificates() const {
575 // The filter never processes the POST, so stub these methods.
576 long getContentLength() const { throw IOException("The request's Content-Length is not available to an ISAPI filter."); }
577 const char* getRequestBody() const { throw IOException("The request body is not available to an ISAPI filter."); }
580 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const char* msg)
582 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
583 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
584 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)ctype,0);
585 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Filter Error</TITLE></HEAD><BODY>"
586 "<H1>Shibboleth Filter Error</H1>";
587 DWORD resplen=strlen(xmsg);
588 pfc->WriteClient(pfc,(LPVOID)xmsg,&resplen,0);
590 pfc->WriteClient(pfc,(LPVOID)msg,&resplen,0);
591 static const char* xmsg2="</BODY></HTML>";
592 resplen=strlen(xmsg2);
593 pfc->WriteClient(pfc,(LPVOID)xmsg2,&resplen,0);
594 return SF_STATUS_REQ_FINISHED;
597 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
599 // Is this a log notification?
600 if (notificationType==SF_NOTIFY_LOG) {
601 if (pfc->pFilterContext)
602 ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=reinterpret_cast<char*>(pfc->pFilterContext);
603 return SF_STATUS_REQ_NEXT_NOTIFICATION;
606 PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
609 // Determine web site number. This can't really fail, I don't think.
611 GetServerVariable(pfc,"INSTANCE_ID",buf,10);
613 // Match site instance to host name, skip if no match.
614 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
615 if (map_i==g_Sites.end())
616 return SF_STATUS_REQ_NEXT_NOTIFICATION;
618 ostringstream threadid;
619 threadid << "[" << getpid() << "] isapi_shib" << '\0';
620 xmltooling::NDC ndc(threadid.str().c_str());
622 ShibTargetIsapiF stf(pfc, pn, map_i->second);
624 // "false" because we don't override the Shib settings
625 pair<bool,long> res = stf.getServiceProvider().doAuthentication(stf);
626 if (!g_spoofKey.empty())
627 pn->SetHeader(pfc, "ShibSpoofCheck:", const_cast<char*>(g_spoofKey.c_str()));
628 if (res.first) return res.second;
630 // "false" because we don't override the Shib settings
631 res = stf.getServiceProvider().doExport(stf);
632 if (res.first) return res.second;
634 res = stf.getServiceProvider().doAuthorization(stf);
635 if (res.first) return res.second;
637 return SF_STATUS_REQ_NEXT_NOTIFICATION;
640 return WriteClientError(pfc,"Out of Memory");
643 if (e==ERROR_NO_DATA)
644 return WriteClientError(pfc,"A required variable or header was empty.");
646 return WriteClientError(pfc,"Shibboleth Filter detected unexpected IIS error.");
648 catch (exception& e) {
649 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
650 return WriteClientError(pfc,"Shibboleth Filter caught an exception, check Event Log for details.");
653 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Shibboleth Filter threw an unknown exception.");
655 return WriteClientError(pfc,"Shibboleth Filter threw an unknown exception.");
659 return WriteClientError(pfc,"Shibboleth Filter reached unreachable code, save my walrus!");
663 /****************************************************************************/
666 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* msg)
668 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
669 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
670 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
671 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Error</TITLE></HEAD><BODY><H1>Shibboleth Error</H1>";
672 DWORD resplen=strlen(xmsg);
673 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg,&resplen,HSE_IO_SYNC);
675 lpECB->WriteClient(lpECB->ConnID,(LPVOID)msg,&resplen,HSE_IO_SYNC);
676 static const char* xmsg2="</BODY></HTML>";
677 resplen=strlen(xmsg2);
678 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg2,&resplen,HSE_IO_SYNC);
679 return HSE_STATUS_SUCCESS;
683 class ShibTargetIsapiE : public AbstractSPRequest
685 LPEXTENSION_CONTROL_BLOCK m_lpECB;
686 multimap<string,string> m_headers;
687 mutable vector<string> m_certs;
688 mutable string m_body;
689 mutable bool m_gotBody;
691 string m_scheme,m_hostname,m_uri;
692 mutable string m_remote_addr,m_remote_user;
695 ShibTargetIsapiE(LPEXTENSION_CONTROL_BLOCK lpECB, const site_t& site)
696 : AbstractSPRequest(SHIBSP_LOGCAT".ISAPI"), m_lpECB(lpECB), m_gotBody(false) {
698 GetServerVariable(lpECB,"HTTPS",ssl,5);
699 bool SSL=(ssl=="on" || ssl=="ON");
701 // Scheme may come from site def or be derived from IIS.
702 m_scheme=site.m_scheme;
703 if (m_scheme.empty() || !g_bNormalizeRequest)
704 m_scheme = SSL ? "https" : "http";
706 // URL path always come from IIS.
708 GetServerVariable(lpECB,"URL",url,255);
710 // Port may come from IIS or from site def.
712 if (!g_bNormalizeRequest || (SSL && site.m_sslport.empty()) || (!SSL && site.m_port.empty()))
713 GetServerVariable(lpECB,"SERVER_PORT",port,10);
715 strncpy(port,site.m_sslport.c_str(),10);
716 static_cast<char*>(port)[10]=0;
719 strncpy(port,site.m_port.c_str(),10);
720 static_cast<char*>(port)[10]=0;
725 GetServerVariable(lpECB, "SERVER_NAME", var, 32);
727 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
729 if (site.m_name!=m_hostname && site.m_aliases.find(m_hostname)==site.m_aliases.end())
730 m_hostname=site.m_name;
733 * IIS screws us over on PATH_INFO (the hits keep on coming). We need to figure out if
734 * the server is set up for proper PATH_INFO handling, or "IIS sucks rabid weasels mode",
735 * which is the default. No perfect way to tell, but we can take a good guess by checking
736 * whether the URL is a substring of the PATH_INFO:
738 * e.g. for /Shibboleth.sso/SAML/POST
740 * Bad mode (default):
741 * URL: /Shibboleth.sso
742 * PathInfo: /Shibboleth.sso/SAML/POST
745 * URL: /Shibboleth.sso
746 * PathInfo: /SAML/POST
751 // Clearly we're only in bad mode if path info exists at all.
752 if (lpECB->lpszPathInfo && *(lpECB->lpszPathInfo)) {
753 if (strstr(lpECB->lpszPathInfo,url))
754 // Pretty good chance we're in bad mode, unless the PathInfo repeats the path itself.
755 uri = lpECB->lpszPathInfo;
758 uri += lpECB->lpszPathInfo;
765 // For consistency with Apache, let's add the query string.
766 if (lpECB->lpszQueryString && *(lpECB->lpszQueryString)) {
768 uri += lpECB->lpszQueryString;
771 setRequestURI(uri.c_str());
773 ~ShibTargetIsapiE() { }
775 const char* getScheme() const {
776 return m_scheme.c_str();
778 const char* getHostname() const {
779 return m_hostname.c_str();
781 int getPort() const {
784 const char* getMethod() const {
785 return m_lpECB->lpszMethod;
787 string getContentType() const {
788 return m_lpECB->lpszContentType ? m_lpECB->lpszContentType : "";
790 long getContentLength() const {
791 return m_lpECB->cbTotalBytes;
793 string getRemoteUser() const {
794 if (m_remote_user.empty()) {
796 GetServerVariable(m_lpECB, "REMOTE_USER", var, 32, false);
800 return m_remote_user;
802 string getRemoteAddr() const {
803 m_remote_addr = AbstractSPRequest::getRemoteAddr();
804 if (m_remote_addr.empty()) {
806 GetServerVariable(m_lpECB, "REMOTE_ADDR", var, 16, false);
810 return m_remote_addr;
812 void log(SPLogLevel level, const string& msg) const {
813 AbstractSPRequest::log(level,msg);
815 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
817 string getHeader(const char* name) const {
819 for (; *name; ++name) {
823 hdr += toupper(*name);
826 GetServerVariable(m_lpECB, const_cast<char*>(hdr.c_str()), buf, 128, false);
827 return buf.empty() ? "" : buf;
829 void setResponseHeader(const char* name, const char* value) {
832 m_headers.insert(make_pair(name,value));
834 m_headers.erase(name);
836 const char* getQueryString() const {
837 return m_lpECB->lpszQueryString;
839 const char* getRequestBody() const {
841 return m_body.c_str();
842 if (m_lpECB->cbTotalBytes > 1024*1024) // 1MB?
843 throw opensaml::SecurityPolicyException("Size of request body exceeded 1M size limit.");
844 else if (m_lpECB->cbTotalBytes > m_lpECB->cbAvailable) {
846 DWORD datalen=m_lpECB->cbTotalBytes;
847 if (m_lpECB->cbAvailable > 0) {
848 m_body.assign(reinterpret_cast<char*>(m_lpECB->lpbData),m_lpECB->cbAvailable);
849 datalen-=m_lpECB->cbAvailable;
854 BOOL ret = m_lpECB->ReadClient(m_lpECB->ConnID, buf, &buflen);
856 throw IOException("Error reading request body from browser.");
858 throw IOException("Socket closed while reading request body from browser.");
859 m_body.append(buf, buflen);
863 else if (m_lpECB->cbAvailable) {
865 m_body.assign(reinterpret_cast<char*>(m_lpECB->lpbData),m_lpECB->cbAvailable);
867 return m_body.c_str();
869 long sendResponse(istream& in, long status) {
870 string hdr = string("Connection: close\r\n");
871 for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
872 hdr += i->first + ": " + i->second + "\r\n";
874 const char* codestr="200 OK";
876 case XMLTOOLING_HTTP_STATUS_UNAUTHORIZED: codestr="401 Authorization Required"; break;
877 case XMLTOOLING_HTTP_STATUS_FORBIDDEN: codestr="403 Forbidden"; break;
878 case XMLTOOLING_HTTP_STATUS_NOTFOUND: codestr="404 Not Found"; break;
879 case XMLTOOLING_HTTP_STATUS_ERROR: codestr="500 Server Error"; break;
881 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, (void*)codestr, 0, (LPDWORD)hdr.c_str());
885 DWORD resplen = in.gcount();
886 m_lpECB->WriteClient(m_lpECB->ConnID, buf, &resplen, HSE_IO_SYNC);
888 return HSE_STATUS_SUCCESS;
890 long sendRedirect(const char* url) {
891 string hdr=string("Location: ") + url + "\r\n"
892 "Content-Type: text/html\r\n"
893 "Content-Length: 40\r\n"
894 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
895 "Cache-Control: private,no-store,no-cache\r\n";
896 for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
897 hdr += i->first + ": " + i->second + "\r\n";
899 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, "302 Moved", 0, (LPDWORD)hdr.c_str());
900 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
902 m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)redmsg, &resplen, HSE_IO_SYNC);
903 return HSE_STATUS_SUCCESS;
905 // Decline happens in the POST processor if this isn't the shire url
906 // Note that it can also happen with HTAccess, but we don't support that, yet.
907 long returnDecline() {
908 return WriteClientError(
910 "ISAPI extension can only be invoked to process Shibboleth protocol requests."
911 "Make sure the mapped file extension doesn't match actual content."
915 return HSE_STATUS_SUCCESS;
918 const vector<string>& getClientCertificates() const {
919 if (m_certs.empty()) {
920 char CertificateBuf[8192];
921 CERT_CONTEXT_EX ccex;
922 ccex.cbAllocated = sizeof(CertificateBuf);
923 ccex.CertContext.pbCertEncoded = (BYTE*)CertificateBuf;
924 DWORD dwSize = sizeof(ccex);
926 if (m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_GET_CERT_INFO_EX, (LPVOID)&ccex, (LPDWORD)dwSize, NULL)) {
927 if (ccex.CertContext.cbCertEncoded) {
929 XMLByte* serialized = Base64::encode(reinterpret_cast<XMLByte*>(CertificateBuf), ccex.CertContext.cbCertEncoded, &outlen);
930 m_certs.push_back(reinterpret_cast<char*>(serialized));
931 #ifdef SHIBSP_XERCESC_HAS_XMLBYTE_RELEASE
932 XMLString::release(&serialized);
934 XMLString::release((char**)&serialized);
942 // Not used in the extension.
943 void clearHeader(const char* rawname, const char* cginame) { throw runtime_error("clearHeader not implemented"); }
944 void setHeader(const char* name, const char* value) { throw runtime_error("setHeader not implemented"); }
945 void setRemoteUser(const char* user) { throw runtime_error("setRemoteUser not implemented"); }
948 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
951 ostringstream threadid;
952 threadid << "[" << getpid() << "] isapi_shib_extension" << '\0';
953 xmltooling::NDC ndc(threadid.str().c_str());
955 // Determine web site number. This can't really fail, I don't think.
957 GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
959 // Match site instance to host name, skip if no match.
960 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
961 if (map_i==g_Sites.end())
962 return WriteClientError(lpECB, "Shibboleth Extension not configured for web site (check <ISAPI> mappings in configuration).");
964 ShibTargetIsapiE ste(lpECB, map_i->second);
965 pair<bool,long> res = ste.getServiceProvider().doHandler(ste);
966 if (res.first) return res.second;
968 return WriteClientError(lpECB, "Shibboleth Extension failed to process request");
972 return WriteClientError(lpECB,"Out of Memory");
975 if (e==ERROR_NO_DATA)
976 return WriteClientError(lpECB,"A required variable or header was empty.");
978 return WriteClientError(lpECB,"Server detected unexpected IIS error.");
980 catch (exception& e) {
981 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
982 return WriteClientError(lpECB,"Shibboleth Extension caught an exception, check Event Log for details.");
985 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Shibboleth Extension threw an unknown exception.");
987 return WriteClientError(lpECB,"Shibboleth Extension threw an unknown exception.");
991 // If we get here we've got an error.
992 return HSE_STATUS_ERROR;