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 ThreadKey* rpc_handle_key = NULL;
84 ShibTargetConfig* g_Config = NULL;
85 vector<string> g_Sites;
88 void destroy_handle(void* data)
90 delete (RPCHandle*)data;
94 LPCSTR lpUNCServerName,
100 LPCSTR messages[] = {message, NULL};
102 HANDLE hElog = RegisterEventSource(lpUNCServerName, "Shibboleth ISAPI Filter");
103 BOOL res = ReportEvent(hElog, wType, 0, dwEventID, lpUserSid, 1, 0, messages, NULL);
104 return (DeregisterEventSource(hElog) && res);
107 extern "C" __declspec(dllexport) BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID)
109 if (fdwReason==DLL_PROCESS_ATTACH)
114 extern "C" BOOL WINAPI GetExtensionVersion(HSE_VERSION_INFO* pVer)
121 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
122 "Extension mode startup not possible, is the DLL loaded as a filter?");
126 pVer->dwExtensionVersion=HSE_VERSION;
127 strncpy(pVer->lpszExtensionDesc,"Shibboleth ISAPI Extension",HSE_MAX_EXT_DLL_NAME_LEN-1);
131 extern "C" BOOL WINAPI GetFilterVersion(PHTTP_FILTER_VERSION pVer)
138 ShibTargetConfig::preinit();
139 g_Config = &(ShibTargetConfig::init(SHIBTARGET_SHIRE, getenv("SHIBCONFIG")));
140 ShibINI& ini = g_Config->getINI();
142 // Create the RPC Handle TLS key.
143 rpc_handle_key=ThreadKey::create(destroy_handle);
145 Category& log=Category::getInstance("isapi_shib.GetFilterVersion");
147 // Read site-specific settings for each instance ID we can find.
150 sprintf(iid,"%u",i++);
152 while (ini.get_tag("isapi",iid,false,&hostname))
154 log.info("configuring for site ID (%d), hostname (%s)",i-1,hostname.empty() ? "null" : hostname.c_str());
156 // If no section exists for the host, mark it as a "skip" site.
157 if (hostname == "skip")
159 log.info("skipping site ID (%d)",i-1);
163 g_Sites.push_back(hostname);
164 sprintf(iid,"%u",i++);
168 catch (SAMLException&)
170 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
171 "Filter startup failed with SAML exception, check shire log for help.");
174 catch (runtime_error& e)
176 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
181 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Filter startup failed with unexpected exception.");
185 pVer->dwFilterVersion=HTTP_FILTER_REVISION;
186 strncpy(pVer->lpszFilterDesc,"Shibboleth ISAPI Filter",SF_MAX_FILTER_DESC_LEN);
187 pVer->dwFlags=(SF_NOTIFY_ORDER_HIGH |
188 SF_NOTIFY_SECURE_PORT |
189 SF_NOTIFY_NONSECURE_PORT |
190 SF_NOTIFY_PREPROC_HEADERS |
192 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter initialized...");
196 extern "C" BOOL WINAPI TerminateExtension(DWORD)
198 return TRUE; // cleanup should happen when filter unloads
201 extern "C" BOOL WINAPI TerminateFilter(DWORD)
203 delete rpc_handle_key;
205 g_Config->shutdown();
207 LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter shut down...");
211 /* Next up, some suck-free versions of various APIs.
213 You DON'T require people to guess the buffer size and THEN tell them the right size.
214 Returning an LPCSTR is apparently way beyond their ken. Not to mention the fact that
215 constant strings aren't typed as such, making it just that much harder. These versions
216 are now updated to use a special growable buffer object, modeled after the standard
217 string class. The standard string won't work because they left out the option to
218 pre-allocate a non-constant buffer.
224 dynabuf() { bufptr=NULL; buflen=0; }
225 dynabuf(size_t s) { bufptr=new char[buflen=s]; *bufptr=0; }
226 ~dynabuf() { delete[] bufptr; }
227 size_t length() const { return bufptr ? strlen(bufptr) : 0; }
228 size_t size() const { return buflen; }
229 bool empty() const { return length()==0; }
230 void reserve(size_t s, bool keep=false);
231 void erase() { if (bufptr) memset(bufptr,0,buflen); }
232 operator char*() { return bufptr; }
233 bool operator ==(const char* s) const;
234 bool operator !=(const char* s) const { return !(*this==s); }
240 void dynabuf::reserve(size_t s, bool keep)
247 p[buflen]=bufptr[buflen];
253 bool dynabuf::operator==(const char* s) const
255 if (buflen==NULL || s==NULL)
256 return (buflen==NULL && s==NULL);
258 return strcmp(bufptr,s)==0;
261 void GetServerVariable(PHTTP_FILTER_CONTEXT pfc, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
262 throw (bad_alloc, DWORD)
268 while (!pfc->GetServerVariable(pfc,lpszVariable,s,&size))
270 // Grumble. Check the error.
271 DWORD e=GetLastError();
272 if (e==ERROR_INSUFFICIENT_BUFFER)
277 if (bRequired && s.empty())
281 void GetServerVariable(LPEXTENSION_CONTROL_BLOCK lpECB, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
282 throw (bad_alloc, DWORD)
288 while (lpECB->GetServerVariable(lpECB->ConnID,lpszVariable,s,&size))
290 // Grumble. Check the error.
291 DWORD e=GetLastError();
292 if (e==ERROR_INSUFFICIENT_BUFFER)
297 if (bRequired && s.empty())
301 void GetHeader(PHTTP_FILTER_PREPROC_HEADERS pn, PHTTP_FILTER_CONTEXT pfc,
302 LPSTR lpszName, dynabuf& s, DWORD size=80, bool bRequired=true)
303 throw (bad_alloc, DWORD)
309 while (!pn->GetHeader(pfc,lpszName,s,&size))
311 // Grumble. Check the error.
312 DWORD e=GetLastError();
313 if (e==ERROR_INSUFFICIENT_BUFFER)
318 if (bRequired && s.empty())
322 inline char hexchar(unsigned short s)
324 return (s<=9) ? ('0' + s) : ('A' + s - 10);
327 string url_encode(const char* url) throw (bad_alloc)
329 static char badchars[]="\"\\+<>#%{}|^~[]`;/?:@=&";
331 for (const char* pch=url; *pch; pch++)
333 if (strchr(badchars,*pch)!=NULL || *pch<=0x1F || *pch>=0x7F)
334 s=s + '%' + hexchar(*pch >> 4) + hexchar(*pch & 0x0F);
341 void get_target_and_appid(
342 PHTTP_FILTER_CONTEXT pfc, PHTTP_FILTER_PREPROC_HEADERS pn, const char* hostname, string& target, string& appid
347 GetServerVariable(pfc,"SERVER_PORT",port,10);
348 GetHeader(pn,pfc,"url",url,256,false);
350 // First get the appid using the normalized hostname.
351 ApplicationMapper mapper;
352 appid = mapper->getApplicationFromParsedURL((pfc->fIsSecurePort ? "https" : "http"), hostname, atoi(port), url);
354 target=static_cast<char*>(url);
355 if (port!=(pfc->fIsSecurePort ? "443" : "80"))
356 target = ':' + static_cast<char*>(port) + target;
358 // For the target, we use the "normalizeRequest" tag to decide how to set the server's name.
360 if (g_Config->getINI().get_tag(appid,"normalizeRequest",true,&tag) && ShibINI::boolean(tag))
362 target=string(pfc->fIsSecurePort ? "https://" : "http://") + hostname + target;
366 GetServerVariable(pfc,"SERVER_NAME",url);
367 target=string(pfc->fIsSecurePort ? "https://" : "http://") + static_cast<char*>(url) + target;
371 string get_shire_location(const char* application_id, const char* target)
374 if (g_Config->getINI().get_tag(application_id,"shireURL",true,&shireURL) && !shireURL.empty())
376 if (shireURL[0]!='/')
378 const char* colon=strchr(target,':');
379 const char* slash=strchr(colon+3,'/');
380 string s(target,slash-target);
387 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const char* msg)
389 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
390 static const char* ctype="Content-Type: text/html\r\n";
391 pfc->AddResponseHeaders(pfc,const_cast<char*>(ctype),0);
392 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",0,0);
393 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Filter Error</TITLE></HEAD><BODY>"
394 "<H1>Shibboleth Filter Error</H1>";
395 DWORD resplen=strlen(xmsg);
396 pfc->WriteClient(pfc,(LPVOID)xmsg,&resplen,0);
398 pfc->WriteClient(pfc,(LPVOID)msg,&resplen,0);
399 static const char* xmsg2="</BODY></HTML>";
400 resplen=strlen(xmsg2);
401 pfc->WriteClient(pfc,(LPVOID)xmsg2,&resplen,0);
402 return SF_STATUS_REQ_FINISHED;
405 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const char* filename, ShibMLP& mlp)
407 ifstream infile(filename);
409 return WriteClientError(pfc,"Unable to open error template, check settings.");
410 string res = mlp.run(infile);
412 static const char* ctype="Content-Type: text/html\r\n";
413 pfc->AddResponseHeaders(pfc,const_cast<char*>(ctype),0);
414 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",0,0);
415 DWORD resplen=res.length();
416 pfc->WriteClient(pfc,(LPVOID)res.c_str(),&resplen,0);
417 return SF_STATUS_REQ_FINISHED;
420 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
422 // Is this a log notification?
423 if (notificationType==SF_NOTIFY_LOG)
425 if (pfc->pFilterContext)
426 ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=static_cast<LPCSTR>(pfc->pFilterContext);
427 return SF_STATUS_REQ_NEXT_NOTIFICATION;
430 PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
433 // Determine web site number. This can't really fail, I don't think.
436 GetServerVariable(pfc,"INSTANCE_ID",buf,10);
437 if ((site_id=strtoul(buf,NULL,10))==0)
438 return WriteClientError(pfc,"IIS site instance appears to be invalid.");
440 // Match site instance to site settings.
441 if (site_id>g_Sites.size() || g_Sites[site_id-1].length()==0)
442 return SF_STATUS_REQ_NEXT_NOTIFICATION;
443 string& site=g_Sites[site_id-1];
445 string application_id;
447 get_target_and_appid(pfc,pn,site.c_str(),target_url,application_id);
448 string shire_url=get_shire_location(application_id.c_str(),target_url.c_str());
450 // If the user is accessing the SHIRE acceptance point, pass it on.
451 if (target_url.find(shire_url)!=string::npos)
452 return SF_STATUS_REQ_NEXT_NOTIFICATION;
454 // Now check the policy for this application.
456 ShibINI& ini=g_Config->getINI();
457 if (!ini.get_tag(application_id,"requireSession",true,&tag) || !ShibINI::boolean(tag))
458 return SF_STATUS_REQ_NEXT_NOTIFICATION;
460 // SSL content check.
461 if (ini.get_tag(application_id,"contentSSLOnly",true,&tag) && ShibINI::boolean(tag) && !pfc->fIsSecurePort)
463 return WriteClientError(pfc,
464 "This server is configured to deny non-SSL requests for secure resources. "
465 "Try your request again using https instead of http.");
468 ostringstream threadid;
469 threadid << "[" << getpid() << "] shire" << '\0';
470 saml::NDC ndc(threadid.str().c_str());
472 // Set SHIRE policies.
474 config.checkIPAddress = (ini.get_tag(application_id,"checkIPAddress",true,&tag) && ShibINI::boolean(tag));
475 config.lifetime=config.timeout=0;
477 if (ini.get_tag(application_id, "authLifetime", true, &tag))
478 config.lifetime=strtoul(tag.c_str(),NULL,10);
480 if (ini.get_tag(application_id, "authTimeout", true, &tag))
481 config.timeout=strtoul(tag.c_str(),NULL,10);
483 // Pull the config data we need to handle the various possible conditions.
485 if (!ini.get_tag(application_id, "cookieName", true, &shib_cookie))
486 return WriteClientError(pfc,"The cookieName configuration setting is missing, check configuration.");
489 if (!ini.get_tag(application_id, "wayfURL", true, &wayfLocation))
490 return WriteClientError(pfc,"The wayfURL configuration setting is missing, check configuration.");
493 if (!ini.get_tag(application_id, "shireError", true, &shireError))
494 return WriteClientError(pfc,"The shireError configuration setting is missing, check configuration.");
497 if (!ini.get_tag(application_id, "accessError", true, &shireError))
498 return WriteClientError(pfc,"The accessError configuration setting is missing, check configuration.");
500 // Get an RPC handle and build the SHIRE object.
501 RPCHandle* rpc_handle = (RPCHandle*)rpc_handle_key->getData();
504 rpc_handle = new RPCHandle(shib_target_sockname(), SHIBRPC_PROG, SHIBRPC_VERS_1);
505 rpc_handle_key->setData(rpc_handle);
507 SHIRE shire(rpc_handle, config, shire_url.c_str());
509 // Check for authentication cookie.
510 const char* session_id=NULL;
511 GetHeader(pn,pfc,"Cookie:",buf,128,false);
512 Category::getInstance("isapi_shib.HttpFilterProc").debug("cookie header is {%s}",(const char*)buf);
513 if (buf.empty() || !(session_id=strstr(buf,shib_cookie.c_str())) || *(session_id+shib_cookie.length())!='=')
516 string wayf("Location: ");
517 wayf+=wayfLocation + "?shire=" + url_encode(shire_url.c_str()) + "&target=" + url_encode(target_url.c_str()) + "\r\n";
518 // Insert the headers.
519 pfc->AddResponseHeaders(pfc,const_cast<char*>(wayf.c_str()),0);
520 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"302 Please Wait",0,0);
521 return SF_STATUS_REQ_FINISHED;
524 session_id+=shib_cookie.length() + 1; /* Skip over the '=' */
525 char* cookieend=strchr(session_id,';');
527 *cookieend = '\0'; /* Ignore anyting after a ; */
529 // Make sure this session is still valid.
530 RPCError* status = NULL;
531 ShibMLP markupProcessor;
532 bool has_tag = ini.get_tag(application_id, "supportContact", true, &tag);
533 markupProcessor.insert("supportContact", has_tag ? tag : "");
534 has_tag = ini.get_tag(application_id, "logoLocation", true, &tag);
535 markupProcessor.insert("logoLocation", has_tag ? tag : "");
536 markupProcessor.insert("requestURL", target_url);
539 GetServerVariable(pfc,"REMOTE_ADDR",abuf,16);
541 status = shire.sessionIsValid(session_id, abuf, application_id.c_str());
543 catch (ShibTargetException &e) {
544 markupProcessor.insert("errorType", "SHIRE Processing Error");
545 markupProcessor.insert("errorText", e.what());
546 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
547 return WriteClientError(pfc, shireError.c_str(), markupProcessor);
550 markupProcessor.insert("errorType", "SHIRE Processing Error");
551 markupProcessor.insert("errorText", "Unexpected Exception");
552 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
553 return WriteClientError(pfc, shireError.c_str(), markupProcessor);
557 if (status->isError()) {
558 if (status->isRetryable()) {
561 string wayf("Location: ");
562 wayf+=wayfLocation + "?shire=" + url_encode(shire_url.c_str()) + "&target=" + url_encode(target_url.c_str()) + "\r\n";
563 // Insert the headers.
564 pfc->AddResponseHeaders(pfc,const_cast<char*>(wayf.c_str()),0);
565 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"302 Please Wait",0,0);
566 return SF_STATUS_REQ_FINISHED;
569 // return the error page to the user
570 markupProcessor.insert(*status);
572 return WriteClientError(pfc, shireError.c_str(), markupProcessor);
579 rm_config.checkIPAddress = config.checkIPAddress;
580 RM rm(rpc_handle,rm_config);
582 // Get the attributes.
583 vector<SAMLAssertion*> assertions;
584 SAMLAuthenticationStatement* sso_statement=NULL;
585 status = rm.getAssertions(session_id, buf, application_id.c_str(), assertions, &sso_statement);
587 if (status->isError()) {
589 if (!ini.get_tag(application_id, "rmError", true, &shireError))
590 return WriteClientError(pfc,"The rmError configuration setting is missing, check configuration.");
592 markupProcessor.insert(*status);
594 return WriteClientError(pfc, rmError.c_str(), markupProcessor);
598 // Only allow a single assertion...
599 if (assertions.size() > 1) {
600 for (int k = 0; k < assertions.size(); k++)
601 delete assertions[k];
602 delete sso_statement;
603 return WriteClientError(pfc, accessError.c_str(), markupProcessor);
606 // Get the AAP providers, which contain the attribute policy info.
607 Iterator<IAAP*> provs=g_Config->getAAPProviders();
609 // Clear out the list of mapped attributes
610 while (provs.hasNext())
612 IAAP* aap=provs.next();
616 Iterator<const IAttributeRule*> rules=aap->getAttributeRules();
617 while (rules.hasNext())
619 const char* header=rules.next()->getHeader();
621 pn->SetHeader(pfc,const_cast<char*>(header),"");
627 for (int k = 0; k < assertions.size(); k++)
628 delete assertions[k];
629 delete sso_statement;
636 // Clear relevant headers.
637 pn->SetHeader(pfc,"remote-user:","");
638 pn->SetHeader(pfc,"Shib-Attributes:","");
639 pn->SetHeader(pfc,"Shib-Origin-Site:","");
640 pn->SetHeader(pfc,"Shib-Authentication-Method:","");
642 pn->SetHeader(pfc,"Shib-Application-ID:","");
643 pn->SetHeader(pfc,"Shib-Application-ID:",const_cast<char*>(application_id.c_str()));
645 // Maybe export the assertion.
646 if (ini.get_tag(application_id,"exportAssertion",true,&tag) && ShibINI::boolean(tag))
649 RM::serialize(*(assertions[0]), assertion);
650 string::size_type lfeed;
651 while ((lfeed=assertion.find('\n'))!=string::npos)
652 assertion.erase(lfeed,1);
653 pn->SetHeader(pfc,"Shib-Attributes:",const_cast<char*>(assertion.c_str()));
658 auto_ptr_char os(sso_statement->getSubject()->getNameQualifier());
659 auto_ptr_char am(sso_statement->getAuthMethod());
660 pn->SetHeader(pfc,"Shib-Origin-Site:", const_cast<char*>(os.get()));
661 pn->SetHeader(pfc,"Shib-Authentication-Method:", const_cast<char*>(am.get()));
664 // Export the attributes.
665 Iterator<SAMLAssertion*> a_iter(assertions);
666 while (a_iter.hasNext()) {
667 SAMLAssertion* assert=a_iter.next();
668 Iterator<SAMLStatement*> statements=assert->getStatements();
669 while (statements.hasNext()) {
670 SAMLAttributeStatement* astate=dynamic_cast<SAMLAttributeStatement*>(statements.next());
673 Iterator<SAMLAttribute*> attrs=astate->getAttributes();
674 while (attrs.hasNext()) {
675 SAMLAttribute* attr=attrs.next();
677 // Are we supposed to export it?
678 AAP wrapper(g_Config->getAAPProviders(),attr->getName(),attr->getNamespace());
682 Iterator<string> vals=attr->getSingleByteValues();
683 if (!strcmp(wrapper->getHeader(),"REMOTE_USER") && vals.hasNext()) {
684 char* principal=const_cast<char*>(vals.next().c_str());
685 pn->SetHeader(pfc,"remote-user:",principal);
686 pfc->pFilterContext=pfc->AllocMem(pfc,strlen(principal)+1,0);
687 if (pfc->pFilterContext)
688 strcpy(static_cast<char*>(pfc->pFilterContext),principal);
692 for (int it = 0; vals.hasNext(); it++) {
693 string value = vals.next();
694 for (string::size_type pos = value.find_first_of(";", string::size_type(0)); pos != string::npos; pos = value.find_first_of(";", pos)) {
695 value.insert(pos, "\\");
701 header=header + ';' + value;
703 string hname2=string(wrapper->getHeader()) + ':';
704 pn->SetHeader(pfc,const_cast<char*>(hname2.c_str()),const_cast<char*>(header.c_str()));
711 for (int k = 0; k < assertions.size(); k++)
712 delete assertions[k];
713 delete sso_statement;
715 return SF_STATUS_REQ_NEXT_NOTIFICATION;
719 return WriteClientError(pfc,"Out of Memory");
723 if (e==ERROR_NO_DATA)
724 return WriteClientError(pfc,"A required variable or header was empty.");
726 return WriteClientError(pfc,"Server detected unexpected IIS error.");
730 return WriteClientError(pfc,"Server caught an unknown exception.");
733 return WriteClientError(pfc,"Server reached unreachable code!");
736 void get_target_and_appid(LPEXTENSION_CONTROL_BLOCK lpECB, const char* hostname, string& target, string& appid)
741 GetServerVariable(lpECB,"HTTPS",ssl,5);
742 GetServerVariable(lpECB,"SERVER_PORT",port,10);
743 GetServerVariable(lpECB,"URL",url,255);
744 bool SSL=(ssl=="on");
746 // First get the appid using the normalized hostname.
747 ApplicationMapper mapper;
748 appid = mapper->getApplicationFromParsedURL((SSL ? "https" : "http"), hostname, atoi(port), url);
750 target=static_cast<char*>(url);
751 if (port!=(SSL ? "443" : "80"))
752 target = ':' + static_cast<char*>(port) + target;
754 // For the target, we use the "normalizeRequest" tag to decide how to set the server's name.
756 if (g_Config->getINI().get_tag(appid,"normalizeRequest",true,&tag) && ShibINI::boolean(tag))
758 target=string(SSL ? "https://" : "http://") + hostname + target;
762 GetServerVariable(lpECB,"SERVER_NAME",url);
763 target=string(SSL ? "https://" : "http://") + static_cast<char*>(url) + target;
767 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* msg)
769 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
770 static const char* ctype="Content-Type: text/html\r\n";
771 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
772 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Error</TITLE></HEAD><BODY><H1>Shibboleth Error</H1>";
773 DWORD resplen=strlen(xmsg);
774 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg,&resplen,HSE_IO_SYNC);
776 lpECB->WriteClient(lpECB->ConnID,(LPVOID)msg,&resplen,HSE_IO_SYNC);
777 static const char* xmsg2="</BODY></HTML>";
778 resplen=strlen(xmsg2);
779 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg2,&resplen,HSE_IO_SYNC);
780 return HSE_STATUS_SUCCESS;
783 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* filename, ShibMLP& mlp)
785 ifstream infile(filename);
787 return WriteClientError(lpECB,"Unable to open error template, check settings.");
789 string res = mlp.run(infile);
790 static const char* ctype="Content-Type: text/html\r\n";
791 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
792 DWORD resplen=res.length();
793 lpECB->WriteClient(lpECB->ConnID,(LPVOID)res.c_str(),&resplen,0);
794 return HSE_STATUS_SUCCESS;
797 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
799 ostringstream threadid;
800 threadid << "[" << getpid() << "] shire" << '\0';
801 saml::NDC ndc(threadid.str().c_str());
803 ShibINI& ini = g_Config->getINI();
805 ShibMLP markupProcessor;
809 // Determine web site number. This can't really fail, I don't think.
812 GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
813 if ((site_id=strtoul(buf,NULL,10))==0)
814 return WriteClientError(lpECB,"IIS site instance appears to be invalid.");
816 // Match site instance to site settings.
817 if (site_id>g_Sites.size() || g_Sites[site_id-1].length()==0)
818 return WriteClientError(lpECB,"Shibboleth filter not configured for this web site.");
819 string& site=g_Sites[site_id-1];
821 string target_url,application_id;
822 get_target_and_appid(lpECB,site.c_str(),target_url,application_id);
824 if (!ini.get_tag(application_id, "shireError", true, &shireError))
825 return WriteClientError(lpECB,"The shireError configuration setting is missing, check configuration.");
827 string shire_url = target_url;
829 // Set SHIRE policies.
832 config.checkIPAddress = (ini.get_tag(application_id,"checkIPAddress",true,&tag) && ShibINI::boolean(tag));
833 config.lifetime=config.timeout=0;
835 if (ini.get_tag(application_id, "authLifetime", true, &tag))
836 config.lifetime=strtoul(tag.c_str(),NULL,10);
838 if (ini.get_tag(application_id, "authTimeout", true, &tag))
839 config.timeout=strtoul(tag.c_str(),NULL,10);
841 // Pull the config data we need to handle the various possible conditions.
843 if (!ini.get_tag(application_id, "cookieName", true, &shib_cookie))
844 return WriteClientError(lpECB,"The cookieName configuration setting is missing, check configuration.");
847 if (!ini.get_tag(application_id, "wayfURL", true, &wayfLocation))
848 return WriteClientError(lpECB,"The wayfURL configuration setting is missing, check configuration.");
850 bool has_tag = ini.get_tag(application_id, "supportContact", true, &tag);
851 markupProcessor.insert("supportContact", has_tag ? tag : "");
852 has_tag = ini.get_tag(application_id, "logoLocation", true, &tag);
853 markupProcessor.insert("logoLocation", has_tag ? tag : "");
854 markupProcessor.insert("requestURL", target_url.c_str());
856 // Get an RPC handle and build the SHIRE object.
857 RPCHandle* rpc_handle = (RPCHandle*)rpc_handle_key->getData();
860 rpc_handle = new RPCHandle(shib_target_sockname(), SHIBRPC_PROG, SHIBRPC_VERS_1);
861 rpc_handle_key->setData(rpc_handle);
863 SHIRE shire(rpc_handle, config, shire_url.c_str());
865 // Process SHIRE POST
866 if (ini.get_tag(application_id, "shireSSLOnly", true, &tag) && ShibINI::boolean(tag))
868 // Make sure this is SSL, if it should be.
869 GetServerVariable(lpECB,"HTTPS",buf,10);
871 throw ShibTargetException(SHIBRPC_OK,"blocked non-SSL access to SHIRE POST processor");
874 // Make sure this is a POST
875 if (stricmp(lpECB->lpszMethod,"POST"))
876 throw ShibTargetException(SHIBRPC_OK,"blocked non-POST to SHIRE POST processor");
878 // Sure sure this POST is an appropriate content type
879 if (!lpECB->lpszContentType || stricmp(lpECB->lpszContentType,"application/x-www-form-urlencoded"))
880 throw ShibTargetException(SHIBRPC_OK,"blocked bad content-type to SHIRE POST processor");
882 // Make sure the "bytes sent" is a reasonable number and that we have all of it.
883 if (lpECB->cbTotalBytes > 1024*1024) // 1MB?
884 throw ShibTargetException (SHIBRPC_OK,"blocked too-large a post to SHIRE POST processor");
885 else if (lpECB->cbTotalBytes>lpECB->cbAvailable)
886 throw ShibTargetException (SHIBRPC_OK,"blocked incomplete post to SHIRE POST processor");
888 // Parse the incoming data.
889 HQUERY params=ParseQuery(lpECB);
891 throw ShibTargetException (SHIBRPC_OK,"unable to parse form data");
893 // Make sure the TARGET parameter exists
894 const char* target = QueryValue(params,"TARGET");
895 if (!target || *target == '\0')
896 throw ShibTargetException(SHIBRPC_OK,"SHIRE POST failed to find TARGET parameter");
898 // Make sure the SAMLResponse parameter exists
899 const char* post = QueryValue(params,"SAMLResponse");
900 if (!post || *post == '\0')
901 throw ShibTargetException (SHIBRPC_OK,"SHIRE POST failed to find SAMLResponse parameter");
903 GetServerVariable(lpECB,"REMOTE_ADDR",buf,16);
907 RPCError* status = shire.sessionCreate(post,buf,application_id.c_str(),cookie);
909 if (status->isError()) {
910 if (status->isRetryable()) {
912 string wayf=wayfLocation + "?shire=" + url_encode(shire_url.c_str()) + "&target=" + url_encode(target);
913 DWORD len=wayf.length();
914 if (lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_URL_REDIRECT_RESP,(LPVOID)wayf.c_str(),&len,0))
915 return HSE_STATUS_SUCCESS;
916 return HSE_STATUS_ERROR;
919 // Return this error to the user.
920 markupProcessor.insert(*status);
922 return WriteClientError(lpECB,shireError.c_str(),markupProcessor);
926 // We've got a good session, set the cookie and redirect to target.
927 shib_cookie = "Set-Cookie: " + shib_cookie + '=' + cookie + "; path=/\r\n"
928 "Location: " + target + "\r\n"
929 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
930 "Cache-Control: private,no-store,no-cache\r\n"
931 "Connection: close\r\n";
932 HSE_SEND_HEADER_EX_INFO hinfo;
933 hinfo.pszStatus="302 Moved";
934 hinfo.pszHeader=shib_cookie.c_str();
936 hinfo.cchHeader=shib_cookie.length();
937 hinfo.fKeepConn=FALSE;
938 if (lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER_EX,&hinfo,0,0))
939 return HSE_STATUS_SUCCESS;
941 catch (ShibTargetException &e) {
942 markupProcessor.insert ("errorType", "SHIRE Processing Error");
943 markupProcessor.insert ("errorText", e.what());
944 markupProcessor.insert ("errorDesc", "An error occurred while processing your request.");
945 return WriteClientError(lpECB,shireError.c_str(),markupProcessor);
948 markupProcessor.insert ("errorType", "SHIRE Processing Error");
949 markupProcessor.insert ("errorText", "Unexpected Exception");
950 markupProcessor.insert ("errorDesc", "An error occurred while processing your request.");
951 return WriteClientError(lpECB,shireError.c_str(),markupProcessor);
954 return HSE_STATUS_ERROR;