2 * Copyright 2001-2005 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 <xmltooling/util/NDC.h>
33 #include <saml/saml.h>
34 #include <shib/shib.h>
35 #include <shib-target/shib-target.h>
44 using namespace shibsp;
45 using namespace xmltooling;
50 static const XMLCh name[] = UNICODE_LITERAL_4(n,a,m,e);
51 static const XMLCh port[] = UNICODE_LITERAL_4(p,o,r,t);
52 static const XMLCh sslport[] = UNICODE_LITERAL_7(s,s,l,p,o,r,t);
53 static const XMLCh scheme[] = UNICODE_LITERAL_6(s,c,h,e,m,e);
54 static const XMLCh id[] = UNICODE_LITERAL_2(i,d);
55 static const XMLCh Implementation[] = UNICODE_LITERAL_14(I,m,p,l,e,m,e,n,t,a,t,i,o,n);
56 static const XMLCh ISAPI[] = UNICODE_LITERAL_5(I,S,A,P,I);
57 static const XMLCh Alias[] = UNICODE_LITERAL_5(A,l,i,a,s);
58 static const XMLCh normalizeRequest[] = UNICODE_LITERAL_16(n,o,r,m,a,l,i,z,e,R,e,q,u,e,s,t);
59 static const XMLCh Site[] = UNICODE_LITERAL_4(S,i,t,e);
62 site_t(const DOMElement* e)
64 auto_ptr_char n(e->getAttributeNS(NULL,name));
65 auto_ptr_char s(e->getAttributeNS(NULL,scheme));
66 auto_ptr_char p(e->getAttributeNS(NULL,port));
67 auto_ptr_char p2(e->getAttributeNS(NULL,sslport));
68 if (n.get()) m_name=n.get();
69 if (s.get()) m_scheme=s.get();
70 if (p.get()) m_port=p.get();
71 if (p2.get()) m_sslport=p2.get();
72 e = XMLHelper::getFirstChildElement(e, Alias);
74 if (e->hasChildNodes()) {
75 auto_ptr_char alias(e->getFirstChild()->getNodeValue());
76 m_aliases.insert(alias.get());
78 e = XMLHelper::getNextSiblingElement(e, Alias);
81 string m_scheme,m_port,m_sslport,m_name;
82 set<string> m_aliases;
86 shibtarget::ShibTargetConfig* g_Config = NULL;
87 map<string,site_t> g_Sites;
88 bool g_bNormalizeRequest = true;
92 LPCSTR lpUNCServerName,
98 LPCSTR messages[] = {message, NULL};
100 HANDLE hElog = RegisterEventSource(lpUNCServerName, "Shibboleth ISAPI Filter");
101 BOOL res = ReportEvent(hElog, wType, 0, dwEventID, lpUserSid, 1, 0, messages, NULL);
102 return (DeregisterEventSource(hElog) && res);
105 extern "C" __declspec(dllexport) BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID)
107 if (fdwReason==DLL_PROCESS_ATTACH)
112 extern "C" BOOL WINAPI GetExtensionVersion(HSE_VERSION_INFO* pVer)
118 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
119 "Extension mode startup not possible, is the DLL loaded as a filter?");
123 pVer->dwExtensionVersion=HSE_VERSION;
124 strncpy(pVer->lpszExtensionDesc,"Shibboleth ISAPI Extension",HSE_MAX_EXT_DLL_NAME_LEN-1);
128 extern "C" BOOL WINAPI TerminateExtension(DWORD)
130 return TRUE; // cleanup should happen when filter unloads
133 extern "C" BOOL WINAPI GetFilterVersion(PHTTP_FILTER_VERSION pVer)
138 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
139 "Reentrant filter initialization, ignoring...");
147 LPCSTR schemadir=getenv("SHIBSCHEMAS");
149 schemadir=SHIB_SCHEMAS;
150 LPCSTR config=getenv("SHIBCONFIG");
153 g_Config=&shibtarget::ShibTargetConfig::getConfig();
154 SPConfig::getConfig().setFeatures(
159 SPConfig::RequestMapping |
160 SPConfig::InProcess |
163 if (!g_Config->init(schemadir)) {
165 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
166 "Filter startup failed during library initialization, check native log for help.");
169 else if (!g_Config->load(config)) {
171 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
172 "Filter startup failed to load configuration, check native log for help.");
176 // Access the implementation-specifics for site mappings.
177 ServiceProvider* conf=SPConfig::getConfig().getServiceProvider();
178 xmltooling::Locker locker(conf);
179 const PropertySet* props=conf->getPropertySet("Local");
181 const DOMElement* impl=XMLHelper::getFirstChildElement(props->getElement(),Implementation);
182 if (impl && (impl=XMLHelper::getFirstChildElement(impl,ISAPI))) {
183 const XMLCh* flag=impl->getAttributeNS(NULL,normalizeRequest);
184 g_bNormalizeRequest=(!flag || !*flag || *flag==chDigit_1 || *flag==chLatin_t);
185 impl=XMLHelper::getFirstChildElement(impl,Site);
187 auto_ptr_char id(impl->getAttributeNS(NULL,id));
189 g_Sites.insert(pair<string,site_t>(id.get(),site_t(impl)));
190 impl=XMLHelper::getNextSiblingElement(impl,Site);
198 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Filter startup failed with an exception.");
203 pVer->dwFilterVersion=HTTP_FILTER_REVISION;
204 strncpy(pVer->lpszFilterDesc,"Shibboleth ISAPI Filter",SF_MAX_FILTER_DESC_LEN);
205 pVer->dwFlags=(SF_NOTIFY_ORDER_HIGH |
206 SF_NOTIFY_SECURE_PORT |
207 SF_NOTIFY_NONSECURE_PORT |
208 SF_NOTIFY_PREPROC_HEADERS |
210 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter initialized...");
214 extern "C" BOOL WINAPI TerminateFilter(DWORD)
217 g_Config->shutdown();
219 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter shut down...");
223 /* Next up, some suck-free versions of various APIs.
225 You DON'T require people to guess the buffer size and THEN tell them the right size.
226 Returning an LPCSTR is apparently way beyond their ken. Not to mention the fact that
227 constant strings aren't typed as such, making it just that much harder. These versions
228 are now updated to use a special growable buffer object, modeled after the standard
229 string class. The standard string won't work because they left out the option to
230 pre-allocate a non-constant buffer.
236 dynabuf() { bufptr=NULL; buflen=0; }
237 dynabuf(size_t s) { bufptr=new char[buflen=s]; *bufptr=0; }
238 ~dynabuf() { delete[] bufptr; }
239 size_t length() const { return bufptr ? strlen(bufptr) : 0; }
240 size_t size() const { return buflen; }
241 bool empty() const { return length()==0; }
242 void reserve(size_t s, bool keep=false);
243 void erase() { if (bufptr) memset(bufptr,0,buflen); }
244 operator char*() { return bufptr; }
245 bool operator ==(const char* s) const;
246 bool operator !=(const char* s) const { return !(*this==s); }
252 void dynabuf::reserve(size_t s, bool keep)
259 p[buflen]=bufptr[buflen];
265 bool dynabuf::operator==(const char* s) const
267 if (buflen==NULL || s==NULL)
268 return (buflen==NULL && s==NULL);
270 return strcmp(bufptr,s)==0;
273 void GetServerVariable(PHTTP_FILTER_CONTEXT pfc, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
279 while (!pfc->GetServerVariable(pfc,lpszVariable,s,&size)) {
280 // Grumble. Check the error.
281 DWORD e=GetLastError();
282 if (e==ERROR_INSUFFICIENT_BUFFER)
287 if (bRequired && s.empty())
291 void GetServerVariable(LPEXTENSION_CONTROL_BLOCK lpECB, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
297 while (!lpECB->GetServerVariable(lpECB->ConnID,lpszVariable,s,&size)) {
298 // Grumble. Check the error.
299 DWORD e=GetLastError();
300 if (e==ERROR_INSUFFICIENT_BUFFER)
305 if (bRequired && s.empty())
309 void GetHeader(PHTTP_FILTER_PREPROC_HEADERS pn, PHTTP_FILTER_CONTEXT pfc,
310 LPSTR lpszName, dynabuf& s, DWORD size=80, bool bRequired=true)
316 while (!pn->GetHeader(pfc,lpszName,s,&size)) {
317 // Grumble. Check the error.
318 DWORD e=GetLastError();
319 if (e==ERROR_INSUFFICIENT_BUFFER)
324 if (bRequired && s.empty())
328 /****************************************************************************/
331 class ShibTargetIsapiF : public AbstractSPRequest
333 PHTTP_FILTER_CONTEXT m_pfc;
334 PHTTP_FILTER_PREPROC_HEADERS m_pn;
335 map<string,string> m_headers;
336 vector<XSECCryptoX509*> m_certs;
338 string m_scheme,m_hostname,m_uri;
339 mutable string m_remote_addr,m_content_type,m_method;
342 ShibTargetIsapiF(PHTTP_FILTER_CONTEXT pfc, PHTTP_FILTER_PREPROC_HEADERS pn, const site_t& site) {
347 // URL path always come from IIS.
349 GetHeader(pn,pfc,"url",var,256,false);
352 // Port may come from IIS or from site def.
353 if (!g_bNormalizeRequest || (pfc->fIsSecurePort && site.m_sslport.empty()) || (!pfc->fIsSecurePort && site.m_port.empty())) {
354 GetServerVariable(pfc,"SERVER_PORT",var,10);
357 else if (pfc->fIsSecurePort) {
358 m_port = atoi(site.m_sslport.c_str());
361 m_port = atoi(site.m_port.c_str());
364 // Scheme may come from site def or be derived from IIS.
365 m_scheme=site.m_scheme;
366 if (m_scheme.empty() || !g_bNormalizeRequest)
367 m_scheme=pfc->fIsSecurePort ? "https" : "http";
369 GetServerVariable(pfc,"SERVER_NAME",var,32);
371 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
373 if (site.m_name!=m_hostname && site.m_aliases.find(m_hostname)==site.m_aliases.end())
374 m_hostname=site.m_name;
376 ~ShibTargetIsapiF() { }
378 const char* getScheme() const {
379 return m_scheme.c_str();
381 const char* getHostname() const {
382 return m_hostname.c_str();
384 int getPort() const {
387 const char* getRequestURI() const {
388 return m_uri.c_str();
390 const char* getMethod() const {
391 if (m_method.empty()) {
393 GetServerVariable(m_pfc,"REQUEST_METHOD",var,5,false);
397 return m_method.c_str();
399 string getContentType() const {
400 if (m_content_type.empty()) {
402 GetServerVariable(m_pfc,"CONTENT_TYPE",var,32,false);
404 m_content_type = var;
406 return m_content_type;
408 long getContentLength() const {
411 string getRemoteAddr() const {
412 if (m_remote_addr.empty()) {
414 GetServerVariable(m_pfc,"REMOTE_ADDR",var,16,false);
418 return m_remote_addr;
420 void log(SPLogLevel level, const string& msg) {
421 AbstractSPRequest::log(level,msg);
422 if (level >= SPError)
423 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
425 void clearHeader(const char* name) {
426 string hdr(!strcmp(name,"REMOTE_USER") ? "remote-user" : name);
428 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), "");
430 void setHeader(const char* name, const char* value) {
433 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), const_cast<char*>(value));
435 string getHeader(const char* name) const {
439 GetHeader(m_pn, m_pfc, const_cast<char*>(hdr.c_str()), buf, 1024, false);
442 void setRemoteUser(const char* user) {
443 setHeader("remote-user", user);
445 string getRemoteUser() const {
446 return getHeader("remote-user");
448 void setResponseHeader(const char* name, const char* value) {
451 m_headers[name] = value;
453 m_headers.erase(name);
455 long sendResponse(istream& in, long status) {
456 string hdr = string("Connection: close\r\n");
457 for (map<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
458 hdr += i->first + ": " + i->second + "\r\n";
460 const char* codestr="200 OK";
462 case SAML_HTTP_STATUS_FORBIDDEN:codestr="403 Forbidden"; break;
463 case SAML_HTTP_STATUS_NOTFOUND: codestr="404 Not Found"; break;
464 case SAML_HTTP_STATUS_ERROR: codestr="500 Server Error"; break;
466 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, (void*)codestr, (DWORD)hdr.c_str(), 0);
470 DWORD resplen = in.gcount();
471 m_pfc->WriteClient(m_pfc, buf, &resplen, 0);
473 return SF_STATUS_REQ_FINISHED;
475 long sendRedirect(const char* url) {
476 // XXX: Don't support the httpRedirect option, yet.
477 string hdr=string("Location: ") + url + "\r\n"
478 "Content-Type: text/html\r\n"
479 "Content-Length: 40\r\n"
480 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
481 "Cache-Control: private,no-store,no-cache\r\n";
482 for (map<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
483 hdr += i->first + ": " + i->second + "\r\n";
485 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, "302 Please Wait", (DWORD)hdr.c_str(), 0);
486 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
488 m_pfc->WriteClient(m_pfc, (LPVOID)redmsg, &resplen, 0);
489 return SF_STATUS_REQ_FINISHED;
491 long returnDecline() {
492 return SF_STATUS_REQ_NEXT_NOTIFICATION;
495 return SF_STATUS_REQ_NEXT_NOTIFICATION;
498 const vector<XSECCryptoX509*>& getClientCertificates() const {
502 // The filter never processes the POST, so stub these methods.
503 const char* getQueryString() const { throw runtime_error("getQueryString not implemented"); }
504 const char* getRequestBody() const { throw runtime_error("getRequestBody not implemented"); }
507 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const char* msg)
509 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
510 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
511 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)ctype,0);
512 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Filter Error</TITLE></HEAD><BODY>"
513 "<H1>Shibboleth Filter Error</H1>";
514 DWORD resplen=strlen(xmsg);
515 pfc->WriteClient(pfc,(LPVOID)xmsg,&resplen,0);
517 pfc->WriteClient(pfc,(LPVOID)msg,&resplen,0);
518 static const char* xmsg2="</BODY></HTML>";
519 resplen=strlen(xmsg2);
520 pfc->WriteClient(pfc,(LPVOID)xmsg2,&resplen,0);
521 return SF_STATUS_REQ_FINISHED;
524 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
526 // Is this a log notification?
527 if (notificationType==SF_NOTIFY_LOG)
529 if (pfc->pFilterContext)
530 ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=static_cast<LPCSTR>(pfc->pFilterContext);
531 return SF_STATUS_REQ_NEXT_NOTIFICATION;
534 PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
537 // Determine web site number. This can't really fail, I don't think.
539 GetServerVariable(pfc,"INSTANCE_ID",buf,10);
541 // Match site instance to host name, skip if no match.
542 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
543 if (map_i==g_Sites.end())
544 return SF_STATUS_REQ_NEXT_NOTIFICATION;
546 ostringstream threadid;
547 threadid << "[" << getpid() << "] isapi_shib" << '\0';
548 xmltooling::NDC ndc(threadid.str().c_str());
550 ShibTargetIsapiF stf(pfc, pn, map_i->second);
552 // "false" because we don't override the Shib settings
553 pair<bool,long> res = stf.getServiceProvider().doAuthentication(stf);
554 if (res.first) return res.second;
556 // "false" because we don't override the Shib settings
557 res = stf.getServiceProvider().doExport(stf);
558 if (res.first) return res.second;
560 res = stf.getServiceProvider().doAuthorization(stf);
561 if (res.first) return res.second;
563 return SF_STATUS_REQ_NEXT_NOTIFICATION;
566 return WriteClientError(pfc,"Out of Memory");
569 if (e==ERROR_NO_DATA)
570 return WriteClientError(pfc,"A required variable or header was empty.");
572 return WriteClientError(pfc,"Shibboleth Filter detected unexpected IIS error.");
574 catch (exception& e) {
575 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
576 return WriteClientError(pfc,"Shibboleth Filter caught an exception, check Event Log for details.");
580 return WriteClientError(pfc,"Shibboleth Filter caught an unknown exception.");
584 return WriteClientError(pfc,"Shibboleth Filter reached unreachable code, save my walrus!");
588 /****************************************************************************/
591 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* msg)
593 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
594 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
595 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
596 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Error</TITLE></HEAD><BODY><H1>Shibboleth Error</H1>";
597 DWORD resplen=strlen(xmsg);
598 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg,&resplen,HSE_IO_SYNC);
600 lpECB->WriteClient(lpECB->ConnID,(LPVOID)msg,&resplen,HSE_IO_SYNC);
601 static const char* xmsg2="</BODY></HTML>";
602 resplen=strlen(xmsg2);
603 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg2,&resplen,HSE_IO_SYNC);
604 return HSE_STATUS_SUCCESS;
608 class ShibTargetIsapiE : public AbstractSPRequest
610 LPEXTENSION_CONTROL_BLOCK m_lpECB;
611 map<string,string> m_headers;
612 vector<XSECCryptoX509*> m_certs;
613 mutable string m_body;
614 mutable bool m_gotBody;
616 string m_scheme,m_hostname,m_uri;
617 mutable string m_remote_addr;
620 ShibTargetIsapiE(LPEXTENSION_CONTROL_BLOCK lpECB, const site_t& site) : m_lpECB(lpECB), m_gotBody(false) {
622 GetServerVariable(lpECB,"HTTPS",ssl,5);
623 bool SSL=(ssl=="on" || ssl=="ON");
625 // Scheme may come from site def or be derived from IIS.
626 m_scheme=site.m_scheme;
627 if (m_scheme.empty() || !g_bNormalizeRequest)
628 m_scheme = SSL ? "https" : "http";
630 // URL path always come from IIS.
632 GetServerVariable(lpECB,"URL",url,255);
634 // Port may come from IIS or from site def.
636 if (!g_bNormalizeRequest || (SSL && site.m_sslport.empty()) || (!SSL && site.m_port.empty()))
637 GetServerVariable(lpECB,"SERVER_PORT",port,10);
639 strncpy(port,site.m_sslport.c_str(),10);
640 static_cast<char*>(port)[10]=0;
643 strncpy(port,site.m_port.c_str(),10);
644 static_cast<char*>(port)[10]=0;
649 GetServerVariable(lpECB, "SERVER_NAME", var, 32);
651 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
653 if (site.m_name!=m_hostname && site.m_aliases.find(m_hostname)==site.m_aliases.end())
654 m_hostname=site.m_name;
657 * IIS screws us over on PATH_INFO (the hits keep on coming). We need to figure out if
658 * the server is set up for proper PATH_INFO handling, or "IIS sucks rabid weasels mode",
659 * which is the default. No perfect way to tell, but we can take a good guess by checking
660 * whether the URL is a substring of the PATH_INFO:
662 * e.g. for /Shibboleth.sso/SAML/POST
664 * Bad mode (default):
665 * URL: /Shibboleth.sso
666 * PathInfo: /Shibboleth.sso/SAML/POST
669 * URL: /Shibboleth.sso
670 * PathInfo: /SAML/POST
673 // Clearly we're only in bad mode if path info exists at all.
674 if (lpECB->lpszPathInfo && *(lpECB->lpszPathInfo)) {
675 if (strstr(lpECB->lpszPathInfo,url))
676 // Pretty good chance we're in bad mode, unless the PathInfo repeats the path itself.
677 m_uri = lpECB->lpszPathInfo;
680 m_uri += lpECB->lpszPathInfo;
684 // For consistency with Apache, let's add the query string.
685 if (lpECB->lpszQueryString && *(lpECB->lpszQueryString)) {
687 m_uri += lpECB->lpszQueryString;
690 ~ShibTargetIsapiE() { }
692 const char* getScheme() const {
693 return m_scheme.c_str();
695 const char* getHostname() const {
696 return m_hostname.c_str();
698 int getPort() const {
701 const char* getRequestURI() const {
702 return m_uri.c_str();
704 const char* getMethod() const {
705 return m_lpECB->lpszMethod;
707 string getContentType() const {
708 return m_lpECB->lpszContentType ? m_lpECB->lpszContentType : "";
710 long getContentLength() const {
711 return m_lpECB->cbTotalBytes;
713 string getRemoteAddr() const {
714 if (m_remote_addr.empty()) {
716 GetServerVariable(m_lpECB, "REMOTE_ADDR", var, 16, false);
720 return m_remote_addr;
722 void log(SPLogLevel level, const string& msg) {
723 AbstractSPRequest::log(level,msg);
724 if (level >= SPError)
725 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
727 string getHeader(const char* name) const {
729 for (; *name; ++name) {
733 hdr += toupper(*name);
736 GetServerVariable(m_lpECB, const_cast<char*>(hdr.c_str()), buf, 128, false);
737 return buf.empty() ? "" : buf;
739 void setResponseHeader(const char* name, const char* value) {
742 m_headers[name] = value;
744 m_headers.erase(name);
746 const char* getQueryString() const {
747 return m_lpECB->lpszQueryString;
749 const char* getRequestBody() const {
751 return m_body.c_str();
752 if (m_lpECB->cbTotalBytes > 1024*1024) // 1MB?
753 throw opensaml::BindingException("Size of POST request body exceeded limit.");
754 else if (m_lpECB->cbTotalBytes != m_lpECB->cbAvailable) {
757 DWORD datalen=m_lpECB->cbTotalBytes;
760 BOOL ret = m_lpECB->ReadClient(m_lpECB->ConnID, buf, &buflen);
762 throw saml::SAMLException("Error reading POST request body from browser.");
763 m_body.append(buf, buflen);
769 m_body.assign(reinterpret_cast<char*>(m_lpECB->lpbData),m_lpECB->cbAvailable);
771 return m_body.c_str();
773 long sendResponse(istream& in, long status) {
774 string hdr = string("Connection: close\r\n");
775 for (map<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
776 hdr += i->first + ": " + i->second + "\r\n";
778 const char* codestr="200 OK";
780 case SAML_HTTP_STATUS_FORBIDDEN:codestr="403 Forbidden"; break;
781 case SAML_HTTP_STATUS_NOTFOUND: codestr="404 Not Found"; break;
782 case SAML_HTTP_STATUS_ERROR: codestr="500 Server Error"; break;
784 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, (void*)codestr, 0, (LPDWORD)hdr.c_str());
788 DWORD resplen = in.gcount();
789 m_lpECB->WriteClient(m_lpECB->ConnID, buf, &resplen, HSE_IO_SYNC);
791 return HSE_STATUS_SUCCESS;
793 long sendRedirect(const char* url) {
794 string hdr=string("Location: ") + url + "\r\n"
795 "Content-Type: text/html\r\n"
796 "Content-Length: 40\r\n"
797 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
798 "Cache-Control: private,no-store,no-cache\r\n";
799 for (map<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
800 hdr += i->first + ": " + i->second + "\r\n";
802 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, "302 Moved", 0, (LPDWORD)hdr.c_str());
803 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
805 m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)redmsg, &resplen, HSE_IO_SYNC);
806 return HSE_STATUS_SUCCESS;
808 // Decline happens in the POST processor if this isn't the shire url
809 // Note that it can also happen with HTAccess, but we don't support that, yet.
810 long returnDecline() {
811 return WriteClientError(
813 "ISAPI extension can only be invoked to process Shibboleth protocol requests."
814 "Make sure the mapped file extension doesn't match actual content."
818 return HSE_STATUS_SUCCESS;
821 const vector<XSECCryptoX509*>& getClientCertificates() const {
825 // Not used in the extension.
826 virtual void clearHeader(const char* name) { throw runtime_error("clearHeader not implemented"); }
827 virtual void setHeader(const char* name, const char* value) { throw runtime_error("setHeader not implemented"); }
828 virtual void setRemoteUser(const char* user) { throw runtime_error("setRemoteUser not implemented"); }
829 virtual string getRemoteUser() const { throw runtime_error("getRemoteUser not implemented"); }
832 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
835 ostringstream threadid;
836 threadid << "[" << getpid() << "] isapi_shib_extension" << '\0';
837 xmltooling::NDC ndc(threadid.str().c_str());
839 // Determine web site number. This can't really fail, I don't think.
841 GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
843 // Match site instance to host name, skip if no match.
844 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
845 if (map_i==g_Sites.end())
846 return WriteClientError(lpECB, "Shibboleth Extension not configured for this web site.");
848 ShibTargetIsapiE ste(lpECB, map_i->second);
849 pair<bool,long> res = ste.getServiceProvider().doHandler(ste);
850 if (res.first) return res.second;
852 return WriteClientError(lpECB, "Shibboleth Extension failed to process request");
856 return WriteClientError(lpECB,"Out of Memory");
859 if (e==ERROR_NO_DATA)
860 return WriteClientError(lpECB,"A required variable or header was empty.");
862 return WriteClientError(lpECB,"Server detected unexpected IIS error.");
864 catch (exception& e) {
865 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
866 return WriteClientError(lpECB,"Shibboleth Extension caught an exception, check Event Log for details.");
870 return WriteClientError(lpECB,"Shibboleth Extension caught an unknown exception.");
874 // If we get here we've got an error.
875 return HSE_STATUS_ERROR;