#include "cgi.h"
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
#include <sys/wait.h>
#include "../util/util.h"
#include "../util/pblock.h"
#include "../../ucx/string.h"
#include "../daemon/netsite.h"
#include "../util/io.h"
#include "cgiutils.h"
#define CGI_VARS 32
#define CGI_RESPONSE_PARSER_BUFLEN 2048
#define CGI_RESPONSE_MAX_LINE_LENGTH 512
int send_cgi(pblock *pb, Session *sn, Request *rq) {
char *path = pblock_findkeyval(pb_key_path, rq->vars);
char *ctlen = pblock_findkeyval(pb_key_content_length, rq->headers);
int64_t content_length =
0;
if(ctlen) {
if(!util_strtoint(ctlen, &content_length)) {
log_ereport(
LOG_FAILURE,
"send-cgi: content-length header is not an integer");
protocol_status(sn, rq,
400,
NULL);
return REQ_ABORTED;
}
}
struct stat s;
if(stat(path, &s)) {
int statuscode = util_errno2status(errno);
protocol_status(sn, rq, statuscode,
NULL);
return REQ_ABORTED;
}
if(
S_ISDIR(s.st_mode)) {
protocol_status(sn, rq,
403,
NULL);
return REQ_ABORTED;
}
param_free(pblock_remove(
"content-type", rq->srvhdrs));
const char *args = pblock_findval(
"query", rq->reqpb);
char **argv = cgi_create_argv(path,
NULL, args);
char **env = http_hdrs2env(rq->headers);
env = cgi_common_vars(sn, rq, env);
env = cgi_specific_vars(sn, rq, args, env,
1);
CGIProcess cgip;
int ret = cgi_start(&cgip, path, argv, env);
if(ret !=
REQ_PROCEED) {
util_env_free(env);
cgi_free_argv(argv);
return ret;
}
util_env_free(env);
cgi_free_argv(argv);
char buf[
4096];
ssize_t r;
if(content_length >
0) {
ssize_t n =
0;
while(n < content_length) {
r = netbuf_getbytes(sn->inbuf, buf,
4096);
if(r <=
0) {
log_ereport(
LOG_FAILURE,
"send-cgi: script: %s: cannot read request body",
path);
kill(cgip.pid,
SIGTERM);
cgi_close(&cgip);
return REQ_ABORTED;
}
ssize_t w = write(cgip.in[
1], buf, r);
if(w <=
0) {
log_ereport(
LOG_FAILURE,
"send-cgi: script: %s: cannot send request body to cgi process",
path);
kill(cgip.pid,
SIGKILL);
cgi_close(&cgip);
return REQ_ABORTED;
}
n += r;
}
}
system_close(cgip.in[
1]);
cgip.in[
1] = -
1;
CGIResponseParser *parser = cgi_parser_new(sn, rq);
WSBool cgiheader =
TRUE;
ssize_t wr =
0;
int result =
REQ_PROCEED;
size_t response_length =
0;
while((r = read(cgip.out[
0], buf,
4096)) >
0) {
if(cgiheader) {
size_t pos;
ret = cgi_parse_response(parser, buf, r, &pos);
if(ret == -
1) {
log_ereport(
LOG_FAILURE,
"broken cgi script response: path: %s", path);
protocol_status(sn, rq,
500,
NULL);
result =
REQ_ABORTED;
break;
}
else if(ret ==
1) {
cgiheader =
FALSE;
if(parser->status >
0) {
protocol_status(sn, rq, parser->status, parser->msg);
}
http_start_response(sn, rq);
if(pos < r) {
response_length += r-pos;
wr = net_write(sn->csd, &buf[pos], r-pos);
if(wr <=
0) {
result =
REQ_ABORTED;
break;
}
}
}
}
else {
response_length += r;
wr = net_write(sn->csd, buf, r);
if(wr <=
0) {
result =
REQ_ABORTED;
break;
}
}
}
char *ctlen_header = pblock_findkeyval(pb_key_content_length, rq->srvhdrs);
if(ctlen_header) {
int64_t ctlenhdr;
if(util_strtoint(ctlen_header, &ctlenhdr)) {
if(ctlenhdr != response_length) {
log_ereport(
LOG_FAILURE,
"cgi-send: script: %s: content length mismatch",
path);
rq->rq_attr.keep_alive =
0;
result =
REQ_ABORTED;
}
}
}
if(result ==
REQ_ABORTED) {
log_ereport(
LOG_FAILURE,
"cgi-send: kill script: %s", path);
kill(cgip.pid,
SIGKILL);
}
cgi_close(&cgip);
cgi_parser_free(parser);
return result;
}
int cgi_start(CGIProcess *p,
char *path,
char *
const argv[],
char *
const envp[]) {
if(pipe(p->in) || pipe(p->out)) {
log_ereport(
LOG_FAILURE,
"send-cgi: cannot create pipe: %s",
strerror(errno));
return REQ_ABORTED;
}
p->pid = fork();
if(p->pid ==
0) {
sstr_t script = sstr(path);
sstr_t parent;
int len = strlen(path);
for(
int i=len-
1;i>=
0;i--) {
if(path[i] ==
'/') {
script = sstrn(path + i +
1, len - i);
parent = sstrdup(sstrn(path, i));
if(chdir(parent.ptr)) {
perror(
"cgi_start: chdir");
free(parent.ptr);
exit(-
1);
}
free(parent.ptr);
break;
}
}
if(dup2(p->in[
0],
STDIN_FILENO) == -
1) {
perror(
"cgi_start: dup2");
exit(
EXIT_FAILURE);
}
if(dup2(p->out[
1],
STDOUT_FILENO) == -
1) {
perror(
"cgi_start: dup2");
exit(
EXIT_FAILURE);
}
system_close(p->in[
1]);
exit(execve(script.ptr, argv, envp));
}
else {
system_close(p->out[
1]);
p->out[
1] = -
1;
}
return REQ_PROCEED;
}
int cgi_close(CGIProcess *p) {
int status = -
1;
waitpid(p->pid, &status,
0);
if(p->in[
0] != -
1) {
system_close(p->in[
0]);
}
if(p->in[
1] != -
1) {
system_close(p->in[
1]);
}
if(p->out[
0] != -
1) {
system_close(p->out[
0]);
}
if(p->out[
1] != -
1) {
system_close(p->out[
1]);
}
return 0;
}
CGIResponseParser* cgi_parser_new(Session *sn, Request *rq) {
CGIResponseParser* parser = pool_malloc(sn->pool,
sizeof(CGIResponseParser));
parser->sn = sn;
parser->rq = rq;
parser->tmp = ucx_buffer_new(
NULL,
64,
UCX_BUFFER_AUTOEXTEND);
parser->status =
0;
parser->msg =
NULL;
return parser;
}
void cgi_parser_free(CGIResponseParser *parser) {
if(parser->tmp) {
ucx_buffer_free(parser->tmp);
}
pool_free(parser->sn->pool, parser);
}
static int parse_lines(CGIResponseParser *parser,
char *buf,
size_t len,
int *pos) {
UcxAllocator a = util_pool_allocator(parser->sn->pool);
sstr_t name;
sstr_t value;
WSBool space =
TRUE;
int i;
int line_begin =
0;
int value_begin =
0;
for(i=
0;i<len;i++) {
char c = buf[i];
if(value_begin == line_begin && c ==
':') {
name = sstrn(buf + line_begin, i - line_begin);
value_begin = i +
1;
}
else if(c ==
'\n') {
if(value_begin == line_begin) {
if(space) {
*pos = i +
1;
return 2;
}
else {
return -
1;
}
}
value = sstrn(buf + value_begin, i - value_begin);
name = sstrlower_a(&a, sstrtrim(name));
value = sstrtrim(value);
if(name.length ==
0 || value.length ==
0) {
return -
1;
}
if(!sstrcmp(name,
S(
"status"))) {
sstr_t codestr = value;
int j;
for(j=
0;j<codestr.length;j++) {
if(!isdigit(codestr.ptr[j])) {
break;
}
if(j >
2) {
break;
}
}
codestr.ptr[j] =
'\0';
int64_t s =
0;
util_strtoint(codestr.ptr, &s);
parser->status = (
int)s;
sstr_t msg = sstrtrim(sstrsubs(value, j +
1));
if(msg.length >
0) {
parser->msg = sstrdup_pool(parser->sn->pool, msg).ptr;
}
}
else {
pblock_nvlinsert(
name.ptr,
name.length,
value.ptr,
value.length,
parser->rq->srvhdrs);
}
line_begin = i+
1;
value_begin = line_begin;
space =
TRUE;
}
else if(!isspace(c)) {
space =
FALSE;
}
}
if(i < len) {
*pos = i;
return 0;
}
return 1;
}
int cgi_parse_response(CGIResponseParser *parser,
char *buf,
size_t len,
size_t *bpos) {
*bpos =
0;
int pos =
0;
if(parser->tmp->pos >
0) {
WSBool nb =
FALSE;
for(pos=
0;pos<len;pos++) {
if(buf[pos] ==
'\n') {
nb =
TRUE;
break;
}
}
ucx_buffer_write(buf,
1, pos, parser->tmp);
if(nb) {
int npos;
int r = parse_lines(parser, parser->tmp->space, parser->tmp->pos, &npos);
switch(r) {
case -
1:
return -
1;
case 0:
return -
1;
case 1:
break;
case 2: {
*bpos = pos +
1;
return 1;
}
}
parser->tmp->pos =
0;
}
else {
if(parser->tmp->pos >
CGI_RESPONSE_MAX_LINE_LENGTH) {
return -
1;
}
}
}
int npos =
0;
int r = parse_lines(parser, buf + pos, len - pos, &npos);
switch(r) {
default:
return -
1;
case 0:
case 1: {
int newlen = len - npos;
if(npos >
0) {
ucx_buffer_write(buf + npos,
1, newlen, parser->tmp);
}
return 0;
}
case 2: {
*bpos = pos + npos;
return 1;
}
}
}