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 g_Config=&SPConfig::getConfig();
155 g_Config->setFeatures(
158 SPConfig::RequestMapping |
159 SPConfig::InProcess |
163 if (!g_Config->init()) {
165 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
166 "Filter startup failed during library initialization, check native log for help.");
170 LPCSTR config=getenv("SHIBSP_CONFIG");
172 config=SHIBSP_CONFIG;
175 DOMDocument* dummydoc=XMLToolingConfig::getConfig().getParser().newDocument();
176 XercesJanitor<DOMDocument> docjanitor(dummydoc);
177 DOMElement* dummy = dummydoc->createElementNS(NULL,path);
178 auto_ptr_XMLCh src(config);
179 dummy->setAttributeNS(NULL,path,src.get());
180 dummy->setAttributeNS(NULL,validate,xmlconstants::XML_ONE);
182 g_Config->setServiceProvider(g_Config->ServiceProviderManager.newPlugin(XML_SERVICE_PROVIDER,dummy));
183 g_Config->getServiceProvider()->init();
185 catch (exception& ex) {
188 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, ex.what());
189 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
190 "Filter startup failed to load configuration, check native log for details.");
194 // Access implementation-specifics and site mappings.
195 ServiceProvider* sp=g_Config->getServiceProvider();
197 const PropertySet* props=sp->getPropertySet("InProcess");
199 pair<bool,const char*> unsetValue=props->getString("unsetHeaderValue");
200 if (unsetValue.first)
201 g_unsetHeaderValue = unsetValue.second;
202 pair<bool,bool> flag=props->getBool("checkSpoofing");
203 g_checkSpoofing = !flag.first || flag.second;
204 flag=props->getBool("catchAll");
205 g_catchAll = flag.first && flag.second;
207 props = props->getPropertySet("ISAPI");
209 flag = props->getBool("normalizeRequest");
210 g_bNormalizeRequest = !flag.first || flag.second;
211 const DOMElement* child = XMLHelper::getFirstChildElement(props->getElement(),Site);
213 auto_ptr_char id(child->getAttributeNS(NULL,id));
215 g_Sites.insert(pair<string,site_t>(id.get(),site_t(child)));
216 child=XMLHelper::getNextSiblingElement(child,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;
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 : AbstractSPRequest(SHIBSP_LOGCAT".ISAPI"), 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* getMethod() const {
404 if (m_method.empty()) {
406 GetServerVariable(m_pfc,"REQUEST_METHOD",var,5,false);
410 return m_method.c_str();
412 string getContentType() const {
413 if (m_content_type.empty()) {
415 GetServerVariable(m_pfc,"CONTENT_TYPE",var,32,false);
417 m_content_type = var;
419 return m_content_type;
421 long getContentLength() const {
424 string getRemoteAddr() const {
425 if (m_remote_addr.empty()) {
427 GetServerVariable(m_pfc,"REMOTE_ADDR",var,16,false);
431 return m_remote_addr;
433 void log(SPLogLevel level, const string& msg) {
434 AbstractSPRequest::log(level,msg);
435 if (level >= SPError)
436 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
438 void clearHeader(const char* rawname, const char* cginame) {
439 if (g_checkSpoofing) {
440 if (m_allhttp.empty())
441 GetServerVariable(m_pfc,"ALL_HTTP",m_allhttp,4096);
442 if (strstr(m_allhttp, cginame))
443 throw opensaml::SecurityPolicyException("Attempt to spoof header ($1) was detected.", params(1, rawname));
445 string hdr(!strcmp(rawname,"REMOTE_USER") ? "remote-user" : rawname);
447 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), const_cast<char*>(g_unsetHeaderValue.c_str()));
449 void setHeader(const char* name, const char* value) {
452 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), const_cast<char*>(value));
454 string getHeader(const char* name) const {
458 GetHeader(m_pn, m_pfc, const_cast<char*>(hdr.c_str()), buf, 256, false);
461 void setRemoteUser(const char* user) {
462 setHeader("remote-user", user);
464 string getRemoteUser() const {
465 return getHeader("remote-user");
467 void setResponseHeader(const char* name, const char* value) {
470 m_headers.insert(make_pair(name,value));
472 m_headers.erase(name);
474 long sendResponse(istream& in, long status) {
475 string hdr = string("Connection: close\r\n");
476 for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
477 hdr += i->first + ": " + i->second + "\r\n";
479 const char* codestr="200 OK";
481 case XMLTOOLING_HTTP_STATUS_UNAUTHORIZED: codestr="401 Authorization Required"; break;
482 case XMLTOOLING_HTTP_STATUS_FORBIDDEN: codestr="403 Forbidden"; break;
483 case XMLTOOLING_HTTP_STATUS_NOTFOUND: codestr="404 Not Found"; break;
484 case XMLTOOLING_HTTP_STATUS_ERROR: codestr="500 Server Error"; break;
486 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, (void*)codestr, (DWORD)hdr.c_str(), 0);
490 DWORD resplen = in.gcount();
491 m_pfc->WriteClient(m_pfc, buf, &resplen, 0);
493 return SF_STATUS_REQ_FINISHED;
495 long sendRedirect(const char* url) {
496 // XXX: Don't support the httpRedirect option, yet.
497 string hdr=string("Location: ") + url + "\r\n"
498 "Content-Type: text/html\r\n"
499 "Content-Length: 40\r\n"
500 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
501 "Cache-Control: private,no-store,no-cache\r\n";
502 for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
503 hdr += i->first + ": " + i->second + "\r\n";
505 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, "302 Please Wait", (DWORD)hdr.c_str(), 0);
506 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
508 m_pfc->WriteClient(m_pfc, (LPVOID)redmsg, &resplen, 0);
509 return SF_STATUS_REQ_FINISHED;
511 long returnDecline() {
512 return SF_STATUS_REQ_NEXT_NOTIFICATION;
515 return SF_STATUS_REQ_NEXT_NOTIFICATION;
518 const vector<string>& getClientCertificates() const {
522 // The filter never processes the POST, so stub these methods.
523 const char* getQueryString() const { throw IOException("getQueryString not implemented"); }
524 const char* getRequestBody() const { throw IOException("getRequestBody not implemented"); }
527 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const char* msg)
529 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
530 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
531 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)ctype,0);
532 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Filter Error</TITLE></HEAD><BODY>"
533 "<H1>Shibboleth Filter Error</H1>";
534 DWORD resplen=strlen(xmsg);
535 pfc->WriteClient(pfc,(LPVOID)xmsg,&resplen,0);
537 pfc->WriteClient(pfc,(LPVOID)msg,&resplen,0);
538 static const char* xmsg2="</BODY></HTML>";
539 resplen=strlen(xmsg2);
540 pfc->WriteClient(pfc,(LPVOID)xmsg2,&resplen,0);
541 return SF_STATUS_REQ_FINISHED;
544 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
546 // Is this a log notification?
547 if (notificationType==SF_NOTIFY_LOG)
549 if (pfc->pFilterContext)
550 ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=static_cast<LPCSTR>(pfc->pFilterContext);
551 return SF_STATUS_REQ_NEXT_NOTIFICATION;
554 PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
557 // Determine web site number. This can't really fail, I don't think.
559 GetServerVariable(pfc,"INSTANCE_ID",buf,10);
561 // Match site instance to host name, skip if no match.
562 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
563 if (map_i==g_Sites.end())
564 return SF_STATUS_REQ_NEXT_NOTIFICATION;
566 ostringstream threadid;
567 threadid << "[" << getpid() << "] isapi_shib" << '\0';
568 xmltooling::NDC ndc(threadid.str().c_str());
570 ShibTargetIsapiF stf(pfc, pn, map_i->second);
572 // "false" because we don't override the Shib settings
573 pair<bool,long> res = stf.getServiceProvider().doAuthentication(stf);
574 if (res.first) return res.second;
576 // "false" because we don't override the Shib settings
577 res = stf.getServiceProvider().doExport(stf);
578 if (res.first) return res.second;
580 res = stf.getServiceProvider().doAuthorization(stf);
581 if (res.first) return res.second;
583 return SF_STATUS_REQ_NEXT_NOTIFICATION;
586 return WriteClientError(pfc,"Out of Memory");
589 if (e==ERROR_NO_DATA)
590 return WriteClientError(pfc,"A required variable or header was empty.");
592 return WriteClientError(pfc,"Shibboleth Filter detected unexpected IIS error.");
594 catch (exception& e) {
595 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
596 return WriteClientError(pfc,"Shibboleth Filter caught an exception, check Event Log for details.");
599 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Shibboleth Filter threw an unknown exception.");
601 return WriteClientError(pfc,"Shibboleth Filter threw an unknown exception.");
605 return WriteClientError(pfc,"Shibboleth Filter reached unreachable code, save my walrus!");
609 /****************************************************************************/
612 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* msg)
614 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
615 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
616 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
617 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Error</TITLE></HEAD><BODY><H1>Shibboleth Error</H1>";
618 DWORD resplen=strlen(xmsg);
619 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg,&resplen,HSE_IO_SYNC);
621 lpECB->WriteClient(lpECB->ConnID,(LPVOID)msg,&resplen,HSE_IO_SYNC);
622 static const char* xmsg2="</BODY></HTML>";
623 resplen=strlen(xmsg2);
624 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg2,&resplen,HSE_IO_SYNC);
625 return HSE_STATUS_SUCCESS;
629 class ShibTargetIsapiE : public AbstractSPRequest
631 LPEXTENSION_CONTROL_BLOCK m_lpECB;
632 multimap<string,string> m_headers;
633 mutable vector<string> m_certs;
634 mutable string m_body;
635 mutable bool m_gotBody;
637 string m_scheme,m_hostname,m_uri;
638 mutable string m_remote_addr,m_remote_user;
641 ShibTargetIsapiE(LPEXTENSION_CONTROL_BLOCK lpECB, const site_t& site)
642 : AbstractSPRequest(SHIBSP_LOGCAT".ISAPI"), 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
697 // Clearly we're only in bad mode if path info exists at all.
698 if (lpECB->lpszPathInfo && *(lpECB->lpszPathInfo)) {
699 if (strstr(lpECB->lpszPathInfo,url))
700 // Pretty good chance we're in bad mode, unless the PathInfo repeats the path itself.
701 uri = lpECB->lpszPathInfo;
704 uri += lpECB->lpszPathInfo;
711 // For consistency with Apache, let's add the query string.
712 if (lpECB->lpszQueryString && *(lpECB->lpszQueryString)) {
714 uri += lpECB->lpszQueryString;
717 setRequestURI(uri.c_str());
719 ~ShibTargetIsapiE() { }
721 const char* getScheme() const {
722 return m_scheme.c_str();
724 const char* getHostname() const {
725 return m_hostname.c_str();
727 int getPort() const {
730 const char* getMethod() const {
731 return m_lpECB->lpszMethod;
733 string getContentType() const {
734 return m_lpECB->lpszContentType ? m_lpECB->lpszContentType : "";
736 long getContentLength() const {
737 return m_lpECB->cbTotalBytes;
739 string getRemoteUser() const {
740 if (m_remote_user.empty()) {
742 GetServerVariable(m_lpECB, "REMOTE_USER", var, 32, false);
746 return m_remote_user;
748 string getRemoteAddr() const {
749 if (m_remote_addr.empty()) {
751 GetServerVariable(m_lpECB, "REMOTE_ADDR", var, 16, false);
755 return m_remote_addr;
757 void log(SPLogLevel level, const string& msg) const {
758 AbstractSPRequest::log(level,msg);
759 if (level >= SPError)
760 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
762 string getHeader(const char* name) const {
764 for (; *name; ++name) {
768 hdr += toupper(*name);
771 GetServerVariable(m_lpECB, const_cast<char*>(hdr.c_str()), buf, 128, false);
772 return buf.empty() ? "" : buf;
774 void setResponseHeader(const char* name, const char* value) {
777 m_headers.insert(make_pair(name,value));
779 m_headers.erase(name);
781 const char* getQueryString() const {
782 return m_lpECB->lpszQueryString;
784 const char* getRequestBody() const {
786 return m_body.c_str();
787 if (m_lpECB->cbTotalBytes > 1024*1024) // 1MB?
788 throw opensaml::SecurityPolicyException("Size of request body exceeded 1M size limit.");
789 else if (m_lpECB->cbTotalBytes > m_lpECB->cbAvailable) {
792 DWORD datalen=m_lpECB->cbTotalBytes;
795 BOOL ret = m_lpECB->ReadClient(m_lpECB->ConnID, buf, &buflen);
797 throw IOException("Error reading request body from browser.");
798 m_body.append(buf, buflen);
802 else if (m_lpECB->cbAvailable) {
804 m_body.assign(reinterpret_cast<char*>(m_lpECB->lpbData),m_lpECB->cbAvailable);
806 return m_body.c_str();
808 long sendResponse(istream& in, long status) {
809 string hdr = string("Connection: close\r\n");
810 for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
811 hdr += i->first + ": " + i->second + "\r\n";
813 const char* codestr="200 OK";
815 case XMLTOOLING_HTTP_STATUS_UNAUTHORIZED: codestr="401 Authorization Required"; break;
816 case XMLTOOLING_HTTP_STATUS_FORBIDDEN: codestr="403 Forbidden"; break;
817 case XMLTOOLING_HTTP_STATUS_NOTFOUND: codestr="404 Not Found"; break;
818 case XMLTOOLING_HTTP_STATUS_ERROR: codestr="500 Server Error"; break;
820 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, (void*)codestr, 0, (LPDWORD)hdr.c_str());
824 DWORD resplen = in.gcount();
825 m_lpECB->WriteClient(m_lpECB->ConnID, buf, &resplen, HSE_IO_SYNC);
827 return HSE_STATUS_SUCCESS;
829 long sendRedirect(const char* url) {
830 string hdr=string("Location: ") + url + "\r\n"
831 "Content-Type: text/html\r\n"
832 "Content-Length: 40\r\n"
833 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
834 "Cache-Control: private,no-store,no-cache\r\n";
835 for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
836 hdr += i->first + ": " + i->second + "\r\n";
838 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, "302 Moved", 0, (LPDWORD)hdr.c_str());
839 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
841 m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)redmsg, &resplen, HSE_IO_SYNC);
842 return HSE_STATUS_SUCCESS;
844 // Decline happens in the POST processor if this isn't the shire url
845 // Note that it can also happen with HTAccess, but we don't support that, yet.
846 long returnDecline() {
847 return WriteClientError(
849 "ISAPI extension can only be invoked to process Shibboleth protocol requests."
850 "Make sure the mapped file extension doesn't match actual content."
854 return HSE_STATUS_SUCCESS;
857 const vector<string>& getClientCertificates() const {
858 if (m_certs.empty()) {
859 char CertificateBuf[8192];
860 CERT_CONTEXT_EX ccex;
861 ccex.cbAllocated = sizeof(CertificateBuf);
862 ccex.CertContext.pbCertEncoded = (BYTE*)CertificateBuf;
863 DWORD dwSize = sizeof(ccex);
865 if (m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_GET_CERT_INFO_EX, (LPVOID)&ccex, (LPDWORD)dwSize, NULL)) {
866 if (ccex.CertContext.cbCertEncoded) {
868 XMLByte* serialized = Base64::encode(reinterpret_cast<XMLByte*>(CertificateBuf), ccex.CertContext.cbCertEncoded, &outlen);
869 m_certs.push_back(reinterpret_cast<char*>(serialized));
870 XMLString::release(&serialized);
877 // Not used in the extension.
878 void clearHeader(const char* rawname, const char* cginame) { throw runtime_error("clearHeader not implemented"); }
879 void setHeader(const char* name, const char* value) { throw runtime_error("setHeader not implemented"); }
880 void setRemoteUser(const char* user) { throw runtime_error("setRemoteUser not implemented"); }
883 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
886 ostringstream threadid;
887 threadid << "[" << getpid() << "] isapi_shib_extension" << '\0';
888 xmltooling::NDC ndc(threadid.str().c_str());
890 // Determine web site number. This can't really fail, I don't think.
892 GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
894 // Match site instance to host name, skip if no match.
895 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
896 if (map_i==g_Sites.end())
897 return WriteClientError(lpECB, "Shibboleth Extension not configured for this web site.");
899 ShibTargetIsapiE ste(lpECB, map_i->second);
900 pair<bool,long> res = ste.getServiceProvider().doHandler(ste);
901 if (res.first) return res.second;
903 return WriteClientError(lpECB, "Shibboleth Extension failed to process request");
907 return WriteClientError(lpECB,"Out of Memory");
910 if (e==ERROR_NO_DATA)
911 return WriteClientError(lpECB,"A required variable or header was empty.");
913 return WriteClientError(lpECB,"Server detected unexpected IIS error.");
915 catch (exception& e) {
916 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
917 return WriteClientError(lpECB,"Shibboleth Extension caught an exception, check Event Log for details.");
920 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Shibboleth Extension threw an unknown exception.");
922 return WriteClientError(lpECB,"Shibboleth Extension threw an unknown exception.");
926 // If we get here we've got an error.
927 return HSE_STATUS_ERROR;