Commit 6fac0e91 authored by Niels Möller's avatar Niels Möller

Bundle tcpconnect and mini-inetd with testsuite.

parent f7a45af6
2013-03-10 Niels Mller <nisse@lysator.liu.se>
* src/testsuite/Makefile.am: Rules to build tcpconnect and
mini-inetd.
* src/testsuite/tcpconnect.c: New program, copied from master
branch.
* src/testsuite/mini-inetd.c: Likewise.
2013-03-09 Niels Mller <nisse@lysator.liu.se>
* src/unix_process.c (utmp_book_keeping): Don't pass utmp's ut_tv
......
......@@ -29,13 +29,15 @@ TS_SH = conv-1-test conv-2-test conv-3-test \
TS_ALL = $(TS_PROGS) $(TS_SH)
noinst_PROGRAMS = $(TS_PROGS)
noinst_PROGRAMS = $(TS_PROGS) mini-inetd tcpconnect
# Workaround to get automake to keep dependencies for testutils.o
EXTRA_PROGRAMS = testutils
LDADD = testutils.o ../liblsh.a ../spki/libspki.a ../nettle/libnettle.a \
$(DOTDOT_LIBARGP)
mini_inetd_LDADD =
tcpconnect_LDADD =
include .dist_rapid7
......
/* mini-inetd
*
* This program is an total reimplementation of the old mini-inetd
* from Thomas Bellman's tcputils.
*
* Copyright (C) 2010 Möller
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
*/
#if HAVE_CONFIG_H
#include "config.h"
#endif
#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
/* For IPPROTO_TCP on FreeBSD */
#include <netinet/in.h>
#include "getopt.h"
static void NORETURN PRINTF_STYLE(1, 2)
die(const char *format, ...)
{
va_list args;
va_start(args, format);
vfprintf(stderr, format, args);
va_end(args);
exit(EXIT_FAILURE);
}
static void PRINTF_STYLE(1, 2)
werror(const char *format, ...)
{
va_list args;
va_start(args, format);
vfprintf(stderr, format, args);
va_end(args);
}
static void
usage (FILE *f)
{
fprintf(f,
/* FIXME: ':' is a bad separator for literal IPv6 addresses.
Support [] around the address? */
"mini-inetd [OPTIONS] [localaddr:]port program [argv0, argv1 ...]\n"
"Options:\n"
" -m max-connections\n"
" --help\n"
" --background\n"
" -4 Only use IPv4.\n"
" -6 Only use IPv6.\n"
" --help Display this help.\n");
}
static void
start_service (int fd, const char *program, char **argv)
{
int pid = fork();
if (pid < 0)
die ("fork failed: %s\n", STRERROR(errno));
if (pid)
{
/* parent */
close(fd);
}
else
{
/* child */
dup2(fd, STDIN_FILENO);
dup2(fd, STDOUT_FILENO);
close(fd);
execv(program, argv);
werror("execv failed: %s\n", STRERROR(errno));
_exit(EXIT_FAILURE);
}
}
#define MAX_SOCKETS 7
int
main (int argc, char **argv)
{
int max_connections = 0;
int background = 0;
int family = AF_UNSPEC;
const char *local_addr;
const char *port;
int c;
char *sep;
struct addrinfo hints;
struct addrinfo *list, *p;
int err;
int fds[MAX_SOCKETS];
int nfds = 0;
int max_fd = 0;
enum { OPT_HELP = 300, OPT_BACKGROUND };
static const struct option options[] =
{
/* Name, args, flag, val */
{ "help", no_argument, NULL, OPT_HELP },
{ "background", no_argument, NULL, OPT_BACKGROUND },
{ NULL, 0, NULL, 0 }
};
while ( (c = getopt_long (argc, argv, "46dm:", options, NULL)) != -1)
switch (c)
{
case '?':
return EXIT_FAILURE;
case OPT_HELP:
usage(stdout);
return EXIT_SUCCESS;
case OPT_BACKGROUND:
background = 1;
break;
case 'd':
/* Currently a NOP for backwards compatibility. */
break;
case 'm':
max_connections = atoi(optarg);
if (max_connections <= 0)
die ("Invalid argument for -m, should be a positive integer.\n");
break;
case '4':
family = AF_INET;
break;
case '6':
#ifdef AF_INET6
family = AF_INET6;
break;
#else
die ("IP version 6 not supported.\n");
#endif
}
argc -= optind;
argv += optind;
if (argc < 2)
{
usage(stderr);
return EXIT_FAILURE;
}
sep = strrchr(argv[0], ':');
if (sep)
{
port = sep + 1;
*sep = '\0';
local_addr = argv[0];
}
else
{
port = argv[0];
local_addr = NULL;
}
memset(&hints, 0, sizeof(hints));
hints.ai_family = family;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
if (!local_addr)
hints.ai_flags = AI_PASSIVE;
err = getaddrinfo (local_addr, port, &hints, &list);
if (err)
die("Failed to look up address: %s\n", gai_strerror(err));
for (p = list; p; p = p->ai_next)
{
int yes = 1;
int fd;
int flags;
if (nfds == MAX_SOCKETS)
{
werror("Large number of matching addresses!? Ignoring all but the first %d\n",
MAX_SOCKETS);
break;
}
fd = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
if (fd < 0)
{
werror("Failed to create socket of family %d\n",
p->ai_family);
continue;
}
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof (yes)) <0)
werror("setsockopt SO_REUSEADDR failed: %s\n", STRERROR(errno));
#if WITH_IPV6 && defined (IPV6_V6ONLY)
if (p->ai_family == AF_INET6)
{
if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &yes, sizeof(yes)) < 0)
werror("setsockopt IPV6_V6ONLY failed: %s\n", STRERROR(errno));
}
#endif
flags = fcntl(fd, F_GETFL);
if (flags < 0)
werror("fcntl F_GETFL failed: %s\n", STRERROR(errno));
else if (fcntl(fd, F_SETFL, flags | 1 | O_NONBLOCK) < 0)
werror("fcntl F_SETFL failed: %s\n", STRERROR(errno));
if (bind(fd, p->ai_addr, p->ai_addrlen) < 0)
{
werror("bind failed: %s\n", STRERROR(errno));
close(fd);
continue;
}
if (listen (fd, 256) < 0)
{
werror("listen failed: %s\n", STRERROR(errno));
close(fd);
continue;
}
fds[nfds++] = fd;
if (fd > max_fd)
max_fd = fd;
}
freeaddrinfo(list);
if (!nfds)
die("No ports could be bound.\n");
if (background)
{
pid_t pid = fork();
switch (pid)
{
case -1:
die("fork failed: %s\n", STRERROR(errno));
case 0:
/* Child process. */
break;
default:
printf("%ld\n", (long) pid);
fflush(stdout);
_exit(EXIT_SUCCESS);
}
}
for (;;)
{
fd_set wanted;
int i;
int res;
FD_ZERO(&wanted);
for (i = 0; i < nfds; i++)
FD_SET(fds[i], &wanted);
do
res = select(max_fd + 1, &wanted, NULL, NULL, NULL);
while (res < 0 && errno == EINTR);
for (i = 0; i < nfds; i++)
if (FD_ISSET(fds[i], &wanted))
{
int conn;
conn = accept(fds[i], NULL, NULL);
if (conn < 0)
{
if (errno != EINTR)
die("accept failed: %s\n", STRERROR(errno));
continue;
}
start_service (conn, argv[1], argv + 2);
if (max_connections)
{
max_connections--;
if (!max_connections)
return EXIT_SUCCESS;
}
}
}
}
/* tcpconnect
*
* This program is an total reimplementation of the old tcpconnect
* from Thomas Bellman's tcputils.
*
* Copyright (C) 2012 Möller
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
*/
#if HAVE_CONFIG_H
#include "config.h"
#endif
#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <netdb.h>
#include <arpa/inet.h>
#include "getopt.h"
static void NORETURN PRINTF_STYLE(1, 2)
die(const char *format, ...)
{
va_list args;
fprintf(stderr, "tcpconnect: ");
va_start(args, format);
vfprintf(stderr, format, args);
va_end(args);
exit(EXIT_FAILURE);
}
static void PRINTF_STYLE(1, 2)
werror(const char *format, ...)
{
va_list args;
fprintf(stderr, "tcpconnect: ");
va_start(args, format);
vfprintf(stderr, format, args);
va_end(args);
}
static void
usage (FILE *f)
{
fprintf(f,
"tcpconnect [OPTIONS] host post\n"
"Options:\n"
" -i Terminate at end-of-file on standard input;\n"
" don't wait for the server to close the connection.\n"
" -r Terminate when the remote server closes the connection;\n"
" don't wait for end-of-file on standard input.\n"
" -v Verbose mode. Prints a message to standard error when\n"
" the connection has been established.\n"
/* FIXME: ':' is a bad separator for literal IPv6 addresses.
Support [] around the address? */
" -l ADDR:PORT\n"
" Bind the local end-point of the connection to IP address\n"
" ADDR, TCP port PORT. Either the IP address or the port,\n"
" but not both, may be left out, meaning that the operating\n"
" system gets to choose that part by itself.\n"
" -4 Only use IPv4.\n"
" -6 Only use IPv6.\n"
" --help Display this help.\n");
}
#define BUF_SIZE 4096
int
main (int argc, char **argv)
{
int family = AF_UNSPEC;
int wait_stdin_eof = 1;
int wait_remote_eof = 1;
int verbose = 0;
#if 0
const char *local_ip = NULL;
const char *local_port = NULL;
#endif
int c;
struct addrinfo hints;
struct addrinfo *list, *p;
int flags;
int fd;
int seen_remote_eof;
int seen_stdin_eof;
char buf_stdin[BUF_SIZE];
unsigned buf_size;
unsigned buf_pos;
int err;
enum { OPT_HELP = 300 };
static const struct option options[] =
{
/* Name, args, flag, val */
{ "help", no_argument, NULL, OPT_HELP },
{ NULL, 0, NULL, 0 }
};
while ( (c = getopt_long (argc, argv, "46irvl:", options, NULL)) != -1)
switch (c)
{
case '?':
return EXIT_FAILURE;
case OPT_HELP:
usage(stdout);
return EXIT_SUCCESS;
case '4':
family = AF_INET;
break;
case '6':
#ifdef AF_INET6
family = AF_INET6;
break;
#else
die ("IP version 6 not supported.\n");
#endif
case 'i':
wait_remote_eof = 0;
break;
case 'r':
wait_stdin_eof = 0;
break;
case 'v':
verbose = 1;
break;
case 'l':
die ("Not implemented.\n");
break;
}
argc -= optind;
argv += optind;
if (argc < 2)
{
usage(stderr);
return EXIT_FAILURE;
}
memset(&hints, 0, sizeof(hints));
hints.ai_family = family;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
err = getaddrinfo (argv[0], argv[1], &hints, &list);
if (err)
die("Failed to look up address: %s\n", gai_strerror(err));
for (p = list, fd = -1; p; p = p->ai_next)
{
fd = socket (p->ai_family, p->ai_socktype, p->ai_protocol);
if (fd < 0)
continue;
if (connect (fd, p->ai_addr, p->ai_addrlen) >= 0)
break;
if (verbose)
fprintf(stderr, "Connect failed: %s\n", strerror(errno));
close (fd);
fd = -1;
}
if (fd < 0)
die("Connection failed.\n");
if (verbose)
switch (p->ai_family)
{
#ifdef AF_INET6
case AF_INET6:
{
const struct sockaddr_in6 *sin6
= (struct sockaddr_in6 *) p->ai_addr;
char buf[INET6_ADDRSTRLEN];
if (!inet_ntop (p->ai_family, &sin6->sin6_addr, buf, sizeof(buf)))
die ("inet_ntop failed: %s\n", strerror(errno));
fprintf(stderr, "[Connected to %s port %d]\n",
buf, ntohs (sin6->sin6_port));
break;
}
#endif
case AF_INET:
{
const struct sockaddr_in *sin
= (struct sockaddr_in *) p->ai_addr;
char buf[INET_ADDRSTRLEN];
if (!inet_ntop (p->ai_family, &sin->sin_addr, buf, sizeof(buf)))
die ("inet_ntop failed: %s\n", strerror(errno));
fprintf(stderr, "[Connected to %s port %d]\n",
buf, ntohs (sin->sin_port));
break;
}
default:
die ("Unknown address family %d.\n", p->ai_family);
}
freeaddrinfo (list);
flags = fcntl(fd, F_GETFL);
if (flags < 0)
werror("fcntl F_GETFL failed: %s\n", strerror(errno));
else if (fcntl(fd, F_SETFL, flags | 1 | O_NONBLOCK) < 0)
werror("fcntl F_SETFL failed: %s\n", strerror(errno));
signal(SIGPIPE, SIG_IGN);
buf_size = buf_pos = 0;
seen_stdin_eof = seen_remote_eof = 0;
/* We do blocking reads of stdin and writes to stdout, and use
FIONREAD to avoid blocking on read. We do non-blocking reads and
writes on the sockets. If we can't write, we keep buffered data
in buf_stdin, and don't read any more stdin data until the
buffered data has been written to the socket. */
for (;;)
{
fd_set read_fds;
fd_set write_fds;
FD_ZERO (&read_fds);
FD_ZERO (&write_fds);
if (buf_size > buf_pos)
FD_SET (fd, &write_fds);
else if (!seen_stdin_eof)
FD_SET (STDIN_FILENO, &read_fds);
if (!seen_remote_eof)
FD_SET (fd, &read_fds);
if (select (fd + 1, &read_fds, &write_fds, NULL, NULL) < 0)
{
if (errno == EINTR)
continue;
die ("select failed: %s\n", strerror(errno));
}
if (FD_ISSET (fd, &write_fds))
{
int res = write(fd, buf_stdin + buf_pos, buf_size - buf_pos);
if (res < 0)
{
if (errno != EINTR)
{
if (verbose)
werror ("write to socket failed: %s\n", strerror(errno));
break;
}
}
else
buf_pos += res;
}
if (FD_ISSET (STDIN_FILENO, &read_fds))
{
int nbytes, res;
if (ioctl (STDIN_FILENO, FIONREAD, &nbytes) < 0
|| nbytes <= 0
|| nbytes > (int) sizeof(buf_stdin))
nbytes = sizeof(buf_stdin);
res = read(STDIN_FILENO, buf_stdin, nbytes);
if (res < 0)
{
if (errno != EINTR)
die("read from stdin failed: %s\n", strerror(errno));
}
else if (res == 0)
{
wait_stdin_eof = 0;
if (!wait_remote_eof)
break;
if (shutdown (fd, SHUT_WR) < 0 && errno != ENOTCONN)
die("shutdown failed: %s\n", strerror(errno));
}
else
{
nbytes = res;
res = write(fd, buf_stdin, nbytes);
if (res < 0)
{
if (errno != EINTR)
{
if (verbose)
werror ("write to socket failed: %s\n", strerror(errno));
break;
}
res = 0;
}
if (res < nbytes)
{
buf_size = nbytes;
buf_pos = res;
}
}
}
if (FD_ISSET (fd, &read_fds))
{
char buf_remote[BUF_SIZE];
int res = read (fd, buf_remote, sizeof (buf_remote));
if (res <= 0)
{
/* Treat read errors in the same way as EOF. */
if (res < 0 && (errno == EINTR || errno == EWOULDBLOCK))
; /* Do nothing */
else
{
if (verbose && res < 0)
werror("read from socket failed: %s\n", strerror(errno));
wait_remote_eof = 0;
if (!wait_stdin_eof)
break;
/* Useful only in case stdout happens to be a socket. */
shutdown (STDOUT_FILENO, SHUT_WR);
}
}
else
{
int size = res;
int done = 0;