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
28 #include <shibsp/SPConfig.h>
31 #include <saml/saml.h>
32 #include <shib/shib.h>
33 #include <shib-target/shib-target.h>
44 using namespace shibsp;
45 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[] = UNICODE_LITERAL_16(n,o,r,m,a,l,i,z,e,R,e,q,u,e,s,t);
61 static const XMLCh Site[] = UNICODE_LITERAL_4(S,i,t,e);
64 site_t(const DOMElement* e)
66 auto_ptr_char n(e->getAttributeNS(NULL,name));
67 auto_ptr_char s(e->getAttributeNS(NULL,scheme));
68 auto_ptr_char p(e->getAttributeNS(NULL,port));
69 auto_ptr_char p2(e->getAttributeNS(NULL,sslport));
70 if (n.get()) m_name=n.get();
71 if (s.get()) m_scheme=s.get();
72 if (p.get()) m_port=p.get();
73 if (p2.get()) m_sslport=p2.get();
74 e = XMLHelper::getFirstChildElement(e, Alias);
76 if (e->hasChildNodes()) {
77 auto_ptr_char alias(e->getFirstChild()->getNodeValue());
78 m_aliases.insert(alias.get());
80 e = XMLHelper::getNextSiblingElement(e, Alias);
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 SPConfig::getConfig().setFeatures(
162 SPConfig::RequestMapper |
163 SPConfig::InProcess |
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();
181 xmltooling::Locker locker(conf);
182 const PropertySet* props=conf->getPropertySet("Local");
184 const DOMElement* impl=XMLHelper::getFirstChildElement(props->getElement(),Implementation);
185 if (impl && (impl=XMLHelper::getFirstChildElement(impl,ISAPI))) {
186 const XMLCh* flag=impl->getAttributeNS(NULL,normalizeRequest);
187 g_bNormalizeRequest=(!flag || !*flag || *flag==chDigit_1 || *flag==chLatin_t);
188 impl=XMLHelper::getFirstChildElement(impl,Site);
190 auto_ptr_char id(impl->getAttributeNS(NULL,id));
192 g_Sites.insert(pair<string,site_t>(id.get(),site_t(impl)));
193 impl=XMLHelper::getNextSiblingElement(impl,Site);
201 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Filter startup failed with an exception.");
206 pVer->dwFilterVersion=HTTP_FILTER_REVISION;
207 strncpy(pVer->lpszFilterDesc,"Shibboleth ISAPI Filter",SF_MAX_FILTER_DESC_LEN);
208 pVer->dwFlags=(SF_NOTIFY_ORDER_HIGH |
209 SF_NOTIFY_SECURE_PORT |
210 SF_NOTIFY_NONSECURE_PORT |
211 SF_NOTIFY_PREPROC_HEADERS |
213 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter initialized...");
217 extern "C" BOOL WINAPI TerminateFilter(DWORD)
220 g_Config->shutdown();
222 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter shut down...");
226 /* Next up, some suck-free versions of various APIs.
228 You DON'T require people to guess the buffer size and THEN tell them the right size.
229 Returning an LPCSTR is apparently way beyond their ken. Not to mention the fact that
230 constant strings aren't typed as such, making it just that much harder. These versions
231 are now updated to use a special growable buffer object, modeled after the standard
232 string class. The standard string won't work because they left out the option to
233 pre-allocate a non-constant buffer.
239 dynabuf() { bufptr=NULL; buflen=0; }
240 dynabuf(size_t s) { bufptr=new char[buflen=s]; *bufptr=0; }
241 ~dynabuf() { delete[] bufptr; }
242 size_t length() const { return bufptr ? strlen(bufptr) : 0; }
243 size_t size() const { return buflen; }
244 bool empty() const { return length()==0; }
245 void reserve(size_t s, bool keep=false);
246 void erase() { if (bufptr) memset(bufptr,0,buflen); }
247 operator char*() { return bufptr; }
248 bool operator ==(const char* s) const;
249 bool operator !=(const char* s) const { return !(*this==s); }
255 void dynabuf::reserve(size_t s, bool keep)
262 p[buflen]=bufptr[buflen];
268 bool dynabuf::operator==(const char* s) const
270 if (buflen==NULL || s==NULL)
271 return (buflen==NULL && s==NULL);
273 return strcmp(bufptr,s)==0;
276 void GetServerVariable(PHTTP_FILTER_CONTEXT pfc, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
277 throw (bad_alloc, DWORD)
283 while (!pfc->GetServerVariable(pfc,lpszVariable,s,&size))
285 // Grumble. Check the error.
286 DWORD e=GetLastError();
287 if (e==ERROR_INSUFFICIENT_BUFFER)
292 if (bRequired && s.empty())
296 void GetServerVariable(LPEXTENSION_CONTROL_BLOCK lpECB, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
297 throw (bad_alloc, DWORD)
303 while (!lpECB->GetServerVariable(lpECB->ConnID,lpszVariable,s,&size))
305 // Grumble. Check the error.
306 DWORD e=GetLastError();
307 if (e==ERROR_INSUFFICIENT_BUFFER)
312 if (bRequired && s.empty())
316 void GetHeader(PHTTP_FILTER_PREPROC_HEADERS pn, PHTTP_FILTER_CONTEXT pfc,
317 LPSTR lpszName, dynabuf& s, DWORD size=80, bool bRequired=true)
318 throw (bad_alloc, DWORD)
324 while (!pn->GetHeader(pfc,lpszName,s,&size))
326 // Grumble. Check the error.
327 DWORD e=GetLastError();
328 if (e==ERROR_INSUFFICIENT_BUFFER)
333 if (bRequired && s.empty())
337 /****************************************************************************/
340 class ShibTargetIsapiF : public ShibTarget
342 PHTTP_FILTER_CONTEXT m_pfc;
343 PHTTP_FILTER_PREPROC_HEADERS m_pn;
346 ShibTargetIsapiF(PHTTP_FILTER_CONTEXT pfc, PHTTP_FILTER_PREPROC_HEADERS pn, const site_t& site) {
348 // URL path always come from IIS.
350 GetHeader(pn,pfc,"url",url,256,false);
352 // Port may come from IIS or from site def.
354 if (!g_bNormalizeRequest || (pfc->fIsSecurePort && site.m_sslport.empty()) || (!pfc->fIsSecurePort && site.m_port.empty()))
355 GetServerVariable(pfc,"SERVER_PORT",port,10);
356 else if (pfc->fIsSecurePort) {
357 strncpy(port,site.m_sslport.c_str(),10);
358 static_cast<char*>(port)[10]=0;
361 strncpy(port,site.m_port.c_str(),10);
362 static_cast<char*>(port)[10]=0;
365 // Scheme may come from site def or be derived from IIS.
366 const char* scheme=site.m_scheme.c_str();
367 if (!scheme || !*scheme || !g_bNormalizeRequest)
368 scheme=pfc->fIsSecurePort ? "https" : "http";
370 // Get the rest of the server variables.
371 dynabuf remote_addr(16),method(5),content_type(32),hostname(32);
372 GetServerVariable(pfc,"SERVER_NAME",hostname,32);
373 GetServerVariable(pfc,"REMOTE_ADDR",remote_addr,16);
374 GetServerVariable(pfc,"REQUEST_METHOD",method,5,false);
375 GetServerVariable(pfc,"CONTENT_TYPE",content_type,32,false);
377 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
378 const char* host=hostname;
379 if (site.m_name!=host && site.m_aliases.find(host)==site.m_aliases.end())
380 host=site.m_name.c_str();
382 init(scheme, host, atoi(port), url, content_type, remote_addr, method);
387 ~ShibTargetIsapiF() { }
389 virtual void log(ShibLogLevel level, const string &msg) {
390 ShibTarget::log(level,msg);
391 if (level == LogLevelError)
392 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
394 virtual string getCookies() const {
396 GetHeader(m_pn, m_pfc, "Cookie:", buf, 128, false);
397 return buf.empty() ? "" : buf;
400 virtual void clearHeader(const string &name) {
401 string hdr = (name=="REMOTE_USER" ? "remote-user" : name) + ":";
402 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), "");
404 virtual void setHeader(const string &name, const string &value) {
405 string hdr = name + ":";
406 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()),
407 const_cast<char*>(value.c_str()));
409 virtual string getHeader(const string &name) {
410 string hdr = name + ":";
412 GetHeader(m_pn, m_pfc, const_cast<char*>(hdr.c_str()), buf, 1024, false);
415 virtual void setRemoteUser(const string &user) {
416 setHeader(string("remote-user"), user);
418 virtual string getRemoteUser(void) {
419 return getHeader(string("remote-user"));
421 virtual void* sendPage(
424 const string& content_type="text/html",
425 const saml::Iterator<header_t>& headers=EMPTY(header_t)) {
426 string hdr = string ("Connection: close\r\nContent-type: ") + content_type + "\r\n";
427 while (headers.hasNext()) {
428 const header_t& h=headers.next();
429 hdr += h.first + ": " + h.second + "\r\n";
432 const char* codestr="200 OK";
434 case 403: codestr="403 Forbidden"; break;
435 case 404: codestr="404 Not Found"; break;
436 case 500: codestr="500 Server Error"; break;
438 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, (void*)codestr, (DWORD)hdr.c_str(), 0);
439 DWORD resplen = msg.size();
440 m_pfc->WriteClient(m_pfc, (LPVOID)msg.c_str(), &resplen, 0);
441 return (void*)SF_STATUS_REQ_FINISHED;
443 virtual void* sendRedirect(const string& url) {
444 // XXX: Don't support the httpRedirect option, yet.
445 string hdrs=m_cookie + string("Location: ") + url + "\r\n"
446 "Content-Type: text/html\r\n"
447 "Content-Length: 40\r\n"
448 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
449 "Cache-Control: private,no-store,no-cache\r\n\r\n";
450 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER,
451 "302 Please Wait", (DWORD)hdrs.c_str(), 0);
452 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
454 m_pfc->WriteClient(m_pfc, (LPVOID)redmsg, &resplen, 0);
455 return reinterpret_cast<void*>(SF_STATUS_REQ_FINISHED);
457 // XXX: We might not ever hit the 'decline' status in this filter.
458 //virtual void* returnDecline(void) { }
459 virtual void* returnOK(void) { return (void*) SF_STATUS_REQ_NEXT_NOTIFICATION; }
461 // The filter never processes the POST, so stub these methods.
462 virtual void setCookie(const string &name, const string &value) {
463 // Set the cookie for later. Use it during the redirect.
464 m_cookie += "Set-Cookie: " + name + "=" + value + "\r\n";
466 virtual const char* getQueryString() const { throw runtime_error("getQueryString not implemented"); }
467 virtual const char* getRequestBody() const { throw runtime_error("getRequestBody not implemented"); }
470 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const char* msg)
472 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
473 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
474 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)ctype,0);
475 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Filter Error</TITLE></HEAD><BODY>"
476 "<H1>Shibboleth Filter Error</H1>";
477 DWORD resplen=strlen(xmsg);
478 pfc->WriteClient(pfc,(LPVOID)xmsg,&resplen,0);
480 pfc->WriteClient(pfc,(LPVOID)msg,&resplen,0);
481 static const char* xmsg2="</BODY></HTML>";
482 resplen=strlen(xmsg2);
483 pfc->WriteClient(pfc,(LPVOID)xmsg2,&resplen,0);
484 return SF_STATUS_REQ_FINISHED;
487 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
489 // Is this a log notification?
490 if (notificationType==SF_NOTIFY_LOG)
492 if (pfc->pFilterContext)
493 ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=static_cast<LPCSTR>(pfc->pFilterContext);
494 return SF_STATUS_REQ_NEXT_NOTIFICATION;
497 PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
500 // Determine web site number. This can't really fail, I don't think.
502 GetServerVariable(pfc,"INSTANCE_ID",buf,10);
504 // Match site instance to host name, skip if no match.
505 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
506 if (map_i==g_Sites.end())
507 return SF_STATUS_REQ_NEXT_NOTIFICATION;
509 ostringstream threadid;
510 threadid << "[" << getpid() << "] isapi_shib" << '\0';
511 saml::NDC ndc(threadid.str().c_str());
513 ShibTargetIsapiF stf(pfc, pn, map_i->second);
515 // "false" because we don't override the Shib settings
516 pair<bool,void*> res = stf.doCheckAuthN();
517 if (res.first) return (DWORD)res.second;
519 // "false" because we don't override the Shib settings
520 res = stf.doExportAssertions();
521 if (res.first) return (DWORD)res.second;
523 res = stf.doCheckAuthZ();
524 if (res.first) return (DWORD)res.second;
526 return SF_STATUS_REQ_NEXT_NOTIFICATION;
529 return WriteClientError(pfc,"Out of Memory");
532 if (e==ERROR_NO_DATA)
533 return WriteClientError(pfc,"A required variable or header was empty.");
535 return WriteClientError(pfc,"Shibboleth Filter detected unexpected IIS error.");
537 catch (saml::SAMLException& e) {
538 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
539 return WriteClientError(pfc,"Shibboleth Filter caught an exception, check Event Log for details.");
543 return WriteClientError(pfc,"Shibboleth Filter caught an unknown exception.");
547 return WriteClientError(pfc,"Shibboleth Filter reached unreachable code, save my walrus!");
551 /****************************************************************************/
554 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* msg)
556 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
557 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
558 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
559 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Error</TITLE></HEAD><BODY><H1>Shibboleth Error</H1>";
560 DWORD resplen=strlen(xmsg);
561 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg,&resplen,HSE_IO_SYNC);
563 lpECB->WriteClient(lpECB->ConnID,(LPVOID)msg,&resplen,HSE_IO_SYNC);
564 static const char* xmsg2="</BODY></HTML>";
565 resplen=strlen(xmsg2);
566 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg2,&resplen,HSE_IO_SYNC);
567 return HSE_STATUS_SUCCESS;
571 class ShibTargetIsapiE : public ShibTarget
573 LPEXTENSION_CONTROL_BLOCK m_lpECB;
575 mutable string m_body;
576 mutable bool m_gotBody;
579 ShibTargetIsapiE(LPEXTENSION_CONTROL_BLOCK lpECB, const site_t& site) : m_gotBody(false) {
581 GetServerVariable(lpECB,"HTTPS",ssl,5);
582 bool SSL=(ssl=="on" || ssl=="ON");
584 // URL path always come from IIS.
586 GetServerVariable(lpECB,"URL",url,255);
588 // Port may come from IIS or from site def.
590 if (!g_bNormalizeRequest || (SSL && site.m_sslport.empty()) || (!SSL && site.m_port.empty()))
591 GetServerVariable(lpECB,"SERVER_PORT",port,10);
593 strncpy(port,site.m_sslport.c_str(),10);
594 static_cast<char*>(port)[10]=0;
597 strncpy(port,site.m_port.c_str(),10);
598 static_cast<char*>(port)[10]=0;
601 // Scheme may come from site def or be derived from IIS.
602 const char* scheme=site.m_scheme.c_str();
603 if (!scheme || !*scheme || !g_bNormalizeRequest) {
604 scheme = SSL ? "https" : "http";
607 // Get the other server variables.
608 dynabuf remote_addr(16),hostname(32);
609 GetServerVariable(lpECB, "REMOTE_ADDR", remote_addr, 16);
610 GetServerVariable(lpECB, "SERVER_NAME", hostname, 32);
612 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
613 const char* host=hostname;
614 if (site.m_name!=host && site.m_aliases.find(host)==site.m_aliases.end())
615 host=site.m_name.c_str();
618 * IIS screws us over on PATH_INFO (the hits keep on coming). We need to figure out if
619 * the server is set up for proper PATH_INFO handling, or "IIS sucks rabid weasels mode",
620 * which is the default. No perfect way to tell, but we can take a good guess by checking
621 * whether the URL is a substring of the PATH_INFO:
623 * e.g. for /Shibboleth.sso/SAML/POST
625 * Bad mode (default):
626 * URL: /Shibboleth.sso
627 * PathInfo: /Shibboleth.sso/SAML/POST
630 * URL: /Shibboleth.sso
631 * PathInfo: /SAML/POST
636 // Clearly we're only in bad mode if path info exists at all.
637 if (lpECB->lpszPathInfo && *(lpECB->lpszPathInfo)) {
638 if (strstr(lpECB->lpszPathInfo,url))
639 // Pretty good chance we're in bad mode, unless the PathInfo repeats the path itself.
640 fullurl=lpECB->lpszPathInfo;
643 fullurl+=lpECB->lpszPathInfo;
647 // For consistency with Apache, let's add the query string.
648 if (lpECB->lpszQueryString && *(lpECB->lpszQueryString)) {
650 fullurl+=lpECB->lpszQueryString;
652 init(scheme, host, atoi(port), fullurl.c_str(), lpECB->lpszContentType, remote_addr, lpECB->lpszMethod);
656 ~ShibTargetIsapiE() { }
658 virtual void log(ShibLogLevel level, const string &msg) {
659 ShibTarget::log(level,msg);
660 if (level == LogLevelError)
661 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
663 virtual string getCookies() const {
665 GetServerVariable(m_lpECB, "HTTP_COOKIE", buf, 128, false);
666 return buf.empty() ? "" : buf;
668 virtual void setCookie(const string &name, const string &value) {
669 // Set the cookie for later. Use it during the redirect.
670 m_cookie += "Set-Cookie: " + name + "=" + value + "\r\n";
672 virtual const char* getQueryString() const {
673 return m_lpECB->lpszQueryString;
675 virtual const char* getRequestBody() const {
677 return m_body.c_str();
678 if (m_lpECB->cbTotalBytes > 1024*1024) // 1MB?
679 throw saml::SAMLException("Size of POST request body exceeded limit.");
680 else if (m_lpECB->cbTotalBytes != m_lpECB->cbAvailable) {
683 DWORD datalen=m_lpECB->cbTotalBytes;
686 BOOL ret = m_lpECB->ReadClient(m_lpECB->ConnID, buf, &buflen);
688 throw saml::SAMLException("Error reading POST request body from browser.");
689 m_body.append(buf, buflen);
695 m_body.assign(reinterpret_cast<char*>(m_lpECB->lpbData),m_lpECB->cbAvailable);
697 return m_body.c_str();
699 virtual void* sendPage(
702 const string& content_type="text/html",
703 const saml::Iterator<header_t>& headers=EMPTY(header_t)) {
704 string hdr = string ("Connection: close\r\nContent-type: ") + content_type + "\r\n";
705 for (unsigned int k = 0; k < headers.size(); k++) {
706 hdr += headers[k].first + ": " + headers[k].second + "\r\n";
709 const char* codestr="200 OK";
711 case 403: codestr="403 Forbidden"; break;
712 case 404: codestr="404 Not Found"; break;
713 case 500: codestr="500 Server Error"; break;
715 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, (void*)codestr, 0, (LPDWORD)hdr.c_str());
716 DWORD resplen = msg.size();
717 m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)msg.c_str(), &resplen, HSE_IO_SYNC);
718 return (void*)HSE_STATUS_SUCCESS;
720 virtual void* sendRedirect(const string& url) {
721 // XXX: Don't support the httpRedirect option, yet.
722 string hdrs = m_cookie + "Location: " + url + "\r\n"
723 "Content-Type: text/html\r\n"
724 "Content-Length: 40\r\n"
725 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
726 "Cache-Control: private,no-store,no-cache\r\n\r\n";
727 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER,
728 "302 Moved", 0, (LPDWORD)hdrs.c_str());
729 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
731 m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)redmsg, &resplen, HSE_IO_SYNC);
732 return (void*)HSE_STATUS_SUCCESS;
734 // Decline happens in the POST processor if this isn't the shire url
735 // Note that it can also happen with HTAccess, but we don't support that, yet.
736 virtual void* returnDecline(void) {
738 WriteClientError(m_lpECB, "ISAPI extension can only be invoked to process Shibboleth protocol requests."
739 "Make sure the mapped file extension doesn't match actual content.");
741 virtual void* returnOK(void) { return (void*) HSE_STATUS_SUCCESS; }
743 // Not used in the extension.
744 virtual void clearHeader(const string &name) { throw runtime_error("clearHeader not implemented"); }
745 virtual void setHeader(const string &name, const string &value) { throw runtime_error("setHeader not implemented"); }
746 virtual string getHeader(const string &name) { throw runtime_error("getHeader not implemented"); }
747 virtual void setRemoteUser(const string &user) { throw runtime_error("setRemoteUser not implemented"); }
748 virtual string getRemoteUser(void) { throw runtime_error("getRemoteUser not implemented"); }
751 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
754 const IApplication* application=NULL;
756 ostringstream threadid;
757 threadid << "[" << getpid() << "] isapi_shib_extension" << '\0';
758 saml::NDC ndc(threadid.str().c_str());
760 // Determine web site number. This can't really fail, I don't think.
762 GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
764 // Match site instance to host name, skip if no match.
765 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
766 if (map_i==g_Sites.end())
767 return WriteClientError(lpECB, "Shibboleth Extension not configured for this web site.");
769 ShibTargetIsapiE ste(lpECB, map_i->second);
770 pair<bool,void*> res = ste.doHandler();
771 if (res.first) return (DWORD)res.second;
773 return WriteClientError(lpECB, "Shibboleth Extension failed to process request");
777 return WriteClientError(lpECB,"Out of Memory");
780 if (e==ERROR_NO_DATA)
781 return WriteClientError(lpECB,"A required variable or header was empty.");
783 return WriteClientError(lpECB,"Server detected unexpected IIS error.");
785 catch (saml::SAMLException& e) {
786 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
787 return WriteClientError(lpECB,"Shibboleth Extension caught an exception, check Event Log for details.");
791 return WriteClientError(lpECB,"Shibboleth Extension caught an unknown exception.");
795 // If we get here we've got an error.
796 return HSE_STATUS_ERROR;