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 catchAll[] =
48 { chLatin_c, chLatin_a, chLatin_t, chLatin_c, chLatin_h, chLatin_A, chLatin_l, chLatin_l, chNull };
49 static const XMLCh name[] = { chLatin_n, chLatin_a, chLatin_m, chLatin_e, chNull };
50 static const XMLCh port[] = { chLatin_p, chLatin_o, chLatin_r, chLatin_t, chNull };
51 static const XMLCh sslport[] = { chLatin_s, chLatin_s, chLatin_l, chLatin_p, chLatin_o, chLatin_r, chLatin_t, chNull };
52 static const XMLCh scheme[] = { chLatin_s, chLatin_c, chLatin_h, chLatin_e, chLatin_m, chLatin_e, chNull };
53 static const XMLCh id[] = { chLatin_i, chLatin_d, chNull };
54 static const XMLCh Implementation[] =
55 { 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 };
56 static const XMLCh ISAPI[] = { chLatin_I, chLatin_S, chLatin_A, chLatin_P, chLatin_I, chNull };
57 static const XMLCh Alias[] = { chLatin_A, chLatin_l, chLatin_i, chLatin_a, chLatin_s, chNull };
58 static const XMLCh normalizeRequest[] =
59 { chLatin_n, chLatin_o, chLatin_r, chLatin_m, chLatin_a, chLatin_l, chLatin_i, chLatin_z, chLatin_e,
60 chLatin_R, chLatin_e, chLatin_q, chLatin_u, chLatin_e, chLatin_s, chLatin_t, chNull
62 static const XMLCh Site[] = { chLatin_S, chLatin_i, chLatin_t, chLatin_e, chNull };
65 site_t(const DOMElement* e)
67 auto_ptr_char n(e->getAttributeNS(NULL,name));
68 auto_ptr_char s(e->getAttributeNS(NULL,scheme));
69 auto_ptr_char p(e->getAttributeNS(NULL,port));
70 auto_ptr_char p2(e->getAttributeNS(NULL,sslport));
71 if (n.get()) m_name=n.get();
72 if (s.get()) m_scheme=s.get();
73 if (p.get()) m_port=p.get();
74 if (p2.get()) m_sslport=p2.get();
75 DOMNodeList* nlist=e->getElementsByTagNameNS(shibtarget::XML::SHIBTARGET_NS,Alias);
76 for (int i=0; nlist && i<nlist->getLength(); i++) {
77 if (nlist->item(i)->hasChildNodes()) {
78 auto_ptr_char alias(nlist->item(i)->getFirstChild()->getNodeValue());
79 m_aliases.insert(alias.get());
83 string m_scheme,m_port,m_sslport,m_name;
84 set<string> m_aliases;
88 ShibTargetConfig* g_Config = NULL;
89 map<string,site_t> g_Sites;
90 bool g_bNormalizeRequest = true;
91 string g_unsetHeaderValue;
92 bool g_checkSpoofing = true;
93 bool g_catchAll = true;
97 LPCSTR lpUNCServerName,
103 LPCSTR messages[] = {message, NULL};
105 HANDLE hElog = RegisterEventSource(lpUNCServerName, "Shibboleth ISAPI Filter");
106 BOOL res = ReportEvent(hElog, wType, 0, dwEventID, lpUserSid, 1, 0, messages, NULL);
107 return (DeregisterEventSource(hElog) && res);
110 extern "C" __declspec(dllexport) BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID)
112 if (fdwReason==DLL_PROCESS_ATTACH)
117 extern "C" BOOL WINAPI GetExtensionVersion(HSE_VERSION_INFO* pVer)
124 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
125 "Extension mode startup not possible, is the DLL loaded as a filter?");
129 pVer->dwExtensionVersion=HSE_VERSION;
130 strncpy(pVer->lpszExtensionDesc,"Shibboleth ISAPI Extension",HSE_MAX_EXT_DLL_NAME_LEN-1);
134 extern "C" BOOL WINAPI TerminateExtension(DWORD)
136 return TRUE; // cleanup should happen when filter unloads
139 extern "C" BOOL WINAPI GetFilterVersion(PHTTP_FILTER_VERSION pVer)
144 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
145 "Reentrant filter initialization, ignoring...");
150 LPCSTR schemadir=getenv("SHIBSCHEMAS");
152 schemadir=SHIB_SCHEMAS;
153 LPCSTR config=getenv("SHIBCONFIG");
156 g_Config=&ShibTargetConfig::getConfig();
157 g_Config->setFeatures(
158 ShibTargetConfig::Listener |
159 ShibTargetConfig::Metadata |
160 ShibTargetConfig::AAP |
161 ShibTargetConfig::RequestMapper |
162 ShibTargetConfig::LocalExtensions |
163 ShibTargetConfig::Logging
165 if (!g_Config->init(schemadir)) {
167 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
168 "Filter startup failed during library initialization, check native log for help.");
171 else if (!g_Config->load(config)) {
173 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
174 "Filter startup failed to load configuration, check native log for help.");
178 // Access the implementation-specifics for site mappings.
179 IConfig* conf=g_Config->getINI();
181 const IPropertySet* props=conf->getPropertySet("Local");
183 pair<bool,const char*> unsetValue=props->getString("unsetHeaderValue");
184 if (unsetValue.first)
185 g_unsetHeaderValue = unsetValue.second;
186 pair<bool,bool> flag=props->getBool("checkSpoofing");
187 g_checkSpoofing = !flag.first || flag.second;
188 flag=props->getBool("checkAll");
189 g_catchAll = !flag.first || flag.second;
191 const DOMElement* impl=saml::XML::getFirstChildElement(
192 props->getElement(),shibtarget::XML::SHIBTARGET_NS,Implementation
194 if (impl && (impl=saml::XML::getFirstChildElement(impl,shibtarget::XML::SHIBTARGET_NS,ISAPI))) {
195 const XMLCh* ch=impl->getAttributeNS(NULL,normalizeRequest);
196 g_bNormalizeRequest=(!ch || !*ch || *ch==chDigit_1 || *ch==chLatin_t);
197 impl=saml::XML::getFirstChildElement(impl,shibtarget::XML::SHIBTARGET_NS,Site);
199 auto_ptr_char id(impl->getAttributeNS(NULL,id));
201 g_Sites.insert(pair<string,site_t>(id.get(),site_t(impl)));
202 impl=saml::XML::getNextSiblingElement(impl,shibtarget::XML::SHIBTARGET_NS,Site);
208 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Filter startup failed with an exception.");
212 pVer->dwFilterVersion=HTTP_FILTER_REVISION;
213 strncpy(pVer->lpszFilterDesc,"Shibboleth ISAPI Filter",SF_MAX_FILTER_DESC_LEN);
214 pVer->dwFlags=(SF_NOTIFY_ORDER_HIGH |
215 SF_NOTIFY_SECURE_PORT |
216 SF_NOTIFY_NONSECURE_PORT |
217 SF_NOTIFY_PREPROC_HEADERS |
219 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter initialized...");
223 extern "C" BOOL WINAPI TerminateFilter(DWORD)
226 g_Config->shutdown();
228 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter shut down...");
232 /* Next up, some suck-free versions of various APIs.
234 You DON'T require people to guess the buffer size and THEN tell them the right size.
235 Returning an LPCSTR is apparently way beyond their ken. Not to mention the fact that
236 constant strings aren't typed as such, making it just that much harder. These versions
237 are now updated to use a special growable buffer object, modeled after the standard
238 string class. The standard string won't work because they left out the option to
239 pre-allocate a non-constant buffer.
245 dynabuf() { bufptr=NULL; buflen=0; }
246 dynabuf(size_t s) { bufptr=new char[buflen=s]; *bufptr=0; }
247 ~dynabuf() { delete[] bufptr; }
248 size_t length() const { return bufptr ? strlen(bufptr) : 0; }
249 size_t size() const { return buflen; }
250 bool empty() const { return length()==0; }
251 void reserve(size_t s, bool keep=false);
252 void erase() { if (bufptr) memset(bufptr,0,buflen); }
253 operator char*() { return bufptr; }
254 bool operator ==(const char* s) const;
255 bool operator !=(const char* s) const { return !(*this==s); }
261 void dynabuf::reserve(size_t s, bool keep)
268 p[buflen]=bufptr[buflen];
274 bool dynabuf::operator==(const char* s) const
276 if (buflen==NULL || s==NULL)
277 return (buflen==NULL && s==NULL);
279 return strcmp(bufptr,s)==0;
282 void GetServerVariable(PHTTP_FILTER_CONTEXT pfc, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
283 throw (bad_alloc, DWORD)
289 while (!pfc->GetServerVariable(pfc,lpszVariable,s,&size))
291 // Grumble. Check the error.
292 DWORD e=GetLastError();
293 if (e==ERROR_INSUFFICIENT_BUFFER)
298 if (bRequired && s.empty())
302 void GetServerVariable(LPEXTENSION_CONTROL_BLOCK lpECB, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
303 throw (bad_alloc, DWORD)
309 while (!lpECB->GetServerVariable(lpECB->ConnID,lpszVariable,s,&size))
311 // Grumble. Check the error.
312 DWORD e=GetLastError();
313 if (e==ERROR_INSUFFICIENT_BUFFER)
318 if (bRequired && s.empty())
322 void GetHeader(PHTTP_FILTER_PREPROC_HEADERS pn, PHTTP_FILTER_CONTEXT pfc,
323 LPSTR lpszName, dynabuf& s, DWORD size=80, bool bRequired=true)
324 throw (bad_alloc, DWORD)
330 while (!pn->GetHeader(pfc,lpszName,s,&size))
332 // Grumble. Check the error.
333 DWORD e=GetLastError();
334 if (e==ERROR_INSUFFICIENT_BUFFER)
339 if (bRequired && s.empty())
343 /****************************************************************************/
346 class ShibTargetIsapiF : public ShibTarget
348 PHTTP_FILTER_CONTEXT m_pfc;
349 PHTTP_FILTER_PREPROC_HEADERS m_pn;
354 ShibTargetIsapiF(PHTTP_FILTER_CONTEXT pfc, PHTTP_FILTER_PREPROC_HEADERS pn, const site_t& site)
355 : m_pfc(pfc), m_pn(pn), m_allhttp(4096) {
357 // URL path always come from IIS.
359 GetHeader(pn,pfc,"url",url,256,false);
361 // Port may come from IIS or from site def.
363 if (!g_bNormalizeRequest || (pfc->fIsSecurePort && site.m_sslport.empty()) || (!pfc->fIsSecurePort && site.m_port.empty()))
364 GetServerVariable(pfc,"SERVER_PORT",port,10);
365 else if (pfc->fIsSecurePort) {
366 strncpy(port,site.m_sslport.c_str(),10);
367 static_cast<char*>(port)[10]=0;
370 strncpy(port,site.m_port.c_str(),10);
371 static_cast<char*>(port)[10]=0;
374 // Scheme may come from site def or be derived from IIS.
375 const char* scheme=site.m_scheme.c_str();
376 if (!scheme || !*scheme || !g_bNormalizeRequest)
377 scheme=pfc->fIsSecurePort ? "https" : "http";
379 // Get the rest of the server variables.
380 dynabuf remote_addr(16),method(5),content_type(32),hostname(32);
381 GetServerVariable(pfc,"SERVER_NAME",hostname,32);
382 GetServerVariable(pfc,"REMOTE_ADDR",remote_addr,16);
383 GetServerVariable(pfc,"REQUEST_METHOD",method,5,false);
384 GetServerVariable(pfc,"CONTENT_TYPE",content_type,32,false);
386 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
387 const char* host=hostname;
388 if (site.m_name!=host && site.m_aliases.find(host)==site.m_aliases.end())
389 host=site.m_name.c_str();
391 init(scheme, host, atoi(port), url, content_type, remote_addr, method);
393 ~ShibTargetIsapiF() {}
395 virtual void log(ShibLogLevel level, const string &msg) {
396 ShibTarget::log(level,msg);
398 virtual string getCookies() const {
400 GetHeader(m_pn, m_pfc, "Cookie:", buf, 128, false);
401 return buf.empty() ? "" : buf;
404 virtual void clearHeader(const string &name) {
405 if (g_checkSpoofing) {
406 if (m_allhttp.empty())
407 GetServerVariable(m_pfc,"ALL_HTTP",m_allhttp,4096);
409 // Map to the expected CGI variable name.
410 string transformed("HTTP_");
411 const char* pch = name.c_str();
413 transformed += (isalnum(*pch) ? toupper(*pch) : '_');
418 if (strstr(m_allhttp, transformed.c_str()))
419 throw SAMLException("Attempt to spoof header ($1) was detected.", params(1, name.c_str()));
421 string hdr = (name=="REMOTE_USER" ? "remote-user" : name) + ":";
422 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), const_cast<char*>(g_unsetHeaderValue.c_str()));
424 virtual void setHeader(const string &name, const string &value) {
425 string hdr = name + ":";
426 m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()),
427 const_cast<char*>(value.c_str()));
429 virtual string getHeader(const string &name) {
430 string hdr = name + ":";
432 GetHeader(m_pn, m_pfc, const_cast<char*>(hdr.c_str()), buf, 1024, false);
435 virtual void setRemoteUser(const string &user) {
436 setHeader(string("remote-user"), user);
438 virtual string getRemoteUser(void) {
439 return getHeader(string("remote-user"));
441 virtual void* sendPage(
444 const string& content_type="text/html",
445 const Iterator<header_t>& headers=EMPTY(header_t)) {
446 string hdr = string ("Connection: close\r\nContent-type: ") + content_type + "\r\n";
447 while (headers.hasNext()) {
448 const header_t& h=headers.next();
449 hdr += h.first + ": " + h.second + "\r\n";
452 const char* codestr="200 OK";
454 case 403: codestr="403 Forbidden"; break;
455 case 404: codestr="404 Not Found"; break;
456 case 500: codestr="500 Server Error"; break;
458 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, (void*)codestr, (DWORD)hdr.c_str(), 0);
459 DWORD resplen = msg.size();
460 m_pfc->WriteClient(m_pfc, (LPVOID)msg.c_str(), &resplen, 0);
461 return (void*)SF_STATUS_REQ_FINISHED;
463 virtual void* sendRedirect(const string& url) {
464 // XXX: Don't support the httpRedirect option, yet.
465 string hdrs=m_cookie + string("Location: ") + url + "\r\n"
466 "Content-Type: text/html\r\n"
467 "Content-Length: 40\r\n"
468 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
469 "Cache-Control: private,no-store,no-cache\r\n\r\n";
470 m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER,
471 "302 Please Wait", (DWORD)hdrs.c_str(), 0);
472 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
474 m_pfc->WriteClient(m_pfc, (LPVOID)redmsg, &resplen, 0);
475 return reinterpret_cast<void*>(SF_STATUS_REQ_FINISHED);
477 // XXX: We might not ever hit the 'decline' status in this filter.
478 //virtual void* returnDecline(void) { }
479 virtual void* returnOK(void) { return (void*) SF_STATUS_REQ_NEXT_NOTIFICATION; }
481 // The filter never processes the POST, so stub these methods.
482 virtual void setCookie(const string &name, const string &value) {
483 // Set the cookie for later. Use it during the redirect.
484 m_cookie += "Set-Cookie: " + name + "=" + value + "\r\n";
486 virtual string getArgs(void) { throw runtime_error("getArgs not implemented"); }
487 virtual string getPostData(void) { throw runtime_error("getPostData not implemented"); }
490 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const char* msg)
492 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
493 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
494 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)ctype,0);
495 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Filter Error</TITLE></HEAD><BODY>"
496 "<H1>Shibboleth Filter Error</H1>";
497 DWORD resplen=strlen(xmsg);
498 pfc->WriteClient(pfc,(LPVOID)xmsg,&resplen,0);
500 pfc->WriteClient(pfc,(LPVOID)msg,&resplen,0);
501 static const char* xmsg2="</BODY></HTML>";
502 resplen=strlen(xmsg2);
503 pfc->WriteClient(pfc,(LPVOID)xmsg2,&resplen,0);
504 return SF_STATUS_REQ_FINISHED;
507 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
509 // Is this a log notification?
510 if (notificationType==SF_NOTIFY_LOG) {
511 if (pfc->pFilterContext)
512 ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=static_cast<LPCSTR>(pfc->pFilterContext);
513 return SF_STATUS_REQ_NEXT_NOTIFICATION;
516 PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
518 // Determine web site number. This can't really fail, I don't think.
520 GetServerVariable(pfc,"INSTANCE_ID",buf,10);
522 // Match site instance to host name, skip if no match.
523 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
524 if (map_i==g_Sites.end())
525 return SF_STATUS_REQ_NEXT_NOTIFICATION;
527 ostringstream threadid;
528 threadid << "[" << getpid() << "] isapi_shib" << '\0';
529 saml::NDC ndc(threadid.str().c_str());
531 ShibTargetIsapiF stf(pfc, pn, map_i->second);
533 // "false" because we don't override the Shib settings
534 pair<bool,void*> res = stf.doCheckAuthN();
535 if (res.first) return (DWORD)res.second;
537 // "false" because we don't override the Shib settings
538 res = stf.doExportAssertions();
539 if (res.first) return (DWORD)res.second;
541 res = stf.doCheckAuthZ();
542 if (res.first) return (DWORD)res.second;
544 return SF_STATUS_REQ_NEXT_NOTIFICATION;
547 return WriteClientError(pfc,"Out of Memory");
550 if (e==ERROR_NO_DATA)
551 return WriteClientError(pfc,"A required variable or header was empty.");
553 return WriteClientError(pfc,"Shibboleth Filter detected unexpected IIS error.");
555 catch (exception& e) {
556 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
557 return WriteClientError(pfc,"Shibboleth Filter caught an exception, ask administrator to check Event Log for details.");
561 return WriteClientError(pfc,"Shibboleth Filter caught an unknown exception.");
565 return WriteClientError(pfc,"Shibboleth Filter reached unreachable code, save my walrus!");
569 /****************************************************************************/
572 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* msg)
574 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
575 static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
576 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
577 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Error</TITLE></HEAD><BODY><H1>Shibboleth Error</H1>";
578 DWORD resplen=strlen(xmsg);
579 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg,&resplen,HSE_IO_SYNC);
581 lpECB->WriteClient(lpECB->ConnID,(LPVOID)msg,&resplen,HSE_IO_SYNC);
582 static const char* xmsg2="</BODY></HTML>";
583 resplen=strlen(xmsg2);
584 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg2,&resplen,HSE_IO_SYNC);
585 return HSE_STATUS_SUCCESS;
589 class ShibTargetIsapiE : public ShibTarget
591 LPEXTENSION_CONTROL_BLOCK m_lpECB;
595 ShibTargetIsapiE(LPEXTENSION_CONTROL_BLOCK lpECB, const site_t& site) {
597 GetServerVariable(lpECB,"HTTPS",ssl,5);
598 bool SSL=(ssl=="on" || ssl=="ON");
600 // URL path always come from IIS.
602 GetServerVariable(lpECB,"URL",url,255);
604 // Port may come from IIS or from site def.
606 if (!g_bNormalizeRequest || (SSL && site.m_sslport.empty()) || (!SSL && site.m_port.empty()))
607 GetServerVariable(lpECB,"SERVER_PORT",port,10);
609 strncpy(port,site.m_sslport.c_str(),10);
610 static_cast<char*>(port)[10]=0;
613 strncpy(port,site.m_port.c_str(),10);
614 static_cast<char*>(port)[10]=0;
617 // Scheme may come from site def or be derived from IIS.
618 const char* scheme=site.m_scheme.c_str();
619 if (!scheme || !*scheme || !g_bNormalizeRequest) {
620 scheme = SSL ? "https" : "http";
623 // Get the other server variables.
624 dynabuf remote_addr(16),hostname(32);
625 GetServerVariable(lpECB, "REMOTE_ADDR", remote_addr, 16);
626 GetServerVariable(lpECB, "SERVER_NAME", hostname, 32);
628 // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
629 const char* host=hostname;
630 if (site.m_name!=host && site.m_aliases.find(host)==site.m_aliases.end())
631 host=site.m_name.c_str();
634 * IIS screws us over on PATH_INFO (the hits keep on coming). We need to figure out if
635 * the server is set up for proper PATH_INFO handling, or "IIS sucks rabid weasels mode",
636 * which is the default. No perfect way to tell, but we can take a good guess by checking
637 * whether the URL is a substring of the PATH_INFO:
639 * e.g. for /Shibboleth.sso/SAML/POST
641 * Bad mode (default):
642 * URL: /Shibboleth.sso
643 * PathInfo: /Shibboleth.sso/SAML/POST
646 * URL: /Shibboleth.sso
647 * PathInfo: /SAML/POST
652 // Clearly we're only in bad mode if path info exists at all.
653 if (lpECB->lpszPathInfo && *(lpECB->lpszPathInfo)) {
654 if (strstr(lpECB->lpszPathInfo,url))
655 // Pretty good chance we're in bad mode, unless the PathInfo repeats the path itself.
656 fullurl=lpECB->lpszPathInfo;
659 fullurl+=lpECB->lpszPathInfo;
666 // For consistency with Apache, let's add the query string.
667 if (lpECB->lpszQueryString && *(lpECB->lpszQueryString)) {
669 fullurl+=lpECB->lpszQueryString;
671 init(scheme, host, atoi(port), fullurl.c_str(), lpECB->lpszContentType, remote_addr, lpECB->lpszMethod);
675 ~ShibTargetIsapiE() { }
677 virtual void log(ShibLogLevel level, const string &msg) {
678 ShibTarget::log(level,msg);
679 if (level == LogLevelError)
680 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
682 virtual string getCookies() const {
684 GetServerVariable(m_lpECB, "HTTP_COOKIE", buf, 128, false);
685 return buf.empty() ? "" : buf;
687 virtual void setCookie(const string &name, const string &value) {
688 // Set the cookie for later. Use it during the redirect.
689 m_cookie += "Set-Cookie: " + name + "=" + value + "\r\n";
691 virtual string getArgs(void) {
692 return string(m_lpECB->lpszQueryString ? m_lpECB->lpszQueryString : "");
694 virtual string getPostData(void) {
695 if (m_lpECB->cbTotalBytes > 1024*1024) // 1MB?
696 throw FatalProfileException("Blocked too-large a submission to profile endpoint.");
697 else if (m_lpECB->cbTotalBytes != m_lpECB->cbAvailable) {
700 DWORD datalen=m_lpECB->cbTotalBytes;
703 BOOL ret = m_lpECB->ReadClient(m_lpECB->ConnID, buf, &buflen);
705 throw FatalProfileException("Error reading profile submission from browser.");
706 cgistr.append(buf, buflen);
712 return string(reinterpret_cast<char*>(m_lpECB->lpbData),m_lpECB->cbAvailable);
714 virtual void* sendPage(
717 const string& content_type="text/html",
718 const Iterator<header_t>& headers=EMPTY(header_t)) {
719 string hdr = m_cookie + "Connection: close\r\nContent-type: " + content_type + "\r\n";
720 for (int k = 0; k < headers.size(); k++) {
721 hdr += headers[k].first + ": " + headers[k].second + "\r\n";
724 const char* codestr="200 OK";
726 case 403: codestr="403 Forbidden"; break;
727 case 404: codestr="404 Not Found"; break;
728 case 500: codestr="500 Server Error"; break;
730 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, (void*)codestr, 0, (LPDWORD)hdr.c_str());
731 DWORD resplen = msg.size();
732 m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)msg.c_str(), &resplen, HSE_IO_SYNC);
733 return (void*)HSE_STATUS_SUCCESS;
735 virtual void* sendRedirect(const string& url) {
736 // XXX: Don't support the httpRedirect option, yet.
737 string hdrs = m_cookie + "Location: " + url + "\r\n"
738 "Content-Type: text/html\r\n"
739 "Content-Length: 40\r\n"
740 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
741 "Cache-Control: private,no-store,no-cache\r\n\r\n";
742 m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER,
743 "302 Moved", 0, (LPDWORD)hdrs.c_str());
744 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
746 m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)redmsg, &resplen, HSE_IO_SYNC);
747 return (void*)HSE_STATUS_SUCCESS;
749 // Decline happens in the POST processor if this isn't the shire url
750 // Note that it can also happen with HTAccess, but we don't support that, yet.
751 virtual void* returnDecline(void) {
753 WriteClientError(m_lpECB, "ISAPI extension can only be invoked to process Shibboleth protocol requests."
754 "Make sure the mapped file extension doesn't match actual content.");
756 virtual void* returnOK(void) { return (void*) HSE_STATUS_SUCCESS; }
758 // Not used in the extension.
759 virtual void clearHeader(const string &name) { throw runtime_error("clearHeader not implemented"); }
760 virtual void setHeader(const string &name, const string &value) { throw runtime_error("setHeader not implemented"); }
761 virtual string getHeader(const string &name) { throw runtime_error("getHeader not implemented"); }
762 virtual void setRemoteUser(const string &user) { throw runtime_error("setRemoteUser not implemented"); }
763 virtual string getRemoteUser(void) { throw runtime_error("getRemoteUser not implemented"); }
766 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
769 ostringstream threadid;
770 threadid << "[" << getpid() << "] isapi_shib_extension" << '\0';
771 saml::NDC ndc(threadid.str().c_str());
773 // Determine web site number. This can't really fail, I don't think.
775 GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
777 // Match site instance to host name, skip if no match.
778 map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
779 if (map_i==g_Sites.end())
780 return WriteClientError(lpECB, "Shibboleth Extension not configured for this web site.");
782 ShibTargetIsapiE ste(lpECB, map_i->second);
783 pair<bool,void*> res = ste.doHandler();
784 if (res.first) return (DWORD)res.second;
786 return WriteClientError(lpECB, "Shibboleth Extension failed to process request");
790 return WriteClientError(lpECB,"Out of Memory");
793 if (e==ERROR_NO_DATA)
794 return WriteClientError(lpECB,"A required variable or header was empty.");
796 return WriteClientError(lpECB,"Server detected unexpected IIS error.");
798 catch (exception& e) {
799 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
800 return WriteClientError(lpECB,"Shibboleth Extension caught an exception, check Event Log for details.");
804 return WriteClientError(lpECB,"Shibboleth Extension caught an unknown exception.");
808 // If we get here we've got an error.
809 return HSE_STATUS_ERROR;