+/*\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 <xmltooling/util/XMLHelper.h>\r
+#include <xercesc/util/XMLUniDefs.hpp>\r
+\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 exception("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 exception("clearHeader not implemented by FastCGI responder.");\r
+ }\r
+ \r
+ virtual void setHeader(const char* name, const char* value) {\r
+ throw exception("setHeader not implemented by FastCGI responder.");\r
+ }\r
+\r
+ virtual void setRemoteUser(const char* user) {\r
+ throw exception("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