2 * Copyright 2001-2007 Internet2
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
20 * Shibboleth ISAPI filter
23 #include "config_win32.h"
25 #define _CRT_NONSTDC_NO_DEPRECATE 1
26 #define _CRT_SECURE_NO_DEPRECATE 1
28 #include <shibsp/AbstractSPRequest.h>
29 #include <shibsp/SPConfig.h>
30 #include <shibsp/ServiceProvider.h>
31 #include <xmltooling/unicode.h>
32 #include <xmltooling/XMLToolingConfig.h>
33 #include <xmltooling/util/NDC.h>
34 #include <xmltooling/util/XMLHelper.h>
35 #include <xercesc/util/XMLUniDefs.hpp>
45 using namespace shibsp;
46 using namespace xmltooling;
47 using namespace xercesc;
52 static const XMLCh path[] = UNICODE_LITERAL_4(p,a,t,h);
53 static const XMLCh validate[] = UNICODE_LITERAL_8(v,a,l,i,d,a,t,e);
54 static const XMLCh name[] = UNICODE_LITERAL_4(n,a,m,e);
55 static const XMLCh port[] = UNICODE_LITERAL_4(p,o,r,t);
56 static const XMLCh sslport[] = UNICODE_LITERAL_7(s,s,l,p,o,r,t);
57 static const XMLCh scheme[] = UNICODE_LITERAL_6(s,c,h,e,m,e);
58 static const XMLCh id[] = UNICODE_LITERAL_2(i,d);
59 static const XMLCh Implementation[] = UNICODE_LITERAL_14(I,m,p,l,e,m,e,n,t,a,t,i,o,n);
60 static const XMLCh ISAPI[] = UNICODE_LITERAL_5(I,S,A,P,I);
61 static const XMLCh Alias[] = UNICODE_LITERAL_5(A,l,i,a,s);
62 static const XMLCh normalizeRequest[] = UNICODE_LITERAL_16(n,o,r,m,a,l,i,z,e,R,e,q,u,e,s,t);
63 static const XMLCh Site[] = UNICODE_LITERAL_4(S,i,t,e);
66 site_t(const DOMElement* e)
68 auto_ptr_char n(e->getAttributeNS(NULL,name));
69 auto_ptr_char s(e->getAttributeNS(NULL,scheme));
70 auto_ptr_char p(e->getAttributeNS(NULL,port));
71 auto_ptr_char p2(e->getAttributeNS(NULL,sslport));
72 if (n.get()) m_name=n.get();
73 if (s.get()) m_scheme=s.get();
74 if (p.get()) m_port=p.get();
75 if (p2.get()) m_sslport=p2.get();
76 e = XMLHelper::getFirstChildElement(e, Alias);
78 if (e->hasChildNodes()) {
79 auto_ptr_char alias(e->getFirstChild()->getNodeValue());
80 m_aliases.insert(alias.get());
82 e = XMLHelper::getNextSiblingElement(e, Alias);
85 string m_scheme,m_port,m_sslport,m_name;
86 set<string> m_aliases;
90 SPConfig* g_Config = NULL;
91 map<string,site_t> g_Sites;
92 bool g_bNormalizeRequest = true;
96 LPCSTR lpUNCServerName,
102 LPCSTR messages[] = {message, NULL};
104 HANDLE hElog = RegisterEventSource(lpUNCServerName, "Shibboleth ISAPI Filter");
105 BOOL res = ReportEvent(hElog, wType, 0, dwEventID, lpUserSid, 1, 0, messages, NULL);
106 return (DeregisterEventSource(hElog) && res);
109 extern "C" __declspec(dllexport) BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID)
111 if (fdwReason==DLL_PROCESS_ATTACH)
116 extern "C" BOOL WINAPI GetExtensionVersion(HSE_VERSION_INFO* pVer)
122 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
123 "Extension mode startup not possible, is the DLL loaded as a filter?");
127 pVer->dwExtensionVersion=HSE_VERSION;
128 strncpy(pVer->lpszExtensionDesc,"Shibboleth ISAPI Extension",HSE_MAX_EXT_DLL_NAME_LEN-1);
132 extern "C" BOOL WINAPI TerminateExtension(DWORD)
134 return TRUE; // cleanup should happen when filter unloads
137 extern "C" BOOL WINAPI GetFilterVersion(PHTTP_FILTER_VERSION pVer)
142 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
143 "Reentrant filter initialization, ignoring...");
147 LPCSTR schemadir=getenv("SHIBSCHEMAS");
149 schemadir=SHIBSP_SCHEMAS;
150 LPCSTR config=getenv("SHIBCONFIG");
152 config=SHIBSP_CONFIG;
153 g_Config=&SPConfig::getConfig();
154 g_Config->setFeatures(
157 SPConfig::RequestMapping |
158 SPConfig::InProcess |
161 if (!g_Config->init(schemadir)) {
163 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
164 "Filter startup failed during library initialization, check native log for help.");
169 DOMDocument* dummydoc=XMLToolingConfig::getConfig().getParser().newDocument();
170 XercesJanitor<DOMDocument> docjanitor(dummydoc);
171 DOMElement* dummy = dummydoc->createElementNS(NULL,path);
172 auto_ptr_XMLCh src(config);
173 dummy->setAttributeNS(NULL,path,src.get());
174 dummy->setAttributeNS(NULL,validate,xmlconstants::XML_ONE);
176 g_Config->setServiceProvider(g_Config->ServiceProviderManager.newPlugin(XML_SERVICE_PROVIDER,dummy));
177 g_Config->getServiceProvider()->init();
179 catch (exception& ex) {
182 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, ex.what());
183 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
184 "Filter startup failed to load configuration, check native log for details.");
188 // Access the implementation-specifics for site mappings.
189 ServiceProvider* sp=g_Config->getServiceProvider();
190 xmltooling::Locker locker(sp);
191 const PropertySet* props=sp->getPropertySet("Local");
193 const DOMElement* impl=XMLHelper::getFirstChildElement(props->getElement(),Implementation);
194 if (impl && (impl=XMLHelper::getFirstChildElement(impl,ISAPI))) {
195 const XMLCh* flag=impl->getAttributeNS(NULL,normalizeRequest);
196 g_bNormalizeRequest=(!flag || !*flag || *flag==chDigit_1 || *flag==chLatin_t);
197 impl=XMLHelper::getFirstChildElement(impl,Site);
199 auto_ptr_char id(impl->getAttributeNS(NULL,id));
201 g_Sites.insert(pair<string,site_t>(id.get(),site_t(impl)));
202 impl=XMLHelper::getNextSiblingElement(impl,Site);
207 pVer->dwFilterVersion=HTTP_FILTER_REVISION;
208 strncpy(pVer->lpszFilterDesc,"Shibboleth ISAPI Filter",SF_MAX_FILTER_DESC_LEN);
209 pVer->dwFlags=(SF_NOTIFY_ORDER_HIGH |
210 SF_NOTIFY_SECURE_PORT |
211 SF_NOTIFY_NONSECURE_PORT |
212 SF_NOTIFY_PREPROC_HEADERS |
214 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter initialized...");
218 extern "C" BOOL WINAPI TerminateFilter(DWORD)
223 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter shut down...");
227 /* Next up, some suck-free versions of various APIs.
229 You DON'T require people to guess the buffer size and THEN tell them the right size.
230 Returning an LPCSTR is apparently way beyond their ken. Not to mention the fact that
231 constant strings aren't typed as such, making it just that much harder. These versions
232 are now updated to use a special growable buffer object, modeled after the standard
233 string class. The standard string won't work because they left out the option to
234 pre-allocate a non-constant buffer.
240 dynabuf() { bufptr=NULL; buflen=0; }
241 dynabuf(size_t s) { bufptr=new char[buflen=s]; *bufptr=0; }
242 ~dynabuf() { delete[] bufptr; }
243 size_t length() const { return bufptr ? strlen(bufptr) : 0; }
244 size_t size() const { return buflen; }
245 bool empty() const { return length()==0; }
246 void reserve(size_t s, bool keep=false);
247 void erase() { if (bufptr) memset(bufptr,0,buflen); }
248 operator char*() { return bufptr; }
249 bool operator ==(const char* s) const;
250 bool operator !=(const char* s) const { return !(*this==s); }
256 void dynabuf::reserve(size_t s, bool keep)
263 p[buflen]=bufptr[buflen];
269 bool dynabuf::operator==(const char* s) const
271 if (buflen==NULL || s==NULL)
272 return (buflen==NULL && s==NULL);
274 return strcmp(bufptr,s)==0;
277 void GetServerVariable(PHTTP_FILTER_CONTEXT pfc, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
283 while (!pfc->GetServerVariable(pfc,lpszVariable,s,&size)) {
284 // Grumble. Check the error.
285 DWORD e=GetLastError();
286 if (e==ERROR_INSUFFICIENT_BUFFER)
291 if (bRequired && s.empty())
295 void GetServerVariable(LPEXTENSION_CONTROL_BLOCK lpECB, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
301 while (!lpECB->GetServerVariable(lpECB->ConnID,lpszVariable,s,&size)) {
302 // Grumble. Check the error.
303 DWORD e=GetLastError();
304 if (e==ERROR_INSUFFICIENT_BUFFER)
309 if (bRequired && s.empty())
313 void GetHeader(PHTTP_FILTER_PREPROC_HEADERS pn, PHTTP_FILTER_CONTEXT pfc,
314 LPSTR lpszName, dynabuf& s, DWORD size=80, bool bRequired=true)
320 while (!pn->GetHeader(pfc,lpszName,s,&size)) {
321 // Grumble. Check the error.
322 DWORD e=GetLastError();
323 if (e==ERROR_INSUFFICIENT_BUFFER)
328 if (bRequired && s.empty())
332 /****************************************************************************/
335 class ShibTargetIsapiF : public AbstractSPRequest
337 PHTTP_FILTER_CONTEXT m_pfc;
338 PHTTP_FILTER_PREPROC_HEADERS m_pn;
339 map<string,string> m_headers;
340 vector<XSECCryptoX509*> m_certs;
342 string m_scheme,m_hostname,m_uri;
343 mutable string m_remote_addr,m_content_type,m_method;
346 ShibTargetIsapiF(PHTTP_FILTER_CONTEXT pfc, PHTTP_FILTER_PREPROC_HEADERS pn, const site_t& site) {
351 // URL path always come from IIS.
353 GetHeader(pn,pfc,"url",var,256,false);
356 // Port may come from IIS or from site def.
357 if (!g_bNormalizeRequest || (pfc->fIsSecurePort && site.m_sslport.empty()) || (!pfc->fIsSecurePort && site.m_port.empty())) {
358 GetServerVariable(pfc,"SERVER_PORT",var,10);
361 else if (pfc->fIsSecurePort) {
362 m_port = atoi(site.m_sslport.c_str());
365 m_port = atoi(site.m_port.c_str());
368 // Scheme may come from site def or be derived from IIS.
369 m_scheme=site.m_scheme;
370 if (m_scheme.empty() || !g_bNormalizeRequest)
371 m_scheme=pfc->fIsSecurePort ? "https" : "http";
373 GetServerVariable(pfc,"SERVER_NAME",var,32);
375 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
377 if (site.m_name!=m_hostname && site.m_aliases.find(m_hostname)==site.m_aliases.end())
378 m_hostname=site.m_name;
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 if (m_method.empty()) {
397 GetServerVariable(m_pfc,"REQUEST_METHOD",var,5,false);
401 return m_method.c_str();
403 string getContentType() const {
404 if (m_content_type.empty()) {
406 GetServerVariable(m_pfc,"CONTENT_TYPE",var,32,false);
408 m_content_type = var;
410 return m_content_type;
412 long getContentLength() const {
415 string getRemoteAddr() const {
416 if (m_remote_addr.empty()) {
418 GetServerVariable(m_pfc,"REMOTE_ADDR",var,16,false);
422 return m_remote_addr;
424 void log(SPLogLevel level, const string& msg) {
425 AbstractSPRequest::log(level,msg);
426 if (level >= SPError)
427 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
429 void clearHeader(const char* name) {
430 string hdr(!strcmp(name,"REMOTE_USER") ? "remote-user" : name);
432 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), "");
434 void setHeader(const char* name, const char* value) {
437 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), const_cast<char*>(value));
439 string getHeader(const char* name) const {
443 GetHeader(m_pn, m_pfc, const_cast<char*>(hdr.c_str()), buf, 1024, false);
446 void setRemoteUser(const char* user) {
447 setHeader("remote-user", user);
449 string getRemoteUser() const {
450 return getHeader("remote-user");
452 void setResponseHeader(const char* name, const char* value) {
455 m_headers[name] = value;
457 m_headers.erase(name);
459 long sendResponse(istream& in, long status) {
460 string hdr = string("Connection: close\r\n");
461 for (map<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
462 hdr += i->first + ": " + i->second + "\r\n";
464 const char* codestr="200 OK";
466 case SAML_HTTP_STATUS_FORBIDDEN:codestr="403 Forbidden"; break;
467 case SAML_HTTP_STATUS_NOTFOUND: codestr="404 Not Found"; break;
468 case SAML_HTTP_STATUS_ERROR: codestr="500 Server Error"; break;
470 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, (void*)codestr, (DWORD)hdr.c_str(), 0);
474 DWORD resplen = in.gcount();
475 m_pfc->WriteClient(m_pfc, buf, &resplen, 0);
477 return SF_STATUS_REQ_FINISHED;
479 long sendRedirect(const char* url) {
480 // XXX: Don't support the httpRedirect option, yet.
481 string hdr=string("Location: ") + url + "\r\n"
482 "Content-Type: text/html\r\n"
483 "Content-Length: 40\r\n"
484 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
485 "Cache-Control: private,no-store,no-cache\r\n";
486 for (map<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
487 hdr += i->first + ": " + i->second + "\r\n";
489 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, "302 Please Wait", (DWORD)hdr.c_str(), 0);
490 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
492 m_pfc->WriteClient(m_pfc, (LPVOID)redmsg, &resplen, 0);
493 return SF_STATUS_REQ_FINISHED;
495 long returnDecline() {
496 return SF_STATUS_REQ_NEXT_NOTIFICATION;
499 return SF_STATUS_REQ_NEXT_NOTIFICATION;
502 const vector<XSECCryptoX509*>& getClientCertificates() const {
506 // The filter never processes the POST, so stub these methods.
507 const char* getQueryString() const { throw IOException("getQueryString not implemented"); }
508 const char* getRequestBody() const { throw IOException("getRequestBody not implemented"); }
511 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const char* msg)
513 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
514 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
515 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)ctype,0);
516 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Filter Error</TITLE></HEAD><BODY>"
517 "<H1>Shibboleth Filter Error</H1>";
518 DWORD resplen=strlen(xmsg);
519 pfc->WriteClient(pfc,(LPVOID)xmsg,&resplen,0);
521 pfc->WriteClient(pfc,(LPVOID)msg,&resplen,0);
522 static const char* xmsg2="</BODY></HTML>";
523 resplen=strlen(xmsg2);
524 pfc->WriteClient(pfc,(LPVOID)xmsg2,&resplen,0);
525 return SF_STATUS_REQ_FINISHED;
528 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
530 // Is this a log notification?
531 if (notificationType==SF_NOTIFY_LOG)
533 if (pfc->pFilterContext)
534 ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=static_cast<LPCSTR>(pfc->pFilterContext);
535 return SF_STATUS_REQ_NEXT_NOTIFICATION;
538 PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
541 // Determine web site number. This can't really fail, I don't think.
543 GetServerVariable(pfc,"INSTANCE_ID",buf,10);
545 // Match site instance to host name, skip if no match.
546 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
547 if (map_i==g_Sites.end())
548 return SF_STATUS_REQ_NEXT_NOTIFICATION;
550 ostringstream threadid;
551 threadid << "[" << getpid() << "] isapi_shib" << '\0';
552 xmltooling::NDC ndc(threadid.str().c_str());
554 ShibTargetIsapiF stf(pfc, pn, map_i->second);
556 // "false" because we don't override the Shib settings
557 pair<bool,long> res = stf.getServiceProvider().doAuthentication(stf);
558 if (res.first) return res.second;
560 // "false" because we don't override the Shib settings
561 res = stf.getServiceProvider().doExport(stf);
562 if (res.first) return res.second;
564 res = stf.getServiceProvider().doAuthorization(stf);
565 if (res.first) return res.second;
567 return SF_STATUS_REQ_NEXT_NOTIFICATION;
570 return WriteClientError(pfc,"Out of Memory");
573 if (e==ERROR_NO_DATA)
574 return WriteClientError(pfc,"A required variable or header was empty.");
576 return WriteClientError(pfc,"Shibboleth Filter detected unexpected IIS error.");
578 catch (exception& e) {
579 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
580 return WriteClientError(pfc,"Shibboleth Filter caught an exception, check Event Log for details.");
584 return WriteClientError(pfc,"Shibboleth Filter caught an unknown exception.");
588 return WriteClientError(pfc,"Shibboleth Filter reached unreachable code, save my walrus!");
592 /****************************************************************************/
595 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* msg)
597 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
598 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
599 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
600 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Error</TITLE></HEAD><BODY><H1>Shibboleth Error</H1>";
601 DWORD resplen=strlen(xmsg);
602 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg,&resplen,HSE_IO_SYNC);
604 lpECB->WriteClient(lpECB->ConnID,(LPVOID)msg,&resplen,HSE_IO_SYNC);
605 static const char* xmsg2="</BODY></HTML>";
606 resplen=strlen(xmsg2);
607 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg2,&resplen,HSE_IO_SYNC);
608 return HSE_STATUS_SUCCESS;
612 class ShibTargetIsapiE : public AbstractSPRequest
614 LPEXTENSION_CONTROL_BLOCK m_lpECB;
615 map<string,string> m_headers;
616 vector<XSECCryptoX509*> m_certs;
617 mutable string m_body;
618 mutable bool m_gotBody;
620 string m_scheme,m_hostname,m_uri;
621 mutable string m_remote_addr;
624 ShibTargetIsapiE(LPEXTENSION_CONTROL_BLOCK lpECB, const site_t& site) : m_lpECB(lpECB), m_gotBody(false) {
626 GetServerVariable(lpECB,"HTTPS",ssl,5);
627 bool SSL=(ssl=="on" || ssl=="ON");
629 // Scheme may come from site def or be derived from IIS.
630 m_scheme=site.m_scheme;
631 if (m_scheme.empty() || !g_bNormalizeRequest)
632 m_scheme = SSL ? "https" : "http";
634 // URL path always come from IIS.
636 GetServerVariable(lpECB,"URL",url,255);
638 // Port may come from IIS or from site def.
640 if (!g_bNormalizeRequest || (SSL && site.m_sslport.empty()) || (!SSL && site.m_port.empty()))
641 GetServerVariable(lpECB,"SERVER_PORT",port,10);
643 strncpy(port,site.m_sslport.c_str(),10);
644 static_cast<char*>(port)[10]=0;
647 strncpy(port,site.m_port.c_str(),10);
648 static_cast<char*>(port)[10]=0;
653 GetServerVariable(lpECB, "SERVER_NAME", var, 32);
655 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
657 if (site.m_name!=m_hostname && site.m_aliases.find(m_hostname)==site.m_aliases.end())
658 m_hostname=site.m_name;
661 * IIS screws us over on PATH_INFO (the hits keep on coming). We need to figure out if
662 * the server is set up for proper PATH_INFO handling, or "IIS sucks rabid weasels mode",
663 * which is the default. No perfect way to tell, but we can take a good guess by checking
664 * whether the URL is a substring of the PATH_INFO:
666 * e.g. for /Shibboleth.sso/SAML/POST
668 * Bad mode (default):
669 * URL: /Shibboleth.sso
670 * PathInfo: /Shibboleth.sso/SAML/POST
673 * URL: /Shibboleth.sso
674 * PathInfo: /SAML/POST
677 // Clearly we're only in bad mode if path info exists at all.
678 if (lpECB->lpszPathInfo && *(lpECB->lpszPathInfo)) {
679 if (strstr(lpECB->lpszPathInfo,url))
680 // Pretty good chance we're in bad mode, unless the PathInfo repeats the path itself.
681 m_uri = lpECB->lpszPathInfo;
684 m_uri += lpECB->lpszPathInfo;
688 // For consistency with Apache, let's add the query string.
689 if (lpECB->lpszQueryString && *(lpECB->lpszQueryString)) {
691 m_uri += lpECB->lpszQueryString;
694 ~ShibTargetIsapiE() { }
696 const char* getScheme() const {
697 return m_scheme.c_str();
699 const char* getHostname() const {
700 return m_hostname.c_str();
702 int getPort() const {
705 const char* getRequestURI() const {
706 return m_uri.c_str();
708 const char* getMethod() const {
709 return m_lpECB->lpszMethod;
711 string getContentType() const {
712 return m_lpECB->lpszContentType ? m_lpECB->lpszContentType : "";
714 long getContentLength() const {
715 return m_lpECB->cbTotalBytes;
717 string getRemoteAddr() const {
718 if (m_remote_addr.empty()) {
720 GetServerVariable(m_lpECB, "REMOTE_ADDR", var, 16, false);
724 return m_remote_addr;
726 void log(SPLogLevel level, const string& msg) {
727 AbstractSPRequest::log(level,msg);
728 if (level >= SPError)
729 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
731 string getHeader(const char* name) const {
733 for (; *name; ++name) {
737 hdr += toupper(*name);
740 GetServerVariable(m_lpECB, const_cast<char*>(hdr.c_str()), buf, 128, false);
741 return buf.empty() ? "" : buf;
743 void setResponseHeader(const char* name, const char* value) {
746 m_headers[name] = value;
748 m_headers.erase(name);
750 const char* getQueryString() const {
751 return m_lpECB->lpszQueryString;
753 const char* getRequestBody() const {
755 return m_body.c_str();
756 if (m_lpECB->cbTotalBytes > 1024*1024) // 1MB?
757 throw opensaml::BindingException("Size of POST request body exceeded limit.");
758 else if (m_lpECB->cbTotalBytes != m_lpECB->cbAvailable) {
761 DWORD datalen=m_lpECB->cbTotalBytes;
764 BOOL ret = m_lpECB->ReadClient(m_lpECB->ConnID, buf, &buflen);
766 throw IOException("Error reading POST request body from browser.");
767 m_body.append(buf, buflen);
773 m_body.assign(reinterpret_cast<char*>(m_lpECB->lpbData),m_lpECB->cbAvailable);
775 return m_body.c_str();
777 long sendResponse(istream& in, long status) {
778 string hdr = string("Connection: close\r\n");
779 for (map<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
780 hdr += i->first + ": " + i->second + "\r\n";
782 const char* codestr="200 OK";
784 case SAML_HTTP_STATUS_FORBIDDEN:codestr="403 Forbidden"; break;
785 case SAML_HTTP_STATUS_NOTFOUND: codestr="404 Not Found"; break;
786 case SAML_HTTP_STATUS_ERROR: codestr="500 Server Error"; break;
788 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, (void*)codestr, 0, (LPDWORD)hdr.c_str());
792 DWORD resplen = in.gcount();
793 m_lpECB->WriteClient(m_lpECB->ConnID, buf, &resplen, HSE_IO_SYNC);
795 return HSE_STATUS_SUCCESS;
797 long sendRedirect(const char* url) {
798 string hdr=string("Location: ") + url + "\r\n"
799 "Content-Type: text/html\r\n"
800 "Content-Length: 40\r\n"
801 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
802 "Cache-Control: private,no-store,no-cache\r\n";
803 for (map<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
804 hdr += i->first + ": " + i->second + "\r\n";
806 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, "302 Moved", 0, (LPDWORD)hdr.c_str());
807 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
809 m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)redmsg, &resplen, HSE_IO_SYNC);
810 return HSE_STATUS_SUCCESS;
812 // Decline happens in the POST processor if this isn't the shire url
813 // Note that it can also happen with HTAccess, but we don't support that, yet.
814 long returnDecline() {
815 return WriteClientError(
817 "ISAPI extension can only be invoked to process Shibboleth protocol requests."
818 "Make sure the mapped file extension doesn't match actual content."
822 return HSE_STATUS_SUCCESS;
825 const vector<XSECCryptoX509*>& getClientCertificates() const {
829 // Not used in the extension.
830 virtual void clearHeader(const char* name) { throw runtime_error("clearHeader not implemented"); }
831 virtual void setHeader(const char* name, const char* value) { throw runtime_error("setHeader not implemented"); }
832 virtual void setRemoteUser(const char* user) { throw runtime_error("setRemoteUser not implemented"); }
833 virtual string getRemoteUser() const { throw runtime_error("getRemoteUser not implemented"); }
836 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
839 ostringstream threadid;
840 threadid << "[" << getpid() << "] isapi_shib_extension" << '\0';
841 xmltooling::NDC ndc(threadid.str().c_str());
843 // Determine web site number. This can't really fail, I don't think.
845 GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
847 // Match site instance to host name, skip if no match.
848 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
849 if (map_i==g_Sites.end())
850 return WriteClientError(lpECB, "Shibboleth Extension not configured for this web site.");
852 ShibTargetIsapiE ste(lpECB, map_i->second);
853 pair<bool,long> res = ste.getServiceProvider().doHandler(ste);
854 if (res.first) return res.second;
856 return WriteClientError(lpECB, "Shibboleth Extension failed to process request");
860 return WriteClientError(lpECB,"Out of Memory");
863 if (e==ERROR_NO_DATA)
864 return WriteClientError(lpECB,"A required variable or header was empty.");
866 return WriteClientError(lpECB,"Server detected unexpected IIS error.");
868 catch (exception& e) {
869 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
870 return WriteClientError(lpECB,"Shibboleth Extension caught an exception, check Event Log for details.");
874 return WriteClientError(lpECB,"Shibboleth Extension caught an unknown exception.");
878 // If we get here we've got an error.
879 return HSE_STATUS_ERROR;