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