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 bool g_catchAll = false;
100 vector<string> g_NoCerts;
104 LPCSTR lpUNCServerName,
110 LPCSTR messages[] = {message, NULL};
112 HANDLE hElog = RegisterEventSource(lpUNCServerName, "Shibboleth ISAPI Filter");
113 BOOL res = ReportEvent(hElog, wType, 0, dwEventID, lpUserSid, 1, 0, messages, NULL);
114 return (DeregisterEventSource(hElog) && res);
117 extern "C" __declspec(dllexport) BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID)
119 if (fdwReason==DLL_PROCESS_ATTACH)
124 extern "C" BOOL WINAPI GetExtensionVersion(HSE_VERSION_INFO* pVer)
130 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
131 "Extension mode startup not possible, is the DLL loaded as a filter?");
135 pVer->dwExtensionVersion=HSE_VERSION;
136 strncpy(pVer->lpszExtensionDesc,"Shibboleth ISAPI Extension",HSE_MAX_EXT_DLL_NAME_LEN-1);
140 extern "C" BOOL WINAPI TerminateExtension(DWORD)
142 return TRUE; // cleanup should happen when filter unloads
145 extern "C" BOOL WINAPI GetFilterVersion(PHTTP_FILTER_VERSION pVer)
150 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
151 "Reentrant filter initialization, ignoring...");
155 LPCSTR schemadir=getenv("SHIBSP_SCHEMAS");
157 schemadir=SHIBSP_SCHEMAS;
158 LPCSTR config=getenv("SHIBSP_CONFIG");
160 config=SHIBSP_CONFIG;
161 g_Config=&SPConfig::getConfig();
162 g_Config->setFeatures(
165 SPConfig::RequestMapping |
166 SPConfig::InProcess |
170 if (!g_Config->init(schemadir)) {
172 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
173 "Filter startup failed during library initialization, check native log for help.");
178 DOMDocument* dummydoc=XMLToolingConfig::getConfig().getParser().newDocument();
179 XercesJanitor<DOMDocument> docjanitor(dummydoc);
180 DOMElement* dummy = dummydoc->createElementNS(NULL,path);
181 auto_ptr_XMLCh src(config);
182 dummy->setAttributeNS(NULL,path,src.get());
183 dummy->setAttributeNS(NULL,validate,xmlconstants::XML_ONE);
185 g_Config->setServiceProvider(g_Config->ServiceProviderManager.newPlugin(XML_SERVICE_PROVIDER,dummy));
186 g_Config->getServiceProvider()->init();
188 catch (exception& ex) {
191 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, ex.what());
192 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
193 "Filter startup failed to load configuration, check native log for details.");
197 // Access the implementation-specifics for site mappings.
198 ServiceProvider* sp=g_Config->getServiceProvider();
200 const PropertySet* props=sp->getPropertySet("InProcess");
202 pair<bool,const char*> unsetValue=props->getString("unsetHeaderValue");
203 if (unsetValue.first)
204 g_unsetHeaderValue = unsetValue.second;
205 pair<bool,bool> flag=props->getBool("checkSpoofing");
206 g_checkSpoofing = !flag.first || flag.second;
207 flag=props->getBool("catchAll");
208 g_catchAll = flag.first && flag.second;
210 const DOMElement* impl=XMLHelper::getFirstChildElement(props->getElement(),Implementation);
211 if (impl && (impl=XMLHelper::getFirstChildElement(impl,ISAPI))) {
212 const XMLCh* flag=impl->getAttributeNS(NULL,normalizeRequest);
213 g_bNormalizeRequest=(!flag || !*flag || *flag==chDigit_1 || *flag==chLatin_t);
214 impl=XMLHelper::getFirstChildElement(impl,Site);
216 auto_ptr_char id(impl->getAttributeNS(NULL,id));
218 g_Sites.insert(pair<string,site_t>(id.get(),site_t(impl)));
219 impl=XMLHelper::getNextSiblingElement(impl,Site);
224 pVer->dwFilterVersion=HTTP_FILTER_REVISION;
225 strncpy(pVer->lpszFilterDesc,"Shibboleth ISAPI Filter",SF_MAX_FILTER_DESC_LEN);
226 pVer->dwFlags=(SF_NOTIFY_ORDER_HIGH |
227 SF_NOTIFY_SECURE_PORT |
228 SF_NOTIFY_NONSECURE_PORT |
229 SF_NOTIFY_PREPROC_HEADERS |
231 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter initialized...");
235 extern "C" BOOL WINAPI TerminateFilter(DWORD)
240 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter shut down...");
244 /* Next up, some suck-free versions of various APIs.
246 You DON'T require people to guess the buffer size and THEN tell them the right size.
247 Returning an LPCSTR is apparently way beyond their ken. Not to mention the fact that
248 constant strings aren't typed as such, making it just that much harder. These versions
249 are now updated to use a special growable buffer object, modeled after the standard
250 string class. The standard string won't work because they left out the option to
251 pre-allocate a non-constant buffer.
257 dynabuf() { bufptr=NULL; buflen=0; }
258 dynabuf(size_t s) { bufptr=new char[buflen=s]; *bufptr=0; }
259 ~dynabuf() { delete[] bufptr; }
260 size_t length() const { return bufptr ? strlen(bufptr) : 0; }
261 size_t size() const { return buflen; }
262 bool empty() const { return length()==0; }
263 void reserve(size_t s, bool keep=false);
264 void erase() { if (bufptr) memset(bufptr,0,buflen); }
265 operator char*() { return bufptr; }
266 bool operator ==(const char* s) const;
267 bool operator !=(const char* s) const { return !(*this==s); }
273 void dynabuf::reserve(size_t s, bool keep)
280 p[buflen]=bufptr[buflen];
286 bool dynabuf::operator==(const char* s) const
288 if (buflen==NULL || s==NULL)
289 return (buflen==NULL && s==NULL);
291 return strcmp(bufptr,s)==0;
294 void GetServerVariable(PHTTP_FILTER_CONTEXT pfc, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
300 while (!pfc->GetServerVariable(pfc,lpszVariable,s,&size)) {
301 // Grumble. Check the error.
302 DWORD e=GetLastError();
303 if (e==ERROR_INSUFFICIENT_BUFFER)
308 if (bRequired && s.empty())
312 void GetServerVariable(LPEXTENSION_CONTROL_BLOCK lpECB, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
318 while (!lpECB->GetServerVariable(lpECB->ConnID,lpszVariable,s,&size)) {
319 // Grumble. Check the error.
320 DWORD e=GetLastError();
321 if (e==ERROR_INSUFFICIENT_BUFFER)
326 if (bRequired && s.empty())
330 void GetHeader(PHTTP_FILTER_PREPROC_HEADERS pn, PHTTP_FILTER_CONTEXT pfc,
331 LPSTR lpszName, dynabuf& s, DWORD size=80, bool bRequired=true)
337 while (!pn->GetHeader(pfc,lpszName,s,&size)) {
338 // Grumble. Check the error.
339 DWORD e=GetLastError();
340 if (e==ERROR_INSUFFICIENT_BUFFER)
345 if (bRequired && s.empty())
349 /****************************************************************************/
352 class ShibTargetIsapiF : public AbstractSPRequest
354 PHTTP_FILTER_CONTEXT m_pfc;
355 PHTTP_FILTER_PREPROC_HEADERS m_pn;
356 multimap<string,string> m_headers;
358 string m_scheme,m_hostname,m_uri;
359 mutable string m_remote_addr,m_content_type,m_method;
363 ShibTargetIsapiF(PHTTP_FILTER_CONTEXT pfc, PHTTP_FILTER_PREPROC_HEADERS pn, const site_t& site)
364 : m_pfc(pfc), m_pn(pn), m_allhttp(4096) {
366 // URL path always come from IIS.
368 GetHeader(pn,pfc,"url",var,256,false);
371 // Port may come from IIS or from site def.
372 if (!g_bNormalizeRequest || (pfc->fIsSecurePort && site.m_sslport.empty()) || (!pfc->fIsSecurePort && site.m_port.empty())) {
373 GetServerVariable(pfc,"SERVER_PORT",var,10);
376 else if (pfc->fIsSecurePort) {
377 m_port = atoi(site.m_sslport.c_str());
380 m_port = atoi(site.m_port.c_str());
383 // Scheme may come from site def or be derived from IIS.
384 m_scheme=site.m_scheme;
385 if (m_scheme.empty() || !g_bNormalizeRequest)
386 m_scheme=pfc->fIsSecurePort ? "https" : "http";
388 GetServerVariable(pfc,"SERVER_NAME",var,32);
390 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
392 if (site.m_name!=m_hostname && site.m_aliases.find(m_hostname)==site.m_aliases.end())
393 m_hostname=site.m_name;
395 ~ShibTargetIsapiF() { }
397 const char* getScheme() const {
398 return m_scheme.c_str();
400 const char* getHostname() const {
401 return m_hostname.c_str();
403 int getPort() const {
406 const char* getRequestURI() const {
407 return m_uri.c_str();
409 const char* getMethod() const {
410 if (m_method.empty()) {
412 GetServerVariable(m_pfc,"REQUEST_METHOD",var,5,false);
416 return m_method.c_str();
418 string getContentType() const {
419 if (m_content_type.empty()) {
421 GetServerVariable(m_pfc,"CONTENT_TYPE",var,32,false);
423 m_content_type = var;
425 return m_content_type;
427 long getContentLength() const {
430 string getRemoteAddr() const {
431 if (m_remote_addr.empty()) {
433 GetServerVariable(m_pfc,"REMOTE_ADDR",var,16,false);
437 return m_remote_addr;
439 void log(SPLogLevel level, const string& msg) {
440 AbstractSPRequest::log(level,msg);
441 if (level >= SPError)
442 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
444 void clearHeader(const char* rawname, const char* cginame) {
445 if (g_checkSpoofing) {
446 if (m_allhttp.empty())
447 GetServerVariable(m_pfc,"ALL_HTTP",m_allhttp,4096);
448 if (strstr(m_allhttp, cginame))
449 throw opensaml::SecurityPolicyException("Attempt to spoof header ($1) was detected.", params(1, rawname));
451 string hdr(!strcmp(rawname,"REMOTE_USER") ? "remote-user" : rawname);
453 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), const_cast<char*>(g_unsetHeaderValue.c_str()));
455 void setHeader(const char* name, const char* value) {
458 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), const_cast<char*>(value));
460 string getHeader(const char* name) const {
464 GetHeader(m_pn, m_pfc, const_cast<char*>(hdr.c_str()), buf, 256, false);
467 void setRemoteUser(const char* user) {
468 setHeader("remote-user", user);
470 string getRemoteUser() const {
471 return getHeader("remote-user");
473 void setResponseHeader(const char* name, const char* value) {
476 m_headers.insert(make_pair(name,value));
478 m_headers.erase(name);
480 long sendResponse(istream& in, long status) {
481 string hdr = string("Connection: close\r\n");
482 for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
483 hdr += i->first + ": " + i->second + "\r\n";
485 const char* codestr="200 OK";
487 case XMLTOOLING_HTTP_STATUS_FORBIDDEN:codestr="403 Forbidden"; break;
488 case XMLTOOLING_HTTP_STATUS_NOTFOUND: codestr="404 Not Found"; break;
489 case XMLTOOLING_HTTP_STATUS_ERROR: codestr="500 Server Error"; break;
491 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, (void*)codestr, (DWORD)hdr.c_str(), 0);
495 DWORD resplen = in.gcount();
496 m_pfc->WriteClient(m_pfc, buf, &resplen, 0);
498 return SF_STATUS_REQ_FINISHED;
500 long sendRedirect(const char* url) {
501 // XXX: Don't support the httpRedirect option, yet.
502 string hdr=string("Location: ") + url + "\r\n"
503 "Content-Type: text/html\r\n"
504 "Content-Length: 40\r\n"
505 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
506 "Cache-Control: private,no-store,no-cache\r\n";
507 for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
508 hdr += i->first + ": " + i->second + "\r\n";
510 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, "302 Please Wait", (DWORD)hdr.c_str(), 0);
511 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
513 m_pfc->WriteClient(m_pfc, (LPVOID)redmsg, &resplen, 0);
514 return SF_STATUS_REQ_FINISHED;
516 long returnDecline() {
517 return SF_STATUS_REQ_NEXT_NOTIFICATION;
520 return SF_STATUS_REQ_NEXT_NOTIFICATION;
523 const vector<string>& getClientCertificates() const {
527 // The filter never processes the POST, so stub these methods.
528 const char* getQueryString() const { throw IOException("getQueryString not implemented"); }
529 const char* getRequestBody() const { throw IOException("getRequestBody not implemented"); }
532 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const char* msg)
534 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
535 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
536 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)ctype,0);
537 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Filter Error</TITLE></HEAD><BODY>"
538 "<H1>Shibboleth Filter Error</H1>";
539 DWORD resplen=strlen(xmsg);
540 pfc->WriteClient(pfc,(LPVOID)xmsg,&resplen,0);
542 pfc->WriteClient(pfc,(LPVOID)msg,&resplen,0);
543 static const char* xmsg2="</BODY></HTML>";
544 resplen=strlen(xmsg2);
545 pfc->WriteClient(pfc,(LPVOID)xmsg2,&resplen,0);
546 return SF_STATUS_REQ_FINISHED;
549 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
551 // Is this a log notification?
552 if (notificationType==SF_NOTIFY_LOG)
554 if (pfc->pFilterContext)
555 ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=static_cast<LPCSTR>(pfc->pFilterContext);
556 return SF_STATUS_REQ_NEXT_NOTIFICATION;
559 PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
562 // Determine web site number. This can't really fail, I don't think.
564 GetServerVariable(pfc,"INSTANCE_ID",buf,10);
566 // Match site instance to host name, skip if no match.
567 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
568 if (map_i==g_Sites.end())
569 return SF_STATUS_REQ_NEXT_NOTIFICATION;
571 ostringstream threadid;
572 threadid << "[" << getpid() << "] isapi_shib" << '\0';
573 xmltooling::NDC ndc(threadid.str().c_str());
575 ShibTargetIsapiF stf(pfc, pn, map_i->second);
577 // "false" because we don't override the Shib settings
578 pair<bool,long> res = stf.getServiceProvider().doAuthentication(stf);
579 if (res.first) return res.second;
581 // "false" because we don't override the Shib settings
582 res = stf.getServiceProvider().doExport(stf);
583 if (res.first) return res.second;
585 res = stf.getServiceProvider().doAuthorization(stf);
586 if (res.first) return res.second;
588 return SF_STATUS_REQ_NEXT_NOTIFICATION;
591 return WriteClientError(pfc,"Out of Memory");
594 if (e==ERROR_NO_DATA)
595 return WriteClientError(pfc,"A required variable or header was empty.");
597 return WriteClientError(pfc,"Shibboleth Filter detected unexpected IIS error.");
599 catch (exception& e) {
600 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
601 return WriteClientError(pfc,"Shibboleth Filter caught an exception, check Event Log for details.");
605 return WriteClientError(pfc,"Shibboleth Filter caught an unknown exception.");
609 return WriteClientError(pfc,"Shibboleth Filter reached unreachable code, save my walrus!");
613 /****************************************************************************/
616 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* msg)
618 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
619 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
620 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
621 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Error</TITLE></HEAD><BODY><H1>Shibboleth Error</H1>";
622 DWORD resplen=strlen(xmsg);
623 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg,&resplen,HSE_IO_SYNC);
625 lpECB->WriteClient(lpECB->ConnID,(LPVOID)msg,&resplen,HSE_IO_SYNC);
626 static const char* xmsg2="</BODY></HTML>";
627 resplen=strlen(xmsg2);
628 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg2,&resplen,HSE_IO_SYNC);
629 return HSE_STATUS_SUCCESS;
633 class ShibTargetIsapiE : public AbstractSPRequest
635 LPEXTENSION_CONTROL_BLOCK m_lpECB;
636 multimap<string,string> m_headers;
637 mutable vector<string> m_certs;
638 mutable string m_body;
639 mutable bool m_gotBody;
641 string m_scheme,m_hostname,m_uri;
642 mutable string m_remote_addr,m_remote_user;
645 ShibTargetIsapiE(LPEXTENSION_CONTROL_BLOCK lpECB, const site_t& site) : m_lpECB(lpECB), m_gotBody(false) {
647 GetServerVariable(lpECB,"HTTPS",ssl,5);
648 bool SSL=(ssl=="on" || ssl=="ON");
650 // Scheme may come from site def or be derived from IIS.
651 m_scheme=site.m_scheme;
652 if (m_scheme.empty() || !g_bNormalizeRequest)
653 m_scheme = SSL ? "https" : "http";
655 // URL path always come from IIS.
657 GetServerVariable(lpECB,"URL",url,255);
659 // Port may come from IIS or from site def.
661 if (!g_bNormalizeRequest || (SSL && site.m_sslport.empty()) || (!SSL && site.m_port.empty()))
662 GetServerVariable(lpECB,"SERVER_PORT",port,10);
664 strncpy(port,site.m_sslport.c_str(),10);
665 static_cast<char*>(port)[10]=0;
668 strncpy(port,site.m_port.c_str(),10);
669 static_cast<char*>(port)[10]=0;
674 GetServerVariable(lpECB, "SERVER_NAME", var, 32);
676 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
678 if (site.m_name!=m_hostname && site.m_aliases.find(m_hostname)==site.m_aliases.end())
679 m_hostname=site.m_name;
682 * IIS screws us over on PATH_INFO (the hits keep on coming). We need to figure out if
683 * the server is set up for proper PATH_INFO handling, or "IIS sucks rabid weasels mode",
684 * which is the default. No perfect way to tell, but we can take a good guess by checking
685 * whether the URL is a substring of the PATH_INFO:
687 * e.g. for /Shibboleth.sso/SAML/POST
689 * Bad mode (default):
690 * URL: /Shibboleth.sso
691 * PathInfo: /Shibboleth.sso/SAML/POST
694 * URL: /Shibboleth.sso
695 * PathInfo: /SAML/POST
698 // Clearly we're only in bad mode if path info exists at all.
699 if (lpECB->lpszPathInfo && *(lpECB->lpszPathInfo)) {
700 if (strstr(lpECB->lpszPathInfo,url))
701 // Pretty good chance we're in bad mode, unless the PathInfo repeats the path itself.
702 m_uri = lpECB->lpszPathInfo;
705 m_uri += lpECB->lpszPathInfo;
712 // For consistency with Apache, let's add the query string.
713 if (lpECB->lpszQueryString && *(lpECB->lpszQueryString)) {
715 m_uri += lpECB->lpszQueryString;
718 ~ShibTargetIsapiE() { }
720 const char* getScheme() const {
721 return m_scheme.c_str();
723 const char* getHostname() const {
724 return m_hostname.c_str();
726 int getPort() const {
729 const char* getRequestURI() const {
730 return m_uri.c_str();
732 const char* getMethod() const {
733 return m_lpECB->lpszMethod;
735 string getContentType() const {
736 return m_lpECB->lpszContentType ? m_lpECB->lpszContentType : "";
738 long getContentLength() const {
739 return m_lpECB->cbTotalBytes;
741 string getRemoteUser() const {
742 if (m_remote_user.empty()) {
744 GetServerVariable(m_lpECB, "REMOTE_USER", var, 32, false);
748 return m_remote_user;
750 string getRemoteAddr() const {
751 if (m_remote_addr.empty()) {
753 GetServerVariable(m_lpECB, "REMOTE_ADDR", var, 16, false);
757 return m_remote_addr;
759 void log(SPLogLevel level, const string& msg) const {
760 AbstractSPRequest::log(level,msg);
761 if (level >= SPError)
762 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
764 string getHeader(const char* name) const {
766 for (; *name; ++name) {
770 hdr += toupper(*name);
773 GetServerVariable(m_lpECB, const_cast<char*>(hdr.c_str()), buf, 128, false);
774 return buf.empty() ? "" : buf;
776 void setResponseHeader(const char* name, const char* value) {
779 m_headers.insert(make_pair(name,value));
781 m_headers.erase(name);
783 const char* getQueryString() const {
784 return m_lpECB->lpszQueryString;
786 const char* getRequestBody() const {
788 return m_body.c_str();
789 if (m_lpECB->cbTotalBytes > 1024*1024) // 1MB?
790 throw opensaml::SecurityPolicyException("Size of request body exceeded 1M size limit.");
791 else if (m_lpECB->cbTotalBytes > m_lpECB->cbAvailable) {
794 DWORD datalen=m_lpECB->cbTotalBytes;
797 BOOL ret = m_lpECB->ReadClient(m_lpECB->ConnID, buf, &buflen);
799 throw IOException("Error reading request body from browser.");
800 m_body.append(buf, buflen);
804 else if (m_lpECB->cbAvailable) {
806 m_body.assign(reinterpret_cast<char*>(m_lpECB->lpbData),m_lpECB->cbAvailable);
808 return m_body.c_str();
810 long sendResponse(istream& in, long status) {
811 string hdr = string("Connection: close\r\n");
812 for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
813 hdr += i->first + ": " + i->second + "\r\n";
815 const char* codestr="200 OK";
817 case XMLTOOLING_HTTP_STATUS_FORBIDDEN:codestr="403 Forbidden"; break;
818 case XMLTOOLING_HTTP_STATUS_NOTFOUND: codestr="404 Not Found"; break;
819 case XMLTOOLING_HTTP_STATUS_ERROR: codestr="500 Server Error"; break;
821 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, (void*)codestr, 0, (LPDWORD)hdr.c_str());
825 DWORD resplen = in.gcount();
826 m_lpECB->WriteClient(m_lpECB->ConnID, buf, &resplen, HSE_IO_SYNC);
828 return HSE_STATUS_SUCCESS;
830 long sendRedirect(const char* url) {
831 string hdr=string("Location: ") + url + "\r\n"
832 "Content-Type: text/html\r\n"
833 "Content-Length: 40\r\n"
834 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
835 "Cache-Control: private,no-store,no-cache\r\n";
836 for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
837 hdr += i->first + ": " + i->second + "\r\n";
839 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, "302 Moved", 0, (LPDWORD)hdr.c_str());
840 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
842 m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)redmsg, &resplen, HSE_IO_SYNC);
843 return HSE_STATUS_SUCCESS;
845 // Decline happens in the POST processor if this isn't the shire url
846 // Note that it can also happen with HTAccess, but we don't support that, yet.
847 long returnDecline() {
848 return WriteClientError(
850 "ISAPI extension can only be invoked to process Shibboleth protocol requests."
851 "Make sure the mapped file extension doesn't match actual content."
855 return HSE_STATUS_SUCCESS;
858 const vector<string>& getClientCertificates() const {
859 if (m_certs.empty()) {
860 char CertificateBuf[8192];
861 CERT_CONTEXT_EX ccex;
862 ccex.cbAllocated = sizeof(CertificateBuf);
863 ccex.CertContext.pbCertEncoded = (BYTE*)CertificateBuf;
864 DWORD dwSize = sizeof(ccex);
866 if (m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_GET_CERT_INFO_EX, (LPVOID)&ccex, (LPDWORD)dwSize, NULL)) {
867 if (ccex.CertContext.cbCertEncoded) {
869 XMLByte* serialized = Base64::encode(reinterpret_cast<XMLByte*>(CertificateBuf), ccex.CertContext.cbCertEncoded, &outlen);
870 m_certs.push_back(reinterpret_cast<char*>(serialized));
871 XMLString::release(&serialized);
878 // Not used in the extension.
879 void clearHeader(const char* rawname, const char* cginame) { throw runtime_error("clearHeader not implemented"); }
880 void setHeader(const char* name, const char* value) { throw runtime_error("setHeader not implemented"); }
881 void setRemoteUser(const char* user) { throw runtime_error("setRemoteUser not implemented"); }
884 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
887 ostringstream threadid;
888 threadid << "[" << getpid() << "] isapi_shib_extension" << '\0';
889 xmltooling::NDC ndc(threadid.str().c_str());
891 // Determine web site number. This can't really fail, I don't think.
893 GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
895 // Match site instance to host name, skip if no match.
896 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
897 if (map_i==g_Sites.end())
898 return WriteClientError(lpECB, "Shibboleth Extension not configured for this web site.");
900 ShibTargetIsapiE ste(lpECB, map_i->second);
901 pair<bool,long> res = ste.getServiceProvider().doHandler(ste);
902 if (res.first) return res.second;
904 return WriteClientError(lpECB, "Shibboleth Extension failed to process request");
908 return WriteClientError(lpECB,"Out of Memory");
911 if (e==ERROR_NO_DATA)
912 return WriteClientError(lpECB,"A required variable or header was empty.");
914 return WriteClientError(lpECB,"Server detected unexpected IIS error.");
916 catch (exception& e) {
917 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
918 return WriteClientError(lpECB,"Shibboleth Extension caught an exception, check Event Log for details.");
922 return WriteClientError(lpECB,"Shibboleth Extension caught an unknown exception.");
926 // If we get here we've got an error.
927 return HSE_STATUS_ERROR;