src/server/safs/cgi.c

Fri, 28 Oct 2016 19:29:38 +0200

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Fri, 28 Oct 2016 19:29:38 +0200
changeset 120
d2eb5fd97df0
parent 119
155cbab9eefd
child 121
a881dc866e23
permissions
-rw-r--r--

adds chdir before cgi execution

/*
 * 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 <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include "../util/util.h"
#include "../util/pblock.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_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) {
        return ret;
    }
    
    char buf[4096]; // I/O buffer
    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) {
                // TODO: handleerror
                return REQ_ABORTED; 
            }
            write(cgip.in[1], buf, r);
            n += r;
        }
    }
    close(cgip.in[1]);
    
    // read from child
    CGIResponseParser *parser = cgi_parser_new(sn, rq);
    WSBool cgiheader = TRUE;
    
    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
        
        // get script directory and script name
        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);
        }
        
        // we need to close this unused pipe
        // otherwise stdin cannot reach EOF
        close(p->in[1]);
        
        // execute program
        exit(execve(script.ptr, 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<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 {
                    // line ends with content but without ':' -> 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;pos<len;pos++) {
            if(buf[pos] == '\n') {
                nb = TRUE;
                break;
            }
        }
        ucx_buffer_write(buf, 1, pos, parser->tmp);
        
        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;
        }
    }
}

mercurial