2 * Copyright 2001-2009 Internet2
\r
4 * Licensed under the Apache License, Version 2.0 (the "License");
\r
5 * you may not use this file except in compliance with the License.
\r
6 * You may obtain a copy of the License at
\r
8 * http://www.apache.org/licenses/LICENSE-2.0
\r
10 * Unless required by applicable law or agreed to in writing, software
\r
11 * distributed under the License is distributed on an "AS IS" BASIS,
\r
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
\r
13 * See the License for the specific language governing permissions and
\r
14 * limitations under the License.
\r
17 /* shibresponder.cpp - Shibboleth FastCGI Responder/Handler
\r
23 #include <saml/saml.h>
\r
24 #include <shib-target/shib-target.h>
\r
26 #include <stdexcept>
\r
28 #ifdef HAVE_UNISTD_H
\r
29 # include <unistd.h>
\r
30 # include <sys/mman.h>
\r
34 using namespace shibtarget;
\r
35 using namespace saml;
\r
36 using namespace std;
\r
44 set<string> g_allowedSchemes;
\r
46 class ShibTargetFCGI : public ShibTarget
\r
48 FCGX_Request* m_req;
\r
51 map<string, string> m_headers;
\r
53 void checkString(const string& s, const char* msg) {
\r
54 string::const_iterator e = s.end();
\r
55 for (string::const_iterator i=s.begin(); i!=e; ++i) {
\r
57 throw runtime_error(msg);
\r
62 ShibTargetFCGI(FCGX_Request* req, char* post_data, const char* scheme=NULL, const char* hostname=NULL, int port=0)
\r
63 : m_req(req), m_body(post_data) {
\r
65 const char* server_name_str = hostname;
\r
66 if (!server_name_str || !*server_name_str)
\r
67 server_name_str = FCGX_GetParam("SERVER_NAME", req->envp);
\r
69 int server_port = port;
\r
71 char* server_port_str = FCGX_GetParam("SERVER_PORT", req->envp);
\r
72 server_port = strtol(server_port_str, &server_port_str, 10);
\r
73 if (*server_port_str) {
\r
74 cerr << "can't parse SERVER_PORT (" << FCGX_GetParam("SERVER_PORT", req->envp) << ")" << endl;
\r
75 throw runtime_error("Unable to determine server port.");
\r
79 const char* server_scheme_str = scheme;
\r
80 if (!server_scheme_str || !*server_scheme_str)
\r
81 server_scheme_str = (server_port == 443 || server_port == 8443) ? "https" : "http";
\r
83 const char* request_uri_str = FCGX_GetParam("REQUEST_URI", req->envp);
\r
84 const char* content_type_str = FCGX_GetParam("CONTENT_TYPE", req->envp);
\r
85 const char* remote_addr_str = FCGX_GetParam("REMOTE_ADDR", req->envp);
\r
86 const char* request_method_str = FCGX_GetParam("REQUEST_METHOD", req->envp);
\r
89 cerr << "server_name = " << server_name_str << endl
\r
90 << "server_port = " << server_port << endl
\r
91 << "request_uri_str = " << request_uri_str << endl
\r
92 << "content_type = " << content_type_str << endl
\r
93 << "remote_address = " << remote_addr_str << endl
\r
94 << "request_method = " << request_method_str << endl;
\r
97 init(server_scheme_str,
\r
101 content_type_str ? content_type_str : "",
\r
107 ~ShibTargetFCGI() { }
\r
109 virtual void log(ShibLogLevel level, const string& msg) {
\r
110 ShibTarget::log(level,msg);
\r
112 if (level == LogLevelError)
\r
113 cerr << "shib: " << msg;
\r
116 virtual string getCookies(void) const {
\r
117 char * cookie = FCGX_GetParam("HTTP_COOKIE", m_req->envp);
\r
118 return cookie ? cookie : "";
\r
121 virtual void setCookie(const string& name, const string& value) {
\r
122 m_cookie += "Set-Cookie: " + name + "=" + value + "\r\n";
\r
125 virtual string getArgs(void) {
\r
126 char * args = FCGX_GetParam("QUERY_STRING", m_req->envp);
\r
127 return args ? args : "";
\r
130 virtual string getPostData(void) {
\r
131 return m_body ? m_body : "";
\r
134 virtual void clearHeader(const string &name) {
\r
135 throw runtime_error("clearHeader not implemented by FastCGI responder.");
\r
138 virtual void setHeader(const string &name, const string &value) {
\r
139 throw runtime_error("setHeader not implemented by FastCGI responder.");
\r
142 virtual string getHeader(const string &name) {
\r
143 throw runtime_error("getHeader not implemented by FastCGI responder.");
\r
146 virtual void setRemoteUser(const string &user) {
\r
147 throw runtime_error("setRemoteUser not implemented by FastCGI responder.");
\r
150 virtual string getRemoteUser(void) {
\r
151 throw runtime_error("getRemoteUser not implemented by FastCGI responder.");
\r
154 virtual void* sendPage(
\r
157 const string& content_type="text/html",
\r
158 const saml::Iterator<header_t>& headers=EMPTY(header_t)) {
\r
160 checkString(content_type, "Detected control character in a response header.");
\r
161 string hdr = string ("Connection: close\r\nContent-type: ") + content_type + "\r\n" + m_cookie;
\r
162 while (headers.hasNext()) {
\r
163 const header_t& h=headers.next();
\r
164 checkString(h.first, "Detected control character in a response header.");
\r
165 checkString(h.second, "Detected control character in a response header.");
\r
166 hdr += h.first + ": " + h.second + "\r\n";
\r
169 const char* codestr="Status: 200 OK";
\r
171 case 500: codestr="Status: 500 Server Error"; break;
\r
172 case 403: codestr="Status: 403 Forbidden"; break;
\r
173 case 404: codestr="Status: 404 Not Found"; break;
\r
176 cout << codestr << "\r\n" << hdr << m_cookie << "\r\n" << msg;
\r
177 return (void*)SHIB_RETURN_DONE;
\r
180 virtual void* sendRedirect(const string& url) {
\r
181 checkString(url, "Detected control character in an attempted redirect.");
\r
182 if (g_allowedSchemes.find(url.substr(0, url.find(':'))) == g_allowedSchemes.end())
\r
183 throw runtime_error("Invalid scheme in attempted redirect.");
\r
184 cout << "Status: 302 Please Wait" << "\r\n" << "Location: " << url << "\r\n" << m_cookie << "\r\n"
\r
185 << "<HTML><BODY>Redirecting...</BODY></HTML>";
\r
186 return (void*)SHIB_RETURN_DONE;
\r
189 virtual void* returnDecline(void) {
\r
190 return (void*)SHIB_RETURN_KO;
\r
193 virtual void* returnOK(void) {
\r
194 return (void*)SHIB_RETURN_OK;
\r
198 // Maximum number of bytes allowed to be read from stdin
\r
199 static const unsigned long STDIN_MAX = 1000000;
\r
201 static long gstdin(FCGX_Request* request, char** content)
\r
203 char* clenstr = FCGX_GetParam("CONTENT_LENGTH", request->envp);
\r
204 unsigned long clen = STDIN_MAX;
\r
207 clen = strtol(clenstr, &clenstr, 10);
\r
209 cerr << "can't parse CONTENT_LENGTH (" << FCGX_GetParam("CONTENT_LENGTH", request->envp) << ")" << endl;
\r
213 // *always* put a cap on the amount of data that will be read
\r
214 if (clen > STDIN_MAX)
\r
217 *content = new char[clen];
\r
219 cin.read(*content, clen);
\r
220 clen = cin.gcount();
\r
223 // *never* read stdin when CONTENT_LENGTH is missing or unparsable
\r
228 // Chew up any remaining stdin - this shouldn't be necessary
\r
229 // but is because mod_fastcgi doesn't handle it correctly.
\r
231 // ignore() doesn't set the eof bit in some versions of glibc++
\r
232 // so use gcount() instead of eof()...
\r
233 do cin.ignore(1024); while (cin.gcount() == 1024);
\r
238 static void print_ok() {
\r
239 cout << "Status: 200 OK" << "\r\n\r\n";
\r
242 static void print_error(const char* msg) {
\r
243 cout << "Status: 500 Server Error" << "\r\n\r\n" << msg;
\r
248 char* shib_config = getenv("SHIB_CONFIG");
\r
249 char* shib_schema = getenv("SHIB_SCHEMA");
\r
250 if ((shib_config == NULL) || (shib_schema == NULL)) {
\r
251 cerr << "SHIB_CONFIG or SHIB_SCHEMA not set." << endl;
\r
254 cerr << "SHIB_CONFIG = " << shib_config << endl
\r
255 << "SHIB_SCHEMA = " << shib_schema << endl;
\r
257 string g_ServerScheme;
\r
258 string g_ServerName;
\r
259 int g_ServerPort=0;
\r
260 ShibTargetConfig* g_Config;
\r
263 g_Config = &ShibTargetConfig::getConfig();
\r
264 g_Config->setFeatures(
\r
265 ShibTargetConfig::Listener |
\r
266 ShibTargetConfig::Metadata |
\r
267 ShibTargetConfig::RequestMapper |
\r
268 ShibTargetConfig::LocalExtensions |
\r
269 ShibTargetConfig::Logging
\r
271 if (!g_Config->init(shib_schema)) {
\r
272 cerr << "failed to initialize Shibboleth libraries" << endl;
\r
276 if (!g_Config->load(shib_config)) {
\r
277 cerr << "failed to load Shibboleth configuration" << endl;
\r
281 IConfig* conf=g_Config->getINI();
\r
282 Locker locker(conf);
\r
283 const IPropertySet* props=conf->getPropertySet("Local");
\r
285 pair<bool,const char*> str=props->getString("allowedSchemes");
\r
287 string schemes=str.second;
\r
289 for (unsigned int i=0; i < schemes.length(); i++) {
\r
290 if (schemes.at(i)==' ') {
\r
291 g_allowedSchemes.insert(schemes.substr(j, i-j));
\r
295 g_allowedSchemes.insert(schemes.substr(j, schemes.length()-j));
\r
298 if (g_allowedSchemes.empty()) {
\r
299 g_allowedSchemes.insert("https");
\r
300 g_allowedSchemes.insert("http");
\r
304 catch (exception& e) {
\r
305 cerr << "exception while initializing Shibboleth configuration:" << e.what() << endl;
\r
309 // Load "authoritative" URL fields.
\r
310 char* var = getenv("SHIBSP_SERVER_NAME");
\r
312 g_ServerName = var;
\r
313 var = getenv("SHIBSP_SERVER_SCHEME");
\r
315 g_ServerScheme = var;
\r
316 var = getenv("SHIBSP_SERVER_PORT");
\r
318 g_ServerPort = atoi(var);
\r
320 streambuf* cin_streambuf = cin.rdbuf();
\r
321 streambuf* cout_streambuf = cout.rdbuf();
\r
322 streambuf* cerr_streambuf = cerr.rdbuf();
\r
324 FCGX_Request request;
\r
327 FCGX_InitRequest(&request, 0, 0);
\r
329 cout << "Shibboleth initialization complete. Starting request loop." << endl;
\r
330 while (FCGX_Accept_r(&request) == 0) {
\r
331 // Note that the default bufsize (0) will cause the use of iostream
\r
332 // methods that require positioning (such as peek(), seek(),
\r
333 // unget() and putback()) to fail (in favour of more efficient IO).
\r
334 fcgi_streambuf cin_fcgi_streambuf(request.in);
\r
335 fcgi_streambuf cout_fcgi_streambuf(request.out);
\r
336 fcgi_streambuf cerr_fcgi_streambuf(request.err);
\r
338 cin.rdbuf(&cin_fcgi_streambuf);
\r
339 cout.rdbuf(&cout_fcgi_streambuf);
\r
340 cerr.rdbuf(&cerr_fcgi_streambuf);
\r
342 // Although FastCGI supports writing before reading,
\r
343 // many http clients (browsers) don't support it (so
\r
344 // the connection deadlocks until a timeout expires!).
\r
346 gstdin(&request, &content);
\r
349 saml::NDC ndc("FastCGI shibresponder");
\r
350 ShibTargetFCGI stf(&request, content, g_ServerScheme.c_str(), g_ServerName.c_str(), g_ServerPort);
\r
352 pair<bool,void*> res = stf.doHandler();
\r
355 cerr << "shib: doHandler handled the request" << endl;
\r
357 switch((long)res.second) {
\r
358 case SHIB_RETURN_OK:
\r
362 case SHIB_RETURN_KO:
\r
363 cerr << "shib: doHandler failed to handle the request" << endl;
\r
364 print_error("<html><body>FastCGI Shibboleth responder should only be used for Shibboleth protocol requests.</body></html>");
\r
367 case SHIB_RETURN_DONE:
\r
368 // response already handled
\r
372 cerr << "shib: doHandler returned an unexpected result: " << (long)res.second << endl;
\r
373 print_error("<html><body>FastCGI Shibboleth responder returned an unexpected result.</body></html>");
\r
378 cerr << "shib: doHandler failed to handle request." << endl;
\r
379 print_error("<html><body>FastCGI Shibboleth responder failed to process request.</body></html>");
\r
383 catch (exception& e) {
\r
384 cerr << "shib: FastCGI responder caught an exception: " << e.what() << endl;
\r
385 print_error("<html><body>FastCGI Shibboleth responder caught an exception, check log for details.</body></html>");
\r
390 // If the output streambufs had non-zero bufsizes and
\r
391 // were constructed outside of the accept loop (i.e.
\r
392 // their destructor won't be called here), they would
\r
393 // have to be flushed here.
\r
396 cout << "Request loop ended." << endl;
\r
398 cin.rdbuf(cin_streambuf);
\r
399 cout.rdbuf(cout_streambuf);
\r
400 cerr.rdbuf(cerr_streambuf);
\r
403 g_Config->shutdown();
\r