2 * Copyright 2001-2009 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
30 #include <shibsp/exceptions.h>
31 #include <shibsp/AbstractSPRequest.h>
32 #include <shibsp/SPConfig.h>
33 #include <shibsp/ServiceProvider.h>
40 #include <xmltooling/unicode.h>
41 #include <xmltooling/XMLToolingConfig.h>
42 #include <xmltooling/util/NDC.h>
43 #include <xmltooling/util/XMLConstants.h>
44 #include <xmltooling/util/XMLHelper.h>
45 #include <xercesc/util/Base64.hpp>
46 #include <xercesc/util/XMLUniDefs.hpp>
52 using namespace shibsp;
53 using namespace xmltooling;
54 using namespace xercesc;
59 static const XMLCh path[] = UNICODE_LITERAL_4(p,a,t,h);
60 static const XMLCh validate[] = UNICODE_LITERAL_8(v,a,l,i,d,a,t,e);
61 static const XMLCh name[] = UNICODE_LITERAL_4(n,a,m,e);
62 static const XMLCh port[] = UNICODE_LITERAL_4(p,o,r,t);
63 static const XMLCh sslport[] = UNICODE_LITERAL_7(s,s,l,p,o,r,t);
64 static const XMLCh scheme[] = UNICODE_LITERAL_6(s,c,h,e,m,e);
65 static const XMLCh id[] = UNICODE_LITERAL_2(i,d);
66 static const XMLCh Alias[] = UNICODE_LITERAL_5(A,l,i,a,s);
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 string g_unsetHeaderValue,g_spoofKey;
98 bool g_checkSpoofing = true;
99 bool g_catchAll = false;
100 bool g_bSafeHeaderNames = false;
101 vector<string> g_NoCerts;
105 LPCSTR lpUNCServerName,
111 LPCSTR messages[] = {message, NULL};
113 HANDLE hElog = RegisterEventSource(lpUNCServerName, "Shibboleth ISAPI Filter");
114 BOOL res = ReportEvent(hElog, wType, 0, dwEventID, lpUserSid, 1, 0, messages, NULL);
115 return (DeregisterEventSource(hElog) && res);
118 void _my_invalid_parameter_handler(
119 const wchar_t * expression,
120 const wchar_t * function,
121 const wchar_t * file,
129 extern "C" __declspec(dllexport) BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID)
131 if (fdwReason==DLL_PROCESS_ATTACH)
136 extern "C" BOOL WINAPI GetExtensionVersion(HSE_VERSION_INFO* pVer)
142 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
143 "Extension mode startup not possible, is the DLL loaded as a filter?");
147 pVer->dwExtensionVersion=HSE_VERSION;
148 strncpy(pVer->lpszExtensionDesc,"Shibboleth ISAPI Extension",HSE_MAX_EXT_DLL_NAME_LEN-1);
152 extern "C" BOOL WINAPI TerminateExtension(DWORD)
154 return TRUE; // cleanup should happen when filter unloads
157 extern "C" BOOL WINAPI GetFilterVersion(PHTTP_FILTER_VERSION pVer)
162 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
163 "Reentrant filter initialization, ignoring...");
167 g_Config=&SPConfig::getConfig();
168 g_Config->setFeatures(
171 SPConfig::RequestMapping |
172 SPConfig::InProcess |
176 if (!g_Config->init()) {
178 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
179 "Filter startup failed during library initialization, check native log for help.");
184 if (!g_Config->instantiate(NULL, true))
185 throw runtime_error("unknown error");
187 catch (exception& ex) {
190 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, ex.what());
191 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
192 "Filter startup failed to load configuration, check native log for details.");
196 // Access implementation-specifics and site mappings.
197 ServiceProvider* sp=g_Config->getServiceProvider();
199 const PropertySet* props=sp->getPropertySet("InProcess");
201 pair<bool,bool> flag=props->getBool("checkSpoofing");
202 g_checkSpoofing = !flag.first || flag.second;
203 flag=props->getBool("catchAll");
204 g_catchAll = flag.first && flag.second;
206 pair<bool,const char*> unsetValue=props->getString("unsetHeaderValue");
207 if (unsetValue.first)
208 g_unsetHeaderValue = unsetValue.second;
209 if (g_checkSpoofing) {
210 unsetValue = props->getString("spoofKey");
211 if (unsetValue.first)
212 g_spoofKey = unsetValue.second;
214 _invalid_parameter_handler old = _set_invalid_parameter_handler(_my_invalid_parameter_handler);
215 unsigned int randkey=0,randkey2=0,randkey3=0,randkey4=0;
216 if (rand_s(&randkey) == 0 && rand_s(&randkey2) == 0 && rand_s(&randkey3) == 0 && rand_s(&randkey4) == 0) {
217 _set_invalid_parameter_handler(old);
218 ostringstream keystr;
219 keystr << randkey << randkey2 << randkey3 << randkey4;
220 g_spoofKey = keystr.str();
223 _set_invalid_parameter_handler(old);
224 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
225 "Filter failed to generate a random anti-spoofing key (if this is Windows 2000 set one manually).");
226 locker.assign(); // pops lock on SP config
234 props = props->getPropertySet("ISAPI");
236 flag = props->getBool("normalizeRequest");
237 g_bNormalizeRequest = !flag.first || flag.second;
238 flag = props->getBool("safeHeaderNames");
239 g_bSafeHeaderNames = flag.first && flag.second;
240 const DOMElement* child = XMLHelper::getFirstChildElement(props->getElement(),Site);
242 auto_ptr_char id(child->getAttributeNS(NULL,id));
244 g_Sites.insert(pair<string,site_t>(id.get(),site_t(child)));
245 child=XMLHelper::getNextSiblingElement(child,Site);
250 pVer->dwFilterVersion=HTTP_FILTER_REVISION;
251 strncpy(pVer->lpszFilterDesc,"Shibboleth ISAPI Filter",SF_MAX_FILTER_DESC_LEN);
252 pVer->dwFlags=(SF_NOTIFY_ORDER_HIGH |
253 SF_NOTIFY_SECURE_PORT |
254 SF_NOTIFY_NONSECURE_PORT |
255 SF_NOTIFY_PREPROC_HEADERS |
257 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter initialized...");
261 extern "C" BOOL WINAPI TerminateFilter(DWORD)
266 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter shut down...");
270 /* Next up, some suck-free versions of various APIs.
272 You DON'T require people to guess the buffer size and THEN tell them the right size.
273 Returning an LPCSTR is apparently way beyond their ken. Not to mention the fact that
274 constant strings aren't typed as such, making it just that much harder. These versions
275 are now updated to use a special growable buffer object, modeled after the standard
276 string class. The standard string won't work because they left out the option to
277 pre-allocate a non-constant buffer.
283 dynabuf() { bufptr=NULL; buflen=0; }
284 dynabuf(size_t s) { bufptr=new char[buflen=s]; *bufptr=0; }
285 ~dynabuf() { delete[] bufptr; }
286 size_t length() const { return bufptr ? strlen(bufptr) : 0; }
287 size_t size() const { return buflen; }
288 bool empty() const { return length()==0; }
289 void reserve(size_t s, bool keep=false);
290 void erase() { if (bufptr) memset(bufptr,0,buflen); }
291 operator char*() { return bufptr; }
292 bool operator ==(const char* s) const;
293 bool operator !=(const char* s) const { return !(*this==s); }
299 void dynabuf::reserve(size_t s, bool keep)
306 p[buflen]=bufptr[buflen];
312 bool dynabuf::operator==(const char* s) const
314 if (buflen==NULL || s==NULL)
315 return (buflen==NULL && s==NULL);
317 return strcmp(bufptr,s)==0;
320 void GetServerVariable(PHTTP_FILTER_CONTEXT pfc, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
326 while (!pfc->GetServerVariable(pfc,lpszVariable,s,&size)) {
327 // Grumble. Check the error.
328 DWORD e=GetLastError();
329 if (e==ERROR_INSUFFICIENT_BUFFER)
334 if (bRequired && s.empty())
338 void GetServerVariable(LPEXTENSION_CONTROL_BLOCK lpECB, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
344 while (!lpECB->GetServerVariable(lpECB->ConnID,lpszVariable,s,&size)) {
345 // Grumble. Check the error.
346 DWORD e=GetLastError();
347 if (e==ERROR_INSUFFICIENT_BUFFER)
352 if (bRequired && s.empty())
356 void GetHeader(PHTTP_FILTER_PREPROC_HEADERS pn, PHTTP_FILTER_CONTEXT pfc,
357 LPSTR lpszName, dynabuf& s, DWORD size=80, bool bRequired=true)
363 while (!pn->GetHeader(pfc,lpszName,s,&size)) {
364 // Grumble. Check the error.
365 DWORD e=GetLastError();
366 if (e==ERROR_INSUFFICIENT_BUFFER)
371 if (bRequired && s.empty())
375 /****************************************************************************/
378 class ShibTargetIsapiF : public AbstractSPRequest
380 PHTTP_FILTER_CONTEXT m_pfc;
381 PHTTP_FILTER_PREPROC_HEADERS m_pn;
382 multimap<string,string> m_headers;
384 string m_scheme,m_hostname;
385 mutable string m_remote_addr,m_content_type,m_method;
390 ShibTargetIsapiF(PHTTP_FILTER_CONTEXT pfc, PHTTP_FILTER_PREPROC_HEADERS pn, const site_t& site)
391 : AbstractSPRequest(SHIBSP_LOGCAT".ISAPI"), m_pfc(pfc), m_pn(pn), m_allhttp(4096), m_firsttime(true) {
393 // URL path always come from IIS.
395 GetHeader(pn,pfc,"url",var,256,false);
398 // Port may come from IIS or from site def.
399 if (!g_bNormalizeRequest || (pfc->fIsSecurePort && site.m_sslport.empty()) || (!pfc->fIsSecurePort && site.m_port.empty())) {
400 GetServerVariable(pfc,"SERVER_PORT",var,10);
403 else if (pfc->fIsSecurePort) {
404 m_port = atoi(site.m_sslport.c_str());
407 m_port = atoi(site.m_port.c_str());
410 // Scheme may come from site def or be derived from IIS.
411 m_scheme=site.m_scheme;
412 if (m_scheme.empty() || !g_bNormalizeRequest)
413 m_scheme=pfc->fIsSecurePort ? "https" : "http";
415 GetServerVariable(pfc,"SERVER_NAME",var,32);
417 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
419 if (site.m_name!=m_hostname && site.m_aliases.find(m_hostname)==site.m_aliases.end())
420 m_hostname=site.m_name;
422 if (!g_spoofKey.empty()) {
423 GetHeader(pn, pfc, "ShibSpoofCheck:", var, 32, false);
424 if (!var.empty() && g_spoofKey == (char*)var)
429 log(SPDebug, "ISAPI filter running more than once");
431 ~ShibTargetIsapiF() { }
433 const char* getScheme() const {
434 return m_scheme.c_str();
436 const char* getHostname() const {
437 return m_hostname.c_str();
439 int getPort() const {
442 const char* getQueryString() const {
443 const char* uri = getRequestURI();
444 uri = (uri ? strchr(uri, '?') : NULL);
445 return uri ? (uri + 1) : NULL;
447 const char* getMethod() const {
448 if (m_method.empty()) {
450 GetServerVariable(m_pfc,"HTTP_METHOD",var,5,false);
454 return m_method.c_str();
456 string getContentType() const {
457 if (m_content_type.empty()) {
459 GetServerVariable(m_pfc,"HTTP_CONTENT_TYPE",var,32,false);
461 m_content_type = var;
463 return m_content_type;
465 string getRemoteAddr() const {
466 m_remote_addr = AbstractSPRequest::getRemoteAddr();
467 if (m_remote_addr.empty()) {
469 GetServerVariable(m_pfc,"REMOTE_ADDR",var,16,false);
473 return m_remote_addr;
475 void log(SPLogLevel level, const string& msg) {
476 AbstractSPRequest::log(level,msg);
478 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
480 string makeSafeHeader(const char* rawname) const {
482 for (; *rawname; ++rawname) {
483 if (isalnum(*rawname))
488 void clearHeader(const char* rawname, const char* cginame) {
489 if (g_checkSpoofing && m_firsttime) {
490 if (m_allhttp.empty())
491 GetServerVariable(m_pfc, "ALL_HTTP", m_allhttp, 4096);
492 string hdr = g_bSafeHeaderNames ? ("HTTP_" + makeSafeHeader(cginame + 5)) : (string(cginame) + ':');
493 if (strstr(m_allhttp, hdr.c_str()))
494 throw opensaml::SecurityPolicyException("Attempt to spoof header ($1) was detected.", params(1, hdr.c_str()));
496 if (g_bSafeHeaderNames) {
497 string hdr = makeSafeHeader(rawname);
498 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), const_cast<char*>(g_unsetHeaderValue.c_str()));
500 else if (!strcmp(rawname,"REMOTE_USER")) {
501 m_pn->SetHeader(m_pfc, "remote-user:", const_cast<char*>(g_unsetHeaderValue.c_str()));
502 m_pn->SetHeader(m_pfc, "remote_user:", const_cast<char*>(g_unsetHeaderValue.c_str()));
505 string hdr = string(rawname) + ':';
506 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), const_cast<char*>(g_unsetHeaderValue.c_str()));
509 void setHeader(const char* name, const char* value) {
510 string hdr = g_bSafeHeaderNames ? makeSafeHeader(name) : (string(name) + ':');
511 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), const_cast<char*>(value));
513 string getSecureHeader(const char* name) const {
514 string hdr = g_bSafeHeaderNames ? makeSafeHeader(name) : (string(name) + ':');
516 GetHeader(m_pn, m_pfc, const_cast<char*>(hdr.c_str()), buf, 256, false);
517 return string(buf.empty() ? "" : buf);
519 string getHeader(const char* name) const {
523 GetHeader(m_pn, m_pfc, const_cast<char*>(hdr.c_str()), buf, 256, false);
524 return string(buf.empty() ? "" : buf);
526 void setRemoteUser(const char* user) {
527 setHeader("remote-user", user);
529 m_pfc->pFilterContext = NULL;
530 else if (m_pfc->pFilterContext = m_pfc->AllocMem(m_pfc, sizeof(char) * (strlen(user) + 1), NULL))
531 strcpy(reinterpret_cast<char*>(m_pfc->pFilterContext), user);
533 string getRemoteUser() const {
534 return getSecureHeader("remote-user");
536 void setResponseHeader(const char* name, const char* value) {
539 m_headers.insert(make_pair(name,value));
541 m_headers.erase(name);
543 long sendResponse(istream& in, long status) {
544 string hdr = string("Connection: close\r\n");
545 for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
546 hdr += i->first + ": " + i->second + "\r\n";
548 const char* codestr="200 OK";
550 case XMLTOOLING_HTTP_STATUS_UNAUTHORIZED: codestr="401 Authorization Required"; break;
551 case XMLTOOLING_HTTP_STATUS_FORBIDDEN: codestr="403 Forbidden"; break;
552 case XMLTOOLING_HTTP_STATUS_NOTFOUND: codestr="404 Not Found"; break;
553 case XMLTOOLING_HTTP_STATUS_ERROR: codestr="500 Server Error"; break;
555 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, (void*)codestr, (DWORD)hdr.c_str(), 0);
559 DWORD resplen = in.gcount();
560 m_pfc->WriteClient(m_pfc, buf, &resplen, 0);
562 return SF_STATUS_REQ_FINISHED;
564 long sendRedirect(const char* url) {
565 // XXX: Don't support the httpRedirect option, yet.
566 string hdr=string("Location: ") + url + "\r\n"
567 "Content-Type: text/html\r\n"
568 "Content-Length: 40\r\n"
569 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
570 "Cache-Control: private,no-store,no-cache\r\n";
571 for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
572 hdr += i->first + ": " + i->second + "\r\n";
574 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, "302 Please Wait", (DWORD)hdr.c_str(), 0);
575 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
577 m_pfc->WriteClient(m_pfc, (LPVOID)redmsg, &resplen, 0);
578 return SF_STATUS_REQ_FINISHED;
580 long returnDecline() {
581 return SF_STATUS_REQ_NEXT_NOTIFICATION;
584 return SF_STATUS_REQ_NEXT_NOTIFICATION;
587 const vector<string>& getClientCertificates() const {
591 // The filter never processes the POST, so stub these methods.
592 long getContentLength() const { throw IOException("The request's Content-Length is not available to an ISAPI filter."); }
593 const char* getRequestBody() const { throw IOException("The request body is not available to an ISAPI filter."); }
596 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, 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 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)ctype,0);
601 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Filter Error</TITLE></HEAD><BODY>"
602 "<H1>Shibboleth Filter Error</H1>";
603 DWORD resplen=strlen(xmsg);
604 pfc->WriteClient(pfc,(LPVOID)xmsg,&resplen,0);
606 pfc->WriteClient(pfc,(LPVOID)msg,&resplen,0);
607 static const char* xmsg2="</BODY></HTML>";
608 resplen=strlen(xmsg2);
609 pfc->WriteClient(pfc,(LPVOID)xmsg2,&resplen,0);
610 return SF_STATUS_REQ_FINISHED;
613 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
615 // Is this a log notification?
616 if (notificationType==SF_NOTIFY_LOG) {
617 if (pfc->pFilterContext)
618 ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=reinterpret_cast<char*>(pfc->pFilterContext);
619 return SF_STATUS_REQ_NEXT_NOTIFICATION;
622 PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
625 // Determine web site number. This can't really fail, I don't think.
627 GetServerVariable(pfc,"INSTANCE_ID",buf,10);
629 // Match site instance to host name, skip if no match.
630 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
631 if (map_i==g_Sites.end())
632 return SF_STATUS_REQ_NEXT_NOTIFICATION;
634 ostringstream threadid;
635 threadid << "[" << getpid() << "] isapi_shib" << '\0';
636 xmltooling::NDC ndc(threadid.str().c_str());
638 ShibTargetIsapiF stf(pfc, pn, map_i->second);
640 // "false" because we don't override the Shib settings
641 pair<bool,long> res = stf.getServiceProvider().doAuthentication(stf);
642 if (!g_spoofKey.empty())
643 pn->SetHeader(pfc, "ShibSpoofCheck:", const_cast<char*>(g_spoofKey.c_str()));
644 if (res.first) return res.second;
646 // "false" because we don't override the Shib settings
647 res = stf.getServiceProvider().doExport(stf);
648 if (res.first) return res.second;
650 res = stf.getServiceProvider().doAuthorization(stf);
651 if (res.first) return res.second;
653 return SF_STATUS_REQ_NEXT_NOTIFICATION;
656 return WriteClientError(pfc,"Out of Memory");
659 if (e==ERROR_NO_DATA)
660 return WriteClientError(pfc,"A required variable or header was empty.");
662 return WriteClientError(pfc,"Shibboleth Filter detected unexpected IIS error.");
664 catch (exception& e) {
665 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
666 return WriteClientError(pfc,"Shibboleth Filter caught an exception, check Event Log for details.");
669 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Shibboleth Filter threw an unknown exception.");
671 return WriteClientError(pfc,"Shibboleth Filter threw an unknown exception.");
675 return WriteClientError(pfc,"Shibboleth Filter reached unreachable code, save my walrus!");
679 /****************************************************************************/
682 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* msg)
684 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
685 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
686 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
687 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Error</TITLE></HEAD><BODY><H1>Shibboleth Error</H1>";
688 DWORD resplen=strlen(xmsg);
689 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg,&resplen,HSE_IO_SYNC);
691 lpECB->WriteClient(lpECB->ConnID,(LPVOID)msg,&resplen,HSE_IO_SYNC);
692 static const char* xmsg2="</BODY></HTML>";
693 resplen=strlen(xmsg2);
694 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg2,&resplen,HSE_IO_SYNC);
695 return HSE_STATUS_SUCCESS;
699 class ShibTargetIsapiE : public AbstractSPRequest
701 LPEXTENSION_CONTROL_BLOCK m_lpECB;
702 multimap<string,string> m_headers;
703 mutable vector<string> m_certs;
704 mutable string m_body;
705 mutable bool m_gotBody;
707 string m_scheme,m_hostname,m_uri;
708 mutable string m_remote_addr,m_remote_user;
711 ShibTargetIsapiE(LPEXTENSION_CONTROL_BLOCK lpECB, const site_t& site)
712 : AbstractSPRequest(SHIBSP_LOGCAT".ISAPI"), m_lpECB(lpECB), m_gotBody(false) {
714 GetServerVariable(lpECB,"HTTPS",ssl,5);
715 bool SSL=(ssl=="on" || ssl=="ON");
717 // Scheme may come from site def or be derived from IIS.
718 m_scheme=site.m_scheme;
719 if (m_scheme.empty() || !g_bNormalizeRequest)
720 m_scheme = SSL ? "https" : "http";
722 // URL path always come from IIS.
724 GetServerVariable(lpECB,"URL",url,255);
726 // Port may come from IIS or from site def.
728 if (!g_bNormalizeRequest || (SSL && site.m_sslport.empty()) || (!SSL && site.m_port.empty()))
729 GetServerVariable(lpECB,"SERVER_PORT",port,10);
731 strncpy(port,site.m_sslport.c_str(),10);
732 static_cast<char*>(port)[10]=0;
735 strncpy(port,site.m_port.c_str(),10);
736 static_cast<char*>(port)[10]=0;
741 GetServerVariable(lpECB, "SERVER_NAME", var, 32);
743 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
745 if (site.m_name!=m_hostname && site.m_aliases.find(m_hostname)==site.m_aliases.end())
746 m_hostname=site.m_name;
749 * IIS screws us over on PATH_INFO (the hits keep on coming). We need to figure out if
750 * the server is set up for proper PATH_INFO handling, or "IIS sucks rabid weasels mode",
751 * which is the default. No perfect way to tell, but we can take a good guess by checking
752 * whether the URL is a substring of the PATH_INFO:
754 * e.g. for /Shibboleth.sso/SAML/POST
756 * Bad mode (default):
757 * URL: /Shibboleth.sso
758 * PathInfo: /Shibboleth.sso/SAML/POST
761 * URL: /Shibboleth.sso
762 * PathInfo: /SAML/POST
767 // Clearly we're only in bad mode if path info exists at all.
768 if (lpECB->lpszPathInfo && *(lpECB->lpszPathInfo)) {
769 if (strstr(lpECB->lpszPathInfo,url))
770 // Pretty good chance we're in bad mode, unless the PathInfo repeats the path itself.
771 uri = lpECB->lpszPathInfo;
774 uri += lpECB->lpszPathInfo;
781 // For consistency with Apache, let's add the query string.
782 if (lpECB->lpszQueryString && *(lpECB->lpszQueryString)) {
784 uri += lpECB->lpszQueryString;
787 setRequestURI(uri.c_str());
789 ~ShibTargetIsapiE() { }
791 const char* getScheme() const {
792 return m_scheme.c_str();
794 const char* getHostname() const {
795 return m_hostname.c_str();
797 int getPort() const {
800 const char* getMethod() const {
801 return m_lpECB->lpszMethod;
803 string getContentType() const {
804 return m_lpECB->lpszContentType ? m_lpECB->lpszContentType : "";
806 long getContentLength() const {
807 return m_lpECB->cbTotalBytes;
809 string getRemoteUser() const {
810 if (m_remote_user.empty()) {
812 GetServerVariable(m_lpECB, "REMOTE_USER", var, 32, false);
816 return m_remote_user;
818 string getRemoteAddr() const {
819 m_remote_addr = AbstractSPRequest::getRemoteAddr();
820 if (m_remote_addr.empty()) {
822 GetServerVariable(m_lpECB, "REMOTE_ADDR", var, 16, false);
826 return m_remote_addr;
828 void log(SPLogLevel level, const string& msg) const {
829 AbstractSPRequest::log(level,msg);
831 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
833 string getHeader(const char* name) const {
835 for (; *name; ++name) {
839 hdr += toupper(*name);
842 GetServerVariable(m_lpECB, const_cast<char*>(hdr.c_str()), buf, 128, false);
843 return buf.empty() ? "" : buf;
845 void setResponseHeader(const char* name, const char* value) {
848 m_headers.insert(make_pair(name,value));
850 m_headers.erase(name);
852 const char* getQueryString() const {
853 return m_lpECB->lpszQueryString;
855 const char* getRequestBody() const {
857 return m_body.c_str();
858 if (m_lpECB->cbTotalBytes > 1024*1024) // 1MB?
859 throw opensaml::SecurityPolicyException("Size of request body exceeded 1M size limit.");
860 else if (m_lpECB->cbTotalBytes > m_lpECB->cbAvailable) {
862 DWORD datalen=m_lpECB->cbTotalBytes;
863 if (m_lpECB->cbAvailable > 0) {
864 m_body.assign(reinterpret_cast<char*>(m_lpECB->lpbData),m_lpECB->cbAvailable);
865 datalen-=m_lpECB->cbAvailable;
870 BOOL ret = m_lpECB->ReadClient(m_lpECB->ConnID, buf, &buflen);
872 throw IOException("Error reading request body from browser.");
874 throw IOException("Socket closed while reading request body from browser.");
875 m_body.append(buf, buflen);
879 else if (m_lpECB->cbAvailable) {
881 m_body.assign(reinterpret_cast<char*>(m_lpECB->lpbData),m_lpECB->cbAvailable);
883 return m_body.c_str();
885 long sendResponse(istream& in, long status) {
886 string hdr = string("Connection: close\r\n");
887 for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
888 hdr += i->first + ": " + i->second + "\r\n";
890 const char* codestr="200 OK";
892 case XMLTOOLING_HTTP_STATUS_UNAUTHORIZED: codestr="401 Authorization Required"; break;
893 case XMLTOOLING_HTTP_STATUS_FORBIDDEN: codestr="403 Forbidden"; break;
894 case XMLTOOLING_HTTP_STATUS_NOTFOUND: codestr="404 Not Found"; break;
895 case XMLTOOLING_HTTP_STATUS_ERROR: codestr="500 Server Error"; break;
897 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, (void*)codestr, 0, (LPDWORD)hdr.c_str());
901 DWORD resplen = in.gcount();
902 m_lpECB->WriteClient(m_lpECB->ConnID, buf, &resplen, HSE_IO_SYNC);
904 return HSE_STATUS_SUCCESS;
906 long sendRedirect(const char* url) {
907 string hdr=string("Location: ") + url + "\r\n"
908 "Content-Type: text/html\r\n"
909 "Content-Length: 40\r\n"
910 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
911 "Cache-Control: private,no-store,no-cache\r\n";
912 for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
913 hdr += i->first + ": " + i->second + "\r\n";
915 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, "302 Moved", 0, (LPDWORD)hdr.c_str());
916 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
918 m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)redmsg, &resplen, HSE_IO_SYNC);
919 return HSE_STATUS_SUCCESS;
921 // Decline happens in the POST processor if this isn't the shire url
922 // Note that it can also happen with HTAccess, but we don't support that, yet.
923 long returnDecline() {
924 return WriteClientError(
926 "ISAPI extension can only be invoked to process Shibboleth protocol requests."
927 "Make sure the mapped file extension doesn't match actual content."
931 return HSE_STATUS_SUCCESS;
934 const vector<string>& getClientCertificates() const {
935 if (m_certs.empty()) {
936 char CertificateBuf[8192];
937 CERT_CONTEXT_EX ccex;
938 ccex.cbAllocated = sizeof(CertificateBuf);
939 ccex.CertContext.pbCertEncoded = (BYTE*)CertificateBuf;
940 DWORD dwSize = sizeof(ccex);
942 if (m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_GET_CERT_INFO_EX, (LPVOID)&ccex, (LPDWORD)dwSize, NULL)) {
943 if (ccex.CertContext.cbCertEncoded) {
945 XMLByte* serialized = Base64::encode(reinterpret_cast<XMLByte*>(CertificateBuf), ccex.CertContext.cbCertEncoded, &outlen);
946 m_certs.push_back(reinterpret_cast<char*>(serialized));
947 #ifdef SHIBSP_XERCESC_HAS_XMLBYTE_RELEASE
948 XMLString::release(&serialized);
950 XMLString::release((char**)&serialized);
958 // Not used in the extension.
959 void clearHeader(const char* rawname, const char* cginame) { throw runtime_error("clearHeader not implemented"); }
960 void setHeader(const char* name, const char* value) { throw runtime_error("setHeader not implemented"); }
961 void setRemoteUser(const char* user) { throw runtime_error("setRemoteUser not implemented"); }
964 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
967 ostringstream threadid;
968 threadid << "[" << getpid() << "] isapi_shib_extension" << '\0';
969 xmltooling::NDC ndc(threadid.str().c_str());
971 // Determine web site number. This can't really fail, I don't think.
973 GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
975 // Match site instance to host name, skip if no match.
976 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
977 if (map_i==g_Sites.end())
978 return WriteClientError(lpECB, "Shibboleth Extension not configured for web site (check <ISAPI> mappings in configuration).");
980 ShibTargetIsapiE ste(lpECB, map_i->second);
981 pair<bool,long> res = ste.getServiceProvider().doHandler(ste);
982 if (res.first) return res.second;
984 return WriteClientError(lpECB, "Shibboleth Extension failed to process request");
988 return WriteClientError(lpECB,"Out of Memory");
991 if (e==ERROR_NO_DATA)
992 return WriteClientError(lpECB,"A required variable or header was empty.");
994 return WriteClientError(lpECB,"Server detected unexpected IIS error.");
996 catch (exception& e) {
997 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
998 return WriteClientError(lpECB,"Shibboleth Extension caught an exception, check Event Log for details.");
1001 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Shibboleth Extension threw an unknown exception.");
1003 return WriteClientError(lpECB,"Shibboleth Extension threw an unknown exception.");
1007 // If we get here we've got an error.
1008 return HSE_STATUS_ERROR;