/* $OpenBSD: slowcgi.c,v 1.62 2021/09/02 14:14:44 jmc Exp $ */ /* * Copyright (c) 2013 David Gwynne * Copyright (c) 2013 Florian Obser * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define TIMEOUT_DEFAULT 120 #define SLOWCGI_USER "www" #define FCGI_CONTENT_SIZE 65535 #define FCGI_PADDING_SIZE 255 #define FCGI_RECORD_SIZE \ (sizeof(struct fcgi_record_header) + FCGI_CONTENT_SIZE + FCGI_PADDING_SIZE) #define FCGI_ALIGNMENT 8 #define FCGI_ALIGN(n) \ (((n) + (FCGI_ALIGNMENT - 1)) & ~(FCGI_ALIGNMENT - 1)) #define STDOUT_DONE 1 #define STDERR_DONE 2 #define SCRIPT_DONE 4 #define FCGI_BEGIN_REQUEST 1 #define FCGI_ABORT_REQUEST 2 #define FCGI_END_REQUEST 3 #define FCGI_PARAMS 4 #define FCGI_STDIN 5 #define FCGI_STDOUT 6 #define FCGI_STDERR 7 #define FCGI_DATA 8 #define FCGI_GET_VALUES 9 #define FCGI_GET_VALUES_RESULT 10 #define FCGI_UNKNOWN_TYPE 11 #define FCGI_MAXTYPE (FCGI_UNKNOWN_TYPE) #define FCGI_REQUEST_COMPLETE 0 #define FCGI_CANT_MPX_CONN 1 #define FCGI_OVERLOADED 2 #define FCGI_UNKNOWN_ROLE 3 #define FD_RESERVE 5 #define FD_NEEDED 6 int cgi_inflight = 0; struct listener { struct event ev, pause; }; struct env_val { SLIST_ENTRY(env_val) entry; char *val; }; SLIST_HEAD(env_head, env_val); struct fcgi_record_header { uint8_t version; uint8_t type; uint16_t id; uint16_t content_len; uint8_t padding_len; uint8_t reserved; }__packed; struct fcgi_response { TAILQ_ENTRY(fcgi_response) entry; uint8_t data[FCGI_RECORD_SIZE]; size_t data_pos; size_t data_len; }; TAILQ_HEAD(fcgi_response_head, fcgi_response); struct fcgi_stdin { TAILQ_ENTRY(fcgi_stdin) entry; uint8_t data[FCGI_RECORD_SIZE]; size_t data_pos; size_t data_len; }; TAILQ_HEAD(fcgi_stdin_head, fcgi_stdin); struct request { LIST_ENTRY(request) entry; struct event ev; struct event resp_ev; struct event tmo; int fd; uint8_t buf[FCGI_RECORD_SIZE]; size_t buf_pos; size_t buf_len; struct fcgi_response_head response_head; struct fcgi_stdin_head stdin_head; uint16_t id; char script_name[PATH_MAX]; struct env_head env; int env_count; pid_t script_pid; int script_status; struct event script_ev; struct event script_err_ev; struct event script_stdin_ev; int stdin_fd_closed; int stdout_fd_closed; int stderr_fd_closed; uint8_t script_flags; uint8_t request_started; int inflight_fds_accounted; }; LIST_HEAD(requests_head, request); struct slowcgi_proc { struct requests_head requests; struct event ev_sigchld; }; struct fcgi_begin_request_body { uint16_t role; uint8_t flags; uint8_t reserved[5]; }__packed; struct fcgi_end_request_body { uint32_t app_status; uint8_t protocol_status; uint8_t reserved[3]; }__packed; __dead void usage(void); int slowcgi_listen(char *, struct passwd *); void slowcgi_paused(int, short, void *); int accept_reserve(int, struct sockaddr *, socklen_t *, int, int *); void slowcgi_accept(int, short, void *); void slowcgi_request(int, short, void *); void slowcgi_response(int, short, void *); void slowcgi_add_response(struct request *, struct fcgi_response *); void slowcgi_timeout(int, short, void *); void slowcgi_sig_handler(int, short, void *); size_t parse_record(uint8_t * , size_t, struct request *); void parse_begin_request(uint8_t *, uint16_t, struct request *, uint16_t); void parse_params(uint8_t *, uint16_t, struct request *, uint16_t); void parse_stdin(uint8_t *, uint16_t, struct request *, uint16_t); void exec_cgi(struct request *); void script_in(int, struct event *, struct request *, uint8_t); void script_std_in(int, short, void *); void script_err_in(int, short, void *); void script_out(int, short, void *); void create_end_record(struct request *); void dump_fcgi_record(const char *, struct fcgi_record_header *); void dump_fcgi_record_header(const char *, struct fcgi_record_header *); void dump_fcgi_begin_request_body(const char *, struct fcgi_begin_request_body *); void dump_fcgi_end_request_body(const char *, struct fcgi_end_request_body *); void cleanup_request(struct request *); struct loggers { __dead void (*err)(int, const char *, ...) __attribute__((__format__ (printf, 2, 3))); __dead void (*errx)(int, const char *, ...) __attribute__((__format__ (printf, 2, 3))); void (*warn)(const char *, ...) __attribute__((__format__ (printf, 1, 2))); void (*warnx)(const char *, ...) __attribute__((__format__ (printf, 1, 2))); void (*info)(const char *, ...) __attribute__((__format__ (printf, 1, 2))); void (*debug)(const char *, ...) __attribute__((__format__ (printf, 1, 2))); }; const struct loggers conslogger = { err, errx, warn, warnx, warnx, /* info */ warnx /* debug */ }; __dead void syslog_err(int, const char *, ...) __attribute__((__format__ (printf, 2, 3))); __dead void syslog_errx(int, const char *, ...) __attribute__((__format__ (printf, 2, 3))); void syslog_warn(const char *, ...) __attribute__((__format__ (printf, 1, 2))); void syslog_warnx(const char *, ...) __attribute__((__format__ (printf, 1, 2))); void syslog_info(const char *, ...) __attribute__((__format__ (printf, 1, 2))); void syslog_debug(const char *, ...) __attribute__((__format__ (printf, 1, 2))); void syslog_vstrerror(int, int, const char *, va_list) __attribute__((__format__ (printf, 3, 0))); const struct loggers syslogger = { syslog_err, syslog_errx, syslog_warn, syslog_warnx, syslog_info, syslog_debug }; const struct loggers *logger = &conslogger; #define lerr(_e, _f...) logger->err((_e), _f) #define lerrx(_e, _f...) logger->errx((_e), _f) #define lwarn(_f...) logger->warn(_f) #define lwarnx(_f...) logger->warnx(_f) #define linfo(_f...) logger->info(_f) #define ldebug(_f...) logger->debug(_f) __dead void usage(void) { extern char *__progname; fprintf(stderr, "usage: %s [-dv] [-p path] [-s socket] [-U user] [-u user]\n", __progname); exit(1); } struct timeval timeout = { TIMEOUT_DEFAULT, 0 }; struct slowcgi_proc slowcgi_proc; int debug = 0; int verbose = 0; int on = 1; char *fcgi_socket = "/var/www/run/slowcgi.sock"; int main(int argc, char *argv[]) { extern char *__progname; struct listener *l = NULL; struct passwd *pw; struct stat sb; int c, fd; const char *chrootpath = NULL; const char *sock_user = SLOWCGI_USER; const char *slowcgi_user = SLOWCGI_USER; /* * Ensure we have fds 0-2 open so that we have no fd overlaps * in exec_cgi() later. Just exit on error, we don't have enough * fds open to output an error message anywhere. */ for (c=0; c < 3; c++) { if (fstat(c, &sb) == -1) { if ((fd = open("/dev/null", O_RDWR)) != -1) { if (dup2(fd, c) == -1) exit(1); if (fd > c) close(fd); } else exit(1); } } while ((c = getopt(argc, argv, "dp:s:U:u:v")) != -1) { switch (c) { case 'd': debug++; break; case 'p': chrootpath = optarg; break; case 's': fcgi_socket = optarg; break; case 'U': sock_user = optarg; break; case 'u': slowcgi_user = optarg; break; case 'v': verbose++; break; default: usage(); /* NOTREACHED */ } } if (geteuid() != 0) errx(1, "need root privileges"); if (!debug && daemon(0, 0) == -1) err(1, "daemon"); if (!debug) { openlog(__progname, LOG_PID|LOG_NDELAY, LOG_DAEMON); logger = &syslogger; } ldebug("sock_user: %s", sock_user); pw = getpwnam(sock_user); if (pw == NULL) lerrx(1, "no %s user", sock_user); fd = slowcgi_listen(fcgi_socket, pw); ldebug("slowcgi_user: %s", slowcgi_user); pw = getpwnam(slowcgi_user); if (pw == NULL) lerrx(1, "no %s user", slowcgi_user); if (chrootpath == NULL) chrootpath = pw->pw_dir; if (chroot(chrootpath) == -1) lerr(1, "chroot(%s)", chrootpath); ldebug("chroot: %s", chrootpath); if (chdir("/") == -1) lerr(1, "chdir(/)"); if (setgroups(1, &pw->pw_gid) || setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) lerr(1, "unable to revoke privs"); if (pledge("stdio rpath unix proc exec", NULL) == -1) lerr(1, "pledge"); LIST_INIT(&slowcgi_proc.requests); event_init(); l = calloc(1, sizeof(*l)); if (l == NULL) lerr(1, "listener ev alloc"); event_set(&l->ev, fd, EV_READ | EV_PERSIST, slowcgi_accept, l); event_add(&l->ev, NULL); evtimer_set(&l->pause, slowcgi_paused, l); signal_set(&slowcgi_proc.ev_sigchld, SIGCHLD, slowcgi_sig_handler, &slowcgi_proc); signal(SIGPIPE, SIG_IGN); signal_add(&slowcgi_proc.ev_sigchld, NULL); event_dispatch(); return (0); } int slowcgi_listen(char *path, struct passwd *pw) { struct sockaddr_un sun; mode_t old_umask; int fd; if ((fd = socket(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0)) == -1) lerr(1, "slowcgi_listen: socket"); bzero(&sun, sizeof(sun)); sun.sun_family = AF_UNIX; if (strlcpy(sun.sun_path, path, sizeof(sun.sun_path)) >= sizeof(sun.sun_path)) lerrx(1, "socket path too long"); if (unlink(path) == -1) if (errno != ENOENT) lerr(1, "slowcgi_listen: unlink %s", path); old_umask = umask(S_IXUSR|S_IXGRP|S_IWOTH|S_IROTH|S_IXOTH); if (bind(fd, (struct sockaddr *)&sun, sizeof(sun)) == -1) lerr(1,"slowcgi_listen: bind: %s", path); umask(old_umask); if (chown(path, pw->pw_uid, pw->pw_gid) == -1) lerr(1, "slowcgi_listen: chown: %s", path); if (listen(fd, 5) == -1) lerr(1, "listen"); ldebug("socket: %s", path); return fd; } void slowcgi_paused(int fd, short events, void *arg) { struct listener *l = arg; event_add(&l->ev, NULL); } int accept_reserve(int sockfd, struct sockaddr *addr, socklen_t *addrlen, int reserve, int *counter) { int ret; if (getdtablecount() + reserve + ((*counter + 1) * FD_NEEDED) >= getdtablesize()) { ldebug("inflight fds exceeded"); errno = EMFILE; return -1; } if ((ret = accept4(sockfd, addr, addrlen, SOCK_NONBLOCK | SOCK_CLOEXEC)) > -1) { (*counter)++; ldebug("inflight incremented, now %d", *counter); } return ret; } void slowcgi_accept(int fd, short events, void *arg) { struct listener *l; struct sockaddr_storage ss; struct timeval backoff; struct request *c; socklen_t len; int s; l = arg; backoff.tv_sec = 1; backoff.tv_usec = 0; c = NULL; len = sizeof(ss); if ((s = accept_reserve(fd, (struct sockaddr *)&ss, &len, FD_RESERVE, &cgi_inflight)) == -1) { switch (errno) { case EINTR: case EWOULDBLOCK: case ECONNABORTED: return; case EMFILE: case ENFILE: event_del(&l->ev); evtimer_add(&l->pause, &backoff); return; default: lerr(1, "accept"); } } c = calloc(1, sizeof(*c)); if (c == NULL) { lwarn("cannot calloc request"); close(s); cgi_inflight--; return; } c->fd = s; c->buf_pos = 0; c->buf_len = 0; c->request_started = 0; c->stdin_fd_closed = c->stdout_fd_closed = c->stderr_fd_closed = 0; c->inflight_fds_accounted = 0; TAILQ_INIT(&c->response_head); TAILQ_INIT(&c->stdin_head); event_set(&c->ev, s, EV_READ | EV_PERSIST, slowcgi_request, c); event_add(&c->ev, NULL); event_set(&c->resp_ev, s, EV_WRITE | EV_PERSIST, slowcgi_response, c); evtimer_set(&c->tmo, slowcgi_timeout, c); evtimer_add(&c->tmo, &timeout); LIST_INSERT_HEAD(&slowcgi_proc.requests, c, entry); } void slowcgi_timeout(int fd, short events, void *arg) { cleanup_request((struct request*) arg); } void slowcgi_sig_handler(int sig, short event, void *arg) { struct request *c; struct slowcgi_proc *p; pid_t pid; int status; p = arg; switch (sig) { case SIGCHLD: while ((pid = waitpid(WAIT_ANY, &status, WNOHANG)) > 0) { LIST_FOREACH(c, &p->requests, entry) if (c->script_pid == pid) break; if (c == NULL) { lwarnx("caught exit of unknown child %i", pid); continue; } if (WIFSIGNALED(status)) c->script_status = WTERMSIG(status); else c->script_status = WEXITSTATUS(status); if (c->script_flags == (STDOUT_DONE | STDERR_DONE)) create_end_record(c); c->script_flags |= SCRIPT_DONE; ldebug("wait: %s", c->script_name); } if (pid == -1 && errno != ECHILD) lwarn("waitpid"); break; default: lerr(1, "unexpected signal: %d", sig); break; } } void slowcgi_add_response(struct request *c, struct fcgi_response *resp) { struct fcgi_record_header *header; size_t padded_len; header = (struct fcgi_record_header*)resp->data; /* The FastCGI spec suggests to align the output buffer */ padded_len = FCGI_ALIGN(resp->data_len); if (padded_len > resp->data_len) { /* There should always be FCGI_PADDING_SIZE bytes left */ if (padded_len > FCGI_RECORD_SIZE) lerr(1, "response too long"); header->padding_len = padded_len - resp->data_len; resp->data_len = padded_len; } TAILQ_INSERT_TAIL(&c->response_head, resp, entry); event_add(&c->resp_ev, NULL); } void slowcgi_response(int fd, short events, void *arg) { struct request *c; struct fcgi_record_header *header; struct fcgi_response *resp; ssize_t n; c = arg; while ((resp = TAILQ_FIRST(&c->response_head))) { header = (struct fcgi_record_header*) resp->data; if (debug > 1) dump_fcgi_record("resp ", header); n = write(fd, resp->data + resp->data_pos, resp->data_len); if (n == -1) { if (errno == EAGAIN || errno == EINTR) return; cleanup_request(c); return; } resp->data_pos += n; resp->data_len -= n; if (resp->data_len == 0) { TAILQ_REMOVE(&c->response_head, resp, entry); free(resp); } } if (TAILQ_EMPTY(&c->response_head)) { if (c->script_flags == (STDOUT_DONE | STDERR_DONE | SCRIPT_DONE)) cleanup_request(c); else event_del(&c->resp_ev); } } void slowcgi_request(int fd, short events, void *arg) { struct request *c; ssize_t n; size_t parsed; c = arg; n = read(fd, c->buf + c->buf_pos + c->buf_len, FCGI_RECORD_SIZE - c->buf_pos-c->buf_len); switch (n) { case -1: switch (errno) { case EINTR: case EAGAIN: return; default: goto fail; } break; case 0: ldebug("closed connection"); goto fail; default: break; } c->buf_len += n; /* * Parse the records as they are received. Per the FastCGI * specification, the server need only receive the FastCGI * parameter records in full; it is free to begin execution * at that point, which is what happens here. */ do { parsed = parse_record(c->buf + c->buf_pos, c->buf_len, c); c->buf_pos += parsed; c->buf_len -= parsed; } while (parsed > 0 && c->buf_len > 0); /* Make space for further reads */ if (c->buf_len > 0) { bcopy(c->buf + c->buf_pos, c->buf, c->buf_len); c->buf_pos = 0; } return; fail: cleanup_request(c); } void parse_begin_request(uint8_t *buf, uint16_t n, struct request *c, uint16_t id) { /* XXX -- FCGI_CANT_MPX_CONN */ if (c->request_started) { lwarnx("unexpected FCGI_BEGIN_REQUEST, ignoring"); return; } if (n != sizeof(struct fcgi_begin_request_body)) { lwarnx("wrong size %d != %lu", n, sizeof(struct fcgi_begin_request_body)); return; } c->request_started = 1; c->id = id; SLIST_INIT(&c->env); c->env_count = 0; } void parse_params(uint8_t *buf, uint16_t n, struct request *c, uint16_t id) { struct env_val *env_entry; uint32_t name_len, val_len; if (!c->request_started) { lwarnx("FCGI_PARAMS without FCGI_BEGIN_REQUEST, ignoring"); return; } if (c->id != id) { lwarnx("unexpected id, ignoring"); return; } /* * If this is the last FastCGI parameter record, * begin execution of the CGI script. */ if (n == 0) { exec_cgi(c); return; } while (n > 0) { if (buf[0] >> 7 == 0) { name_len = buf[0]; n--; buf++; } else { if (n > 3) { name_len = ((buf[0] & 0x7f) << 24) + (buf[1] << 16) + (buf[2] << 8) + buf[3]; n -= 4; buf += 4; } else return; } if (n > 0) { if (buf[0] >> 7 == 0) { val_len = buf[0]; n--; buf++; } else { if (n > 3) { val_len = ((buf[0] & 0x7f) << 24) + (buf[1] << 16) + (buf[2] << 8) + buf[3]; n -= 4; buf += 4; } else return; } } else return; if (n < name_len + val_len) return; if ((env_entry = malloc(sizeof(struct env_val))) == NULL) { lwarnx("cannot allocate env_entry"); return; } if ((env_entry->val = calloc(sizeof(char), name_len + val_len + 2)) == NULL) { lwarnx("cannot allocate env_entry->val"); free(env_entry); return; } bcopy(buf, env_entry->val, name_len); buf += name_len; n -= name_len; env_entry->val[name_len] = '\0'; if (val_len < PATH_MAX && strcmp(env_entry->val, "SCRIPT_NAME") == 0 && c->script_name[0] == '\0') { bcopy(buf, c->script_name, val_len); c->script_name[val_len] = '\0'; } else if (val_len < PATH_MAX && strcmp(env_entry->val, "SCRIPT_FILENAME") == 0) { bcopy(buf, c->script_name, val_len); c->script_name[val_len] = '\0'; } env_entry->val[name_len] = '='; bcopy(buf, (env_entry->val) + name_len + 1, val_len); buf += val_len; n -= val_len; SLIST_INSERT_HEAD(&c->env, env_entry, entry); ldebug("env[%d], %s", c->env_count, env_entry->val); c->env_count++; } } void parse_stdin(uint8_t *buf, uint16_t n, struct request *c, uint16_t id) { struct fcgi_stdin *node; if (c->id != id) { lwarnx("unexpected id, ignoring"); return; } if ((node = calloc(1, sizeof(struct fcgi_stdin))) == NULL) { lwarnx("cannot calloc stdin node"); return; } bcopy(buf, node->data, n); node->data_pos = 0; node->data_len = n; TAILQ_INSERT_TAIL(&c->stdin_head, node, entry); if (event_initialized(&c->script_stdin_ev)) event_add(&c->script_stdin_ev, NULL); } size_t parse_record(uint8_t *buf, size_t n, struct request *c) { struct fcgi_record_header *h; if (n < sizeof(struct fcgi_record_header)) return (0); h = (struct fcgi_record_header*) buf; if (debug > 1) dump_fcgi_record("", h); if (n < sizeof(struct fcgi_record_header) + ntohs(h->content_len) + h->padding_len) return (0); if (h->version != 1) lerrx(1, "wrong version"); switch (h->type) { case FCGI_BEGIN_REQUEST: parse_begin_request(buf + sizeof(struct fcgi_record_header), ntohs(h->content_len), c, ntohs(h->id)); break; case FCGI_PARAMS: parse_params(buf + sizeof(struct fcgi_record_header), ntohs(h->content_len), c, ntohs(h->id)); break; case FCGI_STDIN: parse_stdin(buf + sizeof(struct fcgi_record_header), ntohs(h->content_len), c, ntohs(h->id)); break; default: lwarnx("unimplemented type %d", h->type); break; } return (sizeof(struct fcgi_record_header) + ntohs(h->content_len) + h->padding_len); } /* * Fork a new CGI process to handle the request, translating * between FastCGI parameter records and CGI's environment variables, * as well as between the CGI process' stdin/stdout and the * corresponding FastCGI records. */ void exec_cgi(struct request *c) { struct env_val *env_entry; int s_in[2], s_out[2], s_err[2], i; pid_t pid; char *argv[2]; char **env; char *path; i = 0; if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, s_in) == -1) lerr(1, "socketpair"); if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, s_out) == -1) lerr(1, "socketpair"); if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, s_err) == -1) lerr(1, "socketpair"); cgi_inflight--; c->inflight_fds_accounted = 1; ldebug("fork: %s", c->script_name); switch (pid = fork()) { case -1: c->script_status = errno; lwarn("fork"); close(s_in[0]); close(s_out[0]); close(s_err[0]); close(s_in[1]); close(s_out[1]); close(s_err[1]); c->stdin_fd_closed = c->stdout_fd_closed = c->stderr_fd_closed = 1; c->script_flags = (STDOUT_DONE | STDERR_DONE | SCRIPT_DONE); create_end_record(c); return; case 0: /* Child process */ if (pledge("stdio rpath exec", NULL) == -1) lerr(1, "pledge"); close(s_in[0]); close(s_out[0]); close(s_err[0]); if (dup2(s_in[1], STDIN_FILENO) == -1) _exit(1); if (dup2(s_out[1], STDOUT_FILENO) == -1) _exit(1); if (dup2(s_err[1], STDERR_FILENO) == -1) _exit(1); close(s_in[1]); close(s_out[1]); close(s_err[1]); signal(SIGPIPE, SIG_DFL); path = strrchr(c->script_name, '/'); if (path != NULL) { if (path != c->script_name) { *path = '\0'; if (chdir(c->script_name) == -1) lwarn("cannot chdir to %s", c->script_name); *path = '/'; } else if (chdir("/") == -1) lwarn("cannot chdir to /"); } argv[0] = c->script_name; argv[1] = NULL; if ((env = calloc(c->env_count + 1, sizeof(char*))) == NULL) _exit(1); SLIST_FOREACH(env_entry, &c->env, entry) env[i++] = env_entry->val; env[i++] = NULL; execve(c->script_name, argv, env); lwarn("execve %s", c->script_name); _exit(1); } /* Parent process*/ close(s_in[1]); close(s_out[1]); close(s_err[1]); fcntl(s_in[0], F_SETFD, FD_CLOEXEC); fcntl(s_out[0], F_SETFD, FD_CLOEXEC); fcntl(s_err[0], F_SETFD, FD_CLOEXEC); if (ioctl(s_in[0], FIONBIO, &on) == -1) lerr(1, "script ioctl(FIONBIO)"); if (ioctl(s_out[0], FIONBIO, &on) == -1) lerr(1, "script ioctl(FIONBIO)"); if (ioctl(s_err[0], FIONBIO, &on) == -1) lerr(1, "script ioctl(FIONBIO)"); c->script_pid = pid; event_set(&c->script_stdin_ev, s_in[0], EV_WRITE | EV_PERSIST, script_out, c); event_add(&c->script_stdin_ev, NULL); event_set(&c->script_ev, s_out[0], EV_READ | EV_PERSIST, script_std_in, c); event_add(&c->script_ev, NULL); event_set(&c->script_err_ev, s_err[0], EV_READ | EV_PERSIST, script_err_in, c); event_add(&c->script_err_ev, NULL); } void create_end_record(struct request *c) { struct fcgi_response *resp; struct fcgi_record_header *header; struct fcgi_end_request_body *end_request; if ((resp = calloc(1, sizeof(struct fcgi_response))) == NULL) { lwarnx("cannot malloc fcgi_response"); return; } header = (struct fcgi_record_header*) resp->data; header->version = 1; header->type = FCGI_END_REQUEST; header->id = htons(c->id); header->content_len = htons(sizeof(struct fcgi_end_request_body)); header->padding_len = 0; header->reserved = 0; end_request = (struct fcgi_end_request_body *) (resp->data + sizeof(struct fcgi_record_header)); end_request->app_status = htonl(c->script_status); end_request->protocol_status = FCGI_REQUEST_COMPLETE; end_request->reserved[0] = 0; end_request->reserved[1] = 0; end_request->reserved[2] = 0; resp->data_pos = 0; resp->data_len = sizeof(struct fcgi_end_request_body) + sizeof(struct fcgi_record_header); slowcgi_add_response(c, resp); } void script_in(int fd, struct event *ev, struct request *c, uint8_t type) { struct fcgi_response *resp; struct fcgi_record_header *header; ssize_t n; if ((resp = calloc(1, sizeof(struct fcgi_response))) == NULL) { lwarnx("cannot malloc fcgi_response"); return; } header = (struct fcgi_record_header*) resp->data; header->version = 1; header->type = type; header->id = htons(c->id); header->padding_len = 0; header->reserved = 0; n = read(fd, resp->data + sizeof(struct fcgi_record_header), FCGI_CONTENT_SIZE); if (n == -1) { switch (errno) { case EINTR: case EAGAIN: free(resp); return; default: n = 0; /* fake empty FCGI_STD{OUT,ERR} response */ } } header->content_len = htons(n); resp->data_pos = 0; resp->data_len = n + sizeof(struct fcgi_record_header); slowcgi_add_response(c, resp); if (n == 0) { if (type == FCGI_STDOUT) c->script_flags |= STDOUT_DONE; else c->script_flags |= STDERR_DONE; if (c->script_flags == (STDOUT_DONE | STDERR_DONE | SCRIPT_DONE)) { create_end_record(c); } event_del(ev); close(fd); if (type == FCGI_STDOUT) c->stdout_fd_closed = 1; else c->stderr_fd_closed = 1; } } void script_std_in(int fd, short events, void *arg) { struct request *c = arg; script_in(fd, &c->script_ev, c, FCGI_STDOUT); } void script_err_in(int fd, short events, void *arg) { struct request *c = arg; script_in(fd, &c->script_err_ev, c, FCGI_STDERR); } void script_out(int fd, short events, void *arg) { struct request *c; struct fcgi_stdin *node; ssize_t n; c = arg; while ((node = TAILQ_FIRST(&c->stdin_head))) { if (node->data_len == 0) { /* end of stdin marker */ close(fd); c->stdin_fd_closed = 1; break; } n = write(fd, node->data + node->data_pos, node->data_len); if (n == -1) { if (errno == EAGAIN || errno == EINTR) return; event_del(&c->script_stdin_ev); return; } node->data_pos += n; node->data_len -= n; if (node->data_len == 0) { TAILQ_REMOVE(&c->stdin_head, node, entry); free(node); } } event_del(&c->script_stdin_ev); } void cleanup_request(struct request *c) { struct fcgi_response *resp; struct fcgi_stdin *stdin_node; struct env_val *env_entry; evtimer_del(&c->tmo); if (event_initialized(&c->ev)) event_del(&c->ev); if (event_initialized(&c->resp_ev)) event_del(&c->resp_ev); if (event_initialized(&c->script_ev)) { if (!c->stdout_fd_closed) close(EVENT_FD(&c->script_ev)); event_del(&c->script_ev); } if (event_initialized(&c->script_err_ev)) { if (!c->stderr_fd_closed) close(EVENT_FD(&c->script_err_ev)); event_del(&c->script_err_ev); } if (event_initialized(&c->script_stdin_ev)) { if (!c->stdin_fd_closed) close(EVENT_FD(&c->script_stdin_ev)); event_del(&c->script_stdin_ev); } close(c->fd); while (!SLIST_EMPTY(&c->env)) { env_entry = SLIST_FIRST(&c->env); SLIST_REMOVE_HEAD(&c->env, entry); free(env_entry->val); free(env_entry); } while ((resp = TAILQ_FIRST(&c->response_head))) { TAILQ_REMOVE(&c->response_head, resp, entry); free(resp); } while ((stdin_node = TAILQ_FIRST(&c->stdin_head))) { TAILQ_REMOVE(&c->stdin_head, stdin_node, entry); free(stdin_node); } LIST_REMOVE(c, entry); if (! c->inflight_fds_accounted) cgi_inflight--; free(c); } void dump_fcgi_record(const char *p, struct fcgi_record_header *h) { dump_fcgi_record_header(p, h); if (h->type == FCGI_BEGIN_REQUEST) dump_fcgi_begin_request_body(p, (struct fcgi_begin_request_body *)(h + 1)); else if (h->type == FCGI_END_REQUEST) dump_fcgi_end_request_body(p, (struct fcgi_end_request_body *)(h + 1)); } void dump_fcgi_record_header(const char* p, struct fcgi_record_header *h) { ldebug("%sversion: %d", p, h->version); ldebug("%stype: %d", p, h->type); ldebug("%srequestId: %d", p, ntohs(h->id)); ldebug("%scontentLength: %d", p, ntohs(h->content_len)); ldebug("%spaddingLength: %d", p, h->padding_len); ldebug("%sreserved: %d", p, h->reserved); } void dump_fcgi_begin_request_body(const char *p, struct fcgi_begin_request_body *b) { ldebug("%srole %d", p, ntohs(b->role)); ldebug("%sflags %d", p, b->flags); } void dump_fcgi_end_request_body(const char *p, struct fcgi_end_request_body *b) { ldebug("%sappStatus: %d", p, ntohl(b->app_status)); ldebug("%sprotocolStatus: %d", p, b->protocol_status); } void syslog_vstrerror(int e, int priority, const char *fmt, va_list ap) { char *s; if (vasprintf(&s, fmt, ap) == -1) { syslog(LOG_EMERG, "unable to alloc in syslog_vstrerror"); exit(1); } syslog(priority, "%s: %s", s, strerror(e)); free(s); } __dead void syslog_err(int ecode, const char *fmt, ...) { va_list ap; va_start(ap, fmt); syslog_vstrerror(errno, LOG_CRIT, fmt, ap); va_end(ap); exit(ecode); } __dead void syslog_errx(int ecode, const char *fmt, ...) { va_list ap; va_start(ap, fmt); vsyslog(LOG_CRIT, fmt, ap); va_end(ap); exit(ecode); } void syslog_warn(const char *fmt, ...) { va_list ap; va_start(ap, fmt); syslog_vstrerror(errno, LOG_ERR, fmt, ap); va_end(ap); } void syslog_warnx(const char *fmt, ...) { va_list ap; va_start(ap, fmt); vsyslog(LOG_ERR, fmt, ap); va_end(ap); } void syslog_info(const char *fmt, ...) { va_list ap; va_start(ap, fmt); vsyslog(LOG_INFO, fmt, ap); va_end(ap); } void syslog_debug(const char *fmt, ...) { if (verbose > 0) { va_list ap; va_start(ap, fmt); vsyslog(LOG_DEBUG, fmt, ap); va_end(ap); } }