Was overwriting session id with address.
[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 struct settings_t
81 {
82     settings_t() {}
83     settings_t(string& name) : m_name(name) {}
84     
85     string m_name;
86     vector<string> m_mustContain;
87 };
88
89 // globals
90 namespace {
91     HINSTANCE g_hinstDLL;
92     ThreadKey* rpc_handle_key = NULL;
93     ShibTargetConfig* g_Config = NULL;
94     vector<settings_t> g_Sites;
95 }
96
97 void destroy_handle(void* data)
98 {
99     delete (RPCHandle*)data;
100 }
101
102 BOOL LogEvent(
103     LPCSTR  lpUNCServerName,
104     WORD  wType,
105     DWORD  dwEventID,
106     PSID  lpUserSid,
107     LPCSTR  message)
108 {
109     LPCSTR  messages[] = {message, NULL};
110     
111     HANDLE hElog = RegisterEventSource(lpUNCServerName, "Shibboleth ISAPI Filter");
112     BOOL res = ReportEvent(hElog, wType, 0, dwEventID, lpUserSid, 1, 0, messages, NULL);
113     return (DeregisterEventSource(hElog) && res);
114 }
115
116 extern "C" __declspec(dllexport) BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID)
117 {
118     if (fdwReason==DLL_PROCESS_ATTACH)
119         g_hinstDLL=hinstDLL;
120     return TRUE;
121 }
122
123 extern "C" BOOL WINAPI GetExtensionVersion(HSE_VERSION_INFO* pVer)
124 {
125     if (!pVer)
126         return FALSE;
127         
128     if (!g_Config)
129     {
130         LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
131                 "Extension mode startup not possible, is the DLL loaded as a filter?");
132         return FALSE;
133     }
134
135     pVer->dwExtensionVersion=HSE_VERSION;
136     strncpy(pVer->lpszExtensionDesc,"Shibboleth ISAPI Extension",HSE_MAX_EXT_DLL_NAME_LEN-1);
137     return TRUE;
138 }
139
140 extern "C" BOOL WINAPI GetFilterVersion(PHTTP_FILTER_VERSION pVer)
141 {
142     if (!pVer)
143         return FALSE;
144
145     try
146     {
147         ShibTargetConfig::preinit();
148         g_Config = &(ShibTargetConfig::init(SHIBTARGET_SHIRE, getenv("SHIBCONFIG")));
149         ShibINI& ini = g_Config->getINI();
150
151         // Create the RPC Handle TLS key.
152         rpc_handle_key=ThreadKey::create(destroy_handle);
153
154         Category& log=Category::getInstance("isapi_shib.GetFilterVersion");
155
156         // Read site-specific settings for each instance ID we can find.
157         unsigned short i=1;
158         char iid[8];
159         sprintf(iid,"%u",i++);
160         string hostname;
161         while (ini.get_tag("isapi",iid,false,&hostname))
162         {
163             log.info("configuring for site ID (%d), hostname (%s)",i-1,hostname.empty() ? "null" : hostname.c_str());
164
165             // If no section exists for the host, mark it as a "skip" site.
166             if (!ini.exists(hostname))
167             {
168                 log.info("skipping site ID (%d)",i-1);
169                 g_Sites.push_back(settings_t());
170                 sprintf(iid,"%u",i++);
171                 continue;
172             }
173             
174             settings_t settings(hostname);
175             
176             // Content matching string.
177             string mustcontain;
178             if (ini.get_tag(hostname,"mustContain",true,&mustcontain) && !mustcontain.empty())
179             {
180                 char* buf=strdup(mustcontain.c_str());
181                 _strupr(buf);
182                 char* start=buf;
183                 while (char* sep=strchr(start,';'))
184                 {
185                     *sep='\0';
186                     if (*start)
187                         settings.m_mustContain.push_back(start);
188                     start=sep+1;
189                 }
190                 if (*start)
191                     settings.m_mustContain.push_back(start);
192                 free(buf);
193             }
194             
195             g_Sites.push_back(settings);
196             sprintf(iid,"%u",i++);
197             hostname.erase();
198         }
199     }
200     catch (SAMLException&)
201     {
202         LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
203                 "Filter startup failed with SAML exception, check shire log for help.");
204         return FALSE;
205     }
206     catch (runtime_error& e)
207     {
208         LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
209         return FALSE;
210     }
211     catch (...)
212     {
213         LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Filter startup failed with unexpected exception.");
214         return FALSE;
215     }
216
217     pVer->dwFilterVersion=HTTP_FILTER_REVISION;
218     strncpy(pVer->lpszFilterDesc,"Shibboleth ISAPI Filter",SF_MAX_FILTER_DESC_LEN);
219     pVer->dwFlags=(SF_NOTIFY_ORDER_HIGH |
220                    SF_NOTIFY_SECURE_PORT |
221                    SF_NOTIFY_NONSECURE_PORT |
222                    SF_NOTIFY_PREPROC_HEADERS |
223                    SF_NOTIFY_LOG);
224     LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter initialized...");
225     return TRUE;
226 }
227
228 extern "C" BOOL WINAPI TerminateExtension(DWORD)
229 {
230     return TRUE;    // cleanup should happen when filter unloads
231 }
232
233 extern "C" BOOL WINAPI TerminateFilter(DWORD)
234 {
235     delete rpc_handle_key;
236     if (g_Config)
237         g_Config->shutdown();
238     g_Config = NULL;
239     LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter shut down...");
240     return TRUE;
241 }
242
243 /* Next up, some suck-free versions of various APIs.
244
245    You DON'T require people to guess the buffer size and THEN tell them the right size.
246    Returning an LPCSTR is apparently way beyond their ken. Not to mention the fact that
247    constant strings aren't typed as such, making it just that much harder. These versions
248    are now updated to use a special growable buffer object, modeled after the standard
249    string class. The standard string won't work because they left out the option to
250    pre-allocate a non-constant buffer.
251 */
252
253 class dynabuf
254 {
255 public:
256     dynabuf() { bufptr=NULL; buflen=0; }
257     dynabuf(size_t s) { bufptr=new char[buflen=s]; *bufptr=0; }
258     ~dynabuf() { delete[] bufptr; }
259     size_t length() const { return bufptr ? strlen(bufptr) : 0; }
260     size_t size() const { return buflen; }
261     bool empty() const { return length()==0; }
262     void reserve(size_t s, bool keep=false);
263     void erase() { if (bufptr) memset(bufptr,0,buflen); }
264     operator char*() { return bufptr; }
265     bool operator ==(const char* s) const;
266     bool operator !=(const char* s) const { return !(*this==s); }
267 private:
268     char* bufptr;
269     size_t buflen;
270 };
271
272 void dynabuf::reserve(size_t s, bool keep)
273 {
274     if (s<=buflen)
275         return;
276     char* p=new char[s];
277     if (keep)
278         while (buflen--)
279             p[buflen]=bufptr[buflen];
280     buflen=s;
281     delete[] bufptr;
282     bufptr=p;
283 }
284
285 bool dynabuf::operator==(const char* s) const
286 {
287     if (buflen==NULL || s==NULL)
288         return (buflen==NULL && s==NULL);
289     else
290         return strcmp(bufptr,s)==0;
291 }
292
293 void GetServerVariable(PHTTP_FILTER_CONTEXT pfc, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
294     throw (bad_alloc, DWORD)
295 {
296     s.erase();
297     s.reserve(size);
298     size=s.size();
299
300     while (!pfc->GetServerVariable(pfc,lpszVariable,s,&size))
301     {
302         // Grumble. Check the error.
303         DWORD e=GetLastError();
304         if (e==ERROR_INSUFFICIENT_BUFFER)
305             s.reserve(size);
306         else
307             break;
308     }
309     if (bRequired && s.empty())
310         throw ERROR_NO_DATA;
311 }
312
313 void GetServerVariable(LPEXTENSION_CONTROL_BLOCK lpECB, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
314     throw (bad_alloc, DWORD)
315 {
316     s.erase();
317     s.reserve(size);
318     size=s.size();
319
320     while (lpECB->GetServerVariable(lpECB->ConnID,lpszVariable,s,&size))
321     {
322         // Grumble. Check the error.
323         DWORD e=GetLastError();
324         if (e==ERROR_INSUFFICIENT_BUFFER)
325             s.reserve(size);
326         else
327             break;
328     }
329     if (bRequired && s.empty())
330         throw ERROR_NO_DATA;
331 }
332
333 void GetHeader(PHTTP_FILTER_PREPROC_HEADERS pn, PHTTP_FILTER_CONTEXT pfc,
334                LPSTR lpszName, dynabuf& s, DWORD size=80, bool bRequired=true)
335     throw (bad_alloc, DWORD)
336 {
337     s.erase();
338     s.reserve(size);
339     size=s.size();
340
341     while (!pn->GetHeader(pfc,lpszName,s,&size))
342     {
343         // Grumble. Check the error.
344         DWORD e=GetLastError();
345         if (e==ERROR_INSUFFICIENT_BUFFER)
346             s.reserve(size);
347         else
348             break;
349     }
350     if (bRequired && s.empty())
351         throw ERROR_NO_DATA;
352 }
353
354 inline char hexchar(unsigned short s)
355 {
356     return (s<=9) ? ('0' + s) : ('A' + s - 10);
357 }
358
359 string url_encode(const char* url) throw (bad_alloc)
360 {
361     static char badchars[]="\"\\+<>#%{}|^~[]`;/?:@=&";
362     string s;
363     for (const char* pch=url; *pch; pch++)
364     {
365         if (strchr(badchars,*pch)!=NULL || *pch<=0x1F || *pch>=0x7F)
366             s=s + '%' + hexchar(*pch >> 4) + hexchar(*pch & 0x0F);
367         else
368             s+=*pch;
369     }
370     return s;
371 }
372
373 string get_target(PHTTP_FILTER_CONTEXT pfc, PHTTP_FILTER_PREPROC_HEADERS pn, settings_t& site)
374 {
375     // Reconstructing the requested URL is not fun. Apparently, the PREPROC_HEADERS
376     // event means way pre. As in, none of the usual CGI headers are in place yet.
377     // It's actually almost easier, in a way, because all the path-info and query
378     // stuff is in one place, the requested URL, which we can get. But we have to
379     // reconstruct the protocol/host pair using tweezers.
380     string s;
381     if (pfc->fIsSecurePort)
382         s="https://";
383     else
384         s="http://";
385
386     // We use the "normalizeRequest" tag to decide how to obtain the server's name.
387     dynabuf buf(256);
388     string tag;
389     if (g_Config->getINI().get_tag(site.m_name,"normalizeRequest",true,&tag) && ShibINI::boolean(tag))
390     {
391         s+=site.m_name;
392     }
393     else
394     {
395         GetServerVariable(pfc,"SERVER_NAME",buf);
396         s+=buf;
397     }
398
399     GetServerVariable(pfc,"SERVER_PORT",buf,10);
400     if (buf!=(pfc->fIsSecurePort ? "443" : "80"))
401         s=s + ':' + static_cast<char*>(buf);
402
403     GetHeader(pn,pfc,"url",buf,256,false);
404     s+=buf;
405
406     return s;
407 }
408
409 string get_shire_location(settings_t& site, const char* target)
410 {
411     string shireURL;
412     if (g_Config->getINI().get_tag(site.m_name,"shireURL",true,&shireURL) && !shireURL.empty())
413     {
414         if (shireURL[0]!='/')
415             return shireURL;
416         const char* colon=strchr(target,':');
417         const char* slash=strchr(colon+3,'/');
418         string s(target,slash-target);
419         s+=shireURL;
420         return s;
421     }
422     return shireURL;
423 }
424
425 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const char* msg)
426 {
427     LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
428     static const char* ctype="Content-Type: text/html\r\n";
429     pfc->AddResponseHeaders(pfc,const_cast<char*>(ctype),0);
430     pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",0,0);
431     static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Filter Error</TITLE></HEAD><BODY>"
432                             "<H1>Shibboleth Filter Error</H1>";
433     DWORD resplen=strlen(xmsg);
434     pfc->WriteClient(pfc,(LPVOID)xmsg,&resplen,0);
435     resplen=strlen(msg);
436     pfc->WriteClient(pfc,(LPVOID)msg,&resplen,0);
437     static const char* xmsg2="</BODY></HTML>";
438     resplen=strlen(xmsg2);
439     pfc->WriteClient(pfc,(LPVOID)xmsg2,&resplen,0);
440     return SF_STATUS_REQ_FINISHED;
441 }
442
443 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const char* filename, ShibMLP& mlp)
444 {
445     ifstream infile(filename);
446     if (!infile)
447         return WriteClientError(pfc,"Unable to open error template, check settings.");   
448     string res = mlp.run(infile);
449
450     static const char* ctype="Content-Type: text/html\r\n";
451     pfc->AddResponseHeaders(pfc,const_cast<char*>(ctype),0);
452     pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",0,0);
453     DWORD resplen=res.length();
454     pfc->WriteClient(pfc,(LPVOID)res.c_str(),&resplen,0);
455     return SF_STATUS_REQ_FINISHED;
456 }
457
458 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
459 {
460     // Is this a log notification?
461     if (notificationType==SF_NOTIFY_LOG)
462     {
463         if (pfc->pFilterContext)
464             ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=static_cast<LPCSTR>(pfc->pFilterContext);
465         return SF_STATUS_REQ_NEXT_NOTIFICATION;
466     }
467
468     PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
469     try
470     {
471         // Determine web site number. This can't really fail, I don't think.
472         dynabuf buf(128);
473         ULONG site_id=0;
474         GetServerVariable(pfc,"INSTANCE_ID",buf,10);
475         if ((site_id=strtoul(buf,NULL,10))==0)
476             return WriteClientError(pfc,"IIS site instance appears to be invalid.");
477
478         // Match site instance to site settings.
479         if (site_id>g_Sites.size() || g_Sites[site_id-1].m_name.length()==0)
480             return SF_STATUS_REQ_NEXT_NOTIFICATION;
481         settings_t& site=g_Sites[site_id-1];
482
483         string target_url=get_target(pfc,pn,site);
484         string shire_url=get_shire_location(site,target_url.c_str());
485
486         // If the user is accessing the SHIRE acceptance point, pass it on.
487         if (target_url.find(shire_url)!=string::npos)
488             return SF_STATUS_REQ_NEXT_NOTIFICATION;
489
490         // Get the url request and scan for the must-contain string.
491         if (!site.m_mustContain.empty())
492         {
493             char* upcased=new char[target_url.length()+1];
494             strcpy(upcased,target_url.c_str());
495             _strupr(upcased);
496             for (vector<string>::const_iterator index=site.m_mustContain.begin(); index!=site.m_mustContain.end(); index++)
497                 if (strstr(upcased,index->c_str()))
498                     break;
499             delete[] upcased;
500             if (index==site.m_mustContain.end())
501                 return SF_STATUS_REQ_NEXT_NOTIFICATION;
502         }
503
504         // SSL content check.
505         ShibINI& ini=g_Config->getINI();
506         string tag;
507         if (ini.get_tag(site.m_name,"contentSSLOnly",true,&tag) && ShibINI::boolean(tag) && !pfc->fIsSecurePort)
508         {
509             return WriteClientError(pfc,
510                 "This server is configured to deny non-SSL requests for secure resources. "
511                 "Try your request again using https instead of http.");
512         }
513
514         ostringstream threadid;
515         threadid << "[" << getpid() << "] shire" << '\0';
516         saml::NDC ndc(threadid.str().c_str());
517
518         // Set SHIRE policies.
519         SHIREConfig config;
520         config.checkIPAddress = (ini.get_tag(site.m_name,"checkIPAddress",true,&tag) && ShibINI::boolean(tag));
521         config.lifetime=config.timeout=0;
522         tag.erase();
523         if (ini.get_tag(site.m_name, "authLifetime", true, &tag))
524             config.lifetime=strtoul(tag.c_str(),NULL,10);
525         tag.erase();
526         if (ini.get_tag(site.m_name, "authTimeout", true, &tag))
527             config.timeout=strtoul(tag.c_str(),NULL,10);
528
529         // Pull the config data we need to handle the various possible conditions.
530         string shib_cookie;
531         if (!ini.get_tag(site.m_name, "cookieName", true, &shib_cookie))
532             return WriteClientError(pfc,"The cookieName configuration setting is missing, check configuration.");
533     
534         string wayfLocation;
535         if (!ini.get_tag(site.m_name, "wayfURL", true, &wayfLocation))
536             return WriteClientError(pfc,"The wayfURL configuration setting is missing, check configuration.");
537     
538         string shireError;
539         if (!ini.get_tag(site.m_name, "shireError", true, &shireError))
540             return WriteClientError(pfc,"The shireError configuration setting is missing, check configuration.");
541
542         string accessError;
543         if (!ini.get_tag(site.m_name, "accessError", true, &shireError))
544             return WriteClientError(pfc,"The accessError configuration setting is missing, check configuration.");
545         
546         // Get an RPC handle and build the SHIRE object.
547         RPCHandle* rpc_handle = (RPCHandle*)rpc_handle_key->getData();
548         if (!rpc_handle)
549         {
550             rpc_handle = new RPCHandle(shib_target_sockname(), SHIBRPC_PROG, SHIBRPC_VERS_1);
551             rpc_handle_key->setData(rpc_handle);
552         }
553         SHIRE shire(rpc_handle, config, shire_url);
554
555         // Check for authentication cookie.
556         const char* session_id=NULL;
557         GetHeader(pn,pfc,"Cookie:",buf,128,false);
558         Category::getInstance("isapi_shib.HttpFilterProc").debug("cookie header is {%s}",(const char*)buf);
559         if (buf.empty() || !(session_id=strstr(buf,shib_cookie.c_str())) || *(session_id+shib_cookie.length())!='=')
560         {
561             // Redirect to WAYF.
562             string wayf("Location: ");
563             wayf+=wayfLocation + "?shire=" + url_encode(shire_url.c_str()) + "&target=" + url_encode(target_url.c_str()) + "\r\n";
564             // Insert the headers.
565             pfc->AddResponseHeaders(pfc,const_cast<char*>(wayf.c_str()),0);
566             pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"302 Please Wait",0,0);
567             return SF_STATUS_REQ_FINISHED;
568         }
569
570         session_id+=shib_cookie.length() + 1;   /* Skip over the '=' */
571         char* cookieend=strchr(session_id,';');
572         if (cookieend)
573             *cookieend = '\0';  /* Ignore anyting after a ; */
574   
575         // Make sure this session is still valid.
576         RPCError* status = NULL;
577         ShibMLP markupProcessor;
578         bool has_tag = ini.get_tag(site.m_name, "supportContact", true, &tag);
579         markupProcessor.insert("supportContact", has_tag ? tag : "");
580         has_tag = ini.get_tag(site.m_name, "logoLocation", true, &tag);
581         markupProcessor.insert("logoLocation", has_tag ? tag : "");
582         markupProcessor.insert("requestURL", target_url);
583     
584         dynabuf abuf(16);
585         GetServerVariable(pfc,"REMOTE_ADDR",abuf,16);
586         try {
587             status = shire.sessionIsValid(session_id, abuf, target_url.c_str());
588         }
589         catch (ShibTargetException &e) {
590             markupProcessor.insert("errorType", "SHIRE Processing Error");
591             markupProcessor.insert("errorText", e.what());
592             markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
593             return WriteClientError(pfc, shireError.c_str(), markupProcessor);
594         }
595         catch (...) {
596             markupProcessor.insert("errorType", "SHIRE Processing Error");
597             markupProcessor.insert("errorText", "Unexpected Exception");
598             markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
599             return WriteClientError(pfc, shireError.c_str(), markupProcessor);
600         }
601     
602         // Check the status
603         if (status->isError()) {
604             if (status->isRetryable()) {
605                 // Redirect to WAYF.
606                 delete status;
607                 string wayf("Location: ");
608                 wayf+=wayfLocation + "?shire=" + url_encode(shire_url.c_str()) + "&target=" + url_encode(target_url.c_str()) + "\r\n";
609                 // Insert the headers.
610                 pfc->AddResponseHeaders(pfc,const_cast<char*>(wayf.c_str()),0);
611                 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"302 Please Wait",0,0);
612                 return SF_STATUS_REQ_FINISHED;
613             }
614             else {
615                 // return the error page to the user
616                 markupProcessor.insert(*status);
617                 delete status;
618                 return WriteClientError(pfc, shireError.c_str(), markupProcessor);
619             }
620         }
621         delete status;
622     
623         // Move to RM phase.
624         RMConfig rm_config;
625         rm_config.checkIPAddress = config.checkIPAddress;
626         RM rm(rpc_handle,rm_config);
627
628         // Get the attributes.
629         vector<SAMLAssertion*> assertions;
630         SAMLAuthenticationStatement* sso_statement=NULL;
631         status = rm.getAssertions(session_id, buf, target_url.c_str(), assertions, &sso_statement);
632     
633         if (status->isError()) {
634             string rmError;
635             if (!ini.get_tag(site.m_name, "rmError", true, &shireError))
636                 return WriteClientError(pfc,"The rmError configuration setting is missing, check configuration.");
637     
638             markupProcessor.insert(*status);
639             delete status;
640             return WriteClientError(pfc, rmError.c_str(), markupProcessor);
641         }
642         delete status;
643
644         // Only allow a single assertion...
645         if (assertions.size() > 1) {
646             for (int k = 0; k < assertions.size(); k++)
647               delete assertions[k];
648             delete sso_statement;
649             return WriteClientError(pfc, accessError.c_str(), markupProcessor);
650         }
651
652         // Get the AAP providers, which contain the attribute policy info.
653         Iterator<IAAP*> provs=ShibConfig::getConfig().getAAPProviders();
654     
655         // Clear out the list of mapped attributes
656         while (provs.hasNext())
657         {
658             IAAP* aap=provs.next();
659             aap->lock();
660             try
661             {
662                 Iterator<const IAttributeRule*> rules=aap->getAttributeRules();
663                 while (rules.hasNext())
664                 {
665                     const char* header=rules.next()->getHeader();
666                     if (header)
667                         pn->SetHeader(pfc,const_cast<char*>(header),"");
668                 }
669             }
670             catch(...)
671             {
672                 aap->unlock();
673                 for (int k = 0; k < assertions.size(); k++)
674                   delete assertions[k];
675                 delete sso_statement;
676                 throw;
677             }
678             aap->unlock();
679         }
680         provs.reset();
681
682         // Clear relevant headers.
683         pn->SetHeader(pfc,"remote-user:","");
684         pn->SetHeader(pfc,"Shib-Attributes:","");
685         pn->SetHeader(pfc,"Shib-Origin-Site:","");
686         pn->SetHeader(pfc,"Shib-Authentication-Method:","");
687
688         // Maybe export the assertion.
689         if (ini.get_tag(site.m_name,"exportAssertion",true,&tag) && ShibINI::boolean(tag))
690         {
691             string assertion;
692             RM::serialize(*(assertions[0]), assertion);
693             string::size_type lfeed;
694             while ((lfeed=assertion.find('\n'))!=string::npos)
695                 assertion.erase(lfeed,1);
696             pn->SetHeader(pfc,"Shib-Attributes:",const_cast<char*>(assertion.c_str()));
697         }
698         
699         if (sso_statement)
700         {
701             auto_ptr<char> os(XMLString::transcode(sso_statement->getSubject()->getNameQualifier()));
702             auto_ptr<char> am(XMLString::transcode(sso_statement->getAuthMethod()));
703             pn->SetHeader(pfc,"Shib-Origin-Site:", os.get());
704             pn->SetHeader(pfc,"Shib-Authentication-Method:", am.get());
705         }
706
707         // Export the attributes. Only supports a single statement.
708         Iterator<SAMLAttribute*> j = assertions.size()==1 ? RM::getAttributes(*(assertions[0])) : EMPTY(SAMLAttribute*);
709         while (j.hasNext())
710         {
711             SAMLAttribute* attr=j.next();
712     
713             // Are we supposed to export it?
714             const char* hname=NULL;
715             AAP wrapper(attr->getName(),attr->getNamespace());
716             if (!wrapper.fail())
717                 hname=wrapper->getHeader();
718             if (hname)
719             {
720                 Iterator<string> vals=attr->getSingleByteValues();
721                 if (!strcmp(hname,"REMOTE_USER") && vals.hasNext())
722                 {
723                     char* principal=const_cast<char*>(vals.next().c_str());
724                     pn->SetHeader(pfc,"remote-user:",principal);
725                     pfc->pFilterContext=pfc->AllocMem(pfc,strlen(principal)+1,0);
726                     if (pfc->pFilterContext)
727                         strcpy(static_cast<char*>(pfc->pFilterContext),principal);
728                 }    
729                 else
730                 {
731                     string header;
732                     for (int it = 0; vals.hasNext(); it++) {
733                         string value = vals.next();
734                         for (string::size_type pos = value.find_first_of(";", string::size_type(0)); pos != string::npos; pos = value.find_first_of(";", pos)) {
735                             value.insert(pos, "\\");
736                             pos += 2;
737                         }
738                         if (it == 0)
739                             header=value;
740                         else
741                             header=header + ';' + value;
742                     }
743                     string hname2=string(hname) + ':';
744                     pn->SetHeader(pfc,const_cast<char*>(hname2.c_str()),const_cast<char*>(header.c_str()));
745                 }
746             }
747         }
748     
749         // clean up memory
750         for (int k = 0; k < assertions.size(); k++)
751           delete assertions[k];
752         delete sso_statement;
753
754         return SF_STATUS_REQ_NEXT_NOTIFICATION;
755     }
756     catch(bad_alloc)
757     {
758         return WriteClientError(pfc,"Out of Memory");
759     }
760     catch(DWORD e)
761     {
762         if (e==ERROR_NO_DATA)
763             return WriteClientError(pfc,"A required variable or header was empty.");
764         else
765             return WriteClientError(pfc,"Server detected unexpected IIS error.");
766     }
767     catch(...)
768     {
769         return WriteClientError(pfc,"Server caught an unknown exception.");
770     }
771
772     return WriteClientError(pfc,"Server reached unreachable code!");
773 }
774
775 string get_target(LPEXTENSION_CONTROL_BLOCK lpECB, settings_t& site)
776 {
777     string s;
778     dynabuf buf(256);
779     GetServerVariable(lpECB,"HTTPS",buf);
780     bool SSL=(buf=="on");
781     if (SSL)
782         s="https://";
783     else
784         s="http://";
785
786     // We use the "normalizeRequest" tag to decide how to obtain the server's name.
787     string tag;
788     if (g_Config->getINI().get_tag(site.m_name,"normalizeRequest",true,&tag) && ShibINI::boolean(tag))
789     {
790         s+=site.m_name;
791     }
792     else
793     {
794         GetServerVariable(lpECB,"SERVER_NAME",buf);
795         s+=buf;
796     }
797
798     GetServerVariable(lpECB,"SERVER_PORT",buf,10);
799     if (buf!=(SSL ? "443" : "80"))
800         s=s + ':' + static_cast<char*>(buf);
801
802     GetServerVariable(lpECB,"URL",buf,255);
803     s+=buf;
804
805     return s;
806 }
807
808 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* msg)
809 {
810     LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
811     static const char* ctype="Content-Type: text/html\r\n";
812     lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
813     static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Error</TITLE></HEAD><BODY><H1>Shibboleth Error</H1>";
814     DWORD resplen=strlen(xmsg);
815     lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg,&resplen,HSE_IO_SYNC);
816     resplen=strlen(msg);
817     lpECB->WriteClient(lpECB->ConnID,(LPVOID)msg,&resplen,HSE_IO_SYNC);
818     static const char* xmsg2="</BODY></HTML>";
819     resplen=strlen(xmsg2);
820     lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg2,&resplen,HSE_IO_SYNC);
821     return HSE_STATUS_SUCCESS;
822 }
823
824 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* filename, ShibMLP& mlp)
825 {
826     ifstream infile(filename);
827     if (!infile)
828         return WriteClientError(lpECB,"Unable to open error template, check settings.");   
829
830     string res = mlp.run(infile);
831     static const char* ctype="Content-Type: text/html\r\n";
832     lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
833     DWORD resplen=res.length();
834     lpECB->WriteClient(lpECB->ConnID,(LPVOID)res.c_str(),&resplen,0);
835     return HSE_STATUS_SUCCESS;
836 }
837
838 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
839 {
840     ostringstream threadid;
841     threadid << "[" << getpid() << "] shire" << '\0';
842     saml::NDC ndc(threadid.str().c_str());
843
844     ShibINI& ini = g_Config->getINI();
845     string shireError;
846     ShibMLP markupProcessor;
847
848     try
849     {
850         // Determine web site number. This can't really fail, I don't think.
851         dynabuf buf(128);
852         ULONG site_id=0;
853         GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
854         if ((site_id=strtoul(buf,NULL,10))==0)
855             return WriteClientError(lpECB,"IIS site instance appears to be invalid.");
856
857         // Match site instance to site settings.
858         if (site_id>g_Sites.size() || g_Sites[site_id-1].m_name.length()==0)
859             return WriteClientError(lpECB,"Shibboleth filter not configured for this web site.");
860         settings_t& site=g_Sites[site_id-1];
861
862         if (!ini.get_tag(site.m_name, "shireError", true, &shireError))
863             return WriteClientError(lpECB,"The shireError configuration setting is missing, check configuration.");
864
865         string target_url=get_target(lpECB,site);
866         string shire_url = get_shire_location(site,target_url.c_str());
867
868         // Set SHIRE policies.
869         SHIREConfig config;
870         string tag;
871         config.checkIPAddress = (ini.get_tag(site.m_name,"checkIPAddress",true,&tag) && ShibINI::boolean(tag));
872         config.lifetime=config.timeout=0;
873         tag.erase();
874         if (ini.get_tag(site.m_name, "authLifetime", true, &tag))
875             config.lifetime=strtoul(tag.c_str(),NULL,10);
876         tag.erase();
877         if (ini.get_tag(site.m_name, "authTimeout", true, &tag))
878             config.timeout=strtoul(tag.c_str(),NULL,10);
879
880         // Pull the config data we need to handle the various possible conditions.
881         string shib_cookie;
882         if (!ini.get_tag(site.m_name, "cookieName", true, &shib_cookie))
883             return WriteClientError(lpECB,"The cookieName configuration setting is missing, check configuration.");
884     
885         string wayfLocation;
886         if (!ini.get_tag(site.m_name, "wayfURL", true, &wayfLocation))
887             return WriteClientError(lpECB,"The wayfURL configuration setting is missing, check configuration.");
888     
889         bool has_tag = ini.get_tag(site.m_name, "supportContact", true, &tag);
890         markupProcessor.insert("supportContact", has_tag ? tag : "");
891         has_tag = ini.get_tag(site.m_name, "logoLocation", true, &tag);
892         markupProcessor.insert("logoLocation", has_tag ? tag : "");
893         markupProcessor.insert("requestURL", target_url.c_str());
894   
895         // Get an RPC handle and build the SHIRE object.
896         RPCHandle* rpc_handle = (RPCHandle*)rpc_handle_key->getData();
897         if (!rpc_handle)
898         {
899             rpc_handle = new RPCHandle(shib_target_sockname(), SHIBRPC_PROG, SHIBRPC_VERS_1);
900             rpc_handle_key->setData(rpc_handle);
901         }
902         SHIRE shire(rpc_handle, config, shire_url.c_str());
903
904         // Process SHIRE POST
905         if (ini.get_tag(site.m_name, "shireSSLOnly", true, &tag) && ShibINI::boolean(tag))
906         {
907             // Make sure this is SSL, if it should be.
908             GetServerVariable(lpECB,"HTTPS",buf,10);
909             if (buf!="on")
910                 throw ShibTargetException(SHIBRPC_OK,"blocked non-SSL access to SHIRE POST processor");
911         }
912         
913         // Make sure this is a POST
914         if (stricmp(lpECB->lpszMethod,"POST"))
915             throw ShibTargetException(SHIBRPC_OK,"blocked non-POST to SHIRE POST processor");
916
917         // Sure sure this POST is an appropriate content type
918         if (!lpECB->lpszContentType || stricmp(lpECB->lpszContentType,"application/x-www-form-urlencoded"))
919             throw ShibTargetException(SHIBRPC_OK,"blocked bad content-type to SHIRE POST processor");
920     
921         // Make sure the "bytes sent" is a reasonable number and that we have all of it.
922         if (lpECB->cbTotalBytes > 1024*1024) // 1MB?
923             throw ShibTargetException (SHIBRPC_OK,"blocked too-large a post to SHIRE POST processor");
924         else if (lpECB->cbTotalBytes>lpECB->cbAvailable)
925             throw ShibTargetException (SHIBRPC_OK,"blocked incomplete post to SHIRE POST processor");
926
927         // Parse the incoming data.
928         HQUERY params=ParseQuery(lpECB);
929         if (!params)
930             throw ShibTargetException (SHIBRPC_OK,"unable to parse form data");
931
932         // Make sure the TARGET parameter exists
933         const char* target = QueryValue(params,"TARGET");
934         if (!target || *target == '\0')
935             throw ShibTargetException(SHIBRPC_OK,"SHIRE POST failed to find TARGET parameter");
936     
937         // Make sure the SAMLResponse parameter exists
938         const char* post = QueryValue(params,"SAMLResponse");
939         if (!post || *post == '\0')
940             throw ShibTargetException (SHIBRPC_OK,"SHIRE POST failed to find SAMLResponse parameter");
941
942         GetServerVariable(lpECB,"REMOTE_ADDR",buf,16);
943
944         // Process the post.
945         string cookie;
946         RPCError* status = shire.sessionCreate(post,buf,cookie);
947     
948         if (status->isError()) {
949             if (status->isRetryable()) {
950                 delete status;
951                 string wayf=wayfLocation + "?shire=" + url_encode(shire_url.c_str()) + "&target=" + url_encode(target);
952                 DWORD len=wayf.length();
953                 if (lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_URL_REDIRECT_RESP,(LPVOID)wayf.c_str(),&len,0))
954                     return HSE_STATUS_SUCCESS;
955                 return HSE_STATUS_ERROR;
956             }
957     
958             // Return this error to the user.
959             markupProcessor.insert(*status);
960             delete status;
961             return WriteClientError(lpECB,shireError.c_str(),markupProcessor);
962         }
963         delete status;
964     
965         // We've got a good session, set the cookie and redirect to target.
966         shib_cookie = "Set-Cookie: " + shib_cookie + '=' + cookie + "; path=/\r\n" 
967             "Location: " + target + "\r\n"
968             "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
969             "Cache-Control: private,no-store,no-cache\r\n"
970             "Connection: close\r\n";
971         HSE_SEND_HEADER_EX_INFO hinfo;
972         hinfo.pszStatus="302 Moved";
973         hinfo.pszHeader=shib_cookie.c_str();
974         hinfo.cchStatus=9;
975         hinfo.cchHeader=shib_cookie.length();
976         hinfo.fKeepConn=FALSE;
977         if (lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER_EX,&hinfo,0,0))
978             return HSE_STATUS_SUCCESS;
979     }
980     catch (ShibTargetException &e) {
981         markupProcessor.insert ("errorType", "SHIRE Processing Error");
982         markupProcessor.insert ("errorText", e.what());
983         markupProcessor.insert ("errorDesc", "An error occurred while processing your request.");
984         return WriteClientError(lpECB,shireError.c_str(),markupProcessor);
985     }
986     catch (...) {
987         markupProcessor.insert ("errorType", "SHIRE Processing Error");
988         markupProcessor.insert ("errorText", "Unexpected Exception");
989         markupProcessor.insert ("errorDesc", "An error occurred while processing your request.");
990         return WriteClientError(lpECB,shireError.c_str(),markupProcessor);
991     }
992     
993     return HSE_STATUS_ERROR;
994 }
995