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