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;
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",url,256,false);
348 // 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",port,10);
352 else if (pfc->fIsSecurePort) {
353 strncpy(port,site.m_sslport.c_str(),10);
354 static_cast<char*>(port)[10]=0;
357 strncpy(port,site.m_port.c_str(),10);
358 static_cast<char*>(port)[10]=0;
361 // Scheme may come from site def or be derived from IIS.
362 const char* scheme=site.m_scheme.c_str();
363 if (!scheme || !*scheme || !g_bNormalizeRequest)
364 scheme=pfc->fIsSecurePort ? "https" : "http";
366 // Get the rest of the server variables.
367 dynabuf remote_addr(16),method(5),content_type(32),hostname(32);
368 GetServerVariable(pfc,"SERVER_NAME",hostname,32);
369 GetServerVariable(pfc,"REMOTE_ADDR",remote_addr,16);
370 GetServerVariable(pfc,"REQUEST_METHOD",method,5,false);
371 GetServerVariable(pfc,"CONTENT_TYPE",content_type,32,false);
373 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
374 const char* host=hostname;
375 if (site.m_name!=host && site.m_aliases.find(host)==site.m_aliases.end())
376 host=site.m_name.c_str();
378 init(scheme, host, atoi(port), url, content_type, remote_addr, method);
380 ~ShibTargetIsapiF() { }
382 const char* getScheme() const {
383 return m_scheme.c_str();
385 const char* getHostname() const {
386 return m_hostname.c_str();
388 int getPort() const {
391 const char* getRequestURI() const {
392 return m_uri.c_str();
394 const char* getMethod() const {
395 return m_method.c_str();
397 string getContentType() const {
398 return m_content_type;
400 long getContentLength() const {
403 string getRemoteAddr() const {
404 return m_remote_addr;
406 void log(SPLogLevel level, const string& msg) {
407 AbstractSPRequest::log(level,msg);
408 if (level >= SPError)
409 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
411 void clearHeader(const char* name) {
412 string hdr(!strcmp(name,"REMOTE_USER") ? "remote-user" : name);
414 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), "");
416 void setHeader(const char* name, const char* value) {
419 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), const_cast<char*>(value));
421 string getHeader(const char* name) const {
425 GetHeader(m_pn, m_pfc, const_cast<char*>(hdr.c_str()), buf, 1024, false);
428 void setRemoteUser(const char* user) {
429 setHeader("remote-user", user);
431 string getRemoteUser() const {
432 return getHeader("remote-user");
434 void setResponseHeader(const char* name, const char* value) {
437 m_headers[name] = value;
439 m_headers.erase(name);
441 long sendResponse(istream& in, long status) {
442 string hdr = string("Connection: close\r\n");
443 for (map<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
444 hdr += i->first + ": " + i->second + "\r\n";
446 const char* codestr="200 OK";
448 case SAML_HTTP_STATUS_FORBIDDEN:codestr="403 Forbidden"; break;
449 case SAML_HTTP_STATUS_NOTFOUND: codestr="404 Not Found"; break;
450 case SAML_HTTP_STATUS_ERROR: codestr="500 Server Error"; break;
452 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, (void*)codestr, (DWORD)hdr.c_str(), 0);
456 DWORD resplen = in.gcount();
457 m_pfc->WriteClient(m_pfc, buf, &resplen, 0);
459 return SF_STATUS_REQ_FINISHED;
461 long sendRedirect(const char* url) {
462 // XXX: Don't support the httpRedirect option, yet.
463 string hdr=string("Location: ") + url + "\r\n"
464 "Content-Type: text/html\r\n"
465 "Content-Length: 40\r\n"
466 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
467 "Cache-Control: private,no-store,no-cache\r\n";
468 for (map<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
469 hdr += i->first + ": " + i->second + "\r\n";
471 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, "302 Please Wait", (DWORD)hdr.c_str(), 0);
472 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
474 m_pfc->WriteClient(m_pfc, (LPVOID)redmsg, &resplen, 0);
475 return SF_STATUS_REQ_FINISHED;
477 // XXX: We might not ever hit the 'decline' status in this filter.
478 //long returnDecline(void) { }
479 long returnOK(void) {
480 return SF_STATUS_REQ_NEXT_NOTIFICATION;
483 const vector<XSECCryptoX509*>& getClientCertificates() const {
487 // The filter never processes the POST, so stub these methods.
488 const char* getQueryString() const { throw runtime_error("getQueryString not implemented"); }
489 const char* getRequestBody() const { throw runtime_error("getRequestBody not implemented"); }
492 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const char* msg)
494 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
495 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
496 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)ctype,0);
497 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Filter Error</TITLE></HEAD><BODY>"
498 "<H1>Shibboleth Filter Error</H1>";
499 DWORD resplen=strlen(xmsg);
500 pfc->WriteClient(pfc,(LPVOID)xmsg,&resplen,0);
502 pfc->WriteClient(pfc,(LPVOID)msg,&resplen,0);
503 static const char* xmsg2="</BODY></HTML>";
504 resplen=strlen(xmsg2);
505 pfc->WriteClient(pfc,(LPVOID)xmsg2,&resplen,0);
506 return SF_STATUS_REQ_FINISHED;
509 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
511 // Is this a log notification?
512 if (notificationType==SF_NOTIFY_LOG)
514 if (pfc->pFilterContext)
515 ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=static_cast<LPCSTR>(pfc->pFilterContext);
516 return SF_STATUS_REQ_NEXT_NOTIFICATION;
519 PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
522 // Determine web site number. This can't really fail, I don't think.
524 GetServerVariable(pfc,"INSTANCE_ID",buf,10);
526 // Match site instance to host name, skip if no match.
527 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
528 if (map_i==g_Sites.end())
529 return SF_STATUS_REQ_NEXT_NOTIFICATION;
531 ostringstream threadid;
532 threadid << "[" << getpid() << "] isapi_shib" << '\0';
533 xmltooling::NDC ndc(threadid.str().c_str());
535 ShibTargetIsapiF stf(pfc, pn, map_i->second);
537 // "false" because we don't override the Shib settings
538 pair<bool,long> res = stf.doCheckAuthN();
539 if (res.first) return res.second;
541 // "false" because we don't override the Shib settings
542 res = stf.doExportAssertions();
543 if (res.first) return res.second;
545 res = stf.doCheckAuthZ();
546 if (res.first) return res.second;
548 return SF_STATUS_REQ_NEXT_NOTIFICATION;
551 return WriteClientError(pfc,"Out of Memory");
554 if (e==ERROR_NO_DATA)
555 return WriteClientError(pfc,"A required variable or header was empty.");
557 return WriteClientError(pfc,"Shibboleth Filter detected unexpected IIS error.");
559 catch (exception& e) {
560 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
561 return WriteClientError(pfc,"Shibboleth Filter caught an exception, check Event Log for details.");
565 return WriteClientError(pfc,"Shibboleth Filter caught an unknown exception.");
569 return WriteClientError(pfc,"Shibboleth Filter reached unreachable code, save my walrus!");
573 /****************************************************************************/
576 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* msg)
578 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
579 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
580 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
581 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Error</TITLE></HEAD><BODY><H1>Shibboleth Error</H1>";
582 DWORD resplen=strlen(xmsg);
583 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg,&resplen,HSE_IO_SYNC);
585 lpECB->WriteClient(lpECB->ConnID,(LPVOID)msg,&resplen,HSE_IO_SYNC);
586 static const char* xmsg2="</BODY></HTML>";
587 resplen=strlen(xmsg2);
588 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg2,&resplen,HSE_IO_SYNC);
589 return HSE_STATUS_SUCCESS;
593 class ShibTargetIsapiE : public ShibTarget
595 LPEXTENSION_CONTROL_BLOCK m_lpECB;
596 map<string,string> m_headers;
597 vector<XSECCryptoX509*> m_certs;
598 mutable string m_body;
599 mutable bool m_gotBody;
602 ShibTargetIsapiE(LPEXTENSION_CONTROL_BLOCK lpECB, const site_t& site) : m_lpECB(lpECB), m_gotBody(false) {
604 GetServerVariable(lpECB,"HTTPS",ssl,5);
605 bool SSL=(ssl=="on" || ssl=="ON");
607 // URL path always come from IIS.
609 GetServerVariable(lpECB,"URL",url,255);
611 // Port may come from IIS or from site def.
613 if (!g_bNormalizeRequest || (SSL && site.m_sslport.empty()) || (!SSL && site.m_port.empty()))
614 GetServerVariable(lpECB,"SERVER_PORT",port,10);
616 strncpy(port,site.m_sslport.c_str(),10);
617 static_cast<char*>(port)[10]=0;
620 strncpy(port,site.m_port.c_str(),10);
621 static_cast<char*>(port)[10]=0;
624 // Scheme may come from site def or be derived from IIS.
625 const char* scheme=site.m_scheme.c_str();
626 if (!scheme || !*scheme || !g_bNormalizeRequest) {
627 scheme = SSL ? "https" : "http";
630 // Get the other server variables.
631 dynabuf remote_addr(16),hostname(32);
632 GetServerVariable(lpECB, "REMOTE_ADDR", remote_addr, 16);
633 GetServerVariable(lpECB, "SERVER_NAME", hostname, 32);
635 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
636 const char* host=hostname;
637 if (site.m_name!=host && site.m_aliases.find(host)==site.m_aliases.end())
638 host=site.m_name.c_str();
641 * IIS screws us over on PATH_INFO (the hits keep on coming). We need to figure out if
642 * the server is set up for proper PATH_INFO handling, or "IIS sucks rabid weasels mode",
643 * which is the default. No perfect way to tell, but we can take a good guess by checking
644 * whether the URL is a substring of the PATH_INFO:
646 * e.g. for /Shibboleth.sso/SAML/POST
648 * Bad mode (default):
649 * URL: /Shibboleth.sso
650 * PathInfo: /Shibboleth.sso/SAML/POST
653 * URL: /Shibboleth.sso
654 * PathInfo: /SAML/POST
659 // Clearly we're only in bad mode if path info exists at all.
660 if (lpECB->lpszPathInfo && *(lpECB->lpszPathInfo)) {
661 if (strstr(lpECB->lpszPathInfo,url))
662 // Pretty good chance we're in bad mode, unless the PathInfo repeats the path itself.
663 fullurl=lpECB->lpszPathInfo;
666 fullurl+=lpECB->lpszPathInfo;
670 // For consistency with Apache, let's add the query string.
671 if (lpECB->lpszQueryString && *(lpECB->lpszQueryString)) {
673 fullurl+=lpECB->lpszQueryString;
675 init(scheme, host, atoi(port), fullurl.c_str(), lpECB->lpszContentType, remote_addr, lpECB->lpszMethod);
677 ~ShibTargetIsapiE() { }
679 const char* getScheme() const {
680 return m_scheme.c_str();
682 const char* getHostname() const {
683 return m_hostname.c_str();
685 int getPort() const {
688 const char* getRequestURI() const {
689 return m_uri.c_str();
691 const char* getMethod() const {
692 return m_lpECB->lpszMethod ? m_lpECB->lpszMethod : "";
694 string getContentType() const {
695 return m_lpECB->lpszContentType ? m_lpECB->lpszContentType : "";
697 long getContentLength() const {
698 return m_lpECB->cbTotalBytes;
700 string getRemoteAddr() const {
701 return m_remote_addr;
703 void log(SPLogLevel level, const string& msg) {
704 AbstractSPRequest::log(level,msg);
705 if (level >= SPError)
706 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
708 string getHeader(const char* name) const {
710 for (; *name; ++name) {
714 hdr += toupper(*name);
717 GetServerVariable(m_lpECB, const_cast<char*>(hdr.c_str()), buf, 128, false);
718 return buf.empty() ? "" : buf;
720 void setResponseHeader(const char* name, const char* value) {
723 m_headers[name] = value;
725 m_headers.erase(name);
727 const char* getQueryString() const {
728 return m_lpECB->lpszQueryString;
730 const char* getRequestBody() const {
732 return m_body.c_str();
733 if (m_lpECB->cbTotalBytes > 1024*1024) // 1MB?
734 throw opensaml::BindingException("Size of POST request body exceeded limit.");
735 else if (m_lpECB->cbTotalBytes != m_lpECB->cbAvailable) {
738 DWORD datalen=m_lpECB->cbTotalBytes;
741 BOOL ret = m_lpECB->ReadClient(m_lpECB->ConnID, buf, &buflen);
743 throw saml::SAMLException("Error reading POST request body from browser.");
744 m_body.append(buf, buflen);
750 m_body.assign(reinterpret_cast<char*>(m_lpECB->lpbData),m_lpECB->cbAvailable);
752 return m_body.c_str();
754 long sendResponse(istream& in, long status) {
755 string hdr = string("Connection: close\r\n");
756 for (map<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
757 hdr += i->first + ": " + i->second + "\r\n";
759 const char* codestr="200 OK";
761 case SAML_HTTP_STATUS_FORBIDDEN:codestr="403 Forbidden"; break;
762 case SAML_HTTP_STATUS_NOTFOUND: codestr="404 Not Found"; break;
763 case SAML_HTTP_STATUS_ERROR: codestr="500 Server Error"; break;
765 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, (void*)codestr, 0, (LPDWORD)hdr.c_str());
769 DWORD resplen = in.gcount();
770 m_lpECB->WriteClient(m_lpECB->ConnID, buf, &resplen, HSE_IO_SYNC);
772 return HSE_STATUS_SUCCESS;
774 long sendRedirect(const char* url) {
775 string hdr=string("Location: ") + url + "\r\n"
776 "Content-Type: text/html\r\n"
777 "Content-Length: 40\r\n"
778 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
779 "Cache-Control: private,no-store,no-cache\r\n";
780 for (map<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
781 hdr += i->first + ": " + i->second + "\r\n";
783 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, "302 Moved", 0, (LPDWORD)hdr.c_str());
784 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
786 m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)redmsg, &resplen, HSE_IO_SYNC);
787 return HSE_STATUS_SUCCESS;
789 // Decline happens in the POST processor if this isn't the shire url
790 // Note that it can also happen with HTAccess, but we don't support that, yet.
791 long returnDecline() {
792 return WriteClientError(
794 "ISAPI extension can only be invoked to process Shibboleth protocol requests."
795 "Make sure the mapped file extension doesn't match actual content."
799 return HSE_STATUS_SUCCESS;
802 const vector<XSECCryptoX509*>& getClientCertificates() const {
806 // Not used in the extension.
807 virtual void clearHeader(const char* name) { throw runtime_error("clearHeader not implemented"); }
808 virtual void setHeader(const char* name, const char* value) { throw runtime_error("setHeader not implemented"); }
809 virtual void setRemoteUser(const char* user) { throw runtime_error("setRemoteUser not implemented"); }
810 virtual string getRemoteUser() const { throw runtime_error("getRemoteUser not implemented"); }
813 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
816 const IApplication* application=NULL;
818 ostringstream threadid;
819 threadid << "[" << getpid() << "] isapi_shib_extension" << '\0';
820 xmltooling::NDC ndc(threadid.str().c_str());
822 // Determine web site number. This can't really fail, I don't think.
824 GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
826 // Match site instance to host name, skip if no match.
827 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
828 if (map_i==g_Sites.end())
829 return WriteClientError(lpECB, "Shibboleth Extension not configured for this web site.");
831 ShibTargetIsapiE ste(lpECB, map_i->second);
832 pair<bool,long> res = ste.doHandler();
833 if (res.first) return res.second;
835 return WriteClientError(lpECB, "Shibboleth Extension failed to process request");
839 return WriteClientError(lpECB,"Out of Memory");
842 if (e==ERROR_NO_DATA)
843 return WriteClientError(lpECB,"A required variable or header was empty.");
845 return WriteClientError(lpECB,"Server detected unexpected IIS error.");
847 catch (exception& e) {
848 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
849 return WriteClientError(lpECB,"Shibboleth Extension caught an exception, check Event Log for details.");
853 return WriteClientError(lpECB,"Shibboleth Extension caught an unknown exception.");
857 // If we get here we've got an error.
858 return HSE_STATUS_ERROR;