Added POST handler as an ISAPI extension.
[shibboleth/cpp-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         g_Config = &(ShibTargetConfig::init(SHIBTARGET_SHIRE, getenv("SHIBCONFIG")));
148         ShibINI& ini = g_Config->getINI();
149
150         // Create the RPC Handle TLS key.
151         rpc_handle_key=ThreadKey::create(destroy_handle);
152
153         // Read site-specific settings for each instance ID we can find.
154         unsigned short i=1;
155         char iid[8];
156         sprintf(iid,"%u",i++);
157         string hostname;
158         while (ini.get_tag("isapi",iid,false,&hostname))
159         {
160             // If no section exists for the host, mark it as a "skip" site.
161             if (!ini.exists(hostname))
162             {
163                 g_Sites.push_back(settings_t());
164                 sprintf(iid,"%u",i++);
165                 continue;
166             }
167             
168             settings_t settings(hostname);
169             
170             // Content matching string.
171             string mustcontain;
172             if (ini.get_tag(hostname,"mustContain",true,&mustcontain) && !mustcontain.empty())
173             {
174                 char* buf=strdup(mustcontain.c_str());
175                 _strupr(buf);
176                 char* start=buf;
177                 while (char* sep=strchr(start,';'))
178                 {
179                     *sep='\0';
180                     if (*start)
181                         settings.m_mustContain.push_back(start);
182                     start=sep+1;
183                 }
184                 if (*start)
185                     settings.m_mustContain.push_back(start);
186                 free(buf);
187             }
188             
189             g_Sites.push_back(settings);
190             sprintf(iid,"%u",i++);
191         }
192     }
193     catch (SAMLException&)
194     {
195         LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
196                 "Filter startup failed with SAML exception, check shire log for help.");
197         return FALSE;
198     }
199     catch (...)
200     {
201         LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
202                 "Filter startup failed with unexpected exception, check shire log for help.");
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=exp.find('\n'))!=string::npos)
678 //                exp.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                     pn->SetHeader(pfc,const_cast<char*>(hname),const_cast<char*>(header.c_str()));
727                 }
728             }
729         }
730     
731         // clean up memory
732         for (int k = 0; k < assertions.size(); k++)
733           delete assertions[k];
734         delete sso_statement;
735
736         return SF_STATUS_REQ_NEXT_NOTIFICATION;
737     }
738     catch(bad_alloc)
739     {
740         return WriteClientError(pfc,"Out of Memory");
741     }
742     catch(DWORD e)
743     {
744         if (e==ERROR_NO_DATA)
745             return WriteClientError(pfc,"A required variable or header was empty.");
746         else
747             WriteClientError(pfc,"Server detected unexpected IIS error.");
748     }
749     catch(...)
750     {
751         WriteClientError(pfc,"Server caught an unknown exception.");
752     }
753
754     return WriteClientError(pfc,"Server reached unreachable code!");
755 }
756
757 string get_target(LPEXTENSION_CONTROL_BLOCK lpECB, settings_t& site)
758 {
759     string s;
760     dynabuf buf(256);
761     GetServerVariable(lpECB,"HTTPS",buf);
762     bool SSL=(buf=="on");
763     if (SSL)
764         s="https://";
765     else
766         s="http://";
767
768     // We use the "normalizeRequest" tag to decide how to obtain the server's name.
769     string tag;
770     if (g_Config->getINI().get_tag(site.m_name,"normalizeRequest",true,&tag) && ShibINI::boolean(tag))
771     {
772         s+=site.m_name;
773     }
774     else
775     {
776         GetServerVariable(lpECB,"SERVER_NAME",buf);
777         s+=buf;
778     }
779
780     GetServerVariable(lpECB,"SERVER_PORT",buf,10);
781     if (buf!=(SSL ? "443" : "80"))
782         s=s + ':' + static_cast<char*>(buf);
783
784     GetServerVariable(lpECB,"URL",buf,255);
785     s+=buf;
786
787     return s;
788 }
789
790 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* msg)
791 {
792     LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
793     lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,0);
794     static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Error</TITLE></HEAD><BODY><H1>Shibboleth Error</H1>";
795     DWORD resplen=strlen(xmsg);
796     lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg,&resplen,HSE_IO_SYNC);
797     resplen=strlen(msg);
798     lpECB->WriteClient(lpECB->ConnID,(LPVOID)msg,&resplen,HSE_IO_SYNC);
799     static const char* xmsg2="</BODY></HTML>";
800     resplen=strlen(xmsg2);
801     lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg2,&resplen,HSE_IO_SYNC);
802     return HSE_STATUS_SUCCESS;
803 }
804
805 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* filename, ShibMLP& mlp)
806 {
807     ifstream infile(filename);
808     if (!infile)
809         return WriteClientError(lpECB,"Unable to open error template, check settings.");   
810
811     string res = mlp.run(infile);
812     lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,0);
813     DWORD resplen=res.length();
814     lpECB->WriteClient(lpECB->ConnID,(LPVOID)res.c_str(),&resplen,0);
815     return HSE_STATUS_SUCCESS;
816 }
817
818 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
819 {
820     ostringstream threadid;
821     threadid << "[" << getpid() << "] shire" << '\0';
822     saml::NDC ndc(threadid.str().c_str());
823
824     ShibINI& ini = g_Config->getINI();
825     string shireError;
826     ShibMLP markupProcessor;
827
828     try
829     {
830         // Determine web site number. This can't really fail, I don't think.
831         dynabuf buf(128);
832         ULONG site_id=0;
833         GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
834         if ((site_id=strtoul(buf,NULL,10))==0)
835             return WriteClientError(lpECB,"IIS site instance appears to be invalid.");
836
837         // Match site instance to site settings.
838         if (site_id>g_Sites.size() || g_Sites[site_id-1].m_name.length()==0)
839             return WriteClientError(lpECB,"Shibboleth filter not configured for this web site.");
840         settings_t& site=g_Sites[site_id-1];
841
842         if (!ini.get_tag(site.m_name, "shireError", true, &shireError))
843             return WriteClientError(lpECB,"The shireError configuration setting is missing, check configuration.");
844
845         string target_url=get_target(lpECB,site);
846         string shire_url = get_shire_location(site,target_url.c_str());
847
848         // Set SHIRE policies.
849         SHIREConfig config;
850         string tag;
851         config.checkIPAddress = (ini.get_tag(site.m_name,"checkIPAddress",true,&tag) && ShibINI::boolean(tag));
852         config.lifetime=config.timeout=0;
853         tag.erase();
854         if (ini.get_tag(site.m_name, "authLifetime", true, &tag))
855             config.lifetime=strtoul(tag.c_str(),NULL,10);
856         tag.erase();
857         if (ini.get_tag(site.m_name, "authTimeout", true, &tag))
858             config.timeout=strtoul(tag.c_str(),NULL,10);
859
860         // Pull the config data we need to handle the various possible conditions.
861         string shib_cookie;
862         if (!ini.get_tag(site.m_name, "cookieName", true, &shib_cookie))
863             return WriteClientError(lpECB,"The cookieName configuration setting is missing, check configuration.");
864     
865         string wayfLocation;
866         if (!ini.get_tag(site.m_name, "wayfURL", true, &wayfLocation))
867             return WriteClientError(lpECB,"The wayfURL configuration setting is missing, check configuration.");
868     
869         bool has_tag = ini.get_tag(site.m_name, "supportContact", true, &tag);
870         markupProcessor.insert("supportContact", has_tag ? tag : "");
871         has_tag = ini.get_tag(site.m_name, "logoLocation", true, &tag);
872         markupProcessor.insert("logoLocation", has_tag ? tag : "");
873         markupProcessor.insert("requestURL", target_url.c_str());
874   
875         // Get an RPC handle and build the SHIRE object.
876         RPCHandle* rpc_handle = (RPCHandle*)rpc_handle_key->getData();
877         if (!rpc_handle)
878         {
879             rpc_handle = new RPCHandle(shib_target_sockname(), SHIBRPC_PROG, SHIBRPC_VERS_1);
880             rpc_handle_key->setData(rpc_handle);
881         }
882         SHIRE shire(rpc_handle, config, shire_url.c_str());
883
884         // Process SHIRE POST
885         if (ini.get_tag(site.m_name, "shireSSLOnly", true, &tag) && ShibINI::boolean(tag))
886         {
887             // Make sure this is SSL, if it should be.
888             GetServerVariable(lpECB,"HTTPS",buf,10);
889             if (buf!="on")
890                 throw ShibTargetException(SHIBRPC_OK,"blocked non-SSL access to SHIRE POST processor");
891         }
892         
893         // Make sure this is a POST
894         if (stricmp(lpECB->lpszMethod,"POST"))
895             throw ShibTargetException(SHIBRPC_OK,"blocked non-POST to SHIRE POST processor");
896
897         // Sure sure this POST is an appropriate content type
898         if (!lpECB->lpszContentType || stricmp(lpECB->lpszContentType,"application/x-www-form-urlencoded"))
899             throw ShibTargetException(SHIBRPC_OK,"blocked bad content-type to SHIRE POST processor");
900     
901         // Make sure the "bytes sent" is a reasonable number and that we have all of it.
902         if (lpECB->cbTotalBytes > 1024*1024) // 1MB?
903             throw ShibTargetException (SHIBRPC_OK,"blocked too-large a post to SHIRE POST processor");
904         else if (lpECB->cbTotalBytes>lpECB->cbAvailable)
905             throw ShibTargetException (SHIBRPC_OK,"blocked incomplete post to SHIRE POST processor");
906
907         // Parse the incoming data.
908         HQUERY params=ParseQuery(lpECB);
909         if (!params)
910             throw ShibTargetException (SHIBRPC_OK,"unable to parse form data");
911
912         // Make sure the TARGET parameter exists
913         const char* target = QueryValue(params,"TARGET");
914         if (!target || *target == '\0')
915             throw ShibTargetException(SHIBRPC_OK,"SHIRE POST failed to find TARGET parameter");
916     
917         // Make sure the SAMLResponse parameter exists
918         const char* post = QueryValue(params,"SAMLResponse");
919         if (!post || *post == '\0')
920             throw ShibTargetException (SHIBRPC_OK,"SHIRE POST failed to find SAMLResponse parameter");
921
922         GetServerVariable(lpECB,"REMOTE_ADDR",buf,16);
923
924         // Process the post.
925         string cookie;
926         RPCError* status = shire.sessionCreate(post,buf,cookie);
927     
928         if (status->isError()) {
929             if (status->isRetryable()) {
930                 delete status;
931                 string wayf=wayfLocation + "?shire=" + url_encode(shire_url.c_str()) + "&target=" + url_encode(target);
932                 DWORD len=wayf.length();
933                 if (lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_URL_REDIRECT_RESP,(LPVOID)wayf.c_str(),&len,0))
934                     return HSE_STATUS_SUCCESS;
935                 return HSE_STATUS_ERROR;
936             }
937     
938             // Return this error to the user.
939             markupProcessor.insert(*status);
940             delete status;
941             return WriteClientError(lpECB,shireError.c_str(),markupProcessor);
942         }
943         delete status;
944     
945         // We've got a good session, set the cookie and redirect to target.
946         shib_cookie = "Set-Cookie: " + shib_cookie + '=' + cookie + "; path=/\r\n" 
947             "Location: " + target + "\r\n"
948             "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
949             "Cache-Control: private,no-store,no-cache\r\n"
950             "Connection: close\r\n";
951         HSE_SEND_HEADER_EX_INFO hinfo;
952         hinfo.pszStatus="302 Moved";
953         hinfo.pszHeader=shib_cookie.c_str();
954         hinfo.cchStatus=9;
955         hinfo.cchHeader=shib_cookie.length();
956         hinfo.fKeepConn=FALSE;
957         if (lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER_EX,&hinfo,0,0))
958             return HSE_STATUS_SUCCESS;
959     }
960     catch (ShibTargetException &e) {
961         markupProcessor.insert ("errorType", "SHIRE Processing Error");
962         markupProcessor.insert ("errorText", e.what());
963         markupProcessor.insert ("errorDesc", "An error occurred while processing your request.");
964         return WriteClientError(lpECB,shireError.c_str(),markupProcessor);
965     }
966     catch (...) {
967         markupProcessor.insert ("errorType", "SHIRE Processing Error");
968         markupProcessor.insert ("errorText", "Unexpected Exception");
969         markupProcessor.insert ("errorDesc", "An error occurred while processing your request.");
970         return WriteClientError(lpECB,shireError.c_str(),markupProcessor);
971     }
972     
973     return HSE_STATUS_ERROR;
974 }
975