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;
45 using namespace xmltooling;
50 static const XMLCh name[] = { chLatin_n, chLatin_a, chLatin_m, chLatin_e, chNull };
51 static const XMLCh port[] = { chLatin_p, chLatin_o, chLatin_r, chLatin_t, chNull };
52 static const XMLCh sslport[] = { chLatin_s, chLatin_s, chLatin_l, chLatin_p, chLatin_o, chLatin_r, chLatin_t, chNull };
53 static const XMLCh scheme[] = { chLatin_s, chLatin_c, chLatin_h, chLatin_e, chLatin_m, chLatin_e, chNull };
54 static const XMLCh id[] = { chLatin_i, chLatin_d, chNull };
55 static const XMLCh Implementation[] =
56 { 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 };
57 static const XMLCh ISAPI[] = { chLatin_I, chLatin_S, chLatin_A, chLatin_P, chLatin_I, chNull };
58 static const XMLCh Alias[] = { chLatin_A, chLatin_l, chLatin_i, chLatin_a, chLatin_s, chNull };
59 static const XMLCh normalizeRequest[] =
60 { chLatin_n, chLatin_o, chLatin_r, chLatin_m, chLatin_a, chLatin_l, chLatin_i, chLatin_z, chLatin_e,
61 chLatin_R, chLatin_e, chLatin_q, chLatin_u, chLatin_e, chLatin_s, chLatin_t, chNull
63 static const XMLCh Site[] = { chLatin_S, chLatin_i, chLatin_t, chLatin_e, chNull };
66 site_t(const DOMElement* e)
68 auto_ptr_char n(e->getAttributeNS(NULL,name));
69 auto_ptr_char s(e->getAttributeNS(NULL,scheme));
70 auto_ptr_char p(e->getAttributeNS(NULL,port));
71 auto_ptr_char p2(e->getAttributeNS(NULL,sslport));
72 if (n.get()) m_name=n.get();
73 if (s.get()) m_scheme=s.get();
74 if (p.get()) m_port=p.get();
75 if (p2.get()) m_sslport=p2.get();
76 DOMNodeList* nlist=e->getElementsByTagNameNS(shibtarget::XML::SHIBTARGET_NS,Alias);
77 for (unsigned int i=0; nlist && i<nlist->getLength(); i++) {
78 if (nlist->item(i)->hasChildNodes()) {
79 auto_ptr_char alias(nlist->item(i)->getFirstChild()->getNodeValue());
80 m_aliases.insert(alias.get());
84 string m_scheme,m_port,m_sslport,m_name;
85 set<string> m_aliases;
89 ShibTargetConfig* g_Config = NULL;
90 map<string,site_t> g_Sites;
91 bool g_bNormalizeRequest = 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...");
151 LPCSTR schemadir=getenv("SHIBSCHEMAS");
153 schemadir=SHIB_SCHEMAS;
154 LPCSTR config=getenv("SHIBCONFIG");
157 g_Config=&ShibTargetConfig::getConfig();
158 SPConfig::getConfig().setFeatures(
163 SPConfig::RequestMapper |
164 SPConfig::InProcess |
167 if (!g_Config->init(schemadir)) {
169 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
170 "Filter startup failed during library initialization, check native log for help.");
173 else if (!g_Config->load(config)) {
175 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
176 "Filter startup failed to load configuration, check native log for help.");
180 // Access the implementation-specifics for site mappings.
181 IConfig* conf=g_Config->getINI();
182 saml::Locker locker(conf);
183 const PropertySet* props=conf->getPropertySet("Local");
185 const DOMElement* impl=saml::XML::getFirstChildElement(
186 props->getElement(),shibtarget::XML::SHIBTARGET_NS,Implementation
188 if (impl && (impl=saml::XML::getFirstChildElement(impl,shibtarget::XML::SHIBTARGET_NS,ISAPI))) {
189 const XMLCh* flag=impl->getAttributeNS(NULL,normalizeRequest);
190 g_bNormalizeRequest=(!flag || !*flag || *flag==chDigit_1 || *flag==chLatin_t);
191 impl=saml::XML::getFirstChildElement(impl,shibtarget::XML::SHIBTARGET_NS,Site);
193 auto_ptr_char id(impl->getAttributeNS(NULL,id));
195 g_Sites.insert(pair<string,site_t>(id.get(),site_t(impl)));
196 impl=saml::XML::getNextSiblingElement(impl,shibtarget::XML::SHIBTARGET_NS,Site);
204 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Filter startup failed with an exception.");
209 pVer->dwFilterVersion=HTTP_FILTER_REVISION;
210 strncpy(pVer->lpszFilterDesc,"Shibboleth ISAPI Filter",SF_MAX_FILTER_DESC_LEN);
211 pVer->dwFlags=(SF_NOTIFY_ORDER_HIGH |
212 SF_NOTIFY_SECURE_PORT |
213 SF_NOTIFY_NONSECURE_PORT |
214 SF_NOTIFY_PREPROC_HEADERS |
216 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter initialized...");
220 extern "C" BOOL WINAPI TerminateFilter(DWORD)
223 g_Config->shutdown();
225 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter shut down...");
229 /* Next up, some suck-free versions of various APIs.
231 You DON'T require people to guess the buffer size and THEN tell them the right size.
232 Returning an LPCSTR is apparently way beyond their ken. Not to mention the fact that
233 constant strings aren't typed as such, making it just that much harder. These versions
234 are now updated to use a special growable buffer object, modeled after the standard
235 string class. The standard string won't work because they left out the option to
236 pre-allocate a non-constant buffer.
242 dynabuf() { bufptr=NULL; buflen=0; }
243 dynabuf(size_t s) { bufptr=new char[buflen=s]; *bufptr=0; }
244 ~dynabuf() { delete[] bufptr; }
245 size_t length() const { return bufptr ? strlen(bufptr) : 0; }
246 size_t size() const { return buflen; }
247 bool empty() const { return length()==0; }
248 void reserve(size_t s, bool keep=false);
249 void erase() { if (bufptr) memset(bufptr,0,buflen); }
250 operator char*() { return bufptr; }
251 bool operator ==(const char* s) const;
252 bool operator !=(const char* s) const { return !(*this==s); }
258 void dynabuf::reserve(size_t s, bool keep)
265 p[buflen]=bufptr[buflen];
271 bool dynabuf::operator==(const char* s) const
273 if (buflen==NULL || s==NULL)
274 return (buflen==NULL && s==NULL);
276 return strcmp(bufptr,s)==0;
279 void GetServerVariable(PHTTP_FILTER_CONTEXT pfc, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
280 throw (bad_alloc, DWORD)
286 while (!pfc->GetServerVariable(pfc,lpszVariable,s,&size))
288 // Grumble. Check the error.
289 DWORD e=GetLastError();
290 if (e==ERROR_INSUFFICIENT_BUFFER)
295 if (bRequired && s.empty())
299 void GetServerVariable(LPEXTENSION_CONTROL_BLOCK lpECB, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
300 throw (bad_alloc, DWORD)
306 while (!lpECB->GetServerVariable(lpECB->ConnID,lpszVariable,s,&size))
308 // Grumble. Check the error.
309 DWORD e=GetLastError();
310 if (e==ERROR_INSUFFICIENT_BUFFER)
315 if (bRequired && s.empty())
319 void GetHeader(PHTTP_FILTER_PREPROC_HEADERS pn, PHTTP_FILTER_CONTEXT pfc,
320 LPSTR lpszName, dynabuf& s, DWORD size=80, bool bRequired=true)
321 throw (bad_alloc, DWORD)
327 while (!pn->GetHeader(pfc,lpszName,s,&size))
329 // Grumble. Check the error.
330 DWORD e=GetLastError();
331 if (e==ERROR_INSUFFICIENT_BUFFER)
336 if (bRequired && s.empty())
340 /****************************************************************************/
343 class ShibTargetIsapiF : public ShibTarget
345 PHTTP_FILTER_CONTEXT m_pfc;
346 PHTTP_FILTER_PREPROC_HEADERS m_pn;
349 ShibTargetIsapiF(PHTTP_FILTER_CONTEXT pfc, PHTTP_FILTER_PREPROC_HEADERS pn, const site_t& site) {
351 // URL path always come from IIS.
353 GetHeader(pn,pfc,"url",url,256,false);
355 // Port may come from IIS or from site def.
357 if (!g_bNormalizeRequest || (pfc->fIsSecurePort && site.m_sslport.empty()) || (!pfc->fIsSecurePort && site.m_port.empty()))
358 GetServerVariable(pfc,"SERVER_PORT",port,10);
359 else if (pfc->fIsSecurePort) {
360 strncpy(port,site.m_sslport.c_str(),10);
361 static_cast<char*>(port)[10]=0;
364 strncpy(port,site.m_port.c_str(),10);
365 static_cast<char*>(port)[10]=0;
368 // Scheme may come from site def or be derived from IIS.
369 const char* scheme=site.m_scheme.c_str();
370 if (!scheme || !*scheme || !g_bNormalizeRequest)
371 scheme=pfc->fIsSecurePort ? "https" : "http";
373 // Get the rest of the server variables.
374 dynabuf remote_addr(16),method(5),content_type(32),hostname(32);
375 GetServerVariable(pfc,"SERVER_NAME",hostname,32);
376 GetServerVariable(pfc,"REMOTE_ADDR",remote_addr,16);
377 GetServerVariable(pfc,"REQUEST_METHOD",method,5,false);
378 GetServerVariable(pfc,"CONTENT_TYPE",content_type,32,false);
380 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
381 const char* host=hostname;
382 if (site.m_name!=host && site.m_aliases.find(host)==site.m_aliases.end())
383 host=site.m_name.c_str();
385 init(scheme, host, atoi(port), url, content_type, remote_addr, method);
390 ~ShibTargetIsapiF() { }
392 virtual void log(ShibLogLevel level, const string &msg) {
393 ShibTarget::log(level,msg);
394 if (level == LogLevelError)
395 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
397 virtual string getCookies() const {
399 GetHeader(m_pn, m_pfc, "Cookie:", buf, 128, false);
400 return buf.empty() ? "" : buf;
403 virtual void clearHeader(const string &name) {
404 string hdr = (name=="REMOTE_USER" ? "remote-user" : name) + ":";
405 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), "");
407 virtual void setHeader(const string &name, const string &value) {
408 string hdr = name + ":";
409 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()),
410 const_cast<char*>(value.c_str()));
412 virtual string getHeader(const string &name) {
413 string hdr = name + ":";
415 GetHeader(m_pn, m_pfc, const_cast<char*>(hdr.c_str()), buf, 1024, false);
418 virtual void setRemoteUser(const string &user) {
419 setHeader(string("remote-user"), user);
421 virtual string getRemoteUser(void) {
422 return getHeader(string("remote-user"));
424 virtual void* sendPage(
427 const string& content_type="text/html",
428 const saml::Iterator<header_t>& headers=EMPTY(header_t)) {
429 string hdr = string ("Connection: close\r\nContent-type: ") + content_type + "\r\n";
430 while (headers.hasNext()) {
431 const header_t& h=headers.next();
432 hdr += h.first + ": " + h.second + "\r\n";
435 const char* codestr="200 OK";
437 case 403: codestr="403 Forbidden"; break;
438 case 404: codestr="404 Not Found"; break;
439 case 500: codestr="500 Server Error"; break;
441 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, (void*)codestr, (DWORD)hdr.c_str(), 0);
442 DWORD resplen = msg.size();
443 m_pfc->WriteClient(m_pfc, (LPVOID)msg.c_str(), &resplen, 0);
444 return (void*)SF_STATUS_REQ_FINISHED;
446 virtual void* sendRedirect(const string& url) {
447 // XXX: Don't support the httpRedirect option, yet.
448 string hdrs=m_cookie + string("Location: ") + url + "\r\n"
449 "Content-Type: text/html\r\n"
450 "Content-Length: 40\r\n"
451 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
452 "Cache-Control: private,no-store,no-cache\r\n\r\n";
453 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER,
454 "302 Please Wait", (DWORD)hdrs.c_str(), 0);
455 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
457 m_pfc->WriteClient(m_pfc, (LPVOID)redmsg, &resplen, 0);
458 return reinterpret_cast<void*>(SF_STATUS_REQ_FINISHED);
460 // XXX: We might not ever hit the 'decline' status in this filter.
461 //virtual void* returnDecline(void) { }
462 virtual void* returnOK(void) { return (void*) SF_STATUS_REQ_NEXT_NOTIFICATION; }
464 // The filter never processes the POST, so stub these methods.
465 virtual void setCookie(const string &name, const string &value) {
466 // Set the cookie for later. Use it during the redirect.
467 m_cookie += "Set-Cookie: " + name + "=" + value + "\r\n";
469 virtual const char* getQueryString() const { throw runtime_error("getQueryString not implemented"); }
470 virtual const char* getRequestBody() const { throw runtime_error("getRequestBody not implemented"); }
473 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const char* msg)
475 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
476 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
477 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)ctype,0);
478 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Filter Error</TITLE></HEAD><BODY>"
479 "<H1>Shibboleth Filter Error</H1>";
480 DWORD resplen=strlen(xmsg);
481 pfc->WriteClient(pfc,(LPVOID)xmsg,&resplen,0);
483 pfc->WriteClient(pfc,(LPVOID)msg,&resplen,0);
484 static const char* xmsg2="</BODY></HTML>";
485 resplen=strlen(xmsg2);
486 pfc->WriteClient(pfc,(LPVOID)xmsg2,&resplen,0);
487 return SF_STATUS_REQ_FINISHED;
490 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
492 // Is this a log notification?
493 if (notificationType==SF_NOTIFY_LOG)
495 if (pfc->pFilterContext)
496 ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=static_cast<LPCSTR>(pfc->pFilterContext);
497 return SF_STATUS_REQ_NEXT_NOTIFICATION;
500 PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
503 // Determine web site number. This can't really fail, I don't think.
505 GetServerVariable(pfc,"INSTANCE_ID",buf,10);
507 // Match site instance to host name, skip if no match.
508 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
509 if (map_i==g_Sites.end())
510 return SF_STATUS_REQ_NEXT_NOTIFICATION;
512 ostringstream threadid;
513 threadid << "[" << getpid() << "] isapi_shib" << '\0';
514 saml::NDC ndc(threadid.str().c_str());
516 ShibTargetIsapiF stf(pfc, pn, map_i->second);
518 // "false" because we don't override the Shib settings
519 pair<bool,void*> res = stf.doCheckAuthN();
520 if (res.first) return (DWORD)res.second;
522 // "false" because we don't override the Shib settings
523 res = stf.doExportAssertions();
524 if (res.first) return (DWORD)res.second;
526 res = stf.doCheckAuthZ();
527 if (res.first) return (DWORD)res.second;
529 return SF_STATUS_REQ_NEXT_NOTIFICATION;
532 return WriteClientError(pfc,"Out of Memory");
535 if (e==ERROR_NO_DATA)
536 return WriteClientError(pfc,"A required variable or header was empty.");
538 return WriteClientError(pfc,"Shibboleth Filter detected unexpected IIS error.");
540 catch (saml::SAMLException& e) {
541 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
542 return WriteClientError(pfc,"Shibboleth Filter caught an exception, check Event Log for details.");
546 return WriteClientError(pfc,"Shibboleth Filter caught an unknown exception.");
550 return WriteClientError(pfc,"Shibboleth Filter reached unreachable code, save my walrus!");
554 /****************************************************************************/
557 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* msg)
559 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
560 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
561 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
562 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Error</TITLE></HEAD><BODY><H1>Shibboleth Error</H1>";
563 DWORD resplen=strlen(xmsg);
564 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg,&resplen,HSE_IO_SYNC);
566 lpECB->WriteClient(lpECB->ConnID,(LPVOID)msg,&resplen,HSE_IO_SYNC);
567 static const char* xmsg2="</BODY></HTML>";
568 resplen=strlen(xmsg2);
569 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg2,&resplen,HSE_IO_SYNC);
570 return HSE_STATUS_SUCCESS;
574 class ShibTargetIsapiE : public ShibTarget
576 LPEXTENSION_CONTROL_BLOCK m_lpECB;
578 mutable string m_body;
579 mutable bool m_gotBody;
582 ShibTargetIsapiE(LPEXTENSION_CONTROL_BLOCK lpECB, const site_t& site) : m_gotBody(false) {
584 GetServerVariable(lpECB,"HTTPS",ssl,5);
585 bool SSL=(ssl=="on" || ssl=="ON");
587 // URL path always come from IIS.
589 GetServerVariable(lpECB,"URL",url,255);
591 // Port may come from IIS or from site def.
593 if (!g_bNormalizeRequest || (SSL && site.m_sslport.empty()) || (!SSL && site.m_port.empty()))
594 GetServerVariable(lpECB,"SERVER_PORT",port,10);
596 strncpy(port,site.m_sslport.c_str(),10);
597 static_cast<char*>(port)[10]=0;
600 strncpy(port,site.m_port.c_str(),10);
601 static_cast<char*>(port)[10]=0;
604 // Scheme may come from site def or be derived from IIS.
605 const char* scheme=site.m_scheme.c_str();
606 if (!scheme || !*scheme || !g_bNormalizeRequest) {
607 scheme = SSL ? "https" : "http";
610 // Get the other server variables.
611 dynabuf remote_addr(16),hostname(32);
612 GetServerVariable(lpECB, "REMOTE_ADDR", remote_addr, 16);
613 GetServerVariable(lpECB, "SERVER_NAME", hostname, 32);
615 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
616 const char* host=hostname;
617 if (site.m_name!=host && site.m_aliases.find(host)==site.m_aliases.end())
618 host=site.m_name.c_str();
621 * IIS screws us over on PATH_INFO (the hits keep on coming). We need to figure out if
622 * the server is set up for proper PATH_INFO handling, or "IIS sucks rabid weasels mode",
623 * which is the default. No perfect way to tell, but we can take a good guess by checking
624 * whether the URL is a substring of the PATH_INFO:
626 * e.g. for /Shibboleth.sso/SAML/POST
628 * Bad mode (default):
629 * URL: /Shibboleth.sso
630 * PathInfo: /Shibboleth.sso/SAML/POST
633 * URL: /Shibboleth.sso
634 * PathInfo: /SAML/POST
639 // Clearly we're only in bad mode if path info exists at all.
640 if (lpECB->lpszPathInfo && *(lpECB->lpszPathInfo)) {
641 if (strstr(lpECB->lpszPathInfo,url))
642 // Pretty good chance we're in bad mode, unless the PathInfo repeats the path itself.
643 fullurl=lpECB->lpszPathInfo;
646 fullurl+=lpECB->lpszPathInfo;
650 // For consistency with Apache, let's add the query string.
651 if (lpECB->lpszQueryString && *(lpECB->lpszQueryString)) {
653 fullurl+=lpECB->lpszQueryString;
655 init(scheme, host, atoi(port), fullurl.c_str(), lpECB->lpszContentType, remote_addr, lpECB->lpszMethod);
659 ~ShibTargetIsapiE() { }
661 virtual void log(ShibLogLevel level, const string &msg) {
662 ShibTarget::log(level,msg);
663 if (level == LogLevelError)
664 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
666 virtual string getCookies() const {
668 GetServerVariable(m_lpECB, "HTTP_COOKIE", buf, 128, false);
669 return buf.empty() ? "" : buf;
671 virtual void setCookie(const string &name, const string &value) {
672 // Set the cookie for later. Use it during the redirect.
673 m_cookie += "Set-Cookie: " + name + "=" + value + "\r\n";
675 virtual const char* getQueryString() const {
676 return m_lpECB->lpszQueryString;
678 virtual const char* getRequestBody() const {
680 return m_body.c_str();
681 if (m_lpECB->cbTotalBytes > 1024*1024) // 1MB?
682 throw saml::SAMLException("Size of POST request body exceeded limit.");
683 else if (m_lpECB->cbTotalBytes != m_lpECB->cbAvailable) {
686 DWORD datalen=m_lpECB->cbTotalBytes;
689 BOOL ret = m_lpECB->ReadClient(m_lpECB->ConnID, buf, &buflen);
691 throw saml::SAMLException("Error reading POST request body from browser.");
692 m_body.append(buf, buflen);
698 m_body.assign(reinterpret_cast<char*>(m_lpECB->lpbData),m_lpECB->cbAvailable);
700 return m_body.c_str();
702 virtual void* sendPage(
705 const string& content_type="text/html",
706 const saml::Iterator<header_t>& headers=EMPTY(header_t)) {
707 string hdr = string ("Connection: close\r\nContent-type: ") + content_type + "\r\n";
708 for (unsigned int k = 0; k < headers.size(); k++) {
709 hdr += headers[k].first + ": " + headers[k].second + "\r\n";
712 const char* codestr="200 OK";
714 case 403: codestr="403 Forbidden"; break;
715 case 404: codestr="404 Not Found"; break;
716 case 500: codestr="500 Server Error"; break;
718 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, (void*)codestr, 0, (LPDWORD)hdr.c_str());
719 DWORD resplen = msg.size();
720 m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)msg.c_str(), &resplen, HSE_IO_SYNC);
721 return (void*)HSE_STATUS_SUCCESS;
723 virtual void* sendRedirect(const string& url) {
724 // XXX: Don't support the httpRedirect option, yet.
725 string hdrs = m_cookie + "Location: " + url + "\r\n"
726 "Content-Type: text/html\r\n"
727 "Content-Length: 40\r\n"
728 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
729 "Cache-Control: private,no-store,no-cache\r\n\r\n";
730 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER,
731 "302 Moved", 0, (LPDWORD)hdrs.c_str());
732 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
734 m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)redmsg, &resplen, HSE_IO_SYNC);
735 return (void*)HSE_STATUS_SUCCESS;
737 // Decline happens in the POST processor if this isn't the shire url
738 // Note that it can also happen with HTAccess, but we don't support that, yet.
739 virtual void* returnDecline(void) {
741 WriteClientError(m_lpECB, "ISAPI extension can only be invoked to process Shibboleth protocol requests."
742 "Make sure the mapped file extension doesn't match actual content.");
744 virtual void* returnOK(void) { return (void*) HSE_STATUS_SUCCESS; }
746 // Not used in the extension.
747 virtual void clearHeader(const string &name) { throw runtime_error("clearHeader not implemented"); }
748 virtual void setHeader(const string &name, const string &value) { throw runtime_error("setHeader not implemented"); }
749 virtual string getHeader(const string &name) { throw runtime_error("getHeader not implemented"); }
750 virtual void setRemoteUser(const string &user) { throw runtime_error("setRemoteUser not implemented"); }
751 virtual string getRemoteUser(void) { throw runtime_error("getRemoteUser not implemented"); }
754 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
757 const IApplication* application=NULL;
759 ostringstream threadid;
760 threadid << "[" << getpid() << "] isapi_shib_extension" << '\0';
761 saml::NDC ndc(threadid.str().c_str());
763 // Determine web site number. This can't really fail, I don't think.
765 GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
767 // Match site instance to host name, skip if no match.
768 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
769 if (map_i==g_Sites.end())
770 return WriteClientError(lpECB, "Shibboleth Extension not configured for this web site.");
772 ShibTargetIsapiE ste(lpECB, map_i->second);
773 pair<bool,void*> res = ste.doHandler();
774 if (res.first) return (DWORD)res.second;
776 return WriteClientError(lpECB, "Shibboleth Extension failed to process request");
780 return WriteClientError(lpECB,"Out of Memory");
783 if (e==ERROR_NO_DATA)
784 return WriteClientError(lpECB,"A required variable or header was empty.");
786 return WriteClientError(lpECB,"Server detected unexpected IIS error.");
788 catch (saml::SAMLException& e) {
789 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
790 return WriteClientError(lpECB,"Shibboleth Extension caught an exception, check Event Log for details.");
794 return WriteClientError(lpECB,"Shibboleth Extension caught an unknown exception.");
798 // If we get here we've got an error.
799 return HSE_STATUS_ERROR;