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;
91 ShibTargetConfig* g_Config = NULL;
92 map<string,site_t> g_Sites;
93 bool g_bNormalizeRequest = true;
94 string g_unsetHeaderValue;
95 bool g_checkSpoofing = true;
96 bool g_catchAll = true;
100 LPCSTR lpUNCServerName,
106 LPCSTR messages[] = {message, NULL};
108 HANDLE hElog = RegisterEventSource(lpUNCServerName, "Shibboleth ISAPI Filter");
109 BOOL res = ReportEvent(hElog, wType, 0, dwEventID, lpUserSid, 1, 0, messages, NULL);
110 return (DeregisterEventSource(hElog) && res);
113 extern "C" __declspec(dllexport) BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID)
115 if (fdwReason==DLL_PROCESS_ATTACH)
120 extern "C" BOOL WINAPI GetExtensionVersion(HSE_VERSION_INFO* pVer)
127 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
128 "Extension mode startup not possible, is the DLL loaded as a filter?");
132 pVer->dwExtensionVersion=HSE_VERSION;
133 strncpy(pVer->lpszExtensionDesc,"Shibboleth ISAPI Extension",HSE_MAX_EXT_DLL_NAME_LEN-1);
137 extern "C" BOOL WINAPI TerminateExtension(DWORD)
139 return TRUE; // cleanup should happen when filter unloads
142 extern "C" BOOL WINAPI GetFilterVersion(PHTTP_FILTER_VERSION pVer)
147 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
148 "Reentrant filter initialization, ignoring...");
153 LPCSTR schemadir=getenv("SHIBSCHEMAS");
155 schemadir=SHIB_SCHEMAS;
156 LPCSTR config=getenv("SHIBCONFIG");
159 g_Config=&ShibTargetConfig::getConfig();
160 g_Config->setFeatures(
161 ShibTargetConfig::Listener |
162 ShibTargetConfig::Metadata |
163 ShibTargetConfig::AAP |
164 ShibTargetConfig::RequestMapper |
165 ShibTargetConfig::LocalExtensions |
166 ShibTargetConfig::Logging
168 if (!g_Config->init(schemadir)) {
170 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
171 "Filter startup failed during library initialization, check native log for help.");
174 else if (!g_Config->load(config)) {
176 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
177 "Filter startup failed to load configuration, check native log for help.");
181 // Access the implementation-specifics for site mappings.
182 IConfig* conf=g_Config->getINI();
184 const IPropertySet* props=conf->getPropertySet("Local");
186 pair<bool,const char*> unsetValue=props->getString("unsetHeaderValue");
187 if (unsetValue.first)
188 g_unsetHeaderValue = unsetValue.second;
189 pair<bool,bool> flag=props->getBool("checkSpoofing");
190 g_checkSpoofing = !flag.first || flag.second;
191 flag=props->getBool("catchAll");
192 g_catchAll = !flag.first || flag.second;
194 const DOMElement* impl=saml::XML::getFirstChildElement(
195 props->getElement(),shibtarget::XML::SHIBTARGET_NS,Implementation
197 if (impl && (impl=saml::XML::getFirstChildElement(impl,shibtarget::XML::SHIBTARGET_NS,ISAPI))) {
198 const XMLCh* ch=impl->getAttributeNS(NULL,normalizeRequest);
199 g_bNormalizeRequest=(!ch || !*ch || *ch==chDigit_1 || *ch==chLatin_t);
200 impl=saml::XML::getFirstChildElement(impl,shibtarget::XML::SHIBTARGET_NS,Site);
202 auto_ptr_char id(impl->getAttributeNS(NULL,id));
204 g_Sites.insert(pair<string,site_t>(id.get(),site_t(impl)));
205 impl=saml::XML::getNextSiblingElement(impl,shibtarget::XML::SHIBTARGET_NS,Site);
211 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Filter startup failed with an exception.");
215 pVer->dwFilterVersion=HTTP_FILTER_REVISION;
216 strncpy(pVer->lpszFilterDesc,"Shibboleth ISAPI Filter",SF_MAX_FILTER_DESC_LEN);
217 pVer->dwFlags=(SF_NOTIFY_ORDER_HIGH |
218 SF_NOTIFY_SECURE_PORT |
219 SF_NOTIFY_NONSECURE_PORT |
220 SF_NOTIFY_PREPROC_HEADERS |
222 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter initialized...");
226 extern "C" BOOL WINAPI TerminateFilter(DWORD)
229 g_Config->shutdown();
231 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter shut down...");
235 /* Next up, some suck-free versions of various APIs.
237 You DON'T require people to guess the buffer size and THEN tell them the right size.
238 Returning an LPCSTR is apparently way beyond their ken. Not to mention the fact that
239 constant strings aren't typed as such, making it just that much harder. These versions
240 are now updated to use a special growable buffer object, modeled after the standard
241 string class. The standard string won't work because they left out the option to
242 pre-allocate a non-constant buffer.
248 dynabuf() { bufptr=NULL; buflen=0; }
249 dynabuf(size_t s) { bufptr=new char[buflen=s]; *bufptr=0; }
250 ~dynabuf() { delete[] bufptr; }
251 size_t length() const { return bufptr ? strlen(bufptr) : 0; }
252 size_t size() const { return buflen; }
253 bool empty() const { return length()==0; }
254 void reserve(size_t s, bool keep=false);
255 void erase() { if (bufptr) memset(bufptr,0,buflen); }
256 operator char*() { return bufptr; }
257 bool operator ==(const char* s) const;
258 bool operator !=(const char* s) const { return !(*this==s); }
264 void dynabuf::reserve(size_t s, bool keep)
271 p[buflen]=bufptr[buflen];
277 bool dynabuf::operator==(const char* s) const
279 if (buflen==NULL || s==NULL)
280 return (buflen==NULL && s==NULL);
282 return strcmp(bufptr,s)==0;
285 void GetServerVariable(PHTTP_FILTER_CONTEXT pfc, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
286 throw (bad_alloc, DWORD)
292 while (!pfc->GetServerVariable(pfc,lpszVariable,s,&size))
294 // Grumble. Check the error.
295 DWORD e=GetLastError();
296 if (e==ERROR_INSUFFICIENT_BUFFER)
301 if (bRequired && s.empty())
305 void GetServerVariable(LPEXTENSION_CONTROL_BLOCK lpECB, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
306 throw (bad_alloc, DWORD)
312 while (!lpECB->GetServerVariable(lpECB->ConnID,lpszVariable,s,&size))
314 // Grumble. Check the error.
315 DWORD e=GetLastError();
316 if (e==ERROR_INSUFFICIENT_BUFFER)
321 if (bRequired && s.empty())
325 void GetHeader(PHTTP_FILTER_PREPROC_HEADERS pn, PHTTP_FILTER_CONTEXT pfc,
326 LPSTR lpszName, dynabuf& s, DWORD size=80, bool bRequired=true)
327 throw (bad_alloc, DWORD)
333 while (!pn->GetHeader(pfc,lpszName,s,&size))
335 // Grumble. Check the error.
336 DWORD e=GetLastError();
337 if (e==ERROR_INSUFFICIENT_BUFFER)
342 if (bRequired && s.empty())
346 /****************************************************************************/
349 class ShibTargetIsapiF : public ShibTarget
351 PHTTP_FILTER_CONTEXT m_pfc;
352 PHTTP_FILTER_PREPROC_HEADERS m_pn;
357 ShibTargetIsapiF(PHTTP_FILTER_CONTEXT pfc, PHTTP_FILTER_PREPROC_HEADERS pn, const site_t& site)
358 : m_pfc(pfc), m_pn(pn), m_allhttp(4096) {
360 // URL path always come from IIS.
362 GetHeader(pn,pfc,"url",url,256,false);
364 // Port may come from IIS or from site def.
366 if (!g_bNormalizeRequest || (pfc->fIsSecurePort && site.m_sslport.empty()) || (!pfc->fIsSecurePort && site.m_port.empty()))
367 GetServerVariable(pfc,"SERVER_PORT",port,10);
368 else if (pfc->fIsSecurePort) {
369 strncpy(port,site.m_sslport.c_str(),10);
370 static_cast<char*>(port)[10]=0;
373 strncpy(port,site.m_port.c_str(),10);
374 static_cast<char*>(port)[10]=0;
377 // Scheme may come from site def or be derived from IIS.
378 const char* scheme=site.m_scheme.c_str();
379 if (!scheme || !*scheme || !g_bNormalizeRequest)
380 scheme=pfc->fIsSecurePort ? "https" : "http";
382 // Get the rest of the server variables.
383 dynabuf remote_addr(16),method(5),content_type(32),hostname(32);
384 GetServerVariable(pfc,"SERVER_NAME",hostname,32);
385 GetServerVariable(pfc,"REMOTE_ADDR",remote_addr,16);
386 GetServerVariable(pfc,"REQUEST_METHOD",method,5,false);
387 GetServerVariable(pfc,"CONTENT_TYPE",content_type,32,false);
389 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
390 const char* host=hostname;
391 if (site.m_name!=host && site.m_aliases.find(host)==site.m_aliases.end())
392 host=site.m_name.c_str();
394 init(scheme, host, atoi(port), url, content_type, remote_addr, method);
395 if (!pfc->pFilterContext) {
396 pfc->pFilterContext = pfc->AllocMem(pfc, sizeof(context_t), NULL);
397 if (static_cast<context_t*>(pfc->pFilterContext)) {
398 static_cast<context_t*>(pfc->pFilterContext)->m_user = NULL;
399 static_cast<context_t*>(pfc->pFilterContext)->m_checked = false;
403 ~ShibTargetIsapiF() {}
405 virtual void log(ShibLogLevel level, const string &msg) {
406 ShibTarget::log(level,msg);
408 virtual string getCookies() const {
410 GetHeader(m_pn, m_pfc, "Cookie:", buf, 128, false);
411 return buf.empty() ? "" : buf;
414 virtual void clearHeader(const string &name) {
415 if (g_checkSpoofing && m_pfc->pFilterContext && !static_cast<context_t*>(m_pfc->pFilterContext)->m_checked) {
416 if (m_allhttp.empty())
417 GetServerVariable(m_pfc,"ALL_HTTP",m_allhttp,4096);
419 // Map to the expected CGI variable name.
420 string transformed("HTTP_");
421 const char* pch = name.c_str();
423 transformed += (isalnum(*pch) ? toupper(*pch) : '_');
428 if (strstr(m_allhttp, transformed.c_str()))
429 throw SAMLException("Attempt to spoof header ($1) was detected.", params(1, name.c_str()));
431 string hdr = (name=="REMOTE_USER" ? "remote-user" : name) + ":";
432 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), const_cast<char*>(g_unsetHeaderValue.c_str()));
434 virtual void setHeader(const string &name, const string &value) {
435 string hdr = name + ":";
436 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()),
437 const_cast<char*>(value.c_str()));
439 virtual string getHeader(const string &name) {
440 string hdr = name + ":";
442 GetHeader(m_pn, m_pfc, const_cast<char*>(hdr.c_str()), buf, 1024, false);
445 virtual void setRemoteUser(const string &user) {
446 setHeader(string("remote-user"), user);
447 if (m_pfc->pFilterContext) {
449 static_cast<context_t*>(m_pfc->pFilterContext)->m_user = NULL;
450 else if (static_cast<context_t*>(m_pfc->pFilterContext)->m_user = (char*)m_pfc->AllocMem(m_pfc, sizeof(char) * (user.length() + 1), NULL))
451 strcpy(static_cast<context_t*>(m_pfc->pFilterContext)->m_user, user.c_str());
454 virtual string getRemoteUser(void) {
455 return getHeader(string("remote-user"));
457 virtual void* sendPage(
460 const string& content_type="text/html",
461 const Iterator<header_t>& headers=EMPTY(header_t)) {
462 string hdr = string ("Connection: close\r\nContent-type: ") + content_type + "\r\n";
463 while (headers.hasNext()) {
464 const header_t& h=headers.next();
465 hdr += h.first + ": " + h.second + "\r\n";
468 const char* codestr="200 OK";
470 case 403: codestr="403 Forbidden"; break;
471 case 404: codestr="404 Not Found"; break;
472 case 500: codestr="500 Server Error"; break;
474 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, (void*)codestr, (DWORD)hdr.c_str(), 0);
475 DWORD resplen = msg.size();
476 m_pfc->WriteClient(m_pfc, (LPVOID)msg.c_str(), &resplen, 0);
477 return (void*)SF_STATUS_REQ_FINISHED;
479 virtual void* sendRedirect(const string& url) {
480 // XXX: Don't support the httpRedirect option, yet.
481 string hdrs=m_cookie + string("Location: ") + url + "\r\n"
482 "Content-Type: text/html\r\n"
483 "Content-Length: 40\r\n"
484 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
485 "Cache-Control: private,no-store,no-cache\r\n\r\n";
486 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER,
487 "302 Please Wait", (DWORD)hdrs.c_str(), 0);
488 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
490 m_pfc->WriteClient(m_pfc, (LPVOID)redmsg, &resplen, 0);
491 return reinterpret_cast<void*>(SF_STATUS_REQ_FINISHED);
493 // XXX: We might not ever hit the 'decline' status in this filter.
494 //virtual void* returnDecline(void) { }
495 virtual void* returnOK(void) { return (void*) SF_STATUS_REQ_NEXT_NOTIFICATION; }
497 // The filter never processes the POST, so stub these methods.
498 virtual void setCookie(const string &name, const string &value) {
499 // Set the cookie for later. Use it during the redirect.
500 m_cookie += "Set-Cookie: " + name + "=" + value + "\r\n";
502 virtual string getArgs(void) { throw runtime_error("getArgs not implemented"); }
503 virtual string getPostData(void) { throw runtime_error("getPostData not implemented"); }
506 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const char* msg)
508 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
509 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
510 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)ctype,0);
511 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Filter Error</TITLE></HEAD><BODY>"
512 "<H1>Shibboleth Filter Error</H1>";
513 DWORD resplen=strlen(xmsg);
514 pfc->WriteClient(pfc,(LPVOID)xmsg,&resplen,0);
516 pfc->WriteClient(pfc,(LPVOID)msg,&resplen,0);
517 static const char* xmsg2="</BODY></HTML>";
518 resplen=strlen(xmsg2);
519 pfc->WriteClient(pfc,(LPVOID)xmsg2,&resplen,0);
520 return SF_STATUS_REQ_FINISHED;
523 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
525 // Is this a log notification?
526 if (notificationType==SF_NOTIFY_LOG) {
527 if (pfc->pFilterContext && static_cast<context_t*>(pfc->pFilterContext)->m_user)
528 ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=static_cast<context_t*>(pfc->pFilterContext)->m_user;
529 return SF_STATUS_REQ_NEXT_NOTIFICATION;
532 PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
534 // Determine web site number. This can't really fail, I don't think.
536 GetServerVariable(pfc,"INSTANCE_ID",buf,10);
538 // Match site instance to host name, skip if no match.
539 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
540 if (map_i==g_Sites.end())
541 return SF_STATUS_REQ_NEXT_NOTIFICATION;
543 ostringstream threadid;
544 threadid << "[" << getpid() << "] isapi_shib" << '\0';
545 saml::NDC ndc(threadid.str().c_str());
547 ShibTargetIsapiF stf(pfc, pn, map_i->second);
549 // "false" because we don't override the Shib settings
550 pair<bool,void*> res = stf.doCheckAuthN();
551 if (pfc->pFilterContext)
552 static_cast<context_t*>(pfc->pFilterContext)->m_checked = true;
553 if (res.first) return (DWORD)res.second;
555 // "false" because we don't override the Shib settings
556 res = stf.doExportAssertions();
557 if (res.first) return (DWORD)res.second;
559 res = stf.doCheckAuthZ();
560 if (res.first) return (DWORD)res.second;
562 return SF_STATUS_REQ_NEXT_NOTIFICATION;
565 return WriteClientError(pfc,"Out of Memory");
568 if (e==ERROR_NO_DATA)
569 return WriteClientError(pfc,"A required variable or header was empty.");
571 return WriteClientError(pfc,"Shibboleth Filter detected unexpected IIS error.");
573 catch (exception& e) {
574 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
575 return WriteClientError(pfc,"Shibboleth Filter caught an exception, ask administrator to check Event Log for details.");
579 return WriteClientError(pfc,"Shibboleth Filter caught an unknown exception.");
583 return WriteClientError(pfc,"Shibboleth Filter reached unreachable code, save my walrus!");
587 /****************************************************************************/
590 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* msg)
592 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
593 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
594 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
595 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Error</TITLE></HEAD><BODY><H1>Shibboleth Error</H1>";
596 DWORD resplen=strlen(xmsg);
597 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg,&resplen,HSE_IO_SYNC);
599 lpECB->WriteClient(lpECB->ConnID,(LPVOID)msg,&resplen,HSE_IO_SYNC);
600 static const char* xmsg2="</BODY></HTML>";
601 resplen=strlen(xmsg2);
602 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg2,&resplen,HSE_IO_SYNC);
603 return HSE_STATUS_SUCCESS;
607 class ShibTargetIsapiE : public ShibTarget
609 LPEXTENSION_CONTROL_BLOCK m_lpECB;
613 ShibTargetIsapiE(LPEXTENSION_CONTROL_BLOCK lpECB, const site_t& site) {
615 GetServerVariable(lpECB,"HTTPS",ssl,5);
616 bool SSL=(ssl=="on" || ssl=="ON");
618 // URL path always come from IIS.
620 GetServerVariable(lpECB,"URL",url,255);
622 // Port may come from IIS or from site def.
624 if (!g_bNormalizeRequest || (SSL && site.m_sslport.empty()) || (!SSL && site.m_port.empty()))
625 GetServerVariable(lpECB,"SERVER_PORT",port,10);
627 strncpy(port,site.m_sslport.c_str(),10);
628 static_cast<char*>(port)[10]=0;
631 strncpy(port,site.m_port.c_str(),10);
632 static_cast<char*>(port)[10]=0;
635 // Scheme may come from site def or be derived from IIS.
636 const char* scheme=site.m_scheme.c_str();
637 if (!scheme || !*scheme || !g_bNormalizeRequest) {
638 scheme = SSL ? "https" : "http";
641 // Get the other server variables.
642 dynabuf remote_addr(16),hostname(32);
643 GetServerVariable(lpECB, "REMOTE_ADDR", remote_addr, 16);
644 GetServerVariable(lpECB, "SERVER_NAME", hostname, 32);
646 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
647 const char* host=hostname;
648 if (site.m_name!=host && site.m_aliases.find(host)==site.m_aliases.end())
649 host=site.m_name.c_str();
652 * IIS screws us over on PATH_INFO (the hits keep on coming). We need to figure out if
653 * the server is set up for proper PATH_INFO handling, or "IIS sucks rabid weasels mode",
654 * which is the default. No perfect way to tell, but we can take a good guess by checking
655 * whether the URL is a substring of the PATH_INFO:
657 * e.g. for /Shibboleth.sso/SAML/POST
659 * Bad mode (default):
660 * URL: /Shibboleth.sso
661 * PathInfo: /Shibboleth.sso/SAML/POST
664 * URL: /Shibboleth.sso
665 * PathInfo: /SAML/POST
670 // Clearly we're only in bad mode if path info exists at all.
671 if (lpECB->lpszPathInfo && *(lpECB->lpszPathInfo)) {
672 if (strstr(lpECB->lpszPathInfo,url))
673 // Pretty good chance we're in bad mode, unless the PathInfo repeats the path itself.
674 fullurl=lpECB->lpszPathInfo;
677 fullurl+=lpECB->lpszPathInfo;
684 // For consistency with Apache, let's add the query string.
685 if (lpECB->lpszQueryString && *(lpECB->lpszQueryString)) {
687 fullurl+=lpECB->lpszQueryString;
689 init(scheme, host, atoi(port), fullurl.c_str(), lpECB->lpszContentType, remote_addr, lpECB->lpszMethod);
693 ~ShibTargetIsapiE() { }
695 virtual void log(ShibLogLevel level, const string &msg) {
696 ShibTarget::log(level,msg);
697 if (level == LogLevelError)
698 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
700 virtual string getCookies() const {
702 GetServerVariable(m_lpECB, "HTTP_COOKIE", buf, 128, false);
703 return buf.empty() ? "" : buf;
705 virtual void setCookie(const string &name, const string &value) {
706 // Set the cookie for later. Use it during the redirect.
707 m_cookie += "Set-Cookie: " + name + "=" + value + "\r\n";
709 virtual string getArgs(void) {
710 return string(m_lpECB->lpszQueryString ? m_lpECB->lpszQueryString : "");
712 virtual string getPostData(void) {
713 if (m_lpECB->cbTotalBytes > 1024*1024) // 1MB?
714 throw FatalProfileException("Blocked too-large a submission to profile endpoint.");
715 else if (m_lpECB->cbTotalBytes != m_lpECB->cbAvailable) {
718 DWORD datalen=m_lpECB->cbTotalBytes;
721 BOOL ret = m_lpECB->ReadClient(m_lpECB->ConnID, buf, &buflen);
723 throw FatalProfileException("Error reading profile submission from browser.");
724 cgistr.append(buf, buflen);
730 return string(reinterpret_cast<char*>(m_lpECB->lpbData),m_lpECB->cbAvailable);
732 virtual void* sendPage(
735 const string& content_type="text/html",
736 const Iterator<header_t>& headers=EMPTY(header_t)) {
737 string hdr = m_cookie + "Connection: close\r\nContent-type: " + content_type + "\r\n";
738 for (int k = 0; k < headers.size(); k++) {
739 hdr += headers[k].first + ": " + headers[k].second + "\r\n";
742 const char* codestr="200 OK";
744 case 403: codestr="403 Forbidden"; break;
745 case 404: codestr="404 Not Found"; break;
746 case 500: codestr="500 Server Error"; break;
748 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, (void*)codestr, 0, (LPDWORD)hdr.c_str());
749 DWORD resplen = msg.size();
750 m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)msg.c_str(), &resplen, HSE_IO_SYNC);
751 return (void*)HSE_STATUS_SUCCESS;
753 virtual void* sendRedirect(const string& url) {
754 // XXX: Don't support the httpRedirect option, yet.
755 string hdrs = m_cookie + "Location: " + url + "\r\n"
756 "Content-Type: text/html\r\n"
757 "Content-Length: 40\r\n"
758 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
759 "Cache-Control: private,no-store,no-cache\r\n\r\n";
760 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER,
761 "302 Moved", 0, (LPDWORD)hdrs.c_str());
762 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
764 m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)redmsg, &resplen, HSE_IO_SYNC);
765 return (void*)HSE_STATUS_SUCCESS;
767 // Decline happens in the POST processor if this isn't the shire url
768 // Note that it can also happen with HTAccess, but we don't support that, yet.
769 virtual void* returnDecline(void) {
771 WriteClientError(m_lpECB, "ISAPI extension can only be invoked to process Shibboleth protocol requests."
772 "Make sure the mapped file extension doesn't match actual content.");
774 virtual void* returnOK(void) { return (void*) HSE_STATUS_SUCCESS; }
776 // Not used in the extension.
777 virtual void clearHeader(const string &name) { throw runtime_error("clearHeader not implemented"); }
778 virtual void setHeader(const string &name, const string &value) { throw runtime_error("setHeader not implemented"); }
779 virtual string getHeader(const string &name) { throw runtime_error("getHeader not implemented"); }
780 virtual void setRemoteUser(const string &user) { throw runtime_error("setRemoteUser not implemented"); }
781 virtual string getRemoteUser(void) { throw runtime_error("getRemoteUser not implemented"); }
784 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
787 ostringstream threadid;
788 threadid << "[" << getpid() << "] isapi_shib_extension" << '\0';
789 saml::NDC ndc(threadid.str().c_str());
791 // Determine web site number. This can't really fail, I don't think.
793 GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
795 // Match site instance to host name, skip if no match.
796 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
797 if (map_i==g_Sites.end())
798 return WriteClientError(lpECB, "Shibboleth Extension not configured for this web site.");
800 ShibTargetIsapiE ste(lpECB, map_i->second);
801 pair<bool,void*> res = ste.doHandler();
802 if (res.first) return (DWORD)res.second;
804 return WriteClientError(lpECB, "Shibboleth Extension failed to process request");
808 return WriteClientError(lpECB,"Out of Memory");
811 if (e==ERROR_NO_DATA)
812 return WriteClientError(lpECB,"A required variable or header was empty.");
814 return WriteClientError(lpECB,"Server detected unexpected IIS error.");
816 catch (exception& e) {
817 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
818 return WriteClientError(lpECB,"Shibboleth Extension caught an exception, check Event Log for details.");
822 return WriteClientError(lpECB,"Shibboleth Extension caught an unknown exception.");
826 // If we get here we've got an error.
827 return HSE_STATUS_ERROR;