Path resolution for error templates.
[shibboleth/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 <stdexcept>\r
40 #include <stdlib.h>\r
41 #ifdef HAVE_UNISTD_H\r
42 # include <unistd.h>\r
43 # include <sys/mman.h>\r
44 #endif\r
45 #include <fcgio.h>\r
46 \r
47 using namespace shibsp;\r
48 using namespace xmltooling;\r
49 using namespace xercesc;\r
50 using namespace std;\r
51 \r
52 static const XMLCh path[] =     UNICODE_LITERAL_4(p,a,t,h);\r
53 static const XMLCh validate[] = UNICODE_LITERAL_8(v,a,l,i,d,a,t,e);\r
54 \r
55 typedef enum {\r
56     SHIB_RETURN_OK,\r
57     SHIB_RETURN_KO,\r
58     SHIB_RETURN_DONE\r
59 } shib_return_t;\r
60 \r
61 class ShibTargetFCGI : public AbstractSPRequest\r
62 {\r
63     FCGX_Request* m_req;\r
64     const char* m_body;\r
65     multimap<string,string> m_headers;\r
66     int m_port;\r
67     string m_scheme,m_hostname;\r
68 \r
69 public:\r
70     ShibTargetFCGI(FCGX_Request* req, char* post_data, const char* scheme=NULL, const char* hostname=NULL, int port=0)\r
71         : AbstractSPRequest(SHIBSP_LOGCAT".FastCGI"), m_req(req), m_body(post_data) {\r
72 \r
73         const char* server_name_str = hostname;\r
74         if (!server_name_str || !*server_name_str)\r
75             server_name_str = FCGX_GetParam("SERVER_NAME", req->envp);\r
76         m_hostname = server_name_str;\r
77 \r
78         m_port = port;\r
79         if (!m_port) {\r
80             char* server_port_str = FCGX_GetParam("SERVER_PORT", req->envp);\r
81             m_port = strtol(server_port_str, &server_port_str, 10);\r
82             if (*server_port_str) {\r
83                 cerr << "can't parse SERVER_PORT (" << FCGX_GetParam("SERVER_PORT", req->envp) << ")" << endl;\r
84                 throw runtime_error("Unable to determine server port.");\r
85             }\r
86         }\r
87 \r
88         const char* server_scheme_str = scheme;\r
89         if (!server_scheme_str || !*server_scheme_str)\r
90             server_scheme_str = (m_port == 443 || m_port == 8443) ? "https" : "http";\r
91         m_scheme = server_scheme_str;\r
92 \r
93         setRequestURI(FCGX_GetParam("REQUEST_URI", m_req->envp));\r
94     }\r
95 \r
96     ~ShibTargetFCGI() { }\r
97 \r
98     const char* getScheme() const {\r
99         return m_scheme.c_str();\r
100     }\r
101     const char* getHostname() const {\r
102         return m_hostname.c_str();\r
103     }\r
104     int getPort() const {\r
105         return m_port;\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_UNAUTHORIZED:   codestr="Status: 401 Authorization Required"; break;\r
169             case XMLTOOLING_HTTP_STATUS_FORBIDDEN:      codestr="Status: 403 Forbidden"; break;\r
170             case XMLTOOLING_HTTP_STATUS_NOTFOUND:       codestr="Status: 404 Not Found"; break;\r
171         }\r
172         cout << codestr << "\r\n" << hdr << "\r\n";\r
173         char buf[1024];\r
174         while (in) {\r
175             in.read(buf,1024);\r
176             cout.write(buf, in.gcount());\r
177         }\r
178         return SHIB_RETURN_DONE;\r
179     }\r
180 \r
181     long sendRedirect(const char* url) {\r
182         string hdr=string("Status: 302 Please Wait\r\nLocation: ") + url + "\r\n"\r
183           "Content-Type: text/html\r\n"\r
184           "Content-Length: 40\r\n"\r
185           "Expires: 01-Jan-1997 12:00:00 GMT\r\n"\r
186           "Cache-Control: private,no-store,no-cache\r\n";\r
187         for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)\r
188             hdr += i->first + ": " + i->second + "\r\n";\r
189         hdr += "\r\n";\r
190 \r
191         cout << hdr << "<HTML><BODY>Redirecting...</BODY></HTML>";\r
192         return SHIB_RETURN_DONE;\r
193     }\r
194 \r
195     long returnDecline() {\r
196         return SHIB_RETURN_KO;\r
197     }\r
198     long returnOK() {\r
199         return SHIB_RETURN_OK;\r
200     }\r
201 \r
202     const vector<string>& getClientCertificates() const {\r
203         static vector<string> g_NoCerts;\r
204         return g_NoCerts;\r
205     }\r
206 \r
207     // Not used in the extension.\r
208 \r
209     virtual void clearHeader(const char* rawname, const char* cginame) {\r
210         throw runtime_error("clearHeader not implemented by FastCGI responder.");\r
211     }\r
212   \r
213     virtual void setHeader(const char* name, const char* value) {\r
214         throw runtime_error("setHeader not implemented by FastCGI responder.");\r
215     }\r
216 \r
217     virtual void setRemoteUser(const char* user) {\r
218         throw runtime_error("setRemoteUser not implemented by FastCGI responder.");\r
219     }\r
220 };\r
221 \r
222 // Maximum number of bytes allowed to be read from stdin\r
223 static const unsigned long STDIN_MAX = 1000000;\r
224 \r
225 static long gstdin(FCGX_Request* request, char** content)\r
226 {\r
227     char* clenstr = FCGX_GetParam("CONTENT_LENGTH", request->envp);\r
228     unsigned long clen = STDIN_MAX;\r
229 \r
230     if (clenstr) {\r
231         clen = strtol(clenstr, &clenstr, 10);\r
232         if (*clenstr) {\r
233             cerr << "can't parse CONTENT_LENGTH (" << FCGX_GetParam("CONTENT_LENGTH", request->envp) << ")" << endl;\r
234             clen = STDIN_MAX;\r
235         }\r
236 \r
237         // *always* put a cap on the amount of data that will be read\r
238         if (clen > STDIN_MAX)\r
239             clen = STDIN_MAX;\r
240 \r
241         *content = new char[clen];\r
242 \r
243         cin.read(*content, clen);\r
244         clen = cin.gcount();\r
245     }\r
246     else {\r
247         // *never* read stdin when CONTENT_LENGTH is missing or unparsable\r
248         *content = 0;\r
249         clen = 0;\r
250     }\r
251 \r
252     // Chew up any remaining stdin - this shouldn't be necessary\r
253     // but is because mod_fastcgi doesn't handle it correctly.\r
254 \r
255     // ignore() doesn't set the eof bit in some versions of glibc++\r
256     // so use gcount() instead of eof()...\r
257     do cin.ignore(1024); while (cin.gcount() == 1024);\r
258 \r
259     return clen;\r
260 }\r
261 \r
262 static void print_ok() {\r
263     cout << "Status: 200 OK" << "\r\n\r\n";\r
264 }\r
265 \r
266 static void print_error(const char* msg) {\r
267     cout << "Status: 500 Server Error" << "\r\n\r\n" << msg;\r
268 }\r
269 \r
270 int main(void)\r
271 {\r
272     SPConfig* g_Config=&SPConfig::getConfig();\r
273     g_Config->setFeatures(\r
274         SPConfig::Listener |\r
275         SPConfig::Caching |\r
276         SPConfig::RequestMapping |\r
277         SPConfig::InProcess |\r
278         SPConfig::Logging |\r
279         SPConfig::Handlers\r
280         );\r
281     if (!g_Config->init()) {\r
282         cerr << "failed to initialize Shibboleth libraries" << endl;\r
283         exit(1);\r
284     }\r
285 \r
286     const char* config=getenv("SHIBSP_CONFIG");\r
287     if (!config)\r
288         config=SHIBSP_CONFIG;\r
289 \r
290     try {\r
291         DOMDocument* dummydoc=XMLToolingConfig::getConfig().getParser().newDocument();\r
292         XercesJanitor<DOMDocument> docjanitor(dummydoc);\r
293         DOMElement* dummy = dummydoc->createElementNS(NULL,path);\r
294         auto_ptr_XMLCh src(config);\r
295         dummy->setAttributeNS(NULL,path,src.get());\r
296         dummy->setAttributeNS(NULL,validate,xmlconstants::XML_ONE);\r
297 \r
298         g_Config->setServiceProvider(g_Config->ServiceProviderManager.newPlugin(XML_SERVICE_PROVIDER,dummy));\r
299         g_Config->getServiceProvider()->init();\r
300     }\r
301     catch (exception& ex) {\r
302         g_Config->term();\r
303         cerr << "exception while initializing Shibboleth configuration: " << ex.what() << endl;\r
304         exit(1);\r
305     }\r
306 \r
307     string g_ServerScheme;\r
308     string g_ServerName;\r
309     int g_ServerPort=0;\r
310 \r
311     // Load "authoritative" URL fields.\r
312     char* var = getenv("SHIBSP_SERVER_NAME");\r
313     if (var)\r
314         g_ServerName = var;\r
315     var = getenv("SHIBSP_SERVER_SCHEME");\r
316     if (var)\r
317         g_ServerScheme = var;\r
318     var = getenv("SHIBSP_SERVER_PORT");\r
319     if (var)\r
320         g_ServerPort = atoi(var);\r
321 \r
322     streambuf* cin_streambuf  = cin.rdbuf();\r
323     streambuf* cout_streambuf = cout.rdbuf();\r
324     streambuf* cerr_streambuf = cerr.rdbuf();\r
325 \r
326     FCGX_Request request;\r
327 \r
328     FCGX_Init();\r
329     FCGX_InitRequest(&request, 0, 0);\r
330     \r
331     cout << "Shibboleth initialization complete. Starting request loop." << endl;\r
332     while (FCGX_Accept_r(&request) == 0) {\r
333         // Note that the default bufsize (0) will cause the use of iostream\r
334         // methods that require positioning (such as peek(), seek(),\r
335         // unget() and putback()) to fail (in favour of more efficient IO).\r
336         fcgi_streambuf cin_fcgi_streambuf(request.in);\r
337         fcgi_streambuf cout_fcgi_streambuf(request.out);\r
338         fcgi_streambuf cerr_fcgi_streambuf(request.err);\r
339 \r
340         cin.rdbuf(&cin_fcgi_streambuf);\r
341         cout.rdbuf(&cout_fcgi_streambuf);\r
342         cerr.rdbuf(&cerr_fcgi_streambuf);\r
343 \r
344         // Although FastCGI supports writing before reading,\r
345         // many http clients (browsers) don't support it (so\r
346         // the connection deadlocks until a timeout expires!).\r
347         char* content;\r
348         gstdin(&request, &content);\r
349 \r
350         try {\r
351             xmltooling::NDC ndc("FastCGI shibresponder");\r
352             ShibTargetFCGI stf(&request, content, g_ServerScheme.c_str(), g_ServerName.c_str(), g_ServerPort);\r
353           \r
354             pair<bool,long> res = stf.getServiceProvider().doHandler(stf);\r
355             if (res.first) {\r
356 #ifdef _DEBUG\r
357                 cerr << "shib: doHandler handled the request" << endl;\r
358 #endif\r
359                 switch(res.second) {\r
360                     case SHIB_RETURN_OK:\r
361                         print_ok();\r
362                         break;\r
363               \r
364                     case SHIB_RETURN_KO:\r
365                         cerr << "shib: doHandler failed to handle the request" << endl;\r
366                         print_error("<html><body>FastCGI Shibboleth responder should only be used for Shibboleth protocol requests.</body></html>");\r
367                         break;\r
368 \r
369                     case SHIB_RETURN_DONE:\r
370                         // response already handled\r
371                         break;\r
372               \r
373                     default:\r
374                         cerr << "shib: doHandler returned an unexpected result: " << res.second << endl;\r
375                         print_error("<html><body>FastCGI Shibboleth responder returned an unexpected result.</body></html>");\r
376                         break;\r
377                 }\r
378             }\r
379             else {\r
380                 cerr << "shib: doHandler failed to handle request." << endl;\r
381                 print_error("<html><body>FastCGI Shibboleth responder failed to process request.</body></html>");\r
382             }          \r
383           \r
384         }\r
385         catch (exception& e) {\r
386             cerr << "shib: FastCGI responder caught an exception: " << e.what() << endl;\r
387             print_error("<html><body>FastCGI Shibboleth responder caught an exception, check log for details.</body></html>");\r
388         }\r
389 \r
390         delete[] content;\r
391 \r
392         // If the output streambufs had non-zero bufsizes and\r
393         // were constructed outside of the accept loop (i.e.\r
394         // their destructor won't be called here), they would\r
395         // have to be flushed here.\r
396     }\r
397 \r
398     cout << "Request loop ended." << endl;\r
399 \r
400     cin.rdbuf(cin_streambuf);\r
401     cout.rdbuf(cout_streambuf);\r
402     cerr.rdbuf(cerr_streambuf);\r
403 \r
404     if (g_Config)\r
405         g_Config->term();\r
406  \r
407     return 0;\r
408 }\r