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