75d3185d0ac11874345fd48d30ccc5c3173bcfb2
[shibboleth/cpp-sp.git] / fastcgi / shibauthorizer.cpp
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
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
99     ~ShibTargetFCGIAuth() { }
100
101     const char* getScheme() const {
102         return m_scheme.c_str();
103     }
104     const char* getHostname() const {
105         return m_hostname.c_str();
106     }
107     int getPort() const {
108         return m_port;
109     }
110     const char* getMethod() const {
111         return FCGX_GetParam("REQUEST_METHOD", m_req->envp);
112     }
113     string getContentType() const {
114         const char* s = FCGX_GetParam("CONTENT_TYPE", m_req->envp);
115         return s ? s : "";
116     }
117     long getContentLength() const {
118         const char* s = FCGX_GetParam("CONTENT_LENGTH", m_req->envp);
119         return s ? atol(s) : 0;
120     }
121     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     }
128     void log(SPLogLevel level, const string& msg) const {
129         AbstractSPRequest::log(level,msg);
130         if (level >= SPError)
131             cerr << "shib: " << msg;
132     }
133     void clearHeader(const char* rawname, const char* cginame) {
134         // No need, since we use environment variables.
135     }
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     }
142     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     }
158     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     }
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     }
171     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     }
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     }
188     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     }
199     void setResponseHeader(const char* name, const char* value) {
200         HTTPResponse::setResponseHeader(name, value);
201         if (name) {
202             // Set for later.
203             if (value)
204                 m_response_headers.insert(make_pair(name,value));
205             else
206                 m_response_headers.erase(name);
207         }
208     }
209     const char* getQueryString() const {
210         return FCGX_GetParam("QUERY_STRING", m_req->envp);
211     }
212     const char* getRequestBody() const {
213         throw runtime_error("getRequestBody not implemented by FastCGI authorizer.");
214     }
215
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
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
254     long returnDecline() {
255         return SHIB_RETURN_KO;
256     }
257
258     long returnOK() {
259         return SHIB_RETURN_OK;
260     }
261
262     const vector<string>& getClientCertificates() const {
263         static vector<string> g_NoCerts;
264         return g_NoCerts;
265     }
266 };
267
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
277 static void print_error(const char* msg)
278 {
279     cout << "Status: 500 Server Error" << "\r\n\r\n" << msg;
280 }
281
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 }