Reduce catch all handlers, and make them optional.
[shibboleth/cpp-sp.git] / fastcgi / shibauthorizer.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 /* shibauthorizer.cpp - Shibboleth FastCGI Authorizer\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 ShibTargetFCGIAuth : public ShibTarget\r
43 {\r
44     FCGX_Request* m_req;\r
45     string m_cookie;\r
46 public:\r
47     map<string,string> m_headers;\r
48 \r
49     ShibTargetFCGIAuth(FCGX_Request* req, const char* scheme=NULL, const char* hostname=NULL, int port=0) : m_req(req) {\r
50         const char* server_name_str = hostname;\r
51         if (!server_name_str || !*server_name_str)\r
52             server_name_str = FCGX_GetParam("SERVER_NAME", req->envp);\r
53 \r
54         int server_port = port;\r
55         if (!port) {\r
56             char* server_port_str = FCGX_GetParam("SERVER_PORT", req->envp);\r
57             server_port = strtol(server_port_str, &server_port_str, 10);\r
58             if (*server_port_str) {\r
59                 cerr << "can't parse SERVER_PORT (" << FCGX_GetParam("SERVER_PORT", req->envp) << ")" << endl;\r
60                 throw exception("Unable to determine server port.");\r
61             }\r
62         }\r
63 \r
64         const char* server_scheme_str = scheme;\r
65         if (!server_scheme_str || !*server_scheme_str)\r
66             server_scheme_str = (server_port == 443 || server_port == 8443) ? "https" : "http";\r
67 \r
68         const char* request_uri_str = FCGX_GetParam("REQUEST_URI", req->envp);\r
69         const char* content_type_str = FCGX_GetParam("CONTENT_TYPE", req->envp);\r
70         const char* remote_addr_str = FCGX_GetParam("REMOTE_ADDR", req->envp);\r
71         const char* request_method_str = FCGX_GetParam("REQUEST_METHOD", req->envp);\r
72 \r
73         init(server_scheme_str,\r
74              server_name_str,\r
75              server_port,\r
76              request_uri_str,\r
77              content_type_str ? content_type_str : "",\r
78              remote_addr_str,\r
79              request_method_str\r
80              );\r
81     }\r
82 \r
83     ~ShibTargetFCGIAuth() { }\r
84 \r
85     virtual void log(ShibLogLevel level, const string& msg) {\r
86         ShibTarget::log(level,msg);\r
87         if (level == LogLevelError)\r
88             cerr << "shib: " << msg;\r
89     }\r
90   \r
91     virtual string getCookies(void) const {\r
92         char* cookie = FCGX_GetParam("HTTP_COOKIE", m_req->envp);\r
93         return cookie ? cookie : "";\r
94     }\r
95   \r
96     virtual void setCookie(const string &name, const string &value) {\r
97         m_cookie += "Set-Cookie: " + name + "=" + value + "\r\n";\r
98     }\r
99 \r
100       virtual string getArgs(void) {\r
101         char* args = FCGX_GetParam("QUERY_STRING", m_req->envp);\r
102         return args ? args : "";\r
103     }\r
104 \r
105     virtual string getPostData(void) {\r
106         throw exception("getPostData not implemented by FastCGI authorizer.");\r
107     }\r
108 \r
109     virtual void clearHeader(const string& name) {\r
110         // no need, since request headers turn into actual environment variables\r
111     }\r
112   \r
113     virtual void setHeader(const string& name, const string &value) {\r
114         m_headers[name] = value;\r
115     }\r
116 \r
117     virtual string getHeader(const string& name) {\r
118         if (m_headers.find(name) != m_headers.end())\r
119             return m_headers[name];\r
120         else\r
121             return "";\r
122     }\r
123 \r
124     virtual void setRemoteUser(const string& user) {\r
125         m_headers["REMOTE_USER"] = user;\r
126     }\r
127 \r
128     virtual string getRemoteUser(void) {\r
129         if (m_headers.find("REMOTE_USER") != m_headers.end())\r
130             return m_headers["REMOTE_USER"];\r
131         else {\r
132             char* remote_user = FCGX_GetParam("REMOTE_USER", m_req->envp);\r
133             if (remote_user)\r
134                 return remote_user;\r
135         }\r
136         return "";\r
137     }\r
138 \r
139     virtual void* sendPage(\r
140         const string& msg,\r
141         int code=200,\r
142         const string& content_type="text/html",\r
143         const saml::Iterator<header_t>& headers=EMPTY(header_t)) {\r
144 \r
145         string hdr = m_cookie + "Connection: close\r\nContent-type: " + content_type + "\r\n";\r
146         while (headers.hasNext()) {\r
147             const header_t& h=headers.next();\r
148             hdr += h.first + ": " + h.second + "\r\n";\r
149         }\r
150 \r
151         // We can't return 200 OK here or else the filter is bypassed\r
152         // so custom Shib errors will get turned into a generic page.\r
153         const char* codestr="Status: 500 Server Error";\r
154         switch (code) {\r
155             case 403:   codestr="Status: 403 Forbidden"; break;\r
156             case 404:   codestr="Status: 404 Not Found"; break;\r
157         }\r
158 \r
159         cout << codestr << "\r\n" << hdr << "\r\n" << msg;\r
160         return (void*)SHIB_RETURN_DONE;\r
161     }\r
162 \r
163     virtual void* sendRedirect(const string& url) {\r
164         cout << "Status: 302 Please Wait" << "\r\n"\r
165              << "Location: " << url << "\r\n"\r
166              <<  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 static void print_ok(const map<string,string>& headers)\r
181 {\r
182     cout << "Status: 200 OK" << "\r\n";\r
183     for (map<string,string>::const_iterator iter = headers.begin(); iter != headers.end(); iter++) {\r
184         cout << "Variable-" << iter->first << ": " << iter->second << "\r\n";\r
185     }\r
186     cout << "\r\n";\r
187 }\r
188 \r
189 static void print_error(const char* msg)\r
190 {\r
191     cout << "Status: 500 Server Error" << "\r\n\r\n" << msg;\r
192 }\r
193 \r
194 int main(void)\r
195 {\r
196     char* shib_config = getenv("SHIB_CONFIG");\r
197     char* shib_schema = getenv("SHIB_SCHEMA");\r
198     if ((shib_config == NULL) || (shib_schema == NULL)) {\r
199       cerr << "SHIB_CONFIG or SHIB_SCHEMA not initialized!" << endl;\r
200       exit(1);\r
201     }\r
202     cerr << "SHIB_CONFIG = " << shib_config << endl\r
203          << "SHIB_SCHEMA = " << shib_schema << endl;\r
204 \r
205     string g_ServerScheme;\r
206     string g_ServerName;\r
207     int g_ServerPort = 0;\r
208     ShibTargetConfig* g_Config;\r
209 \r
210     try {\r
211         g_Config = &ShibTargetConfig::getConfig();\r
212         g_Config->setFeatures(\r
213             ShibTargetConfig::Listener |\r
214             ShibTargetConfig::Metadata |\r
215             ShibTargetConfig::AAP |\r
216             ShibTargetConfig::RequestMapper |\r
217             ShibTargetConfig::LocalExtensions |\r
218             ShibTargetConfig::Logging\r
219             );\r
220         if (!g_Config->init(shib_schema)) {\r
221             cerr << "failed to initialize Shibboleth libraries" << endl;\r
222             exit(1);\r
223         }\r
224         \r
225         if (!g_Config->load(shib_config)) {\r
226             cerr << "failed to load Shibboleth configuration" << endl;\r
227             exit(1);\r
228         }\r
229     }\r
230     catch (exception& e) {\r
231         cerr << "exception while initializing Shibboleth configuration: " << e.what() << endl;\r
232         exit(1);\r
233     }\r
234 \r
235     // Load "authoritative" URL fields.\r
236     char* var = getenv("SHIBSP_SERVER_NAME");\r
237     if (var)\r
238         g_ServerName = var;\r
239     var = getenv("SHIBSP_SERVER_SCHEME");\r
240     if (var)\r
241         g_ServerScheme = var;\r
242     var = getenv("SHIBSP_SERVER_PORT");\r
243     if (var)\r
244         g_ServerPort = atoi(var);\r
245 \r
246     streambuf* cout_streambuf = cout.rdbuf();\r
247     streambuf* cerr_streambuf = cerr.rdbuf();\r
248 \r
249     FCGX_Request request;\r
250 \r
251     FCGX_Init();\r
252     FCGX_InitRequest(&request, 0, 0);\r
253     \r
254     cout << "Shibboleth initialization complete. Starting request loop." << endl;\r
255     while (FCGX_Accept_r(&request) == 0)\r
256     {\r
257         // Note that the default bufsize (0) will cause the use of iostream\r
258         // methods that require positioning (such as peek(), seek(),\r
259         // unget() and putback()) to fail (in favour of more efficient IO).\r
260         fcgi_streambuf cout_fcgi_streambuf(request.out);\r
261         fcgi_streambuf cerr_fcgi_streambuf(request.err);\r
262 \r
263         cout.rdbuf(&cout_fcgi_streambuf);\r
264         cerr.rdbuf(&cerr_fcgi_streambuf);\r
265 \r
266         try {\r
267             saml::NDC ndc("FastCGI shibauthorizer");\r
268             ShibTargetFCGIAuth sta(&request, g_ServerScheme.c_str(), g_ServerName.c_str(), g_ServerPort);\r
269           \r
270             pair<bool,void*> res = sta.doCheckAuthN();\r
271             if (res.first) {\r
272 #ifdef _DEBUG\r
273                 cerr << "shib: doCheckAuthN handled the request" << endl;\r
274 #endif\r
275                 switch((long)res.second) {\r
276                     case SHIB_RETURN_OK:\r
277                         print_ok(sta.m_headers);\r
278                         continue;\r
279               \r
280                     case SHIB_RETURN_KO:\r
281                         print_ok(sta.m_headers);\r
282                         continue;\r
283 \r
284                     case SHIB_RETURN_DONE:\r
285                         continue;\r
286               \r
287                     default:\r
288                         cerr << "shib: doCheckAuthN returned an unexpected result: " << (long)res.second << endl;\r
289                         print_error("<html><body>FastCGI Shibboleth authorizer returned an unexpected result.</body></html>");\r
290                         continue;\r
291                 }\r
292             }\r
293           \r
294             res = sta.doExportAssertions();\r
295             if (res.first) {\r
296 #ifdef _DEBUG\r
297                 cerr << "shib: doExportAssertions handled request" << endl;\r
298 #endif\r
299                 switch((long)res.second) {\r
300                     case SHIB_RETURN_OK:\r
301                         print_ok(sta.m_headers);\r
302                         continue;\r
303               \r
304                     case SHIB_RETURN_KO:\r
305                         print_ok(sta.m_headers);\r
306                         continue;\r
307 \r
308                     case SHIB_RETURN_DONE:\r
309                         continue;\r
310               \r
311                     default:\r
312                         cerr << "shib: doExportAssertions returned an unexpected result: " << (long)res.second << endl;\r
313                         print_error("<html><body>FastCGI Shibboleth authorizer returned an unexpected result.</body></html>");\r
314                         continue;\r
315                 }\r
316             }\r
317 \r
318             res = sta.doCheckAuthZ();\r
319             if (res.first) {\r
320 #ifdef _DEBUG\r
321                 cerr << "shib: doCheckAuthZ handled request" << endl;\r
322 #endif\r
323                 switch((long)res.second) {\r
324                     case SHIB_RETURN_OK:\r
325                         print_ok(sta.m_headers);\r
326                         continue;\r
327               \r
328                     case SHIB_RETURN_KO:\r
329                         print_ok(sta.m_headers);\r
330                         continue;\r
331 \r
332                     case SHIB_RETURN_DONE:\r
333                         continue;\r
334               \r
335                     default:\r
336                         cerr << "shib: doCheckAuthZ returned an unexpected result: " << (long)res.second << endl;\r
337                         print_error("<html><body>FastCGI Shibboleth authorizer returned an unexpected result.</body></html>");\r
338                         continue;\r
339                 }\r
340             }\r
341 \r
342             print_ok(sta.m_headers);\r
343           \r
344         }\r
345         catch (exception& e) {\r
346             cerr << "shib: FastCGI authorizer caught an exception: " << e.what() << endl;\r
347             print_error("<html><body>FastCGI Shibboleth authorizer caught an exception, check log for details.</body></html>");\r
348         }\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     cout << "Request loop ended." << endl;\r
356 \r
357     cout.rdbuf(cout_streambuf);\r
358     cerr.rdbuf(cerr_streambuf);\r
359 \r
360     if (g_Config)\r
361         g_Config->shutdown();\r
362  \r
363     return 0;\r
364 }\r