2 * Licensed to the University Corporation for Advanced Internet
3 * Development, Inc. (UCAID) under one or more contributor license
4 * agreements. See the NOTICE file distributed with this work for
5 * additional information regarding copyright ownership.
7 * UCAID licenses this file to you under the Apache License,
8 * Version 2.0 (the "License"); you may not use this file except
9 * in compliance with the License. You may obtain a copy of the
12 * http://www.apache.org/licenses/LICENSE-2.0
14 * Unless required by applicable law or agreed to in writing,
15 * software distributed under the License is distributed on an
16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
17 * either express or implied. See the License for the specific
18 * language governing permissions and limitations under the License.
21 /* shibresponder.cpp - Shibboleth FastCGI Responder/Handler
27 #include "config_win32.h"
29 #define _CRT_NONSTDC_NO_DEPRECATE 1
30 #define _CRT_SECURE_NO_DEPRECATE 1
31 #define _SCL_SECURE_NO_WARNINGS 1
33 #include <shibsp/AbstractSPRequest.h>
34 #include <shibsp/SPConfig.h>
35 #include <shibsp/ServiceProvider.h>
36 #include <xmltooling/unicode.h>
37 #include <xmltooling/XMLToolingConfig.h>
38 #include <xmltooling/util/NDC.h>
39 #include <xmltooling/util/XMLConstants.h>
40 #include <xmltooling/util/XMLHelper.h>
41 #include <xercesc/util/XMLUniDefs.hpp>
47 # include <sys/mman.h>
51 using namespace shibsp;
52 using namespace xmltooling;
53 using namespace xercesc;
56 static const XMLCh path[] = UNICODE_LITERAL_4(p,a,t,h);
57 static const XMLCh validate[] = UNICODE_LITERAL_8(v,a,l,i,d,a,t,e);
65 class ShibTargetFCGI : public AbstractSPRequest
69 multimap<string,string> m_headers;
71 string m_scheme,m_hostname;
74 ShibTargetFCGI(FCGX_Request* req, char* post_data, const char* scheme=nullptr, const char* hostname=nullptr, int port=0)
75 : AbstractSPRequest(SHIBSP_LOGCAT ".FastCGI"), m_req(req), m_body(post_data) {
77 const char* server_name_str = hostname;
78 if (!server_name_str || !*server_name_str)
79 server_name_str = FCGX_GetParam("SERVER_NAME", req->envp);
80 m_hostname = server_name_str;
84 char* server_port_str = FCGX_GetParam("SERVER_PORT", req->envp);
85 m_port = strtol(server_port_str, &server_port_str, 10);
86 if (*server_port_str) {
87 cerr << "can't parse SERVER_PORT (" << FCGX_GetParam("SERVER_PORT", req->envp) << ")" << endl;
88 throw runtime_error("Unable to determine server port.");
92 const char* server_scheme_str = scheme;
93 if (!server_scheme_str || !*server_scheme_str)
94 server_scheme_str = (m_port == 443 || m_port == 8443) ? "https" : "http";
95 m_scheme = server_scheme_str;
97 setRequestURI(FCGX_GetParam("REQUEST_URI", m_req->envp));
100 ~ShibTargetFCGI() { }
102 const char* getScheme() const {
103 return m_scheme.c_str();
105 const char* getHostname() const {
106 return m_hostname.c_str();
108 int getPort() const {
111 const char* getMethod() const {
112 return FCGX_GetParam("REQUEST_METHOD", m_req->envp);
114 string getContentType() const {
115 const char* s = FCGX_GetParam("CONTENT_TYPE", m_req->envp);
118 long getContentLength() const {
119 const char* s = FCGX_GetParam("CONTENT_LENGTH", m_req->envp);
120 return s ? atol(s) : 0;
122 string getRemoteUser() const {
123 const char* s = FCGX_GetParam("REMOTE_USER", m_req->envp);
126 string getRemoteAddr() const {
127 string ret = AbstractSPRequest::getRemoteAddr();
130 const char* s = FCGX_GetParam("REMOTE_ADDR", m_req->envp);
133 void log(SPLogLevel level, const string& msg) const {
134 AbstractSPRequest::log(level,msg);
135 if (level >= SPError)
136 cerr << "shib: " << msg;
139 string getHeader(const char* name) const {
141 for (; *name; ++name) {
145 hdr += toupper(*name);
147 char* s = FCGX_GetParam(hdr.c_str(), m_req->envp);
151 void setResponseHeader(const char* name, const char* value) {
152 HTTPResponse::setResponseHeader(name, value);
156 m_headers.insert(make_pair(name,value));
158 m_headers.erase(name);
162 const char* getQueryString() const {
163 return FCGX_GetParam("QUERY_STRING", m_req->envp);
166 const char* getRequestBody() const {
170 long sendResponse(istream& in, long status) {
171 string hdr = string("Connection: close\r\n");
172 for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
173 hdr += i->first + ": " + i->second + "\r\n";
175 const char* codestr="Status: 200 OK";
177 case XMLTOOLING_HTTP_STATUS_ERROR: codestr="Status: 500 Server Error"; break;
178 case XMLTOOLING_HTTP_STATUS_UNAUTHORIZED: codestr="Status: 401 Authorization Required"; break;
179 case XMLTOOLING_HTTP_STATUS_FORBIDDEN: codestr="Status: 403 Forbidden"; break;
180 case XMLTOOLING_HTTP_STATUS_NOTFOUND: codestr="Status: 404 Not Found"; break;
181 case XMLTOOLING_HTTP_STATUS_NOTMODIFIED: codestr="Status: 304 Not Modified"; break;
183 cout << codestr << "\r\n" << hdr << "\r\n";
187 cout.write(buf, in.gcount());
189 return SHIB_RETURN_DONE;
192 long sendRedirect(const char* url) {
193 HTTPResponse::sendRedirect(url);
194 string hdr=string("Status: 302 Please Wait\r\nLocation: ") + url + "\r\n"
195 "Content-Type: text/html\r\n"
196 "Content-Length: 40\r\n"
197 "Expires: Wed, 01 Jan 1997 12:00:00 GMT\r\n"
198 "Cache-Control: private,no-store,no-cache,max-age=0\r\n";
199 for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
200 hdr += i->first + ": " + i->second + "\r\n";
203 cout << hdr << "<HTML><BODY>Redirecting...</BODY></HTML>";
204 return SHIB_RETURN_DONE;
207 long returnDecline() {
208 return SHIB_RETURN_KO;
211 return SHIB_RETURN_OK;
214 const vector<string>& getClientCertificates() const {
215 static vector<string> g_NoCerts;
219 // Not used in the extension.
221 virtual void clearHeader(const char* rawname, const char* cginame) {
222 throw runtime_error("clearHeader not implemented by FastCGI responder.");
225 virtual void setHeader(const char* name, const char* value) {
226 throw runtime_error("setHeader not implemented by FastCGI responder.");
229 virtual void setRemoteUser(const char* user) {
230 throw runtime_error("setRemoteUser not implemented by FastCGI responder.");
234 // Maximum number of bytes allowed to be read from stdin
235 static const unsigned long STDIN_MAX = 1000000;
237 static long gstdin(FCGX_Request* request, char** content)
239 char* clenstr = FCGX_GetParam("CONTENT_LENGTH", request->envp);
240 unsigned long clen = STDIN_MAX;
243 clen = strtol(clenstr, &clenstr, 10);
245 cerr << "can't parse CONTENT_LENGTH (" << FCGX_GetParam("CONTENT_LENGTH", request->envp) << ")" << endl;
249 // *always* put a cap on the amount of data that will be read
250 if (clen > STDIN_MAX)
253 *content = new char[clen + 1];
255 cin.read(*content, clen);
257 (*content)[clen] = 0;
260 // *never* read stdin when CONTENT_LENGTH is missing or unparsable
265 // Chew up any remaining stdin - this shouldn't be necessary
266 // but is because mod_fastcgi doesn't handle it correctly.
268 // ignore() doesn't set the eof bit in some versions of glibc++
269 // so use gcount() instead of eof()...
270 do cin.ignore(1024); while (cin.gcount() == 1024);
275 static void print_ok() {
276 cout << "Status: 200 OK" << "\r\n\r\n";
279 static void print_error(const char* msg) {
280 cout << "Status: 500 Server Error" << "\r\n\r\n" << msg;
285 SPConfig* g_Config=&SPConfig::getConfig();
286 g_Config->setFeatures(
289 SPConfig::RequestMapping |
290 SPConfig::InProcess |
294 if (!g_Config->init()) {
295 cerr << "failed to initialize Shibboleth libraries" << endl;
300 if (!g_Config->instantiate(nullptr, true))
301 throw runtime_error("unknown error");
303 catch (exception& ex) {
305 cerr << "exception while initializing Shibboleth configuration: " << ex.what() << endl;
309 string g_ServerScheme;
313 // Load "authoritative" URL fields.
314 char* var = getenv("SHIBSP_SERVER_NAME");
317 var = getenv("SHIBSP_SERVER_SCHEME");
319 g_ServerScheme = var;
320 var = getenv("SHIBSP_SERVER_PORT");
322 g_ServerPort = atoi(var);
324 streambuf* cin_streambuf = cin.rdbuf();
325 streambuf* cout_streambuf = cout.rdbuf();
326 streambuf* cerr_streambuf = cerr.rdbuf();
328 FCGX_Request request;
331 FCGX_InitRequest(&request, 0, 0);
333 cout << "Shibboleth initialization complete. Starting request loop." << endl;
334 while (FCGX_Accept_r(&request) == 0) {
335 // Note that the default bufsize (0) will cause the use of iostream
336 // methods that require positioning (such as peek(), seek(),
337 // unget() and putback()) to fail (in favour of more efficient IO).
338 fcgi_streambuf cin_fcgi_streambuf(request.in);
339 fcgi_streambuf cout_fcgi_streambuf(request.out);
340 fcgi_streambuf cerr_fcgi_streambuf(request.err);
342 cin.rdbuf(&cin_fcgi_streambuf);
343 cout.rdbuf(&cout_fcgi_streambuf);
344 cerr.rdbuf(&cerr_fcgi_streambuf);
346 // Although FastCGI supports writing before reading,
347 // many http clients (browsers) don't support it (so
348 // the connection deadlocks until a timeout expires!).
349 char* content = nullptr;
350 gstdin(&request, &content);
351 auto_arrayptr<char> wrapper(content);
354 xmltooling::NDC ndc("FastCGI shibresponder");
355 ShibTargetFCGI stf(&request, content, g_ServerScheme.c_str(), g_ServerName.c_str(), g_ServerPort);
357 pair<bool,long> res = stf.getServiceProvider().doHandler(stf);
359 stf.log(SPRequest::SPDebug, "shib: doHandler handled the request");
366 cerr << "shib: doHandler failed to handle the request" << endl;
367 print_error("<html><body>FastCGI Shibboleth responder should only be used for Shibboleth protocol requests.</body></html>");
370 case SHIB_RETURN_DONE:
371 // response already handled
375 cerr << "shib: doHandler returned an unexpected result: " << res.second << endl;
376 print_error("<html><body>FastCGI Shibboleth responder returned an unexpected result.</body></html>");
381 cerr << "shib: doHandler failed to handle request." << endl;
382 print_error("<html><body>FastCGI Shibboleth responder failed to process request.</body></html>");
386 catch (exception& e) {
387 cerr << "shib: FastCGI responder caught an exception: " << e.what() << endl;
388 print_error("<html><body>FastCGI Shibboleth responder caught an exception, check log for details.</body></html>");
391 // If the output streambufs had non-zero bufsizes and
392 // were constructed outside of the accept loop (i.e.
393 // their destructor won't be called here), they would
394 // have to be flushed here.
397 cout << "Request loop ended." << endl;
399 cin.rdbuf(cin_streambuf);
400 cout.rdbuf(cout_streambuf);
401 cerr.rdbuf(cerr_streambuf);