Add support for returning 304 responses.
[shibboleth/sp.git] / fastcgi / shibresponder.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 /* shibresponder.cpp - Shibboleth FastCGI Responder/Handler
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 ShibTargetFCGI : public AbstractSPRequest
62 {
63     FCGX_Request* m_req;
64     const char* m_body;
65     multimap<string,string> m_headers;
66     int m_port;
67     string m_scheme,m_hostname;
68
69 public:
70     ShibTargetFCGI(FCGX_Request* req, char* post_data, const char* scheme=nullptr, const char* hostname=nullptr, int port=0)
71         : AbstractSPRequest(SHIBSP_LOGCAT".FastCGI"), m_req(req), m_body(post_data) {
72
73         const char* server_name_str = hostname;
74         if (!server_name_str || !*server_name_str)
75             server_name_str = FCGX_GetParam("SERVER_NAME", req->envp);
76         m_hostname = server_name_str;
77
78         m_port = port;
79         if (!m_port) {
80             char* server_port_str = FCGX_GetParam("SERVER_PORT", req->envp);
81             m_port = strtol(server_port_str, &server_port_str, 10);
82             if (*server_port_str) {
83                 cerr << "can't parse SERVER_PORT (" << FCGX_GetParam("SERVER_PORT", req->envp) << ")" << endl;
84                 throw runtime_error("Unable to determine server port.");
85             }
86         }
87
88         const char* server_scheme_str = scheme;
89         if (!server_scheme_str || !*server_scheme_str)
90             server_scheme_str = (m_port == 443 || m_port == 8443) ? "https" : "http";
91         m_scheme = server_scheme_str;
92
93         setRequestURI(FCGX_GetParam("REQUEST_URI", m_req->envp));
94     }
95
96     ~ShibTargetFCGI() { }
97
98     const char* getScheme() const {
99         return m_scheme.c_str();
100     }
101     const char* getHostname() const {
102         return m_hostname.c_str();
103     }
104     int getPort() const {
105         return m_port;
106     }
107     const char* getMethod() const {
108         return FCGX_GetParam("REQUEST_METHOD", m_req->envp);
109     }
110     string getContentType() const {
111         const char* s = FCGX_GetParam("CONTENT_TYPE", m_req->envp);
112         return s ? s : "";
113     }
114     long getContentLength() const {
115         const char* s = FCGX_GetParam("CONTENT_LENGTH", m_req->envp);
116         return s ? atol(s) : 0;
117     }
118     string getRemoteUser() const {
119         const char* s = FCGX_GetParam("REMOTE_USER", m_req->envp);
120         return s ? s : "";
121     }
122     string getRemoteAddr() const {
123         string ret = AbstractSPRequest::getRemoteAddr();
124         if (!ret.empty())
125             return ret;
126         const char* s = FCGX_GetParam("REMOTE_ADDR", m_req->envp);
127         return s ? s : "";
128     }
129     void log(SPLogLevel level, const string& msg) const {
130         AbstractSPRequest::log(level,msg);
131         if (level >= SPError)
132             cerr << "shib: " << msg;
133     }
134
135     string getHeader(const char* name) const {
136         string hdr("HTTP_");
137         for (; *name; ++name) {
138             if (*name=='-')
139                 hdr += '_';
140             else
141                 hdr += toupper(*name);
142         }
143         char* s = FCGX_GetParam(hdr.c_str(), m_req->envp);
144         return s ? s : "";
145     }
146
147     void setResponseHeader(const char* name, const char* value) {
148         HTTPResponse::setResponseHeader(name, value);
149         // Set for later.
150         if (value)
151             m_headers.insert(make_pair(name,value));
152         else
153             m_headers.erase(name);
154     }
155
156     const char* getQueryString() const {
157         return FCGX_GetParam("QUERY_STRING", m_req->envp);
158     }
159
160     const char* getRequestBody() const {
161         return m_body;
162     }
163
164     long sendResponse(istream& in, long status) {
165         string hdr = string("Connection: close\r\n");
166         for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
167             hdr += i->first + ": " + i->second + "\r\n";
168
169         const char* codestr="Status: 200 OK";
170         switch (status) {
171             case XMLTOOLING_HTTP_STATUS_ERROR:          codestr="Status: 500 Server Error"; break;
172             case XMLTOOLING_HTTP_STATUS_UNAUTHORIZED:   codestr="Status: 401 Authorization Required"; break;
173             case XMLTOOLING_HTTP_STATUS_FORBIDDEN:      codestr="Status: 403 Forbidden"; break;
174             case XMLTOOLING_HTTP_STATUS_NOTFOUND:       codestr="Status: 404 Not Found"; break;
175             case XMLTOOLING_HTTP_STATUS_NOTMODIFIED:    codestr="Status: 304 Not Modified"; break;
176         }
177         cout << codestr << "\r\n" << hdr << "\r\n";
178         char buf[1024];
179         while (in) {
180             in.read(buf,1024);
181             cout.write(buf, in.gcount());
182         }
183         return SHIB_RETURN_DONE;
184     }
185
186     long sendRedirect(const char* url) {
187         HTTPResponse::sendRedirect(url);
188         string hdr=string("Status: 302 Please Wait\r\nLocation: ") + url + "\r\n"
189           "Content-Type: text/html\r\n"
190           "Content-Length: 40\r\n"
191           "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
192           "Cache-Control: private,no-store,no-cache\r\n";
193         for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
194             hdr += i->first + ": " + i->second + "\r\n";
195         hdr += "\r\n";
196
197         cout << hdr << "<HTML><BODY>Redirecting...</BODY></HTML>";
198         return SHIB_RETURN_DONE;
199     }
200
201     long returnDecline() {
202         return SHIB_RETURN_KO;
203     }
204     long returnOK() {
205         return SHIB_RETURN_OK;
206     }
207
208     const vector<string>& getClientCertificates() const {
209         static vector<string> g_NoCerts;
210         return g_NoCerts;
211     }
212
213     // Not used in the extension.
214
215     virtual void clearHeader(const char* rawname, const char* cginame) {
216         throw runtime_error("clearHeader not implemented by FastCGI responder.");
217     }
218
219     virtual void setHeader(const char* name, const char* value) {
220         throw runtime_error("setHeader not implemented by FastCGI responder.");
221     }
222
223     virtual void setRemoteUser(const char* user) {
224         throw runtime_error("setRemoteUser not implemented by FastCGI responder.");
225     }
226 };
227
228 // Maximum number of bytes allowed to be read from stdin
229 static const unsigned long STDIN_MAX = 1000000;
230
231 static long gstdin(FCGX_Request* request, char** content)
232 {
233     char* clenstr = FCGX_GetParam("CONTENT_LENGTH", request->envp);
234     unsigned long clen = STDIN_MAX;
235
236     if (clenstr) {
237         clen = strtol(clenstr, &clenstr, 10);
238         if (*clenstr) {
239             cerr << "can't parse CONTENT_LENGTH (" << FCGX_GetParam("CONTENT_LENGTH", request->envp) << ")" << endl;
240             clen = STDIN_MAX;
241         }
242
243         // *always* put a cap on the amount of data that will be read
244         if (clen > STDIN_MAX)
245             clen = STDIN_MAX;
246
247         *content = new char[clen];
248
249         cin.read(*content, clen);
250         clen = cin.gcount();
251     }
252     else {
253         // *never* read stdin when CONTENT_LENGTH is missing or unparsable
254         *content = 0;
255         clen = 0;
256     }
257
258     // Chew up any remaining stdin - this shouldn't be necessary
259     // but is because mod_fastcgi doesn't handle it correctly.
260
261     // ignore() doesn't set the eof bit in some versions of glibc++
262     // so use gcount() instead of eof()...
263     do cin.ignore(1024); while (cin.gcount() == 1024);
264
265     return clen;
266 }
267
268 static void print_ok() {
269     cout << "Status: 200 OK" << "\r\n\r\n";
270 }
271
272 static void print_error(const char* msg) {
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* cin_streambuf  = cin.rdbuf();
318     streambuf* cout_streambuf = cout.rdbuf();
319     streambuf* cerr_streambuf = cerr.rdbuf();
320
321     FCGX_Request request;
322
323     FCGX_Init();
324     FCGX_InitRequest(&request, 0, 0);
325
326     cout << "Shibboleth initialization complete. Starting request loop." << endl;
327     while (FCGX_Accept_r(&request) == 0) {
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 cin_fcgi_streambuf(request.in);
332         fcgi_streambuf cout_fcgi_streambuf(request.out);
333         fcgi_streambuf cerr_fcgi_streambuf(request.err);
334
335         cin.rdbuf(&cin_fcgi_streambuf);
336         cout.rdbuf(&cout_fcgi_streambuf);
337         cerr.rdbuf(&cerr_fcgi_streambuf);
338
339         // Although FastCGI supports writing before reading,
340         // many http clients (browsers) don't support it (so
341         // the connection deadlocks until a timeout expires!).
342         char* content;
343         gstdin(&request, &content);
344
345         try {
346             xmltooling::NDC ndc("FastCGI shibresponder");
347             ShibTargetFCGI stf(&request, content, g_ServerScheme.c_str(), g_ServerName.c_str(), g_ServerPort);
348
349             pair<bool,long> res = stf.getServiceProvider().doHandler(stf);
350             if (res.first) {
351                 stf.log(SPRequest::SPDebug, "shib: doHandler handled the request");
352                 switch(res.second) {
353                     case SHIB_RETURN_OK:
354                         print_ok();
355                         break;
356
357                     case SHIB_RETURN_KO:
358                         cerr << "shib: doHandler failed to handle the request" << endl;
359                         print_error("<html><body>FastCGI Shibboleth responder should only be used for Shibboleth protocol requests.</body></html>");
360                         break;
361
362                     case SHIB_RETURN_DONE:
363                         // response already handled
364                         break;
365
366                     default:
367                         cerr << "shib: doHandler returned an unexpected result: " << res.second << endl;
368                         print_error("<html><body>FastCGI Shibboleth responder returned an unexpected result.</body></html>");
369                         break;
370                 }
371             }
372             else {
373                 cerr << "shib: doHandler failed to handle request." << endl;
374                 print_error("<html><body>FastCGI Shibboleth responder failed to process request.</body></html>");
375             }
376
377         }
378         catch (exception& e) {
379             cerr << "shib: FastCGI responder caught an exception: " << e.what() << endl;
380             print_error("<html><body>FastCGI Shibboleth responder caught an exception, check log for details.</body></html>");
381         }
382
383         delete[] content;
384
385         // If the output streambufs had non-zero bufsizes and
386         // were constructed outside of the accept loop (i.e.
387         // their destructor won't be called here), they would
388         // have to be flushed here.
389     }
390
391     cout << "Request loop ended." << endl;
392
393     cin.rdbuf(cin_streambuf);
394     cout.rdbuf(cout_streambuf);
395     cerr.rdbuf(cerr_streambuf);
396
397     if (g_Config)
398         g_Config->term();
399
400     return 0;
401 }