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 |
168 if (!g_Config->init(schemadir)) {
170 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
171 "Filter startup failed during library initialization, check native log for help.");
176 DOMDocument* dummydoc=XMLToolingConfig::getConfig().getParser().newDocument();
177 XercesJanitor<DOMDocument> docjanitor(dummydoc);
178 DOMElement* dummy = dummydoc->createElementNS(NULL,path);
179 auto_ptr_XMLCh src(config);
180 dummy->setAttributeNS(NULL,path,src.get());
181 dummy->setAttributeNS(NULL,validate,xmlconstants::XML_ONE);
183 g_Config->setServiceProvider(g_Config->ServiceProviderManager.newPlugin(XML_SERVICE_PROVIDER,dummy));
184 g_Config->getServiceProvider()->init();
186 catch (exception& ex) {
189 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, ex.what());
190 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
191 "Filter startup failed to load configuration, check native log for details.");
195 // Access the implementation-specifics for site mappings.
196 ServiceProvider* sp=g_Config->getServiceProvider();
198 const PropertySet* props=sp->getPropertySet("InProcess");
200 pair<bool,const char*> unsetValue=props->getString("unsetHeaderValue");
201 if (unsetValue.first)
202 g_unsetHeaderValue = unsetValue.second;
203 pair<bool,bool> checkSpoofing=props->getBool("checkSpoofing");
204 if (checkSpoofing.first && !checkSpoofing.second)
205 g_checkSpoofing = false;
206 const DOMElement* impl=XMLHelper::getFirstChildElement(props->getElement(),Implementation);
207 if (impl && (impl=XMLHelper::getFirstChildElement(impl,ISAPI))) {
208 const XMLCh* flag=impl->getAttributeNS(NULL,normalizeRequest);
209 g_bNormalizeRequest=(!flag || !*flag || *flag==chDigit_1 || *flag==chLatin_t);
210 impl=XMLHelper::getFirstChildElement(impl,Site);
212 auto_ptr_char id(impl->getAttributeNS(NULL,id));
214 g_Sites.insert(pair<string,site_t>(id.get(),site_t(impl)));
215 impl=XMLHelper::getNextSiblingElement(impl,Site);
220 pVer->dwFilterVersion=HTTP_FILTER_REVISION;
221 strncpy(pVer->lpszFilterDesc,"Shibboleth ISAPI Filter",SF_MAX_FILTER_DESC_LEN);
222 pVer->dwFlags=(SF_NOTIFY_ORDER_HIGH |
223 SF_NOTIFY_SECURE_PORT |
224 SF_NOTIFY_NONSECURE_PORT |
225 SF_NOTIFY_PREPROC_HEADERS |
227 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter initialized...");
231 extern "C" BOOL WINAPI TerminateFilter(DWORD)
236 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter shut down...");
240 /* Next up, some suck-free versions of various APIs.
242 You DON'T require people to guess the buffer size and THEN tell them the right size.
243 Returning an LPCSTR is apparently way beyond their ken. Not to mention the fact that
244 constant strings aren't typed as such, making it just that much harder. These versions
245 are now updated to use a special growable buffer object, modeled after the standard
246 string class. The standard string won't work because they left out the option to
247 pre-allocate a non-constant buffer.
253 dynabuf() { bufptr=NULL; buflen=0; }
254 dynabuf(size_t s) { bufptr=new char[buflen=s]; *bufptr=0; }
255 ~dynabuf() { delete[] bufptr; }
256 size_t length() const { return bufptr ? strlen(bufptr) : 0; }
257 size_t size() const { return buflen; }
258 bool empty() const { return length()==0; }
259 void reserve(size_t s, bool keep=false);
260 void erase() { if (bufptr) memset(bufptr,0,buflen); }
261 operator char*() { return bufptr; }
262 bool operator ==(const char* s) const;
263 bool operator !=(const char* s) const { return !(*this==s); }
269 void dynabuf::reserve(size_t s, bool keep)
276 p[buflen]=bufptr[buflen];
282 bool dynabuf::operator==(const char* s) const
284 if (buflen==NULL || s==NULL)
285 return (buflen==NULL && s==NULL);
287 return strcmp(bufptr,s)==0;
290 void GetServerVariable(PHTTP_FILTER_CONTEXT pfc, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
296 while (!pfc->GetServerVariable(pfc,lpszVariable,s,&size)) {
297 // Grumble. Check the error.
298 DWORD e=GetLastError();
299 if (e==ERROR_INSUFFICIENT_BUFFER)
304 if (bRequired && s.empty())
308 void GetServerVariable(LPEXTENSION_CONTROL_BLOCK lpECB, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
314 while (!lpECB->GetServerVariable(lpECB->ConnID,lpszVariable,s,&size)) {
315 // Grumble. Check the error.
316 DWORD e=GetLastError();
317 if (e==ERROR_INSUFFICIENT_BUFFER)
322 if (bRequired && s.empty())
326 void GetHeader(PHTTP_FILTER_PREPROC_HEADERS pn, PHTTP_FILTER_CONTEXT pfc,
327 LPSTR lpszName, dynabuf& s, DWORD size=80, bool bRequired=true)
333 while (!pn->GetHeader(pfc,lpszName,s,&size)) {
334 // Grumble. Check the error.
335 DWORD e=GetLastError();
336 if (e==ERROR_INSUFFICIENT_BUFFER)
341 if (bRequired && s.empty())
345 /****************************************************************************/
348 class ShibTargetIsapiF : public AbstractSPRequest
350 PHTTP_FILTER_CONTEXT m_pfc;
351 PHTTP_FILTER_PREPROC_HEADERS m_pn;
352 map<string,string> m_headers;
354 string m_scheme,m_hostname,m_uri;
355 mutable string m_remote_addr,m_content_type,m_method;
359 ShibTargetIsapiF(PHTTP_FILTER_CONTEXT pfc, PHTTP_FILTER_PREPROC_HEADERS pn, const site_t& site)
360 : m_pfc(pfc), m_pn(pn), m_allhttp(4096) {
362 // URL path always come from IIS.
364 GetHeader(pn,pfc,"url",var,256,false);
367 // Port may come from IIS or from site def.
368 if (!g_bNormalizeRequest || (pfc->fIsSecurePort && site.m_sslport.empty()) || (!pfc->fIsSecurePort && site.m_port.empty())) {
369 GetServerVariable(pfc,"SERVER_PORT",var,10);
372 else if (pfc->fIsSecurePort) {
373 m_port = atoi(site.m_sslport.c_str());
376 m_port = atoi(site.m_port.c_str());
379 // Scheme may come from site def or be derived from IIS.
380 m_scheme=site.m_scheme;
381 if (m_scheme.empty() || !g_bNormalizeRequest)
382 m_scheme=pfc->fIsSecurePort ? "https" : "http";
384 GetServerVariable(pfc,"SERVER_NAME",var,32);
386 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
388 if (site.m_name!=m_hostname && site.m_aliases.find(m_hostname)==site.m_aliases.end())
389 m_hostname=site.m_name;
391 ~ShibTargetIsapiF() { }
393 const char* getScheme() const {
394 return m_scheme.c_str();
396 const char* getHostname() const {
397 return m_hostname.c_str();
399 int getPort() const {
402 const char* getRequestURI() const {
403 return m_uri.c_str();
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[name] = value;
474 m_headers.erase(name);
476 long sendResponse(istream& in, long status) {
477 string hdr = string("Connection: close\r\n");
478 for (map<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_FORBIDDEN:codestr="403 Forbidden"; break;
484 case XMLTOOLING_HTTP_STATUS_NOTFOUND: codestr="404 Not Found"; break;
485 case XMLTOOLING_HTTP_STATUS_ERROR: codestr="500 Server Error"; break;
487 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, (void*)codestr, (DWORD)hdr.c_str(), 0);
491 DWORD resplen = in.gcount();
492 m_pfc->WriteClient(m_pfc, buf, &resplen, 0);
494 return SF_STATUS_REQ_FINISHED;
496 long sendRedirect(const char* url) {
497 // XXX: Don't support the httpRedirect option, yet.
498 string hdr=string("Location: ") + url + "\r\n"
499 "Content-Type: text/html\r\n"
500 "Content-Length: 40\r\n"
501 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
502 "Cache-Control: private,no-store,no-cache\r\n";
503 for (map<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
504 hdr += i->first + ": " + i->second + "\r\n";
506 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, "302 Please Wait", (DWORD)hdr.c_str(), 0);
507 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
509 m_pfc->WriteClient(m_pfc, (LPVOID)redmsg, &resplen, 0);
510 return SF_STATUS_REQ_FINISHED;
512 long returnDecline() {
513 return SF_STATUS_REQ_NEXT_NOTIFICATION;
516 return SF_STATUS_REQ_NEXT_NOTIFICATION;
519 const vector<string>& getClientCertificates() const {
523 // The filter never processes the POST, so stub these methods.
524 const char* getQueryString() const { throw IOException("getQueryString not implemented"); }
525 const char* getRequestBody() const { throw IOException("getRequestBody not implemented"); }
528 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const char* msg)
530 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
531 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
532 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)ctype,0);
533 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Filter Error</TITLE></HEAD><BODY>"
534 "<H1>Shibboleth Filter Error</H1>";
535 DWORD resplen=strlen(xmsg);
536 pfc->WriteClient(pfc,(LPVOID)xmsg,&resplen,0);
538 pfc->WriteClient(pfc,(LPVOID)msg,&resplen,0);
539 static const char* xmsg2="</BODY></HTML>";
540 resplen=strlen(xmsg2);
541 pfc->WriteClient(pfc,(LPVOID)xmsg2,&resplen,0);
542 return SF_STATUS_REQ_FINISHED;
545 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
547 // Is this a log notification?
548 if (notificationType==SF_NOTIFY_LOG)
550 if (pfc->pFilterContext)
551 ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=static_cast<LPCSTR>(pfc->pFilterContext);
552 return SF_STATUS_REQ_NEXT_NOTIFICATION;
555 PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
558 // Determine web site number. This can't really fail, I don't think.
560 GetServerVariable(pfc,"INSTANCE_ID",buf,10);
562 // Match site instance to host name, skip if no match.
563 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
564 if (map_i==g_Sites.end())
565 return SF_STATUS_REQ_NEXT_NOTIFICATION;
567 ostringstream threadid;
568 threadid << "[" << getpid() << "] isapi_shib" << '\0';
569 xmltooling::NDC ndc(threadid.str().c_str());
571 ShibTargetIsapiF stf(pfc, pn, map_i->second);
573 // "false" because we don't override the Shib settings
574 pair<bool,long> res = stf.getServiceProvider().doAuthentication(stf);
575 if (res.first) return res.second;
577 // "false" because we don't override the Shib settings
578 res = stf.getServiceProvider().doExport(stf);
579 if (res.first) return res.second;
581 res = stf.getServiceProvider().doAuthorization(stf);
582 if (res.first) return res.second;
584 return SF_STATUS_REQ_NEXT_NOTIFICATION;
587 return WriteClientError(pfc,"Out of Memory");
590 if (e==ERROR_NO_DATA)
591 return WriteClientError(pfc,"A required variable or header was empty.");
593 return WriteClientError(pfc,"Shibboleth Filter detected unexpected IIS error.");
595 catch (exception& e) {
596 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
597 return WriteClientError(pfc,"Shibboleth Filter caught an exception, check Event Log for details.");
601 return WriteClientError(pfc,"Shibboleth Filter caught 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 map<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) : m_lpECB(lpECB), m_gotBody(false) {
643 GetServerVariable(lpECB,"HTTPS",ssl,5);
644 bool SSL=(ssl=="on" || ssl=="ON");
646 // Scheme may come from site def or be derived from IIS.
647 m_scheme=site.m_scheme;
648 if (m_scheme.empty() || !g_bNormalizeRequest)
649 m_scheme = SSL ? "https" : "http";
651 // URL path always come from IIS.
653 GetServerVariable(lpECB,"URL",url,255);
655 // Port may come from IIS or from site def.
657 if (!g_bNormalizeRequest || (SSL && site.m_sslport.empty()) || (!SSL && site.m_port.empty()))
658 GetServerVariable(lpECB,"SERVER_PORT",port,10);
660 strncpy(port,site.m_sslport.c_str(),10);
661 static_cast<char*>(port)[10]=0;
664 strncpy(port,site.m_port.c_str(),10);
665 static_cast<char*>(port)[10]=0;
670 GetServerVariable(lpECB, "SERVER_NAME", var, 32);
672 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
674 if (site.m_name!=m_hostname && site.m_aliases.find(m_hostname)==site.m_aliases.end())
675 m_hostname=site.m_name;
678 * IIS screws us over on PATH_INFO (the hits keep on coming). We need to figure out if
679 * the server is set up for proper PATH_INFO handling, or "IIS sucks rabid weasels mode",
680 * which is the default. No perfect way to tell, but we can take a good guess by checking
681 * whether the URL is a substring of the PATH_INFO:
683 * e.g. for /Shibboleth.sso/SAML/POST
685 * Bad mode (default):
686 * URL: /Shibboleth.sso
687 * PathInfo: /Shibboleth.sso/SAML/POST
690 * URL: /Shibboleth.sso
691 * PathInfo: /SAML/POST
694 // Clearly we're only in bad mode if path info exists at all.
695 if (lpECB->lpszPathInfo && *(lpECB->lpszPathInfo)) {
696 if (strstr(lpECB->lpszPathInfo,url))
697 // Pretty good chance we're in bad mode, unless the PathInfo repeats the path itself.
698 m_uri = lpECB->lpszPathInfo;
701 m_uri += lpECB->lpszPathInfo;
705 // For consistency with Apache, let's add the query string.
706 if (lpECB->lpszQueryString && *(lpECB->lpszQueryString)) {
708 m_uri += lpECB->lpszQueryString;
711 ~ShibTargetIsapiE() { }
713 const char* getScheme() const {
714 return m_scheme.c_str();
716 const char* getHostname() const {
717 return m_hostname.c_str();
719 int getPort() const {
722 const char* getRequestURI() const {
723 return m_uri.c_str();
725 const char* getMethod() const {
726 return m_lpECB->lpszMethod;
728 string getContentType() const {
729 return m_lpECB->lpszContentType ? m_lpECB->lpszContentType : "";
731 long getContentLength() const {
732 return m_lpECB->cbTotalBytes;
734 string getRemoteUser() const {
735 if (m_remote_user.empty()) {
737 GetServerVariable(m_lpECB, "REMOTE_USER", var, 32, false);
741 return m_remote_user;
743 string getRemoteAddr() const {
744 if (m_remote_addr.empty()) {
746 GetServerVariable(m_lpECB, "REMOTE_ADDR", var, 16, false);
750 return m_remote_addr;
752 void log(SPLogLevel level, const string& msg) const {
753 AbstractSPRequest::log(level,msg);
754 if (level >= SPError)
755 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
757 string getHeader(const char* name) const {
759 for (; *name; ++name) {
763 hdr += toupper(*name);
766 GetServerVariable(m_lpECB, const_cast<char*>(hdr.c_str()), buf, 128, false);
767 return buf.empty() ? "" : buf;
769 void setResponseHeader(const char* name, const char* value) {
772 m_headers[name] = value;
774 m_headers.erase(name);
776 const char* getQueryString() const {
777 return m_lpECB->lpszQueryString;
779 const char* getRequestBody() const {
781 return m_body.c_str();
782 if (m_lpECB->cbTotalBytes > 1024*1024) // 1MB?
783 throw opensaml::SecurityPolicyException("Size of request body exceeded 1M size limit.");
784 else if (m_lpECB->cbTotalBytes > m_lpECB->cbAvailable) {
787 DWORD datalen=m_lpECB->cbTotalBytes;
790 BOOL ret = m_lpECB->ReadClient(m_lpECB->ConnID, buf, &buflen);
792 throw IOException("Error reading request body from browser.");
793 m_body.append(buf, buflen);
797 else if (m_lpECB->cbAvailable) {
799 m_body.assign(reinterpret_cast<char*>(m_lpECB->lpbData),m_lpECB->cbAvailable);
801 return m_body.c_str();
803 long sendResponse(istream& in, long status) {
804 string hdr = string("Connection: close\r\n");
805 for (map<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
806 hdr += i->first + ": " + i->second + "\r\n";
808 const char* codestr="200 OK";
810 case XMLTOOLING_HTTP_STATUS_FORBIDDEN:codestr="403 Forbidden"; break;
811 case XMLTOOLING_HTTP_STATUS_NOTFOUND: codestr="404 Not Found"; break;
812 case XMLTOOLING_HTTP_STATUS_ERROR: codestr="500 Server Error"; break;
814 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, (void*)codestr, 0, (LPDWORD)hdr.c_str());
818 DWORD resplen = in.gcount();
819 m_lpECB->WriteClient(m_lpECB->ConnID, buf, &resplen, HSE_IO_SYNC);
821 return HSE_STATUS_SUCCESS;
823 long sendRedirect(const char* url) {
824 string hdr=string("Location: ") + url + "\r\n"
825 "Content-Type: text/html\r\n"
826 "Content-Length: 40\r\n"
827 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
828 "Cache-Control: private,no-store,no-cache\r\n";
829 for (map<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
830 hdr += i->first + ": " + i->second + "\r\n";
832 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, "302 Moved", 0, (LPDWORD)hdr.c_str());
833 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
835 m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)redmsg, &resplen, HSE_IO_SYNC);
836 return HSE_STATUS_SUCCESS;
838 // Decline happens in the POST processor if this isn't the shire url
839 // Note that it can also happen with HTAccess, but we don't support that, yet.
840 long returnDecline() {
841 return WriteClientError(
843 "ISAPI extension can only be invoked to process Shibboleth protocol requests."
844 "Make sure the mapped file extension doesn't match actual content."
848 return HSE_STATUS_SUCCESS;
851 const vector<string>& getClientCertificates() const {
852 if (m_certs.empty()) {
853 char CertificateBuf[8192];
854 CERT_CONTEXT_EX ccex;
855 ccex.cbAllocated = sizeof(CertificateBuf);
856 ccex.CertContext.pbCertEncoded = (BYTE*)CertificateBuf;
857 DWORD dwSize = sizeof(ccex);
859 if (m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_GET_CERT_INFO_EX, (LPVOID)&ccex, (LPDWORD)dwSize, NULL)) {
860 if (ccex.CertContext.cbCertEncoded) {
862 XMLByte* serialized = Base64::encode(reinterpret_cast<XMLByte*>(CertificateBuf), ccex.CertContext.cbCertEncoded, &outlen);
863 m_certs.push_back(reinterpret_cast<char*>(serialized));
864 XMLString::release(&serialized);
871 // Not used in the extension.
872 void clearHeader(const char* rawname, const char* cginame) { throw runtime_error("clearHeader not implemented"); }
873 void setHeader(const char* name, const char* value) { throw runtime_error("setHeader not implemented"); }
874 void setRemoteUser(const char* user) { throw runtime_error("setRemoteUser not implemented"); }
877 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
880 ostringstream threadid;
881 threadid << "[" << getpid() << "] isapi_shib_extension" << '\0';
882 xmltooling::NDC ndc(threadid.str().c_str());
884 // Determine web site number. This can't really fail, I don't think.
886 GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
888 // Match site instance to host name, skip if no match.
889 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
890 if (map_i==g_Sites.end())
891 return WriteClientError(lpECB, "Shibboleth Extension not configured for this web site.");
893 ShibTargetIsapiE ste(lpECB, map_i->second);
894 pair<bool,long> res = ste.getServiceProvider().doHandler(ste);
895 if (res.first) return res.second;
897 return WriteClientError(lpECB, "Shibboleth Extension failed to process request");
901 return WriteClientError(lpECB,"Out of Memory");
904 if (e==ERROR_NO_DATA)
905 return WriteClientError(lpECB,"A required variable or header was empty.");
907 return WriteClientError(lpECB,"Server detected unexpected IIS error.");
909 catch (exception& e) {
910 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
911 return WriteClientError(lpECB,"Shibboleth Extension caught an exception, check Event Log for details.");
915 return WriteClientError(lpECB,"Shibboleth Extension caught an unknown exception.");
919 // If we get here we've got an error.
920 return HSE_STATUS_ERROR;