Null pointer bug.
[shibboleth/sp.git] / isapi_shib / isapi_shib.cpp
index db8aa59..e12c1cd 100644 (file)
@@ -79,7 +79,7 @@ using namespace shibtarget;
 
 // globals
 namespace {
-    static const XMLCh name[] = { chLatin_h, chLatin_o, chLatin_s, chLatin_t, chNull };
+    static const XMLCh name[] = { chLatin_n, chLatin_a, chLatin_m, chLatin_e, chNull };
     static const XMLCh port[] = { chLatin_p, chLatin_o, chLatin_r, chLatin_t, chNull };
     static const XMLCh scheme[] = { chLatin_s, chLatin_c, chLatin_h, chLatin_e, chLatin_m, chLatin_e, chNull };
     static const XMLCh id[] = { chLatin_i, chLatin_d, chNull };
@@ -158,9 +158,16 @@ extern "C" BOOL WINAPI GetFilterVersion(PHTTP_FILTER_VERSION pVer)
 {
     if (!pVer)
         return FALSE;
+    else if (g_Config) {
+        LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
+                "Reentrant filter initialization, ignoring...");
+        return TRUE;
+    }
 
+#ifndef _DEBUG
     try
     {
+#endif
         LPCSTR schemadir=getenv("SHIBSCHEMAS");
         if (!schemadir)
             schemadir=SHIB_SCHEMAS;
@@ -173,7 +180,7 @@ extern "C" BOOL WINAPI GetFilterVersion(PHTTP_FILTER_VERSION pVer)
             ShibTargetConfig::Metadata |
             ShibTargetConfig::AAP |
             ShibTargetConfig::RequestMapper |
-            ShibTargetConfig::SHIREExtensions |
+            ShibTargetConfig::LocalExtensions |
             ShibTargetConfig::Logging
             );
         if (!g_Config->init(schemadir,config)) {
@@ -186,7 +193,7 @@ extern "C" BOOL WINAPI GetFilterVersion(PHTTP_FILTER_VERSION pVer)
         // Access the implementation-specifics for site mappings.
         IConfig* conf=g_Config->getINI();
         Locker locker(conf);
-        const IPropertySet* props=conf->getPropertySet("SHIRE");
+        const IPropertySet* props=conf->getPropertySet("Local");
         if (props) {
             const DOMElement* impl=saml::XML::getFirstChildElement(
                 props->getElement(),ShibTargetConfig::SHIBTARGET_NS,Implementation
@@ -203,12 +210,14 @@ extern "C" BOOL WINAPI GetFilterVersion(PHTTP_FILTER_VERSION pVer)
                 }
             }
         }
+#ifndef _DEBUG
     }
     catch (...)
     {
         LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Filter startup failed with an exception.");
         return FALSE;
     }
+#endif
 
     pVer->dwFilterVersion=HTTP_FILTER_REVISION;
     strncpy(pVer->lpszFilterDesc,"Shibboleth ISAPI Filter",SF_MAX_FILTER_DESC_LEN);
@@ -341,10 +350,15 @@ void GetHeader(PHTTP_FILTER_PREPROC_HEADERS pn, PHTTP_FILTER_CONTEXT pfc,
         throw ERROR_NO_DATA;
 }
 
-IRequestMapper::Settings map_request(
-    PHTTP_FILTER_CONTEXT pfc, PHTTP_FILTER_PREPROC_HEADERS pn, IRequestMapper* mapper, const site_t& site, string& target
-    )
+/****************************************************************************/
+// ISAPI Filter
+
+class ShibTargetIsapiF : public ShibTarget
 {
+public:
+  ShibTargetIsapiF(PHTTP_FILTER_CONTEXT pfc, PHTTP_FILTER_PREPROC_HEADERS pn,
+                  const site_t& site) {
+
     // URL path always come from IIS.
     dynabuf url(256);
     GetHeader(pn,pfc,"url",url,256,false);
@@ -362,32 +376,104 @@ IRequestMapper::Settings map_request(
     const char* scheme=site.m_scheme.c_str();
     if (!scheme || !*scheme || !g_bNormalizeRequest)
         scheme=pfc->fIsSecurePort ? "https" : "http";
-    
-    // Start with path.
-    if (!url.empty())
-        target=static_cast<char*>(url);
-    
-    // If port is non-default, prepend it.
-    if ((!strcmp(scheme,"http") && port!="80") || (!strcmp(scheme,"https") && port!="443"))
-        target = ':' + static_cast<char*>(port) + target;
 
-    if (g_bNormalizeRequest) {
-        target = string(scheme) + "://" + site.m_name + target;
-    }
-    else {
-        dynabuf name(64);
-        GetServerVariable(pfc,"SERVER_NAME",name,64);
-        target = string(scheme) + "://" + static_cast<char*>(name) + target;
+    // Get the remote address
+    dynabuf remote_addr(16);
+    GetServerVariable(pfc,"REMOTE_ADDR",remote_addr,16);
+
+    // XXX: How do I get the content type and HTTP Method from this context?
+
+    // TODO: Need to allow for use of SERVER_NAME
+
+    init(g_Config, string(scheme), site.m_name, atoi(port),
+        string(url), string(""), // XXX: content type
+        string(remote_addr), string("") // XXX: http method
+        ); 
+
+    m_pfc = pfc;
+    m_pn = pn;
+  }
+  ~ShibTargetIsapiF() { }
+
+  virtual void log(ShibLogLevel level, const string &msg) {
+      LogEvent(NULL, (level == LogLevelDebug ? EVENTLOG_INFORMATION_TYPE :
+                      (level == LogLevelInfo ? EVENTLOG_INFORMATION_TYPE :
+                      (level == LogLevelWarn ? EVENTLOG_WARNING_TYPE : EVENTLOG_ERROR_TYPE))),
+            2100, NULL, msg.c_str());
+  }
+  virtual string getCookies(void) {
+    dynabuf buf(128);
+    GetHeader(m_pn, m_pfc, "Cookie:", buf, 128, false);
+    return buf.empty() ? "" : buf;
+  }
+  
+  virtual void clearHeader(const string &name) {
+    string hdr = name + ":";
+    m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), "");
+  }
+  virtual void setHeader(const string &name, const string &value) {
+    string hdr = name + ":";
+    m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()),
+                   const_cast<char*>(value.c_str()));
+  }
+  virtual string getHeader(const string &name) {
+    string hdr = name + ":";
+    dynabuf buf(1024);
+    GetHeader(m_pn, m_pfc, const_cast<char*>(hdr.c_str()), buf, 1024, false);
+    return string(buf);
+  }
+  virtual void setRemoteUser(const string &user) {
+    setHeader(string("remote-user"), user);
+  }
+  virtual string getRemoteUser(void) {
+    return getHeader(string("remote-user"));
+  }
+  virtual void* sendPage(const string &msg, const string content_type,
+      const Iterator<header_t>& headers=EMPTY(header_t), int code=200) {
+    string hdr = string ("Connection: close\r\nContent-type: ") + content_type + "\r\n";
+    while (headers.hasNext()) {
+        const header_t& h=headers.next();
+        hdr += h.first + ": " + h.second + "\r\n";
     }
-    return mapper->getSettingsFromParsedURL(scheme,site.m_name.c_str(),strtoul(port,NULL,10),url);
-}
+    hdr += "\r\n";
+    // XXX Need to handle "code"
+    m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, "200 OK", (DWORD)hdr.c_str(), 0);
+    DWORD resplen = msg.size();
+    m_pfc->WriteClient(m_pfc, (LPVOID)msg.c_str(), &resplen, 0);
+    return (void*)SF_STATUS_REQ_FINISHED;
+  }
+  virtual void* sendRedirect(const string url) {
+    // XXX: Don't support the httpRedirect option, yet.
+    string hdrs=string("Location: ") + url + "\r\n"
+      "Content-Type: text/html\r\n"
+      "Content-Length: 40\r\n"
+      "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
+      "Cache-Control: private,no-store,no-cache\r\n\r\n";
+    m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER,
+                                "302 Please Wait", (DWORD)hdrs.c_str(), 0);
+    static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
+    DWORD resplen=40;
+    m_pfc->WriteClient(m_pfc, (LPVOID)redmsg, &resplen, 0);
+    return reinterpret_cast<void*>(SF_STATUS_REQ_FINISHED);
+  }
+  // XXX: We might not ever hit the 'decline' status in this filter.
+  //virtual void* returnDecline(void) { }
+  virtual void* returnOK(void) { return (void*) SF_STATUS_REQ_NEXT_NOTIFICATION; }
+
+  // The filter never processes the POST, so stub these methods.
+  virtual void setCookie(const string &name, const string &value) { throw runtime_error("setCookie not implemented"); }
+  virtual string getArgs(void) { throw runtime_error("getArgs not implemented"); }
+  virtual string getPostData(void) { throw runtime_error("getPostData not implemented"); }
+  
+  PHTTP_FILTER_CONTEXT m_pfc;
+  PHTTP_FILTER_PREPROC_HEADERS m_pn;
+};
 
 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const char* msg)
 {
     LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
-    static const char* ctype="Content-Type: text/html\r\n";
-    pfc->AddResponseHeaders(pfc,const_cast<char*>(ctype),0);
-    pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",0,0);
+    static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
+    pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)ctype,0);
     static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Filter Error</TITLE></HEAD><BODY>"
                             "<H1>Shibboleth Filter Error</H1>";
     DWORD resplen=strlen(xmsg);
@@ -400,6 +486,110 @@ DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const char* msg)
     return SF_STATUS_REQ_FINISHED;
 }
 
+extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
+{
+    // Is this a log notification?
+    if (notificationType==SF_NOTIFY_LOG)
+    {
+        if (pfc->pFilterContext)
+            ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=static_cast<LPCSTR>(pfc->pFilterContext);
+        return SF_STATUS_REQ_NEXT_NOTIFICATION;
+    }
+
+    PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
+    try
+    {
+        // Determine web site number. This can't really fail, I don't think.
+        dynabuf buf(128);
+        GetServerVariable(pfc,"INSTANCE_ID",buf,10);
+
+        // Match site instance to host name, skip if no match.
+        map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
+        if (map_i==g_Sites.end())
+            return SF_STATUS_REQ_NEXT_NOTIFICATION;
+            
+        ostringstream threadid;
+        threadid << "[" << getpid() << "] isapi_shib" << '\0';
+        saml::NDC ndc(threadid.str().c_str());
+
+       ShibTargetIsapiF stf(pfc, pn, map_i->second);
+
+       // "false" because we don't override the Shib settings
+       pair<bool,void*> res = stf.doCheckAuthN();
+       if (res.first) return (DWORD)res.second;
+
+       // "false" because we don't override the Shib settings
+       res = stf.doExportAssertions();
+       if (res.first) return (DWORD)res.second;
+
+       res = stf.doCheckAuthZ();
+       if (res.first) return (DWORD)res.second;
+
+        return SF_STATUS_REQ_NEXT_NOTIFICATION;
+    }
+    catch(bad_alloc) {
+        return WriteClientError(pfc,"Out of Memory");
+    }
+    catch(DWORD e) {
+        if (e==ERROR_NO_DATA)
+            return WriteClientError(pfc,"A required variable or header was empty.");
+        else
+            return WriteClientError(pfc,"Server detected unexpected IIS error.");
+    }
+#ifndef _DEBUG
+    catch(...) {
+        return WriteClientError(pfc,"Server caught an unknown exception.");
+    }
+#endif
+
+    return WriteClientError(pfc,"Server reached unreachable code, save my walrus!");
+}
+        
+
+#if 0
+IRequestMapper::Settings map_request(
+    PHTTP_FILTER_CONTEXT pfc, PHTTP_FILTER_PREPROC_HEADERS pn, IRequestMapper* mapper, const site_t& site, string& target
+    )
+{
+    // URL path always come from IIS.
+    dynabuf url(256);
+    GetHeader(pn,pfc,"url",url,256,false);
+
+    // Port may come from IIS or from site def.
+    dynabuf port(11);
+    if (site.m_port.empty() || !g_bNormalizeRequest)
+        GetServerVariable(pfc,"SERVER_PORT",port,10);
+    else {
+        strncpy(port,site.m_port.c_str(),10);
+        static_cast<char*>(port)[10]=0;
+    }
+    
+    // Scheme may come from site def or be derived from IIS.
+    const char* scheme=site.m_scheme.c_str();
+    if (!scheme || !*scheme || !g_bNormalizeRequest)
+        scheme=pfc->fIsSecurePort ? "https" : "http";
+
+    // Start with scheme and hostname.
+    if (g_bNormalizeRequest) {
+        target = string(scheme) + "://" + site.m_name;
+    }
+    else {
+        dynabuf name(64);
+        GetServerVariable(pfc,"SERVER_NAME",name,64);
+        target = string(scheme) + "://" + static_cast<char*>(name);
+    }
+    
+    // If port is non-default, append it.
+    if ((!strcmp(scheme,"http") && port!="80") || (!strcmp(scheme,"https") && port!="443"))
+        target = target + ':' + static_cast<char*>(port);
+
+    // Append path.
+    if (!url.empty())
+        target+=static_cast<char*>(url);
+    
+    return mapper->getSettingsFromParsedURL(scheme,site.m_name.c_str(),strtoul(port,NULL,10),url);
+}
+
 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const IApplication* app, const char* page, ShibMLP& mlp)
 {
     const IPropertySet* props=app->getPropertySet("Errors");
@@ -410,9 +600,8 @@ DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const IApplication* app, const
             if (!infile.fail()) {
                 const char* res = mlp.run(infile,props);
                 if (res) {
-                    static const char* ctype="Content-Type: text/html\r\n";
-                    pfc->AddResponseHeaders(pfc,const_cast<char*>(ctype),0);
-                    pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",0,0);
+                    static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
+                    pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)ctype,0);
                     DWORD resplen=strlen(res);
                     pfc->WriteClient(pfc,(LPVOID)res,&resplen,0);
                     return SF_STATUS_REQ_FINISHED;
@@ -425,6 +614,30 @@ DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const IApplication* app, const
     return WriteClientError(pfc,"Unable to open error template, check settings.");
 }
 
+DWORD WriteRedirectPage(PHTTP_FILTER_CONTEXT pfc, const IApplication* app, const char* file, ShibMLP& mlp, const char* headers=NULL)
+{
+    ifstream infile(file);
+    if (!infile.fail()) {
+        const char* res = mlp.run(infile,app->getPropertySet("Errors"));
+        if (res) {
+            char buf[255];
+            sprintf(buf,"Content-Length: %u\r\nContent-Type: text/html\r\n\r\n",strlen(res));
+            if (headers) {
+                string h(headers);
+                h+=buf;
+                pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)h.c_str(),0);
+            }
+            else
+                pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)buf,0);
+            DWORD resplen=strlen(res);
+            pfc->WriteClient(pfc,(LPVOID)res,&resplen,0);
+            return SF_STATUS_REQ_FINISHED;
+        }
+    }
+    LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Extension unable to open redirect template.");
+    return WriteClientError(pfc,"Unable to open redirect template, check settings.");
+}
+
 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
 {
     // Is this a log notification?
@@ -467,14 +680,22 @@ extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificat
         
         // Declare SHIRE object for this request.
         SHIRE shire(application);
+        
+        const char* shireURL=shire.getShireURL(targeturl.c_str());
+        if (!shireURL)
+            return WriteClientError(pfc,"Unable to map request to proper shireURL setting, check configuration.");
 
         // If the user is accessing the SHIRE acceptance point, pass it on.
-        if (targeturl.find(shire.getShireURL(targeturl.c_str()))!=string::npos)
+        if (targeturl.find(shireURL)!=string::npos)
             return SF_STATUS_REQ_NEXT_NOTIFICATION;
 
         // Now check the policy for this request.
         pair<bool,bool> requireSession=settings.first->getBool("requireSession");
         pair<const char*,const char*> shib_cookie=shire.getCookieNameProps();
+        pair<bool,bool> httpRedirects=application->getPropertySet("Sessions")->getBool("httpRedirects");
+        pair<bool,const char*> redirectPage=application->getPropertySet("Sessions")->getString("redirectPage");
+        if (httpRedirects.first && !httpRedirects.second && !redirectPage.first)
+            return WriteClientError(pfc,"HTML-based redirection requires a redirectPage property.");
 
         // Check for session cookie.
         const char* session_id=NULL;
@@ -493,11 +714,24 @@ extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificat
                 return SF_STATUS_REQ_NEXT_NOTIFICATION;
     
             // No acceptable cookie, and we require a session.  Generate an AuthnRequest.
-            string loc("Location: ");
-            loc+=shire.getAuthnRequest(targeturl.c_str());
-            pfc->AddResponseHeaders(pfc,const_cast<char*>(loc.c_str()),0);
-            pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"302 Please Wait",0,0);
-            return SF_STATUS_REQ_FINISHED;
+            const char* areq = shire.getAuthnRequest(targeturl.c_str());
+            if (!httpRedirects.first || httpRedirects.second) {
+                string hdrs=string("Location: ") + areq + "\r\n"
+                    "Content-Type: text/html\r\n"
+                    "Content-Length: 40\r\n"
+                    "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
+                    "Cache-Control: private,no-store,no-cache\r\n\r\n";
+                pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"302 Please Wait",(DWORD)hdrs.c_str(),0);
+                static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
+                DWORD resplen=40;
+                pfc->WriteClient(pfc,(LPVOID)redmsg,&resplen,0);
+                return SF_STATUS_REQ_FINISHED;
+            }
+            else {
+                ShibMLP markupProcessor;
+                markupProcessor.insert("requestURL",areq);
+                return WriteRedirectPage(pfc, application, redirectPage.second, markupProcessor);
+            }
         }
 
         // Make sure this session is still valid.
@@ -532,11 +766,23 @@ extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificat
             else if (status->isRetryable()) {
                 // Oops, session is invalid. Generate AuthnRequest.
                 delete status;
-                string loc("Location: ");
-                loc+=shire.getAuthnRequest(targeturl.c_str());
-                pfc->AddResponseHeaders(pfc,const_cast<char*>(loc.c_str()),0);
-                pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"302 Please Wait",0,0);
-                return SF_STATUS_REQ_FINISHED;
+                const char* areq = shire.getAuthnRequest(targeturl.c_str());
+                if (!httpRedirects.first || httpRedirects.second) {
+                    string hdrs=string("Location: ") + areq + "\r\n"
+                        "Content-Type: text/html\r\n"
+                        "Content-Length: 40\r\n"
+                        "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
+                        "Cache-Control: private,no-store,no-cache\r\n\r\n";
+                    pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"302 Please Wait",(DWORD)hdrs.c_str(),0);
+                    static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
+                    DWORD resplen=40;
+                    pfc->WriteClient(pfc,(LPVOID)redmsg,&resplen,0);
+                    return SF_STATUS_REQ_FINISHED;
+                }
+                else {
+                    markupProcessor.insert("requestURL",areq);
+                    return WriteRedirectPage(pfc, application, redirectPage.second, markupProcessor);
+                }
             }
             else {
                 // return the error page to the user
@@ -742,14 +988,196 @@ extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificat
 
     return WriteClientError(pfc,"Server reached unreachable code, save my walrus!");
 }
+#endif // 0
+
+/****************************************************************************/
+// ISAPI Extension
+
+DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* msg)
+{
+    LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
+    static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
+    lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
+    static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Error</TITLE></HEAD><BODY><H1>Shibboleth Error</H1>";
+    DWORD resplen=strlen(xmsg);
+    lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg,&resplen,HSE_IO_SYNC);
+    resplen=strlen(msg);
+    lpECB->WriteClient(lpECB->ConnID,(LPVOID)msg,&resplen,HSE_IO_SYNC);
+    static const char* xmsg2="</BODY></HTML>";
+    resplen=strlen(xmsg2);
+    lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg2,&resplen,HSE_IO_SYNC);
+    return HSE_STATUS_SUCCESS;
+}
+
+
+class ShibTargetIsapiE : public ShibTarget
+{
+public:
+  ShibTargetIsapiE(LPEXTENSION_CONTROL_BLOCK lpECB, const site_t& site)
+  {
+    dynabuf ssl(5);
+    GetServerVariable(lpECB,"HTTPS",ssl,5);
+    bool SSL=(ssl=="on" || ssl=="ON");
+
+    // URL path always come from IIS.
+    dynabuf url(256);
+    GetServerVariable(lpECB,"URL",url,255);
+
+    // Port may come from IIS or from site def.
+    dynabuf port(11);
+    if (site.m_port.empty() || !g_bNormalizeRequest)
+        GetServerVariable(lpECB,"SERVER_PORT",port,10);
+    else {
+        strncpy(port,site.m_port.c_str(),10);
+        static_cast<char*>(port)[10]=0;
+    }
+
+    // Scheme may come from site def or be derived from IIS.
+    const char* scheme=site.m_scheme.c_str();
+    if (!scheme || !*scheme || !g_bNormalizeRequest) {
+        scheme = SSL ? "https" : "http";
+    }
+
+    // Get the remote address
+    dynabuf remote_addr(16);
+    GetServerVariable(lpECB, "REMOTE_ADDR", remote_addr, 16);
+
+    init(g_Config, string(scheme), site.m_name, atoi(port),
+        string(url), string(lpECB->lpszContentType ? lpECB->lpszContentType : ""),
+        string(remote_addr), string(lpECB->lpszMethod)
+        ); 
+
+    m_lpECB = lpECB;
+  }
+  ~ShibTargetIsapiE() { }
+
+  virtual void log(ShibLogLevel level, const string &msg) {
+      LogEvent(NULL, (level == LogLevelDebug ? EVENTLOG_INFORMATION_TYPE :
+                        (level == LogLevelInfo ? EVENTLOG_INFORMATION_TYPE :
+                        (level == LogLevelWarn ? EVENTLOG_WARNING_TYPE : EVENTLOG_ERROR_TYPE))),
+            2100, NULL, msg.c_str());
+  }
+  virtual void setCookie(const string &name, const string &value) {
+    // Set the cookie for later.  Use it during the redirect.
+    m_cookie += "Set-Cookie: " + name + "=" + value + "\r\n";
+  }
+  virtual string getArgs(void) {
+    return string(m_lpECB->lpszQueryString ? m_lpECB->lpszQueryString : "");
+  }
+  virtual string getPostData(void) {
+    if (m_lpECB->cbTotalBytes > 1024*1024) // 1MB?
+      throw ShibTargetException(SHIBRPC_OK,
+                               "blocked too-large a post to SHIRE POST processor");
+    else if (m_lpECB->cbTotalBytes != m_lpECB->cbAvailable) {
+      string cgistr;
+      char buf[8192];
+      DWORD datalen=m_lpECB->cbTotalBytes;
+      while (datalen) {
+       DWORD buflen=8192;
+       BOOL ret = m_lpECB->ReadClient(m_lpECB->ConnID, buf, &buflen);
+       if (!ret || !buflen)
+         throw ShibTargetException(SHIBRPC_OK,
+                                   "error reading POST data from browser");
+       cgistr.append(buf, buflen);
+       datalen-=buflen;
+      }
+      return cgistr;
+    }
+    else
+      return string(reinterpret_cast<char*>(m_lpECB->lpbData),m_lpECB->cbAvailable);
+  }
+  virtual void* sendPage(const string &msg, const string content_type,
+                        const Iterator<header_t>& headers=EMPTY(header_t), int code=200) {
+    string hdr = string ("Connection: close\r\nContent-type: ") + content_type + "\r\n";
+    for (int k = 0; k < headers.size(); k++) {
+      hdr += headers[k].first + ": " + headers[k].second + "\r\n";
+    }
+    hdr += "\r\n";
+    // XXX Need to handle "code"
+    m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER,
+                                  "200 OK", 0, (LPDWORD)hdr.c_str());
+    DWORD resplen = msg.size();
+    m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)msg.c_str(), &resplen, HSE_IO_SYNC);
+    return (void*)HSE_STATUS_SUCCESS;
+  }
+  virtual void* sendRedirect(const string url) {
+    // XXX: Don't support the httpRedirect option, yet.
+    string hdrs = m_cookie + "Location: " + url + "\r\n"
+      "Content-Type: text/html\r\n"
+      "Content-Length: 40\r\n"
+      "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
+      "Cache-Control: private,no-store,no-cache\r\n\r\n";
+    m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER,
+                                "302 Moved", 0, (LPDWORD)hdrs.c_str());
+    static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
+    DWORD resplen=40;
+    m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)redmsg, &resplen, HSE_IO_SYNC);
+    return (void*)HSE_STATUS_SUCCESS;
+  }
+  // Decline happens in the POST processor if this isn't the shire url
+  // Note that it can also happen with HTAccess, but we don't suppor that, yet.
+  virtual void* returnDecline(void) {
+    return (void*)
+      WriteClientError(m_lpECB, "UISAPA extension can only be unvoked to process incoming sessions."
+                      "Make sure the mapped file extension doesn't match actual content.");
+  }
+  virtual void* returnOK(void) { return (void*) HSE_STATUS_SUCCESS; }
+
+  // Not used in the extension.
+  virtual string getCookies(void) { throw runtime_error("getCookies not implemented"); }
+  virtual void clearHeader(const string &name) { throw runtime_error("clearHeader not implemented"); }
+  virtual void setHeader(const string &name, const string &value) { throw runtime_error("setHeader not implemented"); }
+  virtual string getHeader(const string &name) { throw runtime_error("getHeader not implemented"); }
+  virtual void setRemoteUser(const string &user) { throw runtime_error("setRemoteUser not implemented"); }
+  virtual string getRemoteUser(void) { throw runtime_error("getRemoteUser not implemented"); }
+
+  LPEXTENSION_CONTROL_BLOCK m_lpECB;
+  string m_cookie;
+};
+
+extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
+{
+    string targeturl;
+    const IApplication* application=NULL;
+    try
+    {
+        ostringstream threadid;
+        threadid << "[" << getpid() << "] shire_handler" << '\0';
+        saml::NDC ndc(threadid.str().c_str());
+
+        // Determine web site number. This can't really fail, I don't think.
+        dynabuf buf(128);
+        GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
+
+        // Match site instance to host name, skip if no match.
+        map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
+        if (map_i==g_Sites.end())
+            return WriteClientError(lpECB, "Shibboleth Extension not configured for this web site.");
+
+        ShibTargetIsapiE ste(lpECB, map_i->second);
+        pair<bool,void*> res = ste.doHandleProfile();
+        if (res.first) return (DWORD)res.second;
+        
+        return WriteClientError(lpECB, "Shibboleth Extension failed to process request");
+
+    }
+    catch (...) {
+      return WriteClientError(lpECB,
+                             "Shibboleth Extension caught an unknown error.");
+    }
+
+    // If we get here we've got an error.
+    return HSE_STATUS_ERROR;
+}
 
+#if 0
 IRequestMapper::Settings map_request(
     LPEXTENSION_CONTROL_BLOCK lpECB, IRequestMapper* mapper, const site_t& site, string& target
     )
 {
     dynabuf ssl(5);
     GetServerVariable(lpECB,"HTTPS",ssl,5);
-    bool SSL=(ssl=="on");
+    bool SSL=(ssl=="on" || ssl=="ON");
 
     // URL path always come from IIS.
     dynabuf url(256);
@@ -766,42 +1194,29 @@ IRequestMapper::Settings map_request(
 
     // Scheme may come from site def or be derived from IIS.
     const char* scheme=site.m_scheme.c_str();
-    if (!scheme || !*scheme || !g_bNormalizeRequest)
-        scheme=lpECB->lpszMethod;
-    
-    // Start with path.
-    if (!url.empty())
-        target=static_cast<char*>(url);
-    
-    // If port is non-default, prepend it.
-    if ((!strcmp(scheme,"http") && port!="80") || (!strcmp(scheme,"https") && port!="443"))
-        target = ':' + static_cast<char*>(port) + target;
+    if (!scheme || !*scheme || !g_bNormalizeRequest) {
+        scheme = SSL ? "https" : "http";
+    }
 
+    // Start with scheme and hostname.
     if (g_bNormalizeRequest) {
-        target = string(scheme) + "://" + site.m_name + target;
+        target = string(scheme) + "://" + site.m_name;
     }
     else {
         dynabuf name(64);
         GetServerVariable(lpECB,"SERVER_NAME",name,64);
-        target = string(scheme) + "://" + static_cast<char*>(name) + target;
+        target = string(scheme) + "://" + static_cast<char*>(name);
     }
-    return mapper->getSettingsFromParsedURL(scheme,site.m_name.c_str(),strtoul(port,NULL,10),url);
-}
+    
+    // If port is non-default, append it.
+    if ((!strcmp(scheme,"http") && port!="80") || (!strcmp(scheme,"https") && port!="443"))
+        target = target + ':' + static_cast<char*>(port);
 
-DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* msg)
-{
-    LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
-    static const char* ctype="Content-Type: text/html\r\n";
-    lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
-    static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Error</TITLE></HEAD><BODY><H1>Shibboleth Error</H1>";
-    DWORD resplen=strlen(xmsg);
-    lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg,&resplen,HSE_IO_SYNC);
-    resplen=strlen(msg);
-    lpECB->WriteClient(lpECB->ConnID,(LPVOID)msg,&resplen,HSE_IO_SYNC);
-    static const char* xmsg2="</BODY></HTML>";
-    resplen=strlen(xmsg2);
-    lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg2,&resplen,HSE_IO_SYNC);
-    return HSE_STATUS_SUCCESS;
+    // Append path.
+    if (!url.empty())
+        target+=static_cast<char*>(url);
+    
+    return mapper->getSettingsFromParsedURL(scheme,site.m_name.c_str(),strtoul(port,NULL,10),url);
 }
 
 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const IApplication* app, const char* page, ShibMLP& mlp)
@@ -814,7 +1229,7 @@ DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const IApplication* app,
             if (!infile.fail()) {
                 const char* res = mlp.run(infile,props);
                 if (res) {
-                    static const char* ctype="Content-Type: text/html\r\n";
+                    static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
                     lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
                     DWORD resplen=strlen(res);
                     lpECB->WriteClient(lpECB->ConnID,(LPVOID)res,&resplen,0);
@@ -827,6 +1242,30 @@ DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const IApplication* app,
     return WriteClientError(lpECB,"Unable to open error template, check settings.");
 }
 
+DWORD WriteRedirectPage(LPEXTENSION_CONTROL_BLOCK lpECB, const IApplication* app, const char* file, ShibMLP& mlp, const char* headers=NULL)
+{
+    ifstream infile(file);
+    if (!infile.fail()) {
+        const char* res = mlp.run(infile,app->getPropertySet("Errors"));
+        if (res) {
+            char buf[255];
+            sprintf(buf,"Content-Length: %u\r\nContent-Type: text/html\r\n\r\n",strlen(res));
+            if (headers) {
+                string h(headers);
+                h+=buf;
+                lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)h.c_str());
+            }
+            else
+                lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)buf);
+            DWORD resplen=strlen(res);
+            lpECB->WriteClient(lpECB->ConnID,(LPVOID)res,&resplen,0);
+            return HSE_STATUS_SUCCESS;
+        }
+    }
+    LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Extension unable to open redirect template.");
+    return WriteClientError(lpECB,"Unable to open redirect template, check settings.");
+}
+
 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
 {
     string targeturl;
@@ -851,7 +1290,6 @@ extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
         Locker locker(conf);
         
         // Map request to application and content settings.
-        string targeturl;
         IRequestMapper* mapper=conf->getRequestMapper();
         Locker locker2(mapper);
         IRequestMapper::Settings settings=map_request(lpECB,mapper,map_i->second,targeturl);
@@ -862,10 +1300,15 @@ extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
             return WriteClientError(lpECB,"Unable to map request to application session settings, check configuration.");
 
         SHIRE shire(application);
+        
+        const char* shireURL=shire.getShireURL(targeturl.c_str());
+        if (!shireURL)
+            return WriteClientError(lpECB,"Unable to map request to proper shireURL setting, check configuration.");
 
         // Make sure we only process the SHIRE requests.
-        if (!strstr(targeturl.c_str(),shire.getShireURL(targeturl.c_str())))
-            return WriteClientError(lpECB,"The request's application and associated shireURL setting are inconsistent.");;
+        if (!strstr(targeturl.c_str(),shireURL))
+            return WriteClientError(lpECB,"ISAPI extension can only be invoked to process incoming sessions."
+                "Make sure the mapped file extension doesn't match actual content.");
 
         pair<const char*,const char*> shib_cookie=shire.getCookieNameProps();
 
@@ -877,24 +1320,42 @@ extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
                 throw ShibTargetException(SHIBRPC_OK,"blocked non-SSL access to SHIRE POST processor");
         }
         
+        pair<bool,bool> httpRedirects=sessionProps->getBool("httpRedirects");
+        pair<bool,const char*> redirectPage=sessionProps->getString("redirectPage");
+        if (httpRedirects.first && !httpRedirects.second && !redirectPage.first)
+            return WriteClientError(lpECB,"HTML-based redirection requires a redirectPage property.");
+        
+        // Check for Mac web browser
+        /*
+        bool bSafari=false;
+        dynabuf agent(64);
+        GetServerVariable(lpECB,"HTTP_USER_AGENT",agent,64);
+        if (strstr(agent,"AppleWebKit/"))
+            bSafari=true;
+        */
+        
         // If this is a GET, we manufacture an AuthnRequest.
         if (!stricmp(lpECB->lpszMethod,"GET")) {
             const char* areq=lpECB->lpszQueryString ? shire.getLazyAuthnRequest(lpECB->lpszQueryString) : NULL;
             if (!areq)
                 throw ShibTargetException(SHIBRPC_OK, "malformed arguments to request a new session");
-            targeturl = string("Location: ") + areq + "\r\n"
-                "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
-                "Cache-Control: private,no-store,no-cache\r\n"
-                "Connection: close\r\n";
-            HSE_SEND_HEADER_EX_INFO hinfo;
-            hinfo.pszStatus="302 Moved";
-            hinfo.pszHeader=targeturl.c_str();
-            hinfo.cchStatus=9;
-            hinfo.cchHeader=targeturl.length();
-            hinfo.fKeepConn=FALSE;
-            if (lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER_EX,&hinfo,0,0))
+            if (!httpRedirects.first || httpRedirects.second) {
+                string hdrs=string("Location: ") + areq + "\r\n"
+                    "Content-Type: text/html\r\n"
+                    "Content-Length: 40\r\n"
+                    "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
+                    "Cache-Control: private,no-store,no-cache\r\n\r\n";
+                lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"302 Moved",0,(LPDWORD)hdrs.c_str());
+                static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
+                DWORD resplen=40;
+                lpECB->WriteClient(lpECB->ConnID,(LPVOID)redmsg,&resplen,HSE_IO_SYNC);
                 return HSE_STATUS_SUCCESS;
-            return HSE_STATUS_ERROR;
+            }
+            else {
+                ShibMLP markupProcessor;
+                markupProcessor.insert("requestURL",areq);
+                return WriteRedirectPage(lpECB, application, redirectPage.second, markupProcessor);
+            }
         }
         else if (stricmp(lpECB->lpszMethod,"POST"))
             throw ShibTargetException(SHIBRPC_OK,"blocked non-POST to SHIRE POST processor");
@@ -961,10 +1422,22 @@ extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
             if (status->isRetryable()) {
                 delete status;
                 const char* loc=shire.getAuthnRequest(elements.second);
-                DWORD len=strlen(loc);
-                if (lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_URL_REDIRECT_RESP,(LPVOID)loc,&len,0))
+                if (!httpRedirects.first || httpRedirects.second) {
+                    string hdrs=string("Location: ") + loc + "\r\n"
+                        "Content-Type: text/html\r\n"
+                        "Content-Length: 40\r\n"
+                        "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
+                        "Cache-Control: private,no-store,no-cache\r\n\r\n";
+                    lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"302 Moved",0,(LPDWORD)hdrs.c_str());
+                    static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
+                    DWORD resplen=40;
+                    lpECB->WriteClient(lpECB->ConnID,(LPVOID)redmsg,&resplen,HSE_IO_SYNC);
                     return HSE_STATUS_SUCCESS;
-                return HSE_STATUS_ERROR;
+                }
+                else {
+                    markupProcessor.insert("requestURL",loc);
+                    return WriteRedirectPage(lpECB, application, redirectPage.second, markupProcessor);
+                }
             }
     
             // Return this error to the user.
@@ -976,18 +1449,20 @@ extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
     
         // We've got a good session, set the cookie and redirect to target.
         cookie = string("Set-Cookie: ") + shib_cookie.first + '=' + cookie + shib_cookie.second + "\r\n"
-            "Location: " + elements.second + "\r\n"
             "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
-            "Cache-Control: private,no-store,no-cache\r\n"
-            "Connection: close\r\n";
-        HSE_SEND_HEADER_EX_INFO hinfo;
-        hinfo.pszStatus="302 Moved";
-        hinfo.pszHeader=cookie.c_str();
-        hinfo.cchStatus=9;
-        hinfo.cchHeader=cookie.length();
-        hinfo.fKeepConn=FALSE;
-        if (lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER_EX,&hinfo,0,0))
+            "Cache-Control: private,no-store,no-cache\r\n";
+        if (!httpRedirects.first || httpRedirects.second) {
+            cookie=cookie + "Content-Type: text/html\r\nLocation: " + elements.second + "\r\nContent-Length: 40\r\n\r\n";
+            lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"302 Moved",0,(LPDWORD)cookie.c_str());
+            static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
+            DWORD resplen=40;
+            lpECB->WriteClient(lpECB->ConnID,(LPVOID)redmsg,&resplen,HSE_IO_SYNC);
             return HSE_STATUS_SUCCESS;
+        }
+        else {
+            markupProcessor.insert("requestURL",elements.second);
+            return WriteRedirectPage(lpECB, application, redirectPage.second, markupProcessor, cookie.c_str());
+        }
     }
     catch (ShibTargetException &e) {
         if (application) {
@@ -1011,6 +1486,7 @@ extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
         }
     }
 #endif
-    
+
     return HSE_STATUS_ERROR;
 }
+#endif // 0