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)
568 if (pfc->pFilterContext)
569 ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=static_cast<context_t*>(pfc->pFilterContext)->m_user;
570 return SF_STATUS_REQ_NEXT_NOTIFICATION;
573 PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
576 // Determine web site number. This can't really fail, I don't think.
578 GetServerVariable(pfc,"INSTANCE_ID",buf,10);
580 // Match site instance to host name, skip if no match.
581 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
582 if (map_i==g_Sites.end())
583 return SF_STATUS_REQ_NEXT_NOTIFICATION;
585 ostringstream threadid;
586 threadid << "[" << getpid() << "] isapi_shib" << '\0';
587 xmltooling::NDC ndc(threadid.str().c_str());
589 ShibTargetIsapiF stf(pfc, pn, map_i->second);
591 // "false" because we don't override the Shib settings
592 pair<bool,long> res = stf.getServiceProvider().doAuthentication(stf);
593 if (pfc->pFilterContext)
594 static_cast<context_t*>(pfc->pFilterContext)->m_checked = true;
595 if (res.first) return res.second;
597 // "false" because we don't override the Shib settings
598 res = stf.getServiceProvider().doExport(stf);
599 if (res.first) return res.second;
601 res = stf.getServiceProvider().doAuthorization(stf);
602 if (res.first) return res.second;
604 return SF_STATUS_REQ_NEXT_NOTIFICATION;
607 return WriteClientError(pfc,"Out of Memory");
610 if (e==ERROR_NO_DATA)
611 return WriteClientError(pfc,"A required variable or header was empty.");
613 return WriteClientError(pfc,"Shibboleth Filter detected unexpected IIS error.");
615 catch (exception& e) {
616 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
617 return WriteClientError(pfc,"Shibboleth Filter caught an exception, check Event Log for details.");
620 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Shibboleth Filter threw an unknown exception.");
622 return WriteClientError(pfc,"Shibboleth Filter threw an unknown exception.");
626 return WriteClientError(pfc,"Shibboleth Filter reached unreachable code, save my walrus!");
630 /****************************************************************************/
633 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* msg)
635 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
636 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
637 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
638 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Error</TITLE></HEAD><BODY><H1>Shibboleth Error</H1>";
639 DWORD resplen=strlen(xmsg);
640 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg,&resplen,HSE_IO_SYNC);
642 lpECB->WriteClient(lpECB->ConnID,(LPVOID)msg,&resplen,HSE_IO_SYNC);
643 static const char* xmsg2="</BODY></HTML>";
644 resplen=strlen(xmsg2);
645 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg2,&resplen,HSE_IO_SYNC);
646 return HSE_STATUS_SUCCESS;
650 class ShibTargetIsapiE : public AbstractSPRequest
652 LPEXTENSION_CONTROL_BLOCK m_lpECB;
653 multimap<string,string> m_headers;
654 mutable vector<string> m_certs;
655 mutable string m_body;
656 mutable bool m_gotBody;
658 string m_scheme,m_hostname,m_uri;
659 mutable string m_remote_addr,m_remote_user;
662 ShibTargetIsapiE(LPEXTENSION_CONTROL_BLOCK lpECB, const site_t& site)
663 : AbstractSPRequest(SHIBSP_LOGCAT".ISAPI"), m_lpECB(lpECB), m_gotBody(false) {
665 GetServerVariable(lpECB,"HTTPS",ssl,5);
666 bool SSL=(ssl=="on" || ssl=="ON");
668 // Scheme may come from site def or be derived from IIS.
669 m_scheme=site.m_scheme;
670 if (m_scheme.empty() || !g_bNormalizeRequest)
671 m_scheme = SSL ? "https" : "http";
673 // URL path always come from IIS.
675 GetServerVariable(lpECB,"URL",url,255);
677 // Port may come from IIS or from site def.
679 if (!g_bNormalizeRequest || (SSL && site.m_sslport.empty()) || (!SSL && site.m_port.empty()))
680 GetServerVariable(lpECB,"SERVER_PORT",port,10);
682 strncpy(port,site.m_sslport.c_str(),10);
683 static_cast<char*>(port)[10]=0;
686 strncpy(port,site.m_port.c_str(),10);
687 static_cast<char*>(port)[10]=0;
692 GetServerVariable(lpECB, "SERVER_NAME", var, 32);
694 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
696 if (site.m_name!=m_hostname && site.m_aliases.find(m_hostname)==site.m_aliases.end())
697 m_hostname=site.m_name;
700 * IIS screws us over on PATH_INFO (the hits keep on coming). We need to figure out if
701 * the server is set up for proper PATH_INFO handling, or "IIS sucks rabid weasels mode",
702 * which is the default. No perfect way to tell, but we can take a good guess by checking
703 * whether the URL is a substring of the PATH_INFO:
705 * e.g. for /Shibboleth.sso/SAML/POST
707 * Bad mode (default):
708 * URL: /Shibboleth.sso
709 * PathInfo: /Shibboleth.sso/SAML/POST
712 * URL: /Shibboleth.sso
713 * PathInfo: /SAML/POST
718 // Clearly we're only in bad mode if path info exists at all.
719 if (lpECB->lpszPathInfo && *(lpECB->lpszPathInfo)) {
720 if (strstr(lpECB->lpszPathInfo,url))
721 // Pretty good chance we're in bad mode, unless the PathInfo repeats the path itself.
722 uri = lpECB->lpszPathInfo;
725 uri += lpECB->lpszPathInfo;
732 // For consistency with Apache, let's add the query string.
733 if (lpECB->lpszQueryString && *(lpECB->lpszQueryString)) {
735 uri += lpECB->lpszQueryString;
738 setRequestURI(uri.c_str());
740 ~ShibTargetIsapiE() { }
742 const char* getScheme() const {
743 return m_scheme.c_str();
745 const char* getHostname() const {
746 return m_hostname.c_str();
748 int getPort() const {
751 const char* getMethod() const {
752 return m_lpECB->lpszMethod;
754 string getContentType() const {
755 return m_lpECB->lpszContentType ? m_lpECB->lpszContentType : "";
757 long getContentLength() const {
758 return m_lpECB->cbTotalBytes;
760 string getRemoteUser() const {
761 if (m_remote_user.empty()) {
763 GetServerVariable(m_lpECB, "REMOTE_USER", var, 32, false);
767 return m_remote_user;
769 string getRemoteAddr() const {
770 if (m_remote_addr.empty()) {
772 GetServerVariable(m_lpECB, "REMOTE_ADDR", var, 16, false);
776 return m_remote_addr;
778 void log(SPLogLevel level, const string& msg) const {
779 AbstractSPRequest::log(level,msg);
780 if (level >= SPError)
781 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
783 string getHeader(const char* name) const {
785 for (; *name; ++name) {
789 hdr += toupper(*name);
792 GetServerVariable(m_lpECB, const_cast<char*>(hdr.c_str()), buf, 128, false);
793 return buf.empty() ? "" : buf;
795 void setResponseHeader(const char* name, const char* value) {
798 m_headers.insert(make_pair(name,value));
800 m_headers.erase(name);
802 const char* getQueryString() const {
803 return m_lpECB->lpszQueryString;
805 const char* getRequestBody() const {
807 return m_body.c_str();
808 if (m_lpECB->cbTotalBytes > 1024*1024) // 1MB?
809 throw opensaml::SecurityPolicyException("Size of request body exceeded 1M size limit.");
810 else if (m_lpECB->cbTotalBytes > m_lpECB->cbAvailable) {
813 DWORD datalen=m_lpECB->cbTotalBytes;
816 BOOL ret = m_lpECB->ReadClient(m_lpECB->ConnID, buf, &buflen);
818 throw IOException("Error reading request body from browser.");
819 m_body.append(buf, buflen);
823 else if (m_lpECB->cbAvailable) {
825 m_body.assign(reinterpret_cast<char*>(m_lpECB->lpbData),m_lpECB->cbAvailable);
827 return m_body.c_str();
829 long sendResponse(istream& in, long status) {
830 string hdr = string("Connection: close\r\n");
831 for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
832 hdr += i->first + ": " + i->second + "\r\n";
834 const char* codestr="200 OK";
836 case XMLTOOLING_HTTP_STATUS_UNAUTHORIZED: codestr="401 Authorization Required"; break;
837 case XMLTOOLING_HTTP_STATUS_FORBIDDEN: codestr="403 Forbidden"; break;
838 case XMLTOOLING_HTTP_STATUS_NOTFOUND: codestr="404 Not Found"; break;
839 case XMLTOOLING_HTTP_STATUS_ERROR: codestr="500 Server Error"; break;
841 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, (void*)codestr, 0, (LPDWORD)hdr.c_str());
845 DWORD resplen = in.gcount();
846 m_lpECB->WriteClient(m_lpECB->ConnID, buf, &resplen, HSE_IO_SYNC);
848 return HSE_STATUS_SUCCESS;
850 long sendRedirect(const char* url) {
851 string hdr=string("Location: ") + url + "\r\n"
852 "Content-Type: text/html\r\n"
853 "Content-Length: 40\r\n"
854 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
855 "Cache-Control: private,no-store,no-cache\r\n";
856 for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
857 hdr += i->first + ": " + i->second + "\r\n";
859 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, "302 Moved", 0, (LPDWORD)hdr.c_str());
860 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
862 m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)redmsg, &resplen, HSE_IO_SYNC);
863 return HSE_STATUS_SUCCESS;
865 // Decline happens in the POST processor if this isn't the shire url
866 // Note that it can also happen with HTAccess, but we don't support that, yet.
867 long returnDecline() {
868 return WriteClientError(
870 "ISAPI extension can only be invoked to process Shibboleth protocol requests."
871 "Make sure the mapped file extension doesn't match actual content."
875 return HSE_STATUS_SUCCESS;
878 const vector<string>& getClientCertificates() const {
879 if (m_certs.empty()) {
880 char CertificateBuf[8192];
881 CERT_CONTEXT_EX ccex;
882 ccex.cbAllocated = sizeof(CertificateBuf);
883 ccex.CertContext.pbCertEncoded = (BYTE*)CertificateBuf;
884 DWORD dwSize = sizeof(ccex);
886 if (m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_GET_CERT_INFO_EX, (LPVOID)&ccex, (LPDWORD)dwSize, NULL)) {
887 if (ccex.CertContext.cbCertEncoded) {
889 XMLByte* serialized = Base64::encode(reinterpret_cast<XMLByte*>(CertificateBuf), ccex.CertContext.cbCertEncoded, &outlen);
890 m_certs.push_back(reinterpret_cast<char*>(serialized));
891 XMLString::release(&serialized);
898 // Not used in the extension.
899 void clearHeader(const char* rawname, const char* cginame) { throw runtime_error("clearHeader not implemented"); }
900 void setHeader(const char* name, const char* value) { throw runtime_error("setHeader not implemented"); }
901 void setRemoteUser(const char* user) { throw runtime_error("setRemoteUser not implemented"); }
904 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
907 ostringstream threadid;
908 threadid << "[" << getpid() << "] isapi_shib_extension" << '\0';
909 xmltooling::NDC ndc(threadid.str().c_str());
911 // Determine web site number. This can't really fail, I don't think.
913 GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
915 // Match site instance to host name, skip if no match.
916 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
917 if (map_i==g_Sites.end())
918 return WriteClientError(lpECB, "Shibboleth Extension not configured for web site (check <ISAPI> mappings in configuration).");
920 ShibTargetIsapiE ste(lpECB, map_i->second);
921 pair<bool,long> res = ste.getServiceProvider().doHandler(ste);
922 if (res.first) return res.second;
924 return WriteClientError(lpECB, "Shibboleth Extension failed to process request");
928 return WriteClientError(lpECB,"Out of Memory");
931 if (e==ERROR_NO_DATA)
932 return WriteClientError(lpECB,"A required variable or header was empty.");
934 return WriteClientError(lpECB,"Server detected unexpected IIS error.");
936 catch (exception& e) {
937 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
938 return WriteClientError(lpECB,"Shibboleth Extension caught an exception, check Event Log for details.");
941 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Shibboleth Extension threw an unknown exception.");
943 return WriteClientError(lpECB,"Shibboleth Extension threw an unknown exception.");
947 // If we get here we've got an error.
948 return HSE_STATUS_ERROR;