mirror of
https://git.tartarus.org/simon/putty.git
synced 2025-01-09 09:27:59 +00:00
807ed08da0
In the previous few commits I noticed some repeated work in the form of pointless empty implementations of Plug's log method, plus some existing (and some new) empty cases of Socket's endpoint_info. As a cleanup, I'm replacing as many as I can find with uses of a central null implementation in the stubs directory.
418 lines
10 KiB
C
418 lines
10 KiB
C
/*
|
|
* fd-socket.c: implementation of Socket that just talks to two
|
|
* existing input and output file descriptors.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <assert.h>
|
|
#include <errno.h>
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
|
|
#include "tree234.h"
|
|
#include "putty.h"
|
|
#include "network.h"
|
|
|
|
typedef struct FdSocket {
|
|
int outfd, infd, inerrfd; /* >= 0 if socket is open */
|
|
DeferredSocketOpener *opener; /* non-NULL if not opened yet */
|
|
|
|
bufchain pending_output_data;
|
|
bufchain pending_input_data;
|
|
ProxyStderrBuf psb;
|
|
enum { EOF_NO, EOF_PENDING, EOF_SENT } outgoingeof;
|
|
|
|
int pending_error;
|
|
|
|
SockAddr *addr;
|
|
int port;
|
|
Plug *plug;
|
|
|
|
Socket sock;
|
|
} FdSocket;
|
|
|
|
static void fdsocket_select_result_input(int fd, int event);
|
|
static void fdsocket_select_result_output(int fd, int event);
|
|
static void fdsocket_select_result_input_error(int fd, int event);
|
|
|
|
/*
|
|
* Trees to look up the fds in.
|
|
*/
|
|
static tree234 *fdsocket_by_outfd;
|
|
static tree234 *fdsocket_by_infd;
|
|
static tree234 *fdsocket_by_inerrfd;
|
|
|
|
static int fdsocket_infd_cmp(void *av, void *bv)
|
|
{
|
|
FdSocket *a = (FdSocket *)av;
|
|
FdSocket *b = (FdSocket *)bv;
|
|
if (a->infd < b->infd)
|
|
return -1;
|
|
if (a->infd > b->infd)
|
|
return +1;
|
|
return 0;
|
|
}
|
|
static int fdsocket_infd_find(void *av, void *bv)
|
|
{
|
|
int a = *(int *)av;
|
|
FdSocket *b = (FdSocket *)bv;
|
|
if (a < b->infd)
|
|
return -1;
|
|
if (a > b->infd)
|
|
return +1;
|
|
return 0;
|
|
}
|
|
static int fdsocket_inerrfd_cmp(void *av, void *bv)
|
|
{
|
|
FdSocket *a = (FdSocket *)av;
|
|
FdSocket *b = (FdSocket *)bv;
|
|
if (a->inerrfd < b->inerrfd)
|
|
return -1;
|
|
if (a->inerrfd > b->inerrfd)
|
|
return +1;
|
|
return 0;
|
|
}
|
|
static int fdsocket_inerrfd_find(void *av, void *bv)
|
|
{
|
|
int a = *(int *)av;
|
|
FdSocket *b = (FdSocket *)bv;
|
|
if (a < b->inerrfd)
|
|
return -1;
|
|
if (a > b->inerrfd)
|
|
return +1;
|
|
return 0;
|
|
}
|
|
static int fdsocket_outfd_cmp(void *av, void *bv)
|
|
{
|
|
FdSocket *a = (FdSocket *)av;
|
|
FdSocket *b = (FdSocket *)bv;
|
|
if (a->outfd < b->outfd)
|
|
return -1;
|
|
if (a->outfd > b->outfd)
|
|
return +1;
|
|
return 0;
|
|
}
|
|
static int fdsocket_outfd_find(void *av, void *bv)
|
|
{
|
|
int a = *(int *)av;
|
|
FdSocket *b = (FdSocket *)bv;
|
|
if (a < b->outfd)
|
|
return -1;
|
|
if (a > b->outfd)
|
|
return +1;
|
|
return 0;
|
|
}
|
|
|
|
static Plug *fdsocket_plug(Socket *s, Plug *p)
|
|
{
|
|
FdSocket *fds = container_of(s, FdSocket, sock);
|
|
Plug *ret = fds->plug;
|
|
if (p)
|
|
fds->plug = p;
|
|
return ret;
|
|
}
|
|
|
|
static void fdsocket_close(Socket *s)
|
|
{
|
|
FdSocket *fds = container_of(s, FdSocket, sock);
|
|
|
|
if (fds->opener)
|
|
deferred_socket_opener_free(fds->opener);
|
|
|
|
if (fds->outfd >= 0) {
|
|
del234(fdsocket_by_outfd, fds);
|
|
uxsel_del(fds->outfd);
|
|
close(fds->outfd);
|
|
}
|
|
|
|
if (fds->infd >= 0) {
|
|
del234(fdsocket_by_infd, fds);
|
|
uxsel_del(fds->infd);
|
|
close(fds->infd);
|
|
}
|
|
|
|
if (fds->inerrfd >= 0) {
|
|
del234(fdsocket_by_inerrfd, fds);
|
|
uxsel_del(fds->inerrfd);
|
|
close(fds->inerrfd);
|
|
}
|
|
|
|
bufchain_clear(&fds->pending_input_data);
|
|
bufchain_clear(&fds->pending_output_data);
|
|
|
|
if (fds->addr)
|
|
sk_addr_free(fds->addr);
|
|
|
|
delete_callbacks_for_context(fds);
|
|
|
|
sfree(fds);
|
|
}
|
|
|
|
static void fdsocket_error_callback(void *vs)
|
|
{
|
|
FdSocket *fds = (FdSocket *)vs;
|
|
|
|
/*
|
|
* Just in case other socket work has caused this socket to vanish
|
|
* or become somehow non-erroneous before this callback arrived...
|
|
*/
|
|
if (!fds->pending_error)
|
|
return;
|
|
|
|
/*
|
|
* An error has occurred on this socket. Pass it to the plug.
|
|
*/
|
|
plug_closing_errno(fds->plug, fds->pending_error);
|
|
}
|
|
|
|
static int fdsocket_try_send(FdSocket *fds)
|
|
{
|
|
int sent = 0;
|
|
|
|
if (fds->opener)
|
|
return sent;
|
|
|
|
while (bufchain_size(&fds->pending_output_data) > 0) {
|
|
ssize_t ret;
|
|
|
|
ptrlen data = bufchain_prefix(&fds->pending_output_data);
|
|
ret = write(fds->outfd, data.ptr, data.len);
|
|
noise_ultralight(NOISE_SOURCE_IOID, ret);
|
|
if (ret < 0 && errno != EWOULDBLOCK) {
|
|
if (!fds->pending_error) {
|
|
fds->pending_error = errno;
|
|
queue_toplevel_callback(fdsocket_error_callback, fds);
|
|
}
|
|
return 0;
|
|
} else if (ret <= 0) {
|
|
break;
|
|
} else {
|
|
bufchain_consume(&fds->pending_output_data, ret);
|
|
sent += ret;
|
|
}
|
|
}
|
|
|
|
if (fds->outgoingeof == EOF_PENDING) {
|
|
del234(fdsocket_by_outfd, fds);
|
|
close(fds->outfd);
|
|
uxsel_del(fds->outfd);
|
|
fds->outfd = -1;
|
|
fds->outgoingeof = EOF_SENT;
|
|
}
|
|
|
|
if (bufchain_size(&fds->pending_output_data) == 0)
|
|
uxsel_del(fds->outfd);
|
|
else
|
|
uxsel_set(fds->outfd, SELECT_W, fdsocket_select_result_output);
|
|
|
|
return sent;
|
|
}
|
|
|
|
static size_t fdsocket_write(Socket *s, const void *data, size_t len)
|
|
{
|
|
FdSocket *fds = container_of(s, FdSocket, sock);
|
|
|
|
assert(fds->outgoingeof == EOF_NO);
|
|
|
|
bufchain_add(&fds->pending_output_data, data, len);
|
|
|
|
fdsocket_try_send(fds);
|
|
|
|
return bufchain_size(&fds->pending_output_data);
|
|
}
|
|
|
|
static size_t fdsocket_write_oob(Socket *s, const void *data, size_t len)
|
|
{
|
|
/*
|
|
* oob data is treated as inband; nasty, but nothing really
|
|
* better we can do
|
|
*/
|
|
return fdsocket_write(s, data, len);
|
|
}
|
|
|
|
static void fdsocket_write_eof(Socket *s)
|
|
{
|
|
FdSocket *fds = container_of(s, FdSocket, sock);
|
|
|
|
assert(fds->outgoingeof == EOF_NO);
|
|
fds->outgoingeof = EOF_PENDING;
|
|
|
|
fdsocket_try_send(fds);
|
|
}
|
|
|
|
static void fdsocket_set_frozen(Socket *s, bool is_frozen)
|
|
{
|
|
FdSocket *fds = container_of(s, FdSocket, sock);
|
|
|
|
if (fds->infd < 0)
|
|
return;
|
|
|
|
if (is_frozen)
|
|
uxsel_del(fds->infd);
|
|
else
|
|
uxsel_set(fds->infd, SELECT_R, fdsocket_select_result_input);
|
|
}
|
|
|
|
static const char *fdsocket_socket_error(Socket *s)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
static void fdsocket_select_result_input(int fd, int event)
|
|
{
|
|
FdSocket *fds;
|
|
char buf[20480];
|
|
int retd;
|
|
|
|
if (!(fds = find234(fdsocket_by_infd, &fd, fdsocket_infd_find)))
|
|
return;
|
|
|
|
retd = read(fds->infd, buf, sizeof(buf));
|
|
if (retd > 0) {
|
|
plug_receive(fds->plug, 0, buf, retd);
|
|
} else {
|
|
del234(fdsocket_by_infd, fds);
|
|
uxsel_del(fds->infd);
|
|
close(fds->infd);
|
|
fds->infd = -1;
|
|
|
|
if (retd < 0) {
|
|
plug_closing_errno(fds->plug, errno);
|
|
} else {
|
|
plug_closing_normal(fds->plug);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void fdsocket_select_result_output(int fd, int event)
|
|
{
|
|
FdSocket *fds;
|
|
|
|
if (!(fds = find234(fdsocket_by_outfd, &fd, fdsocket_outfd_find)))
|
|
return;
|
|
|
|
if (fdsocket_try_send(fds))
|
|
plug_sent(fds->plug, bufchain_size(&fds->pending_output_data));
|
|
}
|
|
|
|
static void fdsocket_select_result_input_error(int fd, int event)
|
|
{
|
|
FdSocket *fds;
|
|
char buf[20480];
|
|
int retd;
|
|
|
|
if (!(fds = find234(fdsocket_by_inerrfd, &fd, fdsocket_inerrfd_find)))
|
|
return;
|
|
|
|
retd = read(fd, buf, sizeof(buf));
|
|
if (retd > 0) {
|
|
log_proxy_stderr(fds->plug, &fds->sock, &fds->psb, buf, retd);
|
|
} else {
|
|
del234(fdsocket_by_inerrfd, fds);
|
|
uxsel_del(fds->inerrfd);
|
|
close(fds->inerrfd);
|
|
fds->inerrfd = -1;
|
|
}
|
|
}
|
|
|
|
static const SocketVtable FdSocket_sockvt = {
|
|
.plug = fdsocket_plug,
|
|
.close = fdsocket_close,
|
|
.write = fdsocket_write,
|
|
.write_oob = fdsocket_write_oob,
|
|
.write_eof = fdsocket_write_eof,
|
|
.set_frozen = fdsocket_set_frozen,
|
|
.socket_error = fdsocket_socket_error,
|
|
.endpoint_info = nullsock_endpoint_info,
|
|
};
|
|
|
|
static void fdsocket_connect_success_callback(void *ctx)
|
|
{
|
|
FdSocket *fds = (FdSocket *)ctx;
|
|
plug_log(fds->plug, &fds->sock, PLUGLOG_CONNECT_SUCCESS,
|
|
fds->addr, fds->port, NULL, 0);
|
|
}
|
|
|
|
void setup_fd_socket(Socket *s, int infd, int outfd, int inerrfd)
|
|
{
|
|
FdSocket *fds = container_of(s, FdSocket, sock);
|
|
assert(fds->sock.vt == &FdSocket_sockvt);
|
|
|
|
if (fds->opener) {
|
|
deferred_socket_opener_free(fds->opener);
|
|
fds->opener = NULL;
|
|
}
|
|
|
|
fds->infd = infd;
|
|
fds->outfd = outfd;
|
|
fds->inerrfd = inerrfd;
|
|
|
|
if (fds->outfd >= 0) {
|
|
if (!fdsocket_by_outfd)
|
|
fdsocket_by_outfd = newtree234(fdsocket_outfd_cmp);
|
|
add234(fdsocket_by_outfd, fds);
|
|
}
|
|
|
|
if (fds->infd >= 0) {
|
|
if (!fdsocket_by_infd)
|
|
fdsocket_by_infd = newtree234(fdsocket_infd_cmp);
|
|
add234(fdsocket_by_infd, fds);
|
|
uxsel_set(fds->infd, SELECT_R, fdsocket_select_result_input);
|
|
}
|
|
|
|
if (fds->inerrfd >= 0) {
|
|
assert(fds->inerrfd != fds->infd);
|
|
if (!fdsocket_by_inerrfd)
|
|
fdsocket_by_inerrfd = newtree234(fdsocket_inerrfd_cmp);
|
|
add234(fdsocket_by_inerrfd, fds);
|
|
uxsel_set(fds->inerrfd, SELECT_R, fdsocket_select_result_input_error);
|
|
}
|
|
|
|
queue_toplevel_callback(fdsocket_connect_success_callback, fds);
|
|
}
|
|
|
|
void fd_socket_set_psb_prefix(Socket *s, const char *prefix)
|
|
{
|
|
FdSocket *fds = container_of(s, FdSocket, sock);
|
|
assert(fds->sock.vt == &FdSocket_sockvt);
|
|
psb_set_prefix(&fds->psb, prefix);
|
|
}
|
|
|
|
static FdSocket *make_fd_socket_internal(SockAddr *addr, int port, Plug *plug)
|
|
{
|
|
FdSocket *fds;
|
|
|
|
fds = snew(FdSocket);
|
|
fds->sock.vt = &FdSocket_sockvt;
|
|
fds->addr = addr;
|
|
fds->port = port;
|
|
fds->plug = plug;
|
|
fds->outgoingeof = EOF_NO;
|
|
fds->pending_error = 0;
|
|
|
|
fds->opener = NULL;
|
|
fds->infd = fds->outfd = fds->inerrfd = -1;
|
|
|
|
bufchain_init(&fds->pending_input_data);
|
|
bufchain_init(&fds->pending_output_data);
|
|
psb_init(&fds->psb);
|
|
|
|
return fds;
|
|
}
|
|
|
|
Socket *make_fd_socket(int infd, int outfd, int inerrfd,
|
|
SockAddr *addr, int port, Plug *plug)
|
|
{
|
|
FdSocket *fds = make_fd_socket_internal(addr, port, plug);
|
|
setup_fd_socket(&fds->sock, infd, outfd, inerrfd);
|
|
return &fds->sock;
|
|
}
|
|
|
|
Socket *make_deferred_fd_socket(DeferredSocketOpener *opener,
|
|
SockAddr *addr, int port, Plug *plug)
|
|
{
|
|
FdSocket *fds = make_fd_socket_internal(addr, port, plug);
|
|
fds->opener = opener;
|
|
return &fds->sock;
|
|
}
|