2 * Copyright 2001-2009 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.
17 /* isapi_shib.cpp - Shibboleth ISAPI filter
23 #include "config_win32.h"
26 #include <saml/saml.h>
27 #include <shib/shib.h>
28 #include <shib/shib-threads.h>
29 #include <shib-target/shib-target.h>
42 using namespace shibboleth;
43 using namespace shibtarget;
47 static const XMLCh name[] = { chLatin_n, chLatin_a, chLatin_m, chLatin_e, chNull };
48 static const XMLCh port[] = { chLatin_p, chLatin_o, chLatin_r, chLatin_t, chNull };
49 static const XMLCh sslport[] = { chLatin_s, chLatin_s, chLatin_l, chLatin_p, chLatin_o, chLatin_r, chLatin_t, chNull };
50 static const XMLCh scheme[] = { chLatin_s, chLatin_c, chLatin_h, chLatin_e, chLatin_m, chLatin_e, chNull };
51 static const XMLCh id[] = { chLatin_i, chLatin_d, chNull };
52 static const XMLCh Implementation[] =
53 { chLatin_I, chLatin_m, chLatin_p, chLatin_l, chLatin_e, chLatin_m, chLatin_e, chLatin_n, chLatin_t, chLatin_a, chLatin_t, chLatin_i, chLatin_o, chLatin_n, chNull };
54 static const XMLCh ISAPI[] = { chLatin_I, chLatin_S, chLatin_A, chLatin_P, chLatin_I, chNull };
55 static const XMLCh Alias[] = { chLatin_A, chLatin_l, chLatin_i, chLatin_a, chLatin_s, chNull };
56 static const XMLCh normalizeRequest[] =
57 { chLatin_n, chLatin_o, chLatin_r, chLatin_m, chLatin_a, chLatin_l, chLatin_i, chLatin_z, chLatin_e,
58 chLatin_R, chLatin_e, chLatin_q, chLatin_u, chLatin_e, chLatin_s, chLatin_t, chNull
60 static const XMLCh safeHeaderNames[] =
61 { chLatin_s, chLatin_a, chLatin_f, chLatin_e, chLatin_H, chLatin_e, chLatin_a, chLatin_d, chLatin_e, chLatin_r,
62 chLatin_N, chLatin_a, chLatin_m, chLatin_e, chLatin_s, chNull
64 static const XMLCh Site[] = { chLatin_S, chLatin_i, chLatin_t, chLatin_e, chNull };
67 site_t(const DOMElement* e)
69 auto_ptr_char n(e->getAttributeNS(NULL,name));
70 auto_ptr_char s(e->getAttributeNS(NULL,scheme));
71 auto_ptr_char p(e->getAttributeNS(NULL,port));
72 auto_ptr_char p2(e->getAttributeNS(NULL,sslport));
73 if (n.get()) m_name=n.get();
74 if (s.get()) m_scheme=s.get();
75 if (p.get()) m_port=p.get();
76 if (p2.get()) m_sslport=p2.get();
77 DOMNodeList* nlist=e->getElementsByTagNameNS(shibtarget::XML::SHIBTARGET_NS,Alias);
78 for (int i=0; nlist && i<nlist->getLength(); i++) {
79 if (nlist->item(i)->hasChildNodes()) {
80 auto_ptr_char alias(nlist->item(i)->getFirstChild()->getNodeValue());
81 m_aliases.insert(alias.get());
85 string m_scheme,m_port,m_sslport,m_name;
86 set<string> m_aliases;
90 ShibTargetConfig* g_Config = NULL;
91 map<string,site_t> g_Sites;
92 bool g_bNormalizeRequest = true;
93 string g_unsetHeaderValue,g_spoofKey;
94 bool g_checkSpoofing = true;
95 bool g_catchAll = true;
96 bool g_bSafeHeaderNames = false;
100 LPCSTR lpUNCServerName,
106 LPCSTR messages[] = {message, NULL};
108 HANDLE hElog = RegisterEventSource(lpUNCServerName, "Shibboleth ISAPI Filter");
109 BOOL res = ReportEvent(hElog, wType, 0, dwEventID, lpUserSid, 1, 0, messages, NULL);
110 return (DeregisterEventSource(hElog) && res);
113 extern "C" __declspec(dllexport) BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID)
115 if (fdwReason==DLL_PROCESS_ATTACH)
120 extern "C" BOOL WINAPI GetExtensionVersion(HSE_VERSION_INFO* pVer)
127 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
128 "Extension mode startup not possible, is the DLL loaded as a filter?");
132 pVer->dwExtensionVersion=HSE_VERSION;
133 strncpy(pVer->lpszExtensionDesc,"Shibboleth ISAPI Extension",HSE_MAX_EXT_DLL_NAME_LEN-1);
137 extern "C" BOOL WINAPI TerminateExtension(DWORD)
139 return TRUE; // cleanup should happen when filter unloads
142 extern "C" BOOL WINAPI GetFilterVersion(PHTTP_FILTER_VERSION pVer)
147 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
148 "Reentrant filter initialization, ignoring...");
153 LPCSTR schemadir=getenv("SHIBSCHEMAS");
155 schemadir=SHIB_SCHEMAS;
156 LPCSTR config=getenv("SHIBCONFIG");
159 g_Config=&ShibTargetConfig::getConfig();
160 g_Config->setFeatures(
161 ShibTargetConfig::Listener |
162 ShibTargetConfig::Metadata |
163 ShibTargetConfig::AAP |
164 ShibTargetConfig::RequestMapper |
165 ShibTargetConfig::LocalExtensions |
166 ShibTargetConfig::Logging
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.");
174 else if (!g_Config->load(config)) {
176 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
177 "Filter startup failed to load configuration, check native log for help.");
181 // Access the implementation-specifics for site mappings.
182 IConfig* conf=g_Config->getINI();
184 const IPropertySet* props=conf->getPropertySet("Local");
186 pair<bool,bool> flag=props->getBool("checkSpoofing");
187 g_checkSpoofing = !flag.first || flag.second;
188 flag=props->getBool("catchAll");
189 g_catchAll = !flag.first || flag.second;
191 pair<bool,const char*> unsetValue=props->getString("unsetHeaderValue");
192 if (unsetValue.first)
193 g_unsetHeaderValue = unsetValue.second;
194 if (g_checkSpoofing) {
195 unsetValue = props->getString("spoofKey");
196 if (unsetValue.first)
197 g_spoofKey = unsetValue.second;
199 LogEvent(NULL, EVENTLOG_WARNING_TYPE, 2100, NULL,
200 "Filter generating a pseudorandom anti-spoofing key, consider setting spoofKey yourself.");
202 ostringstream keystr;
203 keystr << rand() << rand() << rand() << rand();
204 g_spoofKey = keystr.str();
208 const DOMElement* impl=saml::XML::getFirstChildElement(
209 props->getElement(),shibtarget::XML::SHIBTARGET_NS,Implementation
211 if (impl && (impl=saml::XML::getFirstChildElement(impl,shibtarget::XML::SHIBTARGET_NS,ISAPI))) {
212 const XMLCh* ch=impl->getAttributeNS(NULL,normalizeRequest);
213 g_bNormalizeRequest=(!ch || !*ch || *ch==chDigit_1 || *ch==chLatin_t);
214 ch=impl->getAttributeNS(NULL,safeHeaderNames);
215 g_bSafeHeaderNames=(ch && (*ch==chDigit_1 || *ch==chLatin_t));
216 impl=saml::XML::getFirstChildElement(impl,shibtarget::XML::SHIBTARGET_NS,Site);
218 auto_ptr_char id(impl->getAttributeNS(NULL,id));
220 g_Sites.insert(pair<string,site_t>(id.get(),site_t(impl)));
221 impl=saml::XML::getNextSiblingElement(impl,shibtarget::XML::SHIBTARGET_NS,Site);
227 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Filter startup failed with an exception.");
231 pVer->dwFilterVersion=HTTP_FILTER_REVISION;
232 strncpy(pVer->lpszFilterDesc,"Shibboleth ISAPI Filter",SF_MAX_FILTER_DESC_LEN);
233 pVer->dwFlags=(SF_NOTIFY_ORDER_HIGH |
234 SF_NOTIFY_SECURE_PORT |
235 SF_NOTIFY_NONSECURE_PORT |
236 SF_NOTIFY_PREPROC_HEADERS |
238 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter initialized...");
242 extern "C" BOOL WINAPI TerminateFilter(DWORD)
245 g_Config->shutdown();
247 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter shut down...");
251 /* Next up, some suck-free versions of various APIs.
253 You DON'T require people to guess the buffer size and THEN tell them the right size.
254 Returning an LPCSTR is apparently way beyond their ken. Not to mention the fact that
255 constant strings aren't typed as such, making it just that much harder. These versions
256 are now updated to use a special growable buffer object, modeled after the standard
257 string class. The standard string won't work because they left out the option to
258 pre-allocate a non-constant buffer.
264 dynabuf() { bufptr=NULL; buflen=0; }
265 dynabuf(size_t s) { bufptr=new char[buflen=s]; *bufptr=0; }
266 ~dynabuf() { delete[] bufptr; }
267 size_t length() const { return bufptr ? strlen(bufptr) : 0; }
268 size_t size() const { return buflen; }
269 bool empty() const { return length()==0; }
270 void reserve(size_t s, bool keep=false);
271 void erase() { if (bufptr) memset(bufptr,0,buflen); }
272 operator char*() { return bufptr; }
273 bool operator ==(const char* s) const;
274 bool operator !=(const char* s) const { return !(*this==s); }
280 void dynabuf::reserve(size_t s, bool keep)
287 p[buflen]=bufptr[buflen];
293 bool dynabuf::operator==(const char* s) const
295 if (buflen==NULL || s==NULL)
296 return (buflen==NULL && s==NULL);
298 return strcmp(bufptr,s)==0;
301 void GetServerVariable(PHTTP_FILTER_CONTEXT pfc, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
302 throw (bad_alloc, DWORD)
308 while (!pfc->GetServerVariable(pfc,lpszVariable,s,&size))
310 // Grumble. Check the error.
311 DWORD e=GetLastError();
312 if (e==ERROR_INSUFFICIENT_BUFFER)
317 if (bRequired && s.empty())
321 void GetServerVariable(LPEXTENSION_CONTROL_BLOCK lpECB, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
322 throw (bad_alloc, DWORD)
328 while (!lpECB->GetServerVariable(lpECB->ConnID,lpszVariable,s,&size))
330 // Grumble. Check the error.
331 DWORD e=GetLastError();
332 if (e==ERROR_INSUFFICIENT_BUFFER)
337 if (bRequired && s.empty())
341 void GetHeader(PHTTP_FILTER_PREPROC_HEADERS pn, PHTTP_FILTER_CONTEXT pfc,
342 LPSTR lpszName, dynabuf& s, DWORD size=80, bool bRequired=true)
343 throw (bad_alloc, DWORD)
349 while (!pn->GetHeader(pfc,lpszName,s,&size))
351 // Grumble. Check the error.
352 DWORD e=GetLastError();
353 if (e==ERROR_INSUFFICIENT_BUFFER)
358 if (bRequired && s.empty())
362 /****************************************************************************/
365 class ShibTargetIsapiF : public ShibTarget
367 PHTTP_FILTER_CONTEXT m_pfc;
368 PHTTP_FILTER_PREPROC_HEADERS m_pn;
374 ShibTargetIsapiF(PHTTP_FILTER_CONTEXT pfc, PHTTP_FILTER_PREPROC_HEADERS pn, const site_t& site)
375 : m_pfc(pfc), m_pn(pn), m_allhttp(4096), m_firsttime(true) {
377 // URL path always come from IIS.
379 GetHeader(pn,pfc,"url",url,256,false);
381 // Port may come from IIS or from site def.
383 if (!g_bNormalizeRequest || (pfc->fIsSecurePort && site.m_sslport.empty()) || (!pfc->fIsSecurePort && site.m_port.empty()))
384 GetServerVariable(pfc,"SERVER_PORT",port,10);
385 else if (pfc->fIsSecurePort) {
386 strncpy(port,site.m_sslport.c_str(),10);
387 static_cast<char*>(port)[10]=0;
390 strncpy(port,site.m_port.c_str(),10);
391 static_cast<char*>(port)[10]=0;
394 // Scheme may come from site def or be derived from IIS.
395 const char* scheme=site.m_scheme.c_str();
396 if (!scheme || !*scheme || !g_bNormalizeRequest)
397 scheme=pfc->fIsSecurePort ? "https" : "http";
399 // Get the rest of the server variables.
400 dynabuf remote_addr(16),method(5),content_type(32),hostname(32);
401 GetServerVariable(pfc,"SERVER_NAME",hostname,32);
402 GetServerVariable(pfc,"REMOTE_ADDR",remote_addr,16);
403 GetServerVariable(pfc,"REQUEST_METHOD",method,5,false);
404 GetServerVariable(pfc,"CONTENT_TYPE",content_type,32,false);
406 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
407 const char* host=hostname;
408 if (site.m_name!=host && site.m_aliases.find(host)==site.m_aliases.end())
409 host=site.m_name.c_str();
411 init(scheme, host, atoi(port), url, content_type, remote_addr, method);
413 if (!g_spoofKey.empty()) {
414 GetHeader(pn, pfc, "ShibSpoofCheck:", url, 32, false);
415 if (!url.empty() && g_spoofKey == (char*)url)
420 log(LogLevelDebug, "ISAPI filter running more than once");
422 ~ShibTargetIsapiF() {}
424 virtual void log(ShibLogLevel level, const string &msg) {
425 ShibTarget::log(level,msg);
427 virtual string getCookies() const {
429 GetHeader(m_pn, m_pfc, "Cookie:", buf, 128, false);
430 return buf.empty() ? "" : buf;
432 string makeSafeHeader(const char* rawname) const {
434 for (; *rawname; ++rawname) {
435 if (isalnum(*rawname))
440 virtual void clearHeader(const string &name) {
441 if (g_checkSpoofing && m_firsttime) {
442 if (m_allhttp.empty())
443 GetServerVariable(m_pfc,"ALL_HTTP",m_allhttp,4096);
445 // Map to the expected CGI variable name.
446 string transformed("HTTP_");
447 if (g_bSafeHeaderNames) {
448 string temp = makeSafeHeader(name.c_str());
449 for (const char* pch = temp.c_str(); *pch; ++pch) {
451 transformed += toupper(*pch);
455 for (const char* pch = name.c_str(); *pch; ++pch)
456 transformed += (isalnum(*pch) ? toupper(*pch) : '_');
460 if (strstr(m_allhttp, transformed.c_str()))
461 throw SAMLException("Attempt to spoof header ($1) was detected.", params(1, transformed.c_str()));
463 if (g_bSafeHeaderNames) {
464 string hdr = makeSafeHeader(name.c_str());
465 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), const_cast<char*>(g_unsetHeaderValue.c_str()));
467 else if (name == "REMOTE_USER") {
468 m_pn->SetHeader(m_pfc, "remote-user:", const_cast<char*>(g_unsetHeaderValue.c_str()));
469 m_pn->SetHeader(m_pfc, "remote_user:", const_cast<char*>(g_unsetHeaderValue.c_str()));
472 string hdr = name + ':';
473 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), const_cast<char*>(g_unsetHeaderValue.c_str()));
476 virtual void setHeader(const string &name, const string &value) {
477 string hdr = g_bSafeHeaderNames ? makeSafeHeader(name.c_str()) : (name + ':');
478 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), const_cast<char*>(value.c_str()));
480 virtual string getHeader(const string &name) {
481 string hdr = g_bSafeHeaderNames ? makeSafeHeader(name.c_str()) : (name + ':');
483 GetHeader(m_pn, m_pfc, const_cast<char*>(hdr.c_str()), buf, 1024, false);
484 return string(buf.empty() ? "" : buf);
486 virtual void setRemoteUser(const string &user) {
487 setHeader(string("remote-user"), user);
488 if (m_pfc->pFilterContext) {
490 m_pfc->pFilterContext = NULL;
491 else if (m_pfc->pFilterContext = m_pfc->AllocMem(m_pfc, sizeof(char) * (user.length() + 1), NULL))
492 strcpy(reinterpret_cast<char*>(m_pfc->pFilterContext), user.c_str());
495 virtual string getRemoteUser(void) {
496 return getHeader(string("remote-user"));
498 virtual void* sendPage(
501 const string& content_type="text/html",
502 const Iterator<header_t>& headers=EMPTY(header_t)) {
503 string hdr = string ("Connection: close\r\nContent-type: ") + content_type + "\r\n";
504 while (headers.hasNext()) {
505 const header_t& h=headers.next();
506 hdr += h.first + ": " + h.second + "\r\n";
509 const char* codestr="200 OK";
511 case 403: codestr="403 Forbidden"; break;
512 case 404: codestr="404 Not Found"; break;
513 case 500: codestr="500 Server Error"; break;
515 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, (void*)codestr, (DWORD)hdr.c_str(), 0);
516 DWORD resplen = msg.size();
517 m_pfc->WriteClient(m_pfc, (LPVOID)msg.c_str(), &resplen, 0);
518 return (void*)SF_STATUS_REQ_FINISHED;
520 virtual void* sendRedirect(const string& url) {
521 // XXX: Don't support the httpRedirect option, yet.
522 string hdrs=m_cookie + string("Location: ") + url + "\r\n"
523 "Content-Type: text/html\r\n"
524 "Content-Length: 40\r\n"
525 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
526 "Cache-Control: private,no-store,no-cache\r\n\r\n";
527 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER,
528 "302 Please Wait", (DWORD)hdrs.c_str(), 0);
529 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
531 m_pfc->WriteClient(m_pfc, (LPVOID)redmsg, &resplen, 0);
532 return reinterpret_cast<void*>(SF_STATUS_REQ_FINISHED);
534 // XXX: We might not ever hit the 'decline' status in this filter.
535 //virtual void* returnDecline(void) { }
536 virtual void* returnOK(void) { return (void*) SF_STATUS_REQ_NEXT_NOTIFICATION; }
538 // The filter never processes the POST, so stub these methods.
539 virtual void setCookie(const string &name, const string &value) {
540 // Set the cookie for later. Use it during the redirect.
541 m_cookie += "Set-Cookie: " + name + "=" + value + "\r\n";
543 virtual string getArgs(void) { throw runtime_error("getArgs not implemented"); }
544 virtual string getPostData(void) { throw runtime_error("getPostData not implemented"); }
547 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const char* msg)
549 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
550 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
551 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)ctype,0);
552 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Filter Error</TITLE></HEAD><BODY>"
553 "<H1>Shibboleth Filter Error</H1>";
554 DWORD resplen=strlen(xmsg);
555 pfc->WriteClient(pfc,(LPVOID)xmsg,&resplen,0);
557 pfc->WriteClient(pfc,(LPVOID)msg,&resplen,0);
558 static const char* xmsg2="</BODY></HTML>";
559 resplen=strlen(xmsg2);
560 pfc->WriteClient(pfc,(LPVOID)xmsg2,&resplen,0);
561 return SF_STATUS_REQ_FINISHED;
564 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
566 // Is this a log notification?
567 if (notificationType==SF_NOTIFY_LOG) {
568 if (pfc->pFilterContext)
569 ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=reinterpret_cast<char*>(pfc->pFilterContext);
570 return SF_STATUS_REQ_NEXT_NOTIFICATION;
573 PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
575 // Determine web site number. This can't really fail, I don't think.
577 GetServerVariable(pfc,"INSTANCE_ID",buf,10);
579 // Match site instance to host name, skip if no match.
580 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
581 if (map_i==g_Sites.end())
582 return SF_STATUS_REQ_NEXT_NOTIFICATION;
584 ostringstream threadid;
585 threadid << "[" << getpid() << "] isapi_shib" << '\0';
586 saml::NDC ndc(threadid.str().c_str());
588 ShibTargetIsapiF stf(pfc, pn, map_i->second);
590 // "false" because we don't override the Shib settings
591 pair<bool,void*> res = stf.doCheckAuthN();
592 if (!g_spoofKey.empty())
593 pn->SetHeader(pfc, "ShibSpoofCheck:", const_cast<char*>(g_spoofKey.c_str()));
594 if (res.first) return (DWORD)res.second;
596 // "false" because we don't override the Shib settings
597 res = stf.doExportAssertions();
598 if (res.first) return (DWORD)res.second;
600 res = stf.doCheckAuthZ();
601 if (res.first) return (DWORD)res.second;
603 return SF_STATUS_REQ_NEXT_NOTIFICATION;
606 return WriteClientError(pfc,"Out of Memory");
609 if (e==ERROR_NO_DATA)
610 return WriteClientError(pfc,"A required variable or header was empty.");
612 return WriteClientError(pfc,"Shibboleth Filter detected unexpected IIS error.");
614 catch (exception& e) {
615 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
616 return WriteClientError(pfc,"Shibboleth Filter caught an exception, ask administrator to check Event Log for details.");
620 return WriteClientError(pfc,"Shibboleth Filter caught an unknown exception.");
624 return WriteClientError(pfc,"Shibboleth Filter reached unreachable code, save my walrus!");
628 /****************************************************************************/
631 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* msg)
633 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
634 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
635 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
636 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Error</TITLE></HEAD><BODY><H1>Shibboleth Error</H1>";
637 DWORD resplen=strlen(xmsg);
638 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg,&resplen,HSE_IO_SYNC);
640 lpECB->WriteClient(lpECB->ConnID,(LPVOID)msg,&resplen,HSE_IO_SYNC);
641 static const char* xmsg2="</BODY></HTML>";
642 resplen=strlen(xmsg2);
643 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg2,&resplen,HSE_IO_SYNC);
644 return HSE_STATUS_SUCCESS;
648 class ShibTargetIsapiE : public ShibTarget
650 LPEXTENSION_CONTROL_BLOCK m_lpECB;
654 ShibTargetIsapiE(LPEXTENSION_CONTROL_BLOCK lpECB, const site_t& site) {
656 GetServerVariable(lpECB,"HTTPS",ssl,5);
657 bool SSL=(ssl=="on" || ssl=="ON");
659 // URL path always come from IIS.
661 GetServerVariable(lpECB,"URL",url,255);
663 // Port may come from IIS or from site def.
665 if (!g_bNormalizeRequest || (SSL && site.m_sslport.empty()) || (!SSL && site.m_port.empty()))
666 GetServerVariable(lpECB,"SERVER_PORT",port,10);
668 strncpy(port,site.m_sslport.c_str(),10);
669 static_cast<char*>(port)[10]=0;
672 strncpy(port,site.m_port.c_str(),10);
673 static_cast<char*>(port)[10]=0;
676 // Scheme may come from site def or be derived from IIS.
677 const char* scheme=site.m_scheme.c_str();
678 if (!scheme || !*scheme || !g_bNormalizeRequest) {
679 scheme = SSL ? "https" : "http";
682 // Get the other server variables.
683 dynabuf remote_addr(16),hostname(32);
684 GetServerVariable(lpECB, "REMOTE_ADDR", remote_addr, 16);
685 GetServerVariable(lpECB, "SERVER_NAME", hostname, 32);
687 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
688 const char* host=hostname;
689 if (site.m_name!=host && site.m_aliases.find(host)==site.m_aliases.end())
690 host=site.m_name.c_str();
693 * IIS screws us over on PATH_INFO (the hits keep on coming). We need to figure out if
694 * the server is set up for proper PATH_INFO handling, or "IIS sucks rabid weasels mode",
695 * which is the default. No perfect way to tell, but we can take a good guess by checking
696 * whether the URL is a substring of the PATH_INFO:
698 * e.g. for /Shibboleth.sso/SAML/POST
700 * Bad mode (default):
701 * URL: /Shibboleth.sso
702 * PathInfo: /Shibboleth.sso/SAML/POST
705 * URL: /Shibboleth.sso
706 * PathInfo: /SAML/POST
711 // Clearly we're only in bad mode if path info exists at all.
712 if (lpECB->lpszPathInfo && *(lpECB->lpszPathInfo)) {
713 if (strstr(lpECB->lpszPathInfo,url))
714 // Pretty good chance we're in bad mode, unless the PathInfo repeats the path itself.
715 fullurl=lpECB->lpszPathInfo;
718 fullurl+=lpECB->lpszPathInfo;
725 // For consistency with Apache, let's add the query string.
726 if (lpECB->lpszQueryString && *(lpECB->lpszQueryString)) {
728 fullurl+=lpECB->lpszQueryString;
730 init(scheme, host, atoi(port), fullurl.c_str(), lpECB->lpszContentType, remote_addr, lpECB->lpszMethod);
734 ~ShibTargetIsapiE() { }
736 virtual void log(ShibLogLevel level, const string &msg) {
737 ShibTarget::log(level,msg);
739 virtual string getCookies() const {
741 GetServerVariable(m_lpECB, "HTTP_COOKIE", buf, 128, false);
742 return buf.empty() ? "" : buf;
744 virtual void setCookie(const string &name, const string &value) {
745 // Set the cookie for later. Use it during the redirect.
746 m_cookie += "Set-Cookie: " + name + "=" + value + "\r\n";
748 virtual string getArgs(void) {
749 return string(m_lpECB->lpszQueryString ? m_lpECB->lpszQueryString : "");
751 virtual string getPostData(void) {
752 if (m_lpECB->cbTotalBytes > 1024*1024) // 1MB?
753 throw FatalProfileException("Blocked too-large a submission to profile endpoint.");
754 else if (m_lpECB->cbTotalBytes > m_lpECB->cbAvailable) {
755 string cgistr(reinterpret_cast<char*>(m_lpECB->lpbData),m_lpECB->cbAvailable);
757 DWORD datalen=m_lpECB->cbTotalBytes - m_lpECB->cbAvailable;
760 BOOL ret = m_lpECB->ReadClient(m_lpECB->ConnID, buf, &buflen);
762 throw FatalProfileException("Error reading profile submission from browser.");
763 cgistr.append(buf, buflen);
769 return string(reinterpret_cast<char*>(m_lpECB->lpbData),m_lpECB->cbAvailable);
772 virtual void* sendPage(
775 const string& content_type="text/html",
776 const Iterator<header_t>& headers=EMPTY(header_t)) {
777 string hdr = m_cookie + "Connection: close\r\nContent-type: " + content_type + "\r\n";
778 for (int k = 0; k < headers.size(); k++) {
779 hdr += headers[k].first + ": " + headers[k].second + "\r\n";
782 const char* codestr="200 OK";
784 case 403: codestr="403 Forbidden"; break;
785 case 404: codestr="404 Not Found"; break;
786 case 500: codestr="500 Server Error"; break;
788 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, (void*)codestr, 0, (LPDWORD)hdr.c_str());
789 DWORD resplen = msg.size();
790 m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)msg.c_str(), &resplen, HSE_IO_SYNC);
791 return (void*)HSE_STATUS_SUCCESS;
793 virtual void* sendRedirect(const string& url) {
794 // XXX: Don't support the httpRedirect option, yet.
795 string hdrs = m_cookie + "Location: " + url + "\r\n"
796 "Content-Type: text/html\r\n"
797 "Content-Length: 40\r\n"
798 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
799 "Cache-Control: private,no-store,no-cache\r\n\r\n";
800 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER,
801 "302 Moved", 0, (LPDWORD)hdrs.c_str());
802 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
804 m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)redmsg, &resplen, HSE_IO_SYNC);
805 return (void*)HSE_STATUS_SUCCESS;
807 // Decline happens in the POST processor if this isn't the shire url
808 // Note that it can also happen with HTAccess, but we don't support that, yet.
809 virtual void* returnDecline(void) {
811 WriteClientError(m_lpECB, "ISAPI extension can only be invoked to process Shibboleth protocol requests."
812 "Make sure the mapped file extension doesn't match actual content.");
814 virtual void* returnOK(void) { return (void*) HSE_STATUS_SUCCESS; }
816 // Not used in the extension.
817 virtual void clearHeader(const string &name) { throw runtime_error("clearHeader not implemented"); }
818 virtual void setHeader(const string &name, const string &value) { throw runtime_error("setHeader not implemented"); }
819 virtual string getHeader(const string &name) { throw runtime_error("getHeader not implemented"); }
820 virtual void setRemoteUser(const string &user) { throw runtime_error("setRemoteUser not implemented"); }
821 virtual string getRemoteUser(void) { throw runtime_error("getRemoteUser not implemented"); }
824 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
827 ostringstream threadid;
828 threadid << "[" << getpid() << "] isapi_shib_extension" << '\0';
829 saml::NDC ndc(threadid.str().c_str());
831 // Determine web site number. This can't really fail, I don't think.
833 GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
835 // Match site instance to host name, skip if no match.
836 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
837 if (map_i==g_Sites.end())
838 return WriteClientError(lpECB, "Shibboleth Extension not configured for this web site.");
840 ShibTargetIsapiE ste(lpECB, map_i->second);
841 pair<bool,void*> res = ste.doHandler();
842 if (res.first) return (DWORD)res.second;
844 return WriteClientError(lpECB, "Shibboleth Extension failed to process request");
848 return WriteClientError(lpECB,"Out of Memory");
851 if (e==ERROR_NO_DATA)
852 return WriteClientError(lpECB,"A required variable or header was empty.");
854 return WriteClientError(lpECB,"Server detected unexpected IIS error.");
856 catch (exception& e) {
857 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
858 return WriteClientError(lpECB,"Shibboleth Extension caught an exception, check Event Log for details.");
862 return WriteClientError(lpECB,"Shibboleth Extension caught an unknown exception.");
866 // If we get here we've got an error.
867 return HSE_STATUS_ERROR;