Bump version.
[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];
252
253         cin.read(*content, clen);
254         clen = cin.gcount();
255     }
256     else {
257         // *never* read stdin when CONTENT_LENGTH is missing or unparsable
258         *content = 0;
259         clen = 0;
260     }
261
262     // Chew up any remaining stdin - this shouldn't be necessary
263     // but is because mod_fastcgi doesn't handle it correctly.
264
265     // ignore() doesn't set the eof bit in some versions of glibc++
266     // so use gcount() instead of eof()...
267     do cin.ignore(1024); while (cin.gcount() == 1024);
268
269     return clen;
270 }
271
272 static void print_ok() {
273     cout << "Status: 200 OK" << "\r\n\r\n";
274 }
275
276 static void print_error(const char* msg) {
277     cout << "Status: 500 Server Error" << "\r\n\r\n" << msg;
278 }
279
280 int main(void)
281 {
282     SPConfig* g_Config=&SPConfig::getConfig();
283     g_Config->setFeatures(
284         SPConfig::Listener |
285         SPConfig::Caching |
286         SPConfig::RequestMapping |
287         SPConfig::InProcess |
288         SPConfig::Logging |
289         SPConfig::Handlers
290         );
291     if (!g_Config->init()) {
292         cerr << "failed to initialize Shibboleth libraries" << endl;
293         exit(1);
294     }
295
296     try {
297         if (!g_Config->instantiate(nullptr, true))
298             throw runtime_error("unknown error");
299     }
300     catch (exception& ex) {
301         g_Config->term();
302         cerr << "exception while initializing Shibboleth configuration: " << ex.what() << endl;
303         exit(1);
304     }
305
306     string g_ServerScheme;
307     string g_ServerName;
308     int g_ServerPort=0;
309
310     // Load "authoritative" URL fields.
311     char* var = getenv("SHIBSP_SERVER_NAME");
312     if (var)
313         g_ServerName = var;
314     var = getenv("SHIBSP_SERVER_SCHEME");
315     if (var)
316         g_ServerScheme = var;
317     var = getenv("SHIBSP_SERVER_PORT");
318     if (var)
319         g_ServerPort = atoi(var);
320
321     streambuf* cin_streambuf  = cin.rdbuf();
322     streambuf* cout_streambuf = cout.rdbuf();
323     streambuf* cerr_streambuf = cerr.rdbuf();
324
325     FCGX_Request request;
326
327     FCGX_Init();
328     FCGX_InitRequest(&request, 0, 0);
329
330     cout << "Shibboleth initialization complete. Starting request loop." << endl;
331     while (FCGX_Accept_r(&request) == 0) {
332         // Note that the default bufsize (0) will cause the use of iostream
333         // methods that require positioning (such as peek(), seek(),
334         // unget() and putback()) to fail (in favour of more efficient IO).
335         fcgi_streambuf cin_fcgi_streambuf(request.in);
336         fcgi_streambuf cout_fcgi_streambuf(request.out);
337         fcgi_streambuf cerr_fcgi_streambuf(request.err);
338
339         cin.rdbuf(&cin_fcgi_streambuf);
340         cout.rdbuf(&cout_fcgi_streambuf);
341         cerr.rdbuf(&cerr_fcgi_streambuf);
342
343         // Although FastCGI supports writing before reading,
344         // many http clients (browsers) don't support it (so
345         // the connection deadlocks until a timeout expires!).
346         char* content = nullptr;
347         gstdin(&request, &content);
348         auto_arrayptr<char> wrapper(content);
349
350         try {
351             xmltooling::NDC ndc("FastCGI shibresponder");
352             ShibTargetFCGI stf(&request, content, g_ServerScheme.c_str(), g_ServerName.c_str(), g_ServerPort);
353
354             pair<bool,long> res = stf.getServiceProvider().doHandler(stf);
355             if (res.first) {
356                 stf.log(SPRequest::SPDebug, "shib: doHandler handled the request");
357                 switch(res.second) {
358                     case SHIB_RETURN_OK:
359                         print_ok();
360                         break;
361
362                     case SHIB_RETURN_KO:
363                         cerr << "shib: doHandler failed to handle the request" << endl;
364                         print_error("<html><body>FastCGI Shibboleth responder should only be used for Shibboleth protocol requests.</body></html>");
365                         break;
366
367                     case SHIB_RETURN_DONE:
368                         // response already handled
369                         break;
370
371                     default:
372                         cerr << "shib: doHandler returned an unexpected result: " << res.second << endl;
373                         print_error("<html><body>FastCGI Shibboleth responder returned an unexpected result.</body></html>");
374                         break;
375                 }
376             }
377             else {
378                 cerr << "shib: doHandler failed to handle request." << endl;
379                 print_error("<html><body>FastCGI Shibboleth responder failed to process request.</body></html>");
380             }
381
382         }
383         catch (exception& e) {
384             cerr << "shib: FastCGI responder caught an exception: " << e.what() << endl;
385             print_error("<html><body>FastCGI Shibboleth responder caught an exception, check log for details.</body></html>");
386         }
387
388         // If the output streambufs had non-zero bufsizes and
389         // were constructed outside of the accept loop (i.e.
390         // their destructor won't be called here), they would
391         // have to be flushed here.
392     }
393
394     cout << "Request loop ended." << endl;
395
396     cin.rdbuf(cin_streambuf);
397     cout.rdbuf(cout_streambuf);
398     cerr.rdbuf(cerr_streambuf);
399
400     if (g_Config)
401         g_Config->term();
402
403     return 0;
404 }