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/XMLConstants.h>
35 #include <xmltooling/util/XMLHelper.h>
36 #include <xercesc/util/XMLUniDefs.hpp>
46 using namespace shibsp;
47 using namespace xmltooling;
48 using namespace xercesc;
53 static const XMLCh path[] = UNICODE_LITERAL_4(p,a,t,h);
54 static const XMLCh validate[] = UNICODE_LITERAL_8(v,a,l,i,d,a,t,e);
55 static const XMLCh name[] = UNICODE_LITERAL_4(n,a,m,e);
56 static const XMLCh port[] = UNICODE_LITERAL_4(p,o,r,t);
57 static const XMLCh sslport[] = UNICODE_LITERAL_7(s,s,l,p,o,r,t);
58 static const XMLCh scheme[] = UNICODE_LITERAL_6(s,c,h,e,m,e);
59 static const XMLCh id[] = UNICODE_LITERAL_2(i,d);
60 static const XMLCh Implementation[] = UNICODE_LITERAL_14(I,m,p,l,e,m,e,n,t,a,t,i,o,n);
61 static const XMLCh ISAPI[] = UNICODE_LITERAL_5(I,S,A,P,I);
62 static const XMLCh Alias[] = UNICODE_LITERAL_5(A,l,i,a,s);
63 static const XMLCh normalizeRequest[] = UNICODE_LITERAL_16(n,o,r,m,a,l,i,z,e,R,e,q,u,e,s,t);
64 static const XMLCh Site[] = UNICODE_LITERAL_4(S,i,t,e);
67 site_t(const DOMElement* e)
69 auto_ptr_char n(e->getAttributeNS(NULL,name));
70 auto_ptr_char s(e->getAttributeNS(NULL,scheme));
71 auto_ptr_char p(e->getAttributeNS(NULL,port));
72 auto_ptr_char p2(e->getAttributeNS(NULL,sslport));
73 if (n.get()) m_name=n.get();
74 if (s.get()) m_scheme=s.get();
75 if (p.get()) m_port=p.get();
76 if (p2.get()) m_sslport=p2.get();
77 e = XMLHelper::getFirstChildElement(e, Alias);
79 if (e->hasChildNodes()) {
80 auto_ptr_char alias(e->getFirstChild()->getNodeValue());
81 m_aliases.insert(alias.get());
83 e = XMLHelper::getNextSiblingElement(e, Alias);
86 string m_scheme,m_port,m_sslport,m_name;
87 set<string> m_aliases;
91 SPConfig* g_Config = NULL;
92 map<string,site_t> g_Sites;
93 bool g_bNormalizeRequest = true;
97 LPCSTR lpUNCServerName,
103 LPCSTR messages[] = {message, NULL};
105 HANDLE hElog = RegisterEventSource(lpUNCServerName, "Shibboleth ISAPI Filter");
106 BOOL res = ReportEvent(hElog, wType, 0, dwEventID, lpUserSid, 1, 0, messages, NULL);
107 return (DeregisterEventSource(hElog) && res);
110 extern "C" __declspec(dllexport) BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID)
112 if (fdwReason==DLL_PROCESS_ATTACH)
117 extern "C" BOOL WINAPI GetExtensionVersion(HSE_VERSION_INFO* pVer)
123 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
124 "Extension mode startup not possible, is the DLL loaded as a filter?");
128 pVer->dwExtensionVersion=HSE_VERSION;
129 strncpy(pVer->lpszExtensionDesc,"Shibboleth ISAPI Extension",HSE_MAX_EXT_DLL_NAME_LEN-1);
133 extern "C" BOOL WINAPI TerminateExtension(DWORD)
135 return TRUE; // cleanup should happen when filter unloads
138 extern "C" BOOL WINAPI GetFilterVersion(PHTTP_FILTER_VERSION pVer)
143 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
144 "Reentrant filter initialization, ignoring...");
148 LPCSTR schemadir=getenv("SHIBSP_SCHEMAS");
150 schemadir=SHIBSP_SCHEMAS;
151 LPCSTR config=getenv("SHIBSP_CONFIG");
153 config=SHIBSP_CONFIG;
154 g_Config=&SPConfig::getConfig();
155 g_Config->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.");
171 DOMDocument* dummydoc=XMLToolingConfig::getConfig().getParser().newDocument();
172 XercesJanitor<DOMDocument> docjanitor(dummydoc);
173 DOMElement* dummy = dummydoc->createElementNS(NULL,path);
174 auto_ptr_XMLCh src(config);
175 dummy->setAttributeNS(NULL,path,src.get());
176 dummy->setAttributeNS(NULL,validate,xmlconstants::XML_ONE);
178 g_Config->setServiceProvider(g_Config->ServiceProviderManager.newPlugin(XML_SERVICE_PROVIDER,dummy));
179 g_Config->getServiceProvider()->init();
181 catch (exception& ex) {
184 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, ex.what());
185 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
186 "Filter startup failed to load configuration, check native log for details.");
190 // Access the implementation-specifics for site mappings.
191 ServiceProvider* sp=g_Config->getServiceProvider();
192 xmltooling::Locker locker(sp);
193 const PropertySet* props=sp->getPropertySet("Local");
195 const DOMElement* impl=XMLHelper::getFirstChildElement(props->getElement(),Implementation);
196 if (impl && (impl=XMLHelper::getFirstChildElement(impl,ISAPI))) {
197 const XMLCh* flag=impl->getAttributeNS(NULL,normalizeRequest);
198 g_bNormalizeRequest=(!flag || !*flag || *flag==chDigit_1 || *flag==chLatin_t);
199 impl=XMLHelper::getFirstChildElement(impl,Site);
201 auto_ptr_char id(impl->getAttributeNS(NULL,id));
203 g_Sites.insert(pair<string,site_t>(id.get(),site_t(impl)));
204 impl=XMLHelper::getNextSiblingElement(impl,Site);
209 pVer->dwFilterVersion=HTTP_FILTER_REVISION;
210 strncpy(pVer->lpszFilterDesc,"Shibboleth ISAPI Filter",SF_MAX_FILTER_DESC_LEN);
211 pVer->dwFlags=(SF_NOTIFY_ORDER_HIGH |
212 SF_NOTIFY_SECURE_PORT |
213 SF_NOTIFY_NONSECURE_PORT |
214 SF_NOTIFY_PREPROC_HEADERS |
216 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter initialized...");
220 extern "C" BOOL WINAPI TerminateFilter(DWORD)
225 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter shut down...");
229 /* Next up, some suck-free versions of various APIs.
231 You DON'T require people to guess the buffer size and THEN tell them the right size.
232 Returning an LPCSTR is apparently way beyond their ken. Not to mention the fact that
233 constant strings aren't typed as such, making it just that much harder. These versions
234 are now updated to use a special growable buffer object, modeled after the standard
235 string class. The standard string won't work because they left out the option to
236 pre-allocate a non-constant buffer.
242 dynabuf() { bufptr=NULL; buflen=0; }
243 dynabuf(size_t s) { bufptr=new char[buflen=s]; *bufptr=0; }
244 ~dynabuf() { delete[] bufptr; }
245 size_t length() const { return bufptr ? strlen(bufptr) : 0; }
246 size_t size() const { return buflen; }
247 bool empty() const { return length()==0; }
248 void reserve(size_t s, bool keep=false);
249 void erase() { if (bufptr) memset(bufptr,0,buflen); }
250 operator char*() { return bufptr; }
251 bool operator ==(const char* s) const;
252 bool operator !=(const char* s) const { return !(*this==s); }
258 void dynabuf::reserve(size_t s, bool keep)
265 p[buflen]=bufptr[buflen];
271 bool dynabuf::operator==(const char* s) const
273 if (buflen==NULL || s==NULL)
274 return (buflen==NULL && s==NULL);
276 return strcmp(bufptr,s)==0;
279 void GetServerVariable(PHTTP_FILTER_CONTEXT pfc, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
285 while (!pfc->GetServerVariable(pfc,lpszVariable,s,&size)) {
286 // Grumble. Check the error.
287 DWORD e=GetLastError();
288 if (e==ERROR_INSUFFICIENT_BUFFER)
293 if (bRequired && s.empty())
297 void GetServerVariable(LPEXTENSION_CONTROL_BLOCK lpECB, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
303 while (!lpECB->GetServerVariable(lpECB->ConnID,lpszVariable,s,&size)) {
304 // Grumble. Check the error.
305 DWORD e=GetLastError();
306 if (e==ERROR_INSUFFICIENT_BUFFER)
311 if (bRequired && s.empty())
315 void GetHeader(PHTTP_FILTER_PREPROC_HEADERS pn, PHTTP_FILTER_CONTEXT pfc,
316 LPSTR lpszName, dynabuf& s, DWORD size=80, bool bRequired=true)
322 while (!pn->GetHeader(pfc,lpszName,s,&size)) {
323 // Grumble. Check the error.
324 DWORD e=GetLastError();
325 if (e==ERROR_INSUFFICIENT_BUFFER)
330 if (bRequired && s.empty())
334 /****************************************************************************/
337 class ShibTargetIsapiF : public AbstractSPRequest
339 PHTTP_FILTER_CONTEXT m_pfc;
340 PHTTP_FILTER_PREPROC_HEADERS m_pn;
341 map<string,string> m_headers;
342 vector<XSECCryptoX509*> m_certs;
344 string m_scheme,m_hostname,m_uri;
345 mutable string m_remote_addr,m_content_type,m_method;
348 ShibTargetIsapiF(PHTTP_FILTER_CONTEXT pfc, PHTTP_FILTER_PREPROC_HEADERS pn, const site_t& site) {
353 // URL path always come from IIS.
355 GetHeader(pn,pfc,"url",var,256,false);
358 // Port may come from IIS or from site def.
359 if (!g_bNormalizeRequest || (pfc->fIsSecurePort && site.m_sslport.empty()) || (!pfc->fIsSecurePort && site.m_port.empty())) {
360 GetServerVariable(pfc,"SERVER_PORT",var,10);
363 else if (pfc->fIsSecurePort) {
364 m_port = atoi(site.m_sslport.c_str());
367 m_port = atoi(site.m_port.c_str());
370 // Scheme may come from site def or be derived from IIS.
371 m_scheme=site.m_scheme;
372 if (m_scheme.empty() || !g_bNormalizeRequest)
373 m_scheme=pfc->fIsSecurePort ? "https" : "http";
375 GetServerVariable(pfc,"SERVER_NAME",var,32);
377 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
379 if (site.m_name!=m_hostname && site.m_aliases.find(m_hostname)==site.m_aliases.end())
380 m_hostname=site.m_name;
382 ~ShibTargetIsapiF() { }
384 const char* getScheme() const {
385 return m_scheme.c_str();
387 const char* getHostname() const {
388 return m_hostname.c_str();
390 int getPort() const {
393 const char* getRequestURI() const {
394 return m_uri.c_str();
396 const char* getMethod() const {
397 if (m_method.empty()) {
399 GetServerVariable(m_pfc,"REQUEST_METHOD",var,5,false);
403 return m_method.c_str();
405 string getContentType() const {
406 if (m_content_type.empty()) {
408 GetServerVariable(m_pfc,"CONTENT_TYPE",var,32,false);
410 m_content_type = var;
412 return m_content_type;
414 long getContentLength() const {
417 string getRemoteAddr() const {
418 if (m_remote_addr.empty()) {
420 GetServerVariable(m_pfc,"REMOTE_ADDR",var,16,false);
424 return m_remote_addr;
426 void log(SPLogLevel level, const string& msg) {
427 AbstractSPRequest::log(level,msg);
428 if (level >= SPError)
429 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
431 void clearHeader(const char* name) {
432 string hdr(!strcmp(name,"REMOTE_USER") ? "remote-user" : name);
434 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), "");
436 void setHeader(const char* name, const char* value) {
439 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), const_cast<char*>(value));
441 string getHeader(const char* name) const {
445 GetHeader(m_pn, m_pfc, const_cast<char*>(hdr.c_str()), buf, 1024, false);
448 void setRemoteUser(const char* user) {
449 setHeader("remote-user", user);
451 string getRemoteUser() const {
452 return getHeader("remote-user");
454 void setResponseHeader(const char* name, const char* value) {
457 m_headers[name] = value;
459 m_headers.erase(name);
461 long sendResponse(istream& in, long status) {
462 string hdr = string("Connection: close\r\n");
463 for (map<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
464 hdr += i->first + ": " + i->second + "\r\n";
466 const char* codestr="200 OK";
468 case SAML_HTTP_STATUS_FORBIDDEN:codestr="403 Forbidden"; break;
469 case SAML_HTTP_STATUS_NOTFOUND: codestr="404 Not Found"; break;
470 case SAML_HTTP_STATUS_ERROR: codestr="500 Server Error"; break;
472 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, (void*)codestr, (DWORD)hdr.c_str(), 0);
476 DWORD resplen = in.gcount();
477 m_pfc->WriteClient(m_pfc, buf, &resplen, 0);
479 return SF_STATUS_REQ_FINISHED;
481 long sendRedirect(const char* url) {
482 // XXX: Don't support the httpRedirect option, yet.
483 string hdr=string("Location: ") + url + "\r\n"
484 "Content-Type: text/html\r\n"
485 "Content-Length: 40\r\n"
486 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
487 "Cache-Control: private,no-store,no-cache\r\n";
488 for (map<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
489 hdr += i->first + ": " + i->second + "\r\n";
491 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, "302 Please Wait", (DWORD)hdr.c_str(), 0);
492 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
494 m_pfc->WriteClient(m_pfc, (LPVOID)redmsg, &resplen, 0);
495 return SF_STATUS_REQ_FINISHED;
497 long returnDecline() {
498 return SF_STATUS_REQ_NEXT_NOTIFICATION;
501 return SF_STATUS_REQ_NEXT_NOTIFICATION;
504 const vector<XSECCryptoX509*>& getClientCertificates() const {
508 // The filter never processes the POST, so stub these methods.
509 const char* getQueryString() const { throw IOException("getQueryString not implemented"); }
510 const char* getRequestBody() const { throw IOException("getRequestBody not implemented"); }
513 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const char* msg)
515 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
516 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
517 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)ctype,0);
518 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Filter Error</TITLE></HEAD><BODY>"
519 "<H1>Shibboleth Filter Error</H1>";
520 DWORD resplen=strlen(xmsg);
521 pfc->WriteClient(pfc,(LPVOID)xmsg,&resplen,0);
523 pfc->WriteClient(pfc,(LPVOID)msg,&resplen,0);
524 static const char* xmsg2="</BODY></HTML>";
525 resplen=strlen(xmsg2);
526 pfc->WriteClient(pfc,(LPVOID)xmsg2,&resplen,0);
527 return SF_STATUS_REQ_FINISHED;
530 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
532 // Is this a log notification?
533 if (notificationType==SF_NOTIFY_LOG)
535 if (pfc->pFilterContext)
536 ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=static_cast<LPCSTR>(pfc->pFilterContext);
537 return SF_STATUS_REQ_NEXT_NOTIFICATION;
540 PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
543 // Determine web site number. This can't really fail, I don't think.
545 GetServerVariable(pfc,"INSTANCE_ID",buf,10);
547 // Match site instance to host name, skip if no match.
548 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
549 if (map_i==g_Sites.end())
550 return SF_STATUS_REQ_NEXT_NOTIFICATION;
552 ostringstream threadid;
553 threadid << "[" << getpid() << "] isapi_shib" << '\0';
554 xmltooling::NDC ndc(threadid.str().c_str());
556 ShibTargetIsapiF stf(pfc, pn, map_i->second);
558 // "false" because we don't override the Shib settings
559 pair<bool,long> res = stf.getServiceProvider().doAuthentication(stf);
560 if (res.first) return res.second;
562 // "false" because we don't override the Shib settings
563 res = stf.getServiceProvider().doExport(stf);
564 if (res.first) return res.second;
566 res = stf.getServiceProvider().doAuthorization(stf);
567 if (res.first) return res.second;
569 return SF_STATUS_REQ_NEXT_NOTIFICATION;
572 return WriteClientError(pfc,"Out of Memory");
575 if (e==ERROR_NO_DATA)
576 return WriteClientError(pfc,"A required variable or header was empty.");
578 return WriteClientError(pfc,"Shibboleth Filter detected unexpected IIS error.");
580 catch (exception& e) {
581 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
582 return WriteClientError(pfc,"Shibboleth Filter caught an exception, check Event Log for details.");
586 return WriteClientError(pfc,"Shibboleth Filter caught an unknown exception.");
590 return WriteClientError(pfc,"Shibboleth Filter reached unreachable code, save my walrus!");
594 /****************************************************************************/
597 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* msg)
599 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
600 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
601 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
602 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Error</TITLE></HEAD><BODY><H1>Shibboleth Error</H1>";
603 DWORD resplen=strlen(xmsg);
604 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg,&resplen,HSE_IO_SYNC);
606 lpECB->WriteClient(lpECB->ConnID,(LPVOID)msg,&resplen,HSE_IO_SYNC);
607 static const char* xmsg2="</BODY></HTML>";
608 resplen=strlen(xmsg2);
609 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg2,&resplen,HSE_IO_SYNC);
610 return HSE_STATUS_SUCCESS;
614 class ShibTargetIsapiE : public AbstractSPRequest
616 LPEXTENSION_CONTROL_BLOCK m_lpECB;
617 map<string,string> m_headers;
618 vector<XSECCryptoX509*> m_certs;
619 mutable string m_body;
620 mutable bool m_gotBody;
622 string m_scheme,m_hostname,m_uri;
623 mutable string m_remote_addr;
626 ShibTargetIsapiE(LPEXTENSION_CONTROL_BLOCK lpECB, const site_t& site) : m_lpECB(lpECB), m_gotBody(false) {
628 GetServerVariable(lpECB,"HTTPS",ssl,5);
629 bool SSL=(ssl=="on" || ssl=="ON");
631 // Scheme may come from site def or be derived from IIS.
632 m_scheme=site.m_scheme;
633 if (m_scheme.empty() || !g_bNormalizeRequest)
634 m_scheme = SSL ? "https" : "http";
636 // URL path always come from IIS.
638 GetServerVariable(lpECB,"URL",url,255);
640 // Port may come from IIS or from site def.
642 if (!g_bNormalizeRequest || (SSL && site.m_sslport.empty()) || (!SSL && site.m_port.empty()))
643 GetServerVariable(lpECB,"SERVER_PORT",port,10);
645 strncpy(port,site.m_sslport.c_str(),10);
646 static_cast<char*>(port)[10]=0;
649 strncpy(port,site.m_port.c_str(),10);
650 static_cast<char*>(port)[10]=0;
655 GetServerVariable(lpECB, "SERVER_NAME", var, 32);
657 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
659 if (site.m_name!=m_hostname && site.m_aliases.find(m_hostname)==site.m_aliases.end())
660 m_hostname=site.m_name;
663 * IIS screws us over on PATH_INFO (the hits keep on coming). We need to figure out if
664 * the server is set up for proper PATH_INFO handling, or "IIS sucks rabid weasels mode",
665 * which is the default. No perfect way to tell, but we can take a good guess by checking
666 * whether the URL is a substring of the PATH_INFO:
668 * e.g. for /Shibboleth.sso/SAML/POST
670 * Bad mode (default):
671 * URL: /Shibboleth.sso
672 * PathInfo: /Shibboleth.sso/SAML/POST
675 * URL: /Shibboleth.sso
676 * PathInfo: /SAML/POST
679 // Clearly we're only in bad mode if path info exists at all.
680 if (lpECB->lpszPathInfo && *(lpECB->lpszPathInfo)) {
681 if (strstr(lpECB->lpszPathInfo,url))
682 // Pretty good chance we're in bad mode, unless the PathInfo repeats the path itself.
683 m_uri = lpECB->lpszPathInfo;
686 m_uri += lpECB->lpszPathInfo;
690 // For consistency with Apache, let's add the query string.
691 if (lpECB->lpszQueryString && *(lpECB->lpszQueryString)) {
693 m_uri += lpECB->lpszQueryString;
696 ~ShibTargetIsapiE() { }
698 const char* getScheme() const {
699 return m_scheme.c_str();
701 const char* getHostname() const {
702 return m_hostname.c_str();
704 int getPort() const {
707 const char* getRequestURI() const {
708 return m_uri.c_str();
710 const char* getMethod() const {
711 return m_lpECB->lpszMethod;
713 string getContentType() const {
714 return m_lpECB->lpszContentType ? m_lpECB->lpszContentType : "";
716 long getContentLength() const {
717 return m_lpECB->cbTotalBytes;
719 string getRemoteAddr() const {
720 if (m_remote_addr.empty()) {
722 GetServerVariable(m_lpECB, "REMOTE_ADDR", var, 16, false);
726 return m_remote_addr;
728 void log(SPLogLevel level, const string& msg) {
729 AbstractSPRequest::log(level,msg);
730 if (level >= SPError)
731 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
733 string getHeader(const char* name) const {
735 for (; *name; ++name) {
739 hdr += toupper(*name);
742 GetServerVariable(m_lpECB, const_cast<char*>(hdr.c_str()), buf, 128, false);
743 return buf.empty() ? "" : buf;
745 void setResponseHeader(const char* name, const char* value) {
748 m_headers[name] = value;
750 m_headers.erase(name);
752 const char* getQueryString() const {
753 return m_lpECB->lpszQueryString;
755 const char* getRequestBody() const {
757 return m_body.c_str();
758 if (m_lpECB->cbTotalBytes > 1024*1024) // 1MB?
759 throw opensaml::SecurityPolicyException("Size of request body exceeded 1M size limit.");
760 else if (m_lpECB->cbTotalBytes > m_lpECB->cbAvailable) {
763 DWORD datalen=m_lpECB->cbTotalBytes;
766 BOOL ret = m_lpECB->ReadClient(m_lpECB->ConnID, buf, &buflen);
768 throw IOException("Error reading request body from browser.");
769 m_body.append(buf, buflen);
773 else if (m_lpECB->cbAvailable) {
775 m_body.assign(reinterpret_cast<char*>(m_lpECB->lpbData),m_lpECB->cbAvailable);
777 return m_body.c_str();
779 long sendResponse(istream& in, long status) {
780 string hdr = string("Connection: close\r\n");
781 for (map<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
782 hdr += i->first + ": " + i->second + "\r\n";
784 const char* codestr="200 OK";
786 case SAML_HTTP_STATUS_FORBIDDEN:codestr="403 Forbidden"; break;
787 case SAML_HTTP_STATUS_NOTFOUND: codestr="404 Not Found"; break;
788 case SAML_HTTP_STATUS_ERROR: codestr="500 Server Error"; break;
790 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, (void*)codestr, 0, (LPDWORD)hdr.c_str());
794 DWORD resplen = in.gcount();
795 m_lpECB->WriteClient(m_lpECB->ConnID, buf, &resplen, HSE_IO_SYNC);
797 return HSE_STATUS_SUCCESS;
799 long sendRedirect(const char* url) {
800 string hdr=string("Location: ") + url + "\r\n"
801 "Content-Type: text/html\r\n"
802 "Content-Length: 40\r\n"
803 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
804 "Cache-Control: private,no-store,no-cache\r\n";
805 for (map<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
806 hdr += i->first + ": " + i->second + "\r\n";
808 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, "302 Moved", 0, (LPDWORD)hdr.c_str());
809 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
811 m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)redmsg, &resplen, HSE_IO_SYNC);
812 return HSE_STATUS_SUCCESS;
814 // Decline happens in the POST processor if this isn't the shire url
815 // Note that it can also happen with HTAccess, but we don't support that, yet.
816 long returnDecline() {
817 return WriteClientError(
819 "ISAPI extension can only be invoked to process Shibboleth protocol requests."
820 "Make sure the mapped file extension doesn't match actual content."
824 return HSE_STATUS_SUCCESS;
827 const vector<XSECCryptoX509*>& getClientCertificates() const {
831 // Not used in the extension.
832 virtual void clearHeader(const char* name) { throw runtime_error("clearHeader not implemented"); }
833 virtual void setHeader(const char* name, const char* value) { throw runtime_error("setHeader not implemented"); }
834 virtual void setRemoteUser(const char* user) { throw runtime_error("setRemoteUser not implemented"); }
835 virtual string getRemoteUser() const { throw runtime_error("getRemoteUser not implemented"); }
838 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
841 ostringstream threadid;
842 threadid << "[" << getpid() << "] isapi_shib_extension" << '\0';
843 xmltooling::NDC ndc(threadid.str().c_str());
845 // Determine web site number. This can't really fail, I don't think.
847 GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
849 // Match site instance to host name, skip if no match.
850 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
851 if (map_i==g_Sites.end())
852 return WriteClientError(lpECB, "Shibboleth Extension not configured for this web site.");
854 ShibTargetIsapiE ste(lpECB, map_i->second);
855 pair<bool,long> res = ste.getServiceProvider().doHandler(ste);
856 if (res.first) return res.second;
858 return WriteClientError(lpECB, "Shibboleth Extension failed to process request");
862 return WriteClientError(lpECB,"Out of Memory");
865 if (e==ERROR_NO_DATA)
866 return WriteClientError(lpECB,"A required variable or header was empty.");
868 return WriteClientError(lpECB,"Server detected unexpected IIS error.");
870 catch (exception& e) {
871 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
872 return WriteClientError(lpECB,"Shibboleth Extension caught an exception, check Event Log for details.");
876 return WriteClientError(lpECB,"Shibboleth Extension caught an unknown exception.");
880 // If we get here we've got an error.
881 return HSE_STATUS_ERROR;