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
23 #include "config_win32.h"
25 #define _CRT_NONSTDC_NO_DEPRECATE 1
26 #define _CRT_SECURE_NO_DEPRECATE 1
28 #include <shibsp/AbstractSPRequest.h>
29 #include <shibsp/SPConfig.h>
30 #include <shibsp/ServiceProvider.h>
31 #include <xmltooling/unicode.h>
32 #include <xmltooling/XMLToolingConfig.h>
33 #include <xmltooling/util/NDC.h>
34 #include <xmltooling/util/XMLHelper.h>
35 #include <xercesc/util/XMLUniDefs.hpp>
45 using namespace shibsp;
46 using namespace xmltooling;
47 using namespace xercesc;
52 static const XMLCh path[] = UNICODE_LITERAL_4(p,a,t,h);
53 static const XMLCh name[] = UNICODE_LITERAL_4(n,a,m,e);
54 static const XMLCh port[] = UNICODE_LITERAL_4(p,o,r,t);
55 static const XMLCh sslport[] = UNICODE_LITERAL_7(s,s,l,p,o,r,t);
56 static const XMLCh scheme[] = UNICODE_LITERAL_6(s,c,h,e,m,e);
57 static const XMLCh id[] = UNICODE_LITERAL_2(i,d);
58 static const XMLCh Implementation[] = UNICODE_LITERAL_14(I,m,p,l,e,m,e,n,t,a,t,i,o,n);
59 static const XMLCh ISAPI[] = UNICODE_LITERAL_5(I,S,A,P,I);
60 static const XMLCh Alias[] = UNICODE_LITERAL_5(A,l,i,a,s);
61 static const XMLCh normalizeRequest[] = UNICODE_LITERAL_16(n,o,r,m,a,l,i,z,e,R,e,q,u,e,s,t);
62 static const XMLCh Site[] = UNICODE_LITERAL_4(S,i,t,e);
65 site_t(const DOMElement* e)
67 auto_ptr_char n(e->getAttributeNS(NULL,name));
68 auto_ptr_char s(e->getAttributeNS(NULL,scheme));
69 auto_ptr_char p(e->getAttributeNS(NULL,port));
70 auto_ptr_char p2(e->getAttributeNS(NULL,sslport));
71 if (n.get()) m_name=n.get();
72 if (s.get()) m_scheme=s.get();
73 if (p.get()) m_port=p.get();
74 if (p2.get()) m_sslport=p2.get();
75 e = XMLHelper::getFirstChildElement(e, Alias);
77 if (e->hasChildNodes()) {
78 auto_ptr_char alias(e->getFirstChild()->getNodeValue());
79 m_aliases.insert(alias.get());
81 e = XMLHelper::getNextSiblingElement(e, Alias);
84 string m_scheme,m_port,m_sslport,m_name;
85 set<string> m_aliases;
89 SPConfig* g_Config = NULL;
90 map<string,site_t> g_Sites;
91 bool g_bNormalizeRequest = true;
95 LPCSTR lpUNCServerName,
101 LPCSTR messages[] = {message, NULL};
103 HANDLE hElog = RegisterEventSource(lpUNCServerName, "Shibboleth ISAPI Filter");
104 BOOL res = ReportEvent(hElog, wType, 0, dwEventID, lpUserSid, 1, 0, messages, NULL);
105 return (DeregisterEventSource(hElog) && res);
108 extern "C" __declspec(dllexport) BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID)
110 if (fdwReason==DLL_PROCESS_ATTACH)
115 extern "C" BOOL WINAPI GetExtensionVersion(HSE_VERSION_INFO* pVer)
121 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
122 "Extension mode startup not possible, is the DLL loaded as a filter?");
126 pVer->dwExtensionVersion=HSE_VERSION;
127 strncpy(pVer->lpszExtensionDesc,"Shibboleth ISAPI Extension",HSE_MAX_EXT_DLL_NAME_LEN-1);
131 extern "C" BOOL WINAPI TerminateExtension(DWORD)
133 return TRUE; // cleanup should happen when filter unloads
136 extern "C" BOOL WINAPI GetFilterVersion(PHTTP_FILTER_VERSION pVer)
141 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
142 "Reentrant filter initialization, ignoring...");
146 LPCSTR schemadir=getenv("SHIBSCHEMAS");
148 schemadir=SHIBSP_SCHEMAS;
149 LPCSTR config=getenv("SHIBCONFIG");
151 config=SHIBSP_CONFIG;
152 g_Config=&SPConfig::getConfig();
153 g_Config->setFeatures(
157 SPConfig::RequestMapping |
158 SPConfig::InProcess |
161 if (!g_Config->init(schemadir)) {
163 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
164 "Filter startup failed during library initialization, check native log for help.");
169 DOMDocument* dummydoc=XMLToolingConfig::getConfig().getParser().newDocument();
170 XercesJanitor<DOMDocument> docjanitor(dummydoc);
171 DOMElement* dummy = dummydoc->createElementNS(NULL,path);
172 auto_ptr_XMLCh src(config);
173 dummy->setAttributeNS(NULL,path,src.get());
175 g_Config->setServiceProvider(g_Config->ServiceProviderManager.newPlugin(XML_SERVICE_PROVIDER,dummy));
176 g_Config->getServiceProvider()->init();
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 the implementation-specifics for site mappings.
188 ServiceProvider* sp=g_Config->getServiceProvider();
189 xmltooling::Locker locker(sp);
190 const PropertySet* props=sp->getPropertySet("Local");
192 const DOMElement* impl=XMLHelper::getFirstChildElement(props->getElement(),Implementation);
193 if (impl && (impl=XMLHelper::getFirstChildElement(impl,ISAPI))) {
194 const XMLCh* flag=impl->getAttributeNS(NULL,normalizeRequest);
195 g_bNormalizeRequest=(!flag || !*flag || *flag==chDigit_1 || *flag==chLatin_t);
196 impl=XMLHelper::getFirstChildElement(impl,Site);
198 auto_ptr_char id(impl->getAttributeNS(NULL,id));
200 g_Sites.insert(pair<string,site_t>(id.get(),site_t(impl)));
201 impl=XMLHelper::getNextSiblingElement(impl,Site);
206 pVer->dwFilterVersion=HTTP_FILTER_REVISION;
207 strncpy(pVer->lpszFilterDesc,"Shibboleth ISAPI Filter",SF_MAX_FILTER_DESC_LEN);
208 pVer->dwFlags=(SF_NOTIFY_ORDER_HIGH |
209 SF_NOTIFY_SECURE_PORT |
210 SF_NOTIFY_NONSECURE_PORT |
211 SF_NOTIFY_PREPROC_HEADERS |
213 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter initialized...");
217 extern "C" BOOL WINAPI TerminateFilter(DWORD)
222 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter shut down...");
226 /* Next up, some suck-free versions of various APIs.
228 You DON'T require people to guess the buffer size and THEN tell them the right size.
229 Returning an LPCSTR is apparently way beyond their ken. Not to mention the fact that
230 constant strings aren't typed as such, making it just that much harder. These versions
231 are now updated to use a special growable buffer object, modeled after the standard
232 string class. The standard string won't work because they left out the option to
233 pre-allocate a non-constant buffer.
239 dynabuf() { bufptr=NULL; buflen=0; }
240 dynabuf(size_t s) { bufptr=new char[buflen=s]; *bufptr=0; }
241 ~dynabuf() { delete[] bufptr; }
242 size_t length() const { return bufptr ? strlen(bufptr) : 0; }
243 size_t size() const { return buflen; }
244 bool empty() const { return length()==0; }
245 void reserve(size_t s, bool keep=false);
246 void erase() { if (bufptr) memset(bufptr,0,buflen); }
247 operator char*() { return bufptr; }
248 bool operator ==(const char* s) const;
249 bool operator !=(const char* s) const { return !(*this==s); }
255 void dynabuf::reserve(size_t s, bool keep)
262 p[buflen]=bufptr[buflen];
268 bool dynabuf::operator==(const char* s) const
270 if (buflen==NULL || s==NULL)
271 return (buflen==NULL && s==NULL);
273 return strcmp(bufptr,s)==0;
276 void GetServerVariable(PHTTP_FILTER_CONTEXT pfc, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
282 while (!pfc->GetServerVariable(pfc,lpszVariable,s,&size)) {
283 // Grumble. Check the error.
284 DWORD e=GetLastError();
285 if (e==ERROR_INSUFFICIENT_BUFFER)
290 if (bRequired && s.empty())
294 void GetServerVariable(LPEXTENSION_CONTROL_BLOCK lpECB, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
300 while (!lpECB->GetServerVariable(lpECB->ConnID,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 GetHeader(PHTTP_FILTER_PREPROC_HEADERS pn, PHTTP_FILTER_CONTEXT pfc,
313 LPSTR lpszName, dynabuf& s, DWORD size=80, bool bRequired=true)
319 while (!pn->GetHeader(pfc,lpszName,s,&size)) {
320 // Grumble. Check the error.
321 DWORD e=GetLastError();
322 if (e==ERROR_INSUFFICIENT_BUFFER)
327 if (bRequired && s.empty())
331 /****************************************************************************/
334 class ShibTargetIsapiF : public AbstractSPRequest
336 PHTTP_FILTER_CONTEXT m_pfc;
337 PHTTP_FILTER_PREPROC_HEADERS m_pn;
338 map<string,string> m_headers;
339 vector<XSECCryptoX509*> m_certs;
341 string m_scheme,m_hostname,m_uri;
342 mutable string m_remote_addr,m_content_type,m_method;
345 ShibTargetIsapiF(PHTTP_FILTER_CONTEXT pfc, PHTTP_FILTER_PREPROC_HEADERS pn, const site_t& site) {
350 // URL path always come from IIS.
352 GetHeader(pn,pfc,"url",var,256,false);
355 // Port may come from IIS or from site def.
356 if (!g_bNormalizeRequest || (pfc->fIsSecurePort && site.m_sslport.empty()) || (!pfc->fIsSecurePort && site.m_port.empty())) {
357 GetServerVariable(pfc,"SERVER_PORT",var,10);
360 else if (pfc->fIsSecurePort) {
361 m_port = atoi(site.m_sslport.c_str());
364 m_port = atoi(site.m_port.c_str());
367 // Scheme may come from site def or be derived from IIS.
368 m_scheme=site.m_scheme;
369 if (m_scheme.empty() || !g_bNormalizeRequest)
370 m_scheme=pfc->fIsSecurePort ? "https" : "http";
372 GetServerVariable(pfc,"SERVER_NAME",var,32);
374 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
376 if (site.m_name!=m_hostname && site.m_aliases.find(m_hostname)==site.m_aliases.end())
377 m_hostname=site.m_name;
379 ~ShibTargetIsapiF() { }
381 const char* getScheme() const {
382 return m_scheme.c_str();
384 const char* getHostname() const {
385 return m_hostname.c_str();
387 int getPort() const {
390 const char* getRequestURI() const {
391 return m_uri.c_str();
393 const char* getMethod() const {
394 if (m_method.empty()) {
396 GetServerVariable(m_pfc,"REQUEST_METHOD",var,5,false);
400 return m_method.c_str();
402 string getContentType() const {
403 if (m_content_type.empty()) {
405 GetServerVariable(m_pfc,"CONTENT_TYPE",var,32,false);
407 m_content_type = var;
409 return m_content_type;
411 long getContentLength() const {
414 string getRemoteAddr() const {
415 if (m_remote_addr.empty()) {
417 GetServerVariable(m_pfc,"REMOTE_ADDR",var,16,false);
421 return m_remote_addr;
423 void log(SPLogLevel level, const string& msg) {
424 AbstractSPRequest::log(level,msg);
425 if (level >= SPError)
426 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
428 void clearHeader(const char* name) {
429 string hdr(!strcmp(name,"REMOTE_USER") ? "remote-user" : name);
431 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), "");
433 void setHeader(const char* name, const char* value) {
436 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), const_cast<char*>(value));
438 string getHeader(const char* name) const {
442 GetHeader(m_pn, m_pfc, const_cast<char*>(hdr.c_str()), buf, 1024, false);
445 void setRemoteUser(const char* user) {
446 setHeader("remote-user", user);
448 string getRemoteUser() const {
449 return getHeader("remote-user");
451 void setResponseHeader(const char* name, const char* value) {
454 m_headers[name] = value;
456 m_headers.erase(name);
458 long sendResponse(istream& in, long status) {
459 string hdr = string("Connection: close\r\n");
460 for (map<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
461 hdr += i->first + ": " + i->second + "\r\n";
463 const char* codestr="200 OK";
465 case SAML_HTTP_STATUS_FORBIDDEN:codestr="403 Forbidden"; break;
466 case SAML_HTTP_STATUS_NOTFOUND: codestr="404 Not Found"; break;
467 case SAML_HTTP_STATUS_ERROR: codestr="500 Server Error"; break;
469 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, (void*)codestr, (DWORD)hdr.c_str(), 0);
473 DWORD resplen = in.gcount();
474 m_pfc->WriteClient(m_pfc, buf, &resplen, 0);
476 return SF_STATUS_REQ_FINISHED;
478 long sendRedirect(const char* url) {
479 // XXX: Don't support the httpRedirect option, yet.
480 string hdr=string("Location: ") + url + "\r\n"
481 "Content-Type: text/html\r\n"
482 "Content-Length: 40\r\n"
483 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
484 "Cache-Control: private,no-store,no-cache\r\n";
485 for (map<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
486 hdr += i->first + ": " + i->second + "\r\n";
488 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, "302 Please Wait", (DWORD)hdr.c_str(), 0);
489 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
491 m_pfc->WriteClient(m_pfc, (LPVOID)redmsg, &resplen, 0);
492 return SF_STATUS_REQ_FINISHED;
494 long returnDecline() {
495 return SF_STATUS_REQ_NEXT_NOTIFICATION;
498 return SF_STATUS_REQ_NEXT_NOTIFICATION;
501 const vector<XSECCryptoX509*>& getClientCertificates() const {
505 // The filter never processes the POST, so stub these methods.
506 const char* getQueryString() const { throw IOException("getQueryString not implemented"); }
507 const char* getRequestBody() const { throw IOException("getRequestBody not implemented"); }
510 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const char* msg)
512 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
513 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
514 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)ctype,0);
515 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Filter Error</TITLE></HEAD><BODY>"
516 "<H1>Shibboleth Filter Error</H1>";
517 DWORD resplen=strlen(xmsg);
518 pfc->WriteClient(pfc,(LPVOID)xmsg,&resplen,0);
520 pfc->WriteClient(pfc,(LPVOID)msg,&resplen,0);
521 static const char* xmsg2="</BODY></HTML>";
522 resplen=strlen(xmsg2);
523 pfc->WriteClient(pfc,(LPVOID)xmsg2,&resplen,0);
524 return SF_STATUS_REQ_FINISHED;
527 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
529 // Is this a log notification?
530 if (notificationType==SF_NOTIFY_LOG)
532 if (pfc->pFilterContext)
533 ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=static_cast<LPCSTR>(pfc->pFilterContext);
534 return SF_STATUS_REQ_NEXT_NOTIFICATION;
537 PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
540 // Determine web site number. This can't really fail, I don't think.
542 GetServerVariable(pfc,"INSTANCE_ID",buf,10);
544 // Match site instance to host name, skip if no match.
545 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
546 if (map_i==g_Sites.end())
547 return SF_STATUS_REQ_NEXT_NOTIFICATION;
549 ostringstream threadid;
550 threadid << "[" << getpid() << "] isapi_shib" << '\0';
551 xmltooling::NDC ndc(threadid.str().c_str());
553 ShibTargetIsapiF stf(pfc, pn, map_i->second);
555 // "false" because we don't override the Shib settings
556 pair<bool,long> res = stf.getServiceProvider().doAuthentication(stf);
557 if (res.first) return res.second;
559 // "false" because we don't override the Shib settings
560 res = stf.getServiceProvider().doExport(stf);
561 if (res.first) return res.second;
563 res = stf.getServiceProvider().doAuthorization(stf);
564 if (res.first) return res.second;
566 return SF_STATUS_REQ_NEXT_NOTIFICATION;
569 return WriteClientError(pfc,"Out of Memory");
572 if (e==ERROR_NO_DATA)
573 return WriteClientError(pfc,"A required variable or header was empty.");
575 return WriteClientError(pfc,"Shibboleth Filter detected unexpected IIS error.");
577 catch (exception& e) {
578 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
579 return WriteClientError(pfc,"Shibboleth Filter caught an exception, check Event Log for details.");
583 return WriteClientError(pfc,"Shibboleth Filter caught an unknown exception.");
587 return WriteClientError(pfc,"Shibboleth Filter reached unreachable code, save my walrus!");
591 /****************************************************************************/
594 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* msg)
596 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
597 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
598 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
599 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Error</TITLE></HEAD><BODY><H1>Shibboleth Error</H1>";
600 DWORD resplen=strlen(xmsg);
601 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg,&resplen,HSE_IO_SYNC);
603 lpECB->WriteClient(lpECB->ConnID,(LPVOID)msg,&resplen,HSE_IO_SYNC);
604 static const char* xmsg2="</BODY></HTML>";
605 resplen=strlen(xmsg2);
606 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg2,&resplen,HSE_IO_SYNC);
607 return HSE_STATUS_SUCCESS;
611 class ShibTargetIsapiE : public AbstractSPRequest
613 LPEXTENSION_CONTROL_BLOCK m_lpECB;
614 map<string,string> m_headers;
615 vector<XSECCryptoX509*> m_certs;
616 mutable string m_body;
617 mutable bool m_gotBody;
619 string m_scheme,m_hostname,m_uri;
620 mutable string m_remote_addr;
623 ShibTargetIsapiE(LPEXTENSION_CONTROL_BLOCK lpECB, const site_t& site) : m_lpECB(lpECB), m_gotBody(false) {
625 GetServerVariable(lpECB,"HTTPS",ssl,5);
626 bool SSL=(ssl=="on" || ssl=="ON");
628 // Scheme may come from site def or be derived from IIS.
629 m_scheme=site.m_scheme;
630 if (m_scheme.empty() || !g_bNormalizeRequest)
631 m_scheme = SSL ? "https" : "http";
633 // URL path always come from IIS.
635 GetServerVariable(lpECB,"URL",url,255);
637 // Port may come from IIS or from site def.
639 if (!g_bNormalizeRequest || (SSL && site.m_sslport.empty()) || (!SSL && site.m_port.empty()))
640 GetServerVariable(lpECB,"SERVER_PORT",port,10);
642 strncpy(port,site.m_sslport.c_str(),10);
643 static_cast<char*>(port)[10]=0;
646 strncpy(port,site.m_port.c_str(),10);
647 static_cast<char*>(port)[10]=0;
652 GetServerVariable(lpECB, "SERVER_NAME", var, 32);
654 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
656 if (site.m_name!=m_hostname && site.m_aliases.find(m_hostname)==site.m_aliases.end())
657 m_hostname=site.m_name;
660 * IIS screws us over on PATH_INFO (the hits keep on coming). We need to figure out if
661 * the server is set up for proper PATH_INFO handling, or "IIS sucks rabid weasels mode",
662 * which is the default. No perfect way to tell, but we can take a good guess by checking
663 * whether the URL is a substring of the PATH_INFO:
665 * e.g. for /Shibboleth.sso/SAML/POST
667 * Bad mode (default):
668 * URL: /Shibboleth.sso
669 * PathInfo: /Shibboleth.sso/SAML/POST
672 * URL: /Shibboleth.sso
673 * PathInfo: /SAML/POST
676 // Clearly we're only in bad mode if path info exists at all.
677 if (lpECB->lpszPathInfo && *(lpECB->lpszPathInfo)) {
678 if (strstr(lpECB->lpszPathInfo,url))
679 // Pretty good chance we're in bad mode, unless the PathInfo repeats the path itself.
680 m_uri = lpECB->lpszPathInfo;
683 m_uri += lpECB->lpszPathInfo;
687 // For consistency with Apache, let's add the query string.
688 if (lpECB->lpszQueryString && *(lpECB->lpszQueryString)) {
690 m_uri += lpECB->lpszQueryString;
693 ~ShibTargetIsapiE() { }
695 const char* getScheme() const {
696 return m_scheme.c_str();
698 const char* getHostname() const {
699 return m_hostname.c_str();
701 int getPort() const {
704 const char* getRequestURI() const {
705 return m_uri.c_str();
707 const char* getMethod() const {
708 return m_lpECB->lpszMethod;
710 string getContentType() const {
711 return m_lpECB->lpszContentType ? m_lpECB->lpszContentType : "";
713 long getContentLength() const {
714 return m_lpECB->cbTotalBytes;
716 string getRemoteAddr() const {
717 if (m_remote_addr.empty()) {
719 GetServerVariable(m_lpECB, "REMOTE_ADDR", var, 16, false);
723 return m_remote_addr;
725 void log(SPLogLevel level, const string& msg) {
726 AbstractSPRequest::log(level,msg);
727 if (level >= SPError)
728 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
730 string getHeader(const char* name) const {
732 for (; *name; ++name) {
736 hdr += toupper(*name);
739 GetServerVariable(m_lpECB, const_cast<char*>(hdr.c_str()), buf, 128, false);
740 return buf.empty() ? "" : buf;
742 void setResponseHeader(const char* name, const char* value) {
745 m_headers[name] = value;
747 m_headers.erase(name);
749 const char* getQueryString() const {
750 return m_lpECB->lpszQueryString;
752 const char* getRequestBody() const {
754 return m_body.c_str();
755 if (m_lpECB->cbTotalBytes > 1024*1024) // 1MB?
756 throw opensaml::BindingException("Size of POST request body exceeded limit.");
757 else if (m_lpECB->cbTotalBytes != m_lpECB->cbAvailable) {
760 DWORD datalen=m_lpECB->cbTotalBytes;
763 BOOL ret = m_lpECB->ReadClient(m_lpECB->ConnID, buf, &buflen);
765 throw IOException("Error reading POST request body from browser.");
766 m_body.append(buf, buflen);
772 m_body.assign(reinterpret_cast<char*>(m_lpECB->lpbData),m_lpECB->cbAvailable);
774 return m_body.c_str();
776 long sendResponse(istream& in, long status) {
777 string hdr = string("Connection: close\r\n");
778 for (map<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
779 hdr += i->first + ": " + i->second + "\r\n";
781 const char* codestr="200 OK";
783 case SAML_HTTP_STATUS_FORBIDDEN:codestr="403 Forbidden"; break;
784 case SAML_HTTP_STATUS_NOTFOUND: codestr="404 Not Found"; break;
785 case SAML_HTTP_STATUS_ERROR: codestr="500 Server Error"; break;
787 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, (void*)codestr, 0, (LPDWORD)hdr.c_str());
791 DWORD resplen = in.gcount();
792 m_lpECB->WriteClient(m_lpECB->ConnID, buf, &resplen, HSE_IO_SYNC);
794 return HSE_STATUS_SUCCESS;
796 long sendRedirect(const char* url) {
797 string hdr=string("Location: ") + url + "\r\n"
798 "Content-Type: text/html\r\n"
799 "Content-Length: 40\r\n"
800 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
801 "Cache-Control: private,no-store,no-cache\r\n";
802 for (map<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
803 hdr += i->first + ": " + i->second + "\r\n";
805 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, "302 Moved", 0, (LPDWORD)hdr.c_str());
806 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
808 m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)redmsg, &resplen, HSE_IO_SYNC);
809 return HSE_STATUS_SUCCESS;
811 // Decline happens in the POST processor if this isn't the shire url
812 // Note that it can also happen with HTAccess, but we don't support that, yet.
813 long returnDecline() {
814 return WriteClientError(
816 "ISAPI extension can only be invoked to process Shibboleth protocol requests."
817 "Make sure the mapped file extension doesn't match actual content."
821 return HSE_STATUS_SUCCESS;
824 const vector<XSECCryptoX509*>& getClientCertificates() const {
828 // Not used in the extension.
829 virtual void clearHeader(const char* name) { throw runtime_error("clearHeader not implemented"); }
830 virtual void setHeader(const char* name, const char* value) { throw runtime_error("setHeader not implemented"); }
831 virtual void setRemoteUser(const char* user) { throw runtime_error("setRemoteUser not implemented"); }
832 virtual string getRemoteUser() const { throw runtime_error("getRemoteUser not implemented"); }
835 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
838 ostringstream threadid;
839 threadid << "[" << getpid() << "] isapi_shib_extension" << '\0';
840 xmltooling::NDC ndc(threadid.str().c_str());
842 // Determine web site number. This can't really fail, I don't think.
844 GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
846 // Match site instance to host name, skip if no match.
847 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
848 if (map_i==g_Sites.end())
849 return WriteClientError(lpECB, "Shibboleth Extension not configured for this web site.");
851 ShibTargetIsapiE ste(lpECB, map_i->second);
852 pair<bool,long> res = ste.getServiceProvider().doHandler(ste);
853 if (res.first) return res.second;
855 return WriteClientError(lpECB, "Shibboleth Extension failed to process request");
859 return WriteClientError(lpECB,"Out of Memory");
862 if (e==ERROR_NO_DATA)
863 return WriteClientError(lpECB,"A required variable or header was empty.");
865 return WriteClientError(lpECB,"Server detected unexpected IIS error.");
867 catch (exception& e) {
868 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
869 return WriteClientError(lpECB,"Shibboleth Extension caught an exception, check Event Log for details.");
873 return WriteClientError(lpECB,"Shibboleth Extension caught an unknown exception.");
877 // If we get here we've got an error.
878 return HSE_STATUS_ERROR;