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* 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) {
804 DWORD datalen=m_lpECB->cbTotalBytes;
807 BOOL ret = m_lpECB->ReadClient(m_lpECB->ConnID, buf, &buflen);
809 throw IOException("Error reading request body from browser.");
811 throw IOException("Socket closed while reading request body from browser.");
812 m_body.append(buf, buflen);
816 else if (m_lpECB->cbAvailable) {
818 m_body.assign(reinterpret_cast<char*>(m_lpECB->lpbData),m_lpECB->cbAvailable);
820 return m_body.c_str();
822 long sendResponse(istream& in, long status) {
823 string hdr = string("Connection: close\r\n");
824 for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
825 hdr += i->first + ": " + i->second + "\r\n";
827 const char* codestr="200 OK";
829 case XMLTOOLING_HTTP_STATUS_UNAUTHORIZED: codestr="401 Authorization Required"; break;
830 case XMLTOOLING_HTTP_STATUS_FORBIDDEN: codestr="403 Forbidden"; break;
831 case XMLTOOLING_HTTP_STATUS_NOTFOUND: codestr="404 Not Found"; break;
832 case XMLTOOLING_HTTP_STATUS_ERROR: codestr="500 Server Error"; break;
834 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, (void*)codestr, 0, (LPDWORD)hdr.c_str());
838 DWORD resplen = in.gcount();
839 m_lpECB->WriteClient(m_lpECB->ConnID, buf, &resplen, HSE_IO_SYNC);
841 return HSE_STATUS_SUCCESS;
843 long sendRedirect(const char* url) {
844 string hdr=string("Location: ") + url + "\r\n"
845 "Content-Type: text/html\r\n"
846 "Content-Length: 40\r\n"
847 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
848 "Cache-Control: private,no-store,no-cache\r\n";
849 for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
850 hdr += i->first + ": " + i->second + "\r\n";
852 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, "302 Moved", 0, (LPDWORD)hdr.c_str());
853 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
855 m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)redmsg, &resplen, HSE_IO_SYNC);
856 return HSE_STATUS_SUCCESS;
858 // Decline happens in the POST processor if this isn't the shire url
859 // Note that it can also happen with HTAccess, but we don't support that, yet.
860 long returnDecline() {
861 return WriteClientError(
863 "ISAPI extension can only be invoked to process Shibboleth protocol requests."
864 "Make sure the mapped file extension doesn't match actual content."
868 return HSE_STATUS_SUCCESS;
871 const vector<string>& getClientCertificates() const {
872 if (m_certs.empty()) {
873 char CertificateBuf[8192];
874 CERT_CONTEXT_EX ccex;
875 ccex.cbAllocated = sizeof(CertificateBuf);
876 ccex.CertContext.pbCertEncoded = (BYTE*)CertificateBuf;
877 DWORD dwSize = sizeof(ccex);
879 if (m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_GET_CERT_INFO_EX, (LPVOID)&ccex, (LPDWORD)dwSize, NULL)) {
880 if (ccex.CertContext.cbCertEncoded) {
882 XMLByte* serialized = Base64::encode(reinterpret_cast<XMLByte*>(CertificateBuf), ccex.CertContext.cbCertEncoded, &outlen);
883 m_certs.push_back(reinterpret_cast<char*>(serialized));
884 #ifdef SHIBSP_XERCESC_HAS_XMLBYTE_RELEASE
885 XMLString::release(&serialized);
887 XMLString::release((char**)&serialized);
895 // Not used in the extension.
896 void clearHeader(const char* rawname, const char* cginame) { throw runtime_error("clearHeader not implemented"); }
897 void setHeader(const char* name, const char* value) { throw runtime_error("setHeader not implemented"); }
898 void setRemoteUser(const char* user) { throw runtime_error("setRemoteUser not implemented"); }
901 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
904 ostringstream threadid;
905 threadid << "[" << getpid() << "] isapi_shib_extension" << '\0';
906 xmltooling::NDC ndc(threadid.str().c_str());
908 // Determine web site number. This can't really fail, I don't think.
910 GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
912 // Match site instance to host name, skip if no match.
913 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
914 if (map_i==g_Sites.end())
915 return WriteClientError(lpECB, "Shibboleth Extension not configured for web site (check <ISAPI> mappings in configuration).");
917 ShibTargetIsapiE ste(lpECB, map_i->second);
918 pair<bool,long> res = ste.getServiceProvider().doHandler(ste);
919 if (res.first) return res.second;
921 return WriteClientError(lpECB, "Shibboleth Extension failed to process request");
925 return WriteClientError(lpECB,"Out of Memory");
928 if (e==ERROR_NO_DATA)
929 return WriteClientError(lpECB,"A required variable or header was empty.");
931 return WriteClientError(lpECB,"Server detected unexpected IIS error.");
933 catch (exception& e) {
934 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
935 return WriteClientError(lpECB,"Shibboleth Extension caught an exception, check Event Log for details.");
938 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Shibboleth Extension threw an unknown exception.");
940 return WriteClientError(lpECB,"Shibboleth Extension threw an unknown exception.");
944 // If we get here we've got an error.
945 return HSE_STATUS_ERROR;