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
24 #include "config_win32.h"
26 #define _CRT_NONSTDC_NO_DEPRECATE 1
27 #define _CRT_SECURE_NO_DEPRECATE 1
29 #include <shibsp/AbstractSPRequest.h>
30 #include <shibsp/SPConfig.h>
31 #include <shibsp/ServiceProvider.h>
32 #include <xmltooling/unicode.h>
33 #include <xmltooling/XMLToolingConfig.h>
34 #include <xmltooling/util/NDC.h>
35 #include <xmltooling/util/XMLConstants.h>
36 #include <xmltooling/util/XMLHelper.h>
37 #include <xercesc/util/XMLUniDefs.hpp>
48 using namespace shibsp;
49 using namespace xmltooling;
50 using namespace xercesc;
55 static const XMLCh path[] = UNICODE_LITERAL_4(p,a,t,h);
56 static const XMLCh validate[] = UNICODE_LITERAL_8(v,a,l,i,d,a,t,e);
57 static const XMLCh name[] = UNICODE_LITERAL_4(n,a,m,e);
58 static const XMLCh port[] = UNICODE_LITERAL_4(p,o,r,t);
59 static const XMLCh sslport[] = UNICODE_LITERAL_7(s,s,l,p,o,r,t);
60 static const XMLCh scheme[] = UNICODE_LITERAL_6(s,c,h,e,m,e);
61 static const XMLCh id[] = UNICODE_LITERAL_2(i,d);
62 static const XMLCh Implementation[] = UNICODE_LITERAL_14(I,m,p,l,e,m,e,n,t,a,t,i,o,n);
63 static const XMLCh ISAPI[] = UNICODE_LITERAL_5(I,S,A,P,I);
64 static const XMLCh Alias[] = UNICODE_LITERAL_5(A,l,i,a,s);
65 static const XMLCh normalizeRequest[] = UNICODE_LITERAL_16(n,o,r,m,a,l,i,z,e,R,e,q,u,e,s,t);
66 static const XMLCh Site[] = UNICODE_LITERAL_4(S,i,t,e);
69 site_t(const DOMElement* e)
71 auto_ptr_char n(e->getAttributeNS(NULL,name));
72 auto_ptr_char s(e->getAttributeNS(NULL,scheme));
73 auto_ptr_char p(e->getAttributeNS(NULL,port));
74 auto_ptr_char p2(e->getAttributeNS(NULL,sslport));
75 if (n.get()) m_name=n.get();
76 if (s.get()) m_scheme=s.get();
77 if (p.get()) m_port=p.get();
78 if (p2.get()) m_sslport=p2.get();
79 e = XMLHelper::getFirstChildElement(e, Alias);
81 if (e->hasChildNodes()) {
82 auto_ptr_char alias(e->getFirstChild()->getNodeValue());
83 m_aliases.insert(alias.get());
85 e = XMLHelper::getNextSiblingElement(e, Alias);
88 string m_scheme,m_port,m_sslport,m_name;
89 set<string> m_aliases;
93 SPConfig* g_Config = NULL;
94 map<string,site_t> g_Sites;
95 bool g_bNormalizeRequest = true;
99 LPCSTR lpUNCServerName,
105 LPCSTR messages[] = {message, NULL};
107 HANDLE hElog = RegisterEventSource(lpUNCServerName, "Shibboleth ISAPI Filter");
108 BOOL res = ReportEvent(hElog, wType, 0, dwEventID, lpUserSid, 1, 0, messages, NULL);
109 return (DeregisterEventSource(hElog) && res);
112 extern "C" __declspec(dllexport) BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID)
114 if (fdwReason==DLL_PROCESS_ATTACH)
119 extern "C" BOOL WINAPI GetExtensionVersion(HSE_VERSION_INFO* pVer)
125 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
126 "Extension mode startup not possible, is the DLL loaded as a filter?");
130 pVer->dwExtensionVersion=HSE_VERSION;
131 strncpy(pVer->lpszExtensionDesc,"Shibboleth ISAPI Extension",HSE_MAX_EXT_DLL_NAME_LEN-1);
135 extern "C" BOOL WINAPI TerminateExtension(DWORD)
137 return TRUE; // cleanup should happen when filter unloads
140 extern "C" BOOL WINAPI GetFilterVersion(PHTTP_FILTER_VERSION pVer)
145 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
146 "Reentrant filter initialization, ignoring...");
150 LPCSTR schemadir=getenv("SHIBSP_SCHEMAS");
152 schemadir=SHIBSP_SCHEMAS;
153 LPCSTR config=getenv("SHIBSP_CONFIG");
155 config=SHIBSP_CONFIG;
156 g_Config=&SPConfig::getConfig();
157 g_Config->setFeatures(
160 SPConfig::RequestMapping |
161 SPConfig::InProcess |
164 if (!g_Config->init(schemadir)) {
166 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
167 "Filter startup failed during library initialization, check native log for help.");
172 DOMDocument* dummydoc=XMLToolingConfig::getConfig().getParser().newDocument();
173 XercesJanitor<DOMDocument> docjanitor(dummydoc);
174 DOMElement* dummy = dummydoc->createElementNS(NULL,path);
175 auto_ptr_XMLCh src(config);
176 dummy->setAttributeNS(NULL,path,src.get());
177 dummy->setAttributeNS(NULL,validate,xmlconstants::XML_ONE);
179 g_Config->setServiceProvider(g_Config->ServiceProviderManager.newPlugin(XML_SERVICE_PROVIDER,dummy));
180 g_Config->getServiceProvider()->init();
182 catch (exception& ex) {
185 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, ex.what());
186 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
187 "Filter startup failed to load configuration, check native log for details.");
191 // Access the implementation-specifics for site mappings.
192 ServiceProvider* sp=g_Config->getServiceProvider();
193 xmltooling::Locker locker(sp);
194 const PropertySet* props=sp->getPropertySet("Local");
196 const DOMElement* impl=XMLHelper::getFirstChildElement(props->getElement(),Implementation);
197 if (impl && (impl=XMLHelper::getFirstChildElement(impl,ISAPI))) {
198 const XMLCh* flag=impl->getAttributeNS(NULL,normalizeRequest);
199 g_bNormalizeRequest=(!flag || !*flag || *flag==chDigit_1 || *flag==chLatin_t);
200 impl=XMLHelper::getFirstChildElement(impl,Site);
202 auto_ptr_char id(impl->getAttributeNS(NULL,id));
204 g_Sites.insert(pair<string,site_t>(id.get(),site_t(impl)));
205 impl=XMLHelper::getNextSiblingElement(impl,Site);
210 pVer->dwFilterVersion=HTTP_FILTER_REVISION;
211 strncpy(pVer->lpszFilterDesc,"Shibboleth ISAPI Filter",SF_MAX_FILTER_DESC_LEN);
212 pVer->dwFlags=(SF_NOTIFY_ORDER_HIGH |
213 SF_NOTIFY_SECURE_PORT |
214 SF_NOTIFY_NONSECURE_PORT |
215 SF_NOTIFY_PREPROC_HEADERS |
217 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter initialized...");
221 extern "C" BOOL WINAPI TerminateFilter(DWORD)
226 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter shut down...");
230 /* Next up, some suck-free versions of various APIs.
232 You DON'T require people to guess the buffer size and THEN tell them the right size.
233 Returning an LPCSTR is apparently way beyond their ken. Not to mention the fact that
234 constant strings aren't typed as such, making it just that much harder. These versions
235 are now updated to use a special growable buffer object, modeled after the standard
236 string class. The standard string won't work because they left out the option to
237 pre-allocate a non-constant buffer.
243 dynabuf() { bufptr=NULL; buflen=0; }
244 dynabuf(size_t s) { bufptr=new char[buflen=s]; *bufptr=0; }
245 ~dynabuf() { delete[] bufptr; }
246 size_t length() const { return bufptr ? strlen(bufptr) : 0; }
247 size_t size() const { return buflen; }
248 bool empty() const { return length()==0; }
249 void reserve(size_t s, bool keep=false);
250 void erase() { if (bufptr) memset(bufptr,0,buflen); }
251 operator char*() { return bufptr; }
252 bool operator ==(const char* s) const;
253 bool operator !=(const char* s) const { return !(*this==s); }
259 void dynabuf::reserve(size_t s, bool keep)
266 p[buflen]=bufptr[buflen];
272 bool dynabuf::operator==(const char* s) const
274 if (buflen==NULL || s==NULL)
275 return (buflen==NULL && s==NULL);
277 return strcmp(bufptr,s)==0;
280 void GetServerVariable(PHTTP_FILTER_CONTEXT pfc, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
286 while (!pfc->GetServerVariable(pfc,lpszVariable,s,&size)) {
287 // Grumble. Check the error.
288 DWORD e=GetLastError();
289 if (e==ERROR_INSUFFICIENT_BUFFER)
294 if (bRequired && s.empty())
298 void GetServerVariable(LPEXTENSION_CONTROL_BLOCK lpECB, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
304 while (!lpECB->GetServerVariable(lpECB->ConnID,lpszVariable,s,&size)) {
305 // Grumble. Check the error.
306 DWORD e=GetLastError();
307 if (e==ERROR_INSUFFICIENT_BUFFER)
312 if (bRequired && s.empty())
316 void GetHeader(PHTTP_FILTER_PREPROC_HEADERS pn, PHTTP_FILTER_CONTEXT pfc,
317 LPSTR lpszName, dynabuf& s, DWORD size=80, bool bRequired=true)
323 while (!pn->GetHeader(pfc,lpszName,s,&size)) {
324 // Grumble. Check the error.
325 DWORD e=GetLastError();
326 if (e==ERROR_INSUFFICIENT_BUFFER)
331 if (bRequired && s.empty())
335 /****************************************************************************/
338 class ShibTargetIsapiF : public AbstractSPRequest
340 PHTTP_FILTER_CONTEXT m_pfc;
341 PHTTP_FILTER_PREPROC_HEADERS m_pn;
342 map<string,string> m_headers;
343 vector<string> m_certs;
345 string m_scheme,m_hostname,m_uri;
346 mutable string m_remote_addr,m_content_type,m_method;
349 ShibTargetIsapiF(PHTTP_FILTER_CONTEXT pfc, PHTTP_FILTER_PREPROC_HEADERS pn, const site_t& site) {
354 // URL path always come from IIS.
356 GetHeader(pn,pfc,"url",var,256,false);
359 // Port may come from IIS or from site def.
360 if (!g_bNormalizeRequest || (pfc->fIsSecurePort && site.m_sslport.empty()) || (!pfc->fIsSecurePort && site.m_port.empty())) {
361 GetServerVariable(pfc,"SERVER_PORT",var,10);
364 else if (pfc->fIsSecurePort) {
365 m_port = atoi(site.m_sslport.c_str());
368 m_port = atoi(site.m_port.c_str());
371 // Scheme may come from site def or be derived from IIS.
372 m_scheme=site.m_scheme;
373 if (m_scheme.empty() || !g_bNormalizeRequest)
374 m_scheme=pfc->fIsSecurePort ? "https" : "http";
376 GetServerVariable(pfc,"SERVER_NAME",var,32);
378 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
380 if (site.m_name!=m_hostname && site.m_aliases.find(m_hostname)==site.m_aliases.end())
381 m_hostname=site.m_name;
383 ~ShibTargetIsapiF() { }
385 const char* getScheme() const {
386 return m_scheme.c_str();
388 const char* getHostname() const {
389 return m_hostname.c_str();
391 int getPort() const {
394 const char* getRequestURI() const {
395 return m_uri.c_str();
397 const char* getMethod() const {
398 if (m_method.empty()) {
400 GetServerVariable(m_pfc,"REQUEST_METHOD",var,5,false);
404 return m_method.c_str();
406 string getContentType() const {
407 if (m_content_type.empty()) {
409 GetServerVariable(m_pfc,"CONTENT_TYPE",var,32,false);
411 m_content_type = var;
413 return m_content_type;
415 long getContentLength() const {
418 string getRemoteAddr() const {
419 if (m_remote_addr.empty()) {
421 GetServerVariable(m_pfc,"REMOTE_ADDR",var,16,false);
425 return m_remote_addr;
427 void log(SPLogLevel level, const string& msg) {
428 AbstractSPRequest::log(level,msg);
429 if (level >= SPError)
430 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
432 void clearHeader(const char* name) {
433 string hdr(!strcmp(name,"REMOTE_USER") ? "remote-user" : name);
435 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), "");
437 void setHeader(const char* name, const char* value) {
440 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), const_cast<char*>(value));
442 string getHeader(const char* name) const {
446 GetHeader(m_pn, m_pfc, const_cast<char*>(hdr.c_str()), buf, 1024, false);
449 void setRemoteUser(const char* user) {
450 setHeader("remote-user", user);
452 string getRemoteUser() const {
453 return getHeader("remote-user");
455 void setResponseHeader(const char* name, const char* value) {
458 m_headers[name] = value;
460 m_headers.erase(name);
462 long sendResponse(istream& in, long status) {
463 string hdr = string("Connection: close\r\n");
464 for (map<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
465 hdr += i->first + ": " + i->second + "\r\n";
467 const char* codestr="200 OK";
469 case XMLTOOLING_HTTP_STATUS_FORBIDDEN:codestr="403 Forbidden"; break;
470 case XMLTOOLING_HTTP_STATUS_NOTFOUND: codestr="404 Not Found"; break;
471 case XMLTOOLING_HTTP_STATUS_ERROR: codestr="500 Server Error"; break;
473 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, (void*)codestr, (DWORD)hdr.c_str(), 0);
477 DWORD resplen = in.gcount();
478 m_pfc->WriteClient(m_pfc, buf, &resplen, 0);
480 return SF_STATUS_REQ_FINISHED;
482 long sendRedirect(const char* url) {
483 // XXX: Don't support the httpRedirect option, yet.
484 string hdr=string("Location: ") + url + "\r\n"
485 "Content-Type: text/html\r\n"
486 "Content-Length: 40\r\n"
487 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
488 "Cache-Control: private,no-store,no-cache\r\n";
489 for (map<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
490 hdr += i->first + ": " + i->second + "\r\n";
492 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, "302 Please Wait", (DWORD)hdr.c_str(), 0);
493 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
495 m_pfc->WriteClient(m_pfc, (LPVOID)redmsg, &resplen, 0);
496 return SF_STATUS_REQ_FINISHED;
498 long returnDecline() {
499 return SF_STATUS_REQ_NEXT_NOTIFICATION;
502 return SF_STATUS_REQ_NEXT_NOTIFICATION;
505 const vector<string>& getClientCertificates() const {
509 // The filter never processes the POST, so stub these methods.
510 const char* getQueryString() const { throw IOException("getQueryString not implemented"); }
511 const char* getRequestBody() const { throw IOException("getRequestBody not implemented"); }
514 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const char* msg)
516 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
517 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
518 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)ctype,0);
519 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Filter Error</TITLE></HEAD><BODY>"
520 "<H1>Shibboleth Filter Error</H1>";
521 DWORD resplen=strlen(xmsg);
522 pfc->WriteClient(pfc,(LPVOID)xmsg,&resplen,0);
524 pfc->WriteClient(pfc,(LPVOID)msg,&resplen,0);
525 static const char* xmsg2="</BODY></HTML>";
526 resplen=strlen(xmsg2);
527 pfc->WriteClient(pfc,(LPVOID)xmsg2,&resplen,0);
528 return SF_STATUS_REQ_FINISHED;
531 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
533 // Is this a log notification?
534 if (notificationType==SF_NOTIFY_LOG)
536 if (pfc->pFilterContext)
537 ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=static_cast<LPCSTR>(pfc->pFilterContext);
538 return SF_STATUS_REQ_NEXT_NOTIFICATION;
541 PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
544 // Determine web site number. This can't really fail, I don't think.
546 GetServerVariable(pfc,"INSTANCE_ID",buf,10);
548 // Match site instance to host name, skip if no match.
549 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
550 if (map_i==g_Sites.end())
551 return SF_STATUS_REQ_NEXT_NOTIFICATION;
553 ostringstream threadid;
554 threadid << "[" << getpid() << "] isapi_shib" << '\0';
555 xmltooling::NDC ndc(threadid.str().c_str());
557 ShibTargetIsapiF stf(pfc, pn, map_i->second);
559 // "false" because we don't override the Shib settings
560 pair<bool,long> res = stf.getServiceProvider().doAuthentication(stf);
561 if (res.first) return res.second;
563 // "false" because we don't override the Shib settings
564 res = stf.getServiceProvider().doExport(stf);
565 if (res.first) return res.second;
567 res = stf.getServiceProvider().doAuthorization(stf);
568 if (res.first) return res.second;
570 return SF_STATUS_REQ_NEXT_NOTIFICATION;
573 return WriteClientError(pfc,"Out of Memory");
576 if (e==ERROR_NO_DATA)
577 return WriteClientError(pfc,"A required variable or header was empty.");
579 return WriteClientError(pfc,"Shibboleth Filter detected unexpected IIS error.");
581 catch (exception& e) {
582 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
583 return WriteClientError(pfc,"Shibboleth Filter caught an exception, check Event Log for details.");
587 return WriteClientError(pfc,"Shibboleth Filter caught an unknown exception.");
591 return WriteClientError(pfc,"Shibboleth Filter reached unreachable code, save my walrus!");
595 /****************************************************************************/
598 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* msg)
600 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
601 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
602 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
603 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Error</TITLE></HEAD><BODY><H1>Shibboleth Error</H1>";
604 DWORD resplen=strlen(xmsg);
605 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg,&resplen,HSE_IO_SYNC);
607 lpECB->WriteClient(lpECB->ConnID,(LPVOID)msg,&resplen,HSE_IO_SYNC);
608 static const char* xmsg2="</BODY></HTML>";
609 resplen=strlen(xmsg2);
610 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg2,&resplen,HSE_IO_SYNC);
611 return HSE_STATUS_SUCCESS;
615 class ShibTargetIsapiE : public AbstractSPRequest
617 LPEXTENSION_CONTROL_BLOCK m_lpECB;
618 map<string,string> m_headers;
619 vector<string> m_certs;
620 mutable string m_body;
621 mutable bool m_gotBody;
623 string m_scheme,m_hostname,m_uri;
624 mutable string m_remote_addr;
627 ShibTargetIsapiE(LPEXTENSION_CONTROL_BLOCK lpECB, const site_t& site) : m_lpECB(lpECB), m_gotBody(false) {
629 GetServerVariable(lpECB,"HTTPS",ssl,5);
630 bool SSL=(ssl=="on" || ssl=="ON");
632 // Scheme may come from site def or be derived from IIS.
633 m_scheme=site.m_scheme;
634 if (m_scheme.empty() || !g_bNormalizeRequest)
635 m_scheme = SSL ? "https" : "http";
637 // URL path always come from IIS.
639 GetServerVariable(lpECB,"URL",url,255);
641 // Port may come from IIS or from site def.
643 if (!g_bNormalizeRequest || (SSL && site.m_sslport.empty()) || (!SSL && site.m_port.empty()))
644 GetServerVariable(lpECB,"SERVER_PORT",port,10);
646 strncpy(port,site.m_sslport.c_str(),10);
647 static_cast<char*>(port)[10]=0;
650 strncpy(port,site.m_port.c_str(),10);
651 static_cast<char*>(port)[10]=0;
656 GetServerVariable(lpECB, "SERVER_NAME", var, 32);
658 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
660 if (site.m_name!=m_hostname && site.m_aliases.find(m_hostname)==site.m_aliases.end())
661 m_hostname=site.m_name;
664 * IIS screws us over on PATH_INFO (the hits keep on coming). We need to figure out if
665 * the server is set up for proper PATH_INFO handling, or "IIS sucks rabid weasels mode",
666 * which is the default. No perfect way to tell, but we can take a good guess by checking
667 * whether the URL is a substring of the PATH_INFO:
669 * e.g. for /Shibboleth.sso/SAML/POST
671 * Bad mode (default):
672 * URL: /Shibboleth.sso
673 * PathInfo: /Shibboleth.sso/SAML/POST
676 * URL: /Shibboleth.sso
677 * PathInfo: /SAML/POST
680 // Clearly we're only in bad mode if path info exists at all.
681 if (lpECB->lpszPathInfo && *(lpECB->lpszPathInfo)) {
682 if (strstr(lpECB->lpszPathInfo,url))
683 // Pretty good chance we're in bad mode, unless the PathInfo repeats the path itself.
684 m_uri = lpECB->lpszPathInfo;
687 m_uri += lpECB->lpszPathInfo;
691 // For consistency with Apache, let's add the query string.
692 if (lpECB->lpszQueryString && *(lpECB->lpszQueryString)) {
694 m_uri += lpECB->lpszQueryString;
697 ~ShibTargetIsapiE() { }
699 const char* getScheme() const {
700 return m_scheme.c_str();
702 const char* getHostname() const {
703 return m_hostname.c_str();
705 int getPort() const {
708 const char* getRequestURI() const {
709 return m_uri.c_str();
711 const char* getMethod() const {
712 return m_lpECB->lpszMethod;
714 string getContentType() const {
715 return m_lpECB->lpszContentType ? m_lpECB->lpszContentType : "";
717 long getContentLength() const {
718 return m_lpECB->cbTotalBytes;
720 string getRemoteAddr() const {
721 if (m_remote_addr.empty()) {
723 GetServerVariable(m_lpECB, "REMOTE_ADDR", var, 16, false);
727 return m_remote_addr;
729 void log(SPLogLevel level, const string& msg) {
730 AbstractSPRequest::log(level,msg);
731 if (level >= SPError)
732 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
734 string getHeader(const char* name) const {
736 for (; *name; ++name) {
740 hdr += toupper(*name);
743 GetServerVariable(m_lpECB, const_cast<char*>(hdr.c_str()), buf, 128, false);
744 return buf.empty() ? "" : buf;
746 void setResponseHeader(const char* name, const char* value) {
749 m_headers[name] = value;
751 m_headers.erase(name);
753 const char* getQueryString() const {
754 return m_lpECB->lpszQueryString;
756 const char* getRequestBody() const {
758 return m_body.c_str();
759 if (m_lpECB->cbTotalBytes > 1024*1024) // 1MB?
760 throw opensaml::SecurityPolicyException("Size of request body exceeded 1M size limit.");
761 else if (m_lpECB->cbTotalBytes > m_lpECB->cbAvailable) {
764 DWORD datalen=m_lpECB->cbTotalBytes;
767 BOOL ret = m_lpECB->ReadClient(m_lpECB->ConnID, buf, &buflen);
769 throw IOException("Error reading request body from browser.");
770 m_body.append(buf, buflen);
774 else if (m_lpECB->cbAvailable) {
776 m_body.assign(reinterpret_cast<char*>(m_lpECB->lpbData),m_lpECB->cbAvailable);
778 return m_body.c_str();
780 long sendResponse(istream& in, long status) {
781 string hdr = string("Connection: close\r\n");
782 for (map<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
783 hdr += i->first + ": " + i->second + "\r\n";
785 const char* codestr="200 OK";
787 case XMLTOOLING_HTTP_STATUS_FORBIDDEN:codestr="403 Forbidden"; break;
788 case XMLTOOLING_HTTP_STATUS_NOTFOUND: codestr="404 Not Found"; break;
789 case XMLTOOLING_HTTP_STATUS_ERROR: codestr="500 Server Error"; break;
791 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, (void*)codestr, 0, (LPDWORD)hdr.c_str());
795 DWORD resplen = in.gcount();
796 m_lpECB->WriteClient(m_lpECB->ConnID, buf, &resplen, HSE_IO_SYNC);
798 return HSE_STATUS_SUCCESS;
800 long sendRedirect(const char* url) {
801 string hdr=string("Location: ") + url + "\r\n"
802 "Content-Type: text/html\r\n"
803 "Content-Length: 40\r\n"
804 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
805 "Cache-Control: private,no-store,no-cache\r\n";
806 for (map<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
807 hdr += i->first + ": " + i->second + "\r\n";
809 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, "302 Moved", 0, (LPDWORD)hdr.c_str());
810 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
812 m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)redmsg, &resplen, HSE_IO_SYNC);
813 return HSE_STATUS_SUCCESS;
815 // Decline happens in the POST processor if this isn't the shire url
816 // Note that it can also happen with HTAccess, but we don't support that, yet.
817 long returnDecline() {
818 return WriteClientError(
820 "ISAPI extension can only be invoked to process Shibboleth protocol requests."
821 "Make sure the mapped file extension doesn't match actual content."
825 return HSE_STATUS_SUCCESS;
828 const vector<string>& getClientCertificates() const {
832 // Not used in the extension.
833 virtual void clearHeader(const char* name) { throw runtime_error("clearHeader not implemented"); }
834 virtual void setHeader(const char* name, const char* value) { throw runtime_error("setHeader not implemented"); }
835 virtual void setRemoteUser(const char* user) { throw runtime_error("setRemoteUser not implemented"); }
836 virtual string getRemoteUser() const { throw runtime_error("getRemoteUser not implemented"); }
839 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
842 ostringstream threadid;
843 threadid << "[" << getpid() << "] isapi_shib_extension" << '\0';
844 xmltooling::NDC ndc(threadid.str().c_str());
846 // Determine web site number. This can't really fail, I don't think.
848 GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
850 // Match site instance to host name, skip if no match.
851 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
852 if (map_i==g_Sites.end())
853 return WriteClientError(lpECB, "Shibboleth Extension not configured for this web site.");
855 ShibTargetIsapiE ste(lpECB, map_i->second);
856 pair<bool,long> res = ste.getServiceProvider().doHandler(ste);
857 if (res.first) return res.second;
859 return WriteClientError(lpECB, "Shibboleth Extension failed to process request");
863 return WriteClientError(lpECB,"Out of Memory");
866 if (e==ERROR_NO_DATA)
867 return WriteClientError(lpECB,"A required variable or header was empty.");
869 return WriteClientError(lpECB,"Server detected unexpected IIS error.");
871 catch (exception& e) {
872 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
873 return WriteClientError(lpECB,"Shibboleth Extension caught an exception, check Event Log for details.");
877 return WriteClientError(lpECB,"Shibboleth Extension caught an unknown exception.");
881 // If we get here we've got an error.
882 return HSE_STATUS_ERROR;