mirror of
https://github.com/bdnugget/kcgi.git
synced 2026-11-07 05:47:27 +00:00
632 lines
13 KiB
C
632 lines
13 KiB
C
/* $Id$ */
|
|
/*
|
|
* Copyright (c) 2012, 2014--2017 Kristaps Dzonsons <kristaps@bsd.lv>
|
|
*
|
|
* 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 "config.h"
|
|
|
|
#include <sys/stat.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/uio.h>
|
|
#include <sys/wait.h>
|
|
|
|
#include <assert.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <poll.h>
|
|
#include <signal.h>
|
|
#include <stdarg.h>
|
|
#include <stdio.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
#include "kcgi.h"
|
|
#include "extern.h"
|
|
|
|
int
|
|
kxvasprintf(char **p, const char *fmt, va_list ap)
|
|
{
|
|
int len;
|
|
|
|
if ((len = vasprintf(p, fmt, ap)) >= 0)
|
|
return len;
|
|
|
|
kutil_warn(NULL, NULL, "vasprintf");
|
|
*p = NULL;
|
|
return -1;
|
|
}
|
|
|
|
int
|
|
kxasprintf(char **p, const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
int ret;
|
|
|
|
va_start(ap, fmt);
|
|
ret = kxvasprintf(p, fmt, ap);
|
|
va_end(ap);
|
|
return ret;
|
|
}
|
|
|
|
void *
|
|
kxcalloc(size_t nm, size_t sz)
|
|
{
|
|
void *p;
|
|
|
|
if (nm == 0 || sz == 0) {
|
|
kutil_warnx(NULL, NULL, "calloc: zero length");
|
|
return NULL;
|
|
} else if ((p = calloc(nm, sz)) != NULL)
|
|
return p;
|
|
|
|
kutil_warn(NULL, NULL, "calloc: %zu, %zu", nm, sz);
|
|
return NULL;
|
|
}
|
|
|
|
void *
|
|
kxmalloc(size_t sz)
|
|
{
|
|
void *p;
|
|
|
|
if (sz == 0) {
|
|
kutil_warnx(NULL, NULL, "malloc: zero length");
|
|
return NULL;
|
|
} else if ((p = malloc(sz)) != NULL)
|
|
return p;
|
|
|
|
kutil_warn(NULL, NULL, "malloc: %zu", sz);
|
|
return NULL;
|
|
}
|
|
|
|
void *
|
|
kxrealloc(void *pp, size_t sz)
|
|
{
|
|
void *p;
|
|
|
|
if (sz == 0) {
|
|
kutil_warnx(NULL, NULL, "realloc: zero length");
|
|
return NULL;
|
|
} else if ((p = realloc(pp, sz)) != NULL)
|
|
return p;
|
|
|
|
kutil_warn(NULL, NULL, "realloc: %zu", sz);
|
|
return NULL;
|
|
}
|
|
|
|
void *
|
|
kxreallocarray(void *pp, size_t nm, size_t sz)
|
|
{
|
|
void *p;
|
|
|
|
if (sz == 0 || nm == 0) {
|
|
kutil_warnx(NULL, NULL, "reallocarray: zero length");
|
|
return NULL;
|
|
} else if ((p = reallocarray(pp, nm, sz)) != NULL)
|
|
return p;
|
|
|
|
kutil_warn(NULL, NULL, "reallocarray: %zu, %zu", nm, sz);
|
|
return NULL;
|
|
}
|
|
|
|
char *
|
|
kxstrdup(const char *cp)
|
|
{
|
|
char *p;
|
|
|
|
if (cp == NULL) {
|
|
kutil_warnx(NULL, NULL, "strdup: NULL string");
|
|
return NULL;
|
|
} else if ((p = strdup(cp)) != NULL)
|
|
return p;
|
|
|
|
kutil_warn(NULL, NULL, "strdup");
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* waitpid() and logging anything but a return with EXIT_SUCCESS.
|
|
* Returns KCGI_OK on EXIT_SUCCESS, KCGI_SYSTEM on waitpid() error,
|
|
* KCGI_FORM on child process failure.
|
|
*/
|
|
enum kcgi_err
|
|
kxwaitpid(pid_t pid)
|
|
{
|
|
int st;
|
|
|
|
if (waitpid(pid, &st, 0) == -1) {
|
|
kutil_warn(NULL, NULL, "waitpid");
|
|
return KCGI_SYSTEM;
|
|
} else if (WIFEXITED(st) && WEXITSTATUS(st) == EXIT_SUCCESS)
|
|
return KCGI_OK;
|
|
|
|
if (WIFEXITED(st))
|
|
kutil_warnx(NULL, NULL, "waitpid: child failure");
|
|
else
|
|
kutil_warnx(NULL, NULL, "waitpid: child signal");
|
|
|
|
return KCGI_FORM;
|
|
}
|
|
|
|
/*
|
|
* Set a file-descriptor as being non-blocking.
|
|
* Returns KCGI_SYSTEM on error, KCGI_OK on success.
|
|
*/
|
|
enum kcgi_err
|
|
kxsocketprep(int sock)
|
|
{
|
|
int fl;
|
|
|
|
if ((fl = fcntl(sock, F_GETFL, 0)) == -1) {
|
|
kutil_warn(NULL, NULL, "fcntl");
|
|
return KCGI_SYSTEM;
|
|
} else if (fcntl(sock, F_SETFL, fl | O_NONBLOCK) == -1) {
|
|
kutil_warn(NULL, NULL, "fcntl");
|
|
return KCGI_SYSTEM;
|
|
}
|
|
|
|
return KCGI_OK;
|
|
}
|
|
|
|
/*
|
|
* Create a non-blocking socketpair (AF_UNIX, SOCK_STREAM, protocol 0).
|
|
* Return KCGI_ENFILE on temporary failure, KCGI_SYSTEM on fatal error,
|
|
* KCGI_OK on success.
|
|
* The input socket pair is only valid on success.
|
|
*/
|
|
enum kcgi_err
|
|
kxsocketpair(int sock[2])
|
|
{
|
|
int rc;
|
|
enum kcgi_err er;
|
|
|
|
rc = socketpair(AF_UNIX, SOCK_STREAM, 0, sock);
|
|
if (rc == -1 && (EMFILE == errno || ENFILE == errno)) {
|
|
kutil_warn(NULL, NULL, "socketpair");
|
|
return KCGI_ENFILE;
|
|
} else if (rc == -1) {
|
|
kutil_warn(NULL, NULL, "socketpair");
|
|
return KCGI_SYSTEM;
|
|
}
|
|
|
|
if ((er = kxsocketprep(sock[0])) == KCGI_OK &&
|
|
((er = kxsocketprep(sock[1])) == KCGI_OK))
|
|
return KCGI_OK;
|
|
|
|
close(sock[0]);
|
|
close(sock[1]);
|
|
return er;
|
|
}
|
|
|
|
/*
|
|
* Write a string "buf".
|
|
* If "buf" is NULL, then write a zero-length string.
|
|
* See fullreadword().
|
|
* On error (which shouldn't happen), this will exit the process with
|
|
* the exit code EXIT_FAILURE.
|
|
*/
|
|
void
|
|
fullwriteword(int fd, const char *buf)
|
|
{
|
|
size_t sz;
|
|
|
|
if (buf != NULL) {
|
|
sz = strlen(buf);
|
|
fullwrite(fd, &sz, sizeof(size_t));
|
|
fullwrite(fd, buf, sz);
|
|
} else {
|
|
sz = 0;
|
|
fullwrite(fd, &sz, sizeof(size_t));
|
|
}
|
|
}
|
|
|
|
/*
|
|
* This is like fullwrite() but it does not bail out on errors.
|
|
* We need this for writing our response to a socket that may be closed
|
|
* at any time, like the FastCGI one.
|
|
* Returns KCGI_SYSTEM, KCGI_HUP, or KCGI_OK.
|
|
* Ignores SIGPIPE, restoring it on exit.
|
|
*/
|
|
enum kcgi_err
|
|
fullwritenoerr(int fd, const void *buf, size_t bufsz)
|
|
{
|
|
ssize_t ssz;
|
|
size_t sz;
|
|
struct pollfd pfd;
|
|
int rc;
|
|
enum kcgi_err er = KCGI_OK;
|
|
void (*sig)(int);
|
|
|
|
pfd.fd = fd;
|
|
pfd.events = POLLOUT;
|
|
|
|
if ((sig = signal(SIGPIPE, SIG_IGN)) == SIG_ERR) {
|
|
kutil_warn(NULL, NULL, "signal");
|
|
return KCGI_SYSTEM;
|
|
}
|
|
|
|
for (sz = 0; sz < bufsz; sz += (size_t)ssz) {
|
|
if ((rc = poll(&pfd, 1, -1)) < 0) {
|
|
kutil_warn(NULL, NULL, "poll");
|
|
er = KCGI_SYSTEM;
|
|
break;
|
|
} else if (rc == 0) {
|
|
kutil_warnx(NULL, NULL, "poll: timeout!?");
|
|
ssz = 0;
|
|
continue;
|
|
}
|
|
|
|
if (pfd.revents & POLLHUP) {
|
|
kutil_warnx(NULL, NULL, "poll: hangup");
|
|
er = KCGI_HUP;
|
|
break;
|
|
} else if (pfd.revents & POLLERR) {
|
|
kutil_warnx(NULL, NULL, "poll: error");
|
|
er = KCGI_SYSTEM;
|
|
break;
|
|
}
|
|
|
|
/* See note in fullwrite(). */
|
|
|
|
#ifdef __APPLE__
|
|
if (!(pfd.revents & POLLOUT) &&
|
|
!(pfd.revents & POLLNVAL)) {
|
|
kutil_warnx(NULL, NULL, "poll: no output");
|
|
er = KCGI_SYSTEM;
|
|
break;
|
|
}
|
|
#else
|
|
if (!(pfd.revents & POLLOUT)) {
|
|
kutil_warnx(NULL, NULL, "poll: no output");
|
|
er = KCGI_SYSTEM;
|
|
break;
|
|
}
|
|
#endif
|
|
|
|
if ((ssz = write(fd, buf + sz, bufsz - sz)) < 0) {
|
|
er = errno == EPIPE ? KCGI_HUP : KCGI_SYSTEM;
|
|
kutil_warn(NULL, NULL, "write");
|
|
break;
|
|
} else if (sz > SIZE_MAX - (size_t)ssz) {
|
|
kutil_warnx(NULL, NULL, "write: overflow");
|
|
er = KCGI_SYSTEM;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (signal(SIGPIPE, sig) == SIG_ERR) {
|
|
kutil_warn(NULL, NULL, "signal");
|
|
er = KCGI_SYSTEM;
|
|
}
|
|
|
|
return er;
|
|
}
|
|
|
|
/*
|
|
* Write "buf", which can be NULL so long as bufsz is zero in which case
|
|
* it's a noop.
|
|
* On error (which shouldn't happen), this will exit the process with
|
|
* the exit code EXIT_FAILURE.
|
|
*/
|
|
void
|
|
fullwrite(int fd, const void *buf, size_t bufsz)
|
|
{
|
|
ssize_t ssz;
|
|
size_t sz;
|
|
struct pollfd pfd;
|
|
int rc;
|
|
|
|
if (bufsz == 0)
|
|
return;
|
|
|
|
assert(buf != NULL);
|
|
pfd.fd = fd;
|
|
pfd.events = POLLOUT;
|
|
|
|
for (sz = 0; sz < bufsz; sz += (size_t)ssz) {
|
|
if ((rc = poll(&pfd, 1, INFTIM)) == 0) {
|
|
kutil_warnx(NULL, NULL, "poll: timeout!?");
|
|
ssz = 0;
|
|
continue;
|
|
} else if (rc < 0)
|
|
kutil_err(NULL, NULL, "poll");
|
|
|
|
if (pfd.revents & POLLHUP)
|
|
kutil_errx(NULL, NULL, "poll: hangup");
|
|
else if (pfd.revents & POLLERR)
|
|
kutil_errx(NULL, NULL, "poll: error");
|
|
|
|
/*
|
|
* This CPP exists because testing on Mac OS X will have
|
|
* "fd" point to a device and poll(2) returns POLLNVAL
|
|
* if the descriptor is to a device.
|
|
* This is documented in the BUGS section of poll(2).
|
|
*/
|
|
#ifdef __APPLE__
|
|
if (!(pfd.revents & POLLOUT) &&
|
|
!(pfd.revents & POLLNVAL))
|
|
kutil_errx(NULL, NULL, "poll: no output");
|
|
#else
|
|
if (!(pfd.revents & POLLOUT))
|
|
kutil_errx(NULL, NULL, "poll: no output");
|
|
#endif
|
|
|
|
if ((ssz = write(fd, buf + sz, bufsz - sz)) < 0)
|
|
kutil_err(NULL, NULL, "write");
|
|
else if (sz > SIZE_MAX - (size_t)ssz)
|
|
kutil_errx(NULL, NULL, "write: overflow");
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Read the contents of "buf" of size "bufsz".
|
|
* If "eofok" is set, return zero if there is no data to read.
|
|
* If it's not set, this condition returns <0.
|
|
* Returns <0 on errors and >0 on success.
|
|
*/
|
|
int
|
|
fullread(int fd, void *buf, size_t bufsz, int eofok, enum kcgi_err *er)
|
|
{
|
|
ssize_t ssz;
|
|
size_t sz;
|
|
struct pollfd pfd;
|
|
int rc;
|
|
|
|
pfd.fd = fd;
|
|
pfd.events = POLLIN;
|
|
|
|
for (sz = 0; sz < bufsz; sz += (size_t)ssz) {
|
|
if ((rc = poll(&pfd, 1, INFTIM)) < 0) {
|
|
kutil_warn(NULL, NULL, "poll");
|
|
*er = KCGI_SYSTEM;
|
|
return (-1);
|
|
} else if (rc == 0) {
|
|
kutil_warnx(NULL, NULL, "poll: timeout!?");
|
|
ssz = 0;
|
|
continue;
|
|
}
|
|
|
|
if (!(pfd.revents & POLLIN)) {
|
|
if (eofok && sz == 0) {
|
|
*er = KCGI_OK;
|
|
return 0;
|
|
}
|
|
kutil_warnx(NULL, NULL, "poll: no input");
|
|
*er = KCGI_FORM;
|
|
return (-1);
|
|
}
|
|
|
|
if ((ssz = read(fd, buf + sz, bufsz - sz)) < 0) {
|
|
kutil_warn(NULL, NULL, "read");
|
|
*er = KCGI_SYSTEM;
|
|
return (-1);
|
|
} else if (ssz == 0 && sz > 0) {
|
|
kutil_warnx(NULL, NULL, "read: short read");
|
|
*er = KCGI_FORM;
|
|
return (-1);
|
|
} else if (ssz == 0 && sz == 0 && !eofok) {
|
|
kutil_warnx(NULL, NULL, "read: end of file");
|
|
*er = KCGI_FORM;
|
|
return (-1);
|
|
} else if (ssz == 0 && sz == 0 && eofok) {
|
|
*er = KCGI_OK;
|
|
return 0;
|
|
} else if (sz > SIZE_MAX - (size_t)ssz) {
|
|
kutil_warnx(NULL, NULL, "read: overflow");
|
|
*er = KCGI_FORM;
|
|
return (-1);
|
|
}
|
|
}
|
|
|
|
*er = KCGI_OK;
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Read a string from the stream.
|
|
* Return KCGI_OK on success or another on error.
|
|
* This will set cp to NULL and sz to zero on failure.
|
|
* The cp array (on success) is always NUL-terminated, although the
|
|
* buffer it reads is opaque.
|
|
* On success, "sz" may legit be zero.
|
|
*/
|
|
enum kcgi_err
|
|
fullreadwordsz(int fd, char **cp, size_t *sz)
|
|
{
|
|
enum kcgi_err ke;
|
|
int rc;
|
|
|
|
*cp = NULL;
|
|
*sz = 0;
|
|
|
|
if (fullread(fd, sz, sizeof(size_t), 0, &ke) < 0)
|
|
return ke;
|
|
|
|
/* TODO: check additive overflow of "sz + 1". */
|
|
|
|
if ((*cp = kxmalloc(*sz + 1)) == NULL) {
|
|
*sz = 0;
|
|
return KCGI_ENOMEM;
|
|
}
|
|
(*cp)[*sz] = '\0';
|
|
|
|
if (*sz == 0)
|
|
return KCGI_OK;
|
|
|
|
/* Because we don't set "eofok", never returns zero. */
|
|
|
|
if ((rc = fullread(fd, *cp, *sz, 0, &ke)) > 0) {
|
|
assert(ke == KCGI_OK);
|
|
return ke;
|
|
}
|
|
|
|
assert(rc < 0);
|
|
assert(ke != KCGI_OK);
|
|
free(*cp);
|
|
*cp = NULL;
|
|
*sz = 0;
|
|
return ke;
|
|
}
|
|
|
|
/*
|
|
* See fullreadwordsz() with a discarded size.
|
|
*/
|
|
enum kcgi_err
|
|
fullreadword(int fd, char **cp)
|
|
{
|
|
size_t sz;
|
|
|
|
return fullreadwordsz(fd, cp, &sz);
|
|
}
|
|
|
|
/*
|
|
* Write a file-descriptor "sendfd" and a buffer "b" of length "bsz",
|
|
* which must be 256 bytes or fewer, but not zero.
|
|
* See fullwritefd().
|
|
* Returns zero on failure (any kind), non-zero on success.
|
|
*/
|
|
int
|
|
fullwritefd(int fd, int sendfd, void *b, size_t bsz)
|
|
{
|
|
struct msghdr msg;
|
|
int rc;
|
|
char buf[CMSG_SPACE(sizeof(fd))];
|
|
struct iovec io;
|
|
struct cmsghdr *cmsg;
|
|
struct pollfd pfd;
|
|
|
|
assert(bsz <= 256 && bsz > 0);
|
|
|
|
memset(buf, 0, sizeof(buf));
|
|
memset(&msg, 0, sizeof(struct msghdr));
|
|
memset(&io, 0, sizeof(struct iovec));
|
|
|
|
io.iov_base = b;
|
|
io.iov_len = bsz;
|
|
|
|
msg.msg_iov = &io;
|
|
msg.msg_iovlen = 1;
|
|
msg.msg_control = buf;
|
|
msg.msg_controllen = sizeof(buf);
|
|
|
|
cmsg = CMSG_FIRSTHDR(&msg);
|
|
cmsg->cmsg_level = SOL_SOCKET;
|
|
cmsg->cmsg_type = SCM_RIGHTS;
|
|
cmsg->cmsg_len = CMSG_LEN(sizeof(fd));
|
|
|
|
*((int *)CMSG_DATA(cmsg)) = sendfd;
|
|
|
|
msg.msg_controllen = cmsg->cmsg_len;
|
|
|
|
pfd.fd = fd;
|
|
pfd.events = POLLOUT;
|
|
|
|
again:
|
|
if ((rc = poll(&pfd, 1, INFTIM)) < 0) {
|
|
kutil_warn(NULL, NULL, "poll");
|
|
return 0;
|
|
} else if (rc == 0) {
|
|
kutil_warnx(NULL, NULL, "poll: timeout!?");
|
|
goto again;
|
|
}
|
|
|
|
if (!(pfd.revents & POLLOUT)) {
|
|
kutil_warnx(NULL, NULL, "poll: no output");
|
|
return 0;
|
|
}
|
|
|
|
if ((rc = sendmsg(fd, &msg, 0)) < 0) {
|
|
kutil_warn(NULL, NULL, "sendmsg");
|
|
return 0;
|
|
} else if (rc == 0) {
|
|
kutil_warnx(NULL, NULL, "sendmsg: short write");
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Read a file-descriptor into "recvfd" and a buffer "b" of length
|
|
* "bsz", which must be 256 bytes or fewer, but not zero.
|
|
* See fullwritefd().
|
|
* Returns <0 on system failure, 0 on hangup (remote end closed), and >0
|
|
* on success.
|
|
* The output pointers are only set on success.
|
|
*/
|
|
int
|
|
fullreadfd(int fd, int *recvfd, void *b, size_t bsz)
|
|
{
|
|
struct msghdr msg;
|
|
char m_buffer[256];
|
|
char c_buffer[256];
|
|
struct iovec io;
|
|
struct cmsghdr *cmsg;
|
|
int rc;
|
|
struct pollfd pfd;
|
|
|
|
assert(bsz <= 256 && bsz > 0);
|
|
|
|
memset(&msg, 0, sizeof(struct msghdr));
|
|
memset(&io, 0, sizeof(struct iovec));
|
|
|
|
io.iov_base = m_buffer;
|
|
io.iov_len = sizeof(m_buffer);
|
|
msg.msg_iov = &io;
|
|
msg.msg_iovlen = 1;
|
|
|
|
msg.msg_control = c_buffer;
|
|
msg.msg_controllen = sizeof(c_buffer);
|
|
|
|
pfd.fd = fd;
|
|
pfd.events = POLLIN;
|
|
|
|
again:
|
|
if ((rc = poll(&pfd, 1, INFTIM)) < 0) {
|
|
kutil_warn(NULL, NULL, "poll");
|
|
return (-1);
|
|
} else if (rc == 0) {
|
|
kutil_warnx(NULL, NULL, "poll timeout!?!?");
|
|
goto again;
|
|
}
|
|
|
|
if (!(pfd.revents & POLLIN)) {
|
|
kutil_warnx(NULL, NULL, "poll: no input");
|
|
return 0;
|
|
}
|
|
|
|
if ((rc = recvmsg(fd, &msg, 0)) < 0) {
|
|
kutil_warn(NULL, NULL, "recvmsg");
|
|
return (-1);
|
|
} else if (rc == 0) {
|
|
kutil_warnx(NULL, NULL, "recvmsg: short read");
|
|
return 0;
|
|
}
|
|
|
|
memcpy(b, m_buffer, bsz);
|
|
for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL;
|
|
cmsg = CMSG_NXTHDR(&msg, cmsg)) {
|
|
if (cmsg->cmsg_len == CMSG_LEN(sizeof(int)) &&
|
|
cmsg->cmsg_level == SOL_SOCKET &&
|
|
cmsg->cmsg_type == SCM_RIGHTS) {
|
|
*recvfd = *(int *)CMSG_DATA(cmsg);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
kutil_warnx(NULL, NULL, "recvmsg: no SCM_RIGHTS");
|
|
return (-1);
|
|
}
|