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;
93 SPConfig* g_Config = NULL;
94 map<string,site_t> g_Sites;
95 bool g_bNormalizeRequest = true;
96 string g_unsetHeaderValue;
97 bool g_checkSpoofing = true;
98 bool g_catchAll = false;
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 implementation-specifics and 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> flag=props->getBool("checkSpoofing");
205 g_checkSpoofing = !flag.first || flag.second;
206 flag=props->getBool("catchAll");
207 g_catchAll = flag.first && flag.second;
209 props = props->getPropertySet("ISAPI");
211 flag = props->getBool("normalizeRequest");
212 g_bNormalizeRequest = !flag.first || flag.second;
213 const DOMElement* child = XMLHelper::getFirstChildElement(props->getElement(),Site);
215 auto_ptr_char id(child->getAttributeNS(NULL,id));
217 g_Sites.insert(pair<string,site_t>(id.get(),site_t(child)));
218 child=XMLHelper::getNextSiblingElement(child,Site);
223 pVer->dwFilterVersion=HTTP_FILTER_REVISION;
224 strncpy(pVer->lpszFilterDesc,"Shibboleth ISAPI Filter",SF_MAX_FILTER_DESC_LEN);
225 pVer->dwFlags=(SF_NOTIFY_ORDER_HIGH |
226 SF_NOTIFY_SECURE_PORT |
227 SF_NOTIFY_NONSECURE_PORT |
228 SF_NOTIFY_PREPROC_HEADERS |
230 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter initialized...");
234 extern "C" BOOL WINAPI TerminateFilter(DWORD)
239 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter shut down...");
243 /* Next up, some suck-free versions of various APIs.
245 You DON'T require people to guess the buffer size and THEN tell them the right size.
246 Returning an LPCSTR is apparently way beyond their ken. Not to mention the fact that
247 constant strings aren't typed as such, making it just that much harder. These versions
248 are now updated to use a special growable buffer object, modeled after the standard
249 string class. The standard string won't work because they left out the option to
250 pre-allocate a non-constant buffer.
256 dynabuf() { bufptr=NULL; buflen=0; }
257 dynabuf(size_t s) { bufptr=new char[buflen=s]; *bufptr=0; }
258 ~dynabuf() { delete[] bufptr; }
259 size_t length() const { return bufptr ? strlen(bufptr) : 0; }
260 size_t size() const { return buflen; }
261 bool empty() const { return length()==0; }
262 void reserve(size_t s, bool keep=false);
263 void erase() { if (bufptr) memset(bufptr,0,buflen); }
264 operator char*() { return bufptr; }
265 bool operator ==(const char* s) const;
266 bool operator !=(const char* s) const { return !(*this==s); }
272 void dynabuf::reserve(size_t s, bool keep)
279 p[buflen]=bufptr[buflen];
285 bool dynabuf::operator==(const char* s) const
287 if (buflen==NULL || s==NULL)
288 return (buflen==NULL && s==NULL);
290 return strcmp(bufptr,s)==0;
293 void GetServerVariable(PHTTP_FILTER_CONTEXT pfc, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
299 while (!pfc->GetServerVariable(pfc,lpszVariable,s,&size)) {
300 // Grumble. Check the error.
301 DWORD e=GetLastError();
302 if (e==ERROR_INSUFFICIENT_BUFFER)
307 if (bRequired && s.empty())
311 void GetServerVariable(LPEXTENSION_CONTROL_BLOCK lpECB, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
317 while (!lpECB->GetServerVariable(lpECB->ConnID,lpszVariable,s,&size)) {
318 // Grumble. Check the error.
319 DWORD e=GetLastError();
320 if (e==ERROR_INSUFFICIENT_BUFFER)
325 if (bRequired && s.empty())
329 void GetHeader(PHTTP_FILTER_PREPROC_HEADERS pn, PHTTP_FILTER_CONTEXT pfc,
330 LPSTR lpszName, dynabuf& s, DWORD size=80, bool bRequired=true)
336 while (!pn->GetHeader(pfc,lpszName,s,&size)) {
337 // Grumble. Check the error.
338 DWORD e=GetLastError();
339 if (e==ERROR_INSUFFICIENT_BUFFER)
344 if (bRequired && s.empty())
348 /****************************************************************************/
351 class ShibTargetIsapiF : public AbstractSPRequest
353 PHTTP_FILTER_CONTEXT m_pfc;
354 PHTTP_FILTER_PREPROC_HEADERS m_pn;
355 multimap<string,string> m_headers;
357 string m_scheme,m_hostname;
358 mutable string m_remote_addr,m_content_type,m_method;
362 ShibTargetIsapiF(PHTTP_FILTER_CONTEXT pfc, PHTTP_FILTER_PREPROC_HEADERS pn, const site_t& site)
363 : AbstractSPRequest(SHIBSP_LOGCAT".ISAPI"), m_pfc(pfc), m_pn(pn), m_allhttp(4096) {
365 // URL path always come from IIS.
367 GetHeader(pn,pfc,"url",var,256,false);
370 // Port may come from IIS or from site def.
371 if (!g_bNormalizeRequest || (pfc->fIsSecurePort && site.m_sslport.empty()) || (!pfc->fIsSecurePort && site.m_port.empty())) {
372 GetServerVariable(pfc,"SERVER_PORT",var,10);
375 else if (pfc->fIsSecurePort) {
376 m_port = atoi(site.m_sslport.c_str());
379 m_port = atoi(site.m_port.c_str());
382 // Scheme may come from site def or be derived from IIS.
383 m_scheme=site.m_scheme;
384 if (m_scheme.empty() || !g_bNormalizeRequest)
385 m_scheme=pfc->fIsSecurePort ? "https" : "http";
387 GetServerVariable(pfc,"SERVER_NAME",var,32);
389 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
391 if (site.m_name!=m_hostname && site.m_aliases.find(m_hostname)==site.m_aliases.end())
392 m_hostname=site.m_name;
394 ~ShibTargetIsapiF() { }
396 const char* getScheme() const {
397 return m_scheme.c_str();
399 const char* getHostname() const {
400 return m_hostname.c_str();
402 int getPort() const {
405 const char* getMethod() const {
406 if (m_method.empty()) {
408 GetServerVariable(m_pfc,"REQUEST_METHOD",var,5,false);
412 return m_method.c_str();
414 string getContentType() const {
415 if (m_content_type.empty()) {
417 GetServerVariable(m_pfc,"CONTENT_TYPE",var,32,false);
419 m_content_type = var;
421 return m_content_type;
423 long getContentLength() const {
426 string getRemoteAddr() const {
427 if (m_remote_addr.empty()) {
429 GetServerVariable(m_pfc,"REMOTE_ADDR",var,16,false);
433 return m_remote_addr;
435 void log(SPLogLevel level, const string& msg) {
436 AbstractSPRequest::log(level,msg);
437 if (level >= SPError)
438 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
440 void clearHeader(const char* rawname, const char* cginame) {
441 if (g_checkSpoofing) {
442 if (m_allhttp.empty())
443 GetServerVariable(m_pfc,"ALL_HTTP",m_allhttp,4096);
444 if (strstr(m_allhttp, cginame))
445 throw opensaml::SecurityPolicyException("Attempt to spoof header ($1) was detected.", params(1, rawname));
447 string hdr(!strcmp(rawname,"REMOTE_USER") ? "remote-user" : rawname);
449 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), const_cast<char*>(g_unsetHeaderValue.c_str()));
451 void setHeader(const char* name, const char* value) {
454 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), const_cast<char*>(value));
456 string getHeader(const char* name) const {
460 GetHeader(m_pn, m_pfc, const_cast<char*>(hdr.c_str()), buf, 256, false);
463 void setRemoteUser(const char* user) {
464 setHeader("remote-user", user);
466 string getRemoteUser() const {
467 return getHeader("remote-user");
469 void setResponseHeader(const char* name, const char* value) {
472 m_headers.insert(make_pair(name,value));
474 m_headers.erase(name);
476 long sendResponse(istream& in, long status) {
477 string hdr = string("Connection: close\r\n");
478 for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
479 hdr += i->first + ": " + i->second + "\r\n";
481 const char* codestr="200 OK";
483 case XMLTOOLING_HTTP_STATUS_UNAUTHORIZED: codestr="401 Authorization Required"; break;
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.");
601 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Shibboleth Filter threw an unknown exception.");
603 return WriteClientError(pfc,"Shibboleth Filter threw an unknown exception.");
607 return WriteClientError(pfc,"Shibboleth Filter reached unreachable code, save my walrus!");
611 /****************************************************************************/
614 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* msg)
616 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
617 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
618 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
619 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Error</TITLE></HEAD><BODY><H1>Shibboleth Error</H1>";
620 DWORD resplen=strlen(xmsg);
621 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg,&resplen,HSE_IO_SYNC);
623 lpECB->WriteClient(lpECB->ConnID,(LPVOID)msg,&resplen,HSE_IO_SYNC);
624 static const char* xmsg2="</BODY></HTML>";
625 resplen=strlen(xmsg2);
626 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg2,&resplen,HSE_IO_SYNC);
627 return HSE_STATUS_SUCCESS;
631 class ShibTargetIsapiE : public AbstractSPRequest
633 LPEXTENSION_CONTROL_BLOCK m_lpECB;
634 multimap<string,string> m_headers;
635 mutable vector<string> m_certs;
636 mutable string m_body;
637 mutable bool m_gotBody;
639 string m_scheme,m_hostname,m_uri;
640 mutable string m_remote_addr,m_remote_user;
643 ShibTargetIsapiE(LPEXTENSION_CONTROL_BLOCK lpECB, const site_t& site)
644 : AbstractSPRequest(SHIBSP_LOGCAT".ISAPI"), m_lpECB(lpECB), m_gotBody(false) {
646 GetServerVariable(lpECB,"HTTPS",ssl,5);
647 bool SSL=(ssl=="on" || ssl=="ON");
649 // Scheme may come from site def or be derived from IIS.
650 m_scheme=site.m_scheme;
651 if (m_scheme.empty() || !g_bNormalizeRequest)
652 m_scheme = SSL ? "https" : "http";
654 // URL path always come from IIS.
656 GetServerVariable(lpECB,"URL",url,255);
658 // Port may come from IIS or from site def.
660 if (!g_bNormalizeRequest || (SSL && site.m_sslport.empty()) || (!SSL && site.m_port.empty()))
661 GetServerVariable(lpECB,"SERVER_PORT",port,10);
663 strncpy(port,site.m_sslport.c_str(),10);
664 static_cast<char*>(port)[10]=0;
667 strncpy(port,site.m_port.c_str(),10);
668 static_cast<char*>(port)[10]=0;
673 GetServerVariable(lpECB, "SERVER_NAME", var, 32);
675 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
677 if (site.m_name!=m_hostname && site.m_aliases.find(m_hostname)==site.m_aliases.end())
678 m_hostname=site.m_name;
681 * IIS screws us over on PATH_INFO (the hits keep on coming). We need to figure out if
682 * the server is set up for proper PATH_INFO handling, or "IIS sucks rabid weasels mode",
683 * which is the default. No perfect way to tell, but we can take a good guess by checking
684 * whether the URL is a substring of the PATH_INFO:
686 * e.g. for /Shibboleth.sso/SAML/POST
688 * Bad mode (default):
689 * URL: /Shibboleth.sso
690 * PathInfo: /Shibboleth.sso/SAML/POST
693 * URL: /Shibboleth.sso
694 * PathInfo: /SAML/POST
699 // Clearly we're only in bad mode if path info exists at all.
700 if (lpECB->lpszPathInfo && *(lpECB->lpszPathInfo)) {
701 if (strstr(lpECB->lpszPathInfo,url))
702 // Pretty good chance we're in bad mode, unless the PathInfo repeats the path itself.
703 uri = lpECB->lpszPathInfo;
706 uri += lpECB->lpszPathInfo;
713 // For consistency with Apache, let's add the query string.
714 if (lpECB->lpszQueryString && *(lpECB->lpszQueryString)) {
716 uri += lpECB->lpszQueryString;
719 setRequestURI(uri.c_str());
721 ~ShibTargetIsapiE() { }
723 const char* getScheme() const {
724 return m_scheme.c_str();
726 const char* getHostname() const {
727 return m_hostname.c_str();
729 int getPort() const {
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_UNAUTHORIZED: codestr="401 Authorization Required"; break;
818 case XMLTOOLING_HTTP_STATUS_FORBIDDEN: codestr="403 Forbidden"; break;
819 case XMLTOOLING_HTTP_STATUS_NOTFOUND: codestr="404 Not Found"; break;
820 case XMLTOOLING_HTTP_STATUS_ERROR: codestr="500 Server Error"; break;
822 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, (void*)codestr, 0, (LPDWORD)hdr.c_str());
826 DWORD resplen = in.gcount();
827 m_lpECB->WriteClient(m_lpECB->ConnID, buf, &resplen, HSE_IO_SYNC);
829 return HSE_STATUS_SUCCESS;
831 long sendRedirect(const char* url) {
832 string hdr=string("Location: ") + url + "\r\n"
833 "Content-Type: text/html\r\n"
834 "Content-Length: 40\r\n"
835 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
836 "Cache-Control: private,no-store,no-cache\r\n";
837 for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
838 hdr += i->first + ": " + i->second + "\r\n";
840 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, "302 Moved", 0, (LPDWORD)hdr.c_str());
841 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
843 m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)redmsg, &resplen, HSE_IO_SYNC);
844 return HSE_STATUS_SUCCESS;
846 // Decline happens in the POST processor if this isn't the shire url
847 // Note that it can also happen with HTAccess, but we don't support that, yet.
848 long returnDecline() {
849 return WriteClientError(
851 "ISAPI extension can only be invoked to process Shibboleth protocol requests."
852 "Make sure the mapped file extension doesn't match actual content."
856 return HSE_STATUS_SUCCESS;
859 const vector<string>& getClientCertificates() const {
860 if (m_certs.empty()) {
861 char CertificateBuf[8192];
862 CERT_CONTEXT_EX ccex;
863 ccex.cbAllocated = sizeof(CertificateBuf);
864 ccex.CertContext.pbCertEncoded = (BYTE*)CertificateBuf;
865 DWORD dwSize = sizeof(ccex);
867 if (m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_GET_CERT_INFO_EX, (LPVOID)&ccex, (LPDWORD)dwSize, NULL)) {
868 if (ccex.CertContext.cbCertEncoded) {
870 XMLByte* serialized = Base64::encode(reinterpret_cast<XMLByte*>(CertificateBuf), ccex.CertContext.cbCertEncoded, &outlen);
871 m_certs.push_back(reinterpret_cast<char*>(serialized));
872 XMLString::release(&serialized);
879 // Not used in the extension.
880 void clearHeader(const char* rawname, const char* cginame) { throw runtime_error("clearHeader not implemented"); }
881 void setHeader(const char* name, const char* value) { throw runtime_error("setHeader not implemented"); }
882 void setRemoteUser(const char* user) { throw runtime_error("setRemoteUser not implemented"); }
885 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
888 ostringstream threadid;
889 threadid << "[" << getpid() << "] isapi_shib_extension" << '\0';
890 xmltooling::NDC ndc(threadid.str().c_str());
892 // Determine web site number. This can't really fail, I don't think.
894 GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
896 // Match site instance to host name, skip if no match.
897 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
898 if (map_i==g_Sites.end())
899 return WriteClientError(lpECB, "Shibboleth Extension not configured for this web site.");
901 ShibTargetIsapiE ste(lpECB, map_i->second);
902 pair<bool,long> res = ste.getServiceProvider().doHandler(ste);
903 if (res.first) return res.second;
905 return WriteClientError(lpECB, "Shibboleth Extension failed to process request");
909 return WriteClientError(lpECB,"Out of Memory");
912 if (e==ERROR_NO_DATA)
913 return WriteClientError(lpECB,"A required variable or header was empty.");
915 return WriteClientError(lpECB,"Server detected unexpected IIS error.");
917 catch (exception& e) {
918 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
919 return WriteClientError(lpECB,"Shibboleth Extension caught an exception, check Event Log for details.");
922 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Shibboleth Extension threw an unknown exception.");
924 return WriteClientError(lpECB,"Shibboleth Extension threw an unknown exception.");
928 // If we get here we've got an error.
929 return HSE_STATUS_ERROR;