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>
42 using namespace shibtarget;
44 using namespace xmltooling;
49 static const XMLCh name[] = { chLatin_n, chLatin_a, chLatin_m, chLatin_e, chNull };
50 static const XMLCh port[] = { chLatin_p, chLatin_o, chLatin_r, chLatin_t, chNull };
51 static const XMLCh sslport[] = { chLatin_s, chLatin_s, chLatin_l, chLatin_p, chLatin_o, chLatin_r, chLatin_t, chNull };
52 static const XMLCh scheme[] = { chLatin_s, chLatin_c, chLatin_h, chLatin_e, chLatin_m, chLatin_e, chNull };
53 static const XMLCh id[] = { chLatin_i, chLatin_d, chNull };
54 static const XMLCh Implementation[] =
55 { 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 };
56 static const XMLCh ISAPI[] = { chLatin_I, chLatin_S, chLatin_A, chLatin_P, chLatin_I, chNull };
57 static const XMLCh Alias[] = { chLatin_A, chLatin_l, chLatin_i, chLatin_a, chLatin_s, chNull };
58 static const XMLCh normalizeRequest[] =
59 { chLatin_n, chLatin_o, chLatin_r, chLatin_m, chLatin_a, chLatin_l, chLatin_i, chLatin_z, chLatin_e,
60 chLatin_R, chLatin_e, chLatin_q, chLatin_u, chLatin_e, chLatin_s, chLatin_t, chNull
62 static const XMLCh Site[] = { chLatin_S, chLatin_i, chLatin_t, chLatin_e, chNull };
65 site_t(const DOMElement* e)
67 auto_ptr_char n(e->getAttributeNS(NULL,name));
68 auto_ptr_char s(e->getAttributeNS(NULL,scheme));
69 auto_ptr_char p(e->getAttributeNS(NULL,port));
70 auto_ptr_char p2(e->getAttributeNS(NULL,sslport));
71 if (n.get()) m_name=n.get();
72 if (s.get()) m_scheme=s.get();
73 if (p.get()) m_port=p.get();
74 if (p2.get()) m_sslport=p2.get();
75 DOMNodeList* nlist=e->getElementsByTagNameNS(shibtarget::XML::SHIBTARGET_NS,Alias);
76 for (unsigned int i=0; nlist && i<nlist->getLength(); i++) {
77 if (nlist->item(i)->hasChildNodes()) {
78 auto_ptr_char alias(nlist->item(i)->getFirstChild()->getNodeValue());
79 m_aliases.insert(alias.get());
83 string m_scheme,m_port,m_sslport,m_name;
84 set<string> m_aliases;
88 ShibTargetConfig* g_Config = NULL;
89 map<string,site_t> g_Sites;
90 bool g_bNormalizeRequest = true;
94 LPCSTR lpUNCServerName,
100 LPCSTR messages[] = {message, NULL};
102 HANDLE hElog = RegisterEventSource(lpUNCServerName, "Shibboleth ISAPI Filter");
103 BOOL res = ReportEvent(hElog, wType, 0, dwEventID, lpUserSid, 1, 0, messages, NULL);
104 return (DeregisterEventSource(hElog) && res);
107 extern "C" __declspec(dllexport) BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID)
109 if (fdwReason==DLL_PROCESS_ATTACH)
114 extern "C" BOOL WINAPI GetExtensionVersion(HSE_VERSION_INFO* pVer)
121 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
122 "Extension mode startup not possible, is the DLL loaded as a filter?");
126 pVer->dwExtensionVersion=HSE_VERSION;
127 strncpy(pVer->lpszExtensionDesc,"Shibboleth ISAPI Extension",HSE_MAX_EXT_DLL_NAME_LEN-1);
131 extern "C" BOOL WINAPI TerminateExtension(DWORD)
133 return TRUE; // cleanup should happen when filter unloads
136 extern "C" BOOL WINAPI GetFilterVersion(PHTTP_FILTER_VERSION pVer)
141 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
142 "Reentrant filter initialization, ignoring...");
150 LPCSTR schemadir=getenv("SHIBSCHEMAS");
152 schemadir=SHIB_SCHEMAS;
153 LPCSTR config=getenv("SHIBCONFIG");
156 g_Config=&ShibTargetConfig::getConfig();
157 g_Config->setFeatures(
158 ShibTargetConfig::Listener |
159 ShibTargetConfig::Caching |
160 ShibTargetConfig::Metadata |
161 ShibTargetConfig::AAP |
162 ShibTargetConfig::RequestMapper |
163 ShibTargetConfig::InProcess |
164 ShibTargetConfig::Logging
166 if (!g_Config->init(schemadir)) {
168 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
169 "Filter startup failed during library initialization, check native log for help.");
172 else if (!g_Config->load(config)) {
174 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
175 "Filter startup failed to load configuration, check native log for help.");
179 // Access the implementation-specifics for site mappings.
180 IConfig* conf=g_Config->getINI();
182 const IPropertySet* props=conf->getPropertySet("Local");
184 const DOMElement* impl=saml::XML::getFirstChildElement(
185 props->getElement(),shibtarget::XML::SHIBTARGET_NS,Implementation
187 if (impl && (impl=saml::XML::getFirstChildElement(impl,shibtarget::XML::SHIBTARGET_NS,ISAPI))) {
188 const XMLCh* flag=impl->getAttributeNS(NULL,normalizeRequest);
189 g_bNormalizeRequest=(!flag || !*flag || *flag==chDigit_1 || *flag==chLatin_t);
190 impl=saml::XML::getFirstChildElement(impl,shibtarget::XML::SHIBTARGET_NS,Site);
192 auto_ptr_char id(impl->getAttributeNS(NULL,id));
194 g_Sites.insert(pair<string,site_t>(id.get(),site_t(impl)));
195 impl=saml::XML::getNextSiblingElement(impl,shibtarget::XML::SHIBTARGET_NS,Site);
203 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Filter startup failed with an exception.");
208 pVer->dwFilterVersion=HTTP_FILTER_REVISION;
209 strncpy(pVer->lpszFilterDesc,"Shibboleth ISAPI Filter",SF_MAX_FILTER_DESC_LEN);
210 pVer->dwFlags=(SF_NOTIFY_ORDER_HIGH |
211 SF_NOTIFY_SECURE_PORT |
212 SF_NOTIFY_NONSECURE_PORT |
213 SF_NOTIFY_PREPROC_HEADERS |
215 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter initialized...");
219 extern "C" BOOL WINAPI TerminateFilter(DWORD)
222 g_Config->shutdown();
224 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter shut down...");
228 /* Next up, some suck-free versions of various APIs.
230 You DON'T require people to guess the buffer size and THEN tell them the right size.
231 Returning an LPCSTR is apparently way beyond their ken. Not to mention the fact that
232 constant strings aren't typed as such, making it just that much harder. These versions
233 are now updated to use a special growable buffer object, modeled after the standard
234 string class. The standard string won't work because they left out the option to
235 pre-allocate a non-constant buffer.
241 dynabuf() { bufptr=NULL; buflen=0; }
242 dynabuf(size_t s) { bufptr=new char[buflen=s]; *bufptr=0; }
243 ~dynabuf() { delete[] bufptr; }
244 size_t length() const { return bufptr ? strlen(bufptr) : 0; }
245 size_t size() const { return buflen; }
246 bool empty() const { return length()==0; }
247 void reserve(size_t s, bool keep=false);
248 void erase() { if (bufptr) memset(bufptr,0,buflen); }
249 operator char*() { return bufptr; }
250 bool operator ==(const char* s) const;
251 bool operator !=(const char* s) const { return !(*this==s); }
257 void dynabuf::reserve(size_t s, bool keep)
264 p[buflen]=bufptr[buflen];
270 bool dynabuf::operator==(const char* s) const
272 if (buflen==NULL || s==NULL)
273 return (buflen==NULL && s==NULL);
275 return strcmp(bufptr,s)==0;
278 void GetServerVariable(PHTTP_FILTER_CONTEXT pfc, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
279 throw (bad_alloc, DWORD)
285 while (!pfc->GetServerVariable(pfc,lpszVariable,s,&size))
287 // Grumble. Check the error.
288 DWORD e=GetLastError();
289 if (e==ERROR_INSUFFICIENT_BUFFER)
294 if (bRequired && s.empty())
298 void GetServerVariable(LPEXTENSION_CONTROL_BLOCK lpECB, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
299 throw (bad_alloc, DWORD)
305 while (!lpECB->GetServerVariable(lpECB->ConnID,lpszVariable,s,&size))
307 // Grumble. Check the error.
308 DWORD e=GetLastError();
309 if (e==ERROR_INSUFFICIENT_BUFFER)
314 if (bRequired && s.empty())
318 void GetHeader(PHTTP_FILTER_PREPROC_HEADERS pn, PHTTP_FILTER_CONTEXT pfc,
319 LPSTR lpszName, dynabuf& s, DWORD size=80, bool bRequired=true)
320 throw (bad_alloc, DWORD)
326 while (!pn->GetHeader(pfc,lpszName,s,&size))
328 // Grumble. Check the error.
329 DWORD e=GetLastError();
330 if (e==ERROR_INSUFFICIENT_BUFFER)
335 if (bRequired && s.empty())
339 /****************************************************************************/
342 class ShibTargetIsapiF : public ShibTarget
344 PHTTP_FILTER_CONTEXT m_pfc;
345 PHTTP_FILTER_PREPROC_HEADERS m_pn;
348 ShibTargetIsapiF(PHTTP_FILTER_CONTEXT pfc, PHTTP_FILTER_PREPROC_HEADERS pn, const site_t& site) {
350 // URL path always come from IIS.
352 GetHeader(pn,pfc,"url",url,256,false);
354 // Port may come from IIS or from site def.
356 if (!g_bNormalizeRequest || (pfc->fIsSecurePort && site.m_sslport.empty()) || (!pfc->fIsSecurePort && site.m_port.empty()))
357 GetServerVariable(pfc,"SERVER_PORT",port,10);
358 else if (pfc->fIsSecurePort) {
359 strncpy(port,site.m_sslport.c_str(),10);
360 static_cast<char*>(port)[10]=0;
363 strncpy(port,site.m_port.c_str(),10);
364 static_cast<char*>(port)[10]=0;
367 // Scheme may come from site def or be derived from IIS.
368 const char* scheme=site.m_scheme.c_str();
369 if (!scheme || !*scheme || !g_bNormalizeRequest)
370 scheme=pfc->fIsSecurePort ? "https" : "http";
372 // Get the rest of the server variables.
373 dynabuf remote_addr(16),method(5),content_type(32),hostname(32);
374 GetServerVariable(pfc,"SERVER_NAME",hostname,32);
375 GetServerVariable(pfc,"REMOTE_ADDR",remote_addr,16);
376 GetServerVariable(pfc,"REQUEST_METHOD",method,5,false);
377 GetServerVariable(pfc,"CONTENT_TYPE",content_type,32,false);
379 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
380 const char* host=hostname;
381 if (site.m_name!=host && site.m_aliases.find(host)==site.m_aliases.end())
382 host=site.m_name.c_str();
384 init(scheme, host, atoi(port), url, content_type, remote_addr, method);
389 ~ShibTargetIsapiF() { }
391 virtual void log(ShibLogLevel level, const string &msg) {
392 ShibTarget::log(level,msg);
393 if (level == LogLevelError)
394 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
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 string hdr = (name=="REMOTE_USER" ? "remote-user" : name) + ":";
404 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), "");
406 virtual void setHeader(const string &name, const string &value) {
407 string hdr = name + ":";
408 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()),
409 const_cast<char*>(value.c_str()));
411 virtual string getHeader(const string &name) {
412 string hdr = name + ":";
414 GetHeader(m_pn, m_pfc, const_cast<char*>(hdr.c_str()), buf, 1024, false);
417 virtual void setRemoteUser(const string &user) {
418 setHeader(string("remote-user"), user);
420 virtual string getRemoteUser(void) {
421 return getHeader(string("remote-user"));
423 virtual void* sendPage(
426 const string& content_type="text/html",
427 const Iterator<header_t>& headers=EMPTY(header_t)) {
428 string hdr = string ("Connection: close\r\nContent-type: ") + content_type + "\r\n";
429 while (headers.hasNext()) {
430 const header_t& h=headers.next();
431 hdr += h.first + ": " + h.second + "\r\n";
434 const char* codestr="200 OK";
436 case 403: codestr="403 Forbidden"; break;
437 case 404: codestr="404 Not Found"; break;
438 case 500: codestr="500 Server Error"; break;
440 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, (void*)codestr, (DWORD)hdr.c_str(), 0);
441 DWORD resplen = msg.size();
442 m_pfc->WriteClient(m_pfc, (LPVOID)msg.c_str(), &resplen, 0);
443 return (void*)SF_STATUS_REQ_FINISHED;
445 virtual void* sendRedirect(const string& url) {
446 // XXX: Don't support the httpRedirect option, yet.
447 string hdrs=m_cookie + string("Location: ") + url + "\r\n"
448 "Content-Type: text/html\r\n"
449 "Content-Length: 40\r\n"
450 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
451 "Cache-Control: private,no-store,no-cache\r\n\r\n";
452 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER,
453 "302 Please Wait", (DWORD)hdrs.c_str(), 0);
454 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
456 m_pfc->WriteClient(m_pfc, (LPVOID)redmsg, &resplen, 0);
457 return reinterpret_cast<void*>(SF_STATUS_REQ_FINISHED);
459 // XXX: We might not ever hit the 'decline' status in this filter.
460 //virtual void* returnDecline(void) { }
461 virtual void* returnOK(void) { return (void*) SF_STATUS_REQ_NEXT_NOTIFICATION; }
463 // The filter never processes the POST, so stub these methods.
464 virtual void setCookie(const string &name, const string &value) {
465 // Set the cookie for later. Use it during the redirect.
466 m_cookie += "Set-Cookie: " + name + "=" + value + "\r\n";
468 virtual const char* getQueryString() const { throw runtime_error("getQueryString not implemented"); }
469 virtual const char* getRequestBody() const { throw runtime_error("getRequestBody not implemented"); }
472 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const char* msg)
474 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
475 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
476 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)ctype,0);
477 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Filter Error</TITLE></HEAD><BODY>"
478 "<H1>Shibboleth Filter Error</H1>";
479 DWORD resplen=strlen(xmsg);
480 pfc->WriteClient(pfc,(LPVOID)xmsg,&resplen,0);
482 pfc->WriteClient(pfc,(LPVOID)msg,&resplen,0);
483 static const char* xmsg2="</BODY></HTML>";
484 resplen=strlen(xmsg2);
485 pfc->WriteClient(pfc,(LPVOID)xmsg2,&resplen,0);
486 return SF_STATUS_REQ_FINISHED;
489 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
491 // Is this a log notification?
492 if (notificationType==SF_NOTIFY_LOG)
494 if (pfc->pFilterContext)
495 ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=static_cast<LPCSTR>(pfc->pFilterContext);
496 return SF_STATUS_REQ_NEXT_NOTIFICATION;
499 PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
502 // Determine web site number. This can't really fail, I don't think.
504 GetServerVariable(pfc,"INSTANCE_ID",buf,10);
506 // Match site instance to host name, skip if no match.
507 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
508 if (map_i==g_Sites.end())
509 return SF_STATUS_REQ_NEXT_NOTIFICATION;
511 ostringstream threadid;
512 threadid << "[" << getpid() << "] isapi_shib" << '\0';
513 saml::NDC ndc(threadid.str().c_str());
515 ShibTargetIsapiF stf(pfc, pn, map_i->second);
517 // "false" because we don't override the Shib settings
518 pair<bool,void*> res = stf.doCheckAuthN();
519 if (res.first) return (DWORD)res.second;
521 // "false" because we don't override the Shib settings
522 res = stf.doExportAssertions();
523 if (res.first) return (DWORD)res.second;
525 res = stf.doCheckAuthZ();
526 if (res.first) return (DWORD)res.second;
528 return SF_STATUS_REQ_NEXT_NOTIFICATION;
531 return WriteClientError(pfc,"Out of Memory");
534 if (e==ERROR_NO_DATA)
535 return WriteClientError(pfc,"A required variable or header was empty.");
537 return WriteClientError(pfc,"Shibboleth Filter detected unexpected IIS error.");
539 catch (SAMLException& e) {
540 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
541 return WriteClientError(pfc,"Shibboleth Filter caught an exception, check Event Log for details.");
545 return WriteClientError(pfc,"Shibboleth Filter caught an unknown exception.");
549 return WriteClientError(pfc,"Shibboleth Filter reached unreachable code, save my walrus!");
553 /****************************************************************************/
556 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* msg)
558 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
559 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
560 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
561 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Error</TITLE></HEAD><BODY><H1>Shibboleth Error</H1>";
562 DWORD resplen=strlen(xmsg);
563 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg,&resplen,HSE_IO_SYNC);
565 lpECB->WriteClient(lpECB->ConnID,(LPVOID)msg,&resplen,HSE_IO_SYNC);
566 static const char* xmsg2="</BODY></HTML>";
567 resplen=strlen(xmsg2);
568 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg2,&resplen,HSE_IO_SYNC);
569 return HSE_STATUS_SUCCESS;
573 class ShibTargetIsapiE : public ShibTarget
575 LPEXTENSION_CONTROL_BLOCK m_lpECB;
577 mutable string m_body;
578 mutable bool m_gotBody;
581 ShibTargetIsapiE(LPEXTENSION_CONTROL_BLOCK lpECB, const site_t& site) : m_gotBody(false) {
583 GetServerVariable(lpECB,"HTTPS",ssl,5);
584 bool SSL=(ssl=="on" || ssl=="ON");
586 // URL path always come from IIS.
588 GetServerVariable(lpECB,"URL",url,255);
590 // Port may come from IIS or from site def.
592 if (!g_bNormalizeRequest || (SSL && site.m_sslport.empty()) || (!SSL && site.m_port.empty()))
593 GetServerVariable(lpECB,"SERVER_PORT",port,10);
595 strncpy(port,site.m_sslport.c_str(),10);
596 static_cast<char*>(port)[10]=0;
599 strncpy(port,site.m_port.c_str(),10);
600 static_cast<char*>(port)[10]=0;
603 // Scheme may come from site def or be derived from IIS.
604 const char* scheme=site.m_scheme.c_str();
605 if (!scheme || !*scheme || !g_bNormalizeRequest) {
606 scheme = SSL ? "https" : "http";
609 // Get the other server variables.
610 dynabuf remote_addr(16),hostname(32);
611 GetServerVariable(lpECB, "REMOTE_ADDR", remote_addr, 16);
612 GetServerVariable(lpECB, "SERVER_NAME", hostname, 32);
614 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
615 const char* host=hostname;
616 if (site.m_name!=host && site.m_aliases.find(host)==site.m_aliases.end())
617 host=site.m_name.c_str();
620 * IIS screws us over on PATH_INFO (the hits keep on coming). We need to figure out if
621 * the server is set up for proper PATH_INFO handling, or "IIS sucks rabid weasels mode",
622 * which is the default. No perfect way to tell, but we can take a good guess by checking
623 * whether the URL is a substring of the PATH_INFO:
625 * e.g. for /Shibboleth.sso/SAML/POST
627 * Bad mode (default):
628 * URL: /Shibboleth.sso
629 * PathInfo: /Shibboleth.sso/SAML/POST
632 * URL: /Shibboleth.sso
633 * PathInfo: /SAML/POST
638 // Clearly we're only in bad mode if path info exists at all.
639 if (lpECB->lpszPathInfo && *(lpECB->lpszPathInfo)) {
640 if (strstr(lpECB->lpszPathInfo,url))
641 // Pretty good chance we're in bad mode, unless the PathInfo repeats the path itself.
642 fullurl=lpECB->lpszPathInfo;
645 fullurl+=lpECB->lpszPathInfo;
649 // For consistency with Apache, let's add the query string.
650 if (lpECB->lpszQueryString && *(lpECB->lpszQueryString)) {
652 fullurl+=lpECB->lpszQueryString;
654 init(scheme, host, atoi(port), fullurl.c_str(), lpECB->lpszContentType, remote_addr, lpECB->lpszMethod);
658 ~ShibTargetIsapiE() { }
660 virtual void log(ShibLogLevel level, const string &msg) {
661 ShibTarget::log(level,msg);
662 if (level == LogLevelError)
663 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
665 virtual string getCookies() const {
667 GetServerVariable(m_lpECB, "HTTP_COOKIE", buf, 128, false);
668 return buf.empty() ? "" : buf;
670 virtual void setCookie(const string &name, const string &value) {
671 // Set the cookie for later. Use it during the redirect.
672 m_cookie += "Set-Cookie: " + name + "=" + value + "\r\n";
674 virtual const char* getQueryString() const {
675 return m_lpECB->lpszQueryString;
677 virtual const char* getRequestBody() const {
679 return m_body.c_str();
680 if (m_lpECB->cbTotalBytes > 1024*1024) // 1MB?
681 throw SAMLException("Size of POST request body exceeded limit.");
682 else if (m_lpECB->cbTotalBytes != m_lpECB->cbAvailable) {
685 DWORD datalen=m_lpECB->cbTotalBytes;
688 BOOL ret = m_lpECB->ReadClient(m_lpECB->ConnID, buf, &buflen);
690 throw SAMLException("Error reading POST request body from browser.");
691 m_body.append(buf, buflen);
697 m_body.assign(reinterpret_cast<char*>(m_lpECB->lpbData),m_lpECB->cbAvailable);
699 return m_body.c_str();
701 virtual void* sendPage(
704 const string& content_type="text/html",
705 const Iterator<header_t>& headers=EMPTY(header_t)) {
706 string hdr = string ("Connection: close\r\nContent-type: ") + content_type + "\r\n";
707 for (unsigned int k = 0; k < headers.size(); k++) {
708 hdr += headers[k].first + ": " + headers[k].second + "\r\n";
711 const char* codestr="200 OK";
713 case 403: codestr="403 Forbidden"; break;
714 case 404: codestr="404 Not Found"; break;
715 case 500: codestr="500 Server Error"; break;
717 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, (void*)codestr, 0, (LPDWORD)hdr.c_str());
718 DWORD resplen = msg.size();
719 m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)msg.c_str(), &resplen, HSE_IO_SYNC);
720 return (void*)HSE_STATUS_SUCCESS;
722 virtual void* sendRedirect(const string& url) {
723 // XXX: Don't support the httpRedirect option, yet.
724 string hdrs = m_cookie + "Location: " + url + "\r\n"
725 "Content-Type: text/html\r\n"
726 "Content-Length: 40\r\n"
727 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
728 "Cache-Control: private,no-store,no-cache\r\n\r\n";
729 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER,
730 "302 Moved", 0, (LPDWORD)hdrs.c_str());
731 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
733 m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)redmsg, &resplen, HSE_IO_SYNC);
734 return (void*)HSE_STATUS_SUCCESS;
736 // Decline happens in the POST processor if this isn't the shire url
737 // Note that it can also happen with HTAccess, but we don't support that, yet.
738 virtual void* returnDecline(void) {
740 WriteClientError(m_lpECB, "ISAPI extension can only be invoked to process Shibboleth protocol requests."
741 "Make sure the mapped file extension doesn't match actual content.");
743 virtual void* returnOK(void) { return (void*) HSE_STATUS_SUCCESS; }
745 // Not used in the extension.
746 virtual void clearHeader(const string &name) { throw runtime_error("clearHeader not implemented"); }
747 virtual void setHeader(const string &name, const string &value) { throw runtime_error("setHeader not implemented"); }
748 virtual string getHeader(const string &name) { throw runtime_error("getHeader not implemented"); }
749 virtual void setRemoteUser(const string &user) { throw runtime_error("setRemoteUser not implemented"); }
750 virtual string getRemoteUser(void) { throw runtime_error("getRemoteUser not implemented"); }
753 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
756 const IApplication* application=NULL;
758 ostringstream threadid;
759 threadid << "[" << getpid() << "] isapi_shib_extension" << '\0';
760 saml::NDC ndc(threadid.str().c_str());
762 // Determine web site number. This can't really fail, I don't think.
764 GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
766 // Match site instance to host name, skip if no match.
767 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
768 if (map_i==g_Sites.end())
769 return WriteClientError(lpECB, "Shibboleth Extension not configured for this web site.");
771 ShibTargetIsapiE ste(lpECB, map_i->second);
772 pair<bool,void*> res = ste.doHandler();
773 if (res.first) return (DWORD)res.second;
775 return WriteClientError(lpECB, "Shibboleth Extension failed to process request");
779 return WriteClientError(lpECB,"Out of Memory");
782 if (e==ERROR_NO_DATA)
783 return WriteClientError(lpECB,"A required variable or header was empty.");
785 return WriteClientError(lpECB,"Server detected unexpected IIS error.");
787 catch (SAMLException& e) {
788 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
789 return WriteClientError(lpECB,"Shibboleth Extension caught an exception, check Event Log for details.");
793 return WriteClientError(lpECB,"Shibboleth Extension caught an unknown exception.");
797 // If we get here we've got an error.
798 return HSE_STATUS_ERROR;