9c08eea1b7ebc3a1b4348086ca1124b0e86650d1
[metaproxy-moved-to-github.git] / src / filter_cgi.cpp
1 /* This file is part of Metaproxy.
2    Copyright (C) Index Data
3
4 Metaproxy is free software; you can redistribute it and/or modify it under
5 the terms of the GNU General Public License as published by the Free
6 Software Foundation; either version 2, or (at your option) any later
7 version.
8
9 Metaproxy is distributed in the hope that it will be useful, but WITHOUT ANY
10 WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
17 */
18
19 #include "filter_cgi.hpp"
20 #include <metaproxy/package.hpp>
21 #include <metaproxy/util.hpp>
22 #include "gduutil.hpp"
23 #include <yaz/zgdu.h>
24 #include <yaz/log.h>
25
26 #include <unistd.h>
27 #include <signal.h>
28 #include <sys/wait.h>
29 #include <sstream>
30
31 #include "config.hpp"
32
33 namespace mp = metaproxy_1;
34 namespace yf = mp::filter;
35
36 namespace metaproxy_1 {
37     namespace filter {
38         class CGI::Exec {
39             friend class Rep;
40             friend class CGI;
41             std::string path;
42             std::string program;
43         };
44         class CGI::Rep {
45             friend class CGI;
46             std::list<CGI::Exec> exec_map;
47             std::map<std::string,std::string> env_map;
48             std::map<pid_t,pid_t> children;
49             boost::mutex m_mutex;
50         public:
51             ~Rep();
52         };
53     }
54 }
55
56 yf::CGI::CGI() : m_p(new Rep)
57 {
58
59 }
60
61 yf::CGI::Rep::~Rep()
62 {
63     std::map<pid_t,pid_t>::const_iterator it;
64     boost::mutex::scoped_lock lock(m_mutex);
65
66     for (it = children.begin(); it != children.end(); it++)
67         kill(it->second, SIGTERM);
68 }
69
70 yf::CGI::~CGI()
71 {
72 }
73
74 void yf::CGI::process(mp::Package &package) const
75 {
76     Z_GDU *zgdu_req = package.request().get();
77     Z_GDU *zgdu_res = 0;
78
79     if (!zgdu_req)
80         return;
81
82     if (zgdu_req->which != Z_GDU_HTTP_Request)
83     {
84         package.move();
85         return;
86     }
87
88
89     std::list<CGI::Exec>::const_iterator it;
90     metaproxy_1::odr odr;
91     Z_HTTP_Request *hreq = zgdu_req->u.HTTP_Request;
92     const char *path_cstr = hreq->path;
93     for (it = m_p->exec_map.begin(); it != m_p->exec_map.end(); it++)
94     {
95         if (strncmp(it->path.c_str(), path_cstr, it->path.length()) == 0)
96         {
97             std::string path(path_cstr);
98             const char *program_cstr = it->program.c_str();
99             std::string script_name(path, 0, it->path.length());
100             std::string rest(path, it->path.length());
101             std::string query_string;
102             std::string path_info;
103             size_t qpos = rest.find('?');
104             if (qpos == std::string::npos)
105                 path_info = rest;
106             else
107             {
108                 query_string.assign(rest, qpos + 1, std::string::npos);
109                 path_info.assign(rest, 0, qpos);
110             }
111             int fds[2];
112             int r = pipe(fds);
113             if (r == -1)
114             {
115                 zgdu_res = odr.create_HTTP_Response(
116                     package.session(), hreq, 400);
117                 package.response() = zgdu_res;
118                 continue;
119             }
120             int status;
121             pid_t pid = ::fork();
122             switch (pid)
123             {
124             case 0: /* child */
125                 close(1);
126                 dup(fds[1]);
127                 setenv("REQUEST_METHOD", hreq->method, 1);
128                 setenv("REQUEST_URI", path_cstr, 1);
129                 setenv("SCRIPT_NAME", script_name.c_str(), 1);
130                 setenv("PATH_INFO", path_info.c_str(), 1);
131                 setenv("QUERY_STRING", query_string.c_str(), 1);
132                 {
133                     const char *v;
134                     v = z_HTTP_header_lookup(hreq->headers, "Cookie");
135                     if (v)
136                         setenv("HTTP_COOKIE", v, 1);
137                     v = z_HTTP_header_lookup(hreq->headers, "User-Agent");
138                     if (v)
139                         setenv("HTTP_USER_AGENT", v, 1);
140                     v = z_HTTP_header_lookup(hreq->headers, "Accept");
141                     if (v)
142                         setenv("HTTP_ACCEPT", v, 1);
143                     v = z_HTTP_header_lookup(hreq->headers, "Accept-Encoding");
144                     if (v)
145                         setenv("HTTP_ACCEPT_ENCODING", v, 1);
146                     std::map<std::string,std::string>::const_iterator it;
147                     for (it = m_p->env_map.begin();
148                          it != m_p->env_map.end(); it++)
149                         setenv(it->first.c_str(), it->second.c_str(), 1);
150                     char *program = xstrdup(program_cstr);
151                     char *cp = strrchr(program, '/');
152                     if (cp)
153                     {
154                         *cp++ = '\0';
155                         chdir(program);
156                     }
157                     else
158                         cp = program;
159                     r = execl(cp, cp, (char *) 0);
160                 }
161                 if (r == -1)
162                     exit(1);
163                 exit(0);
164                 break;
165             case -1: /* error */
166                 close(fds[0]);
167                 close(fds[1]);
168                 zgdu_res = odr.create_HTTP_Response(
169                     package.session(), hreq, 400);
170                 package.response() = zgdu_res;
171                 break;
172             default: /* parent */
173                 close(fds[1]);
174                 if (pid)
175                 {
176                     boost::mutex::scoped_lock lock(m_p->m_mutex);
177                     m_p->children[pid] = pid;
178                 }
179                 WRBUF w = wrbuf_alloc();
180                 wrbuf_puts(w, "HTTP/1.1 200 OK\r\n");
181                 while (1)
182                 {
183                     char buf[512];
184                     ssize_t rd = read(fds[0], buf, sizeof buf);
185                     if (rd <= 0)
186                         break;
187                     wrbuf_write(w, buf, rd);
188                 }
189                 close(fds[0]);
190                 waitpid(pid, &status, 0);
191
192                 if (pid)
193                 {
194                     boost::mutex::scoped_lock lock(m_p->m_mutex);
195                     m_p->children.erase(pid);
196                 }
197                 ODR dec = odr_createmem(ODR_DECODE);
198                 odr_setbuf(dec, wrbuf_buf(w), wrbuf_len(w), 0);
199                 r = z_GDU(dec, &zgdu_res, 0, 0);
200                 if (r && zgdu_res)
201                 {
202                     package.response() = zgdu_res;
203                 }
204                 else
205                 {
206                     zgdu_res = odr.create_HTTP_Response(
207                         package.session(), zgdu_req->u.HTTP_Request, 400);
208                     Z_HTTP_Response *hres = zgdu_res->u.HTTP_Response;
209                     z_HTTP_header_add(odr, &hres->headers,
210                                       "Content-Type", "text/plain");
211                     hres->content_buf =
212                         odr_strdup(odr, "Invalid script from script");
213                     hres->content_len = strlen(hres->content_buf);
214                 }
215                 package.response() = zgdu_res;
216                 odr_destroy(dec);
217                 wrbuf_destroy(w);
218                 break;
219             }
220             return;
221         }
222     }
223     package.move();
224 }
225
226 void yf::CGI::configure(const xmlNode *ptr, bool test_only, const char *path)
227 {
228     yaz_log(YLOG_LOG, "cgi::configure path=%s", path);
229     for (ptr = ptr->children; ptr; ptr = ptr->next)
230     {
231         if (ptr->type != XML_ELEMENT_NODE)
232             continue;
233         if (!strcmp((const char *) ptr->name, "map"))
234         {
235             CGI::Exec exec;
236
237             const struct _xmlAttr *attr;
238             for (attr = ptr->properties; attr; attr = attr->next)
239             {
240                 if (!strcmp((const char *) attr->name,  "path"))
241                     exec.path = mp::xml::get_text(attr->children);
242                 else if (!strcmp((const char *) attr->name, "exec"))
243                     exec.program = mp::xml::get_text(attr->children);
244                 else
245                     throw mp::filter::FilterException
246                         ("Bad attribute "
247                          + std::string((const char *) attr->name)
248                          + " in cgi section");
249             }
250             m_p->exec_map.push_back(exec);
251         }
252         else if (!strcmp((const char *) ptr->name, "env"))
253         {
254             std::string name, value;
255
256             const struct _xmlAttr *attr;
257             for (attr = ptr->properties; attr; attr = attr->next)
258             {
259                 if (!strcmp((const char *) attr->name,  "name"))
260                     name = mp::xml::get_text(attr->children);
261                 else if (!strcmp((const char *) attr->name, "value"))
262                     value = mp::xml::get_text(attr->children);
263                 else
264                     throw mp::filter::FilterException
265                         ("Bad attribute "
266                          + std::string((const char *) attr->name)
267                          + " in cgi section");
268             }
269             if (name.length() > 0)
270                 m_p->env_map[name] = value;
271         }
272         else
273         {
274             throw mp::filter::FilterException("Bad element "
275                                                + std::string((const char *)
276                                                              ptr->name));
277         }
278     }
279 }
280
281 static mp::filter::Base* filter_creator()
282 {
283     return new mp::filter::CGI;
284 }
285
286 extern "C" {
287     struct metaproxy_1_filter_struct metaproxy_1_filter_cgi = {
288         0,
289         "cgi",
290         filter_creator
291     };
292 }
293
294
295 /*
296  * Local variables:
297  * c-basic-offset: 4
298  * c-file-style: "Stroustrup"
299  * indent-tabs-mode: nil
300  * End:
301  * vim: shiftwidth=4 tabstop=8 expandtab
302  */
303