2 * Copyright 2001-2010 Internet2
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 /* shibauthorizer.cpp - Shibboleth FastCGI Authorizer
23 #include "config_win32.h"
25 #define _CRT_NONSTDC_NO_DEPRECATE 1
26 #define _CRT_SECURE_NO_DEPRECATE 1
27 #define _SCL_SECURE_NO_WARNINGS 1
29 #include <shibsp/AbstractSPRequest.h>
30 #include <shibsp/SPConfig.h>
31 #include <shibsp/ServiceProvider.h>
32 #include <xmltooling/unicode.h>
33 #include <xmltooling/XMLToolingConfig.h>
34 #include <xmltooling/util/NDC.h>
35 #include <xmltooling/util/XMLConstants.h>
36 #include <xmltooling/util/XMLHelper.h>
37 #include <xercesc/util/XMLUniDefs.hpp>
43 # include <sys/mman.h>
47 using namespace shibsp;
48 using namespace xmltooling;
49 using namespace xercesc;
52 static const XMLCh path[] = UNICODE_LITERAL_4(p,a,t,h);
53 static const XMLCh validate[] = UNICODE_LITERAL_8(v,a,l,i,d,a,t,e);
61 class ShibTargetFCGIAuth : public AbstractSPRequest
65 string m_scheme,m_hostname;
66 multimap<string,string> m_response_headers;
68 map<string,string> m_request_headers;
70 ShibTargetFCGIAuth(FCGX_Request* req, const char* scheme=nullptr, const char* hostname=nullptr, int port=0)
71 : AbstractSPRequest(SHIBSP_LOGCAT".FastCGI"), m_req(req) {
72 const char* server_name_str = hostname;
73 if (!server_name_str || !*server_name_str)
74 server_name_str = FCGX_GetParam("SERVER_NAME", req->envp);
75 m_hostname = server_name_str;
79 char* server_port_str = FCGX_GetParam("SERVER_PORT", req->envp);
80 m_port = strtol(server_port_str, &server_port_str, 10);
81 if (*server_port_str) {
82 cerr << "can't parse SERVER_PORT (" << FCGX_GetParam("SERVER_PORT", req->envp) << ")" << endl;
83 throw runtime_error("Unable to determine server port.");
87 const char* server_scheme_str = scheme;
88 if (!server_scheme_str || !*server_scheme_str)
89 server_scheme_str = (m_port == 443 || m_port == 8443) ? "https" : "http";
90 m_scheme = server_scheme_str;
92 setRequestURI(FCGX_GetParam("REQUEST_URI", m_req->envp));
95 ~ShibTargetFCGIAuth() { }
97 const char* getScheme() const {
98 return m_scheme.c_str();
100 const char* getHostname() const {
101 return m_hostname.c_str();
103 int getPort() const {
106 const char* getMethod() const {
107 return FCGX_GetParam("REQUEST_METHOD", m_req->envp);
109 string getContentType() const {
110 const char* s = FCGX_GetParam("CONTENT_TYPE", m_req->envp);
113 long getContentLength() const {
114 const char* s = FCGX_GetParam("CONTENT_LENGTH", m_req->envp);
115 return s ? atol(s) : 0;
117 string getRemoteAddr() const {
118 string ret = AbstractSPRequest::getRemoteAddr();
121 const char* s = FCGX_GetParam("REMOTE_ADDR", m_req->envp);
124 void log(SPLogLevel level, const string& msg) const {
125 AbstractSPRequest::log(level,msg);
126 if (level >= SPError)
127 cerr << "shib: " << msg;
129 void clearHeader(const char* rawname, const char* cginame) {
130 // No need, since we use environment variables.
132 void setHeader(const char* name, const char* value) {
134 m_request_headers[name] = value;
136 m_request_headers.erase(name);
138 string getHeader(const char* name) const {
139 // Look in the local map first.
140 map<string,string>::const_iterator i = m_request_headers.find(name);
141 if (i != m_request_headers.end())
143 // Nothing set locally and this isn't a "secure" call, so check the request.
145 for (; *name; ++name) {
149 hdr += toupper(*name);
151 char* s = FCGX_GetParam(hdr.c_str(), m_req->envp);
154 string getSecureHeader(const char* name) const {
155 // Look in the local map only.
156 map<string,string>::const_iterator i = m_request_headers.find(name);
157 if (i != m_request_headers.end())
161 void setRemoteUser(const char* user) {
163 m_request_headers["REMOTE_USER"] = user;
165 m_request_headers.erase("REMOTE_USER");
167 string getRemoteUser() const {
168 map<string,string>::const_iterator i = m_request_headers.find("REMOTE_USER");
169 if (i != m_request_headers.end())
172 char* remote_user = FCGX_GetParam("REMOTE_USER", m_req->envp);
178 void setAuthType(const char* authtype) {
180 m_request_headers["AUTH_TYPE"] = authtype;
182 m_request_headers.erase("AUTH_TYPE");
184 string getAuthType() const {
185 map<string,string>::const_iterator i = m_request_headers.find("AUTH_TYPE");
186 if (i != m_request_headers.end())
189 char* auth_type = FCGX_GetParam("AUTH_TYPE", m_req->envp);
195 void setResponseHeader(const char* name, const char* value) {
196 HTTPResponse::setResponseHeader(name, value);
199 m_response_headers.insert(make_pair(name,value));
201 m_response_headers.erase(name);
203 const char* getQueryString() const {
204 return FCGX_GetParam("QUERY_STRING", m_req->envp);
206 const char* getRequestBody() const {
207 throw runtime_error("getRequestBody not implemented by FastCGI authorizer.");
210 long sendResponse(istream& in, long status) {
211 string hdr = string("Connection: close\r\n");
212 for (multimap<string,string>::const_iterator i=m_response_headers.begin(); i!=m_response_headers.end(); ++i)
213 hdr += i->first + ": " + i->second + "\r\n";
215 // We can't return 200 OK here or else the filter is bypassed
216 // so custom Shib errors will get turned into a generic page.
217 const char* codestr="Status: 500 Server Error";
219 case XMLTOOLING_HTTP_STATUS_UNAUTHORIZED: codestr="Status: 401 Authorization Required"; break;
220 case XMLTOOLING_HTTP_STATUS_FORBIDDEN: codestr="Status: 403 Forbidden"; break;
221 case XMLTOOLING_HTTP_STATUS_NOTFOUND: codestr="Status: 404 Not Found"; break;
223 cout << codestr << "\r\n" << hdr << "\r\n";
227 cout.write(buf, in.gcount());
229 return SHIB_RETURN_DONE;
232 long sendRedirect(const char* url) {
233 HTTPResponse::sendRedirect(url);
234 string hdr=string("Status: 302 Please Wait\r\nLocation: ") + url + "\r\n"
235 "Content-Type: text/html\r\n"
236 "Content-Length: 40\r\n"
237 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
238 "Cache-Control: private,no-store,no-cache\r\n";
239 for (multimap<string,string>::const_iterator i=m_response_headers.begin(); i!=m_response_headers.end(); ++i)
240 hdr += i->first + ": " + i->second + "\r\n";
243 cout << hdr << "<HTML><BODY>Redirecting...</BODY></HTML>";
244 return SHIB_RETURN_DONE;
247 long returnDecline() {
248 return SHIB_RETURN_KO;
252 return SHIB_RETURN_OK;
255 const vector<string>& getClientCertificates() const {
256 static vector<string> g_NoCerts;
261 static void print_ok(const map<string,string>& headers)
263 cout << "Status: 200 OK" << "\r\n";
264 for (map<string,string>::const_iterator iter = headers.begin(); iter != headers.end(); iter++) {
265 cout << "Variable-" << iter->first << ": " << iter->second << "\r\n";
270 static void print_error(const char* msg)
272 cout << "Status: 500 Server Error" << "\r\n\r\n" << msg;
277 SPConfig* g_Config=&SPConfig::getConfig();
278 g_Config->setFeatures(
281 SPConfig::RequestMapping |
282 SPConfig::InProcess |
286 if (!g_Config->init()) {
287 cerr << "failed to initialize Shibboleth libraries" << endl;
292 if (!g_Config->instantiate(nullptr, true))
293 throw runtime_error("unknown error");
295 catch (exception& ex) {
297 cerr << "exception while initializing Shibboleth configuration: " << ex.what() << endl;
301 string g_ServerScheme;
305 // Load "authoritative" URL fields.
306 char* var = getenv("SHIBSP_SERVER_NAME");
309 var = getenv("SHIBSP_SERVER_SCHEME");
311 g_ServerScheme = var;
312 var = getenv("SHIBSP_SERVER_PORT");
314 g_ServerPort = atoi(var);
316 streambuf* cout_streambuf = cout.rdbuf();
317 streambuf* cerr_streambuf = cerr.rdbuf();
319 FCGX_Request request;
322 FCGX_InitRequest(&request, 0, 0);
324 cout << "Shibboleth initialization complete. Starting request loop." << endl;
325 while (FCGX_Accept_r(&request) == 0)
327 // Note that the default bufsize (0) will cause the use of iostream
328 // methods that require positioning (such as peek(), seek(),
329 // unget() and putback()) to fail (in favour of more efficient IO).
330 fcgi_streambuf cout_fcgi_streambuf(request.out);
331 fcgi_streambuf cerr_fcgi_streambuf(request.err);
333 cout.rdbuf(&cout_fcgi_streambuf);
334 cerr.rdbuf(&cerr_fcgi_streambuf);
337 xmltooling::NDC ndc("FastCGI shibauthorizer");
338 ShibTargetFCGIAuth sta(&request, g_ServerScheme.c_str(), g_ServerName.c_str(), g_ServerPort);
340 pair<bool,long> res = sta.getServiceProvider().doAuthentication(sta);
342 sta.log(SPRequest::SPDebug, "shib: doAuthentication handled the request");
345 print_ok(sta.m_request_headers);
349 print_ok(sta.m_request_headers);
352 case SHIB_RETURN_DONE:
356 cerr << "shib: doAuthentication returned an unexpected result: " << res.second << endl;
357 print_error("<html><body>FastCGI Shibboleth authorizer returned an unexpected result.</body></html>");
362 res = sta.getServiceProvider().doExport(sta);
364 sta.log(SPRequest::SPDebug, "shib: doExport handled request");
367 print_ok(sta.m_request_headers);
371 print_ok(sta.m_request_headers);
374 case SHIB_RETURN_DONE:
378 cerr << "shib: doExport returned an unexpected result: " << res.second << endl;
379 print_error("<html><body>FastCGI Shibboleth authorizer returned an unexpected result.</body></html>");
384 res = sta.getServiceProvider().doAuthorization(sta);
386 sta.log(SPRequest::SPDebug, "shib: doAuthorization handled request");
389 print_ok(sta.m_request_headers);
393 print_ok(sta.m_request_headers);
396 case SHIB_RETURN_DONE:
400 cerr << "shib: doAuthorization returned an unexpected result: " << res.second << endl;
401 print_error("<html><body>FastCGI Shibboleth authorizer returned an unexpected result.</body></html>");
406 print_ok(sta.m_request_headers);
409 catch (exception& e) {
410 cerr << "shib: FastCGI authorizer caught an exception: " << e.what() << endl;
411 print_error("<html><body>FastCGI Shibboleth authorizer caught an exception, check log for details.</body></html>");
414 // If the output streambufs had non-zero bufsizes and
415 // were constructed outside of the accept loop (i.e.
416 // their destructor won't be called here), they would
417 // have to be flushed here.
419 cout << "Request loop ended." << endl;
421 cout.rdbuf(cout_streambuf);
422 cerr.rdbuf(cerr_streambuf);