1 /**
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.
6 *
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
10 * License at
11 *
12 * http://www.apache.org/licenses/LICENSE-2.0
13 *
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.
19 */
20
21 /* shibauthorizer.cpp - Shibboleth FastCGI Authorizer
22
23 Andre Cruz
24 */
25
26 #define SHIBSP_LITE
27 #include "config_win32.h"
28
29 #define _CRT_NONSTDC_NO_DEPRECATE 1
30 #define _CRT_SECURE_NO_DEPRECATE 1
31 #define _SCL_SECURE_NO_WARNINGS 1
32
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>
42
43 #include <stdexcept>
44 #include <stdlib.h>
45 #ifdef HAVE_UNISTD_H
46 # include <unistd.h>
47 # include <sys/mman.h>
48 #endif
49 #include <fcgio.h>
50
51 using namespace shibsp;
52 using namespace xmltooling;
53 using namespace xercesc;
54 using namespace std;
55
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);
58
59 typedef enum {
60 SHIB_RETURN_OK,
61 SHIB_RETURN_KO,
62 SHIB_RETURN_DONE
63 } shib_return_t;
64
65 class ShibTargetFCGIAuth : public AbstractSPRequest
66 {
67 FCGX_Request* m_req;
68 int m_port;
69 string m_scheme,m_hostname;
70 multimap<string,string> m_response_headers;
71 public:
72 map<string,string> m_request_headers;
73
ShibTargetFCGIAuth(FCGX_Request * req,const char * scheme=nullptr,const char * hostname=nullptr,int port=0)74 ShibTargetFCGIAuth(FCGX_Request* req, const char* scheme=nullptr, const char* hostname=nullptr, int port=0)
75 : AbstractSPRequest(SHIBSP_LOGCAT ".FastCGI"), m_req(req) {
76 const char* server_name_str = hostname;
77 if (!server_name_str || !*server_name_str)
78 server_name_str = FCGX_GetParam("SERVER_NAME", req->envp);
79 m_hostname = server_name_str;
80
81 m_port = port;
82 if (!m_port) {
83 char* server_port_str = FCGX_GetParam("SERVER_PORT", req->envp);
84 m_port = strtol(server_port_str, &server_port_str, 10);
85 if (*server_port_str) {
86 cerr << "can't parse SERVER_PORT (" << FCGX_GetParam("SERVER_PORT", req->envp) << ")" << endl;
87 throw runtime_error("Unable to determine server port.");
88 }
89 }
90
91 const char* server_scheme_str = scheme;
92 if (!server_scheme_str || !*server_scheme_str)
93 server_scheme_str = (m_port == 443 || m_port == 8443) ? "https" : "http";
94 m_scheme = server_scheme_str;
95
96 setRequestURI(FCGX_GetParam("REQUEST_URI", m_req->envp));
97 }
98
~ShibTargetFCGIAuth()99 ~ShibTargetFCGIAuth() { }
100
getScheme() const101 const char* getScheme() const {
102 return m_scheme.c_str();
103 }
getHostname() const104 const char* getHostname() const {
105 return m_hostname.c_str();
106 }
getPort() const107 int getPort() const {
108 return m_port;
109 }
getMethod() const110 const char* getMethod() const {
111 return FCGX_GetParam("REQUEST_METHOD", m_req->envp);
112 }
getContentType() const113 string getContentType() const {
114 const char* s = FCGX_GetParam("CONTENT_TYPE", m_req->envp);
115 return s ? s : "";
116 }
getContentLength() const117 long getContentLength() const {
118 const char* s = FCGX_GetParam("CONTENT_LENGTH", m_req->envp);
119 return s ? atol(s) : 0;
120 }
getRemoteAddr() const121 string getRemoteAddr() const {
122 string ret = AbstractSPRequest::getRemoteAddr();
123 if (!ret.empty())
124 return ret;
125 const char* s = FCGX_GetParam("REMOTE_ADDR", m_req->envp);
126 return s ? s : "";
127 }
log(SPLogLevel level,const string & msg) const128 void log(SPLogLevel level, const string& msg) const {
129 AbstractSPRequest::log(level,msg);
130 if (level >= SPError)
131 cerr << "shib: " << msg;
132 }
clearHeader(const char * rawname,const char * cginame)133 void clearHeader(const char* rawname, const char* cginame) {
134 // No need, since we use environment variables.
135 }
setHeader(const char * name,const char * value)136 void setHeader(const char* name, const char* value) {
137 if (value)
138 m_request_headers[name] = value;
139 else
140 m_request_headers.erase(name);
141 }
getHeader(const char * name) const142 string getHeader(const char* name) const {
143 // Look in the local map first.
144 map<string,string>::const_iterator i = m_request_headers.find(name);
145 if (i != m_request_headers.end())
146 return i->second;
147 // Nothing set locally and this isn't a "secure" call, so check the request.
148 string hdr("HTTP_");
149 for (; *name; ++name) {
150 if (*name=='-')
151 hdr += '_';
152 else
153 hdr += toupper(*name);
154 }
155 char* s = FCGX_GetParam(hdr.c_str(), m_req->envp);
156 return s ? s : "";
157 }
getSecureHeader(const char * name) const158 string getSecureHeader(const char* name) const {
159 // Look in the local map only.
160 map<string,string>::const_iterator i = m_request_headers.find(name);
161 if (i != m_request_headers.end())
162 return i->second;
163 return "";
164 }
setRemoteUser(const char * user)165 void setRemoteUser(const char* user) {
166 if (user)
167 m_request_headers["REMOTE_USER"] = user;
168 else
169 m_request_headers.erase("REMOTE_USER");
170 }
getRemoteUser() const171 string getRemoteUser() const {
172 map<string,string>::const_iterator i = m_request_headers.find("REMOTE_USER");
173 if (i != m_request_headers.end())
174 return i->second;
175 else {
176 char* remote_user = FCGX_GetParam("REMOTE_USER", m_req->envp);
177 if (remote_user)
178 return remote_user;
179 }
180 return "";
181 }
setAuthType(const char * authtype)182 void setAuthType(const char* authtype) {
183 if (authtype)
184 m_request_headers["AUTH_TYPE"] = authtype;
185 else
186 m_request_headers.erase("AUTH_TYPE");
187 }
getAuthType() const188 string getAuthType() const {
189 map<string,string>::const_iterator i = m_request_headers.find("AUTH_TYPE");
190 if (i != m_request_headers.end())
191 return i->second;
192 else {
193 char* auth_type = FCGX_GetParam("AUTH_TYPE", m_req->envp);
194 if (auth_type)
195 return auth_type;
196 }
197 return "";
198 }
setResponseHeader(const char * name,const char * value,bool replace=false)199 void setResponseHeader(const char* name, const char* value, bool replace=false) {
200 HTTPResponse::setResponseHeader(name, value, replace);
201 if (name && *name) {
202 // Set for later.
203 if (replace || !value)
204 m_response_headers.erase(name);
205 if (value && *value)
206 m_response_headers.insert(make_pair(name,value));
207 }
208 }
getQueryString() const209 const char* getQueryString() const {
210 return FCGX_GetParam("QUERY_STRING", m_req->envp);
211 }
getRequestBody() const212 const char* getRequestBody() const {
213 throw runtime_error("getRequestBody not implemented by FastCGI authorizer.");
214 }
215
sendResponse(istream & in,long status)216 long sendResponse(istream& in, long status) {
217 string hdr = string("Connection: close\r\n");
218 for (multimap<string,string>::const_iterator i=m_response_headers.begin(); i!=m_response_headers.end(); ++i)
219 hdr += i->first + ": " + i->second + "\r\n";
220
221 // We can't return 200 OK here or else the filter is bypassed
222 // so custom Shib errors will get turned into a generic page.
223 const char* codestr="Status: 500 Server Error";
224 switch (status) {
225 case XMLTOOLING_HTTP_STATUS_NOTMODIFIED: codestr="Status: 304 Not Modified"; break;
226 case XMLTOOLING_HTTP_STATUS_UNAUTHORIZED: codestr="Status: 401 Authorization Required"; break;
227 case XMLTOOLING_HTTP_STATUS_FORBIDDEN: codestr="Status: 403 Forbidden"; break;
228 case XMLTOOLING_HTTP_STATUS_NOTFOUND: codestr="Status: 404 Not Found"; break;
229 }
230 cout << codestr << "\r\n" << hdr << "\r\n";
231 char buf[1024];
232 while (in) {
233 in.read(buf,1024);
234 cout.write(buf, in.gcount());
235 }
236 return SHIB_RETURN_DONE;
237 }
238
sendRedirect(const char * url)239 long sendRedirect(const char* url) {
240 HTTPResponse::sendRedirect(url);
241 string hdr=string("Status: 302 Please Wait\r\nLocation: ") + url + "\r\n"
242 "Content-Type: text/html\r\n"
243 "Content-Length: 40\r\n"
244 "Expires: Wed, 01 Jan 1997 12:00:00 GMT\r\n"
245 "Cache-Control: private,no-store,no-cache,max-age=0\r\n";
246 for (multimap<string,string>::const_iterator i=m_response_headers.begin(); i!=m_response_headers.end(); ++i)
247 hdr += i->first + ": " + i->second + "\r\n";
248 hdr += "\r\n";
249
250 cout << hdr << "<HTML><BODY>Redirecting...</BODY></HTML>";
251 return SHIB_RETURN_DONE;
252 }
253
returnDecline()254 long returnDecline() {
255 return SHIB_RETURN_KO;
256 }
257
returnOK()258 long returnOK() {
259 return SHIB_RETURN_OK;
260 }
261
getClientCertificates() const262 const vector<string>& getClientCertificates() const {
263 static vector<string> g_NoCerts;
264 return g_NoCerts;
265 }
266 };
267
print_ok(const map<string,string> & headers)268 static void print_ok(const map<string,string>& headers)
269 {
270 cout << "Status: 200 OK" << "\r\n";
271 for (map<string,string>::const_iterator iter = headers.begin(); iter != headers.end(); iter++) {
272 cout << "Variable-" << iter->first << ": " << iter->second << "\r\n";
273 }
274 cout << "\r\n";
275 }
276
print_error(const char * msg)277 static void print_error(const char* msg)
278 {
279 cout << "Status: 500 Server Error" << "\r\n\r\n" << msg;
280 }
281
main(void)282 int main(void)
283 {
284 SPConfig* g_Config=&SPConfig::getConfig();
285 g_Config->setFeatures(
286 SPConfig::Listener |
287 SPConfig::Caching |
288 SPConfig::RequestMapping |
289 SPConfig::InProcess |
290 SPConfig::Logging |
291 SPConfig::Handlers
292 );
293 if (!g_Config->init()) {
294 cerr << "failed to initialize Shibboleth libraries" << endl;
295 exit(1);
296 }
297
298 try {
299 if (!g_Config->instantiate(nullptr, true))
300 throw runtime_error("unknown error");
301 }
302 catch (exception& ex) {
303 g_Config->term();
304 cerr << "exception while initializing Shibboleth configuration: " << ex.what() << endl;
305 exit(1);
306 }
307
308 string g_ServerScheme;
309 string g_ServerName;
310 int g_ServerPort=0;
311
312 // Load "authoritative" URL fields.
313 char* var = getenv("SHIBSP_SERVER_NAME");
314 if (var)
315 g_ServerName = var;
316 var = getenv("SHIBSP_SERVER_SCHEME");
317 if (var)
318 g_ServerScheme = var;
319 var = getenv("SHIBSP_SERVER_PORT");
320 if (var)
321 g_ServerPort = atoi(var);
322
323 streambuf* cout_streambuf = cout.rdbuf();
324 streambuf* cerr_streambuf = cerr.rdbuf();
325
326 FCGX_Request request;
327
328 FCGX_Init();
329 FCGX_InitRequest(&request, 0, 0);
330
331 cout << "Shibboleth initialization complete. Starting request loop." << endl;
332 while (FCGX_Accept_r(&request) == 0)
333 {
334 // Note that the default bufsize (0) will cause the use of iostream
335 // methods that require positioning (such as peek(), seek(),
336 // unget() and putback()) to fail (in favour of more efficient IO).
337 fcgi_streambuf cout_fcgi_streambuf(request.out);
338 fcgi_streambuf cerr_fcgi_streambuf(request.err);
339
340 cout.rdbuf(&cout_fcgi_streambuf);
341 cerr.rdbuf(&cerr_fcgi_streambuf);
342
343 try {
344 xmltooling::NDC ndc("FastCGI shibauthorizer");
345 ShibTargetFCGIAuth sta(&request, g_ServerScheme.c_str(), g_ServerName.c_str(), g_ServerPort);
346
347 pair<bool,long> res = sta.getServiceProvider().doAuthentication(sta);
348 if (res.first) {
349 sta.log(SPRequest::SPDebug, "shib: doAuthentication handled the request");
350 switch(res.second) {
351 case SHIB_RETURN_OK:
352 print_ok(sta.m_request_headers);
353 continue;
354
355 case SHIB_RETURN_KO:
356 print_ok(sta.m_request_headers);
357 continue;
358
359 case SHIB_RETURN_DONE:
360 continue;
361
362 default:
363 cerr << "shib: doAuthentication returned an unexpected result: " << res.second << endl;
364 print_error("<html><body>FastCGI Shibboleth authorizer returned an unexpected result.</body></html>");
365 continue;
366 }
367 }
368
369 res = sta.getServiceProvider().doExport(sta);
370 if (res.first) {
371 sta.log(SPRequest::SPDebug, "shib: doExport handled request");
372 switch(res.second) {
373 case SHIB_RETURN_OK:
374 print_ok(sta.m_request_headers);
375 continue;
376
377 case SHIB_RETURN_KO:
378 print_ok(sta.m_request_headers);
379 continue;
380
381 case SHIB_RETURN_DONE:
382 continue;
383
384 default:
385 cerr << "shib: doExport returned an unexpected result: " << res.second << endl;
386 print_error("<html><body>FastCGI Shibboleth authorizer returned an unexpected result.</body></html>");
387 continue;
388 }
389 }
390
391 res = sta.getServiceProvider().doAuthorization(sta);
392 if (res.first) {
393 sta.log(SPRequest::SPDebug, "shib: doAuthorization handled request");
394 switch(res.second) {
395 case SHIB_RETURN_OK:
396 print_ok(sta.m_request_headers);
397 continue;
398
399 case SHIB_RETURN_KO:
400 print_ok(sta.m_request_headers);
401 continue;
402
403 case SHIB_RETURN_DONE:
404 continue;
405
406 default:
407 cerr << "shib: doAuthorization returned an unexpected result: " << res.second << endl;
408 print_error("<html><body>FastCGI Shibboleth authorizer returned an unexpected result.</body></html>");
409 continue;
410 }
411 }
412
413 print_ok(sta.m_request_headers);
414
415 }
416 catch (exception& e) {
417 cerr << "shib: FastCGI authorizer caught an exception: " << e.what() << endl;
418 print_error("<html><body>FastCGI Shibboleth authorizer caught an exception, check log for details.</body></html>");
419 }
420
421 // If the output streambufs had non-zero bufsizes and
422 // were constructed outside of the accept loop (i.e.
423 // their destructor won't be called here), they would
424 // have to be flushed here.
425 }
426 cout << "Request loop ended." << endl;
427
428 cout.rdbuf(cout_streambuf);
429 cerr.rdbuf(cerr_streambuf);
430
431 if (g_Config)
432 g_Config->term();
433
434 return 0;
435 }
436