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