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/SPConfig.h>
29 #include <xmltooling/util/NDC.h>
32 #include <saml/saml.h>
33 #include <shib/shib.h>
34 #include <shib-target/shib-target.h>
43 using namespace shibsp;
44 using namespace shibtarget;
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 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=&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 IConfig* conf=g_Config->getINI();
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 ShibTarget
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 init(m_scheme.c_str(), m_hostname.c_str(), m_port, m_uri.c_str());
378 ~ShibTargetIsapiF() { }
380 const char* getScheme() const {
381 return m_scheme.c_str();
383 const char* getHostname() const {
384 return m_hostname.c_str();
386 int getPort() const {
389 const char* getRequestURI() const {
390 return m_uri.c_str();
392 const char* getMethod() const {
393 if (m_method.empty()) {
395 GetServerVariable(m_pfc,"REQUEST_METHOD",var,5,false);
399 return m_method.c_str();
401 string getContentType() const {
402 if (m_content_type.empty()) {
404 GetServerVariable(m_pfc,"CONTENT_TYPE",var,32,false);
406 m_content_type = var;
408 return m_content_type;
410 long getContentLength() const {
413 string getRemoteAddr() const {
414 if (m_remote_addr.empty()) {
416 GetServerVariable(m_pfc,"REMOTE_ADDR",var,16,false);
420 return m_remote_addr;
422 void log(SPLogLevel level, const string& msg) {
423 AbstractSPRequest::log(level,msg);
424 if (level >= SPError)
425 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
427 void clearHeader(const char* name) {
428 string hdr(!strcmp(name,"REMOTE_USER") ? "remote-user" : name);
430 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), "");
432 void setHeader(const char* name, const char* value) {
435 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), const_cast<char*>(value));
437 string getHeader(const char* name) const {
441 GetHeader(m_pn, m_pfc, const_cast<char*>(hdr.c_str()), buf, 1024, false);
444 void setRemoteUser(const char* user) {
445 setHeader("remote-user", user);
447 string getRemoteUser() const {
448 return getHeader("remote-user");
450 void setResponseHeader(const char* name, const char* value) {
453 m_headers[name] = value;
455 m_headers.erase(name);
457 long sendResponse(istream& in, long status) {
458 string hdr = string("Connection: close\r\n");
459 for (map<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
460 hdr += i->first + ": " + i->second + "\r\n";
462 const char* codestr="200 OK";
464 case SAML_HTTP_STATUS_FORBIDDEN:codestr="403 Forbidden"; break;
465 case SAML_HTTP_STATUS_NOTFOUND: codestr="404 Not Found"; break;
466 case SAML_HTTP_STATUS_ERROR: codestr="500 Server Error"; break;
468 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, (void*)codestr, (DWORD)hdr.c_str(), 0);
472 DWORD resplen = in.gcount();
473 m_pfc->WriteClient(m_pfc, buf, &resplen, 0);
475 return SF_STATUS_REQ_FINISHED;
477 long sendRedirect(const char* url) {
478 // XXX: Don't support the httpRedirect option, yet.
479 string hdr=string("Location: ") + url + "\r\n"
480 "Content-Type: text/html\r\n"
481 "Content-Length: 40\r\n"
482 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
483 "Cache-Control: private,no-store,no-cache\r\n";
484 for (map<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
485 hdr += i->first + ": " + i->second + "\r\n";
487 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, "302 Please Wait", (DWORD)hdr.c_str(), 0);
488 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
490 m_pfc->WriteClient(m_pfc, (LPVOID)redmsg, &resplen, 0);
491 return SF_STATUS_REQ_FINISHED;
493 // XXX: We might not ever hit the 'decline' status in this filter.
494 //long returnDecline(void) { }
495 long returnOK(void) {
496 return SF_STATUS_REQ_NEXT_NOTIFICATION;
499 const vector<XSECCryptoX509*>& getClientCertificates() const {
503 // The filter never processes the POST, so stub these methods.
504 const char* getQueryString() const { throw runtime_error("getQueryString not implemented"); }
505 const char* getRequestBody() const { throw runtime_error("getRequestBody not implemented"); }
508 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const char* msg)
510 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
511 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
512 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)ctype,0);
513 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Filter Error</TITLE></HEAD><BODY>"
514 "<H1>Shibboleth Filter Error</H1>";
515 DWORD resplen=strlen(xmsg);
516 pfc->WriteClient(pfc,(LPVOID)xmsg,&resplen,0);
518 pfc->WriteClient(pfc,(LPVOID)msg,&resplen,0);
519 static const char* xmsg2="</BODY></HTML>";
520 resplen=strlen(xmsg2);
521 pfc->WriteClient(pfc,(LPVOID)xmsg2,&resplen,0);
522 return SF_STATUS_REQ_FINISHED;
525 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
527 // Is this a log notification?
528 if (notificationType==SF_NOTIFY_LOG)
530 if (pfc->pFilterContext)
531 ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=static_cast<LPCSTR>(pfc->pFilterContext);
532 return SF_STATUS_REQ_NEXT_NOTIFICATION;
535 PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
538 // Determine web site number. This can't really fail, I don't think.
540 GetServerVariable(pfc,"INSTANCE_ID",buf,10);
542 // Match site instance to host name, skip if no match.
543 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
544 if (map_i==g_Sites.end())
545 return SF_STATUS_REQ_NEXT_NOTIFICATION;
547 ostringstream threadid;
548 threadid << "[" << getpid() << "] isapi_shib" << '\0';
549 xmltooling::NDC ndc(threadid.str().c_str());
551 ShibTargetIsapiF stf(pfc, pn, map_i->second);
553 // "false" because we don't override the Shib settings
554 pair<bool,long> res = stf.doCheckAuthN();
555 if (res.first) return res.second;
557 // "false" because we don't override the Shib settings
558 res = stf.doExportAssertions();
559 if (res.first) return res.second;
561 res = stf.doCheckAuthZ();
562 if (res.first) return res.second;
564 return SF_STATUS_REQ_NEXT_NOTIFICATION;
567 return WriteClientError(pfc,"Out of Memory");
570 if (e==ERROR_NO_DATA)
571 return WriteClientError(pfc,"A required variable or header was empty.");
573 return WriteClientError(pfc,"Shibboleth Filter detected unexpected IIS error.");
575 catch (exception& e) {
576 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
577 return WriteClientError(pfc,"Shibboleth Filter caught an exception, check Event Log for details.");
581 return WriteClientError(pfc,"Shibboleth Filter caught an unknown exception.");
585 return WriteClientError(pfc,"Shibboleth Filter reached unreachable code, save my walrus!");
589 /****************************************************************************/
592 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* msg)
594 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
595 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
596 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
597 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Error</TITLE></HEAD><BODY><H1>Shibboleth Error</H1>";
598 DWORD resplen=strlen(xmsg);
599 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg,&resplen,HSE_IO_SYNC);
601 lpECB->WriteClient(lpECB->ConnID,(LPVOID)msg,&resplen,HSE_IO_SYNC);
602 static const char* xmsg2="</BODY></HTML>";
603 resplen=strlen(xmsg2);
604 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg2,&resplen,HSE_IO_SYNC);
605 return HSE_STATUS_SUCCESS;
609 class ShibTargetIsapiE : public ShibTarget
611 LPEXTENSION_CONTROL_BLOCK m_lpECB;
612 map<string,string> m_headers;
613 vector<XSECCryptoX509*> m_certs;
614 mutable string m_body;
615 mutable bool m_gotBody;
617 string m_scheme,m_hostname,m_uri;
618 mutable string m_remote_addr;
621 ShibTargetIsapiE(LPEXTENSION_CONTROL_BLOCK lpECB, const site_t& site) : m_lpECB(lpECB), m_gotBody(false) {
623 GetServerVariable(lpECB,"HTTPS",ssl,5);
624 bool SSL=(ssl=="on" || ssl=="ON");
626 // Scheme may come from site def or be derived from IIS.
627 m_scheme=site.m_scheme;
628 if (m_scheme.empty() || !g_bNormalizeRequest)
629 m_scheme = SSL ? "https" : "http";
631 // URL path always come from IIS.
633 GetServerVariable(lpECB,"URL",url,255);
635 // Port may come from IIS or from site def.
637 if (!g_bNormalizeRequest || (SSL && site.m_sslport.empty()) || (!SSL && site.m_port.empty()))
638 GetServerVariable(lpECB,"SERVER_PORT",port,10);
640 strncpy(port,site.m_sslport.c_str(),10);
641 static_cast<char*>(port)[10]=0;
644 strncpy(port,site.m_port.c_str(),10);
645 static_cast<char*>(port)[10]=0;
650 GetServerVariable(lpECB, "SERVER_NAME", var, 32);
652 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
654 if (site.m_name!=m_hostname && site.m_aliases.find(m_hostname)==site.m_aliases.end())
655 m_hostname=site.m_name;
658 * IIS screws us over on PATH_INFO (the hits keep on coming). We need to figure out if
659 * the server is set up for proper PATH_INFO handling, or "IIS sucks rabid weasels mode",
660 * which is the default. No perfect way to tell, but we can take a good guess by checking
661 * whether the URL is a substring of the PATH_INFO:
663 * e.g. for /Shibboleth.sso/SAML/POST
665 * Bad mode (default):
666 * URL: /Shibboleth.sso
667 * PathInfo: /Shibboleth.sso/SAML/POST
670 * URL: /Shibboleth.sso
671 * PathInfo: /SAML/POST
674 // Clearly we're only in bad mode if path info exists at all.
675 if (lpECB->lpszPathInfo && *(lpECB->lpszPathInfo)) {
676 if (strstr(lpECB->lpszPathInfo,url))
677 // Pretty good chance we're in bad mode, unless the PathInfo repeats the path itself.
678 m_uri = lpECB->lpszPathInfo;
681 m_uri += lpECB->lpszPathInfo;
685 // For consistency with Apache, let's add the query string.
686 if (lpECB->lpszQueryString && *(lpECB->lpszQueryString)) {
688 m_uri += lpECB->lpszQueryString;
698 ~ShibTargetIsapiE() { }
700 const char* getScheme() const {
701 return m_scheme.c_str();
703 const char* getHostname() const {
704 return m_hostname.c_str();
706 int getPort() const {
709 const char* getRequestURI() const {
710 return m_uri.c_str();
712 const char* getMethod() const {
713 return m_lpECB->lpszMethod;
715 string getContentType() const {
716 return m_lpECB->lpszContentType ? m_lpECB->lpszContentType : "";
718 long getContentLength() const {
719 return m_lpECB->cbTotalBytes;
721 string getRemoteAddr() const {
722 if (m_remote_addr.empty()) {
724 GetServerVariable(m_lpECB, "REMOTE_ADDR", var, 16, false);
728 return m_remote_addr;
730 void log(SPLogLevel level, const string& msg) {
731 AbstractSPRequest::log(level,msg);
732 if (level >= SPError)
733 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
735 string getHeader(const char* name) const {
737 for (; *name; ++name) {
741 hdr += toupper(*name);
744 GetServerVariable(m_lpECB, const_cast<char*>(hdr.c_str()), buf, 128, false);
745 return buf.empty() ? "" : buf;
747 void setResponseHeader(const char* name, const char* value) {
750 m_headers[name] = value;
752 m_headers.erase(name);
754 const char* getQueryString() const {
755 return m_lpECB->lpszQueryString;
757 const char* getRequestBody() const {
759 return m_body.c_str();
760 if (m_lpECB->cbTotalBytes > 1024*1024) // 1MB?
761 throw opensaml::BindingException("Size of POST request body exceeded limit.");
762 else if (m_lpECB->cbTotalBytes != m_lpECB->cbAvailable) {
765 DWORD datalen=m_lpECB->cbTotalBytes;
768 BOOL ret = m_lpECB->ReadClient(m_lpECB->ConnID, buf, &buflen);
770 throw saml::SAMLException("Error reading POST request body from browser.");
771 m_body.append(buf, buflen);
777 m_body.assign(reinterpret_cast<char*>(m_lpECB->lpbData),m_lpECB->cbAvailable);
779 return m_body.c_str();
781 long sendResponse(istream& in, long status) {
782 string hdr = string("Connection: close\r\n");
783 for (map<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
784 hdr += i->first + ": " + i->second + "\r\n";
786 const char* codestr="200 OK";
788 case SAML_HTTP_STATUS_FORBIDDEN:codestr="403 Forbidden"; break;
789 case SAML_HTTP_STATUS_NOTFOUND: codestr="404 Not Found"; break;
790 case SAML_HTTP_STATUS_ERROR: codestr="500 Server Error"; break;
792 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, (void*)codestr, 0, (LPDWORD)hdr.c_str());
796 DWORD resplen = in.gcount();
797 m_lpECB->WriteClient(m_lpECB->ConnID, buf, &resplen, HSE_IO_SYNC);
799 return HSE_STATUS_SUCCESS;
801 long sendRedirect(const char* url) {
802 string hdr=string("Location: ") + url + "\r\n"
803 "Content-Type: text/html\r\n"
804 "Content-Length: 40\r\n"
805 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
806 "Cache-Control: private,no-store,no-cache\r\n";
807 for (map<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
808 hdr += i->first + ": " + i->second + "\r\n";
810 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, "302 Moved", 0, (LPDWORD)hdr.c_str());
811 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
813 m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)redmsg, &resplen, HSE_IO_SYNC);
814 return HSE_STATUS_SUCCESS;
816 // Decline happens in the POST processor if this isn't the shire url
817 // Note that it can also happen with HTAccess, but we don't support that, yet.
818 long returnDecline() {
819 return WriteClientError(
821 "ISAPI extension can only be invoked to process Shibboleth protocol requests."
822 "Make sure the mapped file extension doesn't match actual content."
826 return HSE_STATUS_SUCCESS;
829 const vector<XSECCryptoX509*>& getClientCertificates() const {
833 // Not used in the extension.
834 virtual void clearHeader(const char* name) { throw runtime_error("clearHeader not implemented"); }
835 virtual void setHeader(const char* name, const char* value) { throw runtime_error("setHeader not implemented"); }
836 virtual void setRemoteUser(const char* user) { throw runtime_error("setRemoteUser not implemented"); }
837 virtual string getRemoteUser() const { throw runtime_error("getRemoteUser not implemented"); }
840 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
843 const IApplication* application=NULL;
845 ostringstream threadid;
846 threadid << "[" << getpid() << "] isapi_shib_extension" << '\0';
847 xmltooling::NDC ndc(threadid.str().c_str());
849 // Determine web site number. This can't really fail, I don't think.
851 GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
853 // Match site instance to host name, skip if no match.
854 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
855 if (map_i==g_Sites.end())
856 return WriteClientError(lpECB, "Shibboleth Extension not configured for this web site.");
858 ShibTargetIsapiE ste(lpECB, map_i->second);
859 pair<bool,long> res = ste.doHandler();
860 if (res.first) return res.second;
862 return WriteClientError(lpECB, "Shibboleth Extension failed to process request");
866 return WriteClientError(lpECB,"Out of Memory");
869 if (e==ERROR_NO_DATA)
870 return WriteClientError(lpECB,"A required variable or header was empty.");
872 return WriteClientError(lpECB,"Server detected unexpected IIS error.");
874 catch (exception& e) {
875 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
876 return WriteClientError(lpECB,"Shibboleth Extension caught an exception, check Event Log for details.");
880 return WriteClientError(lpECB,"Shibboleth Extension caught an unknown exception.");
884 // If we get here we've got an error.
885 return HSE_STATUS_ERROR;