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