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("SHIBSP_SCHEMAS");
149 schemadir=SHIBSP_SCHEMAS;
150 LPCSTR config=getenv("SHIBSP_CONFIG");
152 config=SHIBSP_CONFIG;
153 g_Config=&SPConfig::getConfig();
154 g_Config->setFeatures(
158 SPConfig::RequestMapping |
159 SPConfig::InProcess |
162 if (!g_Config->init(schemadir)) {
164 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
165 "Filter startup failed during library initialization, check native log for help.");
170 DOMDocument* dummydoc=XMLToolingConfig::getConfig().getParser().newDocument();
171 XercesJanitor<DOMDocument> docjanitor(dummydoc);
172 DOMElement* dummy = dummydoc->createElementNS(NULL,path);
173 auto_ptr_XMLCh src(config);
174 dummy->setAttributeNS(NULL,path,src.get());
175 dummy->setAttributeNS(NULL,validate,xmlconstants::XML_ONE);
177 g_Config->setServiceProvider(g_Config->ServiceProviderManager.newPlugin(XML_SERVICE_PROVIDER,dummy));
178 g_Config->getServiceProvider()->init();
180 catch (exception& ex) {
183 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, ex.what());
184 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
185 "Filter startup failed to load configuration, check native log for details.");
189 // Access the implementation-specifics for site mappings.
190 ServiceProvider* sp=g_Config->getServiceProvider();
191 xmltooling::Locker locker(sp);
192 const PropertySet* props=sp->getPropertySet("Local");
194 const DOMElement* impl=XMLHelper::getFirstChildElement(props->getElement(),Implementation);
195 if (impl && (impl=XMLHelper::getFirstChildElement(impl,ISAPI))) {
196 const XMLCh* flag=impl->getAttributeNS(NULL,normalizeRequest);
197 g_bNormalizeRequest=(!flag || !*flag || *flag==chDigit_1 || *flag==chLatin_t);
198 impl=XMLHelper::getFirstChildElement(impl,Site);
200 auto_ptr_char id(impl->getAttributeNS(NULL,id));
202 g_Sites.insert(pair<string,site_t>(id.get(),site_t(impl)));
203 impl=XMLHelper::getNextSiblingElement(impl,Site);
208 pVer->dwFilterVersion=HTTP_FILTER_REVISION;
209 strncpy(pVer->lpszFilterDesc,"Shibboleth ISAPI Filter",SF_MAX_FILTER_DESC_LEN);
210 pVer->dwFlags=(SF_NOTIFY_ORDER_HIGH |
211 SF_NOTIFY_SECURE_PORT |
212 SF_NOTIFY_NONSECURE_PORT |
213 SF_NOTIFY_PREPROC_HEADERS |
215 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter initialized...");
219 extern "C" BOOL WINAPI TerminateFilter(DWORD)
224 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter shut down...");
228 /* Next up, some suck-free versions of various APIs.
230 You DON'T require people to guess the buffer size and THEN tell them the right size.
231 Returning an LPCSTR is apparently way beyond their ken. Not to mention the fact that
232 constant strings aren't typed as such, making it just that much harder. These versions
233 are now updated to use a special growable buffer object, modeled after the standard
234 string class. The standard string won't work because they left out the option to
235 pre-allocate a non-constant buffer.
241 dynabuf() { bufptr=NULL; buflen=0; }
242 dynabuf(size_t s) { bufptr=new char[buflen=s]; *bufptr=0; }
243 ~dynabuf() { delete[] bufptr; }
244 size_t length() const { return bufptr ? strlen(bufptr) : 0; }
245 size_t size() const { return buflen; }
246 bool empty() const { return length()==0; }
247 void reserve(size_t s, bool keep=false);
248 void erase() { if (bufptr) memset(bufptr,0,buflen); }
249 operator char*() { return bufptr; }
250 bool operator ==(const char* s) const;
251 bool operator !=(const char* s) const { return !(*this==s); }
257 void dynabuf::reserve(size_t s, bool keep)
264 p[buflen]=bufptr[buflen];
270 bool dynabuf::operator==(const char* s) const
272 if (buflen==NULL || s==NULL)
273 return (buflen==NULL && s==NULL);
275 return strcmp(bufptr,s)==0;
278 void GetServerVariable(PHTTP_FILTER_CONTEXT pfc, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
284 while (!pfc->GetServerVariable(pfc,lpszVariable,s,&size)) {
285 // Grumble. Check the error.
286 DWORD e=GetLastError();
287 if (e==ERROR_INSUFFICIENT_BUFFER)
292 if (bRequired && s.empty())
296 void GetServerVariable(LPEXTENSION_CONTROL_BLOCK lpECB, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
302 while (!lpECB->GetServerVariable(lpECB->ConnID,lpszVariable,s,&size)) {
303 // Grumble. Check the error.
304 DWORD e=GetLastError();
305 if (e==ERROR_INSUFFICIENT_BUFFER)
310 if (bRequired && s.empty())
314 void GetHeader(PHTTP_FILTER_PREPROC_HEADERS pn, PHTTP_FILTER_CONTEXT pfc,
315 LPSTR lpszName, dynabuf& s, DWORD size=80, bool bRequired=true)
321 while (!pn->GetHeader(pfc,lpszName,s,&size)) {
322 // Grumble. Check the error.
323 DWORD e=GetLastError();
324 if (e==ERROR_INSUFFICIENT_BUFFER)
329 if (bRequired && s.empty())
333 /****************************************************************************/
336 class ShibTargetIsapiF : public AbstractSPRequest
338 PHTTP_FILTER_CONTEXT m_pfc;
339 PHTTP_FILTER_PREPROC_HEADERS m_pn;
340 map<string,string> m_headers;
341 vector<XSECCryptoX509*> m_certs;
343 string m_scheme,m_hostname,m_uri;
344 mutable string m_remote_addr,m_content_type,m_method;
347 ShibTargetIsapiF(PHTTP_FILTER_CONTEXT pfc, PHTTP_FILTER_PREPROC_HEADERS pn, const site_t& site) {
352 // URL path always come from IIS.
354 GetHeader(pn,pfc,"url",var,256,false);
357 // Port may come from IIS or from site def.
358 if (!g_bNormalizeRequest || (pfc->fIsSecurePort && site.m_sslport.empty()) || (!pfc->fIsSecurePort && site.m_port.empty())) {
359 GetServerVariable(pfc,"SERVER_PORT",var,10);
362 else if (pfc->fIsSecurePort) {
363 m_port = atoi(site.m_sslport.c_str());
366 m_port = atoi(site.m_port.c_str());
369 // Scheme may come from site def or be derived from IIS.
370 m_scheme=site.m_scheme;
371 if (m_scheme.empty() || !g_bNormalizeRequest)
372 m_scheme=pfc->fIsSecurePort ? "https" : "http";
374 GetServerVariable(pfc,"SERVER_NAME",var,32);
376 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
378 if (site.m_name!=m_hostname && site.m_aliases.find(m_hostname)==site.m_aliases.end())
379 m_hostname=site.m_name;
381 ~ShibTargetIsapiF() { }
383 const char* getScheme() const {
384 return m_scheme.c_str();
386 const char* getHostname() const {
387 return m_hostname.c_str();
389 int getPort() const {
392 const char* getRequestURI() const {
393 return m_uri.c_str();
395 const char* getMethod() const {
396 if (m_method.empty()) {
398 GetServerVariable(m_pfc,"REQUEST_METHOD",var,5,false);
402 return m_method.c_str();
404 string getContentType() const {
405 if (m_content_type.empty()) {
407 GetServerVariable(m_pfc,"CONTENT_TYPE",var,32,false);
409 m_content_type = var;
411 return m_content_type;
413 long getContentLength() const {
416 string getRemoteAddr() const {
417 if (m_remote_addr.empty()) {
419 GetServerVariable(m_pfc,"REMOTE_ADDR",var,16,false);
423 return m_remote_addr;
425 void log(SPLogLevel level, const string& msg) {
426 AbstractSPRequest::log(level,msg);
427 if (level >= SPError)
428 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
430 void clearHeader(const char* name) {
431 string hdr(!strcmp(name,"REMOTE_USER") ? "remote-user" : name);
433 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), "");
435 void setHeader(const char* name, const char* value) {
438 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), const_cast<char*>(value));
440 string getHeader(const char* name) const {
444 GetHeader(m_pn, m_pfc, const_cast<char*>(hdr.c_str()), buf, 1024, false);
447 void setRemoteUser(const char* user) {
448 setHeader("remote-user", user);
450 string getRemoteUser() const {
451 return getHeader("remote-user");
453 void setResponseHeader(const char* name, const char* value) {
456 m_headers[name] = value;
458 m_headers.erase(name);
460 long sendResponse(istream& in, long status) {
461 string hdr = string("Connection: close\r\n");
462 for (map<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
463 hdr += i->first + ": " + i->second + "\r\n";
465 const char* codestr="200 OK";
467 case SAML_HTTP_STATUS_FORBIDDEN:codestr="403 Forbidden"; break;
468 case SAML_HTTP_STATUS_NOTFOUND: codestr="404 Not Found"; break;
469 case SAML_HTTP_STATUS_ERROR: codestr="500 Server Error"; break;
471 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, (void*)codestr, (DWORD)hdr.c_str(), 0);
475 DWORD resplen = in.gcount();
476 m_pfc->WriteClient(m_pfc, buf, &resplen, 0);
478 return SF_STATUS_REQ_FINISHED;
480 long sendRedirect(const char* url) {
481 // XXX: Don't support the httpRedirect option, yet.
482 string hdr=string("Location: ") + url + "\r\n"
483 "Content-Type: text/html\r\n"
484 "Content-Length: 40\r\n"
485 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
486 "Cache-Control: private,no-store,no-cache\r\n";
487 for (map<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
488 hdr += i->first + ": " + i->second + "\r\n";
490 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, "302 Please Wait", (DWORD)hdr.c_str(), 0);
491 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
493 m_pfc->WriteClient(m_pfc, (LPVOID)redmsg, &resplen, 0);
494 return SF_STATUS_REQ_FINISHED;
496 long returnDecline() {
497 return SF_STATUS_REQ_NEXT_NOTIFICATION;
500 return SF_STATUS_REQ_NEXT_NOTIFICATION;
503 const vector<XSECCryptoX509*>& getClientCertificates() const {
507 // The filter never processes the POST, so stub these methods.
508 const char* getQueryString() const { throw IOException("getQueryString not implemented"); }
509 const char* getRequestBody() const { throw IOException("getRequestBody not implemented"); }
512 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const char* msg)
514 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
515 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
516 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)ctype,0);
517 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Filter Error</TITLE></HEAD><BODY>"
518 "<H1>Shibboleth Filter Error</H1>";
519 DWORD resplen=strlen(xmsg);
520 pfc->WriteClient(pfc,(LPVOID)xmsg,&resplen,0);
522 pfc->WriteClient(pfc,(LPVOID)msg,&resplen,0);
523 static const char* xmsg2="</BODY></HTML>";
524 resplen=strlen(xmsg2);
525 pfc->WriteClient(pfc,(LPVOID)xmsg2,&resplen,0);
526 return SF_STATUS_REQ_FINISHED;
529 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
531 // Is this a log notification?
532 if (notificationType==SF_NOTIFY_LOG)
534 if (pfc->pFilterContext)
535 ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=static_cast<LPCSTR>(pfc->pFilterContext);
536 return SF_STATUS_REQ_NEXT_NOTIFICATION;
539 PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
542 // Determine web site number. This can't really fail, I don't think.
544 GetServerVariable(pfc,"INSTANCE_ID",buf,10);
546 // Match site instance to host name, skip if no match.
547 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
548 if (map_i==g_Sites.end())
549 return SF_STATUS_REQ_NEXT_NOTIFICATION;
551 ostringstream threadid;
552 threadid << "[" << getpid() << "] isapi_shib" << '\0';
553 xmltooling::NDC ndc(threadid.str().c_str());
555 ShibTargetIsapiF stf(pfc, pn, map_i->second);
557 // "false" because we don't override the Shib settings
558 pair<bool,long> res = stf.getServiceProvider().doAuthentication(stf);
559 if (res.first) return res.second;
561 // "false" because we don't override the Shib settings
562 res = stf.getServiceProvider().doExport(stf);
563 if (res.first) return res.second;
565 res = stf.getServiceProvider().doAuthorization(stf);
566 if (res.first) return res.second;
568 return SF_STATUS_REQ_NEXT_NOTIFICATION;
571 return WriteClientError(pfc,"Out of Memory");
574 if (e==ERROR_NO_DATA)
575 return WriteClientError(pfc,"A required variable or header was empty.");
577 return WriteClientError(pfc,"Shibboleth Filter detected unexpected IIS error.");
579 catch (exception& e) {
580 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
581 return WriteClientError(pfc,"Shibboleth Filter caught an exception, check Event Log for details.");
585 return WriteClientError(pfc,"Shibboleth Filter caught an unknown exception.");
589 return WriteClientError(pfc,"Shibboleth Filter reached unreachable code, save my walrus!");
593 /****************************************************************************/
596 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* msg)
598 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
599 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
600 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
601 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Error</TITLE></HEAD><BODY><H1>Shibboleth Error</H1>";
602 DWORD resplen=strlen(xmsg);
603 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg,&resplen,HSE_IO_SYNC);
605 lpECB->WriteClient(lpECB->ConnID,(LPVOID)msg,&resplen,HSE_IO_SYNC);
606 static const char* xmsg2="</BODY></HTML>";
607 resplen=strlen(xmsg2);
608 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg2,&resplen,HSE_IO_SYNC);
609 return HSE_STATUS_SUCCESS;
613 class ShibTargetIsapiE : public AbstractSPRequest
615 LPEXTENSION_CONTROL_BLOCK m_lpECB;
616 map<string,string> m_headers;
617 vector<XSECCryptoX509*> m_certs;
618 mutable string m_body;
619 mutable bool m_gotBody;
621 string m_scheme,m_hostname,m_uri;
622 mutable string m_remote_addr;
625 ShibTargetIsapiE(LPEXTENSION_CONTROL_BLOCK lpECB, const site_t& site) : m_lpECB(lpECB), m_gotBody(false) {
627 GetServerVariable(lpECB,"HTTPS",ssl,5);
628 bool SSL=(ssl=="on" || ssl=="ON");
630 // Scheme may come from site def or be derived from IIS.
631 m_scheme=site.m_scheme;
632 if (m_scheme.empty() || !g_bNormalizeRequest)
633 m_scheme = SSL ? "https" : "http";
635 // URL path always come from IIS.
637 GetServerVariable(lpECB,"URL",url,255);
639 // Port may come from IIS or from site def.
641 if (!g_bNormalizeRequest || (SSL && site.m_sslport.empty()) || (!SSL && site.m_port.empty()))
642 GetServerVariable(lpECB,"SERVER_PORT",port,10);
644 strncpy(port,site.m_sslport.c_str(),10);
645 static_cast<char*>(port)[10]=0;
648 strncpy(port,site.m_port.c_str(),10);
649 static_cast<char*>(port)[10]=0;
654 GetServerVariable(lpECB, "SERVER_NAME", var, 32);
656 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
658 if (site.m_name!=m_hostname && site.m_aliases.find(m_hostname)==site.m_aliases.end())
659 m_hostname=site.m_name;
662 * IIS screws us over on PATH_INFO (the hits keep on coming). We need to figure out if
663 * the server is set up for proper PATH_INFO handling, or "IIS sucks rabid weasels mode",
664 * which is the default. No perfect way to tell, but we can take a good guess by checking
665 * whether the URL is a substring of the PATH_INFO:
667 * e.g. for /Shibboleth.sso/SAML/POST
669 * Bad mode (default):
670 * URL: /Shibboleth.sso
671 * PathInfo: /Shibboleth.sso/SAML/POST
674 * URL: /Shibboleth.sso
675 * PathInfo: /SAML/POST
678 // Clearly we're only in bad mode if path info exists at all.
679 if (lpECB->lpszPathInfo && *(lpECB->lpszPathInfo)) {
680 if (strstr(lpECB->lpszPathInfo,url))
681 // Pretty good chance we're in bad mode, unless the PathInfo repeats the path itself.
682 m_uri = lpECB->lpszPathInfo;
685 m_uri += lpECB->lpszPathInfo;
689 // For consistency with Apache, let's add the query string.
690 if (lpECB->lpszQueryString && *(lpECB->lpszQueryString)) {
692 m_uri += lpECB->lpszQueryString;
695 ~ShibTargetIsapiE() { }
697 const char* getScheme() const {
698 return m_scheme.c_str();
700 const char* getHostname() const {
701 return m_hostname.c_str();
703 int getPort() const {
706 const char* getRequestURI() const {
707 return m_uri.c_str();
709 const char* getMethod() const {
710 return m_lpECB->lpszMethod;
712 string getContentType() const {
713 return m_lpECB->lpszContentType ? m_lpECB->lpszContentType : "";
715 long getContentLength() const {
716 return m_lpECB->cbTotalBytes;
718 string getRemoteAddr() const {
719 if (m_remote_addr.empty()) {
721 GetServerVariable(m_lpECB, "REMOTE_ADDR", var, 16, false);
725 return m_remote_addr;
727 void log(SPLogLevel level, const string& msg) {
728 AbstractSPRequest::log(level,msg);
729 if (level >= SPError)
730 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
732 string getHeader(const char* name) const {
734 for (; *name; ++name) {
738 hdr += toupper(*name);
741 GetServerVariable(m_lpECB, const_cast<char*>(hdr.c_str()), buf, 128, false);
742 return buf.empty() ? "" : buf;
744 void setResponseHeader(const char* name, const char* value) {
747 m_headers[name] = value;
749 m_headers.erase(name);
751 const char* getQueryString() const {
752 return m_lpECB->lpszQueryString;
754 const char* getRequestBody() const {
756 return m_body.c_str();
757 if (m_lpECB->cbTotalBytes > 1024*1024) // 1MB?
758 throw opensaml::SecurityPolicyException("Size of request body exceeded 1M size limit.");
759 else if (m_lpECB->cbTotalBytes > m_lpECB->cbAvailable) {
762 DWORD datalen=m_lpECB->cbTotalBytes;
765 BOOL ret = m_lpECB->ReadClient(m_lpECB->ConnID, buf, &buflen);
767 throw IOException("Error reading request body from browser.");
768 m_body.append(buf, buflen);
772 else if (m_lpECB->cbAvailable) {
774 m_body.assign(reinterpret_cast<char*>(m_lpECB->lpbData),m_lpECB->cbAvailable);
776 return m_body.c_str();
778 long sendResponse(istream& in, long status) {
779 string hdr = string("Connection: close\r\n");
780 for (map<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
781 hdr += i->first + ": " + i->second + "\r\n";
783 const char* codestr="200 OK";
785 case SAML_HTTP_STATUS_FORBIDDEN:codestr="403 Forbidden"; break;
786 case SAML_HTTP_STATUS_NOTFOUND: codestr="404 Not Found"; break;
787 case SAML_HTTP_STATUS_ERROR: codestr="500 Server Error"; break;
789 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, (void*)codestr, 0, (LPDWORD)hdr.c_str());
793 DWORD resplen = in.gcount();
794 m_lpECB->WriteClient(m_lpECB->ConnID, buf, &resplen, HSE_IO_SYNC);
796 return HSE_STATUS_SUCCESS;
798 long sendRedirect(const char* url) {
799 string hdr=string("Location: ") + url + "\r\n"
800 "Content-Type: text/html\r\n"
801 "Content-Length: 40\r\n"
802 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
803 "Cache-Control: private,no-store,no-cache\r\n";
804 for (map<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
805 hdr += i->first + ": " + i->second + "\r\n";
807 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, "302 Moved", 0, (LPDWORD)hdr.c_str());
808 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
810 m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)redmsg, &resplen, HSE_IO_SYNC);
811 return HSE_STATUS_SUCCESS;
813 // Decline happens in the POST processor if this isn't the shire url
814 // Note that it can also happen with HTAccess, but we don't support that, yet.
815 long returnDecline() {
816 return WriteClientError(
818 "ISAPI extension can only be invoked to process Shibboleth protocol requests."
819 "Make sure the mapped file extension doesn't match actual content."
823 return HSE_STATUS_SUCCESS;
826 const vector<XSECCryptoX509*>& getClientCertificates() const {
830 // Not used in the extension.
831 virtual void clearHeader(const char* name) { throw runtime_error("clearHeader not implemented"); }
832 virtual void setHeader(const char* name, const char* value) { throw runtime_error("setHeader not implemented"); }
833 virtual void setRemoteUser(const char* user) { throw runtime_error("setRemoteUser not implemented"); }
834 virtual string getRemoteUser() const { throw runtime_error("getRemoteUser not implemented"); }
837 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
840 ostringstream threadid;
841 threadid << "[" << getpid() << "] isapi_shib_extension" << '\0';
842 xmltooling::NDC ndc(threadid.str().c_str());
844 // Determine web site number. This can't really fail, I don't think.
846 GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
848 // Match site instance to host name, skip if no match.
849 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
850 if (map_i==g_Sites.end())
851 return WriteClientError(lpECB, "Shibboleth Extension not configured for this web site.");
853 ShibTargetIsapiE ste(lpECB, map_i->second);
854 pair<bool,long> res = ste.getServiceProvider().doHandler(ste);
855 if (res.first) return res.second;
857 return WriteClientError(lpECB, "Shibboleth Extension failed to process request");
861 return WriteClientError(lpECB,"Out of Memory");
864 if (e==ERROR_NO_DATA)
865 return WriteClientError(lpECB,"A required variable or header was empty.");
867 return WriteClientError(lpECB,"Server detected unexpected IIS error.");
869 catch (exception& e) {
870 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
871 return WriteClientError(lpECB,"Shibboleth Extension caught an exception, check Event Log for details.");
875 return WriteClientError(lpECB,"Shibboleth Extension caught an unknown exception.");
879 // If we get here we've got an error.
880 return HSE_STATUS_ERROR;