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"
25 #define _CRT_NONSTDC_NO_DEPRECATE 1
26 #define _CRT_SECURE_NO_DEPRECATE 1
29 #include <saml/saml.h>
30 #include <shib/shib.h>
31 #include <shib-target/shib-target.h>
32 #include <shibsp/SPConfig.h>
43 using namespace shibsp;
44 using namespace shibtarget;
46 using namespace xmltooling;
51 static const XMLCh name[] = { chLatin_n, chLatin_a, chLatin_m, chLatin_e, chNull };
52 static const XMLCh port[] = { chLatin_p, chLatin_o, chLatin_r, chLatin_t, chNull };
53 static const XMLCh sslport[] = { chLatin_s, chLatin_s, chLatin_l, chLatin_p, chLatin_o, chLatin_r, chLatin_t, chNull };
54 static const XMLCh scheme[] = { chLatin_s, chLatin_c, chLatin_h, chLatin_e, chLatin_m, chLatin_e, chNull };
55 static const XMLCh id[] = { chLatin_i, chLatin_d, chNull };
56 static const XMLCh Implementation[] =
57 { 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 };
58 static const XMLCh ISAPI[] = { chLatin_I, chLatin_S, chLatin_A, chLatin_P, chLatin_I, chNull };
59 static const XMLCh Alias[] = { chLatin_A, chLatin_l, chLatin_i, chLatin_a, chLatin_s, chNull };
60 static const XMLCh normalizeRequest[] =
61 { chLatin_n, chLatin_o, chLatin_r, chLatin_m, chLatin_a, chLatin_l, chLatin_i, chLatin_z, chLatin_e,
62 chLatin_R, chLatin_e, chLatin_q, chLatin_u, chLatin_e, chLatin_s, chLatin_t, 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 (unsigned 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;
96 LPCSTR lpUNCServerName,
102 LPCSTR messages[] = {message, NULL};
104 HANDLE hElog = RegisterEventSource(lpUNCServerName, "Shibboleth ISAPI Filter");
105 BOOL res = ReportEvent(hElog, wType, 0, dwEventID, lpUserSid, 1, 0, messages, NULL);
106 return (DeregisterEventSource(hElog) && res);
109 extern "C" __declspec(dllexport) BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID)
111 if (fdwReason==DLL_PROCESS_ATTACH)
116 extern "C" BOOL WINAPI GetExtensionVersion(HSE_VERSION_INFO* pVer)
123 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
124 "Extension mode startup not possible, is the DLL loaded as a filter?");
128 pVer->dwExtensionVersion=HSE_VERSION;
129 strncpy(pVer->lpszExtensionDesc,"Shibboleth ISAPI Extension",HSE_MAX_EXT_DLL_NAME_LEN-1);
133 extern "C" BOOL WINAPI TerminateExtension(DWORD)
135 return TRUE; // cleanup should happen when filter unloads
138 extern "C" BOOL WINAPI GetFilterVersion(PHTTP_FILTER_VERSION pVer)
143 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
144 "Reentrant filter initialization, ignoring...");
152 LPCSTR schemadir=getenv("SHIBSCHEMAS");
154 schemadir=SHIB_SCHEMAS;
155 LPCSTR config=getenv("SHIBCONFIG");
158 g_Config=&ShibTargetConfig::getConfig();
159 SPConfig::getConfig().setFeatures(
164 SPConfig::RequestMapper |
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.");
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 PropertySet* props=conf->getPropertySet("Local");
186 const DOMElement* impl=saml::XML::getFirstChildElement(
187 props->getElement(),shibtarget::XML::SHIBTARGET_NS,Implementation
189 if (impl && (impl=saml::XML::getFirstChildElement(impl,shibtarget::XML::SHIBTARGET_NS,ISAPI))) {
190 const XMLCh* flag=impl->getAttributeNS(NULL,normalizeRequest);
191 g_bNormalizeRequest=(!flag || !*flag || *flag==chDigit_1 || *flag==chLatin_t);
192 impl=saml::XML::getFirstChildElement(impl,shibtarget::XML::SHIBTARGET_NS,Site);
194 auto_ptr_char id(impl->getAttributeNS(NULL,id));
196 g_Sites.insert(pair<string,site_t>(id.get(),site_t(impl)));
197 impl=saml::XML::getNextSiblingElement(impl,shibtarget::XML::SHIBTARGET_NS,Site);
205 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;
350 ShibTargetIsapiF(PHTTP_FILTER_CONTEXT pfc, PHTTP_FILTER_PREPROC_HEADERS pn, const site_t& site) {
352 // URL path always come from IIS.
354 GetHeader(pn,pfc,"url",url,256,false);
356 // Port may come from IIS or from site def.
358 if (!g_bNormalizeRequest || (pfc->fIsSecurePort && site.m_sslport.empty()) || (!pfc->fIsSecurePort && site.m_port.empty()))
359 GetServerVariable(pfc,"SERVER_PORT",port,10);
360 else if (pfc->fIsSecurePort) {
361 strncpy(port,site.m_sslport.c_str(),10);
362 static_cast<char*>(port)[10]=0;
365 strncpy(port,site.m_port.c_str(),10);
366 static_cast<char*>(port)[10]=0;
369 // Scheme may come from site def or be derived from IIS.
370 const char* scheme=site.m_scheme.c_str();
371 if (!scheme || !*scheme || !g_bNormalizeRequest)
372 scheme=pfc->fIsSecurePort ? "https" : "http";
374 // Get the rest of the server variables.
375 dynabuf remote_addr(16),method(5),content_type(32),hostname(32);
376 GetServerVariable(pfc,"SERVER_NAME",hostname,32);
377 GetServerVariable(pfc,"REMOTE_ADDR",remote_addr,16);
378 GetServerVariable(pfc,"REQUEST_METHOD",method,5,false);
379 GetServerVariable(pfc,"CONTENT_TYPE",content_type,32,false);
381 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
382 const char* host=hostname;
383 if (site.m_name!=host && site.m_aliases.find(host)==site.m_aliases.end())
384 host=site.m_name.c_str();
386 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);
395 if (level == LogLevelError)
396 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
398 virtual string getCookies() const {
400 GetHeader(m_pn, m_pfc, "Cookie:", buf, 128, false);
401 return buf.empty() ? "" : buf;
404 virtual void clearHeader(const string &name) {
405 string hdr = (name=="REMOTE_USER" ? "remote-user" : name) + ":";
406 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), "");
408 virtual void setHeader(const string &name, const string &value) {
409 string hdr = name + ":";
410 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()),
411 const_cast<char*>(value.c_str()));
413 virtual string getHeader(const string &name) {
414 string hdr = name + ":";
416 GetHeader(m_pn, m_pfc, const_cast<char*>(hdr.c_str()), buf, 1024, false);
419 virtual void setRemoteUser(const string &user) {
420 setHeader(string("remote-user"), user);
422 virtual string getRemoteUser(void) {
423 return getHeader(string("remote-user"));
425 virtual void* sendPage(
428 const string& content_type="text/html",
429 const Iterator<header_t>& headers=EMPTY(header_t)) {
430 string hdr = string ("Connection: close\r\nContent-type: ") + content_type + "\r\n";
431 while (headers.hasNext()) {
432 const header_t& h=headers.next();
433 hdr += h.first + ": " + h.second + "\r\n";
436 const char* codestr="200 OK";
438 case 403: codestr="403 Forbidden"; break;
439 case 404: codestr="404 Not Found"; break;
440 case 500: codestr="500 Server Error"; break;
442 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, (void*)codestr, (DWORD)hdr.c_str(), 0);
443 DWORD resplen = msg.size();
444 m_pfc->WriteClient(m_pfc, (LPVOID)msg.c_str(), &resplen, 0);
445 return (void*)SF_STATUS_REQ_FINISHED;
447 virtual void* sendRedirect(const string& url) {
448 // XXX: Don't support the httpRedirect option, yet.
449 string hdrs=m_cookie + string("Location: ") + url + "\r\n"
450 "Content-Type: text/html\r\n"
451 "Content-Length: 40\r\n"
452 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
453 "Cache-Control: private,no-store,no-cache\r\n\r\n";
454 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER,
455 "302 Please Wait", (DWORD)hdrs.c_str(), 0);
456 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
458 m_pfc->WriteClient(m_pfc, (LPVOID)redmsg, &resplen, 0);
459 return reinterpret_cast<void*>(SF_STATUS_REQ_FINISHED);
461 // XXX: We might not ever hit the 'decline' status in this filter.
462 //virtual void* returnDecline(void) { }
463 virtual void* returnOK(void) { return (void*) SF_STATUS_REQ_NEXT_NOTIFICATION; }
465 // The filter never processes the POST, so stub these methods.
466 virtual void setCookie(const string &name, const string &value) {
467 // Set the cookie for later. Use it during the redirect.
468 m_cookie += "Set-Cookie: " + name + "=" + value + "\r\n";
470 virtual const char* getQueryString() const { throw runtime_error("getQueryString not implemented"); }
471 virtual const char* getRequestBody() const { throw runtime_error("getRequestBody not implemented"); }
474 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const char* msg)
476 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
477 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
478 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)ctype,0);
479 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Filter Error</TITLE></HEAD><BODY>"
480 "<H1>Shibboleth Filter Error</H1>";
481 DWORD resplen=strlen(xmsg);
482 pfc->WriteClient(pfc,(LPVOID)xmsg,&resplen,0);
484 pfc->WriteClient(pfc,(LPVOID)msg,&resplen,0);
485 static const char* xmsg2="</BODY></HTML>";
486 resplen=strlen(xmsg2);
487 pfc->WriteClient(pfc,(LPVOID)xmsg2,&resplen,0);
488 return SF_STATUS_REQ_FINISHED;
491 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
493 // Is this a log notification?
494 if (notificationType==SF_NOTIFY_LOG)
496 if (pfc->pFilterContext)
497 ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=static_cast<LPCSTR>(pfc->pFilterContext);
498 return SF_STATUS_REQ_NEXT_NOTIFICATION;
501 PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
504 // Determine web site number. This can't really fail, I don't think.
506 GetServerVariable(pfc,"INSTANCE_ID",buf,10);
508 // Match site instance to host name, skip if no match.
509 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
510 if (map_i==g_Sites.end())
511 return SF_STATUS_REQ_NEXT_NOTIFICATION;
513 ostringstream threadid;
514 threadid << "[" << getpid() << "] isapi_shib" << '\0';
515 saml::NDC ndc(threadid.str().c_str());
517 ShibTargetIsapiF stf(pfc, pn, map_i->second);
519 // "false" because we don't override the Shib settings
520 pair<bool,void*> res = stf.doCheckAuthN();
521 if (res.first) return (DWORD)res.second;
523 // "false" because we don't override the Shib settings
524 res = stf.doExportAssertions();
525 if (res.first) return (DWORD)res.second;
527 res = stf.doCheckAuthZ();
528 if (res.first) return (DWORD)res.second;
530 return SF_STATUS_REQ_NEXT_NOTIFICATION;
533 return WriteClientError(pfc,"Out of Memory");
536 if (e==ERROR_NO_DATA)
537 return WriteClientError(pfc,"A required variable or header was empty.");
539 return WriteClientError(pfc,"Shibboleth Filter detected unexpected IIS error.");
541 catch (SAMLException& e) {
542 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
543 return WriteClientError(pfc,"Shibboleth Filter caught an exception, check Event Log for details.");
547 return WriteClientError(pfc,"Shibboleth Filter caught an unknown exception.");
551 return WriteClientError(pfc,"Shibboleth Filter reached unreachable code, save my walrus!");
555 /****************************************************************************/
558 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* msg)
560 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
561 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
562 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
563 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Error</TITLE></HEAD><BODY><H1>Shibboleth Error</H1>";
564 DWORD resplen=strlen(xmsg);
565 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg,&resplen,HSE_IO_SYNC);
567 lpECB->WriteClient(lpECB->ConnID,(LPVOID)msg,&resplen,HSE_IO_SYNC);
568 static const char* xmsg2="</BODY></HTML>";
569 resplen=strlen(xmsg2);
570 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg2,&resplen,HSE_IO_SYNC);
571 return HSE_STATUS_SUCCESS;
575 class ShibTargetIsapiE : public ShibTarget
577 LPEXTENSION_CONTROL_BLOCK m_lpECB;
579 mutable string m_body;
580 mutable bool m_gotBody;
583 ShibTargetIsapiE(LPEXTENSION_CONTROL_BLOCK lpECB, const site_t& site) : m_gotBody(false) {
585 GetServerVariable(lpECB,"HTTPS",ssl,5);
586 bool SSL=(ssl=="on" || ssl=="ON");
588 // URL path always come from IIS.
590 GetServerVariable(lpECB,"URL",url,255);
592 // Port may come from IIS or from site def.
594 if (!g_bNormalizeRequest || (SSL && site.m_sslport.empty()) || (!SSL && site.m_port.empty()))
595 GetServerVariable(lpECB,"SERVER_PORT",port,10);
597 strncpy(port,site.m_sslport.c_str(),10);
598 static_cast<char*>(port)[10]=0;
601 strncpy(port,site.m_port.c_str(),10);
602 static_cast<char*>(port)[10]=0;
605 // Scheme may come from site def or be derived from IIS.
606 const char* scheme=site.m_scheme.c_str();
607 if (!scheme || !*scheme || !g_bNormalizeRequest) {
608 scheme = SSL ? "https" : "http";
611 // Get the other server variables.
612 dynabuf remote_addr(16),hostname(32);
613 GetServerVariable(lpECB, "REMOTE_ADDR", remote_addr, 16);
614 GetServerVariable(lpECB, "SERVER_NAME", hostname, 32);
616 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
617 const char* host=hostname;
618 if (site.m_name!=host && site.m_aliases.find(host)==site.m_aliases.end())
619 host=site.m_name.c_str();
622 * IIS screws us over on PATH_INFO (the hits keep on coming). We need to figure out if
623 * the server is set up for proper PATH_INFO handling, or "IIS sucks rabid weasels mode",
624 * which is the default. No perfect way to tell, but we can take a good guess by checking
625 * whether the URL is a substring of the PATH_INFO:
627 * e.g. for /Shibboleth.sso/SAML/POST
629 * Bad mode (default):
630 * URL: /Shibboleth.sso
631 * PathInfo: /Shibboleth.sso/SAML/POST
634 * URL: /Shibboleth.sso
635 * PathInfo: /SAML/POST
640 // Clearly we're only in bad mode if path info exists at all.
641 if (lpECB->lpszPathInfo && *(lpECB->lpszPathInfo)) {
642 if (strstr(lpECB->lpszPathInfo,url))
643 // Pretty good chance we're in bad mode, unless the PathInfo repeats the path itself.
644 fullurl=lpECB->lpszPathInfo;
647 fullurl+=lpECB->lpszPathInfo;
651 // For consistency with Apache, let's add the query string.
652 if (lpECB->lpszQueryString && *(lpECB->lpszQueryString)) {
654 fullurl+=lpECB->lpszQueryString;
656 init(scheme, host, atoi(port), fullurl.c_str(), lpECB->lpszContentType, remote_addr, lpECB->lpszMethod);
660 ~ShibTargetIsapiE() { }
662 virtual void log(ShibLogLevel level, const string &msg) {
663 ShibTarget::log(level,msg);
664 if (level == LogLevelError)
665 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
667 virtual string getCookies() const {
669 GetServerVariable(m_lpECB, "HTTP_COOKIE", buf, 128, false);
670 return buf.empty() ? "" : buf;
672 virtual void setCookie(const string &name, const string &value) {
673 // Set the cookie for later. Use it during the redirect.
674 m_cookie += "Set-Cookie: " + name + "=" + value + "\r\n";
676 virtual const char* getQueryString() const {
677 return m_lpECB->lpszQueryString;
679 virtual const char* getRequestBody() const {
681 return m_body.c_str();
682 if (m_lpECB->cbTotalBytes > 1024*1024) // 1MB?
683 throw SAMLException("Size of POST request body exceeded limit.");
684 else if (m_lpECB->cbTotalBytes != m_lpECB->cbAvailable) {
687 DWORD datalen=m_lpECB->cbTotalBytes;
690 BOOL ret = m_lpECB->ReadClient(m_lpECB->ConnID, buf, &buflen);
692 throw SAMLException("Error reading POST request body from browser.");
693 m_body.append(buf, buflen);
699 m_body.assign(reinterpret_cast<char*>(m_lpECB->lpbData),m_lpECB->cbAvailable);
701 return m_body.c_str();
703 virtual void* sendPage(
706 const string& content_type="text/html",
707 const Iterator<header_t>& headers=EMPTY(header_t)) {
708 string hdr = string ("Connection: close\r\nContent-type: ") + content_type + "\r\n";
709 for (unsigned int k = 0; k < headers.size(); k++) {
710 hdr += headers[k].first + ": " + headers[k].second + "\r\n";
713 const char* codestr="200 OK";
715 case 403: codestr="403 Forbidden"; break;
716 case 404: codestr="404 Not Found"; break;
717 case 500: codestr="500 Server Error"; break;
719 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, (void*)codestr, 0, (LPDWORD)hdr.c_str());
720 DWORD resplen = msg.size();
721 m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)msg.c_str(), &resplen, HSE_IO_SYNC);
722 return (void*)HSE_STATUS_SUCCESS;
724 virtual void* sendRedirect(const string& url) {
725 // XXX: Don't support the httpRedirect option, yet.
726 string hdrs = m_cookie + "Location: " + url + "\r\n"
727 "Content-Type: text/html\r\n"
728 "Content-Length: 40\r\n"
729 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
730 "Cache-Control: private,no-store,no-cache\r\n\r\n";
731 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER,
732 "302 Moved", 0, (LPDWORD)hdrs.c_str());
733 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
735 m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)redmsg, &resplen, HSE_IO_SYNC);
736 return (void*)HSE_STATUS_SUCCESS;
738 // Decline happens in the POST processor if this isn't the shire url
739 // Note that it can also happen with HTAccess, but we don't support that, yet.
740 virtual void* returnDecline(void) {
742 WriteClientError(m_lpECB, "ISAPI extension can only be invoked to process Shibboleth protocol requests."
743 "Make sure the mapped file extension doesn't match actual content.");
745 virtual void* returnOK(void) { return (void*) HSE_STATUS_SUCCESS; }
747 // Not used in the extension.
748 virtual void clearHeader(const string &name) { throw runtime_error("clearHeader not implemented"); }
749 virtual void setHeader(const string &name, const string &value) { throw runtime_error("setHeader not implemented"); }
750 virtual string getHeader(const string &name) { throw runtime_error("getHeader not implemented"); }
751 virtual void setRemoteUser(const string &user) { throw runtime_error("setRemoteUser not implemented"); }
752 virtual string getRemoteUser(void) { throw runtime_error("getRemoteUser not implemented"); }
755 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
758 const IApplication* application=NULL;
760 ostringstream threadid;
761 threadid << "[" << getpid() << "] isapi_shib_extension" << '\0';
762 saml::NDC ndc(threadid.str().c_str());
764 // Determine web site number. This can't really fail, I don't think.
766 GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
768 // Match site instance to host name, skip if no match.
769 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
770 if (map_i==g_Sites.end())
771 return WriteClientError(lpECB, "Shibboleth Extension not configured for this web site.");
773 ShibTargetIsapiE ste(lpECB, map_i->second);
774 pair<bool,void*> res = ste.doHandler();
775 if (res.first) return (DWORD)res.second;
777 return WriteClientError(lpECB, "Shibboleth Extension failed to process request");
781 return WriteClientError(lpECB,"Out of Memory");
784 if (e==ERROR_NO_DATA)
785 return WriteClientError(lpECB,"A required variable or header was empty.");
787 return WriteClientError(lpECB,"Server detected unexpected IIS error.");
789 catch (SAMLException& e) {
790 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
791 return WriteClientError(lpECB,"Shibboleth Extension caught an exception, check Event Log for details.");
795 return WriteClientError(lpECB,"Shibboleth Extension caught an unknown exception.");
799 // If we get here we've got an error.
800 return HSE_STATUS_ERROR;