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())!='=')
517 sprintf(timebuf,"%u",time(NULL));
518 string wayf("Location: ");
519 wayf+=wayfLocation + "?shire=" + url_encode(shire_url.c_str()) + "&target=" + url_encode(target_url.c_str()) +
520 "&time=" + timebuf + "&providerId=" + application_id + "\r\n";
521 // Insert the headers.
522 pfc->AddResponseHeaders(pfc,const_cast<char*>(wayf.c_str()),0);
523 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"302 Please Wait",0,0);
524 return SF_STATUS_REQ_FINISHED;
527 session_id+=shib_cookie.length() + 1; /* Skip over the '=' */
528 char* cookieend=strchr(session_id,';');
530 *cookieend = '\0'; /* Ignore anyting after a ; */
532 // Make sure this session is still valid.
533 RPCError* status = NULL;
534 ShibMLP markupProcessor;
535 bool has_tag = ini.get_tag(application_id, "supportContact", true, &tag);
536 markupProcessor.insert("supportContact", has_tag ? tag : "");
537 has_tag = ini.get_tag(application_id, "logoLocation", true, &tag);
538 markupProcessor.insert("logoLocation", has_tag ? tag : "");
539 markupProcessor.insert("requestURL", target_url);
542 GetServerVariable(pfc,"REMOTE_ADDR",abuf,16);
544 status = shire.sessionIsValid(session_id, abuf, application_id.c_str());
546 catch (ShibTargetException &e) {
547 markupProcessor.insert("errorType", "SHIRE Processing Error");
548 markupProcessor.insert("errorText", e.what());
549 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
550 return WriteClientError(pfc, shireError.c_str(), markupProcessor);
553 markupProcessor.insert("errorType", "SHIRE Processing Error");
554 markupProcessor.insert("errorText", "Unexpected Exception");
555 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
556 return WriteClientError(pfc, shireError.c_str(), markupProcessor);
560 if (status->isError()) {
561 if (status->isRetryable()) {
565 sprintf(timebuf,"%u",time(NULL));
566 string wayf("Location: ");
567 wayf+=wayfLocation + "?shire=" + url_encode(shire_url.c_str()) + "&target=" + url_encode(target_url.c_str()) +
568 "&time=" + timebuf + "&providerId=" + application_id + "\r\n";
569 // Insert the headers.
570 pfc->AddResponseHeaders(pfc,const_cast<char*>(wayf.c_str()),0);
571 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"302 Please Wait",0,0);
572 return SF_STATUS_REQ_FINISHED;
575 // return the error page to the user
576 markupProcessor.insert(*status);
578 return WriteClientError(pfc, shireError.c_str(), markupProcessor);
585 rm_config.checkIPAddress = config.checkIPAddress;
586 RM rm(rpc_handle,rm_config);
588 // Get the attributes.
589 vector<SAMLAssertion*> assertions;
590 SAMLAuthenticationStatement* sso_statement=NULL;
591 status = rm.getAssertions(session_id, buf, application_id.c_str(), assertions, &sso_statement);
593 if (status->isError()) {
595 if (!ini.get_tag(application_id, "rmError", true, &shireError))
596 return WriteClientError(pfc,"The rmError configuration setting is missing, check configuration.");
598 markupProcessor.insert(*status);
600 return WriteClientError(pfc, rmError.c_str(), markupProcessor);
604 // Only allow a single assertion...
605 if (assertions.size() > 1) {
606 for (int k = 0; k < assertions.size(); k++)
607 delete assertions[k];
608 delete sso_statement;
609 return WriteClientError(pfc, accessError.c_str(), markupProcessor);
612 // Get the AAP providers, which contain the attribute policy info.
613 Iterator<IAAP*> provs=g_Config->getAAPProviders();
615 // Clear out the list of mapped attributes
616 while (provs.hasNext())
618 IAAP* aap=provs.next();
622 Iterator<const IAttributeRule*> rules=aap->getAttributeRules();
623 while (rules.hasNext())
625 const char* header=rules.next()->getHeader();
627 pn->SetHeader(pfc,const_cast<char*>(header),"");
633 for (int k = 0; k < assertions.size(); k++)
634 delete assertions[k];
635 delete sso_statement;
642 // Clear relevant headers.
643 pn->SetHeader(pfc,"remote-user:","");
644 pn->SetHeader(pfc,"Shib-Attributes:","");
645 pn->SetHeader(pfc,"Shib-Origin-Site:","");
646 pn->SetHeader(pfc,"Shib-Authentication-Method:","");
648 pn->SetHeader(pfc,"Shib-Application-ID:","");
649 pn->SetHeader(pfc,"Shib-Application-ID:",const_cast<char*>(application_id.c_str()));
651 // Maybe export the assertion.
652 if (ini.get_tag(application_id,"exportAssertion",true,&tag) && ShibINI::boolean(tag))
655 RM::serialize(*(assertions[0]), assertion);
656 string::size_type lfeed;
657 while ((lfeed=assertion.find('\n'))!=string::npos)
658 assertion.erase(lfeed,1);
659 pn->SetHeader(pfc,"Shib-Attributes:",const_cast<char*>(assertion.c_str()));
664 auto_ptr_char os(sso_statement->getSubject()->getNameQualifier());
665 auto_ptr_char am(sso_statement->getAuthMethod());
666 pn->SetHeader(pfc,"Shib-Origin-Site:", const_cast<char*>(os.get()));
667 pn->SetHeader(pfc,"Shib-Authentication-Method:", const_cast<char*>(am.get()));
670 // Export the attributes.
671 Iterator<SAMLAssertion*> a_iter(assertions);
672 while (a_iter.hasNext()) {
673 SAMLAssertion* assert=a_iter.next();
674 Iterator<SAMLStatement*> statements=assert->getStatements();
675 while (statements.hasNext()) {
676 SAMLAttributeStatement* astate=dynamic_cast<SAMLAttributeStatement*>(statements.next());
679 Iterator<SAMLAttribute*> attrs=astate->getAttributes();
680 while (attrs.hasNext()) {
681 SAMLAttribute* attr=attrs.next();
683 // Are we supposed to export it?
684 AAP wrapper(g_Config->getAAPProviders(),attr->getName(),attr->getNamespace());
688 Iterator<string> vals=attr->getSingleByteValues();
689 if (!strcmp(wrapper->getHeader(),"REMOTE_USER") && vals.hasNext()) {
690 char* principal=const_cast<char*>(vals.next().c_str());
691 pn->SetHeader(pfc,"remote-user:",principal);
692 pfc->pFilterContext=pfc->AllocMem(pfc,strlen(principal)+1,0);
693 if (pfc->pFilterContext)
694 strcpy(static_cast<char*>(pfc->pFilterContext),principal);
698 for (int it = 0; vals.hasNext(); it++) {
699 string value = vals.next();
700 for (string::size_type pos = value.find_first_of(";", string::size_type(0)); pos != string::npos; pos = value.find_first_of(";", pos)) {
701 value.insert(pos, "\\");
707 header=header + ';' + value;
709 string hname2=string(wrapper->getHeader()) + ':';
710 pn->SetHeader(pfc,const_cast<char*>(hname2.c_str()),const_cast<char*>(header.c_str()));
717 for (int k = 0; k < assertions.size(); k++)
718 delete assertions[k];
719 delete sso_statement;
721 return SF_STATUS_REQ_NEXT_NOTIFICATION;
725 return WriteClientError(pfc,"Out of Memory");
729 if (e==ERROR_NO_DATA)
730 return WriteClientError(pfc,"A required variable or header was empty.");
732 return WriteClientError(pfc,"Server detected unexpected IIS error.");
736 return WriteClientError(pfc,"Server caught an unknown exception.");
739 return WriteClientError(pfc,"Server reached unreachable code!");
742 void get_target_and_appid(LPEXTENSION_CONTROL_BLOCK lpECB, const char* hostname, string& target, string& appid)
747 GetServerVariable(lpECB,"HTTPS",ssl,5);
748 GetServerVariable(lpECB,"SERVER_PORT",port,10);
749 GetServerVariable(lpECB,"URL",url,255);
750 bool SSL=(ssl=="on");
752 // First get the appid using the normalized hostname.
753 ApplicationMapper mapper;
754 appid = mapper->getApplicationFromParsedURL((SSL ? "https" : "http"), hostname, atoi(port), url);
756 target=static_cast<char*>(url);
757 if (port!=(SSL ? "443" : "80"))
758 target = ':' + static_cast<char*>(port) + target;
760 // For the target, we use the "normalizeRequest" tag to decide how to set the server's name.
762 if (g_Config->getINI().get_tag(appid,"normalizeRequest",true,&tag) && ShibINI::boolean(tag))
764 target=string(SSL ? "https://" : "http://") + hostname + target;
768 GetServerVariable(lpECB,"SERVER_NAME",url);
769 target=string(SSL ? "https://" : "http://") + static_cast<char*>(url) + target;
773 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* msg)
775 LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
776 static const char* ctype="Content-Type: text/html\r\n";
777 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
778 static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Error</TITLE></HEAD><BODY><H1>Shibboleth Error</H1>";
779 DWORD resplen=strlen(xmsg);
780 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg,&resplen,HSE_IO_SYNC);
782 lpECB->WriteClient(lpECB->ConnID,(LPVOID)msg,&resplen,HSE_IO_SYNC);
783 static const char* xmsg2="</BODY></HTML>";
784 resplen=strlen(xmsg2);
785 lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg2,&resplen,HSE_IO_SYNC);
786 return HSE_STATUS_SUCCESS;
789 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* filename, ShibMLP& mlp)
791 ifstream infile(filename);
793 return WriteClientError(lpECB,"Unable to open error template, check settings.");
795 string res = mlp.run(infile);
796 static const char* ctype="Content-Type: text/html\r\n";
797 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
798 DWORD resplen=res.length();
799 lpECB->WriteClient(lpECB->ConnID,(LPVOID)res.c_str(),&resplen,0);
800 return HSE_STATUS_SUCCESS;
803 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
805 ostringstream threadid;
806 threadid << "[" << getpid() << "] shire" << '\0';
807 saml::NDC ndc(threadid.str().c_str());
809 ShibINI& ini = g_Config->getINI();
811 ShibMLP markupProcessor;
815 // Determine web site number. This can't really fail, I don't think.
818 GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
819 if ((site_id=strtoul(buf,NULL,10))==0)
820 return WriteClientError(lpECB,"IIS site instance appears to be invalid.");
822 // Match site instance to site settings.
823 if (site_id>g_Sites.size() || g_Sites[site_id-1].length()==0)
824 return WriteClientError(lpECB,"Shibboleth filter not configured for this web site.");
825 string& site=g_Sites[site_id-1];
827 string target_url,application_id;
828 get_target_and_appid(lpECB,site.c_str(),target_url,application_id);
830 if (!ini.get_tag(application_id, "shireError", true, &shireError))
831 return WriteClientError(lpECB,"The shireError configuration setting is missing, check configuration.");
833 string shire_url = target_url;
835 // Set SHIRE policies.
838 config.checkIPAddress = (ini.get_tag(application_id,"checkIPAddress",true,&tag) && ShibINI::boolean(tag));
839 config.lifetime=config.timeout=0;
841 if (ini.get_tag(application_id, "authLifetime", true, &tag))
842 config.lifetime=strtoul(tag.c_str(),NULL,10);
844 if (ini.get_tag(application_id, "authTimeout", true, &tag))
845 config.timeout=strtoul(tag.c_str(),NULL,10);
847 // Pull the config data we need to handle the various possible conditions.
849 if (!ini.get_tag(application_id, "cookieName", true, &shib_cookie))
850 return WriteClientError(lpECB,"The cookieName configuration setting is missing, check configuration.");
853 if (!ini.get_tag(application_id, "wayfURL", true, &wayfLocation))
854 return WriteClientError(lpECB,"The wayfURL configuration setting is missing, check configuration.");
856 bool has_tag = ini.get_tag(application_id, "supportContact", true, &tag);
857 markupProcessor.insert("supportContact", has_tag ? tag : "");
858 has_tag = ini.get_tag(application_id, "logoLocation", true, &tag);
859 markupProcessor.insert("logoLocation", has_tag ? tag : "");
860 markupProcessor.insert("requestURL", target_url.c_str());
862 // Get an RPC handle and build the SHIRE object.
863 RPCHandle* rpc_handle = (RPCHandle*)rpc_handle_key->getData();
866 rpc_handle = new RPCHandle(shib_target_sockname(), SHIBRPC_PROG, SHIBRPC_VERS_1);
867 rpc_handle_key->setData(rpc_handle);
869 SHIRE shire(rpc_handle, config, shire_url.c_str());
871 // Process SHIRE POST
872 if (ini.get_tag(application_id, "shireSSLOnly", true, &tag) && ShibINI::boolean(tag))
874 // Make sure this is SSL, if it should be.
875 GetServerVariable(lpECB,"HTTPS",buf,10);
877 throw ShibTargetException(SHIBRPC_OK,"blocked non-SSL access to SHIRE POST processor");
880 // Make sure this is a POST
881 if (stricmp(lpECB->lpszMethod,"POST"))
882 throw ShibTargetException(SHIBRPC_OK,"blocked non-POST to SHIRE POST processor");
884 // Sure sure this POST is an appropriate content type
885 if (!lpECB->lpszContentType || stricmp(lpECB->lpszContentType,"application/x-www-form-urlencoded"))
886 throw ShibTargetException(SHIBRPC_OK,"blocked bad content-type to SHIRE POST processor");
888 // Make sure the "bytes sent" is a reasonable number and that we have all of it.
889 if (lpECB->cbTotalBytes > 1024*1024) // 1MB?
890 throw ShibTargetException (SHIBRPC_OK,"blocked too-large a post to SHIRE POST processor");
891 else if (lpECB->cbTotalBytes>lpECB->cbAvailable)
892 throw ShibTargetException (SHIBRPC_OK,"blocked incomplete post to SHIRE POST processor");
894 // Parse the incoming data.
895 HQUERY params=ParseQuery(lpECB);
897 throw ShibTargetException (SHIBRPC_OK,"unable to parse form data");
899 // Make sure the TARGET parameter exists
900 const char* target = QueryValue(params,"TARGET");
901 if (!target || *target == '\0')
902 throw ShibTargetException(SHIBRPC_OK,"SHIRE POST failed to find TARGET parameter");
904 // Make sure the SAMLResponse parameter exists
905 const char* post = QueryValue(params,"SAMLResponse");
906 if (!post || *post == '\0')
907 throw ShibTargetException (SHIBRPC_OK,"SHIRE POST failed to find SAMLResponse parameter");
909 GetServerVariable(lpECB,"REMOTE_ADDR",buf,16);
913 RPCError* status = shire.sessionCreate(post,buf,application_id.c_str(),cookie);
915 if (status->isError()) {
916 if (status->isRetryable()) {
919 sprintf(timebuf,"%u",time(NULL));
920 string wayf=wayfLocation + "?shire=" + url_encode(shire_url.c_str()) + "&target=" + url_encode(target) +
921 "&time=" + timebuf + "&providerId=" + application_id;
922 DWORD len=wayf.length();
923 if (lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_URL_REDIRECT_RESP,(LPVOID)wayf.c_str(),&len,0))
924 return HSE_STATUS_SUCCESS;
925 return HSE_STATUS_ERROR;
928 // Return this error to the user.
929 markupProcessor.insert(*status);
931 return WriteClientError(lpECB,shireError.c_str(),markupProcessor);
935 // We've got a good session, set the cookie and redirect to target.
936 shib_cookie = "Set-Cookie: " + shib_cookie + '=' + cookie + "; path=/\r\n"
937 "Location: " + target + "\r\n"
938 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
939 "Cache-Control: private,no-store,no-cache\r\n"
940 "Connection: close\r\n";
941 HSE_SEND_HEADER_EX_INFO hinfo;
942 hinfo.pszStatus="302 Moved";
943 hinfo.pszHeader=shib_cookie.c_str();
945 hinfo.cchHeader=shib_cookie.length();
946 hinfo.fKeepConn=FALSE;
947 if (lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER_EX,&hinfo,0,0))
948 return HSE_STATUS_SUCCESS;
950 catch (ShibTargetException &e) {
951 markupProcessor.insert ("errorType", "SHIRE Processing Error");
952 markupProcessor.insert ("errorText", e.what());
953 markupProcessor.insert ("errorDesc", "An error occurred while processing your request.");
954 return WriteClientError(lpECB,shireError.c_str(),markupProcessor);
957 markupProcessor.insert ("errorType", "SHIRE Processing Error");
958 markupProcessor.insert ("errorText", "Unexpected Exception");
959 markupProcessor.insert ("errorDesc", "An error occurred while processing your request.");
960 return WriteClientError(lpECB,shireError.c_str(),markupProcessor);
963 return HSE_STATUS_ERROR;