Rewritten filter based on 1.0 APIs, still missing POST handler.
[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
71 using namespace std;
72 using namespace log4cpp;
73 using namespace saml;
74 using namespace shibboleth;
75 using namespace shibtarget;
76
77 struct settings_t
78 {
79     settings_t() {}
80     settings_t(string& name) : m_name(name) {}
81     
82     string m_name;
83     vector<string> m_mustContain;
84 };
85
86 // globals
87 namespace {
88     HINSTANCE g_hinstDLL;
89     ThreadKey* rpc_handle_key = NULL;
90     ShibTargetConfig* g_Config = NULL;
91     vector<settings_t> g_Sites;
92 }
93
94 void destroy_handle(void* data)
95 {
96     delete (RPCHandle*)data;
97 }
98
99 BOOL LogEvent(
100     LPCSTR  lpUNCServerName,
101     WORD  wType,
102     DWORD  dwEventID,
103     PSID  lpUserSid,
104     LPCSTR  message)
105 {
106     LPCSTR  messages[] = {message, NULL};
107     
108     HANDLE hElog = RegisterEventSource(lpUNCServerName, "Shibboleth ISAPI Filter");
109     BOOL res = ReportEvent(hElog, wType, 0, dwEventID, lpUserSid, 1, 0, messages, NULL);
110     return (DeregisterEventSource(hElog) && res);
111 }
112
113 extern "C" __declspec(dllexport) BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID)
114 {
115     if (fdwReason==DLL_PROCESS_ATTACH)
116         g_hinstDLL=hinstDLL;
117     return TRUE;
118 }
119
120 extern "C" BOOL WINAPI GetFilterVersion(PHTTP_FILTER_VERSION pVer)
121 {
122     if (!pVer)
123         return FALSE;
124
125     try
126     {
127         g_Config = &(ShibTargetConfig::init(SHIBTARGET_SHIRE, getenv("SHIBCONFIG")));
128         ShibINI& ini = g_Config->getINI();
129
130         // Create the RPC Handle TLS key.
131         rpc_handle_key=ThreadKey::create(destroy_handle);
132
133         // Read site-specific settings for each instance ID we can find.
134         unsigned short i=1;
135         char iid[8];
136         sprintf(iid,"%u",i++);
137         string hostname;
138         while (ini.get_tag("isapi",iid,false,&hostname))
139         {
140             // If no section exists for the host, mark it as a "skip" site.
141             if (!ini.exists(hostname))
142             {
143                 g_Sites.push_back(settings_t());
144                 sprintf(iid,"%u",i++);
145                 continue;
146             }
147             
148             settings_t settings(hostname);
149             
150             // Content matching string.
151             string mustcontain;
152             if (ini.get_tag(hostname,"mustContain",true,&mustcontain) && !mustcontain.empty())
153             {
154                 char* buf=strdup(mustcontain.c_str());
155                 _strupr(buf);
156                 char* start=buf;
157                 while (char* sep=strchr(start,';'))
158                 {
159                     *sep='\0';
160                     if (*start)
161                         settings.m_mustContain.push_back(start);
162                     start=sep+1;
163                 }
164                 if (*start)
165                     settings.m_mustContain.push_back(start);
166                 free(buf);
167             }
168             
169             g_Sites.push_back(settings);
170             sprintf(iid,"%u",i++);
171         }
172     }
173     catch (SAMLException&)
174     {
175         LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
176                 "Filter startup failed with SAML exception, check shire log for help.");
177         return FALSE;
178     }
179     catch (...)
180     {
181         LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
182                 "Filter startup failed with unexpected exception, check shire log for help.");
183         return FALSE;
184     }
185
186     pVer->dwFilterVersion=HTTP_FILTER_REVISION;
187     strncpy(pVer->lpszFilterDesc,"Shibboleth ISAPI Filter",SF_MAX_FILTER_DESC_LEN);
188     pVer->dwFlags=(SF_NOTIFY_ORDER_HIGH |
189                    SF_NOTIFY_SECURE_PORT |
190                    SF_NOTIFY_NONSECURE_PORT |
191                    SF_NOTIFY_PREPROC_HEADERS |
192                    SF_NOTIFY_LOG);
193     LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter initialized...");
194     return TRUE;
195 }
196
197 extern "C" BOOL WINAPI TerminateFilter(DWORD dwFlags)
198 {
199     delete rpc_handle_key;
200     if (g_Config)
201         g_Config->shutdown();
202     g_Config = NULL;
203     LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter shut down...");
204     return TRUE;
205 }
206
207 /* Next up, some suck-free versions of various APIs.
208
209    You DON'T require people to guess the buffer size and THEN tell them the right size.
210    Returning an LPCSTR is apparently way beyond their ken. Not to mention the fact that
211    constant strings aren't typed as such, making it just that much harder. These versions
212    are now updated to use a special growable buffer object, modeled after the standard
213    string class. The standard string won't work because they left out the option to
214    pre-allocate a non-constant buffer.
215 */
216
217 class dynabuf
218 {
219 public:
220     dynabuf() { bufptr=NULL; buflen=0; }
221     dynabuf(size_t s) { bufptr=new char[buflen=s]; *bufptr=0; }
222     ~dynabuf() { delete[] bufptr; }
223     size_t length() const { return bufptr ? strlen(bufptr) : 0; }
224     size_t size() const { return buflen; }
225     bool empty() const { return length()==0; }
226     void reserve(size_t s, bool keep=false);
227     void erase() { if (bufptr) *bufptr=0; }
228     operator char*() { return bufptr; }
229     bool operator ==(const char* s) const;
230     bool operator !=(const char* s) const { return !(*this==s); }
231 private:
232     char* bufptr;
233     size_t buflen;
234 };
235
236 void dynabuf::reserve(size_t s, bool keep)
237 {
238     if (s<=buflen)
239         return;
240     char* p=new char[s];
241     if (keep)
242         while (buflen--)
243             p[buflen]=bufptr[buflen];
244     buflen=s;
245     delete[] bufptr;
246     bufptr=p;
247 }
248
249 bool dynabuf::operator==(const char* s) const
250 {
251     if (buflen==NULL || s==NULL)
252         return (buflen==NULL && s==NULL);
253     else
254         return strcmp(bufptr,s)==0;
255 }
256
257 void GetServerVariable(PHTTP_FILTER_CONTEXT pfc, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
258     throw (bad_alloc, DWORD)
259 {
260     s.erase();
261     s.reserve(size);
262     size=s.size();
263
264     while (!pfc->GetServerVariable(pfc,lpszVariable,s,&size))
265     {
266         // Grumble. Check the error.
267         DWORD e=GetLastError();
268         if (e==ERROR_INSUFFICIENT_BUFFER)
269             s.reserve(size);
270         else
271             break;
272     }
273     if (bRequired && s.empty())
274         throw ERROR_NO_DATA;
275 }
276
277 void GetHeader(PHTTP_FILTER_PREPROC_HEADERS pn, PHTTP_FILTER_CONTEXT pfc,
278                LPSTR lpszName, dynabuf& s, DWORD size=80, bool bRequired=true)
279     throw (bad_alloc, DWORD)
280 {
281     s.erase();
282     s.reserve(size);
283     size=s.size();
284
285     while (!pn->GetHeader(pfc,lpszName,s,&size))
286     {
287         // Grumble. Check the error.
288         DWORD e=GetLastError();
289         if (e==ERROR_INSUFFICIENT_BUFFER)
290             s.reserve(size);
291         else
292             break;
293     }
294     if (bRequired && s.empty())
295         throw ERROR_NO_DATA;
296 }
297
298 inline char hexchar(unsigned short s)
299 {
300     return (s<=9) ? ('0' + s) : ('A' + s - 10);
301 }
302
303 string url_encode(const char* url) throw (bad_alloc)
304 {
305     static char badchars[]="\"\\+<>#%{}|^~[]`;/?:@=&";
306     string s;
307     for (const char* pch=url; *pch; pch++)
308     {
309         if (strchr(badchars,*pch)!=NULL || *pch<=0x1F || *pch>=0x7F)
310             s=s + '%' + hexchar(*pch >> 4) + hexchar(*pch & 0x0F);
311         else
312             s+=*pch;
313     }
314     return s;
315 }
316
317 string get_target(PHTTP_FILTER_CONTEXT pfc, PHTTP_FILTER_PREPROC_HEADERS pn, settings_t& site)
318 {
319     // Reconstructing the requested URL is not fun. Apparently, the PREPROC_HEADERS
320     // event means way pre. As in, none of the usual CGI headers are in place yet.
321     // It's actually almost easier, in a way, because all the path-info and query
322     // stuff is in one place, the requested URL, which we can get. But we have to
323     // reconstruct the protocol/host pair using tweezers.
324     string s;
325     if (pfc->fIsSecurePort)
326         s="https://";
327     else
328         s="http://";
329
330     // We use the "normalizeRequest" tag to decide how to obtain the server's name.
331     dynabuf buf(256);
332     string tag;
333     if (g_Config->getINI().get_tag(site.m_name,"normalizeRequest",true,&tag) && ShibINI::boolean(tag))
334     {
335         s+=site.m_name;
336     }
337     else
338     {
339         GetServerVariable(pfc,"SERVER_NAME",buf);
340         s+=buf;
341     }
342
343     GetServerVariable(pfc,"SERVER_PORT",buf,10);
344     if (buf!=(pfc->fIsSecurePort ? "443" : "80"))
345         s=s + ':' + static_cast<char*>(buf);
346
347     GetHeader(pn,pfc,"url",buf,256,false);
348     s+=buf;
349
350     return s;
351 }
352
353 string get_shire_location(PHTTP_FILTER_CONTEXT pfc, settings_t& site, const char* target)
354 {
355     string shireURL;
356     if (g_Config->getINI().get_tag(site.m_name,"shireURL",true,&shireURL) && !shireURL.empty())
357     {
358         if (shireURL[0]!='/')
359             return shireURL;
360         const char* colon=strchr(target,':');
361         const char* slash=strchr(colon+3,'/');
362         string s(target,slash-target);
363         s+=shireURL;
364         return s;
365     }
366     return shireURL;
367 }
368
369 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const char* msg)
370 {
371     LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
372     pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",0,0);
373     static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Filter Error</TITLE></HEAD><BODY>"
374                             "<H1>Shibboleth Filter Error</H1>";
375     DWORD resplen=strlen(xmsg);
376     pfc->WriteClient(pfc,(LPVOID)xmsg,&resplen,0);
377     resplen=strlen(msg);
378     pfc->WriteClient(pfc,(LPVOID)msg,&resplen,0);
379     static const char* xmsg2="</BODY></HTML>";
380     resplen=strlen(xmsg2);
381     pfc->WriteClient(pfc,(LPVOID)xmsg2,&resplen,0);
382     return SF_STATUS_REQ_FINISHED;
383 }
384
385 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const char* filename, ShibMLP& mlp)
386 {
387     ifstream infile(filename);
388     if (!infile)
389         return WriteClientError(pfc,"Unable to open error template, check settings.");   
390
391     string res = mlp.run(infile);
392     pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",0,0);
393     DWORD resplen=res.length();
394     pfc->WriteClient(pfc,(LPVOID)res.c_str(),&resplen,0);
395     return SF_STATUS_REQ_FINISHED;
396 }
397
398 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
399 {
400     // Is this a log notification?
401     if (notificationType==SF_NOTIFY_LOG)
402     {
403         if (pfc->pFilterContext)
404             ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=static_cast<LPCSTR>(pfc->pFilterContext);
405         return SF_STATUS_REQ_NEXT_NOTIFICATION;
406     }
407
408     PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
409     try
410     {
411         // Determine web site number. This can't really fail, I don't think.
412         dynabuf buf(128);
413         ULONG site_id=0;
414         GetServerVariable(pfc,"INSTANCE_ID",buf,10);
415         if ((site_id=strtoul(buf,NULL,10))==0)
416             return WriteClientError(pfc,"IIS site instance appears to be invalid.");
417
418         // Match site instance to site settings.
419         if (site_id>g_Sites.size() || g_Sites[site_id-1].m_name.length()==0)
420             return SF_STATUS_REQ_NEXT_NOTIFICATION;
421         settings_t& site=g_Sites[site_id-1];
422
423         string target_url=get_target(pfc,pn,site);
424         string shire_url=get_shire_location(pfc,site,target_url.c_str());
425
426         // If the user is accessing the SHIRE acceptance point, pass it on.
427         if (target_url.find(shire_url)!=string::npos)
428             return SF_STATUS_REQ_NEXT_NOTIFICATION;
429
430         // Get the url request and scan for the must-contain string.
431         if (!site.m_mustContain.empty())
432         {
433             char* upcased=new char[target_url.length()+1];
434             strcpy(upcased,target_url.c_str());
435             _strupr(upcased);
436             for (vector<string>::const_iterator index=site.m_mustContain.begin(); index!=site.m_mustContain.end(); index++)
437                 if (strstr(upcased,index->c_str()))
438                     break;
439             delete[] upcased;
440             if (index==site.m_mustContain.end())
441                 return SF_STATUS_REQ_NEXT_NOTIFICATION;
442         }
443
444         // SSL content check.
445         ShibINI& ini=g_Config->getINI();
446         string tag;
447         if (ini.get_tag(site.m_name,"contentSSLOnly",true,&tag) && ShibINI::boolean(tag) && !pfc->fIsSecurePort)
448         {
449             return WriteClientError(pfc,
450                 "This server is configured to deny non-SSL requests for secure resources. "
451                 "Try your request again using https instead of http.");
452         }
453
454         ostringstream threadid;
455         threadid << "[" << getpid() << "] shire" << '\0';
456         saml::NDC ndc(threadid.str().c_str());
457
458         // Set SHIRE policies.
459         SHIREConfig config;
460         config.checkIPAddress = (ini.get_tag(site.m_name,"checkIPAddress",true,&tag) && ShibINI::boolean(tag));
461         config.lifetime=config.timeout=0;
462         tag.erase();
463         if (ini.get_tag(site.m_name, "authLifetime", true, &tag))
464             config.lifetime=strtoul(tag.c_str(),NULL,10);
465         tag.erase();
466         if (ini.get_tag(site.m_name, "authTimeout", true, &tag))
467             config.timeout=strtoul(tag.c_str(),NULL,10);
468
469         // Pull the config data we need to handle the various possible conditions.
470         string shib_cookie;
471         if (!ini.get_tag(site.m_name, "cookieName", true, &shib_cookie))
472             return WriteClientError(pfc,"The cookieName configuration setting is missing, check configuration.");
473     
474         string wayfLocation;
475         if (!ini.get_tag(site.m_name, "wayfURL", true, &wayfLocation))
476             return WriteClientError(pfc,"The wayfURL configuration setting is missing, check configuration.");
477     
478         string shireError;
479         if (!ini.get_tag(site.m_name, "shireError", true, &shireError))
480             return WriteClientError(pfc,"The shireError configuration setting is missing, check configuration.");
481
482         string accessError;
483         if (!ini.get_tag(site.m_name, "accessError", true, &shireError))
484             return WriteClientError(pfc,"The accessError configuration setting is missing, check configuration.");
485         
486         // Get an RPC handle and build the SHIRE object.
487         RPCHandle* rpc_handle = (RPCHandle*)rpc_handle_key->getData();
488         if (!rpc_handle)
489         {
490             rpc_handle = new RPCHandle(shib_target_sockname(), SHIBRPC_PROG, SHIBRPC_VERS_1);
491             rpc_handle_key->setData(rpc_handle);
492         }
493         SHIRE shire(rpc_handle, config, shire_url);
494
495         // Check for authentication cookie.
496         const char* session_id=NULL;
497         GetHeader(pn,pfc,"Cookie:",buf,128,false);
498         if (buf.empty() || !(session_id=strstr(buf,shib_cookie.c_str())) || *(session_id+shib_cookie.length())!='=')
499         {
500             // Redirect to WAYF.
501             string wayf("Location: ");
502             wayf+=wayfLocation + "?shire=" + url_encode(shire_url.c_str()) + "&target=" + url_encode(target_url.c_str()) + "\r\n";
503             // Insert the headers.
504             pfc->AddResponseHeaders(pfc,const_cast<char*>(wayf.c_str()),0);
505             pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"302 Please Wait",0,0);
506             return SF_STATUS_REQ_FINISHED;
507         }
508
509         session_id+=shib_cookie.length() + 1;   /* Skip over the '=' */
510         char* cookieend=strchr(session_id,';');
511         if (cookieend)
512             *cookieend = '\0';  /* Ignore anyting after a ; */
513   
514         // Make sure this session is still valid.
515         RPCError* status = NULL;
516         ShibMLP markupProcessor;
517         bool has_tag = ini.get_tag(site.m_name, "supportContact", true, &tag);
518         markupProcessor.insert("supportContact", has_tag ? tag : "");
519         has_tag = ini.get_tag(site.m_name, "logoLocation", true, &tag);
520         markupProcessor.insert("logoLocation", has_tag ? tag : "");
521         markupProcessor.insert("requestURL", target_url);
522     
523         GetServerVariable(pfc,"REMOTE_ADDR",buf,16);
524         try {
525             status = shire.sessionIsValid(session_id, buf, target_url.c_str());
526         }
527         catch (ShibTargetException &e) {
528             markupProcessor.insert("errorType", "SHIRE Processing Error");
529             markupProcessor.insert("errorText", e.what());
530             markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
531             return WriteClientError(pfc, shireError.c_str(), markupProcessor);
532         }
533         catch (...) {
534             markupProcessor.insert("errorType", "SHIRE Processing Error");
535             markupProcessor.insert("errorText", "Unexpected Exception");
536             markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
537             return WriteClientError(pfc, shireError.c_str(), markupProcessor);
538         }
539     
540         // Check the status
541         if (status->isError()) {
542             if (status->isRetryable()) {
543                 // Redirect to WAYF.
544                 delete status;
545                 string wayf("Location: ");
546                 wayf+=wayfLocation + "?shire=" + url_encode(shire_url.c_str()) + "&target=" + url_encode(target_url.c_str()) + "\r\n";
547                 // Insert the headers.
548                 pfc->AddResponseHeaders(pfc,const_cast<char*>(wayf.c_str()),0);
549                 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"302 Please Wait",0,0);
550                 return SF_STATUS_REQ_FINISHED;
551             }
552             else {
553                 // return the error page to the user
554                 markupProcessor.insert(*status);
555                 delete status;
556                 return WriteClientError(pfc, shireError.c_str(), markupProcessor);
557             }
558         }
559         delete status;
560     
561         // Move to RM phase.
562         RMConfig rm_config;
563         rm_config.checkIPAddress = config.checkIPAddress;
564         RM rm(rpc_handle,rm_config);
565
566         // Get the attributes.
567         vector<SAMLAssertion*> assertions;
568         SAMLAuthenticationStatement* sso_statement=NULL;
569         status = rm.getAssertions(session_id, buf, target_url.c_str(), assertions, &sso_statement);
570     
571         if (status->isError()) {
572             string rmError;
573             if (!ini.get_tag(site.m_name, "rmError", true, &shireError))
574                 return WriteClientError(pfc,"The rmError configuration setting is missing, check configuration.");
575     
576             markupProcessor.insert(*status);
577             delete status;
578             return WriteClientError(pfc, rmError.c_str(), markupProcessor);
579         }
580         delete status;
581
582         // Only allow a single assertion...
583         if (assertions.size() > 1) {
584             for (int k = 0; k < assertions.size(); k++)
585               delete assertions[k];
586             delete sso_statement;
587             return WriteClientError(pfc, accessError.c_str(), markupProcessor);
588         }
589
590         // Get the AAP providers, which contain the attribute policy info.
591         Iterator<IAAP*> provs=ShibConfig::getConfig().getAAPProviders();
592     
593         // Clear out the list of mapped attributes
594         while (provs.hasNext())
595         {
596             IAAP* aap=provs.next();
597             aap->lock();
598             try
599             {
600                 Iterator<const IAttributeRule*> rules=aap->getAttributeRules();
601                 while (rules.hasNext())
602                 {
603                     const char* header=rules.next()->getHeader();
604                     if (header)
605                         pn->SetHeader(pfc,const_cast<char*>(header),"");
606                 }
607             }
608             catch(...)
609             {
610                 aap->unlock();
611                 for (int k = 0; k < assertions.size(); k++)
612                   delete assertions[k];
613                 delete sso_statement;
614                 throw;
615             }
616             aap->unlock();
617         }
618         provs.reset();
619
620         // Clear relevant headers.
621         pn->SetHeader(pfc,"remote-user:","");
622         pn->SetHeader(pfc,"Shib-Attributes:","");
623         pn->SetHeader(pfc,"Shib-Origin-Site:","");
624         pn->SetHeader(pfc,"Shib-Authentication-Method:","");
625
626         // Maybe export the assertion.
627         if (ini.get_tag(site.m_name,"exportAssertion",true,&tag) && ShibINI::boolean(tag))
628         {
629             string assertion;
630             RM::serialize(*(assertions[0]), assertion);
631 //            string::size_type lfeed;
632 //            while ((lfeed=exp.find('\n'))!=string::npos)
633 //                exp.erase(lfeed,1);
634             pn->SetHeader(pfc,"Shib-Attributes:",const_cast<char*>(assertion.c_str()));
635         }
636         
637         if (sso_statement)
638         {
639             auto_ptr<char> os(XMLString::transcode(sso_statement->getSubject()->getNameQualifier()));
640             auto_ptr<char> am(XMLString::transcode(sso_statement->getAuthMethod()));
641             pn->SetHeader(pfc,"Shib-Origin-Site:", os.get());
642             pn->SetHeader(pfc,"Shib-Authentication-Method:", am.get());
643         }
644
645         // Export the attributes. Only supports a single statement.
646         Iterator<SAMLAttribute*> j = assertions.size()==1 ? RM::getAttributes(*(assertions[0])) : EMPTY(SAMLAttribute*);
647         while (j.hasNext())
648         {
649             SAMLAttribute* attr=j.next();
650     
651             // Are we supposed to export it?
652             const char* hname=NULL;
653             AAP wrapper(attr->getName(),attr->getNamespace());
654             if (!wrapper.fail())
655                 hname=wrapper->getHeader();
656             if (hname)
657             {
658                 Iterator<string> vals=attr->getSingleByteValues();
659                 if (!strcmp(hname,"REMOTE_USER") && vals.hasNext())
660                 {
661                     char* principal=const_cast<char*>(vals.next().c_str());
662                     pn->SetHeader(pfc,"remote-user:",principal);
663                     pfc->pFilterContext=pfc->AllocMem(pfc,strlen(principal)+1,0);
664                     if (pfc->pFilterContext)
665                         strcpy(static_cast<char*>(pfc->pFilterContext),principal);
666                 }    
667                 else
668                 {
669                     string header;
670                     for (int it = 0; vals.hasNext(); it++) {
671                         string value = vals.next();
672                         for (string::size_type pos = value.find_first_of(";", string::size_type(0)); pos != string::npos; pos = value.find_first_of(";", pos)) {
673                             value.insert(pos, "\\");
674                             pos += 2;
675                         }
676                         if (it == 0)
677                             header=value;
678                         else
679                             header=header + ';' + value;
680                     }
681                     pn->SetHeader(pfc,const_cast<char*>(hname),const_cast<char*>(header.c_str()));
682                 }
683             }
684         }
685     
686         // clean up memory
687         for (int k = 0; k < assertions.size(); k++)
688           delete assertions[k];
689         delete sso_statement;
690
691         return SF_STATUS_REQ_NEXT_NOTIFICATION;
692     }
693     catch(bad_alloc)
694     {
695         return WriteClientError(pfc,"Out of Memory");
696     }
697     catch(DWORD e)
698     {
699         if (e==ERROR_NO_DATA)
700             return WriteClientError(pfc,"A required variable or header was empty.");
701         else
702             WriteClientError(pfc,"Server detected unexpected IIS error.");
703     }
704     catch(...)
705     {
706         WriteClientError(pfc,"Server caught an unknown exception.");
707     }
708
709     return WriteClientError(pfc,"Server reached unreachable code!");
710 }