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 (unsigned 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;
92 LPCSTR lpUNCServerName,
98 LPCSTR messages[] = {message, NULL};
100 HANDLE hElog = RegisterEventSource(lpUNCServerName, "Shibboleth ISAPI Filter");
101 BOOL res = ReportEvent(hElog, wType, 0, dwEventID, lpUserSid, 1, 0, messages, NULL);
102 return (DeregisterEventSource(hElog) && res);
105 extern "C" __declspec(dllexport) BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID)
107 if (fdwReason==DLL_PROCESS_ATTACH)
112 extern "C" BOOL WINAPI GetExtensionVersion(HSE_VERSION_INFO* pVer)
119 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
120 "Extension mode startup not possible, is the DLL loaded as a filter?");
124 pVer->dwExtensionVersion=HSE_VERSION;
125 strncpy(pVer->lpszExtensionDesc,"Shibboleth ISAPI Extension",HSE_MAX_EXT_DLL_NAME_LEN-1);
129 extern "C" BOOL WINAPI TerminateExtension(DWORD)
131 return TRUE; // cleanup should happen when filter unloads
134 extern "C" BOOL WINAPI GetFilterVersion(PHTTP_FILTER_VERSION pVer)
139 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
140 "Reentrant filter initialization, ignoring...");
148 LPCSTR schemadir=getenv("SHIBSCHEMAS");
150 schemadir=SHIB_SCHEMAS;
151 LPCSTR config=getenv("SHIBCONFIG");
154 g_Config=&ShibTargetConfig::getConfig();
155 g_Config->setFeatures(
156 ShibTargetConfig::Listener |
157 ShibTargetConfig::Metadata |
158 ShibTargetConfig::AAP |
159 ShibTargetConfig::RequestMapper |
160 ShibTargetConfig::LocalExtensions |
161 ShibTargetConfig::Logging
163 if (!g_Config->init(schemadir)) {
165 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
166 "Filter startup failed during library initialization, check native log for help.");
169 else if (!g_Config->load(config)) {
171 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
172 "Filter startup failed to load configuration, check native log for help.");
176 // Access the implementation-specifics for site mappings.
177 IConfig* conf=g_Config->getINI();
179 const IPropertySet* props=conf->getPropertySet("Local");
181 const DOMElement* impl=saml::XML::getFirstChildElement(
182 props->getElement(),shibtarget::XML::SHIBTARGET_NS,Implementation
184 if (impl && (impl=saml::XML::getFirstChildElement(impl,shibtarget::XML::SHIBTARGET_NS,ISAPI))) {
185 const XMLCh* flag=impl->getAttributeNS(NULL,normalizeRequest);
186 g_bNormalizeRequest=(!flag || !*flag || *flag==chDigit_1 || *flag==chLatin_t);
187 impl=saml::XML::getFirstChildElement(impl,shibtarget::XML::SHIBTARGET_NS,Site);
189 auto_ptr_char id(impl->getAttributeNS(NULL,id));
191 g_Sites.insert(pair<string,site_t>(id.get(),site_t(impl)));
192 impl=saml::XML::getNextSiblingElement(impl,shibtarget::XML::SHIBTARGET_NS,Site);
200 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Filter startup failed with an exception.");
205 pVer->dwFilterVersion=HTTP_FILTER_REVISION;
206 strncpy(pVer->lpszFilterDesc,"Shibboleth ISAPI Filter",SF_MAX_FILTER_DESC_LEN);
207 pVer->dwFlags=(SF_NOTIFY_ORDER_HIGH |
208 SF_NOTIFY_SECURE_PORT |
209 SF_NOTIFY_NONSECURE_PORT |
210 SF_NOTIFY_PREPROC_HEADERS |
212 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter initialized...");
216 extern "C" BOOL WINAPI TerminateFilter(DWORD)
219 g_Config->shutdown();
221 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter shut down...");
225 /* Next up, some suck-free versions of various APIs.
227 You DON'T require people to guess the buffer size and THEN tell them the right size.
228 Returning an LPCSTR is apparently way beyond their ken. Not to mention the fact that
229 constant strings aren't typed as such, making it just that much harder. These versions
230 are now updated to use a special growable buffer object, modeled after the standard
231 string class. The standard string won't work because they left out the option to
232 pre-allocate a non-constant buffer.
238 dynabuf() { bufptr=NULL; buflen=0; }
239 dynabuf(size_t s) { bufptr=new char[buflen=s]; *bufptr=0; }
240 ~dynabuf() { delete[] bufptr; }
241 size_t length() const { return bufptr ? strlen(bufptr) : 0; }
242 size_t size() const { return buflen; }
243 bool empty() const { return length()==0; }
244 void reserve(size_t s, bool keep=false);
245 void erase() { if (bufptr) memset(bufptr,0,buflen); }
246 operator char*() { return bufptr; }
247 bool operator ==(const char* s) const;
248 bool operator !=(const char* s) const { return !(*this==s); }
254 void dynabuf::reserve(size_t s, bool keep)
261 p[buflen]=bufptr[buflen];
267 bool dynabuf::operator==(const char* s) const
269 if (buflen==NULL || s==NULL)
270 return (buflen==NULL && s==NULL);
272 return strcmp(bufptr,s)==0;
275 void GetServerVariable(PHTTP_FILTER_CONTEXT pfc, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
276 throw (bad_alloc, DWORD)
282 while (!pfc->GetServerVariable(pfc,lpszVariable,s,&size))
284 // Grumble. Check the error.
285 DWORD e=GetLastError();
286 if (e==ERROR_INSUFFICIENT_BUFFER)
291 if (bRequired && s.empty())
295 void GetServerVariable(LPEXTENSION_CONTROL_BLOCK lpECB, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
296 throw (bad_alloc, DWORD)
302 while (!lpECB->GetServerVariable(lpECB->ConnID,lpszVariable,s,&size))
304 // Grumble. Check the error.
305 DWORD e=GetLastError();
306 if (e==ERROR_INSUFFICIENT_BUFFER)
311 if (bRequired && s.empty())
315 void GetHeader(PHTTP_FILTER_PREPROC_HEADERS pn, PHTTP_FILTER_CONTEXT pfc,
316 LPSTR lpszName, dynabuf& s, DWORD size=80, bool bRequired=true)
317 throw (bad_alloc, DWORD)
323 while (!pn->GetHeader(pfc,lpszName,s,&size))
325 // Grumble. Check the error.
326 DWORD e=GetLastError();
327 if (e==ERROR_INSUFFICIENT_BUFFER)
332 if (bRequired && s.empty())
336 /****************************************************************************/
339 class ShibTargetIsapiF : public ShibTarget
341 PHTTP_FILTER_CONTEXT m_pfc;
342 PHTTP_FILTER_PREPROC_HEADERS m_pn;
345 ShibTargetIsapiF(PHTTP_FILTER_CONTEXT pfc, PHTTP_FILTER_PREPROC_HEADERS pn, const site_t& site) {
347 // URL path always come from IIS.
349 GetHeader(pn,pfc,"url",url,256,false);
351 // Port may come from IIS or from site def.
353 if (!g_bNormalizeRequest || (pfc->fIsSecurePort && site.m_sslport.empty()) || (!pfc->fIsSecurePort && site.m_port.empty()))
354 GetServerVariable(pfc,"SERVER_PORT",port,10);
355 else if (pfc->fIsSecurePort) {
356 strncpy(port,site.m_sslport.c_str(),10);
357 static_cast<char*>(port)[10]=0;
360 strncpy(port,site.m_port.c_str(),10);
361 static_cast<char*>(port)[10]=0;
364 // Scheme may come from site def or be derived from IIS.
365 const char* scheme=site.m_scheme.c_str();
366 if (!scheme || !*scheme || !g_bNormalizeRequest)
367 scheme=pfc->fIsSecurePort ? "https" : "http";
369 // Get the rest of the server variables.
370 dynabuf remote_addr(16),method(5),content_type(32),hostname(32);
371 GetServerVariable(pfc,"SERVER_NAME",hostname,32);
372 GetServerVariable(pfc,"REMOTE_ADDR",remote_addr,16);
373 GetServerVariable(pfc,"REQUEST_METHOD",method,5,false);
374 GetServerVariable(pfc,"CONTENT_TYPE",content_type,32,false);
376 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
377 const char* host=hostname;
378 if (site.m_name!=host && site.m_aliases.find(host)==site.m_aliases.end())
379 host=site.m_name.c_str();
381 init(scheme, host, atoi(port), url, content_type, remote_addr, method);
386 ~ShibTargetIsapiF() { }
388 virtual void log(ShibLogLevel level, const string &msg) {
389 ShibTarget::log(level,msg);
390 if (level == LogLevelError)
391 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
393 virtual string getCookies() const {
395 GetHeader(m_pn, m_pfc, "Cookie:", buf, 128, false);
396 return buf.empty() ? "" : buf;
399 virtual void clearHeader(const string &name) {
400 string hdr = name + ":";
401 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), "");
403 virtual void setHeader(const string &name, const string &value) {
404 string hdr = name + ":";
405 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()),
406 const_cast<char*>(value.c_str()));
408 virtual string getHeader(const string &name) {
409 string hdr = name + ":";
411 GetHeader(m_pn, m_pfc, const_cast<char*>(hdr.c_str()), buf, 1024, false);
414 virtual void setRemoteUser(const string &user) {
415 setHeader(string("remote-user"), user);
417 virtual string getRemoteUser(void) {
418 return getHeader(string("remote-user"));
420 virtual void* sendPage(
423 const string& content_type="text/html",
424 const Iterator<header_t>& headers=EMPTY(header_t)) {
425 string hdr = string ("Connection: close\r\nContent-type: ") + content_type + "\r\n";
426 while (headers.hasNext()) {
427 const header_t& h=headers.next();
428 hdr += h.first + ": " + h.second + "\r\n";
431 const char* codestr="200 OK";
433 case 403: codestr="403 Forbidden"; break;
434 case 404: codestr="404 Not Found"; break;
435 case 500: codestr="500 Server Error"; break;
437 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, (void*)codestr, (DWORD)hdr.c_str(), 0);
438 DWORD resplen = msg.size();
439 m_pfc->WriteClient(m_pfc, (LPVOID)msg.c_str(), &resplen, 0);
440 return (void*)SF_STATUS_REQ_FINISHED;
442 virtual void* sendRedirect(const string& url) {
443 // XXX: Don't support the httpRedirect option, yet.
444 string hdrs=m_cookie + string("Location: ") + url + "\r\n"
445 "Content-Type: text/html\r\n"
446 "Content-Length: 40\r\n"
447 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
448 "Cache-Control: private,no-store,no-cache\r\n\r\n";
449 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER,
450 "302 Please Wait", (DWORD)hdrs.c_str(), 0);
451 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
453 m_pfc->WriteClient(m_pfc, (LPVOID)redmsg, &resplen, 0);
454 return reinterpret_cast<void*>(SF_STATUS_REQ_FINISHED);
456 // XXX: We might not ever hit the 'decline' status in this filter.
457 //virtual void* returnDecline(void) { }
458 virtual void* returnOK(void) { return (void*) SF_STATUS_REQ_NEXT_NOTIFICATION; }
460 // The filter never processes the POST, so stub these methods.
461 virtual void setCookie(const string &name, const string &value) {
462 // Set the cookie for later. Use it during the redirect.
463 m_cookie += "Set-Cookie: " + name + "=" + value + "\r\n";
465 virtual string getArgs(void) { throw runtime_error("getArgs not implemented"); }
466 virtual string getPostData(void) { throw runtime_error("getPostData not implemented"); }
469 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const char* msg)
471 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
472 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
473 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)ctype,0);
474 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Filter Error</TITLE></HEAD><BODY>"
475 "<H1>Shibboleth Filter Error</H1>";
476 DWORD resplen=strlen(xmsg);
477 pfc->WriteClient(pfc,(LPVOID)xmsg,&resplen,0);
479 pfc->WriteClient(pfc,(LPVOID)msg,&resplen,0);
480 static const char* xmsg2="</BODY></HTML>";
481 resplen=strlen(xmsg2);
482 pfc->WriteClient(pfc,(LPVOID)xmsg2,&resplen,0);
483 return SF_STATUS_REQ_FINISHED;
486 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
488 // Is this a log notification?
489 if (notificationType==SF_NOTIFY_LOG)
491 if (pfc->pFilterContext)
492 ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=static_cast<LPCSTR>(pfc->pFilterContext);
493 return SF_STATUS_REQ_NEXT_NOTIFICATION;
496 PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
499 // Determine web site number. This can't really fail, I don't think.
501 GetServerVariable(pfc,"INSTANCE_ID",buf,10);
503 // Match site instance to host name, skip if no match.
504 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
505 if (map_i==g_Sites.end())
506 return SF_STATUS_REQ_NEXT_NOTIFICATION;
508 ostringstream threadid;
509 threadid << "[" << getpid() << "] isapi_shib" << '\0';
510 saml::NDC ndc(threadid.str().c_str());
512 ShibTargetIsapiF stf(pfc, pn, map_i->second);
514 // "false" because we don't override the Shib settings
515 pair<bool,void*> res = stf.doCheckAuthN();
516 if (res.first) return (DWORD)res.second;
518 // "false" because we don't override the Shib settings
519 res = stf.doExportAssertions();
520 if (res.first) return (DWORD)res.second;
522 res = stf.doCheckAuthZ();
523 if (res.first) return (DWORD)res.second;
525 return SF_STATUS_REQ_NEXT_NOTIFICATION;
528 return WriteClientError(pfc,"Out of Memory");
531 if (e==ERROR_NO_DATA)
532 return WriteClientError(pfc,"A required variable or header was empty.");
534 return WriteClientError(pfc,"Shibboleth Filter detected unexpected IIS error.");
536 catch (SAMLException& e) {
537 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
538 return WriteClientError(pfc,"Shibboleth Filter caught an exception, check Event Log for details.");
542 return WriteClientError(pfc,"Shibboleth Filter caught an unknown exception.");
546 return WriteClientError(pfc,"Shibboleth Filter reached unreachable code, save my walrus!");
550 /****************************************************************************/
553 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* msg)
555 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
556 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
557 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
558 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Error</TITLE></HEAD><BODY><H1>Shibboleth Error</H1>";
559 DWORD resplen=strlen(xmsg);
560 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg,&resplen,HSE_IO_SYNC);
562 lpECB->WriteClient(lpECB->ConnID,(LPVOID)msg,&resplen,HSE_IO_SYNC);
563 static const char* xmsg2="</BODY></HTML>";
564 resplen=strlen(xmsg2);
565 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg2,&resplen,HSE_IO_SYNC);
566 return HSE_STATUS_SUCCESS;
570 class ShibTargetIsapiE : public ShibTarget
572 LPEXTENSION_CONTROL_BLOCK m_lpECB;
576 ShibTargetIsapiE(LPEXTENSION_CONTROL_BLOCK lpECB, const site_t& site) {
578 GetServerVariable(lpECB,"HTTPS",ssl,5);
579 bool SSL=(ssl=="on" || ssl=="ON");
581 // URL path always come from IIS.
583 GetServerVariable(lpECB,"URL",url,255);
585 // Port may come from IIS or from site def.
587 if (!g_bNormalizeRequest || (SSL && site.m_sslport.empty()) || (!SSL && site.m_port.empty()))
588 GetServerVariable(lpECB,"SERVER_PORT",port,10);
590 strncpy(port,site.m_sslport.c_str(),10);
591 static_cast<char*>(port)[10]=0;
594 strncpy(port,site.m_port.c_str(),10);
595 static_cast<char*>(port)[10]=0;
598 // Scheme may come from site def or be derived from IIS.
599 const char* scheme=site.m_scheme.c_str();
600 if (!scheme || !*scheme || !g_bNormalizeRequest) {
601 scheme = SSL ? "https" : "http";
604 // Get the other server variables.
605 dynabuf remote_addr(16),hostname(32);
606 GetServerVariable(lpECB, "REMOTE_ADDR", remote_addr, 16);
607 GetServerVariable(lpECB, "SERVER_NAME", hostname, 32);
609 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
610 const char* host=hostname;
611 if (site.m_name!=host && site.m_aliases.find(host)==site.m_aliases.end())
612 host=site.m_name.c_str();
615 * IIS screws us over on PATH_INFO (the hits keep on coming). We need to figure out if
616 * the server is set up for proper PATH_INFO handling, or "IIS sucks rabid weasels mode",
617 * which is the default. No perfect way to tell, but we can take a good guess by checking
618 * whether the URL is a substring of the PATH_INFO:
620 * e.g. for /Shibboleth.sso/SAML/POST
622 * Bad mode (default):
623 * URL: /Shibboleth.sso
624 * PathInfo: /Shibboleth.sso/SAML/POST
627 * URL: /Shibboleth.sso
628 * PathInfo: /SAML/POST
633 // Clearly we're only in bad mode if path info exists at all.
634 if (lpECB->lpszPathInfo && *(lpECB->lpszPathInfo)) {
635 if (strstr(lpECB->lpszPathInfo,url))
636 // Pretty good chance we're in bad mode, unless the PathInfo repeats the path itself.
637 fullurl=lpECB->lpszPathInfo;
640 fullurl+=lpECB->lpszPathInfo;
644 // For consistency with Apache, let's add the query string.
645 if (lpECB->lpszQueryString && *(lpECB->lpszQueryString)) {
647 fullurl+=lpECB->lpszQueryString;
649 init(scheme, host, atoi(port), fullurl.c_str(), lpECB->lpszContentType, remote_addr, lpECB->lpszMethod);
653 ~ShibTargetIsapiE() { }
655 virtual void log(ShibLogLevel level, const string &msg) {
656 ShibTarget::log(level,msg);
657 if (level == LogLevelError)
658 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
660 virtual string getCookies() const {
662 GetServerVariable(m_lpECB, "HTTP_COOKIE", buf, 128, false);
663 return buf.empty() ? "" : buf;
665 virtual void setCookie(const string &name, const string &value) {
666 // Set the cookie for later. Use it during the redirect.
667 m_cookie += "Set-Cookie: " + name + "=" + value + "\r\n";
669 virtual string getArgs(void) {
670 return string(m_lpECB->lpszQueryString ? m_lpECB->lpszQueryString : "");
672 virtual string getPostData(void) {
673 if (m_lpECB->cbTotalBytes > 1024*1024) // 1MB?
674 throw FatalProfileException("Blocked too-large a submission to profile endpoint.");
675 else if (m_lpECB->cbTotalBytes != m_lpECB->cbAvailable) {
678 DWORD datalen=m_lpECB->cbTotalBytes;
681 BOOL ret = m_lpECB->ReadClient(m_lpECB->ConnID, buf, &buflen);
683 throw FatalProfileException("Error reading profile submission from browser.");
684 cgistr.append(buf, buflen);
690 return string(reinterpret_cast<char*>(m_lpECB->lpbData),m_lpECB->cbAvailable);
692 virtual void* sendPage(
695 const string& content_type="text/html",
696 const Iterator<header_t>& headers=EMPTY(header_t)) {
697 string hdr = string ("Connection: close\r\nContent-type: ") + content_type + "\r\n";
698 for (unsigned int k = 0; k < headers.size(); k++) {
699 hdr += headers[k].first + ": " + headers[k].second + "\r\n";
702 const char* codestr="200 OK";
704 case 403: codestr="403 Forbidden"; break;
705 case 404: codestr="404 Not Found"; break;
706 case 500: codestr="500 Server Error"; break;
708 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, (void*)codestr, 0, (LPDWORD)hdr.c_str());
709 DWORD resplen = msg.size();
710 m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)msg.c_str(), &resplen, HSE_IO_SYNC);
711 return (void*)HSE_STATUS_SUCCESS;
713 virtual void* sendRedirect(const string& url) {
714 // XXX: Don't support the httpRedirect option, yet.
715 string hdrs = m_cookie + "Location: " + url + "\r\n"
716 "Content-Type: text/html\r\n"
717 "Content-Length: 40\r\n"
718 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
719 "Cache-Control: private,no-store,no-cache\r\n\r\n";
720 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER,
721 "302 Moved", 0, (LPDWORD)hdrs.c_str());
722 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
724 m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)redmsg, &resplen, HSE_IO_SYNC);
725 return (void*)HSE_STATUS_SUCCESS;
727 // Decline happens in the POST processor if this isn't the shire url
728 // Note that it can also happen with HTAccess, but we don't support that, yet.
729 virtual void* returnDecline(void) {
731 WriteClientError(m_lpECB, "ISAPI extension can only be invoked to process Shibboleth protocol requests."
732 "Make sure the mapped file extension doesn't match actual content.");
734 virtual void* returnOK(void) { return (void*) HSE_STATUS_SUCCESS; }
736 // Not used in the extension.
737 virtual void clearHeader(const string &name) { throw runtime_error("clearHeader not implemented"); }
738 virtual void setHeader(const string &name, const string &value) { throw runtime_error("setHeader not implemented"); }
739 virtual string getHeader(const string &name) { throw runtime_error("getHeader not implemented"); }
740 virtual void setRemoteUser(const string &user) { throw runtime_error("setRemoteUser not implemented"); }
741 virtual string getRemoteUser(void) { throw runtime_error("getRemoteUser not implemented"); }
744 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
747 const IApplication* application=NULL;
749 ostringstream threadid;
750 threadid << "[" << getpid() << "] isapi_shib_extension" << '\0';
751 saml::NDC ndc(threadid.str().c_str());
753 // Determine web site number. This can't really fail, I don't think.
755 GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
757 // Match site instance to host name, skip if no match.
758 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
759 if (map_i==g_Sites.end())
760 return WriteClientError(lpECB, "Shibboleth Extension not configured for this web site.");
762 ShibTargetIsapiE ste(lpECB, map_i->second);
763 pair<bool,void*> res = ste.doHandler();
764 if (res.first) return (DWORD)res.second;
766 return WriteClientError(lpECB, "Shibboleth Extension failed to process request");
770 return WriteClientError(lpECB,"Out of Memory");
773 if (e==ERROR_NO_DATA)
774 return WriteClientError(lpECB,"A required variable or header was empty.");
776 return WriteClientError(lpECB,"Server detected unexpected IIS error.");
778 catch (SAMLException& e) {
779 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
780 return WriteClientError(lpECB,"Shibboleth Extension caught an exception, check Event Log for details.");
784 return WriteClientError(lpECB,"Shibboleth Extension caught an unknown exception.");
788 // If we get here we've got an error.
789 return HSE_STATUS_ERROR;