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 Implementation[] = UNICODE_LITERAL_14(I,m,p,l,e,m,e,n,t,a,t,i,o,n);
64 static const XMLCh ISAPI[] = UNICODE_LITERAL_5(I,S,A,P,I);
65 static const XMLCh Alias[] = UNICODE_LITERAL_5(A,l,i,a,s);
66 static const XMLCh normalizeRequest[] = UNICODE_LITERAL_16(n,o,r,m,a,l,i,z,e,R,e,q,u,e,s,t);
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;
98 bool g_checkSpoofing = true;
99 vector<string> g_NoCerts;
103 LPCSTR lpUNCServerName,
109 LPCSTR messages[] = {message, NULL};
111 HANDLE hElog = RegisterEventSource(lpUNCServerName, "Shibboleth ISAPI Filter");
112 BOOL res = ReportEvent(hElog, wType, 0, dwEventID, lpUserSid, 1, 0, messages, NULL);
113 return (DeregisterEventSource(hElog) && res);
116 extern "C" __declspec(dllexport) BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID)
118 if (fdwReason==DLL_PROCESS_ATTACH)
123 extern "C" BOOL WINAPI GetExtensionVersion(HSE_VERSION_INFO* pVer)
129 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
130 "Extension mode startup not possible, is the DLL loaded as a filter?");
134 pVer->dwExtensionVersion=HSE_VERSION;
135 strncpy(pVer->lpszExtensionDesc,"Shibboleth ISAPI Extension",HSE_MAX_EXT_DLL_NAME_LEN-1);
139 extern "C" BOOL WINAPI TerminateExtension(DWORD)
141 return TRUE; // cleanup should happen when filter unloads
144 extern "C" BOOL WINAPI GetFilterVersion(PHTTP_FILTER_VERSION pVer)
149 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
150 "Reentrant filter initialization, ignoring...");
154 LPCSTR schemadir=getenv("SHIBSP_SCHEMAS");
156 schemadir=SHIBSP_SCHEMAS;
157 LPCSTR config=getenv("SHIBSP_CONFIG");
159 config=SHIBSP_CONFIG;
160 g_Config=&SPConfig::getConfig();
161 g_Config->setFeatures(
164 SPConfig::RequestMapping |
165 SPConfig::InProcess |
169 if (!g_Config->init(schemadir)) {
171 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
172 "Filter startup failed during library initialization, check native log for help.");
177 DOMDocument* dummydoc=XMLToolingConfig::getConfig().getParser().newDocument();
178 XercesJanitor<DOMDocument> docjanitor(dummydoc);
179 DOMElement* dummy = dummydoc->createElementNS(NULL,path);
180 auto_ptr_XMLCh src(config);
181 dummy->setAttributeNS(NULL,path,src.get());
182 dummy->setAttributeNS(NULL,validate,xmlconstants::XML_ONE);
184 g_Config->setServiceProvider(g_Config->ServiceProviderManager.newPlugin(XML_SERVICE_PROVIDER,dummy));
185 g_Config->getServiceProvider()->init();
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 the implementation-specifics for site mappings.
197 ServiceProvider* sp=g_Config->getServiceProvider();
199 const PropertySet* props=sp->getPropertySet("InProcess");
201 pair<bool,const char*> unsetValue=props->getString("unsetHeaderValue");
202 if (unsetValue.first)
203 g_unsetHeaderValue = unsetValue.second;
204 pair<bool,bool> checkSpoofing=props->getBool("checkSpoofing");
205 if (checkSpoofing.first && !checkSpoofing.second)
206 g_checkSpoofing = false;
207 const DOMElement* impl=XMLHelper::getFirstChildElement(props->getElement(),Implementation);
208 if (impl && (impl=XMLHelper::getFirstChildElement(impl,ISAPI))) {
209 const XMLCh* flag=impl->getAttributeNS(NULL,normalizeRequest);
210 g_bNormalizeRequest=(!flag || !*flag || *flag==chDigit_1 || *flag==chLatin_t);
211 impl=XMLHelper::getFirstChildElement(impl,Site);
213 auto_ptr_char id(impl->getAttributeNS(NULL,id));
215 g_Sites.insert(pair<string,site_t>(id.get(),site_t(impl)));
216 impl=XMLHelper::getNextSiblingElement(impl,Site);
221 pVer->dwFilterVersion=HTTP_FILTER_REVISION;
222 strncpy(pVer->lpszFilterDesc,"Shibboleth ISAPI Filter",SF_MAX_FILTER_DESC_LEN);
223 pVer->dwFlags=(SF_NOTIFY_ORDER_HIGH |
224 SF_NOTIFY_SECURE_PORT |
225 SF_NOTIFY_NONSECURE_PORT |
226 SF_NOTIFY_PREPROC_HEADERS |
228 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter initialized...");
232 extern "C" BOOL WINAPI TerminateFilter(DWORD)
237 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter shut down...");
241 /* Next up, some suck-free versions of various APIs.
243 You DON'T require people to guess the buffer size and THEN tell them the right size.
244 Returning an LPCSTR is apparently way beyond their ken. Not to mention the fact that
245 constant strings aren't typed as such, making it just that much harder. These versions
246 are now updated to use a special growable buffer object, modeled after the standard
247 string class. The standard string won't work because they left out the option to
248 pre-allocate a non-constant buffer.
254 dynabuf() { bufptr=NULL; buflen=0; }
255 dynabuf(size_t s) { bufptr=new char[buflen=s]; *bufptr=0; }
256 ~dynabuf() { delete[] bufptr; }
257 size_t length() const { return bufptr ? strlen(bufptr) : 0; }
258 size_t size() const { return buflen; }
259 bool empty() const { return length()==0; }
260 void reserve(size_t s, bool keep=false);
261 void erase() { if (bufptr) memset(bufptr,0,buflen); }
262 operator char*() { return bufptr; }
263 bool operator ==(const char* s) const;
264 bool operator !=(const char* s) const { return !(*this==s); }
270 void dynabuf::reserve(size_t s, bool keep)
277 p[buflen]=bufptr[buflen];
283 bool dynabuf::operator==(const char* s) const
285 if (buflen==NULL || s==NULL)
286 return (buflen==NULL && s==NULL);
288 return strcmp(bufptr,s)==0;
291 void GetServerVariable(PHTTP_FILTER_CONTEXT pfc, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
297 while (!pfc->GetServerVariable(pfc,lpszVariable,s,&size)) {
298 // Grumble. Check the error.
299 DWORD e=GetLastError();
300 if (e==ERROR_INSUFFICIENT_BUFFER)
305 if (bRequired && s.empty())
309 void GetServerVariable(LPEXTENSION_CONTROL_BLOCK lpECB, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
315 while (!lpECB->GetServerVariable(lpECB->ConnID,lpszVariable,s,&size)) {
316 // Grumble. Check the error.
317 DWORD e=GetLastError();
318 if (e==ERROR_INSUFFICIENT_BUFFER)
323 if (bRequired && s.empty())
327 void GetHeader(PHTTP_FILTER_PREPROC_HEADERS pn, PHTTP_FILTER_CONTEXT pfc,
328 LPSTR lpszName, dynabuf& s, DWORD size=80, bool bRequired=true)
334 while (!pn->GetHeader(pfc,lpszName,s,&size)) {
335 // Grumble. Check the error.
336 DWORD e=GetLastError();
337 if (e==ERROR_INSUFFICIENT_BUFFER)
342 if (bRequired && s.empty())
346 /****************************************************************************/
349 class ShibTargetIsapiF : public AbstractSPRequest
351 PHTTP_FILTER_CONTEXT m_pfc;
352 PHTTP_FILTER_PREPROC_HEADERS m_pn;
353 multimap<string,string> m_headers;
355 string m_scheme,m_hostname,m_uri;
356 mutable string m_remote_addr,m_content_type,m_method;
360 ShibTargetIsapiF(PHTTP_FILTER_CONTEXT pfc, PHTTP_FILTER_PREPROC_HEADERS pn, const site_t& site)
361 : m_pfc(pfc), m_pn(pn), m_allhttp(4096) {
363 // URL path always come from IIS.
365 GetHeader(pn,pfc,"url",var,256,false);
368 // Port may come from IIS or from site def.
369 if (!g_bNormalizeRequest || (pfc->fIsSecurePort && site.m_sslport.empty()) || (!pfc->fIsSecurePort && site.m_port.empty())) {
370 GetServerVariable(pfc,"SERVER_PORT",var,10);
373 else if (pfc->fIsSecurePort) {
374 m_port = atoi(site.m_sslport.c_str());
377 m_port = atoi(site.m_port.c_str());
380 // Scheme may come from site def or be derived from IIS.
381 m_scheme=site.m_scheme;
382 if (m_scheme.empty() || !g_bNormalizeRequest)
383 m_scheme=pfc->fIsSecurePort ? "https" : "http";
385 GetServerVariable(pfc,"SERVER_NAME",var,32);
387 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
389 if (site.m_name!=m_hostname && site.m_aliases.find(m_hostname)==site.m_aliases.end())
390 m_hostname=site.m_name;
392 ~ShibTargetIsapiF() { }
394 const char* getScheme() const {
395 return m_scheme.c_str();
397 const char* getHostname() const {
398 return m_hostname.c_str();
400 int getPort() const {
403 const char* getRequestURI() const {
404 return m_uri.c_str();
406 const char* getMethod() const {
407 if (m_method.empty()) {
409 GetServerVariable(m_pfc,"REQUEST_METHOD",var,5,false);
413 return m_method.c_str();
415 string getContentType() const {
416 if (m_content_type.empty()) {
418 GetServerVariable(m_pfc,"CONTENT_TYPE",var,32,false);
420 m_content_type = var;
422 return m_content_type;
424 long getContentLength() const {
427 string getRemoteAddr() const {
428 if (m_remote_addr.empty()) {
430 GetServerVariable(m_pfc,"REMOTE_ADDR",var,16,false);
434 return m_remote_addr;
436 void log(SPLogLevel level, const string& msg) {
437 AbstractSPRequest::log(level,msg);
438 if (level >= SPError)
439 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
441 void clearHeader(const char* rawname, const char* cginame) {
442 if (g_checkSpoofing) {
443 if (m_allhttp.empty())
444 GetServerVariable(m_pfc,"ALL_HTTP",m_allhttp,4096);
445 if (strstr(m_allhttp, cginame))
446 throw opensaml::SecurityPolicyException("Attempt to spoof header ($1) was detected.", params(1, rawname));
448 string hdr(!strcmp(rawname,"REMOTE_USER") ? "remote-user" : rawname);
450 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), const_cast<char*>(g_unsetHeaderValue.c_str()));
452 void setHeader(const char* name, const char* value) {
455 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), const_cast<char*>(value));
457 string getHeader(const char* name) const {
461 GetHeader(m_pn, m_pfc, const_cast<char*>(hdr.c_str()), buf, 256, false);
464 void setRemoteUser(const char* user) {
465 setHeader("remote-user", user);
467 string getRemoteUser() const {
468 return getHeader("remote-user");
470 void setResponseHeader(const char* name, const char* value) {
473 m_headers.insert(make_pair(name,value));
475 m_headers.erase(name);
477 long sendResponse(istream& in, long status) {
478 string hdr = string("Connection: close\r\n");
479 for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
480 hdr += i->first + ": " + i->second + "\r\n";
482 const char* codestr="200 OK";
484 case XMLTOOLING_HTTP_STATUS_FORBIDDEN:codestr="403 Forbidden"; break;
485 case XMLTOOLING_HTTP_STATUS_NOTFOUND: codestr="404 Not Found"; break;
486 case XMLTOOLING_HTTP_STATUS_ERROR: codestr="500 Server Error"; break;
488 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, (void*)codestr, (DWORD)hdr.c_str(), 0);
492 DWORD resplen = in.gcount();
493 m_pfc->WriteClient(m_pfc, buf, &resplen, 0);
495 return SF_STATUS_REQ_FINISHED;
497 long sendRedirect(const char* url) {
498 // XXX: Don't support the httpRedirect option, yet.
499 string hdr=string("Location: ") + url + "\r\n"
500 "Content-Type: text/html\r\n"
501 "Content-Length: 40\r\n"
502 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
503 "Cache-Control: private,no-store,no-cache\r\n";
504 for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
505 hdr += i->first + ": " + i->second + "\r\n";
507 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, "302 Please Wait", (DWORD)hdr.c_str(), 0);
508 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
510 m_pfc->WriteClient(m_pfc, (LPVOID)redmsg, &resplen, 0);
511 return SF_STATUS_REQ_FINISHED;
513 long returnDecline() {
514 return SF_STATUS_REQ_NEXT_NOTIFICATION;
517 return SF_STATUS_REQ_NEXT_NOTIFICATION;
520 const vector<string>& getClientCertificates() const {
524 // The filter never processes the POST, so stub these methods.
525 const char* getQueryString() const { throw IOException("getQueryString not implemented"); }
526 const char* getRequestBody() const { throw IOException("getRequestBody not implemented"); }
529 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const char* msg)
531 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
532 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
533 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)ctype,0);
534 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Filter Error</TITLE></HEAD><BODY>"
535 "<H1>Shibboleth Filter Error</H1>";
536 DWORD resplen=strlen(xmsg);
537 pfc->WriteClient(pfc,(LPVOID)xmsg,&resplen,0);
539 pfc->WriteClient(pfc,(LPVOID)msg,&resplen,0);
540 static const char* xmsg2="</BODY></HTML>";
541 resplen=strlen(xmsg2);
542 pfc->WriteClient(pfc,(LPVOID)xmsg2,&resplen,0);
543 return SF_STATUS_REQ_FINISHED;
546 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
548 // Is this a log notification?
549 if (notificationType==SF_NOTIFY_LOG)
551 if (pfc->pFilterContext)
552 ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=static_cast<LPCSTR>(pfc->pFilterContext);
553 return SF_STATUS_REQ_NEXT_NOTIFICATION;
556 PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
559 // Determine web site number. This can't really fail, I don't think.
561 GetServerVariable(pfc,"INSTANCE_ID",buf,10);
563 // Match site instance to host name, skip if no match.
564 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
565 if (map_i==g_Sites.end())
566 return SF_STATUS_REQ_NEXT_NOTIFICATION;
568 ostringstream threadid;
569 threadid << "[" << getpid() << "] isapi_shib" << '\0';
570 xmltooling::NDC ndc(threadid.str().c_str());
572 ShibTargetIsapiF stf(pfc, pn, map_i->second);
574 // "false" because we don't override the Shib settings
575 pair<bool,long> res = stf.getServiceProvider().doAuthentication(stf);
576 if (res.first) return res.second;
578 // "false" because we don't override the Shib settings
579 res = stf.getServiceProvider().doExport(stf);
580 if (res.first) return res.second;
582 res = stf.getServiceProvider().doAuthorization(stf);
583 if (res.first) return res.second;
585 return SF_STATUS_REQ_NEXT_NOTIFICATION;
588 return WriteClientError(pfc,"Out of Memory");
591 if (e==ERROR_NO_DATA)
592 return WriteClientError(pfc,"A required variable or header was empty.");
594 return WriteClientError(pfc,"Shibboleth Filter detected unexpected IIS error.");
596 catch (exception& e) {
597 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
598 return WriteClientError(pfc,"Shibboleth Filter caught an exception, check Event Log for details.");
602 return WriteClientError(pfc,"Shibboleth Filter caught an unknown exception.");
606 return WriteClientError(pfc,"Shibboleth Filter reached unreachable code, save my walrus!");
610 /****************************************************************************/
613 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* msg)
615 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
616 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
617 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
618 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Error</TITLE></HEAD><BODY><H1>Shibboleth Error</H1>";
619 DWORD resplen=strlen(xmsg);
620 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg,&resplen,HSE_IO_SYNC);
622 lpECB->WriteClient(lpECB->ConnID,(LPVOID)msg,&resplen,HSE_IO_SYNC);
623 static const char* xmsg2="</BODY></HTML>";
624 resplen=strlen(xmsg2);
625 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg2,&resplen,HSE_IO_SYNC);
626 return HSE_STATUS_SUCCESS;
630 class ShibTargetIsapiE : public AbstractSPRequest
632 LPEXTENSION_CONTROL_BLOCK m_lpECB;
633 multimap<string,string> m_headers;
634 mutable vector<string> m_certs;
635 mutable string m_body;
636 mutable bool m_gotBody;
638 string m_scheme,m_hostname,m_uri;
639 mutable string m_remote_addr,m_remote_user;
642 ShibTargetIsapiE(LPEXTENSION_CONTROL_BLOCK lpECB, const site_t& site) : m_lpECB(lpECB), m_gotBody(false) {
644 GetServerVariable(lpECB,"HTTPS",ssl,5);
645 bool SSL=(ssl=="on" || ssl=="ON");
647 // Scheme may come from site def or be derived from IIS.
648 m_scheme=site.m_scheme;
649 if (m_scheme.empty() || !g_bNormalizeRequest)
650 m_scheme = SSL ? "https" : "http";
652 // URL path always come from IIS.
654 GetServerVariable(lpECB,"URL",url,255);
656 // Port may come from IIS or from site def.
658 if (!g_bNormalizeRequest || (SSL && site.m_sslport.empty()) || (!SSL && site.m_port.empty()))
659 GetServerVariable(lpECB,"SERVER_PORT",port,10);
661 strncpy(port,site.m_sslport.c_str(),10);
662 static_cast<char*>(port)[10]=0;
665 strncpy(port,site.m_port.c_str(),10);
666 static_cast<char*>(port)[10]=0;
671 GetServerVariable(lpECB, "SERVER_NAME", var, 32);
673 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
675 if (site.m_name!=m_hostname && site.m_aliases.find(m_hostname)==site.m_aliases.end())
676 m_hostname=site.m_name;
679 * IIS screws us over on PATH_INFO (the hits keep on coming). We need to figure out if
680 * the server is set up for proper PATH_INFO handling, or "IIS sucks rabid weasels mode",
681 * which is the default. No perfect way to tell, but we can take a good guess by checking
682 * whether the URL is a substring of the PATH_INFO:
684 * e.g. for /Shibboleth.sso/SAML/POST
686 * Bad mode (default):
687 * URL: /Shibboleth.sso
688 * PathInfo: /Shibboleth.sso/SAML/POST
691 * URL: /Shibboleth.sso
692 * PathInfo: /SAML/POST
695 // Clearly we're only in bad mode if path info exists at all.
696 if (lpECB->lpszPathInfo && *(lpECB->lpszPathInfo)) {
697 if (strstr(lpECB->lpszPathInfo,url))
698 // Pretty good chance we're in bad mode, unless the PathInfo repeats the path itself.
699 m_uri = lpECB->lpszPathInfo;
702 m_uri += lpECB->lpszPathInfo;
709 // For consistency with Apache, let's add the query string.
710 if (lpECB->lpszQueryString && *(lpECB->lpszQueryString)) {
712 m_uri += lpECB->lpszQueryString;
715 ~ShibTargetIsapiE() { }
717 const char* getScheme() const {
718 return m_scheme.c_str();
720 const char* getHostname() const {
721 return m_hostname.c_str();
723 int getPort() const {
726 const char* getRequestURI() const {
727 return m_uri.c_str();
729 const char* getMethod() const {
730 return m_lpECB->lpszMethod;
732 string getContentType() const {
733 return m_lpECB->lpszContentType ? m_lpECB->lpszContentType : "";
735 long getContentLength() const {
736 return m_lpECB->cbTotalBytes;
738 string getRemoteUser() const {
739 if (m_remote_user.empty()) {
741 GetServerVariable(m_lpECB, "REMOTE_USER", var, 32, false);
745 return m_remote_user;
747 string getRemoteAddr() const {
748 if (m_remote_addr.empty()) {
750 GetServerVariable(m_lpECB, "REMOTE_ADDR", var, 16, false);
754 return m_remote_addr;
756 void log(SPLogLevel level, const string& msg) const {
757 AbstractSPRequest::log(level,msg);
758 if (level >= SPError)
759 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
761 string getHeader(const char* name) const {
763 for (; *name; ++name) {
767 hdr += toupper(*name);
770 GetServerVariable(m_lpECB, const_cast<char*>(hdr.c_str()), buf, 128, false);
771 return buf.empty() ? "" : buf;
773 void setResponseHeader(const char* name, const char* value) {
776 m_headers.insert(make_pair(name,value));
778 m_headers.erase(name);
780 const char* getQueryString() const {
781 return m_lpECB->lpszQueryString;
783 const char* getRequestBody() const {
785 return m_body.c_str();
786 if (m_lpECB->cbTotalBytes > 1024*1024) // 1MB?
787 throw opensaml::SecurityPolicyException("Size of request body exceeded 1M size limit.");
788 else if (m_lpECB->cbTotalBytes > m_lpECB->cbAvailable) {
791 DWORD datalen=m_lpECB->cbTotalBytes;
794 BOOL ret = m_lpECB->ReadClient(m_lpECB->ConnID, buf, &buflen);
796 throw IOException("Error reading request body from browser.");
797 m_body.append(buf, buflen);
801 else if (m_lpECB->cbAvailable) {
803 m_body.assign(reinterpret_cast<char*>(m_lpECB->lpbData),m_lpECB->cbAvailable);
805 return m_body.c_str();
807 long sendResponse(istream& in, long status) {
808 string hdr = string("Connection: close\r\n");
809 for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
810 hdr += i->first + ": " + i->second + "\r\n";
812 const char* codestr="200 OK";
814 case XMLTOOLING_HTTP_STATUS_FORBIDDEN:codestr="403 Forbidden"; break;
815 case XMLTOOLING_HTTP_STATUS_NOTFOUND: codestr="404 Not Found"; break;
816 case XMLTOOLING_HTTP_STATUS_ERROR: codestr="500 Server Error"; break;
818 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, (void*)codestr, 0, (LPDWORD)hdr.c_str());
822 DWORD resplen = in.gcount();
823 m_lpECB->WriteClient(m_lpECB->ConnID, buf, &resplen, HSE_IO_SYNC);
825 return HSE_STATUS_SUCCESS;
827 long sendRedirect(const char* url) {
828 string hdr=string("Location: ") + url + "\r\n"
829 "Content-Type: text/html\r\n"
830 "Content-Length: 40\r\n"
831 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
832 "Cache-Control: private,no-store,no-cache\r\n";
833 for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
834 hdr += i->first + ": " + i->second + "\r\n";
836 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, "302 Moved", 0, (LPDWORD)hdr.c_str());
837 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
839 m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)redmsg, &resplen, HSE_IO_SYNC);
840 return HSE_STATUS_SUCCESS;
842 // Decline happens in the POST processor if this isn't the shire url
843 // Note that it can also happen with HTAccess, but we don't support that, yet.
844 long returnDecline() {
845 return WriteClientError(
847 "ISAPI extension can only be invoked to process Shibboleth protocol requests."
848 "Make sure the mapped file extension doesn't match actual content."
852 return HSE_STATUS_SUCCESS;
855 const vector<string>& getClientCertificates() const {
856 if (m_certs.empty()) {
857 char CertificateBuf[8192];
858 CERT_CONTEXT_EX ccex;
859 ccex.cbAllocated = sizeof(CertificateBuf);
860 ccex.CertContext.pbCertEncoded = (BYTE*)CertificateBuf;
861 DWORD dwSize = sizeof(ccex);
863 if (m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_GET_CERT_INFO_EX, (LPVOID)&ccex, (LPDWORD)dwSize, NULL)) {
864 if (ccex.CertContext.cbCertEncoded) {
866 XMLByte* serialized = Base64::encode(reinterpret_cast<XMLByte*>(CertificateBuf), ccex.CertContext.cbCertEncoded, &outlen);
867 m_certs.push_back(reinterpret_cast<char*>(serialized));
868 XMLString::release(&serialized);
875 // Not used in the extension.
876 void clearHeader(const char* rawname, const char* cginame) { throw runtime_error("clearHeader not implemented"); }
877 void setHeader(const char* name, const char* value) { throw runtime_error("setHeader not implemented"); }
878 void setRemoteUser(const char* user) { throw runtime_error("setRemoteUser not implemented"); }
881 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
884 ostringstream threadid;
885 threadid << "[" << getpid() << "] isapi_shib_extension" << '\0';
886 xmltooling::NDC ndc(threadid.str().c_str());
888 // Determine web site number. This can't really fail, I don't think.
890 GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
892 // Match site instance to host name, skip if no match.
893 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
894 if (map_i==g_Sites.end())
895 return WriteClientError(lpECB, "Shibboleth Extension not configured for this web site.");
897 ShibTargetIsapiE ste(lpECB, map_i->second);
898 pair<bool,long> res = ste.getServiceProvider().doHandler(ste);
899 if (res.first) return res.second;
901 return WriteClientError(lpECB, "Shibboleth Extension failed to process request");
905 return WriteClientError(lpECB,"Out of Memory");
908 if (e==ERROR_NO_DATA)
909 return WriteClientError(lpECB,"A required variable or header was empty.");
911 return WriteClientError(lpECB,"Server detected unexpected IIS error.");
913 catch (exception& e) {
914 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
915 return WriteClientError(lpECB,"Shibboleth Extension caught an exception, check Event Log for details.");
919 return WriteClientError(lpECB,"Shibboleth Extension caught an unknown exception.");
923 // If we get here we've got an error.
924 return HSE_STATUS_ERROR;