diff -r a94cf2e94492 -r 38bf6dd8f4e7 src/server/safs/cgi.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/safs/cgi.c Wed Oct 26 15:53:56 2016 +0200 @@ -0,0 +1,270 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2016 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "cgi.h" + +#include +#include +#include + +#include "../util/util.h" +#include "../../ucx/string.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_findval("path", rq->vars); + + 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) { + return ret; + } + + // TODO: send http body + close(cgip.in[1]); + + // read from child + CGIResponseParser *parser = cgi_parser_new(sn, rq); + WSBool cgiheader = TRUE; + + char buf[4096]; + ssize_t r; + while((r = read(cgip.out[0], buf, 4096)) > 0) { + if(cgiheader) { + size_t pos; + int ret = cgi_parse_response(parser, buf, r, &pos); + if(ret == -1) { + protocol_status(sn, rq, 500, NULL); + return REQ_ABORTED; + } else if(ret == 0) { + + } else if(ret == 1) { + cgiheader = FALSE; + http_start_response(sn, rq); + if(pos < r) { + net_write(sn->csd, buf+pos, r-pos); + } + } + } else { + net_write(sn->csd, buf, r); + } + } + + return REQ_PROCEED; +} + +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) { + // child + 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); + } + + // we need to close this unused pipe + // otherwise stdin cannot reach EOF + close(p->in[1]); + + // execute program + exit(execve(path, argv, envp)); + } else { + // parent + close(p->out[1]); + } + + return REQ_PROCEED; +} + +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); + return parser; +} + +/* + * parses a cgi response line and adds the response header to rq->srvhdrs + * returns 0: incomplete line + * 1: successfully parsed lines + * 2: cgi response header complete (empty line) + * -1: error + */ +static int parse_lines(CGIResponseParser *parser, char *buf, size_t len, int *pos) { + sstr_t name; + sstr_t value; + WSBool space = TRUE; + int i; + + int line_begin = 0; + int value_begin = 0; + for(i=0;i error + return -1; + } + } + value = sstrn(buf + value_begin, i - value_begin); + + name = sstrtrim(name); + value = sstrtrim(value); + + if(name.length == 0 || value.length == 0) { + return -1; + } + 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(c != ' ') { + space = FALSE; + } + } + + if(i < len) { + *pos = i; + return 0; + } + return 1; +} + +/* + * returns -1: error + * 0: response header incomplete + * 1: complete + */ +int cgi_parse_response(CGIResponseParser *parser, char *buf, size_t len, size_t *bpos) { + *bpos = 0; + int pos = 0; + if(parser->tmp->pos > 0) { + // the tmp buffer contains an unfinished line + // fill up the buffer until the line is complete + WSBool nb = FALSE; + for(pos=0;postmp); + + if(nb) { + // line complete + 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; + } + } + // reset tmp buffer + 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; + } + } +} +