[BACK]Return to cgi-bozo.c CVS log [TXT][DIR] Up to [cvs.NetBSD.org] / src / libexec / httpd

Annotation of src/libexec/httpd/cgi-bozo.c, Revision 1.6

1.6     ! rtr         1: /*     $NetBSD: cgi-bozo.c,v 1.5 2007/11/04 06:07:52 rtr Exp $ */
1.4       tls         2:
1.1       tls         3: /*     $eterna: cgi-bozo.c,v 1.13 2006/05/17 08:19:10 mrg Exp $        */
                      4:
                      5: /*
                      6:  * Copyright (c) 1997-2006 Matthew R. Green
                      7:  * All rights reserved.
                      8:  *
                      9:  * Redistribution and use in source and binary forms, with or without
                     10:  * modification, are permitted provided that the following conditions
                     11:  * are met:
                     12:  * 1. Redistributions of source code must retain the above copyright
                     13:  *    notice, this list of conditions and the following disclaimer.
                     14:  * 2. Redistributions in binary form must reproduce the above copyright
                     15:  *    notice, this list of conditions and the following disclaimer and
                     16:  *    dedication in the documentation and/or other materials provided
                     17:  *    with the distribution.
                     18:  * 3. The name of the author may not be used to endorse or promote products
                     19:  *    derived from this software without specific prior written permission.
                     20:  *
                     21:  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
                     22:  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
                     23:  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
                     24:  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
                     25:  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
                     26:  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
                     27:  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
                     28:  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
                     29:  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
                     30:  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
                     31:  * SUCH DAMAGE.
                     32:  *
                     33:  */
                     34:
                     35: /* this code implements CGI/1.2 for bozohttpd */
                     36:
                     37: #ifndef NO_CGIBIN_SUPPORT
                     38:
                     39: #include <sys/param.h>
                     40: #include <sys/socket.h>
                     41:
                     42: #include <ctype.h>
                     43: #include <errno.h>
                     44: #include <paths.h>
                     45: #include <signal.h>
                     46: #include <stdlib.h>
                     47: #include <string.h>
                     48: #include <unistd.h>
                     49:
                     50: #include <netinet/in.h>
                     51:
                     52: #include "bozohttpd.h"
                     53:
                     54: #define CGIBIN_PREFIX          "cgi-bin/"
                     55: #define CGIBIN_PREFIX_LEN      (sizeof(CGIBIN_PREFIX)-1)
                     56:
                     57: static char    *cgibin;        /* cgi-bin directory */
                     58: static int     Cflag;          /* added a cgi handler, always process_cgi() */
                     59:
1.6     ! rtr        60: static const char *    content_cgihandler(http_req *, const char *);
        !            61: static void            finish_cgi_output(http_req *request, int, int);
        !            62: static int             parse_header(const char *, ssize_t, char **, char **);
1.1       tls        63:
                     64: void
                     65: set_cgibin(char *path)
                     66: {
                     67:        cgibin = path;
                     68:        debug((DEBUG_OBESE, "cgibin (cgi-bin directory) is %s", cgibin));
                     69: }
                     70:
                     71: /* help build up the environ pointer */
                     72: void
                     73: spsetenv(const char *env, const char *val, char **envp)
                     74: {
                     75:        char *s1 = bozomalloc(strlen(env) + strlen(val) + 2);
                     76:
                     77:        strcpy(s1, env);
                     78:        strcat(s1, "=");
                     79:        strcat(s1, val);
                     80:        debug((DEBUG_OBESE, "spsetenv: %s", s1));
                     81:        *envp = s1;
                     82: }
                     83:
                     84: /*
                     85:  * Checks if the request has asked for a cgi-bin.  Should only be called if
                     86:  * cgibin is set.  If it starts CGIBIN_PREFIX or has a ncontent handler,
                     87:  * process the cgi, otherwise just return.
                     88:  */
                     89: void
                     90: process_cgi(http_req *request)
                     91: {
                     92:        char    buf[WRSZ];
                     93:        struct  headers *headp;
                     94:        const char *type, *clen, *info, *cgihandler;
                     95:        char    *query, *s, *t, *path, *env, *command, *url;
                     96:        char    **envp, **curenvp, *argv[4];
                     97:        size_t  len;
                     98:        ssize_t rbytes;
                     99:        pid_t   pid;
                    100:        int     envpsize, ix, nph;
                    101:        int     sv[2];
                    102:
                    103:        if (!cgibin && !Cflag)
                    104:                return;
                    105:
                    106:        debug((DEBUG_NORMAL, "process_cgi: url `%s'", request->hr_url));
                    107:
                    108:        url = bozostrdup(request->hr_url);
                    109:        if ((s = strchr(url, '?')) != NULL) {
                    110:                *s++ = '\0';
                    111:                query = s;
                    112:        } else
                    113:                query = NULL;
                    114:        path = NULL;
                    115:        envp = NULL;
                    116:        cgihandler = NULL;
                    117:        command = NULL;
                    118:        info = NULL;
                    119:
                    120:        len = strlen(url);
                    121:        if (len == 0 || url[len - 1] == '/') {  /* append index.html */
                    122:                debug((DEBUG_FAT, "appending index.html"));
                    123:                url = bozorealloc(url, len + strlen(index_html) + 1);
                    124:                strcat(url, index_html);
                    125:                debug((DEBUG_NORMAL, "process_cgi: url adjusted to `%s'", url));
                    126:        }
                    127:
                    128:        auth_check(request, url + 1);
                    129:
                    130:        if (!cgibin || strncmp(url + 1, CGIBIN_PREFIX, CGIBIN_PREFIX_LEN) != 0) {
                    131:                cgihandler = content_cgihandler(request, url + 1);
                    132:                if (cgihandler == NULL) {
                    133:                        free(url);
                    134:                        return;
                    135:                }
                    136:                debug((DEBUG_NORMAL, "process_cgi: cgihandler `%s'",
                    137:                    cgihandler));
                    138:        }
                    139:
                    140:        ix = 0;
                    141:        if (cgihandler) {
                    142:                command = url + 1;
                    143:                path = bozostrdup(cgihandler);
                    144:                argv[ix++] = path;
                    145:                        /* argv[] = [ path, command, query, NULL ] */
                    146:        } else {
                    147:                command = url + CGIBIN_PREFIX_LEN + 1;
                    148:                if ((s = strchr(command, '/')) != NULL) {
                    149:                        info = bozostrdup(s);
                    150:                        *s = '\0';
                    151:                }
                    152:                path = bozomalloc(strlen(cgibin) + 1 + strlen(command) + 1);
                    153:                strcpy(path, cgibin);
                    154:                strcat(path, "/");
                    155:                strcat(path, command);
                    156:                        /* argv[] = [ command, query, NULL ] */
                    157:        }
                    158:        argv[ix++] = command;
                    159:        argv[ix++] = query;
                    160:        argv[ix++] = NULL;
                    161:
                    162:        nph = strncmp(command, "nph-", 4) == 0;
                    163:
                    164:        debug((DEBUG_FAT,
                    165:            "process_cgi: path `%s' cmd `%s' info `%s' query `%s' nph `%d'",
                    166:            path, command, strornull(info), strornull(query), nph));
                    167:
                    168:        type = request->hr_content_type;
                    169:        clen = request->hr_content_length;
                    170:
                    171:        envpsize = 13 + request->hr_nheaders +
                    172:            (info && *info ? 1 : 0) +
                    173:            (query && *query ? 1 : 0) +
                    174:            (type && *type ? 1 : 0) +
                    175:            (clen && *clen ? 1 : 0) +
                    176:            (request->hr_remotehost && *request->hr_remotehost ? 1 : 0) +
                    177:            (request->hr_remoteaddr && *request->hr_remoteaddr ? 1 : 0) +
                    178:            auth_cgi_count(request) +
                    179:            (request->hr_serverport && *request->hr_serverport ? 1 : 0);
                    180:
                    181:        envp = bozomalloc(sizeof(*envp) * envpsize);
                    182:        for (ix = 0; ix < envpsize; ix++)
                    183:                envp[ix] = NULL;
                    184:        curenvp = envp;
1.3       tls       185:
                    186:        SIMPLEQ_FOREACH(headp, &request->hr_headers, h_next) {
1.1       tls       187:                const char *s2;
                    188:                env = bozomalloc(6 + strlen(headp->h_header) + 1 +
                    189:                    strlen(headp->h_value));
                    190:
                    191:                t = env;
                    192:                strcpy(t, "HTTP_");
                    193:                t += strlen(t);
                    194:                for (s2 = headp->h_header; *s2; t++, s2++)
                    195:                        if (islower((u_int)*s2))
                    196:                                *t = toupper((u_int)*s2);
                    197:                        else if (*s2 == '-')
                    198:                                *t = '_';
                    199:                        else
                    200:                                *t = *s2;
                    201:                *t = '\0';
                    202:                debug((DEBUG_OBESE, "setting header %s as %s = %s",
                    203:                    headp->h_header, env, headp->h_value));
                    204:                spsetenv(env, headp->h_value, curenvp++);
                    205:                free(env);
                    206:        }
                    207:
                    208: #ifndef _PATH_DEFPATH
                    209: #define _PATH_DEFPATH "/usr/bin:/bin"
                    210: #endif
                    211:
                    212:        spsetenv("PATH", _PATH_DEFPATH, curenvp++);
                    213:        spsetenv("IFS", " \t\n", curenvp++);
                    214:        spsetenv("SERVER_NAME", myname, curenvp++);
                    215:        spsetenv("GATEWAY_INTERFACE", "CGI/1.1", curenvp++);
                    216:        spsetenv("SERVER_PROTOCOL", request->hr_proto, curenvp++);
                    217:        spsetenv("REQUEST_METHOD", request->hr_methodstr, curenvp++);
                    218:        spsetenv("SCRIPT_NAME", url, curenvp++);
                    219:        spsetenv("SCRIPT_FILENAME", url + 1, curenvp++);
                    220:        spsetenv("SERVER_SOFTWARE", server_software, curenvp++);
                    221:        spsetenv("REQUEST_URI", request->hr_url, curenvp++);
                    222:        spsetenv("DATE_GMT", http_date(), curenvp++);
                    223:        if (query && *query)
                    224:                spsetenv("QUERY_STRING", query, curenvp++);
                    225:        if (info && *info)
                    226:                spsetenv("PATH_INFO", info, curenvp++);
                    227:        if (type && *type)
                    228:                spsetenv("CONTENT_TYPE", type, curenvp++);
                    229:        if (clen && *clen)
                    230:                spsetenv("CONTENT_LENGTH", clen, curenvp++);
                    231:        if (request->hr_serverport && *request->hr_serverport)
                    232:                spsetenv("SERVER_PORT", request->hr_serverport, curenvp++);
                    233:        if (request->hr_remotehost && *request->hr_remotehost)
                    234:                spsetenv("REMOTE_HOST", request->hr_remotehost, curenvp++);
                    235:        if (request->hr_remoteaddr && *request->hr_remoteaddr)
                    236:                spsetenv("REMOTE_ADDR", request->hr_remoteaddr, curenvp++);
                    237:        auth_cgi_setenv(request, &curenvp);
                    238:
                    239:        debug((DEBUG_FAT, "process_cgi: going exec %s, %s %s %s",
                    240:            path, argv[0], strornull(argv[1]), strornull(argv[2])));
                    241:
                    242:        if (-1 == socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, sv))
                    243:                error(1, "child socketpair failed: %s", strerror(errno));
                    244:
                    245:        /*
                    246:         * We create 2 procs: one to become the CGI, one read from
                    247:         * the CGI and output to the network, and this parent will
                    248:         * continue reading from the network and writing to the
                    249:         * CGI procsss.
                    250:         */
                    251:        switch (fork()) {
                    252:        case -1: /* eep, failure */
                    253:                error(1, "child fork failed: %s", strerror(errno));
                    254:        case 0:
                    255:                close(sv[0]);
                    256:                dup2(sv[1], STDIN_FILENO);
                    257:                dup2(sv[1], STDOUT_FILENO);
                    258:
                    259:                if (-1 == execve(path, argv, envp))
                    260:                        error(1, "child exec failed: %s", path);
                    261:                /* NOT REACHED */
                    262:                error(1, "child execve returned?!");
                    263:        }
                    264:
                    265:        close(sv[1]);
                    266:
                    267:        /* parent: read from stdin (bozoread()) write to sv[0] */
                    268:        /* child: read from sv[0] (bozowrite()) write to stdout */
                    269:        pid = fork();
                    270:        if (pid == -1)
                    271:                error(1, "io child fork failed: %s", strerror(errno));
                    272:        else if (pid == 0) {
                    273:                /* child reader/writer */
                    274:                close(STDIN_FILENO);
                    275:                finish_cgi_output(request, sv[0], nph);
                    276:                /* if we're done output, our parent is useless... */
                    277:                kill(getppid(), SIGKILL);
                    278:                debug((DEBUG_FAT, "done processing cgi output"));
                    279:                _exit(0);
                    280:        }
                    281:        close(STDOUT_FILENO);
                    282:
                    283:        /* XXX we should have some goo that times us out
                    284:         */
                    285:        while ((rbytes = bozoread(STDIN_FILENO, buf, sizeof buf)) > 0) {
                    286:                ssize_t wbytes;
                    287:                char *bp = buf;
                    288:
                    289:                while (rbytes) {
                    290:                        wbytes = write(sv[0], buf , rbytes);
                    291:                        if (wbytes > 0) {
                    292:                                rbytes -= wbytes;
                    293:                                bp += wbytes;
                    294:                        } else
                    295:                                error(1, "write failed: %s", strerror(errno));
                    296:                }
                    297:        }
                    298:        debug((DEBUG_FAT, "done processing cgi input"));
                    299:        exit(0);
                    300: }
                    301:
                    302: /*
                    303:  * handle parsing a CGI header output, transposing a Status: header
                    304:  * into the HTTP reply (ie, instead of "200 OK").
                    305:  */
                    306: static void
                    307: finish_cgi_output(http_req *request, int in, int nph)
                    308: {
                    309:        char    buf[WRSZ];
1.6     ! rtr       310:        char    *str;
1.1       tls       311:        size_t  len;
                    312:        ssize_t rbytes;
                    313:        SIMPLEQ_HEAD(, headers) headers;
                    314:        struct  headers *hdr;
1.6     ! rtr       315:        int     write_header, nheaders = 0;
1.1       tls       316:
                    317:        /* much of this code is like read_request()'s header loop. hmmm... */
                    318:        SIMPLEQ_INIT(&headers);
                    319:        write_header = nph == 0;
1.2       tls       320:        while (nph == 0 && (str = dgetln(in, (ssize_t *)&len, read)) != NULL) {
1.6     ! rtr       321:                char * hdr_name, * hdr_value;
1.1       tls       322:
1.6     ! rtr       323:                if (parse_header(str, (ssize_t)len, &hdr_name, &hdr_value))
1.1       tls       324:                        break;
                    325:
                    326:                /*
                    327:                 * The CGI 1.{1,2} spec both say that if the cgi program
                    328:                 * returns a `Status:' header field then the server MUST
                    329:                 * return it in the response.  If the cgi program does
                    330:                 * not return any `Status:' header then the server should
                    331:                 * respond with 200 OK.
                    332:                 * XXX The CGI 1.1 and 1.2 specification differ slightly on
                    333:                 * this in that v1.2 says that the script MUST NOT return a
                    334:                 * `Status:' header if it is returning a `Location:' header.
                    335:                 * For compatibility we are going with the CGI 1.1 behavior.
                    336:                 */
1.6     ! rtr       337:                if (strcasecmp(hdr_name, "status") == 0) {
1.1       tls       338:                        debug((DEBUG_OBESE, "process_cgi:  writing HTTP header "
1.6     ! rtr       339:                                            "from status %s ..", hdr_value));
        !           340:                        bozoprintf("%s %s\r\n", request->hr_proto, hdr_value);
1.1       tls       341:                        bozoflush(stdout);
                    342:                        write_header = 0;
1.6     ! rtr       343:                        free(hdr_name);
1.1       tls       344:                        break;
                    345:                }
                    346:
                    347:                hdr = bozomalloc(sizeof *hdr);
1.6     ! rtr       348:                hdr->h_header = hdr_name;
        !           349:                hdr->h_value = hdr_value;
1.1       tls       350:                SIMPLEQ_INSERT_TAIL(&headers, hdr, h_next);
                    351:                nheaders++;
                    352:        }
                    353:
                    354:        if (write_header) {
                    355:                debug((DEBUG_OBESE, "process_cgi:  writing HTTP header .."));
                    356:                bozoprintf("%s 200 OK\r\n", request->hr_proto);
                    357:                bozoflush(stdout);
                    358:        }
                    359:
                    360:        if (nheaders) {
                    361:                debug((DEBUG_OBESE, "process_cgi:  writing delayed HTTP "
                    362:                                    "headers .."));
1.3       tls       363:                SIMPLEQ_FOREACH(hdr, &headers, h_next) {
                    364:                        bozoprintf("%s: %s\r\n", hdr->h_header, hdr->h_value);
1.6     ! rtr       365:                        free(hdr->h_header);
1.5       rtr       366:                        free(hdr);
1.1       tls       367:                }
                    368:                bozoflush(stdout);
                    369:        }
                    370:
                    371:        /* XXX we should have some goo that times us out
                    372:         */
                    373:        while ((rbytes = read(in, buf, sizeof buf)) > 0) {
                    374:                ssize_t wbytes;
                    375:                char *bp = buf;
                    376:
                    377:                while (rbytes) {
                    378:                        wbytes = bozowrite(STDOUT_FILENO, buf, rbytes);
                    379:                        if (wbytes > 0) {
                    380:                                rbytes -= wbytes;
                    381:                                bp += wbytes;
                    382:                        } else
                    383:                                error(1, "cgi output write failed: %s",
                    384:                                    strerror(errno));
                    385:                }
                    386:        }
                    387: }
                    388:
1.6     ! rtr       389: static int
        !           390: parse_header(const char * str, ssize_t len, char ** hdr_str, char ** hdr_val)
        !           391: {
        !           392:        char * name, * value;
        !           393:
        !           394:        /* if the string passed is zero-length bail out */
        !           395:        if (*str == '\0')
        !           396:                return -1;
        !           397:
        !           398:        name = value = bozostrdup(str);
        !           399:
        !           400:        /* locate the ':' separator in the header/value */
        !           401:        name = strnsep(&value, ":", &len);
        !           402:
        !           403:        if (NULL == name || -1 == len) {
        !           404:                free(name);
        !           405:                return -1;
        !           406:        }
        !           407:
        !           408:        /* skip leading space/tab */
        !           409:        while (*value == ' ' || *value == '\t')
        !           410:                len--, value++;
        !           411:
        !           412:        *hdr_str = name;
        !           413:        *hdr_val = value;
        !           414:
        !           415:        return 0;
        !           416: }
        !           417:
1.1       tls       418: /*
                    419:  * given the file name, return a CGI interpreter
                    420:  */
                    421: static const char *
                    422: content_cgihandler(http_req *request, const char *file)
                    423: {
                    424:        struct  content_map     *map;
                    425:
                    426:        map = match_content_map(file, 0);
                    427:        if (map)
                    428:                return (map->cgihandler);
                    429:        return (NULL);
                    430: }
                    431:
                    432: #ifndef NO_DYNAMIC_CONTENT
                    433: /* cgi maps are simple ".postfix /path/to/prog" */
                    434: void
                    435: add_content_map_cgi(char *arg, char *cgihandler)
                    436: {
                    437:        struct content_map *map;
                    438:
                    439:        debug((DEBUG_NORMAL, "add_content_map_cgi: name %s cgi %s", arg, cgihandler));
                    440:
                    441:        Cflag = 1;
                    442:
                    443:        map = get_content_map(arg);
                    444:        map->name = arg;
                    445:        map->type = map->encoding = map->encoding11 = NULL;
                    446:        map->cgihandler = cgihandler;
                    447: }
                    448: #endif /* NO_DYNAMIC_CONTENT */
                    449:
                    450: #endif /* NO_CGIBIN_SUPPORT */

CVSweb <webmaster@jp.NetBSD.org>