SSPCPP-616 - clean up concatenated string literals
[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         if (name) {
154             // Set for later.
155             if (value)
156                 m_headers.insert(make_pair(name,value));
157             else
158                 m_headers.erase(name);
159         }
160     }
161
162     const char* getQueryString() const {
163         return FCGX_GetParam("QUERY_STRING", m_req->envp);
164     }
165
166     const char* getRequestBody() const {
167         return m_body;
168     }
169
170     long sendResponse(istream& in, long status) {
171         string hdr = string("Connection: close\r\n");
172         for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
173             hdr += i->first + ": " + i->second + "\r\n";
174
175         const char* codestr="Status: 200 OK";
176         switch (status) {
177             case XMLTOOLING_HTTP_STATUS_ERROR:          codestr="Status: 500 Server Error"; break;
178             case XMLTOOLING_HTTP_STATUS_UNAUTHORIZED:   codestr="Status: 401 Authorization Required"; break;
179             case XMLTOOLING_HTTP_STATUS_FORBIDDEN:      codestr="Status: 403 Forbidden"; break;
180             case XMLTOOLING_HTTP_STATUS_NOTFOUND:       codestr="Status: 404 Not Found"; break;
181             case XMLTOOLING_HTTP_STATUS_NOTMODIFIED:    codestr="Status: 304 Not Modified"; break;
182         }
183         cout << codestr << "\r\n" << hdr << "\r\n";
184         char buf[1024];
185         while (in) {
186             in.read(buf,1024);
187             cout.write(buf, in.gcount());
188         }
189         return SHIB_RETURN_DONE;
190     }
191
192     long sendRedirect(const char* url) {
193         HTTPResponse::sendRedirect(url);
194         string hdr=string("Status: 302 Please Wait\r\nLocation: ") + url + "\r\n"
195           "Content-Type: text/html\r\n"
196           "Content-Length: 40\r\n"
197           "Expires: Wed, 01 Jan 1997 12:00:00 GMT\r\n"
198           "Cache-Control: private,no-store,no-cache,max-age=0\r\n";
199         for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
200             hdr += i->first + ": " + i->second + "\r\n";
201         hdr += "\r\n";
202
203         cout << hdr << "<HTML><BODY>Redirecting...</BODY></HTML>";
204         return SHIB_RETURN_DONE;
205     }
206
207     long returnDecline() {
208         return SHIB_RETURN_KO;
209     }
210     long returnOK() {
211         return SHIB_RETURN_OK;
212     }
213
214     const vector<string>& getClientCertificates() const {
215         static vector<string> g_NoCerts;
216         return g_NoCerts;
217     }
218
219     // Not used in the extension.
220
221     virtual void clearHeader(const char* rawname, const char* cginame) {
222         throw runtime_error("clearHeader not implemented by FastCGI responder.");
223     }
224
225     virtual void setHeader(const char* name, const char* value) {
226         throw runtime_error("setHeader not implemented by FastCGI responder.");
227     }
228
229     virtual void setRemoteUser(const char* user) {
230         throw runtime_error("setRemoteUser not implemented by FastCGI responder.");
231     }
232 };
233
234 // Maximum number of bytes allowed to be read from stdin
235 static const unsigned long STDIN_MAX = 1000000;
236
237 static long gstdin(FCGX_Request* request, char** content)
238 {
239     char* clenstr = FCGX_GetParam("CONTENT_LENGTH", request->envp);
240     unsigned long clen = STDIN_MAX;
241
242     if (clenstr) {
243         clen = strtol(clenstr, &clenstr, 10);
244         if (*clenstr) {
245             cerr << "can't parse CONTENT_LENGTH (" << FCGX_GetParam("CONTENT_LENGTH", request->envp) << ")" << endl;
246             clen = STDIN_MAX;
247         }
248
249         // *always* put a cap on the amount of data that will be read
250         if (clen > STDIN_MAX)
251             clen = STDIN_MAX;
252
253         *content = new char[clen + 1];
254
255         cin.read(*content, clen);
256         clen = cin.gcount();
257         (*content)[clen] = 0;
258     }
259     else {
260         // *never* read stdin when CONTENT_LENGTH is missing or unparsable
261         *content = 0;
262         clen = 0;
263     }
264
265     // Chew up any remaining stdin - this shouldn't be necessary
266     // but is because mod_fastcgi doesn't handle it correctly.
267
268     // ignore() doesn't set the eof bit in some versions of glibc++
269     // so use gcount() instead of eof()...
270     do cin.ignore(1024); while (cin.gcount() == 1024);
271
272     return clen;
273 }
274
275 static void print_ok() {
276     cout << "Status: 200 OK" << "\r\n\r\n";
277 }
278
279 static void print_error(const char* msg) {
280     cout << "Status: 500 Server Error" << "\r\n\r\n" << msg;
281 }
282
283 int main(void)
284 {
285     SPConfig* g_Config=&SPConfig::getConfig();
286     g_Config->setFeatures(
287         SPConfig::Listener |
288         SPConfig::Caching |
289         SPConfig::RequestMapping |
290         SPConfig::InProcess |
291         SPConfig::Logging |
292         SPConfig::Handlers
293         );
294     if (!g_Config->init()) {
295         cerr << "failed to initialize Shibboleth libraries" << endl;
296         exit(1);
297     }
298
299     try {
300         if (!g_Config->instantiate(nullptr, true))
301             throw runtime_error("unknown error");
302     }
303     catch (exception& ex) {
304         g_Config->term();
305         cerr << "exception while initializing Shibboleth configuration: " << ex.what() << endl;
306         exit(1);
307     }
308
309     string g_ServerScheme;
310     string g_ServerName;
311     int g_ServerPort=0;
312
313     // Load "authoritative" URL fields.
314     char* var = getenv("SHIBSP_SERVER_NAME");
315     if (var)
316         g_ServerName = var;
317     var = getenv("SHIBSP_SERVER_SCHEME");
318     if (var)
319         g_ServerScheme = var;
320     var = getenv("SHIBSP_SERVER_PORT");
321     if (var)
322         g_ServerPort = atoi(var);
323
324     streambuf* cin_streambuf  = cin.rdbuf();
325     streambuf* cout_streambuf = cout.rdbuf();
326     streambuf* cerr_streambuf = cerr.rdbuf();
327
328     FCGX_Request request;
329
330     FCGX_Init();
331     FCGX_InitRequest(&request, 0, 0);
332
333     cout << "Shibboleth initialization complete. Starting request loop." << endl;
334     while (FCGX_Accept_r(&request) == 0) {
335         // Note that the default bufsize (0) will cause the use of iostream
336         // methods that require positioning (such as peek(), seek(),
337         // unget() and putback()) to fail (in favour of more efficient IO).
338         fcgi_streambuf cin_fcgi_streambuf(request.in);
339         fcgi_streambuf cout_fcgi_streambuf(request.out);
340         fcgi_streambuf cerr_fcgi_streambuf(request.err);
341
342         cin.rdbuf(&cin_fcgi_streambuf);
343         cout.rdbuf(&cout_fcgi_streambuf);
344         cerr.rdbuf(&cerr_fcgi_streambuf);
345
346         // Although FastCGI supports writing before reading,
347         // many http clients (browsers) don't support it (so
348         // the connection deadlocks until a timeout expires!).
349         char* content = nullptr;
350         gstdin(&request, &content);
351         auto_arrayptr<char> wrapper(content);
352
353         try {
354             xmltooling::NDC ndc("FastCGI shibresponder");
355             ShibTargetFCGI stf(&request, content, g_ServerScheme.c_str(), g_ServerName.c_str(), g_ServerPort);
356
357             pair<bool,long> res = stf.getServiceProvider().doHandler(stf);
358             if (res.first) {
359                 stf.log(SPRequest::SPDebug, "shib: doHandler handled the request");
360                 switch(res.second) {
361                     case SHIB_RETURN_OK:
362                         print_ok();
363                         break;
364
365                     case SHIB_RETURN_KO:
366                         cerr << "shib: doHandler failed to handle the request" << endl;
367                         print_error("<html><body>FastCGI Shibboleth responder should only be used for Shibboleth protocol requests.</body></html>");
368                         break;
369
370                     case SHIB_RETURN_DONE:
371                         // response already handled
372                         break;
373
374                     default:
375                         cerr << "shib: doHandler returned an unexpected result: " << res.second << endl;
376                         print_error("<html><body>FastCGI Shibboleth responder returned an unexpected result.</body></html>");
377                         break;
378                 }
379             }
380             else {
381                 cerr << "shib: doHandler failed to handle request." << endl;
382                 print_error("<html><body>FastCGI Shibboleth responder failed to process request.</body></html>");
383             }
384
385         }
386         catch (exception& e) {
387             cerr << "shib: FastCGI responder caught an exception: " << e.what() << endl;
388             print_error("<html><body>FastCGI Shibboleth responder caught an exception, check log for details.</body></html>");
389         }
390
391         // If the output streambufs had non-zero bufsizes and
392         // were constructed outside of the accept loop (i.e.
393         // their destructor won't be called here), they would
394         // have to be flushed here.
395     }
396
397     cout << "Request loop ended." << endl;
398
399     cin.rdbuf(cin_streambuf);
400     cout.rdbuf(cout_streambuf);
401     cerr.rdbuf(cerr_streambuf);
402
403     if (g_Config)
404         g_Config->term();
405
406     return 0;
407 }