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