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 Alias[] = UNICODE_LITERAL_5(A,l,i,a,s);
64 static const XMLCh Site[] = UNICODE_LITERAL_4(S,i,t,e);
67 site_t(const DOMElement* e)
69 auto_ptr_char n(e->getAttributeNS(NULL,name));
70 auto_ptr_char s(e->getAttributeNS(NULL,scheme));
71 auto_ptr_char p(e->getAttributeNS(NULL,port));
72 auto_ptr_char p2(e->getAttributeNS(NULL,sslport));
73 if (n.get()) m_name=n.get();
74 if (s.get()) m_scheme=s.get();
75 if (p.get()) m_port=p.get();
76 if (p2.get()) m_sslport=p2.get();
77 e = XMLHelper::getFirstChildElement(e, Alias);
79 if (e->hasChildNodes()) {
80 auto_ptr_char alias(e->getFirstChild()->getNodeValue());
81 m_aliases.insert(alias.get());
83 e = XMLHelper::getNextSiblingElement(e, Alias);
86 string m_scheme,m_port,m_sslport,m_name;
87 set<string> m_aliases;
96 SPConfig* g_Config = NULL;
97 map<string,site_t> g_Sites;
98 bool g_bNormalizeRequest = true;
99 string g_unsetHeaderValue;
100 bool g_checkSpoofing = true;
101 bool g_catchAll = false;
102 vector<string> g_NoCerts;
106 LPCSTR lpUNCServerName,
112 LPCSTR messages[] = {message, NULL};
114 HANDLE hElog = RegisterEventSource(lpUNCServerName, "Shibboleth ISAPI Filter");
115 BOOL res = ReportEvent(hElog, wType, 0, dwEventID, lpUserSid, 1, 0, messages, NULL);
116 return (DeregisterEventSource(hElog) && res);
119 extern "C" __declspec(dllexport) BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID)
121 if (fdwReason==DLL_PROCESS_ATTACH)
126 extern "C" BOOL WINAPI GetExtensionVersion(HSE_VERSION_INFO* pVer)
132 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
133 "Extension mode startup not possible, is the DLL loaded as a filter?");
137 pVer->dwExtensionVersion=HSE_VERSION;
138 strncpy(pVer->lpszExtensionDesc,"Shibboleth ISAPI Extension",HSE_MAX_EXT_DLL_NAME_LEN-1);
142 extern "C" BOOL WINAPI TerminateExtension(DWORD)
144 return TRUE; // cleanup should happen when filter unloads
147 extern "C" BOOL WINAPI GetFilterVersion(PHTTP_FILTER_VERSION pVer)
152 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
153 "Reentrant filter initialization, ignoring...");
157 g_Config=&SPConfig::getConfig();
158 g_Config->setFeatures(
161 SPConfig::RequestMapping |
162 SPConfig::InProcess |
166 if (!g_Config->init()) {
168 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
169 "Filter startup failed during library initialization, check native log for help.");
174 if (!g_Config->instantiate(NULL, true))
175 throw exception("unknown error");
177 catch (exception& ex) {
180 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, ex.what());
181 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
182 "Filter startup failed to load configuration, check native log for details.");
186 // Access implementation-specifics and site mappings.
187 ServiceProvider* sp=g_Config->getServiceProvider();
189 const PropertySet* props=sp->getPropertySet("InProcess");
191 pair<bool,const char*> unsetValue=props->getString("unsetHeaderValue");
192 if (unsetValue.first)
193 g_unsetHeaderValue = unsetValue.second;
194 pair<bool,bool> flag=props->getBool("checkSpoofing");
195 g_checkSpoofing = !flag.first || flag.second;
196 flag=props->getBool("catchAll");
197 g_catchAll = flag.first && flag.second;
199 props = props->getPropertySet("ISAPI");
201 flag = props->getBool("normalizeRequest");
202 g_bNormalizeRequest = !flag.first || flag.second;
203 const DOMElement* child = XMLHelper::getFirstChildElement(props->getElement(),Site);
205 auto_ptr_char id(child->getAttributeNS(NULL,id));
207 g_Sites.insert(pair<string,site_t>(id.get(),site_t(child)));
208 child=XMLHelper::getNextSiblingElement(child,Site);
213 pVer->dwFilterVersion=HTTP_FILTER_REVISION;
214 strncpy(pVer->lpszFilterDesc,"Shibboleth ISAPI Filter",SF_MAX_FILTER_DESC_LEN);
215 pVer->dwFlags=(SF_NOTIFY_ORDER_HIGH |
216 SF_NOTIFY_SECURE_PORT |
217 SF_NOTIFY_NONSECURE_PORT |
218 SF_NOTIFY_PREPROC_HEADERS |
220 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter initialized...");
224 extern "C" BOOL WINAPI TerminateFilter(DWORD)
229 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter shut down...");
233 /* Next up, some suck-free versions of various APIs.
235 You DON'T require people to guess the buffer size and THEN tell them the right size.
236 Returning an LPCSTR is apparently way beyond their ken. Not to mention the fact that
237 constant strings aren't typed as such, making it just that much harder. These versions
238 are now updated to use a special growable buffer object, modeled after the standard
239 string class. The standard string won't work because they left out the option to
240 pre-allocate a non-constant buffer.
246 dynabuf() { bufptr=NULL; buflen=0; }
247 dynabuf(size_t s) { bufptr=new char[buflen=s]; *bufptr=0; }
248 ~dynabuf() { delete[] bufptr; }
249 size_t length() const { return bufptr ? strlen(bufptr) : 0; }
250 size_t size() const { return buflen; }
251 bool empty() const { return length()==0; }
252 void reserve(size_t s, bool keep=false);
253 void erase() { if (bufptr) memset(bufptr,0,buflen); }
254 operator char*() { return bufptr; }
255 bool operator ==(const char* s) const;
256 bool operator !=(const char* s) const { return !(*this==s); }
262 void dynabuf::reserve(size_t s, bool keep)
269 p[buflen]=bufptr[buflen];
275 bool dynabuf::operator==(const char* s) const
277 if (buflen==NULL || s==NULL)
278 return (buflen==NULL && s==NULL);
280 return strcmp(bufptr,s)==0;
283 void GetServerVariable(PHTTP_FILTER_CONTEXT pfc, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
289 while (!pfc->GetServerVariable(pfc,lpszVariable,s,&size)) {
290 // Grumble. Check the error.
291 DWORD e=GetLastError();
292 if (e==ERROR_INSUFFICIENT_BUFFER)
297 if (bRequired && s.empty())
301 void GetServerVariable(LPEXTENSION_CONTROL_BLOCK lpECB, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
307 while (!lpECB->GetServerVariable(lpECB->ConnID,lpszVariable,s,&size)) {
308 // Grumble. Check the error.
309 DWORD e=GetLastError();
310 if (e==ERROR_INSUFFICIENT_BUFFER)
315 if (bRequired && s.empty())
319 void GetHeader(PHTTP_FILTER_PREPROC_HEADERS pn, PHTTP_FILTER_CONTEXT pfc,
320 LPSTR lpszName, dynabuf& s, DWORD size=80, bool bRequired=true)
326 while (!pn->GetHeader(pfc,lpszName,s,&size)) {
327 // Grumble. Check the error.
328 DWORD e=GetLastError();
329 if (e==ERROR_INSUFFICIENT_BUFFER)
334 if (bRequired && s.empty())
338 /****************************************************************************/
341 class ShibTargetIsapiF : public AbstractSPRequest
343 PHTTP_FILTER_CONTEXT m_pfc;
344 PHTTP_FILTER_PREPROC_HEADERS m_pn;
345 multimap<string,string> m_headers;
347 string m_scheme,m_hostname;
348 mutable string m_remote_addr,m_content_type,m_method;
352 ShibTargetIsapiF(PHTTP_FILTER_CONTEXT pfc, PHTTP_FILTER_PREPROC_HEADERS pn, const site_t& site)
353 : AbstractSPRequest(SHIBSP_LOGCAT".ISAPI"), m_pfc(pfc), m_pn(pn), m_allhttp(4096) {
355 // URL path always come from IIS.
357 GetHeader(pn,pfc,"url",var,256,false);
360 // Port may come from IIS or from site def.
361 if (!g_bNormalizeRequest || (pfc->fIsSecurePort && site.m_sslport.empty()) || (!pfc->fIsSecurePort && site.m_port.empty())) {
362 GetServerVariable(pfc,"SERVER_PORT",var,10);
365 else if (pfc->fIsSecurePort) {
366 m_port = atoi(site.m_sslport.c_str());
369 m_port = atoi(site.m_port.c_str());
372 // Scheme may come from site def or be derived from IIS.
373 m_scheme=site.m_scheme;
374 if (m_scheme.empty() || !g_bNormalizeRequest)
375 m_scheme=pfc->fIsSecurePort ? "https" : "http";
377 GetServerVariable(pfc,"SERVER_NAME",var,32);
379 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
381 if (site.m_name!=m_hostname && site.m_aliases.find(m_hostname)==site.m_aliases.end())
382 m_hostname=site.m_name;
384 if (!pfc->pFilterContext) {
385 pfc->pFilterContext = pfc->AllocMem(pfc, sizeof(context_t), NULL);
386 if (static_cast<context_t*>(pfc->pFilterContext)) {
387 static_cast<context_t*>(pfc->pFilterContext)->m_user = NULL;
388 static_cast<context_t*>(pfc->pFilterContext)->m_checked = false;
392 ~ShibTargetIsapiF() { }
394 const char* getScheme() const {
395 return m_scheme.c_str();
397 const char* getHostname() const {
398 return m_hostname.c_str();
400 int getPort() const {
403 const char* getMethod() const {
404 if (m_method.empty()) {
406 GetServerVariable(m_pfc,"REQUEST_METHOD",var,5,false);
410 return m_method.c_str();
412 string getContentType() const {
413 if (m_content_type.empty()) {
415 GetServerVariable(m_pfc,"CONTENT_TYPE",var,32,false);
417 m_content_type = var;
419 return m_content_type;
421 long getContentLength() const {
424 string getRemoteAddr() const {
425 if (m_remote_addr.empty()) {
427 GetServerVariable(m_pfc,"REMOTE_ADDR",var,16,false);
431 return m_remote_addr;
433 void log(SPLogLevel level, const string& msg) {
434 AbstractSPRequest::log(level,msg);
435 if (level >= SPError)
436 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
438 void clearHeader(const char* rawname, const char* cginame) {
439 if (g_checkSpoofing && m_pfc->pFilterContext && !static_cast<context_t*>(m_pfc->pFilterContext)->m_checked) {
440 if (m_allhttp.empty())
441 GetServerVariable(m_pfc,"ALL_HTTP",m_allhttp,4096);
442 if (strstr(m_allhttp, cginame))
443 throw opensaml::SecurityPolicyException("Attempt to spoof header ($1) was detected.", params(1, rawname));
445 string hdr(!strcmp(rawname,"REMOTE_USER") ? "remote-user" : rawname);
447 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), const_cast<char*>(g_unsetHeaderValue.c_str()));
449 void setHeader(const char* name, const char* value) {
452 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), const_cast<char*>(value));
454 string getHeader(const char* name) const {
458 GetHeader(m_pn, m_pfc, const_cast<char*>(hdr.c_str()), buf, 256, false);
461 void setRemoteUser(const char* user) {
462 setHeader("remote-user", user);
463 if (m_pfc->pFilterContext) {
465 static_cast<context_t*>(m_pfc->pFilterContext)->m_user = NULL;
466 else if (static_cast<context_t*>(m_pfc->pFilterContext)->m_user = (char*)m_pfc->AllocMem(m_pfc, sizeof(char) * (strlen(user) + 1), NULL))
467 strcpy(static_cast<context_t*>(m_pfc->pFilterContext)->m_user, user);
470 string getRemoteUser() const {
471 return getHeader("remote-user");
473 void setResponseHeader(const char* name, const char* value) {
476 m_headers.insert(make_pair(name,value));
478 m_headers.erase(name);
480 long sendResponse(istream& in, long status) {
481 string hdr = string("Connection: close\r\n");
482 for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
483 hdr += i->first + ": " + i->second + "\r\n";
485 const char* codestr="200 OK";
487 case XMLTOOLING_HTTP_STATUS_UNAUTHORIZED: codestr="401 Authorization Required"; break;
488 case XMLTOOLING_HTTP_STATUS_FORBIDDEN: codestr="403 Forbidden"; break;
489 case XMLTOOLING_HTTP_STATUS_NOTFOUND: codestr="404 Not Found"; break;
490 case XMLTOOLING_HTTP_STATUS_ERROR: codestr="500 Server Error"; break;
492 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, (void*)codestr, (DWORD)hdr.c_str(), 0);
496 DWORD resplen = in.gcount();
497 m_pfc->WriteClient(m_pfc, buf, &resplen, 0);
499 return SF_STATUS_REQ_FINISHED;
501 long sendRedirect(const char* url) {
502 // XXX: Don't support the httpRedirect option, yet.
503 string hdr=string("Location: ") + url + "\r\n"
504 "Content-Type: text/html\r\n"
505 "Content-Length: 40\r\n"
506 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
507 "Cache-Control: private,no-store,no-cache\r\n";
508 for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
509 hdr += i->first + ": " + i->second + "\r\n";
511 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, "302 Please Wait", (DWORD)hdr.c_str(), 0);
512 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
514 m_pfc->WriteClient(m_pfc, (LPVOID)redmsg, &resplen, 0);
515 return SF_STATUS_REQ_FINISHED;
517 long returnDecline() {
518 return SF_STATUS_REQ_NEXT_NOTIFICATION;
521 return SF_STATUS_REQ_NEXT_NOTIFICATION;
524 const vector<string>& getClientCertificates() const {
528 // The filter never processes the POST, so stub these methods.
529 const char* getQueryString() const { throw IOException("getQueryString not implemented"); }
530 const char* getRequestBody() const { throw IOException("getRequestBody not implemented"); }
533 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const char* msg)
535 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
536 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
537 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)ctype,0);
538 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Filter Error</TITLE></HEAD><BODY>"
539 "<H1>Shibboleth Filter Error</H1>";
540 DWORD resplen=strlen(xmsg);
541 pfc->WriteClient(pfc,(LPVOID)xmsg,&resplen,0);
543 pfc->WriteClient(pfc,(LPVOID)msg,&resplen,0);
544 static const char* xmsg2="</BODY></HTML>";
545 resplen=strlen(xmsg2);
546 pfc->WriteClient(pfc,(LPVOID)xmsg2,&resplen,0);
547 return SF_STATUS_REQ_FINISHED;
550 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
552 // Is this a log notification?
553 if (notificationType==SF_NOTIFY_LOG) {
554 if (pfc->pFilterContext && static_cast<context_t*>(pfc->pFilterContext)->m_user)
555 ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=static_cast<context_t*>(pfc->pFilterContext)->m_user;
556 return SF_STATUS_REQ_NEXT_NOTIFICATION;
559 PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
562 // Determine web site number. This can't really fail, I don't think.
564 GetServerVariable(pfc,"INSTANCE_ID",buf,10);
566 // Match site instance to host name, skip if no match.
567 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
568 if (map_i==g_Sites.end())
569 return SF_STATUS_REQ_NEXT_NOTIFICATION;
571 ostringstream threadid;
572 threadid << "[" << getpid() << "] isapi_shib" << '\0';
573 xmltooling::NDC ndc(threadid.str().c_str());
575 ShibTargetIsapiF stf(pfc, pn, map_i->second);
577 // "false" because we don't override the Shib settings
578 pair<bool,long> res = stf.getServiceProvider().doAuthentication(stf);
579 if (pfc->pFilterContext)
580 static_cast<context_t*>(pfc->pFilterContext)->m_checked = true;
581 if (res.first) return res.second;
583 // "false" because we don't override the Shib settings
584 res = stf.getServiceProvider().doExport(stf);
585 if (res.first) return res.second;
587 res = stf.getServiceProvider().doAuthorization(stf);
588 if (res.first) return res.second;
590 return SF_STATUS_REQ_NEXT_NOTIFICATION;
593 return WriteClientError(pfc,"Out of Memory");
596 if (e==ERROR_NO_DATA)
597 return WriteClientError(pfc,"A required variable or header was empty.");
599 return WriteClientError(pfc,"Shibboleth Filter detected unexpected IIS error.");
601 catch (exception& e) {
602 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
603 return WriteClientError(pfc,"Shibboleth Filter caught an exception, check Event Log for details.");
606 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Shibboleth Filter threw an unknown exception.");
608 return WriteClientError(pfc,"Shibboleth Filter threw an unknown exception.");
612 return WriteClientError(pfc,"Shibboleth Filter reached unreachable code, save my walrus!");
616 /****************************************************************************/
619 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* msg)
621 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
622 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
623 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
624 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Error</TITLE></HEAD><BODY><H1>Shibboleth Error</H1>";
625 DWORD resplen=strlen(xmsg);
626 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg,&resplen,HSE_IO_SYNC);
628 lpECB->WriteClient(lpECB->ConnID,(LPVOID)msg,&resplen,HSE_IO_SYNC);
629 static const char* xmsg2="</BODY></HTML>";
630 resplen=strlen(xmsg2);
631 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg2,&resplen,HSE_IO_SYNC);
632 return HSE_STATUS_SUCCESS;
636 class ShibTargetIsapiE : public AbstractSPRequest
638 LPEXTENSION_CONTROL_BLOCK m_lpECB;
639 multimap<string,string> m_headers;
640 mutable vector<string> m_certs;
641 mutable string m_body;
642 mutable bool m_gotBody;
644 string m_scheme,m_hostname,m_uri;
645 mutable string m_remote_addr,m_remote_user;
648 ShibTargetIsapiE(LPEXTENSION_CONTROL_BLOCK lpECB, const site_t& site)
649 : AbstractSPRequest(SHIBSP_LOGCAT".ISAPI"), m_lpECB(lpECB), m_gotBody(false) {
651 GetServerVariable(lpECB,"HTTPS",ssl,5);
652 bool SSL=(ssl=="on" || ssl=="ON");
654 // Scheme may come from site def or be derived from IIS.
655 m_scheme=site.m_scheme;
656 if (m_scheme.empty() || !g_bNormalizeRequest)
657 m_scheme = SSL ? "https" : "http";
659 // URL path always come from IIS.
661 GetServerVariable(lpECB,"URL",url,255);
663 // Port may come from IIS or from site def.
665 if (!g_bNormalizeRequest || (SSL && site.m_sslport.empty()) || (!SSL && site.m_port.empty()))
666 GetServerVariable(lpECB,"SERVER_PORT",port,10);
668 strncpy(port,site.m_sslport.c_str(),10);
669 static_cast<char*>(port)[10]=0;
672 strncpy(port,site.m_port.c_str(),10);
673 static_cast<char*>(port)[10]=0;
678 GetServerVariable(lpECB, "SERVER_NAME", var, 32);
680 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
682 if (site.m_name!=m_hostname && site.m_aliases.find(m_hostname)==site.m_aliases.end())
683 m_hostname=site.m_name;
686 * IIS screws us over on PATH_INFO (the hits keep on coming). We need to figure out if
687 * the server is set up for proper PATH_INFO handling, or "IIS sucks rabid weasels mode",
688 * which is the default. No perfect way to tell, but we can take a good guess by checking
689 * whether the URL is a substring of the PATH_INFO:
691 * e.g. for /Shibboleth.sso/SAML/POST
693 * Bad mode (default):
694 * URL: /Shibboleth.sso
695 * PathInfo: /Shibboleth.sso/SAML/POST
698 * URL: /Shibboleth.sso
699 * PathInfo: /SAML/POST
704 // Clearly we're only in bad mode if path info exists at all.
705 if (lpECB->lpszPathInfo && *(lpECB->lpszPathInfo)) {
706 if (strstr(lpECB->lpszPathInfo,url))
707 // Pretty good chance we're in bad mode, unless the PathInfo repeats the path itself.
708 uri = lpECB->lpszPathInfo;
711 uri += lpECB->lpszPathInfo;
718 // For consistency with Apache, let's add the query string.
719 if (lpECB->lpszQueryString && *(lpECB->lpszQueryString)) {
721 uri += lpECB->lpszQueryString;
724 setRequestURI(uri.c_str());
726 ~ShibTargetIsapiE() { }
728 const char* getScheme() const {
729 return m_scheme.c_str();
731 const char* getHostname() const {
732 return m_hostname.c_str();
734 int getPort() const {
737 const char* getMethod() const {
738 return m_lpECB->lpszMethod;
740 string getContentType() const {
741 return m_lpECB->lpszContentType ? m_lpECB->lpszContentType : "";
743 long getContentLength() const {
744 return m_lpECB->cbTotalBytes;
746 string getRemoteUser() const {
747 if (m_remote_user.empty()) {
749 GetServerVariable(m_lpECB, "REMOTE_USER", var, 32, false);
753 return m_remote_user;
755 string getRemoteAddr() const {
756 if (m_remote_addr.empty()) {
758 GetServerVariable(m_lpECB, "REMOTE_ADDR", var, 16, false);
762 return m_remote_addr;
764 void log(SPLogLevel level, const string& msg) const {
765 AbstractSPRequest::log(level,msg);
766 if (level >= SPError)
767 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
769 string getHeader(const char* name) const {
771 for (; *name; ++name) {
775 hdr += toupper(*name);
778 GetServerVariable(m_lpECB, const_cast<char*>(hdr.c_str()), buf, 128, false);
779 return buf.empty() ? "" : buf;
781 void setResponseHeader(const char* name, const char* value) {
784 m_headers.insert(make_pair(name,value));
786 m_headers.erase(name);
788 const char* getQueryString() const {
789 return m_lpECB->lpszQueryString;
791 const char* getRequestBody() const {
793 return m_body.c_str();
794 if (m_lpECB->cbTotalBytes > 1024*1024) // 1MB?
795 throw opensaml::SecurityPolicyException("Size of request body exceeded 1M size limit.");
796 else if (m_lpECB->cbTotalBytes > m_lpECB->cbAvailable) {
799 DWORD datalen=m_lpECB->cbTotalBytes;
802 BOOL ret = m_lpECB->ReadClient(m_lpECB->ConnID, buf, &buflen);
804 throw IOException("Error reading request body from browser.");
805 m_body.append(buf, buflen);
809 else if (m_lpECB->cbAvailable) {
811 m_body.assign(reinterpret_cast<char*>(m_lpECB->lpbData),m_lpECB->cbAvailable);
813 return m_body.c_str();
815 long sendResponse(istream& in, long status) {
816 string hdr = string("Connection: close\r\n");
817 for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
818 hdr += i->first + ": " + i->second + "\r\n";
820 const char* codestr="200 OK";
822 case XMLTOOLING_HTTP_STATUS_UNAUTHORIZED: codestr="401 Authorization Required"; break;
823 case XMLTOOLING_HTTP_STATUS_FORBIDDEN: codestr="403 Forbidden"; break;
824 case XMLTOOLING_HTTP_STATUS_NOTFOUND: codestr="404 Not Found"; break;
825 case XMLTOOLING_HTTP_STATUS_ERROR: codestr="500 Server Error"; break;
827 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, (void*)codestr, 0, (LPDWORD)hdr.c_str());
831 DWORD resplen = in.gcount();
832 m_lpECB->WriteClient(m_lpECB->ConnID, buf, &resplen, HSE_IO_SYNC);
834 return HSE_STATUS_SUCCESS;
836 long sendRedirect(const char* url) {
837 string hdr=string("Location: ") + url + "\r\n"
838 "Content-Type: text/html\r\n"
839 "Content-Length: 40\r\n"
840 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
841 "Cache-Control: private,no-store,no-cache\r\n";
842 for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
843 hdr += i->first + ": " + i->second + "\r\n";
845 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, "302 Moved", 0, (LPDWORD)hdr.c_str());
846 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
848 m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)redmsg, &resplen, HSE_IO_SYNC);
849 return HSE_STATUS_SUCCESS;
851 // Decline happens in the POST processor if this isn't the shire url
852 // Note that it can also happen with HTAccess, but we don't support that, yet.
853 long returnDecline() {
854 return WriteClientError(
856 "ISAPI extension can only be invoked to process Shibboleth protocol requests."
857 "Make sure the mapped file extension doesn't match actual content."
861 return HSE_STATUS_SUCCESS;
864 const vector<string>& getClientCertificates() const {
865 if (m_certs.empty()) {
866 char CertificateBuf[8192];
867 CERT_CONTEXT_EX ccex;
868 ccex.cbAllocated = sizeof(CertificateBuf);
869 ccex.CertContext.pbCertEncoded = (BYTE*)CertificateBuf;
870 DWORD dwSize = sizeof(ccex);
872 if (m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_GET_CERT_INFO_EX, (LPVOID)&ccex, (LPDWORD)dwSize, NULL)) {
873 if (ccex.CertContext.cbCertEncoded) {
875 XMLByte* serialized = Base64::encode(reinterpret_cast<XMLByte*>(CertificateBuf), ccex.CertContext.cbCertEncoded, &outlen);
876 m_certs.push_back(reinterpret_cast<char*>(serialized));
877 XMLString::release(&serialized);
884 // Not used in the extension.
885 void clearHeader(const char* rawname, const char* cginame) { throw runtime_error("clearHeader not implemented"); }
886 void setHeader(const char* name, const char* value) { throw runtime_error("setHeader not implemented"); }
887 void setRemoteUser(const char* user) { throw runtime_error("setRemoteUser not implemented"); }
890 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
893 ostringstream threadid;
894 threadid << "[" << getpid() << "] isapi_shib_extension" << '\0';
895 xmltooling::NDC ndc(threadid.str().c_str());
897 // Determine web site number. This can't really fail, I don't think.
899 GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
901 // Match site instance to host name, skip if no match.
902 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
903 if (map_i==g_Sites.end())
904 return WriteClientError(lpECB, "Shibboleth Extension not configured for web site (check <ISAPI> mappings in configuration).");
906 ShibTargetIsapiE ste(lpECB, map_i->second);
907 pair<bool,long> res = ste.getServiceProvider().doHandler(ste);
908 if (res.first) return res.second;
910 return WriteClientError(lpECB, "Shibboleth Extension failed to process request");
914 return WriteClientError(lpECB,"Out of Memory");
917 if (e==ERROR_NO_DATA)
918 return WriteClientError(lpECB,"A required variable or header was empty.");
920 return WriteClientError(lpECB,"Server detected unexpected IIS error.");
922 catch (exception& e) {
923 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
924 return WriteClientError(lpECB,"Shibboleth Extension caught an exception, check Event Log for details.");
927 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Shibboleth Extension threw an unknown exception.");
929 return WriteClientError(lpECB,"Shibboleth Extension threw an unknown exception.");
933 // If we get here we've got an error.
934 return HSE_STATUS_ERROR;