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 <xmltooling/util/NDC.h>
32 #include <shib-target/shib-target.h>
41 using namespace shibsp;
42 using namespace xmltooling;
47 static const XMLCh name[] = UNICODE_LITERAL_4(n,a,m,e);
48 static const XMLCh port[] = UNICODE_LITERAL_4(p,o,r,t);
49 static const XMLCh sslport[] = UNICODE_LITERAL_7(s,s,l,p,o,r,t);
50 static const XMLCh scheme[] = UNICODE_LITERAL_6(s,c,h,e,m,e);
51 static const XMLCh id[] = UNICODE_LITERAL_2(i,d);
52 static const XMLCh Implementation[] = UNICODE_LITERAL_14(I,m,p,l,e,m,e,n,t,a,t,i,o,n);
53 static const XMLCh ISAPI[] = UNICODE_LITERAL_5(I,S,A,P,I);
54 static const XMLCh Alias[] = UNICODE_LITERAL_5(A,l,i,a,s);
55 static const XMLCh normalizeRequest[] = UNICODE_LITERAL_16(n,o,r,m,a,l,i,z,e,R,e,q,u,e,s,t);
56 static const XMLCh Site[] = UNICODE_LITERAL_4(S,i,t,e);
59 site_t(const DOMElement* e)
61 auto_ptr_char n(e->getAttributeNS(NULL,name));
62 auto_ptr_char s(e->getAttributeNS(NULL,scheme));
63 auto_ptr_char p(e->getAttributeNS(NULL,port));
64 auto_ptr_char p2(e->getAttributeNS(NULL,sslport));
65 if (n.get()) m_name=n.get();
66 if (s.get()) m_scheme=s.get();
67 if (p.get()) m_port=p.get();
68 if (p2.get()) m_sslport=p2.get();
69 e = XMLHelper::getFirstChildElement(e, Alias);
71 if (e->hasChildNodes()) {
72 auto_ptr_char alias(e->getFirstChild()->getNodeValue());
73 m_aliases.insert(alias.get());
75 e = XMLHelper::getNextSiblingElement(e, Alias);
78 string m_scheme,m_port,m_sslport,m_name;
79 set<string> m_aliases;
83 shibtarget::ShibTargetConfig* g_Config = NULL;
84 map<string,site_t> g_Sites;
85 bool g_bNormalizeRequest = true;
89 LPCSTR lpUNCServerName,
95 LPCSTR messages[] = {message, NULL};
97 HANDLE hElog = RegisterEventSource(lpUNCServerName, "Shibboleth ISAPI Filter");
98 BOOL res = ReportEvent(hElog, wType, 0, dwEventID, lpUserSid, 1, 0, messages, NULL);
99 return (DeregisterEventSource(hElog) && res);
102 extern "C" __declspec(dllexport) BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID)
104 if (fdwReason==DLL_PROCESS_ATTACH)
109 extern "C" BOOL WINAPI GetExtensionVersion(HSE_VERSION_INFO* pVer)
115 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
116 "Extension mode startup not possible, is the DLL loaded as a filter?");
120 pVer->dwExtensionVersion=HSE_VERSION;
121 strncpy(pVer->lpszExtensionDesc,"Shibboleth ISAPI Extension",HSE_MAX_EXT_DLL_NAME_LEN-1);
125 extern "C" BOOL WINAPI TerminateExtension(DWORD)
127 return TRUE; // cleanup should happen when filter unloads
130 extern "C" BOOL WINAPI GetFilterVersion(PHTTP_FILTER_VERSION pVer)
135 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
136 "Reentrant filter initialization, ignoring...");
144 LPCSTR schemadir=getenv("SHIBSCHEMAS");
146 schemadir=SHIB_SCHEMAS;
147 LPCSTR config=getenv("SHIBCONFIG");
150 g_Config=&shibtarget::ShibTargetConfig::getConfig();
151 SPConfig::getConfig().setFeatures(
156 SPConfig::RequestMapping |
157 SPConfig::InProcess |
160 if (!g_Config->init(schemadir)) {
162 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
163 "Filter startup failed during library initialization, check native log for help.");
166 else if (!g_Config->load(config)) {
168 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
169 "Filter startup failed to load configuration, check native log for help.");
173 // Access the implementation-specifics for site mappings.
174 ServiceProvider* conf=SPConfig::getConfig().getServiceProvider();
175 xmltooling::Locker locker(conf);
176 const PropertySet* props=conf->getPropertySet("Local");
178 const DOMElement* impl=XMLHelper::getFirstChildElement(props->getElement(),Implementation);
179 if (impl && (impl=XMLHelper::getFirstChildElement(impl,ISAPI))) {
180 const XMLCh* flag=impl->getAttributeNS(NULL,normalizeRequest);
181 g_bNormalizeRequest=(!flag || !*flag || *flag==chDigit_1 || *flag==chLatin_t);
182 impl=XMLHelper::getFirstChildElement(impl,Site);
184 auto_ptr_char id(impl->getAttributeNS(NULL,id));
186 g_Sites.insert(pair<string,site_t>(id.get(),site_t(impl)));
187 impl=XMLHelper::getNextSiblingElement(impl,Site);
195 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Filter startup failed with an exception.");
200 pVer->dwFilterVersion=HTTP_FILTER_REVISION;
201 strncpy(pVer->lpszFilterDesc,"Shibboleth ISAPI Filter",SF_MAX_FILTER_DESC_LEN);
202 pVer->dwFlags=(SF_NOTIFY_ORDER_HIGH |
203 SF_NOTIFY_SECURE_PORT |
204 SF_NOTIFY_NONSECURE_PORT |
205 SF_NOTIFY_PREPROC_HEADERS |
207 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter initialized...");
211 extern "C" BOOL WINAPI TerminateFilter(DWORD)
214 g_Config->shutdown();
216 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter shut down...");
220 /* Next up, some suck-free versions of various APIs.
222 You DON'T require people to guess the buffer size and THEN tell them the right size.
223 Returning an LPCSTR is apparently way beyond their ken. Not to mention the fact that
224 constant strings aren't typed as such, making it just that much harder. These versions
225 are now updated to use a special growable buffer object, modeled after the standard
226 string class. The standard string won't work because they left out the option to
227 pre-allocate a non-constant buffer.
233 dynabuf() { bufptr=NULL; buflen=0; }
234 dynabuf(size_t s) { bufptr=new char[buflen=s]; *bufptr=0; }
235 ~dynabuf() { delete[] bufptr; }
236 size_t length() const { return bufptr ? strlen(bufptr) : 0; }
237 size_t size() const { return buflen; }
238 bool empty() const { return length()==0; }
239 void reserve(size_t s, bool keep=false);
240 void erase() { if (bufptr) memset(bufptr,0,buflen); }
241 operator char*() { return bufptr; }
242 bool operator ==(const char* s) const;
243 bool operator !=(const char* s) const { return !(*this==s); }
249 void dynabuf::reserve(size_t s, bool keep)
256 p[buflen]=bufptr[buflen];
262 bool dynabuf::operator==(const char* s) const
264 if (buflen==NULL || s==NULL)
265 return (buflen==NULL && s==NULL);
267 return strcmp(bufptr,s)==0;
270 void GetServerVariable(PHTTP_FILTER_CONTEXT pfc, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
276 while (!pfc->GetServerVariable(pfc,lpszVariable,s,&size)) {
277 // Grumble. Check the error.
278 DWORD e=GetLastError();
279 if (e==ERROR_INSUFFICIENT_BUFFER)
284 if (bRequired && s.empty())
288 void GetServerVariable(LPEXTENSION_CONTROL_BLOCK lpECB, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
294 while (!lpECB->GetServerVariable(lpECB->ConnID,lpszVariable,s,&size)) {
295 // Grumble. Check the error.
296 DWORD e=GetLastError();
297 if (e==ERROR_INSUFFICIENT_BUFFER)
302 if (bRequired && s.empty())
306 void GetHeader(PHTTP_FILTER_PREPROC_HEADERS pn, PHTTP_FILTER_CONTEXT pfc,
307 LPSTR lpszName, dynabuf& s, DWORD size=80, bool bRequired=true)
313 while (!pn->GetHeader(pfc,lpszName,s,&size)) {
314 // Grumble. Check the error.
315 DWORD e=GetLastError();
316 if (e==ERROR_INSUFFICIENT_BUFFER)
321 if (bRequired && s.empty())
325 /****************************************************************************/
328 class ShibTargetIsapiF : public AbstractSPRequest
330 PHTTP_FILTER_CONTEXT m_pfc;
331 PHTTP_FILTER_PREPROC_HEADERS m_pn;
332 map<string,string> m_headers;
333 vector<XSECCryptoX509*> m_certs;
335 string m_scheme,m_hostname,m_uri;
336 mutable string m_remote_addr,m_content_type,m_method;
339 ShibTargetIsapiF(PHTTP_FILTER_CONTEXT pfc, PHTTP_FILTER_PREPROC_HEADERS pn, const site_t& site) {
344 // URL path always come from IIS.
346 GetHeader(pn,pfc,"url",var,256,false);
349 // Port may come from IIS or from site def.
350 if (!g_bNormalizeRequest || (pfc->fIsSecurePort && site.m_sslport.empty()) || (!pfc->fIsSecurePort && site.m_port.empty())) {
351 GetServerVariable(pfc,"SERVER_PORT",var,10);
354 else if (pfc->fIsSecurePort) {
355 m_port = atoi(site.m_sslport.c_str());
358 m_port = atoi(site.m_port.c_str());
361 // Scheme may come from site def or be derived from IIS.
362 m_scheme=site.m_scheme;
363 if (m_scheme.empty() || !g_bNormalizeRequest)
364 m_scheme=pfc->fIsSecurePort ? "https" : "http";
366 GetServerVariable(pfc,"SERVER_NAME",var,32);
368 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
370 if (site.m_name!=m_hostname && site.m_aliases.find(m_hostname)==site.m_aliases.end())
371 m_hostname=site.m_name;
373 ~ShibTargetIsapiF() { }
375 const char* getScheme() const {
376 return m_scheme.c_str();
378 const char* getHostname() const {
379 return m_hostname.c_str();
381 int getPort() const {
384 const char* getRequestURI() const {
385 return m_uri.c_str();
387 const char* getMethod() const {
388 if (m_method.empty()) {
390 GetServerVariable(m_pfc,"REQUEST_METHOD",var,5,false);
394 return m_method.c_str();
396 string getContentType() const {
397 if (m_content_type.empty()) {
399 GetServerVariable(m_pfc,"CONTENT_TYPE",var,32,false);
401 m_content_type = var;
403 return m_content_type;
405 long getContentLength() const {
408 string getRemoteAddr() const {
409 if (m_remote_addr.empty()) {
411 GetServerVariable(m_pfc,"REMOTE_ADDR",var,16,false);
415 return m_remote_addr;
417 void log(SPLogLevel level, const string& msg) {
418 AbstractSPRequest::log(level,msg);
419 if (level >= SPError)
420 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
422 void clearHeader(const char* name) {
423 string hdr(!strcmp(name,"REMOTE_USER") ? "remote-user" : name);
425 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), "");
427 void setHeader(const char* name, const char* value) {
430 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), const_cast<char*>(value));
432 string getHeader(const char* name) const {
436 GetHeader(m_pn, m_pfc, const_cast<char*>(hdr.c_str()), buf, 1024, false);
439 void setRemoteUser(const char* user) {
440 setHeader("remote-user", user);
442 string getRemoteUser() const {
443 return getHeader("remote-user");
445 void setResponseHeader(const char* name, const char* value) {
448 m_headers[name] = value;
450 m_headers.erase(name);
452 long sendResponse(istream& in, long status) {
453 string hdr = string("Connection: close\r\n");
454 for (map<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
455 hdr += i->first + ": " + i->second + "\r\n";
457 const char* codestr="200 OK";
459 case SAML_HTTP_STATUS_FORBIDDEN:codestr="403 Forbidden"; break;
460 case SAML_HTTP_STATUS_NOTFOUND: codestr="404 Not Found"; break;
461 case SAML_HTTP_STATUS_ERROR: codestr="500 Server Error"; break;
463 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, (void*)codestr, (DWORD)hdr.c_str(), 0);
467 DWORD resplen = in.gcount();
468 m_pfc->WriteClient(m_pfc, buf, &resplen, 0);
470 return SF_STATUS_REQ_FINISHED;
472 long sendRedirect(const char* url) {
473 // XXX: Don't support the httpRedirect option, yet.
474 string hdr=string("Location: ") + url + "\r\n"
475 "Content-Type: text/html\r\n"
476 "Content-Length: 40\r\n"
477 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
478 "Cache-Control: private,no-store,no-cache\r\n";
479 for (map<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
480 hdr += i->first + ": " + i->second + "\r\n";
482 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, "302 Please Wait", (DWORD)hdr.c_str(), 0);
483 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
485 m_pfc->WriteClient(m_pfc, (LPVOID)redmsg, &resplen, 0);
486 return SF_STATUS_REQ_FINISHED;
488 long returnDecline() {
489 return SF_STATUS_REQ_NEXT_NOTIFICATION;
492 return SF_STATUS_REQ_NEXT_NOTIFICATION;
495 const vector<XSECCryptoX509*>& getClientCertificates() const {
499 // The filter never processes the POST, so stub these methods.
500 const char* getQueryString() const { throw runtime_error("getQueryString not implemented"); }
501 const char* getRequestBody() const { throw runtime_error("getRequestBody not implemented"); }
504 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const char* msg)
506 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
507 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
508 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)ctype,0);
509 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Filter Error</TITLE></HEAD><BODY>"
510 "<H1>Shibboleth Filter Error</H1>";
511 DWORD resplen=strlen(xmsg);
512 pfc->WriteClient(pfc,(LPVOID)xmsg,&resplen,0);
514 pfc->WriteClient(pfc,(LPVOID)msg,&resplen,0);
515 static const char* xmsg2="</BODY></HTML>";
516 resplen=strlen(xmsg2);
517 pfc->WriteClient(pfc,(LPVOID)xmsg2,&resplen,0);
518 return SF_STATUS_REQ_FINISHED;
521 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
523 // Is this a log notification?
524 if (notificationType==SF_NOTIFY_LOG)
526 if (pfc->pFilterContext)
527 ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=static_cast<LPCSTR>(pfc->pFilterContext);
528 return SF_STATUS_REQ_NEXT_NOTIFICATION;
531 PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
534 // Determine web site number. This can't really fail, I don't think.
536 GetServerVariable(pfc,"INSTANCE_ID",buf,10);
538 // Match site instance to host name, skip if no match.
539 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
540 if (map_i==g_Sites.end())
541 return SF_STATUS_REQ_NEXT_NOTIFICATION;
543 ostringstream threadid;
544 threadid << "[" << getpid() << "] isapi_shib" << '\0';
545 xmltooling::NDC ndc(threadid.str().c_str());
547 ShibTargetIsapiF stf(pfc, pn, map_i->second);
549 // "false" because we don't override the Shib settings
550 pair<bool,long> res = stf.getServiceProvider().doAuthentication(stf);
551 if (res.first) return res.second;
553 // "false" because we don't override the Shib settings
554 res = stf.getServiceProvider().doExport(stf);
555 if (res.first) return res.second;
557 res = stf.getServiceProvider().doAuthorization(stf);
558 if (res.first) return res.second;
560 return SF_STATUS_REQ_NEXT_NOTIFICATION;
563 return WriteClientError(pfc,"Out of Memory");
566 if (e==ERROR_NO_DATA)
567 return WriteClientError(pfc,"A required variable or header was empty.");
569 return WriteClientError(pfc,"Shibboleth Filter detected unexpected IIS error.");
571 catch (exception& e) {
572 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
573 return WriteClientError(pfc,"Shibboleth Filter caught an exception, check Event Log for details.");
577 return WriteClientError(pfc,"Shibboleth Filter caught an unknown exception.");
581 return WriteClientError(pfc,"Shibboleth Filter reached unreachable code, save my walrus!");
585 /****************************************************************************/
588 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* msg)
590 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
591 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
592 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
593 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Error</TITLE></HEAD><BODY><H1>Shibboleth Error</H1>";
594 DWORD resplen=strlen(xmsg);
595 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg,&resplen,HSE_IO_SYNC);
597 lpECB->WriteClient(lpECB->ConnID,(LPVOID)msg,&resplen,HSE_IO_SYNC);
598 static const char* xmsg2="</BODY></HTML>";
599 resplen=strlen(xmsg2);
600 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg2,&resplen,HSE_IO_SYNC);
601 return HSE_STATUS_SUCCESS;
605 class ShibTargetIsapiE : public AbstractSPRequest
607 LPEXTENSION_CONTROL_BLOCK m_lpECB;
608 map<string,string> m_headers;
609 vector<XSECCryptoX509*> m_certs;
610 mutable string m_body;
611 mutable bool m_gotBody;
613 string m_scheme,m_hostname,m_uri;
614 mutable string m_remote_addr;
617 ShibTargetIsapiE(LPEXTENSION_CONTROL_BLOCK lpECB, const site_t& site) : m_lpECB(lpECB), m_gotBody(false) {
619 GetServerVariable(lpECB,"HTTPS",ssl,5);
620 bool SSL=(ssl=="on" || ssl=="ON");
622 // Scheme may come from site def or be derived from IIS.
623 m_scheme=site.m_scheme;
624 if (m_scheme.empty() || !g_bNormalizeRequest)
625 m_scheme = SSL ? "https" : "http";
627 // URL path always come from IIS.
629 GetServerVariable(lpECB,"URL",url,255);
631 // Port may come from IIS or from site def.
633 if (!g_bNormalizeRequest || (SSL && site.m_sslport.empty()) || (!SSL && site.m_port.empty()))
634 GetServerVariable(lpECB,"SERVER_PORT",port,10);
636 strncpy(port,site.m_sslport.c_str(),10);
637 static_cast<char*>(port)[10]=0;
640 strncpy(port,site.m_port.c_str(),10);
641 static_cast<char*>(port)[10]=0;
646 GetServerVariable(lpECB, "SERVER_NAME", var, 32);
648 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
650 if (site.m_name!=m_hostname && site.m_aliases.find(m_hostname)==site.m_aliases.end())
651 m_hostname=site.m_name;
654 * IIS screws us over on PATH_INFO (the hits keep on coming). We need to figure out if
655 * the server is set up for proper PATH_INFO handling, or "IIS sucks rabid weasels mode",
656 * which is the default. No perfect way to tell, but we can take a good guess by checking
657 * whether the URL is a substring of the PATH_INFO:
659 * e.g. for /Shibboleth.sso/SAML/POST
661 * Bad mode (default):
662 * URL: /Shibboleth.sso
663 * PathInfo: /Shibboleth.sso/SAML/POST
666 * URL: /Shibboleth.sso
667 * PathInfo: /SAML/POST
670 // Clearly we're only in bad mode if path info exists at all.
671 if (lpECB->lpszPathInfo && *(lpECB->lpszPathInfo)) {
672 if (strstr(lpECB->lpszPathInfo,url))
673 // Pretty good chance we're in bad mode, unless the PathInfo repeats the path itself.
674 m_uri = lpECB->lpszPathInfo;
677 m_uri += lpECB->lpszPathInfo;
681 // For consistency with Apache, let's add the query string.
682 if (lpECB->lpszQueryString && *(lpECB->lpszQueryString)) {
684 m_uri += lpECB->lpszQueryString;
687 ~ShibTargetIsapiE() { }
689 const char* getScheme() const {
690 return m_scheme.c_str();
692 const char* getHostname() const {
693 return m_hostname.c_str();
695 int getPort() const {
698 const char* getRequestURI() const {
699 return m_uri.c_str();
701 const char* getMethod() const {
702 return m_lpECB->lpszMethod;
704 string getContentType() const {
705 return m_lpECB->lpszContentType ? m_lpECB->lpszContentType : "";
707 long getContentLength() const {
708 return m_lpECB->cbTotalBytes;
710 string getRemoteAddr() const {
711 if (m_remote_addr.empty()) {
713 GetServerVariable(m_lpECB, "REMOTE_ADDR", var, 16, false);
717 return m_remote_addr;
719 void log(SPLogLevel level, const string& msg) {
720 AbstractSPRequest::log(level,msg);
721 if (level >= SPError)
722 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
724 string getHeader(const char* name) const {
726 for (; *name; ++name) {
730 hdr += toupper(*name);
733 GetServerVariable(m_lpECB, const_cast<char*>(hdr.c_str()), buf, 128, false);
734 return buf.empty() ? "" : buf;
736 void setResponseHeader(const char* name, const char* value) {
739 m_headers[name] = value;
741 m_headers.erase(name);
743 const char* getQueryString() const {
744 return m_lpECB->lpszQueryString;
746 const char* getRequestBody() const {
748 return m_body.c_str();
749 if (m_lpECB->cbTotalBytes > 1024*1024) // 1MB?
750 throw opensaml::BindingException("Size of POST request body exceeded limit.");
751 else if (m_lpECB->cbTotalBytes != m_lpECB->cbAvailable) {
754 DWORD datalen=m_lpECB->cbTotalBytes;
757 BOOL ret = m_lpECB->ReadClient(m_lpECB->ConnID, buf, &buflen);
759 throw saml::SAMLException("Error reading POST request body from browser.");
760 m_body.append(buf, buflen);
766 m_body.assign(reinterpret_cast<char*>(m_lpECB->lpbData),m_lpECB->cbAvailable);
768 return m_body.c_str();
770 long sendResponse(istream& in, long status) {
771 string hdr = string("Connection: close\r\n");
772 for (map<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
773 hdr += i->first + ": " + i->second + "\r\n";
775 const char* codestr="200 OK";
777 case SAML_HTTP_STATUS_FORBIDDEN:codestr="403 Forbidden"; break;
778 case SAML_HTTP_STATUS_NOTFOUND: codestr="404 Not Found"; break;
779 case SAML_HTTP_STATUS_ERROR: codestr="500 Server Error"; break;
781 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, (void*)codestr, 0, (LPDWORD)hdr.c_str());
785 DWORD resplen = in.gcount();
786 m_lpECB->WriteClient(m_lpECB->ConnID, buf, &resplen, HSE_IO_SYNC);
788 return HSE_STATUS_SUCCESS;
790 long sendRedirect(const char* url) {
791 string hdr=string("Location: ") + url + "\r\n"
792 "Content-Type: text/html\r\n"
793 "Content-Length: 40\r\n"
794 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
795 "Cache-Control: private,no-store,no-cache\r\n";
796 for (map<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
797 hdr += i->first + ": " + i->second + "\r\n";
799 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, "302 Moved", 0, (LPDWORD)hdr.c_str());
800 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
802 m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)redmsg, &resplen, HSE_IO_SYNC);
803 return HSE_STATUS_SUCCESS;
805 // Decline happens in the POST processor if this isn't the shire url
806 // Note that it can also happen with HTAccess, but we don't support that, yet.
807 long returnDecline() {
808 return WriteClientError(
810 "ISAPI extension can only be invoked to process Shibboleth protocol requests."
811 "Make sure the mapped file extension doesn't match actual content."
815 return HSE_STATUS_SUCCESS;
818 const vector<XSECCryptoX509*>& getClientCertificates() const {
822 // Not used in the extension.
823 virtual void clearHeader(const char* name) { throw runtime_error("clearHeader not implemented"); }
824 virtual void setHeader(const char* name, const char* value) { throw runtime_error("setHeader not implemented"); }
825 virtual void setRemoteUser(const char* user) { throw runtime_error("setRemoteUser not implemented"); }
826 virtual string getRemoteUser() const { throw runtime_error("getRemoteUser not implemented"); }
829 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
832 ostringstream threadid;
833 threadid << "[" << getpid() << "] isapi_shib_extension" << '\0';
834 xmltooling::NDC ndc(threadid.str().c_str());
836 // Determine web site number. This can't really fail, I don't think.
838 GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
840 // Match site instance to host name, skip if no match.
841 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
842 if (map_i==g_Sites.end())
843 return WriteClientError(lpECB, "Shibboleth Extension not configured for this web site.");
845 ShibTargetIsapiE ste(lpECB, map_i->second);
846 pair<bool,long> res = ste.getServiceProvider().doHandler(ste);
847 if (res.first) return res.second;
849 return WriteClientError(lpECB, "Shibboleth Extension failed to process request");
853 return WriteClientError(lpECB,"Out of Memory");
856 if (e==ERROR_NO_DATA)
857 return WriteClientError(lpECB,"A required variable or header was empty.");
859 return WriteClientError(lpECB,"Server detected unexpected IIS error.");
861 catch (exception& e) {
862 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
863 return WriteClientError(lpECB,"Shibboleth Extension caught an exception, check Event Log for details.");
867 return WriteClientError(lpECB,"Shibboleth Extension caught an unknown exception.");
871 // If we get here we've got an error.
872 return HSE_STATUS_ERROR;