811 lines
20 KiB
C
811 lines
20 KiB
C
/*-
|
|
* Copyright (c) 2026 Taylor R. Campbell
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*/
|
|
|
|
#include "config.h"
|
|
#include <sys/select.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/wait.h>
|
|
|
|
#include <arpa/inet.h>
|
|
|
|
#include <netinet/in.h>
|
|
|
|
#include <assert.h>
|
|
#include <err.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <netdb.h>
|
|
#include <signal.h>
|
|
#include <stdarg.h>
|
|
#include <stdbool.h>
|
|
#include <stddef.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
#include <getarg.h>
|
|
#include <roken.h>
|
|
|
|
/*
|
|
* Arbitrary but matches SOCKS5.
|
|
*/
|
|
#define SOCKS4A_MAXUSERID 255
|
|
|
|
/*
|
|
* Binary DNS name -- *(n(1 byte), label(n bytes)), 0(1 byte) -- is
|
|
* limited to 255 bytes. Hostname text notation with dots doesn't have
|
|
* the zero length byte for the trailing empty label, so that's limited
|
|
* to 254 bytes with a trailing dot, or 253 bytes without. To keep it
|
|
* simple and allow the trailing dot or not, we'll just take 254 as the
|
|
* maximum length.
|
|
*/
|
|
#define SOCKS4A_MAXHOSTNAME 254
|
|
|
|
#define SOCKS4A_MAXUSERHOST0 \
|
|
(SOCKS4A_MAXUSERID + 1 + SOCKS4A_MAXHOSTNAME + 1)
|
|
|
|
struct socks4a_request {
|
|
uint8_t vn;
|
|
uint8_t cd;
|
|
uint8_t dstport[2];
|
|
uint8_t dstip[4];
|
|
char userhost[SOCKS4A_MAXUSERHOST0];
|
|
};
|
|
|
|
struct socks4a_reply {
|
|
uint8_t vn;
|
|
uint8_t cd;
|
|
uint8_t dstport[2];
|
|
uint8_t dstip[4];
|
|
};
|
|
|
|
/*
|
|
* readall(fd, buf, len)
|
|
*
|
|
* Read exactly len bytes from fd into buf, or return -1 if we hit
|
|
* EOF or read error.
|
|
*
|
|
* fd must be in blocking mode.
|
|
*/
|
|
static int
|
|
readall(int fd, void *buf, size_t len)
|
|
{
|
|
char *p;
|
|
ssize_t nread;
|
|
|
|
for (p = buf; len > 0; p += (size_t)nread, len -= (size_t)nread) {
|
|
nread = read(fd, p, len);
|
|
if (nread == -1)
|
|
return -1;
|
|
if (nread == 0)
|
|
return -1;
|
|
if ((size_t)nread > len)
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* writeall(fd, buf, len)
|
|
*
|
|
* Write exactly len bytes from buf to fd, or return -1 if we hit
|
|
* write error.
|
|
*
|
|
* fd must be in blocking mode.
|
|
*/
|
|
static int
|
|
writeall(int fd, const void *buf, size_t len)
|
|
{
|
|
const char *p;
|
|
ssize_t nwrit;
|
|
|
|
for (p = buf; len > 0; p += (size_t)nwrit, len -= (size_t)nwrit) {
|
|
nwrit = write(fd, p, len);
|
|
if (nwrit == -1)
|
|
return -1;
|
|
if (nwrit == 0) /* shouldn't happen in blocking mode */
|
|
return -1;
|
|
if ((size_t)nwrit > len)
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* readstep(fd, buf, &i, &n, size)
|
|
*
|
|
* Read from fd some bytes into positions [i, i + size - n) into
|
|
* the circular ring buffer buf of the given size, and advance i
|
|
* and n by the number of bytes written. Reduce i modulo size.
|
|
*/
|
|
static int
|
|
readstep(int fd, char *buf, unsigned *i, unsigned *n, unsigned size)
|
|
{
|
|
const ssize_t nread = read(fd, buf + *i, size - max(*i, *n));
|
|
|
|
if (nread == -1) {
|
|
warn("read");
|
|
return -1;
|
|
}
|
|
if (nread == 0)
|
|
return -1;
|
|
if ((size_t)nread > size - max(*i, *n))
|
|
errx(EXIT_FAILURE, "read overrun");
|
|
*i += (size_t)nread;
|
|
*i %= size;
|
|
*n += (size_t)nread;
|
|
assert(*n <= size);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* writestep(fd, buf, &i, &n, size)
|
|
*
|
|
* Write to fd some bytes from positions [i - n, i) in the
|
|
* circular ring buffer buf of the given size, and deduct the
|
|
* number of bytes written from n.
|
|
*/
|
|
static int
|
|
writestep(int fd, const char *buf, unsigned *i, unsigned *n, unsigned size)
|
|
{
|
|
size_t k, len;
|
|
ssize_t nwrit;
|
|
|
|
/*
|
|
* Write a single contiguous chunk. Could use writev but more
|
|
* work, not worth the trouble.
|
|
*/
|
|
if (*n > *i) {
|
|
k = size - (*n - *i);
|
|
len = *n - *i;
|
|
} else {
|
|
k = *i - *n;
|
|
len = *n;
|
|
}
|
|
nwrit = write(fd, buf + k, len);
|
|
if (nwrit == -1)
|
|
return -1;
|
|
if ((size_t)nwrit > len)
|
|
errx(EXIT_FAILURE, "write overrun");
|
|
*n -= len;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* transfer(clientfd, serverfd)
|
|
*
|
|
* Transfer data in both directions between clientfd and serverfd.
|
|
* If either side reports EOF or refuses writes, shut down the
|
|
* corresponding direction of the other side.
|
|
*/
|
|
static void
|
|
transfer(int clientfd, int serverfd)
|
|
{
|
|
char c2sbuf[4096];
|
|
char s2cbuf[4096];
|
|
unsigned c2s_i = 0, c2s_n = 0;
|
|
unsigned s2c_i = 0, s2c_n = 0;
|
|
bool clienteof = false, clientclosed = false;
|
|
bool servereof = false, serverclosed = false;
|
|
|
|
/*
|
|
* Loop as long as there's anything to do.
|
|
*/
|
|
for (;;) {
|
|
fd_set readfds, writefds, exceptfds;
|
|
int maxfd = -1, nfds;
|
|
|
|
FD_ZERO(&readfds);
|
|
FD_ZERO(&writefds);
|
|
FD_ZERO(&exceptfds);
|
|
|
|
/*
|
|
* If the client isn't done sending, and the server is
|
|
* still receiving, and there's space in the
|
|
* client->server buffer, wait until we can read from
|
|
* the client.
|
|
*/
|
|
if (!clienteof && c2s_n < sizeof(c2sbuf)) {
|
|
FD_SET(clientfd, &readfds);
|
|
maxfd = max(maxfd, clientfd);
|
|
}
|
|
|
|
/*
|
|
* If the client is still receiving, and there's data
|
|
* in the server->client buffer, wait until we can
|
|
* write to the client.
|
|
*/
|
|
if (!clientclosed && s2c_n > 0) {
|
|
FD_SET(clientfd, &writefds);
|
|
maxfd = max(maxfd, clientfd);
|
|
}
|
|
|
|
/*
|
|
* Ditto but the other way around for client/server.
|
|
*/
|
|
if (!servereof && s2c_n < sizeof(s2cbuf)) {
|
|
FD_SET(serverfd, &readfds);
|
|
maxfd = max(maxfd, serverfd);
|
|
}
|
|
if (!serverclosed && c2s_n > 0) {
|
|
FD_SET(serverfd, &writefds);
|
|
maxfd = max(maxfd, serverfd);
|
|
}
|
|
|
|
/*
|
|
* If there's nothing to do, stop.
|
|
*/
|
|
if (maxfd < 0)
|
|
break;
|
|
|
|
/*
|
|
* Wait until some I/O is ready.
|
|
*/
|
|
nfds = select(maxfd + 1, &readfds, &writefds, &exceptfds,
|
|
NULL);
|
|
if (nfds == -1)
|
|
err(EXIT_FAILURE, "select");
|
|
if (nfds == 0)
|
|
errx(EXIT_FAILURE, "buggy select without timeout");
|
|
assert((unsigned)nfds <= 2);
|
|
|
|
/*
|
|
* Process all the ready I/O. Handle writes first to
|
|
* free up buffer space, then reads to use the space.
|
|
*
|
|
* If send to one side fails, shut down the other side
|
|
* for reads -- we won't receive anything more from it
|
|
* to send on.
|
|
*
|
|
* If we have hit EOF from one side, _and_ we have
|
|
* written everything from the buffer to the other
|
|
* side, shut down the other side for writes -- we have
|
|
* nothing more to write.
|
|
*/
|
|
if (FD_ISSET(clientfd, &writefds)) {
|
|
assert(!clientclosed && s2c_n > 0);
|
|
if (writestep(clientfd, s2cbuf, &s2c_i, &s2c_n,
|
|
sizeof(s2cbuf))) {
|
|
clientclosed = true;
|
|
if (shutdown(serverfd, SHUT_RD) == -1)
|
|
warn("shutdown(serverfd, SHUT_RD)");
|
|
}
|
|
if (servereof && s2c_n == 0) {
|
|
if (shutdown(clientfd, SHUT_WR) == -1)
|
|
warn("shutdown(clientfd, SHUT_WR)");
|
|
}
|
|
}
|
|
if (FD_ISSET(serverfd, &writefds)) {
|
|
assert(!serverclosed && c2s_n > 0);
|
|
if (writestep(serverfd, c2sbuf, &c2s_i, &c2s_n,
|
|
sizeof(c2sbuf))) {
|
|
serverclosed = true;
|
|
if (shutdown(clientfd, SHUT_RD) == -1)
|
|
warn("shutdown(clientfd, SHUT_RD)");
|
|
}
|
|
if (clienteof && c2s_n == 0) {
|
|
if (shutdown(serverfd, SHUT_WR) == -1)
|
|
warn("shutdown(serverfd, SHUT_WR)");
|
|
}
|
|
}
|
|
|
|
if (FD_ISSET(clientfd, &readfds)) {
|
|
assert(!clienteof && c2s_n < sizeof(c2sbuf));
|
|
if (readstep(clientfd, c2sbuf, &c2s_i, &c2s_n,
|
|
sizeof(c2sbuf))) {
|
|
clienteof = true;
|
|
if (c2s_n == 0 &&
|
|
shutdown(serverfd, SHUT_WR) == -1)
|
|
warn("shutdown(serverfd, SHUT_WR)");
|
|
}
|
|
}
|
|
if (FD_ISSET(serverfd, &readfds)) {
|
|
assert(!servereof && s2c_n < sizeof(s2cbuf));
|
|
if (readstep(serverfd, s2cbuf, &s2c_i, &s2c_n,
|
|
sizeof(s2cbuf))) {
|
|
servereof = true;
|
|
if (s2c_n == 0 &&
|
|
shutdown(clientfd, SHUT_WR) == -1)
|
|
warn("shutdown(clientfd, SHUT_WR)");
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Close the file descriptors (not really necessary since we're
|
|
* about to exit).
|
|
*/
|
|
if (close(clientfd) == -1)
|
|
warn("close clientfd");
|
|
if (close(serverfd) == -1)
|
|
warn("close serverfd");
|
|
}
|
|
|
|
/*
|
|
* handleclient(clientfd, argc, argv)
|
|
*
|
|
* Read a SOCKS4a request from clientfd and handle it according to
|
|
* the match specifications in the command-line arguments
|
|
* argc/argv.
|
|
*/
|
|
static void
|
|
handleclient(int clientfd, int argc, char **argv)
|
|
{
|
|
struct socks4a_request req;
|
|
struct socks4a_reply reply;
|
|
const char *user, *host;
|
|
uint16_t port;
|
|
char portstr[sizeof("65536")];
|
|
unsigned i;
|
|
|
|
/*
|
|
* Read the fixed part of a SOCKS4a request and validate it.
|
|
*/
|
|
if (readall(clientfd, &req,
|
|
offsetof(struct socks4a_request, userhost)) == -1)
|
|
goto fail;
|
|
if (req.vn != 4) /* SOCKS4a */
|
|
goto fail;
|
|
if (req.cd != 1) /* CONNECT */
|
|
goto fail;
|
|
if (req.dstip[0] != 0 || /* magic SOCKS4a IP address */
|
|
req.dstip[1] != 0 ||
|
|
req.dstip[2] != 0 ||
|
|
req.dstip[3] != 1)
|
|
goto fail;
|
|
|
|
/*
|
|
* Read the username and hostname byte by byte so we can stop
|
|
* at a NUL terminator.
|
|
*
|
|
* Suboptimal, of course, but saves us the trouble of a read
|
|
* that goes past the SOCKS4a request into the data which we
|
|
* need to transfer, because I wrote this with syscalls rather
|
|
* than with buffered stdio(3).
|
|
*/
|
|
i = 0;
|
|
user = &req.userhost[i];
|
|
for (; i < sizeof(req.userhost); i++) {
|
|
if (readall(clientfd, &req.userhost[i], 1) != 0)
|
|
goto fail;
|
|
if (req.userhost[i] == '\0')
|
|
break;
|
|
}
|
|
if (i == sizeof(req.userhost))
|
|
goto fail;
|
|
assert(req.userhost[i] == '\0');
|
|
i++;
|
|
host = &req.userhost[i];
|
|
for (; i < sizeof(req.userhost); i++) {
|
|
if (readall(clientfd, &req.userhost[i], 1) != 0)
|
|
goto fail;
|
|
if (req.userhost[i] == '\0')
|
|
break;
|
|
}
|
|
if (i == sizeof(req.userhost))
|
|
goto fail;
|
|
|
|
/*
|
|
* Format the port number as a string so we can conveniently
|
|
* compare it to one of the match arguments.
|
|
*/
|
|
port = ((uint16_t)req.dstport[0] << 8) | req.dstport[1];
|
|
snprintf(portstr, sizeof(portstr), "%d", port);
|
|
|
|
/*
|
|
* Find a matching (host, port, user) specification and connect
|
|
* to the corresponding destination host/port.
|
|
*/
|
|
for (; argc >= 5; argv += 5, argc -= 5) {
|
|
if (strcmp(host, argv[0]) == 0 &&
|
|
strcmp(portstr, argv[1]) == 0 &&
|
|
strcmp(user, argv[2]) == 0) {
|
|
const struct addrinfo hints = {
|
|
.ai_socktype = SOCK_STREAM,
|
|
};
|
|
struct addrinfo *result, *ai;
|
|
int error;
|
|
|
|
/*
|
|
* Resolve the destination host/port and try
|
|
* connecting to it. First successful
|
|
* connection wins.
|
|
*/
|
|
error = getaddrinfo(argv[3], argv[4], &hints, &result);
|
|
if (error) {
|
|
warnx("getaddrinfo: %s", gai_strerror(error));
|
|
goto fail;
|
|
}
|
|
|
|
for (ai = result; ai != NULL; ai = ai->ai_next) {
|
|
const int serverfd = socket(ai->ai_family,
|
|
ai->ai_socktype, ai->ai_protocol);
|
|
|
|
if (serverfd == -1) {
|
|
warn("socket");
|
|
continue;
|
|
}
|
|
if (connect(serverfd, ai->ai_addr,
|
|
ai->ai_addrlen) == -1) {
|
|
char hoststr[1024], servstr[128];
|
|
|
|
if (ai->ai_next != NULL) {
|
|
/*
|
|
* Avoid warning noise
|
|
* when there's
|
|
* multiple addresses
|
|
* and only one of them
|
|
* works.
|
|
*/
|
|
} else if (getnameinfo(ai->ai_addr,
|
|
ai->ai_addrlen,
|
|
hoststr, sizeof(hoststr),
|
|
servstr, sizeof(servstr),
|
|
NI_NUMERICHOST|NI_NUMERICSERV))
|
|
{
|
|
warn("connect");
|
|
} else {
|
|
warn("connect to %s port %s",
|
|
hoststr, servstr);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* Success! Reply success to the
|
|
* client and start transferring data.
|
|
*/
|
|
memset(&reply, 0, sizeof(reply));
|
|
reply.vn = 0;
|
|
reply.cd = 0x5a; /* 90: connected */
|
|
(void)writeall(clientfd, &reply,
|
|
sizeof(reply));
|
|
transfer(clientfd, serverfd);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Couldn't connect to the destination. Report
|
|
* failure back to the client.
|
|
*/
|
|
goto fail;
|
|
}
|
|
}
|
|
|
|
fail: memset(&reply, 0, sizeof(reply));
|
|
reply.vn = 0;
|
|
reply.cd = 0x5b; /* 91: request rejected or failed */
|
|
(void)writeall(clientfd, &reply, sizeof(reply));
|
|
}
|
|
|
|
int sigchld_pipe[2];
|
|
|
|
/*
|
|
* handlesigchld(signo)
|
|
*
|
|
* Signal handler for SIGCHLD. Arranges to wake select(2).
|
|
*/
|
|
static void
|
|
handlesigchld(int signo)
|
|
{
|
|
int errno_save = errno;
|
|
|
|
(void)signo; /* ignore */
|
|
|
|
/*
|
|
* Write a byte to the SIGCHLD pipe so that select(2) will wake
|
|
* up.
|
|
*/
|
|
while (write(sigchld_pipe[1], "", 1) == -1 && errno == EINTR) {
|
|
}
|
|
|
|
errno = errno_save; /* restore errno */
|
|
}
|
|
|
|
/*
|
|
* makenonblocking(fd)
|
|
*
|
|
* Make fd non-blocking. Fail with err(3) if it can't be done.
|
|
*/
|
|
static void
|
|
makenonblocking(int fd)
|
|
{
|
|
int flags;
|
|
|
|
if ((flags = fcntl(fd, F_GETFL)) == -1)
|
|
err(EXIT_FAILURE, "fcntl(F_GETFL)");
|
|
flags |= O_NONBLOCK;
|
|
if (fcntl(fd, F_SETFL, flags) == -1)
|
|
err(EXIT_FAILURE, "fcntl(F_SETFL)");
|
|
}
|
|
|
|
/*
|
|
* makeblocking(fd)
|
|
*
|
|
* Make fd blocking. Fail with err(3) if it can't be done.
|
|
*/
|
|
static void
|
|
makeblocking(int fd)
|
|
{
|
|
int flags;
|
|
|
|
if ((flags = fcntl(fd, F_GETFL)) == -1)
|
|
err(EXIT_FAILURE, "fcntl(F_GETFL)");
|
|
flags &= ~O_NONBLOCK;
|
|
if (fcntl(fd, F_SETFL, flags) == -1)
|
|
err(EXIT_FAILURE, "fcntl(F_SETFL)");
|
|
}
|
|
|
|
static int help_flag;
|
|
static int version_flag;
|
|
static struct getargs args[] = {
|
|
{
|
|
"version",
|
|
0,
|
|
arg_flag,
|
|
&version_flag,
|
|
NULL,
|
|
NULL
|
|
},
|
|
{
|
|
"help",
|
|
'h',
|
|
arg_flag,
|
|
&help_flag,
|
|
NULL,
|
|
NULL
|
|
}
|
|
};
|
|
|
|
int
|
|
main(int argc, char **argv)
|
|
{
|
|
int port, optidx = 0;
|
|
struct sockaddr_in sin = {
|
|
#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN
|
|
.sin_len = sizeof(sin),
|
|
#endif
|
|
.sin_family = AF_INET,
|
|
.sin_port = 0,
|
|
.sin_addr = { .s_addr = htonl(INADDR_LOOPBACK) },
|
|
};
|
|
int listenfd;
|
|
socklen_t namelen;
|
|
unsigned maxchildbudget = 8, childbudget = maxchildbudget;
|
|
sigset_t mask, omask;
|
|
|
|
setprogname(argv[0]);
|
|
if (getarg(args, sizeof(args)/sizeof(args[0]), argc, argv, &optidx)) {
|
|
arg_printusage(args, sizeof(args)/sizeof(args[0]), NULL,
|
|
"listenport matchhost matchport matchuser host port");
|
|
return 1;
|
|
}
|
|
|
|
if(version_flag) {
|
|
print_version(NULL);
|
|
exit(0);
|
|
}
|
|
|
|
argc -= optidx;
|
|
argv += optidx;
|
|
|
|
/*
|
|
* Verify arguments.
|
|
*/
|
|
if (help_flag || argc < 6 || ((argc - 1) % 5) != 0) {
|
|
arg_printusage(args, sizeof(args)/sizeof(args[0]), NULL,
|
|
"listenport matchhost matchport matchuser host port");
|
|
if (help_flag || argc == 0)
|
|
return 0;
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Parse the port number. If it's 0, the OS will choose it for
|
|
* us.
|
|
*/
|
|
argc--;
|
|
port = atoi(*argv++);
|
|
if (port < 0 || port > 65535)
|
|
errx(EXIT_FAILURE, "bad port");
|
|
sin.sin_port = htons(port);
|
|
|
|
/*
|
|
* Create a socket to accept SOCKS4a connections.
|
|
*/
|
|
listenfd = socket(AF_INET, SOCK_STREAM, 0);
|
|
if (listenfd == -1)
|
|
err(EXIT_FAILURE, "socket");
|
|
if (bind(listenfd, (const struct sockaddr *)&sin, sizeof(sin)) == -1)
|
|
err(EXIT_FAILURE, "bind");
|
|
namelen = sizeof(sin);
|
|
if (getsockname(listenfd, (struct sockaddr *)&sin, &namelen) == -1)
|
|
err(EXIT_FAILURE, "getsockname");
|
|
if (namelen != sizeof(sin))
|
|
errx(EXIT_FAILURE, "bad socket name length");
|
|
|
|
/*
|
|
* Prepare to listen for connections and make it nonblocking so
|
|
* we only block in select(2) which can safely be woken by
|
|
* SIGCHLD.
|
|
*/
|
|
if (listen(listenfd, 1) == -1)
|
|
err(EXIT_FAILURE, "listen");
|
|
makenonblocking(listenfd);
|
|
|
|
/*
|
|
* Now that we're listening for connections, print the port
|
|
* number in case the OS chose it. Make sure to flush it so
|
|
* callers have a chance to read it before we start waiting to
|
|
* accept connections.
|
|
*/
|
|
printf("%d\n", ntohs(sin.sin_port));
|
|
fflush(stdout);
|
|
if (ferror(stdout))
|
|
err(EXIT_FAILURE, "print port number");
|
|
|
|
/*
|
|
* Block SIGCHLD and arrange to handle it to wake us up in
|
|
* select(2).
|
|
*/
|
|
if (sigemptyset(&mask) == -1)
|
|
err(EXIT_FAILURE, "sigemptyset");
|
|
if (sigaddset(&mask, SIGCHLD) == -1)
|
|
err(EXIT_FAILURE, "sigaddset");
|
|
if (sigprocmask(SIG_BLOCK, &mask, &omask) == -1)
|
|
err(EXIT_FAILURE, "sigprocmask");
|
|
if (pipe(sigchld_pipe) == -1)
|
|
err(EXIT_FAILURE, "pipe");
|
|
makenonblocking(sigchld_pipe[0]);
|
|
makenonblocking(sigchld_pipe[1]);
|
|
if (signal(SIGCHLD, &handlesigchld) == SIG_ERR)
|
|
err(EXIT_FAILURE, "signal(SIGCHLD)");
|
|
|
|
/*
|
|
* Accept connections in a loop and process them in as many
|
|
* children as we have the budget for. We don't stop until
|
|
* we're terminated by a signal.
|
|
*/
|
|
for (;;) {
|
|
fd_set readfds, writefds, exceptfds;
|
|
int maxfd, nfds, selecterror, clientfd;
|
|
pid_t child;
|
|
|
|
FD_ZERO(&readfds);
|
|
FD_ZERO(&writefds);
|
|
FD_ZERO(&exceptfds);
|
|
FD_SET(sigchld_pipe[0], &readfds);
|
|
FD_SET(listenfd, &readfds);
|
|
maxfd = max(sigchld_pipe[0], listenfd);
|
|
|
|
/*
|
|
* If we're out of budget for more children, just wait
|
|
* for one to complete before accepting a connection.
|
|
*/
|
|
if (childbudget == 0) {
|
|
if ((child = waitpid(-1, NULL, 0)) == -1)
|
|
err(EXIT_FAILURE, "waitpid");
|
|
childbudget++;
|
|
}
|
|
|
|
/*
|
|
* Wait until there is a connection to accept. Allow
|
|
* SIGCHLD delivery while we wait. If SIGCHLD is
|
|
* delivered in the window between sigprocmask and
|
|
* select, no big deal: the SIGCHLD handler will write
|
|
* to a pipe that wakes up select.
|
|
*/
|
|
if (sigprocmask(SIG_SETMASK, &omask, NULL) == -1)
|
|
err(EXIT_FAILURE, "sigprocmask(SIG_SETMASK)");
|
|
nfds = select(maxfd + 1, &readfds, &writefds, &exceptfds,
|
|
NULL);
|
|
selecterror = errno;
|
|
if (sigprocmask(SIG_BLOCK, &mask, &omask) == -1)
|
|
err(EXIT_FAILURE, "sigprocmask");
|
|
errno = selecterror;
|
|
if (nfds == -1) {
|
|
if (errno == EINTR) /* if signal(2) doesn't restart */
|
|
continue;
|
|
err(EXIT_FAILURE, "select");
|
|
}
|
|
if (nfds == 0)
|
|
errx(EXIT_FAILURE, "buggy select without timeout");
|
|
|
|
/*
|
|
* If we got SIGCHLD, consume everything out of the
|
|
* pipe and then reap all children.
|
|
*/
|
|
if (FD_ISSET(sigchld_pipe[0], &readfds)) {
|
|
char ch;
|
|
ssize_t nread;
|
|
|
|
while ((nread = read(sigchld_pipe[0], &ch, 1)) != -1) {
|
|
if (nread == 0)
|
|
errx(EXIT_FAILURE, "SIGCHLD pipe EOF");
|
|
}
|
|
if (errno != EAGAIN)
|
|
err(EXIT_FAILURE, "read");
|
|
|
|
while (childbudget < maxchildbudget) {
|
|
if ((child = waitpid(-1, NULL, WNOHANG)) == -1)
|
|
err(EXIT_FAILURE, "waitpid");
|
|
if (child == 0)
|
|
break;
|
|
childbudget++;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Accept a connection, or contine waiting if it would
|
|
* block. (No need to check FD_ISSET(listenfd,
|
|
* &readfds): if it's not ready, we'll get EAGAIN
|
|
* immediately and restart the loop -- and the client
|
|
* could back out anyway, so checking FD_ISSET doesn't
|
|
* obviate the need to check for EAGAIN.)
|
|
*/
|
|
clientfd = accept(listenfd, NULL, NULL);
|
|
if (clientfd == -1) {
|
|
if (errno == EAGAIN)
|
|
continue;
|
|
err(EXIT_FAILURE, "accept");
|
|
}
|
|
|
|
/*
|
|
* POSIX leaves it unspecified whether the fd it
|
|
* returns is blocking or non-blocking. Make it
|
|
* blocking, since readall/writeall used to read the
|
|
* SOCKS4a request and write the SOCKS4a response
|
|
* expects that.
|
|
*/
|
|
makeblocking(clientfd);
|
|
|
|
/*
|
|
* Fork a child to handle it; then close the client
|
|
* socket so we don't leak it.
|
|
*/
|
|
if ((child = fork()) == -1)
|
|
err(EXIT_FAILURE, "fork");
|
|
if (child == 0) {
|
|
if (close(listenfd) == -1)
|
|
warn("close listenfd");
|
|
if (close(sigchld_pipe[0]) == -1)
|
|
warn("close sigchld_pipe[0]");
|
|
if (close(sigchld_pipe[1]) == -1)
|
|
warn("close sigchld_pipe[1]");
|
|
handleclient(clientfd, argc, argv);
|
|
_exit(0);
|
|
}
|
|
childbudget--;
|
|
if (close(clientfd) == -1)
|
|
warn("close clientfd");
|
|
}
|
|
/*NOTREACHED*/
|
|
return 0;
|
|
}
|