Port fastcgi code to trunk.
[shibboleth/cpp-sp.git] / fastcgi / shibresponder.cpp
1 /*\r
2  *  Copyright 2001-2007 Internet2\r
3  * \r
4  * Licensed under the Apache License, Version 2.0 (the "License");\r
5  * you may not use this file except in compliance with the License.\r
6  * You may obtain a copy of the License at\r
7  *\r
8  *     http://www.apache.org/licenses/LICENSE-2.0\r
9  *\r
10  * Unless required by applicable law or agreed to in writing, software\r
11  * distributed under the License is distributed on an "AS IS" BASIS,\r
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
13  * See the License for the specific language governing permissions and\r
14  * limitations under the License.\r
15  */\r
16 \r
17 /* shibresponder.cpp - Shibboleth FastCGI Responder/Handler\r
18 \r
19    Andre Cruz\r
20 */\r
21 \r
22 #define SHIBSP_LITE\r
23 #include "config_win32.h"\r
24 \r
25 #define _CRT_NONSTDC_NO_DEPRECATE 1\r
26 #define _CRT_SECURE_NO_DEPRECATE 1\r
27 #define _SCL_SECURE_NO_WARNINGS 1\r
28 \r
29 #include <shibsp/AbstractSPRequest.h>\r
30 #include <shibsp/SPConfig.h>\r
31 #include <shibsp/ServiceProvider.h>\r
32 #include <xmltooling/unicode.h>\r
33 #include <xmltooling/XMLToolingConfig.h>\r
34 #include <xmltooling/util/NDC.h>\r
35 #include <xmltooling/util/XMLConstants.h>\r
36 #include <xmltooling/util/XMLHelper.h>\r
37 #include <xercesc/util/XMLUniDefs.hpp>\r
38 \r
39 #include <stdlib.h>\r
40 #ifdef HAVE_UNISTD_H\r
41 # include <unistd.h>\r
42 # include <sys/mman.h>\r
43 #endif\r
44 #include <fcgio.h>\r
45 \r
46 using namespace shibsp;\r
47 using namespace xmltooling;\r
48 using namespace xercesc;\r
49 using namespace std;\r
50 \r
51 static const XMLCh path[] =     UNICODE_LITERAL_4(p,a,t,h);\r
52 static const XMLCh validate[] = UNICODE_LITERAL_8(v,a,l,i,d,a,t,e);\r
53 \r
54 typedef enum {\r
55     SHIB_RETURN_OK,\r
56     SHIB_RETURN_KO,\r
57     SHIB_RETURN_DONE\r
58 } shib_return_t;\r
59 \r
60 class ShibTargetFCGI : public AbstractSPRequest\r
61 {\r
62     FCGX_Request* m_req;\r
63     const char* m_body;\r
64     multimap<string,string> m_headers;\r
65     int m_port;\r
66     string m_scheme,m_hostname;\r
67 \r
68 public:\r
69     ShibTargetFCGI(FCGX_Request* req, char* post_data, const char* scheme=NULL, const char* hostname=NULL, int port=0)\r
70         : m_req(req), m_body(post_data) {\r
71 \r
72         const char* server_name_str = hostname;\r
73         if (!server_name_str || !*server_name_str)\r
74             server_name_str = FCGX_GetParam("SERVER_NAME", req->envp);\r
75         m_hostname = server_name_str;\r
76 \r
77         m_port = port;\r
78         if (!m_port) {\r
79             char* server_port_str = FCGX_GetParam("SERVER_PORT", req->envp);\r
80             m_port = strtol(server_port_str, &server_port_str, 10);\r
81             if (*server_port_str) {\r
82                 cerr << "can't parse SERVER_PORT (" << FCGX_GetParam("SERVER_PORT", req->envp) << ")" << endl;\r
83                 throw exception("Unable to determine server port.");\r
84             }\r
85         }\r
86 \r
87         const char* server_scheme_str = scheme;\r
88         if (!server_scheme_str || !*server_scheme_str)\r
89             server_scheme_str = (m_port == 443 || m_port == 8443) ? "https" : "http";\r
90         m_scheme = server_scheme_str;\r
91     }\r
92 \r
93     ~ShibTargetFCGI() { }\r
94 \r
95     const char* getScheme() const {\r
96         return m_scheme.c_str();\r
97     }\r
98     const char* getHostname() const {\r
99         return m_hostname.c_str();\r
100     }\r
101     int getPort() const {\r
102         return m_port;\r
103     }\r
104     const char* getRequestURI() const {\r
105         return FCGX_GetParam("REQUEST_URI", m_req->envp);\r
106     }\r
107     const char* getMethod() const {\r
108         return FCGX_GetParam("REQUEST_METHOD", m_req->envp);\r
109     }\r
110     string getContentType() const {\r
111         const char* s = FCGX_GetParam("CONTENT_TYPE", m_req->envp);\r
112         return s ? s : "";\r
113     }\r
114     long getContentLength() const {\r
115         const char* s = FCGX_GetParam("CONTENT_LENGTH", m_req->envp);\r
116         return s ? atol(s) : 0;\r
117     }\r
118     string getRemoteUser() const {\r
119         const char* s = FCGX_GetParam("REMOTE_USER", m_req->envp);\r
120         return s ? s : "";\r
121     }\r
122     string getRemoteAddr() const {\r
123         const char* s = FCGX_GetParam("REMOTE_ADDR", m_req->envp);\r
124         return s ? s : "";\r
125     }\r
126     void log(SPLogLevel level, const string& msg) const {\r
127         AbstractSPRequest::log(level,msg);\r
128         if (level >= SPError)\r
129             cerr << "shib: " << msg;\r
130     }\r
131 \r
132     string getHeader(const char* name) const {\r
133         string hdr("HTTP_");\r
134         for (; *name; ++name) {\r
135             if (*name=='-')\r
136                 hdr += '_';\r
137             else\r
138                 hdr += toupper(*name);\r
139         }\r
140         char* s = FCGX_GetParam(hdr.c_str(), m_req->envp);\r
141         return s ? s : "";\r
142     }\r
143 \r
144     void setResponseHeader(const char* name, const char* value) {\r
145         // Set for later.\r
146         if (value)\r
147             m_headers.insert(make_pair(name,value));\r
148         else\r
149             m_headers.erase(name);\r
150     }\r
151 \r
152     const char* getQueryString() const {\r
153         return FCGX_GetParam("QUERY_STRING", m_req->envp);\r
154     }\r
155 \r
156     const char* getRequestBody() const {\r
157         return m_body;\r
158     }\r
159 \r
160     long sendResponse(istream& in, long status) {\r
161         string hdr = string("Connection: close\r\n");\r
162         for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)\r
163             hdr += i->first + ": " + i->second + "\r\n";\r
164 \r
165         const char* codestr="Status: 200 OK";\r
166         switch (status) {\r
167             case XMLTOOLING_HTTP_STATUS_ERROR:    codestr="Status: 500 Server Error"; break;\r
168             case XMLTOOLING_HTTP_STATUS_FORBIDDEN:codestr="Status: 403 Forbidden"; break;\r
169             case XMLTOOLING_HTTP_STATUS_NOTFOUND: codestr="Status: 404 Not Found"; break;\r
170         }\r
171         cout << codestr << "\r\n" << hdr << "\r\n";\r
172         char buf[1024];\r
173         while (in) {\r
174             in.read(buf,1024);\r
175             cout.write(buf, in.gcount());\r
176         }\r
177         return SHIB_RETURN_DONE;\r
178     }\r
179 \r
180     long sendRedirect(const char* url) {\r
181         string hdr=string("Status: 302 Please Wait\r\nLocation: ") + url + "\r\n"\r
182           "Content-Type: text/html\r\n"\r
183           "Content-Length: 40\r\n"\r
184           "Expires: 01-Jan-1997 12:00:00 GMT\r\n"\r
185           "Cache-Control: private,no-store,no-cache\r\n";\r
186         for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)\r
187             hdr += i->first + ": " + i->second + "\r\n";\r
188         hdr += "\r\n";\r
189 \r
190         cout << hdr << "<HTML><BODY>Redirecting...</BODY></HTML>";\r
191         return SHIB_RETURN_DONE;\r
192     }\r
193 \r
194     long returnDecline() {\r
195         return SHIB_RETURN_KO;\r
196     }\r
197     long returnOK() {\r
198         return SHIB_RETURN_OK;\r
199     }\r
200 \r
201     const vector<string>& getClientCertificates() const {\r
202         static vector<string> g_NoCerts;\r
203         return g_NoCerts;\r
204     }\r
205 \r
206     // Not used in the extension.\r
207 \r
208     virtual void clearHeader(const char* rawname, const char* cginame) {\r
209         throw exception("clearHeader not implemented by FastCGI responder.");\r
210     }\r
211   \r
212     virtual void setHeader(const char* name, const char* value) {\r
213         throw exception("setHeader not implemented by FastCGI responder.");\r
214     }\r
215 \r
216     virtual void setRemoteUser(const char* user) {\r
217         throw exception("setRemoteUser not implemented by FastCGI responder.");\r
218     }\r
219 };\r
220 \r
221 // Maximum number of bytes allowed to be read from stdin\r
222 static const unsigned long STDIN_MAX = 1000000;\r
223 \r
224 static long gstdin(FCGX_Request* request, char** content)\r
225 {\r
226     char* clenstr = FCGX_GetParam("CONTENT_LENGTH", request->envp);\r
227     unsigned long clen = STDIN_MAX;\r
228 \r
229     if (clenstr) {\r
230         clen = strtol(clenstr, &clenstr, 10);\r
231         if (*clenstr) {\r
232             cerr << "can't parse CONTENT_LENGTH (" << FCGX_GetParam("CONTENT_LENGTH", request->envp) << ")" << endl;\r
233             clen = STDIN_MAX;\r
234         }\r
235 \r
236         // *always* put a cap on the amount of data that will be read\r
237         if (clen > STDIN_MAX)\r
238             clen = STDIN_MAX;\r
239 \r
240         *content = new char[clen];\r
241 \r
242         cin.read(*content, clen);\r
243         clen = cin.gcount();\r
244     }\r
245     else {\r
246         // *never* read stdin when CONTENT_LENGTH is missing or unparsable\r
247         *content = 0;\r
248         clen = 0;\r
249     }\r
250 \r
251     // Chew up any remaining stdin - this shouldn't be necessary\r
252     // but is because mod_fastcgi doesn't handle it correctly.\r
253 \r
254     // ignore() doesn't set the eof bit in some versions of glibc++\r
255     // so use gcount() instead of eof()...\r
256     do cin.ignore(1024); while (cin.gcount() == 1024);\r
257 \r
258     return clen;\r
259 }\r
260 \r
261 static void print_ok() {\r
262     cout << "Status: 200 OK" << "\r\n\r\n";\r
263 }\r
264 \r
265 static void print_error(const char* msg) {\r
266     cout << "Status: 500 Server Error" << "\r\n\r\n" << msg;\r
267 }\r
268 \r
269 int main(void)\r
270 {\r
271     const char* schemadir=getenv("SHIBSP_SCHEMAS");\r
272     if (!schemadir)\r
273         schemadir=SHIBSP_SCHEMAS;\r
274     const char* config=getenv("SHIBSP_CONFIG");\r
275     if (!config)\r
276         config=SHIBSP_CONFIG;\r
277 \r
278     cerr << "SHIBSP_CONFIG = " << config << endl\r
279          << "SHIBSP_SCHEMAS = " << schemadir << endl;\r
280 \r
281     SPConfig* g_Config=&SPConfig::getConfig();\r
282     g_Config->setFeatures(\r
283         SPConfig::Listener |\r
284         SPConfig::Caching |\r
285         SPConfig::RequestMapping |\r
286         SPConfig::InProcess |\r
287         SPConfig::Logging |\r
288         SPConfig::Handlers\r
289         );\r
290     if (!g_Config->init(schemadir)) {\r
291         cerr << "failed to initialize Shibboleth libraries" << endl;\r
292         exit(1);\r
293     }\r
294 \r
295     try {\r
296         DOMDocument* dummydoc=XMLToolingConfig::getConfig().getParser().newDocument();\r
297         XercesJanitor<DOMDocument> docjanitor(dummydoc);\r
298         DOMElement* dummy = dummydoc->createElementNS(NULL,path);\r
299         auto_ptr_XMLCh src(config);\r
300         dummy->setAttributeNS(NULL,path,src.get());\r
301         dummy->setAttributeNS(NULL,validate,xmlconstants::XML_ONE);\r
302 \r
303         g_Config->setServiceProvider(g_Config->ServiceProviderManager.newPlugin(XML_SERVICE_PROVIDER,dummy));\r
304         g_Config->getServiceProvider()->init();\r
305     }\r
306     catch (exception& ex) {\r
307         g_Config->term();\r
308         cerr << "exception while initializing Shibboleth configuration: " << ex.what() << endl;\r
309         exit(1);\r
310     }\r
311 \r
312     string g_ServerScheme;\r
313     string g_ServerName;\r
314     int g_ServerPort=0;\r
315 \r
316     // Load "authoritative" URL fields.\r
317     char* var = getenv("SHIBSP_SERVER_NAME");\r
318     if (var)\r
319         g_ServerName = var;\r
320     var = getenv("SHIBSP_SERVER_SCHEME");\r
321     if (var)\r
322         g_ServerScheme = var;\r
323     var = getenv("SHIBSP_SERVER_PORT");\r
324     if (var)\r
325         g_ServerPort = atoi(var);\r
326 \r
327     streambuf* cin_streambuf  = cin.rdbuf();\r
328     streambuf* cout_streambuf = cout.rdbuf();\r
329     streambuf* cerr_streambuf = cerr.rdbuf();\r
330 \r
331     FCGX_Request request;\r
332 \r
333     FCGX_Init();\r
334     FCGX_InitRequest(&request, 0, 0);\r
335     \r
336     cout << "Shibboleth initialization complete. Starting request loop." << endl;\r
337     while (FCGX_Accept_r(&request) == 0) {\r
338         // Note that the default bufsize (0) will cause the use of iostream\r
339         // methods that require positioning (such as peek(), seek(),\r
340         // unget() and putback()) to fail (in favour of more efficient IO).\r
341         fcgi_streambuf cin_fcgi_streambuf(request.in);\r
342         fcgi_streambuf cout_fcgi_streambuf(request.out);\r
343         fcgi_streambuf cerr_fcgi_streambuf(request.err);\r
344 \r
345         cin.rdbuf(&cin_fcgi_streambuf);\r
346         cout.rdbuf(&cout_fcgi_streambuf);\r
347         cerr.rdbuf(&cerr_fcgi_streambuf);\r
348 \r
349         // Although FastCGI supports writing before reading,\r
350         // many http clients (browsers) don't support it (so\r
351         // the connection deadlocks until a timeout expires!).\r
352         char* content;\r
353         gstdin(&request, &content);\r
354 \r
355         try {\r
356             xmltooling::NDC ndc("FastCGI shibresponder");\r
357             ShibTargetFCGI stf(&request, content, g_ServerScheme.c_str(), g_ServerName.c_str(), g_ServerPort);\r
358           \r
359             pair<bool,long> res = stf.getServiceProvider().doHandler(stf);\r
360             if (res.first) {\r
361 #ifdef _DEBUG\r
362                 cerr << "shib: doHandler handled the request" << endl;\r
363 #endif\r
364                 switch(res.second) {\r
365                     case SHIB_RETURN_OK:\r
366                         print_ok();\r
367                         break;\r
368               \r
369                     case SHIB_RETURN_KO:\r
370                         cerr << "shib: doHandler failed to handle the request" << endl;\r
371                         print_error("<html><body>FastCGI Shibboleth responder should only be used for Shibboleth protocol requests.</body></html>");\r
372                         break;\r
373 \r
374                     case SHIB_RETURN_DONE:\r
375                         // response already handled\r
376                         break;\r
377               \r
378                     default:\r
379                         cerr << "shib: doHandler returned an unexpected result: " << res.second << endl;\r
380                         print_error("<html><body>FastCGI Shibboleth responder returned an unexpected result.</body></html>");\r
381                         break;\r
382                 }\r
383             }\r
384             else {\r
385                 cerr << "shib: doHandler failed to handle request." << endl;\r
386                 print_error("<html><body>FastCGI Shibboleth responder failed to process request.</body></html>");\r
387             }          \r
388           \r
389         }\r
390         catch (exception& e) {\r
391             cerr << "shib: FastCGI responder caught an exception: " << e.what() << endl;\r
392             print_error("<html><body>FastCGI Shibboleth responder caught an exception, check log for details.</body></html>");\r
393         }\r
394 \r
395         delete[] content;\r
396 \r
397         // If the output streambufs had non-zero bufsizes and\r
398         // were constructed outside of the accept loop (i.e.\r
399         // their destructor won't be called here), they would\r
400         // have to be flushed here.\r
401     }\r
402 \r
403     cout << "Request loop ended." << endl;\r
404 \r
405     cin.rdbuf(cin_streambuf);\r
406     cout.rdbuf(cout_streambuf);\r
407     cerr.rdbuf(cerr_streambuf);\r
408 \r
409     if (g_Config)\r
410         g_Config->term();\r
411  \r
412     return 0;\r
413 }\r