fastcgi c语言版

/*
 * Copyright (c) 2007-2013 Grzegorz Nosek
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 */

#define NO_FCGI_DEFINES

#include "config.h"
#include <stdarg.h>
#include <fcgi_stdio.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/select.h>
#include <errno.h>
#include <string.h>
#include <sys/stat.h>
#include <limits.h>
#include <stdbool.h>
#include <sys/wait.h>
#include <ctype.h>

#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <netinet/in.h>

#ifdef HAVE_SYSTEMD
#include <systemd/sd-daemon.h>
#endif

/* glibc doesn't seem to export it */
#ifndef UNIX_PATH_MAX
#define UNIX_PATH_MAX 108
#endif

extern char **environ;
static char * const * inherited_environ;
static const char **allowed_programs;
static size_t allowed_programs_count;

static const char * blacklisted_env_vars[] = {
    "AUTH_TYPE",
    "CONTENT_LENGTH",
    "CONTENT_TYPE",
    "GATEWAY_INTERFACE",
    "PATH_INFO",
    "PATH_TRANSLATED",
    "QUERY_STRING",
    "REMOTE_ADDR",
    "REMOTE_HOST",
    "REMOTE_IDENT",
    "REMOTE_USER",
    "REQUEST_METHOD",
    "SCRIPT_NAME",
    "SERVER_NAME",
    "SERVER_PORT",
    "SERVER_PROTOCOL",
    "SERVER_SOFTWARE",
    NULL,
};

static int stderr_to_fastcgi = 0;


#define FCGI_BUF_SIZE 4096

static int write_all(int fd, char *buf, size_t size)
{
    size_t nleft = size;
    while (nleft > 0) {
        ssize_t nwritten = write(fd, buf, nleft);
        if (nwritten < 0)
            return nleft - size; /* zero or negative to indicate error */

        buf += nwritten;
        nleft -= nwritten;
    }

    return size;
}

#define MAX_VA_SENTINEL INT_MIN

static int max_va(int p1, ...)
{
    va_list va;
    int max = p1;
    int p;

    va_start(va, p1);
    do {
        p = va_arg(va, int);
        if (p > max)
            max = p;
    } while (p != MAX_VA_SENTINEL);
    va_end(va);

    return max;
}

enum reply_state_t {
    REPLY_STATE_INIT,
    REPLY_STATE_HEADER,
    REPLY_STATE_CR,
    REPLY_STATE_LF,
    REPLY_STATE_2CR,
    REPLY_STATE_2LF,
    REPLY_STATE_BODY,
    REPLY_STATE_MAX
};

enum char_class_t {
    CC_NORMAL,
    CC_CR,
    CC_LF,
    CC_MAX
};

#define ACTION_MASK    (15 << 4)
#define ACTION_EMIT    0
#define ACTION_ERROR    (1 << 4)
#define ACTION_END    (2 << 4)
#define ACTION_SKIP    (3 << 4)
#define ACTION_EXTRA_CR    (4 << 4)
#define ACTION_EXTRA_LF    (5 << 4)

static const unsigned char header_state_machine[REPLY_STATE_MAX][CC_MAX] = {
    [REPLY_STATE_INIT] = {
        [CC_NORMAL] = REPLY_STATE_HEADER,
        [CC_CR] = ACTION_ERROR,
        [CC_LF] = ACTION_ERROR,
    },
    [REPLY_STATE_HEADER] = {
        [CC_NORMAL] = REPLY_STATE_HEADER,
        [CC_CR] = REPLY_STATE_CR,
        [CC_LF] = REPLY_STATE_LF | ACTION_EXTRA_CR,
    },
    [REPLY_STATE_CR] = {
        [CC_NORMAL] = REPLY_STATE_HEADER | ACTION_EXTRA_LF,
        [CC_CR] = REPLY_STATE_CR | ACTION_SKIP,
        [CC_LF] = REPLY_STATE_LF,
    },
    [REPLY_STATE_LF] = {
        [CC_NORMAL] = REPLY_STATE_HEADER,
        [CC_CR] = REPLY_STATE_2CR,
        [CC_LF] = REPLY_STATE_2LF | ACTION_EXTRA_CR,
    },
    [REPLY_STATE_2CR] = {
        [CC_NORMAL] = REPLY_STATE_BODY | ACTION_EXTRA_LF,
        [CC_CR] = REPLY_STATE_CR | ACTION_SKIP,
        [CC_LF] = REPLY_STATE_2LF,
    },
    [REPLY_STATE_2LF] = {
        [CC_NORMAL] = REPLY_STATE_BODY | ACTION_END,
        [CC_CR] = REPLY_STATE_BODY | ACTION_END,
        [CC_LF] = REPLY_STATE_BODY | ACTION_END,
    },
    [REPLY_STATE_BODY] = {
        [CC_NORMAL] = REPLY_STATE_BODY | ACTION_END,
        [CC_CR] = REPLY_STATE_BODY | ACTION_END,
        [CC_LF] = REPLY_STATE_BODY | ACTION_END,
    },
};

struct fcgi_context {
    int fd_stdin;
    int fd_stdout;
    int fd_stderr;
    unsigned int reply_state;
    pid_t cgi_pid;
};

static void fcgi_finish(struct fcgi_context *fc, const char* msg)
{
    if (fc->reply_state == REPLY_STATE_INIT) {
        FCGI_puts("Status: 502 Bad Gateway\nContent-type: text/plain\n");
        FCGI_printf("An error occurred while %s\n", msg);
    }

    if (fc->fd_stdin >= 0) close(fc->fd_stdin);
    if (fc->fd_stdout >= 0) close(fc->fd_stdout);
    if (fc->fd_stderr >= 0) close(fc->fd_stderr);

    if (fc->cgi_pid)
        kill(SIGTERM, fc->cgi_pid);
}

static const char * fcgi_pass_fd(struct fcgi_context *fc, int *fdp, FCGI_FILE *ffp, char *buf, size_t bufsize)
{
    ssize_t nread;
    char *p = buf;
    unsigned char cclass, next_state;

    nread = read(*fdp, buf, bufsize);
    if (nread > 0) {
        while (p < buf + nread) {
            if (*p == '\r') {
                cclass = CC_CR;
            } else if (*p == '\n') {
                cclass = CC_LF;
            } else {
                cclass = CC_NORMAL;
            }
            next_state = header_state_machine[fc->reply_state][cclass];
            fc->reply_state = next_state & ~ACTION_MASK;
            switch(next_state & ACTION_MASK) {
                case ACTION_ERROR:
                    return "parsing CGI reply";

                case ACTION_END:
                    goto out_of_loop;

                case ACTION_SKIP:
                    goto next_char;

                case ACTION_EXTRA_CR:
                    if (FCGI_fputc('\r', ffp) == EOF) return "writing CGI reply";
                    break;

                case ACTION_EXTRA_LF:
                    if (FCGI_fputc('\n', ffp) == EOF) return "writing CGI reply";
                    break;
            }
            if (FCGI_fputc(*p, ffp) == EOF) {
                return "writing CGI reply";
            }
next_char:
            p++;
        }
out_of_loop:
        if (p < buf + nread) {
            if (FCGI_fwrite(p, 1, buf + nread - p, ffp) != (size_t)(buf + nread - p)) {
                return "writing CGI reply";
            }
        }
    } else {
        if (nread < 0) {
            return "reading CGI reply";
        }
        close(*fdp);
        *fdp = -1;
    }

    return NULL;
}

static const char * fcgi_pass_raw_fd(int *fdp, int fd_out, char *buf, size_t bufsize)
{
    ssize_t nread;

    nread = read(*fdp, buf, bufsize);
    if (nread > 0) {
        if (write_all(fd_out, buf, nread) != nread) {
            return "writing CGI reply";
        }
    } else {
        if (nread < 0) {
            return "reading CGI reply";
        }
        close(*fdp);
        *fdp = -1;
    }
    return NULL;
}

static bool fcgi_pass_request(struct fcgi_context *fc)
{
    char buf[FCGI_BUF_SIZE];
    ssize_t nread;

    /* eat the whole request and pass it to CGI */
    while ((nread = FCGI_fread(buf, 1, sizeof(buf), FCGI_stdin)) > 0) {
        if (write_all(fc->fd_stdin, buf, nread) <= 0) {
            fcgi_finish(fc, "reading the request");
            return false;
        }
    }
    close(fc->fd_stdin);
    fc->fd_stdin = -1;

    return true;
}

static void fcgi_pass(struct fcgi_context *fc)
{
    char buf[FCGI_BUF_SIZE];
    fd_set rset;
    int maxfd = 1 + max_va(fc->fd_stdout, fc->fd_stderr, MAX_VA_SENTINEL);
    int nready;
    const char *err;

    if (!fcgi_pass_request(fc))
        return;

    /* now pass CGI reply back */
    while (fc->fd_stdout >= 0 || fc->fd_stderr >= 0) {
        FD_ZERO(&rset);
        if (fc->fd_stdout >= 0) FD_SET(fc->fd_stdout, &rset);
        if (fc->fd_stderr >= 0) FD_SET(fc->fd_stderr, &rset);
        nready = select(maxfd, &rset, NULL, NULL, NULL);
        if (nready < 0) {
            if (errno == EAGAIN) continue;
            fcgi_finish(fc, "waiting for CGI reply");
            return;
        }
        if (fc->fd_stdout >= 0 && FD_ISSET(fc->fd_stdout, &rset)) {
            err = fcgi_pass_fd(fc, &fc->fd_stdout, FCGI_stdout, buf, sizeof(buf));
            if (err) {
                fcgi_finish(fc, err);
                return;
            }
        }
        if (fc->fd_stderr >= 0 && FD_ISSET(fc->fd_stderr, &rset)) {
            if (stderr_to_fastcgi)
                err = fcgi_pass_fd(fc, &fc->fd_stderr, FCGI_stderr, buf, sizeof(buf));
            else
                err = fcgi_pass_raw_fd(&fc->fd_stderr, 2, buf, sizeof(buf));
            if (err) {
                fcgi_finish(fc, err);
                return;
            }
        }
    }

    fc->cgi_pid = 0;

    fcgi_finish(fc, "reading CGI reply (no response received)");
}

static int check_file_perms(const char *path)
{
    struct stat ls;
    struct stat fs;

    if (lstat(path, &ls) < 0) {
        return -ENOENT;
    } else if (S_ISREG(ls.st_mode)) {
        if (ls.st_mode & S_IXUSR) {
            return 0;
        } else {
            return -EACCES;
        }
    } else if (!S_ISLNK(ls.st_mode)) {
        return -EACCES;
    }

    if (stat(path, &fs) < 0) {
        return -ENOENT;
    } else if (S_ISREG(fs.st_mode)) {
        if (fs.st_mode & S_IXUSR) {
            return 0;
        } else {
            return -EACCES;
        }
    } else {
        return -EACCES;
    }
}

static char *get_cgi_filename(void) /* and fixup environment */
{
    int buflen = 1, docrootlen;
    char *buf = NULL;
    char *docroot, *scriptname, *p;

    int rf_len;
    char *pathinfo = NULL;

    if ((p = getenv("SCRIPT_FILENAME"))) {
        if (check_file_perms(p) != 0)
            goto err;
        return strdup(p);
    }

    if ((p = getenv("DOCUMENT_ROOT"))) {
        docroot = p;
        docrootlen = strlen(p);
        buflen += docrootlen;
    } else {
        goto err;
    }

    if ((p = getenv("SCRIPT_NAME"))) {
        buflen += strlen(p);
        scriptname = p;
    } else {
        goto err;
    }

    buf = malloc(buflen);
    if (!buf) goto err;

    strcpy(buf, docroot);
    strcpy(buf + docrootlen, scriptname);
    pathinfo = strdup(buf);
    if (!pathinfo) {
        goto err;
    }

    while(1) {
        switch(check_file_perms(buf)) {
            case -EACCES:
                goto err;
            case 0:
                rf_len = strlen(buf);
                if (rf_len < buflen - 1) {
                    setenv("PATH_INFO", pathinfo + rf_len, 1);
                    setenv("SCRIPT_NAME", buf + docrootlen, 1);
                } else {
                    unsetenv("PATH_INFO");
                }
                free(pathinfo);
                return buf;
            default:
                p = strrchr(buf, '/');
                if (!p) goto err;
                *p = 0;
        }
    }

err:
    free(pathinfo);
    free(buf);
    return NULL;
}

static int blacklisted_env(const char *var_name, const char *var_name_end)
{
    const char **p;

    if (var_name_end - var_name > 4 && !strncmp(var_name, "HTTP", 4)) {
        /* HTTP_*, HTTPS */
        return 1;
    }

    for (p = blacklisted_env_vars; *p; p++) {
        if (!strcmp(var_name, *p)) {
            return 1;
        }
    }

    return 0;
}

static void inherit_environment(void)
{
    char * const * p;
    char *q;

    for (p = inherited_environ; *p; p++) {
        q = strchr(*p, '=');
        if (!q) {
            fprintf(stderr, "Suspect value in environment: %s\n", *p);
            continue;
        }
        *q = 0;

        if (!getenv(*p) && !blacklisted_env(*p, q)) {
            *q = '=';
            putenv(*p);
        }

        *q = '=';
    }
}

static bool is_allowed_program(const char *program) {
    size_t i;
    if (!allowed_programs_count)
        return true;

    for (i = 0; i < allowed_programs_count; i++) {
        if (!strcmp(allowed_programs[i], program))
            return true;
    }

    return false;
}

static void cgi_error(const char *message, const char *reason, const char *filename)
{
    printf("Status: %s\r\nContent-Type: text/plain\r\n\r\n%s\r\n",
        message, message);
    fflush(stdout);
    if (filename) {
        fprintf(stderr, "%s (%s)\n", reason, filename);
    } else {
        fputs(reason, stderr);
        fputc('\n', stderr);
    }
    _exit(99);
}

static void handle_fcgi_request(void)
{
    int pipe_in[2];
    int pipe_out[2];
    int pipe_err[2];
    char *filename;
    char *last_slash;
    char *p;
    pid_t pid;

    struct fcgi_context fc;

    if (pipe(pipe_in) < 0) goto err_pipein;
    if (pipe(pipe_out) < 0) goto err_pipeout;
    if (pipe(pipe_err) < 0) goto err_pipeerr;

    switch((pid = fork())) {
        case -1:
            goto err_fork;

        case 0: /* child */
            close(pipe_in[1]);
            close(pipe_out[0]);
            close(pipe_err[0]);

            dup2(pipe_in[0], 0);
            dup2(pipe_out[1], 1);
            dup2(pipe_err[1], 2);

            close(pipe_in[0]);
            close(pipe_out[1]);
            close(pipe_err[1]);

            close(FCGI_fileno(FCGI_stdout));

            signal(SIGCHLD, SIG_DFL);
            signal(SIGPIPE, SIG_DFL);

            filename = get_cgi_filename();
            inherit_environment();
            if (!filename)
                cgi_error("403 Forbidden", "Cannot get script name, are DOCUMENT_ROOT and SCRIPT_NAME (or SCRIPT_FILENAME) set and is the script executable?", NULL);

            if (!is_allowed_program(filename))
                cgi_error("403 Forbidden", "The given script is not allowed to execute", filename);

            p = getenv("FCGI_CHDIR");
            if (p == NULL) {
                last_slash = strrchr(filename, '/');
                if (!last_slash)
                    cgi_error("403 Forbidden", "Script name must be a fully qualified path", filename);

                *last_slash = 0;
                if (chdir(filename) < 0)
                    cgi_error("403 Forbidden", "Cannot chdir to script directory", filename);

                *last_slash = '/';
            } else if (strcmp(p, "-") != 0) {
                if (chdir(p) < 0) {
                    cgi_error("403 Forbidden", "Cannot chdir to FCGI_CHDIR directory", p);
                }
            }

            execl(filename, filename, (void *)NULL);
            cgi_error("502 Bad Gateway", "Cannot execute script", filename);

        default: /* parent */
            close(pipe_in[0]);
            close(pipe_out[1]);
            close(pipe_err[1]);

            fc.fd_stdin = pipe_in[1];
            fc.fd_stdout = pipe_out[0];
            fc.fd_stderr = pipe_err[0];
            fc.reply_state = REPLY_STATE_INIT;
            fc.cgi_pid = pid;

            fcgi_pass(&fc);
    }
    return;

err_fork:
    close(pipe_err[0]);
    close(pipe_err[1]);

err_pipeerr:
    close(pipe_out[0]);
    close(pipe_out[1]);

err_pipeout:
    close(pipe_in[0]);
    close(pipe_in[1]);

err_pipein:

    FCGI_puts("Status: 502 Bad Gateway\nContent-type: text/plain\n");
    FCGI_puts("System error");
}

static volatile sig_atomic_t sigint_received ;
static void sigint_handler(int __attribute__((__unused__))dummy)
{
    sigint_received = 1;
    FCGX_ShutdownPending(); // Or we could send SIGUSR1
}

static void fcgiwrap_main(void)
{
    struct sigaction a;
    signal(SIGCHLD, SIG_IGN);
    signal(SIGPIPE, SIG_IGN);

    // Use sigaction for SIGINT so we can avoid SA_RESTART and actually react
    a.sa_handler = sigint_handler;
    a.sa_flags = 0;
    sigemptyset( &a.sa_mask );
    sigaction( SIGINT, &a, NULL );
    sigaction( SIGTERM, &a, NULL );

    inherited_environ = environ;

    while (FCGI_Accept() >= 0 && !sigint_received) {
        handle_fcgi_request();
    }
}

static volatile sig_atomic_t nrunning;

static void sigchld_handler(int dummy)
{
    int status;

    while ((dummy = waitpid(-1, &status, WNOHANG)) > 0) {
        /* sanity check */
        if (nrunning > 0)
            nrunning--;

        /* we _should_ print something about the exit code
         * but the sighandler context is _very_ bad for this
         */
    }
}

static void prefork(int nchildren)
{
    int startup = 1;

    if (nchildren == 1) {
        return;
    }

    signal(SIGCHLD, sigchld_handler);

    while (1) {
        while (nrunning < nchildren) {
            pid_t pid = fork();
            if (pid == 0) {
                return;
            } else if (pid != -1) {
                nrunning++;
            } else {
                if (startup) {
                    fprintf(stderr, "Failed to prefork: %s\n", strerror(errno));
                    exit(1);
                } else {
                    fprintf(stderr, "Failed to fork: %s\n", strerror(errno));
                    break;
                }
            }
        }
        startup = 0;
        pause();
    }
}

static int listen_on_fd(int fd) {
    int one = 1;

    if (listen(fd, 511) < 0) {
        perror("Failed to listen");
        return -1;
    }
    if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof one) < 0) {
        perror("Failed to enable SO_REUSEADDR");
        return -1;
    }
    if (dup2(fd, 0) < 0) {
        perror("Failed to move socket to fd 0");
        return -1;
    }
    if (close(fd) < 0) {
        perror("Failed to close original socket");
        return -1;
    }

    return 0;
}

static int setup_socket(char *url) {
    char *p = url;
    char *q;
    int fd;
    int port;
    size_t sockaddr_size;

    union {
        struct sockaddr sa;
        struct sockaddr_un sa_un;
        struct sockaddr_in sa_in;
        struct sockaddr_in6 sa_in6;
    } sa;

    if (!strncmp(p, "unix:", sizeof("unix:") - 1)) {
        p += sizeof("unix:") - 1;

        if (strlen(p) >= UNIX_PATH_MAX) {
            fprintf(stderr, "Socket path too long, exceeds %d characters\n",
                    UNIX_PATH_MAX);
            return -1;
        }

        sockaddr_size = sizeof sa.sa_un;
        sa.sa_un.sun_family = AF_UNIX;
        strcpy(sa.sa_un.sun_path, p);
    } else if (!strncmp(p, "tcp:", sizeof("tcp:") - 1)) {
        p += sizeof("tcp:") - 1;

        q = strchr(p, ':');
        if (!q) {
            goto invalid_url;
        }
        port = atoi(q+1);
        if (port <= 0 || port > 65535) {
            goto invalid_url;
        }
        sockaddr_size = sizeof sa.sa_in;
        sa.sa_in.sin_family = AF_INET;
        sa.sa_in.sin_port = htons(port);
        *q = 0;
        if (inet_pton(AF_INET, p, &sa.sa_in.sin_addr) < 1) {
            goto invalid_url;
        }
    } else if (!strncmp(p, "tcp6:[", sizeof("tcp6:[") - 1)) {
        p += sizeof("tcp6:[") - 1;
        q = strchr(p, ']');
        if (!q || !q[0] || q[1] != ':') {
            goto invalid_url;
        }
        port = atoi(q+2);
        if (port <= 0 || port > 65535) {
            goto invalid_url;
        }
        sockaddr_size = sizeof sa.sa_in6;
        sa.sa_in6.sin6_family = AF_INET6;
        sa.sa_in6.sin6_port = htons(port);
        *q = 0;
        if (inet_pton(AF_INET6, p, &sa.sa_in6.sin6_addr) < 1) {
            goto invalid_url;
        }
    } else {
invalid_url:
        fprintf(stderr, "Valid socket URLs are:\n"
                        "unix:/path/to/socket for Unix sockets\n"
                        "tcp:dot.ted.qu.ad:port for IPv4 sockets\n"
                        "tcp6:[ipv6_addr]:port for IPv6 sockets\n");
        return -1;
    }

    fd = socket(sa.sa.sa_family, SOCK_STREAM, 0);
    if (fd < 0) {
        perror("Failed to create socket");
        return -1;
    }
    if (bind(fd, &sa.sa, sockaddr_size) < 0) {
        perror("Failed to bind");
        return -1;
    }

    if (listen_on_fd(fd) < 0) {
        return -1;
    }

    return fd;
}

int main(int argc, char **argv)
{
    int nchildren = 1;
    char *socket_url = NULL;
    int fd = 0;
    int c;

    while ((c = getopt(argc, argv, "c:hfs:p:")) != -1) {
        switch (c) {
            case 'f':
                stderr_to_fastcgi++;
                break;
            case 'h':
                printf("Usage: %s [OPTION]\nInvokes CGI scripts as FCGI.\n\n"
                    PACKAGE_NAME" version "PACKAGE_VERSION"\n\n"
                    "Options are:\n"
                    "  -f\t\t\tSend CGI's stderr over FastCGI\n"
                    "  -c <number>\t\tNumber of processes to prefork\n"
                    "  -s <socket_url>\tSocket to bind to (say -s help for help)\n"
                    "  -h\t\t\tShow this help message and exit\n"
                    "  -p <path>\t\tRestrict execution to this script. (repeated options will be merged)\n"
                    "\nReport bugs to Grzegorz Nosek <"PACKAGE_BUGREPORT">.\n"
                    PACKAGE_NAME" home page: <http://nginx.localdomain.pl/wiki/FcgiWrap>\n",
                    argv[0]
                );
                return 0;
            case 'c':
                nchildren = atoi(optarg);
                break;
            case 's':
                socket_url = strdup(optarg);
                break;
            case 'p':
                allowed_programs = realloc(allowed_programs, (allowed_programs_count + 1) * sizeof (char *));
                if (!allowed_programs)
                    abort();
                allowed_programs[allowed_programs_count++] = strdup(optarg);
                break;
            case '?':
                if (optopt == 'c' || optopt == 's' || optopt == 'p')
                    fprintf(stderr, "Option -%c requires an argument.\n", optopt);
                else if (isprint(optopt))
                    fprintf(stderr, "Unknown option `-%c'.\n", optopt);
                else
                    fprintf(stderr,
                        "Unknown option character `\\x%x'.\n",
                        optopt);
                return 1;
            default:
                abort();
        }
    }

#ifdef HAVE_SYSTEMD
    if (sd_listen_fds(true) > 0) {
        /* systemd woke us up. we should never see more than one FD passed to us. */
        if (listen_on_fd(SD_LISTEN_FDS_START) < 0) {
            return 1;
        }
    } else
#endif
    if (socket_url) {
        fd = setup_socket(socket_url);
        if (fd < 0) {
            return 1;
        }
    }

    prefork(nchildren);
    fcgiwrap_main();

    if (fd) {
        const char *p = socket_url;
        close(fd);

        if (socket_url) {
            if (!strncmp(p, "unix:", sizeof("unix:") - 1)) {
                p += sizeof("unix:") - 1;
                unlink(p);
            }
        }
    }
    return 0;
}

* Copyright (c) 2007-2013 Grzegorz Nosek
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
 
#define NO_FCGI_DEFINES
 
#include "config.h"
#include <stdarg.h>
#include <fcgi_stdio.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/select.h>
#include <errno.h>
#include <string.h>
#include <sys/stat.h>
#include <limits.h>
#include <stdbool.h>
#include <sys/wait.h>
#include <ctype.h>
 
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <netinet/in.h>
 
#ifdef HAVE_SYSTEMD
#include <systemd/sd-daemon.h>
#endif
 
/* glibc doesn't seem to export it */
#ifndef UNIX_PATH_MAX
#define UNIX_PATH_MAX 108
#endif
 
extern char **environ;
static char * const * inherited_environ;
static const char **allowed_programs;
static size_t allowed_programs_count;
 
static const char * blacklisted_env_vars[] = {
"AUTH_TYPE",
"CONTENT_LENGTH",
"CONTENT_TYPE",
"GATEWAY_INTERFACE",
"PATH_INFO",
"PATH_TRANSLATED",
"QUERY_STRING",
"REMOTE_ADDR",
"REMOTE_HOST",
"REMOTE_IDENT",
"REMOTE_USER",
"REQUEST_METHOD",
"SCRIPT_NAME",
"SERVER_NAME",
"SERVER_PORT",
"SERVER_PROTOCOL",
"SERVER_SOFTWARE",
NULL,
};
 
static int stderr_to_fastcgi = 0;
 
 
#define FCGI_BUF_SIZE 4096
 
static int write_all(int fd, char *buf, size_t size)
{
size_t nleft = size;
while (nleft > 0) {
ssize_t nwritten = write(fd, buf, nleft);
if (nwritten < 0)
return nleft - size; /* zero or negative to indicate error */
 
buf += nwritten;
nleft -= nwritten;
}
 
return size;
}
 
#define MAX_VA_SENTINEL INT_MIN
 
static int max_va(int p1, ...)
{
va_list va;
int max = p1;
int p;
 
va_start(va, p1);
do {
p = va_arg(va, int);
if (p > max)
max = p;
} while (p != MAX_VA_SENTINEL);
va_end(va);
 
return max;
}
 
enum reply_state_t {
REPLY_STATE_INIT,
REPLY_STATE_HEADER,
REPLY_STATE_CR,
REPLY_STATE_LF,
REPLY_STATE_2CR,
REPLY_STATE_2LF,
REPLY_STATE_BODY,
REPLY_STATE_MAX
};
 
enum char_class_t {
CC_NORMAL,
CC_CR,
CC_LF,
CC_MAX
};
 
#define ACTION_MASK (15 << 4)
#define ACTION_EMIT 0
#define ACTION_ERROR (1 << 4)
#define ACTION_END (2 << 4)
#define ACTION_SKIP (3 << 4)
#define ACTION_EXTRA_CR (4 << 4)
#define ACTION_EXTRA_LF (5 << 4)
 
static const unsigned char header_state_machine[REPLY_STATE_MAX][CC_MAX] = {
[REPLY_STATE_INIT] = {
[CC_NORMAL] = REPLY_STATE_HEADER,
[CC_CR] = ACTION_ERROR,
[CC_LF] = ACTION_ERROR,
},
[REPLY_STATE_HEADER] = {
[CC_NORMAL] = REPLY_STATE_HEADER,
[CC_CR] = REPLY_STATE_CR,
[CC_LF] = REPLY_STATE_LF | ACTION_EXTRA_CR,
},
[REPLY_STATE_CR] = {
[CC_NORMAL] = REPLY_STATE_HEADER | ACTION_EXTRA_LF,
[CC_CR] = REPLY_STATE_CR | ACTION_SKIP,
[CC_LF] = REPLY_STATE_LF,
},
[REPLY_STATE_LF] = {
[CC_NORMAL] = REPLY_STATE_HEADER,
[CC_CR] = REPLY_STATE_2CR,
[CC_LF] = REPLY_STATE_2LF | ACTION_EXTRA_CR,
},
[REPLY_STATE_2CR] = {
[CC_NORMAL] = REPLY_STATE_BODY | ACTION_EXTRA_LF,
[CC_CR] = REPLY_STATE_CR | ACTION_SKIP,
[CC_LF] = REPLY_STATE_2LF,
},
[REPLY_STATE_2LF] = {
[CC_NORMAL] = REPLY_STATE_BODY | ACTION_END,
[CC_CR] = REPLY_STATE_BODY | ACTION_END,
[CC_LF] = REPLY_STATE_BODY | ACTION_END,
},
[REPLY_STATE_BODY] = {
[CC_NORMAL] = REPLY_STATE_BODY | ACTION_END,
[CC_CR] = REPLY_STATE_BODY | ACTION_END,
[CC_LF] = REPLY_STATE_BODY | ACTION_END,
},
};
 
struct fcgi_context {
int fd_stdin;
int fd_stdout;
int fd_stderr;
unsigned int reply_state;
pid_t cgi_pid;
};
 
static void fcgi_finish(struct fcgi_context *fc, const char* msg)
{
if (fc->reply_state == REPLY_STATE_INIT) {
FCGI_puts("Status: 502 Bad Gateway\nContent-type: text/plain\n");
FCGI_printf("An error occurred while %s\n", msg);
}
 
if (fc->fd_stdin >= 0) close(fc->fd_stdin);
if (fc->fd_stdout >= 0) close(fc->fd_stdout);
if (fc->fd_stderr >= 0) close(fc->fd_stderr);
 
if (fc->cgi_pid)
kill(SIGTERM, fc->cgi_pid);
}
 
static const char * fcgi_pass_fd(struct fcgi_context *fc, int *fdp, FCGI_FILE *ffp, char *buf, size_t bufsize)
{
ssize_t nread;
char *p = buf;
unsigned char cclass, next_state;
 
nread = read(*fdp, buf, bufsize);
if (nread > 0) {
while (p < buf + nread) {
if (*p == '\r') {
cclass = CC_CR;
} else if (*p == '\n') {
cclass = CC_LF;
} else {
cclass = CC_NORMAL;
}
next_state = header_state_machine[fc->reply_state][cclass];
fc->reply_state = next_state & ~ACTION_MASK;
switch(next_state & ACTION_MASK) {
case ACTION_ERROR:
return "parsing CGI reply";
 
case ACTION_END:
goto out_of_loop;
 
case ACTION_SKIP:
goto next_char;
 
case ACTION_EXTRA_CR:
if (FCGI_fputc('\r', ffp) == EOF) return "writing CGI reply";
break;
 
case ACTION_EXTRA_LF:
if (FCGI_fputc('\n', ffp) == EOF) return "writing CGI reply";
break;
}
if (FCGI_fputc(*p, ffp) == EOF) {
return "writing CGI reply";
}
next_char:
p++;
}
out_of_loop:
if (p < buf + nread) {
if (FCGI_fwrite(p, 1, buf + nread - p, ffp) != (size_t)(buf + nread - p)) {
return "writing CGI reply";
}
}
} else {
if (nread < 0) {
return "reading CGI reply";
}
close(*fdp);
*fdp = -1;
}
 
return NULL;
}
 
static const char * fcgi_pass_raw_fd(int *fdp, int fd_out, char *buf, size_t bufsize)
{
ssize_t nread;
 
nread = read(*fdp, buf, bufsize);
if (nread > 0) {
if (write_all(fd_out, buf, nread) != nread) {
return "writing CGI reply";
}
} else {
if (nread < 0) {
return "reading CGI reply";
}
close(*fdp);
*fdp = -1;
}
return NULL;
}
 
static bool fcgi_pass_request(struct fcgi_context *fc)
{
char buf[FCGI_BUF_SIZE];
ssize_t nread;
 
/* eat the whole request and pass it to CGI */
while ((nread = FCGI_fread(buf, 1, sizeof(buf), FCGI_stdin)) > 0) {
if (write_all(fc->fd_stdin, buf, nread) <= 0) {
fcgi_finish(fc, "reading the request");
return false;
}
}
close(fc->fd_stdin);
fc->fd_stdin = -1;
 
return true;
}
 
static void fcgi_pass(struct fcgi_context *fc)
{
char buf[FCGI_BUF_SIZE];
fd_set rset;
int maxfd = 1 + max_va(fc->fd_stdout, fc->fd_stderr, MAX_VA_SENTINEL);
int nready;
const char *err;
 
if (!fcgi_pass_request(fc))
return;
 
/* now pass CGI reply back */
while (fc->fd_stdout >= 0 || fc->fd_stderr >= 0) {
FD_ZERO(&rset);
if (fc->fd_stdout >= 0) FD_SET(fc->fd_stdout, &rset);
if (fc->fd_stderr >= 0) FD_SET(fc->fd_stderr, &rset);
nready = select(maxfd, &rset, NULL, NULL, NULL);
if (nready < 0) {
if (errno == EAGAIN) continue;
fcgi_finish(fc, "waiting for CGI reply");
return;
}
if (fc->fd_stdout >= 0 && FD_ISSET(fc->fd_stdout, &rset)) {
err = fcgi_pass_fd(fc, &fc->fd_stdout, FCGI_stdout, buf, sizeof(buf));
if (err) {
fcgi_finish(fc, err);
return;
}
}
if (fc->fd_stderr >= 0 && FD_ISSET(fc->fd_stderr, &rset)) {
if (stderr_to_fastcgi)
err = fcgi_pass_fd(fc, &fc->fd_stderr, FCGI_stderr, buf, sizeof(buf));
else
err = fcgi_pass_raw_fd(&fc->fd_stderr, 2, buf, sizeof(buf));
if (err) {
fcgi_finish(fc, err);
return;
}
}
}
 
fc->cgi_pid = 0;
 
fcgi_finish(fc, "reading CGI reply (no response received)");
}
 
static int check_file_perms(const char *path)
{
struct stat ls;
struct stat fs;
 
if (lstat(path, &ls) < 0) {
return -ENOENT;
} else if (S_ISREG(ls.st_mode)) {
if (ls.st_mode & S_IXUSR) {
return 0;
} else {
return -EACCES;
}
} else if (!S_ISLNK(ls.st_mode)) {
return -EACCES;
}
 
if (stat(path, &fs) < 0) {
return -ENOENT;
} else if (S_ISREG(fs.st_mode)) {
if (fs.st_mode & S_IXUSR) {
return 0;
} else {
return -EACCES;
}
} else {
return -EACCES;
}
}
 
static char *get_cgi_filename(void) /* and fixup environment */
{
int buflen = 1, docrootlen;
char *buf = NULL;
char *docroot, *scriptname, *p;
 
int rf_len;
char *pathinfo = NULL;
 
if ((p = getenv("SCRIPT_FILENAME"))) {
if (check_file_perms(p) != 0)
goto err;
return strdup(p);
}
 
if ((p = getenv("DOCUMENT_ROOT"))) {
docroot = p;
docrootlen = strlen(p);
buflen += docrootlen;
} else {
goto err;
}
 
if ((p = getenv("SCRIPT_NAME"))) {
buflen += strlen(p);
scriptname = p;
} else {
goto err;
}
 
buf = malloc(buflen);
if (!buf) goto err;
 
strcpy(buf, docroot);
strcpy(buf + docrootlen, scriptname);
pathinfo = strdup(buf);
if (!pathinfo) {
goto err;
}
 
while(1) {
switch(check_file_perms(buf)) {
case -EACCES:
goto err;
case 0:
rf_len = strlen(buf);
if (rf_len < buflen - 1) {
setenv("PATH_INFO", pathinfo + rf_len, 1);
setenv("SCRIPT_NAME", buf + docrootlen, 1);
} else {
unsetenv("PATH_INFO");
}
free(pathinfo);
return buf;
default:
p = strrchr(buf, '/');
if (!p) goto err;
*p = 0;
}
}
 
err:
free(pathinfo);
free(buf);
return NULL;
}
 
static int blacklisted_env(const char *var_name, const char *var_name_end)
{
const char **p;
 
if (var_name_end - var_name > 4 && !strncmp(var_name, "HTTP", 4)) {
/* HTTP_*, HTTPS */
return 1;
}
 
for (p = blacklisted_env_vars; *p; p++) {
if (!strcmp(var_name, *p)) {
return 1;
}
}
 
return 0;
}
 
static void inherit_environment(void)
{
char * const * p;
char *q;
 
for (p = inherited_environ; *p; p++) {
q = strchr(*p, '=');
if (!q) {
fprintf(stderr, "Suspect value in environment: %s\n", *p);
continue;
}
*q = 0;
 
if (!getenv(*p) && !blacklisted_env(*p, q)) {
*q = '=';
putenv(*p);
}
 
*q = '=';
}
}
 
static bool is_allowed_program(const char *program) {
size_t i;
if (!allowed_programs_count)
return true;
 
for (i = 0; i < allowed_programs_count; i++) {
if (!strcmp(allowed_programs[i], program))
return true;
}
 
return false;
}
 
static void cgi_error(const char *message, const char *reason, const char *filename)
{
printf("Status: %s\r\nContent-Type: text/plain\r\n\r\n%s\r\n",
message, message);
fflush(stdout);
if (filename) {
fprintf(stderr, "%s (%s)\n", reason, filename);
} else {
fputs(reason, stderr);
fputc('\n', stderr);
}
_exit(99);
}
 
static void handle_fcgi_request(void)
{
int pipe_in[2];
int pipe_out[2];
int pipe_err[2];
char *filename;
char *last_slash;
char *p;
pid_t pid;
 
struct fcgi_context fc;
 
if (pipe(pipe_in) < 0) goto err_pipein;
if (pipe(pipe_out) < 0) goto err_pipeout;
if (pipe(pipe_err) < 0) goto err_pipeerr;
 
switch((pid = fork())) {
case -1:
goto err_fork;
 
case 0: /* child */
close(pipe_in[1]);
close(pipe_out[0]);
close(pipe_err[0]);
 
dup2(pipe_in[0], 0);
dup2(pipe_out[1], 1);
dup2(pipe_err[1], 2);
 
close(pipe_in[0]);
close(pipe_out[1]);
close(pipe_err[1]);
 
close(FCGI_fileno(FCGI_stdout));
 
signal(SIGCHLD, SIG_DFL);
signal(SIGPIPE, SIG_DFL);
 
filename = get_cgi_filename();
inherit_environment();
if (!filename)
cgi_error("403 Forbidden", "Cannot get script name, are DOCUMENT_ROOT and SCRIPT_NAME (or SCRIPT_FILENAME) set and is the script executable?", NULL);
 
if (!is_allowed_program(filename))
cgi_error("403 Forbidden", "The given script is not allowed to execute", filename);
 
p = getenv("FCGI_CHDIR");
if (p == NULL) {
last_slash = strrchr(filename, '/');
if (!last_slash)
cgi_error("403 Forbidden", "Script name must be a fully qualified path", filename);
 
*last_slash = 0;
if (chdir(filename) < 0)
cgi_error("403 Forbidden", "Cannot chdir to script directory", filename);
 
*last_slash = '/';
} else if (strcmp(p, "-") != 0) {
if (chdir(p) < 0) {
cgi_error("403 Forbidden", "Cannot chdir to FCGI_CHDIR directory", p);
}
}
 
execl(filename, filename, (void *)NULL);
cgi_error("502 Bad Gateway", "Cannot execute script", filename);
 
default: /* parent */
close(pipe_in[0]);
close(pipe_out[1]);
close(pipe_err[1]);
 
fc.fd_stdin = pipe_in[1];
fc.fd_stdout = pipe_out[0];
fc.fd_stderr = pipe_err[0];
fc.reply_state = REPLY_STATE_INIT;
fc.cgi_pid = pid;
 
fcgi_pass(&fc);
}
return;
 
err_fork:
close(pipe_err[0]);
close(pipe_err[1]);
 
err_pipeerr:
close(pipe_out[0]);
close(pipe_out[1]);
 
err_pipeout:
close(pipe_in[0]);
close(pipe_in[1]);
 
err_pipein:
 
FCGI_puts("Status: 502 Bad Gateway\nContent-type: text/plain\n");
FCGI_puts("System error");
}
 
static volatile sig_atomic_t sigint_received ;
static void sigint_handler(int __attribute__((__unused__))dummy)
{
sigint_received = 1;
FCGX_ShutdownPending(); // Or we could send SIGUSR1
}
 
static void fcgiwrap_main(void)
{
struct sigaction a;
signal(SIGCHLD, SIG_IGN);
signal(SIGPIPE, SIG_IGN);
 
// Use sigaction for SIGINT so we can avoid SA_RESTART and actually react
a.sa_handler = sigint_handler;
a.sa_flags = 0;
sigemptyset( &a.sa_mask );
sigaction( SIGINT, &a, NULL );
sigaction( SIGTERM, &a, NULL );
 
inherited_environ = environ;
 
while (FCGI_Accept() >= 0 && !sigint_received) {
handle_fcgi_request();
}
}
 
static volatile sig_atomic_t nrunning;
 
static void sigchld_handler(int dummy)
{
int status;
 
while ((dummy = waitpid(-1, &status, WNOHANG)) > 0) {
/* sanity check */
if (nrunning > 0)
nrunning--;
 
/* we _should_ print something about the exit code
* but the sighandler context is _very_ bad for this
*/
}
}
 
static void prefork(int nchildren)
{
int startup = 1;
 
if (nchildren == 1) {
return;
}
 
signal(SIGCHLD, sigchld_handler);
 
while (1) {
while (nrunning < nchildren) {
pid_t pid = fork();
if (pid == 0) {
return;
} else if (pid != -1) {
nrunning++;
} else {
if (startup) {
fprintf(stderr, "Failed to prefork: %s\n", strerror(errno));
exit(1);
} else {
fprintf(stderr, "Failed to fork: %s\n", strerror(errno));
break;
}
}
}
startup = 0;
pause();
}
}
 
static int listen_on_fd(int fd) {
int one = 1;
 
if (listen(fd, 511) < 0) {
perror("Failed to listen");
return -1;
}
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof one) < 0) {
perror("Failed to enable SO_REUSEADDR");
return -1;
}
if (dup2(fd, 0) < 0) {
perror("Failed to move socket to fd 0");
return -1;
}
if (close(fd) < 0) {
perror("Failed to close original socket");
return -1;
}
 
return 0;
}
 
static int setup_socket(char *url) {
char *p = url;
char *q;
int fd;
int port;
size_t sockaddr_size;
 
union {
struct sockaddr sa;
struct sockaddr_un sa_un;
struct sockaddr_in sa_in;
struct sockaddr_in6 sa_in6;
} sa;
 
if (!strncmp(p, "unix:", sizeof("unix:") - 1)) {
p += sizeof("unix:") - 1;
 
if (strlen(p) >= UNIX_PATH_MAX) {
fprintf(stderr, "Socket path too long, exceeds %d characters\n",
UNIX_PATH_MAX);
return -1;
}
 
sockaddr_size = sizeof sa.sa_un;
sa.sa_un.sun_family = AF_UNIX;
strcpy(sa.sa_un.sun_path, p);
} else if (!strncmp(p, "tcp:", sizeof("tcp:") - 1)) {
p += sizeof("tcp:") - 1;
 
q = strchr(p, ':');
if (!q) {
goto invalid_url;
}
port = atoi(q+1);
if (port <= 0 || port > 65535) {
goto invalid_url;
}
sockaddr_size = sizeof sa.sa_in;
sa.sa_in.sin_family = AF_INET;
sa.sa_in.sin_port = htons(port);
*q = 0;
if (inet_pton(AF_INET, p, &sa.sa_in.sin_addr) < 1) {
goto invalid_url;
}
} else if (!strncmp(p, "tcp6:[", sizeof("tcp6:[") - 1)) {
p += sizeof("tcp6:[") - 1;
q = strchr(p, ']');
if (!q || !q[0] || q[1] != ':') {
goto invalid_url;
}
port = atoi(q+2);
if (port <= 0 || port > 65535) {
goto invalid_url;
}
sockaddr_size = sizeof sa.sa_in6;
sa.sa_in6.sin6_family = AF_INET6;
sa.sa_in6.sin6_port = htons(port);
*q = 0;
if (inet_pton(AF_INET6, p, &sa.sa_in6.sin6_addr) < 1) {
goto invalid_url;
}
} else {
invalid_url:
fprintf(stderr, "Valid socket URLs are:\n"
"unix:/path/to/socket for Unix sockets\n"
"tcp:dot.ted.qu.ad:port for IPv4 sockets\n"
"tcp6:[ipv6_addr]:port for IPv6 sockets\n");
return -1;
}
 
fd = socket(sa.sa.sa_family, SOCK_STREAM, 0);
if (fd < 0) {
perror("Failed to create socket");
return -1;
}
if (bind(fd, &sa.sa, sockaddr_size) < 0) {
perror("Failed to bind");
return -1;
}
 
if (listen_on_fd(fd) < 0) {
return -1;
}
 
return fd;
}
 
int main(int argc, char **argv)
{
int nchildren = 1;
char *socket_url = NULL;
int fd = 0;
int c;
 
while ((c = getopt(argc, argv, "c:hfs:p:")) != -1) {
switch (c) {
case 'f':
stderr_to_fastcgi++;
break;
case 'h':
printf("Usage: %s [OPTION]\nInvokes CGI scripts as FCGI.\n\n"
PACKAGE_NAME" version "PACKAGE_VERSION"\n\n"
"Options are:\n"
" -f\t\t\tSend CGI's stderr over FastCGI\n"
" -c <number>\t\tNumber of processes to prefork\n"
" -s <socket_url>\tSocket to bind to (say -s help for help)\n"
" -h\t\t\tShow this help message and exit\n"
" -p <path>\t\tRestrict execution to this script. (repeated options will be merged)\n"
"\nReport bugs to Grzegorz Nosek <"PACKAGE_BUGREPORT">.\n"
PACKAGE_NAME" home page: <http://nginx.localdomain.pl/wiki/FcgiWrap>\n",
argv[0]
);
return 0;
case 'c':
nchildren = atoi(optarg);
break;
case 's':
socket_url = strdup(optarg);
break;
case 'p':
allowed_programs = realloc(allowed_programs, (allowed_programs_count + 1) * sizeof (char *));
if (!allowed_programs)
abort();
allowed_programs[allowed_programs_count++] = strdup(optarg);
break;
case '?':
if (optopt == 'c' || optopt == 's' || optopt == 'p')
fprintf(stderr, "Option -%c requires an argument.\n", optopt);
else if (isprint(optopt))
fprintf(stderr, "Unknown option `-%c'.\n", optopt);
else
fprintf(stderr,
"Unknown option character `\\x%x'.\n",
optopt);
return 1;
default:
abort();
}
}
 
#ifdef HAVE_SYSTEMD
if (sd_listen_fds(true) > 0) {
/* systemd woke us up. we should never see more than one FD passed to us. */
if (listen_on_fd(SD_LISTEN_FDS_START) < 0) {
return 1;
}
} else
#endif
if (socket_url) {
fd = setup_socket(socket_url);
if (fd < 0) {
return 1;
}
}
 
prefork(nchildren);
fcgiwrap_main();
 
if (fd) {
const char *p = socket_url;
close(fd);
 
if (socket_url) {
if (!strncmp(p, "unix:", sizeof("unix:") - 1)) {
p += sizeof("unix:") - 1;
unlink(p);
}
}
}
return 0;

}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值