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>
50 using namespace shibsp;
51 using namespace xmltooling;
52 using namespace xercesc;
57 static const XMLCh path[] = UNICODE_LITERAL_4(p,a,t,h);
58 static const XMLCh validate[] = UNICODE_LITERAL_8(v,a,l,i,d,a,t,e);
59 static const XMLCh name[] = UNICODE_LITERAL_4(n,a,m,e);
60 static const XMLCh port[] = UNICODE_LITERAL_4(p,o,r,t);
61 static const XMLCh sslport[] = UNICODE_LITERAL_7(s,s,l,p,o,r,t);
62 static const XMLCh scheme[] = UNICODE_LITERAL_6(s,c,h,e,m,e);
63 static const XMLCh id[] = UNICODE_LITERAL_2(i,d);
64 static const XMLCh Alias[] = UNICODE_LITERAL_5(A,l,i,a,s);
65 static const XMLCh Site[] = UNICODE_LITERAL_4(S,i,t,e);
68 site_t(const DOMElement* e)
70 auto_ptr_char n(e->getAttributeNS(NULL,name));
71 auto_ptr_char s(e->getAttributeNS(NULL,scheme));
72 auto_ptr_char p(e->getAttributeNS(NULL,port));
73 auto_ptr_char p2(e->getAttributeNS(NULL,sslport));
74 if (n.get()) m_name=n.get();
75 if (s.get()) m_scheme=s.get();
76 if (p.get()) m_port=p.get();
77 if (p2.get()) m_sslport=p2.get();
78 e = XMLHelper::getFirstChildElement(e, Alias);
80 if (e->hasChildNodes()) {
81 auto_ptr_char alias(e->getFirstChild()->getNodeValue());
82 m_aliases.insert(alias.get());
84 e = XMLHelper::getNextSiblingElement(e, Alias);
87 string m_scheme,m_port,m_sslport,m_name;
88 set<string> m_aliases;
97 SPConfig* g_Config = NULL;
98 map<string,site_t> g_Sites;
99 bool g_bNormalizeRequest = true;
100 string g_unsetHeaderValue;
101 bool g_checkSpoofing = true;
102 bool g_catchAll = false;
103 vector<string> g_NoCerts;
107 LPCSTR lpUNCServerName,
113 LPCSTR messages[] = {message, NULL};
115 HANDLE hElog = RegisterEventSource(lpUNCServerName, "Shibboleth ISAPI Filter");
116 BOOL res = ReportEvent(hElog, wType, 0, dwEventID, lpUserSid, 1, 0, messages, NULL);
117 return (DeregisterEventSource(hElog) && res);
120 extern "C" __declspec(dllexport) BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID)
122 if (fdwReason==DLL_PROCESS_ATTACH)
127 extern "C" BOOL WINAPI GetExtensionVersion(HSE_VERSION_INFO* pVer)
133 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
134 "Extension mode startup not possible, is the DLL loaded as a filter?");
138 pVer->dwExtensionVersion=HSE_VERSION;
139 strncpy(pVer->lpszExtensionDesc,"Shibboleth ISAPI Extension",HSE_MAX_EXT_DLL_NAME_LEN-1);
143 extern "C" BOOL WINAPI TerminateExtension(DWORD)
145 return TRUE; // cleanup should happen when filter unloads
148 extern "C" BOOL WINAPI GetFilterVersion(PHTTP_FILTER_VERSION pVer)
153 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
154 "Reentrant filter initialization, ignoring...");
158 g_Config=&SPConfig::getConfig();
159 g_Config->setFeatures(
162 SPConfig::RequestMapping |
163 SPConfig::InProcess |
167 if (!g_Config->init()) {
169 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
170 "Filter startup failed during library initialization, check native log for help.");
175 if (!g_Config->instantiate(NULL, true))
176 throw runtime_error("unknown error");
178 catch (exception& ex) {
181 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, ex.what());
182 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
183 "Filter startup failed to load configuration, check native log for details.");
187 // Access implementation-specifics and site mappings.
188 ServiceProvider* sp=g_Config->getServiceProvider();
190 const PropertySet* props=sp->getPropertySet("InProcess");
192 pair<bool,const char*> unsetValue=props->getString("unsetHeaderValue");
193 if (unsetValue.first)
194 g_unsetHeaderValue = unsetValue.second;
195 pair<bool,bool> flag=props->getBool("checkSpoofing");
196 g_checkSpoofing = !flag.first || flag.second;
197 flag=props->getBool("catchAll");
198 g_catchAll = flag.first && flag.second;
200 props = props->getPropertySet("ISAPI");
202 flag = props->getBool("normalizeRequest");
203 g_bNormalizeRequest = !flag.first || flag.second;
204 const DOMElement* child = XMLHelper::getFirstChildElement(props->getElement(),Site);
206 auto_ptr_char id(child->getAttributeNS(NULL,id));
208 g_Sites.insert(pair<string,site_t>(id.get(),site_t(child)));
209 child=XMLHelper::getNextSiblingElement(child,Site);
214 pVer->dwFilterVersion=HTTP_FILTER_REVISION;
215 strncpy(pVer->lpszFilterDesc,"Shibboleth ISAPI Filter",SF_MAX_FILTER_DESC_LEN);
216 pVer->dwFlags=(SF_NOTIFY_ORDER_HIGH |
217 SF_NOTIFY_SECURE_PORT |
218 SF_NOTIFY_NONSECURE_PORT |
219 SF_NOTIFY_PREPROC_HEADERS |
221 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter initialized...");
225 extern "C" BOOL WINAPI TerminateFilter(DWORD)
230 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter shut down...");
234 /* Next up, some suck-free versions of various APIs.
236 You DON'T require people to guess the buffer size and THEN tell them the right size.
237 Returning an LPCSTR is apparently way beyond their ken. Not to mention the fact that
238 constant strings aren't typed as such, making it just that much harder. These versions
239 are now updated to use a special growable buffer object, modeled after the standard
240 string class. The standard string won't work because they left out the option to
241 pre-allocate a non-constant buffer.
247 dynabuf() { bufptr=NULL; buflen=0; }
248 dynabuf(size_t s) { bufptr=new char[buflen=s]; *bufptr=0; }
249 ~dynabuf() { delete[] bufptr; }
250 size_t length() const { return bufptr ? strlen(bufptr) : 0; }
251 size_t size() const { return buflen; }
252 bool empty() const { return length()==0; }
253 void reserve(size_t s, bool keep=false);
254 void erase() { if (bufptr) memset(bufptr,0,buflen); }
255 operator char*() { return bufptr; }
256 bool operator ==(const char* s) const;
257 bool operator !=(const char* s) const { return !(*this==s); }
263 void dynabuf::reserve(size_t s, bool keep)
270 p[buflen]=bufptr[buflen];
276 bool dynabuf::operator==(const char* s) const
278 if (buflen==NULL || s==NULL)
279 return (buflen==NULL && s==NULL);
281 return strcmp(bufptr,s)==0;
284 void GetServerVariable(PHTTP_FILTER_CONTEXT pfc, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
290 while (!pfc->GetServerVariable(pfc,lpszVariable,s,&size)) {
291 // Grumble. Check the error.
292 DWORD e=GetLastError();
293 if (e==ERROR_INSUFFICIENT_BUFFER)
298 if (bRequired && s.empty())
302 void GetServerVariable(LPEXTENSION_CONTROL_BLOCK lpECB, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
308 while (!lpECB->GetServerVariable(lpECB->ConnID,lpszVariable,s,&size)) {
309 // Grumble. Check the error.
310 DWORD e=GetLastError();
311 if (e==ERROR_INSUFFICIENT_BUFFER)
316 if (bRequired && s.empty())
320 void GetHeader(PHTTP_FILTER_PREPROC_HEADERS pn, PHTTP_FILTER_CONTEXT pfc,
321 LPSTR lpszName, dynabuf& s, DWORD size=80, bool bRequired=true)
327 while (!pn->GetHeader(pfc,lpszName,s,&size)) {
328 // Grumble. Check the error.
329 DWORD e=GetLastError();
330 if (e==ERROR_INSUFFICIENT_BUFFER)
335 if (bRequired && s.empty())
339 /****************************************************************************/
342 class ShibTargetIsapiF : public AbstractSPRequest
344 PHTTP_FILTER_CONTEXT m_pfc;
345 PHTTP_FILTER_PREPROC_HEADERS m_pn;
346 multimap<string,string> m_headers;
348 string m_scheme,m_hostname;
349 mutable string m_remote_addr,m_content_type,m_method;
353 ShibTargetIsapiF(PHTTP_FILTER_CONTEXT pfc, PHTTP_FILTER_PREPROC_HEADERS pn, const site_t& site)
354 : AbstractSPRequest(SHIBSP_LOGCAT".ISAPI"), m_pfc(pfc), m_pn(pn), m_allhttp(4096) {
356 // URL path always come from IIS.
358 GetHeader(pn,pfc,"url",var,256,false);
361 // Port may come from IIS or from site def.
362 if (!g_bNormalizeRequest || (pfc->fIsSecurePort && site.m_sslport.empty()) || (!pfc->fIsSecurePort && site.m_port.empty())) {
363 GetServerVariable(pfc,"SERVER_PORT",var,10);
366 else if (pfc->fIsSecurePort) {
367 m_port = atoi(site.m_sslport.c_str());
370 m_port = atoi(site.m_port.c_str());
373 // Scheme may come from site def or be derived from IIS.
374 m_scheme=site.m_scheme;
375 if (m_scheme.empty() || !g_bNormalizeRequest)
376 m_scheme=pfc->fIsSecurePort ? "https" : "http";
378 GetServerVariable(pfc,"SERVER_NAME",var,32);
380 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
382 if (site.m_name!=m_hostname && site.m_aliases.find(m_hostname)==site.m_aliases.end())
383 m_hostname=site.m_name;
385 if (!pfc->pFilterContext) {
386 pfc->pFilterContext = pfc->AllocMem(pfc, sizeof(context_t), NULL);
387 if (static_cast<context_t*>(pfc->pFilterContext)) {
388 static_cast<context_t*>(pfc->pFilterContext)->m_user = NULL;
389 static_cast<context_t*>(pfc->pFilterContext)->m_checked = false;
393 ~ShibTargetIsapiF() { }
395 const char* getScheme() const {
396 return m_scheme.c_str();
398 const char* getHostname() const {
399 return m_hostname.c_str();
401 int getPort() const {
404 const char* getMethod() const {
405 if (m_method.empty()) {
407 GetServerVariable(m_pfc,"REQUEST_METHOD",var,5,false);
411 return m_method.c_str();
413 string getContentType() const {
414 if (m_content_type.empty()) {
416 GetServerVariable(m_pfc,"CONTENT_TYPE",var,32,false);
418 m_content_type = var;
420 return m_content_type;
422 long getContentLength() const {
425 string getRemoteAddr() const {
426 if (m_remote_addr.empty()) {
428 GetServerVariable(m_pfc,"REMOTE_ADDR",var,16,false);
432 return m_remote_addr;
434 void log(SPLogLevel level, const string& msg) {
435 AbstractSPRequest::log(level,msg);
436 if (level >= SPError)
437 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
439 void clearHeader(const char* rawname, const char* cginame) {
440 if (g_checkSpoofing && m_pfc->pFilterContext && !static_cast<context_t*>(m_pfc->pFilterContext)->m_checked) {
441 if (m_allhttp.empty())
442 GetServerVariable(m_pfc,"ALL_HTTP",m_allhttp,4096);
443 if (strstr(m_allhttp, cginame))
444 throw opensaml::SecurityPolicyException("Attempt to spoof header ($1) was detected.", params(1, rawname));
446 string hdr(!strcmp(rawname,"REMOTE_USER") ? "remote-user" : rawname);
448 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), const_cast<char*>(g_unsetHeaderValue.c_str()));
450 void setHeader(const char* name, const char* value) {
453 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), const_cast<char*>(value));
455 string getHeader(const char* name) const {
459 GetHeader(m_pn, m_pfc, const_cast<char*>(hdr.c_str()), buf, 256, false);
462 void setRemoteUser(const char* user) {
463 setHeader("remote-user", user);
464 if (m_pfc->pFilterContext) {
466 static_cast<context_t*>(m_pfc->pFilterContext)->m_user = NULL;
467 else if (static_cast<context_t*>(m_pfc->pFilterContext)->m_user = (char*)m_pfc->AllocMem(m_pfc, sizeof(char) * (strlen(user) + 1), NULL))
468 strcpy(static_cast<context_t*>(m_pfc->pFilterContext)->m_user, user);
471 string getRemoteUser() const {
472 return getHeader("remote-user");
474 void setResponseHeader(const char* name, const char* value) {
477 m_headers.insert(make_pair(name,value));
479 m_headers.erase(name);
481 long sendResponse(istream& in, long status) {
482 string hdr = string("Connection: close\r\n");
483 for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
484 hdr += i->first + ": " + i->second + "\r\n";
486 const char* codestr="200 OK";
488 case XMLTOOLING_HTTP_STATUS_UNAUTHORIZED: codestr="401 Authorization Required"; break;
489 case XMLTOOLING_HTTP_STATUS_FORBIDDEN: codestr="403 Forbidden"; break;
490 case XMLTOOLING_HTTP_STATUS_NOTFOUND: codestr="404 Not Found"; break;
491 case XMLTOOLING_HTTP_STATUS_ERROR: codestr="500 Server Error"; break;
493 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, (void*)codestr, (DWORD)hdr.c_str(), 0);
497 DWORD resplen = in.gcount();
498 m_pfc->WriteClient(m_pfc, buf, &resplen, 0);
500 return SF_STATUS_REQ_FINISHED;
502 long sendRedirect(const char* url) {
503 // XXX: Don't support the httpRedirect option, yet.
504 string hdr=string("Location: ") + url + "\r\n"
505 "Content-Type: text/html\r\n"
506 "Content-Length: 40\r\n"
507 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
508 "Cache-Control: private,no-store,no-cache\r\n";
509 for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
510 hdr += i->first + ": " + i->second + "\r\n";
512 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, "302 Please Wait", (DWORD)hdr.c_str(), 0);
513 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
515 m_pfc->WriteClient(m_pfc, (LPVOID)redmsg, &resplen, 0);
516 return SF_STATUS_REQ_FINISHED;
518 long returnDecline() {
519 return SF_STATUS_REQ_NEXT_NOTIFICATION;
522 return SF_STATUS_REQ_NEXT_NOTIFICATION;
525 const vector<string>& getClientCertificates() const {
529 // The filter never processes the POST, so stub these methods.
530 const char* getQueryString() const { throw IOException("getQueryString not implemented"); }
531 const char* getRequestBody() const { throw IOException("getRequestBody not implemented"); }
534 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const char* msg)
536 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
537 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
538 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)ctype,0);
539 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Filter Error</TITLE></HEAD><BODY>"
540 "<H1>Shibboleth Filter Error</H1>";
541 DWORD resplen=strlen(xmsg);
542 pfc->WriteClient(pfc,(LPVOID)xmsg,&resplen,0);
544 pfc->WriteClient(pfc,(LPVOID)msg,&resplen,0);
545 static const char* xmsg2="</BODY></HTML>";
546 resplen=strlen(xmsg2);
547 pfc->WriteClient(pfc,(LPVOID)xmsg2,&resplen,0);
548 return SF_STATUS_REQ_FINISHED;
551 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
553 // Is this a log notification?
554 if (notificationType==SF_NOTIFY_LOG) {
555 if (pfc->pFilterContext && static_cast<context_t*>(pfc->pFilterContext)->m_user)
556 ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=static_cast<context_t*>(pfc->pFilterContext)->m_user;
557 return SF_STATUS_REQ_NEXT_NOTIFICATION;
560 PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
563 // Determine web site number. This can't really fail, I don't think.
565 GetServerVariable(pfc,"INSTANCE_ID",buf,10);
567 // Match site instance to host name, skip if no match.
568 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
569 if (map_i==g_Sites.end())
570 return SF_STATUS_REQ_NEXT_NOTIFICATION;
572 ostringstream threadid;
573 threadid << "[" << getpid() << "] isapi_shib" << '\0';
574 xmltooling::NDC ndc(threadid.str().c_str());
576 ShibTargetIsapiF stf(pfc, pn, map_i->second);
578 // "false" because we don't override the Shib settings
579 pair<bool,long> res = stf.getServiceProvider().doAuthentication(stf);
580 if (pfc->pFilterContext)
581 static_cast<context_t*>(pfc->pFilterContext)->m_checked = true;
582 if (res.first) return res.second;
584 // "false" because we don't override the Shib settings
585 res = stf.getServiceProvider().doExport(stf);
586 if (res.first) return res.second;
588 res = stf.getServiceProvider().doAuthorization(stf);
589 if (res.first) return res.second;
591 return SF_STATUS_REQ_NEXT_NOTIFICATION;
594 return WriteClientError(pfc,"Out of Memory");
597 if (e==ERROR_NO_DATA)
598 return WriteClientError(pfc,"A required variable or header was empty.");
600 return WriteClientError(pfc,"Shibboleth Filter detected unexpected IIS error.");
602 catch (exception& e) {
603 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
604 return WriteClientError(pfc,"Shibboleth Filter caught an exception, check Event Log for details.");
607 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Shibboleth Filter threw an unknown exception.");
609 return WriteClientError(pfc,"Shibboleth Filter threw an unknown exception.");
613 return WriteClientError(pfc,"Shibboleth Filter reached unreachable code, save my walrus!");
617 /****************************************************************************/
620 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* msg)
622 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
623 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
624 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
625 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Error</TITLE></HEAD><BODY><H1>Shibboleth Error</H1>";
626 DWORD resplen=strlen(xmsg);
627 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg,&resplen,HSE_IO_SYNC);
629 lpECB->WriteClient(lpECB->ConnID,(LPVOID)msg,&resplen,HSE_IO_SYNC);
630 static const char* xmsg2="</BODY></HTML>";
631 resplen=strlen(xmsg2);
632 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg2,&resplen,HSE_IO_SYNC);
633 return HSE_STATUS_SUCCESS;
637 class ShibTargetIsapiE : public AbstractSPRequest
639 LPEXTENSION_CONTROL_BLOCK m_lpECB;
640 multimap<string,string> m_headers;
641 mutable vector<string> m_certs;
642 mutable string m_body;
643 mutable bool m_gotBody;
645 string m_scheme,m_hostname,m_uri;
646 mutable string m_remote_addr,m_remote_user;
649 ShibTargetIsapiE(LPEXTENSION_CONTROL_BLOCK lpECB, const site_t& site)
650 : AbstractSPRequest(SHIBSP_LOGCAT".ISAPI"), m_lpECB(lpECB), m_gotBody(false) {
652 GetServerVariable(lpECB,"HTTPS",ssl,5);
653 bool SSL=(ssl=="on" || ssl=="ON");
655 // Scheme may come from site def or be derived from IIS.
656 m_scheme=site.m_scheme;
657 if (m_scheme.empty() || !g_bNormalizeRequest)
658 m_scheme = SSL ? "https" : "http";
660 // URL path always come from IIS.
662 GetServerVariable(lpECB,"URL",url,255);
664 // Port may come from IIS or from site def.
666 if (!g_bNormalizeRequest || (SSL && site.m_sslport.empty()) || (!SSL && site.m_port.empty()))
667 GetServerVariable(lpECB,"SERVER_PORT",port,10);
669 strncpy(port,site.m_sslport.c_str(),10);
670 static_cast<char*>(port)[10]=0;
673 strncpy(port,site.m_port.c_str(),10);
674 static_cast<char*>(port)[10]=0;
679 GetServerVariable(lpECB, "SERVER_NAME", var, 32);
681 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
683 if (site.m_name!=m_hostname && site.m_aliases.find(m_hostname)==site.m_aliases.end())
684 m_hostname=site.m_name;
687 * IIS screws us over on PATH_INFO (the hits keep on coming). We need to figure out if
688 * the server is set up for proper PATH_INFO handling, or "IIS sucks rabid weasels mode",
689 * which is the default. No perfect way to tell, but we can take a good guess by checking
690 * whether the URL is a substring of the PATH_INFO:
692 * e.g. for /Shibboleth.sso/SAML/POST
694 * Bad mode (default):
695 * URL: /Shibboleth.sso
696 * PathInfo: /Shibboleth.sso/SAML/POST
699 * URL: /Shibboleth.sso
700 * PathInfo: /SAML/POST
705 // Clearly we're only in bad mode if path info exists at all.
706 if (lpECB->lpszPathInfo && *(lpECB->lpszPathInfo)) {
707 if (strstr(lpECB->lpszPathInfo,url))
708 // Pretty good chance we're in bad mode, unless the PathInfo repeats the path itself.
709 uri = lpECB->lpszPathInfo;
712 uri += lpECB->lpszPathInfo;
719 // For consistency with Apache, let's add the query string.
720 if (lpECB->lpszQueryString && *(lpECB->lpszQueryString)) {
722 uri += lpECB->lpszQueryString;
725 setRequestURI(uri.c_str());
727 ~ShibTargetIsapiE() { }
729 const char* getScheme() const {
730 return m_scheme.c_str();
732 const char* getHostname() const {
733 return m_hostname.c_str();
735 int getPort() const {
738 const char* getMethod() const {
739 return m_lpECB->lpszMethod;
741 string getContentType() const {
742 return m_lpECB->lpszContentType ? m_lpECB->lpszContentType : "";
744 long getContentLength() const {
745 return m_lpECB->cbTotalBytes;
747 string getRemoteUser() const {
748 if (m_remote_user.empty()) {
750 GetServerVariable(m_lpECB, "REMOTE_USER", var, 32, false);
754 return m_remote_user;
756 string getRemoteAddr() const {
757 if (m_remote_addr.empty()) {
759 GetServerVariable(m_lpECB, "REMOTE_ADDR", var, 16, false);
763 return m_remote_addr;
765 void log(SPLogLevel level, const string& msg) const {
766 AbstractSPRequest::log(level,msg);
767 if (level >= SPError)
768 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
770 string getHeader(const char* name) const {
772 for (; *name; ++name) {
776 hdr += toupper(*name);
779 GetServerVariable(m_lpECB, const_cast<char*>(hdr.c_str()), buf, 128, false);
780 return buf.empty() ? "" : buf;
782 void setResponseHeader(const char* name, const char* value) {
785 m_headers.insert(make_pair(name,value));
787 m_headers.erase(name);
789 const char* getQueryString() const {
790 return m_lpECB->lpszQueryString;
792 const char* getRequestBody() const {
794 return m_body.c_str();
795 if (m_lpECB->cbTotalBytes > 1024*1024) // 1MB?
796 throw opensaml::SecurityPolicyException("Size of request body exceeded 1M size limit.");
797 else if (m_lpECB->cbTotalBytes > m_lpECB->cbAvailable) {
800 DWORD datalen=m_lpECB->cbTotalBytes;
803 BOOL ret = m_lpECB->ReadClient(m_lpECB->ConnID, buf, &buflen);
805 throw IOException("Error reading request body from browser.");
806 m_body.append(buf, buflen);
810 else if (m_lpECB->cbAvailable) {
812 m_body.assign(reinterpret_cast<char*>(m_lpECB->lpbData),m_lpECB->cbAvailable);
814 return m_body.c_str();
816 long sendResponse(istream& in, long status) {
817 string hdr = string("Connection: close\r\n");
818 for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
819 hdr += i->first + ": " + i->second + "\r\n";
821 const char* codestr="200 OK";
823 case XMLTOOLING_HTTP_STATUS_UNAUTHORIZED: codestr="401 Authorization Required"; break;
824 case XMLTOOLING_HTTP_STATUS_FORBIDDEN: codestr="403 Forbidden"; break;
825 case XMLTOOLING_HTTP_STATUS_NOTFOUND: codestr="404 Not Found"; break;
826 case XMLTOOLING_HTTP_STATUS_ERROR: codestr="500 Server Error"; break;
828 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, (void*)codestr, 0, (LPDWORD)hdr.c_str());
832 DWORD resplen = in.gcount();
833 m_lpECB->WriteClient(m_lpECB->ConnID, buf, &resplen, HSE_IO_SYNC);
835 return HSE_STATUS_SUCCESS;
837 long sendRedirect(const char* url) {
838 string hdr=string("Location: ") + url + "\r\n"
839 "Content-Type: text/html\r\n"
840 "Content-Length: 40\r\n"
841 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
842 "Cache-Control: private,no-store,no-cache\r\n";
843 for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
844 hdr += i->first + ": " + i->second + "\r\n";
846 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, "302 Moved", 0, (LPDWORD)hdr.c_str());
847 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
849 m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)redmsg, &resplen, HSE_IO_SYNC);
850 return HSE_STATUS_SUCCESS;
852 // Decline happens in the POST processor if this isn't the shire url
853 // Note that it can also happen with HTAccess, but we don't support that, yet.
854 long returnDecline() {
855 return WriteClientError(
857 "ISAPI extension can only be invoked to process Shibboleth protocol requests."
858 "Make sure the mapped file extension doesn't match actual content."
862 return HSE_STATUS_SUCCESS;
865 const vector<string>& getClientCertificates() const {
866 if (m_certs.empty()) {
867 char CertificateBuf[8192];
868 CERT_CONTEXT_EX ccex;
869 ccex.cbAllocated = sizeof(CertificateBuf);
870 ccex.CertContext.pbCertEncoded = (BYTE*)CertificateBuf;
871 DWORD dwSize = sizeof(ccex);
873 if (m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_GET_CERT_INFO_EX, (LPVOID)&ccex, (LPDWORD)dwSize, NULL)) {
874 if (ccex.CertContext.cbCertEncoded) {
876 XMLByte* serialized = Base64::encode(reinterpret_cast<XMLByte*>(CertificateBuf), ccex.CertContext.cbCertEncoded, &outlen);
877 m_certs.push_back(reinterpret_cast<char*>(serialized));
878 XMLString::release(&serialized);
885 // Not used in the extension.
886 void clearHeader(const char* rawname, const char* cginame) { throw runtime_error("clearHeader not implemented"); }
887 void setHeader(const char* name, const char* value) { throw runtime_error("setHeader not implemented"); }
888 void setRemoteUser(const char* user) { throw runtime_error("setRemoteUser not implemented"); }
891 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
894 ostringstream threadid;
895 threadid << "[" << getpid() << "] isapi_shib_extension" << '\0';
896 xmltooling::NDC ndc(threadid.str().c_str());
898 // Determine web site number. This can't really fail, I don't think.
900 GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
902 // Match site instance to host name, skip if no match.
903 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
904 if (map_i==g_Sites.end())
905 return WriteClientError(lpECB, "Shibboleth Extension not configured for web site (check <ISAPI> mappings in configuration).");
907 ShibTargetIsapiE ste(lpECB, map_i->second);
908 pair<bool,long> res = ste.getServiceProvider().doHandler(ste);
909 if (res.first) return res.second;
911 return WriteClientError(lpECB, "Shibboleth Extension failed to process request");
915 return WriteClientError(lpECB,"Out of Memory");
918 if (e==ERROR_NO_DATA)
919 return WriteClientError(lpECB,"A required variable or header was empty.");
921 return WriteClientError(lpECB,"Server detected unexpected IIS error.");
923 catch (exception& e) {
924 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
925 return WriteClientError(lpECB,"Shibboleth Extension caught an exception, check Event Log for details.");
928 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Shibboleth Extension threw an unknown exception.");
930 return WriteClientError(lpECB,"Shibboleth Extension threw an unknown exception.");
934 // If we get here we've got an error.
935 return HSE_STATUS_ERROR;