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