Ensure "controlled" headers are never pulled from the request.
[shibboleth/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 #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 ShibTargetFCGIAuth : public AbstractSPRequest\r
62 {\r
63     FCGX_Request* m_req;\r
64     int m_port;\r
65     string m_scheme,m_hostname;\r
66     set<string> m_cleared_headers;\r
67     multimap<string,string> m_response_headers;\r
68 public:\r
69     map<string,string> m_request_headers;\r
70 \r
71     ShibTargetFCGIAuth(FCGX_Request* req, const char* scheme=NULL, const char* hostname=NULL, int port=0)\r
72             : AbstractSPRequest(SHIBSP_LOGCAT".FastCGI"), m_req(req) {\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         setRequestURI(FCGX_GetParam("REQUEST_URI", m_req->envp));\r
94     }\r
95 \r
96     ~ShibTargetFCGIAuth() { }\r
97 \r
98     const char* getScheme() const {\r
99         return m_scheme.c_str();\r
100     }\r
101     const char* getHostname() const {\r
102         return m_hostname.c_str();\r
103     }\r
104     int getPort() const {\r
105         return m_port;\r
106     }\r
107     const char* getMethod() const {\r
108         return FCGX_GetParam("REQUEST_METHOD", m_req->envp);\r
109     }\r
110     string getContentType() const {\r
111         const char* s = FCGX_GetParam("CONTENT_TYPE", m_req->envp);\r
112         return s ? s : "";\r
113     }\r
114     long getContentLength() const {\r
115         const char* s = FCGX_GetParam("CONTENT_LENGTH", m_req->envp);\r
116         return s ? atol(s) : 0;\r
117     }\r
118     string getRemoteAddr() const {\r
119         const char* s = FCGX_GetParam("REMOTE_ADDR", m_req->envp);\r
120         return s ? s : "";\r
121     }\r
122     void log(SPLogLevel level, const string& msg) const {\r
123         AbstractSPRequest::log(level,msg);\r
124         if (level >= SPError)\r
125             cerr << "shib: " << msg;\r
126     }\r
127     void clearHeader(const char* rawname, const char* cginame) {\r
128         // Need to save off the name to prevent access to the header later.\r
129         m_cleared_headers.insert(rawname);\r
130     }\r
131     void setHeader(const char* name, const char* value) {\r
132         if (value)\r
133             m_request_headers[name] = value;\r
134         else\r
135             m_request_headers.erase(name);\r
136     }\r
137     virtual string getHeader(const char* name) const {\r
138         // Look in the local map first.\r
139         map<string,string>::const_iterator i = m_request_headers.find(name);\r
140         if (i != m_request_headers.end())\r
141             return i->second;\r
142         // If not in the local set, see if it's a "controlled" header by\r
143         // checking the cleared list.\r
144         if (m_cleared_headers.count(name) > 0)\r
145             return "";\r
146         // Nothing set locally and it's safe, so try the request.\r
147         string hdr("HTTP_");\r
148         for (; *name; ++name) {\r
149             if (*name=='-')\r
150                 hdr += '_';\r
151             else\r
152                 hdr += toupper(*name);\r
153         }\r
154         char* s = FCGX_GetParam(hdr.c_str(), m_req->envp);\r
155         return s ? s : "";\r
156     }\r
157     void setRemoteUser(const char* user) {\r
158         if (user)\r
159             m_request_headers["REMOTE_USER"] = user;\r
160         else\r
161             m_request_headers.erase("REMOTE_USER");\r
162     }\r
163     string getRemoteUser() const {\r
164         map<string,string>::const_iterator i = m_request_headers.find("REMOTE_USER");\r
165         if (i != m_request_headers.end())\r
166             return i->second;\r
167         else {\r
168             char* remote_user = FCGX_GetParam("REMOTE_USER", m_req->envp);\r
169             if (remote_user)\r
170                 return remote_user;\r
171         }\r
172         return "";\r
173     }\r
174     void setResponseHeader(const char* name, const char* value) {\r
175         // Set for later.\r
176         if (value)\r
177             m_response_headers.insert(make_pair(name,value));\r
178         else\r
179             m_response_headers.erase(name);\r
180     }\r
181     const char* getQueryString() const {\r
182         return FCGX_GetParam("QUERY_STRING", m_req->envp);\r
183     }\r
184     const char* getRequestBody() const {\r
185         throw runtime_error("getRequestBody not implemented by FastCGI authorizer.");\r
186     }\r
187 \r
188     long sendResponse(istream& in, long status) {\r
189         string hdr = string("Connection: close\r\n");\r
190         for (multimap<string,string>::const_iterator i=m_response_headers.begin(); i!=m_response_headers.end(); ++i)\r
191             hdr += i->first + ": " + i->second + "\r\n";\r
192 \r
193         // We can't return 200 OK here or else the filter is bypassed\r
194         // so custom Shib errors will get turned into a generic page.\r
195         const char* codestr="Status: 500 Server Error";\r
196         switch (status) {\r
197             case XMLTOOLING_HTTP_STATUS_UNAUTHORIZED:   codestr="Status: 401 Authorization Required"; break;\r
198             case XMLTOOLING_HTTP_STATUS_FORBIDDEN:      codestr="Status: 403 Forbidden"; break;\r
199             case XMLTOOLING_HTTP_STATUS_NOTFOUND:       codestr="Status: 404 Not Found"; break;\r
200         }\r
201         cout << codestr << "\r\n" << hdr << "\r\n";\r
202         char buf[1024];\r
203         while (in) {\r
204             in.read(buf,1024);\r
205             cout.write(buf, in.gcount());\r
206         }\r
207         return SHIB_RETURN_DONE;\r
208     }\r
209 \r
210     long sendRedirect(const char* url) {\r
211         string hdr=string("Status: 302 Please Wait\r\nLocation: ") + url + "\r\n"\r
212           "Content-Type: text/html\r\n"\r
213           "Content-Length: 40\r\n"\r
214           "Expires: 01-Jan-1997 12:00:00 GMT\r\n"\r
215           "Cache-Control: private,no-store,no-cache\r\n";\r
216         for (multimap<string,string>::const_iterator i=m_response_headers.begin(); i!=m_response_headers.end(); ++i)\r
217             hdr += i->first + ": " + i->second + "\r\n";\r
218         hdr += "\r\n";\r
219 \r
220         cout << hdr << "<HTML><BODY>Redirecting...</BODY></HTML>";\r
221         return SHIB_RETURN_DONE;\r
222     }\r
223 \r
224     long returnDecline() {\r
225         return SHIB_RETURN_KO;\r
226     }\r
227 \r
228     long returnOK() {\r
229         return SHIB_RETURN_OK;\r
230     }\r
231 \r
232     const vector<string>& getClientCertificates() const {\r
233         static vector<string> g_NoCerts;\r
234         return g_NoCerts;\r
235     }\r
236 };\r
237 \r
238 static void print_ok(const map<string,string>& headers)\r
239 {\r
240     cout << "Status: 200 OK" << "\r\n";\r
241     for (map<string,string>::const_iterator iter = headers.begin(); iter != headers.end(); iter++) {\r
242         cout << "Variable-" << iter->first << ": " << iter->second << "\r\n";\r
243     }\r
244     cout << "\r\n";\r
245 }\r
246 \r
247 static void print_error(const char* msg)\r
248 {\r
249     cout << "Status: 500 Server Error" << "\r\n\r\n" << msg;\r
250 }\r
251 \r
252 int main(void)\r
253 {\r
254     SPConfig* g_Config=&SPConfig::getConfig();\r
255     g_Config->setFeatures(\r
256         SPConfig::Listener |\r
257         SPConfig::Caching |\r
258         SPConfig::RequestMapping |\r
259         SPConfig::InProcess |\r
260         SPConfig::Logging |\r
261         SPConfig::Handlers\r
262         );\r
263     if (!g_Config->init()) {\r
264         cerr << "failed to initialize Shibboleth libraries" << endl;\r
265         exit(1);\r
266     }\r
267 \r
268     try {\r
269         if (!g_Config->instantiate(NULL, true))\r
270             throw exception("unknown error");\r
271     }\r
272     catch (exception& ex) {\r
273         g_Config->term();\r
274         cerr << "exception while initializing Shibboleth configuration: " << ex.what() << endl;\r
275         exit(1);\r
276     }\r
277 \r
278     string g_ServerScheme;\r
279     string g_ServerName;\r
280     int g_ServerPort=0;\r
281 \r
282     // Load "authoritative" URL fields.\r
283     char* var = getenv("SHIBSP_SERVER_NAME");\r
284     if (var)\r
285         g_ServerName = var;\r
286     var = getenv("SHIBSP_SERVER_SCHEME");\r
287     if (var)\r
288         g_ServerScheme = var;\r
289     var = getenv("SHIBSP_SERVER_PORT");\r
290     if (var)\r
291         g_ServerPort = atoi(var);\r
292 \r
293     streambuf* cout_streambuf = cout.rdbuf();\r
294     streambuf* cerr_streambuf = cerr.rdbuf();\r
295 \r
296     FCGX_Request request;\r
297 \r
298     FCGX_Init();\r
299     FCGX_InitRequest(&request, 0, 0);\r
300 \r
301     cout << "Shibboleth initialization complete. Starting request loop." << endl;\r
302     while (FCGX_Accept_r(&request) == 0)\r
303     {\r
304         // Note that the default bufsize (0) will cause the use of iostream\r
305         // methods that require positioning (such as peek(), seek(),\r
306         // unget() and putback()) to fail (in favour of more efficient IO).\r
307         fcgi_streambuf cout_fcgi_streambuf(request.out);\r
308         fcgi_streambuf cerr_fcgi_streambuf(request.err);\r
309 \r
310         cout.rdbuf(&cout_fcgi_streambuf);\r
311         cerr.rdbuf(&cerr_fcgi_streambuf);\r
312 \r
313         try {\r
314             xmltooling::NDC ndc("FastCGI shibauthorizer");\r
315             ShibTargetFCGIAuth sta(&request, g_ServerScheme.c_str(), g_ServerName.c_str(), g_ServerPort);\r
316 \r
317             pair<bool,long> res = sta.getServiceProvider().doAuthentication(sta);\r
318             if (res.first) {\r
319 #ifdef _DEBUG\r
320                 cerr << "shib: doAuthentication handled the request" << endl;\r
321 #endif\r
322                 switch(res.second) {\r
323                     case SHIB_RETURN_OK:\r
324                         print_ok(sta.m_request_headers);\r
325                         continue;\r
326 \r
327                     case SHIB_RETURN_KO:\r
328                         print_ok(sta.m_request_headers);\r
329                         continue;\r
330 \r
331                     case SHIB_RETURN_DONE:\r
332                         continue;\r
333 \r
334                     default:\r
335                         cerr << "shib: doAuthentication returned an unexpected result: " << res.second << endl;\r
336                         print_error("<html><body>FastCGI Shibboleth authorizer returned an unexpected result.</body></html>");\r
337                         continue;\r
338                 }\r
339             }\r
340 \r
341             res = sta.getServiceProvider().doExport(sta);\r
342             if (res.first) {\r
343 #ifdef _DEBUG\r
344                 cerr << "shib: doExport handled request" << endl;\r
345 #endif\r
346                 switch(res.second) {\r
347                     case SHIB_RETURN_OK:\r
348                         print_ok(sta.m_request_headers);\r
349                         continue;\r
350 \r
351                     case SHIB_RETURN_KO:\r
352                         print_ok(sta.m_request_headers);\r
353                         continue;\r
354 \r
355                     case SHIB_RETURN_DONE:\r
356                         continue;\r
357 \r
358                     default:\r
359                         cerr << "shib: doExport returned an unexpected result: " << res.second << endl;\r
360                         print_error("<html><body>FastCGI Shibboleth authorizer returned an unexpected result.</body></html>");\r
361                         continue;\r
362                 }\r
363             }\r
364 \r
365             res = sta.getServiceProvider().doAuthorization(sta);\r
366             if (res.first) {\r
367 #ifdef _DEBUG\r
368                 cerr << "shib: doAuthorization handled request" << endl;\r
369 #endif\r
370                 switch(res.second) {\r
371                     case SHIB_RETURN_OK:\r
372                         print_ok(sta.m_request_headers);\r
373                         continue;\r
374 \r
375                     case SHIB_RETURN_KO:\r
376                         print_ok(sta.m_request_headers);\r
377                         continue;\r
378 \r
379                     case SHIB_RETURN_DONE:\r
380                         continue;\r
381 \r
382                     default:\r
383                         cerr << "shib: doAuthorization returned an unexpected result: " << res.second << endl;\r
384                         print_error("<html><body>FastCGI Shibboleth authorizer returned an unexpected result.</body></html>");\r
385                         continue;\r
386                 }\r
387             }\r
388 \r
389             print_ok(sta.m_request_headers);\r
390 \r
391         }\r
392         catch (exception& e) {\r
393             cerr << "shib: FastCGI authorizer caught an exception: " << e.what() << endl;\r
394             print_error("<html><body>FastCGI Shibboleth authorizer caught an exception, check log for details.</body></html>");\r
395         }\r
396 \r
397         // If the output streambufs had non-zero bufsizes and\r
398         // were constructed outside of the accept loop (i.e.\r
399         // their destructor won't be called here), they would\r
400         // have to be flushed here.\r
401     }\r
402     cout << "Request loop ended." << endl;\r
403 \r
404     cout.rdbuf(cout_streambuf);\r
405     cerr.rdbuf(cerr_streambuf);\r
406 \r
407     if (g_Config)\r
408         g_Config->term();\r
409 \r
410     return 0;\r
411 }\r