2 * Copyright 2001-2005 Internet2
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 /* isapi_shib.cpp - Shibboleth ISAPI filter
23 #include "config_win32.h"
26 #include <saml/saml.h>
27 #include <shib/shib.h>
28 #include <shib/shib-threads.h>
29 #include <shib-target/shib-target.h>
42 using namespace shibboleth;
43 using namespace shibtarget;
47 static const XMLCh name[] = { chLatin_n, chLatin_a, chLatin_m, chLatin_e, chNull };
48 static const XMLCh port[] = { chLatin_p, chLatin_o, chLatin_r, chLatin_t, chNull };
49 static const XMLCh sslport[] = { chLatin_s, chLatin_s, chLatin_l, chLatin_p, chLatin_o, chLatin_r, chLatin_t, chNull };
50 static const XMLCh scheme[] = { chLatin_s, chLatin_c, chLatin_h, chLatin_e, chLatin_m, chLatin_e, chNull };
51 static const XMLCh id[] = { chLatin_i, chLatin_d, chNull };
52 static const XMLCh Implementation[] =
53 { chLatin_I, chLatin_m, chLatin_p, chLatin_l, chLatin_e, chLatin_m, chLatin_e, chLatin_n, chLatin_t, chLatin_a, chLatin_t, chLatin_i, chLatin_o, chLatin_n, chNull };
54 static const XMLCh ISAPI[] = { chLatin_I, chLatin_S, chLatin_A, chLatin_P, chLatin_I, chNull };
55 static const XMLCh Alias[] = { chLatin_A, chLatin_l, chLatin_i, chLatin_a, chLatin_s, chNull };
56 static const XMLCh normalizeRequest[] =
57 { chLatin_n, chLatin_o, chLatin_r, chLatin_m, chLatin_a, chLatin_l, chLatin_i, chLatin_z, chLatin_e,
58 chLatin_R, chLatin_e, chLatin_q, chLatin_u, chLatin_e, chLatin_s, chLatin_t, chNull
60 static const XMLCh Site[] = { chLatin_S, chLatin_i, chLatin_t, chLatin_e, chNull };
63 site_t(const DOMElement* e)
65 auto_ptr_char n(e->getAttributeNS(NULL,name));
66 auto_ptr_char s(e->getAttributeNS(NULL,scheme));
67 auto_ptr_char p(e->getAttributeNS(NULL,port));
68 auto_ptr_char p2(e->getAttributeNS(NULL,sslport));
69 if (n.get()) m_name=n.get();
70 if (s.get()) m_scheme=s.get();
71 if (p.get()) m_port=p.get();
72 if (p2.get()) m_sslport=p2.get();
73 DOMNodeList* nlist=e->getElementsByTagNameNS(shibtarget::XML::SHIBTARGET_NS,Alias);
74 for (int i=0; nlist && i<nlist->getLength(); i++) {
75 if (nlist->item(i)->hasChildNodes()) {
76 auto_ptr_char alias(nlist->item(i)->getFirstChild()->getNodeValue());
77 m_aliases.insert(alias.get());
81 string m_scheme,m_port,m_sslport,m_name;
82 set<string> m_aliases;
86 ShibTargetConfig* g_Config = NULL;
87 map<string,site_t> g_Sites;
88 bool g_bNormalizeRequest = true;
89 string g_unsetHeaderValue;
93 LPCSTR lpUNCServerName,
99 LPCSTR messages[] = {message, NULL};
101 HANDLE hElog = RegisterEventSource(lpUNCServerName, "Shibboleth ISAPI Filter");
102 BOOL res = ReportEvent(hElog, wType, 0, dwEventID, lpUserSid, 1, 0, messages, NULL);
103 return (DeregisterEventSource(hElog) && res);
106 extern "C" __declspec(dllexport) BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID)
108 if (fdwReason==DLL_PROCESS_ATTACH)
113 extern "C" BOOL WINAPI GetExtensionVersion(HSE_VERSION_INFO* pVer)
120 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
121 "Extension mode startup not possible, is the DLL loaded as a filter?");
125 pVer->dwExtensionVersion=HSE_VERSION;
126 strncpy(pVer->lpszExtensionDesc,"Shibboleth ISAPI Extension",HSE_MAX_EXT_DLL_NAME_LEN-1);
130 extern "C" BOOL WINAPI TerminateExtension(DWORD)
132 return TRUE; // cleanup should happen when filter unloads
135 extern "C" BOOL WINAPI GetFilterVersion(PHTTP_FILTER_VERSION pVer)
140 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
141 "Reentrant filter initialization, ignoring...");
149 LPCSTR schemadir=getenv("SHIBSCHEMAS");
151 schemadir=SHIB_SCHEMAS;
152 LPCSTR config=getenv("SHIBCONFIG");
155 g_Config=&ShibTargetConfig::getConfig();
156 g_Config->setFeatures(
157 ShibTargetConfig::Listener |
158 ShibTargetConfig::Metadata |
159 ShibTargetConfig::AAP |
160 ShibTargetConfig::RequestMapper |
161 ShibTargetConfig::LocalExtensions |
162 ShibTargetConfig::Logging
164 if (!g_Config->init(schemadir)) {
166 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
167 "Filter startup failed during library initialization, check native log for help.");
170 else if (!g_Config->load(config)) {
172 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
173 "Filter startup failed to load configuration, check native log for help.");
177 // Access the implementation-specifics for site mappings.
178 IConfig* conf=g_Config->getINI();
180 const IPropertySet* props=conf->getPropertySet("Local");
182 pair<bool,const char*> unsetValue=props->getString("unsetHeaderValue");
183 if (unsetValue.first)
184 g_unsetHeaderValue = unsetValue.second;
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);
395 virtual string getCookies() const {
397 GetHeader(m_pn, m_pfc, "Cookie:", buf, 128, false);
398 return buf.empty() ? "" : buf;
401 virtual void clearHeader(const string &name) {
402 string hdr = (name=="REMOTE_USER" ? "remote-user" : name) + ":";
403 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), const_cast<char*>(g_unsetHeaderValue.c_str()));
405 virtual void setHeader(const string &name, const string &value) {
406 string hdr = name + ":";
407 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()),
408 const_cast<char*>(value.c_str()));
410 virtual string getHeader(const string &name) {
411 string hdr = name + ":";
413 GetHeader(m_pn, m_pfc, const_cast<char*>(hdr.c_str()), buf, 1024, false);
416 virtual void setRemoteUser(const string &user) {
417 setHeader(string("remote-user"), user);
419 virtual string getRemoteUser(void) {
420 return getHeader(string("remote-user"));
422 virtual void* sendPage(
425 const string& content_type="text/html",
426 const Iterator<header_t>& headers=EMPTY(header_t)) {
427 string hdr = string ("Connection: close\r\nContent-type: ") + content_type + "\r\n";
428 while (headers.hasNext()) {
429 const header_t& h=headers.next();
430 hdr += h.first + ": " + h.second + "\r\n";
433 const char* codestr="200 OK";
435 case 403: codestr="403 Forbidden"; break;
436 case 404: codestr="404 Not Found"; break;
437 case 500: codestr="500 Server Error"; break;
439 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, (void*)codestr, (DWORD)hdr.c_str(), 0);
440 DWORD resplen = msg.size();
441 m_pfc->WriteClient(m_pfc, (LPVOID)msg.c_str(), &resplen, 0);
442 return (void*)SF_STATUS_REQ_FINISHED;
444 virtual void* sendRedirect(const string& url) {
445 // XXX: Don't support the httpRedirect option, yet.
446 string hdrs=m_cookie + string("Location: ") + url + "\r\n"
447 "Content-Type: text/html\r\n"
448 "Content-Length: 40\r\n"
449 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
450 "Cache-Control: private,no-store,no-cache\r\n\r\n";
451 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER,
452 "302 Please Wait", (DWORD)hdrs.c_str(), 0);
453 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
455 m_pfc->WriteClient(m_pfc, (LPVOID)redmsg, &resplen, 0);
456 return reinterpret_cast<void*>(SF_STATUS_REQ_FINISHED);
458 // XXX: We might not ever hit the 'decline' status in this filter.
459 //virtual void* returnDecline(void) { }
460 virtual void* returnOK(void) { return (void*) SF_STATUS_REQ_NEXT_NOTIFICATION; }
462 // The filter never processes the POST, so stub these methods.
463 virtual void setCookie(const string &name, const string &value) {
464 // Set the cookie for later. Use it during the redirect.
465 m_cookie += "Set-Cookie: " + name + "=" + value + "\r\n";
467 virtual string getArgs(void) { throw runtime_error("getArgs not implemented"); }
468 virtual string getPostData(void) { throw runtime_error("getPostData not implemented"); }
471 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const char* msg)
473 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
474 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
475 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)ctype,0);
476 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Filter Error</TITLE></HEAD><BODY>"
477 "<H1>Shibboleth Filter Error</H1>";
478 DWORD resplen=strlen(xmsg);
479 pfc->WriteClient(pfc,(LPVOID)xmsg,&resplen,0);
481 pfc->WriteClient(pfc,(LPVOID)msg,&resplen,0);
482 static const char* xmsg2="</BODY></HTML>";
483 resplen=strlen(xmsg2);
484 pfc->WriteClient(pfc,(LPVOID)xmsg2,&resplen,0);
485 return SF_STATUS_REQ_FINISHED;
488 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
490 // Is this a log notification?
491 if (notificationType==SF_NOTIFY_LOG)
493 if (pfc->pFilterContext)
494 ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=static_cast<LPCSTR>(pfc->pFilterContext);
495 return SF_STATUS_REQ_NEXT_NOTIFICATION;
498 PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
501 // Determine web site number. This can't really fail, I don't think.
503 GetServerVariable(pfc,"INSTANCE_ID",buf,10);
505 // Match site instance to host name, skip if no match.
506 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
507 if (map_i==g_Sites.end())
508 return SF_STATUS_REQ_NEXT_NOTIFICATION;
510 ostringstream threadid;
511 threadid << "[" << getpid() << "] isapi_shib" << '\0';
512 saml::NDC ndc(threadid.str().c_str());
514 ShibTargetIsapiF stf(pfc, pn, map_i->second);
516 // "false" because we don't override the Shib settings
517 pair<bool,void*> res = stf.doCheckAuthN();
518 if (res.first) return (DWORD)res.second;
520 // "false" because we don't override the Shib settings
521 res = stf.doExportAssertions();
522 if (res.first) return (DWORD)res.second;
524 res = stf.doCheckAuthZ();
525 if (res.first) return (DWORD)res.second;
527 return SF_STATUS_REQ_NEXT_NOTIFICATION;
530 return WriteClientError(pfc,"Out of Memory");
533 if (e==ERROR_NO_DATA)
534 return WriteClientError(pfc,"A required variable or header was empty.");
536 return WriteClientError(pfc,"Shibboleth Filter detected unexpected IIS error.");
538 catch (SAMLException& e) {
539 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
540 return WriteClientError(pfc,"Shibboleth Filter caught an exception, ask administrator to check Event Log for details.");
544 return WriteClientError(pfc,"Shibboleth Filter caught an unknown exception.");
548 return WriteClientError(pfc,"Shibboleth Filter reached unreachable code, save my walrus!");
552 /****************************************************************************/
555 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* msg)
557 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
558 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
559 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
560 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Error</TITLE></HEAD><BODY><H1>Shibboleth Error</H1>";
561 DWORD resplen=strlen(xmsg);
562 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg,&resplen,HSE_IO_SYNC);
564 lpECB->WriteClient(lpECB->ConnID,(LPVOID)msg,&resplen,HSE_IO_SYNC);
565 static const char* xmsg2="</BODY></HTML>";
566 resplen=strlen(xmsg2);
567 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg2,&resplen,HSE_IO_SYNC);
568 return HSE_STATUS_SUCCESS;
572 class ShibTargetIsapiE : public ShibTarget
574 LPEXTENSION_CONTROL_BLOCK m_lpECB;
578 ShibTargetIsapiE(LPEXTENSION_CONTROL_BLOCK lpECB, const site_t& site) {
580 GetServerVariable(lpECB,"HTTPS",ssl,5);
581 bool SSL=(ssl=="on" || ssl=="ON");
583 // URL path always come from IIS.
585 GetServerVariable(lpECB,"URL",url,255);
587 // Port may come from IIS or from site def.
589 if (!g_bNormalizeRequest || (SSL && site.m_sslport.empty()) || (!SSL && site.m_port.empty()))
590 GetServerVariable(lpECB,"SERVER_PORT",port,10);
592 strncpy(port,site.m_sslport.c_str(),10);
593 static_cast<char*>(port)[10]=0;
596 strncpy(port,site.m_port.c_str(),10);
597 static_cast<char*>(port)[10]=0;
600 // Scheme may come from site def or be derived from IIS.
601 const char* scheme=site.m_scheme.c_str();
602 if (!scheme || !*scheme || !g_bNormalizeRequest) {
603 scheme = SSL ? "https" : "http";
606 // Get the other server variables.
607 dynabuf remote_addr(16),hostname(32);
608 GetServerVariable(lpECB, "REMOTE_ADDR", remote_addr, 16);
609 GetServerVariable(lpECB, "SERVER_NAME", hostname, 32);
611 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
612 const char* host=hostname;
613 if (site.m_name!=host && site.m_aliases.find(host)==site.m_aliases.end())
614 host=site.m_name.c_str();
617 * IIS screws us over on PATH_INFO (the hits keep on coming). We need to figure out if
618 * the server is set up for proper PATH_INFO handling, or "IIS sucks rabid weasels mode",
619 * which is the default. No perfect way to tell, but we can take a good guess by checking
620 * whether the URL is a substring of the PATH_INFO:
622 * e.g. for /Shibboleth.sso/SAML/POST
624 * Bad mode (default):
625 * URL: /Shibboleth.sso
626 * PathInfo: /Shibboleth.sso/SAML/POST
629 * URL: /Shibboleth.sso
630 * PathInfo: /SAML/POST
635 // Clearly we're only in bad mode if path info exists at all.
636 if (lpECB->lpszPathInfo && *(lpECB->lpszPathInfo)) {
637 if (strstr(lpECB->lpszPathInfo,url))
638 // Pretty good chance we're in bad mode, unless the PathInfo repeats the path itself.
639 fullurl=lpECB->lpszPathInfo;
642 fullurl+=lpECB->lpszPathInfo;
646 // For consistency with Apache, let's add the query string.
647 if (lpECB->lpszQueryString && *(lpECB->lpszQueryString)) {
649 fullurl+=lpECB->lpszQueryString;
651 init(scheme, host, atoi(port), fullurl.c_str(), lpECB->lpszContentType, remote_addr, lpECB->lpszMethod);
655 ~ShibTargetIsapiE() { }
657 virtual void log(ShibLogLevel level, const string &msg) {
658 ShibTarget::log(level,msg);
659 if (level == LogLevelError)
660 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
662 virtual string getCookies() const {
664 GetServerVariable(m_lpECB, "HTTP_COOKIE", buf, 128, false);
665 return buf.empty() ? "" : buf;
667 virtual void setCookie(const string &name, const string &value) {
668 // Set the cookie for later. Use it during the redirect.
669 m_cookie += "Set-Cookie: " + name + "=" + value + "\r\n";
671 virtual string getArgs(void) {
672 return string(m_lpECB->lpszQueryString ? m_lpECB->lpszQueryString : "");
674 virtual string getPostData(void) {
675 if (m_lpECB->cbTotalBytes > 1024*1024) // 1MB?
676 throw FatalProfileException("Blocked too-large a submission to profile endpoint.");
677 else if (m_lpECB->cbTotalBytes != m_lpECB->cbAvailable) {
680 DWORD datalen=m_lpECB->cbTotalBytes;
683 BOOL ret = m_lpECB->ReadClient(m_lpECB->ConnID, buf, &buflen);
685 throw FatalProfileException("Error reading profile submission from browser.");
686 cgistr.append(buf, buflen);
692 return string(reinterpret_cast<char*>(m_lpECB->lpbData),m_lpECB->cbAvailable);
694 virtual void* sendPage(
697 const string& content_type="text/html",
698 const Iterator<header_t>& headers=EMPTY(header_t)) {
699 string hdr = string ("Connection: close\r\nContent-type: ") + content_type + "\r\n";
700 for (int k = 0; k < headers.size(); k++) {
701 hdr += headers[k].first + ": " + headers[k].second + "\r\n";
704 const char* codestr="200 OK";
706 case 403: codestr="403 Forbidden"; break;
707 case 404: codestr="404 Not Found"; break;
708 case 500: codestr="500 Server Error"; break;
710 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, (void*)codestr, 0, (LPDWORD)hdr.c_str());
711 DWORD resplen = msg.size();
712 m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)msg.c_str(), &resplen, HSE_IO_SYNC);
713 return (void*)HSE_STATUS_SUCCESS;
715 virtual void* sendRedirect(const string& url) {
716 // XXX: Don't support the httpRedirect option, yet.
717 string hdrs = m_cookie + "Location: " + url + "\r\n"
718 "Content-Type: text/html\r\n"
719 "Content-Length: 40\r\n"
720 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
721 "Cache-Control: private,no-store,no-cache\r\n\r\n";
722 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER,
723 "302 Moved", 0, (LPDWORD)hdrs.c_str());
724 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
726 m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)redmsg, &resplen, HSE_IO_SYNC);
727 return (void*)HSE_STATUS_SUCCESS;
729 // Decline happens in the POST processor if this isn't the shire url
730 // Note that it can also happen with HTAccess, but we don't support that, yet.
731 virtual void* returnDecline(void) {
733 WriteClientError(m_lpECB, "ISAPI extension can only be invoked to process Shibboleth protocol requests."
734 "Make sure the mapped file extension doesn't match actual content.");
736 virtual void* returnOK(void) { return (void*) HSE_STATUS_SUCCESS; }
738 // Not used in the extension.
739 virtual void clearHeader(const string &name) { throw runtime_error("clearHeader not implemented"); }
740 virtual void setHeader(const string &name, const string &value) { throw runtime_error("setHeader not implemented"); }
741 virtual string getHeader(const string &name) { throw runtime_error("getHeader not implemented"); }
742 virtual void setRemoteUser(const string &user) { throw runtime_error("setRemoteUser not implemented"); }
743 virtual string getRemoteUser(void) { throw runtime_error("getRemoteUser not implemented"); }
746 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
749 const IApplication* application=NULL;
751 ostringstream threadid;
752 threadid << "[" << getpid() << "] isapi_shib_extension" << '\0';
753 saml::NDC ndc(threadid.str().c_str());
755 // Determine web site number. This can't really fail, I don't think.
757 GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
759 // Match site instance to host name, skip if no match.
760 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
761 if (map_i==g_Sites.end())
762 return WriteClientError(lpECB, "Shibboleth Extension not configured for this web site.");
764 ShibTargetIsapiE ste(lpECB, map_i->second);
765 pair<bool,void*> res = ste.doHandler();
766 if (res.first) return (DWORD)res.second;
768 return WriteClientError(lpECB, "Shibboleth Extension failed to process request");
772 return WriteClientError(lpECB,"Out of Memory");
775 if (e==ERROR_NO_DATA)
776 return WriteClientError(lpECB,"A required variable or header was empty.");
778 return WriteClientError(lpECB,"Server detected unexpected IIS error.");
780 catch (SAMLException& e) {
781 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
782 return WriteClientError(lpECB,"Shibboleth Extension caught an exception, check Event Log for details.");
786 return WriteClientError(lpECB,"Shibboleth Extension caught an unknown exception.");
790 // If we get here we've got an error.
791 return HSE_STATUS_ERROR;