Added a bit of logging.
[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) *bufptr=0; }
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     pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",0,0);
429     static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Filter Error</TITLE></HEAD><BODY>"
430                             "<H1>Shibboleth Filter Error</H1>";
431     DWORD resplen=strlen(xmsg);
432     pfc->WriteClient(pfc,(LPVOID)xmsg,&resplen,0);
433     resplen=strlen(msg);
434     pfc->WriteClient(pfc,(LPVOID)msg,&resplen,0);
435     static const char* xmsg2="</BODY></HTML>";
436     resplen=strlen(xmsg2);
437     pfc->WriteClient(pfc,(LPVOID)xmsg2,&resplen,0);
438     return SF_STATUS_REQ_FINISHED;
439 }
440
441 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const char* filename, ShibMLP& mlp)
442 {
443     ifstream infile(filename);
444     if (!infile)
445         return WriteClientError(pfc,"Unable to open error template, check settings.");   
446
447     string res = mlp.run(infile);
448     pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",0,0);
449     DWORD resplen=res.length();
450     pfc->WriteClient(pfc,(LPVOID)res.c_str(),&resplen,0);
451     return SF_STATUS_REQ_FINISHED;
452 }
453
454 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
455 {
456     // Is this a log notification?
457     if (notificationType==SF_NOTIFY_LOG)
458     {
459         if (pfc->pFilterContext)
460             ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=static_cast<LPCSTR>(pfc->pFilterContext);
461         return SF_STATUS_REQ_NEXT_NOTIFICATION;
462     }
463
464     PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
465     try
466     {
467         // Determine web site number. This can't really fail, I don't think.
468         dynabuf buf(128);
469         ULONG site_id=0;
470         GetServerVariable(pfc,"INSTANCE_ID",buf,10);
471         if ((site_id=strtoul(buf,NULL,10))==0)
472             return WriteClientError(pfc,"IIS site instance appears to be invalid.");
473
474         // Match site instance to site settings.
475         if (site_id>g_Sites.size() || g_Sites[site_id-1].m_name.length()==0)
476             return SF_STATUS_REQ_NEXT_NOTIFICATION;
477         settings_t& site=g_Sites[site_id-1];
478
479         string target_url=get_target(pfc,pn,site);
480         string shire_url=get_shire_location(site,target_url.c_str());
481
482         // If the user is accessing the SHIRE acceptance point, pass it on.
483         if (target_url.find(shire_url)!=string::npos)
484             return SF_STATUS_REQ_NEXT_NOTIFICATION;
485
486         // Get the url request and scan for the must-contain string.
487         if (!site.m_mustContain.empty())
488         {
489             char* upcased=new char[target_url.length()+1];
490             strcpy(upcased,target_url.c_str());
491             _strupr(upcased);
492             for (vector<string>::const_iterator index=site.m_mustContain.begin(); index!=site.m_mustContain.end(); index++)
493                 if (strstr(upcased,index->c_str()))
494                     break;
495             delete[] upcased;
496             if (index==site.m_mustContain.end())
497                 return SF_STATUS_REQ_NEXT_NOTIFICATION;
498         }
499
500         // SSL content check.
501         ShibINI& ini=g_Config->getINI();
502         string tag;
503         if (ini.get_tag(site.m_name,"contentSSLOnly",true,&tag) && ShibINI::boolean(tag) && !pfc->fIsSecurePort)
504         {
505             return WriteClientError(pfc,
506                 "This server is configured to deny non-SSL requests for secure resources. "
507                 "Try your request again using https instead of http.");
508         }
509
510         ostringstream threadid;
511         threadid << "[" << getpid() << "] shire" << '\0';
512         saml::NDC ndc(threadid.str().c_str());
513
514         // Set SHIRE policies.
515         SHIREConfig config;
516         config.checkIPAddress = (ini.get_tag(site.m_name,"checkIPAddress",true,&tag) && ShibINI::boolean(tag));
517         config.lifetime=config.timeout=0;
518         tag.erase();
519         if (ini.get_tag(site.m_name, "authLifetime", true, &tag))
520             config.lifetime=strtoul(tag.c_str(),NULL,10);
521         tag.erase();
522         if (ini.get_tag(site.m_name, "authTimeout", true, &tag))
523             config.timeout=strtoul(tag.c_str(),NULL,10);
524
525         // Pull the config data we need to handle the various possible conditions.
526         string shib_cookie;
527         if (!ini.get_tag(site.m_name, "cookieName", true, &shib_cookie))
528             return WriteClientError(pfc,"The cookieName configuration setting is missing, check configuration.");
529     
530         string wayfLocation;
531         if (!ini.get_tag(site.m_name, "wayfURL", true, &wayfLocation))
532             return WriteClientError(pfc,"The wayfURL configuration setting is missing, check configuration.");
533     
534         string shireError;
535         if (!ini.get_tag(site.m_name, "shireError", true, &shireError))
536             return WriteClientError(pfc,"The shireError configuration setting is missing, check configuration.");
537
538         string accessError;
539         if (!ini.get_tag(site.m_name, "accessError", true, &shireError))
540             return WriteClientError(pfc,"The accessError configuration setting is missing, check configuration.");
541         
542         // Get an RPC handle and build the SHIRE object.
543         RPCHandle* rpc_handle = (RPCHandle*)rpc_handle_key->getData();
544         if (!rpc_handle)
545         {
546             rpc_handle = new RPCHandle(shib_target_sockname(), SHIBRPC_PROG, SHIBRPC_VERS_1);
547             rpc_handle_key->setData(rpc_handle);
548         }
549         SHIRE shire(rpc_handle, config, shire_url);
550
551         // Check for authentication cookie.
552         const char* session_id=NULL;
553         GetHeader(pn,pfc,"Cookie:",buf,128,false);
554         if (buf.empty() || !(session_id=strstr(buf,shib_cookie.c_str())) || *(session_id+shib_cookie.length())!='=')
555         {
556             // Redirect to WAYF.
557             string wayf("Location: ");
558             wayf+=wayfLocation + "?shire=" + url_encode(shire_url.c_str()) + "&target=" + url_encode(target_url.c_str()) + "\r\n";
559             // Insert the headers.
560             pfc->AddResponseHeaders(pfc,const_cast<char*>(wayf.c_str()),0);
561             pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"302 Please Wait",0,0);
562             return SF_STATUS_REQ_FINISHED;
563         }
564
565         session_id+=shib_cookie.length() + 1;   /* Skip over the '=' */
566         char* cookieend=strchr(session_id,';');
567         if (cookieend)
568             *cookieend = '\0';  /* Ignore anyting after a ; */
569   
570         // Make sure this session is still valid.
571         RPCError* status = NULL;
572         ShibMLP markupProcessor;
573         bool has_tag = ini.get_tag(site.m_name, "supportContact", true, &tag);
574         markupProcessor.insert("supportContact", has_tag ? tag : "");
575         has_tag = ini.get_tag(site.m_name, "logoLocation", true, &tag);
576         markupProcessor.insert("logoLocation", has_tag ? tag : "");
577         markupProcessor.insert("requestURL", target_url);
578     
579         GetServerVariable(pfc,"REMOTE_ADDR",buf,16);
580         try {
581             status = shire.sessionIsValid(session_id, buf, target_url.c_str());
582         }
583         catch (ShibTargetException &e) {
584             markupProcessor.insert("errorType", "SHIRE Processing Error");
585             markupProcessor.insert("errorText", e.what());
586             markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
587             return WriteClientError(pfc, shireError.c_str(), markupProcessor);
588         }
589         catch (...) {
590             markupProcessor.insert("errorType", "SHIRE Processing Error");
591             markupProcessor.insert("errorText", "Unexpected Exception");
592             markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
593             return WriteClientError(pfc, shireError.c_str(), markupProcessor);
594         }
595     
596         // Check the status
597         if (status->isError()) {
598             if (status->isRetryable()) {
599                 // Redirect to WAYF.
600                 delete status;
601                 string wayf("Location: ");
602                 wayf+=wayfLocation + "?shire=" + url_encode(shire_url.c_str()) + "&target=" + url_encode(target_url.c_str()) + "\r\n";
603                 // Insert the headers.
604                 pfc->AddResponseHeaders(pfc,const_cast<char*>(wayf.c_str()),0);
605                 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"302 Please Wait",0,0);
606                 return SF_STATUS_REQ_FINISHED;
607             }
608             else {
609                 // return the error page to the user
610                 markupProcessor.insert(*status);
611                 delete status;
612                 return WriteClientError(pfc, shireError.c_str(), markupProcessor);
613             }
614         }
615         delete status;
616     
617         // Move to RM phase.
618         RMConfig rm_config;
619         rm_config.checkIPAddress = config.checkIPAddress;
620         RM rm(rpc_handle,rm_config);
621
622         // Get the attributes.
623         vector<SAMLAssertion*> assertions;
624         SAMLAuthenticationStatement* sso_statement=NULL;
625         status = rm.getAssertions(session_id, buf, target_url.c_str(), assertions, &sso_statement);
626     
627         if (status->isError()) {
628             string rmError;
629             if (!ini.get_tag(site.m_name, "rmError", true, &shireError))
630                 return WriteClientError(pfc,"The rmError configuration setting is missing, check configuration.");
631     
632             markupProcessor.insert(*status);
633             delete status;
634             return WriteClientError(pfc, rmError.c_str(), markupProcessor);
635         }
636         delete status;
637
638         // Only allow a single assertion...
639         if (assertions.size() > 1) {
640             for (int k = 0; k < assertions.size(); k++)
641               delete assertions[k];
642             delete sso_statement;
643             return WriteClientError(pfc, accessError.c_str(), markupProcessor);
644         }
645
646         // Get the AAP providers, which contain the attribute policy info.
647         Iterator<IAAP*> provs=ShibConfig::getConfig().getAAPProviders();
648     
649         // Clear out the list of mapped attributes
650         while (provs.hasNext())
651         {
652             IAAP* aap=provs.next();
653             aap->lock();
654             try
655             {
656                 Iterator<const IAttributeRule*> rules=aap->getAttributeRules();
657                 while (rules.hasNext())
658                 {
659                     const char* header=rules.next()->getHeader();
660                     if (header)
661                         pn->SetHeader(pfc,const_cast<char*>(header),"");
662                 }
663             }
664             catch(...)
665             {
666                 aap->unlock();
667                 for (int k = 0; k < assertions.size(); k++)
668                   delete assertions[k];
669                 delete sso_statement;
670                 throw;
671             }
672             aap->unlock();
673         }
674         provs.reset();
675
676         // Clear relevant headers.
677         pn->SetHeader(pfc,"remote-user:","");
678         pn->SetHeader(pfc,"Shib-Attributes:","");
679         pn->SetHeader(pfc,"Shib-Origin-Site:","");
680         pn->SetHeader(pfc,"Shib-Authentication-Method:","");
681
682         // Maybe export the assertion.
683         if (ini.get_tag(site.m_name,"exportAssertion",true,&tag) && ShibINI::boolean(tag))
684         {
685             string assertion;
686             RM::serialize(*(assertions[0]), assertion);
687             string::size_type lfeed;
688             while ((lfeed=assertion.find('\n'))!=string::npos)
689                 assertion.erase(lfeed,1);
690             pn->SetHeader(pfc,"Shib-Attributes:",const_cast<char*>(assertion.c_str()));
691         }
692         
693         if (sso_statement)
694         {
695             auto_ptr<char> os(XMLString::transcode(sso_statement->getSubject()->getNameQualifier()));
696             auto_ptr<char> am(XMLString::transcode(sso_statement->getAuthMethod()));
697             pn->SetHeader(pfc,"Shib-Origin-Site:", os.get());
698             pn->SetHeader(pfc,"Shib-Authentication-Method:", am.get());
699         }
700
701         // Export the attributes. Only supports a single statement.
702         Iterator<SAMLAttribute*> j = assertions.size()==1 ? RM::getAttributes(*(assertions[0])) : EMPTY(SAMLAttribute*);
703         while (j.hasNext())
704         {
705             SAMLAttribute* attr=j.next();
706     
707             // Are we supposed to export it?
708             const char* hname=NULL;
709             AAP wrapper(attr->getName(),attr->getNamespace());
710             if (!wrapper.fail())
711                 hname=wrapper->getHeader();
712             if (hname)
713             {
714                 Iterator<string> vals=attr->getSingleByteValues();
715                 if (!strcmp(hname,"REMOTE_USER") && vals.hasNext())
716                 {
717                     char* principal=const_cast<char*>(vals.next().c_str());
718                     pn->SetHeader(pfc,"remote-user:",principal);
719                     pfc->pFilterContext=pfc->AllocMem(pfc,strlen(principal)+1,0);
720                     if (pfc->pFilterContext)
721                         strcpy(static_cast<char*>(pfc->pFilterContext),principal);
722                 }    
723                 else
724                 {
725                     string header;
726                     for (int it = 0; vals.hasNext(); it++) {
727                         string value = vals.next();
728                         for (string::size_type pos = value.find_first_of(";", string::size_type(0)); pos != string::npos; pos = value.find_first_of(";", pos)) {
729                             value.insert(pos, "\\");
730                             pos += 2;
731                         }
732                         if (it == 0)
733                             header=value;
734                         else
735                             header=header + ';' + value;
736                     }
737                     string hname2=string(hname) + ':';
738                     pn->SetHeader(pfc,const_cast<char*>(hname2.c_str()),const_cast<char*>(header.c_str()));
739                 }
740             }
741         }
742     
743         // clean up memory
744         for (int k = 0; k < assertions.size(); k++)
745           delete assertions[k];
746         delete sso_statement;
747
748         return SF_STATUS_REQ_NEXT_NOTIFICATION;
749     }
750     catch(bad_alloc)
751     {
752         return WriteClientError(pfc,"Out of Memory");
753     }
754     catch(DWORD e)
755     {
756         if (e==ERROR_NO_DATA)
757             return WriteClientError(pfc,"A required variable or header was empty.");
758         else
759             return WriteClientError(pfc,"Server detected unexpected IIS error.");
760     }
761     catch(...)
762     {
763         return WriteClientError(pfc,"Server caught an unknown exception.");
764     }
765
766     return WriteClientError(pfc,"Server reached unreachable code!");
767 }
768
769 string get_target(LPEXTENSION_CONTROL_BLOCK lpECB, settings_t& site)
770 {
771     string s;
772     dynabuf buf(256);
773     GetServerVariable(lpECB,"HTTPS",buf);
774     bool SSL=(buf=="on");
775     if (SSL)
776         s="https://";
777     else
778         s="http://";
779
780     // We use the "normalizeRequest" tag to decide how to obtain the server's name.
781     string tag;
782     if (g_Config->getINI().get_tag(site.m_name,"normalizeRequest",true,&tag) && ShibINI::boolean(tag))
783     {
784         s+=site.m_name;
785     }
786     else
787     {
788         GetServerVariable(lpECB,"SERVER_NAME",buf);
789         s+=buf;
790     }
791
792     GetServerVariable(lpECB,"SERVER_PORT",buf,10);
793     if (buf!=(SSL ? "443" : "80"))
794         s=s + ':' + static_cast<char*>(buf);
795
796     GetServerVariable(lpECB,"URL",buf,255);
797     s+=buf;
798
799     return s;
800 }
801
802 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* msg)
803 {
804     LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
805     lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,0);
806     static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Error</TITLE></HEAD><BODY><H1>Shibboleth Error</H1>";
807     DWORD resplen=strlen(xmsg);
808     lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg,&resplen,HSE_IO_SYNC);
809     resplen=strlen(msg);
810     lpECB->WriteClient(lpECB->ConnID,(LPVOID)msg,&resplen,HSE_IO_SYNC);
811     static const char* xmsg2="</BODY></HTML>";
812     resplen=strlen(xmsg2);
813     lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg2,&resplen,HSE_IO_SYNC);
814     return HSE_STATUS_SUCCESS;
815 }
816
817 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* filename, ShibMLP& mlp)
818 {
819     ifstream infile(filename);
820     if (!infile)
821         return WriteClientError(lpECB,"Unable to open error template, check settings.");   
822
823     string res = mlp.run(infile);
824     lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,0);
825     DWORD resplen=res.length();
826     lpECB->WriteClient(lpECB->ConnID,(LPVOID)res.c_str(),&resplen,0);
827     return HSE_STATUS_SUCCESS;
828 }
829
830 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
831 {
832     ostringstream threadid;
833     threadid << "[" << getpid() << "] shire" << '\0';
834     saml::NDC ndc(threadid.str().c_str());
835
836     ShibINI& ini = g_Config->getINI();
837     string shireError;
838     ShibMLP markupProcessor;
839
840     try
841     {
842         // Determine web site number. This can't really fail, I don't think.
843         dynabuf buf(128);
844         ULONG site_id=0;
845         GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
846         if ((site_id=strtoul(buf,NULL,10))==0)
847             return WriteClientError(lpECB,"IIS site instance appears to be invalid.");
848
849         // Match site instance to site settings.
850         if (site_id>g_Sites.size() || g_Sites[site_id-1].m_name.length()==0)
851             return WriteClientError(lpECB,"Shibboleth filter not configured for this web site.");
852         settings_t& site=g_Sites[site_id-1];
853
854         if (!ini.get_tag(site.m_name, "shireError", true, &shireError))
855             return WriteClientError(lpECB,"The shireError configuration setting is missing, check configuration.");
856
857         string target_url=get_target(lpECB,site);
858         string shire_url = get_shire_location(site,target_url.c_str());
859
860         // Set SHIRE policies.
861         SHIREConfig config;
862         string tag;
863         config.checkIPAddress = (ini.get_tag(site.m_name,"checkIPAddress",true,&tag) && ShibINI::boolean(tag));
864         config.lifetime=config.timeout=0;
865         tag.erase();
866         if (ini.get_tag(site.m_name, "authLifetime", true, &tag))
867             config.lifetime=strtoul(tag.c_str(),NULL,10);
868         tag.erase();
869         if (ini.get_tag(site.m_name, "authTimeout", true, &tag))
870             config.timeout=strtoul(tag.c_str(),NULL,10);
871
872         // Pull the config data we need to handle the various possible conditions.
873         string shib_cookie;
874         if (!ini.get_tag(site.m_name, "cookieName", true, &shib_cookie))
875             return WriteClientError(lpECB,"The cookieName configuration setting is missing, check configuration.");
876     
877         string wayfLocation;
878         if (!ini.get_tag(site.m_name, "wayfURL", true, &wayfLocation))
879             return WriteClientError(lpECB,"The wayfURL configuration setting is missing, check configuration.");
880     
881         bool has_tag = ini.get_tag(site.m_name, "supportContact", true, &tag);
882         markupProcessor.insert("supportContact", has_tag ? tag : "");
883         has_tag = ini.get_tag(site.m_name, "logoLocation", true, &tag);
884         markupProcessor.insert("logoLocation", has_tag ? tag : "");
885         markupProcessor.insert("requestURL", target_url.c_str());
886   
887         // Get an RPC handle and build the SHIRE object.
888         RPCHandle* rpc_handle = (RPCHandle*)rpc_handle_key->getData();
889         if (!rpc_handle)
890         {
891             rpc_handle = new RPCHandle(shib_target_sockname(), SHIBRPC_PROG, SHIBRPC_VERS_1);
892             rpc_handle_key->setData(rpc_handle);
893         }
894         SHIRE shire(rpc_handle, config, shire_url.c_str());
895
896         // Process SHIRE POST
897         if (ini.get_tag(site.m_name, "shireSSLOnly", true, &tag) && ShibINI::boolean(tag))
898         {
899             // Make sure this is SSL, if it should be.
900             GetServerVariable(lpECB,"HTTPS",buf,10);
901             if (buf!="on")
902                 throw ShibTargetException(SHIBRPC_OK,"blocked non-SSL access to SHIRE POST processor");
903         }
904         
905         // Make sure this is a POST
906         if (stricmp(lpECB->lpszMethod,"POST"))
907             throw ShibTargetException(SHIBRPC_OK,"blocked non-POST to SHIRE POST processor");
908
909         // Sure sure this POST is an appropriate content type
910         if (!lpECB->lpszContentType || stricmp(lpECB->lpszContentType,"application/x-www-form-urlencoded"))
911             throw ShibTargetException(SHIBRPC_OK,"blocked bad content-type to SHIRE POST processor");
912     
913         // Make sure the "bytes sent" is a reasonable number and that we have all of it.
914         if (lpECB->cbTotalBytes > 1024*1024) // 1MB?
915             throw ShibTargetException (SHIBRPC_OK,"blocked too-large a post to SHIRE POST processor");
916         else if (lpECB->cbTotalBytes>lpECB->cbAvailable)
917             throw ShibTargetException (SHIBRPC_OK,"blocked incomplete post to SHIRE POST processor");
918
919         // Parse the incoming data.
920         HQUERY params=ParseQuery(lpECB);
921         if (!params)
922             throw ShibTargetException (SHIBRPC_OK,"unable to parse form data");
923
924         // Make sure the TARGET parameter exists
925         const char* target = QueryValue(params,"TARGET");
926         if (!target || *target == '\0')
927             throw ShibTargetException(SHIBRPC_OK,"SHIRE POST failed to find TARGET parameter");
928     
929         // Make sure the SAMLResponse parameter exists
930         const char* post = QueryValue(params,"SAMLResponse");
931         if (!post || *post == '\0')
932             throw ShibTargetException (SHIBRPC_OK,"SHIRE POST failed to find SAMLResponse parameter");
933
934         GetServerVariable(lpECB,"REMOTE_ADDR",buf,16);
935
936         // Process the post.
937         string cookie;
938         RPCError* status = shire.sessionCreate(post,buf,cookie);
939     
940         if (status->isError()) {
941             if (status->isRetryable()) {
942                 delete status;
943                 string wayf=wayfLocation + "?shire=" + url_encode(shire_url.c_str()) + "&target=" + url_encode(target);
944                 DWORD len=wayf.length();
945                 if (lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_URL_REDIRECT_RESP,(LPVOID)wayf.c_str(),&len,0))
946                     return HSE_STATUS_SUCCESS;
947                 return HSE_STATUS_ERROR;
948             }
949     
950             // Return this error to the user.
951             markupProcessor.insert(*status);
952             delete status;
953             return WriteClientError(lpECB,shireError.c_str(),markupProcessor);
954         }
955         delete status;
956     
957         // We've got a good session, set the cookie and redirect to target.
958         shib_cookie = "Set-Cookie: " + shib_cookie + '=' + cookie + "; path=/\r\n" 
959             "Location: " + target + "\r\n"
960             "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
961             "Cache-Control: private,no-store,no-cache\r\n"
962             "Connection: close\r\n";
963         HSE_SEND_HEADER_EX_INFO hinfo;
964         hinfo.pszStatus="302 Moved";
965         hinfo.pszHeader=shib_cookie.c_str();
966         hinfo.cchStatus=9;
967         hinfo.cchHeader=shib_cookie.length();
968         hinfo.fKeepConn=FALSE;
969         if (lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER_EX,&hinfo,0,0))
970             return HSE_STATUS_SUCCESS;
971     }
972     catch (ShibTargetException &e) {
973         markupProcessor.insert ("errorType", "SHIRE Processing Error");
974         markupProcessor.insert ("errorText", e.what());
975         markupProcessor.insert ("errorDesc", "An error occurred while processing your request.");
976         return WriteClientError(lpECB,shireError.c_str(),markupProcessor);
977     }
978     catch (...) {
979         markupProcessor.insert ("errorType", "SHIRE Processing Error");
980         markupProcessor.insert ("errorText", "Unexpected Exception");
981         markupProcessor.insert ("errorDesc", "An error occurred while processing your request.");
982         return WriteClientError(lpECB,shireError.c_str(),markupProcessor);
983     }
984     
985     return HSE_STATUS_ERROR;
986 }
987