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 ShibTargetConfig::preinit();
148 g_Config = &(ShibTargetConfig::init(SHIBTARGET_SHIRE, getenv("SHIBCONFIG")));
149 ShibINI& ini = g_Config->getINI();
151 // Create the RPC Handle TLS key.
152 rpc_handle_key=ThreadKey::create(destroy_handle);
154 // Read site-specific settings for each instance ID we can find.
157 sprintf(iid,"%u",i++);
159 while (ini.get_tag("isapi",iid,false,&hostname))
161 // If no section exists for the host, mark it as a "skip" site.
162 if (!ini.exists(hostname))
164 g_Sites.push_back(settings_t());
165 sprintf(iid,"%u",i++);
169 settings_t settings(hostname);
171 // Content matching string.
173 if (ini.get_tag(hostname,"mustContain",true,&mustcontain) && !mustcontain.empty())
175 char* buf=strdup(mustcontain.c_str());
178 while (char* sep=strchr(start,';'))
182 settings.m_mustContain.push_back(start);
186 settings.m_mustContain.push_back(start);
190 g_Sites.push_back(settings);
191 sprintf(iid,"%u",i++);
194 catch (SAMLException&)
196 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
197 "Filter startup failed with SAML exception, check shire log for help.");
200 catch (runtime_error& e)
202 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
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=assertion.find('\n'))!=string::npos)
678 assertion.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 string hname2=string(hname) + ':';
727 pn->SetHeader(pfc,const_cast<char*>(hname2.c_str()),const_cast<char*>(header.c_str()));
733 for (int k = 0; k < assertions.size(); k++)
734 delete assertions[k];
735 delete sso_statement;
737 return SF_STATUS_REQ_NEXT_NOTIFICATION;
741 return WriteClientError(pfc,"Out of Memory");
745 if (e==ERROR_NO_DATA)
746 return WriteClientError(pfc,"A required variable or header was empty.");
748 return WriteClientError(pfc,"Server detected unexpected IIS error.");
752 return WriteClientError(pfc,"Server caught an unknown exception.");
755 return WriteClientError(pfc,"Server reached unreachable code!");
758 string get_target(LPEXTENSION_CONTROL_BLOCK lpECB, settings_t& site)
762 GetServerVariable(lpECB,"HTTPS",buf);
763 bool SSL=(buf=="on");
769 // We use the "normalizeRequest" tag to decide how to obtain the server's name.
771 if (g_Config->getINI().get_tag(site.m_name,"normalizeRequest",true,&tag) && ShibINI::boolean(tag))
777 GetServerVariable(lpECB,"SERVER_NAME",buf);
781 GetServerVariable(lpECB,"SERVER_PORT",buf,10);
782 if (buf!=(SSL ? "443" : "80"))
783 s=s + ':' + static_cast<char*>(buf);
785 GetServerVariable(lpECB,"URL",buf,255);
791 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* msg)
793 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
794 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,0);
795 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Error</TITLE></HEAD><BODY><H1>Shibboleth Error</H1>";
796 DWORD resplen=strlen(xmsg);
797 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg,&resplen,HSE_IO_SYNC);
799 lpECB->WriteClient(lpECB->ConnID,(LPVOID)msg,&resplen,HSE_IO_SYNC);
800 static const char* xmsg2="</BODY></HTML>";
801 resplen=strlen(xmsg2);
802 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg2,&resplen,HSE_IO_SYNC);
803 return HSE_STATUS_SUCCESS;
806 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* filename, ShibMLP& mlp)
808 ifstream infile(filename);
810 return WriteClientError(lpECB,"Unable to open error template, check settings.");
812 string res = mlp.run(infile);
813 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,0);
814 DWORD resplen=res.length();
815 lpECB->WriteClient(lpECB->ConnID,(LPVOID)res.c_str(),&resplen,0);
816 return HSE_STATUS_SUCCESS;
819 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
821 ostringstream threadid;
822 threadid << "[" << getpid() << "] shire" << '\0';
823 saml::NDC ndc(threadid.str().c_str());
825 ShibINI& ini = g_Config->getINI();
827 ShibMLP markupProcessor;
831 // Determine web site number. This can't really fail, I don't think.
834 GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
835 if ((site_id=strtoul(buf,NULL,10))==0)
836 return WriteClientError(lpECB,"IIS site instance appears to be invalid.");
838 // Match site instance to site settings.
839 if (site_id>g_Sites.size() || g_Sites[site_id-1].m_name.length()==0)
840 return WriteClientError(lpECB,"Shibboleth filter not configured for this web site.");
841 settings_t& site=g_Sites[site_id-1];
843 if (!ini.get_tag(site.m_name, "shireError", true, &shireError))
844 return WriteClientError(lpECB,"The shireError configuration setting is missing, check configuration.");
846 string target_url=get_target(lpECB,site);
847 string shire_url = get_shire_location(site,target_url.c_str());
849 // Set SHIRE policies.
852 config.checkIPAddress = (ini.get_tag(site.m_name,"checkIPAddress",true,&tag) && ShibINI::boolean(tag));
853 config.lifetime=config.timeout=0;
855 if (ini.get_tag(site.m_name, "authLifetime", true, &tag))
856 config.lifetime=strtoul(tag.c_str(),NULL,10);
858 if (ini.get_tag(site.m_name, "authTimeout", true, &tag))
859 config.timeout=strtoul(tag.c_str(),NULL,10);
861 // Pull the config data we need to handle the various possible conditions.
863 if (!ini.get_tag(site.m_name, "cookieName", true, &shib_cookie))
864 return WriteClientError(lpECB,"The cookieName configuration setting is missing, check configuration.");
867 if (!ini.get_tag(site.m_name, "wayfURL", true, &wayfLocation))
868 return WriteClientError(lpECB,"The wayfURL configuration setting is missing, check configuration.");
870 bool has_tag = ini.get_tag(site.m_name, "supportContact", true, &tag);
871 markupProcessor.insert("supportContact", has_tag ? tag : "");
872 has_tag = ini.get_tag(site.m_name, "logoLocation", true, &tag);
873 markupProcessor.insert("logoLocation", has_tag ? tag : "");
874 markupProcessor.insert("requestURL", target_url.c_str());
876 // Get an RPC handle and build the SHIRE object.
877 RPCHandle* rpc_handle = (RPCHandle*)rpc_handle_key->getData();
880 rpc_handle = new RPCHandle(shib_target_sockname(), SHIBRPC_PROG, SHIBRPC_VERS_1);
881 rpc_handle_key->setData(rpc_handle);
883 SHIRE shire(rpc_handle, config, shire_url.c_str());
885 // Process SHIRE POST
886 if (ini.get_tag(site.m_name, "shireSSLOnly", true, &tag) && ShibINI::boolean(tag))
888 // Make sure this is SSL, if it should be.
889 GetServerVariable(lpECB,"HTTPS",buf,10);
891 throw ShibTargetException(SHIBRPC_OK,"blocked non-SSL access to SHIRE POST processor");
894 // Make sure this is a POST
895 if (stricmp(lpECB->lpszMethod,"POST"))
896 throw ShibTargetException(SHIBRPC_OK,"blocked non-POST to SHIRE POST processor");
898 // Sure sure this POST is an appropriate content type
899 if (!lpECB->lpszContentType || stricmp(lpECB->lpszContentType,"application/x-www-form-urlencoded"))
900 throw ShibTargetException(SHIBRPC_OK,"blocked bad content-type to SHIRE POST processor");
902 // Make sure the "bytes sent" is a reasonable number and that we have all of it.
903 if (lpECB->cbTotalBytes > 1024*1024) // 1MB?
904 throw ShibTargetException (SHIBRPC_OK,"blocked too-large a post to SHIRE POST processor");
905 else if (lpECB->cbTotalBytes>lpECB->cbAvailable)
906 throw ShibTargetException (SHIBRPC_OK,"blocked incomplete post to SHIRE POST processor");
908 // Parse the incoming data.
909 HQUERY params=ParseQuery(lpECB);
911 throw ShibTargetException (SHIBRPC_OK,"unable to parse form data");
913 // Make sure the TARGET parameter exists
914 const char* target = QueryValue(params,"TARGET");
915 if (!target || *target == '\0')
916 throw ShibTargetException(SHIBRPC_OK,"SHIRE POST failed to find TARGET parameter");
918 // Make sure the SAMLResponse parameter exists
919 const char* post = QueryValue(params,"SAMLResponse");
920 if (!post || *post == '\0')
921 throw ShibTargetException (SHIBRPC_OK,"SHIRE POST failed to find SAMLResponse parameter");
923 GetServerVariable(lpECB,"REMOTE_ADDR",buf,16);
927 RPCError* status = shire.sessionCreate(post,buf,cookie);
929 if (status->isError()) {
930 if (status->isRetryable()) {
932 string wayf=wayfLocation + "?shire=" + url_encode(shire_url.c_str()) + "&target=" + url_encode(target);
933 DWORD len=wayf.length();
934 if (lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_URL_REDIRECT_RESP,(LPVOID)wayf.c_str(),&len,0))
935 return HSE_STATUS_SUCCESS;
936 return HSE_STATUS_ERROR;
939 // Return this error to the user.
940 markupProcessor.insert(*status);
942 return WriteClientError(lpECB,shireError.c_str(),markupProcessor);
946 // We've got a good session, set the cookie and redirect to target.
947 shib_cookie = "Set-Cookie: " + shib_cookie + '=' + cookie + "; path=/\r\n"
948 "Location: " + target + "\r\n"
949 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
950 "Cache-Control: private,no-store,no-cache\r\n"
951 "Connection: close\r\n";
952 HSE_SEND_HEADER_EX_INFO hinfo;
953 hinfo.pszStatus="302 Moved";
954 hinfo.pszHeader=shib_cookie.c_str();
956 hinfo.cchHeader=shib_cookie.length();
957 hinfo.fKeepConn=FALSE;
958 if (lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER_EX,&hinfo,0,0))
959 return HSE_STATUS_SUCCESS;
961 catch (ShibTargetException &e) {
962 markupProcessor.insert ("errorType", "SHIRE Processing Error");
963 markupProcessor.insert ("errorText", e.what());
964 markupProcessor.insert ("errorDesc", "An error occurred while processing your request.");
965 return WriteClientError(lpECB,shireError.c_str(),markupProcessor);
968 markupProcessor.insert ("errorType", "SHIRE Processing Error");
969 markupProcessor.insert ("errorText", "Unexpected Exception");
970 markupProcessor.insert ("errorDesc", "An error occurred while processing your request.");
971 return WriteClientError(lpECB,shireError.c_str(),markupProcessor);
974 return HSE_STATUS_ERROR;