Switch exception class, seems to fail on some gcc versions.
[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 // SAML Runtime\r
23 #include <saml/saml.h>\r
24 #include <shib-target/shib-target.h>\r
25 \r
26 #include <stdlib.h>\r
27 #ifdef HAVE_UNISTD_H\r
28 # include <unistd.h>\r
29 # include <sys/mman.h>\r
30 #endif\r
31 #include <fcgio.h>\r
32 \r
33 using namespace shibtarget;\r
34 using namespace std;\r
35 \r
36 typedef enum {\r
37     SHIB_RETURN_OK,\r
38     SHIB_RETURN_KO,\r
39     SHIB_RETURN_DONE\r
40 } shib_return_t;\r
41 \r
42 class ShibTargetFCGI : public ShibTarget\r
43 {\r
44     FCGX_Request* m_req;\r
45     char* m_body;\r
46     string m_cookie;\r
47     map<string, string> m_headers;\r
48 \r
49 public:\r
50     ShibTargetFCGI(FCGX_Request* req, char* post_data, const char* scheme=NULL, const char* hostname=NULL, int port=0)\r
51         : m_req(req), m_body(post_data) {\r
52 \r
53         const char* server_name_str = hostname;\r
54         if (!server_name_str || !*server_name_str)\r
55             server_name_str = FCGX_GetParam("SERVER_NAME", req->envp);\r
56 \r
57         int server_port = port;\r
58         if (!port) {\r
59             char* server_port_str = FCGX_GetParam("SERVER_PORT", req->envp);\r
60             server_port = strtol(server_port_str, &server_port_str, 10);\r
61             if (*server_port_str) {\r
62                 cerr << "can't parse SERVER_PORT (" << FCGX_GetParam("SERVER_PORT", req->envp) << ")" << endl;\r
63                 throw exception("Unable to determine server port.");\r
64             }\r
65         }\r
66 \r
67         const char* server_scheme_str = scheme;\r
68         if (!server_scheme_str || !*server_scheme_str)\r
69             server_scheme_str = (server_port == 443 || server_port == 8443) ? "https" : "http";\r
70 \r
71         const char* request_uri_str = FCGX_GetParam("REQUEST_URI", req->envp);\r
72         const char* content_type_str = FCGX_GetParam("CONTENT_TYPE", req->envp);\r
73         const char* remote_addr_str = FCGX_GetParam("REMOTE_ADDR", req->envp);\r
74         const char* request_method_str = FCGX_GetParam("REQUEST_METHOD", req->envp);\r
75 \r
76 #ifdef _DEBUG\r
77         cerr << "server_name = " << server_name_str << endl\r
78              << "server_port = " << server_port << endl\r
79              << "request_uri_str = " << request_uri_str << endl\r
80              << "content_type = " << content_type_str << endl\r
81              << "remote_address = " << remote_addr_str << endl\r
82              << "request_method = " << request_method_str << endl;\r
83 #endif\r
84 \r
85         init(server_scheme_str,\r
86              server_name_str,\r
87              server_port,\r
88              request_uri_str,\r
89              content_type_str ? content_type_str : "",\r
90              remote_addr_str,\r
91              request_method_str\r
92              );\r
93     }\r
94 \r
95     ~ShibTargetFCGI() { }\r
96 \r
97     virtual void log(ShibLogLevel level, const string& msg) {\r
98         ShibTarget::log(level,msg);\r
99     \r
100         if (level == LogLevelError)\r
101             cerr << "shib: " << msg;\r
102     }\r
103   \r
104     virtual string getCookies(void) const {\r
105         char * cookie = FCGX_GetParam("HTTP_COOKIE", m_req->envp);\r
106         return cookie ? cookie : "";\r
107     }\r
108   \r
109     virtual void setCookie(const string& name, const string& value) {\r
110         m_cookie += "Set-Cookie: " + name + "=" + value + "\r\n";\r
111     }\r
112 \r
113     virtual string getArgs(void) {\r
114         char * args = FCGX_GetParam("QUERY_STRING", m_req->envp);\r
115         return args ? args : "";\r
116     }\r
117 \r
118     virtual string getPostData(void) {\r
119         return m_body ? m_body : "";\r
120     }\r
121 \r
122     virtual void clearHeader(const string &name) {\r
123         throw runtime_error("clearHeader not implemented by FastCGI responder.");\r
124     }\r
125   \r
126     virtual void setHeader(const string &name, const string &value) {\r
127         throw runtime_error("setHeader not implemented by FastCGI responder.");\r
128     }\r
129 \r
130     virtual string getHeader(const string &name) {\r
131         throw runtime_error("getHeader not implemented by FastCGI responder.");\r
132     }\r
133 \r
134     virtual void setRemoteUser(const string &user) {\r
135         throw runtime_error("setRemoteUser not implemented by FastCGI responder.");\r
136     }\r
137 \r
138     virtual string getRemoteUser(void) {\r
139         throw runtime_error("getRemoteUser not implemented by FastCGI responder.");\r
140     }\r
141 \r
142     virtual void* sendPage(\r
143         const string& msg,\r
144         int code=200,\r
145         const string& content_type="text/html",\r
146         const saml::Iterator<header_t>& headers=EMPTY(header_t)) {\r
147 \r
148         string hdr = string ("Connection: close\r\nContent-type: ") + content_type + "\r\n" + m_cookie;\r
149         while (headers.hasNext()) {\r
150             const header_t& h=headers.next();\r
151             hdr += h.first + ": " + h.second + "\r\n";\r
152         }\r
153 \r
154         const char* codestr="Status: 200 OK";\r
155         switch (code) {\r
156             case 500:   codestr="Status: 500 Server Error"; break;\r
157             case 403:   codestr="Status: 403 Forbidden"; break;\r
158             case 404:   codestr="Status: 404 Not Found"; break;\r
159         }\r
160 \r
161         cout << codestr << "\r\n" << hdr << m_cookie << "\r\n" << msg;\r
162         return (void*)SHIB_RETURN_DONE;\r
163     }\r
164 \r
165     virtual void* sendRedirect(const string& url) {\r
166         cout << "Status: 302 Please Wait" << "\r\n" << "Location: " << url << "\r\n" << m_cookie << "\r\n"\r
167             << "<HTML><BODY>Redirecting...</BODY></HTML>";\r
168         return (void*)SHIB_RETURN_DONE;\r
169     }\r
170 \r
171     virtual void* returnDecline(void) { \r
172         return (void*)SHIB_RETURN_KO;\r
173     }\r
174 \r
175     virtual void* returnOK(void) {\r
176         return (void*)SHIB_RETURN_OK;\r
177     }\r
178 };\r
179 \r
180 // Maximum number of bytes allowed to be read from stdin\r
181 static const unsigned long STDIN_MAX = 1000000;\r
182 \r
183 static long gstdin(FCGX_Request* request, char** content)\r
184 {\r
185     char* clenstr = FCGX_GetParam("CONTENT_LENGTH", request->envp);\r
186     unsigned long clen = STDIN_MAX;\r
187 \r
188     if (clenstr) {\r
189         clen = strtol(clenstr, &clenstr, 10);\r
190         if (*clenstr) {\r
191             cerr << "can't parse CONTENT_LENGTH (" << FCGX_GetParam("CONTENT_LENGTH", request->envp) << ")" << endl;\r
192             clen = STDIN_MAX;\r
193         }\r
194 \r
195         // *always* put a cap on the amount of data that will be read\r
196         if (clen > STDIN_MAX)\r
197             clen = STDIN_MAX;\r
198 \r
199         *content = new char[clen];\r
200 \r
201         cin.read(*content, clen);\r
202         clen = cin.gcount();\r
203     }\r
204     else {\r
205         // *never* read stdin when CONTENT_LENGTH is missing or unparsable\r
206         *content = 0;\r
207         clen = 0;\r
208     }\r
209 \r
210     // Chew up any remaining stdin - this shouldn't be necessary\r
211     // but is because mod_fastcgi doesn't handle it correctly.\r
212 \r
213     // ignore() doesn't set the eof bit in some versions of glibc++\r
214     // so use gcount() instead of eof()...\r
215     do cin.ignore(1024); while (cin.gcount() == 1024);\r
216 \r
217     return clen;\r
218 }\r
219 \r
220 static void print_ok() {\r
221     cout << "Status: 200 OK" << "\r\n\r\n";\r
222 }\r
223 \r
224 static void print_error(const char* msg) {\r
225     cout << "Status: 500 Server Error" << "\r\n\r\n" << msg;\r
226 }\r
227 \r
228 int main(void)\r
229 {\r
230     char* shib_config = getenv("SHIB_CONFIG");\r
231     char* shib_schema = getenv("SHIB_SCHEMA");\r
232     if ((shib_config == NULL) || (shib_schema == NULL)) {\r
233         cerr << "SHIB_CONFIG or SHIB_SCHEMA not set." << endl;\r
234         exit(1);\r
235     }\r
236     cerr << "SHIB_CONFIG = " << shib_config << endl\r
237          << "SHIB_SCHEMA = " << shib_schema << endl;\r
238 \r
239     string g_ServerScheme;\r
240     string g_ServerName;\r
241     int g_ServerPort=0;\r
242     ShibTargetConfig* g_Config;\r
243 \r
244     try {\r
245         g_Config = &ShibTargetConfig::getConfig();\r
246         g_Config->setFeatures(\r
247             ShibTargetConfig::Listener |\r
248             ShibTargetConfig::Metadata |\r
249             ShibTargetConfig::RequestMapper |\r
250             ShibTargetConfig::LocalExtensions |\r
251             ShibTargetConfig::Logging\r
252             );\r
253         if (!g_Config->init(shib_schema)) {\r
254             cerr << "failed to initialize Shibboleth libraries" << endl;\r
255             exit(1);\r
256         }\r
257         \r
258         if (!g_Config->load(shib_config)) {\r
259             cerr << "failed to load Shibboleth configuration" << endl;\r
260             exit(1);\r
261         }\r
262     }\r
263     catch (exception& e) {\r
264         cerr << "exception while initializing Shibboleth configuration:" << e.what() << endl;\r
265         exit(1);\r
266     }\r
267 \r
268     // Load "authoritative" URL fields.\r
269     char* var = getenv("SHIBSP_SERVER_NAME");\r
270     if (var)\r
271         g_ServerName = var;\r
272     var = getenv("SHIBSP_SERVER_SCHEME");\r
273     if (var)\r
274         g_ServerScheme = var;\r
275     var = getenv("SHIBSP_SERVER_PORT");\r
276     if (var)\r
277         g_ServerPort = atoi(var);\r
278 \r
279     streambuf* cin_streambuf  = cin.rdbuf();\r
280     streambuf* cout_streambuf = cout.rdbuf();\r
281     streambuf* cerr_streambuf = cerr.rdbuf();\r
282 \r
283     FCGX_Request request;\r
284 \r
285     FCGX_Init();\r
286     FCGX_InitRequest(&request, 0, 0);\r
287     \r
288     cout << "Shibboleth initialization complete. Starting request loop." << endl;\r
289     while (FCGX_Accept_r(&request) == 0) {\r
290         // Note that the default bufsize (0) will cause the use of iostream\r
291         // methods that require positioning (such as peek(), seek(),\r
292         // unget() and putback()) to fail (in favour of more efficient IO).\r
293         fcgi_streambuf cin_fcgi_streambuf(request.in);\r
294         fcgi_streambuf cout_fcgi_streambuf(request.out);\r
295         fcgi_streambuf cerr_fcgi_streambuf(request.err);\r
296 \r
297         cin.rdbuf(&cin_fcgi_streambuf);\r
298         cout.rdbuf(&cout_fcgi_streambuf);\r
299         cerr.rdbuf(&cerr_fcgi_streambuf);\r
300 \r
301         // Although FastCGI supports writing before reading,\r
302         // many http clients (browsers) don't support it (so\r
303         // the connection deadlocks until a timeout expires!).\r
304         char* content;\r
305         gstdin(&request, &content);\r
306 \r
307         try {\r
308             saml::NDC ndc("FastCGI shibresponder");\r
309             ShibTargetFCGI stf(&request, content, g_ServerScheme.c_str(), g_ServerName.c_str(), g_ServerPort);\r
310           \r
311             pair<bool,void*> res = stf.doHandler();\r
312             if (res.first) {\r
313 #ifdef _DEBUG\r
314                 cerr << "shib: doHandler handled the request" << endl;\r
315 #endif\r
316                 switch((long)res.second) {\r
317                     case SHIB_RETURN_OK:\r
318                         print_ok();\r
319                         break;\r
320               \r
321                     case SHIB_RETURN_KO:\r
322                         cerr << "shib: doHandler failed to handle the request" << endl;\r
323                         print_error("<html><body>FastCGI Shibboleth responder should only be used for Shibboleth protocol requests.</body></html>");\r
324                         break;\r
325 \r
326                     case SHIB_RETURN_DONE:\r
327                         // response already handled\r
328                         break;\r
329               \r
330                     default:\r
331                         cerr << "shib: doHandler returned an unexpected result: " << (long)res.second << endl;\r
332                         print_error("<html><body>FastCGI Shibboleth responder returned an unexpected result.</body></html>");\r
333                         break;\r
334                 }\r
335             }\r
336             else {\r
337                 cerr << "shib: doHandler failed to handle request." << endl;\r
338                 print_error("<html><body>FastCGI Shibboleth responder failed to process request.</body></html>");\r
339             }          \r
340           \r
341         }\r
342         catch (exception& e) {\r
343             cerr << "shib: FastCGI responder caught an exception: " << e.what() << endl;\r
344             print_error("<html><body>FastCGI Shibboleth responder caught an exception, check log for details.</body></html>");\r
345         }\r
346 \r
347         delete[] content;\r
348 \r
349         // If the output streambufs had non-zero bufsizes and\r
350         // were constructed outside of the accept loop (i.e.\r
351         // their destructor won't be called here), they would\r
352         // have to be flushed here.\r
353     }\r
354 \r
355     cout << "Request loop ended." << endl;\r
356 \r
357     cin.rdbuf(cin_streambuf);\r
358     cout.rdbuf(cout_streambuf);\r
359     cerr.rdbuf(cerr_streambuf);\r
360 \r
361     if (g_Config)\r
362         g_Config->shutdown();\r
363  \r
364     return 0;\r
365 }\r