adds minimal cgi implementation

Wed, 26 Oct 2016 15:53:56 +0200

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Wed, 26 Oct 2016 15:53:56 +0200
changeset 118
38bf6dd8f4e7
parent 117
a94cf2e94492
child 119
155cbab9eefd

adds minimal cgi implementation

some env vars still missing
request body not supported yet

src/server/daemon/http.c file | annotate | diff | comparison | revisions
src/server/daemon/netsite.h file | annotate | diff | comparison | revisions
src/server/daemon/ws-fn.c file | annotate | diff | comparison | revisions
src/server/public/nsapi.h file | annotate | diff | comparison | revisions
src/server/safs/cgi.c file | annotate | diff | comparison | revisions
src/server/safs/cgi.h file | annotate | diff | comparison | revisions
src/server/safs/cgiutils.c file | annotate | diff | comparison | revisions
src/server/safs/cgiutils.h file | annotate | diff | comparison | revisions
src/server/safs/objs.mk file | annotate | diff | comparison | revisions
src/server/util/util.c file | annotate | diff | comparison | revisions
templates/config/obj.conf file | annotate | diff | comparison | revisions
--- a/src/server/daemon/http.c	Sun Oct 23 11:12:12 2016 +0200
+++ b/src/server/daemon/http.c	Wed Oct 26 15:53:56 2016 +0200
@@ -246,3 +246,65 @@
 }
 
 
+/* ---------------------------- http_hdrs2env ----------------------------- */
+
+NSAPI_PUBLIC char **http_hdrs2env(pblock *pb)
+{
+    char *t, *n, **env;
+    struct pb_entry *p;
+    pb_param *pp;
+    register int x, y, z;
+    int pos, ts, ln;
+
+    /* Find out how many there are. */
+    for(x = 0, y = 0; x < pb->hsize; x++) {
+        p = pb->ht[x];
+        while(p) {
+            ++y;
+            p = p->next;
+        }
+    }
+
+    env = util_env_create(NULL, y, &pos);
+
+    ts = 1024;
+    t = (char *) MALLOC(ts);
+
+    for(x = 0; x < pb->hsize; x++) {
+        p = pb->ht[x];
+
+        while(p) {
+            pp = p->param;
+
+            ln = strlen(pp->name) + 6;
+
+            if(ln >= ts) {
+                ts = ln;
+                t = (char *) REALLOC(t, ts);
+            }
+            n = pp->name;
+
+            /* Skip authorization for CGI */
+            if(strcasecmp(n, "authorization")) {
+                if(strcasecmp(n, "content-type") &&
+                   strcasecmp(n, "content-length")) 
+                {
+                    strncpy(t, "HTTP_", 5);
+                    z = 5;
+                }
+                else
+                    z = 0;
+
+                for(y = 0; n[y]; ++y, ++z)
+                    t[z] = (n[y] == '-' ? '_' : toupper(n[y]));
+                t[z] = '\0';
+
+                env[pos++] = util_env_str(t, pp->value);
+            }
+            p = p->next;
+        }
+    }
+    env[pos] = NULL;
+    FREE(t);
+    return env;
+}
--- a/src/server/daemon/netsite.h	Sun Oct 23 11:12:12 2016 +0200
+++ b/src/server/daemon/netsite.h	Wed Oct 26 15:53:56 2016 +0200
@@ -99,7 +99,6 @@
 //define FREE(ptr) INTsystem_free(ptr)
 NSAPI_PUBLIC void INTsystem_free(void *ptr);
 
-#define STRDUP(ptr) INTsystem_strdup(ptr)
 NSAPI_PUBLIC char *INTsystem_strdup(const char *ptr);
 
 /*
--- a/src/server/daemon/ws-fn.c	Sun Oct 23 11:12:12 2016 +0200
+++ b/src/server/daemon/ws-fn.c	Wed Oct 26 15:53:56 2016 +0200
@@ -37,6 +37,7 @@
 #include "../safs/init.h"
 #include "../safs/common.h"
 #include "../safs/addlog.h"
+#include "../safs/cgi.h"
 #include "../webdav/webdav.h"
 
 #include "../admin/admin.h"
@@ -62,5 +63,6 @@
     { "check-acl", check_acl, NULL, NULL, 0},
     { "print-message", print_message, NULL, NULL, 0},
     { "common-log", common_log, NULL, NULL, 0},
+    { "send-cgi", send_cgi, NULL, NULL, 0},
     {NULL, NULL, NULL, NULL, 0}
 };
--- a/src/server/public/nsapi.h	Sun Oct 23 11:12:12 2016 +0200
+++ b/src/server/public/nsapi.h	Wed Oct 26 15:53:56 2016 +0200
@@ -383,6 +383,13 @@
 
 /* --- End native platform includes --- */
 
+#ifndef TRUE
+#define TRUE 1
+#endif
+#ifndef FALSE
+#define FALSE 0
+#endif
+
 /* --- Begin type definitions --- */
 
 /* NOTE: both SYS_FILE and SYS_NETFD are actually NSPR PRFileDesc * and can */
@@ -1359,6 +1366,7 @@
 #define CALLOC(size) calloc(1, size)
 #define REALLOC realloc
 #define FREE    free
+#define STRDUP  strdup
 
 // func util functions
 FuncStruct* func_resolve(pblock *pb, Session *sn, Request *rq);
@@ -1384,6 +1392,7 @@
 NSAPI_PUBLIC int http_check_preconditions(Session *sn, Request *rq, struct tm *mtm, const char *etag);
 NSAPI_PUBLIC int http_set_finfo(Session *sn, Request *rq, struct stat *finfo);
 
+NSAPI_PUBLIC char **http_hdrs2env(pblock *pb);
 
 typedef void (*thrstartfunc)(void *);
 SYS_THREAD INTsysthread_start(int prio, int stksz, thrstartfunc fn, void *arg);
--- /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 <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#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<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;
+        }
+    }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/safs/cgi.h	Wed Oct 26 15:53:56 2016 +0200
@@ -0,0 +1,65 @@
+/*
+ * 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.
+ */
+
+#ifndef CGI_H
+#define CGI_H
+
+#include "../public/nsapi.h"
+#include "../../ucx/buffer.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct {
+    int in[2];
+    int out[2];
+    pid_t pid;
+} CGIProcess;
+
+typedef struct {
+    Session   *sn;
+    Request   *rq;
+    UcxBuffer *tmp;
+} CGIResponseParser;
+    
+int send_cgi(pblock *pb, Session *sn, Request *rq);
+
+char** cgi_add_vars(char **env, Session *sn, Request *rq);
+
+int cgi_start(CGIProcess *p, char *path, char *const argv[], char *const envp[]);
+
+CGIResponseParser* cgi_parser_new(Session *sn, Request *rq);
+int cgi_parse_response(CGIResponseParser *parser, char *buf, size_t len, size_t *bpos);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* CGI_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/safs/cgiutils.c	Wed Oct 26 15:53:56 2016 +0200
@@ -0,0 +1,379 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2008 Sun Microsystems, Inc. All rights reserved.
+ *
+ * THE BSD LICENSE
+ *
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer. 
+ * 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. 
+ *
+ * Neither the name of the  nor the names of its contributors may be
+ * used to endorse or promote products derived from this software without 
+ * specific prior written permission. 
+ *
+ * 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 OWNER 
+ * 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.
+ */
+
+// code from open webserver
+// safs/cgi.cpp
+
+#include "cgiutils.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "../util/util.h"
+#include "../util/pblock.h"
+
+
+//-----------------------------------------------------------------------------
+// Macros
+//-----------------------------------------------------------------------------
+
+#define CGI_VERSION "CGI/1.1"
+
+// CGI env sizing
+#define MAX_CGI_COMMON_VARS      23
+#define MAX_CGI_SPECIFIC_VARS    10
+#define MAX_CGI_CLIENT_AUTH_VARS 64
+
+//-----------------------------------------------------------------------------
+// cgi_parse_query
+//-----------------------------------------------------------------------------
+
+int cgi_parse_query(char **argv, int n, char *q)
+{
+    int i = 0;
+
+    for (;;) {
+        // Parse an arg from the query string
+        char *arg = q;
+        while (*q != ' ' && *q != '\0')
+            q++;
+
+        // Caller must ensure argv[] is appropriately sized
+        WS_ASSERT(i < n);
+        if (i >= n)
+            return -1;
+
+        // Escape any shell characters (does a MALLOC on our behalf)
+        char c = *q;
+        *q = '\0';
+        argv[i] = util_sh_escape(arg); // TODO
+        *q = c;
+
+        // Unescape this arg, bailing on error
+        if (!argv[i] || !util_uri_unescape_strict(argv[i]))
+            return -1;
+
+        // We successfully parsed another arg
+        i++;
+
+        // We're done when we hit the end of the query string
+        if (*q == '\0')
+            break;
+
+        q++;
+    }
+
+    return i;
+}
+
+//-----------------------------------------------------------------------------
+// cgi_create_argv
+//-----------------------------------------------------------------------------
+
+char **cgi_create_argv(const char *program, const char *script, const char *query)
+{
+    int nargs = 1;
+
+    // The script name, if any, will be argv[1]
+    if (script)
+        nargs++;
+
+    // Turn '+' into ' ' in query string, counting the number of arg as we go
+    char *qargs = NULL;
+    if (query && !strchr(query, '=')) {
+        qargs = STRDUP(query);
+        if (!qargs)
+            return NULL;
+        nargs++;
+        for (char *t = qargs; *t; t++) {
+            if (*t == '+')
+                *t = ' ';
+            if (*t == ' ')
+                nargs++;
+        }
+    }
+
+    // Allocate the argv[] array, leaving room for the trailing NULL
+    char **argv = (char **) MALLOC((nargs + 1) * sizeof(char *));
+    if (!argv)
+        return NULL;
+
+    int i = 0;
+
+    // Set argv[0] to the program name
+    argv[i] = STRDUP(program);
+    i++;
+
+    // Set argv[1] to the script name
+    if (script) {
+        argv[i] = STRDUP(script);
+        i++;
+    }
+
+    // Parse the query string into argv[]
+    if (qargs) {
+        int n = cgi_parse_query(&argv[i], nargs - i, qargs);
+        if (n > 0)
+            i += n;
+    }
+
+    // Mark end of argv[]
+    argv[i] = NULL;
+
+    return argv;
+}
+
+//----------------------------------------------------------------------------
+// cgi_get_request_uri
+//----------------------------------------------------------------------------
+
+char* cgi_get_request_uri(Request *req)
+{
+    // Extract the encoded URI from the request line if possible
+    char *clf_request = pblock_findkeyval(pb_key_clf_request, req->reqpb);
+    if (clf_request) {
+        // Find the beginning of the method
+        char *method = clf_request;
+        while (*method && isspace(*method))
+            method++;
+
+        // Skip over the method
+        char *uri = method;
+        while (*uri && !isspace(*uri))
+            uri++;
+
+        // Find the beginning of the URI
+        while (*uri && isspace(*uri))
+            uri++;
+
+        // Find the end of the URI
+        char *end = uri;
+        while (*end && !isspace(*end)) 
+            end++;
+
+        // Make a copy of the uri
+        int len = end - uri;
+        char* request_uri = (char*) MALLOC(len+1);
+        memcpy(request_uri, uri, len);
+        request_uri[len] = '\0';
+
+        return request_uri;
+    }
+
+    // No request line.
+    return NULL;
+}
+
+//-----------------------------------------------------------------------------
+// cgi_specific_vars
+//-----------------------------------------------------------------------------
+
+char** cgi_specific_vars(Session *sn, Request *rq, const char *args,
+                         char** env, int scriptVars)
+{
+    int y;
+    register pblock *pb;
+    char c, *t, *u;
+
+    pb = rq->reqpb;
+
+    int x;
+    env = util_env_create(env, MAX_CGI_SPECIFIC_VARS, &x);
+    env[x++] = util_env_str("GATEWAY_INTERFACE", CGI_VERSION);
+
+    // Added error check for missing request information.
+    t = pblock_findval("protocol", pb);
+    if (t == NULL) {
+        log_ereport(LOG_FAILURE, "cgi_specific_vars", "missing \"protocol\" in rq->reqpb");
+        return NULL;
+    }
+    env[x++] = util_env_str("SERVER_PROTOCOL", t);
+
+    t = pblock_findval("method", pb);
+    if (t == NULL) {
+        log_ereport(LOG_FAILURE, "cgi_specific_vars", "missing \"request method\" in rq->reqpb");
+        return NULL;
+    }
+    env[x++] = util_env_str("REQUEST_METHOD", t);
+
+    if (args)
+        env[x++] = util_env_str("QUERY_STRING", args);
+
+    // set REQUEST_URI
+    if (rq->orig_rq) {
+        t = cgi_get_request_uri(rq->orig_rq);
+    } else {
+        t = cgi_get_request_uri(rq);
+    }
+    if (t) {
+        env[x++] = util_env_str("REQUEST_URI", t);
+        FREE(t);
+    }
+
+    if (scriptVars) {
+        t = pblock_findval("uri", pb);
+
+        /* Simulate CGI URIs by truncating path info */
+        if ((u = pblock_findval("path-info", rq->vars))) {
+            y = strlen(t) - strlen(u);
+            if (y >= 0) {
+                c = t[y];
+                t[y] = '\0';
+            }
+            env[x++] = util_env_str("SCRIPT_NAME", t);
+            if (y >= 0) {
+                t[y] = c;
+            }
+
+            env[x++] = util_env_str("PATH_INFO", u);
+            
+            // TODO
+            /*
+            if((t = INTservact_translate_uri2(u, sn, rq))) {
+                env[x++] = util_env_str("PATH_TRANSLATED", t);
+                // keep path-translated in rq->vars since we may need it
+                // during fastcgi processing
+                pblock_nvinsert("path-translated", t, rq->vars);
+                FREE(t);
+            }
+            */
+        } else {
+            env[x++] = util_env_str("SCRIPT_NAME", t);
+        }
+
+        if (t = pblock_findval("path", rq->vars))
+            env[x++] = util_env_str("SCRIPT_FILENAME", t);
+    }
+
+    env[x] = NULL;
+    return env;
+}
+
+//-----------------------------------------------------------------------------
+// cgi_common_vars
+//-----------------------------------------------------------------------------
+
+char** cgi_common_vars(Session *sn, Request *rq, char **env) 
+{
+    // TODO: enable code and add cgi_client_auth_vars()
+    return env;
+#if 0
+    char *t;
+    int x;
+
+    env = util_env_create(env, MAX_CGI_COMMON_VARS, &x);
+
+    if (!_env_initialized) cgi_env_init();
+    if (_env_path) env[x++] = util_env_str("PATH", _env_path);
+    if (_env_tz) env[x++] = util_env_str("TZ", _env_tz);
+    if (_env_lang) env[x++] = util_env_str("LANG", _env_lang);
+    if (_env_ldpath) env[x++] = util_env_str(LIBRARY_PATH, _env_ldpath);
+    env[x++] = util_env_str("SERVER_SOFTWARE", PRODUCT_HEADER_ID"/"PRODUCT_VERSION_ID);
+
+    NSString srvName, portStr;
+    char buf1[256], buf2[64];
+    srvName.useStatic(buf1, sizeof(buf1), 0); 
+    portStr.useStatic(buf2, sizeof(buf2), 0); 
+    GetServerHostnameAndPort(*rq, *sn, srvName, portStr);
+    env[x++] = util_env_str("SERVER_PORT", (char*)(const char*)portStr);
+    env[x++] = util_env_str("SERVER_NAME", (char*)(const char*)srvName);
+
+    t = http_uri2url_dynamic("","",sn,rq);
+    env[x++] = util_env_str("SERVER_URL", t);
+    FREE(t);
+
+    char *ip;
+    ip = pblock_findval("ip", sn->client);
+    t = session_dns(sn);
+    env[x++] = util_env_str("REMOTE_HOST", (t ? t : ip));
+    env[x++] = util_env_str("REMOTE_ADDR", ip);
+
+    if((t = pblock_findval("auth-user", rq->vars))) {
+        env[x++] = util_env_str("REMOTE_USER", t);
+         if((t = pblock_findval("auth-type", rq->vars))) {
+             env[x++] = util_env_str("AUTH_TYPE", t);
+        }
+        if((t = pblock_findval("auth-userdn", rq->vars))) {
+            env[x++] = util_env_str("REMOTE_USERDN", t);
+        }
+    }
+    if((t = pblock_findval("password-policy", rq->vars))) {
+        /* chrisk made up this variable name */
+        env[x++] = util_env_str("PASSWORD_POLICY", t);
+    }
+
+    // Handle Apache ErrorDocument-style variables from the send-error SAF
+    if (rq->orig_rq != rq) {
+        if (t = pblock_findval("uri", rq->orig_rq->reqpb)) {
+            env[x++] = util_env_str("REDIRECT_URL", t);
+        }
+        if (t = pblock_findval("status", rq->orig_rq->srvhdrs)) {
+            env[x++] = util_env_str("REDIRECT_STATUS", t);
+        }
+    }
+
+    if (GetSecurity(sn)) {
+        env[x++] = util_env_str("HTTPS", "ON");
+
+        if (t = pblock_findval("keysize", sn->client))
+            env[x++] = util_env_str("HTTPS_KEYSIZE", t);
+
+        if (t = pblock_findval("secret-keysize", sn->client))
+            env[x++] = util_env_str("HTTPS_SECRETKEYSIZE", t);
+
+        t = pblock_findval("ssl-id", sn->client);
+        env[x++] = util_env_str("HTTPS_SESSIONID", t ? t : (char *)"");
+
+        unsigned char random_bytes[NUM_RANDOM_BYTES + 2];
+        char random_string[NUM_RANDOM_BYTES*2 + 2];
+        PK11_GenerateRandom(random_bytes, NUM_RANDOM_BYTES);
+
+        int i;
+        for(i = 0; i < NUM_RANDOM_BYTES; i++)  {
+            sprintf(&random_string[i*2], "%02x", 
+                    (unsigned int)(random_bytes[i] & 0xff));
+        }
+        random_string[NUM_RANDOM_BYTES*2] = '\0';
+        env[x++] = util_env_str("HTTPS_RANDOM", random_string);
+
+    } else {
+        env[x++] = util_env_str("HTTPS", "OFF");
+    }
+
+    env[x] = NULL;
+
+    env = cgi_client_auth_vars(sn, rq, env);
+
+    return env;
+#endif
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/safs/cgiutils.h	Wed Oct 26 15:53:56 2016 +0200
@@ -0,0 +1,50 @@
+/*
+ * 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.
+ */
+
+#ifndef CGIUTILS_H
+#define CGIUTILS_H
+
+#include "../public/nsapi.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+int cgi_parse_query(char **argv, int n, char *q);
+char **cgi_create_argv(const char *program, const char *script, const char *query);
+char* cgi_get_request_uri(Request *req);
+char** cgi_specific_vars(Session *sn, Request *rq, const char *args,
+                         char** env, int scriptVars);
+char** cgi_common_vars(Session *sn, Request *rq, char **env);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* CGIUTILS_H */
+
--- a/src/server/safs/objs.mk	Sun Oct 23 11:12:12 2016 +0200
+++ b/src/server/safs/objs.mk	Wed Oct 26 15:53:56 2016 +0200
@@ -39,6 +39,8 @@
 SAFOBJ += pcheck.o
 SAFOBJ += common.o
 SAFOBJ += addlog.o
+SAFOBJ += cgi.o
+SAFOBJ += cgiutils.o
 
 SAFOBJS = $(SAFOBJ:%=$(SAFS_OBJPRE)%)
 SAFSOURCE = $(SAFOBJ:%.o=safs/%.c)
--- a/src/server/util/util.c	Sun Oct 23 11:12:12 2016 +0200
+++ b/src/server/util/util.c	Wed Oct 26 15:53:56 2016 +0200
@@ -149,10 +149,11 @@
 
 NSAPI_PUBLIC char *util_env_str(const char *name, const char *value) {
     char *t;
+    
+    size_t len = strlen(name) + strlen(value) + 2;
+    t = (char *) MALLOC(len); /* 2: '=' and '\0' */
 
-    t = (char *) MALLOC(strlen(name)+strlen(value)+2); /* 2: '=' and '\0' */
-
-    sprintf(t, "%s=%s", name, value);
+    snprintf(t, len, "%s=%s", name, value);
 
     return t;
 }
@@ -182,6 +183,24 @@
 }
 
 
+/* ---------------------------- util_sh_escape ---------------------------- */
+
+
+NSAPI_PUBLIC char *util_sh_escape(char *s)
+{
+    char *ns = (char *) MALLOC(strlen(s) * 2 + 1);   /* worst case */
+    register char *t, *u;
+
+    for(t = s, u = ns; *t; ++t, ++u) {
+        if(strchr("&;`'\"|*?~<>^()[]{}$\\ #!", *t))
+            *u++ = '\\';
+        *u = *t;
+    }
+    *u = '\0';
+    return ns;
+}
+
+
 /* ---------------------------- util_env_find ----------------------------- */
 
 
--- a/templates/config/obj.conf	Sun Oct 23 11:12:12 2016 +0200
+++ b/templates/config/obj.conf	Wed Oct 26 15:53:56 2016 +0200
@@ -22,3 +22,7 @@
 <Object name="admin">
 Service fn="admin-service"
 </Object>
+
+<Object name="cgi-bin">
+Service fn="send-cgi"
+</Object>

mercurial