Redesigned target around URL->application mapping
[shibboleth/sp.git] / isapi_shib / isapi_shib.cpp
1 /*
2  * The Shibboleth License, Version 1.
3  * Copyright (c) 2002
4  * University Corporation for Advanced Internet Development, Inc.
5  * All rights reserved
6  *
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions are met:
10  *
11  * Redistributions of source code must retain the above copyright notice, this
12  * list of conditions and the following disclaimer.
13  *
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.
22  *
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
28  *
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.
33  *
34  *
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.
48  */
49
50 /* isapi_shib.cpp - Shibboleth ISAPI filter
51
52    Scott Cantor
53    8/23/02
54 */
55
56 // SAML Runtime
57 #include <saml/saml.h>
58 #include <shib/shib.h>
59 #include <shib/shib-threads.h>
60 #include <shib-target/shib-target.h>
61
62 #include <log4cpp/Category.hh>
63
64 #include <ctime>
65 #include <fstream>
66 #include <sstream>
67 #include <stdexcept>
68
69 #include <httpfilt.h>
70 #include <httpext.h>
71
72 #include <cgiparse.h>
73
74 using namespace std;
75 using namespace log4cpp;
76 using namespace saml;
77 using namespace shibboleth;
78 using namespace shibtarget;
79
80 // globals
81 namespace {
82     HINSTANCE g_hinstDLL;
83     ThreadKey* rpc_handle_key = NULL;
84     ShibTargetConfig* g_Config = NULL;
85     vector<string> g_Sites;
86 }
87
88 void destroy_handle(void* data)
89 {
90     delete (RPCHandle*)data;
91 }
92
93 BOOL LogEvent(
94     LPCSTR  lpUNCServerName,
95     WORD  wType,
96     DWORD  dwEventID,
97     PSID  lpUserSid,
98     LPCSTR  message)
99 {
100     LPCSTR  messages[] = {message, NULL};
101     
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);
105 }
106
107 extern "C" __declspec(dllexport) BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID)
108 {
109     if (fdwReason==DLL_PROCESS_ATTACH)
110         g_hinstDLL=hinstDLL;
111     return TRUE;
112 }
113
114 extern "C" BOOL WINAPI GetExtensionVersion(HSE_VERSION_INFO* pVer)
115 {
116     if (!pVer)
117         return FALSE;
118         
119     if (!g_Config)
120     {
121         LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
122                 "Extension mode startup not possible, is the DLL loaded as a filter?");
123         return FALSE;
124     }
125
126     pVer->dwExtensionVersion=HSE_VERSION;
127     strncpy(pVer->lpszExtensionDesc,"Shibboleth ISAPI Extension",HSE_MAX_EXT_DLL_NAME_LEN-1);
128     return TRUE;
129 }
130
131 extern "C" BOOL WINAPI GetFilterVersion(PHTTP_FILTER_VERSION pVer)
132 {
133     if (!pVer)
134         return FALSE;
135
136     try
137     {
138         ShibTargetConfig::preinit();
139         g_Config = &(ShibTargetConfig::init(SHIBTARGET_SHIRE, getenv("SHIBCONFIG")));
140         ShibINI& ini = g_Config->getINI();
141
142         // Create the RPC Handle TLS key.
143         rpc_handle_key=ThreadKey::create(destroy_handle);
144
145         Category& log=Category::getInstance("isapi_shib.GetFilterVersion");
146
147         // Read site-specific settings for each instance ID we can find.
148         unsigned short i=1;
149         char iid[8];
150         sprintf(iid,"%u",i++);
151         string hostname;
152         while (ini.get_tag("isapi",iid,false,&hostname))
153         {
154             log.info("configuring for site ID (%d), hostname (%s)",i-1,hostname.empty() ? "null" : hostname.c_str());
155
156             // If no section exists for the host, mark it as a "skip" site.
157             if (hostname == "skip")
158             {
159                 log.info("skipping site ID (%d)",i-1);
160                 hostname.erase();
161             }
162
163             g_Sites.push_back(hostname);
164             sprintf(iid,"%u",i++);
165             hostname.erase();
166         }
167     }
168     catch (SAMLException&)
169     {
170         LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
171                 "Filter startup failed with SAML exception, check shire log for help.");
172         return FALSE;
173     }
174     catch (runtime_error& e)
175     {
176         LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
177         return FALSE;
178     }
179     catch (...)
180     {
181         LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Filter startup failed with unexpected exception.");
182         return FALSE;
183     }
184
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 |
191                    SF_NOTIFY_LOG);
192     LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter initialized...");
193     return TRUE;
194 }
195
196 extern "C" BOOL WINAPI TerminateExtension(DWORD)
197 {
198     return TRUE;    // cleanup should happen when filter unloads
199 }
200
201 extern "C" BOOL WINAPI TerminateFilter(DWORD)
202 {
203     delete rpc_handle_key;
204     if (g_Config)
205         g_Config->shutdown();
206     g_Config = NULL;
207     LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter shut down...");
208     return TRUE;
209 }
210
211 /* Next up, some suck-free versions of various APIs.
212
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.
219 */
220
221 class dynabuf
222 {
223 public:
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); }
235 private:
236     char* bufptr;
237     size_t buflen;
238 };
239
240 void dynabuf::reserve(size_t s, bool keep)
241 {
242     if (s<=buflen)
243         return;
244     char* p=new char[s];
245     if (keep)
246         while (buflen--)
247             p[buflen]=bufptr[buflen];
248     buflen=s;
249     delete[] bufptr;
250     bufptr=p;
251 }
252
253 bool dynabuf::operator==(const char* s) const
254 {
255     if (buflen==NULL || s==NULL)
256         return (buflen==NULL && s==NULL);
257     else
258         return strcmp(bufptr,s)==0;
259 }
260
261 void GetServerVariable(PHTTP_FILTER_CONTEXT pfc, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
262     throw (bad_alloc, DWORD)
263 {
264     s.erase();
265     s.reserve(size);
266     size=s.size();
267
268     while (!pfc->GetServerVariable(pfc,lpszVariable,s,&size))
269     {
270         // Grumble. Check the error.
271         DWORD e=GetLastError();
272         if (e==ERROR_INSUFFICIENT_BUFFER)
273             s.reserve(size);
274         else
275             break;
276     }
277     if (bRequired && s.empty())
278         throw ERROR_NO_DATA;
279 }
280
281 void GetServerVariable(LPEXTENSION_CONTROL_BLOCK lpECB, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
282     throw (bad_alloc, DWORD)
283 {
284     s.erase();
285     s.reserve(size);
286     size=s.size();
287
288     while (lpECB->GetServerVariable(lpECB->ConnID,lpszVariable,s,&size))
289     {
290         // Grumble. Check the error.
291         DWORD e=GetLastError();
292         if (e==ERROR_INSUFFICIENT_BUFFER)
293             s.reserve(size);
294         else
295             break;
296     }
297     if (bRequired && s.empty())
298         throw ERROR_NO_DATA;
299 }
300
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)
304 {
305     s.erase();
306     s.reserve(size);
307     size=s.size();
308
309     while (!pn->GetHeader(pfc,lpszName,s,&size))
310     {
311         // Grumble. Check the error.
312         DWORD e=GetLastError();
313         if (e==ERROR_INSUFFICIENT_BUFFER)
314             s.reserve(size);
315         else
316             break;
317     }
318     if (bRequired && s.empty())
319         throw ERROR_NO_DATA;
320 }
321
322 inline char hexchar(unsigned short s)
323 {
324     return (s<=9) ? ('0' + s) : ('A' + s - 10);
325 }
326
327 string url_encode(const char* url) throw (bad_alloc)
328 {
329     static char badchars[]="\"\\+<>#%{}|^~[]`;/?:@=&";
330     string s;
331     for (const char* pch=url; *pch; pch++)
332     {
333         if (strchr(badchars,*pch)!=NULL || *pch<=0x1F || *pch>=0x7F)
334             s=s + '%' + hexchar(*pch >> 4) + hexchar(*pch & 0x0F);
335         else
336             s+=*pch;
337     }
338     return s;
339 }
340
341 void get_target_and_appid(
342     PHTTP_FILTER_CONTEXT pfc, PHTTP_FILTER_PREPROC_HEADERS pn, const char* hostname, string& target, string& appid
343     )
344 {
345     dynabuf port(10);
346     dynabuf url(256);
347     GetServerVariable(pfc,"SERVER_PORT",port,10);
348     GetHeader(pn,pfc,"url",url,256,false);
349     
350     // First get the appid using the normalized hostname.
351     ApplicationMapper mapper;
352     appid = mapper->getApplicationFromParsedURL((pfc->fIsSecurePort ? "https" : "http"), hostname, atoi(port), url);
353
354     target=static_cast<char*>(url);
355     if (port!=(pfc->fIsSecurePort ? "443" : "80"))
356         target = ':' + static_cast<char*>(port) + target;
357
358     // For the target, we use the "normalizeRequest" tag to decide how to set the server's name.
359     string tag;
360     if (g_Config->getINI().get_tag(appid,"normalizeRequest",true,&tag) && ShibINI::boolean(tag))
361     {
362         target=string(pfc->fIsSecurePort ? "https://" : "http://") + hostname + target;
363     }
364     else
365     {
366         GetServerVariable(pfc,"SERVER_NAME",url);
367         target=string(pfc->fIsSecurePort ? "https://" : "http://") + static_cast<char*>(url) + target;
368     }
369 }
370
371 string get_shire_location(const char* application_id, const char* target)
372 {
373     string shireURL;
374     if (g_Config->getINI().get_tag(application_id,"shireURL",true,&shireURL) && !shireURL.empty())
375     {
376         if (shireURL[0]!='/')
377             return shireURL;
378         const char* colon=strchr(target,':');
379         const char* slash=strchr(colon+3,'/');
380         string s(target,slash-target);
381         s+=shireURL;
382         return s;
383     }
384     return shireURL;
385 }
386
387 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const char* msg)
388 {
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);
397     resplen=strlen(msg);
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;
403 }
404
405 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const char* filename, ShibMLP& mlp)
406 {
407     ifstream infile(filename);
408     if (!infile)
409         return WriteClientError(pfc,"Unable to open error template, check settings.");   
410     string res = mlp.run(infile);
411
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;
418 }
419
420 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
421 {
422     // Is this a log notification?
423     if (notificationType==SF_NOTIFY_LOG)
424     {
425         if (pfc->pFilterContext)
426             ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=static_cast<LPCSTR>(pfc->pFilterContext);
427         return SF_STATUS_REQ_NEXT_NOTIFICATION;
428     }
429
430     PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
431     try
432     {
433         // Determine web site number. This can't really fail, I don't think.
434         dynabuf buf(128);
435         ULONG site_id=0;
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.");
439
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];
444         
445         string application_id;
446         string target_url;
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());
449
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;
453
454         // Now check the policy for this application.
455         string tag;
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;
459
460         // SSL content check.
461         if (ini.get_tag(application_id,"contentSSLOnly",true,&tag) && ShibINI::boolean(tag) && !pfc->fIsSecurePort)
462         {
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.");
466         }
467
468         ostringstream threadid;
469         threadid << "[" << getpid() << "] shire" << '\0';
470         saml::NDC ndc(threadid.str().c_str());
471
472         // Set SHIRE policies.
473         SHIREConfig config;
474         config.checkIPAddress = (ini.get_tag(application_id,"checkIPAddress",true,&tag) && ShibINI::boolean(tag));
475         config.lifetime=config.timeout=0;
476         tag.erase();
477         if (ini.get_tag(application_id, "authLifetime", true, &tag))
478             config.lifetime=strtoul(tag.c_str(),NULL,10);
479         tag.erase();
480         if (ini.get_tag(application_id, "authTimeout", true, &tag))
481             config.timeout=strtoul(tag.c_str(),NULL,10);
482
483         // Pull the config data we need to handle the various possible conditions.
484         string shib_cookie;
485         if (!ini.get_tag(application_id, "cookieName", true, &shib_cookie))
486             return WriteClientError(pfc,"The cookieName configuration setting is missing, check configuration.");
487     
488         string wayfLocation;
489         if (!ini.get_tag(application_id, "wayfURL", true, &wayfLocation))
490             return WriteClientError(pfc,"The wayfURL configuration setting is missing, check configuration.");
491     
492         string shireError;
493         if (!ini.get_tag(application_id, "shireError", true, &shireError))
494             return WriteClientError(pfc,"The shireError configuration setting is missing, check configuration.");
495
496         string accessError;
497         if (!ini.get_tag(application_id, "accessError", true, &shireError))
498             return WriteClientError(pfc,"The accessError configuration setting is missing, check configuration.");
499         
500         // Get an RPC handle and build the SHIRE object.
501         RPCHandle* rpc_handle = (RPCHandle*)rpc_handle_key->getData();
502         if (!rpc_handle)
503         {
504             rpc_handle = new RPCHandle(shib_target_sockname(), SHIBRPC_PROG, SHIBRPC_VERS_1);
505             rpc_handle_key->setData(rpc_handle);
506         }
507         SHIRE shire(rpc_handle, config, shire_url.c_str());
508
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())!='=')
514         {
515             // Redirect to WAYF.
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;
522         }
523
524         session_id+=shib_cookie.length() + 1;   /* Skip over the '=' */
525         char* cookieend=strchr(session_id,';');
526         if (cookieend)
527             *cookieend = '\0';  /* Ignore anyting after a ; */
528   
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);
537     
538         dynabuf abuf(16);
539         GetServerVariable(pfc,"REMOTE_ADDR",abuf,16);
540         try {
541             status = shire.sessionIsValid(session_id, abuf, application_id.c_str());
542         }
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);
548         }
549         catch (...) {
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);
554         }
555     
556         // Check the status
557         if (status->isError()) {
558             if (status->isRetryable()) {
559                 // Redirect to WAYF.
560                 delete status;
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;
567             }
568             else {
569                 // return the error page to the user
570                 markupProcessor.insert(*status);
571                 delete status;
572                 return WriteClientError(pfc, shireError.c_str(), markupProcessor);
573             }
574         }
575         delete status;
576     
577         // Move to RM phase.
578         RMConfig rm_config;
579         rm_config.checkIPAddress = config.checkIPAddress;
580         RM rm(rpc_handle,rm_config);
581
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);
586     
587         if (status->isError()) {
588             string rmError;
589             if (!ini.get_tag(application_id, "rmError", true, &shireError))
590                 return WriteClientError(pfc,"The rmError configuration setting is missing, check configuration.");
591     
592             markupProcessor.insert(*status);
593             delete status;
594             return WriteClientError(pfc, rmError.c_str(), markupProcessor);
595         }
596         delete status;
597
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);
604         }
605
606         // Get the AAP providers, which contain the attribute policy info.
607         Iterator<IAAP*> provs=g_Config->getAAPProviders();
608     
609         // Clear out the list of mapped attributes
610         while (provs.hasNext())
611         {
612             IAAP* aap=provs.next();
613             aap->lock();
614             try
615             {
616                 Iterator<const IAttributeRule*> rules=aap->getAttributeRules();
617                 while (rules.hasNext())
618                 {
619                     const char* header=rules.next()->getHeader();
620                     if (header)
621                         pn->SetHeader(pfc,const_cast<char*>(header),"");
622                 }
623             }
624             catch(...)
625             {
626                 aap->unlock();
627                 for (int k = 0; k < assertions.size(); k++)
628                   delete assertions[k];
629                 delete sso_statement;
630                 throw;
631             }
632             aap->unlock();
633         }
634         provs.reset();
635
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:","");
641
642         pn->SetHeader(pfc,"Shib-Application-ID:","");
643         pn->SetHeader(pfc,"Shib-Application-ID:",const_cast<char*>(application_id.c_str()));
644
645         // Maybe export the assertion.
646         if (ini.get_tag(application_id,"exportAssertion",true,&tag) && ShibINI::boolean(tag))
647         {
648             string assertion;
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()));
654         }
655         
656         if (sso_statement)
657         {
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()));
662         }
663
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());
671                 if (!astate)
672                     continue;
673                 Iterator<SAMLAttribute*> attrs=astate->getAttributes();
674                 while (attrs.hasNext()) {
675                     SAMLAttribute* attr=attrs.next();
676         
677                     // Are we supposed to export it?
678                     AAP wrapper(g_Config->getAAPProviders(),attr->getName(),attr->getNamespace());
679                     if (wrapper.fail())
680                         continue;
681                 
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);
689                     }
690                     else {
691                         string header;
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, "\\");
696                                 pos += 2;
697                             }
698                             if (it == 0)
699                                 header=value;
700                             else
701                                 header=header + ';' + value;
702                         }
703                         string hname2=string(wrapper->getHeader()) + ':';
704                         pn->SetHeader(pfc,const_cast<char*>(hname2.c_str()),const_cast<char*>(header.c_str()));
705                         }
706                 }
707             }
708         }
709     
710         // clean up memory
711         for (int k = 0; k < assertions.size(); k++)
712           delete assertions[k];
713         delete sso_statement;
714
715         return SF_STATUS_REQ_NEXT_NOTIFICATION;
716     }
717     catch(bad_alloc)
718     {
719         return WriteClientError(pfc,"Out of Memory");
720     }
721     catch(DWORD e)
722     {
723         if (e==ERROR_NO_DATA)
724             return WriteClientError(pfc,"A required variable or header was empty.");
725         else
726             return WriteClientError(pfc,"Server detected unexpected IIS error.");
727     }
728     catch(...)
729     {
730         return WriteClientError(pfc,"Server caught an unknown exception.");
731     }
732
733     return WriteClientError(pfc,"Server reached unreachable code!");
734 }
735
736 void get_target_and_appid(LPEXTENSION_CONTROL_BLOCK lpECB, const char* hostname, string& target, string& appid)
737 {
738     dynabuf ssl(5);
739     dynabuf port(10);
740     dynabuf url(256);
741     GetServerVariable(lpECB,"HTTPS",ssl,5);
742     GetServerVariable(lpECB,"SERVER_PORT",port,10);
743     GetServerVariable(lpECB,"URL",url,255);
744     bool SSL=(ssl=="on");
745     
746     // First get the appid using the normalized hostname.
747     ApplicationMapper mapper;
748     appid = mapper->getApplicationFromParsedURL((SSL ? "https" : "http"), hostname, atoi(port), url);
749
750     target=static_cast<char*>(url);
751     if (port!=(SSL ? "443" : "80"))
752         target = ':' + static_cast<char*>(port) + target;
753
754     // For the target, we use the "normalizeRequest" tag to decide how to set the server's name.
755     string tag;
756     if (g_Config->getINI().get_tag(appid,"normalizeRequest",true,&tag) && ShibINI::boolean(tag))
757     {
758         target=string(SSL ? "https://" : "http://") + hostname + target;
759     }
760     else
761     {
762         GetServerVariable(lpECB,"SERVER_NAME",url);
763         target=string(SSL ? "https://" : "http://") + static_cast<char*>(url) + target;
764     }
765 }
766
767 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* msg)
768 {
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);
775     resplen=strlen(msg);
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;
781 }
782
783 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* filename, ShibMLP& mlp)
784 {
785     ifstream infile(filename);
786     if (!infile)
787         return WriteClientError(lpECB,"Unable to open error template, check settings.");   
788
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;
795 }
796
797 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
798 {
799     ostringstream threadid;
800     threadid << "[" << getpid() << "] shire" << '\0';
801     saml::NDC ndc(threadid.str().c_str());
802
803     ShibINI& ini = g_Config->getINI();
804     string shireError;
805     ShibMLP markupProcessor;
806
807     try
808     {
809         // Determine web site number. This can't really fail, I don't think.
810         dynabuf buf(128);
811         ULONG site_id=0;
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.");
815
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];
820
821         string target_url,application_id;
822         get_target_and_appid(lpECB,site.c_str(),target_url,application_id);
823
824         if (!ini.get_tag(application_id, "shireError", true, &shireError))
825             return WriteClientError(lpECB,"The shireError configuration setting is missing, check configuration.");
826
827         string shire_url = target_url;
828
829         // Set SHIRE policies.
830         SHIREConfig config;
831         string tag;
832         config.checkIPAddress = (ini.get_tag(application_id,"checkIPAddress",true,&tag) && ShibINI::boolean(tag));
833         config.lifetime=config.timeout=0;
834         tag.erase();
835         if (ini.get_tag(application_id, "authLifetime", true, &tag))
836             config.lifetime=strtoul(tag.c_str(),NULL,10);
837         tag.erase();
838         if (ini.get_tag(application_id, "authTimeout", true, &tag))
839             config.timeout=strtoul(tag.c_str(),NULL,10);
840
841         // Pull the config data we need to handle the various possible conditions.
842         string shib_cookie;
843         if (!ini.get_tag(application_id, "cookieName", true, &shib_cookie))
844             return WriteClientError(lpECB,"The cookieName configuration setting is missing, check configuration.");
845     
846         string wayfLocation;
847         if (!ini.get_tag(application_id, "wayfURL", true, &wayfLocation))
848             return WriteClientError(lpECB,"The wayfURL configuration setting is missing, check configuration.");
849     
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());
855   
856         // Get an RPC handle and build the SHIRE object.
857         RPCHandle* rpc_handle = (RPCHandle*)rpc_handle_key->getData();
858         if (!rpc_handle)
859         {
860             rpc_handle = new RPCHandle(shib_target_sockname(), SHIBRPC_PROG, SHIBRPC_VERS_1);
861             rpc_handle_key->setData(rpc_handle);
862         }
863         SHIRE shire(rpc_handle, config, shire_url.c_str());
864
865         // Process SHIRE POST
866         if (ini.get_tag(application_id, "shireSSLOnly", true, &tag) && ShibINI::boolean(tag))
867         {
868             // Make sure this is SSL, if it should be.
869             GetServerVariable(lpECB,"HTTPS",buf,10);
870             if (buf!="on")
871                 throw ShibTargetException(SHIBRPC_OK,"blocked non-SSL access to SHIRE POST processor");
872         }
873         
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");
877
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");
881     
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");
887
888         // Parse the incoming data.
889         HQUERY params=ParseQuery(lpECB);
890         if (!params)
891             throw ShibTargetException (SHIBRPC_OK,"unable to parse form data");
892
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");
897     
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");
902
903         GetServerVariable(lpECB,"REMOTE_ADDR",buf,16);
904
905         // Process the post.
906         string cookie;
907         RPCError* status = shire.sessionCreate(post,buf,application_id.c_str(),cookie);
908     
909         if (status->isError()) {
910             if (status->isRetryable()) {
911                 delete status;
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;
917             }
918     
919             // Return this error to the user.
920             markupProcessor.insert(*status);
921             delete status;
922             return WriteClientError(lpECB,shireError.c_str(),markupProcessor);
923         }
924         delete status;
925     
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();
935         hinfo.cchStatus=9;
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;
940     }
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);
946     }
947     catch (...) {
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);
952     }
953     
954     return HSE_STATUS_ERROR;
955 }
956