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>
49 using namespace shibsp;
50 using namespace xmltooling;
51 using namespace xercesc;
56 static const XMLCh path[] = UNICODE_LITERAL_4(p,a,t,h);
57 static const XMLCh validate[] = UNICODE_LITERAL_8(v,a,l,i,d,a,t,e);
58 static const XMLCh name[] = UNICODE_LITERAL_4(n,a,m,e);
59 static const XMLCh port[] = UNICODE_LITERAL_4(p,o,r,t);
60 static const XMLCh sslport[] = UNICODE_LITERAL_7(s,s,l,p,o,r,t);
61 static const XMLCh scheme[] = UNICODE_LITERAL_6(s,c,h,e,m,e);
62 static const XMLCh id[] = UNICODE_LITERAL_2(i,d);
63 static const XMLCh Implementation[] = UNICODE_LITERAL_14(I,m,p,l,e,m,e,n,t,a,t,i,o,n);
64 static const XMLCh ISAPI[] = UNICODE_LITERAL_5(I,S,A,P,I);
65 static const XMLCh Alias[] = UNICODE_LITERAL_5(A,l,i,a,s);
66 static const XMLCh normalizeRequest[] = UNICODE_LITERAL_16(n,o,r,m,a,l,i,z,e,R,e,q,u,e,s,t);
67 static const XMLCh Site[] = UNICODE_LITERAL_4(S,i,t,e);
70 site_t(const DOMElement* e)
72 auto_ptr_char n(e->getAttributeNS(NULL,name));
73 auto_ptr_char s(e->getAttributeNS(NULL,scheme));
74 auto_ptr_char p(e->getAttributeNS(NULL,port));
75 auto_ptr_char p2(e->getAttributeNS(NULL,sslport));
76 if (n.get()) m_name=n.get();
77 if (s.get()) m_scheme=s.get();
78 if (p.get()) m_port=p.get();
79 if (p2.get()) m_sslport=p2.get();
80 e = XMLHelper::getFirstChildElement(e, Alias);
82 if (e->hasChildNodes()) {
83 auto_ptr_char alias(e->getFirstChild()->getNodeValue());
84 m_aliases.insert(alias.get());
86 e = XMLHelper::getNextSiblingElement(e, Alias);
89 string m_scheme,m_port,m_sslport,m_name;
90 set<string> m_aliases;
94 SPConfig* g_Config = NULL;
95 map<string,site_t> g_Sites;
96 bool g_bNormalizeRequest = true;
97 string g_unsetHeaderValue;
98 bool g_checkSpoofing = true;
99 bool g_catchAll = 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 LPCSTR schemadir=getenv("SHIBSP_SCHEMAS");
157 schemadir=SHIBSP_SCHEMAS;
158 LPCSTR config=getenv("SHIBSP_CONFIG");
160 config=SHIBSP_CONFIG;
161 g_Config=&SPConfig::getConfig();
162 g_Config->setFeatures(
165 SPConfig::RequestMapping |
166 SPConfig::InProcess |
170 if (!g_Config->init(schemadir)) {
172 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
173 "Filter startup failed during library initialization, check native log for help.");
178 DOMDocument* dummydoc=XMLToolingConfig::getConfig().getParser().newDocument();
179 XercesJanitor<DOMDocument> docjanitor(dummydoc);
180 DOMElement* dummy = dummydoc->createElementNS(NULL,path);
181 auto_ptr_XMLCh src(config);
182 dummy->setAttributeNS(NULL,path,src.get());
183 dummy->setAttributeNS(NULL,validate,xmlconstants::XML_ONE);
185 g_Config->setServiceProvider(g_Config->ServiceProviderManager.newPlugin(XML_SERVICE_PROVIDER,dummy));
186 g_Config->getServiceProvider()->init();
188 catch (exception& ex) {
191 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, ex.what());
192 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
193 "Filter startup failed to load configuration, check native log for details.");
197 // Access the implementation-specifics for site mappings.
198 ServiceProvider* sp=g_Config->getServiceProvider();
200 const PropertySet* props=sp->getPropertySet("InProcess");
202 pair<bool,const char*> unsetValue=props->getString("unsetHeaderValue");
203 if (unsetValue.first)
204 g_unsetHeaderValue = unsetValue.second;
205 pair<bool,bool> flag=props->getBool("checkSpoofing");
206 g_checkSpoofing = !flag.first || flag.second;
207 flag=props->getBool("catchAll");
208 g_catchAll = flag.first && flag.second;
210 const DOMElement* impl=XMLHelper::getFirstChildElement(props->getElement(),Implementation);
211 if (impl && (impl=XMLHelper::getFirstChildElement(impl,ISAPI))) {
212 const XMLCh* flag=impl->getAttributeNS(NULL,normalizeRequest);
213 g_bNormalizeRequest=(!flag || !*flag || *flag==chDigit_1 || *flag==chLatin_t);
214 impl=XMLHelper::getFirstChildElement(impl,Site);
216 auto_ptr_char id(impl->getAttributeNS(NULL,id));
218 g_Sites.insert(pair<string,site_t>(id.get(),site_t(impl)));
219 impl=XMLHelper::getNextSiblingElement(impl,Site);
224 pVer->dwFilterVersion=HTTP_FILTER_REVISION;
225 strncpy(pVer->lpszFilterDesc,"Shibboleth ISAPI Filter",SF_MAX_FILTER_DESC_LEN);
226 pVer->dwFlags=(SF_NOTIFY_ORDER_HIGH |
227 SF_NOTIFY_SECURE_PORT |
228 SF_NOTIFY_NONSECURE_PORT |
229 SF_NOTIFY_PREPROC_HEADERS |
231 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter initialized...");
235 extern "C" BOOL WINAPI TerminateFilter(DWORD)
240 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter shut down...");
244 /* Next up, some suck-free versions of various APIs.
246 You DON'T require people to guess the buffer size and THEN tell them the right size.
247 Returning an LPCSTR is apparently way beyond their ken. Not to mention the fact that
248 constant strings aren't typed as such, making it just that much harder. These versions
249 are now updated to use a special growable buffer object, modeled after the standard
250 string class. The standard string won't work because they left out the option to
251 pre-allocate a non-constant buffer.
257 dynabuf() { bufptr=NULL; buflen=0; }
258 dynabuf(size_t s) { bufptr=new char[buflen=s]; *bufptr=0; }
259 ~dynabuf() { delete[] bufptr; }
260 size_t length() const { return bufptr ? strlen(bufptr) : 0; }
261 size_t size() const { return buflen; }
262 bool empty() const { return length()==0; }
263 void reserve(size_t s, bool keep=false);
264 void erase() { if (bufptr) memset(bufptr,0,buflen); }
265 operator char*() { return bufptr; }
266 bool operator ==(const char* s) const;
267 bool operator !=(const char* s) const { return !(*this==s); }
273 void dynabuf::reserve(size_t s, bool keep)
280 p[buflen]=bufptr[buflen];
286 bool dynabuf::operator==(const char* s) const
288 if (buflen==NULL || s==NULL)
289 return (buflen==NULL && s==NULL);
291 return strcmp(bufptr,s)==0;
294 void GetServerVariable(PHTTP_FILTER_CONTEXT pfc, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
300 while (!pfc->GetServerVariable(pfc,lpszVariable,s,&size)) {
301 // Grumble. Check the error.
302 DWORD e=GetLastError();
303 if (e==ERROR_INSUFFICIENT_BUFFER)
308 if (bRequired && s.empty())
312 void GetServerVariable(LPEXTENSION_CONTROL_BLOCK lpECB, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
318 while (!lpECB->GetServerVariable(lpECB->ConnID,lpszVariable,s,&size)) {
319 // Grumble. Check the error.
320 DWORD e=GetLastError();
321 if (e==ERROR_INSUFFICIENT_BUFFER)
326 if (bRequired && s.empty())
330 void GetHeader(PHTTP_FILTER_PREPROC_HEADERS pn, PHTTP_FILTER_CONTEXT pfc,
331 LPSTR lpszName, dynabuf& s, DWORD size=80, bool bRequired=true)
337 while (!pn->GetHeader(pfc,lpszName,s,&size)) {
338 // Grumble. Check the error.
339 DWORD e=GetLastError();
340 if (e==ERROR_INSUFFICIENT_BUFFER)
345 if (bRequired && s.empty())
349 /****************************************************************************/
352 class ShibTargetIsapiF : public AbstractSPRequest
354 PHTTP_FILTER_CONTEXT m_pfc;
355 PHTTP_FILTER_PREPROC_HEADERS m_pn;
356 multimap<string,string> m_headers;
358 string m_scheme,m_hostname;
359 mutable string m_remote_addr,m_content_type,m_method;
363 ShibTargetIsapiF(PHTTP_FILTER_CONTEXT pfc, PHTTP_FILTER_PREPROC_HEADERS pn, const site_t& site)
364 : m_pfc(pfc), m_pn(pn), m_allhttp(4096) {
366 // URL path always come from IIS.
368 GetHeader(pn,pfc,"url",var,256,false);
371 // Port may come from IIS or from site def.
372 if (!g_bNormalizeRequest || (pfc->fIsSecurePort && site.m_sslport.empty()) || (!pfc->fIsSecurePort && site.m_port.empty())) {
373 GetServerVariable(pfc,"SERVER_PORT",var,10);
376 else if (pfc->fIsSecurePort) {
377 m_port = atoi(site.m_sslport.c_str());
380 m_port = atoi(site.m_port.c_str());
383 // Scheme may come from site def or be derived from IIS.
384 m_scheme=site.m_scheme;
385 if (m_scheme.empty() || !g_bNormalizeRequest)
386 m_scheme=pfc->fIsSecurePort ? "https" : "http";
388 GetServerVariable(pfc,"SERVER_NAME",var,32);
390 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
392 if (site.m_name!=m_hostname && site.m_aliases.find(m_hostname)==site.m_aliases.end())
393 m_hostname=site.m_name;
395 ~ShibTargetIsapiF() { }
397 const char* getScheme() const {
398 return m_scheme.c_str();
400 const char* getHostname() const {
401 return m_hostname.c_str();
403 int getPort() const {
406 const char* getMethod() const {
407 if (m_method.empty()) {
409 GetServerVariable(m_pfc,"REQUEST_METHOD",var,5,false);
413 return m_method.c_str();
415 string getContentType() const {
416 if (m_content_type.empty()) {
418 GetServerVariable(m_pfc,"CONTENT_TYPE",var,32,false);
420 m_content_type = var;
422 return m_content_type;
424 long getContentLength() const {
427 string getRemoteAddr() const {
428 if (m_remote_addr.empty()) {
430 GetServerVariable(m_pfc,"REMOTE_ADDR",var,16,false);
434 return m_remote_addr;
436 void log(SPLogLevel level, const string& msg) {
437 AbstractSPRequest::log(level,msg);
438 if (level >= SPError)
439 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
441 void clearHeader(const char* rawname, const char* cginame) {
442 if (g_checkSpoofing) {
443 if (m_allhttp.empty())
444 GetServerVariable(m_pfc,"ALL_HTTP",m_allhttp,4096);
445 if (strstr(m_allhttp, cginame))
446 throw opensaml::SecurityPolicyException("Attempt to spoof header ($1) was detected.", params(1, rawname));
448 string hdr(!strcmp(rawname,"REMOTE_USER") ? "remote-user" : rawname);
450 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), const_cast<char*>(g_unsetHeaderValue.c_str()));
452 void setHeader(const char* name, const char* value) {
455 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), const_cast<char*>(value));
457 string getHeader(const char* name) const {
461 GetHeader(m_pn, m_pfc, const_cast<char*>(hdr.c_str()), buf, 256, false);
464 void setRemoteUser(const char* user) {
465 setHeader("remote-user", user);
467 string getRemoteUser() const {
468 return getHeader("remote-user");
470 void setResponseHeader(const char* name, const char* value) {
473 m_headers.insert(make_pair(name,value));
475 m_headers.erase(name);
477 long sendResponse(istream& in, long status) {
478 string hdr = string("Connection: close\r\n");
479 for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
480 hdr += i->first + ": " + i->second + "\r\n";
482 const char* codestr="200 OK";
484 case XMLTOOLING_HTTP_STATUS_UNAUTHORIZED: codestr="401 Authorization Required"; break;
485 case XMLTOOLING_HTTP_STATUS_FORBIDDEN: codestr="403 Forbidden"; break;
486 case XMLTOOLING_HTTP_STATUS_NOTFOUND: codestr="404 Not Found"; break;
487 case XMLTOOLING_HTTP_STATUS_ERROR: codestr="500 Server Error"; break;
489 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, (void*)codestr, (DWORD)hdr.c_str(), 0);
493 DWORD resplen = in.gcount();
494 m_pfc->WriteClient(m_pfc, buf, &resplen, 0);
496 return SF_STATUS_REQ_FINISHED;
498 long sendRedirect(const char* url) {
499 // XXX: Don't support the httpRedirect option, yet.
500 string hdr=string("Location: ") + url + "\r\n"
501 "Content-Type: text/html\r\n"
502 "Content-Length: 40\r\n"
503 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
504 "Cache-Control: private,no-store,no-cache\r\n";
505 for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
506 hdr += i->first + ": " + i->second + "\r\n";
508 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, "302 Please Wait", (DWORD)hdr.c_str(), 0);
509 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
511 m_pfc->WriteClient(m_pfc, (LPVOID)redmsg, &resplen, 0);
512 return SF_STATUS_REQ_FINISHED;
514 long returnDecline() {
515 return SF_STATUS_REQ_NEXT_NOTIFICATION;
518 return SF_STATUS_REQ_NEXT_NOTIFICATION;
521 const vector<string>& getClientCertificates() const {
525 // The filter never processes the POST, so stub these methods.
526 const char* getQueryString() const { throw IOException("getQueryString not implemented"); }
527 const char* getRequestBody() const { throw IOException("getRequestBody not implemented"); }
530 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const char* msg)
532 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
533 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
534 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)ctype,0);
535 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Filter Error</TITLE></HEAD><BODY>"
536 "<H1>Shibboleth Filter Error</H1>";
537 DWORD resplen=strlen(xmsg);
538 pfc->WriteClient(pfc,(LPVOID)xmsg,&resplen,0);
540 pfc->WriteClient(pfc,(LPVOID)msg,&resplen,0);
541 static const char* xmsg2="</BODY></HTML>";
542 resplen=strlen(xmsg2);
543 pfc->WriteClient(pfc,(LPVOID)xmsg2,&resplen,0);
544 return SF_STATUS_REQ_FINISHED;
547 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
549 // Is this a log notification?
550 if (notificationType==SF_NOTIFY_LOG)
552 if (pfc->pFilterContext)
553 ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=static_cast<LPCSTR>(pfc->pFilterContext);
554 return SF_STATUS_REQ_NEXT_NOTIFICATION;
557 PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
560 // Determine web site number. This can't really fail, I don't think.
562 GetServerVariable(pfc,"INSTANCE_ID",buf,10);
564 // Match site instance to host name, skip if no match.
565 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
566 if (map_i==g_Sites.end())
567 return SF_STATUS_REQ_NEXT_NOTIFICATION;
569 ostringstream threadid;
570 threadid << "[" << getpid() << "] isapi_shib" << '\0';
571 xmltooling::NDC ndc(threadid.str().c_str());
573 ShibTargetIsapiF stf(pfc, pn, map_i->second);
575 // "false" because we don't override the Shib settings
576 pair<bool,long> res = stf.getServiceProvider().doAuthentication(stf);
577 if (res.first) return res.second;
579 // "false" because we don't override the Shib settings
580 res = stf.getServiceProvider().doExport(stf);
581 if (res.first) return res.second;
583 res = stf.getServiceProvider().doAuthorization(stf);
584 if (res.first) return res.second;
586 return SF_STATUS_REQ_NEXT_NOTIFICATION;
589 return WriteClientError(pfc,"Out of Memory");
592 if (e==ERROR_NO_DATA)
593 return WriteClientError(pfc,"A required variable or header was empty.");
595 return WriteClientError(pfc,"Shibboleth Filter detected unexpected IIS error.");
597 catch (exception& e) {
598 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
599 return WriteClientError(pfc,"Shibboleth Filter caught an exception, check Event Log for details.");
602 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Shibboleth Filter threw an unknown exception.");
604 return WriteClientError(pfc,"Shibboleth Filter threw an unknown exception.");
608 return WriteClientError(pfc,"Shibboleth Filter reached unreachable code, save my walrus!");
612 /****************************************************************************/
615 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* msg)
617 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
618 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
619 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
620 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Error</TITLE></HEAD><BODY><H1>Shibboleth Error</H1>";
621 DWORD resplen=strlen(xmsg);
622 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg,&resplen,HSE_IO_SYNC);
624 lpECB->WriteClient(lpECB->ConnID,(LPVOID)msg,&resplen,HSE_IO_SYNC);
625 static const char* xmsg2="</BODY></HTML>";
626 resplen=strlen(xmsg2);
627 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg2,&resplen,HSE_IO_SYNC);
628 return HSE_STATUS_SUCCESS;
632 class ShibTargetIsapiE : public AbstractSPRequest
634 LPEXTENSION_CONTROL_BLOCK m_lpECB;
635 multimap<string,string> m_headers;
636 mutable vector<string> m_certs;
637 mutable string m_body;
638 mutable bool m_gotBody;
640 string m_scheme,m_hostname,m_uri;
641 mutable string m_remote_addr,m_remote_user;
644 ShibTargetIsapiE(LPEXTENSION_CONTROL_BLOCK lpECB, const site_t& site) : m_lpECB(lpECB), m_gotBody(false) {
646 GetServerVariable(lpECB,"HTTPS",ssl,5);
647 bool SSL=(ssl=="on" || ssl=="ON");
649 // Scheme may come from site def or be derived from IIS.
650 m_scheme=site.m_scheme;
651 if (m_scheme.empty() || !g_bNormalizeRequest)
652 m_scheme = SSL ? "https" : "http";
654 // URL path always come from IIS.
656 GetServerVariable(lpECB,"URL",url,255);
658 // Port may come from IIS or from site def.
660 if (!g_bNormalizeRequest || (SSL && site.m_sslport.empty()) || (!SSL && site.m_port.empty()))
661 GetServerVariable(lpECB,"SERVER_PORT",port,10);
663 strncpy(port,site.m_sslport.c_str(),10);
664 static_cast<char*>(port)[10]=0;
667 strncpy(port,site.m_port.c_str(),10);
668 static_cast<char*>(port)[10]=0;
673 GetServerVariable(lpECB, "SERVER_NAME", var, 32);
675 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
677 if (site.m_name!=m_hostname && site.m_aliases.find(m_hostname)==site.m_aliases.end())
678 m_hostname=site.m_name;
681 * IIS screws us over on PATH_INFO (the hits keep on coming). We need to figure out if
682 * the server is set up for proper PATH_INFO handling, or "IIS sucks rabid weasels mode",
683 * which is the default. No perfect way to tell, but we can take a good guess by checking
684 * whether the URL is a substring of the PATH_INFO:
686 * e.g. for /Shibboleth.sso/SAML/POST
688 * Bad mode (default):
689 * URL: /Shibboleth.sso
690 * PathInfo: /Shibboleth.sso/SAML/POST
693 * URL: /Shibboleth.sso
694 * PathInfo: /SAML/POST
699 // Clearly we're only in bad mode if path info exists at all.
700 if (lpECB->lpszPathInfo && *(lpECB->lpszPathInfo)) {
701 if (strstr(lpECB->lpszPathInfo,url))
702 // Pretty good chance we're in bad mode, unless the PathInfo repeats the path itself.
703 uri = lpECB->lpszPathInfo;
706 uri += lpECB->lpszPathInfo;
713 // For consistency with Apache, let's add the query string.
714 if (lpECB->lpszQueryString && *(lpECB->lpszQueryString)) {
716 uri += lpECB->lpszQueryString;
719 setRequestURI(uri.c_str());
721 ~ShibTargetIsapiE() { }
723 const char* getScheme() const {
724 return m_scheme.c_str();
726 const char* getHostname() const {
727 return m_hostname.c_str();
729 int getPort() const {
732 const char* getMethod() const {
733 return m_lpECB->lpszMethod;
735 string getContentType() const {
736 return m_lpECB->lpszContentType ? m_lpECB->lpszContentType : "";
738 long getContentLength() const {
739 return m_lpECB->cbTotalBytes;
741 string getRemoteUser() const {
742 if (m_remote_user.empty()) {
744 GetServerVariable(m_lpECB, "REMOTE_USER", var, 32, false);
748 return m_remote_user;
750 string getRemoteAddr() const {
751 if (m_remote_addr.empty()) {
753 GetServerVariable(m_lpECB, "REMOTE_ADDR", var, 16, false);
757 return m_remote_addr;
759 void log(SPLogLevel level, const string& msg) const {
760 AbstractSPRequest::log(level,msg);
761 if (level >= SPError)
762 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
764 string getHeader(const char* name) const {
766 for (; *name; ++name) {
770 hdr += toupper(*name);
773 GetServerVariable(m_lpECB, const_cast<char*>(hdr.c_str()), buf, 128, false);
774 return buf.empty() ? "" : buf;
776 void setResponseHeader(const char* name, const char* value) {
779 m_headers.insert(make_pair(name,value));
781 m_headers.erase(name);
783 const char* getQueryString() const {
784 return m_lpECB->lpszQueryString;
786 const char* getRequestBody() const {
788 return m_body.c_str();
789 if (m_lpECB->cbTotalBytes > 1024*1024) // 1MB?
790 throw opensaml::SecurityPolicyException("Size of request body exceeded 1M size limit.");
791 else if (m_lpECB->cbTotalBytes > m_lpECB->cbAvailable) {
794 DWORD datalen=m_lpECB->cbTotalBytes;
797 BOOL ret = m_lpECB->ReadClient(m_lpECB->ConnID, buf, &buflen);
799 throw IOException("Error reading request body from browser.");
800 m_body.append(buf, buflen);
804 else if (m_lpECB->cbAvailable) {
806 m_body.assign(reinterpret_cast<char*>(m_lpECB->lpbData),m_lpECB->cbAvailable);
808 return m_body.c_str();
810 long sendResponse(istream& in, long status) {
811 string hdr = string("Connection: close\r\n");
812 for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
813 hdr += i->first + ": " + i->second + "\r\n";
815 const char* codestr="200 OK";
817 case XMLTOOLING_HTTP_STATUS_UNAUTHORIZED: codestr="401 Authorization Required"; break;
818 case XMLTOOLING_HTTP_STATUS_FORBIDDEN: codestr="403 Forbidden"; break;
819 case XMLTOOLING_HTTP_STATUS_NOTFOUND: codestr="404 Not Found"; break;
820 case XMLTOOLING_HTTP_STATUS_ERROR: codestr="500 Server Error"; break;
822 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, (void*)codestr, 0, (LPDWORD)hdr.c_str());
826 DWORD resplen = in.gcount();
827 m_lpECB->WriteClient(m_lpECB->ConnID, buf, &resplen, HSE_IO_SYNC);
829 return HSE_STATUS_SUCCESS;
831 long sendRedirect(const char* url) {
832 string hdr=string("Location: ") + url + "\r\n"
833 "Content-Type: text/html\r\n"
834 "Content-Length: 40\r\n"
835 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
836 "Cache-Control: private,no-store,no-cache\r\n";
837 for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
838 hdr += i->first + ": " + i->second + "\r\n";
840 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, "302 Moved", 0, (LPDWORD)hdr.c_str());
841 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
843 m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)redmsg, &resplen, HSE_IO_SYNC);
844 return HSE_STATUS_SUCCESS;
846 // Decline happens in the POST processor if this isn't the shire url
847 // Note that it can also happen with HTAccess, but we don't support that, yet.
848 long returnDecline() {
849 return WriteClientError(
851 "ISAPI extension can only be invoked to process Shibboleth protocol requests."
852 "Make sure the mapped file extension doesn't match actual content."
856 return HSE_STATUS_SUCCESS;
859 const vector<string>& getClientCertificates() const {
860 if (m_certs.empty()) {
861 char CertificateBuf[8192];
862 CERT_CONTEXT_EX ccex;
863 ccex.cbAllocated = sizeof(CertificateBuf);
864 ccex.CertContext.pbCertEncoded = (BYTE*)CertificateBuf;
865 DWORD dwSize = sizeof(ccex);
867 if (m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_GET_CERT_INFO_EX, (LPVOID)&ccex, (LPDWORD)dwSize, NULL)) {
868 if (ccex.CertContext.cbCertEncoded) {
870 XMLByte* serialized = Base64::encode(reinterpret_cast<XMLByte*>(CertificateBuf), ccex.CertContext.cbCertEncoded, &outlen);
871 m_certs.push_back(reinterpret_cast<char*>(serialized));
872 XMLString::release(&serialized);
879 // Not used in the extension.
880 void clearHeader(const char* rawname, const char* cginame) { throw runtime_error("clearHeader not implemented"); }
881 void setHeader(const char* name, const char* value) { throw runtime_error("setHeader not implemented"); }
882 void setRemoteUser(const char* user) { throw runtime_error("setRemoteUser not implemented"); }
885 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
888 ostringstream threadid;
889 threadid << "[" << getpid() << "] isapi_shib_extension" << '\0';
890 xmltooling::NDC ndc(threadid.str().c_str());
892 // Determine web site number. This can't really fail, I don't think.
894 GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
896 // Match site instance to host name, skip if no match.
897 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
898 if (map_i==g_Sites.end())
899 return WriteClientError(lpECB, "Shibboleth Extension not configured for this web site.");
901 ShibTargetIsapiE ste(lpECB, map_i->second);
902 pair<bool,long> res = ste.getServiceProvider().doHandler(ste);
903 if (res.first) return res.second;
905 return WriteClientError(lpECB, "Shibboleth Extension failed to process request");
909 return WriteClientError(lpECB,"Out of Memory");
912 if (e==ERROR_NO_DATA)
913 return WriteClientError(lpECB,"A required variable or header was empty.");
915 return WriteClientError(lpECB,"Server detected unexpected IIS error.");
917 catch (exception& e) {
918 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
919 return WriteClientError(lpECB,"Shibboleth Extension caught an exception, check Event Log for details.");
922 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Shibboleth Extension threw an unknown exception.");
924 return WriteClientError(lpECB,"Shibboleth Extension threw an unknown exception.");
928 // If we get here we've got an error.
929 return HSE_STATUS_ERROR;