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
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* getQueryString() const {
405 const char* uri = getRequestURI();
406 uri = (uri ? strchr(uri, '?') : NULL);
407 return uri ? (uri + 1) : NULL;
409 const char* getMethod() const {
410 if (m_method.empty()) {
412 GetServerVariable(m_pfc,"HTTP_METHOD",var,5,false);
416 return m_method.c_str();
418 string getContentType() const {
419 if (m_content_type.empty()) {
421 GetServerVariable(m_pfc,"HTTP_CONTENT_TYPE",var,32,false);
423 m_content_type = var;
425 return m_content_type;
427 string getRemoteAddr() const {
428 m_remote_addr = AbstractSPRequest::getRemoteAddr();
429 if (m_remote_addr.empty()) {
431 GetServerVariable(m_pfc,"REMOTE_ADDR",var,16,false);
435 return m_remote_addr;
437 void log(SPLogLevel level, const string& msg) {
438 AbstractSPRequest::log(level,msg);
439 if (level >= SPError)
440 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
442 void clearHeader(const char* rawname, const char* cginame) {
443 if (g_checkSpoofing && m_pfc->pFilterContext && !static_cast<context_t*>(m_pfc->pFilterContext)->m_checked) {
444 if (m_allhttp.empty())
445 GetServerVariable(m_pfc,"ALL_HTTP",m_allhttp,4096);
446 if (strstr(m_allhttp, cginame))
447 throw opensaml::SecurityPolicyException("Attempt to spoof header ($1) was detected.", params(1, rawname));
449 string hdr(!strcmp(rawname,"REMOTE_USER") ? "remote-user" : rawname);
451 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), const_cast<char*>(g_unsetHeaderValue.c_str()));
453 void setHeader(const char* name, const char* value) {
456 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), const_cast<char*>(value));
458 string getHeader(const char* name) const {
462 GetHeader(m_pn, m_pfc, const_cast<char*>(hdr.c_str()), buf, 256, false);
465 void setRemoteUser(const char* user) {
466 setHeader("remote-user", user);
467 if (m_pfc->pFilterContext) {
469 static_cast<context_t*>(m_pfc->pFilterContext)->m_user = NULL;
470 else if (static_cast<context_t*>(m_pfc->pFilterContext)->m_user = (char*)m_pfc->AllocMem(m_pfc, sizeof(char) * (strlen(user) + 1), NULL))
471 strcpy(static_cast<context_t*>(m_pfc->pFilterContext)->m_user, user);
474 string getRemoteUser() const {
475 return getHeader("remote-user");
477 void setResponseHeader(const char* name, const char* value) {
480 m_headers.insert(make_pair(name,value));
482 m_headers.erase(name);
484 long sendResponse(istream& in, long status) {
485 string hdr = string("Connection: close\r\n");
486 for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
487 hdr += i->first + ": " + i->second + "\r\n";
489 const char* codestr="200 OK";
491 case XMLTOOLING_HTTP_STATUS_UNAUTHORIZED: codestr="401 Authorization Required"; break;
492 case XMLTOOLING_HTTP_STATUS_FORBIDDEN: codestr="403 Forbidden"; break;
493 case XMLTOOLING_HTTP_STATUS_NOTFOUND: codestr="404 Not Found"; break;
494 case XMLTOOLING_HTTP_STATUS_ERROR: codestr="500 Server Error"; break;
496 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, (void*)codestr, (DWORD)hdr.c_str(), 0);
500 DWORD resplen = in.gcount();
501 m_pfc->WriteClient(m_pfc, buf, &resplen, 0);
503 return SF_STATUS_REQ_FINISHED;
505 long sendRedirect(const char* url) {
506 // XXX: Don't support the httpRedirect option, yet.
507 string hdr=string("Location: ") + url + "\r\n"
508 "Content-Type: text/html\r\n"
509 "Content-Length: 40\r\n"
510 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
511 "Cache-Control: private,no-store,no-cache\r\n";
512 for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
513 hdr += i->first + ": " + i->second + "\r\n";
515 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, "302 Please Wait", (DWORD)hdr.c_str(), 0);
516 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
518 m_pfc->WriteClient(m_pfc, (LPVOID)redmsg, &resplen, 0);
519 return SF_STATUS_REQ_FINISHED;
521 long returnDecline() {
522 return SF_STATUS_REQ_NEXT_NOTIFICATION;
525 return SF_STATUS_REQ_NEXT_NOTIFICATION;
528 const vector<string>& getClientCertificates() const {
532 // The filter never processes the POST, so stub these methods.
533 long getContentLength() const { throw IOException("The request's Content-Length is not available to an ISAPI filter."); }
534 const char* getRequestBody() const { throw IOException("The request body is not available to an ISAPI filter."); }
537 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const char* msg)
539 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
540 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
541 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)ctype,0);
542 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Filter Error</TITLE></HEAD><BODY>"
543 "<H1>Shibboleth Filter Error</H1>";
544 DWORD resplen=strlen(xmsg);
545 pfc->WriteClient(pfc,(LPVOID)xmsg,&resplen,0);
547 pfc->WriteClient(pfc,(LPVOID)msg,&resplen,0);
548 static const char* xmsg2="</BODY></HTML>";
549 resplen=strlen(xmsg2);
550 pfc->WriteClient(pfc,(LPVOID)xmsg2,&resplen,0);
551 return SF_STATUS_REQ_FINISHED;
554 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
556 // Is this a log notification?
557 if (notificationType==SF_NOTIFY_LOG) {
558 if (pfc->pFilterContext && static_cast<context_t*>(pfc->pFilterContext)->m_user)
559 ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=static_cast<context_t*>(pfc->pFilterContext)->m_user;
560 return SF_STATUS_REQ_NEXT_NOTIFICATION;
563 PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
566 // Determine web site number. This can't really fail, I don't think.
568 GetServerVariable(pfc,"INSTANCE_ID",buf,10);
570 // Match site instance to host name, skip if no match.
571 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
572 if (map_i==g_Sites.end())
573 return SF_STATUS_REQ_NEXT_NOTIFICATION;
575 ostringstream threadid;
576 threadid << "[" << getpid() << "] isapi_shib" << '\0';
577 xmltooling::NDC ndc(threadid.str().c_str());
579 ShibTargetIsapiF stf(pfc, pn, map_i->second);
581 // "false" because we don't override the Shib settings
582 pair<bool,long> res = stf.getServiceProvider().doAuthentication(stf);
583 if (pfc->pFilterContext)
584 static_cast<context_t*>(pfc->pFilterContext)->m_checked = true;
585 if (res.first) return res.second;
587 // "false" because we don't override the Shib settings
588 res = stf.getServiceProvider().doExport(stf);
589 if (res.first) return res.second;
591 res = stf.getServiceProvider().doAuthorization(stf);
592 if (res.first) return res.second;
594 return SF_STATUS_REQ_NEXT_NOTIFICATION;
597 return WriteClientError(pfc,"Out of Memory");
600 if (e==ERROR_NO_DATA)
601 return WriteClientError(pfc,"A required variable or header was empty.");
603 return WriteClientError(pfc,"Shibboleth Filter detected unexpected IIS error.");
605 catch (exception& e) {
606 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
607 return WriteClientError(pfc,"Shibboleth Filter caught an exception, check Event Log for details.");
610 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Shibboleth Filter threw an unknown exception.");
612 return WriteClientError(pfc,"Shibboleth Filter threw an unknown exception.");
616 return WriteClientError(pfc,"Shibboleth Filter reached unreachable code, save my walrus!");
620 /****************************************************************************/
623 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* msg)
625 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
626 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
627 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
628 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Error</TITLE></HEAD><BODY><H1>Shibboleth Error</H1>";
629 DWORD resplen=strlen(xmsg);
630 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg,&resplen,HSE_IO_SYNC);
632 lpECB->WriteClient(lpECB->ConnID,(LPVOID)msg,&resplen,HSE_IO_SYNC);
633 static const char* xmsg2="</BODY></HTML>";
634 resplen=strlen(xmsg2);
635 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg2,&resplen,HSE_IO_SYNC);
636 return HSE_STATUS_SUCCESS;
640 class ShibTargetIsapiE : public AbstractSPRequest
642 LPEXTENSION_CONTROL_BLOCK m_lpECB;
643 multimap<string,string> m_headers;
644 mutable vector<string> m_certs;
645 mutable string m_body;
646 mutable bool m_gotBody;
648 string m_scheme,m_hostname,m_uri;
649 mutable string m_remote_addr,m_remote_user;
652 ShibTargetIsapiE(LPEXTENSION_CONTROL_BLOCK lpECB, const site_t& site)
653 : AbstractSPRequest(SHIBSP_LOGCAT".ISAPI"), m_lpECB(lpECB), m_gotBody(false) {
655 GetServerVariable(lpECB,"HTTPS",ssl,5);
656 bool SSL=(ssl=="on" || ssl=="ON");
658 // Scheme may come from site def or be derived from IIS.
659 m_scheme=site.m_scheme;
660 if (m_scheme.empty() || !g_bNormalizeRequest)
661 m_scheme = SSL ? "https" : "http";
663 // URL path always come from IIS.
665 GetServerVariable(lpECB,"URL",url,255);
667 // Port may come from IIS or from site def.
669 if (!g_bNormalizeRequest || (SSL && site.m_sslport.empty()) || (!SSL && site.m_port.empty()))
670 GetServerVariable(lpECB,"SERVER_PORT",port,10);
672 strncpy(port,site.m_sslport.c_str(),10);
673 static_cast<char*>(port)[10]=0;
676 strncpy(port,site.m_port.c_str(),10);
677 static_cast<char*>(port)[10]=0;
682 GetServerVariable(lpECB, "SERVER_NAME", var, 32);
684 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
686 if (site.m_name!=m_hostname && site.m_aliases.find(m_hostname)==site.m_aliases.end())
687 m_hostname=site.m_name;
690 * IIS screws us over on PATH_INFO (the hits keep on coming). We need to figure out if
691 * the server is set up for proper PATH_INFO handling, or "IIS sucks rabid weasels mode",
692 * which is the default. No perfect way to tell, but we can take a good guess by checking
693 * whether the URL is a substring of the PATH_INFO:
695 * e.g. for /Shibboleth.sso/SAML/POST
697 * Bad mode (default):
698 * URL: /Shibboleth.sso
699 * PathInfo: /Shibboleth.sso/SAML/POST
702 * URL: /Shibboleth.sso
703 * PathInfo: /SAML/POST
708 // Clearly we're only in bad mode if path info exists at all.
709 if (lpECB->lpszPathInfo && *(lpECB->lpszPathInfo)) {
710 if (strstr(lpECB->lpszPathInfo,url))
711 // Pretty good chance we're in bad mode, unless the PathInfo repeats the path itself.
712 uri = lpECB->lpszPathInfo;
715 uri += lpECB->lpszPathInfo;
722 // For consistency with Apache, let's add the query string.
723 if (lpECB->lpszQueryString && *(lpECB->lpszQueryString)) {
725 uri += lpECB->lpszQueryString;
728 setRequestURI(uri.c_str());
730 ~ShibTargetIsapiE() { }
732 const char* getScheme() const {
733 return m_scheme.c_str();
735 const char* getHostname() const {
736 return m_hostname.c_str();
738 int getPort() const {
741 const char* getMethod() const {
742 return m_lpECB->lpszMethod;
744 string getContentType() const {
745 return m_lpECB->lpszContentType ? m_lpECB->lpszContentType : "";
747 long getContentLength() const {
748 return m_lpECB->cbTotalBytes;
750 string getRemoteUser() const {
751 if (m_remote_user.empty()) {
753 GetServerVariable(m_lpECB, "REMOTE_USER", var, 32, false);
757 return m_remote_user;
759 string getRemoteAddr() const {
760 m_remote_addr = AbstractSPRequest::getRemoteAddr();
761 if (m_remote_addr.empty()) {
763 GetServerVariable(m_lpECB, "REMOTE_ADDR", var, 16, false);
767 return m_remote_addr;
769 void log(SPLogLevel level, const string& msg) const {
770 AbstractSPRequest::log(level,msg);
771 if (level >= SPError)
772 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
774 string getHeader(const char* name) const {
776 for (; *name; ++name) {
780 hdr += toupper(*name);
783 GetServerVariable(m_lpECB, const_cast<char*>(hdr.c_str()), buf, 128, false);
784 return buf.empty() ? "" : buf;
786 void setResponseHeader(const char* name, const char* value) {
789 m_headers.insert(make_pair(name,value));
791 m_headers.erase(name);
793 const char* getQueryString() const {
794 return m_lpECB->lpszQueryString;
796 const char* getRequestBody() const {
798 return m_body.c_str();
799 if (m_lpECB->cbTotalBytes > 1024*1024) // 1MB?
800 throw opensaml::SecurityPolicyException("Size of request body exceeded 1M size limit.");
801 else if (m_lpECB->cbTotalBytes > m_lpECB->cbAvailable) {
803 DWORD datalen=m_lpECB->cbTotalBytes;
804 if (m_lpECB->cbAvailable > 0) {
805 m_body.assign(reinterpret_cast<char*>(m_lpECB->lpbData),m_lpECB->cbAvailable);
806 datalen-=m_lpECB->cbAvailable;
811 BOOL ret = m_lpECB->ReadClient(m_lpECB->ConnID, buf, &buflen);
813 throw IOException("Error reading request body from browser.");
815 throw IOException("Socket closed while reading request body from browser.");
816 m_body.append(buf, buflen);
820 else if (m_lpECB->cbAvailable) {
822 m_body.assign(reinterpret_cast<char*>(m_lpECB->lpbData),m_lpECB->cbAvailable);
824 return m_body.c_str();
826 long sendResponse(istream& in, long status) {
827 string hdr = string("Connection: close\r\n");
828 for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
829 hdr += i->first + ": " + i->second + "\r\n";
831 const char* codestr="200 OK";
833 case XMLTOOLING_HTTP_STATUS_UNAUTHORIZED: codestr="401 Authorization Required"; break;
834 case XMLTOOLING_HTTP_STATUS_FORBIDDEN: codestr="403 Forbidden"; break;
835 case XMLTOOLING_HTTP_STATUS_NOTFOUND: codestr="404 Not Found"; break;
836 case XMLTOOLING_HTTP_STATUS_ERROR: codestr="500 Server Error"; break;
838 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, (void*)codestr, 0, (LPDWORD)hdr.c_str());
842 DWORD resplen = in.gcount();
843 m_lpECB->WriteClient(m_lpECB->ConnID, buf, &resplen, HSE_IO_SYNC);
845 return HSE_STATUS_SUCCESS;
847 long sendRedirect(const char* url) {
848 string hdr=string("Location: ") + url + "\r\n"
849 "Content-Type: text/html\r\n"
850 "Content-Length: 40\r\n"
851 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
852 "Cache-Control: private,no-store,no-cache\r\n";
853 for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
854 hdr += i->first + ": " + i->second + "\r\n";
856 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, "302 Moved", 0, (LPDWORD)hdr.c_str());
857 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
859 m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)redmsg, &resplen, HSE_IO_SYNC);
860 return HSE_STATUS_SUCCESS;
862 // Decline happens in the POST processor if this isn't the shire url
863 // Note that it can also happen with HTAccess, but we don't support that, yet.
864 long returnDecline() {
865 return WriteClientError(
867 "ISAPI extension can only be invoked to process Shibboleth protocol requests."
868 "Make sure the mapped file extension doesn't match actual content."
872 return HSE_STATUS_SUCCESS;
875 const vector<string>& getClientCertificates() const {
876 if (m_certs.empty()) {
877 char CertificateBuf[8192];
878 CERT_CONTEXT_EX ccex;
879 ccex.cbAllocated = sizeof(CertificateBuf);
880 ccex.CertContext.pbCertEncoded = (BYTE*)CertificateBuf;
881 DWORD dwSize = sizeof(ccex);
883 if (m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_GET_CERT_INFO_EX, (LPVOID)&ccex, (LPDWORD)dwSize, NULL)) {
884 if (ccex.CertContext.cbCertEncoded) {
886 XMLByte* serialized = Base64::encode(reinterpret_cast<XMLByte*>(CertificateBuf), ccex.CertContext.cbCertEncoded, &outlen);
887 m_certs.push_back(reinterpret_cast<char*>(serialized));
888 #ifdef SHIBSP_XERCESC_HAS_XMLBYTE_RELEASE
889 XMLString::release(&serialized);
891 XMLString::release((char**)&serialized);
899 // Not used in the extension.
900 void clearHeader(const char* rawname, const char* cginame) { throw runtime_error("clearHeader not implemented"); }
901 void setHeader(const char* name, const char* value) { throw runtime_error("setHeader not implemented"); }
902 void setRemoteUser(const char* user) { throw runtime_error("setRemoteUser not implemented"); }
905 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
908 ostringstream threadid;
909 threadid << "[" << getpid() << "] isapi_shib_extension" << '\0';
910 xmltooling::NDC ndc(threadid.str().c_str());
912 // Determine web site number. This can't really fail, I don't think.
914 GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
916 // Match site instance to host name, skip if no match.
917 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
918 if (map_i==g_Sites.end())
919 return WriteClientError(lpECB, "Shibboleth Extension not configured for web site (check <ISAPI> mappings in configuration).");
921 ShibTargetIsapiE ste(lpECB, map_i->second);
922 pair<bool,long> res = ste.getServiceProvider().doHandler(ste);
923 if (res.first) return res.second;
925 return WriteClientError(lpECB, "Shibboleth Extension failed to process request");
929 return WriteClientError(lpECB,"Out of Memory");
932 if (e==ERROR_NO_DATA)
933 return WriteClientError(lpECB,"A required variable or header was empty.");
935 return WriteClientError(lpECB,"Server detected unexpected IIS error.");
937 catch (exception& e) {
938 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
939 return WriteClientError(lpECB,"Shibboleth Extension caught an exception, check Event Log for details.");
942 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Shibboleth Extension threw an unknown exception.");
944 return WriteClientError(lpECB,"Shibboleth Extension threw an unknown exception.");
948 // If we get here we've got an error.
949 return HSE_STATUS_ERROR;