diff --git a/defs.h b/defs.h index c30ac5cf..039341fe 100644 --- a/defs.h +++ b/defs.h @@ -98,6 +98,8 @@ typedef struct SockAddr SockAddr; typedef struct Socket Socket; typedef struct Plug Plug; typedef struct SocketPeerInfo SocketPeerInfo; +typedef struct DeferredSocketOpener DeferredSocketOpener; +typedef struct DeferredSocketOpenerVtable DeferredSocketOpenerVtable; typedef struct Backend Backend; typedef struct BackendVtable BackendVtable; diff --git a/network.h b/network.h index 551d0be2..5d0112b0 100644 --- a/network.h +++ b/network.h @@ -402,4 +402,31 @@ void psb_init(ProxyStderrBuf *psb); void log_proxy_stderr( Plug *plug, ProxyStderrBuf *psb, const void *vdata, size_t len); +/* ---------------------------------------------------------------------- + * The DeferredSocketOpener trait. This is a thing that some Socket + * implementations may choose to own if they need to delay actually + * setting up the underlying connection. For example, sockets used in + * local-proxy handling (Unix FdSocket / Windows HandleSocket) might + * need to do this if they have to prompt the user interactively for + * parts of the command they'll run. + * + * Mostly, a DeferredSocketOpener implementation will keep to itself, + * arrange its own callbacks in order to do whatever setup it needs, + * and when it's ready, call back to its parent Socket via some + * implementation-specific API of its own. So the shared API here + * requires almost nothing: the only thing we need is a free function, + * so that if the owner of a Socket of this kind needs to close it + * before the deferred connection process is finished, the Socket can + * also clean up the DeferredSocketOpener dangling off it. + */ + +struct DeferredSocketOpener { + const DeferredSocketOpenerVtable *vt; +}; +struct DeferredSocketOpenerVtable { + void (*free)(DeferredSocketOpener *); +}; +static inline void deferred_socket_opener_free(DeferredSocketOpener *dso) +{ dso->vt->free(dso); } + #endif diff --git a/unix/fd-socket.c b/unix/fd-socket.c index 013c5361..85dbc7c0 100644 --- a/unix/fd-socket.c +++ b/unix/fd-socket.c @@ -14,7 +14,8 @@ #include "network.h" typedef struct FdSocket { - int outfd, infd, inerrfd; + 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; @@ -115,6 +116,9 @@ 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); @@ -165,6 +169,9 @@ 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; @@ -326,27 +333,20 @@ static void fdsocket_connect_success_callback(void *ctx) NULL, 0); } -Socket *make_fd_socket(int infd, int outfd, int inerrfd, - SockAddr *addr, int port, Plug *plug) +void setup_fd_socket(Socket *s, int infd, int outfd, int inerrfd) { - FdSocket *fds; + FdSocket *fds = container_of(s, FdSocket, sock); + assert(fds->sock.vt == &FdSocket_sockvt); - 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; + if (fds->opener) { + deferred_socket_opener_free(fds->opener); + fds->opener = NULL; + } fds->infd = infd; fds->outfd = outfd; fds->inerrfd = inerrfd; - bufchain_init(&fds->pending_input_data); - bufchain_init(&fds->pending_output_data); - psb_init(&fds->psb); - if (fds->outfd >= 0) { if (!fdsocket_by_outfd) fdsocket_by_outfd = newtree234(fdsocket_outfd_cmp); @@ -369,6 +369,42 @@ Socket *make_fd_socket(int infd, int outfd, int inerrfd, } queue_toplevel_callback(fdsocket_connect_success_callback, fds); +} +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; } diff --git a/unix/platform.h b/unix/platform.h index 79b171ca..86996b3d 100644 --- a/unix/platform.h +++ b/unix/platform.h @@ -382,6 +382,9 @@ bool so_peercred(int fd, int *pid, int *uid, int *gid); */ Socket *make_fd_socket(int infd, int outfd, int inerrfd, SockAddr *addr, int port, Plug *plug); +Socket *make_deferred_fd_socket(DeferredSocketOpener *opener, + SockAddr *addr, int port, Plug *plug); +void setup_fd_socket(Socket *s, int infd, int outfd, int inerrfd); /* * Default font setting, which can vary depending on NOT_X_WINDOWS. diff --git a/windows/handle-socket.c b/windows/handle-socket.c index 32479d64..8c13d686 100644 --- a/windows/handle-socket.c +++ b/windows/handle-socket.c @@ -11,32 +11,51 @@ #include "putty.h" #include "network.h" +/* + * Freezing one of these sockets is a slightly fiddly business, + * because the reads from the handle are happening in a separate + * thread as blocking system calls and so once one is in progress it + * can't sensibly be interrupted. Hence, after the user tries to + * freeze one of these sockets, it's unavoidable that we may receive + * one more load of data before we manage to get winhandl.c to stop + * reading. + */ +typedef enum HandleSocketFreezeState { + UNFROZEN, /* reading as normal */ + FREEZING, /* have been set to frozen but winhandl is still reading */ + FROZEN, /* really frozen - winhandl has been throttled */ + THAWING /* we're gradually releasing our remaining data */ +} HandleSocketFreezeState; + typedef struct HandleSocket { - HANDLE send_H, recv_H, stderr_H; - struct handle *send_h, *recv_h, *stderr_h; + union { + struct { + HANDLE send_H, recv_H, stderr_H; + struct handle *send_h, *recv_h, *stderr_h; - /* - * Freezing one of these sockets is a slightly fiddly business, - * because the reads from the handle are happening in a separate - * thread as blocking system calls and so once one is in progress - * it can't sensibly be interrupted. Hence, after the user tries - * to freeze one of these sockets, it's unavoidable that we may - * receive one more load of data before we manage to get - * winhandl.c to stop reading. - */ - enum { - UNFROZEN, /* reading as normal */ - FREEZING, /* have been set to frozen but winhandl is still reading */ - FROZEN, /* really frozen - winhandl has been throttled */ - THAWING /* we're gradually releasing our remaining data */ - } frozen; - /* We buffer data here if we receive it from winhandl while frozen. */ - bufchain inputdata; + HandleSocketFreezeState frozen; + /* We buffer data here if we receive it from winhandl + * while frozen. */ + bufchain inputdata; - /* Handle logging proxy error messages from stderr_H, if we have one. */ - ProxyStderrBuf psb; + /* Handle logging proxy error messages from stderr_H, if + * we have one */ + ProxyStderrBuf psb; - bool defer_close, deferred_close; /* in case of re-entrance */ + bool defer_close, deferred_close; /* in case of re-entrance */ + }; + struct { + DeferredSocketOpener *opener; + + /* We buffer data here if we receive it via sk_write + * before the socket is opened. */ + bufchain outputdata; + + bool output_eof_pending; + + bool start_frozen; + }; + }; char *error; @@ -348,6 +367,7 @@ Socket *make_handle_socket(HANDLE send_H, HANDLE recv_H, HANDLE stderr_H, hs->port = port; hs->plug = plug; hs->error = NULL; + hs->frozen = UNFROZEN; bufchain_init(&hs->inputdata); psb_init(&hs->psb); @@ -367,3 +387,119 @@ Socket *make_handle_socket(HANDLE send_H, HANDLE recv_H, HANDLE stderr_H, return &hs->sock; } + +static void sk_handle_deferred_close(Socket *s) +{ + HandleSocket *hs = container_of(s, HandleSocket, sock); + + deferred_socket_opener_free(hs->opener); + bufchain_clear(&hs->outputdata); + + if (hs->addr) + sk_addr_free(hs->addr); + + delete_callbacks_for_context(hs); + + sfree(hs); +} + +static size_t sk_handle_deferred_write(Socket *s, const void *data, size_t len) +{ + HandleSocket *hs = container_of(s, HandleSocket, sock); + assert(!hs->output_eof_pending); + bufchain_add(&hs->outputdata, data, len); + return bufchain_size(&hs->outputdata); +} + +static void sk_handle_deferred_write_eof(Socket *s) +{ + HandleSocket *hs = container_of(s, HandleSocket, sock); + assert(!hs->output_eof_pending); + hs->output_eof_pending = true; +} + +static void sk_handle_deferred_set_frozen(Socket *s, bool is_frozen) +{ + HandleSocket *hs = container_of(s, HandleSocket, sock); + hs->frozen = is_frozen; +} + +static SocketPeerInfo *sk_handle_deferred_peer_info(Socket *s) +{ + return NULL; +} + +static const SocketVtable HandleSocket_deferred_sockvt = { + .plug = sk_handle_plug, + .close = sk_handle_deferred_close, + .write = sk_handle_deferred_write, + .write_oob = sk_handle_deferred_write, + .write_eof = sk_handle_deferred_write_eof, + .set_frozen = sk_handle_deferred_set_frozen, + .socket_error = sk_handle_socket_error, + .peer_info = sk_handle_deferred_peer_info, +}; + +Socket *make_deferred_handle_socket(DeferredSocketOpener *opener, + SockAddr *addr, int port, Plug *plug) +{ + HandleSocket *hs = snew(HandleSocket); + hs->sock.vt = &HandleSocket_deferred_sockvt; + hs->addr = addr; + hs->port = port; + hs->plug = plug; + hs->error = NULL; + + hs->opener = opener; + bufchain_init(&hs->outputdata); + hs->output_eof_pending = false; + hs->start_frozen = false; + + return &hs->sock; +} + +void setup_handle_socket(Socket *s, HANDLE send_H, HANDLE recv_H, + HANDLE stderr_H, bool overlapped) +{ + HandleSocket *hs = container_of(s, HandleSocket, sock); + assert(hs->sock.vt == &HandleSocket_deferred_sockvt); + + int flags = (overlapped ? HANDLE_FLAG_OVERLAPPED : 0); + + struct handle *recv_h = handle_input_new( + recv_H, handle_gotdata, hs, flags); + struct handle *send_h = handle_output_new( + send_H, handle_sentdata, hs, flags); + struct handle *stderr_h = !stderr_H ? NULL : handle_input_new( + stderr_H, handle_stderr, hs, flags); + + while (bufchain_size(&hs->outputdata)) { + ptrlen data = bufchain_prefix(&hs->outputdata); + handle_write(send_h, data.ptr, data.len); + bufchain_consume(&hs->outputdata, data.len); + } + + if (hs->output_eof_pending) + handle_write_eof(send_h); + + bool start_frozen = hs->start_frozen; + + deferred_socket_opener_free(hs->opener); + bufchain_clear(&hs->outputdata); + + hs->sock.vt = &HandleSocket_sockvt; + hs->frozen = start_frozen ? FREEZING : UNFROZEN; + bufchain_init(&hs->inputdata); + psb_init(&hs->psb); + + hs->recv_H = recv_H; + hs->recv_h = recv_h; + hs->send_H = send_H; + hs->send_h = send_h; + hs->stderr_H = stderr_H; + hs->stderr_h = stderr_h; + + hs->defer_close = hs->deferred_close = false; + + queue_toplevel_callback(sk_handle_connect_success_callback, hs); +} diff --git a/windows/platform.h b/windows/platform.h index 78ea1e59..660bf590 100644 --- a/windows/platform.h +++ b/windows/platform.h @@ -339,6 +339,10 @@ extern HANDLE winselcli_event; Socket *make_handle_socket(HANDLE send_H, HANDLE recv_H, HANDLE stderr_H, SockAddr *addr, int port, Plug *plug, bool overlapped); /* winhsock */ +Socket *make_deferred_handle_socket(DeferredSocketOpener *opener, + SockAddr *addr, int port, Plug *plug); +void setup_handle_socket(Socket *s, HANDLE send_H, HANDLE recv_H, + HANDLE stderr_H, bool overlapped); Socket *new_named_pipe_client(const char *pipename, Plug *plug); /* winnpc */ Socket *new_named_pipe_listener(const char *pipename, Plug *plug); /* winnps */