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 <saml/saml.h>
\r
24 #include <shib-target/shib-target.h>
\r
27 #ifdef HAVE_UNISTD_H
\r
28 # include <unistd.h>
\r
29 # include <sys/mman.h>
\r
33 using namespace shibtarget;
\r
34 using namespace std;
\r
42 class ShibTargetFCGIAuth : public ShibTarget
\r
44 FCGX_Request* m_req;
\r
47 map<string,string> m_headers;
\r
49 ShibTargetFCGIAuth(FCGX_Request* req, const char* scheme=NULL, const char* hostname=NULL, int port=0) : m_req(req) {
\r
50 const char* server_name_str = hostname;
\r
51 if (!server_name_str || !*server_name_str)
\r
52 server_name_str = FCGX_GetParam("SERVER_NAME", req->envp);
\r
54 int server_port = port;
\r
56 char* server_port_str = FCGX_GetParam("SERVER_PORT", req->envp);
\r
57 server_port = strtol(server_port_str, &server_port_str, 10);
\r
58 if (*server_port_str) {
\r
59 cerr << "can't parse SERVER_PORT (" << FCGX_GetParam("SERVER_PORT", req->envp) << ")" << endl;
\r
60 throw exception("Unable to determine server port.");
\r
64 const char* server_scheme_str = scheme;
\r
65 if (!server_scheme_str || !*server_scheme_str)
\r
66 server_scheme_str = (server_port == 443 || server_port == 8443) ? "https" : "http";
\r
68 const char* request_uri_str = FCGX_GetParam("REQUEST_URI", req->envp);
\r
69 const char* content_type_str = FCGX_GetParam("CONTENT_TYPE", req->envp);
\r
70 const char* remote_addr_str = FCGX_GetParam("REMOTE_ADDR", req->envp);
\r
71 const char* request_method_str = FCGX_GetParam("REQUEST_METHOD", req->envp);
\r
73 init(server_scheme_str,
\r
77 content_type_str ? content_type_str : "",
\r
83 ~ShibTargetFCGIAuth() { }
\r
85 virtual void log(ShibLogLevel level, const string& msg) {
\r
86 ShibTarget::log(level,msg);
\r
87 if (level == LogLevelError)
\r
88 cerr << "shib: " << msg;
\r
91 virtual string getCookies(void) const {
\r
92 char* cookie = FCGX_GetParam("HTTP_COOKIE", m_req->envp);
\r
93 return cookie ? cookie : "";
\r
96 virtual void setCookie(const string &name, const string &value) {
\r
97 m_cookie += "Set-Cookie: " + name + "=" + value + "\r\n";
\r
100 virtual string getArgs(void) {
\r
101 char* args = FCGX_GetParam("QUERY_STRING", m_req->envp);
\r
102 return args ? args : "";
\r
105 virtual string getPostData(void) {
\r
106 throw exception("getPostData not implemented by FastCGI authorizer.");
\r
109 virtual void clearHeader(const string& name) {
\r
110 // no need, since request headers turn into actual environment variables
\r
113 virtual void setHeader(const string& name, const string &value) {
\r
114 m_headers[name] = value;
\r
117 virtual string getHeader(const string& name) {
\r
118 if (m_headers.find(name) != m_headers.end())
\r
119 return m_headers[name];
\r
124 virtual void setRemoteUser(const string& user) {
\r
125 m_headers["REMOTE_USER"] = user;
\r
128 virtual string getRemoteUser(void) {
\r
129 if (m_headers.find("REMOTE_USER") != m_headers.end())
\r
130 return m_headers["REMOTE_USER"];
\r
132 char* remote_user = FCGX_GetParam("REMOTE_USER", m_req->envp);
\r
134 return remote_user;
\r
139 virtual void* sendPage(
\r
142 const string& content_type="text/html",
\r
143 const saml::Iterator<header_t>& headers=EMPTY(header_t)) {
\r
145 string hdr = m_cookie + "Connection: close\r\nContent-type: " + content_type + "\r\n";
\r
146 while (headers.hasNext()) {
\r
147 const header_t& h=headers.next();
\r
148 hdr += h.first + ": " + h.second + "\r\n";
\r
151 // We can't return 200 OK here or else the filter is bypassed
\r
152 // so custom Shib errors will get turned into a generic page.
\r
153 const char* codestr="Status: 500 Server Error";
\r
155 case 403: codestr="Status: 403 Forbidden"; break;
\r
156 case 404: codestr="Status: 404 Not Found"; break;
\r
159 cout << codestr << "\r\n" << hdr << "\r\n" << msg;
\r
160 return (void*)SHIB_RETURN_DONE;
\r
163 virtual void* sendRedirect(const string& url) {
\r
164 cout << "Status: 302 Please Wait" << "\r\n"
\r
165 << "Location: " << url << "\r\n"
\r
166 << m_cookie << "\r\n"
\r
167 << "<HTML><BODY>Redirecting...</BODY></HTML>";
\r
168 return (void*)SHIB_RETURN_DONE;
\r
171 virtual void* returnDecline(void) {
\r
172 return (void*)SHIB_RETURN_KO;
\r
175 virtual void* returnOK(void) {
\r
176 return (void*)SHIB_RETURN_OK;
\r
180 static void print_ok(const map<string,string>& headers)
\r
182 cout << "Status: 200 OK" << "\r\n";
\r
183 for (map<string,string>::const_iterator iter = headers.begin(); iter != headers.end(); iter++) {
\r
184 cout << "Variable-" << iter->first << ": " << iter->second << "\r\n";
\r
189 static void print_error(const char* msg)
\r
191 cout << "Status: 500 Server Error" << "\r\n\r\n" << msg;
\r
196 char* shib_config = getenv("SHIB_CONFIG");
\r
197 char* shib_schema = getenv("SHIB_SCHEMA");
\r
198 if ((shib_config == NULL) || (shib_schema == NULL)) {
\r
199 cerr << "SHIB_CONFIG or SHIB_SCHEMA not initialized!" << endl;
\r
202 cerr << "SHIB_CONFIG = " << shib_config << endl
\r
203 << "SHIB_SCHEMA = " << shib_schema << endl;
\r
205 string g_ServerScheme;
\r
206 string g_ServerName;
\r
207 int g_ServerPort = 0;
\r
208 ShibTargetConfig* g_Config;
\r
211 g_Config = &ShibTargetConfig::getConfig();
\r
212 g_Config->setFeatures(
\r
213 ShibTargetConfig::Listener |
\r
214 ShibTargetConfig::Metadata |
\r
215 ShibTargetConfig::AAP |
\r
216 ShibTargetConfig::RequestMapper |
\r
217 ShibTargetConfig::LocalExtensions |
\r
218 ShibTargetConfig::Logging
\r
220 if (!g_Config->init(shib_schema)) {
\r
221 cerr << "failed to initialize Shibboleth libraries" << endl;
\r
225 if (!g_Config->load(shib_config)) {
\r
226 cerr << "failed to load Shibboleth configuration" << endl;
\r
230 catch (exception& e) {
\r
231 cerr << "exception while initializing Shibboleth configuration: " << e.what() << endl;
\r
235 // Load "authoritative" URL fields.
\r
236 char* var = getenv("SHIBSP_SERVER_NAME");
\r
238 g_ServerName = var;
\r
239 var = getenv("SHIBSP_SERVER_SCHEME");
\r
241 g_ServerScheme = var;
\r
242 var = getenv("SHIBSP_SERVER_PORT");
\r
244 g_ServerPort = atoi(var);
\r
246 streambuf* cout_streambuf = cout.rdbuf();
\r
247 streambuf* cerr_streambuf = cerr.rdbuf();
\r
249 FCGX_Request request;
\r
252 FCGX_InitRequest(&request, 0, 0);
\r
254 cout << "Shibboleth initialization complete. Starting request loop." << endl;
\r
255 while (FCGX_Accept_r(&request) == 0)
\r
257 // Note that the default bufsize (0) will cause the use of iostream
\r
258 // methods that require positioning (such as peek(), seek(),
\r
259 // unget() and putback()) to fail (in favour of more efficient IO).
\r
260 fcgi_streambuf cout_fcgi_streambuf(request.out);
\r
261 fcgi_streambuf cerr_fcgi_streambuf(request.err);
\r
263 cout.rdbuf(&cout_fcgi_streambuf);
\r
264 cerr.rdbuf(&cerr_fcgi_streambuf);
\r
267 saml::NDC ndc("FastCGI shibauthorizer");
\r
268 ShibTargetFCGIAuth sta(&request, g_ServerScheme.c_str(), g_ServerName.c_str(), g_ServerPort);
\r
270 pair<bool,void*> res = sta.doCheckAuthN();
\r
273 cerr << "shib: doCheckAuthN handled the request" << endl;
\r
275 switch((long)res.second) {
\r
276 case SHIB_RETURN_OK:
\r
277 print_ok(sta.m_headers);
\r
280 case SHIB_RETURN_KO:
\r
281 print_ok(sta.m_headers);
\r
284 case SHIB_RETURN_DONE:
\r
288 cerr << "shib: doCheckAuthN returned an unexpected result: " << (long)res.second << endl;
\r
289 print_error("<html><body>FastCGI Shibboleth authorizer returned an unexpected result.</body></html>");
\r
294 res = sta.doExportAssertions();
\r
297 cerr << "shib: doExportAssertions handled request" << endl;
\r
299 switch((long)res.second) {
\r
300 case SHIB_RETURN_OK:
\r
301 print_ok(sta.m_headers);
\r
304 case SHIB_RETURN_KO:
\r
305 print_ok(sta.m_headers);
\r
308 case SHIB_RETURN_DONE:
\r
312 cerr << "shib: doExportAssertions returned an unexpected result: " << (long)res.second << endl;
\r
313 print_error("<html><body>FastCGI Shibboleth authorizer returned an unexpected result.</body></html>");
\r
318 res = sta.doCheckAuthZ();
\r
321 cerr << "shib: doCheckAuthZ handled request" << endl;
\r
323 switch((long)res.second) {
\r
324 case SHIB_RETURN_OK:
\r
325 print_ok(sta.m_headers);
\r
328 case SHIB_RETURN_KO:
\r
329 print_ok(sta.m_headers);
\r
332 case SHIB_RETURN_DONE:
\r
336 cerr << "shib: doCheckAuthZ returned an unexpected result: " << (long)res.second << endl;
\r
337 print_error("<html><body>FastCGI Shibboleth authorizer returned an unexpected result.</body></html>");
\r
342 print_ok(sta.m_headers);
\r
345 catch (exception& e) {
\r
346 cerr << "shib: FastCGI authorizer caught an exception: " << e.what() << endl;
\r
347 print_error("<html><body>FastCGI Shibboleth authorizer caught an exception, check log for details.</body></html>");
\r
350 // If the output streambufs had non-zero bufsizes and
\r
351 // were constructed outside of the accept loop (i.e.
\r
352 // their destructor won't be called here), they would
\r
353 // have to be flushed here.
\r
355 cout << "Request loop ended." << endl;
\r
357 cout.rdbuf(cout_streambuf);
\r
358 cerr.rdbuf(cerr_streambuf);
\r
361 g_Config->shutdown();
\r