1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29 #include "cgi.h"
30
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <unistd.h>
34
35 #include <sys/types.h>
36 #include <signal.h>
37 #include <sys/wait.h>
38
39 #include "../util/util.h"
40 #include "../util/pblock.h"
41 #include "../../ucx/string.h"
42 #include "../daemon/netsite.h"
43 #include "../util/io.h"
44
45 #include "cgiutils.h"
46
47 #define CGI_VARS 32
48
49 #define CGI_RESPONSE_PARSER_BUFLEN 2048
50 #define CGI_RESPONSE_MAX_LINE_LENGTH 512
51
52 int send_cgi(pblock *pb, Session *sn, Request *rq) {
53 char *path = pblock_findkeyval(pb_key_path, rq->vars);
54 char *ctlen = pblock_findkeyval(pb_key_content_length, rq->headers);
55 int64_t content_length =
0;
56
57 if(ctlen) {
58 if(!util_strtoint(ctlen, &content_length)) {
59 log_ereport(
60 LOG_FAILURE,
61 "send-cgi: content-length header is not an integer");
62 protocol_status(sn, rq,
400,
NULL);
63 return REQ_ABORTED;
64 }
65 }
66
67 struct stat s;
68 if(stat(path, &s)) {
69 int statuscode = util_errno2status(errno);
70 protocol_status(sn, rq, statuscode,
NULL);
71 return REQ_ABORTED;
72 }
73 if(
S_ISDIR(s.st_mode)) {
74 protocol_status(sn, rq,
403,
NULL);
75 return REQ_ABORTED;
76 }
77
78 param_free(pblock_remove(
"content-type", rq->srvhdrs));
79
80 const char *args = pblock_findval(
"query", rq->reqpb);
81 char **argv = cgi_create_argv(path,
NULL, args);
82
83 char **env = http_hdrs2env(rq->headers);
84 env = cgi_common_vars(sn, rq, env);
85 env = cgi_specific_vars(sn, rq, args, env,
1);
86
87 CGIProcess cgip;
88 int ret = cgi_start(&cgip, path, argv, env);
89 if(ret !=
REQ_PROCEED) {
90 util_env_free(env);
91 cgi_free_argv(argv);
92 return ret;
93 }
94
95 util_env_free(env);
96 cgi_free_argv(argv);
97
98 char buf[
4096];
99 ssize_t r;
100
101 if(content_length >
0) {
102 ssize_t n =
0;
103 while(n < content_length) {
104 r = netbuf_getbytes(sn->inbuf, buf,
4096);
105 if(r <=
0) {
106
107 log_ereport(
108 LOG_FAILURE,
109 "send-cgi: script: %s: cannot read request body",
110 path);
111 kill(cgip.pid,
SIGTERM);
112 cgi_close(&cgip);
113 return REQ_ABORTED;
114 }
115 ssize_t w = write(cgip.in[
1], buf, r);
116 if(w <=
0) {
117
118 log_ereport(
119 LOG_FAILURE,
120 "send-cgi: script: %s: cannot send request body to cgi process",
121 path);
122 kill(cgip.pid,
SIGKILL);
123 cgi_close(&cgip);
124 return REQ_ABORTED;
125 }
126 n += r;
127 }
128 }
129 system_close(cgip.in[
1]);
130 cgip.in[
1] = -
1;
131
132
133 CGIResponseParser *parser = cgi_parser_new(sn, rq);
134 WSBool cgiheader =
TRUE;
135 ssize_t wr =
0;
136 int result =
REQ_PROCEED;
137 size_t response_length =
0;
138 while((r = read(cgip.out[
0], buf,
4096)) >
0) {
139 if(cgiheader) {
140 size_t pos;
141 ret = cgi_parse_response(parser, buf, r, &pos);
142 if(ret == -
1) {
143 log_ereport(
144 LOG_FAILURE,
145 "broken cgi script response: path: %s", path);
146 protocol_status(sn, rq,
500,
NULL);
147 result =
REQ_ABORTED;
148 break;
149 }
else if(ret ==
1) {
150 cgiheader =
FALSE;
151 if(parser->status >
0) {
152 protocol_status(sn, rq, parser->status, parser->msg);
153 }
154 http_start_response(sn, rq);
155 if(pos < r) {
156 response_length += r-pos;
157 wr = net_write(sn->csd, &buf[pos], r-pos);
158 if(wr <=
0) {
159 result =
REQ_ABORTED;
160 break;
161 }
162 }
163 }
164 }
else {
165 response_length += r;
166 wr = net_write(sn->csd, buf, r);
167 if(wr <=
0) {
168 result =
REQ_ABORTED;
169 break;
170 }
171 }
172 }
173
174 char *ctlen_header = pblock_findkeyval(pb_key_content_length, rq->srvhdrs);
175 if(ctlen_header) {
176 int64_t ctlenhdr;
177 if(util_strtoint(ctlen_header, &ctlenhdr)) {
178 if(ctlenhdr != response_length) {
179 log_ereport(
180 LOG_FAILURE,
181 "cgi-send: script: %s: content length mismatch",
182 path);
183 rq->rq_attr.keep_alive =
0;
184 result =
REQ_ABORTED;
185 }
186 }
187 }
188
189 if(result ==
REQ_ABORTED) {
190 log_ereport(
LOG_FAILURE,
"cgi-send: kill script: %s", path);
191 kill(cgip.pid,
SIGKILL);
192 }
193 cgi_close(&cgip);
194
195 cgi_parser_free(parser);
196 return result;
197 }
198
199 int cgi_start(CGIProcess *p,
char *path,
char *
const argv[],
char *
const envp[]) {
200 if(pipe(p->in) || pipe(p->out)) {
201 log_ereport(
202 LOG_FAILURE,
203 "send-cgi: cannot create pipe: %s",
204 strerror(errno));
205 return REQ_ABORTED;
206 }
207
208 p->pid = fork();
209 if(p->pid ==
0) {
210
211
212
213 sstr_t script = sstr(path);
214 sstr_t parent;
215 int len = strlen(path);
216 for(
int i=len-
1;i>=
0;i--) {
217 if(path[i] ==
'/') {
218 script = sstrn(path + i +
1, len - i);
219 parent = sstrdup(sstrn(path, i));
220 if(chdir(parent.ptr)) {
221 perror(
"cgi_start: chdir");
222 free(parent.ptr);
223 exit(-
1);
224 }
225 free(parent.ptr);
226 break;
227 }
228 }
229
230 if(dup2(p->in[
0],
STDIN_FILENO) == -
1) {
231 perror(
"cgi_start: dup2");
232 exit(
EXIT_FAILURE);
233 }
234 if(dup2(p->out[
1],
STDOUT_FILENO) == -
1) {
235 perror(
"cgi_start: dup2");
236 exit(
EXIT_FAILURE);
237 }
238
239
240
241 system_close(p->in[
1]);
242
243
244 exit(execve(script.ptr, argv, envp));
245 }
else {
246
247 system_close(p->out[
1]);
248 p->out[
1] = -
1;
249 }
250
251 return REQ_PROCEED;
252 }
253
254 int cgi_close(CGIProcess *p) {
255 int status = -
1;
256 waitpid(p->pid, &status,
0);
257
258 if(p->in[
0] != -
1) {
259 system_close(p->in[
0]);
260 }
261 if(p->in[
1] != -
1) {
262 system_close(p->in[
1]);
263 }
264 if(p->out[
0] != -
1) {
265 system_close(p->out[
0]);
266 }
267 if(p->out[
1] != -
1) {
268 system_close(p->out[
1]);
269 }
270
271 return 0;
272 }
273
274 CGIResponseParser* cgi_parser_new(Session *sn, Request *rq) {
275 CGIResponseParser* parser = pool_malloc(sn->pool,
sizeof(CGIResponseParser));
276 parser->sn = sn;
277 parser->rq = rq;
278 parser->tmp = ucx_buffer_new(
NULL,
64,
UCX_BUFFER_AUTOEXTEND);
279 parser->status =
0;
280 parser->msg =
NULL;
281 return parser;
282 }
283
284 void cgi_parser_free(CGIResponseParser *parser) {
285 if(parser->tmp) {
286 ucx_buffer_free(parser->tmp);
287 }
288 pool_free(parser->sn->pool, parser);
289 }
290
291
292
293
294
295
296
297
298 static int parse_lines(CGIResponseParser *parser,
char *buf,
size_t len,
int *pos) {
299 UcxAllocator a = util_pool_allocator(parser->sn->pool);
300 sstr_t name;
301 sstr_t value;
302 WSBool space =
TRUE;
303 int i;
304
305 int line_begin =
0;
306 int value_begin =
0;
307 for(i=
0;i<len;i++) {
308 char c = buf[i];
309 if(value_begin == line_begin && c ==
':') {
310 name = sstrn(buf + line_begin, i - line_begin);
311 value_begin = i +
1;
312 }
else if(c ==
'\n') {
313 if(value_begin == line_begin) {
314 if(space) {
315 *pos = i +
1;
316 return 2;
317 }
else {
318
319 return -
1;
320 }
321 }
322 value = sstrn(buf + value_begin, i - value_begin);
323
324 name = sstrlower_a(&a, sstrtrim(name));
325 value = sstrtrim(value);
326
327 if(name.length ==
0 || value.length ==
0) {
328 return -
1;
329 }
330
331 if(!sstrcmp(name,
S(
"status"))) {
332 sstr_t codestr = value;
333 int j;
334 for(j=
0;j<codestr.length;j++) {
335 if(!isdigit(codestr.ptr[j])) {
336 break;
337 }
338 if(j >
2) {
339 break;
340 }
341 }
342 codestr.ptr[j] =
'\0';
343
344 int64_t s =
0;
345 util_strtoint(codestr.ptr, &s);
346 parser->status = (
int)s;
347
348 sstr_t msg = sstrtrim(sstrsubs(value, j +
1));
349
350 if(msg.length >
0) {
351 parser->msg = sstrdup_pool(parser->sn->pool, msg).ptr;
352 }
353 }
else {
354 pblock_nvlinsert(
355 name.ptr,
356 name.length,
357 value.ptr,
358 value.length,
359 parser->rq->srvhdrs);
360 }
361
362 line_begin = i+
1;
363 value_begin = line_begin;
364 space =
TRUE;
365 }
else if(!isspace(c)) {
366 space =
FALSE;
367 }
368 }
369
370 if(i < len) {
371 *pos = i;
372 return 0;
373 }
374 return 1;
375 }
376
377
378
379
380
381
382 int cgi_parse_response(CGIResponseParser *parser,
char *buf,
size_t len,
size_t *bpos) {
383 *bpos =
0;
384 int pos =
0;
385 if(parser->tmp->pos >
0) {
386
387
388 WSBool nb =
FALSE;
389 for(pos=
0;pos<len;pos++) {
390 if(buf[pos] ==
'\n') {
391 nb =
TRUE;
392 break;
393 }
394 }
395 ucx_buffer_write(buf,
1, pos, parser->tmp);
396
397 if(nb) {
398
399 int npos;
400 int r = parse_lines(parser, parser->tmp->space, parser->tmp->pos, &npos);
401 switch(r) {
402 case -
1:
return -
1;
403 case 0:
return -
1;
404 case 1:
break;
405 case 2: {
406 *bpos = pos +
1;
407 return 1;
408 }
409 }
410
411 parser->tmp->pos =
0;
412 }
else {
413 if(parser->tmp->pos >
CGI_RESPONSE_MAX_LINE_LENGTH) {
414 return -
1;
415 }
416 }
417 }
418
419 int npos =
0;
420 int r = parse_lines(parser, buf + pos, len - pos, &npos);
421 switch(r) {
422 default:
return -
1;
423 case 0:
424 case 1: {
425 int newlen = len - npos;
426 if(npos >
0) {
427 ucx_buffer_write(buf + npos,
1, newlen, parser->tmp);
428 }
429 return 0;
430 }
431 case 2: {
432 *bpos = pos + npos;
433 return 1;
434 }
435 }
436 }
437