Use shibboleth-sp as package name for compatibility.
[shibboleth/cpp-sp.git] / fastcgi / shibauthorizer.cpp
index 4661ee8..76d4a27 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
-/* shibauthorizer.cpp - Shibboleth FastCGI Authorizer\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 <xmltooling/util/XMLHelper.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 ShibTargetFCGIAuth : public AbstractSPRequest\r
-{\r
-    FCGX_Request* m_req;\r
-    int m_port;\r
-    string m_scheme,m_hostname;\r
-    multimap<string,string> m_response_headers;\r
-public:\r
-    map<string,string> m_request_headers;\r
-\r
-    ShibTargetFCGIAuth(FCGX_Request* req, const char* scheme=NULL, const char* hostname=NULL, int port=0)\r
-            : AbstractSPRequest(SHIBSP_LOGCAT".FastCGI"), m_req(req) {\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
-        setRequestURI(FCGX_GetParam("REQUEST_URI", m_req->envp));\r
-    }\r
-\r
-    ~ShibTargetFCGIAuth() { }\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* 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 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
-    void clearHeader(const char* rawname, const char* cginame) {\r
-        // no need, since request headers turn into actual environment variables\r
-    }\r
-    void setHeader(const char* name, const char* value) {\r
-        if (value)\r
-            m_request_headers[name] = value;\r
-        else\r
-            m_request_headers.erase(name);\r
-    }\r
-    virtual string getHeader(const char* name) const {\r
-        map<string,string>::const_iterator i = m_request_headers.find(name);\r
-        if (i != m_request_headers.end())\r
-            return i->second;\r
-        else\r
-            return "";\r
-    }\r
-    void setRemoteUser(const char* user) {\r
-        if (user)\r
-            m_request_headers["REMOTE_USER"] = user;\r
-        else\r
-            m_request_headers.erase("REMOTE_USER");\r
-    }\r
-    string getRemoteUser() const {\r
-        map<string,string>::const_iterator i = m_request_headers.find("REMOTE_USER");\r
-        if (i != m_request_headers.end())\r
-            return i->second;\r
-        else {\r
-            char* remote_user = FCGX_GetParam("REMOTE_USER", m_req->envp);\r
-            if (remote_user)\r
-                return remote_user;\r
-        }\r
-        return "";\r
-    }\r
-    void setResponseHeader(const char* name, const char* value) {\r
-        // Set for later.\r
-        if (value)\r
-            m_response_headers.insert(make_pair(name,value));\r
-        else\r
-            m_response_headers.erase(name);\r
-    }\r
-    const char* getQueryString() const {\r
-        return FCGX_GetParam("QUERY_STRING", m_req->envp);\r
-    }\r
-    const char* getRequestBody() const {\r
-        throw runtime_error("getRequestBody not implemented by FastCGI authorizer.");\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_response_headers.begin(); i!=m_response_headers.end(); ++i)\r
-            hdr += i->first + ": " + i->second + "\r\n";\r
-\r
-        // We can't return 200 OK here or else the filter is bypassed\r
-        // so custom Shib errors will get turned into a generic page.\r
-        const char* codestr="Status: 500 Server Error";\r
-        switch (status) {\r
-            case XMLTOOLING_HTTP_STATUS_UNAUTHORIZED:   codestr="Status: 401 Authorization Required"; 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_response_headers.begin(); i!=m_response_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
-\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
-\r
-static void print_ok(const map<string,string>& headers)\r
-{\r
-    cout << "Status: 200 OK" << "\r\n";\r
-    for (map<string,string>::const_iterator iter = headers.begin(); iter != headers.end(); iter++) {\r
-        cout << "Variable-" << iter->first << ": " << iter->second << "\r\n";\r
-    }\r
-    cout << "\r\n";\r
-}\r
-\r
-static void print_error(const char* msg)\r
-{\r
-    cout << "Status: 500 Server Error" << "\r\n\r\n" << msg;\r
-}\r
-\r
-int main(void)\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()) {\r
-        cerr << "failed to initialize Shibboleth libraries" << endl;\r
-        exit(1);\r
-    }\r
-\r
-    try {\r
-        if (!g_Config->instantiate(NULL, true))\r
-            throw exception("unknown error");\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* 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
-    {\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 cout_fcgi_streambuf(request.out);\r
-        fcgi_streambuf cerr_fcgi_streambuf(request.err);\r
-\r
-        cout.rdbuf(&cout_fcgi_streambuf);\r
-        cerr.rdbuf(&cerr_fcgi_streambuf);\r
-\r
-        try {\r
-            xmltooling::NDC ndc("FastCGI shibauthorizer");\r
-            ShibTargetFCGIAuth sta(&request, g_ServerScheme.c_str(), g_ServerName.c_str(), g_ServerPort);\r
-\r
-            pair<bool,long> res = sta.getServiceProvider().doAuthentication(sta);\r
-            if (res.first) {\r
-#ifdef _DEBUG\r
-                cerr << "shib: doAuthentication handled the request" << endl;\r
-#endif\r
-                switch(res.second) {\r
-                    case SHIB_RETURN_OK:\r
-                        print_ok(sta.m_request_headers);\r
-                        continue;\r
-\r
-                    case SHIB_RETURN_KO:\r
-                        print_ok(sta.m_request_headers);\r
-                        continue;\r
-\r
-                    case SHIB_RETURN_DONE:\r
-                        continue;\r
-\r
-                    default:\r
-                        cerr << "shib: doAuthentication returned an unexpected result: " << res.second << endl;\r
-                        print_error("<html><body>FastCGI Shibboleth authorizer returned an unexpected result.</body></html>");\r
-                        continue;\r
-                }\r
-            }\r
-\r
-            res = sta.getServiceProvider().doExport(sta);\r
-            if (res.first) {\r
-#ifdef _DEBUG\r
-                cerr << "shib: doExport handled request" << endl;\r
-#endif\r
-                switch(res.second) {\r
-                    case SHIB_RETURN_OK:\r
-                        print_ok(sta.m_request_headers);\r
-                        continue;\r
-\r
-                    case SHIB_RETURN_KO:\r
-                        print_ok(sta.m_request_headers);\r
-                        continue;\r
-\r
-                    case SHIB_RETURN_DONE:\r
-                        continue;\r
-\r
-                    default:\r
-                        cerr << "shib: doExport returned an unexpected result: " << res.second << endl;\r
-                        print_error("<html><body>FastCGI Shibboleth authorizer returned an unexpected result.</body></html>");\r
-                        continue;\r
-                }\r
-            }\r
-\r
-            res = sta.getServiceProvider().doAuthorization(sta);\r
-            if (res.first) {\r
-#ifdef _DEBUG\r
-                cerr << "shib: doAuthorization handled request" << endl;\r
-#endif\r
-                switch(res.second) {\r
-                    case SHIB_RETURN_OK:\r
-                        print_ok(sta.m_request_headers);\r
-                        continue;\r
-\r
-                    case SHIB_RETURN_KO:\r
-                        print_ok(sta.m_request_headers);\r
-                        continue;\r
-\r
-                    case SHIB_RETURN_DONE:\r
-                        continue;\r
-\r
-                    default:\r
-                        cerr << "shib: doAuthorization returned an unexpected result: " << res.second << endl;\r
-                        print_error("<html><body>FastCGI Shibboleth authorizer returned an unexpected result.</body></html>");\r
-                        continue;\r
-                }\r
-            }\r
-\r
-            print_ok(sta.m_request_headers);\r
-\r
-        }\r
-        catch (exception& e) {\r
-            cerr << "shib: FastCGI authorizer caught an exception: " << e.what() << endl;\r
-            print_error("<html><body>FastCGI Shibboleth authorizer caught an exception, check log for details.</body></html>");\r
-        }\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
-    cout << "Request loop ended." << endl;\r
-\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.
+ */
+
+/* shibauthorizer.cpp - Shibboleth FastCGI Authorizer
+
+   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 ShibTargetFCGIAuth : public AbstractSPRequest
+{
+    FCGX_Request* m_req;
+    int m_port;
+    string m_scheme,m_hostname;
+    multimap<string,string> m_response_headers;
+public:
+    map<string,string> m_request_headers;
+
+    ShibTargetFCGIAuth(FCGX_Request* req, const char* scheme=nullptr, const char* hostname=nullptr, int port=0)
+            : AbstractSPRequest(SHIBSP_LOGCAT ".FastCGI"), m_req(req) {
+        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));
+    }
+
+    ~ShibTargetFCGIAuth() { }
+
+    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 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;
+    }
+    void clearHeader(const char* rawname, const char* cginame) {
+        // No need, since we use environment variables.
+    }
+    void setHeader(const char* name, const char* value) {
+        if (value)
+            m_request_headers[name] = value;
+        else
+            m_request_headers.erase(name);
+    }
+    string getHeader(const char* name) const {
+        // Look in the local map first.
+        map<string,string>::const_iterator i = m_request_headers.find(name);
+        if (i != m_request_headers.end())
+            return i->second;
+        // Nothing set locally and this isn't a "secure" call, so check the request.
+        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 : "";
+    }
+    string getSecureHeader(const char* name) const {
+        // Look in the local map only.
+        map<string,string>::const_iterator i = m_request_headers.find(name);
+        if (i != m_request_headers.end())
+            return i->second;
+        return "";
+    }
+    void setRemoteUser(const char* user) {
+        if (user)
+            m_request_headers["REMOTE_USER"] = user;
+        else
+            m_request_headers.erase("REMOTE_USER");
+    }
+    string getRemoteUser() const {
+        map<string,string>::const_iterator i = m_request_headers.find("REMOTE_USER");
+        if (i != m_request_headers.end())
+            return i->second;
+        else {
+            char* remote_user = FCGX_GetParam("REMOTE_USER", m_req->envp);
+            if (remote_user)
+                return remote_user;
+        }
+        return "";
+    }
+    void setAuthType(const char* authtype) {
+        if (authtype)
+            m_request_headers["AUTH_TYPE"] = authtype;
+        else
+            m_request_headers.erase("AUTH_TYPE");
+    }
+    string getAuthType() const {
+        map<string,string>::const_iterator i = m_request_headers.find("AUTH_TYPE");
+        if (i != m_request_headers.end())
+            return i->second;
+        else {
+            char* auth_type = FCGX_GetParam("AUTH_TYPE", m_req->envp);
+            if (auth_type)
+                return auth_type;
+        }
+        return "";
+    }
+    void setResponseHeader(const char* name, const char* value) {
+        HTTPResponse::setResponseHeader(name, value);
+        if (name) {
+            // Set for later.
+            if (value)
+                m_response_headers.insert(make_pair(name,value));
+            else
+                m_response_headers.erase(name);
+        }
+    }
+    const char* getQueryString() const {
+        return FCGX_GetParam("QUERY_STRING", m_req->envp);
+    }
+    const char* getRequestBody() const {
+        throw runtime_error("getRequestBody not implemented by FastCGI authorizer.");
+    }
+
+    long sendResponse(istream& in, long status) {
+        string hdr = string("Connection: close\r\n");
+        for (multimap<string,string>::const_iterator i=m_response_headers.begin(); i!=m_response_headers.end(); ++i)
+            hdr += i->first + ": " + i->second + "\r\n";
+
+        // We can't return 200 OK here or else the filter is bypassed
+        // so custom Shib errors will get turned into a generic page.
+        const char* codestr="Status: 500 Server Error";
+        switch (status) {
+            case XMLTOOLING_HTTP_STATUS_NOTMODIFIED:    codestr="Status: 304 Not Modified"; 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;
+        }
+        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_response_headers.begin(); i!=m_response_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;
+    }
+};
+
+static void print_ok(const map<string,string>& headers)
+{
+    cout << "Status: 200 OK" << "\r\n";
+    for (map<string,string>::const_iterator iter = headers.begin(); iter != headers.end(); iter++) {
+        cout << "Variable-" << iter->first << ": " << iter->second << "\r\n";
+    }
+    cout << "\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* 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 cout_fcgi_streambuf(request.out);
+        fcgi_streambuf cerr_fcgi_streambuf(request.err);
+
+        cout.rdbuf(&cout_fcgi_streambuf);
+        cerr.rdbuf(&cerr_fcgi_streambuf);
+
+        try {
+            xmltooling::NDC ndc("FastCGI shibauthorizer");
+            ShibTargetFCGIAuth sta(&request, g_ServerScheme.c_str(), g_ServerName.c_str(), g_ServerPort);
+
+            pair<bool,long> res = sta.getServiceProvider().doAuthentication(sta);
+            if (res.first) {
+                sta.log(SPRequest::SPDebug, "shib: doAuthentication handled the request");
+                switch(res.second) {
+                    case SHIB_RETURN_OK:
+                        print_ok(sta.m_request_headers);
+                        continue;
+
+                    case SHIB_RETURN_KO:
+                        print_ok(sta.m_request_headers);
+                        continue;
+
+                    case SHIB_RETURN_DONE:
+                        continue;
+
+                    default:
+                        cerr << "shib: doAuthentication returned an unexpected result: " << res.second << endl;
+                        print_error("<html><body>FastCGI Shibboleth authorizer returned an unexpected result.</body></html>");
+                        continue;
+                }
+            }
+
+            res = sta.getServiceProvider().doExport(sta);
+            if (res.first) {
+                sta.log(SPRequest::SPDebug, "shib: doExport handled request");
+                switch(res.second) {
+                    case SHIB_RETURN_OK:
+                        print_ok(sta.m_request_headers);
+                        continue;
+
+                    case SHIB_RETURN_KO:
+                        print_ok(sta.m_request_headers);
+                        continue;
+
+                    case SHIB_RETURN_DONE:
+                        continue;
+
+                    default:
+                        cerr << "shib: doExport returned an unexpected result: " << res.second << endl;
+                        print_error("<html><body>FastCGI Shibboleth authorizer returned an unexpected result.</body></html>");
+                        continue;
+                }
+            }
+
+            res = sta.getServiceProvider().doAuthorization(sta);
+            if (res.first) {
+                sta.log(SPRequest::SPDebug, "shib: doAuthorization handled request");
+                switch(res.second) {
+                    case SHIB_RETURN_OK:
+                        print_ok(sta.m_request_headers);
+                        continue;
+
+                    case SHIB_RETURN_KO:
+                        print_ok(sta.m_request_headers);
+                        continue;
+
+                    case SHIB_RETURN_DONE:
+                        continue;
+
+                    default:
+                        cerr << "shib: doAuthorization returned an unexpected result: " << res.second << endl;
+                        print_error("<html><body>FastCGI Shibboleth authorizer returned an unexpected result.</body></html>");
+                        continue;
+                }
+            }
+
+            print_ok(sta.m_request_headers);
+
+        }
+        catch (exception& e) {
+            cerr << "shib: FastCGI authorizer caught an exception: " << e.what() << endl;
+            print_error("<html><body>FastCGI Shibboleth authorizer 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;
+
+    cout.rdbuf(cout_streambuf);
+    cerr.rdbuf(cerr_streambuf);
+
+    if (g_Config)
+        g_Config->term();
+
+    return 0;
+}