2 * The Shibboleth License, Version 1.
4 * University Corporation for Advanced Internet Development, Inc.
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions are met:
11 * Redistributions of source code must retain the above copyright notice, this
12 * list of conditions and the following disclaimer.
14 * Redistributions in binary form must reproduce the above copyright notice,
15 * this list of conditions and the following disclaimer in the documentation
16 * and/or other materials provided with the distribution, if any, must include
17 * the following acknowledgment: "This product includes software developed by
18 * the University Corporation for Advanced Internet Development
19 * <http://www.ucaid.edu>Internet2 Project. Alternately, this acknowledegement
20 * may appear in the software itself, if and wherever such third-party
21 * acknowledgments normally appear.
23 * Neither the name of Shibboleth nor the names of its contributors, nor
24 * Internet2, nor the University Corporation for Advanced Internet Development,
25 * Inc., nor UCAID may be used to endorse or promote products derived from this
26 * software without specific prior written permission. For written permission,
27 * please contact shibboleth@shibboleth.org
29 * Products derived from this software may not be called Shibboleth, Internet2,
30 * UCAID, or the University Corporation for Advanced Internet Development, nor
31 * may Shibboleth appear in their name, without prior written permission of the
32 * University Corporation for Advanced Internet Development.
35 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
36 * AND WITH ALL FAULTS. ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
37 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
38 * PARTICULAR PURPOSE, AND NON-INFRINGEMENT ARE DISCLAIMED AND THE ENTIRE RISK
39 * OF SATISFACTORY QUALITY, PERFORMANCE, ACCURACY, AND EFFORT IS WITH LICENSEE.
40 * IN NO EVENT SHALL THE COPYRIGHT OWNER, CONTRIBUTORS OR THE UNIVERSITY
41 * CORPORATION FOR ADVANCED INTERNET DEVELOPMENT, INC. BE LIABLE FOR ANY DIRECT,
42 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
43 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
44 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
45 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
46 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
47 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
50 /* isapi_shib.cpp - Shibboleth ISAPI filter
57 #include <saml/saml.h>
58 #include <shib/shib.h>
59 #include <shib/shib-threads.h>
60 #include <shib-target/shib-target.h>
62 #include <log4cpp/Category.hh>
75 using namespace log4cpp;
77 using namespace shibboleth;
78 using namespace shibtarget;
83 settings_t(string& name) : m_name(name) {}
86 vector<string> m_mustContain;
92 ThreadKey* rpc_handle_key = NULL;
93 ShibTargetConfig* g_Config = NULL;
94 vector<settings_t> g_Sites;
97 void destroy_handle(void* data)
99 delete (RPCHandle*)data;
103 LPCSTR lpUNCServerName,
109 LPCSTR messages[] = {message, NULL};
111 HANDLE hElog = RegisterEventSource(lpUNCServerName, "Shibboleth ISAPI Filter");
112 BOOL res = ReportEvent(hElog, wType, 0, dwEventID, lpUserSid, 1, 0, messages, NULL);
113 return (DeregisterEventSource(hElog) && res);
116 extern "C" __declspec(dllexport) BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID)
118 if (fdwReason==DLL_PROCESS_ATTACH)
123 extern "C" BOOL WINAPI GetExtensionVersion(HSE_VERSION_INFO* pVer)
130 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
131 "Extension mode startup not possible, is the DLL loaded as a filter?");
135 pVer->dwExtensionVersion=HSE_VERSION;
136 strncpy(pVer->lpszExtensionDesc,"Shibboleth ISAPI Extension",HSE_MAX_EXT_DLL_NAME_LEN-1);
140 extern "C" BOOL WINAPI GetFilterVersion(PHTTP_FILTER_VERSION pVer)
147 g_Config = &(ShibTargetConfig::init(SHIBTARGET_SHIRE, getenv("SHIBCONFIG")));
148 ShibINI& ini = g_Config->getINI();
150 // Create the RPC Handle TLS key.
151 rpc_handle_key=ThreadKey::create(destroy_handle);
153 // Read site-specific settings for each instance ID we can find.
156 sprintf(iid,"%u",i++);
158 while (ini.get_tag("isapi",iid,false,&hostname))
160 // If no section exists for the host, mark it as a "skip" site.
161 if (!ini.exists(hostname))
163 g_Sites.push_back(settings_t());
164 sprintf(iid,"%u",i++);
168 settings_t settings(hostname);
170 // Content matching string.
172 if (ini.get_tag(hostname,"mustContain",true,&mustcontain) && !mustcontain.empty())
174 char* buf=strdup(mustcontain.c_str());
177 while (char* sep=strchr(start,';'))
181 settings.m_mustContain.push_back(start);
185 settings.m_mustContain.push_back(start);
189 g_Sites.push_back(settings);
190 sprintf(iid,"%u",i++);
193 catch (SAMLException&)
195 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
196 "Filter startup failed with SAML exception, check shire log for help.");
201 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
202 "Filter startup failed with unexpected exception, check shire log for help.");
206 pVer->dwFilterVersion=HTTP_FILTER_REVISION;
207 strncpy(pVer->lpszFilterDesc,"Shibboleth ISAPI Filter",SF_MAX_FILTER_DESC_LEN);
208 pVer->dwFlags=(SF_NOTIFY_ORDER_HIGH |
209 SF_NOTIFY_SECURE_PORT |
210 SF_NOTIFY_NONSECURE_PORT |
211 SF_NOTIFY_PREPROC_HEADERS |
213 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter initialized...");
217 extern "C" BOOL WINAPI TerminateExtension(DWORD)
219 return TRUE; // cleanup should happen when filter unloads
222 extern "C" BOOL WINAPI TerminateFilter(DWORD)
224 delete rpc_handle_key;
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) *bufptr=0; }
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 inline char hexchar(unsigned short s)
345 return (s<=9) ? ('0' + s) : ('A' + s - 10);
348 string url_encode(const char* url) throw (bad_alloc)
350 static char badchars[]="\"\\+<>#%{}|^~[]`;/?:@=&";
352 for (const char* pch=url; *pch; pch++)
354 if (strchr(badchars,*pch)!=NULL || *pch<=0x1F || *pch>=0x7F)
355 s=s + '%' + hexchar(*pch >> 4) + hexchar(*pch & 0x0F);
362 string get_target(PHTTP_FILTER_CONTEXT pfc, PHTTP_FILTER_PREPROC_HEADERS pn, settings_t& site)
364 // Reconstructing the requested URL is not fun. Apparently, the PREPROC_HEADERS
365 // event means way pre. As in, none of the usual CGI headers are in place yet.
366 // It's actually almost easier, in a way, because all the path-info and query
367 // stuff is in one place, the requested URL, which we can get. But we have to
368 // reconstruct the protocol/host pair using tweezers.
370 if (pfc->fIsSecurePort)
375 // We use the "normalizeRequest" tag to decide how to obtain the server's name.
378 if (g_Config->getINI().get_tag(site.m_name,"normalizeRequest",true,&tag) && ShibINI::boolean(tag))
384 GetServerVariable(pfc,"SERVER_NAME",buf);
388 GetServerVariable(pfc,"SERVER_PORT",buf,10);
389 if (buf!=(pfc->fIsSecurePort ? "443" : "80"))
390 s=s + ':' + static_cast<char*>(buf);
392 GetHeader(pn,pfc,"url",buf,256,false);
398 string get_shire_location(settings_t& site, const char* target)
401 if (g_Config->getINI().get_tag(site.m_name,"shireURL",true,&shireURL) && !shireURL.empty())
403 if (shireURL[0]!='/')
405 const char* colon=strchr(target,':');
406 const char* slash=strchr(colon+3,'/');
407 string s(target,slash-target);
414 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const char* msg)
416 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
417 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",0,0);
418 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Filter Error</TITLE></HEAD><BODY>"
419 "<H1>Shibboleth Filter Error</H1>";
420 DWORD resplen=strlen(xmsg);
421 pfc->WriteClient(pfc,(LPVOID)xmsg,&resplen,0);
423 pfc->WriteClient(pfc,(LPVOID)msg,&resplen,0);
424 static const char* xmsg2="</BODY></HTML>";
425 resplen=strlen(xmsg2);
426 pfc->WriteClient(pfc,(LPVOID)xmsg2,&resplen,0);
427 return SF_STATUS_REQ_FINISHED;
430 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const char* filename, ShibMLP& mlp)
432 ifstream infile(filename);
434 return WriteClientError(pfc,"Unable to open error template, check settings.");
436 string res = mlp.run(infile);
437 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",0,0);
438 DWORD resplen=res.length();
439 pfc->WriteClient(pfc,(LPVOID)res.c_str(),&resplen,0);
440 return SF_STATUS_REQ_FINISHED;
443 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
445 // Is this a log notification?
446 if (notificationType==SF_NOTIFY_LOG)
448 if (pfc->pFilterContext)
449 ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=static_cast<LPCSTR>(pfc->pFilterContext);
450 return SF_STATUS_REQ_NEXT_NOTIFICATION;
453 PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
456 // Determine web site number. This can't really fail, I don't think.
459 GetServerVariable(pfc,"INSTANCE_ID",buf,10);
460 if ((site_id=strtoul(buf,NULL,10))==0)
461 return WriteClientError(pfc,"IIS site instance appears to be invalid.");
463 // Match site instance to site settings.
464 if (site_id>g_Sites.size() || g_Sites[site_id-1].m_name.length()==0)
465 return SF_STATUS_REQ_NEXT_NOTIFICATION;
466 settings_t& site=g_Sites[site_id-1];
468 string target_url=get_target(pfc,pn,site);
469 string shire_url=get_shire_location(site,target_url.c_str());
471 // If the user is accessing the SHIRE acceptance point, pass it on.
472 if (target_url.find(shire_url)!=string::npos)
473 return SF_STATUS_REQ_NEXT_NOTIFICATION;
475 // Get the url request and scan for the must-contain string.
476 if (!site.m_mustContain.empty())
478 char* upcased=new char[target_url.length()+1];
479 strcpy(upcased,target_url.c_str());
481 for (vector<string>::const_iterator index=site.m_mustContain.begin(); index!=site.m_mustContain.end(); index++)
482 if (strstr(upcased,index->c_str()))
485 if (index==site.m_mustContain.end())
486 return SF_STATUS_REQ_NEXT_NOTIFICATION;
489 // SSL content check.
490 ShibINI& ini=g_Config->getINI();
492 if (ini.get_tag(site.m_name,"contentSSLOnly",true,&tag) && ShibINI::boolean(tag) && !pfc->fIsSecurePort)
494 return WriteClientError(pfc,
495 "This server is configured to deny non-SSL requests for secure resources. "
496 "Try your request again using https instead of http.");
499 ostringstream threadid;
500 threadid << "[" << getpid() << "] shire" << '\0';
501 saml::NDC ndc(threadid.str().c_str());
503 // Set SHIRE policies.
505 config.checkIPAddress = (ini.get_tag(site.m_name,"checkIPAddress",true,&tag) && ShibINI::boolean(tag));
506 config.lifetime=config.timeout=0;
508 if (ini.get_tag(site.m_name, "authLifetime", true, &tag))
509 config.lifetime=strtoul(tag.c_str(),NULL,10);
511 if (ini.get_tag(site.m_name, "authTimeout", true, &tag))
512 config.timeout=strtoul(tag.c_str(),NULL,10);
514 // Pull the config data we need to handle the various possible conditions.
516 if (!ini.get_tag(site.m_name, "cookieName", true, &shib_cookie))
517 return WriteClientError(pfc,"The cookieName configuration setting is missing, check configuration.");
520 if (!ini.get_tag(site.m_name, "wayfURL", true, &wayfLocation))
521 return WriteClientError(pfc,"The wayfURL configuration setting is missing, check configuration.");
524 if (!ini.get_tag(site.m_name, "shireError", true, &shireError))
525 return WriteClientError(pfc,"The shireError configuration setting is missing, check configuration.");
528 if (!ini.get_tag(site.m_name, "accessError", true, &shireError))
529 return WriteClientError(pfc,"The accessError configuration setting is missing, check configuration.");
531 // Get an RPC handle and build the SHIRE object.
532 RPCHandle* rpc_handle = (RPCHandle*)rpc_handle_key->getData();
535 rpc_handle = new RPCHandle(shib_target_sockname(), SHIBRPC_PROG, SHIBRPC_VERS_1);
536 rpc_handle_key->setData(rpc_handle);
538 SHIRE shire(rpc_handle, config, shire_url);
540 // Check for authentication cookie.
541 const char* session_id=NULL;
542 GetHeader(pn,pfc,"Cookie:",buf,128,false);
543 if (buf.empty() || !(session_id=strstr(buf,shib_cookie.c_str())) || *(session_id+shib_cookie.length())!='=')
546 string wayf("Location: ");
547 wayf+=wayfLocation + "?shire=" + url_encode(shire_url.c_str()) + "&target=" + url_encode(target_url.c_str()) + "\r\n";
548 // Insert the headers.
549 pfc->AddResponseHeaders(pfc,const_cast<char*>(wayf.c_str()),0);
550 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"302 Please Wait",0,0);
551 return SF_STATUS_REQ_FINISHED;
554 session_id+=shib_cookie.length() + 1; /* Skip over the '=' */
555 char* cookieend=strchr(session_id,';');
557 *cookieend = '\0'; /* Ignore anyting after a ; */
559 // Make sure this session is still valid.
560 RPCError* status = NULL;
561 ShibMLP markupProcessor;
562 bool has_tag = ini.get_tag(site.m_name, "supportContact", true, &tag);
563 markupProcessor.insert("supportContact", has_tag ? tag : "");
564 has_tag = ini.get_tag(site.m_name, "logoLocation", true, &tag);
565 markupProcessor.insert("logoLocation", has_tag ? tag : "");
566 markupProcessor.insert("requestURL", target_url);
568 GetServerVariable(pfc,"REMOTE_ADDR",buf,16);
570 status = shire.sessionIsValid(session_id, buf, target_url.c_str());
572 catch (ShibTargetException &e) {
573 markupProcessor.insert("errorType", "SHIRE Processing Error");
574 markupProcessor.insert("errorText", e.what());
575 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
576 return WriteClientError(pfc, shireError.c_str(), markupProcessor);
579 markupProcessor.insert("errorType", "SHIRE Processing Error");
580 markupProcessor.insert("errorText", "Unexpected Exception");
581 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
582 return WriteClientError(pfc, shireError.c_str(), markupProcessor);
586 if (status->isError()) {
587 if (status->isRetryable()) {
590 string wayf("Location: ");
591 wayf+=wayfLocation + "?shire=" + url_encode(shire_url.c_str()) + "&target=" + url_encode(target_url.c_str()) + "\r\n";
592 // Insert the headers.
593 pfc->AddResponseHeaders(pfc,const_cast<char*>(wayf.c_str()),0);
594 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"302 Please Wait",0,0);
595 return SF_STATUS_REQ_FINISHED;
598 // return the error page to the user
599 markupProcessor.insert(*status);
601 return WriteClientError(pfc, shireError.c_str(), markupProcessor);
608 rm_config.checkIPAddress = config.checkIPAddress;
609 RM rm(rpc_handle,rm_config);
611 // Get the attributes.
612 vector<SAMLAssertion*> assertions;
613 SAMLAuthenticationStatement* sso_statement=NULL;
614 status = rm.getAssertions(session_id, buf, target_url.c_str(), assertions, &sso_statement);
616 if (status->isError()) {
618 if (!ini.get_tag(site.m_name, "rmError", true, &shireError))
619 return WriteClientError(pfc,"The rmError configuration setting is missing, check configuration.");
621 markupProcessor.insert(*status);
623 return WriteClientError(pfc, rmError.c_str(), markupProcessor);
627 // Only allow a single assertion...
628 if (assertions.size() > 1) {
629 for (int k = 0; k < assertions.size(); k++)
630 delete assertions[k];
631 delete sso_statement;
632 return WriteClientError(pfc, accessError.c_str(), markupProcessor);
635 // Get the AAP providers, which contain the attribute policy info.
636 Iterator<IAAP*> provs=ShibConfig::getConfig().getAAPProviders();
638 // Clear out the list of mapped attributes
639 while (provs.hasNext())
641 IAAP* aap=provs.next();
645 Iterator<const IAttributeRule*> rules=aap->getAttributeRules();
646 while (rules.hasNext())
648 const char* header=rules.next()->getHeader();
650 pn->SetHeader(pfc,const_cast<char*>(header),"");
656 for (int k = 0; k < assertions.size(); k++)
657 delete assertions[k];
658 delete sso_statement;
665 // Clear relevant headers.
666 pn->SetHeader(pfc,"remote-user:","");
667 pn->SetHeader(pfc,"Shib-Attributes:","");
668 pn->SetHeader(pfc,"Shib-Origin-Site:","");
669 pn->SetHeader(pfc,"Shib-Authentication-Method:","");
671 // Maybe export the assertion.
672 if (ini.get_tag(site.m_name,"exportAssertion",true,&tag) && ShibINI::boolean(tag))
675 RM::serialize(*(assertions[0]), assertion);
676 // string::size_type lfeed;
677 // while ((lfeed=exp.find('\n'))!=string::npos)
678 // exp.erase(lfeed,1);
679 pn->SetHeader(pfc,"Shib-Attributes:",const_cast<char*>(assertion.c_str()));
684 auto_ptr<char> os(XMLString::transcode(sso_statement->getSubject()->getNameQualifier()));
685 auto_ptr<char> am(XMLString::transcode(sso_statement->getAuthMethod()));
686 pn->SetHeader(pfc,"Shib-Origin-Site:", os.get());
687 pn->SetHeader(pfc,"Shib-Authentication-Method:", am.get());
690 // Export the attributes. Only supports a single statement.
691 Iterator<SAMLAttribute*> j = assertions.size()==1 ? RM::getAttributes(*(assertions[0])) : EMPTY(SAMLAttribute*);
694 SAMLAttribute* attr=j.next();
696 // Are we supposed to export it?
697 const char* hname=NULL;
698 AAP wrapper(attr->getName(),attr->getNamespace());
700 hname=wrapper->getHeader();
703 Iterator<string> vals=attr->getSingleByteValues();
704 if (!strcmp(hname,"REMOTE_USER") && vals.hasNext())
706 char* principal=const_cast<char*>(vals.next().c_str());
707 pn->SetHeader(pfc,"remote-user:",principal);
708 pfc->pFilterContext=pfc->AllocMem(pfc,strlen(principal)+1,0);
709 if (pfc->pFilterContext)
710 strcpy(static_cast<char*>(pfc->pFilterContext),principal);
715 for (int it = 0; vals.hasNext(); it++) {
716 string value = vals.next();
717 for (string::size_type pos = value.find_first_of(";", string::size_type(0)); pos != string::npos; pos = value.find_first_of(";", pos)) {
718 value.insert(pos, "\\");
724 header=header + ';' + value;
726 pn->SetHeader(pfc,const_cast<char*>(hname),const_cast<char*>(header.c_str()));
732 for (int k = 0; k < assertions.size(); k++)
733 delete assertions[k];
734 delete sso_statement;
736 return SF_STATUS_REQ_NEXT_NOTIFICATION;
740 return WriteClientError(pfc,"Out of Memory");
744 if (e==ERROR_NO_DATA)
745 return WriteClientError(pfc,"A required variable or header was empty.");
747 WriteClientError(pfc,"Server detected unexpected IIS error.");
751 WriteClientError(pfc,"Server caught an unknown exception.");
754 return WriteClientError(pfc,"Server reached unreachable code!");
757 string get_target(LPEXTENSION_CONTROL_BLOCK lpECB, settings_t& site)
761 GetServerVariable(lpECB,"HTTPS",buf);
762 bool SSL=(buf=="on");
768 // We use the "normalizeRequest" tag to decide how to obtain the server's name.
770 if (g_Config->getINI().get_tag(site.m_name,"normalizeRequest",true,&tag) && ShibINI::boolean(tag))
776 GetServerVariable(lpECB,"SERVER_NAME",buf);
780 GetServerVariable(lpECB,"SERVER_PORT",buf,10);
781 if (buf!=(SSL ? "443" : "80"))
782 s=s + ':' + static_cast<char*>(buf);
784 GetServerVariable(lpECB,"URL",buf,255);
790 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* msg)
792 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
793 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,0);
794 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Error</TITLE></HEAD><BODY><H1>Shibboleth Error</H1>";
795 DWORD resplen=strlen(xmsg);
796 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg,&resplen,HSE_IO_SYNC);
798 lpECB->WriteClient(lpECB->ConnID,(LPVOID)msg,&resplen,HSE_IO_SYNC);
799 static const char* xmsg2="</BODY></HTML>";
800 resplen=strlen(xmsg2);
801 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg2,&resplen,HSE_IO_SYNC);
802 return HSE_STATUS_SUCCESS;
805 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* filename, ShibMLP& mlp)
807 ifstream infile(filename);
809 return WriteClientError(lpECB,"Unable to open error template, check settings.");
811 string res = mlp.run(infile);
812 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,0);
813 DWORD resplen=res.length();
814 lpECB->WriteClient(lpECB->ConnID,(LPVOID)res.c_str(),&resplen,0);
815 return HSE_STATUS_SUCCESS;
818 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
820 ostringstream threadid;
821 threadid << "[" << getpid() << "] shire" << '\0';
822 saml::NDC ndc(threadid.str().c_str());
824 ShibINI& ini = g_Config->getINI();
826 ShibMLP markupProcessor;
830 // Determine web site number. This can't really fail, I don't think.
833 GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
834 if ((site_id=strtoul(buf,NULL,10))==0)
835 return WriteClientError(lpECB,"IIS site instance appears to be invalid.");
837 // Match site instance to site settings.
838 if (site_id>g_Sites.size() || g_Sites[site_id-1].m_name.length()==0)
839 return WriteClientError(lpECB,"Shibboleth filter not configured for this web site.");
840 settings_t& site=g_Sites[site_id-1];
842 if (!ini.get_tag(site.m_name, "shireError", true, &shireError))
843 return WriteClientError(lpECB,"The shireError configuration setting is missing, check configuration.");
845 string target_url=get_target(lpECB,site);
846 string shire_url = get_shire_location(site,target_url.c_str());
848 // Set SHIRE policies.
851 config.checkIPAddress = (ini.get_tag(site.m_name,"checkIPAddress",true,&tag) && ShibINI::boolean(tag));
852 config.lifetime=config.timeout=0;
854 if (ini.get_tag(site.m_name, "authLifetime", true, &tag))
855 config.lifetime=strtoul(tag.c_str(),NULL,10);
857 if (ini.get_tag(site.m_name, "authTimeout", true, &tag))
858 config.timeout=strtoul(tag.c_str(),NULL,10);
860 // Pull the config data we need to handle the various possible conditions.
862 if (!ini.get_tag(site.m_name, "cookieName", true, &shib_cookie))
863 return WriteClientError(lpECB,"The cookieName configuration setting is missing, check configuration.");
866 if (!ini.get_tag(site.m_name, "wayfURL", true, &wayfLocation))
867 return WriteClientError(lpECB,"The wayfURL configuration setting is missing, check configuration.");
869 bool has_tag = ini.get_tag(site.m_name, "supportContact", true, &tag);
870 markupProcessor.insert("supportContact", has_tag ? tag : "");
871 has_tag = ini.get_tag(site.m_name, "logoLocation", true, &tag);
872 markupProcessor.insert("logoLocation", has_tag ? tag : "");
873 markupProcessor.insert("requestURL", target_url.c_str());
875 // Get an RPC handle and build the SHIRE object.
876 RPCHandle* rpc_handle = (RPCHandle*)rpc_handle_key->getData();
879 rpc_handle = new RPCHandle(shib_target_sockname(), SHIBRPC_PROG, SHIBRPC_VERS_1);
880 rpc_handle_key->setData(rpc_handle);
882 SHIRE shire(rpc_handle, config, shire_url.c_str());
884 // Process SHIRE POST
885 if (ini.get_tag(site.m_name, "shireSSLOnly", true, &tag) && ShibINI::boolean(tag))
887 // Make sure this is SSL, if it should be.
888 GetServerVariable(lpECB,"HTTPS",buf,10);
890 throw ShibTargetException(SHIBRPC_OK,"blocked non-SSL access to SHIRE POST processor");
893 // Make sure this is a POST
894 if (stricmp(lpECB->lpszMethod,"POST"))
895 throw ShibTargetException(SHIBRPC_OK,"blocked non-POST to SHIRE POST processor");
897 // Sure sure this POST is an appropriate content type
898 if (!lpECB->lpszContentType || stricmp(lpECB->lpszContentType,"application/x-www-form-urlencoded"))
899 throw ShibTargetException(SHIBRPC_OK,"blocked bad content-type to SHIRE POST processor");
901 // Make sure the "bytes sent" is a reasonable number and that we have all of it.
902 if (lpECB->cbTotalBytes > 1024*1024) // 1MB?
903 throw ShibTargetException (SHIBRPC_OK,"blocked too-large a post to SHIRE POST processor");
904 else if (lpECB->cbTotalBytes>lpECB->cbAvailable)
905 throw ShibTargetException (SHIBRPC_OK,"blocked incomplete post to SHIRE POST processor");
907 // Parse the incoming data.
908 HQUERY params=ParseQuery(lpECB);
910 throw ShibTargetException (SHIBRPC_OK,"unable to parse form data");
912 // Make sure the TARGET parameter exists
913 const char* target = QueryValue(params,"TARGET");
914 if (!target || *target == '\0')
915 throw ShibTargetException(SHIBRPC_OK,"SHIRE POST failed to find TARGET parameter");
917 // Make sure the SAMLResponse parameter exists
918 const char* post = QueryValue(params,"SAMLResponse");
919 if (!post || *post == '\0')
920 throw ShibTargetException (SHIBRPC_OK,"SHIRE POST failed to find SAMLResponse parameter");
922 GetServerVariable(lpECB,"REMOTE_ADDR",buf,16);
926 RPCError* status = shire.sessionCreate(post,buf,cookie);
928 if (status->isError()) {
929 if (status->isRetryable()) {
931 string wayf=wayfLocation + "?shire=" + url_encode(shire_url.c_str()) + "&target=" + url_encode(target);
932 DWORD len=wayf.length();
933 if (lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_URL_REDIRECT_RESP,(LPVOID)wayf.c_str(),&len,0))
934 return HSE_STATUS_SUCCESS;
935 return HSE_STATUS_ERROR;
938 // Return this error to the user.
939 markupProcessor.insert(*status);
941 return WriteClientError(lpECB,shireError.c_str(),markupProcessor);
945 // We've got a good session, set the cookie and redirect to target.
946 shib_cookie = "Set-Cookie: " + shib_cookie + '=' + cookie + "; path=/\r\n"
947 "Location: " + target + "\r\n"
948 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
949 "Cache-Control: private,no-store,no-cache\r\n"
950 "Connection: close\r\n";
951 HSE_SEND_HEADER_EX_INFO hinfo;
952 hinfo.pszStatus="302 Moved";
953 hinfo.pszHeader=shib_cookie.c_str();
955 hinfo.cchHeader=shib_cookie.length();
956 hinfo.fKeepConn=FALSE;
957 if (lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER_EX,&hinfo,0,0))
958 return HSE_STATUS_SUCCESS;
960 catch (ShibTargetException &e) {
961 markupProcessor.insert ("errorType", "SHIRE Processing Error");
962 markupProcessor.insert ("errorText", e.what());
963 markupProcessor.insert ("errorDesc", "An error occurred while processing your request.");
964 return WriteClientError(lpECB,shireError.c_str(),markupProcessor);
967 markupProcessor.insert ("errorType", "SHIRE Processing Error");
968 markupProcessor.insert ("errorText", "Unexpected Exception");
969 markupProcessor.insert ("errorDesc", "An error occurred while processing your request.");
970 return WriteClientError(lpECB,shireError.c_str(),markupProcessor);
973 return HSE_STATUS_ERROR;