2 * Copyright 2001-2007 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
29 #include <shibsp/AbstractSPRequest.h>
30 #include <shibsp/SPConfig.h>
31 #include <shibsp/ServiceProvider.h>
32 #include <xmltooling/unicode.h>
33 #include <xmltooling/XMLToolingConfig.h>
34 #include <xmltooling/util/NDC.h>
35 #include <xmltooling/util/XMLConstants.h>
36 #include <xmltooling/util/XMLHelper.h>
37 #include <xercesc/util/Base64.hpp>
38 #include <xercesc/util/XMLUniDefs.hpp>
50 using namespace shibsp;
51 using namespace xmltooling;
52 using namespace xercesc;
57 static const XMLCh path[] = UNICODE_LITERAL_4(p,a,t,h);
58 static const XMLCh validate[] = UNICODE_LITERAL_8(v,a,l,i,d,a,t,e);
59 static const XMLCh name[] = UNICODE_LITERAL_4(n,a,m,e);
60 static const XMLCh port[] = UNICODE_LITERAL_4(p,o,r,t);
61 static const XMLCh sslport[] = UNICODE_LITERAL_7(s,s,l,p,o,r,t);
62 static const XMLCh scheme[] = UNICODE_LITERAL_6(s,c,h,e,m,e);
63 static const XMLCh id[] = UNICODE_LITERAL_2(i,d);
64 static const XMLCh Alias[] = UNICODE_LITERAL_5(A,l,i,a,s);
65 static const XMLCh Site[] = UNICODE_LITERAL_4(S,i,t,e);
68 site_t(const DOMElement* e)
70 auto_ptr_char n(e->getAttributeNS(NULL,name));
71 auto_ptr_char s(e->getAttributeNS(NULL,scheme));
72 auto_ptr_char p(e->getAttributeNS(NULL,port));
73 auto_ptr_char p2(e->getAttributeNS(NULL,sslport));
74 if (n.get()) m_name=n.get();
75 if (s.get()) m_scheme=s.get();
76 if (p.get()) m_port=p.get();
77 if (p2.get()) m_sslport=p2.get();
78 e = XMLHelper::getFirstChildElement(e, Alias);
80 if (e->hasChildNodes()) {
81 auto_ptr_char alias(e->getFirstChild()->getNodeValue());
82 m_aliases.insert(alias.get());
84 e = XMLHelper::getNextSiblingElement(e, Alias);
87 string m_scheme,m_port,m_sslport,m_name;
88 set<string> m_aliases;
97 SPConfig* g_Config = NULL;
98 map<string,site_t> g_Sites;
99 bool g_bNormalizeRequest = true;
100 string g_unsetHeaderValue;
101 bool g_checkSpoofing = true;
102 bool g_catchAll = false;
103 vector<string> g_NoCerts;
107 LPCSTR lpUNCServerName,
113 LPCSTR messages[] = {message, NULL};
115 HANDLE hElog = RegisterEventSource(lpUNCServerName, "Shibboleth ISAPI Filter");
116 BOOL res = ReportEvent(hElog, wType, 0, dwEventID, lpUserSid, 1, 0, messages, NULL);
117 return (DeregisterEventSource(hElog) && res);
120 extern "C" __declspec(dllexport) BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID)
122 if (fdwReason==DLL_PROCESS_ATTACH)
127 extern "C" BOOL WINAPI GetExtensionVersion(HSE_VERSION_INFO* pVer)
133 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
134 "Extension mode startup not possible, is the DLL loaded as a filter?");
138 pVer->dwExtensionVersion=HSE_VERSION;
139 strncpy(pVer->lpszExtensionDesc,"Shibboleth ISAPI Extension",HSE_MAX_EXT_DLL_NAME_LEN-1);
143 extern "C" BOOL WINAPI TerminateExtension(DWORD)
145 return TRUE; // cleanup should happen when filter unloads
148 extern "C" BOOL WINAPI GetFilterVersion(PHTTP_FILTER_VERSION pVer)
153 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
154 "Reentrant filter initialization, ignoring...");
158 g_Config=&SPConfig::getConfig();
159 g_Config->setFeatures(
162 SPConfig::RequestMapping |
163 SPConfig::InProcess |
167 if (!g_Config->init()) {
169 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
170 "Filter startup failed during library initialization, check native log for help.");
175 if (!g_Config->instantiate(NULL, true))
176 throw runtime_error("unknown error");
178 catch (exception& ex) {
181 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, ex.what());
182 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
183 "Filter startup failed to load configuration, check native log for details.");
187 // Access implementation-specifics and site mappings.
188 ServiceProvider* sp=g_Config->getServiceProvider();
190 const PropertySet* props=sp->getPropertySet("InProcess");
192 pair<bool,const char*> unsetValue=props->getString("unsetHeaderValue");
193 if (unsetValue.first)
194 g_unsetHeaderValue = unsetValue.second;
195 pair<bool,bool> flag=props->getBool("checkSpoofing");
196 g_checkSpoofing = !flag.first || flag.second;
197 flag=props->getBool("catchAll");
198 g_catchAll = flag.first && flag.second;
200 props = props->getPropertySet("ISAPI");
202 flag = props->getBool("normalizeRequest");
203 g_bNormalizeRequest = !flag.first || flag.second;
204 const DOMElement* child = XMLHelper::getFirstChildElement(props->getElement(),Site);
206 auto_ptr_char id(child->getAttributeNS(NULL,id));
208 g_Sites.insert(pair<string,site_t>(id.get(),site_t(child)));
209 child=XMLHelper::getNextSiblingElement(child,Site);
214 pVer->dwFilterVersion=HTTP_FILTER_REVISION;
215 strncpy(pVer->lpszFilterDesc,"Shibboleth ISAPI Filter",SF_MAX_FILTER_DESC_LEN);
216 pVer->dwFlags=(SF_NOTIFY_ORDER_HIGH |
217 SF_NOTIFY_SECURE_PORT |
218 SF_NOTIFY_NONSECURE_PORT |
219 SF_NOTIFY_PREPROC_HEADERS |
221 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter initialized...");
225 extern "C" BOOL WINAPI TerminateFilter(DWORD)
230 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter shut down...");
234 /* Next up, some suck-free versions of various APIs.
236 You DON'T require people to guess the buffer size and THEN tell them the right size.
237 Returning an LPCSTR is apparently way beyond their ken. Not to mention the fact that
238 constant strings aren't typed as such, making it just that much harder. These versions
239 are now updated to use a special growable buffer object, modeled after the standard
240 string class. The standard string won't work because they left out the option to
241 pre-allocate a non-constant buffer.
247 dynabuf() { bufptr=NULL; buflen=0; }
248 dynabuf(size_t s) { bufptr=new char[buflen=s]; *bufptr=0; }
249 ~dynabuf() { delete[] bufptr; }
250 size_t length() const { return bufptr ? strlen(bufptr) : 0; }
251 size_t size() const { return buflen; }
252 bool empty() const { return length()==0; }
253 void reserve(size_t s, bool keep=false);
254 void erase() { if (bufptr) memset(bufptr,0,buflen); }
255 operator char*() { return bufptr; }
256 bool operator ==(const char* s) const;
257 bool operator !=(const char* s) const { return !(*this==s); }
263 void dynabuf::reserve(size_t s, bool keep)
270 p[buflen]=bufptr[buflen];
276 bool dynabuf::operator==(const char* s) const
278 if (buflen==NULL || s==NULL)
279 return (buflen==NULL && s==NULL);
281 return strcmp(bufptr,s)==0;
284 void GetServerVariable(PHTTP_FILTER_CONTEXT pfc, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
290 while (!pfc->GetServerVariable(pfc,lpszVariable,s,&size)) {
291 // Grumble. Check the error.
292 DWORD e=GetLastError();
293 if (e==ERROR_INSUFFICIENT_BUFFER)
298 if (bRequired && s.empty())
302 void GetServerVariable(LPEXTENSION_CONTROL_BLOCK lpECB, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
308 while (!lpECB->GetServerVariable(lpECB->ConnID,lpszVariable,s,&size)) {
309 // Grumble. Check the error.
310 DWORD e=GetLastError();
311 if (e==ERROR_INSUFFICIENT_BUFFER)
316 if (bRequired && s.empty())
320 void GetHeader(PHTTP_FILTER_PREPROC_HEADERS pn, PHTTP_FILTER_CONTEXT pfc,
321 LPSTR lpszName, dynabuf& s, DWORD size=80, bool bRequired=true)
327 while (!pn->GetHeader(pfc,lpszName,s,&size)) {
328 // Grumble. Check the error.
329 DWORD e=GetLastError();
330 if (e==ERROR_INSUFFICIENT_BUFFER)
335 if (bRequired && s.empty())
339 /****************************************************************************/
342 class ShibTargetIsapiF : public AbstractSPRequest
344 PHTTP_FILTER_CONTEXT m_pfc;
345 PHTTP_FILTER_PREPROC_HEADERS m_pn;
346 multimap<string,string> m_headers;
348 string m_scheme,m_hostname;
349 mutable string m_remote_addr,m_content_type,m_method;
353 ShibTargetIsapiF(PHTTP_FILTER_CONTEXT pfc, PHTTP_FILTER_PREPROC_HEADERS pn, const site_t& site)
354 : AbstractSPRequest(SHIBSP_LOGCAT".ISAPI"), m_pfc(pfc), m_pn(pn), m_allhttp(4096) {
356 // URL path always come from IIS.
358 GetHeader(pn,pfc,"url",var,256,false);
361 // Port may come from IIS or from site def.
362 if (!g_bNormalizeRequest || (pfc->fIsSecurePort && site.m_sslport.empty()) || (!pfc->fIsSecurePort && site.m_port.empty())) {
363 GetServerVariable(pfc,"SERVER_PORT",var,10);
366 else if (pfc->fIsSecurePort) {
367 m_port = atoi(site.m_sslport.c_str());
370 m_port = atoi(site.m_port.c_str());
373 // Scheme may come from site def or be derived from IIS.
374 m_scheme=site.m_scheme;
375 if (m_scheme.empty() || !g_bNormalizeRequest)
376 m_scheme=pfc->fIsSecurePort ? "https" : "http";
378 GetServerVariable(pfc,"SERVER_NAME",var,32);
380 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
382 if (site.m_name!=m_hostname && site.m_aliases.find(m_hostname)==site.m_aliases.end())
383 m_hostname=site.m_name;
385 if (!pfc->pFilterContext) {
386 pfc->pFilterContext = pfc->AllocMem(pfc, sizeof(context_t), NULL);
387 if (static_cast<context_t*>(pfc->pFilterContext)) {
388 static_cast<context_t*>(pfc->pFilterContext)->m_user = NULL;
389 static_cast<context_t*>(pfc->pFilterContext)->m_checked = false;
393 ~ShibTargetIsapiF() { }
395 const char* getScheme() const {
396 return m_scheme.c_str();
398 const char* getHostname() const {
399 return m_hostname.c_str();
401 int getPort() const {
404 const char* getMethod() const {
405 if (m_method.empty()) {
407 GetServerVariable(m_pfc,"REQUEST_METHOD",var,5,false);
411 return m_method.c_str();
413 string getContentType() const {
414 if (m_content_type.empty()) {
416 GetServerVariable(m_pfc,"CONTENT_TYPE",var,32,false);
418 m_content_type = var;
420 return m_content_type;
422 long getContentLength() const {
425 string getRemoteAddr() const {
426 m_remote_addr = AbstractSPRequest::getRemoteAddr();
427 if (m_remote_addr.empty()) {
429 GetServerVariable(m_pfc,"REMOTE_ADDR",var,16,false);
433 return m_remote_addr;
435 void log(SPLogLevel level, const string& msg) {
436 AbstractSPRequest::log(level,msg);
437 if (level >= SPError)
438 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
440 void clearHeader(const char* rawname, const char* cginame) {
441 if (g_checkSpoofing && m_pfc->pFilterContext && !static_cast<context_t*>(m_pfc->pFilterContext)->m_checked) {
442 if (m_allhttp.empty())
443 GetServerVariable(m_pfc,"ALL_HTTP",m_allhttp,4096);
444 if (strstr(m_allhttp, cginame))
445 throw opensaml::SecurityPolicyException("Attempt to spoof header ($1) was detected.", params(1, rawname));
447 string hdr(!strcmp(rawname,"REMOTE_USER") ? "remote-user" : rawname);
449 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), const_cast<char*>(g_unsetHeaderValue.c_str()));
451 void setHeader(const char* name, const char* value) {
454 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), const_cast<char*>(value));
456 string getHeader(const char* name) const {
460 GetHeader(m_pn, m_pfc, const_cast<char*>(hdr.c_str()), buf, 256, false);
463 void setRemoteUser(const char* user) {
464 setHeader("remote-user", user);
465 if (m_pfc->pFilterContext) {
467 static_cast<context_t*>(m_pfc->pFilterContext)->m_user = NULL;
468 else if (static_cast<context_t*>(m_pfc->pFilterContext)->m_user = (char*)m_pfc->AllocMem(m_pfc, sizeof(char) * (strlen(user) + 1), NULL))
469 strcpy(static_cast<context_t*>(m_pfc->pFilterContext)->m_user, user);
472 string getRemoteUser() const {
473 return getHeader("remote-user");
475 void setResponseHeader(const char* name, const char* value) {
478 m_headers.insert(make_pair(name,value));
480 m_headers.erase(name);
482 long sendResponse(istream& in, long status) {
483 string hdr = string("Connection: close\r\n");
484 for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
485 hdr += i->first + ": " + i->second + "\r\n";
487 const char* codestr="200 OK";
489 case XMLTOOLING_HTTP_STATUS_UNAUTHORIZED: codestr="401 Authorization Required"; break;
490 case XMLTOOLING_HTTP_STATUS_FORBIDDEN: codestr="403 Forbidden"; break;
491 case XMLTOOLING_HTTP_STATUS_NOTFOUND: codestr="404 Not Found"; break;
492 case XMLTOOLING_HTTP_STATUS_ERROR: codestr="500 Server Error"; break;
494 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, (void*)codestr, (DWORD)hdr.c_str(), 0);
498 DWORD resplen = in.gcount();
499 m_pfc->WriteClient(m_pfc, buf, &resplen, 0);
501 return SF_STATUS_REQ_FINISHED;
503 long sendRedirect(const char* url) {
504 // XXX: Don't support the httpRedirect option, yet.
505 string hdr=string("Location: ") + url + "\r\n"
506 "Content-Type: text/html\r\n"
507 "Content-Length: 40\r\n"
508 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
509 "Cache-Control: private,no-store,no-cache\r\n";
510 for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
511 hdr += i->first + ": " + i->second + "\r\n";
513 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, "302 Please Wait", (DWORD)hdr.c_str(), 0);
514 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
516 m_pfc->WriteClient(m_pfc, (LPVOID)redmsg, &resplen, 0);
517 return SF_STATUS_REQ_FINISHED;
519 long returnDecline() {
520 return SF_STATUS_REQ_NEXT_NOTIFICATION;
523 return SF_STATUS_REQ_NEXT_NOTIFICATION;
526 const vector<string>& getClientCertificates() const {
530 // The filter never processes the POST, so stub these methods.
531 const char* getQueryString() const { throw IOException("getQueryString not implemented"); }
532 const char* getRequestBody() const { throw IOException("getRequestBody not implemented"); }
535 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const char* msg)
537 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
538 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
539 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)ctype,0);
540 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Filter Error</TITLE></HEAD><BODY>"
541 "<H1>Shibboleth Filter Error</H1>";
542 DWORD resplen=strlen(xmsg);
543 pfc->WriteClient(pfc,(LPVOID)xmsg,&resplen,0);
545 pfc->WriteClient(pfc,(LPVOID)msg,&resplen,0);
546 static const char* xmsg2="</BODY></HTML>";
547 resplen=strlen(xmsg2);
548 pfc->WriteClient(pfc,(LPVOID)xmsg2,&resplen,0);
549 return SF_STATUS_REQ_FINISHED;
552 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
554 // Is this a log notification?
555 if (notificationType==SF_NOTIFY_LOG) {
556 if (pfc->pFilterContext && static_cast<context_t*>(pfc->pFilterContext)->m_user)
557 ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=static_cast<context_t*>(pfc->pFilterContext)->m_user;
558 return SF_STATUS_REQ_NEXT_NOTIFICATION;
561 PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
564 // Determine web site number. This can't really fail, I don't think.
566 GetServerVariable(pfc,"INSTANCE_ID",buf,10);
568 // Match site instance to host name, skip if no match.
569 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
570 if (map_i==g_Sites.end())
571 return SF_STATUS_REQ_NEXT_NOTIFICATION;
573 ostringstream threadid;
574 threadid << "[" << getpid() << "] isapi_shib" << '\0';
575 xmltooling::NDC ndc(threadid.str().c_str());
577 ShibTargetIsapiF stf(pfc, pn, map_i->second);
579 // "false" because we don't override the Shib settings
580 pair<bool,long> res = stf.getServiceProvider().doAuthentication(stf);
581 if (pfc->pFilterContext)
582 static_cast<context_t*>(pfc->pFilterContext)->m_checked = true;
583 if (res.first) return res.second;
585 // "false" because we don't override the Shib settings
586 res = stf.getServiceProvider().doExport(stf);
587 if (res.first) return res.second;
589 res = stf.getServiceProvider().doAuthorization(stf);
590 if (res.first) return res.second;
592 return SF_STATUS_REQ_NEXT_NOTIFICATION;
595 return WriteClientError(pfc,"Out of Memory");
598 if (e==ERROR_NO_DATA)
599 return WriteClientError(pfc,"A required variable or header was empty.");
601 return WriteClientError(pfc,"Shibboleth Filter detected unexpected IIS error.");
603 catch (exception& e) {
604 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
605 return WriteClientError(pfc,"Shibboleth Filter caught an exception, check Event Log for details.");
608 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Shibboleth Filter threw an unknown exception.");
610 return WriteClientError(pfc,"Shibboleth Filter threw an unknown exception.");
614 return WriteClientError(pfc,"Shibboleth Filter reached unreachable code, save my walrus!");
618 /****************************************************************************/
621 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* msg)
623 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
624 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
625 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
626 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Error</TITLE></HEAD><BODY><H1>Shibboleth Error</H1>";
627 DWORD resplen=strlen(xmsg);
628 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg,&resplen,HSE_IO_SYNC);
630 lpECB->WriteClient(lpECB->ConnID,(LPVOID)msg,&resplen,HSE_IO_SYNC);
631 static const char* xmsg2="</BODY></HTML>";
632 resplen=strlen(xmsg2);
633 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg2,&resplen,HSE_IO_SYNC);
634 return HSE_STATUS_SUCCESS;
638 class ShibTargetIsapiE : public AbstractSPRequest
640 LPEXTENSION_CONTROL_BLOCK m_lpECB;
641 multimap<string,string> m_headers;
642 mutable vector<string> m_certs;
643 mutable string m_body;
644 mutable bool m_gotBody;
646 string m_scheme,m_hostname,m_uri;
647 mutable string m_remote_addr,m_remote_user;
650 ShibTargetIsapiE(LPEXTENSION_CONTROL_BLOCK lpECB, const site_t& site)
651 : AbstractSPRequest(SHIBSP_LOGCAT".ISAPI"), m_lpECB(lpECB), m_gotBody(false) {
653 GetServerVariable(lpECB,"HTTPS",ssl,5);
654 bool SSL=(ssl=="on" || ssl=="ON");
656 // Scheme may come from site def or be derived from IIS.
657 m_scheme=site.m_scheme;
658 if (m_scheme.empty() || !g_bNormalizeRequest)
659 m_scheme = SSL ? "https" : "http";
661 // URL path always come from IIS.
663 GetServerVariable(lpECB,"URL",url,255);
665 // Port may come from IIS or from site def.
667 if (!g_bNormalizeRequest || (SSL && site.m_sslport.empty()) || (!SSL && site.m_port.empty()))
668 GetServerVariable(lpECB,"SERVER_PORT",port,10);
670 strncpy(port,site.m_sslport.c_str(),10);
671 static_cast<char*>(port)[10]=0;
674 strncpy(port,site.m_port.c_str(),10);
675 static_cast<char*>(port)[10]=0;
680 GetServerVariable(lpECB, "SERVER_NAME", var, 32);
682 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
684 if (site.m_name!=m_hostname && site.m_aliases.find(m_hostname)==site.m_aliases.end())
685 m_hostname=site.m_name;
688 * IIS screws us over on PATH_INFO (the hits keep on coming). We need to figure out if
689 * the server is set up for proper PATH_INFO handling, or "IIS sucks rabid weasels mode",
690 * which is the default. No perfect way to tell, but we can take a good guess by checking
691 * whether the URL is a substring of the PATH_INFO:
693 * e.g. for /Shibboleth.sso/SAML/POST
695 * Bad mode (default):
696 * URL: /Shibboleth.sso
697 * PathInfo: /Shibboleth.sso/SAML/POST
700 * URL: /Shibboleth.sso
701 * PathInfo: /SAML/POST
706 // Clearly we're only in bad mode if path info exists at all.
707 if (lpECB->lpszPathInfo && *(lpECB->lpszPathInfo)) {
708 if (strstr(lpECB->lpszPathInfo,url))
709 // Pretty good chance we're in bad mode, unless the PathInfo repeats the path itself.
710 uri = lpECB->lpszPathInfo;
713 uri += lpECB->lpszPathInfo;
720 // For consistency with Apache, let's add the query string.
721 if (lpECB->lpszQueryString && *(lpECB->lpszQueryString)) {
723 uri += lpECB->lpszQueryString;
726 setRequestURI(uri.c_str());
728 ~ShibTargetIsapiE() { }
730 const char* getScheme() const {
731 return m_scheme.c_str();
733 const char* getHostname() const {
734 return m_hostname.c_str();
736 int getPort() const {
739 const char* getMethod() const {
740 return m_lpECB->lpszMethod;
742 string getContentType() const {
743 return m_lpECB->lpszContentType ? m_lpECB->lpszContentType : "";
745 long getContentLength() const {
746 return m_lpECB->cbTotalBytes;
748 string getRemoteUser() const {
749 if (m_remote_user.empty()) {
751 GetServerVariable(m_lpECB, "REMOTE_USER", var, 32, false);
755 return m_remote_user;
757 string getRemoteAddr() const {
758 m_remote_addr = AbstractSPRequest::getRemoteAddr();
759 if (m_remote_addr.empty()) {
761 GetServerVariable(m_lpECB, "REMOTE_ADDR", var, 16, false);
765 return m_remote_addr;
767 void log(SPLogLevel level, const string& msg) const {
768 AbstractSPRequest::log(level,msg);
769 if (level >= SPError)
770 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
772 string getHeader(const char* name) const {
774 for (; *name; ++name) {
778 hdr += toupper(*name);
781 GetServerVariable(m_lpECB, const_cast<char*>(hdr.c_str()), buf, 128, false);
782 return buf.empty() ? "" : buf;
784 void setResponseHeader(const char* name, const char* value) {
787 m_headers.insert(make_pair(name,value));
789 m_headers.erase(name);
791 const char* getQueryString() const {
792 return m_lpECB->lpszQueryString;
794 const char* getRequestBody() const {
796 return m_body.c_str();
797 if (m_lpECB->cbTotalBytes > 1024*1024) // 1MB?
798 throw opensaml::SecurityPolicyException("Size of request body exceeded 1M size limit.");
799 else if (m_lpECB->cbTotalBytes > m_lpECB->cbAvailable) {
802 DWORD datalen=m_lpECB->cbTotalBytes;
805 BOOL ret = m_lpECB->ReadClient(m_lpECB->ConnID, buf, &buflen);
807 throw IOException("Error reading request body from browser.");
808 m_body.append(buf, buflen);
812 else if (m_lpECB->cbAvailable) {
814 m_body.assign(reinterpret_cast<char*>(m_lpECB->lpbData),m_lpECB->cbAvailable);
816 return m_body.c_str();
818 long sendResponse(istream& in, long status) {
819 string hdr = string("Connection: close\r\n");
820 for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
821 hdr += i->first + ": " + i->second + "\r\n";
823 const char* codestr="200 OK";
825 case XMLTOOLING_HTTP_STATUS_UNAUTHORIZED: codestr="401 Authorization Required"; break;
826 case XMLTOOLING_HTTP_STATUS_FORBIDDEN: codestr="403 Forbidden"; break;
827 case XMLTOOLING_HTTP_STATUS_NOTFOUND: codestr="404 Not Found"; break;
828 case XMLTOOLING_HTTP_STATUS_ERROR: codestr="500 Server Error"; break;
830 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, (void*)codestr, 0, (LPDWORD)hdr.c_str());
834 DWORD resplen = in.gcount();
835 m_lpECB->WriteClient(m_lpECB->ConnID, buf, &resplen, HSE_IO_SYNC);
837 return HSE_STATUS_SUCCESS;
839 long sendRedirect(const char* url) {
840 string hdr=string("Location: ") + url + "\r\n"
841 "Content-Type: text/html\r\n"
842 "Content-Length: 40\r\n"
843 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
844 "Cache-Control: private,no-store,no-cache\r\n";
845 for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
846 hdr += i->first + ": " + i->second + "\r\n";
848 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, "302 Moved", 0, (LPDWORD)hdr.c_str());
849 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
851 m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)redmsg, &resplen, HSE_IO_SYNC);
852 return HSE_STATUS_SUCCESS;
854 // Decline happens in the POST processor if this isn't the shire url
855 // Note that it can also happen with HTAccess, but we don't support that, yet.
856 long returnDecline() {
857 return WriteClientError(
859 "ISAPI extension can only be invoked to process Shibboleth protocol requests."
860 "Make sure the mapped file extension doesn't match actual content."
864 return HSE_STATUS_SUCCESS;
867 const vector<string>& getClientCertificates() const {
868 if (m_certs.empty()) {
869 char CertificateBuf[8192];
870 CERT_CONTEXT_EX ccex;
871 ccex.cbAllocated = sizeof(CertificateBuf);
872 ccex.CertContext.pbCertEncoded = (BYTE*)CertificateBuf;
873 DWORD dwSize = sizeof(ccex);
875 if (m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_GET_CERT_INFO_EX, (LPVOID)&ccex, (LPDWORD)dwSize, NULL)) {
876 if (ccex.CertContext.cbCertEncoded) {
878 XMLByte* serialized = Base64::encode(reinterpret_cast<XMLByte*>(CertificateBuf), ccex.CertContext.cbCertEncoded, &outlen);
879 m_certs.push_back(reinterpret_cast<char*>(serialized));
880 XMLString::release(&serialized);
887 // Not used in the extension.
888 void clearHeader(const char* rawname, const char* cginame) { throw runtime_error("clearHeader not implemented"); }
889 void setHeader(const char* name, const char* value) { throw runtime_error("setHeader not implemented"); }
890 void setRemoteUser(const char* user) { throw runtime_error("setRemoteUser not implemented"); }
893 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
896 ostringstream threadid;
897 threadid << "[" << getpid() << "] isapi_shib_extension" << '\0';
898 xmltooling::NDC ndc(threadid.str().c_str());
900 // Determine web site number. This can't really fail, I don't think.
902 GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
904 // Match site instance to host name, skip if no match.
905 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
906 if (map_i==g_Sites.end())
907 return WriteClientError(lpECB, "Shibboleth Extension not configured for web site (check <ISAPI> mappings in configuration).");
909 ShibTargetIsapiE ste(lpECB, map_i->second);
910 pair<bool,long> res = ste.getServiceProvider().doHandler(ste);
911 if (res.first) return res.second;
913 return WriteClientError(lpECB, "Shibboleth Extension failed to process request");
917 return WriteClientError(lpECB,"Out of Memory");
920 if (e==ERROR_NO_DATA)
921 return WriteClientError(lpECB,"A required variable or header was empty.");
923 return WriteClientError(lpECB,"Server detected unexpected IIS error.");
925 catch (exception& e) {
926 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
927 return WriteClientError(lpECB,"Shibboleth Extension caught an exception, check Event Log for details.");
930 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Shibboleth Extension threw an unknown exception.");
932 return WriteClientError(lpECB,"Shibboleth Extension threw an unknown exception.");
936 // If we get here we've got an error.
937 return HSE_STATUS_ERROR;