2 * Copyright 2001-2005 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 Site[] = { chLatin_S, chLatin_i, chLatin_t, chLatin_e, chNull };
63 site_t(const DOMElement* e)
65 auto_ptr_char n(e->getAttributeNS(NULL,name));
66 auto_ptr_char s(e->getAttributeNS(NULL,scheme));
67 auto_ptr_char p(e->getAttributeNS(NULL,port));
68 auto_ptr_char p2(e->getAttributeNS(NULL,sslport));
69 if (n.get()) m_name=n.get();
70 if (s.get()) m_scheme=s.get();
71 if (p.get()) m_port=p.get();
72 if (p2.get()) m_sslport=p2.get();
73 DOMNodeList* nlist=e->getElementsByTagNameNS(shibtarget::XML::SHIBTARGET_NS,Alias);
74 for (int i=0; nlist && i<nlist->getLength(); i++) {
75 if (nlist->item(i)->hasChildNodes()) {
76 auto_ptr_char alias(nlist->item(i)->getFirstChild()->getNodeValue());
77 m_aliases.insert(alias.get());
81 string m_scheme,m_port,m_sslport,m_name;
82 set<string> m_aliases;
86 ShibTargetConfig* g_Config = NULL;
87 map<string,site_t> g_Sites;
88 bool g_bNormalizeRequest = true;
89 string g_unsetHeaderValue;
90 bool g_checkSpoofing = true;
91 bool g_catchAll = true;
95 LPCSTR lpUNCServerName,
101 LPCSTR messages[] = {message, NULL};
103 HANDLE hElog = RegisterEventSource(lpUNCServerName, "Shibboleth ISAPI Filter");
104 BOOL res = ReportEvent(hElog, wType, 0, dwEventID, lpUserSid, 1, 0, messages, NULL);
105 return (DeregisterEventSource(hElog) && res);
108 extern "C" __declspec(dllexport) BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID)
110 if (fdwReason==DLL_PROCESS_ATTACH)
115 extern "C" BOOL WINAPI GetExtensionVersion(HSE_VERSION_INFO* pVer)
122 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
123 "Extension mode startup not possible, is the DLL loaded as a filter?");
127 pVer->dwExtensionVersion=HSE_VERSION;
128 strncpy(pVer->lpszExtensionDesc,"Shibboleth ISAPI Extension",HSE_MAX_EXT_DLL_NAME_LEN-1);
132 extern "C" BOOL WINAPI TerminateExtension(DWORD)
134 return TRUE; // cleanup should happen when filter unloads
137 extern "C" BOOL WINAPI GetFilterVersion(PHTTP_FILTER_VERSION pVer)
142 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
143 "Reentrant filter initialization, ignoring...");
148 LPCSTR schemadir=getenv("SHIBSCHEMAS");
150 schemadir=SHIB_SCHEMAS;
151 LPCSTR config=getenv("SHIBCONFIG");
154 g_Config=&ShibTargetConfig::getConfig();
155 g_Config->setFeatures(
156 ShibTargetConfig::Listener |
157 ShibTargetConfig::Metadata |
158 ShibTargetConfig::AAP |
159 ShibTargetConfig::RequestMapper |
160 ShibTargetConfig::LocalExtensions |
161 ShibTargetConfig::Logging
163 if (!g_Config->init(schemadir)) {
165 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
166 "Filter startup failed during library initialization, check native log for help.");
169 else if (!g_Config->load(config)) {
171 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
172 "Filter startup failed to load configuration, check native log for help.");
176 // Access the implementation-specifics for site mappings.
177 IConfig* conf=g_Config->getINI();
179 const IPropertySet* props=conf->getPropertySet("Local");
181 pair<bool,const char*> unsetValue=props->getString("unsetHeaderValue");
182 if (unsetValue.first)
183 g_unsetHeaderValue = unsetValue.second;
184 pair<bool,bool> flag=props->getBool("checkSpoofing");
185 g_checkSpoofing = !flag.first || flag.second;
186 flag=props->getBool("catchAll");
187 g_catchAll = !flag.first || flag.second;
189 const DOMElement* impl=saml::XML::getFirstChildElement(
190 props->getElement(),shibtarget::XML::SHIBTARGET_NS,Implementation
192 if (impl && (impl=saml::XML::getFirstChildElement(impl,shibtarget::XML::SHIBTARGET_NS,ISAPI))) {
193 const XMLCh* ch=impl->getAttributeNS(NULL,normalizeRequest);
194 g_bNormalizeRequest=(!ch || !*ch || *ch==chDigit_1 || *ch==chLatin_t);
195 impl=saml::XML::getFirstChildElement(impl,shibtarget::XML::SHIBTARGET_NS,Site);
197 auto_ptr_char id(impl->getAttributeNS(NULL,id));
199 g_Sites.insert(pair<string,site_t>(id.get(),site_t(impl)));
200 impl=saml::XML::getNextSiblingElement(impl,shibtarget::XML::SHIBTARGET_NS,Site);
206 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Filter startup failed with an exception.");
210 pVer->dwFilterVersion=HTTP_FILTER_REVISION;
211 strncpy(pVer->lpszFilterDesc,"Shibboleth ISAPI Filter",SF_MAX_FILTER_DESC_LEN);
212 pVer->dwFlags=(SF_NOTIFY_ORDER_HIGH |
213 SF_NOTIFY_SECURE_PORT |
214 SF_NOTIFY_NONSECURE_PORT |
215 SF_NOTIFY_PREPROC_HEADERS |
217 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter initialized...");
221 extern "C" BOOL WINAPI TerminateFilter(DWORD)
224 g_Config->shutdown();
226 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter shut down...");
230 /* Next up, some suck-free versions of various APIs.
232 You DON'T require people to guess the buffer size and THEN tell them the right size.
233 Returning an LPCSTR is apparently way beyond their ken. Not to mention the fact that
234 constant strings aren't typed as such, making it just that much harder. These versions
235 are now updated to use a special growable buffer object, modeled after the standard
236 string class. The standard string won't work because they left out the option to
237 pre-allocate a non-constant buffer.
243 dynabuf() { bufptr=NULL; buflen=0; }
244 dynabuf(size_t s) { bufptr=new char[buflen=s]; *bufptr=0; }
245 ~dynabuf() { delete[] bufptr; }
246 size_t length() const { return bufptr ? strlen(bufptr) : 0; }
247 size_t size() const { return buflen; }
248 bool empty() const { return length()==0; }
249 void reserve(size_t s, bool keep=false);
250 void erase() { if (bufptr) memset(bufptr,0,buflen); }
251 operator char*() { return bufptr; }
252 bool operator ==(const char* s) const;
253 bool operator !=(const char* s) const { return !(*this==s); }
259 void dynabuf::reserve(size_t s, bool keep)
266 p[buflen]=bufptr[buflen];
272 bool dynabuf::operator==(const char* s) const
274 if (buflen==NULL || s==NULL)
275 return (buflen==NULL && s==NULL);
277 return strcmp(bufptr,s)==0;
280 void GetServerVariable(PHTTP_FILTER_CONTEXT pfc, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
281 throw (bad_alloc, DWORD)
287 while (!pfc->GetServerVariable(pfc,lpszVariable,s,&size))
289 // Grumble. Check the error.
290 DWORD e=GetLastError();
291 if (e==ERROR_INSUFFICIENT_BUFFER)
296 if (bRequired && s.empty())
300 void GetServerVariable(LPEXTENSION_CONTROL_BLOCK lpECB, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
301 throw (bad_alloc, DWORD)
307 while (!lpECB->GetServerVariable(lpECB->ConnID,lpszVariable,s,&size))
309 // Grumble. Check the error.
310 DWORD e=GetLastError();
311 if (e==ERROR_INSUFFICIENT_BUFFER)
316 if (bRequired && s.empty())
320 void GetHeader(PHTTP_FILTER_PREPROC_HEADERS pn, PHTTP_FILTER_CONTEXT pfc,
321 LPSTR lpszName, dynabuf& s, DWORD size=80, bool bRequired=true)
322 throw (bad_alloc, DWORD)
328 while (!pn->GetHeader(pfc,lpszName,s,&size))
330 // Grumble. Check the error.
331 DWORD e=GetLastError();
332 if (e==ERROR_INSUFFICIENT_BUFFER)
337 if (bRequired && s.empty())
341 /****************************************************************************/
344 class ShibTargetIsapiF : public ShibTarget
346 PHTTP_FILTER_CONTEXT m_pfc;
347 PHTTP_FILTER_PREPROC_HEADERS m_pn;
352 ShibTargetIsapiF(PHTTP_FILTER_CONTEXT pfc, PHTTP_FILTER_PREPROC_HEADERS pn, const site_t& site)
353 : m_pfc(pfc), m_pn(pn), m_allhttp(4096) {
355 // URL path always come from IIS.
357 GetHeader(pn,pfc,"url",url,256,false);
359 // Port may come from IIS or from site def.
361 if (!g_bNormalizeRequest || (pfc->fIsSecurePort && site.m_sslport.empty()) || (!pfc->fIsSecurePort && site.m_port.empty()))
362 GetServerVariable(pfc,"SERVER_PORT",port,10);
363 else if (pfc->fIsSecurePort) {
364 strncpy(port,site.m_sslport.c_str(),10);
365 static_cast<char*>(port)[10]=0;
368 strncpy(port,site.m_port.c_str(),10);
369 static_cast<char*>(port)[10]=0;
372 // Scheme may come from site def or be derived from IIS.
373 const char* scheme=site.m_scheme.c_str();
374 if (!scheme || !*scheme || !g_bNormalizeRequest)
375 scheme=pfc->fIsSecurePort ? "https" : "http";
377 // Get the rest of the server variables.
378 dynabuf remote_addr(16),method(5),content_type(32),hostname(32);
379 GetServerVariable(pfc,"SERVER_NAME",hostname,32);
380 GetServerVariable(pfc,"REMOTE_ADDR",remote_addr,16);
381 GetServerVariable(pfc,"REQUEST_METHOD",method,5,false);
382 GetServerVariable(pfc,"CONTENT_TYPE",content_type,32,false);
384 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
385 const char* host=hostname;
386 if (site.m_name!=host && site.m_aliases.find(host)==site.m_aliases.end())
387 host=site.m_name.c_str();
389 init(scheme, host, atoi(port), url, content_type, remote_addr, method);
391 ~ShibTargetIsapiF() {}
393 virtual void log(ShibLogLevel level, const string &msg) {
394 ShibTarget::log(level,msg);
396 virtual string getCookies() const {
398 GetHeader(m_pn, m_pfc, "Cookie:", buf, 128, false);
399 return buf.empty() ? "" : buf;
402 virtual void clearHeader(const string &name) {
403 if (g_checkSpoofing) {
404 if (m_allhttp.empty())
405 GetServerVariable(m_pfc,"ALL_HTTP",m_allhttp,4096);
407 // Map to the expected CGI variable name.
408 string transformed("HTTP_");
409 const char* pch = name.c_str();
411 transformed += (isalnum(*pch) ? toupper(*pch) : '_');
416 if (strstr(m_allhttp, transformed.c_str()))
417 throw SAMLException("Attempt to spoof header ($1) was detected.", params(1, name.c_str()));
419 string hdr = (name=="REMOTE_USER" ? "remote-user" : name) + ":";
420 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), const_cast<char*>(g_unsetHeaderValue.c_str()));
422 virtual void setHeader(const string &name, const string &value) {
423 string hdr = name + ":";
424 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()),
425 const_cast<char*>(value.c_str()));
427 virtual string getHeader(const string &name) {
428 string hdr = name + ":";
430 GetHeader(m_pn, m_pfc, const_cast<char*>(hdr.c_str()), buf, 1024, false);
433 virtual void setRemoteUser(const string &user) {
434 setHeader(string("remote-user"), user);
436 virtual string getRemoteUser(void) {
437 return getHeader(string("remote-user"));
439 virtual void* sendPage(
442 const string& content_type="text/html",
443 const Iterator<header_t>& headers=EMPTY(header_t)) {
444 string hdr = string ("Connection: close\r\nContent-type: ") + content_type + "\r\n";
445 while (headers.hasNext()) {
446 const header_t& h=headers.next();
447 hdr += h.first + ": " + h.second + "\r\n";
450 const char* codestr="200 OK";
452 case 403: codestr="403 Forbidden"; break;
453 case 404: codestr="404 Not Found"; break;
454 case 500: codestr="500 Server Error"; break;
456 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, (void*)codestr, (DWORD)hdr.c_str(), 0);
457 DWORD resplen = msg.size();
458 m_pfc->WriteClient(m_pfc, (LPVOID)msg.c_str(), &resplen, 0);
459 return (void*)SF_STATUS_REQ_FINISHED;
461 virtual void* sendRedirect(const string& url) {
462 // XXX: Don't support the httpRedirect option, yet.
463 string hdrs=m_cookie + string("Location: ") + url + "\r\n"
464 "Content-Type: text/html\r\n"
465 "Content-Length: 40\r\n"
466 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
467 "Cache-Control: private,no-store,no-cache\r\n\r\n";
468 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER,
469 "302 Please Wait", (DWORD)hdrs.c_str(), 0);
470 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
472 m_pfc->WriteClient(m_pfc, (LPVOID)redmsg, &resplen, 0);
473 return reinterpret_cast<void*>(SF_STATUS_REQ_FINISHED);
475 // XXX: We might not ever hit the 'decline' status in this filter.
476 //virtual void* returnDecline(void) { }
477 virtual void* returnOK(void) { return (void*) SF_STATUS_REQ_NEXT_NOTIFICATION; }
479 // The filter never processes the POST, so stub these methods.
480 virtual void setCookie(const string &name, const string &value) {
481 // Set the cookie for later. Use it during the redirect.
482 m_cookie += "Set-Cookie: " + name + "=" + value + "\r\n";
484 virtual string getArgs(void) { throw runtime_error("getArgs not implemented"); }
485 virtual string getPostData(void) { throw runtime_error("getPostData not implemented"); }
488 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const char* msg)
490 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
491 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
492 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)ctype,0);
493 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Filter Error</TITLE></HEAD><BODY>"
494 "<H1>Shibboleth Filter Error</H1>";
495 DWORD resplen=strlen(xmsg);
496 pfc->WriteClient(pfc,(LPVOID)xmsg,&resplen,0);
498 pfc->WriteClient(pfc,(LPVOID)msg,&resplen,0);
499 static const char* xmsg2="</BODY></HTML>";
500 resplen=strlen(xmsg2);
501 pfc->WriteClient(pfc,(LPVOID)xmsg2,&resplen,0);
502 return SF_STATUS_REQ_FINISHED;
505 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
507 // Is this a log notification?
508 if (notificationType==SF_NOTIFY_LOG) {
509 if (pfc->pFilterContext)
510 ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=static_cast<LPCSTR>(pfc->pFilterContext);
511 return SF_STATUS_REQ_NEXT_NOTIFICATION;
514 PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
516 // Determine web site number. This can't really fail, I don't think.
518 GetServerVariable(pfc,"INSTANCE_ID",buf,10);
520 // Match site instance to host name, skip if no match.
521 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
522 if (map_i==g_Sites.end())
523 return SF_STATUS_REQ_NEXT_NOTIFICATION;
525 ostringstream threadid;
526 threadid << "[" << getpid() << "] isapi_shib" << '\0';
527 saml::NDC ndc(threadid.str().c_str());
529 ShibTargetIsapiF stf(pfc, pn, map_i->second);
531 // "false" because we don't override the Shib settings
532 pair<bool,void*> res = stf.doCheckAuthN();
533 if (res.first) return (DWORD)res.second;
535 // "false" because we don't override the Shib settings
536 res = stf.doExportAssertions();
537 if (res.first) return (DWORD)res.second;
539 res = stf.doCheckAuthZ();
540 if (res.first) return (DWORD)res.second;
542 return SF_STATUS_REQ_NEXT_NOTIFICATION;
545 return WriteClientError(pfc,"Out of Memory");
548 if (e==ERROR_NO_DATA)
549 return WriteClientError(pfc,"A required variable or header was empty.");
551 return WriteClientError(pfc,"Shibboleth Filter detected unexpected IIS error.");
553 catch (exception& e) {
554 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
555 return WriteClientError(pfc,"Shibboleth Filter caught an exception, ask administrator to check Event Log for details.");
559 return WriteClientError(pfc,"Shibboleth Filter caught an unknown exception.");
563 return WriteClientError(pfc,"Shibboleth Filter reached unreachable code, save my walrus!");
567 /****************************************************************************/
570 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* msg)
572 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
573 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
574 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
575 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Error</TITLE></HEAD><BODY><H1>Shibboleth Error</H1>";
576 DWORD resplen=strlen(xmsg);
577 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg,&resplen,HSE_IO_SYNC);
579 lpECB->WriteClient(lpECB->ConnID,(LPVOID)msg,&resplen,HSE_IO_SYNC);
580 static const char* xmsg2="</BODY></HTML>";
581 resplen=strlen(xmsg2);
582 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg2,&resplen,HSE_IO_SYNC);
583 return HSE_STATUS_SUCCESS;
587 class ShibTargetIsapiE : public ShibTarget
589 LPEXTENSION_CONTROL_BLOCK m_lpECB;
593 ShibTargetIsapiE(LPEXTENSION_CONTROL_BLOCK lpECB, const site_t& site) {
595 GetServerVariable(lpECB,"HTTPS",ssl,5);
596 bool SSL=(ssl=="on" || ssl=="ON");
598 // URL path always come from IIS.
600 GetServerVariable(lpECB,"URL",url,255);
602 // Port may come from IIS or from site def.
604 if (!g_bNormalizeRequest || (SSL && site.m_sslport.empty()) || (!SSL && site.m_port.empty()))
605 GetServerVariable(lpECB,"SERVER_PORT",port,10);
607 strncpy(port,site.m_sslport.c_str(),10);
608 static_cast<char*>(port)[10]=0;
611 strncpy(port,site.m_port.c_str(),10);
612 static_cast<char*>(port)[10]=0;
615 // Scheme may come from site def or be derived from IIS.
616 const char* scheme=site.m_scheme.c_str();
617 if (!scheme || !*scheme || !g_bNormalizeRequest) {
618 scheme = SSL ? "https" : "http";
621 // Get the other server variables.
622 dynabuf remote_addr(16),hostname(32);
623 GetServerVariable(lpECB, "REMOTE_ADDR", remote_addr, 16);
624 GetServerVariable(lpECB, "SERVER_NAME", hostname, 32);
626 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
627 const char* host=hostname;
628 if (site.m_name!=host && site.m_aliases.find(host)==site.m_aliases.end())
629 host=site.m_name.c_str();
632 * IIS screws us over on PATH_INFO (the hits keep on coming). We need to figure out if
633 * the server is set up for proper PATH_INFO handling, or "IIS sucks rabid weasels mode",
634 * which is the default. No perfect way to tell, but we can take a good guess by checking
635 * whether the URL is a substring of the PATH_INFO:
637 * e.g. for /Shibboleth.sso/SAML/POST
639 * Bad mode (default):
640 * URL: /Shibboleth.sso
641 * PathInfo: /Shibboleth.sso/SAML/POST
644 * URL: /Shibboleth.sso
645 * PathInfo: /SAML/POST
650 // Clearly we're only in bad mode if path info exists at all.
651 if (lpECB->lpszPathInfo && *(lpECB->lpszPathInfo)) {
652 if (strstr(lpECB->lpszPathInfo,url))
653 // Pretty good chance we're in bad mode, unless the PathInfo repeats the path itself.
654 fullurl=lpECB->lpszPathInfo;
657 fullurl+=lpECB->lpszPathInfo;
664 // For consistency with Apache, let's add the query string.
665 if (lpECB->lpszQueryString && *(lpECB->lpszQueryString)) {
667 fullurl+=lpECB->lpszQueryString;
669 init(scheme, host, atoi(port), fullurl.c_str(), lpECB->lpszContentType, remote_addr, lpECB->lpszMethod);
673 ~ShibTargetIsapiE() { }
675 virtual void log(ShibLogLevel level, const string &msg) {
676 ShibTarget::log(level,msg);
677 if (level == LogLevelError)
678 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
680 virtual string getCookies() const {
682 GetServerVariable(m_lpECB, "HTTP_COOKIE", buf, 128, false);
683 return buf.empty() ? "" : buf;
685 virtual void setCookie(const string &name, const string &value) {
686 // Set the cookie for later. Use it during the redirect.
687 m_cookie += "Set-Cookie: " + name + "=" + value + "\r\n";
689 virtual string getArgs(void) {
690 return string(m_lpECB->lpszQueryString ? m_lpECB->lpszQueryString : "");
692 virtual string getPostData(void) {
693 if (m_lpECB->cbTotalBytes > 1024*1024) // 1MB?
694 throw FatalProfileException("Blocked too-large a submission to profile endpoint.");
695 else if (m_lpECB->cbTotalBytes != m_lpECB->cbAvailable) {
698 DWORD datalen=m_lpECB->cbTotalBytes;
701 BOOL ret = m_lpECB->ReadClient(m_lpECB->ConnID, buf, &buflen);
703 throw FatalProfileException("Error reading profile submission from browser.");
704 cgistr.append(buf, buflen);
710 return string(reinterpret_cast<char*>(m_lpECB->lpbData),m_lpECB->cbAvailable);
712 virtual void* sendPage(
715 const string& content_type="text/html",
716 const Iterator<header_t>& headers=EMPTY(header_t)) {
717 string hdr = m_cookie + "Connection: close\r\nContent-type: " + content_type + "\r\n";
718 for (int k = 0; k < headers.size(); k++) {
719 hdr += headers[k].first + ": " + headers[k].second + "\r\n";
722 const char* codestr="200 OK";
724 case 403: codestr="403 Forbidden"; break;
725 case 404: codestr="404 Not Found"; break;
726 case 500: codestr="500 Server Error"; break;
728 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, (void*)codestr, 0, (LPDWORD)hdr.c_str());
729 DWORD resplen = msg.size();
730 m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)msg.c_str(), &resplen, HSE_IO_SYNC);
731 return (void*)HSE_STATUS_SUCCESS;
733 virtual void* sendRedirect(const string& url) {
734 // XXX: Don't support the httpRedirect option, yet.
735 string hdrs = m_cookie + "Location: " + url + "\r\n"
736 "Content-Type: text/html\r\n"
737 "Content-Length: 40\r\n"
738 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
739 "Cache-Control: private,no-store,no-cache\r\n\r\n";
740 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER,
741 "302 Moved", 0, (LPDWORD)hdrs.c_str());
742 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
744 m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)redmsg, &resplen, HSE_IO_SYNC);
745 return (void*)HSE_STATUS_SUCCESS;
747 // Decline happens in the POST processor if this isn't the shire url
748 // Note that it can also happen with HTAccess, but we don't support that, yet.
749 virtual void* returnDecline(void) {
751 WriteClientError(m_lpECB, "ISAPI extension can only be invoked to process Shibboleth protocol requests."
752 "Make sure the mapped file extension doesn't match actual content.");
754 virtual void* returnOK(void) { return (void*) HSE_STATUS_SUCCESS; }
756 // Not used in the extension.
757 virtual void clearHeader(const string &name) { throw runtime_error("clearHeader not implemented"); }
758 virtual void setHeader(const string &name, const string &value) { throw runtime_error("setHeader not implemented"); }
759 virtual string getHeader(const string &name) { throw runtime_error("getHeader not implemented"); }
760 virtual void setRemoteUser(const string &user) { throw runtime_error("setRemoteUser not implemented"); }
761 virtual string getRemoteUser(void) { throw runtime_error("getRemoteUser not implemented"); }
764 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
767 ostringstream threadid;
768 threadid << "[" << getpid() << "] isapi_shib_extension" << '\0';
769 saml::NDC ndc(threadid.str().c_str());
771 // Determine web site number. This can't really fail, I don't think.
773 GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
775 // Match site instance to host name, skip if no match.
776 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
777 if (map_i==g_Sites.end())
778 return WriteClientError(lpECB, "Shibboleth Extension not configured for this web site.");
780 ShibTargetIsapiE ste(lpECB, map_i->second);
781 pair<bool,void*> res = ste.doHandler();
782 if (res.first) return (DWORD)res.second;
784 return WriteClientError(lpECB, "Shibboleth Extension failed to process request");
788 return WriteClientError(lpECB,"Out of Memory");
791 if (e==ERROR_NO_DATA)
792 return WriteClientError(lpECB,"A required variable or header was empty.");
794 return WriteClientError(lpECB,"Server detected unexpected IIS error.");
796 catch (exception& e) {
797 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
798 return WriteClientError(lpECB,"Shibboleth Extension caught an exception, check Event Log for details.");
802 return WriteClientError(lpECB,"Shibboleth Extension caught an unknown exception.");
806 // If we get here we've got an error.
807 return HSE_STATUS_ERROR;