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) *bufptr=0; }
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 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",0,0);
429 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Filter Error</TITLE></HEAD><BODY>"
430 "<H1>Shibboleth Filter Error</H1>";
431 DWORD resplen=strlen(xmsg);
432 pfc->WriteClient(pfc,(LPVOID)xmsg,&resplen,0);
434 pfc->WriteClient(pfc,(LPVOID)msg,&resplen,0);
435 static const char* xmsg2="</BODY></HTML>";
436 resplen=strlen(xmsg2);
437 pfc->WriteClient(pfc,(LPVOID)xmsg2,&resplen,0);
438 return SF_STATUS_REQ_FINISHED;
441 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const char* filename, ShibMLP& mlp)
443 ifstream infile(filename);
445 return WriteClientError(pfc,"Unable to open error template, check settings.");
447 string res = mlp.run(infile);
448 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",0,0);
449 DWORD resplen=res.length();
450 pfc->WriteClient(pfc,(LPVOID)res.c_str(),&resplen,0);
451 return SF_STATUS_REQ_FINISHED;
454 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
456 // Is this a log notification?
457 if (notificationType==SF_NOTIFY_LOG)
459 if (pfc->pFilterContext)
460 ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=static_cast<LPCSTR>(pfc->pFilterContext);
461 return SF_STATUS_REQ_NEXT_NOTIFICATION;
464 PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
467 // Determine web site number. This can't really fail, I don't think.
470 GetServerVariable(pfc,"INSTANCE_ID",buf,10);
471 if ((site_id=strtoul(buf,NULL,10))==0)
472 return WriteClientError(pfc,"IIS site instance appears to be invalid.");
474 // Match site instance to site settings.
475 if (site_id>g_Sites.size() || g_Sites[site_id-1].m_name.length()==0)
476 return SF_STATUS_REQ_NEXT_NOTIFICATION;
477 settings_t& site=g_Sites[site_id-1];
479 string target_url=get_target(pfc,pn,site);
480 string shire_url=get_shire_location(site,target_url.c_str());
482 // If the user is accessing the SHIRE acceptance point, pass it on.
483 if (target_url.find(shire_url)!=string::npos)
484 return SF_STATUS_REQ_NEXT_NOTIFICATION;
486 // Get the url request and scan for the must-contain string.
487 if (!site.m_mustContain.empty())
489 char* upcased=new char[target_url.length()+1];
490 strcpy(upcased,target_url.c_str());
492 for (vector<string>::const_iterator index=site.m_mustContain.begin(); index!=site.m_mustContain.end(); index++)
493 if (strstr(upcased,index->c_str()))
496 if (index==site.m_mustContain.end())
497 return SF_STATUS_REQ_NEXT_NOTIFICATION;
500 // SSL content check.
501 ShibINI& ini=g_Config->getINI();
503 if (ini.get_tag(site.m_name,"contentSSLOnly",true,&tag) && ShibINI::boolean(tag) && !pfc->fIsSecurePort)
505 return WriteClientError(pfc,
506 "This server is configured to deny non-SSL requests for secure resources. "
507 "Try your request again using https instead of http.");
510 ostringstream threadid;
511 threadid << "[" << getpid() << "] shire" << '\0';
512 saml::NDC ndc(threadid.str().c_str());
514 // Set SHIRE policies.
516 config.checkIPAddress = (ini.get_tag(site.m_name,"checkIPAddress",true,&tag) && ShibINI::boolean(tag));
517 config.lifetime=config.timeout=0;
519 if (ini.get_tag(site.m_name, "authLifetime", true, &tag))
520 config.lifetime=strtoul(tag.c_str(),NULL,10);
522 if (ini.get_tag(site.m_name, "authTimeout", true, &tag))
523 config.timeout=strtoul(tag.c_str(),NULL,10);
525 // Pull the config data we need to handle the various possible conditions.
527 if (!ini.get_tag(site.m_name, "cookieName", true, &shib_cookie))
528 return WriteClientError(pfc,"The cookieName configuration setting is missing, check configuration.");
531 if (!ini.get_tag(site.m_name, "wayfURL", true, &wayfLocation))
532 return WriteClientError(pfc,"The wayfURL configuration setting is missing, check configuration.");
535 if (!ini.get_tag(site.m_name, "shireError", true, &shireError))
536 return WriteClientError(pfc,"The shireError configuration setting is missing, check configuration.");
539 if (!ini.get_tag(site.m_name, "accessError", true, &shireError))
540 return WriteClientError(pfc,"The accessError configuration setting is missing, check configuration.");
542 // Get an RPC handle and build the SHIRE object.
543 RPCHandle* rpc_handle = (RPCHandle*)rpc_handle_key->getData();
546 rpc_handle = new RPCHandle(shib_target_sockname(), SHIBRPC_PROG, SHIBRPC_VERS_1);
547 rpc_handle_key->setData(rpc_handle);
549 SHIRE shire(rpc_handle, config, shire_url);
551 // Check for authentication cookie.
552 const char* session_id=NULL;
553 GetHeader(pn,pfc,"Cookie:",buf,128,false);
554 if (buf.empty() || !(session_id=strstr(buf,shib_cookie.c_str())) || *(session_id+shib_cookie.length())!='=')
557 string wayf("Location: ");
558 wayf+=wayfLocation + "?shire=" + url_encode(shire_url.c_str()) + "&target=" + url_encode(target_url.c_str()) + "\r\n";
559 // Insert the headers.
560 pfc->AddResponseHeaders(pfc,const_cast<char*>(wayf.c_str()),0);
561 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"302 Please Wait",0,0);
562 return SF_STATUS_REQ_FINISHED;
565 session_id+=shib_cookie.length() + 1; /* Skip over the '=' */
566 char* cookieend=strchr(session_id,';');
568 *cookieend = '\0'; /* Ignore anyting after a ; */
570 // Make sure this session is still valid.
571 RPCError* status = NULL;
572 ShibMLP markupProcessor;
573 bool has_tag = ini.get_tag(site.m_name, "supportContact", true, &tag);
574 markupProcessor.insert("supportContact", has_tag ? tag : "");
575 has_tag = ini.get_tag(site.m_name, "logoLocation", true, &tag);
576 markupProcessor.insert("logoLocation", has_tag ? tag : "");
577 markupProcessor.insert("requestURL", target_url);
579 GetServerVariable(pfc,"REMOTE_ADDR",buf,16);
581 status = shire.sessionIsValid(session_id, buf, target_url.c_str());
583 catch (ShibTargetException &e) {
584 markupProcessor.insert("errorType", "SHIRE Processing Error");
585 markupProcessor.insert("errorText", e.what());
586 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
587 return WriteClientError(pfc, shireError.c_str(), markupProcessor);
590 markupProcessor.insert("errorType", "SHIRE Processing Error");
591 markupProcessor.insert("errorText", "Unexpected Exception");
592 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
593 return WriteClientError(pfc, shireError.c_str(), markupProcessor);
597 if (status->isError()) {
598 if (status->isRetryable()) {
601 string wayf("Location: ");
602 wayf+=wayfLocation + "?shire=" + url_encode(shire_url.c_str()) + "&target=" + url_encode(target_url.c_str()) + "\r\n";
603 // Insert the headers.
604 pfc->AddResponseHeaders(pfc,const_cast<char*>(wayf.c_str()),0);
605 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"302 Please Wait",0,0);
606 return SF_STATUS_REQ_FINISHED;
609 // return the error page to the user
610 markupProcessor.insert(*status);
612 return WriteClientError(pfc, shireError.c_str(), markupProcessor);
619 rm_config.checkIPAddress = config.checkIPAddress;
620 RM rm(rpc_handle,rm_config);
622 // Get the attributes.
623 vector<SAMLAssertion*> assertions;
624 SAMLAuthenticationStatement* sso_statement=NULL;
625 status = rm.getAssertions(session_id, buf, target_url.c_str(), assertions, &sso_statement);
627 if (status->isError()) {
629 if (!ini.get_tag(site.m_name, "rmError", true, &shireError))
630 return WriteClientError(pfc,"The rmError configuration setting is missing, check configuration.");
632 markupProcessor.insert(*status);
634 return WriteClientError(pfc, rmError.c_str(), markupProcessor);
638 // Only allow a single assertion...
639 if (assertions.size() > 1) {
640 for (int k = 0; k < assertions.size(); k++)
641 delete assertions[k];
642 delete sso_statement;
643 return WriteClientError(pfc, accessError.c_str(), markupProcessor);
646 // Get the AAP providers, which contain the attribute policy info.
647 Iterator<IAAP*> provs=ShibConfig::getConfig().getAAPProviders();
649 // Clear out the list of mapped attributes
650 while (provs.hasNext())
652 IAAP* aap=provs.next();
656 Iterator<const IAttributeRule*> rules=aap->getAttributeRules();
657 while (rules.hasNext())
659 const char* header=rules.next()->getHeader();
661 pn->SetHeader(pfc,const_cast<char*>(header),"");
667 for (int k = 0; k < assertions.size(); k++)
668 delete assertions[k];
669 delete sso_statement;
676 // Clear relevant headers.
677 pn->SetHeader(pfc,"remote-user:","");
678 pn->SetHeader(pfc,"Shib-Attributes:","");
679 pn->SetHeader(pfc,"Shib-Origin-Site:","");
680 pn->SetHeader(pfc,"Shib-Authentication-Method:","");
682 // Maybe export the assertion.
683 if (ini.get_tag(site.m_name,"exportAssertion",true,&tag) && ShibINI::boolean(tag))
686 RM::serialize(*(assertions[0]), assertion);
687 string::size_type lfeed;
688 while ((lfeed=assertion.find('\n'))!=string::npos)
689 assertion.erase(lfeed,1);
690 pn->SetHeader(pfc,"Shib-Attributes:",const_cast<char*>(assertion.c_str()));
695 auto_ptr<char> os(XMLString::transcode(sso_statement->getSubject()->getNameQualifier()));
696 auto_ptr<char> am(XMLString::transcode(sso_statement->getAuthMethod()));
697 pn->SetHeader(pfc,"Shib-Origin-Site:", os.get());
698 pn->SetHeader(pfc,"Shib-Authentication-Method:", am.get());
701 // Export the attributes. Only supports a single statement.
702 Iterator<SAMLAttribute*> j = assertions.size()==1 ? RM::getAttributes(*(assertions[0])) : EMPTY(SAMLAttribute*);
705 SAMLAttribute* attr=j.next();
707 // Are we supposed to export it?
708 const char* hname=NULL;
709 AAP wrapper(attr->getName(),attr->getNamespace());
711 hname=wrapper->getHeader();
714 Iterator<string> vals=attr->getSingleByteValues();
715 if (!strcmp(hname,"REMOTE_USER") && vals.hasNext())
717 char* principal=const_cast<char*>(vals.next().c_str());
718 pn->SetHeader(pfc,"remote-user:",principal);
719 pfc->pFilterContext=pfc->AllocMem(pfc,strlen(principal)+1,0);
720 if (pfc->pFilterContext)
721 strcpy(static_cast<char*>(pfc->pFilterContext),principal);
726 for (int it = 0; vals.hasNext(); it++) {
727 string value = vals.next();
728 for (string::size_type pos = value.find_first_of(";", string::size_type(0)); pos != string::npos; pos = value.find_first_of(";", pos)) {
729 value.insert(pos, "\\");
735 header=header + ';' + value;
737 string hname2=string(hname) + ':';
738 pn->SetHeader(pfc,const_cast<char*>(hname2.c_str()),const_cast<char*>(header.c_str()));
744 for (int k = 0; k < assertions.size(); k++)
745 delete assertions[k];
746 delete sso_statement;
748 return SF_STATUS_REQ_NEXT_NOTIFICATION;
752 return WriteClientError(pfc,"Out of Memory");
756 if (e==ERROR_NO_DATA)
757 return WriteClientError(pfc,"A required variable or header was empty.");
759 return WriteClientError(pfc,"Server detected unexpected IIS error.");
763 return WriteClientError(pfc,"Server caught an unknown exception.");
766 return WriteClientError(pfc,"Server reached unreachable code!");
769 string get_target(LPEXTENSION_CONTROL_BLOCK lpECB, settings_t& site)
773 GetServerVariable(lpECB,"HTTPS",buf);
774 bool SSL=(buf=="on");
780 // We use the "normalizeRequest" tag to decide how to obtain the server's name.
782 if (g_Config->getINI().get_tag(site.m_name,"normalizeRequest",true,&tag) && ShibINI::boolean(tag))
788 GetServerVariable(lpECB,"SERVER_NAME",buf);
792 GetServerVariable(lpECB,"SERVER_PORT",buf,10);
793 if (buf!=(SSL ? "443" : "80"))
794 s=s + ':' + static_cast<char*>(buf);
796 GetServerVariable(lpECB,"URL",buf,255);
802 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* msg)
804 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
805 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,0);
806 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Error</TITLE></HEAD><BODY><H1>Shibboleth Error</H1>";
807 DWORD resplen=strlen(xmsg);
808 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg,&resplen,HSE_IO_SYNC);
810 lpECB->WriteClient(lpECB->ConnID,(LPVOID)msg,&resplen,HSE_IO_SYNC);
811 static const char* xmsg2="</BODY></HTML>";
812 resplen=strlen(xmsg2);
813 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg2,&resplen,HSE_IO_SYNC);
814 return HSE_STATUS_SUCCESS;
817 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* filename, ShibMLP& mlp)
819 ifstream infile(filename);
821 return WriteClientError(lpECB,"Unable to open error template, check settings.");
823 string res = mlp.run(infile);
824 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,0);
825 DWORD resplen=res.length();
826 lpECB->WriteClient(lpECB->ConnID,(LPVOID)res.c_str(),&resplen,0);
827 return HSE_STATUS_SUCCESS;
830 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
832 ostringstream threadid;
833 threadid << "[" << getpid() << "] shire" << '\0';
834 saml::NDC ndc(threadid.str().c_str());
836 ShibINI& ini = g_Config->getINI();
838 ShibMLP markupProcessor;
842 // Determine web site number. This can't really fail, I don't think.
845 GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
846 if ((site_id=strtoul(buf,NULL,10))==0)
847 return WriteClientError(lpECB,"IIS site instance appears to be invalid.");
849 // Match site instance to site settings.
850 if (site_id>g_Sites.size() || g_Sites[site_id-1].m_name.length()==0)
851 return WriteClientError(lpECB,"Shibboleth filter not configured for this web site.");
852 settings_t& site=g_Sites[site_id-1];
854 if (!ini.get_tag(site.m_name, "shireError", true, &shireError))
855 return WriteClientError(lpECB,"The shireError configuration setting is missing, check configuration.");
857 string target_url=get_target(lpECB,site);
858 string shire_url = get_shire_location(site,target_url.c_str());
860 // Set SHIRE policies.
863 config.checkIPAddress = (ini.get_tag(site.m_name,"checkIPAddress",true,&tag) && ShibINI::boolean(tag));
864 config.lifetime=config.timeout=0;
866 if (ini.get_tag(site.m_name, "authLifetime", true, &tag))
867 config.lifetime=strtoul(tag.c_str(),NULL,10);
869 if (ini.get_tag(site.m_name, "authTimeout", true, &tag))
870 config.timeout=strtoul(tag.c_str(),NULL,10);
872 // Pull the config data we need to handle the various possible conditions.
874 if (!ini.get_tag(site.m_name, "cookieName", true, &shib_cookie))
875 return WriteClientError(lpECB,"The cookieName configuration setting is missing, check configuration.");
878 if (!ini.get_tag(site.m_name, "wayfURL", true, &wayfLocation))
879 return WriteClientError(lpECB,"The wayfURL configuration setting is missing, check configuration.");
881 bool has_tag = ini.get_tag(site.m_name, "supportContact", true, &tag);
882 markupProcessor.insert("supportContact", has_tag ? tag : "");
883 has_tag = ini.get_tag(site.m_name, "logoLocation", true, &tag);
884 markupProcessor.insert("logoLocation", has_tag ? tag : "");
885 markupProcessor.insert("requestURL", target_url.c_str());
887 // Get an RPC handle and build the SHIRE object.
888 RPCHandle* rpc_handle = (RPCHandle*)rpc_handle_key->getData();
891 rpc_handle = new RPCHandle(shib_target_sockname(), SHIBRPC_PROG, SHIBRPC_VERS_1);
892 rpc_handle_key->setData(rpc_handle);
894 SHIRE shire(rpc_handle, config, shire_url.c_str());
896 // Process SHIRE POST
897 if (ini.get_tag(site.m_name, "shireSSLOnly", true, &tag) && ShibINI::boolean(tag))
899 // Make sure this is SSL, if it should be.
900 GetServerVariable(lpECB,"HTTPS",buf,10);
902 throw ShibTargetException(SHIBRPC_OK,"blocked non-SSL access to SHIRE POST processor");
905 // Make sure this is a POST
906 if (stricmp(lpECB->lpszMethod,"POST"))
907 throw ShibTargetException(SHIBRPC_OK,"blocked non-POST to SHIRE POST processor");
909 // Sure sure this POST is an appropriate content type
910 if (!lpECB->lpszContentType || stricmp(lpECB->lpszContentType,"application/x-www-form-urlencoded"))
911 throw ShibTargetException(SHIBRPC_OK,"blocked bad content-type to SHIRE POST processor");
913 // Make sure the "bytes sent" is a reasonable number and that we have all of it.
914 if (lpECB->cbTotalBytes > 1024*1024) // 1MB?
915 throw ShibTargetException (SHIBRPC_OK,"blocked too-large a post to SHIRE POST processor");
916 else if (lpECB->cbTotalBytes>lpECB->cbAvailable)
917 throw ShibTargetException (SHIBRPC_OK,"blocked incomplete post to SHIRE POST processor");
919 // Parse the incoming data.
920 HQUERY params=ParseQuery(lpECB);
922 throw ShibTargetException (SHIBRPC_OK,"unable to parse form data");
924 // Make sure the TARGET parameter exists
925 const char* target = QueryValue(params,"TARGET");
926 if (!target || *target == '\0')
927 throw ShibTargetException(SHIBRPC_OK,"SHIRE POST failed to find TARGET parameter");
929 // Make sure the SAMLResponse parameter exists
930 const char* post = QueryValue(params,"SAMLResponse");
931 if (!post || *post == '\0')
932 throw ShibTargetException (SHIBRPC_OK,"SHIRE POST failed to find SAMLResponse parameter");
934 GetServerVariable(lpECB,"REMOTE_ADDR",buf,16);
938 RPCError* status = shire.sessionCreate(post,buf,cookie);
940 if (status->isError()) {
941 if (status->isRetryable()) {
943 string wayf=wayfLocation + "?shire=" + url_encode(shire_url.c_str()) + "&target=" + url_encode(target);
944 DWORD len=wayf.length();
945 if (lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_URL_REDIRECT_RESP,(LPVOID)wayf.c_str(),&len,0))
946 return HSE_STATUS_SUCCESS;
947 return HSE_STATUS_ERROR;
950 // Return this error to the user.
951 markupProcessor.insert(*status);
953 return WriteClientError(lpECB,shireError.c_str(),markupProcessor);
957 // We've got a good session, set the cookie and redirect to target.
958 shib_cookie = "Set-Cookie: " + shib_cookie + '=' + cookie + "; path=/\r\n"
959 "Location: " + target + "\r\n"
960 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
961 "Cache-Control: private,no-store,no-cache\r\n"
962 "Connection: close\r\n";
963 HSE_SEND_HEADER_EX_INFO hinfo;
964 hinfo.pszStatus="302 Moved";
965 hinfo.pszHeader=shib_cookie.c_str();
967 hinfo.cchHeader=shib_cookie.length();
968 hinfo.fKeepConn=FALSE;
969 if (lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER_EX,&hinfo,0,0))
970 return HSE_STATUS_SUCCESS;
972 catch (ShibTargetException &e) {
973 markupProcessor.insert ("errorType", "SHIRE Processing Error");
974 markupProcessor.insert ("errorText", e.what());
975 markupProcessor.insert ("errorDesc", "An error occurred while processing your request.");
976 return WriteClientError(lpECB,shireError.c_str(),markupProcessor);
979 markupProcessor.insert ("errorType", "SHIRE Processing Error");
980 markupProcessor.insert ("errorText", "Unexpected Exception");
981 markupProcessor.insert ("errorDesc", "An error occurred while processing your request.");
982 return WriteClientError(lpECB,shireError.c_str(),markupProcessor);
985 return HSE_STATUS_ERROR;