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/Base64.hpp>
38 #include <xercesc/util/XMLUniDefs.hpp>
49 using namespace shibsp;
50 using namespace xmltooling;
51 using namespace xercesc;
56 static const XMLCh path[] = UNICODE_LITERAL_4(p,a,t,h);
57 static const XMLCh validate[] = UNICODE_LITERAL_8(v,a,l,i,d,a,t,e);
58 static const XMLCh name[] = UNICODE_LITERAL_4(n,a,m,e);
59 static const XMLCh port[] = UNICODE_LITERAL_4(p,o,r,t);
60 static const XMLCh sslport[] = UNICODE_LITERAL_7(s,s,l,p,o,r,t);
61 static const XMLCh scheme[] = UNICODE_LITERAL_6(s,c,h,e,m,e);
62 static const XMLCh id[] = UNICODE_LITERAL_2(i,d);
63 static const XMLCh Implementation[] = UNICODE_LITERAL_14(I,m,p,l,e,m,e,n,t,a,t,i,o,n);
64 static const XMLCh ISAPI[] = UNICODE_LITERAL_5(I,S,A,P,I);
65 static const XMLCh Alias[] = UNICODE_LITERAL_5(A,l,i,a,s);
66 static const XMLCh normalizeRequest[] = UNICODE_LITERAL_16(n,o,r,m,a,l,i,z,e,R,e,q,u,e,s,t);
67 static const XMLCh Site[] = UNICODE_LITERAL_4(S,i,t,e);
70 site_t(const DOMElement* e)
72 auto_ptr_char n(e->getAttributeNS(NULL,name));
73 auto_ptr_char s(e->getAttributeNS(NULL,scheme));
74 auto_ptr_char p(e->getAttributeNS(NULL,port));
75 auto_ptr_char p2(e->getAttributeNS(NULL,sslport));
76 if (n.get()) m_name=n.get();
77 if (s.get()) m_scheme=s.get();
78 if (p.get()) m_port=p.get();
79 if (p2.get()) m_sslport=p2.get();
80 e = XMLHelper::getFirstChildElement(e, Alias);
82 if (e->hasChildNodes()) {
83 auto_ptr_char alias(e->getFirstChild()->getNodeValue());
84 m_aliases.insert(alias.get());
86 e = XMLHelper::getNextSiblingElement(e, Alias);
89 string m_scheme,m_port,m_sslport,m_name;
90 set<string> m_aliases;
94 SPConfig* g_Config = NULL;
95 map<string,site_t> g_Sites;
96 bool g_bNormalizeRequest = true;
97 vector<string> g_NoCerts;
101 LPCSTR lpUNCServerName,
107 LPCSTR messages[] = {message, NULL};
109 HANDLE hElog = RegisterEventSource(lpUNCServerName, "Shibboleth ISAPI Filter");
110 BOOL res = ReportEvent(hElog, wType, 0, dwEventID, lpUserSid, 1, 0, messages, NULL);
111 return (DeregisterEventSource(hElog) && res);
114 extern "C" __declspec(dllexport) BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID)
116 if (fdwReason==DLL_PROCESS_ATTACH)
121 extern "C" BOOL WINAPI GetExtensionVersion(HSE_VERSION_INFO* pVer)
127 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
128 "Extension mode startup not possible, is the DLL loaded as a filter?");
132 pVer->dwExtensionVersion=HSE_VERSION;
133 strncpy(pVer->lpszExtensionDesc,"Shibboleth ISAPI Extension",HSE_MAX_EXT_DLL_NAME_LEN-1);
137 extern "C" BOOL WINAPI TerminateExtension(DWORD)
139 return TRUE; // cleanup should happen when filter unloads
142 extern "C" BOOL WINAPI GetFilterVersion(PHTTP_FILTER_VERSION pVer)
147 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
148 "Reentrant filter initialization, ignoring...");
152 LPCSTR schemadir=getenv("SHIBSP_SCHEMAS");
154 schemadir=SHIBSP_SCHEMAS;
155 LPCSTR config=getenv("SHIBSP_CONFIG");
157 config=SHIBSP_CONFIG;
158 g_Config=&SPConfig::getConfig();
159 g_Config->setFeatures(
162 SPConfig::RequestMapping |
163 SPConfig::InProcess |
166 if (!g_Config->init(schemadir)) {
168 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
169 "Filter startup failed during library initialization, check native log for help.");
174 DOMDocument* dummydoc=XMLToolingConfig::getConfig().getParser().newDocument();
175 XercesJanitor<DOMDocument> docjanitor(dummydoc);
176 DOMElement* dummy = dummydoc->createElementNS(NULL,path);
177 auto_ptr_XMLCh src(config);
178 dummy->setAttributeNS(NULL,path,src.get());
179 dummy->setAttributeNS(NULL,validate,xmlconstants::XML_ONE);
181 g_Config->setServiceProvider(g_Config->ServiceProviderManager.newPlugin(XML_SERVICE_PROVIDER,dummy));
182 g_Config->getServiceProvider()->init();
184 catch (exception& ex) {
187 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, ex.what());
188 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
189 "Filter startup failed to load configuration, check native log for details.");
193 // Access the implementation-specifics for site mappings.
194 ServiceProvider* sp=g_Config->getServiceProvider();
195 xmltooling::Locker locker(sp);
196 const PropertySet* props=sp->getPropertySet("InProcess");
198 const DOMElement* impl=XMLHelper::getFirstChildElement(props->getElement(),Implementation);
199 if (impl && (impl=XMLHelper::getFirstChildElement(impl,ISAPI))) {
200 const XMLCh* flag=impl->getAttributeNS(NULL,normalizeRequest);
201 g_bNormalizeRequest=(!flag || !*flag || *flag==chDigit_1 || *flag==chLatin_t);
202 impl=XMLHelper::getFirstChildElement(impl,Site);
204 auto_ptr_char id(impl->getAttributeNS(NULL,id));
206 g_Sites.insert(pair<string,site_t>(id.get(),site_t(impl)));
207 impl=XMLHelper::getNextSiblingElement(impl,Site);
212 pVer->dwFilterVersion=HTTP_FILTER_REVISION;
213 strncpy(pVer->lpszFilterDesc,"Shibboleth ISAPI Filter",SF_MAX_FILTER_DESC_LEN);
214 pVer->dwFlags=(SF_NOTIFY_ORDER_HIGH |
215 SF_NOTIFY_SECURE_PORT |
216 SF_NOTIFY_NONSECURE_PORT |
217 SF_NOTIFY_PREPROC_HEADERS |
219 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter initialized...");
223 extern "C" BOOL WINAPI TerminateFilter(DWORD)
228 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter shut down...");
232 /* Next up, some suck-free versions of various APIs.
234 You DON'T require people to guess the buffer size and THEN tell them the right size.
235 Returning an LPCSTR is apparently way beyond their ken. Not to mention the fact that
236 constant strings aren't typed as such, making it just that much harder. These versions
237 are now updated to use a special growable buffer object, modeled after the standard
238 string class. The standard string won't work because they left out the option to
239 pre-allocate a non-constant buffer.
245 dynabuf() { bufptr=NULL; buflen=0; }
246 dynabuf(size_t s) { bufptr=new char[buflen=s]; *bufptr=0; }
247 ~dynabuf() { delete[] bufptr; }
248 size_t length() const { return bufptr ? strlen(bufptr) : 0; }
249 size_t size() const { return buflen; }
250 bool empty() const { return length()==0; }
251 void reserve(size_t s, bool keep=false);
252 void erase() { if (bufptr) memset(bufptr,0,buflen); }
253 operator char*() { return bufptr; }
254 bool operator ==(const char* s) const;
255 bool operator !=(const char* s) const { return !(*this==s); }
261 void dynabuf::reserve(size_t s, bool keep)
268 p[buflen]=bufptr[buflen];
274 bool dynabuf::operator==(const char* s) const
276 if (buflen==NULL || s==NULL)
277 return (buflen==NULL && s==NULL);
279 return strcmp(bufptr,s)==0;
282 void GetServerVariable(PHTTP_FILTER_CONTEXT pfc, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
288 while (!pfc->GetServerVariable(pfc,lpszVariable,s,&size)) {
289 // Grumble. Check the error.
290 DWORD e=GetLastError();
291 if (e==ERROR_INSUFFICIENT_BUFFER)
296 if (bRequired && s.empty())
300 void GetServerVariable(LPEXTENSION_CONTROL_BLOCK lpECB, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
306 while (!lpECB->GetServerVariable(lpECB->ConnID,lpszVariable,s,&size)) {
307 // Grumble. Check the error.
308 DWORD e=GetLastError();
309 if (e==ERROR_INSUFFICIENT_BUFFER)
314 if (bRequired && s.empty())
318 void GetHeader(PHTTP_FILTER_PREPROC_HEADERS pn, PHTTP_FILTER_CONTEXT pfc,
319 LPSTR lpszName, dynabuf& s, DWORD size=80, bool bRequired=true)
325 while (!pn->GetHeader(pfc,lpszName,s,&size)) {
326 // Grumble. Check the error.
327 DWORD e=GetLastError();
328 if (e==ERROR_INSUFFICIENT_BUFFER)
333 if (bRequired && s.empty())
337 /****************************************************************************/
340 class ShibTargetIsapiF : public AbstractSPRequest
342 PHTTP_FILTER_CONTEXT m_pfc;
343 PHTTP_FILTER_PREPROC_HEADERS m_pn;
344 map<string,string> m_headers;
346 string m_scheme,m_hostname,m_uri;
347 mutable string m_remote_addr,m_content_type,m_method;
350 ShibTargetIsapiF(PHTTP_FILTER_CONTEXT pfc, PHTTP_FILTER_PREPROC_HEADERS pn, const site_t& site) {
355 // URL path always come from IIS.
357 GetHeader(pn,pfc,"url",var,256,false);
360 // Port may come from IIS or from site def.
361 if (!g_bNormalizeRequest || (pfc->fIsSecurePort && site.m_sslport.empty()) || (!pfc->fIsSecurePort && site.m_port.empty())) {
362 GetServerVariable(pfc,"SERVER_PORT",var,10);
365 else if (pfc->fIsSecurePort) {
366 m_port = atoi(site.m_sslport.c_str());
369 m_port = atoi(site.m_port.c_str());
372 // Scheme may come from site def or be derived from IIS.
373 m_scheme=site.m_scheme;
374 if (m_scheme.empty() || !g_bNormalizeRequest)
375 m_scheme=pfc->fIsSecurePort ? "https" : "http";
377 GetServerVariable(pfc,"SERVER_NAME",var,32);
379 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
381 if (site.m_name!=m_hostname && site.m_aliases.find(m_hostname)==site.m_aliases.end())
382 m_hostname=site.m_name;
384 ~ShibTargetIsapiF() { }
386 const char* getScheme() const {
387 return m_scheme.c_str();
389 const char* getHostname() const {
390 return m_hostname.c_str();
392 int getPort() const {
395 const char* getRequestURI() const {
396 return m_uri.c_str();
398 const char* getMethod() const {
399 if (m_method.empty()) {
401 GetServerVariable(m_pfc,"REQUEST_METHOD",var,5,false);
405 return m_method.c_str();
407 string getContentType() const {
408 if (m_content_type.empty()) {
410 GetServerVariable(m_pfc,"CONTENT_TYPE",var,32,false);
412 m_content_type = var;
414 return m_content_type;
416 long getContentLength() const {
419 string getRemoteAddr() const {
420 if (m_remote_addr.empty()) {
422 GetServerVariable(m_pfc,"REMOTE_ADDR",var,16,false);
426 return m_remote_addr;
428 void log(SPLogLevel level, const string& msg) {
429 AbstractSPRequest::log(level,msg);
430 if (level >= SPError)
431 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
433 void clearHeader(const char* name) {
434 string hdr(!strcmp(name,"REMOTE_USER") ? "remote-user" : name);
436 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), "");
438 void setHeader(const char* name, const char* value) {
441 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), const_cast<char*>(value));
443 string getHeader(const char* name) const {
447 GetHeader(m_pn, m_pfc, const_cast<char*>(hdr.c_str()), buf, 1024, false);
450 void setRemoteUser(const char* user) {
451 setHeader("remote-user", user);
453 string getRemoteUser() const {
454 return getHeader("remote-user");
456 void setResponseHeader(const char* name, const char* value) {
459 m_headers[name] = value;
461 m_headers.erase(name);
463 long sendResponse(istream& in, long status) {
464 string hdr = string("Connection: close\r\n");
465 for (map<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
466 hdr += i->first + ": " + i->second + "\r\n";
468 const char* codestr="200 OK";
470 case XMLTOOLING_HTTP_STATUS_FORBIDDEN:codestr="403 Forbidden"; break;
471 case XMLTOOLING_HTTP_STATUS_NOTFOUND: codestr="404 Not Found"; break;
472 case XMLTOOLING_HTTP_STATUS_ERROR: codestr="500 Server Error"; break;
474 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, (void*)codestr, (DWORD)hdr.c_str(), 0);
478 DWORD resplen = in.gcount();
479 m_pfc->WriteClient(m_pfc, buf, &resplen, 0);
481 return SF_STATUS_REQ_FINISHED;
483 long sendRedirect(const char* url) {
484 // XXX: Don't support the httpRedirect option, yet.
485 string hdr=string("Location: ") + url + "\r\n"
486 "Content-Type: text/html\r\n"
487 "Content-Length: 40\r\n"
488 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
489 "Cache-Control: private,no-store,no-cache\r\n";
490 for (map<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
491 hdr += i->first + ": " + i->second + "\r\n";
493 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, "302 Please Wait", (DWORD)hdr.c_str(), 0);
494 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
496 m_pfc->WriteClient(m_pfc, (LPVOID)redmsg, &resplen, 0);
497 return SF_STATUS_REQ_FINISHED;
499 long returnDecline() {
500 return SF_STATUS_REQ_NEXT_NOTIFICATION;
503 return SF_STATUS_REQ_NEXT_NOTIFICATION;
506 const vector<string>& getClientCertificates() const {
510 // The filter never processes the POST, so stub these methods.
511 const char* getQueryString() const { throw IOException("getQueryString not implemented"); }
512 const char* getRequestBody() const { throw IOException("getRequestBody not implemented"); }
515 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const char* msg)
517 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
518 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
519 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)ctype,0);
520 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Filter Error</TITLE></HEAD><BODY>"
521 "<H1>Shibboleth Filter Error</H1>";
522 DWORD resplen=strlen(xmsg);
523 pfc->WriteClient(pfc,(LPVOID)xmsg,&resplen,0);
525 pfc->WriteClient(pfc,(LPVOID)msg,&resplen,0);
526 static const char* xmsg2="</BODY></HTML>";
527 resplen=strlen(xmsg2);
528 pfc->WriteClient(pfc,(LPVOID)xmsg2,&resplen,0);
529 return SF_STATUS_REQ_FINISHED;
532 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
534 // Is this a log notification?
535 if (notificationType==SF_NOTIFY_LOG)
537 if (pfc->pFilterContext)
538 ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=static_cast<LPCSTR>(pfc->pFilterContext);
539 return SF_STATUS_REQ_NEXT_NOTIFICATION;
542 PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
545 // Determine web site number. This can't really fail, I don't think.
547 GetServerVariable(pfc,"INSTANCE_ID",buf,10);
549 // Match site instance to host name, skip if no match.
550 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
551 if (map_i==g_Sites.end())
552 return SF_STATUS_REQ_NEXT_NOTIFICATION;
554 ostringstream threadid;
555 threadid << "[" << getpid() << "] isapi_shib" << '\0';
556 xmltooling::NDC ndc(threadid.str().c_str());
558 ShibTargetIsapiF stf(pfc, pn, map_i->second);
560 // "false" because we don't override the Shib settings
561 pair<bool,long> res = stf.getServiceProvider().doAuthentication(stf);
562 if (res.first) return res.second;
564 // "false" because we don't override the Shib settings
565 res = stf.getServiceProvider().doExport(stf);
566 if (res.first) return res.second;
568 res = stf.getServiceProvider().doAuthorization(stf);
569 if (res.first) return res.second;
571 return SF_STATUS_REQ_NEXT_NOTIFICATION;
574 return WriteClientError(pfc,"Out of Memory");
577 if (e==ERROR_NO_DATA)
578 return WriteClientError(pfc,"A required variable or header was empty.");
580 return WriteClientError(pfc,"Shibboleth Filter detected unexpected IIS error.");
582 catch (exception& e) {
583 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
584 return WriteClientError(pfc,"Shibboleth Filter caught an exception, check Event Log for details.");
588 return WriteClientError(pfc,"Shibboleth Filter caught an unknown exception.");
592 return WriteClientError(pfc,"Shibboleth Filter reached unreachable code, save my walrus!");
596 /****************************************************************************/
599 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* msg)
601 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
602 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
603 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
604 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Error</TITLE></HEAD><BODY><H1>Shibboleth Error</H1>";
605 DWORD resplen=strlen(xmsg);
606 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg,&resplen,HSE_IO_SYNC);
608 lpECB->WriteClient(lpECB->ConnID,(LPVOID)msg,&resplen,HSE_IO_SYNC);
609 static const char* xmsg2="</BODY></HTML>";
610 resplen=strlen(xmsg2);
611 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg2,&resplen,HSE_IO_SYNC);
612 return HSE_STATUS_SUCCESS;
616 class ShibTargetIsapiE : public AbstractSPRequest
618 LPEXTENSION_CONTROL_BLOCK m_lpECB;
619 map<string,string> m_headers;
620 mutable vector<string> m_certs;
621 mutable string m_body;
622 mutable bool m_gotBody;
624 string m_scheme,m_hostname,m_uri;
625 mutable string m_remote_addr,m_remote_user;
628 ShibTargetIsapiE(LPEXTENSION_CONTROL_BLOCK lpECB, const site_t& site) : m_lpECB(lpECB), m_gotBody(false) {
630 GetServerVariable(lpECB,"HTTPS",ssl,5);
631 bool SSL=(ssl=="on" || ssl=="ON");
633 // Scheme may come from site def or be derived from IIS.
634 m_scheme=site.m_scheme;
635 if (m_scheme.empty() || !g_bNormalizeRequest)
636 m_scheme = SSL ? "https" : "http";
638 // URL path always come from IIS.
640 GetServerVariable(lpECB,"URL",url,255);
642 // Port may come from IIS or from site def.
644 if (!g_bNormalizeRequest || (SSL && site.m_sslport.empty()) || (!SSL && site.m_port.empty()))
645 GetServerVariable(lpECB,"SERVER_PORT",port,10);
647 strncpy(port,site.m_sslport.c_str(),10);
648 static_cast<char*>(port)[10]=0;
651 strncpy(port,site.m_port.c_str(),10);
652 static_cast<char*>(port)[10]=0;
657 GetServerVariable(lpECB, "SERVER_NAME", var, 32);
659 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
661 if (site.m_name!=m_hostname && site.m_aliases.find(m_hostname)==site.m_aliases.end())
662 m_hostname=site.m_name;
665 * IIS screws us over on PATH_INFO (the hits keep on coming). We need to figure out if
666 * the server is set up for proper PATH_INFO handling, or "IIS sucks rabid weasels mode",
667 * which is the default. No perfect way to tell, but we can take a good guess by checking
668 * whether the URL is a substring of the PATH_INFO:
670 * e.g. for /Shibboleth.sso/SAML/POST
672 * Bad mode (default):
673 * URL: /Shibboleth.sso
674 * PathInfo: /Shibboleth.sso/SAML/POST
677 * URL: /Shibboleth.sso
678 * PathInfo: /SAML/POST
681 // Clearly we're only in bad mode if path info exists at all.
682 if (lpECB->lpszPathInfo && *(lpECB->lpszPathInfo)) {
683 if (strstr(lpECB->lpszPathInfo,url))
684 // Pretty good chance we're in bad mode, unless the PathInfo repeats the path itself.
685 m_uri = lpECB->lpszPathInfo;
688 m_uri += lpECB->lpszPathInfo;
692 // For consistency with Apache, let's add the query string.
693 if (lpECB->lpszQueryString && *(lpECB->lpszQueryString)) {
695 m_uri += lpECB->lpszQueryString;
698 ~ShibTargetIsapiE() { }
700 const char* getScheme() const {
701 return m_scheme.c_str();
703 const char* getHostname() const {
704 return m_hostname.c_str();
706 int getPort() const {
709 const char* getRequestURI() const {
710 return m_uri.c_str();
712 const char* getMethod() const {
713 return m_lpECB->lpszMethod;
715 string getContentType() const {
716 return m_lpECB->lpszContentType ? m_lpECB->lpszContentType : "";
718 long getContentLength() const {
719 return m_lpECB->cbTotalBytes;
721 string getRemoteUser() const {
722 if (m_remote_user.empty()) {
724 GetServerVariable(m_lpECB, "REMOTE_USER", var, 32, false);
728 return m_remote_user;
730 string getRemoteAddr() const {
731 if (m_remote_addr.empty()) {
733 GetServerVariable(m_lpECB, "REMOTE_ADDR", var, 16, false);
737 return m_remote_addr;
739 void log(SPLogLevel level, const string& msg) const {
740 AbstractSPRequest::log(level,msg);
741 if (level >= SPError)
742 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
744 string getHeader(const char* name) const {
746 for (; *name; ++name) {
750 hdr += toupper(*name);
753 GetServerVariable(m_lpECB, const_cast<char*>(hdr.c_str()), buf, 128, false);
754 return buf.empty() ? "" : buf;
756 void setResponseHeader(const char* name, const char* value) {
759 m_headers[name] = value;
761 m_headers.erase(name);
763 const char* getQueryString() const {
764 return m_lpECB->lpszQueryString;
766 const char* getRequestBody() const {
768 return m_body.c_str();
769 if (m_lpECB->cbTotalBytes > 1024*1024) // 1MB?
770 throw opensaml::SecurityPolicyException("Size of request body exceeded 1M size limit.");
771 else if (m_lpECB->cbTotalBytes > m_lpECB->cbAvailable) {
774 DWORD datalen=m_lpECB->cbTotalBytes;
777 BOOL ret = m_lpECB->ReadClient(m_lpECB->ConnID, buf, &buflen);
779 throw IOException("Error reading request body from browser.");
780 m_body.append(buf, buflen);
784 else if (m_lpECB->cbAvailable) {
786 m_body.assign(reinterpret_cast<char*>(m_lpECB->lpbData),m_lpECB->cbAvailable);
788 return m_body.c_str();
790 long sendResponse(istream& in, long status) {
791 string hdr = string("Connection: close\r\n");
792 for (map<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
793 hdr += i->first + ": " + i->second + "\r\n";
795 const char* codestr="200 OK";
797 case XMLTOOLING_HTTP_STATUS_FORBIDDEN:codestr="403 Forbidden"; break;
798 case XMLTOOLING_HTTP_STATUS_NOTFOUND: codestr="404 Not Found"; break;
799 case XMLTOOLING_HTTP_STATUS_ERROR: codestr="500 Server Error"; break;
801 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, (void*)codestr, 0, (LPDWORD)hdr.c_str());
805 DWORD resplen = in.gcount();
806 m_lpECB->WriteClient(m_lpECB->ConnID, buf, &resplen, HSE_IO_SYNC);
808 return HSE_STATUS_SUCCESS;
810 long sendRedirect(const char* url) {
811 string hdr=string("Location: ") + url + "\r\n"
812 "Content-Type: text/html\r\n"
813 "Content-Length: 40\r\n"
814 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
815 "Cache-Control: private,no-store,no-cache\r\n";
816 for (map<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
817 hdr += i->first + ": " + i->second + "\r\n";
819 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, "302 Moved", 0, (LPDWORD)hdr.c_str());
820 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
822 m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)redmsg, &resplen, HSE_IO_SYNC);
823 return HSE_STATUS_SUCCESS;
825 // Decline happens in the POST processor if this isn't the shire url
826 // Note that it can also happen with HTAccess, but we don't support that, yet.
827 long returnDecline() {
828 return WriteClientError(
830 "ISAPI extension can only be invoked to process Shibboleth protocol requests."
831 "Make sure the mapped file extension doesn't match actual content."
835 return HSE_STATUS_SUCCESS;
838 const vector<string>& getClientCertificates() const {
839 if (m_certs.empty()) {
840 char CertificateBuf[8192];
841 CERT_CONTEXT_EX ccex;
842 ccex.cbAllocated = sizeof(CertificateBuf);
843 ccex.CertContext.pbCertEncoded = (BYTE*)CertificateBuf;
844 DWORD dwSize = sizeof(ccex);
846 if (m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_GET_CERT_INFO_EX, (LPVOID)&ccex, (LPDWORD)dwSize, NULL)) {
847 if (ccex.CertContext.cbCertEncoded) {
849 XMLByte* serialized = Base64::encode(reinterpret_cast<XMLByte*>(CertificateBuf), ccex.CertContext.cbCertEncoded, &outlen);
850 m_certs.push_back(reinterpret_cast<char*>(serialized));
851 XMLString::release(&serialized);
858 // Not used in the extension.
859 virtual void clearHeader(const char* name) { throw runtime_error("clearHeader not implemented"); }
860 virtual void setHeader(const char* name, const char* value) { throw runtime_error("setHeader not implemented"); }
861 virtual void setRemoteUser(const char* user) { throw runtime_error("setRemoteUser not implemented"); }
864 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
867 ostringstream threadid;
868 threadid << "[" << getpid() << "] isapi_shib_extension" << '\0';
869 xmltooling::NDC ndc(threadid.str().c_str());
871 // Determine web site number. This can't really fail, I don't think.
873 GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
875 // Match site instance to host name, skip if no match.
876 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
877 if (map_i==g_Sites.end())
878 return WriteClientError(lpECB, "Shibboleth Extension not configured for this web site.");
880 ShibTargetIsapiE ste(lpECB, map_i->second);
881 pair<bool,long> res = ste.getServiceProvider().doHandler(ste);
882 if (res.first) return res.second;
884 return WriteClientError(lpECB, "Shibboleth Extension failed to process request");
888 return WriteClientError(lpECB,"Out of Memory");
891 if (e==ERROR_NO_DATA)
892 return WriteClientError(lpECB,"A required variable or header was empty.");
894 return WriteClientError(lpECB,"Server detected unexpected IIS error.");
896 catch (exception& e) {
897 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
898 return WriteClientError(lpECB,"Shibboleth Extension caught an exception, check Event Log for details.");
902 return WriteClientError(lpECB,"Shibboleth Extension caught an unknown exception.");
906 // If we get here we've got an error.
907 return HSE_STATUS_ERROR;