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 ISAPI[] = UNICODE_LITERAL_5(I,S,A,P,I);
64 static const XMLCh Alias[] = UNICODE_LITERAL_5(A,l,i,a,s);
65 static const XMLCh normalizeRequest[] = UNICODE_LITERAL_16(n,o,r,m,a,l,i,z,e,R,e,q,u,e,s,t);
66 static const XMLCh Site[] = UNICODE_LITERAL_4(S,i,t,e);
69 site_t(const DOMElement* e)
71 auto_ptr_char n(e->getAttributeNS(NULL,name));
72 auto_ptr_char s(e->getAttributeNS(NULL,scheme));
73 auto_ptr_char p(e->getAttributeNS(NULL,port));
74 auto_ptr_char p2(e->getAttributeNS(NULL,sslport));
75 if (n.get()) m_name=n.get();
76 if (s.get()) m_scheme=s.get();
77 if (p.get()) m_port=p.get();
78 if (p2.get()) m_sslport=p2.get();
79 e = XMLHelper::getFirstChildElement(e, Alias);
81 if (e->hasChildNodes()) {
82 auto_ptr_char alias(e->getFirstChild()->getNodeValue());
83 m_aliases.insert(alias.get());
85 e = XMLHelper::getNextSiblingElement(e, Alias);
88 string m_scheme,m_port,m_sslport,m_name;
89 set<string> m_aliases;
98 SPConfig* g_Config = NULL;
99 map<string,site_t> g_Sites;
100 bool g_bNormalizeRequest = true;
101 string g_unsetHeaderValue;
102 bool g_checkSpoofing = true;
103 bool g_catchAll = false;
104 vector<string> g_NoCerts;
108 LPCSTR lpUNCServerName,
114 LPCSTR messages[] = {message, NULL};
116 HANDLE hElog = RegisterEventSource(lpUNCServerName, "Shibboleth ISAPI Filter");
117 BOOL res = ReportEvent(hElog, wType, 0, dwEventID, lpUserSid, 1, 0, messages, NULL);
118 return (DeregisterEventSource(hElog) && res);
121 extern "C" __declspec(dllexport) BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID)
123 if (fdwReason==DLL_PROCESS_ATTACH)
128 extern "C" BOOL WINAPI GetExtensionVersion(HSE_VERSION_INFO* pVer)
134 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
135 "Extension mode startup not possible, is the DLL loaded as a filter?");
139 pVer->dwExtensionVersion=HSE_VERSION;
140 strncpy(pVer->lpszExtensionDesc,"Shibboleth ISAPI Extension",HSE_MAX_EXT_DLL_NAME_LEN-1);
144 extern "C" BOOL WINAPI TerminateExtension(DWORD)
146 return TRUE; // cleanup should happen when filter unloads
149 extern "C" BOOL WINAPI GetFilterVersion(PHTTP_FILTER_VERSION pVer)
154 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
155 "Reentrant filter initialization, ignoring...");
159 g_Config=&SPConfig::getConfig();
160 g_Config->setFeatures(
163 SPConfig::RequestMapping |
164 SPConfig::InProcess |
168 if (!g_Config->init()) {
170 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
171 "Filter startup failed during library initialization, check native log for help.");
175 LPCSTR config=getenv("SHIBSP_CONFIG");
177 config=SHIBSP_CONFIG;
180 DOMDocument* dummydoc=XMLToolingConfig::getConfig().getParser().newDocument();
181 XercesJanitor<DOMDocument> docjanitor(dummydoc);
182 DOMElement* dummy = dummydoc->createElementNS(NULL,path);
183 auto_ptr_XMLCh src(config);
184 dummy->setAttributeNS(NULL,path,src.get());
185 dummy->setAttributeNS(NULL,validate,xmlconstants::XML_ONE);
187 g_Config->setServiceProvider(g_Config->ServiceProviderManager.newPlugin(XML_SERVICE_PROVIDER,dummy));
188 g_Config->getServiceProvider()->init();
190 catch (exception& ex) {
193 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, ex.what());
194 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
195 "Filter startup failed to load configuration, check native log for details.");
199 // Access implementation-specifics and site mappings.
200 ServiceProvider* sp=g_Config->getServiceProvider();
202 const PropertySet* props=sp->getPropertySet("InProcess");
204 pair<bool,const char*> unsetValue=props->getString("unsetHeaderValue");
205 if (unsetValue.first)
206 g_unsetHeaderValue = unsetValue.second;
207 pair<bool,bool> flag=props->getBool("checkSpoofing");
208 g_checkSpoofing = !flag.first || flag.second;
209 flag=props->getBool("catchAll");
210 g_catchAll = flag.first && flag.second;
212 props = props->getPropertySet("ISAPI");
214 flag = props->getBool("normalizeRequest");
215 g_bNormalizeRequest = !flag.first || flag.second;
216 const DOMElement* child = XMLHelper::getFirstChildElement(props->getElement(),Site);
218 auto_ptr_char id(child->getAttributeNS(NULL,id));
220 g_Sites.insert(pair<string,site_t>(id.get(),site_t(child)));
221 child=XMLHelper::getNextSiblingElement(child,Site);
226 pVer->dwFilterVersion=HTTP_FILTER_REVISION;
227 strncpy(pVer->lpszFilterDesc,"Shibboleth ISAPI Filter",SF_MAX_FILTER_DESC_LEN);
228 pVer->dwFlags=(SF_NOTIFY_ORDER_HIGH |
229 SF_NOTIFY_SECURE_PORT |
230 SF_NOTIFY_NONSECURE_PORT |
231 SF_NOTIFY_PREPROC_HEADERS |
233 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter initialized...");
237 extern "C" BOOL WINAPI TerminateFilter(DWORD)
242 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter shut down...");
246 /* Next up, some suck-free versions of various APIs.
248 You DON'T require people to guess the buffer size and THEN tell them the right size.
249 Returning an LPCSTR is apparently way beyond their ken. Not to mention the fact that
250 constant strings aren't typed as such, making it just that much harder. These versions
251 are now updated to use a special growable buffer object, modeled after the standard
252 string class. The standard string won't work because they left out the option to
253 pre-allocate a non-constant buffer.
259 dynabuf() { bufptr=NULL; buflen=0; }
260 dynabuf(size_t s) { bufptr=new char[buflen=s]; *bufptr=0; }
261 ~dynabuf() { delete[] bufptr; }
262 size_t length() const { return bufptr ? strlen(bufptr) : 0; }
263 size_t size() const { return buflen; }
264 bool empty() const { return length()==0; }
265 void reserve(size_t s, bool keep=false);
266 void erase() { if (bufptr) memset(bufptr,0,buflen); }
267 operator char*() { return bufptr; }
268 bool operator ==(const char* s) const;
269 bool operator !=(const char* s) const { return !(*this==s); }
275 void dynabuf::reserve(size_t s, bool keep)
282 p[buflen]=bufptr[buflen];
288 bool dynabuf::operator==(const char* s) const
290 if (buflen==NULL || s==NULL)
291 return (buflen==NULL && s==NULL);
293 return strcmp(bufptr,s)==0;
296 void GetServerVariable(PHTTP_FILTER_CONTEXT pfc, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
302 while (!pfc->GetServerVariable(pfc,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 GetServerVariable(LPEXTENSION_CONTROL_BLOCK lpECB, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
320 while (!lpECB->GetServerVariable(lpECB->ConnID,lpszVariable,s,&size)) {
321 // Grumble. Check the error.
322 DWORD e=GetLastError();
323 if (e==ERROR_INSUFFICIENT_BUFFER)
328 if (bRequired && s.empty())
332 void GetHeader(PHTTP_FILTER_PREPROC_HEADERS pn, PHTTP_FILTER_CONTEXT pfc,
333 LPSTR lpszName, dynabuf& s, DWORD size=80, bool bRequired=true)
339 while (!pn->GetHeader(pfc,lpszName,s,&size)) {
340 // Grumble. Check the error.
341 DWORD e=GetLastError();
342 if (e==ERROR_INSUFFICIENT_BUFFER)
347 if (bRequired && s.empty())
351 /****************************************************************************/
354 class ShibTargetIsapiF : public AbstractSPRequest
356 PHTTP_FILTER_CONTEXT m_pfc;
357 PHTTP_FILTER_PREPROC_HEADERS m_pn;
358 multimap<string,string> m_headers;
360 string m_scheme,m_hostname;
361 mutable string m_remote_addr,m_content_type,m_method;
365 ShibTargetIsapiF(PHTTP_FILTER_CONTEXT pfc, PHTTP_FILTER_PREPROC_HEADERS pn, const site_t& site)
366 : AbstractSPRequest(SHIBSP_LOGCAT".ISAPI"), m_pfc(pfc), m_pn(pn), m_allhttp(4096) {
368 // URL path always come from IIS.
370 GetHeader(pn,pfc,"url",var,256,false);
373 // Port may come from IIS or from site def.
374 if (!g_bNormalizeRequest || (pfc->fIsSecurePort && site.m_sslport.empty()) || (!pfc->fIsSecurePort && site.m_port.empty())) {
375 GetServerVariable(pfc,"SERVER_PORT",var,10);
378 else if (pfc->fIsSecurePort) {
379 m_port = atoi(site.m_sslport.c_str());
382 m_port = atoi(site.m_port.c_str());
385 // Scheme may come from site def or be derived from IIS.
386 m_scheme=site.m_scheme;
387 if (m_scheme.empty() || !g_bNormalizeRequest)
388 m_scheme=pfc->fIsSecurePort ? "https" : "http";
390 GetServerVariable(pfc,"SERVER_NAME",var,32);
392 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
394 if (site.m_name!=m_hostname && site.m_aliases.find(m_hostname)==site.m_aliases.end())
395 m_hostname=site.m_name;
397 if (!pfc->pFilterContext) {
398 pfc->pFilterContext = pfc->AllocMem(pfc, sizeof(context_t), NULL);
399 if (static_cast<context_t*>(pfc->pFilterContext)) {
400 static_cast<context_t*>(pfc->pFilterContext)->m_user = NULL;
401 static_cast<context_t*>(pfc->pFilterContext)->m_checked = false;
405 ~ShibTargetIsapiF() { }
407 const char* getScheme() const {
408 return m_scheme.c_str();
410 const char* getHostname() const {
411 return m_hostname.c_str();
413 int getPort() const {
416 const char* getMethod() const {
417 if (m_method.empty()) {
419 GetServerVariable(m_pfc,"REQUEST_METHOD",var,5,false);
423 return m_method.c_str();
425 string getContentType() const {
426 if (m_content_type.empty()) {
428 GetServerVariable(m_pfc,"CONTENT_TYPE",var,32,false);
430 m_content_type = var;
432 return m_content_type;
434 long getContentLength() const {
437 string getRemoteAddr() const {
438 if (m_remote_addr.empty()) {
440 GetServerVariable(m_pfc,"REMOTE_ADDR",var,16,false);
444 return m_remote_addr;
446 void log(SPLogLevel level, const string& msg) {
447 AbstractSPRequest::log(level,msg);
448 if (level >= SPError)
449 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
451 void clearHeader(const char* rawname, const char* cginame) {
452 if (g_checkSpoofing && m_pfc->pFilterContext && !static_cast<context_t*>(m_pfc->pFilterContext)->m_checked) {
453 if (m_allhttp.empty())
454 GetServerVariable(m_pfc,"ALL_HTTP",m_allhttp,4096);
455 if (strstr(m_allhttp, cginame))
456 throw opensaml::SecurityPolicyException("Attempt to spoof header ($1) was detected.", params(1, rawname));
458 string hdr(!strcmp(rawname,"REMOTE_USER") ? "remote-user" : rawname);
460 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), const_cast<char*>(g_unsetHeaderValue.c_str()));
462 void setHeader(const char* name, const char* value) {
465 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), const_cast<char*>(value));
467 string getHeader(const char* name) const {
471 GetHeader(m_pn, m_pfc, const_cast<char*>(hdr.c_str()), buf, 256, false);
474 void setRemoteUser(const char* user) {
475 setHeader("remote-user", user);
476 if (m_pfc->pFilterContext) {
478 static_cast<context_t*>(m_pfc->pFilterContext)->m_user = NULL;
479 else if (static_cast<context_t*>(m_pfc->pFilterContext)->m_user = (char*)m_pfc->AllocMem(m_pfc, sizeof(char) * (strlen(user) + 1), NULL))
480 strcpy(static_cast<context_t*>(m_pfc->pFilterContext)->m_user, user);
483 string getRemoteUser() const {
484 return getHeader("remote-user");
486 void setResponseHeader(const char* name, const char* value) {
489 m_headers.insert(make_pair(name,value));
491 m_headers.erase(name);
493 long sendResponse(istream& in, long status) {
494 string hdr = string("Connection: close\r\n");
495 for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
496 hdr += i->first + ": " + i->second + "\r\n";
498 const char* codestr="200 OK";
500 case XMLTOOLING_HTTP_STATUS_UNAUTHORIZED: codestr="401 Authorization Required"; break;
501 case XMLTOOLING_HTTP_STATUS_FORBIDDEN: codestr="403 Forbidden"; break;
502 case XMLTOOLING_HTTP_STATUS_NOTFOUND: codestr="404 Not Found"; break;
503 case XMLTOOLING_HTTP_STATUS_ERROR: codestr="500 Server Error"; break;
505 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, (void*)codestr, (DWORD)hdr.c_str(), 0);
509 DWORD resplen = in.gcount();
510 m_pfc->WriteClient(m_pfc, buf, &resplen, 0);
512 return SF_STATUS_REQ_FINISHED;
514 long sendRedirect(const char* url) {
515 // XXX: Don't support the httpRedirect option, yet.
516 string hdr=string("Location: ") + url + "\r\n"
517 "Content-Type: text/html\r\n"
518 "Content-Length: 40\r\n"
519 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
520 "Cache-Control: private,no-store,no-cache\r\n";
521 for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
522 hdr += i->first + ": " + i->second + "\r\n";
524 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, "302 Please Wait", (DWORD)hdr.c_str(), 0);
525 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
527 m_pfc->WriteClient(m_pfc, (LPVOID)redmsg, &resplen, 0);
528 return SF_STATUS_REQ_FINISHED;
530 long returnDecline() {
531 return SF_STATUS_REQ_NEXT_NOTIFICATION;
534 return SF_STATUS_REQ_NEXT_NOTIFICATION;
537 const vector<string>& getClientCertificates() const {
541 // The filter never processes the POST, so stub these methods.
542 const char* getQueryString() const { throw IOException("getQueryString not implemented"); }
543 const char* getRequestBody() const { throw IOException("getRequestBody not implemented"); }
546 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const char* msg)
548 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
549 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
550 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)ctype,0);
551 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Filter Error</TITLE></HEAD><BODY>"
552 "<H1>Shibboleth Filter Error</H1>";
553 DWORD resplen=strlen(xmsg);
554 pfc->WriteClient(pfc,(LPVOID)xmsg,&resplen,0);
556 pfc->WriteClient(pfc,(LPVOID)msg,&resplen,0);
557 static const char* xmsg2="</BODY></HTML>";
558 resplen=strlen(xmsg2);
559 pfc->WriteClient(pfc,(LPVOID)xmsg2,&resplen,0);
560 return SF_STATUS_REQ_FINISHED;
563 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
565 // Is this a log notification?
566 if (notificationType==SF_NOTIFY_LOG) {
567 if (pfc->pFilterContext && static_cast<context_t*>(pfc->pFilterContext)->m_user)
568 ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=static_cast<context_t*>(pfc->pFilterContext)->m_user;
569 return SF_STATUS_REQ_NEXT_NOTIFICATION;
572 PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
575 // Determine web site number. This can't really fail, I don't think.
577 GetServerVariable(pfc,"INSTANCE_ID",buf,10);
579 // Match site instance to host name, skip if no match.
580 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
581 if (map_i==g_Sites.end())
582 return SF_STATUS_REQ_NEXT_NOTIFICATION;
584 ostringstream threadid;
585 threadid << "[" << getpid() << "] isapi_shib" << '\0';
586 xmltooling::NDC ndc(threadid.str().c_str());
588 ShibTargetIsapiF stf(pfc, pn, map_i->second);
590 // "false" because we don't override the Shib settings
591 pair<bool,long> res = stf.getServiceProvider().doAuthentication(stf);
592 if (pfc->pFilterContext)
593 static_cast<context_t*>(pfc->pFilterContext)->m_checked = true;
594 if (res.first) return res.second;
596 // "false" because we don't override the Shib settings
597 res = stf.getServiceProvider().doExport(stf);
598 if (res.first) return res.second;
600 res = stf.getServiceProvider().doAuthorization(stf);
601 if (res.first) return res.second;
603 return SF_STATUS_REQ_NEXT_NOTIFICATION;
606 return WriteClientError(pfc,"Out of Memory");
609 if (e==ERROR_NO_DATA)
610 return WriteClientError(pfc,"A required variable or header was empty.");
612 return WriteClientError(pfc,"Shibboleth Filter detected unexpected IIS error.");
614 catch (exception& e) {
615 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
616 return WriteClientError(pfc,"Shibboleth Filter caught an exception, check Event Log for details.");
619 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Shibboleth Filter threw an unknown exception.");
621 return WriteClientError(pfc,"Shibboleth Filter threw an unknown exception.");
625 return WriteClientError(pfc,"Shibboleth Filter reached unreachable code, save my walrus!");
629 /****************************************************************************/
632 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* msg)
634 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
635 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
636 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
637 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Error</TITLE></HEAD><BODY><H1>Shibboleth Error</H1>";
638 DWORD resplen=strlen(xmsg);
639 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg,&resplen,HSE_IO_SYNC);
641 lpECB->WriteClient(lpECB->ConnID,(LPVOID)msg,&resplen,HSE_IO_SYNC);
642 static const char* xmsg2="</BODY></HTML>";
643 resplen=strlen(xmsg2);
644 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg2,&resplen,HSE_IO_SYNC);
645 return HSE_STATUS_SUCCESS;
649 class ShibTargetIsapiE : public AbstractSPRequest
651 LPEXTENSION_CONTROL_BLOCK m_lpECB;
652 multimap<string,string> m_headers;
653 mutable vector<string> m_certs;
654 mutable string m_body;
655 mutable bool m_gotBody;
657 string m_scheme,m_hostname,m_uri;
658 mutable string m_remote_addr,m_remote_user;
661 ShibTargetIsapiE(LPEXTENSION_CONTROL_BLOCK lpECB, const site_t& site)
662 : AbstractSPRequest(SHIBSP_LOGCAT".ISAPI"), m_lpECB(lpECB), m_gotBody(false) {
664 GetServerVariable(lpECB,"HTTPS",ssl,5);
665 bool SSL=(ssl=="on" || ssl=="ON");
667 // Scheme may come from site def or be derived from IIS.
668 m_scheme=site.m_scheme;
669 if (m_scheme.empty() || !g_bNormalizeRequest)
670 m_scheme = SSL ? "https" : "http";
672 // URL path always come from IIS.
674 GetServerVariable(lpECB,"URL",url,255);
676 // Port may come from IIS or from site def.
678 if (!g_bNormalizeRequest || (SSL && site.m_sslport.empty()) || (!SSL && site.m_port.empty()))
679 GetServerVariable(lpECB,"SERVER_PORT",port,10);
681 strncpy(port,site.m_sslport.c_str(),10);
682 static_cast<char*>(port)[10]=0;
685 strncpy(port,site.m_port.c_str(),10);
686 static_cast<char*>(port)[10]=0;
691 GetServerVariable(lpECB, "SERVER_NAME", var, 32);
693 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
695 if (site.m_name!=m_hostname && site.m_aliases.find(m_hostname)==site.m_aliases.end())
696 m_hostname=site.m_name;
699 * IIS screws us over on PATH_INFO (the hits keep on coming). We need to figure out if
700 * the server is set up for proper PATH_INFO handling, or "IIS sucks rabid weasels mode",
701 * which is the default. No perfect way to tell, but we can take a good guess by checking
702 * whether the URL is a substring of the PATH_INFO:
704 * e.g. for /Shibboleth.sso/SAML/POST
706 * Bad mode (default):
707 * URL: /Shibboleth.sso
708 * PathInfo: /Shibboleth.sso/SAML/POST
711 * URL: /Shibboleth.sso
712 * PathInfo: /SAML/POST
717 // Clearly we're only in bad mode if path info exists at all.
718 if (lpECB->lpszPathInfo && *(lpECB->lpszPathInfo)) {
719 if (strstr(lpECB->lpszPathInfo,url))
720 // Pretty good chance we're in bad mode, unless the PathInfo repeats the path itself.
721 uri = lpECB->lpszPathInfo;
724 uri += lpECB->lpszPathInfo;
731 // For consistency with Apache, let's add the query string.
732 if (lpECB->lpszQueryString && *(lpECB->lpszQueryString)) {
734 uri += lpECB->lpszQueryString;
737 setRequestURI(uri.c_str());
739 ~ShibTargetIsapiE() { }
741 const char* getScheme() const {
742 return m_scheme.c_str();
744 const char* getHostname() const {
745 return m_hostname.c_str();
747 int getPort() const {
750 const char* getMethod() const {
751 return m_lpECB->lpszMethod;
753 string getContentType() const {
754 return m_lpECB->lpszContentType ? m_lpECB->lpszContentType : "";
756 long getContentLength() const {
757 return m_lpECB->cbTotalBytes;
759 string getRemoteUser() const {
760 if (m_remote_user.empty()) {
762 GetServerVariable(m_lpECB, "REMOTE_USER", var, 32, false);
766 return m_remote_user;
768 string getRemoteAddr() const {
769 if (m_remote_addr.empty()) {
771 GetServerVariable(m_lpECB, "REMOTE_ADDR", var, 16, false);
775 return m_remote_addr;
777 void log(SPLogLevel level, const string& msg) const {
778 AbstractSPRequest::log(level,msg);
779 if (level >= SPError)
780 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
782 string getHeader(const char* name) const {
784 for (; *name; ++name) {
788 hdr += toupper(*name);
791 GetServerVariable(m_lpECB, const_cast<char*>(hdr.c_str()), buf, 128, false);
792 return buf.empty() ? "" : buf;
794 void setResponseHeader(const char* name, const char* value) {
797 m_headers.insert(make_pair(name,value));
799 m_headers.erase(name);
801 const char* getQueryString() const {
802 return m_lpECB->lpszQueryString;
804 const char* getRequestBody() const {
806 return m_body.c_str();
807 if (m_lpECB->cbTotalBytes > 1024*1024) // 1MB?
808 throw opensaml::SecurityPolicyException("Size of request body exceeded 1M size limit.");
809 else if (m_lpECB->cbTotalBytes > m_lpECB->cbAvailable) {
812 DWORD datalen=m_lpECB->cbTotalBytes;
815 BOOL ret = m_lpECB->ReadClient(m_lpECB->ConnID, buf, &buflen);
817 throw IOException("Error reading request body from browser.");
818 m_body.append(buf, buflen);
822 else if (m_lpECB->cbAvailable) {
824 m_body.assign(reinterpret_cast<char*>(m_lpECB->lpbData),m_lpECB->cbAvailable);
826 return m_body.c_str();
828 long sendResponse(istream& in, long status) {
829 string hdr = string("Connection: close\r\n");
830 for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
831 hdr += i->first + ": " + i->second + "\r\n";
833 const char* codestr="200 OK";
835 case XMLTOOLING_HTTP_STATUS_UNAUTHORIZED: codestr="401 Authorization Required"; break;
836 case XMLTOOLING_HTTP_STATUS_FORBIDDEN: codestr="403 Forbidden"; break;
837 case XMLTOOLING_HTTP_STATUS_NOTFOUND: codestr="404 Not Found"; break;
838 case XMLTOOLING_HTTP_STATUS_ERROR: codestr="500 Server Error"; break;
840 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, (void*)codestr, 0, (LPDWORD)hdr.c_str());
844 DWORD resplen = in.gcount();
845 m_lpECB->WriteClient(m_lpECB->ConnID, buf, &resplen, HSE_IO_SYNC);
847 return HSE_STATUS_SUCCESS;
849 long sendRedirect(const char* url) {
850 string hdr=string("Location: ") + url + "\r\n"
851 "Content-Type: text/html\r\n"
852 "Content-Length: 40\r\n"
853 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
854 "Cache-Control: private,no-store,no-cache\r\n";
855 for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
856 hdr += i->first + ": " + i->second + "\r\n";
858 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, "302 Moved", 0, (LPDWORD)hdr.c_str());
859 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
861 m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)redmsg, &resplen, HSE_IO_SYNC);
862 return HSE_STATUS_SUCCESS;
864 // Decline happens in the POST processor if this isn't the shire url
865 // Note that it can also happen with HTAccess, but we don't support that, yet.
866 long returnDecline() {
867 return WriteClientError(
869 "ISAPI extension can only be invoked to process Shibboleth protocol requests."
870 "Make sure the mapped file extension doesn't match actual content."
874 return HSE_STATUS_SUCCESS;
877 const vector<string>& getClientCertificates() const {
878 if (m_certs.empty()) {
879 char CertificateBuf[8192];
880 CERT_CONTEXT_EX ccex;
881 ccex.cbAllocated = sizeof(CertificateBuf);
882 ccex.CertContext.pbCertEncoded = (BYTE*)CertificateBuf;
883 DWORD dwSize = sizeof(ccex);
885 if (m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_GET_CERT_INFO_EX, (LPVOID)&ccex, (LPDWORD)dwSize, NULL)) {
886 if (ccex.CertContext.cbCertEncoded) {
888 XMLByte* serialized = Base64::encode(reinterpret_cast<XMLByte*>(CertificateBuf), ccex.CertContext.cbCertEncoded, &outlen);
889 m_certs.push_back(reinterpret_cast<char*>(serialized));
890 XMLString::release(&serialized);
897 // Not used in the extension.
898 void clearHeader(const char* rawname, const char* cginame) { throw runtime_error("clearHeader not implemented"); }
899 void setHeader(const char* name, const char* value) { throw runtime_error("setHeader not implemented"); }
900 void setRemoteUser(const char* user) { throw runtime_error("setRemoteUser not implemented"); }
903 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
906 ostringstream threadid;
907 threadid << "[" << getpid() << "] isapi_shib_extension" << '\0';
908 xmltooling::NDC ndc(threadid.str().c_str());
910 // Determine web site number. This can't really fail, I don't think.
912 GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
914 // Match site instance to host name, skip if no match.
915 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
916 if (map_i==g_Sites.end())
917 return WriteClientError(lpECB, "Shibboleth Extension not configured for web site (check <ISAPI> mappings in configuration).");
919 ShibTargetIsapiE ste(lpECB, map_i->second);
920 pair<bool,long> res = ste.getServiceProvider().doHandler(ste);
921 if (res.first) return res.second;
923 return WriteClientError(lpECB, "Shibboleth Extension failed to process request");
927 return WriteClientError(lpECB,"Out of Memory");
930 if (e==ERROR_NO_DATA)
931 return WriteClientError(lpECB,"A required variable or header was empty.");
933 return WriteClientError(lpECB,"Server detected unexpected IIS error.");
935 catch (exception& e) {
936 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
937 return WriteClientError(lpECB,"Shibboleth Extension caught an exception, check Event Log for details.");
940 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Shibboleth Extension threw an unknown exception.");
942 return WriteClientError(lpECB,"Shibboleth Extension threw an unknown exception.");
946 // If we get here we've got an error.
947 return HSE_STATUS_ERROR;