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.");
173 LPCSTR config=getenv("SHIBSP_CONFIG");
175 config=SHIBSP_CONFIG;
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 implementation-specifics and 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 props = props->getPropertySet("ISAPI");
212 flag = props->getBool("normalizeRequest");
213 g_bNormalizeRequest = !flag.first || flag.second;
214 const DOMElement* child = XMLHelper::getFirstChildElement(props->getElement(),Site);
216 auto_ptr_char id(child->getAttributeNS(NULL,id));
218 g_Sites.insert(pair<string,site_t>(id.get(),site_t(child)));
219 child=XMLHelper::getNextSiblingElement(child,Site);
224 pVer->dwFilterVersion=HTTP_FILTER_REVISION;
225 strncpy(pVer->lpszFilterDesc,"Shibboleth ISAPI Filter",SF_MAX_FILTER_DESC_LEN);
226 pVer->dwFlags=(SF_NOTIFY_ORDER_HIGH |
227 SF_NOTIFY_SECURE_PORT |
228 SF_NOTIFY_NONSECURE_PORT |
229 SF_NOTIFY_PREPROC_HEADERS |
231 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter initialized...");
235 extern "C" BOOL WINAPI TerminateFilter(DWORD)
240 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter shut down...");
244 /* Next up, some suck-free versions of various APIs.
246 You DON'T require people to guess the buffer size and THEN tell them the right size.
247 Returning an LPCSTR is apparently way beyond their ken. Not to mention the fact that
248 constant strings aren't typed as such, making it just that much harder. These versions
249 are now updated to use a special growable buffer object, modeled after the standard
250 string class. The standard string won't work because they left out the option to
251 pre-allocate a non-constant buffer.
257 dynabuf() { bufptr=NULL; buflen=0; }
258 dynabuf(size_t s) { bufptr=new char[buflen=s]; *bufptr=0; }
259 ~dynabuf() { delete[] bufptr; }
260 size_t length() const { return bufptr ? strlen(bufptr) : 0; }
261 size_t size() const { return buflen; }
262 bool empty() const { return length()==0; }
263 void reserve(size_t s, bool keep=false);
264 void erase() { if (bufptr) memset(bufptr,0,buflen); }
265 operator char*() { return bufptr; }
266 bool operator ==(const char* s) const;
267 bool operator !=(const char* s) const { return !(*this==s); }
273 void dynabuf::reserve(size_t s, bool keep)
280 p[buflen]=bufptr[buflen];
286 bool dynabuf::operator==(const char* s) const
288 if (buflen==NULL || s==NULL)
289 return (buflen==NULL && s==NULL);
291 return strcmp(bufptr,s)==0;
294 void GetServerVariable(PHTTP_FILTER_CONTEXT pfc, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
300 while (!pfc->GetServerVariable(pfc,lpszVariable,s,&size)) {
301 // Grumble. Check the error.
302 DWORD e=GetLastError();
303 if (e==ERROR_INSUFFICIENT_BUFFER)
308 if (bRequired && s.empty())
312 void GetServerVariable(LPEXTENSION_CONTROL_BLOCK lpECB, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
318 while (!lpECB->GetServerVariable(lpECB->ConnID,lpszVariable,s,&size)) {
319 // Grumble. Check the error.
320 DWORD e=GetLastError();
321 if (e==ERROR_INSUFFICIENT_BUFFER)
326 if (bRequired && s.empty())
330 void GetHeader(PHTTP_FILTER_PREPROC_HEADERS pn, PHTTP_FILTER_CONTEXT pfc,
331 LPSTR lpszName, dynabuf& s, DWORD size=80, bool bRequired=true)
337 while (!pn->GetHeader(pfc,lpszName,s,&size)) {
338 // Grumble. Check the error.
339 DWORD e=GetLastError();
340 if (e==ERROR_INSUFFICIENT_BUFFER)
345 if (bRequired && s.empty())
349 /****************************************************************************/
352 class ShibTargetIsapiF : public AbstractSPRequest
354 PHTTP_FILTER_CONTEXT m_pfc;
355 PHTTP_FILTER_PREPROC_HEADERS m_pn;
356 multimap<string,string> m_headers;
358 string m_scheme,m_hostname;
359 mutable string m_remote_addr,m_content_type,m_method;
363 ShibTargetIsapiF(PHTTP_FILTER_CONTEXT pfc, PHTTP_FILTER_PREPROC_HEADERS pn, const site_t& site)
364 : AbstractSPRequest(SHIBSP_LOGCAT".ISAPI"), 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 if (!pfc->pFilterContext) {
396 pfc->pFilterContext = pfc->AllocMem(pfc, sizeof(context_t), NULL);
397 if (static_cast<context_t*>(pfc->pFilterContext)) {
398 static_cast<context_t*>(pfc->pFilterContext)->m_user = NULL;
399 static_cast<context_t*>(pfc->pFilterContext)->m_checked = false;
403 ~ShibTargetIsapiF() { }
405 const char* getScheme() const {
406 return m_scheme.c_str();
408 const char* getHostname() const {
409 return m_hostname.c_str();
411 int getPort() const {
414 const char* getMethod() const {
415 if (m_method.empty()) {
417 GetServerVariable(m_pfc,"REQUEST_METHOD",var,5,false);
421 return m_method.c_str();
423 string getContentType() const {
424 if (m_content_type.empty()) {
426 GetServerVariable(m_pfc,"CONTENT_TYPE",var,32,false);
428 m_content_type = var;
430 return m_content_type;
432 long getContentLength() const {
435 string getRemoteAddr() const {
436 if (m_remote_addr.empty()) {
438 GetServerVariable(m_pfc,"REMOTE_ADDR",var,16,false);
442 return m_remote_addr;
444 void log(SPLogLevel level, const string& msg) {
445 AbstractSPRequest::log(level,msg);
446 if (level >= SPError)
447 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
449 void clearHeader(const char* rawname, const char* cginame) {
450 if (g_checkSpoofing && m_pfc->pFilterContext && !static_cast<context_t*>(m_pfc->pFilterContext)->m_checked) {
451 if (m_allhttp.empty())
452 GetServerVariable(m_pfc,"ALL_HTTP",m_allhttp,4096);
453 if (strstr(m_allhttp, cginame))
454 throw opensaml::SecurityPolicyException("Attempt to spoof header ($1) was detected.", params(1, rawname));
456 string hdr(!strcmp(rawname,"REMOTE_USER") ? "remote-user" : rawname);
458 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), const_cast<char*>(g_unsetHeaderValue.c_str()));
460 void setHeader(const char* name, const char* value) {
463 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), const_cast<char*>(value));
465 string getHeader(const char* name) const {
469 GetHeader(m_pn, m_pfc, const_cast<char*>(hdr.c_str()), buf, 256, false);
472 void setRemoteUser(const char* user) {
473 setHeader("remote-user", user);
474 if (m_pfc->pFilterContext) {
476 static_cast<context_t*>(m_pfc->pFilterContext)->m_user = NULL;
477 else if (static_cast<context_t*>(m_pfc->pFilterContext)->m_user = (char*)m_pfc->AllocMem(m_pfc, sizeof(char) * (strlen(user) + 1), NULL))
478 strcpy(static_cast<context_t*>(m_pfc->pFilterContext)->m_user, user);
481 string getRemoteUser() const {
482 return getHeader("remote-user");
484 void setResponseHeader(const char* name, const char* value) {
487 m_headers.insert(make_pair(name,value));
489 m_headers.erase(name);
491 long sendResponse(istream& in, long status) {
492 string hdr = string("Connection: close\r\n");
493 for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
494 hdr += i->first + ": " + i->second + "\r\n";
496 const char* codestr="200 OK";
498 case XMLTOOLING_HTTP_STATUS_UNAUTHORIZED: codestr="401 Authorization Required"; break;
499 case XMLTOOLING_HTTP_STATUS_FORBIDDEN: codestr="403 Forbidden"; break;
500 case XMLTOOLING_HTTP_STATUS_NOTFOUND: codestr="404 Not Found"; break;
501 case XMLTOOLING_HTTP_STATUS_ERROR: codestr="500 Server Error"; break;
503 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, (void*)codestr, (DWORD)hdr.c_str(), 0);
507 DWORD resplen = in.gcount();
508 m_pfc->WriteClient(m_pfc, buf, &resplen, 0);
510 return SF_STATUS_REQ_FINISHED;
512 long sendRedirect(const char* url) {
513 // XXX: Don't support the httpRedirect option, yet.
514 string hdr=string("Location: ") + url + "\r\n"
515 "Content-Type: text/html\r\n"
516 "Content-Length: 40\r\n"
517 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
518 "Cache-Control: private,no-store,no-cache\r\n";
519 for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
520 hdr += i->first + ": " + i->second + "\r\n";
522 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, "302 Please Wait", (DWORD)hdr.c_str(), 0);
523 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
525 m_pfc->WriteClient(m_pfc, (LPVOID)redmsg, &resplen, 0);
526 return SF_STATUS_REQ_FINISHED;
528 long returnDecline() {
529 return SF_STATUS_REQ_NEXT_NOTIFICATION;
532 return SF_STATUS_REQ_NEXT_NOTIFICATION;
535 const vector<string>& getClientCertificates() const {
539 // The filter never processes the POST, so stub these methods.
540 const char* getQueryString() const { throw IOException("getQueryString not implemented"); }
541 const char* getRequestBody() const { throw IOException("getRequestBody not implemented"); }
544 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const char* msg)
546 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
547 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
548 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)ctype,0);
549 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Filter Error</TITLE></HEAD><BODY>"
550 "<H1>Shibboleth Filter Error</H1>";
551 DWORD resplen=strlen(xmsg);
552 pfc->WriteClient(pfc,(LPVOID)xmsg,&resplen,0);
554 pfc->WriteClient(pfc,(LPVOID)msg,&resplen,0);
555 static const char* xmsg2="</BODY></HTML>";
556 resplen=strlen(xmsg2);
557 pfc->WriteClient(pfc,(LPVOID)xmsg2,&resplen,0);
558 return SF_STATUS_REQ_FINISHED;
561 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
563 // Is this a log notification?
564 if (notificationType==SF_NOTIFY_LOG) {
565 if (pfc->pFilterContext && static_cast<context_t*>(pfc->pFilterContext)->m_user)
566 ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=static_cast<context_t*>(pfc->pFilterContext)->m_user;
567 return SF_STATUS_REQ_NEXT_NOTIFICATION;
570 PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
573 // Determine web site number. This can't really fail, I don't think.
575 GetServerVariable(pfc,"INSTANCE_ID",buf,10);
577 // Match site instance to host name, skip if no match.
578 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
579 if (map_i==g_Sites.end())
580 return SF_STATUS_REQ_NEXT_NOTIFICATION;
582 ostringstream threadid;
583 threadid << "[" << getpid() << "] isapi_shib" << '\0';
584 xmltooling::NDC ndc(threadid.str().c_str());
586 ShibTargetIsapiF stf(pfc, pn, map_i->second);
588 // "false" because we don't override the Shib settings
589 pair<bool,long> res = stf.getServiceProvider().doAuthentication(stf);
590 if (pfc->pFilterContext)
591 static_cast<context_t*>(pfc->pFilterContext)->m_checked = true;
592 if (res.first) return res.second;
594 // "false" because we don't override the Shib settings
595 res = stf.getServiceProvider().doExport(stf);
596 if (res.first) return res.second;
598 res = stf.getServiceProvider().doAuthorization(stf);
599 if (res.first) return res.second;
601 return SF_STATUS_REQ_NEXT_NOTIFICATION;
604 return WriteClientError(pfc,"Out of Memory");
607 if (e==ERROR_NO_DATA)
608 return WriteClientError(pfc,"A required variable or header was empty.");
610 return WriteClientError(pfc,"Shibboleth Filter detected unexpected IIS error.");
612 catch (exception& e) {
613 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
614 return WriteClientError(pfc,"Shibboleth Filter caught an exception, check Event Log for details.");
617 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Shibboleth Filter threw an unknown exception.");
619 return WriteClientError(pfc,"Shibboleth Filter threw an unknown exception.");
623 return WriteClientError(pfc,"Shibboleth Filter reached unreachable code, save my walrus!");
627 /****************************************************************************/
630 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* msg)
632 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
633 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
634 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
635 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Error</TITLE></HEAD><BODY><H1>Shibboleth Error</H1>";
636 DWORD resplen=strlen(xmsg);
637 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg,&resplen,HSE_IO_SYNC);
639 lpECB->WriteClient(lpECB->ConnID,(LPVOID)msg,&resplen,HSE_IO_SYNC);
640 static const char* xmsg2="</BODY></HTML>";
641 resplen=strlen(xmsg2);
642 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg2,&resplen,HSE_IO_SYNC);
643 return HSE_STATUS_SUCCESS;
647 class ShibTargetIsapiE : public AbstractSPRequest
649 LPEXTENSION_CONTROL_BLOCK m_lpECB;
650 multimap<string,string> m_headers;
651 mutable vector<string> m_certs;
652 mutable string m_body;
653 mutable bool m_gotBody;
655 string m_scheme,m_hostname,m_uri;
656 mutable string m_remote_addr,m_remote_user;
659 ShibTargetIsapiE(LPEXTENSION_CONTROL_BLOCK lpECB, const site_t& site)
660 : AbstractSPRequest(SHIBSP_LOGCAT".ISAPI"), m_lpECB(lpECB), m_gotBody(false) {
662 GetServerVariable(lpECB,"HTTPS",ssl,5);
663 bool SSL=(ssl=="on" || ssl=="ON");
665 // Scheme may come from site def or be derived from IIS.
666 m_scheme=site.m_scheme;
667 if (m_scheme.empty() || !g_bNormalizeRequest)
668 m_scheme = SSL ? "https" : "http";
670 // URL path always come from IIS.
672 GetServerVariable(lpECB,"URL",url,255);
674 // Port may come from IIS or from site def.
676 if (!g_bNormalizeRequest || (SSL && site.m_sslport.empty()) || (!SSL && site.m_port.empty()))
677 GetServerVariable(lpECB,"SERVER_PORT",port,10);
679 strncpy(port,site.m_sslport.c_str(),10);
680 static_cast<char*>(port)[10]=0;
683 strncpy(port,site.m_port.c_str(),10);
684 static_cast<char*>(port)[10]=0;
689 GetServerVariable(lpECB, "SERVER_NAME", var, 32);
691 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
693 if (site.m_name!=m_hostname && site.m_aliases.find(m_hostname)==site.m_aliases.end())
694 m_hostname=site.m_name;
697 * IIS screws us over on PATH_INFO (the hits keep on coming). We need to figure out if
698 * the server is set up for proper PATH_INFO handling, or "IIS sucks rabid weasels mode",
699 * which is the default. No perfect way to tell, but we can take a good guess by checking
700 * whether the URL is a substring of the PATH_INFO:
702 * e.g. for /Shibboleth.sso/SAML/POST
704 * Bad mode (default):
705 * URL: /Shibboleth.sso
706 * PathInfo: /Shibboleth.sso/SAML/POST
709 * URL: /Shibboleth.sso
710 * PathInfo: /SAML/POST
715 // Clearly we're only in bad mode if path info exists at all.
716 if (lpECB->lpszPathInfo && *(lpECB->lpszPathInfo)) {
717 if (strstr(lpECB->lpszPathInfo,url))
718 // Pretty good chance we're in bad mode, unless the PathInfo repeats the path itself.
719 uri = lpECB->lpszPathInfo;
722 uri += lpECB->lpszPathInfo;
729 // For consistency with Apache, let's add the query string.
730 if (lpECB->lpszQueryString && *(lpECB->lpszQueryString)) {
732 uri += lpECB->lpszQueryString;
735 setRequestURI(uri.c_str());
737 ~ShibTargetIsapiE() { }
739 const char* getScheme() const {
740 return m_scheme.c_str();
742 const char* getHostname() const {
743 return m_hostname.c_str();
745 int getPort() const {
748 const char* getMethod() const {
749 return m_lpECB->lpszMethod;
751 string getContentType() const {
752 return m_lpECB->lpszContentType ? m_lpECB->lpszContentType : "";
754 long getContentLength() const {
755 return m_lpECB->cbTotalBytes;
757 string getRemoteUser() const {
758 if (m_remote_user.empty()) {
760 GetServerVariable(m_lpECB, "REMOTE_USER", var, 32, false);
764 return m_remote_user;
766 string getRemoteAddr() const {
767 if (m_remote_addr.empty()) {
769 GetServerVariable(m_lpECB, "REMOTE_ADDR", var, 16, false);
773 return m_remote_addr;
775 void log(SPLogLevel level, const string& msg) const {
776 AbstractSPRequest::log(level,msg);
777 if (level >= SPError)
778 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
780 string getHeader(const char* name) const {
782 for (; *name; ++name) {
786 hdr += toupper(*name);
789 GetServerVariable(m_lpECB, const_cast<char*>(hdr.c_str()), buf, 128, false);
790 return buf.empty() ? "" : buf;
792 void setResponseHeader(const char* name, const char* value) {
795 m_headers.insert(make_pair(name,value));
797 m_headers.erase(name);
799 const char* getQueryString() const {
800 return m_lpECB->lpszQueryString;
802 const char* getRequestBody() const {
804 return m_body.c_str();
805 if (m_lpECB->cbTotalBytes > 1024*1024) // 1MB?
806 throw opensaml::SecurityPolicyException("Size of request body exceeded 1M size limit.");
807 else if (m_lpECB->cbTotalBytes > m_lpECB->cbAvailable) {
810 DWORD datalen=m_lpECB->cbTotalBytes;
813 BOOL ret = m_lpECB->ReadClient(m_lpECB->ConnID, buf, &buflen);
815 throw IOException("Error 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 XMLString::release(&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;