metaproxy  1.13.0
filter_cgi.cpp
Go to the documentation of this file.
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 <fcntl.h>
28 #include <signal.h>
29 #include <sys/wait.h>
30 #include <yaz/poll.h>
31 #include <sstream>
32 
33 #include "config.hpp"
34 
35 namespace mp = metaproxy_1;
36 namespace yf = mp::filter;
37 
38 namespace metaproxy_1 {
39  namespace filter {
40  class CGI::Exec {
41  friend class Rep;
42  friend class CGI;
43  std::string path;
44  std::string program;
45  };
46  class CGI::Rep {
47  friend class CGI;
48  std::list<CGI::Exec> exec_map;
49  std::map<std::string,std::string> env_map;
50  std::map<pid_t,pid_t> children;
51  boost::mutex m_mutex;
52  std::string documentroot;
53  void child(Z_HTTP_Request *, const CGI::Exec *);
54  public:
55  ~Rep();
56  };
57  }
58 }
59 
60 yf::CGI::CGI() : m_p(new Rep)
61 {
62 
63 }
64 
65 yf::CGI::Rep::~Rep()
66 {
67  std::map<pid_t,pid_t>::const_iterator it;
68  boost::mutex::scoped_lock lock(m_mutex);
69 
70  for (it = children.begin(); it != children.end(); it++)
71  kill(it->second, SIGTERM);
72 }
73 
74 yf::CGI::~CGI()
75 {
76 }
77 
78 void yf::CGI::Rep::child(Z_HTTP_Request *hreq, const CGI::Exec *it)
79 {
80  const char *path_cstr = hreq->path;
81  std::string path(path_cstr);
82  const char *program_cstr = it->program.c_str();
83  std::string script_name(path, 0, it->path.length());
84  std::string rest(path, it->path.length());
85  std::string query_string;
86  std::string path_info;
87  size_t qpos = rest.find('?');
88  if (qpos == std::string::npos)
89  path_info = rest;
90  else
91  {
92  query_string.assign(rest, qpos + 1, std::string::npos);
93  path_info.assign(rest, 0, qpos);
94  }
95  setenv("REQUEST_METHOD", hreq->method, 1);
96  setenv("REQUEST_URI", path_cstr, 1);
97  setenv("SCRIPT_NAME", script_name.c_str(), 1);
98  setenv("PATH_INFO", path_info.c_str(), 1);
99  setenv("QUERY_STRING", query_string.c_str(), 1);
100  const char *v;
101  v = z_HTTP_header_lookup(hreq->headers, "Cookie");
102  if (v)
103  setenv("HTTP_COOKIE", v, 1);
104  v = z_HTTP_header_lookup(hreq->headers, "User-Agent");
105  if (v)
106  setenv("HTTP_USER_AGENT", v, 1);
107  v = z_HTTP_header_lookup(hreq->headers, "Accept");
108  if (v)
109  setenv("HTTP_ACCEPT", v, 1);
110  v = z_HTTP_header_lookup(hreq->headers, "Accept-Encoding");
111  if (v)
112  setenv("HTTP_ACCEPT_ENCODING", v, 1);
113  setenv("DOCUMENT_ROOT", documentroot.c_str(), 1);
114  setenv("GATEWAY_INTERFACE", "CGI/1.1", 1);
115 
116  v = z_HTTP_header_lookup(hreq->headers, "Content-Type");
117  if (v)
118  {
119  char tmp[40];
120  sprintf(tmp, "%d", hreq->content_len);
121  setenv("CONTENT_LENGTH", tmp, 1);
122  setenv("CONTENT_TYPE", v, 1);
123  }
124  // apply user-defined environment
125  std::map<std::string,std::string>::const_iterator it_e;
126  for (it_e = env_map.begin();
127  it_e != env_map.end(); it_e++)
128  setenv(it_e->first.c_str(), it_e->second.c_str(), 1);
129  // change directory to configuration root
130  // then to CGI program directory (could be relative)
131  chdir(documentroot.c_str());
132  char *program = xstrdup(program_cstr);
133  char *cp = strrchr(program, '/');
134  if (cp)
135  {
136  *cp++ = '\0';
137  chdir(program);
138  }
139  else
140  cp = program;
141  int r = execl(cp, cp, (char *) 0);
142  if (r == -1)
143  exit(1);
144  exit(0);
145 }
146 
147 void yf::CGI::process(mp::Package &package) const
148 {
149  Z_GDU *zgdu_req = package.request().get();
150  Z_GDU *zgdu_res = 0;
151 
152  if (!zgdu_req || zgdu_req->which != Z_GDU_HTTP_Request)
153  {
154  package.move();
155  return;
156  }
157  std::list<CGI::Exec>::const_iterator it;
158  metaproxy_1::odr odr;
159  Z_HTTP_Request *hreq = zgdu_req->u.HTTP_Request;
160  const char *path_cstr = hreq->path;
161  for (it = m_p->exec_map.begin(); it != m_p->exec_map.end(); it++)
162  {
163  if (strncmp(it->path.c_str(), path_cstr, it->path.length()) == 0)
164  {
165  int fds_response[2];
166  int r = pipe(fds_response);
167  if (r == -1)
168  {
169  zgdu_res = odr.create_HTTP_Response(
170  package.session(), hreq, 400);
171  package.response() = zgdu_res;
172  continue;
173  }
174  int fds_request[2];
175  r = pipe(fds_request);
176  if (r == -1)
177  {
178  zgdu_res = odr.create_HTTP_Response(
179  package.session(), hreq, 400);
180  package.response() = zgdu_res;
181  close(fds_response[0]);
182  close(fds_response[1]);
183  continue;
184  }
185 
186  int status;
187  pid_t pid = ::fork();
188  switch (pid)
189  {
190  case 0: /* child */
191  /* POSTed content */
192  close(0);
193  dup(fds_request[0]);
194  close(fds_request[1]);
195  /* response */
196  close(1);
197  close(fds_response[0]);
198  dup(fds_response[1]);
199  m_p->child(hreq, &(*it));
200  break;
201  case -1: /* error */
202  close(fds_request[0]);
203  close(fds_request[1]);
204  close(fds_response[0]);
205  close(fds_response[1]);
206  zgdu_res = odr.create_HTTP_Response(
207  package.session(), hreq, 400);
208  package.response() = zgdu_res;
209  break;
210  default: /* parent */
211  close(fds_response[1]);
212  close(fds_request[0]);
213  if (pid)
214  {
215  boost::mutex::scoped_lock lock(m_p->m_mutex);
216  m_p->children[pid] = pid;
217  }
218  WRBUF w = wrbuf_alloc();
219  wrbuf_puts(w, "HTTP/1.1 200 OK\r\n");
220  fcntl(fds_response[0], F_SETFL, O_NONBLOCK);
221  fcntl(fds_request[1], F_SETFL, O_NONBLOCK);
222  int no_write = 0;
223  while (1)
224  {
225  int num = 1;
226  struct yaz_poll_fd fds[2];
227  fds[0].fd = fds_response[0];
228  fds[0].input_mask = yaz_poll_read;
229  if (no_write < hreq->content_len)
230  {
231  fds[1].fd = fds_request[1];
232  fds[1].input_mask = yaz_poll_write;
233  num = 2;
234  }
235  int r = yaz_poll(fds, num, 60, 0);
236  if (r <= 0)
237  break;
238  if (fds[0].output_mask & (yaz_poll_read|yaz_poll_except))
239  {
240  char buf[512];
241  ssize_t rd = read(fds_response[0], buf, sizeof buf);
242  if (rd <= 0)
243  break;
244  wrbuf_write(w, buf, rd);
245  }
246  if (num == 2 && fds[1].output_mask & yaz_poll_write)
247  {
248  ssize_t wd = write(fds_request[1],
249  hreq->content_buf + no_write,
250  hreq->content_len - no_write);
251  if (wd <= 0)
252  break;
253  no_write += wd;
254  }
255  }
256  close(fds_request[1]);
257  close(fds_response[0]);
258  waitpid(pid, &status, 0);
259 
260  if (pid)
261  {
262  boost::mutex::scoped_lock lock(m_p->m_mutex);
263  m_p->children.erase(pid);
264  }
265  ODR dec = odr_createmem(ODR_DECODE);
266  odr_setbuf(dec, wrbuf_buf(w), wrbuf_len(w), 0);
267  r = z_GDU(dec, &zgdu_res, 0, 0);
268  if (r && zgdu_res)
269  {
270  package.response() = zgdu_res;
271  }
272  else
273  {
274  zgdu_res = odr.create_HTTP_Response(
275  package.session(), zgdu_req->u.HTTP_Request, 400);
276  Z_HTTP_Response *hres = zgdu_res->u.HTTP_Response;
277  z_HTTP_header_add(odr, &hres->headers,
278  "Content-Type", "text/plain");
279  hres->content_buf =
280  odr_strdup(odr, "Invalid script from script");
281  hres->content_len = strlen(hres->content_buf);
282  }
283  package.response() = zgdu_res;
284  odr_destroy(dec);
285  wrbuf_destroy(w);
286  break;
287  }
288  return;
289  }
290  }
291  package.move();
292 }
293 
294 void yf::CGI::configure(const xmlNode *ptr, bool test_only, const char *path)
295 {
296  yaz_log(YLOG_LOG, "cgi::configure path=%s", path);
297  for (ptr = ptr->children; ptr; ptr = ptr->next)
298  {
299  if (ptr->type != XML_ELEMENT_NODE)
300  continue;
301  if (!strcmp((const char *) ptr->name, "map"))
302  {
303  CGI::Exec exec;
304 
305  const struct _xmlAttr *attr;
306  for (attr = ptr->properties; attr; attr = attr->next)
307  {
308  if (!strcmp((const char *) attr->name, "path"))
309  exec.path = mp::xml::get_text(attr->children);
310  else if (!strcmp((const char *) attr->name, "exec"))
311  exec.program = mp::xml::get_text(attr->children);
312  else
313  throw mp::filter::FilterException
314  ("Bad attribute "
315  + std::string((const char *) attr->name)
316  + " in cgi section");
317  }
318  m_p->exec_map.push_back(exec);
319  }
320  else if (!strcmp((const char *) ptr->name, "env"))
321  {
322  std::string name, value;
323 
324  const struct _xmlAttr *attr;
325  for (attr = ptr->properties; attr; attr = attr->next)
326  {
327  if (!strcmp((const char *) attr->name, "name"))
328  name = mp::xml::get_text(attr->children);
329  else if (!strcmp((const char *) attr->name, "value"))
330  value = mp::xml::get_text(attr->children);
331  else
332  throw mp::filter::FilterException
333  ("Bad attribute "
334  + std::string((const char *) attr->name)
335  + " in cgi section");
336  }
337  if (name.length() > 0)
338  m_p->env_map[name] = value;
339  }
340  else if (!strcmp((const char *) ptr->name, "documentroot"))
341  {
342  m_p->documentroot = path;
343  }
344  else
345  {
346  throw mp::filter::FilterException("Bad element "
347  + std::string((const char *)
348  ptr->name));
349  }
350  }
351  if (m_p->documentroot.length() == 0)
352  m_p->documentroot = ".";
353 }
354 
355 static mp::filter::Base* filter_creator()
356 {
357  return new mp::filter::CGI;
358 }
359 
360 extern "C" {
361  struct metaproxy_1_filter_struct metaproxy_1_filter_cgi = {
362  0,
363  "cgi",
365  };
366 }
367 
368 
369 /*
370  * Local variables:
371  * c-basic-offset: 4
372  * c-file-style: "Stroustrup"
373  * indent-tabs-mode: nil
374  * End:
375  * vim: shiftwidth=4 tabstop=8 expandtab
376  */
377 
struct metaproxy_1_filter_struct metaproxy_1_filter_cgi
Definition: filter_cgi.cpp:361
std::list< CGI::Exec > exec_map
Definition: filter_cgi.cpp:48
boost::scoped_ptr< Rep > m_p
Definition: filter_cgi.hpp:30
static mp::filter::Base * filter_creator()
Definition: filter_cgi.cpp:355
std::map< std::string, std::string > env_map
Definition: filter_cgi.cpp:49
std::map< pid_t, pid_t > children
Definition: filter_cgi.cpp:50