/* This file is part of GNU Pies.
Copyright (C) 2019 Sergey Poznyakoff
GNU Pies 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 3, or (at your option)
any later version.
GNU Pies 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 GNU Pies. If not, see . */
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
typedef struct netcat_server netcat_server_t;
typedef struct netcat_stream netcat_stream_t;
typedef ssize_t (*netcat_stream_io_t) (netcat_server_t *srv);
typedef int (*netcat_stream_disconnect_t) (netcat_server_t *srv);
enum { IN, OUT };
struct netcat_server
{
char const *id;
netcat_server_t *next, *prev;
struct pollfd *pollfd;
int state;
netcat_stream_disconnect_t disconnect;
netcat_server_t *peer;
struct iobuf buf[2];
};
static netcat_server_t *server_head, *server_tail;
netcat_server_t *
netcat_server_create (char const *id, struct pollfd *pfd, int state,
netcat_stream_disconnect_t dis, netcat_server_t *peer)
{
netcat_server_t *srv = grecs_zalloc (sizeof (*srv));
srv->id = id;
srv->pollfd = pfd;
srv->state = state;
srv->pollfd->events |= srv->state;
srv->disconnect = dis;
srv->peer = peer;
if (peer)
srv->peer->peer = srv;
iobuf_reset (&srv->buf[IN]);
iobuf_reset (&srv->buf[OUT]);
srv->prev = server_tail;
srv->next = NULL;
if (server_tail)
server_tail->next = srv;
else
server_head = srv;
server_tail = srv;
return srv;
}
void
netcat_server_remove (netcat_server_t *srv)
{
netcat_server_t *p;
if ((p = srv->next) != NULL)
p->prev = srv->prev;
else
server_tail = srv->prev;
if ((p = srv->prev) != NULL)
p->next = srv->next;
else
server_head = srv->next;
if (srv->peer)
srv->peer->peer = NULL;
free (srv);
}
/*
net_reader
1. If there is free space in the IN buffer, fill it with the data read
from the fd and return
2. Otherwise, clear the POLLIN bit.
stdout_writer (peer -> net_reader)
1. If OUT buffer is empty
1.1 If the peer's IN buffer is not empty, transfer data from it
1.2 Else raise the peers POLLIN bit and return.
2. Write as much as possible from OUT to fd
stdin_reader (same as net_reader)
net_writer (peer -> stdin_reader) same as stdout_writer
*/
ssize_t
netcat_stream_read (netcat_server_t *srv)
{
ssize_t n;
if (iobuf_avail_size (&srv->buf[IN]))
{
n = iobuf_fill (&srv->buf[IN], srv->pollfd->fd);
if (n == -1)
{
grecs_error (NULL, errno, "%s: read", srv->id);
srv->pollfd->events &= ~POLLIN;
return -1;
}
if (n == 0)
{
/* No more input is expected */
srv->disconnect (srv);
srv->state &= ~POLLIN;
srv->pollfd->events &= ~POLLIN;
if (srv->pollfd->events == 0)
srv->pollfd->fd = -1;
}
}
else
{
srv->pollfd->events &= ~POLLIN;
n = 0; /* catch timeout? */
}
return n;
}
static inline int
peer_is_state (netcat_server_t *srv, int state)
{
return srv->peer && (srv->peer->state & state);
}
ssize_t
netcat_stream_write (netcat_server_t *srv)
{
ssize_t n;
if (iobuf_data_size (&srv->buf[OUT]) == 0)
{
if (!peer_is_state (srv, POLLIN))
{
// shutdown write end
srv->disconnect (srv);
srv->state &= ~POLLOUT;
srv->pollfd->events &= ~POLLOUT;
if (srv->pollfd->events == 0)
srv->pollfd->fd = -1;
return -1;
}
if (iobuf_copy (&srv->buf[OUT], &srv->peer->buf[IN]) == 0)
{
srv->peer->pollfd->events |= POLLIN;
return 0;
}
}
n = iobuf_flush (&srv->buf[OUT], srv->pollfd->fd);
if (n == -1)
{
grecs_error (NULL, errno, "%s: write", srv->id);
return -1;
}
if (n == 0)
{
// FIXME: eof
return -1;
}
return 0;
}
int
disconnect_in (netcat_server_t *srv)
{
return shutdown (srv->pollfd->fd, SHUT_RD);
}
int
disconnect_out (netcat_server_t *srv)
{
return shutdown (srv->pollfd->fd, SHUT_WR);
}
int
disconnect_stdin (netcat_server_t *srv)
{
return close (srv->pollfd->fd);
}
int
disconnect_stdout (netcat_server_t *srv)
{
close (srv->pollfd->fd);
exit (0);
}
static int
netcat (char const *urlstr)
{
int fd;
struct pies_url *url;
struct pollfd pfd[3];
int nfd = sizeof (pfd) / sizeof (pfd[0]);
netcat_server_t *srvin, *srvout, *srv;
if (pies_url_create (&url, urlstr))
{
perror (urlstr);
return 64;
}
fd = url_connect (url, NULL);
if (fd == -1)
return 1;
pfd[0].fd = 0;
pfd[0].events = 0;
srvin = netcat_server_create ("stdin", &pfd[0], POLLIN, disconnect_stdin, NULL);
pfd[1].fd = 1;
pfd[1].events = 0;
srvout = netcat_server_create ("stdout", &pfd[1], POLLOUT, disconnect_stdout, NULL);
pfd[2].fd = fd;
pfd[2].events = 0;
netcat_server_create ("netread", &pfd[2], POLLIN, disconnect_in, srvout);
netcat_server_create ("netwrite", &pfd[2], POLLOUT, disconnect_out, srvin);
while (server_head)
{
ssize_t n = poll (pfd, nfd, -1);
if (n == -1)
{
if (errno != EINTR)
grecs_error (NULL, errno, "poll");
continue;
}
for (srv = server_head; srv; )
{
netcat_server_t *next = srv->next;
int events;
events = (srv->pollfd->events|POLLHUP)
& (srv->pollfd->revents & (srv->state|POLLHUP));
if (events)
{
if (events & POLLHUP)
{
//grecs_error (NULL, 0, "HUP on %s", srv->id);
srv->disconnect (srv);
srv->pollfd->events &= ~srv->state;
if (srv->pollfd->events == 0)
srv->pollfd->fd = -1;
srv->state = 0;
}
else if (events & POLLIN)
netcat_stream_read (srv);
else if (events & POLLOUT)
netcat_stream_write (srv);
}
if (srv->state == 0 || srv->pollfd->fd == -1)
netcat_server_remove (srv);
srv = next;
}
}
return 0;
}
static void
redirect (int sfd, char const *name)
{
int fd;
fd = open (name, sfd ? (O_CREAT | O_TRUNC | O_WRONLY) : O_RDONLY, 0644);
if (fd == -1)
{
perror (name);
exit (1);
}
if (dup2 (fd, sfd) == -1)
{
perror ("dup2");
exit (1);
}
}
static void
usage (FILE *fp)
{
fprintf (fp, "usage: nt [-i FILE] [-o FILE] URL\n");
}
int
main (int argc, char **argv)
{
int c;
while ((c = getopt (argc, argv, "i:o:")) != EOF)
{
switch (c)
{
case 'i':
redirect (0, optarg);
break;
case 'o':
redirect (1, optarg);
break;
default:
exit (64);
}
}
argc -= optind;
argv += optind;
if (argc != 1)
{
usage (stderr);
exit (64);
}
return netcat (argv[0]);
}