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,m_uri;
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* getRequestURI() const {
407 return m_uri.c_str();
409 const char* getMethod() const {
410 if (m_method.empty()) {
412 GetServerVariable(m_pfc,"REQUEST_METHOD",var,5,false);
416 return m_method.c_str();
418 string getContentType() const {
419 if (m_content_type.empty()) {
421 GetServerVariable(m_pfc,"CONTENT_TYPE",var,32,false);
423 m_content_type = var;
425 return m_content_type;
427 long getContentLength() const {
430 string getRemoteAddr() const {
431 if (m_remote_addr.empty()) {
433 GetServerVariable(m_pfc,"REMOTE_ADDR",var,16,false);
437 return m_remote_addr;
439 void log(SPLogLevel level, const string& msg) {
440 AbstractSPRequest::log(level,msg);
441 if (level >= SPError)
442 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
444 void clearHeader(const char* rawname, const char* cginame) {
445 if (g_checkSpoofing) {
446 if (m_allhttp.empty())
447 GetServerVariable(m_pfc,"ALL_HTTP",m_allhttp,4096);
448 if (strstr(m_allhttp, cginame))
449 throw opensaml::SecurityPolicyException("Attempt to spoof header ($1) was detected.", params(1, rawname));
451 string hdr(!strcmp(rawname,"REMOTE_USER") ? "remote-user" : rawname);
453 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), const_cast<char*>(g_unsetHeaderValue.c_str()));
455 void setHeader(const char* name, const char* value) {
458 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), const_cast<char*>(value));
460 string getHeader(const char* name) const {
464 GetHeader(m_pn, m_pfc, const_cast<char*>(hdr.c_str()), buf, 256, false);
467 void setRemoteUser(const char* user) {
468 setHeader("remote-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)
555 if (pfc->pFilterContext)
556 ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=static_cast<LPCSTR>(pfc->pFilterContext);
557 return SF_STATUS_REQ_NEXT_NOTIFICATION;
560 PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
563 // Determine web site number. This can't really fail, I don't think.
565 GetServerVariable(pfc,"INSTANCE_ID",buf,10);
567 // Match site instance to host name, skip if no match.
568 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
569 if (map_i==g_Sites.end())
570 return SF_STATUS_REQ_NEXT_NOTIFICATION;
572 ostringstream threadid;
573 threadid << "[" << getpid() << "] isapi_shib" << '\0';
574 xmltooling::NDC ndc(threadid.str().c_str());
576 ShibTargetIsapiF stf(pfc, pn, map_i->second);
578 // "false" because we don't override the Shib settings
579 pair<bool,long> res = stf.getServiceProvider().doAuthentication(stf);
580 if (res.first) return res.second;
582 // "false" because we don't override the Shib settings
583 res = stf.getServiceProvider().doExport(stf);
584 if (res.first) return res.second;
586 res = stf.getServiceProvider().doAuthorization(stf);
587 if (res.first) return res.second;
589 return SF_STATUS_REQ_NEXT_NOTIFICATION;
592 return WriteClientError(pfc,"Out of Memory");
595 if (e==ERROR_NO_DATA)
596 return WriteClientError(pfc,"A required variable or header was empty.");
598 return WriteClientError(pfc,"Shibboleth Filter detected unexpected IIS error.");
600 catch (exception& e) {
601 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
602 return WriteClientError(pfc,"Shibboleth Filter caught an exception, check Event Log for details.");
605 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Shibboleth Filter threw an unknown exception.");
607 return WriteClientError(pfc,"Shibboleth Filter threw an unknown exception.");
611 return WriteClientError(pfc,"Shibboleth Filter reached unreachable code, save my walrus!");
615 /****************************************************************************/
618 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* msg)
620 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
621 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
622 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
623 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Error</TITLE></HEAD><BODY><H1>Shibboleth Error</H1>";
624 DWORD resplen=strlen(xmsg);
625 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg,&resplen,HSE_IO_SYNC);
627 lpECB->WriteClient(lpECB->ConnID,(LPVOID)msg,&resplen,HSE_IO_SYNC);
628 static const char* xmsg2="</BODY></HTML>";
629 resplen=strlen(xmsg2);
630 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg2,&resplen,HSE_IO_SYNC);
631 return HSE_STATUS_SUCCESS;
635 class ShibTargetIsapiE : public AbstractSPRequest
637 LPEXTENSION_CONTROL_BLOCK m_lpECB;
638 multimap<string,string> m_headers;
639 mutable vector<string> m_certs;
640 mutable string m_body;
641 mutable bool m_gotBody;
643 string m_scheme,m_hostname,m_uri;
644 mutable string m_remote_addr,m_remote_user;
647 ShibTargetIsapiE(LPEXTENSION_CONTROL_BLOCK lpECB, const site_t& site) : m_lpECB(lpECB), m_gotBody(false) {
649 GetServerVariable(lpECB,"HTTPS",ssl,5);
650 bool SSL=(ssl=="on" || ssl=="ON");
652 // Scheme may come from site def or be derived from IIS.
653 m_scheme=site.m_scheme;
654 if (m_scheme.empty() || !g_bNormalizeRequest)
655 m_scheme = SSL ? "https" : "http";
657 // URL path always come from IIS.
659 GetServerVariable(lpECB,"URL",url,255);
661 // Port may come from IIS or from site def.
663 if (!g_bNormalizeRequest || (SSL && site.m_sslport.empty()) || (!SSL && site.m_port.empty()))
664 GetServerVariable(lpECB,"SERVER_PORT",port,10);
666 strncpy(port,site.m_sslport.c_str(),10);
667 static_cast<char*>(port)[10]=0;
670 strncpy(port,site.m_port.c_str(),10);
671 static_cast<char*>(port)[10]=0;
676 GetServerVariable(lpECB, "SERVER_NAME", var, 32);
678 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
680 if (site.m_name!=m_hostname && site.m_aliases.find(m_hostname)==site.m_aliases.end())
681 m_hostname=site.m_name;
684 * IIS screws us over on PATH_INFO (the hits keep on coming). We need to figure out if
685 * the server is set up for proper PATH_INFO handling, or "IIS sucks rabid weasels mode",
686 * which is the default. No perfect way to tell, but we can take a good guess by checking
687 * whether the URL is a substring of the PATH_INFO:
689 * e.g. for /Shibboleth.sso/SAML/POST
691 * Bad mode (default):
692 * URL: /Shibboleth.sso
693 * PathInfo: /Shibboleth.sso/SAML/POST
696 * URL: /Shibboleth.sso
697 * PathInfo: /SAML/POST
700 // Clearly we're only in bad mode if path info exists at all.
701 if (lpECB->lpszPathInfo && *(lpECB->lpszPathInfo)) {
702 if (strstr(lpECB->lpszPathInfo,url))
703 // Pretty good chance we're in bad mode, unless the PathInfo repeats the path itself.
704 m_uri = lpECB->lpszPathInfo;
707 m_uri += lpECB->lpszPathInfo;
714 // For consistency with Apache, let's add the query string.
715 if (lpECB->lpszQueryString && *(lpECB->lpszQueryString)) {
717 m_uri += lpECB->lpszQueryString;
720 ~ShibTargetIsapiE() { }
722 const char* getScheme() const {
723 return m_scheme.c_str();
725 const char* getHostname() const {
726 return m_hostname.c_str();
728 int getPort() const {
731 const char* getRequestURI() const {
732 return m_uri.c_str();
734 const char* getMethod() const {
735 return m_lpECB->lpszMethod;
737 string getContentType() const {
738 return m_lpECB->lpszContentType ? m_lpECB->lpszContentType : "";
740 long getContentLength() const {
741 return m_lpECB->cbTotalBytes;
743 string getRemoteUser() const {
744 if (m_remote_user.empty()) {
746 GetServerVariable(m_lpECB, "REMOTE_USER", var, 32, false);
750 return m_remote_user;
752 string getRemoteAddr() const {
753 if (m_remote_addr.empty()) {
755 GetServerVariable(m_lpECB, "REMOTE_ADDR", var, 16, false);
759 return m_remote_addr;
761 void log(SPLogLevel level, const string& msg) const {
762 AbstractSPRequest::log(level,msg);
763 if (level >= SPError)
764 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
766 string getHeader(const char* name) const {
768 for (; *name; ++name) {
772 hdr += toupper(*name);
775 GetServerVariable(m_lpECB, const_cast<char*>(hdr.c_str()), buf, 128, false);
776 return buf.empty() ? "" : buf;
778 void setResponseHeader(const char* name, const char* value) {
781 m_headers.insert(make_pair(name,value));
783 m_headers.erase(name);
785 const char* getQueryString() const {
786 return m_lpECB->lpszQueryString;
788 const char* getRequestBody() const {
790 return m_body.c_str();
791 if (m_lpECB->cbTotalBytes > 1024*1024) // 1MB?
792 throw opensaml::SecurityPolicyException("Size of request body exceeded 1M size limit.");
793 else if (m_lpECB->cbTotalBytes > m_lpECB->cbAvailable) {
796 DWORD datalen=m_lpECB->cbTotalBytes;
799 BOOL ret = m_lpECB->ReadClient(m_lpECB->ConnID, buf, &buflen);
801 throw IOException("Error reading request body from browser.");
802 m_body.append(buf, buflen);
806 else if (m_lpECB->cbAvailable) {
808 m_body.assign(reinterpret_cast<char*>(m_lpECB->lpbData),m_lpECB->cbAvailable);
810 return m_body.c_str();
812 long sendResponse(istream& in, long status) {
813 string hdr = string("Connection: close\r\n");
814 for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
815 hdr += i->first + ": " + i->second + "\r\n";
817 const char* codestr="200 OK";
819 case XMLTOOLING_HTTP_STATUS_UNAUTHORIZED: codestr="401 Authorization Required"; break;
820 case XMLTOOLING_HTTP_STATUS_FORBIDDEN: codestr="403 Forbidden"; break;
821 case XMLTOOLING_HTTP_STATUS_NOTFOUND: codestr="404 Not Found"; break;
822 case XMLTOOLING_HTTP_STATUS_ERROR: codestr="500 Server Error"; break;
824 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, (void*)codestr, 0, (LPDWORD)hdr.c_str());
828 DWORD resplen = in.gcount();
829 m_lpECB->WriteClient(m_lpECB->ConnID, buf, &resplen, HSE_IO_SYNC);
831 return HSE_STATUS_SUCCESS;
833 long sendRedirect(const char* url) {
834 string hdr=string("Location: ") + url + "\r\n"
835 "Content-Type: text/html\r\n"
836 "Content-Length: 40\r\n"
837 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
838 "Cache-Control: private,no-store,no-cache\r\n";
839 for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
840 hdr += i->first + ": " + i->second + "\r\n";
842 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, "302 Moved", 0, (LPDWORD)hdr.c_str());
843 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
845 m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)redmsg, &resplen, HSE_IO_SYNC);
846 return HSE_STATUS_SUCCESS;
848 // Decline happens in the POST processor if this isn't the shire url
849 // Note that it can also happen with HTAccess, but we don't support that, yet.
850 long returnDecline() {
851 return WriteClientError(
853 "ISAPI extension can only be invoked to process Shibboleth protocol requests."
854 "Make sure the mapped file extension doesn't match actual content."
858 return HSE_STATUS_SUCCESS;
861 const vector<string>& getClientCertificates() const {
862 if (m_certs.empty()) {
863 char CertificateBuf[8192];
864 CERT_CONTEXT_EX ccex;
865 ccex.cbAllocated = sizeof(CertificateBuf);
866 ccex.CertContext.pbCertEncoded = (BYTE*)CertificateBuf;
867 DWORD dwSize = sizeof(ccex);
869 if (m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_GET_CERT_INFO_EX, (LPVOID)&ccex, (LPDWORD)dwSize, NULL)) {
870 if (ccex.CertContext.cbCertEncoded) {
872 XMLByte* serialized = Base64::encode(reinterpret_cast<XMLByte*>(CertificateBuf), ccex.CertContext.cbCertEncoded, &outlen);
873 m_certs.push_back(reinterpret_cast<char*>(serialized));
874 XMLString::release(&serialized);
881 // Not used in the extension.
882 void clearHeader(const char* rawname, const char* cginame) { throw runtime_error("clearHeader not implemented"); }
883 void setHeader(const char* name, const char* value) { throw runtime_error("setHeader not implemented"); }
884 void setRemoteUser(const char* user) { throw runtime_error("setRemoteUser not implemented"); }
887 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
890 ostringstream threadid;
891 threadid << "[" << getpid() << "] isapi_shib_extension" << '\0';
892 xmltooling::NDC ndc(threadid.str().c_str());
894 // Determine web site number. This can't really fail, I don't think.
896 GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
898 // Match site instance to host name, skip if no match.
899 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
900 if (map_i==g_Sites.end())
901 return WriteClientError(lpECB, "Shibboleth Extension not configured for this web site.");
903 ShibTargetIsapiE ste(lpECB, map_i->second);
904 pair<bool,long> res = ste.getServiceProvider().doHandler(ste);
905 if (res.first) return res.second;
907 return WriteClientError(lpECB, "Shibboleth Extension failed to process request");
911 return WriteClientError(lpECB,"Out of Memory");
914 if (e==ERROR_NO_DATA)
915 return WriteClientError(lpECB,"A required variable or header was empty.");
917 return WriteClientError(lpECB,"Server detected unexpected IIS error.");
919 catch (exception& e) {
920 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
921 return WriteClientError(lpECB,"Shibboleth Extension caught an exception, check Event Log for details.");
924 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Shibboleth Extension threw an unknown exception.");
926 return WriteClientError(lpECB,"Shibboleth Extension threw an unknown exception.");
930 // If we get here we've got an error.
931 return HSE_STATUS_ERROR;