From f1746d69b172f8ab196ed52ec1941f374130eb57 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 24 Oct 2021 09:18:12 +0100 Subject: [PATCH] Add 'description' methods for Backend and Plug. These will typically be implemented by objects that are both a Backend *and* a Plug, and the two methods will deliver the same results to any caller, regardless of which facet of the object is known to that caller. Their purpose is to deliver a user-oriented natural-language description of what network connection the object is handling, so that it can appear in diagnostic messages. The messages I specifically have in mind are going to appear in cases where proxies require interactive authentication: when PuTTY prompts interactively for a password, it will need to explain which *thing* it's asking for the password for, and these descriptions are what it will use to describe the thing in question. Each backend is allowed to compose these messages however it thinks best. In all cases at present, the description string is constructed by the new centralised default_description() function, which takes a host name and port number and combines them with the backend's display name. But the SSH backend does things a bit differently, because it uses the _logical_ host name (the one that goes with the SSH host key) rather than the physical destination of the network connection. That seems more appropriate when the question it's really helping the user to answer is "What host am I supposed to be entering the password for?" In this commit, no clients of the new methods are introduced. I have a draft implementation of actually using it for the purpose I describe above, but it needs polishing. --- network.h | 22 ++++++++++++++++++++++ otherbackends/raw.c | 17 +++++++++++++++++ otherbackends/rlogin.c | 17 +++++++++++++++++ otherbackends/supdup.c | 17 +++++++++++++++++ otherbackends/telnet.c | 17 +++++++++++++++++ putty.h | 27 +++++++++++++++++++++++++++ ssh/ssh.c | 31 +++++++++++++++++++++++++------ utils/CMakeLists.txt | 1 + utils/default_description.c | 22 ++++++++++++++++++++++ 9 files changed, 165 insertions(+), 6 deletions(-) create mode 100644 utils/default_description.c diff --git a/network.h b/network.h index 099895bc..0b600f63 100644 --- a/network.h +++ b/network.h @@ -129,6 +129,26 @@ struct PlugVtable { * want the connection for some reason, or 0 on success. */ int (*accepting)(Plug *p, accept_fn_t constructor, accept_ctx_t ctx); + + /* + * Returns a user-facing description of the nature of the network + * connection being made. Used in interactive proxy authentication + * to announce which connection attempt is now in control of the + * Seat. + * + * The idea is not just to be written in natural language, but to + * connect with the user's idea of _why_ they think some + * connection is being made. For example, instead of saying 'TCP + * connection to 123.45.67.89 port 22', you might say 'SSH + * connection to [logical host name for SSH host key purposes]'. + * + * This function pointer may be NULL, or may exist but return + * NULL, in which case no user-facing description is available. + * + * If a non-NULL string is returned, it must be freed by the + * caller. + */ + char *(*description)(Plug *p); }; /* Proxy indirection layer. @@ -236,6 +256,8 @@ static inline void plug_sent (Plug *p, size_t bufsize) { p->vt->sent(p, bufsize); } static inline int plug_accepting(Plug *p, accept_fn_t cons, accept_ctx_t ctx) { return p->vt->accepting(p, cons, ctx); } +static inline char *plug_description(Plug *p) +{ return p->vt->description ? p->vt->description(p) : NULL; } /* * Special error values are returned from sk_namelookup and sk_new diff --git a/otherbackends/raw.c b/otherbackends/raw.c index d6693244..57130dc6 100644 --- a/otherbackends/raw.c +++ b/otherbackends/raw.c @@ -19,6 +19,7 @@ struct Raw { LogContext *logctx; Ldisc *ldisc; bool sent_console_eof, sent_socket_eof, socket_connected; + char *description; Conf *conf; @@ -115,11 +116,24 @@ static void raw_sent(Plug *plug, size_t bufsize) seat_sent(raw->seat, raw->bufsize); } +static char *raw_plug_description(Plug *plug) +{ + Raw *raw = container_of(plug, Raw, plug); + return dupstr(raw->description); +} + +static char *raw_backend_description(Backend *backend) +{ + Raw *raw = container_of(backend, Raw, backend); + return dupstr(raw->description); +} + static const PlugVtable Raw_plugvt = { .log = raw_log, .closing = raw_closing, .receive = raw_receive, .sent = raw_sent, + .description = raw_plug_description, }; /* @@ -151,6 +165,7 @@ static char *raw_init(const BackendVtable *vt, Seat *seat, raw->bufsize = 0; raw->socket_connected = false; raw->conf = conf_copy(conf); + raw->description = default_description(vt, host, port); raw->seat = seat; raw->logctx = logctx; @@ -205,6 +220,7 @@ static void raw_free(Backend *be) if (raw->s) sk_close(raw->s); conf_free(raw->conf); + sfree(raw->description); sfree(raw); } @@ -337,6 +353,7 @@ const BackendVtable raw_backend = { .provide_ldisc = raw_provide_ldisc, .unthrottle = raw_unthrottle, .cfg_info = raw_cfg_info, + .description = raw_backend_description, .id = "raw", .displayname_tc = "Raw", .displayname_lc = "raw", diff --git a/otherbackends/rlogin.c b/otherbackends/rlogin.c index 44a3f625..4e3abffe 100644 --- a/otherbackends/rlogin.c +++ b/otherbackends/rlogin.c @@ -23,6 +23,7 @@ struct Rlogin { Seat *seat; LogContext *logctx; Ldisc *ldisc; + char *description; Conf *conf; @@ -192,11 +193,24 @@ static void rlogin_startup(Rlogin *rlogin, int prompt_result, ldisc_check_sendok(rlogin->ldisc); } +static char *rlogin_plug_description(Plug *plug) +{ + Rlogin *rlogin = container_of(plug, Rlogin, plug); + return dupstr(rlogin->description); +} + +static char *rlogin_backend_description(Backend *backend) +{ + Rlogin *rlogin = container_of(backend, Rlogin, backend); + return dupstr(rlogin->description); +} + static const PlugVtable Rlogin_plugvt = { .log = rlogin_log, .closing = rlogin_closing, .receive = rlogin_receive, .sent = rlogin_sent, + .description = rlogin_plug_description, }; /* @@ -232,6 +246,7 @@ static char *rlogin_init(const BackendVtable *vt, Seat *seat, rlogin->cansize = false; rlogin->prompt = NULL; rlogin->conf = conf_copy(conf); + rlogin->description = default_description(vt, host, port); *backend_handle = &rlogin->backend; addressfamily = conf_get_int(conf, CONF_addressfamily); @@ -283,6 +298,7 @@ static void rlogin_free(Backend *be) if (rlogin->s) sk_close(rlogin->s); conf_free(rlogin->conf); + sfree(rlogin->description); sfree(rlogin); } @@ -444,6 +460,7 @@ const BackendVtable rlogin_backend = { .provide_ldisc = rlogin_provide_ldisc, .unthrottle = rlogin_unthrottle, .cfg_info = rlogin_cfg_info, + .description = rlogin_backend_description, .id = "rlogin", .displayname_tc = "Rlogin", .displayname_lc = "Rlogin", /* proper name, so capitalise it anyway */ diff --git a/otherbackends/supdup.c b/otherbackends/supdup.c index 2ec8e0f9..278e689e 100644 --- a/otherbackends/supdup.c +++ b/otherbackends/supdup.c @@ -73,6 +73,7 @@ struct supdup_tag LogContext *logctx; Ldisc *ldisc; int term_width, term_height; + char *description; long long ttyopt; long tcmxv; @@ -641,6 +642,18 @@ static void supdup_send_config(Supdup *supdup) supdup_send_36bits(supdup, TTYROL); // scroll amount } +static char *supdup_plug_description(Plug *plug) +{ + Supdup *supdup = container_of(plug, Supdup, plug); + return dupstr(supdup->description); +} + +static char *supdup_backend_description(Backend *backend) +{ + Supdup *supdup = container_of(backend, Supdup, backend); + return dupstr(supdup->description); +} + /* * Called to set up the Supdup connection. * @@ -660,6 +673,7 @@ static char *supdup_init(const BackendVtable *x, Seat *seat, .closing = supdup_closing, .receive = supdup_receive, .sent = supdup_sent, + .description = supdup_plug_description, }; SockAddr *addr; const char *err; @@ -681,6 +695,7 @@ static char *supdup_init(const BackendVtable *x, Seat *seat, supdup->term_height = conf_get_int(supdup->conf, CONF_height); supdup->pinger = NULL; supdup->sent_location = false; + supdup->description = default_description(supdup->backend.vt, host, port); *backend_handle = &supdup->backend; switch (conf_get_int(supdup->conf, CONF_supdup_ascii_set)) { @@ -799,6 +814,7 @@ static void supdup_free(Backend *be) if (supdup->pinger) pinger_free(supdup->pinger); conf_free(supdup->conf); + sfree(supdup->description); sfree(supdup); } @@ -935,6 +951,7 @@ const BackendVtable supdup_backend = { .provide_ldisc = supdup_provide_ldisc, .unthrottle = supdup_unthrottle, .cfg_info = supdup_cfg_info, + .description = supdup_backend_description, .id = "supdup", .displayname_tc = "SUPDUP", .displayname_lc = "SUPDUP", /* proper name, so capitalise it anyway */ diff --git a/otherbackends/telnet.c b/otherbackends/telnet.c index 048bb2d6..0011cf0d 100644 --- a/otherbackends/telnet.c +++ b/otherbackends/telnet.c @@ -178,6 +178,7 @@ struct Telnet { LogContext *logctx; Ldisc *ldisc; int term_width, term_height; + char *description; int opt_states[NUM_OPTS]; @@ -680,11 +681,24 @@ static void telnet_sent(Plug *plug, size_t bufsize) seat_sent(telnet->seat, telnet->bufsize); } +static char *telnet_plug_description(Plug *plug) +{ + Telnet *telnet = container_of(plug, Telnet, plug); + return dupstr(telnet->description); +} + +static char *telnet_backend_description(Backend *backend) +{ + Telnet *telnet = container_of(backend, Telnet, backend); + return dupstr(telnet->description); +} + static const PlugVtable Telnet_plugvt = { .log = telnet_log, .closing = telnet_closing, .receive = telnet_receive, .sent = telnet_sent, + .description = telnet_plug_description, }; /* @@ -724,6 +738,7 @@ static char *telnet_init(const BackendVtable *vt, Seat *seat, telnet->state = TOP_LEVEL; telnet->ldisc = NULL; telnet->pinger = NULL; + telnet->description = default_description(vt, host, port); *backend_handle = &telnet->backend; /* @@ -813,6 +828,7 @@ static void telnet_free(Backend *be) if (telnet->pinger) pinger_free(telnet->pinger); conf_free(telnet->conf); + sfree(telnet->description); sfree(telnet); } /* @@ -1081,6 +1097,7 @@ const BackendVtable telnet_backend = { .provide_ldisc = telnet_provide_ldisc, .unthrottle = telnet_unthrottle, .cfg_info = telnet_cfg_info, + .description = telnet_backend_description, .id = "telnet", .displayname_tc = "Telnet", .displayname_lc = "Telnet", /* proper name, so capitalise it anyway */ diff --git a/putty.h b/putty.h index a2136a05..82ee83a8 100644 --- a/putty.h +++ b/putty.h @@ -680,6 +680,28 @@ struct BackendVtable { * connections that would be lost if this one were terminated. */ char *(*close_warn_text)(Backend *be); + /* + * Returns a user-facing description of the nature of the network + * connection being made. Used in interactive proxy authentication + * to announce which connection attempt is now in control of the + * Seat. + * + * The idea is not just to be written in natural language, but to + * connect with the user's idea of _why_ they think some + * connection is being made. For example, instead of saying 'TCP + * connection to 123.45.67.89 port 22', you might say 'SSH + * connection to [logical host name for SSH host key purposes]'. + * + * This function pointer may be NULL, or may exist but return + * NULL, in which case no user-facing description is available. + * (Backends which are never proxied, such as pty and ConPTY, need + * not bother to fill this in.) + * + * If a non-NULL string is returned, it must be freed by the + * caller. + */ + char *(*description)(Backend *be); + /* 'id' is a machine-readable name for the backend, used in * saved-session storage. 'displayname_tc' and 'displayname_lc' * are human-readable names, one in title-case for config boxes, @@ -728,6 +750,11 @@ static inline void backend_unthrottle(Backend *be, size_t bufsize) { be->vt->unthrottle(be, bufsize); } static inline int backend_cfg_info(Backend *be) { return be->vt->cfg_info(be); } +static inline char *backend_description(Backend *be) +{ return be->vt->description ? be->vt->description(be) : NULL; } + +char *default_description(const BackendVtable *backvt, + const char *host, int port); extern const struct BackendVtable *const backends[]; /* diff --git a/ssh/ssh.c b/ssh/ssh.c index 103f5795..c9ce2b0b 100644 --- a/ssh/ssh.c +++ b/ssh/ssh.c @@ -57,6 +57,7 @@ struct Ssh { char *savedhost; int savedport; char *fullhostname; + char *description; bool fallback_cmd; int exitcode; @@ -717,11 +718,24 @@ static char *ssh_close_warn_text(Backend *be) return msg; } +static char *ssh_plug_description(Plug *plug) +{ + Ssh *ssh = container_of(plug, Ssh, plug); + return dupstr(ssh->description); +} + +static char *ssh_backend_description(Backend *backend) +{ + Ssh *ssh = container_of(backend, Ssh, backend); + return dupstr(ssh->description); +} + static const PlugVtable Ssh_plugvt = { .log = ssh_socket_log, .closing = ssh_closing, .receive = ssh_receive, .sent = ssh_sent, + .description = ssh_plug_description, }; /* @@ -731,17 +745,13 @@ static const PlugVtable Ssh_plugvt = { * freed by the caller. */ static char *connect_to_host( - Ssh *ssh, const char *host, int port, char **realhost, + Ssh *ssh, const char *host, int port, char *loghost, char **realhost, bool nodelay, bool keepalive) { SockAddr *addr; const char *err; - char *loghost; int addressfamily, sshprot; - ssh_hostport_setup(host, port, ssh->conf, - &ssh->savedhost, &ssh->savedport, &loghost); - ssh->plug.vt = &Ssh_plugvt; /* @@ -938,11 +948,17 @@ static char *ssh_init(const BackendVtable *vt, Seat *seat, ssh->cl_dummy.vt = &dummy_connlayer_vtable; ssh->cl_dummy.logctx = ssh->logctx = logctx; + char *loghost; + + ssh_hostport_setup(host, port, ssh->conf, + &ssh->savedhost, &ssh->savedport, &loghost); + ssh->description = default_description(vt, ssh->savedhost, ssh->savedport); + random_ref(); /* do this now - may be needed by sharing setup code */ ssh->need_random_unref = true; char *conn_err = connect_to_host( - ssh, host, port, realhost, nodelay, keepalive); + ssh, host, port, loghost, realhost, nodelay, keepalive); if (conn_err) { /* Call random_unref now instead of waiting until the caller * frees this useless Ssh object, in case the caller is @@ -985,6 +1001,7 @@ static void ssh_free(Backend *be) #endif sfree(ssh->deferred_abort_message); + sfree(ssh->description); delete_callbacks_for_context(ssh); /* likely to catch ic_out_raw */ @@ -1247,6 +1264,7 @@ const BackendVtable ssh_backend = { .cfg_info = ssh_cfg_info, .test_for_upstream = ssh_test_for_upstream, .close_warn_text = ssh_close_warn_text, + .description = ssh_backend_description, .id = "ssh", .displayname_tc = "SSH", .displayname_lc = "SSH", /* proper name, so capitalise it anyway */ @@ -1273,6 +1291,7 @@ const BackendVtable sshconn_backend = { .cfg_info = ssh_cfg_info, .test_for_upstream = ssh_test_for_upstream, .close_warn_text = ssh_close_warn_text, + .description = ssh_backend_description, .id = "ssh-connection", .displayname_tc = "Bare ssh-connection", .displayname_lc = "bare ssh-connection", diff --git a/utils/CMakeLists.txt b/utils/CMakeLists.txt index 6376c0c9..97557eb6 100644 --- a/utils/CMakeLists.txt +++ b/utils/CMakeLists.txt @@ -11,6 +11,7 @@ add_sources_from_current_dir(utils conf_launchable.c ctrlparse.c debug.c + default_description.c dupcat.c dupprintf.c dupstr.c diff --git a/utils/default_description.c b/utils/default_description.c new file mode 100644 index 00000000..e0695ee6 --- /dev/null +++ b/utils/default_description.c @@ -0,0 +1,22 @@ +/* + * Construct a description string for a backend to use as + * backend_description(), or a plug as plug_description(). + * + * For some backends this will be overridden: e.g. SSH prefers to + * think in terms of _logical_ host names (i.e. the one associated + * with the host key) rather than the physical details of where you're + * connecting to. But this default is good for simpler backends. + */ + +#include "putty.h" + +char *default_description(const BackendVtable *backvt, + const char *host, int port) +{ + const char *be_name = backvt->displayname_lc; + + if (backvt->default_port && port == backvt->default_port) + return dupprintf("%s connection to %s", be_name, host); + else + return dupprintf("%s connection to %s port %d", be_name, host, port); +}