From 5db33359a2ef58bb1d05771df045685b7529bcf6 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 24 Oct 2004 13:29:03 +0000 Subject: [PATCH] Further merges from trunk: Jacob's specials menu work, Jacob's extra -N docs, my environment variables patch, and my UTF-8 locale override mechanism in Unix PuTTY/pterm. [originally from svn r4673] --- config.c | 64 +++++++++++------------- doc/config.but | 43 +++++++++------- doc/man-pl.but | 4 ++ doc/plink.but | 3 +- plink.c | 1 + putty.h | 5 ++ settings.c | 2 + ssh.c | 132 ++++++++++++++++++++++++++++++++++++++++++++----- telnet.c | 5 ++ unix/pterm.c | 7 +-- unix/pty.c | 23 +++++++++ unix/unix.h | 4 +- unix/uxcfg.c | 15 ++++++ unix/uxplink.c | 1 + unix/uxucs.c | 27 ++++++++-- window.c | 4 +- 16 files changed, 261 insertions(+), 79 deletions(-) diff --git a/config.c b/config.c index 8588b23c..7898c7a8 100644 --- a/config.c +++ b/config.c @@ -1288,6 +1288,36 @@ void setup_config_box(struct controlbox *b, struct sesslist *sesslist, HELPCTX(connection_username), dlg_stdeditbox_handler, I(offsetof(Config,username)), I(sizeof(((Config *)0)->username))); + + ctrl_text(s, "Environment variables:", HELPCTX(telnet_environ)); + ctrl_columns(s, 2, 80, 20); + ed = (struct environ_data *) + ctrl_alloc(b, sizeof(struct environ_data)); + ed->varbox = ctrl_editbox(s, "Variable", 'v', 60, + HELPCTX(telnet_environ), + environ_handler, P(ed), P(NULL)); + ed->varbox->generic.column = 0; + ed->valbox = ctrl_editbox(s, "Value", 'l', 60, + HELPCTX(telnet_environ), + environ_handler, P(ed), P(NULL)); + ed->valbox->generic.column = 0; + ed->addbutton = ctrl_pushbutton(s, "Add", 'd', + HELPCTX(telnet_environ), + environ_handler, P(ed)); + ed->addbutton->generic.column = 1; + ed->rembutton = ctrl_pushbutton(s, "Remove", 'r', + HELPCTX(telnet_environ), + environ_handler, P(ed)); + ed->rembutton->generic.column = 1; + ctrl_columns(s, 1, 100); + ed->listbox = ctrl_listbox(s, NULL, NO_SHORTCUT, + HELPCTX(telnet_environ), + environ_handler, P(ed)); + ed->listbox->listbox.height = 3; + ed->listbox->listbox.ncols = 2; + ed->listbox->listbox.percentages = snewn(2, int); + ed->listbox->listbox.percentages[0] = 30; + ed->listbox->listbox.percentages[1] = 70; } s = ctrl_getset(b, "Connection", "keepalive", @@ -1389,40 +1419,6 @@ void setup_config_box(struct controlbox *b, struct sesslist *sesslist, ctrl_settitle(b, "Connection/Telnet", "Options controlling Telnet connections"); - if (!midsession) { - s = ctrl_getset(b, "Connection/Telnet", "data", - "Data to send to the server"); - ctrl_text(s, "Environment variables:", HELPCTX(telnet_environ)); - ctrl_columns(s, 2, 80, 20); - ed = (struct environ_data *) - ctrl_alloc(b, sizeof(struct environ_data)); - ed->varbox = ctrl_editbox(s, "Variable", 'v', 60, - HELPCTX(telnet_environ), - environ_handler, P(ed), P(NULL)); - ed->varbox->generic.column = 0; - ed->valbox = ctrl_editbox(s, "Value", 'l', 60, - HELPCTX(telnet_environ), - environ_handler, P(ed), P(NULL)); - ed->valbox->generic.column = 0; - ed->addbutton = ctrl_pushbutton(s, "Add", 'd', - HELPCTX(telnet_environ), - environ_handler, P(ed)); - ed->addbutton->generic.column = 1; - ed->rembutton = ctrl_pushbutton(s, "Remove", 'r', - HELPCTX(telnet_environ), - environ_handler, P(ed)); - ed->rembutton->generic.column = 1; - ctrl_columns(s, 1, 100); - ed->listbox = ctrl_listbox(s, NULL, NO_SHORTCUT, - HELPCTX(telnet_environ), - environ_handler, P(ed)); - ed->listbox->listbox.height = 3; - ed->listbox->listbox.ncols = 2; - ed->listbox->listbox.percentages = snewn(2, int); - ed->listbox->listbox.percentages[0] = 30; - ed->listbox->listbox.percentages[1] = 70; - } - s = ctrl_getset(b, "Connection/Telnet", "protocol", "Telnet protocol adjustments"); diff --git a/doc/config.but b/doc/config.but index 994caeec..6fb95b6b 100644 --- a/doc/config.but +++ b/doc/config.but @@ -1,4 +1,4 @@ -\versionid $Id: config.but,v 1.92.2.1 2004/10/24 13:21:11 simon Exp $ +\versionid $Id: config.but,v 1.92.2.2 2004/10/24 13:29:02 simon Exp $ \C{config} Configuring PuTTY @@ -1502,6 +1502,30 @@ it explicitly every time. (Some Telnet servers don't support this.) In this box you can type that user name. +\S{config-environ} Setting environment variables on the server + +\cfg{winhelp-topic}{telnet.environ} + +The Telnet protocol provides a means for the client to pass +environment variables to the server. Many Telnet servers have +stopped supporting this feature due to security flaws, but PuTTY +still supports it for the benefit of any servers which have found +other ways around the security problems than just disabling the +whole mechanism. + +Version 2 of the SSH protocol also provides a similar mechanism, +which is easier to implement without security flaws. Newer SSH2 +servers are more likely to support it than older ones. + +This configuration data is not used in the SSHv1, rlogin or raw +protocols. + +To add an environment variable to the list transmitted down the +connection, you enter the variable name in the \q{Variable} box, +enter its value in the \q{Value} box, and press the \q{Add} button. +To remove one from the list, select it in the list box and press +\q{Remove}. + \S{config-keepalive} Using keepalives to prevent disconnection \cfg{winhelp-topic}{connection.keepalive} @@ -1768,23 +1792,6 @@ configuration fields will be ignored. The Telnet panel allows you to configure options that only apply to Telnet sessions. -\S{config-environ} Setting environment variables on the server - -\cfg{winhelp-topic}{telnet.environ} - -The Telnet protocol provides a means for the client to pass -environment variables to the server. Many Telnet servers have -stopped supporting this feature due to security flaws, but PuTTY -still supports it for the benefit of any servers which have found -other ways around the security problems than just disabling the -whole mechanism. - -To add an environment variable to the list transmitted down the -connection, you enter the variable name in the \q{Variable} box, -enter its value in the \q{Value} box, and press the \q{Add} button. -To remove one from the list, select it in the list box and press -\q{Remove}. - \S{config-oldenviron} \q{Handling of OLD_ENVIRON ambiguity} \cfg{winhelp-topic}{telnet.oldenviron} diff --git a/doc/man-pl.but b/doc/man-pl.but index 1f34dc1a..cf0d4194 100644 --- a/doc/man-pl.but +++ b/doc/man-pl.but @@ -136,6 +136,10 @@ tunnel all their connections. Only works in SSH. \dd Remote command is SSH subsystem (SSH-2 only). +\dt \cw{-N} + +\dd Don't start a remote command or shell at all (SSH-2 only). + \S{plink-manpage-more-information} MORE INFORMATION For more information on plink, it's probably best to go and look at diff --git a/doc/plink.but b/doc/plink.but index 37416456..74974ce5 100644 --- a/doc/plink.but +++ b/doc/plink.but @@ -1,4 +1,4 @@ -\versionid $Id: plink.but,v 1.25 2004/08/19 13:14:55 jacob Exp $ +\versionid $Id: plink.but,v 1.25.2.1 2004/10/24 13:29:02 simon Exp $ \C{plink} Using the command-line connection tool Plink @@ -71,6 +71,7 @@ use Plink: \c -C enable compression \c -i key private key file for authentication \c -s remote command is an SSH subsystem (SSH-2 only) +\c -N don't start a shell/command (SSH-2 only) Once this works, you are ready to use Plink. diff --git a/plink.c b/plink.c index f6e2a702..b99646af 100644 --- a/plink.c +++ b/plink.c @@ -234,6 +234,7 @@ static void usage(void) printf(" -C enable compression\n"); printf(" -i key private key file for authentication\n"); printf(" -s remote command is an SSH subsystem (SSH-2 only)\n"); + printf(" -N don't start a shell/command (SSH-2 only)\n"); exit(1); } diff --git a/putty.h b/putty.h index bc778254..34a7a760 100644 --- a/putty.h +++ b/putty.h @@ -450,6 +450,7 @@ struct config_tag { /* translations */ int vtmode; char line_codepage[128]; + int utf8_override; int xlat_capslockcyr; /* X11 forwarding */ int x11_forward; @@ -562,6 +563,10 @@ void sys_cursor(void *frontend, int x, int y); void request_paste(void *frontend); void frontend_keypress(void *frontend); void ldisc_update(void *frontend, int echo, int edit); +/* It's the backend's responsibility to invoke this at the start of a + * connection, if necessary; it can also invoke it later if the set of + * special commands changes. It does not need to invoke it at session + * shutdown. */ void update_specials_menu(void *frontend); int from_backend(void *frontend, int is_stderr, const char *data, int len); #define OPTIMISE_IS_SCROLL 1 diff --git a/settings.c b/settings.c index b0f223e9..a8f70917 100644 --- a/settings.c +++ b/settings.c @@ -314,6 +314,7 @@ void save_open_settings(void *sesskey, int do_host, Config *cfg) write_setting_s(sesskey, buf, buf2); } write_setting_s(sesskey, "LineCodePage", cfg->line_codepage); + write_setting_i(sesskey, "UTF8Override", cfg->utf8_override); write_setting_s(sesskey, "Printer", cfg->printer); write_setting_i(sesskey, "CapsLockCyr", cfg->xlat_capslockcyr); write_setting_i(sesskey, "ScrollBar", cfg->scrollbar); @@ -608,6 +609,7 @@ void load_open_settings(void *sesskey, int do_host, Config *cfg) */ gpps(sesskey, "LineCodePage", "", cfg->line_codepage, sizeof(cfg->line_codepage)); + gppi(sesskey, "UTF8Override", 1, &cfg->utf8_override); gpps(sesskey, "Printer", "", cfg->printer, sizeof(cfg->printer)); gppi (sesskey, "CapsLockCyr", 0, &cfg->xlat_capslockcyr); gppi(sesskey, "ScrollBar", 1, &cfg->scrollbar); diff --git a/ssh.c b/ssh.c index 8356a075..4fed0b8c 100644 --- a/ssh.c +++ b/ssh.c @@ -4788,6 +4788,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, int ispkt) int siglen, retlen, len; char *q, *agentreq, *ret; int try_send; + int num_env, env_left, env_ok; }; crState(do_ssh2_authconn_state); @@ -5645,6 +5646,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, int ispkt) ssh->mainchan->v.v2.remmaxpkt = ssh_pkt_getuint32(ssh); bufchain_init(&ssh->mainchan->v.v2.outbuffer); add234(ssh->channels, ssh->mainchan); + update_specials_menu(ssh->frontend); logevent("Opened channel for session"); } else ssh->mainchan = NULL; @@ -5953,6 +5955,82 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, int ispkt) ssh->editing = ssh->echoing = 1; } + /* + * Send environment variables. + * + * Simplest thing here is to send all the requests at once, and + * then wait for a whole bunch of successes or failures. + */ + if (ssh->mainchan && *ssh->cfg.environmt) { + char *e = ssh->cfg.environmt; + char *var, *varend, *val; + + s->num_env = 0; + + while (*e) { + var = e; + while (*e && *e != '\t') e++; + varend = e; + if (*e == '\t') e++; + val = e; + while (*e) e++; + e++; + + ssh2_pkt_init(ssh, SSH2_MSG_CHANNEL_REQUEST); + ssh2_pkt_adduint32(ssh, ssh->mainchan->remoteid); + ssh2_pkt_addstring(ssh, "env"); + ssh2_pkt_addbool(ssh, 1); /* want reply */ + ssh2_pkt_addstring_start(ssh); + ssh2_pkt_addstring_data(ssh, var, varend-var); + ssh2_pkt_addstring(ssh, val); + ssh2_pkt_send(ssh); + + s->num_env++; + } + + logeventf(ssh, "Sent %d environment variables", s->num_env); + + s->env_ok = 0; + s->env_left = s->num_env; + + while (s->env_left > 0) { + do { + crWaitUntilV(ispkt); + if (ssh->pktin.type == SSH2_MSG_CHANNEL_WINDOW_ADJUST) { + unsigned i = ssh_pkt_getuint32(ssh); + struct ssh_channel *c; + c = find234(ssh->channels, &i, ssh_channelfind); + if (!c) + continue; /* nonexistent channel */ + c->v.v2.remwindow += ssh_pkt_getuint32(ssh); + } + } while (ssh->pktin.type == SSH2_MSG_CHANNEL_WINDOW_ADJUST); + + if (ssh->pktin.type != SSH2_MSG_CHANNEL_SUCCESS) { + if (ssh->pktin.type != SSH2_MSG_CHANNEL_FAILURE) { + bombout(("Unexpected response to environment request:" + " packet type %d", ssh->pktin.type)); + crStopV; + } + } else { + s->env_ok++; + } + + s->env_left--; + } + + if (s->env_ok == s->num_env) { + logevent("All environment variables successfully set"); + } else if (s->env_ok == 0) { + logevent("All environment variables refused"); + c_write_str(ssh, "Server refused to set environment variables\r\n"); + } else { + logeventf(ssh, "%d environment variables refused", + s->num_env - s->env_ok); + c_write_str(ssh, "Server refused to set all environment variables\r\n"); + } + } + /* * Start a shell or a remote command. We may have to attempt * this twice if the config data has provided a second choice @@ -6150,7 +6228,9 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, int ispkt) /* Do pre-close processing on the channel. */ switch (c->type) { case CHAN_MAINSESSION: - break; /* nothing to see here, move along */ + ssh->mainchan = NULL; + update_specials_menu(ssh->frontend); + break; case CHAN_X11: if (c->u.x11.s != NULL) x11_close(c->u.x11.s); @@ -6801,23 +6881,49 @@ static void ssh_size(void *handle, int width, int height) */ static const struct telnet_special *ssh_get_specials(void *handle) { + static const struct telnet_special ignore_special[] = { + {"IGNORE message", TS_NOP}, + }; + static const struct telnet_special ssh2_session_specials[] = { + {"", 0}, + {"Break", TS_BRK} + /* XXX we should also support signals */ + }; + static const struct telnet_special specials_end[] = { + {NULL, 0} + }; + static struct telnet_special ssh_specials[lenof(ignore_special) + + lenof(ssh2_session_specials) + + lenof(specials_end)]; Ssh ssh = (Ssh) handle; + int i = 0; +#define ADD_SPECIALS(name) \ + do { \ + assert((i + lenof(name)) <= lenof(ssh_specials)); \ + memcpy(&ssh_specials[i], name, sizeof name); \ + i += lenof(name); \ + } while(0) if (ssh->version == 1) { - static const struct telnet_special ssh1_specials[] = { - {"IGNORE message", TS_NOP}, - {NULL, 0} - }; - return ssh1_specials; + /* Don't bother offering IGNORE if we've decided the remote + * won't cope with it, since we wouldn't bother sending it if + * asked anyway. */ + if (!(ssh->remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE)) + ADD_SPECIALS(ignore_special); } else if (ssh->version == 2) { - static const struct telnet_special ssh2_specials[] = { - {"Break", TS_BRK}, - {"IGNORE message", TS_NOP}, - {NULL, 0} - }; - return ssh2_specials; - } else + /* XXX add rekey, when implemented */ + ADD_SPECIALS(ignore_special); + if (ssh->mainchan) + ADD_SPECIALS(ssh2_session_specials); + } /* else we're not ready yet */ + + if (i) { + ADD_SPECIALS(specials_end); + return ssh_specials; + } else { return NULL; + } +#undef ADD_SPECIALS } /* diff --git a/telnet.c b/telnet.c index 5b340488..540c6d2c 100644 --- a/telnet.c +++ b/telnet.c @@ -762,6 +762,11 @@ static const char *telnet_init(void *frontend_handle, void **backend_handle, */ telnet->in_synch = FALSE; + /* + * We can send special commands from the start. + */ + update_specials_menu(telnet->frontend); + return NULL; } diff --git a/unix/pterm.c b/unix/pterm.c index 56130c99..79ebc70a 100644 --- a/unix/pterm.c +++ b/unix/pterm.c @@ -2721,8 +2721,8 @@ void setup_fonts_ucs(struct gui_data *inst) inst->font_width = gdk_char_width(inst->fonts[0], ' '); inst->font_height = inst->fonts[0]->ascent + inst->fonts[0]->descent; - inst->direct_to_font = init_ucs(&inst->ucsdata, - inst->cfg.line_codepage, font_charset, + inst->direct_to_font = init_ucs(&inst->ucsdata, inst->cfg.line_codepage, + inst->cfg.utf8_override, font_charset, inst->cfg.vtmode); } @@ -3201,7 +3201,6 @@ static void start_backend(struct gui_data *inst) sfree(title); } inst->back->provide_logctx(inst->backhandle, inst->logctx); - update_specials_menu(inst); term_provide_resize_fn(inst->term, inst->back->size, inst->backhandle); @@ -3420,6 +3419,8 @@ int pt_main(int argc, char **argv) inst->specialsitem1 = menuitem; MKMENUITEM(NULL, NULL); inst->specialsitem2 = menuitem; + gtk_widget_hide(inst->specialsitem1); + gtk_widget_hide(inst->specialsitem2); MKMENUITEM("Clear Scrollback", clear_scrollback_menuitem); MKMENUITEM("Reset Terminal", reset_terminal_menuitem); MKMENUITEM("Copy All", copy_all_menuitem); diff --git a/unix/pty.c b/unix/pty.c index 0be58fb6..61b9d03a 100644 --- a/unix/pty.c +++ b/unix/pty.c @@ -590,6 +590,29 @@ static const char *pty_init(void *frontend, void **backend_handle, Config *cfg, sprintf(windowid_env_var, "WINDOWID=%ld", windowid); putenv(windowid_env_var); } + { + char *e = cfg->environmt; + char *var, *varend, *val, *varval; + while (*e) { + var = e; + while (*e && *e != '\t') e++; + varend = e; + if (*e == '\t') e++; + val = e; + while (*e) e++; + e++; + + varval = dupprintf("%.*s=%s", varend-var, var, val); + putenv(varval); + /* + * We must not free varval, since putenv links it + * into the environment _in place_. Weird, but + * there we go. Memory usage will be rationalised + * as soon as we exec anyway. + */ + } + } + /* * SIGINT and SIGQUIT may have been set to ignored by our * parent, particularly by things like sh -c 'pterm &' and diff --git a/unix/unix.h b/unix/unix.h index f12f576d..384789aa 100644 --- a/unix/unix.h +++ b/unix/unix.h @@ -118,8 +118,8 @@ void (*putty_signal(int sig, void (*func)(int)))(int); * Exports from unicode.c. */ struct unicode_data; -int init_ucs(struct unicode_data *ucsdata, - char *line_codepage, int font_charset, int vtmode); +int init_ucs(struct unicode_data *ucsdata, char *line_codepage, + int utf8_override, int font_charset, int vtmode); /* * Spare function exported directly from uxnet.c. diff --git a/unix/uxcfg.c b/unix/uxcfg.c index 813009ef..fff29680 100644 --- a/unix/uxcfg.c +++ b/unix/uxcfg.c @@ -129,6 +129,21 @@ void unix_setup_config_box(struct controlbox *b, int midsession, void *win) HELPCTX(no_help), dlg_stdeditbox_handler, I(offsetof(Config,shadowboldoffset)), I(-1)); + /* + * Markus Kuhn feels, not totally unreasonably, that it's good + * for all applications to shift into UTF-8 mode if they notice + * that they've been started with a LANG setting dictating it, + * so that people don't have to keep remembering a separate + * UTF-8 option for every application they use. Therefore, + * here's an override option in the Translation panel. + */ + s = ctrl_getset(b, "Window/Translation", "trans", + "Character set translation on received data"); + ctrl_checkbox(s, "Override with UTF-8 if locale says so", 'l', + HELPCTX(translation_utf8_override), + dlg_stdcheckbox_handler, + I(offsetof(Config,utf8_override))); + /* * Unix supports a local-command proxy. This also means we must * adjust the text on the `Telnet command' control. diff --git a/unix/uxplink.c b/unix/uxplink.c index 146fcb30..e2b4d3d9 100644 --- a/unix/uxplink.c +++ b/unix/uxplink.c @@ -230,6 +230,7 @@ static void usage(void) printf(" -C enable compression\n"); printf(" -i key private key file for authentication\n"); printf(" -s remote command is an SSH subsystem (SSH-2 only)\n"); + printf(" -N don't start a shell/command (SSH-2 only)\n"); exit(1); } diff --git a/unix/uxucs.c b/unix/uxucs.c index 601b35da..83595538 100644 --- a/unix/uxucs.c +++ b/unix/uxucs.c @@ -106,8 +106,8 @@ int wc_to_mb(int codepage, int flags, wchar_t *wcstr, int wclen, /* * Return value is TRUE if pterm is to run in direct-to-font mode. */ -int init_ucs(struct unicode_data *ucsdata, - char *linecharset, int font_charset, int vtmode) +int init_ucs(struct unicode_data *ucsdata, char *linecharset, + int utf8_override, int font_charset, int vtmode) { int i, ret = 0; @@ -120,10 +120,27 @@ int init_ucs(struct unicode_data *ucsdata, ucsdata->font_codepage = -1; /* - * line_codepage should be decoded from the specification in - * cfg. + * If utf8_override is set and the POSIX locale settings + * dictate a UTF-8 character set, then just go straight for + * UTF-8. */ - ucsdata->line_codepage = decode_codepage(linecharset); + ucsdata->line_codepage = CS_NONE; + if (utf8_override) { + const char *s; + if (((s = getenv("LC_ALL")) && *s) || + ((s = getenv("LC_CTYPE")) && *s) || + ((s = getenv("LANG")) && *s)) { + if (strstr(s, "UTF-8")) + ucsdata->line_codepage = CS_UTF8; + } + } + + /* + * Failing that, line_codepage should be decoded from the + * specification in cfg. + */ + if (ucsdata->line_codepage == CS_NONE) + ucsdata->line_codepage = decode_codepage(linecharset); /* * If line_codepage is _still_ CS_NONE, we assume we're using diff --git a/window.c b/window.c index 793a5166..e5526629 100644 --- a/window.c +++ b/window.c @@ -686,8 +686,6 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) SetScrollInfo(hwnd, SB_VERT, &si, FALSE); } - start_backend(); - /* * Prepare the mouse handler. */ @@ -742,7 +740,7 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) } } - update_specials_menu(NULL); + start_backend(); /* * Set up the initial input locale.