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 Category& log=Category::getInstance("isapi_shib.GetFilterVersion");
156 // Read site-specific settings for each instance ID we can find.
159 sprintf(iid,"%u",i++);
161 while (ini.get_tag("isapi",iid,false,&hostname))
163 log.info("configuring for site ID (%d), hostname (%s)",i-1,hostname.empty() ? "null" : hostname.c_str());
165 // If no section exists for the host, mark it as a "skip" site.
166 if (!ini.exists(hostname))
168 log.info("skipping site ID (%d)",i-1);
169 g_Sites.push_back(settings_t());
170 sprintf(iid,"%u",i++);
174 settings_t settings(hostname);
176 // Content matching string.
178 if (ini.get_tag(hostname,"mustContain",true,&mustcontain) && !mustcontain.empty())
180 char* buf=strdup(mustcontain.c_str());
183 while (char* sep=strchr(start,';'))
187 settings.m_mustContain.push_back(start);
191 settings.m_mustContain.push_back(start);
195 g_Sites.push_back(settings);
196 sprintf(iid,"%u",i++);
200 catch (SAMLException&)
202 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
203 "Filter startup failed with SAML exception, check shire log for help.");
206 catch (runtime_error& e)
208 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
213 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Filter startup failed with unexpected exception.");
217 pVer->dwFilterVersion=HTTP_FILTER_REVISION;
218 strncpy(pVer->lpszFilterDesc,"Shibboleth ISAPI Filter",SF_MAX_FILTER_DESC_LEN);
219 pVer->dwFlags=(SF_NOTIFY_ORDER_HIGH |
220 SF_NOTIFY_SECURE_PORT |
221 SF_NOTIFY_NONSECURE_PORT |
222 SF_NOTIFY_PREPROC_HEADERS |
224 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter initialized...");
228 extern "C" BOOL WINAPI TerminateExtension(DWORD)
230 return TRUE; // cleanup should happen when filter unloads
233 extern "C" BOOL WINAPI TerminateFilter(DWORD)
235 delete rpc_handle_key;
237 g_Config->shutdown();
239 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter shut down...");
243 /* Next up, some suck-free versions of various APIs.
245 You DON'T require people to guess the buffer size and THEN tell them the right size.
246 Returning an LPCSTR is apparently way beyond their ken. Not to mention the fact that
247 constant strings aren't typed as such, making it just that much harder. These versions
248 are now updated to use a special growable buffer object, modeled after the standard
249 string class. The standard string won't work because they left out the option to
250 pre-allocate a non-constant buffer.
256 dynabuf() { bufptr=NULL; buflen=0; }
257 dynabuf(size_t s) { bufptr=new char[buflen=s]; *bufptr=0; }
258 ~dynabuf() { delete[] bufptr; }
259 size_t length() const { return bufptr ? strlen(bufptr) : 0; }
260 size_t size() const { return buflen; }
261 bool empty() const { return length()==0; }
262 void reserve(size_t s, bool keep=false);
263 void erase() { if (bufptr) memset(bufptr,0,buflen); }
264 operator char*() { return bufptr; }
265 bool operator ==(const char* s) const;
266 bool operator !=(const char* s) const { return !(*this==s); }
272 void dynabuf::reserve(size_t s, bool keep)
279 p[buflen]=bufptr[buflen];
285 bool dynabuf::operator==(const char* s) const
287 if (buflen==NULL || s==NULL)
288 return (buflen==NULL && s==NULL);
290 return strcmp(bufptr,s)==0;
293 void GetServerVariable(PHTTP_FILTER_CONTEXT pfc, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
294 throw (bad_alloc, DWORD)
300 while (!pfc->GetServerVariable(pfc,lpszVariable,s,&size))
302 // Grumble. Check the error.
303 DWORD e=GetLastError();
304 if (e==ERROR_INSUFFICIENT_BUFFER)
309 if (bRequired && s.empty())
313 void GetServerVariable(LPEXTENSION_CONTROL_BLOCK lpECB, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
314 throw (bad_alloc, DWORD)
320 while (lpECB->GetServerVariable(lpECB->ConnID,lpszVariable,s,&size))
322 // Grumble. Check the error.
323 DWORD e=GetLastError();
324 if (e==ERROR_INSUFFICIENT_BUFFER)
329 if (bRequired && s.empty())
333 void GetHeader(PHTTP_FILTER_PREPROC_HEADERS pn, PHTTP_FILTER_CONTEXT pfc,
334 LPSTR lpszName, dynabuf& s, DWORD size=80, bool bRequired=true)
335 throw (bad_alloc, DWORD)
341 while (!pn->GetHeader(pfc,lpszName,s,&size))
343 // Grumble. Check the error.
344 DWORD e=GetLastError();
345 if (e==ERROR_INSUFFICIENT_BUFFER)
350 if (bRequired && s.empty())
354 inline char hexchar(unsigned short s)
356 return (s<=9) ? ('0' + s) : ('A' + s - 10);
359 string url_encode(const char* url) throw (bad_alloc)
361 static char badchars[]="\"\\+<>#%{}|^~[]`;/?:@=&";
363 for (const char* pch=url; *pch; pch++)
365 if (strchr(badchars,*pch)!=NULL || *pch<=0x1F || *pch>=0x7F)
366 s=s + '%' + hexchar(*pch >> 4) + hexchar(*pch & 0x0F);
373 string get_target(PHTTP_FILTER_CONTEXT pfc, PHTTP_FILTER_PREPROC_HEADERS pn, settings_t& site)
375 // Reconstructing the requested URL is not fun. Apparently, the PREPROC_HEADERS
376 // event means way pre. As in, none of the usual CGI headers are in place yet.
377 // It's actually almost easier, in a way, because all the path-info and query
378 // stuff is in one place, the requested URL, which we can get. But we have to
379 // reconstruct the protocol/host pair using tweezers.
381 if (pfc->fIsSecurePort)
386 // We use the "normalizeRequest" tag to decide how to obtain the server's name.
389 if (g_Config->getINI().get_tag(site.m_name,"normalizeRequest",true,&tag) && ShibINI::boolean(tag))
395 GetServerVariable(pfc,"SERVER_NAME",buf);
399 GetServerVariable(pfc,"SERVER_PORT",buf,10);
400 if (buf!=(pfc->fIsSecurePort ? "443" : "80"))
401 s=s + ':' + static_cast<char*>(buf);
403 GetHeader(pn,pfc,"url",buf,256,false);
409 string get_shire_location(settings_t& site, const char* target)
412 if (g_Config->getINI().get_tag(site.m_name,"shireURL",true,&shireURL) && !shireURL.empty())
414 if (shireURL[0]!='/')
416 const char* colon=strchr(target,':');
417 const char* slash=strchr(colon+3,'/');
418 string s(target,slash-target);
425 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const char* msg)
427 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
428 static const char* ctype="Content-Type: text/html\r\n";
429 pfc->AddResponseHeaders(pfc,const_cast<char*>(ctype),0);
430 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",0,0);
431 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Filter Error</TITLE></HEAD><BODY>"
432 "<H1>Shibboleth Filter Error</H1>";
433 DWORD resplen=strlen(xmsg);
434 pfc->WriteClient(pfc,(LPVOID)xmsg,&resplen,0);
436 pfc->WriteClient(pfc,(LPVOID)msg,&resplen,0);
437 static const char* xmsg2="</BODY></HTML>";
438 resplen=strlen(xmsg2);
439 pfc->WriteClient(pfc,(LPVOID)xmsg2,&resplen,0);
440 return SF_STATUS_REQ_FINISHED;
443 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const char* filename, ShibMLP& mlp)
445 ifstream infile(filename);
447 return WriteClientError(pfc,"Unable to open error template, check settings.");
448 string res = mlp.run(infile);
450 static const char* ctype="Content-Type: text/html\r\n";
451 pfc->AddResponseHeaders(pfc,const_cast<char*>(ctype),0);
452 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",0,0);
453 DWORD resplen=res.length();
454 pfc->WriteClient(pfc,(LPVOID)res.c_str(),&resplen,0);
455 return SF_STATUS_REQ_FINISHED;
458 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
460 // Is this a log notification?
461 if (notificationType==SF_NOTIFY_LOG)
463 if (pfc->pFilterContext)
464 ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=static_cast<LPCSTR>(pfc->pFilterContext);
465 return SF_STATUS_REQ_NEXT_NOTIFICATION;
468 PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
471 // Determine web site number. This can't really fail, I don't think.
474 GetServerVariable(pfc,"INSTANCE_ID",buf,10);
475 if ((site_id=strtoul(buf,NULL,10))==0)
476 return WriteClientError(pfc,"IIS site instance appears to be invalid.");
478 // Match site instance to site settings.
479 if (site_id>g_Sites.size() || g_Sites[site_id-1].m_name.length()==0)
480 return SF_STATUS_REQ_NEXT_NOTIFICATION;
481 settings_t& site=g_Sites[site_id-1];
483 string target_url=get_target(pfc,pn,site);
484 string shire_url=get_shire_location(site,target_url.c_str());
486 // If the user is accessing the SHIRE acceptance point, pass it on.
487 if (target_url.find(shire_url)!=string::npos)
488 return SF_STATUS_REQ_NEXT_NOTIFICATION;
490 // Get the url request and scan for the must-contain string.
491 if (!site.m_mustContain.empty())
493 char* upcased=new char[target_url.length()+1];
494 strcpy(upcased,target_url.c_str());
496 for (vector<string>::const_iterator index=site.m_mustContain.begin(); index!=site.m_mustContain.end(); index++)
497 if (strstr(upcased,index->c_str()))
500 if (index==site.m_mustContain.end())
501 return SF_STATUS_REQ_NEXT_NOTIFICATION;
504 // SSL content check.
505 ShibINI& ini=g_Config->getINI();
507 if (ini.get_tag(site.m_name,"contentSSLOnly",true,&tag) && ShibINI::boolean(tag) && !pfc->fIsSecurePort)
509 return WriteClientError(pfc,
510 "This server is configured to deny non-SSL requests for secure resources. "
511 "Try your request again using https instead of http.");
514 ostringstream threadid;
515 threadid << "[" << getpid() << "] shire" << '\0';
516 saml::NDC ndc(threadid.str().c_str());
518 // Set SHIRE policies.
520 config.checkIPAddress = (ini.get_tag(site.m_name,"checkIPAddress",true,&tag) && ShibINI::boolean(tag));
521 config.lifetime=config.timeout=0;
523 if (ini.get_tag(site.m_name, "authLifetime", true, &tag))
524 config.lifetime=strtoul(tag.c_str(),NULL,10);
526 if (ini.get_tag(site.m_name, "authTimeout", true, &tag))
527 config.timeout=strtoul(tag.c_str(),NULL,10);
529 // Pull the config data we need to handle the various possible conditions.
531 if (!ini.get_tag(site.m_name, "cookieName", true, &shib_cookie))
532 return WriteClientError(pfc,"The cookieName configuration setting is missing, check configuration.");
535 if (!ini.get_tag(site.m_name, "wayfURL", true, &wayfLocation))
536 return WriteClientError(pfc,"The wayfURL configuration setting is missing, check configuration.");
539 if (!ini.get_tag(site.m_name, "shireError", true, &shireError))
540 return WriteClientError(pfc,"The shireError configuration setting is missing, check configuration.");
543 if (!ini.get_tag(site.m_name, "accessError", true, &shireError))
544 return WriteClientError(pfc,"The accessError configuration setting is missing, check configuration.");
546 // Get an RPC handle and build the SHIRE object.
547 RPCHandle* rpc_handle = (RPCHandle*)rpc_handle_key->getData();
550 rpc_handle = new RPCHandle(shib_target_sockname(), SHIBRPC_PROG, SHIBRPC_VERS_1);
551 rpc_handle_key->setData(rpc_handle);
553 SHIRE shire(rpc_handle, config, shire_url);
555 // Check for authentication cookie.
556 const char* session_id=NULL;
557 GetHeader(pn,pfc,"Cookie:",buf,128,false);
558 Category::getInstance("isapi_shib.HttpFilterProc").debug("cookie header is {%s}",(const char*)buf);
559 if (buf.empty() || !(session_id=strstr(buf,shib_cookie.c_str())) || *(session_id+shib_cookie.length())!='=')
562 string wayf("Location: ");
563 wayf+=wayfLocation + "?shire=" + url_encode(shire_url.c_str()) + "&target=" + url_encode(target_url.c_str()) + "\r\n";
564 // Insert the headers.
565 pfc->AddResponseHeaders(pfc,const_cast<char*>(wayf.c_str()),0);
566 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"302 Please Wait",0,0);
567 return SF_STATUS_REQ_FINISHED;
570 session_id+=shib_cookie.length() + 1; /* Skip over the '=' */
571 char* cookieend=strchr(session_id,';');
573 *cookieend = '\0'; /* Ignore anyting after a ; */
575 // Make sure this session is still valid.
576 RPCError* status = NULL;
577 ShibMLP markupProcessor;
578 bool has_tag = ini.get_tag(site.m_name, "supportContact", true, &tag);
579 markupProcessor.insert("supportContact", has_tag ? tag : "");
580 has_tag = ini.get_tag(site.m_name, "logoLocation", true, &tag);
581 markupProcessor.insert("logoLocation", has_tag ? tag : "");
582 markupProcessor.insert("requestURL", target_url);
585 GetServerVariable(pfc,"REMOTE_ADDR",abuf,16);
587 status = shire.sessionIsValid(session_id, abuf, target_url.c_str());
589 catch (ShibTargetException &e) {
590 markupProcessor.insert("errorType", "SHIRE Processing Error");
591 markupProcessor.insert("errorText", e.what());
592 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
593 return WriteClientError(pfc, shireError.c_str(), markupProcessor);
596 markupProcessor.insert("errorType", "SHIRE Processing Error");
597 markupProcessor.insert("errorText", "Unexpected Exception");
598 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
599 return WriteClientError(pfc, shireError.c_str(), markupProcessor);
603 if (status->isError()) {
604 if (status->isRetryable()) {
607 string wayf("Location: ");
608 wayf+=wayfLocation + "?shire=" + url_encode(shire_url.c_str()) + "&target=" + url_encode(target_url.c_str()) + "\r\n";
609 // Insert the headers.
610 pfc->AddResponseHeaders(pfc,const_cast<char*>(wayf.c_str()),0);
611 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"302 Please Wait",0,0);
612 return SF_STATUS_REQ_FINISHED;
615 // return the error page to the user
616 markupProcessor.insert(*status);
618 return WriteClientError(pfc, shireError.c_str(), markupProcessor);
625 rm_config.checkIPAddress = config.checkIPAddress;
626 RM rm(rpc_handle,rm_config);
628 // Get the attributes.
629 vector<SAMLAssertion*> assertions;
630 SAMLAuthenticationStatement* sso_statement=NULL;
631 status = rm.getAssertions(session_id, buf, target_url.c_str(), assertions, &sso_statement);
633 if (status->isError()) {
635 if (!ini.get_tag(site.m_name, "rmError", true, &shireError))
636 return WriteClientError(pfc,"The rmError configuration setting is missing, check configuration.");
638 markupProcessor.insert(*status);
640 return WriteClientError(pfc, rmError.c_str(), markupProcessor);
644 // Only allow a single assertion...
645 if (assertions.size() > 1) {
646 for (int k = 0; k < assertions.size(); k++)
647 delete assertions[k];
648 delete sso_statement;
649 return WriteClientError(pfc, accessError.c_str(), markupProcessor);
652 // Get the AAP providers, which contain the attribute policy info.
653 Iterator<IAAP*> provs=ShibConfig::getConfig().getAAPProviders();
655 // Clear out the list of mapped attributes
656 while (provs.hasNext())
658 IAAP* aap=provs.next();
662 Iterator<const IAttributeRule*> rules=aap->getAttributeRules();
663 while (rules.hasNext())
665 const char* header=rules.next()->getHeader();
667 pn->SetHeader(pfc,const_cast<char*>(header),"");
673 for (int k = 0; k < assertions.size(); k++)
674 delete assertions[k];
675 delete sso_statement;
682 // Clear relevant headers.
683 pn->SetHeader(pfc,"remote-user:","");
684 pn->SetHeader(pfc,"Shib-Attributes:","");
685 pn->SetHeader(pfc,"Shib-Origin-Site:","");
686 pn->SetHeader(pfc,"Shib-Authentication-Method:","");
688 // Maybe export the assertion.
689 if (ini.get_tag(site.m_name,"exportAssertion",true,&tag) && ShibINI::boolean(tag))
692 RM::serialize(*(assertions[0]), assertion);
693 string::size_type lfeed;
694 while ((lfeed=assertion.find('\n'))!=string::npos)
695 assertion.erase(lfeed,1);
696 pn->SetHeader(pfc,"Shib-Attributes:",const_cast<char*>(assertion.c_str()));
701 auto_ptr<char> os(XMLString::transcode(sso_statement->getSubject()->getNameQualifier()));
702 auto_ptr<char> am(XMLString::transcode(sso_statement->getAuthMethod()));
703 pn->SetHeader(pfc,"Shib-Origin-Site:", os.get());
704 pn->SetHeader(pfc,"Shib-Authentication-Method:", am.get());
707 // Export the attributes. Only supports a single statement.
708 Iterator<SAMLAttribute*> j = assertions.size()==1 ? RM::getAttributes(*(assertions[0])) : EMPTY(SAMLAttribute*);
711 SAMLAttribute* attr=j.next();
713 // Are we supposed to export it?
714 const char* hname=NULL;
715 AAP wrapper(attr->getName(),attr->getNamespace());
717 hname=wrapper->getHeader();
720 Iterator<string> vals=attr->getSingleByteValues();
721 if (!strcmp(hname,"REMOTE_USER") && vals.hasNext())
723 char* principal=const_cast<char*>(vals.next().c_str());
724 pn->SetHeader(pfc,"remote-user:",principal);
725 pfc->pFilterContext=pfc->AllocMem(pfc,strlen(principal)+1,0);
726 if (pfc->pFilterContext)
727 strcpy(static_cast<char*>(pfc->pFilterContext),principal);
732 for (int it = 0; vals.hasNext(); it++) {
733 string value = vals.next();
734 for (string::size_type pos = value.find_first_of(";", string::size_type(0)); pos != string::npos; pos = value.find_first_of(";", pos)) {
735 value.insert(pos, "\\");
741 header=header + ';' + value;
743 string hname2=string(hname) + ':';
744 pn->SetHeader(pfc,const_cast<char*>(hname2.c_str()),const_cast<char*>(header.c_str()));
750 for (int k = 0; k < assertions.size(); k++)
751 delete assertions[k];
752 delete sso_statement;
754 return SF_STATUS_REQ_NEXT_NOTIFICATION;
758 return WriteClientError(pfc,"Out of Memory");
762 if (e==ERROR_NO_DATA)
763 return WriteClientError(pfc,"A required variable or header was empty.");
765 return WriteClientError(pfc,"Server detected unexpected IIS error.");
769 return WriteClientError(pfc,"Server caught an unknown exception.");
772 return WriteClientError(pfc,"Server reached unreachable code!");
775 string get_target(LPEXTENSION_CONTROL_BLOCK lpECB, settings_t& site)
779 GetServerVariable(lpECB,"HTTPS",buf);
780 bool SSL=(buf=="on");
786 // We use the "normalizeRequest" tag to decide how to obtain the server's name.
788 if (g_Config->getINI().get_tag(site.m_name,"normalizeRequest",true,&tag) && ShibINI::boolean(tag))
794 GetServerVariable(lpECB,"SERVER_NAME",buf);
798 GetServerVariable(lpECB,"SERVER_PORT",buf,10);
799 if (buf!=(SSL ? "443" : "80"))
800 s=s + ':' + static_cast<char*>(buf);
802 GetServerVariable(lpECB,"URL",buf,255);
808 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* msg)
810 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
811 static const char* ctype="Content-Type: text/html\r\n";
812 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
813 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Error</TITLE></HEAD><BODY><H1>Shibboleth Error</H1>";
814 DWORD resplen=strlen(xmsg);
815 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg,&resplen,HSE_IO_SYNC);
817 lpECB->WriteClient(lpECB->ConnID,(LPVOID)msg,&resplen,HSE_IO_SYNC);
818 static const char* xmsg2="</BODY></HTML>";
819 resplen=strlen(xmsg2);
820 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg2,&resplen,HSE_IO_SYNC);
821 return HSE_STATUS_SUCCESS;
824 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* filename, ShibMLP& mlp)
826 ifstream infile(filename);
828 return WriteClientError(lpECB,"Unable to open error template, check settings.");
830 string res = mlp.run(infile);
831 static const char* ctype="Content-Type: text/html\r\n";
832 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
833 DWORD resplen=res.length();
834 lpECB->WriteClient(lpECB->ConnID,(LPVOID)res.c_str(),&resplen,0);
835 return HSE_STATUS_SUCCESS;
838 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
840 ostringstream threadid;
841 threadid << "[" << getpid() << "] shire" << '\0';
842 saml::NDC ndc(threadid.str().c_str());
844 ShibINI& ini = g_Config->getINI();
846 ShibMLP markupProcessor;
850 // Determine web site number. This can't really fail, I don't think.
853 GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
854 if ((site_id=strtoul(buf,NULL,10))==0)
855 return WriteClientError(lpECB,"IIS site instance appears to be invalid.");
857 // Match site instance to site settings.
858 if (site_id>g_Sites.size() || g_Sites[site_id-1].m_name.length()==0)
859 return WriteClientError(lpECB,"Shibboleth filter not configured for this web site.");
860 settings_t& site=g_Sites[site_id-1];
862 if (!ini.get_tag(site.m_name, "shireError", true, &shireError))
863 return WriteClientError(lpECB,"The shireError configuration setting is missing, check configuration.");
865 string target_url=get_target(lpECB,site);
866 string shire_url = get_shire_location(site,target_url.c_str());
868 // Set SHIRE policies.
871 config.checkIPAddress = (ini.get_tag(site.m_name,"checkIPAddress",true,&tag) && ShibINI::boolean(tag));
872 config.lifetime=config.timeout=0;
874 if (ini.get_tag(site.m_name, "authLifetime", true, &tag))
875 config.lifetime=strtoul(tag.c_str(),NULL,10);
877 if (ini.get_tag(site.m_name, "authTimeout", true, &tag))
878 config.timeout=strtoul(tag.c_str(),NULL,10);
880 // Pull the config data we need to handle the various possible conditions.
882 if (!ini.get_tag(site.m_name, "cookieName", true, &shib_cookie))
883 return WriteClientError(lpECB,"The cookieName configuration setting is missing, check configuration.");
886 if (!ini.get_tag(site.m_name, "wayfURL", true, &wayfLocation))
887 return WriteClientError(lpECB,"The wayfURL configuration setting is missing, check configuration.");
889 bool has_tag = ini.get_tag(site.m_name, "supportContact", true, &tag);
890 markupProcessor.insert("supportContact", has_tag ? tag : "");
891 has_tag = ini.get_tag(site.m_name, "logoLocation", true, &tag);
892 markupProcessor.insert("logoLocation", has_tag ? tag : "");
893 markupProcessor.insert("requestURL", target_url.c_str());
895 // Get an RPC handle and build the SHIRE object.
896 RPCHandle* rpc_handle = (RPCHandle*)rpc_handle_key->getData();
899 rpc_handle = new RPCHandle(shib_target_sockname(), SHIBRPC_PROG, SHIBRPC_VERS_1);
900 rpc_handle_key->setData(rpc_handle);
902 SHIRE shire(rpc_handle, config, shire_url.c_str());
904 // Process SHIRE POST
905 if (ini.get_tag(site.m_name, "shireSSLOnly", true, &tag) && ShibINI::boolean(tag))
907 // Make sure this is SSL, if it should be.
908 GetServerVariable(lpECB,"HTTPS",buf,10);
910 throw ShibTargetException(SHIBRPC_OK,"blocked non-SSL access to SHIRE POST processor");
913 // Make sure this is a POST
914 if (stricmp(lpECB->lpszMethod,"POST"))
915 throw ShibTargetException(SHIBRPC_OK,"blocked non-POST to SHIRE POST processor");
917 // Sure sure this POST is an appropriate content type
918 if (!lpECB->lpszContentType || stricmp(lpECB->lpszContentType,"application/x-www-form-urlencoded"))
919 throw ShibTargetException(SHIBRPC_OK,"blocked bad content-type to SHIRE POST processor");
921 // Make sure the "bytes sent" is a reasonable number and that we have all of it.
922 if (lpECB->cbTotalBytes > 1024*1024) // 1MB?
923 throw ShibTargetException (SHIBRPC_OK,"blocked too-large a post to SHIRE POST processor");
924 else if (lpECB->cbTotalBytes>lpECB->cbAvailable)
925 throw ShibTargetException (SHIBRPC_OK,"blocked incomplete post to SHIRE POST processor");
927 // Parse the incoming data.
928 HQUERY params=ParseQuery(lpECB);
930 throw ShibTargetException (SHIBRPC_OK,"unable to parse form data");
932 // Make sure the TARGET parameter exists
933 const char* target = QueryValue(params,"TARGET");
934 if (!target || *target == '\0')
935 throw ShibTargetException(SHIBRPC_OK,"SHIRE POST failed to find TARGET parameter");
937 // Make sure the SAMLResponse parameter exists
938 const char* post = QueryValue(params,"SAMLResponse");
939 if (!post || *post == '\0')
940 throw ShibTargetException (SHIBRPC_OK,"SHIRE POST failed to find SAMLResponse parameter");
942 GetServerVariable(lpECB,"REMOTE_ADDR",buf,16);
946 RPCError* status = shire.sessionCreate(post,buf,cookie);
948 if (status->isError()) {
949 if (status->isRetryable()) {
951 string wayf=wayfLocation + "?shire=" + url_encode(shire_url.c_str()) + "&target=" + url_encode(target);
952 DWORD len=wayf.length();
953 if (lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_URL_REDIRECT_RESP,(LPVOID)wayf.c_str(),&len,0))
954 return HSE_STATUS_SUCCESS;
955 return HSE_STATUS_ERROR;
958 // Return this error to the user.
959 markupProcessor.insert(*status);
961 return WriteClientError(lpECB,shireError.c_str(),markupProcessor);
965 // We've got a good session, set the cookie and redirect to target.
966 shib_cookie = "Set-Cookie: " + shib_cookie + '=' + cookie + "; path=/\r\n"
967 "Location: " + target + "\r\n"
968 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
969 "Cache-Control: private,no-store,no-cache\r\n"
970 "Connection: close\r\n";
971 HSE_SEND_HEADER_EX_INFO hinfo;
972 hinfo.pszStatus="302 Moved";
973 hinfo.pszHeader=shib_cookie.c_str();
975 hinfo.cchHeader=shib_cookie.length();
976 hinfo.fKeepConn=FALSE;
977 if (lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER_EX,&hinfo,0,0))
978 return HSE_STATUS_SUCCESS;
980 catch (ShibTargetException &e) {
981 markupProcessor.insert ("errorType", "SHIRE Processing Error");
982 markupProcessor.insert ("errorText", e.what());
983 markupProcessor.insert ("errorDesc", "An error occurred while processing your request.");
984 return WriteClientError(lpECB,shireError.c_str(),markupProcessor);
987 markupProcessor.insert ("errorType", "SHIRE Processing Error");
988 markupProcessor.insert ("errorText", "Unexpected Exception");
989 markupProcessor.insert ("errorDesc", "An error occurred while processing your request.");
990 return WriteClientError(lpECB,shireError.c_str(),markupProcessor);
993 return HSE_STATUS_ERROR;