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