version 0.2.1
[fms.git] / libs / shttpd / cgi.c
1 /*
2  * Copyright (c) 2004-2005 Sergey Lyubka <valenok@gmail.com>
3  * All rights reserved
4  *
5  * "THE BEER-WARE LICENSE" (Revision 42):
6  * Sergey Lyubka wrote this file.  As long as you retain this notice you
7  * can do whatever you want with this stuff. If we meet some day, and you think
8  * this stuff is worth it, you can buy me a beer in return.
9  */
10
11 #include "defs.h"
12
13 #if !defined(NO_CGI)
14 struct env_block {
15         char    buf[ENV_MAX];           /* Environment buffer           */
16         int     len;                    /* Space taken                  */
17         char    *vars[CGI_ENV_VARS];    /* Point into the buffer        */
18         int     nvars;                  /* Number of variables          */
19 };
20
21 /*
22  * UNIX socketpair() implementation. Why? Because Windows does not have it.
23  * Return 0 on success, -1 on error.
24  */
25 static int
26 my_socketpair(struct conn *c, int sp[2])
27 {
28         struct sockaddr_in      sa;
29         int                     sock, ret = -1;
30         socklen_t               len = sizeof(sa);
31
32         (void) memset(&sa, 0, sizeof(sa));
33         sa.sin_family           = AF_INET;
34         sa.sin_port             = htons(0);
35         sa.sin_addr.s_addr      = htonl(INADDR_LOOPBACK);
36
37         if ((sock = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
38                 elog(E_LOG, c, "mysocketpair: socket(): %d", ERRNO);
39         } else if (bind(sock, (struct sockaddr *) &sa, len) != 0) {
40                 elog(E_LOG, c, "mysocketpair: bind(): %d", ERRNO);
41                 (void) closesocket(sock);
42         } else if (listen(sock, 1) != 0) {
43                 elog(E_LOG, c, "mysocketpair: listen(): %d", ERRNO);
44                 (void) closesocket(sock);
45         } else if (getsockname(sock, (struct sockaddr *) &sa, &len) != 0) {
46                 elog(E_LOG, c, "mysocketpair: getsockname(): %d", ERRNO);
47                 (void) closesocket(sock);
48         } else if ((sp[0] = socket(AF_INET, SOCK_STREAM, 6)) == -1) {
49                 elog(E_LOG, c, "mysocketpair: socket(): %d", ERRNO);
50                 (void) closesocket(sock);
51         } else if (connect(sp[0], (struct sockaddr *) &sa, len) != 0) {
52                 elog(E_LOG, c, "mysocketpair: connect(): %d", ERRNO);
53                 (void) closesocket(sock);
54                 (void) closesocket(sp[0]);
55         } else if ((sp[1] = accept(sock,(struct sockaddr *) &sa, &len)) == -1) {
56                 elog(E_LOG, c, "mysocketpair: accept(): %d", ERRNO);
57                 (void) closesocket(sock);
58                 (void) closesocket(sp[0]);
59         } else {
60                 /* Success */
61                 ret = 0;
62                 (void) closesocket(sock);
63         }
64
65 #ifndef _WIN32
66         (void) fcntl(sp[0], F_SETFD, FD_CLOEXEC);
67         (void) fcntl(sp[1], F_SETFD, FD_CLOEXEC);
68 #endif /* _WIN32*/
69
70         return (ret);
71 }
72
73 static void
74 addenv(struct env_block *block, const char *fmt, ...)
75 {
76         int     n, space;
77         va_list ap;
78
79         space = sizeof(block->buf) - block->len - 2;
80         assert(space >= 0);
81
82         va_start(ap, fmt);
83         n = vsnprintf(block->buf + block->len, space, fmt, ap);
84         va_end(ap);
85
86         if (n > 0 && n < space && block->nvars < CGI_ENV_VARS - 2) {
87                 block->vars[block->nvars++] = block->buf + block->len;
88                 block->len += n + 1;    /* Include \0 terminator */
89         }
90 }
91
92 static void
93 add_http_headers_to_env(struct env_block *b, const char *s, int len)
94 {
95         const char      *p, *v, *e = s + len;
96         int             space, n, i, ch;
97
98         /* Loop through all headers in the request */
99         while (s < e) {
100
101                 /* Find where this header ends. Remember where value starts */
102                 for (p = s, v = NULL; p < e && *p != '\n'; p++)
103                         if (v == NULL && *p == ':') 
104                                 v = p;
105
106                 /* 2 null terminators and "HTTP_" */
107                 space = (sizeof(b->buf) - b->len) - (2 + 5);
108                 assert(space >= 0);
109         
110                 /* Copy header if enough space in the environment block */
111                 if (v > s && p > v + 2 && space > p - s) {
112
113                         /* Store var */
114                         if (b->nvars < (int) NELEMS(b->vars) - 1)
115                                 b->vars[b->nvars++] = b->buf + b->len;
116
117                         (void) memcpy(b->buf + b->len, "HTTP_", 5);
118                         b->len += 5;
119
120                         /* Copy header name. Substitute '-' to '_' */
121                         n = v - s;
122                         for (i = 0; i < n; i++) {
123                                 ch = s[i] == '-' ? '_' : s[i];
124                                 b->buf[b->len++] = toupper(ch);
125                         }
126
127                         b->buf[b->len++] = '=';
128
129                         /* Copy header value */
130                         v += 2;
131                         n = p[-1] == '\r' ? (p - v) - 1 : p - v;
132                         for (i = 0; i < n; i++)
133                                 b->buf[b->len++] = v[i];
134
135                         /* Null-terminate */
136                         b->buf[b->len++] = '\0';
137                 }
138
139                 s = p + 1;      /* Shift to the next header */
140         }
141 }
142
143 static void
144 prepare_environment(const struct conn *c, const char *prog,
145                 struct env_block *blk)
146 {
147         const struct headers    *h = &c->ch;
148         const char              *s, *root = c->ctx->options[OPT_ROOT];
149         size_t                  len;
150
151         blk->len = blk->nvars = 0;
152
153         /* Prepare the environment block */
154         addenv(blk, "%s", "GATEWAY_INTERFACE=CGI/1.1");
155         addenv(blk, "%s", "SERVER_PROTOCOL=HTTP/1.1");
156         addenv(blk, "%s", "REDIRECT_STATUS=200");       /* PHP */
157         addenv(blk, "SERVER_PORT=%d", c->loc_port);
158         addenv(blk, "SERVER_NAME=%s", c->ctx->options[OPT_AUTH_REALM]);
159         addenv(blk, "SERVER_ROOT=%s", root);
160         addenv(blk, "DOCUMENT_ROOT=%s", root);
161         addenv(blk, "REQUEST_METHOD=%s", known_http_methods[c->method].ptr);
162         addenv(blk, "REMOTE_ADDR=%s", inet_ntoa(c->sa.u.sin.sin_addr));
163         addenv(blk, "REMOTE_PORT=%hu", ntohs(c->sa.u.sin.sin_port));
164         addenv(blk, "REQUEST_URI=%s", c->uri);
165         addenv(blk, "SCRIPT_NAME=%s", prog + strlen(root));
166         addenv(blk, "SCRIPT_FILENAME=%s", prog);        /* PHP */
167         addenv(blk, "PATH_TRANSLATED=%s", prog);
168
169         if (h->ct.v_vec.len > 0)
170                 addenv(blk, "CONTENT_TYPE=%.*s", 
171                     h->ct.v_vec.len, h->ct.v_vec.ptr);
172
173         if (c->query != NULL)
174                 addenv(blk, "QUERY_STRING=%s", c->query);
175
176         if (c->path_info != NULL)
177                 addenv(blk, "PATH_INFO=/%s", c->path_info);
178
179         if (h->cl.v_big_int > 0)
180                 addenv(blk, "CONTENT_LENGTH=%lu", h->cl.v_big_int);
181
182         if ((s = getenv("PATH")) != NULL)
183                 addenv(blk, "PATH=%s", s);
184
185 #ifdef _WIN32
186         if ((s = getenv("COMSPEC")) != NULL)
187                 addenv(blk, "COMSPEC=%s", s);
188         if ((s = getenv("SYSTEMROOT")) != NULL)
189                 addenv(blk, "SYSTEMROOT=%s", s);
190 #else
191         if ((s = getenv("LD_LIBRARY_PATH")) != NULL)
192                 addenv(blk, "LD_LIBRARY_PATH=%s", s);
193 #endif /* _WIN32 */
194
195         if ((s = getenv("PERLLIB")) != NULL)
196                 addenv(blk, "PERLLIB=%s", s);
197
198         if (h->user.v_vec.len > 0) {
199                 addenv(blk, "REMOTE_USER=%.*s",
200                     h->user.v_vec.len, h->user.v_vec.ptr);
201                 addenv(blk, "%s", "AUTH_TYPE=Digest");
202         }
203
204         /* Add user-specified variables */
205         s = c->ctx->options[OPT_CGI_ENVIRONMENT];
206         FOR_EACH_WORD_IN_LIST(s, len)
207                 addenv(blk, "%.*s", len, s);
208
209         /* Add all headers as HTTP_* variables */
210         add_http_headers_to_env(blk, c->headers,
211             c->rem.headers_len - (c->headers - c->request));
212
213         blk->vars[blk->nvars++] = NULL;
214         blk->buf[blk->len++] = '\0';
215
216         assert(blk->nvars < CGI_ENV_VARS);
217         assert(blk->len > 0);
218         assert(blk->len < (int) sizeof(blk->buf));
219
220         /* Debug stuff to view passed environment */
221         DBG(("%s: %d vars, %d env size", prog, blk->nvars, blk->len));
222         {
223                 int i;
224                 for (i = 0 ; i < blk->nvars; i++)
225                         DBG(("[%s]", blk->vars[i] ? blk->vars[i] : "null"));
226         }
227 }
228
229 int
230 run_cgi(struct conn *c, const char *prog)
231 {
232         struct env_block        blk;
233         char                    dir[FILENAME_MAX], *p;
234         int                     ret, pair[2];
235
236         prepare_environment(c, prog, &blk);
237         pair[0] = pair[1] = -1;
238
239         /* CGI must be executed in its own directory */
240         (void) my_snprintf(dir, sizeof(dir), "%s", prog);
241         for (p = dir + strlen(dir) - 1; p > dir; p--)
242                 if (*p == '/') {
243                         *p++ = '\0';
244                         break;
245                 }
246         
247         if (my_socketpair(c, pair) != 0) {
248                 ret = -1;
249         } else if (spawn_process(c, prog, blk.buf, blk.vars, pair[1], dir)) {
250                 ret = -1;
251                 (void) closesocket(pair[0]);
252                 (void) closesocket(pair[1]);
253         } else {
254                 ret = 0;
255                 c->loc.chan.sock = pair[0];
256         }
257
258         return (ret);
259 }
260
261 void
262 do_cgi(struct conn *c)
263 {
264         DBG(("running CGI: [%s]", c->uri));
265         assert(c->loc.io.size > CGI_REPLY_LEN);
266         memcpy(c->loc.io.buf, CGI_REPLY, CGI_REPLY_LEN);
267         c->loc.io.head = c->loc.io.tail = c->loc.io.total = CGI_REPLY_LEN;
268         c->loc.io_class = &io_cgi;
269         c->loc.flags = FLAG_R;
270         if (c->method == METHOD_POST)
271                 c->loc.flags |= FLAG_W;
272 }
273
274 #endif /* !NO_CGI */