Code:
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <netdb.h>
#include <signal.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
/* Number of connections buffered by the kernel. Suggestion only.
*/
#ifndef BACKLOG
#define BACKLOG 50
#endif /* BACKLOG */
/* Constants useful for converting socket addresses and ports to strings.
*/
#ifndef NI_MAXHOST
#define NI_MAXHOST 1040
#endif
#ifndef NI_MAXSERV
#define NI_MAXSERV 64
#endif
/* Helper function: write to a file descriptor.
* Returns 0 if successful, errno error code otherwise.
*/
static int writefd(const int descriptor, const void *const data, size_t const size)
{
const unsigned char *head = (const unsigned char *)data;
const unsigned char *const tail = size + (const unsigned char *)data;
ssize_t bytes;
while (head < tail) {
bytes = write(descriptor, head, (size_t)(tail - head));
if (bytes > (ssize_t)0)
head += bytes;
else
if (bytes != (ssize_t)-1)
return errno = EIO;
else
if (errno != EINTR)
return errno;
}
return 0;
}
/* Helper function: Write strings to standard error.
* Returns 0 if successful, errno error code otherwise.
* Keeps errno unchanged.
*/
static int wrerr(const size_t strings, ...)
{
va_list args;
size_t i;
int saved_errno;
saved_errno = errno;
va_start(args, strings);
for (i = 0; i < strings; i++) {
const char *const string = va_arg(args, const char *);
const size_t length = (string) ? strlen(string) : 0;
int result;
if (length < 1)
continue;
result = writefd(STDERR_FILENO, string, length);
if (result) {
va_end(args);
errno = saved_errno;
return result;
}
}
va_end(args);
errno = saved_errno;
return 0;
}
/* Helper function: Close descriptor.
* Returns 0 if success, errno otherwise.
*/
static int closefd(const int descriptor)
{
int result;
do {
result = close(descriptor);
} while (result == -1 && errno == EINTR);
if (result == -1)
return errno;
else
return 0;
}
/* Child process reaper; triggered by SIGCHLD.
*/
static void reap(int signum __attribute__((unused)))
{
pid_t p;
do {
p = waitpid((pid_t)-1, NULL, WNOHANG);
} while (p != (pid_t)0 && p != (pid_t)-1);
}
static int install_reaper(void)
{
struct sigaction act;
sigemptyset(&act.sa_mask);
act.sa_handler = reap;
act.sa_flags = 0;
if (sigaction(SIGCHLD, &act, NULL) == -1)
return errno;
else
return 0;
}
/* Interrupt signal handler.
*/
static volatile sig_atomic_t interrupted = 0;
static void interrupt_handler(int signum)
{
if (!interrupted)
interrupted = signum;
}
static int install_interrupt_handler(const int signum)
{
struct sigaction act;
sigemptyset(&act.sa_mask);
act.sa_handler = interrupt_handler;
act.sa_flags = 0;
if (sigaction(signum, &act, NULL) == -1)
return errno;
else
return 0;
}
int main(int argc, char *argv[])
{
char hostname[NI_MAXHOST], portname[NI_MAXSERV];
const char *requested_host, *requested_port;
struct addrinfo hints, *list, *curr;
int result, socketfd;
char client_host[NI_MAXHOST];
char client_port[NI_MAXSERV];
struct sockaddr_in6 client_addr;
socklen_t client_addrlen;
int connfd;
pid_t child;
/* No parameters? Help requested? */
if (argc < 4 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
wrerr(1, "\n");
wrerr(3, "Usage: ", argv[0], " [ -h | --help ]\n");
wrerr(3, " ", argv[0], " ADDRESS PORT COMMAND [ ARGS .. ]\n");
wrerr(1, "\n");
wrerr(1, "This program will create a listening socket\n");
wrerr(1, "on the specified address and port.\n");
wrerr(1, "\n");
wrerr(1, "When an incoming connection is established, a child\n");
wrerr(1, "process will be spawned, with standard input and output\n");
wrerr(1, "redirected to the incoming connection.\n");
wrerr(1, "\n");
wrerr(1, "Send a SIGINT (Ctrl+C) or SIGHUP signal to the process\n");
wrerr(1, "to stop it listening to new incoming connections.\n");
wrerr(1, "\n");
return 1;
}
/* Install interrupt handler on SIGINT and SIGHUP,
* and child process reaper on SIGCHLD. */
if (install_interrupt_handler(SIGINT) ||
install_interrupt_handler(SIGHUP) ||
install_reaper()) {
wrerr(3, "Cannot install signal handlers: ", strerror(errno), ".\n");
return 1;
}
/* Pick out host and port from command line parameters. */
requested_host = argv[1];
requested_port = argv[2];
/* NULL or empty or "*" or ":" or "." means wildcard address. */
if (!requested_host ||
!*requested_host ||
!strcmp(requested_host, "*") ||
!strcmp(requested_host, ":") ||
!strcmp(requested_host, "."))
requested_host = NULL;
/* Define address info hints; what kind of sockets we allow. */
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC; /* Both IPv4 and IPv6 are accepted. */
hints.ai_socktype = SOCK_STREAM; /* TCP sockets. */
hints.ai_flags = AI_PASSIVE; /* NULL will match wildcard address. */
hints.ai_protocol = 0; /* Any protocol. */
hints.ai_canonname = NULL;
hints.ai_addr = NULL;
hints.ai_next = NULL;
/* Get the chain of possible sockets. */
result = getaddrinfo(requested_host, requested_port, &hints, &list);
if (result) {
const char *const errmsg = gai_strerror(result);
const char *const req_host = (requested_host) ? requested_host : "*";
wrerr(6, req_host, " ", requested_port, ": ", errmsg, ".\n");
return 1;
}
/* Try sockets one by one to see which one works. */
socketfd = -1;
for (curr = list; curr != NULL; curr = curr->ai_next) {
/* Create the socket. */
socketfd = socket(curr->ai_family, curr->ai_socktype, curr->ai_protocol);
if (socketfd == -1)
continue;
/* Bind to the socket. */
if (bind(socketfd, curr->ai_addr, curr->ai_addrlen) == -1) {
/* Cannot bind. */
closefd(socketfd);
/* Try next. */
continue;
}
/* Listen for incoming connections on the socket. */
if (listen(socketfd, BACKLOG) == -1) {
/* Failed. */
closefd(socketfd);
/* Try next. */
continue;
}
/* This is a good socket. */
break;
}
/* Failed to bind to a socket? */
if (!curr) {
const char *const req_host = (requested_host) ? requested_host : "*";
freeaddrinfo(list);
wrerr(4, req_host, " ", requested_port, ": Could not find a suitable socket to bind to.\n");
return 1;
}
/* Convert the actual bound socket address and port to strings. */
result = getnameinfo(curr->ai_addr, curr->ai_addrlen,
hostname, sizeof hostname,
portname, sizeof portname,
NI_NUMERICHOST | NI_NUMERICSERV);
if (result) {
const char *const errmsg = gai_strerror(result);
freeaddrinfo(list);
closefd(socketfd);
wrerr(3, "Cannot get bound socket address and port: ", errmsg, ".\n");
return 1;
}
/* Discard the socket info chain. */
freeaddrinfo(list);
list = NULL; curr = NULL;
/* Let the user know we are listening. */
wrerr(5, "Listening for incoming connections to ", hostname, " port ", portname, ".\n");
/* Incoming connection loop. */
while (!interrupted) {
/* Opportunistically reap dead children.
* If two or more exit at the same time,
* we may only get one signal. */
reap(0);
/* Accept an incoming connection. */
client_addrlen = sizeof client_addr;
connfd = accept(socketfd, (struct sockaddr *)&client_addr, &client_addrlen);
/* Not an incoming connection? */
if (connfd == -1) {
/* Need to retry? (Possibly interrupted.) */
if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK)
continue;
/* No, it was a real error. */
break;
}
/* Get the client address and port as strings. */
result = getnameinfo((struct sockaddr *)&client_addr, client_addrlen,
client_host, sizeof client_host,
client_port, sizeof client_port,
NI_NUMERICHOST | NI_NUMERICSERV);
if (result) {
const char *const errmsg = gai_strerror(result);
wrerr(2, errmsg, ".\n");
closefd(connfd);
continue;
}
/* Let the user know about the connection. */
wrerr(5, "Connection from ", client_host, " port ", client_port, ".\n");
/* Fork the child process. */
child = fork();
if (child == (pid_t)-1) {
const char *const errmsg = strerror(errno);
wrerr(3, "Cannot fork child process: ", errmsg, ".\n");
closefd(connfd);
continue;
}
/* Parent process will simply close the connection descriptor,
* and wait for a new connection. */
if (child) {
closefd(connfd);
continue;
}
/* This is the child process. */
/* Close the original socket. */
closefd(socketfd);
/* Duplicate the connection to standard input. */
do {
result = dup2(connfd, STDIN_FILENO);
} while (result == -1 && errno == EINTR);
if (result == -1) {
const char *const errmsg = strerror(errno);
wrerr(3, "Cannot redirect connection to standard input of child: ", errmsg, ".\n");
exit(127);
}
/* Duplicate the connection to standard output. */
do {
result = dup2(connfd, STDOUT_FILENO);
} while (result == -1 && errno == EINTR);
if (result == -1) {
const char *const errmsg = strerror(errno);
wrerr(3, "Cannot redirect connection to standard output of child: ", errmsg, ".\n");
exit(127);
}
/* Close the extra descriptor. */
if (connfd != STDIN_FILENO && connfd != STDOUT_FILENO)
closefd(connfd);
/* Execute the desired command. */
execvp(argv[3], argv + 3);
/* FAILED! */
wrerr(5, "Failed to execute ", argv[3], ": ", strerror(errno), ".\n");
closefd(STDIN_FILENO);
closefd(STDOUT_FILENO);
exit(127);
}
if (interrupted)
wrerr(3, "Caught ", strsignal(interrupted), ", exiting.\n");
else
wrerr(3, "Error accepting an incoming connection: ", strerror(errno), ".\n");
/* Close the socket. */
closefd(socketfd);
return 0;
} In particular, it