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