2 * Copyright 2001-2007 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 /* shibauthorizer.cpp - Shibboleth FastCGI Authorizer
\r
23 #include "config_win32.h"
\r
25 #define _CRT_NONSTDC_NO_DEPRECATE 1
\r
26 #define _CRT_SECURE_NO_DEPRECATE 1
\r
27 #define _SCL_SECURE_NO_WARNINGS 1
\r
29 #include <shibsp/AbstractSPRequest.h>
\r
30 #include <shibsp/SPConfig.h>
\r
31 #include <shibsp/ServiceProvider.h>
\r
32 #include <xmltooling/unicode.h>
\r
33 #include <xmltooling/XMLToolingConfig.h>
\r
34 #include <xmltooling/util/NDC.h>
\r
35 #include <xmltooling/util/XMLConstants.h>
\r
36 #include <xmltooling/util/XMLHelper.h>
\r
37 #include <xercesc/util/XMLUniDefs.hpp>
\r
39 #include <stdexcept>
\r
41 #ifdef HAVE_UNISTD_H
\r
42 # include <unistd.h>
\r
43 # include <sys/mman.h>
\r
47 using namespace shibsp;
\r
48 using namespace xmltooling;
\r
49 using namespace xercesc;
\r
50 using namespace std;
\r
52 static const XMLCh path[] = UNICODE_LITERAL_4(p,a,t,h);
\r
53 static const XMLCh validate[] = UNICODE_LITERAL_8(v,a,l,i,d,a,t,e);
\r
61 class ShibTargetFCGIAuth : public AbstractSPRequest
\r
63 FCGX_Request* m_req;
\r
65 string m_scheme,m_hostname;
\r
66 multimap<string,string> m_response_headers;
\r
68 map<string,string> m_request_headers;
\r
70 ShibTargetFCGIAuth(FCGX_Request* req, const char* scheme=NULL, const char* hostname=NULL, int port=0)
\r
71 : AbstractSPRequest(SHIBSP_LOGCAT".FastCGI"), m_req(req) {
\r
72 const char* server_name_str = hostname;
\r
73 if (!server_name_str || !*server_name_str)
\r
74 server_name_str = FCGX_GetParam("SERVER_NAME", req->envp);
\r
75 m_hostname = server_name_str;
\r
79 char* server_port_str = FCGX_GetParam("SERVER_PORT", req->envp);
\r
80 m_port = strtol(server_port_str, &server_port_str, 10);
\r
81 if (*server_port_str) {
\r
82 cerr << "can't parse SERVER_PORT (" << FCGX_GetParam("SERVER_PORT", req->envp) << ")" << endl;
\r
83 throw runtime_error("Unable to determine server port.");
\r
87 const char* server_scheme_str = scheme;
\r
88 if (!server_scheme_str || !*server_scheme_str)
\r
89 server_scheme_str = (m_port == 443 || m_port == 8443) ? "https" : "http";
\r
90 m_scheme = server_scheme_str;
\r
92 setRequestURI(FCGX_GetParam("REQUEST_URI", m_req->envp));
\r
95 ~ShibTargetFCGIAuth() { }
\r
97 const char* getScheme() const {
\r
98 return m_scheme.c_str();
\r
100 const char* getHostname() const {
\r
101 return m_hostname.c_str();
\r
103 int getPort() const {
\r
106 const char* getMethod() const {
\r
107 return FCGX_GetParam("REQUEST_METHOD", m_req->envp);
\r
109 string getContentType() const {
\r
110 const char* s = FCGX_GetParam("CONTENT_TYPE", m_req->envp);
\r
113 long getContentLength() const {
\r
114 const char* s = FCGX_GetParam("CONTENT_LENGTH", m_req->envp);
\r
115 return s ? atol(s) : 0;
\r
117 string getRemoteAddr() const {
\r
118 const char* s = FCGX_GetParam("REMOTE_ADDR", m_req->envp);
\r
121 void log(SPLogLevel level, const string& msg) const {
\r
122 AbstractSPRequest::log(level,msg);
\r
123 if (level >= SPError)
\r
124 cerr << "shib: " << msg;
\r
126 void clearHeader(const char* rawname, const char* cginame) {
\r
127 // no need, since request headers turn into actual environment variables
\r
129 void setHeader(const char* name, const char* value) {
\r
131 m_request_headers[name] = value;
\r
133 m_request_headers.erase(name);
\r
135 virtual string getHeader(const char* name) const {
\r
136 map<string,string>::const_iterator i = m_request_headers.find(name);
\r
137 if (i != m_request_headers.end())
\r
142 void setRemoteUser(const char* user) {
\r
144 m_request_headers["REMOTE_USER"] = user;
\r
146 m_request_headers.erase("REMOTE_USER");
\r
148 string getRemoteUser() const {
\r
149 map<string,string>::const_iterator i = m_request_headers.find("REMOTE_USER");
\r
150 if (i != m_request_headers.end())
\r
153 char* remote_user = FCGX_GetParam("REMOTE_USER", m_req->envp);
\r
155 return remote_user;
\r
159 void setResponseHeader(const char* name, const char* value) {
\r
162 m_response_headers.insert(make_pair(name,value));
\r
164 m_response_headers.erase(name);
\r
166 const char* getQueryString() const {
\r
167 return FCGX_GetParam("QUERY_STRING", m_req->envp);
\r
169 const char* getRequestBody() const {
\r
170 throw runtime_error("getRequestBody not implemented by FastCGI authorizer.");
\r
173 long sendResponse(istream& in, long status) {
\r
174 string hdr = string("Connection: close\r\n");
\r
175 for (multimap<string,string>::const_iterator i=m_response_headers.begin(); i!=m_response_headers.end(); ++i)
\r
176 hdr += i->first + ": " + i->second + "\r\n";
\r
178 // We can't return 200 OK here or else the filter is bypassed
\r
179 // so custom Shib errors will get turned into a generic page.
\r
180 const char* codestr="Status: 500 Server Error";
\r
182 case XMLTOOLING_HTTP_STATUS_UNAUTHORIZED: codestr="Status: 401 Authorization Required"; break;
\r
183 case XMLTOOLING_HTTP_STATUS_FORBIDDEN: codestr="Status: 403 Forbidden"; break;
\r
184 case XMLTOOLING_HTTP_STATUS_NOTFOUND: codestr="Status: 404 Not Found"; break;
\r
186 cout << codestr << "\r\n" << hdr << "\r\n";
\r
190 cout.write(buf, in.gcount());
\r
192 return SHIB_RETURN_DONE;
\r
195 long sendRedirect(const char* url) {
\r
196 string hdr=string("Status: 302 Please Wait\r\nLocation: ") + url + "\r\n"
\r
197 "Content-Type: text/html\r\n"
\r
198 "Content-Length: 40\r\n"
\r
199 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
\r
200 "Cache-Control: private,no-store,no-cache\r\n";
\r
201 for (multimap<string,string>::const_iterator i=m_response_headers.begin(); i!=m_response_headers.end(); ++i)
\r
202 hdr += i->first + ": " + i->second + "\r\n";
\r
205 cout << hdr << "<HTML><BODY>Redirecting...</BODY></HTML>";
\r
206 return SHIB_RETURN_DONE;
\r
209 long returnDecline() {
\r
210 return SHIB_RETURN_KO;
\r
214 return SHIB_RETURN_OK;
\r
217 const vector<string>& getClientCertificates() const {
\r
218 static vector<string> g_NoCerts;
\r
223 static void print_ok(const map<string,string>& headers)
\r
225 cout << "Status: 200 OK" << "\r\n";
\r
226 for (map<string,string>::const_iterator iter = headers.begin(); iter != headers.end(); iter++) {
\r
227 cout << "Variable-" << iter->first << ": " << iter->second << "\r\n";
\r
232 static void print_error(const char* msg)
\r
234 cout << "Status: 500 Server Error" << "\r\n\r\n" << msg;
\r
239 const char* schemadir=getenv("SHIBSP_SCHEMAS");
\r
241 schemadir=SHIBSP_SCHEMAS;
\r
242 const char* config=getenv("SHIBSP_CONFIG");
\r
244 config=SHIBSP_CONFIG;
\r
246 cerr << "SHIBSP_CONFIG = " << config << endl
\r
247 << "SHIBSP_SCHEMAS = " << schemadir << endl;
\r
249 SPConfig* g_Config=&SPConfig::getConfig();
\r
250 g_Config->setFeatures(
\r
251 SPConfig::Listener |
\r
252 SPConfig::Caching |
\r
253 SPConfig::RequestMapping |
\r
254 SPConfig::InProcess |
\r
255 SPConfig::Logging |
\r
258 if (!g_Config->init(schemadir)) {
\r
259 cerr << "failed to initialize Shibboleth libraries" << endl;
\r
264 DOMDocument* dummydoc=XMLToolingConfig::getConfig().getParser().newDocument();
\r
265 XercesJanitor<DOMDocument> docjanitor(dummydoc);
\r
266 DOMElement* dummy = dummydoc->createElementNS(NULL,path);
\r
267 auto_ptr_XMLCh src(config);
\r
268 dummy->setAttributeNS(NULL,path,src.get());
\r
269 dummy->setAttributeNS(NULL,validate,xmlconstants::XML_ONE);
\r
271 g_Config->setServiceProvider(g_Config->ServiceProviderManager.newPlugin(XML_SERVICE_PROVIDER,dummy));
\r
272 g_Config->getServiceProvider()->init();
\r
274 catch (exception& ex) {
\r
276 cerr << "exception while initializing Shibboleth configuration: " << ex.what() << endl;
\r
280 string g_ServerScheme;
\r
281 string g_ServerName;
\r
282 int g_ServerPort=0;
\r
284 // Load "authoritative" URL fields.
\r
285 char* var = getenv("SHIBSP_SERVER_NAME");
\r
287 g_ServerName = var;
\r
288 var = getenv("SHIBSP_SERVER_SCHEME");
\r
290 g_ServerScheme = var;
\r
291 var = getenv("SHIBSP_SERVER_PORT");
\r
293 g_ServerPort = atoi(var);
\r
295 streambuf* cout_streambuf = cout.rdbuf();
\r
296 streambuf* cerr_streambuf = cerr.rdbuf();
\r
298 FCGX_Request request;
\r
301 FCGX_InitRequest(&request, 0, 0);
\r
303 cout << "Shibboleth initialization complete. Starting request loop." << endl;
\r
304 while (FCGX_Accept_r(&request) == 0)
\r
306 // Note that the default bufsize (0) will cause the use of iostream
\r
307 // methods that require positioning (such as peek(), seek(),
\r
308 // unget() and putback()) to fail (in favour of more efficient IO).
\r
309 fcgi_streambuf cout_fcgi_streambuf(request.out);
\r
310 fcgi_streambuf cerr_fcgi_streambuf(request.err);
\r
312 cout.rdbuf(&cout_fcgi_streambuf);
\r
313 cerr.rdbuf(&cerr_fcgi_streambuf);
\r
316 xmltooling::NDC ndc("FastCGI shibauthorizer");
\r
317 ShibTargetFCGIAuth sta(&request, g_ServerScheme.c_str(), g_ServerName.c_str(), g_ServerPort);
\r
319 pair<bool,long> res = sta.getServiceProvider().doAuthentication(sta);
\r
322 cerr << "shib: doAuthentication handled the request" << endl;
\r
324 switch(res.second) {
\r
325 case SHIB_RETURN_OK:
\r
326 print_ok(sta.m_request_headers);
\r
329 case SHIB_RETURN_KO:
\r
330 print_ok(sta.m_request_headers);
\r
333 case SHIB_RETURN_DONE:
\r
337 cerr << "shib: doAuthentication returned an unexpected result: " << res.second << endl;
\r
338 print_error("<html><body>FastCGI Shibboleth authorizer returned an unexpected result.</body></html>");
\r
343 res = sta.getServiceProvider().doExport(sta);
\r
346 cerr << "shib: doExport handled request" << endl;
\r
348 switch(res.second) {
\r
349 case SHIB_RETURN_OK:
\r
350 print_ok(sta.m_request_headers);
\r
353 case SHIB_RETURN_KO:
\r
354 print_ok(sta.m_request_headers);
\r
357 case SHIB_RETURN_DONE:
\r
361 cerr << "shib: doExport returned an unexpected result: " << res.second << endl;
\r
362 print_error("<html><body>FastCGI Shibboleth authorizer returned an unexpected result.</body></html>");
\r
367 res = sta.getServiceProvider().doAuthorization(sta);
\r
370 cerr << "shib: doAuthorization handled request" << endl;
\r
372 switch(res.second) {
\r
373 case SHIB_RETURN_OK:
\r
374 print_ok(sta.m_request_headers);
\r
377 case SHIB_RETURN_KO:
\r
378 print_ok(sta.m_request_headers);
\r
381 case SHIB_RETURN_DONE:
\r
385 cerr << "shib: doAuthorization returned an unexpected result: " << res.second << endl;
\r
386 print_error("<html><body>FastCGI Shibboleth authorizer returned an unexpected result.</body></html>");
\r
391 print_ok(sta.m_request_headers);
\r
394 catch (exception& e) {
\r
395 cerr << "shib: FastCGI authorizer caught an exception: " << e.what() << endl;
\r
396 print_error("<html><body>FastCGI Shibboleth authorizer caught an exception, check log for details.</body></html>");
\r
399 // If the output streambufs had non-zero bufsizes and
\r
400 // were constructed outside of the accept loop (i.e.
\r
401 // their destructor won't be called here), they would
\r
402 // have to be flushed here.
\r
404 cout << "Request loop ended." << endl;
\r
406 cout.rdbuf(cout_streambuf);
\r
407 cerr.rdbuf(cerr_streambuf);
\r