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