SSPCPP-616 - clean up concatenated string literals
[shibboleth/cpp-sp.git] / fastcgi / shibresponder.cpp
index 293a5b1..32afe8e 100644 (file)
-/*\r
- *  Copyright 2001-2007 Internet2\r
- * \r
- * Licensed under the Apache License, Version 2.0 (the "License");\r
- * you may not use this file except in compliance with the License.\r
- * You may obtain a copy of the License at\r
- *\r
- *     http://www.apache.org/licenses/LICENSE-2.0\r
- *\r
- * Unless required by applicable law or agreed to in writing, software\r
- * distributed under the License is distributed on an "AS IS" BASIS,\r
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
- * See the License for the specific language governing permissions and\r
- * limitations under the License.\r
- */\r
-\r
-/* shibresponder.cpp - Shibboleth FastCGI Responder/Handler\r
-\r
-   Andre Cruz\r
-*/\r
-\r
-#define SHIBSP_LITE\r
-#include "config_win32.h"\r
-\r
-#define _CRT_NONSTDC_NO_DEPRECATE 1\r
-#define _CRT_SECURE_NO_DEPRECATE 1\r
-#define _SCL_SECURE_NO_WARNINGS 1\r
-\r
-#include <shibsp/AbstractSPRequest.h>\r
-#include <shibsp/SPConfig.h>\r
-#include <shibsp/ServiceProvider.h>\r
-#include <xmltooling/unicode.h>\r
-#include <xmltooling/XMLToolingConfig.h>\r
-#include <xmltooling/util/NDC.h>\r
-#include <xmltooling/util/XMLConstants.h>\r
-#include <xercesc/util/XMLUniDefs.hpp>\r
-\r
-#include <stdexcept>\r
-#include <stdlib.h>\r
-#ifdef HAVE_UNISTD_H\r
-# include <unistd.h>\r
-# include <sys/mman.h>\r
-#endif\r
-#include <fcgio.h>\r
-\r
-using namespace shibsp;\r
-using namespace xmltooling;\r
-using namespace xercesc;\r
-using namespace std;\r
-\r
-static const XMLCh path[] =     UNICODE_LITERAL_4(p,a,t,h);\r
-static const XMLCh validate[] = UNICODE_LITERAL_8(v,a,l,i,d,a,t,e);\r
-\r
-typedef enum {\r
-    SHIB_RETURN_OK,\r
-    SHIB_RETURN_KO,\r
-    SHIB_RETURN_DONE\r
-} shib_return_t;\r
-\r
-class ShibTargetFCGI : public AbstractSPRequest\r
-{\r
-    FCGX_Request* m_req;\r
-    const char* m_body;\r
-    multimap<string,string> m_headers;\r
-    int m_port;\r
-    string m_scheme,m_hostname;\r
-\r
-public:\r
-    ShibTargetFCGI(FCGX_Request* req, char* post_data, const char* scheme=NULL, const char* hostname=NULL, int port=0)\r
-        : m_req(req), m_body(post_data) {\r
-\r
-        const char* server_name_str = hostname;\r
-        if (!server_name_str || !*server_name_str)\r
-            server_name_str = FCGX_GetParam("SERVER_NAME", req->envp);\r
-        m_hostname = server_name_str;\r
-\r
-        m_port = port;\r
-        if (!m_port) {\r
-            char* server_port_str = FCGX_GetParam("SERVER_PORT", req->envp);\r
-            m_port = strtol(server_port_str, &server_port_str, 10);\r
-            if (*server_port_str) {\r
-                cerr << "can't parse SERVER_PORT (" << FCGX_GetParam("SERVER_PORT", req->envp) << ")" << endl;\r
-                throw runtime_error("Unable to determine server port.");\r
-            }\r
-        }\r
-\r
-        const char* server_scheme_str = scheme;\r
-        if (!server_scheme_str || !*server_scheme_str)\r
-            server_scheme_str = (m_port == 443 || m_port == 8443) ? "https" : "http";\r
-        m_scheme = server_scheme_str;\r
-    }\r
-\r
-    ~ShibTargetFCGI() { }\r
-\r
-    const char* getScheme() const {\r
-        return m_scheme.c_str();\r
-    }\r
-    const char* getHostname() const {\r
-        return m_hostname.c_str();\r
-    }\r
-    int getPort() const {\r
-        return m_port;\r
-    }\r
-    const char* getRequestURI() const {\r
-        return FCGX_GetParam("REQUEST_URI", m_req->envp);\r
-    }\r
-    const char* getMethod() const {\r
-        return FCGX_GetParam("REQUEST_METHOD", m_req->envp);\r
-    }\r
-    string getContentType() const {\r
-        const char* s = FCGX_GetParam("CONTENT_TYPE", m_req->envp);\r
-        return s ? s : "";\r
-    }\r
-    long getContentLength() const {\r
-        const char* s = FCGX_GetParam("CONTENT_LENGTH", m_req->envp);\r
-        return s ? atol(s) : 0;\r
-    }\r
-    string getRemoteUser() const {\r
-        const char* s = FCGX_GetParam("REMOTE_USER", m_req->envp);\r
-        return s ? s : "";\r
-    }\r
-    string getRemoteAddr() const {\r
-        const char* s = FCGX_GetParam("REMOTE_ADDR", m_req->envp);\r
-        return s ? s : "";\r
-    }\r
-    void log(SPLogLevel level, const string& msg) const {\r
-        AbstractSPRequest::log(level,msg);\r
-        if (level >= SPError)\r
-            cerr << "shib: " << msg;\r
-    }\r
-\r
-    string getHeader(const char* name) const {\r
-        string hdr("HTTP_");\r
-        for (; *name; ++name) {\r
-            if (*name=='-')\r
-                hdr += '_';\r
-            else\r
-                hdr += toupper(*name);\r
-        }\r
-        char* s = FCGX_GetParam(hdr.c_str(), m_req->envp);\r
-        return s ? s : "";\r
-    }\r
-\r
-    void setResponseHeader(const char* name, const char* value) {\r
-        // Set for later.\r
-        if (value)\r
-            m_headers.insert(make_pair(name,value));\r
-        else\r
-            m_headers.erase(name);\r
-    }\r
-\r
-    const char* getQueryString() const {\r
-        return FCGX_GetParam("QUERY_STRING", m_req->envp);\r
-    }\r
-\r
-    const char* getRequestBody() const {\r
-        return m_body;\r
-    }\r
-\r
-    long sendResponse(istream& in, long status) {\r
-        string hdr = string("Connection: close\r\n");\r
-        for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)\r
-            hdr += i->first + ": " + i->second + "\r\n";\r
-\r
-        const char* codestr="Status: 200 OK";\r
-        switch (status) {\r
-            case XMLTOOLING_HTTP_STATUS_ERROR:    codestr="Status: 500 Server Error"; break;\r
-            case XMLTOOLING_HTTP_STATUS_FORBIDDEN:codestr="Status: 403 Forbidden"; break;\r
-            case XMLTOOLING_HTTP_STATUS_NOTFOUND: codestr="Status: 404 Not Found"; break;\r
-        }\r
-        cout << codestr << "\r\n" << hdr << "\r\n";\r
-        char buf[1024];\r
-        while (in) {\r
-            in.read(buf,1024);\r
-            cout.write(buf, in.gcount());\r
-        }\r
-        return SHIB_RETURN_DONE;\r
-    }\r
-\r
-    long sendRedirect(const char* url) {\r
-        string hdr=string("Status: 302 Please Wait\r\nLocation: ") + url + "\r\n"\r
-          "Content-Type: text/html\r\n"\r
-          "Content-Length: 40\r\n"\r
-          "Expires: 01-Jan-1997 12:00:00 GMT\r\n"\r
-          "Cache-Control: private,no-store,no-cache\r\n";\r
-        for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)\r
-            hdr += i->first + ": " + i->second + "\r\n";\r
-        hdr += "\r\n";\r
-\r
-        cout << hdr << "<HTML><BODY>Redirecting...</BODY></HTML>";\r
-        return SHIB_RETURN_DONE;\r
-    }\r
-\r
-    long returnDecline() {\r
-        return SHIB_RETURN_KO;\r
-    }\r
-    long returnOK() {\r
-        return SHIB_RETURN_OK;\r
-    }\r
-\r
-    const vector<string>& getClientCertificates() const {\r
-        static vector<string> g_NoCerts;\r
-        return g_NoCerts;\r
-    }\r
-\r
-    // Not used in the extension.\r
-\r
-    virtual void clearHeader(const char* rawname, const char* cginame) {\r
-        throw runtime_error("clearHeader not implemented by FastCGI responder.");\r
-    }\r
-  \r
-    virtual void setHeader(const char* name, const char* value) {\r
-        throw runtime_error("setHeader not implemented by FastCGI responder.");\r
-    }\r
-\r
-    virtual void setRemoteUser(const char* user) {\r
-        throw runtime_error("setRemoteUser not implemented by FastCGI responder.");\r
-    }\r
-};\r
-\r
-// Maximum number of bytes allowed to be read from stdin\r
-static const unsigned long STDIN_MAX = 1000000;\r
-\r
-static long gstdin(FCGX_Request* request, char** content)\r
-{\r
-    char* clenstr = FCGX_GetParam("CONTENT_LENGTH", request->envp);\r
-    unsigned long clen = STDIN_MAX;\r
-\r
-    if (clenstr) {\r
-        clen = strtol(clenstr, &clenstr, 10);\r
-        if (*clenstr) {\r
-            cerr << "can't parse CONTENT_LENGTH (" << FCGX_GetParam("CONTENT_LENGTH", request->envp) << ")" << endl;\r
-            clen = STDIN_MAX;\r
-        }\r
-\r
-        // *always* put a cap on the amount of data that will be read\r
-        if (clen > STDIN_MAX)\r
-            clen = STDIN_MAX;\r
-\r
-        *content = new char[clen];\r
-\r
-        cin.read(*content, clen);\r
-        clen = cin.gcount();\r
-    }\r
-    else {\r
-        // *never* read stdin when CONTENT_LENGTH is missing or unparsable\r
-        *content = 0;\r
-        clen = 0;\r
-    }\r
-\r
-    // Chew up any remaining stdin - this shouldn't be necessary\r
-    // but is because mod_fastcgi doesn't handle it correctly.\r
-\r
-    // ignore() doesn't set the eof bit in some versions of glibc++\r
-    // so use gcount() instead of eof()...\r
-    do cin.ignore(1024); while (cin.gcount() == 1024);\r
-\r
-    return clen;\r
-}\r
-\r
-static void print_ok() {\r
-    cout << "Status: 200 OK" << "\r\n\r\n";\r
-}\r
-\r
-static void print_error(const char* msg) {\r
-    cout << "Status: 500 Server Error" << "\r\n\r\n" << msg;\r
-}\r
-\r
-int main(void)\r
-{\r
-    const char* schemadir=getenv("SHIBSP_SCHEMAS");\r
-    if (!schemadir)\r
-        schemadir=SHIBSP_SCHEMAS;\r
-    const char* config=getenv("SHIBSP_CONFIG");\r
-    if (!config)\r
-        config=SHIBSP_CONFIG;\r
-\r
-    cerr << "SHIBSP_CONFIG = " << config << endl\r
-         << "SHIBSP_SCHEMAS = " << schemadir << endl;\r
-\r
-    SPConfig* g_Config=&SPConfig::getConfig();\r
-    g_Config->setFeatures(\r
-        SPConfig::Listener |\r
-        SPConfig::Caching |\r
-        SPConfig::RequestMapping |\r
-        SPConfig::InProcess |\r
-        SPConfig::Logging |\r
-        SPConfig::Handlers\r
-        );\r
-    if (!g_Config->init(schemadir)) {\r
-        cerr << "failed to initialize Shibboleth libraries" << endl;\r
-        exit(1);\r
-    }\r
-\r
-    try {\r
-        DOMDocument* dummydoc=XMLToolingConfig::getConfig().getParser().newDocument();\r
-        XercesJanitor<DOMDocument> docjanitor(dummydoc);\r
-        DOMElement* dummy = dummydoc->createElementNS(NULL,path);\r
-        auto_ptr_XMLCh src(config);\r
-        dummy->setAttributeNS(NULL,path,src.get());\r
-        dummy->setAttributeNS(NULL,validate,xmlconstants::XML_ONE);\r
-\r
-        g_Config->setServiceProvider(g_Config->ServiceProviderManager.newPlugin(XML_SERVICE_PROVIDER,dummy));\r
-        g_Config->getServiceProvider()->init();\r
-    }\r
-    catch (exception& ex) {\r
-        g_Config->term();\r
-        cerr << "exception while initializing Shibboleth configuration: " << ex.what() << endl;\r
-        exit(1);\r
-    }\r
-\r
-    string g_ServerScheme;\r
-    string g_ServerName;\r
-    int g_ServerPort=0;\r
-\r
-    // Load "authoritative" URL fields.\r
-    char* var = getenv("SHIBSP_SERVER_NAME");\r
-    if (var)\r
-        g_ServerName = var;\r
-    var = getenv("SHIBSP_SERVER_SCHEME");\r
-    if (var)\r
-        g_ServerScheme = var;\r
-    var = getenv("SHIBSP_SERVER_PORT");\r
-    if (var)\r
-        g_ServerPort = atoi(var);\r
-\r
-    streambuf* cin_streambuf  = cin.rdbuf();\r
-    streambuf* cout_streambuf = cout.rdbuf();\r
-    streambuf* cerr_streambuf = cerr.rdbuf();\r
-\r
-    FCGX_Request request;\r
-\r
-    FCGX_Init();\r
-    FCGX_InitRequest(&request, 0, 0);\r
-    \r
-    cout << "Shibboleth initialization complete. Starting request loop." << endl;\r
-    while (FCGX_Accept_r(&request) == 0) {\r
-        // Note that the default bufsize (0) will cause the use of iostream\r
-        // methods that require positioning (such as peek(), seek(),\r
-        // unget() and putback()) to fail (in favour of more efficient IO).\r
-        fcgi_streambuf cin_fcgi_streambuf(request.in);\r
-        fcgi_streambuf cout_fcgi_streambuf(request.out);\r
-        fcgi_streambuf cerr_fcgi_streambuf(request.err);\r
-\r
-        cin.rdbuf(&cin_fcgi_streambuf);\r
-        cout.rdbuf(&cout_fcgi_streambuf);\r
-        cerr.rdbuf(&cerr_fcgi_streambuf);\r
-\r
-        // Although FastCGI supports writing before reading,\r
-        // many http clients (browsers) don't support it (so\r
-        // the connection deadlocks until a timeout expires!).\r
-        char* content;\r
-        gstdin(&request, &content);\r
-\r
-        try {\r
-            xmltooling::NDC ndc("FastCGI shibresponder");\r
-            ShibTargetFCGI stf(&request, content, g_ServerScheme.c_str(), g_ServerName.c_str(), g_ServerPort);\r
-          \r
-            pair<bool,long> res = stf.getServiceProvider().doHandler(stf);\r
-            if (res.first) {\r
-#ifdef _DEBUG\r
-                cerr << "shib: doHandler handled the request" << endl;\r
-#endif\r
-                switch(res.second) {\r
-                    case SHIB_RETURN_OK:\r
-                        print_ok();\r
-                        break;\r
-              \r
-                    case SHIB_RETURN_KO:\r
-                        cerr << "shib: doHandler failed to handle the request" << endl;\r
-                        print_error("<html><body>FastCGI Shibboleth responder should only be used for Shibboleth protocol requests.</body></html>");\r
-                        break;\r
-\r
-                    case SHIB_RETURN_DONE:\r
-                        // response already handled\r
-                        break;\r
-              \r
-                    default:\r
-                        cerr << "shib: doHandler returned an unexpected result: " << res.second << endl;\r
-                        print_error("<html><body>FastCGI Shibboleth responder returned an unexpected result.</body></html>");\r
-                        break;\r
-                }\r
-            }\r
-            else {\r
-                cerr << "shib: doHandler failed to handle request." << endl;\r
-                print_error("<html><body>FastCGI Shibboleth responder failed to process request.</body></html>");\r
-            }          \r
-          \r
-        }\r
-        catch (exception& e) {\r
-            cerr << "shib: FastCGI responder caught an exception: " << e.what() << endl;\r
-            print_error("<html><body>FastCGI Shibboleth responder caught an exception, check log for details.</body></html>");\r
-        }\r
-\r
-        delete[] content;\r
-\r
-        // If the output streambufs had non-zero bufsizes and\r
-        // were constructed outside of the accept loop (i.e.\r
-        // their destructor won't be called here), they would\r
-        // have to be flushed here.\r
-    }\r
-\r
-    cout << "Request loop ended." << endl;\r
-\r
-    cin.rdbuf(cin_streambuf);\r
-    cout.rdbuf(cout_streambuf);\r
-    cerr.rdbuf(cerr_streambuf);\r
-\r
-    if (g_Config)\r
-        g_Config->term();\r
\r
-    return 0;\r
-}\r
+/**
+ * Licensed to the University Corporation for Advanced Internet
+ * Development, Inc. (UCAID) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for
+ * additional information regarding copyright ownership.
+ *
+ * UCAID licenses this file to you under the Apache License,
+ * Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the
+ * License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific
+ * language governing permissions and limitations under the License.
+ */
+
+/* shibresponder.cpp - Shibboleth FastCGI Responder/Handler
+
+   Andre Cruz
+*/
+
+#define SHIBSP_LITE
+#include "config_win32.h"
+
+#define _CRT_NONSTDC_NO_DEPRECATE 1
+#define _CRT_SECURE_NO_DEPRECATE 1
+#define _SCL_SECURE_NO_WARNINGS 1
+
+#include <shibsp/AbstractSPRequest.h>
+#include <shibsp/SPConfig.h>
+#include <shibsp/ServiceProvider.h>
+#include <xmltooling/unicode.h>
+#include <xmltooling/XMLToolingConfig.h>
+#include <xmltooling/util/NDC.h>
+#include <xmltooling/util/XMLConstants.h>
+#include <xmltooling/util/XMLHelper.h>
+#include <xercesc/util/XMLUniDefs.hpp>
+
+#include <stdexcept>
+#include <stdlib.h>
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+# include <sys/mman.h>
+#endif
+#include <fcgio.h>
+
+using namespace shibsp;
+using namespace xmltooling;
+using namespace xercesc;
+using namespace std;
+
+static const XMLCh path[] =     UNICODE_LITERAL_4(p,a,t,h);
+static const XMLCh validate[] = UNICODE_LITERAL_8(v,a,l,i,d,a,t,e);
+
+typedef enum {
+    SHIB_RETURN_OK,
+    SHIB_RETURN_KO,
+    SHIB_RETURN_DONE
+} shib_return_t;
+
+class ShibTargetFCGI : public AbstractSPRequest
+{
+    FCGX_Request* m_req;
+    const char* m_body;
+    multimap<string,string> m_headers;
+    int m_port;
+    string m_scheme,m_hostname;
+
+public:
+    ShibTargetFCGI(FCGX_Request* req, char* post_data, const char* scheme=nullptr, const char* hostname=nullptr, int port=0)
+        : AbstractSPRequest(SHIBSP_LOGCAT ".FastCGI"), m_req(req), m_body(post_data) {
+
+        const char* server_name_str = hostname;
+        if (!server_name_str || !*server_name_str)
+            server_name_str = FCGX_GetParam("SERVER_NAME", req->envp);
+        m_hostname = server_name_str;
+
+        m_port = port;
+        if (!m_port) {
+            char* server_port_str = FCGX_GetParam("SERVER_PORT", req->envp);
+            m_port = strtol(server_port_str, &server_port_str, 10);
+            if (*server_port_str) {
+                cerr << "can't parse SERVER_PORT (" << FCGX_GetParam("SERVER_PORT", req->envp) << ")" << endl;
+                throw runtime_error("Unable to determine server port.");
+            }
+        }
+
+        const char* server_scheme_str = scheme;
+        if (!server_scheme_str || !*server_scheme_str)
+            server_scheme_str = (m_port == 443 || m_port == 8443) ? "https" : "http";
+        m_scheme = server_scheme_str;
+
+        setRequestURI(FCGX_GetParam("REQUEST_URI", m_req->envp));
+    }
+
+    ~ShibTargetFCGI() { }
+
+    const char* getScheme() const {
+        return m_scheme.c_str();
+    }
+    const char* getHostname() const {
+        return m_hostname.c_str();
+    }
+    int getPort() const {
+        return m_port;
+    }
+    const char* getMethod() const {
+        return FCGX_GetParam("REQUEST_METHOD", m_req->envp);
+    }
+    string getContentType() const {
+        const char* s = FCGX_GetParam("CONTENT_TYPE", m_req->envp);
+        return s ? s : "";
+    }
+    long getContentLength() const {
+        const char* s = FCGX_GetParam("CONTENT_LENGTH", m_req->envp);
+        return s ? atol(s) : 0;
+    }
+    string getRemoteUser() const {
+        const char* s = FCGX_GetParam("REMOTE_USER", m_req->envp);
+        return s ? s : "";
+    }
+    string getRemoteAddr() const {
+        string ret = AbstractSPRequest::getRemoteAddr();
+        if (!ret.empty())
+            return ret;
+        const char* s = FCGX_GetParam("REMOTE_ADDR", m_req->envp);
+        return s ? s : "";
+    }
+    void log(SPLogLevel level, const string& msg) const {
+        AbstractSPRequest::log(level,msg);
+        if (level >= SPError)
+            cerr << "shib: " << msg;
+    }
+
+    string getHeader(const char* name) const {
+        string hdr("HTTP_");
+        for (; *name; ++name) {
+            if (*name=='-')
+                hdr += '_';
+            else
+                hdr += toupper(*name);
+        }
+        char* s = FCGX_GetParam(hdr.c_str(), m_req->envp);
+        return s ? s : "";
+    }
+
+    void setResponseHeader(const char* name, const char* value) {
+        HTTPResponse::setResponseHeader(name, value);
+        if (name) {
+            // Set for later.
+            if (value)
+                m_headers.insert(make_pair(name,value));
+            else
+                m_headers.erase(name);
+        }
+    }
+
+    const char* getQueryString() const {
+        return FCGX_GetParam("QUERY_STRING", m_req->envp);
+    }
+
+    const char* getRequestBody() const {
+        return m_body;
+    }
+
+    long sendResponse(istream& in, long status) {
+        string hdr = string("Connection: close\r\n");
+        for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
+            hdr += i->first + ": " + i->second + "\r\n";
+
+        const char* codestr="Status: 200 OK";
+        switch (status) {
+            case XMLTOOLING_HTTP_STATUS_ERROR:          codestr="Status: 500 Server Error"; break;
+            case XMLTOOLING_HTTP_STATUS_UNAUTHORIZED:   codestr="Status: 401 Authorization Required"; break;
+            case XMLTOOLING_HTTP_STATUS_FORBIDDEN:      codestr="Status: 403 Forbidden"; break;
+            case XMLTOOLING_HTTP_STATUS_NOTFOUND:       codestr="Status: 404 Not Found"; break;
+            case XMLTOOLING_HTTP_STATUS_NOTMODIFIED:    codestr="Status: 304 Not Modified"; break;
+        }
+        cout << codestr << "\r\n" << hdr << "\r\n";
+        char buf[1024];
+        while (in) {
+            in.read(buf,1024);
+            cout.write(buf, in.gcount());
+        }
+        return SHIB_RETURN_DONE;
+    }
+
+    long sendRedirect(const char* url) {
+        HTTPResponse::sendRedirect(url);
+        string hdr=string("Status: 302 Please Wait\r\nLocation: ") + url + "\r\n"
+          "Content-Type: text/html\r\n"
+          "Content-Length: 40\r\n"
+          "Expires: Wed, 01 Jan 1997 12:00:00 GMT\r\n"
+          "Cache-Control: private,no-store,no-cache,max-age=0\r\n";
+        for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
+            hdr += i->first + ": " + i->second + "\r\n";
+        hdr += "\r\n";
+
+        cout << hdr << "<HTML><BODY>Redirecting...</BODY></HTML>";
+        return SHIB_RETURN_DONE;
+    }
+
+    long returnDecline() {
+        return SHIB_RETURN_KO;
+    }
+    long returnOK() {
+        return SHIB_RETURN_OK;
+    }
+
+    const vector<string>& getClientCertificates() const {
+        static vector<string> g_NoCerts;
+        return g_NoCerts;
+    }
+
+    // Not used in the extension.
+
+    virtual void clearHeader(const char* rawname, const char* cginame) {
+        throw runtime_error("clearHeader not implemented by FastCGI responder.");
+    }
+
+    virtual void setHeader(const char* name, const char* value) {
+        throw runtime_error("setHeader not implemented by FastCGI responder.");
+    }
+
+    virtual void setRemoteUser(const char* user) {
+        throw runtime_error("setRemoteUser not implemented by FastCGI responder.");
+    }
+};
+
+// Maximum number of bytes allowed to be read from stdin
+static const unsigned long STDIN_MAX = 1000000;
+
+static long gstdin(FCGX_Request* request, char** content)
+{
+    char* clenstr = FCGX_GetParam("CONTENT_LENGTH", request->envp);
+    unsigned long clen = STDIN_MAX;
+
+    if (clenstr) {
+        clen = strtol(clenstr, &clenstr, 10);
+        if (*clenstr) {
+            cerr << "can't parse CONTENT_LENGTH (" << FCGX_GetParam("CONTENT_LENGTH", request->envp) << ")" << endl;
+            clen = STDIN_MAX;
+        }
+
+        // *always* put a cap on the amount of data that will be read
+        if (clen > STDIN_MAX)
+            clen = STDIN_MAX;
+
+        *content = new char[clen + 1];
+
+        cin.read(*content, clen);
+        clen = cin.gcount();
+        (*content)[clen] = 0;
+    }
+    else {
+        // *never* read stdin when CONTENT_LENGTH is missing or unparsable
+        *content = 0;
+        clen = 0;
+    }
+
+    // Chew up any remaining stdin - this shouldn't be necessary
+    // but is because mod_fastcgi doesn't handle it correctly.
+
+    // ignore() doesn't set the eof bit in some versions of glibc++
+    // so use gcount() instead of eof()...
+    do cin.ignore(1024); while (cin.gcount() == 1024);
+
+    return clen;
+}
+
+static void print_ok() {
+    cout << "Status: 200 OK" << "\r\n\r\n";
+}
+
+static void print_error(const char* msg) {
+    cout << "Status: 500 Server Error" << "\r\n\r\n" << msg;
+}
+
+int main(void)
+{
+    SPConfig* g_Config=&SPConfig::getConfig();
+    g_Config->setFeatures(
+        SPConfig::Listener |
+        SPConfig::Caching |
+        SPConfig::RequestMapping |
+        SPConfig::InProcess |
+        SPConfig::Logging |
+        SPConfig::Handlers
+        );
+    if (!g_Config->init()) {
+        cerr << "failed to initialize Shibboleth libraries" << endl;
+        exit(1);
+    }
+
+    try {
+        if (!g_Config->instantiate(nullptr, true))
+            throw runtime_error("unknown error");
+    }
+    catch (exception& ex) {
+        g_Config->term();
+        cerr << "exception while initializing Shibboleth configuration: " << ex.what() << endl;
+        exit(1);
+    }
+
+    string g_ServerScheme;
+    string g_ServerName;
+    int g_ServerPort=0;
+
+    // Load "authoritative" URL fields.
+    char* var = getenv("SHIBSP_SERVER_NAME");
+    if (var)
+        g_ServerName = var;
+    var = getenv("SHIBSP_SERVER_SCHEME");
+    if (var)
+        g_ServerScheme = var;
+    var = getenv("SHIBSP_SERVER_PORT");
+    if (var)
+        g_ServerPort = atoi(var);
+
+    streambuf* cin_streambuf  = cin.rdbuf();
+    streambuf* cout_streambuf = cout.rdbuf();
+    streambuf* cerr_streambuf = cerr.rdbuf();
+
+    FCGX_Request request;
+
+    FCGX_Init();
+    FCGX_InitRequest(&request, 0, 0);
+
+    cout << "Shibboleth initialization complete. Starting request loop." << endl;
+    while (FCGX_Accept_r(&request) == 0) {
+        // Note that the default bufsize (0) will cause the use of iostream
+        // methods that require positioning (such as peek(), seek(),
+        // unget() and putback()) to fail (in favour of more efficient IO).
+        fcgi_streambuf cin_fcgi_streambuf(request.in);
+        fcgi_streambuf cout_fcgi_streambuf(request.out);
+        fcgi_streambuf cerr_fcgi_streambuf(request.err);
+
+        cin.rdbuf(&cin_fcgi_streambuf);
+        cout.rdbuf(&cout_fcgi_streambuf);
+        cerr.rdbuf(&cerr_fcgi_streambuf);
+
+        // Although FastCGI supports writing before reading,
+        // many http clients (browsers) don't support it (so
+        // the connection deadlocks until a timeout expires!).
+        char* content = nullptr;
+        gstdin(&request, &content);
+        auto_arrayptr<char> wrapper(content);
+
+        try {
+            xmltooling::NDC ndc("FastCGI shibresponder");
+            ShibTargetFCGI stf(&request, content, g_ServerScheme.c_str(), g_ServerName.c_str(), g_ServerPort);
+
+            pair<bool,long> res = stf.getServiceProvider().doHandler(stf);
+            if (res.first) {
+                stf.log(SPRequest::SPDebug, "shib: doHandler handled the request");
+                switch(res.second) {
+                    case SHIB_RETURN_OK:
+                        print_ok();
+                        break;
+
+                    case SHIB_RETURN_KO:
+                        cerr << "shib: doHandler failed to handle the request" << endl;
+                        print_error("<html><body>FastCGI Shibboleth responder should only be used for Shibboleth protocol requests.</body></html>");
+                        break;
+
+                    case SHIB_RETURN_DONE:
+                        // response already handled
+                        break;
+
+                    default:
+                        cerr << "shib: doHandler returned an unexpected result: " << res.second << endl;
+                        print_error("<html><body>FastCGI Shibboleth responder returned an unexpected result.</body></html>");
+                        break;
+                }
+            }
+            else {
+                cerr << "shib: doHandler failed to handle request." << endl;
+                print_error("<html><body>FastCGI Shibboleth responder failed to process request.</body></html>");
+            }
+
+        }
+        catch (exception& e) {
+            cerr << "shib: FastCGI responder caught an exception: " << e.what() << endl;
+            print_error("<html><body>FastCGI Shibboleth responder caught an exception, check log for details.</body></html>");
+        }
+
+        // If the output streambufs had non-zero bufsizes and
+        // were constructed outside of the accept loop (i.e.
+        // their destructor won't be called here), they would
+        // have to be flushed here.
+    }
+
+    cout << "Request loop ended." << endl;
+
+    cin.rdbuf(cin_streambuf);
+    cout.rdbuf(cout_streambuf);
+    cerr.rdbuf(cerr_streambuf);
+
+    if (g_Config)
+        g_Config->term();
+
+    return 0;
+}