Index: .fossil-settings/ignore-glob ================================================================== --- .fossil-settings/ignore-glob +++ .fossil-settings/ignore-glob @@ -4,10 +4,12 @@ *.GID/* *.RES *.RES/* *.RSA *.RSA/* +*.a +*.a/* *.cnt *.cnt/* *.dsp *.dsp/* *.dsw @@ -42,18 +44,22 @@ *.td2/* *.tds *.tds/* .bmake .bmake/* +.deps +.deps/* .svn/*.DSA .svn/*.DSA/* .svn/*.GID .svn/*.GID/* .svn/*.RES .svn/*.RES/* .svn/*.RSA .svn/*.RSA/* +.svn/*.a +.svn/*.a/* .svn/*.cnt .svn/*.cnt/* .svn/*.dsp .svn/*.dsp/* .svn/*.dsw @@ -88,26 +94,34 @@ .svn/*.td2/* .svn/*.tds .svn/*.tds/* .svn/.bmake .svn/.bmake/* +.svn/.deps +.svn/.deps/* .svn/MSVC .svn/MSVC/* +.svn/Makefile .svn/Makefile.bor .svn/Makefile.bor/* .svn/Makefile.cyg .svn/Makefile.cyg/* .svn/Makefile.lcc .svn/Makefile.lcc/* .svn/Makefile.vc .svn/Makefile.vc/* +.svn/Makefile/* .svn/Output .svn/Output/* .svn/build.log .svn/build.log/* .svn/build.out .svn/build.out/* +.svn/config.status +.svn/config.status/* +.svn/empty.h +.svn/empty.h/* .svn/local .svn/local/* .svn/pageant .svn/pageant/* .svn/plink @@ -114,34 +128,44 @@ .svn/plink/* .svn/pscp .svn/pscp/* .svn/psftp .svn/psftp/* +.svn/pterm +.svn/pterm/* .svn/putty .svn/putty/* .svn/puttygen .svn/puttygen/* .svn/puttytel .svn/puttytel/* +.svn/stamp-h1 +.svn/stamp-h1/* +.svn/uxconfig.h +.svn/uxconfig.h/* MSVC MSVC/* +Makefile Makefile.bor Makefile.bor/* Makefile.cyg Makefile.cyg/* Makefile.lcc Makefile.lcc/* Makefile.vc Makefile.vc/* +Makefile/* Output Output/* build.log build.log/* build.out build.out/* charset/sbcsdat.c charset/sbcsdat.c/* +config.status +config.status/* contrib/cygtermd/cygtermd.exe contrib/cygtermd/cygtermd.exe/* doc/*.1 doc/*.1/* doc/*.GID @@ -168,10 +192,12 @@ doc/*.log/* doc/*.txt doc/*.txt/* doc/vstr.but doc/vstr.but/* +empty.h +empty.h/* icons/*.c icons/*.c/* icons/*.ico icons/*.ico/* icons/*.png @@ -198,21 +224,29 @@ plink/* pscp pscp/* psftp psftp/* +pterm +pterm/* putty putty/* puttygen puttygen/* puttytel puttytel/* +stamp-h1 +stamp-h1/* testdata/bignum.txt testdata/bignum.txt/* unix/*.log unix/*.log/* +unix/.deps +unix/.deps/* unix/Makefile +unix/Makefile.am +unix/Makefile.am/* unix/Makefile.gtk unix/Makefile.gtk/* unix/Makefile.in unix/Makefile.in/* unix/Makefile.local @@ -222,18 +256,26 @@ unix/Makefile/* unix/aclocal.m4 unix/aclocal.m4/* unix/autom4te.cache unix/autom4te.cache/* +unix/compile +unix/compile/* unix/config.status unix/config.status/* unix/configure unix/configure/* +unix/depcomp +unix/depcomp/* +unix/empty.h +unix/empty.h/* unix/install-sh unix/install-sh/* unix/local unix/local/* +unix/missing +unix/missing/* unix/plink unix/plink/* unix/pscp unix/pscp/* unix/psftp @@ -244,14 +286,18 @@ unix/putty/* unix/puttygen unix/puttygen/* unix/puttytel unix/puttytel/* +unix/stamp-h1 +unix/stamp-h1/* unix/uxconfig.h unix/uxconfig.h/* unix/uxconfig.in unix/uxconfig.in/* +uxconfig.h +uxconfig.h/* windows/*.DSA windows/*.DSA/* windows/*.GID windows/*.GID/* windows/*.RES ADDED Buildscr.cv Index: Buildscr.cv ================================================================== --- /dev/null +++ Buildscr.cv @@ -0,0 +1,39 @@ +# -*- sh -*- + +# Build script to scan PuTTY with the downloadable Coverity scanner +# and generate a tar file to upload to their open-source scanning +# service. + +module putty + +# Preparations. +in putty do ./mkfiles.pl +in putty do ./mkauto.sh +in putty/doc do make + +# Scan the Unix build, on a 64-bit system to differentiate as much as +# possible from the other scan of the cross-platform files. +delegate covscan64 + in putty do ./configure + in putty do cov-build --dir cov-int make + in putty do tar czvf cov-int.tar.gz cov-int + return putty/cov-int.tar.gz +enddelegate + +# Scan the Windows build, by means of building with Winelib (since as +# of 2013-07-22, the Coverity Scan website doesn't offer a 32-bit +# Windows scanner for download). +delegate covscan32wine + in putty do tar xzvf cov-int.tar.gz + in putty/windows do cov-build --dir ../cov-int make -f Makefile.cyg CC=winegcc RC=wrc + in putty do tar czvf cov-int.tar.gz cov-int + return putty/cov-int.tar.gz +enddelegate + +# Provide the revision number as one of the build outputs, to make it +# easy to construct a curl upload command which will annotate it +# appropriately when uploaded. +in putty do echo $(revision) > revision.txt + +deliver putty/revision.txt $@ +deliver putty/cov-int.tar.gz $@ Index: CHECKLST.txt ================================================================== --- CHECKLST.txt +++ CHECKLST.txt @@ -175,14 +175,17 @@ - After running webupdate, run update-rsync on chiark and verify that the rsync mirror package (~/ftp/putty-website-mirror) contains a subdirectory for the new version and mentions it in its .htaccess. - Announce the release! - + Mail the announcement to . - * Put a 'Reply-To: putty@projects.tartarus.org' header on the - mail so that people don't keep replying to my personal - address. + + Construct a release announcement email whose message body is the + announcement written above, and which includes the following + headers: + * Reply-To: + * Subject: PuTTY X.YZ is released + + Mail that release announcement to + . + Post it to comp.security.ssh. + Mention it in on mono. - Relax (slightly). Index: LATEST.VER ================================================================== --- LATEST.VER +++ LATEST.VER @@ -1,1 +1,1 @@ -0.62 +0.63 Index: LICENCE ================================================================== --- LICENCE +++ LICENCE @@ -1,22 +1,12 @@ -PuTTY-CAC is Copyright 2009-2012 Daniel Risacher -PuTTY-CAC is available under the GPLv2 license. -PuTTY-CAC is a derivative work of PuTTY-SC. - -PuTTY-SC is Copyright 2005-2008 Pascal Buchbinder. -PuTTY-SC is available under the GPLv2 license. -PuTTY-SC is a derivative work of PuTTY. - -PuTTY is copyright 1997-2011 Simon Tatham. +PuTTY is copyright 1997-2013 Simon Tatham. Portions copyright Robert de Bath, Joris van Rantwijk, Delian Delchev, Andreas Schultz, Jeroen Massar, Wez Furlong, Nicolas Barry, Justin Bradford, Ben Harris, Malcolm Smith, Ahmad Khalifa, Markus Kuhn, Colin Watson, and CORE SDI S.A. -CAPI support donated by Andrew Prout. - Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, Index: README ================================================================== --- README +++ README @@ -38,11 +38,11 @@ - windows/Makefile.bor is for the Borland C compiler. Type `make -f Makefile.bor' while in the `windows' subdirectory to build all the PuTTY binaries. - - windows/Makefile.cyg is for Cygwin / mingw32 installations. Type + - windows/Makefile.cyg is for Cygwin / MinGW installations. Type `make -f Makefile.cyg' while in the `windows' subdirectory to build all the PuTTY binaries. You'll probably need quite a recent version of the w32api package. Note that by default the multiple monitor and HTML Help support are @@ -63,12 +63,16 @@ For building on Unix: - unix/configure is for Unix and GTK. If you don't have GTK, you should still be able to build the command-line utilities (PSCP, - PSFTP, Plink, PuTTYgen) using this script. To use it, change - into the `unix' subdirectory, run `./configure' and then `make'. + PSFTP, Plink, PuTTYgen) using this script. To use it, change into + the `unix' subdirectory, run `./configure' and then `make'. Or you + can do the same in the top-level directory (we provide a little + wrapper that invokes configure one level down), which is more like + a normal Unix source archive but doesn't do so well at keeping the + per-platform stuff in each platform's subdirectory; it's up to you. Note that Unix PuTTY has mostly only been tested on Linux so far; portability problems such as BSD-style ptys or different header file requirements are expected. @@ -78,26 +82,50 @@ Makefile.ux' respectively. Makefile.gtk builds all the programs but relies on Gtk, whereas Makefile.ux builds only the command-line utilities and has no Gtk dependence. - For the graphical utilities, Gtk+-1.2 and Gtk+-2.0 should both be - supported. + supported. If you have both installed, you can manually specify + which one you want by giving the option '--with-gtk=1' or + '--with-gtk=2' to the configure script. (2 is the default, of + course.) In the absence of either, the configure script will + automatically construct a Makefile which builds only the + command-line utilities; you can manually create this condition by + giving configure the option '--without-gtk'. + + - pterm would like to be setuid or setgid, as appropriate, to permit + it to write records of user logins to /var/run/utmp and + /var/log/wtmp. (Of course it will not use this privilege for + anything else, and in particular it will drop all privileges before + starting up complex subsystems like GTK.) By default the makefile + will not attempt to add privileges to the pterm executable at 'make + install' time, but you can ask it to do so by running configure + with the option '--enable-setuid=USER' or '--enable-setgid=GROUP'. + + - The Unix Makefiles have an `install' target. Note that by default + it tries to install `man' pages; if you have fetched the source via + Subversion then you will need to have built these using Halibut + first - see below. - - Both Unix Makefiles have an `install' target. Note that by default - it tries to install `man' pages, which you may need to have built - using Halibut first -- see below. + - It's also possible to build the Windows version of PuTTY to run + on Unix by using Winelib. To do this, change to the `windows' + directory and run `make -f Makefile.cyg CC=winegcc RC=wrc'. All of the Makefiles are generated automatically from the file -`Recipe' by the Perl script `mkfiles.pl'. Additions and corrections -to Recipe and the mkfiles.pl are much more useful than additions and -corrections to the alternative Makefiles themselves. +`Recipe' by the Perl script `mkfiles.pl' (except for the Unix one, +which is generated by the `configure' script; mkfiles.pl only +generates the input to automake). Additions and corrections to Recipe, +mkfiles.pl and/or configure.ac are much more useful than additions and +corrections to the actual Makefiles, Makefile.am or Makefile.in. The Unix `configure' script and its various requirements are generated by the shell script `mkauto.sh', which requires GNU Autoconf, GNU Automake, and Gtk; if you've got the source from Subversion rather than using one of our source snapshots, you'll need to run this -yourself. +yourself. The input file to Automake is generated by mkfiles.pl along +with all the rest of the makefiles, so you will need to run mkfiles.pl +and then mkauto.sh. Documentation (in various formats including Windows Help and Unix `man' pages) is built from the Halibut (`.but') files in the `doc' subdirectory using `doc/Makefile'. If you aren't using one of our source snapshots, you'll need to do this yourself. Halibut can be Index: Recipe ================================================================== --- Recipe +++ Recipe @@ -17,11 +17,11 @@ !makefile cygwin windows/Makefile.cyg !makefile borland windows/Makefile.bor !makefile lcc windows/Makefile.lcc !makefile gtk unix/Makefile.gtk !makefile unix unix/Makefile.ux -!makefile ac unix/Makefile.in +!makefile am unix/Makefile.am !makefile osx macosx/Makefile !makefile devcppproj windows/DEVCPP # Source directories. !srcdir charset/ !srcdir windows/ @@ -111,10 +111,15 @@ # Uses ASCII rather than Unicode to specify the tab control in # the resource file. Probably most useful when compiling with # Cygnus/mingw32, whose resource compiler may have less of a # problem with it. # +# - COMPAT=/DNO_SECUREZEROMEMORY (Windows only) +# Disables PuTTY's use of SecureZeroMemory(), which is missing +# from some environments' header files. This is enabled by +# default in the Cygwin Makefile. +# # - XFLAGS=/DTELNET_DEFAULT # Causes PuTTY to default to the Telnet protocol (in the absence # of Default Settings and so on to the contrary). Normally PuTTY # will default to SSH. # @@ -167,18 +172,34 @@ else \ $(CC) $(COMPAT) $(XFLAGS) $(CFLAGS) $(VER) -c ../version.c; \ fi !end !specialobj gtk version +# In the automake build, we have to do the whole job by supplying +# extra CFLAGS, so we have to put the if statement inside one big +# backtick expression. We also force rebuilding via a -D option that +# makes version.o include empty.h, which we construct ourselves and +# touch whenever any source file is updated. +!cflags am version $(VER) -DINCLUDE_EMPTY_H `if test -z "$(VER)" && (cd $(srcdir)/..; md5sum -c manifest >/dev/null 2>&1); then cat $(srcdir)/../version.def; else echo "$(VER)"; fi` +!begin am +BUILT_SOURCES = empty.h +empty.h: $(allsources) + echo '/* Empty file touched by automake makefile to force rebuild of version.o */' >$@ + +!end +!begin >empty.h +/* Empty file touched by automake makefile to force rebuild of version.o */ +!end # Add VER to Windows resource targets, and force them to be rebuilt every # time, on the assumption that they will contain version information. !begin vc vars CFLAGS = $(CFLAGS) /DHAS_GSSAPI /DSECURITY_WIN32 RCFLAGS = $(RCFLAGS) $(VER) !end !begin cygwin vars +CFLAGS += -DSECURITY_WIN32 # XXX GNU-ism, but it's probably all right for a Cygwin/MinGW Makefile. RCFLAGS += $(patsubst -D%,--define %,$(VER)) !end !begin borland vars # Borland doesn't support +=. This probably shouldn't work, but seems to. @@ -225,10 +246,25 @@ $(MAKE) install INSTALL_PROGRAM="$(INSTALL_PROGRAM) -s" !end !begin osx vars CFLAGS += -DMACOSX !end + +# List the man pages for the automake makefile. +!begin am +man1_MANS = ../doc/plink.1 ../doc/pscp.1 ../doc/psftp.1 ../doc/pterm.1 \ + ../doc/putty.1 ../doc/puttygen.1 ../doc/puttytel.1 +!end + +# In automake, chgrp/chmod pterm after installation, if configured to. +!begin am +if HAVE_SETID_CMD +install-exec-local: + @SETID_CMD@ $(bindir)/pterm + chmod @SETID_MODE@ $(bindir)/pterm +endif +!end # Random symbols. !begin cygwin vars # _WIN32_IE is required to expose identifiers that only make sense on # systems with IE5+ installed, such as some arguments to SHGetFolderPath(). @@ -243,18 +279,18 @@ # names. A line beginning `+' is assumed to continue the previous # line. # Terminal emulator and its (platform-independent) dependencies. TERMINAL = terminal wcwidth ldiscucs logging tree234 minibidi - + config dialog + + config dialog conf # GUI front end and terminal emulator (putty, puttytel). GUITERM = TERMINAL window windlg winctrls sizetip winucs winprint + winutils wincfg sercfg winhelp winjump # Same thing on Unix. -UXTERM = TERMINAL uxcfg sercfg uxucs uxprint timing +UXTERM = TERMINAL uxcfg sercfg uxucs uxprint timing callback GTKTERM = UXTERM gtkwin gtkcfg gtkdlg gtkfont gtkcols xkeysym OSXTERM = UXTERM osxwin osxdlg osxctrls # Non-SSH back ends (putty, puttytel, plink). NONSSH = telnet raw rlogin ldisc pinger @@ -261,22 +297,23 @@ # SSH back end (putty, plink, pscp, psftp). SSH = ssh sshcrc sshdes sshmd5 sshrsa sshrand sshsha sshblowf + sshdh sshcrcda sshpubk sshzlib sshdss x11fwd portfwd + sshaes sshsh256 sshsh512 sshbn wildcard pinger ssharcf - + sshgssc pgssapi -WINSSH = SSH winnoise winpgntc wingss -UXSSH = SSH uxnoise uxagentc uxgss + + sshgssc pgssapi sshshare +WINSSH = SSH winnoise winsecur winpgntc wingss winshare winnps winnpc + + winhsock errsock +UXSSH = SSH uxnoise uxagentc uxgss uxshare # SFTP implementation (pscp, psftp). SFTP = sftp int64 logging # Miscellaneous objects appearing in all the network utilities (not # Pageant or PuTTYgen). -MISC = timing misc version settings tree234 proxy +MISC = timing callback misc version settings tree234 proxy conf WINMISC = MISC winstore winnet winhandl cmdline windefs winmisc winproxy - + wintime + + wintime winhsock errsock UXMISC = MISC uxstore uxsel uxnet cmdline uxmisc uxproxy time OSXMISC = MISC uxstore uxsel osxsel uxnet uxmisc uxproxy time # Character set library, for use in pterm. CHARSET = sbcsdat slookup sbcs utf8 toucs fromucs xenc mimeenc macenc localenc @@ -305,24 +342,24 @@ # X/GTK Unix app, [U] for command-line Unix app. putty : [G] GUITERM NONSSH WINSSH W_BE_ALL WINMISC winx11 putty.res LIBS puttytel : [G] GUITERM NONSSH W_BE_NOSSH WINMISC puttytel.res nogss LIBS plink : [C] winplink wincons NONSSH WINSSH W_BE_ALL logging WINMISC - + winx11 plink.res winnojmp LIBS + + winx11 plink.res winnojmp noterm LIBS pscp : [C] pscp winsftp wincons WINSSH BE_SSH SFTP wildcard WINMISC + pscp.res winnojmp LIBS psftp : [C] psftp winsftp wincons WINSSH BE_SSH SFTP wildcard WINMISC + psftp.res winnojmp LIBS pageant : [G] winpgnt sshrsa sshpubk sshdes sshbn sshmd5 version tree234 - + misc sshaes sshsha winpgntc sshdss sshsh256 sshsh512 winutils - + winmisc winhelp pageant.res LIBS + + misc sshaes sshsha winsecur winpgntc sshdss sshsh256 sshsh512 + + winutils winmisc winhelp conf pageant.res LIBS puttygen : [G] winpgen sshrsag sshdssg sshprime sshdes sshbn sshmd5 version + sshrand winnoise sshsha winstore misc winctrls sshrsa sshdss winmisc + sshpubk sshaes sshsh256 sshsh512 import winutils puttygen.res - + tree234 notiming winhelp winnojmp LIBS wintime + + tree234 notiming winhelp winnojmp conf LIBS wintime pterm : [X] GTKTERM uxmisc misc ldisc settings uxpty uxsel BE_NONE uxstore + uxsignal CHARSET cmdline uxpterm version time xpmpterm xpmptcfg + nogss putty : [X] GTKTERM uxmisc misc ldisc settings uxsel U_BE_ALL uxstore @@ -331,17 +368,17 @@ puttytel : [X] GTKTERM uxmisc misc ldisc settings uxsel U_BE_NOSSH + uxstore uxsignal CHARSET uxputty NONSSH UXMISC xpmputty xpmpucfg + nogss plink : [U] uxplink uxcons NONSSH UXSSH U_BE_ALL logging UXMISC uxsignal - + ux_x11 + + ux_x11 noterm puttygen : [U] cmdgen sshrsag sshdssg sshprime sshdes sshbn sshmd5 version + sshrand uxnoise sshsha misc sshrsa sshdss uxcons uxstore uxmisc + sshpubk sshaes sshsh256 sshsh512 import puttygen.res time tree234 - + uxgen notiming + + uxgen notiming conf pscp : [U] pscp uxsftp uxcons UXSSH BE_SSH SFTP wildcard UXMISC psftp : [U] psftp uxsftp uxcons UXSSH BE_SSH SFTP wildcard UXMISC PuTTY : [MX] osxmain OSXTERM OSXMISC CHARSET U_BE_ALL NONSSH UXSSH + ux_x11 uxpty uxsignal testback putty.icns info.plist ADDED callback.c Index: callback.c ================================================================== --- /dev/null +++ callback.c @@ -0,0 +1,74 @@ +/* + * Facility for queueing callback functions to be run from the + * top-level event loop after the current top-level activity finishes. + */ + +#include + +#include "putty.h" + +struct callback { + struct callback *next; + + toplevel_callback_fn_t fn; + void *ctx; +}; + +struct callback *cbhead = NULL, *cbtail = NULL; + +toplevel_callback_notify_fn_t notify_frontend = NULL; +void *frontend = NULL; + +void request_callback_notifications(toplevel_callback_notify_fn_t fn, + void *fr) +{ + notify_frontend = fn; + frontend = fr; +} + +void queue_toplevel_callback(toplevel_callback_fn_t fn, void *ctx) +{ + struct callback *cb; + + cb = snew(struct callback); + cb->fn = fn; + cb->ctx = ctx; + + /* If the front end has requested notification of pending + * callbacks, and we didn't already have one queued, let it know + * we do have one now. */ + if (notify_frontend && !cbhead) + notify_frontend(frontend); + + if (cbtail) + cbtail->next = cb; + else + cbhead = cb; + cbtail = cb; + cb->next = NULL; +} + +void run_toplevel_callbacks(void) +{ + if (cbhead) { + struct callback *cb = cbhead; + /* + * Careful ordering here. We call the function _before_ + * advancing cbhead (though, of course, we must free cb + * _after_ advancing it). This means that if the very last + * callback schedules another callback, cbhead does not become + * NULL at any point, and so the frontend notification + * function won't be needlessly pestered. + */ + cb->fn(cb->ctx); + cbhead = cb->next; + sfree(cb); + if (!cbhead) + cbtail = NULL; + } +} + +int toplevel_callback_pending(void) +{ + return cbhead != NULL; +} DELETED capi.c Index: capi.c ================================================================== --- capi.c +++ /dev/null @@ -1,819 +0,0 @@ -/* - * CAPI: Windows Crypto API support file. - * Andrew Prout, aprout at ll mit edu - */ - -#include -#include -#include "capi.h" -#include "ssh.h" -#define SHA1_BYTES 20 - -typedef unsigned char uint8; -typedef signed char sint8; -typedef unsigned short uint16; -typedef signed short sint16; -//typedef unsigned long uint32; -typedef signed long sint32; - -#pragma comment(lib, "Crypt32.lib") -#pragma comment(lib, "Cryptui.lib") - -//#define DebugLog_FileName "c:\\blah\\putty-capi.log" -#ifdef DebugLog_FileName -void AsciiDumpBuffer(FILE* iStream, uint8* buf, uint32 size) { - uint32 x; - for (x=0; x= 32 && buf[x] <= 126) - fprintf(iStream, "%hc", buf[x]); - else - fprintf(iStream, "."); - } -} -void HexDumpBuffer(FILE* iStream, uint8* buf, uint32 size, char* newlinepad) { - uint32 x, tmp; - if (newlinepad) - fprintf(iStream, "%s", newlinepad); - for (x=0; x> 24); \ - (cp)[1] = (unsigned char)((value) >> 16); \ - (cp)[2] = (unsigned char)((value) >> 8); \ - (cp)[3] = (unsigned char)(value); } - -#define CAPI_BYTES_USED_IN_INT32(i) \ - (i & 0xFF000000 ? 4 : ( \ - i & 0x00FF0000 ? 3 : ( \ - i & 0x0000FF00 ? 2 : 1 \ - ) \ - )) - -uint8 GetCodeFromHex(const char iHex) { - if (iHex >= '0' && iHex <= '9') // numbers - return iHex - 48; - if (iHex >= 'A' && iHex <= 'F') // uppercase A-F - return iHex - 55; - if (iHex >= 'a' && iHex <= 'f') // lowercase a-f - return iHex - 87; - return 255; -} - -BOOL hextobytes(const char* iHex, uint8* oBytes) { - uint32 x = 0; - uint8 val; - while (iHex[x]) { - val = GetCodeFromHex(iHex[x]); - if (val >= 16) - return FALSE; - if (x % 2) - oBytes[x/2] |= val; - else - oBytes[x/2] = (val << 4); - x++; -#ifdef _DEBUG - if (x > 10000) - RaiseException(STATUS_BUFFER_OVERFLOW, EXCEPTION_NONCONTINUABLE, 0, 0); -#endif - } - return TRUE; -} - -struct ssh2_userkey capi_key_ssh2_userkey = { 0, 0, 0 }; - -struct CAPI_PUBKEY_BIT_BLOB_struct { - PUBLICKEYSTRUC publickeystruct; - RSAPUBKEY rsapubkey; -// BYTE modulus[0]; -}; - -BOOL capi_get_cert_handle(char* certID, PCCERT_CONTEXT* oCertContext) { - BOOL retval = FALSE; - PCCERT_CONTEXT pCertContext = NULL, pFindCertContext = NULL; - HCERTSTORE hStore = NULL; - CRYPT_HASH_BLOB chb = { 0, NULL }; - DWORD FoundCount = 0, dwStoreType, tmpSize; - char *LcertID = NULL, *LcertID_StoreType, *LcertID_StoreName, *LcertID_fingerprint; - - if (certID == NULL || oCertContext == NULL) { - debuglog("capi_get_cert_handle: input parameter is NULL that cannot be\n"); - return FALSE; // no goto cleanup, it'll crash - } - - if ((LcertID = malloc(strlen(certID) + 1)) == NULL) { - debuglog("capi_get_cert_handle: malloc for LcertID failed\n"); - goto cleanup; - } - strcpy(LcertID, certID); - - LcertID_StoreType = strtok(LcertID, "\\"); - LcertID_StoreName = strtok(NULL, "\\"); - LcertID_fingerprint = strtok(NULL, "\\"); - if (LcertID_StoreType == NULL || LcertID_StoreName == NULL || LcertID_fingerprint == NULL) { - debuglog("capi_get_cert_handle: strtok(LcertID) failed\n"); - goto cleanup; - } - - if (strcmp(LcertID_StoreType, "User") == 0) - dwStoreType = CERT_SYSTEM_STORE_CURRENT_USER; - else if (strcmp(LcertID_StoreType, "System") == 0) - dwStoreType = CERT_SYSTEM_STORE_LOCAL_MACHINE; - else { - debuglog("capi_get_cert_handle: Unknown store type\n"); - goto cleanup; - } - - if (strlen(LcertID_fingerprint) != (SHA1_BYTES * 2)) { - debuglog("capi_get_cert_handle: strlen(LcertID_fingerprint) != (SHA1_BYTES * 2)\n"); - goto cleanup; - } - - chb.cbData = SHA1_BYTES; - if ((chb.pbData = (BYTE*) malloc(SHA1_BYTES)) == NULL) { - debuglog("capi_get_cert_handle: malloc for chb.pbData failed\n"); - goto cleanup; - } - if (!hextobytes(LcertID_fingerprint, chb.pbData)) { - debuglog("capi_get_cert_handle: hextobytes(LcertID_fingerprint) failed\n"); - goto cleanup; - } - - if((hStore = CertOpenStore(CERT_STORE_PROV_SYSTEM_A, PKCS_7_ASN_ENCODING | X509_ASN_ENCODING, 0 /*hCryptProv*/, dwStoreType | CERT_STORE_READONLY_FLAG | CERT_STORE_OPEN_EXISTING_FLAG | CERT_STORE_ENUM_ARCHIVED_FLAG, LcertID_StoreName)) == NULL) { - debuglog("capi_get_cert_handle: CertOpenStore(%d, %s) failed\n", dwStoreType, LcertID_StoreName); - goto cleanup; - } - - while (pFindCertContext = CertFindCertificateInStore(hStore, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 0, CERT_FIND_SHA1_HASH, &chb, pFindCertContext)) { - debuglog("capi_get_cert_handle: found a cert, checking for private key...\n"); - tmpSize = 0; - if (CertGetCertificateContextProperty(pFindCertContext, CERT_KEY_PROV_INFO_PROP_ID, NULL, &tmpSize)) { - debuglog("capi_get_cert_handle: got a private key duplicating context...\n"); - if (pCertContext == NULL) - pCertContext = CertDuplicateCertificateContext(pFindCertContext); - FoundCount++; - debuglog("capi_get_cert_handle: All set\n"); - } - else { - // no private key, ignore the cert - } - } - - if (FoundCount != 1) { - debuglog("capi_get_cert_handle: FoundCount != 1. FoundCount=%d\n", FoundCount); - goto cleanup; - } - - if (strcmp(pCertContext->pCertInfo->SubjectPublicKeyInfo.Algorithm.pszObjId, szOID_RSA_RSA) != 0) { - // Not an RSA key? egads, bail out... - debuglog("capi_get_cert_handle: Not an RSA key?\n"); - debuglog("pCertContext->pCertInfo->SubjectPublicKeyInfo.Algorithm.pszObjId=%s\n", pCertContext->pCertInfo->SubjectPublicKeyInfo.Algorithm.pszObjId); - goto cleanup; - } - - *oCertContext = pCertContext; - pCertContext = NULL; // to avoid the free in cleanup; - - retval = TRUE; -cleanup: - if (chb.pbData) - free(chb.pbData); - chb.pbData = NULL; - - if (LcertID) - free(LcertID); - LcertID = NULL; - - if (pCertContext) - CertFreeCertificateContext(pCertContext); - pCertContext = NULL; - - return retval; -} - -BOOL capi_display_cert_ui(HWND hwnd, char* certID, WCHAR* title) { - BOOL retval = FALSE; - PCCERT_CONTEXT pCertContext = NULL; - - if (!capi_get_cert_handle(certID, &pCertContext)) { - debuglog("capi_display_cert_ui: capi_get_cert_handle failed\n"); - goto cleanup; - } - - if (!CryptUIDlgViewContext(CERT_STORE_CERTIFICATE_CONTEXT, pCertContext, hwnd, title, 0, NULL)) { - debuglog("capi_display_cert_ui: CryptUIDlgViewContext failed\n"); - goto cleanup; - } - - retval = TRUE; - -cleanup: - if (pCertContext) - CertFreeCertificateContext(pCertContext); - pCertContext = NULL; - - return retval; -} - -BOOL capi_get_pubkey_blob(PCCERT_CONTEXT pCertContext, unsigned char** pubkey, int *blob_len) { - BOOL retval = FALSE; - DWORD tmpSize, mbits, mbytes, ebytes; - int i; // signed, for loop goes to -1 - unsigned char *p = NULL;//, *modu = NULL; - struct CAPI_PUBKEY_BIT_BLOB_struct* capi_pubkey = NULL; - Bignum modu = NULL; - - if (pubkey == NULL || blob_len == NULL) { - debuglog("capi_get_pubkey: input parameter is NULL that cannot be\n"); - return FALSE; // no goto cleanup, it'll crash - } - - if ((mbits = CertGetPublicKeyLength(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, &pCertContext->pCertInfo->SubjectPublicKeyInfo)) == 0) { - debuglog("capi_get_pubkey: CertGetPublicKeyLength failed\n"); - goto cleanup; - } - debuglog("capi_get_pubkey: mbits=%d\n", mbits); - - tmpSize = 0; - if (!CryptDecodeObject(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, RSA_CSP_PUBLICKEYBLOB, pCertContext->pCertInfo->SubjectPublicKeyInfo.PublicKey.pbData, pCertContext->pCertInfo->SubjectPublicKeyInfo.PublicKey.cbData, 0, NULL, &tmpSize)) { - debuglog("capi_get_pubkey: CryptDecodeObject[1] failed\n"); - goto cleanup; - } - capi_pubkey = malloc(tmpSize); - if (!CryptDecodeObject(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, RSA_CSP_PUBLICKEYBLOB, pCertContext->pCertInfo->SubjectPublicKeyInfo.PublicKey.pbData, pCertContext->pCertInfo->SubjectPublicKeyInfo.PublicKey.cbData, 0, capi_pubkey, &tmpSize)) { - debuglog("capi_get_pubkey: CryptDecodeObject[2] failed\n"); - goto cleanup; - } - - // the below formula for the format is taken from sc.c... I agree with the comments there, it's ugly. - -// ebytes = (ebits/8) + ((ebits % 8) ? 1 : 0); - ebytes = CAPI_BYTES_USED_IN_INT32(capi_pubkey->rsapubkey.pubexp); - mbytes = (mbits/8) + ((mbits % 8) ? 1 : 0); - - // offset by sizeof(*capi_pubkey) bytes to the public key... - //modu = ((unsigned char*) capi_pubkey) + sizeof(*capi_pubkey); - modu = bignum_from_bytes(((unsigned char*) capi_pubkey) + sizeof(*capi_pubkey), mbytes); - - debuglog("capi_get_pubkey_int: tmpSize=%d, capi_pubkey=:\n", tmpSize); - debuglog_buffer(capi_pubkey, tmpSize); - - *blob_len = 4+7+4+ebytes+4+(1+mbytes); // mbytes has a leading zero - if ((p = *pubkey = (unsigned char*) malloc(*blob_len)) == NULL) { - debuglog("capi_get_pubkey: malloc for *pubkey failed\n"); - goto cleanup; - } - - CAPI_PUT_32BIT(p, 7); - p += 4; - - memcpy(p, "ssh-rsa", 7); - p += 7; - - CAPI_PUT_32BIT(p, ebytes); - p += 4; - - debuglog("capi_get_pubkey_int: ebytes=%d, capi_pubkey->rsapubkey.pubexp=%d (0x%08x), mbytes=%d, modu=:\n", ebytes, capi_pubkey->rsapubkey.pubexp, capi_pubkey->rsapubkey.pubexp, mbytes); - debuglog_buffer(modu, mbytes); - - for (i=ebytes-1; i>=0; i--) - *p++ = (unsigned char) ((capi_pubkey->rsapubkey.pubexp & (0xFF << (i*8))) >> (i*8)); - - CAPI_PUT_32BIT(p, mbytes + 1); // add room for a leading zero - p += 4; - - *p++ = 0; // leading zero - for (i = 0; i < (int) mbytes; i++) - *p++ = bignum_byte(modu, i); - - retval = TRUE; -cleanup: - if (modu) - freebn(modu); - - if (capi_pubkey) - free(capi_pubkey); - capi_pubkey = NULL; - - if (!retval) { - if (pubkey) { - if (*pubkey) - free(*pubkey); - *pubkey = NULL; - } - if (blob_len) - *blob_len = 0; - } - - return retval; -} - -BOOL capi_get_pubkey_int(void *f /*frontend*/, char* certID, unsigned char** pubkey, char **algorithm, int *blob_len, PCCERT_CONTEXT* oCertContext) { - BOOL retval = FALSE; - PCCERT_CONTEXT pCertContext = NULL; - - if (certID == NULL || pubkey == NULL || algorithm == NULL || blob_len == NULL) { - debuglog("capi_get_pubkey: input parameter is NULL that cannot be\n"); - return FALSE; // no goto cleanup, it'll crash - } - *pubkey = NULL; - *algorithm = NULL; - *blob_len = 0; - // goto cleanup now OK - - if ((*algorithm = calloc(sizeof(char *), strlen("ssh-rsa")+1)) == NULL) { - debuglog("capi_get_pubkey: calloc for *algorithm failed\n"); - goto cleanup; - } - strcpy(*algorithm, "ssh-rsa"); - - if (!capi_get_cert_handle(certID, &pCertContext)) { - debuglog("capi_get_pubkey: capi_get_cert_handle failed\n"); - goto cleanup; - } - - if (!capi_get_pubkey_blob(pCertContext, pubkey, blob_len)) { - debuglog("capi_get_pubkey_int: capi_get_pubkey_blob failed\n"); - goto cleanup; - } - - if (oCertContext) { - *oCertContext = pCertContext; - pCertContext = NULL; // to avoid the free in cleanup; - } - retval = TRUE; -cleanup: - if (!retval) { - if (pubkey) { - if (*pubkey) - free(*pubkey); - *pubkey = NULL; - } - if (algorithm) { - if (*algorithm) - free(*algorithm); - *algorithm = NULL; - } - if (blob_len) - *blob_len = 0; - } - - return retval; -} -BOOL capi_get_pubkey(void *f, char* certID, unsigned char** pubkey, char **algorithm, int *blob_len) { - return capi_get_pubkey_int(f, certID, pubkey, algorithm, blob_len, NULL); -} - -char *capi_base64key(char *data, int len) { - int bi, bn; - char out[4]; - int datalen = len; - char *buffi = calloc(len + len, sizeof(char *)); - int buffi_pos = 0; - for(bi=0;bi<(len + len); bi++) buffi[bi] = '\0'; - while (datalen > 0) { - bn = (datalen < 3 ? datalen : 3); - base64_encode_atom(data, bn, out); - data += bn; - datalen -= bn; - for (bi = 0; bi < 4; bi++) { - buffi[buffi_pos] = out[bi]; - buffi_pos++; - } - } - return buffi; -} - -char* capi_get_key_string(char* certID) { - unsigned char *pubkey, *algorithm; - int pubkey_len; - char *key64 = NULL, *keystring = NULL; - - if (!capi_get_pubkey(NULL, certID, &pubkey, &algorithm, &pubkey_len)) { - debuglog("capi_get_key_string: capi_get_pubkey failed\n"); - goto cleanup; - } - debuglog("capi_get_key_string: Got pubkey. algorithm=%s. pubkey_len=%d. pubkey=:\n", algorithm, pubkey_len); - debuglog_buffer(pubkey, pubkey_len); - if ((key64 = capi_base64key(pubkey, pubkey_len)) == NULL) { - debuglog("capi_get_key_string: capi_get_pubkey failed\n"); - goto cleanup; - } - if ((keystring = calloc(1, strlen("ssh-rsa")+1+strlen(key64)+1+strlen("CAPI:")+strlen(certID) + 1)) == NULL) { - debuglog("capi_get_key_string: capi_get_pubkey failed\n"); - goto cleanup; - } - sprintf(keystring, "ssh-rsa %s CAPI:%s", key64, certID); -cleanup: - return keystring; -} - -BOOL capi_get_key_handle(void *f, char* certID, struct capi_keyhandle_struct** keyhandle) { - BOOL retval = FALSE; - struct capi_keyhandle_struct* newkeyhandle = NULL; - PCCERT_CONTEXT pCertContext = NULL; - HCRYPTKEY privkey = 0; - DWORD ckpi_size; - CRYPT_KEY_PROV_INFO* ckpi = NULL; - - if (certID == NULL || keyhandle == NULL) { - debuglog("capi_get_pubkey: input parameter is NULL that cannot be\n"); - return FALSE; // no goto cleanup, it'll crash - } - *keyhandle = NULL; - // goto cleanup now OK - - if ((newkeyhandle = calloc(1, sizeof(struct capi_keyhandle_struct))) == NULL) - goto cleanup; - - if (!capi_get_pubkey_int(f, certID, &newkeyhandle->pubkey, &newkeyhandle->algorithm, &newkeyhandle->pubkey_len, &pCertContext)) - goto cleanup; - - ckpi_size = 0; - if (!CertGetCertificateContextProperty(pCertContext, CERT_KEY_PROV_INFO_PROP_ID, NULL, &ckpi_size)) - goto cleanup; - if ((ckpi = (CRYPT_KEY_PROV_INFO*) malloc(ckpi_size)) == NULL) - goto cleanup; - if (!CertGetCertificateContextProperty(pCertContext, CERT_KEY_PROV_INFO_PROP_ID, ckpi, &ckpi_size)) - goto cleanup; - if (ckpi->dwProvType == 0) { // CNG - // eh, later... -debuglog("capi_get_key_handle: CNG Key, bailing...\n"); - goto cleanup; - } - else { // CAPI - if (!CryptAcquireContextW((HCRYPTPROV*) &newkeyhandle->win_provider, ckpi->pwszContainerName, ckpi->pwszProvName, ckpi->dwProvType, ((ckpi->dwFlags & CRYPT_MACHINE_KEYSET) ? CRYPT_MACHINE_KEYSET : 0) )) { - debuglog("capi_get_key_handle: Error calling CryptAcquireContext. GetLastError()=%i (0x%08x)\n", GetLastError(), GetLastError()); - goto cleanup; - } - newkeyhandle->win_keyspec = ckpi->dwKeySpec; - } - - *keyhandle = newkeyhandle; - retval = TRUE; -cleanup: - if (pCertContext) - CertFreeCertificateContext(pCertContext); - pCertContext = NULL; - - if (ckpi) - free(ckpi); - ckpi = NULL; - - if (!retval) { - if (newkeyhandle) { - if (newkeyhandle->win_provider) - CryptReleaseContext((HCRYPTPROV) newkeyhandle->win_provider, 0); - newkeyhandle->win_provider = NULL; - - if (newkeyhandle->pubkey) - free(newkeyhandle->pubkey); - newkeyhandle->pubkey = NULL; - - if (newkeyhandle->algorithm) - free(newkeyhandle->algorithm); - newkeyhandle->algorithm = NULL; - - free(newkeyhandle); - } - newkeyhandle = NULL; - } - - return retval; -} - -unsigned char* capi_sig_certID(char* certID, char *sigdata, int sigdata_len, int *sigblob_len) { - BOOL success = FALSE; - unsigned char* retval = NULL, *rawsig = NULL, *p; - HCRYPTHASH hash = 0; - DWORD ckpi_size, tmpSize, tmpHashLen, rawsig_len, x; - Bignum bn = NULL; - HCRYPTPROV hProv = 0; - CRYPT_KEY_PROV_INFO* ckpi = NULL; - PCCERT_CONTEXT pCertContext = NULL; - - debuglog("capi_sig_certID called. sigdata_len=%d\n", sigdata_len); - debuglog_buffer(sigdata, sigdata_len); - - // Find cert context from certID - if (!capi_get_cert_handle(certID, &pCertContext)) { - debuglog("capi_sig: capi_get_cert_handle failed\n"); - goto cleanup; - } - - // find the key info from the cert and get a handle - if (!CertGetCertificateContextProperty(pCertContext, CERT_KEY_PROV_INFO_PROP_ID, NULL, &ckpi_size)) - goto cleanup; - if ((ckpi = (CRYPT_KEY_PROV_INFO*) malloc(ckpi_size)) == NULL) - goto cleanup; - if (!CertGetCertificateContextProperty(pCertContext, CERT_KEY_PROV_INFO_PROP_ID, ckpi, &ckpi_size)) - goto cleanup; - if (ckpi->dwProvType == 0) { // CNG - // eh, later... -debuglog("capi_sig_certID: CNG Key, bailing...\n"); - goto cleanup; - } - else { // CAPI - if (!CryptAcquireContextW(&hProv, ckpi->pwszContainerName, ckpi->pwszProvName, ckpi->dwProvType, ((ckpi->dwFlags & CRYPT_MACHINE_KEYSET) ? CRYPT_MACHINE_KEYSET : 0) )) { - debuglog("capi_sig_certID: Error calling CryptAcquireContext. GetLastError()=%i (0x%08x)\n", GetLastError(), GetLastError()); - goto cleanup; - } - } - - // create the hash object, set it to SHA-1 and confirm expectations - if (!CryptCreateHash(hProv, CALG_SHA1, 0, 0, &hash)) - goto cleanup; - tmpSize = sizeof(tmpHashLen); - if (!CryptGetHashParam(hash, HP_HASHSIZE, (BYTE *) &tmpHashLen, &tmpSize, 0)) - goto cleanup; - if (tmpHashLen != SHA1_BYTES) - goto cleanup; - - if (!CryptHashData(hash, sigdata, sigdata_len, 0)) - goto cleanup; - - // hash & sign - rawsig_len = 0; - if (!CryptSignHash(hash, ckpi->dwKeySpec, NULL, 0, NULL, &rawsig_len)) - goto cleanup; - - rawsig = malloc(rawsig_len); - if (!CryptSignHash(hash, ckpi->dwKeySpec, NULL, 0, rawsig, &rawsig_len)) - goto cleanup; - - // convert to SSH-style buffer - bn = bignum_from_bytes(rawsig, rawsig_len); - tmpSize = (bignum_bitcount(bn) + 7) / 8; - *sigblob_len = 4 + 7 + 4 + tmpSize; - if ((p = retval = calloc(1, *sigblob_len)) == NULL) - goto cleanup; - - CAPI_PUT_32BIT(p, 7); - p += 4; - - memcpy(p, "ssh-rsa", 7); - p += 7; - - CAPI_PUT_32BIT(p, tmpSize); - p += 4; - - for (x = 0; x < tmpSize; x++) - *p++ = bignum_byte(bn, x); - - success = TRUE; -cleanup: - if (pCertContext) - CertFreeCertificateContext(pCertContext); - pCertContext = NULL; - - if (hProv) - CryptReleaseContext(hProv, 0); - hProv = 0; - - if (ckpi) - free(ckpi); - ckpi = NULL; - - if (hash) - CryptDestroyHash(hash); - hash = 0; - - if (rawsig) - free(rawsig); - rawsig = NULL; - - if (bn) - freebn(bn); - bn = NULL; - - if (!success) { - if (retval) - free(retval); - retval = NULL; - } - return retval; -} - -unsigned char* capi_sig(struct capi_keyhandle_struct* keyhandle, char *sigdata, int sigdata_len, int *sigblob_len) { - BOOL success = FALSE; - unsigned char* retval = NULL, *rawsig = NULL, *p; - HCRYPTHASH hash = 0; - DWORD tmpSize, tmpHashLen, rawsig_len, x; - Bignum bn = NULL; - - debuglog("capi_sig(keyhandle) called. sigdata_len=%d\n", sigdata_len); - debuglog_buffer(sigdata, sigdata_len); - - if (!CryptCreateHash((HCRYPTPROV) keyhandle->win_provider, CALG_SHA1, 0, 0, &hash)) - goto cleanup; - tmpSize = sizeof(tmpHashLen); - if (!CryptGetHashParam(hash, HP_HASHSIZE, (BYTE *) &tmpHashLen, &tmpSize, 0)) - goto cleanup; - if (tmpHashLen != SHA1_BYTES) - goto cleanup; - - if (!CryptHashData(hash, sigdata, sigdata_len, 0)) - goto cleanup; - - rawsig_len = 0; - if (!CryptSignHash(hash, keyhandle->win_keyspec, NULL, 0, NULL, &rawsig_len)) - goto cleanup; - - rawsig = malloc(rawsig_len); - if (!CryptSignHash(hash, keyhandle->win_keyspec, NULL, 0, rawsig, &rawsig_len)) - goto cleanup; - - bn = bignum_from_bytes(rawsig, rawsig_len); - tmpSize = (bignum_bitcount(bn) + 7) / 8; - *sigblob_len = 4 + 7 + 4 + tmpSize; - if ((p = retval = calloc(1, *sigblob_len)) == NULL) - goto cleanup; - - CAPI_PUT_32BIT(p, 7); - p += 4; - - memcpy(p, "ssh-rsa", 7); - p += 7; - - CAPI_PUT_32BIT(p, tmpSize); - p += 4; - - for (x = 0; x < tmpSize; x++) - *p++ = bignum_byte(bn, x); - - success = TRUE; -cleanup: - if (hash) - CryptDestroyHash(hash); - hash = 0; - - if (rawsig) - free(rawsig); - rawsig = NULL; - - if (bn) - freebn(bn); - bn = NULL; - - if (!success) { - if (retval) - free(retval); - retval = NULL; - } - return retval; -} - -void capi_release_key(struct capi_keyhandle_struct** keyhandle) { - if (keyhandle) { - if (*keyhandle) { - if ((*keyhandle)->win_provider) - CryptReleaseContext((HCRYPTPROV) (*keyhandle)->win_provider, 0); - (*keyhandle)->win_provider = NULL; - - if ((*keyhandle)->pubkey) - free((*keyhandle)->pubkey); - (*keyhandle)->pubkey = NULL; - - if ((*keyhandle)->algorithm) - free((*keyhandle)->algorithm); - (*keyhandle)->algorithm = NULL; - - free(*keyhandle); - } - *keyhandle = NULL; - } - return; -} - -struct CAPI_userkey* Create_CAPI_userkey(const char* certID, PCERT_CONTEXT pCertContext) { - BOOL success = FALSE; - struct CAPI_userkey* retval = NULL; - PCERT_CONTEXT LpCertContext = NULL; - - if (pCertContext == NULL) { - if (!capi_get_cert_handle(certID, &LpCertContext)) { - debuglog("Create_CAPI_userkey: capi_get_cert_handle failed\n"); - goto cleanup; - } - pCertContext = LpCertContext; - } - - if ((retval = malloc(sizeof(struct CAPI_userkey))) == NULL) { - debuglog("Create_CAPI_userkey: malloc for retval failed\n"); - goto cleanup; - } - retval->certID = NULL; - retval->blob = NULL; - - if ((retval->certID = malloc(strlen(certID) + 1)) == NULL) { - debuglog("Create_CAPI_userkey: malloc for certID failed\n"); - goto cleanup; - } - strcpy(retval->certID, certID); - - if (!capi_get_pubkey_blob(pCertContext, &retval->blob, &retval->bloblen)) { - debuglog("Create_CAPI_userkey: capi_get_pubkey_blob failed\n"); - goto cleanup; - } - - success = TRUE; -cleanup: - if (LpCertContext) - CertFreeCertificateContext(LpCertContext); - LpCertContext = NULL; - - if (!success) { - Free_CAPI_userkey(retval); - retval = NULL; - } - return retval; -} -void Free_CAPI_userkey(struct CAPI_userkey* ckey) { - if (ckey->certID) - free(ckey->certID); - ckey->certID = NULL; - - if (ckey->blob) - free(ckey->blob); - ckey->blob = NULL; - - free(ckey); -} - -char* CAPI_userkey_GetComment(struct CAPI_userkey* ckey) { - char *retval = NULL; - - debuglog("CAPI_userkey_GetComment: called\n"); - if ((retval = malloc(5 + strlen(ckey->certID) + 1)) == NULL) { - debuglog("CAPI_userkey_GetComment: malloc failed\n"); - return NULL; - } - sprintf(retval, "CAPI:%s", ckey->certID); - return retval; -} - - - DELETED capi.h Index: capi.h ================================================================== --- capi.h +++ /dev/null @@ -1,43 +0,0 @@ -/* - * CAPI: Windows Crypto API header file. - * Andrew Prout, aprout at ll mit edu - */ - -#ifndef PUTTY_CAPI_H -#define PUTTY_CAPI_H - -#ifdef _WINDOWS - -struct capi_keyhandle_struct { - void* win_provider; - char* algorithm; - unsigned char* pubkey; - unsigned int pubkey_len; - unsigned int win_keyspec; -}; - -struct CAPI_userkey { - char *certID; // StoreType\StoreName\HexSHA1 - unsigned char *blob; - int bloblen; -}; -#define CAPI_userkey_Comment_Length(x) (strlen(x->certID) + 5 /* "CAPI:" */) - -extern struct ssh2_userkey capi_key_ssh2_userkey; - -BOOL capi_get_pubkey(void *f, char* certID, unsigned char** pubkey, char **algorithm, int *blob_len); -BOOL capi_get_key_handle(void *f, char* certID, struct capi_keyhandle_struct** keyhandle); -BOOL capi_display_cert_ui(HWND hwnd, char* certID, WCHAR* title); -//BOOL capi_get_cert_handle(char* certID, PCCERT_CONTEXT* oCertContext); -unsigned char* capi_sig(struct capi_keyhandle_struct* keyhandle, char *sigdata, int sigdata_len, int *sigblob_len); -unsigned char* capi_sig_certID(char* certID, char *sigdata, int sigdata_len, int *sigblob_len); -void capi_release_key(struct capi_keyhandle_struct** keyhandle); -char* capi_get_key_string(char* certID); -char* CAPI_userkey_GetComment(struct CAPI_userkey* ckey); - -struct CAPI_userkey* Create_CAPI_userkey(const char* certID, CERT_CONTEXT* pCertContext); -void Free_CAPI_userkey(struct CAPI_userkey* ckey); - -#endif //#ifdef _WINDOWS - -#endif //#ifndef PUTTY_CAPI_H Index: charset/charset.h ================================================================== --- charset/charset.h +++ charset/charset.h @@ -30,10 +30,11 @@ CS_ISO8859_14, CS_ISO8859_15, CS_ISO8859_16, CS_CP437, CS_CP850, + CS_CP852, CS_CP866, CS_CP1250, CS_CP1251, CS_CP1252, CS_CP1253, @@ -96,11 +97,12 @@ * NULL, `errlen' will be ignored, and the library will choose * something sensible to do on its own. For Unicode, this will be * U+FFFD (REPLACEMENT CHARACTER). */ -int charset_to_unicode(char **input, int *inlen, wchar_t *output, int outlen, +int charset_to_unicode(const char **input, int *inlen, + wchar_t *output, int outlen, int charset, charset_state *state, const wchar_t *errstr, int errlen); /* * Routine to convert Unicode to an MB/SB character set. @@ -119,11 +121,12 @@ * `errlen' will be ignored, and the library will choose something * sensible to do on its own (which will vary depending on the * output charset). */ -int charset_from_unicode(wchar_t **input, int *inlen, char *output, int outlen, +int charset_from_unicode(const wchar_t **input, int *inlen, + char *output, int outlen, int charset, charset_state *state, const char *errstr, int errlen); /* * Convert X11 encoding names to and from our charset identifiers. Index: charset/fromucs.c ================================================================== --- charset/fromucs.c +++ charset/fromucs.c @@ -38,11 +38,12 @@ } else { param->stopped = 1; } } -int charset_from_unicode(wchar_t **input, int *inlen, char *output, int outlen, +int charset_from_unicode(const wchar_t **input, int *inlen, + char *output, int outlen, int charset, charset_state *state, const char *errstr, int errlen) { charset_spec const *spec = charset_find_spec(charset); charset_state localstate; Index: charset/localenc.c ================================================================== --- charset/localenc.c +++ charset/localenc.c @@ -19,10 +19,11 @@ const char *name; int charset; int return_in_enum; /* enumeration misses some charsets */ } localencs[] = { { "", CS_NONE, 0 }, + { "UTF-8", CS_UTF8, 1 }, { "ISO-8859-1", CS_ISO8859_1, 1 }, { "ISO-8859-1 with X11 line drawing", CS_ISO8859_1_X11, 0 }, { "ISO-8859-2", CS_ISO8859_2, 1 }, { "ISO-8859-3", CS_ISO8859_3, 1 }, { "ISO-8859-4", CS_ISO8859_4, 1 }, @@ -37,10 +38,11 @@ { "ISO-8859-14", CS_ISO8859_14, 1 }, { "ISO-8859-15", CS_ISO8859_15, 1 }, { "ISO-8859-16", CS_ISO8859_16, 1 }, { "CP437", CS_CP437, 1 }, { "CP850", CS_CP850, 1 }, + { "CP852", CS_CP852, 1 }, { "CP866", CS_CP866, 1 }, { "CP1250", CS_CP1250, 1 }, { "CP1251", CS_CP1251, 1 }, { "CP1252", CS_CP1252, 1 }, { "CP1253", CS_CP1253, 1 }, @@ -72,11 +74,10 @@ { "Mac VT100", CS_MAC_VT100, 1 }, { "Mac VT100 (old)", CS_MAC_VT100_OLD, 0 }, { "VISCII", CS_VISCII, 1 }, { "HP ROMAN8", CS_HP_ROMAN8, 1 }, { "DEC MCS", CS_DEC_MCS, 1 }, - { "UTF-8", CS_UTF8, 1 }, }; const char *charset_to_localenc(int charset) { int i; Index: charset/mimeenc.c ================================================================== --- charset/mimeenc.c +++ charset/mimeenc.c @@ -133,10 +133,15 @@ { "IBM850", CS_CP850 }, { "cp850", CS_CP850 }, { "850", CS_CP850 }, { "csPC850Multilingual", CS_CP850 }, + { "IBM852", CS_CP852 }, + { "cp852", CS_CP852 }, + { "852", CS_CP852 }, + { "csIBM852", CS_CP852 }, + { "IBM866", CS_CP866 }, { "cp866", CS_CP866 }, { "866", CS_CP866 }, { "csIBM866", CS_CP866 }, Index: charset/sbcs.dat ================================================================== --- charset/sbcs.dat +++ charset/sbcs.dat @@ -392,10 +392,32 @@ 2514 2534 252c 251c 2500 253c 255e 255f 255a 2554 2569 2566 2560 2550 256c 2567 2568 2564 2565 2559 2558 2552 2553 256b 256a 2518 250c 2588 2584 258c 2590 2580 0440 0441 0442 0443 0444 0445 0446 0447 0448 0449 044a 044b 044c 044d 044e 044f 0401 0451 0404 0454 0407 0457 040e 045e 00b0 2219 00b7 221a 2116 00a4 25a0 00a0 + Another old DOS code page, submitted by a user and checked against + the translation table at + http://msdn.microsoft.com/en-us/goglobal/cc305161.aspx . + +charset CS_CP852 +0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f +0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f +0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f +0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f +0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f +0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f +0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f +0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f +00c7 00fc 00e9 00e2 00e4 016f 0107 00e7 0142 00eb 0150 0151 00ee 0179 00c4 0106 +00c9 0139 013a 00f4 00f6 013d 013e 015a 015b 00d6 00dc 0164 0165 0141 00d7 010d +00e1 00ed 00f3 00fa 0104 0105 017d 017e 0118 0119 00ac 017a 010c 015f 00ab 00bb +2591 2592 2593 2502 2524 00c1 00c2 011a 015e 2563 2551 2557 255d 017b 017c 2510 +2514 2534 252c 251c 2500 253c 0102 0103 255a 2554 2569 2566 2560 2550 256c 00a4 +0111 0110 010e 00cb 010f 0147 00cd 00ce 011b 2518 250c 2588 2584 0162 016e 2580 +00d3 00df 00d4 0143 0144 0148 0160 0161 0154 00da 0155 0170 00fd 00dd 0163 00b4 +00ad 02dd 02db 02c7 02d8 00a7 00f7 00b8 00b0 00a8 02d9 0171 0158 0159 25a0 00a0 + Here are some Windows code pages, generated by this piece of Bourne shell: for i in 1250 1251 1252 1253 1254 1255 1256 1257 1258; do echo charset CS_CP$i Index: charset/toucs.c ================================================================== --- charset/toucs.c +++ charset/toucs.c @@ -44,11 +44,12 @@ } else { param->stopped = 1; } } -int charset_to_unicode(char **input, int *inlen, wchar_t *output, int outlen, +int charset_to_unicode(const char **input, int *inlen, + wchar_t *output, int outlen, int charset, charset_state *state, const wchar_t *errstr, int errlen) { charset_spec const *spec = charset_find_spec(charset); charset_state localstate; Index: charset/xenc.c ================================================================== --- charset/xenc.c +++ charset/xenc.c @@ -44,10 +44,11 @@ */ { "iso8859-16", CS_ISO8859_16 }, { "koi8-u", CS_KOI8_U }, { "ibm-cp437", CS_CP437 }, { "ibm-cp850", CS_CP850 }, + { "ibm-cp852", CS_CP852 }, { "ibm-cp866", CS_CP866 }, { "microsoft-cp1250", CS_CP1250 }, { "microsoft-cp1251", CS_CP1251 }, { "microsoft-cp1252", CS_CP1252 }, { "microsoft-cp1253", CS_CP1253 }, Index: cmdgen.c ================================================================== --- cmdgen.c +++ cmdgen.c @@ -99,10 +99,20 @@ vfprintf(stderr, p, ap); va_end(ap); fputc('\n', stderr); cleanup_exit(1); } + +void nonfatal(char *p, ...) +{ + va_list ap; + fprintf(stderr, "ERROR: "); + va_start(ap, p); + vfprintf(stderr, p, ap); + va_end(ap); + fputc('\n', stderr); +} /* * Stubs to let everything else link sensibly. */ void log_eventlog(void *handle, const char *event) @@ -116,14 +126,11 @@ { } void showversion(void) { - char *verstr = dupstr(ver); - verstr[0] = tolower((unsigned char)verstr[0]); - printf("PuTTYgen %s\n", verstr); - sfree(verstr); + printf("puttygen: %s\n", ver); } void usage(int standalone) { fprintf(stderr, @@ -255,16 +262,15 @@ } int main(int argc, char **argv) { char *infile = NULL; - Filename infilename; + Filename *infilename = NULL, *outfilename = NULL; enum { NOKEYGEN, RSA1, RSA2, DSA } keytype = NOKEYGEN; char *outfile = NULL, *outfiletmp = NULL; - Filename outfilename; enum { PRIVATE, PUBLIC, PUBLICO, FP, OPENSSH, SSHCOM } outtype = PRIVATE; - int bits = 1024; + int bits = 2048; char *comment = NULL, *origcomment = NULL; int change_passphrase = FALSE; int errs = FALSE, nogo = FALSE; int intype = SSH_KEYTYPE_UNOPENABLE; int sshver = 0; @@ -534,11 +540,11 @@ * course of action. */ if (infile) { infilename = filename_from_str(infile); - intype = key_type(&infilename); + intype = key_type(infilename); switch (intype) { /* * It would be nice here to be able to load _public_ * key files, in any of a number of forms, and (a) @@ -666,11 +672,11 @@ fprintf(stderr, "puttygen: failed to collect entropy, " "could not generate key\n"); return 1; } random_add_heavynoise(entropy, bits / 8); - memset(entropy, 0, bits/8); + smemclr(entropy, bits/8); sfree(entropy); if (keytype == DSA) { struct dss_key *dsskey = snew(struct dss_key); dsa_generate(dsskey, bits, progressfn, &prog); @@ -705,25 +711,25 @@ /* * Find out whether the input key is encrypted. */ if (intype == SSH_KEYTYPE_SSH1) - encrypted = rsakey_encrypted(&infilename, &origcomment); + encrypted = rsakey_encrypted(infilename, &origcomment); else if (intype == SSH_KEYTYPE_SSH2) - encrypted = ssh2_userkey_encrypted(&infilename, &origcomment); + encrypted = ssh2_userkey_encrypted(infilename, &origcomment); else - encrypted = import_encrypted(&infilename, intype, &origcomment); + encrypted = import_encrypted(infilename, intype, &origcomment); /* * If so, ask for a passphrase. */ if (encrypted && load_encrypted) { prompts_t *p = new_prompts(NULL); int ret; p->to_server = FALSE; p->name = dupstr("SSH key passphrase"); - add_prompt(p, dupstr("Enter passphrase to load key: "), FALSE, 512); + add_prompt(p, dupstr("Enter passphrase to load key: "), FALSE); ret = console_get_userpass_input(p, NULL, 0); assert(ret >= 0); if (!ret) { free_prompts(p); perror("puttygen: unable to read passphrase"); @@ -744,11 +750,11 @@ if (!load_encrypted) { void *vblob; unsigned char *blob; int n, l, bloblen; - ret = rsakey_pubblob(&infilename, &vblob, &bloblen, + ret = rsakey_pubblob(infilename, &vblob, &bloblen, &origcomment, &error); blob = (unsigned char *)vblob; n = 4; /* skip modulus bits */ @@ -765,30 +771,35 @@ } else n += l; } ssh1key->comment = dupstr(origcomment); ssh1key->private_exponent = NULL; + ssh1key->p = NULL; + ssh1key->q = NULL; + ssh1key->iqmp = NULL; } else { - ret = loadrsakey(&infilename, ssh1key, passphrase, &error); + ret = loadrsakey(infilename, ssh1key, passphrase, &error); } if (ret > 0) error = NULL; else if (!error) error = "unknown error"; break; case SSH_KEYTYPE_SSH2: if (!load_encrypted) { - ssh2blob = ssh2_userkey_loadpub(&infilename, &ssh2alg, + ssh2blob = ssh2_userkey_loadpub(infilename, &ssh2alg, &ssh2bloblen, NULL, &error); - ssh2algf = find_pubkey_alg(ssh2alg); - if (ssh2algf) - bits = ssh2algf->pubkey_bits(ssh2blob, ssh2bloblen); - else - bits = -1; + if (ssh2blob) { + ssh2algf = find_pubkey_alg(ssh2alg); + if (ssh2algf) + bits = ssh2algf->pubkey_bits(ssh2blob, ssh2bloblen); + else + bits = -1; + } } else { - ssh2key = ssh2_load_userkey(&infilename, passphrase, &error); + ssh2key = ssh2_load_userkey(infilename, passphrase, &error); } if ((ssh2key && ssh2key != SSH2_WRONG_PASSPHRASE) || ssh2blob) error = NULL; else if (!error) { if (ssh2key == SSH2_WRONG_PASSPHRASE) @@ -798,11 +809,11 @@ } break; case SSH_KEYTYPE_OPENSSH: case SSH_KEYTYPE_SSHCOM: - ssh2key = import_ssh2(&infilename, intype, passphrase, &error); + ssh2key = import_ssh2(infilename, intype, passphrase, &error); if (ssh2key) { if (ssh2key != SSH2_WRONG_PASSPHRASE) error = NULL; else error = "wrong passphrase"; @@ -844,12 +855,12 @@ prompts_t *p = new_prompts(NULL); int ret; p->to_server = FALSE; p->name = dupstr("New SSH key passphrase"); - add_prompt(p, dupstr("Enter passphrase to save key: "), FALSE, 512); - add_prompt(p, dupstr("Re-enter passphrase to verify: "), FALSE, 512); + add_prompt(p, dupstr("Enter passphrase to save key: "), FALSE); + add_prompt(p, dupstr("Re-enter passphrase to verify: "), FALSE); ret = console_get_userpass_input(p, NULL, 0); assert(ret >= 0); if (!ret) { free_prompts(p); perror("puttygen: unable to read new passphrase"); @@ -859,11 +870,11 @@ free_prompts(p); fprintf(stderr, "puttygen: passphrases do not match\n"); return 1; } if (passphrase) { - memset(passphrase, 0, strlen(passphrase)); + smemclr(passphrase, strlen(passphrase)); sfree(passphrase); } passphrase = dupstr(p->prompts[0]->result); free_prompts(p); if (!*passphrase) { @@ -890,18 +901,18 @@ int ret; case PRIVATE: if (sshver == 1) { assert(ssh1key); - ret = saversakey(&outfilename, ssh1key, passphrase); + ret = saversakey(outfilename, ssh1key, passphrase); if (!ret) { fprintf(stderr, "puttygen: unable to save SSH-1 private key\n"); return 1; } } else { assert(ssh2key); - ret = ssh2_save_userkey(&outfilename, ssh2key, passphrase); + ret = ssh2_save_userkey(outfilename, ssh2key, passphrase); if (!ret) { fprintf(stderr, "puttygen: unable to save SSH-2 private key\n"); return 1; } } @@ -1021,11 +1032,11 @@ case OPENSSH: case SSHCOM: assert(sshver == 2); assert(ssh2key); - ret = export_ssh2(&outfilename, outtype, ssh2key, passphrase); + ret = export_ssh2(outfilename, outtype, ssh2key, passphrase); if (!ret) { fprintf(stderr, "puttygen: unable to export key\n"); return 1; } if (outfiletmp) { @@ -1034,11 +1045,11 @@ } break; } if (passphrase) { - memset(passphrase, 0, strlen(passphrase)); + smemclr(passphrase, strlen(passphrase)); sfree(passphrase); } if (ssh1key) freersakey(ssh1key); Index: cmdline.c ================================================================== --- cmdline.c +++ cmdline.c @@ -61,11 +61,11 @@ void cmdline_cleanup(void) { int pri; if (cmdline_password) { - memset(cmdline_password, 0, strlen(cmdline_password)); + smemclr(cmdline_password, strlen(cmdline_password)); sfree(cmdline_password); cmdline_password = NULL; } for (pri = 0; pri < NPRIORITIES; pri++) { @@ -103,19 +103,16 @@ * to try). */ if (tried_once) return 0; - strncpy(p->prompts[0]->result, cmdline_password, - p->prompts[0]->result_len); - p->prompts[0]->result[p->prompts[0]->result_len-1] = '\0'; - memset(cmdline_password, 0, strlen(cmdline_password)); + prompt_set_result(p->prompts[0], cmdline_password); + smemclr(cmdline_password, strlen(cmdline_password)); sfree(cmdline_password); cmdline_password = NULL; tried_once = 1; return 1; - } /* * Here we have a flags word which describes the capabilities of * the particular tool on whose behalf we're running. We will @@ -160,148 +157,162 @@ if ((x) == 2 && !value) return -2; \ ret = x; \ if (need_save < 0) return x; \ } while (0) -int cmdline_process_param(char *p, char *value, int need_save, Config *cfg) +int cmdline_process_param(char *p, char *value, int need_save, Conf *conf) { int ret = 0; if (!strcmp(p, "-load")) { RETURN(2); /* This parameter must be processed immediately rather than being * saved. */ - do_defaults(value, cfg); + do_defaults(value, conf); loaded_session = TRUE; cmdline_session_name = dupstr(value); return 2; } if (!strcmp(p, "-ssh")) { RETURN(1); UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK); SAVEABLE(0); - default_protocol = cfg->protocol = PROT_SSH; - default_port = cfg->port = 22; + default_protocol = PROT_SSH; + default_port = 22; + conf_set_int(conf, CONF_protocol, default_protocol); + conf_set_int(conf, CONF_port, default_port); return 1; } if (!strcmp(p, "-telnet")) { RETURN(1); UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK); SAVEABLE(0); - default_protocol = cfg->protocol = PROT_TELNET; - default_port = cfg->port = 23; + default_protocol = PROT_TELNET; + default_port = 23; + conf_set_int(conf, CONF_protocol, default_protocol); + conf_set_int(conf, CONF_port, default_port); return 1; } if (!strcmp(p, "-rlogin")) { RETURN(1); UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK); SAVEABLE(0); - default_protocol = cfg->protocol = PROT_RLOGIN; - default_port = cfg->port = 513; + default_protocol = PROT_RLOGIN; + default_port = 513; + conf_set_int(conf, CONF_protocol, default_protocol); + conf_set_int(conf, CONF_port, default_port); return 1; } if (!strcmp(p, "-raw")) { RETURN(1); UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK); SAVEABLE(0); - default_protocol = cfg->protocol = PROT_RAW; + default_protocol = PROT_RAW; + conf_set_int(conf, CONF_protocol, default_protocol); } if (!strcmp(p, "-serial")) { RETURN(1); /* Serial is not NONNETWORK in an odd sense of the word */ UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK); SAVEABLE(0); - default_protocol = cfg->protocol = PROT_SERIAL; - /* The host parameter will already be loaded into cfg->host, so copy it across */ - strncpy(cfg->serline, cfg->host, sizeof(cfg->serline) - 1); - cfg->serline[sizeof(cfg->serline) - 1] = '\0'; + default_protocol = PROT_SERIAL; + conf_set_int(conf, CONF_protocol, default_protocol); + /* The host parameter will already be loaded into CONF_host, + * so copy it across */ + conf_set_str(conf, CONF_serline, conf_get_str(conf, CONF_host)); } if (!strcmp(p, "-v")) { RETURN(1); flags |= FLAG_VERBOSE; } if (!strcmp(p, "-l")) { RETURN(2); UNAVAILABLE_IN(TOOLTYPE_NONNETWORK); SAVEABLE(0); - strncpy(cfg->username, value, sizeof(cfg->username)); - cfg->username[sizeof(cfg->username) - 1] = '\0'; + conf_set_str(conf, CONF_username, value); } if (!strcmp(p, "-loghost")) { RETURN(2); UNAVAILABLE_IN(TOOLTYPE_NONNETWORK); SAVEABLE(0); - strncpy(cfg->loghost, value, sizeof(cfg->loghost)); - cfg->loghost[sizeof(cfg->loghost) - 1] = '\0'; + conf_set_str(conf, CONF_loghost, value); } if ((!strcmp(p, "-L") || !strcmp(p, "-R") || !strcmp(p, "-D"))) { - char *fwd, *ptr, *q, *qq; - int dynamic, i=0; + char type, *q, *qq, *key, *val; RETURN(2); UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK); SAVEABLE(0); - dynamic = !strcmp(p, "-D"); - fwd = value; - ptr = cfg->portfwd; - /* if existing forwards, find end of list */ - while (*ptr) { - while (*ptr) - ptr++; - ptr++; - } - i = ptr - cfg->portfwd; - ptr[0] = p[1]; /* insert a 'L', 'R' or 'D' at the start */ - ptr++; - if (1 + strlen(fwd) + 2 > sizeof(cfg->portfwd) - i) { - cmdline_error("out of space for port forwardings"); - return ret; - } - strncpy(ptr, fwd, sizeof(cfg->portfwd) - i - 2); - if (!dynamic) { + if (strcmp(p, "-D")) { /* + * For -L or -R forwarding types: + * * We expect _at least_ two colons in this string. The * possible formats are `sourceport:desthost:destport', * or `sourceip:sourceport:desthost:destport' if you're * specifying a particular loopback address. We need to * replace the one between source and dest with a \t; * this means we must find the second-to-last colon in * the string. + * + * (This looks like a foolish way of doing it given the + * existence of strrchr, but it's more efficient than + * two strrchrs - not to mention that the second strrchr + * would require us to modify the input string!) */ - q = qq = strchr(ptr, ':'); + + type = p[1]; /* 'L' or 'R' */ + + q = qq = strchr(value, ':'); while (qq) { char *qqq = strchr(qq+1, ':'); if (qqq) q = qq; qq = qqq; } - if (q) *q = '\t'; /* replace second-last colon with \t */ + + if (!q) { + cmdline_error("-%c expects at least two colons in its" + " argument", type); + return ret; + } + + key = dupprintf("%c%.*s", type, (int)(q - value), value); + val = dupstr(q+1); + } else { + /* + * Dynamic port forwardings are entered under the same key + * as if they were local (because they occupy the same + * port space - a local and a dynamic forwarding on the + * same local port are mutually exclusive), with the + * special value "D" (which can be distinguished from + * anything in the ordinary -L case by containing no + * colon). + */ + key = dupprintf("L%s", value); + val = dupstr("D"); } - cfg->portfwd[sizeof(cfg->portfwd) - 1] = '\0'; - cfg->portfwd[sizeof(cfg->portfwd) - 2] = '\0'; - ptr[strlen(ptr)+1] = '\000'; /* append 2nd '\000' */ + conf_set_str_str(conf, CONF_portfwd, key, val); + sfree(key); + sfree(val); } if ((!strcmp(p, "-nc"))) { char *host, *portp; RETURN(2); UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK); SAVEABLE(0); - host = portp = value; - while (*portp && *portp != ':') - portp++; - if (*portp) { - unsigned len = portp - host; - if (len >= sizeof(cfg->ssh_nc_host)) - len = sizeof(cfg->ssh_nc_host) - 1; - memcpy(cfg->ssh_nc_host, value, len); - cfg->ssh_nc_host[len] = '\0'; - cfg->ssh_nc_port = atoi(portp+1); - } else { + portp = strchr(value, ':'); + if (!portp) { cmdline_error("-nc expects argument of form 'host:port'"); return ret; } + + host = dupprintf("%.*s", (int)(portp - value), value); + conf_set_str(conf, CONF_ssh_nc_host, host); + conf_set_int(conf, CONF_ssh_nc_port, atoi(portp + 1)); + sfree(host); } if (!strcmp(p, "-m")) { char *filename, *command; int cmdlen, cmdsize; FILE *fp; @@ -315,12 +326,11 @@ cmdlen = cmdsize = 0; command = NULL; fp = fopen(filename, "r"); if (!fp) { - cmdline_error("unable to open command " - "file \"%s\"", filename); + cmdline_error("unable to open command file \"%s\"", filename); return ret; } do { c = fgetc(fp); d = c; @@ -330,143 +340,147 @@ cmdsize = cmdlen + 512; command = sresize(command, cmdsize, char); } command[cmdlen++] = d; } while (c != EOF); - cfg->remote_cmd_ptr = command; - cfg->remote_cmd_ptr2 = NULL; - cfg->nopty = TRUE; /* command => no terminal */ fclose(fp); + conf_set_str(conf, CONF_remote_cmd, command); + conf_set_str(conf, CONF_remote_cmd2, ""); + conf_set_int(conf, CONF_nopty, TRUE); /* command => no terminal */ + sfree(command); } if (!strcmp(p, "-P")) { RETURN(2); UNAVAILABLE_IN(TOOLTYPE_NONNETWORK); SAVEABLE(1); /* lower priority than -ssh,-telnet */ - cfg->port = atoi(value); + conf_set_int(conf, CONF_port, atoi(value)); } if (!strcmp(p, "-pw")) { RETURN(2); UNAVAILABLE_IN(TOOLTYPE_NONNETWORK); SAVEABLE(1); /* We delay evaluating this until after the protocol is decided, * so that we can warn if it's of no use with the selected protocol */ - if (cfg->protocol != PROT_SSH) + if (conf_get_int(conf, CONF_protocol) != PROT_SSH) cmdline_error("the -pw option can only be used with the " "SSH protocol"); else { cmdline_password = dupstr(value); /* Assuming that `value' is directly from argv, make a good faith * attempt to trample it, to stop it showing up in `ps' output * on Unix-like systems. Not guaranteed, of course. */ - memset(value, 0, strlen(value)); + smemclr(value, strlen(value)); } } if (!strcmp(p, "-agent") || !strcmp(p, "-pagent") || !strcmp(p, "-pageant")) { RETURN(1); UNAVAILABLE_IN(TOOLTYPE_NONNETWORK); SAVEABLE(0); - cfg->tryagent = TRUE; + conf_set_int(conf, CONF_tryagent, TRUE); } if (!strcmp(p, "-noagent") || !strcmp(p, "-nopagent") || !strcmp(p, "-nopageant")) { RETURN(1); UNAVAILABLE_IN(TOOLTYPE_NONNETWORK); SAVEABLE(0); - cfg->tryagent = FALSE; + conf_set_int(conf, CONF_tryagent, FALSE); } if (!strcmp(p, "-A")) { RETURN(1); UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK); SAVEABLE(0); - cfg->agentfwd = 1; + conf_set_int(conf, CONF_agentfwd, 1); } if (!strcmp(p, "-a")) { RETURN(1); UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK); SAVEABLE(0); - cfg->agentfwd = 0; + conf_set_int(conf, CONF_agentfwd, 0); } if (!strcmp(p, "-X")) { RETURN(1); UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK); SAVEABLE(0); - cfg->x11_forward = 1; + conf_set_int(conf, CONF_x11_forward, 1); } if (!strcmp(p, "-x")) { RETURN(1); UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK); SAVEABLE(0); - cfg->x11_forward = 0; + conf_set_int(conf, CONF_x11_forward, 0); } if (!strcmp(p, "-t")) { RETURN(1); UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK); SAVEABLE(1); /* lower priority than -m */ - cfg->nopty = 0; + conf_set_int(conf, CONF_nopty, 0); } if (!strcmp(p, "-T")) { RETURN(1); UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK); SAVEABLE(1); - cfg->nopty = 1; + conf_set_int(conf, CONF_nopty, 1); } if (!strcmp(p, "-N")) { RETURN(1); UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK); SAVEABLE(0); - cfg->ssh_no_shell = 1; + conf_set_int(conf, CONF_ssh_no_shell, 1); } if (!strcmp(p, "-C")) { RETURN(1); UNAVAILABLE_IN(TOOLTYPE_NONNETWORK); SAVEABLE(0); - cfg->compression = 1; + conf_set_int(conf, CONF_compression, 1); } if (!strcmp(p, "-1")) { RETURN(1); UNAVAILABLE_IN(TOOLTYPE_NONNETWORK); SAVEABLE(0); - cfg->sshprot = 0; /* ssh protocol 1 only */ + conf_set_int(conf, CONF_sshprot, 0); /* ssh protocol 1 only */ } if (!strcmp(p, "-2")) { RETURN(1); UNAVAILABLE_IN(TOOLTYPE_NONNETWORK); SAVEABLE(0); - cfg->sshprot = 3; /* ssh protocol 2 only */ + conf_set_int(conf, CONF_sshprot, 3); /* ssh protocol 2 only */ } if (!strcmp(p, "-i")) { + Filename *fn; RETURN(2); UNAVAILABLE_IN(TOOLTYPE_NONNETWORK); SAVEABLE(0); - cfg->keyfile = filename_from_str(value); + fn = filename_from_str(value); + conf_set_filename(conf, CONF_keyfile, fn); + filename_free(fn); } if (!strcmp(p, "-4") || !strcmp(p, "-ipv4")) { RETURN(1); SAVEABLE(1); - cfg->addressfamily = ADDRTYPE_IPV4; + conf_set_int(conf, CONF_addressfamily, ADDRTYPE_IPV4); } if (!strcmp(p, "-6") || !strcmp(p, "-ipv6")) { RETURN(1); SAVEABLE(1); - cfg->addressfamily = ADDRTYPE_IPV6; + conf_set_int(conf, CONF_addressfamily, ADDRTYPE_IPV6); } if (!strcmp(p, "-sercfg")) { char* nextitem; RETURN(2); UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK); SAVEABLE(1); - if (cfg->protocol != PROT_SERIAL) + if (conf_get_int(conf, CONF_protocol) != PROT_SERIAL) cmdline_error("the -sercfg option can only be used with the " "serial protocol"); /* Value[0] contains one or more , separated values, like 19200,8,n,1,X */ nextitem = value; while (nextitem[0] != '\0') { @@ -481,72 +495,62 @@ skip = 1; } if (length == 1) { switch (*nextitem) { case '1': - cfg->serstopbits = 2; - break; case '2': - cfg->serstopbits = 4; + conf_set_int(conf, CONF_serstopbits, 2 * (*nextitem-'0')); break; case '5': - cfg->serdatabits = 5; - break; case '6': - cfg->serdatabits = 6; - break; case '7': - cfg->serdatabits = 7; - break; case '8': - cfg->serdatabits = 8; - break; case '9': - cfg->serdatabits = 9; + conf_set_int(conf, CONF_serdatabits, *nextitem-'0'); break; case 'n': - cfg->serparity = SER_PAR_NONE; + conf_set_int(conf, CONF_serparity, SER_PAR_NONE); break; case 'o': - cfg->serparity = SER_PAR_ODD; + conf_set_int(conf, CONF_serparity, SER_PAR_ODD); break; case 'e': - cfg->serparity = SER_PAR_EVEN; + conf_set_int(conf, CONF_serparity, SER_PAR_EVEN); break; case 'm': - cfg->serparity = SER_PAR_MARK; + conf_set_int(conf, CONF_serparity, SER_PAR_MARK); break; case 's': - cfg->serparity = SER_PAR_SPACE; + conf_set_int(conf, CONF_serparity, SER_PAR_SPACE); break; case 'N': - cfg->serflow = SER_FLOW_NONE; + conf_set_int(conf, CONF_serflow, SER_FLOW_NONE); break; case 'X': - cfg->serflow = SER_FLOW_XONXOFF; + conf_set_int(conf, CONF_serflow, SER_FLOW_XONXOFF); break; case 'R': - cfg->serflow = SER_FLOW_RTSCTS; + conf_set_int(conf, CONF_serflow, SER_FLOW_RTSCTS); break; case 'D': - cfg->serflow = SER_FLOW_DSRDTR; + conf_set_int(conf, CONF_serflow, SER_FLOW_DSRDTR); break; default: cmdline_error("Unrecognised suboption \"-sercfg %c\"", *nextitem); } } else if (length == 3 && !strncmp(nextitem,"1.5",3)) { /* Messy special case */ - cfg->serstopbits = 3; + conf_set_int(conf, CONF_serstopbits, 3); } else { int serspeed = atoi(nextitem); if (serspeed != 0) { - cfg->serspeed = serspeed; + conf_set_int(conf, CONF_serspeed, serspeed); } else { cmdline_error("Unrecognised suboption \"-sercfg %s\"", nextitem); } } @@ -554,13 +558,13 @@ } } return ret; /* unrecognised */ } -void cmdline_run_saved(Config *cfg) +void cmdline_run_saved(Conf *conf) { int pri, i; for (pri = 0; pri < NPRIORITIES; pri++) for (i = 0; i < saves[pri].nsaved; i++) cmdline_process_param(saves[pri].params[i].p, - saves[pri].params[i].value, 0, cfg); + saves[pri].params[i].value, 0, conf); } ADDED conf.c Index: conf.c ================================================================== --- /dev/null +++ conf.c @@ -0,0 +1,613 @@ +/* + * conf.c: implementation of the internal storage format used for + * the configuration of a PuTTY session. + */ + +#include +#include +#include + +#include "tree234.h" +#include "putty.h" + +/* + * Enumeration of types used in keys and values. + */ +typedef enum { TYPE_NONE, TYPE_INT, TYPE_STR, TYPE_FILENAME, TYPE_FONT } Type; + +/* + * Arrays which allow us to look up the subkey and value types for a + * given primary key id. + */ +#define CONF_SUBKEYTYPE_DEF(valtype, keytype, keyword) TYPE_ ## keytype, +static int subkeytypes[] = { CONFIG_OPTIONS(CONF_SUBKEYTYPE_DEF) }; +#define CONF_VALUETYPE_DEF(valtype, keytype, keyword) TYPE_ ## valtype, +static int valuetypes[] = { CONFIG_OPTIONS(CONF_VALUETYPE_DEF) }; + +/* + * Configuration keys are primarily integers (big enum of all the + * different configurable options); some keys have string-designated + * subkeys, such as the list of environment variables (subkeys + * defined by the variable names); some have integer-designated + * subkeys (wordness, colours, preference lists). + */ +struct key { + int primary; + union { + int i; + char *s; + } secondary; +}; + +struct value { + union { + int intval; + char *stringval; + Filename *fileval; + FontSpec *fontval; + } u; +}; + +struct conf_entry { + struct key key; + struct value value; +}; + +struct conf_tag { + tree234 *tree; +}; + +/* + * Because 'struct key' is the first element in 'struct conf_entry', + * it's safe (guaranteed by the C standard) to cast arbitrarily back + * and forth between the two types. Therefore, we only need one + * comparison function, which can double as a main sort function for + * the tree (comparing two conf_entry structures with each other) + * and a search function (looking up an externally supplied key). + */ +static int conf_cmp(void *av, void *bv) +{ + struct key *a = (struct key *)av; + struct key *b = (struct key *)bv; + + if (a->primary < b->primary) + return -1; + else if (a->primary > b->primary) + return +1; + switch (subkeytypes[a->primary]) { + case TYPE_INT: + if (a->secondary.i < b->secondary.i) + return -1; + else if (a->secondary.i > b->secondary.i) + return +1; + return 0; + case TYPE_STR: + return strcmp(a->secondary.s, b->secondary.s); + default: + return 0; + } +} + +/* + * Free any dynamic data items pointed to by a 'struct key'. We + * don't free the structure itself, since it's probably part of a + * larger allocated block. + */ +static void free_key(struct key *key) +{ + if (subkeytypes[key->primary] == TYPE_STR) + sfree(key->secondary.s); +} + +/* + * Copy a 'struct key' into another one, copying its dynamic data + * if necessary. + */ +static void copy_key(struct key *to, struct key *from) +{ + to->primary = from->primary; + switch (subkeytypes[to->primary]) { + case TYPE_INT: + to->secondary.i = from->secondary.i; + break; + case TYPE_STR: + to->secondary.s = dupstr(from->secondary.s); + break; + } +} + +/* + * Free any dynamic data items pointed to by a 'struct value'. We + * don't free the value itself, since it's probably part of a larger + * allocated block. + */ +static void free_value(struct value *val, int type) +{ + if (type == TYPE_STR) + sfree(val->u.stringval); + else if (type == TYPE_FILENAME) + filename_free(val->u.fileval); + else if (type == TYPE_FONT) + fontspec_free(val->u.fontval); +} + +/* + * Copy a 'struct value' into another one, copying its dynamic data + * if necessary. + */ +static void copy_value(struct value *to, struct value *from, int type) +{ + switch (type) { + case TYPE_INT: + to->u.intval = from->u.intval; + break; + case TYPE_STR: + to->u.stringval = dupstr(from->u.stringval); + break; + case TYPE_FILENAME: + to->u.fileval = filename_copy(from->u.fileval); + break; + case TYPE_FONT: + to->u.fontval = fontspec_copy(from->u.fontval); + break; + } +} + +/* + * Free an entire 'struct conf_entry' and its dynamic data. + */ +static void free_entry(struct conf_entry *entry) +{ + free_key(&entry->key); + free_value(&entry->value, valuetypes[entry->key.primary]); + sfree(entry); +} + +Conf *conf_new(void) +{ + Conf *conf = snew(struct conf_tag); + + conf->tree = newtree234(conf_cmp); + + return conf; +} + +static void conf_clear(Conf *conf) +{ + struct conf_entry *entry; + + while ((entry = delpos234(conf->tree, 0)) != NULL) + free_entry(entry); +} + +void conf_free(Conf *conf) +{ + conf_clear(conf); + freetree234(conf->tree); + sfree(conf); +} + +static void conf_insert(Conf *conf, struct conf_entry *entry) +{ + struct conf_entry *oldentry = add234(conf->tree, entry); + if (oldentry && oldentry != entry) { + del234(conf->tree, oldentry); + free_entry(oldentry); + oldentry = add234(conf->tree, entry); + assert(oldentry == entry); + } +} + +void conf_copy_into(Conf *newconf, Conf *oldconf) +{ + struct conf_entry *entry, *entry2; + int i; + + conf_clear(newconf); + + for (i = 0; (entry = index234(oldconf->tree, i)) != NULL; i++) { + entry2 = snew(struct conf_entry); + copy_key(&entry2->key, &entry->key); + copy_value(&entry2->value, &entry->value, + valuetypes[entry->key.primary]); + add234(newconf->tree, entry2); + } +} + +Conf *conf_copy(Conf *oldconf) +{ + Conf *newconf = conf_new(); + + conf_copy_into(newconf, oldconf); + + return newconf; +} + +int conf_get_int(Conf *conf, int primary) +{ + struct key key; + struct conf_entry *entry; + + assert(subkeytypes[primary] == TYPE_NONE); + assert(valuetypes[primary] == TYPE_INT); + key.primary = primary; + entry = find234(conf->tree, &key, NULL); + assert(entry); + return entry->value.u.intval; +} + +int conf_get_int_int(Conf *conf, int primary, int secondary) +{ + struct key key; + struct conf_entry *entry; + + assert(subkeytypes[primary] == TYPE_INT); + assert(valuetypes[primary] == TYPE_INT); + key.primary = primary; + key.secondary.i = secondary; + entry = find234(conf->tree, &key, NULL); + assert(entry); + return entry->value.u.intval; +} + +char *conf_get_str(Conf *conf, int primary) +{ + struct key key; + struct conf_entry *entry; + + assert(subkeytypes[primary] == TYPE_NONE); + assert(valuetypes[primary] == TYPE_STR); + key.primary = primary; + entry = find234(conf->tree, &key, NULL); + assert(entry); + return entry->value.u.stringval; +} + +char *conf_get_str_str_opt(Conf *conf, int primary, const char *secondary) +{ + struct key key; + struct conf_entry *entry; + + assert(subkeytypes[primary] == TYPE_STR); + assert(valuetypes[primary] == TYPE_STR); + key.primary = primary; + key.secondary.s = (char *)secondary; + entry = find234(conf->tree, &key, NULL); + return entry ? entry->value.u.stringval : NULL; +} + +char *conf_get_str_str(Conf *conf, int primary, const char *secondary) +{ + char *ret = conf_get_str_str_opt(conf, primary, secondary); + assert(ret); + return ret; +} + +char *conf_get_str_strs(Conf *conf, int primary, + char *subkeyin, char **subkeyout) +{ + struct key key; + struct conf_entry *entry; + + assert(subkeytypes[primary] == TYPE_STR); + assert(valuetypes[primary] == TYPE_STR); + key.primary = primary; + if (subkeyin) { + key.secondary.s = subkeyin; + entry = findrel234(conf->tree, &key, NULL, REL234_GT); + } else { + key.secondary.s = ""; + entry = findrel234(conf->tree, &key, NULL, REL234_GE); + } + if (!entry || entry->key.primary != primary) + return NULL; + *subkeyout = entry->key.secondary.s; + return entry->value.u.stringval; +} + +char *conf_get_str_nthstrkey(Conf *conf, int primary, int n) +{ + struct key key; + struct conf_entry *entry; + int index; + + assert(subkeytypes[primary] == TYPE_STR); + assert(valuetypes[primary] == TYPE_STR); + key.primary = primary; + key.secondary.s = ""; + entry = findrelpos234(conf->tree, &key, NULL, REL234_GE, &index); + if (!entry || entry->key.primary != primary) + return NULL; + entry = index234(conf->tree, index + n); + if (!entry || entry->key.primary != primary) + return NULL; + return entry->key.secondary.s; +} + +Filename *conf_get_filename(Conf *conf, int primary) +{ + struct key key; + struct conf_entry *entry; + + assert(subkeytypes[primary] == TYPE_NONE); + assert(valuetypes[primary] == TYPE_FILENAME); + key.primary = primary; + entry = find234(conf->tree, &key, NULL); + assert(entry); + return entry->value.u.fileval; +} + +FontSpec *conf_get_fontspec(Conf *conf, int primary) +{ + struct key key; + struct conf_entry *entry; + + assert(subkeytypes[primary] == TYPE_NONE); + assert(valuetypes[primary] == TYPE_FONT); + key.primary = primary; + entry = find234(conf->tree, &key, NULL); + assert(entry); + return entry->value.u.fontval; +} + +void conf_set_int(Conf *conf, int primary, int value) +{ + struct conf_entry *entry = snew(struct conf_entry); + + assert(subkeytypes[primary] == TYPE_NONE); + assert(valuetypes[primary] == TYPE_INT); + entry->key.primary = primary; + entry->value.u.intval = value; + conf_insert(conf, entry); +} + +void conf_set_int_int(Conf *conf, int primary, int secondary, int value) +{ + struct conf_entry *entry = snew(struct conf_entry); + + assert(subkeytypes[primary] == TYPE_INT); + assert(valuetypes[primary] == TYPE_INT); + entry->key.primary = primary; + entry->key.secondary.i = secondary; + entry->value.u.intval = value; + conf_insert(conf, entry); +} + +void conf_set_str(Conf *conf, int primary, const char *value) +{ + struct conf_entry *entry = snew(struct conf_entry); + + assert(subkeytypes[primary] == TYPE_NONE); + assert(valuetypes[primary] == TYPE_STR); + entry->key.primary = primary; + entry->value.u.stringval = dupstr(value); + conf_insert(conf, entry); +} + +void conf_set_str_str(Conf *conf, int primary, const char *secondary, + const char *value) +{ + struct conf_entry *entry = snew(struct conf_entry); + + assert(subkeytypes[primary] == TYPE_STR); + assert(valuetypes[primary] == TYPE_STR); + entry->key.primary = primary; + entry->key.secondary.s = dupstr(secondary); + entry->value.u.stringval = dupstr(value); + conf_insert(conf, entry); +} + +void conf_del_str_str(Conf *conf, int primary, const char *secondary) +{ + struct key key; + struct conf_entry *entry; + + assert(subkeytypes[primary] == TYPE_STR); + assert(valuetypes[primary] == TYPE_STR); + key.primary = primary; + key.secondary.s = (char *)secondary; + entry = find234(conf->tree, &key, NULL); + if (entry) { + del234(conf->tree, entry); + free_entry(entry); + } + } + +void conf_set_filename(Conf *conf, int primary, const Filename *value) +{ + struct conf_entry *entry = snew(struct conf_entry); + + assert(subkeytypes[primary] == TYPE_NONE); + assert(valuetypes[primary] == TYPE_FILENAME); + entry->key.primary = primary; + entry->value.u.fileval = filename_copy(value); + conf_insert(conf, entry); +} + +void conf_set_fontspec(Conf *conf, int primary, const FontSpec *value) +{ + struct conf_entry *entry = snew(struct conf_entry); + + assert(subkeytypes[primary] == TYPE_NONE); + assert(valuetypes[primary] == TYPE_FONT); + entry->key.primary = primary; + entry->value.u.fontval = fontspec_copy(value); + conf_insert(conf, entry); +} + +int conf_serialised_size(Conf *conf) +{ + int i; + struct conf_entry *entry; + int size = 0; + + for (i = 0; (entry = index234(conf->tree, i)) != NULL; i++) { + size += 4; /* primary key */ + switch (subkeytypes[entry->key.primary]) { + case TYPE_INT: + size += 4; + break; + case TYPE_STR: + size += 1 + strlen(entry->key.secondary.s); + break; + } + switch (valuetypes[entry->key.primary]) { + case TYPE_INT: + size += 4; + break; + case TYPE_STR: + size += 1 + strlen(entry->value.u.stringval); + break; + case TYPE_FILENAME: + size += filename_serialise(entry->value.u.fileval, NULL); + break; + case TYPE_FONT: + size += fontspec_serialise(entry->value.u.fontval, NULL); + break; + } + } + + size += 4; /* terminator value */ + + return size; +} + +void conf_serialise(Conf *conf, void *vdata) +{ + unsigned char *data = (unsigned char *)vdata; + int i, len; + struct conf_entry *entry; + + for (i = 0; (entry = index234(conf->tree, i)) != NULL; i++) { + PUT_32BIT_MSB_FIRST(data, entry->key.primary); + data += 4; + + switch (subkeytypes[entry->key.primary]) { + case TYPE_INT: + PUT_32BIT_MSB_FIRST(data, entry->key.secondary.i); + data += 4; + break; + case TYPE_STR: + len = strlen(entry->key.secondary.s); + memcpy(data, entry->key.secondary.s, len); + data += len; + *data++ = 0; + break; + } + switch (valuetypes[entry->key.primary]) { + case TYPE_INT: + PUT_32BIT_MSB_FIRST(data, entry->value.u.intval); + data += 4; + break; + case TYPE_STR: + len = strlen(entry->value.u.stringval); + memcpy(data, entry->value.u.stringval, len); + data += len; + *data++ = 0; + break; + case TYPE_FILENAME: + data += filename_serialise(entry->value.u.fileval, data); + break; + case TYPE_FONT: + data += fontspec_serialise(entry->value.u.fontval, data); + break; + } + } + + PUT_32BIT_MSB_FIRST(data, 0xFFFFFFFFU); +} + +int conf_deserialise(Conf *conf, void *vdata, int maxsize) +{ + unsigned char *data = (unsigned char *)vdata; + unsigned char *start = data; + struct conf_entry *entry; + unsigned primary; + int used; + unsigned char *zero; + + while (maxsize >= 4) { + primary = GET_32BIT_MSB_FIRST(data); + data += 4, maxsize -= 4; + + if (primary >= N_CONFIG_OPTIONS) + break; + + entry = snew(struct conf_entry); + entry->key.primary = primary; + + switch (subkeytypes[entry->key.primary]) { + case TYPE_INT: + if (maxsize < 4) { + sfree(entry); + goto done; + } + entry->key.secondary.i = toint(GET_32BIT_MSB_FIRST(data)); + data += 4, maxsize -= 4; + break; + case TYPE_STR: + zero = memchr(data, 0, maxsize); + if (!zero) { + sfree(entry); + goto done; + } + entry->key.secondary.s = dupstr((char *)data); + maxsize -= (zero + 1 - data); + data = zero + 1; + break; + } + + switch (valuetypes[entry->key.primary]) { + case TYPE_INT: + if (maxsize < 4) { + if (subkeytypes[entry->key.primary] == TYPE_STR) + sfree(entry->key.secondary.s); + sfree(entry); + goto done; + } + entry->value.u.intval = toint(GET_32BIT_MSB_FIRST(data)); + data += 4, maxsize -= 4; + break; + case TYPE_STR: + zero = memchr(data, 0, maxsize); + if (!zero) { + if (subkeytypes[entry->key.primary] == TYPE_STR) + sfree(entry->key.secondary.s); + sfree(entry); + goto done; + } + entry->value.u.stringval = dupstr((char *)data); + maxsize -= (zero + 1 - data); + data = zero + 1; + break; + case TYPE_FILENAME: + entry->value.u.fileval = + filename_deserialise(data, maxsize, &used); + if (!entry->value.u.fileval) { + if (subkeytypes[entry->key.primary] == TYPE_STR) + sfree(entry->key.secondary.s); + sfree(entry); + goto done; + } + data += used; + maxsize -= used; + break; + case TYPE_FONT: + entry->value.u.fontval = + fontspec_deserialise(data, maxsize, &used); + if (!entry->value.u.fontval) { + if (subkeytypes[entry->key.primary] == TYPE_STR) + sfree(entry->key.secondary.s); + sfree(entry); + goto done; + } + data += used; + maxsize -= used; + break; + } + conf_insert(conf, entry); + } + + done: + return (int)(data - start); +} Index: config.c ================================================================== --- config.c +++ config.c @@ -8,93 +8,223 @@ #include "putty.h" #include "dialog.h" #include "storage.h" -/* PuTTY SC start */ -#include "pkcs11.h" -#include "sc.h" -/* PuTTY SC end */ - -/* PuTTY CAPI start */ -#ifdef _WINDOWS -#include "capi.h" -#include -#include -#include -#endif -/* PuTTY CAPI end */ - #define PRINTER_DISABLED_STRING "None (printing disabled)" #define HOST_BOX_TITLE "Host Name (or IP address)" #define PORT_BOX_TITLE "Port" + +void conf_radiobutton_handler(union control *ctrl, void *dlg, + void *data, int event) +{ + int button; + Conf *conf = (Conf *)data; + + /* + * For a standard radio button set, the context parameter gives + * the primary key (CONF_foo), and the extra data per button + * gives the value the target field should take if that button + * is the one selected. + */ + if (event == EVENT_REFRESH) { + int val = conf_get_int(conf, ctrl->radio.context.i); + for (button = 0; button < ctrl->radio.nbuttons; button++) + if (val == ctrl->radio.buttondata[button].i) + break; + /* We expected that `break' to happen, in all circumstances. */ + assert(button < ctrl->radio.nbuttons); + dlg_radiobutton_set(ctrl, dlg, button); + } else if (event == EVENT_VALCHANGE) { + button = dlg_radiobutton_get(ctrl, dlg); + assert(button >= 0 && button < ctrl->radio.nbuttons); + conf_set_int(conf, ctrl->radio.context.i, + ctrl->radio.buttondata[button].i); + } +} + +#define CHECKBOX_INVERT (1<<30) +void conf_checkbox_handler(union control *ctrl, void *dlg, + void *data, int event) +{ + int key, invert; + Conf *conf = (Conf *)data; + + /* + * For a standard checkbox, the context parameter gives the + * primary key (CONF_foo), optionally ORed with CHECKBOX_INVERT. + */ + key = ctrl->checkbox.context.i; + if (key & CHECKBOX_INVERT) { + key &= ~CHECKBOX_INVERT; + invert = 1; + } else + invert = 0; + + /* + * C lacks a logical XOR, so the following code uses the idiom + * (!a ^ !b) to obtain the logical XOR of a and b. (That is, 1 + * iff exactly one of a and b is nonzero, otherwise 0.) + */ + + if (event == EVENT_REFRESH) { + int val = conf_get_int(conf, key); + dlg_checkbox_set(ctrl, dlg, (!val ^ !invert)); + } else if (event == EVENT_VALCHANGE) { + conf_set_int(conf, key, !dlg_checkbox_get(ctrl,dlg) ^ !invert); + } +} + +void conf_editbox_handler(union control *ctrl, void *dlg, + void *data, int event) +{ + /* + * The standard edit-box handler expects the main `context' + * field to contain the primary key. The secondary `context2' + * field indicates the type of this field: + * + * - if context2 > 0, the field is a string. + * - if context2 == -1, the field is an int and the edit box + * is numeric. + * - if context2 < -1, the field is an int and the edit box is + * _floating_, and (-context2) gives the scale. (E.g. if + * context2 == -1000, then typing 1.2 into the box will set + * the field to 1200.) + */ + int key = ctrl->editbox.context.i; + int length = ctrl->editbox.context2.i; + Conf *conf = (Conf *)data; + + if (length > 0) { + if (event == EVENT_REFRESH) { + char *field = conf_get_str(conf, key); + dlg_editbox_set(ctrl, dlg, field); + } else if (event == EVENT_VALCHANGE) { + char *field = dlg_editbox_get(ctrl, dlg); + conf_set_str(conf, key, field); + sfree(field); + } + } else if (length < 0) { + if (event == EVENT_REFRESH) { + char str[80]; + int value = conf_get_int(conf, key); + if (length == -1) + sprintf(str, "%d", value); + else + sprintf(str, "%g", (double)value / (double)(-length)); + dlg_editbox_set(ctrl, dlg, str); + } else if (event == EVENT_VALCHANGE) { + char *str = dlg_editbox_get(ctrl, dlg); + if (length == -1) + conf_set_int(conf, key, atoi(str)); + else + conf_set_int(conf, key, (int)((-length) * atof(str))); + sfree(str); + } + } +} + +void conf_filesel_handler(union control *ctrl, void *dlg, + void *data, int event) +{ + int key = ctrl->fileselect.context.i; + Conf *conf = (Conf *)data; + + if (event == EVENT_REFRESH) { + dlg_filesel_set(ctrl, dlg, conf_get_filename(conf, key)); + } else if (event == EVENT_VALCHANGE) { + Filename *filename = dlg_filesel_get(ctrl, dlg); + conf_set_filename(conf, key, filename); + filename_free(filename); + } +} + +void conf_fontsel_handler(union control *ctrl, void *dlg, + void *data, int event) +{ + int key = ctrl->fontselect.context.i; + Conf *conf = (Conf *)data; + + if (event == EVENT_REFRESH) { + dlg_fontsel_set(ctrl, dlg, conf_get_fontspec(conf, key)); + } else if (event == EVENT_VALCHANGE) { + FontSpec *fontspec = dlg_fontsel_get(ctrl, dlg); + conf_set_fontspec(conf, key, fontspec); + fontspec_free(fontspec); + } +} static void config_host_handler(union control *ctrl, void *dlg, void *data, int event) { - Config *cfg = (Config *)data; + Conf *conf = (Conf *)data; /* * This function works just like the standard edit box handler, * only it has to choose the control's label and text from two * different places depending on the protocol. */ if (event == EVENT_REFRESH) { - if (cfg->protocol == PROT_SERIAL) { + if (conf_get_int(conf, CONF_protocol) == PROT_SERIAL) { /* * This label text is carefully chosen to contain an n, * since that's the shortcut for the host name control. */ dlg_label_change(ctrl, dlg, "Serial line"); - dlg_editbox_set(ctrl, dlg, cfg->serline); + dlg_editbox_set(ctrl, dlg, conf_get_str(conf, CONF_serline)); } else { dlg_label_change(ctrl, dlg, HOST_BOX_TITLE); - dlg_editbox_set(ctrl, dlg, cfg->host); + dlg_editbox_set(ctrl, dlg, conf_get_str(conf, CONF_host)); } } else if (event == EVENT_VALCHANGE) { - if (cfg->protocol == PROT_SERIAL) - dlg_editbox_get(ctrl, dlg, cfg->serline, lenof(cfg->serline)); + char *s = dlg_editbox_get(ctrl, dlg); + if (conf_get_int(conf, CONF_protocol) == PROT_SERIAL) + conf_set_str(conf, CONF_serline, s); else - dlg_editbox_get(ctrl, dlg, cfg->host, lenof(cfg->host)); + conf_set_str(conf, CONF_host, s); + sfree(s); } } static void config_port_handler(union control *ctrl, void *dlg, void *data, int event) { - Config *cfg = (Config *)data; + Conf *conf = (Conf *)data; char buf[80]; /* * This function works similarly to the standard edit box handler, * only it has to choose the control's label and text from two * different places depending on the protocol. */ if (event == EVENT_REFRESH) { - if (cfg->protocol == PROT_SERIAL) { + if (conf_get_int(conf, CONF_protocol) == PROT_SERIAL) { /* * This label text is carefully chosen to contain a p, * since that's the shortcut for the port control. */ dlg_label_change(ctrl, dlg, "Speed"); - sprintf(buf, "%d", cfg->serspeed); + sprintf(buf, "%d", conf_get_int(conf, CONF_serspeed)); } else { dlg_label_change(ctrl, dlg, PORT_BOX_TITLE); - if (cfg->port != 0) - sprintf(buf, "%d", cfg->port); + if (conf_get_int(conf, CONF_port) != 0) + sprintf(buf, "%d", conf_get_int(conf, CONF_port)); else /* Display an (invalid) port of 0 as blank */ buf[0] = '\0'; } dlg_editbox_set(ctrl, dlg, buf); } else if (event == EVENT_VALCHANGE) { - dlg_editbox_get(ctrl, dlg, buf, lenof(buf)); - if (cfg->protocol == PROT_SERIAL) - cfg->serspeed = atoi(buf); + char *s = dlg_editbox_get(ctrl, dlg); + int i = atoi(s); + sfree(s); + + if (conf_get_int(conf, CONF_protocol) == PROT_SERIAL) + conf_set_int(conf, CONF_serspeed, i); else - cfg->port = atoi(buf); + conf_set_int(conf, CONF_port, i); } } struct hostport { union control *host, *port; @@ -107,11 +237,11 @@ */ void config_protocolbuttons_handler(union control *ctrl, void *dlg, void *data, int event) { int button; - Config *cfg = (Config *)data; + Conf *conf = (Conf *)data; struct hostport *hp = (struct hostport *)ctrl->radio.context.p; /* * This function works just like the standard radio-button * handler, except that it also has to change the setting of @@ -118,24 +248,29 @@ * the port box, and refresh both host and port boxes when. We * expect the context parameter to point at a hostport * structure giving the `union control's for both. */ if (event == EVENT_REFRESH) { + int protocol = conf_get_int(conf, CONF_protocol); for (button = 0; button < ctrl->radio.nbuttons; button++) - if (cfg->protocol == ctrl->radio.buttondata[button].i) + if (protocol == ctrl->radio.buttondata[button].i) break; /* We expected that `break' to happen, in all circumstances. */ assert(button < ctrl->radio.nbuttons); dlg_radiobutton_set(ctrl, dlg, button); } else if (event == EVENT_VALCHANGE) { - int oldproto = cfg->protocol; + int oldproto = conf_get_int(conf, CONF_protocol); + int newproto, port; + button = dlg_radiobutton_get(ctrl, dlg); assert(button >= 0 && button < ctrl->radio.nbuttons); - cfg->protocol = ctrl->radio.buttondata[button].i; - if (oldproto != cfg->protocol) { + newproto = ctrl->radio.buttondata[button].i; + conf_set_int(conf, CONF_protocol, newproto); + + if (oldproto != newproto) { Backend *ob = backend_from_proto(oldproto); - Backend *nb = backend_from_proto(cfg->protocol); + Backend *nb = backend_from_proto(newproto); assert(ob); assert(nb); /* Iff the user hasn't changed the port from the old protocol's * default, update it with the new protocol's default. * (This includes a "default" of 0, implying that there is no @@ -143,12 +278,13 @@ * displayed as a blank.) * This helps with the common case of tabbing through the * controls in order and setting a non-default port before * getting to the protocol; we want that non-default port * to be preserved. */ - if (cfg->port == ob->default_port) - cfg->port = nb->default_port; + port = conf_get_int(conf, CONF_port); + if (port == ob->default_port) + conf_set_int(conf, CONF_port, nb->default_port); } dlg_refresh(hp->host, dlg); dlg_refresh(hp->port, dlg); } } @@ -155,68 +291,70 @@ static void loggingbuttons_handler(union control *ctrl, void *dlg, void *data, int event) { int button; - Config *cfg = (Config *)data; + Conf *conf = (Conf *)data; /* This function works just like the standard radio-button handler, * but it has to fall back to "no logging" in situations where the * configured logging type isn't applicable. */ if (event == EVENT_REFRESH) { + int logtype = conf_get_int(conf, CONF_logtype); + for (button = 0; button < ctrl->radio.nbuttons; button++) - if (cfg->logtype == ctrl->radio.buttondata[button].i) + if (logtype == ctrl->radio.buttondata[button].i) break; - - /* We fell off the end, so we lack the configured logging type */ - if (button == ctrl->radio.nbuttons) { - button=0; - cfg->logtype=LGTYP_NONE; - } - dlg_radiobutton_set(ctrl, dlg, button); + + /* We fell off the end, so we lack the configured logging type */ + if (button == ctrl->radio.nbuttons) { + button = 0; + conf_set_int(conf, CONF_logtype, LGTYP_NONE); + } + dlg_radiobutton_set(ctrl, dlg, button); } else if (event == EVENT_VALCHANGE) { button = dlg_radiobutton_get(ctrl, dlg); assert(button >= 0 && button < ctrl->radio.nbuttons); - cfg->logtype = ctrl->radio.buttondata[button].i; + conf_set_int(conf, CONF_logtype, ctrl->radio.buttondata[button].i); } } static void numeric_keypad_handler(union control *ctrl, void *dlg, void *data, int event) { int button; - Config *cfg = (Config *)data; + Conf *conf = (Conf *)data; /* * This function works much like the standard radio button - * handler, but it has to handle two fields in Config. + * handler, but it has to handle two fields in Conf. */ if (event == EVENT_REFRESH) { - if (cfg->nethack_keypad) + if (conf_get_int(conf, CONF_nethack_keypad)) button = 2; - else if (cfg->app_keypad) + else if (conf_get_int(conf, CONF_app_keypad)) button = 1; else button = 0; assert(button < ctrl->radio.nbuttons); dlg_radiobutton_set(ctrl, dlg, button); } else if (event == EVENT_VALCHANGE) { button = dlg_radiobutton_get(ctrl, dlg); assert(button >= 0 && button < ctrl->radio.nbuttons); if (button == 2) { - cfg->app_keypad = FALSE; - cfg->nethack_keypad = TRUE; + conf_set_int(conf, CONF_app_keypad, FALSE); + conf_set_int(conf, CONF_nethack_keypad, TRUE); } else { - cfg->app_keypad = (button != 0); - cfg->nethack_keypad = FALSE; + conf_set_int(conf, CONF_app_keypad, (button != 0)); + conf_set_int(conf, CONF_nethack_keypad, FALSE); } } } static void cipherlist_handler(union control *ctrl, void *dlg, void *data, int event) { - Config *cfg = (Config *)data; + Conf *conf = (Conf *)data; if (event == EVENT_REFRESH) { int i; static const struct { char *s; int c; } ciphers[] = { { "3DES", CIPHER_3DES }, @@ -230,11 +368,11 @@ /* Set up the "selected ciphers" box. */ /* (cipherlist assumed to contain all ciphers) */ dlg_update_start(ctrl, dlg); dlg_listbox_clear(ctrl, dlg); for (i = 0; i < CIPHER_MAX; i++) { - int c = cfg->ssh_cipherlist[i]; + int c = conf_get_int_int(conf, CONF_ssh_cipherlist, i); int j; char *cstr = NULL; for (j = 0; j < (sizeof ciphers) / (sizeof ciphers[0]); j++) { if (ciphers[j].c == c) { cstr = ciphers[j].s; @@ -248,27 +386,27 @@ } else if (event == EVENT_VALCHANGE) { int i; /* Update array to match the list box. */ for (i=0; i < CIPHER_MAX; i++) - cfg->ssh_cipherlist[i] = dlg_listbox_getid(ctrl, dlg, i); - + conf_set_int_int(conf, CONF_ssh_cipherlist, i, + dlg_listbox_getid(ctrl, dlg, i)); } } #ifndef NO_GSSAPI static void gsslist_handler(union control *ctrl, void *dlg, void *data, int event) { - Config *cfg = (Config *)data; + Conf *conf = (Conf *)data; if (event == EVENT_REFRESH) { int i; dlg_update_start(ctrl, dlg); dlg_listbox_clear(ctrl, dlg); for (i = 0; i < ngsslibs; i++) { - int id = cfg->ssh_gsslist[i]; + int id = conf_get_int_int(conf, CONF_ssh_gsslist, i); assert(id >= 0 && id < ngsslibs); dlg_listbox_addwithid(ctrl, dlg, gsslibnames[id], id); } dlg_update_done(ctrl, dlg); @@ -275,19 +413,20 @@ } else if (event == EVENT_VALCHANGE) { int i; /* Update array to match the list box. */ for (i=0; i < ngsslibs; i++) - cfg->ssh_gsslist[i] = dlg_listbox_getid(ctrl, dlg, i); + conf_set_int_int(conf, CONF_ssh_gsslist, i, + dlg_listbox_getid(ctrl, dlg, i)); } } #endif static void kexlist_handler(union control *ctrl, void *dlg, void *data, int event) { - Config *cfg = (Config *)data; + Conf *conf = (Conf *)data; if (event == EVENT_REFRESH) { int i; static const struct { char *s; int k; } kexes[] = { { "Diffie-Hellman group 1", KEX_DHGROUP1 }, @@ -300,11 +439,11 @@ /* Set up the "kex preference" box. */ /* (kexlist assumed to contain all algorithms) */ dlg_update_start(ctrl, dlg); dlg_listbox_clear(ctrl, dlg); for (i = 0; i < KEX_MAX; i++) { - int k = cfg->ssh_kexlist[i]; + int k = conf_get_int_int(conf, CONF_ssh_kexlist, i); int j; char *kstr = NULL; for (j = 0; j < (sizeof kexes) / (sizeof kexes[0]); j++) { if (kexes[j].k == k) { kstr = kexes[j].s; @@ -318,22 +457,23 @@ } else if (event == EVENT_VALCHANGE) { int i; /* Update array to match the list box. */ for (i=0; i < KEX_MAX; i++) - cfg->ssh_kexlist[i] = dlg_listbox_getid(ctrl, dlg, i); - + conf_set_int_int(conf, CONF_ssh_kexlist, i, + dlg_listbox_getid(ctrl, dlg, i)); } } static void printerbox_handler(union control *ctrl, void *dlg, void *data, int event) { - Config *cfg = (Config *)data; + Conf *conf = (Conf *)data; if (event == EVENT_REFRESH) { int nprinters, i; printer_enum *pe; + char *printer; dlg_update_start(ctrl, dlg); /* * Some backends may wish to disable the drop-down list on * this edit box. Be prepared for this. @@ -344,54 +484,66 @@ pe = printer_start_enum(&nprinters); for (i = 0; i < nprinters; i++) dlg_listbox_add(ctrl, dlg, printer_get_name(pe, i)); printer_finish_enum(pe); } - dlg_editbox_set(ctrl, dlg, - (*cfg->printer ? cfg->printer : - PRINTER_DISABLED_STRING)); + printer = conf_get_str(conf, CONF_printer); + if (!printer) + printer = PRINTER_DISABLED_STRING; + dlg_editbox_set(ctrl, dlg, printer); dlg_update_done(ctrl, dlg); } else if (event == EVENT_VALCHANGE) { - dlg_editbox_get(ctrl, dlg, cfg->printer, sizeof(cfg->printer)); - if (!strcmp(cfg->printer, PRINTER_DISABLED_STRING)) - *cfg->printer = '\0'; + char *printer = dlg_editbox_get(ctrl, dlg); + if (!strcmp(printer, PRINTER_DISABLED_STRING)) + printer[0] = '\0'; + conf_set_str(conf, CONF_printer, printer); + sfree(printer); } } static void codepage_handler(union control *ctrl, void *dlg, void *data, int event) { - Config *cfg = (Config *)data; + Conf *conf = (Conf *)data; if (event == EVENT_REFRESH) { int i; const char *cp, *thiscp; dlg_update_start(ctrl, dlg); - thiscp = cp_name(decode_codepage(cfg->line_codepage)); + thiscp = cp_name(decode_codepage(conf_get_str(conf, + CONF_line_codepage))); dlg_listbox_clear(ctrl, dlg); for (i = 0; (cp = cp_enumerate(i)) != NULL; i++) dlg_listbox_add(ctrl, dlg, cp); dlg_editbox_set(ctrl, dlg, thiscp); - strcpy(cfg->line_codepage, thiscp); + conf_set_str(conf, CONF_line_codepage, thiscp); dlg_update_done(ctrl, dlg); } else if (event == EVENT_VALCHANGE) { - dlg_editbox_get(ctrl, dlg, cfg->line_codepage, - sizeof(cfg->line_codepage)); - strcpy(cfg->line_codepage, - cp_name(decode_codepage(cfg->line_codepage))); + char *codepage = dlg_editbox_get(ctrl, dlg); + conf_set_str(conf, CONF_line_codepage, + cp_name(decode_codepage(codepage))); + sfree(codepage); } } static void sshbug_handler(union control *ctrl, void *dlg, void *data, int event) { + Conf *conf = (Conf *)data; if (event == EVENT_REFRESH) { + /* + * We must fetch the previously configured value from the Conf + * before we start modifying the drop-down list, otherwise the + * spurious SELCHANGE we trigger in the process will overwrite + * the value we wanted to keep. + */ + int oldconf = conf_get_int(conf, ctrl->listbox.context.i); dlg_update_start(ctrl, dlg); dlg_listbox_clear(ctrl, dlg); dlg_listbox_addwithid(ctrl, dlg, "Auto", AUTO); dlg_listbox_addwithid(ctrl, dlg, "Off", FORCE_OFF); dlg_listbox_addwithid(ctrl, dlg, "On", FORCE_ON); - switch (*(int *)ATOFFSET(data, ctrl->listbox.context.i)) { + switch (oldconf) { case AUTO: dlg_listbox_select(ctrl, dlg, 0); break; case FORCE_OFF: dlg_listbox_select(ctrl, dlg, 1); break; case FORCE_ON: dlg_listbox_select(ctrl, dlg, 2); break; } dlg_update_done(ctrl, dlg); @@ -399,370 +551,49 @@ int i = dlg_listbox_index(ctrl, dlg); if (i < 0) i = AUTO; else i = dlg_listbox_getid(ctrl, dlg, i); - *(int *)ATOFFSET(data, ctrl->listbox.context.i) = i; + conf_set_int(conf, ctrl->listbox.context.i, i); } } -#define SAVEDSESSION_LEN 2048 - struct sessionsaver_data { union control *editbox, *listbox, *loadbutton, *savebutton, *delbutton; union control *okbutton, *cancelbutton; struct sesslist sesslist; int midsession; -}; - -/* PuTTY SC start */ -void *m_label_dlg = NULL; -void *m_cert_dlg = NULL; -void *m_keystring_dlg = NULL; -void *sc_get_label_dialog() { - return m_label_dlg; -} -void *m_label_ctrl = NULL; -void *m_cert_ctrl = NULL; -void *m_keystring_ctrl = NULL; -void *sc_get_label_ctrl() { - return m_label_ctrl; -} - -void sc_cert_handler(union control *ctrl, void *dlg, void *data, int event); -void sc_tokenlabel_handler(union control *ctrl, void *dlg, void *data, int event ) { - Config *cfg = (Config *)data; - m_label_dlg = dlg; - m_label_ctrl = ctrl; - - if(event == EVENT_REFRESH) { - dlg_update_start(ctrl, dlg); - dlg_listbox_clear(ctrl, dlg); - if(filename_is_null(cfg->pkcs11_libfile)) { - strcpy(cfg->pkcs11_token_label, ""); - dlg_listbox_add(ctrl, dlg, ""); - } else { - int i; - CK_RV rv = 0; - HINSTANCE hLib = LoadLibrary((char *)&cfg->pkcs11_libfile); - CK_C_GetFunctionList pGFL = (CK_RV (*)(CK_FUNCTION_LIST_PTR_PTR))GetProcAddress(hLib, "C_GetFunctionList"); - if (pGFL == NULL) { - strcpy(cfg->pkcs11_token_label, ""); - dlg_listbox_add(ctrl, dlg, ""); - } else { - CK_FUNCTION_LIST_PTR fl = 0; - rv = pGFL(&fl); - if(rv != CKR_OK) { - strcpy(cfg->pkcs11_token_label, ""); - dlg_listbox_add(ctrl, dlg, ""); - } else { - int rv1, rv2; - unsigned long slot_count = 16; - CK_SLOT_ID slots[16]; - rv1 = fl->C_Initialize(0); - rv2 = fl->C_GetSlotList(TRUE, slots, &slot_count); - if((rv1 != CKR_OK) || (rv2 != CKR_OK)) { - strcpy(cfg->pkcs11_token_label, ""); - dlg_listbox_add(ctrl, dlg, ""); - } else { - if(slot_count == 0) { - strcpy(cfg->pkcs11_token_label, ""); - dlg_listbox_add(ctrl, dlg, ""); - } - for(i=0; iC_GetTokenInfo(slot,&token_info); - { - char buf[40]; - int j; - memset(buf, 0, 40); - strncpy(buf, token_info.label, 30); - for(j=29;j>0;j--) { - if(buf[j] == ' ') { - buf[j] = '\0'; - } else { - break; - } - } - dlg_listbox_add(ctrl, dlg, buf); - } - } - } - fl->C_Finalize(0); - } - } - FreeLibrary(hLib); - } - dlg_editbox_set(ctrl, dlg, cfg->pkcs11_token_label); - dlg_update_done(ctrl, dlg); - } else if (event == EVENT_VALCHANGE) { - char buf[70]; - dlg_editbox_get(ctrl, dlg, buf, - sizeof(buf)); - if(strncmp(buf, "pkcs11_token_label, buf); - } - } - if(m_cert_dlg != NULL) { - sc_cert_handler(m_cert_ctrl, m_cert_dlg, data, EVENT_REFRESH); - } - -} - -void sc_keystring_handler(union control *ctrl, void *dlg, void *data, int event ) { - m_keystring_dlg = dlg; - m_keystring_ctrl = ctrl; -} - -void sc_cert_handler(union control *ctrl, void *dlg, void *data, int event ) { - Config *cfg = (Config *)data; - m_cert_dlg = dlg; - m_cert_ctrl = ctrl; - - if(event == EVENT_REFRESH) { - dlg_update_start(ctrl, dlg); - dlg_listbox_clear(ctrl, dlg); - if(cfg->pkcs11_token_label == NULL || - strlen(cfg->pkcs11_token_label) == 0) { - strcpy(cfg->pkcs11_token_label, ""); - dlg_listbox_add(ctrl, dlg, ""); - } else { - sc_lib *sclib; - if (cfg->sclib == NULL) { cfg->sclib = calloc(sizeof(sc_lib), 1); } - sclib = cfg->sclib; - sc_init_library(NULL, 0, sclib, &cfg->pkcs11_libfile); - if(sclib->m_fl) { - CK_SESSION_HANDLE session = sc_get_session(NULL, 0, sclib->m_fl, cfg->pkcs11_token_label); - if(session) { - char msg[1024] = ""; - sc_cert_list *pcl; - sc_cert_list *cl = sc_get_cert_list(sclib, session, msg); - pcl = cl; - while(pcl != NULL) { - char *p_buf; - p_buf = calloc(1,pcl->cert_attr[0].ulValueLen+1); - strncpy(p_buf, pcl->cert_attr[0].pValue, pcl->cert_attr[0].ulValueLen); - dlg_listbox_add(ctrl, dlg, p_buf); - free(p_buf); - pcl = pcl->next; - } - sc_free_cert_list(cl); - // sclib->m_fl->C_CloseSession(session); - } - // sclib->m_fl->C_Finalize(0); - } - // free(sclib); - } - dlg_editbox_set(ctrl, dlg, cfg->pkcs11_cert_label); - dlg_update_done(ctrl, dlg); - } else if (event == EVENT_VALCHANGE) { - sc_lib *sclib; - char token_label[70]; - char cert_label[70]; - int blob_len; - char *algorithm; /* minor memory leak */ - sclib = cfg->sclib; - dlg_editbox_get(m_label_ctrl, m_label_dlg, token_label, sizeof(token_label)); - dlg_editbox_get(ctrl, dlg, cert_label, sizeof(cert_label)); - if(strncmp(cert_label, "pkcs11_cert_label, cert_label); - sc_get_pub(NULL, 0, sclib, token_label, cert_label, &algorithm, &blob_len); - if (m_keystring_dlg != NULL && sclib && sclib->keystring != NULL) { - dlg_editbox_set(m_keystring_ctrl, m_keystring_dlg, sclib->keystring); - } - if (sclib == NULL) { - dlg_editbox_set(m_keystring_ctrl, m_keystring_dlg, "no sclib"); - } else - if (sclib->keystring == NULL) { - dlg_editbox_set(m_keystring_ctrl, m_keystring_dlg, "no sclib keystring"); - } - } - } -} -/* PuTTY SC end */ - -/* PuTTY CAPI begin */ -#ifdef _WINDOWS -struct capi_data { - union control *certstore_droplist, *certID_text, *cert_browse, *keystring_text; -}; - -void capi_certstore_handler(union control *ctrl, void *dlg, void *data, int event ) { - Config *cfg = (Config *)data; - struct capi_data *capid = (struct capi_data *)ctrl->generic.context.p; - - if (event == EVENT_REFRESH) { - if (ctrl == capid->certstore_droplist) { - dlg_update_start(ctrl, dlg); - dlg_listbox_clear(ctrl, dlg); - dlg_listbox_add(ctrl, dlg, "User\\MY (Personal Certificates)"); - dlg_listbox_add(ctrl, dlg, "System\\MY (Personal Certificates)"); - if (strncmp(cfg->capi_certID, "System\\MY", 9) == 0) - dlg_listbox_select(ctrl, dlg, 1); - else - dlg_listbox_select(ctrl, dlg, 0); /* *shrug* */ - dlg_update_done(ctrl, dlg); - } - } -} - -void capi_certID_handler(union control *ctrl, void *dlg, void *data, int event ) { - Config *cfg = (Config *)data; - struct capi_data *capid = (struct capi_data *)ctrl->generic.context.p; - char* tmpKeystring = NULL; - - if (event == EVENT_REFRESH) { - dlg_editbox_set(ctrl, dlg, cfg->capi_certID); - } else if (event == EVENT_VALCHANGE) { - dlg_editbox_get(ctrl, dlg, cfg->capi_certID, sizeof(cfg->capi_certID)); - } - - if (cfg->capi_certID[0]) { - if ((tmpKeystring = capi_get_key_string(cfg->capi_certID)) != NULL) { - dlg_editbox_set(capid->keystring_text, dlg, tmpKeystring); - free(tmpKeystring); - tmpKeystring = NULL; - } - } -} - -typedef BOOL (WINAPI *PCertSelectCertificateA)( -__inout PCERT_SELECT_STRUCT_A pCertSelectInfo -); - -void capi_certstore_browse_handler(union control *ctrl, void *dlg, void *data, int event ) { - Config *cfg = (Config *)data; - struct capi_data *capid = (struct capi_data *)ctrl->generic.context.p; - HCERTSTORE hStore = NULL; - CERT_SELECT_STRUCT_A* css = NULL; - CERT_CONTEXT** acc = NULL; - unsigned int tmpSHA1size = 0, dwCertStoreUser; - unsigned char tmpSHA1[20]; - char tmpSHA1hex[41] = ""; - char tmpCertID[100] = ""; - char* tmpKeystring = NULL; - HMODULE hCertDlgDLL = NULL; - PCertSelectCertificateA f_csca = NULL; - int i; - - if (event == EVENT_ACTION) { - i = dlg_listbox_index(capid->certstore_droplist, dlg); - if (i < 0) - goto cleanup; - - if ((hCertDlgDLL = LoadLibrary("CryptDlg.dll")) == NULL) - goto cleanup; - if ((f_csca = (PCertSelectCertificateA) GetProcAddress(hCertDlgDLL, "CertSelectCertificateA")) == NULL) - goto cleanup; - - dwCertStoreUser = CERT_SYSTEM_STORE_CURRENT_USER; - if (i == 1) - dwCertStoreUser = CERT_SYSTEM_STORE_LOCAL_MACHINE; - - if ((hStore = CertOpenStore(CERT_STORE_PROV_SYSTEM_A, PKCS_7_ASN_ENCODING | X509_ASN_ENCODING, 0 /*hCryptProv*/, dwCertStoreUser | CERT_STORE_READONLY_FLAG | CERT_STORE_OPEN_EXISTING_FLAG | CERT_STORE_ENUM_ARCHIVED_FLAG, "MY")) == NULL) - goto cleanup; - - acc = (CERT_CONTEXT**) malloc(sizeof(CERT_CONTEXT*)); - acc[0] = NULL; - css = (CERT_SELECT_STRUCT_A*) malloc(sizeof(CERT_SELECT_STRUCT_A)); - memset(css, 0, sizeof(CERT_SELECT_STRUCT_A)); - css->dwSize = sizeof(CERT_SELECT_STRUCT_A); - css->hwndParent = ((struct dlgparam *) dlg)->hwnd; - css->hInstance = NULL; - css->pTemplateName = NULL; - css->dwFlags = 0; - css->szTitle = "PuTTY: Select Certificate for CAPI Auth"; - css->cCertStore = 1; - css->arrayCertStore = &hStore; - css->szPurposeOid = szOID_PKIX_KP_CLIENT_AUTH; - css->cCertContext = 1; // count of arrayCertContext indexes allocated - css->arrayCertContext = acc; - - if (!f_csca(css)) // GetProcAddress(hCertDlgDLL, "CertSelectCertificateA") - goto cleanup; - - if (css->cCertContext != 1) - goto cleanup; - if (acc[0] == NULL) - goto cleanup; - - tmpSHA1size = sizeof(tmpSHA1); - if (!CertGetCertificateContextProperty(acc[0], CERT_HASH_PROP_ID, tmpSHA1, &tmpSHA1size)) - memset(tmpSHA1, 0, sizeof(tmpSHA1)); - _snprintf(tmpSHA1hex, sizeof(tmpSHA1hex)-1, "%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X", tmpSHA1[0], tmpSHA1[1], tmpSHA1[2], tmpSHA1[3], tmpSHA1[4], tmpSHA1[5], tmpSHA1[6], tmpSHA1[7], tmpSHA1[8], tmpSHA1[9], tmpSHA1[10], tmpSHA1[11], tmpSHA1[12], tmpSHA1[13], tmpSHA1[14], tmpSHA1[15], tmpSHA1[16], tmpSHA1[17], tmpSHA1[18], tmpSHA1[19]); - tmpSHA1hex[sizeof(tmpSHA1hex)-1] = '\0'; - _snprintf(tmpCertID, sizeof(tmpCertID)-1, "%s\\%s", i == 1 ? "Machine\\MY" : "User\\MY", tmpSHA1hex); - tmpCertID[sizeof(tmpCertID)-1] = '\0'; - dlg_editbox_set(capid->certID_text, dlg, tmpCertID); - - strncpy(cfg->capi_certID, tmpCertID, sizeof(cfg->capi_certID)); - cfg->capi_certID[sizeof(cfg->capi_certID)-1] = '\0'; - - if ((tmpKeystring = capi_get_key_string(tmpCertID)) != NULL) { - dlg_editbox_set(capid->keystring_text, dlg, tmpKeystring); - free(tmpKeystring); - tmpKeystring = NULL; - } - } -cleanup: - if (hCertDlgDLL) { - FreeLibrary(hCertDlgDLL); - f_csca = NULL; - hCertDlgDLL = NULL; - } - if (acc) { - if (acc[0]) - CertFreeCertificateContext(acc[0]); - acc[0] = NULL; - free(acc); - acc = NULL; - } - - if (css) - free(css); - css = NULL; - - if (hStore) - CertCloseStore(hStore, 0); - hStore = NULL; - - return; -} -#endif -/* PuTTY CAPI end */ - + char *savedsession; /* the current contents of ssd->editbox */ +}; + +static void sessionsaver_data_free(void *ssdv) +{ + struct sessionsaver_data *ssd = (struct sessionsaver_data *)ssdv; + sfree(ssd->savedsession); + sfree(ssd); +} /* * Helper function to load the session selected in the list box, if * any, as this is done in more than one place below. Returns 0 for * failure. */ static int load_selected_session(struct sessionsaver_data *ssd, - char *savedsession, - void *dlg, Config *cfg, int *maybe_launch) + void *dlg, Conf *conf, int *maybe_launch) { int i = dlg_listbox_index(ssd->listbox, dlg); int isdef; if (i < 0) { dlg_beep(dlg); return 0; } isdef = !strcmp(ssd->sesslist.sessions[i], "Default Settings"); - load_settings(ssd->sesslist.sessions[i], cfg); - if (!isdef) { - strncpy(savedsession, ssd->sesslist.sessions[i], - SAVEDSESSION_LEN); - savedsession[SAVEDSESSION_LEN-1] = '\0'; - if (maybe_launch) - *maybe_launch = TRUE; - } else { - savedsession[0] = '\0'; - if (maybe_launch) - *maybe_launch = FALSE; - } + load_settings(ssd->sesslist.sessions[i], conf); + sfree(ssd->savedsession); + ssd->savedsession = dupstr(isdef ? "" : ssd->sesslist.sessions[i]); + if (maybe_launch) + *maybe_launch = !isdef; dlg_refresh(NULL, dlg); /* Restore the selection, which might have been clobbered by * changing the value of the edit box. */ dlg_listbox_select(ssd->listbox, dlg, i); return 1; @@ -769,34 +600,17 @@ } static void sessionsaver_handler(union control *ctrl, void *dlg, void *data, int event) { - Config *cfg = (Config *)data; + Conf *conf = (Conf *)data; struct sessionsaver_data *ssd = (struct sessionsaver_data *)ctrl->generic.context.p; - char *savedsession; - - /* - * The first time we're called in a new dialog, we must - * allocate space to store the current contents of the saved - * session edit box (since it must persist even when we switch - * panels, but is not part of the Config). - */ - if (!ssd->editbox) { - savedsession = NULL; - } else if (!dlg_get_privdata(ssd->editbox, dlg)) { - savedsession = (char *) - dlg_alloc_privdata(ssd->editbox, dlg, SAVEDSESSION_LEN); - savedsession[0] = '\0'; - } else { - savedsession = dlg_get_privdata(ssd->editbox, dlg); - } if (event == EVENT_REFRESH) { if (ctrl == ssd->editbox) { - dlg_editbox_set(ctrl, dlg, savedsession); + dlg_editbox_set(ctrl, dlg, ssd->savedsession); } else if (ctrl == ssd->listbox) { int i; dlg_update_start(ctrl, dlg); dlg_listbox_clear(ctrl, dlg); for (i = 0; i < ssd->sesslist.nsessions; i++) @@ -804,17 +618,17 @@ dlg_update_done(ctrl, dlg); } } else if (event == EVENT_VALCHANGE) { int top, bottom, halfway, i; if (ctrl == ssd->editbox) { - dlg_editbox_get(ctrl, dlg, savedsession, - SAVEDSESSION_LEN); + sfree(ssd->savedsession); + ssd->savedsession = dlg_editbox_get(ctrl, dlg); top = ssd->sesslist.nsessions; bottom = -1; while (top-bottom > 1) { halfway = (top+bottom)/2; - i = strcmp(savedsession, ssd->sesslist.sessions[halfway]); + i = strcmp(ssd->savedsession, ssd->sesslist.sessions[halfway]); if (i <= 0 ) { top = halfway; } else { bottom = halfway; } @@ -834,33 +648,29 @@ * We must load the selected session, and then * terminate the configuration dialog _if_ there was a * double-click on the list box _and_ that session * contains a hostname. */ - if (load_selected_session(ssd, savedsession, dlg, cfg, &mbl) && - (mbl && ctrl == ssd->listbox && cfg_launchable(cfg))) { + if (load_selected_session(ssd, dlg, conf, &mbl) && + (mbl && ctrl == ssd->listbox && conf_launchable(conf))) { dlg_end(dlg, 1); /* it's all over, and succeeded */ } } else if (ctrl == ssd->savebutton) { - int isdef = !strcmp(savedsession, "Default Settings"); - if (!savedsession[0]) { + int isdef = !strcmp(ssd->savedsession, "Default Settings"); + if (!ssd->savedsession[0]) { int i = dlg_listbox_index(ssd->listbox, dlg); if (i < 0) { dlg_beep(dlg); return; } isdef = !strcmp(ssd->sesslist.sessions[i], "Default Settings"); - if (!isdef) { - strncpy(savedsession, ssd->sesslist.sessions[i], - SAVEDSESSION_LEN); - savedsession[SAVEDSESSION_LEN-1] = '\0'; - } else { - savedsession[0] = '\0'; - } + sfree(ssd->savedsession); + ssd->savedsession = dupstr(isdef ? "" : + ssd->sesslist.sessions[i]); } { - char *errmsg = save_settings(savedsession, cfg); + char *errmsg = save_settings(ssd->savedsession, conf); if (errmsg) { dlg_error_msg(dlg, errmsg); sfree(errmsg); } } @@ -891,33 +701,34 @@ * the session list previously had the focus, _and_ * there was a session selected in that which had a * valid host name in it, then load it and go. */ if (dlg_last_focused(ctrl, dlg) == ssd->listbox && - !cfg_launchable(cfg)) { - Config cfg2; + !conf_launchable(conf)) { + Conf *conf2 = conf_new(); int mbl = FALSE; - if (!load_selected_session(ssd, savedsession, dlg, - &cfg2, &mbl)) { + if (!load_selected_session(ssd, dlg, conf2, &mbl)) { dlg_beep(dlg); + conf_free(conf2); return; } /* If at this point we have a valid session, go! */ - if (mbl && cfg_launchable(&cfg2)) { - *cfg = cfg2; /* structure copy */ - cfg->remote_cmd_ptr = NULL; + if (mbl && conf_launchable(conf2)) { + conf_copy_into(conf, conf2); dlg_end(dlg, 1); } else dlg_beep(dlg); + + conf_free(conf2); return; } /* * Otherwise, do the normal thing: if we have a valid * session, get going. */ - if (cfg_launchable(cfg)) { + if (conf_launchable(conf)) { dlg_end(dlg, 1); } else dlg_beep(dlg); } else if (ctrl == ssd->cancelbutton) { dlg_end(dlg, 0); @@ -930,11 +741,11 @@ }; static void charclass_handler(union control *ctrl, void *dlg, void *data, int event) { - Config *cfg = (Config *)data; + Conf *conf = (Conf *)data; struct charclass_data *ccd = (struct charclass_data *)ctrl->generic.context.p; if (event == EVENT_REFRESH) { if (ctrl == ccd->listbox) { @@ -942,24 +753,26 @@ dlg_update_start(ctrl, dlg); dlg_listbox_clear(ctrl, dlg); for (i = 0; i < 128; i++) { char str[100]; sprintf(str, "%d\t(0x%02X)\t%c\t%d", i, i, - (i >= 0x21 && i != 0x7F) ? i : ' ', cfg->wordness[i]); + (i >= 0x21 && i != 0x7F) ? i : ' ', + conf_get_int_int(conf, CONF_wordness, i)); dlg_listbox_add(ctrl, dlg, str); } dlg_update_done(ctrl, dlg); } } else if (event == EVENT_ACTION) { if (ctrl == ccd->button) { - char str[100]; + char *str; int i, n; - dlg_editbox_get(ccd->editbox, dlg, str, sizeof(str)); + str = dlg_editbox_get(ccd->editbox, dlg); n = atoi(str); + sfree(str); for (i = 0; i < 128; i++) { if (dlg_listbox_issel(ccd->listbox, dlg, i)) - cfg->wordness[i] = n; + conf_set_int_int(conf, CONF_wordness, i, n); } dlg_refresh(ccd->listbox, dlg); } } } @@ -983,11 +796,11 @@ }; static void colour_handler(union control *ctrl, void *dlg, void *data, int event) { - Config *cfg = (Config *)data; + Conf *conf = (Conf *)data; struct colour_data *cd = (struct colour_data *)ctrl->generic.context.p; int update = FALSE, clear = FALSE, r, g, b; if (event == EVENT_REFRESH) { @@ -1007,35 +820,36 @@ int i = dlg_listbox_index(ctrl, dlg); if (i < 0) { clear = TRUE; } else { clear = FALSE; - r = cfg->colours[i][0]; - g = cfg->colours[i][1]; - b = cfg->colours[i][2]; + r = conf_get_int_int(conf, CONF_colours, i*3+0); + g = conf_get_int_int(conf, CONF_colours, i*3+1); + b = conf_get_int_int(conf, CONF_colours, i*3+2); } update = TRUE; } } else if (event == EVENT_VALCHANGE) { if (ctrl == cd->redit || ctrl == cd->gedit || ctrl == cd->bedit) { /* The user has changed the colour using the edit boxes. */ - char buf[80]; + char *str; int i, cval; - dlg_editbox_get(ctrl, dlg, buf, lenof(buf)); - cval = atoi(buf); + str = dlg_editbox_get(ctrl, dlg); + cval = atoi(str); + sfree(str); if (cval > 255) cval = 255; if (cval < 0) cval = 0; i = dlg_listbox_index(cd->listbox, dlg); if (i >= 0) { if (ctrl == cd->redit) - cfg->colours[i][0] = cval; + conf_set_int_int(conf, CONF_colours, i*3+0, cval); else if (ctrl == cd->gedit) - cfg->colours[i][1] = cval; + conf_set_int_int(conf, CONF_colours, i*3+1, cval); else if (ctrl == cd->bedit) - cfg->colours[i][2] = cval; + conf_set_int_int(conf, CONF_colours, i*3+2, cval); } } } else if (event == EVENT_ACTION) { if (ctrl == cd->button) { int i = dlg_listbox_index(cd->listbox, dlg); @@ -1047,13 +861,13 @@ * Start a colour selector, which will send us an * EVENT_CALLBACK when it's finished and allow us to * pick up the results. */ dlg_coloursel_start(ctrl, dlg, - cfg->colours[i][0], - cfg->colours[i][1], - cfg->colours[i][2]); + conf_get_int_int(conf, CONF_colours, i*3+0), + conf_get_int_int(conf, CONF_colours, i*3+1), + conf_get_int_int(conf, CONF_colours, i*3+2)); } } else if (event == EVENT_CALLBACK) { if (ctrl == cd->button) { int i = dlg_listbox_index(cd->listbox, dlg); /* @@ -1060,13 +874,13 @@ * Collect the results of the colour selector. Will * return nonzero on success, or zero if the colour * selector did nothing (user hit Cancel, for example). */ if (dlg_coloursel_results(ctrl, dlg, &r, &g, &b)) { - cfg->colours[i][0] = r; - cfg->colours[i][1] = g; - cfg->colours[i][2] = b; + conf_set_int_int(conf, CONF_colours, i*3+0, r); + conf_set_int_int(conf, CONF_colours, i*3+1, g); + conf_set_int_int(conf, CONF_colours, i*3+2, b); clear = FALSE; update = TRUE; } } } @@ -1091,26 +905,25 @@ }; static void ttymodes_handler(union control *ctrl, void *dlg, void *data, int event) { - Config *cfg = (Config *)data; + Conf *conf = (Conf *)data; struct ttymodes_data *td = (struct ttymodes_data *)ctrl->generic.context.p; if (event == EVENT_REFRESH) { if (ctrl == td->listbox) { - char *p = cfg->ttymodes; + char *key, *val; dlg_update_start(ctrl, dlg); dlg_listbox_clear(ctrl, dlg); - while (*p) { - int tabpos = strchr(p, '\t') - p; - char *disp = dupprintf("%.*s\t%s", tabpos, p, - (p[tabpos+1] == 'A') ? "(auto)" : - p+tabpos+2); + for (val = conf_get_str_strs(conf, CONF_ttymodes, NULL, &key); + val != NULL; + val = conf_get_str_strs(conf, CONF_ttymodes, key, &key)) { + char *disp = dupprintf("%s\t%s", key, + (val[0] == 'A') ? "(auto)" : val+1); dlg_listbox_add(ctrl, dlg, disp); - p += strlen(p) + 1; sfree(disp); } dlg_update_done(ctrl, dlg); } else if (ctrl == td->modelist) { int i; @@ -1126,77 +939,51 @@ } else if (event == EVENT_ACTION) { if (ctrl == td->addbutton) { int ind = dlg_listbox_index(td->modelist, dlg); if (ind >= 0) { char type = dlg_radiobutton_get(td->valradio, dlg) ? 'V' : 'A'; - int slen, left; - char *p, str[lenof(cfg->ttymodes)]; + const char *key; + char *str, *val; /* Construct new entry */ - memset(str, 0, lenof(str)); - strncpy(str, ttymodes[ind], lenof(str)-3); - slen = strlen(str); - str[slen] = '\t'; - str[slen+1] = type; - slen += 2; - if (type == 'V') { - dlg_editbox_get(td->valbox, dlg, str+slen, lenof(str)-slen); - } - /* Find end of list, deleting any existing instance */ - p = cfg->ttymodes; - left = lenof(cfg->ttymodes); - while (*p) { - int t = strchr(p, '\t') - p; - if (t == strlen(ttymodes[ind]) && - strncmp(p, ttymodes[ind], t) == 0) { - memmove(p, p+strlen(p)+1, left - (strlen(p)+1)); - continue; - } - left -= strlen(p) + 1; - p += strlen(p) + 1; - } - /* Append new entry */ - memset(p, 0, left); - strncpy(p, str, left - 2); + key = ttymodes[ind]; + str = dlg_editbox_get(td->valbox, dlg); + val = dupprintf("%c%s", type, str); + sfree(str); + conf_set_str_str(conf, CONF_ttymodes, key, val); + sfree(val); dlg_refresh(td->listbox, dlg); } else dlg_beep(dlg); } else if (ctrl == td->rembutton) { - char *p = cfg->ttymodes; - int i = 0, len = lenof(cfg->ttymodes); - while (*p) { - int multisel = dlg_listbox_index(td->listbox, dlg) < 0; + int i = 0; + char *key, *val; + int multisel = dlg_listbox_index(td->listbox, dlg) < 0; + for (val = conf_get_str_strs(conf, CONF_ttymodes, NULL, &key); + val != NULL; + val = conf_get_str_strs(conf, CONF_ttymodes, key, &key)) { if (dlg_listbox_issel(td->listbox, dlg, i)) { if (!multisel) { /* Populate controls with entry we're about to * delete, for ease of editing. * (If multiple entries were selected, don't * touch the controls.) */ - char *val = strchr(p, '\t'); - if (val) { - int ind = 0; - val++; - while (ttymodes[ind]) { - if (strlen(ttymodes[ind]) == val-p-1 && - !strncmp(ttymodes[ind], p, val-p-1)) - break; - ind++; - } - dlg_listbox_select(td->modelist, dlg, ind); - dlg_radiobutton_set(td->valradio, dlg, - (*val == 'V')); - dlg_editbox_set(td->valbox, dlg, val+1); - } - } - memmove(p, p+strlen(p)+1, len - (strlen(p)+1)); - i++; - continue; - } - len -= strlen(p) + 1; - p += strlen(p) + 1; - i++; - } - memset(p, 0, lenof(cfg->ttymodes) - len); + int ind = 0; + val++; + while (ttymodes[ind]) { + if (!strcmp(ttymodes[ind], key)) + break; + ind++; + } + dlg_listbox_select(td->modelist, dlg, ind); + dlg_radiobutton_set(td->valradio, dlg, + (*val == 'V')); + dlg_editbox_set(td->valbox, dlg, val+1); + } + conf_del_str_str(conf, CONF_ttymodes, key); + } + i++; + } dlg_refresh(td->listbox, dlg); } } } @@ -1205,100 +992,71 @@ }; static void environ_handler(union control *ctrl, void *dlg, void *data, int event) { - Config *cfg = (Config *)data; + Conf *conf = (Conf *)data; struct environ_data *ed = (struct environ_data *)ctrl->generic.context.p; if (event == EVENT_REFRESH) { if (ctrl == ed->listbox) { - char *p = cfg->environmt; + char *key, *val; dlg_update_start(ctrl, dlg); dlg_listbox_clear(ctrl, dlg); - while (*p) { + for (val = conf_get_str_strs(conf, CONF_environmt, NULL, &key); + val != NULL; + val = conf_get_str_strs(conf, CONF_environmt, key, &key)) { + char *p = dupprintf("%s\t%s", key, val); dlg_listbox_add(ctrl, dlg, p); - p += strlen(p) + 1; + sfree(p); } dlg_update_done(ctrl, dlg); } } else if (event == EVENT_ACTION) { if (ctrl == ed->addbutton) { - char str[sizeof(cfg->environmt)]; - char *p; - dlg_editbox_get(ed->varbox, dlg, str, sizeof(str)-1); - if (!*str) { - dlg_beep(dlg); - return; - } - p = str + strlen(str); - *p++ = '\t'; - dlg_editbox_get(ed->valbox, dlg, p, sizeof(str)-1 - (p - str)); - if (!*p) { - dlg_beep(dlg); - return; - } - p = cfg->environmt; - while (*p) { - while (*p) - p++; - p++; - } - if ((p - cfg->environmt) + strlen(str) + 2 < - sizeof(cfg->environmt)) { - strcpy(p, str); - p[strlen(str) + 1] = '\0'; - dlg_listbox_add(ed->listbox, dlg, str); - dlg_editbox_set(ed->varbox, dlg, ""); - dlg_editbox_set(ed->valbox, dlg, ""); - } else { - dlg_error_msg(dlg, "Environment too big"); - } + char *key, *val, *str; + key = dlg_editbox_get(ed->varbox, dlg); + if (!*key) { + sfree(key); + dlg_beep(dlg); + return; + } + val = dlg_editbox_get(ed->valbox, dlg); + if (!*val) { + sfree(key); + sfree(val); + dlg_beep(dlg); + return; + } + conf_set_str_str(conf, CONF_environmt, key, val); + str = dupcat(key, "\t", val, NULL); + dlg_editbox_set(ed->varbox, dlg, ""); + dlg_editbox_set(ed->valbox, dlg, ""); + sfree(str); + sfree(key); + sfree(val); + dlg_refresh(ed->listbox, dlg); } else if (ctrl == ed->rembutton) { int i = dlg_listbox_index(ed->listbox, dlg); if (i < 0) { dlg_beep(dlg); } else { - char *p, *q, *str; - - dlg_listbox_del(ed->listbox, dlg, i); - p = cfg->environmt; - while (i > 0) { - if (!*p) - goto disaster; - while (*p) - p++; - p++; - i--; - } - q = p; - if (!*p) - goto disaster; - /* Populate controls with the entry we're about to delete - * for ease of editing */ - str = p; - p = strchr(p, '\t'); - if (!p) - goto disaster; - *p = '\0'; - dlg_editbox_set(ed->varbox, dlg, str); - p++; - str = p; - dlg_editbox_set(ed->valbox, dlg, str); - p = strchr(p, '\0'); - if (!p) - goto disaster; - p++; - while (*p) { - while (*p) - *q++ = *p++; - *q++ = *p++; - } - *q = '\0'; - disaster:; - } + char *key, *val; + + key = conf_get_str_nthstrkey(conf, CONF_environmt, i); + if (key) { + /* Populate controls with the entry we're about to delete + * for ease of editing */ + val = conf_get_str_str(conf, CONF_environmt, key); + dlg_editbox_set(ed->varbox, dlg, key); + dlg_editbox_set(ed->valbox, dlg, val); + /* And delete it */ + conf_del_str_str(conf, CONF_environmt, key); + } + } + dlg_refresh(ed->listbox, dlg); } } } struct portfwd_data { @@ -1310,22 +1068,43 @@ }; static void portfwd_handler(union control *ctrl, void *dlg, void *data, int event) { - Config *cfg = (Config *)data; + Conf *conf = (Conf *)data; struct portfwd_data *pfd = (struct portfwd_data *)ctrl->generic.context.p; if (event == EVENT_REFRESH) { if (ctrl == pfd->listbox) { - char *p = cfg->portfwd; + char *key, *val; dlg_update_start(ctrl, dlg); dlg_listbox_clear(ctrl, dlg); - while (*p) { + for (val = conf_get_str_strs(conf, CONF_portfwd, NULL, &key); + val != NULL; + val = conf_get_str_strs(conf, CONF_portfwd, key, &key)) { + char *p; + if (!strcmp(val, "D")) { + char *L; + /* + * A dynamic forwarding is stored as L12345=D or + * 6L12345=D (since it's mutually exclusive with + * L12345=anything else), but displayed as D12345 + * to match the fiction that 'Local', 'Remote' and + * 'Dynamic' are three distinct modes and also to + * align with OpenSSH's command line option syntax + * that people will already be used to. So, for + * display purposes, find the L in the key string + * and turn it into a D. + */ + p = dupprintf("%s\t", key); + L = strchr(p, 'L'); + if (L) *L = 'D'; + } else + p = dupprintf("%s\t%s", key, val); dlg_listbox_add(ctrl, dlg, p); - p += strlen(p) + 1; + sfree(p); } dlg_update_done(ctrl, dlg); } else if (ctrl == pfd->direction) { /* * Default is Local. @@ -1336,141 +1115,114 @@ dlg_radiobutton_set(ctrl, dlg, 0); #endif } } else if (event == EVENT_ACTION) { if (ctrl == pfd->addbutton) { - char str[sizeof(cfg->portfwd)]; - char *p; - int i, type; + char *family, *type, *src, *key, *val; int whichbutton; - i = 0; #ifndef NO_IPV6 whichbutton = dlg_radiobutton_get(pfd->addressfamily, dlg); if (whichbutton == 1) - str[i++] = '4'; + family = "4"; else if (whichbutton == 2) - str[i++] = '6'; + family = "6"; + else + family = ""; #endif whichbutton = dlg_radiobutton_get(pfd->direction, dlg); if (whichbutton == 0) - type = 'L'; + type = "L"; else if (whichbutton == 1) - type = 'R'; + type = "R"; else - type = 'D'; - str[i++] = type; + type = "D"; - dlg_editbox_get(pfd->sourcebox, dlg, str+i, sizeof(str) - i); - if (!str[i]) { + src = dlg_editbox_get(pfd->sourcebox, dlg); + if (!*src) { dlg_error_msg(dlg, "You need to specify a source port number"); + sfree(src); return; } - p = str + strlen(str); - if (type != 'D') { - *p++ = '\t'; - dlg_editbox_get(pfd->destbox, dlg, p, - sizeof(str) - (p - str)); - if (!*p || !strchr(p, ':')) { + if (*type != 'D') { + val = dlg_editbox_get(pfd->destbox, dlg); + if (!*val || !strchr(val, ':')) { dlg_error_msg(dlg, "You need to specify a destination address\n" "in the form \"host.name:port\""); + sfree(src); + sfree(val); return; } - } else - *p = '\0'; - p = cfg->portfwd; - while (*p) { - if (strcmp(p,str) == 0) { - dlg_error_msg(dlg, "Specified forwarding already exists"); - break; - } - while (*p) - p++; - p++; - } - if (!*p) { - if ((p - cfg->portfwd) + strlen(str) + 2 <= - sizeof(cfg->portfwd)) { - strcpy(p, str); - p[strlen(str) + 1] = '\0'; - dlg_listbox_add(pfd->listbox, dlg, str); - dlg_editbox_set(pfd->sourcebox, dlg, ""); - dlg_editbox_set(pfd->destbox, dlg, ""); - } else { - dlg_error_msg(dlg, "Too many forwardings"); - } - } + } else { + type = "L"; + val = dupstr("D"); /* special case */ + } + + key = dupcat(family, type, src, NULL); + sfree(src); + + if (conf_get_str_str_opt(conf, CONF_portfwd, key)) { + dlg_error_msg(dlg, "Specified forwarding already exists"); + } else { + conf_set_str_str(conf, CONF_portfwd, key, val); + } + + sfree(key); + sfree(val); + dlg_refresh(pfd->listbox, dlg); } else if (ctrl == pfd->rembutton) { int i = dlg_listbox_index(pfd->listbox, dlg); - if (i < 0) + if (i < 0) { dlg_beep(dlg); - else { - char *p, *q, *src, *dst; - char dir; - - dlg_listbox_del(pfd->listbox, dlg, i); - p = cfg->portfwd; - while (i > 0) { - if (!*p) - goto disaster2; - while (*p) - p++; - p++; - i--; - } - q = p; - if (!*p) - goto disaster2; - /* Populate the controls with the entry we're about to - * delete, for ease of editing. */ - { + } else { + char *key, *val, *p; + + key = conf_get_str_nthstrkey(conf, CONF_portfwd, i); + if (key) { static const char *const afs = "A46"; - char *afp = strchr(afs, *p); + static const char *const dirs = "LRD"; + char *afp; + int dir; +#ifndef NO_IPV6 + int idx; +#endif + + /* Populate controls with the entry we're about to delete + * for ease of editing */ + p = key; + + afp = strchr(afs, *p); #ifndef NO_IPV6 - int idx = afp ? afp-afs : 0; + idx = afp ? afp-afs : 0; #endif if (afp) p++; #ifndef NO_IPV6 dlg_radiobutton_set(pfd->addressfamily, dlg, idx); #endif - } - { - static const char *const dirs = "LRD"; + dir = *p; + + val = conf_get_str_str(conf, CONF_portfwd, key); + if (!strcmp(val, "D")) { + dir = 'D'; + val = ""; + } + dlg_radiobutton_set(pfd->direction, dlg, strchr(dirs, dir) - dirs); - } - p++; - if (dir != 'D') { - src = p; - p = strchr(p, '\t'); - if (!p) - goto disaster2; - *p = '\0'; - p++; - dst = p; - } else { - src = p; - dst = ""; - } - p = strchr(p, '\0'); - if (!p) - goto disaster2; - dlg_editbox_set(pfd->sourcebox, dlg, src); - dlg_editbox_set(pfd->destbox, dlg, dst); - p++; - while (*p) { - while (*p) - *q++ = *p++; - *q++ = *p++; - } - *q = '\0'; - disaster2:; - } + p++; + + dlg_editbox_set(pfd->sourcebox, dlg, p); + dlg_editbox_set(pfd->destbox, dlg, val); + /* And delete it */ + conf_del_str_str(conf, CONF_portfwd, key); + } + } + dlg_refresh(pfd->listbox, dlg); } } } void setup_config_box(struct controlbox *b, int midsession, @@ -1478,24 +1230,21 @@ { struct controlset *s; struct sessionsaver_data *ssd; struct charclass_data *ccd; struct colour_data *cd; - /* PuTTY CAPI start */ -#ifdef _WINDOWS - struct capi_data *capid; -#endif - /* PuTTY CAPI end */ struct ttymodes_data *td; struct environ_data *ed; struct portfwd_data *pfd; union control *c; char *str; ssd = (struct sessionsaver_data *) - ctrl_alloc(b, sizeof(struct sessionsaver_data)); + ctrl_alloc_with_free(b, sizeof(struct sessionsaver_data), + sessionsaver_data_free); memset(ssd, 0, sizeof(*ssd)); + ssd->savedsession = dupstr(""); ssd->midsession = midsession; /* * The standard panel that appears at the bottom of all panels: * Open, Cancel, Apply etc. @@ -1610,17 +1359,17 @@ ssd->delbutton = NULL; } ctrl_columns(s, 1, 100); s = ctrl_getset(b, "Session", "otheropts", NULL); - c = ctrl_radiobuttons(s, "Close window on exit:", 'x', 4, - HELPCTX(session_coe), - dlg_stdradiobutton_handler, - I(offsetof(Config, close_on_exit)), - "Always", I(FORCE_ON), - "Never", I(FORCE_OFF), - "Only on clean exit", I(AUTO), NULL); + ctrl_radiobuttons(s, "Close window on exit:", 'x', 4, + HELPCTX(session_coe), + conf_radiobutton_handler, + I(CONF_close_on_exit), + "Always", I(FORCE_ON), + "Never", I(FORCE_OFF), + "Only on clean exit", I(AUTO), NULL); /* * The Session/Logging panel. */ ctrl_settitle(b, "Session/Logging", "Options controlling session logging"); @@ -1641,11 +1390,11 @@ sshrawlogname = NULL; /* this will just placate optimisers */ } ctrl_radiobuttons(s, "Session logging:", NO_SHORTCUT, 2, HELPCTX(logging_main), loggingbuttons_handler, - I(offsetof(Config, logtype)), + I(CONF_logtype), "None", 't', I(LGTYP_NONE), "Printable output", 'p', I(LGTYP_ASCII), "All session output", 'l', I(LGTYP_DEBUG), sshlogname, 's', I(LGTYP_PACKETS), sshrawlogname, 'r', I(LGTYP_SSHRAW), @@ -1652,34 +1401,34 @@ NULL); } ctrl_filesel(s, "Log file name:", 'f', NULL, TRUE, "Select session log file name", HELPCTX(logging_filename), - dlg_stdfilesel_handler, I(offsetof(Config, logfilename))); + conf_filesel_handler, I(CONF_logfilename)); ctrl_text(s, "(Log file name can contain &Y, &M, &D for date," " &T for time, and &H for host name)", HELPCTX(logging_filename)); ctrl_radiobuttons(s, "What to do if the log file already exists:", 'e', 1, HELPCTX(logging_exists), - dlg_stdradiobutton_handler, I(offsetof(Config,logxfovr)), + conf_radiobutton_handler, I(CONF_logxfovr), "Always overwrite it", I(LGXF_OVR), "Always append to the end of it", I(LGXF_APN), "Ask the user every time", I(LGXF_ASK), NULL); ctrl_checkbox(s, "Flush log file frequently", 'u', HELPCTX(logging_flush), - dlg_stdcheckbox_handler, I(offsetof(Config,logflush))); + conf_checkbox_handler, I(CONF_logflush)); if ((midsession && protocol == PROT_SSH) || (!midsession && backend_from_proto(PROT_SSH))) { s = ctrl_getset(b, "Session/Logging", "ssh", "Options specific to SSH packet logging"); ctrl_checkbox(s, "Omit known password fields", 'k', HELPCTX(logging_ssh_omit_password), - dlg_stdcheckbox_handler, I(offsetof(Config,logomitpass))); + conf_checkbox_handler, I(CONF_logomitpass)); ctrl_checkbox(s, "Omit session data", 'd', HELPCTX(logging_ssh_omit_data), - dlg_stdcheckbox_handler, I(offsetof(Config,logomitdata))); + conf_checkbox_handler, I(CONF_logomitdata)); } /* * The Terminal panel. */ @@ -1686,41 +1435,40 @@ ctrl_settitle(b, "Terminal", "Options controlling the terminal emulation"); s = ctrl_getset(b, "Terminal", "general", "Set various terminal options"); ctrl_checkbox(s, "Auto wrap mode initially on", 'w', HELPCTX(terminal_autowrap), - dlg_stdcheckbox_handler, I(offsetof(Config,wrap_mode))); + conf_checkbox_handler, I(CONF_wrap_mode)); ctrl_checkbox(s, "DEC Origin Mode initially on", 'd', HELPCTX(terminal_decom), - dlg_stdcheckbox_handler, I(offsetof(Config,dec_om))); + conf_checkbox_handler, I(CONF_dec_om)); ctrl_checkbox(s, "Implicit CR in every LF", 'r', HELPCTX(terminal_lfhascr), - dlg_stdcheckbox_handler, I(offsetof(Config,lfhascr))); + conf_checkbox_handler, I(CONF_lfhascr)); ctrl_checkbox(s, "Implicit LF in every CR", 'f', HELPCTX(terminal_crhaslf), - dlg_stdcheckbox_handler, I(offsetof(Config,crhaslf))); + conf_checkbox_handler, I(CONF_crhaslf)); ctrl_checkbox(s, "Use background colour to erase screen", 'e', HELPCTX(terminal_bce), - dlg_stdcheckbox_handler, I(offsetof(Config,bce))); + conf_checkbox_handler, I(CONF_bce)); ctrl_checkbox(s, "Enable blinking text", 'n', HELPCTX(terminal_blink), - dlg_stdcheckbox_handler, I(offsetof(Config,blinktext))); + conf_checkbox_handler, I(CONF_blinktext)); ctrl_editbox(s, "Answerback to ^E:", 's', 100, HELPCTX(terminal_answerback), - dlg_stdeditbox_handler, I(offsetof(Config,answerback)), - I(sizeof(((Config *)0)->answerback))); + conf_editbox_handler, I(CONF_answerback), I(1)); s = ctrl_getset(b, "Terminal", "ldisc", "Line discipline options"); ctrl_radiobuttons(s, "Local echo:", 'l', 3, HELPCTX(terminal_localecho), - dlg_stdradiobutton_handler,I(offsetof(Config,localecho)), + conf_radiobutton_handler,I(CONF_localecho), "Auto", I(AUTO), "Force on", I(FORCE_ON), "Force off", I(FORCE_OFF), NULL); ctrl_radiobuttons(s, "Local line editing:", 't', 3, HELPCTX(terminal_localedit), - dlg_stdradiobutton_handler,I(offsetof(Config,localedit)), + conf_radiobutton_handler,I(CONF_localedit), "Auto", I(AUTO), "Force on", I(FORCE_ON), "Force off", I(FORCE_OFF), NULL); s = ctrl_getset(b, "Terminal", "printing", "Remote-controlled printing"); @@ -1736,31 +1484,31 @@ s = ctrl_getset(b, "Terminal/Keyboard", "mappings", "Change the sequences sent by:"); ctrl_radiobuttons(s, "The Backspace key", 'b', 2, HELPCTX(keyboard_backspace), - dlg_stdradiobutton_handler, - I(offsetof(Config, bksp_is_delete)), + conf_radiobutton_handler, + I(CONF_bksp_is_delete), "Control-H", I(0), "Control-? (127)", I(1), NULL); ctrl_radiobuttons(s, "The Home and End keys", 'e', 2, HELPCTX(keyboard_homeend), - dlg_stdradiobutton_handler, - I(offsetof(Config, rxvt_homeend)), + conf_radiobutton_handler, + I(CONF_rxvt_homeend), "Standard", I(0), "rxvt", I(1), NULL); ctrl_radiobuttons(s, "The Function keys and keypad", 'f', 3, HELPCTX(keyboard_funkeys), - dlg_stdradiobutton_handler, - I(offsetof(Config, funky_type)), + conf_radiobutton_handler, + I(CONF_funky_type), "ESC[n~", I(0), "Linux", I(1), "Xterm R6", I(2), "VT400", I(3), "VT100+", I(4), "SCO", I(5), NULL); s = ctrl_getset(b, "Terminal/Keyboard", "appkeypad", "Application keypad settings:"); ctrl_radiobuttons(s, "Initial state of cursor keys:", 'r', 3, HELPCTX(keyboard_appcursor), - dlg_stdradiobutton_handler, - I(offsetof(Config, app_cursor)), + conf_radiobutton_handler, + I(CONF_app_cursor), "Normal", I(0), "Application", I(1), NULL); ctrl_radiobuttons(s, "Initial state of numeric keypad:", 'n', 3, HELPCTX(keyboard_appkeypad), numeric_keypad_handler, P(NULL), "Normal", I(0), "Application", I(1), "NetHack", I(2), @@ -1773,32 +1521,32 @@ "Options controlling the terminal bell"); s = ctrl_getset(b, "Terminal/Bell", "style", "Set the style of bell"); ctrl_radiobuttons(s, "Action to happen when a bell occurs:", 'b', 1, HELPCTX(bell_style), - dlg_stdradiobutton_handler, I(offsetof(Config, beep)), + conf_radiobutton_handler, I(CONF_beep), "None (bell disabled)", I(BELL_DISABLED), "Make default system alert sound", I(BELL_DEFAULT), "Visual bell (flash window)", I(BELL_VISUAL), NULL); s = ctrl_getset(b, "Terminal/Bell", "overload", "Control the bell overload behaviour"); ctrl_checkbox(s, "Bell is temporarily disabled when over-used", 'd', HELPCTX(bell_overload), - dlg_stdcheckbox_handler, I(offsetof(Config,bellovl))); + conf_checkbox_handler, I(CONF_bellovl)); ctrl_editbox(s, "Over-use means this many bells...", 'm', 20, HELPCTX(bell_overload), - dlg_stdeditbox_handler, I(offsetof(Config,bellovl_n)), I(-1)); + conf_editbox_handler, I(CONF_bellovl_n), I(-1)); ctrl_editbox(s, "... in this many seconds", 't', 20, HELPCTX(bell_overload), - dlg_stdeditbox_handler, I(offsetof(Config,bellovl_t)), + conf_editbox_handler, I(CONF_bellovl_t), I(-TICKSPERSEC)); ctrl_text(s, "The bell is re-enabled after a few seconds of silence.", HELPCTX(bell_overload)); ctrl_editbox(s, "Seconds of silence required", 's', 20, HELPCTX(bell_overload), - dlg_stdeditbox_handler, I(offsetof(Config,bellovl_s)), + conf_editbox_handler, I(CONF_bellovl_s), I(-TICKSPERSEC)); /* * The Terminal/Features panel. */ @@ -1806,47 +1554,47 @@ "Enabling and disabling advanced terminal features"); s = ctrl_getset(b, "Terminal/Features", "main", NULL); ctrl_checkbox(s, "Disable application cursor keys mode", 'u', HELPCTX(features_application), - dlg_stdcheckbox_handler, I(offsetof(Config,no_applic_c))); + conf_checkbox_handler, I(CONF_no_applic_c)); ctrl_checkbox(s, "Disable application keypad mode", 'k', HELPCTX(features_application), - dlg_stdcheckbox_handler, I(offsetof(Config,no_applic_k))); + conf_checkbox_handler, I(CONF_no_applic_k)); ctrl_checkbox(s, "Disable xterm-style mouse reporting", 'x', HELPCTX(features_mouse), - dlg_stdcheckbox_handler, I(offsetof(Config,no_mouse_rep))); + conf_checkbox_handler, I(CONF_no_mouse_rep)); ctrl_checkbox(s, "Disable remote-controlled terminal resizing", 's', HELPCTX(features_resize), - dlg_stdcheckbox_handler, - I(offsetof(Config,no_remote_resize))); + conf_checkbox_handler, + I(CONF_no_remote_resize)); ctrl_checkbox(s, "Disable switching to alternate terminal screen", 'w', HELPCTX(features_altscreen), - dlg_stdcheckbox_handler, I(offsetof(Config,no_alt_screen))); + conf_checkbox_handler, I(CONF_no_alt_screen)); ctrl_checkbox(s, "Disable remote-controlled window title changing", 't', HELPCTX(features_retitle), - dlg_stdcheckbox_handler, - I(offsetof(Config,no_remote_wintitle))); + conf_checkbox_handler, + I(CONF_no_remote_wintitle)); ctrl_radiobuttons(s, "Response to remote title query (SECURITY):", 'q', 3, HELPCTX(features_qtitle), - dlg_stdradiobutton_handler, - I(offsetof(Config,remote_qtitle_action)), + conf_radiobutton_handler, + I(CONF_remote_qtitle_action), "None", I(TITLE_NONE), "Empty string", I(TITLE_EMPTY), "Window title", I(TITLE_REAL), NULL); ctrl_checkbox(s, "Disable destructive backspace on server sending ^?",'b', HELPCTX(features_dbackspace), - dlg_stdcheckbox_handler, I(offsetof(Config,no_dbackspace))); + conf_checkbox_handler, I(CONF_no_dbackspace)); ctrl_checkbox(s, "Disable remote-controlled character set configuration", - 'r', HELPCTX(features_charset), dlg_stdcheckbox_handler, - I(offsetof(Config,no_remote_charset))); + 'r', HELPCTX(features_charset), conf_checkbox_handler, + I(CONF_no_remote_charset)); ctrl_checkbox(s, "Disable Arabic text shaping", - 'l', HELPCTX(features_arabicshaping), dlg_stdcheckbox_handler, - I(offsetof(Config, arabicshaping))); + 'l', HELPCTX(features_arabicshaping), conf_checkbox_handler, + I(CONF_arabicshaping)); ctrl_checkbox(s, "Disable bidirectional text display", - 'd', HELPCTX(features_bidi), dlg_stdcheckbox_handler, - I(offsetof(Config, bidi))); + 'd', HELPCTX(features_bidi), conf_checkbox_handler, + I(CONF_bidi)); /* * The Window panel. */ str = dupprintf("Options controlling %s's window", appname); @@ -1855,36 +1603,36 @@ s = ctrl_getset(b, "Window", "size", "Set the size of the window"); ctrl_columns(s, 2, 50, 50); c = ctrl_editbox(s, "Columns", 'm', 100, HELPCTX(window_size), - dlg_stdeditbox_handler, I(offsetof(Config,width)), I(-1)); + conf_editbox_handler, I(CONF_width), I(-1)); c->generic.column = 0; c = ctrl_editbox(s, "Rows", 'r', 100, HELPCTX(window_size), - dlg_stdeditbox_handler, I(offsetof(Config,height)),I(-1)); + conf_editbox_handler, I(CONF_height),I(-1)); c->generic.column = 1; ctrl_columns(s, 1, 100); s = ctrl_getset(b, "Window", "scrollback", "Control the scrollback in the window"); ctrl_editbox(s, "Lines of scrollback", 's', 50, HELPCTX(window_scrollback), - dlg_stdeditbox_handler, I(offsetof(Config,savelines)), I(-1)); + conf_editbox_handler, I(CONF_savelines), I(-1)); ctrl_checkbox(s, "Display scrollbar", 'd', HELPCTX(window_scrollback), - dlg_stdcheckbox_handler, I(offsetof(Config,scrollbar))); + conf_checkbox_handler, I(CONF_scrollbar)); ctrl_checkbox(s, "Reset scrollback on keypress", 'k', HELPCTX(window_scrollback), - dlg_stdcheckbox_handler, I(offsetof(Config,scroll_on_key))); + conf_checkbox_handler, I(CONF_scroll_on_key)); ctrl_checkbox(s, "Reset scrollback on display activity", 'p', HELPCTX(window_scrollback), - dlg_stdcheckbox_handler, I(offsetof(Config,scroll_on_disp))); + conf_checkbox_handler, I(CONF_scroll_on_disp)); ctrl_checkbox(s, "Push erased text into scrollback", 'e', HELPCTX(window_erased), - dlg_stdcheckbox_handler, - I(offsetof(Config,erase_to_scrollback))); + conf_checkbox_handler, + I(CONF_erase_to_scrollback)); /* * The Window/Appearance panel. */ str = dupprintf("Configure the appearance of %s's window", appname); @@ -1893,37 +1641,37 @@ s = ctrl_getset(b, "Window/Appearance", "cursor", "Adjust the use of the cursor"); ctrl_radiobuttons(s, "Cursor appearance:", NO_SHORTCUT, 3, HELPCTX(appearance_cursor), - dlg_stdradiobutton_handler, - I(offsetof(Config, cursor_type)), + conf_radiobutton_handler, + I(CONF_cursor_type), "Block", 'l', I(0), "Underline", 'u', I(1), "Vertical line", 'v', I(2), NULL); ctrl_checkbox(s, "Cursor blinks", 'b', HELPCTX(appearance_cursor), - dlg_stdcheckbox_handler, I(offsetof(Config,blink_cur))); + conf_checkbox_handler, I(CONF_blink_cur)); s = ctrl_getset(b, "Window/Appearance", "font", "Font settings"); ctrl_fontsel(s, "Font used in the terminal window", 'n', HELPCTX(appearance_font), - dlg_stdfontsel_handler, I(offsetof(Config, font))); + conf_fontsel_handler, I(CONF_font)); s = ctrl_getset(b, "Window/Appearance", "mouse", "Adjust the use of the mouse pointer"); ctrl_checkbox(s, "Hide mouse pointer when typing in window", 'p', HELPCTX(appearance_hidemouse), - dlg_stdcheckbox_handler, I(offsetof(Config,hide_mouseptr))); + conf_checkbox_handler, I(CONF_hide_mouseptr)); s = ctrl_getset(b, "Window/Appearance", "border", "Adjust the window border"); ctrl_editbox(s, "Gap between text and window edge:", 'e', 20, HELPCTX(appearance_border), - dlg_stdeditbox_handler, - I(offsetof(Config,window_border)), I(-1)); + conf_editbox_handler, + I(CONF_window_border), I(-1)); /* * The Window/Behaviour panel. */ str = dupprintf("Configure the behaviour of %s's window", appname); @@ -1932,21 +1680,20 @@ s = ctrl_getset(b, "Window/Behaviour", "title", "Adjust the behaviour of the window title"); ctrl_editbox(s, "Window title:", 't', 100, HELPCTX(appearance_title), - dlg_stdeditbox_handler, I(offsetof(Config,wintitle)), - I(sizeof(((Config *)0)->wintitle))); + conf_editbox_handler, I(CONF_wintitle), I(1)); ctrl_checkbox(s, "Separate window and icon titles", 'i', HELPCTX(appearance_title), - dlg_stdcheckbox_handler, - I(CHECKBOX_INVERT | offsetof(Config,win_name_always))); + conf_checkbox_handler, + I(CHECKBOX_INVERT | CONF_win_name_always)); s = ctrl_getset(b, "Window/Behaviour", "main", NULL); ctrl_checkbox(s, "Warn before closing window", 'w', HELPCTX(behaviour_closewarn), - dlg_stdcheckbox_handler, I(offsetof(Config,warn_on_close))); + conf_checkbox_handler, I(CONF_warn_on_close)); /* * The Window/Translation panel. */ ctrl_settitle(b, "Window/Translation", @@ -1959,25 +1706,25 @@ codepage_handler, P(NULL), P(NULL)); s = ctrl_getset(b, "Window/Translation", "tweaks", NULL); ctrl_checkbox(s, "Treat CJK ambiguous characters as wide", 'w', HELPCTX(translation_cjk_ambig_wide), - dlg_stdcheckbox_handler, I(offsetof(Config,cjk_ambig_wide))); + conf_checkbox_handler, I(CONF_cjk_ambig_wide)); str = dupprintf("Adjust how %s handles line drawing characters", appname); s = ctrl_getset(b, "Window/Translation", "linedraw", str); sfree(str); ctrl_radiobuttons(s, "Handling of line drawing characters:", NO_SHORTCUT,1, HELPCTX(translation_linedraw), - dlg_stdradiobutton_handler, - I(offsetof(Config, vtmode)), + conf_radiobutton_handler, + I(CONF_vtmode), "Use Unicode line drawing code points",'u',I(VT_UNICODE), "Poor man's line drawing (+, - and |)",'p',I(VT_POORMAN), NULL); ctrl_checkbox(s, "Copy and paste line drawing characters as lqqqk",'d', HELPCTX(selection_linedraw), - dlg_stdcheckbox_handler, I(offsetof(Config,rawcnp))); + conf_checkbox_handler, I(CONF_rawcnp)); /* * The Window/Selection panel. */ ctrl_settitle(b, "Window/Selection", "Options controlling copy and paste"); @@ -1984,17 +1731,17 @@ s = ctrl_getset(b, "Window/Selection", "mouse", "Control use of mouse"); ctrl_checkbox(s, "Shift overrides application's use of mouse", 'p', HELPCTX(selection_shiftdrag), - dlg_stdcheckbox_handler, I(offsetof(Config,mouse_override))); + conf_checkbox_handler, I(CONF_mouse_override)); ctrl_radiobuttons(s, "Default selection mode (Alt+drag does the other one):", NO_SHORTCUT, 2, HELPCTX(selection_rect), - dlg_stdradiobutton_handler, - I(offsetof(Config, rect_select)), + conf_radiobutton_handler, + I(CONF_rect_select), "Normal", 'n', I(0), "Rectangular block", 'r', I(1), NULL); s = ctrl_getset(b, "Window/Selection", "charclass", "Control the select-one-word-at-a-time mode"); @@ -2028,17 +1775,21 @@ s = ctrl_getset(b, "Window/Colours", "general", "General options for colour usage"); ctrl_checkbox(s, "Allow terminal to specify ANSI colours", 'i', HELPCTX(colours_ansi), - dlg_stdcheckbox_handler, I(offsetof(Config,ansi_colour))); + conf_checkbox_handler, I(CONF_ansi_colour)); ctrl_checkbox(s, "Allow terminal to use xterm 256-colour mode", '2', - HELPCTX(colours_xterm256), dlg_stdcheckbox_handler, - I(offsetof(Config,xterm_256_colour))); - ctrl_checkbox(s, "Bolded text is a different colour", 'b', - HELPCTX(colours_bold), - dlg_stdcheckbox_handler, I(offsetof(Config,bold_colour))); + HELPCTX(colours_xterm256), conf_checkbox_handler, + I(CONF_xterm_256_colour)); + ctrl_radiobuttons(s, "Indicate bolded text by changing:", 'b', 3, + HELPCTX(colours_bold), + conf_radiobutton_handler, I(CONF_bold_style), + "The font", I(1), + "The colour", I(2), + "Both", I(3), + NULL); str = dupprintf("Adjust the precise colours %s displays", appname); s = ctrl_getset(b, "Window/Colours", "adjust", str); sfree(str); ctrl_text(s, "Select a colour from the list, and then click the" @@ -2076,31 +1827,31 @@ s = ctrl_getset(b, "Connection", "keepalive", "Sending of null packets to keep session active"); ctrl_editbox(s, "Seconds between keepalives (0 to turn off)", 'k', 20, HELPCTX(connection_keepalive), - dlg_stdeditbox_handler, I(offsetof(Config,ping_interval)), + conf_editbox_handler, I(CONF_ping_interval), I(-1)); if (!midsession) { s = ctrl_getset(b, "Connection", "tcp", "Low-level TCP connection options"); ctrl_checkbox(s, "Disable Nagle's algorithm (TCP_NODELAY option)", 'n', HELPCTX(connection_nodelay), - dlg_stdcheckbox_handler, - I(offsetof(Config,tcp_nodelay))); + conf_checkbox_handler, + I(CONF_tcp_nodelay)); ctrl_checkbox(s, "Enable TCP keepalives (SO_KEEPALIVE option)", 'p', HELPCTX(connection_tcpkeepalive), - dlg_stdcheckbox_handler, - I(offsetof(Config,tcp_keepalives))); + conf_checkbox_handler, + I(CONF_tcp_keepalives)); #ifndef NO_IPV6 s = ctrl_getset(b, "Connection", "ipversion", "Internet protocol version"); ctrl_radiobuttons(s, NULL, NO_SHORTCUT, 3, HELPCTX(connection_ipversion), - dlg_stdradiobutton_handler, - I(offsetof(Config, addressfamily)), + conf_radiobutton_handler, + I(CONF_addressfamily), "Auto", 'u', I(ADDRTYPE_UNSPEC), "IPv4", '4', I(ADDRTYPE_IPV4), "IPv6", '6', I(ADDRTYPE_IPV6), NULL); #endif @@ -2111,12 +1862,11 @@ "Logical name of remote host:"; s = ctrl_getset(b, "Connection", "identity", "Logical name of remote host"); ctrl_editbox(s, label, 'm', 100, HELPCTX(connection_loghost), - dlg_stdeditbox_handler, I(offsetof(Config,loghost)), - I(sizeof(((Config *)0)->loghost))); + conf_editbox_handler, I(CONF_loghost), I(1)); } } /* * A sub-panel Connection/Data, containing options that @@ -2127,23 +1877,22 @@ s = ctrl_getset(b, "Connection/Data", "login", "Login details"); ctrl_editbox(s, "Auto-login username", 'u', 50, HELPCTX(connection_username), - dlg_stdeditbox_handler, I(offsetof(Config,username)), - I(sizeof(((Config *)0)->username))); + conf_editbox_handler, I(CONF_username), I(1)); { /* We assume the local username is sufficiently stable * to include on the dialog box. */ char *user = get_username(); char *userlabel = dupprintf("Use system username (%s)", user ? user : ""); sfree(user); ctrl_radiobuttons(s, "When username is not specified:", 'n', 4, HELPCTX(connection_username_from_env), - dlg_stdradiobutton_handler, - I(offsetof(Config, username_from_env)), + conf_radiobutton_handler, + I(CONF_username_from_env), "Prompt", I(FALSE), userlabel, I(TRUE), NULL); sfree(userlabel); } @@ -2150,16 +1899,14 @@ s = ctrl_getset(b, "Connection/Data", "term", "Terminal details"); ctrl_editbox(s, "Terminal-type string", 't', 50, HELPCTX(connection_termtype), - dlg_stdeditbox_handler, I(offsetof(Config,termtype)), - I(sizeof(((Config *)0)->termtype))); + conf_editbox_handler, I(CONF_termtype), I(1)); ctrl_editbox(s, "Terminal speeds", 's', 50, HELPCTX(connection_termspeed), - dlg_stdeditbox_handler, I(offsetof(Config,termspeed)), - I(sizeof(((Config *)0)->termspeed))); + conf_editbox_handler, I(CONF_termspeed), I(1)); s = ctrl_getset(b, "Connection/Data", "env", "Environment variables"); ctrl_columns(s, 2, 80, 20); ed = (struct environ_data *) @@ -2201,64 +1948,59 @@ "Options controlling proxy usage"); s = ctrl_getset(b, "Connection/Proxy", "basics", NULL); ctrl_radiobuttons(s, "Proxy type:", 't', 3, HELPCTX(proxy_type), - dlg_stdradiobutton_handler, - I(offsetof(Config, proxy_type)), + conf_radiobutton_handler, + I(CONF_proxy_type), "None", I(PROXY_NONE), "SOCKS 4", I(PROXY_SOCKS4), "SOCKS 5", I(PROXY_SOCKS5), "HTTP", I(PROXY_HTTP), "Telnet", I(PROXY_TELNET), NULL); ctrl_columns(s, 2, 80, 20); c = ctrl_editbox(s, "Proxy hostname", 'y', 100, HELPCTX(proxy_main), - dlg_stdeditbox_handler, - I(offsetof(Config,proxy_host)), - I(sizeof(((Config *)0)->proxy_host))); + conf_editbox_handler, + I(CONF_proxy_host), I(1)); c->generic.column = 0; c = ctrl_editbox(s, "Port", 'p', 100, HELPCTX(proxy_main), - dlg_stdeditbox_handler, - I(offsetof(Config,proxy_port)), + conf_editbox_handler, + I(CONF_proxy_port), I(-1)); c->generic.column = 1; ctrl_columns(s, 1, 100); ctrl_editbox(s, "Exclude Hosts/IPs", 'e', 100, HELPCTX(proxy_exclude), - dlg_stdeditbox_handler, - I(offsetof(Config,proxy_exclude_list)), - I(sizeof(((Config *)0)->proxy_exclude_list))); + conf_editbox_handler, + I(CONF_proxy_exclude_list), I(1)); ctrl_checkbox(s, "Consider proxying local host connections", 'x', HELPCTX(proxy_exclude), - dlg_stdcheckbox_handler, - I(offsetof(Config,even_proxy_localhost))); + conf_checkbox_handler, + I(CONF_even_proxy_localhost)); ctrl_radiobuttons(s, "Do DNS name lookup at proxy end:", 'd', 3, HELPCTX(proxy_dns), - dlg_stdradiobutton_handler, - I(offsetof(Config, proxy_dns)), + conf_radiobutton_handler, + I(CONF_proxy_dns), "No", I(FORCE_OFF), "Auto", I(AUTO), "Yes", I(FORCE_ON), NULL); ctrl_editbox(s, "Username", 'u', 60, HELPCTX(proxy_auth), - dlg_stdeditbox_handler, - I(offsetof(Config,proxy_username)), - I(sizeof(((Config *)0)->proxy_username))); + conf_editbox_handler, + I(CONF_proxy_username), I(1)); c = ctrl_editbox(s, "Password", 'w', 60, HELPCTX(proxy_auth), - dlg_stdeditbox_handler, - I(offsetof(Config,proxy_password)), - I(sizeof(((Config *)0)->proxy_password))); + conf_editbox_handler, + I(CONF_proxy_password), I(1)); c->editbox.password = 1; ctrl_editbox(s, "Telnet command", 'm', 100, HELPCTX(proxy_command), - dlg_stdeditbox_handler, - I(offsetof(Config,proxy_telnet_command)), - I(sizeof(((Config *)0)->proxy_telnet_command))); + conf_editbox_handler, + I(CONF_proxy_telnet_command), I(1)); } /* * The Telnet panel exists in the base config box, and in a * mid-session reconfig box _if_ we're using Telnet. @@ -2275,28 +2017,28 @@ if (!midsession) { ctrl_radiobuttons(s, "Handling of OLD_ENVIRON ambiguity:", NO_SHORTCUT, 2, HELPCTX(telnet_oldenviron), - dlg_stdradiobutton_handler, - I(offsetof(Config, rfc_environ)), + conf_radiobutton_handler, + I(CONF_rfc_environ), "BSD (commonplace)", 'b', I(0), "RFC 1408 (unusual)", 'f', I(1), NULL); ctrl_radiobuttons(s, "Telnet negotiation mode:", 't', 2, HELPCTX(telnet_passive), - dlg_stdradiobutton_handler, - I(offsetof(Config, passive_telnet)), + conf_radiobutton_handler, + I(CONF_passive_telnet), "Passive", I(1), "Active", I(0), NULL); } ctrl_checkbox(s, "Keyboard sends Telnet special commands", 'k', HELPCTX(telnet_specialkeys), - dlg_stdcheckbox_handler, - I(offsetof(Config,telnet_keyboard))); + conf_checkbox_handler, + I(CONF_telnet_keyboard)); ctrl_checkbox(s, "Return key sends Telnet New Line instead of ^M", 'm', HELPCTX(telnet_newline), - dlg_stdcheckbox_handler, - I(offsetof(Config,telnet_newline))); + conf_checkbox_handler, + I(CONF_telnet_newline)); } if (!midsession) { /* @@ -2307,12 +2049,11 @@ s = ctrl_getset(b, "Connection/Rlogin", "data", "Data to send to the server"); ctrl_editbox(s, "Local username:", 'l', 50, HELPCTX(rlogin_localuser), - dlg_stdeditbox_handler, I(offsetof(Config,localusername)), - I(sizeof(((Config *)0)->localusername))); + conf_editbox_handler, I(CONF_localusername), I(1)); } /* * All the SSH stuff is omitted in PuTTYtel, or in a reconfig @@ -2338,55 +2079,61 @@ s = ctrl_getset(b, "Connection/SSH", "data", "Data to send to the server"); ctrl_editbox(s, "Remote command:", 'r', 100, HELPCTX(ssh_command), - dlg_stdeditbox_handler, I(offsetof(Config,remote_cmd)), - I(sizeof(((Config *)0)->remote_cmd))); + conf_editbox_handler, I(CONF_remote_cmd), I(1)); s = ctrl_getset(b, "Connection/SSH", "protocol", "Protocol options"); ctrl_checkbox(s, "Don't start a shell or command at all", 'n', HELPCTX(ssh_noshell), - dlg_stdcheckbox_handler, - I(offsetof(Config,ssh_no_shell))); + conf_checkbox_handler, + I(CONF_ssh_no_shell)); } if (!midsession || protcfginfo != 1) { s = ctrl_getset(b, "Connection/SSH", "protocol", "Protocol options"); ctrl_checkbox(s, "Enable compression", 'e', HELPCTX(ssh_compress), - dlg_stdcheckbox_handler, - I(offsetof(Config,compression))); + conf_checkbox_handler, + I(CONF_compression)); + } + + if (!midsession || protcfginfo != 1) { + s = ctrl_getset(b, "Connection/SSH", "sharing", "Sharing an SSH connection between PuTTY tools"); + + ctrl_checkbox(s, "Share SSH connections if possible", 's', + HELPCTX(ssh_share), + conf_checkbox_handler, + I(CONF_ssh_connection_sharing)); + + ctrl_text(s, "Permitted roles in a shared connection:", + HELPCTX(ssh_share)); + ctrl_checkbox(s, "Upstream (connecting to the real server)", 'u', + HELPCTX(ssh_share), + conf_checkbox_handler, + I(CONF_ssh_connection_sharing_upstream)); + ctrl_checkbox(s, "Downstream (connecting to the upstream PuTTY)", 'd', + HELPCTX(ssh_share), + conf_checkbox_handler, + I(CONF_ssh_connection_sharing_downstream)); } if (!midsession) { s = ctrl_getset(b, "Connection/SSH", "protocol", "Protocol options"); ctrl_radiobuttons(s, "Preferred SSH protocol version:", NO_SHORTCUT, 4, HELPCTX(ssh_protocol), - dlg_stdradiobutton_handler, - I(offsetof(Config, sshprot)), + conf_radiobutton_handler, + I(CONF_sshprot), "1 only", 'l', I(0), "1", '1', I(1), "2", '2', I(2), "2 only", 'y', I(3), NULL); } - if (!midsession || protcfginfo != 1) { - s = ctrl_getset(b, "Connection/SSH", "encryption", "Encryption options"); - c = ctrl_draglist(s, "Encryption cipher selection policy:", 's', - HELPCTX(ssh_ciphers), - cipherlist_handler, P(NULL)); - c->listbox.height = 6; - - ctrl_checkbox(s, "Enable legacy use of single-DES in SSH-2", 'i', - HELPCTX(ssh_ciphers), - dlg_stdcheckbox_handler, - I(offsetof(Config,ssh2_des_cbc))); - } - /* * The Connection/SSH/Kex panel. (Owing to repeat key * exchange, this is all meaningful in mid-session _if_ * we're using SSH-2 or haven't decided yet.) */ @@ -2404,111 +2151,43 @@ s = ctrl_getset(b, "Connection/SSH/Kex", "repeat", "Options controlling key re-exchange"); ctrl_editbox(s, "Max minutes before rekey (0 for no limit)", 't', 20, HELPCTX(ssh_kex_repeat), - dlg_stdeditbox_handler, - I(offsetof(Config,ssh_rekey_time)), + conf_editbox_handler, + I(CONF_ssh_rekey_time), I(-1)); ctrl_editbox(s, "Max data before rekey (0 for no limit)", 'x', 20, HELPCTX(ssh_kex_repeat), - dlg_stdeditbox_handler, - I(offsetof(Config,ssh_rekey_data)), + conf_editbox_handler, + I(CONF_ssh_rekey_data), I(16)); ctrl_text(s, "(Use 1M for 1 megabyte, 1G for 1 gigabyte etc)", HELPCTX(ssh_kex_repeat)); } - if (!midsession) { - /* PuTTY SC start */ - /* - * The Connection/SSH/Pkcs11 panel. - */ - ctrl_settitle(b, "Connection/SSH/Pkcs11", - "Options controlling PKCS11 SSH authentication"); - - s = ctrl_getset(b, "Connection/SSH/Pkcs11", "methods", - "Authentication methods"); - ctrl_checkbox(s, "Use Windows event log", NO_SHORTCUT, - HELPCTX(ssh_write_syslog), - dlg_stdcheckbox_handler, - I(offsetof(Config,try_write_syslog))); - ctrl_checkbox(s, "Attempt \"PKCS#11 smartcard\" auth (SSH-2)", NO_SHORTCUT, - HELPCTX(ssh_auth_pkcs11), - dlg_stdcheckbox_handler, - I(offsetof(Config,try_pkcs11_auth))); - - s = ctrl_getset(b, "Connection/SSH/Pkcs11", "params", - "Authentication parameters"); - ctrl_filesel(s, "PKCS#11 library for authentication:", NO_SHORTCUT, - FALSE , FALSE, "Select PKCS#11 library file", - HELPCTX(ssh_auth_pkcs11_libfile), - sc_dlg_stdfilesel_handler11, I(offsetof(Config, pkcs11_libfile))); - m_label_ctrl = ctrl_combobox(s, "Token label:", - NO_SHORTCUT, 70, HELPCTX(ssh_auth_pkcs11_token_label), - sc_tokenlabel_handler, P(NULL), P(NULL)); - m_cert_ctrl = ctrl_combobox(s, "Certificate label:", - NO_SHORTCUT, 70, HELPCTX(ssh_auth_pkcs11_cert_label), - sc_cert_handler, P(NULL), P(NULL)); - m_keystring_ctrl = ctrl_editbox(s, "SSH keystring:", - NO_SHORTCUT, 100, HELPCTX(ssh_auth_pkcs11_cert_label), - sc_keystring_handler, P(NULL), P(NULL)); - - /* PuTTY SC end */ - - /* PuTTY CAPI start */ -#ifdef _WINDOWS - /* - * The Connection/SSH/CAPI panel. - */ - ctrl_settitle(b, "Connection/SSH/CAPI", - "Options controlling MS CAPI SSH authentication"); - capid = (struct capi_data *) ctrl_alloc(b, sizeof(struct capi_data)); - s = ctrl_getset(b, "Connection/SSH/CAPI", "methods", - "Authentication methods"); - ctrl_checkbox(s, "Attempt \"CAPI Certificate\" (Key-only) auth (SSH-2)", NO_SHORTCUT, - HELPCTX(ssh_auth_capi), - dlg_stdcheckbox_handler, - I(offsetof(Config,try_capi_auth))); - s = ctrl_getset(b, "Connection/SSH/CAPI", "params", - "Authentication parameters"); - capid->certstore_droplist = ctrl_droplist(s, "Store:", NO_SHORTCUT, 85, - HELPCTX(ssh_auth_capi_certstore_label), - capi_certstore_handler, P(capid)); - - ctrl_columns(s, 2, 75, 25); - capid->certID_text = - ctrl_editbox(s, "Cert:", NO_SHORTCUT, 80, - HELPCTX(ssh_auth_capi_certstore_label), - capi_certID_handler - , P(capid), P(NULL) - ); - capid->certID_text->generic.column = 0; - capid->cert_browse = ctrl_pushbutton(s, "Browse", NO_SHORTCUT, - HELPCTX(ssh_auth_capi), - capi_certstore_browse_handler, P(capid)); - capid->cert_browse->generic.column = 1; - capid->keystring_text = ctrl_editbox(s, "SSH keystring:", - NO_SHORTCUT, 100, HELPCTX(ssh_auth_capi), - dlg_stdeditbox_handler, P(NULL), P(NULL)); - - -/* m_label_ctrl = ctrl_combobox(s, "Certificate Store:", - NO_SHORTCUT, 70, HELPCTX(ssh_auth_capi_certstore_label), - capi_certstore_handler, P(NULL), P(NULL)); - m_cert_ctrl = ctrl_combobox(s, "Certificate fingerprint:", - NO_SHORTCUT, 70, HELPCTX(ssh_auth_capi_certfingerprint_label), - capi_certfingerprint_handler, P(NULL), P(NULL)); - m_keystring_ctrl = ctrl_editbox(s, "SSH keystring:", - NO_SHORTCUT, 100, HELPCTX(ssh_auth_pkcs11_cert_label), - capi_keystring_handler, P(NULL), P(NULL)); -*/ - -#endif - - /* PuTTY CAPI end */ - + if (!midsession || protcfginfo != 1) { + /* + * The Connection/SSH/Cipher panel. + */ + ctrl_settitle(b, "Connection/SSH/Cipher", + "Options controlling SSH encryption"); + + s = ctrl_getset(b, "Connection/SSH/Cipher", + "encryption", "Encryption options"); + c = ctrl_draglist(s, "Encryption cipher selection policy:", 's', + HELPCTX(ssh_ciphers), + cipherlist_handler, P(NULL)); + c->listbox.height = 6; + + ctrl_checkbox(s, "Enable legacy use of single-DES in SSH-2", 'i', + HELPCTX(ssh_ciphers), + conf_checkbox_handler, + I(CONF_ssh2_des_cbc)); + } + + if (!midsession) { /* * The Connection/SSH/Auth panel. */ ctrl_settitle(b, "Connection/SSH/Auth", @@ -2515,45 +2194,45 @@ "Options controlling SSH authentication"); s = ctrl_getset(b, "Connection/SSH/Auth", "main", NULL); ctrl_checkbox(s, "Bypass authentication entirely (SSH-2 only)", 'b', HELPCTX(ssh_auth_bypass), - dlg_stdcheckbox_handler, - I(offsetof(Config,ssh_no_userauth))); + conf_checkbox_handler, + I(CONF_ssh_no_userauth)); ctrl_checkbox(s, "Display pre-authentication banner (SSH-2 only)", 'd', HELPCTX(ssh_auth_banner), - dlg_stdcheckbox_handler, - I(offsetof(Config,ssh_show_banner))); + conf_checkbox_handler, + I(CONF_ssh_show_banner)); s = ctrl_getset(b, "Connection/SSH/Auth", "methods", "Authentication methods"); ctrl_checkbox(s, "Attempt authentication using Pageant", 'p', HELPCTX(ssh_auth_pageant), - dlg_stdcheckbox_handler, - I(offsetof(Config,tryagent))); + conf_checkbox_handler, + I(CONF_tryagent)); ctrl_checkbox(s, "Attempt TIS or CryptoCard auth (SSH-1)", 'm', HELPCTX(ssh_auth_tis), - dlg_stdcheckbox_handler, - I(offsetof(Config,try_tis_auth))); + conf_checkbox_handler, + I(CONF_try_tis_auth)); ctrl_checkbox(s, "Attempt \"keyboard-interactive\" auth (SSH-2)", 'i', HELPCTX(ssh_auth_ki), - dlg_stdcheckbox_handler, - I(offsetof(Config,try_ki_auth))); + conf_checkbox_handler, + I(CONF_try_ki_auth)); s = ctrl_getset(b, "Connection/SSH/Auth", "params", "Authentication parameters"); ctrl_checkbox(s, "Allow agent forwarding", 'f', HELPCTX(ssh_auth_agentfwd), - dlg_stdcheckbox_handler, I(offsetof(Config,agentfwd))); + conf_checkbox_handler, I(CONF_agentfwd)); ctrl_checkbox(s, "Allow attempted changes of username in SSH-2", NO_SHORTCUT, HELPCTX(ssh_auth_changeuser), - dlg_stdcheckbox_handler, - I(offsetof(Config,change_username))); + conf_checkbox_handler, + I(CONF_change_username)); ctrl_filesel(s, "Private key file for authentication:", 'k', FILTER_KEY_FILES, FALSE, "Select private key file", HELPCTX(ssh_auth_privkey), - dlg_stdfilesel_handler, I(offsetof(Config, keyfile))); + conf_filesel_handler, I(CONF_keyfile)); #ifndef NO_GSSAPI /* * Connection/SSH/Auth/GSSAPI, which sadly won't fit on * the main Auth panel. @@ -2562,17 +2241,17 @@ "Options controlling GSSAPI authentication"); s = ctrl_getset(b, "Connection/SSH/Auth/GSSAPI", "gssapi", NULL); ctrl_checkbox(s, "Attempt GSSAPI authentication (SSH-2 only)", 't', HELPCTX(ssh_gssapi), - dlg_stdcheckbox_handler, - I(offsetof(Config,try_gssapi_auth))); + conf_checkbox_handler, + I(CONF_try_gssapi_auth)); ctrl_checkbox(s, "Allow GSSAPI credential delegation", 'l', HELPCTX(ssh_gssapi_delegation), - dlg_stdcheckbox_handler, - I(offsetof(Config,gssapifwd))); + conf_checkbox_handler, + I(CONF_gssapifwd)); /* * GSSAPI library selection. */ if (ngsslibs > 1) { @@ -2601,12 +2280,12 @@ */ ctrl_filesel(s, "User-supplied GSSAPI library path:", 's', FILTER_DYNLIB_FILES, FALSE, "Select library file", HELPCTX(ssh_gssapi_libraries), - dlg_stdfilesel_handler, - I(offsetof(Config, ssh_gss_custom))); + conf_filesel_handler, + I(CONF_ssh_gss_custom)); } #endif } if (!midsession) { @@ -2616,12 +2295,12 @@ ctrl_settitle(b, "Connection/SSH/TTY", "Remote terminal settings"); s = ctrl_getset(b, "Connection/SSH/TTY", "sshtty", NULL); ctrl_checkbox(s, "Don't allocate a pseudo-terminal", 'p', HELPCTX(ssh_nopty), - dlg_stdcheckbox_handler, - I(offsetof(Config,nopty))); + conf_checkbox_handler, + I(CONF_nopty)); s = ctrl_getset(b, "Connection/SSH/TTY", "ttymodes", "Terminal modes"); td = (struct ttymodes_data *) ctrl_alloc(b, sizeof(struct ttymodes_data)); @@ -2683,19 +2362,18 @@ "Options controlling SSH X11 forwarding"); s = ctrl_getset(b, "Connection/SSH/X11", "x11", "X11 forwarding"); ctrl_checkbox(s, "Enable X11 forwarding", 'e', HELPCTX(ssh_tunnels_x11), - dlg_stdcheckbox_handler,I(offsetof(Config,x11_forward))); + conf_checkbox_handler,I(CONF_x11_forward)); ctrl_editbox(s, "X display location", 'x', 50, HELPCTX(ssh_tunnels_x11), - dlg_stdeditbox_handler, I(offsetof(Config,x11_display)), - I(sizeof(((Config *)0)->x11_display))); + conf_editbox_handler, I(CONF_x11_display), I(1)); ctrl_radiobuttons(s, "Remote X11 authentication protocol", 'u', 2, HELPCTX(ssh_tunnels_x11auth), - dlg_stdradiobutton_handler, - I(offsetof(Config, x11_auth)), + conf_radiobutton_handler, + I(CONF_x11_auth), "MIT-Magic-Cookie-1", I(X11_MIT), "XDM-Authorization-1", I(X11_XDM), NULL); } /* @@ -2706,16 +2384,16 @@ s = ctrl_getset(b, "Connection/SSH/Tunnels", "portfwd", "Port forwarding"); ctrl_checkbox(s, "Local ports accept connections from other hosts",'t', HELPCTX(ssh_tunnels_portfwd_localhost), - dlg_stdcheckbox_handler, - I(offsetof(Config,lport_acceptall))); + conf_checkbox_handler, + I(CONF_lport_acceptall)); ctrl_checkbox(s, "Remote ports do the same (SSH-2 only)", 'p', HELPCTX(ssh_tunnels_portfwd_localhost), - dlg_stdcheckbox_handler, - I(offsetof(Config,rport_acceptall))); + conf_checkbox_handler, + I(CONF_rport_acceptall)); ctrl_columns(s, 3, 55, 20, 25); c = ctrl_text(s, "Forwarded ports:", HELPCTX(ssh_tunnels_portfwd)); c->generic.column = COLUMN_FIELD(0,2); /* You want to select from the list, _then_ hit Remove. So tab order @@ -2779,36 +2457,39 @@ s = ctrl_getset(b, "Connection/SSH/Bugs", "main", "Detection of known bugs in SSH servers"); ctrl_droplist(s, "Chokes on SSH-1 ignore messages", 'i', 20, HELPCTX(ssh_bugs_ignore1), - sshbug_handler, I(offsetof(Config,sshbug_ignore1))); + sshbug_handler, I(CONF_sshbug_ignore1)); ctrl_droplist(s, "Refuses all SSH-1 password camouflage", 's', 20, HELPCTX(ssh_bugs_plainpw1), - sshbug_handler, I(offsetof(Config,sshbug_plainpw1))); + sshbug_handler, I(CONF_sshbug_plainpw1)); ctrl_droplist(s, "Chokes on SSH-1 RSA authentication", 'r', 20, HELPCTX(ssh_bugs_rsa1), - sshbug_handler, I(offsetof(Config,sshbug_rsa1))); + sshbug_handler, I(CONF_sshbug_rsa1)); ctrl_droplist(s, "Chokes on SSH-2 ignore messages", '2', 20, HELPCTX(ssh_bugs_ignore2), - sshbug_handler, I(offsetof(Config,sshbug_ignore2))); + sshbug_handler, I(CONF_sshbug_ignore2)); + ctrl_droplist(s, "Chokes on PuTTY's SSH-2 'winadj' requests", 'j', + 20, HELPCTX(ssh_bugs_winadj), + sshbug_handler, I(CONF_sshbug_winadj)); ctrl_droplist(s, "Miscomputes SSH-2 HMAC keys", 'm', 20, HELPCTX(ssh_bugs_hmac2), - sshbug_handler, I(offsetof(Config,sshbug_hmac2))); + sshbug_handler, I(CONF_sshbug_hmac2)); ctrl_droplist(s, "Miscomputes SSH-2 encryption keys", 'e', 20, HELPCTX(ssh_bugs_derivekey2), - sshbug_handler, I(offsetof(Config,sshbug_derivekey2))); + sshbug_handler, I(CONF_sshbug_derivekey2)); ctrl_droplist(s, "Requires padding on SSH-2 RSA signatures", 'p', 20, HELPCTX(ssh_bugs_rsapad2), - sshbug_handler, I(offsetof(Config,sshbug_rsapad2))); + sshbug_handler, I(CONF_sshbug_rsapad2)); ctrl_droplist(s, "Misuses the session ID in SSH-2 PK auth", 'n', 20, HELPCTX(ssh_bugs_pksessid2), - sshbug_handler, I(offsetof(Config,sshbug_pksessid2))); + sshbug_handler, I(CONF_sshbug_pksessid2)); ctrl_droplist(s, "Handles SSH-2 key re-exchange badly", 'k', 20, HELPCTX(ssh_bugs_rekey2), - sshbug_handler, I(offsetof(Config,sshbug_rekey2))); + sshbug_handler, I(CONF_sshbug_rekey2)); ctrl_droplist(s, "Ignores SSH-2 maximum packet size", 'x', 20, HELPCTX(ssh_bugs_maxpkt2), - sshbug_handler, I(offsetof(Config,sshbug_maxpkt2))); + sshbug_handler, I(CONF_sshbug_maxpkt2)); } } } ADDED configure Index: configure ================================================================== --- /dev/null +++ configure @@ -0,0 +1,3 @@ +#!/bin/sh + +$(echo "$0" | sed '$s!configure$!unix/configure!') "$@" Index: contrib/cygtermd/pty.c ================================================================== --- contrib/cygtermd/pty.c +++ contrib/cygtermd/pty.c @@ -120,13 +120,27 @@ if ((fd = open("/dev/tty", O_RDWR)) >= 0) { ioctl(fd, TIOCNOTTY, &i); close(fd); } #endif + /* + * Make the new pty our controlling terminal. On some OSes + * this is done with TIOCSCTTY; Cygwin doesn't have that, so + * instead it's done by simply opening the pty without + * O_NOCTTY. This code is primarily intended for Cygwin, but + * it's useful to have it work in other contexts for testing + * purposes, so I leave the TIOCSCTTY here anyway. + */ + if ((fd = open(ptyname, O_RDWR)) >= 0) { #ifdef TIOCSCTTY - ioctl(0, TIOCSCTTY, &i); + ioctl(fd, TIOCSCTTY, &i); #endif + close(fd); + } else { + perror("slave pty: open"); + exit(127); + } tcsetpgrp(0, getpgrp()); for (i = 0; i < shdata->nenvvars; i++) putenv(shdata->envvars[i]); if (shdata->termtype) Index: contrib/cygtermd/telnet.c ================================================================== --- contrib/cygtermd/telnet.c +++ contrib/cygtermd/telnet.c @@ -422,11 +422,14 @@ telnet->state = SEENIAC; else { char cc = c; sel_write(telnet->pty, &cc, 1); - telnet->state = SEENCR; + if (c == CR) + telnet->state = SEENCR; + else + telnet->state = TOP_LEVEL; } break; case SEENIAC: if (c == DO) telnet->state = SEENDO; ADDED contrib/logparse.pl Index: contrib/logparse.pl ================================================================== --- /dev/null +++ contrib/logparse.pl @@ -0,0 +1,952 @@ +#!/usr/bin/perl + +use strict; +use warnings; +use FileHandle; + +my $dumpchannels = 0; +my $dumpdata = 0; +while ($ARGV[0] =~ /^-/) { + my $opt = shift @ARGV; + if ($opt eq "--") { + last; # stop processing options + } elsif ($opt eq "-c") { + $dumpchannels = 1; + } elsif ($opt eq "-d") { + $dumpdata = 1; + } else { + die "unrecognised option '$opt'\n"; + } +} + +my @channels = (); # ultimate channel ids are indices in this array +my %chan_by_id = (); # indexed by 'c%d' or 's%d' for client and server ids +my %globalreq = (); # indexed by 'i' or 'o' + +my %packets = ( +#define SSH2_MSG_DISCONNECT 1 /* 0x1 */ + 'SSH2_MSG_DISCONNECT' => sub { + my ($direction, $seq, $data) = @_; + my ($reason, $description, $lang) = &parse("uss", $data); + printf "%s\n", &str($description); + }, +#define SSH2_MSG_IGNORE 2 /* 0x2 */ + 'SSH2_MSG_IGNORE' => sub { + my ($direction, $seq, $data) = @_; + my ($str) = &parse("s", $data); + printf "(%d bytes)\n", length $str; + }, +#define SSH2_MSG_UNIMPLEMENTED 3 /* 0x3 */ + 'SSH2_MSG_UNIMPLEMENTED' => sub { + my ($direction, $seq, $data) = @_; + my ($rseq) = &parse("u", $data); + printf "i%d\n", $rseq; + }, +#define SSH2_MSG_DEBUG 4 /* 0x4 */ + 'SSH2_MSG_DEBUG' => sub { + my ($direction, $seq, $data) = @_; + my ($disp, $message, $lang) = &parse("bss", $data); + printf "%s\n", &str($message); + }, +#define SSH2_MSG_SERVICE_REQUEST 5 /* 0x5 */ + 'SSH2_MSG_SERVICE_REQUEST' => sub { + my ($direction, $seq, $data) = @_; + my ($service) = &parse("s", $data); + printf "%s\n", &str($service); + }, +#define SSH2_MSG_SERVICE_ACCEPT 6 /* 0x6 */ + 'SSH2_MSG_SERVICE_ACCEPT' => sub { + my ($direction, $seq, $data) = @_; + my ($service) = &parse("s", $data); + printf "%s\n", &str($service); + }, +#define SSH2_MSG_KEXINIT 20 /* 0x14 */ + 'SSH2_MSG_KEXINIT' => sub { + my ($direction, $seq, $data) = @_; + print "\n"; + }, +#define SSH2_MSG_NEWKEYS 21 /* 0x15 */ + 'SSH2_MSG_NEWKEYS' => sub { + my ($direction, $seq, $data) = @_; + print "\n"; + }, +#define SSH2_MSG_KEXDH_INIT 30 /* 0x1e */ + 'SSH2_MSG_KEXDH_INIT' => sub { + my ($direction, $seq, $data) = @_; + print "\n"; + }, +#define SSH2_MSG_KEXDH_REPLY 31 /* 0x1f */ + 'SSH2_MSG_KEXDH_REPLY' => sub { + my ($direction, $seq, $data) = @_; + print "\n"; + }, +#define SSH2_MSG_KEX_DH_GEX_REQUEST 30 /* 0x1e */ + 'SSH2_MSG_KEX_DH_GEX_REQUEST' => sub { + my ($direction, $seq, $data) = @_; + print "\n"; + }, +#define SSH2_MSG_KEX_DH_GEX_GROUP 31 /* 0x1f */ + 'SSH2_MSG_KEX_DH_GEX_GROUP' => sub { + my ($direction, $seq, $data) = @_; + print "\n"; + }, +#define SSH2_MSG_KEX_DH_GEX_INIT 32 /* 0x20 */ + 'SSH2_MSG_KEX_DH_GEX_INIT' => sub { + my ($direction, $seq, $data) = @_; + print "\n"; + }, +#define SSH2_MSG_KEX_DH_GEX_REPLY 33 /* 0x21 */ + 'SSH2_MSG_KEX_DH_GEX_REPLY' => sub { + my ($direction, $seq, $data) = @_; + print "\n"; + }, +#define SSH2_MSG_KEXRSA_PUBKEY 30 /* 0x1e */ + 'SSH2_MSG_KEXRSA_PUBKEY' => sub { + my ($direction, $seq, $data) = @_; + print "\n"; + }, +#define SSH2_MSG_KEXRSA_SECRET 31 /* 0x1f */ + 'SSH2_MSG_KEXRSA_SECRET' => sub { + my ($direction, $seq, $data) = @_; + print "\n"; + }, +#define SSH2_MSG_KEXRSA_DONE 32 /* 0x20 */ + 'SSH2_MSG_KEXRSA_DONE' => sub { + my ($direction, $seq, $data) = @_; + print "\n"; + }, +#define SSH2_MSG_USERAUTH_REQUEST 50 /* 0x32 */ + 'SSH2_MSG_USERAUTH_REQUEST' => sub { + my ($direction, $seq, $data) = @_; + my ($user, $service, $method) = &parse("sss", $data); + my $out = sprintf "%s %s %s", + &str($user), &str($service), &str($method); + if ($method eq "publickey") { + my ($real) = &parse("b", $data); + $out .= " real=$real"; + } elsif ($method eq "password") { + my ($change) = &parse("b", $data); + $out .= " change=$change"; + } + print "$out\n"; + }, +#define SSH2_MSG_USERAUTH_FAILURE 51 /* 0x33 */ + 'SSH2_MSG_USERAUTH_FAILURE' => sub { + my ($direction, $seq, $data) = @_; + my ($options) = &parse("s", $data); + printf "%s\n", &str($options); + }, +#define SSH2_MSG_USERAUTH_SUCCESS 52 /* 0x34 */ + 'SSH2_MSG_USERAUTH_SUCCESS' => sub { + my ($direction, $seq, $data) = @_; + print "\n"; + }, +#define SSH2_MSG_USERAUTH_BANNER 53 /* 0x35 */ + 'SSH2_MSG_USERAUTH_BANNER' => sub { + my ($direction, $seq, $data) = @_; + print "\n"; + }, +#define SSH2_MSG_USERAUTH_PK_OK 60 /* 0x3c */ + 'SSH2_MSG_USERAUTH_PK_OK' => sub { + my ($direction, $seq, $data) = @_; + print "\n"; + }, +#define SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ 60 /* 0x3c */ + 'SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ' => sub { + my ($direction, $seq, $data) = @_; + print "\n"; + }, +#define SSH2_MSG_USERAUTH_INFO_REQUEST 60 /* 0x3c */ + 'SSH2_MSG_USERAUTH_INFO_REQUEST' => sub { + my ($direction, $seq, $data) = @_; + print "\n"; + }, +#define SSH2_MSG_USERAUTH_INFO_RESPONSE 61 /* 0x3d */ + 'SSH2_MSG_USERAUTH_INFO_RESPONSE' => sub { + my ($direction, $seq, $data) = @_; + print "\n"; + }, +#define SSH2_MSG_GLOBAL_REQUEST 80 /* 0x50 */ + 'SSH2_MSG_GLOBAL_REQUEST' => sub { + my ($direction, $seq, $data) = @_; + my ($type, $wantreply) = &parse("sb", $data); + printf "%s (%s)", $type, $wantreply eq "yes" ? "reply" : "noreply"; + my $request = [$seq, $type]; + push @{$globalreq{$direction}}, $request if $wantreply eq "yes"; + if ($type eq "tcpip-forward" or $type eq "cancel-tcpip-forward") { + my ($addr, $port) = &parse("su", $data); + printf " %s:%s", $addr, $port; + push @$request, $port; + } + print "\n"; + }, +#define SSH2_MSG_REQUEST_SUCCESS 81 /* 0x51 */ + 'SSH2_MSG_REQUEST_SUCCESS' => sub { + my ($direction, $seq, $data) = @_; + my $otherdir = ($direction eq "i" ? "o" : "i"); + my $request = shift @{$globalreq{$otherdir}}; + if (defined $request) { + printf "to %s", $request->[0]; + if ($request->[1] eq "tcpip-forward" and $request->[2] == 0) { + my ($port) = &parse("u", $data); + printf " port=%s", $port; + } + } else { + print "(spurious?)"; + } + print "\n"; + }, +#define SSH2_MSG_REQUEST_FAILURE 82 /* 0x52 */ + 'SSH2_MSG_REQUEST_FAILURE' => sub { + my ($direction, $seq, $data) = @_; + my $otherdir = ($direction eq "i" ? "o" : "i"); + my $request = shift @{$globalreq{$otherdir}}; + if (defined $request) { + printf "to %s", $request->[0]; + } else { + print "(spurious?)"; + } + print "\n"; + }, +#define SSH2_MSG_CHANNEL_OPEN 90 /* 0x5a */ + 'SSH2_MSG_CHANNEL_OPEN' => sub { + my ($direction, $seq, $data) = @_; + my ($type, $sid, $winsize, $packet) = &parse("suuu", $data); + # CHANNEL_OPEN tells the other side the _sender's_ id for the + # channel, so this choice between "s" and "c" prefixes is + # opposite to every other message in the protocol, which all + # quote the _recipient's_ id of the channel. + $sid = ($direction eq "i" ? "s" : "c") . $sid; + my $chan = {'id'=>$sid, 'state'=>'halfopen', + 'i'=>{'win'=>0, 'seq'=>0}, + 'o'=>{'win'=>0, 'seq'=>0}}; + $chan->{$direction}{'win'} = $winsize; + push @channels, $chan; + my $index = $#channels; + $chan_by_id{$sid} = $index; + printf "ch%d (%s) %s (--%d)", $index, $chan->{'id'}, $type, + $chan->{$direction}{'win'}; + if ($type eq "x11") { + my ($addr, $port) = &parse("su", $data); + printf " from %s:%s", $addr, $port; + } elsif ($type eq "forwarded-tcpip") { + my ($saddr, $sport, $paddr, $pport) = &parse("susu", $data); + printf " to %s:%s from %s:%s", $saddr, $sport, $paddr, $pport; + } elsif ($type eq "direct-tcpip") { + my ($daddr, $dport, $saddr, $sport) = &parse("susu", $data); + printf " to %s:%s from %s:%s", $daddr, $dport, $saddr, $sport; + } + print "\n"; + }, +#define SSH2_MSG_CHANNEL_OPEN_CONFIRMATION 91 /* 0x5b */ + 'SSH2_MSG_CHANNEL_OPEN_CONFIRMATION' => sub { + my ($direction, $seq, $data) = @_; + my ($rid, $sid, $winsize, $packet) = &parse("uuuu", $data); + $rid = ($direction eq "i" ? "c" : "s") . $rid; + my $index = $chan_by_id{$rid}; + if (!defined $index) { + printf "UNKNOWN_CHANNEL (%s) (--%d)\n", $rid, $winsize; + return; + } + $sid = ($direction eq "i" ? "s" : "c") . $sid; + $chan_by_id{$sid} = $index; + my $chan = $channels[$index]; + $chan->{'id'} = ($direction eq "i" ? "$rid/$sid" : "$sid/$rid"); + $chan->{'state'} = 'open'; + $chan->{$direction}{'win'} = $winsize; + printf "ch%d (%s) (--%d)\n", $index, $chan->{'id'}, + $chan->{$direction}{'win'}; + }, +#define SSH2_MSG_CHANNEL_OPEN_FAILURE 92 /* 0x5c */ + 'SSH2_MSG_CHANNEL_OPEN_FAILURE' => sub { + my ($direction, $seq, $data) = @_; + my ($rid, $reason, $desc, $lang) = &parse("uuss", $data); + $rid = ($direction eq "i" ? "c" : "s") . $rid; + my $index = $chan_by_id{$rid}; + if (!defined $index) { + printf "UNKNOWN_CHANNEL (%s) %s\n", $rid, &str($reason); + return; + } + my $chan = $channels[$index]; + $chan->{'state'} = 'rejected'; + printf "ch%d (%s) %s\n", $index, $chan->{'id'}, &str($reason); + }, +#define SSH2_MSG_CHANNEL_WINDOW_ADJUST 93 /* 0x5d */ + 'SSH2_MSG_CHANNEL_WINDOW_ADJUST' => sub { + my ($direction, $seq, $data) = @_; + my ($rid, $bytes) = &parse("uu", $data); + $rid = ($direction eq "i" ? "c" : "s") . $rid; + my $index = $chan_by_id{$rid}; + if (!defined $index) { + printf "UNKNOWN_CHANNEL (%s) +%d\n", $rid, $bytes; + return; + } + my $chan = $channels[$index]; + $chan->{$direction}{'win'} += $bytes; + printf "ch%d (%s) +%d (--%d)\n", $index, $chan->{'id'}, $bytes, + $chan->{$direction}{'win'}; + }, +#define SSH2_MSG_CHANNEL_DATA 94 /* 0x5e */ + 'SSH2_MSG_CHANNEL_DATA' => sub { + my ($direction, $seq, $data) = @_; + my ($rid, $bytes) = &parse("uu", $data); + $rid = ($direction eq "i" ? "c" : "s") . $rid; + my $index = $chan_by_id{$rid}; + if (!defined $index) { + printf "UNKNOWN_CHANNEL (%s), %s bytes\n", $rid, $bytes; + return; + } + my $chan = $channels[$index]; + $chan->{$direction}{'seq'} += $bytes; + printf "ch%d (%s), %s bytes (%d--%d)\n", $index, $chan->{'id'}, $bytes, + $chan->{$direction}{'seq'}-$bytes, $chan->{$direction}{'seq'}; + my @realdata = splice @$data, 0, $bytes; + if ($dumpdata) { + my $filekey = $direction . "file"; + if (!defined $chan->{$filekey}) { + my $filename = sprintf "ch%d.%s", $index, $direction; + $chan->{$filekey} = FileHandle->new(">$filename"); + if (!defined $chan->{$filekey}) { + die "$filename: $!\n"; + } + } + die "channel data not present in $seq\n" if @realdata < $bytes; + my $rawdata = pack "C*", @realdata; + my $fh = $chan->{$filekey}; + print $fh $rawdata; + } + if (@realdata == $bytes and defined $chan->{$direction."data"}) { + my $rawdata = pack "C*", @realdata; + $chan->{$direction."data"}->($chan, $index, $direction, $rawdata); + } + }, +#define SSH2_MSG_CHANNEL_EXTENDED_DATA 95 /* 0x5f */ + 'SSH2_MSG_CHANNEL_EXTENDED_DATA' => sub { + my ($direction, $seq, $data) = @_; + my ($rid, $type, $bytes) = &parse("uuu", $data); + if ($type == 1) { + $type = "SSH_EXTENDED_DATA_STDERR"; + } + $rid = ($direction eq "i" ? "c" : "s") . $rid; + my $index = $chan_by_id{$rid}; + if (!defined $index) { + printf "UNKNOWN_CHANNEL (%s), type %s, %s bytes\n", $rid, + $type, $bytes; + return; + } + my $chan = $channels[$index]; + $chan->{$direction}{'seq'} += $bytes; + printf "ch%d (%s), type %s, %s bytes (%d--%d)\n", $index,$chan->{'id'}, + $type, $bytes, $chan->{$direction}{'seq'}-$bytes, + $chan->{$direction}{'seq'}; + my @realdata = splice @$data, 0, $bytes; + if ($dumpdata) { + # We treat EXTENDED_DATA as equivalent to DATA, for the + # moment. It's not clear what else would be a better thing + # to do with it, and this at least is the Right Answer if + # the data is going to a terminal and the aim is to debug + # the terminal emulator. + my $filekey = $direction . "file"; + if (!defined $chan->{$filekey}) { + my $filename = sprintf "ch%d.%s", $index, $direction; + $chan->{$filekey} = FileHandle->new(">$filename"); + if (!defined $chan->{$filekey}) { + die "$filename: $!\n"; + } + } + die "channel data not present in $seq\n" if @realdata < $bytes; + my $rawdata = pack "C*", @realdata; + my $fh = $chan->{$filekey}; + print $fh $rawdata; + } + if (@realdata == $bytes and defined $chan->{$direction."data"}) { + my $rawdata = pack "C*", @realdata; + $chan->{$direction."data"}->($chan, $index, $direction, $rawdata); + } + }, +#define SSH2_MSG_CHANNEL_EOF 96 /* 0x60 */ + 'SSH2_MSG_CHANNEL_EOF' => sub { + my ($direction, $seq, $data) = @_; + my ($rid) = &parse("uu", $data); + $rid = ($direction eq "i" ? "c" : "s") . $rid; + my $index = $chan_by_id{$rid}; + if (!defined $index) { + printf "UNKNOWN_CHANNEL (%s)\n", $rid; + return; + } + my $chan = $channels[$index]; + printf "ch%d (%s)\n", $index, $chan->{'id'}; + }, +#define SSH2_MSG_CHANNEL_CLOSE 97 /* 0x61 */ + 'SSH2_MSG_CHANNEL_CLOSE' => sub { + my ($direction, $seq, $data) = @_; + my ($rid) = &parse("uu", $data); + $rid = ($direction eq "i" ? "c" : "s") . $rid; + my $index = $chan_by_id{$rid}; + if (!defined $index) { + printf "UNKNOWN_CHANNEL (%s)\n", $rid; + return; + } + my $chan = $channels[$index]; + $chan->{'state'} = ($chan->{'state'} eq "open" ? "halfclosed" : + $chan->{'state'} eq "halfclosed" ? "closed" : + "confused"); + if ($chan->{'state'} eq "closed") { + $chan->{'ifile'}->close if defined $chan->{'ifile'}; + $chan->{'ofile'}->close if defined $chan->{'ofile'}; + } + printf "ch%d (%s)\n", $index, $chan->{'id'}; + }, +#define SSH2_MSG_CHANNEL_REQUEST 98 /* 0x62 */ + 'SSH2_MSG_CHANNEL_REQUEST' => sub { + my ($direction, $seq, $data) = @_; + my ($rid, $type, $wantreply) = &parse("usb", $data); + $rid = ($direction eq "i" ? "c" : "s") . $rid; + my $index = $chan_by_id{$rid}; + my $chan; + if (!defined $index) { + printf "UNKNOWN_CHANNEL (%s) %s (%s)", $rid, + $type, $wantreply eq "yes" ? "reply" : "noreply"; + } else { + $chan = $channels[$index]; + printf "ch%d (%s) %s (%s)", $index, $chan->{'id'}, + $type, $wantreply eq "yes" ? "reply" : "noreply"; + push @{$chan->{'requests_'.$direction}}, [$seq, $type] + if $wantreply eq "yes"; + } + if ($type eq "pty-req") { + my ($term, $w, $h, $pw, $ph, $modes) = &parse("suuuus", $data); + printf " %s %sx%s", &str($term), $w, $h; + } elsif ($type eq "x11-req") { + my ($single, $xprot, $xcookie, $xscreen) = &parse("bssu", $data); + print " one-off" if $single eq "yes"; + printf " %s :%s", $xprot, $xscreen; + } elsif ($type eq "exec") { + my ($command) = &parse("s", $data); + printf " %s", &str($command); + } elsif ($type eq "subsystem") { + my ($subsys) = &parse("s", $data); + printf " %s", &str($subsys); + if ($subsys eq "sftp") { + &sftp_setup($index); + } + } elsif ($type eq "window-change") { + my ($w, $h, $pw, $ph) = &parse("uuuu", $data); + printf " %sx%s", $w, $h; + } elsif ($type eq "xon-xoff") { + my ($can) = &parse("b", $data); + printf " %s", $can; + } elsif ($type eq "signal") { + my ($sig) = &parse("s", $data); + printf " %s", &str($sig); + } elsif ($type eq "exit-status") { + my ($status) = &parse("u", $data); + printf " %s", $status; + } elsif ($type eq "exit-signal") { + my ($sig, $core, $error, $lang) = &parse("sbss", $data); + printf " %s", &str($sig); + print " (core dumped)" if $core eq "yes"; + } + print "\n"; + }, +#define SSH2_MSG_CHANNEL_SUCCESS 99 /* 0x63 */ + 'SSH2_MSG_CHANNEL_SUCCESS' => sub { + my ($direction, $seq, $data) = @_; + my ($rid) = &parse("uu", $data); + $rid = ($direction eq "i" ? "c" : "s") . $rid; + my $index = $chan_by_id{$rid}; + if (!defined $index) { + printf "UNKNOWN_CHANNEL (%s)\n", $rid; + return; + } + my $chan = $channels[$index]; + printf "ch%d (%s)", $index, $chan->{'id'}; + my $otherdir = ($direction eq "i" ? "o" : "i"); + my $request = shift @{$chan->{'requests_' . $otherdir}}; + if (defined $request) { + printf " to %s", $request->[0]; + } else { + print " (spurious?)"; + } + print "\n"; + }, +#define SSH2_MSG_CHANNEL_FAILURE 100 /* 0x64 */ + 'SSH2_MSG_CHANNEL_FAILURE' => sub { + my ($direction, $seq, $data) = @_; + my ($rid) = &parse("uu", $data); + $rid = ($direction eq "i" ? "c" : "s") . $rid; + my $index = $chan_by_id{$rid}; + if (!defined $index) { + printf "UNKNOWN_CHANNEL (%s)\n", $rid; + return; + } + my $chan = $channels[$index]; + printf "ch%d (%s)", $index, $chan->{'id'}; + my $otherdir = ($direction eq "i" ? "o" : "i"); + my $request = shift @{$chan->{'requests_' . $otherdir}}; + if (defined $request) { + printf " to %s", $request->[0]; + } else { + print " (spurious?)"; + } + print "\n"; + }, +#define SSH2_MSG_USERAUTH_GSSAPI_RESPONSE 60 + 'SSH2_MSG_USERAUTH_GSSAPI_RESPONSE' => sub { + my ($direction, $seq, $data) = @_; + print "\n"; + }, +#define SSH2_MSG_USERAUTH_GSSAPI_TOKEN 61 + 'SSH2_MSG_USERAUTH_GSSAPI_TOKEN' => sub { + my ($direction, $seq, $data) = @_; + print "\n"; + }, +#define SSH2_MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE 63 + 'SSH2_MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE' => sub { + my ($direction, $seq, $data) = @_; + print "\n"; + }, +#define SSH2_MSG_USERAUTH_GSSAPI_ERROR 64 + 'SSH2_MSG_USERAUTH_GSSAPI_ERROR' => sub { + my ($direction, $seq, $data) = @_; + print "\n"; + }, +#define SSH2_MSG_USERAUTH_GSSAPI_ERRTOK 65 + 'SSH2_MSG_USERAUTH_GSSAPI_ERRTOK' => sub { + my ($direction, $seq, $data) = @_; + print "\n"; + }, +#define SSH2_MSG_USERAUTH_GSSAPI_MIC 66 + 'SSH2_MSG_USERAUTH_GSSAPI_MIC' => sub { + my ($direction, $seq, $data) = @_; + print "\n"; + }, +); + +my %sftp_packets = ( +#define SSH_FXP_INIT 1 /* 0x1 */ + 0x1 => sub { + my ($chan, $index, $direction, $id, $data) = @_; + my ($ver) = &parse("u", $data); + printf "SSH_FXP_INIT %d\n", $ver; + }, +#define SSH_FXP_VERSION 2 /* 0x2 */ + 0x2 => sub { + my ($chan, $index, $direction, $id, $data) = @_; + my ($ver) = &parse("u", $data); + printf "SSH_FXP_VERSION %d\n", $ver; + }, +#define SSH_FXP_OPEN 3 /* 0x3 */ + 0x3 => sub { + my ($chan, $index, $direction, $id, $data) = @_; + my ($reqid, $path, $pflags) = &parse("usu", $data); + &sftp_logreq($chan, $direction, $reqid, $id, "SSH_FXP_OPEN"); + printf " \"%s\" ", $path; + if ($pflags eq 0) { + print "0"; + } else { + my $sep = ""; + if ($pflags & 1) { $pflags ^= 1; print "${sep}READ"; $sep = "|"; } + if ($pflags & 2) { $pflags ^= 2; print "${sep}WRITE"; $sep = "|"; } + if ($pflags & 4) { $pflags ^= 4; print "${sep}APPEND"; $sep = "|"; } + if ($pflags & 8) { $pflags ^= 8; print "${sep}CREAT"; $sep = "|"; } + if ($pflags & 16) { $pflags ^= 16; print "${sep}TRUNC"; $sep = "|"; } + if ($pflags & 32) { $pflags ^= 32; print "${sep}EXCL"; $sep = "|"; } + if ($pflags) { print "${sep}${pflags}"; } + } + print "\n"; + }, +#define SSH_FXP_CLOSE 4 /* 0x4 */ + 0x4 => sub { + my ($chan, $index, $direction, $id, $data) = @_; + my ($reqid, $handle) = &parse("us", $data); + &sftp_logreq($chan, $direction, $reqid, $id, "SSH_FXP_CLOSE"); + printf " \"%s\"", &stringescape($handle); + print "\n"; + }, +#define SSH_FXP_READ 5 /* 0x5 */ + 0x5 => sub { + my ($chan, $index, $direction, $id, $data) = @_; + my ($reqid, $handle, $offset, $len) = &parse("usUu", $data); + &sftp_logreq($chan, $direction, $reqid, $id, "SSH_FXP_READ"); + printf " \"%s\" %d %d", &stringescape($handle), $offset, $len; + print "\n"; + }, +#define SSH_FXP_WRITE 6 /* 0x6 */ + 0x6 => sub { + my ($chan, $index, $direction, $id, $data) = @_; + my ($reqid, $handle, $offset, $wdata) = &parse("usUs", $data); + &sftp_logreq($chan, $direction, $reqid, $id, "SSH_FXP_WRITE"); + printf " \"%s\" %d [%d bytes]", &stringescape($handle), $offset, length $wdata; + print "\n"; + }, +#define SSH_FXP_LSTAT 7 /* 0x7 */ + 0x7 => sub { + my ($chan, $index, $direction, $id, $data) = @_; + my ($reqid, $path) = &parse("us", $data); + &sftp_logreq($chan, $direction, $reqid, $id, "SSH_FXP_LSTAT"); + printf " \"%s\"", $path; + print "\n"; + }, +#define SSH_FXP_FSTAT 8 /* 0x8 */ + 0x8 => sub { + my ($chan, $index, $direction, $id, $data) = @_; + my ($reqid, $handle) = &parse("us", $data); + &sftp_logreq($chan, $direction, $reqid, $id, "SSH_FXP_FSTAT"); + printf " \"%s\"", &stringescape($handle); + print "\n"; + }, +#define SSH_FXP_SETSTAT 9 /* 0x9 */ + 0x9 => sub { + my ($chan, $index, $direction, $id, $data) = @_; + my ($reqid, $path) = &parse("us", $data); + &sftp_logreq($chan, $direction, $reqid, $id, "SSH_FXP_SETSTAT"); + my $attrs = &sftp_parse_attrs($data); + printf " \"%s\" %s", $path, $attrs; + print "\n"; + }, +#define SSH_FXP_FSETSTAT 10 /* 0xa */ + 0xa => sub { + my ($chan, $index, $direction, $id, $data) = @_; + my ($reqid, $handle) = &parse("us", $data); + &sftp_logreq($chan, $direction, $reqid, $id, "SSH_FXP_FSETSTAT"); + my $attrs = &sftp_parse_attrs($data); + printf " \"%s\" %s", &stringescape($handle), $attrs; + print "\n"; + }, +#define SSH_FXP_OPENDIR 11 /* 0xb */ + 0xb => sub { + my ($chan, $index, $direction, $id, $data) = @_; + my ($reqid, $path) = &parse("us", $data); + &sftp_logreq($chan, $direction, $reqid, $id, "SSH_FXP_OPENDIR"); + printf " \"%s\"", $path; + print "\n"; + }, +#define SSH_FXP_READDIR 12 /* 0xc */ + 0xc => sub { + my ($chan, $index, $direction, $id, $data) = @_; + my ($reqid, $handle) = &parse("us", $data); + &sftp_logreq($chan, $direction, $reqid, $id, "SSH_FXP_READDIR"); + printf " \"%s\"", &stringescape($handle); + print "\n"; + }, +#define SSH_FXP_REMOVE 13 /* 0xd */ + 0xd => sub { + my ($chan, $index, $direction, $id, $data) = @_; + my ($reqid, $path) = &parse("us", $data); + &sftp_logreq($chan, $direction, $reqid, $id, "SSH_FXP_REMOVE"); + printf " \"%s\"", $path; + print "\n"; + }, +#define SSH_FXP_MKDIR 14 /* 0xe */ + 0xe => sub { + my ($chan, $index, $direction, $id, $data) = @_; + my ($reqid, $path) = &parse("us", $data); + &sftp_logreq($chan, $direction, $reqid, $id, "SSH_FXP_MKDIR"); + printf " \"%s\"", $path; + print "\n"; + }, +#define SSH_FXP_RMDIR 15 /* 0xf */ + 0xf => sub { + my ($chan, $index, $direction, $id, $data) = @_; + my ($reqid, $path) = &parse("us", $data); + &sftp_logreq($chan, $direction, $reqid, $id, "SSH_FXP_RMDIR"); + printf " \"%s\"", $path; + print "\n"; + }, +#define SSH_FXP_REALPATH 16 /* 0x10 */ + 0x10 => sub { + my ($chan, $index, $direction, $id, $data) = @_; + my ($reqid, $path) = &parse("us", $data); + &sftp_logreq($chan, $direction, $reqid, $id, "SSH_FXP_REALPATH"); + printf " \"%s\"", $path; + print "\n"; + }, +#define SSH_FXP_STAT 17 /* 0x11 */ + 0x11 => sub { + my ($chan, $index, $direction, $id, $data) = @_; + my ($reqid, $path) = &parse("us", $data); + &sftp_logreq($chan, $direction, $reqid, $id, "SSH_FXP_STAT"); + printf " \"%s\"", $path; + print "\n"; + }, +#define SSH_FXP_RENAME 18 /* 0x12 */ + 0x12 => sub { + my ($chan, $index, $direction, $id, $data) = @_; + my ($reqid, $srcpath, $dstpath) = &parse("uss", $data); + &sftp_logreq($chan, $direction, $reqid, $id, "SSH_FXP_RENAME"); + printf " \"%s\" \"%s\"", $srcpath, $dstpath; + print "\n"; + }, +#define SSH_FXP_STATUS 101 /* 0x65 */ + 0x65 => sub { + my ($chan, $index, $direction, $id, $data) = @_; + my ($reqid, $status) = &parse("uu", $data); + &sftp_logreply($chan, $direction, $reqid, $id, "SSH_FXP_STATUS"); + print " "; + if ($status eq "0") { print "SSH_FX_OK"; } + elsif ($status eq "1") { print "SSH_FX_EOF"; } + elsif ($status eq "2") { print "SSH_FX_NO_SUCH_FILE"; } + elsif ($status eq "3") { print "SSH_FX_PERMISSION_DENIED"; } + elsif ($status eq "4") { print "SSH_FX_FAILURE"; } + elsif ($status eq "5") { print "SSH_FX_BAD_MESSAGE"; } + elsif ($status eq "6") { print "SSH_FX_NO_CONNECTION"; } + elsif ($status eq "7") { print "SSH_FX_CONNECTION_LOST"; } + elsif ($status eq "8") { print "SSH_FX_OP_UNSUPPORTED"; } + else { printf "[unknown status %d]", $status; } + print "\n"; + }, +#define SSH_FXP_HANDLE 102 /* 0x66 */ + 0x66 => sub { + my ($chan, $index, $direction, $id, $data) = @_; + my ($reqid, $handle) = &parse("us", $data); + &sftp_logreply($chan, $direction, $reqid, $id, "SSH_FXP_HANDLE"); + printf " \"%s\"", &stringescape($handle); + print "\n"; + }, +#define SSH_FXP_DATA 103 /* 0x67 */ + 0x67 => sub { + my ($chan, $index, $direction, $id, $data) = @_; + my ($reqid, $retdata) = &parse("us", $data); + &sftp_logreply($chan, $direction, $reqid, $id, "SSH_FXP_DATA"); + printf " [%d bytes]", length $retdata; + print "\n"; + }, +#define SSH_FXP_NAME 104 /* 0x68 */ + 0x68 => sub { + my ($chan, $index, $direction, $id, $data) = @_; + my ($reqid, $count) = &parse("uu", $data); + &sftp_logreply($chan, $direction, $reqid, $id, "SSH_FXP_NAME"); + for my $i (1..$count) { + my ($name, $longname) = &parse("ss", $data); + my $attrs = &sftp_parse_attrs($data); + print " [name=\"$name\", longname=\"$longname\", attrs=$attrs]"; + } + print "\n"; + }, +#define SSH_FXP_ATTRS 105 /* 0x69 */ + 0x69 => sub { + my ($chan, $index, $direction, $id, $data) = @_; + my ($reqid) = &parse("u", $data); + &sftp_logreply($chan, $direction, $reqid, $id, "SSH_FXP_ATTRS"); + my $attrs = &sftp_parse_attrs($data); + printf " %s", $attrs; + print "\n"; + }, +#define SSH_FXP_EXTENDED 200 /* 0xc8 */ + 0xc8 => sub { + my ($chan, $index, $direction, $id, $data) = @_; + my ($reqid, $type) = &parse("us", $data); + &sftp_logreq($chan, $direction, $reqid, $id, "SSH_FXP_EXTENDED"); + printf " \"%s\"", $type; + print "\n"; + }, +#define SSH_FXP_EXTENDED_REPLY 201 /* 0xc9 */ + 0xc9 => sub { + my ($chan, $index, $direction, $id, $data) = @_; + my ($reqid) = &parse("u", $data); + print "\n"; + &sftp_logreply($chan, $direction, $reqid,$id,"SSH_FXP_EXTENDED_REPLY"); + }, +); + +my ($direction, $seq, $ourseq, $type, $data, $recording); +my %ourseqs = ('i'=>0, 'o'=>0); + +$recording = 0; +while (<>) { + if ($recording) { + if (/^ [0-9a-fA-F]{8} ((?:[0-9a-fA-F]{2} )*[0-9a-fA-F]{2})/) { + push @$data, map { $_ eq "XX" ? -1 : hex $_ } split / /, $1; + } else { + $recording = 0; + my $fullseq = "$direction$ourseq"; + print "$fullseq: $type "; + if (defined $packets{$type}) { + $packets{$type}->($direction, $fullseq, $data); + } else { + printf "raw %s\n", join "", map { sprintf "%02x", $_ } @$data; + } + } + } + if (/^(Incoming|Outgoing) packet #0x([0-9a-fA-F]+), type \d+ \/ 0x[0-9a-fA-F]+ \((.*)\)/) { + $direction = ($1 eq "Incoming" ? 'i' : 'o'); + # $seq is the sequence number quoted in the log file. $ourseq + # is our own count of the sequence number, which differs in + # that it shouldn't wrap at 2^32, should anyone manage to run + # this script over such a huge log file. + $seq = hex $2; + $ourseq = $ourseqs{$direction}++; + $type = $3; + $data = []; + $recording = 1; + } +} + +if ($dumpchannels) { + my %stateorder = ('closed'=>0, 'rejected'=>1, + 'halfclosed'=>2, 'open'=>3, 'halfopen'=>4); + for my $index (0..$#channels) { + my $chan = $channels[$index]; + my $so = $stateorder{$chan->{'state'}}; + $so = 1000 unless defined $so; # any state I've missed above comes last + $chan->{'index'} = sprintf "ch%d", $index; + $chan->{'order'} = sprintf "%08d %08d", $so, $index; + } + my @sortedchannels = sort { $a->{'order'} cmp $b->{'order'} } @channels; + for my $chan (@sortedchannels) { + printf "%s (%s): %s\n", $chan->{'index'}, $chan->{'id'}, $chan->{'state'}; + } +} + +sub parseone { + my ($type, $data) = @_; + if ($type eq "u") { # uint32 + my @bytes = splice @$data, 0, 4; + return "" if @bytes < 4 or grep { $_<0 } @bytes; + return unpack "N", pack "C*", @bytes; + } elsif ($type eq "U") { # uint64 + my @bytes = splice @$data, 0, 8; + return "" if @bytes < 8 or grep { $_<0 } @bytes; + my @words = unpack "NN", pack "C*", @bytes; + return ($words[0] << 32) + $words[1]; + } elsif ($type eq "b") { # boolean + my $byte = shift @$data; + return "" if !defined $byte or $byte < 0; + return $byte ? "yes" : "no"; + } elsif ($type eq "B") { # byte + my $byte = shift @$data; + return "" if !defined $byte or $byte < 0; + return $byte; + } elsif ($type eq "s" or $type eq "m") { # string, mpint + my @bytes = splice @$data, 0, 4; + return "" if @bytes < 4 or grep { $_<0 } @bytes; + my $len = unpack "N", pack "C*", @bytes; + @bytes = splice @$data, 0, $len; + return "" if @bytes < $len or grep { $_<0 } @bytes; + if ($type eq "mpint") { + my $str = ""; + if ($bytes[0] >= 128) { + # Take two's complement. + @bytes = map { 0xFF ^ $_ } @bytes; + for my $i (reverse 0..$#bytes) { + if ($bytes[$i] < 0xFF) { + $bytes[$i]++; + last; + } else { + $bytes[$i] = 0; + } + } + $str = "-"; + } + $str .= "0x" . join "", map { sprintf "%02x", $_ } @bytes; + return $str; + } else { + return pack "C*", @bytes; + } + } +} + +sub parse { + my ($template, $data) = @_; + return map { &parseone($_, $data) } split //, $template; +} + +sub str { + # Quote as a string. If I get enthusiastic I might arrange for + # strange characters inside the string to be quoted. + my $str = shift @_; + return "'$str'"; +} + +sub sftp_setup { + my $index = shift @_; + my $chan = $channels[$index]; + $chan->{'obuf'} = $chan->{'ibuf'} = ''; + $chan->{'ocnt'} = $chan->{'icnt'} = 0; + $chan->{'odata'} = $chan->{'idata'} = \&sftp_data; + $chan->{'sftpreqs'} = {}; +} + +sub sftp_data { + my ($chan, $index, $direction, $data) = @_; + my $buf = \$chan->{$direction."buf"}; + my $cnt = \$chan->{$direction."cnt"}; + $$buf .= $data; + while (length $$buf >= 4) { + my $msglen = unpack "N", $$buf; + last if length $$buf < 4 + $msglen; + my $msg = substr $$buf, 4, $msglen; + $$buf = substr $$buf, 4 + $msglen; + $msg = [unpack "C*", $msg]; + my $type = shift @$msg; + my $id = sprintf "ch%d_sftp_%s%d", $index, $direction, ${$cnt}++; + print "$id: "; + if (defined $sftp_packets{$type}) { + $sftp_packets{$type}->($chan, $index, $direction, $id, $msg); + } else { + printf "unknown SFTP packet type %d\n", $type; + } + } +} + +sub sftp_logreq { + my ($chan, $direction, $reqid, $id, $name) = @_; + print "$name"; + if ($direction eq "o") { # requests coming _in_ are too weird to track + $chan->{'sftpreqs'}->{$reqid} = $id; + } +} + +sub sftp_logreply { + my ($chan, $direction, $reqid, $id, $name) = @_; + print "$name"; + if ($direction eq "i") { # replies going _out_ are too weird to track + if (defined $chan->{'sftpreqs'}->{$reqid}) { + print " to ", $chan->{'sftpreqs'}->{$reqid}; + $chan->{'sftpreqs'}->{$reqid} = undef; + } + } +} + +sub sftp_parse_attrs { + my ($data) = @_; + my ($flags) = &parse("u", $data); + return $flags if $flags eq ""; + my $out = "{"; + my $sep = ""; + if ($flags & 0x00000001) { # SSH_FILEXFER_ATTR_SIZE + $out .= $sep . sprintf "size=%d", &parse("U", $data); + $sep = ", "; + } + if ($flags & 0x00000002) { # SSH_FILEXFER_ATTR_UIDGID + $out .= $sep . sprintf "uid=%d", &parse("u", $data); + $out .= $sep . sprintf "gid=%d", &parse("u", $data); + $sep = ", "; + } + if ($flags & 0x00000004) { # SSH_FILEXFER_ATTR_PERMISSIONS + $out .= $sep . sprintf "perms=%#o", &parse("u", $data); + $sep = ", "; + } + if ($flags & 0x00000008) { # SSH_FILEXFER_ATTR_ACMODTIME + $out .= $sep . sprintf "atime=%d", &parse("u", $data); + $out .= $sep . sprintf "mtime=%d", &parse("u", $data); + $sep = ", "; + } + if ($flags & 0x80000000) { # SSH_FILEXFER_ATTR_EXTENDED + my $extcount = &parse("u", $data); + while ($extcount-- > 0) { + $out .= $sep . sprintf "\"%s\"=\"%s\"", &parse("ss", $data); + $sep = ", "; + } + } + $out .= "}"; + return $out; +} + +sub stringescape { + my ($str) = @_; + $str =~ s!\\!\\\\!g; + $str =~ s![^ -~]!sprintf "\\x%02X", ord $&!eg; + return $str; +} Index: cproxy.c ================================================================== --- cproxy.c +++ cproxy.c @@ -128,11 +128,12 @@ outbuf[0] = 0x01; /* Version */ outbuf[1] = 0x01; /* One attribute */ outbuf[2] = 0x04; /* Response */ outbuf[3] = 0x10; /* Length */ hmacmd5_chap(data, p->chap_current_datalen, - p->cfg.proxy_password, &outbuf[4]); + conf_get_str(p->conf, CONF_proxy_password), + &outbuf[4]); sk_write(p->sub_socket, (char *)outbuf, 20); break; case 0x11: /* Chose a protocol */ if (data[0] != 0x85) { @@ -157,25 +158,27 @@ return 0; } int proxy_socks5_selectchap(Proxy_Socket p) { - if (p->cfg.proxy_username[0] || p->cfg.proxy_password[0]) { + char *username = conf_get_str(p->conf, CONF_proxy_username); + char *password = conf_get_str(p->conf, CONF_proxy_password); + if (username[0] || password[0]) { char chapbuf[514]; int ulen; chapbuf[0] = '\x01'; /* Version */ chapbuf[1] = '\x02'; /* Number of attributes sent */ chapbuf[2] = '\x11'; /* First attribute - algorithms list */ chapbuf[3] = '\x01'; /* Only one CHAP algorithm */ chapbuf[4] = '\x85'; /* ...and it's HMAC-MD5, the core one */ chapbuf[5] = '\x02'; /* Second attribute - username */ - ulen = strlen(p->cfg.proxy_username); + ulen = strlen(username); if (ulen > 255) ulen = 255; if (ulen < 1) ulen = 1; chapbuf[6] = ulen; - memcpy(chapbuf+7, p->cfg.proxy_username, ulen); + memcpy(chapbuf+7, username, ulen); sk_write(p->sub_socket, chapbuf, ulen + 7); p->chap_num_attributes = 0; p->chap_num_attributes_processed = 0; p->chap_current_attribute = -1; Index: dialog.c ================================================================== --- dialog.c +++ dialog.c @@ -11,14 +11,10 @@ #define DEFINE_INTORPTR_FNS #include "putty.h" #include "dialog.h" -/* PuTTY SC start */ -//#include -/* PuTTY SC end */ - int ctrl_path_elements(char *path) { int i = 1; while (*path) { if (*path == '/') i++; @@ -49,10 +45,11 @@ ret->nctrlsets = ret->ctrlsetsize = 0; ret->ctrlsets = NULL; ret->nfrees = ret->freesize = 0; ret->frees = NULL; + ret->freefuncs = NULL; return ret; } void ctrl_free_box(struct controlbox *b) @@ -61,13 +58,14 @@ for (i = 0; i < b->nctrlsets; i++) { ctrl_free_set(b->ctrlsets[i]); } for (i = 0; i < b->nfrees; i++) - sfree(b->frees[i]); + b->freefuncs[i](b->frees[i]); sfree(b->ctrlsets); sfree(b->frees); + sfree(b->freefuncs); sfree(b); } void ctrl_free_set(struct controlset *s) { @@ -183,11 +181,12 @@ b->nctrlsets++; return s; } /* Allocate some private data in a controlbox. */ -void *ctrl_alloc(struct controlbox *b, size_t size) +void *ctrl_alloc_with_free(struct controlbox *b, size_t size, + ctrl_freefn_t freefunc) { void *p; /* * This is an internal allocation routine, so it's allowed to * use smalloc directly. @@ -194,14 +193,27 @@ */ p = smalloc(size); if (b->nfrees >= b->freesize) { b->freesize = b->nfrees + 32; b->frees = sresize(b->frees, b->freesize, void *); + b->freefuncs = sresize(b->freefuncs, b->freesize, ctrl_freefn_t); } - b->frees[b->nfrees++] = p; + b->frees[b->nfrees] = p; + b->freefuncs[b->nfrees] = freefunc; + b->nfrees++; return p; } + +static void ctrl_default_free(void *p) +{ + sfree(p); +} + +void *ctrl_alloc(struct controlbox *b, size_t size) +{ + return ctrl_alloc_with_free(b, size, ctrl_default_free); +} static union control *ctrl_new(struct controlset *s, int type, intorptr helpctx, handler_fn handler, intorptr context) { @@ -458,159 +470,5 @@ sfree(ctrl->fileselect.title); break; } sfree(ctrl); } - -void dlg_stdradiobutton_handler(union control *ctrl, void *dlg, - void *data, int event) -{ - int button; - /* - * For a standard radio button set, the context parameter gives - * offsetof(targetfield, Config), and the extra data per button - * gives the value the target field should take if that button - * is the one selected. - */ - if (event == EVENT_REFRESH) { - for (button = 0; button < ctrl->radio.nbuttons; button++) - if (*(int *)ATOFFSET(data, ctrl->radio.context.i) == - ctrl->radio.buttondata[button].i) - break; - /* We expected that `break' to happen, in all circumstances. */ - assert(button < ctrl->radio.nbuttons); - dlg_radiobutton_set(ctrl, dlg, button); - } else if (event == EVENT_VALCHANGE) { - button = dlg_radiobutton_get(ctrl, dlg); - assert(button >= 0 && button < ctrl->radio.nbuttons); - *(int *)ATOFFSET(data, ctrl->radio.context.i) = - ctrl->radio.buttondata[button].i; - } -} - -void dlg_stdcheckbox_handler(union control *ctrl, void *dlg, - void *data, int event) -{ - int offset, invert; - - /* - * For a standard checkbox, the context parameter gives - * offsetof(targetfield, Config), optionally ORed with - * CHECKBOX_INVERT. - */ - offset = ctrl->checkbox.context.i; - if (offset & CHECKBOX_INVERT) { - offset &= ~CHECKBOX_INVERT; - invert = 1; - } else - invert = 0; - - /* - * C lacks a logical XOR, so the following code uses the idiom - * (!a ^ !b) to obtain the logical XOR of a and b. (That is, 1 - * iff exactly one of a and b is nonzero, otherwise 0.) - */ - - if (event == EVENT_REFRESH) { - dlg_checkbox_set(ctrl,dlg, (!*(int *)ATOFFSET(data,offset) ^ !invert)); - } else if (event == EVENT_VALCHANGE) { - *(int *)ATOFFSET(data, offset) = !dlg_checkbox_get(ctrl,dlg) ^ !invert; - } -} - -void dlg_stdeditbox_handler(union control *ctrl, void *dlg, - void *data, int event) -{ - /* - * The standard edit-box handler expects the main `context' - * field to contain the `offsetof' a field in the structure - * pointed to by `data'. The secondary `context2' field - * indicates the type of this field: - * - * - if context2 > 0, the field is a char array and context2 - * gives its size. - * - if context2 == -1, the field is an int and the edit box - * is numeric. - * - if context2 < -1, the field is an int and the edit box is - * _floating_, and (-context2) gives the scale. (E.g. if - * context2 == -1000, then typing 1.2 into the box will set - * the field to 1200.) - */ - int offset = ctrl->editbox.context.i; - int length = ctrl->editbox.context2.i; - - if (length > 0) { - char *field = (char *)ATOFFSET(data, offset); - if (event == EVENT_REFRESH) { - dlg_editbox_set(ctrl, dlg, field); - } else if (event == EVENT_VALCHANGE) { - dlg_editbox_get(ctrl, dlg, field, length); - } - } else if (length < 0) { - int *field = (int *)ATOFFSET(data, offset); - char data[80]; - if (event == EVENT_REFRESH) { - if (length == -1) - sprintf(data, "%d", *field); - else - sprintf(data, "%g", (double)*field / (double)(-length)); - dlg_editbox_set(ctrl, dlg, data); - } else if (event == EVENT_VALCHANGE) { - dlg_editbox_get(ctrl, dlg, data, lenof(data)); - if (length == -1) - *field = atoi(data); - else - *field = (int)((-length) * atof(data)); - } - } -} - -void dlg_stdfilesel_handler(union control *ctrl, void *dlg, - void *data, int event) -{ - /* - * The standard file-selector handler expects the `context' - * field to contain the `offsetof' a Filename field in the - * structure pointed to by `data'. - */ - int offset = ctrl->fileselect.context.i; - - if (event == EVENT_REFRESH) { - dlg_filesel_set(ctrl, dlg, *(Filename *)ATOFFSET(data, offset)); - } else if (event == EVENT_VALCHANGE) { - dlg_filesel_get(ctrl, dlg, (Filename *)ATOFFSET(data, offset)); - } -} - -/* PuTTY SC start */ -void *sc_get_label_dialog(); -void *sc_get_label_ctrl(); -void sc_tokenlabel_handler(union control *ctrl, void *dlg, void *data, int event ); -void sc_dlg_stdfilesel_handler11(union control *ctrl, void *dlg, void *data, int event) { - int offset = ctrl->fileselect.context.i; - if (event == EVENT_REFRESH) { - dlg_filesel_set(ctrl, dlg, *(Filename *)ATOFFSET(data, offset)); - } else if (event == EVENT_VALCHANGE) { - dlg_filesel_get(ctrl, dlg, (Filename *)ATOFFSET(data, offset)); - } - if(sc_get_label_dialog() != NULL) { - sc_tokenlabel_handler(sc_get_label_ctrl(), sc_get_label_dialog(), data, EVENT_REFRESH); - } -} -/* PuTTY SC end */ - -void dlg_stdfontsel_handler(union control *ctrl, void *dlg, - void *data, int event) -{ - /* - * The standard file-selector handler expects the `context' - * field to contain the `offsetof' a FontSpec field in the - * structure pointed to by `data'. - */ - int offset = ctrl->fontselect.context.i; - - if (event == EVENT_REFRESH) { - dlg_fontsel_set(ctrl, dlg, *(FontSpec *)ATOFFSET(data, offset)); - } else if (event == EVENT_VALCHANGE) { - dlg_fontsel_get(ctrl, dlg, (FontSpec *)ATOFFSET(data, offset)); - } -} Index: dialog.h ================================================================== --- dialog.h +++ dialog.h @@ -160,11 +160,11 @@ * called when that control's setting is changed, or when * the control's setting needs initialising. * * The `data' parameter points to the writable data being * modified as a result of the configuration activity; for - * example, the PuTTY `Config' structure, although not + * example, the PuTTY `Conf' structure, although not * necessarily. * * The `dlg' parameter is passed back to the platform- * specific routines to read and write the actual control * state. @@ -425,10 +425,12 @@ int ncontrols; /* number of `union control' in array */ int ctrlsize; /* allocated size of array */ union control **ctrls; /* actual array */ }; +typedef void (*ctrl_freefn_t)(void *); /* used by ctrl_alloc_with_free */ + /* * This is the container structure which holds a complete set of * controls. */ struct controlbox { @@ -436,10 +438,11 @@ int ctrlsetsize; /* ctrlset size */ struct controlset **ctrlsets; /* actual array of ctrlsets */ int nfrees; int freesize; void **frees; /* array of aux data areas to free */ + ctrl_freefn_t *freefuncs; /* parallel array of free functions */ }; struct controlbox *ctrl_new_box(void); void ctrl_free_box(struct controlbox *); @@ -462,12 +465,18 @@ * will be automatically freed when the controlbox is freed. Note * that a controlbox is a dialog-box _template_, not an instance, * and so data allocated through this function is better not used * to hold modifiable per-instance things. It's mostly here for * allocating structures to be passed as control handler params. + * + * ctrl_alloc_with_free also allows you to provide a function to free + * the structure, in case there are other dynamically allocated bits + * and pieces dangling off it. */ void *ctrl_alloc(struct controlbox *b, size_t size); +void *ctrl_alloc_with_free(struct controlbox *b, size_t size, + ctrl_freefn_t freefunc); /* * Individual routines to create `union control' structures in a controlset. * * Most of these routines allow the most common fields to be set @@ -519,79 +528,20 @@ union control *ctrl_checkbox(struct controlset *, char *label, char shortcut, intorptr helpctx, handler_fn handler, intorptr context); union control *ctrl_tabdelay(struct controlset *, union control *); -/* - * Standard handler routines to cover most of the common cases in - * the config box. - */ -/* - * The standard radio-button handler expects the main `context' - * field to contain the `offsetof' of an int field in the structure - * pointed to by `data', and expects each of the individual button - * data to give a value for that int field. - */ -void dlg_stdradiobutton_handler(union control *ctrl, void *dlg, - void *data, int event); -/* - * The standard checkbox handler expects the main `context' field - * to contain the `offsetof' an int field in the structure pointed - * to by `data', optionally ORed with CHECKBOX_INVERT to indicate - * that the sense of the datum is opposite to the sense of the - * checkbox. - */ -#define CHECKBOX_INVERT (1<<30) -void dlg_stdcheckbox_handler(union control *ctrl, void *dlg, - void *data, int event); -/* - * The standard edit-box handler expects the main `context' field - * to contain the `offsetof' a field in the structure pointed to by - * `data'. The secondary `context2' field indicates the type of - * this field: - * - * - if context2 > 0, the field is a char array and context2 gives - * its size. - * - if context2 == -1, the field is an int and the edit box is - * numeric. - * - if context2 < -1, the field is an int and the edit box is - * _floating_, and (-context2) gives the scale. (E.g. if - * context2 == -1000, then typing 1.2 into the box will set the - * field to 1200.) - */ -void dlg_stdeditbox_handler(union control *ctrl, void *dlg, - void *data, int event); -/* - * The standard file-selector handler expects the main `context' - * field to contain the `offsetof' a Filename field in the - * structure pointed to by `data'. - */ -void dlg_stdfilesel_handler(union control *ctrl, void *dlg, - void *data, int event); -/* PuTTY SC start */ -void sc_dlg_stdfilesel_handler11(union control *ctrl, void *dlg, - void *data, int event); -/* PuTTY SC end */ - -/* - * The standard font-selector handler expects the main `context' - * field to contain the `offsetof' a Font field in the structure - * pointed to by `data'. - */ -void dlg_stdfontsel_handler(union control *ctrl, void *dlg, - void *data, int event); - /* * Routines the platform-independent dialog code can call to read * and write the values of controls. */ void dlg_radiobutton_set(union control *ctrl, void *dlg, int whichbutton); int dlg_radiobutton_get(union control *ctrl, void *dlg); void dlg_checkbox_set(union control *ctrl, void *dlg, int checked); int dlg_checkbox_get(union control *ctrl, void *dlg); void dlg_editbox_set(union control *ctrl, void *dlg, char const *text); -void dlg_editbox_get(union control *ctrl, void *dlg, char *buffer, int length); +char *dlg_editbox_get(union control *ctrl, void *dlg); /* result must be freed by caller */ /* The `listbox' functions can also apply to combo boxes. */ void dlg_listbox_clear(union control *ctrl, void *dlg); void dlg_listbox_del(union control *ctrl, void *dlg, int index); void dlg_listbox_add(union control *ctrl, void *dlg, char const *text); /* @@ -607,14 +557,14 @@ /* dlg_listbox_index returns <0 if no single element is selected. */ int dlg_listbox_index(union control *ctrl, void *dlg); int dlg_listbox_issel(union control *ctrl, void *dlg, int index); void dlg_listbox_select(union control *ctrl, void *dlg, int index); void dlg_text_set(union control *ctrl, void *dlg, char const *text); -void dlg_filesel_set(union control *ctrl, void *dlg, Filename fn); -void dlg_filesel_get(union control *ctrl, void *dlg, Filename *fn); -void dlg_fontsel_set(union control *ctrl, void *dlg, FontSpec fn); -void dlg_fontsel_get(union control *ctrl, void *dlg, FontSpec *fn); +void dlg_filesel_set(union control *ctrl, void *dlg, Filename *fn); +Filename *dlg_filesel_get(union control *ctrl, void *dlg); +void dlg_fontsel_set(union control *ctrl, void *dlg, FontSpec *fn); +FontSpec *dlg_fontsel_get(union control *ctrl, void *dlg); /* * Bracketing a large set of updates in these two functions will * cause the front end (if possible) to delay updating the screen * until it's all complete, thus avoiding flicker. */ Index: doc/blurb.but ================================================================== --- doc/blurb.but +++ doc/blurb.but @@ -1,6 +1,8 @@ -\define{versionidblurb} \versionid $Id: blurb.but 9072 2011-01-05 12:01:00Z jacob $ +\define{versionidblurb} \versionid $Id: blurb.but 9993 2013-08-05 15:15:17Z jacob $ + +\define{dash} \u2013{-} \title PuTTY User Manual \cfg{xhtml-leaf-level}{1} \cfg{xhtml-leaf-smallest-contents}{2} @@ -29,8 +31,8 @@ features not described here; and the \i\cw{pterm} and command-line \cw{puttygen} utilities are not described at all. The only Unix-specific documentation that currently exists is the \I{man pages for PuTTY tools}man pages. -\copyright This manual is copyright 2001-2011 Simon Tatham. All +\copyright This manual is copyright 2001-2013 Simon Tatham. All rights reserved. You may distribute this documentation under the MIT licence. See \k{licence} for the licence text in full. Index: doc/config.but ================================================================== --- doc/config.but +++ doc/config.but @@ -1,6 +1,6 @@ -\define{versionidconfig} \versionid $Id: config.but 9063 2010-12-29 14:11:25Z simon $ +\define{versionidconfig} \versionid $Id: config.but 10088 2013-11-18 22:34:57Z jacob $ \C{config} Configuring PuTTY This chapter describes all the \i{configuration options} in PuTTY. @@ -1252,16 +1252,20 @@ the server expects. Unfortunately, there is no satisfactory mechanism for PuTTY and the server to communicate this information, so it must usually be manually configured. There are a lot of character sets to choose from. The \q{Remote -character set} option lets you select one. By default PuTTY will -attempt to choose a character set that is right for your \i{locale} as -reported by Windows; if it gets it wrong, you can select a different -one using this control. +character set} option lets you select one. -A few notable character sets are: +By default PuTTY will use the \i{UTF-8} encoding of \i{Unicode}, which +can represent pretty much any character; data coming from the server +is interpreted as UTF-8, and keystrokes are sent UTF-8 encoded. This +is what most modern distributions of Linux will expect by default. +However, if this is wrong for your server, you can select a different +character set using this control. + +A few other notable character sets are: \b The \i{ISO-8859} series are all standard character sets that include various accented characters appropriate for different sets of languages. @@ -1271,16 +1275,10 @@ Euro symbol. \b If you want the old IBM PC character set with block graphics and line-drawing characters, you can select \q{\i{CP437}}. -\b PuTTY also supports \i{Unicode} mode, in which the data coming from -the server is interpreted as being in the \i{UTF-8} encoding of Unicode, -and keystrokes are sent UTF-8 encoded. If you select \q{UTF-8} as a -character set you can use this mode. Not all server-side applications -will support it. - If you need support for a numeric \i{code page} which is not listed in the drop-down list, such as code page 866, then you can try entering its name manually (\c{\i{CP866}} for example) in the list box. If the underlying version of Windows has the appropriate translation table installed, PuTTY will use it. @@ -1539,24 +1537,26 @@ If you do not see \cq{colors#256} in the output, you may need to change your terminal setting. On modern Linux machines, you could try \cq{xterm-256color}. -\S{config-boldcolour} \q{Bolded text is a different colour} +\S{config-boldcolour} \q{Indicate bolded text by changing} \cfg{winhelp-topic}{colours.bold} When the server sends a \i{control sequence} indicating that some text -should be displayed in \i{bold}, PuTTY can handle this two ways. It can -either change the \i{font} for a bold version, or use the same font in a -brighter colour. This control lets you choose which. - -By default the box is checked, so non-bold text is displayed in -light grey and bold text is displayed in bright white (and similarly -in other colours). If you uncheck the box, bold and non-bold text -will be displayed in the same colour, and instead the font will -change to indicate the difference. +should be displayed in \i{bold}, PuTTY can handle this in several +ways. It can either change the \i{font} for a bold version, or use the +same font in a brighter colour, or it can do both (brighten the colour +\e{and} embolden the font). This control lets you choose which. + +By default bold is indicated by colour, so non-bold text is displayed +in light grey and bold text is displayed in bright white (and +similarly in other colours). If you change the setting to \q{The font} +box, bold and non-bold text will be displayed in the same colour, and +instead the font will change to indicate the difference. If you select +\q{Both}, the font and the colour will both change. \S{config-logpalette} \q{Attempt to use \i{logical palettes}} \cfg{winhelp-topic}{colours.logpal} @@ -2272,61 +2272,70 @@ If you select \q{1 only} or \q{2 only} here, PuTTY will only connect if the server you connect to offers the SSH protocol version you have specified. -\S{config-ssh-encryption} \ii{Encryption} algorithm selection - -\cfg{winhelp-topic}{ssh.ciphers} - -PuTTY supports a variety of different \i{encryption algorithm}s, and -allows you to choose which one you prefer to use. You can do this by -dragging the algorithms up and down in the list box (or moving them -using the Up and Down buttons) to specify a preference order. When -you make an SSH connection, PuTTY will search down the list from the -top until it finds an algorithm supported by the server, and then -use that. - -PuTTY currently supports the following algorithms: - -\b \i{AES} (Rijndael) - 256, 192, or 128-bit SDCTR or CBC (SSH-2 only) - -\b \i{Arcfour} (RC4) - 256 or 128-bit stream cipher (SSH-2 only) - -\b \i{Blowfish} - 256-bit SDCTR (SSH-2 only) or 128-bit CBC - -\b \ii{Triple-DES} - 168-bit SDCTR (SSH-2 only) or CBC - -\b \ii{Single-DES} - 56-bit CBC (see below for SSH-2) - -If the algorithm PuTTY finds is below the \q{warn below here} line, -you will see a warning box when you make the connection: - -\c The first cipher supported by the server -\c is single-DES, which is below the configured -\c warning threshold. -\c Do you want to continue with this connection? - -This warns you that the first available encryption is not a very -secure one. Typically you would put the \q{warn below here} line -between the encryptions you consider secure and the ones you -consider substandard. By default, PuTTY supplies a preference order -intended to reflect a reasonable preference in terms of security and -speed. - -In SSH-2, the encryption algorithm is negotiated independently for -each direction of the connection, although PuTTY does not support -separate configuration of the preference orders. As a result you may -get two warnings similar to the one above, possibly with different -encryptions. - -Single-DES is not recommended in the SSH-2 protocol -standards, but one or two server implementations do support it. -PuTTY can use single-DES to interoperate with -these servers if you enable the \q{Enable legacy use of single-DES in -SSH-2} option; by default this is disabled and PuTTY will stick to -recommended ciphers. +\S{config-ssh-sharing} Sharing an SSH connection between PuTTY tools + +\cfg{winhelp-topic}{ssh.sharing} + +The controls in this box allow you to configure PuTTY to reuse an +existing SSH connection, where possible. + +The SSH-2 protocol permits you to run multiple data channels over the +same SSH connection, so that you can log in just once (and do the +expensive encryption setup just once) and then have more than one +terminal window open. + +Each instance of PuTTY can still run at most one terminal session, but +using the controls in this box, you can configure PuTTY to check if +another instance of itself has already connected to the target host, +and if so, share that instance's SSH connection instead of starting a +separate new one. + +To enable this feature, just tick the box \q{Share SSH connections if +possible}. Then, whenever you start up a PuTTY session connecting to a +particular host, it will try to reuse an existing SSH connection if +one is available. For example, selecting \q{Duplicate Session} from +the system menu will launch another session on the same host, and if +sharing is enabled then it will reuse the existing SSH connection. + +When this mode is in use, the first PuTTY that connected to a given +server becomes the \q{upstream}, which means that it is the one +managing the real SSH connection. All subsequent PuTTYs which reuse +the connection are referred to as \q{downstreams}: they do not connect +to the real server at all, but instead connect to the upstream PuTTY +via local inter-process communication methods. + +For this system to be activated, \e{both} the upstream and downstream +instances of PuTTY must have the sharing option enabled. + +The upstream PuTTY can therefore not terminate until all its +downstreams have closed. This is similar to the effect you get with +port forwarding or X11 forwarding, in which a PuTTY whose terminal +session has already finished will still remain open so as to keep +serving forwarded connections. + +In case you need to configure this system in more detail, there are +two additional checkboxes which allow you to specify whether a +particular PuTTY can act as an upstream or a downstream or both. +(These boxes only take effect if the main \q{Share SSH connections if +possible} box is also ticked.) By default both of these boxes are +ticked, so that multiple PuTTYs started from the same configuration +will designate one of themselves as the upstream and share a single +connection; but if for some reason you need a particular PuTTY +configuration \e{not} to be an upstream (e.g. because you definitely +need it to close promptly) or not to be a downstream (e.g. because it +needs to do its own authentication using a special private key) then +you can untick one or the other of these boxes. + +I have referred to \q{PuTTY} throughout the above discussion, but all +the other PuTTY tools which make SSH connections can use this +mechanism too. For example, if PSCP or PSFTP loads a configuration +with sharing enabled, then it can act as a downstream and use an +existing SSH connection set up by an instance of GUI PuTTY. The one +special case is that PSCP and PSFTP will \e{never} act as upstreams. \H{config-ssh-kex} The Kex panel \# FIXME: This whole section is draft. Feel free to revise. @@ -2450,10 +2459,62 @@ in part on rekeys occuring before a 32-bit packet sequence number wraps around. Unlike time-based rekeys, data-based rekeys won't occur when the SSH connection is idle, so they shouldn't cause the same problems. The SSH-1 protocol, incidentally, has even weaker integrity protection than SSH-2 without rekeys. + +\H{config-ssh-encryption} The Cipher panel + +\cfg{winhelp-topic}{ssh.ciphers} + +PuTTY supports a variety of different \i{encryption algorithm}s, and +allows you to choose which one you prefer to use. You can do this by +dragging the algorithms up and down in the list box (or moving them +using the Up and Down buttons) to specify a preference order. When +you make an SSH connection, PuTTY will search down the list from the +top until it finds an algorithm supported by the server, and then +use that. + +PuTTY currently supports the following algorithms: + +\b \i{AES} (Rijndael) - 256, 192, or 128-bit SDCTR or CBC (SSH-2 only) + +\b \i{Arcfour} (RC4) - 256 or 128-bit stream cipher (SSH-2 only) + +\b \i{Blowfish} - 256-bit SDCTR (SSH-2 only) or 128-bit CBC + +\b \ii{Triple-DES} - 168-bit SDCTR (SSH-2 only) or CBC + +\b \ii{Single-DES} - 56-bit CBC (see below for SSH-2) + +If the algorithm PuTTY finds is below the \q{warn below here} line, +you will see a warning box when you make the connection: + +\c The first cipher supported by the server +\c is single-DES, which is below the configured +\c warning threshold. +\c Do you want to continue with this connection? + +This warns you that the first available encryption is not a very +secure one. Typically you would put the \q{warn below here} line +between the encryptions you consider secure and the ones you +consider substandard. By default, PuTTY supplies a preference order +intended to reflect a reasonable preference in terms of security and +speed. + +In SSH-2, the encryption algorithm is negotiated independently for +each direction of the connection, although PuTTY does not support +separate configuration of the preference orders. As a result you may +get two warnings similar to the one above, possibly with different +encryptions. + +Single-DES is not recommended in the SSH-2 protocol +standards, but one or two server implementations do support it. +PuTTY can use single-DES to interoperate with +these servers if you enable the \q{Enable legacy use of single-DES in +SSH-2} option; by default this is disabled and PuTTY will stick to +recommended ciphers. \H{config-ssh-auth} The Auth panel The Auth panel allows you to configure \i{authentication} options for SSH sessions. @@ -3203,10 +3264,33 @@ If this bug is detected, PuTTY never allows the channel's \i{flow-control window} to grow large enough to allow the server to send an over-sized packet. If this bug is enabled when talking to a correct server, the session will work correctly, but download performance will be less than it could be. + +\S{config-ssh-bug-winadj} \q{Chokes on PuTTY's SSH-2 \cq{winadj} requests} + +\cfg{winhelp-topic}{ssh.bugs.winadj} + +PuTTY sometimes sends a special request to SSH servers in the middle +of channel data, with the name \cw{winadj@putty.projects.tartarus.org} +(see \k{sshnames-channel}). The purpose of this request is to measure +the round-trip time to the server, which PuTTY uses to tune its flow +control. The server does not actually have to \e{understand} the +message; it is expected to send back a \cw{SSH_MSG_CHANNEL_FAILURE} +message indicating that it didn't understand it. (All PuTTY needs for +its timing calculations is \e{some} kind of response.) + +It has been known for some SSH servers to get confused by this message +in one way or another \dash because it has a long name, or because +they can't cope with unrecognised request names even to the extent of +sending back the correct failure response, or because they handle it +sensibly but fill up the server's log file with pointless spam, or +whatever. PuTTY therefore supports this bug-compatibility flag: if it +believes the server has this bug, it will never send its +\cq{winadj@putty.projects.tartarus.org} request, and will make do +without its timing data. \H{config-serial} The Serial panel The \i{Serial} panel allows you to configure options that only apply when PuTTY is connecting to a local \I{serial port}\i{serial line}. Index: doc/errors.but ================================================================== --- doc/errors.but +++ doc/errors.but @@ -1,6 +1,6 @@ -\define{versioniderrors} \versionid $Id: errors.but 8897 2010-03-13 14:47:14Z jacob $ +\define{versioniderrors} \versionid $Id: errors.but 9627 2012-08-26 09:50:57Z jacob $ \C{errors} Common \i{error messages} This chapter lists a number of common error messages which PuTTY and its associated tools can produce, and explains what they mean in @@ -56,24 +56,10 @@ expect the host key to have changed. If so, verify the new host key in the same way as you would if it was new. See \k{gs-hostkey} for more information on host keys. -\H{errors-portfwd-space} \q{Out of space for port forwardings} - -PuTTY has a fixed-size buffer which it uses to store the details of -all \i{port forwardings} you have set up in an SSH session. If you -specify too many port forwardings on the PuTTY or Plink command line -and this buffer becomes full, you will see this error message. - -We need to fix this (fixed-size buffers are almost always a mistake) -but we haven't got round to it. If you actually have trouble with -this, let us know and we'll move it up our priority list. - -If you're running into this limit, you may want to consider using -dynamic port forwarding instead; see \k{using-port-forwarding}. - \H{errors-cipher-warning} \q{The first cipher supported by the server is ... below the configured warning threshold} This occurs when the SSH server does not offer any ciphers which you have configured PuTTY to consider strong enough. By default, PuTTY Index: doc/faq.but ================================================================== --- doc/faq.but +++ doc/faq.but @@ -1,6 +1,6 @@ -\define{versionidfaq} \versionid $Id: faq.but 8733 2009-11-01 22:06:05Z jacob $ +\define{versionidfaq} \versionid $Id: faq.but 9391 2012-01-30 00:29:32Z jacob $ \A{faq} PuTTY \i{FAQ} This FAQ is published on the PuTTY web site, and also provided as an appendix in the manual. @@ -1041,10 +1041,27 @@ This is caused by a bug in certain versions of \i{Windows XP} which is triggered by PuTTY 0.58. This was fixed in 0.59. The \W{http://www.chiark.greenend.org.uk/~sgtatham/putty/wishlist/xp-wont-run}{\q{xp-wont-run}} entry in PuTTY's wishlist has more details. +\S{faq-system32}{Question} When I put PuTTY in +\cw{C:\\WINDOWS\\\i{SYSTEM32}} on my \i{64-bit Windows} system, +\i{\q{Duplicate Session}} doesn't work. + +The short answer is not to put the PuTTY executables in that location. + +On 64-bit systems, \cw{C:\\WINDOWS\\SYSTEM32} is intended to contain +only 64-bit binaries; Windows' 32-bit binaries live in +\cw{C:\\WINDOWS\\SYSWOW64}. When a 32-bit program such as PuTTY runs +on a 64-bit system, it cannot by default see the \q{real} +\cw{C:\\WINDOWS\\SYSTEM32} at all, because the +\W{http://msdn.microsoft.com/en-us/library/aa384187(v=vs.85).aspx}{File +System Redirector} arranges that the running program sees the +appropriate kind of binaries in \cw{SYSTEM32}. Thus, operations in +the PuTTY suite that involve it accessing its own executables, such as +\i{\q{New Session}} and \q{Duplicate Session}, will not work. + \H{faq-secure} Security questions \S{faq-publicpc}{Question} Is it safe for me to download PuTTY and use it on a public PC? Index: doc/index.but ================================================================== --- doc/index.but +++ doc/index.but @@ -1,6 +1,6 @@ -\define{versionidindex} \versionid $Id: index.but 9009 2010-09-25 16:18:02Z jacob $ +\define{versionidindex} \versionid $Id: index.but 9391 2012-01-30 00:29:32Z jacob $ \IM{Unix version} Unix version of PuTTY tools \IM{Unix version} Linux version of PuTTY tools \IM{Unix} Unix @@ -847,5 +847,10 @@ \IM{web browsers} web browser \IM{GSSAPI credential delegation} GSSAPI credential delegation \IM{GSSAPI credential delegation} credential delegation, GSSAPI \IM{GSSAPI credential delegation} delegation, of GSSAPI credentials + +\IM{SYSTEM32} \cw{SYSTEM32} directory, on Windows + +\IM{64-bit Windows} 64-bit Windows +\IM{64-bit Windows} Windows, 64-bit Index: doc/licence.but ================================================================== --- doc/licence.but +++ doc/licence.but @@ -1,10 +1,10 @@ -\define{versionidlicence} \versionid $Id: licence.but 9072 2011-01-05 12:01:00Z jacob $ +\define{versionidlicence} \versionid $Id: licence.but 9993 2013-08-05 15:15:17Z jacob $ \A{licence} PuTTY \ii{Licence} -PuTTY is \i{copyright} 1997-2011 Simon Tatham. +PuTTY is \i{copyright} 1997-2013 Simon Tatham. Portions copyright Robert de Bath, Joris van Rantwijk, Delian Delchev, Andreas Schultz, Jeroen Massar, Wez Furlong, Nicolas Barry, Justin Bradford, Ben Harris, Malcolm Smith, Ahmad Khalifa, Markus Kuhn, Colin Watson, and CORE SDI S.A. Index: doc/man-ptel.but ================================================================== --- doc/man-ptel.but +++ doc/man-ptel.but @@ -35,11 +35,11 @@ \dt \cw{\-fb} \e{font-name} \dd Specify the font to use for bold text displayed in the terminal. If the \cw{BoldAsColour} resource is set to 1 (the default), bold text will be displayed in different colours instead of a different font, -so this option will be ignored. If \cw{BoldAsColour} is set to 0 +so this option will be ignored. If \cw{BoldAsColour} is set to 0 or 2 and you do not specify a bold font, \cw{puttytel} will overprint the normal font to make it look bolder. \dt \cw{\-fw} \e{font-name} @@ -48,11 +48,11 @@ \dt \cw{\-fwb} \e{font-name} \dd Specify the font to use for bold double-width characters (typically Chinese, Japanese and Korean text). Like \cw{-fb}, this -will be ignored unless the \cw{BoldAsColour} resource is set to 0. +will be ignored unless the \cw{BoldAsColour} resource is set to 0 or 2. \dt \cw{\-geometry} \e{geometry} \dd Specify the size of the terminal, in rows and columns of text. See \e{X(7)} for more information on the syntax of geometry @@ -72,16 +72,16 @@ \dd Specify the background colour to use for normal text. \dt \cw{\-bfg} \e{colour} \dd Specify the foreground colour to use for bold text, if the -\cw{BoldAsColour} resource is set to 1 (the default). +\cw{BoldAsColour} resource is set to 1 (the default) or 2. \dt \cw{\-bbg} \e{colour} \dd Specify the foreground colour to use for bold reverse-video text, if -the \cw{BoldAsColour} resource is set to 1 (the default). (This +the \cw{BoldAsColour} resource is set to 1 (the default) or 2. (This colour is best thought of as the bold version of the background colour; so it only appears when text is displayed \e{in} the background colour.) \dt \cw{\-cfg} \e{colour} Index: doc/man-pter.but ================================================================== --- doc/man-pter.but +++ doc/man-pter.but @@ -55,11 +55,11 @@ \dt \cw{\-fb} \e{font-name} \dd Specify the font to use for bold text displayed in the terminal. If the \cw{BoldAsColour} resource is set to 1 (the default), bold text will be displayed in different colours instead of a different font, -so this option will be ignored. If \cw{BoldAsColour} is set to 0 +so this option will be ignored. If \cw{BoldAsColour} is set to 0 or 2 and you do not specify a bold font, \cw{pterm} will overprint the normal font to make it look bolder. \dt \cw{\-fw} \e{font-name} @@ -68,11 +68,11 @@ \dt \cw{\-fwb} \e{font-name} \dd Specify the font to use for bold double-width characters (typically Chinese, Japanese and Korean text). Like \cw{-fb}, this -will be ignored unless the \cw{BoldAsColour} resource is set to 0. +will be ignored unless the \cw{BoldAsColour} resource is set to 0 or 2. \dt \cw{\-geometry} \e{geometry} \dd Specify the size of the terminal, in rows and columns of text. See \e{X(7)} for more information on the syntax of geometry @@ -92,16 +92,16 @@ \dd Specify the background colour to use for normal text. \dt \cw{\-bfg} \e{colour} \dd Specify the foreground colour to use for bold text, if the -\cw{BoldAsColour} resource is set to 1 (the default). +\cw{BoldAsColour} resource is set to 1 (the default) or 2. \dt \cw{\-bbg} \e{colour} \dd Specify the foreground colour to use for bold reverse-video text, if -the \cw{BoldAsColour} resource is set to 1 (the default). (This +the \cw{BoldAsColour} resource is set to 1 (the default) or 2. (This colour is best thought of as the bold version of the background colour; so it only appears when text is displayed \e{in} the background colour.) \dt \cw{\-cfg} \e{colour} @@ -496,11 +496,11 @@ \dt \cw{pterm.BoldFont} \dd This resource is the same as the \cw{\-fb} command-line option: it controls the font used to display bold text when \cw{BoldAsColour} -is turned off. The default is unset (the font will be bolded by +is set to 0 or 2. The default is unset (the font will be bolded by printing it twice at a one-pixel offset). \dt \cw{pterm.WideFont} \dd This resource is the same as the \cw{\-fw} command-line option: it @@ -509,11 +509,11 @@ \dt \cw{pterm.WideBoldFont} \dd This resource is the same as the \cw{\-fwb} command-line option: it controls the font used to display double-width characters in bold, -when \cw{BoldAsColour} is turned off. The default is unset +when \cw{BoldAsColour} is set to 0 or 2. The default is unset (double-width characters are displayed in bold by printing them twice at a one-pixel offset). \dt \cw{pterm.ShadowBoldOffset} @@ -527,14 +527,15 @@ to the left; for really large fonts, you may want to set it higher than 1 (in one direction or the other). \dt \cw{pterm.BoldAsColour} -\dd This option should be set to either 0 or 1; the default is 1. It -specifies the default state of auto wrap mode. When set to 1, bold +\dd This option should be set to either 0, 1, or 2; the default is 1. +It specifies how bold text should be displayed. When set to 1, bold text is shown by displaying it in a brighter colour; when set to 0, -bold text is shown by displaying it in a heavier font. +bold text is shown by displaying it in a heavier font; when set to 2, +both effects happen at once (a heavy font \e{and} a brighter colour). \dt \cw{pterm.Colour0}, \cw{pterm.Colour1}, ..., \cw{pterm.Colour21} \dd These options control the various colours used to display text in the \cw{pterm} window. Each one should be specified as a triple Index: doc/man-putt.but ================================================================== --- doc/man-putt.but +++ doc/man-putt.but @@ -35,11 +35,11 @@ \dd Specify the font to use for bold text displayed in the terminal. If the \cw{BoldAsColour} resource is set to 1 (the default), bold text will be displayed in different colours instead of a different font, so this option will be ignored. If \cw{BoldAsColour} is set to -0 and you do not specify a bold font, \cw{putty} will overprint the +0 or 2 and you do not specify a bold font, \cw{putty} will overprint the normal font to make it look bolder. \dt \cw{\-fw} \e{font-name} \dd Specify the font to use for double-width characters (typically @@ -47,11 +47,11 @@ \dt \cw{\-fwb} \e{font-name} \dd Specify the font to use for bold double-width characters (typically Chinese, Japanese and Korean text). Like \cw{-fb}, this -will be ignored unless the \cw{BoldAsColour} resource is set to 0. +will be ignored unless the \cw{BoldAsColour} resource is set to 0 or 2. \dt \cw{\-geometry} \e{geometry} \dd Specify the size of the terminal, in rows and columns of text. See \e{X(7)} for more information on the syntax of geometry @@ -71,16 +71,16 @@ \dd Specify the background colour to use for normal text. \dt \cw{\-bfg} \e{colour} \dd Specify the foreground colour to use for bold text, if the -\cw{BoldAsColour} resource is set to 1 (the default). +\cw{BoldAsColour} resource is set to 1 (the default) or 2. \dt \cw{\-bbg} \e{colour} \dd Specify the foreground colour to use for bold reverse-video -text, if the \cw{BoldAsColour} resource is set to 1 (the default). +text, if the \cw{BoldAsColour} resource is set to 1 (the default) or 2. (This colour is best thought of as the bold version of the background colour; so it only appears when text is displayed \e{in} the background colour.) \dt \cw{\-cfg} \e{colour} Index: doc/pgpkeys.but ================================================================== --- doc/pgpkeys.but +++ doc/pgpkeys.but @@ -1,6 +1,6 @@ -\define{versionidpgpkeys} \versionid $Id: pgpkeys.but 5598 2005-04-05 19:36:25Z simon $ +\define{versionidpgpkeys} \versionid $Id$ \A{pgpkeys} PuTTY download keys and signatures \cfg{winhelp-topic}{pgpfingerprints} Index: doc/plink.but ================================================================== --- doc/plink.but +++ doc/plink.but @@ -1,6 +1,6 @@ -\define{versionidplink} \versionid $Id: plink.but 9366 2011-12-10 12:08:09Z simon $ +\define{versionidplink} \versionid $Id: plink.but 9998 2013-08-06 17:09:07Z simon $ \C{plink} Using the command-line connection tool \i{Plink} \i{Plink} (PuTTY Link) is a command-line connection tool similar to UNIX \c{ssh}. It is mostly used for \i{automated operations}, such as @@ -41,11 +41,11 @@ version of Plink you're using, and gives you a brief summary of how to use Plink: \c Z:\sysosd>plink \c PuTTY Link: command-line connection utility -\c Release 0.62 +\c Release 0.63 \c Usage: plink [options] [user@]host [command] \c ("host" can also be a PuTTY saved session name) \c Options: \c -V print version information and exit \c -pgpfp print PGP key fingerprints and exit Index: doc/pscp.but ================================================================== --- doc/pscp.but +++ doc/pscp.but @@ -1,6 +1,6 @@ -\define{versionidpscp} \versionid $Id: pscp.but 9366 2011-12-10 12:08:09Z simon $ +\define{versionidpscp} \versionid $Id: pscp.but 9998 2013-08-06 17:09:07Z simon $ \#FIXME: Need examples \C{pscp} Using \i{PSCP} to transfer files securely @@ -39,11 +39,11 @@ version of PSCP you're using, and gives you a brief summary of how to use PSCP: \c Z:\owendadmin>pscp \c PuTTY Secure Copy client -\c Release 0.62 +\c Release 0.63 \c Usage: pscp [options] [user@]host:source target \c pscp [options] source [source...] [user@]host:target \c pscp [options] -ls [user@]host:filespec \c Options: \c -V print version information and exit Index: doc/pubkey.but ================================================================== --- doc/pubkey.but +++ doc/pubkey.but @@ -1,6 +1,6 @@ -\define{versionidpubkey} \versionid $Id: pubkey.but 8607 2009-07-12 12:02:58Z simon $ +\define{versionidpubkey} \versionid $Id: pubkey.but 9422 2012-03-04 01:01:11Z jacob $ \C{pubkey} Using public keys for SSH authentication \H{pubkey-intro} \ii{Public key authentication} - an introduction @@ -149,22 +149,10 @@ The \q{Number of bits} input box allows you to choose the strength of the key PuTTYgen will generate. Currently 1024 bits should be sufficient for most purposes. -Note that an RSA key is generated by finding two primes of half the -length requested, and then multiplying them together. For example, -if you ask PuTTYgen for a 1024-bit RSA key, it will create two -512-bit primes and multiply them. The result of this multiplication -might be 1024 bits long, or it might be only 1023; so you may not -get the exact length of key you asked for. This is perfectly normal, -and you do not need to worry. The lengths should only ever differ by -one, and there is no perceptible drop in security as a result. - -DSA keys are not created by multiplying primes together, so they -should always be exactly the length you asked for. - \S{puttygen-generate} The \q{Generate} button \cfg{winhelp-topic}{puttygen.generate} Once you have chosen the type of key you want, and the strength of ADDED errsock.c Index: errsock.c ================================================================== --- /dev/null +++ errsock.c @@ -0,0 +1,67 @@ +/* + * A dummy Socket implementation which just holds an error message. + */ + +#include +#include + +#define DEFINE_PLUG_METHOD_MACROS +#include "tree234.h" +#include "putty.h" +#include "network.h" + +typedef struct Socket_error_tag *Error_Socket; + +struct Socket_error_tag { + const struct socket_function_table *fn; + /* the above variable absolutely *must* be the first in this structure */ + + char *error; + Plug plug; +}; + +static Plug sk_error_plug(Socket s, Plug p) +{ + Error_Socket ps = (Error_Socket) s; + Plug ret = ps->plug; + if (p) + ps->plug = p; + return ret; +} + +static void sk_error_close(Socket s) +{ + Error_Socket ps = (Error_Socket) s; + + sfree(ps->error); + sfree(ps); +} + +static const char *sk_error_socket_error(Socket s) +{ + Error_Socket ps = (Error_Socket) s; + return ps->error; +} + +Socket new_error_socket(const char *errmsg, Plug plug) +{ + static const struct socket_function_table socket_fn_table = { + sk_error_plug, + sk_error_close, + NULL /* write */, + NULL /* write_oob */, + NULL /* write_eof */, + NULL /* flush */, + NULL /* set_frozen */, + sk_error_socket_error + }; + + Error_Socket ret; + + ret = snew(struct Socket_error_tag); + ret->fn = &socket_fn_table; + ret->plug = plug; + ret->error = dupstr(errmsg); + + return (Socket) ret; +} Index: import.c ================================================================== --- import.c +++ import.c @@ -287,12 +287,12 @@ int bytes; unsigned char *d = (unsigned char *) data; if (len < 4) goto error; - bytes = GET_32BIT(d); - if (len < 4+bytes) + bytes = toint(GET_32BIT(d)); + if (bytes < 0 || len-4 < bytes) goto error; ret->start = d + 4; ret->bytes = bytes; return bytes+4; @@ -319,11 +319,11 @@ static struct openssh_key *load_openssh_key(const Filename *filename, const char **errmsg_p) { struct openssh_key *ret; - FILE *fp; + FILE *fp = NULL; char *line = NULL; char *errmsg, *p; int headers_done; char base64_bit[4]; int base64_chars = 0; @@ -332,11 +332,11 @@ ret->keyblob = NULL; ret->keyblob_len = ret->keyblob_size = 0; ret->encrypted = 0; memset(ret->iv, 0, sizeof(ret->iv)); - fp = f_open(*filename, "r", FALSE); + fp = f_open(filename, "r", FALSE); if (!fp) { errmsg = "unable to open key file"; goto error; } @@ -356,11 +356,11 @@ ret->type = OSSH_DSA; else { errmsg = "unrecognised key type"; goto error; } - memset(line, 0, strlen(line)); + smemclr(line, strlen(line)); sfree(line); line = NULL; headers_done = 0; while (1) { @@ -368,12 +368,15 @@ errmsg = "unexpected end of file"; goto error; } strip_crlf(line); if (0 == strncmp(line, "-----END ", 9) && - 0 == strcmp(line+strlen(line)-16, "PRIVATE KEY-----")) + 0 == strcmp(line+strlen(line)-16, "PRIVATE KEY-----")) { + sfree(line); + line = NULL; break; /* done */ + } if ((p = strchr(line, ':')) != NULL) { if (headers_done) { errmsg = "header found in body of key data"; goto error; } @@ -440,20 +443,23 @@ } memcpy(ret->keyblob + ret->keyblob_len, out, len); ret->keyblob_len += len; - memset(out, 0, sizeof(out)); + smemclr(out, sizeof(out)); } p++; } } - memset(line, 0, strlen(line)); + smemclr(line, strlen(line)); sfree(line); line = NULL; } + + fclose(fp); + fp = NULL; if (ret->keyblob_len == 0 || !ret->keyblob) { errmsg = "key body not present"; goto error; } @@ -461,30 +467,31 @@ if (ret->encrypted && ret->keyblob_len % 8 != 0) { errmsg = "encrypted key blob is not a multiple of cipher block size"; goto error; } - memset(base64_bit, 0, sizeof(base64_bit)); + smemclr(base64_bit, sizeof(base64_bit)); if (errmsg_p) *errmsg_p = NULL; return ret; error: if (line) { - memset(line, 0, strlen(line)); + smemclr(line, strlen(line)); sfree(line); line = NULL; } - memset(base64_bit, 0, sizeof(base64_bit)); + smemclr(base64_bit, sizeof(base64_bit)); if (ret) { if (ret->keyblob) { - memset(ret->keyblob, 0, ret->keyblob_size); + smemclr(ret->keyblob, ret->keyblob_size); sfree(ret->keyblob); } - memset(ret, 0, sizeof(*ret)); + smemclr(ret, sizeof(*ret)); sfree(ret); } if (errmsg_p) *errmsg_p = errmsg; + if (fp) fclose(fp); return NULL; } int openssh_encrypted(const Filename *filename) { @@ -492,13 +499,13 @@ int ret; if (!key) return 0; ret = key->encrypted; - memset(key->keyblob, 0, key->keyblob_size); + smemclr(key->keyblob, key->keyblob_size); sfree(key->keyblob); - memset(key, 0, sizeof(*key)); + smemclr(key, sizeof(*key)); sfree(key); return ret; } struct ssh2_userkey *openssh_read(const Filename *filename, char *passphrase, @@ -562,12 +569,12 @@ aes_iv(ctx, (unsigned char *)key->iv); aes_ssh2_decrypt_blk(ctx, key->keyblob, key->keyblob_len); aes_free_context(ctx); } - memset(&md5c, 0, sizeof(md5c)); - memset(keybuf, 0, sizeof(keybuf)); + smemclr(&md5c, sizeof(md5c)); + smemclr(keybuf, sizeof(keybuf)); } /* * Now we have a decrypted key blob, which contains an ASN.1 * encoded private key. We must now untangle the ASN.1. @@ -586,16 +593,17 @@ * order. */ p = key->keyblob; - /* Expect the SEQUENCE header. Take its absence as a failure to decrypt. */ + /* Expect the SEQUENCE header. Take its absence as a failure to + * decrypt, if the key was encrypted. */ ret = ber_read_id_len(p, key->keyblob_len, &id, &len, &flags); p += ret; if (ret < 0 || id != 16) { errmsg = "ASN.1 decoding failure"; - retval = SSH2_WRONG_PASSPHRASE; + retval = key->encrypted ? SSH2_WRONG_PASSPHRASE : NULL; goto error; } /* Expect a load of INTEGERs. */ if (key->type == OSSH_RSA) @@ -623,11 +631,11 @@ &id, &len, &flags); p += ret; if (ret < 0 || id != 2 || key->keyblob+key->keyblob_len-p < len) { errmsg = "ASN.1 decoding failure"; - retval = SSH2_WRONG_PASSPHRASE; + retval = key->encrypted ? SSH2_WRONG_PASSPHRASE : NULL; goto error; } if (i == 0) { /* @@ -696,16 +704,16 @@ errmsg = NULL; /* no error */ retval = retkey; error: if (blob) { - memset(blob, 0, blobsize); + smemclr(blob, blobsize); sfree(blob); } - memset(key->keyblob, 0, key->keyblob_size); + smemclr(key->keyblob, key->keyblob_size); sfree(key->keyblob); - memset(key, 0, sizeof(*key)); + smemclr(key, sizeof(*key)); sfree(key); if (errmsg_p) *errmsg_p = errmsg; return retval; } @@ -738,10 +746,14 @@ if (key->alg == &ssh_rsa) { int pos; struct mpint_pos n, e, d, p, q, iqmp, dmp1, dmq1; Bignum bd, bp, bq, bdmp1, bdmq1; + /* + * These blobs were generated from inside PuTTY, so we needn't + * treat them as untrusted. + */ pos = 4 + GET_32BIT(pubblob); pos += ssh2_read_mpint(pubblob+pos, publen-pos, &e); pos += ssh2_read_mpint(pubblob+pos, publen-pos, &n); pos = 0; pos += ssh2_read_mpint(privblob+pos, privlen-pos, &d); @@ -791,10 +803,14 @@ footer = "-----END RSA PRIVATE KEY-----\n"; } else if (key->alg == &ssh_dss) { int pos; struct mpint_pos p, q, g, y, x; + /* + * These blobs were generated from inside PuTTY, so we needn't + * treat them as untrusted. + */ pos = 4 + GET_32BIT(pubblob); pos += ssh2_read_mpint(pubblob+pos, publen-pos, &p); pos += ssh2_read_mpint(pubblob+pos, publen-pos, &q); pos += ssh2_read_mpint(pubblob+pos, publen-pos, &g); pos += ssh2_read_mpint(pubblob+pos, publen-pos, &y); @@ -909,19 +925,19 @@ /* * Now encrypt the key blob. */ des3_encrypt_pubkey_ossh(keybuf, iv, outblob, outlen); - memset(&md5c, 0, sizeof(md5c)); - memset(keybuf, 0, sizeof(keybuf)); + smemclr(&md5c, sizeof(md5c)); + smemclr(keybuf, sizeof(keybuf)); } /* * And save it. We'll use Unix line endings just in case it's * subsequently transferred in binary mode. */ - fp = f_open(*filename, "wb", TRUE); /* ensure Unix line endings */ + fp = f_open(filename, "wb", TRUE); /* ensure Unix line endings */ if (!fp) goto error; fputs(header, fp); if (passphrase) { fprintf(fp, "Proc-Type: 4,ENCRYPTED\nDEK-Info: DES-EDE3-CBC,"); @@ -934,23 +950,23 @@ fclose(fp); ret = 1; error: if (outblob) { - memset(outblob, 0, outlen); + smemclr(outblob, outlen); sfree(outblob); } if (spareblob) { - memset(spareblob, 0, sparelen); + smemclr(spareblob, sparelen); sfree(spareblob); } if (privblob) { - memset(privblob, 0, privlen); + smemclr(privblob, privlen); sfree(privblob); } if (pubblob) { - memset(pubblob, 0, publen); + smemclr(pubblob, publen); sfree(pubblob); } return ret; } @@ -1051,11 +1067,11 @@ ret = snew(struct sshcom_key); ret->comment[0] = '\0'; ret->keyblob = NULL; ret->keyblob_len = ret->keyblob_size = 0; - fp = f_open(*filename, "r", FALSE); + fp = f_open(filename, "r", FALSE); if (!fp) { errmsg = "unable to open key file"; goto error; } if (!(line = fgetline(fp))) { @@ -1065,11 +1081,11 @@ strip_crlf(line); if (0 != strcmp(line, "---- BEGIN SSH2 ENCRYPTED PRIVATE KEY ----")) { errmsg = "file does not begin with ssh.com key header"; goto error; } - memset(line, 0, strlen(line)); + smemclr(line, strlen(line)); sfree(line); line = NULL; headers_done = 0; while (1) { @@ -1076,12 +1092,15 @@ if (!(line = fgetline(fp))) { errmsg = "unexpected end of file"; goto error; } strip_crlf(line); - if (!strcmp(line, "---- END SSH2 ENCRYPTED PRIVATE KEY ----")) + if (!strcmp(line, "---- END SSH2 ENCRYPTED PRIVATE KEY ----")) { + sfree(line); + line = NULL; break; /* done */ + } if ((p = strchr(line, ':')) != NULL) { if (headers_done) { errmsg = "header found in body of key data"; goto error; } @@ -1110,11 +1129,11 @@ line = sresize(line, len + line2len + 1, char); strcpy(line + len - 1, line2); len += line2len - 1; assert(!line[len]); - memset(line2, 0, strlen(line2)); + smemclr(line2, strlen(line2)); sfree(line2); line2 = NULL; } p = line + hdrstart; strip_crlf(p); @@ -1156,35 +1175,39 @@ } p++; } } - memset(line, 0, strlen(line)); + smemclr(line, strlen(line)); sfree(line); line = NULL; } if (ret->keyblob_len == 0 || !ret->keyblob) { errmsg = "key body not present"; goto error; } + fclose(fp); if (errmsg_p) *errmsg_p = NULL; return ret; error: + if (fp) + fclose(fp); + if (line) { - memset(line, 0, strlen(line)); + smemclr(line, strlen(line)); sfree(line); line = NULL; } if (ret) { if (ret->keyblob) { - memset(ret->keyblob, 0, ret->keyblob_size); + smemclr(ret->keyblob, ret->keyblob_size); sfree(ret->keyblob); } - memset(ret, 0, sizeof(*ret)); + smemclr(ret, sizeof(*ret)); sfree(ret); } if (errmsg_p) *errmsg_p = errmsg; return NULL; } @@ -1191,50 +1214,56 @@ int sshcom_encrypted(const Filename *filename, char **comment) { struct sshcom_key *key = load_sshcom_key(filename, NULL); int pos, len, answer; + + answer = 0; *comment = NULL; if (!key) - return 0; + goto done; /* * Check magic number. */ - if (GET_32BIT(key->keyblob) != 0x3f6ff9eb) - return 0; /* key is invalid */ + if (GET_32BIT(key->keyblob) != 0x3f6ff9eb) { + goto done; /* key is invalid */ + } /* * Find the cipher-type string. */ - answer = 0; pos = 8; if (key->keyblob_len < pos+4) goto done; /* key is far too short */ - pos += 4 + GET_32BIT(key->keyblob + pos); /* skip key type */ - if (key->keyblob_len < pos+4) + len = toint(GET_32BIT(key->keyblob + pos)); + if (len < 0 || len > key->keyblob_len - pos - 4) goto done; /* key is far too short */ - len = GET_32BIT(key->keyblob + pos); /* find cipher-type length */ - if (key->keyblob_len < pos+4+len) + pos += 4 + len; /* skip key type */ + len = toint(GET_32BIT(key->keyblob + pos)); /* find cipher-type length */ + if (len < 0 || len > key->keyblob_len - pos - 4) goto done; /* cipher type string is incomplete */ if (len != 4 || 0 != memcmp(key->keyblob + pos + 4, "none", 4)) answer = 1; done: - *comment = dupstr(key->comment); - memset(key->keyblob, 0, key->keyblob_size); - sfree(key->keyblob); - memset(key, 0, sizeof(*key)); - sfree(key); + if (key) { + *comment = dupstr(key->comment); + smemclr(key->keyblob, key->keyblob_size); + sfree(key->keyblob); + smemclr(key, sizeof(*key)); + sfree(key); + } else { + *comment = dupstr(""); + } return answer; } static int sshcom_read_mpint(void *data, int len, struct mpint_pos *ret) { - int bits; - int bytes; + unsigned bits, bytes; unsigned char *d = (unsigned char *) data; if (len < 4) goto error; bits = GET_32BIT(d); @@ -1302,11 +1331,12 @@ /* * Determine the key type. */ pos = 8; if (key->keyblob_len < pos+4 || - (len = GET_32BIT(key->keyblob + pos)) > key->keyblob_len - pos - 4) { + (len = toint(GET_32BIT(key->keyblob + pos))) < 0 || + len > key->keyblob_len - pos - 4) { errmsg = "key blob does not contain a key type string"; goto error; } if (len > sizeof(prefix_rsa) - 1 && !memcmp(key->keyblob+pos+4, prefix_rsa, sizeof(prefix_rsa) - 1)) { @@ -1322,11 +1352,12 @@ /* * Determine the cipher type. */ if (key->keyblob_len < pos+4 || - (len = GET_32BIT(key->keyblob + pos)) > key->keyblob_len - pos - 4) { + (len = toint(GET_32BIT(key->keyblob + pos))) < 0 || + len > key->keyblob_len - pos - 4) { errmsg = "key blob does not contain a cipher type string"; goto error; } if (len == 4 && !memcmp(key->keyblob+pos+4, "none", 4)) encrypted = 0; @@ -1340,11 +1371,12 @@ /* * Get hold of the encrypted part of the key. */ if (key->keyblob_len < pos+4 || - (len = GET_32BIT(key->keyblob + pos)) > key->keyblob_len - pos - 4) { + (len = toint(GET_32BIT(key->keyblob + pos))) < 0 || + len > key->keyblob_len - pos - 4) { errmsg = "key blob does not contain actual key data"; goto error; } ciphertext = (char *)key->keyblob + pos + 4; cipherlen = len; @@ -1388,12 +1420,12 @@ */ memset(iv, 0, sizeof(iv)); des3_decrypt_pubkey_ossh(keybuf, iv, (unsigned char *)ciphertext, cipherlen); - memset(&md5c, 0, sizeof(md5c)); - memset(keybuf, 0, sizeof(keybuf)); + smemclr(&md5c, sizeof(md5c)); + smemclr(keybuf, sizeof(keybuf)); /* * Hereafter we return WRONG_PASSPHRASE for any parsing * error. (But only if we've just tried to decrypt it! * Returning WRONG_PASSPHRASE for an unencrypted key is @@ -1404,11 +1436,11 @@ } /* * Strip away the containing string to get to the real meat. */ - len = GET_32BIT(ciphertext); + len = toint(GET_32BIT(ciphertext)); if (len < 0 || len > cipherlen-4) { errmsg = "containing string was ill-formed"; goto error; } ciphertext += 4; @@ -1445,13 +1477,16 @@ pos += put_string(blob+pos, d.start, d.bytes); pos += put_mp(blob+pos, q.start, q.bytes); pos += put_mp(blob+pos, p.start, p.bytes); pos += put_mp(blob+pos, u.start, u.bytes); privlen = pos - publen; - } else if (type == DSA) { + } else { struct mpint_pos p, q, g, x, y; int pos = 4; + + assert(type == DSA); /* the only other option from the if above */ + if (GET_32BIT(ciphertext) != 0) { errmsg = "predefined DSA parameters not supported"; goto error; } pos += sshcom_read_mpint(ciphertext+pos, cipherlen-pos, &p); @@ -1472,12 +1507,11 @@ pos += put_mp(blob+pos, g.start, g.bytes); pos += put_mp(blob+pos, y.start, y.bytes); publen = pos; pos += put_mp(blob+pos, x.start, x.bytes); privlen = pos - publen; - } else - return NULL; + } assert(privlen > 0); /* should have bombed by now if not */ retkey = snew(struct ssh2_userkey); retkey->alg = alg; @@ -1492,16 +1526,16 @@ errmsg = NULL; /* no error */ ret = retkey; error: if (blob) { - memset(blob, 0, blobsize); + smemclr(blob, blobsize); sfree(blob); } - memset(key->keyblob, 0, key->keyblob_size); + smemclr(key->keyblob, key->keyblob_size); sfree(key->keyblob); - memset(key, 0, sizeof(*key)); + smemclr(key, sizeof(*key)); sfree(key); if (errmsg_p) *errmsg_p = errmsg; return ret; } @@ -1533,10 +1567,14 @@ */ if (key->alg == &ssh_rsa) { int pos; struct mpint_pos n, e, d, p, q, iqmp; + /* + * These blobs were generated from inside PuTTY, so we needn't + * treat them as untrusted. + */ pos = 4 + GET_32BIT(pubblob); pos += ssh2_read_mpint(pubblob+pos, publen-pos, &e); pos += ssh2_read_mpint(pubblob+pos, publen-pos, &n); pos = 0; pos += ssh2_read_mpint(privblob+pos, privlen-pos, &d); @@ -1558,10 +1596,14 @@ type = "if-modn{sign{rsa-pkcs1-sha1},encrypt{rsa-pkcs1v2-oaep}}"; } else if (key->alg == &ssh_dss) { int pos; struct mpint_pos p, q, g, y, x; + /* + * These blobs were generated from inside PuTTY, so we needn't + * treat them as untrusted. + */ pos = 4 + GET_32BIT(pubblob); pos += ssh2_read_mpint(pubblob+pos, publen-pos, &p); pos += ssh2_read_mpint(pubblob+pos, publen-pos, &q); pos += ssh2_read_mpint(pubblob+pos, publen-pos, &g); pos += ssh2_read_mpint(pubblob+pos, publen-pos, &y); @@ -1662,19 +1704,19 @@ */ memset(iv, 0, sizeof(iv)); des3_encrypt_pubkey_ossh(keybuf, iv, (unsigned char *)ciphertext, cipherlen); - memset(&md5c, 0, sizeof(md5c)); - memset(keybuf, 0, sizeof(keybuf)); + smemclr(&md5c, sizeof(md5c)); + smemclr(keybuf, sizeof(keybuf)); } /* * And save it. We'll use Unix line endings just in case it's * subsequently transferred in binary mode. */ - fp = f_open(*filename, "wb", TRUE); /* ensure Unix line endings */ + fp = f_open(filename, "wb", TRUE); /* ensure Unix line endings */ if (!fp) goto error; fputs("---- BEGIN SSH2 ENCRYPTED PRIVATE KEY ----\n", fp); fprintf(fp, "Comment: \""); /* @@ -1698,18 +1740,18 @@ fclose(fp); ret = 1; error: if (outblob) { - memset(outblob, 0, outlen); + smemclr(outblob, outlen); sfree(outblob); } if (privblob) { - memset(privblob, 0, privlen); + smemclr(privblob, privlen); sfree(privblob); } if (pubblob) { - memset(pubblob, 0, publen); + smemclr(pubblob, publen); sfree(pubblob); } return ret; } Index: ldisc.c ================================================================== --- ldisc.c +++ ldisc.c @@ -10,16 +10,16 @@ #include "putty.h" #include "terminal.h" #include "ldisc.h" -#define ECHOING (ldisc->cfg->localecho == FORCE_ON || \ - (ldisc->cfg->localecho == AUTO && \ +#define ECHOING (ldisc->localecho == FORCE_ON || \ + (ldisc->localecho == AUTO && \ (ldisc->back->ldisc(ldisc->backhandle, LD_ECHO) || \ term_ldisc(ldisc->term, LD_ECHO)))) -#define EDITING (ldisc->cfg->localedit == FORCE_ON || \ - (ldisc->cfg->localedit == AUTO && \ +#define EDITING (ldisc->localedit == FORCE_ON || \ + (ldisc->localedit == AUTO && \ (ldisc->back->ldisc(ldisc->backhandle, LD_EDIT) || \ term_ldisc(ldisc->term, LD_EDIT)))) static void c_write(Ldisc ldisc, char *buf, int len) { @@ -74,11 +74,11 @@ } #define CTRL(x) (x^'@') #define KCTRL(x) ((x^'@') | 0x100) -void *ldisc_create(Config *mycfg, Terminal *term, +void *ldisc_create(Conf *conf, Terminal *term, Backend *back, void *backhandle, void *frontend) { Ldisc ldisc = snew(struct ldisc_tag); @@ -85,24 +85,36 @@ ldisc->buf = NULL; ldisc->buflen = 0; ldisc->bufsiz = 0; ldisc->quotenext = 0; - ldisc->cfg = mycfg; ldisc->back = back; ldisc->backhandle = backhandle; ldisc->term = term; ldisc->frontend = frontend; + + ldisc_configure(ldisc, conf); /* Link ourselves into the backend and the terminal */ if (term) term->ldisc = ldisc; if (back) back->provide_ldisc(backhandle, ldisc); return ldisc; } + +void ldisc_configure(void *handle, Conf *conf) +{ + Ldisc ldisc = (Ldisc) handle; + + ldisc->telnet_keyboard = conf_get_int(conf, CONF_telnet_keyboard); + ldisc->telnet_newline = conf_get_int(conf, CONF_telnet_newline); + ldisc->protocol = conf_get_int(conf, CONF_protocol); + ldisc->localecho = conf_get_int(conf, CONF_localecho); + ldisc->localedit = conf_get_int(conf, CONF_localedit); +} void ldisc_free(void *handle) { Ldisc ldisc = (Ldisc) handle; @@ -131,10 +143,22 @@ * Notify the front end that something was pressed, in case * it's depending on finding out (e.g. keypress termination for * Close On Exit). */ frontend_keypress(ldisc->frontend); + + if (interactive && ldisc->term) { + /* + * Interrupt a paste from the clipboard, if one was in + * progress when the user pressed a key. This is easier than + * buffering the current piece of data and saving it until the + * terminal has finished pasting, and has the potential side + * benefit of permitting a user to cancel an accidental huge + * paste. + */ + term_nopaste(ldisc->term); + } /* * Less than zero means null terminated special string. */ if (len < 0) { @@ -201,11 +225,11 @@ /* * We don't send IP, SUSP or ABORT if the user has * configured telnet specials off! This breaks * talkers otherwise. */ - if (!ldisc->cfg->telnet_keyboard) + if (!ldisc->telnet_keyboard) goto default_case; if (c == CTRL('C')) ldisc->back->special(ldisc->backhandle, TS_IP); if (c == CTRL('Z')) ldisc->back->special(ldisc->backhandle, TS_SUSP); @@ -253,22 +277,22 @@ * entertaining ways, and _doesn't_ fall out of * the bottom of the if and through to the * default clause because of the break. */ case CTRL('J'): - if (ldisc->cfg->protocol == PROT_RAW && + if (ldisc->protocol == PROT_RAW && ldisc->buflen > 0 && ldisc->buf[ldisc->buflen - 1] == '\r') { if (ECHOING) bsb(ldisc, plen(ldisc, ldisc->buf[ldisc->buflen - 1])); ldisc->buflen--; /* FALLTHROUGH */ case KCTRL('M'): /* send with newline */ if (ldisc->buflen > 0) ldisc->back->send(ldisc->backhandle, ldisc->buf, ldisc->buflen); - if (ldisc->cfg->protocol == PROT_RAW) + if (ldisc->protocol == PROT_RAW) ldisc->back->send(ldisc->backhandle, "\r\n", 2); - else if (ldisc->cfg->protocol == PROT_TELNET && ldisc->cfg->telnet_newline) + else if (ldisc->protocol == PROT_TELNET && ldisc->telnet_newline) ldisc->back->special(ldisc->backhandle, TS_EOL); else ldisc->back->send(ldisc->backhandle, "\r", 1); if (ECHOING) c_write(ldisc, "\r\n", 2); @@ -298,31 +322,31 @@ } } if (len > 0) { if (ECHOING) c_write(ldisc, buf, len); - if (keyflag && ldisc->cfg->protocol == PROT_TELNET && len == 1) { + if (keyflag && ldisc->protocol == PROT_TELNET && len == 1) { switch (buf[0]) { case CTRL('M'): - if (ldisc->cfg->protocol == PROT_TELNET && ldisc->cfg->telnet_newline) + if (ldisc->protocol == PROT_TELNET && ldisc->telnet_newline) ldisc->back->special(ldisc->backhandle, TS_EOL); else ldisc->back->send(ldisc->backhandle, "\r", 1); break; case CTRL('?'): case CTRL('H'): - if (ldisc->cfg->telnet_keyboard) { + if (ldisc->telnet_keyboard) { ldisc->back->special(ldisc->backhandle, TS_EC); break; } case CTRL('C'): - if (ldisc->cfg->telnet_keyboard) { + if (ldisc->telnet_keyboard) { ldisc->back->special(ldisc->backhandle, TS_IP); break; } case CTRL('Z'): - if (ldisc->cfg->telnet_keyboard) { + if (ldisc->telnet_keyboard) { ldisc->back->special(ldisc->backhandle, TS_SUSP); break; } default: Index: ldisc.h ================================================================== --- ldisc.h +++ ldisc.h @@ -9,14 +9,18 @@ #define PUTTY_LDISC_H typedef struct ldisc_tag { Terminal *term; Backend *back; - Config *cfg; void *backhandle; void *frontend; + /* + * Values cached out of conf. + */ + int telnet_keyboard, telnet_newline, protocol, localecho, localedit; + char *buf; int buflen, bufsiz, quotenext; } *Ldisc; #endif /* PUTTY_LDISC_H */ Index: ldiscucs.c ================================================================== --- ldiscucs.c +++ ldiscucs.c @@ -49,17 +49,16 @@ if (in_utf(ldisc->term)) { /* UTF is a simple algorithm */ for (p = linebuffer, i = 0; i < len; i++) { unsigned long ch = widebuf[i]; - if ((ch & 0xF800) == 0xD800) { + if (IS_SURROGATE(ch)) { #ifdef PLATFORM_IS_UTF16 if (i+1 < len) { unsigned long ch2 = widebuf[i+1]; - if ((ch & 0xFC00) == 0xD800 && - (ch2 & 0xFC00) == 0xDC00) { - ch = 0x10000 + ((ch & 0x3FF) << 10) + (ch2 & 0x3FF); + if (IS_SURROGATE_PAIR(ch, ch2)) { + ch = FROM_SURROGATES(ch, ch2); i++; } } else #endif { Index: logging.c ================================================================== --- logging.c +++ logging.c @@ -14,16 +14,17 @@ /* log session to file stuff ... */ struct LogContext { FILE *lgfp; enum { L_CLOSED, L_OPENING, L_OPEN, L_ERROR } state; bufchain queue; - Filename currlogfilename; + Filename *currlogfilename; void *frontend; - Config cfg; + Conf *conf; + int logtype; /* cached out of conf */ }; -static void xlatlognam(Filename *d, Filename s, char *hostname, struct tm *tm); +static Filename *xlatlognam(Filename *s, char *hostname, struct tm *tm); /* * Internal wrapper function which must be called for _all_ output * to the log file. It takes care of opening the log file if it * isn't open, buffering data if it's in the process of being @@ -73,11 +74,11 @@ /* * Flush any open log file. */ void logflush(void *handle) { struct LogContext *ctx = (struct LogContext *)handle; - if (ctx->cfg.logtype > 0) + if (ctx->logtype > 0) if (ctx->state == L_OPEN) fflush(ctx->lgfp); } static void logfopen_callback(void *handle, int mode) @@ -108,16 +109,16 @@ event = dupprintf("%s session log (%s mode) to file: %s", ctx->state == L_ERROR ? (mode == 0 ? "Disabled writing" : "Error writing") : (mode == 1 ? "Appending" : "Writing new"), - (ctx->cfg.logtype == LGTYP_ASCII ? "ASCII" : - ctx->cfg.logtype == LGTYP_DEBUG ? "raw" : - ctx->cfg.logtype == LGTYP_PACKETS ? "SSH packets" : - ctx->cfg.logtype == LGTYP_SSHRAW ? "SSH raw data" : + (ctx->logtype == LGTYP_ASCII ? "ASCII" : + ctx->logtype == LGTYP_DEBUG ? "raw" : + ctx->logtype == LGTYP_PACKETS ? "SSH packets" : + ctx->logtype == LGTYP_SSHRAW ? "SSH raw data" : "unknown"), - filename_to_str(&ctx->currlogfilename)); + filename_to_str(ctx->currlogfilename)); logevent(ctx->frontend, event); sfree(event); /* * Having either succeeded or failed in opening the log file, @@ -146,23 +147,28 @@ /* Prevent repeat calls */ if (ctx->state != L_CLOSED) return; - if (!ctx->cfg.logtype) + if (!ctx->logtype) return; tm = ltime(); /* substitute special codes in file name */ - xlatlognam(&ctx->currlogfilename, ctx->cfg.logfilename,ctx->cfg.host, &tm); + if (ctx->currlogfilename) + filename_free(ctx->currlogfilename); + ctx->currlogfilename = + xlatlognam(conf_get_filename(ctx->conf, CONF_logfilename), + conf_get_str(ctx->conf, CONF_host), &tm); ctx->lgfp = f_open(ctx->currlogfilename, "r", FALSE); /* file already present? */ if (ctx->lgfp) { + int logxfovr = conf_get_int(ctx->conf, CONF_logxfovr); fclose(ctx->lgfp); - if (ctx->cfg.logxfovr != LGXF_ASK) { - mode = ((ctx->cfg.logxfovr == LGXF_OVR) ? 2 : 1); + if (logxfovr != LGXF_ASK) { + mode = ((logxfovr == LGXF_OVR) ? 2 : 1); } else mode = askappend(ctx->frontend, ctx->currlogfilename, logfopen_callback, ctx); } else mode = 2; /* create == overwrite */ @@ -187,12 +193,12 @@ * Log session traffic. */ void logtraffic(void *handle, unsigned char c, int logmode) { struct LogContext *ctx = (struct LogContext *)handle; - if (ctx->cfg.logtype > 0) { - if (ctx->cfg.logtype == logmode) + if (ctx->logtype > 0) { + if (ctx->logtype == logmode) logwrite(ctx, &c, 1); } } /* @@ -212,12 +218,12 @@ fflush(stderr); } /* If we don't have a context yet (eg winnet.c init) then skip entirely */ if (!ctx) return; - if (ctx->cfg.logtype != LGTYP_PACKETS && - ctx->cfg.logtype != LGTYP_SSHRAW) + if (ctx->logtype != LGTYP_PACKETS && + ctx->logtype != LGTYP_SSHRAW) return; logprintf(ctx, "Event Log: %s\r\n", event); logflush(ctx); } @@ -227,35 +233,55 @@ * Set of blanking areas must be in increasing order. */ void log_packet(void *handle, int direction, int type, char *texttype, const void *data, int len, int n_blanks, const struct logblank_t *blanks, - const unsigned long *seq) + const unsigned long *seq, + unsigned downstream_id, const char *additional_log_text) { struct LogContext *ctx = (struct LogContext *)handle; char dumpdata[80], smalldata[5]; int p = 0, b = 0, omitted = 0; int output_pos = 0; /* NZ if pending output in dumpdata */ - if (!(ctx->cfg.logtype == LGTYP_SSHRAW || - (ctx->cfg.logtype == LGTYP_PACKETS && texttype))) + if (!(ctx->logtype == LGTYP_SSHRAW || + (ctx->logtype == LGTYP_PACKETS && texttype))) return; /* Packet header. */ if (texttype) { - if (seq) { - logprintf(ctx, "%s packet #0x%lx, type %d / 0x%02x (%s)\r\n", - direction == PKT_INCOMING ? "Incoming" : "Outgoing", - *seq, type, type, texttype); - } else { - logprintf(ctx, "%s packet type %d / 0x%02x (%s)\r\n", - direction == PKT_INCOMING ? "Incoming" : "Outgoing", - type, type, texttype); - } - } else { - logprintf(ctx, "%s raw data\r\n", - direction == PKT_INCOMING ? "Incoming" : "Outgoing"); + logprintf(ctx, "%s packet ", + direction == PKT_INCOMING ? "Incoming" : "Outgoing"); + + if (seq) + logprintf(ctx, "#0x%lx, ", *seq); + + logprintf(ctx, "type %d / 0x%02x (%s)", type, type, texttype); + + if (downstream_id) { + logprintf(ctx, " on behalf of downstream #%u", downstream_id); + if (additional_log_text) + logprintf(ctx, " (%s)", additional_log_text); + } + + logprintf(ctx, "\r\n"); + } else { + /* + * Raw data is logged with a timestamp, so that it's possible + * to determine whether a mysterious delay occurred at the + * client or server end. (Timestamping the raw data avoids + * cluttering the normal case of only logging decrypted SSH + * messages, and also adds conceptual rigour in the case where + * an SSH message arrives in several pieces.) + */ + char buf[256]; + struct tm tm; + tm = ltime(); + strftime(buf, 24, "%Y-%m-%d %H:%M:%S", &tm); + logprintf(ctx, "%s raw data at %s\r\n", + direction == PKT_INCOMING ? "Incoming" : "Outgoing", + buf); } /* * Output a hex/ASCII dump of the packet body, blanking/omitting * parts as specified. @@ -324,17 +350,19 @@ logprintf(ctx, " (%d byte%s omitted)\r\n", omitted, (omitted==1?"":"s")); logflush(ctx); } -void *log_init(void *frontend, Config *cfg) +void *log_init(void *frontend, Conf *conf) { struct LogContext *ctx = snew(struct LogContext); ctx->lgfp = NULL; ctx->state = L_CLOSED; ctx->frontend = frontend; - ctx->cfg = *cfg; /* STRUCTURE COPY */ + ctx->conf = conf_copy(conf); + ctx->logtype = conf_get_int(ctx->conf, CONF_logtype); + ctx->currlogfilename = NULL; bufchain_init(&ctx->queue); return ctx; } void log_free(void *handle) @@ -341,28 +369,35 @@ { struct LogContext *ctx = (struct LogContext *)handle; logfclose(ctx); bufchain_clear(&ctx->queue); + if (ctx->currlogfilename) + filename_free(ctx->currlogfilename); sfree(ctx); } -void log_reconfig(void *handle, Config *cfg) +void log_reconfig(void *handle, Conf *conf) { struct LogContext *ctx = (struct LogContext *)handle; int reset_logging; - if (!filename_equal(ctx->cfg.logfilename, cfg->logfilename) || - ctx->cfg.logtype != cfg->logtype) + if (!filename_equal(conf_get_filename(ctx->conf, CONF_logfilename), + conf_get_filename(conf, CONF_logfilename)) || + conf_get_int(ctx->conf, CONF_logtype) != + conf_get_int(conf, CONF_logtype)) reset_logging = TRUE; else reset_logging = FALSE; if (reset_logging) logfclose(ctx); - ctx->cfg = *cfg; /* STRUCTURE COPY */ + conf_free(ctx->conf); + ctx->conf = conf_copy(conf); + + ctx->logtype = conf_get_int(ctx->conf, CONF_logtype); if (reset_logging) logfopen(ctx); } @@ -370,21 +405,23 @@ * translate format codes into time/date strings * and insert them into log file name * * "&Y":YYYY "&m":MM "&d":DD "&T":hhmmss "&h": "&&":& */ -static void xlatlognam(Filename *dest, Filename src, - char *hostname, struct tm *tm) { +static Filename *xlatlognam(Filename *src, char *hostname, struct tm *tm) +{ char buf[10], *bufp; int size; - char buffer[FILENAME_MAX]; - int len = sizeof(buffer)-1; - char *d; + char *buffer; + int buflen, bufsize; const char *s; + Filename *ret; - d = buffer; - s = filename_to_str(&src); + bufsize = FILENAME_MAX; + buffer = snewn(bufsize, char); + buflen = 0; + s = filename_to_str(src); while (*s) { /* Let (bufp, len) be the string to append. */ bufp = buf; /* don't usually override this */ if (*s == '&') { @@ -416,15 +453,18 @@ } } else { buf[0] = *s++; size = 1; } - if (size > len) - size = len; - memcpy(d, bufp, size); - d += size; - len -= size; - } - *d = '\0'; - - *dest = filename_from_str(buffer); + if (bufsize <= buflen + size) { + bufsize = (buflen + size) * 5 / 4 + 512; + buffer = sresize(buffer, bufsize, char); + } + memcpy(buffer + buflen, bufp, size); + buflen += size; + } + buffer[buflen] = '\0'; + + ret = filename_from_str(buffer); + sfree(buffer); + return ret; } Index: macosx/README.OSX ================================================================== --- macosx/README.OSX +++ macosx/README.OSX @@ -18,10 +18,19 @@ for the data and therefore loses them all. If that happens, don't say you weren't warned! Other ways in which the port is currently unfinished include: +Bit rot +------- + + - the conversion of the old fixed-size 'Config' structure to the + new dynamic 'Conf' was never applied to this directory + + - probably other things are out of date too; it would need some + work to make it compile again + Missing terminal window features -------------------------------- - terminal display is horribly slow Index: macosx/osxmain.m ================================================================== --- macosx/osxmain.m +++ macosx/osxmain.m @@ -82,10 +82,28 @@ [alert setInformativeText:[NSString stringWithCString:errorbuf]]; [alert runModal]; } exit(1); } + +void nonfatal(void *frontend, char *p, ...) +{ + char *errorbuf; + NSAlert *alert; + va_list ap; + + va_start(ap, p); + errorbuf = dupvprintf(p, ap); + va_end(ap); + + alert = [[[NSAlert alloc] init] autorelease]; + [alert addButtonWithTitle:@"Error"]; + [alert setInformativeText:[NSString stringWithCString:errorbuf]]; + [alert runModal]; + + sfree(errorbuf); +} void fatalbox(char *p, ...) { va_list ap; va_start(ap, p); Index: macosx/osxwin.m ================================================================== --- macosx/osxwin.m +++ macosx/osxwin.m @@ -107,15 +107,15 @@ if (attr & ATTR_REVERSE) { int t = nfg; nfg = nbg; nbg = t; } - if (cfg.bold_colour && (attr & ATTR_BOLD)) { + if ((cfg.bold_style & 2) && (attr & ATTR_BOLD)) { if (nfg < 16) nfg |= 8; else if (nfg >= 256) nfg |= 1; } - if (cfg.bold_colour && (attr & ATTR_BLINK)) { + if ((cfg.bold_style & 2) && (attr & ATTR_BLINK)) { if (nbg < 16) nbg |= 8; else if (nbg >= 256) nbg |= 1; } if (attr & TATTR_ACTCURS) { nfg = 260; @@ -127,11 +127,11 @@ /* FIXME: what do we actually have to do about wide characters? */ } else { widefactor = 1; } - /* FIXME: ATTR_BOLD without cfg.bold_colour */ + /* FIXME: ATTR_BOLD if cfg.bold_style & 1 */ if ((lattr & LATTR_MODE) != LATTR_NORM) { x *= 2; if (x >= term->cols) return; @@ -955,11 +955,11 @@ { SessionWindow *win = (SessionWindow *)frontend; if (n >= 16) n += 256 - 16; - if (n > NALLCOLOURS) + if (n >= NALLCOLOURS) return; [win setColour:n r:r/255.0 g:g/255.0 b:b/255.0]; /* * FIXME: do we need an OS X equivalent of set_window_background? Index: minibidi.c ================================================================== --- minibidi.c +++ minibidi.c @@ -1,7 +1,7 @@ /************************************************************************ - * $Id: minibidi.c 9169 2011-05-07 10:57:19Z simon $ + * $Id: minibidi.c 9426 2012-03-05 18:34:40Z simon $ * * ------------ * Description: * ------------ * This is an implemention of Unicode's Bidirectional Algorithm @@ -12,13 +12,13 @@ * Author: Ahmad Khalifa * * ----------------- * Revision Details: (Updated by Revision Control System) * ----------------- - * $Date: 2011-05-07 05:57:19 -0500 (Sat, 07 May 2011) $ + * $Date: 2012-03-05 12:34:40 -0600 (Mon, 05 Mar 2012) $ * $Author: simon $ - * $Revision: 9169 $ + * $Revision: 9426 $ * * (www.arabeyes.org - under MIT license) * ************************************************************************/ @@ -56,11 +56,11 @@ #define leastGreaterOdd(x) ( ((x)+1) | 1 ) #define leastGreaterEven(x) ( ((x)+2) &~ 1 ) typedef struct bidi_char { - wchar_t origwc, wc; + unsigned int origwc, wc; unsigned short index; } bidi_char; /* function declarations */ void flipThisRun(bidi_char *from, unsigned char* level, int max, int count); @@ -68,11 +68,11 @@ unsigned char getType(int ch); unsigned char setOverrideBits(unsigned char level, unsigned char override); int getPreviousLevel(unsigned char* level, int from); int do_shape(bidi_char *line, bidi_char *to, int count); int do_bidi(bidi_char *line, int count); -void doMirror(wchar_t* ch); +void doMirror(unsigned int *ch); /* character types */ enum { L, LRE, @@ -1634,11 +1634,11 @@ /* * Bad, Horrible function * takes a pointer to a character that is checked for * having a mirror glyph. */ -void doMirror(wchar_t* ch) +void doMirror(unsigned int *ch) { if ((*ch & 0xFF00) == 0) { switch (*ch) { case 0x0028: *ch = 0x0029; break; case 0x0029: *ch = 0x0028; break; Index: misc.c ================================================================== --- misc.c +++ misc.c @@ -97,28 +97,52 @@ p->to_server = TRUE; /* to be on the safe side */ p->name = p->instruction = NULL; p->name_reqd = p->instr_reqd = FALSE; return p; } -void add_prompt(prompts_t *p, char *promptstr, int echo, size_t len) +void add_prompt(prompts_t *p, char *promptstr, int echo) { prompt_t *pr = snew(prompt_t); - char *result = snewn(len, char); pr->prompt = promptstr; pr->echo = echo; - pr->result = result; - pr->result_len = len; + pr->result = NULL; + pr->resultsize = 0; p->n_prompts++; p->prompts = sresize(p->prompts, p->n_prompts, prompt_t *); p->prompts[p->n_prompts-1] = pr; +} +void prompt_ensure_result_size(prompt_t *pr, int newlen) +{ + if ((int)pr->resultsize < newlen) { + char *newbuf; + newlen = newlen * 5 / 4 + 512; /* avoid too many small allocs */ + + /* + * We don't use sresize / realloc here, because we will be + * storing sensitive stuff like passwords in here, and we want + * to make sure that the data doesn't get copied around in + * memory without the old copy being destroyed. + */ + newbuf = snewn(newlen, char); + memcpy(newbuf, pr->result, pr->resultsize); + smemclr(pr->result, pr->resultsize); + sfree(pr->result); + pr->result = newbuf; + pr->resultsize = newlen; + } +} +void prompt_set_result(prompt_t *pr, const char *newstr) +{ + prompt_ensure_result_size(pr, strlen(newstr) + 1); + strcpy(pr->result, newstr); } void free_prompts(prompts_t *p) { size_t i; for (i=0; i < p->n_prompts; i++) { prompt_t *pr = p->prompts[i]; - memset(pr->result, 0, pr->result_len); /* burn the evidence */ + smemclr(pr->result, pr->resultsize); /* burn the evidence */ sfree(pr->result); sfree(pr->prompt); sfree(pr); } sfree(p->prompts); @@ -173,10 +197,41 @@ } va_end(ap); return p; } + +void burnstr(char *string) /* sfree(str), only clear it first */ +{ + if (string) { + smemclr(string, strlen(string)); + sfree(string); + } +} + +int toint(unsigned u) +{ + /* + * Convert an unsigned to an int, without running into the + * undefined behaviour which happens by the strict C standard if + * the value overflows. You'd hope that sensible compilers would + * do the sensible thing in response to a cast, but actually I + * don't trust modern compilers not to do silly things like + * assuming that _obviously_ you wouldn't have caused an overflow + * and so they can elide an 'if (i < 0)' test immediately after + * the cast. + * + * Sensible compilers ought of course to optimise this entire + * function into 'just return the input value'! + */ + if (u <= (unsigned)INT_MAX) + return (int)u; + else if (u >= (unsigned)INT_MIN) /* wrap in cast _to_ unsigned is OK */ + return INT_MIN + (int)(u - (unsigned)INT_MIN); + else + return INT_MIN; /* fallback; should never occur on binary machines */ +} /* * Do an sprintf(), but into a custom-allocated buffer. * * Currently I'm doing this via vsnprintf. This has worked so far, @@ -330,16 +385,15 @@ * call * - retrieve a larger amount of initial data from the list * - return the current size of the buffer chain in bytes */ -#define BUFFER_GRANULE 512 +#define BUFFER_MIN_GRANULE 512 struct bufchain_granule { struct bufchain_granule *next; - int buflen, bufpos; - char buf[BUFFER_GRANULE]; + char *bufpos, *bufend, *bufmax; }; void bufchain_init(bufchain *ch) { ch->head = ch->tail = NULL; @@ -369,32 +423,33 @@ if (len == 0) return; ch->buffersize += len; - if (ch->tail && ch->tail->buflen < BUFFER_GRANULE) { - int copylen = min(len, BUFFER_GRANULE - ch->tail->buflen); - memcpy(ch->tail->buf + ch->tail->buflen, buf, copylen); - buf += copylen; - len -= copylen; - ch->tail->buflen += copylen; - } while (len > 0) { - int grainlen = min(len, BUFFER_GRANULE); - struct bufchain_granule *newbuf; - newbuf = snew(struct bufchain_granule); - newbuf->bufpos = 0; - newbuf->buflen = grainlen; - memcpy(newbuf->buf, buf, grainlen); - buf += grainlen; - len -= grainlen; - if (ch->tail) - ch->tail->next = newbuf; - else - ch->head = ch->tail = newbuf; - newbuf->next = NULL; - ch->tail = newbuf; + if (ch->tail && ch->tail->bufend < ch->tail->bufmax) { + int copylen = min(len, ch->tail->bufmax - ch->tail->bufend); + memcpy(ch->tail->bufend, buf, copylen); + buf += copylen; + len -= copylen; + ch->tail->bufend += copylen; + } + if (len > 0) { + int grainlen = + max(sizeof(struct bufchain_granule) + len, BUFFER_MIN_GRANULE); + struct bufchain_granule *newbuf; + newbuf = smalloc(grainlen); + newbuf->bufpos = newbuf->bufend = + (char *)newbuf + sizeof(struct bufchain_granule); + newbuf->bufmax = (char *)newbuf + grainlen; + newbuf->next = NULL; + if (ch->tail) + ch->tail->next = newbuf; + else + ch->head = newbuf; + ch->tail = newbuf; + } } } void bufchain_consume(bufchain *ch, int len) { @@ -402,28 +457,28 @@ assert(ch->buffersize >= len); while (len > 0) { int remlen = len; assert(ch->head != NULL); - if (remlen >= ch->head->buflen - ch->head->bufpos) { - remlen = ch->head->buflen - ch->head->bufpos; + if (remlen >= ch->head->bufend - ch->head->bufpos) { + remlen = ch->head->bufend - ch->head->bufpos; tmp = ch->head; ch->head = tmp->next; - sfree(tmp); if (!ch->head) ch->tail = NULL; + sfree(tmp); } else ch->head->bufpos += remlen; ch->buffersize -= remlen; len -= remlen; } } void bufchain_prefix(bufchain *ch, void **data, int *len) { - *len = ch->head->buflen - ch->head->bufpos; - *data = ch->head->buf + ch->head->bufpos; + *len = ch->head->bufend - ch->head->bufpos; + *data = ch->head->bufpos; } void bufchain_fetch(bufchain *ch, void *data, int len) { struct bufchain_granule *tmp; @@ -434,13 +489,13 @@ assert(ch->buffersize >= len); while (len > 0) { int remlen = len; assert(tmp != NULL); - if (remlen >= tmp->buflen - tmp->bufpos) - remlen = tmp->buflen - tmp->bufpos; - memcpy(data_c, tmp->buf + tmp->bufpos, remlen); + if (remlen >= tmp->bufend - tmp->bufpos) + remlen = tmp->bufend - tmp->bufpos; + memcpy(data_c, tmp->bufpos, remlen); tmp = tmp->next; len -= remlen; data_c += remlen; } @@ -633,23 +688,63 @@ } #endif /* def DEBUG */ /* - * Determine whether or not a Config structure represents a session - * which can sensibly be launched right now. - */ -int cfg_launchable(const Config *cfg) -{ - if (cfg->protocol == PROT_SERIAL) - return cfg->serline[0] != 0; - else - return cfg->host[0] != 0; -} - -char const *cfg_dest(const Config *cfg) -{ - if (cfg->protocol == PROT_SERIAL) - return cfg->serline; - else - return cfg->host; -} + * Determine whether or not a Conf represents a session which can + * sensibly be launched right now. + */ +int conf_launchable(Conf *conf) +{ + if (conf_get_int(conf, CONF_protocol) == PROT_SERIAL) + return conf_get_str(conf, CONF_serline)[0] != 0; + else + return conf_get_str(conf, CONF_host)[0] != 0; +} + +char const *conf_dest(Conf *conf) +{ + if (conf_get_int(conf, CONF_protocol) == PROT_SERIAL) + return conf_get_str(conf, CONF_serline); + else + return conf_get_str(conf, CONF_host); +} + +#ifndef PLATFORM_HAS_SMEMCLR +/* + * Securely wipe memory. + * + * The actual wiping is no different from what memset would do: the + * point of 'securely' is to try to be sure over-clever compilers + * won't optimise away memsets on variables that are about to be freed + * or go out of scope. See + * https://buildsecurityin.us-cert.gov/bsi-rules/home/g1/771-BSI.html + * + * Some platforms (e.g. Windows) may provide their own version of this + * function. + */ +void smemclr(void *b, size_t n) { + volatile char *vp; + + if (b && n > 0) { + /* + * Zero out the memory. + */ + memset(b, 0, n); + + /* + * Perform a volatile access to the object, forcing the + * compiler to admit that the previous memset was important. + * + * This while loop should in practice run for zero iterations + * (since we know we just zeroed the object out), but in + * theory (as far as the compiler knows) it might range over + * the whole object. (If we had just written, say, '*vp = + * *vp;', a compiler could in principle have 'helpfully' + * optimised the memset into only zeroing out the first byte. + * This should be robust.) + */ + vp = b; + while (*vp) vp++; + } +} +#endif Index: misc.h ================================================================== --- misc.h +++ misc.h @@ -24,12 +24,19 @@ unsigned long parse_blocksize(const char *bs); char ctrlparse(char *s, char **next); char *dupstr(const char *s); char *dupcat(const char *s1, ...); -char *dupprintf(const char *fmt, ...); +char *dupprintf(const char *fmt, ...) +#ifdef __GNUC__ + __attribute__ ((format (printf, 1, 2))) +#endif + ; char *dupvprintf(const char *fmt, va_list ap); +void burnstr(char *string); + +int toint(unsigned); char *fgetline(FILE *fp); void base64_encode_atom(unsigned char *data, int n, char *out); @@ -46,10 +53,12 @@ void bufchain_prefix(bufchain *ch, void **data, int *len); void bufchain_consume(bufchain *ch, int len); void bufchain_fetch(bufchain *ch, void *data, int len); struct tm ltime(void); + +void smemclr(void *b, size_t len); /* * Debugging functions. * * Output goes to debug.log Index: mkauto.sh ================================================================== --- mkauto.sh +++ mkauto.sh @@ -5,43 +5,7 @@ # It's nice to be able to run this from inside the unix subdir as # well as from outside. test -f unix.h && cd .. -# Persuade automake to give us a copy of its install-sh. This is a -# pain because I don't actually want to have to _use_ automake. -# Instead, I construct a trivial unrelated automake project in a -# temporary subdirectory, run automake so that it'll copy -# install-sh into that directory, then copy it back out again. -# Hideous, but it should work. - -mkdir automake-grievous-hack -cat > automake-grievous-hack/hello.c << EOF -#include -int main(int argc, char **argv) -{ - printf("hello, world\n"); - return 0; -} -EOF -cat > automake-grievous-hack/Makefile.am << EOF -bin_PROGRAMS = hello -hello_SOURCES = hello.c -EOF -cat > automake-grievous-hack/configure.ac << EOF -AC_INIT -AM_INIT_AUTOMAKE(hello, 1.0) -AC_CONFIG_FILES([Makefile]) -AC_PROG_CC -AC_OUTPUT -EOF -echo Some news > automake-grievous-hack/NEWS -echo Some text > automake-grievous-hack/README -echo Some people > automake-grievous-hack/AUTHORS -echo Some changes > automake-grievous-hack/ChangeLog -rm -f install-sh # this won't work if we accidentally have one _here_ -(cd automake-grievous-hack && autoreconf -i && \ - cp install-sh ../unix/install-sh) -rm -rf automake-grievous-hack - -# That was the hard bit. Now run autoconf on our real configure.in. -(cd unix && autoreconf && rm -rf aclocal.m4 autom4te.cache) +# Run autoconf on our real configure.in. +(cd unix && autoreconf -i && rm -rf autom4te.cache) Index: mkfiles.pl ================================================================== --- mkfiles.pl +++ mkfiles.pl @@ -15,11 +15,27 @@ # # FIXME: no attempt made to handle !forceobj in the project files. use warnings; use FileHandle; +use File::Basename; use Cwd; + +if ($#ARGV >= 0 and ($ARGV[0] eq "-u" or $ARGV[0] eq "-U")) { + # Convenience for Unix users: -u means that after we finish what + # we're doing here, we also run mkauto.sh and then 'configure' in + # the Unix subdirectory. So it's a one-stop shop for regenerating + # the actual end-product Unix makefile. + # + # Arguments supplied after -u go to configure. + # + # -U is identical, but runs 'configure' at the _top_ level, for + # people who habitually do that. + $do_unix = ($ARGV[0] eq "-U" ? 2 : 1); + shift @ARGV; + @confargs = @ARGV; +} open IN, "Recipe" or do { # We want to deal correctly with being run from one of the # subdirs in the source tree. So if we can't find Recipe here, # try one level up. @@ -28,11 +44,11 @@ }; # HACK: One of the source files in `charset' is auto-generated by # sbcsgen.pl. We need to generate that _now_, before attempting # dependency analysis. -eval 'chdir "charset"; require "sbcsgen.pl"; chdir ".."'; +eval 'chdir "charset"; require "sbcsgen.pl"; chdir ".."; select STDOUT;'; @srcdirs = ("./"); $divert = undef; # ref to scalar in which text is currently being put $help = ""; # list of newline-free lines of help text @@ -62,13 +78,21 @@ if ($_[0] eq "!end") { $divert = undef; next; } if ($_[0] eq "!name") { $project_name = $_[1]; next; } if ($_[0] eq "!srcdir") { push @srcdirs, $_[1]; next; } if ($_[0] eq "!makefile" and &mfval($_[1])) { $makefiles{$_[1]}=$_[2]; next;} if ($_[0] eq "!specialobj" and &mfval($_[1])) { $specialobj{$_[1]}->{$_[2]} = 1; next;} + if ($_[0] eq "!cflags" and &mfval($_[1])) { + ($rest = $_) =~ s/^\s*\S+\s+\S+\s+\S+\s*//; # find rest of input line + $rest = 1 if $rest eq ""; + $cflags{$_[1]}->{$_[2]} = $rest; + next; + } if ($_[0] eq "!forceobj") { $forceobj{$_[1]} = 1; next; } if ($_[0] eq "!begin") { - if (&mfval($_[1])) { + if ($_[1] =~ /^>(.*)/) { + $divert = \$auxfiles{$1}; + } elsif (&mfval($_[1])) { $sect = $_[2] ? $_[2] : "end"; $divert = \($makefile_extra{$_[1]}->{$sect}); } else { $dummy = ''; $divert = \$dummy; @@ -120,10 +144,16 @@ } $lastlistref = $listref; } close IN; + +foreach $aux (sort keys %auxfiles) { + open AUX, ">$aux"; + print AUX $auxfiles{$aux}; + close AUX; +} # Now retrieve the complete list of objects and resource files, and # construct dependency data for them. While we're here, expand the # object list for each program, and complain if its type isn't set. @prognames = sort keys %programs; @@ -175,15 +205,17 @@ # # In this pass we write out a hash %further which maps a source # file name into a listref containing further source file names. %further = (); +%allsourcefiles = (); # this is wanted by some makefiles while (scalar @scanlist > 0) { $file = shift @scanlist; next if defined $further{$file}; # skip if we've already done it $further{$file} = []; $dirfile = &findfile($file); + $allsourcefiles{$dirfile} = 1; open IN, "$dirfile" or die "unable to open source file $file\n"; while () { chomp; /^\s*#include\s+\"([^\"]+)\"/ and do { push @{$further{$file}}, $1; @@ -224,11 +256,11 @@ my ($type) = @_; # Returns true if the argument is a known makefile type. Otherwise, # prints a warning and returns false; if (grep { $type eq $_ } ("vc","vcproj","cygwin","borland","lcc","devcppproj","gtk","unix", - "ac","osx",)) { + "am","osx",)) { return 1; } warn "$.:unknown makefile type '$type'\n"; return 0; } @@ -388,20 +420,22 @@ if($suffix eq "1" && $types =~ /:X:/) { return map("$_.1", &progrealnames($types)); } return (); } + +$orig_dir = cwd; # Now we're ready to output the actual Makefiles. if (defined $makefiles{'cygwin'}) { $dirpfx = &dirpfx($makefiles{'cygwin'}, "/"); ##-- CygWin makefile open OUT, ">$makefiles{'cygwin'}"; select OUT; print - "# Makefile for $project_name under cygwin.\n". + "# Makefile for $project_name under Cygwin, MinGW, or Winelib.\n". "#\n# This file was created by `mkfiles.pl' from the `Recipe' file.\n". "# DO NOT EDIT THIS FILE DIRECTLY; edit Recipe or mkfiles.pl instead.\n"; # gcc command line option is -D not /D ($_ = $help) =~ s/([=" ])\/D/$1-D/gs; print $_; @@ -417,11 +451,11 @@ "# RC = wrc\n". "# You may also need to tell windres where to find include files:\n". "# RCINC = --include-dir c:\\cygwin\\include\\\n". "\n". &splitline("CFLAGS = -mno-cygwin -Wall -O2 -D_WINDOWS -DDEBUG -DWIN32S_COMPAT". - " -D_NO_OLDNAMES -DNO_MULTIMON -DNO_HTMLHELP " . + " -D_NO_OLDNAMES -DNO_MULTIMON -DNO_HTMLHELP -DNO_SECUREZEROMEMORY " . (join " ", map {"-I$dirpfx$_"} @srcdirs)) . "\n". "LDFLAGS = -mno-cygwin -s\n". &splitline("RCFLAGS = \$(RCINC) --define WIN32=1 --define _WIN32=1". " --define WINVER=0x0400")."\n". @@ -448,19 +482,19 @@ } else { print &splitline(sprintf("%s: %s", $d->{obj}, join " ", @{$d->{deps}})), "\n"; } if ($d->{obj} =~ /\.res\.o$/) { - print "\t\$(RC) \$(RCFL) \$(RCFLAGS) ".$d->{deps}->[0]." ".$d->{obj}."\n\n"; + print "\t\$(RC) \$(RCFL) \$(RCFLAGS) ".$d->{deps}->[0]." -o ".$d->{obj}."\n\n"; } else { print "\t\$(CC) \$(COMPAT) \$(CFLAGS) \$(XFLAGS) -c ".$d->{deps}->[0]."\n\n"; } } print "\n"; print $makefile_extra{'cygwin'}->{'end'}; print "\nclean:\n". - "\trm -f *.o *.exe *.res.o *.map\n". + "\trm -f *.o *.exe *.res.o *.so *.map\n". "\n". "FORCE:\n"; select STDOUT; close OUT; } @@ -667,12 +701,10 @@ } if (defined $makefiles{'vcproj'}) { $dirpfx = &dirpfx($makefiles{'vcproj'}, "\\"); - $orig_dir = cwd; - ##-- MSVC 6 Workspace and projects # # Note: All files created in this section are written in binary # mode, because although MSVC's command-line make can deal with # LF-only line endings, MSVC project files really _need_ to be @@ -1082,75 +1114,96 @@ "\trm -f *.o". (join "", map { " $_" } &progrealnames("U")) . "\n"; print "\nFORCE:\n"; select STDOUT; close OUT; } -if (defined $makefiles{'ac'}) { - $dirpfx = &dirpfx($makefiles{'ac'}, "/"); +if (defined $makefiles{'am'}) { + $dirpfx = "\$(srcdir)/" . &dirpfx($makefiles{'am'}, "/"); - ##-- Unix/autoconf makefile - open OUT, ">$makefiles{'ac'}"; select OUT; + ##-- Unix/autoconf Makefile.am + open OUT, ">$makefiles{'am'}"; select OUT; print - "# Makefile.in for $project_name under Unix with Autoconf.\n". + "# Makefile.am for $project_name under Unix with Autoconf/Automake.\n". "#\n# This file was created by `mkfiles.pl' from the `Recipe' file.\n". - "# DO NOT EDIT THIS FILE DIRECTLY; edit Recipe or mkfiles.pl instead.\n"; - # gcc command line option is -D not /D - ($_ = $help) =~ s/([=" ])\/D/$1-D/gs; - print $_; - print - "\n". - "CC = \@CC\@\n". - "\n". - &splitline("CFLAGS = \@CFLAGS\@ \@PUTTYCFLAGS\@ \@CPPFLAGS\@ " . - "\@DEFS\@ \@GTK_CFLAGS\@ " . - (join " ", map {"-I$dirpfx$_"} @srcdirs))."\n". - "XLDFLAGS = \@LDFLAGS\@ \@LIBS\@ \@GTK_LIBS\@\n". - "ULDFLAGS = \@LDFLAGS\@ \@LIBS\@\n". - "INSTALL=\@INSTALL\@\n". - "INSTALL_PROGRAM=\$(INSTALL)\n". - "INSTALL_DATA=\$(INSTALL)\n". - "prefix=\@prefix\@\n". - "exec_prefix=\@exec_prefix\@\n". - "bindir=\@bindir\@\n". - "datarootdir=\@datarootdir\@\n". - "mandir=\@mandir\@\n". - "man1dir=\$(mandir)/man1\n". - "\n". - &def($makefile_extra{'gtk'}->{'vars'}) . - "\n". - ".SUFFIXES:\n". - "\n". - "\n". - "all: \@all_targets\@\n". - &splitline("all-cli:" . join "", map { " $_" } &progrealnames("U"))."\n". - &splitline("all-gtk:" . join "", map { " $_" } &progrealnames("X"))."\n"; - print "\n"; + "# DO NOT EDIT THIS FILE DIRECTLY; edit Recipe or mkfiles.pl instead.\n\n"; + + # Complete list of source and header files. Not used by the + # auto-generated parts of this makefile, but Recipe might like to + # have it available as a variable so that mandatory-rebuild things + # (version.o) can conveniently be made to depend on it. + @sources = ("allsources", "=", + map {"${dirpfx}$_"} sort keys %allsourcefiles); + print &splitline(join " ", @sources), "\n\n"; + + @cliprogs = ("bin_PROGRAMS", "="); + foreach $p (&prognames("U")) { + ($prog, $type) = split ",", $p; + push @cliprogs, $prog; + } + @allprogs = @cliprogs; + foreach $p (&prognames("X")) { + ($prog, $type) = split ",", $p; + push @allprogs, $prog; + } + print "if HAVE_GTK\n"; + print &splitline(join " ", @allprogs), "\n"; + print "else\n"; + print &splitline(join " ", @cliprogs), "\n"; + print "endif\n\n"; + + %objtosrc = (); + foreach $d (&deps("X", undef, $dirpfx, "/", "am")) { + $objtosrc{$d->{obj}} = $d->{deps}->[0]; + } + + print &splitline(join " ", "AM_CPPFLAGS", "=", + map {"-I$dirpfx$_"} @srcdirs), "\n"; + + @amcflags = ("\$(COMPAT)", "\$(XFLAGS)", "\$(WARNINGOPTS)"); + print "if HAVE_GTK\n"; + print &splitline(join " ", "AM_CFLAGS", "=", + "\$(GTK_CFLAGS)", @amcflags), "\n"; + print "else\n"; + print &splitline(join " ", "AM_CFLAGS", "=", @amcflags), "\n"; + print "endif\n\n"; + + %amspeciallibs = (); + foreach $obj (sort { $a cmp $b } keys %{$cflags{'am'}}) { + print "lib${obj}_a_SOURCES = ", $objtosrc{$obj}, "\n"; + print &splitline(join " ", "lib${obj}_a_CFLAGS", "=", @amcflags, + $cflags{'am'}->{$obj}), "\n"; + $amspeciallibs{$obj} = "lib${obj}.a"; + } + print &splitline(join " ", "noinst_LIBRARIES", "=", + sort { $a cmp $b } values %amspeciallibs), "\n\n"; + foreach $p (&prognames("X:U")) { ($prog, $type) = split ",", $p; - $objstr = &objects($p, "X.o", undef, undef); - print &splitline($prog . ": " . $objstr), "\n"; - $libstr = &objects($p, undef, undef, "-lX"); - print &splitline("\t\$(CC) -o \$@ " . - $objstr . " \$(${type}LDFLAGS) $libstr", 69), "\n\n"; - } - foreach $d (&deps("X.o", undef, $dirpfx, "/", "gtk")) { - if ($forceobj{$d->{obj_orig}}) { - printf("%s: FORCE\n", $d->{obj}); - } else { - print &splitline(sprintf("%s: %s", $d->{obj}, - join " ", @{$d->{deps}})), "\n"; - } - print &splitline("\t\$(CC) \$(COMPAT) \$(CFLAGS) \$(XFLAGS) -c $d->{deps}->[0]\n"); - } - print "\n"; - print $makefile_extra{'gtk'}->{'end'}; - print "\nclean:\n". - "\trm -f *.o". (join "", map { " $_" } &progrealnames("X:U")) . "\n"; - print "\ndistclean: clean\n". - "\t". &splitline("rm -f config.status config.cache config.log ". - "configure.lineno config.status.lineno Makefile") . "\n"; - print "\nFORCE:\n"; + print "if HAVE_GTK\n" if $type eq "X"; + @progsources = ("${prog}_SOURCES", "="); + %sourcefiles = (); + @ldadd = (); + $objstr = &objects($p, "X", undef, undef); + foreach $obj (split / /,$objstr) { + if ($amspeciallibs{$obj}) { + push @ldadd, $amspeciallibs{$obj}; + } else { + $sourcefiles{$objtosrc{$obj}} = 1; + } + } + push @progsources, sort { $a cmp $b } keys %sourcefiles; + print &splitline(join " ", @progsources), "\n"; + if ($type eq "X") { + push @ldadd, "\$(GTK_LIBS)"; + } + if (@ldadd) { + print &splitline(join " ", "${prog}_LDADD", "=", @ldadd), "\n"; + } + print "endif\n" if $type eq "X"; + print "\n"; + } + print $makefile_extra{'am'}->{'end'}; select STDOUT; close OUT; } if (defined $makefiles{'lcc'}) { $dirpfx = &dirpfx($makefiles{'lcc'}, "\\"); @@ -1333,10 +1386,12 @@ # Get names of all Windows projects (GUI and console) my @prognames = &prognames("G:C"); foreach $progname (@prognames) { create_devcpp_project(\%all_object_deps, $progname); } + + chdir $orig_dir; sub create_devcpp_project { my ($all_object_deps, $progname) = @_; # Construct program's dependency info (Taken from 'vcproj', seems to work right here, too.) %seen_objects = (); @@ -1489,5 +1544,19 @@ "AutoIncBuildNr=0\r\n"; select STDOUT; close OUT; chdir ".."; } } + +# All done, so do the Unix postprocessing if asked to. + +if ($do_unix) { + chdir $orig_dir; + system "./mkauto.sh"; + die "mkfiles.pl: mkauto.sh returned $?\n" if $? > 0; + if ($do_unix == 1) { + chdir ($targetdir = dirname($makefiles{"am"})) + or die "$targetdir: chdir: $!\n"; + } + system "./configure", @confargs; + die "mkfiles.pl: configure returned $?\n" if $? > 0; +} Index: mksrcarc.sh ================================================================== --- mksrcarc.sh +++ mksrcarc.sh @@ -18,7 +18,7 @@ bintext=testdata/*.txt # These are actual binary files which we don't want transforming. bin=`{ ls -1 windows/*.ico windows/putty.iss windows/website.url macosx/*.icns; \ find . -name '*.dsp' -print -o -name '*.dsw' -print; }` zip -k -l putty-src.zip $text > /dev/null -zip -k -l putty-src.zip $bintext >& /dev/null +zip -k -l putty-src.zip $bintext > /dev/null 2>&1 zip -k putty-src.zip $bin > /dev/null Index: mkunxarc.sh ================================================================== --- mkunxarc.sh +++ mkunxarc.sh @@ -1,6 +1,6 @@ -#!/bin/sh +#!/bin/bash # Build a Unix source distribution from the PuTTY CVS area. # # Pass an argument of the form `2004-02-08' to have the archive # tagged as a development snapshot; of the form `0.54' to have it @@ -8,20 +8,23 @@ # custom build. Otherwise it'll be tagged as unidentified. case "$1" in ????-??-??) case "$1" in *[!-0-9]*) echo "Malformed snapshot ID '$1'" >&2;exit 1;;esac - arcsuffix="-`cat LATEST.VER`-$1" + autoconfver="`cat LATEST.VER`-$1" + arcsuffix="-$autoconfver" ver="-DSNAPSHOT=$1" docver= ;; r*) - arcsuffix="-$1" - ver="-DSVN_REV=$1" + autoconfver="$1" + arcsuffix="-$autoconfver" + ver="-DSVN_REV=${1#r}" docver= ;; '') + autoconfver="X.XX" # got to put something in here! arcsuffix= ver= docver= ;; *pre) @@ -33,19 +36,19 @@ ver="-DPRERELEASE=$1 -DSVN_REV=$2" docver="VERSION=\"PuTTY prerelease $1:r$2\"" ;; *) case "$1" in *[!.0-9a-z~]*) echo "Malformed release ID '$1'">&2;exit 1;;esac - arcsuffix="-$1" + autoconfver="$1" + arcsuffix="-$autoconfver" ver="-DRELEASE=$1" docver="VERSION=\"PuTTY release $1\"" ;; esac perl mkfiles.pl (cd doc && make -s ${docver:+"$docver"}) -sh mkauto.sh 2>/dev/null relver=`cat LATEST.VER` arcname="putty$arcsuffix" mkdir uxarc mkdir uxarc/$arcname @@ -56,15 +59,19 @@ -type d -exec mkdir uxarc/$arcname/{} \; find . -name uxarc -prune -o \ -name CVS -prune -o \ -name .cvsignore -prune -o \ -name .svn -prune -o \ + -name configure.ac -prune -o \ -name '*.zip' -prune -o \ -name '*.tar.gz' -prune -o \ -type f -exec ln -s $PWD/{} uxarc/$arcname/{} \; if test "x$ver" != "x"; then (cd uxarc/$arcname; md5sum `find . -name '*.[ch]' -print` > manifest; echo "$ver" > version.def) fi +sed "s/^AC_INIT(putty,.*/AC_INIT(putty, $autoconfver)/" unix/configure.ac > uxarc/$arcname/unix/configure.ac +(cd uxarc/$arcname && sh mkauto.sh) 2>errors || { cat errors >&2; exit 1; } + tar -C uxarc -chzof $arcname.tar.gz $arcname rm -rf uxarc Index: network.h ================================================================== --- network.h +++ network.h @@ -13,40 +13,38 @@ #ifndef PUTTY_NETWORK_H #define PUTTY_NETWORK_H #ifndef DONE_TYPEDEFS #define DONE_TYPEDEFS -typedef struct config_tag Config; +typedef struct conf_tag Conf; typedef struct backend_tag Backend; typedef struct terminal_tag Terminal; #endif typedef struct SockAddr_tag *SockAddr; /* pay attention to levels of indirection */ typedef struct socket_function_table **Socket; typedef struct plug_function_table **Plug; -#ifndef OSSOCKET_DEFINED -typedef void *OSSocket; -#endif - struct socket_function_table { Plug(*plug) (Socket s, Plug p); /* use a different plug (return the old one) */ /* if p is NULL, it doesn't change the plug */ /* but it does return the one it's using */ void (*close) (Socket s); int (*write) (Socket s, const char *data, int len); int (*write_oob) (Socket s, const char *data, int len); + void (*write_eof) (Socket s); void (*flush) (Socket s); - void (*set_private_ptr) (Socket s, void *ptr); - void *(*get_private_ptr) (Socket s); void (*set_frozen) (Socket s, int is_frozen); /* ignored by tcp, but vital for ssl */ const char *(*socket_error) (Socket s); }; +typedef union { void *p; int i; } accept_ctx_t; +typedef Socket (*accept_fn_t)(accept_ctx_t ctx, Plug plug); + struct plug_function_table { void (*log)(Plug p, int type, SockAddr addr, int port, const char *error_msg, int error_code); /* * Passes the client progress reports on the process of setting @@ -80,45 +78,52 @@ /* * The `sent' function is called when the pending send backlog * on a socket is cleared or partially cleared. The new backlog * size is passed in the `bufsize' parameter. */ - int (*accepting)(Plug p, OSSocket sock); + int (*accepting)(Plug p, accept_fn_t constructor, accept_ctx_t ctx); /* - * returns 0 if the host at address addr is a valid host for connecting or error + * `accepting' is called only on listener-type sockets, and is + * passed a constructor function+context that will create a fresh + * Socket describing the connection. It returns nonzero if it + * doesn't want the connection for some reason, or 0 on success. */ }; /* proxy indirection layer */ /* NB, control of 'addr' is passed via new_connection, which takes * responsibility for freeing it */ Socket new_connection(SockAddr addr, char *hostname, int port, int privport, int oobinline, int nodelay, int keepalive, - Plug plug, const Config *cfg); + Plug plug, Conf *conf); Socket new_listener(char *srcaddr, int port, Plug plug, int local_host_only, - const Config *cfg, int addressfamily); + Conf *conf, int addressfamily); SockAddr name_lookup(char *host, int port, char **canonicalname, - const Config *cfg, int addressfamily); + Conf *conf, int addressfamily); +int proxy_for_destination (SockAddr addr, const char *hostname, int port, + Conf *conf); /* platform-dependent callback from new_connection() */ /* (same caveat about addr as new_connection()) */ Socket platform_new_connection(SockAddr addr, char *hostname, int port, int privport, int oobinline, int nodelay, int keepalive, - Plug plug, const Config *cfg); + Plug plug, Conf *conf); /* socket functions */ void sk_init(void); /* called once at program startup */ void sk_cleanup(void); /* called just before program exit */ SockAddr sk_namelookup(const char *host, char **canonicalname, int address_family); SockAddr sk_nonamelookup(const char *host); void sk_getaddr(SockAddr addr, char *buf, int buflen); -int sk_hostname_is_local(char *name); +int sk_addr_needs_port(SockAddr addr); +int sk_hostname_is_local(const char *name); int sk_address_is_local(SockAddr addr); +int sk_address_is_special_local(SockAddr addr); int sk_addrtype(SockAddr addr); void sk_addrcopy(SockAddr addr, char *buf); void sk_addr_free(SockAddr addr); /* sk_addr_dup generates another SockAddr which contains the same data * as the original one and can be freed independently. May not actually @@ -132,36 +137,25 @@ Socket sk_new(SockAddr addr, int port, int privport, int oobinline, int nodelay, int keepalive, Plug p); Socket sk_newlistener(char *srcaddr, int port, Plug plug, int local_host_only, int address_family); -Socket sk_register(OSSocket sock, Plug plug); - #define sk_plug(s,p) (((*s)->plug) (s, p)) #define sk_close(s) (((*s)->close) (s)) #define sk_write(s,buf,len) (((*s)->write) (s, buf, len)) #define sk_write_oob(s,buf,len) (((*s)->write_oob) (s, buf, len)) +#define sk_write_eof(s) (((*s)->write_eof) (s)) #define sk_flush(s) (((*s)->flush) (s)) #ifdef DEFINE_PLUG_METHOD_MACROS #define plug_log(p,type,addr,port,msg,code) (((*p)->log) (p, type, addr, port, msg, code)) #define plug_closing(p,msg,code,callback) (((*p)->closing) (p, msg, code, callback)) #define plug_receive(p,urgent,buf,len) (((*p)->receive) (p, urgent, buf, len)) #define plug_sent(p,bufsize) (((*p)->sent) (p, bufsize)) -#define plug_accepting(p, sock) (((*p)->accepting)(p, sock)) +#define plug_accepting(p, constructor, ctx) (((*p)->accepting)(p, constructor, ctx)) #endif -/* - * Each socket abstraction contains a `void *' private field in - * which the client can keep state. - * - * This is perhaps unnecessary now that we have the notion of a plug, - * but there is some existing code that uses it, so it stays. - */ -#define sk_set_private_ptr(s, ptr) (((*s)->set_private_ptr) (s, ptr)) -#define sk_get_private_ptr(s) (((*s)->get_private_ptr) (s)) - /* * Special error values are returned from sk_namelookup and sk_new * if there's a problem. These functions extract an error message, * or return NULL if there's no problem. */ @@ -185,16 +179,10 @@ * associated local socket in order to avoid unbounded buffer * growth. */ #define sk_set_frozen(s, is_frozen) (((*s)->set_frozen) (s, is_frozen)) -/* - * Call this after an operation that might have tried to send on a - * socket, to clean up any pending network errors. - */ -void net_pending_errors(void); - /* * Simple wrapper on getservbyname(), needed by ssh.c. Returns the * port number, in host byte order (suitable for printf and so on). * Returns 0 on failure. Any platform not supporting getservbyname * can just return 0 - this function is not required to handle @@ -206,10 +194,16 @@ * Look up the local hostname; return value needs freeing. * May return NULL. */ char *get_hostname(void); +/* + * Trivial socket implementation which just stores an error. Found in + * errsock.c. + */ +Socket new_error_socket(const char *errmsg, Plug plug); + /********** SSL stuff **********/ /* * This section is subject to change, but you get the general idea * of what it will eventually look like. ADDED noshare.c Index: noshare.c ================================================================== --- /dev/null +++ noshare.c @@ -0,0 +1,24 @@ +/* + * Stub implementation of SSH connection-sharing IPC, for any + * platform which can't support it at all. + */ + +#include +#include +#include + +#include "tree234.h" +#include "putty.h" +#include "ssh.h" +#include "network.h" + +int platform_ssh_share(const char *name, Conf *conf, + Plug downplug, Plug upplug, Socket *sock, + char **logtext) +{ + return SHARE_NONE; +} + +void platform_ssh_share_cleanup(const char *name) +{ +} ADDED noterm.c Index: noterm.c ================================================================== --- /dev/null +++ noterm.c @@ -0,0 +1,11 @@ +/* + * Stubs of functions in terminal.c, for use in programs that don't + * have a terminal. + */ + +#include "putty.h" +#include "terminal.h" + +void term_nopaste(Terminal *term) +{ +} Index: notiming.c ================================================================== --- notiming.c +++ notiming.c @@ -9,11 +9,11 @@ * have their own rigorous and different means of noise collection. */ #include "putty.h" -long schedule_timer(int ticks, timer_fn_t fn, void *ctx) +unsigned long schedule_timer(int ticks, timer_fn_t fn, void *ctx) { return 0; } void expire_timer_context(void *ctx) Index: pinger.c ================================================================== --- pinger.c +++ pinger.c @@ -6,22 +6,22 @@ #include "putty.h" struct pinger_tag { int interval; int pending; - long next; + unsigned long next; Backend *back; void *backhandle; }; static void pinger_schedule(Pinger pinger); -static void pinger_timer(void *ctx, long now) +static void pinger_timer(void *ctx, unsigned long now) { Pinger pinger = (Pinger)ctx; - if (pinger->pending && now - pinger->next >= 0) { + if (pinger->pending && now == pinger->next) { pinger->back->special(pinger->backhandle, TS_PING); pinger->pending = FALSE; pinger_schedule(pinger); } } @@ -41,31 +41,32 @@ pinger->next = next; pinger->pending = TRUE; } } -Pinger pinger_new(Config *cfg, Backend *back, void *backhandle) +Pinger pinger_new(Conf *conf, Backend *back, void *backhandle) { Pinger pinger = snew(struct pinger_tag); - pinger->interval = cfg->ping_interval; + pinger->interval = conf_get_int(conf, CONF_ping_interval); pinger->pending = FALSE; pinger->back = back; pinger->backhandle = backhandle; pinger_schedule(pinger); return pinger; } -void pinger_reconfig(Pinger pinger, Config *oldcfg, Config *newcfg) +void pinger_reconfig(Pinger pinger, Conf *oldconf, Conf *newconf) { - if (oldcfg->ping_interval != newcfg->ping_interval) { - pinger->interval = newcfg->ping_interval; + int newinterval = conf_get_int(newconf, CONF_ping_interval); + if (conf_get_int(oldconf, CONF_ping_interval) != newinterval) { + pinger->interval = newinterval; pinger_schedule(pinger); } } void pinger_free(Pinger pinger) { expire_timer_context(pinger); sfree(pinger); } DELETED pkcs11.h Index: pkcs11.h ================================================================== --- pkcs11.h +++ /dev/null @@ -1,1357 +0,0 @@ -/* pkcs11.h - Copyright 2006, 2007 g10 Code GmbH - Copyright 2006 Andreas Jellinghaus - - This file is free software; as a special exception the author gives - unlimited permission to copy and/or distribute it, with or without - modifications, as long as this notice is preserved. - - This file is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY, to the extent permitted by law; without even - the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR - PURPOSE. */ - -/* Please submit changes back to the Scute project at - http://www.scute.org/ (or send them to marcus@g10code.com), so that - they can be picked up by other projects from there as well. */ - -/* This file is a modified implementation of the PKCS #11 standard by - RSA Security Inc. It is mostly a drop-in replacement, with the - following change: - - This header file does not require any macro definitions by the user - (like CK_DEFINE_FUNCTION etc). In fact, it defines those macros - for you (if useful, some are missing, let me know if you need - more). - - There is an additional API available that does comply better to the - GNU coding standard. It can be switched on by defining - CRYPTOKI_GNU before including this header file. For this, the - following changes are made to the specification: - - All structure types are changed to a "struct ck_foo" where CK_FOO - is the type name in PKCS #11. - - All non-structure types are changed to ck_foo_t where CK_FOO is the - lowercase version of the type name in PKCS #11. The basic types - (CK_ULONG et al.) are removed without substitute. - - All members of structures are modified in the following way: Type - indication prefixes are removed, and underscore characters are - inserted before words. Then the result is lowercased. - - Note that function names are still in the original case, as they - need for ABI compatibility. - - CK_FALSE, CK_TRUE and NULL_PTR are removed without substitute. Use - . - - If CRYPTOKI_COMPAT is defined before including this header file, - then none of the API changes above take place, and the API is the - one defined by the PKCS #11 standard. */ - -#ifndef PKCS11_H -#define PKCS11_H 1 - -#if defined(__cplusplus) -extern "C" { -#endif - - -/* The version of cryptoki we implement. The revision is changed with - each modification of this file. If you do not use the "official" - version of this file, please consider deleting the revision macro - (you may use a macro with a different name to keep track of your - versions). */ -#define CRYPTOKI_VERSION_MAJOR 2 -#define CRYPTOKI_VERSION_MINOR 20 -#define CRYPTOKI_VERSION_REVISION 6 - - -/* Compatibility interface is default, unless CRYPTOKI_GNU is - given. */ -#ifndef CRYPTOKI_GNU -#ifndef CRYPTOKI_COMPAT -#define CRYPTOKI_COMPAT 1 -#endif -#endif - -/* System dependencies. */ - -#if defined(_WIN32) || defined(CRYPTOKI_FORCE_WIN32) - -/* There is a matching pop below. */ -#pragma pack(push, cryptoki, 1) - -#ifdef CRYPTOKI_EXPORTS -#define CK_SPEC __declspec(dllexport) -#else -#define CK_SPEC __declspec(dllimport) -#endif - -#else - -#define CK_SPEC - -#endif - - -#ifdef CRYPTOKI_COMPAT - /* If we are in compatibility mode, switch all exposed names to the - PKCS #11 variant. There are corresponding #undefs below. */ - -#define ck_flags_t CK_FLAGS -#define ck_version _CK_VERSION - -#define ck_info _CK_INFO -#define cryptoki_version cryptokiVersion -#define manufacturer_id manufacturerID -#define library_description libraryDescription -#define library_version libraryVersion - -#define ck_notification_t CK_NOTIFICATION -#define ck_slot_id_t CK_SLOT_ID - -#define ck_slot_info _CK_SLOT_INFO -#define slot_description slotDescription -#define hardware_version hardwareVersion -#define firmware_version firmwareVersion - -#define ck_token_info _CK_TOKEN_INFO -#define serial_number serialNumber -#define max_session_count ulMaxSessionCount -#define session_count ulSessionCount -#define max_rw_session_count ulMaxRwSessionCount -#define rw_session_count ulRwSessionCount -#define max_pin_len ulMaxPinLen -#define min_pin_len ulMinPinLen -#define total_public_memory ulTotalPublicMemory -#define free_public_memory ulFreePublicMemory -#define total_private_memory ulTotalPrivateMemory -#define free_private_memory ulFreePrivateMemory -#define utc_time utcTime - -#define ck_session_handle_t CK_SESSION_HANDLE -#define ck_user_type_t CK_USER_TYPE -#define ck_state_t CK_STATE - -#define ck_session_info _CK_SESSION_INFO -#define slot_id slotID -#define device_error ulDeviceError - -#define ck_object_handle_t CK_OBJECT_HANDLE -#define ck_object_class_t CK_OBJECT_CLASS -#define ck_hw_feature_type_t CK_HW_FEATURE_TYPE -#define ck_key_type_t CK_KEY_TYPE -#define ck_certificate_type_t CK_CERTIFICATE_TYPE -#define ck_attribute_type_t CK_ATTRIBUTE_TYPE - -#define ck_attribute _CK_ATTRIBUTE -#define value pValue -#define value_len ulValueLen - -#define ck_date _CK_DATE - -#define ck_mechanism_type_t CK_MECHANISM_TYPE - -#define ck_mechanism _CK_MECHANISM -#define parameter pParameter -#define parameter_len ulParameterLen - -#define ck_mechanism_info _CK_MECHANISM_INFO -#define min_key_size ulMinKeySize -#define max_key_size ulMaxKeySize - -#define ck_rv_t CK_RV -#define ck_notify_t CK_NOTIFY - -#define ck_function_list _CK_FUNCTION_LIST - -#define ck_createmutex_t CK_CREATEMUTEX -#define ck_destroymutex_t CK_DESTROYMUTEX -#define ck_lockmutex_t CK_LOCKMUTEX -#define ck_unlockmutex_t CK_UNLOCKMUTEX - -#define ck_c_initialize_args _CK_C_INITIALIZE_ARGS -#define create_mutex CreateMutex -#define destroy_mutex DestroyMutex -#define lock_mutex LockMutex -#define unlock_mutex UnlockMutex -#define reserved pReserved - -#endif /* CRYPTOKI_COMPAT */ - - - -typedef unsigned long ck_flags_t; - -struct ck_version -{ - unsigned char major; - unsigned char minor; -}; - - -struct ck_info -{ - struct ck_version cryptoki_version; - unsigned char manufacturer_id[32]; - ck_flags_t flags; - unsigned char library_description[32]; - struct ck_version library_version; -}; - - -typedef unsigned long ck_notification_t; - -#define CKN_SURRENDER (0) - - -typedef unsigned long ck_slot_id_t; - - -struct ck_slot_info -{ - unsigned char slot_description[64]; - unsigned char manufacturer_id[32]; - ck_flags_t flags; - struct ck_version hardware_version; - struct ck_version firmware_version; -}; - - -#define CKF_TOKEN_PRESENT (1 << 0) -#define CKF_REMOVABLE_DEVICE (1 << 1) -#define CKF_HW_SLOT (1 << 2) -#define CKF_ARRAY_ATTRIBUTE (1 << 30) - - -struct ck_token_info -{ - unsigned char label[32]; - unsigned char manufacturer_id[32]; - unsigned char model[16]; - unsigned char serial_number[16]; - ck_flags_t flags; - unsigned long max_session_count; - unsigned long session_count; - unsigned long max_rw_session_count; - unsigned long rw_session_count; - unsigned long max_pin_len; - unsigned long min_pin_len; - unsigned long total_public_memory; - unsigned long free_public_memory; - unsigned long total_private_memory; - unsigned long free_private_memory; - struct ck_version hardware_version; - struct ck_version firmware_version; - unsigned char utc_time[16]; -}; - - -#define CKF_RNG (1 << 0) -#define CKF_WRITE_PROTECTED (1 << 1) -#define CKF_LOGIN_REQUIRED (1 << 2) -#define CKF_USER_PIN_INITIALIZED (1 << 3) -#define CKF_RESTORE_KEY_NOT_NEEDED (1 << 5) -#define CKF_CLOCK_ON_TOKEN (1 << 6) -#define CKF_PROTECTED_AUTHENTICATION_PATH (1 << 8) -#define CKF_DUAL_CRYPTO_OPERATIONS (1 << 9) -#define CKF_TOKEN_INITIALIZED (1 << 10) -#define CKF_SECONDARY_AUTHENTICATION (1 << 11) -#define CKF_USER_PIN_COUNT_LOW (1 << 16) -#define CKF_USER_PIN_FINAL_TRY (1 << 17) -#define CKF_USER_PIN_LOCKED (1 << 18) -#define CKF_USER_PIN_TO_BE_CHANGED (1 << 19) -#define CKF_SO_PIN_COUNT_LOW (1 << 20) -#define CKF_SO_PIN_FINAL_TRY (1 << 21) -#define CKF_SO_PIN_LOCKED (1 << 22) -#define CKF_SO_PIN_TO_BE_CHANGED (1 << 23) - -#define CK_UNAVAILABLE_INFORMATION ((unsigned long) -1) -#define CK_EFFECTIVELY_INFINITE (0) - - -typedef unsigned long ck_session_handle_t; - -#define CK_INVALID_HANDLE (0) - - -typedef unsigned long ck_user_type_t; - -#define CKU_SO (0) -#define CKU_USER (1) -#define CKU_CONTEXT_SPECIFIC (2) - - -typedef unsigned long ck_state_t; - -#define CKS_RO_PUBLIC_SESSION (0) -#define CKS_RO_USER_FUNCTIONS (1) -#define CKS_RW_PUBLIC_SESSION (2) -#define CKS_RW_USER_FUNCTIONS (3) -#define CKS_RW_SO_FUNCTIONS (4) - - -struct ck_session_info -{ - ck_slot_id_t slot_id; - ck_state_t state; - ck_flags_t flags; - unsigned long device_error; -}; - -#define CKF_RW_SESSION (1 << 1) -#define CKF_SERIAL_SESSION (1 << 2) - - -typedef unsigned long ck_object_handle_t; - - -typedef unsigned long ck_object_class_t; - -#define CKO_DATA (0) -#define CKO_CERTIFICATE (1) -#define CKO_PUBLIC_KEY (2) -#define CKO_PRIVATE_KEY (3) -#define CKO_SECRET_KEY (4) -#define CKO_HW_FEATURE (5) -#define CKO_DOMAIN_PARAMETERS (6) -#define CKO_MECHANISM (7) -#define CKO_VENDOR_DEFINED ((unsigned long) (1 << 31)) - - -typedef unsigned long ck_hw_feature_type_t; - -#define CKH_MONOTONIC_COUNTER (1) -#define CKH_CLOCK (2) -#define CKH_USER_INTERFACE (3) -#define CKH_VENDOR_DEFINED ((unsigned long) (1 << 31)) - - -typedef unsigned long ck_key_type_t; - -#define CKK_RSA (0) -#define CKK_DSA (1) -#define CKK_DH (2) -#define CKK_ECDSA (3) -#define CKK_EC (3) -#define CKK_X9_42_DH (4) -#define CKK_KEA (5) -#define CKK_GENERIC_SECRET (0x10) -#define CKK_RC2 (0x11) -#define CKK_RC4 (0x12) -#define CKK_DES (0x13) -#define CKK_DES2 (0x14) -#define CKK_DES3 (0x15) -#define CKK_CAST (0x16) -#define CKK_CAST3 (0x17) -#define CKK_CAST128 (0x18) -#define CKK_RC5 (0x19) -#define CKK_IDEA (0x1a) -#define CKK_SKIPJACK (0x1b) -#define CKK_BATON (0x1c) -#define CKK_JUNIPER (0x1d) -#define CKK_CDMF (0x1e) -#define CKK_AES (0x1f) -#define CKK_BLOWFISH (0x20) -#define CKK_TWOFISH (0x21) -#define CKK_VENDOR_DEFINED ((unsigned long) (1 << 31)) - - -typedef unsigned long ck_certificate_type_t; - -#define CKC_X_509 (0) -#define CKC_X_509_ATTR_CERT (1) -#define CKC_WTLS (2) -#define CKC_VENDOR_DEFINED ((unsigned long) (1 << 31)) - - -typedef unsigned long ck_attribute_type_t; - -#define CKA_CLASS (0) -#define CKA_TOKEN (1) -#define CKA_PRIVATE (2) -#define CKA_LABEL (3) -#define CKA_APPLICATION (0x10) -#define CKA_VALUE (0x11) -#define CKA_OBJECT_ID (0x12) -#define CKA_CERTIFICATE_TYPE (0x80) -#define CKA_ISSUER (0x81) -#define CKA_SERIAL_NUMBER (0x82) -#define CKA_AC_ISSUER (0x83) -#define CKA_OWNER (0x84) -#define CKA_ATTR_TYPES (0x85) -#define CKA_TRUSTED (0x86) -#define CKA_CERTIFICATE_CATEGORY (0x87) -#define CKA_JAVA_MIDP_SECURITY_DOMAIN (0x88) -#define CKA_URL (0x89) -#define CKA_HASH_OF_SUBJECT_PUBLIC_KEY (0x8a) -#define CKA_HASH_OF_ISSUER_PUBLIC_KEY (0x8b) -#define CKA_CHECK_VALUE (0x90) -#define CKA_KEY_TYPE (0x100) -#define CKA_SUBJECT (0x101) -#define CKA_ID (0x102) -#define CKA_SENSITIVE (0x103) -#define CKA_ENCRYPT (0x104) -#define CKA_DECRYPT (0x105) -#define CKA_WRAP (0x106) -#define CKA_UNWRAP (0x107) -#define CKA_SIGN (0x108) -#define CKA_SIGN_RECOVER (0x109) -#define CKA_VERIFY (0x10a) -#define CKA_VERIFY_RECOVER (0x10b) -#define CKA_DERIVE (0x10c) -#define CKA_START_DATE (0x110) -#define CKA_END_DATE (0x111) -#define CKA_MODULUS (0x120) -#define CKA_MODULUS_BITS (0x121) -#define CKA_PUBLIC_EXPONENT (0x122) -#define CKA_PRIVATE_EXPONENT (0x123) -#define CKA_PRIME_1 (0x124) -#define CKA_PRIME_2 (0x125) -#define CKA_EXPONENT_1 (0x126) -#define CKA_EXPONENT_2 (0x127) -#define CKA_COEFFICIENT (0x128) -#define CKA_PRIME (0x130) -#define CKA_SUBPRIME (0x131) -#define CKA_BASE (0x132) -#define CKA_PRIME_BITS (0x133) -#define CKA_SUB_PRIME_BITS (0x134) -#define CKA_VALUE_BITS (0x160) -#define CKA_VALUE_LEN (0x161) -#define CKA_EXTRACTABLE (0x162) -#define CKA_LOCAL (0x163) -#define CKA_NEVER_EXTRACTABLE (0x164) -#define CKA_ALWAYS_SENSITIVE (0x165) -#define CKA_KEY_GEN_MECHANISM (0x166) -#define CKA_MODIFIABLE (0x170) -#define CKA_ECDSA_PARAMS (0x180) -#define CKA_EC_PARAMS (0x180) -#define CKA_EC_POINT (0x181) -#define CKA_SECONDARY_AUTH (0x200) -#define CKA_AUTH_PIN_FLAGS (0x201) -#define CKA_ALWAYS_AUTHENTICATE (0x202) -#define CKA_WRAP_WITH_TRUSTED (0x210) -#define CKA_HW_FEATURE_TYPE (0x300) -#define CKA_RESET_ON_INIT (0x301) -#define CKA_HAS_RESET (0x302) -#define CKA_PIXEL_X (0x400) -#define CKA_PIXEL_Y (0x401) -#define CKA_RESOLUTION (0x402) -#define CKA_CHAR_ROWS (0x403) -#define CKA_CHAR_COLUMNS (0x404) -#define CKA_COLOR (0x405) -#define CKA_BITS_PER_PIXEL (0x406) -#define CKA_CHAR_SETS (0x480) -#define CKA_ENCODING_METHODS (0x481) -#define CKA_MIME_TYPES (0x482) -#define CKA_MECHANISM_TYPE (0x500) -#define CKA_REQUIRED_CMS_ATTRIBUTES (0x501) -#define CKA_DEFAULT_CMS_ATTRIBUTES (0x502) -#define CKA_SUPPORTED_CMS_ATTRIBUTES (0x503) -#define CKA_WRAP_TEMPLATE (CKF_ARRAY_ATTRIBUTE | 0x211) -#define CKA_UNWRAP_TEMPLATE (CKF_ARRAY_ATTRIBUTE | 0x212) -#define CKA_ALLOWED_MECHANISMS (CKF_ARRAY_ATTRIBUTE | 0x600) -#define CKA_VENDOR_DEFINED ((unsigned long) (1 << 31)) - - -struct ck_attribute -{ - ck_attribute_type_t type; - void *value; - unsigned long value_len; -}; - - -struct ck_date -{ - unsigned char year[4]; - unsigned char month[2]; - unsigned char day[2]; -}; - - -typedef unsigned long ck_mechanism_type_t; - -#define CKM_RSA_PKCS_KEY_PAIR_GEN (0) -#define CKM_RSA_PKCS (1) -#define CKM_RSA_9796 (2) -#define CKM_RSA_X_509 (3) -#define CKM_MD2_RSA_PKCS (4) -#define CKM_MD5_RSA_PKCS (5) -#define CKM_SHA1_RSA_PKCS (6) -#define CKM_RIPEMD128_RSA_PKCS (7) -#define CKM_RIPEMD160_RSA_PKCS (8) -#define CKM_RSA_PKCS_OAEP (9) -#define CKM_RSA_X9_31_KEY_PAIR_GEN (0xa) -#define CKM_RSA_X9_31 (0xb) -#define CKM_SHA1_RSA_X9_31 (0xc) -#define CKM_RSA_PKCS_PSS (0xd) -#define CKM_SHA1_RSA_PKCS_PSS (0xe) -#define CKM_DSA_KEY_PAIR_GEN (0x10) -#define CKM_DSA (0x11) -#define CKM_DSA_SHA1 (0x12) -#define CKM_DH_PKCS_KEY_PAIR_GEN (0x20) -#define CKM_DH_PKCS_DERIVE (0x21) -#define CKM_X9_42_DH_KEY_PAIR_GEN (0x30) -#define CKM_X9_42_DH_DERIVE (0x31) -#define CKM_X9_42_DH_HYBRID_DERIVE (0x32) -#define CKM_X9_42_MQV_DERIVE (0x33) -#define CKM_SHA256_RSA_PKCS (0x40) -#define CKM_SHA384_RSA_PKCS (0x41) -#define CKM_SHA512_RSA_PKCS (0x42) -#define CKM_SHA256_RSA_PKCS_PSS (0x43) -#define CKM_SHA384_RSA_PKCS_PSS (0x44) -#define CKM_SHA512_RSA_PKCS_PSS (0x45) -#define CKM_RC2_KEY_GEN (0x100) -#define CKM_RC2_ECB (0x101) -#define CKM_RC2_CBC (0x102) -#define CKM_RC2_MAC (0x103) -#define CKM_RC2_MAC_GENERAL (0x104) -#define CKM_RC2_CBC_PAD (0x105) -#define CKM_RC4_KEY_GEN (0x110) -#define CKM_RC4 (0x111) -#define CKM_DES_KEY_GEN (0x120) -#define CKM_DES_ECB (0x121) -#define CKM_DES_CBC (0x122) -#define CKM_DES_MAC (0x123) -#define CKM_DES_MAC_GENERAL (0x124) -#define CKM_DES_CBC_PAD (0x125) -#define CKM_DES2_KEY_GEN (0x130) -#define CKM_DES3_KEY_GEN (0x131) -#define CKM_DES3_ECB (0x132) -#define CKM_DES3_CBC (0x133) -#define CKM_DES3_MAC (0x134) -#define CKM_DES3_MAC_GENERAL (0x135) -#define CKM_DES3_CBC_PAD (0x136) -#define CKM_CDMF_KEY_GEN (0x140) -#define CKM_CDMF_ECB (0x141) -#define CKM_CDMF_CBC (0x142) -#define CKM_CDMF_MAC (0x143) -#define CKM_CDMF_MAC_GENERAL (0x144) -#define CKM_CDMF_CBC_PAD (0x145) -#define CKM_MD2 (0x200) -#define CKM_MD2_HMAC (0x201) -#define CKM_MD2_HMAC_GENERAL (0x202) -#define CKM_MD5 (0x210) -#define CKM_MD5_HMAC (0x211) -#define CKM_MD5_HMAC_GENERAL (0x212) -#define CKM_SHA_1 (0x220) -#define CKM_SHA_1_HMAC (0x221) -#define CKM_SHA_1_HMAC_GENERAL (0x222) -#define CKM_RIPEMD128 (0x230) -#define CKM_RIPEMD128_HMAC (0x231) -#define CKM_RIPEMD128_HMAC_GENERAL (0x232) -#define CKM_RIPEMD160 (0x240) -#define CKM_RIPEMD160_HMAC (0x241) -#define CKM_RIPEMD160_HMAC_GENERAL (0x242) -#define CKM_SHA256 (0x250) -#define CKM_SHA256_HMAC (0x251) -#define CKM_SHA256_HMAC_GENERAL (0x252) -#define CKM_SHA384 (0x260) -#define CKM_SHA384_HMAC (0x261) -#define CKM_SHA384_HMAC_GENERAL (0x262) -#define CKM_SHA512 (0x270) -#define CKM_SHA512_HMAC (0x271) -#define CKM_SHA512_HMAC_GENERAL (0x272) -#define CKM_CAST_KEY_GEN (0x300) -#define CKM_CAST_ECB (0x301) -#define CKM_CAST_CBC (0x302) -#define CKM_CAST_MAC (0x303) -#define CKM_CAST_MAC_GENERAL (0x304) -#define CKM_CAST_CBC_PAD (0x305) -#define CKM_CAST3_KEY_GEN (0x310) -#define CKM_CAST3_ECB (0x311) -#define CKM_CAST3_CBC (0x312) -#define CKM_CAST3_MAC (0x313) -#define CKM_CAST3_MAC_GENERAL (0x314) -#define CKM_CAST3_CBC_PAD (0x315) -#define CKM_CAST5_KEY_GEN (0x320) -#define CKM_CAST128_KEY_GEN (0x320) -#define CKM_CAST5_ECB (0x321) -#define CKM_CAST128_ECB (0x321) -#define CKM_CAST5_CBC (0x322) -#define CKM_CAST128_CBC (0x322) -#define CKM_CAST5_MAC (0x323) -#define CKM_CAST128_MAC (0x323) -#define CKM_CAST5_MAC_GENERAL (0x324) -#define CKM_CAST128_MAC_GENERAL (0x324) -#define CKM_CAST5_CBC_PAD (0x325) -#define CKM_CAST128_CBC_PAD (0x325) -#define CKM_RC5_KEY_GEN (0x330) -#define CKM_RC5_ECB (0x331) -#define CKM_RC5_CBC (0x332) -#define CKM_RC5_MAC (0x333) -#define CKM_RC5_MAC_GENERAL (0x334) -#define CKM_RC5_CBC_PAD (0x335) -#define CKM_IDEA_KEY_GEN (0x340) -#define CKM_IDEA_ECB (0x341) -#define CKM_IDEA_CBC (0x342) -#define CKM_IDEA_MAC (0x343) -#define CKM_IDEA_MAC_GENERAL (0x344) -#define CKM_IDEA_CBC_PAD (0x345) -#define CKM_GENERIC_SECRET_KEY_GEN (0x350) -#define CKM_CONCATENATE_BASE_AND_KEY (0x360) -#define CKM_CONCATENATE_BASE_AND_DATA (0x362) -#define CKM_CONCATENATE_DATA_AND_BASE (0x363) -#define CKM_XOR_BASE_AND_DATA (0x364) -#define CKM_EXTRACT_KEY_FROM_KEY (0x365) -#define CKM_SSL3_PRE_MASTER_KEY_GEN (0x370) -#define CKM_SSL3_MASTER_KEY_DERIVE (0x371) -#define CKM_SSL3_KEY_AND_MAC_DERIVE (0x372) -#define CKM_SSL3_MASTER_KEY_DERIVE_DH (0x373) -#define CKM_TLS_PRE_MASTER_KEY_GEN (0x374) -#define CKM_TLS_MASTER_KEY_DERIVE (0x375) -#define CKM_TLS_KEY_AND_MAC_DERIVE (0x376) -#define CKM_TLS_MASTER_KEY_DERIVE_DH (0x377) -#define CKM_SSL3_MD5_MAC (0x380) -#define CKM_SSL3_SHA1_MAC (0x381) -#define CKM_MD5_KEY_DERIVATION (0x390) -#define CKM_MD2_KEY_DERIVATION (0x391) -#define CKM_SHA1_KEY_DERIVATION (0x392) -#define CKM_PBE_MD2_DES_CBC (0x3a0) -#define CKM_PBE_MD5_DES_CBC (0x3a1) -#define CKM_PBE_MD5_CAST_CBC (0x3a2) -#define CKM_PBE_MD5_CAST3_CBC (0x3a3) -#define CKM_PBE_MD5_CAST5_CBC (0x3a4) -#define CKM_PBE_MD5_CAST128_CBC (0x3a4) -#define CKM_PBE_SHA1_CAST5_CBC (0x3a5) -#define CKM_PBE_SHA1_CAST128_CBC (0x3a5) -#define CKM_PBE_SHA1_RC4_128 (0x3a6) -#define CKM_PBE_SHA1_RC4_40 (0x3a7) -#define CKM_PBE_SHA1_DES3_EDE_CBC (0x3a8) -#define CKM_PBE_SHA1_DES2_EDE_CBC (0x3a9) -#define CKM_PBE_SHA1_RC2_128_CBC (0x3aa) -#define CKM_PBE_SHA1_RC2_40_CBC (0x3ab) -#define CKM_PKCS5_PBKD2 (0x3b0) -#define CKM_PBA_SHA1_WITH_SHA1_HMAC (0x3c0) -#define CKM_KEY_WRAP_LYNKS (0x400) -#define CKM_KEY_WRAP_SET_OAEP (0x401) -#define CKM_SKIPJACK_KEY_GEN (0x1000) -#define CKM_SKIPJACK_ECB64 (0x1001) -#define CKM_SKIPJACK_CBC64 (0x1002) -#define CKM_SKIPJACK_OFB64 (0x1003) -#define CKM_SKIPJACK_CFB64 (0x1004) -#define CKM_SKIPJACK_CFB32 (0x1005) -#define CKM_SKIPJACK_CFB16 (0x1006) -#define CKM_SKIPJACK_CFB8 (0x1007) -#define CKM_SKIPJACK_WRAP (0x1008) -#define CKM_SKIPJACK_PRIVATE_WRAP (0x1009) -#define CKM_SKIPJACK_RELAYX (0x100a) -#define CKM_KEA_KEY_PAIR_GEN (0x1010) -#define CKM_KEA_KEY_DERIVE (0x1011) -#define CKM_FORTEZZA_TIMESTAMP (0x1020) -#define CKM_BATON_KEY_GEN (0x1030) -#define CKM_BATON_ECB128 (0x1031) -#define CKM_BATON_ECB96 (0x1032) -#define CKM_BATON_CBC128 (0x1033) -#define CKM_BATON_COUNTER (0x1034) -#define CKM_BATON_SHUFFLE (0x1035) -#define CKM_BATON_WRAP (0x1036) -#define CKM_ECDSA_KEY_PAIR_GEN (0x1040) -#define CKM_EC_KEY_PAIR_GEN (0x1040) -#define CKM_ECDSA (0x1041) -#define CKM_ECDSA_SHA1 (0x1042) -#define CKM_ECDH1_DERIVE (0x1050) -#define CKM_ECDH1_COFACTOR_DERIVE (0x1051) -#define CKM_ECMQV_DERIVE (0x1052) -#define CKM_JUNIPER_KEY_GEN (0x1060) -#define CKM_JUNIPER_ECB128 (0x1061) -#define CKM_JUNIPER_CBC128 (0x1062) -#define CKM_JUNIPER_COUNTER (0x1063) -#define CKM_JUNIPER_SHUFFLE (0x1064) -#define CKM_JUNIPER_WRAP (0x1065) -#define CKM_FASTHASH (0x1070) -#define CKM_AES_KEY_GEN (0x1080) -#define CKM_AES_ECB (0x1081) -#define CKM_AES_CBC (0x1082) -#define CKM_AES_MAC (0x1083) -#define CKM_AES_MAC_GENERAL (0x1084) -#define CKM_AES_CBC_PAD (0x1085) -#define CKM_DSA_PARAMETER_GEN (0x2000) -#define CKM_DH_PKCS_PARAMETER_GEN (0x2001) -#define CKM_X9_42_DH_PARAMETER_GEN (0x2002) -#define CKM_VENDOR_DEFINED ((unsigned long) (1 << 31)) - - -struct ck_mechanism -{ - ck_mechanism_type_t mechanism; - void *parameter; - unsigned long parameter_len; -}; - - -struct ck_mechanism_info -{ - unsigned long min_key_size; - unsigned long max_key_size; - ck_flags_t flags; -}; - -#define CKF_HW (1 << 0) -#define CKF_ENCRYPT (1 << 8) -#define CKF_DECRYPT (1 << 9) -#define CKF_DIGEST (1 << 10) -#define CKF_SIGN (1 << 11) -#define CKF_SIGN_RECOVER (1 << 12) -#define CKF_VERIFY (1 << 13) -#define CKF_VERIFY_RECOVER (1 << 14) -#define CKF_GENERATE (1 << 15) -#define CKF_GENERATE_KEY_PAIR (1 << 16) -#define CKF_WRAP (1 << 17) -#define CKF_UNWRAP (1 << 18) -#define CKF_DERIVE (1 << 19) -#define CKF_EXTENSION ((unsigned long) (1 << 31)) - - -/* Flags for C_WaitForSlotEvent. */ -#define CKF_DONT_BLOCK (1) - - -typedef unsigned long ck_rv_t; - - -typedef ck_rv_t (*ck_notify_t) (ck_session_handle_t session, - ck_notification_t event, void *application); - -/* Forward reference. */ -struct ck_function_list; - -#define _CK_DECLARE_FUNCTION(name, args) \ -typedef ck_rv_t (*CK_ ## name) args; \ -ck_rv_t CK_SPEC name args - -_CK_DECLARE_FUNCTION (C_Initialize, (void *init_args)); -_CK_DECLARE_FUNCTION (C_Finalize, (void *reserved)); -_CK_DECLARE_FUNCTION (C_GetInfo, (struct ck_info *info)); -_CK_DECLARE_FUNCTION (C_GetFunctionList, - (struct ck_function_list **function_list)); - -_CK_DECLARE_FUNCTION (C_GetSlotList, - (unsigned char token_present, ck_slot_id_t *slot_list, - unsigned long *count)); -_CK_DECLARE_FUNCTION (C_GetSlotInfo, - (ck_slot_id_t slot_id, struct ck_slot_info *info)); -_CK_DECLARE_FUNCTION (C_GetTokenInfo, - (ck_slot_id_t slot_id, struct ck_token_info *info)); -_CK_DECLARE_FUNCTION (C_WaitForSlotEvent, - (ck_flags_t flags, ck_slot_id_t *slot, void *reserved)); -_CK_DECLARE_FUNCTION (C_GetMechanismList, - (ck_slot_id_t slot_id, - ck_mechanism_type_t *mechanism_list, - unsigned long *count)); -_CK_DECLARE_FUNCTION (C_GetMechanismInfo, - (ck_slot_id_t slot_id, ck_mechanism_type_t type, - struct ck_mechanism_info *info)); -_CK_DECLARE_FUNCTION (C_InitToken, - (ck_slot_id_t slot_id, unsigned char *pin, - unsigned long pin_len, unsigned char *label)); -_CK_DECLARE_FUNCTION (C_InitPIN, - (ck_session_handle_t session, unsigned char *pin, - unsigned long pin_len)); -_CK_DECLARE_FUNCTION (C_SetPIN, - (ck_session_handle_t session, unsigned char *old_pin, - unsigned long old_len, unsigned char *new_pin, - unsigned long new_len)); - -_CK_DECLARE_FUNCTION (C_OpenSession, - (ck_slot_id_t slot_id, ck_flags_t flags, - void *application, ck_notify_t notify, - ck_session_handle_t *session)); -_CK_DECLARE_FUNCTION (C_CloseSession, (ck_session_handle_t session)); -_CK_DECLARE_FUNCTION (C_CloseAllSessions, (ck_slot_id_t slot_id)); -_CK_DECLARE_FUNCTION (C_GetSessionInfo, - (ck_session_handle_t session, - struct ck_session_info *info)); -_CK_DECLARE_FUNCTION (C_GetOperationState, - (ck_session_handle_t session, - unsigned char *operation_state, - unsigned long *operation_state_len)); -_CK_DECLARE_FUNCTION (C_SetOperationState, - (ck_session_handle_t session, - unsigned char *operation_state, - unsigned long operation_state_len, - ck_object_handle_t encryption_key, - ck_object_handle_t authentiation_key)); -_CK_DECLARE_FUNCTION (C_Login, - (ck_session_handle_t session, ck_user_type_t user_type, - unsigned char *pin, unsigned long pin_len)); -_CK_DECLARE_FUNCTION (C_Logout, (ck_session_handle_t session)); - -_CK_DECLARE_FUNCTION (C_CreateObject, - (ck_session_handle_t session, - struct ck_attribute *templ, - unsigned long count, ck_object_handle_t *object)); -_CK_DECLARE_FUNCTION (C_CopyObject, - (ck_session_handle_t session, ck_object_handle_t object, - struct ck_attribute *templ, unsigned long count, - ck_object_handle_t *new_object)); -_CK_DECLARE_FUNCTION (C_DestroyObject, - (ck_session_handle_t session, - ck_object_handle_t object)); -_CK_DECLARE_FUNCTION (C_GetObjectSize, - (ck_session_handle_t session, - ck_object_handle_t object, - unsigned long *size)); -_CK_DECLARE_FUNCTION (C_GetAttributeValue, - (ck_session_handle_t session, - ck_object_handle_t object, - struct ck_attribute *templ, - unsigned long count)); -_CK_DECLARE_FUNCTION (C_SetAttributeValue, - (ck_session_handle_t session, - ck_object_handle_t object, - struct ck_attribute *templ, - unsigned long count)); -_CK_DECLARE_FUNCTION (C_FindObjectsInit, - (ck_session_handle_t session, - struct ck_attribute *templ, - unsigned long count)); -_CK_DECLARE_FUNCTION (C_FindObjects, - (ck_session_handle_t session, - ck_object_handle_t *object, - unsigned long max_object_count, - unsigned long *object_count)); -_CK_DECLARE_FUNCTION (C_FindObjectsFinal, - (ck_session_handle_t session)); - -_CK_DECLARE_FUNCTION (C_EncryptInit, - (ck_session_handle_t session, - struct ck_mechanism *mechanism, - ck_object_handle_t key)); -_CK_DECLARE_FUNCTION (C_Encrypt, - (ck_session_handle_t session, - unsigned char *data, unsigned long data_len, - unsigned char *encrypted_data, - unsigned long *encrypted_data_len)); -_CK_DECLARE_FUNCTION (C_EncryptUpdate, - (ck_session_handle_t session, - unsigned char *part, unsigned long part_len, - unsigned char *encrypted_part, - unsigned long *encrypted_part_len)); -_CK_DECLARE_FUNCTION (C_EncryptFinal, - (ck_session_handle_t session, - unsigned char *last_encrypted_part, - unsigned long *last_encrypted_part_len)); - -_CK_DECLARE_FUNCTION (C_DecryptInit, - (ck_session_handle_t session, - struct ck_mechanism *mechanism, - ck_object_handle_t key)); -_CK_DECLARE_FUNCTION (C_Decrypt, - (ck_session_handle_t session, - unsigned char *encrypted_data, - unsigned long encrypted_data_len, - unsigned char *data, unsigned long *data_len)); -_CK_DECLARE_FUNCTION (C_DecryptUpdate, - (ck_session_handle_t session, - unsigned char *encrypted_part, - unsigned long encrypted_part_len, - unsigned char *part, unsigned long *part_len)); -_CK_DECLARE_FUNCTION (C_DecryptFinal, - (ck_session_handle_t session, - unsigned char *last_part, - unsigned long *last_part_len)); - -_CK_DECLARE_FUNCTION (C_DigestInit, - (ck_session_handle_t session, - struct ck_mechanism *mechanism)); -_CK_DECLARE_FUNCTION (C_Digest, - (ck_session_handle_t session, - unsigned char *data, unsigned long data_len, - unsigned char *digest, - unsigned long *digest_len)); -_CK_DECLARE_FUNCTION (C_DigestUpdate, - (ck_session_handle_t session, - unsigned char *part, unsigned long part_len)); -_CK_DECLARE_FUNCTION (C_DigestKey, - (ck_session_handle_t session, ck_object_handle_t key)); -_CK_DECLARE_FUNCTION (C_DigestFinal, - (ck_session_handle_t session, - unsigned char *digest, - unsigned long *digest_len)); - -_CK_DECLARE_FUNCTION (C_SignInit, - (ck_session_handle_t session, - struct ck_mechanism *mechanism, - ck_object_handle_t key)); -_CK_DECLARE_FUNCTION (C_Sign, - (ck_session_handle_t session, - unsigned char *data, unsigned long data_len, - unsigned char *signature, - unsigned long *signature_len)); -_CK_DECLARE_FUNCTION (C_SignUpdate, - (ck_session_handle_t session, - unsigned char *part, unsigned long part_len)); -_CK_DECLARE_FUNCTION (C_SignFinal, - (ck_session_handle_t session, - unsigned char *signature, - unsigned long *signature_len)); -_CK_DECLARE_FUNCTION (C_SignRecoverInit, - (ck_session_handle_t session, - struct ck_mechanism *mechanism, - ck_object_handle_t key)); -_CK_DECLARE_FUNCTION (C_SignRecover, - (ck_session_handle_t session, - unsigned char *data, unsigned long data_len, - unsigned char *signature, - unsigned long *signature_len)); - -_CK_DECLARE_FUNCTION (C_VerifyInit, - (ck_session_handle_t session, - struct ck_mechanism *mechanism, - ck_object_handle_t key)); -_CK_DECLARE_FUNCTION (C_Verify, - (ck_session_handle_t session, - unsigned char *data, unsigned long data_len, - unsigned char *signature, - unsigned long signature_len)); -_CK_DECLARE_FUNCTION (C_VerifyUpdate, - (ck_session_handle_t session, - unsigned char *part, unsigned long part_len)); -_CK_DECLARE_FUNCTION (C_VerifyFinal, - (ck_session_handle_t session, - unsigned char *signature, - unsigned long signature_len)); -_CK_DECLARE_FUNCTION (C_VerifyRecoverInit, - (ck_session_handle_t session, - struct ck_mechanism *mechanism, - ck_object_handle_t key)); -_CK_DECLARE_FUNCTION (C_VerifyRecover, - (ck_session_handle_t session, - unsigned char *signature, - unsigned long signature_len, - unsigned char *data, - unsigned long *data_len)); - -_CK_DECLARE_FUNCTION (C_DigestEncryptUpdate, - (ck_session_handle_t session, - unsigned char *part, unsigned long part_len, - unsigned char *encrypted_part, - unsigned long *encrypted_part_len)); -_CK_DECLARE_FUNCTION (C_DecryptDigestUpdate, - (ck_session_handle_t session, - unsigned char *encrypted_part, - unsigned long encrypted_part_len, - unsigned char *part, - unsigned long *part_len)); -_CK_DECLARE_FUNCTION (C_SignEncryptUpdate, - (ck_session_handle_t session, - unsigned char *part, unsigned long part_len, - unsigned char *encrypted_part, - unsigned long *encrypted_part_len)); -_CK_DECLARE_FUNCTION (C_DecryptVerifyUpdate, - (ck_session_handle_t session, - unsigned char *encrypted_part, - unsigned long encrypted_part_len, - unsigned char *part, - unsigned long *part_len)); - -_CK_DECLARE_FUNCTION (C_GenerateKey, - (ck_session_handle_t session, - struct ck_mechanism *mechanism, - struct ck_attribute *templ, - unsigned long count, - ck_object_handle_t *key)); -_CK_DECLARE_FUNCTION (C_GenerateKeyPair, - (ck_session_handle_t session, - struct ck_mechanism *mechanism, - struct ck_attribute *public_key_template, - unsigned long public_key_attribute_count, - struct ck_attribute *private_key_template, - unsigned long private_key_attribute_count, - ck_object_handle_t *public_key, - ck_object_handle_t *private_key)); -_CK_DECLARE_FUNCTION (C_WrapKey, - (ck_session_handle_t session, - struct ck_mechanism *mechanism, - ck_object_handle_t wrapping_key, - ck_object_handle_t key, - unsigned char *wrapped_key, - unsigned long *wrapped_key_len)); -_CK_DECLARE_FUNCTION (C_UnwrapKey, - (ck_session_handle_t session, - struct ck_mechanism *mechanism, - ck_object_handle_t unwrapping_key, - unsigned char *wrapped_key, - unsigned long wrapped_key_len, - struct ck_attribute *templ, - unsigned long attribute_count, - ck_object_handle_t *key)); -_CK_DECLARE_FUNCTION (C_DeriveKey, - (ck_session_handle_t session, - struct ck_mechanism *mechanism, - ck_object_handle_t base_key, - struct ck_attribute *templ, - unsigned long attribute_count, - ck_object_handle_t *key)); - -_CK_DECLARE_FUNCTION (C_SeedRandom, - (ck_session_handle_t session, unsigned char *seed, - unsigned long seed_len)); -_CK_DECLARE_FUNCTION (C_GenerateRandom, - (ck_session_handle_t session, - unsigned char *random_data, - unsigned long random_len)); - -_CK_DECLARE_FUNCTION (C_GetFunctionStatus, (ck_session_handle_t session)); -_CK_DECLARE_FUNCTION (C_CancelFunction, (ck_session_handle_t session)); - - -struct ck_function_list -{ - struct ck_version version; - CK_C_Initialize C_Initialize; - CK_C_Finalize C_Finalize; - CK_C_GetInfo C_GetInfo; - CK_C_GetFunctionList C_GetFunctionList; - CK_C_GetSlotList C_GetSlotList; - CK_C_GetSlotInfo C_GetSlotInfo; - CK_C_GetTokenInfo C_GetTokenInfo; - CK_C_GetMechanismList C_GetMechanismList; - CK_C_GetMechanismInfo C_GetMechanismInfo; - CK_C_InitToken C_InitToken; - CK_C_InitPIN C_InitPIN; - CK_C_SetPIN C_SetPIN; - CK_C_OpenSession C_OpenSession; - CK_C_CloseSession C_CloseSession; - CK_C_CloseAllSessions C_CloseAllSessions; - CK_C_GetSessionInfo C_GetSessionInfo; - CK_C_GetOperationState C_GetOperationState; - CK_C_SetOperationState C_SetOperationState; - CK_C_Login C_Login; - CK_C_Logout C_Logout; - CK_C_CreateObject C_CreateObject; - CK_C_CopyObject C_CopyObject; - CK_C_DestroyObject C_DestroyObject; - CK_C_GetObjectSize C_GetObjectSize; - CK_C_GetAttributeValue C_GetAttributeValue; - CK_C_SetAttributeValue C_SetAttributeValue; - CK_C_FindObjectsInit C_FindObjectsInit; - CK_C_FindObjects C_FindObjects; - CK_C_FindObjectsFinal C_FindObjectsFinal; - CK_C_EncryptInit C_EncryptInit; - CK_C_Encrypt C_Encrypt; - CK_C_EncryptUpdate C_EncryptUpdate; - CK_C_EncryptFinal C_EncryptFinal; - CK_C_DecryptInit C_DecryptInit; - CK_C_Decrypt C_Decrypt; - CK_C_DecryptUpdate C_DecryptUpdate; - CK_C_DecryptFinal C_DecryptFinal; - CK_C_DigestInit C_DigestInit; - CK_C_Digest C_Digest; - CK_C_DigestUpdate C_DigestUpdate; - CK_C_DigestKey C_DigestKey; - CK_C_DigestFinal C_DigestFinal; - CK_C_SignInit C_SignInit; - CK_C_Sign C_Sign; - CK_C_SignUpdate C_SignUpdate; - CK_C_SignFinal C_SignFinal; - CK_C_SignRecoverInit C_SignRecoverInit; - CK_C_SignRecover C_SignRecover; - CK_C_VerifyInit C_VerifyInit; - CK_C_Verify C_Verify; - CK_C_VerifyUpdate C_VerifyUpdate; - CK_C_VerifyFinal C_VerifyFinal; - CK_C_VerifyRecoverInit C_VerifyRecoverInit; - CK_C_VerifyRecover C_VerifyRecover; - CK_C_DigestEncryptUpdate C_DigestEncryptUpdate; - CK_C_DecryptDigestUpdate C_DecryptDigestUpdate; - CK_C_SignEncryptUpdate C_SignEncryptUpdate; - CK_C_DecryptVerifyUpdate C_DecryptVerifyUpdate; - CK_C_GenerateKey C_GenerateKey; - CK_C_GenerateKeyPair C_GenerateKeyPair; - CK_C_WrapKey C_WrapKey; - CK_C_UnwrapKey C_UnwrapKey; - CK_C_DeriveKey C_DeriveKey; - CK_C_SeedRandom C_SeedRandom; - CK_C_GenerateRandom C_GenerateRandom; - CK_C_GetFunctionStatus C_GetFunctionStatus; - CK_C_CancelFunction C_CancelFunction; - CK_C_WaitForSlotEvent C_WaitForSlotEvent; -}; - - -typedef ck_rv_t (*ck_createmutex_t) (void **mutex); -typedef ck_rv_t (*ck_destroymutex_t) (void *mutex); -typedef ck_rv_t (*ck_lockmutex_t) (void *mutex); -typedef ck_rv_t (*ck_unlockmutex_t) (void *mutex); - - -struct ck_c_initialize_args -{ - ck_createmutex_t create_mutex; - ck_destroymutex_t destroy_mutex; - ck_lockmutex_t lock_mutex; - ck_unlockmutex_t unlock_mutex; - ck_flags_t flags; - void *reserved; -}; - - -#define CKF_LIBRARY_CANT_CREATE_OS_THREADS (1 << 0) -#define CKF_OS_LOCKING_OK (1 << 1) - -#define CKR_OK (0) -#define CKR_CANCEL (1) -#define CKR_HOST_MEMORY (2) -#define CKR_SLOT_ID_INVALID (3) -#define CKR_GENERAL_ERROR (5) -#define CKR_FUNCTION_FAILED (6) -#define CKR_ARGUMENTS_BAD (7) -#define CKR_NO_EVENT (8) -#define CKR_NEED_TO_CREATE_THREADS (9) -#define CKR_CANT_LOCK (0xa) -#define CKR_ATTRIBUTE_READ_ONLY (0x10) -#define CKR_ATTRIBUTE_SENSITIVE (0x11) -#define CKR_ATTRIBUTE_TYPE_INVALID (0x12) -#define CKR_ATTRIBUTE_VALUE_INVALID (0x13) -#define CKR_DATA_INVALID (0x20) -#define CKR_DATA_LEN_RANGE (0x21) -#define CKR_DEVICE_ERROR (0x30) -#define CKR_DEVICE_MEMORY (0x31) -#define CKR_DEVICE_REMOVED (0x32) -#define CKR_ENCRYPTED_DATA_INVALID (0x40) -#define CKR_ENCRYPTED_DATA_LEN_RANGE (0x41) -#define CKR_FUNCTION_CANCELED (0x50) -#define CKR_FUNCTION_NOT_PARALLEL (0x51) -#define CKR_FUNCTION_NOT_SUPPORTED (0x54) -#define CKR_KEY_HANDLE_INVALID (0x60) -#define CKR_KEY_SIZE_RANGE (0x62) -#define CKR_KEY_TYPE_INCONSISTENT (0x63) -#define CKR_KEY_NOT_NEEDED (0x64) -#define CKR_KEY_CHANGED (0x65) -#define CKR_KEY_NEEDED (0x66) -#define CKR_KEY_INDIGESTIBLE (0x67) -#define CKR_KEY_FUNCTION_NOT_PERMITTED (0x68) -#define CKR_KEY_NOT_WRAPPABLE (0x69) -#define CKR_KEY_UNEXTRACTABLE (0x6a) -#define CKR_MECHANISM_INVALID (0x70) -#define CKR_MECHANISM_PARAM_INVALID (0x71) -#define CKR_OBJECT_HANDLE_INVALID (0x82) -#define CKR_OPERATION_ACTIVE (0x90) -#define CKR_OPERATION_NOT_INITIALIZED (0x91) -#define CKR_PIN_INCORRECT (0xa0) -#define CKR_PIN_INVALID (0xa1) -#define CKR_PIN_LEN_RANGE (0xa2) -#define CKR_PIN_EXPIRED (0xa3) -#define CKR_PIN_LOCKED (0xa4) -#define CKR_SESSION_CLOSED (0xb0) -#define CKR_SESSION_COUNT (0xb1) -#define CKR_SESSION_HANDLE_INVALID (0xb3) -#define CKR_SESSION_PARALLEL_NOT_SUPPORTED (0xb4) -#define CKR_SESSION_READ_ONLY (0xb5) -#define CKR_SESSION_EXISTS (0xb6) -#define CKR_SESSION_READ_ONLY_EXISTS (0xb7) -#define CKR_SESSION_READ_WRITE_SO_EXISTS (0xb8) -#define CKR_SIGNATURE_INVALID (0xc0) -#define CKR_SIGNATURE_LEN_RANGE (0xc1) -#define CKR_TEMPLATE_INCOMPLETE (0xd0) -#define CKR_TEMPLATE_INCONSISTENT (0xd1) -#define CKR_TOKEN_NOT_PRESENT (0xe0) -#define CKR_TOKEN_NOT_RECOGNIZED (0xe1) -#define CKR_TOKEN_WRITE_PROTECTED (0xe2) -#define CKR_UNWRAPPING_KEY_HANDLE_INVALID (0xf0) -#define CKR_UNWRAPPING_KEY_SIZE_RANGE (0xf1) -#define CKR_UNWRAPPING_KEY_TYPE_INCONSISTENT (0xf2) -#define CKR_USER_ALREADY_LOGGED_IN (0x100) -#define CKR_USER_NOT_LOGGED_IN (0x101) -#define CKR_USER_PIN_NOT_INITIALIZED (0x102) -#define CKR_USER_TYPE_INVALID (0x103) -#define CKR_USER_ANOTHER_ALREADY_LOGGED_IN (0x104) -#define CKR_USER_TOO_MANY_TYPES (0x105) -#define CKR_WRAPPED_KEY_INVALID (0x110) -#define CKR_WRAPPED_KEY_LEN_RANGE (0x112) -#define CKR_WRAPPING_KEY_HANDLE_INVALID (0x113) -#define CKR_WRAPPING_KEY_SIZE_RANGE (0x114) -#define CKR_WRAPPING_KEY_TYPE_INCONSISTENT (0x115) -#define CKR_RANDOM_SEED_NOT_SUPPORTED (0x120) -#define CKR_RANDOM_NO_RNG (0x121) -#define CKR_DOMAIN_PARAMS_INVALID (0x130) -#define CKR_BUFFER_TOO_SMALL (0x150) -#define CKR_SAVED_STATE_INVALID (0x160) -#define CKR_INFORMATION_SENSITIVE (0x170) -#define CKR_STATE_UNSAVEABLE (0x180) -#define CKR_CRYPTOKI_NOT_INITIALIZED (0x190) -#define CKR_CRYPTOKI_ALREADY_INITIALIZED (0x191) -#define CKR_MUTEX_BAD (0x1a0) -#define CKR_MUTEX_NOT_LOCKED (0x1a1) -#define CKR_FUNCTION_REJECTED (0x200) -#define CKR_VENDOR_DEFINED ((unsigned long) (1 << 31)) - - - -/* Compatibility layer. */ - -#ifdef CRYPTOKI_COMPAT - -#undef CK_DEFINE_FUNCTION -#define CK_DEFINE_FUNCTION(retval, name) retval CK_SPEC name - -/* For NULL. */ -#include - -typedef unsigned char CK_BYTE; -typedef unsigned char CK_CHAR; -typedef unsigned char CK_UTF8CHAR; -typedef unsigned char CK_BBOOL; -typedef unsigned long int CK_ULONG; -typedef long int CK_LONG; -typedef CK_BYTE *CK_BYTE_PTR; -typedef CK_CHAR *CK_CHAR_PTR; -typedef CK_UTF8CHAR *CK_UTF8CHAR_PTR; -typedef CK_ULONG *CK_ULONG_PTR; -typedef void *CK_VOID_PTR; -typedef void **CK_VOID_PTR_PTR; -#define CK_FALSE 0 -#define CK_TRUE 1 -#ifndef CK_DISABLE_TRUE_FALSE -#ifndef FALSE -#define FALSE 0 -#endif -#ifndef TRUE -#define TRUE 1 -#endif -#endif - -typedef struct ck_version CK_VERSION; -typedef struct ck_version *CK_VERSION_PTR; - -typedef struct ck_info CK_INFO; -typedef struct ck_info *CK_INFO_PTR; - -typedef ck_slot_id_t *CK_SLOT_ID_PTR; - -typedef struct ck_slot_info CK_SLOT_INFO; -typedef struct ck_slot_info *CK_SLOT_INFO_PTR; - -typedef struct ck_token_info CK_TOKEN_INFO; -typedef struct ck_token_info *CK_TOKEN_INFO_PTR; - -typedef ck_session_handle_t *CK_SESSION_HANDLE_PTR; - -typedef struct ck_session_info CK_SESSION_INFO; -typedef struct ck_session_info *CK_SESSION_INFO_PTR; - -typedef ck_object_handle_t *CK_OBJECT_HANDLE_PTR; - -typedef ck_object_class_t *CK_OBJECT_CLASS_PTR; - -typedef struct ck_attribute CK_ATTRIBUTE; -typedef struct ck_attribute *CK_ATTRIBUTE_PTR; - -typedef struct ck_date CK_DATE; -typedef struct ck_date *CK_DATE_PTR; - -typedef ck_mechanism_type_t *CK_MECHANISM_TYPE_PTR; - -typedef struct ck_mechanism CK_MECHANISM; -typedef struct ck_mechanism *CK_MECHANISM_PTR; - -typedef struct ck_mechanism_info CK_MECHANISM_INFO; -typedef struct ck_mechanism_info *CK_MECHANISM_INFO_PTR; - -typedef struct ck_function_list CK_FUNCTION_LIST; -typedef struct ck_function_list *CK_FUNCTION_LIST_PTR; -typedef struct ck_function_list **CK_FUNCTION_LIST_PTR_PTR; - -typedef struct ck_c_initialize_args CK_C_INITIALIZE_ARGS; -typedef struct ck_c_initialize_args *CK_C_INITIALIZE_ARGS_PTR; - -#define NULL_PTR NULL - -/* Delete the helper macros defined at the top of the file. */ -#undef ck_flags_t -#undef ck_version - -#undef ck_info -#undef cryptoki_version -#undef manufacturer_id -#undef library_description -#undef library_version - -#undef ck_notification_t -#undef ck_slot_id_t - -#undef ck_slot_info -#undef slot_description -#undef hardware_version -#undef firmware_version - -#undef ck_token_info -#undef serial_number -#undef max_session_count -#undef session_count -#undef max_rw_session_count -#undef rw_session_count -#undef max_pin_len -#undef min_pin_len -#undef total_public_memory -#undef free_public_memory -#undef total_private_memory -#undef free_private_memory -#undef utc_time - -#undef ck_session_handle_t -#undef ck_user_type_t -#undef ck_state_t - -#undef ck_session_info -#undef slot_id -#undef device_error - -#undef ck_object_handle_t -#undef ck_object_class_t -#undef ck_hw_feature_type_t -#undef ck_key_type_t -#undef ck_certificate_type_t -#undef ck_attribute_type_t - -#undef ck_attribute -#undef value -#undef value_len - -#undef ck_date - -#undef ck_mechanism_type_t - -#undef ck_mechanism -#undef parameter -#undef parameter_len - -#undef ck_mechanism_info -#undef min_key_size -#undef max_key_size - -#undef ck_rv_t -#undef ck_notify_t - -#undef ck_function_list - -#undef ck_createmutex_t -#undef ck_destroymutex_t -#undef ck_lockmutex_t -#undef ck_unlockmutex_t - -#undef ck_c_initialize_args -#undef create_mutex -#undef destroy_mutex -#undef lock_mutex -#undef unlock_mutex -#undef reserved - -#endif /* CRYPTOKI_COMPAT */ - - -/* System dependencies. */ -#if defined(_WIN32) || defined(CRYPTOKI_FORCE_WIN32) -#pragma pack(pop, cryptoki) -#endif - -#if defined(__cplusplus) -} -#endif - -#endif /* PKCS11_H */ Index: portfwd.c ================================================================== --- portfwd.c +++ portfwd.c @@ -13,191 +13,269 @@ #endif #ifndef TRUE #define TRUE 1 #endif -struct PFwdPrivate { +struct PortForwarding { const struct plug_function_table *fn; /* the above variable absolutely *must* be the first in this structure */ - void *c; /* (channel) data used by ssh.c */ + struct ssh_channel *c; /* channel structure held by ssh.c */ void *backhandle; /* instance of SSH backend itself */ /* Note that backhandle need not be filled in if c is non-NULL */ Socket s; int throttled, throttle_override; int ready; /* * `dynamic' does double duty. It's set to 0 for an ordinary * forwarded port, and nonzero for SOCKS-style dynamic port - * forwarding; but it also represents the state of the SOCKS - * exchange. + * forwarding; but the nonzero values are also a state machine + * tracking where the SOCKS exchange has got to. */ int dynamic; /* * `hostname' and `port' are the real hostname and port, once - * we know what we're connecting to; they're unused for this - * purpose while conducting a local SOCKS exchange, which means - * we can also use them as a buffer and pointer for reading - * data from the SOCKS client. + * we know what we're connecting to. */ - char hostname[256+8]; + char *hostname; int port; + /* + * `socksbuf' is the buffer we use to accumulate a SOCKS request. + */ + char *socksbuf; + int sockslen, sockssize; /* * When doing dynamic port forwarding, we can receive * connection data before we are actually able to send it; so * we may have to temporarily hold some in a dynamically * allocated buffer here. */ void *buffer; int buflen; }; + +struct PortListener { + const struct plug_function_table *fn; + /* the above variable absolutely *must* be the first in this structure */ + void *backhandle; /* instance of SSH backend itself */ + Socket s; + /* + * `dynamic' is set to 0 for an ordinary forwarded port, and + * nonzero for SOCKS-style dynamic port forwarding. + */ + int dynamic; + /* + * `hostname' and `port' are the real hostname and port, for + * ordinary forwardings. + */ + char *hostname; + int port; +}; + +static struct PortForwarding *new_portfwd_state(void) +{ + struct PortForwarding *pf = snew(struct PortForwarding); + pf->hostname = NULL; + pf->socksbuf = NULL; + pf->sockslen = pf->sockssize = 0; + pf->buffer = NULL; + return pf; +} + +static void free_portfwd_state(struct PortForwarding *pf) +{ + if (!pf) + return; + sfree(pf->hostname); + sfree(pf->socksbuf); + sfree(pf->buffer); + sfree(pf); +} + +static struct PortListener *new_portlistener_state(void) +{ + struct PortListener *pl = snew(struct PortListener); + pl->hostname = NULL; + return pl; +} + +static void free_portlistener_state(struct PortListener *pl) +{ + if (!pl) + return; + sfree(pl->hostname); + sfree(pl); +} static void pfd_log(Plug plug, int type, SockAddr addr, int port, const char *error_msg, int error_code) +{ + /* we have to dump these since we have no interface to logging.c */ +} + +static void pfl_log(Plug plug, int type, SockAddr addr, int port, + const char *error_msg, int error_code) { /* we have to dump these since we have no interface to logging.c */ } static int pfd_closing(Plug plug, const char *error_msg, int error_code, int calling_back) { - struct PFwdPrivate *pr = (struct PFwdPrivate *) plug; - - /* - * We have no way to communicate down the forwarded connection, - * so if an error occurred on the socket, we just ignore it - * and treat it like a proper close. - */ - if (pr->c) - sshfwd_close(pr->c); - pfd_close(pr->s); + struct PortForwarding *pf = (struct PortForwarding *) plug; + + if (error_msg) { + /* + * Socket error. Slam the connection instantly shut. + */ + if (pf->c) { + sshfwd_unclean_close(pf->c, error_msg); + } else { + /* + * We might not have an SSH channel, if a socket error + * occurred during SOCKS negotiation. If not, we must + * clean ourself up without sshfwd_unclean_close's call + * back to pfd_close. + */ + pfd_close(pf); + } + } else { + /* + * Ordinary EOF received on socket. Send an EOF on the SSH + * channel. + */ + if (pf->c) + sshfwd_write_eof(pf->c); + } + + return 1; +} + +static int pfl_closing(Plug plug, const char *error_msg, int error_code, + int calling_back) +{ + struct PortListener *pl = (struct PortListener *) plug; + pfl_terminate(pl); return 1; } static int pfd_receive(Plug plug, int urgent, char *data, int len) { - struct PFwdPrivate *pr = (struct PFwdPrivate *) plug; - if (pr->dynamic) { + struct PortForwarding *pf = (struct PortForwarding *) plug; + if (pf->dynamic) { while (len--) { - /* - * Throughout SOCKS negotiation, "hostname" is re-used as a - * random protocol buffer with "port" storing the length. - */ - if (pr->port >= lenof(pr->hostname)) { - /* Request too long. */ - if ((pr->dynamic >> 12) == 4) { - /* Send back a SOCKS 4 error before closing. */ - char data[8]; - memset(data, 0, sizeof(data)); - data[1] = 91; /* generic `request rejected' */ - sk_write(pr->s, data, 8); - } - pfd_close(pr->s); - return 1; - } - pr->hostname[pr->port++] = *data++; + if (pf->sockslen >= pf->sockssize) { + pf->sockssize = pf->sockslen * 5 / 4 + 256; + pf->socksbuf = sresize(pf->socksbuf, pf->sockssize, char); + } + pf->socksbuf[pf->sockslen++] = *data++; /* * Now check what's in the buffer to see if it's a * valid and complete message in the SOCKS exchange. */ - if ((pr->dynamic == 1 || (pr->dynamic >> 12) == 4) && - pr->hostname[0] == 4) { + if ((pf->dynamic == 1 || (pf->dynamic >> 12) == 4) && + pf->socksbuf[0] == 4) { /* * SOCKS 4. */ - if (pr->dynamic == 1) - pr->dynamic = 0x4000; - if (pr->port < 2) continue;/* don't have command code yet */ - if (pr->hostname[1] != 1) { + if (pf->dynamic == 1) + pf->dynamic = 0x4000; + if (pf->sockslen < 2) + continue; /* don't have command code yet */ + if (pf->socksbuf[1] != 1) { /* Not CONNECT. */ /* Send back a SOCKS 4 error before closing. */ char data[8]; memset(data, 0, sizeof(data)); data[1] = 91; /* generic `request rejected' */ - sk_write(pr->s, data, 8); - pfd_close(pr->s); + sk_write(pf->s, data, 8); + pfd_close(pf); return 1; } - if (pr->port <= 8) continue; /* haven't started user/hostname */ - if (pr->hostname[pr->port-1] != 0) + if (pf->sockslen <= 8) + continue; /* haven't started user/hostname */ + if (pf->socksbuf[pf->sockslen-1] != 0) continue; /* haven't _finished_ user/hostname */ /* * Now we have a full SOCKS 4 request. Check it to * see if it's a SOCKS 4A request. */ - if (pr->hostname[4] == 0 && pr->hostname[5] == 0 && - pr->hostname[6] == 0 && pr->hostname[7] != 0) { + if (pf->socksbuf[4] == 0 && pf->socksbuf[5] == 0 && + pf->socksbuf[6] == 0 && pf->socksbuf[7] != 0) { /* * It's SOCKS 4A. So if we haven't yet * collected the host name, we should continue * waiting for data in order to do so; if we * have, we can go ahead. */ int len; - if (pr->dynamic == 0x4000) { - pr->dynamic = 0x4001; - pr->port = 8; /* reset buffer to overwrite name */ + if (pf->dynamic == 0x4000) { + pf->dynamic = 0x4001; + pf->sockslen = 8; /* reset buffer to overwrite name */ continue; } - pr->hostname[0] = 0; /* reply version code */ - pr->hostname[1] = 90; /* request granted */ - sk_write(pr->s, pr->hostname, 8); - len= pr->port - 8; - pr->port = GET_16BIT_MSB_FIRST(pr->hostname+2); - memmove(pr->hostname, pr->hostname + 8, len); + pf->socksbuf[0] = 0; /* reply version code */ + pf->socksbuf[1] = 90; /* request granted */ + sk_write(pf->s, pf->socksbuf, 8); + len = pf->sockslen - 8; + pf->port = GET_16BIT_MSB_FIRST(pf->socksbuf+2); + pf->hostname = snewn(len+1, char); + pf->hostname[len] = '\0'; + memcpy(pf->hostname, pf->socksbuf + 8, len); goto connect; } else { /* * It's SOCKS 4, which means we should format * the IP address into the hostname string and * then just go. */ - pr->hostname[0] = 0; /* reply version code */ - pr->hostname[1] = 90; /* request granted */ - sk_write(pr->s, pr->hostname, 8); - pr->port = GET_16BIT_MSB_FIRST(pr->hostname+2); - sprintf(pr->hostname, "%d.%d.%d.%d", - (unsigned char)pr->hostname[4], - (unsigned char)pr->hostname[5], - (unsigned char)pr->hostname[6], - (unsigned char)pr->hostname[7]); + pf->socksbuf[0] = 0; /* reply version code */ + pf->socksbuf[1] = 90; /* request granted */ + sk_write(pf->s, pf->socksbuf, 8); + pf->port = GET_16BIT_MSB_FIRST(pf->socksbuf+2); + pf->hostname = dupprintf("%d.%d.%d.%d", + (unsigned char)pf->socksbuf[4], + (unsigned char)pf->socksbuf[5], + (unsigned char)pf->socksbuf[6], + (unsigned char)pf->socksbuf[7]); goto connect; } } - if ((pr->dynamic == 1 || (pr->dynamic >> 12) == 5) && - pr->hostname[0] == 5) { + if ((pf->dynamic == 1 || (pf->dynamic >> 12) == 5) && + pf->socksbuf[0] == 5) { /* * SOCKS 5. */ - if (pr->dynamic == 1) - pr->dynamic = 0x5000; + if (pf->dynamic == 1) + pf->dynamic = 0x5000; - if (pr->dynamic == 0x5000) { + if (pf->dynamic == 0x5000) { int i, method; char data[2]; /* * We're receiving a set of method identifiers. */ - if (pr->port < 2) continue;/* no method count yet */ - if (pr->port < 2 + (unsigned char)pr->hostname[1]) + if (pf->sockslen < 2) + continue; /* no method count yet */ + if (pf->sockslen < 2 + (unsigned char)pf->socksbuf[1]) continue; /* no methods yet */ method = 0xFF; /* invalid */ - for (i = 0; i < (unsigned char)pr->hostname[1]; i++) - if (pr->hostname[2+i] == 0) { + for (i = 0; i < (unsigned char)pf->socksbuf[1]; i++) + if (pf->socksbuf[2+i] == 0) { method = 0;/* no auth */ break; } data[0] = 5; data[1] = method; - sk_write(pr->s, data, 2); - pr->dynamic = 0x5001; - pr->port = 0; /* re-empty the buffer */ + sk_write(pf->s, data, 2); + pf->dynamic = 0x5001; + pf->sockslen = 0; /* re-empty the buffer */ continue; } - if (pr->dynamic == 0x5001) { + if (pf->dynamic == 0x5001) { /* * We're receiving a SOCKS request. */ unsigned char reply[10]; /* SOCKS5 atyp=1 reply */ int atype, alen = 0; @@ -211,53 +289,54 @@ */ memset(reply, 0, lenof(reply)); reply[0] = 5; /* VER */ reply[3] = 1; /* ATYP = 1 (IPv4, 0.0.0.0:0) */ - if (pr->port < 6) continue; - atype = (unsigned char)pr->hostname[3]; + if (pf->sockslen < 6) continue; + atype = (unsigned char)pf->socksbuf[3]; if (atype == 1) /* IPv4 address */ alen = 4; if (atype == 4) /* IPv6 address */ alen = 16; if (atype == 3) /* domain name has leading length */ - alen = 1 + (unsigned char)pr->hostname[4]; - if (pr->port < 6 + alen) continue; - if (pr->hostname[1] != 1 || pr->hostname[2] != 0) { + alen = 1 + (unsigned char)pf->socksbuf[4]; + if (pf->sockslen < 6 + alen) continue; + if (pf->socksbuf[1] != 1 || pf->socksbuf[2] != 0) { /* Not CONNECT or reserved field nonzero - error */ reply[1] = 1; /* generic failure */ - sk_write(pr->s, (char *) reply, lenof(reply)); - pfd_close(pr->s); + sk_write(pf->s, (char *) reply, lenof(reply)); + pfd_close(pf); return 1; } /* * Now we have a viable connect request. Switch * on atype. */ - pr->port = GET_16BIT_MSB_FIRST(pr->hostname+4+alen); + pf->port = GET_16BIT_MSB_FIRST(pf->socksbuf+4+alen); if (atype == 1) { /* REP=0 (success) already */ - sk_write(pr->s, (char *) reply, lenof(reply)); - sprintf(pr->hostname, "%d.%d.%d.%d", - (unsigned char)pr->hostname[4], - (unsigned char)pr->hostname[5], - (unsigned char)pr->hostname[6], - (unsigned char)pr->hostname[7]); + sk_write(pf->s, (char *) reply, lenof(reply)); + pf->hostname = dupprintf("%d.%d.%d.%d", + (unsigned char)pf->socksbuf[4], + (unsigned char)pf->socksbuf[5], + (unsigned char)pf->socksbuf[6], + (unsigned char)pf->socksbuf[7]); goto connect; } else if (atype == 3) { /* REP=0 (success) already */ - sk_write(pr->s, (char *) reply, lenof(reply)); - memmove(pr->hostname, pr->hostname + 5, alen-1); - pr->hostname[alen-1] = '\0'; + sk_write(pf->s, (char *) reply, lenof(reply)); + pf->hostname = snewn(alen, char); + pf->hostname[alen-1] = '\0'; + memcpy(pf->hostname, pf->socksbuf + 5, alen-1); goto connect; } else { /* * Unknown address type. (FIXME: support IPv6!) */ reply[1] = 8; /* atype not supported */ - sk_write(pr->s, (char *) reply, lenof(reply)); - pfd_close(pr->s); + sk_write(pf->s, (char *) reply, lenof(reply)); + pfd_close(pf); return 1; } } } @@ -265,69 +344,75 @@ * If we get here without either having done `continue' * or `goto connect', it must be because there is no * sensible interpretation of what's in our buffer. So * close the connection rudely. */ - pfd_close(pr->s); + pfd_close(pf); return 1; } return 1; /* * We come here when we're ready to make an actual * connection. */ connect: + sfree(pf->socksbuf); + pf->socksbuf = NULL; /* * Freeze the socket until the SSH server confirms the * connection. */ - sk_set_frozen(pr->s, 1); + sk_set_frozen(pf->s, 1); - pr->c = new_sock_channel(pr->backhandle, pr->s); - if (pr->c == NULL) { - pfd_close(pr->s); + pf->c = new_sock_channel(pf->backhandle, pf); + if (pf->c == NULL) { + pfd_close(pf); return 1; } else { /* asks to forward to the specified host/port for this */ - ssh_send_port_open(pr->c, pr->hostname, pr->port, "forwarding"); + ssh_send_port_open(pf->c, pf->hostname, pf->port, "forwarding"); } - pr->dynamic = 0; + pf->dynamic = 0; /* * If there's any data remaining in our current buffer, * save it to be sent on pfd_confirm(). */ if (len > 0) { - pr->buffer = snewn(len, char); - memcpy(pr->buffer, data, len); - pr->buflen = len; + pf->buffer = snewn(len, char); + memcpy(pf->buffer, data, len); + pf->buflen = len; } } - if (pr->ready) { - if (sshfwd_write(pr->c, data, len) > 0) { - pr->throttled = 1; - sk_set_frozen(pr->s, 1); + if (pf->ready) { + if (sshfwd_write(pf->c, data, len) > 0) { + pf->throttled = 1; + sk_set_frozen(pf->s, 1); } } return 1; } static void pfd_sent(Plug plug, int bufsize) { - struct PFwdPrivate *pr = (struct PFwdPrivate *) plug; + struct PortForwarding *pf = (struct PortForwarding *) plug; - if (pr->c) - sshfwd_unthrottle(pr->c, bufsize); + if (pf->c) + sshfwd_unthrottle(pf->c, bufsize); } /* - * Called when receiving a PORT OPEN from the server + * Called when receiving a PORT OPEN from the server to make a + * connection to a destination host. + * + * On success, returns NULL and fills in *pf_ret. On error, returns a + * dynamically allocated error message string. */ -const char *pfd_newconnect(Socket *s, char *hostname, int port, - void *c, const Config *cfg, int addressfamily) +char *pfd_connect(struct PortForwarding **pf_ret, char *hostname,int port, + void *c, Conf *conf, int addressfamily) { static const struct plug_function_table fn_table = { pfd_log, pfd_closing, pfd_receive, @@ -336,221 +421,216 @@ }; SockAddr addr; const char *err; char *dummy_realhost; - struct PFwdPrivate *pr; + struct PortForwarding *pf; /* * Try to find host. */ - addr = name_lookup(hostname, port, &dummy_realhost, cfg, addressfamily); + addr = name_lookup(hostname, port, &dummy_realhost, conf, addressfamily); if ((err = sk_addr_error(addr)) != NULL) { + char *err_ret = dupstr(err); sk_addr_free(addr); - return err; + sfree(dummy_realhost); + return err_ret; } /* * Open socket. */ - pr = snew(struct PFwdPrivate); - pr->buffer = NULL; - pr->fn = &fn_table; - pr->throttled = pr->throttle_override = 0; - pr->ready = 1; - pr->c = c; - pr->backhandle = NULL; /* we shouldn't need this */ - pr->dynamic = 0; - - pr->s = *s = new_connection(addr, dummy_realhost, port, - 0, 1, 0, 0, (Plug) pr, cfg); - if ((err = sk_socket_error(*s)) != NULL) { - sfree(pr); - return err; - } - - sk_set_private_ptr(*s, pr); + pf = *pf_ret = new_portfwd_state(); + pf->fn = &fn_table; + pf->throttled = pf->throttle_override = 0; + pf->ready = 1; + pf->c = c; + pf->backhandle = NULL; /* we shouldn't need this */ + pf->dynamic = 0; + + pf->s = new_connection(addr, dummy_realhost, port, + 0, 1, 0, 0, (Plug) pf, conf); + sfree(dummy_realhost); + if ((err = sk_socket_error(pf->s)) != NULL) { + char *err_ret = dupstr(err); + sk_close(pf->s); + free_portfwd_state(pf); + *pf_ret = NULL; + return err_ret; + } + return NULL; } /* called when someone connects to the local port */ -static int pfd_accepting(Plug p, OSSocket sock) +static int pfl_accepting(Plug p, accept_fn_t constructor, accept_ctx_t ctx) { static const struct plug_function_table fn_table = { pfd_log, pfd_closing, pfd_receive, pfd_sent, NULL }; - struct PFwdPrivate *pr, *org; + struct PortForwarding *pf; + struct PortListener *pl; Socket s; const char *err; - org = (struct PFwdPrivate *)p; - pr = snew(struct PFwdPrivate); - pr->buffer = NULL; - pr->fn = &fn_table; - - pr->c = NULL; - pr->backhandle = org->backhandle; - - pr->s = s = sk_register(sock, (Plug) pr); + pl = (struct PortListener *)p; + pf = new_portfwd_state(); + pf->fn = &fn_table; + + pf->c = NULL; + pf->backhandle = pl->backhandle; + + pf->s = s = constructor(ctx, (Plug) pf); if ((err = sk_socket_error(s)) != NULL) { - sfree(pr); + free_portfwd_state(pf); return err != NULL; } - sk_set_private_ptr(s, pr); - - pr->throttled = pr->throttle_override = 0; - pr->ready = 0; - - if (org->dynamic) { - pr->dynamic = 1; - pr->port = 0; /* "hostname" buffer is so far empty */ + pf->throttled = pf->throttle_override = 0; + pf->ready = 0; + + if (pl->dynamic) { + pf->dynamic = 1; + pf->port = 0; /* "hostname" buffer is so far empty */ sk_set_frozen(s, 0); /* we want to receive SOCKS _now_! */ } else { - pr->dynamic = 0; - strcpy(pr->hostname, org->hostname); - pr->port = org->port; - pr->c = new_sock_channel(org->backhandle, s); + pf->dynamic = 0; + pf->hostname = dupstr(pl->hostname); + pf->port = pl->port; + pf->c = new_sock_channel(pl->backhandle, pf); - if (pr->c == NULL) { - sfree(pr); + if (pf->c == NULL) { + free_portfwd_state(pf); return 1; } else { /* asks to forward to the specified host/port for this */ - ssh_send_port_open(pr->c, pr->hostname, pr->port, "forwarding"); + ssh_send_port_open(pf->c, pf->hostname, pf->port, "forwarding"); } } return 0; } -/* Add a new forwarding from port -> desthost:destport - sets up a listener on the local machine on (srcaddr:)port +/* + * Add a new port-forwarding listener from srcaddr:port -> desthost:destport. + * + * On success, returns NULL and fills in *pl_ret. On error, returns a + * dynamically allocated error message string. */ -const char *pfd_addforward(char *desthost, int destport, char *srcaddr, - int port, void *backhandle, const Config *cfg, - void **sockdata, int address_family) +char *pfl_listen(char *desthost, int destport, char *srcaddr, + int port, void *backhandle, Conf *conf, + struct PortListener **pl_ret, int address_family) { static const struct plug_function_table fn_table = { - pfd_log, - pfd_closing, - pfd_receive, /* should not happen... */ - pfd_sent, /* also should not happen */ - pfd_accepting + pfl_log, + pfl_closing, + NULL, /* recv */ + NULL, /* send */ + pfl_accepting }; const char *err; - struct PFwdPrivate *pr; - Socket s; + struct PortListener *pl; /* * Open socket. */ - pr = snew(struct PFwdPrivate); - pr->buffer = NULL; - pr->fn = &fn_table; - pr->c = NULL; + pl = *pl_ret = new_portlistener_state(); + pl->fn = &fn_table; if (desthost) { - strcpy(pr->hostname, desthost); - pr->port = destport; - pr->dynamic = 0; + pl->hostname = dupstr(desthost); + pl->port = destport; + pl->dynamic = 0; } else - pr->dynamic = 1; - pr->throttled = pr->throttle_override = 0; - pr->ready = 0; - pr->backhandle = backhandle; - - pr->s = s = new_listener(srcaddr, port, (Plug) pr, - !cfg->lport_acceptall, cfg, address_family); - if ((err = sk_socket_error(s)) != NULL) { - sfree(pr); - return err; - } - - sk_set_private_ptr(s, pr); - - *sockdata = (void *)s; + pl->dynamic = 1; + pl->backhandle = backhandle; + + pl->s = new_listener(srcaddr, port, (Plug) pl, + !conf_get_int(conf, CONF_lport_acceptall), + conf, address_family); + if ((err = sk_socket_error(pl->s)) != NULL) { + char *err_ret = dupstr(err); + sk_close(pl->s); + free_portlistener_state(pl); + *pl_ret = NULL; + return err_ret; + } return NULL; } -void pfd_close(Socket s) +void pfd_close(struct PortForwarding *pf) { - struct PFwdPrivate *pr; - - if (!s) + if (!pf) return; - pr = (struct PFwdPrivate *) sk_get_private_ptr(s); - - sfree(pr->buffer); - sfree(pr); - - sk_close(s); + sk_close(pf->s); + free_portfwd_state(pf); } /* * Terminate a listener. */ -void pfd_terminate(void *sv) -{ - pfd_close((Socket)sv); -} - -void pfd_unthrottle(Socket s) -{ - struct PFwdPrivate *pr; - if (!s) - return; - pr = (struct PFwdPrivate *) sk_get_private_ptr(s); - - pr->throttled = 0; - sk_set_frozen(s, pr->throttled || pr->throttle_override); -} - -void pfd_override_throttle(Socket s, int enable) -{ - struct PFwdPrivate *pr; - if (!s) - return; - pr = (struct PFwdPrivate *) sk_get_private_ptr(s); - - pr->throttle_override = enable; - sk_set_frozen(s, pr->throttled || pr->throttle_override); +void pfl_terminate(struct PortListener *pl) +{ + if (!pl) + return; + + sk_close(pl->s); + free_portlistener_state(pl); +} + +void pfd_unthrottle(struct PortForwarding *pf) +{ + if (!pf) + return; + + pf->throttled = 0; + sk_set_frozen(pf->s, pf->throttled || pf->throttle_override); +} + +void pfd_override_throttle(struct PortForwarding *pf, int enable) +{ + if (!pf) + return; + + pf->throttle_override = enable; + sk_set_frozen(pf->s, pf->throttled || pf->throttle_override); } /* * Called to send data down the raw connection. */ -int pfd_send(Socket s, char *data, int len) +int pfd_send(struct PortForwarding *pf, char *data, int len) { - if (s == NULL) + if (pf == NULL) return 0; - return sk_write(s, data, len); + return sk_write(pf->s, data, len); +} + +void pfd_send_eof(struct PortForwarding *pf) +{ + sk_write_eof(pf->s); } - -void pfd_confirm(Socket s) +void pfd_confirm(struct PortForwarding *pf) { - struct PFwdPrivate *pr; - - if (s == NULL) + if (pf == NULL) return; - pr = (struct PFwdPrivate *) sk_get_private_ptr(s); - pr->ready = 1; - sk_set_frozen(s, 0); - sk_write(s, NULL, 0); - if (pr->buffer) { - sshfwd_write(pr->c, pr->buffer, pr->buflen); - sfree(pr->buffer); - pr->buffer = NULL; + pf->ready = 1; + sk_set_frozen(pf->s, 0); + sk_write(pf->s, NULL, 0); + if (pf->buffer) { + sshfwd_write(pf->c, pf->buffer, pf->buflen); + sfree(pf->buffer); + pf->buffer = NULL; } } Index: pproxy.c ================================================================== --- pproxy.c +++ pproxy.c @@ -9,9 +9,9 @@ #include "proxy.h" Socket platform_new_connection(SockAddr addr, char *hostname, int port, int privport, int oobinline, int nodelay, int keepalive, - Plug plug, const Config *cfg) + Plug plug, Conf *conf) { return NULL; } Index: proxy.c ================================================================== --- proxy.c +++ proxy.c @@ -12,13 +12,14 @@ #define DEFINE_PLUG_METHOD_MACROS #include "putty.h" #include "network.h" #include "proxy.h" -#define do_proxy_dns(cfg) \ - (cfg->proxy_dns == FORCE_ON || \ - (cfg->proxy_dns == AUTO && cfg->proxy_type != PROXY_SOCKS4)) +#define do_proxy_dns(conf) \ + (conf_get_int(conf, CONF_proxy_dns) == FORCE_ON || \ + (conf_get_int(conf, CONF_proxy_dns) == AUTO && \ + conf_get_int(conf, CONF_proxy_type) != PROXY_SOCKS4)) /* * Call this when proxy negotiation is complete, so that this * socket can begin working normally. */ @@ -62,10 +63,13 @@ /* if we were asked to flush the output during * the proxy negotiation process, do so now. */ if (p->pending_flush) sk_flush(p->sub_socket); + /* if we have a pending EOF to send, send it */ + if (p->pending_eof) sk_write_eof(p->sub_socket); + /* if the backend wanted the socket unfrozen, try to unfreeze. * our set_frozen handler will flush buffered receive data before * unfreezing the actual underlying socket. */ if (!p->freeze) @@ -113,10 +117,21 @@ bufchain_add(&ps->pending_oob_output_data, data, len); return len; } return sk_write_oob(ps->sub_socket, data, len); } + +static void sk_proxy_write_eof (Socket s) +{ + Proxy_Socket ps = (Proxy_Socket) s; + + if (ps->state != PROXY_STATE_ACTIVE) { + ps->pending_eof = 1; + return; + } + sk_write_eof(ps->sub_socket); +} static void sk_proxy_flush (Socket s) { Proxy_Socket ps = (Proxy_Socket) s; @@ -125,22 +140,10 @@ return; } sk_flush(ps->sub_socket); } -static void sk_proxy_set_private_ptr (Socket s, void *ptr) -{ - Proxy_Socket ps = (Proxy_Socket) s; - sk_set_private_ptr(ps->sub_socket, ptr); -} - -static void * sk_proxy_get_private_ptr (Socket s) -{ - Proxy_Socket ps = (Proxy_Socket) s; - return sk_get_private_ptr(ps->sub_socket); -} - static void sk_proxy_set_frozen (Socket s, int is_frozen) { Proxy_Socket ps = (Proxy_Socket) s; if (ps->state != PROXY_STATE_ACTIVE) { @@ -244,39 +247,50 @@ return; } plug_sent(ps->plug, bufsize); } -static int plug_proxy_accepting (Plug p, OSSocket sock) +static int plug_proxy_accepting(Plug p, + accept_fn_t constructor, accept_ctx_t ctx) { Proxy_Plug pp = (Proxy_Plug) p; Proxy_Socket ps = pp->proxy_socket; if (ps->state != PROXY_STATE_ACTIVE) { - ps->accepting_sock = sock; + ps->accepting_constructor = constructor; + ps->accepting_ctx = ctx; return ps->negotiate(ps, PROXY_CHANGE_ACCEPTING); } - return plug_accepting(ps->plug, sock); + return plug_accepting(ps->plug, constructor, ctx); } /* * This function can accept a NULL pointer as `addr', in which case * it will only check the host name. */ -static int proxy_for_destination (SockAddr addr, char *hostname, int port, - const Config *cfg) +int proxy_for_destination (SockAddr addr, const char *hostname, + int port, Conf *conf) { int s = 0, e = 0; char hostip[64]; int hostip_len, hostname_len; const char *exclude_list; + /* + * Special local connections such as Unix-domain sockets + * unconditionally cannot be proxied, even in proxy-localhost + * mode. There just isn't any way to ask any known proxy type for + * them. + */ + if (addr && sk_address_is_special_local(addr)) + return 0; /* do not proxy */ + /* * Check the host name and IP against the hard-coded * representations of `localhost'. */ - if (!cfg->even_proxy_localhost && + if (!conf_get_int(conf, CONF_even_proxy_localhost) && (sk_hostname_is_local(hostname) || (addr && sk_address_is_local(addr)))) return 0; /* do not proxy */ /* we want a string representation of the IP address for comparisons */ @@ -286,11 +300,11 @@ } else hostip_len = 0; /* placate gcc; shouldn't be required */ hostname_len = strlen(hostname); - exclude_list = cfg->proxy_exclude_list; + exclude_list = conf_get_str(conf, CONF_proxy_exclude_list); /* now parse the exclude list, and see if either our IP * or hostname matches anything in it. */ @@ -347,15 +361,15 @@ /* no matches in the exclude list, so use the proxy */ return 1; } SockAddr name_lookup(char *host, int port, char **canonicalname, - const Config *cfg, int addressfamily) + Conf *conf, int addressfamily) { - if (cfg->proxy_type != PROXY_NONE && - do_proxy_dns(cfg) && - proxy_for_destination(NULL, host, port, cfg)) { + if (conf_get_int(conf, CONF_proxy_type) != PROXY_NONE && + do_proxy_dns(conf) && + proxy_for_destination(NULL, host, port, conf)) { *canonicalname = dupstr(host); return sk_nonamelookup(host); } return sk_namelookup(host, canonicalname, addressfamily); @@ -362,20 +376,19 @@ } Socket new_connection(SockAddr addr, char *hostname, int port, int privport, int oobinline, int nodelay, int keepalive, - Plug plug, const Config *cfg) + Plug plug, Conf *conf) { static const struct socket_function_table socket_fn_table = { sk_proxy_plug, sk_proxy_close, sk_proxy_write, sk_proxy_write_oob, + sk_proxy_write_eof, sk_proxy_flush, - sk_proxy_set_private_ptr, - sk_proxy_get_private_ptr, sk_proxy_set_frozen, sk_proxy_socket_error }; static const struct plug_function_table plug_fn_table = { @@ -384,51 +397,54 @@ plug_proxy_receive, plug_proxy_sent, plug_proxy_accepting }; - if (cfg->proxy_type != PROXY_NONE && - proxy_for_destination(addr, hostname, port, cfg)) + if (conf_get_int(conf, CONF_proxy_type) != PROXY_NONE && + proxy_for_destination(addr, hostname, port, conf)) { Proxy_Socket ret; Proxy_Plug pplug; SockAddr proxy_addr; char *proxy_canonical_name; Socket sret; + int type; if ((sret = platform_new_connection(addr, hostname, port, privport, oobinline, nodelay, keepalive, - plug, cfg)) != + plug, conf)) != NULL) return sret; ret = snew(struct Socket_proxy_tag); ret->fn = &socket_fn_table; - ret->cfg = *cfg; /* STRUCTURE COPY */ + ret->conf = conf_copy(conf); ret->plug = plug; ret->remote_addr = addr; /* will need to be freed on close */ ret->remote_port = port; ret->error = NULL; ret->pending_flush = 0; + ret->pending_eof = 0; ret->freeze = 0; bufchain_init(&ret->pending_input_data); bufchain_init(&ret->pending_output_data); bufchain_init(&ret->pending_oob_output_data); ret->sub_socket = NULL; ret->state = PROXY_STATE_NEW; ret->negotiate = NULL; - - if (cfg->proxy_type == PROXY_HTTP) { + + type = conf_get_int(conf, CONF_proxy_type); + if (type == PROXY_HTTP) { ret->negotiate = proxy_http_negotiate; - } else if (cfg->proxy_type == PROXY_SOCKS4) { + } else if (type == PROXY_SOCKS4) { ret->negotiate = proxy_socks4_negotiate; - } else if (cfg->proxy_type == PROXY_SOCKS5) { + } else if (type == PROXY_SOCKS5) { ret->negotiate = proxy_socks5_negotiate; - } else if (cfg->proxy_type == PROXY_TELNET) { + } else if (type == PROXY_TELNET) { ret->negotiate = proxy_telnet_negotiate; } else { ret->error = "Proxy error: Unknown proxy method"; return (Socket) ret; } @@ -438,22 +454,26 @@ pplug = snew(struct Plug_proxy_tag); pplug->fn = &plug_fn_table; pplug->proxy_socket = ret; /* look-up proxy */ - proxy_addr = sk_namelookup(cfg->proxy_host, - &proxy_canonical_name, cfg->addressfamily); + proxy_addr = sk_namelookup(conf_get_str(conf, CONF_proxy_host), + &proxy_canonical_name, + conf_get_int(conf, CONF_addressfamily)); if (sk_addr_error(proxy_addr) != NULL) { ret->error = "Proxy error: Unable to resolve proxy host name"; + sfree(pplug); + sk_addr_free(proxy_addr); return (Socket)ret; } sfree(proxy_canonical_name); /* create the actual socket we will be using, * connected to our proxy server and port. */ - ret->sub_socket = sk_new(proxy_addr, cfg->proxy_port, + ret->sub_socket = sk_new(proxy_addr, + conf_get_int(conf, CONF_proxy_port), privport, oobinline, nodelay, keepalive, (Plug) pplug); if (sk_socket_error(ret->sub_socket) != NULL) return (Socket) ret; @@ -467,11 +487,11 @@ /* no proxy, so just return the direct socket */ return sk_new(addr, port, privport, oobinline, nodelay, keepalive, plug); } Socket new_listener(char *srcaddr, int port, Plug plug, int local_host_only, - const Config *cfg, int addressfamily) + Conf *conf, int addressfamily) { /* TODO: SOCKS (and potentially others) support inbound * TODO: connections via the proxy. support them. */ @@ -523,30 +543,35 @@ * so we'll send off the initial bits of the request. * for this proxy method, it's just a simple HTTP * request */ char *buf, dest[512]; + char *username, *password; sk_getaddr(p->remote_addr, dest, lenof(dest)); buf = dupprintf("CONNECT %s:%i HTTP/1.1\r\nHost: %s:%i\r\n", dest, p->remote_port, dest, p->remote_port); sk_write(p->sub_socket, buf, strlen(buf)); sfree(buf); - if (p->cfg.proxy_username[0] || p->cfg.proxy_password[0]) { - char buf[sizeof(p->cfg.proxy_username)+sizeof(p->cfg.proxy_password)]; - char buf2[sizeof(buf)*4/3 + 100]; + username = conf_get_str(p->conf, CONF_proxy_username); + password = conf_get_str(p->conf, CONF_proxy_password); + if (username[0] || password[0]) { + char *buf, *buf2; int i, j, len; - sprintf(buf, "%s:%s", p->cfg.proxy_username, p->cfg.proxy_password); + buf = dupprintf("%s:%s", username, password); len = strlen(buf); + buf2 = snewn(len * 4 / 3 + 100, char); sprintf(buf2, "Proxy-Authorization: Basic "); for (i = 0, j = strlen(buf2); i < len; i += 3, j += 4) base64_encode_atom((unsigned char *)(buf+i), (len-i > 3 ? 3 : len-i), buf2+j); strcpy(buf2+j, "\r\n"); sk_write(p->sub_socket, buf2, strlen(buf2)); + sfree(buf); + sfree(buf2); } sk_write(p->sub_socket, "\r\n", 2); p->state = 1; @@ -578,11 +603,12 @@ /* we should _never_ see this, as we are using our socket to * connect to a proxy, not accepting inbound connections. * what should we do? close the socket with an appropriate * error message? */ - return plug_accepting(p->plug, p->accepting_sock); + return plug_accepting(p->plug, + p->accepting_constructor, p->accepting_ctx); } if (change == PROXY_CHANGE_RECEIVE) { /* we have received data from the underlying socket, which * we'll need to parse, process, and respond to appropriately. @@ -709,15 +735,15 @@ * user ID (variable length, null terminated string) */ int length, type, namelen; char *command, addr[4], hostname[512]; + char *username; type = sk_addrtype(p->remote_addr); if (type == ADDRTYPE_IPV6) { - plug_closing(p->plug, "Proxy error: SOCKS version 4 does" - " not support IPv6", PROXY_ERROR_GENERAL, 0); + p->error = "Proxy error: SOCKS version 4 does not support IPv6"; return 1; } else if (type == ADDRTYPE_IPV4) { namelen = 0; sk_addrcopy(p->remote_addr, addr); } else { /* type == ADDRTYPE_NAME */ @@ -726,13 +752,14 @@ namelen = strlen(hostname) + 1; /* include the NUL */ addr[0] = addr[1] = addr[2] = 0; addr[3] = 1; } - length = strlen(p->cfg.proxy_username) + namelen + 9; + username = conf_get_str(p->conf, CONF_proxy_username); + length = strlen(username) + namelen + 9; command = snewn(length, char); - strcpy(command + 8, p->cfg.proxy_username); + strcpy(command + 8, username); command[0] = 4; /* version 4 */ command[1] = 1; /* CONNECT command */ /* port */ @@ -741,14 +768,15 @@ /* address */ memcpy(command + 4, addr, 4); /* hostname */ - memcpy(command + 8 + strlen(p->cfg.proxy_username) + 1, + memcpy(command + 8 + strlen(username) + 1, hostname, namelen); sk_write(p->sub_socket, command, length); + sfree(username); sfree(command); p->state = 1; return 0; } @@ -778,11 +806,12 @@ /* we should _never_ see this, as we are using our socket to * connect to a proxy, not accepting inbound connections. * what should we do? close the socket with an appropriate * error message? */ - return plug_accepting(p->plug, p->accepting_sock); + return plug_accepting(p->plug, + p->accepting_constructor, p->accepting_ctx); } if (change == PROXY_CHANGE_RECEIVE) { /* we have received data from the underlying socket, which * we'll need to parse, process, and respond to appropriately. @@ -866,14 +895,17 @@ * 0x02 = username/password * 0x03 = CHAP */ char command[5]; + char *username, *password; int len; command[0] = 5; /* version 5 */ - if (p->cfg.proxy_username[0] || p->cfg.proxy_password[0]) { + username = conf_get_str(p->conf, CONF_proxy_username); + password = conf_get_str(p->conf, CONF_proxy_password); + if (username[0] || password[0]) { command[2] = 0x00; /* no authentication */ len = 3; proxy_socks5_offerencryptedauth (command, &len); command[len++] = 0x02; /* username/password */ command[1] = len - 2; /* Number of methods supported */ @@ -914,11 +946,12 @@ /* we should _never_ see this, as we are using our socket to * connect to a proxy, not accepting inbound connections. * what should we do? close the socket with an appropriate * error message? */ - return plug_accepting(p->plug, p->accepting_sock); + return plug_accepting(p->plug, + p->accepting_constructor, p->accepting_ctx); } if (change == PROXY_CHANGE_RECEIVE) { /* we have received data from the underlying socket, which * we'll need to parse, process, and respond to appropriately. @@ -1146,22 +1179,24 @@ PROXY_ERROR_GENERAL, 0); return 1; } if (p->state == 5) { - if (p->cfg.proxy_username[0] || p->cfg.proxy_password[0]) { - char userpwbuf[514]; + char *username = conf_get_str(p->conf, CONF_proxy_username); + char *password = conf_get_str(p->conf, CONF_proxy_password); + if (username[0] || password[0]) { + char userpwbuf[255 + 255 + 3]; int ulen, plen; - ulen = strlen(p->cfg.proxy_username); + ulen = strlen(username); if (ulen > 255) ulen = 255; if (ulen < 1) ulen = 1; - plen = strlen(p->cfg.proxy_password); + plen = strlen(password); if (plen > 255) plen = 255; if (plen < 1) plen = 1; userpwbuf[0] = 1; /* version number of subnegotiation */ userpwbuf[1] = ulen; - memcpy(userpwbuf+2, p->cfg.proxy_username, ulen); + memcpy(userpwbuf+2, username, ulen); userpwbuf[ulen+2] = plen; - memcpy(userpwbuf+ulen+3, p->cfg.proxy_password, plen); + memcpy(userpwbuf+ulen+3, password, plen); sk_write(p->sub_socket, userpwbuf, ulen + plen + 3); p->state = 7; } else plug_closing(p->plug, "Proxy error: Server chose " "username/password authentication but we " @@ -1190,12 +1225,13 @@ * telnet port and send a command such as `connect host port'. The * command is configurable, since this proxy type is typically not * standardised or at all well-defined.) */ -char *format_telnet_command(SockAddr addr, int port, const Config *cfg) +char *format_telnet_command(SockAddr addr, int port, Conf *conf) { + char *fmt = conf_get_str(conf, CONF_proxy_telnet_command); char *ret = NULL; int retlen = 0, retsize = 0; int so = 0, eo = 0; #define ENSURE(n) do { \ if (retsize < retlen + n) { \ @@ -1206,42 +1242,41 @@ /* we need to escape \\, \%, \r, \n, \t, \x??, \0???, * %%, %host, %port, %user, and %pass */ - while (cfg->proxy_telnet_command[eo] != 0) { + while (fmt[eo] != 0) { /* scan forward until we hit end-of-line, * or an escape character (\ or %) */ - while (cfg->proxy_telnet_command[eo] != 0 && - cfg->proxy_telnet_command[eo] != '%' && - cfg->proxy_telnet_command[eo] != '\\') eo++; + while (fmt[eo] != 0 && fmt[eo] != '%' && fmt[eo] != '\\') + eo++; /* if we hit eol, break out of our escaping loop */ - if (cfg->proxy_telnet_command[eo] == 0) break; + if (fmt[eo] == 0) break; /* if there was any unescaped text before the escape * character, send that now */ if (eo != so) { ENSURE(eo - so); - memcpy(ret + retlen, cfg->proxy_telnet_command + so, eo - so); + memcpy(ret + retlen, fmt + so, eo - so); retlen += eo - so; } so = eo++; /* if the escape character was the last character of * the line, we'll just stop and send it. */ - if (cfg->proxy_telnet_command[eo] == 0) break; + if (fmt[eo] == 0) break; - if (cfg->proxy_telnet_command[so] == '\\') { + if (fmt[so] == '\\') { /* we recognize \\, \%, \r, \n, \t, \x??. * anything else, we just send unescaped (including the \). */ - switch (cfg->proxy_telnet_command[eo]) { + switch (fmt[eo]) { case '\\': ENSURE(1); ret[retlen++] = '\\'; eo++; @@ -1278,19 +1313,16 @@ unsigned char v = 0; int i = 0; for (;;) { eo++; - if (cfg->proxy_telnet_command[eo] >= '0' && - cfg->proxy_telnet_command[eo] <= '9') - v += cfg->proxy_telnet_command[eo] - '0'; - else if (cfg->proxy_telnet_command[eo] >= 'a' && - cfg->proxy_telnet_command[eo] <= 'f') - v += cfg->proxy_telnet_command[eo] - 'a' + 10; - else if (cfg->proxy_telnet_command[eo] >= 'A' && - cfg->proxy_telnet_command[eo] <= 'F') - v += cfg->proxy_telnet_command[eo] - 'A' + 10; + if (fmt[eo] >= '0' && fmt[eo] <= '9') + v += fmt[eo] - '0'; + else if (fmt[eo] >= 'a' && fmt[eo] <= 'f') + v += fmt[eo] - 'a' + 10; + else if (fmt[eo] >= 'A' && fmt[eo] <= 'F') + v += fmt[eo] - 'A' + 10; else { /* non hex character, so we abort and just * send the whole thing unescaped (including \x) */ ENSURE(1); @@ -1313,11 +1345,11 @@ } break; default: ENSURE(2); - memcpy(ret+retlen, cfg->proxy_telnet_command + so, 2); + memcpy(ret+retlen, fmt + so, 2); retlen += 2; eo++; break; } } else { @@ -1325,64 +1357,62 @@ /* % escape. we recognize %%, %host, %port, %user, %pass. * %proxyhost, %proxyport. Anything else we just send * unescaped (including the %). */ - if (cfg->proxy_telnet_command[eo] == '%') { + if (fmt[eo] == '%') { ENSURE(1); ret[retlen++] = '%'; eo++; } - else if (strnicmp(cfg->proxy_telnet_command + eo, - "host", 4) == 0) { + else if (strnicmp(fmt + eo, "host", 4) == 0) { char dest[512]; int destlen; sk_getaddr(addr, dest, lenof(dest)); destlen = strlen(dest); ENSURE(destlen); memcpy(ret+retlen, dest, destlen); retlen += destlen; eo += 4; } - else if (strnicmp(cfg->proxy_telnet_command + eo, - "port", 4) == 0) { + else if (strnicmp(fmt + eo, "port", 4) == 0) { char portstr[8], portlen; portlen = sprintf(portstr, "%i", port); ENSURE(portlen); memcpy(ret + retlen, portstr, portlen); retlen += portlen; eo += 4; } - else if (strnicmp(cfg->proxy_telnet_command + eo, - "user", 4) == 0) { - int userlen = strlen(cfg->proxy_username); + else if (strnicmp(fmt + eo, "user", 4) == 0) { + char *username = conf_get_str(conf, CONF_proxy_username); + int userlen = strlen(username); ENSURE(userlen); - memcpy(ret+retlen, cfg->proxy_username, userlen); + memcpy(ret+retlen, username, userlen); retlen += userlen; eo += 4; } - else if (strnicmp(cfg->proxy_telnet_command + eo, - "pass", 4) == 0) { - int passlen = strlen(cfg->proxy_password); + else if (strnicmp(fmt + eo, "pass", 4) == 0) { + char *password = conf_get_str(conf, CONF_proxy_password); + int passlen = strlen(password); ENSURE(passlen); - memcpy(ret+retlen, cfg->proxy_password, passlen); + memcpy(ret+retlen, password, passlen); retlen += passlen; eo += 4; } - else if (strnicmp(cfg->proxy_telnet_command + eo, - "proxyhost", 9) == 0) { - int phlen = strlen(cfg->proxy_host); + else if (strnicmp(fmt + eo, "proxyhost", 9) == 0) { + char *host = conf_get_str(conf, CONF_proxy_host); + int phlen = strlen(host); ENSURE(phlen); - memcpy(ret+retlen, cfg->proxy_host, phlen); + memcpy(ret+retlen, host, phlen); retlen += phlen; eo += 9; } - else if (strnicmp(cfg->proxy_telnet_command + eo, - "proxyport", 9) == 0) { + else if (strnicmp(fmt + eo, "proxyport", 9) == 0) { + int port = conf_get_int(conf, CONF_proxy_port); char pport[50]; int pplen; - sprintf(pport, "%d", cfg->proxy_port); + sprintf(pport, "%d", port); pplen = strlen(pport); ENSURE(pplen); memcpy(ret+retlen, pport, pplen); retlen += pplen; eo += 9; @@ -1402,11 +1432,11 @@ } /* if there is any unescaped text at the end of the line, send it */ if (eo != so) { ENSURE(eo - so); - memcpy(ret + retlen, cfg->proxy_telnet_command + so, eo - so); + memcpy(ret + retlen, fmt + so, eo - so); retlen += eo - so; } ENSURE(1); ret[retlen] = '\0'; @@ -1419,11 +1449,11 @@ { if (p->state == PROXY_CHANGE_NEW) { char *formatted_cmd; formatted_cmd = format_telnet_command(p->remote_addr, p->remote_port, - &p->cfg); + p->conf); sk_write(p->sub_socket, formatted_cmd, strlen(formatted_cmd)); sfree(formatted_cmd); p->state = 1; @@ -1455,11 +1485,12 @@ /* we should _never_ see this, as we are using our socket to * connect to a proxy, not accepting inbound connections. * what should we do? close the socket with an appropriate * error message? */ - return plug_accepting(p->plug, p->accepting_sock); + return plug_accepting(p->plug, + p->accepting_constructor, p->accepting_ctx); } if (change == PROXY_CHANGE_RECEIVE) { /* we have received data from the underlying socket, which * we'll need to parse, process, and respond to appropriately. Index: proxy.h ================================================================== --- proxy.h +++ proxy.h @@ -28,10 +28,11 @@ bufchain pending_output_data; bufchain pending_oob_output_data; int pending_flush; bufchain pending_input_data; + int pending_eof; #define PROXY_STATE_NEW -1 #define PROXY_STATE_ACTIVE 0 int state; /* proxy states greater than 0 are implementation @@ -75,14 +76,15 @@ /* sent */ int sent_bufsize; /* accepting */ - OSSocket accepting_sock; + accept_fn_t accepting_constructor; + accept_ctx_t accepting_ctx; /* configuration, used to look up proxy settings */ - Config cfg; + Conf *conf; /* CHAP transient data */ int chap_num_attributes; int chap_num_attributes_processed; int chap_current_attribute; @@ -108,11 +110,11 @@ /* * This may be reused by local-command proxies on individual * platforms. */ -char *format_telnet_command(SockAddr addr, int port, const Config *cfg); +char *format_telnet_command(SockAddr addr, int port, Conf *conf); /* * These are implemented in cproxy.c or nocproxy.c, depending on * whether encrypted proxy authentication is available. */ Index: pscp.c ================================================================== --- pscp.c +++ pscp.c @@ -39,14 +39,16 @@ static int try_scp = 1; static int try_sftp = 1; static int main_cmd_is_sftp = 0; static int fallback_cmd_is_sftp = 0; static int using_sftp = 0; +static int uploading = 0; static Backend *back; static void *backhandle; -static Config cfg; +static Conf *conf; +int sent_eof = FALSE; static void source(char *src); static void rsource(char *src); static void sink(char *targ, char *src); @@ -125,10 +127,23 @@ tell_str(stderr, str2); sfree(str2); errs++; cleanup_exit(1); +} +void nonfatal(char *fmt, ...) +{ + char *str, *str2; + va_list ap; + va_start(ap, fmt); + str = dupvprintf(fmt, ap); + str2 = dupcat("Error: ", str, "\n", NULL); + sfree(str); + va_end(ap); + tell_str(stderr, str2); + sfree(str2); + errs++; } void connection_fatal(void *frontend, char *fmt, ...) { char *str, *str2; va_list ap; @@ -211,10 +226,24 @@ * No "untrusted" output should get here (the way the code is * currently, it's all diverted by FLAG_STDERR). */ assert(!"Unexpected call to from_backend_untrusted()"); return 0; /* not reached */ +} +int from_backend_eof(void *frontend) +{ + /* + * We usually expect to be the party deciding when to close the + * connection, so if we see EOF before we sent it ourselves, we + * should panic. The exception is if we're using old-style scp and + * downloading rather than uploading. + */ + if ((using_sftp || uploading) && !sent_eof) { + connection_fatal(frontend, + "Received unexpected end-of-file from server"); + } + return FALSE; } static int ssh_scp_recv(unsigned char *buf, int len) { outptr = buf; outlen = len; @@ -296,15 +325,39 @@ errs++; if (back != NULL && back->connected(backhandle)) { char ch; back->special(backhandle, TS_EOF); + sent_eof = TRUE; ssh_scp_recv((unsigned char *) &ch, 1); } cleanup_exit(1); } + +/* + * Wait for the reply to a single SFTP request. Parallels the same + * function in psftp.c (but isn't centralised into sftp.c because the + * latter module handles SFTP only and shouldn't assume that SFTP is + * the only thing going on by calling connection_fatal). + */ +struct sftp_packet *sftp_wait_for_reply(struct sftp_request *req) +{ + struct sftp_packet *pktin; + struct sftp_request *rreq; + + sftp_register(req); + pktin = sftp_recv(); + if (pktin == NULL) + connection_fatal(NULL, "did not receive SFTP response packet " + "from server"); + rreq = sftp_find_request(pktin); + if (rreq != req) + connection_fatal(NULL, "unable to understand SFTP response packet " + "from server: %s", fxp_error()); + return pktin; +} /* * Open an SSH connection to user@host and execute cmd. */ static void do_cmd(char *host, char *user, char *cmd) @@ -331,155 +384,166 @@ * If we haven't loaded session details already (e.g., from -load), * try looking for a session called "host". */ if (!loaded_session) { /* Try to load settings for `host' into a temporary config */ - Config cfg2; - cfg2.host[0] = '\0'; - do_defaults(host, &cfg2); - if (cfg2.host[0] != '\0') { + Conf *conf2 = conf_new(); + conf_set_str(conf2, CONF_host, ""); + do_defaults(host, conf2); + if (conf_get_str(conf2, CONF_host)[0] != '\0') { /* Settings present and include hostname */ /* Re-load data into the real config. */ - do_defaults(host, &cfg); + do_defaults(host, conf); } else { /* Session doesn't exist or mention a hostname. */ /* Use `host' as a bare hostname. */ - strncpy(cfg.host, host, sizeof(cfg.host) - 1); - cfg.host[sizeof(cfg.host) - 1] = '\0'; + conf_set_str(conf, CONF_host, host); } } else { /* Patch in hostname `host' to session details. */ - strncpy(cfg.host, host, sizeof(cfg.host) - 1); - cfg.host[sizeof(cfg.host) - 1] = '\0'; + conf_set_str(conf, CONF_host, host); } /* * Force use of SSH. (If they got the protocol wrong we assume the * port is useless too.) */ - if (cfg.protocol != PROT_SSH) { - cfg.protocol = PROT_SSH; - cfg.port = 22; + if (conf_get_int(conf, CONF_protocol) != PROT_SSH) { + conf_set_int(conf, CONF_protocol, PROT_SSH); + conf_set_int(conf, CONF_port, 22); } /* * Enact command-line overrides. */ - cmdline_run_saved(&cfg); - - /* - * Trim leading whitespace off the hostname if it's there. - */ - { - int space = strspn(cfg.host, " \t"); - memmove(cfg.host, cfg.host+space, 1+strlen(cfg.host)-space); - } - - /* See if host is of the form user@host */ - if (cfg.host[0] != '\0') { - char *atsign = strrchr(cfg.host, '@'); - /* Make sure we're not overflowing the user field */ - if (atsign) { - if (atsign - cfg.host < sizeof cfg.username) { - strncpy(cfg.username, cfg.host, atsign - cfg.host); - cfg.username[atsign - cfg.host] = '\0'; - } - memmove(cfg.host, atsign + 1, 1 + strlen(atsign + 1)); - } - } - - /* - * Remove any remaining whitespace from the hostname. - */ - { - int p1 = 0, p2 = 0; - while (cfg.host[p2] != '\0') { - if (cfg.host[p2] != ' ' && cfg.host[p2] != '\t') { - cfg.host[p1] = cfg.host[p2]; - p1++; - } - p2++; - } - cfg.host[p1] = '\0'; + cmdline_run_saved(conf); + + /* + * Muck about with the hostname in various ways. + */ + { + char *hostbuf = dupstr(conf_get_str(conf, CONF_host)); + char *host = hostbuf; + char *p, *q; + + /* + * Trim leading whitespace. + */ + host += strspn(host, " \t"); + + /* + * See if host is of the form user@host, and separate out + * the username if so. + */ + if (host[0] != '\0') { + char *atsign = strrchr(host, '@'); + if (atsign) { + *atsign = '\0'; + conf_set_str(conf, CONF_username, host); + host = atsign + 1; + } + } + + /* + * Remove any remaining whitespace. + */ + p = hostbuf; + q = host; + while (*q) { + if (*q != ' ' && *q != '\t') + *p++ = *q; + q++; + } + *p = '\0'; + + conf_set_str(conf, CONF_host, hostbuf); + sfree(hostbuf); } /* Set username */ if (user != NULL && user[0] != '\0') { - strncpy(cfg.username, user, sizeof(cfg.username) - 1); - cfg.username[sizeof(cfg.username) - 1] = '\0'; - } else if (cfg.username[0] == '\0') { + conf_set_str(conf, CONF_username, user); + } else if (conf_get_str(conf, CONF_username)[0] == '\0') { user = get_username(); if (!user) bump("Empty user name"); else { if (verbose) tell_user(stderr, "Guessing user name: %s", user); - strncpy(cfg.username, user, sizeof(cfg.username) - 1); - cfg.username[sizeof(cfg.username) - 1] = '\0'; + conf_set_str(conf, CONF_username, user); sfree(user); } } /* * Disable scary things which shouldn't be enabled for simple * things like SCP and SFTP: agent forwarding, port forwarding, * X forwarding. */ - cfg.x11_forward = 0; - cfg.agentfwd = 0; - cfg.portfwd[0] = cfg.portfwd[1] = '\0'; - cfg.ssh_simple = TRUE; + conf_set_int(conf, CONF_x11_forward, 0); + conf_set_int(conf, CONF_agentfwd, 0); + conf_set_int(conf, CONF_ssh_simple, TRUE); + { + char *key; + while ((key = conf_get_str_nthstrkey(conf, CONF_portfwd, 0)) != NULL) + conf_del_str_str(conf, CONF_portfwd, key); + } /* * Set up main and possibly fallback command depending on * options specified by user. * Attempt to start the SFTP subsystem as a first choice, * falling back to the provided scp command if that fails. */ - cfg.remote_cmd_ptr2 = NULL; + conf_set_str(conf, CONF_remote_cmd2, ""); if (try_sftp) { /* First choice is SFTP subsystem. */ main_cmd_is_sftp = 1; - strcpy(cfg.remote_cmd, "sftp"); - cfg.ssh_subsys = TRUE; + conf_set_str(conf, CONF_remote_cmd, "sftp"); + conf_set_int(conf, CONF_ssh_subsys, TRUE); if (try_scp) { /* Fallback is to use the provided scp command. */ fallback_cmd_is_sftp = 0; - cfg.remote_cmd_ptr2 = cmd; - cfg.ssh_subsys2 = FALSE; + conf_set_str(conf, CONF_remote_cmd2, cmd); + conf_set_int(conf, CONF_ssh_subsys2, FALSE); } else { /* Since we're not going to try SCP, we may as well try * harder to find an SFTP server, since in the current * implementation we have a spare slot. */ fallback_cmd_is_sftp = 1; /* see psftp.c for full explanation of this kludge */ - cfg.remote_cmd_ptr2 = - "test -x /usr/lib/sftp-server && exec /usr/lib/sftp-server\n" - "test -x /usr/local/lib/sftp-server && exec /usr/local/lib/sftp-server\n" - "exec sftp-server"; - cfg.ssh_subsys2 = FALSE; + conf_set_str(conf, CONF_remote_cmd2, + "test -x /usr/lib/sftp-server &&" + " exec /usr/lib/sftp-server\n" + "test -x /usr/local/lib/sftp-server &&" + " exec /usr/local/lib/sftp-server\n" + "exec sftp-server"); + conf_set_int(conf, CONF_ssh_subsys2, FALSE); } } else { /* Don't try SFTP at all; just try the scp command. */ main_cmd_is_sftp = 0; - cfg.remote_cmd_ptr = cmd; - cfg.ssh_subsys = FALSE; + conf_set_str(conf, CONF_remote_cmd, cmd); + conf_set_int(conf, CONF_ssh_subsys, FALSE); } - cfg.nopty = TRUE; + conf_set_int(conf, CONF_nopty, TRUE); back = &ssh_backend; - err = back->init(NULL, &backhandle, &cfg, cfg.host, cfg.port, &realhost, - 0, cfg.tcp_keepalives); + err = back->init(NULL, &backhandle, conf, + conf_get_str(conf, CONF_host), + conf_get_int(conf, CONF_port), + &realhost, 0, + conf_get_int(conf, CONF_tcp_keepalives)); if (err != NULL) bump("ssh_init: %s", err); - logctx = log_init(NULL, &cfg); + logctx = log_init(NULL, conf); back->provide_logctx(backhandle, logctx); console_provide_logctx(logctx); ssh_scp_init(); if (verbose && realhost != NULL && errs == 0) - tell_user(stderr, "Connected to %s\n", realhost); + tell_user(stderr, "Connected to %s", realhost); sfree(realhost); } /* * Update statistic information about current file. @@ -624,11 +688,11 @@ bump("Protocol error: Lost connection"); rbuf[p++] = ch; } while (p < sizeof(rbuf) && ch != '\n'); rbuf[p - 1] = '\0'; if (resp == 1) - tell_user(stderr, "%s\n", rbuf); + tell_user(stderr, "%s", rbuf); else bump("%s", rbuf); errs++; return (-1); } @@ -657,11 +721,11 @@ { struct fxp_handle *dirh; struct fxp_names *names; struct fxp_name *ournames; struct sftp_packet *pktin; - struct sftp_request *req, *rreq; + struct sftp_request *req; int nnames, namesize; int i; if (!fxp_init()) { tell_user(stderr, "unable to initialise SFTP: %s", fxp_error()); @@ -669,27 +733,25 @@ return; } printf("Listing directory %s\n", dirname); - sftp_register(req = fxp_opendir_send(dirname)); - rreq = sftp_find_request(pktin = sftp_recv()); - assert(rreq == req); - dirh = fxp_opendir_recv(pktin, rreq); + req = fxp_opendir_send(dirname); + pktin = sftp_wait_for_reply(req); + dirh = fxp_opendir_recv(pktin, req); if (dirh == NULL) { printf("Unable to open %s: %s\n", dirname, fxp_error()); } else { nnames = namesize = 0; ournames = NULL; while (1) { - sftp_register(req = fxp_readdir_send(dirh)); - rreq = sftp_find_request(pktin = sftp_recv()); - assert(rreq == req); - names = fxp_readdir_recv(pktin, rreq); + req = fxp_readdir_send(dirh); + pktin = sftp_wait_for_reply(req); + names = fxp_readdir_recv(pktin, req); if (names == NULL) { if (fxp_error_type() == SSH_FX_EOF) break; printf("Reading directory %s: %s\n", dirname, fxp_error()); @@ -708,26 +770,28 @@ for (i = 0; i < names->nnames; i++) ournames[nnames++] = names->names[i]; names->nnames = 0; /* prevent free_names */ fxp_free_names(names); } - sftp_register(req = fxp_close_send(dirh)); - rreq = sftp_find_request(pktin = sftp_recv()); - assert(rreq == req); - fxp_close_recv(pktin, rreq); + req = fxp_close_send(dirh); + pktin = sftp_wait_for_reply(req); + fxp_close_recv(pktin, req); /* * Now we have our filenames. Sort them by actual file * name, and then output the longname parts. */ - qsort(ournames, nnames, sizeof(*ournames), sftp_ls_compare); + if (nnames > 0) + qsort(ournames, nnames, sizeof(*ournames), sftp_ls_compare); /* * And print them. */ for (i = 0; i < nnames; i++) printf("%s\n", ournames[i].longname); + + sfree(ournames); } } /* ---------------------------------------------------------------------- * Helper routines that contain the actual SCP protocol elements, @@ -758,24 +822,23 @@ /* * Find out whether the target filespec is in fact a * directory. */ struct sftp_packet *pktin; - struct sftp_request *req, *rreq; + struct sftp_request *req; struct fxp_attrs attrs; int ret; if (!fxp_init()) { tell_user(stderr, "unable to initialise SFTP: %s", fxp_error()); errs++; return 1; } - sftp_register(req = fxp_stat_send(target)); - rreq = sftp_find_request(pktin = sftp_recv()); - assert(rreq == req); - ret = fxp_stat_recv(pktin, rreq, &attrs); + req = fxp_stat_send(target); + pktin = sftp_wait_for_reply(req); + ret = fxp_stat_recv(pktin, req, &attrs); if (!ret || !(attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS)) scp_sftp_targetisdir = 0; else scp_sftp_targetisdir = (attrs.permissions & 0040000) != 0; @@ -817,32 +880,37 @@ back->send(backhandle, buf, strlen(buf)); return response(); } } -int scp_send_filename(char *name, uint64 size, int modes) +int scp_send_filename(char *name, uint64 size, int permissions) { if (using_sftp) { char *fullname; struct sftp_packet *pktin; - struct sftp_request *req, *rreq; + struct sftp_request *req; + struct fxp_attrs attrs; if (scp_sftp_targetisdir) { fullname = dupcat(scp_sftp_remotepath, "/", name, NULL); } else { fullname = dupstr(scp_sftp_remotepath); } - sftp_register(req = fxp_open_send(fullname, SSH_FXF_WRITE | - SSH_FXF_CREAT | SSH_FXF_TRUNC)); - rreq = sftp_find_request(pktin = sftp_recv()); - assert(rreq == req); - scp_sftp_filehandle = fxp_open_recv(pktin, rreq); + attrs.flags = 0; + PUT_PERMISSIONS(attrs, permissions); + + req = fxp_open_send(fullname, + SSH_FXF_WRITE | SSH_FXF_CREAT | SSH_FXF_TRUNC, + &attrs); + pktin = sftp_wait_for_reply(req); + scp_sftp_filehandle = fxp_open_recv(pktin, req); if (!scp_sftp_filehandle) { tell_user(stderr, "pscp: unable to open %s: %s", fullname, fxp_error()); + sfree(fullname); errs++; return 1; } scp_sftp_fileoffset = uint64_make(0, 0); scp_sftp_xfer = xfer_upload_init(scp_sftp_filehandle, @@ -851,11 +919,13 @@ return 0; } else { char buf[40]; char sizestr[40]; uint64_decimal(size, sizestr); - sprintf(buf, "C%04o %s ", modes, sizestr); + if (permissions < 0) + permissions = 0644; + sprintf(buf, "C%04o %s ", (int)(permissions & 07777), sizestr); back->send(backhandle, buf, strlen(buf)); back->send(backhandle, name, strlen(name)); back->send(backhandle, "\n", 1); return response(); } @@ -872,12 +942,14 @@ } while (!xfer_upload_ready(scp_sftp_xfer)) { pktin = sftp_recv(); ret = xfer_upload_gotpkt(scp_sftp_xfer, pktin); - if (!ret) { - tell_user(stderr, "error while writing: %s\n", fxp_error()); + if (ret <= 0) { + tell_user(stderr, "error while writing: %s", fxp_error()); + if (ret == INT_MIN) /* pktin not even freed */ + sfree(pktin); errs++; return 1; } } @@ -907,16 +979,23 @@ int scp_send_finish(void) { if (using_sftp) { struct fxp_attrs attrs; struct sftp_packet *pktin; - struct sftp_request *req, *rreq; + struct sftp_request *req; int ret; while (!xfer_done(scp_sftp_xfer)) { pktin = sftp_recv(); - xfer_upload_gotpkt(scp_sftp_xfer, pktin); + ret = xfer_upload_gotpkt(scp_sftp_xfer, pktin); + if (ret <= 0) { + tell_user(stderr, "error while writing: %s", fxp_error()); + if (ret == INT_MIN) /* pktin not even freed */ + sfree(pktin); + errs++; + return 1; + } } xfer_cleanup(scp_sftp_xfer); if (!scp_sftp_filehandle) { return 1; @@ -923,23 +1002,21 @@ } if (scp_has_times) { attrs.flags = SSH_FILEXFER_ATTR_ACMODTIME; attrs.atime = scp_sftp_atime; attrs.mtime = scp_sftp_mtime; - sftp_register(req = fxp_fsetstat_send(scp_sftp_filehandle, attrs)); - rreq = sftp_find_request(pktin = sftp_recv()); - assert(rreq == req); - ret = fxp_fsetstat_recv(pktin, rreq); + req = fxp_fsetstat_send(scp_sftp_filehandle, attrs); + pktin = sftp_wait_for_reply(req); + ret = fxp_fsetstat_recv(pktin, req); if (!ret) { - tell_user(stderr, "unable to set file times: %s\n", fxp_error()); + tell_user(stderr, "unable to set file times: %s", fxp_error()); errs++; } } - sftp_register(req = fxp_close_send(scp_sftp_filehandle)); - rreq = sftp_find_request(pktin = sftp_recv()); - assert(rreq == req); - fxp_close_recv(pktin, rreq); + req = fxp_close_send(scp_sftp_filehandle); + pktin = sftp_wait_for_reply(req); + fxp_close_recv(pktin, req); scp_has_times = 0; return 0; } else { back->send(backhandle, "", 1); return response(); @@ -965,11 +1042,11 @@ if (using_sftp) { char *fullname; char const *err; struct fxp_attrs attrs; struct sftp_packet *pktin; - struct sftp_request *req, *rreq; + struct sftp_request *req; int ret; if (scp_sftp_targetisdir) { fullname = dupcat(scp_sftp_remotepath, "/", name, NULL); } else { @@ -981,29 +1058,28 @@ * directory, because if it exists already it's OK just to * use it. Instead, we will stat it afterwards, and if it * exists and is a directory we will assume we were either * successful or it didn't matter. */ - sftp_register(req = fxp_mkdir_send(fullname)); - rreq = sftp_find_request(pktin = sftp_recv()); - assert(rreq == req); - ret = fxp_mkdir_recv(pktin, rreq); + req = fxp_mkdir_send(fullname); + pktin = sftp_wait_for_reply(req); + ret = fxp_mkdir_recv(pktin, req); if (!ret) err = fxp_error(); else err = "server reported no error"; - sftp_register(req = fxp_stat_send(fullname)); - rreq = sftp_find_request(pktin = sftp_recv()); - assert(rreq == req); - ret = fxp_stat_recv(pktin, rreq, &attrs); + req = fxp_stat_send(fullname); + pktin = sftp_wait_for_reply(req); + ret = fxp_stat_recv(pktin, req, &attrs); if (!ret || !(attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS) || !(attrs.permissions & 0040000)) { tell_user(stderr, "unable to create directory %s: %s", fullname, err); + sfree(fullname); errs++; return 1; } scp_sftp_remotepath = fullname; @@ -1055,10 +1131,13 @@ */ newsource = snewn(1+strlen(source), char); if (!wc_unescape(newsource, source)) { /* Yes, here we go; it's a wildcard. Bah. */ char *dupsource, *lastpart, *dirpart, *wildcard; + + sfree(newsource); + dupsource = dupstr(source); lastpart = stripslashes(dupsource, 0); wildcard = dupstr(lastpart); *lastpart = '\0'; if (*dupsource && dupsource[1]) { @@ -1131,11 +1210,11 @@ #define SCP_SINK_RETRY 4 /* not an action; just try again */ struct scp_sink_action { int action; /* FILE, DIR, ENDDIR */ char *buf; /* will need freeing after use */ char *name; /* filename or dirname (not ENDDIR) */ - int mode; /* access mode (not ENDDIR) */ + long permissions; /* access permissions (not ENDDIR) */ uint64 size; /* file size (not ENDDIR) */ int settime; /* 1 if atime and mtime are filled */ unsigned long atime, mtime; /* access times for the file */ }; @@ -1144,11 +1223,11 @@ if (using_sftp) { char *fname; int must_free_fname; struct fxp_attrs attrs; struct sftp_packet *pktin; - struct sftp_request *req, *rreq; + struct sftp_request *req; int ret; if (!scp_sftp_dirstack_head) { if (!scp_sftp_donethistarget) { /* @@ -1213,18 +1292,18 @@ /* * Now we have a filename. Stat it, and see if it's a file * or a directory. */ - sftp_register(req = fxp_stat_send(fname)); - rreq = sftp_find_request(pktin = sftp_recv()); - assert(rreq == req); - ret = fxp_stat_recv(pktin, rreq, &attrs); + req = fxp_stat_send(fname); + pktin = sftp_wait_for_reply(req); + ret = fxp_stat_recv(pktin, req, &attrs); if (!ret || !(attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS)) { tell_user(stderr, "unable to identify %s: %s", fname, ret ? "file type not supplied" : fxp_error()); + if (must_free_fname) sfree(fname); errs++; return 1; } if (attrs.permissions & 0040000) { @@ -1269,17 +1348,16 @@ * If targetisdir is _already_ set (meaning we're * already in the middle of going through another such * list), we must push the other (target,namelist) pair * on a stack. */ - sftp_register(req = fxp_opendir_send(fname)); - rreq = sftp_find_request(pktin = sftp_recv()); - assert(rreq == req); - dirhandle = fxp_opendir_recv(pktin, rreq); + req = fxp_opendir_send(fname); + pktin = sftp_wait_for_reply(req); + dirhandle = fxp_opendir_recv(pktin, req); if (!dirhandle) { - tell_user(stderr, "scp: unable to open directory %s: %s", + tell_user(stderr, "pscp: unable to open directory %s: %s", fname, fxp_error()); if (must_free_fname) sfree(fname); errs++; return 1; } @@ -1286,20 +1364,24 @@ nnames = namesize = 0; ournames = NULL; while (1) { int i; - sftp_register(req = fxp_readdir_send(dirhandle)); - rreq = sftp_find_request(pktin = sftp_recv()); - assert(rreq == req); - names = fxp_readdir_recv(pktin, rreq); + req = fxp_readdir_send(dirhandle); + pktin = sftp_wait_for_reply(req); + names = fxp_readdir_recv(pktin, req); if (names == NULL) { if (fxp_error_type() == SSH_FX_EOF) break; - tell_user(stderr, "scp: reading directory %s: %s\n", + tell_user(stderr, "pscp: reading directory %s: %s", fname, fxp_error()); + + req = fxp_close_send(dirhandle); + pktin = sftp_wait_for_reply(req); + fxp_close_recv(pktin, req); + if (must_free_fname) sfree(fname); sfree(ournames); errs++; return 1; } @@ -1319,22 +1401,21 @@ * reading a directory, and aren't worth * complaining about. */ } else if (!vet_filename(names->names[i].filename)) { tell_user(stderr, "ignoring potentially dangerous server-" - "supplied filename '%s'\n", + "supplied filename '%s'", names->names[i].filename); } else ournames[nnames++] = names->names[i]; } names->nnames = 0; /* prevent free_names */ fxp_free_names(names); } - sftp_register(req = fxp_close_send(dirhandle)); - rreq = sftp_find_request(pktin = sftp_recv()); - assert(rreq == req); - fxp_close_recv(pktin, rreq); + req = fxp_close_send(dirhandle); + pktin = sftp_wait_for_reply(req); + fxp_close_recv(pktin, req); newitem = snew(struct scp_sftp_dirstack); newitem->next = scp_sftp_dirstack_head; newitem->names = ournames; newitem->namepos = 0; @@ -1357,11 +1438,11 @@ } else { act->action = SCP_SINK_DIR; act->buf = dupstr(stripslashes(fname, 0)); act->name = act->buf; act->size = uint64_make(0,0); /* duhh, it's a directory */ - act->mode = 07777 & attrs.permissions; + act->permissions = 07777 & attrs.permissions; if (scp_sftp_preserve && (attrs.flags & SSH_FILEXFER_ATTR_ACMODTIME)) { act->atime = attrs.atime; act->mtime = attrs.mtime; act->settime = 1; @@ -1379,11 +1460,11 @@ act->name = act->buf; if (attrs.flags & SSH_FILEXFER_ATTR_SIZE) { act->size = attrs.size; } else act->size = uint64_make(ULONG_MAX,ULONG_MAX); /* no idea */ - act->mode = 07777 & attrs.permissions; + act->permissions = 07777 & attrs.permissions; if (scp_sftp_preserve && (attrs.flags & SSH_FILEXFER_ATTR_ACMODTIME)) { act->atime = attrs.atime; act->mtime = attrs.mtime; act->settime = 1; @@ -1423,11 +1504,11 @@ act->buf[i++] = ch; } while (ch != '\n'); act->buf[i - 1] = '\0'; switch (action) { case '\01': /* error */ - tell_user(stderr, "%s\n", act->buf); + tell_user(stderr, "%s", act->buf); errs++; continue; /* go round again */ case '\02': /* fatal error */ bump("%s", act->buf); case 'E': @@ -1461,11 +1542,12 @@ * SCP_SINK_DIR. */ { char sizestr[40]; - if (sscanf(act->buf, "%o %s %n", &act->mode, sizestr, &i) != 2) + if (sscanf(act->buf, "%lo %s %n", &act->permissions, + sizestr, &i) != 2) bump("Protocol error: Illegal file descriptor format"); act->size = uint64_from_decimal(sizestr); act->name = act->buf + i; return 0; } @@ -1474,16 +1556,15 @@ int scp_accept_filexfer(void) { if (using_sftp) { struct sftp_packet *pktin; - struct sftp_request *req, *rreq; + struct sftp_request *req; - sftp_register(req = fxp_open_send(scp_sftp_currentname, SSH_FXF_READ)); - rreq = sftp_find_request(pktin = sftp_recv()); - assert(rreq == req); - scp_sftp_filehandle = fxp_open_recv(pktin, rreq); + req = fxp_open_send(scp_sftp_currentname, SSH_FXF_READ, NULL); + pktin = sftp_wait_for_reply(req); + scp_sftp_filehandle = fxp_open_recv(pktin, req); if (!scp_sftp_filehandle) { tell_user(stderr, "pscp: unable to open %s: %s", scp_sftp_currentname, fxp_error()); errs++; @@ -1508,13 +1589,14 @@ void *vbuf; xfer_download_queue(scp_sftp_xfer); pktin = sftp_recv(); ret = xfer_download_gotpkt(scp_sftp_xfer, pktin); - - if (ret < 0) { + if (ret <= 0) { tell_user(stderr, "pscp: error while reading: %s", fxp_error()); + if (ret == INT_MIN) /* pktin not even freed */ + sfree(pktin); errs++; return -1; } if (xfer_download_data(scp_sftp_xfer, &vbuf, &actuallen)) { @@ -1540,33 +1622,39 @@ int scp_finish_filerecv(void) { if (using_sftp) { struct sftp_packet *pktin; - struct sftp_request *req, *rreq; + struct sftp_request *req; /* * Ensure that xfer_done() will work correctly, so we can * clean up any outstanding requests from the file * transfer. */ xfer_set_error(scp_sftp_xfer); while (!xfer_done(scp_sftp_xfer)) { void *vbuf; - int len; + int ret, len; pktin = sftp_recv(); - xfer_download_gotpkt(scp_sftp_xfer, pktin); + ret = xfer_download_gotpkt(scp_sftp_xfer, pktin); + if (ret <= 0) { + tell_user(stderr, "pscp: error while reading: %s", fxp_error()); + if (ret == INT_MIN) /* pktin not even freed */ + sfree(pktin); + errs++; + return -1; + } if (xfer_download_data(scp_sftp_xfer, &vbuf, &len)) sfree(vbuf); } xfer_cleanup(scp_sftp_xfer); - sftp_register(req = fxp_close_send(scp_sftp_filehandle)); - rreq = sftp_find_request(pktin = sftp_recv()); - assert(rreq == req); - fxp_close_recv(pktin, rreq); + req = fxp_close_send(scp_sftp_filehandle); + pktin = sftp_wait_for_reply(req); + fxp_close_recv(pktin, req); return 0; } else { back->send(backhandle, "", 1); return response(); } @@ -1581,11 +1669,11 @@ char *str, *str2; va_list ap; va_start(ap, fmt); errs++; str = dupvprintf(fmt, ap); - str2 = dupcat("scp: ", str, "\n", NULL); + str2 = dupcat("pscp: ", str, "\n", NULL); sfree(str); scp_send_errmsg(str2); tell_user(stderr, "%s", str2); va_end(ap); sfree(str2); @@ -1596,10 +1684,11 @@ */ static void source(char *src) { uint64 size; unsigned long mtime, atime; + long permissions; char *last; RFile *f; int attr; uint64 i; uint64 stat_bytes; @@ -1643,27 +1732,31 @@ if (strrchr(last, '\\') != NULL) last = strrchr(last, '\\') + 1; if (last == src && strchr(src, ':') != NULL) last = strchr(src, ':') + 1; - f = open_existing_file(src, &size, &mtime, &atime); + f = open_existing_file(src, &size, &mtime, &atime, &permissions); if (f == NULL) { run_err("%s: Cannot open file", src); return; } if (preserve) { - if (scp_send_filetimes(mtime, atime)) + if (scp_send_filetimes(mtime, atime)) { + close_rfile(f); return; + } } if (verbose) { char sizestr[40]; uint64_decimal(size, sizestr); tell_user(stderr, "Sending file %s, size=%s", last, sizestr); } - if (scp_send_filename(last, size, 0644)) + if (scp_send_filename(last, size, permissions)) { + close_rfile(f); return; + } stat_bytes = uint64_make(0,0); stat_starttime = time(NULL); stat_lasttime = 0; @@ -1862,31 +1955,38 @@ exists = (attr != FILE_TYPE_NONEXISTENT); if (act.action == SCP_SINK_DIR) { if (exists && attr != FILE_TYPE_DIRECTORY) { run_err("%s: Not a directory", destfname); + sfree(destfname); continue; } if (!exists) { if (!create_directory(destfname)) { run_err("%s: Cannot create directory", destfname); + sfree(destfname); continue; } } sink(destfname, NULL); /* can we set the timestamp for directories ? */ + sfree(destfname); continue; } - f = open_new_file(destfname); + f = open_new_file(destfname, act.permissions); if (f == NULL) { run_err("%s: Cannot create file", destfname); + sfree(destfname); continue; } - if (scp_accept_filexfer()) + if (scp_accept_filexfer()) { + sfree(destfname); + close_wfile(f); return; + } stat_bytes = uint64_make(0, 0); stat_starttime = time(NULL); stat_lasttime = 0; stat_name = stripslashes(destfname, 1); @@ -1929,10 +2029,11 @@ } close_wfile(f); if (wrerror) { run_err("%s: Write error", destfname); + sfree(destfname); continue; } (void) scp_finish_filerecv(); sfree(destfname); sfree(act.buf); @@ -1945,10 +2046,12 @@ static void toremote(int argc, char *argv[]) { char *src, *targ, *host, *user; char *cmd; int i, wc_type; + + uploading = 1; targ = argv[argc - 1]; /* Separate host from filename */ host = targ; @@ -2034,10 +2137,12 @@ */ static void tolocal(int argc, char *argv[]) { char *src, *targ, *host, *user; char *cmd; + + uploading = 0; if (argc != 2) bump("More than one remote source not supported"); src = argv[0]; @@ -2200,10 +2305,13 @@ va_end(ap); fprintf(stderr, "\n try typing just \"pscp\" for help\n"); exit(1); } +const int share_can_be_downstream = TRUE; +const int share_can_be_upstream = FALSE; + /* * Main program. (Called `psftp_main' because it gets called from * *sftp.c; bit silly, I know, but it had to be called _something_.) */ int psftp_main(int argc, char *argv[]) @@ -2219,18 +2327,19 @@ ; cmdline_tooltype = TOOLTYPE_FILETRANSFER; sk_init(); /* Load Default Settings before doing anything else. */ - do_defaults(NULL, &cfg); + conf = conf_new(); + do_defaults(NULL, conf); loaded_session = FALSE; for (i = 1; i < argc; i++) { int ret; if (argv[i][0] != '-') break; - ret = cmdline_process_param(argv[i], i+1connected(backhandle)) { char ch; back->special(backhandle, TS_EOF); + sent_eof = TRUE; ssh_scp_recv((unsigned char *) &ch, 1); } random_save_seed(); cmdline_cleanup(); Index: psftp.c ================================================================== --- psftp.c +++ psftp.c @@ -34,11 +34,32 @@ */ char *pwd, *homedir; static Backend *back; static void *backhandle; -static Config cfg; +static Conf *conf; +int sent_eof = FALSE; + +/* ---------------------------------------------------------------------- + * Manage sending requests and waiting for replies. + */ +struct sftp_packet *sftp_wait_for_reply(struct sftp_request *req) +{ + struct sftp_packet *pktin; + struct sftp_request *rreq; + + sftp_register(req); + pktin = sftp_recv(); + if (pktin == NULL) + connection_fatal(NULL, "did not receive SFTP response packet " + "from server"); + rreq = sftp_find_request(pktin); + if (rreq != req) + connection_fatal(NULL, "unable to understand SFTP response packet " + "from server: %s", fxp_error()); + return pktin; +} /* ---------------------------------------------------------------------- * Higher-level helper functions used in commands. */ @@ -49,11 +70,11 @@ */ char *canonify(char *name) { char *fullname, *canonname; struct sftp_packet *pktin; - struct sftp_request *req, *rreq; + struct sftp_request *req; if (name[0] == '/') { fullname = dupstr(name); } else { char *slash; @@ -62,14 +83,13 @@ else slash = "/"; fullname = dupcat(pwd, slash, name, NULL); } - sftp_register(req = fxp_realpath_send(fullname)); - rreq = sftp_find_request(pktin = sftp_recv()); - assert(rreq == req); - canonname = fxp_realpath_recv(pktin, rreq); + req = fxp_realpath_send(fullname); + pktin = sftp_wait_for_reply(req); + canonname = fxp_realpath_recv(pktin, req); if (canonname) { sfree(fullname); return canonname; } else { @@ -120,17 +140,16 @@ * Now i points at the slash. Deal with the final special * case i==0 (ie the whole path was "/nonexistentfile"). */ fullname[i] = '\0'; /* separate the string */ if (i == 0) { - sftp_register(req = fxp_realpath_send("/")); + req = fxp_realpath_send("/"); } else { - sftp_register(req = fxp_realpath_send(fullname)); + req = fxp_realpath_send(fullname); } - rreq = sftp_find_request(pktin = sftp_recv()); - assert(rreq == req); - canonname = fxp_realpath_recv(pktin, rreq); + pktin = sftp_wait_for_reply(req); + canonname = fxp_realpath_recv(pktin, req); if (!canonname) { /* Even that failed. Restore our best guess at the * constructed filename and give up */ fullname[i] = '/'; /* restore slash and last component */ @@ -205,29 +224,28 @@ */ int sftp_get_file(char *fname, char *outfname, int recurse, int restart) { struct fxp_handle *fh; struct sftp_packet *pktin; - struct sftp_request *req, *rreq; + struct sftp_request *req; struct fxp_xfer *xfer; uint64 offset; WFile *file; int ret, shown_err = FALSE; + struct fxp_attrs attrs; /* * In recursive mode, see if we're dealing with a directory. * (If we're not in recursive mode, we need not even check: the * subsequent FXP_OPEN will return a usable error message.) */ if (recurse) { - struct fxp_attrs attrs; int result; - sftp_register(req = fxp_stat_send(fname)); - rreq = sftp_find_request(pktin = sftp_recv()); - assert(rreq == req); - result = fxp_stat_recv(pktin, rreq, &attrs); + req = fxp_stat_send(fname); + pktin = sftp_wait_for_reply(req); + result = fxp_stat_recv(pktin, req, &attrs); if (result && (attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS) && (attrs.permissions & 0040000)) { @@ -249,14 +267,13 @@ /* * Now get the list of filenames in the remote * directory. */ - sftp_register(req = fxp_opendir_send(fname)); - rreq = sftp_find_request(pktin = sftp_recv()); - assert(rreq == req); - dirhandle = fxp_opendir_recv(pktin, rreq); + req = fxp_opendir_send(fname); + pktin = sftp_wait_for_reply(req); + dirhandle = fxp_opendir_recv(pktin, req); if (!dirhandle) { printf("%s: unable to open directory: %s\n", fname, fxp_error()); return 0; @@ -264,19 +281,23 @@ nnames = namesize = 0; ournames = NULL; while (1) { int i; - sftp_register(req = fxp_readdir_send(dirhandle)); - rreq = sftp_find_request(pktin = sftp_recv()); - assert(rreq == req); - names = fxp_readdir_recv(pktin, rreq); + req = fxp_readdir_send(dirhandle); + pktin = sftp_wait_for_reply(req); + names = fxp_readdir_recv(pktin, req); if (names == NULL) { if (fxp_error_type() == SSH_FX_EOF) break; printf("%s: reading directory: %s\n", fname, fxp_error()); + + req = fxp_close_send(dirhandle); + pktin = sftp_wait_for_reply(req); + fxp_close_recv(pktin, req); + sfree(ournames); return 0; } if (names->nnames == 0) { fxp_free_names(names); @@ -298,23 +319,23 @@ fxp_dup_name(&names->names[i]); } } fxp_free_names(names); } - sftp_register(req = fxp_close_send(dirhandle)); - rreq = sftp_find_request(pktin = sftp_recv()); - assert(rreq == req); - fxp_close_recv(pktin, rreq); + req = fxp_close_send(dirhandle); + pktin = sftp_wait_for_reply(req); + fxp_close_recv(pktin, req); /* * Sort the names into a clear order. This ought to * make things more predictable when we're doing a * reget of the same directory, just in case two * readdirs on the same remote directory return a * different order. */ - qsort(ournames, nnames, sizeof(*ournames), sftp_name_compare); + if (nnames > 0) + qsort(ournames, nnames, sizeof(*ournames), sftp_name_compare); /* * If we're in restart mode, find the last filename on * this list that already exists. We may have to do a * reget on _that_ file, but shouldn't have to do @@ -325,15 +346,12 @@ i = 0; if (restart) { while (i < nnames) { char *nextoutfname; int ret; - if (outfname) - nextoutfname = dir_file_cat(outfname, - ournames[i]->filename); - else - nextoutfname = dupstr(ournames[i]->filename); + nextoutfname = dir_file_cat(outfname, + ournames[i]->filename); ret = (file_type(nextoutfname) == FILE_TYPE_NONEXISTENT); sfree(nextoutfname); if (ret) break; i++; @@ -351,15 +369,11 @@ for (; i < nnames; i++) { char *nextfname, *nextoutfname; int ret; nextfname = dupcat(fname, "/", ournames[i]->filename, NULL); - if (outfname) - nextoutfname = dir_file_cat(outfname, - ournames[i]->filename); - else - nextoutfname = dupstr(ournames[i]->filename); + nextoutfname = dir_file_cat(outfname, ournames[i]->filename); ret = sftp_get_file(nextfname, nextoutfname, recurse, restart); restart = FALSE; /* after first partial file, do full */ sfree(nextoutfname); sfree(nextfname); if (!ret) { @@ -381,33 +395,36 @@ return 1; } } - sftp_register(req = fxp_open_send(fname, SSH_FXF_READ)); - rreq = sftp_find_request(pktin = sftp_recv()); - assert(rreq == req); - fh = fxp_open_recv(pktin, rreq); + req = fxp_stat_send(fname); + pktin = sftp_wait_for_reply(req); + if (!fxp_stat_recv(pktin, req, &attrs)) + attrs.flags = 0; + + req = fxp_open_send(fname, SSH_FXF_READ, NULL); + pktin = sftp_wait_for_reply(req); + fh = fxp_open_recv(pktin, req); if (!fh) { printf("%s: open for read: %s\n", fname, fxp_error()); return 0; } if (restart) { file = open_existing_wfile(outfname, NULL); } else { - file = open_new_file(outfname); + file = open_new_file(outfname, GET_PERMISSIONS(attrs)); } if (!file) { printf("local: unable to open %s\n", outfname); - sftp_register(req = fxp_close_send(fh)); - rreq = sftp_find_request(pktin = sftp_recv()); - assert(rreq == req); - fxp_close_recv(pktin, rreq); + req = fxp_close_send(fh); + pktin = sftp_wait_for_reply(req); + fxp_close_recv(pktin, req); return 0; } if (restart) { @@ -414,14 +431,13 @@ char decbuf[30]; if (seek_file(file, uint64_make(0,0) , FROM_END) == -1) { close_wfile(file); printf("reget: cannot restart %s - file too large\n", outfname); - sftp_register(req = fxp_close_send(fh)); - rreq = sftp_find_request(pktin = sftp_recv()); - assert(rreq == req); - fxp_close_recv(pktin, rreq); + req = fxp_close_send(fh); + pktin = sftp_wait_for_reply(req); + fxp_close_recv(pktin, req); return 0; } offset = get_file_posn(file); @@ -445,16 +461,17 @@ int wpos, wlen; xfer_download_queue(xfer); pktin = sftp_recv(); ret = xfer_download_gotpkt(xfer, pktin); - - if (ret < 0) { + if (ret <= 0) { if (!shown_err) { printf("error while reading: %s\n", fxp_error()); shown_err = TRUE; } + if (ret == INT_MIN) /* pktin not even freed */ + sfree(pktin); ret = 0; } while (xfer_download_data(xfer, &vbuf, &len)) { unsigned char *buf = (unsigned char *)vbuf; @@ -481,35 +498,35 @@ xfer_cleanup(xfer); close_wfile(file); - sftp_register(req = fxp_close_send(fh)); - rreq = sftp_find_request(pktin = sftp_recv()); - assert(rreq == req); - fxp_close_recv(pktin, rreq); + req = fxp_close_send(fh); + pktin = sftp_wait_for_reply(req); + fxp_close_recv(pktin, req); return ret; } int sftp_put_file(char *fname, char *outfname, int recurse, int restart) { struct fxp_handle *fh; struct fxp_xfer *xfer; struct sftp_packet *pktin; - struct sftp_request *req, *rreq; + struct sftp_request *req; uint64 offset; RFile *file; int ret, err, eof; + struct fxp_attrs attrs; + long permissions; /* * In recursive mode, see if we're dealing with a directory. * (If we're not in recursive mode, we need not even check: the * subsequent fopen will return an error message.) */ if (recurse && file_type(fname) == FILE_TYPE_DIRECTORY) { - struct fxp_attrs attrs; int result; int nnames, namesize; char *name, **ournames; DirHandle *dh; int i; @@ -516,21 +533,19 @@ /* * First, attempt to create the destination directory, * unless it already exists. */ - sftp_register(req = fxp_stat_send(outfname)); - rreq = sftp_find_request(pktin = sftp_recv()); - assert(rreq == req); - result = fxp_stat_recv(pktin, rreq, &attrs); + req = fxp_stat_send(outfname); + pktin = sftp_wait_for_reply(req); + result = fxp_stat_recv(pktin, req, &attrs); if (!result || !(attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS) || !(attrs.permissions & 0040000)) { - sftp_register(req = fxp_mkdir_send(outfname)); - rreq = sftp_find_request(pktin = sftp_recv()); - assert(rreq == req); - result = fxp_mkdir_recv(pktin, rreq); + req = fxp_mkdir_send(outfname); + pktin = sftp_wait_for_reply(req); + result = fxp_mkdir_recv(pktin, req); if (!result) { printf("%s: create directory: %s\n", outfname, fxp_error()); return 0; @@ -561,11 +576,12 @@ * Sort the names into a clear order. This ought to make * things more predictable when we're doing a reput of the * same directory, just in case two readdirs on the same * local directory return a different order. */ - qsort(ournames, nnames, sizeof(*ournames), bare_name_compare); + if (nnames > 0) + qsort(ournames, nnames, sizeof(*ournames), bare_name_compare); /* * If we're in restart mode, find the last filename on this * list that already exists. We may have to do a reput on * _that_ file, but shouldn't have to do anything on the @@ -576,14 +592,13 @@ i = 0; if (restart) { while (i < nnames) { char *nextoutfname; nextoutfname = dupcat(outfname, "/", ournames[i], NULL); - sftp_register(req = fxp_stat_send(nextoutfname)); - rreq = sftp_find_request(pktin = sftp_recv()); - assert(rreq == req); - result = fxp_stat_recv(pktin, rreq, &attrs); + req = fxp_stat_send(nextoutfname); + pktin = sftp_wait_for_reply(req); + result = fxp_stat_recv(pktin, req, &attrs); sfree(nextoutfname); if (!result) break; i++; } @@ -599,14 +614,11 @@ */ for (; i < nnames; i++) { char *nextfname, *nextoutfname; int ret; - if (fname) - nextfname = dir_file_cat(fname, ournames[i]); - else - nextfname = dupstr(ournames[i]); + nextfname = dir_file_cat(fname, ournames[i]); nextoutfname = dupcat(outfname, "/", ournames[i], NULL); ret = sftp_put_file(nextfname, nextoutfname, recurse, restart); restart = FALSE; /* after first partial file, do full */ sfree(nextoutfname); sfree(nextfname); @@ -628,24 +640,26 @@ sfree(ournames); return 1; } - file = open_existing_file(fname, NULL, NULL, NULL); + file = open_existing_file(fname, NULL, NULL, NULL, &permissions); if (!file) { printf("local: unable to open %s\n", fname); return 0; } + attrs.flags = 0; + PUT_PERMISSIONS(attrs, permissions); if (restart) { - sftp_register(req = fxp_open_send(outfname, SSH_FXF_WRITE)); + req = fxp_open_send(outfname, SSH_FXF_WRITE, &attrs); } else { - sftp_register(req = fxp_open_send(outfname, SSH_FXF_WRITE | - SSH_FXF_CREAT | SSH_FXF_TRUNC)); + req = fxp_open_send(outfname, + SSH_FXF_WRITE | SSH_FXF_CREAT | SSH_FXF_TRUNC, + &attrs); } - rreq = sftp_find_request(pktin = sftp_recv()); - assert(rreq == req); - fh = fxp_open_recv(pktin, rreq); + pktin = sftp_wait_for_reply(req); + fh = fxp_open_recv(pktin, req); if (!fh) { close_rfile(file); printf("%s: open for write: %s\n", outfname, fxp_error()); return 0; @@ -654,14 +668,13 @@ if (restart) { char decbuf[30]; struct fxp_attrs attrs; int ret; - sftp_register(req = fxp_fstat_send(fh)); - rreq = sftp_find_request(pktin = sftp_recv()); - assert(rreq == req); - ret = fxp_fstat_recv(pktin, rreq, &attrs); + req = fxp_fstat_send(fh); + pktin = sftp_wait_for_reply(req); + ret = fxp_fstat_recv(pktin, req, &attrs); if (!ret) { close_rfile(file); printf("read size of %s: %s\n", outfname, fxp_error()); return 0; @@ -707,23 +720,26 @@ } if (!xfer_done(xfer)) { pktin = sftp_recv(); ret = xfer_upload_gotpkt(xfer, pktin); - if (ret <= 0 && !err) { - printf("error while writing: %s\n", fxp_error()); - err = 1; + if (ret <= 0) { + if (ret == INT_MIN) /* pktin not even freed */ + sfree(pktin); + if (!err) { + printf("error while writing: %s\n", fxp_error()); + err = 1; + } } } } xfer_cleanup(xfer); - sftp_register(req = fxp_close_send(fh)); - rreq = sftp_find_request(pktin = sftp_recv()); - assert(rreq == req); - fxp_close_recv(pktin, rreq); + req = fxp_close_send(fh); + pktin = sftp_wait_for_reply(req); + fxp_close_recv(pktin, req); close_rfile(file); return ret; } @@ -741,11 +757,11 @@ } SftpWildcardMatcher; SftpWildcardMatcher *sftp_begin_wildcard_matching(char *name) { struct sftp_packet *pktin; - struct sftp_request *req, *rreq; + struct sftp_request *req; char *wildcard; char *unwcdir, *tmpdir, *cdir; int len, check; SftpWildcardMatcher *swcm; struct fxp_handle *dirh; @@ -772,14 +788,13 @@ return NULL; } cdir = canonify(unwcdir); - sftp_register(req = fxp_opendir_send(cdir)); - rreq = sftp_find_request(pktin = sftp_recv()); - assert(rreq == req); - dirh = fxp_opendir_recv(pktin, rreq); + req = fxp_opendir_send(cdir); + pktin = sftp_wait_for_reply(req); + dirh = fxp_opendir_recv(pktin, req); if (dirh) { swcm = snew(SftpWildcardMatcher); swcm->dirh = dirh; swcm->names = NULL; @@ -798,30 +813,39 @@ char *sftp_wildcard_get_filename(SftpWildcardMatcher *swcm) { struct fxp_name *name; struct sftp_packet *pktin; - struct sftp_request *req, *rreq; + struct sftp_request *req; while (1) { if (swcm->names && swcm->namepos >= swcm->names->nnames) { fxp_free_names(swcm->names); swcm->names = NULL; } if (!swcm->names) { - sftp_register(req = fxp_readdir_send(swcm->dirh)); - rreq = sftp_find_request(pktin = sftp_recv()); - assert(rreq == req); - swcm->names = fxp_readdir_recv(pktin, rreq); + req = fxp_readdir_send(swcm->dirh); + pktin = sftp_wait_for_reply(req); + swcm->names = fxp_readdir_recv(pktin, req); if (!swcm->names) { if (fxp_error_type() != SSH_FX_EOF) printf("%s: reading directory: %s\n", swcm->prefix, fxp_error()); return NULL; - } + } else if (swcm->names->nnames == 0) { + /* + * Another failure mode which we treat as EOF is if + * the server reports success from FXP_READDIR but + * returns no actual names. This is unusual, since + * from most servers you'd expect at least "." and + * "..", but there's nothing forbidding a server from + * omitting those if it wants to. + */ + return NULL; + } swcm->namepos = 0; } assert(swcm->names && swcm->namepos < swcm->names->nnames); @@ -852,16 +876,15 @@ } void sftp_finish_wildcard_matching(SftpWildcardMatcher *swcm) { struct sftp_packet *pktin; - struct sftp_request *req, *rreq; + struct sftp_request *req; - sftp_register(req = fxp_close_send(swcm->dirh)); - rreq = sftp_find_request(pktin = sftp_recv()); - assert(rreq == req); - fxp_close_recv(pktin, rreq); + req = fxp_close_send(swcm->dirh); + pktin = sftp_wait_for_reply(req); + fxp_close_recv(pktin, req); if (swcm->names) fxp_free_names(swcm->names); sfree(swcm->prefix); @@ -897,10 +920,11 @@ cname = canonify(newname); if (!cname) { printf("%s: canonify: %s\n", newname, fxp_error()); ret = 0; } + sfree(newname); matched = TRUE; ret &= func(ctx, cname); sfree(cname); } @@ -968,10 +992,11 @@ } if (back != NULL && back->connected(backhandle)) { char ch; back->special(backhandle, TS_EOF); + sent_eof = TRUE; sftp_recvdata(&ch, 1); } do_sftp_cleanup(); return 0; @@ -987,11 +1012,11 @@ struct fxp_names *names; struct fxp_name **ournames; int nnames, namesize; char *dir, *cdir, *unwcdir, *wildcard; struct sftp_packet *pktin; - struct sftp_request *req, *rreq; + struct sftp_request *req; int i; if (back == NULL) { not_connected(); return 0; @@ -1008,10 +1033,11 @@ wildcard = NULL; } else { char *tmpdir; int len, check; + sfree(unwcdir); wildcard = stripslashes(dir, 0); unwcdir = dupstr(dir); len = wildcard - dir; unwcdir[len] = '\0'; if (len > 0 && unwcdir[len-1] == '/') @@ -1034,27 +1060,25 @@ return 0; } printf("Listing directory %s\n", cdir); - sftp_register(req = fxp_opendir_send(cdir)); - rreq = sftp_find_request(pktin = sftp_recv()); - assert(rreq == req); - dirh = fxp_opendir_recv(pktin, rreq); + req = fxp_opendir_send(cdir); + pktin = sftp_wait_for_reply(req); + dirh = fxp_opendir_recv(pktin, req); if (dirh == NULL) { printf("Unable to open %s: %s\n", dir, fxp_error()); } else { nnames = namesize = 0; ournames = NULL; while (1) { - sftp_register(req = fxp_readdir_send(dirh)); - rreq = sftp_find_request(pktin = sftp_recv()); - assert(rreq == req); - names = fxp_readdir_recv(pktin, rreq); + req = fxp_readdir_send(dirh); + pktin = sftp_wait_for_reply(req); + names = fxp_readdir_recv(pktin, req); if (names == NULL) { if (fxp_error_type() == SSH_FX_EOF) break; printf("Reading directory %s: %s\n", dir, fxp_error()); @@ -1074,20 +1098,20 @@ if (!wildcard || wc_match(wildcard, names->names[i].filename)) ournames[nnames++] = fxp_dup_name(&names->names[i]); fxp_free_names(names); } - sftp_register(req = fxp_close_send(dirh)); - rreq = sftp_find_request(pktin = sftp_recv()); - assert(rreq == req); - fxp_close_recv(pktin, rreq); + req = fxp_close_send(dirh); + pktin = sftp_wait_for_reply(req); + fxp_close_recv(pktin, req); /* * Now we have our filenames. Sort them by actual file * name, and then output the longname parts. */ - qsort(ournames, nnames, sizeof(*ournames), sftp_name_compare); + if (nnames > 0) + qsort(ournames, nnames, sizeof(*ournames), sftp_name_compare); /* * And print them. */ for (i = 0; i < nnames; i++) { @@ -1109,11 +1133,11 @@ */ int sftp_cmd_cd(struct sftp_command *cmd) { struct fxp_handle *dirh; struct sftp_packet *pktin; - struct sftp_request *req, *rreq; + struct sftp_request *req; char *dir; if (back == NULL) { not_connected(); return 0; @@ -1127,25 +1151,23 @@ if (!dir) { printf("%s: canonify: %s\n", dir, fxp_error()); return 0; } - sftp_register(req = fxp_opendir_send(dir)); - rreq = sftp_find_request(pktin = sftp_recv()); - assert(rreq == req); - dirh = fxp_opendir_recv(pktin, rreq); + req = fxp_opendir_send(dir); + pktin = sftp_wait_for_reply(req); + dirh = fxp_opendir_recv(pktin, req); if (!dirh) { printf("Directory %s: %s\n", dir, fxp_error()); sfree(dir); return 0; } - sftp_register(req = fxp_close_send(dirh)); - rreq = sftp_find_request(pktin = sftp_recv()); - assert(rreq == req); - fxp_close_recv(pktin, rreq); + req = fxp_close_send(dirh); + pktin = sftp_wait_for_reply(req); + fxp_close_recv(pktin, req); sfree(pwd); pwd = dir; printf("Remote directory is now %s\n", pwd); @@ -1234,11 +1256,13 @@ while (origwfname) { fname = canonify(origwfname); if (!fname) { + sftp_finish_wildcard_matching(swcm); printf("%s: canonify: %s\n", origwfname, fxp_error()); + sfree(origwfname); sfree(unwcfname); return 0; } if (!multiple && i < cmd->nwords) @@ -1390,11 +1414,11 @@ int sftp_cmd_mkdir(struct sftp_command *cmd) { char *dir; struct sftp_packet *pktin; - struct sftp_request *req, *rreq; + struct sftp_request *req; int result; int i, ret; if (back == NULL) { not_connected(); @@ -1412,14 +1436,13 @@ if (!dir) { printf("%s: canonify: %s\n", dir, fxp_error()); return 0; } - sftp_register(req = fxp_mkdir_send(dir)); - rreq = sftp_find_request(pktin = sftp_recv()); - assert(rreq == req); - result = fxp_mkdir_recv(pktin, rreq); + req = fxp_mkdir_send(dir); + pktin = sftp_wait_for_reply(req); + result = fxp_mkdir_recv(pktin, req); if (!result) { printf("mkdir %s: %s\n", dir, fxp_error()); ret = 0; } else @@ -1432,17 +1455,16 @@ } static int sftp_action_rmdir(void *vctx, char *dir) { struct sftp_packet *pktin; - struct sftp_request *req, *rreq; + struct sftp_request *req; int result; - sftp_register(req = fxp_rmdir_send(dir)); - rreq = sftp_find_request(pktin = sftp_recv()); - assert(rreq == req); - result = fxp_rmdir_recv(pktin, rreq); + req = fxp_rmdir_send(dir); + pktin = sftp_wait_for_reply(req); + result = fxp_rmdir_recv(pktin, req); if (!result) { printf("rmdir %s: %s\n", dir, fxp_error()); return 0; } @@ -1474,17 +1496,16 @@ } static int sftp_action_rm(void *vctx, char *fname) { struct sftp_packet *pktin; - struct sftp_request *req, *rreq; + struct sftp_request *req; int result; - sftp_register(req = fxp_remove_send(fname)); - rreq = sftp_find_request(pktin = sftp_recv()); - assert(rreq == req); - result = fxp_remove_recv(pktin, rreq); + req = fxp_remove_send(fname); + pktin = sftp_wait_for_reply(req); + result = fxp_remove_recv(pktin, req); if (!result) { printf("rm %s: %s\n", fname, fxp_error()); return 0; } @@ -1516,18 +1537,17 @@ } static int check_is_dir(char *dstfname) { struct sftp_packet *pktin; - struct sftp_request *req, *rreq; + struct sftp_request *req; struct fxp_attrs attrs; int result; - sftp_register(req = fxp_stat_send(dstfname)); - rreq = sftp_find_request(pktin = sftp_recv()); - assert(rreq == req); - result = fxp_stat_recv(pktin, rreq, &attrs); + req = fxp_stat_send(dstfname); + pktin = sftp_wait_for_reply(req); + result = fxp_stat_recv(pktin, req, &attrs); if (result && (attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS) && (attrs.permissions & 0040000)) return TRUE; @@ -1542,11 +1562,11 @@ static int sftp_action_mv(void *vctx, char *srcfname) { struct sftp_context_mv *ctx = (struct sftp_context_mv *)vctx; struct sftp_packet *pktin; - struct sftp_request *req, *rreq; + struct sftp_request *req; const char *error; char *finalfname, *newcanon = NULL; int ret, result; if (ctx->dest_is_dir) { @@ -1567,14 +1587,13 @@ finalfname = newcanon; } else { finalfname = ctx->dstfname; } - sftp_register(req = fxp_rename_send(srcfname, finalfname)); - rreq = sftp_find_request(pktin = sftp_recv()); - assert(rreq == req); - result = fxp_rename_recv(pktin, rreq); + req = fxp_rename_send(srcfname, finalfname); + pktin = sftp_wait_for_reply(req); + result = fxp_rename_recv(pktin, req); error = result ? NULL : fxp_error(); if (error) { printf("mv %s %s: %s\n", srcfname, finalfname, error); @@ -1639,19 +1658,18 @@ static int sftp_action_chmod(void *vctx, char *fname) { struct fxp_attrs attrs; struct sftp_packet *pktin; - struct sftp_request *req, *rreq; + struct sftp_request *req; int result; unsigned oldperms, newperms; struct sftp_context_chmod *ctx = (struct sftp_context_chmod *)vctx; - sftp_register(req = fxp_stat_send(fname)); - rreq = sftp_find_request(pktin = sftp_recv()); - assert(rreq == req); - result = fxp_stat_recv(pktin, rreq, &attrs); + req = fxp_stat_send(fname); + pktin = sftp_wait_for_reply(req); + result = fxp_stat_recv(pktin, req, &attrs); if (!result || !(attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS)) { printf("get attrs for %s: %s\n", fname, result ? "file permissions not provided" : fxp_error()); return 0; @@ -1664,14 +1682,13 @@ newperms = attrs.permissions & 07777; if (oldperms == newperms) return 1; /* no need to do anything! */ - sftp_register(req = fxp_setstat_send(fname, attrs)); - rreq = sftp_find_request(pktin = sftp_recv()); - assert(rreq == req); - result = fxp_setstat_recv(pktin, rreq); + req = fxp_setstat_send(fname, attrs); + pktin = sftp_wait_for_reply(req); + result = fxp_setstat_recv(pktin, req); if (!result) { printf("set attrs for %s: %s\n", fname, fxp_error()); return 0; } @@ -2217,10 +2234,11 @@ if (!line || !*line) { cmd->obey = sftp_cmd_quit; if ((mode == 0) || (modeflags & 1)) printf("quit\n"); + sfree(line); return cmd; /* eof */ } line[strcspn(line, "\r\n")] = '\0'; @@ -2263,14 +2281,17 @@ * >firstword< * >second word< * >this has "quotes" in< * >and"this"< */ - while (*p) { + while (1) { /* skip whitespace */ while (*p && (*p == ' ' || *p == '\t')) p++; + /* terminate loop */ + if (!*p) + break; /* mark start of word */ q = r = p; /* q sits at start, r writes word */ quoting = 0; while (*p) { if (!quoting && (*p == ' ' || *p == '\t')) @@ -2314,11 +2335,11 @@ } static int do_sftp_init(void) { struct sftp_packet *pktin; - struct sftp_request *req, *rreq; + struct sftp_request *req; /* * Do protocol initialisation. */ if (!fxp_init()) { @@ -2328,14 +2349,13 @@ } /* * Find out where our home directory is. */ - sftp_register(req = fxp_realpath_send(".")); - rreq = sftp_find_request(pktin = sftp_recv()); - assert(rreq == req); - homedir = fxp_realpath_recv(pktin, rreq); + req = fxp_realpath_send("."); + pktin = sftp_wait_for_reply(req); + homedir = fxp_realpath_recv(pktin, req); if (!homedir) { fprintf(stderr, "Warning: failed to resolve home directory: %s\n", fxp_error()); @@ -2350,10 +2370,11 @@ void do_sftp_cleanup() { char ch; if (back) { back->special(backhandle, TS_EOF); + sent_eof = TRUE; sftp_recvdata(&ch, 1); back->free(backhandle); sftp_cleanup_request(); back = NULL; backhandle = NULL; @@ -2455,10 +2476,22 @@ va_end(ap); fputs(str2, stderr); sfree(str2); cleanup_exit(1); +} +void nonfatal(char *fmt, ...) +{ + char *str, *str2; + va_list ap; + va_start(ap, fmt); + str = dupvprintf(fmt, ap); + str2 = dupcat("Error: ", str, "\n", NULL); + sfree(str); + va_end(ap); + fputs(str2, stderr); + sfree(str2); } void connection_fatal(void *frontend, char *fmt, ...) { char *str, *str2; va_list ap; @@ -2557,10 +2590,23 @@ * No "untrusted" output should get here (the way the code is * currently, it's all diverted by FLAG_STDERR). */ assert(!"Unexpected call to from_backend_untrusted()"); return 0; /* not reached */ +} +int from_backend_eof(void *frontend) +{ + /* + * We expect to be the party deciding when to close the + * connection, so if we see EOF before we sent it ourselves, we + * should panic. + */ + if (!sent_eof) { + connection_fatal(frontend, + "Received unexpected end-of-file from SFTP server"); + } + return FALSE; } int sftp_recvdata(char *buf, int len) { outptr = (unsigned char *) buf; outlen = len; @@ -2662,116 +2708,119 @@ * If we haven't loaded session details already (e.g., from -load), * try looking for a session called "host". */ if (!loaded_session) { /* Try to load settings for `host' into a temporary config */ - Config cfg2; - cfg2.host[0] = '\0'; - do_defaults(host, &cfg2); - if (cfg2.host[0] != '\0') { + Conf *conf2 = conf_new(); + conf_set_str(conf2, CONF_host, ""); + do_defaults(host, conf2); + if (conf_get_str(conf2, CONF_host)[0] != '\0') { /* Settings present and include hostname */ /* Re-load data into the real config. */ - do_defaults(host, &cfg); + do_defaults(host, conf); } else { /* Session doesn't exist or mention a hostname. */ /* Use `host' as a bare hostname. */ - strncpy(cfg.host, host, sizeof(cfg.host) - 1); - cfg.host[sizeof(cfg.host) - 1] = '\0'; + conf_set_str(conf, CONF_host, host); } + conf_free(conf2); } else { /* Patch in hostname `host' to session details. */ - strncpy(cfg.host, host, sizeof(cfg.host) - 1); - cfg.host[sizeof(cfg.host) - 1] = '\0'; + conf_set_str(conf, CONF_host, host); } /* * Force use of SSH. (If they got the protocol wrong we assume the * port is useless too.) */ - if (cfg.protocol != PROT_SSH) { - cfg.protocol = PROT_SSH; - cfg.port = 22; + if (conf_get_int(conf, CONF_protocol) != PROT_SSH) { + conf_set_int(conf, CONF_protocol, PROT_SSH); + conf_set_int(conf, CONF_port, 22); } /* * If saved session / Default Settings says SSH-1 (`1 only' or `1'), * then change it to SSH-2, on the grounds that that's more likely to * work for SFTP. (Can be overridden with `-1' option.) * But if it says `2 only' or `2', respect which. */ - if (cfg.sshprot != 2 && cfg.sshprot != 3) - cfg.sshprot = 2; + if ((conf_get_int(conf, CONF_sshprot) & ~1) != 2) /* is it 2 or 3? */ + conf_set_int(conf, CONF_sshprot, 2); /* * Enact command-line overrides. */ - cmdline_run_saved(&cfg); - - /* - * Trim leading whitespace off the hostname if it's there. - */ - { - int space = strspn(cfg.host, " \t"); - memmove(cfg.host, cfg.host+space, 1+strlen(cfg.host)-space); - } - - /* See if host is of the form user@host */ - if (cfg.host[0] != '\0') { - char *atsign = strrchr(cfg.host, '@'); - /* Make sure we're not overflowing the user field */ - if (atsign) { - if (atsign - cfg.host < sizeof cfg.username) { - strncpy(cfg.username, cfg.host, atsign - cfg.host); - cfg.username[atsign - cfg.host] = '\0'; - } - memmove(cfg.host, atsign + 1, 1 + strlen(atsign + 1)); - } - } - - /* - * Trim a colon suffix off the hostname if it's there. - */ - cfg.host[strcspn(cfg.host, ":")] = '\0'; - - /* - * Remove any remaining whitespace from the hostname. - */ - { - int p1 = 0, p2 = 0; - while (cfg.host[p2] != '\0') { - if (cfg.host[p2] != ' ' && cfg.host[p2] != '\t') { - cfg.host[p1] = cfg.host[p2]; - p1++; - } - p2++; - } - cfg.host[p1] = '\0'; + cmdline_run_saved(conf); + + /* + * Muck about with the hostname in various ways. + */ + { + char *hostbuf = dupstr(conf_get_str(conf, CONF_host)); + char *host = hostbuf; + char *p, *q; + + /* + * Trim leading whitespace. + */ + host += strspn(host, " \t"); + + /* + * See if host is of the form user@host, and separate out + * the username if so. + */ + if (host[0] != '\0') { + char *atsign = strrchr(host, '@'); + if (atsign) { + *atsign = '\0'; + conf_set_str(conf, CONF_username, host); + host = atsign + 1; + } + } + + /* + * Remove any remaining whitespace. + */ + p = hostbuf; + q = host; + while (*q) { + if (*q != ' ' && *q != '\t') + *p++ = *q; + q++; + } + *p = '\0'; + + conf_set_str(conf, CONF_host, hostbuf); + sfree(hostbuf); } /* Set username */ if (user != NULL && user[0] != '\0') { - strncpy(cfg.username, user, sizeof(cfg.username) - 1); - cfg.username[sizeof(cfg.username) - 1] = '\0'; + conf_set_str(conf, CONF_username, user); } if (portnumber) - cfg.port = portnumber; + conf_set_int(conf, CONF_port, portnumber); /* * Disable scary things which shouldn't be enabled for simple * things like SCP and SFTP: agent forwarding, port forwarding, * X forwarding. */ - cfg.x11_forward = 0; - cfg.agentfwd = 0; - cfg.portfwd[0] = cfg.portfwd[1] = '\0'; - cfg.ssh_simple = TRUE; + conf_set_int(conf, CONF_x11_forward, 0); + conf_set_int(conf, CONF_agentfwd, 0); + conf_set_int(conf, CONF_ssh_simple, TRUE); + { + char *key; + while ((key = conf_get_str_nthstrkey(conf, CONF_portfwd, 0)) != NULL) + conf_del_str_str(conf, CONF_portfwd, key); + } /* Set up subsystem name. */ - strcpy(cfg.remote_cmd, "sftp"); - cfg.ssh_subsys = TRUE; - cfg.nopty = TRUE; + conf_set_str(conf, CONF_remote_cmd, "sftp"); + conf_set_int(conf, CONF_ssh_subsys, TRUE); + conf_set_int(conf, CONF_nopty, TRUE); /* * Set up fallback option, for SSH-1 servers or servers with the * sftp subsystem not enabled but the server binary installed * in the usual place. We only support fallback on Unix @@ -2786,25 +2835,30 @@ * * the idea being that this will attempt to use either of the * obvious pathnames and then give up, and when it does give up * it will print the preferred pathname in the error messages. */ - cfg.remote_cmd_ptr2 = - "test -x /usr/lib/sftp-server && exec /usr/lib/sftp-server\n" - "test -x /usr/local/lib/sftp-server && exec /usr/local/lib/sftp-server\n" - "exec sftp-server"; - cfg.ssh_subsys2 = FALSE; + conf_set_str(conf, CONF_remote_cmd2, + "test -x /usr/lib/sftp-server &&" + " exec /usr/lib/sftp-server\n" + "test -x /usr/local/lib/sftp-server &&" + " exec /usr/local/lib/sftp-server\n" + "exec sftp-server"); + conf_set_int(conf, CONF_ssh_subsys2, FALSE); back = &ssh_backend; - err = back->init(NULL, &backhandle, &cfg, cfg.host, cfg.port, &realhost, - 0, cfg.tcp_keepalives); + err = back->init(NULL, &backhandle, conf, + conf_get_str(conf, CONF_host), + conf_get_int(conf, CONF_port), + &realhost, 0, + conf_get_int(conf, CONF_tcp_keepalives)); if (err != NULL) { fprintf(stderr, "ssh_init: %s\n", err); return 1; } - logctx = log_init(NULL, &cfg); + logctx = log_init(NULL, conf); back->provide_logctx(backhandle, logctx); console_provide_logctx(logctx); while (!back->sendok(backhandle)) { if (back->exitcode(backhandle) >= 0) return 1; @@ -2829,10 +2883,13 @@ va_end(ap); fprintf(stderr, "\n try typing \"psftp -h\" for help\n"); exit(1); } +const int share_can_be_downstream = TRUE; +const int share_can_be_upstream = FALSE; + /* * Main program. Parse arguments etc. */ int psftp_main(int argc, char *argv[]) { @@ -2852,11 +2909,12 @@ sk_init(); userhost = user = NULL; /* Load Default Settings before doing anything else. */ - do_defaults(NULL, &cfg); + conf = conf_new(); + do_defaults(NULL, conf); loaded_session = FALSE; for (i = 1; i < argc; i++) { int ret; if (argv[i][0] != '-') { @@ -2864,26 +2922,28 @@ usage(); else userhost = dupstr(argv[i]); continue; } - ret = cmdline_process_param(argv[i], i+1connected(backhandle)) { char ch; back->special(backhandle, TS_EOF); + sent_eof = TRUE; sftp_recvdata(&ch, 1); } do_sftp_cleanup(); random_save_seed(); cmdline_cleanup(); Index: psftp.h ================================================================== --- psftp.h +++ psftp.h @@ -83,19 +83,21 @@ * only opening a file and reading it, or creating a file and writing * it. None of this read-and-write, seeking-back-and-forth stuff. */ typedef struct RFile RFile; typedef struct WFile WFile; -/* Output params size, mtime and atime can all be NULL if desired */ +/* Output params size, perms, mtime and atime can all be NULL if + * desired. perms will be -1 if the OS does not support POSIX permissions. */ RFile *open_existing_file(char *name, uint64 *size, - unsigned long *mtime, unsigned long *atime); + unsigned long *mtime, unsigned long *atime, + long *perms); WFile *open_existing_wfile(char *name, uint64 *size); /* Returns <0 on error, 0 on eof, or number of bytes read, as usual */ int read_from_file(RFile *f, void *buffer, int length); /* Closes and frees the RFile */ void close_rfile(RFile *f); -WFile *open_new_file(char *name); +WFile *open_new_file(char *name, long perms); /* Returns <0 on error, 0 on eof, or number of bytes written, as usual */ int write_to_file(WFile *f, void *buffer, int length); void set_file_times(WFile *f, unsigned long mtime, unsigned long atime); /* Closes and frees the WFile */ void close_wfile(WFile *f); Index: putty.h ================================================================== --- putty.h +++ putty.h @@ -16,11 +16,11 @@ #endif #endif #ifndef DONE_TYPEDEFS #define DONE_TYPEDEFS -typedef struct config_tag Config; +typedef struct conf_tag Conf; typedef struct backend_tag Backend; typedef struct terminal_tag Terminal; #endif #include "puttyps.h" @@ -302,34 +302,34 @@ /* Actions on remote window title query */ TITLE_NONE, TITLE_EMPTY, TITLE_REAL }; enum { - /* Protocol back ends. (cfg.protocol) */ + /* Protocol back ends. (CONF_protocol) */ PROT_RAW, PROT_TELNET, PROT_RLOGIN, PROT_SSH, /* PROT_SERIAL is supported on a subset of platforms, but it doesn't * hurt to define it globally. */ PROT_SERIAL }; enum { - /* Bell settings (cfg.beep) */ + /* Bell settings (CONF_beep) */ BELL_DISABLED, BELL_DEFAULT, BELL_VISUAL, BELL_WAVEFILE, BELL_PCSPEAKER }; enum { - /* Taskbar flashing indication on bell (cfg.beep_ind) */ + /* Taskbar flashing indication on bell (CONF_beep_ind) */ B_IND_DISABLED, B_IND_FLASH, B_IND_STEADY }; enum { - /* Resize actions (cfg.resize_action) */ + /* Resize actions (CONF_resize_action) */ RESIZE_TERM, RESIZE_DISABLED, RESIZE_FONT, RESIZE_EITHER }; enum { - /* Function key types (cfg.funky_type) */ + /* Function key types (CONF_funky_type) */ FUNKY_TILDE, FUNKY_LINUX, FUNKY_XTERM, FUNKY_VT400, FUNKY_VT100P, @@ -413,16 +413,15 @@ ADDRTYPE_UNSPEC, ADDRTYPE_IPV4, ADDRTYPE_IPV6, ADDRTYPE_NAME }; struct backend_tag { const char *(*init) (void *frontend_handle, void **backend_handle, - Config *cfg, - char *host, int port, char **realhost, int nodelay, - int keepalive); + Conf *conf, char *host, int port, char **realhost, + int nodelay, int keepalive); void (*free) (void *handle); /* back->reconfig() passes in a replacement configuration. */ - void (*reconfig) (void *handle, Config *cfg); + void (*reconfig) (void *handle, Conf *conf); /* back->send() returns the current amount of buffered data. */ int (*send) (void *handle, char *buf, int len); /* back->sendbuffer() does the same thing but without attempting a send */ int (*sendbuffer) (void *handle); void (*size) (void *handle, int width, int height); @@ -459,230 +458,10 @@ * Name of this particular application, for use in the config box * and other pieces of text. */ extern const char *const appname; -/* - * IMPORTANT POLICY POINT: everything in this structure which wants - * to be treated like an integer must be an actual, honest-to- - * goodness `int'. No enum-typed variables. This is because parts - * of the code will want to pass around `int *' pointers to them - * and we can't run the risk of porting to some system on which the - * enum comes out as a different size from int. - */ -struct config_tag { - /* Basic options */ - char host[512]; - int port; - int protocol; - int addressfamily; - int close_on_exit; - int warn_on_close; - int ping_interval; /* in seconds */ - int tcp_nodelay; - int tcp_keepalives; - char loghost[512]; /* logical host being contacted, for host key check */ - /* Proxy options */ - char proxy_exclude_list[512]; - int proxy_dns; - int even_proxy_localhost; - int proxy_type; - char proxy_host[512]; - int proxy_port; - char proxy_username[128]; - char proxy_password[128]; - char proxy_telnet_command[512]; - /* SSH options */ - char remote_cmd[512]; - char *remote_cmd_ptr; /* might point to a larger command - * but never for loading/saving */ - char *remote_cmd_ptr2; /* might point to a larger command - * but never for loading/saving */ - int nopty; - int compression; - int ssh_kexlist[KEX_MAX]; - int ssh_rekey_time; /* in minutes */ - char ssh_rekey_data[16]; - int tryagent; - int agentfwd; - int change_username; /* allow username switching in SSH-2 */ - int ssh_cipherlist[CIPHER_MAX]; - Filename keyfile; - int sshprot; /* use v1 or v2 when both available */ - int ssh2_des_cbc; /* "des-cbc" unrecommended SSH-2 cipher */ - int ssh_no_userauth; /* bypass "ssh-userauth" (SSH-2 only) */ - int ssh_show_banner; /* show USERAUTH_BANNERs (SSH-2 only) */ - int try_tis_auth; - int try_ki_auth; - /* PuTTY SC start */ - int try_write_syslog; /* check box (not persistent) */ - int try_pkcs11_auth; /* check box */ - Filename pkcs11_libfile; /* token lib */ - void *sclib; /* sc's owned struct */ - char pkcs11_token_label[70]; /* token label */ - char pkcs11_cert_label[70]; /* cert label */ - /* PuTTY SC end */ - /* PuTTY CAPI start */ - int try_capi_auth; /* check box */ - char capi_certID[150]; /* {Machine|User}\{Store Name}\cert sha-1 fingerprint (in hex) */ - /* PuTTY end start */ - int try_gssapi_auth; /* attempt gssapi auth */ - int gssapifwd; /* forward tgt via gss */ - int ssh_gsslist[4]; /* preference order for local GSS libs */ - Filename ssh_gss_custom; - int ssh_subsys; /* run a subsystem rather than a command */ - int ssh_subsys2; /* fallback to go with remote_cmd_ptr2 */ - int ssh_no_shell; /* avoid running a shell */ - char ssh_nc_host[512]; /* host to connect to in `nc' mode */ - int ssh_nc_port; /* port to connect to in `nc' mode */ - /* Telnet options */ - char termtype[32]; - char termspeed[32]; - char ttymodes[768]; /* MODE\tVvalue\0MODE\tA\0\0 */ - char environmt[1024]; /* VAR\tvalue\0VAR\tvalue\0\0 */ - char username[100]; - int username_from_env; - char localusername[100]; - int rfc_environ; - int passive_telnet; - /* Serial port options */ - char serline[256]; - int serspeed; - int serdatabits, serstopbits; - int serparity; - int serflow; - /* Keyboard options */ - int bksp_is_delete; - int rxvt_homeend; - int funky_type; - int no_applic_c; /* totally disable app cursor keys */ - int no_applic_k; /* totally disable app keypad */ - int no_mouse_rep; /* totally disable mouse reporting */ - int no_remote_resize; /* disable remote resizing */ - int no_alt_screen; /* disable alternate screen */ - int no_remote_wintitle; /* disable remote retitling */ - int no_dbackspace; /* disable destructive backspace */ - int no_remote_charset; /* disable remote charset config */ - int remote_qtitle_action; /* remote win title query action */ - int app_cursor; - int app_keypad; - int nethack_keypad; - int telnet_keyboard; - int telnet_newline; - int alt_f4; /* is it special? */ - int alt_space; /* is it special? */ - int alt_only; /* is it special? */ - int localecho; - int localedit; - int alwaysontop; - int fullscreenonaltenter; - int scroll_on_key; - int scroll_on_disp; - int erase_to_scrollback; - int compose_key; - int ctrlaltkeys; - char wintitle[256]; /* initial window title */ - /* Terminal options */ - int savelines; - int dec_om; - int wrap_mode; - int lfhascr; - int cursor_type; /* 0=block 1=underline 2=vertical */ - int blink_cur; - int beep; - int beep_ind; - int bellovl; /* bell overload protection active? */ - int bellovl_n; /* number of bells to cause overload */ - int bellovl_t; /* time interval for overload (seconds) */ - int bellovl_s; /* period of silence to re-enable bell (s) */ - Filename bell_wavefile; - int scrollbar; - int scrollbar_in_fullscreen; - int resize_action; - int bce; - int blinktext; - int win_name_always; - int width, height; - FontSpec font; - int font_quality; - Filename logfilename; - int logtype; - int logxfovr; - int logflush; - int logomitpass; - int logomitdata; - int hide_mouseptr; - int sunken_edge; - int window_border; - char answerback[256]; - char printer[128]; - int arabicshaping; - int bidi; - /* Colour options */ - int ansi_colour; - int xterm_256_colour; - int system_colour; - int try_palette; - int bold_colour; - unsigned char colours[22][3]; - /* Selection options */ - int mouse_is_xterm; - int rect_select; - int rawcnp; - int rtf_paste; - int mouse_override; - short wordness[256]; - /* translations */ - int vtmode; - char line_codepage[128]; - int cjk_ambig_wide; - int utf8_override; - int xlat_capslockcyr; - /* X11 forwarding */ - int x11_forward; - char x11_display[128]; - int x11_auth; - Filename xauthfile; - /* port forwarding */ - int lport_acceptall; /* accept conns from hosts other than localhost */ - int rport_acceptall; /* same for remote forwarded ports (SSH-2 only) */ - /* - * The port forwarding string contains a number of - * NUL-terminated substrings, terminated in turn by an empty - * string (i.e. a second NUL immediately after the previous - * one). Each string can be of one of the following forms: - * - * [LR]localport\thost:port - * [LR]localaddr:localport\thost:port - * Dlocalport - * Dlocaladdr:localport - */ - char portfwd[1024]; - /* SSH bug compatibility modes */ - int sshbug_ignore1, sshbug_plainpw1, sshbug_rsa1, - sshbug_hmac2, sshbug_derivekey2, sshbug_rsapad2, - sshbug_pksessid2, sshbug_rekey2, sshbug_maxpkt2, - sshbug_ignore2; - /* - * ssh_simple means that we promise never to open any channel other - * than the main one, which means it can safely use a very large - * window in SSH-2. - */ - int ssh_simple; - /* Options for pterm. Should split out into platform-dependent part. */ - int stamp_utmp; - int login_shell; - int scrollbar_on_left; - int shadowbold; - FontSpec boldfont; - FontSpec widefont; - FontSpec wideboldfont; - int shadowboldoffset; - int crhaslf; - char winclass[256]; -}; - /* * Some global flags denoting the type of application. * * FLAG_VERBOSE is set when the user requests verbose details. * @@ -744,12 +523,23 @@ * `prompt[]' too.) */ typedef struct { char *prompt; int echo; - char *result; /* allocated/freed by caller */ - size_t result_len; + /* + * 'result' must be a dynamically allocated array of exactly + * 'resultsize' chars. The code for actually reading input may + * realloc it bigger (and adjust resultsize accordingly) if it has + * to. The caller should free it again when finished with it. + * + * If resultsize==0, then result may be NULL. When setting up a + * prompt_t, it's therefore easiest to initialise them this way, + * which means all actual allocation is done by the callee. This + * is what add_prompt does. + */ + char *result; + size_t resultsize; } prompt_t; typedef struct { /* * Indicates whether the information entered is to be used locally * (for instance a key passphrase prompt), or is destined for the wire. @@ -768,11 +558,13 @@ void *frontend; void *data; /* slot for housekeeping data, managed by * get_userpass_input(); initially NULL */ } prompts_t; prompts_t *new_prompts(void *frontend); -void add_prompt(prompts_t *p, char *promptstr, int echo, size_t len); +void add_prompt(prompts_t *p, char *promptstr, int echo); +void prompt_set_result(prompt_t *pr, const char *newstr); +void prompt_ensure_result_size(prompt_t *pr, int len); /* Burn the evidence. (Assumes _all_ strings want free()ing.) */ void free_prompts(prompts_t *p); /* * Exports from the front end. @@ -795,10 +587,11 @@ void write_clip(void *frontend, wchar_t *, int *, int, int); void get_clip(void *frontend, wchar_t **, int *); void optimised_move(void *frontend, int, int, int); void set_raw_mouse_mode(void *frontend, int); void connection_fatal(void *frontend, char *, ...); +void nonfatal(char *, ...); void fatalbox(char *, ...); void modalfatalbox(char *, ...); #ifdef macintosh #pragma noreturn(fatalbox) #pragma noreturn(modalfatalbox) @@ -814,10 +607,15 @@ * 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); int from_backend_untrusted(void *frontend, const char *data, int len); +/* Called when the back end wants to indicate that EOF has arrived on + * the server-to-client stream. Returns FALSE to indicate that we + * intend to keep the session open in the other direction, or TRUE to + * indicate that if they're closing so are we. */ +int from_backend_eof(void *frontend); void notify_remote_exit(void *frontend); /* Get a sensible value for a tty mode. NULL return = don't set. * Otherwise, returned value should be freed by caller. */ char *get_ttymode(void *frontend, const char *mode); /* @@ -848,10 +646,277 @@ }; void set_busy_status(void *frontend, int status); void cleanup_exit(int); +/* + * Exports from conf.c, and a big enum (via parametric macro) of + * configuration option keys. + */ +#define CONFIG_OPTIONS(X) \ + /* X(value-type, subkey-type, keyword) */ \ + X(STR, NONE, host) \ + X(INT, NONE, port) \ + X(INT, NONE, protocol) \ + X(INT, NONE, addressfamily) \ + X(INT, NONE, close_on_exit) \ + X(INT, NONE, warn_on_close) \ + X(INT, NONE, ping_interval) /* in seconds */ \ + X(INT, NONE, tcp_nodelay) \ + X(INT, NONE, tcp_keepalives) \ + X(STR, NONE, loghost) /* logical host being contacted, for host key check */ \ + /* Proxy options */ \ + X(STR, NONE, proxy_exclude_list) \ + X(INT, NONE, proxy_dns) \ + X(INT, NONE, even_proxy_localhost) \ + X(INT, NONE, proxy_type) \ + X(STR, NONE, proxy_host) \ + X(INT, NONE, proxy_port) \ + X(STR, NONE, proxy_username) \ + X(STR, NONE, proxy_password) \ + X(STR, NONE, proxy_telnet_command) \ + /* SSH options */ \ + X(STR, NONE, remote_cmd) \ + X(STR, NONE, remote_cmd2) /* fallback if remote_cmd fails; never loaded or saved */ \ + X(INT, NONE, nopty) \ + X(INT, NONE, compression) \ + X(INT, INT, ssh_kexlist) \ + X(INT, NONE, ssh_rekey_time) /* in minutes */ \ + X(STR, NONE, ssh_rekey_data) /* string encoding e.g. "100K", "2M", "1G" */ \ + X(INT, NONE, tryagent) \ + X(INT, NONE, agentfwd) \ + X(INT, NONE, change_username) /* allow username switching in SSH-2 */ \ + X(INT, INT, ssh_cipherlist) \ + X(FILENAME, NONE, keyfile) \ + X(INT, NONE, sshprot) /* use v1 or v2 when both available */ \ + X(INT, NONE, ssh2_des_cbc) /* "des-cbc" unrecommended SSH-2 cipher */ \ + X(INT, NONE, ssh_no_userauth) /* bypass "ssh-userauth" (SSH-2 only) */ \ + X(INT, NONE, ssh_show_banner) /* show USERAUTH_BANNERs (SSH-2 only) */ \ + X(INT, NONE, try_tis_auth) \ + X(INT, NONE, try_ki_auth) \ + X(INT, NONE, try_gssapi_auth) /* attempt gssapi auth */ \ + X(INT, NONE, gssapifwd) /* forward tgt via gss */ \ + X(INT, INT, ssh_gsslist) /* preference order for local GSS libs */ \ + X(FILENAME, NONE, ssh_gss_custom) \ + X(INT, NONE, ssh_subsys) /* run a subsystem rather than a command */ \ + X(INT, NONE, ssh_subsys2) /* fallback to go with remote_cmd_ptr2 */ \ + X(INT, NONE, ssh_no_shell) /* avoid running a shell */ \ + X(STR, NONE, ssh_nc_host) /* host to connect to in `nc' mode */ \ + X(INT, NONE, ssh_nc_port) /* port to connect to in `nc' mode */ \ + /* Telnet options */ \ + X(STR, NONE, termtype) \ + X(STR, NONE, termspeed) \ + X(STR, STR, ttymodes) /* values are "Vvalue" or "A" */ \ + X(STR, STR, environmt) \ + X(STR, NONE, username) \ + X(INT, NONE, username_from_env) \ + X(STR, NONE, localusername) \ + X(INT, NONE, rfc_environ) \ + X(INT, NONE, passive_telnet) \ + /* Serial port options */ \ + X(STR, NONE, serline) \ + X(INT, NONE, serspeed) \ + X(INT, NONE, serdatabits) \ + X(INT, NONE, serstopbits) \ + X(INT, NONE, serparity) \ + X(INT, NONE, serflow) \ + /* Keyboard options */ \ + X(INT, NONE, bksp_is_delete) \ + X(INT, NONE, rxvt_homeend) \ + X(INT, NONE, funky_type) \ + X(INT, NONE, no_applic_c) /* totally disable app cursor keys */ \ + X(INT, NONE, no_applic_k) /* totally disable app keypad */ \ + X(INT, NONE, no_mouse_rep) /* totally disable mouse reporting */ \ + X(INT, NONE, no_remote_resize) /* disable remote resizing */ \ + X(INT, NONE, no_alt_screen) /* disable alternate screen */ \ + X(INT, NONE, no_remote_wintitle) /* disable remote retitling */ \ + X(INT, NONE, no_dbackspace) /* disable destructive backspace */ \ + X(INT, NONE, no_remote_charset) /* disable remote charset config */ \ + X(INT, NONE, remote_qtitle_action) /* remote win title query action */ \ + X(INT, NONE, app_cursor) \ + X(INT, NONE, app_keypad) \ + X(INT, NONE, nethack_keypad) \ + X(INT, NONE, telnet_keyboard) \ + X(INT, NONE, telnet_newline) \ + X(INT, NONE, alt_f4) /* is it special? */ \ + X(INT, NONE, alt_space) /* is it special? */ \ + X(INT, NONE, alt_only) /* is it special? */ \ + X(INT, NONE, localecho) \ + X(INT, NONE, localedit) \ + X(INT, NONE, alwaysontop) \ + X(INT, NONE, fullscreenonaltenter) \ + X(INT, NONE, scroll_on_key) \ + X(INT, NONE, scroll_on_disp) \ + X(INT, NONE, erase_to_scrollback) \ + X(INT, NONE, compose_key) \ + X(INT, NONE, ctrlaltkeys) \ + X(STR, NONE, wintitle) /* initial window title */ \ + /* Terminal options */ \ + X(INT, NONE, savelines) \ + X(INT, NONE, dec_om) \ + X(INT, NONE, wrap_mode) \ + X(INT, NONE, lfhascr) \ + X(INT, NONE, cursor_type) /* 0=block 1=underline 2=vertical */ \ + X(INT, NONE, blink_cur) \ + X(INT, NONE, beep) \ + X(INT, NONE, beep_ind) \ + X(INT, NONE, bellovl) /* bell overload protection active? */ \ + X(INT, NONE, bellovl_n) /* number of bells to cause overload */ \ + X(INT, NONE, bellovl_t) /* time interval for overload (seconds) */ \ + X(INT, NONE, bellovl_s) /* period of silence to re-enable bell (s) */ \ + X(FILENAME, NONE, bell_wavefile) \ + X(INT, NONE, scrollbar) \ + X(INT, NONE, scrollbar_in_fullscreen) \ + X(INT, NONE, resize_action) \ + X(INT, NONE, bce) \ + X(INT, NONE, blinktext) \ + X(INT, NONE, win_name_always) \ + X(INT, NONE, width) \ + X(INT, NONE, height) \ + X(FONT, NONE, font) \ + X(INT, NONE, font_quality) \ + X(FILENAME, NONE, logfilename) \ + X(INT, NONE, logtype) \ + X(INT, NONE, logxfovr) \ + X(INT, NONE, logflush) \ + X(INT, NONE, logomitpass) \ + X(INT, NONE, logomitdata) \ + X(INT, NONE, hide_mouseptr) \ + X(INT, NONE, sunken_edge) \ + X(INT, NONE, window_border) \ + X(STR, NONE, answerback) \ + X(STR, NONE, printer) \ + X(INT, NONE, arabicshaping) \ + X(INT, NONE, bidi) \ + /* Colour options */ \ + X(INT, NONE, ansi_colour) \ + X(INT, NONE, xterm_256_colour) \ + X(INT, NONE, system_colour) \ + X(INT, NONE, try_palette) \ + X(INT, NONE, bold_style) \ + X(INT, INT, colours) \ + /* Selection options */ \ + X(INT, NONE, mouse_is_xterm) \ + X(INT, NONE, rect_select) \ + X(INT, NONE, rawcnp) \ + X(INT, NONE, rtf_paste) \ + X(INT, NONE, mouse_override) \ + X(INT, INT, wordness) \ + /* translations */ \ + X(INT, NONE, vtmode) \ + X(STR, NONE, line_codepage) \ + X(INT, NONE, cjk_ambig_wide) \ + X(INT, NONE, utf8_override) \ + X(INT, NONE, xlat_capslockcyr) \ + /* X11 forwarding */ \ + X(INT, NONE, x11_forward) \ + X(STR, NONE, x11_display) \ + X(INT, NONE, x11_auth) \ + X(FILENAME, NONE, xauthfile) \ + /* port forwarding */ \ + X(INT, NONE, lport_acceptall) /* accept conns from hosts other than localhost */ \ + X(INT, NONE, rport_acceptall) /* same for remote forwarded ports (SSH-2 only) */ \ + /* \ + * Subkeys for 'portfwd' can have the following forms: \ + * \ + * [LR]localport \ + * [LR]localaddr:localport \ + * \ + * Dynamic forwardings are indicated by an 'L' key, and the \ + * special value "D". For all other forwardings, the value \ + * should be of the form 'host:port'. \ + */ \ + X(STR, STR, portfwd) \ + /* SSH bug compatibility modes */ \ + X(INT, NONE, sshbug_ignore1) \ + X(INT, NONE, sshbug_plainpw1) \ + X(INT, NONE, sshbug_rsa1) \ + X(INT, NONE, sshbug_hmac2) \ + X(INT, NONE, sshbug_derivekey2) \ + X(INT, NONE, sshbug_rsapad2) \ + X(INT, NONE, sshbug_pksessid2) \ + X(INT, NONE, sshbug_rekey2) \ + X(INT, NONE, sshbug_maxpkt2) \ + X(INT, NONE, sshbug_ignore2) \ + X(INT, NONE, sshbug_winadj) \ + /* \ + * ssh_simple means that we promise never to open any channel \ + * other than the main one, which means it can safely use a very \ + * large window in SSH-2. \ + */ \ + X(INT, NONE, ssh_simple) \ + X(INT, NONE, ssh_connection_sharing) \ + X(INT, NONE, ssh_connection_sharing_upstream) \ + X(INT, NONE, ssh_connection_sharing_downstream) \ + /* Options for pterm. Should split out into platform-dependent part. */ \ + X(INT, NONE, stamp_utmp) \ + X(INT, NONE, login_shell) \ + X(INT, NONE, scrollbar_on_left) \ + X(INT, NONE, shadowbold) \ + X(FONT, NONE, boldfont) \ + X(FONT, NONE, widefont) \ + X(FONT, NONE, wideboldfont) \ + X(INT, NONE, shadowboldoffset) \ + X(INT, NONE, crhaslf) \ + X(STR, NONE, winclass) \ + +/* Now define the actual enum of option keywords using that macro. */ +#define CONF_ENUM_DEF(valtype, keytype, keyword) CONF_ ## keyword, +enum config_primary_key { CONFIG_OPTIONS(CONF_ENUM_DEF) N_CONFIG_OPTIONS }; +#undef CONF_ENUM_DEF + +#define NCFGCOLOURS 22 /* number of colours in CONF_colours above */ + +/* Functions handling configuration structures. */ +Conf *conf_new(void); /* create an empty configuration */ +void conf_free(Conf *conf); +Conf *conf_copy(Conf *oldconf); +void conf_copy_into(Conf *dest, Conf *src); +/* Mandatory accessor functions: enforce by assertion that keys exist. */ +int conf_get_int(Conf *conf, int key); +int conf_get_int_int(Conf *conf, int key, int subkey); +char *conf_get_str(Conf *conf, int key); /* result still owned by conf */ +char *conf_get_str_str(Conf *conf, int key, const char *subkey); +Filename *conf_get_filename(Conf *conf, int key); +FontSpec *conf_get_fontspec(Conf *conf, int key); /* still owned by conf */ +/* Optional accessor function: return NULL if key does not exist. */ +char *conf_get_str_str_opt(Conf *conf, int key, const char *subkey); +/* Accessor function to step through a string-subkeyed list. + * Returns the next subkey after the provided one, or the first if NULL. + * Returns NULL if there are none left. + * Both the return value and *subkeyout are still owned by conf. */ +char *conf_get_str_strs(Conf *conf, int key, char *subkeyin, char **subkeyout); +/* Return the nth string subkey in a list. Owned by conf. NULL if beyond end */ +char *conf_get_str_nthstrkey(Conf *conf, int key, int n); +/* Functions to set entries in configuration. Always copy their inputs. */ +void conf_set_int(Conf *conf, int key, int value); +void conf_set_int_int(Conf *conf, int key, int subkey, int value); +void conf_set_str(Conf *conf, int key, const char *value); +void conf_set_str_str(Conf *conf, int key, + const char *subkey, const char *val); +void conf_del_str_str(Conf *conf, int key, const char *subkey); +void conf_set_filename(Conf *conf, int key, const Filename *val); +void conf_set_fontspec(Conf *conf, int key, const FontSpec *val); +/* Serialisation functions for Duplicate Session */ +int conf_serialised_size(Conf *conf); +void conf_serialise(Conf *conf, void *data); +int conf_deserialise(Conf *conf, void *data, int maxsize);/*returns size used*/ + +/* + * Functions to copy, free, serialise and deserialise FontSpecs. + * Provided per-platform, to go with the platform's idea of a + * FontSpec's contents. + * + * fontspec_serialise returns the number of bytes written, and can + * handle data==NULL without crashing. So you can call it once to find + * out a size, then again once you've allocated a buffer. + */ +FontSpec *fontspec_copy(const FontSpec *f); +void fontspec_free(FontSpec *f); +int fontspec_serialise(FontSpec *f, void *data); +FontSpec *fontspec_deserialise(void *data, int maxsize, int *used); + /* * Exports from noise.c. */ void noise_get_heavy(void (*func) (void *, int)); void noise_get_light(void (*func) (void *, int)); @@ -863,17 +928,17 @@ /* * Exports from settings.c. */ Backend *backend_from_name(const char *name); Backend *backend_from_proto(int proto); -int get_remote_username(Config *cfg, char *user, size_t len); -char *save_settings(char *section, Config * cfg); -void save_open_settings(void *sesskey, Config *cfg); -void load_settings(char *section, Config * cfg); -void load_open_settings(void *sesskey, Config *cfg); +char *get_remote_username(Conf *conf); /* dynamically allocated */ +char *save_settings(char *section, Conf *conf); +void save_open_settings(void *sesskey, Conf *conf); +void load_settings(char *section, Conf *conf); +void load_open_settings(void *sesskey, Conf *conf); void get_sesslist(struct sesslist *, int allocate); -void do_defaults(char *, Config *); +void do_defaults(char *, Conf *); void registry_cleanup(void); /* * Functions used by settings.c to provide platform-specific * default settings. @@ -882,21 +947,25 @@ * opinion of its own. This is because there's no integer value * which I can reliably set aside to indicate `nil'. The string * function is perfectly all right returning NULL, of course. The * Filename and FontSpec functions are _not allowed_ to fail to * return, since these defaults _must_ be per-platform.) + * + * The 'Filename *' returned by platform_default_filename, and the + * 'FontSpec *' returned by platform_default_fontspec, have ownership + * transferred to the caller, and must be freed. */ char *platform_default_s(const char *name); int platform_default_i(const char *name, int def); -Filename platform_default_filename(const char *name); -FontSpec platform_default_fontspec(const char *name); +Filename *platform_default_filename(const char *name); +FontSpec *platform_default_fontspec(const char *name); /* * Exports from terminal.c. */ -Terminal *term_init(Config *, struct unicode_data *, void *); +Terminal *term_init(Conf *, struct unicode_data *, void *); void term_free(Terminal *); void term_size(Terminal *, int, int, int); void term_paint(Terminal *, Context, int, int, int, int, int); void term_scroll(Terminal *, int, int); void term_scroll_to_selection(Terminal *, int); @@ -909,16 +978,14 @@ void term_deselect(Terminal *); void term_update(Terminal *); void term_invalidate(Terminal *); void term_blink(Terminal *, int set_cursor); void term_do_paste(Terminal *); -int term_paste_pending(Terminal *); -void term_paste(Terminal *); void term_nopaste(Terminal *); int term_ldisc(Terminal *, int option); void term_copyall(Terminal *); -void term_reconfig(Terminal *, Config *); +void term_reconfig(Terminal *, Conf *); void term_seen_key_event(Terminal *); int term_data(Terminal *, int is_stderr, const char *data, int len); int term_data_untrusted(Terminal *, const char *data, int len); void term_provide_resize_fn(Terminal *term, void (*resize_fn)(void *, int, int), @@ -932,13 +999,13 @@ int format_arrow_key(char *buf, Terminal *term, int xkey, int ctrl); /* * Exports from logging.c. */ -void *log_init(void *frontend, Config *cfg); +void *log_init(void *frontend, Conf *conf); void log_free(void *logctx); -void log_reconfig(void *logctx, Config *cfg); +void log_reconfig(void *logctx, Conf *conf); void logfopen(void *logctx); void logfclose(void *logctx); void logtraffic(void *logctx, unsigned char c, int logmode); void logflush(void *logctx); void log_eventlog(void *logctx, const char *string); @@ -950,11 +1017,12 @@ int type; }; void log_packet(void *logctx, int direction, int type, char *texttype, const void *data, int len, int n_blanks, const struct logblank_t *blanks, - const unsigned long *sequence); + const unsigned long *sequence, + unsigned downstream_id, const char *additional_log_text); /* * Exports from testback.c */ @@ -985,11 +1053,12 @@ extern Backend ssh_backend; /* * Exports from ldisc.c. */ -void *ldisc_create(Config *, Terminal *, Backend *, void *, void *); +void *ldisc_create(Conf *, Terminal *, Backend *, void *, void *); +void ldisc_configure(void *, Conf *); void ldisc_free(void *); void ldisc_send(void *handle, char *buf, int len, int interactive); /* * Exports from ldiscucs.c. @@ -1013,21 +1082,21 @@ /* * Exports from pinger.c. */ typedef struct pinger_tag *Pinger; -Pinger pinger_new(Config *cfg, Backend *back, void *backhandle); -void pinger_reconfig(Pinger, Config *oldcfg, Config *newcfg); +Pinger pinger_new(Conf *conf, Backend *back, void *backhandle); +void pinger_reconfig(Pinger, Conf *oldconf, Conf *newconf); void pinger_free(Pinger); /* * Exports from misc.c. */ #include "misc.h" -int cfg_launchable(const Config *cfg); -char const *cfg_dest(const Config *cfg); +int conf_launchable(Conf *conf); +char const *conf_dest(Conf *conf); /* * Exports from sercfg.c. */ void ser_setup_config_box(struct controlbox *b, int midsession, @@ -1044,13 +1113,13 @@ #ifndef CP_UTF8 #define CP_UTF8 65001 #endif /* void init_ucs(void); -- this is now in platform-specific headers */ int is_dbcs_leadbyte(int codepage, char byte); -int mb_to_wc(int codepage, int flags, char *mbstr, int mblen, +int mb_to_wc(int codepage, int flags, const char *mbstr, int mblen, wchar_t *wcstr, int wclen); -int wc_to_mb(int codepage, int flags, wchar_t *wcstr, int wclen, +int wc_to_mb(int codepage, int flags, const wchar_t *wcstr, int wclen, char *mbstr, int mblen, char *defchr, int *defused, struct unicode_data *ucsdata); wchar_t xlat_uskbd2cyrllic(int ch); int check_compose(int first, int second); int decode_codepage(char *cp_name); @@ -1059,14 +1128,14 @@ void get_unitab(int codepage, wchar_t * unitab, int ftype); /* * Exports from wcwidth.c */ -int mk_wcwidth(wchar_t ucs); -int mk_wcswidth(const wchar_t *pwcs, size_t n); -int mk_wcwidth_cjk(wchar_t ucs); -int mk_wcswidth_cjk(const wchar_t *pwcs, size_t n); +int mk_wcwidth(unsigned int ucs); +int mk_wcswidth(const unsigned int *pwcs, size_t n); +int mk_wcwidth_cjk(unsigned int ucs); +int mk_wcswidth_cjk(const unsigned int *pwcs, size_t n); /* * Exports from mscrypto.c */ #ifdef MSCRYPTOAPI @@ -1128,11 +1197,11 @@ * - 2 means overwrite the log file * - 1 means append to the log file * - 0 means cancel logging for this session * - -1 means please wait. */ -int askappend(void *frontend, Filename filename, +int askappend(void *frontend, Filename *filename, void (*callback)(void *ctx, int result), void *ctx); /* * Exports from console frontends (wincons.c, uxcons.c) * that aren't equivalents to things in windlg.c et al. @@ -1157,12 +1226,12 @@ /* * Exports from cmdline.c (and also cmdline_error(), which is * defined differently in various places and required _by_ * cmdline.c). */ -int cmdline_process_param(char *, char *, int, Config *); -void cmdline_run_saved(Config *); +int cmdline_process_param(char *, char *, int, Conf *); +void cmdline_run_saved(Conf *); void cmdline_cleanup(void); int cmdline_get_passwd_input(prompts_t *p, unsigned char *in, int inlen); #define TOOLTYPE_FILETRANSFER 1 #define TOOLTYPE_NONNETWORK 2 extern int cmdline_tooltype; @@ -1171,18 +1240,30 @@ /* * Exports from config.c. */ struct controlbox; +union control; +void conf_radiobutton_handler(union control *ctrl, void *dlg, + void *data, int event); +#define CHECKBOX_INVERT (1<<30) +void conf_checkbox_handler(union control *ctrl, void *dlg, + void *data, int event); +void conf_editbox_handler(union control *ctrl, void *dlg, + void *data, int event); +void conf_filesel_handler(union control *ctrl, void *dlg, + void *data, int event); +void conf_fontsel_handler(union control *ctrl, void *dlg, + void *data, int event); void setup_config_box(struct controlbox *b, int midsession, int protocol, int protcfginfo); /* * Exports from minibidi.c. */ typedef struct bidi_char { - wchar_t origwc, wc; + unsigned int origwc, wc; unsigned short index; } bidi_char; int do_bidi(bidi_char *line, int count); int do_shape(bidi_char *line, bidi_char *to, int count); int is_rtl(int c); @@ -1198,15 +1279,22 @@ }; extern const char *const x11_authnames[]; /* declared in x11fwd.c */ /* * Miscellaneous exports from the platform-specific code. + * + * filename_serialise and filename_deserialise have the same semantics + * as fontspec_serialise and fontspec_deserialise above. */ -Filename filename_from_str(const char *string); +Filename *filename_from_str(const char *string); const char *filename_to_str(const Filename *fn); -int filename_equal(Filename f1, Filename f2); -int filename_is_null(Filename fn); +int filename_equal(const Filename *f1, const Filename *f2); +int filename_is_null(const Filename *fn); +Filename *filename_copy(const Filename *fn); +void filename_free(Filename *fn); +int filename_serialise(const Filename *f, void *data); +Filename *filename_deserialise(void *data, int maxsize, int *used); char *get_username(void); /* return value needs freeing */ char *get_random_data(int bytes); /* used in cmdgen.c */ /* * Exports and imports from timing.c. @@ -1296,15 +1384,45 @@ * the hypothetical 1% error in wait() will be partially corrected * for anyway when, _after_ run_timers() returns, you call * GETTICKCOUNT() and compare the result with the returned `next' * value to find out how long you have to make your next wait().) */ -typedef void (*timer_fn_t)(void *ctx, long now); -long schedule_timer(int ticks, timer_fn_t fn, void *ctx); +typedef void (*timer_fn_t)(void *ctx, unsigned long now); +unsigned long schedule_timer(int ticks, timer_fn_t fn, void *ctx); void expire_timer_context(void *ctx); -int run_timers(long now, long *next); -void timer_change_notify(long next); +int run_timers(unsigned long now, unsigned long *next); +void timer_change_notify(unsigned long next); + +/* + * Exports from callback.c. + * + * This provides a method of queuing function calls to be run at the + * earliest convenience from the top-level event loop. Use it if + * you're deep in a nested chain of calls and want to trigger an + * action which will probably lead to your function being re-entered + * recursively if you just call the initiating function the normal + * way. + * + * Most front ends run the queued callbacks by simply calling + * run_toplevel_callbacks() after handling each event in their + * top-level event loop. However, if a front end doesn't have control + * over its own event loop (e.g. because it's using GTK) then it can + * instead request notifications when a callback is available, so that + * it knows to ask its delegate event loop to do the same thing. Also, + * if a front end needs to know whether a callback is pending without + * actually running it (e.g. so as to put a zero timeout on a select() + * call) then it can call toplevel_callback_pending(), which will + * return true if at least one callback is in the queue. + */ +typedef void (*toplevel_callback_fn_t)(void *ctx); +void queue_toplevel_callback(toplevel_callback_fn_t fn, void *ctx); +void run_toplevel_callbacks(void); +int toplevel_callback_pending(void); + +typedef void (*toplevel_callback_notify_fn_t)(void *frontend); +void request_callback_notifications(toplevel_callback_notify_fn_t notify, + void *frontend); /* * Define no-op macros for the jump list functions, on platforms that * don't support them. (This is a bit of a hack, and it'd be nicer to * localise even the calls to those functions into the Windows front @@ -1313,6 +1431,31 @@ #ifndef JUMPLIST_SUPPORTED #define add_session_to_jumplist(x) ((void)0) #define remove_session_from_jumplist(x) ((void)0) #endif +/* SURROGATE PAIR */ +#ifndef IS_HIGH_SURROGATE +#define HIGH_SURROGATE_START 0xd800 +#define HIGH_SURROGATE_END 0xdbff +#define LOW_SURROGATE_START 0xdc00 +#define LOW_SURROGATE_END 0xdfff + +#define IS_HIGH_SURROGATE(wch) (((wch) >= HIGH_SURROGATE_START) && \ + ((wch) <= HIGH_SURROGATE_END)) +#define IS_LOW_SURROGATE(wch) (((wch) >= LOW_SURROGATE_START) && \ + ((wch) <= LOW_SURROGATE_END)) +#define IS_SURROGATE_PAIR(hs, ls) (IS_HIGH_SURROGATE(hs) && \ + IS_LOW_SURROGATE(ls)) +#endif + + +#define IS_SURROGATE(wch) (((wch) >= HIGH_SURROGATE_START) && \ + ((wch) <= LOW_SURROGATE_END)) +#define HIGH_SURROGATE_OF(codept) \ + (HIGH_SURROGATE_START + (((codept) - 0x10000) >> 10)) +#define LOW_SURROGATE_OF(codept) \ + (LOW_SURROGATE_START + (((codept) - 0x10000) & 0x3FF)) +#define FROM_SURROGATES(wch1, wch2) \ + (0x10000 + (((wch1) & 0x3FF) << 10) + ((wch2) & 0x3FF)) + #endif Index: puttymem.h ================================================================== --- puttymem.h +++ puttymem.h @@ -32,11 +32,21 @@ /* * Direct use of smalloc within the code should be avoided where * possible, in favour of these type-casting macros which ensure * you don't mistakenly allocate enough space for one sort of * structure and assign it to a different sort of pointer. + * + * The nasty trick in sresize with sizeof arranges for the compiler, + * in passing, to type-check the expression ((type *)0 == (ptr)), i.e. + * to type-check that the input pointer is a pointer to the correct + * type. The construction sizeof(stuff) ? (b) : (b) looks like a + * violation of the first principle of safe macros, but in fact it's + * OK - although it _expands_ the macro parameter more than once, it + * only _evaluates_ it once, so it's still side-effect safe. */ #define snew(type) ((type *)snmalloc(1, sizeof(type))) #define snewn(n, type) ((type *)snmalloc((n), sizeof(type))) -#define sresize(ptr, n, type) ((type *)snrealloc((ptr), (n), sizeof(type))) +#define sresize(ptr, n, type) \ + ((type *)snrealloc(sizeof((type *)0 == (ptr)) ? (ptr) : (ptr), \ + (n), sizeof(type))) #endif Index: raw.c ================================================================== --- raw.c +++ raw.c @@ -2,10 +2,11 @@ * "Raw" backend. */ #include #include +#include #include "putty.h" #ifndef FALSE #define FALSE 0 @@ -19,12 +20,14 @@ typedef struct raw_backend_data { const struct plug_function_table *fn; /* the above field _must_ be first in the structure */ Socket s; + int closed_on_socket_error; int bufsize; void *frontend; + int sent_console_eof, sent_socket_eof; } *Raw; static void raw_size(void *handle, int width, int height); static void c_write(Raw raw, char *buf, int len) @@ -45,27 +48,59 @@ msg = dupprintf("Connecting to %s port %d", addrbuf, port); else msg = dupprintf("Failed to connect to %s: %s", addrbuf, error_msg); logevent(raw->frontend, msg); + sfree(msg); +} + +static void raw_check_close(Raw raw) +{ + /* + * Called after we send EOF on either the socket or the console. + * Its job is to wind up the session once we have sent EOF on both. + */ + if (raw->sent_console_eof && raw->sent_socket_eof) { + if (raw->s) { + sk_close(raw->s); + raw->s = NULL; + notify_remote_exit(raw->frontend); + } + } } static int raw_closing(Plug plug, const char *error_msg, int error_code, int calling_back) { Raw raw = (Raw) plug; - if (raw->s) { - sk_close(raw->s); - raw->s = NULL; - notify_remote_exit(raw->frontend); - } if (error_msg) { - /* A socket error has occurred. */ - logevent(raw->frontend, error_msg); - connection_fatal(raw->frontend, "%s", error_msg); - } /* Otherwise, the remote side closed the connection normally. */ + /* A socket error has occurred. */ + if (raw->s) { + sk_close(raw->s); + raw->s = NULL; + raw->closed_on_socket_error = TRUE; + notify_remote_exit(raw->frontend); + } + logevent(raw->frontend, error_msg); + connection_fatal(raw->frontend, "%s", error_msg); + } else { + /* Otherwise, the remote side closed the connection normally. */ + if (!raw->sent_console_eof && from_backend_eof(raw->frontend)) { + /* + * The front end wants us to close the outgoing side of the + * connection as soon as we see EOF from the far end. + */ + if (!raw->sent_socket_eof) { + if (raw->s) + sk_write_eof(raw->s); + raw->sent_socket_eof= TRUE; + } + } + raw->sent_console_eof = TRUE; + raw_check_close(raw); + } return 0; } static int raw_receive(Plug plug, int urgent, char *data, int len) { @@ -87,11 +122,11 @@ * * Also places the canonical host name into `realhost'. It must be * freed by the caller. */ static const char *raw_init(void *frontend_handle, void **backend_handle, - Config *cfg, + Conf *conf, char *host, int port, char **realhost, int nodelay, int keepalive) { static const struct plug_function_table fn_table = { raw_log, @@ -100,31 +135,36 @@ raw_sent }; SockAddr addr; const char *err; Raw raw; + int addressfamily; + char *loghost; raw = snew(struct raw_backend_data); raw->fn = &fn_table; raw->s = NULL; + raw->closed_on_socket_error = FALSE; *backend_handle = raw; + raw->sent_console_eof = raw->sent_socket_eof = FALSE; raw->frontend = frontend_handle; + addressfamily = conf_get_int(conf, CONF_addressfamily); /* * Try to find host. */ { char *buf; buf = dupprintf("Looking up host \"%s\"%s", host, - (cfg->addressfamily == ADDRTYPE_IPV4 ? " (IPv4)" : - (cfg->addressfamily == ADDRTYPE_IPV6 ? " (IPv6)" : + (addressfamily == ADDRTYPE_IPV4 ? " (IPv4)" : + (addressfamily == ADDRTYPE_IPV6 ? " (IPv6)" : ""))); logevent(raw->frontend, buf); sfree(buf); } - addr = name_lookup(host, port, realhost, cfg, cfg->addressfamily); + addr = name_lookup(host, port, realhost, conf, addressfamily); if ((err = sk_addr_error(addr)) != NULL) { sk_addr_free(addr); return err; } @@ -133,19 +173,20 @@ /* * Open socket. */ raw->s = new_connection(addr, *realhost, port, 0, 1, nodelay, keepalive, - (Plug) raw, cfg); + (Plug) raw, conf); if ((err = sk_socket_error(raw->s)) != NULL) return err; - if (*cfg->loghost) { + loghost = conf_get_str(conf, CONF_loghost); + if (*loghost) { char *colon; sfree(*realhost); - *realhost = dupstr(cfg->loghost); + *realhost = dupstr(loghost); colon = strrchr(*realhost, ':'); if (colon) { /* * FIXME: if we ever update this aspect of ssh.c for * IPv6 literal management, this should change in line @@ -168,11 +209,11 @@ } /* * Stub routine (we don't have any need to reconfigure this backend). */ -static void raw_reconfig(void *handle, Config *cfg) +static void raw_reconfig(void *handle, Conf *conf) { } /* * Called to send data down the raw connection. @@ -206,15 +247,21 @@ /* Do nothing! */ return; } /* - * Send raw special codes. + * Send raw special codes. We only handle outgoing EOF here. */ static void raw_special(void *handle, Telnet_Special code) { - /* Do nothing! */ + Raw raw = (Raw) handle; + if (code == TS_EOF && raw->s) { + sk_write_eof(raw->s); + raw->sent_socket_eof= TRUE; + raw_check_close(raw); + } + return; } /* * Return a list of the special codes that make sense in this @@ -262,10 +309,12 @@ static int raw_exitcode(void *handle) { Raw raw = (Raw) handle; if (raw->s != NULL) return -1; /* still connected */ + else if (raw->closed_on_socket_error) + return INT_MAX; /* a socket error counts as an unclean exit */ else /* Exit codes are a meaningless concept in the Raw protocol */ return 0; } Index: rlogin.c ================================================================== --- rlogin.c +++ rlogin.c @@ -2,10 +2,11 @@ * Rlogin backend. */ #include #include +#include #include #include "putty.h" #ifndef FALSE @@ -20,17 +21,18 @@ typedef struct rlogin_tag { const struct plug_function_table *fn; /* the above field _must_ be first in the structure */ Socket s; + int closed_on_socket_error; int bufsize; int firstbyte; int cansize; int term_width, term_height; void *frontend; - Config cfg; + Conf *conf; /* In case we need to read a username from the terminal before starting */ prompts_t *prompt; } *Rlogin; @@ -54,19 +56,29 @@ msg = dupprintf("Connecting to %s port %d", addrbuf, port); else msg = dupprintf("Failed to connect to %s: %s", addrbuf, error_msg); logevent(rlogin->frontend, msg); + sfree(msg); } static int rlogin_closing(Plug plug, const char *error_msg, int error_code, int calling_back) { Rlogin rlogin = (Rlogin) plug; + + /* + * We don't implement independent EOF in each direction for Telnet + * connections; as soon as we get word that the remote side has + * sent us EOF, we wind up the whole connection. + */ + if (rlogin->s) { sk_close(rlogin->s); rlogin->s = NULL; + if (error_msg) + rlogin->closed_on_socket_error = TRUE; notify_remote_exit(rlogin->frontend); } if (error_msg) { /* A socket error has occurred. */ logevent(rlogin->frontend, error_msg); @@ -120,22 +132,22 @@ static void rlogin_startup(Rlogin rlogin, const char *ruser) { char z = 0; char *p; - sk_write(rlogin->s, &z, 1); - sk_write(rlogin->s, rlogin->cfg.localusername, - strlen(rlogin->cfg.localusername)); - sk_write(rlogin->s, &z, 1); - sk_write(rlogin->s, ruser, - strlen(ruser)); - sk_write(rlogin->s, &z, 1); - sk_write(rlogin->s, rlogin->cfg.termtype, - strlen(rlogin->cfg.termtype)); - sk_write(rlogin->s, "/", 1); - for (p = rlogin->cfg.termspeed; isdigit((unsigned char)*p); p++) continue; - sk_write(rlogin->s, rlogin->cfg.termspeed, p - rlogin->cfg.termspeed); + + sk_write(rlogin->s, &z, 1); + p = conf_get_str(rlogin->conf, CONF_localusername); + sk_write(rlogin->s, p, strlen(p)); + sk_write(rlogin->s, &z, 1); + sk_write(rlogin->s, ruser, strlen(ruser)); + sk_write(rlogin->s, &z, 1); + p = conf_get_str(rlogin->conf, CONF_termtype); + sk_write(rlogin->s, p, strlen(p)); + sk_write(rlogin->s, "/", 1); + p = conf_get_str(rlogin->conf, CONF_termspeed); + sk_write(rlogin->s, p, strspn(p, "0123456789")); rlogin->bufsize = sk_write(rlogin->s, &z, 1); rlogin->prompt = NULL; } @@ -146,11 +158,11 @@ * * Also places the canonical host name into `realhost'. It must be * freed by the caller. */ static const char *rlogin_init(void *frontend_handle, void **backend_handle, - Config *cfg, + Conf *conf, char *host, int port, char **realhost, int nodelay, int keepalive) { static const struct plug_function_table fn_table = { rlogin_log, @@ -159,37 +171,41 @@ rlogin_sent }; SockAddr addr; const char *err; Rlogin rlogin; - char ruser[sizeof(cfg->username)]; + char *ruser; + int addressfamily; + char *loghost; rlogin = snew(struct rlogin_tag); rlogin->fn = &fn_table; rlogin->s = NULL; + rlogin->closed_on_socket_error = FALSE; rlogin->frontend = frontend_handle; - rlogin->term_width = cfg->width; - rlogin->term_height = cfg->height; + rlogin->term_width = conf_get_int(conf, CONF_width); + rlogin->term_height = conf_get_int(conf, CONF_height); rlogin->firstbyte = 1; rlogin->cansize = 0; rlogin->prompt = NULL; - rlogin->cfg = *cfg; /* STRUCTURE COPY */ + rlogin->conf = conf_copy(conf); *backend_handle = rlogin; + addressfamily = conf_get_int(conf, CONF_addressfamily); /* * Try to find host. */ { char *buf; buf = dupprintf("Looking up host \"%s\"%s", host, - (cfg->addressfamily == ADDRTYPE_IPV4 ? " (IPv4)" : - (cfg->addressfamily == ADDRTYPE_IPV6 ? " (IPv6)" : + (addressfamily == ADDRTYPE_IPV4 ? " (IPv4)" : + (addressfamily == ADDRTYPE_IPV6 ? " (IPv6)" : ""))); logevent(rlogin->frontend, buf); sfree(buf); } - addr = name_lookup(host, port, realhost, cfg, cfg->addressfamily); + addr = name_lookup(host, port, realhost, conf, addressfamily); if ((err = sk_addr_error(addr)) != NULL) { sk_addr_free(addr); return err; } @@ -198,19 +214,20 @@ /* * Open socket. */ rlogin->s = new_connection(addr, *realhost, port, 1, 0, - nodelay, keepalive, (Plug) rlogin, cfg); + nodelay, keepalive, (Plug) rlogin, conf); if ((err = sk_socket_error(rlogin->s)) != NULL) return err; - if (*cfg->loghost) { + loghost = conf_get_str(conf, CONF_loghost); + if (*loghost) { char *colon; sfree(*realhost); - *realhost = dupstr(cfg->loghost); + *realhost = dupstr(loghost); colon = strrchr(*realhost, ':'); if (colon) { /* * FIXME: if we ever update this aspect of ssh.c for * IPv6 literal management, this should change in line @@ -224,20 +241,20 @@ * Send local username, remote username, terminal type and * terminal speed - unless we don't have the remote username yet, * in which case we prompt for it and may end up deferring doing * anything else until the local prompt mechanism returns. */ - if (get_remote_username(cfg, ruser, sizeof(ruser))) { + if ((ruser = get_remote_username(conf)) != NULL) { rlogin_startup(rlogin, ruser); + sfree(ruser); } else { int ret; rlogin->prompt = new_prompts(rlogin->frontend); rlogin->prompt->to_server = TRUE; rlogin->prompt->name = dupstr("Rlogin login name"); - add_prompt(rlogin->prompt, dupstr("rlogin username: "), TRUE, - sizeof(cfg->username)); + add_prompt(rlogin->prompt, dupstr("rlogin username: "), TRUE); ret = get_userpass_input(rlogin->prompt, NULL, 0); if (ret >= 0) { rlogin_startup(rlogin, rlogin->prompt->prompts[0]->result); } } @@ -251,17 +268,18 @@ if (rlogin->prompt) free_prompts(rlogin->prompt); if (rlogin->s) sk_close(rlogin->s); + conf_free(rlogin->conf); sfree(rlogin); } /* * Stub routine (we don't have any need to reconfigure this backend). */ -static void rlogin_reconfig(void *handle, Config *cfg) +static void rlogin_reconfig(void *handle, Conf *conf) { } /* * Called to send data down the rlogin connection. @@ -378,10 +396,12 @@ static int rlogin_exitcode(void *handle) { Rlogin rlogin = (Rlogin) handle; if (rlogin->s != NULL) return -1; /* still connected */ + else if (rlogin->closed_on_socket_error) + return INT_MAX; /* a socket error counts as an unclean exit */ else /* If we ever implement RSH, we'll probably need to do this properly */ return 0; } DELETED sc.c Index: sc.c ================================================================== --- sc.c +++ /dev/null @@ -1,966 +0,0 @@ -/* -*-mode: c; indent-tabs-mode: nil; c-basic-offset: 4; -*- */ -/* - * This file is part of PuTTY SC, a modification of PuTTY - * supporting smartcard for authentication. - * - * PuTTY SC is available at http://www.joebar.ch/puttysc/ - * - * Copyright (C) 2005-2007 Pascal Buchbinder - * - * This program 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 2 - * of the License, or (at your option) any later version. - * - * This program 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 this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, - * MA 02110-1301, USA. - * - */ - -#include -#include -#include -#include -#include - -/* putty */ -#include "ssh.h" -#include "pkcs11.h" - -/* this */ -#include "sc.h" - -#ifndef FALSE -#define FALSE 0 -#endif -#ifndef TRUE -#define TRUE 1 -#endif - -static const char rcsid_sc_c[] = "$Id: sc.c,v 1.34 2007/03/04 20:41:56 pbu Exp $"; - -/* this is the OID for rsaEncryption plus a required null object as the parameter field. */ -/* for more on how ASN.1 works, see http://luca.ntop.org/Teaching/Appunti/asn1.html */ - -static const char OIDrsaEncryption[] = { 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00 }; -int find_substring( char * haystack, long haysize, const char* needle, long needlesize) ; -unsigned int asn1_length(unsigned char** cursor) ; -int extract_key_from_cert(unsigned char* cert, int certsize, unsigned char **expo, unsigned char** modu, - int* elen, int* mlen); - - -#define SC_PUT_32BIT(cp, value) { \ - (cp)[0] = (unsigned char)((value) >> 24); \ - (cp)[1] = (unsigned char)((value) >> 16); \ - (cp)[2] = (unsigned char)((value) >> 8); \ - (cp)[3] = (unsigned char)(value); } - -static const u_char id_sha1[] = { - 0x30, 0x21, 0x30, 0x09, - 0x06, 0x05, 0x2b, 0x0e, - 0x03, 0x02, 0x1a, 0x05, - 0x00, 0x04, 0x14 -}; - -#define SC_STR_MAX_LEN 8192 -#define SC_MAX_O 20 - -void sc_write_syslog(char *msg) { - char szSyslogBuffer[SC_STR_MAX_LEN]; - HANDLE hEventLog; - int rc; - DWORD logw = 0x00000001L; - - sprintf(szSyslogBuffer, "%s: %s %s", - "puttysc", "info", msg); - - if ((hEventLog = RegisterEventSource(NULL, "puttysc"))) { - LPSTR aszStrings[] = {szSyslogBuffer}; - rc = ReportEvent(hEventLog, - (WORD)EVENTLOG_INFORMATION_TYPE, - 0, - (DWORD)logw, - NULL, - 1, - 0, - (LPCTSTR*)aszStrings, - NULL); - DeregisterEventSource(hEventLog); - } -} - -char *sc_base64key(char *data, int len) { - int bi, bn; - char out[4]; - int datalen = len; - char *buffi = calloc(len + len, sizeof(char *)); - int buffi_pos = 0; - for(bi=0;bi<(len + len); bi++) buffi[bi] = '\0'; - while (datalen > 0) { - bn = (datalen < 3 ? datalen : 3); - base64_encode_atom(data, bn, out); - data += bn; - datalen -= bn; - for (bi = 0; bi < 4; bi++) { - buffi[buffi_pos] = out[bi]; - buffi_pos++; - } - } - return buffi; -} - -void sc_free_sclib(sc_lib *sclib) { - if(sclib->rsakey != NULL) { - free(sclib->rsakey->exponent); - free(sclib->rsakey->modulus); - free(sclib->rsakey); - } - if (sclib->keystring != NULL) { - free(sclib->keystring); - sclib->keystring = NULL; - } - sclib->m_fl->C_Finalize(0); - sclib->m_fl = 0; - free(sclib->m_KeyID); - sclib->m_KeyID = NULL; - free(sclib->m_SshPK); - sclib->m_SshPK = NULL; - sclib->m_SshPK_len = 0; - free(sclib->m_SshPk_alg); - sclib->m_SshPk_alg = NULL; - FreeLibrary(sclib->hLib); - free(sclib); -} - -int sc_init_library(void *f, int try_write_syslog, sc_lib *sclib, - Filename *pkcs11_libfile) { - CK_FUNCTION_LIST_PTR fl = 0; - CK_C_GetFunctionList pGFL = 0; - unsigned long slot_count = 16; - CK_SLOT_ID slots[16]; - CK_RV rv = 0; - char *msg = ""; - sclib->hLib = LoadLibrary((char *)pkcs11_libfile); - - if (sclib->hLib == NULL) { - msg = "sc: Cannot load PKCS 11 DLL."; - goto err; - } - pGFL= (CK_RV (*)(CK_FUNCTION_LIST_PTR_PTR))GetProcAddress(sclib->hLib, "C_GetFunctionList"); - if (pGFL == NULL) { - msg = "sc: Cannot find GetFunctionList()"; - goto err; - } - rv = pGFL(&fl); - if(rv != CKR_OK) { - msg = "sc: Can't get function list"; - goto err; - } - rv = fl->C_Initialize (0); - if (CKR_OK != rv ) { - msg = "sc: C_Initialize failed"; - goto err; - } - rv = fl->C_GetSlotList (TRUE, slots, &slot_count); - if (CKR_OK != rv) { - msg = "sc: C_GetSlotList failed"; - goto err; - } - if (slot_count < 1) { - msg = "sc: No token available"; - goto err; - } - sclib->m_fl = fl; - return TRUE; - - err: - logevent(f, msg); - if(try_write_syslog) sc_write_syslog(msg); - FreeLibrary(sclib->hLib); - return FALSE; -} - -/* In an attempt to work around the wierdness of the ActivClient - library, I'm going to do an evil hack: If there's only one slot, - I'm going to use it regardless of what the token_label is. - ActivClient generates token labels on the fly, so we can't count on - them being the same from one session to the next. */ - -CK_SESSION_HANDLE sc_get_session(void *f, int try_write_syslog, CK_FUNCTION_LIST_PTR fl, - const char *token_label) { -#define SC_MAX_SLOT 16 - CK_SESSION_HANDLE session = 0; - unsigned long slot_count = SC_MAX_SLOT; - CK_TOKEN_INFO token_info; - CK_SLOT_ID slots[SC_MAX_SLOT]; - CK_SLOT_ID c_slot = SC_MAX_SLOT; - CK_SLOT_ID slot = SC_MAX_SLOT; - CK_RV rv = 0; - int i; - char msg[SC_STR_MAX_LEN] = ""; - - sprintf(msg, "sc: sc_get_session called"); - logevent(f, msg); - - if(fl == 0) { - sprintf(msg, "sc: Invalid state, no function list"); - goto err; - } - rv = fl->C_GetSlotList(TRUE, slots, &slot_count); - if(CKR_OK != rv) { - sprintf(msg, "sc: C_GetSlotList failed 0x%.4x", (int)rv); - goto err; - } - - sprintf(msg, "sc: slot_count = %d", (int)slot_count); - logevent(f, msg); - - if(slot_count < 1) { - sprintf(msg, "sc: No token available"); - goto err; - } - - if (slot_count == 1) { - char msg[SC_STR_MAX_LEN] = ""; - CK_TOKEN_INFO token_info; - rv = fl->C_GetTokenInfo(slots[0],&token_info); - sprintf(msg, "sc: forcing token label to the only allowed value: %s", token_info.label); - c_slot = 0; - logevent(f, msg); - } else { - - for(i=0; iC_GetTokenInfo(slot,&token_info); - if (CKR_OK != rv) { - sprintf(msg, "sc: C_GetTokenInfo failed for token in slot %i", i); - goto err; - } - { - char buf[40]; - memset(buf, 0, 40); - strncpy(buf, token_info.label, 39); - sprintf(msg, "sc: Found token in slot %i: %s", i, buf); - logevent(f, msg); - if(f) { - if(try_write_syslog) sc_write_syslog(msg); - } - } - if(strncmp(token_label, token_info.label, strlen(token_label)) == 0) { - c_slot = i; - break; - } - } - if(c_slot == 64) { - sprintf(msg, "sc: No token named: %s", token_label); - goto err; - } - } - rv = fl->C_OpenSession(slots[c_slot],CKF_SERIAL_SESSION|CKF_RW_SESSION, 0, 0, &session); - if (CKR_OK != rv) { - sprintf(msg, "sc: C_OpenSession failed"); - goto err; - } else { - if(f) logevent(f, "sc: Session opened"); - } - return session; - err: - if(f) { - logevent(f, msg); - if(try_write_syslog) sc_write_syslog(msg); - } - // m_fl->C_Finalize(0); - // m_fl = 0; - return 0; -} - -void sc_free_cert_list(sc_cert_list *cert_list) { - sc_cert_list *cl = cert_list; - while(cl != NULL) { - sc_cert_list *next = cl->next; - int i; - for(i=0; icert_attr)/sizeof(CK_ATTRIBUTE); i++) { - free(cl->cert_attr[i].pValue); - cl->cert_attr[i].pValue = NULL; - } - free(cl); - cl = next; - } - cert_list = NULL; -} - -sc_cert_list *sc_get_cert_list(sc_lib *sclib, CK_SESSION_HANDLE session, char *err_msg) { - CK_RV rv; - int i; - /* STORE OBJECTS AND ATTRIBUTES */ - sc_cert_list *cl = NULL; - sc_cert_list *pcl = NULL; - CK_OBJECT_HANDLE list[SC_MAX_O]; - CK_ULONG found = 0; - /* TEMPLATES: */ - CK_BBOOL bFalse = 0; - CK_BBOOL bTrue = 1; - CK_OBJECT_CLASS class_cert = CKO_CERTIFICATE; - CK_ATTRIBUTE cert_template[] = { - { CKA_CLASS, &class_cert, sizeof (class_cert) }, - { CKA_TOKEN, &bTrue, sizeof (bTrue) }, - { CKA_PRIVATE, &bFalse, sizeof (bFalse) } - }; - - rv = sclib->m_fl->C_FindObjectsInit(session, cert_template, sizeof(cert_template)/sizeof(CK_ATTRIBUTE)); - if (CKR_OK != rv) { - sprintf(err_msg, "sc: C_FindObjectsInit (certificate) failed, 0x%.4x", (int)rv); - return NULL; - } - rv = sclib->m_fl->C_FindObjects(session, list, SC_MAX_O-1, &found); - if (CKR_OK != rv) { - sprintf(err_msg, "sc: C_FindObjects (certificate) failed, 0x%.4x", (int)rv); - return NULL; - } - rv = sclib->m_fl->C_FindObjectsFinal(session); - if (CKR_OK != rv) { - sprintf(err_msg, "sc: C_FindObjectsFinal (certificate) failed, 0x%.4x", (int)rv); - return NULL; - } - if (found < 1) { - sprintf(err_msg, "sc: No certificate found"); - return NULL; - } - - cl = calloc(1, sizeof(sc_cert_list)); - cl->cert_attr[0].type = CKA_LABEL; /* first element is the label of the cert */ - cl->cert_attr[1].type = CKA_ID; /* second element is the id */ - cl->cert_attr[2].type = CKA_VALUE; /* third element is the value -add risacher */ - pcl = cl; - for(i=0; im_fl->C_GetAttributeValue(session, pO, pcl->cert_attr, sizeof(pcl->cert_attr)/sizeof(CK_ATTRIBUTE)); - if(CKR_OK == rv) { - int nr; - for(nr=0; nrcert_attr)/sizeof(CK_ATTRIBUTE); nr++) { - pcl->cert_attr[nr].pValue = calloc(pcl->cert_attr[nr].ulValueLen+1, sizeof(char *)); - } - rv = sclib->m_fl->C_GetAttributeValue(session, pO, pcl->cert_attr, sizeof(pcl->cert_attr)/sizeof(CK_ATTRIBUTE)); - if(CKR_OK == rv) { - if(inext = calloc(1, sizeof(sc_cert_list)); - pcl = pcl->next; - pcl->cert_attr[0].type = CKA_LABEL; - pcl->cert_attr[1].type = CKA_ID; - pcl->cert_attr[2].type = CKA_VALUE; - } else { - pcl->next = NULL; - } - } else { - sprintf(err_msg, "sc: GetAttributeValue failed, no data for cert"); - for(nr=0; nrcert_attr)/sizeof(CK_ATTRIBUTE); nr++) { - free(pcl->cert_attr[nr].pValue); - pcl->cert_attr[nr].pValue = NULL; - } - free(pcl); - pcl = NULL; - return cl; - } - } else { - sprintf(err_msg, "sc: GetAttributeValue failed (cert), 0x%.4x", (int)rv); - free(pcl); - pcl = NULL; - return cl; - } - } // for objects - return cl; -} - -void sc_free_pub_list(sc_pub_list *pub_list) { - sc_pub_list *cl = pub_list; - while(cl != NULL) { - sc_pub_list *next = cl->next; - int i; - for(i=0; ipub_attr)/sizeof(CK_ATTRIBUTE); i++) { - free(cl->pub_attr[i].pValue); - cl->pub_attr[i].pValue = NULL; - } - free(cl); - cl = next; - } - pub_list = NULL; -} - -sc_pub_list *sc_get_pub_list(sc_lib *sclib, CK_SESSION_HANDLE session, char *err_msg) { - CK_RV rv; - int i; - /* STORE OBJECTS AND ATTRIBUTES */ - sc_pub_list *pl = NULL; - sc_pub_list *ppl = NULL; - CK_OBJECT_HANDLE list[SC_MAX_O]; - CK_ULONG found = 0; - /* TEMPLATES: */ - CK_BBOOL bFalse = 0; - CK_BBOOL bTrue = 1; - CK_OBJECT_CLASS class_public_key = CKO_PUBLIC_KEY; - CK_KEY_TYPE key_type = CKK_RSA; - CK_ATTRIBUTE key_template[] = { - { CKA_CLASS, &class_public_key, sizeof (class_public_key) }, - /* { CKA_KEY_TYPE, &key_type, sizeof (key_type) }, */ - { CKA_TOKEN, &bTrue, sizeof (bTrue) }, - { CKA_PRIVATE, &bFalse, sizeof (bFalse) } - }; - - rv = sclib->m_fl->C_FindObjectsInit(session, key_template, sizeof(key_template)/sizeof(CK_ATTRIBUTE)); - if (CKR_OK != rv) { - sprintf(err_msg, "sc: C_FindObjectsInit (pub key) failed, 0x%.4x", (int)rv); - return NULL; - } - rv = sclib->m_fl->C_FindObjects(session, list, SC_MAX_O-1, &found); - if (CKR_OK != rv) { - sprintf(err_msg, "sc: C_FindObjects (pub key) failed, 0x%.4x", (int)rv); - return NULL; - } - rv = sclib->m_fl->C_FindObjectsFinal(session); - if (CKR_OK != rv) { - sprintf(err_msg, "sc: C_FindObjectsFinal (pub key) failed, 0x%.4x", (int)rv); - return NULL; - } - if (found < 1) { - sprintf(err_msg, "sc: No pub key found (beta)"); - return NULL; - } - - pl = calloc(1, sizeof(sc_pub_list)); - pl->pub_attr[0].type = CKA_ID; - pl->pub_attr[1].type = CKA_MODULUS_BITS; - pl->pub_attr[2].type = CKA_MODULUS; - pl->pub_attr[3].type = CKA_PUBLIC_EXPONENT; - ppl = pl; - for(i=0; im_fl->C_GetAttributeValue(session, pO, ppl->pub_attr, sizeof(ppl->pub_attr)/sizeof(CK_ATTRIBUTE)); - if(CKR_OK == rv) { - int nr; - for(nr=0; nrpub_attr)/sizeof(CK_ATTRIBUTE); nr++) { - ppl->pub_attr[nr].pValue = calloc(ppl->pub_attr[nr].ulValueLen+1, sizeof(char *)); - } - rv = sclib->m_fl->C_GetAttributeValue(session, pO, ppl->pub_attr, sizeof(ppl->pub_attr)/sizeof(CK_ATTRIBUTE)); - if(CKR_OK == rv) { - if(inext = calloc(1, sizeof(sc_pub_list)); - ppl = ppl->next; - ppl->pub_attr[0].type = CKA_ID; - ppl->pub_attr[1].type = CKA_MODULUS_BITS; - ppl->pub_attr[2].type = CKA_MODULUS; - ppl->pub_attr[3].type = CKA_PUBLIC_EXPONENT; - } else { - ppl->next = NULL; - } - } else { - sprintf(err_msg, "sc: GetAttributeValue failed, no data for pub key"); - for(nr=0; nrpub_attr)/sizeof(CK_ATTRIBUTE); nr++) { - free(ppl->pub_attr[nr].pValue); - ppl->pub_attr[nr].pValue = NULL; - } - free(ppl); - ppl = NULL; - return pl; - } - } else { - sprintf(err_msg, "sc: GetAttributeValue failed (pub), 0x%.4x", (int)rv); - free(ppl); - ppl = NULL; - return pl; - } - } // for objects - - return pl; -} - - -/* x509 Certificates are encoded in ASN.1 DER form */ - -/* find exponent and modulus (and lengths thereof) inside a cert. - return 1 on success, 0 on failure */ -int extract_key_from_cert(unsigned char* cert, int certsize, unsigned char **expo, unsigned char** modu, - int* elen, int* mlen) { - unsigned char *cursor = cert; - int oidpos; - oidpos = find_substring(cursor, certsize, OIDrsaEncryption, sizeof(OIDrsaEncryption)); - - if (oidpos != -1) { - cursor += oidpos; - - /* the OID, plus the NULL, consume 12 bytes */ - cursor += 12; /* skip OID and NULL */ - cursor += 2; /* skip 1st BIT STRING header */ - asn1_length(&cursor); /* skip length of 1st BIT STRING header */ - cursor += 2; /* skip 2nd BIT STRING header */ - asn1_length(&cursor); /* skip length of 2nd BIT STRING header */ - /* now we should be at the start of the ASN1 INTEGER for the pubkey (whew!) */ - if (02 == *(cursor++)) { - *mlen = asn1_length(&cursor); - if (*mlen == ~0) {return 0;} - *modu = cursor; - cursor += *mlen; - if (02 == *(cursor++)) { - *elen = asn1_length(&cursor); - if (*elen == ~0) {return 0;} - *expo = cursor; - return 1; - } - } - } - *expo = NULL; - *modu = NULL; - return 0; -} - - - -/* Given a handle pointing to an asn.1 BER/DER length field, return - the length as an unsigned integer and update the handle to point - just past the length. Note that this obviously will fail horribly - if the length field is larger than the size of an unsigned int; in - which case it returns ~0 (-1). */ - -unsigned int asn1_length(unsigned char** cursor) { - if (**cursor & 0x80) { - /* long form */ - unsigned int length = 0; - unsigned int lenlen = 0x7f & *((*cursor)++); - unsigned int i; - if (lenlen > sizeof (unsigned int)) { return ~0; } - for (i=0; i< lenlen; i++) { - length = ((length)<<8) + *((*cursor)++); - } - return length; - } else { - /* short form */ - return (unsigned int) *((*cursor)++); - } -} - -int find_substring(char* haystack, long haysize, const char* needle, long needlesize) -{ - int i, j; - - for (i=0; i< haysize-needlesize; i++) { - for (j=0; j < needlesize; j++) { - if (*(haystack+i+j) != *(needle+j)) { - goto nope; - } - } - return i; - nope: - j=0; - } - return -1; -} - -unsigned char *generate_keystring (sc_lib *sclib, char **algorithm, - int *blob_len, - unsigned char *expop, int elen, - unsigned char *modup, int mlen) { - unsigned char *expo, *modu; - unsigned char *blob, *p; - int i; - expo = bignum_from_bytes(expop, elen); - modu = bignum_from_bytes(modup, mlen); - - /* risacher: I'm not precisely sure why this is here */ - /* or why it's written this way */ - /* Are there any interesting cases where ((8 * x) + 8) / 8 != x+1 ? */ - - /* I suspect that this is to make sure that the modulus and - exponent are non-negative, but this is duplicative if the - public key was extracted from a certificate, since ASN.1 DER - already requires that integers start with a zero bit if - non-negative. If the public key was retrieved from the - SmartCard directly, then PKCS#11 specifies that it should be a - "Big Integer", which is unsigned, and thus adding a leading - zero would be required (if the most significant bit is set), - because OpenSSH treats them as signed integers. Since these - are handled as multi-precision integers, in theory leading - zeros won't matter but means that your public key string will - be 1-2 bytes longer than otherwise. - */ - - elen = ((8 * elen) + 8) / 8; - mlen = ((8 * mlen) + 8) / 8; - - *blob_len = 19 + elen + mlen; - *algorithm = calloc(sizeof(char *), strlen("ssh-rsa")+1); - strcpy(*algorithm, "ssh-rsa"); - /* ugly (but used in pagent prototype) */ - if(sclib->rsakey != NULL) { - free(sclib->rsakey->exponent); - free(sclib->rsakey->modulus); - free(sclib->rsakey); - } - sclib->rsakey = calloc(1, sizeof(struct RSAKey)); - sclib->rsakey->exponent = expo; - sclib->rsakey->modulus = modu; - - blob = calloc(sizeof(char *), *blob_len); - p = blob; - SC_PUT_32BIT(p, 7); - p += 4; - memcpy(p, "ssh-rsa", 7); - p += 7; - SC_PUT_32BIT(p, elen); - p += 4; - for (i = elen; i--;) *p++ = bignum_byte(expo, i); - SC_PUT_32BIT(p, mlen); - p += 4; - for (i = mlen; i--;) - *p++ = bignum_byte(modu, i); - - sclib->m_SshPK = calloc(sizeof(char *), *blob_len); - memcpy(sclib->m_SshPK, blob, *blob_len); - sclib->m_SshPK_len = *blob_len; - - sclib->m_SshPk_alg = calloc(sizeof(char *), strlen("ssh-rsa")+1); - strcpy(sclib->m_SshPk_alg, "ssh-rsa"); - return blob; -} - -unsigned char *sc_get_pub(void *f, int try_write_syslog, sc_lib *sclib, - const char *token_label, const char *cert_label, - char **algorithm, int *blob_len) { - /* return pub_key and blob_len */ - unsigned char *pub_key = NULL; - sc_cert_list *cl; - - unsigned char *expop, *modup; - int elen, mlen; - - /* some local helper: */ - char msg[SC_STR_MAX_LEN] = ""; - - /* STORE OBJECTS AND ATTRIBUTES */ - CK_SESSION_HANDLE session = 0; - - sprintf(msg, "sc: Called sc_get_pub: token_label=%s, cert_label=%s", token_label, cert_label); - logevent(f, msg); - - /* OPEN SESSION */ - session = sc_get_session(f, try_write_syslog, sclib->m_fl, token_label); - if(session == 0) { - return NULL; - } - - /* SEARCH THE SPECIFIED CERTIFICATE AND DETERMINE THE ID */ - { - sc_cert_list *pcl; - msg[0]='\0'; - cl = sc_get_cert_list(sclib, session, msg); - if(cl == NULL) goto err; - if(strlen(msg) > 0) { - logevent(f, msg); - if(try_write_syslog) sc_write_syslog(msg); - } - pcl = cl; - while(pcl != NULL) { - int len = strlen(cert_label); - if(pcl->cert_attr[0].ulValueLen < len) len = pcl->cert_attr[0].ulValueLen; - if(strncmp(cert_label, pcl->cert_attr[0].pValue, len) == 0) { - sclib->m_KeyID = calloc(sizeof(char *), pcl->cert_attr[1].ulValueLen+1); - strncpy(sclib->m_KeyID, pcl->cert_attr[1].pValue, pcl->cert_attr[1].ulValueLen); - { - char *p_buf = calloc(1,pcl->cert_attr[0].ulValueLen+1); - strncpy(p_buf, pcl->cert_attr[0].pValue, pcl->cert_attr[0].ulValueLen); - sprintf(msg, "sc: Found cert attr[0]: %s", p_buf); - free(p_buf); - } - logevent(f, msg); - if(try_write_syslog) sc_write_syslog(msg); - sprintf(msg, "sc: Found cert attr[1] : type 0x%x length 0x%x", pcl->cert_attr[1].type, pcl->cert_attr[1].ulValueLen); - logevent(f, msg); - extract_key_from_cert(pcl->cert_attr[2].pValue, pcl->cert_attr[2].ulValueLen, - &expop, &modup, - &elen, &mlen); - if(try_write_syslog) sc_write_syslog(msg); - break; - } - pcl = pcl->next; - } - } - - /* NOW GET THE PUB KEY FOR THIS CERT */ - if(sclib->m_KeyID == NULL) { - sprintf(msg, "sc: No cert found (4) : %s", cert_label); - goto err; - } - if (NULL == expop) { - /* this is the old code path that shouldn't ever happen any more */ - /* the key should already be extracted */ - sc_pub_list *pl; - sc_pub_list *ppl; - msg[0]='\0'; - - pl = sc_get_pub_list(sclib, session, msg); - if(pl == NULL) goto err; - if(strlen(msg) > 0) { - logevent(f, msg); - if(try_write_syslog) sc_write_syslog(msg); - } - ppl = pl; - while(ppl != NULL) { - sprintf(msg, "sc: trying ppl at 0x%x", ppl); - logevent(f, msg); - if(strncmp(sclib->m_KeyID, ppl->pub_attr[0].pValue, ppl->pub_attr[0].ulValueLen) == 0) { - // attr 0: id - // attr 2: modulus - // attr 3: exponent - unsigned char *blob; - - { - char *p_buf = calloc(1, ppl->pub_attr[0].ulValueLen+1); - strncpy(p_buf, ppl->pub_attr[0].pValue, ppl->pub_attr[0].ulValueLen); - sprintf(msg, "sc: Found key: %s", p_buf); - free(p_buf); - } - logevent(f, msg); - if(try_write_syslog) sc_write_syslog(msg); - - //used in sclib free(expo); - //free(modu); - break; - } - ppl = ppl->next; - } - sc_free_pub_list(pl); - } - pub_key = generate_keystring(sclib, algorithm, blob_len, - expop, elen, - modup, mlen); - { - char *buffi = sc_base64key(pub_key, *blob_len); - if (sclib->keystring) { free(sclib->keystring); } - sclib->keystring = calloc (1, strlen("ssh-rsa ")+strlen(buffi)+strlen(cert_label)); - sprintf(sclib->keystring, "ssh-rsa %s %s", buffi, cert_label); - sprintf(msg, "sc: %s", sclib->keystring); - logevent(f, msg); - if(try_write_syslog) sc_write_syslog(msg); - free(buffi); - } - - if(sclib->m_SshPK == NULL) { - sprintf(msg, "sc: No pub key found: %s", cert_label); - goto err; - } - sclib->m_fl->C_CloseSession(session); - sc_free_cert_list(cl); - return pub_key; - - err: - if (cl) { sc_free_cert_list(cl); } - logevent(f, msg); - if(try_write_syslog) sc_write_syslog(msg); - sclib->m_fl->C_CloseSession(session); - return NULL; -} - -/* elements within sc_pubkey_blob must NOT be freed */ -struct sc_pubkey_blob *sc_login_pub(void *f, int try_write_syslog, sc_lib *sclib, - const char *token_label, const char *password) { - CK_RV rv = 0; - struct sc_pubkey_blob *key11; - CK_SESSION_HANDLE session = 0; - - session = sc_get_session(f, try_write_syslog, sclib->m_fl, token_label); - if(session == 0) { - return NULL; - } - - rv = sclib->m_fl->C_Login(session, CKU_USER, (CK_CHAR_PTR)password, strlen(password)); - if (CKR_OK != rv) { - logevent(f, "sc: Login failed for public key"); - if(try_write_syslog) sc_write_syslog("sc: Login failed"); - sclib->m_fl->C_CloseSession(session); - return (void *)SSH2_WRONG_PASSPHRASE; - } - - logevent(f, "sc: Login successful"); - - sclib->m_fl->C_Logout(session); - sclib->m_fl->C_CloseSession(session); - - /* free when deleting sclib! */ - key11 = calloc(sizeof(struct sc_pubkey_blob), 1); - key11->data = sclib->m_SshPK; - key11->alg = sclib->m_SshPk_alg; - key11->len = sclib->m_SshPK_len; - - return key11; -} - - -unsigned char *sc_sig(void *f, int try_write_syslog, sc_lib *sclib, - const char *token_label, const char *password_s, - char *sigdata, int sigdata_len, int *sigblob_len) { - CK_RV rv = 0; - char msg[SC_STR_MAX_LEN] = ""; - CK_SESSION_HANDLE session = 0; - const char *pwd = password_s; - /* TEMPLATES: */ - CK_BBOOL bTrue = 1; - CK_OBJECT_CLASS class_private_key = CKO_PRIVATE_KEY; - CK_KEY_TYPE key_type = CKK_RSA; - CK_ATTRIBUTE key_template[] = { - { CKA_CLASS, &class_private_key, sizeof (class_private_key) }, - { CKA_KEY_TYPE, &key_type, sizeof (key_type) }, - { CKA_TOKEN, &bTrue, sizeof (bTrue) }, - { CKA_SIGN, &bTrue, sizeof (bTrue) }, - { CKA_PRIVATE, &bTrue, sizeof (bTrue) } - }; - CK_ATTRIBUTE key_getattributes[] = { - {CKA_ID, NULL_PTR, 0}, /* ID to search the key */ - {CKA_MODULUS, NULL_PTR, 0} - }; - /* STORE OBJECTS AND ATTRIBUTES */ - CK_OBJECT_HANDLE list[SC_MAX_O]; - CK_ULONG found = 0; - CK_OBJECT_HANDLE pO; - int ii,j; - - unsigned char *ret = NULL; - *sigblob_len = 0; - - session = sc_get_session(f, try_write_syslog, sclib->m_fl, token_label); - if(session == 0) { - return NULL; - } - - rv = sclib->m_fl->C_Login(session, CKU_USER, (CK_CHAR_PTR)pwd, strlen(pwd)); - if (CKR_OK != rv) { - logevent(f, "sc: Login failed in sc_sig"); - sclib->m_fl->C_CloseSession(session); - return NULL; - } - rv = sclib->m_fl->C_FindObjectsInit(session, key_template, 4); - if (CKR_OK != rv) { - sprintf(msg, "sc: C_FindObjectsInit priv key failed, 0x%.4x", (int)rv); - goto err; - } - rv = sclib->m_fl->C_FindObjects(session, list, SC_MAX_O-1, &found); - if (CKR_OK != rv) { - sprintf(msg, "sc: C_FindObjects priv key failed, 0x%.4x", (int)rv); - goto err; - } - rv = sclib->m_fl->C_FindObjectsFinal(session); - if (CKR_OK != rv) { - sprintf(msg, "sc: C_FindObjectsFinal priv key failed, 0x%.4x", (int)rv); - goto err; - } - if (found < 1) { - sprintf(msg, "sc: No priv keys found"); - goto err; - } - for(ii=0; iim_fl->C_GetAttributeValue(session, pO, key_getattributes, ts); - if(CKR_OK == rv) { - for(nr=0;nrm_fl->C_GetAttributeValue(session, pO, key_getattributes, ts) == CKR_OK) { - if(strncmp(key_getattributes[0].pValue, sclib->m_KeyID, key_getattributes[0].ulValueLen) == 0) { - CK_BYTE signature[500]; - CK_ULONG signature_length = 500; - CK_MECHANISM mechanism = { CKM_RSA_PKCS, NULL_PTR, 0 }; - unsigned char *bytes; - Bignum out; - int nbytes; - int r; - unsigned char hash_sha[20]; - - char *p_buf = calloc(1, key_getattributes[0].ulValueLen+1); - strncpy(p_buf, key_getattributes[0].pValue, key_getattributes[0].ulValueLen); - sprintf(msg, "sc: Found pkey: %s", p_buf); - free(p_buf); - logevent(f, msg); - if(try_write_syslog) sc_write_syslog(msg); - - rv = sclib->m_fl->C_SignInit(session, &mechanism, pO); - if (CKR_OK != rv) { - free(key_getattributes[0].pValue); - free(key_getattributes[1].pValue); - sprintf(msg, "sc: SignInit failed, 0x%.4x", (int)rv); - goto err; - } - - /* rsa2_sign() */ - SHA_Simple(sigdata, sigdata_len, hash_sha); - // MD5Simple(sigdata, sigdata_len, hash_md5); - { - int message_len = sizeof(id_sha1) + sizeof(hash_sha); - CK_BYTE *message = calloc(sizeof(CK_BYTE), message_len); - for(j=0;jm_fl->C_Sign(session, message, message_len, signature, &signature_length); - free(message); - if (CKR_OK != rv) { - free(key_getattributes[0].pValue); - free(key_getattributes[1].pValue); - sprintf(msg, "sc: Sign failed, 0x%.4x", (int)rv); - goto err; - } - } - - out = bignum_from_bytes(signature, signature_length); - nbytes = (bignum_bitcount(out) + 7) / 8; - *sigblob_len = 4 + 7 + 4 + nbytes; - bytes = calloc(sizeof(char *), *sigblob_len); - SC_PUT_32BIT(bytes, 7); - memcpy(bytes + 4, "ssh-rsa", 7); - SC_PUT_32BIT(bytes + 4 + 7, nbytes); - for (r = 0; r < nbytes; r++) - bytes[4 + 7 + 4 + r] = bignum_byte(out, nbytes - 1 - r); - ret = bytes; - - free(out); - free(key_getattributes[0].pValue); - free(key_getattributes[1].pValue); - break; - } - } else { - logevent(f, "sc: GetAttributeValue failed, no data loaded"); - } - free(key_getattributes[0].pValue); - free(key_getattributes[1].pValue); - } else { - sprintf(msg, "sc: GetAttributeValue failed (pkey), 0x%.4x", (int)rv); - logevent(f, msg); - if(try_write_syslog) sc_write_syslog(msg); - } - } - - sclib->m_fl->C_Logout(session); - sclib->m_fl->C_CloseSession(session); - return ret; - - err: - logevent(f, msg); - if(try_write_syslog) sc_write_syslog(msg); - sclib->m_fl->C_Logout(session); - sclib->m_fl->C_CloseSession(session); - if(ret != NULL) free(ret); - /* just return an invalid signature ... */ - *sigblob_len = 1; - return " "; -} - DELETED sc.h Index: sc.h ================================================================== --- sc.h +++ /dev/null @@ -1,82 +0,0 @@ -/* -*-mode: c; indent-tabs-mode: nil; c-basic-offset: 4; -*- */ -/* - * This file is part of PuTTY SC, a modification of PuTTY - * supporting smartcard for authentication. - * - * PuTTY SC is available at http://www.joebar.ch/puttysc/ - * - * Copyright (C) 2005-2007 Pascal Buchbinder - * - * This program 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 2 - * of the License, or (at your option) any later version. - * - * This program 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 this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, - * MA 02110-1301, USA. - * - */ - -#ifndef PUTTY_SC_H -#define PUTTY_SC_H - -static const char rcsid_sc_h[] = "$Id: sc.h,v 1.19 2007/03/04 20:41:56 pbu Exp $"; - -struct sc_pubkey_blob { - void *data; - int len; - char *alg; -}; - -typedef struct sc_lib_st { - HINSTANCE hLib; - CK_FUNCTION_LIST_PTR m_fl; - unsigned char *m_KeyID; - unsigned char *m_SshPK; - int m_SshPK_len; - char *m_SshPk_alg; - char *keystring; - struct RSAKey *rsakey; - /* void (*sc_lib_close)(struct sc_lib_st *); */ -} sc_lib; - -typedef struct sc_cert_list_st { - CK_ATTRIBUTE cert_attr[3]; /* types: 0=CKA_LABEL 1=CKA_ID 3=CKA_VALUE */ - struct sc_cert_list_st *next; -} sc_cert_list; - -typedef struct sc_pub_list_st { - CK_ATTRIBUTE pub_attr[4]; /* types: 0=CKA_ID, 1=CKA_MODULUS_BITS, 2=CKA_MODULUS, 3=CKA_PUBLIC_EXPONENT */ - struct sc_pub_list_st *next; -} sc_pub_list; - -void sc_write_syslog(char *msg); -char *sc_base64key(char *data, int len); - -int sc_init_library(void *f, int try_write_syslog, sc_lib *sclib, Filename *pkcs11_libfile); -void sc_free_sclib(sc_lib *sclib); -CK_SESSION_HANDLE sc_get_session(void *f, int try_write_syslog, CK_FUNCTION_LIST_PTR fl, - const char *token_label); - -sc_cert_list *sc_get_cert_list(sc_lib *sclib, CK_SESSION_HANDLE session, char *err_msg); -void sc_free_cert_list(sc_cert_list *cert_list); -sc_pub_list *sc_get_pub_list(sc_lib *sclib, CK_SESSION_HANDLE session, char *err_msg); -void sc_free_pub_list(sc_pub_list *pub_list); - -unsigned char *sc_get_pub(void *f, int try_write_syslog, sc_lib *sclib, - const char *token_label, const char *cert_label, - char **algorithm, int *blob_len); -struct sc_pubkey_blob *sc_login_pub(void *f, int try_write_syslog, sc_lib *sclib, - const char *token_label, const char *password); -unsigned char *sc_sig(void *f, int try_write_syslog, sc_lib *sclib, - const char *token_label, const char *password_s, - char *sigdata, int sigdata_len, int *sigblob_len); - -#endif Index: sercfg.c ================================================================== --- sercfg.c +++ sercfg.c @@ -29,14 +29,18 @@ {"Mark", SER_PAR_MARK}, {"Space", SER_PAR_SPACE}, }; int mask = ctrl->listbox.context.i; int i, j; - Config *cfg = (Config *)data; + Conf *conf = (Conf *)data; if (event == EVENT_REFRESH) { - int oldparity = cfg->serparity;/* preserve past reentrant calls */ + /* Fetching this once at the start of the function ensures we + * remember what the right value is supposed to be when + * operations below cause reentrant calls to this function. */ + int oldparity = conf_get_int(conf, CONF_serparity); + dlg_update_start(ctrl, dlg); dlg_listbox_clear(ctrl, dlg); for (i = 0; i < lenof(parities); i++) { if (mask & (1 << i)) dlg_listbox_addwithid(ctrl, dlg, parities[i].name, @@ -54,18 +58,18 @@ if (i == lenof(parities)) { /* an unsupported setting was chosen */ dlg_listbox_select(ctrl, dlg, 0); oldparity = SER_PAR_NONE; } dlg_update_done(ctrl, dlg); - cfg->serparity = oldparity; /* restore */ + conf_set_int(conf, CONF_serparity, oldparity); /* restore */ } else if (event == EVENT_SELCHANGE) { int i = dlg_listbox_index(ctrl, dlg); if (i < 0) i = SER_PAR_NONE; else i = dlg_listbox_getid(ctrl, dlg, i); - cfg->serparity = i; + conf_set_int(conf, CONF_serparity, i); } } static void serial_flow_handler(union control *ctrl, void *dlg, void *data, int event) @@ -79,14 +83,18 @@ {"RTS/CTS", SER_FLOW_RTSCTS}, {"DSR/DTR", SER_FLOW_DSRDTR}, }; int mask = ctrl->listbox.context.i; int i, j; - Config *cfg = (Config *)data; + Conf *conf = (Conf *)data; if (event == EVENT_REFRESH) { - int oldflow = cfg->serflow; /* preserve past reentrant calls */ + /* Fetching this once at the start of the function ensures we + * remember what the right value is supposed to be when + * operations below cause reentrant calls to this function. */ + int oldflow = conf_get_int(conf, CONF_serflow); + dlg_update_start(ctrl, dlg); dlg_listbox_clear(ctrl, dlg); for (i = 0; i < lenof(flows); i++) { if (mask & (1 << i)) dlg_listbox_addwithid(ctrl, dlg, flows[i].name, flows[i].val); @@ -103,18 +111,18 @@ if (i == lenof(flows)) { /* an unsupported setting was chosen */ dlg_listbox_select(ctrl, dlg, 0); oldflow = SER_FLOW_NONE; } dlg_update_done(ctrl, dlg); - cfg->serflow = oldflow; /* restore */ + conf_set_int(conf, CONF_serflow, oldflow);/* restore */ } else if (event == EVENT_SELCHANGE) { int i = dlg_listbox_index(ctrl, dlg); if (i < 0) i = SER_FLOW_NONE; else i = dlg_listbox_getid(ctrl, dlg, i); - cfg->serflow = i; + conf_set_int(conf, CONF_serflow, i); } } void ser_setup_config_box(struct controlbox *b, int midsession, int parity_mask, int flow_mask) @@ -171,29 +179,28 @@ */ s = ctrl_getset(b, "Connection/Serial", "serline", "Select a serial line"); ctrl_editbox(s, "Serial line to connect to", 'l', 40, HELPCTX(serial_line), - dlg_stdeditbox_handler, I(offsetof(Config,serline)), - I(sizeof(((Config *)0)->serline))); + conf_editbox_handler, I(CONF_serline), I(1)); } s = ctrl_getset(b, "Connection/Serial", "sercfg", "Configure the serial line"); ctrl_editbox(s, "Speed (baud)", 's', 40, HELPCTX(serial_speed), - dlg_stdeditbox_handler, I(offsetof(Config,serspeed)), I(-1)); + conf_editbox_handler, I(CONF_serspeed), I(-1)); ctrl_editbox(s, "Data bits", 'b', 40, HELPCTX(serial_databits), - dlg_stdeditbox_handler,I(offsetof(Config,serdatabits)),I(-1)); + conf_editbox_handler, I(CONF_serdatabits), I(-1)); /* * Stop bits come in units of one half. */ ctrl_editbox(s, "Stop bits", 't', 40, HELPCTX(serial_stopbits), - dlg_stdeditbox_handler,I(offsetof(Config,serstopbits)),I(-2)); + conf_editbox_handler, I(CONF_serstopbits), I(-2)); ctrl_droplist(s, "Parity", 'p', 40, HELPCTX(serial_parity), serial_parity_handler, I(parity_mask)); ctrl_droplist(s, "Flow control", 'f', 40, HELPCTX(serial_flow), serial_flow_handler, I(flow_mask)); } Index: settings.c ================================================================== --- settings.c +++ settings.c @@ -68,125 +68,205 @@ if ((*p)->protocol == proto) return *p; return NULL; } -int get_remote_username(Config *cfg, char *user, size_t len) -{ - if (*cfg->username) { - strncpy(user, cfg->username, len); - user[len-1] = '\0'; - } else { - if (cfg->username_from_env) { - /* Use local username. */ - char *luser = get_username(); - if (luser) { - strncpy(user, luser, len); - user[len-1] = '\0'; - sfree(luser); - } else { - *user = '\0'; - } - } else { - *user = '\0'; - } - } - return (*user != '\0'); +char *get_remote_username(Conf *conf) +{ + char *username = conf_get_str(conf, CONF_username); + if (*username) { + return dupstr(username); + } else if (conf_get_int(conf, CONF_username_from_env)) { + /* Use local username. */ + return get_username(); /* might still be NULL */ + } else { + return NULL; + } +} + +static char *gpps_raw(void *handle, const char *name, const char *def) +{ + char *ret = read_setting_s(handle, name); + if (!ret) + ret = platform_default_s(name); + if (!ret) + ret = def ? dupstr(def) : NULL; /* permit NULL as final fallback */ + return ret; } static void gpps(void *handle, const char *name, const char *def, - char *val, int len) -{ - if (!read_setting_s(handle, name, val, len)) { - char *pdef; - - pdef = platform_default_s(name); - if (pdef) { - strncpy(val, pdef, len); - sfree(pdef); - } else { - strncpy(val, def, len); - } - - val[len - 1] = '\0'; - } + Conf *conf, int primary) +{ + char *val = gpps_raw(handle, name, def); + conf_set_str(conf, primary, val); + sfree(val); } /* * gppfont and gppfile cannot have local defaults, since the very - * format of a Filename or Font is platform-dependent. So the + * format of a Filename or FontSpec is platform-dependent. So the * platform-dependent functions MUST return some sort of value. */ -static void gppfont(void *handle, const char *name, FontSpec *result) -{ - if (!read_setting_fontspec(handle, name, result)) - *result = platform_default_fontspec(name); -} -static void gppfile(void *handle, const char *name, Filename *result) -{ - if (!read_setting_filename(handle, name, result)) - *result = platform_default_filename(name); +static void gppfont(void *handle, const char *name, Conf *conf, int primary) +{ + FontSpec *result = read_setting_fontspec(handle, name); + if (!result) + result = platform_default_fontspec(name); + conf_set_fontspec(conf, primary, result); + fontspec_free(result); +} +static void gppfile(void *handle, const char *name, Conf *conf, int primary) +{ + Filename *result = read_setting_filename(handle, name); + if (!result) + result = platform_default_filename(name); + conf_set_filename(conf, primary, result); + filename_free(result); } -static void gppi(void *handle, char *name, int def, int *i) +static int gppi_raw(void *handle, char *name, int def) { def = platform_default_i(name, def); - *i = read_setting_i(handle, name, def); + return read_setting_i(handle, name, def); +} + +static void gppi(void *handle, char *name, int def, Conf *conf, int primary) +{ + conf_set_int(conf, primary, gppi_raw(handle, name, def)); } /* * Read a set of name-value pairs in the format we occasionally use: * NAME\tVALUE\0NAME\tVALUE\0\0 in memory * NAME=VALUE,NAME=VALUE, in storage * `def' is in the storage format. */ -static void gppmap(void *handle, char *name, char *def, char *val, int len) +static int gppmap(void *handle, char *name, Conf *conf, int primary) { - char *buf = snewn(2*len, char), *p, *q; - gpps(handle, name, def, buf, 2*len); + char *buf, *p, *q, *key, *val; + + /* + * Start by clearing any existing subkeys of this key from conf. + */ + while ((key = conf_get_str_nthstrkey(conf, primary, 0)) != NULL) + conf_del_str_str(conf, primary, key); + + /* + * Now read a serialised list from the settings and unmarshal it + * into its components. + */ + buf = gpps_raw(handle, name, NULL); + if (!buf) + return FALSE; + p = buf; - q = val; while (*p) { + q = buf; + val = NULL; while (*p && *p != ',') { int c = *p++; if (c == '=') - c = '\t'; + c = '\0'; if (c == '\\') c = *p++; *q++ = c; + if (!c) + val = q; } if (*p == ',') p++; - *q++ = '\0'; + if (!val) + val = q; + *q = '\0'; + + if (primary == CONF_portfwd && strchr(buf, 'D') != NULL) { + /* + * Backwards-compatibility hack: dynamic forwardings are + * indexed in the data store as a third type letter in the + * key, 'D' alongside 'L' and 'R' - but really, they + * should be filed under 'L' with a special _value_, + * because local and dynamic forwardings both involve + * _listening_ on a local port, and are hence mutually + * exclusive on the same port number. So here we translate + * the legacy storage format into the sensible internal + * form, by finding the D and turning it into a L. + */ + char *newkey = dupstr(buf); + *strchr(newkey, 'D') = 'L'; + conf_set_str_str(conf, primary, newkey, "D"); + sfree(newkey); + } else { + conf_set_str_str(conf, primary, buf, val); + } } - *q = '\0'; sfree(buf); + + return TRUE; } /* * Write a set of name/value pairs in the above format. */ -static void wmap(void *handle, char const *key, char const *value, int len) +static void wmap(void *handle, char const *outkey, Conf *conf, int primary) { - char *buf = snewn(2*len, char), *p; - const char *q; + char *buf, *p, *q, *key, *realkey, *val; + int len; + + len = 1; /* allow for NUL */ + + for (val = conf_get_str_strs(conf, primary, NULL, &key); + val != NULL; + val = conf_get_str_strs(conf, primary, key, &key)) + len += 2 + 2 * (strlen(key) + strlen(val)); /* allow for escaping */ + + buf = snewn(len, char); p = buf; - q = value; - while (*q) { - while (*q) { - int c = *q++; - if (c == '=' || c == ',' || c == '\\') + + for (val = conf_get_str_strs(conf, primary, NULL, &key); + val != NULL; + val = conf_get_str_strs(conf, primary, key, &key)) { + + if (primary == CONF_portfwd && !strcmp(val, "D")) { + /* + * Backwards-compatibility hack, as above: translate from + * the sensible internal representation of dynamic + * forwardings (key "L", value "D") to the + * conceptually incoherent legacy storage format (key + * "D", value empty). + */ + char *L; + + realkey = key; /* restore it at end of loop */ + val = ""; + key = dupstr(key); + L = strchr(key, 'L'); + if (L) *L = 'D'; + } else { + realkey = NULL; + } + + if (p != buf) + *p++ = ','; + for (q = key; *q; q++) { + if (*q == '=' || *q == ',' || *q == '\\') + *p++ = '\\'; + *p++ = *q; + } + *p++ = '='; + for (q = val; *q; q++) { + if (*q == '=' || *q == ',' || *q == '\\') *p++ = '\\'; - if (c == '\t') - c = '='; - *p++ = c; + *p++ = *q; } - *p++ = ','; - q++; + + if (realkey) { + free(key); + key = realkey; + } } *p = '\0'; - write_setting_s(handle, key, buf); + write_setting_s(handle, outkey, buf); sfree(buf); } static int key2val(const struct keyvalwhere *mapping, int nmaps, char *key) @@ -212,21 +292,21 @@ * to the end and duplicates are weeded. * XXX: assumes vals in 'mapping' are small +ve integers */ static void gprefs(void *sesskey, char *name, char *def, const struct keyvalwhere *mapping, int nvals, - int *array) + Conf *conf, int primary) { - char commalist[256]; + char *commalist; char *p, *q; int i, j, n, v, pos; unsigned long seen = 0; /* bitmap for weeding dups etc */ /* * Fetch the string which we'll parse as a comma-separated list. */ - gpps(sesskey, name, def, commalist, sizeof(commalist)); + commalist = gpps_raw(sesskey, name, def); /* * Go through that list and convert it into values. */ n = 0; @@ -241,13 +321,16 @@ if (*p) *p++ = '\0'; v = key2val(mapping, nvals, q); if (v != -1 && !(seen & (1 << v))) { seen |= (1 << v); - array[n++] = v; + conf_set_int_int(conf, primary, n, v); + n++; } } + + sfree(commalist); /* * Now go through 'mapping' and add values that weren't mentioned * in the list we fetched. We may have to loop over it multiple * times so that we add values before other values whose default @@ -270,22 +353,24 @@ */ if (mapping[i].vrel == -1) { pos = (mapping[i].where < 0 ? n : 0); } else { for (j = 0; j < n; j++) - if (array[j] == mapping[i].vrel) + if (conf_get_int_int(conf, primary, j) == + mapping[i].vrel) break; assert(j < n); /* implied by (seen & (1<= pos; j--) - array[j+1] = array[j]; - array[pos] = mapping[i].v; + conf_set_int_int(conf, primary, j+1, + conf_get_int_int(conf, primary, j)); + conf_set_int_int(conf, primary, pos, mapping[i].v); n++; } } } } @@ -293,27 +378,29 @@ /* * Write out a preference list. */ static void wprefs(void *sesskey, char *name, const struct keyvalwhere *mapping, int nvals, - int *array) + Conf *conf, int primary) { char *buf, *p; int i, maxlen; for (maxlen = i = 0; i < nvals; i++) { - const char *s = val2key(mapping, nvals, array[i]); + const char *s = val2key(mapping, nvals, + conf_get_int_int(conf, primary, i)); if (s) { maxlen += (maxlen > 0 ? 1 : 0) + strlen(s); } } buf = snewn(maxlen + 1, char); p = buf; for (i = 0; i < nvals; i++) { - const char *s = val2key(mapping, nvals, array[i]); + const char *s = val2key(mapping, nvals, + conf_get_int_int(conf, primary, i)); if (s) { p += sprintf(p, "%s%s", (p > buf ? "," : ""), s); } } @@ -323,539 +410,498 @@ write_setting_s(sesskey, name, buf); sfree(buf); } -char *save_settings(char *section, Config * cfg) +char *save_settings(char *section, Conf *conf) { void *sesskey; char *errmsg; sesskey = open_settings_w(section, &errmsg); if (!sesskey) return errmsg; - save_open_settings(sesskey, cfg); + save_open_settings(sesskey, conf); close_settings_w(sesskey); return NULL; } -void save_open_settings(void *sesskey, Config *cfg) +void save_open_settings(void *sesskey, Conf *conf) { int i; char *p; write_setting_i(sesskey, "Present", 1); - write_setting_s(sesskey, "HostName", cfg->host); - write_setting_filename(sesskey, "LogFileName", cfg->logfilename); - write_setting_i(sesskey, "LogType", cfg->logtype); - write_setting_i(sesskey, "LogFileClash", cfg->logxfovr); - write_setting_i(sesskey, "LogFlush", cfg->logflush); - write_setting_i(sesskey, "SSHLogOmitPasswords", cfg->logomitpass); - write_setting_i(sesskey, "SSHLogOmitData", cfg->logomitdata); - p = "raw"; - { - const Backend *b = backend_from_proto(cfg->protocol); - if (b) - p = b->name; - } - write_setting_s(sesskey, "Protocol", p); - write_setting_i(sesskey, "PortNumber", cfg->port); - /* The CloseOnExit numbers are arranged in a different order from - * the standard FORCE_ON / FORCE_OFF / AUTO. */ - write_setting_i(sesskey, "CloseOnExit", (cfg->close_on_exit+2)%3); - write_setting_i(sesskey, "WarnOnClose", !!cfg->warn_on_close); - write_setting_i(sesskey, "PingInterval", cfg->ping_interval / 60); /* minutes */ - write_setting_i(sesskey, "PingIntervalSecs", cfg->ping_interval % 60); /* seconds */ - write_setting_i(sesskey, "TCPNoDelay", cfg->tcp_nodelay); - write_setting_i(sesskey, "TCPKeepalives", cfg->tcp_keepalives); - write_setting_s(sesskey, "TerminalType", cfg->termtype); - write_setting_s(sesskey, "TerminalSpeed", cfg->termspeed); - wmap(sesskey, "TerminalModes", cfg->ttymodes, lenof(cfg->ttymodes)); - - /* Address family selection */ - write_setting_i(sesskey, "AddressFamily", cfg->addressfamily); - - /* proxy settings */ - write_setting_s(sesskey, "ProxyExcludeList", cfg->proxy_exclude_list); - write_setting_i(sesskey, "ProxyDNS", (cfg->proxy_dns+2)%3); - write_setting_i(sesskey, "ProxyLocalhost", cfg->even_proxy_localhost); - write_setting_i(sesskey, "ProxyMethod", cfg->proxy_type); - write_setting_s(sesskey, "ProxyHost", cfg->proxy_host); - write_setting_i(sesskey, "ProxyPort", cfg->proxy_port); - write_setting_s(sesskey, "ProxyUsername", cfg->proxy_username); - write_setting_s(sesskey, "ProxyPassword", cfg->proxy_password); - write_setting_s(sesskey, "ProxyTelnetCommand", cfg->proxy_telnet_command); - wmap(sesskey, "Environment", cfg->environmt, lenof(cfg->environmt)); - write_setting_s(sesskey, "UserName", cfg->username); - write_setting_i(sesskey, "UserNameFromEnvironment", cfg->username_from_env); - write_setting_s(sesskey, "LocalUserName", cfg->localusername); - write_setting_i(sesskey, "NoPTY", cfg->nopty); - write_setting_i(sesskey, "Compression", cfg->compression); - write_setting_i(sesskey, "TryAgent", cfg->tryagent); - write_setting_i(sesskey, "AgentFwd", cfg->agentfwd); - write_setting_i(sesskey, "GssapiFwd", cfg->gssapifwd); - write_setting_i(sesskey, "ChangeUsername", cfg->change_username); - wprefs(sesskey, "Cipher", ciphernames, CIPHER_MAX, - cfg->ssh_cipherlist); - wprefs(sesskey, "KEX", kexnames, KEX_MAX, cfg->ssh_kexlist); - write_setting_i(sesskey, "RekeyTime", cfg->ssh_rekey_time); - write_setting_s(sesskey, "RekeyBytes", cfg->ssh_rekey_data); - write_setting_i(sesskey, "SshNoAuth", cfg->ssh_no_userauth); - write_setting_i(sesskey, "SshBanner", cfg->ssh_show_banner); - write_setting_i(sesskey, "AuthTIS", cfg->try_tis_auth); - write_setting_i(sesskey, "AuthKI", cfg->try_ki_auth); - write_setting_i(sesskey, "AuthGSSAPI", cfg->try_gssapi_auth); -#ifndef NO_GSSAPI - wprefs(sesskey, "GSSLibs", gsslibkeywords, ngsslibs, - cfg->ssh_gsslist); - write_setting_filename(sesskey, "GSSCustom", cfg->ssh_gss_custom); -#endif - write_setting_i(sesskey, "SshNoShell", cfg->ssh_no_shell); - write_setting_i(sesskey, "SshProt", cfg->sshprot); - write_setting_s(sesskey, "LogHost", cfg->loghost); - write_setting_i(sesskey, "SSH2DES", cfg->ssh2_des_cbc); - write_setting_filename(sesskey, "PublicKeyFile", cfg->keyfile); - write_setting_s(sesskey, "RemoteCommand", cfg->remote_cmd); - write_setting_i(sesskey, "RFCEnviron", cfg->rfc_environ); -/* PuTTY SC start */ - write_setting_i(sesskey, "AuthPKCS11", cfg->try_pkcs11_auth); - write_setting_filename(sesskey, "PKCS11LibFile", cfg->pkcs11_libfile); - write_setting_s(sesskey, "PKCS11TokenLabel", cfg->pkcs11_token_label); - write_setting_s(sesskey, "PKCS11CertLabel", cfg->pkcs11_cert_label); -/* PuTTY SC end */ -/* PuTTY CAPI start */ -#ifdef _WINDOWS - write_setting_i(sesskey, "AuthCAPI", cfg->try_capi_auth); - write_setting_s(sesskey, "CAPICertID", cfg->capi_certID); -#endif -/* PuTTY CAPI end */ - write_setting_i(sesskey, "PassiveTelnet", cfg->passive_telnet); - write_setting_i(sesskey, "BackspaceIsDelete", cfg->bksp_is_delete); - write_setting_i(sesskey, "RXVTHomeEnd", cfg->rxvt_homeend); - write_setting_i(sesskey, "LinuxFunctionKeys", cfg->funky_type); - write_setting_i(sesskey, "NoApplicationKeys", cfg->no_applic_k); - write_setting_i(sesskey, "NoApplicationCursors", cfg->no_applic_c); - write_setting_i(sesskey, "NoMouseReporting", cfg->no_mouse_rep); - write_setting_i(sesskey, "NoRemoteResize", cfg->no_remote_resize); - write_setting_i(sesskey, "NoAltScreen", cfg->no_alt_screen); - write_setting_i(sesskey, "NoRemoteWinTitle", cfg->no_remote_wintitle); - write_setting_i(sesskey, "RemoteQTitleAction", cfg->remote_qtitle_action); - write_setting_i(sesskey, "NoDBackspace", cfg->no_dbackspace); - write_setting_i(sesskey, "NoRemoteCharset", cfg->no_remote_charset); - write_setting_i(sesskey, "ApplicationCursorKeys", cfg->app_cursor); - write_setting_i(sesskey, "ApplicationKeypad", cfg->app_keypad); - write_setting_i(sesskey, "NetHackKeypad", cfg->nethack_keypad); - write_setting_i(sesskey, "AltF4", cfg->alt_f4); - write_setting_i(sesskey, "AltSpace", cfg->alt_space); - write_setting_i(sesskey, "AltOnly", cfg->alt_only); - write_setting_i(sesskey, "ComposeKey", cfg->compose_key); - write_setting_i(sesskey, "CtrlAltKeys", cfg->ctrlaltkeys); - write_setting_i(sesskey, "TelnetKey", cfg->telnet_keyboard); - write_setting_i(sesskey, "TelnetRet", cfg->telnet_newline); - write_setting_i(sesskey, "LocalEcho", cfg->localecho); - write_setting_i(sesskey, "LocalEdit", cfg->localedit); - write_setting_s(sesskey, "Answerback", cfg->answerback); - write_setting_i(sesskey, "AlwaysOnTop", cfg->alwaysontop); - write_setting_i(sesskey, "FullScreenOnAltEnter", cfg->fullscreenonaltenter); - write_setting_i(sesskey, "HideMousePtr", cfg->hide_mouseptr); - write_setting_i(sesskey, "SunkenEdge", cfg->sunken_edge); - write_setting_i(sesskey, "WindowBorder", cfg->window_border); - write_setting_i(sesskey, "CurType", cfg->cursor_type); - write_setting_i(sesskey, "BlinkCur", cfg->blink_cur); - write_setting_i(sesskey, "Beep", cfg->beep); - write_setting_i(sesskey, "BeepInd", cfg->beep_ind); - write_setting_filename(sesskey, "BellWaveFile", cfg->bell_wavefile); - write_setting_i(sesskey, "BellOverload", cfg->bellovl); - write_setting_i(sesskey, "BellOverloadN", cfg->bellovl_n); - write_setting_i(sesskey, "BellOverloadT", cfg->bellovl_t -#ifdef PUTTY_UNIX_H - * 1000 -#endif - ); - write_setting_i(sesskey, "BellOverloadS", cfg->bellovl_s -#ifdef PUTTY_UNIX_H - * 1000 -#endif - ); - write_setting_i(sesskey, "ScrollbackLines", cfg->savelines); - write_setting_i(sesskey, "DECOriginMode", cfg->dec_om); - write_setting_i(sesskey, "AutoWrapMode", cfg->wrap_mode); - write_setting_i(sesskey, "LFImpliesCR", cfg->lfhascr); - write_setting_i(sesskey, "CRImpliesLF", cfg->crhaslf); - write_setting_i(sesskey, "DisableArabicShaping", cfg->arabicshaping); - write_setting_i(sesskey, "DisableBidi", cfg->bidi); - write_setting_i(sesskey, "WinNameAlways", cfg->win_name_always); - write_setting_s(sesskey, "WinTitle", cfg->wintitle); - write_setting_i(sesskey, "TermWidth", cfg->width); - write_setting_i(sesskey, "TermHeight", cfg->height); - write_setting_fontspec(sesskey, "Font", cfg->font); - write_setting_i(sesskey, "FontQuality", cfg->font_quality); - write_setting_i(sesskey, "FontVTMode", cfg->vtmode); - write_setting_i(sesskey, "UseSystemColours", cfg->system_colour); - write_setting_i(sesskey, "TryPalette", cfg->try_palette); - write_setting_i(sesskey, "ANSIColour", cfg->ansi_colour); - write_setting_i(sesskey, "Xterm256Colour", cfg->xterm_256_colour); - write_setting_i(sesskey, "BoldAsColour", cfg->bold_colour); - - for (i = 0; i < 22; i++) { - char buf[20], buf2[30]; - sprintf(buf, "Colour%d", i); - sprintf(buf2, "%d,%d,%d", cfg->colours[i][0], - cfg->colours[i][1], cfg->colours[i][2]); - write_setting_s(sesskey, buf, buf2); - } - write_setting_i(sesskey, "RawCNP", cfg->rawcnp); - write_setting_i(sesskey, "PasteRTF", cfg->rtf_paste); - write_setting_i(sesskey, "MouseIsXterm", cfg->mouse_is_xterm); - write_setting_i(sesskey, "RectSelect", cfg->rect_select); - write_setting_i(sesskey, "MouseOverride", cfg->mouse_override); + write_setting_s(sesskey, "HostName", conf_get_str(conf, CONF_host)); + write_setting_filename(sesskey, "LogFileName", conf_get_filename(conf, CONF_logfilename)); + write_setting_i(sesskey, "LogType", conf_get_int(conf, CONF_logtype)); + write_setting_i(sesskey, "LogFileClash", conf_get_int(conf, CONF_logxfovr)); + write_setting_i(sesskey, "LogFlush", conf_get_int(conf, CONF_logflush)); + write_setting_i(sesskey, "SSHLogOmitPasswords", conf_get_int(conf, CONF_logomitpass)); + write_setting_i(sesskey, "SSHLogOmitData", conf_get_int(conf, CONF_logomitdata)); + p = "raw"; + { + const Backend *b = backend_from_proto(conf_get_int(conf, CONF_protocol)); + if (b) + p = b->name; + } + write_setting_s(sesskey, "Protocol", p); + write_setting_i(sesskey, "PortNumber", conf_get_int(conf, CONF_port)); + /* The CloseOnExit numbers are arranged in a different order from + * the standard FORCE_ON / FORCE_OFF / AUTO. */ + write_setting_i(sesskey, "CloseOnExit", (conf_get_int(conf, CONF_close_on_exit)+2)%3); + write_setting_i(sesskey, "WarnOnClose", !!conf_get_int(conf, CONF_warn_on_close)); + write_setting_i(sesskey, "PingInterval", conf_get_int(conf, CONF_ping_interval) / 60); /* minutes */ + write_setting_i(sesskey, "PingIntervalSecs", conf_get_int(conf, CONF_ping_interval) % 60); /* seconds */ + write_setting_i(sesskey, "TCPNoDelay", conf_get_int(conf, CONF_tcp_nodelay)); + write_setting_i(sesskey, "TCPKeepalives", conf_get_int(conf, CONF_tcp_keepalives)); + write_setting_s(sesskey, "TerminalType", conf_get_str(conf, CONF_termtype)); + write_setting_s(sesskey, "TerminalSpeed", conf_get_str(conf, CONF_termspeed)); + wmap(sesskey, "TerminalModes", conf, CONF_ttymodes); + + /* Address family selection */ + write_setting_i(sesskey, "AddressFamily", conf_get_int(conf, CONF_addressfamily)); + + /* proxy settings */ + write_setting_s(sesskey, "ProxyExcludeList", conf_get_str(conf, CONF_proxy_exclude_list)); + write_setting_i(sesskey, "ProxyDNS", (conf_get_int(conf, CONF_proxy_dns)+2)%3); + write_setting_i(sesskey, "ProxyLocalhost", conf_get_int(conf, CONF_even_proxy_localhost)); + write_setting_i(sesskey, "ProxyMethod", conf_get_int(conf, CONF_proxy_type)); + write_setting_s(sesskey, "ProxyHost", conf_get_str(conf, CONF_proxy_host)); + write_setting_i(sesskey, "ProxyPort", conf_get_int(conf, CONF_proxy_port)); + write_setting_s(sesskey, "ProxyUsername", conf_get_str(conf, CONF_proxy_username)); + write_setting_s(sesskey, "ProxyPassword", conf_get_str(conf, CONF_proxy_password)); + write_setting_s(sesskey, "ProxyTelnetCommand", conf_get_str(conf, CONF_proxy_telnet_command)); + wmap(sesskey, "Environment", conf, CONF_environmt); + write_setting_s(sesskey, "UserName", conf_get_str(conf, CONF_username)); + write_setting_i(sesskey, "UserNameFromEnvironment", conf_get_int(conf, CONF_username_from_env)); + write_setting_s(sesskey, "LocalUserName", conf_get_str(conf, CONF_localusername)); + write_setting_i(sesskey, "NoPTY", conf_get_int(conf, CONF_nopty)); + write_setting_i(sesskey, "Compression", conf_get_int(conf, CONF_compression)); + write_setting_i(sesskey, "TryAgent", conf_get_int(conf, CONF_tryagent)); + write_setting_i(sesskey, "AgentFwd", conf_get_int(conf, CONF_agentfwd)); + write_setting_i(sesskey, "GssapiFwd", conf_get_int(conf, CONF_gssapifwd)); + write_setting_i(sesskey, "ChangeUsername", conf_get_int(conf, CONF_change_username)); + wprefs(sesskey, "Cipher", ciphernames, CIPHER_MAX, conf, CONF_ssh_cipherlist); + wprefs(sesskey, "KEX", kexnames, KEX_MAX, conf, CONF_ssh_kexlist); + write_setting_i(sesskey, "RekeyTime", conf_get_int(conf, CONF_ssh_rekey_time)); + write_setting_s(sesskey, "RekeyBytes", conf_get_str(conf, CONF_ssh_rekey_data)); + write_setting_i(sesskey, "SshNoAuth", conf_get_int(conf, CONF_ssh_no_userauth)); + write_setting_i(sesskey, "SshBanner", conf_get_int(conf, CONF_ssh_show_banner)); + write_setting_i(sesskey, "AuthTIS", conf_get_int(conf, CONF_try_tis_auth)); + write_setting_i(sesskey, "AuthKI", conf_get_int(conf, CONF_try_ki_auth)); + write_setting_i(sesskey, "AuthGSSAPI", conf_get_int(conf, CONF_try_gssapi_auth)); +#ifndef NO_GSSAPI + wprefs(sesskey, "GSSLibs", gsslibkeywords, ngsslibs, conf, CONF_ssh_gsslist); + write_setting_filename(sesskey, "GSSCustom", conf_get_filename(conf, CONF_ssh_gss_custom)); +#endif + write_setting_i(sesskey, "SshNoShell", conf_get_int(conf, CONF_ssh_no_shell)); + write_setting_i(sesskey, "SshProt", conf_get_int(conf, CONF_sshprot)); + write_setting_s(sesskey, "LogHost", conf_get_str(conf, CONF_loghost)); + write_setting_i(sesskey, "SSH2DES", conf_get_int(conf, CONF_ssh2_des_cbc)); + write_setting_filename(sesskey, "PublicKeyFile", conf_get_filename(conf, CONF_keyfile)); + write_setting_s(sesskey, "RemoteCommand", conf_get_str(conf, CONF_remote_cmd)); + write_setting_i(sesskey, "RFCEnviron", conf_get_int(conf, CONF_rfc_environ)); + write_setting_i(sesskey, "PassiveTelnet", conf_get_int(conf, CONF_passive_telnet)); + write_setting_i(sesskey, "BackspaceIsDelete", conf_get_int(conf, CONF_bksp_is_delete)); + write_setting_i(sesskey, "RXVTHomeEnd", conf_get_int(conf, CONF_rxvt_homeend)); + write_setting_i(sesskey, "LinuxFunctionKeys", conf_get_int(conf, CONF_funky_type)); + write_setting_i(sesskey, "NoApplicationKeys", conf_get_int(conf, CONF_no_applic_k)); + write_setting_i(sesskey, "NoApplicationCursors", conf_get_int(conf, CONF_no_applic_c)); + write_setting_i(sesskey, "NoMouseReporting", conf_get_int(conf, CONF_no_mouse_rep)); + write_setting_i(sesskey, "NoRemoteResize", conf_get_int(conf, CONF_no_remote_resize)); + write_setting_i(sesskey, "NoAltScreen", conf_get_int(conf, CONF_no_alt_screen)); + write_setting_i(sesskey, "NoRemoteWinTitle", conf_get_int(conf, CONF_no_remote_wintitle)); + write_setting_i(sesskey, "RemoteQTitleAction", conf_get_int(conf, CONF_remote_qtitle_action)); + write_setting_i(sesskey, "NoDBackspace", conf_get_int(conf, CONF_no_dbackspace)); + write_setting_i(sesskey, "NoRemoteCharset", conf_get_int(conf, CONF_no_remote_charset)); + write_setting_i(sesskey, "ApplicationCursorKeys", conf_get_int(conf, CONF_app_cursor)); + write_setting_i(sesskey, "ApplicationKeypad", conf_get_int(conf, CONF_app_keypad)); + write_setting_i(sesskey, "NetHackKeypad", conf_get_int(conf, CONF_nethack_keypad)); + write_setting_i(sesskey, "AltF4", conf_get_int(conf, CONF_alt_f4)); + write_setting_i(sesskey, "AltSpace", conf_get_int(conf, CONF_alt_space)); + write_setting_i(sesskey, "AltOnly", conf_get_int(conf, CONF_alt_only)); + write_setting_i(sesskey, "ComposeKey", conf_get_int(conf, CONF_compose_key)); + write_setting_i(sesskey, "CtrlAltKeys", conf_get_int(conf, CONF_ctrlaltkeys)); + write_setting_i(sesskey, "TelnetKey", conf_get_int(conf, CONF_telnet_keyboard)); + write_setting_i(sesskey, "TelnetRet", conf_get_int(conf, CONF_telnet_newline)); + write_setting_i(sesskey, "LocalEcho", conf_get_int(conf, CONF_localecho)); + write_setting_i(sesskey, "LocalEdit", conf_get_int(conf, CONF_localedit)); + write_setting_s(sesskey, "Answerback", conf_get_str(conf, CONF_answerback)); + write_setting_i(sesskey, "AlwaysOnTop", conf_get_int(conf, CONF_alwaysontop)); + write_setting_i(sesskey, "FullScreenOnAltEnter", conf_get_int(conf, CONF_fullscreenonaltenter)); + write_setting_i(sesskey, "HideMousePtr", conf_get_int(conf, CONF_hide_mouseptr)); + write_setting_i(sesskey, "SunkenEdge", conf_get_int(conf, CONF_sunken_edge)); + write_setting_i(sesskey, "WindowBorder", conf_get_int(conf, CONF_window_border)); + write_setting_i(sesskey, "CurType", conf_get_int(conf, CONF_cursor_type)); + write_setting_i(sesskey, "BlinkCur", conf_get_int(conf, CONF_blink_cur)); + write_setting_i(sesskey, "Beep", conf_get_int(conf, CONF_beep)); + write_setting_i(sesskey, "BeepInd", conf_get_int(conf, CONF_beep_ind)); + write_setting_filename(sesskey, "BellWaveFile", conf_get_filename(conf, CONF_bell_wavefile)); + write_setting_i(sesskey, "BellOverload", conf_get_int(conf, CONF_bellovl)); + write_setting_i(sesskey, "BellOverloadN", conf_get_int(conf, CONF_bellovl_n)); + write_setting_i(sesskey, "BellOverloadT", conf_get_int(conf, CONF_bellovl_t) +#ifdef PUTTY_UNIX_H + * 1000 +#endif + ); + write_setting_i(sesskey, "BellOverloadS", conf_get_int(conf, CONF_bellovl_s) +#ifdef PUTTY_UNIX_H + * 1000 +#endif + ); + write_setting_i(sesskey, "ScrollbackLines", conf_get_int(conf, CONF_savelines)); + write_setting_i(sesskey, "DECOriginMode", conf_get_int(conf, CONF_dec_om)); + write_setting_i(sesskey, "AutoWrapMode", conf_get_int(conf, CONF_wrap_mode)); + write_setting_i(sesskey, "LFImpliesCR", conf_get_int(conf, CONF_lfhascr)); + write_setting_i(sesskey, "CRImpliesLF", conf_get_int(conf, CONF_crhaslf)); + write_setting_i(sesskey, "DisableArabicShaping", conf_get_int(conf, CONF_arabicshaping)); + write_setting_i(sesskey, "DisableBidi", conf_get_int(conf, CONF_bidi)); + write_setting_i(sesskey, "WinNameAlways", conf_get_int(conf, CONF_win_name_always)); + write_setting_s(sesskey, "WinTitle", conf_get_str(conf, CONF_wintitle)); + write_setting_i(sesskey, "TermWidth", conf_get_int(conf, CONF_width)); + write_setting_i(sesskey, "TermHeight", conf_get_int(conf, CONF_height)); + write_setting_fontspec(sesskey, "Font", conf_get_fontspec(conf, CONF_font)); + write_setting_i(sesskey, "FontQuality", conf_get_int(conf, CONF_font_quality)); + write_setting_i(sesskey, "FontVTMode", conf_get_int(conf, CONF_vtmode)); + write_setting_i(sesskey, "UseSystemColours", conf_get_int(conf, CONF_system_colour)); + write_setting_i(sesskey, "TryPalette", conf_get_int(conf, CONF_try_palette)); + write_setting_i(sesskey, "ANSIColour", conf_get_int(conf, CONF_ansi_colour)); + write_setting_i(sesskey, "Xterm256Colour", conf_get_int(conf, CONF_xterm_256_colour)); + write_setting_i(sesskey, "BoldAsColour", conf_get_int(conf, CONF_bold_style)-1); + + for (i = 0; i < 22; i++) { + char buf[20], buf2[30]; + sprintf(buf, "Colour%d", i); + sprintf(buf2, "%d,%d,%d", + conf_get_int_int(conf, CONF_colours, i*3+0), + conf_get_int_int(conf, CONF_colours, i*3+1), + conf_get_int_int(conf, CONF_colours, i*3+2)); + write_setting_s(sesskey, buf, buf2); + } + write_setting_i(sesskey, "RawCNP", conf_get_int(conf, CONF_rawcnp)); + write_setting_i(sesskey, "PasteRTF", conf_get_int(conf, CONF_rtf_paste)); + write_setting_i(sesskey, "MouseIsXterm", conf_get_int(conf, CONF_mouse_is_xterm)); + write_setting_i(sesskey, "RectSelect", conf_get_int(conf, CONF_rect_select)); + write_setting_i(sesskey, "MouseOverride", conf_get_int(conf, CONF_mouse_override)); for (i = 0; i < 256; i += 32) { char buf[20], buf2[256]; int j; sprintf(buf, "Wordness%d", i); *buf2 = '\0'; for (j = i; j < i + 32; j++) { sprintf(buf2 + strlen(buf2), "%s%d", - (*buf2 ? "," : ""), cfg->wordness[j]); + (*buf2 ? "," : ""), + conf_get_int_int(conf, CONF_wordness, j)); } write_setting_s(sesskey, buf, buf2); } - write_setting_s(sesskey, "LineCodePage", cfg->line_codepage); - write_setting_i(sesskey, "CJKAmbigWide", cfg->cjk_ambig_wide); - 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); - write_setting_i(sesskey, "ScrollBarFullScreen", cfg->scrollbar_in_fullscreen); - write_setting_i(sesskey, "ScrollOnKey", cfg->scroll_on_key); - write_setting_i(sesskey, "ScrollOnDisp", cfg->scroll_on_disp); - write_setting_i(sesskey, "EraseToScrollback", cfg->erase_to_scrollback); - write_setting_i(sesskey, "LockSize", cfg->resize_action); - write_setting_i(sesskey, "BCE", cfg->bce); - write_setting_i(sesskey, "BlinkText", cfg->blinktext); - write_setting_i(sesskey, "X11Forward", cfg->x11_forward); - write_setting_s(sesskey, "X11Display", cfg->x11_display); - write_setting_i(sesskey, "X11AuthType", cfg->x11_auth); - write_setting_filename(sesskey, "X11AuthFile", cfg->xauthfile); - write_setting_i(sesskey, "LocalPortAcceptAll", cfg->lport_acceptall); - write_setting_i(sesskey, "RemotePortAcceptAll", cfg->rport_acceptall); - wmap(sesskey, "PortForwardings", cfg->portfwd, lenof(cfg->portfwd)); - write_setting_i(sesskey, "BugIgnore1", 2-cfg->sshbug_ignore1); - write_setting_i(sesskey, "BugPlainPW1", 2-cfg->sshbug_plainpw1); - write_setting_i(sesskey, "BugRSA1", 2-cfg->sshbug_rsa1); - write_setting_i(sesskey, "BugIgnore2", 2-cfg->sshbug_ignore2); - write_setting_i(sesskey, "BugHMAC2", 2-cfg->sshbug_hmac2); - write_setting_i(sesskey, "BugDeriveKey2", 2-cfg->sshbug_derivekey2); - write_setting_i(sesskey, "BugRSAPad2", 2-cfg->sshbug_rsapad2); - write_setting_i(sesskey, "BugPKSessID2", 2-cfg->sshbug_pksessid2); - write_setting_i(sesskey, "BugRekey2", 2-cfg->sshbug_rekey2); - write_setting_i(sesskey, "BugMaxPkt2", 2-cfg->sshbug_maxpkt2); - write_setting_i(sesskey, "StampUtmp", cfg->stamp_utmp); - write_setting_i(sesskey, "LoginShell", cfg->login_shell); - write_setting_i(sesskey, "ScrollbarOnLeft", cfg->scrollbar_on_left); - write_setting_fontspec(sesskey, "BoldFont", cfg->boldfont); - write_setting_fontspec(sesskey, "WideFont", cfg->widefont); - write_setting_fontspec(sesskey, "WideBoldFont", cfg->wideboldfont); - write_setting_i(sesskey, "ShadowBold", cfg->shadowbold); - write_setting_i(sesskey, "ShadowBoldOffset", cfg->shadowboldoffset); - write_setting_s(sesskey, "SerialLine", cfg->serline); - write_setting_i(sesskey, "SerialSpeed", cfg->serspeed); - write_setting_i(sesskey, "SerialDataBits", cfg->serdatabits); - write_setting_i(sesskey, "SerialStopHalfbits", cfg->serstopbits); - write_setting_i(sesskey, "SerialParity", cfg->serparity); - write_setting_i(sesskey, "SerialFlowControl", cfg->serflow); - write_setting_s(sesskey, "WindowClass", cfg->winclass); -} - -void load_settings(char *section, Config * cfg) + write_setting_s(sesskey, "LineCodePage", conf_get_str(conf, CONF_line_codepage)); + write_setting_i(sesskey, "CJKAmbigWide", conf_get_int(conf, CONF_cjk_ambig_wide)); + write_setting_i(sesskey, "UTF8Override", conf_get_int(conf, CONF_utf8_override)); + write_setting_s(sesskey, "Printer", conf_get_str(conf, CONF_printer)); + write_setting_i(sesskey, "CapsLockCyr", conf_get_int(conf, CONF_xlat_capslockcyr)); + write_setting_i(sesskey, "ScrollBar", conf_get_int(conf, CONF_scrollbar)); + write_setting_i(sesskey, "ScrollBarFullScreen", conf_get_int(conf, CONF_scrollbar_in_fullscreen)); + write_setting_i(sesskey, "ScrollOnKey", conf_get_int(conf, CONF_scroll_on_key)); + write_setting_i(sesskey, "ScrollOnDisp", conf_get_int(conf, CONF_scroll_on_disp)); + write_setting_i(sesskey, "EraseToScrollback", conf_get_int(conf, CONF_erase_to_scrollback)); + write_setting_i(sesskey, "LockSize", conf_get_int(conf, CONF_resize_action)); + write_setting_i(sesskey, "BCE", conf_get_int(conf, CONF_bce)); + write_setting_i(sesskey, "BlinkText", conf_get_int(conf, CONF_blinktext)); + write_setting_i(sesskey, "X11Forward", conf_get_int(conf, CONF_x11_forward)); + write_setting_s(sesskey, "X11Display", conf_get_str(conf, CONF_x11_display)); + write_setting_i(sesskey, "X11AuthType", conf_get_int(conf, CONF_x11_auth)); + write_setting_filename(sesskey, "X11AuthFile", conf_get_filename(conf, CONF_xauthfile)); + write_setting_i(sesskey, "LocalPortAcceptAll", conf_get_int(conf, CONF_lport_acceptall)); + write_setting_i(sesskey, "RemotePortAcceptAll", conf_get_int(conf, CONF_rport_acceptall)); + wmap(sesskey, "PortForwardings", conf, CONF_portfwd); + write_setting_i(sesskey, "BugIgnore1", 2-conf_get_int(conf, CONF_sshbug_ignore1)); + write_setting_i(sesskey, "BugPlainPW1", 2-conf_get_int(conf, CONF_sshbug_plainpw1)); + write_setting_i(sesskey, "BugRSA1", 2-conf_get_int(conf, CONF_sshbug_rsa1)); + write_setting_i(sesskey, "BugIgnore2", 2-conf_get_int(conf, CONF_sshbug_ignore2)); + write_setting_i(sesskey, "BugHMAC2", 2-conf_get_int(conf, CONF_sshbug_hmac2)); + write_setting_i(sesskey, "BugDeriveKey2", 2-conf_get_int(conf, CONF_sshbug_derivekey2)); + write_setting_i(sesskey, "BugRSAPad2", 2-conf_get_int(conf, CONF_sshbug_rsapad2)); + write_setting_i(sesskey, "BugPKSessID2", 2-conf_get_int(conf, CONF_sshbug_pksessid2)); + write_setting_i(sesskey, "BugRekey2", 2-conf_get_int(conf, CONF_sshbug_rekey2)); + write_setting_i(sesskey, "BugMaxPkt2", 2-conf_get_int(conf, CONF_sshbug_maxpkt2)); + write_setting_i(sesskey, "BugWinadj", 2-conf_get_int(conf, CONF_sshbug_winadj)); + write_setting_i(sesskey, "StampUtmp", conf_get_int(conf, CONF_stamp_utmp)); + write_setting_i(sesskey, "LoginShell", conf_get_int(conf, CONF_login_shell)); + write_setting_i(sesskey, "ScrollbarOnLeft", conf_get_int(conf, CONF_scrollbar_on_left)); + write_setting_fontspec(sesskey, "BoldFont", conf_get_fontspec(conf, CONF_boldfont)); + write_setting_fontspec(sesskey, "WideFont", conf_get_fontspec(conf, CONF_widefont)); + write_setting_fontspec(sesskey, "WideBoldFont", conf_get_fontspec(conf, CONF_wideboldfont)); + write_setting_i(sesskey, "ShadowBold", conf_get_int(conf, CONF_shadowbold)); + write_setting_i(sesskey, "ShadowBoldOffset", conf_get_int(conf, CONF_shadowboldoffset)); + write_setting_s(sesskey, "SerialLine", conf_get_str(conf, CONF_serline)); + write_setting_i(sesskey, "SerialSpeed", conf_get_int(conf, CONF_serspeed)); + write_setting_i(sesskey, "SerialDataBits", conf_get_int(conf, CONF_serdatabits)); + write_setting_i(sesskey, "SerialStopHalfbits", conf_get_int(conf, CONF_serstopbits)); + write_setting_i(sesskey, "SerialParity", conf_get_int(conf, CONF_serparity)); + write_setting_i(sesskey, "SerialFlowControl", conf_get_int(conf, CONF_serflow)); + write_setting_s(sesskey, "WindowClass", conf_get_str(conf, CONF_winclass)); + write_setting_i(sesskey, "ConnectionSharing", conf_get_int(conf, CONF_ssh_connection_sharing)); + write_setting_i(sesskey, "ConnectionSharingUpstream", conf_get_int(conf, CONF_ssh_connection_sharing_upstream)); + write_setting_i(sesskey, "ConnectionSharingDownstream", conf_get_int(conf, CONF_ssh_connection_sharing_downstream)); +} + +void load_settings(char *section, Conf *conf) { void *sesskey; sesskey = open_settings_r(section); - load_open_settings(sesskey, cfg); + load_open_settings(sesskey, conf); close_settings_r(sesskey); - if (cfg_launchable(cfg)) + if (conf_launchable(conf)) add_session_to_jumplist(section); } -void load_open_settings(void *sesskey, Config *cfg) +void load_open_settings(void *sesskey, Conf *conf) { int i; - char prot[10]; - - cfg->ssh_subsys = 0; /* FIXME: load this properly */ - cfg->remote_cmd_ptr = NULL; - cfg->remote_cmd_ptr2 = NULL; - cfg->ssh_nc_host[0] = '\0'; - - gpps(sesskey, "HostName", "", cfg->host, sizeof(cfg->host)); - gppfile(sesskey, "LogFileName", &cfg->logfilename); - gppi(sesskey, "LogType", 0, &cfg->logtype); - gppi(sesskey, "LogFileClash", LGXF_ASK, &cfg->logxfovr); - gppi(sesskey, "LogFlush", 1, &cfg->logflush); - gppi(sesskey, "SSHLogOmitPasswords", 1, &cfg->logomitpass); - gppi(sesskey, "SSHLogOmitData", 0, &cfg->logomitdata); - - gpps(sesskey, "Protocol", "default", prot, 10); - cfg->protocol = default_protocol; - cfg->port = default_port; + char *prot; + + conf_set_int(conf, CONF_ssh_subsys, 0); /* FIXME: load this properly */ + conf_set_str(conf, CONF_remote_cmd, ""); + conf_set_str(conf, CONF_remote_cmd2, ""); + conf_set_str(conf, CONF_ssh_nc_host, ""); + + gpps(sesskey, "HostName", "", conf, CONF_host); + gppfile(sesskey, "LogFileName", conf, CONF_logfilename); + gppi(sesskey, "LogType", 0, conf, CONF_logtype); + gppi(sesskey, "LogFileClash", LGXF_ASK, conf, CONF_logxfovr); + gppi(sesskey, "LogFlush", 1, conf, CONF_logflush); + gppi(sesskey, "SSHLogOmitPasswords", 1, conf, CONF_logomitpass); + gppi(sesskey, "SSHLogOmitData", 0, conf, CONF_logomitdata); + + prot = gpps_raw(sesskey, "Protocol", "default"); + conf_set_int(conf, CONF_protocol, default_protocol); + conf_set_int(conf, CONF_port, default_port); { const Backend *b = backend_from_name(prot); if (b) { - cfg->protocol = b->protocol; - gppi(sesskey, "PortNumber", default_port, &cfg->port); + conf_set_int(conf, CONF_protocol, b->protocol); + gppi(sesskey, "PortNumber", default_port, conf, CONF_port); } } + sfree(prot); /* Address family selection */ - gppi(sesskey, "AddressFamily", ADDRTYPE_UNSPEC, &cfg->addressfamily); + gppi(sesskey, "AddressFamily", ADDRTYPE_UNSPEC, conf, CONF_addressfamily); /* The CloseOnExit numbers are arranged in a different order from * the standard FORCE_ON / FORCE_OFF / AUTO. */ - gppi(sesskey, "CloseOnExit", 1, &i); cfg->close_on_exit = (i+1)%3; - gppi(sesskey, "WarnOnClose", 1, &cfg->warn_on_close); + i = gppi_raw(sesskey, "CloseOnExit", 1); conf_set_int(conf, CONF_close_on_exit, (i+1)%3); + gppi(sesskey, "WarnOnClose", 1, conf, CONF_warn_on_close); { /* This is two values for backward compatibility with 0.50/0.51 */ int pingmin, pingsec; - gppi(sesskey, "PingInterval", 0, &pingmin); - gppi(sesskey, "PingIntervalSecs", 0, &pingsec); - cfg->ping_interval = pingmin * 60 + pingsec; - } - gppi(sesskey, "TCPNoDelay", 1, &cfg->tcp_nodelay); - gppi(sesskey, "TCPKeepalives", 0, &cfg->tcp_keepalives); - gpps(sesskey, "TerminalType", "xterm", cfg->termtype, - sizeof(cfg->termtype)); - gpps(sesskey, "TerminalSpeed", "38400,38400", cfg->termspeed, - sizeof(cfg->termspeed)); - { + pingmin = gppi_raw(sesskey, "PingInterval", 0); + pingsec = gppi_raw(sesskey, "PingIntervalSecs", 0); + conf_set_int(conf, CONF_ping_interval, pingmin * 60 + pingsec); + } + gppi(sesskey, "TCPNoDelay", 1, conf, CONF_tcp_nodelay); + gppi(sesskey, "TCPKeepalives", 0, conf, CONF_tcp_keepalives); + gpps(sesskey, "TerminalType", "xterm", conf, CONF_termtype); + gpps(sesskey, "TerminalSpeed", "38400,38400", conf, CONF_termspeed); + if (!gppmap(sesskey, "TerminalModes", conf, CONF_ttymodes)) { /* This hardcodes a big set of defaults in any new saved * sessions. Let's hope we don't change our mind. */ - int i; - char *def = dupstr(""); - /* Default: all set to "auto" */ - for (i = 0; ttymodes[i]; i++) { - char *def2 = dupprintf("%s%s=A,", def, ttymodes[i]); - sfree(def); - def = def2; - } - gppmap(sesskey, "TerminalModes", def, - cfg->ttymodes, lenof(cfg->ttymodes)); - sfree(def); + for (i = 0; ttymodes[i]; i++) + conf_set_str_str(conf, CONF_ttymodes, ttymodes[i], "A"); } /* proxy settings */ - gpps(sesskey, "ProxyExcludeList", "", cfg->proxy_exclude_list, - sizeof(cfg->proxy_exclude_list)); - gppi(sesskey, "ProxyDNS", 1, &i); cfg->proxy_dns = (i+1)%3; - gppi(sesskey, "ProxyLocalhost", 0, &cfg->even_proxy_localhost); - gppi(sesskey, "ProxyMethod", -1, &cfg->proxy_type); - if (cfg->proxy_type == -1) { - int i; - gppi(sesskey, "ProxyType", 0, &i); - if (i == 0) - cfg->proxy_type = PROXY_NONE; - else if (i == 1) - cfg->proxy_type = PROXY_HTTP; - else if (i == 3) - cfg->proxy_type = PROXY_TELNET; - else if (i == 4) - cfg->proxy_type = PROXY_CMD; - else { - gppi(sesskey, "ProxySOCKSVersion", 5, &i); - if (i == 5) - cfg->proxy_type = PROXY_SOCKS5; - else - cfg->proxy_type = PROXY_SOCKS4; - } - } - gpps(sesskey, "ProxyHost", "proxy", cfg->proxy_host, - sizeof(cfg->proxy_host)); - gppi(sesskey, "ProxyPort", 80, &cfg->proxy_port); - gpps(sesskey, "ProxyUsername", "", cfg->proxy_username, - sizeof(cfg->proxy_username)); - gpps(sesskey, "ProxyPassword", "", cfg->proxy_password, - sizeof(cfg->proxy_password)); - gpps(sesskey, "ProxyTelnetCommand", "connect %host %port\\n", - cfg->proxy_telnet_command, sizeof(cfg->proxy_telnet_command)); - gppmap(sesskey, "Environment", "", cfg->environmt, lenof(cfg->environmt)); - gpps(sesskey, "UserName", "", cfg->username, sizeof(cfg->username)); - gppi(sesskey, "UserNameFromEnvironment", 0, &cfg->username_from_env); - gpps(sesskey, "LocalUserName", "", cfg->localusername, - sizeof(cfg->localusername)); - gppi(sesskey, "NoPTY", 0, &cfg->nopty); - gppi(sesskey, "Compression", 0, &cfg->compression); - gppi(sesskey, "TryAgent", 1, &cfg->tryagent); - gppi(sesskey, "AgentFwd", 0, &cfg->agentfwd); - gppi(sesskey, "ChangeUsername", 0, &cfg->change_username); - gppi(sesskey, "GssapiFwd", 0, &cfg->gssapifwd); - gprefs(sesskey, "Cipher", "\0", - ciphernames, CIPHER_MAX, cfg->ssh_cipherlist); + gpps(sesskey, "ProxyExcludeList", "", conf, CONF_proxy_exclude_list); + i = gppi_raw(sesskey, "ProxyDNS", 1); conf_set_int(conf, CONF_proxy_dns, (i+1)%3); + gppi(sesskey, "ProxyLocalhost", 0, conf, CONF_even_proxy_localhost); + gppi(sesskey, "ProxyMethod", -1, conf, CONF_proxy_type); + if (conf_get_int(conf, CONF_proxy_type) == -1) { + int i; + i = gppi_raw(sesskey, "ProxyType", 0); + if (i == 0) + conf_set_int(conf, CONF_proxy_type, PROXY_NONE); + else if (i == 1) + conf_set_int(conf, CONF_proxy_type, PROXY_HTTP); + else if (i == 3) + conf_set_int(conf, CONF_proxy_type, PROXY_TELNET); + else if (i == 4) + conf_set_int(conf, CONF_proxy_type, PROXY_CMD); + else { + i = gppi_raw(sesskey, "ProxySOCKSVersion", 5); + if (i == 5) + conf_set_int(conf, CONF_proxy_type, PROXY_SOCKS5); + else + conf_set_int(conf, CONF_proxy_type, PROXY_SOCKS4); + } + } + gpps(sesskey, "ProxyHost", "proxy", conf, CONF_proxy_host); + gppi(sesskey, "ProxyPort", 80, conf, CONF_proxy_port); + gpps(sesskey, "ProxyUsername", "", conf, CONF_proxy_username); + gpps(sesskey, "ProxyPassword", "", conf, CONF_proxy_password); + gpps(sesskey, "ProxyTelnetCommand", "connect %host %port\\n", + conf, CONF_proxy_telnet_command); + gppmap(sesskey, "Environment", conf, CONF_environmt); + gpps(sesskey, "UserName", "", conf, CONF_username); + gppi(sesskey, "UserNameFromEnvironment", 0, conf, CONF_username_from_env); + gpps(sesskey, "LocalUserName", "", conf, CONF_localusername); + gppi(sesskey, "NoPTY", 0, conf, CONF_nopty); + gppi(sesskey, "Compression", 0, conf, CONF_compression); + gppi(sesskey, "TryAgent", 1, conf, CONF_tryagent); + gppi(sesskey, "AgentFwd", 0, conf, CONF_agentfwd); + gppi(sesskey, "ChangeUsername", 0, conf, CONF_change_username); + gppi(sesskey, "GssapiFwd", 0, conf, CONF_gssapifwd); + gprefs(sesskey, "Cipher", "\0", + ciphernames, CIPHER_MAX, conf, CONF_ssh_cipherlist); { /* Backward-compatibility: we used to have an option to * disable gex under the "bugs" panel after one report of * a server which offered it then choked, but we never got * a server version string or any other reports. */ char *default_kexes; - gppi(sesskey, "BugDHGEx2", 0, &i); i = 2-i; + i = 2 - gppi_raw(sesskey, "BugDHGEx2", 0); if (i == FORCE_ON) default_kexes = "dh-group14-sha1,dh-group1-sha1,rsa,WARN,dh-gex-sha1"; else default_kexes = "dh-gex-sha1,dh-group14-sha1,dh-group1-sha1,rsa,WARN"; gprefs(sesskey, "KEX", default_kexes, - kexnames, KEX_MAX, cfg->ssh_kexlist); - } - gppi(sesskey, "RekeyTime", 60, &cfg->ssh_rekey_time); - gpps(sesskey, "RekeyBytes", "1G", cfg->ssh_rekey_data, - sizeof(cfg->ssh_rekey_data)); - gppi(sesskey, "SshProt", 2, &cfg->sshprot); - gpps(sesskey, "LogHost", "", cfg->loghost, sizeof(cfg->loghost)); - gppi(sesskey, "SSH2DES", 0, &cfg->ssh2_des_cbc); - gppi(sesskey, "SshNoAuth", 0, &cfg->ssh_no_userauth); - gppi(sesskey, "SshBanner", 1, &cfg->ssh_show_banner); - gppi(sesskey, "AuthTIS", 0, &cfg->try_tis_auth); - gppi(sesskey, "AuthKI", 1, &cfg->try_ki_auth); - gppi(sesskey, "AuthGSSAPI", 1, &cfg->try_gssapi_auth); + kexnames, KEX_MAX, conf, CONF_ssh_kexlist); + } + gppi(sesskey, "RekeyTime", 60, conf, CONF_ssh_rekey_time); + gpps(sesskey, "RekeyBytes", "1G", conf, CONF_ssh_rekey_data); + gppi(sesskey, "SshProt", 2, conf, CONF_sshprot); + gpps(sesskey, "LogHost", "", conf, CONF_loghost); + gppi(sesskey, "SSH2DES", 0, conf, CONF_ssh2_des_cbc); + gppi(sesskey, "SshNoAuth", 0, conf, CONF_ssh_no_userauth); + gppi(sesskey, "SshBanner", 1, conf, CONF_ssh_show_banner); + gppi(sesskey, "AuthTIS", 0, conf, CONF_try_tis_auth); + gppi(sesskey, "AuthKI", 1, conf, CONF_try_ki_auth); + gppi(sesskey, "AuthGSSAPI", 1, conf, CONF_try_gssapi_auth); #ifndef NO_GSSAPI gprefs(sesskey, "GSSLibs", "\0", - gsslibkeywords, ngsslibs, cfg->ssh_gsslist); - gppfile(sesskey, "GSSCustom", &cfg->ssh_gss_custom); -#endif - gppi(sesskey, "SshNoShell", 0, &cfg->ssh_no_shell); - gppfile(sesskey, "PublicKeyFile", &cfg->keyfile); - gpps(sesskey, "RemoteCommand", "", cfg->remote_cmd, - sizeof(cfg->remote_cmd)); - gppi(sesskey, "RFCEnviron", 0, &cfg->rfc_environ); - gppi(sesskey, "PassiveTelnet", 0, &cfg->passive_telnet); -/* PuTTY SC start */ - gppi(sesskey, "AuthPKCS11", 0, &cfg->try_pkcs11_auth); - gppfile(sesskey, "PKCS11LibFile", &cfg->pkcs11_libfile); - { int k; for(k=0;kpkcs11_token_label);k++) cfg->pkcs11_token_label[k] = '\0'; } - gpps(sesskey, "PKCS11TokenLabel", "", cfg->pkcs11_token_label, - sizeof(cfg->pkcs11_token_label)); - { int k; for(k=0;kpkcs11_cert_label);k++) cfg->pkcs11_cert_label[k] = '\0'; } - gpps(sesskey, "PKCS11CertLabel", "", cfg->pkcs11_cert_label, - sizeof(cfg->pkcs11_cert_label)); -/* PuTTY SC end */ -/* PuTTY CAPI start */ -#ifdef _WINDOWS - gppi(sesskey, "AuthCAPI", 0, &cfg->try_capi_auth); - gpps(sesskey, "CAPICertID", "", cfg->capi_certID, sizeof(cfg->capi_certID)); -#endif -/* PuTTY CAPI end */ - gppi(sesskey, "BackspaceIsDelete", 1, &cfg->bksp_is_delete); - gppi(sesskey, "RXVTHomeEnd", 0, &cfg->rxvt_homeend); - gppi(sesskey, "LinuxFunctionKeys", 0, &cfg->funky_type); - gppi(sesskey, "NoApplicationKeys", 0, &cfg->no_applic_k); - gppi(sesskey, "NoApplicationCursors", 0, &cfg->no_applic_c); - gppi(sesskey, "NoMouseReporting", 0, &cfg->no_mouse_rep); - gppi(sesskey, "NoRemoteResize", 0, &cfg->no_remote_resize); - gppi(sesskey, "NoAltScreen", 0, &cfg->no_alt_screen); - gppi(sesskey, "NoRemoteWinTitle", 0, &cfg->no_remote_wintitle); + gsslibkeywords, ngsslibs, conf, CONF_ssh_gsslist); + gppfile(sesskey, "GSSCustom", conf, CONF_ssh_gss_custom); +#endif + gppi(sesskey, "SshNoShell", 0, conf, CONF_ssh_no_shell); + gppfile(sesskey, "PublicKeyFile", conf, CONF_keyfile); + gpps(sesskey, "RemoteCommand", "", conf, CONF_remote_cmd); + gppi(sesskey, "RFCEnviron", 0, conf, CONF_rfc_environ); + gppi(sesskey, "PassiveTelnet", 0, conf, CONF_passive_telnet); + gppi(sesskey, "BackspaceIsDelete", 1, conf, CONF_bksp_is_delete); + gppi(sesskey, "RXVTHomeEnd", 0, conf, CONF_rxvt_homeend); + gppi(sesskey, "LinuxFunctionKeys", 0, conf, CONF_funky_type); + gppi(sesskey, "NoApplicationKeys", 0, conf, CONF_no_applic_k); + gppi(sesskey, "NoApplicationCursors", 0, conf, CONF_no_applic_c); + gppi(sesskey, "NoMouseReporting", 0, conf, CONF_no_mouse_rep); + gppi(sesskey, "NoRemoteResize", 0, conf, CONF_no_remote_resize); + gppi(sesskey, "NoAltScreen", 0, conf, CONF_no_alt_screen); + gppi(sesskey, "NoRemoteWinTitle", 0, conf, CONF_no_remote_wintitle); { /* Backward compatibility */ - int no_remote_qtitle; - gppi(sesskey, "NoRemoteQTitle", 1, &no_remote_qtitle); + int no_remote_qtitle = gppi_raw(sesskey, "NoRemoteQTitle", 1); /* We deliberately interpret the old setting of "no response" as * "empty string". This changes the behaviour, but hopefully for * the better; the user can always recover the old behaviour. */ gppi(sesskey, "RemoteQTitleAction", no_remote_qtitle ? TITLE_EMPTY : TITLE_REAL, - &cfg->remote_qtitle_action); - } - gppi(sesskey, "NoDBackspace", 0, &cfg->no_dbackspace); - gppi(sesskey, "NoRemoteCharset", 0, &cfg->no_remote_charset); - gppi(sesskey, "ApplicationCursorKeys", 0, &cfg->app_cursor); - gppi(sesskey, "ApplicationKeypad", 0, &cfg->app_keypad); - gppi(sesskey, "NetHackKeypad", 0, &cfg->nethack_keypad); - gppi(sesskey, "AltF4", 1, &cfg->alt_f4); - gppi(sesskey, "AltSpace", 0, &cfg->alt_space); - gppi(sesskey, "AltOnly", 0, &cfg->alt_only); - gppi(sesskey, "ComposeKey", 0, &cfg->compose_key); - gppi(sesskey, "CtrlAltKeys", 1, &cfg->ctrlaltkeys); - gppi(sesskey, "TelnetKey", 0, &cfg->telnet_keyboard); - gppi(sesskey, "TelnetRet", 1, &cfg->telnet_newline); - gppi(sesskey, "LocalEcho", AUTO, &cfg->localecho); - gppi(sesskey, "LocalEdit", AUTO, &cfg->localedit); - gpps(sesskey, "Answerback", "PuTTY", cfg->answerback, - sizeof(cfg->answerback)); - gppi(sesskey, "AlwaysOnTop", 0, &cfg->alwaysontop); - gppi(sesskey, "FullScreenOnAltEnter", 0, &cfg->fullscreenonaltenter); - gppi(sesskey, "HideMousePtr", 0, &cfg->hide_mouseptr); - gppi(sesskey, "SunkenEdge", 0, &cfg->sunken_edge); - gppi(sesskey, "WindowBorder", 1, &cfg->window_border); - gppi(sesskey, "CurType", 0, &cfg->cursor_type); - gppi(sesskey, "BlinkCur", 0, &cfg->blink_cur); - /* pedantic compiler tells me I can't use &cfg->beep as an int * :-) */ - gppi(sesskey, "Beep", 1, &cfg->beep); - gppi(sesskey, "BeepInd", 0, &cfg->beep_ind); - gppfile(sesskey, "BellWaveFile", &cfg->bell_wavefile); - gppi(sesskey, "BellOverload", 1, &cfg->bellovl); - gppi(sesskey, "BellOverloadN", 5, &cfg->bellovl_n); - gppi(sesskey, "BellOverloadT", 2*TICKSPERSEC -#ifdef PUTTY_UNIX_H - *1000 -#endif - , &i); - cfg->bellovl_t = i -#ifdef PUTTY_UNIX_H - / 1000 -#endif - ; - gppi(sesskey, "BellOverloadS", 5*TICKSPERSEC -#ifdef PUTTY_UNIX_H - *1000 -#endif - , &i); - cfg->bellovl_s = i -#ifdef PUTTY_UNIX_H - / 1000 -#endif - ; - gppi(sesskey, "ScrollbackLines", 200, &cfg->savelines); - gppi(sesskey, "DECOriginMode", 0, &cfg->dec_om); - gppi(sesskey, "AutoWrapMode", 1, &cfg->wrap_mode); - gppi(sesskey, "LFImpliesCR", 0, &cfg->lfhascr); - gppi(sesskey, "CRImpliesLF", 0, &cfg->crhaslf); - gppi(sesskey, "DisableArabicShaping", 0, &cfg->arabicshaping); - gppi(sesskey, "DisableBidi", 0, &cfg->bidi); - gppi(sesskey, "WinNameAlways", 1, &cfg->win_name_always); - gpps(sesskey, "WinTitle", "", cfg->wintitle, sizeof(cfg->wintitle)); - gppi(sesskey, "TermWidth", 80, &cfg->width); - gppi(sesskey, "TermHeight", 24, &cfg->height); - gppfont(sesskey, "Font", &cfg->font); - gppi(sesskey, "FontQuality", FQ_DEFAULT, &cfg->font_quality); - gppi(sesskey, "FontVTMode", VT_UNICODE, (int *) &cfg->vtmode); - gppi(sesskey, "UseSystemColours", 0, &cfg->system_colour); - gppi(sesskey, "TryPalette", 0, &cfg->try_palette); - gppi(sesskey, "ANSIColour", 1, &cfg->ansi_colour); - gppi(sesskey, "Xterm256Colour", 1, &cfg->xterm_256_colour); - gppi(sesskey, "BoldAsColour", 1, &cfg->bold_colour); + conf, CONF_remote_qtitle_action); + } + gppi(sesskey, "NoDBackspace", 0, conf, CONF_no_dbackspace); + gppi(sesskey, "NoRemoteCharset", 0, conf, CONF_no_remote_charset); + gppi(sesskey, "ApplicationCursorKeys", 0, conf, CONF_app_cursor); + gppi(sesskey, "ApplicationKeypad", 0, conf, CONF_app_keypad); + gppi(sesskey, "NetHackKeypad", 0, conf, CONF_nethack_keypad); + gppi(sesskey, "AltF4", 1, conf, CONF_alt_f4); + gppi(sesskey, "AltSpace", 0, conf, CONF_alt_space); + gppi(sesskey, "AltOnly", 0, conf, CONF_alt_only); + gppi(sesskey, "ComposeKey", 0, conf, CONF_compose_key); + gppi(sesskey, "CtrlAltKeys", 1, conf, CONF_ctrlaltkeys); + gppi(sesskey, "TelnetKey", 0, conf, CONF_telnet_keyboard); + gppi(sesskey, "TelnetRet", 1, conf, CONF_telnet_newline); + gppi(sesskey, "LocalEcho", AUTO, conf, CONF_localecho); + gppi(sesskey, "LocalEdit", AUTO, conf, CONF_localedit); + gpps(sesskey, "Answerback", "PuTTY", conf, CONF_answerback); + gppi(sesskey, "AlwaysOnTop", 0, conf, CONF_alwaysontop); + gppi(sesskey, "FullScreenOnAltEnter", 0, conf, CONF_fullscreenonaltenter); + gppi(sesskey, "HideMousePtr", 0, conf, CONF_hide_mouseptr); + gppi(sesskey, "SunkenEdge", 0, conf, CONF_sunken_edge); + gppi(sesskey, "WindowBorder", 1, conf, CONF_window_border); + gppi(sesskey, "CurType", 0, conf, CONF_cursor_type); + gppi(sesskey, "BlinkCur", 0, conf, CONF_blink_cur); + /* pedantic compiler tells me I can't use conf, CONF_beep as an int * :-) */ + gppi(sesskey, "Beep", 1, conf, CONF_beep); + gppi(sesskey, "BeepInd", 0, conf, CONF_beep_ind); + gppfile(sesskey, "BellWaveFile", conf, CONF_bell_wavefile); + gppi(sesskey, "BellOverload", 1, conf, CONF_bellovl); + gppi(sesskey, "BellOverloadN", 5, conf, CONF_bellovl_n); + i = gppi_raw(sesskey, "BellOverloadT", 2*TICKSPERSEC +#ifdef PUTTY_UNIX_H + *1000 +#endif + ); + conf_set_int(conf, CONF_bellovl_t, i +#ifdef PUTTY_UNIX_H + / 1000 +#endif + ); + i = gppi_raw(sesskey, "BellOverloadS", 5*TICKSPERSEC +#ifdef PUTTY_UNIX_H + *1000 +#endif + ); + conf_set_int(conf, CONF_bellovl_s, i +#ifdef PUTTY_UNIX_H + / 1000 +#endif + ); + gppi(sesskey, "ScrollbackLines", 2000, conf, CONF_savelines); + gppi(sesskey, "DECOriginMode", 0, conf, CONF_dec_om); + gppi(sesskey, "AutoWrapMode", 1, conf, CONF_wrap_mode); + gppi(sesskey, "LFImpliesCR", 0, conf, CONF_lfhascr); + gppi(sesskey, "CRImpliesLF", 0, conf, CONF_crhaslf); + gppi(sesskey, "DisableArabicShaping", 0, conf, CONF_arabicshaping); + gppi(sesskey, "DisableBidi", 0, conf, CONF_bidi); + gppi(sesskey, "WinNameAlways", 1, conf, CONF_win_name_always); + gpps(sesskey, "WinTitle", "", conf, CONF_wintitle); + gppi(sesskey, "TermWidth", 80, conf, CONF_width); + gppi(sesskey, "TermHeight", 24, conf, CONF_height); + gppfont(sesskey, "Font", conf, CONF_font); + gppi(sesskey, "FontQuality", FQ_DEFAULT, conf, CONF_font_quality); + gppi(sesskey, "FontVTMode", VT_UNICODE, conf, CONF_vtmode); + gppi(sesskey, "UseSystemColours", 0, conf, CONF_system_colour); + gppi(sesskey, "TryPalette", 0, conf, CONF_try_palette); + gppi(sesskey, "ANSIColour", 1, conf, CONF_ansi_colour); + gppi(sesskey, "Xterm256Colour", 1, conf, CONF_xterm_256_colour); + i = gppi_raw(sesskey, "BoldAsColour", 1); conf_set_int(conf, CONF_bold_style, i+1); for (i = 0; i < 22; i++) { static const char *const defaults[] = { "187,187,187", "255,255,255", "0,0,0", "85,85,85", "0,0,0", "0,255,0", "0,0,0", "85,85,85", "187,0,0", "255,85,85", "0,187,0", "85,255,85", "187,187,0", "255,255,85", "0,0,187", "85,85,255", "187,0,187", "255,85,255", "0,187,187", "85,255,255", "187,187,187", "255,255,255" }; - char buf[20], buf2[30]; + char buf[20], *buf2; int c0, c1, c2; sprintf(buf, "Colour%d", i); - gpps(sesskey, buf, defaults[i], buf2, sizeof(buf2)); - if (sscanf(buf2, "%d,%d,%d", &c0, &c1, &c2) == 3) { - cfg->colours[i][0] = c0; - cfg->colours[i][1] = c1; - cfg->colours[i][2] = c2; - } - } - gppi(sesskey, "RawCNP", 0, &cfg->rawcnp); - gppi(sesskey, "PasteRTF", 0, &cfg->rtf_paste); - gppi(sesskey, "MouseIsXterm", 0, &cfg->mouse_is_xterm); - gppi(sesskey, "RectSelect", 0, &cfg->rect_select); - gppi(sesskey, "MouseOverride", 1, &cfg->mouse_override); + buf2 = gpps_raw(sesskey, buf, defaults[i]); + if (sscanf(buf2, "%d,%d,%d", &c0, &c1, &c2) == 3) { + conf_set_int_int(conf, CONF_colours, i*3+0, c0); + conf_set_int_int(conf, CONF_colours, i*3+1, c1); + conf_set_int_int(conf, CONF_colours, i*3+2, c2); + } + sfree(buf2); + } + gppi(sesskey, "RawCNP", 0, conf, CONF_rawcnp); + gppi(sesskey, "PasteRTF", 0, conf, CONF_rtf_paste); + gppi(sesskey, "MouseIsXterm", 0, conf, CONF_mouse_is_xterm); + gppi(sesskey, "RectSelect", 0, conf, CONF_rect_select); + gppi(sesskey, "MouseOverride", 1, conf, CONF_mouse_override); for (i = 0; i < 256; i += 32) { static const char *const defaults[] = { "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0", "0,1,2,1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,1,1,1,1,1,1", "1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,1,1,1,2", @@ -863,90 +909,93 @@ "1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1", "1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1", "2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2", "2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2" }; - char buf[20], buf2[256], *p; + char buf[20], *buf2, *p; int j; sprintf(buf, "Wordness%d", i); - gpps(sesskey, buf, defaults[i / 32], buf2, sizeof(buf2)); + buf2 = gpps_raw(sesskey, buf, defaults[i / 32]); p = buf2; for (j = i; j < i + 32; j++) { char *q = p; while (*p && *p != ',') p++; if (*p == ',') *p++ = '\0'; - cfg->wordness[j] = atoi(q); + conf_set_int_int(conf, CONF_wordness, j, atoi(q)); } + sfree(buf2); } /* * The empty default for LineCodePage will be converted later * into a plausible default for the locale. */ - gpps(sesskey, "LineCodePage", "", cfg->line_codepage, - sizeof(cfg->line_codepage)); - gppi(sesskey, "CJKAmbigWide", 0, &cfg->cjk_ambig_wide); - 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); - gppi(sesskey, "ScrollBarFullScreen", 0, &cfg->scrollbar_in_fullscreen); - gppi(sesskey, "ScrollOnKey", 0, &cfg->scroll_on_key); - gppi(sesskey, "ScrollOnDisp", 1, &cfg->scroll_on_disp); - gppi(sesskey, "EraseToScrollback", 1, &cfg->erase_to_scrollback); - gppi(sesskey, "LockSize", 0, &cfg->resize_action); - gppi(sesskey, "BCE", 1, &cfg->bce); - gppi(sesskey, "BlinkText", 0, &cfg->blinktext); - gppi(sesskey, "X11Forward", 0, &cfg->x11_forward); - gpps(sesskey, "X11Display", "", cfg->x11_display, - sizeof(cfg->x11_display)); - gppi(sesskey, "X11AuthType", X11_MIT, &cfg->x11_auth); - gppfile(sesskey, "X11AuthFile", &cfg->xauthfile); - - gppi(sesskey, "LocalPortAcceptAll", 0, &cfg->lport_acceptall); - gppi(sesskey, "RemotePortAcceptAll", 0, &cfg->rport_acceptall); - gppmap(sesskey, "PortForwardings", "", cfg->portfwd, lenof(cfg->portfwd)); - gppi(sesskey, "BugIgnore1", 0, &i); cfg->sshbug_ignore1 = 2-i; - gppi(sesskey, "BugPlainPW1", 0, &i); cfg->sshbug_plainpw1 = 2-i; - gppi(sesskey, "BugRSA1", 0, &i); cfg->sshbug_rsa1 = 2-i; - gppi(sesskey, "BugIgnore2", 0, &i); cfg->sshbug_ignore2 = 2-i; + gpps(sesskey, "LineCodePage", "", conf, CONF_line_codepage); + gppi(sesskey, "CJKAmbigWide", 0, conf, CONF_cjk_ambig_wide); + gppi(sesskey, "UTF8Override", 1, conf, CONF_utf8_override); + gpps(sesskey, "Printer", "", conf, CONF_printer); + gppi(sesskey, "CapsLockCyr", 0, conf, CONF_xlat_capslockcyr); + gppi(sesskey, "ScrollBar", 1, conf, CONF_scrollbar); + gppi(sesskey, "ScrollBarFullScreen", 0, conf, CONF_scrollbar_in_fullscreen); + gppi(sesskey, "ScrollOnKey", 0, conf, CONF_scroll_on_key); + gppi(sesskey, "ScrollOnDisp", 1, conf, CONF_scroll_on_disp); + gppi(sesskey, "EraseToScrollback", 1, conf, CONF_erase_to_scrollback); + gppi(sesskey, "LockSize", 0, conf, CONF_resize_action); + gppi(sesskey, "BCE", 1, conf, CONF_bce); + gppi(sesskey, "BlinkText", 0, conf, CONF_blinktext); + gppi(sesskey, "X11Forward", 0, conf, CONF_x11_forward); + gpps(sesskey, "X11Display", "", conf, CONF_x11_display); + gppi(sesskey, "X11AuthType", X11_MIT, conf, CONF_x11_auth); + gppfile(sesskey, "X11AuthFile", conf, CONF_xauthfile); + + gppi(sesskey, "LocalPortAcceptAll", 0, conf, CONF_lport_acceptall); + gppi(sesskey, "RemotePortAcceptAll", 0, conf, CONF_rport_acceptall); + gppmap(sesskey, "PortForwardings", conf, CONF_portfwd); + i = gppi_raw(sesskey, "BugIgnore1", 0); conf_set_int(conf, CONF_sshbug_ignore1, 2-i); + i = gppi_raw(sesskey, "BugPlainPW1", 0); conf_set_int(conf, CONF_sshbug_plainpw1, 2-i); + i = gppi_raw(sesskey, "BugRSA1", 0); conf_set_int(conf, CONF_sshbug_rsa1, 2-i); + i = gppi_raw(sesskey, "BugIgnore2", 0); conf_set_int(conf, CONF_sshbug_ignore2, 2-i); { int i; - gppi(sesskey, "BugHMAC2", 0, &i); cfg->sshbug_hmac2 = 2-i; - if (cfg->sshbug_hmac2 == AUTO) { - gppi(sesskey, "BuggyMAC", 0, &i); - if (i == 1) - cfg->sshbug_hmac2 = FORCE_ON; - } - } - gppi(sesskey, "BugDeriveKey2", 0, &i); cfg->sshbug_derivekey2 = 2-i; - gppi(sesskey, "BugRSAPad2", 0, &i); cfg->sshbug_rsapad2 = 2-i; - gppi(sesskey, "BugPKSessID2", 0, &i); cfg->sshbug_pksessid2 = 2-i; - gppi(sesskey, "BugRekey2", 0, &i); cfg->sshbug_rekey2 = 2-i; - gppi(sesskey, "BugMaxPkt2", 0, &i); cfg->sshbug_maxpkt2 = 2-i; - cfg->ssh_simple = FALSE; - gppi(sesskey, "StampUtmp", 1, &cfg->stamp_utmp); - gppi(sesskey, "LoginShell", 1, &cfg->login_shell); - gppi(sesskey, "ScrollbarOnLeft", 0, &cfg->scrollbar_on_left); - gppi(sesskey, "ShadowBold", 0, &cfg->shadowbold); - gppfont(sesskey, "BoldFont", &cfg->boldfont); - gppfont(sesskey, "WideFont", &cfg->widefont); - gppfont(sesskey, "WideBoldFont", &cfg->wideboldfont); - gppi(sesskey, "ShadowBoldOffset", 1, &cfg->shadowboldoffset); - gpps(sesskey, "SerialLine", "", cfg->serline, sizeof(cfg->serline)); - gppi(sesskey, "SerialSpeed", 9600, &cfg->serspeed); - gppi(sesskey, "SerialDataBits", 8, &cfg->serdatabits); - gppi(sesskey, "SerialStopHalfbits", 2, &cfg->serstopbits); - gppi(sesskey, "SerialParity", SER_PAR_NONE, &cfg->serparity); - gppi(sesskey, "SerialFlowControl", SER_FLOW_XONXOFF, &cfg->serflow); - gpps(sesskey, "WindowClass", "", cfg->winclass, sizeof(cfg->winclass)); -} - -void do_defaults(char *session, Config * cfg) -{ - load_settings(session, cfg); + i = gppi_raw(sesskey, "BugHMAC2", 0); conf_set_int(conf, CONF_sshbug_hmac2, 2-i); + if (2-i == AUTO) { + i = gppi_raw(sesskey, "BuggyMAC", 0); + if (i == 1) + conf_set_int(conf, CONF_sshbug_hmac2, FORCE_ON); + } + } + i = gppi_raw(sesskey, "BugDeriveKey2", 0); conf_set_int(conf, CONF_sshbug_derivekey2, 2-i); + i = gppi_raw(sesskey, "BugRSAPad2", 0); conf_set_int(conf, CONF_sshbug_rsapad2, 2-i); + i = gppi_raw(sesskey, "BugPKSessID2", 0); conf_set_int(conf, CONF_sshbug_pksessid2, 2-i); + i = gppi_raw(sesskey, "BugRekey2", 0); conf_set_int(conf, CONF_sshbug_rekey2, 2-i); + i = gppi_raw(sesskey, "BugMaxPkt2", 0); conf_set_int(conf, CONF_sshbug_maxpkt2, 2-i); + i = gppi_raw(sesskey, "BugWinadj", 0); conf_set_int(conf, CONF_sshbug_winadj, 2-i); + conf_set_int(conf, CONF_ssh_simple, FALSE); + gppi(sesskey, "StampUtmp", 1, conf, CONF_stamp_utmp); + gppi(sesskey, "LoginShell", 1, conf, CONF_login_shell); + gppi(sesskey, "ScrollbarOnLeft", 0, conf, CONF_scrollbar_on_left); + gppi(sesskey, "ShadowBold", 0, conf, CONF_shadowbold); + gppfont(sesskey, "BoldFont", conf, CONF_boldfont); + gppfont(sesskey, "WideFont", conf, CONF_widefont); + gppfont(sesskey, "WideBoldFont", conf, CONF_wideboldfont); + gppi(sesskey, "ShadowBoldOffset", 1, conf, CONF_shadowboldoffset); + gpps(sesskey, "SerialLine", "", conf, CONF_serline); + gppi(sesskey, "SerialSpeed", 9600, conf, CONF_serspeed); + gppi(sesskey, "SerialDataBits", 8, conf, CONF_serdatabits); + gppi(sesskey, "SerialStopHalfbits", 2, conf, CONF_serstopbits); + gppi(sesskey, "SerialParity", SER_PAR_NONE, conf, CONF_serparity); + gppi(sesskey, "SerialFlowControl", SER_FLOW_XONXOFF, conf, CONF_serflow); + gpps(sesskey, "WindowClass", "", conf, CONF_winclass); + gppi(sesskey, "ConnectionSharing", 0, conf, CONF_ssh_connection_sharing); + gppi(sesskey, "ConnectionSharingUpstream", 1, conf, CONF_ssh_connection_sharing_upstream); + gppi(sesskey, "ConnectionSharingDownstream", 1, conf, CONF_ssh_connection_sharing_downstream); +} + +void do_defaults(char *session, Conf *conf) +{ + load_settings(session, conf); } static int sessioncmp(const void *av, const void *bv) { const char *a = *(const char *const *) av; Index: sftp.c ================================================================== --- sftp.c +++ sftp.c @@ -42,35 +42,36 @@ memcpy(pkt->data + pkt->length - len, data, len); } static void sftp_pkt_addbyte(struct sftp_packet *pkt, unsigned char byte) { sftp_pkt_adddata(pkt, &byte, 1); +} +static void sftp_pkt_adduint32(struct sftp_packet *pkt, + unsigned long value) +{ + unsigned char x[4]; + PUT_32BIT(x, value); + sftp_pkt_adddata(pkt, x, 4); } static struct sftp_packet *sftp_pkt_init(int pkt_type) { struct sftp_packet *pkt; pkt = snew(struct sftp_packet); pkt->data = NULL; pkt->savedpos = -1; pkt->length = 0; pkt->maxlen = 0; + sftp_pkt_adduint32(pkt, 0); /* length field will be filled in later */ sftp_pkt_addbyte(pkt, (unsigned char) pkt_type); return pkt; } /* static void sftp_pkt_addbool(struct sftp_packet *pkt, unsigned char value) { sftp_pkt_adddata(pkt, &value, 1); } */ -static void sftp_pkt_adduint32(struct sftp_packet *pkt, - unsigned long value) -{ - unsigned char x[4]; - PUT_32BIT(x, value); - sftp_pkt_adddata(pkt, x, 4); -} static void sftp_pkt_adduint64(struct sftp_packet *pkt, uint64 value) { unsigned char x[8]; PUT_32BIT(x, value.hi); PUT_32BIT(x + 4, value.lo); @@ -147,11 +148,11 @@ char **p, int *length) { *p = NULL; if (pkt->length - pkt->savedpos < 4) return 0; - *length = GET_32BIT(pkt->data + pkt->savedpos); + *length = toint(GET_32BIT(pkt->data + pkt->savedpos)); pkt->savedpos += 4; if ((int)(pkt->length - pkt->savedpos) < *length || *length < 0) { *length = 0; return 0; } @@ -213,13 +214,12 @@ * Send and receive packet functions. */ int sftp_send(struct sftp_packet *pkt) { int ret; - char x[4]; - PUT_32BIT(x, pkt->length); - ret = (sftp_senddata(x, 4) && sftp_senddata(pkt->data, pkt->length)); + PUT_32BIT(pkt->data, pkt->length - 4); + ret = sftp_senddata(pkt->data, pkt->length); sftp_pkt_free(pkt); return ret; } struct sftp_packet *sftp_recv(void) { @@ -364,11 +364,10 @@ } req = find234(sftp_requests, &id, sftp_reqfind); if (!req || !req->registered) { fxp_internal_error("request ID mismatch\n"); - sftp_pkt_free(pktin); return NULL; } del234(sftp_requests, req); @@ -546,20 +545,24 @@ } /* * Open a file. */ -struct sftp_request *fxp_open_send(char *path, int type) +struct sftp_request *fxp_open_send(char *path, int type, + struct fxp_attrs *attrs) { struct sftp_request *req = sftp_alloc_request(); struct sftp_packet *pktout; pktout = sftp_pkt_init(SSH_FXP_OPEN); sftp_pkt_adduint32(pktout, req->id); sftp_pkt_addstring(pktout, path); sftp_pkt_adduint32(pktout, type); - sftp_pkt_adduint32(pktout, 0); /* (FIXME) empty ATTRS structure */ + if (attrs) + sftp_pkt_addattrs(pktout, *attrs); + else + sftp_pkt_adduint32(pktout, 0); /* empty ATTRS structure */ sftp_send(pktout); return req; } @@ -1191,19 +1194,27 @@ xfer_download_queue(xfer); return xfer; } +/* + * Returns INT_MIN to indicate that it didn't even get as far as + * fxp_read_recv and hence has not freed pktin. + */ int xfer_download_gotpkt(struct fxp_xfer *xfer, struct sftp_packet *pktin) { struct sftp_request *rreq; struct req *rr; rreq = sftp_find_request(pktin); + if (!rreq) + return INT_MIN; /* this packet doesn't even make sense */ rr = (struct req *)fxp_get_userdata(rreq); - if (!rr) - return 0; /* this packet isn't ours */ + if (!rr) { + fxp_internal_error("request ID is not part of the current download"); + return INT_MIN; /* this packet isn't ours */ + } rr->retlen = fxp_read_recv(pktin, rreq, rr->buffer, rr->len); #ifdef DEBUG_DOWNLOAD printf("read request %p has returned [%d]\n", rr, rr->retlen); #endif @@ -1370,20 +1381,28 @@ #ifdef DEBUG_UPLOAD { char buf[40]; uint64_decimal(rr->offset, buf); printf("queueing write request %p at %s [len %d]\n", rr, buf, len); } #endif } +/* + * Returns INT_MIN to indicate that it didn't even get as far as + * fxp_write_recv and hence has not freed pktin. + */ int xfer_upload_gotpkt(struct fxp_xfer *xfer, struct sftp_packet *pktin) { struct sftp_request *rreq; struct req *rr, *prev, *next; int ret; rreq = sftp_find_request(pktin); + if (!rreq) + return INT_MIN; /* this packet doesn't even make sense */ rr = (struct req *)fxp_get_userdata(rreq); - if (!rr) - return 0; /* this packet isn't ours */ + if (!rr) { + fxp_internal_error("request ID is not part of the current upload"); + return INT_MIN; /* this packet isn't ours */ + } ret = fxp_write_recv(pktin, rreq); #ifdef DEBUG_UPLOAD printf("write request %p has returned [%d]\n", rr, ret); #endif Index: sftp.h ================================================================== --- sftp.h +++ sftp.h @@ -80,10 +80,23 @@ unsigned long permissions; unsigned long atime; unsigned long mtime; }; +/* + * Copy between the possibly-unused permissions field in an fxp_attrs + * and a possibly-negative integer containing the same permissions. + */ +#define PUT_PERMISSIONS(attrs, perms) \ + ((perms) >= 0 ? \ + ((attrs).flags |= SSH_FILEXFER_ATTR_PERMISSIONS, \ + (attrs).permissions = (perms)) : \ + ((attrs).flags &= ~SSH_FILEXFER_ATTR_PERMISSIONS)) +#define GET_PERMISSIONS(attrs) \ + ((attrs).flags & SSH_FILEXFER_ATTR_PERMISSIONS ? \ + (attrs).permissions : -1) + struct fxp_handle { char *hstring; int hlen; }; @@ -114,13 +127,15 @@ */ struct sftp_request *fxp_realpath_send(char *path); char *fxp_realpath_recv(struct sftp_packet *pktin, struct sftp_request *req); /* - * Open a file. + * Open a file. 'attrs' contains attributes to be applied to the file + * if it's being created. */ -struct sftp_request *fxp_open_send(char *path, int type); +struct sftp_request *fxp_open_send(char *path, int type, + struct fxp_attrs *attrs); struct fxp_handle *fxp_open_recv(struct sftp_packet *pktin, struct sftp_request *req); /* * Open a directory. Index: ssh.c ================================================================== --- ssh.c +++ ssh.c @@ -15,127 +15,17 @@ #ifndef NO_GSSAPI #include "sshgssc.h" #include "sshgss.h" #endif -/* PuTTY SC start */ -#include "pkcs11.h" -#include "sc.h" -/* PuTTY SC end */ - -/* PuTTY CAPI start */ -#ifdef _WINDOWS - #include "capi.h" -#endif -/* PuTTY CAPI end */ - #ifndef FALSE #define FALSE 0 #endif #ifndef TRUE #define TRUE 1 #endif -#define SSH1_MSG_DISCONNECT 1 /* 0x1 */ -#define SSH1_SMSG_PUBLIC_KEY 2 /* 0x2 */ -#define SSH1_CMSG_SESSION_KEY 3 /* 0x3 */ -#define SSH1_CMSG_USER 4 /* 0x4 */ -#define SSH1_CMSG_AUTH_RSA 6 /* 0x6 */ -#define SSH1_SMSG_AUTH_RSA_CHALLENGE 7 /* 0x7 */ -#define SSH1_CMSG_AUTH_RSA_RESPONSE 8 /* 0x8 */ -#define SSH1_CMSG_AUTH_PASSWORD 9 /* 0x9 */ -#define SSH1_CMSG_REQUEST_PTY 10 /* 0xa */ -#define SSH1_CMSG_WINDOW_SIZE 11 /* 0xb */ -#define SSH1_CMSG_EXEC_SHELL 12 /* 0xc */ -#define SSH1_CMSG_EXEC_CMD 13 /* 0xd */ -#define SSH1_SMSG_SUCCESS 14 /* 0xe */ -#define SSH1_SMSG_FAILURE 15 /* 0xf */ -#define SSH1_CMSG_STDIN_DATA 16 /* 0x10 */ -#define SSH1_SMSG_STDOUT_DATA 17 /* 0x11 */ -#define SSH1_SMSG_STDERR_DATA 18 /* 0x12 */ -#define SSH1_CMSG_EOF 19 /* 0x13 */ -#define SSH1_SMSG_EXIT_STATUS 20 /* 0x14 */ -#define SSH1_MSG_CHANNEL_OPEN_CONFIRMATION 21 /* 0x15 */ -#define SSH1_MSG_CHANNEL_OPEN_FAILURE 22 /* 0x16 */ -#define SSH1_MSG_CHANNEL_DATA 23 /* 0x17 */ -#define SSH1_MSG_CHANNEL_CLOSE 24 /* 0x18 */ -#define SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION 25 /* 0x19 */ -#define SSH1_SMSG_X11_OPEN 27 /* 0x1b */ -#define SSH1_CMSG_PORT_FORWARD_REQUEST 28 /* 0x1c */ -#define SSH1_MSG_PORT_OPEN 29 /* 0x1d */ -#define SSH1_CMSG_AGENT_REQUEST_FORWARDING 30 /* 0x1e */ -#define SSH1_SMSG_AGENT_OPEN 31 /* 0x1f */ -#define SSH1_MSG_IGNORE 32 /* 0x20 */ -#define SSH1_CMSG_EXIT_CONFIRMATION 33 /* 0x21 */ -#define SSH1_CMSG_X11_REQUEST_FORWARDING 34 /* 0x22 */ -#define SSH1_CMSG_AUTH_RHOSTS_RSA 35 /* 0x23 */ -#define SSH1_MSG_DEBUG 36 /* 0x24 */ -#define SSH1_CMSG_REQUEST_COMPRESSION 37 /* 0x25 */ -#define SSH1_CMSG_AUTH_TIS 39 /* 0x27 */ -#define SSH1_SMSG_AUTH_TIS_CHALLENGE 40 /* 0x28 */ -#define SSH1_CMSG_AUTH_TIS_RESPONSE 41 /* 0x29 */ -#define SSH1_CMSG_AUTH_CCARD 70 /* 0x46 */ -#define SSH1_SMSG_AUTH_CCARD_CHALLENGE 71 /* 0x47 */ -#define SSH1_CMSG_AUTH_CCARD_RESPONSE 72 /* 0x48 */ - -#define SSH1_AUTH_RHOSTS 1 /* 0x1 */ -#define SSH1_AUTH_RSA 2 /* 0x2 */ -#define SSH1_AUTH_PASSWORD 3 /* 0x3 */ -#define SSH1_AUTH_RHOSTS_RSA 4 /* 0x4 */ -#define SSH1_AUTH_TIS 5 /* 0x5 */ -#define SSH1_AUTH_CCARD 16 /* 0x10 */ - -#define SSH1_PROTOFLAG_SCREEN_NUMBER 1 /* 0x1 */ -/* Mask for protoflags we will echo back to server if seen */ -#define SSH1_PROTOFLAGS_SUPPORTED 0 /* 0x1 */ - -#define SSH2_MSG_DISCONNECT 1 /* 0x1 */ -#define SSH2_MSG_IGNORE 2 /* 0x2 */ -#define SSH2_MSG_UNIMPLEMENTED 3 /* 0x3 */ -#define SSH2_MSG_DEBUG 4 /* 0x4 */ -#define SSH2_MSG_SERVICE_REQUEST 5 /* 0x5 */ -#define SSH2_MSG_SERVICE_ACCEPT 6 /* 0x6 */ -#define SSH2_MSG_KEXINIT 20 /* 0x14 */ -#define SSH2_MSG_NEWKEYS 21 /* 0x15 */ -#define SSH2_MSG_KEXDH_INIT 30 /* 0x1e */ -#define SSH2_MSG_KEXDH_REPLY 31 /* 0x1f */ -#define SSH2_MSG_KEX_DH_GEX_REQUEST 30 /* 0x1e */ -#define SSH2_MSG_KEX_DH_GEX_GROUP 31 /* 0x1f */ -#define SSH2_MSG_KEX_DH_GEX_INIT 32 /* 0x20 */ -#define SSH2_MSG_KEX_DH_GEX_REPLY 33 /* 0x21 */ -#define SSH2_MSG_KEXRSA_PUBKEY 30 /* 0x1e */ -#define SSH2_MSG_KEXRSA_SECRET 31 /* 0x1f */ -#define SSH2_MSG_KEXRSA_DONE 32 /* 0x20 */ -#define SSH2_MSG_USERAUTH_REQUEST 50 /* 0x32 */ -#define SSH2_MSG_USERAUTH_FAILURE 51 /* 0x33 */ -#define SSH2_MSG_USERAUTH_SUCCESS 52 /* 0x34 */ -#define SSH2_MSG_USERAUTH_BANNER 53 /* 0x35 */ -#define SSH2_MSG_USERAUTH_PK_OK 60 /* 0x3c */ -#define SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ 60 /* 0x3c */ -#define SSH2_MSG_USERAUTH_INFO_REQUEST 60 /* 0x3c */ -#define SSH2_MSG_USERAUTH_INFO_RESPONSE 61 /* 0x3d */ -#define SSH2_MSG_GLOBAL_REQUEST 80 /* 0x50 */ -#define SSH2_MSG_REQUEST_SUCCESS 81 /* 0x51 */ -#define SSH2_MSG_REQUEST_FAILURE 82 /* 0x52 */ -#define SSH2_MSG_CHANNEL_OPEN 90 /* 0x5a */ -#define SSH2_MSG_CHANNEL_OPEN_CONFIRMATION 91 /* 0x5b */ -#define SSH2_MSG_CHANNEL_OPEN_FAILURE 92 /* 0x5c */ -#define SSH2_MSG_CHANNEL_WINDOW_ADJUST 93 /* 0x5d */ -#define SSH2_MSG_CHANNEL_DATA 94 /* 0x5e */ -#define SSH2_MSG_CHANNEL_EXTENDED_DATA 95 /* 0x5f */ -#define SSH2_MSG_CHANNEL_EOF 96 /* 0x60 */ -#define SSH2_MSG_CHANNEL_CLOSE 97 /* 0x61 */ -#define SSH2_MSG_CHANNEL_REQUEST 98 /* 0x62 */ -#define SSH2_MSG_CHANNEL_SUCCESS 99 /* 0x63 */ -#define SSH2_MSG_CHANNEL_FAILURE 100 /* 0x64 */ -#define SSH2_MSG_USERAUTH_GSSAPI_RESPONSE 60 -#define SSH2_MSG_USERAUTH_GSSAPI_TOKEN 61 -#define SSH2_MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE 63 -#define SSH2_MSG_USERAUTH_GSSAPI_ERROR 64 -#define SSH2_MSG_USERAUTH_GSSAPI_ERRTOK 65 -#define SSH2_MSG_USERAUTH_GSSAPI_MIC 66 - /* * Packet type contexts, so that ssh2_pkt_type can correctly decode * the ambiguous type numbers back into the correct type strings. */ typedef enum { @@ -150,26 +40,10 @@ SSH2_PKTCTX_PASSWORD, SSH2_PKTCTX_GSSAPI, SSH2_PKTCTX_KBDINTER } Pkt_ACtx; -#define SSH2_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT 1 /* 0x1 */ -#define SSH2_DISCONNECT_PROTOCOL_ERROR 2 /* 0x2 */ -#define SSH2_DISCONNECT_KEY_EXCHANGE_FAILED 3 /* 0x3 */ -#define SSH2_DISCONNECT_HOST_AUTHENTICATION_FAILED 4 /* 0x4 */ -#define SSH2_DISCONNECT_MAC_ERROR 5 /* 0x5 */ -#define SSH2_DISCONNECT_COMPRESSION_ERROR 6 /* 0x6 */ -#define SSH2_DISCONNECT_SERVICE_NOT_AVAILABLE 7 /* 0x7 */ -#define SSH2_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED 8 /* 0x8 */ -#define SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE 9 /* 0x9 */ -#define SSH2_DISCONNECT_CONNECTION_LOST 10 /* 0xa */ -#define SSH2_DISCONNECT_BY_APPLICATION 11 /* 0xb */ -#define SSH2_DISCONNECT_TOO_MANY_CONNECTIONS 12 /* 0xc */ -#define SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER 13 /* 0xd */ -#define SSH2_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE 14 /* 0xe */ -#define SSH2_DISCONNECT_ILLEGAL_USER_NAME 15 /* 0xf */ - static const char *const ssh2_disconnect_reasons[] = { NULL, "host not allowed to connect", "protocol error", "key exchange failed", @@ -185,17 +59,10 @@ "auth cancelled by user", "no more auth methods available", "illegal user name", }; -#define SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED 1 /* 0x1 */ -#define SSH2_OPEN_CONNECT_FAILED 2 /* 0x2 */ -#define SSH2_OPEN_UNKNOWN_CHANNEL_TYPE 3 /* 0x3 */ -#define SSH2_OPEN_RESOURCE_SHORTAGE 4 /* 0x4 */ - -#define SSH2_EXTENDED_DATA_STDERR 1 /* 0x1 */ - /* * Various remote-bug flags. */ #define BUG_CHOKES_ON_SSH1_IGNORE 1 #define BUG_SSH2_HMAC 2 @@ -205,10 +72,11 @@ #define BUG_SSH2_DERIVEKEY 32 #define BUG_SSH2_REKEY 64 #define BUG_SSH2_PK_SESSIONID 128 #define BUG_SSH2_MAXPKT 256 #define BUG_CHOKES_ON_SSH2_IGNORE 512 +#define BUG_CHOKES_ON_WINADJ 1024 /* * Codes for terminal modes. * Most of these are the same in SSH-1 and SSH-2. * This list is derived from RFC 4254 and @@ -413,13 +281,10 @@ #undef translatec /* Enumeration values for fields in SSH-1 packets */ enum { PKT_END, PKT_INT, PKT_CHAR, PKT_DATA, PKT_STR, PKT_BIGNUM, - /* These values are for communicating relevant semantics of - * fields to the packet logging code. */ - PKTT_OTHER, PKTT_PASSWORD, PKTT_DATA }; /* * Coroutine mechanics for the sillier bits of the code. If these * macros look impenetrable to you, you might find it helpful to @@ -439,16 +304,20 @@ * - select the C/C++ tab and the General category * - under `Debug info:', select anything _other_ than `Program * Database for Edit and Continue'. */ #define crBegin(v) { int *crLine = &v; switch(v) { case 0:; -#define crState(t) \ - struct t *s; \ - if (!ssh->t) ssh->t = snew(struct t); \ - s = ssh->t; +#define crBeginState crBegin(s->crLine) +#define crStateP(t, v) \ + struct t *s; \ + if (!(v)) { s = (v) = snew(struct t); s->crLine = 0; } \ + s = (v); +#define crState(t) crStateP(t, ssh->t) #define crFinish(z) } *crLine = 0; return (z); } #define crFinishV } *crLine = 0; return; } +#define crFinishFree(z) } sfree(s); return (z); } +#define crFinishFreeV } sfree(s); return; } #define crReturn(z) \ do {\ *crLine =__LINE__; return (z); case __LINE__:;\ } while (0) #define crReturnV \ @@ -458,28 +327,23 @@ #define crStop(z) do{ *crLine = 0; return (z); }while(0) #define crStopV do{ *crLine = 0; return; }while(0) #define crWaitUntil(c) do { crReturn(0); } while (!(c)) #define crWaitUntilV(c) do { crReturnV; } while (!(c)) -typedef struct ssh_tag *Ssh; struct Packet; -/* PuTTY SC start */ -int loaded_pkcs11=FALSE; -/* PuTTY SC end */ - static struct Packet *ssh1_pkt_init(int pkt_type); static struct Packet *ssh2_pkt_init(int pkt_type); static void ssh_pkt_ensure(struct Packet *, int length); -static void ssh_pkt_adddata(struct Packet *, void *data, int len); +static void ssh_pkt_adddata(struct Packet *, const void *data, int len); static void ssh_pkt_addbyte(struct Packet *, unsigned char value); static void ssh2_pkt_addbool(struct Packet *, unsigned char value); static void ssh_pkt_adduint32(struct Packet *, unsigned long value); static void ssh_pkt_addstring_start(struct Packet *); -static void ssh_pkt_addstring_str(struct Packet *, char *data); -static void ssh_pkt_addstring_data(struct Packet *, char *data, int len); -static void ssh_pkt_addstring(struct Packet *, char *data); +static void ssh_pkt_addstring_str(struct Packet *, const char *data); +static void ssh_pkt_addstring_data(struct Packet *, const char *data, int len); +static void ssh_pkt_addstring(struct Packet *, const char *data); static unsigned char *ssh2_mpint_fmt(Bignum b, int *len); static void ssh1_pkt_addmp(struct Packet *, Bignum b); static void ssh2_pkt_addmp(struct Packet *, Bignum b); static int ssh2_pkt_construct(Ssh, struct Packet *); static void ssh2_pkt_send(Ssh, struct Packet *); @@ -486,10 +350,12 @@ static void ssh2_pkt_send_noqueue(Ssh, struct Packet *); static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen, struct Packet *pktin); static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, struct Packet *pktin); +static void ssh2_channel_check_close(struct ssh_channel *c); +static void ssh_channel_destroy(struct ssh_channel *c); /* * Buffer management constants. There are several of these for * various different purposes: * @@ -528,17 +394,14 @@ #define OUR_V2_WINSIZE 16384 #define OUR_V2_BIGWIN 0x7fffffff #define OUR_V2_MAXPKT 0x4000UL #define OUR_V2_PACKETLIMIT 0x9000UL -/* Maximum length of passwords/passphrases (arbitrary) */ -#define SSH_MAX_PASSWORD_LEN 100 - const static struct ssh_signkey *hostkey_algs[] = { &ssh_rsa, &ssh_dss }; const static struct ssh_mac *macs[] = { - &ssh_hmac_sha1, &ssh_hmac_sha1_96, &ssh_hmac_md5 + &ssh_hmac_sha256, &ssh_hmac_sha1, &ssh_hmac_sha1_96, &ssh_hmac_md5 }; const static struct ssh_mac *buggymacs[] = { &ssh_hmac_sha1_buggy, &ssh_hmac_sha1_96_buggy, &ssh_hmac_md5 }; @@ -572,19 +435,41 @@ enum { /* channel types */ CHAN_MAINSESSION, CHAN_X11, CHAN_AGENT, CHAN_SOCKDATA, - CHAN_SOCKDATA_DORMANT /* one the remote hasn't confirmed */ + CHAN_SOCKDATA_DORMANT, /* one the remote hasn't confirmed */ + /* + * CHAN_SHARING indicates a channel which is tracked here on + * behalf of a connection-sharing downstream. We do almost nothing + * with these channels ourselves: all messages relating to them + * get thrown straight to sshshare.c and passed on almost + * unmodified to downstream. + */ + CHAN_SHARING, + /* + * CHAN_ZOMBIE is used to indicate a channel for which we've + * already destroyed the local data source: for instance, if a + * forwarded port experiences a socket error on the local side, we + * immediately destroy its local socket and turn the SSH channel + * into CHAN_ZOMBIE. + */ + CHAN_ZOMBIE }; +typedef void (*handler_fn_t)(Ssh ssh, struct Packet *pktin); +typedef void (*chandler_fn_t)(Ssh ssh, struct Packet *pktin, void *ctx); +typedef void (*cchandler_fn_t)(struct ssh_channel *, struct Packet *, void *); + /* - * little structure to keep track of outstanding WINDOW_ADJUSTs + * Each channel has a queue of outstanding CHANNEL_REQUESTS and their + * handlers. */ -struct winadj { - struct winadj *next; - unsigned size; +struct outstanding_channel_request { + cchandler_fn_t handler; + void *ctx; + struct outstanding_channel_request *next; }; /* * 2-3-4 tree storing channels. */ @@ -601,22 +486,39 @@ * 2 We have sent SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION. * 4 We have received SSH1_MSG_CHANNEL_CLOSE. * 8 We have received SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION. * * A channel is completely finished with when all four bits are set. + * + * In SSH-2, the four bits mean: + * + * 1 We have sent SSH2_MSG_CHANNEL_EOF. + * 2 We have sent SSH2_MSG_CHANNEL_CLOSE. + * 4 We have received SSH2_MSG_CHANNEL_EOF. + * 8 We have received SSH2_MSG_CHANNEL_CLOSE. + * + * A channel is completely finished with when we have both sent + * and received CLOSE. + * + * The symbolic constants below use the SSH-2 terminology, which + * is a bit confusing in SSH-1, but we have to use _something_. */ +#define CLOSES_SENT_EOF 1 +#define CLOSES_SENT_CLOSE 2 +#define CLOSES_RCVD_EOF 4 +#define CLOSES_RCVD_CLOSE 8 int closes; /* - * This flag indicates that a close is pending on the outgoing - * side of the channel: that is, wherever we're getting the data - * for this channel has sent us some data followed by EOF. We - * can't actually close the channel until we've finished sending - * the data, so we set this flag instead to remind us to - * initiate the closing process once our buffer is clear. + * This flag indicates that an EOF is pending on the outgoing side + * of the channel: that is, wherever we're getting the data for + * this channel has sent us some data followed by EOF. We can't + * actually send the EOF until we've finished sending the data, so + * we set this flag instead to remind us to do so once our buffer + * is clear. */ - int pending_close; + int pending_eof; /* * True if this channel is causing the underlying connection to be * throttled. */ @@ -632,29 +534,34 @@ * the remote end had available to it after it sent the * last data packet or window adjust ack. */ int remlocwin; /* - * These store the list of window adjusts that haven't + * These store the list of channel requests that haven't * been acked. */ - struct winadj *winadj_head, *winadj_tail; + struct outstanding_channel_request *chanreq_head, *chanreq_tail; enum { THROTTLED, UNTHROTTLING, UNTHROTTLED } throttle_state; } v2; } v; union { struct ssh_agent_channel { unsigned char *message; unsigned char msglen[4]; unsigned lensofar, totallen; + int outstanding_requests; } a; struct ssh_x11_channel { - Socket s; + struct X11Connection *xconn; + int initial; } x11; struct ssh_pfd_channel { - Socket s; + struct PortForwarding *pf; } pfd; + struct ssh_sharing_channel { + void *ctx; + } sharing; } u; }; /* * 2-3-4 tree storing remote->local port forwardings. SSH-1 and SSH-2 @@ -685,16 +592,25 @@ */ struct ssh_portfwd; /* forward declaration */ struct ssh_rportfwd { unsigned sport, dport; - char dhost[256]; + char *shost, *dhost; char *sportdesc; + void *share_ctx; struct ssh_portfwd *pfrec; }; -#define free_rportfwd(pf) ( \ - ((pf) ? (sfree((pf)->sportdesc)) : (void)0 ), sfree(pf) ) + +static void free_rportfwd(struct ssh_rportfwd *pf) +{ + if (pf) { + sfree(pf->sportdesc); + sfree(pf->shost); + sfree(pf->dhost); + sfree(pf); + } +} /* * Separately to the rportfwd tree (which is for looking up port * open requests from the server), a tree of _these_ structures is * used to keep track of all the currently open port forwardings, @@ -707,41 +623,67 @@ unsigned sport, dport; char *saddr, *daddr; char *sserv, *dserv; struct ssh_rportfwd *remote; int addressfamily; - void *local; + struct PortListener *local; }; #define free_portfwd(pf) ( \ ((pf) ? (sfree((pf)->saddr), sfree((pf)->daddr), \ sfree((pf)->sserv), sfree((pf)->dserv)) : (void)0 ), sfree(pf) ) struct Packet { - long length; /* length of `data' actually used */ + long length; /* length of packet: see below */ long forcepad; /* SSH-2: force padding to at least this length */ int type; /* only used for incoming packets */ unsigned long sequence; /* SSH-2 incoming sequence number */ unsigned char *data; /* allocated storage */ unsigned char *body; /* offset of payload within `data' */ - long savedpos; /* temporary index into `data' (for strings) */ + long savedpos; /* dual-purpose saved packet position: see below */ long maxlen; /* amount of storage allocated for `data' */ long encrypted_len; /* for SSH-2 total-size counting */ /* - * State associated with packet logging + * A note on the 'length' and 'savedpos' fields above. + * + * Incoming packets are set up so that pkt->length is measured + * relative to pkt->body, which itself points to a few bytes after + * pkt->data (skipping some uninteresting header fields including + * the packet type code). The ssh_pkt_get* functions all expect + * this setup, and they also use pkt->savedpos to indicate how far + * through the packet being decoded they've got - and that, too, + * is an offset from pkt->body rather than pkt->data. + * + * During construction of an outgoing packet, however, pkt->length + * is measured relative to the base pointer pkt->data, and + * pkt->body is not really used for anything until the packet is + * ready for sending. In this mode, pkt->savedpos is reused as a + * temporary variable by the addstring functions, which write out + * a string length field and then keep going back and updating it + * as more data is appended to the subsequent string data field; + * pkt->savedpos stores the offset (again relative to pkt->data) + * of the start of the string data field. */ - int logmode; - int nblanks; - struct logblank_t *blanks; + + /* Extra metadata used in SSH packet logging mode, allowing us to + * log in the packet header line that the packet came from a + * connection-sharing downstream and what if anything unusual was + * done to it. The additional_log_text field is expected to be a + * static string - it will not be freed. */ + unsigned downstream_id; + const char *additional_log_text; }; static void ssh1_protocol(Ssh ssh, void *vin, int inlen, struct Packet *pktin); static void ssh2_protocol(Ssh ssh, void *vin, int inlen, struct Packet *pktin); +static void ssh2_bare_connection_protocol(Ssh ssh, void *vin, int inlen, + struct Packet *pktin); static void ssh1_protocol_setup(Ssh ssh); static void ssh2_protocol_setup(Ssh ssh); +static void ssh2_bare_connection_protocol_setup(Ssh ssh); static void ssh_size(void *handle, int width, int height); static void ssh_special(void *handle, Telnet_Special); static int ssh2_try_send(struct ssh_channel *c); static void ssh2_add_channel_data(struct ssh_channel *c, char *buf, int len); static void ssh_throttle_all(Ssh ssh, int enable, int bufsize); @@ -749,13 +691,14 @@ static int ssh_sendbuffer(void *handle); static int ssh_do_close(Ssh ssh, int notify_exit); static unsigned long ssh_pkt_getuint32(struct Packet *pkt); static int ssh2_pkt_getbool(struct Packet *pkt); static void ssh_pkt_getstring(struct Packet *pkt, char **p, int *length); -static void ssh2_timer(void *ctx, long now); -static int do_ssh2_transport(Ssh ssh, void *vin, int inlen, - struct Packet *pktin); +static void ssh2_timer(void *ctx, unsigned long now); +static void do_ssh2_transport(Ssh ssh, void *vin, int inlen, + struct Packet *pktin); +static void ssh2_msg_unexpected(Ssh ssh, struct Packet *pktin); struct rdpkt1_state_tag { long len, pad, biglen, to_read; unsigned long realcrc, gotcrc; unsigned char *p; @@ -770,12 +713,17 @@ int cipherblk; unsigned long incoming_sequence; struct Packet *pktin; }; -typedef void (*handler_fn_t)(Ssh ssh, struct Packet *pktin); -typedef void (*chandler_fn_t)(Ssh ssh, struct Packet *pktin, void *ctx); +struct rdpkt2_bare_state_tag { + char length[4]; + long packetlen; + int i; + unsigned long incoming_sequence; + struct Packet *pktin; +}; struct queued_handler; struct queued_handler { int msg1, msg2; chandler_fn_t handler; @@ -811,13 +759,18 @@ void *cs_mac_ctx, *sc_mac_ctx; const struct ssh_compress *cscomp, *sccomp; void *cs_comp_ctx, *sc_comp_ctx; const struct ssh_kex *kex; const struct ssh_signkey *hostkey; + char *hostkey_str; /* string representation, for easy checking in rekeys */ unsigned char v2_session_id[SSH2_KEX_MAX_HASH_LEN]; int v2_session_id_len; void *kex_ctx; + + int bare_connection; + int attempting_connshare; + void *connshare; char *savedhost; int savedport; int send_ok; int echoing, editing; @@ -843,10 +796,12 @@ SSH_STATE_SESSION, SSH_STATE_CLOSED } state; int size_needed, eof_needed; + int sent_console_eof; + int got_pty; /* affects EOF behaviour on main channel */ struct Packet **queue; int queuelen, queuesize; int queueing; unsigned char *deferred_send_data; @@ -864,10 +819,12 @@ Pkt_KCtx pkt_kctx; Pkt_ACtx pkt_actx; struct X11Display *x11disp; + struct X11FakeAuth *x11auth; + tree234 *x11authtree; int version; int conn_throttle_count; int overall_bufsize; int throttled_all; @@ -874,39 +831,53 @@ int v1_stdout_throttling; unsigned long v2_outgoing_sequence; int ssh1_rdpkt_crstate; int ssh2_rdpkt_crstate; - int do_ssh_init_crstate; + int ssh2_bare_rdpkt_crstate; int ssh_gotdata_crstate; - int do_ssh1_login_crstate; int do_ssh1_connection_crstate; - int do_ssh2_transport_crstate; - int do_ssh2_authconn_crstate; void *do_ssh_init_state; void *do_ssh1_login_state; void *do_ssh2_transport_state; void *do_ssh2_authconn_state; + void *do_ssh_connection_init_state; struct rdpkt1_state_tag rdpkt1_state; struct rdpkt2_state_tag rdpkt2_state; + struct rdpkt2_bare_state_tag rdpkt2_bare_state; /* SSH-1 and SSH-2 use this for different things, but both use it */ int protocol_initial_phase_done; void (*protocol) (Ssh ssh, void *vin, int inlen, struct Packet *pkt); struct Packet *(*s_rdpkt) (Ssh ssh, unsigned char **data, int *datalen); + int (*do_ssh_init)(Ssh ssh, unsigned char c); /* - * We maintain a full _copy_ of a Config structure here, not - * merely a pointer to it. That way, when we're passed a new - * one for reconfiguration, we can check the differences and - * potentially reconfigure port forwardings etc in mid-session. + * We maintain our own copy of a Conf structure here. That way, + * when we're passed a new one for reconfiguration, we can check + * the differences and potentially reconfigure port forwardings + * etc in mid-session. */ - Config cfg; + Conf *conf; + + /* + * Values cached out of conf so as to avoid the tree234 lookup + * cost every time they're used. + */ + int logomitdata; + + /* + * Dynamically allocated username string created during SSH + * login. Stored in here rather than in the coroutine state so + * that it'll be reliably freed if we shut down the SSH session + * at some unexpected moment. + */ + char *username; /* * Used to transfer data back from async callbacks. */ void *agent_response; @@ -934,10 +905,11 @@ /* * Queues of one-off handler functions for success/failure * indications from a request. */ struct queued_handler *qhead, *qtail; + handler_fn_t q_saved_handler1, q_saved_handler2; /* * This module deals with sending keepalives. */ Pinger pinger; @@ -947,11 +919,11 @@ * size-based rekeys. */ unsigned long incoming_data_size, outgoing_data_size, deferred_data_size; unsigned long max_data_size; int kex_in_progress; - long next_rekey, last_rekey; + unsigned long next_rekey, last_rekey; char *deferred_rekey_reason; /* points to STATIC string; don't free */ /* * Fully qualified host name, which we need if doing GSSAPI. */ @@ -978,58 +950,43 @@ va_end(ap); logevent(buf); sfree(buf); } -#define bombout(msg) \ - do { \ - char *text = dupprintf msg; \ - ssh_do_close(ssh, FALSE); \ - logevent(text); \ - connection_fatal(ssh->frontend, "%s", text); \ - sfree(text); \ - } while (0) - -/* Functions to leave bits out of the SSH packet log file. */ - -static void dont_log_password(Ssh ssh, struct Packet *pkt, int blanktype) -{ - if (ssh->cfg.logomitpass) - pkt->logmode = blanktype; -} - -static void dont_log_data(Ssh ssh, struct Packet *pkt, int blanktype) -{ - if (ssh->cfg.logomitdata) - pkt->logmode = blanktype; -} - -static void end_log_omission(Ssh ssh, struct Packet *pkt) -{ - pkt->logmode = PKTLOG_EMIT; -} - -/* Helper function for common bits of parsing cfg.ttymodes. */ -static void parse_ttymodes(Ssh ssh, char *modes, +static void bomb_out(Ssh ssh, char *text) +{ + ssh_do_close(ssh, FALSE); + logevent(text); + connection_fatal(ssh->frontend, "%s", text); + sfree(text); +} + +#define bombout(msg) bomb_out(ssh, dupprintf msg) + +/* Helper function for common bits of parsing ttymodes. */ +static void parse_ttymodes(Ssh ssh, void (*do_mode)(void *data, char *mode, char *val), void *data) { - while (*modes) { - char *t = strchr(modes, '\t'); - char *m = snewn(t-modes+1, char); - char *val; - strncpy(m, modes, t-modes); - m[t-modes] = '\0'; - if (*(t+1) == 'A') - val = get_ttymode(ssh->frontend, m); - else - val = dupstr(t+2); - if (val) - do_mode(data, m, val); - sfree(m); - sfree(val); - modes += strlen(modes) + 1; + char *key, *val; + + for (val = conf_get_str_strs(ssh->conf, CONF_ttymodes, NULL, &key); + val != NULL; + val = conf_get_str_strs(ssh->conf, CONF_ttymodes, key, &key)) { + /* + * val[0] is either 'V', indicating that an explicit value + * follows it, or 'A' indicating that we should pass the + * value through from the local environment via get_ttymode. + */ + if (val[0] == 'A') { + val = get_ttymode(ssh->frontend, key); + if (val) { + do_mode(data, key, val); + sfree(val); + } + } else + do_mode(data, key, val + 1); /* skip the 'V' */ } } static int ssh_channelcmp(void *av, void *bv) { @@ -1068,11 +1025,13 @@ static int ssh_rportcmp_ssh2(void *av, void *bv) { struct ssh_rportfwd *a = (struct ssh_rportfwd *) av; struct ssh_rportfwd *b = (struct ssh_rportfwd *) bv; - + int i; + if ( (i = strcmp(a->shost, b->shost)) != 0) + return i < 0 ? -1 : +1; if (a->sport > b->sport) return +1; if (a->sport < b->sport) return -1; return 0; @@ -1199,16 +1158,122 @@ { struct Packet *pkt = snew(struct Packet); pkt->body = pkt->data = NULL; pkt->maxlen = 0; - pkt->logmode = PKTLOG_EMIT; - pkt->nblanks = 0; - pkt->blanks = NULL; return pkt; } + +static void ssh1_log_incoming_packet(Ssh ssh, struct Packet *pkt) +{ + int nblanks = 0; + struct logblank_t blanks[4]; + char *str; + int slen; + + pkt->savedpos = 0; + + if (ssh->logomitdata && + (pkt->type == SSH1_SMSG_STDOUT_DATA || + pkt->type == SSH1_SMSG_STDERR_DATA || + pkt->type == SSH1_MSG_CHANNEL_DATA)) { + /* "Session data" packets - omit the data string. */ + if (pkt->type == SSH1_MSG_CHANNEL_DATA) + ssh_pkt_getuint32(pkt); /* skip channel id */ + blanks[nblanks].offset = pkt->savedpos + 4; + blanks[nblanks].type = PKTLOG_OMIT; + ssh_pkt_getstring(pkt, &str, &slen); + if (str) { + blanks[nblanks].len = slen; + nblanks++; + } + } + log_packet(ssh->logctx, PKT_INCOMING, pkt->type, + ssh1_pkt_type(pkt->type), + pkt->body, pkt->length, nblanks, blanks, NULL, + 0, NULL); +} + +static void ssh1_log_outgoing_packet(Ssh ssh, struct Packet *pkt) +{ + int nblanks = 0; + struct logblank_t blanks[4]; + char *str; + int slen; + + /* + * For outgoing packets, pkt->length represents the length of the + * whole packet starting at pkt->data (including some header), and + * pkt->body refers to the point within that where the log-worthy + * payload begins. However, incoming packets expect pkt->length to + * represent only the payload length (that is, it's measured from + * pkt->body not from pkt->data). Temporarily adjust our outgoing + * packet to conform to the incoming-packet semantics, so that we + * can analyse it with the ssh_pkt_get functions. + */ + pkt->length -= (pkt->body - pkt->data); + pkt->savedpos = 0; + + if (ssh->logomitdata && + (pkt->type == SSH1_CMSG_STDIN_DATA || + pkt->type == SSH1_MSG_CHANNEL_DATA)) { + /* "Session data" packets - omit the data string. */ + if (pkt->type == SSH1_MSG_CHANNEL_DATA) + ssh_pkt_getuint32(pkt); /* skip channel id */ + blanks[nblanks].offset = pkt->savedpos + 4; + blanks[nblanks].type = PKTLOG_OMIT; + ssh_pkt_getstring(pkt, &str, &slen); + if (str) { + blanks[nblanks].len = slen; + nblanks++; + } + } + + if ((pkt->type == SSH1_CMSG_AUTH_PASSWORD || + pkt->type == SSH1_CMSG_AUTH_TIS_RESPONSE || + pkt->type == SSH1_CMSG_AUTH_CCARD_RESPONSE) && + conf_get_int(ssh->conf, CONF_logomitpass)) { + /* If this is a password or similar packet, blank the password(s). */ + blanks[nblanks].offset = 0; + blanks[nblanks].len = pkt->length; + blanks[nblanks].type = PKTLOG_BLANK; + nblanks++; + } else if (pkt->type == SSH1_CMSG_X11_REQUEST_FORWARDING && + conf_get_int(ssh->conf, CONF_logomitpass)) { + /* + * If this is an X forwarding request packet, blank the fake + * auth data. + * + * Note that while we blank the X authentication data here, we + * don't take any special action to blank the start of an X11 + * channel, so using MIT-MAGIC-COOKIE-1 and actually opening + * an X connection without having session blanking enabled is + * likely to leak your cookie into the log. + */ + pkt->savedpos = 0; + ssh_pkt_getstring(pkt, &str, &slen); + blanks[nblanks].offset = pkt->savedpos; + blanks[nblanks].type = PKTLOG_BLANK; + ssh_pkt_getstring(pkt, &str, &slen); + if (str) { + blanks[nblanks].len = pkt->savedpos - blanks[nblanks].offset; + nblanks++; + } + } + + log_packet(ssh->logctx, PKT_OUTGOING, pkt->data[12], + ssh1_pkt_type(pkt->data[12]), + pkt->body, pkt->length, + nblanks, blanks, NULL, 0, NULL); + + /* + * Undo the above adjustment of pkt->length, to put the packet + * back in the state we found it. + */ + pkt->length += (pkt->body - pkt->data); +} /* * Collect incoming data in the incoming packet buffer. * Decipher and verify the packet when it is completely read. * Drop SSH1_MSG_DEBUG and SSH1_MSG_IGNORE packets. @@ -1279,11 +1344,10 @@ ssh_free_packet(st->pktin); crStop(NULL); } st->pktin->body = st->pktin->data + st->pad + 1; - st->pktin->savedpos = 0; if (ssh->v1_compressing) { unsigned char *decompblk; int decomplen; if (!zlib_decompress_block(ssh->sc_comp_ctx, @@ -1308,40 +1372,170 @@ } st->pktin->type = st->pktin->body[-1]; /* - * Log incoming packet, possibly omitting sensitive fields. + * Now pktin->body and pktin->length identify the semantic content + * of the packet, excluding the initial type byte. */ - if (ssh->logctx) { - int nblanks = 0; - struct logblank_t blank; - if (ssh->cfg.logomitdata) { - int do_blank = FALSE, blank_prefix = 0; - /* "Session data" packets - omit the data field */ - if ((st->pktin->type == SSH1_SMSG_STDOUT_DATA) || - (st->pktin->type == SSH1_SMSG_STDERR_DATA)) { - do_blank = TRUE; blank_prefix = 4; - } else if (st->pktin->type == SSH1_MSG_CHANNEL_DATA) { - do_blank = TRUE; blank_prefix = 8; - } - if (do_blank) { - blank.offset = blank_prefix; - blank.len = st->pktin->length; - blank.type = PKTLOG_OMIT; - nblanks = 1; - } - } - log_packet(ssh->logctx, - PKT_INCOMING, st->pktin->type, - ssh1_pkt_type(st->pktin->type), - st->pktin->body, st->pktin->length, - nblanks, &blank, NULL); - } + + if (ssh->logctx) + ssh1_log_incoming_packet(ssh, st->pktin); + + st->pktin->savedpos = 0; crFinish(st->pktin); } + +static void ssh2_log_incoming_packet(Ssh ssh, struct Packet *pkt) +{ + int nblanks = 0; + struct logblank_t blanks[4]; + char *str; + int slen; + + pkt->savedpos = 0; + + if (ssh->logomitdata && + (pkt->type == SSH2_MSG_CHANNEL_DATA || + pkt->type == SSH2_MSG_CHANNEL_EXTENDED_DATA)) { + /* "Session data" packets - omit the data string. */ + ssh_pkt_getuint32(pkt); /* skip channel id */ + if (pkt->type == SSH2_MSG_CHANNEL_EXTENDED_DATA) + ssh_pkt_getuint32(pkt); /* skip extended data type */ + blanks[nblanks].offset = pkt->savedpos + 4; + blanks[nblanks].type = PKTLOG_OMIT; + ssh_pkt_getstring(pkt, &str, &slen); + if (str) { + blanks[nblanks].len = slen; + nblanks++; + } + } + + log_packet(ssh->logctx, PKT_INCOMING, pkt->type, + ssh2_pkt_type(ssh->pkt_kctx, ssh->pkt_actx, pkt->type), + pkt->body, pkt->length, nblanks, blanks, &pkt->sequence, + 0, NULL); +} + +static void ssh2_log_outgoing_packet(Ssh ssh, struct Packet *pkt) +{ + int nblanks = 0; + struct logblank_t blanks[4]; + char *str; + int slen; + + /* + * For outgoing packets, pkt->length represents the length of the + * whole packet starting at pkt->data (including some header), and + * pkt->body refers to the point within that where the log-worthy + * payload begins. However, incoming packets expect pkt->length to + * represent only the payload length (that is, it's measured from + * pkt->body not from pkt->data). Temporarily adjust our outgoing + * packet to conform to the incoming-packet semantics, so that we + * can analyse it with the ssh_pkt_get functions. + */ + pkt->length -= (pkt->body - pkt->data); + pkt->savedpos = 0; + + if (ssh->logomitdata && + (pkt->type == SSH2_MSG_CHANNEL_DATA || + pkt->type == SSH2_MSG_CHANNEL_EXTENDED_DATA)) { + /* "Session data" packets - omit the data string. */ + ssh_pkt_getuint32(pkt); /* skip channel id */ + if (pkt->type == SSH2_MSG_CHANNEL_EXTENDED_DATA) + ssh_pkt_getuint32(pkt); /* skip extended data type */ + blanks[nblanks].offset = pkt->savedpos + 4; + blanks[nblanks].type = PKTLOG_OMIT; + ssh_pkt_getstring(pkt, &str, &slen); + if (str) { + blanks[nblanks].len = slen; + nblanks++; + } + } + + if (pkt->type == SSH2_MSG_USERAUTH_REQUEST && + conf_get_int(ssh->conf, CONF_logomitpass)) { + /* If this is a password packet, blank the password(s). */ + pkt->savedpos = 0; + ssh_pkt_getstring(pkt, &str, &slen); + ssh_pkt_getstring(pkt, &str, &slen); + ssh_pkt_getstring(pkt, &str, &slen); + if (slen == 8 && !memcmp(str, "password", 8)) { + ssh2_pkt_getbool(pkt); + /* Blank the password field. */ + blanks[nblanks].offset = pkt->savedpos; + blanks[nblanks].type = PKTLOG_BLANK; + ssh_pkt_getstring(pkt, &str, &slen); + if (str) { + blanks[nblanks].len = pkt->savedpos - blanks[nblanks].offset; + nblanks++; + /* If there's another password field beyond it (change of + * password), blank that too. */ + ssh_pkt_getstring(pkt, &str, &slen); + if (str) + blanks[nblanks-1].len = + pkt->savedpos - blanks[nblanks].offset; + } + } + } else if (ssh->pkt_actx == SSH2_PKTCTX_KBDINTER && + pkt->type == SSH2_MSG_USERAUTH_INFO_RESPONSE && + conf_get_int(ssh->conf, CONF_logomitpass)) { + /* If this is a keyboard-interactive response packet, blank + * the responses. */ + pkt->savedpos = 0; + ssh_pkt_getuint32(pkt); + blanks[nblanks].offset = pkt->savedpos; + blanks[nblanks].type = PKTLOG_BLANK; + while (1) { + ssh_pkt_getstring(pkt, &str, &slen); + if (!str) + break; + } + blanks[nblanks].len = pkt->savedpos - blanks[nblanks].offset; + nblanks++; + } else if (pkt->type == SSH2_MSG_CHANNEL_REQUEST && + conf_get_int(ssh->conf, CONF_logomitpass)) { + /* + * If this is an X forwarding request packet, blank the fake + * auth data. + * + * Note that while we blank the X authentication data here, we + * don't take any special action to blank the start of an X11 + * channel, so using MIT-MAGIC-COOKIE-1 and actually opening + * an X connection without having session blanking enabled is + * likely to leak your cookie into the log. + */ + pkt->savedpos = 0; + ssh_pkt_getuint32(pkt); + ssh_pkt_getstring(pkt, &str, &slen); + if (slen == 7 && !memcmp(str, "x11-req", 0)) { + ssh2_pkt_getbool(pkt); + ssh2_pkt_getbool(pkt); + ssh_pkt_getstring(pkt, &str, &slen); + blanks[nblanks].offset = pkt->savedpos; + blanks[nblanks].type = PKTLOG_BLANK; + ssh_pkt_getstring(pkt, &str, &slen); + if (str) { + blanks[nblanks].len = pkt->savedpos - blanks[nblanks].offset; + nblanks++; + } + } + } + + log_packet(ssh->logctx, PKT_OUTGOING, pkt->data[5], + ssh2_pkt_type(ssh->pkt_kctx, ssh->pkt_actx, pkt->data[5]), + pkt->body, pkt->length, nblanks, blanks, + &ssh->v2_outgoing_sequence, + pkt->downstream_id, pkt->additional_log_text); + + /* + * Undo the above adjustment of pkt->length, to put the packet + * back in the state we found it. + */ + pkt->length += (pkt->body - pkt->data); +} static struct Packet *ssh2_rdpkt(Ssh ssh, unsigned char **data, int *datalen) { struct rdpkt2_state_tag *st = &ssh->rdpkt2_state; @@ -1411,11 +1605,12 @@ st->pktin->data + st->packetlen, st->cipherblk); st->packetlen += st->cipherblk; /* See if that gives us a valid packet. */ if (ssh->scmac->verresult(ssh->sc_mac_ctx, st->pktin->data + st->packetlen) && - (st->len = GET_32BIT(st->pktin->data)) + 4 == st->packetlen) + ((st->len = toint(GET_32BIT(st->pktin->data))) == + st->packetlen-4)) break; if (st->packetlen >= OUR_V2_PACKETLIMIT) { bombout(("No valid incoming packet found")); ssh_free_packet(st->pktin); crStop(NULL); @@ -1444,11 +1639,11 @@ st->pktin->data, st->cipherblk); /* * Now get the length figure. */ - st->len = GET_32BIT(st->pktin->data); + st->len = toint(GET_32BIT(st->pktin->data)); /* * _Completely_ silly lengths should be stomped on before they * do us any more damage. */ @@ -1536,41 +1731,82 @@ memcpy(st->pktin->data + 5, newpayload, newlen); sfree(newpayload); } } - st->pktin->savedpos = 6; - st->pktin->body = st->pktin->data; + /* + * pktin->body and pktin->length should identify the semantic + * content of the packet, excluding the initial type byte. + */ st->pktin->type = st->pktin->data[5]; + st->pktin->body = st->pktin->data + 6; + st->pktin->length = st->packetlen - 6 - st->pad; + assert(st->pktin->length >= 0); /* one last double-check */ + + if (ssh->logctx) + ssh2_log_incoming_packet(ssh, st->pktin); + + st->pktin->savedpos = 0; + + crFinish(st->pktin); +} + +static struct Packet *ssh2_bare_connection_rdpkt(Ssh ssh, unsigned char **data, + int *datalen) +{ + struct rdpkt2_bare_state_tag *st = &ssh->rdpkt2_bare_state; + + crBegin(ssh->ssh2_bare_rdpkt_crstate); + + /* + * Read the packet length field. + */ + for (st->i = 0; st->i < 4; st->i++) { + while ((*datalen) == 0) + crReturn(NULL); + st->length[st->i] = *(*data)++; + (*datalen)--; + } + + st->packetlen = toint(GET_32BIT_MSB_FIRST(st->length)); + if (st->packetlen <= 0 || st->packetlen >= OUR_V2_PACKETLIMIT) { + bombout(("Invalid packet length received")); + crStop(NULL); + } + + st->pktin = ssh_new_packet(); + st->pktin->data = snewn(st->packetlen, unsigned char); + + st->pktin->encrypted_len = st->packetlen; + + st->pktin->sequence = st->incoming_sequence++; + + /* + * Read the remainder of the packet. + */ + for (st->i = 0; st->i < st->packetlen; st->i++) { + while ((*datalen) == 0) + crReturn(NULL); + st->pktin->data[st->i] = *(*data)++; + (*datalen)--; + } + + /* + * pktin->body and pktin->length should identify the semantic + * content of the packet, excluding the initial type byte. + */ + st->pktin->type = st->pktin->data[0]; + st->pktin->body = st->pktin->data + 1; + st->pktin->length = st->packetlen - 1; /* * Log incoming packet, possibly omitting sensitive fields. */ - if (ssh->logctx) { - int nblanks = 0; - struct logblank_t blank; - if (ssh->cfg.logomitdata) { - int do_blank = FALSE, blank_prefix = 0; - /* "Session data" packets - omit the data field */ - if (st->pktin->type == SSH2_MSG_CHANNEL_DATA) { - do_blank = TRUE; blank_prefix = 8; - } else if (st->pktin->type == SSH2_MSG_CHANNEL_EXTENDED_DATA) { - do_blank = TRUE; blank_prefix = 12; - } - if (do_blank) { - blank.offset = blank_prefix; - blank.len = (st->pktin->length-6) - blank_prefix; - blank.type = PKTLOG_OMIT; - nblanks = 1; - } - } - log_packet(ssh->logctx, PKT_INCOMING, st->pktin->type, - ssh2_pkt_type(ssh->pkt_kctx, ssh->pkt_actx, - st->pktin->type), - st->pktin->data+6, st->pktin->length-6, - nblanks, &blank, &st->pktin->sequence); - } + if (ssh->logctx) + ssh2_log_incoming_packet(ssh, st->pktin); + + st->pktin->savedpos = 0; crFinish(st->pktin); } static int s_wrpkt_prepare(Ssh ssh, struct Packet *pkt, int *offset_p) @@ -1587,16 +1823,11 @@ volatile #endif int len; if (ssh->logctx) - log_packet(ssh->logctx, PKT_OUTGOING, pkt->data[12], - ssh1_pkt_type(pkt->data[12]), - pkt->body, pkt->length - (pkt->body - pkt->data), - pkt->nblanks, pkt->blanks, NULL); - sfree(pkt->blanks); pkt->blanks = NULL; - pkt->nblanks = 0; + ssh1_log_outgoing_packet(ssh, pkt); if (ssh->v1_compressing) { unsigned char *compblk; int complen; zlib_compress_block(ssh->cs_comp_ctx, @@ -1631,11 +1862,13 @@ static int s_write(Ssh ssh, void *data, int len) { if (ssh->logctx) log_packet(ssh->logctx, PKT_OUTGOING, -1, NULL, data, len, - 0, NULL, NULL); + 0, NULL, NULL, 0, NULL); + if (!ssh->s) + return 0; return sk_write(ssh->s, (char *)data, len); } static void s_wrpkt(Ssh ssh, struct Packet *pkt) { @@ -1702,20 +1935,10 @@ break; case PKT_BIGNUM: bn = va_arg(ap, Bignum); ssh1_pkt_addmp(pkt, bn); break; - /* Tokens for modifications to packet logging */ - case PKTT_PASSWORD: - dont_log_password(ssh, pkt, PKTLOG_BLANK); - break; - case PKTT_DATA: - dont_log_data(ssh, pkt, PKTLOG_OMIT); - break; - case PKTT_OTHER: - end_log_omission(ssh, pkt); - break; } } return pkt; } @@ -1790,21 +2013,12 @@ pkt->maxlen = length + 256; pkt->data = sresize(pkt->data, pkt->maxlen + APIEXTRA, unsigned char); if (body) pkt->body = pkt->data + offset; } } -static void ssh_pkt_adddata(struct Packet *pkt, void *data, int len) -{ - if (pkt->logmode != PKTLOG_EMIT) { - pkt->nblanks++; - pkt->blanks = sresize(pkt->blanks, pkt->nblanks, struct logblank_t); - assert(pkt->body); - pkt->blanks[pkt->nblanks-1].offset = pkt->length - - (pkt->body - pkt->data); - pkt->blanks[pkt->nblanks-1].len = len; - pkt->blanks[pkt->nblanks-1].type = pkt->logmode; - } +static void ssh_pkt_adddata(struct Packet *pkt, const void *data, int len) +{ pkt->length += len; ssh_pkt_ensure(pkt, pkt->length); memcpy(pkt->data + pkt->length - len, data, len); } static void ssh_pkt_addbyte(struct Packet *pkt, unsigned char byte) @@ -1824,21 +2038,22 @@ static void ssh_pkt_addstring_start(struct Packet *pkt) { ssh_pkt_adduint32(pkt, 0); pkt->savedpos = pkt->length; } -static void ssh_pkt_addstring_str(struct Packet *pkt, char *data) +static void ssh_pkt_addstring_str(struct Packet *pkt, const char *data) { ssh_pkt_adddata(pkt, data, strlen(data)); PUT_32BIT(pkt->data + pkt->savedpos - 4, pkt->length - pkt->savedpos); } -static void ssh_pkt_addstring_data(struct Packet *pkt, char *data, int len) +static void ssh_pkt_addstring_data(struct Packet *pkt, const char *data, + int len) { ssh_pkt_adddata(pkt, data, len); PUT_32BIT(pkt->data + pkt->savedpos - 4, pkt->length - pkt->savedpos); } -static void ssh_pkt_addstring(struct Packet *pkt, char *data) +static void ssh_pkt_addstring(struct Packet *pkt, const char *data) { ssh_pkt_addstring_start(pkt); ssh_pkt_addstring_str(pkt, data); } static void ssh1_pkt_addmp(struct Packet *pkt, Bignum b) @@ -1878,10 +2093,13 @@ { struct Packet *pkt = ssh_new_packet(); pkt->length = 4 + 8; /* space for length + max padding */ ssh_pkt_addbyte(pkt, pkt_type); pkt->body = pkt->data + pkt->length; + pkt->type = pkt_type; + pkt->downstream_id = 0; + pkt->additional_log_text = NULL; return pkt; } /* For legacy code (SSH-1 and -2 packet construction used to be separate) */ #define ssh2_pkt_ensure(pkt, length) ssh_pkt_ensure(pkt, length) @@ -1896,12 +2114,15 @@ static struct Packet *ssh2_pkt_init(int pkt_type) { struct Packet *pkt = ssh_new_packet(); pkt->length = 5; /* space for packet length + padding length */ pkt->forcepad = 0; + pkt->type = pkt_type; ssh_pkt_addbyte(pkt, (unsigned char) pkt_type); pkt->body = pkt->data + pkt->length; /* after packet type */ + pkt->downstream_id = 0; + pkt->additional_log_text = NULL; return pkt; } /* * Construct an SSH-2 final-form packet: compress it, encrypt it, @@ -1911,16 +2132,22 @@ static int ssh2_pkt_construct(Ssh ssh, struct Packet *pkt) { int cipherblk, maclen, padding, i; if (ssh->logctx) - log_packet(ssh->logctx, PKT_OUTGOING, pkt->data[5], - ssh2_pkt_type(ssh->pkt_kctx, ssh->pkt_actx, pkt->data[5]), - pkt->body, pkt->length - (pkt->body - pkt->data), - pkt->nblanks, pkt->blanks, &ssh->v2_outgoing_sequence); - sfree(pkt->blanks); pkt->blanks = NULL; - pkt->nblanks = 0; + ssh2_log_outgoing_packet(ssh, pkt); + + if (ssh->bare_connection) { + /* + * Trivial packet construction for the bare connection + * protocol. + */ + PUT_32BIT(pkt->data + 1, pkt->length - 5); + pkt->body = pkt->data + 1; + ssh->v2_outgoing_sequence++; /* only for diagnostics, really */ + return pkt->length - 1; + } /* * Compress packet payload. */ { @@ -1967,10 +2194,11 @@ pkt->data, pkt->length + padding); pkt->encrypted_len = pkt->length + padding; /* Ready-to-send packet starts at pkt->data. We return length. */ + pkt->body = pkt->data; return pkt->length + padding + maclen; } /* * Routines called from the main SSH code to send packets. There @@ -2024,16 +2252,17 @@ ssh2_pkt_defer_noqueue(ssh, pkt, FALSE); ssh_pkt_defersend(ssh); return; } len = ssh2_pkt_construct(ssh, pkt); - backlog = s_write(ssh, pkt->data, len); + backlog = s_write(ssh, pkt->body, len); if (backlog > SSH_MAX_BACKLOG) ssh_throttle_all(ssh, 1, backlog); ssh->outgoing_data_size += pkt->encrypted_len; if (!ssh->kex_in_progress && + !ssh->bare_connection && ssh->max_data_size != 0 && ssh->outgoing_data_size > ssh->max_data_size) do_ssh2_transport(ssh, "too much data sent", -1, NULL); ssh_free_packet(pkt); @@ -2061,11 +2290,11 @@ ssh->deferred_size = ssh->deferred_len + len + 128; ssh->deferred_send_data = sresize(ssh->deferred_send_data, ssh->deferred_size, unsigned char); } - memcpy(ssh->deferred_send_data + ssh->deferred_len, pkt->data, len); + memcpy(ssh->deferred_send_data + ssh->deferred_len, pkt->body, len); ssh->deferred_len += len; ssh->deferred_data_size += pkt->encrypted_len; ssh_free_packet(pkt); } @@ -2131,10 +2360,11 @@ if (backlog > SSH_MAX_BACKLOG) ssh_throttle_all(ssh, 1, backlog); ssh->outgoing_data_size += ssh->deferred_data_size; if (!ssh->kex_in_progress && + !ssh->bare_connection && ssh->max_data_size != 0 && ssh->outgoing_data_size > ssh->max_data_size) do_ssh2_transport(ssh, "too much data sent", -1, NULL); ssh->deferred_data_size = 0; } @@ -2275,11 +2505,11 @@ int len; *p = NULL; *length = 0; if (pkt->length - pkt->savedpos < 4) return; - len = GET_32BIT(pkt->body + pkt->savedpos); + len = toint(GET_32BIT(pkt->body + pkt->savedpos)); if (len < 0) return; *length = len; pkt->savedpos += 4; if (pkt->length - pkt->savedpos < *length) @@ -2359,31 +2589,42 @@ /* * See if this is in fact an ssh-rsa signature and a buggy * server; otherwise we can just do this the easy way. */ - if ((ssh->remote_bugs & BUG_SSH2_RSA_PADDING) && + if ((ssh->remote_bugs & BUG_SSH2_RSA_PADDING) && pkblob_len > 4+7+4 && (GET_32BIT(pkblob) == 7 && !memcmp(pkblob+4, "ssh-rsa", 7))) { int pos, len, siglen; /* * Find the byte length of the modulus. */ pos = 4+7; /* skip over "ssh-rsa" */ - pos += 4 + GET_32BIT(pkblob+pos); /* skip over exponent */ - len = GET_32BIT(pkblob+pos); /* find length of modulus */ + len = toint(GET_32BIT(pkblob+pos)); /* get length of exponent */ + if (len < 0 || len > pkblob_len - pos - 4) + goto give_up; + pos += 4 + len; /* skip over exponent */ + if (pkblob_len - pos < 4) + goto give_up; + len = toint(GET_32BIT(pkblob+pos)); /* find length of modulus */ + if (len < 0 || len > pkblob_len - pos - 4) + goto give_up; pos += 4; /* find modulus itself */ while (len > 0 && pkblob[pos] == 0) len--, pos++; /* debug(("modulus length is %d\n", len)); */ /* * Now find the signature integer. */ pos = 4+7; /* skip over "ssh-rsa" */ - siglen = GET_32BIT(sigblob+pos); + if (sigblob_len < pos+4) + goto give_up; + siglen = toint(GET_32BIT(sigblob+pos)); + if (siglen != sigblob_len - pos - 4) + goto give_up; /* debug(("signature length is %d\n", siglen)); */ if (len != siglen) { unsigned char newlen[4]; ssh2_pkt_addstring_start(pkt); @@ -2401,11 +2642,14 @@ ssh2_pkt_addstring_data(pkt, (char *)(sigblob+pos), siglen); /* dmemdump(sigblob+pos, siglen); */ return; } - /* Otherwise fall through and do it the easy way. */ + /* Otherwise fall through and do it the easy way. We also come + * here as a fallback if we discover above that the key blob + * is misformatted in some way. */ + give_up:; } ssh2_pkt_addstring_start(pkt); ssh2_pkt_addstring_data(pkt, (char *)sigblob, sigblob_len); } @@ -2430,12 +2674,12 @@ * - Not all servers reporting "Cisco-1.25" have all the bugs listed * here -- in particular, we've heard of one that's perfectly happy * with SSH1_MSG_IGNOREs -- but this string never seems to change, * so we can't distinguish them. */ - if (ssh->cfg.sshbug_ignore1 == FORCE_ON || - (ssh->cfg.sshbug_ignore1 == AUTO && + if (conf_get_int(ssh->conf, CONF_sshbug_ignore1) == FORCE_ON || + (conf_get_int(ssh->conf, CONF_sshbug_ignore1) == AUTO && (!strcmp(imp, "1.2.18") || !strcmp(imp, "1.2.19") || !strcmp(imp, "1.2.20") || !strcmp(imp, "1.2.21") || !strcmp(imp, "1.2.22") || !strcmp(imp, "Cisco-1.25") || !strcmp(imp, "OSU_1.4alpha3") || !strcmp(imp, "OSU_1.5alpha4")))) { /* @@ -2445,12 +2689,12 @@ */ ssh->remote_bugs |= BUG_CHOKES_ON_SSH1_IGNORE; logevent("We believe remote version has SSH-1 ignore bug"); } - if (ssh->cfg.sshbug_plainpw1 == FORCE_ON || - (ssh->cfg.sshbug_plainpw1 == AUTO && + if (conf_get_int(ssh->conf, CONF_sshbug_plainpw1) == FORCE_ON || + (conf_get_int(ssh->conf, CONF_sshbug_plainpw1) == AUTO && (!strcmp(imp, "Cisco-1.25") || !strcmp(imp, "OSU_1.4alpha3")))) { /* * These versions need a plain password sent; they can't * handle having a null and a random length of data after * the password. @@ -2457,12 +2701,12 @@ */ ssh->remote_bugs |= BUG_NEEDS_SSH1_PLAIN_PASSWORD; logevent("We believe remote version needs a plain SSH-1 password"); } - if (ssh->cfg.sshbug_rsa1 == FORCE_ON || - (ssh->cfg.sshbug_rsa1 == AUTO && + if (conf_get_int(ssh->conf, CONF_sshbug_rsa1) == FORCE_ON || + (conf_get_int(ssh->conf, CONF_sshbug_rsa1) == AUTO && (!strcmp(imp, "Cisco-1.25")))) { /* * These versions apparently have no clue whatever about * RSA authentication and will panic and die if they see * an AUTH_RSA message. @@ -2469,12 +2713,12 @@ */ ssh->remote_bugs |= BUG_CHOKES_ON_RSA; logevent("We believe remote version can't handle SSH-1 RSA authentication"); } - if (ssh->cfg.sshbug_hmac2 == FORCE_ON || - (ssh->cfg.sshbug_hmac2 == AUTO && + if (conf_get_int(ssh->conf, CONF_sshbug_hmac2) == FORCE_ON || + (conf_get_int(ssh->conf, CONF_sshbug_hmac2) == AUTO && !wc_match("* VShell", imp) && (wc_match("2.1.0*", imp) || wc_match("2.0.*", imp) || wc_match("2.2.0*", imp) || wc_match("2.3.0*", imp) || wc_match("2.1 *", imp)))) { /* @@ -2482,12 +2726,12 @@ */ ssh->remote_bugs |= BUG_SSH2_HMAC; logevent("We believe remote version has SSH-2 HMAC bug"); } - if (ssh->cfg.sshbug_derivekey2 == FORCE_ON || - (ssh->cfg.sshbug_derivekey2 == AUTO && + if (conf_get_int(ssh->conf, CONF_sshbug_derivekey2) == FORCE_ON || + (conf_get_int(ssh->conf, CONF_sshbug_derivekey2) == AUTO && !wc_match("* VShell", imp) && (wc_match("2.0.0*", imp) || wc_match("2.0.10*", imp) ))) { /* * These versions have the key-derivation bug (failing to * include the literal shared secret in the hashes that @@ -2495,34 +2739,34 @@ */ ssh->remote_bugs |= BUG_SSH2_DERIVEKEY; logevent("We believe remote version has SSH-2 key-derivation bug"); } - if (ssh->cfg.sshbug_rsapad2 == FORCE_ON || - (ssh->cfg.sshbug_rsapad2 == AUTO && + if (conf_get_int(ssh->conf, CONF_sshbug_rsapad2) == FORCE_ON || + (conf_get_int(ssh->conf, CONF_sshbug_rsapad2) == AUTO && (wc_match("OpenSSH_2.[5-9]*", imp) || wc_match("OpenSSH_3.[0-2]*", imp)))) { /* * These versions have the SSH-2 RSA padding bug. */ ssh->remote_bugs |= BUG_SSH2_RSA_PADDING; logevent("We believe remote version has SSH-2 RSA padding bug"); } - if (ssh->cfg.sshbug_pksessid2 == FORCE_ON || - (ssh->cfg.sshbug_pksessid2 == AUTO && + if (conf_get_int(ssh->conf, CONF_sshbug_pksessid2) == FORCE_ON || + (conf_get_int(ssh->conf, CONF_sshbug_pksessid2) == AUTO && wc_match("OpenSSH_2.[0-2]*", imp))) { /* * These versions have the SSH-2 session-ID bug in * public-key authentication. */ ssh->remote_bugs |= BUG_SSH2_PK_SESSIONID; logevent("We believe remote version has SSH-2 public-key-session-ID bug"); } - if (ssh->cfg.sshbug_rekey2 == FORCE_ON || - (ssh->cfg.sshbug_rekey2 == AUTO && + if (conf_get_int(ssh->conf, CONF_sshbug_rekey2) == FORCE_ON || + (conf_get_int(ssh->conf, CONF_sshbug_rekey2) == AUTO && (wc_match("DigiSSH_2.0", imp) || wc_match("OpenSSH_2.[0-4]*", imp) || wc_match("OpenSSH_2.5.[0-3]*", imp) || wc_match("Sun_SSH_1.0", imp) || wc_match("Sun_SSH_1.0.1", imp) || @@ -2533,42 +2777,47 @@ */ ssh->remote_bugs |= BUG_SSH2_REKEY; logevent("We believe remote version has SSH-2 rekey bug"); } - if (ssh->cfg.sshbug_maxpkt2 == FORCE_ON || - (ssh->cfg.sshbug_maxpkt2 == AUTO && + if (conf_get_int(ssh->conf, CONF_sshbug_maxpkt2) == FORCE_ON || + (conf_get_int(ssh->conf, CONF_sshbug_maxpkt2) == AUTO && (wc_match("1.36_sshlib GlobalSCAPE", imp) || wc_match("1.36 sshlib: GlobalScape", imp)))) { /* * This version ignores our makpkt and needs to be throttled. */ ssh->remote_bugs |= BUG_SSH2_MAXPKT; logevent("We believe remote version ignores SSH-2 maximum packet size"); } - if (ssh->cfg.sshbug_ignore2 == FORCE_ON) { + if (conf_get_int(ssh->conf, CONF_sshbug_ignore2) == FORCE_ON) { /* * Servers that don't support SSH2_MSG_IGNORE. Currently, * none detected automatically. */ ssh->remote_bugs |= BUG_CHOKES_ON_SSH2_IGNORE; logevent("We believe remote version has SSH-2 ignore bug"); } + + if (conf_get_int(ssh->conf, CONF_sshbug_winadj) == FORCE_ON) { + /* + * Servers that don't support our winadj request for one + * reason or another. Currently, none detected automatically. + */ + ssh->remote_bugs |= BUG_CHOKES_ON_WINADJ; + logevent("We believe remote version has winadj bug"); + } } /* * The `software version' part of an SSH version string is required * to contain no spaces or minus signs. */ static void ssh_fix_verstring(char *str) { - /* Eat "SSH--". */ - assert(*str == 'S'); str++; - assert(*str == 'S'); str++; - assert(*str == 'H'); str++; - assert(*str == '-'); str++; + /* Eat "-". */ while (*str && *str != '-') str++; assert(*str == '-'); str++; /* Convert minus signs and spaces in the remaining string into * underscores. */ @@ -2580,30 +2829,31 @@ } /* * Send an appropriate SSH version string. */ -static void ssh_send_verstring(Ssh ssh, char *svers) +static void ssh_send_verstring(Ssh ssh, const char *protoname, char *svers) { char *verstring; if (ssh->version == 2) { /* * Construct a v2 version string. */ - verstring = dupprintf("SSH-2.0-%s\015\012", sshver); + verstring = dupprintf("%s2.0-%s\015\012", protoname, sshver); } else { /* * Construct a v1 version string. */ + assert(!strcmp(protoname, "SSH-")); /* no v1 bare connection protocol */ verstring = dupprintf("SSH-%s-%s\012", (ssh_versioncmp(svers, "1.5") <= 0 ? svers : "1.5"), sshver); } - ssh_fix_verstring(verstring); + ssh_fix_verstring(verstring + strlen(protoname)); if (ssh->version == 2) { size_t len; /* * Record our version string. @@ -2620,45 +2870,45 @@ sfree(verstring); } static int do_ssh_init(Ssh ssh, unsigned char c) { + static const char protoname[] = "SSH-"; + struct do_ssh_init_state { + int crLine; int vslen; char version[10]; char *vstring; int vstrsize; int i; int proto1, proto2; }; crState(do_ssh_init_state); + + crBeginState; - crBegin(ssh->do_ssh_init_crstate); - - /* Search for a line beginning with the string "SSH-" in the input. */ + /* Search for a line beginning with the protocol name prefix in + * the input. */ for (;;) { - if (c != 'S') goto no; - crReturn(1); - if (c != 'S') goto no; - crReturn(1); - if (c != 'H') goto no; - crReturn(1); - if (c != '-') goto no; + for (s->i = 0; protoname[s->i]; s->i++) { + if ((char)c != protoname[s->i]) goto no; + crReturn(1); + } break; no: while (c != '\012') crReturn(1); crReturn(1); } - s->vstrsize = 16; + s->vstrsize = sizeof(protoname) + 16; s->vstring = snewn(s->vstrsize, char); - strcpy(s->vstring, "SSH-"); - s->vslen = 4; + strcpy(s->vstring, protoname); + s->vslen = strlen(protoname); s->i = 0; while (1) { - crReturn(1); /* get another char */ if (s->vslen >= s->vstrsize - 1) { s->vstrsize += 16; s->vstring = sresize(s->vstring, s->vstrsize, char); } s->vstring[s->vslen++] = c; @@ -2668,10 +2918,11 @@ s->i = -1; } else if (s->i < sizeof(s->version) - 1) s->version[s->i++] = c; } else if (c == '\012') break; + crReturn(1); /* get another char */ } ssh->agentfwd_enabled = FALSE; ssh->rdpkt2_state.incoming_sequence = 0; @@ -2687,29 +2938,29 @@ /* Anything strictly below "2.0" means protocol 1 is supported. */ s->proto1 = ssh_versioncmp(s->version, "2.0") < 0; /* Anything greater or equal to "1.99" means protocol 2 is supported. */ s->proto2 = ssh_versioncmp(s->version, "1.99") >= 0; - if (ssh->cfg.sshprot == 0 && !s->proto1) { + if (conf_get_int(ssh->conf, CONF_sshprot) == 0 && !s->proto1) { bombout(("SSH protocol version 1 required by user but not provided by server")); crStop(0); } - if (ssh->cfg.sshprot == 3 && !s->proto2) { + if (conf_get_int(ssh->conf, CONF_sshprot) == 3 && !s->proto2) { bombout(("SSH protocol version 2 required by user but not provided by server")); crStop(0); } - if (s->proto2 && (ssh->cfg.sshprot >= 2 || !s->proto1)) + if (s->proto2 && (conf_get_int(ssh->conf, CONF_sshprot) >= 2 || !s->proto1)) ssh->version = 2; else ssh->version = 1; logeventf(ssh, "Using SSH protocol version %d", ssh->version); /* Send the version string, if we haven't already */ - if (ssh->cfg.sshprot != 3) - ssh_send_verstring(ssh, s->version); + if (conf_get_int(ssh->conf, CONF_sshprot) != 3) + ssh_send_verstring(ssh, protoname, s->version); if (ssh->version == 2) { size_t len; /* * Record their version string. @@ -2736,11 +2987,122 @@ if (ssh->version == 2) do_ssh2_transport(ssh, NULL, -1, NULL); update_specials_menu(ssh->frontend); ssh->state = SSH_STATE_BEFORE_SIZE; - ssh->pinger = pinger_new(&ssh->cfg, &ssh_backend, ssh); + ssh->pinger = pinger_new(ssh->conf, &ssh_backend, ssh); + + sfree(s->vstring); + + crFinish(0); +} + +static int do_ssh_connection_init(Ssh ssh, unsigned char c) +{ + /* + * Ordinary SSH begins with the banner "SSH-x.y-...". This is just + * the ssh-connection part, extracted and given a trivial binary + * packet protocol, so we replace 'SSH-' at the start with a new + * name. In proper SSH style (though of course this part of the + * proper SSH protocol _isn't_ subject to this kind of + * DNS-domain-based extension), we define the new name in our + * extension space. + */ + static const char protoname[] = + "SSHCONNECTION@putty.projects.tartarus.org-"; + + struct do_ssh_connection_init_state { + int crLine; + int vslen; + char version[10]; + char *vstring; + int vstrsize; + int i; + }; + crState(do_ssh_connection_init_state); + + crBeginState; + + /* Search for a line beginning with the protocol name prefix in + * the input. */ + for (;;) { + for (s->i = 0; protoname[s->i]; s->i++) { + if ((char)c != protoname[s->i]) goto no; + crReturn(1); + } + break; + no: + while (c != '\012') + crReturn(1); + crReturn(1); + } + + s->vstrsize = sizeof(protoname) + 16; + s->vstring = snewn(s->vstrsize, char); + strcpy(s->vstring, protoname); + s->vslen = strlen(protoname); + s->i = 0; + while (1) { + if (s->vslen >= s->vstrsize - 1) { + s->vstrsize += 16; + s->vstring = sresize(s->vstring, s->vstrsize, char); + } + s->vstring[s->vslen++] = c; + if (s->i >= 0) { + if (c == '-') { + s->version[s->i] = '\0'; + s->i = -1; + } else if (s->i < sizeof(s->version) - 1) + s->version[s->i++] = c; + } else if (c == '\012') + break; + crReturn(1); /* get another char */ + } + + ssh->agentfwd_enabled = FALSE; + ssh->rdpkt2_bare_state.incoming_sequence = 0; + + s->vstring[s->vslen] = 0; + s->vstring[strcspn(s->vstring, "\015\012")] = '\0';/* remove EOL chars */ + logeventf(ssh, "Server version: %s", s->vstring); + ssh_detect_bugs(ssh, s->vstring); + + /* + * Decide which SSH protocol version to support. This is easy in + * bare ssh-connection mode: only 2.0 is legal. + */ + if (ssh_versioncmp(s->version, "2.0") < 0) { + bombout(("Server announces compatibility with SSH-1 in bare ssh-connection protocol")); + crStop(0); + } + if (conf_get_int(ssh->conf, CONF_sshprot) == 0) { + bombout(("Bare ssh-connection protocol cannot be run in SSH-1-only mode")); + crStop(0); + } + + ssh->version = 2; + + logeventf(ssh, "Using bare ssh-connection protocol"); + + /* Send the version string, if we haven't already */ + ssh_send_verstring(ssh, protoname, s->version); + + /* + * Initialise bare connection protocol. + */ + ssh->protocol = ssh2_bare_connection_protocol; + ssh2_bare_connection_protocol_setup(ssh); + ssh->s_rdpkt = ssh2_bare_connection_rdpkt; + + update_specials_menu(ssh->frontend); + ssh->state = SSH_STATE_BEFORE_SIZE; + ssh->pinger = pinger_new(ssh->conf, &ssh_backend, ssh); + + /* + * Get authconn (really just conn) under way. + */ + do_ssh2_authconn(ssh, NULL, 0, NULL); sfree(s->vstring); crFinish(0); } @@ -2794,11 +3156,11 @@ static void ssh_gotdata(Ssh ssh, unsigned char *data, int datalen) { /* Log raw data, if we're in that mode. */ if (ssh->logctx) log_packet(ssh->logctx, PKT_INCOMING, -1, NULL, data, datalen, - 0, NULL, NULL); + 0, NULL, NULL, 0, NULL); crBegin(ssh->ssh_gotdata_crstate); /* * To begin with, feed the characters one by one to the @@ -2808,11 +3170,11 @@ */ while (1) { int ret; /* need not be kept across crReturn */ if (datalen == 0) crReturnV; /* more data please */ - ret = do_ssh_init(ssh, *data); + ret = ssh->do_ssh_init(ssh, *data); data++; datalen--; if (ret == 0) break; } @@ -2872,15 +3234,15 @@ */ if (ssh->channels) { while (NULL != (c = index234(ssh->channels, 0))) { switch (c->type) { case CHAN_X11: - x11_close(c->u.x11.s); + x11_close(c->u.x11.xconn); break; case CHAN_SOCKDATA: case CHAN_SOCKDATA_DORMANT: - pfd_close(c->u.pfd.s); + pfd_close(c->u.pfd.pf); break; } del234(ssh->channels, c); /* moving next one to index 0 */ if (ssh->version == 2) bufchain_clear(&c->v.v2.outbuffer); @@ -2894,11 +3256,11 @@ if (ssh->portfwds) { struct ssh_portfwd *pf; while (NULL != (pf = index234(ssh->portfwds, 0))) { /* Dispose of any listening socket. */ if (pf->local) - pfd_terminate(pf->local); + pfl_terminate(pf->local); del234(ssh->portfwds, pf); /* moving next one to index 0 */ free_portfwd(pf); } freetree234(ssh->portfwds); ssh->portfwds = NULL; @@ -2905,25 +3267,75 @@ } return ret; } -static void ssh_log(Plug plug, int type, SockAddr addr, int port, - const char *error_msg, int error_code) +static void ssh_socket_log(Plug plug, int type, SockAddr addr, int port, + const char *error_msg, int error_code) { Ssh ssh = (Ssh) plug; char addrbuf[256], *msg; - sk_getaddr(addr, addrbuf, lenof(addrbuf)); - - if (type == 0) - msg = dupprintf("Connecting to %s port %d", addrbuf, port); - else - msg = dupprintf("Failed to connect to %s: %s", addrbuf, error_msg); - - logevent(msg); - sfree(msg); + if (ssh->attempting_connshare) { + /* + * While we're attempting connection sharing, don't loudly log + * everything that happens. Real TCP connections need to be + * logged when we _start_ trying to connect, because it might + * be ages before they respond if something goes wrong; but + * connection sharing is local and quick to respond, and it's + * sufficient to simply wait and see whether it worked + * afterwards. + */ + } else { + sk_getaddr(addr, addrbuf, lenof(addrbuf)); + + if (type == 0) { + if (sk_addr_needs_port(addr)) { + msg = dupprintf("Connecting to %s port %d", addrbuf, port); + } else { + msg = dupprintf("Connecting to %s", addrbuf); + } + } else { + msg = dupprintf("Failed to connect to %s: %s", addrbuf, error_msg); + } + + logevent(msg); + sfree(msg); + } +} + +void ssh_connshare_log(Ssh ssh, int event, const char *logtext, + const char *ds_err, const char *us_err) +{ + if (event == SHARE_NONE) { + /* In this case, 'logtext' is an error message indicating a + * reason why connection sharing couldn't be set up _at all_. + * Failing that, ds_err and us_err indicate why we couldn't be + * a downstream and an upstream respectively. */ + if (logtext) { + logeventf(ssh, "Could not set up connection sharing: %s", logtext); + } else { + if (ds_err) + logeventf(ssh, "Could not set up connection sharing" + " as downstream: %s", ds_err); + if (us_err) + logeventf(ssh, "Could not set up connection sharing" + " as upstream: %s", us_err); + } + } else if (event == SHARE_DOWNSTREAM) { + /* In this case, 'logtext' is a local endpoint address */ + logeventf(ssh, "Using existing shared connection at %s", logtext); + /* Also we should mention this in the console window to avoid + * confusing users as to why this window doesn't behave the + * usual way. */ + if ((flags & FLAG_VERBOSE) || (flags & FLAG_INTERACTIVE)) { + c_write_str(ssh,"Reusing a shared connection to this server.\r\n"); + } + } else if (event == SHARE_UPSTREAM) { + /* In this case, 'logtext' is a local endpoint address too */ + logeventf(ssh, "Sharing this connection at %s", logtext); + } } static int ssh_closing(Plug plug, const char *error_msg, int error_code, int calling_back) { @@ -2980,24 +3392,27 @@ */ static const char *connect_to_host(Ssh ssh, char *host, int port, char **realhost, int nodelay, int keepalive) { static const struct plug_function_table fn_table = { - ssh_log, + ssh_socket_log, ssh_closing, ssh_receive, ssh_sent, NULL }; SockAddr addr; const char *err; - - if (*ssh->cfg.loghost) { + char *loghost; + int addressfamily, sshprot; + + loghost = conf_get_str(ssh->conf, CONF_loghost); + if (*loghost) { char *colon; - ssh->savedhost = dupstr(ssh->cfg.loghost); + ssh->savedhost = dupstr(loghost); ssh->savedport = 22; /* default ssh port */ /* * A colon suffix on savedhost also lets us affect * savedport. @@ -3015,53 +3430,82 @@ if (port < 0) port = 22; /* default ssh port */ ssh->savedport = port; } - /* - * Try to find host. - */ - logeventf(ssh, "Looking up host \"%s\"%s", host, - (ssh->cfg.addressfamily == ADDRTYPE_IPV4 ? " (IPv4)" : - (ssh->cfg.addressfamily == ADDRTYPE_IPV6 ? " (IPv6)" : ""))); - addr = name_lookup(host, port, realhost, &ssh->cfg, - ssh->cfg.addressfamily); - if ((err = sk_addr_error(addr)) != NULL) { - sk_addr_free(addr); - return err; - } - ssh->fullhostname = dupstr(*realhost); /* save in case of GSSAPI */ - - /* - * Open socket. - */ - ssh->fn = &fn_table; - ssh->s = new_connection(addr, *realhost, port, - 0, 1, nodelay, keepalive, (Plug) ssh, &ssh->cfg); - if ((err = sk_socket_error(ssh->s)) != NULL) { - ssh->s = NULL; - notify_remote_exit(ssh->frontend); - return err; + ssh->fn = &fn_table; /* make 'ssh' usable as a Plug */ + + /* + * Try connection-sharing, in case that means we don't open a + * socket after all. ssh_connection_sharing_init will connect to a + * previously established upstream if it can, and failing that, + * establish a listening socket for _us_ to be the upstream. In + * the latter case it will return NULL just as if it had done + * nothing, because here we only need to care if we're a + * downstream and need to do our connection setup differently. + */ + ssh->connshare = NULL; + ssh->attempting_connshare = TRUE; /* affects socket logging behaviour */ + ssh->s = ssh_connection_sharing_init(ssh->savedhost, ssh->savedport, + ssh->conf, ssh, &ssh->connshare); + ssh->attempting_connshare = FALSE; + if (ssh->s != NULL) { + /* + * We are a downstream. + */ + ssh->bare_connection = TRUE; + ssh->do_ssh_init = do_ssh_connection_init; + ssh->fullhostname = NULL; + *realhost = dupstr(host); /* best we can do */ + } else { + /* + * We're not a downstream, so open a normal socket. + */ + ssh->do_ssh_init = do_ssh_init; + + /* + * Try to find host. + */ + addressfamily = conf_get_int(ssh->conf, CONF_addressfamily); + logeventf(ssh, "Looking up host \"%s\"%s", host, + (addressfamily == ADDRTYPE_IPV4 ? " (IPv4)" : + (addressfamily == ADDRTYPE_IPV6 ? " (IPv6)" : ""))); + addr = name_lookup(host, port, realhost, ssh->conf, addressfamily); + if ((err = sk_addr_error(addr)) != NULL) { + sk_addr_free(addr); + return err; + } + ssh->fullhostname = dupstr(*realhost); /* save in case of GSSAPI */ + + ssh->s = new_connection(addr, *realhost, port, + 0, 1, nodelay, keepalive, + (Plug) ssh, ssh->conf); + if ((err = sk_socket_error(ssh->s)) != NULL) { + ssh->s = NULL; + notify_remote_exit(ssh->frontend); + return err; + } } /* * If the SSH version number's fixed, set it now, and if it's SSH-2, * send the version string too. */ - if (ssh->cfg.sshprot == 0) + sshprot = conf_get_int(ssh->conf, CONF_sshprot); + if (sshprot == 0) ssh->version = 1; - if (ssh->cfg.sshprot == 3) { + if (sshprot == 3 && !ssh->bare_connection) { ssh->version = 2; - ssh_send_verstring(ssh, NULL); + ssh_send_verstring(ssh, "SSH-", NULL); } /* * loghost, if configured, overrides realhost. */ - if (*ssh->cfg.loghost) { + if (*loghost) { sfree(*realhost); - *realhost = dupstr(ssh->cfg.loghost); + *realhost = dupstr(loghost); } return NULL; } @@ -3101,17 +3545,17 @@ /* * This is treated separately, outside the switch. */ break; case CHAN_X11: - x11_override_throttle(c->u.x11.s, enable); + x11_override_throttle(c->u.x11.xconn, enable); break; case CHAN_AGENT: /* Agent channels require no buffer management. */ break; case CHAN_SOCKDATA: - pfd_override_throttle(c->u.pfd.s, enable); + pfd_override_throttle(c->u.pfd.pf, enable); break; } } } @@ -3150,10 +3594,11 @@ { struct ssh_channel *c = (struct ssh_channel *)cv; Ssh ssh = c->ssh; void *sentreply = reply; + c->u.a.outstanding_requests--; if (!sentreply) { /* Fake SSH_AGENT_FAILURE. */ sentreply = "\0\0\0\1\5"; replylen = 5; } @@ -3162,17 +3607,21 @@ ssh2_try_send(c); } else { send_packet(ssh, SSH1_MSG_CHANNEL_DATA, PKT_INT, c->remoteid, PKT_INT, replylen, - PKTT_DATA, PKT_DATA, sentreply, replylen, - PKTT_OTHER, PKT_END); } if (reply) sfree(reply); + /* + * If we've already seen an incoming EOF but haven't sent an + * outgoing one, this may be the moment to send it. + */ + if (c->u.a.outstanding_requests == 0 && (c->closes & CLOSES_RCVD_EOF)) + sshfwd_write_eof(c); } /* * Client-initiated disconnection. Send a DISCONNECT if `wire_reason' * non-NULL, otherwise just close the connection. `client_reason' == NULL @@ -3212,21 +3661,20 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen, struct Packet *pktin) { int i, j, ret; unsigned char cookie[8], *ptr; - struct RSAKey servkey, hostkey; struct MD5Context md5c; struct do_ssh1_login_state { + int crLine; int len; unsigned char *rsabuf, *keystr1, *keystr2; unsigned long supported_ciphers_mask, supported_auths_mask; int tried_publickey, tried_agent; int tis_auth_refused, ccard_auth_refused; unsigned char session_id[16]; int cipher_type; - char username[100]; void *publickey_blob; int publickey_bloblen; char *publickey_comment; int publickey_encrypted; prompts_t *cur_prompt; @@ -3239,14 +3687,16 @@ struct RSAKey key; Bignum challenge; char *commentp; int commentlen; int dlgret; + Filename *keyfile; + struct RSAKey servkey, hostkey; }; crState(do_ssh1_login_state); - crBegin(ssh->do_ssh1_login_crstate); + crBeginState; if (!pktin) crWaitUntil(pktin); if (pktin->type != SSH1_SMSG_PUBLIC_KEY) { @@ -3261,12 +3711,12 @@ bombout(("SSH-1 public key packet stopped before random cookie")); crStop(0); } memcpy(cookie, ptr, 8); - if (!ssh1_pkt_getrsakey(pktin, &servkey, &s->keystr1) || - !ssh1_pkt_getrsakey(pktin, &hostkey, &s->keystr2)) { + if (!ssh1_pkt_getrsakey(pktin, &s->servkey, &s->keystr1) || + !ssh1_pkt_getrsakey(pktin, &s->hostkey, &s->keystr2)) { bombout(("Failed to read SSH-1 public keys from public key packet")); crStop(0); } /* @@ -3274,13 +3724,13 @@ */ { char logmsg[80]; logevent("Host key fingerprint is:"); strcpy(logmsg, " "); - hostkey.comment = NULL; + s->hostkey.comment = NULL; rsa_fingerprint(logmsg + strlen(logmsg), - sizeof(logmsg) - strlen(logmsg), &hostkey); + sizeof(logmsg) - strlen(logmsg), &s->hostkey); logevent(logmsg); } ssh->v1_remote_protoflags = ssh_pkt_getuint32(pktin); s->supported_ciphers_mask = ssh_pkt_getuint32(pktin); @@ -3291,28 +3741,29 @@ ssh->v1_local_protoflags = ssh->v1_remote_protoflags & SSH1_PROTOFLAGS_SUPPORTED; ssh->v1_local_protoflags |= SSH1_PROTOFLAG_SCREEN_NUMBER; MD5Init(&md5c); - MD5Update(&md5c, s->keystr2, hostkey.bytes); - MD5Update(&md5c, s->keystr1, servkey.bytes); + MD5Update(&md5c, s->keystr2, s->hostkey.bytes); + MD5Update(&md5c, s->keystr1, s->servkey.bytes); MD5Update(&md5c, cookie, 8); MD5Final(s->session_id, &md5c); for (i = 0; i < 32; i++) ssh->session_key[i] = random_byte(); /* * Verify that the `bits' and `bytes' parameters match. */ - if (hostkey.bits > hostkey.bytes * 8 || - servkey.bits > servkey.bytes * 8) { + if (s->hostkey.bits > s->hostkey.bytes * 8 || + s->servkey.bits > s->servkey.bytes * 8) { bombout(("SSH-1 public keys were badly formatted")); crStop(0); } - s->len = (hostkey.bytes > servkey.bytes ? hostkey.bytes : servkey.bytes); + s->len = (s->hostkey.bytes > s->servkey.bytes ? + s->hostkey.bytes : s->servkey.bytes); s->rsabuf = snewn(s->len, unsigned char); /* * Verify the host key. @@ -3319,15 +3770,15 @@ */ { /* * First format the key into a string. */ - int len = rsastr_len(&hostkey); + int len = rsastr_len(&s->hostkey); char fingerprint[100]; char *keystr = snewn(len, char); - rsastr_fmt(keystr, &hostkey); - rsa_fingerprint(fingerprint, sizeof(fingerprint), &hostkey); + rsastr_fmt(keystr, &s->hostkey); + rsa_fingerprint(fingerprint, sizeof(fingerprint), &s->hostkey); ssh_set_frozen(ssh, 1); s->dlgret = verify_ssh_host_key(ssh->frontend, ssh->savedhost, ssh->savedport, "rsa", keystr, fingerprint, @@ -3357,18 +3808,18 @@ s->rsabuf[i] = ssh->session_key[i]; if (i < 16) s->rsabuf[i] ^= s->session_id[i]; } - if (hostkey.bytes > servkey.bytes) { - ret = rsaencrypt(s->rsabuf, 32, &servkey); - if (ret) - ret = rsaencrypt(s->rsabuf, servkey.bytes, &hostkey); - } else { - ret = rsaencrypt(s->rsabuf, 32, &hostkey); - if (ret) - ret = rsaencrypt(s->rsabuf, hostkey.bytes, &servkey); + if (s->hostkey.bytes > s->servkey.bytes) { + ret = rsaencrypt(s->rsabuf, 32, &s->servkey); + if (ret) + ret = rsaencrypt(s->rsabuf, s->servkey.bytes, &s->hostkey); + } else { + ret = rsaencrypt(s->rsabuf, 32, &s->hostkey); + if (ret) + ret = rsaencrypt(s->rsabuf, s->hostkey.bytes, &s->servkey); } if (!ret) { bombout(("SSH-1 public key encryptions failed due to bad formatting")); crStop(0); } @@ -3378,11 +3829,12 @@ { int cipher_chosen = 0, warn = 0; char *cipher_string = NULL; int i; for (i = 0; !cipher_chosen && i < CIPHER_MAX; i++) { - int next_cipher = ssh->cfg.ssh_cipherlist[i]; + int next_cipher = conf_get_int_int(ssh->conf, + CONF_ssh_cipherlist, i); if (next_cipher == CIPHER_WARN) { /* If/when we choose a cipher, warn about it */ warn = 1; } else if (next_cipher == CIPHER_AES) { /* XXX Probably don't need to mention this. */ @@ -3466,25 +3918,25 @@ logeventf(ssh, "Initialised %s encryption", ssh->cipher->text_name); ssh->crcda_ctx = crcda_make_context(); logevent("Installing CRC compensation attack detector"); - if (servkey.modulus) { - sfree(servkey.modulus); - servkey.modulus = NULL; - } - if (servkey.exponent) { - sfree(servkey.exponent); - servkey.exponent = NULL; - } - if (hostkey.modulus) { - sfree(hostkey.modulus); - hostkey.modulus = NULL; - } - if (hostkey.exponent) { - sfree(hostkey.exponent); - hostkey.exponent = NULL; + if (s->servkey.modulus) { + sfree(s->servkey.modulus); + s->servkey.modulus = NULL; + } + if (s->servkey.exponent) { + sfree(s->servkey.exponent); + s->servkey.exponent = NULL; + } + if (s->hostkey.modulus) { + sfree(s->hostkey.modulus); + s->hostkey.modulus = NULL; + } + if (s->hostkey.exponent) { + sfree(s->hostkey.exponent); + s->hostkey.exponent = NULL; } crWaitUntil(pktin); if (pktin->type != SSH1_SMSG_SUCCESS) { bombout(("Encryption not successfully enabled")); @@ -3493,18 +3945,16 @@ logevent("Successfully started encryption"); fflush(stdout); /* FIXME eh? */ { - if (!get_remote_username(&ssh->cfg, s->username, - sizeof(s->username))) { + if ((ssh->username = get_remote_username(ssh->conf)) == NULL) { int ret; /* need not be kept over crReturn */ s->cur_prompt = new_prompts(ssh->frontend); s->cur_prompt->to_server = TRUE; s->cur_prompt->name = dupstr("SSH login name"); - add_prompt(s->cur_prompt, dupstr("login as: "), TRUE, - lenof(s->username)); + add_prompt(s->cur_prompt, dupstr("login as: "), TRUE); ret = get_userpass_input(s->cur_prompt, NULL, 0); while (ret < 0) { ssh->send_ok = 1; crWaitUntil(!pktin); ret = get_userpass_input(s->cur_prompt, in, inlen); @@ -3516,18 +3966,17 @@ */ free_prompts(s->cur_prompt); ssh_disconnect(ssh, "No username provided", NULL, 0, TRUE); crStop(0); } - memcpy(s->username, s->cur_prompt->prompts[0]->result, - lenof(s->username)); + ssh->username = dupstr(s->cur_prompt->prompts[0]->result); free_prompts(s->cur_prompt); } - send_packet(ssh, SSH1_CMSG_USER, PKT_STR, s->username, PKT_END); + send_packet(ssh, SSH1_CMSG_USER, PKT_STR, ssh->username, PKT_END); { - char *userlog = dupprintf("Sent username \"%s\"", s->username); + char *userlog = dupprintf("Sent username \"%s\"", ssh->username); logevent(userlog); if (flags & FLAG_INTERACTIVE && (!((flags & FLAG_STDERR) && (flags & FLAG_VERBOSE)))) { c_write_str(ssh, userlog); c_write_str(ssh, "\r\n"); @@ -3546,28 +3995,29 @@ } s->tis_auth_refused = s->ccard_auth_refused = 0; /* * Load the public half of any configured keyfile for later use. */ - if (!filename_is_null(ssh->cfg.keyfile)) { + s->keyfile = conf_get_filename(ssh->conf, CONF_keyfile); + if (!filename_is_null(s->keyfile)) { int keytype; logeventf(ssh, "Reading private key file \"%.150s\"", - filename_to_str(&ssh->cfg.keyfile)); - keytype = key_type(&ssh->cfg.keyfile); + filename_to_str(s->keyfile)); + keytype = key_type(s->keyfile); if (keytype == SSH_KEYTYPE_SSH1) { const char *error; - if (rsakey_pubblob(&ssh->cfg.keyfile, + if (rsakey_pubblob(s->keyfile, &s->publickey_blob, &s->publickey_bloblen, &s->publickey_comment, &error)) { - s->publickey_encrypted = rsakey_encrypted(&ssh->cfg.keyfile, + s->publickey_encrypted = rsakey_encrypted(s->keyfile, NULL); } else { char *msgbuf; logeventf(ssh, "Unable to load private key (%s)", error); msgbuf = dupprintf("Unable to load private key file " "\"%.150s\" (%s)\r\n", - filename_to_str(&ssh->cfg.keyfile), + filename_to_str(s->keyfile), error); c_write_str(ssh, msgbuf); sfree(msgbuf); s->publickey_blob = NULL; } @@ -3575,11 +4025,11 @@ char *msgbuf; logeventf(ssh, "Unable to use this key file (%s)", key_type_to_str(keytype)); msgbuf = dupprintf("Unable to use key file \"%.150s\"" " (%s)\r\n", - filename_to_str(&ssh->cfg.keyfile), + filename_to_str(s->keyfile), key_type_to_str(keytype)); c_write_str(ssh, msgbuf); sfree(msgbuf); s->publickey_blob = NULL; } @@ -3587,11 +4037,11 @@ s->publickey_blob = NULL; while (pktin->type == SSH1_SMSG_FAILURE) { s->pwpkt_type = SSH1_CMSG_AUTH_PASSWORD; - if (ssh->cfg.tryagent && agent_exists() && !s->tried_agent) { + if (conf_get_int(ssh->conf, CONF_tryagent) && agent_exists() && !s->tried_agent) { /* * Attempt RSA authentication using Pageant. */ void *r; @@ -3617,36 +4067,42 @@ } s->response = (unsigned char *) r; if (s->response && s->responselen >= 5 && s->response[4] == SSH1_AGENT_RSA_IDENTITIES_ANSWER) { s->p = s->response + 5; - s->nkeys = GET_32BIT(s->p); + s->nkeys = toint(GET_32BIT(s->p)); + if (s->nkeys < 0) { + logeventf(ssh, "Pageant reported negative key count %d", + s->nkeys); + s->nkeys = 0; + } s->p += 4; logeventf(ssh, "Pageant has %d SSH-1 keys", s->nkeys); for (s->keyi = 0; s->keyi < s->nkeys; s->keyi++) { unsigned char *pkblob = s->p; s->p += 4; { int n, ok = FALSE; do { /* do while (0) to make breaking easy */ n = ssh1_read_bignum - (s->p, s->responselen-(s->p-s->response), + (s->p, toint(s->responselen-(s->p-s->response)), &s->key.exponent); if (n < 0) break; s->p += n; n = ssh1_read_bignum - (s->p, s->responselen-(s->p-s->response), + (s->p, toint(s->responselen-(s->p-s->response)), &s->key.modulus); if (n < 0) - break; + break; s->p += n; if (s->responselen - (s->p-s->response) < 4) break; - s->commentlen = GET_32BIT(s->p); + s->commentlen = toint(GET_32BIT(s->p)); s->p += 4; - if (s->responselen - (s->p-s->response) < + if (s->commentlen < 0 || + toint(s->responselen - (s->p-s->response)) < s->commentlen) break; s->commentp = (char *)s->p; s->p += s->commentlen; ok = TRUE; @@ -3771,12 +4227,13 @@ * key file. */ int got_passphrase; /* need not be kept over crReturn */ if (flags & FLAG_VERBOSE) c_write_str(ssh, "Trying public key authentication.\r\n"); + s->keyfile = conf_get_filename(ssh->conf, CONF_keyfile); logeventf(ssh, "Trying public key \"%s\"", - filename_to_str(&ssh->cfg.keyfile)); + filename_to_str(s->keyfile)); s->tried_publickey = 1; got_passphrase = FALSE; while (!got_passphrase) { /* * Get a passphrase, if necessary. @@ -3792,12 +4249,11 @@ s->cur_prompt = new_prompts(ssh->frontend); s->cur_prompt->to_server = FALSE; s->cur_prompt->name = dupstr("SSH key passphrase"); add_prompt(s->cur_prompt, dupprintf("Passphrase for key \"%.100s\": ", - s->publickey_comment), - FALSE, SSH_MAX_PASSWORD_LEN); + s->publickey_comment), FALSE); ret = get_userpass_input(s->cur_prompt, NULL, 0); while (ret < 0) { ssh->send_ok = 1; crWaitUntil(!pktin); ret = get_userpass_input(s->cur_prompt, in, inlen); @@ -3814,22 +4270,23 @@ free_prompts(s->cur_prompt); } /* * Try decrypting key with passphrase. */ - ret = loadrsakey(&ssh->cfg.keyfile, &s->key, passphrase, + s->keyfile = conf_get_filename(ssh->conf, CONF_keyfile); + ret = loadrsakey(s->keyfile, &s->key, passphrase, &error); if (passphrase) { - memset(passphrase, 0, strlen(passphrase)); + smemclr(passphrase, strlen(passphrase)); sfree(passphrase); } if (ret == 1) { /* Correct passphrase. */ got_passphrase = TRUE; } else if (ret == 0) { c_write_str(ssh, "Couldn't load private key from "); - c_write_str(ssh, filename_to_str(&ssh->cfg.keyfile)); + c_write_str(ssh, filename_to_str(s->keyfile)); c_write_str(ssh, " ("); c_write_str(ssh, error); c_write_str(ssh, ").\r\n"); got_passphrase = FALSE; break; /* go and try something else */ @@ -3908,11 +4365,11 @@ /* * Otherwise, try various forms of password-like authentication. */ s->cur_prompt = new_prompts(ssh->frontend); - if (ssh->cfg.try_tis_auth && + if (conf_get_int(ssh->conf, CONF_try_tis_auth) && (s->supported_auths_mask & (1 << SSH1_AUTH_TIS)) && !s->tis_auth_refused) { s->pwpkt_type = SSH1_CMSG_AUTH_TIS_RESPONSE; logevent("Requested TIS authentication"); send_packet(ssh, SSH1_CMSG_AUTH_TIS, PKT_END); @@ -3947,15 +4404,15 @@ s->cur_prompt->instruction = dupprintf("Using TIS authentication.%s%s", (*instr_suf) ? "\n" : "", instr_suf); s->cur_prompt->instr_reqd = TRUE; - add_prompt(s->cur_prompt, prompt, FALSE, SSH_MAX_PASSWORD_LEN); + add_prompt(s->cur_prompt, prompt, FALSE); sfree(instr_suf); } } - if (ssh->cfg.try_tis_auth && + if (conf_get_int(ssh->conf, CONF_try_tis_auth) && (s->supported_auths_mask & (1 << SSH1_AUTH_CCARD)) && !s->ccard_auth_refused) { s->pwpkt_type = SSH1_CMSG_AUTH_CCARD_RESPONSE; logevent("Requested CryptoCard authentication"); send_packet(ssh, SSH1_CMSG_AUTH_CCARD, PKT_END); @@ -3990,11 +4447,11 @@ s->cur_prompt->instruction = dupprintf("Using CryptoCard authentication.%s%s", (*instr_suf) ? "\n" : "", instr_suf); s->cur_prompt->instr_reqd = TRUE; - add_prompt(s->cur_prompt, prompt, FALSE, SSH_MAX_PASSWORD_LEN); + add_prompt(s->cur_prompt, prompt, FALSE); sfree(instr_suf); } } if (s->pwpkt_type == SSH1_CMSG_AUTH_PASSWORD) { if ((s->supported_auths_mask & (1 << SSH1_AUTH_PASSWORD)) == 0) { @@ -4001,13 +4458,13 @@ bombout(("No supported authentication methods available")); crStop(0); } s->cur_prompt->to_server = TRUE; s->cur_prompt->name = dupstr("SSH password"); - add_prompt(s->cur_prompt, dupprintf("%.90s@%.90s's password: ", - s->username, ssh->savedhost), - FALSE, SSH_MAX_PASSWORD_LEN); + add_prompt(s->cur_prompt, dupprintf("%s@%s's password: ", + ssh->username, ssh->savedhost), + FALSE); } /* * Show password prompt, having first obtained it via a TIS * or CryptoCard exchange if we're doing TIS or CryptoCard @@ -4095,13 +4552,12 @@ randomstr = snewn(top + 1, char); for (i = bottom; i <= top; i++) { if (i == pwlen) { defer_packet(ssh, s->pwpkt_type, - PKTT_PASSWORD, PKT_STR, - s->cur_prompt->prompts[0]->result, - PKTT_OTHER, PKT_END); + PKT_STR,s->cur_prompt->prompts[0]->result, + PKT_END); } else { for (j = 0; j < i; j++) { do { randomstr[j] = random_byte(); } while (randomstr[j] == '\0'); @@ -4135,30 +4591,30 @@ } } else { ss = s->cur_prompt->prompts[0]->result; } logevent("Sending length-padded password"); - send_packet(ssh, s->pwpkt_type, PKTT_PASSWORD, + send_packet(ssh, s->pwpkt_type, PKT_INT, len, PKT_DATA, ss, len, - PKTT_OTHER, PKT_END); + PKT_END); } else { /* * The server is believed unable to cope with * any of our password camouflage methods. */ int len; len = strlen(s->cur_prompt->prompts[0]->result); logevent("Sending unpadded password"); send_packet(ssh, s->pwpkt_type, - PKTT_PASSWORD, PKT_INT, len, + PKT_INT, len, PKT_DATA, s->cur_prompt->prompts[0]->result, len, - PKTT_OTHER, PKT_END); + PKT_END); } } else { - send_packet(ssh, s->pwpkt_type, PKTT_PASSWORD, + send_packet(ssh, s->pwpkt_type, PKT_STR, s->cur_prompt->prompts[0]->result, - PKTT_OTHER, PKT_END); + PKT_END); } logevent("Sent password"); free_prompts(s->cur_prompt); crWaitUntil(pktin); if (pktin->type == SSH1_SMSG_FAILURE) { @@ -4180,74 +4636,77 @@ logevent("Authentication successful"); crFinish(1); } -void sshfwd_close(struct ssh_channel *c) +static void ssh_channel_try_eof(struct ssh_channel *c) +{ + Ssh ssh = c->ssh; + assert(c->pending_eof); /* precondition for calling us */ + if (c->halfopen) + return; /* can't close: not even opened yet */ + if (ssh->version == 2 && bufchain_size(&c->v.v2.outbuffer) > 0) + return; /* can't send EOF: pending outgoing data */ + + c->pending_eof = FALSE; /* we're about to send it */ + if (ssh->version == 1) { + send_packet(ssh, SSH1_MSG_CHANNEL_CLOSE, PKT_INT, c->remoteid, + PKT_END); + c->closes |= CLOSES_SENT_EOF; + } else { + struct Packet *pktout; + pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_EOF); + ssh2_pkt_adduint32(pktout, c->remoteid); + ssh2_pkt_send(ssh, pktout); + c->closes |= CLOSES_SENT_EOF; + ssh2_channel_check_close(c); + } +} + +Conf *sshfwd_get_conf(struct ssh_channel *c) +{ + Ssh ssh = c->ssh; + return ssh->conf; +} + +void sshfwd_write_eof(struct ssh_channel *c) +{ + Ssh ssh = c->ssh; + + if (ssh->state == SSH_STATE_CLOSED) + return; + + if (c->closes & CLOSES_SENT_EOF) + return; + + c->pending_eof = TRUE; + ssh_channel_try_eof(c); +} + +void sshfwd_unclean_close(struct ssh_channel *c, const char *err) { Ssh ssh = c->ssh; if (ssh->state == SSH_STATE_CLOSED) return; - if (!c->closes) { - /* - * If halfopen is true, we have sent - * CHANNEL_OPEN for this channel, but it hasn't even been - * acknowledged by the server. So we must set a close flag - * on it now, and then when the server acks the channel - * open, we can close it then. - */ - if (!c->halfopen) { - if (ssh->version == 1) { - send_packet(ssh, SSH1_MSG_CHANNEL_CLOSE, PKT_INT, c->remoteid, - PKT_END); - c->closes = 1; /* sent MSG_CLOSE */ - } else { - int bytes_to_send = bufchain_size(&c->v.v2.outbuffer); - if (bytes_to_send > 0) { - /* - * If we still have unsent data in our outgoing - * buffer for this channel, we can't actually - * initiate a close operation yet or that data - * will be lost. Instead, set the pending_close - * flag so that when we do clear the buffer - * we'll start closing the channel. - */ - char logmsg[160] = {'\0'}; - sprintf( - logmsg, - "Forwarded port pending to be closed : " - "%d bytes remaining", - bytes_to_send); - logevent(logmsg); - - c->pending_close = TRUE; - } else { - /* - * No locally buffered data, so we can send the - * close message immediately. - */ - struct Packet *pktout; - pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_CLOSE); - ssh2_pkt_adduint32(pktout, c->remoteid); - ssh2_pkt_send(ssh, pktout); - c->closes = 1; /* sent MSG_CLOSE */ - logevent("Nothing left to send, closing channel"); - } - } - } - - if (c->type == CHAN_X11) { - c->u.x11.s = NULL; - logevent("Forwarded X11 connection terminated"); - } else if (c->type == CHAN_SOCKDATA || - c->type == CHAN_SOCKDATA_DORMANT) { - c->u.pfd.s = NULL; - logevent("Forwarded port closed"); - } - } + switch (c->type) { + case CHAN_X11: + x11_close(c->u.x11.xconn); + logeventf(ssh, "Forwarded X11 connection terminated due to local " + "error: %s", err); + break; + case CHAN_SOCKDATA: + case CHAN_SOCKDATA_DORMANT: + pfd_close(c->u.pfd.pf); + logeventf(ssh, "Forwarded port closed due to local error: %s", err); + break; + } + c->type = CHAN_ZOMBIE; + c->pending_eof = FALSE; /* this will confuse a zombie channel */ + + ssh2_channel_check_close(c); } int sshfwd_write(struct ssh_channel *c, char *buf, int len) { Ssh ssh = c->ssh; @@ -4256,12 +4715,12 @@ return 0; if (ssh->version == 1) { send_packet(ssh, SSH1_MSG_CHANNEL_DATA, PKT_INT, c->remoteid, - PKT_INT, len, PKTT_DATA, PKT_DATA, buf, len, - PKTT_OTHER, PKT_END); + PKT_INT, len, PKT_DATA, buf, len, + PKT_END); /* * In SSH-1 we can return 0 here - implying that forwarded * connections are never individually throttled - because * the only circumstance that can cause throttling will be * the whole SSH connection backing up, in which case @@ -4302,31 +4761,30 @@ assert(pktin->type == qh->msg1 || pktin->type == qh->msg2); if (qh->msg1 > 0) { assert(ssh->packet_dispatch[qh->msg1] == ssh_queueing_handler); - ssh->packet_dispatch[qh->msg1] = NULL; + ssh->packet_dispatch[qh->msg1] = ssh->q_saved_handler1; } if (qh->msg2 > 0) { assert(ssh->packet_dispatch[qh->msg2] == ssh_queueing_handler); - ssh->packet_dispatch[qh->msg2] = NULL; + ssh->packet_dispatch[qh->msg2] = ssh->q_saved_handler2; } if (qh->next) { ssh->qhead = qh->next; if (ssh->qhead->msg1 > 0) { - assert(ssh->packet_dispatch[ssh->qhead->msg1] == NULL); + ssh->q_saved_handler1 = ssh->packet_dispatch[ssh->qhead->msg1]; ssh->packet_dispatch[ssh->qhead->msg1] = ssh_queueing_handler; } if (ssh->qhead->msg2 > 0) { - assert(ssh->packet_dispatch[ssh->qhead->msg2] == NULL); + ssh->q_saved_handler2 = ssh->packet_dispatch[ssh->qhead->msg2]; ssh->packet_dispatch[ssh->qhead->msg2] = ssh_queueing_handler; } } else { ssh->qhead = ssh->qtail = NULL; - ssh->packet_dispatch[pktin->type] = NULL; } qh->handler(ssh, pktin, qh->ctx); sfree(qh); @@ -4346,15 +4804,15 @@ if (ssh->qtail == NULL) { ssh->qhead = qh; if (qh->msg1 > 0) { - assert(ssh->packet_dispatch[qh->msg1] == NULL); + ssh->q_saved_handler1 = ssh->packet_dispatch[ssh->qhead->msg1]; ssh->packet_dispatch[qh->msg1] = ssh_queueing_handler; } if (qh->msg2 > 0) { - assert(ssh->packet_dispatch[qh->msg2] == NULL); + ssh->q_saved_handler2 = ssh->packet_dispatch[ssh->qhead->msg2]; ssh->packet_dispatch[qh->msg2] = ssh_queueing_handler; } } else { ssh->qtail->next = qh; } @@ -4378,15 +4836,50 @@ pf->pfrec->remote = NULL; free_rportfwd(pf); } } -static void ssh_setup_portfwd(Ssh ssh, const Config *cfg) +int ssh_alloc_sharing_rportfwd(Ssh ssh, const char *shost, int sport, + void *share_ctx) { - const char *portfwd_strptr = cfg->portfwd; + struct ssh_rportfwd *pf = snew(struct ssh_rportfwd); + pf->dhost = NULL; + pf->dport = 0; + pf->share_ctx = share_ctx; + pf->shost = dupstr(shost); + pf->sport = sport; + pf->sportdesc = NULL; + if (!ssh->rportfwds) { + assert(ssh->version == 2); + ssh->rportfwds = newtree234(ssh_rportcmp_ssh2); + } + if (add234(ssh->rportfwds, pf) != pf) { + sfree(pf->shost); + sfree(pf); + return FALSE; + } + return TRUE; +} + +static void ssh_sharing_global_request_response(Ssh ssh, struct Packet *pktin, + void *ctx) +{ + share_got_pkt_from_server(ctx, pktin->type, + pktin->body, pktin->length); +} + +void ssh_sharing_queue_global_request(Ssh ssh, void *share_ctx) +{ + ssh_queue_handler(ssh, SSH2_MSG_REQUEST_SUCCESS, SSH2_MSG_REQUEST_FAILURE, + ssh_sharing_global_request_response, share_ctx); +} + +static void ssh_setup_portfwd(Ssh ssh, Conf *conf) +{ struct ssh_portfwd *epf; int i; + char *key, *val; if (!ssh->portfwds) { ssh->portfwds = newtree234(ssh_portcmp); } else { /* @@ -4400,84 +4893,38 @@ int i; for (i = 0; (epf = index234(ssh->portfwds, i)) != NULL; i++) epf->status = DESTROY; } - while (*portfwd_strptr) { + for (val = conf_get_str_strs(conf, CONF_portfwd, NULL, &key); + val != NULL; + val = conf_get_str_strs(conf, CONF_portfwd, key, &key)) { + char *kp, *kp2, *vp, *vp2; char address_family, type; int sport,dport,sserv,dserv; - char sports[256], dports[256], saddr[256], host[256]; - int n; + char *sports, *dports, *saddr, *host; + + kp = key; address_family = 'A'; type = 'L'; - if (*portfwd_strptr == 'A' || - *portfwd_strptr == '4' || - *portfwd_strptr == '6') - address_family = *portfwd_strptr++; - if (*portfwd_strptr == 'L' || - *portfwd_strptr == 'R' || - *portfwd_strptr == 'D') - type = *portfwd_strptr++; - - saddr[0] = '\0'; - - n = 0; - while (*portfwd_strptr && *portfwd_strptr != '\t') { - if (*portfwd_strptr == ':') { - /* - * We've seen a colon in the middle of the - * source port number. This means that - * everything we've seen until now is the - * source _address_, so we'll move it into - * saddr and start sports from the beginning - * again. - */ - portfwd_strptr++; - sports[n] = '\0'; - if (ssh->version == 1 && type == 'R') { - logeventf(ssh, "SSH-1 cannot handle remote source address " - "spec \"%s\"; ignoring", sports); - } else - strcpy(saddr, sports); - n = 0; - } - if (n < lenof(sports)-1) sports[n++] = *portfwd_strptr++; - } - sports[n] = 0; - if (type != 'D') { - if (*portfwd_strptr == '\t') - portfwd_strptr++; - n = 0; - while (*portfwd_strptr && *portfwd_strptr != ':') { - if (n < lenof(host)-1) host[n++] = *portfwd_strptr++; - } - host[n] = 0; - if (*portfwd_strptr == ':') - portfwd_strptr++; - n = 0; - while (*portfwd_strptr) { - if (n < lenof(dports)-1) dports[n++] = *portfwd_strptr++; - } - dports[n] = 0; - portfwd_strptr++; - dport = atoi(dports); - dserv = 0; - if (dport == 0) { - dserv = 1; - dport = net_service_lookup(dports); - if (!dport) { - logeventf(ssh, "Service lookup failed for destination" - " port \"%s\"", dports); - } - } + if (*kp == 'A' || *kp == '4' || *kp == '6') + address_family = *kp++; + if (*kp == 'L' || *kp == 'R') + type = *kp++; + + if ((kp2 = strchr(kp, ':')) != NULL) { + /* + * There's a colon in the middle of the source port + * string, which means that the part before it is + * actually a source address. + */ + saddr = dupprintf("%.*s", (int)(kp2 - kp), kp); + sports = kp2+1; } else { - while (*portfwd_strptr) portfwd_strptr++; - host[0] = 0; - dports[0] = 0; - dport = dserv = -1; - portfwd_strptr++; /* eat the NUL and move to next one */ + saddr = NULL; + sports = kp; } sport = atoi(sports); sserv = 0; if (sport == 0) { sserv = 1; @@ -4485,20 +4932,48 @@ if (!sport) { logeventf(ssh, "Service lookup failed for source" " port \"%s\"", sports); } } + + if (type == 'L' && !strcmp(val, "D")) { + /* dynamic forwarding */ + host = NULL; + dports = NULL; + dport = -1; + dserv = 0; + type = 'D'; + } else { + /* ordinary forwarding */ + vp = val; + vp2 = vp + strcspn(vp, ":"); + host = dupprintf("%.*s", (int)(vp2 - vp), vp); + if (vp2) + vp2++; + dports = vp2; + dport = atoi(dports); + dserv = 0; + if (dport == 0) { + dserv = 1; + dport = net_service_lookup(dports); + if (!dport) { + logeventf(ssh, "Service lookup failed for destination" + " port \"%s\"", dports); + } + } + } + if (sport && dport) { /* Set up a description of the source port. */ struct ssh_portfwd *pfrec, *epfrec; pfrec = snew(struct ssh_portfwd); pfrec->type = type; - pfrec->saddr = *saddr ? dupstr(saddr) : NULL; + pfrec->saddr = saddr; pfrec->sserv = sserv ? dupstr(sports) : NULL; pfrec->sport = sport; - pfrec->daddr = *host ? dupstr(host) : NULL; + pfrec->daddr = host; pfrec->dserv = dserv ? dupstr(dports) : NULL; pfrec->dport = dport; pfrec->local = NULL; pfrec->remote = NULL; pfrec->addressfamily = (address_family == '4' ? ADDRTYPE_IPV4 : @@ -4522,10 +4997,13 @@ */ free_portfwd(pfrec); } else { pfrec->status = CREATE; } + } else { + sfree(saddr); + sfree(host); } } /* * Now go through and destroy any port forwardings which were @@ -4575,26 +5053,26 @@ pktout = ssh2_pkt_init(SSH2_MSG_GLOBAL_REQUEST); ssh2_pkt_addstring(pktout, "cancel-tcpip-forward"); ssh2_pkt_addbool(pktout, 0);/* _don't_ want reply */ if (epf->saddr) { ssh2_pkt_addstring(pktout, epf->saddr); - } else if (ssh->cfg.rport_acceptall) { - /* XXX: ssh->cfg.rport_acceptall may not represent + } else if (conf_get_int(conf, CONF_rport_acceptall)) { + /* XXX: rport_acceptall may not represent * what was used to open the original connection, * since it's reconfigurable. */ - ssh2_pkt_addstring(pktout, "0.0.0.0"); + ssh2_pkt_addstring(pktout, ""); } else { - ssh2_pkt_addstring(pktout, "127.0.0.1"); + ssh2_pkt_addstring(pktout, "localhost"); } ssh2_pkt_adduint32(pktout, epf->sport); ssh2_pkt_send(ssh, pktout); } del234(ssh->rportfwds, rpf); free_rportfwd(rpf); } else if (epf->local) { - pfd_terminate(epf->local); + pfl_terminate(epf->local); } delpos234(ssh->portfwds, i); free_portfwd(epf); i--; /* so we don't skip one in the list */ @@ -4623,33 +5101,35 @@ epf->dport, epf->dserv ? ")" : ""); } if (epf->type == 'L') { - const char *err = pfd_addforward(epf->daddr, epf->dport, - epf->saddr, epf->sport, - ssh, cfg, - &epf->local, - epf->addressfamily); + char *err = pfl_listen(epf->daddr, epf->dport, + epf->saddr, epf->sport, + ssh, conf, &epf->local, + epf->addressfamily); logeventf(ssh, "Local %sport %s forwarding to %s%s%s", epf->addressfamily == ADDRTYPE_IPV4 ? "IPv4 " : epf->addressfamily == ADDRTYPE_IPV6 ? "IPv6 " : "", sportdesc, dportdesc, err ? " failed: " : "", err ? err : ""); + if (err) + sfree(err); } else if (epf->type == 'D') { - const char *err = pfd_addforward(NULL, -1, - epf->saddr, epf->sport, - ssh, cfg, - &epf->local, - epf->addressfamily); + char *err = pfl_listen(NULL, -1, epf->saddr, epf->sport, + ssh, conf, &epf->local, + epf->addressfamily); logeventf(ssh, "Local %sport %s SOCKS dynamic forwarding%s%s", epf->addressfamily == ADDRTYPE_IPV4 ? "IPv4 " : epf->addressfamily == ADDRTYPE_IPV6 ? "IPv6 " : "", sportdesc, err ? " failed: " : "", err ? err : ""); + + if (err) + sfree(err); } else { struct ssh_rportfwd *pf; /* * Ensure the remote port forwardings tree exists. @@ -4660,13 +5140,20 @@ else ssh->rportfwds = newtree234(ssh_rportcmp_ssh2); } pf = snew(struct ssh_rportfwd); - strncpy(pf->dhost, epf->daddr, lenof(pf->dhost)-1); - pf->dhost[lenof(pf->dhost)-1] = '\0'; + pf->share_ctx = NULL; + pf->dhost = dupstr(epf->daddr); pf->dport = epf->dport; + if (epf->saddr) { + pf->shost = dupstr(epf->saddr); + } else if (conf_get_int(conf, CONF_rport_acceptall)) { + pf->shost = dupstr(""); + } else { + pf->shost = dupstr("localhost"); + } pf->sport = epf->sport; if (add234(ssh->rportfwds, pf) != pf) { logeventf(ssh, "Duplicate remote port forwarding to %s:%d", epf->daddr, epf->dport); sfree(pf); @@ -4691,18 +5178,12 @@ } else { struct Packet *pktout; pktout = ssh2_pkt_init(SSH2_MSG_GLOBAL_REQUEST); ssh2_pkt_addstring(pktout, "tcpip-forward"); ssh2_pkt_addbool(pktout, 1);/* want reply */ - if (epf->saddr) { - ssh2_pkt_addstring(pktout, epf->saddr); - } else if (cfg->rport_acceptall) { - ssh2_pkt_addstring(pktout, "0.0.0.0"); - } else { - ssh2_pkt_addstring(pktout, "127.0.0.1"); - } - ssh2_pkt_adduint32(pktout, epf->sport); + ssh2_pkt_addstring(pktout, pf->shost); + ssh2_pkt_adduint32(pktout, pf->sport); ssh2_pkt_send(ssh, pktout); ssh_queue_handler(ssh, SSH2_MSG_REQUEST_SUCCESS, SSH2_MSG_REQUEST_FAILURE, ssh_rportfwd_succfail, pf); @@ -4748,32 +5229,23 @@ logevent("Rejected X11 connect request"); } else { c = snew(struct ssh_channel); c->ssh = ssh; - if (x11_init(&c->u.x11.s, ssh->x11disp, c, - NULL, -1, &ssh->cfg) != NULL) { - logevent("Opening X11 forward connection failed"); - sfree(c); - send_packet(ssh, SSH1_MSG_CHANNEL_OPEN_FAILURE, - PKT_INT, remoteid, PKT_END); - } else { - logevent - ("Opening X11 forward connection succeeded"); - c->remoteid = remoteid; - c->halfopen = FALSE; - c->localid = alloc_channel_id(ssh); - c->closes = 0; - c->pending_close = FALSE; - c->throttling_conn = 0; - c->type = CHAN_X11; /* identify channel type */ - add234(ssh->channels, c); - send_packet(ssh, SSH1_MSG_CHANNEL_OPEN_CONFIRMATION, - PKT_INT, c->remoteid, PKT_INT, - c->localid, PKT_END); - logevent("Opened X11 forward channel"); - } + c->u.x11.xconn = x11_init(ssh->x11authtree, c, NULL, -1); + c->remoteid = remoteid; + c->halfopen = FALSE; + c->localid = alloc_channel_id(ssh); + c->closes = 0; + c->pending_eof = FALSE; + c->throttling_conn = 0; + c->type = CHAN_X11; /* identify channel type */ + add234(ssh->channels, c); + send_packet(ssh, SSH1_MSG_CHANNEL_OPEN_CONFIRMATION, + PKT_INT, c->remoteid, PKT_INT, + c->localid, PKT_END); + logevent("Opened X11 forward channel"); } } static void ssh1_smsg_agent_open(Ssh ssh, struct Packet *pktin) { @@ -4791,14 +5263,16 @@ c->ssh = ssh; c->remoteid = remoteid; c->halfopen = FALSE; c->localid = alloc_channel_id(ssh); c->closes = 0; - c->pending_close = FALSE; + c->pending_eof = FALSE; c->throttling_conn = 0; c->type = CHAN_AGENT; /* identify channel type */ c->u.a.lensofar = 0; + c->u.a.message = NULL; + c->u.a.outstanding_requests = 0; add234(ssh->channels, c); send_packet(ssh, SSH1_MSG_CHANNEL_OPEN_CONFIRMATION, PKT_INT, c->remoteid, PKT_INT, c->localid, PKT_END); } @@ -4806,60 +5280,60 @@ static void ssh1_msg_port_open(Ssh ssh, struct Packet *pktin) { /* Remote side is trying to open a channel to talk to a * forwarded port. Give them back a local channel number. */ - struct ssh_channel *c; struct ssh_rportfwd pf, *pfp; int remoteid; int hostsize, port; char *host; - const char *e; - c = snew(struct ssh_channel); - c->ssh = ssh; + char *err; remoteid = ssh_pkt_getuint32(pktin); ssh_pkt_getstring(pktin, &host, &hostsize); port = ssh_pkt_getuint32(pktin); - if (hostsize >= lenof(pf.dhost)) - hostsize = lenof(pf.dhost)-1; - memcpy(pf.dhost, host, hostsize); - pf.dhost[hostsize] = '\0'; + pf.dhost = dupprintf("%.*s", hostsize, host); pf.dport = port; pfp = find234(ssh->rportfwds, &pf, NULL); if (pfp == NULL) { logeventf(ssh, "Rejected remote port open request for %s:%d", pf.dhost, port); send_packet(ssh, SSH1_MSG_CHANNEL_OPEN_FAILURE, PKT_INT, remoteid, PKT_END); } else { + struct ssh_channel *c = snew(struct ssh_channel); + c->ssh = ssh; + logeventf(ssh, "Received remote port open request for %s:%d", pf.dhost, port); - e = pfd_newconnect(&c->u.pfd.s, pf.dhost, port, - c, &ssh->cfg, pfp->pfrec->addressfamily); - if (e != NULL) { - logeventf(ssh, "Port open failed: %s", e); + err = pfd_connect(&c->u.pfd.pf, pf.dhost, port, + c, ssh->conf, pfp->pfrec->addressfamily); + if (err != NULL) { + logeventf(ssh, "Port open failed: %s", err); + sfree(err); sfree(c); send_packet(ssh, SSH1_MSG_CHANNEL_OPEN_FAILURE, PKT_INT, remoteid, PKT_END); } else { c->remoteid = remoteid; c->halfopen = FALSE; c->localid = alloc_channel_id(ssh); c->closes = 0; - c->pending_close = FALSE; + c->pending_eof = FALSE; c->throttling_conn = 0; c->type = CHAN_SOCKDATA; /* identify channel type */ add234(ssh->channels, c); send_packet(ssh, SSH1_MSG_CHANNEL_OPEN_CONFIRMATION, PKT_INT, c->remoteid, PKT_INT, c->localid, PKT_END); logevent("Forwarded port opened successfully"); } } + + sfree(pf.dhost); } static void ssh1_msg_channel_open_confirmation(Ssh ssh, struct Packet *pktin) { unsigned int remoteid = ssh_pkt_getuint32(pktin); @@ -4870,22 +5344,21 @@ if (c && c->type == CHAN_SOCKDATA_DORMANT) { c->remoteid = localid; c->halfopen = FALSE; c->type = CHAN_SOCKDATA; c->throttling_conn = 0; - pfd_confirm(c->u.pfd.s); + pfd_confirm(c->u.pfd.pf); } - if (c && c->closes) { + if (c && c->pending_eof) { /* * We have a pending close on this channel, * which we decided on before the server acked * the channel open. So now we know the * remoteid, we can close it again. */ - send_packet(ssh, SSH1_MSG_CHANNEL_CLOSE, - PKT_INT, c->remoteid, PKT_END); + ssh_channel_try_eof(c); } } static void ssh1_msg_channel_open_failure(Ssh ssh, struct Packet *pktin) { @@ -4893,11 +5366,11 @@ struct ssh_channel *c; c = find234(ssh->channels, &remoteid, ssh_channelfind); if (c && c->type == CHAN_SOCKDATA_DORMANT) { logevent("Forwarded connection refused by server"); - pfd_close(c->u.pfd.s); + pfd_close(c->u.pfd.pf); del234(ssh->channels, c); sfree(c); } } @@ -4906,38 +5379,66 @@ /* Remote side closes a channel. */ unsigned i = ssh_pkt_getuint32(pktin); struct ssh_channel *c; c = find234(ssh->channels, &i, ssh_channelfind); if (c && !c->halfopen) { - int closetype; - closetype = - (pktin->type == SSH1_MSG_CHANNEL_CLOSE ? 1 : 2); - - if ((c->closes == 0) && (c->type == CHAN_X11)) { - logevent("Forwarded X11 connection terminated"); - assert(c->u.x11.s != NULL); - x11_close(c->u.x11.s); - c->u.x11.s = NULL; - } - if ((c->closes == 0) && (c->type == CHAN_SOCKDATA)) { - logevent("Forwarded port closed"); - assert(c->u.pfd.s != NULL); - pfd_close(c->u.pfd.s); - c->u.pfd.s = NULL; - } - - c->closes |= (closetype << 2); /* seen this message */ - if (!(c->closes & closetype)) { - send_packet(ssh, pktin->type, PKT_INT, c->remoteid, - PKT_END); - c->closes |= closetype; /* sent it too */ - } - - if (c->closes == 15) { - del234(ssh->channels, c); - sfree(c); - } + + if (pktin->type == SSH1_MSG_CHANNEL_CLOSE && + !(c->closes & CLOSES_RCVD_EOF)) { + /* + * Received CHANNEL_CLOSE, which we translate into + * outgoing EOF. + */ + int send_close = FALSE; + + c->closes |= CLOSES_RCVD_EOF; + + switch (c->type) { + case CHAN_X11: + if (c->u.x11.xconn) + x11_send_eof(c->u.x11.xconn); + else + send_close = TRUE; + break; + case CHAN_SOCKDATA: + if (c->u.pfd.pf) + pfd_send_eof(c->u.pfd.pf); + else + send_close = TRUE; + break; + case CHAN_AGENT: + send_close = TRUE; + break; + } + + if (send_close && !(c->closes & CLOSES_SENT_EOF)) { + send_packet(ssh, SSH1_MSG_CHANNEL_CLOSE, PKT_INT, c->remoteid, + PKT_END); + c->closes |= CLOSES_SENT_EOF; + } + } + + if (pktin->type == SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION && + !(c->closes & CLOSES_RCVD_CLOSE)) { + + if (!(c->closes & CLOSES_SENT_EOF)) { + bombout(("Received CHANNEL_CLOSE_CONFIRMATION for channel %d" + " for which we never sent CHANNEL_CLOSE\n", i)); + } + + c->closes |= CLOSES_RCVD_CLOSE; + } + + if (!((CLOSES_SENT_EOF | CLOSES_RCVD_EOF) & ~c->closes) && + !(c->closes & CLOSES_SENT_CLOSE)) { + send_packet(ssh, SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION, + PKT_INT, c->remoteid, PKT_END); + c->closes |= CLOSES_SENT_CLOSE; + } + + if (!((CLOSES_SENT_CLOSE | CLOSES_RCVD_CLOSE) & ~c->closes)) + ssh_channel_destroy(c); } else { bombout(("Received CHANNEL_CLOSE%s for %s channel %d\n", pktin->type == SSH1_MSG_CHANNEL_CLOSE ? "" : "_CONFIRMATION", c ? "half-open" : "nonexistent", i)); @@ -4957,14 +5458,14 @@ c = find234(ssh->channels, &i, ssh_channelfind); if (c) { int bufsize = 0; switch (c->type) { case CHAN_X11: - bufsize = x11_send(c->u.x11.s, p, len); + bufsize = x11_send(c->u.x11.xconn, p, len); break; case CHAN_SOCKDATA: - bufsize = pfd_send(c->u.pfd.s, p, len); + bufsize = pfd_send(c->u.pfd.pf, p, len); break; case CHAN_AGENT: /* Data for an agent message. Buffer it. */ while (len > 0) { if (c->u.a.lensofar < 4) { @@ -4993,10 +5494,11 @@ c->u.a.lensofar += l; } if (c->u.a.lensofar == c->u.a.totallen) { void *reply; int replylen; + c->u.a.outstanding_requests++; if (agent_query(c->u.a.message, c->u.a.totallen, &reply, &replylen, ssh_agentf_callback, c)) ssh_agentf_callback(c, reply, replylen); @@ -5047,10 +5549,14 @@ } ssh2_pkt_addbyte(pktout, ssh_ttymodes[i].opcode); ssh2_pkt_addbyte(pktout, arg); } +int ssh_agent_forwarding_permitted(Ssh ssh) +{ + return conf_get_int(ssh->conf, CONF_agentfwd) && agent_exists(); +} static void do_ssh1_connection(Ssh ssh, unsigned char *in, int inlen, struct Packet *pktin) { crBegin(ssh->do_ssh1_connection_crstate); @@ -5067,11 +5573,11 @@ ssh->packet_dispatch[SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION] = ssh1_msg_channel_close; ssh->packet_dispatch[SSH1_MSG_CHANNEL_DATA] = ssh1_msg_channel_data; ssh->packet_dispatch[SSH1_SMSG_EXIT_STATUS] = ssh1_smsg_exit_status; - if (ssh->cfg.agentfwd && agent_exists()) { + if (ssh_agent_forwarding_permitted(ssh)) { logevent("Requesting agent forwarding"); send_packet(ssh, SSH1_CMSG_AGENT_REQUEST_FORWARDING, PKT_END); do { crReturnV; } while (!pktin); @@ -5086,71 +5592,70 @@ ssh->agentfwd_enabled = TRUE; ssh->packet_dispatch[SSH1_SMSG_AGENT_OPEN] = ssh1_smsg_agent_open; } } - if (ssh->cfg.x11_forward && - (ssh->x11disp = x11_setup_display(ssh->cfg.x11_display, - ssh->cfg.x11_auth, &ssh->cfg))) { - logevent("Requesting X11 forwarding"); - /* - * Note that while we blank the X authentication data here, we don't - * take any special action to blank the start of an X11 channel, - * so using MIT-MAGIC-COOKIE-1 and actually opening an X connection - * without having session blanking enabled is likely to leak your - * cookie into the log. - */ - if (ssh->v1_local_protoflags & SSH1_PROTOFLAG_SCREEN_NUMBER) { - send_packet(ssh, SSH1_CMSG_X11_REQUEST_FORWARDING, - PKT_STR, ssh->x11disp->remoteauthprotoname, - PKTT_PASSWORD, - PKT_STR, ssh->x11disp->remoteauthdatastring, - PKTT_OTHER, - PKT_INT, ssh->x11disp->screennum, - PKT_END); - } else { - send_packet(ssh, SSH1_CMSG_X11_REQUEST_FORWARDING, - PKT_STR, ssh->x11disp->remoteauthprotoname, - PKTT_PASSWORD, - PKT_STR, ssh->x11disp->remoteauthdatastring, - PKTT_OTHER, - PKT_END); - } - do { - crReturnV; - } while (!pktin); - if (pktin->type != SSH1_SMSG_SUCCESS - && pktin->type != SSH1_SMSG_FAILURE) { - bombout(("Protocol confusion")); - crStopV; - } else if (pktin->type == SSH1_SMSG_FAILURE) { - logevent("X11 forwarding refused"); - } else { - logevent("X11 forwarding enabled"); - ssh->X11_fwd_enabled = TRUE; - ssh->packet_dispatch[SSH1_SMSG_X11_OPEN] = ssh1_smsg_x11_open; - } - } - - ssh_setup_portfwd(ssh, &ssh->cfg); + if (conf_get_int(ssh->conf, CONF_x11_forward)) { + ssh->x11disp = + x11_setup_display(conf_get_str(ssh->conf, CONF_x11_display), + ssh->conf); + if (!ssh->x11disp) { + /* FIXME: return an error message from x11_setup_display */ + logevent("X11 forwarding not enabled: unable to" + " initialise X display"); + } else { + ssh->x11auth = x11_invent_fake_auth + (ssh->x11authtree, conf_get_int(ssh->conf, CONF_x11_auth)); + ssh->x11auth->disp = ssh->x11disp; + + logevent("Requesting X11 forwarding"); + if (ssh->v1_local_protoflags & SSH1_PROTOFLAG_SCREEN_NUMBER) { + send_packet(ssh, SSH1_CMSG_X11_REQUEST_FORWARDING, + PKT_STR, ssh->x11auth->protoname, + PKT_STR, ssh->x11auth->datastring, + PKT_INT, ssh->x11disp->screennum, + PKT_END); + } else { + send_packet(ssh, SSH1_CMSG_X11_REQUEST_FORWARDING, + PKT_STR, ssh->x11auth->protoname, + PKT_STR, ssh->x11auth->datastring, + PKT_END); + } + do { + crReturnV; + } while (!pktin); + if (pktin->type != SSH1_SMSG_SUCCESS + && pktin->type != SSH1_SMSG_FAILURE) { + bombout(("Protocol confusion")); + crStopV; + } else if (pktin->type == SSH1_SMSG_FAILURE) { + logevent("X11 forwarding refused"); + } else { + logevent("X11 forwarding enabled"); + ssh->X11_fwd_enabled = TRUE; + ssh->packet_dispatch[SSH1_SMSG_X11_OPEN] = ssh1_smsg_x11_open; + } + } + } + + ssh_setup_portfwd(ssh, ssh->conf); ssh->packet_dispatch[SSH1_MSG_PORT_OPEN] = ssh1_msg_port_open; - if (!ssh->cfg.nopty) { + if (!conf_get_int(ssh->conf, CONF_nopty)) { struct Packet *pkt; /* Unpick the terminal-speed string. */ /* XXX perhaps we should allow no speeds to be sent. */ ssh->ospeed = 38400; ssh->ispeed = 38400; /* last-resort defaults */ - sscanf(ssh->cfg.termspeed, "%d,%d", &ssh->ospeed, &ssh->ispeed); + sscanf(conf_get_str(ssh->conf, CONF_termspeed), "%d,%d", &ssh->ospeed, &ssh->ispeed); /* Send the pty request. */ pkt = ssh1_pkt_init(SSH1_CMSG_REQUEST_PTY); - ssh_pkt_addstring(pkt, ssh->cfg.termtype); + ssh_pkt_addstring(pkt, conf_get_str(ssh->conf, CONF_termtype)); ssh_pkt_adduint32(pkt, ssh->term_height); ssh_pkt_adduint32(pkt, ssh->term_width); ssh_pkt_adduint32(pkt, 0); /* width in pixels */ ssh_pkt_adduint32(pkt, 0); /* height in pixels */ - parse_ttymodes(ssh, ssh->cfg.ttymodes, - ssh1_send_ttymode, (void *)pkt); + parse_ttymodes(ssh, ssh1_send_ttymode, (void *)pkt); ssh_pkt_addbyte(pkt, SSH1_TTY_OP_ISPEED); ssh_pkt_adduint32(pkt, ssh->ispeed); ssh_pkt_addbyte(pkt, SSH1_TTY_OP_OSPEED); ssh_pkt_adduint32(pkt, ssh->ospeed); ssh_pkt_addbyte(pkt, SSH_TTY_OP_END); @@ -5164,18 +5669,20 @@ bombout(("Protocol confusion")); crStopV; } else if (pktin->type == SSH1_SMSG_FAILURE) { c_write_str(ssh, "Server refused to allocate pty\r\n"); ssh->editing = ssh->echoing = 1; - } - logeventf(ssh, "Allocated pty (ospeed %dbps, ispeed %dbps)", - ssh->ospeed, ssh->ispeed); + } else { + logeventf(ssh, "Allocated pty (ospeed %dbps, ispeed %dbps)", + ssh->ospeed, ssh->ispeed); + ssh->got_pty = TRUE; + } } else { ssh->editing = ssh->echoing = 1; } - if (ssh->cfg.compression) { + if (conf_get_int(ssh->conf, CONF_compression)) { send_packet(ssh, SSH1_CMSG_REQUEST_COMPRESSION, PKT_INT, 6, PKT_END); do { crReturnV; } while (!pktin); if (pktin->type != SSH1_SMSG_SUCCESS @@ -5199,16 +5706,15 @@ * Special case: if the first-choice command is an SSH-2 * subsystem (hence not usable here) and the second choice * exists, we fall straight back to that. */ { - char *cmd = ssh->cfg.remote_cmd_ptr; - - if (!cmd) cmd = ssh->cfg.remote_cmd; + char *cmd = conf_get_str(ssh->conf, CONF_remote_cmd); - if (ssh->cfg.ssh_subsys && ssh->cfg.remote_cmd_ptr2) { - cmd = ssh->cfg.remote_cmd_ptr2; + if (conf_get_int(ssh->conf, CONF_ssh_subsys) && + conf_get_str(ssh->conf, CONF_remote_cmd2)) { + cmd = conf_get_str(ssh->conf, CONF_remote_cmd2); ssh->fallback_cmd = TRUE; } if (*cmd) send_packet(ssh, SSH1_CMSG_EXEC_CMD, PKT_STR, cmd, PKT_END); else @@ -5247,12 +5753,12 @@ } } else { while (inlen > 0) { int len = min(inlen, 512); send_packet(ssh, SSH1_CMSG_STDIN_DATA, - PKT_INT, len, PKTT_DATA, PKT_DATA, in, len, - PKTT_OTHER, PKT_END); + PKT_INT, len, PKT_DATA, in, len, + PKT_END); in += len; inlen -= len; } } } @@ -5409,15 +5915,16 @@ } /* * Handle the SSH-2 transport layer. */ -static int do_ssh2_transport(Ssh ssh, void *vin, int inlen, +static void do_ssh2_transport(Ssh ssh, void *vin, int inlen, struct Packet *pktin) { unsigned char *in = (unsigned char *)vin; struct do_ssh2_transport_state { + int crLine; int nbits, pbits, warn_kex, warn_cscipher, warn_sccipher; Bignum p, g, e, f, K; void *our_kexinit; int our_kexinitlen; int kex_init_value, kex_reply_value; @@ -5447,11 +5954,13 @@ int guessok; int ignorepkt; }; crState(do_ssh2_transport_state); - crBegin(ssh->do_ssh2_transport_crstate); + assert(!ssh->bare_connection); + + crBeginState; s->cscipher_tobe = s->sccipher_tobe = NULL; s->csmac_tobe = s->scmac_tobe = NULL; s->cscomp_tobe = s->sccomp_tobe = NULL; @@ -5468,18 +5977,18 @@ s->maclist = macs, s->nmacs = lenof(macs); begin_key_exchange: ssh->pkt_kctx = SSH2_PKTCTX_NOKEX; { - int i, j, commalist_started; + int i, j, k, commalist_started; /* * Set up the preferred key exchange. (NULL => warn below here) */ s->n_preferred_kex = 0; for (i = 0; i < KEX_MAX; i++) { - switch (ssh->cfg.ssh_kexlist[i]) { + switch (conf_get_int_int(ssh->conf, CONF_ssh_kexlist, i)) { case KEX_DHGEX: s->preferred_kex[s->n_preferred_kex++] = &ssh_diffiehellman_gex; break; case KEX_DHGROUP14: @@ -5507,16 +6016,16 @@ /* * Set up the preferred ciphers. (NULL => warn below here) */ s->n_preferred_ciphers = 0; for (i = 0; i < CIPHER_MAX; i++) { - switch (ssh->cfg.ssh_cipherlist[i]) { + switch (conf_get_int_int(ssh->conf, CONF_ssh_cipherlist, i)) { case CIPHER_BLOWFISH: s->preferred_ciphers[s->n_preferred_ciphers++] = &ssh2_blowfish; break; case CIPHER_DES: - if (ssh->cfg.ssh2_des_cbc) { + if (conf_get_int(ssh->conf, CONF_ssh2_des_cbc)) { s->preferred_ciphers[s->n_preferred_ciphers++] = &ssh2_des; } break; case CIPHER_3DES: s->preferred_ciphers[s->n_preferred_ciphers++] = &ssh2_3des; @@ -5538,11 +6047,11 @@ } /* * Set up preferred compression. */ - if (ssh->cfg.compression) + if (conf_get_int(ssh->conf, CONF_compression)) s->preferred_comp = &ssh_zlib; else s->preferred_comp = &ssh_comp_none; /* @@ -5574,55 +6083,55 @@ ssh2_pkt_addstring_str(s->pktout, k->list[j]->name); commalist_started = 1; } } /* List server host key algorithms. */ - ssh2_pkt_addstring_start(s->pktout); - for (i = 0; i < lenof(hostkey_algs); i++) { - ssh2_pkt_addstring_str(s->pktout, hostkey_algs[i]->name); - if (i < lenof(hostkey_algs) - 1) - ssh2_pkt_addstring_str(s->pktout, ","); - } - /* List client->server encryption algorithms. */ - ssh2_pkt_addstring_start(s->pktout); - commalist_started = 0; - for (i = 0; i < s->n_preferred_ciphers; i++) { - const struct ssh2_ciphers *c = s->preferred_ciphers[i]; - if (!c) continue; /* warning flag */ - for (j = 0; j < c->nciphers; j++) { - if (commalist_started) - ssh2_pkt_addstring_str(s->pktout, ","); - ssh2_pkt_addstring_str(s->pktout, c->list[j]->name); - commalist_started = 1; - } - } - /* List server->client encryption algorithms. */ - ssh2_pkt_addstring_start(s->pktout); - commalist_started = 0; - for (i = 0; i < s->n_preferred_ciphers; i++) { - const struct ssh2_ciphers *c = s->preferred_ciphers[i]; - if (!c) continue; /* warning flag */ - for (j = 0; j < c->nciphers; j++) { - if (commalist_started) - ssh2_pkt_addstring_str(s->pktout, ","); - ssh2_pkt_addstring_str(s->pktout, c->list[j]->name); - commalist_started = 1; - } - } - /* List client->server MAC algorithms. */ - ssh2_pkt_addstring_start(s->pktout); - for (i = 0; i < s->nmacs; i++) { - ssh2_pkt_addstring_str(s->pktout, s->maclist[i]->name); - if (i < s->nmacs - 1) - ssh2_pkt_addstring_str(s->pktout, ","); - } - /* List server->client MAC algorithms. */ - ssh2_pkt_addstring_start(s->pktout); - for (i = 0; i < s->nmacs; i++) { - ssh2_pkt_addstring_str(s->pktout, s->maclist[i]->name); - if (i < s->nmacs - 1) - ssh2_pkt_addstring_str(s->pktout, ","); + if (!s->got_session_id) { + /* + * In the first key exchange, we list all the algorithms + * we're prepared to cope with. + */ + ssh2_pkt_addstring_start(s->pktout); + for (i = 0; i < lenof(hostkey_algs); i++) { + ssh2_pkt_addstring_str(s->pktout, hostkey_algs[i]->name); + if (i < lenof(hostkey_algs) - 1) + ssh2_pkt_addstring_str(s->pktout, ","); + } + } else { + /* + * In subsequent key exchanges, we list only the kex + * algorithm that was selected in the first key exchange, + * so that we keep getting the same host key and hence + * don't have to interrupt the user's session to ask for + * reverification. + */ + assert(ssh->kex); + ssh2_pkt_addstring(s->pktout, ssh->hostkey->name); + } + /* List encryption algorithms (client->server then server->client). */ + for (k = 0; k < 2; k++) { + ssh2_pkt_addstring_start(s->pktout); + commalist_started = 0; + for (i = 0; i < s->n_preferred_ciphers; i++) { + const struct ssh2_ciphers *c = s->preferred_ciphers[i]; + if (!c) continue; /* warning flag */ + for (j = 0; j < c->nciphers; j++) { + if (commalist_started) + ssh2_pkt_addstring_str(s->pktout, ","); + ssh2_pkt_addstring_str(s->pktout, c->list[j]->name); + commalist_started = 1; + } + } + } + /* List MAC algorithms (client->server then server->client). */ + for (j = 0; j < 2; j++) { + ssh2_pkt_addstring_start(s->pktout); + for (i = 0; i < s->nmacs; i++) { + ssh2_pkt_addstring_str(s->pktout, s->maclist[i]->name); + if (i < s->nmacs - 1) + ssh2_pkt_addstring_str(s->pktout, ","); + } } /* List client->server compression algorithms, * then server->client compression algorithms. (We use the * same set twice.) */ for (j = 0; j < 2; j++) { @@ -5665,11 +6174,11 @@ memcpy(s->our_kexinit, s->pktout->data + 5, s->our_kexinitlen); ssh2_pkt_send_noqueue(ssh, s->pktout); if (!pktin) - crWaitUntil(pktin); + crWaitUntilV(pktin); /* * Now examine the other side's KEXINIT to see what we're up * to. */ @@ -5677,11 +6186,11 @@ char *str, *preferred; int i, j, len; if (pktin->type != SSH2_MSG_KEXINIT) { bombout(("expected key exchange packet from server")); - crStop(0); + crStopV; } ssh->kex = NULL; ssh->hostkey = NULL; s->cscipher_tobe = NULL; s->sccipher_tobe = NULL; @@ -5712,11 +6221,11 @@ break; } if (!ssh->kex) { bombout(("Couldn't agree a key exchange algorithm (available: %s)", str ? str : "(null)")); - crStop(0); + crStopV; } /* * Note that the server's guess is considered wrong if it doesn't match * the first algorithm in our list, even if it's still the algorithm * we end up using. @@ -5727,10 +6236,16 @@ if (in_commasep_string(hostkey_algs[i]->name, str, len)) { ssh->hostkey = hostkey_algs[i]; break; } } + if (!ssh->hostkey) { + bombout(("Couldn't agree a host key algorithm (available: %s)", + str ? str : "(null)")); + crStopV; + } + s->guessok = s->guessok && first_in_commasep_string(hostkey_algs[0]->name, str, len); ssh_pkt_getstring(pktin, &str, &len); /* client->server cipher */ for (i = 0; i < s->n_preferred_ciphers; i++) { const struct ssh2_ciphers *c = s->preferred_ciphers[i]; @@ -5748,11 +6263,11 @@ break; } if (!s->cscipher_tobe) { bombout(("Couldn't agree a client-to-server cipher (available: %s)", str ? str : "(null)")); - crStop(0); + crStopV; } ssh_pkt_getstring(pktin, &str, &len); /* server->client cipher */ for (i = 0; i < s->n_preferred_ciphers; i++) { const struct ssh2_ciphers *c = s->preferred_ciphers[i]; @@ -5770,11 +6285,11 @@ break; } if (!s->sccipher_tobe) { bombout(("Couldn't agree a server-to-client cipher (available: %s)", str ? str : "(null)")); - crStop(0); + crStopV; } ssh_pkt_getstring(pktin, &str, &len); /* client->server mac */ for (i = 0; i < s->nmacs; i++) { if (in_commasep_string(s->maclist[i]->name, str, len)) { @@ -5827,31 +6342,41 @@ } ssh_pkt_getstring(pktin, &str, &len); /* client->server language */ ssh_pkt_getstring(pktin, &str, &len); /* server->client language */ s->ignorepkt = ssh2_pkt_getbool(pktin) && !s->guessok; + ssh->exhash = ssh->kex->hash->init(); + hash_string(ssh->kex->hash, ssh->exhash, ssh->v_c, strlen(ssh->v_c)); + hash_string(ssh->kex->hash, ssh->exhash, ssh->v_s, strlen(ssh->v_s)); + hash_string(ssh->kex->hash, ssh->exhash, + s->our_kexinit, s->our_kexinitlen); + sfree(s->our_kexinit); + /* Include the type byte in the hash of server's KEXINIT */ + hash_string(ssh->kex->hash, ssh->exhash, + pktin->body - 1, pktin->length + 1); + if (s->warn_kex) { ssh_set_frozen(ssh, 1); s->dlgret = askalg(ssh->frontend, "key-exchange algorithm", ssh->kex->name, ssh_dialog_callback, ssh); if (s->dlgret < 0) { do { - crReturn(0); + crReturnV; if (pktin) { bombout(("Unexpected data from server while" " waiting for user response")); - crStop(0); + crStopV; } } while (pktin || inlen > 0); s->dlgret = ssh->user_response; } ssh_set_frozen(ssh, 0); if (s->dlgret == 0) { ssh_disconnect(ssh, "User aborted at kex warning", NULL, 0, TRUE); - crStop(0); + crStopV; } } if (s->warn_cscipher) { ssh_set_frozen(ssh, 1); @@ -5859,24 +6384,24 @@ "client-to-server cipher", s->cscipher_tobe->name, ssh_dialog_callback, ssh); if (s->dlgret < 0) { do { - crReturn(0); + crReturnV; if (pktin) { bombout(("Unexpected data from server while" " waiting for user response")); - crStop(0); + crStopV; } } while (pktin || inlen > 0); s->dlgret = ssh->user_response; } ssh_set_frozen(ssh, 0); if (s->dlgret == 0) { ssh_disconnect(ssh, "User aborted at cipher warning", NULL, 0, TRUE); - crStop(0); + crStopV; } } if (s->warn_sccipher) { ssh_set_frozen(ssh, 1); @@ -5884,39 +6409,29 @@ "server-to-client cipher", s->sccipher_tobe->name, ssh_dialog_callback, ssh); if (s->dlgret < 0) { do { - crReturn(0); + crReturnV; if (pktin) { bombout(("Unexpected data from server while" " waiting for user response")); - crStop(0); + crStopV; } } while (pktin || inlen > 0); s->dlgret = ssh->user_response; } ssh_set_frozen(ssh, 0); if (s->dlgret == 0) { ssh_disconnect(ssh, "User aborted at cipher warning", NULL, 0, TRUE); - crStop(0); + crStopV; } } - ssh->exhash = ssh->kex->hash->init(); - hash_string(ssh->kex->hash, ssh->exhash, ssh->v_c, strlen(ssh->v_c)); - hash_string(ssh->kex->hash, ssh->exhash, ssh->v_s, strlen(ssh->v_s)); - hash_string(ssh->kex->hash, ssh->exhash, - s->our_kexinit, s->our_kexinitlen); - sfree(s->our_kexinit); - if (pktin->length > 5) - hash_string(ssh->kex->hash, ssh->exhash, - pktin->data + 5, pktin->length - 5); - if (s->ignorepkt) /* first_kex_packet_follows */ - crWaitUntil(pktin); /* Ignore packet */ + crWaitUntilV(pktin); /* Ignore packet */ } if (ssh->kex->main_type == KEXTYPE_DH) { /* * Work out the number of bits of key we will need from the @@ -5949,20 +6464,20 @@ s->pbits = 512 << ((s->nbits - 1) / 64); s->pktout = ssh2_pkt_init(SSH2_MSG_KEX_DH_GEX_REQUEST); ssh2_pkt_adduint32(s->pktout, s->pbits); ssh2_pkt_send_noqueue(ssh, s->pktout); - crWaitUntil(pktin); + crWaitUntilV(pktin); if (pktin->type != SSH2_MSG_KEX_DH_GEX_GROUP) { bombout(("expected key exchange group packet from server")); - crStop(0); + crStopV; } s->p = ssh2_pkt_getmp(pktin); s->g = ssh2_pkt_getmp(pktin); if (!s->p || !s->g) { bombout(("unable to read mp-ints from incoming group packet")); - crStop(0); + crStopV; } ssh->kex_ctx = dh_setup_gex(s->p, s->g); s->kex_init_value = SSH2_MSG_KEX_DH_GEX_INIT; s->kex_reply_value = SSH2_MSG_KEX_DH_GEX_REPLY; } else { @@ -5984,22 +6499,22 @@ s->pktout = ssh2_pkt_init(s->kex_init_value); ssh2_pkt_addmp(s->pktout, s->e); ssh2_pkt_send_noqueue(ssh, s->pktout); set_busy_status(ssh->frontend, BUSY_WAITING); /* wait for server */ - crWaitUntil(pktin); + crWaitUntilV(pktin); if (pktin->type != s->kex_reply_value) { bombout(("expected key exchange reply packet from server")); - crStop(0); + crStopV; } set_busy_status(ssh->frontend, BUSY_CPU); /* cogitate */ ssh_pkt_getstring(pktin, &s->hostkeydata, &s->hostkeylen); s->hkey = ssh->hostkey->newkey(s->hostkeydata, s->hostkeylen); s->f = ssh2_pkt_getmp(pktin); if (!s->f) { bombout(("unable to parse key exchange reply packet")); - crStop(0); + crStopV; } ssh_pkt_getstring(pktin, &s->sigdata, &s->siglen); s->K = dh_find_K(ssh->kex_ctx, s->f); @@ -6028,14 +6543,14 @@ ssh->pkt_kctx = SSH2_PKTCTX_RSAKEX; /* * RSA key exchange. First expect a KEXRSA_PUBKEY packet * from the server. */ - crWaitUntil(pktin); + crWaitUntilV(pktin); if (pktin->type != SSH2_MSG_KEXRSA_PUBKEY) { bombout(("expected RSA public key packet from server")); - crStop(0); + crStopV; } ssh_pkt_getstring(pktin, &s->hostkeydata, &s->hostkeylen); hash_string(ssh->kex->hash, ssh->exhash, s->hostkeydata, s->hostkeylen); @@ -6050,11 +6565,11 @@ s->rsakey = ssh_rsakex_newkey(s->rsakeydata, s->rsakeylen); if (!s->rsakey) { sfree(s->rsakeydata); bombout(("unable to parse RSA public key from server")); - crStop(0); + crStopV; } hash_string(ssh->kex->hash, ssh->exhash, s->rsakeydata, s->rsakeylen); /* @@ -6110,15 +6625,15 @@ sfree(outstr); } ssh_rsakex_freekey(s->rsakey); - crWaitUntil(pktin); + crWaitUntilV(pktin); if (pktin->type != SSH2_MSG_KEXRSA_DONE) { sfree(s->rsakeydata); bombout(("expected signature packet from server")); - crStop(0); + crStopV; } ssh_pkt_getstring(pktin, &s->sigdata, &s->siglen); sfree(s->rsakeydata); @@ -6138,48 +6653,64 @@ if (!s->hkey || !ssh->hostkey->verifysig(s->hkey, s->sigdata, s->siglen, (char *)s->exchange_hash, ssh->kex->hash->hlen)) { bombout(("Server's host key did not match the signature supplied")); - crStop(0); + crStopV; } - /* - * Authenticate remote host: verify host key. (We've already - * checked the signature of the exchange hash.) - */ s->keystr = ssh->hostkey->fmtkey(s->hkey); - s->fingerprint = ssh->hostkey->fingerprint(s->hkey); - ssh_set_frozen(ssh, 1); - s->dlgret = verify_ssh_host_key(ssh->frontend, - ssh->savedhost, ssh->savedport, - ssh->hostkey->keytype, s->keystr, - s->fingerprint, - ssh_dialog_callback, ssh); - if (s->dlgret < 0) { - do { - crReturn(0); - if (pktin) { - bombout(("Unexpected data from server while waiting" - " for user host key response")); - crStop(0); - } - } while (pktin || inlen > 0); - s->dlgret = ssh->user_response; - } - ssh_set_frozen(ssh, 0); - if (s->dlgret == 0) { - ssh_disconnect(ssh, "User aborted at host key verification", NULL, - 0, TRUE); - crStop(0); - } - if (!s->got_session_id) { /* don't bother logging this in rekeys */ - logevent("Host key fingerprint is:"); - logevent(s->fingerprint); - } - sfree(s->fingerprint); - sfree(s->keystr); + if (!s->got_session_id) { + /* + * Authenticate remote host: verify host key. (We've already + * checked the signature of the exchange hash.) + */ + s->fingerprint = ssh->hostkey->fingerprint(s->hkey); + ssh_set_frozen(ssh, 1); + s->dlgret = verify_ssh_host_key(ssh->frontend, + ssh->savedhost, ssh->savedport, + ssh->hostkey->keytype, s->keystr, + s->fingerprint, + ssh_dialog_callback, ssh); + if (s->dlgret < 0) { + do { + crReturnV; + if (pktin) { + bombout(("Unexpected data from server while waiting" + " for user host key response")); + crStopV; + } + } while (pktin || inlen > 0); + s->dlgret = ssh->user_response; + } + ssh_set_frozen(ssh, 0); + if (s->dlgret == 0) { + ssh_disconnect(ssh, "User aborted at host key verification", NULL, + 0, TRUE); + crStopV; + } + logevent("Host key fingerprint is:"); + logevent(s->fingerprint); + sfree(s->fingerprint); + /* + * Save this host key, to check against the one presented in + * subsequent rekeys. + */ + ssh->hostkey_str = s->keystr; + } else { + /* + * In a rekey, we never present an interactive host key + * verification request to the user. Instead, we simply + * enforce that the key we're seeing this time is identical to + * the one we saw before. + */ + if (strcmp(ssh->hostkey_str, s->keystr)) { + bombout(("Host key was different in repeat key exchange")); + crStopV; + } + sfree(s->keystr); + } ssh->hostkey->freekey(s->hkey); /* * The exchange hash from the very first key exchange is also * the session id, used in session key construction and @@ -6237,11 +6768,11 @@ ssh->cscipher->setiv(ssh->cs_cipher_ctx, keyspace); ssh2_mkkey(ssh,s->K,s->exchange_hash,'E',keyspace); assert(ssh->csmac->len <= ssh->kex->hash->hlen * SSH2_MKKEY_ITERS); ssh->csmac->setkey(ssh->cs_mac_ctx, keyspace); - memset(keyspace, 0, sizeof(keyspace)); + smemclr(keyspace, sizeof(keyspace)); } logeventf(ssh, "Initialised %.200s client->server encryption", ssh->cscipher->text_name); logeventf(ssh, "Initialised %.200s client->server MAC algorithm", @@ -6258,14 +6789,14 @@ ssh2_pkt_queuesend(ssh); /* * Expect SSH2_MSG_NEWKEYS from server. */ - crWaitUntil(pktin); + crWaitUntilV(pktin); if (pktin->type != SSH2_MSG_NEWKEYS) { bombout(("expected new-keys packet from server")); - crStop(0); + crStopV; } ssh->incoming_data_size = 0; /* start counting from here */ /* * We've seen server NEWKEYS, so create and initialise @@ -6303,11 +6834,11 @@ ssh->sccipher->setiv(ssh->sc_cipher_ctx, keyspace); ssh2_mkkey(ssh,s->K,s->exchange_hash,'F',keyspace); assert(ssh->scmac->len <= ssh->kex->hash->hlen * SSH2_MKKEY_ITERS); ssh->scmac->setkey(ssh->sc_mac_ctx, keyspace); - memset(keyspace, 0, sizeof(keyspace)); + smemclr(keyspace, sizeof(keyspace)); } logeventf(ssh, "Initialised %.200s server->client encryption", ssh->sccipher->text_name); logeventf(ssh, "Initialised %.200s server->client MAC algorithm", ssh->scmac->text_name); @@ -6334,27 +6865,14 @@ /* * Otherwise, schedule a timer for our next rekey. */ ssh->kex_in_progress = FALSE; ssh->last_rekey = GETTICKCOUNT(); - if (ssh->cfg.ssh_rekey_time != 0) - ssh->next_rekey = schedule_timer(ssh->cfg.ssh_rekey_time*60*TICKSPERSEC, + if (conf_get_int(ssh->conf, CONF_ssh_rekey_time) != 0) + ssh->next_rekey = schedule_timer(conf_get_int(ssh->conf, CONF_ssh_rekey_time)*60*TICKSPERSEC, ssh2_timer, ssh); - /* - * If this is the first key exchange phase, we must pass the - * SSH2_MSG_NEWKEYS packet to the next layer, not because it - * wants to see it but because it will need time to initialise - * itself before it sees an actual packet. In subsequent key - * exchange phases, we don't pass SSH2_MSG_NEWKEYS on, because - * it would only confuse the layer above. - */ - if (s->activated_authconn) { - crReturn(0); - } - s->activated_authconn = TRUE; - /* * Now we're encrypting. Begin returning 1 to the protocol main * function so that other things can run on top of the * transport. If we ever see a KEXINIT, we must go back to the * start. @@ -6369,11 +6887,18 @@ * we should consider rekeying (for delayed compression). */ while (!((pktin && pktin->type == SSH2_MSG_KEXINIT) || (!pktin && inlen < 0))) { wait_for_rekey: - crReturn(1); + if (!ssh->protocol_initial_phase_done) { + ssh->protocol_initial_phase_done = TRUE; + /* + * Allow authconn to initialise itself. + */ + do_ssh2_authconn(ssh, NULL, 0, NULL); + } + crReturnV; } if (pktin) { logevent("Server initiated key re-exchange"); } else { if (inlen == -2) { @@ -6416,23 +6941,23 @@ (char *)in); /* Reset the counters, so that at least this message doesn't * hit the event log _too_ often. */ ssh->outgoing_data_size = 0; ssh->incoming_data_size = 0; - if (ssh->cfg.ssh_rekey_time != 0) { + if (conf_get_int(ssh->conf, CONF_ssh_rekey_time) != 0) { ssh->next_rekey = - schedule_timer(ssh->cfg.ssh_rekey_time*60*TICKSPERSEC, + schedule_timer(conf_get_int(ssh->conf, CONF_ssh_rekey_time)*60*TICKSPERSEC, ssh2_timer, ssh); } goto wait_for_rekey; /* this is still utterly horrid */ } else { logeventf(ssh, "Initiating key re-exchange (%s)", (char *)in); } } goto begin_key_exchange; - crFinish(1); + crFinishV; } /* * Add data to an SSH-2 channel output buffer. */ @@ -6447,10 +6972,11 @@ */ static int ssh2_try_send(struct ssh_channel *c) { Ssh ssh = c->ssh; struct Packet *pktout; + int ret; while (c->v.v2.remwindow > 0 && bufchain_size(&c->v.v2.outbuffer) > 0) { int len; void *data; bufchain_prefix(&c->v.v2.outbuffer, &data, &len); @@ -6459,62 +6985,69 @@ if ((unsigned)len > c->v.v2.remmaxpkt) len = c->v.v2.remmaxpkt; pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_DATA); ssh2_pkt_adduint32(pktout, c->remoteid); ssh2_pkt_addstring_start(pktout); - dont_log_data(ssh, pktout, PKTLOG_OMIT); ssh2_pkt_addstring_data(pktout, data, len); - end_log_omission(ssh, pktout); ssh2_pkt_send(ssh, pktout); bufchain_consume(&c->v.v2.outbuffer, len); c->v.v2.remwindow -= len; } /* * After having sent as much data as we can, return the amount * still buffered. */ - return bufchain_size(&c->v.v2.outbuffer); + ret = bufchain_size(&c->v.v2.outbuffer); + + /* + * And if there's no data pending but we need to send an EOF, send + * it. + */ + if (!ret && c->pending_eof) + ssh_channel_try_eof(c); + + return ret; } static void ssh2_try_send_and_unthrottle(Ssh ssh, struct ssh_channel *c) { int bufsize; - if (c->closes) - return; /* don't send on closing channels */ + if (c->closes & CLOSES_SENT_EOF) + return; /* don't send on channels we've EOFed */ bufsize = ssh2_try_send(c); if (bufsize == 0) { switch (c->type) { case CHAN_MAINSESSION: /* stdin need not receive an unthrottle * notification since it will be polled */ break; case CHAN_X11: - x11_unthrottle(c->u.x11.s); + x11_unthrottle(c->u.x11.xconn); break; case CHAN_AGENT: /* agent sockets are request/response and need no * buffer management */ break; case CHAN_SOCKDATA: - pfd_unthrottle(c->u.pfd.s); + pfd_unthrottle(c->u.pfd.pf); break; } } +} +static int ssh_is_simple(Ssh ssh) +{ /* - * If we've emptied the channel's output buffer and there's a - * pending close event, start the channel-closing procedure. + * We use the 'simple' variant of the SSH protocol if we're asked + * to, except not if we're also doing connection-sharing (either + * tunnelling our packets over an upstream or expecting to be + * tunnelled over ourselves), since then the assumption that we + * have only one channel to worry about is not true after all. */ - if (c->pending_close && bufchain_size(&c->v.v2.outbuffer) == 0) { - struct Packet *pktout; - pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_CLOSE); - ssh2_pkt_adduint32(pktout, c->remoteid); - ssh2_pkt_send(ssh, pktout); - c->closes = 1; - c->pending_close = FALSE; - } + return (conf_get_int(ssh->conf, CONF_ssh_simple) && + !ssh->bare_connection && !ssh->connshare); } /* * Set up most of a new ssh_channel for SSH-2. */ @@ -6521,42 +7054,116 @@ static void ssh2_channel_init(struct ssh_channel *c) { Ssh ssh = c->ssh; c->localid = alloc_channel_id(ssh); c->closes = 0; - c->pending_close = FALSE; + c->pending_eof = FALSE; c->throttling_conn = FALSE; c->v.v2.locwindow = c->v.v2.locmaxwin = c->v.v2.remlocwin = - ssh->cfg.ssh_simple ? OUR_V2_BIGWIN : OUR_V2_WINSIZE; - c->v.v2.winadj_head = c->v.v2.winadj_tail = NULL; + ssh_is_simple(ssh) ? OUR_V2_BIGWIN : OUR_V2_WINSIZE; + c->v.v2.chanreq_head = NULL; c->v.v2.throttle_state = UNTHROTTLED; bufchain_init(&c->v.v2.outbuffer); } + +/* + * Construct the common parts of a CHANNEL_OPEN. + */ +static struct Packet *ssh2_chanopen_init(struct ssh_channel *c, char *type) +{ + struct Packet *pktout; + + pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_OPEN); + ssh2_pkt_addstring(pktout, type); + ssh2_pkt_adduint32(pktout, c->localid); + ssh2_pkt_adduint32(pktout, c->v.v2.locwindow);/* our window size */ + ssh2_pkt_adduint32(pktout, OUR_V2_MAXPKT); /* our max pkt size */ + return pktout; +} + +/* + * CHANNEL_FAILURE doesn't come with any indication of what message + * caused it, so we have to keep track of the outstanding + * CHANNEL_REQUESTs ourselves. + */ +static void ssh2_queue_chanreq_handler(struct ssh_channel *c, + cchandler_fn_t handler, void *ctx) +{ + struct outstanding_channel_request *ocr = + snew(struct outstanding_channel_request); + + assert(!(c->closes & (CLOSES_SENT_CLOSE | CLOSES_RCVD_CLOSE))); + ocr->handler = handler; + ocr->ctx = ctx; + ocr->next = NULL; + if (!c->v.v2.chanreq_head) + c->v.v2.chanreq_head = ocr; + else + c->v.v2.chanreq_tail->next = ocr; + c->v.v2.chanreq_tail = ocr; +} + +/* + * Construct the common parts of a CHANNEL_REQUEST. If handler is not + * NULL then a reply will be requested and the handler will be called + * when it arrives. The returned packet is ready to have any + * request-specific data added and be sent. Note that if a handler is + * provided, it's essential that the request actually be sent. + * + * The handler will usually be passed the response packet in pktin. + * If pktin is NULL, this means that no reply will ever be forthcoming + * (e.g. because the entire connection is being destroyed) and the + * handler should free any storage it's holding. + */ +static struct Packet *ssh2_chanreq_init(struct ssh_channel *c, char *type, + cchandler_fn_t handler, void *ctx) +{ + struct Packet *pktout; + + assert(!(c->closes & (CLOSES_SENT_CLOSE | CLOSES_RCVD_CLOSE))); + pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST); + ssh2_pkt_adduint32(pktout, c->remoteid); + ssh2_pkt_addstring(pktout, type); + ssh2_pkt_addbool(pktout, handler != NULL); + if (handler != NULL) + ssh2_queue_chanreq_handler(c, handler, ctx); + return pktout; +} /* * Potentially enlarge the window on an SSH-2 channel. */ +static void ssh2_handle_winadj_response(struct ssh_channel *, struct Packet *, + void *); static void ssh2_set_window(struct ssh_channel *c, int newwin) { Ssh ssh = c->ssh; /* - * Never send WINDOW_ADJUST for a channel that the remote side - * already thinks it's closed; there's no point, since it won't - * be sending any more data anyway. + * Never send WINDOW_ADJUST for a channel that the remote side has + * already sent EOF on; there's no point, since it won't be + * sending any more data anyway. Ditto if _we've_ already sent + * CLOSE. */ - if (c->closes != 0) + if (c->closes & (CLOSES_RCVD_EOF | CLOSES_SENT_CLOSE)) return; + /* + * Also, never widen the window for an X11 channel when we're + * still waiting to see its initial auth and may yet hand it off + * to a downstream. + */ + if (c->type == CHAN_X11 && c->u.x11.initial) + return; + /* * If the remote end has a habit of ignoring maxpkt, limit the * window so that it has no choice (assuming it doesn't ignore the * window as well). */ if ((ssh->remote_bugs & BUG_SSH2_MAXPKT) && newwin > OUR_V2_MAXPKT) newwin = OUR_V2_MAXPKT; - /* * Only send a WINDOW_ADJUST if there's significantly more window * available than the other end thinks there is. This saves us * sending a WINDOW_ADJUST for every character in a shell session. @@ -6563,11 +7170,11 @@ * * "Significant" is arbitrarily defined as half the window size. */ if (newwin / 2 >= c->v.v2.locwindow) { struct Packet *pktout; - struct winadj *wa; + unsigned *up; /* * In order to keep track of how much window the client * actually has available, we'd like it to acknowledge each * WINDOW_ADJUST. We can't do that directly, so we accompany @@ -6574,37 +7181,19 @@ * it with a CHANNEL_REQUEST that has to be acknowledged. * * This is only necessary if we're opening the window wide. * If we're not, then throughput is being constrained by * something other than the maximum window size anyway. - * - * We also only send this if the main channel has finished its - * initial CHANNEL_REQUESTs and installed the default - * CHANNEL_FAILURE handler, so as not to risk giving it - * unexpected CHANNEL_FAILUREs. */ if (newwin == c->v.v2.locmaxwin && - ssh->packet_dispatch[SSH2_MSG_CHANNEL_FAILURE]) { - pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST); - ssh2_pkt_adduint32(pktout, c->remoteid); - ssh2_pkt_addstring(pktout, "winadj@putty.projects.tartarus.org"); - ssh2_pkt_addbool(pktout, TRUE); + !(ssh->remote_bugs & BUG_CHOKES_ON_WINADJ)) { + up = snew(unsigned); + *up = newwin - c->v.v2.locwindow; + pktout = ssh2_chanreq_init(c, "winadj@putty.projects.tartarus.org", + ssh2_handle_winadj_response, up); ssh2_pkt_send(ssh, pktout); - /* - * CHANNEL_FAILURE doesn't come with any indication of - * what message caused it, so we have to keep track of the - * outstanding CHANNEL_REQUESTs ourselves. - */ - wa = snew(struct winadj); - wa->size = newwin - c->v.v2.locwindow; - wa->next = NULL; - if (!c->v.v2.winadj_head) - c->v.v2.winadj_head = wa; - else - c->v.v2.winadj_tail->next = wa; - c->v.v2.winadj_tail = wa; if (c->v.v2.throttle_state != UNTHROTTLED) c->v.v2.throttle_state = UNTHROTTLING; } else { /* Pretend the WINDOW_ADJUST was acked immediately. */ c->v.v2.remlocwin = newwin; @@ -6627,11 +7216,12 @@ unsigned localid = ssh_pkt_getuint32(pktin); struct ssh_channel *c; c = find234(ssh->channels, &localid, ssh_channelfind); if (!c || - (c->halfopen && pktin->type != SSH2_MSG_CHANNEL_OPEN_CONFIRMATION && + (c->type != CHAN_SHARING && c->halfopen && + pktin->type != SSH2_MSG_CHANNEL_OPEN_CONFIRMATION && pktin->type != SSH2_MSG_CHANNEL_OPEN_FAILURE)) { char *buf = dupprintf("Received %s for %s channel %u", ssh2_pkt_type(ssh->pkt_kctx, ssh->pkt_actx, pktin->type), c ? "half-open" : "nonexistent", localid); @@ -6640,79 +7230,73 @@ return NULL; } return c; } -static int ssh2_handle_winadj_response(struct ssh_channel *c) -{ - struct winadj *wa = c->v.v2.winadj_head; - if (!wa) - return FALSE; - c->v.v2.winadj_head = wa->next; - c->v.v2.remlocwin += wa->size; - sfree(wa); +static void ssh2_handle_winadj_response(struct ssh_channel *c, + struct Packet *pktin, void *ctx) +{ + unsigned *sizep = ctx; + + /* + * Winadj responses should always be failures. However, at least + * one server ("boks_sshd") is known to return SUCCESS for channel + * requests it's never heard of, such as "winadj@putty". Raised + * with foxt.com as bug 090916-090424, but for the sake of a quiet + * life, we don't worry about what kind of response we got. + */ + + c->v.v2.remlocwin += *sizep; + sfree(sizep); /* * winadj messages are only sent when the window is fully open, so * if we get an ack of one, we know any pending unthrottle is * complete. */ if (c->v.v2.throttle_state == UNTHROTTLING) c->v.v2.throttle_state = UNTHROTTLED; - return TRUE; -} - -static void ssh2_msg_channel_success(Ssh ssh, struct Packet *pktin) -{ - /* - * This should never get called. All channel requests are either - * sent with want_reply false, are sent before this handler gets - * installed, or are "winadj@putty" requests, which servers should - * never respond to with success. - * - * However, at least one server ("boks_sshd") is known to return - * SUCCESS for channel requests it's never heard of, such as - * "winadj@putty". Raised with foxt.com as bug 090916-090424, but - * for the sake of a quiet life, we handle it just the same as the - * expected FAILURE. - */ - struct ssh_channel *c; - - c = ssh2_channel_msg(ssh, pktin); - if (!c) - return; - if (!ssh2_handle_winadj_response(c)) - ssh_disconnect(ssh, NULL, - "Received unsolicited SSH_MSG_CHANNEL_SUCCESS", - SSH2_DISCONNECT_PROTOCOL_ERROR, FALSE); -} - -static void ssh2_msg_channel_failure(Ssh ssh, struct Packet *pktin) -{ - /* - * The only time this should get called is for "winadj@putty" - * messages sent above. All other channel requests are either - * sent with want_reply false or are sent before this handler gets - * installed. - */ - struct ssh_channel *c; - - c = ssh2_channel_msg(ssh, pktin); - if (!c) - return; - if (!ssh2_handle_winadj_response(c)) - ssh_disconnect(ssh, NULL, - "Received unsolicited SSH_MSG_CHANNEL_FAILURE", - SSH2_DISCONNECT_PROTOCOL_ERROR, FALSE); +} + +static void ssh2_msg_channel_response(Ssh ssh, struct Packet *pktin) +{ + struct ssh_channel *c = ssh2_channel_msg(ssh, pktin); + struct outstanding_channel_request *ocr; + + if (!c) return; + if (c->type == CHAN_SHARING) { + share_got_pkt_from_server(c->u.sharing.ctx, pktin->type, + pktin->body, pktin->length); + return; + } + ocr = c->v.v2.chanreq_head; + if (!ocr) { + ssh2_msg_unexpected(ssh, pktin); + return; + } + ocr->handler(c, pktin, ocr->ctx); + c->v.v2.chanreq_head = ocr->next; + sfree(ocr); + /* + * We may now initiate channel-closing procedures, if that + * CHANNEL_REQUEST was the last thing outstanding before we send + * CHANNEL_CLOSE. + */ + ssh2_channel_check_close(c); } static void ssh2_msg_channel_window_adjust(Ssh ssh, struct Packet *pktin) { struct ssh_channel *c; c = ssh2_channel_msg(ssh, pktin); if (!c) return; - if (!c->closes) { + if (c->type == CHAN_SHARING) { + share_got_pkt_from_server(c->u.sharing.ctx, pktin->type, + pktin->body, pktin->length); + return; + } + if (!(c->closes & CLOSES_SENT_EOF)) { c->v.v2.remwindow += ssh_pkt_getuint32(pktin); ssh2_try_send_and_unthrottle(ssh, c); } } @@ -6722,10 +7306,15 @@ int length; struct ssh_channel *c; c = ssh2_channel_msg(ssh, pktin); if (!c) return; + if (c->type == CHAN_SHARING) { + share_got_pkt_from_server(c->u.sharing.ctx, pktin->type, + pktin->body, pktin->length); + return; + } if (pktin->type == SSH2_MSG_CHANNEL_EXTENDED_DATA && ssh_pkt_getuint32(pktin) != SSH2_EXTENDED_DATA_STDERR) return; /* extended but not stderr */ ssh_pkt_getstring(pktin, &data, &length); if (data) { @@ -6738,14 +7327,14 @@ from_backend(ssh->frontend, pktin->type == SSH2_MSG_CHANNEL_EXTENDED_DATA, data, length); break; case CHAN_X11: - bufsize = x11_send(c->u.x11.s, data, length); + bufsize = x11_send(c->u.x11.xconn, data, length); break; case CHAN_SOCKDATA: - bufsize = pfd_send(c->u.pfd.s, data, length); + bufsize = pfd_send(c->u.pfd.pf, data, length); break; case CHAN_AGENT: while (length > 0) { if (c->u.a.lensofar < 4) { unsigned int l = min(4 - c->u.a.lensofar, @@ -6774,16 +7363,18 @@ c->u.a.lensofar += l; } if (c->u.a.lensofar == c->u.a.totallen) { void *reply; int replylen; + c->u.a.outstanding_requests++; if (agent_query(c->u.a.message, c->u.a.totallen, &reply, &replylen, ssh_agentf_callback, c)) ssh_agentf_callback(c, reply, replylen); sfree(c->u.a.message); + c->u.a.message = NULL; c->u.a.lensofar = 0; } } bufsize = 0; break; @@ -6808,130 +7399,307 @@ /* * If we're either buffering way too much data, or if we're * buffering anything at all and we're in "simple" mode, * throttle the whole channel. */ - if ((bufsize > c->v.v2.locmaxwin || - (ssh->cfg.ssh_simple && bufsize > 0)) && - !c->throttling_conn) { + if ((bufsize > c->v.v2.locmaxwin || (ssh_is_simple(ssh) && bufsize>0)) + && !c->throttling_conn) { c->throttling_conn = 1; ssh_throttle_conn(ssh, +1); } } } + +static void ssh_check_termination(Ssh ssh) +{ + if (ssh->version == 2 && + !conf_get_int(ssh->conf, CONF_ssh_no_shell) && + count234(ssh->channels) == 0 && + !(ssh->connshare && share_ndownstreams(ssh->connshare) > 0)) { + /* + * We used to send SSH_MSG_DISCONNECT here, because I'd + * believed that _every_ conforming SSH-2 connection had to + * end with a disconnect being sent by at least one side; + * apparently I was wrong and it's perfectly OK to + * unceremoniously slam the connection shut when you're done, + * and indeed OpenSSH feels this is more polite than sending a + * DISCONNECT. So now we don't. + */ + ssh_disconnect(ssh, "All channels closed", NULL, 0, TRUE); + } +} + +void ssh_sharing_downstream_connected(Ssh ssh, unsigned id) +{ + logeventf(ssh, "Connection sharing downstream #%u connected", id); +} + +void ssh_sharing_downstream_disconnected(Ssh ssh, unsigned id) +{ + logeventf(ssh, "Connection sharing downstream #%u disconnected", id); + ssh_check_termination(ssh); +} + +void ssh_sharing_logf(Ssh ssh, unsigned id, const char *logfmt, ...) +{ + va_list ap; + char *buf; + + va_start(ap, logfmt); + buf = dupvprintf(logfmt, ap); + va_end(ap); + if (id) + logeventf(ssh, "Connection sharing downstream #%u: %s", id, buf); + else + logeventf(ssh, "Connection sharing: %s", buf); + sfree(buf); +} + +static void ssh_channel_destroy(struct ssh_channel *c) +{ + Ssh ssh = c->ssh; + + switch (c->type) { + case CHAN_MAINSESSION: + ssh->mainchan = NULL; + update_specials_menu(ssh->frontend); + break; + case CHAN_X11: + if (c->u.x11.xconn != NULL) + x11_close(c->u.x11.xconn); + logevent("Forwarded X11 connection terminated"); + break; + case CHAN_AGENT: + sfree(c->u.a.message); + break; + case CHAN_SOCKDATA: + if (c->u.pfd.pf != NULL) + pfd_close(c->u.pfd.pf); + logevent("Forwarded port closed"); + break; + } + + del234(ssh->channels, c); + if (ssh->version == 2) { + bufchain_clear(&c->v.v2.outbuffer); + assert(c->v.v2.chanreq_head == NULL); + } + sfree(c); + + /* + * If that was the last channel left open, we might need to + * terminate. + */ + ssh_check_termination(ssh); +} + +static void ssh2_channel_check_close(struct ssh_channel *c) +{ + Ssh ssh = c->ssh; + struct Packet *pktout; + + if (c->halfopen) { + /* + * If we've sent out our own CHANNEL_OPEN but not yet seen + * either OPEN_CONFIRMATION or OPEN_FAILURE in response, then + * it's too early to be sending close messages of any kind. + */ + return; + } + + if ((!((CLOSES_SENT_EOF | CLOSES_RCVD_EOF) & ~c->closes) || + c->type == CHAN_ZOMBIE) && + !c->v.v2.chanreq_head && + !(c->closes & CLOSES_SENT_CLOSE)) { + /* + * We have both sent and received EOF (or the channel is a + * zombie), and we have no outstanding channel requests, which + * means the channel is in final wind-up. But we haven't sent + * CLOSE, so let's do so now. + */ + pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_CLOSE); + ssh2_pkt_adduint32(pktout, c->remoteid); + ssh2_pkt_send(ssh, pktout); + c->closes |= CLOSES_SENT_EOF | CLOSES_SENT_CLOSE; + } + + if (!((CLOSES_SENT_CLOSE | CLOSES_RCVD_CLOSE) & ~c->closes)) { + assert(c->v.v2.chanreq_head == NULL); + /* + * We have both sent and received CLOSE, which means we're + * completely done with the channel. + */ + ssh_channel_destroy(c); + } +} + +static void ssh2_channel_got_eof(struct ssh_channel *c) +{ + if (c->closes & CLOSES_RCVD_EOF) + return; /* already seen EOF */ + c->closes |= CLOSES_RCVD_EOF; + + if (c->type == CHAN_X11) { + x11_send_eof(c->u.x11.xconn); + } else if (c->type == CHAN_AGENT) { + if (c->u.a.outstanding_requests == 0) { + /* Manufacture an outgoing EOF in response to the incoming one. */ + sshfwd_write_eof(c); + } + } else if (c->type == CHAN_SOCKDATA) { + pfd_send_eof(c->u.pfd.pf); + } else if (c->type == CHAN_MAINSESSION) { + Ssh ssh = c->ssh; + + if (!ssh->sent_console_eof && + (from_backend_eof(ssh->frontend) || ssh->got_pty)) { + /* + * Either from_backend_eof told us that the front end + * wants us to close the outgoing side of the connection + * as soon as we see EOF from the far end, or else we've + * unilaterally decided to do that because we've allocated + * a remote pty and hence EOF isn't a particularly + * meaningful concept. + */ + sshfwd_write_eof(c); + } + ssh->sent_console_eof = TRUE; + } + + ssh2_channel_check_close(c); +} static void ssh2_msg_channel_eof(Ssh ssh, struct Packet *pktin) { struct ssh_channel *c; c = ssh2_channel_msg(ssh, pktin); if (!c) return; - - if (c->type == CHAN_X11) { - /* - * Remote EOF on an X11 channel means we should - * wrap up and close the channel ourselves. - */ - x11_close(c->u.x11.s); - c->u.x11.s = NULL; - sshfwd_close(c); - } else if (c->type == CHAN_AGENT) { - sshfwd_close(c); - } else if (c->type == CHAN_SOCKDATA) { - pfd_close(c->u.pfd.s); - c->u.pfd.s = NULL; - sshfwd_close(c); - } + if (c->type == CHAN_SHARING) { + share_got_pkt_from_server(c->u.sharing.ctx, pktin->type, + pktin->body, pktin->length); + return; + } + ssh2_channel_got_eof(c); } static void ssh2_msg_channel_close(Ssh ssh, struct Packet *pktin) { struct ssh_channel *c; - struct Packet *pktout; c = ssh2_channel_msg(ssh, pktin); if (!c) return; - /* Do pre-close processing on the channel. */ - switch (c->type) { - case CHAN_MAINSESSION: - ssh->mainchan = NULL; - update_specials_menu(ssh->frontend); - break; - case CHAN_X11: - if (c->u.x11.s != NULL) - x11_close(c->u.x11.s); - sshfwd_close(c); - break; - case CHAN_AGENT: - sshfwd_close(c); - break; - case CHAN_SOCKDATA: - if (c->u.pfd.s != NULL) - pfd_close(c->u.pfd.s); - sshfwd_close(c); - break; - } - if (c->closes == 0) { - pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_CLOSE); - ssh2_pkt_adduint32(pktout, c->remoteid); - ssh2_pkt_send(ssh, pktout); - } - del234(ssh->channels, c); - bufchain_clear(&c->v.v2.outbuffer); - sfree(c); - - /* - * See if that was the last channel left open. - * (This is only our termination condition if we're - * not running in -N mode.) - */ - if (!ssh->cfg.ssh_no_shell && count234(ssh->channels) == 0) { - /* - * We used to send SSH_MSG_DISCONNECT here, - * because I'd believed that _every_ conforming - * SSH-2 connection had to end with a disconnect - * being sent by at least one side; apparently - * I was wrong and it's perfectly OK to - * unceremoniously slam the connection shut - * when you're done, and indeed OpenSSH feels - * this is more polite than sending a - * DISCONNECT. So now we don't. - */ - ssh_disconnect(ssh, "All channels closed", NULL, 0, TRUE); + if (c->type == CHAN_SHARING) { + share_got_pkt_from_server(c->u.sharing.ctx, pktin->type, + pktin->body, pktin->length); + return; + } + + /* + * When we receive CLOSE on a channel, we assume it comes with an + * implied EOF if we haven't seen EOF yet. + */ + ssh2_channel_got_eof(c); + + /* + * And we also send an outgoing EOF, if we haven't already, on the + * assumption that CLOSE is a pretty forceful announcement that + * the remote side is doing away with the entire channel. (If it + * had wanted to send us EOF and continue receiving data from us, + * it would have just sent CHANNEL_EOF.) + */ + if (!(c->closes & CLOSES_SENT_EOF)) { + /* + * Make sure we don't read any more from whatever our local + * data source is for this channel. + */ + switch (c->type) { + case CHAN_MAINSESSION: + ssh->send_ok = 0; /* stop trying to read from stdin */ + break; + case CHAN_X11: + x11_override_throttle(c->u.x11.xconn, 1); + break; + case CHAN_SOCKDATA: + pfd_override_throttle(c->u.pfd.pf, 1); + break; + } + + /* + * Abandon any buffered data we still wanted to send to this + * channel. Receiving a CHANNEL_CLOSE is an indication that + * the server really wants to get on and _destroy_ this + * channel, and it isn't going to send us any further + * WINDOW_ADJUSTs to permit us to send pending stuff. + */ + bufchain_clear(&c->v.v2.outbuffer); + + /* + * Send outgoing EOF. + */ + sshfwd_write_eof(c); + } + + /* + * Now process the actual close. + */ + if (!(c->closes & CLOSES_RCVD_CLOSE)) { + c->closes |= CLOSES_RCVD_CLOSE; + ssh2_channel_check_close(c); } } static void ssh2_msg_channel_open_confirmation(Ssh ssh, struct Packet *pktin) { struct ssh_channel *c; - struct Packet *pktout; c = ssh2_channel_msg(ssh, pktin); if (!c) return; - if (c->type != CHAN_SOCKDATA_DORMANT) - return; /* dunno why they're confirming this */ + if (c->type == CHAN_SHARING) { + share_got_pkt_from_server(c->u.sharing.ctx, pktin->type, + pktin->body, pktin->length); + return; + } + assert(c->halfopen); /* ssh2_channel_msg will have enforced this */ c->remoteid = ssh_pkt_getuint32(pktin); c->halfopen = FALSE; - c->type = CHAN_SOCKDATA; c->v.v2.remwindow = ssh_pkt_getuint32(pktin); c->v.v2.remmaxpkt = ssh_pkt_getuint32(pktin); - if (c->u.pfd.s) - pfd_confirm(c->u.pfd.s); - if (c->closes) { - /* - * We have a pending close on this channel, - * which we decided on before the server acked - * the channel open. So now we know the - * remoteid, we can close it again. - */ - pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_CLOSE); - ssh2_pkt_adduint32(pktout, c->remoteid); - ssh2_pkt_send(ssh, pktout); - } + + if (c->type == CHAN_SOCKDATA_DORMANT) { + c->type = CHAN_SOCKDATA; + if (c->u.pfd.pf) + pfd_confirm(c->u.pfd.pf); + } else if (c->type == CHAN_ZOMBIE) { + /* + * This case can occur if a local socket error occurred + * between us sending out CHANNEL_OPEN and receiving + * OPEN_CONFIRMATION. In this case, all we can do is + * immediately initiate close proceedings now that we know the + * server's id to put in the close message. + */ + ssh2_channel_check_close(c); + } else { + /* + * We never expect to receive OPEN_CONFIRMATION for any + * *other* channel type (since only local-to-remote port + * forwardings cause us to send CHANNEL_OPEN after the main + * channel is live - all other auxiliary channel types are + * initiated from the server end). It's safe to enforce this + * by assertion rather than by ssh_disconnect, because the + * real point is that we never constructed a half-open channel + * structure in the first place with any type other than the + * above. + */ + assert(!"Funny channel type in ssh2_msg_channel_open_confirmation"); + } + + if (c->pending_eof) + ssh_channel_try_eof(c); /* in case we had a pending EOF */ } static void ssh2_msg_channel_open_failure(Ssh ssh, struct Packet *pktin) { static const char *const reasons[] = { @@ -6943,24 +7711,50 @@ }; unsigned reason_code; char *reason_string; int reason_length; struct ssh_channel *c; + c = ssh2_channel_msg(ssh, pktin); if (!c) return; - if (c->type != CHAN_SOCKDATA_DORMANT) - return; /* dunno why they're failing this */ - - reason_code = ssh_pkt_getuint32(pktin); - if (reason_code >= lenof(reasons)) - reason_code = 0; /* ensure reasons[reason_code] in range */ - ssh_pkt_getstring(pktin, &reason_string, &reason_length); - logeventf(ssh, "Forwarded connection refused by server: %s [%.*s]", - reasons[reason_code], reason_length, reason_string); - - pfd_close(c->u.pfd.s); + if (c->type == CHAN_SHARING) { + share_got_pkt_from_server(c->u.sharing.ctx, pktin->type, + pktin->body, pktin->length); + return; + } + assert(c->halfopen); /* ssh2_channel_msg will have enforced this */ + + if (c->type == CHAN_SOCKDATA_DORMANT) { + reason_code = ssh_pkt_getuint32(pktin); + if (reason_code >= lenof(reasons)) + reason_code = 0; /* ensure reasons[reason_code] in range */ + ssh_pkt_getstring(pktin, &reason_string, &reason_length); + logeventf(ssh, "Forwarded connection refused by server: %s [%.*s]", + reasons[reason_code], reason_length, reason_string); + + pfd_close(c->u.pfd.pf); + } else if (c->type == CHAN_ZOMBIE) { + /* + * This case can occur if a local socket error occurred + * between us sending out CHANNEL_OPEN and receiving + * OPEN_FAILURE. In this case, we need do nothing except allow + * the code below to throw the half-open channel away. + */ + } else { + /* + * We never expect to receive OPEN_FAILURE for any *other* + * channel type (since only local-to-remote port forwardings + * cause us to send CHANNEL_OPEN after the main channel is + * live - all other auxiliary channel types are initiated from + * the server end). It's safe to enforce this by assertion + * rather than by ssh_disconnect, because the real point is + * that we never constructed a half-open channel structure in + * the first place with any type other than the above. + */ + assert(!"Funny channel type in ssh2_msg_channel_open_failure"); + } del234(ssh->channels, c); sfree(c); } @@ -6973,10 +7767,15 @@ struct Packet *pktout; c = ssh2_channel_msg(ssh, pktin); if (!c) return; + if (c->type == CHAN_SHARING) { + share_got_pkt_from_server(c->u.sharing.ctx, pktin->type, + pktin->body, pktin->length); + return; + } ssh_pkt_getstring(pktin, &type, &typelen); want_reply = ssh2_pkt_getbool(pktin); /* * Having got the channel number, we now look at @@ -7016,20 +7815,22 @@ /* If it's 0, it hardly matters; assume string */ if (num == 0) { is_int = FALSE; } else { int maybe_int = FALSE, maybe_str = FALSE; -#define CHECK_HYPOTHESIS(offset, result) \ - do { \ - long q = offset; \ - if (q >= 0 && q+4 <= len) { \ - q = q + 4 + GET_32BIT(p+q); \ - if (q >= 0 && q+4 <= len && \ - ((q = q + 4 + GET_32BIT(p+q))!= 0) && q == len) \ - result = TRUE; \ - } \ - } while(0) +#define CHECK_HYPOTHESIS(offset, result) \ + do \ + { \ + int q = toint(offset); \ + if (q >= 0 && q+4 <= len) { \ + q = toint(q + 4 + GET_32BIT(p+q)); \ + if (q >= 0 && q+4 <= len && \ + ((q = toint(q + 4 + GET_32BIT(p+q))) != 0) && \ + q == len) \ + result = TRUE; \ + } \ + } while(0) CHECK_HYPOTHESIS(4+1, maybe_int); CHECK_HYPOTHESIS(4+num+1, maybe_str); #undef CHECK_HYPOTHESIS if (maybe_int && !maybe_str) is_int = TRUE; @@ -7162,10 +7963,34 @@ if (want_reply) { pktout = ssh2_pkt_init(SSH2_MSG_REQUEST_FAILURE); ssh2_pkt_send(ssh, pktout); } } + +struct X11FakeAuth *ssh_sharing_add_x11_display(Ssh ssh, int authtype, + void *share_cs, + void *share_chan) +{ + struct X11FakeAuth *auth; + + /* + * Make up a new set of fake X11 auth data, and add it to the tree + * of currently valid ones with an indication of the sharing + * context that it's relevant to. + */ + auth = x11_invent_fake_auth(ssh->x11authtree, authtype); + auth->share_cs = share_cs; + auth->share_chan = share_chan; + + return auth; +} + +void ssh_sharing_remove_x11_display(Ssh ssh, struct X11FakeAuth *auth) +{ + del234(ssh->x11authtree, auth); + x11_free_fake_auth(auth); +} static void ssh2_msg_channel_open(Ssh ssh, struct Packet *pktin) { char *type; int typelen; @@ -7173,10 +7998,11 @@ int peeraddrlen; int peerport; char *error = NULL; struct ssh_channel *c; unsigned remid, winsize, pktsize; + unsigned our_winsize_override = 0; struct Packet *pktout; ssh_pkt_getstring(pktin, &type, &typelen); c = snew(struct ssh_channel); c->ssh = ssh; @@ -7185,11 +8011,10 @@ winsize = ssh_pkt_getuint32(pktin); pktsize = ssh_pkt_getuint32(pktin); if (typelen == 3 && !memcmp(type, "x11", 3)) { char *addrstr; - const char *x11err; ssh_pkt_getstring(pktin, &peeraddr, &peeraddrlen); addrstr = snewn(peeraddrlen+1, char); memcpy(addrstr, peeraddr, peeraddrlen); addrstr[peeraddrlen] = '\0'; @@ -7196,46 +8021,73 @@ peerport = ssh_pkt_getuint32(pktin); logeventf(ssh, "Received X11 connect request from %s:%d", addrstr, peerport); - if (!ssh->X11_fwd_enabled) + if (!ssh->X11_fwd_enabled && !ssh->connshare) error = "X11 forwarding is not enabled"; - else if ((x11err = x11_init(&c->u.x11.s, ssh->x11disp, c, - addrstr, peerport, &ssh->cfg)) != NULL) { - logeventf(ssh, "Local X11 connection failed: %s", x11err); - error = "Unable to open an X11 connection"; - } else { - logevent("Opening X11 forward connection succeeded"); + else { + c->u.x11.xconn = x11_init(ssh->x11authtree, c, + addrstr, peerport); c->type = CHAN_X11; + c->u.x11.initial = TRUE; + + /* + * If we are a connection-sharing upstream, then we should + * initially present a very small window, adequate to take + * the X11 initial authorisation packet but not much more. + * Downstream will then present us a larger window (by + * fiat of the connection-sharing protocol) and we can + * guarantee to send a positive-valued WINDOW_ADJUST. + */ + if (ssh->connshare) + our_winsize_override = 128; + + logevent("Opened X11 forward channel"); } sfree(addrstr); } else if (typelen == 15 && !memcmp(type, "forwarded-tcpip", 15)) { struct ssh_rportfwd pf, *realpf; - char *dummy; - int dummylen; - ssh_pkt_getstring(pktin, &dummy, &dummylen);/* skip address */ + char *shost; + int shostlen; + ssh_pkt_getstring(pktin, &shost, &shostlen);/* skip address */ + pf.shost = dupprintf("%.*s", shostlen, shost); pf.sport = ssh_pkt_getuint32(pktin); ssh_pkt_getstring(pktin, &peeraddr, &peeraddrlen); peerport = ssh_pkt_getuint32(pktin); realpf = find234(ssh->rportfwds, &pf, NULL); - logeventf(ssh, "Received remote port %d open request " - "from %s:%d", pf.sport, peeraddr, peerport); + logeventf(ssh, "Received remote port %s:%d open request " + "from %s:%d", pf.shost, pf.sport, peeraddr, peerport); + sfree(pf.shost); + if (realpf == NULL) { error = "Remote port is not recognised"; } else { - const char *e = pfd_newconnect(&c->u.pfd.s, - realpf->dhost, - realpf->dport, c, - &ssh->cfg, - realpf->pfrec->addressfamily); + char *err; + + if (realpf->share_ctx) { + /* + * This port forwarding is on behalf of a + * connection-sharing downstream, so abandon our own + * channel-open procedure and just pass the message on + * to sshshare.c. + */ + share_got_pkt_from_server(realpf->share_ctx, pktin->type, + pktin->body, pktin->length); + sfree(c); + return; + } + + err = pfd_connect(&c->u.pfd.pf, realpf->dhost, realpf->dport, + c, ssh->conf, realpf->pfrec->addressfamily); logeventf(ssh, "Attempting to forward remote port to " "%s:%d", realpf->dhost, realpf->dport); - if (e != NULL) { - logeventf(ssh, "Port open failed: %s", e); + if (err != NULL) { + logeventf(ssh, "Port open failed: %s", err); + sfree(err); error = "Port open failed"; } else { logevent("Forwarded port opened successfully"); c->type = CHAN_SOCKDATA; } @@ -7245,10 +8097,12 @@ if (!ssh->agentfwd_enabled) error = "Agent forwarding is not enabled"; else { c->type = CHAN_AGENT; /* identify channel type */ c->u.a.lensofar = 0; + c->u.a.message = NULL; + c->u.a.outstanding_requests = 0; } } else { error = "Unsupported channel type requested"; } @@ -7265,28 +8119,69 @@ sfree(c); } else { ssh2_channel_init(c); c->v.v2.remwindow = winsize; c->v.v2.remmaxpkt = pktsize; + if (our_winsize_override) { + c->v.v2.locwindow = c->v.v2.locmaxwin = c->v.v2.remlocwin = + our_winsize_override; + } add234(ssh->channels, c); pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_OPEN_CONFIRMATION); ssh2_pkt_adduint32(pktout, c->remoteid); ssh2_pkt_adduint32(pktout, c->localid); ssh2_pkt_adduint32(pktout, c->v.v2.locwindow); ssh2_pkt_adduint32(pktout, OUR_V2_MAXPKT); /* our max pkt size */ ssh2_pkt_send(ssh, pktout); } } + +void sshfwd_x11_sharing_handover(struct ssh_channel *c, + void *share_cs, void *share_chan, + const char *peer_addr, int peer_port, + int endian, int protomajor, int protominor, + const void *initial_data, int initial_len) +{ + /* + * This function is called when we've just discovered that an X + * forwarding channel on which we'd been handling the initial auth + * ourselves turns out to be destined for a connection-sharing + * downstream. So we turn the channel into a CHAN_SHARING, meaning + * that we completely stop tracking windows and buffering data and + * just pass more or less unmodified SSH messages back and forth. + */ + c->type = CHAN_SHARING; + c->u.sharing.ctx = share_cs; + share_setup_x11_channel(share_cs, share_chan, + c->localid, c->remoteid, c->v.v2.remwindow, + c->v.v2.remmaxpkt, c->v.v2.locwindow, + peer_addr, peer_port, endian, + protomajor, protominor, + initial_data, initial_len); +} + +void sshfwd_x11_is_local(struct ssh_channel *c) +{ + /* + * This function is called when we've just discovered that an X + * forwarding channel is _not_ destined for a connection-sharing + * downstream but we're going to handle it ourselves. We stop + * presenting a cautiously small window and go into ordinary data + * exchange mode. + */ + c->u.x11.initial = FALSE; + ssh2_set_window(c, ssh_is_simple(c->ssh) ? OUR_V2_BIGWIN : OUR_V2_WINSIZE); +} /* * Buffer banner messages for later display at some convenient point, * if we're going to display them. */ static void ssh2_msg_userauth_banner(Ssh ssh, struct Packet *pktin) { /* Arbitrary limit to prevent unbounded inflation of buffer */ - if (ssh->cfg.ssh_show_banner && + if (conf_get_int(ssh->conf, CONF_ssh_show_banner) && bufchain_size(&ssh->banner) <= 131072) { char *banner = NULL; int size = 0; ssh_pkt_getstring(pktin, &banner, &size); if (banner) @@ -7311,18 +8206,216 @@ break; } ssh2_pkt_addbyte(pktout, ssh_ttymodes[i].opcode); ssh2_pkt_adduint32(pktout, arg); } + +static void ssh2_setup_x11(struct ssh_channel *c, struct Packet *pktin, + void *ctx) +{ + struct ssh2_setup_x11_state { + int crLine; + }; + Ssh ssh = c->ssh; + struct Packet *pktout; + crStateP(ssh2_setup_x11_state, ctx); + + crBeginState; + + logevent("Requesting X11 forwarding"); + pktout = ssh2_chanreq_init(ssh->mainchan, "x11-req", + ssh2_setup_x11, s); + ssh2_pkt_addbool(pktout, 0); /* many connections */ + ssh2_pkt_addstring(pktout, ssh->x11auth->protoname); + ssh2_pkt_addstring(pktout, ssh->x11auth->datastring); + ssh2_pkt_adduint32(pktout, ssh->x11disp->screennum); + ssh2_pkt_send(ssh, pktout); + + /* Wait to be called back with either a response packet, or NULL + * meaning clean up and free our data */ + crReturnV; + + if (pktin) { + if (pktin->type == SSH2_MSG_CHANNEL_SUCCESS) { + logevent("X11 forwarding enabled"); + ssh->X11_fwd_enabled = TRUE; + } else + logevent("X11 forwarding refused"); + } + + crFinishFreeV; +} + +static void ssh2_setup_agent(struct ssh_channel *c, struct Packet *pktin, + void *ctx) +{ + struct ssh2_setup_agent_state { + int crLine; + }; + Ssh ssh = c->ssh; + struct Packet *pktout; + crStateP(ssh2_setup_agent_state, ctx); + + crBeginState; + + logevent("Requesting OpenSSH-style agent forwarding"); + pktout = ssh2_chanreq_init(ssh->mainchan, "auth-agent-req@openssh.com", + ssh2_setup_agent, s); + ssh2_pkt_send(ssh, pktout); + + /* Wait to be called back with either a response packet, or NULL + * meaning clean up and free our data */ + crReturnV; + + if (pktin) { + if (pktin->type == SSH2_MSG_CHANNEL_SUCCESS) { + logevent("Agent forwarding enabled"); + ssh->agentfwd_enabled = TRUE; + } else + logevent("Agent forwarding refused"); + } + + crFinishFreeV; +} + +static void ssh2_setup_pty(struct ssh_channel *c, struct Packet *pktin, + void *ctx) +{ + struct ssh2_setup_pty_state { + int crLine; + }; + Ssh ssh = c->ssh; + struct Packet *pktout; + crStateP(ssh2_setup_pty_state, ctx); + + crBeginState; + + /* Unpick the terminal-speed string. */ + /* XXX perhaps we should allow no speeds to be sent. */ + ssh->ospeed = 38400; ssh->ispeed = 38400; /* last-resort defaults */ + sscanf(conf_get_str(ssh->conf, CONF_termspeed), "%d,%d", &ssh->ospeed, &ssh->ispeed); + /* Build the pty request. */ + pktout = ssh2_chanreq_init(ssh->mainchan, "pty-req", + ssh2_setup_pty, s); + ssh2_pkt_addstring(pktout, conf_get_str(ssh->conf, CONF_termtype)); + ssh2_pkt_adduint32(pktout, ssh->term_width); + ssh2_pkt_adduint32(pktout, ssh->term_height); + ssh2_pkt_adduint32(pktout, 0); /* pixel width */ + ssh2_pkt_adduint32(pktout, 0); /* pixel height */ + ssh2_pkt_addstring_start(pktout); + parse_ttymodes(ssh, ssh2_send_ttymode, (void *)pktout); + ssh2_pkt_addbyte(pktout, SSH2_TTY_OP_ISPEED); + ssh2_pkt_adduint32(pktout, ssh->ispeed); + ssh2_pkt_addbyte(pktout, SSH2_TTY_OP_OSPEED); + ssh2_pkt_adduint32(pktout, ssh->ospeed); + ssh2_pkt_addstring_data(pktout, "\0", 1); /* TTY_OP_END */ + ssh2_pkt_send(ssh, pktout); + ssh->state = SSH_STATE_INTERMED; + + /* Wait to be called back with either a response packet, or NULL + * meaning clean up and free our data */ + crReturnV; + + if (pktin) { + if (pktin->type == SSH2_MSG_CHANNEL_SUCCESS) { + logeventf(ssh, "Allocated pty (ospeed %dbps, ispeed %dbps)", + ssh->ospeed, ssh->ispeed); + ssh->got_pty = TRUE; + } else { + c_write_str(ssh, "Server refused to allocate pty\r\n"); + ssh->editing = ssh->echoing = 1; + } + } + + crFinishFreeV; +} + +static void ssh2_setup_env(struct ssh_channel *c, struct Packet *pktin, + void *ctx) +{ + struct ssh2_setup_env_state { + int crLine; + int num_env, env_left, env_ok; + }; + Ssh ssh = c->ssh; + struct Packet *pktout; + crStateP(ssh2_setup_env_state, ctx); + + crBeginState; + + /* + * 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. + */ + s->num_env = 0; + { + char *key, *val; + + for (val = conf_get_str_strs(ssh->conf, CONF_environmt, NULL, &key); + val != NULL; + val = conf_get_str_strs(ssh->conf, CONF_environmt, key, &key)) { + pktout = ssh2_chanreq_init(ssh->mainchan, "env", ssh2_setup_env, s); + ssh2_pkt_addstring(pktout, key); + ssh2_pkt_addstring(pktout, val); + ssh2_pkt_send(ssh, pktout); + + s->num_env++; + } + if (s->num_env) + logeventf(ssh, "Sent %d environment variables", s->num_env); + } + + if (s->num_env) { + s->env_ok = 0; + s->env_left = s->num_env; + + while (s->env_left > 0) { + /* Wait to be called back with either a response packet, + * or NULL meaning clean up and free our data */ + crReturnV; + if (!pktin) goto out; + if (pktin->type == SSH2_MSG_CHANNEL_SUCCESS) + 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"); + } + } + out:; + crFinishFreeV; +} /* * Handle the SSH-2 userauth and connection layers. */ +static void ssh2_msg_authconn(Ssh ssh, struct Packet *pktin) +{ + do_ssh2_authconn(ssh, NULL, 0, pktin); +} + +static void ssh2_response_authconn(struct ssh_channel *c, struct Packet *pktin, + void *ctx) +{ + do_ssh2_authconn(c->ssh, NULL, 0, pktin); +} + static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, struct Packet *pktin) { struct do_ssh2_authconn_state { + int crLine; enum { AUTH_TYPE_NONE, AUTH_TYPE_PUBLICKEY, AUTH_TYPE_PUBLICKEY_OFFER_LOUD, AUTH_TYPE_PUBLICKEY_OFFER_QUIET, @@ -7331,27 +8424,20 @@ AUTH_TYPE_KEYBOARD_INTERACTIVE, AUTH_TYPE_KEYBOARD_INTERACTIVE_QUIET } type; int done_service_req; int gotit, need_pw, can_pubkey, can_passwd, can_keyb_inter; - /* PuTTY SC start */ - int can_pkcs11, tried_pkcs11, pkcs11_key_loaded; - /* PuTTY SC end */ - /* PuTTY CAPI start */ - int can_capi, tried_capi, capi_key_loaded; - struct capi_keyhandle_struct* capi_keyhandle; - /* PuTTY CAPI end */ int tried_pubkey_config, done_agent; #ifndef NO_GSSAPI int can_gssapi; int tried_gssapi; #endif int kbd_inter_refused; int we_are_in, userauth_success; prompts_t *cur_prompt; int num_prompts; - char username[100]; + char *username; char *password; int got_username; void *publickey_blob; int publickey_bloblen; int publickey_encrypted; @@ -7364,12 +8450,12 @@ char *pkblob, *alg, *commentp; int pklen, alglen, commentlen; int siglen, retlen, len; char *q, *agentreq, *ret; int try_send; - int num_env, env_left, env_ok; struct Packet *pktout; + Filename *keyfile; #ifndef NO_GSSAPI struct ssh_gss_library *gsslib; Ssh_gss_ctx gss_ctx; Ssh_gss_buf gss_buf; Ssh_gss_buf gss_rcvtok, gss_sndtok; @@ -7377,55 +8463,70 @@ Ssh_gss_stat gss_stat; #endif }; crState(do_ssh2_authconn_state); - crBegin(ssh->do_ssh2_authconn_crstate); + crBeginState; + /* Register as a handler for all the messages this coroutine handles. */ + ssh->packet_dispatch[SSH2_MSG_SERVICE_ACCEPT] = ssh2_msg_authconn; + ssh->packet_dispatch[SSH2_MSG_USERAUTH_REQUEST] = ssh2_msg_authconn; + ssh->packet_dispatch[SSH2_MSG_USERAUTH_FAILURE] = ssh2_msg_authconn; + ssh->packet_dispatch[SSH2_MSG_USERAUTH_SUCCESS] = ssh2_msg_authconn; + ssh->packet_dispatch[SSH2_MSG_USERAUTH_BANNER] = ssh2_msg_authconn; + ssh->packet_dispatch[SSH2_MSG_USERAUTH_PK_OK] = ssh2_msg_authconn; + /* ssh->packet_dispatch[SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ] = ssh2_msg_authconn; duplicate case value */ + /* ssh->packet_dispatch[SSH2_MSG_USERAUTH_INFO_REQUEST] = ssh2_msg_authconn; duplicate case value */ + ssh->packet_dispatch[SSH2_MSG_USERAUTH_INFO_RESPONSE] = ssh2_msg_authconn; + ssh->packet_dispatch[SSH2_MSG_GLOBAL_REQUEST] = ssh2_msg_authconn; + ssh->packet_dispatch[SSH2_MSG_REQUEST_SUCCESS] = ssh2_msg_authconn; + ssh->packet_dispatch[SSH2_MSG_REQUEST_FAILURE] = ssh2_msg_authconn; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN] = ssh2_msg_authconn; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN_CONFIRMATION] = ssh2_msg_authconn; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN_FAILURE] = ssh2_msg_authconn; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_WINDOW_ADJUST] = ssh2_msg_authconn; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_DATA] = ssh2_msg_authconn; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_EXTENDED_DATA] = ssh2_msg_authconn; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_EOF] = ssh2_msg_authconn; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_CLOSE] = ssh2_msg_authconn; + s->done_service_req = FALSE; s->we_are_in = s->userauth_success = FALSE; + s->agent_response = NULL; #ifndef NO_GSSAPI s->tried_gssapi = FALSE; #endif - /* PuTTY SC start */ - s->tried_pkcs11 = FALSE; - s->can_pkcs11 = FALSE; - s->pkcs11_key_loaded = FALSE; - /* PuTTY SC end */ - /* PuTTY CAPI start */ - s->tried_capi = FALSE; - s->can_capi = FALSE; - s->capi_key_loaded = FALSE; - s->capi_keyhandle = NULL; - /* PuTTY CAPI end */ - - if (!ssh->cfg.ssh_no_userauth) { - /* - * Request userauth protocol, and await a response to it. - */ - s->pktout = ssh2_pkt_init(SSH2_MSG_SERVICE_REQUEST); - ssh2_pkt_addstring(s->pktout, "ssh-userauth"); - ssh2_pkt_send(ssh, s->pktout); - crWaitUntilV(pktin); - if (pktin->type == SSH2_MSG_SERVICE_ACCEPT) - s->done_service_req = TRUE; - } - if (!s->done_service_req) { - /* - * Request connection protocol directly, without authentication. - */ - s->pktout = ssh2_pkt_init(SSH2_MSG_SERVICE_REQUEST); - ssh2_pkt_addstring(s->pktout, "ssh-connection"); - ssh2_pkt_send(ssh, s->pktout); - crWaitUntilV(pktin); - if (pktin->type == SSH2_MSG_SERVICE_ACCEPT) { - s->we_are_in = TRUE; /* no auth required */ - } else { - bombout(("Server refused service request")); - crStopV; - } + if (!ssh->bare_connection) { + if (!conf_get_int(ssh->conf, CONF_ssh_no_userauth)) { + /* + * Request userauth protocol, and await a response to it. + */ + s->pktout = ssh2_pkt_init(SSH2_MSG_SERVICE_REQUEST); + ssh2_pkt_addstring(s->pktout, "ssh-userauth"); + ssh2_pkt_send(ssh, s->pktout); + crWaitUntilV(pktin); + if (pktin->type == SSH2_MSG_SERVICE_ACCEPT) + s->done_service_req = TRUE; + } + if (!s->done_service_req) { + /* + * Request connection protocol directly, without authentication. + */ + s->pktout = ssh2_pkt_init(SSH2_MSG_SERVICE_REQUEST); + ssh2_pkt_addstring(s->pktout, "ssh-connection"); + ssh2_pkt_send(ssh, s->pktout); + crWaitUntilV(pktin); + if (pktin->type == SSH2_MSG_SERVICE_ACCEPT) { + s->we_are_in = TRUE; /* no auth required */ + } else { + bombout(("Server refused service request")); + crStopV; + } + } + } else { + s->we_are_in = TRUE; } /* Arrange to be able to deal with any BANNERs that come in. * (We do this now as packets may come in during the next bit.) */ bufchain_init(&ssh->banner); @@ -7440,32 +8541,33 @@ /* * Load the public half of any configured public key file * for later use. */ - if (!filename_is_null(ssh->cfg.keyfile)) { + s->keyfile = conf_get_filename(ssh->conf, CONF_keyfile); + if (!filename_is_null(s->keyfile)) { int keytype; logeventf(ssh, "Reading private key file \"%.150s\"", - filename_to_str(&ssh->cfg.keyfile)); - keytype = key_type(&ssh->cfg.keyfile); + filename_to_str(s->keyfile)); + keytype = key_type(s->keyfile); if (keytype == SSH_KEYTYPE_SSH2) { const char *error; s->publickey_blob = - ssh2_userkey_loadpub(&ssh->cfg.keyfile, + ssh2_userkey_loadpub(s->keyfile, &s->publickey_algorithm, &s->publickey_bloblen, &s->publickey_comment, &error); if (s->publickey_blob) { s->publickey_encrypted = - ssh2_userkey_encrypted(&ssh->cfg.keyfile, NULL); + ssh2_userkey_encrypted(s->keyfile, NULL); } else { char *msgbuf; logeventf(ssh, "Unable to load private key (%s)", error); msgbuf = dupprintf("Unable to load private key file " "\"%.150s\" (%s)\r\n", - filename_to_str(&ssh->cfg.keyfile), + filename_to_str(s->keyfile), error); c_write_str(ssh, msgbuf); sfree(msgbuf); } } else { @@ -7472,69 +8574,26 @@ char *msgbuf; logeventf(ssh, "Unable to use this key file (%s)", key_type_to_str(keytype)); msgbuf = dupprintf("Unable to use key file \"%.150s\"" " (%s)\r\n", - filename_to_str(&ssh->cfg.keyfile), + filename_to_str(s->keyfile), key_type_to_str(keytype)); c_write_str(ssh, msgbuf); sfree(msgbuf); s->publickey_blob = NULL; } } - /* PuTTY SC start */ - else if (ssh->cfg.try_pkcs11_auth) { - if(!loaded_pkcs11 && !filename_is_null(ssh->cfg.pkcs11_libfile)) { - if (ssh->cfg.sclib == NULL) { ssh->cfg.sclib = calloc(sizeof(sc_lib), 1); } - if(s->can_pkcs11 = sc_init_library(ssh->frontend, ssh->cfg.try_write_syslog, ssh->cfg.sclib, - &ssh->cfg.pkcs11_libfile)) { - loaded_pkcs11=1; - } else { - free(ssh->cfg.sclib); - sc_write_syslog("sc: Failed to load pkcs11 library"); - logevent("sc: Failed to load pkcs11 library"); - } - } - if(loaded_pkcs11) { - logeventf(ssh, "Using key (%s) from token (%s)", - ssh->cfg.pkcs11_cert_label, - ssh->cfg.pkcs11_token_label); - - s->publickey_blob = (unsigned char *)sc_get_pub(ssh->frontend, - ssh->cfg.try_write_syslog, - ssh->cfg.sclib, - ssh->cfg.pkcs11_token_label, - ssh->cfg.pkcs11_cert_label, - &s->publickey_algorithm, - &s->publickey_bloblen); - s->pkcs11_key_loaded = TRUE; - s->publickey_encrypted = TRUE; - s->publickey_comment = calloc(strlen(ssh->cfg.pkcs11_cert_label) + 1, 1); - strcpy(s->publickey_comment, ssh->cfg.pkcs11_cert_label); - } - } - /* PuTTY SC end */ - /* PuTTY CAPI start */ - else if (ssh->cfg.try_capi_auth) { - logeventf(ssh, "Using cert (%s) from CAPI", ssh->cfg.capi_certID); - if (capi_get_pubkey(ssh->frontend, ssh->cfg.capi_certID, (unsigned char**) &s->publickey_blob, &s->publickey_algorithm, &s->publickey_bloblen)) { - s->capi_key_loaded = TRUE; - s->publickey_encrypted = FALSE; // never encrypted (as far as PuTTY knows) - s->publickey_comment = calloc(sizeof(ssh->cfg.capi_certID) + 6, 1); - _snprintf(s->publickey_comment, sizeof(ssh->cfg.capi_certID) + 5, "CAPI:%s", ssh->cfg.capi_certID); - } - } - /* PuTTY CAPI end */ /* * Find out about any keys Pageant has (but if there's a * public key configured, filter out all others). */ s->nkeys = 0; s->agent_response = NULL; s->pkblob_in_agent = NULL; - if (ssh->cfg.tryagent && agent_exists()) { + if (conf_get_int(ssh->conf, CONF_tryagent) && agent_exists()) { void *r; logevent("Pageant is running. Requesting keys."); @@ -7558,17 +8617,57 @@ if (s->agent_response && s->agent_responselen >= 5 && s->agent_response[4] == SSH2_AGENT_IDENTITIES_ANSWER) { int keyi; unsigned char *p; p = s->agent_response + 5; - s->nkeys = GET_32BIT(p); + s->nkeys = toint(GET_32BIT(p)); + + /* + * Vet the Pageant response to ensure that the key + * count and blob lengths make sense. + */ + if (s->nkeys < 0) { + logeventf(ssh, "Pageant response contained a negative" + " key count %d", s->nkeys); + s->nkeys = 0; + goto done_agent_query; + } else { + unsigned char *q = p + 4; + int lenleft = s->agent_responselen - 5 - 4; + + for (keyi = 0; keyi < s->nkeys; keyi++) { + int bloblen, commentlen; + if (lenleft < 4) { + logeventf(ssh, "Pageant response was truncated"); + s->nkeys = 0; + goto done_agent_query; + } + bloblen = toint(GET_32BIT(q)); + if (bloblen < 0 || bloblen > lenleft) { + logeventf(ssh, "Pageant response was truncated"); + s->nkeys = 0; + goto done_agent_query; + } + lenleft -= 4 + bloblen; + q += 4 + bloblen; + commentlen = toint(GET_32BIT(q)); + if (commentlen < 0 || commentlen > lenleft) { + logeventf(ssh, "Pageant response was truncated"); + s->nkeys = 0; + goto done_agent_query; + } + lenleft -= 4 + commentlen; + q += 4 + commentlen; + } + } + p += 4; logeventf(ssh, "Pageant has %d SSH-2 keys", s->nkeys); if (s->publickey_blob) { /* See if configured key is in agent. */ for (keyi = 0; keyi < s->nkeys; keyi++) { - s->pklen = GET_32BIT(p); + s->pklen = toint(GET_32BIT(p)); if (s->pklen == s->publickey_bloblen && !memcmp(p+4, s->publickey_blob, s->publickey_bloblen)) { logeventf(ssh, "Pageant key #%d matches " "configured key file", keyi); @@ -7575,20 +8674,21 @@ s->keyi = keyi; s->pkblob_in_agent = p; break; } p += 4 + s->pklen; - p += GET_32BIT(p) + 4; /* comment */ + p += toint(GET_32BIT(p)) + 4; /* comment */ } if (!s->pkblob_in_agent) { logevent("Configured key file not in Pageant"); s->nkeys = 0; } } } else { logevent("Failed to get reply from Pageant"); } + done_agent_query:; } } /* @@ -7613,30 +8713,27 @@ * type a username, and then _either_ their key will be * accepted, _or_ they will type a password. If they mistype * the username they will want to be able to get back and * retype it! */ - s->username[0] = '\0'; s->got_username = FALSE; while (!s->we_are_in) { /* * Get a username. */ - if (s->got_username && !ssh->cfg.change_username) { + if (s->got_username && !conf_get_int(ssh->conf, CONF_change_username)) { /* * We got a username last time round this loop, and * with change_username turned off we don't try to get * it again. */ - } else if (!get_remote_username(&ssh->cfg, s->username, - sizeof(s->username))) { + } else if ((ssh->username = get_remote_username(ssh->conf)) == NULL) { int ret; /* need not be kept over crReturn */ s->cur_prompt = new_prompts(ssh->frontend); s->cur_prompt->to_server = TRUE; s->cur_prompt->name = dupstr("SSH login name"); - add_prompt(s->cur_prompt, dupstr("login as: "), TRUE, - lenof(s->username)); + add_prompt(s->cur_prompt, dupstr("login as: "), TRUE); ret = get_userpass_input(s->cur_prompt, NULL, 0); while (ret < 0) { ssh->send_ok = 1; crWaitUntilV(!pktin); ret = get_userpass_input(s->cur_prompt, in, inlen); @@ -7649,17 +8746,16 @@ */ free_prompts(s->cur_prompt); ssh_disconnect(ssh, "No username provided", NULL, 0, TRUE); crStopV; } - memcpy(s->username, s->cur_prompt->prompts[0]->result, - lenof(s->username)); + ssh->username = dupstr(s->cur_prompt->prompts[0]->result); free_prompts(s->cur_prompt); } else { char *stuff; if ((flags & FLAG_VERBOSE) || (flags & FLAG_INTERACTIVE)) { - stuff = dupprintf("Using username \"%s\".\r\n", s->username); + stuff = dupprintf("Using username \"%s\".\r\n", ssh->username); c_write_str(ssh, stuff); sfree(stuff); } } s->got_username = TRUE; @@ -7670,11 +8766,11 @@ * authentication methods we can usefully try next. */ ssh->pkt_actx = SSH2_PKTCTX_NOAUTH; s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST); - ssh2_pkt_addstring(s->pktout, s->username); + ssh2_pkt_addstring(s->pktout, ssh->username); ssh2_pkt_addstring(s->pktout, "ssh-connection");/* service requested */ ssh2_pkt_addstring(s->pktout, "none"); /* method */ ssh2_pkt_send(ssh, s->pktout); s->type = AUTH_TYPE_NONE; s->gotit = FALSE; @@ -7800,11 +8896,11 @@ } else { assert(s->type == AUTH_TYPE_PASSWORD); logevent("Password authentication failed"); c_write_str(ssh, "Access denied\r\n"); - if (ssh->cfg.change_username) { + if (conf_get_int(ssh->conf, CONF_change_username)) { /* XXX perhaps we should allow * keyboard-interactive to do this too? */ s->we_are_in = FALSE; break; } @@ -7814,24 +8910,18 @@ logevent("Further authentication required"); } s->can_pubkey = in_commasep_string("publickey", methods, methlen); - /* PuTTY SC start */ - s->can_pkcs11= ssh->cfg.try_pkcs11_auth && s->can_pubkey && s->pkcs11_key_loaded; - /* PuTTY SC end */ - /* PuTTY CAPI start */ - s->can_capi= ssh->cfg.try_capi_auth && s->can_pubkey && s->capi_key_loaded; - /* PuTTY CAPI end */ s->can_passwd = in_commasep_string("password", methods, methlen); - s->can_keyb_inter = ssh->cfg.try_ki_auth && + s->can_keyb_inter = conf_get_int(ssh->conf, CONF_try_ki_auth) && in_commasep_string("keyboard-interactive", methods, methlen); #ifndef NO_GSSAPI if (!ssh->gsslibs) - ssh->gsslibs = ssh_gss_setup(&ssh->cfg); - s->can_gssapi = ssh->cfg.try_gssapi_auth && + ssh->gsslibs = ssh_gss_setup(ssh->conf); + s->can_gssapi = conf_get_int(ssh->conf, CONF_try_gssapi_auth) && in_commasep_string("gssapi-with-mic", methods, methlen) && ssh->gsslibs->nlibraries > 0; #endif } @@ -7846,25 +8936,25 @@ ssh->pkt_actx = SSH2_PKTCTX_PUBLICKEY; logeventf(ssh, "Trying Pageant key #%d", s->keyi); /* Unpack key from agent response */ - s->pklen = GET_32BIT(s->agentp); + s->pklen = toint(GET_32BIT(s->agentp)); s->agentp += 4; s->pkblob = (char *)s->agentp; s->agentp += s->pklen; - s->alglen = GET_32BIT(s->pkblob); + s->alglen = toint(GET_32BIT(s->pkblob)); s->alg = s->pkblob + 4; - s->commentlen = GET_32BIT(s->agentp); + s->commentlen = toint(GET_32BIT(s->agentp)); s->agentp += 4; s->commentp = (char *)s->agentp; s->agentp += s->commentlen; /* s->agentp now points at next key, if any */ /* See if server will accept it */ s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST); - ssh2_pkt_addstring(s->pktout, s->username); + ssh2_pkt_addstring(s->pktout, ssh->username); ssh2_pkt_addstring(s->pktout, "ssh-connection"); /* service requested */ ssh2_pkt_addstring(s->pktout, "publickey"); /* method */ ssh2_pkt_addbool(s->pktout, FALSE); /* no signature included */ @@ -7895,11 +8985,11 @@ /* * Server is willing to accept the key. * Construct a SIGN_REQUEST. */ s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST); - ssh2_pkt_addstring(s->pktout, s->username); + ssh2_pkt_addstring(s->pktout, ssh->username); ssh2_pkt_addstring(s->pktout, "ssh-connection"); /* service requested */ ssh2_pkt_addstring(s->pktout, "publickey"); /* method */ ssh2_pkt_addbool(s->pktout, TRUE); /* signature included */ @@ -7956,11 +9046,13 @@ s->retlen = ssh->agent_response_len; } s->ret = vret; sfree(s->agentreq); if (s->ret) { - if (s->ret[4] == SSH2_AGENT_SIGN_RESPONSE) { + if (s->retlen >= 9 && + s->ret[4] == SSH2_AGENT_SIGN_RESPONSE && + GET_32BIT(s->ret + 5) <= (unsigned)(s->retlen-9)) { logevent("Sending Pageant's response"); ssh2_add_sigblob(ssh, s->pktout, s->pkblob, s->pklen, s->ret + 9, GET_32BIT(s->ret + 5)); @@ -7982,33 +9074,16 @@ s->keyi++; if (s->keyi >= s->nkeys) s->done_agent = TRUE; } - /* PuTTY SC marker */ /* PuTTY CAPI marker */ - } else if ((s->can_pubkey && s->publickey_blob && - !s->tried_pubkey_config) || - (s->can_pkcs11 && s->publickey_blob && - !s->tried_pkcs11 && s->pkcs11_key_loaded) - || (s->can_capi && s->publickey_blob && !s->tried_capi && s->pkcs11_key_loaded) - ) { + } else if (s->can_pubkey && s->publickey_blob && + !s->tried_pubkey_config) { struct ssh2_userkey *key; /* not live over crReturn */ char *passphrase; /* not live over crReturn */ - /* PuTTY SC start */ - struct sc_pubkey_blob *key11 = NULL; - char passphrase11[512]; - if(s->can_pkcs11) { - s->tried_pkcs11 = TRUE; - } - /* PuTTY SC end */ - /* PuTTY CAPI start */ - if (s->can_capi) - s->tried_capi = TRUE; - /* PuTTY CAPI end */ - ssh->pkt_actx = SSH2_PKTCTX_PUBLICKEY; s->tried_pubkey_config = TRUE; /* @@ -8016,11 +9091,11 @@ * * First, offer the public blob to see if the server is * willing to accept it. */ s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST); - ssh2_pkt_addstring(s->pktout, s->username); + ssh2_pkt_addstring(s->pktout, ssh->username); ssh2_pkt_addstring(s->pktout, "ssh-connection"); /* service requested */ ssh2_pkt_addstring(s->pktout, "publickey"); /* method */ ssh2_pkt_addbool(s->pktout, FALSE); /* no signature included */ @@ -8051,31 +9126,22 @@ c_write_str(ssh, "\"\r\n"); } key = NULL; while (!key) { const char *error; /* not live over crReturn */ - /* PuTTY SC marker */ - if (s->publickey_encrypted || (s->can_pkcs11 && s->pkcs11_key_loaded)) { + if (s->publickey_encrypted) { /* * Get a passphrase from the user. */ int ret; /* need not be kept over crReturn */ s->cur_prompt = new_prompts(ssh->frontend); s->cur_prompt->to_server = FALSE; s->cur_prompt->name = dupstr("SSH key passphrase"); - /* PuTTY SC start */ - if(s->can_pkcs11 && s->pkcs11_key_loaded) { - add_prompt(s->cur_prompt, - dupprintf("Passphrase for smartcard \"%s\": ", - ssh->cfg.pkcs11_token_label), - FALSE, SSH_MAX_PASSWORD_LEN); - } else - /* PuTTY SC end */ add_prompt(s->cur_prompt, dupprintf("Passphrase for key \"%.100s\": ", s->publickey_comment), - FALSE, SSH_MAX_PASSWORD_LEN); + FALSE); ret = get_userpass_input(s->cur_prompt, NULL, 0); while (ret < 0) { ssh->send_ok = 1; crWaitUntilV(!pktin); ret = get_userpass_input(s->cur_prompt, @@ -8099,38 +9165,15 @@ } /* * Try decrypting the key. */ - /* PuTTY SC start */ - if(s->can_pkcs11 && s->pkcs11_key_loaded) { - key11 = sc_login_pub(ssh->frontend, ssh->cfg.try_write_syslog, ssh->cfg.sclib, - (const char *)&ssh->cfg.pkcs11_token_label, passphrase); - key = (struct ssh2_userkey *)key11; - if(key11) { - strcpy(passphrase11, passphrase); - } - } - else - /* PuTTY SC end */ - /* PuTTY CAPI start */ - if(s->can_capi && s->capi_key_loaded) {/*chained off the else above*/ - if (capi_get_key_handle(ssh->frontend, ssh->cfg.capi_certID, &s->capi_keyhandle)) { - key = &capi_key_ssh2_userkey; // special flag-struct - } - else { - logeventf(ssh, "capi_get_key_handle(%s) returned false. s->capi_keyhandle=%08x", ssh->cfg.capi_certID, s->capi_keyhandle); - error = "Failed to load CAPI key"; - } - } - else - /* PuTTY CAPI end */ - key = ssh2_load_userkey(&ssh->cfg.keyfile, passphrase, - &error); + s->keyfile = conf_get_filename(ssh->conf, CONF_keyfile); + key = ssh2_load_userkey(s->keyfile, passphrase, &error); if (passphrase) { /* burn the evidence */ - memset(passphrase, 0, strlen(passphrase)); + smemclr(passphrase, strlen(passphrase)); sfree(passphrase); } if (key == SSH2_WRONG_PASSPHRASE || key == NULL) { if (passphrase && (key == SSH2_WRONG_PASSPHRASE)) { @@ -8156,34 +9199,20 @@ * We have loaded the private key and the server * has announced that it's willing to accept it. * Hallelujah. Generate a signature and send it. */ s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST); - ssh2_pkt_addstring(s->pktout, s->username); + ssh2_pkt_addstring(s->pktout, ssh->username); ssh2_pkt_addstring(s->pktout, "ssh-connection"); /* service requested */ ssh2_pkt_addstring(s->pktout, "publickey"); /* method */ ssh2_pkt_addbool(s->pktout, TRUE); /* signature follows */ - /* PuTTY SC start */ - if((key11 != NULL) && (s->pkcs11_key_loaded)) { - ssh2_pkt_addstring(s->pktout, key11->alg); - pkblob = calloc(key11->len,1); - memcpy(pkblob, key11->data, key11->len); - pkblob_len = key11->len; - } else /* PuTTY SC end */ /* PuTTY CAPI start */ if (s->capi_keyhandle) { - ssh2_pkt_addstring(s->pktout, s->capi_keyhandle->algorithm); - pkblob_len = s->capi_keyhandle->pubkey_len; - pkblob = calloc(pkblob_len,1); - memcpy(pkblob, s->capi_keyhandle->pubkey, pkblob_len); - } else { - /* PuTTY CAPI end */ ssh2_pkt_addstring(s->pktout, key->alg->name); pkblob = key->alg->public_blob(key->data, &pkblob_len); - } ssh2_pkt_addstring_start(s->pktout); ssh2_pkt_addstring_data(s->pktout, (char *)pkblob, pkblob_len); /* @@ -8209,31 +9238,10 @@ p += ssh->v2_session_id_len; memcpy(sigdata+p, s->pktout->data + 5, s->pktout->length - 5); p += s->pktout->length - 5; assert(p == sigdata_len); - /* PuTTY SC start */ - if((key11 != NULL) && (s->pkcs11_key_loaded)) { - sigblob = sc_sig(ssh->frontend, ssh->cfg.try_write_syslog, ssh->cfg.sclib, - ssh->cfg.pkcs11_token_label, passphrase11, - sigdata, sigdata_len, &sigblob_len); - memset(passphrase11, 0, strlen(passphrase11)); - } - else - /* PuTTY SC end */ - /* PuTTY CAPI start */ - if(s->capi_key_loaded && (s->capi_keyhandle != NULL)) { /* chained off else from above */ - if ((sigblob = capi_sig(s->capi_keyhandle, sigdata, sigdata_len, &sigblob_len)) == NULL) { - capi_release_key(&s->capi_keyhandle); - sfree(pkblob); - sfree(sigdata); - bombout(("CAPI failed to sign data")); - crStopV; - } - } - else - /* PuTTY CAPI end */ sigblob = key->alg->sign(key->data, (char *)sigdata, sigdata_len, &sigblob_len); ssh2_add_sigblob(ssh, s->pktout, pkblob, pkblob_len, sigblob, sigblob_len); sfree(pkblob); @@ -8241,29 +9249,10 @@ sfree(sigdata); ssh2_pkt_send(ssh, s->pktout); logevent("Sent public key signature"); s->type = AUTH_TYPE_PUBLICKEY; - /* PuTTY SC start */ - if((key11 != NULL) && (s->pkcs11_key_loaded)) { - sc_free_sclib(ssh->cfg.sclib); - ssh->cfg.sclib = NULL; - free(key11); - key11 = NULL; - loaded_pkcs11=0; - s->pkcs11_key_loaded = FALSE; - } - else - /* PuTTY SC end */ - /* PuTTY CAPI start */ - if(s->capi_key_loaded || s->capi_keyhandle) { /* chained off else from above */ - capi_release_key(&s->capi_keyhandle); - s->capi_key_loaded = FALSE; - key = NULL; - } - else - /* PuTTY CAPI end */ key->alg->freekey(key->data); } #ifndef NO_GSSAPI } else if (s->can_gssapi && !s->tried_gssapi) { @@ -8284,11 +9273,12 @@ */ { int i, j; s->gsslib = NULL; for (i = 0; i < ngsslibs; i++) { - int want_id = ssh->cfg.ssh_gsslist[i]; + int want_id = conf_get_int_int(ssh->conf, + CONF_ssh_gsslist, i); for (j = 0; j < ssh->gsslibs->nlibraries; j++) if (ssh->gsslibs->libraries[j].id == want_id) { s->gsslib = &ssh->gsslibs->libraries[j]; goto got_gsslib; /* double break */ } @@ -8307,11 +9297,11 @@ if (s->gsslib->gsslogmsg) logevent(s->gsslib->gsslogmsg); /* Sending USERAUTH_REQUEST with "gssapi-with-mic" method */ s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST); - ssh2_pkt_addstring(s->pktout, s->username); + ssh2_pkt_addstring(s->pktout, ssh->username); ssh2_pkt_addstring(s->pktout, "ssh-connection"); ssh2_pkt_addstring(s->pktout, "gssapi-with-mic"); logevent("Attempting GSSAPI authentication"); /* add mechanism info */ @@ -8379,11 +9369,11 @@ do { s->gss_stat = s->gsslib->init_sec_context (s->gsslib, &s->gss_ctx, s->gss_srv_name, - ssh->cfg.gssapifwd, + conf_get_int(ssh->conf, CONF_gssapifwd), &s->gss_rcvtok, &s->gss_sndtok); if (s->gss_stat!=SSH_GSS_S_COMPLETE && s->gss_stat!=SSH_GSS_S_CONTINUE_NEEDED) { @@ -8435,11 +9425,11 @@ s->pktout = ssh2_pkt_init(0); micoffset = s->pktout->length; ssh_pkt_addstring_start(s->pktout); ssh_pkt_addstring_data(s->pktout, (char *)ssh->v2_session_id, ssh->v2_session_id_len); ssh_pkt_addbyte(s->pktout, SSH2_MSG_USERAUTH_REQUEST); - ssh_pkt_addstring(s->pktout, s->username); + ssh_pkt_addstring(s->pktout, ssh->username); ssh_pkt_addstring(s->pktout, "ssh-connection"); ssh_pkt_addstring(s->pktout, "gssapi-with-mic"); s->gss_buf.value = (char *)s->pktout->data + micoffset; s->gss_buf.length = s->pktout->length - micoffset; @@ -8466,11 +9456,11 @@ s->type = AUTH_TYPE_KEYBOARD_INTERACTIVE; ssh->pkt_actx = SSH2_PKTCTX_KBDINTER; s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST); - ssh2_pkt_addstring(s->pktout, s->username); + ssh2_pkt_addstring(s->pktout, ssh->username); ssh2_pkt_addstring(s->pktout, "ssh-connection"); /* service requested */ ssh2_pkt_addstring(s->pktout, "keyboard-interactive"); /* method */ ssh2_pkt_addstring(s->pktout, ""); /* lang */ @@ -8527,11 +9517,11 @@ prompt = noprompt; prompt_len = lenof(noprompt)-1; } add_prompt(s->cur_prompt, dupprintf("%.*s", prompt_len, prompt), - echo, SSH_MAX_PASSWORD_LEN); + echo); } if (name_len) { /* FIXME: better prefix to distinguish from * local prompts? */ @@ -8588,14 +9578,12 @@ * Send the response(s) to the server. */ s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_INFO_RESPONSE); ssh2_pkt_adduint32(s->pktout, s->num_prompts); for (i=0; i < s->num_prompts; i++) { - dont_log_password(ssh, s->pktout, PKTLOG_BLANK); ssh2_pkt_addstring(s->pktout, s->cur_prompt->prompts[i]->result); - end_log_omission(ssh, s->pktout); } ssh2_pkt_send_with_padding(ssh, s->pktout, 256); /* * Free the prompts structure from this iteration. @@ -8628,14 +9616,14 @@ ssh->pkt_actx = SSH2_PKTCTX_PASSWORD; s->cur_prompt = new_prompts(ssh->frontend); s->cur_prompt->to_server = TRUE; s->cur_prompt->name = dupstr("SSH password"); - add_prompt(s->cur_prompt, dupprintf("%.90s@%.90s's password: ", - s->username, + add_prompt(s->cur_prompt, dupprintf("%s@%s's password: ", + ssh->username, ssh->savedhost), - FALSE, SSH_MAX_PASSWORD_LEN); + FALSE); ret = get_userpass_input(s->cur_prompt, NULL, 0); while (ret < 0) { ssh->send_ok = 1; crWaitUntilV(!pktin); @@ -8669,18 +9657,16 @@ * Anyone using a password longer than 256 bytes * probably doesn't have much to worry about from * people who find out how long their password is! */ s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST); - ssh2_pkt_addstring(s->pktout, s->username); + ssh2_pkt_addstring(s->pktout, ssh->username); ssh2_pkt_addstring(s->pktout, "ssh-connection"); /* service requested */ ssh2_pkt_addstring(s->pktout, "password"); ssh2_pkt_addbool(s->pktout, FALSE); - dont_log_password(ssh, s->pktout, PKTLOG_BLANK); ssh2_pkt_addstring(s->pktout, s->password); - end_log_omission(ssh, s->pktout); ssh2_pkt_send_with_padding(ssh, s->pktout, 256); logevent("Sent password"); s->type = AUTH_TYPE_PASSWORD; /* @@ -8733,15 +9719,15 @@ * (On the other hand, some servers don't even bother * to check this field.) */ add_prompt(s->cur_prompt, dupstr("Current password (blank for previously entered password): "), - FALSE, SSH_MAX_PASSWORD_LEN); + FALSE); add_prompt(s->cur_prompt, dupstr("Enter new password: "), - FALSE, SSH_MAX_PASSWORD_LEN); + FALSE); add_prompt(s->cur_prompt, dupstr("Confirm new password: "), - FALSE, SSH_MAX_PASSWORD_LEN); + FALSE); /* * Loop until the user manages to enter the same * password twice. */ @@ -8758,11 +9744,11 @@ /* * Failed to get responses. Terminate. */ /* burn the evidence */ free_prompts(s->cur_prompt); - memset(s->password, 0, strlen(s->password)); + smemclr(s->password, strlen(s->password)); sfree(s->password); ssh_disconnect(ssh, NULL, "Unable to authenticate", SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER, TRUE); crStopV; @@ -8774,11 +9760,11 @@ * one. * (A side effect is that the user doesn't have to * re-enter it if they louse up the new password.) */ if (s->cur_prompt->prompts[0]->result[0]) { - memset(s->password, 0, strlen(s->password)); + smemclr(s->password, strlen(s->password)); /* burn the evidence */ sfree(s->password); s->password = dupstr(s->cur_prompt->prompts[0]->result); } @@ -8798,21 +9784,19 @@ /* * Send the new password (along with the old one). * (see above for padding rationale) */ s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST); - ssh2_pkt_addstring(s->pktout, s->username); + ssh2_pkt_addstring(s->pktout, ssh->username); ssh2_pkt_addstring(s->pktout, "ssh-connection"); /* service requested */ ssh2_pkt_addstring(s->pktout, "password"); ssh2_pkt_addbool(s->pktout, TRUE); - dont_log_password(ssh, s->pktout, PKTLOG_BLANK); ssh2_pkt_addstring(s->pktout, s->password); ssh2_pkt_addstring(s->pktout, s->cur_prompt->prompts[1]->result); free_prompts(s->cur_prompt); - end_log_omission(ssh, s->pktout); ssh2_pkt_send_with_padding(ssh, s->pktout, 256); logevent("Sent new password"); /* * Now see what the server has to say about it. @@ -8841,11 +9825,11 @@ /* * We don't need the old password any more, in any * case. Burn the evidence. */ - memset(s->password, 0, strlen(s->password)); + smemclr(s->password, strlen(s->password)); sfree(s->password); } else { char *str = dupprintf("No supported authentication methods available" " (server sent: %.*s)", @@ -8871,11 +9855,11 @@ sfree(s->publickey_comment); } if (s->agent_response) sfree(s->agent_response); - if (s->userauth_success) { + if (s->userauth_success && !ssh->bare_connection) { /* * We've just received USERAUTH_SUCCESS, and we haven't sent any * packets since. Signal the transport layer to consider enacting * delayed compression. * @@ -8885,14 +9869,10 @@ * become set for other reasons.) */ do_ssh2_transport(ssh, "enabling delayed compression", -2, NULL); } - /* - * Now the connection protocol has started, one way or another. - */ - ssh->channels = newtree234(ssh_channelcmp); /* * Set up handlers for some connection protocol messages, so we * don't have to handle them repeatedly in this coroutine. @@ -8903,42 +9883,36 @@ ssh2_msg_global_request; /* * Create the main session channel. */ - if (ssh->cfg.ssh_no_shell) { + if (conf_get_int(ssh->conf, CONF_ssh_no_shell)) { ssh->mainchan = NULL; - } else if (*ssh->cfg.ssh_nc_host) { - /* - * Just start a direct-tcpip channel and use it as the main - * channel. - */ + } else { ssh->mainchan = snew(struct ssh_channel); ssh->mainchan->ssh = ssh; ssh2_channel_init(ssh->mainchan); - logeventf(ssh, - "Opening direct-tcpip channel to %s:%d in place of session", - ssh->cfg.ssh_nc_host, ssh->cfg.ssh_nc_port); - s->pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_OPEN); - ssh2_pkt_addstring(s->pktout, "direct-tcpip"); - ssh2_pkt_adduint32(s->pktout, ssh->mainchan->localid); - ssh2_pkt_adduint32(s->pktout, ssh->mainchan->v.v2.locwindow);/* our window size */ - ssh2_pkt_adduint32(s->pktout, OUR_V2_MAXPKT); /* our max pkt size */ - ssh2_pkt_addstring(s->pktout, ssh->cfg.ssh_nc_host); - ssh2_pkt_adduint32(s->pktout, ssh->cfg.ssh_nc_port); - /* - * There's nothing meaningful to put in the originator - * fields, but some servers insist on syntactically correct - * information. - */ - ssh2_pkt_addstring(s->pktout, "0.0.0.0"); - ssh2_pkt_adduint32(s->pktout, 0); - ssh2_pkt_send(ssh, s->pktout); - + + if (*conf_get_str(ssh->conf, CONF_ssh_nc_host)) { + /* + * Just start a direct-tcpip channel and use it as the main + * channel. + */ + ssh_send_port_open(ssh->mainchan, + conf_get_str(ssh->conf, CONF_ssh_nc_host), + conf_get_int(ssh->conf, CONF_ssh_nc_port), + "main channel"); + ssh->ncmode = TRUE; + } else { + s->pktout = ssh2_chanopen_init(ssh->mainchan, "session"); + logevent("Opening session as main channel"); + ssh2_pkt_send(ssh, s->pktout); + ssh->ncmode = FALSE; + } crWaitUntilV(pktin); if (pktin->type != SSH2_MSG_CHANNEL_OPEN_CONFIRMATION) { - bombout(("Server refused to open a direct-tcpip channel")); + bombout(("Server refused to open channel")); crStopV; /* FIXME: error data comes back in FAILURE packet */ } if (ssh_pkt_getuint32(pktin) != ssh->mainchan->localid) { bombout(("Server's channel confirmation cited wrong channel")); @@ -8949,41 +9923,11 @@ ssh->mainchan->type = CHAN_MAINSESSION; ssh->mainchan->v.v2.remwindow = ssh_pkt_getuint32(pktin); ssh->mainchan->v.v2.remmaxpkt = ssh_pkt_getuint32(pktin); add234(ssh->channels, ssh->mainchan); update_specials_menu(ssh->frontend); - logevent("Opened direct-tcpip channel"); - ssh->ncmode = TRUE; - } else { - ssh->mainchan = snew(struct ssh_channel); - ssh->mainchan->ssh = ssh; - ssh2_channel_init(ssh->mainchan); - s->pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_OPEN); - ssh2_pkt_addstring(s->pktout, "session"); - ssh2_pkt_adduint32(s->pktout, ssh->mainchan->localid); - ssh2_pkt_adduint32(s->pktout, ssh->mainchan->v.v2.locwindow);/* our window size */ - ssh2_pkt_adduint32(s->pktout, OUR_V2_MAXPKT); /* our max pkt size */ - ssh2_pkt_send(ssh, s->pktout); - crWaitUntilV(pktin); - if (pktin->type != SSH2_MSG_CHANNEL_OPEN_CONFIRMATION) { - bombout(("Server refused to open a session")); - crStopV; - /* FIXME: error data comes back in FAILURE packet */ - } - if (ssh_pkt_getuint32(pktin) != ssh->mainchan->localid) { - bombout(("Server's channel confirmation cited wrong channel")); - crStopV; - } - ssh->mainchan->remoteid = ssh_pkt_getuint32(pktin); - ssh->mainchan->halfopen = FALSE; - ssh->mainchan->type = CHAN_MAINSESSION; - ssh->mainchan->v.v2.remwindow = ssh_pkt_getuint32(pktin); - ssh->mainchan->v.v2.remmaxpkt = ssh_pkt_getuint32(pktin); - add234(ssh->channels, ssh->mainchan); - update_specials_menu(ssh->frontend); - logevent("Opened channel for session"); - ssh->ncmode = FALSE; + logevent("Opened main channel"); } /* * Now we have a channel, make dispatch table entries for * general channel-based messages. @@ -8999,284 +9943,143 @@ ssh2_msg_channel_open_failure; ssh->packet_dispatch[SSH2_MSG_CHANNEL_REQUEST] = ssh2_msg_channel_request; ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN] = ssh2_msg_channel_open; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_SUCCESS] = ssh2_msg_channel_response; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_FAILURE] = ssh2_msg_channel_response; - if (ssh->mainchan && ssh->cfg.ssh_simple) { + /* + * Now the connection protocol is properly up and running, with + * all those dispatch table entries, so it's safe to let + * downstreams start trying to open extra channels through us. + */ + if (ssh->connshare) + share_activate(ssh->connshare, ssh->v_s); + + if (ssh->mainchan && ssh_is_simple(ssh)) { /* * This message indicates to the server that we promise * not to try to run any other channel in parallel with * this one, so it's safe for it to advertise a very large * window and leave the flow control to TCP. */ - s->pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST); - ssh2_pkt_adduint32(s->pktout, ssh->mainchan->remoteid); - ssh2_pkt_addstring(s->pktout, "simple@putty.projects.tartarus.org"); - ssh2_pkt_addbool(s->pktout, 0); /* no reply */ - ssh2_pkt_send(ssh, s->pktout); - } - - /* - * Potentially enable X11 forwarding. - */ - if (ssh->mainchan && !ssh->ncmode && ssh->cfg.x11_forward && - (ssh->x11disp = x11_setup_display(ssh->cfg.x11_display, - ssh->cfg.x11_auth, &ssh->cfg))) { - logevent("Requesting X11 forwarding"); - s->pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST); - ssh2_pkt_adduint32(s->pktout, ssh->mainchan->remoteid); - ssh2_pkt_addstring(s->pktout, "x11-req"); - ssh2_pkt_addbool(s->pktout, 1); /* want reply */ - ssh2_pkt_addbool(s->pktout, 0); /* many connections */ - ssh2_pkt_addstring(s->pktout, ssh->x11disp->remoteauthprotoname); - /* - * Note that while we blank the X authentication data here, we don't - * take any special action to blank the start of an X11 channel, - * so using MIT-MAGIC-COOKIE-1 and actually opening an X connection - * without having session blanking enabled is likely to leak your - * cookie into the log. - */ - dont_log_password(ssh, s->pktout, PKTLOG_BLANK); - ssh2_pkt_addstring(s->pktout, ssh->x11disp->remoteauthdatastring); - end_log_omission(ssh, s->pktout); - ssh2_pkt_adduint32(s->pktout, ssh->x11disp->screennum); - ssh2_pkt_send(ssh, s->pktout); - - crWaitUntilV(pktin); - - if (pktin->type != SSH2_MSG_CHANNEL_SUCCESS) { - if (pktin->type != SSH2_MSG_CHANNEL_FAILURE) { - bombout(("Unexpected response to X11 forwarding request:" - " packet type %d", pktin->type)); - crStopV; - } - logevent("X11 forwarding refused"); - } else { - logevent("X11 forwarding enabled"); - ssh->X11_fwd_enabled = TRUE; - } + s->pktout = ssh2_chanreq_init(ssh->mainchan, + "simple@putty.projects.tartarus.org", + NULL, NULL); + ssh2_pkt_send(ssh, s->pktout); } /* * Enable port forwardings. */ - ssh_setup_portfwd(ssh, &ssh->cfg); - - /* - * Potentially enable agent forwarding. - */ - if (ssh->mainchan && !ssh->ncmode && ssh->cfg.agentfwd && agent_exists()) { - logevent("Requesting OpenSSH-style agent forwarding"); - s->pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST); - ssh2_pkt_adduint32(s->pktout, ssh->mainchan->remoteid); - ssh2_pkt_addstring(s->pktout, "auth-agent-req@openssh.com"); - ssh2_pkt_addbool(s->pktout, 1); /* want reply */ - ssh2_pkt_send(ssh, s->pktout); - - crWaitUntilV(pktin); - - if (pktin->type != SSH2_MSG_CHANNEL_SUCCESS) { - if (pktin->type != SSH2_MSG_CHANNEL_FAILURE) { - bombout(("Unexpected response to agent forwarding request:" - " packet type %d", pktin->type)); - crStopV; - } - logevent("Agent forwarding refused"); - } else { - logevent("Agent forwarding enabled"); - ssh->agentfwd_enabled = TRUE; - } - } - - /* - * Now allocate a pty for the session. - */ - if (ssh->mainchan && !ssh->ncmode && !ssh->cfg.nopty) { - /* Unpick the terminal-speed string. */ - /* XXX perhaps we should allow no speeds to be sent. */ - ssh->ospeed = 38400; ssh->ispeed = 38400; /* last-resort defaults */ - sscanf(ssh->cfg.termspeed, "%d,%d", &ssh->ospeed, &ssh->ispeed); - /* Build the pty request. */ - s->pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST); - ssh2_pkt_adduint32(s->pktout, ssh->mainchan->remoteid); /* recipient channel */ - ssh2_pkt_addstring(s->pktout, "pty-req"); - ssh2_pkt_addbool(s->pktout, 1); /* want reply */ - ssh2_pkt_addstring(s->pktout, ssh->cfg.termtype); - ssh2_pkt_adduint32(s->pktout, ssh->term_width); - ssh2_pkt_adduint32(s->pktout, ssh->term_height); - ssh2_pkt_adduint32(s->pktout, 0); /* pixel width */ - ssh2_pkt_adduint32(s->pktout, 0); /* pixel height */ - ssh2_pkt_addstring_start(s->pktout); - parse_ttymodes(ssh, ssh->cfg.ttymodes, - ssh2_send_ttymode, (void *)s->pktout); - ssh2_pkt_addbyte(s->pktout, SSH2_TTY_OP_ISPEED); - ssh2_pkt_adduint32(s->pktout, ssh->ispeed); - ssh2_pkt_addbyte(s->pktout, SSH2_TTY_OP_OSPEED); - ssh2_pkt_adduint32(s->pktout, ssh->ospeed); - ssh2_pkt_addstring_data(s->pktout, "\0", 1); /* TTY_OP_END */ - ssh2_pkt_send(ssh, s->pktout); - ssh->state = SSH_STATE_INTERMED; - - crWaitUntilV(pktin); - - if (pktin->type != SSH2_MSG_CHANNEL_SUCCESS) { - if (pktin->type != SSH2_MSG_CHANNEL_FAILURE) { - bombout(("Unexpected response to pty request:" - " packet type %d", pktin->type)); - crStopV; - } - c_write_str(ssh, "Server refused to allocate pty\r\n"); - ssh->editing = ssh->echoing = 1; - } else { - logeventf(ssh, "Allocated pty (ospeed %dbps, ispeed %dbps)", - ssh->ospeed, ssh->ispeed); - } - } else { - 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->ncmode && *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++; - - s->pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST); - ssh2_pkt_adduint32(s->pktout, ssh->mainchan->remoteid); - ssh2_pkt_addstring(s->pktout, "env"); - ssh2_pkt_addbool(s->pktout, 1); /* want reply */ - ssh2_pkt_addstring_start(s->pktout); - ssh2_pkt_addstring_data(s->pktout, var, varend-var); - ssh2_pkt_addstring(s->pktout, val); - ssh2_pkt_send(ssh, s->pktout); - - 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) { - crWaitUntilV(pktin); - - if (pktin->type != SSH2_MSG_CHANNEL_SUCCESS) { - if (pktin->type != SSH2_MSG_CHANNEL_FAILURE) { - bombout(("Unexpected response to environment request:" - " packet type %d", 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 - * of command. - */ - if (ssh->mainchan && !ssh->ncmode) while (1) { - int subsys; - char *cmd; - - if (ssh->fallback_cmd) { - subsys = ssh->cfg.ssh_subsys2; - cmd = ssh->cfg.remote_cmd_ptr2; - } else { - subsys = ssh->cfg.ssh_subsys; - cmd = ssh->cfg.remote_cmd_ptr; - if (!cmd) cmd = ssh->cfg.remote_cmd; - } - - s->pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST); - ssh2_pkt_adduint32(s->pktout, ssh->mainchan->remoteid); /* recipient channel */ - if (subsys) { - ssh2_pkt_addstring(s->pktout, "subsystem"); - ssh2_pkt_addbool(s->pktout, 1); /* want reply */ - ssh2_pkt_addstring(s->pktout, cmd); - } else if (*cmd) { - ssh2_pkt_addstring(s->pktout, "exec"); - ssh2_pkt_addbool(s->pktout, 1); /* want reply */ - ssh2_pkt_addstring(s->pktout, cmd); - } else { - ssh2_pkt_addstring(s->pktout, "shell"); - ssh2_pkt_addbool(s->pktout, 1); /* want reply */ - } - ssh2_pkt_send(ssh, s->pktout); - - crWaitUntilV(pktin); - - if (pktin->type != SSH2_MSG_CHANNEL_SUCCESS) { - if (pktin->type != SSH2_MSG_CHANNEL_FAILURE) { - bombout(("Unexpected response to shell/command request:" - " packet type %d", pktin->type)); - crStopV; - } - /* - * We failed to start the command. If this is the - * fallback command, we really are finished; if it's - * not, and if the fallback command exists, try falling - * back to it before complaining. - */ - if (!ssh->fallback_cmd && ssh->cfg.remote_cmd_ptr2 != NULL) { - logevent("Primary command failed; attempting fallback"); - ssh->fallback_cmd = TRUE; - continue; - } - bombout(("Server refused to start a shell/command")); - crStopV; - } else { - logevent("Started a shell/command"); - } - break; + ssh_setup_portfwd(ssh, ssh->conf); + + if (ssh->mainchan && !ssh->ncmode) { + /* + * Send the CHANNEL_REQUESTS for the main session channel. + * Each one is handled by its own little asynchronous + * co-routine. + */ + + /* Potentially enable X11 forwarding. */ + if (conf_get_int(ssh->conf, CONF_x11_forward)) { + ssh->x11disp = + x11_setup_display(conf_get_str(ssh->conf, CONF_x11_display), + ssh->conf); + if (!ssh->x11disp) { + /* FIXME: return an error message from x11_setup_display */ + logevent("X11 forwarding not enabled: unable to" + " initialise X display"); + } else { + ssh->x11auth = x11_invent_fake_auth + (ssh->x11authtree, conf_get_int(ssh->conf, CONF_x11_auth)); + ssh->x11auth->disp = ssh->x11disp; + + ssh2_setup_x11(ssh->mainchan, NULL, NULL); + } + } + + /* Potentially enable agent forwarding. */ + if (ssh_agent_forwarding_permitted(ssh)) + ssh2_setup_agent(ssh->mainchan, NULL, NULL); + + /* Now allocate a pty for the session. */ + if (!conf_get_int(ssh->conf, CONF_nopty)) + ssh2_setup_pty(ssh->mainchan, NULL, NULL); + + /* Send environment variables. */ + ssh2_setup_env(ssh->mainchan, NULL, NULL); + + /* + * Start a shell or a remote command. We may have to attempt + * this twice if the config data has provided a second choice + * of command. + */ + while (1) { + int subsys; + char *cmd; + + if (ssh->fallback_cmd) { + subsys = conf_get_int(ssh->conf, CONF_ssh_subsys2); + cmd = conf_get_str(ssh->conf, CONF_remote_cmd2); + } else { + subsys = conf_get_int(ssh->conf, CONF_ssh_subsys); + cmd = conf_get_str(ssh->conf, CONF_remote_cmd); + } + + if (subsys) { + s->pktout = ssh2_chanreq_init(ssh->mainchan, "subsystem", + ssh2_response_authconn, NULL); + ssh2_pkt_addstring(s->pktout, cmd); + } else if (*cmd) { + s->pktout = ssh2_chanreq_init(ssh->mainchan, "exec", + ssh2_response_authconn, NULL); + ssh2_pkt_addstring(s->pktout, cmd); + } else { + s->pktout = ssh2_chanreq_init(ssh->mainchan, "shell", + ssh2_response_authconn, NULL); + } + ssh2_pkt_send(ssh, s->pktout); + + crWaitUntilV(pktin); + + if (pktin->type != SSH2_MSG_CHANNEL_SUCCESS) { + if (pktin->type != SSH2_MSG_CHANNEL_FAILURE) { + bombout(("Unexpected response to shell/command request:" + " packet type %d", pktin->type)); + crStopV; + } + /* + * We failed to start the command. If this is the + * fallback command, we really are finished; if it's + * not, and if the fallback command exists, try falling + * back to it before complaining. + */ + if (!ssh->fallback_cmd && + *conf_get_str(ssh->conf, CONF_remote_cmd2)) { + logevent("Primary command failed; attempting fallback"); + ssh->fallback_cmd = TRUE; + continue; + } + bombout(("Server refused to start a shell/command")); + crStopV; + } else { + logevent("Started a shell/command"); + } + break; + } + } else { + ssh->editing = ssh->echoing = TRUE; } ssh->state = SSH_STATE_SESSION; if (ssh->size_needed) ssh_size(ssh, ssh->term_width, ssh->term_height); if (ssh->eof_needed) ssh_special(ssh, TS_EOF); - /* - * All the initial channel requests are done, so install the default - * failure handler. - */ - ssh->packet_dispatch[SSH2_MSG_CHANNEL_SUCCESS] = ssh2_msg_channel_success; - ssh->packet_dispatch[SSH2_MSG_CHANNEL_FAILURE] = ssh2_msg_channel_failure; - /* * Transfer data! */ if (ssh->ldisc) ldisc_send(ssh->ldisc, NULL, 0, 0);/* cause ldisc to notice changes */ @@ -9358,10 +10161,29 @@ ssh2_pkt_getbool(pktin); ssh_pkt_getstring(pktin, &msg, &msglen); logeventf(ssh, "Remote debug message: %.*s", msglen, msg); } + +static void ssh2_msg_transport(Ssh ssh, struct Packet *pktin) +{ + do_ssh2_transport(ssh, NULL, 0, pktin); +} + +/* + * Called if we receive a packet that isn't allowed by the protocol. + * This only applies to packets whose meaning PuTTY understands. + * Entirely unknown packets are handled below. + */ +static void ssh2_msg_unexpected(Ssh ssh, struct Packet *pktin) +{ + char *buf = dupprintf("Server protocol violation: unexpected %s packet", + ssh2_pkt_type(ssh->pkt_kctx, ssh->pkt_actx, + pktin->type)); + ssh_disconnect(ssh, NULL, buf, SSH2_DISCONNECT_PROTOCOL_ERROR, FALSE); + sfree(buf); +} static void ssh2_msg_something_unimplemented(Ssh ssh, struct Packet *pktin) { struct Packet *pktout; pktout = ssh2_pkt_init(SSH2_MSG_UNIMPLEMENTED); @@ -9385,64 +10207,109 @@ */ for (i = 0; i < 256; i++) ssh->packet_dispatch[i] = ssh2_msg_something_unimplemented; /* - * Any message we actually understand, we set to NULL so that - * the coroutines will get it. + * Initially, we only accept transport messages (and a few generic + * ones). do_ssh2_authconn will add more when it starts. + * Messages that are understood but not currently acceptable go to + * ssh2_msg_unexpected. */ - ssh->packet_dispatch[SSH2_MSG_UNIMPLEMENTED] = NULL; - ssh->packet_dispatch[SSH2_MSG_SERVICE_REQUEST] = NULL; - ssh->packet_dispatch[SSH2_MSG_SERVICE_ACCEPT] = NULL; - ssh->packet_dispatch[SSH2_MSG_KEXINIT] = NULL; - ssh->packet_dispatch[SSH2_MSG_NEWKEYS] = NULL; - ssh->packet_dispatch[SSH2_MSG_KEXDH_INIT] = NULL; - ssh->packet_dispatch[SSH2_MSG_KEXDH_REPLY] = NULL; - /* ssh->packet_dispatch[SSH2_MSG_KEX_DH_GEX_REQUEST] = NULL; duplicate case value */ - /* ssh->packet_dispatch[SSH2_MSG_KEX_DH_GEX_GROUP] = NULL; duplicate case value */ - ssh->packet_dispatch[SSH2_MSG_KEX_DH_GEX_INIT] = NULL; - ssh->packet_dispatch[SSH2_MSG_KEX_DH_GEX_REPLY] = NULL; - ssh->packet_dispatch[SSH2_MSG_USERAUTH_REQUEST] = NULL; - ssh->packet_dispatch[SSH2_MSG_USERAUTH_FAILURE] = NULL; - ssh->packet_dispatch[SSH2_MSG_USERAUTH_SUCCESS] = NULL; - ssh->packet_dispatch[SSH2_MSG_USERAUTH_BANNER] = NULL; - ssh->packet_dispatch[SSH2_MSG_USERAUTH_PK_OK] = NULL; - /* ssh->packet_dispatch[SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ] = NULL; duplicate case value */ - /* ssh->packet_dispatch[SSH2_MSG_USERAUTH_INFO_REQUEST] = NULL; duplicate case value */ - ssh->packet_dispatch[SSH2_MSG_USERAUTH_INFO_RESPONSE] = NULL; - ssh->packet_dispatch[SSH2_MSG_GLOBAL_REQUEST] = NULL; - ssh->packet_dispatch[SSH2_MSG_REQUEST_SUCCESS] = NULL; - ssh->packet_dispatch[SSH2_MSG_REQUEST_FAILURE] = NULL; - ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN] = NULL; - ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN_CONFIRMATION] = NULL; - ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN_FAILURE] = NULL; - ssh->packet_dispatch[SSH2_MSG_CHANNEL_WINDOW_ADJUST] = NULL; - ssh->packet_dispatch[SSH2_MSG_CHANNEL_DATA] = NULL; - ssh->packet_dispatch[SSH2_MSG_CHANNEL_EXTENDED_DATA] = NULL; - ssh->packet_dispatch[SSH2_MSG_CHANNEL_EOF] = NULL; - ssh->packet_dispatch[SSH2_MSG_CHANNEL_CLOSE] = NULL; - ssh->packet_dispatch[SSH2_MSG_CHANNEL_REQUEST] = NULL; - ssh->packet_dispatch[SSH2_MSG_CHANNEL_SUCCESS] = NULL; - ssh->packet_dispatch[SSH2_MSG_CHANNEL_FAILURE] = NULL; + ssh->packet_dispatch[SSH2_MSG_UNIMPLEMENTED] = ssh2_msg_unexpected; + ssh->packet_dispatch[SSH2_MSG_SERVICE_REQUEST] = ssh2_msg_unexpected; + ssh->packet_dispatch[SSH2_MSG_SERVICE_ACCEPT] = ssh2_msg_unexpected; + ssh->packet_dispatch[SSH2_MSG_KEXINIT] = ssh2_msg_transport; + ssh->packet_dispatch[SSH2_MSG_NEWKEYS] = ssh2_msg_transport; + ssh->packet_dispatch[SSH2_MSG_KEXDH_INIT] = ssh2_msg_transport; + ssh->packet_dispatch[SSH2_MSG_KEXDH_REPLY] = ssh2_msg_transport; + /* ssh->packet_dispatch[SSH2_MSG_KEX_DH_GEX_REQUEST] = ssh2_msg_transport; duplicate case value */ + /* ssh->packet_dispatch[SSH2_MSG_KEX_DH_GEX_GROUP] = ssh2_msg_transport; duplicate case value */ + ssh->packet_dispatch[SSH2_MSG_KEX_DH_GEX_INIT] = ssh2_msg_transport; + ssh->packet_dispatch[SSH2_MSG_KEX_DH_GEX_REPLY] = ssh2_msg_transport; + ssh->packet_dispatch[SSH2_MSG_USERAUTH_REQUEST] = ssh2_msg_unexpected; + ssh->packet_dispatch[SSH2_MSG_USERAUTH_FAILURE] = ssh2_msg_unexpected; + ssh->packet_dispatch[SSH2_MSG_USERAUTH_SUCCESS] = ssh2_msg_unexpected; + ssh->packet_dispatch[SSH2_MSG_USERAUTH_BANNER] = ssh2_msg_unexpected; + ssh->packet_dispatch[SSH2_MSG_USERAUTH_PK_OK] = ssh2_msg_unexpected; + /* ssh->packet_dispatch[SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ] = ssh2_msg_unexpected; duplicate case value */ + /* ssh->packet_dispatch[SSH2_MSG_USERAUTH_INFO_REQUEST] = ssh2_msg_unexpected; duplicate case value */ + ssh->packet_dispatch[SSH2_MSG_USERAUTH_INFO_RESPONSE] = ssh2_msg_unexpected; + ssh->packet_dispatch[SSH2_MSG_GLOBAL_REQUEST] = ssh2_msg_unexpected; + ssh->packet_dispatch[SSH2_MSG_REQUEST_SUCCESS] = ssh2_msg_unexpected; + ssh->packet_dispatch[SSH2_MSG_REQUEST_FAILURE] = ssh2_msg_unexpected; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN] = ssh2_msg_unexpected; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN_CONFIRMATION] = ssh2_msg_unexpected; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN_FAILURE] = ssh2_msg_unexpected; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_WINDOW_ADJUST] = ssh2_msg_unexpected; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_DATA] = ssh2_msg_unexpected; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_EXTENDED_DATA] = ssh2_msg_unexpected; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_EOF] = ssh2_msg_unexpected; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_CLOSE] = ssh2_msg_unexpected; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_REQUEST] = ssh2_msg_unexpected; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_SUCCESS] = ssh2_msg_unexpected; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_FAILURE] = ssh2_msg_unexpected; /* - * These special message types we install handlers for. + * These messages have a special handler from the start. */ ssh->packet_dispatch[SSH2_MSG_DISCONNECT] = ssh2_msg_disconnect; ssh->packet_dispatch[SSH2_MSG_IGNORE] = ssh_msg_ignore; /* shared with SSH-1 */ ssh->packet_dispatch[SSH2_MSG_DEBUG] = ssh2_msg_debug; } -static void ssh2_timer(void *ctx, long now) +static void ssh2_bare_connection_protocol_setup(Ssh ssh) +{ + int i; + + /* + * Most messages cause SSH2_MSG_UNIMPLEMENTED. + */ + for (i = 0; i < 256; i++) + ssh->packet_dispatch[i] = ssh2_msg_something_unimplemented; + + /* + * Initially, we set all ssh-connection messages to 'unexpected'; + * do_ssh2_authconn will fill things in properly. We also handle a + * couple of messages from the transport protocol which aren't + * related to key exchange (UNIMPLEMENTED, IGNORE, DEBUG, + * DISCONNECT). + */ + ssh->packet_dispatch[SSH2_MSG_GLOBAL_REQUEST] = ssh2_msg_unexpected; + ssh->packet_dispatch[SSH2_MSG_REQUEST_SUCCESS] = ssh2_msg_unexpected; + ssh->packet_dispatch[SSH2_MSG_REQUEST_FAILURE] = ssh2_msg_unexpected; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN] = ssh2_msg_unexpected; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN_CONFIRMATION] = ssh2_msg_unexpected; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN_FAILURE] = ssh2_msg_unexpected; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_WINDOW_ADJUST] = ssh2_msg_unexpected; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_DATA] = ssh2_msg_unexpected; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_EXTENDED_DATA] = ssh2_msg_unexpected; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_EOF] = ssh2_msg_unexpected; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_CLOSE] = ssh2_msg_unexpected; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_REQUEST] = ssh2_msg_unexpected; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_SUCCESS] = ssh2_msg_unexpected; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_FAILURE] = ssh2_msg_unexpected; + + ssh->packet_dispatch[SSH2_MSG_UNIMPLEMENTED] = ssh2_msg_unexpected; + + /* + * These messages have a special handler from the start. + */ + ssh->packet_dispatch[SSH2_MSG_DISCONNECT] = ssh2_msg_disconnect; + ssh->packet_dispatch[SSH2_MSG_IGNORE] = ssh_msg_ignore; + ssh->packet_dispatch[SSH2_MSG_DEBUG] = ssh2_msg_debug; +} + +static void ssh2_timer(void *ctx, unsigned long now) { Ssh ssh = (Ssh)ctx; if (ssh->state == SSH_STATE_CLOSED) return; - if (!ssh->kex_in_progress && ssh->cfg.ssh_rekey_time != 0 && - now - ssh->next_rekey >= 0) { + if (!ssh->kex_in_progress && !ssh->bare_connection && + conf_get_int(ssh->conf, CONF_ssh_rekey_time) != 0 && + now == ssh->next_rekey) { do_ssh2_transport(ssh, "timeout", -1, NULL); } } static void ssh2_protocol(Ssh ssh, void *vin, int inlen, @@ -9458,45 +10325,51 @@ ssh->max_data_size != 0 && ssh->incoming_data_size > ssh->max_data_size) do_ssh2_transport(ssh, "too much data received", -1, NULL); } - if (pktin && ssh->packet_dispatch[pktin->type]) { + if (pktin) ssh->packet_dispatch[pktin->type](ssh, pktin); + else if (!ssh->protocol_initial_phase_done) + do_ssh2_transport(ssh, in, inlen, pktin); + else + do_ssh2_authconn(ssh, in, inlen, pktin); +} + +static void ssh2_bare_connection_protocol(Ssh ssh, void *vin, int inlen, + struct Packet *pktin) +{ + unsigned char *in = (unsigned char *)vin; + if (ssh->state == SSH_STATE_CLOSED) return; - } - - if (!ssh->protocol_initial_phase_done || - (pktin && pktin->type >= 20 && pktin->type < 50)) { - if (do_ssh2_transport(ssh, in, inlen, pktin) && - !ssh->protocol_initial_phase_done) { - ssh->protocol_initial_phase_done = TRUE; - /* - * Allow authconn to initialise itself. - */ - do_ssh2_authconn(ssh, NULL, 0, NULL); - } - } else { - do_ssh2_authconn(ssh, in, inlen, pktin); - } + + if (pktin) + ssh->packet_dispatch[pktin->type](ssh, pktin); + else + do_ssh2_authconn(ssh, in, inlen, pktin); +} + +static void ssh_cache_conf_values(Ssh ssh) +{ + ssh->logomitdata = conf_get_int(ssh->conf, CONF_logomitdata); } /* * Called to set up the connection. * * Returns an error message, or NULL on success. */ static const char *ssh_init(void *frontend_handle, void **backend_handle, - Config *cfg, - char *host, int port, char **realhost, int nodelay, - int keepalive) + Conf *conf, char *host, int port, char **realhost, + int nodelay, int keepalive) { const char *p; Ssh ssh; ssh = snew(struct ssh_tag); - ssh->cfg = *cfg; /* STRUCTURE COPY */ + ssh->conf = conf_copy(conf); + ssh_cache_conf_values(ssh); ssh->version = 0; /* when not ready yet */ ssh->s = NULL; ssh->cipher = NULL; ssh->v1_cipher_ctx = NULL; ssh->crcda_ctx = NULL; @@ -9513,10 +10386,11 @@ ssh->sccomp = NULL; ssh->sc_comp_ctx = NULL; ssh->kex = NULL; ssh->kex_ctx = NULL; ssh->hostkey = NULL; + ssh->hostkey_str = NULL; ssh->exitcode = -1; ssh->close_expected = FALSE; ssh->clean_exit = FALSE; ssh->state = SSH_STATE_PREPACKET; ssh->size_needed = FALSE; @@ -9528,21 +10402,21 @@ ssh->deferred_size = 0; ssh->fallback_cmd = 0; ssh->pkt_kctx = SSH2_PKTCTX_NOKEX; ssh->pkt_actx = SSH2_PKTCTX_NOAUTH; ssh->x11disp = NULL; + ssh->x11auth = NULL; + ssh->x11authtree = newtree234(x11_authcmp); ssh->v1_compressing = FALSE; ssh->v2_outgoing_sequence = 0; ssh->ssh1_rdpkt_crstate = 0; ssh->ssh2_rdpkt_crstate = 0; - ssh->do_ssh_init_crstate = 0; + ssh->ssh2_bare_rdpkt_crstate = 0; ssh->ssh_gotdata_crstate = 0; ssh->do_ssh1_connection_crstate = 0; - ssh->do_ssh1_login_crstate = 0; - ssh->do_ssh2_transport_crstate = 0; - ssh->do_ssh2_authconn_crstate = 0; ssh->do_ssh_init_state = NULL; + ssh->do_ssh_connection_init_state = NULL; ssh->do_ssh1_login_state = NULL; ssh->do_ssh2_transport_state = NULL; ssh->do_ssh2_authconn_state = NULL; ssh->v_c = NULL; ssh->v_s = NULL; @@ -9554,21 +10428,26 @@ ssh->queueing = FALSE; ssh->qhead = ssh->qtail = NULL; ssh->deferred_rekey_reason = NULL; bufchain_init(&ssh->queued_incoming_data); ssh->frozen = FALSE; + ssh->username = NULL; + ssh->sent_console_eof = FALSE; + ssh->got_pty = FALSE; + ssh->bare_connection = FALSE; + ssh->attempting_connshare = FALSE; *backend_handle = ssh; #ifdef MSCRYPTOAPI if (crypto_startup() == 0) return "Microsoft high encryption pack not installed!"; #endif ssh->frontend = frontend_handle; - ssh->term_width = ssh->cfg.width; - ssh->term_height = ssh->cfg.height; + ssh->term_width = conf_get_int(ssh->conf, CONF_width); + ssh->term_height = conf_get_int(ssh->conf, CONF_height); ssh->channels = NULL; ssh->rportfwds = NULL; ssh->portfwds = NULL; @@ -9585,11 +10464,12 @@ ssh->pinger = NULL; ssh->incoming_data_size = ssh->outgoing_data_size = ssh->deferred_data_size = 0L; - ssh->max_data_size = parse_blocksize(ssh->cfg.ssh_rekey_data); + ssh->max_data_size = parse_blocksize(conf_get_str(ssh->conf, + CONF_ssh_rekey_data)); ssh->kex_in_progress = FALSE; #ifndef NO_GSSAPI ssh->gsslibs = NULL; #endif @@ -9606,10 +10486,11 @@ static void ssh_free(void *handle) { Ssh ssh = (Ssh) handle; struct ssh_channel *c; struct ssh_rportfwd *pf; + struct X11FakeAuth *auth; if (ssh->v1_cipher_ctx) ssh->cipher->free_context(ssh->v1_cipher_ctx); if (ssh->cs_cipher_ctx) ssh->cscipher->free_context(ssh->cs_cipher_ctx); @@ -9640,49 +10521,67 @@ sfree(ssh->queue); while (ssh->qhead) { struct queued_handler *qh = ssh->qhead; ssh->qhead = qh->next; - sfree(ssh->qhead); + sfree(qh); } ssh->qhead = ssh->qtail = NULL; if (ssh->channels) { while ((c = delpos234(ssh->channels, 0)) != NULL) { switch (c->type) { case CHAN_X11: - if (c->u.x11.s != NULL) - x11_close(c->u.x11.s); + if (c->u.x11.xconn != NULL) + x11_close(c->u.x11.xconn); break; case CHAN_SOCKDATA: case CHAN_SOCKDATA_DORMANT: - if (c->u.pfd.s != NULL) - pfd_close(c->u.pfd.s); + if (c->u.pfd.pf != NULL) + pfd_close(c->u.pfd.pf); break; } + if (ssh->version == 2) { + struct outstanding_channel_request *ocr, *nocr; + ocr = c->v.v2.chanreq_head; + while (ocr) { + ocr->handler(c, NULL, ocr->ctx); + nocr = ocr->next; + sfree(ocr); + ocr = nocr; + } + bufchain_clear(&c->v.v2.outbuffer); + } sfree(c); } freetree234(ssh->channels); ssh->channels = NULL; } + if (ssh->connshare) + sharestate_free(ssh->connshare); + if (ssh->rportfwds) { while ((pf = delpos234(ssh->rportfwds, 0)) != NULL) free_rportfwd(pf); freetree234(ssh->rportfwds); ssh->rportfwds = NULL; } sfree(ssh->deferred_send_data); if (ssh->x11disp) x11_free_display(ssh->x11disp); + while ((auth = delpos234(ssh->x11authtree, 0)) != NULL) + x11_free_fake_auth(auth); + freetree234(ssh->x11authtree); sfree(ssh->do_ssh_init_state); sfree(ssh->do_ssh1_login_state); sfree(ssh->do_ssh2_transport_state); sfree(ssh->do_ssh2_authconn_state); sfree(ssh->v_c); sfree(ssh->v_s); sfree(ssh->fullhostname); + sfree(ssh->hostkey_str); if (ssh->crcda_ctx) { crcda_free_context(ssh->crcda_ctx); ssh->crcda_ctx = NULL; } if (ssh->s) @@ -9689,10 +10588,12 @@ ssh_do_close(ssh, TRUE); expire_timer_context(ssh); if (ssh->pinger) pinger_free(ssh->pinger); bufchain_clear(&ssh->queued_incoming_data); + sfree(ssh->username); + conf_free(ssh->conf); #ifndef NO_GSSAPI if (ssh->gsslibs) ssh_gss_cleanup(ssh->gsslibs); #endif sfree(ssh); @@ -9701,56 +10602,67 @@ } /* * Reconfigure the SSH backend. */ -static void ssh_reconfig(void *handle, Config *cfg) +static void ssh_reconfig(void *handle, Conf *conf) { Ssh ssh = (Ssh) handle; char *rekeying = NULL, rekey_mandatory = FALSE; unsigned long old_max_data_size; + int i, rekey_time; - pinger_reconfig(ssh->pinger, &ssh->cfg, cfg); + pinger_reconfig(ssh->pinger, ssh->conf, conf); if (ssh->portfwds) - ssh_setup_portfwd(ssh, cfg); - - if (ssh->cfg.ssh_rekey_time != cfg->ssh_rekey_time && - cfg->ssh_rekey_time != 0) { - long new_next = ssh->last_rekey + cfg->ssh_rekey_time*60*TICKSPERSEC; - long now = GETTICKCOUNT(); - - if (new_next - now < 0) { + ssh_setup_portfwd(ssh, conf); + + rekey_time = conf_get_int(conf, CONF_ssh_rekey_time); + if (conf_get_int(ssh->conf, CONF_ssh_rekey_time) != rekey_time && + rekey_time != 0) { + unsigned long new_next = ssh->last_rekey + rekey_time*60*TICKSPERSEC; + unsigned long now = GETTICKCOUNT(); + + if (now - ssh->last_rekey > rekey_time*60*TICKSPERSEC) { rekeying = "timeout shortened"; } else { ssh->next_rekey = schedule_timer(new_next - now, ssh2_timer, ssh); } } old_max_data_size = ssh->max_data_size; - ssh->max_data_size = parse_blocksize(cfg->ssh_rekey_data); + ssh->max_data_size = parse_blocksize(conf_get_str(ssh->conf, + CONF_ssh_rekey_data)); if (old_max_data_size != ssh->max_data_size && ssh->max_data_size != 0) { if (ssh->outgoing_data_size > ssh->max_data_size || ssh->incoming_data_size > ssh->max_data_size) rekeying = "data limit lowered"; } - if (ssh->cfg.compression != cfg->compression) { + if (conf_get_int(ssh->conf, CONF_compression) != + conf_get_int(conf, CONF_compression)) { rekeying = "compression setting changed"; rekey_mandatory = TRUE; } - if (ssh->cfg.ssh2_des_cbc != cfg->ssh2_des_cbc || - memcmp(ssh->cfg.ssh_cipherlist, cfg->ssh_cipherlist, - sizeof(ssh->cfg.ssh_cipherlist))) { + for (i = 0; i < CIPHER_MAX; i++) + if (conf_get_int_int(ssh->conf, CONF_ssh_cipherlist, i) != + conf_get_int_int(conf, CONF_ssh_cipherlist, i)) { + rekeying = "cipher settings changed"; + rekey_mandatory = TRUE; + } + if (conf_get_int(ssh->conf, CONF_ssh2_des_cbc) != + conf_get_int(conf, CONF_ssh2_des_cbc)) { rekeying = "cipher settings changed"; rekey_mandatory = TRUE; } - ssh->cfg = *cfg; /* STRUCTURE COPY */ + conf_free(ssh->conf); + ssh->conf = conf_copy(conf); + ssh_cache_conf_values(ssh); - if (rekeying) { + if (!ssh->bare_connection && rekeying) { if (!ssh->kex_in_progress) { do_ssh2_transport(ssh, rekeying, -1, NULL); } else if (rekey_mandatory) { ssh->deferred_rekey_reason = rekeying; } @@ -9792,11 +10704,11 @@ override_value = ssh->overall_bufsize; if (ssh->version == 1) { return override_value; } else if (ssh->version == 2) { - if (!ssh->mainchan || ssh->mainchan->closes > 0) + if (!ssh->mainchan) return override_value; else return (override_value + bufchain_size(&ssh->mainchan->v.v2.outbuffer)); } @@ -9822,21 +10734,19 @@ break; /* do nothing */ case SSH_STATE_INTERMED: ssh->size_needed = TRUE; /* buffer for later */ break; case SSH_STATE_SESSION: - if (!ssh->cfg.nopty) { + if (!conf_get_int(ssh->conf, CONF_nopty)) { if (ssh->version == 1) { send_packet(ssh, SSH1_CMSG_WINDOW_SIZE, PKT_INT, ssh->term_height, PKT_INT, ssh->term_width, PKT_INT, 0, PKT_INT, 0, PKT_END); } else if (ssh->mainchan) { - pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST); - ssh2_pkt_adduint32(pktout, ssh->mainchan->remoteid); - ssh2_pkt_addstring(pktout, "window-change"); - ssh2_pkt_addbool(pktout, 0); + pktout = ssh2_chanreq_init(ssh->mainchan, "window-change", + NULL, NULL); ssh2_pkt_adduint32(pktout, ssh->term_width); ssh2_pkt_adduint32(pktout, ssh->term_height); ssh2_pkt_adduint32(pktout, 0); ssh2_pkt_adduint32(pktout, 0); ssh2_pkt_send(ssh, pktout); @@ -9903,11 +10813,11 @@ if (!(ssh->remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE)) ADD_SPECIALS(ssh1_ignore_special); } else if (ssh->version == 2) { if (!(ssh->remote_bugs & BUG_CHOKES_ON_SSH2_IGNORE)) ADD_SPECIALS(ssh2_ignore_special); - if (!(ssh->remote_bugs & BUG_SSH2_REKEY)) + if (!(ssh->remote_bugs & BUG_SSH2_REKEY) && !ssh->bare_connection) ADD_SPECIALS(ssh2_rekey_special); if (ssh->mainchan) ADD_SPECIALS(ssh2_session_specials); } /* else we're not ready yet */ @@ -9941,13 +10851,11 @@ return; } if (ssh->version == 1) { send_packet(ssh, SSH1_CMSG_EOF, PKT_END); } else if (ssh->mainchan) { - struct Packet *pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_EOF); - ssh2_pkt_adduint32(pktout, ssh->mainchan->remoteid); - ssh2_pkt_send(ssh, pktout); + sshfwd_write_eof(ssh->mainchan); ssh->send_ok = 0; /* now stop trying to read from stdin */ } logevent("Sent EOF message"); } else if (code == TS_PING || code == TS_NOP) { if (ssh->state == SSH_STATE_CLOSED @@ -9961,23 +10869,21 @@ ssh2_pkt_addstring_start(pktout); ssh2_pkt_send_noqueue(ssh, pktout); } } } else if (code == TS_REKEY) { - if (!ssh->kex_in_progress && ssh->version == 2) { + if (!ssh->kex_in_progress && !ssh->bare_connection && + ssh->version == 2) { do_ssh2_transport(ssh, "at user request", -1, NULL); } } else if (code == TS_BRK) { if (ssh->state == SSH_STATE_CLOSED || ssh->state == SSH_STATE_PREPACKET) return; if (ssh->version == 1) { logevent("Unable to send BREAK signal in SSH-1"); } else if (ssh->mainchan) { - pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST); - ssh2_pkt_adduint32(pktout, ssh->mainchan->remoteid); - ssh2_pkt_addstring(pktout, "break"); - ssh2_pkt_addbool(pktout, 0); + pktout = ssh2_chanreq_init(ssh->mainchan, "break", NULL, NULL); ssh2_pkt_adduint32(pktout, 0); /* default break length */ ssh2_pkt_send(ssh, pktout); } } else { /* Is is a POSIX signal? */ @@ -9998,14 +10904,11 @@ /* The SSH-2 protocol does in principle support arbitrary named * signals, including signame@domain, but we don't support those. */ if (signame) { /* It's a signal. */ if (ssh->version == 2 && ssh->mainchan) { - pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST); - ssh2_pkt_adduint32(pktout, ssh->mainchan->remoteid); - ssh2_pkt_addstring(pktout, "signal"); - ssh2_pkt_addbool(pktout, 0); + pktout = ssh2_chanreq_init(ssh->mainchan, "signal", NULL, NULL); ssh2_pkt_addstring(pktout, signame); ssh2_pkt_send(ssh, pktout); logeventf(ssh, "Sent signal SIG%s", signame); } } else { @@ -10012,24 +10915,59 @@ /* Never heard of it. Do nothing */ } } } -void *new_sock_channel(void *handle, Socket s) +void *new_sock_channel(void *handle, struct PortForwarding *pf) { Ssh ssh = (Ssh) handle; struct ssh_channel *c; c = snew(struct ssh_channel); c->ssh = ssh; ssh2_channel_init(c); c->halfopen = TRUE; c->type = CHAN_SOCKDATA_DORMANT;/* identify channel type */ - c->u.pfd.s = s; + c->u.pfd.pf = pf; add234(ssh->channels, c); return c; } + +unsigned ssh_alloc_sharing_channel(Ssh ssh, void *sharing_ctx) +{ + struct ssh_channel *c; + c = snew(struct ssh_channel); + + c->ssh = ssh; + ssh2_channel_init(c); + c->type = CHAN_SHARING; + c->u.sharing.ctx = sharing_ctx; + add234(ssh->channels, c); + return c->localid; +} + +void ssh_delete_sharing_channel(Ssh ssh, unsigned localid) +{ + struct ssh_channel *c; + + c = find234(ssh->channels, &localid, ssh_channelfind); + if (c) + ssh_channel_destroy(c); +} + +void ssh_send_packet_from_downstream(Ssh ssh, unsigned id, int type, + const void *data, int datalen, + const char *additional_log_text) +{ + struct Packet *pkt; + + pkt = ssh2_pkt_init(type); + pkt->downstream_id = id; + pkt->additional_log_text = additional_log_text; + ssh2_pkt_adddata(pkt, data, datalen); + ssh2_pkt_send(ssh, pkt); +} /* * This is called when stdout/stderr (the entity to which * from_backend sends data) manages to clear some backlog. */ @@ -10046,43 +10984,45 @@ } else { if (ssh->mainchan) { ssh2_set_window(ssh->mainchan, bufsize < ssh->mainchan->v.v2.locmaxwin ? ssh->mainchan->v.v2.locmaxwin - bufsize : 0); - if (ssh->cfg.ssh_simple) + if (ssh_is_simple(ssh)) buflimit = 0; else buflimit = ssh->mainchan->v.v2.locmaxwin; if (ssh->mainchan->throttling_conn && bufsize <= buflimit) { ssh->mainchan->throttling_conn = 0; ssh_throttle_conn(ssh, -1); } } } + + /* + * Now process any SSH connection data that was stashed in our + * queue while we were frozen. + */ + ssh_process_queued_incoming_data(ssh); } void ssh_send_port_open(void *channel, char *hostname, int port, char *org) { struct ssh_channel *c = (struct ssh_channel *)channel; Ssh ssh = c->ssh; struct Packet *pktout; - logeventf(ssh, "Opening forwarded connection to %s:%d", hostname, port); + logeventf(ssh, "Opening connection to %s:%d for %s", hostname, port, org); if (ssh->version == 1) { send_packet(ssh, SSH1_MSG_PORT_OPEN, PKT_INT, c->localid, PKT_STR, hostname, PKT_INT, port, /* PKT_STR, , */ PKT_END); } else { - pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_OPEN); - ssh2_pkt_addstring(pktout, "direct-tcpip"); - ssh2_pkt_adduint32(pktout, c->localid); - ssh2_pkt_adduint32(pktout, c->v.v2.locwindow);/* our window size */ - ssh2_pkt_adduint32(pktout, OUR_V2_MAXPKT); /* our max pkt size */ + pktout = ssh2_chanopen_init(c, "direct-tcpip"); ssh2_pkt_addstring(pktout, hostname); ssh2_pkt_adduint32(pktout, port); /* * We make up values for the originator data; partly it's * too much hassle to keep track, and partly I'm not Index: ssh.h ================================================================== --- ssh.h +++ ssh.h @@ -5,19 +5,58 @@ #include "tree234.h" #include "network.h" #include "int64.h" #include "misc.h" -/* PuTTY SC start */ -#include "pkcs11.h" -/* PuTTY SC end */ - struct ssh_channel; +typedef struct ssh_tag *Ssh; -extern void sshfwd_close(struct ssh_channel *c); extern int sshfwd_write(struct ssh_channel *c, char *, int); +extern void sshfwd_write_eof(struct ssh_channel *c); +extern void sshfwd_unclean_close(struct ssh_channel *c, const char *err); extern void sshfwd_unthrottle(struct ssh_channel *c, int bufsize); +Conf *sshfwd_get_conf(struct ssh_channel *c); +void sshfwd_x11_sharing_handover(struct ssh_channel *c, + void *share_cs, void *share_chan, + const char *peer_addr, int peer_port, + int endian, int protomajor, int protominor, + const void *initial_data, int initial_len); +void sshfwd_x11_is_local(struct ssh_channel *c); + +extern Socket ssh_connection_sharing_init(const char *host, int port, + Conf *conf, Ssh ssh, void **state); +void share_got_pkt_from_server(void *ctx, int type, + unsigned char *pkt, int pktlen); +void share_activate(void *state, const char *server_verstring); +void sharestate_free(void *state); +int share_ndownstreams(void *state); + +void ssh_connshare_log(Ssh ssh, int event, const char *logtext, + const char *ds_err, const char *us_err); +unsigned ssh_alloc_sharing_channel(Ssh ssh, void *sharing_ctx); +void ssh_delete_sharing_channel(Ssh ssh, unsigned localid); +int ssh_alloc_sharing_rportfwd(Ssh ssh, const char *shost, int sport, + void *share_ctx); +void ssh_sharing_queue_global_request(Ssh ssh, void *share_ctx); +struct X11FakeAuth *ssh_sharing_add_x11_display(Ssh ssh, int authtype, + void *share_cs, + void *share_chan); +void ssh_sharing_remove_x11_display(Ssh ssh, struct X11FakeAuth *auth); +void ssh_send_packet_from_downstream(Ssh ssh, unsigned id, int type, + const void *pkt, int pktlen, + const char *additional_log_text); +void ssh_sharing_downstream_connected(Ssh ssh, unsigned id); +void ssh_sharing_downstream_disconnected(Ssh ssh, unsigned id); +void ssh_sharing_logf(Ssh ssh, unsigned id, const char *logfmt, ...); +int ssh_agent_forwarding_permitted(Ssh ssh); +void share_setup_x11_channel(void *csv, void *chanv, + unsigned upstream_id, unsigned server_id, + unsigned server_currwin, unsigned server_maxpkt, + unsigned client_adjusted_window, + const char *peer_addr, int peer_port, int endian, + int protomajor, int protominor, + const void *initial_data, int initial_len); /* * Useful thing. */ #ifndef lenof @@ -134,13 +173,13 @@ unsigned char block[64]; int blkused; uint32 lenhi, lenlo; } SHA_State; void SHA_Init(SHA_State * s); -void SHA_Bytes(SHA_State * s, void *p, int len); +void SHA_Bytes(SHA_State * s, const void *p, int len); void SHA_Final(SHA_State * s, unsigned char *output); -void SHA_Simple(void *p, int len, unsigned char *output); +void SHA_Simple(const void *p, int len, unsigned char *output); void hmac_sha1_simple(void *key, int keylen, void *data, int datalen, unsigned char *output); typedef struct { uint32 h[8]; @@ -298,10 +337,11 @@ extern const struct ssh_mac ssh_hmac_md5; extern const struct ssh_mac ssh_hmac_sha1; extern const struct ssh_mac ssh_hmac_sha1_buggy; extern const struct ssh_mac ssh_hmac_sha1_96; extern const struct ssh_mac ssh_hmac_sha1_96_buggy; +extern const struct ssh_mac ssh_hmac_sha256; void *aes_make_context(void); void aes_free_context(void *handle); void aes128_key(void *handle, unsigned char *key); void aes192_key(void *handle, unsigned char *key); @@ -329,30 +369,32 @@ int random_byte(void); void random_add_noise(void *noise, int length); void random_add_heavynoise(void *noise, int length); void logevent(void *, const char *); + +struct PortForwarding; /* Allocate and register a new channel for port forwarding */ -void *new_sock_channel(void *handle, Socket s); +void *new_sock_channel(void *handle, struct PortForwarding *pf); void ssh_send_port_open(void *channel, char *hostname, int port, char *org); /* Exports from portfwd.c */ -extern const char *pfd_newconnect(Socket * s, char *hostname, int port, - void *c, const Config *cfg, - int addressfamily); +extern char *pfd_connect(struct PortForwarding **pf, char *hostname, int port, + void *c, Conf *conf, int addressfamily); +extern void pfd_close(struct PortForwarding *); +extern int pfd_send(struct PortForwarding *, char *data, int len); +extern void pfd_send_eof(struct PortForwarding *); +extern void pfd_confirm(struct PortForwarding *); +extern void pfd_unthrottle(struct PortForwarding *); +extern void pfd_override_throttle(struct PortForwarding *, int enable); +struct PortListener; /* desthost == NULL indicates dynamic (SOCKS) port forwarding */ -extern const char *pfd_addforward(char *desthost, int destport, char *srcaddr, - int port, void *backhandle, - const Config *cfg, void **sockdata, - int address_family); -extern void pfd_close(Socket s); -extern void pfd_terminate(void *sockdata); -extern int pfd_send(Socket s, char *data, int len); -extern void pfd_confirm(Socket s); -extern void pfd_unthrottle(Socket s); -extern void pfd_override_throttle(Socket s, int enable); +extern char *pfl_listen(char *desthost, int destport, char *srcaddr, + int port, void *backhandle, Conf *conf, + struct PortListener **pl, int address_family); +extern void pfl_terminate(struct PortListener *); /* Exports from x11fwd.c */ enum { X11_TRANS_IPV4 = 0, X11_TRANS_IPV6 = 6, X11_TRANS_UNIX = 256 }; @@ -369,48 +411,66 @@ * gubbins */ SockAddr addr; int port; char *realhost; - /* Auth details we invented for the virtual display on the SSH server. */ - int remoteauthproto; - unsigned char *remoteauthdata; - int remoteauthdatalen; - char *remoteauthprotoname; - char *remoteauthdatastring; - /* Our local auth details for talking to the real X display. */ int localauthproto; unsigned char *localauthdata; int localauthdatalen; +}; +struct X11FakeAuth { + /* Auth details we invented for a virtual display on the SSH server. */ + int proto; + unsigned char *data; + int datalen; + char *protoname; + char *datastring; + + /* The encrypted form of the first block, in XDM-AUTHORIZATION-1. + * Used as part of the key when these structures are organised + * into a tree. See x11_invent_fake_auth for explanation. */ + unsigned char *xa1_firstblock; /* * Used inside x11fwd.c to remember recently seen * XDM-AUTHORIZATION-1 strings, to avoid replay attacks. */ tree234 *xdmseen; + + /* + * What to do with an X connection matching this auth data. + */ + struct X11Display *disp; + void *share_cs, *share_chan; }; +void *x11_make_greeting(int endian, int protomajor, int protominor, + int auth_proto, const void *auth_data, int auth_len, + const char *peer_ip, int peer_port, + int *outlen); +int x11_authcmp(void *av, void *bv); /* for putting X11FakeAuth in a tree234 */ /* * x11_setup_display() parses the display variable and fills in an * X11Display structure. Some remote auth details are invented; * the supplied authtype parameter configures the preferred * authorisation protocol to use at the remote end. The local auth * details are looked up by calling platform_get_x11_auth. */ -extern struct X11Display *x11_setup_display(char *display, int authtype, - const Config *); +extern struct X11Display *x11_setup_display(char *display, Conf *); void x11_free_display(struct X11Display *disp); -extern const char *x11_init(Socket *, struct X11Display *, void *, - const char *, int, const Config *); -extern void x11_close(Socket); -extern int x11_send(Socket, char *, int); -extern void x11_unthrottle(Socket s); -extern void x11_override_throttle(Socket s, int enable); +struct X11FakeAuth *x11_invent_fake_auth(tree234 *t, int authtype); +void x11_free_fake_auth(struct X11FakeAuth *auth); +struct X11Connection; /* opaque outside x11fwd.c */ +struct X11Connection *x11_init(tree234 *authtree, void *, const char *, int); +extern void x11_close(struct X11Connection *); +extern int x11_send(struct X11Connection *, char *, int); +extern void x11_send_eof(struct X11Connection *s); +extern void x11_unthrottle(struct X11Connection *s); +extern void x11_override_throttle(struct X11Connection *s, int enable); char *x11_display(const char *display); /* Platform-dependent X11 functions */ -extern void platform_get_x11_auth(struct X11Display *display, - const Config *); +extern void platform_get_x11_auth(struct X11Display *display, Conf *); /* examine a mostly-filled-in X11Display and fill in localauth* */ extern const int platform_uses_x11_unix_by_default; /* choose default X transport in the absence of a specified one */ SockAddr platform_get_x11_unix_address(const char *path, int displaynum); /* make up a SockAddr naming the address for displaynum */ @@ -430,10 +490,12 @@ * for local oddities like Unix-domain socket transport, and * calling this function to do the rest of the work. */ void x11_get_auth_from_authfile(struct X11Display *display, const char *authfilename); +int x11_identify_auth_proto(const char *proto); +void *x11_dehexify(const char *hex, int *outlen); Bignum copybn(Bignum b); Bignum bn_power_2(int n); void bn_restore_invariant(Bignum b); Bignum bignum_from_long(unsigned long n); @@ -533,12 +595,14 @@ void aes256_encrypt_pubkey(unsigned char *key, unsigned char *blk, int len); void aes256_decrypt_pubkey(unsigned char *key, unsigned char *blk, int len); -void des_encrypt_xdmauth(unsigned char *key, unsigned char *blk, int len); -void des_decrypt_xdmauth(unsigned char *key, unsigned char *blk, int len); +void des_encrypt_xdmauth(const unsigned char *key, + unsigned char *blk, int len); +void des_decrypt_xdmauth(const unsigned char *key, + unsigned char *blk, int len); /* * For progress updates in the key generation utility. */ #define PROGFN_INITIALISE 1 @@ -552,11 +616,12 @@ int rsa_generate(struct RSAKey *key, int bits, progfn_t pfn, void *pfnparam); int dsa_generate(struct dss_key *key, int bits, progfn_t pfn, void *pfnparam); Bignum primegen(int bits, int modulus, int residue, Bignum factor, - int phase, progfn_t pfn, void *pfnparam); + int phase, progfn_t pfn, void *pfnparam, unsigned firstbits); +void invent_firstbits(unsigned *one, unsigned *two); /* * zlib compression. */ @@ -567,10 +632,131 @@ int zlib_compress_block(void *, unsigned char *block, int len, unsigned char **outblock, int *outlen); int zlib_decompress_block(void *, unsigned char *block, int len, unsigned char **outblock, int *outlen); +/* + * Connection-sharing API provided by platforms. This function must + * either: + * - return SHARE_NONE and do nothing + * - return SHARE_DOWNSTREAM and set *sock to a Socket connected to + * downplug + * - return SHARE_UPSTREAM and set *sock to a Socket connected to + * upplug. + */ +enum { SHARE_NONE, SHARE_DOWNSTREAM, SHARE_UPSTREAM }; +int platform_ssh_share(const char *name, Conf *conf, + Plug downplug, Plug upplug, Socket *sock, + char **logtext, char **ds_err, char **us_err, + int can_upstream, int can_downstream); +void platform_ssh_share_cleanup(const char *name); + +/* + * SSH-1 message type codes. + */ +#define SSH1_MSG_DISCONNECT 1 /* 0x1 */ +#define SSH1_SMSG_PUBLIC_KEY 2 /* 0x2 */ +#define SSH1_CMSG_SESSION_KEY 3 /* 0x3 */ +#define SSH1_CMSG_USER 4 /* 0x4 */ +#define SSH1_CMSG_AUTH_RSA 6 /* 0x6 */ +#define SSH1_SMSG_AUTH_RSA_CHALLENGE 7 /* 0x7 */ +#define SSH1_CMSG_AUTH_RSA_RESPONSE 8 /* 0x8 */ +#define SSH1_CMSG_AUTH_PASSWORD 9 /* 0x9 */ +#define SSH1_CMSG_REQUEST_PTY 10 /* 0xa */ +#define SSH1_CMSG_WINDOW_SIZE 11 /* 0xb */ +#define SSH1_CMSG_EXEC_SHELL 12 /* 0xc */ +#define SSH1_CMSG_EXEC_CMD 13 /* 0xd */ +#define SSH1_SMSG_SUCCESS 14 /* 0xe */ +#define SSH1_SMSG_FAILURE 15 /* 0xf */ +#define SSH1_CMSG_STDIN_DATA 16 /* 0x10 */ +#define SSH1_SMSG_STDOUT_DATA 17 /* 0x11 */ +#define SSH1_SMSG_STDERR_DATA 18 /* 0x12 */ +#define SSH1_CMSG_EOF 19 /* 0x13 */ +#define SSH1_SMSG_EXIT_STATUS 20 /* 0x14 */ +#define SSH1_MSG_CHANNEL_OPEN_CONFIRMATION 21 /* 0x15 */ +#define SSH1_MSG_CHANNEL_OPEN_FAILURE 22 /* 0x16 */ +#define SSH1_MSG_CHANNEL_DATA 23 /* 0x17 */ +#define SSH1_MSG_CHANNEL_CLOSE 24 /* 0x18 */ +#define SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION 25 /* 0x19 */ +#define SSH1_SMSG_X11_OPEN 27 /* 0x1b */ +#define SSH1_CMSG_PORT_FORWARD_REQUEST 28 /* 0x1c */ +#define SSH1_MSG_PORT_OPEN 29 /* 0x1d */ +#define SSH1_CMSG_AGENT_REQUEST_FORWARDING 30 /* 0x1e */ +#define SSH1_SMSG_AGENT_OPEN 31 /* 0x1f */ +#define SSH1_MSG_IGNORE 32 /* 0x20 */ +#define SSH1_CMSG_EXIT_CONFIRMATION 33 /* 0x21 */ +#define SSH1_CMSG_X11_REQUEST_FORWARDING 34 /* 0x22 */ +#define SSH1_CMSG_AUTH_RHOSTS_RSA 35 /* 0x23 */ +#define SSH1_MSG_DEBUG 36 /* 0x24 */ +#define SSH1_CMSG_REQUEST_COMPRESSION 37 /* 0x25 */ +#define SSH1_CMSG_AUTH_TIS 39 /* 0x27 */ +#define SSH1_SMSG_AUTH_TIS_CHALLENGE 40 /* 0x28 */ +#define SSH1_CMSG_AUTH_TIS_RESPONSE 41 /* 0x29 */ +#define SSH1_CMSG_AUTH_CCARD 70 /* 0x46 */ +#define SSH1_SMSG_AUTH_CCARD_CHALLENGE 71 /* 0x47 */ +#define SSH1_CMSG_AUTH_CCARD_RESPONSE 72 /* 0x48 */ + +#define SSH1_AUTH_RHOSTS 1 /* 0x1 */ +#define SSH1_AUTH_RSA 2 /* 0x2 */ +#define SSH1_AUTH_PASSWORD 3 /* 0x3 */ +#define SSH1_AUTH_RHOSTS_RSA 4 /* 0x4 */ +#define SSH1_AUTH_TIS 5 /* 0x5 */ +#define SSH1_AUTH_CCARD 16 /* 0x10 */ + +#define SSH1_PROTOFLAG_SCREEN_NUMBER 1 /* 0x1 */ +/* Mask for protoflags we will echo back to server if seen */ +#define SSH1_PROTOFLAGS_SUPPORTED 0 /* 0x1 */ + +/* + * SSH-2 message type codes. + */ +#define SSH2_MSG_DISCONNECT 1 /* 0x1 */ +#define SSH2_MSG_IGNORE 2 /* 0x2 */ +#define SSH2_MSG_UNIMPLEMENTED 3 /* 0x3 */ +#define SSH2_MSG_DEBUG 4 /* 0x4 */ +#define SSH2_MSG_SERVICE_REQUEST 5 /* 0x5 */ +#define SSH2_MSG_SERVICE_ACCEPT 6 /* 0x6 */ +#define SSH2_MSG_KEXINIT 20 /* 0x14 */ +#define SSH2_MSG_NEWKEYS 21 /* 0x15 */ +#define SSH2_MSG_KEXDH_INIT 30 /* 0x1e */ +#define SSH2_MSG_KEXDH_REPLY 31 /* 0x1f */ +#define SSH2_MSG_KEX_DH_GEX_REQUEST 30 /* 0x1e */ +#define SSH2_MSG_KEX_DH_GEX_GROUP 31 /* 0x1f */ +#define SSH2_MSG_KEX_DH_GEX_INIT 32 /* 0x20 */ +#define SSH2_MSG_KEX_DH_GEX_REPLY 33 /* 0x21 */ +#define SSH2_MSG_KEXRSA_PUBKEY 30 /* 0x1e */ +#define SSH2_MSG_KEXRSA_SECRET 31 /* 0x1f */ +#define SSH2_MSG_KEXRSA_DONE 32 /* 0x20 */ +#define SSH2_MSG_USERAUTH_REQUEST 50 /* 0x32 */ +#define SSH2_MSG_USERAUTH_FAILURE 51 /* 0x33 */ +#define SSH2_MSG_USERAUTH_SUCCESS 52 /* 0x34 */ +#define SSH2_MSG_USERAUTH_BANNER 53 /* 0x35 */ +#define SSH2_MSG_USERAUTH_PK_OK 60 /* 0x3c */ +#define SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ 60 /* 0x3c */ +#define SSH2_MSG_USERAUTH_INFO_REQUEST 60 /* 0x3c */ +#define SSH2_MSG_USERAUTH_INFO_RESPONSE 61 /* 0x3d */ +#define SSH2_MSG_GLOBAL_REQUEST 80 /* 0x50 */ +#define SSH2_MSG_REQUEST_SUCCESS 81 /* 0x51 */ +#define SSH2_MSG_REQUEST_FAILURE 82 /* 0x52 */ +#define SSH2_MSG_CHANNEL_OPEN 90 /* 0x5a */ +#define SSH2_MSG_CHANNEL_OPEN_CONFIRMATION 91 /* 0x5b */ +#define SSH2_MSG_CHANNEL_OPEN_FAILURE 92 /* 0x5c */ +#define SSH2_MSG_CHANNEL_WINDOW_ADJUST 93 /* 0x5d */ +#define SSH2_MSG_CHANNEL_DATA 94 /* 0x5e */ +#define SSH2_MSG_CHANNEL_EXTENDED_DATA 95 /* 0x5f */ +#define SSH2_MSG_CHANNEL_EOF 96 /* 0x60 */ +#define SSH2_MSG_CHANNEL_CLOSE 97 /* 0x61 */ +#define SSH2_MSG_CHANNEL_REQUEST 98 /* 0x62 */ +#define SSH2_MSG_CHANNEL_SUCCESS 99 /* 0x63 */ +#define SSH2_MSG_CHANNEL_FAILURE 100 /* 0x64 */ +#define SSH2_MSG_USERAUTH_GSSAPI_RESPONSE 60 +#define SSH2_MSG_USERAUTH_GSSAPI_TOKEN 61 +#define SSH2_MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE 63 +#define SSH2_MSG_USERAUTH_GSSAPI_ERROR 64 +#define SSH2_MSG_USERAUTH_GSSAPI_ERRTOK 65 +#define SSH2_MSG_USERAUTH_GSSAPI_MIC 66 + /* * SSH-1 agent messages. */ #define SSH1_AGENTC_REQUEST_RSA_IDENTITIES 1 #define SSH1_AGENT_RSA_IDENTITIES_ANSWER 2 @@ -595,10 +781,36 @@ #define SSH2_AGENT_SIGN_RESPONSE 14 #define SSH2_AGENTC_ADD_IDENTITY 17 #define SSH2_AGENTC_REMOVE_IDENTITY 18 #define SSH2_AGENTC_REMOVE_ALL_IDENTITIES 19 +/* + * Assorted other SSH-related enumerations. + */ +#define SSH2_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT 1 /* 0x1 */ +#define SSH2_DISCONNECT_PROTOCOL_ERROR 2 /* 0x2 */ +#define SSH2_DISCONNECT_KEY_EXCHANGE_FAILED 3 /* 0x3 */ +#define SSH2_DISCONNECT_HOST_AUTHENTICATION_FAILED 4 /* 0x4 */ +#define SSH2_DISCONNECT_MAC_ERROR 5 /* 0x5 */ +#define SSH2_DISCONNECT_COMPRESSION_ERROR 6 /* 0x6 */ +#define SSH2_DISCONNECT_SERVICE_NOT_AVAILABLE 7 /* 0x7 */ +#define SSH2_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED 8 /* 0x8 */ +#define SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE 9 /* 0x9 */ +#define SSH2_DISCONNECT_CONNECTION_LOST 10 /* 0xa */ +#define SSH2_DISCONNECT_BY_APPLICATION 11 /* 0xb */ +#define SSH2_DISCONNECT_TOO_MANY_CONNECTIONS 12 /* 0xc */ +#define SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER 13 /* 0xd */ +#define SSH2_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE 14 /* 0xe */ +#define SSH2_DISCONNECT_ILLEGAL_USER_NAME 15 /* 0xf */ + +#define SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED 1 /* 0x1 */ +#define SSH2_OPEN_CONNECT_FAILED 2 /* 0x2 */ +#define SSH2_OPEN_UNKNOWN_CHANNEL_TYPE 3 /* 0x3 */ +#define SSH2_OPEN_RESOURCE_SHORTAGE 4 /* 0x4 */ + +#define SSH2_EXTENDED_DATA_STDERR 1 /* 0x1 */ + /* * Need this to warn about support for the original SSH-2 keyfile * format. */ void old_keyfile_warning(void); Index: sshaes.c ================================================================== --- sshaes.c +++ sshaes.c @@ -1155,20 +1155,20 @@ { AESContext ctx; aes_setup(&ctx, 16, key, 32); memset(ctx.iv, 0, sizeof(ctx.iv)); aes_encrypt_cbc(blk, len, &ctx); - memset(&ctx, 0, sizeof(ctx)); + smemclr(&ctx, sizeof(ctx)); } void aes256_decrypt_pubkey(unsigned char *key, unsigned char *blk, int len) { AESContext ctx; aes_setup(&ctx, 16, key, 32); memset(ctx.iv, 0, sizeof(ctx.iv)); aes_decrypt_cbc(blk, len, &ctx); - memset(&ctx, 0, sizeof(ctx)); + smemclr(&ctx, sizeof(ctx)); } static const struct ssh2_cipher ssh_aes128_ctr = { aes_make_context, aes_free_context, aes_iv, aes128_key, aes_ssh2_sdctr, aes_ssh2_sdctr, Index: ssharcf.c ================================================================== --- ssharcf.c +++ ssharcf.c @@ -73,11 +73,11 @@ static void arcfour_stir(ArcfourContext *ctx) { unsigned char *junk = snewn(1536, unsigned char); memset(junk, 0, 1536); arcfour_block(ctx, junk, 1536); - memset(junk, 0, 1536); + smemclr(junk, 1536); sfree(junk); } static void arcfour128_key(void *handle, unsigned char *key) { Index: sshbn.c ================================================================== --- sshbn.c +++ sshbn.c @@ -4,10 +4,11 @@ #include #include #include #include +#include #include "misc.h" /* * Usage notes: @@ -118,11 +119,15 @@ Bignum Zero = bnZero, One = bnOne; static Bignum newbn(int length) { - Bignum b = snewn(length + 1, BignumInt); + Bignum b; + + assert(length >= 0 && length < INT_MAX / BIGNUM_INT_BITS); + + b = snewn(length + 1, BignumInt); if (!b) abort(); /* FIXME */ memset(b, 0, (length + 1) * sizeof(*b)); b[0] = length; return b; @@ -146,17 +151,21 @@ void freebn(Bignum b) { /* * Burn the evidence, just in case. */ - memset(b, 0, sizeof(b[0]) * (b[0] + 1)); + smemclr(b, sizeof(b[0]) * (b[0] + 1)); sfree(b); } Bignum bn_power_2(int n) { - Bignum ret = newbn(n / BIGNUM_INT_BITS + 1); + Bignum ret; + + assert(n >= 0); + + ret = newbn(n / BIGNUM_INT_BITS + 1); bignum_set_bit(ret, n, 1); return ret; } /* @@ -596,10 +605,11 @@ BignumDblInt addend; addend = (BignumDblInt)n << bshift; while (addend) { + assert(word <= number[0]); addend += number[word]; number[word] = (BignumInt) addend & BIGNUM_INT_MASK; addend >>= BIGNUM_INT_BITS; word++; } @@ -622,10 +632,11 @@ BignumInt m0, m1; unsigned int h; int i, k; m0 = m[0]; + assert(m0 >> (BIGNUM_INT_BITS-1) == 1); if (mlen > 1) m1 = m[1]; else m1 = 0; @@ -813,24 +824,19 @@ result[result[0] - i] = a[i + mlen]; while (result[0] > 1 && result[result[0]] == 0) result[0]--; /* Free temporary arrays */ - for (i = 0; i < 2 * mlen; i++) - a[i] = 0; + smemclr(a, 2 * mlen * sizeof(*a)); sfree(a); - for (i = 0; i < scratchlen; i++) - scratch[i] = 0; + smemclr(scratch, scratchlen * sizeof(*scratch)); sfree(scratch); - for (i = 0; i < 2 * mlen; i++) - b[i] = 0; + smemclr(b, 2 * mlen * sizeof(*b)); sfree(b); - for (i = 0; i < mlen; i++) - m[i] = 0; + smemclr(m, mlen * sizeof(*m)); sfree(m); - for (i = 0; i < mlen; i++) - n[i] = 0; + smemclr(n, mlen * sizeof(*n)); sfree(n); freebn(base); return result; @@ -871,10 +877,11 @@ * below.) */ len = mod[0]; r = bn_power_2(BIGNUM_INT_BITS * len); inv = modinv(mod, r); + assert(inv); /* cannot fail, since mod is odd and r is a power of 2 */ /* * Multiply the base by r mod n, to get it into Montgomery * representation. */ @@ -963,27 +970,21 @@ result[result[0] - i] = a[i + len]; while (result[0] > 1 && result[result[0]] == 0) result[0]--; /* Free temporary arrays */ - for (i = 0; i < scratchlen; i++) - scratch[i] = 0; + smemclr(scratch, scratchlen * sizeof(*scratch)); sfree(scratch); - for (i = 0; i < 2 * len; i++) - a[i] = 0; + smemclr(a, 2 * len * sizeof(*a)); sfree(a); - for (i = 0; i < 2 * len; i++) - b[i] = 0; + smemclr(b, 2 * len * sizeof(*b)); sfree(b); - for (i = 0; i < len; i++) - mninv[i] = 0; + smemclr(mninv, len * sizeof(*mninv)); sfree(mninv); - for (i = 0; i < len; i++) - n[i] = 0; + smemclr(n, len * sizeof(*n)); sfree(n); - for (i = 0; i < len; i++) - x[i] = 0; + smemclr(x, len * sizeof(*x)); sfree(x); return result; } @@ -997,10 +998,16 @@ BignumInt *a, *n, *m, *o, *scratch; int mshift, scratchlen; int pqlen, mlen, rlen, i, j; Bignum result; + /* + * The most significant word of mod needs to be non-zero. It + * should already be, but let's make sure. + */ + assert(mod[mod[0]] != 0); + /* Allocate m of size mlen, copy mod to m */ /* We use big endian internally */ mlen = mod[0]; m = snewn(mlen, BignumInt); for (j = 0; j < mlen; j++) @@ -1016,10 +1023,17 @@ m[mlen - 1] = m[mlen - 1] << mshift; } pqlen = (p[0] > q[0] ? p[0] : q[0]); + /* + * Make sure that we're allowing enough space. The shifting below + * will underflow the vectors we allocate if pqlen is too small. + */ + if (2*pqlen <= mlen) + pqlen = mlen/2 + 1; + /* Allocate n of size pqlen, copy p to n */ n = snewn(pqlen, BignumInt); i = pqlen - p[0]; for (j = 0; j < i; j++) n[j] = 0; @@ -1062,24 +1076,19 @@ result[result[0] - i] = a[i + 2 * pqlen - rlen]; while (result[0] > 1 && result[result[0]] == 0) result[0]--; /* Free temporary arrays */ - for (i = 0; i < scratchlen; i++) - scratch[i] = 0; + smemclr(scratch, scratchlen * sizeof(*scratch)); sfree(scratch); - for (i = 0; i < 2 * pqlen; i++) - a[i] = 0; + smemclr(a, 2 * pqlen * sizeof(*a)); sfree(a); - for (i = 0; i < mlen; i++) - m[i] = 0; + smemclr(m, mlen * sizeof(*m)); sfree(m); - for (i = 0; i < pqlen; i++) - n[i] = 0; + smemclr(n, pqlen * sizeof(*n)); sfree(n); - for (i = 0; i < pqlen; i++) - o[i] = 0; + smemclr(o, pqlen * sizeof(*o)); sfree(o); return result; } @@ -1094,10 +1103,16 @@ { BignumInt *n, *m; int mshift; int plen, mlen, i, j; + /* + * The most significant word of mod needs to be non-zero. It + * should already be, but let's make sure. + */ + assert(mod[mod[0]] != 0); + /* Allocate m of size mlen, copy mod to m */ /* We use big endian internally */ mlen = mod[0]; m = snewn(mlen, BignumInt); for (j = 0; j < mlen; j++) @@ -1145,15 +1160,13 @@ result[i] = j >= 0 ? n[j] : 0; } } /* Free temporary arrays */ - for (i = 0; i < mlen; i++) - m[i] = 0; + smemclr(m, mlen * sizeof(*m)); sfree(m); - for (i = 0; i < plen; i++) - n[i] = 0; + smemclr(n, plen * sizeof(*n)); sfree(n); } /* * Decrement a number. @@ -1168,10 +1181,12 @@ Bignum bignum_from_bytes(const unsigned char *data, int nbytes) { Bignum result; int w, i; + + assert(nbytes >= 0 && nbytes < INT_MAX/8); w = (nbytes + BIGNUM_INT_BYTES - 1) / BIGNUM_INT_BYTES; /* bytes->words */ result = newbn(w); for (i = 1; i <= w; i++) @@ -1245,11 +1260,11 @@ /* * Return a byte from a bignum; 0 is least significant, etc. */ int bignum_byte(Bignum bn, int i) { - if (i >= (int)(BIGNUM_INT_BYTES * bn[0])) + if (i < 0 || i >= (int)(BIGNUM_INT_BYTES * bn[0])) return 0; /* beyond the end */ else return (bn[i / BIGNUM_INT_BYTES + 1] >> ((i % BIGNUM_INT_BYTES)*8)) & 0xFF; } @@ -1257,11 +1272,11 @@ /* * Return a bit from a bignum; 0 is least significant, etc. */ int bignum_bit(Bignum bn, int i) { - if (i >= (int)(BIGNUM_INT_BITS * bn[0])) + if (i < 0 || i >= (int)(BIGNUM_INT_BITS * bn[0])) return 0; /* beyond the end */ else return (bn[i / BIGNUM_INT_BITS + 1] >> (i % BIGNUM_INT_BITS)) & 1; } @@ -1268,11 +1283,11 @@ /* * Set a bit in a bignum; 0 is least significant, etc. */ void bignum_set_bit(Bignum bn, int bitnum, int value) { - if (bitnum >= (int)(BIGNUM_INT_BITS * bn[0])) + if (bitnum < 0 || bitnum >= (int)(BIGNUM_INT_BITS * bn[0])) abort(); /* beyond the end */ else { int v = bitnum / BIGNUM_INT_BITS + 1; int mask = 1 << (bitnum % BIGNUM_INT_BITS); if (value) @@ -1304,11 +1319,22 @@ * Compare two bignums. Returns like strcmp. */ int bignum_cmp(Bignum a, Bignum b) { int amax = a[0], bmax = b[0]; - int i = (amax > bmax ? amax : bmax); + int i; + + /* Annoyingly we have two representations of zero */ + if (amax == 1 && a[amax] == 0) + amax = 0; + if (bmax == 1 && b[bmax] == 0) + bmax = 0; + + assert(amax == 0 || a[amax] != 0); + assert(bmax == 0 || b[bmax] != 0); + + i = (amax > bmax ? amax : bmax); while (i) { BignumInt aval = (i > amax ? 0 : a[i]); BignumInt bval = (i > bmax ? 0 : b[i]); if (aval < bval) return -1; @@ -1325,10 +1351,12 @@ Bignum bignum_rshift(Bignum a, int shift) { Bignum ret; int i, shiftw, shiftb, shiftbb, bits; BignumInt ai, ai1; + + assert(shift >= 0); bits = bignum_bitcount(a) - shift; ret = newbn((bits + BIGNUM_INT_BITS - 1) / BIGNUM_INT_BITS); if (ret) { @@ -1396,12 +1424,11 @@ maxspot = i; } } ret[0] = maxspot; - for (i = 0; i < wslen; i++) - workspace[i] = 0; + smemclr(workspace, wslen * sizeof(*workspace)); sfree(workspace); return ret; } /* @@ -1627,13 +1654,30 @@ Bignum b = copybn(number); Bignum xp = copybn(Zero); Bignum x = copybn(One); int sign = +1; + assert(number[number[0]] != 0); + assert(modulus[modulus[0]] != 0); + while (bignum_cmp(b, One) != 0) { - Bignum t = newbn(b[0]); - Bignum q = newbn(a[0]); + Bignum t, q; + + if (bignum_cmp(b, Zero) == 0) { + /* + * Found a common factor between the inputs, so we cannot + * return a modular inverse at all. + */ + freebn(b); + freebn(a); + freebn(xp); + freebn(x); + return NULL; + } + + t = newbn(b[0]); + q = newbn(a[0]); bigdivmod(a, b, t, q); while (t[0] > 1 && t[t[0]] == 0) t[0]--; freebn(a); a = b; @@ -1748,10 +1792,11 @@ memmove(ret, ret + ndigit, ndigits - ndigit); /* * Done. */ + smemclr(workspace, x[0] * sizeof(*workspace)); sfree(workspace); return ret; } #ifdef TESTBN @@ -1759,11 +1804,11 @@ #include #include #include /* - * gcc -g -O0 -DTESTBN -o testbn sshbn.c misc.c -I unix -I charset + * gcc -Wall -g -O0 -DTESTBN -o testbn sshbn.c misc.c conf.c tree234.c unix/uxmisc.c -I. -I unix -I charset * * Then feed to this program's standard input the output of * testdata/bignum.py . */ @@ -1833,11 +1878,11 @@ if (!strcmp(buf, "mul")) { Bignum a, b, c, p; if (ptrnum != 3) { - printf("%d: mul with %d parameters, expected 3\n", line); + printf("%d: mul with %d parameters, expected 3\n", line, ptrnum); exit(1); } a = bignum_from_bytes(ptrs[0], ptrs[1]-ptrs[0]); b = bignum_from_bytes(ptrs[1], ptrs[2]-ptrs[1]); c = bignum_from_bytes(ptrs[2], ptrs[3]-ptrs[2]); @@ -1861,16 +1906,54 @@ sfree(ps); } freebn(a); freebn(b); freebn(c); + freebn(p); + } else if (!strcmp(buf, "modmul")) { + Bignum a, b, m, c, p; + + if (ptrnum != 4) { + printf("%d: modmul with %d parameters, expected 4\n", + line, ptrnum); + exit(1); + } + a = bignum_from_bytes(ptrs[0], ptrs[1]-ptrs[0]); + b = bignum_from_bytes(ptrs[1], ptrs[2]-ptrs[1]); + m = bignum_from_bytes(ptrs[2], ptrs[3]-ptrs[2]); + c = bignum_from_bytes(ptrs[3], ptrs[4]-ptrs[3]); + p = modmul(a, b, m); + + if (bignum_cmp(c, p) == 0) { + passes++; + } else { + char *as = bignum_decimal(a); + char *bs = bignum_decimal(b); + char *ms = bignum_decimal(m); + char *cs = bignum_decimal(c); + char *ps = bignum_decimal(p); + + printf("%d: fail: %s * %s mod %s gave %s expected %s\n", + line, as, bs, ms, ps, cs); + fails++; + + sfree(as); + sfree(bs); + sfree(ms); + sfree(cs); + sfree(ps); + } + freebn(a); + freebn(b); + freebn(m); + freebn(c); freebn(p); } else if (!strcmp(buf, "pow")) { Bignum base, expt, modulus, expected, answer; if (ptrnum != 4) { - printf("%d: mul with %d parameters, expected 3\n", line); + printf("%d: mul with %d parameters, expected 4\n", line, ptrnum); exit(1); } base = bignum_from_bytes(ptrs[0], ptrs[1]-ptrs[0]); expt = bignum_from_bytes(ptrs[1], ptrs[2]-ptrs[1]); Index: sshdes.c ================================================================== --- sshdes.c +++ sshdes.c @@ -856,11 +856,11 @@ des_key_setup(GET_32BIT_MSB_FIRST(key + 8), GET_32BIT_MSB_FIRST(key + 12), &ourkeys[1]); des_key_setup(GET_32BIT_MSB_FIRST(key), GET_32BIT_MSB_FIRST(key + 4), &ourkeys[2]); des_3cbc_decrypt(blk, len, ourkeys); - memset(ourkeys, 0, sizeof(ourkeys)); + smemclr(ourkeys, sizeof(ourkeys)); } void des3_encrypt_pubkey(unsigned char *key, unsigned char *blk, int len) { DESContext ourkeys[3]; @@ -869,11 +869,11 @@ des_key_setup(GET_32BIT_MSB_FIRST(key + 8), GET_32BIT_MSB_FIRST(key + 12), &ourkeys[1]); des_key_setup(GET_32BIT_MSB_FIRST(key), GET_32BIT_MSB_FIRST(key + 4), &ourkeys[2]); des_3cbc_encrypt(blk, len, ourkeys); - memset(ourkeys, 0, sizeof(ourkeys)); + smemclr(ourkeys, sizeof(ourkeys)); } void des3_decrypt_pubkey_ossh(unsigned char *key, unsigned char *iv, unsigned char *blk, int len) { @@ -885,11 +885,11 @@ des_key_setup(GET_32BIT_MSB_FIRST(key + 16), GET_32BIT_MSB_FIRST(key + 20), &ourkeys[2]); ourkeys[0].iv0 = GET_32BIT_MSB_FIRST(iv); ourkeys[0].iv1 = GET_32BIT_MSB_FIRST(iv+4); des_cbc3_decrypt(blk, len, ourkeys); - memset(ourkeys, 0, sizeof(ourkeys)); + smemclr(ourkeys, sizeof(ourkeys)); } void des3_encrypt_pubkey_ossh(unsigned char *key, unsigned char *iv, unsigned char *blk, int len) { @@ -901,14 +901,14 @@ des_key_setup(GET_32BIT_MSB_FIRST(key + 16), GET_32BIT_MSB_FIRST(key + 20), &ourkeys[2]); ourkeys[0].iv0 = GET_32BIT_MSB_FIRST(iv); ourkeys[0].iv1 = GET_32BIT_MSB_FIRST(iv+4); des_cbc3_encrypt(blk, len, ourkeys); - memset(ourkeys, 0, sizeof(ourkeys)); + smemclr(ourkeys, sizeof(ourkeys)); } -static void des_keysetup_xdmauth(unsigned char *keydata, DESContext *dc) +static void des_keysetup_xdmauth(const unsigned char *keydata, DESContext *dc) { unsigned char key[8]; int i, nbits, j; unsigned int bits; @@ -927,22 +927,24 @@ } des_key_setup(GET_32BIT_MSB_FIRST(key), GET_32BIT_MSB_FIRST(key + 4), dc); } -void des_encrypt_xdmauth(unsigned char *keydata, unsigned char *blk, int len) +void des_encrypt_xdmauth(const unsigned char *keydata, + unsigned char *blk, int len) { DESContext dc; des_keysetup_xdmauth(keydata, &dc); - des_cbc_encrypt(blk, 24, &dc); + des_cbc_encrypt(blk, len, &dc); } -void des_decrypt_xdmauth(unsigned char *keydata, unsigned char *blk, int len) +void des_decrypt_xdmauth(const unsigned char *keydata, + unsigned char *blk, int len) { DESContext dc; des_keysetup_xdmauth(keydata, &dc); - des_cbc_decrypt(blk, 24, &dc); + des_cbc_decrypt(blk, len, &dc); } static const struct ssh2_cipher ssh_3des_ssh2 = { des3_make_context, des3_free_context, des3_iv, des3_key, des3_ssh2_encrypt_blk, des3_ssh2_decrypt_blk, @@ -1027,5 +1029,60 @@ const struct ssh_cipher ssh_des = { des_ssh1_make_context, des3_free_context, des_sesskey, des_encrypt_blk, des_decrypt_blk, 8, "single-DES CBC" }; + +#ifdef TEST_XDM_AUTH + +/* + * Small standalone utility which allows encryption and decryption of + * single cipher blocks in the XDM-AUTHORIZATION-1 style. Written + * during the rework of X authorisation for connection sharing, to + * check the corner case when xa1_firstblock matches but the rest of + * the authorisation is bogus. + * + * Just compile this file on its own with the above ifdef symbol + * predefined: + +gcc -DTEST_XDM_AUTH -o sshdes sshdes.c + + */ + +#include +void *safemalloc(size_t n, size_t size) { return calloc(n, size); } +void safefree(void *p) { return free(p); } +void smemclr(void *p, size_t size) { memset(p, 0, size); } +int main(int argc, char **argv) +{ + unsigned char words[2][8]; + unsigned char out[8]; + int i, j; + + memset(words, 0, sizeof(words)); + + for (i = 0; i < 2; i++) { + for (j = 0; j < 8 && argv[i+1][2*j]; j++) { + char x[3]; + unsigned u; + x[0] = argv[i+1][2*j]; + x[1] = argv[i+1][2*j+1]; + x[2] = 0; + sscanf(x, "%02x", &u); + words[i][j] = u; + } + } + + memcpy(out, words[0], 8); + des_decrypt_xdmauth(words[1], out, 8); + printf("decrypt(%s,%s) = ", argv[1], argv[2]); + for (i = 0; i < 8; i++) printf("%02x", out[i]); + printf("\n"); + + memcpy(out, words[0], 8); + des_encrypt_xdmauth(words[1], out, 8); + printf("encrypt(%s,%s) = ", argv[1], argv[2]); + for (i = 0; i < 8; i++) printf("%02x", out[i]); + printf("\n"); +} + +#endif Index: sshdss.c ================================================================== --- sshdss.c +++ sshdss.c @@ -18,11 +18,11 @@ SHA_Bytes(s, lenbuf, 4); while (len-- > 0) { lenbuf[0] = bignum_byte(b, len); SHA_Bytes(s, lenbuf, 1); } - memset(lenbuf, 0, sizeof(lenbuf)); + smemclr(lenbuf, sizeof(lenbuf)); } static void sha512_mpint(SHA512_State * s, Bignum b) { unsigned char lenbuf[4]; @@ -32,19 +32,21 @@ SHA512_Bytes(s, lenbuf, 4); while (len-- > 0) { lenbuf[0] = bignum_byte(b, len); SHA512_Bytes(s, lenbuf, 1); } - memset(lenbuf, 0, sizeof(lenbuf)); + smemclr(lenbuf, sizeof(lenbuf)); } static void getstring(char **data, int *datalen, char **p, int *length) { *p = NULL; if (*datalen < 4) return; - *length = GET_32BIT(*data); + *length = toint(GET_32BIT(*data)); + if (*length < 0) + return; *datalen -= 4; *data += 4; if (*datalen < *length) return; *p = *data; @@ -67,27 +69,30 @@ } static Bignum get160(char **data, int *datalen) { Bignum b; + + if (*datalen < 20) + return NULL; b = bignum_from_bytes((unsigned char *)*data, 20); *data += 20; *datalen -= 20; return b; } + +static void dss_freekey(void *key); /* forward reference */ static void *dss_newkey(char *data, int len) { char *p; int slen; struct dss_key *dss; dss = snew(struct dss_key); - if (!dss) - return NULL; getstring(&data, &len, &p, &slen); #ifdef DEBUG_DSS { int i; @@ -96,29 +101,43 @@ printf(" %02x", (unsigned char) (data[i])); printf("\n"); } #endif - if (!p || memcmp(p, "ssh-dss", 7)) { + if (!p || slen != 7 || memcmp(p, "ssh-dss", 7)) { sfree(dss); return NULL; } dss->p = getmp(&data, &len); dss->q = getmp(&data, &len); dss->g = getmp(&data, &len); dss->y = getmp(&data, &len); + dss->x = NULL; + + if (!dss->p || !dss->q || !dss->g || !dss->y || + !bignum_cmp(dss->q, Zero) || !bignum_cmp(dss->p, Zero)) { + /* Invalid key. */ + dss_freekey(dss); + return NULL; + } return dss; } static void dss_freekey(void *key) { struct dss_key *dss = (struct dss_key *) key; - freebn(dss->p); - freebn(dss->q); - freebn(dss->g); - freebn(dss->y); + if (dss->p) + freebn(dss->p); + if (dss->q) + freebn(dss->q); + if (dss->g) + freebn(dss->g); + if (dss->y) + freebn(dss->y); + if (dss->x) + freebn(dss->x); sfree(dss); } static char *dss_fmtkey(void *key) { @@ -247,17 +266,33 @@ } sig += 4, siglen -= 4; /* skip yet another length field */ } r = get160(&sig, &siglen); s = get160(&sig, &siglen); - if (!r || !s) + if (!r || !s) { + if (r) + freebn(r); + if (s) + freebn(s); return 0; + } + + if (!bignum_cmp(s, Zero)) { + freebn(r); + freebn(s); + return 0; + } /* * Step 1. w <- s^-1 mod q. */ w = modinv(s, dss->q); + if (!w) { + freebn(r); + freebn(s); + return 0; + } /* * Step 2. u1 <- SHA(message) * w mod q. */ SHA_Simple(data, datalen, (unsigned char *)hash); @@ -285,10 +320,12 @@ ret = !bignum_cmp(v, r); freebn(w); freebn(sha); + freebn(u1); + freebn(u2); freebn(gu1p); freebn(yu2p); freebn(gu1yu2p); freebn(v); freebn(r); @@ -375,11 +412,17 @@ SHA_State s; unsigned char digest[20]; Bignum ytest; dss = dss_newkey((char *) pub_blob, pub_len); + if (!dss) + return NULL; dss->x = getmp(&pb, &priv_len); + if (!dss->x) { + dss_freekey(dss); + return NULL; + } /* * Check the obsolete hash in the old DSS key format. */ hashlen = -1; @@ -400,10 +443,11 @@ * Now ensure g^x mod p really is y. */ ytest = modpow(dss->g, dss->x, dss->p); if (0 != bignum_cmp(ytest, dss->y)) { dss_freekey(dss); + freebn(ytest); return NULL; } freebn(ytest); return dss; @@ -413,27 +457,22 @@ { char **b = (char **) blob; struct dss_key *dss; dss = snew(struct dss_key); - if (!dss) - return NULL; dss->p = getmp(b, len); dss->q = getmp(b, len); dss->g = getmp(b, len); dss->y = getmp(b, len); dss->x = getmp(b, len); - if (!dss->p || !dss->q || !dss->g || !dss->y || !dss->x) { - sfree(dss->p); - sfree(dss->q); - sfree(dss->g); - sfree(dss->y); - sfree(dss->x); - sfree(dss); - return NULL; + if (!dss->p || !dss->q || !dss->g || !dss->y || !dss->x || + !bignum_cmp(dss->q, Zero) || !bignum_cmp(dss->p, Zero)) { + /* Invalid key. */ + dss_freekey(dss); + return NULL; } return dss; } @@ -469,10 +508,12 @@ { struct dss_key *dss; int ret; dss = dss_newkey((char *) blob, len); + if (!dss) + return -1; ret = bignum_bitcount(dss->p); dss_freekey(dss); return ret; } @@ -571,36 +612,52 @@ * Now hash that digest plus the message hash. */ SHA512_Init(&ss); SHA512_Bytes(&ss, digest512, sizeof(digest512)); SHA512_Bytes(&ss, digest, sizeof(digest)); - SHA512_Final(&ss, digest512); - - memset(&ss, 0, sizeof(ss)); - - /* - * Now convert the result into a bignum, and reduce it mod q. - */ - proto_k = bignum_from_bytes(digest512, 64); - k = bigmod(proto_k, dss->q); - freebn(proto_k); - - memset(digest512, 0, sizeof(digest512)); + + while (1) { + SHA512_State ss2 = ss; /* structure copy */ + SHA512_Final(&ss2, digest512); + + smemclr(&ss2, sizeof(ss2)); + + /* + * Now convert the result into a bignum, and reduce it mod q. + */ + proto_k = bignum_from_bytes(digest512, 64); + k = bigmod(proto_k, dss->q); + freebn(proto_k); + kinv = modinv(k, dss->q); /* k^-1 mod q */ + if (!kinv) { /* very unlikely */ + freebn(k); + /* Perturb the hash to think of a different k. */ + SHA512_Bytes(&ss, "x", 1); + /* Go round and try again. */ + continue; + } + + break; + } + + smemclr(&ss, sizeof(ss)); + + smemclr(digest512, sizeof(digest512)); /* * Now we have k, so just go ahead and compute the signature. */ gkp = modpow(dss->g, k, dss->p); /* g^k mod p */ r = bigmod(gkp, dss->q); /* r = (g^k mod p) mod q */ freebn(gkp); hash = bignum_from_bytes(digest, 20); - kinv = modinv(k, dss->q); /* k^-1 mod q */ hxr = bigmuladd(dss->x, r, hash); /* hash + x*r */ s = modmul(kinv, hxr, dss->q); /* s = k^-1 * (hash + x*r) mod q */ freebn(hxr); freebn(kinv); + freebn(k); freebn(hash); /* * Signature blob is * Index: sshdssg.c ================================================================== --- sshdssg.c +++ sshdssg.c @@ -7,10 +7,11 @@ int dsa_generate(struct dss_key *key, int bits, progfn_t pfn, void *pfnparam) { Bignum qm1, power, g, h, tmp; + unsigned pfirst, qfirst; int progress; /* * Set up the phase limits for the progress report. We do this * by passing minus the phase number. @@ -68,19 +69,20 @@ pfn(pfnparam, PROGFN_PHASE_EXTENT, 4, 0x2000); pfn(pfnparam, PROGFN_EXP_PHASE, 4, -49152); pfn(pfnparam, PROGFN_READY, 0, 0); + invent_firstbits(&pfirst, &qfirst); /* * Generate q: a prime of length 160. */ - key->q = primegen(160, 2, 2, NULL, 1, pfn, pfnparam); + key->q = primegen(160, 2, 2, NULL, 1, pfn, pfnparam, qfirst); /* * Now generate p: a prime of length `bits', such that p-1 is * divisible by q. */ - key->p = primegen(bits-160, 2, 2, key->q, 2, pfn, pfnparam); + key->p = primegen(bits-160, 2, 2, key->q, 2, pfn, pfnparam, pfirst); /* * Next we need g. Raise 2 to the power (p-1)/q modulo p, and * if that comes out to one then try 3, then 4 and so on. As * soon as we hit a non-unit (and non-zero!) one, that'll do Index: sshgss.h ================================================================== --- sshgss.h +++ sshgss.h @@ -45,11 +45,11 @@ */ struct ssh_gss_liblist { struct ssh_gss_library *libraries; int nlibraries; }; -struct ssh_gss_liblist *ssh_gss_setup(const Config *cfg); +struct ssh_gss_liblist *ssh_gss_setup(Conf *conf); void ssh_gss_cleanup(struct ssh_gss_liblist *list); /* * Fills in buf with a string describing the GSSAPI mechanism in * use. buf->data is not dynamically allocated. Index: sshmd5.c ================================================================== --- sshmd5.c +++ sshmd5.c @@ -247,11 +247,11 @@ for (i = 0; i < len && i < 64; i++) foo[i] ^= key[i]; MD5Init(&keys[1]); MD5Update(&keys[1], foo, 64); - memset(foo, 0, 64); /* burn the evidence */ + smemclr(foo, 64); /* burn the evidence */ } static void hmacmd5_key_16(void *handle, unsigned char *key) { hmacmd5_key(handle, key, 16); @@ -310,15 +310,11 @@ static void hmacmd5_do_hmac_ssh(void *handle, unsigned char const *blk, int len, unsigned long seq, unsigned char *hmac) { unsigned char seqbuf[16]; - seqbuf[0] = (unsigned char) ((seq >> 24) & 0xFF); - seqbuf[1] = (unsigned char) ((seq >> 16) & 0xFF); - seqbuf[2] = (unsigned char) ((seq >> 8) & 0xFF); - seqbuf[3] = (unsigned char) ((seq) & 0xFF); - + PUT_32BIT_MSB_FIRST(seqbuf, seq); hmacmd5_do_hmac_internal(handle, seqbuf, 4, blk, len, hmac); } static void hmacmd5_generate(void *handle, unsigned char *blk, int len, unsigned long seq) Index: sshnogss.c ================================================================== --- sshnogss.c +++ sshnogss.c @@ -1,11 +1,11 @@ #include "putty.h" #ifndef NO_GSSAPI /* For platforms not supporting GSSAPI */ -struct ssh_gss_liblist *ssh_gss_setup(const Config *cfg) +struct ssh_gss_liblist *ssh_gss_setup(Conf *conf) { struct ssh_gss_liblist *list = snew(struct ssh_gss_liblist *); list->libraries = NULL; list->nlibraries = 0; return list; Index: sshprime.c ================================================================== --- sshprime.c +++ sshprime.c @@ -121,1065 +121,710 @@ * return list * list = sieve(65535) * for i in list[1:]: sys.stdout.write("%d," % i) */ static const unsigned short primes[] = { - 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, - 71, 73, 79, 83, 89, 97, 101, - 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, - 179, 181, 191, 193, - 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, - 277, 281, 283, 293, - 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, - 389, 397, 401, 409, - 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, - 499, 503, 509, 521, - 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 613, - 617, 619, 631, 641, - 643, 647, 653, 659, 661, 673, 677, 683, 691, 701, 709, 719, 727, 733, - 739, 743, 751, 757, - 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829, 839, 853, 857, - 859, 863, 877, 881, - 883, 887, 907, 911, 919, 929, 937, 941, 947, 953, 967, 971, 977, 983, - 991, 997, 1009, - 1013, 1019, 1021, 1031, 1033, 1039, 1049, 1051, 1061, 1063, 1069, 1087, - 1091, 1093, - 1097, 1103, 1109, 1117, 1123, 1129, 1151, 1153, 1163, 1171, 1181, 1187, - 1193, 1201, - 1213, 1217, 1223, 1229, 1231, 1237, 1249, 1259, 1277, 1279, 1283, 1289, - 1291, 1297, - 1301, 1303, 1307, 1319, 1321, 1327, 1361, 1367, 1373, 1381, 1399, 1409, - 1423, 1427, - 1429, 1433, 1439, 1447, 1451, 1453, 1459, 1471, 1481, 1483, 1487, 1489, - 1493, 1499, - 1511, 1523, 1531, 1543, 1549, 1553, 1559, 1567, 1571, 1579, 1583, 1597, - 1601, 1607, - 1609, 1613, 1619, 1621, 1627, 1637, 1657, 1663, 1667, 1669, 1693, 1697, - 1699, 1709, - 1721, 1723, 1733, 1741, 1747, 1753, 1759, 1777, 1783, 1787, 1789, 1801, - 1811, 1823, - 1831, 1847, 1861, 1867, 1871, 1873, 1877, 1879, 1889, 1901, 1907, 1913, - 1931, 1933, - 1949, 1951, 1973, 1979, 1987, 1993, 1997, 1999, 2003, 2011, 2017, 2027, - 2029, 2039, - 2053, 2063, 2069, 2081, 2083, 2087, 2089, 2099, 2111, 2113, 2129, 2131, - 2137, 2141, - 2143, 2153, 2161, 2179, 2203, 2207, 2213, 2221, 2237, 2239, 2243, 2251, - 2267, 2269, - 2273, 2281, 2287, 2293, 2297, 2309, 2311, 2333, 2339, 2341, 2347, 2351, - 2357, 2371, - 2377, 2381, 2383, 2389, 2393, 2399, 2411, 2417, 2423, 2437, 2441, 2447, - 2459, 2467, - 2473, 2477, 2503, 2521, 2531, 2539, 2543, 2549, 2551, 2557, 2579, 2591, - 2593, 2609, - 2617, 2621, 2633, 2647, 2657, 2659, 2663, 2671, 2677, 2683, 2687, 2689, - 2693, 2699, - 2707, 2711, 2713, 2719, 2729, 2731, 2741, 2749, 2753, 2767, 2777, 2789, - 2791, 2797, - 2801, 2803, 2819, 2833, 2837, 2843, 2851, 2857, 2861, 2879, 2887, 2897, - 2903, 2909, - 2917, 2927, 2939, 2953, 2957, 2963, 2969, 2971, 2999, 3001, 3011, 3019, - 3023, 3037, - 3041, 3049, 3061, 3067, 3079, 3083, 3089, 3109, 3119, 3121, 3137, 3163, - 3167, 3169, - 3181, 3187, 3191, 3203, 3209, 3217, 3221, 3229, 3251, 3253, 3257, 3259, - 3271, 3299, - 3301, 3307, 3313, 3319, 3323, 3329, 3331, 3343, 3347, 3359, 3361, 3371, - 3373, 3389, - 3391, 3407, 3413, 3433, 3449, 3457, 3461, 3463, 3467, 3469, 3491, 3499, - 3511, 3517, - 3527, 3529, 3533, 3539, 3541, 3547, 3557, 3559, 3571, 3581, 3583, 3593, - 3607, 3613, - 3617, 3623, 3631, 3637, 3643, 3659, 3671, 3673, 3677, 3691, 3697, 3701, - 3709, 3719, - 3727, 3733, 3739, 3761, 3767, 3769, 3779, 3793, 3797, 3803, 3821, 3823, - 3833, 3847, - 3851, 3853, 3863, 3877, 3881, 3889, 3907, 3911, 3917, 3919, 3923, 3929, - 3931, 3943, - 3947, 3967, 3989, 4001, 4003, 4007, 4013, 4019, 4021, 4027, 4049, 4051, - 4057, 4073, - 4079, 4091, 4093, 4099, 4111, 4127, 4129, 4133, 4139, 4153, 4157, 4159, - 4177, 4201, - 4211, 4217, 4219, 4229, 4231, 4241, 4243, 4253, 4259, 4261, 4271, 4273, - 4283, 4289, - 4297, 4327, 4337, 4339, 4349, 4357, 4363, 4373, 4391, 4397, 4409, 4421, - 4423, 4441, - 4447, 4451, 4457, 4463, 4481, 4483, 4493, 4507, 4513, 4517, 4519, 4523, - 4547, 4549, - 4561, 4567, 4583, 4591, 4597, 4603, 4621, 4637, 4639, 4643, 4649, 4651, - 4657, 4663, - 4673, 4679, 4691, 4703, 4721, 4723, 4729, 4733, 4751, 4759, 4783, 4787, - 4789, 4793, - 4799, 4801, 4813, 4817, 4831, 4861, 4871, 4877, 4889, 4903, 4909, 4919, - 4931, 4933, - 4937, 4943, 4951, 4957, 4967, 4969, 4973, 4987, 4993, 4999, 5003, 5009, - 5011, 5021, - 5023, 5039, 5051, 5059, 5077, 5081, 5087, 5099, 5101, 5107, 5113, 5119, - 5147, 5153, - 5167, 5171, 5179, 5189, 5197, 5209, 5227, 5231, 5233, 5237, 5261, 5273, - 5279, 5281, - 5297, 5303, 5309, 5323, 5333, 5347, 5351, 5381, 5387, 5393, 5399, 5407, - 5413, 5417, - 5419, 5431, 5437, 5441, 5443, 5449, 5471, 5477, 5479, 5483, 5501, 5503, - 5507, 5519, - 5521, 5527, 5531, 5557, 5563, 5569, 5573, 5581, 5591, 5623, 5639, 5641, - 5647, 5651, - 5653, 5657, 5659, 5669, 5683, 5689, 5693, 5701, 5711, 5717, 5737, 5741, - 5743, 5749, - 5779, 5783, 5791, 5801, 5807, 5813, 5821, 5827, 5839, 5843, 5849, 5851, - 5857, 5861, - 5867, 5869, 5879, 5881, 5897, 5903, 5923, 5927, 5939, 5953, 5981, 5987, - 6007, 6011, - 6029, 6037, 6043, 6047, 6053, 6067, 6073, 6079, 6089, 6091, 6101, 6113, - 6121, 6131, - 6133, 6143, 6151, 6163, 6173, 6197, 6199, 6203, 6211, 6217, 6221, 6229, - 6247, 6257, - 6263, 6269, 6271, 6277, 6287, 6299, 6301, 6311, 6317, 6323, 6329, 6337, - 6343, 6353, - 6359, 6361, 6367, 6373, 6379, 6389, 6397, 6421, 6427, 6449, 6451, 6469, - 6473, 6481, - 6491, 6521, 6529, 6547, 6551, 6553, 6563, 6569, 6571, 6577, 6581, 6599, - 6607, 6619, - 6637, 6653, 6659, 6661, 6673, 6679, 6689, 6691, 6701, 6703, 6709, 6719, - 6733, 6737, - 6761, 6763, 6779, 6781, 6791, 6793, 6803, 6823, 6827, 6829, 6833, 6841, - 6857, 6863, - 6869, 6871, 6883, 6899, 6907, 6911, 6917, 6947, 6949, 6959, 6961, 6967, - 6971, 6977, - 6983, 6991, 6997, 7001, 7013, 7019, 7027, 7039, 7043, 7057, 7069, 7079, - 7103, 7109, - 7121, 7127, 7129, 7151, 7159, 7177, 7187, 7193, 7207, 7211, 7213, 7219, - 7229, 7237, - 7243, 7247, 7253, 7283, 7297, 7307, 7309, 7321, 7331, 7333, 7349, 7351, - 7369, 7393, - 7411, 7417, 7433, 7451, 7457, 7459, 7477, 7481, 7487, 7489, 7499, 7507, - 7517, 7523, - 7529, 7537, 7541, 7547, 7549, 7559, 7561, 7573, 7577, 7583, 7589, 7591, - 7603, 7607, - 7621, 7639, 7643, 7649, 7669, 7673, 7681, 7687, 7691, 7699, 7703, 7717, - 7723, 7727, - 7741, 7753, 7757, 7759, 7789, 7793, 7817, 7823, 7829, 7841, 7853, 7867, - 7873, 7877, - 7879, 7883, 7901, 7907, 7919, 7927, 7933, 7937, 7949, 7951, 7963, 7993, - 8009, 8011, - 8017, 8039, 8053, 8059, 8069, 8081, 8087, 8089, 8093, 8101, 8111, 8117, - 8123, 8147, - 8161, 8167, 8171, 8179, 8191, 8209, 8219, 8221, 8231, 8233, 8237, 8243, - 8263, 8269, - 8273, 8287, 8291, 8293, 8297, 8311, 8317, 8329, 8353, 8363, 8369, 8377, - 8387, 8389, - 8419, 8423, 8429, 8431, 8443, 8447, 8461, 8467, 8501, 8513, 8521, 8527, - 8537, 8539, - 8543, 8563, 8573, 8581, 8597, 8599, 8609, 8623, 8627, 8629, 8641, 8647, - 8663, 8669, - 8677, 8681, 8689, 8693, 8699, 8707, 8713, 8719, 8731, 8737, 8741, 8747, - 8753, 8761, - 8779, 8783, 8803, 8807, 8819, 8821, 8831, 8837, 8839, 8849, 8861, 8863, - 8867, 8887, - 8893, 8923, 8929, 8933, 8941, 8951, 8963, 8969, 8971, 8999, 9001, 9007, - 9011, 9013, - 9029, 9041, 9043, 9049, 9059, 9067, 9091, 9103, 9109, 9127, 9133, 9137, - 9151, 9157, - 9161, 9173, 9181, 9187, 9199, 9203, 9209, 9221, 9227, 9239, 9241, 9257, - 9277, 9281, - 9283, 9293, 9311, 9319, 9323, 9337, 9341, 9343, 9349, 9371, 9377, 9391, - 9397, 9403, - 9413, 9419, 9421, 9431, 9433, 9437, 9439, 9461, 9463, 9467, 9473, 9479, - 9491, 9497, - 9511, 9521, 9533, 9539, 9547, 9551, 9587, 9601, 9613, 9619, 9623, 9629, - 9631, 9643, - 9649, 9661, 9677, 9679, 9689, 9697, 9719, 9721, 9733, 9739, 9743, 9749, - 9767, 9769, - 9781, 9787, 9791, 9803, 9811, 9817, 9829, 9833, 9839, 9851, 9857, 9859, - 9871, 9883, + 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, + 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, + 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, + 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, + 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, + 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, + 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, + 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, + 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, + 683, 691, 701, 709, 719, 727, 733, 739, 743, 751, 757, 761, 769, + 773, 787, 797, 809, 811, 821, 823, 827, 829, 839, 853, 857, 859, + 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947, 953, + 967, 971, 977, 983, 991, 997, 1009, 1013, 1019, 1021, 1031, 1033, + 1039, 1049, 1051, 1061, 1063, 1069, 1087, 1091, 1093, 1097, 1103, + 1109, 1117, 1123, 1129, 1151, 1153, 1163, 1171, 1181, 1187, 1193, + 1201, 1213, 1217, 1223, 1229, 1231, 1237, 1249, 1259, 1277, 1279, + 1283, 1289, 1291, 1297, 1301, 1303, 1307, 1319, 1321, 1327, 1361, + 1367, 1373, 1381, 1399, 1409, 1423, 1427, 1429, 1433, 1439, 1447, + 1451, 1453, 1459, 1471, 1481, 1483, 1487, 1489, 1493, 1499, 1511, + 1523, 1531, 1543, 1549, 1553, 1559, 1567, 1571, 1579, 1583, 1597, + 1601, 1607, 1609, 1613, 1619, 1621, 1627, 1637, 1657, 1663, 1667, + 1669, 1693, 1697, 1699, 1709, 1721, 1723, 1733, 1741, 1747, 1753, + 1759, 1777, 1783, 1787, 1789, 1801, 1811, 1823, 1831, 1847, 1861, + 1867, 1871, 1873, 1877, 1879, 1889, 1901, 1907, 1913, 1931, 1933, + 1949, 1951, 1973, 1979, 1987, 1993, 1997, 1999, 2003, 2011, 2017, + 2027, 2029, 2039, 2053, 2063, 2069, 2081, 2083, 2087, 2089, 2099, + 2111, 2113, 2129, 2131, 2137, 2141, 2143, 2153, 2161, 2179, 2203, + 2207, 2213, 2221, 2237, 2239, 2243, 2251, 2267, 2269, 2273, 2281, + 2287, 2293, 2297, 2309, 2311, 2333, 2339, 2341, 2347, 2351, 2357, + 2371, 2377, 2381, 2383, 2389, 2393, 2399, 2411, 2417, 2423, 2437, + 2441, 2447, 2459, 2467, 2473, 2477, 2503, 2521, 2531, 2539, 2543, + 2549, 2551, 2557, 2579, 2591, 2593, 2609, 2617, 2621, 2633, 2647, + 2657, 2659, 2663, 2671, 2677, 2683, 2687, 2689, 2693, 2699, 2707, + 2711, 2713, 2719, 2729, 2731, 2741, 2749, 2753, 2767, 2777, 2789, + 2791, 2797, 2801, 2803, 2819, 2833, 2837, 2843, 2851, 2857, 2861, + 2879, 2887, 2897, 2903, 2909, 2917, 2927, 2939, 2953, 2957, 2963, + 2969, 2971, 2999, 3001, 3011, 3019, 3023, 3037, 3041, 3049, 3061, + 3067, 3079, 3083, 3089, 3109, 3119, 3121, 3137, 3163, 3167, 3169, + 3181, 3187, 3191, 3203, 3209, 3217, 3221, 3229, 3251, 3253, 3257, + 3259, 3271, 3299, 3301, 3307, 3313, 3319, 3323, 3329, 3331, 3343, + 3347, 3359, 3361, 3371, 3373, 3389, 3391, 3407, 3413, 3433, 3449, + 3457, 3461, 3463, 3467, 3469, 3491, 3499, 3511, 3517, 3527, 3529, + 3533, 3539, 3541, 3547, 3557, 3559, 3571, 3581, 3583, 3593, 3607, + 3613, 3617, 3623, 3631, 3637, 3643, 3659, 3671, 3673, 3677, 3691, + 3697, 3701, 3709, 3719, 3727, 3733, 3739, 3761, 3767, 3769, 3779, + 3793, 3797, 3803, 3821, 3823, 3833, 3847, 3851, 3853, 3863, 3877, + 3881, 3889, 3907, 3911, 3917, 3919, 3923, 3929, 3931, 3943, 3947, + 3967, 3989, 4001, 4003, 4007, 4013, 4019, 4021, 4027, 4049, 4051, + 4057, 4073, 4079, 4091, 4093, 4099, 4111, 4127, 4129, 4133, 4139, + 4153, 4157, 4159, 4177, 4201, 4211, 4217, 4219, 4229, 4231, 4241, + 4243, 4253, 4259, 4261, 4271, 4273, 4283, 4289, 4297, 4327, 4337, + 4339, 4349, 4357, 4363, 4373, 4391, 4397, 4409, 4421, 4423, 4441, + 4447, 4451, 4457, 4463, 4481, 4483, 4493, 4507, 4513, 4517, 4519, + 4523, 4547, 4549, 4561, 4567, 4583, 4591, 4597, 4603, 4621, 4637, + 4639, 4643, 4649, 4651, 4657, 4663, 4673, 4679, 4691, 4703, 4721, + 4723, 4729, 4733, 4751, 4759, 4783, 4787, 4789, 4793, 4799, 4801, + 4813, 4817, 4831, 4861, 4871, 4877, 4889, 4903, 4909, 4919, 4931, + 4933, 4937, 4943, 4951, 4957, 4967, 4969, 4973, 4987, 4993, 4999, + 5003, 5009, 5011, 5021, 5023, 5039, 5051, 5059, 5077, 5081, 5087, + 5099, 5101, 5107, 5113, 5119, 5147, 5153, 5167, 5171, 5179, 5189, + 5197, 5209, 5227, 5231, 5233, 5237, 5261, 5273, 5279, 5281, 5297, + 5303, 5309, 5323, 5333, 5347, 5351, 5381, 5387, 5393, 5399, 5407, + 5413, 5417, 5419, 5431, 5437, 5441, 5443, 5449, 5471, 5477, 5479, + 5483, 5501, 5503, 5507, 5519, 5521, 5527, 5531, 5557, 5563, 5569, + 5573, 5581, 5591, 5623, 5639, 5641, 5647, 5651, 5653, 5657, 5659, + 5669, 5683, 5689, 5693, 5701, 5711, 5717, 5737, 5741, 5743, 5749, + 5779, 5783, 5791, 5801, 5807, 5813, 5821, 5827, 5839, 5843, 5849, + 5851, 5857, 5861, 5867, 5869, 5879, 5881, 5897, 5903, 5923, 5927, + 5939, 5953, 5981, 5987, 6007, 6011, 6029, 6037, 6043, 6047, 6053, + 6067, 6073, 6079, 6089, 6091, 6101, 6113, 6121, 6131, 6133, 6143, + 6151, 6163, 6173, 6197, 6199, 6203, 6211, 6217, 6221, 6229, 6247, + 6257, 6263, 6269, 6271, 6277, 6287, 6299, 6301, 6311, 6317, 6323, + 6329, 6337, 6343, 6353, 6359, 6361, 6367, 6373, 6379, 6389, 6397, + 6421, 6427, 6449, 6451, 6469, 6473, 6481, 6491, 6521, 6529, 6547, + 6551, 6553, 6563, 6569, 6571, 6577, 6581, 6599, 6607, 6619, 6637, + 6653, 6659, 6661, 6673, 6679, 6689, 6691, 6701, 6703, 6709, 6719, + 6733, 6737, 6761, 6763, 6779, 6781, 6791, 6793, 6803, 6823, 6827, + 6829, 6833, 6841, 6857, 6863, 6869, 6871, 6883, 6899, 6907, 6911, + 6917, 6947, 6949, 6959, 6961, 6967, 6971, 6977, 6983, 6991, 6997, + 7001, 7013, 7019, 7027, 7039, 7043, 7057, 7069, 7079, 7103, 7109, + 7121, 7127, 7129, 7151, 7159, 7177, 7187, 7193, 7207, 7211, 7213, + 7219, 7229, 7237, 7243, 7247, 7253, 7283, 7297, 7307, 7309, 7321, + 7331, 7333, 7349, 7351, 7369, 7393, 7411, 7417, 7433, 7451, 7457, + 7459, 7477, 7481, 7487, 7489, 7499, 7507, 7517, 7523, 7529, 7537, + 7541, 7547, 7549, 7559, 7561, 7573, 7577, 7583, 7589, 7591, 7603, + 7607, 7621, 7639, 7643, 7649, 7669, 7673, 7681, 7687, 7691, 7699, + 7703, 7717, 7723, 7727, 7741, 7753, 7757, 7759, 7789, 7793, 7817, + 7823, 7829, 7841, 7853, 7867, 7873, 7877, 7879, 7883, 7901, 7907, + 7919, 7927, 7933, 7937, 7949, 7951, 7963, 7993, 8009, 8011, 8017, + 8039, 8053, 8059, 8069, 8081, 8087, 8089, 8093, 8101, 8111, 8117, + 8123, 8147, 8161, 8167, 8171, 8179, 8191, 8209, 8219, 8221, 8231, + 8233, 8237, 8243, 8263, 8269, 8273, 8287, 8291, 8293, 8297, 8311, + 8317, 8329, 8353, 8363, 8369, 8377, 8387, 8389, 8419, 8423, 8429, + 8431, 8443, 8447, 8461, 8467, 8501, 8513, 8521, 8527, 8537, 8539, + 8543, 8563, 8573, 8581, 8597, 8599, 8609, 8623, 8627, 8629, 8641, + 8647, 8663, 8669, 8677, 8681, 8689, 8693, 8699, 8707, 8713, 8719, + 8731, 8737, 8741, 8747, 8753, 8761, 8779, 8783, 8803, 8807, 8819, + 8821, 8831, 8837, 8839, 8849, 8861, 8863, 8867, 8887, 8893, 8923, + 8929, 8933, 8941, 8951, 8963, 8969, 8971, 8999, 9001, 9007, 9011, + 9013, 9029, 9041, 9043, 9049, 9059, 9067, 9091, 9103, 9109, 9127, + 9133, 9137, 9151, 9157, 9161, 9173, 9181, 9187, 9199, 9203, 9209, + 9221, 9227, 9239, 9241, 9257, 9277, 9281, 9283, 9293, 9311, 9319, + 9323, 9337, 9341, 9343, 9349, 9371, 9377, 9391, 9397, 9403, 9413, + 9419, 9421, 9431, 9433, 9437, 9439, 9461, 9463, 9467, 9473, 9479, + 9491, 9497, 9511, 9521, 9533, 9539, 9547, 9551, 9587, 9601, 9613, + 9619, 9623, 9629, 9631, 9643, 9649, 9661, 9677, 9679, 9689, 9697, + 9719, 9721, 9733, 9739, 9743, 9749, 9767, 9769, 9781, 9787, 9791, + 9803, 9811, 9817, 9829, 9833, 9839, 9851, 9857, 9859, 9871, 9883, 9887, 9901, 9907, 9923, 9929, 9931, 9941, 9949, 9967, 9973, 10007, - 10009, 10037, - 10039, 10061, 10067, 10069, 10079, 10091, 10093, 10099, 10103, 10111, - 10133, 10139, - 10141, 10151, 10159, 10163, 10169, 10177, 10181, 10193, 10211, 10223, - 10243, 10247, - 10253, 10259, 10267, 10271, 10273, 10289, 10301, 10303, 10313, 10321, - 10331, 10333, - 10337, 10343, 10357, 10369, 10391, 10399, 10427, 10429, 10433, 10453, - 10457, 10459, - 10463, 10477, 10487, 10499, 10501, 10513, 10529, 10531, 10559, 10567, - 10589, 10597, - 10601, 10607, 10613, 10627, 10631, 10639, 10651, 10657, 10663, 10667, - 10687, 10691, - 10709, 10711, 10723, 10729, 10733, 10739, 10753, 10771, 10781, 10789, - 10799, 10831, - 10837, 10847, 10853, 10859, 10861, 10867, 10883, 10889, 10891, 10903, - 10909, 10937, - 10939, 10949, 10957, 10973, 10979, 10987, 10993, 11003, 11027, 11047, - 11057, 11059, - 11069, 11071, 11083, 11087, 11093, 11113, 11117, 11119, 11131, 11149, - 11159, 11161, - 11171, 11173, 11177, 11197, 11213, 11239, 11243, 11251, 11257, 11261, - 11273, 11279, - 11287, 11299, 11311, 11317, 11321, 11329, 11351, 11353, 11369, 11383, - 11393, 11399, - 11411, 11423, 11437, 11443, 11447, 11467, 11471, 11483, 11489, 11491, - 11497, 11503, - 11519, 11527, 11549, 11551, 11579, 11587, 11593, 11597, 11617, 11621, - 11633, 11657, - 11677, 11681, 11689, 11699, 11701, 11717, 11719, 11731, 11743, 11777, - 11779, 11783, - 11789, 11801, 11807, 11813, 11821, 11827, 11831, 11833, 11839, 11863, - 11867, 11887, - 11897, 11903, 11909, 11923, 11927, 11933, 11939, 11941, 11953, 11959, - 11969, 11971, - 11981, 11987, 12007, 12011, 12037, 12041, 12043, 12049, 12071, 12073, - 12097, 12101, - 12107, 12109, 12113, 12119, 12143, 12149, 12157, 12161, 12163, 12197, - 12203, 12211, - 12227, 12239, 12241, 12251, 12253, 12263, 12269, 12277, 12281, 12289, - 12301, 12323, - 12329, 12343, 12347, 12373, 12377, 12379, 12391, 12401, 12409, 12413, - 12421, 12433, - 12437, 12451, 12457, 12473, 12479, 12487, 12491, 12497, 12503, 12511, - 12517, 12527, - 12539, 12541, 12547, 12553, 12569, 12577, 12583, 12589, 12601, 12611, - 12613, 12619, - 12637, 12641, 12647, 12653, 12659, 12671, 12689, 12697, 12703, 12713, - 12721, 12739, - 12743, 12757, 12763, 12781, 12791, 12799, 12809, 12821, 12823, 12829, - 12841, 12853, - 12889, 12893, 12899, 12907, 12911, 12917, 12919, 12923, 12941, 12953, - 12959, 12967, - 12973, 12979, 12983, 13001, 13003, 13007, 13009, 13033, 13037, 13043, - 13049, 13063, - 13093, 13099, 13103, 13109, 13121, 13127, 13147, 13151, 13159, 13163, - 13171, 13177, - 13183, 13187, 13217, 13219, 13229, 13241, 13249, 13259, 13267, 13291, - 13297, 13309, - 13313, 13327, 13331, 13337, 13339, 13367, 13381, 13397, 13399, 13411, - 13417, 13421, - 13441, 13451, 13457, 13463, 13469, 13477, 13487, 13499, 13513, 13523, - 13537, 13553, - 13567, 13577, 13591, 13597, 13613, 13619, 13627, 13633, 13649, 13669, - 13679, 13681, - 13687, 13691, 13693, 13697, 13709, 13711, 13721, 13723, 13729, 13751, - 13757, 13759, - 13763, 13781, 13789, 13799, 13807, 13829, 13831, 13841, 13859, 13873, - 13877, 13879, - 13883, 13901, 13903, 13907, 13913, 13921, 13931, 13933, 13963, 13967, - 13997, 13999, - 14009, 14011, 14029, 14033, 14051, 14057, 14071, 14081, 14083, 14087, - 14107, 14143, - 14149, 14153, 14159, 14173, 14177, 14197, 14207, 14221, 14243, 14249, - 14251, 14281, - 14293, 14303, 14321, 14323, 14327, 14341, 14347, 14369, 14387, 14389, - 14401, 14407, - 14411, 14419, 14423, 14431, 14437, 14447, 14449, 14461, 14479, 14489, - 14503, 14519, - 14533, 14537, 14543, 14549, 14551, 14557, 14561, 14563, 14591, 14593, - 14621, 14627, - 14629, 14633, 14639, 14653, 14657, 14669, 14683, 14699, 14713, 14717, - 14723, 14731, - 14737, 14741, 14747, 14753, 14759, 14767, 14771, 14779, 14783, 14797, - 14813, 14821, - 14827, 14831, 14843, 14851, 14867, 14869, 14879, 14887, 14891, 14897, - 14923, 14929, - 14939, 14947, 14951, 14957, 14969, 14983, 15013, 15017, 15031, 15053, - 15061, 15073, - 15077, 15083, 15091, 15101, 15107, 15121, 15131, 15137, 15139, 15149, - 15161, 15173, - 15187, 15193, 15199, 15217, 15227, 15233, 15241, 15259, 15263, 15269, - 15271, 15277, - 15287, 15289, 15299, 15307, 15313, 15319, 15329, 15331, 15349, 15359, - 15361, 15373, - 15377, 15383, 15391, 15401, 15413, 15427, 15439, 15443, 15451, 15461, - 15467, 15473, - 15493, 15497, 15511, 15527, 15541, 15551, 15559, 15569, 15581, 15583, - 15601, 15607, - 15619, 15629, 15641, 15643, 15647, 15649, 15661, 15667, 15671, 15679, - 15683, 15727, - 15731, 15733, 15737, 15739, 15749, 15761, 15767, 15773, 15787, 15791, - 15797, 15803, - 15809, 15817, 15823, 15859, 15877, 15881, 15887, 15889, 15901, 15907, - 15913, 15919, - 15923, 15937, 15959, 15971, 15973, 15991, 16001, 16007, 16033, 16057, - 16061, 16063, - 16067, 16069, 16073, 16087, 16091, 16097, 16103, 16111, 16127, 16139, - 16141, 16183, - 16187, 16189, 16193, 16217, 16223, 16229, 16231, 16249, 16253, 16267, - 16273, 16301, - 16319, 16333, 16339, 16349, 16361, 16363, 16369, 16381, 16411, 16417, - 16421, 16427, - 16433, 16447, 16451, 16453, 16477, 16481, 16487, 16493, 16519, 16529, - 16547, 16553, - 16561, 16567, 16573, 16603, 16607, 16619, 16631, 16633, 16649, 16651, - 16657, 16661, - 16673, 16691, 16693, 16699, 16703, 16729, 16741, 16747, 16759, 16763, - 16787, 16811, - 16823, 16829, 16831, 16843, 16871, 16879, 16883, 16889, 16901, 16903, - 16921, 16927, - 16931, 16937, 16943, 16963, 16979, 16981, 16987, 16993, 17011, 17021, - 17027, 17029, - 17033, 17041, 17047, 17053, 17077, 17093, 17099, 17107, 17117, 17123, - 17137, 17159, - 17167, 17183, 17189, 17191, 17203, 17207, 17209, 17231, 17239, 17257, - 17291, 17293, - 17299, 17317, 17321, 17327, 17333, 17341, 17351, 17359, 17377, 17383, - 17387, 17389, - 17393, 17401, 17417, 17419, 17431, 17443, 17449, 17467, 17471, 17477, - 17483, 17489, - 17491, 17497, 17509, 17519, 17539, 17551, 17569, 17573, 17579, 17581, - 17597, 17599, - 17609, 17623, 17627, 17657, 17659, 17669, 17681, 17683, 17707, 17713, - 17729, 17737, - 17747, 17749, 17761, 17783, 17789, 17791, 17807, 17827, 17837, 17839, - 17851, 17863, - 17881, 17891, 17903, 17909, 17911, 17921, 17923, 17929, 17939, 17957, - 17959, 17971, - 17977, 17981, 17987, 17989, 18013, 18041, 18043, 18047, 18049, 18059, - 18061, 18077, - 18089, 18097, 18119, 18121, 18127, 18131, 18133, 18143, 18149, 18169, - 18181, 18191, - 18199, 18211, 18217, 18223, 18229, 18233, 18251, 18253, 18257, 18269, - 18287, 18289, - 18301, 18307, 18311, 18313, 18329, 18341, 18353, 18367, 18371, 18379, - 18397, 18401, - 18413, 18427, 18433, 18439, 18443, 18451, 18457, 18461, 18481, 18493, - 18503, 18517, - 18521, 18523, 18539, 18541, 18553, 18583, 18587, 18593, 18617, 18637, - 18661, 18671, - 18679, 18691, 18701, 18713, 18719, 18731, 18743, 18749, 18757, 18773, - 18787, 18793, - 18797, 18803, 18839, 18859, 18869, 18899, 18911, 18913, 18917, 18919, - 18947, 18959, - 18973, 18979, 19001, 19009, 19013, 19031, 19037, 19051, 19069, 19073, - 19079, 19081, - 19087, 19121, 19139, 19141, 19157, 19163, 19181, 19183, 19207, 19211, - 19213, 19219, - 19231, 19237, 19249, 19259, 19267, 19273, 19289, 19301, 19309, 19319, - 19333, 19373, - 19379, 19381, 19387, 19391, 19403, 19417, 19421, 19423, 19427, 19429, - 19433, 19441, - 19447, 19457, 19463, 19469, 19471, 19477, 19483, 19489, 19501, 19507, - 19531, 19541, - 19543, 19553, 19559, 19571, 19577, 19583, 19597, 19603, 19609, 19661, - 19681, 19687, - 19697, 19699, 19709, 19717, 19727, 19739, 19751, 19753, 19759, 19763, - 19777, 19793, - 19801, 19813, 19819, 19841, 19843, 19853, 19861, 19867, 19889, 19891, - 19913, 19919, - 19927, 19937, 19949, 19961, 19963, 19973, 19979, 19991, 19993, 19997, - 20011, 20021, - 20023, 20029, 20047, 20051, 20063, 20071, 20089, 20101, 20107, 20113, - 20117, 20123, - 20129, 20143, 20147, 20149, 20161, 20173, 20177, 20183, 20201, 20219, - 20231, 20233, - 20249, 20261, 20269, 20287, 20297, 20323, 20327, 20333, 20341, 20347, - 20353, 20357, - 20359, 20369, 20389, 20393, 20399, 20407, 20411, 20431, 20441, 20443, - 20477, 20479, - 20483, 20507, 20509, 20521, 20533, 20543, 20549, 20551, 20563, 20593, - 20599, 20611, - 20627, 20639, 20641, 20663, 20681, 20693, 20707, 20717, 20719, 20731, - 20743, 20747, - 20749, 20753, 20759, 20771, 20773, 20789, 20807, 20809, 20849, 20857, - 20873, 20879, - 20887, 20897, 20899, 20903, 20921, 20929, 20939, 20947, 20959, 20963, - 20981, 20983, - 21001, 21011, 21013, 21017, 21019, 21023, 21031, 21059, 21061, 21067, - 21089, 21101, - 21107, 21121, 21139, 21143, 21149, 21157, 21163, 21169, 21179, 21187, - 21191, 21193, - 21211, 21221, 21227, 21247, 21269, 21277, 21283, 21313, 21317, 21319, - 21323, 21341, - 21347, 21377, 21379, 21383, 21391, 21397, 21401, 21407, 21419, 21433, - 21467, 21481, - 21487, 21491, 21493, 21499, 21503, 21517, 21521, 21523, 21529, 21557, - 21559, 21563, - 21569, 21577, 21587, 21589, 21599, 21601, 21611, 21613, 21617, 21647, - 21649, 21661, - 21673, 21683, 21701, 21713, 21727, 21737, 21739, 21751, 21757, 21767, - 21773, 21787, - 21799, 21803, 21817, 21821, 21839, 21841, 21851, 21859, 21863, 21871, - 21881, 21893, - 21911, 21929, 21937, 21943, 21961, 21977, 21991, 21997, 22003, 22013, - 22027, 22031, - 22037, 22039, 22051, 22063, 22067, 22073, 22079, 22091, 22093, 22109, - 22111, 22123, - 22129, 22133, 22147, 22153, 22157, 22159, 22171, 22189, 22193, 22229, - 22247, 22259, - 22271, 22273, 22277, 22279, 22283, 22291, 22303, 22307, 22343, 22349, - 22367, 22369, - 22381, 22391, 22397, 22409, 22433, 22441, 22447, 22453, 22469, 22481, - 22483, 22501, - 22511, 22531, 22541, 22543, 22549, 22567, 22571, 22573, 22613, 22619, - 22621, 22637, - 22639, 22643, 22651, 22669, 22679, 22691, 22697, 22699, 22709, 22717, - 22721, 22727, - 22739, 22741, 22751, 22769, 22777, 22783, 22787, 22807, 22811, 22817, - 22853, 22859, - 22861, 22871, 22877, 22901, 22907, 22921, 22937, 22943, 22961, 22963, - 22973, 22993, - 23003, 23011, 23017, 23021, 23027, 23029, 23039, 23041, 23053, 23057, - 23059, 23063, - 23071, 23081, 23087, 23099, 23117, 23131, 23143, 23159, 23167, 23173, - 23189, 23197, - 23201, 23203, 23209, 23227, 23251, 23269, 23279, 23291, 23293, 23297, - 23311, 23321, - 23327, 23333, 23339, 23357, 23369, 23371, 23399, 23417, 23431, 23447, - 23459, 23473, - 23497, 23509, 23531, 23537, 23539, 23549, 23557, 23561, 23563, 23567, - 23581, 23593, - 23599, 23603, 23609, 23623, 23627, 23629, 23633, 23663, 23669, 23671, - 23677, 23687, - 23689, 23719, 23741, 23743, 23747, 23753, 23761, 23767, 23773, 23789, - 23801, 23813, - 23819, 23827, 23831, 23833, 23857, 23869, 23873, 23879, 23887, 23893, - 23899, 23909, - 23911, 23917, 23929, 23957, 23971, 23977, 23981, 23993, 24001, 24007, - 24019, 24023, - 24029, 24043, 24049, 24061, 24071, 24077, 24083, 24091, 24097, 24103, - 24107, 24109, - 24113, 24121, 24133, 24137, 24151, 24169, 24179, 24181, 24197, 24203, - 24223, 24229, - 24239, 24247, 24251, 24281, 24317, 24329, 24337, 24359, 24371, 24373, - 24379, 24391, - 24407, 24413, 24419, 24421, 24439, 24443, 24469, 24473, 24481, 24499, - 24509, 24517, - 24527, 24533, 24547, 24551, 24571, 24593, 24611, 24623, 24631, 24659, - 24671, 24677, - 24683, 24691, 24697, 24709, 24733, 24749, 24763, 24767, 24781, 24793, - 24799, 24809, - 24821, 24841, 24847, 24851, 24859, 24877, 24889, 24907, 24917, 24919, - 24923, 24943, - 24953, 24967, 24971, 24977, 24979, 24989, 25013, 25031, 25033, 25037, - 25057, 25073, - 25087, 25097, 25111, 25117, 25121, 25127, 25147, 25153, 25163, 25169, - 25171, 25183, - 25189, 25219, 25229, 25237, 25243, 25247, 25253, 25261, 25301, 25303, - 25307, 25309, - 25321, 25339, 25343, 25349, 25357, 25367, 25373, 25391, 25409, 25411, - 25423, 25439, - 25447, 25453, 25457, 25463, 25469, 25471, 25523, 25537, 25541, 25561, - 25577, 25579, - 25583, 25589, 25601, 25603, 25609, 25621, 25633, 25639, 25643, 25657, - 25667, 25673, - 25679, 25693, 25703, 25717, 25733, 25741, 25747, 25759, 25763, 25771, - 25793, 25799, - 25801, 25819, 25841, 25847, 25849, 25867, 25873, 25889, 25903, 25913, - 25919, 25931, - 25933, 25939, 25943, 25951, 25969, 25981, 25997, 25999, 26003, 26017, - 26021, 26029, - 26041, 26053, 26083, 26099, 26107, 26111, 26113, 26119, 26141, 26153, - 26161, 26171, - 26177, 26183, 26189, 26203, 26209, 26227, 26237, 26249, 26251, 26261, - 26263, 26267, - 26293, 26297, 26309, 26317, 26321, 26339, 26347, 26357, 26371, 26387, - 26393, 26399, - 26407, 26417, 26423, 26431, 26437, 26449, 26459, 26479, 26489, 26497, - 26501, 26513, - 26539, 26557, 26561, 26573, 26591, 26597, 26627, 26633, 26641, 26647, - 26669, 26681, - 26683, 26687, 26693, 26699, 26701, 26711, 26713, 26717, 26723, 26729, - 26731, 26737, - 26759, 26777, 26783, 26801, 26813, 26821, 26833, 26839, 26849, 26861, - 26863, 26879, - 26881, 26891, 26893, 26903, 26921, 26927, 26947, 26951, 26953, 26959, - 26981, 26987, - 26993, 27011, 27017, 27031, 27043, 27059, 27061, 27067, 27073, 27077, - 27091, 27103, - 27107, 27109, 27127, 27143, 27179, 27191, 27197, 27211, 27239, 27241, - 27253, 27259, - 27271, 27277, 27281, 27283, 27299, 27329, 27337, 27361, 27367, 27397, - 27407, 27409, - 27427, 27431, 27437, 27449, 27457, 27479, 27481, 27487, 27509, 27527, - 27529, 27539, - 27541, 27551, 27581, 27583, 27611, 27617, 27631, 27647, 27653, 27673, - 27689, 27691, - 27697, 27701, 27733, 27737, 27739, 27743, 27749, 27751, 27763, 27767, - 27773, 27779, - 27791, 27793, 27799, 27803, 27809, 27817, 27823, 27827, 27847, 27851, - 27883, 27893, - 27901, 27917, 27919, 27941, 27943, 27947, 27953, 27961, 27967, 27983, - 27997, 28001, - 28019, 28027, 28031, 28051, 28057, 28069, 28081, 28087, 28097, 28099, - 28109, 28111, - 28123, 28151, 28163, 28181, 28183, 28201, 28211, 28219, 28229, 28277, - 28279, 28283, - 28289, 28297, 28307, 28309, 28319, 28349, 28351, 28387, 28393, 28403, - 28409, 28411, - 28429, 28433, 28439, 28447, 28463, 28477, 28493, 28499, 28513, 28517, - 28537, 28541, - 28547, 28549, 28559, 28571, 28573, 28579, 28591, 28597, 28603, 28607, - 28619, 28621, - 28627, 28631, 28643, 28649, 28657, 28661, 28663, 28669, 28687, 28697, - 28703, 28711, - 28723, 28729, 28751, 28753, 28759, 28771, 28789, 28793, 28807, 28813, - 28817, 28837, - 28843, 28859, 28867, 28871, 28879, 28901, 28909, 28921, 28927, 28933, - 28949, 28961, - 28979, 29009, 29017, 29021, 29023, 29027, 29033, 29059, 29063, 29077, - 29101, 29123, - 29129, 29131, 29137, 29147, 29153, 29167, 29173, 29179, 29191, 29201, - 29207, 29209, - 29221, 29231, 29243, 29251, 29269, 29287, 29297, 29303, 29311, 29327, - 29333, 29339, - 29347, 29363, 29383, 29387, 29389, 29399, 29401, 29411, 29423, 29429, - 29437, 29443, - 29453, 29473, 29483, 29501, 29527, 29531, 29537, 29567, 29569, 29573, - 29581, 29587, - 29599, 29611, 29629, 29633, 29641, 29663, 29669, 29671, 29683, 29717, - 29723, 29741, - 29753, 29759, 29761, 29789, 29803, 29819, 29833, 29837, 29851, 29863, - 29867, 29873, - 29879, 29881, 29917, 29921, 29927, 29947, 29959, 29983, 29989, 30011, - 30013, 30029, - 30047, 30059, 30071, 30089, 30091, 30097, 30103, 30109, 30113, 30119, - 30133, 30137, - 30139, 30161, 30169, 30181, 30187, 30197, 30203, 30211, 30223, 30241, - 30253, 30259, - 30269, 30271, 30293, 30307, 30313, 30319, 30323, 30341, 30347, 30367, - 30389, 30391, - 30403, 30427, 30431, 30449, 30467, 30469, 30491, 30493, 30497, 30509, - 30517, 30529, - 30539, 30553, 30557, 30559, 30577, 30593, 30631, 30637, 30643, 30649, - 30661, 30671, - 30677, 30689, 30697, 30703, 30707, 30713, 30727, 30757, 30763, 30773, - 30781, 30803, - 30809, 30817, 30829, 30839, 30841, 30851, 30853, 30859, 30869, 30871, - 30881, 30893, - 30911, 30931, 30937, 30941, 30949, 30971, 30977, 30983, 31013, 31019, - 31033, 31039, - 31051, 31063, 31069, 31079, 31081, 31091, 31121, 31123, 31139, 31147, - 31151, 31153, - 31159, 31177, 31181, 31183, 31189, 31193, 31219, 31223, 31231, 31237, - 31247, 31249, - 31253, 31259, 31267, 31271, 31277, 31307, 31319, 31321, 31327, 31333, - 31337, 31357, - 31379, 31387, 31391, 31393, 31397, 31469, 31477, 31481, 31489, 31511, - 31513, 31517, - 31531, 31541, 31543, 31547, 31567, 31573, 31583, 31601, 31607, 31627, - 31643, 31649, - 31657, 31663, 31667, 31687, 31699, 31721, 31723, 31727, 31729, 31741, - 31751, 31769, - 31771, 31793, 31799, 31817, 31847, 31849, 31859, 31873, 31883, 31891, - 31907, 31957, - 31963, 31973, 31981, 31991, 32003, 32009, 32027, 32029, 32051, 32057, - 32059, 32063, - 32069, 32077, 32083, 32089, 32099, 32117, 32119, 32141, 32143, 32159, - 32173, 32183, - 32189, 32191, 32203, 32213, 32233, 32237, 32251, 32257, 32261, 32297, - 32299, 32303, - 32309, 32321, 32323, 32327, 32341, 32353, 32359, 32363, 32369, 32371, - 32377, 32381, - 32401, 32411, 32413, 32423, 32429, 32441, 32443, 32467, 32479, 32491, - 32497, 32503, - 32507, 32531, 32533, 32537, 32561, 32563, 32569, 32573, 32579, 32587, - 32603, 32609, - 32611, 32621, 32633, 32647, 32653, 32687, 32693, 32707, 32713, 32717, - 32719, 32749, - 32771, 32779, 32783, 32789, 32797, 32801, 32803, 32831, 32833, 32839, - 32843, 32869, - 32887, 32909, 32911, 32917, 32933, 32939, 32941, 32957, 32969, 32971, - 32983, 32987, - 32993, 32999, 33013, 33023, 33029, 33037, 33049, 33053, 33071, 33073, - 33083, 33091, - 33107, 33113, 33119, 33149, 33151, 33161, 33179, 33181, 33191, 33199, - 33203, 33211, - 33223, 33247, 33287, 33289, 33301, 33311, 33317, 33329, 33331, 33343, - 33347, 33349, - 33353, 33359, 33377, 33391, 33403, 33409, 33413, 33427, 33457, 33461, - 33469, 33479, - 33487, 33493, 33503, 33521, 33529, 33533, 33547, 33563, 33569, 33577, - 33581, 33587, - 33589, 33599, 33601, 33613, 33617, 33619, 33623, 33629, 33637, 33641, - 33647, 33679, - 33703, 33713, 33721, 33739, 33749, 33751, 33757, 33767, 33769, 33773, - 33791, 33797, - 33809, 33811, 33827, 33829, 33851, 33857, 33863, 33871, 33889, 33893, - 33911, 33923, - 33931, 33937, 33941, 33961, 33967, 33997, 34019, 34031, 34033, 34039, - 34057, 34061, - 34123, 34127, 34129, 34141, 34147, 34157, 34159, 34171, 34183, 34211, - 34213, 34217, - 34231, 34253, 34259, 34261, 34267, 34273, 34283, 34297, 34301, 34303, - 34313, 34319, - 34327, 34337, 34351, 34361, 34367, 34369, 34381, 34403, 34421, 34429, - 34439, 34457, - 34469, 34471, 34483, 34487, 34499, 34501, 34511, 34513, 34519, 34537, - 34543, 34549, - 34583, 34589, 34591, 34603, 34607, 34613, 34631, 34649, 34651, 34667, - 34673, 34679, - 34687, 34693, 34703, 34721, 34729, 34739, 34747, 34757, 34759, 34763, - 34781, 34807, - 34819, 34841, 34843, 34847, 34849, 34871, 34877, 34883, 34897, 34913, - 34919, 34939, - 34949, 34961, 34963, 34981, 35023, 35027, 35051, 35053, 35059, 35069, - 35081, 35083, - 35089, 35099, 35107, 35111, 35117, 35129, 35141, 35149, 35153, 35159, - 35171, 35201, - 35221, 35227, 35251, 35257, 35267, 35279, 35281, 35291, 35311, 35317, - 35323, 35327, - 35339, 35353, 35363, 35381, 35393, 35401, 35407, 35419, 35423, 35437, - 35447, 35449, - 35461, 35491, 35507, 35509, 35521, 35527, 35531, 35533, 35537, 35543, - 35569, 35573, - 35591, 35593, 35597, 35603, 35617, 35671, 35677, 35729, 35731, 35747, - 35753, 35759, - 35771, 35797, 35801, 35803, 35809, 35831, 35837, 35839, 35851, 35863, - 35869, 35879, - 35897, 35899, 35911, 35923, 35933, 35951, 35963, 35969, 35977, 35983, - 35993, 35999, - 36007, 36011, 36013, 36017, 36037, 36061, 36067, 36073, 36083, 36097, - 36107, 36109, - 36131, 36137, 36151, 36161, 36187, 36191, 36209, 36217, 36229, 36241, - 36251, 36263, - 36269, 36277, 36293, 36299, 36307, 36313, 36319, 36341, 36343, 36353, - 36373, 36383, - 36389, 36433, 36451, 36457, 36467, 36469, 36473, 36479, 36493, 36497, - 36523, 36527, - 36529, 36541, 36551, 36559, 36563, 36571, 36583, 36587, 36599, 36607, - 36629, 36637, - 36643, 36653, 36671, 36677, 36683, 36691, 36697, 36709, 36713, 36721, - 36739, 36749, - 36761, 36767, 36779, 36781, 36787, 36791, 36793, 36809, 36821, 36833, - 36847, 36857, - 36871, 36877, 36887, 36899, 36901, 36913, 36919, 36923, 36929, 36931, - 36943, 36947, - 36973, 36979, 36997, 37003, 37013, 37019, 37021, 37039, 37049, 37057, - 37061, 37087, - 37097, 37117, 37123, 37139, 37159, 37171, 37181, 37189, 37199, 37201, - 37217, 37223, - 37243, 37253, 37273, 37277, 37307, 37309, 37313, 37321, 37337, 37339, - 37357, 37361, - 37363, 37369, 37379, 37397, 37409, 37423, 37441, 37447, 37463, 37483, - 37489, 37493, - 37501, 37507, 37511, 37517, 37529, 37537, 37547, 37549, 37561, 37567, - 37571, 37573, - 37579, 37589, 37591, 37607, 37619, 37633, 37643, 37649, 37657, 37663, - 37691, 37693, - 37699, 37717, 37747, 37781, 37783, 37799, 37811, 37813, 37831, 37847, - 37853, 37861, - 37871, 37879, 37889, 37897, 37907, 37951, 37957, 37963, 37967, 37987, - 37991, 37993, - 37997, 38011, 38039, 38047, 38053, 38069, 38083, 38113, 38119, 38149, - 38153, 38167, - 38177, 38183, 38189, 38197, 38201, 38219, 38231, 38237, 38239, 38261, - 38273, 38281, - 38287, 38299, 38303, 38317, 38321, 38327, 38329, 38333, 38351, 38371, - 38377, 38393, - 38431, 38447, 38449, 38453, 38459, 38461, 38501, 38543, 38557, 38561, - 38567, 38569, - 38593, 38603, 38609, 38611, 38629, 38639, 38651, 38653, 38669, 38671, - 38677, 38693, - 38699, 38707, 38711, 38713, 38723, 38729, 38737, 38747, 38749, 38767, - 38783, 38791, - 38803, 38821, 38833, 38839, 38851, 38861, 38867, 38873, 38891, 38903, - 38917, 38921, - 38923, 38933, 38953, 38959, 38971, 38977, 38993, 39019, 39023, 39041, - 39043, 39047, - 39079, 39089, 39097, 39103, 39107, 39113, 39119, 39133, 39139, 39157, - 39161, 39163, - 39181, 39191, 39199, 39209, 39217, 39227, 39229, 39233, 39239, 39241, - 39251, 39293, - 39301, 39313, 39317, 39323, 39341, 39343, 39359, 39367, 39371, 39373, - 39383, 39397, - 39409, 39419, 39439, 39443, 39451, 39461, 39499, 39503, 39509, 39511, - 39521, 39541, - 39551, 39563, 39569, 39581, 39607, 39619, 39623, 39631, 39659, 39667, - 39671, 39679, - 39703, 39709, 39719, 39727, 39733, 39749, 39761, 39769, 39779, 39791, - 39799, 39821, - 39827, 39829, 39839, 39841, 39847, 39857, 39863, 39869, 39877, 39883, - 39887, 39901, - 39929, 39937, 39953, 39971, 39979, 39983, 39989, 40009, 40013, 40031, - 40037, 40039, - 40063, 40087, 40093, 40099, 40111, 40123, 40127, 40129, 40151, 40153, - 40163, 40169, - 40177, 40189, 40193, 40213, 40231, 40237, 40241, 40253, 40277, 40283, - 40289, 40343, - 40351, 40357, 40361, 40387, 40423, 40427, 40429, 40433, 40459, 40471, - 40483, 40487, - 40493, 40499, 40507, 40519, 40529, 40531, 40543, 40559, 40577, 40583, - 40591, 40597, - 40609, 40627, 40637, 40639, 40693, 40697, 40699, 40709, 40739, 40751, - 40759, 40763, - 40771, 40787, 40801, 40813, 40819, 40823, 40829, 40841, 40847, 40849, - 40853, 40867, - 40879, 40883, 40897, 40903, 40927, 40933, 40939, 40949, 40961, 40973, - 40993, 41011, - 41017, 41023, 41039, 41047, 41051, 41057, 41077, 41081, 41113, 41117, - 41131, 41141, - 41143, 41149, 41161, 41177, 41179, 41183, 41189, 41201, 41203, 41213, - 41221, 41227, - 41231, 41233, 41243, 41257, 41263, 41269, 41281, 41299, 41333, 41341, - 41351, 41357, - 41381, 41387, 41389, 41399, 41411, 41413, 41443, 41453, 41467, 41479, - 41491, 41507, - 41513, 41519, 41521, 41539, 41543, 41549, 41579, 41593, 41597, 41603, - 41609, 41611, - 41617, 41621, 41627, 41641, 41647, 41651, 41659, 41669, 41681, 41687, - 41719, 41729, - 41737, 41759, 41761, 41771, 41777, 41801, 41809, 41813, 41843, 41849, - 41851, 41863, - 41879, 41887, 41893, 41897, 41903, 41911, 41927, 41941, 41947, 41953, - 41957, 41959, - 41969, 41981, 41983, 41999, 42013, 42017, 42019, 42023, 42043, 42061, - 42071, 42073, - 42083, 42089, 42101, 42131, 42139, 42157, 42169, 42179, 42181, 42187, - 42193, 42197, - 42209, 42221, 42223, 42227, 42239, 42257, 42281, 42283, 42293, 42299, - 42307, 42323, - 42331, 42337, 42349, 42359, 42373, 42379, 42391, 42397, 42403, 42407, - 42409, 42433, - 42437, 42443, 42451, 42457, 42461, 42463, 42467, 42473, 42487, 42491, - 42499, 42509, - 42533, 42557, 42569, 42571, 42577, 42589, 42611, 42641, 42643, 42649, - 42667, 42677, - 42683, 42689, 42697, 42701, 42703, 42709, 42719, 42727, 42737, 42743, - 42751, 42767, - 42773, 42787, 42793, 42797, 42821, 42829, 42839, 42841, 42853, 42859, - 42863, 42899, - 42901, 42923, 42929, 42937, 42943, 42953, 42961, 42967, 42979, 42989, - 43003, 43013, - 43019, 43037, 43049, 43051, 43063, 43067, 43093, 43103, 43117, 43133, - 43151, 43159, - 43177, 43189, 43201, 43207, 43223, 43237, 43261, 43271, 43283, 43291, - 43313, 43319, - 43321, 43331, 43391, 43397, 43399, 43403, 43411, 43427, 43441, 43451, - 43457, 43481, - 43487, 43499, 43517, 43541, 43543, 43573, 43577, 43579, 43591, 43597, - 43607, 43609, - 43613, 43627, 43633, 43649, 43651, 43661, 43669, 43691, 43711, 43717, - 43721, 43753, - 43759, 43777, 43781, 43783, 43787, 43789, 43793, 43801, 43853, 43867, - 43889, 43891, - 43913, 43933, 43943, 43951, 43961, 43963, 43969, 43973, 43987, 43991, - 43997, 44017, - 44021, 44027, 44029, 44041, 44053, 44059, 44071, 44087, 44089, 44101, - 44111, 44119, - 44123, 44129, 44131, 44159, 44171, 44179, 44189, 44201, 44203, 44207, - 44221, 44249, - 44257, 44263, 44267, 44269, 44273, 44279, 44281, 44293, 44351, 44357, - 44371, 44381, - 44383, 44389, 44417, 44449, 44453, 44483, 44491, 44497, 44501, 44507, - 44519, 44531, - 44533, 44537, 44543, 44549, 44563, 44579, 44587, 44617, 44621, 44623, - 44633, 44641, - 44647, 44651, 44657, 44683, 44687, 44699, 44701, 44711, 44729, 44741, - 44753, 44771, - 44773, 44777, 44789, 44797, 44809, 44819, 44839, 44843, 44851, 44867, - 44879, 44887, - 44893, 44909, 44917, 44927, 44939, 44953, 44959, 44963, 44971, 44983, - 44987, 45007, - 45013, 45053, 45061, 45077, 45083, 45119, 45121, 45127, 45131, 45137, - 45139, 45161, - 45179, 45181, 45191, 45197, 45233, 45247, 45259, 45263, 45281, 45289, - 45293, 45307, - 45317, 45319, 45329, 45337, 45341, 45343, 45361, 45377, 45389, 45403, - 45413, 45427, - 45433, 45439, 45481, 45491, 45497, 45503, 45523, 45533, 45541, 45553, - 45557, 45569, - 45587, 45589, 45599, 45613, 45631, 45641, 45659, 45667, 45673, 45677, - 45691, 45697, - 45707, 45737, 45751, 45757, 45763, 45767, 45779, 45817, 45821, 45823, - 45827, 45833, - 45841, 45853, 45863, 45869, 45887, 45893, 45943, 45949, 45953, 45959, - 45971, 45979, - 45989, 46021, 46027, 46049, 46051, 46061, 46073, 46091, 46093, 46099, - 46103, 46133, - 46141, 46147, 46153, 46171, 46181, 46183, 46187, 46199, 46219, 46229, - 46237, 46261, - 46271, 46273, 46279, 46301, 46307, 46309, 46327, 46337, 46349, 46351, - 46381, 46399, - 46411, 46439, 46441, 46447, 46451, 46457, 46471, 46477, 46489, 46499, - 46507, 46511, - 46523, 46549, 46559, 46567, 46573, 46589, 46591, 46601, 46619, 46633, - 46639, 46643, - 46649, 46663, 46679, 46681, 46687, 46691, 46703, 46723, 46727, 46747, - 46751, 46757, - 46769, 46771, 46807, 46811, 46817, 46819, 46829, 46831, 46853, 46861, - 46867, 46877, - 46889, 46901, 46919, 46933, 46957, 46993, 46997, 47017, 47041, 47051, - 47057, 47059, - 47087, 47093, 47111, 47119, 47123, 47129, 47137, 47143, 47147, 47149, - 47161, 47189, - 47207, 47221, 47237, 47251, 47269, 47279, 47287, 47293, 47297, 47303, - 47309, 47317, - 47339, 47351, 47353, 47363, 47381, 47387, 47389, 47407, 47417, 47419, - 47431, 47441, - 47459, 47491, 47497, 47501, 47507, 47513, 47521, 47527, 47533, 47543, - 47563, 47569, - 47581, 47591, 47599, 47609, 47623, 47629, 47639, 47653, 47657, 47659, - 47681, 47699, - 47701, 47711, 47713, 47717, 47737, 47741, 47743, 47777, 47779, 47791, - 47797, 47807, - 47809, 47819, 47837, 47843, 47857, 47869, 47881, 47903, 47911, 47917, - 47933, 47939, - 47947, 47951, 47963, 47969, 47977, 47981, 48017, 48023, 48029, 48049, - 48073, 48079, - 48091, 48109, 48119, 48121, 48131, 48157, 48163, 48179, 48187, 48193, - 48197, 48221, - 48239, 48247, 48259, 48271, 48281, 48299, 48311, 48313, 48337, 48341, - 48353, 48371, - 48383, 48397, 48407, 48409, 48413, 48437, 48449, 48463, 48473, 48479, - 48481, 48487, - 48491, 48497, 48523, 48527, 48533, 48539, 48541, 48563, 48571, 48589, - 48593, 48611, - 48619, 48623, 48647, 48649, 48661, 48673, 48677, 48679, 48731, 48733, - 48751, 48757, - 48761, 48767, 48779, 48781, 48787, 48799, 48809, 48817, 48821, 48823, - 48847, 48857, - 48859, 48869, 48871, 48883, 48889, 48907, 48947, 48953, 48973, 48989, - 48991, 49003, - 49009, 49019, 49031, 49033, 49037, 49043, 49057, 49069, 49081, 49103, - 49109, 49117, - 49121, 49123, 49139, 49157, 49169, 49171, 49177, 49193, 49199, 49201, - 49207, 49211, - 49223, 49253, 49261, 49277, 49279, 49297, 49307, 49331, 49333, 49339, - 49363, 49367, - 49369, 49391, 49393, 49409, 49411, 49417, 49429, 49433, 49451, 49459, - 49463, 49477, - 49481, 49499, 49523, 49529, 49531, 49537, 49547, 49549, 49559, 49597, - 49603, 49613, - 49627, 49633, 49639, 49663, 49667, 49669, 49681, 49697, 49711, 49727, - 49739, 49741, - 49747, 49757, 49783, 49787, 49789, 49801, 49807, 49811, 49823, 49831, - 49843, 49853, - 49871, 49877, 49891, 49919, 49921, 49927, 49937, 49939, 49943, 49957, - 49991, 49993, - 49999, 50021, 50023, 50033, 50047, 50051, 50053, 50069, 50077, 50087, - 50093, 50101, - 50111, 50119, 50123, 50129, 50131, 50147, 50153, 50159, 50177, 50207, - 50221, 50227, - 50231, 50261, 50263, 50273, 50287, 50291, 50311, 50321, 50329, 50333, - 50341, 50359, - 50363, 50377, 50383, 50387, 50411, 50417, 50423, 50441, 50459, 50461, - 50497, 50503, - 50513, 50527, 50539, 50543, 50549, 50551, 50581, 50587, 50591, 50593, - 50599, 50627, - 50647, 50651, 50671, 50683, 50707, 50723, 50741, 50753, 50767, 50773, - 50777, 50789, - 50821, 50833, 50839, 50849, 50857, 50867, 50873, 50891, 50893, 50909, - 50923, 50929, - 50951, 50957, 50969, 50971, 50989, 50993, 51001, 51031, 51043, 51047, - 51059, 51061, - 51071, 51109, 51131, 51133, 51137, 51151, 51157, 51169, 51193, 51197, - 51199, 51203, - 51217, 51229, 51239, 51241, 51257, 51263, 51283, 51287, 51307, 51329, - 51341, 51343, - 51347, 51349, 51361, 51383, 51407, 51413, 51419, 51421, 51427, 51431, - 51437, 51439, - 51449, 51461, 51473, 51479, 51481, 51487, 51503, 51511, 51517, 51521, - 51539, 51551, - 51563, 51577, 51581, 51593, 51599, 51607, 51613, 51631, 51637, 51647, - 51659, 51673, - 51679, 51683, 51691, 51713, 51719, 51721, 51749, 51767, 51769, 51787, - 51797, 51803, - 51817, 51827, 51829, 51839, 51853, 51859, 51869, 51871, 51893, 51899, - 51907, 51913, - 51929, 51941, 51949, 51971, 51973, 51977, 51991, 52009, 52021, 52027, - 52051, 52057, - 52067, 52069, 52081, 52103, 52121, 52127, 52147, 52153, 52163, 52177, - 52181, 52183, - 52189, 52201, 52223, 52237, 52249, 52253, 52259, 52267, 52289, 52291, - 52301, 52313, - 52321, 52361, 52363, 52369, 52379, 52387, 52391, 52433, 52453, 52457, - 52489, 52501, - 52511, 52517, 52529, 52541, 52543, 52553, 52561, 52567, 52571, 52579, - 52583, 52609, - 52627, 52631, 52639, 52667, 52673, 52691, 52697, 52709, 52711, 52721, - 52727, 52733, - 52747, 52757, 52769, 52783, 52807, 52813, 52817, 52837, 52859, 52861, - 52879, 52883, - 52889, 52901, 52903, 52919, 52937, 52951, 52957, 52963, 52967, 52973, - 52981, 52999, - 53003, 53017, 53047, 53051, 53069, 53077, 53087, 53089, 53093, 53101, - 53113, 53117, - 53129, 53147, 53149, 53161, 53171, 53173, 53189, 53197, 53201, 53231, - 53233, 53239, - 53267, 53269, 53279, 53281, 53299, 53309, 53323, 53327, 53353, 53359, - 53377, 53381, - 53401, 53407, 53411, 53419, 53437, 53441, 53453, 53479, 53503, 53507, - 53527, 53549, - 53551, 53569, 53591, 53593, 53597, 53609, 53611, 53617, 53623, 53629, - 53633, 53639, - 53653, 53657, 53681, 53693, 53699, 53717, 53719, 53731, 53759, 53773, - 53777, 53783, - 53791, 53813, 53819, 53831, 53849, 53857, 53861, 53881, 53887, 53891, - 53897, 53899, - 53917, 53923, 53927, 53939, 53951, 53959, 53987, 53993, 54001, 54011, - 54013, 54037, - 54049, 54059, 54083, 54091, 54101, 54121, 54133, 54139, 54151, 54163, - 54167, 54181, - 54193, 54217, 54251, 54269, 54277, 54287, 54293, 54311, 54319, 54323, - 54331, 54347, - 54361, 54367, 54371, 54377, 54401, 54403, 54409, 54413, 54419, 54421, - 54437, 54443, - 54449, 54469, 54493, 54497, 54499, 54503, 54517, 54521, 54539, 54541, - 54547, 54559, - 54563, 54577, 54581, 54583, 54601, 54617, 54623, 54629, 54631, 54647, - 54667, 54673, - 54679, 54709, 54713, 54721, 54727, 54751, 54767, 54773, 54779, 54787, - 54799, 54829, - 54833, 54851, 54869, 54877, 54881, 54907, 54917, 54919, 54941, 54949, - 54959, 54973, - 54979, 54983, 55001, 55009, 55021, 55049, 55051, 55057, 55061, 55073, - 55079, 55103, - 55109, 55117, 55127, 55147, 55163, 55171, 55201, 55207, 55213, 55217, - 55219, 55229, - 55243, 55249, 55259, 55291, 55313, 55331, 55333, 55337, 55339, 55343, - 55351, 55373, - 55381, 55399, 55411, 55439, 55441, 55457, 55469, 55487, 55501, 55511, - 55529, 55541, - 55547, 55579, 55589, 55603, 55609, 55619, 55621, 55631, 55633, 55639, - 55661, 55663, - 55667, 55673, 55681, 55691, 55697, 55711, 55717, 55721, 55733, 55763, - 55787, 55793, - 55799, 55807, 55813, 55817, 55819, 55823, 55829, 55837, 55843, 55849, - 55871, 55889, - 55897, 55901, 55903, 55921, 55927, 55931, 55933, 55949, 55967, 55987, - 55997, 56003, - 56009, 56039, 56041, 56053, 56081, 56087, 56093, 56099, 56101, 56113, - 56123, 56131, - 56149, 56167, 56171, 56179, 56197, 56207, 56209, 56237, 56239, 56249, - 56263, 56267, - 56269, 56299, 56311, 56333, 56359, 56369, 56377, 56383, 56393, 56401, - 56417, 56431, - 56437, 56443, 56453, 56467, 56473, 56477, 56479, 56489, 56501, 56503, - 56509, 56519, - 56527, 56531, 56533, 56543, 56569, 56591, 56597, 56599, 56611, 56629, - 56633, 56659, - 56663, 56671, 56681, 56687, 56701, 56711, 56713, 56731, 56737, 56747, - 56767, 56773, - 56779, 56783, 56807, 56809, 56813, 56821, 56827, 56843, 56857, 56873, - 56891, 56893, - 56897, 56909, 56911, 56921, 56923, 56929, 56941, 56951, 56957, 56963, - 56983, 56989, - 56993, 56999, 57037, 57041, 57047, 57059, 57073, 57077, 57089, 57097, - 57107, 57119, - 57131, 57139, 57143, 57149, 57163, 57173, 57179, 57191, 57193, 57203, - 57221, 57223, - 57241, 57251, 57259, 57269, 57271, 57283, 57287, 57301, 57329, 57331, - 57347, 57349, - 57367, 57373, 57383, 57389, 57397, 57413, 57427, 57457, 57467, 57487, - 57493, 57503, - 57527, 57529, 57557, 57559, 57571, 57587, 57593, 57601, 57637, 57641, - 57649, 57653, - 57667, 57679, 57689, 57697, 57709, 57713, 57719, 57727, 57731, 57737, - 57751, 57773, - 57781, 57787, 57791, 57793, 57803, 57809, 57829, 57839, 57847, 57853, - 57859, 57881, - 57899, 57901, 57917, 57923, 57943, 57947, 57973, 57977, 57991, 58013, - 58027, 58031, - 58043, 58049, 58057, 58061, 58067, 58073, 58099, 58109, 58111, 58129, - 58147, 58151, - 58153, 58169, 58171, 58189, 58193, 58199, 58207, 58211, 58217, 58229, - 58231, 58237, - 58243, 58271, 58309, 58313, 58321, 58337, 58363, 58367, 58369, 58379, - 58391, 58393, - 58403, 58411, 58417, 58427, 58439, 58441, 58451, 58453, 58477, 58481, - 58511, 58537, - 58543, 58549, 58567, 58573, 58579, 58601, 58603, 58613, 58631, 58657, - 58661, 58679, - 58687, 58693, 58699, 58711, 58727, 58733, 58741, 58757, 58763, 58771, - 58787, 58789, - 58831, 58889, 58897, 58901, 58907, 58909, 58913, 58921, 58937, 58943, - 58963, 58967, - 58979, 58991, 58997, 59009, 59011, 59021, 59023, 59029, 59051, 59053, - 59063, 59069, - 59077, 59083, 59093, 59107, 59113, 59119, 59123, 59141, 59149, 59159, - 59167, 59183, - 59197, 59207, 59209, 59219, 59221, 59233, 59239, 59243, 59263, 59273, - 59281, 59333, - 59341, 59351, 59357, 59359, 59369, 59377, 59387, 59393, 59399, 59407, - 59417, 59419, - 59441, 59443, 59447, 59453, 59467, 59471, 59473, 59497, 59509, 59513, - 59539, 59557, - 59561, 59567, 59581, 59611, 59617, 59621, 59627, 59629, 59651, 59659, - 59663, 59669, - 59671, 59693, 59699, 59707, 59723, 59729, 59743, 59747, 59753, 59771, - 59779, 59791, - 59797, 59809, 59833, 59863, 59879, 59887, 59921, 59929, 59951, 59957, - 59971, 59981, - 59999, 60013, 60017, 60029, 60037, 60041, 60077, 60083, 60089, 60091, - 60101, 60103, - 60107, 60127, 60133, 60139, 60149, 60161, 60167, 60169, 60209, 60217, - 60223, 60251, - 60257, 60259, 60271, 60289, 60293, 60317, 60331, 60337, 60343, 60353, - 60373, 60383, - 60397, 60413, 60427, 60443, 60449, 60457, 60493, 60497, 60509, 60521, - 60527, 60539, - 60589, 60601, 60607, 60611, 60617, 60623, 60631, 60637, 60647, 60649, - 60659, 60661, - 60679, 60689, 60703, 60719, 60727, 60733, 60737, 60757, 60761, 60763, - 60773, 60779, - 60793, 60811, 60821, 60859, 60869, 60887, 60889, 60899, 60901, 60913, - 60917, 60919, - 60923, 60937, 60943, 60953, 60961, 61001, 61007, 61027, 61031, 61043, - 61051, 61057, - 61091, 61099, 61121, 61129, 61141, 61151, 61153, 61169, 61211, 61223, - 61231, 61253, - 61261, 61283, 61291, 61297, 61331, 61333, 61339, 61343, 61357, 61363, - 61379, 61381, - 61403, 61409, 61417, 61441, 61463, 61469, 61471, 61483, 61487, 61493, - 61507, 61511, - 61519, 61543, 61547, 61553, 61559, 61561, 61583, 61603, 61609, 61613, - 61627, 61631, - 61637, 61643, 61651, 61657, 61667, 61673, 61681, 61687, 61703, 61717, - 61723, 61729, - 61751, 61757, 61781, 61813, 61819, 61837, 61843, 61861, 61871, 61879, - 61909, 61927, - 61933, 61949, 61961, 61967, 61979, 61981, 61987, 61991, 62003, 62011, - 62017, 62039, - 62047, 62053, 62057, 62071, 62081, 62099, 62119, 62129, 62131, 62137, - 62141, 62143, - 62171, 62189, 62191, 62201, 62207, 62213, 62219, 62233, 62273, 62297, - 62299, 62303, - 62311, 62323, 62327, 62347, 62351, 62383, 62401, 62417, 62423, 62459, - 62467, 62473, - 62477, 62483, 62497, 62501, 62507, 62533, 62539, 62549, 62563, 62581, - 62591, 62597, - 62603, 62617, 62627, 62633, 62639, 62653, 62659, 62683, 62687, 62701, - 62723, 62731, - 62743, 62753, 62761, 62773, 62791, 62801, 62819, 62827, 62851, 62861, - 62869, 62873, - 62897, 62903, 62921, 62927, 62929, 62939, 62969, 62971, 62981, 62983, - 62987, 62989, - 63029, 63031, 63059, 63067, 63073, 63079, 63097, 63103, 63113, 63127, - 63131, 63149, - 63179, 63197, 63199, 63211, 63241, 63247, 63277, 63281, 63299, 63311, - 63313, 63317, - 63331, 63337, 63347, 63353, 63361, 63367, 63377, 63389, 63391, 63397, - 63409, 63419, - 63421, 63439, 63443, 63463, 63467, 63473, 63487, 63493, 63499, 63521, - 63527, 63533, - 63541, 63559, 63577, 63587, 63589, 63599, 63601, 63607, 63611, 63617, - 63629, 63647, - 63649, 63659, 63667, 63671, 63689, 63691, 63697, 63703, 63709, 63719, - 63727, 63737, - 63743, 63761, 63773, 63781, 63793, 63799, 63803, 63809, 63823, 63839, - 63841, 63853, - 63857, 63863, 63901, 63907, 63913, 63929, 63949, 63977, 63997, 64007, - 64013, 64019, - 64033, 64037, 64063, 64067, 64081, 64091, 64109, 64123, 64151, 64153, - 64157, 64171, - 64187, 64189, 64217, 64223, 64231, 64237, 64271, 64279, 64283, 64301, - 64303, 64319, - 64327, 64333, 64373, 64381, 64399, 64403, 64433, 64439, 64451, 64453, - 64483, 64489, - 64499, 64513, 64553, 64567, 64577, 64579, 64591, 64601, 64609, 64613, - 64621, 64627, - 64633, 64661, 64663, 64667, 64679, 64693, 64709, 64717, 64747, 64763, - 64781, 64783, - 64793, 64811, 64817, 64849, 64853, 64871, 64877, 64879, 64891, 64901, - 64919, 64921, - 64927, 64937, 64951, 64969, 64997, 65003, 65011, 65027, 65029, 65033, - 65053, 65063, - 65071, 65089, 65099, 65101, 65111, 65119, 65123, 65129, 65141, 65147, - 65167, 65171, - 65173, 65179, 65183, 65203, 65213, 65239, 65257, 65267, 65269, 65287, - 65293, 65309, - 65323, 65327, 65353, 65357, 65371, 65381, 65393, 65407, 65413, 65419, - 65423, 65437, - 65447, 65449, 65479, 65497, 65519, 65521, + 10009, 10037, 10039, 10061, 10067, 10069, 10079, 10091, 10093, + 10099, 10103, 10111, 10133, 10139, 10141, 10151, 10159, 10163, + 10169, 10177, 10181, 10193, 10211, 10223, 10243, 10247, 10253, + 10259, 10267, 10271, 10273, 10289, 10301, 10303, 10313, 10321, + 10331, 10333, 10337, 10343, 10357, 10369, 10391, 10399, 10427, + 10429, 10433, 10453, 10457, 10459, 10463, 10477, 10487, 10499, + 10501, 10513, 10529, 10531, 10559, 10567, 10589, 10597, 10601, + 10607, 10613, 10627, 10631, 10639, 10651, 10657, 10663, 10667, + 10687, 10691, 10709, 10711, 10723, 10729, 10733, 10739, 10753, + 10771, 10781, 10789, 10799, 10831, 10837, 10847, 10853, 10859, + 10861, 10867, 10883, 10889, 10891, 10903, 10909, 10937, 10939, + 10949, 10957, 10973, 10979, 10987, 10993, 11003, 11027, 11047, + 11057, 11059, 11069, 11071, 11083, 11087, 11093, 11113, 11117, + 11119, 11131, 11149, 11159, 11161, 11171, 11173, 11177, 11197, + 11213, 11239, 11243, 11251, 11257, 11261, 11273, 11279, 11287, + 11299, 11311, 11317, 11321, 11329, 11351, 11353, 11369, 11383, + 11393, 11399, 11411, 11423, 11437, 11443, 11447, 11467, 11471, + 11483, 11489, 11491, 11497, 11503, 11519, 11527, 11549, 11551, + 11579, 11587, 11593, 11597, 11617, 11621, 11633, 11657, 11677, + 11681, 11689, 11699, 11701, 11717, 11719, 11731, 11743, 11777, + 11779, 11783, 11789, 11801, 11807, 11813, 11821, 11827, 11831, + 11833, 11839, 11863, 11867, 11887, 11897, 11903, 11909, 11923, + 11927, 11933, 11939, 11941, 11953, 11959, 11969, 11971, 11981, + 11987, 12007, 12011, 12037, 12041, 12043, 12049, 12071, 12073, + 12097, 12101, 12107, 12109, 12113, 12119, 12143, 12149, 12157, + 12161, 12163, 12197, 12203, 12211, 12227, 12239, 12241, 12251, + 12253, 12263, 12269, 12277, 12281, 12289, 12301, 12323, 12329, + 12343, 12347, 12373, 12377, 12379, 12391, 12401, 12409, 12413, + 12421, 12433, 12437, 12451, 12457, 12473, 12479, 12487, 12491, + 12497, 12503, 12511, 12517, 12527, 12539, 12541, 12547, 12553, + 12569, 12577, 12583, 12589, 12601, 12611, 12613, 12619, 12637, + 12641, 12647, 12653, 12659, 12671, 12689, 12697, 12703, 12713, + 12721, 12739, 12743, 12757, 12763, 12781, 12791, 12799, 12809, + 12821, 12823, 12829, 12841, 12853, 12889, 12893, 12899, 12907, + 12911, 12917, 12919, 12923, 12941, 12953, 12959, 12967, 12973, + 12979, 12983, 13001, 13003, 13007, 13009, 13033, 13037, 13043, + 13049, 13063, 13093, 13099, 13103, 13109, 13121, 13127, 13147, + 13151, 13159, 13163, 13171, 13177, 13183, 13187, 13217, 13219, + 13229, 13241, 13249, 13259, 13267, 13291, 13297, 13309, 13313, + 13327, 13331, 13337, 13339, 13367, 13381, 13397, 13399, 13411, + 13417, 13421, 13441, 13451, 13457, 13463, 13469, 13477, 13487, + 13499, 13513, 13523, 13537, 13553, 13567, 13577, 13591, 13597, + 13613, 13619, 13627, 13633, 13649, 13669, 13679, 13681, 13687, + 13691, 13693, 13697, 13709, 13711, 13721, 13723, 13729, 13751, + 13757, 13759, 13763, 13781, 13789, 13799, 13807, 13829, 13831, + 13841, 13859, 13873, 13877, 13879, 13883, 13901, 13903, 13907, + 13913, 13921, 13931, 13933, 13963, 13967, 13997, 13999, 14009, + 14011, 14029, 14033, 14051, 14057, 14071, 14081, 14083, 14087, + 14107, 14143, 14149, 14153, 14159, 14173, 14177, 14197, 14207, + 14221, 14243, 14249, 14251, 14281, 14293, 14303, 14321, 14323, + 14327, 14341, 14347, 14369, 14387, 14389, 14401, 14407, 14411, + 14419, 14423, 14431, 14437, 14447, 14449, 14461, 14479, 14489, + 14503, 14519, 14533, 14537, 14543, 14549, 14551, 14557, 14561, + 14563, 14591, 14593, 14621, 14627, 14629, 14633, 14639, 14653, + 14657, 14669, 14683, 14699, 14713, 14717, 14723, 14731, 14737, + 14741, 14747, 14753, 14759, 14767, 14771, 14779, 14783, 14797, + 14813, 14821, 14827, 14831, 14843, 14851, 14867, 14869, 14879, + 14887, 14891, 14897, 14923, 14929, 14939, 14947, 14951, 14957, + 14969, 14983, 15013, 15017, 15031, 15053, 15061, 15073, 15077, + 15083, 15091, 15101, 15107, 15121, 15131, 15137, 15139, 15149, + 15161, 15173, 15187, 15193, 15199, 15217, 15227, 15233, 15241, + 15259, 15263, 15269, 15271, 15277, 15287, 15289, 15299, 15307, + 15313, 15319, 15329, 15331, 15349, 15359, 15361, 15373, 15377, + 15383, 15391, 15401, 15413, 15427, 15439, 15443, 15451, 15461, + 15467, 15473, 15493, 15497, 15511, 15527, 15541, 15551, 15559, + 15569, 15581, 15583, 15601, 15607, 15619, 15629, 15641, 15643, + 15647, 15649, 15661, 15667, 15671, 15679, 15683, 15727, 15731, + 15733, 15737, 15739, 15749, 15761, 15767, 15773, 15787, 15791, + 15797, 15803, 15809, 15817, 15823, 15859, 15877, 15881, 15887, + 15889, 15901, 15907, 15913, 15919, 15923, 15937, 15959, 15971, + 15973, 15991, 16001, 16007, 16033, 16057, 16061, 16063, 16067, + 16069, 16073, 16087, 16091, 16097, 16103, 16111, 16127, 16139, + 16141, 16183, 16187, 16189, 16193, 16217, 16223, 16229, 16231, + 16249, 16253, 16267, 16273, 16301, 16319, 16333, 16339, 16349, + 16361, 16363, 16369, 16381, 16411, 16417, 16421, 16427, 16433, + 16447, 16451, 16453, 16477, 16481, 16487, 16493, 16519, 16529, + 16547, 16553, 16561, 16567, 16573, 16603, 16607, 16619, 16631, + 16633, 16649, 16651, 16657, 16661, 16673, 16691, 16693, 16699, + 16703, 16729, 16741, 16747, 16759, 16763, 16787, 16811, 16823, + 16829, 16831, 16843, 16871, 16879, 16883, 16889, 16901, 16903, + 16921, 16927, 16931, 16937, 16943, 16963, 16979, 16981, 16987, + 16993, 17011, 17021, 17027, 17029, 17033, 17041, 17047, 17053, + 17077, 17093, 17099, 17107, 17117, 17123, 17137, 17159, 17167, + 17183, 17189, 17191, 17203, 17207, 17209, 17231, 17239, 17257, + 17291, 17293, 17299, 17317, 17321, 17327, 17333, 17341, 17351, + 17359, 17377, 17383, 17387, 17389, 17393, 17401, 17417, 17419, + 17431, 17443, 17449, 17467, 17471, 17477, 17483, 17489, 17491, + 17497, 17509, 17519, 17539, 17551, 17569, 17573, 17579, 17581, + 17597, 17599, 17609, 17623, 17627, 17657, 17659, 17669, 17681, + 17683, 17707, 17713, 17729, 17737, 17747, 17749, 17761, 17783, + 17789, 17791, 17807, 17827, 17837, 17839, 17851, 17863, 17881, + 17891, 17903, 17909, 17911, 17921, 17923, 17929, 17939, 17957, + 17959, 17971, 17977, 17981, 17987, 17989, 18013, 18041, 18043, + 18047, 18049, 18059, 18061, 18077, 18089, 18097, 18119, 18121, + 18127, 18131, 18133, 18143, 18149, 18169, 18181, 18191, 18199, + 18211, 18217, 18223, 18229, 18233, 18251, 18253, 18257, 18269, + 18287, 18289, 18301, 18307, 18311, 18313, 18329, 18341, 18353, + 18367, 18371, 18379, 18397, 18401, 18413, 18427, 18433, 18439, + 18443, 18451, 18457, 18461, 18481, 18493, 18503, 18517, 18521, + 18523, 18539, 18541, 18553, 18583, 18587, 18593, 18617, 18637, + 18661, 18671, 18679, 18691, 18701, 18713, 18719, 18731, 18743, + 18749, 18757, 18773, 18787, 18793, 18797, 18803, 18839, 18859, + 18869, 18899, 18911, 18913, 18917, 18919, 18947, 18959, 18973, + 18979, 19001, 19009, 19013, 19031, 19037, 19051, 19069, 19073, + 19079, 19081, 19087, 19121, 19139, 19141, 19157, 19163, 19181, + 19183, 19207, 19211, 19213, 19219, 19231, 19237, 19249, 19259, + 19267, 19273, 19289, 19301, 19309, 19319, 19333, 19373, 19379, + 19381, 19387, 19391, 19403, 19417, 19421, 19423, 19427, 19429, + 19433, 19441, 19447, 19457, 19463, 19469, 19471, 19477, 19483, + 19489, 19501, 19507, 19531, 19541, 19543, 19553, 19559, 19571, + 19577, 19583, 19597, 19603, 19609, 19661, 19681, 19687, 19697, + 19699, 19709, 19717, 19727, 19739, 19751, 19753, 19759, 19763, + 19777, 19793, 19801, 19813, 19819, 19841, 19843, 19853, 19861, + 19867, 19889, 19891, 19913, 19919, 19927, 19937, 19949, 19961, + 19963, 19973, 19979, 19991, 19993, 19997, 20011, 20021, 20023, + 20029, 20047, 20051, 20063, 20071, 20089, 20101, 20107, 20113, + 20117, 20123, 20129, 20143, 20147, 20149, 20161, 20173, 20177, + 20183, 20201, 20219, 20231, 20233, 20249, 20261, 20269, 20287, + 20297, 20323, 20327, 20333, 20341, 20347, 20353, 20357, 20359, + 20369, 20389, 20393, 20399, 20407, 20411, 20431, 20441, 20443, + 20477, 20479, 20483, 20507, 20509, 20521, 20533, 20543, 20549, + 20551, 20563, 20593, 20599, 20611, 20627, 20639, 20641, 20663, + 20681, 20693, 20707, 20717, 20719, 20731, 20743, 20747, 20749, + 20753, 20759, 20771, 20773, 20789, 20807, 20809, 20849, 20857, + 20873, 20879, 20887, 20897, 20899, 20903, 20921, 20929, 20939, + 20947, 20959, 20963, 20981, 20983, 21001, 21011, 21013, 21017, + 21019, 21023, 21031, 21059, 21061, 21067, 21089, 21101, 21107, + 21121, 21139, 21143, 21149, 21157, 21163, 21169, 21179, 21187, + 21191, 21193, 21211, 21221, 21227, 21247, 21269, 21277, 21283, + 21313, 21317, 21319, 21323, 21341, 21347, 21377, 21379, 21383, + 21391, 21397, 21401, 21407, 21419, 21433, 21467, 21481, 21487, + 21491, 21493, 21499, 21503, 21517, 21521, 21523, 21529, 21557, + 21559, 21563, 21569, 21577, 21587, 21589, 21599, 21601, 21611, + 21613, 21617, 21647, 21649, 21661, 21673, 21683, 21701, 21713, + 21727, 21737, 21739, 21751, 21757, 21767, 21773, 21787, 21799, + 21803, 21817, 21821, 21839, 21841, 21851, 21859, 21863, 21871, + 21881, 21893, 21911, 21929, 21937, 21943, 21961, 21977, 21991, + 21997, 22003, 22013, 22027, 22031, 22037, 22039, 22051, 22063, + 22067, 22073, 22079, 22091, 22093, 22109, 22111, 22123, 22129, + 22133, 22147, 22153, 22157, 22159, 22171, 22189, 22193, 22229, + 22247, 22259, 22271, 22273, 22277, 22279, 22283, 22291, 22303, + 22307, 22343, 22349, 22367, 22369, 22381, 22391, 22397, 22409, + 22433, 22441, 22447, 22453, 22469, 22481, 22483, 22501, 22511, + 22531, 22541, 22543, 22549, 22567, 22571, 22573, 22613, 22619, + 22621, 22637, 22639, 22643, 22651, 22669, 22679, 22691, 22697, + 22699, 22709, 22717, 22721, 22727, 22739, 22741, 22751, 22769, + 22777, 22783, 22787, 22807, 22811, 22817, 22853, 22859, 22861, + 22871, 22877, 22901, 22907, 22921, 22937, 22943, 22961, 22963, + 22973, 22993, 23003, 23011, 23017, 23021, 23027, 23029, 23039, + 23041, 23053, 23057, 23059, 23063, 23071, 23081, 23087, 23099, + 23117, 23131, 23143, 23159, 23167, 23173, 23189, 23197, 23201, + 23203, 23209, 23227, 23251, 23269, 23279, 23291, 23293, 23297, + 23311, 23321, 23327, 23333, 23339, 23357, 23369, 23371, 23399, + 23417, 23431, 23447, 23459, 23473, 23497, 23509, 23531, 23537, + 23539, 23549, 23557, 23561, 23563, 23567, 23581, 23593, 23599, + 23603, 23609, 23623, 23627, 23629, 23633, 23663, 23669, 23671, + 23677, 23687, 23689, 23719, 23741, 23743, 23747, 23753, 23761, + 23767, 23773, 23789, 23801, 23813, 23819, 23827, 23831, 23833, + 23857, 23869, 23873, 23879, 23887, 23893, 23899, 23909, 23911, + 23917, 23929, 23957, 23971, 23977, 23981, 23993, 24001, 24007, + 24019, 24023, 24029, 24043, 24049, 24061, 24071, 24077, 24083, + 24091, 24097, 24103, 24107, 24109, 24113, 24121, 24133, 24137, + 24151, 24169, 24179, 24181, 24197, 24203, 24223, 24229, 24239, + 24247, 24251, 24281, 24317, 24329, 24337, 24359, 24371, 24373, + 24379, 24391, 24407, 24413, 24419, 24421, 24439, 24443, 24469, + 24473, 24481, 24499, 24509, 24517, 24527, 24533, 24547, 24551, + 24571, 24593, 24611, 24623, 24631, 24659, 24671, 24677, 24683, + 24691, 24697, 24709, 24733, 24749, 24763, 24767, 24781, 24793, + 24799, 24809, 24821, 24841, 24847, 24851, 24859, 24877, 24889, + 24907, 24917, 24919, 24923, 24943, 24953, 24967, 24971, 24977, + 24979, 24989, 25013, 25031, 25033, 25037, 25057, 25073, 25087, + 25097, 25111, 25117, 25121, 25127, 25147, 25153, 25163, 25169, + 25171, 25183, 25189, 25219, 25229, 25237, 25243, 25247, 25253, + 25261, 25301, 25303, 25307, 25309, 25321, 25339, 25343, 25349, + 25357, 25367, 25373, 25391, 25409, 25411, 25423, 25439, 25447, + 25453, 25457, 25463, 25469, 25471, 25523, 25537, 25541, 25561, + 25577, 25579, 25583, 25589, 25601, 25603, 25609, 25621, 25633, + 25639, 25643, 25657, 25667, 25673, 25679, 25693, 25703, 25717, + 25733, 25741, 25747, 25759, 25763, 25771, 25793, 25799, 25801, + 25819, 25841, 25847, 25849, 25867, 25873, 25889, 25903, 25913, + 25919, 25931, 25933, 25939, 25943, 25951, 25969, 25981, 25997, + 25999, 26003, 26017, 26021, 26029, 26041, 26053, 26083, 26099, + 26107, 26111, 26113, 26119, 26141, 26153, 26161, 26171, 26177, + 26183, 26189, 26203, 26209, 26227, 26237, 26249, 26251, 26261, + 26263, 26267, 26293, 26297, 26309, 26317, 26321, 26339, 26347, + 26357, 26371, 26387, 26393, 26399, 26407, 26417, 26423, 26431, + 26437, 26449, 26459, 26479, 26489, 26497, 26501, 26513, 26539, + 26557, 26561, 26573, 26591, 26597, 26627, 26633, 26641, 26647, + 26669, 26681, 26683, 26687, 26693, 26699, 26701, 26711, 26713, + 26717, 26723, 26729, 26731, 26737, 26759, 26777, 26783, 26801, + 26813, 26821, 26833, 26839, 26849, 26861, 26863, 26879, 26881, + 26891, 26893, 26903, 26921, 26927, 26947, 26951, 26953, 26959, + 26981, 26987, 26993, 27011, 27017, 27031, 27043, 27059, 27061, + 27067, 27073, 27077, 27091, 27103, 27107, 27109, 27127, 27143, + 27179, 27191, 27197, 27211, 27239, 27241, 27253, 27259, 27271, + 27277, 27281, 27283, 27299, 27329, 27337, 27361, 27367, 27397, + 27407, 27409, 27427, 27431, 27437, 27449, 27457, 27479, 27481, + 27487, 27509, 27527, 27529, 27539, 27541, 27551, 27581, 27583, + 27611, 27617, 27631, 27647, 27653, 27673, 27689, 27691, 27697, + 27701, 27733, 27737, 27739, 27743, 27749, 27751, 27763, 27767, + 27773, 27779, 27791, 27793, 27799, 27803, 27809, 27817, 27823, + 27827, 27847, 27851, 27883, 27893, 27901, 27917, 27919, 27941, + 27943, 27947, 27953, 27961, 27967, 27983, 27997, 28001, 28019, + 28027, 28031, 28051, 28057, 28069, 28081, 28087, 28097, 28099, + 28109, 28111, 28123, 28151, 28163, 28181, 28183, 28201, 28211, + 28219, 28229, 28277, 28279, 28283, 28289, 28297, 28307, 28309, + 28319, 28349, 28351, 28387, 28393, 28403, 28409, 28411, 28429, + 28433, 28439, 28447, 28463, 28477, 28493, 28499, 28513, 28517, + 28537, 28541, 28547, 28549, 28559, 28571, 28573, 28579, 28591, + 28597, 28603, 28607, 28619, 28621, 28627, 28631, 28643, 28649, + 28657, 28661, 28663, 28669, 28687, 28697, 28703, 28711, 28723, + 28729, 28751, 28753, 28759, 28771, 28789, 28793, 28807, 28813, + 28817, 28837, 28843, 28859, 28867, 28871, 28879, 28901, 28909, + 28921, 28927, 28933, 28949, 28961, 28979, 29009, 29017, 29021, + 29023, 29027, 29033, 29059, 29063, 29077, 29101, 29123, 29129, + 29131, 29137, 29147, 29153, 29167, 29173, 29179, 29191, 29201, + 29207, 29209, 29221, 29231, 29243, 29251, 29269, 29287, 29297, + 29303, 29311, 29327, 29333, 29339, 29347, 29363, 29383, 29387, + 29389, 29399, 29401, 29411, 29423, 29429, 29437, 29443, 29453, + 29473, 29483, 29501, 29527, 29531, 29537, 29567, 29569, 29573, + 29581, 29587, 29599, 29611, 29629, 29633, 29641, 29663, 29669, + 29671, 29683, 29717, 29723, 29741, 29753, 29759, 29761, 29789, + 29803, 29819, 29833, 29837, 29851, 29863, 29867, 29873, 29879, + 29881, 29917, 29921, 29927, 29947, 29959, 29983, 29989, 30011, + 30013, 30029, 30047, 30059, 30071, 30089, 30091, 30097, 30103, + 30109, 30113, 30119, 30133, 30137, 30139, 30161, 30169, 30181, + 30187, 30197, 30203, 30211, 30223, 30241, 30253, 30259, 30269, + 30271, 30293, 30307, 30313, 30319, 30323, 30341, 30347, 30367, + 30389, 30391, 30403, 30427, 30431, 30449, 30467, 30469, 30491, + 30493, 30497, 30509, 30517, 30529, 30539, 30553, 30557, 30559, + 30577, 30593, 30631, 30637, 30643, 30649, 30661, 30671, 30677, + 30689, 30697, 30703, 30707, 30713, 30727, 30757, 30763, 30773, + 30781, 30803, 30809, 30817, 30829, 30839, 30841, 30851, 30853, + 30859, 30869, 30871, 30881, 30893, 30911, 30931, 30937, 30941, + 30949, 30971, 30977, 30983, 31013, 31019, 31033, 31039, 31051, + 31063, 31069, 31079, 31081, 31091, 31121, 31123, 31139, 31147, + 31151, 31153, 31159, 31177, 31181, 31183, 31189, 31193, 31219, + 31223, 31231, 31237, 31247, 31249, 31253, 31259, 31267, 31271, + 31277, 31307, 31319, 31321, 31327, 31333, 31337, 31357, 31379, + 31387, 31391, 31393, 31397, 31469, 31477, 31481, 31489, 31511, + 31513, 31517, 31531, 31541, 31543, 31547, 31567, 31573, 31583, + 31601, 31607, 31627, 31643, 31649, 31657, 31663, 31667, 31687, + 31699, 31721, 31723, 31727, 31729, 31741, 31751, 31769, 31771, + 31793, 31799, 31817, 31847, 31849, 31859, 31873, 31883, 31891, + 31907, 31957, 31963, 31973, 31981, 31991, 32003, 32009, 32027, + 32029, 32051, 32057, 32059, 32063, 32069, 32077, 32083, 32089, + 32099, 32117, 32119, 32141, 32143, 32159, 32173, 32183, 32189, + 32191, 32203, 32213, 32233, 32237, 32251, 32257, 32261, 32297, + 32299, 32303, 32309, 32321, 32323, 32327, 32341, 32353, 32359, + 32363, 32369, 32371, 32377, 32381, 32401, 32411, 32413, 32423, + 32429, 32441, 32443, 32467, 32479, 32491, 32497, 32503, 32507, + 32531, 32533, 32537, 32561, 32563, 32569, 32573, 32579, 32587, + 32603, 32609, 32611, 32621, 32633, 32647, 32653, 32687, 32693, + 32707, 32713, 32717, 32719, 32749, 32771, 32779, 32783, 32789, + 32797, 32801, 32803, 32831, 32833, 32839, 32843, 32869, 32887, + 32909, 32911, 32917, 32933, 32939, 32941, 32957, 32969, 32971, + 32983, 32987, 32993, 32999, 33013, 33023, 33029, 33037, 33049, + 33053, 33071, 33073, 33083, 33091, 33107, 33113, 33119, 33149, + 33151, 33161, 33179, 33181, 33191, 33199, 33203, 33211, 33223, + 33247, 33287, 33289, 33301, 33311, 33317, 33329, 33331, 33343, + 33347, 33349, 33353, 33359, 33377, 33391, 33403, 33409, 33413, + 33427, 33457, 33461, 33469, 33479, 33487, 33493, 33503, 33521, + 33529, 33533, 33547, 33563, 33569, 33577, 33581, 33587, 33589, + 33599, 33601, 33613, 33617, 33619, 33623, 33629, 33637, 33641, + 33647, 33679, 33703, 33713, 33721, 33739, 33749, 33751, 33757, + 33767, 33769, 33773, 33791, 33797, 33809, 33811, 33827, 33829, + 33851, 33857, 33863, 33871, 33889, 33893, 33911, 33923, 33931, + 33937, 33941, 33961, 33967, 33997, 34019, 34031, 34033, 34039, + 34057, 34061, 34123, 34127, 34129, 34141, 34147, 34157, 34159, + 34171, 34183, 34211, 34213, 34217, 34231, 34253, 34259, 34261, + 34267, 34273, 34283, 34297, 34301, 34303, 34313, 34319, 34327, + 34337, 34351, 34361, 34367, 34369, 34381, 34403, 34421, 34429, + 34439, 34457, 34469, 34471, 34483, 34487, 34499, 34501, 34511, + 34513, 34519, 34537, 34543, 34549, 34583, 34589, 34591, 34603, + 34607, 34613, 34631, 34649, 34651, 34667, 34673, 34679, 34687, + 34693, 34703, 34721, 34729, 34739, 34747, 34757, 34759, 34763, + 34781, 34807, 34819, 34841, 34843, 34847, 34849, 34871, 34877, + 34883, 34897, 34913, 34919, 34939, 34949, 34961, 34963, 34981, + 35023, 35027, 35051, 35053, 35059, 35069, 35081, 35083, 35089, + 35099, 35107, 35111, 35117, 35129, 35141, 35149, 35153, 35159, + 35171, 35201, 35221, 35227, 35251, 35257, 35267, 35279, 35281, + 35291, 35311, 35317, 35323, 35327, 35339, 35353, 35363, 35381, + 35393, 35401, 35407, 35419, 35423, 35437, 35447, 35449, 35461, + 35491, 35507, 35509, 35521, 35527, 35531, 35533, 35537, 35543, + 35569, 35573, 35591, 35593, 35597, 35603, 35617, 35671, 35677, + 35729, 35731, 35747, 35753, 35759, 35771, 35797, 35801, 35803, + 35809, 35831, 35837, 35839, 35851, 35863, 35869, 35879, 35897, + 35899, 35911, 35923, 35933, 35951, 35963, 35969, 35977, 35983, + 35993, 35999, 36007, 36011, 36013, 36017, 36037, 36061, 36067, + 36073, 36083, 36097, 36107, 36109, 36131, 36137, 36151, 36161, + 36187, 36191, 36209, 36217, 36229, 36241, 36251, 36263, 36269, + 36277, 36293, 36299, 36307, 36313, 36319, 36341, 36343, 36353, + 36373, 36383, 36389, 36433, 36451, 36457, 36467, 36469, 36473, + 36479, 36493, 36497, 36523, 36527, 36529, 36541, 36551, 36559, + 36563, 36571, 36583, 36587, 36599, 36607, 36629, 36637, 36643, + 36653, 36671, 36677, 36683, 36691, 36697, 36709, 36713, 36721, + 36739, 36749, 36761, 36767, 36779, 36781, 36787, 36791, 36793, + 36809, 36821, 36833, 36847, 36857, 36871, 36877, 36887, 36899, + 36901, 36913, 36919, 36923, 36929, 36931, 36943, 36947, 36973, + 36979, 36997, 37003, 37013, 37019, 37021, 37039, 37049, 37057, + 37061, 37087, 37097, 37117, 37123, 37139, 37159, 37171, 37181, + 37189, 37199, 37201, 37217, 37223, 37243, 37253, 37273, 37277, + 37307, 37309, 37313, 37321, 37337, 37339, 37357, 37361, 37363, + 37369, 37379, 37397, 37409, 37423, 37441, 37447, 37463, 37483, + 37489, 37493, 37501, 37507, 37511, 37517, 37529, 37537, 37547, + 37549, 37561, 37567, 37571, 37573, 37579, 37589, 37591, 37607, + 37619, 37633, 37643, 37649, 37657, 37663, 37691, 37693, 37699, + 37717, 37747, 37781, 37783, 37799, 37811, 37813, 37831, 37847, + 37853, 37861, 37871, 37879, 37889, 37897, 37907, 37951, 37957, + 37963, 37967, 37987, 37991, 37993, 37997, 38011, 38039, 38047, + 38053, 38069, 38083, 38113, 38119, 38149, 38153, 38167, 38177, + 38183, 38189, 38197, 38201, 38219, 38231, 38237, 38239, 38261, + 38273, 38281, 38287, 38299, 38303, 38317, 38321, 38327, 38329, + 38333, 38351, 38371, 38377, 38393, 38431, 38447, 38449, 38453, + 38459, 38461, 38501, 38543, 38557, 38561, 38567, 38569, 38593, + 38603, 38609, 38611, 38629, 38639, 38651, 38653, 38669, 38671, + 38677, 38693, 38699, 38707, 38711, 38713, 38723, 38729, 38737, + 38747, 38749, 38767, 38783, 38791, 38803, 38821, 38833, 38839, + 38851, 38861, 38867, 38873, 38891, 38903, 38917, 38921, 38923, + 38933, 38953, 38959, 38971, 38977, 38993, 39019, 39023, 39041, + 39043, 39047, 39079, 39089, 39097, 39103, 39107, 39113, 39119, + 39133, 39139, 39157, 39161, 39163, 39181, 39191, 39199, 39209, + 39217, 39227, 39229, 39233, 39239, 39241, 39251, 39293, 39301, + 39313, 39317, 39323, 39341, 39343, 39359, 39367, 39371, 39373, + 39383, 39397, 39409, 39419, 39439, 39443, 39451, 39461, 39499, + 39503, 39509, 39511, 39521, 39541, 39551, 39563, 39569, 39581, + 39607, 39619, 39623, 39631, 39659, 39667, 39671, 39679, 39703, + 39709, 39719, 39727, 39733, 39749, 39761, 39769, 39779, 39791, + 39799, 39821, 39827, 39829, 39839, 39841, 39847, 39857, 39863, + 39869, 39877, 39883, 39887, 39901, 39929, 39937, 39953, 39971, + 39979, 39983, 39989, 40009, 40013, 40031, 40037, 40039, 40063, + 40087, 40093, 40099, 40111, 40123, 40127, 40129, 40151, 40153, + 40163, 40169, 40177, 40189, 40193, 40213, 40231, 40237, 40241, + 40253, 40277, 40283, 40289, 40343, 40351, 40357, 40361, 40387, + 40423, 40427, 40429, 40433, 40459, 40471, 40483, 40487, 40493, + 40499, 40507, 40519, 40529, 40531, 40543, 40559, 40577, 40583, + 40591, 40597, 40609, 40627, 40637, 40639, 40693, 40697, 40699, + 40709, 40739, 40751, 40759, 40763, 40771, 40787, 40801, 40813, + 40819, 40823, 40829, 40841, 40847, 40849, 40853, 40867, 40879, + 40883, 40897, 40903, 40927, 40933, 40939, 40949, 40961, 40973, + 40993, 41011, 41017, 41023, 41039, 41047, 41051, 41057, 41077, + 41081, 41113, 41117, 41131, 41141, 41143, 41149, 41161, 41177, + 41179, 41183, 41189, 41201, 41203, 41213, 41221, 41227, 41231, + 41233, 41243, 41257, 41263, 41269, 41281, 41299, 41333, 41341, + 41351, 41357, 41381, 41387, 41389, 41399, 41411, 41413, 41443, + 41453, 41467, 41479, 41491, 41507, 41513, 41519, 41521, 41539, + 41543, 41549, 41579, 41593, 41597, 41603, 41609, 41611, 41617, + 41621, 41627, 41641, 41647, 41651, 41659, 41669, 41681, 41687, + 41719, 41729, 41737, 41759, 41761, 41771, 41777, 41801, 41809, + 41813, 41843, 41849, 41851, 41863, 41879, 41887, 41893, 41897, + 41903, 41911, 41927, 41941, 41947, 41953, 41957, 41959, 41969, + 41981, 41983, 41999, 42013, 42017, 42019, 42023, 42043, 42061, + 42071, 42073, 42083, 42089, 42101, 42131, 42139, 42157, 42169, + 42179, 42181, 42187, 42193, 42197, 42209, 42221, 42223, 42227, + 42239, 42257, 42281, 42283, 42293, 42299, 42307, 42323, 42331, + 42337, 42349, 42359, 42373, 42379, 42391, 42397, 42403, 42407, + 42409, 42433, 42437, 42443, 42451, 42457, 42461, 42463, 42467, + 42473, 42487, 42491, 42499, 42509, 42533, 42557, 42569, 42571, + 42577, 42589, 42611, 42641, 42643, 42649, 42667, 42677, 42683, + 42689, 42697, 42701, 42703, 42709, 42719, 42727, 42737, 42743, + 42751, 42767, 42773, 42787, 42793, 42797, 42821, 42829, 42839, + 42841, 42853, 42859, 42863, 42899, 42901, 42923, 42929, 42937, + 42943, 42953, 42961, 42967, 42979, 42989, 43003, 43013, 43019, + 43037, 43049, 43051, 43063, 43067, 43093, 43103, 43117, 43133, + 43151, 43159, 43177, 43189, 43201, 43207, 43223, 43237, 43261, + 43271, 43283, 43291, 43313, 43319, 43321, 43331, 43391, 43397, + 43399, 43403, 43411, 43427, 43441, 43451, 43457, 43481, 43487, + 43499, 43517, 43541, 43543, 43573, 43577, 43579, 43591, 43597, + 43607, 43609, 43613, 43627, 43633, 43649, 43651, 43661, 43669, + 43691, 43711, 43717, 43721, 43753, 43759, 43777, 43781, 43783, + 43787, 43789, 43793, 43801, 43853, 43867, 43889, 43891, 43913, + 43933, 43943, 43951, 43961, 43963, 43969, 43973, 43987, 43991, + 43997, 44017, 44021, 44027, 44029, 44041, 44053, 44059, 44071, + 44087, 44089, 44101, 44111, 44119, 44123, 44129, 44131, 44159, + 44171, 44179, 44189, 44201, 44203, 44207, 44221, 44249, 44257, + 44263, 44267, 44269, 44273, 44279, 44281, 44293, 44351, 44357, + 44371, 44381, 44383, 44389, 44417, 44449, 44453, 44483, 44491, + 44497, 44501, 44507, 44519, 44531, 44533, 44537, 44543, 44549, + 44563, 44579, 44587, 44617, 44621, 44623, 44633, 44641, 44647, + 44651, 44657, 44683, 44687, 44699, 44701, 44711, 44729, 44741, + 44753, 44771, 44773, 44777, 44789, 44797, 44809, 44819, 44839, + 44843, 44851, 44867, 44879, 44887, 44893, 44909, 44917, 44927, + 44939, 44953, 44959, 44963, 44971, 44983, 44987, 45007, 45013, + 45053, 45061, 45077, 45083, 45119, 45121, 45127, 45131, 45137, + 45139, 45161, 45179, 45181, 45191, 45197, 45233, 45247, 45259, + 45263, 45281, 45289, 45293, 45307, 45317, 45319, 45329, 45337, + 45341, 45343, 45361, 45377, 45389, 45403, 45413, 45427, 45433, + 45439, 45481, 45491, 45497, 45503, 45523, 45533, 45541, 45553, + 45557, 45569, 45587, 45589, 45599, 45613, 45631, 45641, 45659, + 45667, 45673, 45677, 45691, 45697, 45707, 45737, 45751, 45757, + 45763, 45767, 45779, 45817, 45821, 45823, 45827, 45833, 45841, + 45853, 45863, 45869, 45887, 45893, 45943, 45949, 45953, 45959, + 45971, 45979, 45989, 46021, 46027, 46049, 46051, 46061, 46073, + 46091, 46093, 46099, 46103, 46133, 46141, 46147, 46153, 46171, + 46181, 46183, 46187, 46199, 46219, 46229, 46237, 46261, 46271, + 46273, 46279, 46301, 46307, 46309, 46327, 46337, 46349, 46351, + 46381, 46399, 46411, 46439, 46441, 46447, 46451, 46457, 46471, + 46477, 46489, 46499, 46507, 46511, 46523, 46549, 46559, 46567, + 46573, 46589, 46591, 46601, 46619, 46633, 46639, 46643, 46649, + 46663, 46679, 46681, 46687, 46691, 46703, 46723, 46727, 46747, + 46751, 46757, 46769, 46771, 46807, 46811, 46817, 46819, 46829, + 46831, 46853, 46861, 46867, 46877, 46889, 46901, 46919, 46933, + 46957, 46993, 46997, 47017, 47041, 47051, 47057, 47059, 47087, + 47093, 47111, 47119, 47123, 47129, 47137, 47143, 47147, 47149, + 47161, 47189, 47207, 47221, 47237, 47251, 47269, 47279, 47287, + 47293, 47297, 47303, 47309, 47317, 47339, 47351, 47353, 47363, + 47381, 47387, 47389, 47407, 47417, 47419, 47431, 47441, 47459, + 47491, 47497, 47501, 47507, 47513, 47521, 47527, 47533, 47543, + 47563, 47569, 47581, 47591, 47599, 47609, 47623, 47629, 47639, + 47653, 47657, 47659, 47681, 47699, 47701, 47711, 47713, 47717, + 47737, 47741, 47743, 47777, 47779, 47791, 47797, 47807, 47809, + 47819, 47837, 47843, 47857, 47869, 47881, 47903, 47911, 47917, + 47933, 47939, 47947, 47951, 47963, 47969, 47977, 47981, 48017, + 48023, 48029, 48049, 48073, 48079, 48091, 48109, 48119, 48121, + 48131, 48157, 48163, 48179, 48187, 48193, 48197, 48221, 48239, + 48247, 48259, 48271, 48281, 48299, 48311, 48313, 48337, 48341, + 48353, 48371, 48383, 48397, 48407, 48409, 48413, 48437, 48449, + 48463, 48473, 48479, 48481, 48487, 48491, 48497, 48523, 48527, + 48533, 48539, 48541, 48563, 48571, 48589, 48593, 48611, 48619, + 48623, 48647, 48649, 48661, 48673, 48677, 48679, 48731, 48733, + 48751, 48757, 48761, 48767, 48779, 48781, 48787, 48799, 48809, + 48817, 48821, 48823, 48847, 48857, 48859, 48869, 48871, 48883, + 48889, 48907, 48947, 48953, 48973, 48989, 48991, 49003, 49009, + 49019, 49031, 49033, 49037, 49043, 49057, 49069, 49081, 49103, + 49109, 49117, 49121, 49123, 49139, 49157, 49169, 49171, 49177, + 49193, 49199, 49201, 49207, 49211, 49223, 49253, 49261, 49277, + 49279, 49297, 49307, 49331, 49333, 49339, 49363, 49367, 49369, + 49391, 49393, 49409, 49411, 49417, 49429, 49433, 49451, 49459, + 49463, 49477, 49481, 49499, 49523, 49529, 49531, 49537, 49547, + 49549, 49559, 49597, 49603, 49613, 49627, 49633, 49639, 49663, + 49667, 49669, 49681, 49697, 49711, 49727, 49739, 49741, 49747, + 49757, 49783, 49787, 49789, 49801, 49807, 49811, 49823, 49831, + 49843, 49853, 49871, 49877, 49891, 49919, 49921, 49927, 49937, + 49939, 49943, 49957, 49991, 49993, 49999, 50021, 50023, 50033, + 50047, 50051, 50053, 50069, 50077, 50087, 50093, 50101, 50111, + 50119, 50123, 50129, 50131, 50147, 50153, 50159, 50177, 50207, + 50221, 50227, 50231, 50261, 50263, 50273, 50287, 50291, 50311, + 50321, 50329, 50333, 50341, 50359, 50363, 50377, 50383, 50387, + 50411, 50417, 50423, 50441, 50459, 50461, 50497, 50503, 50513, + 50527, 50539, 50543, 50549, 50551, 50581, 50587, 50591, 50593, + 50599, 50627, 50647, 50651, 50671, 50683, 50707, 50723, 50741, + 50753, 50767, 50773, 50777, 50789, 50821, 50833, 50839, 50849, + 50857, 50867, 50873, 50891, 50893, 50909, 50923, 50929, 50951, + 50957, 50969, 50971, 50989, 50993, 51001, 51031, 51043, 51047, + 51059, 51061, 51071, 51109, 51131, 51133, 51137, 51151, 51157, + 51169, 51193, 51197, 51199, 51203, 51217, 51229, 51239, 51241, + 51257, 51263, 51283, 51287, 51307, 51329, 51341, 51343, 51347, + 51349, 51361, 51383, 51407, 51413, 51419, 51421, 51427, 51431, + 51437, 51439, 51449, 51461, 51473, 51479, 51481, 51487, 51503, + 51511, 51517, 51521, 51539, 51551, 51563, 51577, 51581, 51593, + 51599, 51607, 51613, 51631, 51637, 51647, 51659, 51673, 51679, + 51683, 51691, 51713, 51719, 51721, 51749, 51767, 51769, 51787, + 51797, 51803, 51817, 51827, 51829, 51839, 51853, 51859, 51869, + 51871, 51893, 51899, 51907, 51913, 51929, 51941, 51949, 51971, + 51973, 51977, 51991, 52009, 52021, 52027, 52051, 52057, 52067, + 52069, 52081, 52103, 52121, 52127, 52147, 52153, 52163, 52177, + 52181, 52183, 52189, 52201, 52223, 52237, 52249, 52253, 52259, + 52267, 52289, 52291, 52301, 52313, 52321, 52361, 52363, 52369, + 52379, 52387, 52391, 52433, 52453, 52457, 52489, 52501, 52511, + 52517, 52529, 52541, 52543, 52553, 52561, 52567, 52571, 52579, + 52583, 52609, 52627, 52631, 52639, 52667, 52673, 52691, 52697, + 52709, 52711, 52721, 52727, 52733, 52747, 52757, 52769, 52783, + 52807, 52813, 52817, 52837, 52859, 52861, 52879, 52883, 52889, + 52901, 52903, 52919, 52937, 52951, 52957, 52963, 52967, 52973, + 52981, 52999, 53003, 53017, 53047, 53051, 53069, 53077, 53087, + 53089, 53093, 53101, 53113, 53117, 53129, 53147, 53149, 53161, + 53171, 53173, 53189, 53197, 53201, 53231, 53233, 53239, 53267, + 53269, 53279, 53281, 53299, 53309, 53323, 53327, 53353, 53359, + 53377, 53381, 53401, 53407, 53411, 53419, 53437, 53441, 53453, + 53479, 53503, 53507, 53527, 53549, 53551, 53569, 53591, 53593, + 53597, 53609, 53611, 53617, 53623, 53629, 53633, 53639, 53653, + 53657, 53681, 53693, 53699, 53717, 53719, 53731, 53759, 53773, + 53777, 53783, 53791, 53813, 53819, 53831, 53849, 53857, 53861, + 53881, 53887, 53891, 53897, 53899, 53917, 53923, 53927, 53939, + 53951, 53959, 53987, 53993, 54001, 54011, 54013, 54037, 54049, + 54059, 54083, 54091, 54101, 54121, 54133, 54139, 54151, 54163, + 54167, 54181, 54193, 54217, 54251, 54269, 54277, 54287, 54293, + 54311, 54319, 54323, 54331, 54347, 54361, 54367, 54371, 54377, + 54401, 54403, 54409, 54413, 54419, 54421, 54437, 54443, 54449, + 54469, 54493, 54497, 54499, 54503, 54517, 54521, 54539, 54541, + 54547, 54559, 54563, 54577, 54581, 54583, 54601, 54617, 54623, + 54629, 54631, 54647, 54667, 54673, 54679, 54709, 54713, 54721, + 54727, 54751, 54767, 54773, 54779, 54787, 54799, 54829, 54833, + 54851, 54869, 54877, 54881, 54907, 54917, 54919, 54941, 54949, + 54959, 54973, 54979, 54983, 55001, 55009, 55021, 55049, 55051, + 55057, 55061, 55073, 55079, 55103, 55109, 55117, 55127, 55147, + 55163, 55171, 55201, 55207, 55213, 55217, 55219, 55229, 55243, + 55249, 55259, 55291, 55313, 55331, 55333, 55337, 55339, 55343, + 55351, 55373, 55381, 55399, 55411, 55439, 55441, 55457, 55469, + 55487, 55501, 55511, 55529, 55541, 55547, 55579, 55589, 55603, + 55609, 55619, 55621, 55631, 55633, 55639, 55661, 55663, 55667, + 55673, 55681, 55691, 55697, 55711, 55717, 55721, 55733, 55763, + 55787, 55793, 55799, 55807, 55813, 55817, 55819, 55823, 55829, + 55837, 55843, 55849, 55871, 55889, 55897, 55901, 55903, 55921, + 55927, 55931, 55933, 55949, 55967, 55987, 55997, 56003, 56009, + 56039, 56041, 56053, 56081, 56087, 56093, 56099, 56101, 56113, + 56123, 56131, 56149, 56167, 56171, 56179, 56197, 56207, 56209, + 56237, 56239, 56249, 56263, 56267, 56269, 56299, 56311, 56333, + 56359, 56369, 56377, 56383, 56393, 56401, 56417, 56431, 56437, + 56443, 56453, 56467, 56473, 56477, 56479, 56489, 56501, 56503, + 56509, 56519, 56527, 56531, 56533, 56543, 56569, 56591, 56597, + 56599, 56611, 56629, 56633, 56659, 56663, 56671, 56681, 56687, + 56701, 56711, 56713, 56731, 56737, 56747, 56767, 56773, 56779, + 56783, 56807, 56809, 56813, 56821, 56827, 56843, 56857, 56873, + 56891, 56893, 56897, 56909, 56911, 56921, 56923, 56929, 56941, + 56951, 56957, 56963, 56983, 56989, 56993, 56999, 57037, 57041, + 57047, 57059, 57073, 57077, 57089, 57097, 57107, 57119, 57131, + 57139, 57143, 57149, 57163, 57173, 57179, 57191, 57193, 57203, + 57221, 57223, 57241, 57251, 57259, 57269, 57271, 57283, 57287, + 57301, 57329, 57331, 57347, 57349, 57367, 57373, 57383, 57389, + 57397, 57413, 57427, 57457, 57467, 57487, 57493, 57503, 57527, + 57529, 57557, 57559, 57571, 57587, 57593, 57601, 57637, 57641, + 57649, 57653, 57667, 57679, 57689, 57697, 57709, 57713, 57719, + 57727, 57731, 57737, 57751, 57773, 57781, 57787, 57791, 57793, + 57803, 57809, 57829, 57839, 57847, 57853, 57859, 57881, 57899, + 57901, 57917, 57923, 57943, 57947, 57973, 57977, 57991, 58013, + 58027, 58031, 58043, 58049, 58057, 58061, 58067, 58073, 58099, + 58109, 58111, 58129, 58147, 58151, 58153, 58169, 58171, 58189, + 58193, 58199, 58207, 58211, 58217, 58229, 58231, 58237, 58243, + 58271, 58309, 58313, 58321, 58337, 58363, 58367, 58369, 58379, + 58391, 58393, 58403, 58411, 58417, 58427, 58439, 58441, 58451, + 58453, 58477, 58481, 58511, 58537, 58543, 58549, 58567, 58573, + 58579, 58601, 58603, 58613, 58631, 58657, 58661, 58679, 58687, + 58693, 58699, 58711, 58727, 58733, 58741, 58757, 58763, 58771, + 58787, 58789, 58831, 58889, 58897, 58901, 58907, 58909, 58913, + 58921, 58937, 58943, 58963, 58967, 58979, 58991, 58997, 59009, + 59011, 59021, 59023, 59029, 59051, 59053, 59063, 59069, 59077, + 59083, 59093, 59107, 59113, 59119, 59123, 59141, 59149, 59159, + 59167, 59183, 59197, 59207, 59209, 59219, 59221, 59233, 59239, + 59243, 59263, 59273, 59281, 59333, 59341, 59351, 59357, 59359, + 59369, 59377, 59387, 59393, 59399, 59407, 59417, 59419, 59441, + 59443, 59447, 59453, 59467, 59471, 59473, 59497, 59509, 59513, + 59539, 59557, 59561, 59567, 59581, 59611, 59617, 59621, 59627, + 59629, 59651, 59659, 59663, 59669, 59671, 59693, 59699, 59707, + 59723, 59729, 59743, 59747, 59753, 59771, 59779, 59791, 59797, + 59809, 59833, 59863, 59879, 59887, 59921, 59929, 59951, 59957, + 59971, 59981, 59999, 60013, 60017, 60029, 60037, 60041, 60077, + 60083, 60089, 60091, 60101, 60103, 60107, 60127, 60133, 60139, + 60149, 60161, 60167, 60169, 60209, 60217, 60223, 60251, 60257, + 60259, 60271, 60289, 60293, 60317, 60331, 60337, 60343, 60353, + 60373, 60383, 60397, 60413, 60427, 60443, 60449, 60457, 60493, + 60497, 60509, 60521, 60527, 60539, 60589, 60601, 60607, 60611, + 60617, 60623, 60631, 60637, 60647, 60649, 60659, 60661, 60679, + 60689, 60703, 60719, 60727, 60733, 60737, 60757, 60761, 60763, + 60773, 60779, 60793, 60811, 60821, 60859, 60869, 60887, 60889, + 60899, 60901, 60913, 60917, 60919, 60923, 60937, 60943, 60953, + 60961, 61001, 61007, 61027, 61031, 61043, 61051, 61057, 61091, + 61099, 61121, 61129, 61141, 61151, 61153, 61169, 61211, 61223, + 61231, 61253, 61261, 61283, 61291, 61297, 61331, 61333, 61339, + 61343, 61357, 61363, 61379, 61381, 61403, 61409, 61417, 61441, + 61463, 61469, 61471, 61483, 61487, 61493, 61507, 61511, 61519, + 61543, 61547, 61553, 61559, 61561, 61583, 61603, 61609, 61613, + 61627, 61631, 61637, 61643, 61651, 61657, 61667, 61673, 61681, + 61687, 61703, 61717, 61723, 61729, 61751, 61757, 61781, 61813, + 61819, 61837, 61843, 61861, 61871, 61879, 61909, 61927, 61933, + 61949, 61961, 61967, 61979, 61981, 61987, 61991, 62003, 62011, + 62017, 62039, 62047, 62053, 62057, 62071, 62081, 62099, 62119, + 62129, 62131, 62137, 62141, 62143, 62171, 62189, 62191, 62201, + 62207, 62213, 62219, 62233, 62273, 62297, 62299, 62303, 62311, + 62323, 62327, 62347, 62351, 62383, 62401, 62417, 62423, 62459, + 62467, 62473, 62477, 62483, 62497, 62501, 62507, 62533, 62539, + 62549, 62563, 62581, 62591, 62597, 62603, 62617, 62627, 62633, + 62639, 62653, 62659, 62683, 62687, 62701, 62723, 62731, 62743, + 62753, 62761, 62773, 62791, 62801, 62819, 62827, 62851, 62861, + 62869, 62873, 62897, 62903, 62921, 62927, 62929, 62939, 62969, + 62971, 62981, 62983, 62987, 62989, 63029, 63031, 63059, 63067, + 63073, 63079, 63097, 63103, 63113, 63127, 63131, 63149, 63179, + 63197, 63199, 63211, 63241, 63247, 63277, 63281, 63299, 63311, + 63313, 63317, 63331, 63337, 63347, 63353, 63361, 63367, 63377, + 63389, 63391, 63397, 63409, 63419, 63421, 63439, 63443, 63463, + 63467, 63473, 63487, 63493, 63499, 63521, 63527, 63533, 63541, + 63559, 63577, 63587, 63589, 63599, 63601, 63607, 63611, 63617, + 63629, 63647, 63649, 63659, 63667, 63671, 63689, 63691, 63697, + 63703, 63709, 63719, 63727, 63737, 63743, 63761, 63773, 63781, + 63793, 63799, 63803, 63809, 63823, 63839, 63841, 63853, 63857, + 63863, 63901, 63907, 63913, 63929, 63949, 63977, 63997, 64007, + 64013, 64019, 64033, 64037, 64063, 64067, 64081, 64091, 64109, + 64123, 64151, 64153, 64157, 64171, 64187, 64189, 64217, 64223, + 64231, 64237, 64271, 64279, 64283, 64301, 64303, 64319, 64327, + 64333, 64373, 64381, 64399, 64403, 64433, 64439, 64451, 64453, + 64483, 64489, 64499, 64513, 64553, 64567, 64577, 64579, 64591, + 64601, 64609, 64613, 64621, 64627, 64633, 64661, 64663, 64667, + 64679, 64693, 64709, 64717, 64747, 64763, 64781, 64783, 64793, + 64811, 64817, 64849, 64853, 64871, 64877, 64879, 64891, 64901, + 64919, 64921, 64927, 64937, 64951, 64969, 64997, 65003, 65011, + 65027, 65029, 65033, 65053, 65063, 65071, 65089, 65099, 65101, + 65111, 65119, 65123, 65129, 65141, 65147, 65167, 65171, 65173, + 65179, 65183, 65203, 65213, 65239, 65257, 65267, 65269, 65287, + 65293, 65309, 65323, 65327, 65353, 65357, 65371, 65381, 65393, + 65407, 65413, 65419, 65423, 65437, 65447, 65449, 65479, 65497, + 65519, 65521, }; #define NPRIMES (sizeof(primes) / sizeof(*primes)) /* @@ -1191,15 +836,23 @@ * * - for use in DSA, we can arrange to select a prime which is one * more than a multiple of a dirty great bignum. In this case * `bits' gives the size of the factor by which we _multiply_ * that bignum, rather than the size of the whole number. + * + * - for the basically cosmetic purposes of generating keys of the + * length actually specified rather than off by one bit, we permit + * the caller to provide an unsigned integer 'firstbits' which will + * match the top few bits of the returned prime. (That is, there + * will exist some n such that (returnvalue >> n) == firstbits.) If + * 'firstbits' is not needed, specifying it to either 0 or 1 is + * an adequate no-op. */ Bignum primegen(int bits, int modulus, int residue, Bignum factor, - int phase, progfn_t pfn, void *pfnparam) + int phase, progfn_t pfn, void *pfnparam, unsigned firstbits) { - int i, k, v, byte, bitsleft, check, checks; + int i, k, v, byte, bitsleft, check, checks, fbsize; unsigned long delta; unsigned long moduli[NPRIMES + 1]; unsigned long residues[NPRIMES + 1]; unsigned long multipliers[NPRIMES + 1]; Bignum p, pm1, q, wqp, wqp2; @@ -1206,10 +859,14 @@ int progress = 0; byte = 0; bitsleft = 0; + fbsize = 0; + while (firstbits >> fbsize) /* work out how to align this */ + fbsize++; + STARTOVER: pfn(pfnparam, PROGFN_PROGRESS, phase, ++progress); /* @@ -1218,13 +875,15 @@ * random number with the top bit set and the bottom bit clear, * multiply it by `factor', and add one. */ p = bn_power_2(bits - 1); for (i = 0; i < bits; i++) { - if (i == 0 || i == bits - 1) + if (i == 0 || i == bits - 1) { v = (i != 0 || !factor) ? 1 : 0; - else { + } else if (i >= bits - fbsize) { + v = (firstbits >> (i - (bits - fbsize))) & 1; + } else { if (bitsleft <= 0) bitsleft = 8, byte = random_byte(); v = byte & 1; byte >>= 1; bitsleft--; @@ -1394,5 +1053,34 @@ */ freebn(q); freebn(pm1); return p; } + +/* + * Invent a pair of values suitable for use as 'firstbits' in the + * above function, such that their product is at least 2. + * + * This is used for generating both RSA and DSA keys which have + * exactly the specified number of bits rather than one fewer - if you + * generate an a-bit and a b-bit number completely at random and + * multiply them together, you could end up with either an (ab-1)-bit + * number or an (ab)-bit number. The former happens log(2)*2-1 of the + * time (about 39%) and, though actually harmless, every time it + * occurs it has a non-zero probability of sparking a user email along + * the lines of 'Hey, I asked PuTTYgen for a 2048-bit key and I only + * got 2047 bits! Bug!' + */ +void invent_firstbits(unsigned *one, unsigned *two) +{ + /* + * Our criterion is that any number in the range [one,one+1) + * multiplied by any number in the range [two,two+1) should have + * the highest bit set. It should be clear that we can trivially + * test this by multiplying the smallest values in each interval, + * i.e. the ones we actually invented. + */ + do { + *one = 0x100 | random_byte(); + *two = 0x100 | random_byte(); + } while (*one * *two < 0x20000); +} Index: sshpubk.c ================================================================== --- sshpubk.c +++ sshpubk.c @@ -65,18 +65,19 @@ if (buf[i] != 0 || buf[i + 1] != 0 || buf[i + 2] != 0 || buf[i + 3] != 0) goto end; /* reserved field nonzero, panic! */ i += 4; /* Now the serious stuff. An ordinary SSH-1 public key. */ - i += makekey(buf + i, len, key, NULL, 1); - if (i < 0) + j = makekey(buf + i, len, key, NULL, 1); + if (j < 0) goto end; /* overran */ + i += j; /* Next, the comment field. */ - j = GET_32BIT(buf + i); + j = toint(GET_32BIT(buf + i)); i += 4; - if (len - i < j) + if (j < 0 || len - i < j) goto end; comment = snewn(j + 1, char); if (comment) { memcpy(comment, buf + i, j); comment[j] = '\0'; @@ -106,11 +107,11 @@ if (ciphertype) { MD5Init(&md5c); MD5Update(&md5c, (unsigned char *)passphrase, strlen(passphrase)); MD5Final(keybuf, &md5c); des3_decrypt_pubkey(keybuf, buf + i, (len - i + 7) & ~7); - memset(keybuf, 0, sizeof(keybuf)); /* burn the evidence */ + smemclr(keybuf, sizeof(keybuf)); /* burn the evidence */ } /* * We are now in the secret part of the key. The first four * bytes should be of the form a, b, a, b. @@ -148,11 +149,11 @@ ret = 0; } else ret = 1; end: - memset(buf, 0, sizeof(buf)); /* burn the evidence */ + smemclr(buf, sizeof(buf)); /* burn the evidence */ return ret; } int loadrsakey(const Filename *filename, struct RSAKey *key, char *passphrase, const char **errorstr) @@ -160,11 +161,11 @@ FILE *fp; char buf[64]; int ret = 0; const char *error = NULL; - fp = f_open(*filename, "rb", FALSE); + fp = f_open(filename, "rb", FALSE); if (!fp) { error = "can't open file"; goto end; } @@ -201,11 +202,11 @@ int rsakey_encrypted(const Filename *filename, char **comment) { FILE *fp; char buf[64]; - fp = f_open(*filename, "rb", FALSE); + fp = f_open(filename, "rb", FALSE); if (!fp) return 0; /* doesn't even exist */ /* * Read the first line of the file and see if it's a v1 private @@ -239,11 +240,11 @@ /* Default return if we fail. */ *blob = NULL; *bloblen = 0; ret = 0; - fp = f_open(*filename, "rb", FALSE); + fp = f_open(filename, "rb", FALSE); if (!fp) { error = "can't open file"; goto end; } @@ -255,12 +256,12 @@ memset(&key, 0, sizeof(key)); if (loadrsakey_main(fp, &key, TRUE, commentptr, NULL, &error)) { *blob = rsa_public_blob(&key, bloblen); freersakey(&key); ret = 1; - fp = NULL; } + fp = NULL; /* loadrsakey_main unconditionally closes fp */ } else { error = "not an SSH-1 RSA file"; } end: @@ -356,17 +357,17 @@ if (passphrase) { MD5Init(&md5c); MD5Update(&md5c, (unsigned char *)passphrase, strlen(passphrase)); MD5Final(keybuf, &md5c); des3_encrypt_pubkey(keybuf, estart, p - estart); - memset(keybuf, 0, sizeof(keybuf)); /* burn the evidence */ + smemclr(keybuf, sizeof(keybuf)); /* burn the evidence */ } /* * Done. Write the result to the file. */ - fp = f_open(*filename, "wb", TRUE); + fp = f_open(filename, "wb", TRUE); if (fp) { int ret = (fwrite(buf, 1, p - buf, fp) == (size_t) (p - buf)); if (fclose(fp)) ret = 0; return ret; @@ -460,11 +461,11 @@ static int read_header(FILE * fp, char *header) { int len = 39; int c; - while (len > 0) { + while (1) { c = fgetc(fp); if (c == '\n' || c == '\r' || c == EOF) return 0; /* failure */ if (c == ':') { c = fgetc(fp); @@ -630,11 +631,11 @@ ret = NULL; /* return NULL for most errors */ encryption = comment = mac = NULL; public_blob = private_blob = NULL; - fp = f_open(*filename, "rb", FALSE); + fp = f_open(filename, "rb", FALSE); if (!fp) { error = "can't open file"; goto error; } @@ -645,10 +646,15 @@ old_fmt = 0; } else if (0 == strcmp(header, "PuTTY-User-Key-File-1")) { /* this is an old key file; warn and then continue */ old_keyfile_warning(); old_fmt = 1; + } else if (0 == strncmp(header, "PuTTY-User-Key-File-", 20)) { + /* this is a key file FROM THE FUTURE; refuse it, but with a + * more specific error message than the generic one below */ + error = "PuTTY key format too new"; + goto error; } else { error = "not a PuTTY SSH-2 private key"; goto error; } error = "file format error"; @@ -672,11 +678,10 @@ cipherblk = 16; } else if (!strcmp(encryption, "none")) { cipher = 0; cipherblk = 1; } else { - sfree(encryption); goto error; } /* Read the Comment header line. */ if (!read_header(fp, header) || 0 != strcmp(header, "Comment")) @@ -792,18 +797,18 @@ SHA_Bytes(&s, passphrase, passlen); SHA_Final(&s, mackey); hmac_sha1_simple(mackey, 20, macdata, maclen, binary); - memset(mackey, 0, sizeof(mackey)); - memset(&s, 0, sizeof(s)); + smemclr(mackey, sizeof(mackey)); + smemclr(&s, sizeof(s)); } else { SHA_Simple(macdata, maclen, binary); } if (free_macdata) { - memset(macdata, 0, maclen); + smemclr(macdata, maclen); sfree(macdata); } for (i = 0; i < 20; i++) sprintf(realmac + 2 * i, "%02x", binary[i]); @@ -879,21 +884,24 @@ const char *error = NULL; char *comment; public_blob = NULL; - fp = f_open(*filename, "rb", FALSE); + fp = f_open(filename, "rb", FALSE); if (!fp) { error = "can't open file"; goto error; } /* Read the first header line which contains the key type. */ if (!read_header(fp, header) || (0 != strcmp(header, "PuTTY-User-Key-File-2") && 0 != strcmp(header, "PuTTY-User-Key-File-1"))) { - error = "not a PuTTY SSH-2 private key"; + if (0 == strncmp(header, "PuTTY-User-Key-File-", 20)) + error = "PuTTY key format too new"; + else + error = "not a PuTTY SSH-2 private key"; goto error; } error = "file format error"; if ((b = read_body(fp)) == NULL) goto error; @@ -960,11 +968,11 @@ int ret; if (commentptr) *commentptr = NULL; - fp = f_open(*filename, "rb", FALSE); + fp = f_open(filename, "rb", FALSE); if (!fp) return 0; if (!read_header(fp, header) || (0 != strcmp(header, "PuTTY-User-Key-File-2") && 0 != strcmp(header, "PuTTY-User-Key-File-1"))) { @@ -998,10 +1006,12 @@ return 1; } if (commentptr) *commentptr = comment; + else + sfree(comment); fclose(fp); if (!strcmp(b, "aes256-cbc")) ret = 1; else @@ -1114,14 +1124,14 @@ SHA_Bytes(&s, header, sizeof(header)-1); if (passphrase) SHA_Bytes(&s, passphrase, strlen(passphrase)); SHA_Final(&s, mackey); hmac_sha1_simple(mackey, 20, macdata, maclen, priv_mac); - memset(macdata, 0, maclen); + smemclr(macdata, maclen); sfree(macdata); - memset(mackey, 0, sizeof(mackey)); - memset(&s, 0, sizeof(s)); + smemclr(mackey, sizeof(mackey)); + smemclr(&s, sizeof(s)); } if (passphrase) { unsigned char key[40]; SHA_State s; @@ -1137,15 +1147,15 @@ SHA_Bytes(&s, passphrase, passlen); SHA_Final(&s, key + 20); aes256_encrypt_pubkey(key, priv_blob_encrypted, priv_encrypted_len); - memset(key, 0, sizeof(key)); - memset(&s, 0, sizeof(s)); + smemclr(key, sizeof(key)); + smemclr(&s, sizeof(s)); } - fp = f_open(*filename, "w", TRUE); + fp = f_open(filename, "w", TRUE); if (!fp) return 0; fprintf(fp, "PuTTY-User-Key-File-2: %s\n", key->alg->name); fprintf(fp, "Encryption: %s\n", cipherstr); fprintf(fp, "Comment: %s\n", key->comment); @@ -1158,11 +1168,11 @@ fprintf(fp, "%02x", priv_mac[i]); fprintf(fp, "\n"); fclose(fp); sfree(pub_blob); - memset(priv_blob, 0, priv_blob_len); + smemclr(priv_blob, priv_blob_len); sfree(priv_blob); sfree(priv_blob_encrypted); return 1; } @@ -1177,11 +1187,11 @@ const char putty2_sig[] = "PuTTY-User-Key-File-"; const char sshcom_sig[] = "---- BEGIN SSH2 ENCRYPTED PRIVAT"; const char openssh_sig[] = "-----BEGIN "; int i; - fp = f_open(*filename, "r", FALSE); + fp = f_open(filename, "r", FALSE); if (!fp) return SSH_KEYTYPE_UNOPENABLE; i = fread(buf, 1, sizeof(buf), fp); fclose(fp); if (i < 0) Index: sshrand.c ================================================================== --- sshrand.c +++ sshrand.c @@ -46,10 +46,14 @@ }; static struct RandPool pool; int random_active = 0; long next_noise_collection; + +#ifdef RANDOM_DIAGNOSTICS +int random_diagnostics = 0; +#endif static void random_stir(void) { word32 block[HASHINPUT / sizeof(word32)]; word32 digest[HASHSIZE / sizeof(word32)]; @@ -62,10 +66,34 @@ if (pool.stir_pending) return; pool.stir_pending = TRUE; noise_get_light(random_add_noise); + +#ifdef RANDOM_DIAGNOSTICS + { + int p, q; + printf("random stir starting\npool:\n"); + for (p = 0; p < POOLSIZE; p += HASHSIZE) { + printf(" "); + for (q = 0; q < HASHSIZE; q += 4) { + printf(" %08x", *(word32 *)(pool.pool + p + q)); + } + printf("\n"); + } + printf("incoming:\n "); + for (q = 0; q < HASHSIZE; q += 4) { + printf(" %08x", *(word32 *)(pool.incoming + q)); + } + printf("\nincomingb:\n "); + for (q = 0; q < HASHINPUT; q += 4) { + printf(" %08x", *(word32 *)(pool.incomingb + q)); + } + printf("\n"); + random_diagnostics++; + } +#endif SHATransform((word32 *) pool.incoming, (word32 *) pool.incomingb); pool.incomingpos = 0; /* @@ -114,10 +142,33 @@ */ for (k = 0; k < sizeof(digest) / sizeof(*digest); k++) ((word32 *) (pool.pool + j))[k] = digest[k]; } + +#ifdef RANDOM_DIAGNOSTICS + if (i == 0) { + int p, q; + printf("random stir midpoint\npool:\n"); + for (p = 0; p < POOLSIZE; p += HASHSIZE) { + printf(" "); + for (q = 0; q < HASHSIZE; q += 4) { + printf(" %08x", *(word32 *)(pool.pool + p + q)); + } + printf("\n"); + } + printf("incoming:\n "); + for (q = 0; q < HASHSIZE; q += 4) { + printf(" %08x", *(word32 *)(pool.incoming + q)); + } + printf("\nincomingb:\n "); + for (q = 0; q < HASHINPUT; q += 4) { + printf(" %08x", *(word32 *)(pool.incomingb + q)); + } + printf("\n"); + } +#endif } /* * Might as well save this value back into `incoming', just so * there'll be some extra bizarreness there. @@ -126,10 +177,34 @@ memcpy(pool.incoming, digest, sizeof(digest)); pool.poolpos = sizeof(pool.incoming); pool.stir_pending = FALSE; + +#ifdef RANDOM_DIAGNOSTICS + { + int p, q; + printf("random stir done\npool:\n"); + for (p = 0; p < POOLSIZE; p += HASHSIZE) { + printf(" "); + for (q = 0; q < HASHSIZE; q += 4) { + printf(" %08x", *(word32 *)(pool.pool + p + q)); + } + printf("\n"); + } + printf("incoming:\n "); + for (q = 0; q < HASHSIZE; q += 4) { + printf(" %08x", *(word32 *)(pool.incoming + q)); + } + printf("\nincomingb:\n "); + for (q = 0; q < HASHINPUT; q += 4) { + printf(" %08x", *(word32 *)(pool.incomingb + q)); + } + printf("\n"); + random_diagnostics--; + } +#endif } void random_add_noise(void *noise, int length) { unsigned char *p = noise; @@ -197,13 +272,13 @@ for (i = 0; i < length; i++) pool.pool[i] ^= *p++; pool.poolpos = i; } -static void random_timer(void *ctx, long now) +static void random_timer(void *ctx, unsigned long now) { - if (random_active > 0 && now - next_noise_collection >= 0) { + if (random_active > 0 && now == next_noise_collection) { noise_regular(); next_noise_collection = schedule_timer(NOISE_REGULAR_INTERVAL, random_timer, &pool); } } @@ -217,25 +292,27 @@ random_stir(); next_noise_collection = schedule_timer(NOISE_REGULAR_INTERVAL, random_timer, &pool); } - random_active++; } void random_unref(void) { + assert(random_active > 0); + if (random_active == 1) { + random_save_seed(); + expire_timer_context(&pool); + } random_active--; - assert(random_active >= 0); - if (random_active) return; - - expire_timer_context(&pool); } int random_byte(void) { + assert(random_active); + if (pool.poolpos >= POOLSIZE) random_stir(); return pool.pool[pool.poolpos++]; } Index: sshrsa.c ================================================================== --- sshrsa.c +++ sshrsa.c @@ -108,11 +108,11 @@ SHA512_Bytes(s, lenbuf, 4); while (len-- > 0) { lenbuf[0] = bignum_byte(b, len); SHA512_Bytes(s, lenbuf, 1); } - memset(lenbuf, 0, sizeof(lenbuf)); + smemclr(lenbuf, sizeof(lenbuf)); } /* * Compute (base ^ exp) % mod, provided mod == p * q, with p,q * distinct primes, and iqmp is the multiplicative inverse of q mod p. @@ -271,13 +271,22 @@ */ if (bignum_cmp(random, Zero) <= 0 || bignum_cmp(random, key->modulus) >= 0) { freebn(random); continue; - } else { - break; } + + /* + * Also, make sure it has an inverse mod modulus. + */ + random_inverse = modinv(random, key->modulus); + if (!random_inverse) { + freebn(random); + continue; + } + + break; } /* * RSA blinding relies on the fact that (xy)^d mod n is equal * to (x^d mod n) * (y^d mod n) mod n. We invent a random pair @@ -292,11 +301,10 @@ * _y^d_, and use the _public_ exponent to compute (y^d)^e = y * from it, which is much faster to do. */ random_encrypted = crt_modpow(random, key->exponent, key->modulus, key->p, key->q, key->iqmp); - random_inverse = modinv(random, key->modulus); input_blinded = modmul(input, random_encrypted, key->modulus); ret_blinded = crt_modpow(input_blinded, key->private_exponent, key->modulus, key->p, key->q, key->iqmp); ret = modmul(ret_blinded, random_inverse, key->modulus); @@ -411,20 +419,22 @@ /* e * d must be congruent to 1, modulo (p-1) and modulo (q-1). */ pm1 = copybn(key->p); decbn(pm1); ed = modmul(key->exponent, key->private_exponent, pm1); + freebn(pm1); cmp = bignum_cmp(ed, One); - sfree(ed); + freebn(ed); if (cmp != 0) return 0; qm1 = copybn(key->q); decbn(qm1); ed = modmul(key->exponent, key->private_exponent, qm1); + freebn(qm1); cmp = bignum_cmp(ed, One); - sfree(ed); + freebn(ed); if (cmp != 0) return 0; /* * Ensure p > q. @@ -439,18 +449,20 @@ key->p = key->q; key->q = tmp; freebn(key->iqmp); key->iqmp = modinv(key->q, key->p); + if (!key->iqmp) + return 0; } /* * Ensure iqmp * q is congruent to 1, modulo p. */ n = modmul(key->iqmp, key->q, key->p); cmp = bignum_cmp(n, One); - sfree(n); + freebn(n); if (cmp != 0) return 0; return 1; } @@ -523,11 +535,13 @@ static void getstring(char **data, int *datalen, char **p, int *length) { *p = NULL; if (*datalen < 4) return; - *length = GET_32BIT(*data); + *length = toint(GET_32BIT(*data)); + if (*length < 0) + return; *datalen -= 4; *data += 4; if (*datalen < *length) return; *p = *data; @@ -545,19 +559,19 @@ return NULL; b = bignum_from_bytes((unsigned char *)p, length); return b; } +static void rsa2_freekey(void *key); /* forward reference */ + static void *rsa2_newkey(char *data, int len) { char *p; int slen; struct RSAKey *rsa; rsa = snew(struct RSAKey); - if (!rsa) - return NULL; getstring(&data, &len, &p, &slen); if (!p || slen != 7 || memcmp(p, "ssh-rsa", 7)) { sfree(rsa); return NULL; @@ -565,10 +579,15 @@ rsa->exponent = getmp(&data, &len); rsa->modulus = getmp(&data, &len); rsa->private_exponent = NULL; rsa->p = rsa->q = rsa->iqmp = NULL; rsa->comment = NULL; + + if (!rsa->exponent || !rsa->modulus) { + rsa2_freekey(rsa); + return NULL; + } return rsa; } static void rsa2_freekey(void *key) @@ -688,12 +707,10 @@ { char **b = (char **) blob; struct RSAKey *rsa; rsa = snew(struct RSAKey); - if (!rsa) - return NULL; rsa->comment = NULL; rsa->modulus = getmp(b, len); rsa->exponent = getmp(b, len); rsa->private_exponent = getmp(b, len); @@ -701,17 +718,16 @@ rsa->p = getmp(b, len); rsa->q = getmp(b, len); if (!rsa->modulus || !rsa->exponent || !rsa->private_exponent || !rsa->iqmp || !rsa->p || !rsa->q) { - sfree(rsa->modulus); - sfree(rsa->exponent); - sfree(rsa->private_exponent); - sfree(rsa->iqmp); - sfree(rsa->p); - sfree(rsa->q); - sfree(rsa); + rsa2_freekey(rsa); + return NULL; + } + + if (!rsa_verify(rsa)) { + rsa2_freekey(rsa); return NULL; } return rsa; } @@ -836,10 +852,12 @@ getstring(&sig, &siglen, &p, &slen); if (!p || slen != 7 || memcmp(p, "ssh-rsa", 7)) { return 0; } in = getmp(&sig, &siglen); + if (!in) + return 0; out = modpow(in, rsa->exponent, rsa->modulus); freebn(in); ret = 1; Index: sshrsag.c ================================================================== --- sshrsag.c +++ sshrsag.c @@ -1,17 +1,20 @@ /* * RSA key generation. */ +#include + #include "ssh.h" #define RSA_EXPONENT 37 /* we like this prime */ int rsa_generate(struct RSAKey *key, int bits, progfn_t pfn, void *pfnparam) { Bignum pm1, qm1, phi_n; + unsigned pfirst, qfirst; /* * Set up the phase limits for the progress report. We do this * by passing minus the phase number. * @@ -57,14 +60,15 @@ * congruent to 1 modulo e. (Strictly speaking, we wanted (p-1) * and e to be coprime, and (q-1) and e to be coprime, but in * general that's slightly more fiddly to arrange. By choosing * a prime e, we can simplify the criterion.) */ + invent_firstbits(&pfirst, &qfirst); key->p = primegen(bits / 2, RSA_EXPONENT, 1, NULL, - 1, pfn, pfnparam); + 1, pfn, pfnparam, pfirst); key->q = primegen(bits - bits / 2, RSA_EXPONENT, 1, NULL, - 2, pfn, pfnparam); + 2, pfn, pfnparam, qfirst); /* * Ensure p > q, by swapping them if not. */ if (bignum_cmp(key->p, key->q) < 0) { @@ -88,16 +92,18 @@ phi_n = bigmul(pm1, qm1); pfn(pfnparam, PROGFN_PROGRESS, 3, 3); freebn(pm1); freebn(qm1); key->private_exponent = modinv(key->exponent, phi_n); + assert(key->private_exponent); pfn(pfnparam, PROGFN_PROGRESS, 3, 4); key->iqmp = modinv(key->q, key->p); + assert(key->iqmp); pfn(pfnparam, PROGFN_PROGRESS, 3, 5); /* * Clean up temporary numbers. */ freebn(phi_n); return 1; } Index: sshsh256.c ================================================================== --- sshsh256.c +++ sshsh256.c @@ -215,10 +215,120 @@ } const struct ssh_hash ssh_sha256 = { sha256_init, sha256_bytes, sha256_final, 32, "SHA-256" }; + +/* ---------------------------------------------------------------------- + * The above is the SHA-256 algorithm itself. Now we implement the + * HMAC wrapper on it. + */ + +static void *sha256_make_context(void) +{ + return snewn(3, SHA256_State); +} + +static void sha256_free_context(void *handle) +{ + sfree(handle); +} + +static void sha256_key_internal(void *handle, unsigned char *key, int len) +{ + SHA256_State *keys = (SHA256_State *)handle; + unsigned char foo[64]; + int i; + + memset(foo, 0x36, 64); + for (i = 0; i < len && i < 64; i++) + foo[i] ^= key[i]; + SHA256_Init(&keys[0]); + SHA256_Bytes(&keys[0], foo, 64); + + memset(foo, 0x5C, 64); + for (i = 0; i < len && i < 64; i++) + foo[i] ^= key[i]; + SHA256_Init(&keys[1]); + SHA256_Bytes(&keys[1], foo, 64); + + smemclr(foo, 64); /* burn the evidence */ +} + +static void sha256_key(void *handle, unsigned char *key) +{ + sha256_key_internal(handle, key, 32); +} + +static void hmacsha256_start(void *handle) +{ + SHA256_State *keys = (SHA256_State *)handle; + + keys[2] = keys[0]; /* structure copy */ +} + +static void hmacsha256_bytes(void *handle, unsigned char const *blk, int len) +{ + SHA256_State *keys = (SHA256_State *)handle; + SHA256_Bytes(&keys[2], (void *)blk, len); +} + +static void hmacsha256_genresult(void *handle, unsigned char *hmac) +{ + SHA256_State *keys = (SHA256_State *)handle; + SHA256_State s; + unsigned char intermediate[32]; + + s = keys[2]; /* structure copy */ + SHA256_Final(&s, intermediate); + s = keys[1]; /* structure copy */ + SHA256_Bytes(&s, intermediate, 32); + SHA256_Final(&s, hmac); +} + +static void sha256_do_hmac(void *handle, unsigned char *blk, int len, + unsigned long seq, unsigned char *hmac) +{ + unsigned char seqbuf[4]; + + PUT_32BIT_MSB_FIRST(seqbuf, seq); + hmacsha256_start(handle); + hmacsha256_bytes(handle, seqbuf, 4); + hmacsha256_bytes(handle, blk, len); + hmacsha256_genresult(handle, hmac); +} + +static void sha256_generate(void *handle, unsigned char *blk, int len, + unsigned long seq) +{ + sha256_do_hmac(handle, blk, len, seq, blk + len); +} + +static int hmacsha256_verresult(void *handle, unsigned char const *hmac) +{ + unsigned char correct[32]; + hmacsha256_genresult(handle, correct); + return !memcmp(correct, hmac, 32); +} + +static int sha256_verify(void *handle, unsigned char *blk, int len, + unsigned long seq) +{ + unsigned char correct[32]; + sha256_do_hmac(handle, blk, len, seq, correct); + return !memcmp(correct, blk + len, 32); +} + +const struct ssh_mac ssh_hmac_sha256 = { + sha256_make_context, sha256_free_context, sha256_key, + sha256_generate, sha256_verify, + hmacsha256_start, hmacsha256_bytes, + hmacsha256_genresult, hmacsha256_verresult, + "hmac-sha2-256", + 32, + "HMAC-SHA-256" +}; #ifdef TEST #include #include Index: sshsha.c ================================================================== --- sshsha.c +++ sshsha.c @@ -25,10 +25,25 @@ void SHATransform(word32 * digest, word32 * block) { word32 w[80]; word32 a, b, c, d, e; int t; + +#ifdef RANDOM_DIAGNOSTICS + { + extern int random_diagnostics; + if (random_diagnostics) { + int i; + printf("SHATransform:"); + for (i = 0; i < 5; i++) + printf(" %08x", digest[i]); + printf(" +"); + for (i = 0; i < 16; i++) + printf(" %08x", block[i]); + } + } +#endif for (t = 0; t < 16; t++) w[t] = block[t]; for (t = 16; t < 80; t++) { @@ -81,10 +96,23 @@ digest[0] += a; digest[1] += b; digest[2] += c; digest[3] += d; digest[4] += e; + +#ifdef RANDOM_DIAGNOSTICS + { + extern int random_diagnostics; + if (random_diagnostics) { + int i; + printf(" ="); + for (i = 0; i < 5; i++) + printf(" %08x", digest[i]); + printf("\n"); + } + } +#endif } /* ---------------------------------------------------------------------- * Outer SHA algorithm: take an arbitrary length byte string, * convert it into 16-word blocks with the prescribed padding at @@ -96,13 +124,13 @@ SHA_Core_Init(s->h); s->blkused = 0; s->lenhi = s->lenlo = 0; } -void SHA_Bytes(SHA_State * s, void *p, int len) +void SHA_Bytes(SHA_State * s, const void *p, int len) { - unsigned char *q = (unsigned char *) p; + const unsigned char *q = (const unsigned char *) p; uint32 wordblock[16]; uint32 lenw = len; int i; /* @@ -177,11 +205,11 @@ output[i * 4 + 2] = (s->h[i] >> 8) & 0xFF; output[i * 4 + 3] = (s->h[i]) & 0xFF; } } -void SHA_Simple(void *p, int len, unsigned char *output) +void SHA_Simple(const void *p, int len, unsigned char *output) { SHA_State s; SHA_Init(&s); SHA_Bytes(&s, p, len); @@ -251,11 +279,11 @@ for (i = 0; i < len && i < 64; i++) foo[i] ^= key[i]; SHA_Init(&keys[1]); SHA_Bytes(&keys[1], foo, 64); - memset(foo, 0, 64); /* burn the evidence */ + smemclr(foo, 64); /* burn the evidence */ } static void sha1_key(void *handle, unsigned char *key) { sha1_key_internal(handle, key, 20); @@ -295,15 +323,11 @@ static void sha1_do_hmac(void *handle, unsigned char *blk, int len, unsigned long seq, unsigned char *hmac) { unsigned char seqbuf[4]; - seqbuf[0] = (unsigned char) ((seq >> 24) & 0xFF); - seqbuf[1] = (unsigned char) ((seq >> 16) & 0xFF); - seqbuf[2] = (unsigned char) ((seq >> 8) & 0xFF); - seqbuf[3] = (unsigned char) ((seq) & 0xFF); - + PUT_32BIT_MSB_FIRST(seqbuf, seq); hmacsha1_start(handle); hmacsha1_bytes(handle, seqbuf, 4); hmacsha1_bytes(handle, blk, len); hmacsha1_genresult(handle, hmac); } ADDED sshshare.c Index: sshshare.c ================================================================== --- /dev/null +++ sshshare.c @@ -0,0 +1,2103 @@ +/* + * Support for SSH connection sharing, i.e. permitting one PuTTY to + * open its own channels over the SSH session being run by another. + */ + +/* + * Discussion and technical documentation + * ====================================== + * + * The basic strategy for PuTTY's implementation of SSH connection + * sharing is to have a single 'upstream' PuTTY process, which manages + * the real SSH connection and all the cryptography, and then zero or + * more 'downstream' PuTTYs, which never talk to the real host but + * only talk to the upstream through local IPC (Unix-domain sockets or + * Windows named pipes). + * + * The downstreams communicate with the upstream using a protocol + * derived from SSH itself, which I'll document in detail below. In + * brief, though: the downstream->upstream protocol uses a trivial + * binary packet protocol (just length/type/data) to encapsulate + * unencrypted SSH messages, and downstreams talk to the upstream more + * or less as if it was an SSH server itself. (So downstreams can + * themselves open multiple SSH channels, for example, by sending + * multiple SSH2_MSG_CHANNEL_OPENs; they can send CHANNEL_REQUESTs of + * their choice within each channel, and they handle their own + * WINDOW_ADJUST messages.) + * + * The upstream would ideally handle these downstreams by just putting + * their messages into the queue for proper SSH-2 encapsulation and + * encryption and sending them straight on to the server. However, + * that's not quite feasible as written, because client-side channel + * IDs could easily conflict (between multiple downstreams, or between + * a downstream and the upstream). To protect against that, the + * upstream rewrites the client-side channel IDs in messages it passes + * on to the server, so that it's performing what you might describe + * as 'channel-number NAT'. Then the upstream remembers which of its + * own channel IDs are channels it's managing itself, and which are + * placeholders associated with a particular downstream, so that when + * replies come in from the server they can be sent on to the relevant + * downstream (after un-NATting the channel number, of course). + * + * Global requests from downstreams are only accepted if the upstream + * knows what to do about them; currently the only such requests are + * the ones having to do with remote-to-local port forwarding (in + * which, again, the upstream remembers that some of the forwardings + * it's asked the server to set up were on behalf of particular + * downstreams, and sends the incoming CHANNEL_OPENs to those + * downstreams when connections come in). + * + * Other fiddly pieces of this mechanism are X forwarding and + * (OpenSSH-style) agent forwarding. Both of these have a fundamental + * problem arising from the protocol design: that the CHANNEL_OPEN + * from the server introducing a forwarded connection does not carry + * any indication of which session channel gave rise to it; so if + * session channels from multiple downstreams enable those forwarding + * methods, it's hard for the upstream to know which downstream to + * send the resulting connections back to. + * + * For X forwarding, we can work around this in a really painful way + * by using the fake X11 authorisation data sent to the server as part + * of the forwarding setup: upstream ensures that every X forwarding + * request carries distinguishable fake auth data, and then when X + * connections come in it waits to see the auth data in the X11 setup + * message before it decides which downstream to pass the connection + * on to. + * + * For agent forwarding, that workaround is unavailable. As a result, + * this system (and, as far as I can think of, any other system too) + * has the fundamental constraint that it can only forward one SSH + * agent - it can't forward two agents to different session channels. + * So downstreams can request agent forwarding if they like, but if + * they do, they'll get whatever SSH agent is known to the upstream + * (if any) forwarded to their sessions. + * + * Downstream-to-upstream protocol + * ------------------------------- + * + * Here I document in detail the protocol spoken between PuTTY + * downstreams and upstreams over local IPC. The IPC mechanism can + * vary between host platforms, but the protocol is the same. + * + * The protocol commences with a version exchange which is exactly + * like the SSH-2 one, in that each side sends a single line of text + * of the form + * + * -- [comments] \r\n + * + * The only difference is that in real SSH-2, is the string + * "SSH", whereas in this protocol the string is + * "SSHCONNECTION@putty.projects.tartarus.org". + * + * (The SSH RFCs allow many protocol-level identifier namespaces to be + * extended by implementors without central standardisation as long as + * they suffix "@" and a domain name they control to their new ids. + * RFC 4253 does not define this particular name to be changeable at + * all, but I like to think this is obviously how it would have done + * so if the working group had foreseen the need :-) + * + * Thereafter, all data exchanged consists of a sequence of binary + * packets concatenated end-to-end, each of which is of the form + * + * uint32 length of packet, N + * byte[N] N bytes of packet data + * + * and, since these are SSH-2 messages, the first data byte is taken + * to be the packet type code. + * + * These messages are interpreted as those of an SSH connection, after + * userauth completes, and without any repeat key exchange. + * Specifically, any message from the SSH Connection Protocol is + * permitted, and also SSH_MSG_IGNORE, SSH_MSG_DEBUG, + * SSH_MSG_DISCONNECT and SSH_MSG_UNIMPLEMENTED from the SSH Transport + * Protocol. + * + * This protocol imposes a few additional requirements, over and above + * those of the standard SSH Connection Protocol: + * + * Message sizes are not permitted to exceed 0x4010 (16400) bytes, + * including their length header. + * + * When the server (i.e. really the PuTTY upstream) sends + * SSH_MSG_CHANNEL_OPEN with channel type "x11", and the client + * (downstream) responds with SSH_MSG_CHANNEL_OPEN_CONFIRMATION, that + * confirmation message MUST include an initial window size of at + * least 256. (Rationale: this is a bit of a fudge which makes it + * easier, by eliminating the possibility of nasty edge cases, for an + * upstream to arrange not to pass the CHANNEL_OPEN on to downstream + * until after it's seen the X11 auth data to decide which downstream + * it needs to go to.) + */ + +#include +#include +#include +#include + +#include "putty.h" +#include "tree234.h" +#include "ssh.h" + +struct ssh_sharing_state { + const struct plug_function_table *fn; + /* the above variable absolutely *must* be the first in this structure */ + + char *sockname; /* the socket name, kept for cleanup */ + Socket listensock; /* the master listening Socket */ + tree234 *connections; /* holds ssh_sharing_connstates */ + unsigned nextid; /* preferred id for next connstate */ + Ssh ssh; /* instance of the ssh backend */ + char *server_verstring; /* server version string after "SSH-" */ +}; + +struct share_globreq; + +struct ssh_sharing_connstate { + const struct plug_function_table *fn; + /* the above variable absolutely *must* be the first in this structure */ + + unsigned id; /* used to identify this downstream in log messages */ + + Socket sock; /* the Socket for this connection */ + struct ssh_sharing_state *parent; + + int crLine; /* coroutine state for share_receive */ + + int sent_verstring, got_verstring, curr_packetlen; + + unsigned char recvbuf[0x4010]; + int recvlen; + + /* + * Assorted state we have to remember about this downstream, so + * that we can clean it up appropriately when the downstream goes + * away. + */ + + /* Channels which don't have a downstream id, i.e. we've passed a + * CHANNEL_OPEN down from the server but not had an + * OPEN_CONFIRMATION or OPEN_FAILURE back. If downstream goes + * away, we respond to all of these with OPEN_FAILURE. */ + tree234 *halfchannels; /* stores 'struct share_halfchannel' */ + + /* Channels which do have a downstream id. We need to index these + * by both server id and upstream id, so we can find a channel + * when handling either an upward or a downward message referring + * to it. */ + tree234 *channels_by_us; /* stores 'struct share_channel' */ + tree234 *channels_by_server; /* stores 'struct share_channel' */ + + /* Another class of channel which doesn't have a downstream id. + * The difference between these and halfchannels is that xchannels + * do have an *upstream* id, because upstream has already accepted + * the channel request from the server. This arises in the case of + * X forwarding, where we have to accept the request and read the + * X authorisation data before we know whether the channel needs + * to be forwarded to a downstream. */ + tree234 *xchannels_by_us; /* stores 'struct share_xchannel' */ + tree234 *xchannels_by_server; /* stores 'struct share_xchannel' */ + + /* Remote port forwarding requests in force. */ + tree234 *forwardings; /* stores 'struct share_forwarding' */ + + /* Global requests we've sent on to the server, pending replies. */ + struct share_globreq *globreq_head, *globreq_tail; +}; + +struct share_halfchannel { + unsigned server_id; +}; + +/* States of a share_channel. */ +enum { + OPEN, + SENT_CLOSE, + RCVD_CLOSE, + /* Downstream has sent CHANNEL_OPEN but server hasn't replied yet. + * If downstream goes away when a channel is in this state, we + * must wait for the server's response before starting to send + * CLOSE. Channels in this state are also not held in + * channels_by_server, because their server_id field is + * meaningless. */ + UNACKNOWLEDGED +}; + +struct share_channel { + unsigned downstream_id, upstream_id, server_id; + int downstream_maxpkt; + int state; + /* + * Some channels (specifically, channels on which downstream has + * sent "x11-req") have the additional function of storing a set + * of downstream X authorisation data and a handle to an upstream + * fake set. + */ + struct X11FakeAuth *x11_auth_upstream; + int x11_auth_proto; + char *x11_auth_data; + int x11_auth_datalen; + int x11_one_shot; +}; + +struct share_forwarding { + char *host; + int port; + int active; /* has the server sent REQUEST_SUCCESS? */ +}; + +struct share_xchannel_message { + struct share_xchannel_message *next; + int type; + unsigned char *data; + int datalen; +}; + +struct share_xchannel { + unsigned upstream_id, server_id; + + /* + * xchannels come in two flavours: live and dead. Live ones are + * waiting for an OPEN_CONFIRMATION or OPEN_FAILURE from + * downstream; dead ones have had an OPEN_FAILURE, so they only + * exist as a means of letting us conveniently respond to further + * channel messages from the server until such time as the server + * sends us CHANNEL_CLOSE. + */ + int live; + + /* + * When we receive OPEN_CONFIRMATION, we will need to send a + * WINDOW_ADJUST to the server to synchronise the windows. For + * this purpose we need to know what window we have so far offered + * the server. We record this as exactly the value in the + * OPEN_CONFIRMATION that upstream sent us, adjusted by the amount + * by which the two X greetings differed in length. + */ + int window; + + /* + * Linked list of SSH messages from the server relating to this + * channel, which we queue up until downstream sends us an + * OPEN_CONFIRMATION and we can belatedly send them all on. + */ + struct share_xchannel_message *msghead, *msgtail; +}; + +enum { + GLOBREQ_TCPIP_FORWARD, + GLOBREQ_CANCEL_TCPIP_FORWARD +}; + +struct share_globreq { + struct share_globreq *next; + int type; + int want_reply; + struct share_forwarding *fwd; +}; + +static int share_connstate_cmp(void *av, void *bv) +{ + const struct ssh_sharing_connstate *a = + (const struct ssh_sharing_connstate *)av; + const struct ssh_sharing_connstate *b = + (const struct ssh_sharing_connstate *)bv; + + if (a->id < b->id) + return -1; + else if (a->id > b->id) + return +1; + else + return 0; +} + +static unsigned share_find_unused_id +(struct ssh_sharing_state *sharestate, unsigned first) +{ + int low_orig, low, mid, high, high_orig; + struct ssh_sharing_connstate *cs; + unsigned ret; + + /* + * Find the lowest unused downstream ID greater or equal to + * 'first'. + * + * Begin by seeing if 'first' itself is available. If it is, we'll + * just return it; if it's already in the tree, we'll find the + * tree index where it appears and use that for the next stage. + */ + { + struct ssh_sharing_connstate dummy; + dummy.id = first; + cs = findrelpos234(sharestate->connections, &dummy, NULL, + REL234_GE, &low_orig); + if (!cs) + return first; + } + + /* + * Now binary-search using the counted B-tree, to find the largest + * ID which is in a contiguous sequence from the beginning of that + * range. + */ + low = low_orig; + high = high_orig = count234(sharestate->connections); + while (high - low > 1) { + mid = (high + low) / 2; + cs = index234(sharestate->connections, mid); + if (cs->id == first + (mid - low_orig)) + low = mid; /* this one is still in the sequence */ + else + high = mid; /* this one is past the end */ + } + + /* + * Now low is the tree index of the largest ID in the initial + * sequence. So the return value is one more than low's id, and we + * know low's id is given by the formula in the binary search loop + * above. + * + * (If an SSH connection went on for _enormously_ long, we might + * reach a point where all ids from 'first' to UINT_MAX were in + * use. In that situation the formula below would wrap round by + * one and return zero, which is conveniently the right way to + * signal 'no id available' from this function.) + */ + ret = first + (low - low_orig) + 1; + { + struct ssh_sharing_connstate dummy; + dummy.id = ret; + assert(NULL == find234(sharestate->connections, &dummy, NULL)); + } + return ret; +} + +static int share_halfchannel_cmp(void *av, void *bv) +{ + const struct share_halfchannel *a = (const struct share_halfchannel *)av; + const struct share_halfchannel *b = (const struct share_halfchannel *)bv; + + if (a->server_id < b->server_id) + return -1; + else if (a->server_id > b->server_id) + return +1; + else + return 0; +} + +static int share_channel_us_cmp(void *av, void *bv) +{ + const struct share_channel *a = (const struct share_channel *)av; + const struct share_channel *b = (const struct share_channel *)bv; + + if (a->upstream_id < b->upstream_id) + return -1; + else if (a->upstream_id > b->upstream_id) + return +1; + else + return 0; +} + +static int share_channel_server_cmp(void *av, void *bv) +{ + const struct share_channel *a = (const struct share_channel *)av; + const struct share_channel *b = (const struct share_channel *)bv; + + if (a->server_id < b->server_id) + return -1; + else if (a->server_id > b->server_id) + return +1; + else + return 0; +} + +static int share_xchannel_us_cmp(void *av, void *bv) +{ + const struct share_xchannel *a = (const struct share_xchannel *)av; + const struct share_xchannel *b = (const struct share_xchannel *)bv; + + if (a->upstream_id < b->upstream_id) + return -1; + else if (a->upstream_id > b->upstream_id) + return +1; + else + return 0; +} + +static int share_xchannel_server_cmp(void *av, void *bv) +{ + const struct share_xchannel *a = (const struct share_xchannel *)av; + const struct share_xchannel *b = (const struct share_xchannel *)bv; + + if (a->server_id < b->server_id) + return -1; + else if (a->server_id > b->server_id) + return +1; + else + return 0; +} + +static int share_forwarding_cmp(void *av, void *bv) +{ + const struct share_forwarding *a = (const struct share_forwarding *)av; + const struct share_forwarding *b = (const struct share_forwarding *)bv; + int i; + + if ((i = strcmp(a->host, b->host)) != 0) + return i; + else if (a->port < b->port) + return -1; + else if (a->port > b->port) + return +1; + else + return 0; +} + +static void share_xchannel_free(struct share_xchannel *xc) +{ + while (xc->msghead) { + struct share_xchannel_message *tmp = xc->msghead; + xc->msghead = tmp->next; + sfree(tmp); + } + sfree(xc); +} + +static void share_connstate_free(struct ssh_sharing_connstate *cs) +{ + struct share_halfchannel *hc; + struct share_xchannel *xc; + struct share_channel *chan; + struct share_forwarding *fwd; + + while ((hc = (struct share_halfchannel *) + delpos234(cs->halfchannels, 0)) != NULL) + sfree(hc); + freetree234(cs->halfchannels); + + /* All channels live in 'channels_by_us' but only some in + * 'channels_by_server', so we use the former to find the list of + * ones to free */ + freetree234(cs->channels_by_server); + while ((chan = (struct share_channel *) + delpos234(cs->channels_by_us, 0)) != NULL) + sfree(chan); + freetree234(cs->channels_by_us); + + /* But every xchannel is in both trees, so it doesn't matter which + * we use to free them. */ + while ((xc = (struct share_xchannel *) + delpos234(cs->xchannels_by_us, 0)) != NULL) + share_xchannel_free(xc); + freetree234(cs->xchannels_by_us); + freetree234(cs->xchannels_by_server); + + while ((fwd = (struct share_forwarding *) + delpos234(cs->forwardings, 0)) != NULL) + sfree(fwd); + freetree234(cs->forwardings); + + while (cs->globreq_head) { + struct share_globreq *globreq = cs->globreq_head; + cs->globreq_head = cs->globreq_head->next; + sfree(globreq); + } + + sfree(cs); +} + +void sharestate_free(void *v) +{ + struct ssh_sharing_state *sharestate = (struct ssh_sharing_state *)v; + struct ssh_sharing_connstate *cs; + + platform_ssh_share_cleanup(sharestate->sockname); + + while ((cs = (struct ssh_sharing_connstate *) + delpos234(sharestate->connections, 0)) != NULL) { + share_connstate_free(cs); + } + freetree234(sharestate->connections); + sfree(sharestate->server_verstring); + sfree(sharestate->sockname); + sfree(sharestate); +} + +static struct share_halfchannel *share_add_halfchannel + (struct ssh_sharing_connstate *cs, unsigned server_id) +{ + struct share_halfchannel *hc = snew(struct share_halfchannel); + hc->server_id = server_id; + if (add234(cs->halfchannels, hc) != hc) { + /* Duplicate?! */ + sfree(hc); + return NULL; + } else { + return hc; + } +} + +static struct share_halfchannel *share_find_halfchannel + (struct ssh_sharing_connstate *cs, unsigned server_id) +{ + struct share_halfchannel dummyhc; + dummyhc.server_id = server_id; + return find234(cs->halfchannels, &dummyhc, NULL); +} + +static void share_remove_halfchannel(struct ssh_sharing_connstate *cs, + struct share_halfchannel *hc) +{ + del234(cs->halfchannels, hc); + sfree(hc); +} + +static struct share_channel *share_add_channel + (struct ssh_sharing_connstate *cs, unsigned downstream_id, + unsigned upstream_id, unsigned server_id, int state, int maxpkt) +{ + struct share_channel *chan = snew(struct share_channel); + chan->downstream_id = downstream_id; + chan->upstream_id = upstream_id; + chan->server_id = server_id; + chan->state = state; + chan->downstream_maxpkt = maxpkt; + chan->x11_auth_upstream = NULL; + chan->x11_auth_data = NULL; + chan->x11_auth_proto = -1; + chan->x11_auth_datalen = 0; + chan->x11_one_shot = 0; + if (add234(cs->channels_by_us, chan) != chan) { + sfree(chan); + return NULL; + } + if (chan->state != UNACKNOWLEDGED) { + if (add234(cs->channels_by_server, chan) != chan) { + del234(cs->channels_by_us, chan); + sfree(chan); + return NULL; + } + } + return chan; +} + +static void share_channel_set_server_id(struct ssh_sharing_connstate *cs, + struct share_channel *chan, + unsigned server_id, int newstate) +{ + chan->server_id = server_id; + chan->state = newstate; + assert(newstate != UNACKNOWLEDGED); + add234(cs->channels_by_server, chan); +} + +static struct share_channel *share_find_channel_by_upstream + (struct ssh_sharing_connstate *cs, unsigned upstream_id) +{ + struct share_channel dummychan; + dummychan.upstream_id = upstream_id; + return find234(cs->channels_by_us, &dummychan, NULL); +} + +static struct share_channel *share_find_channel_by_server + (struct ssh_sharing_connstate *cs, unsigned server_id) +{ + struct share_channel dummychan; + dummychan.server_id = server_id; + return find234(cs->channels_by_server, &dummychan, NULL); +} + +static void share_remove_channel(struct ssh_sharing_connstate *cs, + struct share_channel *chan) +{ + del234(cs->channels_by_us, chan); + del234(cs->channels_by_server, chan); + if (chan->x11_auth_upstream) + ssh_sharing_remove_x11_display(cs->parent->ssh, + chan->x11_auth_upstream); + sfree(chan->x11_auth_data); + sfree(chan); +} + +static struct share_xchannel *share_add_xchannel + (struct ssh_sharing_connstate *cs, + unsigned upstream_id, unsigned server_id) +{ + struct share_xchannel *xc = snew(struct share_xchannel); + xc->upstream_id = upstream_id; + xc->server_id = server_id; + xc->live = TRUE; + xc->msghead = xc->msgtail = NULL; + if (add234(cs->xchannels_by_us, xc) != xc) { + sfree(xc); + return NULL; + } + if (add234(cs->xchannels_by_server, xc) != xc) { + del234(cs->xchannels_by_us, xc); + sfree(xc); + return NULL; + } + return xc; +} + +static struct share_xchannel *share_find_xchannel_by_upstream + (struct ssh_sharing_connstate *cs, unsigned upstream_id) +{ + struct share_xchannel dummyxc; + dummyxc.upstream_id = upstream_id; + return find234(cs->xchannels_by_us, &dummyxc, NULL); +} + +static struct share_xchannel *share_find_xchannel_by_server + (struct ssh_sharing_connstate *cs, unsigned server_id) +{ + struct share_xchannel dummyxc; + dummyxc.server_id = server_id; + return find234(cs->xchannels_by_server, &dummyxc, NULL); +} + +static void share_remove_xchannel(struct ssh_sharing_connstate *cs, + struct share_xchannel *xc) +{ + del234(cs->xchannels_by_us, xc); + del234(cs->xchannels_by_server, xc); + share_xchannel_free(xc); +} + +static struct share_forwarding *share_add_forwarding + (struct ssh_sharing_connstate *cs, + const char *host, int port) +{ + struct share_forwarding *fwd = snew(struct share_forwarding); + fwd->host = dupstr(host); + fwd->port = port; + fwd->active = FALSE; + if (add234(cs->forwardings, fwd) != fwd) { + /* Duplicate?! */ + sfree(fwd); + return NULL; + } + return fwd; +} + +static struct share_forwarding *share_find_forwarding + (struct ssh_sharing_connstate *cs, const char *host, int port) +{ + struct share_forwarding dummyfwd, *ret; + dummyfwd.host = dupstr(host); + dummyfwd.port = port; + ret = find234(cs->forwardings, &dummyfwd, NULL); + sfree(dummyfwd.host); + return ret; +} + +static void share_remove_forwarding(struct ssh_sharing_connstate *cs, + struct share_forwarding *fwd) +{ + del234(cs->forwardings, fwd); + sfree(fwd); +} + +static void send_packet_to_downstream(struct ssh_sharing_connstate *cs, + int type, const void *pkt, int pktlen, + struct share_channel *chan) +{ + if (!cs->sock) /* throw away all packets destined for a dead downstream */ + return; + + if (type == SSH2_MSG_CHANNEL_DATA) { + /* + * Special case which we take care of at a low level, so as to + * be sure to apply it in all cases. On rare occasions we + * might find that we have a channel for which the + * downstream's maximum packet size exceeds the max packet + * size we presented to the server on its behalf. (This can + * occur in X11 forwarding, where we have to send _our_ + * CHANNEL_OPEN_CONFIRMATION before we discover which if any + * downstream the channel is destined for, so if that + * downstream turns out to present a smaller max packet size + * then we're in this situation.) + * + * If that happens, we just chop up the packet into pieces and + * send them as separate CHANNEL_DATA packets. + */ + const char *upkt = (const char *)pkt; + char header[13]; /* 4 length + 1 type + 4 channel id + 4 string len */ + + int len = toint(GET_32BIT(upkt + 4)); + upkt += 8; /* skip channel id + length field */ + + if (len < 0 || len > pktlen - 8) + len = pktlen - 8; + + do { + int this_len = (len > chan->downstream_maxpkt ? + chan->downstream_maxpkt : len); + PUT_32BIT(header, this_len + 9); + header[4] = type; + PUT_32BIT(header + 5, chan->downstream_id); + PUT_32BIT(header + 9, this_len); + sk_write(cs->sock, header, 13); + sk_write(cs->sock, upkt, this_len); + len -= this_len; + upkt += this_len; + } while (len > 0); + } else { + /* + * Just do the obvious thing. + */ + char header[9]; + + PUT_32BIT(header, pktlen + 1); + header[4] = type; + sk_write(cs->sock, header, 5); + sk_write(cs->sock, pkt, pktlen); + } +} + +static void share_try_cleanup(struct ssh_sharing_connstate *cs) +{ + int i; + struct share_halfchannel *hc; + struct share_channel *chan; + struct share_forwarding *fwd; + + /* + * Any half-open channels, i.e. those for which we'd received + * CHANNEL_OPEN from the server but not passed back a response + * from downstream, should be responded to with OPEN_FAILURE. + */ + while ((hc = (struct share_halfchannel *) + index234(cs->halfchannels, 0)) != NULL) { + static const char reason[] = "PuTTY downstream no longer available"; + static const char lang[] = "en"; + unsigned char packet[256]; + int pos = 0; + + PUT_32BIT(packet + pos, hc->server_id); pos += 4; + PUT_32BIT(packet + pos, SSH2_OPEN_CONNECT_FAILED); pos += 4; + PUT_32BIT(packet + pos, strlen(reason)); pos += 4; + memcpy(packet + pos, reason, strlen(reason)); pos += strlen(reason); + PUT_32BIT(packet + pos, strlen(lang)); pos += 4; + memcpy(packet + pos, lang, strlen(lang)); pos += strlen(lang); + ssh_send_packet_from_downstream(cs->parent->ssh, cs->id, + SSH2_MSG_CHANNEL_OPEN_FAILURE, + packet, pos, "cleanup after" + " downstream went away"); + + share_remove_halfchannel(cs, hc); + } + + /* + * Any actually open channels should have a CHANNEL_CLOSE sent for + * them, unless we've already done so. We won't be able to + * actually clean them up until CHANNEL_CLOSE comes back from the + * server, though (unless the server happens to have sent a CLOSE + * already). + * + * Another annoying exception is UNACKNOWLEDGED channels, i.e. + * we've _sent_ a CHANNEL_OPEN to the server but not received an + * OPEN_CONFIRMATION or OPEN_FAILURE. We must wait for a reply + * before closing the channel, because until we see that reply we + * won't have the server's channel id to put in the close message. + */ + for (i = 0; (chan = (struct share_channel *) + index234(cs->channels_by_us, i)) != NULL; i++) { + unsigned char packet[256]; + int pos = 0; + + if (chan->state != SENT_CLOSE && chan->state != UNACKNOWLEDGED) { + PUT_32BIT(packet + pos, chan->server_id); pos += 4; + ssh_send_packet_from_downstream(cs->parent->ssh, cs->id, + SSH2_MSG_CHANNEL_CLOSE, + packet, pos, "cleanup after" + " downstream went away"); + if (chan->state != RCVD_CLOSE) { + chan->state = SENT_CLOSE; + } else { + /* In this case, we _can_ clear up the channel now. */ + ssh_delete_sharing_channel(cs->parent->ssh, chan->upstream_id); + share_remove_channel(cs, chan); + i--; /* don't accidentally skip one as a result */ + } + } + } + + /* + * Any remote port forwardings we're managing on behalf of this + * downstream should be cancelled. Again, we must defer those for + * which we haven't yet seen REQUEST_SUCCESS/FAILURE. + * + * We take a fire-and-forget approach during cleanup, not + * bothering to set want_reply. + */ + for (i = 0; (fwd = (struct share_forwarding *) + index234(cs->forwardings, i)) != NULL; i++) { + if (fwd->active) { + static const char request[] = "cancel-tcpip-forward"; + char *packet = snewn(256 + strlen(fwd->host), char); + int pos = 0; + + PUT_32BIT(packet + pos, strlen(request)); pos += 4; + memcpy(packet + pos, request, strlen(request)); + pos += strlen(request); + + packet[pos++] = 0; /* !want_reply */ + + PUT_32BIT(packet + pos, strlen(fwd->host)); pos += 4; + memcpy(packet + pos, fwd->host, strlen(fwd->host)); + pos += strlen(fwd->host); + + PUT_32BIT(packet + pos, fwd->port); pos += 4; + + ssh_send_packet_from_downstream(cs->parent->ssh, cs->id, + SSH2_MSG_GLOBAL_REQUEST, + packet, pos, "cleanup after" + " downstream went away"); + + share_remove_forwarding(cs, fwd); + i--; /* don't accidentally skip one as a result */ + } + } + + if (count234(cs->halfchannels) == 0 && + count234(cs->channels_by_us) == 0 && + count234(cs->forwardings) == 0) { + /* + * Now we're _really_ done, so we can get rid of cs completely. + */ + del234(cs->parent->connections, cs); + ssh_sharing_downstream_disconnected(cs->parent->ssh, cs->id); + share_connstate_free(cs); + } +} + +static void share_begin_cleanup(struct ssh_sharing_connstate *cs) +{ + + sk_close(cs->sock); + cs->sock = NULL; + + share_try_cleanup(cs); +} + +static void share_disconnect(struct ssh_sharing_connstate *cs, + const char *message) +{ + static const char lang[] = "en"; + int msglen = strlen(message); + char *packet = snewn(msglen + 256, char); + int pos = 0; + + PUT_32BIT(packet + pos, SSH2_DISCONNECT_PROTOCOL_ERROR); pos += 4; + + PUT_32BIT(packet + pos, msglen); pos += 4; + memcpy(packet + pos, message, msglen); + pos += msglen; + + PUT_32BIT(packet + pos, strlen(lang)); pos += 4; + memcpy(packet + pos, lang, strlen(lang)); pos += strlen(lang); + + send_packet_to_downstream(cs, SSH2_MSG_DISCONNECT, packet, pos, NULL); + + share_begin_cleanup(cs); +} + +static int share_closing(Plug plug, const char *error_msg, int error_code, + int calling_back) +{ + struct ssh_sharing_connstate *cs = (struct ssh_sharing_connstate *)plug; + if (error_msg) + ssh_sharing_logf(cs->parent->ssh, cs->id, "%s", error_msg); + share_begin_cleanup(cs); + return 1; +} + +static int getstring_inner(const void *vdata, int datalen, + char **out, int *outlen) +{ + const unsigned char *data = (const unsigned char *)vdata; + int len; + + if (datalen < 4) + return FALSE; + + len = toint(GET_32BIT(data)); + if (len < 0 || len > datalen - 4) + return FALSE; + + if (outlen) + *outlen = len + 4; /* total size including length field */ + if (out) + *out = dupprintf("%.*s", len, (char *)data + 4); + return TRUE; +} + +static char *getstring(const void *data, int datalen) +{ + char *ret; + if (getstring_inner(data, datalen, &ret, NULL)) + return ret; + else + return NULL; +} + +static int getstring_size(const void *data, int datalen) +{ + int ret; + if (getstring_inner(data, datalen, NULL, &ret)) + return ret; + else + return -1; +} + +/* + * Append a message to the end of an xchannel's queue, with the length + * and type code filled in and the data block allocated but + * uninitialised. + */ +struct share_xchannel_message *share_xchannel_add_message +(struct share_xchannel *xc, int type, int len) +{ + unsigned char *block; + struct share_xchannel_message *msg; + + /* + * Be a little tricksy here by allocating a single memory block + * containing both the 'struct share_xchannel_message' and the + * actual data. Simplifies freeing it later. + */ + block = smalloc(sizeof(struct share_xchannel_message) + len); + msg = (struct share_xchannel_message *)block; + msg->data = block + sizeof(struct share_xchannel_message); + msg->datalen = len; + msg->type = type; + + /* + * Queue it in the xchannel. + */ + if (xc->msgtail) + xc->msgtail->next = msg; + else + xc->msghead = msg; + msg->next = NULL; + xc->msgtail = msg; + + return msg; +} + +void share_dead_xchannel_respond(struct ssh_sharing_connstate *cs, + struct share_xchannel *xc) +{ + /* + * Handle queued incoming messages from the server destined for an + * xchannel which is dead (i.e. downstream sent OPEN_FAILURE). + */ + int delete = FALSE; + while (xc->msghead) { + struct share_xchannel_message *msg = xc->msghead; + xc->msghead = msg->next; + + if (msg->type == SSH2_MSG_CHANNEL_REQUEST && msg->datalen > 4) { + /* + * A CHANNEL_REQUEST is responded to by sending + * CHANNEL_FAILURE, if it has want_reply set. + */ + int wantreplypos = getstring_size(msg->data, msg->datalen); + if (wantreplypos > 0 && wantreplypos < msg->datalen && + msg->data[wantreplypos] != 0) { + unsigned char id[4]; + PUT_32BIT(id, xc->server_id); + ssh_send_packet_from_downstream + (cs->parent->ssh, cs->id, SSH2_MSG_CHANNEL_FAILURE, id, 4, + "downstream refused X channel open"); + } + } else if (msg->type == SSH2_MSG_CHANNEL_CLOSE) { + /* + * On CHANNEL_CLOSE we can discard the channel completely. + */ + delete = TRUE; + } + + sfree(msg); + } + xc->msgtail = NULL; + if (delete) { + ssh_delete_sharing_channel(cs->parent->ssh, xc->upstream_id); + share_remove_xchannel(cs, xc); + } +} + +void share_xchannel_confirmation(struct ssh_sharing_connstate *cs, + struct share_xchannel *xc, + struct share_channel *chan, + unsigned downstream_window) +{ + unsigned char window_adjust[8]; + + /* + * Send all the queued messages downstream. + */ + while (xc->msghead) { + struct share_xchannel_message *msg = xc->msghead; + xc->msghead = msg->next; + + if (msg->datalen >= 4) + PUT_32BIT(msg->data, chan->downstream_id); + send_packet_to_downstream(cs, msg->type, + msg->data, msg->datalen, chan); + + sfree(msg); + } + + /* + * Send a WINDOW_ADJUST back upstream, to synchronise the window + * size downstream thinks it's presented with the one we've + * actually presented. + */ + PUT_32BIT(window_adjust, xc->server_id); + PUT_32BIT(window_adjust + 4, downstream_window - xc->window); + ssh_send_packet_from_downstream(cs->parent->ssh, cs->id, + SSH2_MSG_CHANNEL_WINDOW_ADJUST, + window_adjust, 8, "window adjustment after" + " downstream accepted X channel"); +} + +void share_xchannel_failure(struct ssh_sharing_connstate *cs, + struct share_xchannel *xc) +{ + /* + * If downstream refuses to open our X channel at all for some + * reason, we must respond by sending an emergency CLOSE upstream. + */ + unsigned char id[4]; + PUT_32BIT(id, xc->server_id); + ssh_send_packet_from_downstream + (cs->parent->ssh, cs->id, SSH2_MSG_CHANNEL_CLOSE, id, 4, + "downstream refused X channel open"); + + /* + * Now mark the xchannel as dead, and respond to anything sent on + * it until we see CLOSE for it in turn. + */ + xc->live = FALSE; + share_dead_xchannel_respond(cs, xc); +} + +void share_setup_x11_channel(void *csv, void *chanv, + unsigned upstream_id, unsigned server_id, + unsigned server_currwin, unsigned server_maxpkt, + unsigned client_adjusted_window, + const char *peer_addr, int peer_port, int endian, + int protomajor, int protominor, + const void *initial_data, int initial_len) +{ + struct ssh_sharing_connstate *cs = (struct ssh_sharing_connstate *)csv; + struct share_channel *chan = (struct share_channel *)chanv; + struct share_xchannel *xc; + struct share_xchannel_message *msg; + void *greeting; + int greeting_len; + unsigned char *pkt; + int pktlen; + + /* + * Create an xchannel containing data we've already received from + * the X client, and preload it with a CHANNEL_DATA message + * containing our own made-up authorisation greeting and any + * additional data sent from the server so far. + */ + xc = share_add_xchannel(cs, upstream_id, server_id); + greeting = x11_make_greeting(endian, protomajor, protominor, + chan->x11_auth_proto, + chan->x11_auth_data, chan->x11_auth_datalen, + peer_addr, peer_port, &greeting_len); + msg = share_xchannel_add_message(xc, SSH2_MSG_CHANNEL_DATA, + 8 + greeting_len + initial_len); + /* leave the channel id field unfilled - we don't know the + * downstream id yet, of course */ + PUT_32BIT(msg->data + 4, greeting_len + initial_len); + memcpy(msg->data + 8, greeting, greeting_len); + memcpy(msg->data + 8 + greeting_len, initial_data, initial_len); + sfree(greeting); + + xc->window = client_adjusted_window + greeting_len; + + /* + * Send on a CHANNEL_OPEN to downstream. + */ + pktlen = 27 + strlen(peer_addr); + pkt = snewn(pktlen, unsigned char); + PUT_32BIT(pkt, 3); /* strlen("x11") */ + memcpy(pkt+4, "x11", 3); + PUT_32BIT(pkt+7, server_id); + PUT_32BIT(pkt+11, server_currwin); + PUT_32BIT(pkt+15, server_maxpkt); + PUT_32BIT(pkt+19, strlen(peer_addr)); + memcpy(pkt+23, peer_addr, strlen(peer_addr)); + PUT_32BIT(pkt+23+strlen(peer_addr), peer_port); + send_packet_to_downstream(cs, SSH2_MSG_CHANNEL_OPEN, pkt, pktlen, NULL); + sfree(pkt); + + /* + * If this was a once-only X forwarding, clean it up now. + */ + if (chan->x11_one_shot) { + ssh_sharing_remove_x11_display(cs->parent->ssh, + chan->x11_auth_upstream); + chan->x11_auth_upstream = NULL; + sfree(chan->x11_auth_data); + chan->x11_auth_proto = -1; + chan->x11_auth_datalen = 0; + chan->x11_one_shot = 0; + } +} + +void share_got_pkt_from_server(void *csv, int type, + unsigned char *pkt, int pktlen) +{ + struct ssh_sharing_connstate *cs = (struct ssh_sharing_connstate *)csv; + struct share_globreq *globreq; + int id_pos; + unsigned upstream_id, server_id; + struct share_channel *chan; + struct share_xchannel *xc; + + switch (type) { + case SSH2_MSG_REQUEST_SUCCESS: + case SSH2_MSG_REQUEST_FAILURE: + globreq = cs->globreq_head; + if (globreq->type == GLOBREQ_TCPIP_FORWARD) { + if (type == SSH2_MSG_REQUEST_FAILURE) { + share_remove_forwarding(cs, globreq->fwd); + } else { + globreq->fwd->active = TRUE; + } + } else if (globreq->type == GLOBREQ_CANCEL_TCPIP_FORWARD) { + if (type == SSH2_MSG_REQUEST_SUCCESS) { + share_remove_forwarding(cs, globreq->fwd); + } + } + if (globreq->want_reply) { + send_packet_to_downstream(cs, type, pkt, pktlen, NULL); + } + cs->globreq_head = globreq->next; + sfree(globreq); + if (cs->globreq_head == NULL) + cs->globreq_tail = NULL; + + if (!cs->sock) { + /* Retry cleaning up this connection, in case that reply + * was the last thing we were waiting for. */ + share_try_cleanup(cs); + } + + break; + + case SSH2_MSG_CHANNEL_OPEN: + id_pos = getstring_size(pkt, pktlen); + assert(id_pos >= 0); + server_id = GET_32BIT(pkt + id_pos); + share_add_halfchannel(cs, server_id); + + send_packet_to_downstream(cs, type, pkt, pktlen, NULL); + break; + + case SSH2_MSG_CHANNEL_OPEN_CONFIRMATION: + case SSH2_MSG_CHANNEL_OPEN_FAILURE: + case SSH2_MSG_CHANNEL_CLOSE: + case SSH2_MSG_CHANNEL_WINDOW_ADJUST: + case SSH2_MSG_CHANNEL_DATA: + case SSH2_MSG_CHANNEL_EXTENDED_DATA: + case SSH2_MSG_CHANNEL_EOF: + case SSH2_MSG_CHANNEL_REQUEST: + case SSH2_MSG_CHANNEL_SUCCESS: + case SSH2_MSG_CHANNEL_FAILURE: + /* + * All these messages have the recipient channel id as the + * first uint32 field in the packet. Substitute the downstream + * channel id for our one and pass the packet downstream. + */ + assert(pktlen >= 4); + upstream_id = GET_32BIT(pkt); + if ((chan = share_find_channel_by_upstream(cs, upstream_id)) != NULL) { + /* + * The normal case: this id refers to an open channel. + */ + PUT_32BIT(pkt, chan->downstream_id); + send_packet_to_downstream(cs, type, pkt, pktlen, chan); + + /* + * Update the channel state, for messages that need it. + */ + if (type == SSH2_MSG_CHANNEL_OPEN_CONFIRMATION) { + if (chan->state == UNACKNOWLEDGED && pktlen >= 8) { + share_channel_set_server_id(cs, chan, GET_32BIT(pkt+4), + OPEN); + if (!cs->sock) { + /* Retry cleaning up this connection, so that we + * can send an immediate CLOSE on this channel for + * which we now know the server id. */ + share_try_cleanup(cs); + } + } + } else if (type == SSH2_MSG_CHANNEL_OPEN_FAILURE) { + ssh_delete_sharing_channel(cs->parent->ssh, chan->upstream_id); + share_remove_channel(cs, chan); + } else if (type == SSH2_MSG_CHANNEL_CLOSE) { + if (chan->state == SENT_CLOSE) { + ssh_delete_sharing_channel(cs->parent->ssh, + chan->upstream_id); + share_remove_channel(cs, chan); + if (!cs->sock) { + /* Retry cleaning up this connection, in case this + * channel closure was the last thing we were + * waiting for. */ + share_try_cleanup(cs); + } + } else { + chan->state = RCVD_CLOSE; + } + } + } else if ((xc = share_find_xchannel_by_upstream(cs, upstream_id)) + != NULL) { + /* + * The unusual case: this id refers to an xchannel. Add it + * to the xchannel's queue. + */ + struct share_xchannel_message *msg; + + msg = share_xchannel_add_message(xc, type, pktlen); + memcpy(msg->data, pkt, pktlen); + + /* If the xchannel is dead, then also respond to it (which + * may involve deleting the channel). */ + if (!xc->live) + share_dead_xchannel_respond(cs, xc); + } + break; + + default: + assert(!"This packet type should never have come from ssh.c"); + break; + } +} + +static void share_got_pkt_from_downstream(struct ssh_sharing_connstate *cs, + int type, + unsigned char *pkt, int pktlen) +{ + char *request_name; + struct share_forwarding *fwd; + int id_pos; + unsigned old_id, new_id, server_id; + struct share_globreq *globreq; + struct share_channel *chan; + struct share_halfchannel *hc; + struct share_xchannel *xc; + char *err = NULL; + + switch (type) { + case SSH2_MSG_DISCONNECT: + /* + * This message stops here: if downstream is disconnecting + * from us, that doesn't mean we want to disconnect from the + * SSH server. Close the downstream connection and start + * cleanup. + */ + share_begin_cleanup(cs); + break; + + case SSH2_MSG_GLOBAL_REQUEST: + /* + * The only global requests we understand are "tcpip-forward" + * and "cancel-tcpip-forward". Since those require us to + * maintain state, we must assume that other global requests + * will probably require that too, and so we don't forward on + * any request we don't understand. + */ + request_name = getstring(pkt, pktlen); + if (request_name == NULL) { + err = dupprintf("Truncated GLOBAL_REQUEST packet"); + goto confused; + } + + if (!strcmp(request_name, "tcpip-forward")) { + int wantreplypos, orig_wantreply, port, ret; + char *host; + + sfree(request_name); + + /* + * Pick the packet apart to find the want_reply field and + * the host/port we're going to ask to listen on. + */ + wantreplypos = getstring_size(pkt, pktlen); + if (wantreplypos < 0 || wantreplypos >= pktlen) { + err = dupprintf("Truncated GLOBAL_REQUEST packet"); + goto confused; + } + orig_wantreply = pkt[wantreplypos]; + port = getstring_size(pkt + (wantreplypos + 1), + pktlen - (wantreplypos + 1)); + port += (wantreplypos + 1); + if (port < 0 || port > pktlen - 4) { + err = dupprintf("Truncated GLOBAL_REQUEST packet"); + goto confused; + } + host = getstring(pkt + (wantreplypos + 1), + pktlen - (wantreplypos + 1)); + assert(host != NULL); + port = GET_32BIT(pkt + port); + + /* + * See if we can allocate space in ssh.c's tree of remote + * port forwardings. If we can't, it's because another + * client sharing this connection has already allocated + * the identical port forwarding, so we take it on + * ourselves to manufacture a failure packet and send it + * back to downstream. + */ + ret = ssh_alloc_sharing_rportfwd(cs->parent->ssh, host, port, cs); + if (!ret) { + if (orig_wantreply) { + send_packet_to_downstream(cs, SSH2_MSG_REQUEST_FAILURE, + "", 0, NULL); + } + } else { + /* + * We've managed to make space for this forwarding + * locally. Pass the request on to the SSH server, but + * set want_reply even if it wasn't originally set, so + * that we know whether this forwarding needs to be + * cleaned up if downstream goes away. + */ + int old_wantreply = pkt[wantreplypos]; + pkt[wantreplypos] = 1; + ssh_send_packet_from_downstream + (cs->parent->ssh, cs->id, type, pkt, pktlen, + old_wantreply ? NULL : "upstream added want_reply flag"); + fwd = share_add_forwarding(cs, host, port); + ssh_sharing_queue_global_request(cs->parent->ssh, cs); + + if (fwd) { + globreq = snew(struct share_globreq); + globreq->next = NULL; + if (cs->globreq_tail) + cs->globreq_tail->next = globreq; + else + cs->globreq_head = globreq; + globreq->fwd = fwd; + globreq->want_reply = orig_wantreply; + globreq->type = GLOBREQ_TCPIP_FORWARD; + } + } + + sfree(host); + } else if (!strcmp(request_name, "cancel-tcpip-forward")) { + int wantreplypos, orig_wantreply, port; + char *host; + struct share_forwarding *fwd; + + sfree(request_name); + + /* + * Pick the packet apart to find the want_reply field and + * the host/port we're going to ask to listen on. + */ + wantreplypos = getstring_size(pkt, pktlen); + if (wantreplypos < 0 || wantreplypos >= pktlen) { + err = dupprintf("Truncated GLOBAL_REQUEST packet"); + goto confused; + } + orig_wantreply = pkt[wantreplypos]; + port = getstring_size(pkt + (wantreplypos + 1), + pktlen - (wantreplypos + 1)); + port += (wantreplypos + 1); + if (port < 0 || port > pktlen - 4) { + err = dupprintf("Truncated GLOBAL_REQUEST packet"); + goto confused; + } + host = getstring(pkt + (wantreplypos + 1), + pktlen - (wantreplypos + 1)); + assert(host != NULL); + port = GET_32BIT(pkt + port); + + /* + * Look up the existing forwarding with these details. + */ + fwd = share_find_forwarding(cs, host, port); + if (!fwd) { + if (orig_wantreply) { + send_packet_to_downstream(cs, SSH2_MSG_REQUEST_FAILURE, + "", 0, NULL); + } + } else { + /* + * Pass the cancel request on to the SSH server, but + * set want_reply even if it wasn't originally set, so + * that _we_ know whether the forwarding has been + * deleted even if downstream doesn't want to know. + */ + int old_wantreply = pkt[wantreplypos]; + pkt[wantreplypos] = 1; + ssh_send_packet_from_downstream + (cs->parent->ssh, cs->id, type, pkt, pktlen, + old_wantreply ? NULL : "upstream added want_reply flag"); + ssh_sharing_queue_global_request(cs->parent->ssh, cs); + } + + sfree(host); + } else { + /* + * Request we don't understand. Manufacture a failure + * message if an answer was required. + */ + int wantreplypos; + + sfree(request_name); + + wantreplypos = getstring_size(pkt, pktlen); + if (wantreplypos < 0 || wantreplypos >= pktlen) { + err = dupprintf("Truncated GLOBAL_REQUEST packet"); + goto confused; + } + if (pkt[wantreplypos]) + send_packet_to_downstream(cs, SSH2_MSG_REQUEST_FAILURE, + "", 0, NULL); + } + break; + + case SSH2_MSG_CHANNEL_OPEN: + /* Sender channel id comes after the channel type string */ + id_pos = getstring_size(pkt, pktlen); + if (id_pos < 0 || id_pos > pktlen - 12) { + err = dupprintf("Truncated CHANNEL_OPEN packet"); + goto confused; + } + + old_id = GET_32BIT(pkt + id_pos); + new_id = ssh_alloc_sharing_channel(cs->parent->ssh, cs); + share_add_channel(cs, old_id, new_id, 0, UNACKNOWLEDGED, + GET_32BIT(pkt + id_pos + 8)); + PUT_32BIT(pkt + id_pos, new_id); + ssh_send_packet_from_downstream(cs->parent->ssh, cs->id, + type, pkt, pktlen, NULL); + break; + + case SSH2_MSG_CHANNEL_OPEN_CONFIRMATION: + if (pktlen < 16) { + err = dupprintf("Truncated CHANNEL_OPEN_CONFIRMATION packet"); + goto confused; + } + + id_pos = 4; /* sender channel id is 2nd uint32 field in packet */ + old_id = GET_32BIT(pkt + id_pos); + + server_id = GET_32BIT(pkt); + /* This server id may refer to either a halfchannel or an xchannel. */ + hc = NULL, xc = NULL; /* placate optimiser */ + if ((hc = share_find_halfchannel(cs, server_id)) != NULL) { + new_id = ssh_alloc_sharing_channel(cs->parent->ssh, cs); + } else if ((xc = share_find_xchannel_by_server(cs, server_id)) + != NULL) { + new_id = xc->upstream_id; + } else { + err = dupprintf("CHANNEL_OPEN_CONFIRMATION packet cited unknown channel %u", (unsigned)server_id); + goto confused; + } + + PUT_32BIT(pkt + id_pos, new_id); + + chan = share_add_channel(cs, old_id, new_id, server_id, OPEN, + GET_32BIT(pkt + 12)); + + if (hc) { + ssh_send_packet_from_downstream(cs->parent->ssh, cs->id, + type, pkt, pktlen, NULL); + share_remove_halfchannel(cs, hc); + } else if (xc) { + unsigned downstream_window = GET_32BIT(pkt + 8); + if (downstream_window < 256) { + err = dupprintf("Initial window size for x11 channel must be at least 256 (got %u)", downstream_window); + goto confused; + } + share_xchannel_confirmation(cs, xc, chan, downstream_window); + share_remove_xchannel(cs, xc); + } + + break; + + case SSH2_MSG_CHANNEL_OPEN_FAILURE: + if (pktlen < 4) { + err = dupprintf("Truncated CHANNEL_OPEN_FAILURE packet"); + goto confused; + } + + server_id = GET_32BIT(pkt); + /* This server id may refer to either a halfchannel or an xchannel. */ + if ((hc = share_find_halfchannel(cs, server_id)) != NULL) { + ssh_send_packet_from_downstream(cs->parent->ssh, cs->id, + type, pkt, pktlen, NULL); + share_remove_halfchannel(cs, hc); + } else if ((xc = share_find_xchannel_by_server(cs, server_id)) + != NULL) { + share_xchannel_failure(cs, xc); + } else { + err = dupprintf("CHANNEL_OPEN_FAILURE packet cited unknown channel %u", (unsigned)server_id); + goto confused; + } + + break; + + case SSH2_MSG_CHANNEL_WINDOW_ADJUST: + case SSH2_MSG_CHANNEL_DATA: + case SSH2_MSG_CHANNEL_EXTENDED_DATA: + case SSH2_MSG_CHANNEL_EOF: + case SSH2_MSG_CHANNEL_CLOSE: + case SSH2_MSG_CHANNEL_REQUEST: + case SSH2_MSG_CHANNEL_SUCCESS: + case SSH2_MSG_CHANNEL_FAILURE: + case SSH2_MSG_IGNORE: + case SSH2_MSG_DEBUG: + if (type == SSH2_MSG_CHANNEL_REQUEST && + (request_name = getstring(pkt + 4, pktlen - 4)) != NULL) { + /* + * Agent forwarding requests from downstream are treated + * specially. Because OpenSSHD doesn't let us enable agent + * forwarding independently per session channel, and in + * particular because the OpenSSH-defined agent forwarding + * protocol does not mark agent-channel requests with the + * id of the session channel they originate from, the only + * way we can implement agent forwarding in a + * connection-shared PuTTY is to forward the _upstream_ + * agent. Hence, we unilaterally deny agent forwarding + * requests from downstreams if we aren't prepared to + * forward an agent ourselves. + * + * (If we are, then we dutifully pass agent forwarding + * requests upstream. OpenSSHD has the curious behaviour + * that all but the first such request will be rejected, + * but all session channels opened after the first request + * get agent forwarding enabled whether they ask for it or + * not; but that's not our concern, since other SSH + * servers supporting the same piece of protocol might in + * principle at least manage to enable agent forwarding on + * precisely the channels that requested it, even if the + * subsequent CHANNEL_OPENs still can't be associated with + * a parent session channel.) + */ + if (!strcmp(request_name, "auth-agent-req@openssh.com") && + !ssh_agent_forwarding_permitted(cs->parent->ssh)) { + unsigned server_id = GET_32BIT(pkt); + unsigned char recipient_id[4]; + chan = share_find_channel_by_server(cs, server_id); + if (chan) { + PUT_32BIT(recipient_id, chan->downstream_id); + send_packet_to_downstream(cs, SSH2_MSG_CHANNEL_FAILURE, + recipient_id, 4, NULL); + } else { + char *buf = dupprintf("Agent forwarding request for " + "unrecognised channel %u", server_id); + share_disconnect(cs, buf); + sfree(buf); + return; + } + break; + } + + /* + * Another thing we treat specially is X11 forwarding + * requests. For these, we have to make up another set of + * X11 auth data, and enter it into our SSH connection's + * list of possible X11 authorisation credentials so that + * when we see an X11 channel open request we can know + * whether it's one to handle locally or one to pass on to + * a downstream, and if the latter, which one. + */ + if (!strcmp(request_name, "x11-req")) { + unsigned server_id = GET_32BIT(pkt); + int want_reply, single_connection, screen; + char *auth_proto_str, *auth_data; + int auth_proto, protolen, datalen; + int pos; + + chan = share_find_channel_by_server(cs, server_id); + if (!chan) { + char *buf = dupprintf("X11 forwarding request for " + "unrecognised channel %u", server_id); + share_disconnect(cs, buf); + sfree(buf); + return; + } + + /* + * Pick apart the whole message to find the downstream + * auth details. + */ + /* we have already seen: 4 bytes channel id, 4+7 request name */ + if (pktlen < 17) { + err = dupprintf("Truncated CHANNEL_REQUEST(\"x11\") packet"); + goto confused; + } + want_reply = pkt[15] != 0; + single_connection = pkt[16] != 0; + auth_proto_str = getstring(pkt+17, pktlen-17); + pos = 17 + getstring_size(pkt+17, pktlen-17); + auth_data = getstring(pkt+pos, pktlen-pos); + pos += getstring_size(pkt+pos, pktlen-pos); + if (pktlen < pos+4) { + err = dupprintf("Truncated CHANNEL_REQUEST(\"x11\") packet"); + goto confused; + } + screen = GET_32BIT(pkt+pos); + + auth_proto = x11_identify_auth_proto(auth_proto_str); + if (auth_proto < 0) { + /* Reject due to not understanding downstream's + * requested authorisation method. */ + unsigned char recipient_id[4]; + PUT_32BIT(recipient_id, chan->downstream_id); + send_packet_to_downstream(cs, SSH2_MSG_CHANNEL_FAILURE, + recipient_id, 4, NULL); + } + + chan->x11_auth_proto = auth_proto; + chan->x11_auth_data = x11_dehexify(auth_data, + &chan->x11_auth_datalen); + chan->x11_auth_upstream = + ssh_sharing_add_x11_display(cs->parent->ssh, auth_proto, + cs, chan); + chan->x11_one_shot = single_connection; + + /* + * Now construct a replacement X forwarding request, + * containing our own auth data, and send that to the + * server. + */ + protolen = strlen(chan->x11_auth_upstream->protoname); + datalen = strlen(chan->x11_auth_upstream->datastring); + pktlen = 29+protolen+datalen; + pkt = snewn(pktlen, unsigned char); + PUT_32BIT(pkt, server_id); + PUT_32BIT(pkt+4, 7); /* strlen("x11-req") */ + memcpy(pkt+8, "x11-req", 7); + pkt[15] = want_reply; + pkt[16] = single_connection; + PUT_32BIT(pkt+17, protolen); + memcpy(pkt+21, chan->x11_auth_upstream->protoname, protolen); + PUT_32BIT(pkt+21+protolen, datalen); + memcpy(pkt+25+protolen, chan->x11_auth_upstream->datastring, + datalen); + PUT_32BIT(pkt+25+protolen+datalen, screen); + ssh_send_packet_from_downstream(cs->parent->ssh, cs->id, + SSH2_MSG_CHANNEL_REQUEST, + pkt, pktlen, NULL); + sfree(pkt); + + break; + } + } + + ssh_send_packet_from_downstream(cs->parent->ssh, cs->id, + type, pkt, pktlen, NULL); + if (type == SSH2_MSG_CHANNEL_CLOSE && pktlen >= 4) { + server_id = GET_32BIT(pkt); + chan = share_find_channel_by_server(cs, server_id); + if (chan) { + if (chan->state == RCVD_CLOSE) { + ssh_delete_sharing_channel(cs->parent->ssh, + chan->upstream_id); + share_remove_channel(cs, chan); + } else { + chan->state = SENT_CLOSE; + } + } + } + break; + + default: + err = dupprintf("Unexpected packet type %d\n", type); + goto confused; + + /* + * Any other packet type is unexpected. In particular, we + * never pass GLOBAL_REQUESTs downstream, so we never expect + * to see SSH2_MSG_REQUEST_{SUCCESS,FAILURE}. + */ + confused: + assert(err != NULL); + share_disconnect(cs, err); + sfree(err); + break; + } +} + +/* + * Coroutine macros similar to, but simplified from, those in ssh.c. + */ +#define crBegin(v) { int *crLine = &v; switch(v) { case 0:; +#define crFinish(z) } *crLine = 0; return (z); } +#define crGetChar(c) do \ + { \ + while (len == 0) { \ + *crLine =__LINE__; return 1; case __LINE__:; \ + } \ + len--; \ + (c) = (unsigned char)*data++; \ + } while (0) + +static int share_receive(Plug plug, int urgent, char *data, int len) +{ + struct ssh_sharing_connstate *cs = (struct ssh_sharing_connstate *)plug; + static const char expected_verstring_prefix[] = + "SSHCONNECTION@putty.projects.tartarus.org-2.0-"; + unsigned char c; + + crBegin(cs->crLine); + + /* + * First read the version string from downstream. + */ + cs->recvlen = 0; + while (1) { + crGetChar(c); + if (c == '\012') + break; + if (cs->recvlen > sizeof(cs->recvbuf)) { + char *buf = dupprintf("Version string far too long\n"); + share_disconnect(cs, buf); + sfree(buf); + goto dead; + } + cs->recvbuf[cs->recvlen++] = c; + } + + /* + * Now parse the version string to make sure it's at least vaguely + * sensible, and log it. + */ + if (cs->recvlen < sizeof(expected_verstring_prefix)-1 || + memcmp(cs->recvbuf, expected_verstring_prefix, + sizeof(expected_verstring_prefix) - 1)) { + char *buf = dupprintf("Version string did not have expected prefix\n"); + share_disconnect(cs, buf); + sfree(buf); + goto dead; + } + if (cs->recvlen > 0 && cs->recvbuf[cs->recvlen-1] == '\015') + cs->recvlen--; /* trim off \r before \n */ + ssh_sharing_logf(cs->parent->ssh, cs->id, + "Downstream version string: %.*s", + cs->recvlen, cs->recvbuf); + + /* + * Loop round reading packets. + */ + while (1) { + cs->recvlen = 0; + while (cs->recvlen < 4) { + crGetChar(c); + cs->recvbuf[cs->recvlen++] = c; + } + cs->curr_packetlen = toint(GET_32BIT(cs->recvbuf) + 4); + if (cs->curr_packetlen < 5 || + cs->curr_packetlen > sizeof(cs->recvbuf)) { + char *buf = dupprintf("Bad packet length %u\n", + (unsigned)cs->curr_packetlen); + share_disconnect(cs, buf); + sfree(buf); + goto dead; + } + while (cs->recvlen < cs->curr_packetlen) { + crGetChar(c); + cs->recvbuf[cs->recvlen++] = c; + } + + share_got_pkt_from_downstream(cs, cs->recvbuf[4], + cs->recvbuf + 5, cs->recvlen - 5); + } + + dead:; + crFinish(1); +} + +static void share_sent(Plug plug, int bufsize) +{ + /* struct ssh_sharing_connstate *cs = (struct ssh_sharing_connstate *)plug; */ + + /* + * We do nothing here, because we expect that there won't be a + * need to throttle and unthrottle the connection to a downstream. + * It should automatically throttle itself: if the SSH server + * sends huge amounts of data on all channels then it'll run out + * of window until our downstream sends it back some + * WINDOW_ADJUSTs. + */ +} + +static int share_listen_closing(Plug plug, const char *error_msg, + int error_code, int calling_back) +{ + struct ssh_sharing_state *sharestate = (struct ssh_sharing_state *)plug; + if (error_msg) + ssh_sharing_logf(sharestate->ssh, 0, + "listening socket: %s", error_msg); + sk_close(sharestate->listensock); + return 1; +} + +static void share_send_verstring(struct ssh_sharing_connstate *cs) +{ + char *fullstring = dupcat("SSHCONNECTION@putty.projects.tartarus.org-2.0-", + cs->parent->server_verstring, "\015\012", NULL); + sk_write(cs->sock, fullstring, strlen(fullstring)); + sfree(fullstring); + + cs->sent_verstring = TRUE; +} + +int share_ndownstreams(void *state) +{ + struct ssh_sharing_state *sharestate = (struct ssh_sharing_state *)state; + return count234(sharestate->connections); +} + +void share_activate(void *state, const char *server_verstring) +{ + /* + * Indication from ssh.c that we are now ready to begin serving + * any downstreams that have already connected to us. + */ + struct ssh_sharing_state *sharestate = (struct ssh_sharing_state *)state; + struct ssh_sharing_connstate *cs; + int i; + + /* + * Trim the server's version string down to just the software + * version component, removing "SSH-2.0-" or whatever at the + * front. + */ + for (i = 0; i < 2; i++) { + server_verstring += strcspn(server_verstring, "-"); + if (*server_verstring) + server_verstring++; + } + + sharestate->server_verstring = dupstr(server_verstring); + + for (i = 0; (cs = (struct ssh_sharing_connstate *) + index234(sharestate->connections, i)) != NULL; i++) { + assert(!cs->sent_verstring); + share_send_verstring(cs); + } +} + +static int share_listen_accepting(Plug plug, + accept_fn_t constructor, accept_ctx_t ctx) +{ + static const struct plug_function_table connection_fn_table = { + NULL, /* no log function, because that's for outgoing connections */ + share_closing, + share_receive, + share_sent, + NULL /* no accepting function, because we've already done it */ + }; + struct ssh_sharing_state *sharestate = (struct ssh_sharing_state *)plug; + struct ssh_sharing_connstate *cs; + const char *err; + + /* + * A new downstream has connected to us. + */ + cs = snew(struct ssh_sharing_connstate); + cs->fn = &connection_fn_table; + cs->parent = sharestate; + + if ((cs->id = share_find_unused_id(sharestate, sharestate->nextid)) == 0 && + (cs->id = share_find_unused_id(sharestate, 1)) == 0) { + sfree(cs); + return 1; + } + sharestate->nextid = cs->id + 1; + if (sharestate->nextid == 0) + sharestate->nextid++; /* only happens in VERY long-running upstreams */ + + cs->sock = constructor(ctx, (Plug) cs); + if ((err = sk_socket_error(cs->sock)) != NULL) { + sfree(cs); + return err != NULL; + } + + sk_set_frozen(cs->sock, 0); + + add234(cs->parent->connections, cs); + + cs->sent_verstring = FALSE; + if (sharestate->server_verstring) + share_send_verstring(cs); + + cs->got_verstring = FALSE; + cs->recvlen = 0; + cs->crLine = 0; + cs->halfchannels = newtree234(share_halfchannel_cmp); + cs->channels_by_us = newtree234(share_channel_us_cmp); + cs->channels_by_server = newtree234(share_channel_server_cmp); + cs->xchannels_by_us = newtree234(share_xchannel_us_cmp); + cs->xchannels_by_server = newtree234(share_xchannel_server_cmp); + cs->forwardings = newtree234(share_forwarding_cmp); + cs->globreq_head = cs->globreq_tail = NULL; + + ssh_sharing_downstream_connected(sharestate->ssh, cs->id); + + return 0; +} + +/* Per-application overrides for what roles we can take (e.g. pscp + * will never be an upstream) */ +extern const int share_can_be_downstream; +extern const int share_can_be_upstream; + +/* + * Init function for connection sharing. We either open a listening + * socket and become an upstream, or connect to an existing one and + * become a downstream, or do neither. We are responsible for deciding + * which of these to do (including checking the Conf to see if + * connection sharing is even enabled in the first place). If we + * become a downstream, we return the Socket with which we connected + * to the upstream; otherwise (whether or not we have established an + * upstream) we return NULL. + */ +Socket ssh_connection_sharing_init(const char *host, int port, + Conf *conf, Ssh ssh, void **state) +{ + static const struct plug_function_table listen_fn_table = { + NULL, /* no log function, because that's for outgoing connections */ + share_listen_closing, + NULL, /* no receive function on a listening socket */ + NULL, /* no sent function on a listening socket */ + share_listen_accepting + }; + + int result, can_upstream, can_downstream; + char *logtext, *ds_err, *us_err; + char *sockname; + Socket sock; + struct ssh_sharing_state *sharestate; + + if (!conf_get_int(conf, CONF_ssh_connection_sharing)) + return NULL; /* do not share anything */ + can_upstream = share_can_be_upstream && + conf_get_int(conf, CONF_ssh_connection_sharing_upstream); + can_downstream = share_can_be_downstream && + conf_get_int(conf, CONF_ssh_connection_sharing_downstream); + if (!can_upstream && !can_downstream) + return NULL; + + /* + * Decide on the string used to identify the connection point + * between upstream and downstream (be it a Windows named pipe or + * a Unix-domain socket or whatever else). + * + * I wondered about making this a SHA hash of all sorts of pieces + * of the PuTTY configuration - essentially everything PuTTY uses + * to know where and how to make a connection, including all the + * proxy details (or rather, all the _relevant_ ones - only + * including settings that other settings didn't prevent from + * having any effect), plus the username. However, I think it's + * better to keep it really simple: the connection point + * identifier is derived from the hostname and port used to index + * the host-key cache (not necessarily where we _physically_ + * connected to, in cases involving proxies or CONF_loghost), plus + * the username if one is specified. + */ + { + char *username = get_remote_username(conf); + + if (port == 22) { + if (username) + sockname = dupprintf("%s@%s", username, host); + else + sockname = dupprintf("%s", host); + } else { + if (username) + sockname = dupprintf("%s@%s:%d", username, host, port); + else + sockname = dupprintf("%s:%d", host, port); + } + + sfree(username); + + /* + * The platform-specific code may transform this further in + * order to conform to local namespace conventions (e.g. not + * using slashes in filenames), but that's its job and not + * ours. + */ + } + + /* + * Create a data structure for the listening plug if we turn out + * to be an upstream. + */ + sharestate = snew(struct ssh_sharing_state); + sharestate->fn = &listen_fn_table; + sharestate->listensock = NULL; + + /* + * Now hand off to a per-platform routine that either connects to + * an existing upstream (using 'ssh' as the plug), establishes our + * own upstream (using 'sharestate' as the plug), or forks off a + * separate upstream and then connects to that. It will return a + * code telling us which kind of socket it put in 'sock'. + */ + sock = NULL; + logtext = ds_err = us_err = NULL; + result = platform_ssh_share(sockname, conf, (Plug)ssh, + (Plug)sharestate, &sock, &logtext, &ds_err, + &us_err, can_upstream, can_downstream); + ssh_connshare_log(ssh, result, logtext, ds_err, us_err); + sfree(logtext); + sfree(ds_err); + sfree(us_err); + switch (result) { + case SHARE_NONE: + /* + * We aren't sharing our connection at all (e.g. something + * went wrong setting the socket up). Free the upstream + * structure and return NULL. + */ + assert(sock == NULL); + *state = NULL; + sfree(sharestate); + sfree(sockname); + return NULL; + + case SHARE_DOWNSTREAM: + /* + * We are downstream, so free sharestate which it turns out we + * don't need after all, and return the downstream socket as a + * replacement for an ordinary SSH connection. + */ + *state = NULL; + sfree(sharestate); + sfree(sockname); + return sock; + + case SHARE_UPSTREAM: + /* + * We are upstream. Set up sharestate properly and pass a copy + * to the caller; return NULL, to tell ssh.c that it has to + * make an ordinary connection after all. + */ + *state = sharestate; + sharestate->listensock = sock; + sharestate->connections = newtree234(share_connstate_cmp); + sharestate->ssh = ssh; + sharestate->server_verstring = NULL; + sharestate->sockname = dupstr(sockname); + sharestate->nextid = 1; + return NULL; + } + + return NULL; +} Index: sshzlib.c ================================================================== --- sshzlib.c +++ sshzlib.c @@ -36,10 +36,11 @@ * everybody has used it. I don't intend that this should happen * again. */ #include +#include #include #ifdef ZLIB_STANDALONE /* Index: storage.h ================================================================== --- storage.h +++ storage.h @@ -7,13 +7,13 @@ #define PUTTY_STORAGE_H /* ---------------------------------------------------------------------- * Functions to save and restore PuTTY sessions. Note that this is * only the low-level code to do the reading and writing. The - * higher-level code that translates a Config structure into a set - * of (key,value) pairs is elsewhere, since it doesn't (mostly) - * change between platforms. + * higher-level code that translates an internal Conf structure into + * a set of (key,value) pairs in their external storage format is + * elsewhere, since it doesn't (mostly) change between platforms. */ /* * Write a saved session. The caller is expected to call * open_setting_w() to get a `void *' handle, then pass that to a @@ -29,36 +29,35 @@ * Any returned error message must be freed after use. */ void *open_settings_w(const char *sessionname, char **errmsg); void write_setting_s(void *handle, const char *key, const char *value); void write_setting_i(void *handle, const char *key, int value); -void write_setting_filename(void *handle, const char *key, Filename value); -void write_setting_fontspec(void *handle, const char *key, FontSpec font); +void write_setting_filename(void *handle, const char *key, Filename *value); +void write_setting_fontspec(void *handle, const char *key, FontSpec *font); void close_settings_w(void *handle); /* * Read a saved session. The caller is expected to call * open_setting_r() to get a `void *' handle, then pass that to a * number of calls to read_setting_s() and read_setting_i(), and * then close it using close_settings_r(). * - * read_setting_s() writes into the provided buffer and returns a - * pointer to the same buffer. + * read_setting_s() returns a dynamically allocated string which the + * caller must free. read_setting_filename() and + * read_setting_fontspec() likewise return dynamically allocated + * structures. * * If a particular string setting is not present in the session, * read_setting_s() can return NULL, in which case the caller * should invent a sensible default. If an integer setting is not * present, read_setting_i() returns its provided default. - * - * read_setting_filename() and read_setting_fontspec() each read into - * the provided buffer, and return zero if they failed to. */ void *open_settings_r(const char *sessionname); -char *read_setting_s(void *handle, const char *key, char *buffer, int buflen); +char *read_setting_s(void *handle, const char *key); int read_setting_i(void *handle, const char *key, int defvalue); -int read_setting_filename(void *handle, const char *key, Filename *value); -int read_setting_fontspec(void *handle, const char *key, FontSpec *font); +Filename *read_setting_filename(void *handle, const char *key); +FontSpec *read_setting_fontspec(void *handle, const char *key); void close_settings_r(void *handle); /* * Delete a whole saved session. */ Index: telnet.c ================================================================== --- telnet.c +++ telnet.c @@ -2,10 +2,11 @@ * Telnet backend. */ #include #include +#include #include "putty.h" #ifndef FALSE #define FALSE 0 @@ -179,10 +180,11 @@ typedef struct telnet_tag { const struct plug_function_table *fn; /* the above field _must_ be first in the structure */ Socket s; + int closed_on_socket_error; void *frontend; void *ldisc; int term_width, term_height; @@ -199,11 +201,11 @@ enum { TOP_LEVEL, SEENIAC, SEENWILL, SEENWONT, SEENDO, SEENDONT, SEENSB, SUBNEGOT, SUBNEG_IAC, SEENCR } state; - Config cfg; + Conf *conf; Pinger pinger; } *Telnet; #define TELNET_MAX_BACKLOG 4096 @@ -361,54 +363,59 @@ send_opt(telnet, (cmd == WILL ? DONT : WONT), option); } static void process_subneg(Telnet telnet) { - unsigned char b[2048], *p, *q; - int var, value, n; - char *e; + unsigned char *b, *p, *q; + int var, value, n, bsize; + char *e, *eval, *ekey, *user; switch (telnet->sb_opt) { case TELOPT_TSPEED: if (telnet->sb_len == 1 && telnet->sb_buf[0] == TELQUAL_SEND) { char *logbuf; + char *termspeed = conf_get_str(telnet->conf, CONF_termspeed); + b = snewn(20 + strlen(termspeed), unsigned char); b[0] = IAC; b[1] = SB; b[2] = TELOPT_TSPEED; b[3] = TELQUAL_IS; - strcpy((char *)(b + 4), telnet->cfg.termspeed); - n = 4 + strlen(telnet->cfg.termspeed); + strcpy((char *)(b + 4), termspeed); + n = 4 + strlen(termspeed); b[n] = IAC; b[n + 1] = SE; telnet->bufsize = sk_write(telnet->s, (char *)b, n + 2); logevent(telnet->frontend, "server:\tSB TSPEED SEND"); - logbuf = dupprintf("client:\tSB TSPEED IS %s", telnet->cfg.termspeed); + logbuf = dupprintf("client:\tSB TSPEED IS %s", termspeed); logevent(telnet->frontend, logbuf); sfree(logbuf); + sfree(b); } else logevent(telnet->frontend, "server:\tSB TSPEED "); break; case TELOPT_TTYPE: if (telnet->sb_len == 1 && telnet->sb_buf[0] == TELQUAL_SEND) { char *logbuf; + char *termtype = conf_get_str(telnet->conf, CONF_termtype); + b = snewn(20 + strlen(termtype), unsigned char); b[0] = IAC; b[1] = SB; b[2] = TELOPT_TTYPE; b[3] = TELQUAL_IS; - for (n = 0; telnet->cfg.termtype[n]; n++) - b[n + 4] = (telnet->cfg.termtype[n] >= 'a' - && telnet->cfg.termtype[n] <= - 'z' ? telnet->cfg.termtype[n] + 'A' - - 'a' : telnet->cfg.termtype[n]); + for (n = 0; termtype[n]; n++) + b[n + 4] = (termtype[n] >= 'a' && termtype[n] <= 'z' ? + termtype[n] + 'A' - 'a' : + termtype[n]); b[n + 4] = IAC; b[n + 5] = SE; telnet->bufsize = sk_write(telnet->s, (char *)b, n + 6); b[n + 4] = 0; logevent(telnet->frontend, "server:\tSB TTYPE SEND"); logbuf = dupprintf("client:\tSB TTYPE IS %s", b + 4); logevent(telnet->frontend, logbuf); sfree(logbuf); + sfree(b); } else logevent(telnet->frontend, "server:\tSB TTYPE \r\n"); break; case TELOPT_OLD_ENVIRON: case TELOPT_NEW_ENVIRON: @@ -419,11 +426,11 @@ p++; logbuf = dupprintf("server:\tSB %s SEND", telopt(telnet->sb_opt)); logevent(telnet->frontend, logbuf); sfree(logbuf); if (telnet->sb_opt == TELOPT_OLD_ENVIRON) { - if (telnet->cfg.rfc_environ) { + if (conf_get_int(telnet->conf, CONF_rfc_environ)) { value = RFC_VALUE; var = RFC_VAR; } else { value = BSD_VALUE; var = BSD_VAR; @@ -447,54 +454,79 @@ * isn't in doubt. */ value = RFC_VALUE; var = RFC_VAR; } + bsize = 20; + for (eval = conf_get_str_strs(telnet->conf, CONF_environmt, + NULL, &ekey); + eval != NULL; + eval = conf_get_str_strs(telnet->conf, CONF_environmt, + ekey, &ekey)) + bsize += strlen(ekey) + strlen(eval) + 2; + user = get_remote_username(telnet->conf); + if (user) + bsize += 6 + strlen(user); + + b = snewn(bsize, unsigned char); b[0] = IAC; b[1] = SB; b[2] = telnet->sb_opt; b[3] = TELQUAL_IS; n = 4; - e = telnet->cfg.environmt; - while (*e) { - b[n++] = var; - while (*e && *e != '\t') - b[n++] = *e++; - if (*e == '\t') - e++; - b[n++] = value; - while (*e) - b[n++] = *e++; - e++; - } - { - char user[sizeof(telnet->cfg.username)]; - (void) get_remote_username(&telnet->cfg, user, sizeof(user)); - if (*user) { - b[n++] = var; - b[n++] = 'U'; - b[n++] = 'S'; - b[n++] = 'E'; - b[n++] = 'R'; - b[n++] = value; - e = user; - while (*e) - b[n++] = *e++; - } - b[n++] = IAC; - b[n++] = SE; - telnet->bufsize = sk_write(telnet->s, (char *)b, n); - logbuf = dupprintf("client:\tSB %s IS %s%s%s%s", - telopt(telnet->sb_opt), - *user ? "USER=" : "", - user, - *user ? " " : "", - n == 6 ? "" : - (*telnet->cfg.environmt ? "" : "")); + for (eval = conf_get_str_strs(telnet->conf, CONF_environmt, + NULL, &ekey); + eval != NULL; + eval = conf_get_str_strs(telnet->conf, CONF_environmt, + ekey, &ekey)) { + b[n++] = var; + for (e = ekey; *e; e++) + b[n++] = *e; + b[n++] = value; + for (e = eval; *e; e++) + b[n++] = *e; + } + if (user) { + b[n++] = var; + b[n++] = 'U'; + b[n++] = 'S'; + b[n++] = 'E'; + b[n++] = 'R'; + b[n++] = value; + for (e = user; *e; e++) + b[n++] = *e; + } + b[n++] = IAC; + b[n++] = SE; + telnet->bufsize = sk_write(telnet->s, (char *)b, n); + if (n == 6) { + logbuf = dupprintf("client:\tSB %s IS ", + telopt(telnet->sb_opt)); + logevent(telnet->frontend, logbuf); + sfree(logbuf); + } else { + logbuf = dupprintf("client:\tSB %s IS:", + telopt(telnet->sb_opt)); logevent(telnet->frontend, logbuf); sfree(logbuf); + for (eval = conf_get_str_strs(telnet->conf, CONF_environmt, + NULL, &ekey); + eval != NULL; + eval = conf_get_str_strs(telnet->conf, CONF_environmt, + ekey, &ekey)) { + logbuf = dupprintf("\t%s=%s", ekey, eval); + logevent(telnet->frontend, logbuf); + sfree(logbuf); + } + if (user) { + logbuf = dupprintf("\tUSER=%s", user); + logevent(telnet->frontend, logbuf); + sfree(logbuf); + } } + sfree(b); + sfree(user); } break; } } @@ -628,20 +660,29 @@ msg = dupprintf("Connecting to %s port %d", addrbuf, port); else msg = dupprintf("Failed to connect to %s: %s", addrbuf, error_msg); logevent(telnet->frontend, msg); + sfree(msg); } static int telnet_closing(Plug plug, const char *error_msg, int error_code, int calling_back) { Telnet telnet = (Telnet) plug; + /* + * We don't implement independent EOF in each direction for Telnet + * connections; as soon as we get word that the remote side has + * sent us EOF, we wind up the whole connection. + */ + if (telnet->s) { sk_close(telnet->s); telnet->s = NULL; + if (error_msg) + telnet->closed_on_socket_error = TRUE; notify_remote_exit(telnet->frontend); } if (error_msg) { logevent(telnet->frontend, error_msg); connection_fatal(telnet->frontend, "%s", error_msg); @@ -672,13 +713,12 @@ * * Also places the canonical host name into `realhost'. It must be * freed by the caller. */ static const char *telnet_init(void *frontend_handle, void **backend_handle, - Config *cfg, - char *host, int port, char **realhost, - int nodelay, int keepalive) + Conf *conf, char *host, int port, + char **realhost, int nodelay, int keepalive) { static const struct plug_function_table fn_table = { telnet_log, telnet_closing, telnet_receive, @@ -685,23 +725,26 @@ telnet_sent }; SockAddr addr; const char *err; Telnet telnet; + char *loghost; + int addressfamily; telnet = snew(struct telnet_tag); telnet->fn = &fn_table; - telnet->cfg = *cfg; /* STRUCTURE COPY */ + telnet->conf = conf_copy(conf); telnet->s = NULL; + telnet->closed_on_socket_error = FALSE; telnet->echoing = TRUE; telnet->editing = TRUE; telnet->activated = FALSE; telnet->sb_buf = NULL; telnet->sb_size = 0; telnet->frontend = frontend_handle; - telnet->term_width = telnet->cfg.width; - telnet->term_height = telnet->cfg.height; + telnet->term_width = conf_get_int(telnet->conf, CONF_width); + telnet->term_height = conf_get_int(telnet->conf, CONF_height); telnet->state = TOP_LEVEL; telnet->ldisc = NULL; telnet->pinger = NULL; *backend_handle = telnet; @@ -708,18 +751,19 @@ /* * Try to find host. */ { char *buf; + addressfamily = conf_get_int(telnet->conf, CONF_addressfamily); buf = dupprintf("Looking up host \"%s\"%s", host, - (cfg->addressfamily == ADDRTYPE_IPV4 ? " (IPv4)" : - (cfg->addressfamily == ADDRTYPE_IPV6 ? " (IPv6)" : + (addressfamily == ADDRTYPE_IPV4 ? " (IPv4)" : + (addressfamily == ADDRTYPE_IPV6 ? " (IPv6)" : ""))); logevent(telnet->frontend, buf); sfree(buf); } - addr = name_lookup(host, port, realhost, &telnet->cfg, cfg->addressfamily); + addr = name_lookup(host, port, realhost, telnet->conf, addressfamily); if ((err = sk_addr_error(addr)) != NULL) { sk_addr_free(addr); return err; } @@ -728,20 +772,20 @@ /* * Open socket. */ telnet->s = new_connection(addr, *realhost, port, 0, 1, - nodelay, keepalive, (Plug) telnet, &telnet->cfg); + nodelay, keepalive, (Plug) telnet, telnet->conf); if ((err = sk_socket_error(telnet->s)) != NULL) return err; - telnet->pinger = pinger_new(&telnet->cfg, &telnet_backend, telnet); + telnet->pinger = pinger_new(telnet->conf, &telnet_backend, telnet); /* * Initialise option states. */ - if (telnet->cfg.passive_telnet) { + if (conf_get_int(telnet->conf, CONF_passive_telnet)) { const struct Opt *const *o; for (o = opts; *o; o++) telnet->opt_states[(*o)->index] = INACTIVE; } else { @@ -766,15 +810,16 @@ update_specials_menu(telnet->frontend); /* * loghost overrides realhost, if specified. */ - if (*telnet->cfg.loghost) { + loghost = conf_get_str(telnet->conf, CONF_loghost); + if (*loghost) { char *colon; sfree(*realhost); - *realhost = dupstr(telnet->cfg.loghost); + *realhost = dupstr(loghost); colon = strrchr(*realhost, ':'); if (colon) { /* * FIXME: if we ever update this aspect of ssh.c for * IPv6 literal management, this should change in line @@ -794,22 +839,24 @@ sfree(telnet->sb_buf); if (telnet->s) sk_close(telnet->s); if (telnet->pinger) pinger_free(telnet->pinger); + conf_free(telnet->conf); sfree(telnet); } /* * Reconfigure the Telnet backend. There's no immediate action * necessary, in this backend: we just save the fresh config for * any subsequent negotiations. */ -static void telnet_reconfig(void *handle, Config *cfg) +static void telnet_reconfig(void *handle, Conf *conf) { Telnet telnet = (Telnet) handle; - pinger_reconfig(telnet->pinger, &telnet->cfg, cfg); - telnet->cfg = *cfg; /* STRUCTURE COPY */ + pinger_reconfig(telnet->pinger, telnet->conf, conf); + conf_free(telnet->conf); + telnet->conf = conf_copy(conf); } /* * Called to send data down the Telnet connection. */ @@ -1053,10 +1100,12 @@ static int telnet_exitcode(void *handle) { Telnet telnet = (Telnet) handle; if (telnet->s != NULL) return -1; /* still connected */ + else if (telnet->closed_on_socket_error) + return INT_MAX; /* a socket error counts as an unclean exit */ else /* Telnet doesn't transmit exit codes back to the client */ return 0; } Index: terminal.c ================================================================== --- terminal.c +++ terminal.c @@ -987,11 +987,11 @@ * Get the number of lines in the scrollback. */ static int sblines(Terminal *term) { int sblines = count234(term->scrollback); - if (term->cfg.erase_to_scrollback && + if (term->erase_to_scrollback && term->alt_which && term->alt_screen) { sblines += term->alt_sblines; } return sblines; } @@ -1013,11 +1013,11 @@ } else { int altlines = 0; assert(!screen); - if (term->cfg.erase_to_scrollback && + if (term->erase_to_scrollback && term->alt_which && term->alt_screen) { altlines = term->alt_sblines; } if (y < -altlines) { whichtree = term->scrollback; @@ -1063,36 +1063,36 @@ #define scrlineptr(x) (lineptr)(term,x,__LINE__,TRUE) static void term_schedule_tblink(Terminal *term); static void term_schedule_cblink(Terminal *term); -static void term_timer(void *ctx, long now) +static void term_timer(void *ctx, unsigned long now) { Terminal *term = (Terminal *)ctx; int update = FALSE; - if (term->tblink_pending && now - term->next_tblink >= 0) { + if (term->tblink_pending && now == term->next_tblink) { term->tblinker = !term->tblinker; term->tblink_pending = FALSE; term_schedule_tblink(term); update = TRUE; } - if (term->cblink_pending && now - term->next_cblink >= 0) { + if (term->cblink_pending && now == term->next_cblink) { term->cblinker = !term->cblinker; term->cblink_pending = FALSE; term_schedule_cblink(term); update = TRUE; } - if (term->in_vbell && now - term->vbell_end >= 0) { + if (term->in_vbell && now == term->vbell_end) { term->in_vbell = FALSE; update = TRUE; } if (update || - (term->window_update_pending && now - term->next_update >= 0)) + (term->window_update_pending && now == term->next_update)) term_update(term); } static void term_schedule_update(Terminal *term) { @@ -1131,11 +1131,11 @@ /* * Likewise with cursor blinks. */ static void term_schedule_cblink(Terminal *term) { - if (term->cfg.blink_cur && term->has_focus) { + if (term->blink_cur && term->has_focus) { if (!term->cblink_pending) term->next_cblink = schedule_timer(CBLINK_DELAY, term_timer, term); term->cblink_pending = TRUE; } else { term->cblinker = 1; /* reset when not in use */ @@ -1195,15 +1195,15 @@ if (term->cols != -1) { int i; for (i = 0; i < term->cols; i++) term->tabs[i] = (i % 8 == 0 ? TRUE : FALSE); } - term->alt_om = term->dec_om = term->cfg.dec_om; + term->alt_om = term->dec_om = conf_get_int(term->conf, CONF_dec_om); term->alt_ins = term->insert = FALSE; term->alt_wnext = term->wrapnext = term->save_wnext = term->alt_save_wnext = FALSE; - term->alt_wrap = term->wrap = term->cfg.wrap_mode; + term->alt_wrap = term->wrap = conf_get_int(term->conf, CONF_wrap_mode); term->alt_cset = term->cset = term->save_cset = term->alt_save_cset = 0; term->alt_utf = term->utf = term->save_utf = term->alt_save_utf = 0; term->utf_state = 0; term->alt_sco_acs = term->sco_acs = term->save_sco_acs = term->alt_save_sco_acs = 0; @@ -1214,23 +1214,26 @@ term->cursor_on = 1; term->big_cursor = 0; term->default_attr = term->save_attr = term->alt_save_attr = term->curr_attr = ATTR_DEFAULT; term->term_editing = term->term_echoing = FALSE; - term->app_cursor_keys = term->cfg.app_cursor; - term->app_keypad_keys = term->cfg.app_keypad; - term->use_bce = term->cfg.bce; - term->blink_is_real = term->cfg.blinktext; + term->app_cursor_keys = conf_get_int(term->conf, CONF_app_cursor); + term->app_keypad_keys = conf_get_int(term->conf, CONF_app_keypad); + term->use_bce = conf_get_int(term->conf, CONF_bce); + term->blink_is_real = conf_get_int(term->conf, CONF_blinktext); term->erase_char = term->basic_erase_char; term->alt_which = 0; term_print_finish(term); term->xterm_mouse = 0; + term->xterm_extended_mouse = 0; + term->urxvt_extended_mouse = 0; set_raw_mouse_mode(term->frontend, FALSE); + term->bracketed_paste = FALSE; { int i; for (i = 0; i < 256; i++) - term->wordness[i] = term->cfg.wordness[i]; + term->wordness[i] = conf_get_int_int(term->conf, CONF_wordness, i); } if (term->screen) { swap_screen(term, 1, FALSE, FALSE); erase_lots(term, FALSE, TRUE, TRUE); swap_screen(term, 0, FALSE, FALSE); @@ -1259,11 +1262,11 @@ term->window_update_pending = FALSE; ctx = get_ctx(term->frontend); if (ctx) { int need_sbar_update = term->seen_disp_event; - if (term->seen_disp_event && term->cfg.scroll_on_disp) { + if (term->seen_disp_event && term->scroll_on_disp) { term->disptop = 0; /* return to main screen */ term->seen_disp_event = 0; need_sbar_update = TRUE; } @@ -1298,11 +1301,11 @@ term->nbeeps = 0; /* * Reset the scrollback on keypress, if we're doing that. */ - if (term->cfg.scroll_on_key) { + if (term->scroll_on_key) { term->disptop = 0; /* return to main screen */ seen_disp_event(term); } } @@ -1324,18 +1327,88 @@ term->erase_char = term->basic_erase_char; if (term->use_bce) term->erase_char.attr = (term->curr_attr & (ATTR_FGMASK | ATTR_BGMASK)); } + +/* + * We copy a bunch of stuff out of the Conf structure into local + * fields in the Terminal structure, to avoid the repeated tree234 + * lookups which would be involved in fetching them from the former + * every time. + */ +void term_copy_stuff_from_conf(Terminal *term) +{ + term->ansi_colour = conf_get_int(term->conf, CONF_ansi_colour); + term->arabicshaping = conf_get_int(term->conf, CONF_arabicshaping); + term->beep = conf_get_int(term->conf, CONF_beep); + term->bellovl = conf_get_int(term->conf, CONF_bellovl); + term->bellovl_n = conf_get_int(term->conf, CONF_bellovl_n); + term->bellovl_s = conf_get_int(term->conf, CONF_bellovl_s); + term->bellovl_t = conf_get_int(term->conf, CONF_bellovl_t); + term->bidi = conf_get_int(term->conf, CONF_bidi); + term->bksp_is_delete = conf_get_int(term->conf, CONF_bksp_is_delete); + term->blink_cur = conf_get_int(term->conf, CONF_blink_cur); + term->blinktext = conf_get_int(term->conf, CONF_blinktext); + term->cjk_ambig_wide = conf_get_int(term->conf, CONF_cjk_ambig_wide); + term->conf_height = conf_get_int(term->conf, CONF_height); + term->conf_width = conf_get_int(term->conf, CONF_width); + term->crhaslf = conf_get_int(term->conf, CONF_crhaslf); + term->erase_to_scrollback = conf_get_int(term->conf, CONF_erase_to_scrollback); + term->funky_type = conf_get_int(term->conf, CONF_funky_type); + term->lfhascr = conf_get_int(term->conf, CONF_lfhascr); + term->logflush = conf_get_int(term->conf, CONF_logflush); + term->logtype = conf_get_int(term->conf, CONF_logtype); + term->mouse_override = conf_get_int(term->conf, CONF_mouse_override); + term->nethack_keypad = conf_get_int(term->conf, CONF_nethack_keypad); + term->no_alt_screen = conf_get_int(term->conf, CONF_no_alt_screen); + term->no_applic_c = conf_get_int(term->conf, CONF_no_applic_c); + term->no_applic_k = conf_get_int(term->conf, CONF_no_applic_k); + term->no_dbackspace = conf_get_int(term->conf, CONF_no_dbackspace); + term->no_mouse_rep = conf_get_int(term->conf, CONF_no_mouse_rep); + term->no_remote_charset = conf_get_int(term->conf, CONF_no_remote_charset); + term->no_remote_resize = conf_get_int(term->conf, CONF_no_remote_resize); + term->no_remote_wintitle = conf_get_int(term->conf, CONF_no_remote_wintitle); + term->rawcnp = conf_get_int(term->conf, CONF_rawcnp); + term->rect_select = conf_get_int(term->conf, CONF_rect_select); + term->remote_qtitle_action = conf_get_int(term->conf, CONF_remote_qtitle_action); + term->rxvt_homeend = conf_get_int(term->conf, CONF_rxvt_homeend); + term->scroll_on_disp = conf_get_int(term->conf, CONF_scroll_on_disp); + term->scroll_on_key = conf_get_int(term->conf, CONF_scroll_on_key); + term->xterm_256_colour = conf_get_int(term->conf, CONF_xterm_256_colour); + + /* + * Parse the control-character escapes in the configured + * answerback string. + */ + { + char *answerback = conf_get_str(term->conf, CONF_answerback); + int maxlen = strlen(answerback); + + term->answerback = snewn(maxlen, char); + term->answerbacklen = 0; + + while (*answerback) { + char *n; + char c = ctrlparse(answerback, &n); + if (n) { + term->answerback[term->answerbacklen++] = c; + answerback = n; + } else { + term->answerback[term->answerbacklen++] = *answerback++; + } + } + } +} /* * When the user reconfigures us, we need to check the forbidden- * alternate-screen config option, disable raw mouse mode if the * user has disabled mouse reporting, and abandon a print job if * the user has disabled printing. */ -void term_reconfig(Terminal *term, Config *cfg) +void term_reconfig(Terminal *term, Conf *conf) { /* * Before adopting the new config, check all those terminal * settings which control power-on defaults; and if they've * changed, we will modify the current state as well as the @@ -1343,25 +1416,32 @@ * Mode, BCE, blinking text, character classes. */ int reset_wrap, reset_decom, reset_bce, reset_tblink, reset_charclass; int i; - reset_wrap = (term->cfg.wrap_mode != cfg->wrap_mode); - reset_decom = (term->cfg.dec_om != cfg->dec_om); - reset_bce = (term->cfg.bce != cfg->bce); - reset_tblink = (term->cfg.blinktext != cfg->blinktext); + reset_wrap = (conf_get_int(term->conf, CONF_wrap_mode) != + conf_get_int(conf, CONF_wrap_mode)); + reset_decom = (conf_get_int(term->conf, CONF_dec_om) != + conf_get_int(conf, CONF_dec_om)); + reset_bce = (conf_get_int(term->conf, CONF_bce) != + conf_get_int(conf, CONF_bce)); + reset_tblink = (conf_get_int(term->conf, CONF_blinktext) != + conf_get_int(conf, CONF_blinktext)); reset_charclass = 0; - for (i = 0; i < lenof(term->cfg.wordness); i++) - if (term->cfg.wordness[i] != cfg->wordness[i]) + for (i = 0; i < 256; i++) + if (conf_get_int_int(term->conf, CONF_wordness, i) != + conf_get_int_int(conf, CONF_wordness, i)) reset_charclass = 1; /* * If the bidi or shaping settings have changed, flush the bidi * cache completely. */ - if (term->cfg.arabicshaping != cfg->arabicshaping || - term->cfg.bidi != cfg->bidi) { + if (conf_get_int(term->conf, CONF_arabicshaping) != + conf_get_int(conf, CONF_arabicshaping) || + conf_get_int(term->conf, CONF_bidi) != + conf_get_int(conf, CONF_bidi)) { for (i = 0; i < term->bidi_cache_size; i++) { sfree(term->pre_bidi_cache[i].chars); sfree(term->post_bidi_cache[i].chars); term->pre_bidi_cache[i].width = -1; term->pre_bidi_cache[i].chars = NULL; @@ -1368,43 +1448,45 @@ term->post_bidi_cache[i].width = -1; term->post_bidi_cache[i].chars = NULL; } } - term->cfg = *cfg; /* STRUCTURE COPY */ + conf_free(term->conf); + term->conf = conf_copy(conf); if (reset_wrap) - term->alt_wrap = term->wrap = term->cfg.wrap_mode; + term->alt_wrap = term->wrap = conf_get_int(term->conf, CONF_wrap_mode); if (reset_decom) - term->alt_om = term->dec_om = term->cfg.dec_om; + term->alt_om = term->dec_om = conf_get_int(term->conf, CONF_dec_om); if (reset_bce) { - term->use_bce = term->cfg.bce; + term->use_bce = conf_get_int(term->conf, CONF_bce); set_erase_char(term); } if (reset_tblink) { - term->blink_is_real = term->cfg.blinktext; + term->blink_is_real = conf_get_int(term->conf, CONF_blinktext); } if (reset_charclass) for (i = 0; i < 256; i++) - term->wordness[i] = term->cfg.wordness[i]; + term->wordness[i] = conf_get_int_int(term->conf, CONF_wordness, i); - if (term->cfg.no_alt_screen) + if (conf_get_int(term->conf, CONF_no_alt_screen)) swap_screen(term, 0, FALSE, FALSE); - if (term->cfg.no_mouse_rep) { + if (conf_get_int(term->conf, CONF_no_mouse_rep)) { term->xterm_mouse = 0; set_raw_mouse_mode(term->frontend, 0); } - if (term->cfg.no_remote_charset) { + if (conf_get_int(term->conf, CONF_no_remote_charset)) { term->cset_attr[0] = term->cset_attr[1] = CSET_ASCII; term->sco_acs = term->alt_sco_acs = 0; term->utf = 0; } - if (!*term->cfg.printer) { + if (!conf_get_str(term->conf, CONF_printer)) { term_print_finish(term); } term_schedule_tblink(term); term_schedule_cblink(term); + term_copy_stuff_from_conf(term); } /* * Clear the scrollback. */ @@ -1421,11 +1503,11 @@ } /* * Initialise the terminal. */ -Terminal *term_init(Config *mycfg, struct unicode_data *ucsdata, +Terminal *term_init(Conf *myconf, struct unicode_data *ucsdata, void *frontend) { Terminal *term; /* @@ -1433,18 +1515,17 @@ * that need it. */ term = snew(Terminal); term->frontend = frontend; term->ucsdata = ucsdata; - term->cfg = *mycfg; /* STRUCTURE COPY */ + term->conf = conf_copy(myconf); term->logctx = NULL; term->compatibility_level = TM_PUTTY; strcpy(term->id_string, "\033[?6c"); term->cblink_pending = term->tblink_pending = FALSE; term->paste_buffer = NULL; term->paste_len = 0; - term->last_paste = 0; bufchain_init(&term->inbuf); bufchain_init(&term->printer_buf); term->printing = term->only_printing = FALSE; term->print_job = NULL; term->vt52_mode = FALSE; @@ -1456,10 +1537,12 @@ term->has_focus = 1; term->repeat_off = FALSE; term->termstate = TOPLEVEL; term->selstate = NO_SELECTION; term->curstype = 0; + + term_copy_stuff_from_conf(term); term->screen = term->alt_screen = term->scrollback = NULL; term->tempsblines = 0; term->alt_sblines = 0; term->disptop = 0; @@ -1535,15 +1618,21 @@ sfree(term->wcTo); for (i = 0; i < term->bidi_cache_size; i++) { sfree(term->pre_bidi_cache[i].chars); sfree(term->post_bidi_cache[i].chars); + sfree(term->post_bidi_cache[i].forward); + sfree(term->post_bidi_cache[i].backward); } sfree(term->pre_bidi_cache); sfree(term->post_bidi_cache); + + sfree(term->tabs); expire_timer_context(term); + + conf_free(term->conf); sfree(term); } /* @@ -1625,11 +1714,12 @@ } /* Do this loop to shrink the screen if newrows < rows */ while (term->rows > newrows) { if (term->curs.y < term->rows - 1) { /* delete bottom row, unless it contains the cursor */ - sfree(delpos234(term->screen, term->rows - 1)); + line = delpos234(term->screen, term->rows - 1); + freeline(line); } else { /* push top row to scrollback */ line = delpos234(term->screen, 0); addpos234(term->scrollback, compressline(line), sblen++); freeline(line); @@ -1887,11 +1977,11 @@ * affect the scrollback buffer. */ static void scroll(Terminal *term, int topline, int botline, int lines, int sb) { termline *line; - int i, seltop; + int i, seltop, scrollwinsize; #ifdef OPTIMISE_SCROLL int olddisptop, shift; #endif /* OPTIMISE_SCROLL */ if (topline != 0 || term->alt_which != 0) @@ -1899,12 +1989,18 @@ #ifdef OPTIMISE_SCROLL olddisptop = term->disptop; shift = lines; #endif /* OPTIMISE_SCROLL */ + + scrollwinsize = botline - topline + 1; + if (lines < 0) { - while (lines < 0) { + lines = -lines; + if (lines > scrollwinsize) + lines = scrollwinsize; + while (lines-- > 0) { line = delpos234(term->screen, botline); resizeline(term, line, term->cols); for (i = 0; i < term->cols; i++) copy_termchar(line, i, &term->erase_char); line->lattr = LATTR_NORM; @@ -1922,15 +2018,15 @@ if (term->selend.y > botline) { term->selend.y = botline + 1; term->selend.x = 0; } } - - lines++; } } else { - while (lines > 0) { + if (lines > scrollwinsize) + lines = scrollwinsize; + while (lines-- > 0) { line = delpos234(term->screen, topline); #ifdef TERM_CC_DIAGS cc_check(line); #endif if (sb && term->savelines > 0) { @@ -2012,12 +2108,10 @@ term->selanchor.y = seltop; term->selanchor.x = 0; } } } - - lines--; } } #ifdef OPTIMISE_SCROLL shift += term->disptop - olddisptop; if (shift < term->rows && shift > -term->rows && shift != 0) @@ -2241,11 +2335,11 @@ /* Lines scrolled away shouldn't be brought back on if the terminal * resizes. */ if (start.y == 0 && start.x == 0 && end.x == 0 && erase_lattr) erasing_lines_from_top = 1; - if (term->cfg.erase_to_scrollback && erasing_lines_from_top) { + if (term->erase_to_scrollback && erasing_lines_from_top) { /* If it's a whole number of lines, starting at the top, and * we're fully erasing them, erase by scrolling and keep the * lines in the scrollback. */ int scrolllines = end.y; if (end.y == term->rows) { @@ -2332,17 +2426,17 @@ term->vt52_mode = !state; if (term->vt52_mode) { term->blink_is_real = FALSE; term->vt52_bold = FALSE; } else { - term->blink_is_real = term->cfg.blinktext; + term->blink_is_real = term->blinktext; } term_schedule_tblink(term); break; case 3: /* DECCOLM: 80/132 columns */ deselect(term); - if (!term->cfg.no_remote_resize) + if (!term->no_remote_resize) request_resize(term->frontend, state ? 132 : 80, term->rows); term->reset_132 = state; term->alt_t = term->marg_t = 0; term->alt_b = term->marg_b = term->rows - 1; move(term, 0, 0, 0); @@ -2385,43 +2479,52 @@ seen_disp_event(term); break; case 47: /* alternate screen */ compatibility(OTHER); deselect(term); - swap_screen(term, term->cfg.no_alt_screen ? 0 : state, FALSE, FALSE); + swap_screen(term, term->no_alt_screen ? 0 : state, FALSE, FALSE); term->disptop = 0; break; case 1000: /* xterm mouse 1 (normal) */ term->xterm_mouse = state ? 1 : 0; set_raw_mouse_mode(term->frontend, state); break; case 1002: /* xterm mouse 2 (inc. button drags) */ term->xterm_mouse = state ? 2 : 0; set_raw_mouse_mode(term->frontend, state); + break; + case 1006: /* xterm extended mouse */ + term->xterm_extended_mouse = state ? 1 : 0; + break; + case 1015: /* urxvt extended mouse */ + term->urxvt_extended_mouse = state ? 1 : 0; break; case 1047: /* alternate screen */ compatibility(OTHER); deselect(term); - swap_screen(term, term->cfg.no_alt_screen ? 0 : state, TRUE, TRUE); + swap_screen(term, term->no_alt_screen ? 0 : state, TRUE, TRUE); term->disptop = 0; break; case 1048: /* save/restore cursor */ - if (!term->cfg.no_alt_screen) + if (!term->no_alt_screen) save_cursor(term, state); if (!state) seen_disp_event(term); break; case 1049: /* cursor & alternate screen */ - if (state && !term->cfg.no_alt_screen) + if (state && !term->no_alt_screen) save_cursor(term, state); if (!state) seen_disp_event(term); compatibility(OTHER); deselect(term); - swap_screen(term, term->cfg.no_alt_screen ? 0 : state, TRUE, FALSE); - if (!state && !term->cfg.no_alt_screen) + swap_screen(term, term->no_alt_screen ? 0 : state, TRUE, FALSE); + if (!state && !term->no_alt_screen) save_cursor(term, state); term->disptop = 0; break; + case 2004: /* xterm bracketed paste */ + term->bracketed_paste = state ? TRUE : FALSE; + break; } else switch (mode) { case 4: /* IRM: set insert mode */ compatibility(VT102); term->insert = state; @@ -2452,31 +2555,31 @@ } else { term->osc_string[term->osc_strlen] = '\0'; switch (term->esc_args[0]) { case 0: case 1: - if (!term->cfg.no_remote_wintitle) + if (!term->no_remote_wintitle) set_icon(term->frontend, term->osc_string); if (term->esc_args[0] == 1) break; /* fall through: parameter 0 means set both */ case 2: case 21: - if (!term->cfg.no_remote_wintitle) + if (!term->no_remote_wintitle) set_title(term->frontend, term->osc_string); break; } } } /* * ANSI printing routines. */ -static void term_print_setup(Terminal *term) +static void term_print_setup(Terminal *term, char *printer) { bufchain_clear(&term->printer_buf); - term->print_job = printer_start_job(term->cfg.printer); + term->print_job = printer_start_job(printer); } static void term_print_flush(Terminal *term) { void *data; int len; @@ -2547,11 +2650,11 @@ /* * Optionally log the session traffic to a file. Useful for * debugging and possibly also useful for actual logging. */ - if (term->cfg.logtype == LGTYP_DEBUG && term->logctx) + if (term->logtype == LGTYP_DEBUG && term->logctx) logtraffic(term->logctx, (unsigned char) c, LGTYP_DEBUG); } else { c = unget; unget = -1; } @@ -2739,11 +2842,11 @@ if (c == '\177' && term->termstate < DO_CTRLS && has_compat(OTHER)) { if (term->curs.x && !term->wrapnext) term->curs.x--; term->wrapnext = FALSE; /* destructive backspace might be disabled */ - if (!term->cfg.no_dbackspace) { + if (!term->no_dbackspace) { check_boundary(term, term->curs.x, term->curs.y); check_boundary(term, term->curs.x+1, term->curs.y); copy_termchar(scrlineptr(term->curs.y), term->curs.x, &term->erase_char); } @@ -2759,23 +2862,12 @@ * Don't put a CR in the default string as this tends to * upset some weird software. */ compatibility(ANSIMIN); if (term->ldisc) { - char abuf[lenof(term->cfg.answerback)], *s, *d; - for (s = term->cfg.answerback, d = abuf; *s;) { - char *n; - char c = ctrlparse(s, &n); - if (n) { - *d++ = c; - s = n; - } else { - *d++ = *s++; - } - } lpage_send(term->ldisc, DEFAULT_CODEPAGE, - abuf, d - abuf, 0); + term->answerback, term->answerbacklen, 0); } break; case '\007': /* BEL: Bell */ { struct beeptime *newbeep; @@ -2798,29 +2890,29 @@ /* * Throw out any beeps that happened more than * t seconds ago. */ while (term->beephead && - term->beephead->ticks < ticks - term->cfg.bellovl_t) { + term->beephead->ticks < ticks - term->bellovl_t) { struct beeptime *tmp = term->beephead; term->beephead = tmp->next; sfree(tmp); if (!term->beephead) term->beeptail = NULL; term->nbeeps--; } - if (term->cfg.bellovl && term->beep_overloaded && - ticks - term->lastbeep >= (unsigned)term->cfg.bellovl_s) { + if (term->bellovl && term->beep_overloaded && + ticks - term->lastbeep >= (unsigned)term->bellovl_s) { /* * If we're currently overloaded and the * last beep was more than s seconds ago, * leave overload mode. */ term->beep_overloaded = FALSE; - } else if (term->cfg.bellovl && !term->beep_overloaded && - term->nbeeps >= term->cfg.bellovl_n) { + } else if (term->bellovl && !term->beep_overloaded && + term->nbeeps >= term->bellovl_n) { /* * Now, if we have n or more beeps * remaining in the queue, go into overload * mode. */ @@ -2829,14 +2921,14 @@ term->lastbeep = ticks; /* * Perform an actual beep if we're not overloaded. */ - if (!term->cfg.bellovl || !term->beep_overloaded) { - do_beep(term->frontend, term->cfg.beep); + if (!term->bellovl || !term->beep_overloaded) { + do_beep(term->frontend, term->beep); - if (term->cfg.beep == BELL_VISUAL) { + if (term->beep == BELL_VISUAL) { term_schedule_vbell(term, FALSE, 0); } } seen_disp_event(term); } @@ -2872,18 +2964,17 @@ break; case '\015': /* CR: Carriage return */ term->curs.x = 0; term->wrapnext = FALSE; seen_disp_event(term); - term->paste_hold = 0; - - if (term->cfg.crhaslf) { - if (term->curs.y == term->marg_b) - scroll(term, term->marg_t, term->marg_b, 1, TRUE); - else if (term->curs.y < term->rows - 1) - term->curs.y++; - } + + if (term->crhaslf) { + if (term->curs.y == term->marg_b) + scroll(term, term->marg_t, term->marg_b, 1, TRUE); + else if (term->curs.y < term->rows - 1) + term->curs.y++; + } if (term->logctx) logtraffic(term->logctx, (unsigned char) c, LGTYP_ASCII); break; case '\014': /* FF: Form feed */ if (has_compat(SCOANSI)) { @@ -2899,15 +2990,14 @@ case '\012': /* LF: Line feed */ if (term->curs.y == term->marg_b) scroll(term, term->marg_t, term->marg_b, 1, TRUE); else if (term->curs.y < term->rows - 1) term->curs.y++; - if (term->cfg.lfhascr) + if (term->lfhascr) term->curs.x = 0; term->wrapnext = FALSE; seen_disp_event(term); - term->paste_hold = 0; if (term->logctx) logtraffic(term->logctx, (unsigned char) c, LGTYP_ASCII); break; case '\t': /* HT: Character tabulation */ { @@ -2941,13 +3031,13 @@ termline *cline = scrlineptr(term->curs.y); int width = 0; if (DIRECT_CHAR(c)) width = 1; if (!width) - width = (term->cfg.cjk_ambig_wide ? - mk_wcwidth_cjk((wchar_t) c) : - mk_wcwidth((wchar_t) c)); + width = (term->cjk_ambig_wide ? + mk_wcwidth_cjk((unsigned int) c) : + mk_wcwidth((unsigned int) c)); if (term->wrapnext && term->wrap && width > 0) { cline->lattr |= LATTR_WRAPPED; if (term->curs.y == term->marg_b) scroll(term, term->marg_t, term->marg_b, 1, TRUE); @@ -3164,11 +3254,11 @@ compatibility(VT100); power_on(term, TRUE); if (term->ldisc) /* cause ldisc to notice changes */ ldisc_send(term->ldisc, NULL, 0, 0); if (term->reset_132) { - if (!term->cfg.no_remote_resize) + if (!term->no_remote_resize) request_resize(term->frontend, 80, term->rows); term->reset_132 = 0; } term->disptop = 0; seen_disp_event(term); @@ -3229,59 +3319,59 @@ } break; /* GZD4: G0 designate 94-set */ case ANSI('A', '('): compatibility(VT100); - if (!term->cfg.no_remote_charset) + if (!term->no_remote_charset) term->cset_attr[0] = CSET_GBCHR; break; case ANSI('B', '('): compatibility(VT100); - if (!term->cfg.no_remote_charset) + if (!term->no_remote_charset) term->cset_attr[0] = CSET_ASCII; break; case ANSI('0', '('): compatibility(VT100); - if (!term->cfg.no_remote_charset) + if (!term->no_remote_charset) term->cset_attr[0] = CSET_LINEDRW; break; case ANSI('U', '('): compatibility(OTHER); - if (!term->cfg.no_remote_charset) + if (!term->no_remote_charset) term->cset_attr[0] = CSET_SCOACS; break; /* G1D4: G1-designate 94-set */ case ANSI('A', ')'): compatibility(VT100); - if (!term->cfg.no_remote_charset) + if (!term->no_remote_charset) term->cset_attr[1] = CSET_GBCHR; break; case ANSI('B', ')'): compatibility(VT100); - if (!term->cfg.no_remote_charset) + if (!term->no_remote_charset) term->cset_attr[1] = CSET_ASCII; break; case ANSI('0', ')'): compatibility(VT100); - if (!term->cfg.no_remote_charset) + if (!term->no_remote_charset) term->cset_attr[1] = CSET_LINEDRW; break; case ANSI('U', ')'): compatibility(OTHER); - if (!term->cfg.no_remote_charset) + if (!term->no_remote_charset) term->cset_attr[1] = CSET_SCOACS; break; /* DOCS: Designate other coding system */ case ANSI('8', '%'): /* Old Linux code */ case ANSI('G', '%'): compatibility(OTHER); - if (!term->cfg.no_remote_charset) + if (!term->no_remote_charset) term->utf = 1; break; case ANSI('@', '%'): compatibility(OTHER); - if (!term->cfg.no_remote_charset) + if (!term->no_remote_charset) term->utf = 0; break; } break; case SEEN_CSI: @@ -3461,16 +3551,19 @@ break; case 'i': /* MC: Media copy */ case ANSI_QUE('i'): compatibility(VT100); { + char *printer; if (term->esc_nargs != 1) break; - if (term->esc_args[0] == 5 && *term->cfg.printer) { + if (term->esc_args[0] == 5 && + (printer = conf_get_str(term->conf, + CONF_printer))[0]) { term->printing = TRUE; term->only_printing = !term->esc_query; term->print_state = 0; - term_print_setup(term); + term_print_setup(term, printer); } else if (term->esc_args[0] == 4 && term->printing) { term_print_finish(term); } } @@ -3589,19 +3682,19 @@ case 7: /* enable reverse video */ term->curr_attr |= ATTR_REVERSE; break; case 10: /* SCO acs off */ compatibility(SCOANSI); - if (term->cfg.no_remote_charset) break; + if (term->no_remote_charset) break; term->sco_acs = 0; break; case 11: /* SCO acs on */ compatibility(SCOANSI); - if (term->cfg.no_remote_charset) break; + if (term->no_remote_charset) break; term->sco_acs = 1; break; case 12: /* SCO acs on, |0x80 */ compatibility(SCOANSI); - if (term->cfg.no_remote_charset) break; + if (term->no_remote_charset) break; term->sco_acs = 2; break; case 22: /* disable bold */ compatibility2(OTHER, VT220); term->curr_attr &= ~ATTR_BOLD; break; @@ -3720,11 +3813,11 @@ */ if (term->esc_nargs <= 1 && (term->esc_args[0] < 1 || term->esc_args[0] >= 24)) { compatibility(VT340TEXT); - if (!term->cfg.no_remote_resize) + if (!term->no_remote_resize) request_resize(term->frontend, term->cols, def(term->esc_args[0], 24)); deselect(term); } else if (term->esc_nargs >= 1 && term->esc_args[0] >= 1 && @@ -3740,11 +3833,11 @@ case 2: set_iconic(term->frontend, TRUE); break; case 3: if (term->esc_nargs >= 3) { - if (!term->cfg.no_remote_resize) + if (!term->no_remote_resize) move_window(term->frontend, def(term->esc_args[1], 0), def(term->esc_args[2], 0)); } break; @@ -3765,14 +3858,14 @@ case 7: refresh_window(term->frontend); break; case 8: if (term->esc_nargs >= 3) { - if (!term->cfg.no_remote_resize) + if (!term->no_remote_resize) request_resize(term->frontend, - def(term->esc_args[2], term->cfg.width), - def(term->esc_args[1], term->cfg.height)); + def(term->esc_args[2], term->conf_width), + def(term->esc_args[1], term->conf_height)); } break; case 9: if (term->esc_nargs >= 2) set_zoomed(term->frontend, @@ -3823,12 +3916,12 @@ * what they would like it to do. */ break; case 20: if (term->ldisc && - term->cfg.remote_qtitle_action != TITLE_NONE) { - if(term->cfg.remote_qtitle_action == TITLE_REAL) + term->remote_qtitle_action != TITLE_NONE) { + if(term->remote_qtitle_action == TITLE_REAL) p = get_window_title(term->frontend, TRUE); else p = EMPTY_WINDOW_TITLE; len = strlen(p); ldisc_send(term->ldisc, "\033]L", 3, 0); @@ -3836,12 +3929,12 @@ ldisc_send(term->ldisc, "\033\\", 2, 0); } break; case 21: if (term->ldisc && - term->cfg.remote_qtitle_action != TITLE_NONE) { - if(term->cfg.remote_qtitle_action == TITLE_REAL) + term->remote_qtitle_action != TITLE_NONE) { + if(term->remote_qtitle_action == TITLE_REAL) p = get_window_title(term->frontend, FALSE); else p = EMPTY_WINDOW_TITLE; len = strlen(p); ldisc_send(term->ldisc, "\033]l", 3, 0); @@ -3873,14 +3966,14 @@ * support any size in reasonable range * (24..49 AIUI) with no default specified. */ compatibility(VT420); if (term->esc_nargs == 1 && term->esc_args[0] > 0) { - if (!term->cfg.no_remote_resize) + if (!term->no_remote_resize) request_resize(term->frontend, term->cols, def(term->esc_args[0], - term->cfg.height)); + term->conf_height)); deselect(term); } break; case ANSI('|', '$'): /* DECSCPP */ /* @@ -3888,14 +3981,15 @@ * Docs imply range is only 80 or 132, but * I'll allow any. */ compatibility(VT340TEXT); if (term->esc_nargs <= 1) { - if (!term->cfg.no_remote_resize) + if (!term->no_remote_resize) request_resize(term->frontend, def(term->esc_args[0], - term->cfg.width), term->rows); + term->conf_width), + term->rows); deselect(term); } break; case 'X': /* ECH: write N spaces w/o moving cursor */ /* XXX VTTEST says this is vt220, vt510 manual @@ -4093,11 +4187,11 @@ #if 0 /* Is this a good idea ? * Well we should do a soft reset at this point ... */ if (!has_compat(VT420) && has_compat(VT100)) { - if (!term->cfg.no_remote_resize) { + if (!term->no_remote_resize) { if (term->reset_132) request_resize(132, 24); else request_resize(80, 24); } @@ -4328,11 +4422,11 @@ /* XXX This should switch to VT100 mode not current or default * VT mode. But this will only have effect in a VT220+ * emulation. */ term->vt52_mode = FALSE; - term->blink_is_real = term->cfg.blinktext; + term->blink_is_real = term->blinktext; term_schedule_tblink(term); break; #if 0 case '^': /* XXX Enter auto print mode */ @@ -4472,11 +4566,11 @@ check_selection(term, term->curs, cursplus); } } term_print_flush(term); - if (term->cfg.logflush) + if (term->logflush) logflush(term->logctx); } /* * To prevent having to run the reasonably tricky bidi algorithm @@ -4575,11 +4669,11 @@ { termchar *lchars; int it; /* Do Arabic shaping and bidi. */ - if(!term->cfg.bidi || !term->cfg.arabicshaping) { + if(!term->bidi || !term->arabicshaping) { if (!term_bidi_cache_hit(term, scr_y, ldata->chars, term->cols)) { if (term->wcFromTo_size < term->cols) { term->wcFromTo_size = term->cols; @@ -4593,11 +4687,11 @@ { unsigned long uc = (ldata->chars[it].chr); switch (uc & CSET_MASK) { case CSET_LINEDRW: - if (!term->cfg.rawcnp) { + if (!term->rawcnp) { uc = term->ucsdata->unitab_xterm[uc & 0xFF]; break; } case CSET_ASCII: uc = term->ucsdata->unitab_line[uc & 0xFF]; @@ -4614,23 +4708,23 @@ uc = term->ucsdata->unitab_oemcp[uc & 0xFF]; break; } term->wcFrom[it].origwc = term->wcFrom[it].wc = - (wchar_t)uc; + (unsigned int)uc; term->wcFrom[it].index = it; } - if(!term->cfg.bidi) + if(!term->bidi) do_bidi(term->wcFrom, term->cols); /* this is saved iff done from inside the shaping */ - if(!term->cfg.bidi && term->cfg.arabicshaping) + if(!term->bidi && term->arabicshaping) for(it=0; itcols; it++) term->wcTo[it] = term->wcFrom[it]; - if(!term->cfg.arabicshaping) + if(!term->arabicshaping) do_shape(term->wcFrom, term->wcTo, term->cols); if (term->ltemp_size < ldata->size) { term->ltemp_size = ldata->size; term->ltemp = sresize(term->ltemp, term->ltemp_size, @@ -4688,18 +4782,18 @@ rv = (!term->rvideo ^ !term->in_vbell ? ATTR_REVERSE : 0); /* Depends on: * screen array, disptop, scrtop, * selection, rv, - * cfg.blinkpc, blink_is_real, tblinker, - * curs.y, curs.x, cblinker, cfg.blink_cur, cursor_on, has_focus, wrapnext + * blinkpc, blink_is_real, tblinker, + * curs.y, curs.x, cblinker, blink_cur, cursor_on, has_focus, wrapnext */ /* Has the cursor position or type changed ? */ if (term->cursor_on) { if (term->has_focus) { - if (term->cblinker || !term->cfg.blink_cur) + if (term->cblinker || !term->blink_cur) cursor = TATTR_ACTCURS; else cursor = 0; } else cursor = TATTR_PASCURS; @@ -4803,15 +4897,15 @@ scrpos.x = backward ? backward[j] : j; tchar = d->chr; tattr = d->attr; - if (!term->cfg.ansi_colour) + if (!term->ansi_colour) tattr = (tattr & ~(ATTR_FGMASK | ATTR_BGMASK)) | ATTR_DEFFG | ATTR_DEFBG; - if (!term->cfg.xterm_256_colour) { + if (!term->xterm_256_colour) { int colour; colour = (tattr & ATTR_FGMASK) >> ATTR_FGSHIFT; if (colour >= 16 && colour < 256) tattr = (tattr &~ ATTR_FGMASK) | ATTR_DEFFG; colour = (tattr & ATTR_BGMASK) >> ATTR_BGSHIFT; @@ -4937,15 +5031,17 @@ if ((term->disptext[i]->chars[j].attr ^ tattr) & ATTR_WIDE) dirty_line = TRUE; break_run = ((tattr ^ attr) & term->attr_mask) != 0; +#ifdef USES_VTLINE_HACK /* Special hack for VT100 Linedraw glyphs */ if ((tchar >= 0x23BA && tchar <= 0x23BD) || (j > 0 && (newline[j-1].chr >= 0x23BA && newline[j-1].chr <= 0x23BD))) break_run = TRUE; +#endif /* * Separate out sequences of characters that have the * same CSET, if that CSET is a magic one. */ @@ -4989,14 +5085,21 @@ d, tchar, tattr)) { do_copy = TRUE; dirty_run = TRUE; } - if (ccount >= chlen) { + if (ccount+2 > chlen) { chlen = ccount + 256; ch = sresize(ch, chlen, wchar_t); } + +#ifdef PLATFORM_IS_UTF16 + if (tchar > 0x10000 && tchar < 0x110000) { + ch[ccount++] = (wchar_t) HIGH_SURROGATE_OF(tchar); + ch[ccount++] = (wchar_t) LOW_SURROGATE_OF(tchar); + } else +#endif /* PLATFORM_IS_UTF16 */ ch[ccount++] = (wchar_t) tchar; if (d->cc_next) { termchar *dd = d; @@ -5016,14 +5119,21 @@ case CSET_SCOACS: schar = term->ucsdata->unitab_scoacs[schar&0xFF]; break; } - if (ccount >= chlen) { + if (ccount+2 > chlen) { chlen = ccount + 256; ch = sresize(ch, chlen, wchar_t); } + +#ifdef PLATFORM_IS_UTF16 + if (schar > 0x10000 && schar < 0x110000) { + ch[ccount++] = (wchar_t) HIGH_SURROGATE_OF(schar); + ch[ccount++] = (wchar_t) LOW_SURROGATE_OF(schar); + } else +#endif /* PLATFORM_IS_UTF16 */ ch[ccount++] = (wchar_t) schar; } attr |= TATTR_COMBINING; } @@ -5265,11 +5375,11 @@ int uc = ldata->chars[x].chr; attr = ldata->chars[x].attr; switch (uc & CSET_MASK) { case CSET_LINEDRW: - if (!term->cfg.rawcnp) { + if (!term->rawcnp) { uc = term->ucsdata->unitab_xterm[uc & 0xFF]; break; } case CSET_ASCII: uc = term->ucsdata->unitab_line[uc & 0xFF]; @@ -5519,11 +5629,12 @@ if (wordtype(term, UCSGET(ldata->chars, p.x+1)) == wvalue) p.x++; else break; } else { - if (ldata->lattr & LATTR_WRAPPED) { + if (p.y+1 < term->rows && + (ldata->lattr & LATTR_WRAPPED)) { termline *ldata2; ldata2 = lineptr(p.y+1); if (wordtype(term, UCSGET(ldata2->chars, 0)) == wvalue) { p.x = 0; @@ -5589,10 +5700,37 @@ decpos(term->selend); term->selend = sel_spread_half(term, term->selend, +1); incpos(term->selend); } } + +static void term_paste_callback(void *vterm) +{ + Terminal *term = (Terminal *)vterm; + + if (term->paste_len == 0) + return; + + while (term->paste_pos < term->paste_len) { + int n = 0; + while (n + term->paste_pos < term->paste_len) { + if (term->paste_buffer[term->paste_pos + n++] == '\015') + break; + } + if (term->ldisc) + luni_send(term->ldisc, term->paste_buffer + term->paste_pos, n, 0); + term->paste_pos += n; + + if (term->paste_pos < term->paste_len) { + queue_toplevel_callback(term_paste_callback, term); + return; + } + } + sfree(term->paste_buffer); + term->paste_buffer = NULL; + term->paste_len = 0; +} void term_do_paste(Terminal *term) { wchar_t *data; int len; @@ -5603,12 +5741,17 @@ term_seen_key_event(term); /* pasted data counts */ if (term->paste_buffer) sfree(term->paste_buffer); - term->paste_pos = term->paste_hold = term->paste_len = 0; - term->paste_buffer = snewn(len, wchar_t); + term->paste_pos = term->paste_len = 0; + term->paste_buffer = snewn(len + 12, wchar_t); + + if (term->bracketed_paste) { + memcpy(term->paste_buffer, L"\033[200~", 6 * sizeof(wchar_t)); + term->paste_len += 6; + } p = q = data; while (p < data + len) { while (p < data + len && !(p <= data + len - sel_nl_sz && @@ -5627,32 +5770,40 @@ term->paste_buffer[term->paste_len++] = '\015'; p += sel_nl_sz; } q = p; } + + if (term->bracketed_paste) { + memcpy(term->paste_buffer + term->paste_len, + L"\033[201~", 6 * sizeof(wchar_t)); + term->paste_len += 6; + } /* Assume a small paste will be OK in one go. */ if (term->paste_len < 256) { if (term->ldisc) luni_send(term->ldisc, term->paste_buffer, term->paste_len, 0); if (term->paste_buffer) sfree(term->paste_buffer); term->paste_buffer = 0; - term->paste_pos = term->paste_hold = term->paste_len = 0; + term->paste_pos = term->paste_len = 0; } } get_clip(term->frontend, NULL, NULL); + + queue_toplevel_callback(term_paste_callback, term); } void term_mouse(Terminal *term, Mouse_Button braw, Mouse_Button bcooked, Mouse_Action a, int x, int y, int shift, int ctrl, int alt) { pos selpoint; termline *ldata; int raw_mouse = (term->xterm_mouse && - !term->cfg.no_mouse_rep && - !(term->cfg.mouse_override && shift)); + !term->no_mouse_rep && + !(term->mouse_override && shift)); int default_seltype; if (y < 0) { y = 0; if (a == MA_DRAG && !raw_mouse) @@ -5699,29 +5850,30 @@ * presses. */ if (raw_mouse && (term->selstate != ABOUT_TO) && (term->selstate != DRAGGING)) { int encstate = 0, r, c; - char abuf[16]; + char abuf[32]; + int len = 0; if (term->ldisc) { switch (braw) { case MBT_LEFT: - encstate = 0x20; /* left button down */ + encstate = 0x00; /* left button down */ break; case MBT_MIDDLE: - encstate = 0x21; + encstate = 0x01; break; case MBT_RIGHT: - encstate = 0x22; + encstate = 0x02; break; case MBT_WHEEL_UP: - encstate = 0x60; + encstate = 0x40; break; case MBT_WHEEL_DOWN: - encstate = 0x61; + encstate = 0x41; break; default: break; /* placate gcc warning about enum use */ } switch (a) { case MA_DRAG: @@ -5728,11 +5880,13 @@ if (term->xterm_mouse == 1) return; encstate += 0x20; break; case MA_RELEASE: - encstate = 0x23; + /* If multiple extensions are enabled, the xterm 1006 is used, so it's okay to check for only that */ + if (!term->xterm_extended_mouse) + encstate = 0x03; term->mouse_is_down = 0; break; case MA_CLICK: if (term->mouse_is_down == braw) return; @@ -5742,24 +5896,31 @@ } if (shift) encstate += 0x04; if (ctrl) encstate += 0x10; - r = y + 33; - c = x + 33; + r = y + 1; + c = x + 1; - sprintf(abuf, "\033[M%c%c%c", encstate, c, r); - ldisc_send(term->ldisc, abuf, 6, 0); + /* Check the extensions in decreasing order of preference. Encoding the release event above assumes that 1006 comes first. */ + if (term->xterm_extended_mouse) { + len = sprintf(abuf, "\033[<%d;%d;%d%c", encstate, c, r, a == MA_RELEASE ? 'm' : 'M'); + } else if (term->urxvt_extended_mouse) { + len = sprintf(abuf, "\033[%d;%d;%dM", encstate + 32, c, r); + } else if (c <= 223 && r <= 223) { + len = sprintf(abuf, "\033[M%c%c%c", encstate + 32, c + 32, r + 32); + } + ldisc_send(term->ldisc, abuf, len, 0); } return; } /* * Set the selection type (rectangular or normal) at the start * of a selection attempt, from the state of Alt. */ - if (!alt ^ !term->cfg.rect_select) + if (!alt ^ !term->rect_select) default_seltype = RECTANGULAR; else default_seltype = LEXICOGRAPHIC; if (term->selstate == NO_SELECTION) { @@ -5865,10 +6026,17 @@ #endif )) { request_paste(term->frontend); } + /* + * Since terminal output is suppressed during drag-selects, we + * should make sure to write any pending output if one has just + * finished. + */ + if (term->selstate != DRAGGING) + term_out(term); term_update(term); } int format_arrow_key(char *buf, Terminal *term, int xkey, int ctrl) { @@ -5875,11 +6043,11 @@ char *p = buf; if (term->vt52_mode) p += sprintf((char *) p, "\x1B%c", xkey); else { - int app_flg = (term->app_cursor_keys && !term->cfg.no_applic_c); + int app_flg = (term->app_cursor_keys && !term->no_applic_c); #if 0 /* * RDB: VT100 & VT102 manuals both state the app cursor * keys only work if the app keypad is on. * @@ -5904,482 +6072,14 @@ } return p - buf; } -void term_key(Terminal *term, Key_Sym keysym, wchar_t *text, size_t tlen, - unsigned int modifiers, unsigned int flags) -{ - char output[10]; - char *p = output; - int prependesc = FALSE; -#if 0 - int i; - - fprintf(stderr, "keysym = %d, %d chars:", keysym, tlen); - for (i = 0; i < tlen; i++) - fprintf(stderr, " %04x", (unsigned)text[i]); - fprintf(stderr, "\n"); -#endif - - /* XXX Num Lock */ - if ((flags & PKF_REPEAT) && term->repeat_off) - return; - - /* Currently, Meta always just prefixes everything with ESC. */ - if (modifiers & PKM_META) - prependesc = TRUE; - modifiers &= ~PKM_META; - - /* - * Alt is only used for Alt+keypad, which isn't supported yet, so - * ignore it. - */ - modifiers &= ~PKM_ALT; - - /* Standard local function keys */ - switch (modifiers & (PKM_SHIFT | PKM_CONTROL)) { - case PKM_SHIFT: - if (keysym == PK_PAGEUP) - /* scroll up one page */; - if (keysym == PK_PAGEDOWN) - /* scroll down on page */; - if (keysym == PK_INSERT) - term_do_paste(term); - break; - case PKM_CONTROL: - if (keysym == PK_PAGEUP) - /* scroll up one line */; - if (keysym == PK_PAGEDOWN) - /* scroll down one line */; - /* Control-Numlock for app-keypad mode switch */ - if (keysym == PK_PF1) - term->app_keypad_keys ^= 1; - break; - } - - if (modifiers & PKM_ALT) { - /* Alt+F4 (close) */ - /* Alt+Return (full screen) */ - /* Alt+Space (system menu) */ - } - - if (keysym == PK_NULL && (modifiers & PKM_CONTROL) && tlen == 1 && - text[0] >= 0x20 && text[0] <= 0x7e) { - /* ASCII chars + Control */ - if ((text[0] >= 0x40 && text[0] <= 0x5f) || - (text[0] >= 0x61 && text[0] <= 0x7a)) - text[0] &= 0x1f; - else { - /* - * Control-2 should return ^@ (0x00), Control-6 should return - * ^^ (0x1E), and Control-Minus should return ^_ (0x1F). Since - * the DOS keyboard handling did it, and we have nothing better - * to do with the key combo in question, we'll also map - * Control-Backquote to ^\ (0x1C). - */ - switch (text[0]) { - case ' ': text[0] = 0x00; break; - case '-': text[0] = 0x1f; break; - case '/': text[0] = 0x1f; break; - case '2': text[0] = 0x00; break; - case '3': text[0] = 0x1b; break; - case '4': text[0] = 0x1c; break; - case '5': text[0] = 0x1d; break; - case '6': text[0] = 0x1e; break; - case '7': text[0] = 0x1f; break; - case '8': text[0] = 0x7f; break; - case '`': text[0] = 0x1c; break; - } - } - } - - /* Nethack keypad */ - if (term->cfg.nethack_keypad) { - char c = 0; - switch (keysym) { - case PK_KP1: c = 'b'; break; - case PK_KP2: c = 'j'; break; - case PK_KP3: c = 'n'; break; - case PK_KP4: c = 'h'; break; - case PK_KP5: c = '.'; break; - case PK_KP6: c = 'l'; break; - case PK_KP7: c = 'y'; break; - case PK_KP8: c = 'k'; break; - case PK_KP9: c = 'u'; break; - default: break; /* else gcc warns `enum value not used' */ - } - if (c != 0) { - if (c != '.') { - if (modifiers & PKM_CONTROL) - c &= 0x1f; - else if (modifiers & PKM_SHIFT) - c = toupper((unsigned char)c); - } - *p++ = c; - goto done; - } - } - - /* Numeric Keypad */ - if (PK_ISKEYPAD(keysym)) { - int xkey = 0; - - /* - * In VT400 mode, PFn always emits an escape sequence. In - * Linux and tilde modes, this only happens in app keypad mode. - */ - if (term->cfg.funky_type == FUNKY_VT400 || - ((term->cfg.funky_type == FUNKY_LINUX || - term->cfg.funky_type == FUNKY_TILDE) && - term->app_keypad_keys && !term->cfg.no_applic_k)) { - switch (keysym) { - case PK_PF1: xkey = 'P'; break; - case PK_PF2: xkey = 'Q'; break; - case PK_PF3: xkey = 'R'; break; - case PK_PF4: xkey = 'S'; break; - default: break; /* else gcc warns `enum value not used' */ - } - } - if (term->app_keypad_keys && !term->cfg.no_applic_k) { - switch (keysym) { - case PK_KP0: xkey = 'p'; break; - case PK_KP1: xkey = 'q'; break; - case PK_KP2: xkey = 'r'; break; - case PK_KP3: xkey = 's'; break; - case PK_KP4: xkey = 't'; break; - case PK_KP5: xkey = 'u'; break; - case PK_KP6: xkey = 'v'; break; - case PK_KP7: xkey = 'w'; break; - case PK_KP8: xkey = 'x'; break; - case PK_KP9: xkey = 'y'; break; - case PK_KPDECIMAL: xkey = 'n'; break; - case PK_KPENTER: xkey = 'M'; break; - default: break; /* else gcc warns `enum value not used' */ - } - if (term->cfg.funky_type == FUNKY_XTERM && tlen > 0) { - /* - * xterm can't see the layout of the keypad, so it has - * to rely on the X keysyms returned by the keys. - * Hence, we look at the strings here, not the PuTTY - * keysyms (which describe the layout). - */ - switch (text[0]) { - case '+': - if (modifiers & PKM_SHIFT) - xkey = 'l'; - else - xkey = 'k'; - break; - case '/': xkey = 'o'; break; - case '*': xkey = 'j'; break; - case '-': xkey = 'm'; break; - } - } else { - /* - * In all other modes, we try to retain the layout of - * the DEC keypad in application mode. - */ - switch (keysym) { - case PK_KPBIGPLUS: - /* This key covers the '-' and ',' keys on a VT220 */ - if (modifiers & PKM_SHIFT) - xkey = 'm'; /* VT220 '-' */ - else - xkey = 'l'; /* VT220 ',' */ - break; - case PK_KPMINUS: xkey = 'm'; break; - case PK_KPCOMMA: xkey = 'l'; break; - default: break; /* else gcc warns `enum value not used' */ - } - } - } - if (xkey) { - if (term->vt52_mode) { - if (xkey >= 'P' && xkey <= 'S') - p += sprintf((char *) p, "\x1B%c", xkey); - else - p += sprintf((char *) p, "\x1B?%c", xkey); - } else - p += sprintf((char *) p, "\x1BO%c", xkey); - goto done; - } - /* Not in application mode -- treat the number pad as arrow keys? */ - if ((flags & PKF_NUMLOCK) == 0) { - switch (keysym) { - case PK_KP0: keysym = PK_INSERT; break; - case PK_KP1: keysym = PK_END; break; - case PK_KP2: keysym = PK_DOWN; break; - case PK_KP3: keysym = PK_PAGEDOWN; break; - case PK_KP4: keysym = PK_LEFT; break; - case PK_KP5: keysym = PK_REST; break; - case PK_KP6: keysym = PK_RIGHT; break; - case PK_KP7: keysym = PK_HOME; break; - case PK_KP8: keysym = PK_UP; break; - case PK_KP9: keysym = PK_PAGEUP; break; - default: break; /* else gcc warns `enum value not used' */ - } - } - } - - /* Miscellaneous keys */ - switch (keysym) { - case PK_ESCAPE: - *p++ = 0x1b; - goto done; - case PK_BACKSPACE: - if (modifiers == 0) - *p++ = (term->cfg.bksp_is_delete ? 0x7F : 0x08); - else if (modifiers == PKM_SHIFT) - /* We do the opposite of what is configured */ - *p++ = (term->cfg.bksp_is_delete ? 0x08 : 0x7F); - else break; - goto done; - case PK_TAB: - if (modifiers == 0) - *p++ = 0x09; - else if (modifiers == PKM_SHIFT) - *p++ = 0x1B, *p++ = '[', *p++ = 'Z'; - else break; - goto done; - /* XXX window.c has ctrl+shift+space sending 0xa0 */ - case PK_PAUSE: - if (modifiers == PKM_CONTROL) - *p++ = 26; - else break; - goto done; - case PK_RETURN: - case PK_KPENTER: /* Odd keypad modes handled above */ - if (modifiers == 0) { - *p++ = 0x0d; - if (term->cr_lf_return) - *p++ = 0x0a; - goto done; - } - default: break; /* else gcc warns `enum value not used' */ - } - - /* SCO function keys and editing keys */ - if (term->cfg.funky_type == FUNKY_SCO) { - if (PK_ISFKEY(keysym) && keysym <= PK_F12) { - static char const codes[] = - "MNOPQRSTUVWX" "YZabcdefghij" "klmnopqrstuv" "wxyz@[\\]^_`{"; - int index = keysym - PK_F1; - - if (modifiers & PKM_SHIFT) index += 12; - if (modifiers & PKM_CONTROL) index += 24; - p += sprintf((char *) p, "\x1B[%c", codes[index]); - goto done; - } - if (PK_ISEDITING(keysym)) { - int xkey = 0; - - switch (keysym) { - case PK_DELETE: *p++ = 0x7f; goto done; - case PK_HOME: xkey = 'H'; break; - case PK_INSERT: xkey = 'L'; break; - case PK_END: xkey = 'F'; break; - case PK_PAGEUP: xkey = 'I'; break; - case PK_PAGEDOWN: xkey = 'G'; break; - default: break; /* else gcc warns `enum value not used' */ - } - p += sprintf((char *) p, "\x1B[%c", xkey); - } - } - - if (PK_ISEDITING(keysym) && (modifiers & PKM_SHIFT) == 0) { - int code; - - if (term->cfg.funky_type == FUNKY_XTERM) { - /* Xterm shuffles these keys, apparently. */ - switch (keysym) { - case PK_HOME: keysym = PK_INSERT; break; - case PK_INSERT: keysym = PK_HOME; break; - case PK_DELETE: keysym = PK_END; break; - case PK_END: keysym = PK_PAGEUP; break; - case PK_PAGEUP: keysym = PK_DELETE; break; - case PK_PAGEDOWN: keysym = PK_PAGEDOWN; break; - default: break; /* else gcc warns `enum value not used' */ - } - } - - /* RXVT Home/End */ - if (term->cfg.rxvt_homeend && - (keysym == PK_HOME || keysym == PK_END)) { - p += sprintf((char *) p, keysym == PK_HOME ? "\x1B[H" : "\x1BOw"); - goto done; - } - - if (term->vt52_mode) { - int xkey; - - /* - * A real VT52 doesn't have these, and a VT220 doesn't - * send anything for them in VT52 mode. - */ - switch (keysym) { - case PK_HOME: xkey = 'H'; break; - case PK_INSERT: xkey = 'L'; break; - case PK_DELETE: xkey = 'M'; break; - case PK_END: xkey = 'E'; break; - case PK_PAGEUP: xkey = 'I'; break; - case PK_PAGEDOWN: xkey = 'G'; break; - default: xkey=0; break; /* else gcc warns `enum value not used'*/ - } - p += sprintf((char *) p, "\x1B%c", xkey); - goto done; - } - - switch (keysym) { - case PK_HOME: code = 1; break; - case PK_INSERT: code = 2; break; - case PK_DELETE: code = 3; break; - case PK_END: code = 4; break; - case PK_PAGEUP: code = 5; break; - case PK_PAGEDOWN: code = 6; break; - default: code = 0; break; /* else gcc warns `enum value not used' */ - } - p += sprintf((char *) p, "\x1B[%d~", code); - goto done; - } - - if (PK_ISFKEY(keysym)) { - /* Map Shift+F1-F10 to F11-F20 */ - if (keysym >= PK_F1 && keysym <= PK_F10 && (modifiers & PKM_SHIFT)) - keysym += 10; - if ((term->vt52_mode || term->cfg.funky_type == FUNKY_VT100P) && - keysym <= PK_F14) { - /* XXX This overrides the XTERM/VT52 mode below */ - int offt = 0; - if (keysym >= PK_F6) offt++; - if (keysym >= PK_F12) offt++; - p += sprintf((char *) p, term->vt52_mode ? "\x1B%c" : "\x1BO%c", - 'P' + keysym - PK_F1 - offt); - goto done; - } - if (term->cfg.funky_type == FUNKY_LINUX && keysym <= PK_F5) { - p += sprintf((char *) p, "\x1B[[%c", 'A' + keysym - PK_F1); - goto done; - } - if (term->cfg.funky_type == FUNKY_XTERM && keysym <= PK_F4) { - if (term->vt52_mode) - p += sprintf((char *) p, "\x1B%c", 'P' + keysym - PK_F1); - else - p += sprintf((char *) p, "\x1BO%c", 'P' + keysym - PK_F1); - goto done; - } - p += sprintf((char *) p, "\x1B[%d~", 11 + keysym - PK_F1); - goto done; - } - - if (PK_ISCURSOR(keysym)) { - int xkey; - - switch (keysym) { - case PK_UP: xkey = 'A'; break; - case PK_DOWN: xkey = 'B'; break; - case PK_RIGHT: xkey = 'C'; break; - case PK_LEFT: xkey = 'D'; break; - case PK_REST: xkey = 'G'; break; /* centre key on number pad */ - default: xkey = 0; break; /* else gcc warns `enum value not used' */ - } - p += format_arrow_key(p, term, xkey, modifiers == PKM_CONTROL); - goto done; - } - - done: - if (p > output || tlen > 0) { - /* - * Interrupt an ongoing paste. I'm not sure - * this is sensible, but for the moment it's - * preferable to having to faff about buffering - * things. - */ - term_nopaste(term); - - /* - * We need not bother about stdin backlogs - * here, because in GUI PuTTY we can't do - * anything about it anyway; there's no means - * of asking Windows to hold off on KEYDOWN - * messages. We _have_ to buffer everything - * we're sent. - */ - term_seen_key_event(term); - - if (prependesc) { -#if 0 - fprintf(stderr, "sending ESC\n"); -#endif - ldisc_send(term->ldisc, "\x1b", 1, 1); - } - - if (p > output) { -#if 0 - fprintf(stderr, "sending %d bytes:", p - output); - for (i = 0; i < p - output; i++) - fprintf(stderr, " %02x", output[i]); - fprintf(stderr, "\n"); -#endif - ldisc_send(term->ldisc, output, p - output, 1); - } else if (tlen > 0) { -#if 0 - fprintf(stderr, "sending %d unichars:", tlen); - for (i = 0; i < tlen; i++) - fprintf(stderr, " %04x", (unsigned) text[i]); - fprintf(stderr, "\n"); -#endif - luni_send(term->ldisc, text, tlen, 1); - } - } -} - void term_nopaste(Terminal *term) { if (term->paste_len == 0) return; - sfree(term->paste_buffer); - term->paste_buffer = NULL; - term->paste_len = 0; -} - -int term_paste_pending(Terminal *term) -{ - return term->paste_len != 0; -} - -void term_paste(Terminal *term) -{ - long now, paste_diff; - - if (term->paste_len == 0) - return; - - /* Don't wait forever to paste */ - if (term->paste_hold) { - now = GETTICKCOUNT(); - paste_diff = now - term->last_paste; - if (paste_diff >= 0 && paste_diff < 450) - return; - } - term->paste_hold = 0; - - while (term->paste_pos < term->paste_len) { - int n = 0; - while (n + term->paste_pos < term->paste_len) { - if (term->paste_buffer[term->paste_pos + n++] == '\015') - break; - } - if (term->ldisc) - luni_send(term->ldisc, term->paste_buffer + term->paste_pos, n, 0); - term->paste_pos += n; - - if (term->paste_pos < term->paste_len) { - term->paste_hold = 1; - return; - } - } sfree(term->paste_buffer); term->paste_buffer = NULL; term->paste_len = 0; } @@ -6391,10 +6091,18 @@ void term_deselect(Terminal *term) { deselect(term); term_update(term); + + /* + * Since terminal output is suppressed during drag-selects, we + * should make sure to write any pending output if one has just + * finished. + */ + if (term->selstate != DRAGGING) + term_out(term); } int term_ldisc(Terminal *term, int option) { if (option == LD_ECHO) @@ -6478,13 +6186,13 @@ */ char *term_get_ttymode(Terminal *term, const char *mode) { char *val = NULL; if (strcmp(mode, "ERASE") == 0) { - val = term->cfg.bksp_is_delete ? "^?" : "^H"; + val = term->bksp_is_delete ? "^?" : "^H"; } - /* FIXME: perhaps we should set ONLCR based on cfg.lfhascr as well? */ + /* FIXME: perhaps we should set ONLCR based on lfhascr as well? */ /* FIXME: or ECHO and friends based on local echo state? */ return dupstr(val); } struct term_userpass_state { @@ -6526,11 +6234,11 @@ * Zero all the results, in case we abort half-way through. */ { int i; for (i = 0; i < (int)p->n_prompts; i++) - memset(p->prompts[i]->result, 0, p->prompts[i]->result_len); + prompt_set_result(p->prompts[i], ""); } } while (s->curr_prompt < p->n_prompts) { @@ -6553,12 +6261,12 @@ inlen--; switch (c) { case 10: case 13: term_data(term, 0, "\r\n", 2); + prompt_ensure_result_size(pr, s->pos + 1); pr->result[s->pos] = '\0'; - pr->result[pr->result_len - 1] = '\0'; /* go to next prompt, if any */ s->curr_prompt++; s->done_prompt = 0; finished_prompt = 1; /* break out */ break; @@ -6589,14 +6297,13 @@ /* * This simplistic check for printability is disabled * when we're doing password input, because some people * have control characters in their passwords. */ - if ((!pr->echo || - (c >= ' ' && c <= '~') || - ((unsigned char) c >= 160)) - && s->pos < pr->result_len - 1) { + if (!pr->echo || (c >= ' ' && c <= '~') || + ((unsigned char) c >= 160)) { + prompt_ensure_result_size(pr, s->pos + 1); pr->result[s->pos++] = c; if (pr->echo) term_data(term, 0, &c, 1); } break; Index: terminal.h ================================================================== --- terminal.h +++ terminal.h @@ -150,11 +150,15 @@ int repeat_off, cr_lf_return; int seen_disp_event; int big_cursor; int xterm_mouse; /* send mouse messages to host */ + int xterm_extended_mouse; + int urxvt_extended_mouse; int mouse_is_down; /* used while tracking mouse buttons */ + + int bracketed_paste; int cset_attr[2]; /* * Saved settings on the alternate screen. @@ -216,12 +220,11 @@ /* Mask of attributes to pay attention to when painting. */ int attr_mask; wchar_t *paste_buffer; - int paste_len, paste_pos, paste_hold; - long last_paste; + int paste_len, paste_pos; void (*resize_fn)(void *, int, int); void *resize_ctx; void *ldisc; @@ -231,17 +234,17 @@ void *logctx; struct unicode_data *ucsdata; /* - * We maintain a full _copy_ of a Config structure here, not - * merely a pointer to it. That way, when we're passed a new - * one for reconfiguration, we can check the differences and - * adjust the _current_ setting of (e.g.) auto wrap mode rather - * than only the default. + * We maintain a full copy of a Conf here, not merely a pointer + * to it. That way, when we're passed a new one for + * reconfiguration, we can check the differences and adjust the + * _current_ setting of (e.g.) auto wrap mode rather than only + * the default. */ - Config cfg; + Conf *conf; /* * from_backend calls term_out, but it can also be called from * the ldisc if the ldisc is called _within_ term_out. So we * have to guard against re-entrancy - if from_backend is @@ -271,10 +274,56 @@ int ltemp_size; bidi_char *wcFrom, *wcTo; int wcFromTo_size; struct bidi_cache_entry *pre_bidi_cache, *post_bidi_cache; int bidi_cache_size; + + /* + * We copy a bunch of stuff out of the Conf structure into local + * fields in the Terminal structure, to avoid the repeated + * tree234 lookups which would be involved in fetching them from + * the former every time. + */ + int ansi_colour; + char *answerback; + int answerbacklen; + int arabicshaping; + int beep; + int bellovl; + int bellovl_n; + int bellovl_s; + int bellovl_t; + int bidi; + int bksp_is_delete; + int blink_cur; + int blinktext; + int cjk_ambig_wide; + int conf_height; + int conf_width; + int crhaslf; + int erase_to_scrollback; + int funky_type; + int lfhascr; + int logflush; + int logtype; + int mouse_override; + int nethack_keypad; + int no_alt_screen; + int no_applic_c; + int no_applic_k; + int no_dbackspace; + int no_mouse_rep; + int no_remote_charset; + int no_remote_resize; + int no_remote_wintitle; + int rawcnp; + int rect_select; + int remote_qtitle_action; + int rxvt_homeend; + int scroll_on_disp; + int scroll_on_key; + int xterm_256_colour; }; #define in_utf(term) ((term)->utf || (term)->ucsdata->line_codepage==CP_UTF8) #endif Index: testback.c ================================================================== --- testback.c +++ testback.c @@ -1,6 +1,6 @@ -/* $Id: testback.c 7628 2007-06-30 21:56:44Z jacob $ */ +/* $Id: testback.c 9214 2011-07-14 18:52:21Z simon $ */ /* * Copyright (c) 1999 Simon Tatham * Copyright (c) 1999 Ben Harris * All rights reserved. * @@ -31,17 +31,17 @@ #include #include #include "putty.h" -static const char *null_init(void *, void **, Config *, char *, int, char **, +static const char *null_init(void *, void **, Conf *, char *, int, char **, int, int); -static const char *loop_init(void *, void **, Config *, char *, int, char **, +static const char *loop_init(void *, void **, Conf *, char *, int, char **, int, int); static void null_free(void *); static void loop_free(void *); -static void null_reconfig(void *, Config *); +static void null_reconfig(void *, Conf *); static int null_send(void *, char *, int); static int loop_send(void *, char *, int); static int null_sendbuffer(void *); static void null_size(void *, int, int); static void null_special(void *, Telnet_Special); @@ -72,18 +72,18 @@ struct loop_state { Terminal *term; }; static const char *null_init(void *frontend_handle, void **backend_handle, - Config *cfg, char *host, int port, + Conf *conf, char *host, int port, char **realhost, int nodelay, int keepalive) { return NULL; } static const char *loop_init(void *frontend_handle, void **backend_handle, - Config *cfg, char *host, int port, + Conf *conf, char *host, int port, char **realhost, int nodelay, int keepalive) { struct loop_state *st = snew(struct loop_state); st->term = frontend_handle; *backend_handle = st; @@ -99,11 +99,11 @@ { sfree(handle); } -static void null_reconfig(void *handle, Config *cfg) { +static void null_reconfig(void *handle, Conf *conf) { } static int null_send(void *handle, char *buf, int len) { Index: testdata/bignum.py ================================================================== --- testdata/bignum.py +++ testdata/bignum.py @@ -101,10 +101,19 @@ a, b, p = findprod((1< #include @@ -16,16 +34,17 @@ #include "tree234.h" struct timer { timer_fn_t fn; void *ctx; - long now; + unsigned long now; + unsigned long when_set; }; static tree234 *timers = NULL; static tree234 *timer_contexts = NULL; -static long now = 0L; +static unsigned long now = 0L; static int compare_timers(void *av, void *bv) { struct timer *a = (struct timer *)av; struct timer *b = (struct timer *)bv; @@ -39,18 +58,16 @@ /* * Failing that, compare on the other two fields, just so that * we don't get unwanted equality. */ -#ifdef __LCC__ +#if defined(__LCC__) || defined(__clang__) /* lcc won't let us compare function pointers. Legal, but annoying. */ { int c = memcmp(&a->fn, &b->fn, sizeof(a->fn)); - if (c < 0) - return -1; - else if (c > 0) - return +1; + if (c) + return c; } #else if (a->fn < b->fn) return -1; else if (a->fn > b->fn) @@ -87,18 +104,19 @@ timer_contexts = newtree234(compare_timer_contexts); now = GETTICKCOUNT(); } } -long schedule_timer(int ticks, timer_fn_t fn, void *ctx) +unsigned long schedule_timer(int ticks, timer_fn_t fn, void *ctx) { - long when; + unsigned long when; struct timer *t, *first; init_timers(); - when = ticks + GETTICKCOUNT(); + now = GETTICKCOUNT(); + when = ticks + now; /* * Just in case our various defences against timing skew fail * us: if we try to schedule a timer that's already in the * past, we instead schedule it for the immediate future. @@ -108,10 +126,11 @@ t = snew(struct timer); t->fn = fn; t->ctx = ctx; t->now = when; + t->when_set = now; if (t != add234(timers, t)) { sfree(t); /* identical timer already exists */ } else { add234(timer_contexts, t->ctx);/* don't care if this fails */ @@ -132,69 +151,17 @@ /* * Call to run any timers whose time has reached the present. * Returns the time (in ticks) expected until the next timer after * that triggers. */ -int run_timers(long anow, long *next) +int run_timers(unsigned long anow, unsigned long *next) { struct timer *first; init_timers(); -#ifdef TIMING_SYNC - /* - * In this ifdef I put some code which deals with the - * possibility that `anow' disagrees with GETTICKCOUNT by a - * significant margin. Our strategy for dealing with it differs - * depending on platform, because on some platforms - * GETTICKCOUNT is more likely to be right whereas on others - * `anow' is a better gold standard. - */ - { - long tnow = GETTICKCOUNT(); - - if (tnow + TICKSPERSEC/50 - anow < 0 || - anow + TICKSPERSEC/50 - tnow < 0 - ) { -#if defined TIMING_SYNC_ANOW - /* - * If anow is accurate and the tick count is wrong, - * this is likely to be because the tick count is - * derived from the system clock which has changed (as - * can occur on Unix). Therefore, we resolve this by - * inventing an offset which is used to adjust all - * future output from GETTICKCOUNT. - * - * A platform which defines TIMING_SYNC_ANOW is - * expected to have also defined this offset variable - * in (its platform-specific adjunct to) putty.h. - * Therefore we can simply reference it here and assume - * that it will exist. - */ - tickcount_offset += anow - tnow; -#elif defined TIMING_SYNC_TICKCOUNT - /* - * If the tick count is more likely to be accurate, we - * simply use that as our time value, which may mean we - * run no timers in this call (because we got called - * early), or alternatively it may mean we run lots of - * timers in a hurry because we were called late. - */ - anow = tnow; -#else -/* - * Any platform which defines TIMING_SYNC must also define one of the two - * auxiliary symbols TIMING_SYNC_ANOW and TIMING_SYNC_TICKCOUNT, to - * indicate which measurement to trust when the two disagree. - */ -#error TIMING_SYNC definition incomplete -#endif - } - } -#endif - - now = anow; + now = GETTICKCOUNT(); while (1) { first = (struct timer *)index234(timers, 0); if (!first) @@ -205,11 +172,12 @@ * This timer belongs to a context that has been * expired. Delete it without running. */ delpos234(timers, 0); sfree(first); - } else if (first->now - now <= 0) { + } else if (now - (first->when_set - 10) > + first->now - (first->when_set - 10)) { /* * This timer is active and has reached its running * time. Run it. */ delpos234(timers, 0); Index: tree234.c ================================================================== --- tree234.c +++ tree234.c @@ -27,16 +27,22 @@ #include #include #include -#include "puttymem.h" #include "tree234.h" #ifdef TEST #define LOG(x) (printf x) +#define snew(type) ((type *)malloc(sizeof(type))) +#define snewn(n, type) ((type *)malloc((n) * sizeof(type))) +#define sresize(ptr, n, type) \ + ((type *)realloc(sizeof((type *)0 == (ptr)) ? (ptr) : (ptr), \ + (n) * sizeof(type))) +#define sfree(ptr) free(ptr) #else +#include "puttymem.h" #define LOG(x) #endif typedef struct node234_Tag node234; @@ -218,11 +224,11 @@ n->kids[0], n->counts[0], n->elems[0], n->kids[1], n->counts[1], n->elems[1], n->kids[2], n->counts[2], n->elems[2], n->kids[3], n->counts[3])); LOG((" need to insert %p/%d [%p] %p/%d at position %d\n", - left, lcount, e, right, rcount, np - n->kids)); + left, lcount, e, right, rcount, (int)(np - n->kids))); if (n->elems[1] == NULL) { /* * Insert in a 2-node; simple. */ if (np == &n->kids[0]) { @@ -1467,13 +1473,14 @@ } while (count234(tree) > 0) { printf("cleanup: tree size %d\n", count234(tree)); j = randomnumber(&seed); j %= count234(tree); - printf("deleting string %s from index %d\n", array[j], j); + printf("deleting string %s from index %d\n", + (const char *)array[j], j); delpostest(j); } return 0; } #endif Index: unix/configure.ac ================================================================== --- unix/configure.ac +++ unix/configure.ac @@ -2,22 +2,47 @@ # * Autoconf 2.50 or newer # * Gtk (for $prefix/share/aclocal/gtk.m4) # * Automake (for aclocal) # If you've got them, running "autoreconf" should work. -AC_INIT +# Version number is substituted by Buildscr for releases, snapshots +# and custom builds out of svn; X.XX shows up in ad-hoc developer +# builds, which shouldn't matter +AC_INIT(putty, X.XX) AC_CONFIG_FILES([Makefile]) AC_CONFIG_HEADERS([uxconfig.h:uxconfig.in]) +AM_INIT_AUTOMAKE([-Wall -Werror foreign]) AC_PROG_INSTALL -AC_PROG_CC -if test "X$GCC" = Xyes; then - PUTTYCFLAGS="-Wall -Werror" -else - PUTTYCFLAGS="" -fi -AC_SUBST(PUTTYCFLAGS) +AC_PROG_RANLIB +ifdef([AM_PROG_AR],[AM_PROG_AR]) +AM_PROG_CC_C_O + +# Mild abuse of the '--enable' option format to allow manual +# specification of setuid or setgid setup in pterm. +setidtype=none +AC_ARG_ENABLE([setuid], + [AS_HELP_STRING([--enable-setuid=USER], + [make pterm setuid to a given user])], + [case "$enableval" in + no) setidtype=none;; + *) setidtype=setuid; setidval="$enableval";; + esac]) +AC_ARG_ENABLE([setgid], + [AS_HELP_STRING([--enable-setgid=GROUP], + [make pterm setgid to a given group])], + [case "$enableval" in + no) setidtype=none;; + *) setidtype=setgid; setidval="$enableval";; + esac]) +AM_CONDITIONAL(HAVE_SETID_CMD, [test "$setidtype" != "none"]) +AS_IF([test "x$setidtype" = "xsetuid"], + [SETID_CMD="chown $setidval"; SETID_MODE="4755"]) +AS_IF([test "x$setidtype" = "xsetgid"], + [SETID_CMD="chgrp $setidval"; SETID_MODE="2755"]) +AC_SUBST(SETID_CMD) +AC_SUBST(SETID_MODE) AC_ARG_WITH([gssapi], [AS_HELP_STRING([--without-gssapi], [disable GSSAPI support])], [], @@ -24,33 +49,69 @@ [with_gssapi=yes]) WITH_GSSAPI= AS_IF([test "x$with_gssapi" != xno], [AC_DEFINE([WITH_GSSAPI], [1], [Define if building with GSSAPI support.])]) + +AC_ARG_WITH([gtk], + [AS_HELP_STRING([--with-gtk=VER], + [specify GTK version to use (`1' or `2')]) +AS_HELP_STRING([--without-gtk], + [do not use GTK (build command-line tools only)])], + [gtk_version_desired="$withval"], + [gtk_version_desired="any"]) + +case "$gtk_version_desired" in + 1 | 2 | any | no) ;; + yes) gtk_version_desired="any" ;; + *) AC_ERROR([Invalid GTK version specified]) +esac AC_CHECK_HEADERS([utmpx.h sys/select.h],,,[ #include #include ]) -# Look for both GTK 1 and GTK 2. -AM_PATH_GTK([1.2.0], [gtk=1], [gtk=none]) -AM_PATH_GTK_2_0([2.0.0], [gtk=2], []) -if test "$gtk" = "none"; then - all_targets="all-cli" -else - all_targets="all-cli all-gtk" -fi +# Look for both GTK 2 and GTK 1, in descending order of preference. If +# we can't find either, have the makefile only build the CLI programs. + +gtk=none + +case "$gtk_version_desired:$gtk" in + 2:none | any:none) + ifdef([AM_PATH_GTK_2_0],[ + AM_PATH_GTK_2_0([2.0.0], [gtk=2], []) + ],[AC_WARNING([generating configure script without GTK 2 autodetection])]) + ;; +esac + +case "$gtk_version_desired:$gtk" in + 1:none | any:none) + ifdef([AM_PATH_GTK],[ + AM_PATH_GTK([1.2.0], [gtk=1], []) + ],[ + # manual check for gtk1 + AC_PATH_PROG(GTK1_CONFIG, gtk-config, absent) + if test "$GTK1_CONFIG" != "absent"; then + GTK_CFLAGS=`"$GTK1_CONFIG" --cflags` + GTK_LIBS=`"$GTK1_CONFIG" --libs` + gtk=1 + fi + ]) + ;; +esac + +AM_CONDITIONAL(HAVE_GTK, [test "$gtk" != "none"]) + if test "$gtk" = "2"; then ac_save_CFLAGS="$CFLAGS" ac_save_LIBS="$LIBS" CFLAGS="$CFLAGS $GTK_CFLAGS" LIBS="$GTK_LIBS $LIBS" AC_CHECK_FUNCS([pango_font_family_is_monospace pango_font_map_list_families]) CFLAGS="$ac_save_CFLAGS" LIBS="$ac_save_LIBS" fi -AC_SUBST([all_targets]) AC_SEARCH_LIBS([socket], [xnet]) AS_IF([test "x$with_gssapi" != xno], [AC_SEARCH_LIBS( @@ -61,15 +122,45 @@ AC_SEARCH_LIBS( [gss_init_sec_context],[gssapi gssapi_krb5 gss], [], [AC_DEFINE([NO_GSSAPI_LIB], [1], [Define if we could not find a gssapi library])])])]) -AC_CHECK_LIB(X11, XOpenDisplay) +AC_CHECK_LIB(X11, XOpenDisplay, + [GTK_LIBS="-lX11 $GTK_LIBS" + AC_DEFINE([HAVE_LIBX11],[],[Define if libX11.a is available])]) + +AC_CHECK_FUNCS([getaddrinfo posix_openpt ptsname setresuid strsignal updwtmpx]) +AC_CHECK_DECLS([CLOCK_MONOTONIC], [], [], [[#include ]]) +AC_SEARCH_LIBS([clock_gettime], [rt], [AC_DEFINE([HAVE_CLOCK_GETTIME],[],[Define if clock_gettime() is available])]) -AC_CHECK_FUNCS([getaddrinfo ptsname setresuid strsignal updwtmpx]) +if test "x$GCC" = "xyes"; then + : + AC_SUBST(WARNINGOPTS, ['-Wall -Werror']) +else + : + AC_SUBST(WARNINGOPTS, []) +fi AC_OUTPUT + +if test "$gtk_version_desired" = "no"; then cat <ncontrols; i++) { c = s->ctrls[i]; if (c->generic.type == CTRL_CHECKBOX && - c->generic.context.i == offsetof(Config,scrollbar)) { + c->generic.context.i == CONF_scrollbar) { /* * Control i is the scrollbar checkbox. * Control s->ncontrols-1 is the scrollbar-on-left one. */ if (i < s->ncontrols-2) { @@ -87,33 +87,33 @@ ctrl_settitle(b, "Window/Fonts", "Options controlling font usage"); s = ctrl_getset(b, "Window/Fonts", "font", "Fonts for displaying non-bold text"); ctrl_fontsel(s, "Font used for ordinary text", 'f', HELPCTX(no_help), - dlg_stdfontsel_handler, I(offsetof(Config,font))); + conf_fontsel_handler, I(CONF_font)); ctrl_fontsel(s, "Font used for wide (CJK) text", 'w', HELPCTX(no_help), - dlg_stdfontsel_handler, I(offsetof(Config,widefont))); + conf_fontsel_handler, I(CONF_widefont)); s = ctrl_getset(b, "Window/Fonts", "fontbold", "Fonts for displaying bolded text"); ctrl_fontsel(s, "Font used for bolded text", 'b', HELPCTX(no_help), - dlg_stdfontsel_handler, I(offsetof(Config,boldfont))); + conf_fontsel_handler, I(CONF_boldfont)); ctrl_fontsel(s, "Font used for bold wide text", 'i', HELPCTX(no_help), - dlg_stdfontsel_handler, I(offsetof(Config,wideboldfont))); + conf_fontsel_handler, I(CONF_wideboldfont)); ctrl_checkbox(s, "Use shadow bold instead of bold fonts", 'u', HELPCTX(no_help), - dlg_stdcheckbox_handler, - I(offsetof(Config,shadowbold))); + conf_checkbox_handler, + I(CONF_shadowbold)); ctrl_text(s, "(Note that bold fonts or shadow bolding are only" " used if you have not requested bolding to be done by" " changing the text colour.)", HELPCTX(no_help)); ctrl_editbox(s, "Horizontal offset for shadow bold:", 'z', 20, - HELPCTX(no_help), dlg_stdeditbox_handler, - I(offsetof(Config,shadowboldoffset)), I(-1)); + HELPCTX(no_help), conf_editbox_handler, + I(CONF_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, @@ -123,12 +123,12 @@ */ 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))); + conf_checkbox_handler, + I(CONF_utf8_override)); if (!midsession) { /* * Allow the user to specify the window class as part of the saved * configuration, so that they can have their window manager treat @@ -135,10 +135,9 @@ * different kinds of PuTTY and pterm differently if they want to. */ s = ctrl_getset(b, "Window/Behaviour", "x11", "X Window System settings"); ctrl_editbox(s, "Window class name:", 'z', 50, - HELPCTX(no_help), dlg_stdeditbox_handler, - I(offsetof(Config,winclass)), - I(sizeof(((Config *)0)->winclass))); + HELPCTX(no_help), conf_editbox_handler, + I(CONF_winclass), I(1)); } } Index: unix/gtkcols.h ================================================================== --- unix/gtkcols.h +++ unix/gtkcols.h @@ -6,11 +6,11 @@ #ifndef COLUMNS_H #define COLUMNS_H #include -#include +#include #ifdef __cplusplus extern "C" { #endif /* __cplusplus */ Index: unix/gtkdlg.c ================================================================== --- unix/gtkdlg.c +++ unix/gtkdlg.c @@ -328,52 +328,42 @@ * the "changed" signal. * * The first call to "changed", if allowed to proceed normally, * will cause an EVENT_VALCHANGE event on the edit box, causing * a call to dlg_editbox_get() which will read the empty string - * out of the GtkEntry - and promptly write it straight into - * the Config structure, which is precisely where our `text' - * pointer is probably pointing, so the second editing - * operation will insert that instead of the string we - * originally asked for. + * out of the GtkEntry - and promptly write it straight into the + * Conf structure, which is precisely where our `text' pointer + * is probably pointing, so the second editing operation will + * insert that instead of the string we originally asked for. * * Hence, we must take our own copy of the text before we do * this. */ tmpstring = dupstr(text); gtk_entry_set_text(GTK_ENTRY(entry), tmpstring); sfree(tmpstring); } -void dlg_editbox_get(union control *ctrl, void *dlg, char *buffer, int length) +char *dlg_editbox_get(union control *ctrl, void *dlg) { struct dlgparam *dp = (struct dlgparam *)dlg; struct uctrl *uc = dlg_find_byctrl(dp, ctrl); assert(uc->ctrl->generic.type == CTRL_EDITBOX); #if GTK_CHECK_VERSION(2,4,0) if (uc->combo) { #if GTK_CHECK_VERSION(2,6,0) - strncpy(buffer, - gtk_combo_box_get_active_text(GTK_COMBO_BOX(uc->combo)), - length); + return dupstr(gtk_combo_box_get_active_text(GTK_COMBO_BOX(uc->combo))); #else - strncpy(buffer, - gtk_entry_get_text - (GTK_ENTRY(gtk_bin_get_child(GTK_BIN(uc->combo)))), - length); + return dupstr(gtk_entry_get_text + (GTK_ENTRY(gtk_bin_get_child(GTK_BIN(uc->combo))))); #endif - buffer[length-1] = '\0'; - return; } #endif if (uc->entry) { - strncpy(buffer, gtk_entry_get_text(GTK_ENTRY(uc->entry)), - length); - buffer[length-1] = '\0'; - return; + return dupstr(gtk_entry_get_text(GTK_ENTRY(uc->entry))); } assert(!"We shouldn't get here"); } @@ -914,48 +904,52 @@ assert(!"This shouldn't happen"); break; } } -void dlg_filesel_set(union control *ctrl, void *dlg, Filename fn) -{ - struct dlgparam *dp = (struct dlgparam *)dlg; - struct uctrl *uc = dlg_find_byctrl(dp, ctrl); - assert(uc->ctrl->generic.type == CTRL_FILESELECT); - assert(uc->entry != NULL); - gtk_entry_set_text(GTK_ENTRY(uc->entry), fn.path); -} - -void dlg_filesel_get(union control *ctrl, void *dlg, Filename *fn) -{ - struct dlgparam *dp = (struct dlgparam *)dlg; - struct uctrl *uc = dlg_find_byctrl(dp, ctrl); - assert(uc->ctrl->generic.type == CTRL_FILESELECT); - assert(uc->entry != NULL); - strncpy(fn->path, gtk_entry_get_text(GTK_ENTRY(uc->entry)), - lenof(fn->path)); - fn->path[lenof(fn->path)-1] = '\0'; -} - -void dlg_fontsel_set(union control *ctrl, void *dlg, FontSpec fs) -{ - struct dlgparam *dp = (struct dlgparam *)dlg; - struct uctrl *uc = dlg_find_byctrl(dp, ctrl); - assert(uc->ctrl->generic.type == CTRL_FONTSELECT); - assert(uc->entry != NULL); - gtk_entry_set_text(GTK_ENTRY(uc->entry), fs.name); -} - -void dlg_fontsel_get(union control *ctrl, void *dlg, FontSpec *fs) -{ - struct dlgparam *dp = (struct dlgparam *)dlg; - struct uctrl *uc = dlg_find_byctrl(dp, ctrl); - assert(uc->ctrl->generic.type == CTRL_FONTSELECT); - assert(uc->entry != NULL); - strncpy(fs->name, gtk_entry_get_text(GTK_ENTRY(uc->entry)), - lenof(fs->name)); - fs->name[lenof(fs->name)-1] = '\0'; +void dlg_filesel_set(union control *ctrl, void *dlg, Filename *fn) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + struct uctrl *uc = dlg_find_byctrl(dp, ctrl); + /* We must copy fn->path before passing it to gtk_entry_set_text. + * See comment in dlg_editbox_set() for the reasons. */ + char *duppath = dupstr(fn->path); + assert(uc->ctrl->generic.type == CTRL_FILESELECT); + assert(uc->entry != NULL); + gtk_entry_set_text(GTK_ENTRY(uc->entry), duppath); + sfree(duppath); +} + +Filename *dlg_filesel_get(union control *ctrl, void *dlg) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + struct uctrl *uc = dlg_find_byctrl(dp, ctrl); + assert(uc->ctrl->generic.type == CTRL_FILESELECT); + assert(uc->entry != NULL); + return filename_from_str(gtk_entry_get_text(GTK_ENTRY(uc->entry))); +} + +void dlg_fontsel_set(union control *ctrl, void *dlg, FontSpec *fs) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + struct uctrl *uc = dlg_find_byctrl(dp, ctrl); + /* We must copy fs->name before passing it to gtk_entry_set_text. + * See comment in dlg_editbox_set() for the reasons. */ + char *dupname = dupstr(fs->name); + assert(uc->ctrl->generic.type == CTRL_FONTSELECT); + assert(uc->entry != NULL); + gtk_entry_set_text(GTK_ENTRY(uc->entry), dupname); + sfree(dupname); +} + +FontSpec *dlg_fontsel_get(union control *ctrl, void *dlg) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + struct uctrl *uc = dlg_find_byctrl(dp, ctrl); + assert(uc->ctrl->generic.type == CTRL_FONTSELECT); + assert(uc->entry != NULL); + return fontspec_new(gtk_entry_get_text(GTK_ENTRY(uc->entry))); } /* * Bracketing a large set of updates in these two functions will * cause the front end (if possible) to delay updating the screen @@ -2824,16 +2818,16 @@ gtk_widget_hide(dlg->action_area); gtk_dialog_set_has_separator(dlg, FALSE); #endif } -int do_config_box(const char *title, Config *cfg, int midsession, +int do_config_box(const char *title, Conf *conf, int midsession, int protcfginfo) { GtkWidget *window, *hbox, *vbox, *cols, *label, *tree, *treescroll, *panels, *panelvbox; - int index, level; + int index, level, protocol; struct controlbox *ctrlbox; char *path; #if GTK_CHECK_VERSION(2,0,0) GtkTreeStore *treestore; GtkCellRenderer *treerenderer; @@ -2857,12 +2851,13 @@ } window = gtk_dialog_new(); ctrlbox = ctrl_new_box(); - setup_config_box(ctrlbox, midsession, cfg->protocol, protcfginfo); - unix_setup_config_box(ctrlbox, midsession, cfg->protocol); + protocol = conf_get_int(conf, CONF_protocol); + setup_config_box(ctrlbox, midsession, protocol, protcfginfo); + unix_setup_config_box(ctrlbox, midsession, protocol); gtk_setup_config_box(ctrlbox, midsession, window); gtk_window_set_title(GTK_WINDOW(window), title); hbox = gtk_hbox_new(FALSE, 4); gtk_box_pack_start(GTK_BOX(GTK_DIALOG(window)->vbox), hbox, TRUE, TRUE, 0); @@ -3093,11 +3088,11 @@ &selparams[index]); dp.treeitems[index] = selparams[index].treeitem; } #endif - dp.data = cfg; + dp.data = conf; dlg_refresh(NULL, &dp); dp.shortcuts = &selparams[0].shortcuts; #if !GTK_CHECK_VERSION(2,0,0) dp.currtreeitem = dp.treeitems[0]; @@ -3265,11 +3260,11 @@ ctrl_free_box(ctrlbox); return dp.retval; } -static int string_width(char *text) +int string_width(char *text) { GtkWidget *label = gtk_label_new(text); GtkRequisition req; gtk_widget_size_request(label, &req); gtk_object_sink(GTK_OBJECT(label)); @@ -3391,10 +3386,17 @@ { messagebox(window, "PuTTY Fatal Error", msg, string_width("REASONABLY LONG LINE OF TEXT FOR BASIC SANITY"), "OK", 'o', 1, 1, NULL); } + +void nonfatal_message_box(void *window, char *msg) +{ + messagebox(window, "PuTTY Error", msg, + string_width("REASONABLY LONG LINE OF TEXT FOR BASIC SANITY"), + "OK", 'o', 1, 1, NULL); +} void fatalbox(char *p, ...) { va_list ap; char *msg; @@ -3403,10 +3405,21 @@ va_end(ap); fatal_message_box(NULL, msg); sfree(msg); cleanup_exit(1); } + +void nonfatal(char *p, ...) +{ + va_list ap; + char *msg; + va_start(ap, p); + msg = dupvprintf(p, ap); + va_end(ap); + nonfatal_message_box(NULL, msg); + sfree(msg); +} static GtkWidget *aboutbox = NULL; static void about_close_clicked(GtkButton *button, gpointer data) { @@ -3417,11 +3430,11 @@ static void licence_clicked(GtkButton *button, gpointer data) { char *title; char *licence = - "Copyright 1997-2011 Simon Tatham.\n\n" + "Copyright 1997-2013 Simon Tatham.\n\n" "Portions copyright Robert de Bath, Joris van Rantwijk, Delian " "Delchev, Andreas Schultz, Jeroen Massar, Wez Furlong, Nicolas " "Barry, Justin Bradford, Ben Harris, Malcolm Smith, Ahmad Khalifa, " "Markus Kuhn, Colin Watson, and CORE SDI S.A.\n\n" @@ -3498,11 +3511,11 @@ w = gtk_label_new(ver); gtk_box_pack_start(GTK_BOX(GTK_DIALOG(aboutbox)->vbox), w, FALSE, FALSE, 5); gtk_widget_show(w); - w = gtk_label_new("Copyright 1997-2011 Simon Tatham. All rights reserved"); + w = gtk_label_new("Copyright 1997-2013 Simon Tatham. All rights reserved"); gtk_box_pack_start(GTK_BOX(GTK_DIALOG(aboutbox)->vbox), w, FALSE, FALSE, 5); gtk_widget_show(w); set_transient_window_pos(GTK_WIDGET(window), aboutbox); @@ -3750,11 +3763,11 @@ dlg_listbox_add(es->listctrl, &es->dp, es->events[es->nevents]); } es->nevents++; } -int askappend(void *frontend, Filename filename, +int askappend(void *frontend, Filename *filename, void (*callback)(void *ctx, int result), void *ctx) { static const char msgtemplate[] = "The session log file \"%.*s\" already exists. " "You can overwrite it with a new session log, " @@ -3762,11 +3775,11 @@ "or disable session logging for this session."; char *message; char *mbtitle; int mbret; - message = dupprintf(msgtemplate, FILENAME_MAX, filename.path); + message = dupprintf(msgtemplate, FILENAME_MAX, filename->path); mbtitle = dupprintf("%s Log to File", appname); mbret = messagebox(get_window(frontend), mbtitle, message, string_width("LINE OF TEXT SUITABLE FOR THE" " ASKAPPEND WIDTH"), Index: unix/gtkfont.c ================================================================== --- unix/gtkfont.c +++ unix/gtkfont.c @@ -28,16 +28,10 @@ * * - it would be nice to have a display of the current font name, * and in particular whether it's client- or server-side, * during the progress of the font selector. * - * - all the GDK font functions used in the x11font subclass are - * deprecated, so one day they may go away. When this happens - - * or before, if I'm feeling proactive - it oughtn't to be too - * difficult in principle to convert the whole thing to use - * actual Xlib font calls. - * * - it would be nice if we could move the processing of * underline and VT100 double width into this module, so that * instead of using the ghastly pixmap-stretching technique * everywhere we could tell the Pango backend to scale its * fonts to double size properly and at full resolution. @@ -44,10 +38,15 @@ * However, this requires me to learn how to make Pango stretch * text to an arbitrary aspect ratio (for double-width only * text, which perversely is harder than DW+DH), and right now * I haven't the energy. */ + +#if !GLIB_CHECK_VERSION(1,3,7) +#define g_ascii_strcasecmp g_strcasecmp +#define g_ascii_strncasecmp g_strncasecmp +#endif /* * Ad-hoc vtable mechanism to allow font structures to be * polymorphic. * @@ -75,13 +74,16 @@ /* * `Methods' of the `class'. */ unifont *(*create)(GtkWidget *widget, const char *name, int wide, int bold, int shadowoffset, int shadowalways); + unifont *(*create_fallback)(GtkWidget *widget, int height, int wide, + int bold, int shadowoffset, int shadowalways); void (*destroy)(unifont *font); + int (*has_glyph)(unifont *font, wchar_t glyph); void (*draw_text)(GdkDrawable *target, GdkGC *gc, unifont *font, - int x, int y, const char *string, int len, int wide, + int x, int y, const wchar_t *string, int len, int wide, int bold, int cellwidth); void (*enum_fonts)(GtkWidget *widget, fontsel_add_entry callback, void *callback_ctx); char *(*canonify_fontname)(GtkWidget *widget, const char *name, int *size, int *flags, int resolve_aliases); @@ -92,15 +94,16 @@ */ const char *prefix; }; /* ---------------------------------------------------------------------- - * GDK-based X11 font implementation. + * X11 font implementation, directly using Xlib calls. */ +static int x11font_has_glyph(unifont *font, wchar_t glyph); static void x11font_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font, - int x, int y, const char *string, int len, + int x, int y, const wchar_t *string, int len, int wide, int bold, int cellwidth); static unifont *x11font_create(GtkWidget *widget, const char *name, int wide, int bold, int shadowoffset, int shadowalways); static void x11font_destroy(unifont *font); @@ -122,44 +125,53 @@ * tried to fetch a subfont already (thus distinguishing NULL * because we haven't tried yet from NULL because we tried and * failed, so that we don't keep trying and failing * subsequently). */ - GdkFont *fonts[4]; + XFontStruct *fonts[4]; int allocated[4]; /* * `sixteen_bit' is true iff the font object is indexed by * values larger than a byte. That is, this flag tells us - * whether we use gdk_draw_text_wc() or gdk_draw_text(). + * whether we use XDrawString or XDrawString16, etc. */ int sixteen_bit; /* * `variable' is true iff the font is non-fixed-pitch. This * enables some code which takes greater care over character * positioning during text drawing. */ int variable; + /* + * real_charset is the charset used when translating text into the + * font's internal encoding inside draw_text(). This need not be + * the same as the public_charset provided to the client; for + * example, public_charset might be CS_ISO8859_1 while + * real_charset is CS_ISO8859_1_X11. + */ + int real_charset; /* * Data passed in to unifont_create(). */ int wide, bold, shadowoffset, shadowalways; }; static const struct unifont_vtable x11font_vtable = { x11font_create, + NULL, /* no fallback fonts in X11 */ x11font_destroy, + x11font_has_glyph, x11font_draw_text, x11font_enum_fonts, x11font_canonify_fontname, x11font_scale_fontname, "server", }; -char *x11_guess_derived_font_name(GdkFont *font, int bold, int wide) +static char *x11_guess_derived_font_name(XFontStruct *xfs, int bold, int wide) { - XFontStruct *xfs = GDK_FONT_XFONT(font); - Display *disp = GDK_FONT_XDISPLAY(font); + Display *disp = GDK_DISPLAY(); Atom fontprop = XInternAtom(disp, "FONT", False); unsigned long ret; if (XGetFontProperty(xfs, fontprop, &ret)) { char *name = XGetAtomName(disp, (Atom)ret); if (name && name[0] == '-') { @@ -177,12 +189,14 @@ strings[nstr++] = p+1; } p++; } - if (nstr < lenof(strings)) + if (nstr < lenof(strings)) { + sfree(dupname); return NULL; /* XLFD was malformed */ + } if (bold) strings[2] = "bold"; if (wide) { @@ -204,42 +218,100 @@ } } return NULL; } -static int x11_font_width(GdkFont *font, int sixteen_bit) +static int x11_font_width(XFontStruct *xfs, int sixteen_bit) { if (sixteen_bit) { XChar2b space; space.byte1 = 0; space.byte2 = '0'; - return gdk_text_width(font, (const gchar *)&space, 2); + return XTextWidth16(xfs, &space, 1); + } else { + return XTextWidth(xfs, "0", 1); + } +} + +static int x11_font_has_glyph(XFontStruct *xfs, int byte1, int byte2) +{ + int index; + + /* + * Not to be confused with x11font_has_glyph, which is a method of + * the x11font 'class' and hence takes a unifont as argument. This + * is the low-level function which grubs about in an actual + * XFontStruct to see if a given glyph exists. + * + * We must do this ourselves rather than letting Xlib's + * XTextExtents16 do the job, because XTextExtents will helpfully + * substitute the font's default_char for any missing glyph and + * not tell us it did so, which precisely won't help us find out + * which glyphs _are_ missing. + * + * The man page for XQueryFont is rather confusing about how the + * per_char array in the XFontStruct is laid out, because it gives + * formulae for determining the two-byte X character code _from_ + * an index into the per_char array. Going the other way, it's + * rather simpler: + * + * The valid character codes have byte1 between min_byte1 and + * max_byte1 inclusive, and byte2 between min_char_or_byte2 and + * max_char_or_byte2 inclusive. This gives a rectangle of size + * (max_byte2-min_byte1+1) by + * (max_char_or_byte2-min_char_or_byte2+1), which is precisely the + * rectangle encoded in the per_char array. Hence, given a + * character code which is valid in the sense that it falls + * somewhere in that rectangle, its index in per_char is given by + * setting + * + * x = byte2 - min_char_or_byte2 + * y = byte1 - min_byte1 + * index = y * (max_char_or_byte2-min_char_or_byte2+1) + x + * + * If min_byte1 and min_byte2 are both zero, that's a special case + * which can be treated as if min_byte2 was 1 instead, i.e. the + * per_char array just runs from min_char_or_byte2 to + * max_char_or_byte2 inclusive, and byte1 should always be zero. + */ + + if (byte2 < xfs->min_char_or_byte2 || byte2 > xfs->max_char_or_byte2) + return FALSE; + + if (xfs->min_byte1 == 0 && xfs->max_byte1 == 0) { + index = byte2 - xfs->min_char_or_byte2; } else { - return gdk_char_width(font, '0'); + if (byte1 < xfs->min_byte1 || byte1 > xfs->max_byte1) + return FALSE; + index = ((byte2 - xfs->min_char_or_byte2) + + ((byte1 - xfs->min_byte1) * + (xfs->max_char_or_byte2 - xfs->min_char_or_byte2 + 1))); } + + if (!xfs->per_char) /* per_char NULL => everything in range exists */ + return TRUE; + + return (xfs->per_char[index].ascent + xfs->per_char[index].descent > 0 || + xfs->per_char[index].width > 0); } static unifont *x11font_create(GtkWidget *widget, const char *name, int wide, int bold, int shadowoffset, int shadowalways) { struct x11font *xfont; - GdkFont *font; XFontStruct *xfs; - Display *disp; + Display *disp = GDK_DISPLAY(); Atom charset_registry, charset_encoding, spacing; unsigned long registry_ret, encoding_ret, spacing_ret; int pubcs, realcs, sixteen_bit, variable; int i; - font = gdk_font_load(name); - if (!font) + xfs = XLoadQueryFont(disp, name); + if (!xfs) return NULL; - xfs = GDK_FONT_XFONT(font); - disp = GDK_FONT_XDISPLAY(font); - charset_registry = XInternAtom(disp, "CHARSET_REGISTRY", False); charset_encoding = XInternAtom(disp, "CHARSET_ENCODING", False); pubcs = realcs = CS_NONE; sixteen_bit = FALSE; @@ -264,31 +336,23 @@ sixteen_bit = TRUE; pubcs = realcs = CS_UTF8; } /* - * Hack for X line-drawing characters: if the primary - * font is encoded as ISO-8859-1, and has valid glyphs - * in the first 32 char positions, it is assumed that - * those glyphs are the VT100 line-drawing character - * set. - * - * Actually, we'll hack even harder by only checking - * position 0x19 (vertical line, VT100 linedrawing - * `x'). Then we can check it easily by seeing if the - * ascent and descent differ. + * Hack for X line-drawing characters: if the primary font + * is encoded as ISO-8859-1, and has valid glyphs in the + * low character positions, it is assumed that those + * glyphs are the VT100 line-drawing character set. */ if (pubcs == CS_ISO8859_1) { - int lb, rb, wid, asc, desc; - gchar text[2]; - - text[1] = '\0'; - text[0] = '\x12'; - gdk_string_extents(font, text, &lb, &rb, &wid, &asc, &desc); - if (asc != desc) - realcs = CS_ISO8859_1_X11; - } + int ch; + for (ch = 1; ch < 32; ch++) + if (!x11_font_has_glyph(xfs, 0, ch)) + break; + if (ch == 32) + realcs = CS_ISO8859_1_X11; + } sfree(encoding); } } @@ -301,17 +365,18 @@ variable = FALSE; } xfont = snew(struct x11font); xfont->u.vt = &x11font_vtable; - xfont->u.width = x11_font_width(font, sixteen_bit); - xfont->u.ascent = font->ascent; - xfont->u.descent = font->descent; + xfont->u.width = x11_font_width(xfs, sixteen_bit); + xfont->u.ascent = xfs->ascent; + xfont->u.descent = xfs->descent; xfont->u.height = xfont->u.ascent + xfont->u.descent; xfont->u.public_charset = pubcs; - xfont->u.real_charset = realcs; - xfont->fonts[0] = font; + xfont->u.want_fallback = TRUE; + xfont->real_charset = realcs; + xfont->fonts[0] = xfs; xfont->allocated[0] = TRUE; xfont->sixteen_bit = sixteen_bit; xfont->variable = variable; xfont->wide = wide; xfont->bold = bold; @@ -326,67 +391,154 @@ return (unifont *)xfont; } static void x11font_destroy(unifont *font) { + Display *disp = GDK_DISPLAY(); struct x11font *xfont = (struct x11font *)font; int i; for (i = 0; i < lenof(xfont->fonts); i++) if (xfont->fonts[i]) - gdk_font_unref(xfont->fonts[i]); + XFreeFont(disp, xfont->fonts[i]); sfree(font); } static void x11_alloc_subfont(struct x11font *xfont, int sfid) { + Display *disp = GDK_DISPLAY(); char *derived_name = x11_guess_derived_font_name (xfont->fonts[0], sfid & 1, !!(sfid & 2)); - xfont->fonts[sfid] = gdk_font_load(derived_name); /* may be NULL */ + xfont->fonts[sfid] = XLoadQueryFont(disp, derived_name); xfont->allocated[sfid] = TRUE; sfree(derived_name); + /* Note that xfont->fonts[sfid] may still be NULL, if XLQF failed. */ +} + +static int x11font_has_glyph(unifont *font, wchar_t glyph) +{ + struct x11font *xfont = (struct x11font *)font; + + if (xfont->sixteen_bit) { + /* + * This X font has 16-bit character indices, which means + * we can directly use our Unicode input value. + */ + return x11_font_has_glyph(xfont->fonts[0], glyph >> 8, glyph & 0xFF); + } else { + /* + * This X font has 8-bit indices, so we must convert to the + * appropriate character set. + */ + char sbstring[2]; + int sblen = wc_to_mb(xfont->real_charset, 0, &glyph, 1, + sbstring, 2, "", NULL, NULL); + if (sblen == 0 || !sbstring[0]) + return FALSE; /* not even in the charset */ + + return x11_font_has_glyph(xfont->fonts[0], 0, + (unsigned char)sbstring[0]); + } +} + +#if !GTK_CHECK_VERSION(2,0,0) +#define GDK_DRAWABLE_XID(d) GDK_WINDOW_XWINDOW(d) /* GTK1's name for this */ +#endif + +static void x11font_really_draw_text_16(GdkDrawable *target, XFontStruct *xfs, + GC gc, int x, int y, + const XChar2b *string, int nchars, + int shadowoffset, + int fontvariable, int cellwidth) +{ + Display *disp = GDK_DISPLAY(); + int step, nsteps, centre; + + if (fontvariable) { + /* + * In a variable-pitch font, we draw one character at a + * time, and centre it in the character cell. + */ + step = 1; + nsteps = nchars; + centre = TRUE; + } else { + /* + * In a fixed-pitch font, we can draw the whole lot in one go. + */ + step = nchars; + nsteps = 1; + centre = FALSE; + } + + while (nsteps-- > 0) { + int X = x; + if (centre) + X += (cellwidth - XTextWidth16(xfs, string, step)) / 2; + + XDrawString16(disp, GDK_DRAWABLE_XID(target), gc, + X, y, string, step); + if (shadowoffset) + XDrawString16(disp, GDK_DRAWABLE_XID(target), gc, + X + shadowoffset, y, string, step); + + x += cellwidth; + string += step; + } } -static void x11font_really_draw_text(GdkDrawable *target, GdkFont *font, - GdkGC *gc, int x, int y, - const gchar *string, int clen, int nchars, - int shadowbold, int shadowoffset, - int fontvariable, int cellwidth) +static void x11font_really_draw_text(GdkDrawable *target, XFontStruct *xfs, + GC gc, int x, int y, + const char *string, int nchars, + int shadowoffset, + int fontvariable, int cellwidth) { - int step = clen * nchars, nsteps = 1, centre = FALSE; + Display *disp = GDK_DISPLAY(); + int step, nsteps, centre; if (fontvariable) { /* * In a variable-pitch font, we draw one character at a * time, and centre it in the character cell. */ - step = clen; + step = 1; nsteps = nchars; centre = TRUE; + } else { + /* + * In a fixed-pitch font, we can draw the whole lot in one go. + */ + step = nchars; + nsteps = 1; + centre = FALSE; } while (nsteps-- > 0) { int X = x; if (centre) - X += (cellwidth - gdk_text_width(font, string, step)) / 2; + X += (cellwidth - XTextWidth(xfs, string, step)) / 2; - gdk_draw_text(target, font, gc, X, y, string, step); - if (shadowbold) - gdk_draw_text(target, font, gc, X + shadowoffset, y, string, step); + XDrawString(disp, GDK_DRAWABLE_XID(target), gc, + X, y, string, step); + if (shadowoffset) + XDrawString(disp, GDK_DRAWABLE_XID(target), gc, + X + shadowoffset, y, string, step); x += cellwidth; string += step; } } -static void x11font_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font, - int x, int y, const char *string, int len, +static void x11font_draw_text(GdkDrawable *target, GdkGC *gdkgc, unifont *font, + int x, int y, const wchar_t *string, int len, int wide, int bold, int cellwidth) { + Display *disp = GDK_DISPLAY(); struct x11font *xfont = (struct x11font *)font; + GC gc = GDK_GC_XGC(gdkgc); int sfid; - int shadowbold = FALSE; + int shadowoffset = 0; int mult = (wide ? 2 : 1); wide -= xfont->wide; bold -= xfont->bold; @@ -393,67 +545,59 @@ /* * Decide which subfont we're using, and whether we have to * use shadow bold. */ if (xfont->shadowalways && bold) { - shadowbold = TRUE; + shadowoffset = xfont->shadowoffset; bold = 0; } sfid = 2 * wide + bold; if (!xfont->allocated[sfid]) x11_alloc_subfont(xfont, sfid); if (bold && !xfont->fonts[sfid]) { bold = 0; - shadowbold = TRUE; + shadowoffset = xfont->shadowoffset; sfid = 2 * wide + bold; if (!xfont->allocated[sfid]) x11_alloc_subfont(xfont, sfid); } if (!xfont->fonts[sfid]) return; /* we've tried our best, but no luck */ + XSetFont(disp, gc, xfont->fonts[sfid]->fid); + if (xfont->sixteen_bit) { /* * This X font has 16-bit character indices, which means - * we expect our string to have been passed in UTF-8. + * we can directly use our Unicode input string. */ XChar2b *xcs; - wchar_t *wcs; - int nchars, maxchars, i; - - /* - * Convert the input string to wide-character Unicode. - */ - maxchars = 0; - for (i = 0; i < len; i++) - if ((unsigned char)string[i] <= 0x7F || - (unsigned char)string[i] >= 0xC0) - maxchars++; - wcs = snewn(maxchars+1, wchar_t); - nchars = charset_to_unicode((char **)&string, &len, wcs, maxchars, - CS_UTF8, NULL, NULL, 0); - assert(nchars <= maxchars); - wcs[nchars] = L'\0'; - - xcs = snewn(nchars, XChar2b); - for (i = 0; i < nchars; i++) { - xcs[i].byte1 = wcs[i] >> 8; - xcs[i].byte2 = wcs[i]; - } - - x11font_really_draw_text(target, xfont->fonts[sfid], gc, x, y, - (gchar *)xcs, 2, nchars, - shadowbold, xfont->shadowoffset, - xfont->variable, cellwidth * mult); - sfree(xcs); - sfree(wcs); - } else { - x11font_really_draw_text(target, xfont->fonts[sfid], gc, x, y, - string, 1, len, - shadowbold, xfont->shadowoffset, - xfont->variable, cellwidth * mult); + int i; + + xcs = snewn(len, XChar2b); + for (i = 0; i < len; i++) { + xcs[i].byte1 = string[i] >> 8; + xcs[i].byte2 = string[i]; + } + + x11font_really_draw_text_16(target, xfont->fonts[sfid], gc, x, y, + xcs, len, shadowoffset, + xfont->variable, cellwidth * mult); + sfree(xcs); + } else { + /* + * This X font has 8-bit indices, so we must convert to the + * appropriate character set. + */ + char *sbstring = snewn(len+1, char); + int sblen = wc_to_mb(xfont->real_charset, 0, string, len, + sbstring, len+1, ".", NULL, NULL); + x11font_really_draw_text(target, xfont->fonts[sfid], gc, x, y, + sbstring, sblen, shadowoffset, + xfont->variable, cellwidth * mult); + sfree(sbstring); } } static void x11font_enum_fonts(GtkWidget *widget, fontsel_add_entry callback, void *callback_ctx) @@ -522,25 +666,25 @@ * with some strange formatting. */ style = p; p += sprintf(p, "%s", components[2][0] ? components[2] : "regular"); - if (!g_strcasecmp(components[3], "i")) + if (!g_ascii_strcasecmp(components[3], "i")) p += sprintf(p, " italic"); - else if (!g_strcasecmp(components[3], "o")) + else if (!g_ascii_strcasecmp(components[3], "o")) p += sprintf(p, " oblique"); - else if (!g_strcasecmp(components[3], "ri")) + else if (!g_ascii_strcasecmp(components[3], "ri")) p += sprintf(p, " reverse italic"); - else if (!g_strcasecmp(components[3], "ro")) + else if (!g_ascii_strcasecmp(components[3], "ro")) p += sprintf(p, " reverse oblique"); - else if (!g_strcasecmp(components[3], "ot")) + else if (!g_ascii_strcasecmp(components[3], "ot")) p += sprintf(p, " other-slant"); - if (components[4][0] && g_strcasecmp(components[4], "normal")) + if (components[4][0] && g_ascii_strcasecmp(components[4], "normal")) p += sprintf(p, " %s", components[4]); - if (!g_strcasecmp(components[10], "m")) + if (!g_ascii_strcasecmp(components[10], "m")) p += sprintf(p, " [M]"); - if (!g_strcasecmp(components[10], "c")) + if (!g_ascii_strcasecmp(components[10], "c")) p += sprintf(p, " [C]"); if (components[5][0]) p += sprintf(p, " %s", components[5]); /* @@ -548,27 +692,27 @@ * couple of transformations done on it to make it * sort more sensibly. */ p++; stylekey = p; - if (!g_strcasecmp(components[2], "medium") || - !g_strcasecmp(components[2], "regular") || - !g_strcasecmp(components[2], "normal") || - !g_strcasecmp(components[2], "book")) + if (!g_ascii_strcasecmp(components[2], "medium") || + !g_ascii_strcasecmp(components[2], "regular") || + !g_ascii_strcasecmp(components[2], "normal") || + !g_ascii_strcasecmp(components[2], "book")) weightkey = 0; - else if (!g_strncasecmp(components[2], "demi", 4) || - !g_strncasecmp(components[2], "semi", 4)) + else if (!g_ascii_strncasecmp(components[2], "demi", 4) || + !g_ascii_strncasecmp(components[2], "semi", 4)) weightkey = 1; else weightkey = 2; - if (!g_strcasecmp(components[3], "r")) + if (!g_ascii_strcasecmp(components[3], "r")) slantkey = 0; - else if (!g_strncasecmp(components[3], "r", 1)) + else if (!g_ascii_strncasecmp(components[3], "r", 1)) slantkey = 2; else slantkey = 1; - if (!g_strcasecmp(components[4], "normal")) + if (!g_ascii_strcasecmp(components[4], "normal")) setwidthkey = 0; else setwidthkey = 1; p += sprintf(p, "%04d%04d%s%04d%04d%s%04d%04d%s%04d%s%04d%s", @@ -636,23 +780,20 @@ * * However, we must carefully avoid canonifying font * _aliases_, unless specifically asked to, because the font * selector treats them as worthwhile in their own right. */ - GdkFont *font = gdk_font_load(name); XFontStruct *xfs; - Display *disp; + Display *disp = GDK_DISPLAY(); Atom fontprop, fontprop2; unsigned long ret; - if (!font) + xfs = XLoadQueryFont(disp, name); + + if (!xfs) return NULL; /* didn't make sense to us, sorry */ - gdk_font_ref(font); - - xfs = GDK_FONT_XFONT(font); - disp = GDK_FONT_XDISPLAY(font); fontprop = XInternAtom(disp, "FONT", False); if (XGetFontProperty(xfs, fontprop, &ret)) { char *newname = XGetAtomName(disp, (Atom)ret); if (newname) { @@ -659,11 +800,11 @@ unsigned long fsize = 12; fontprop2 = XInternAtom(disp, "PIXEL_SIZE", False); if (XGetFontProperty(xfs, fontprop2, &fsize) && fsize > 0) { *size = fsize; - gdk_font_unref(font); + XFreeFont(disp, xfs); if (flags) { if (name[0] == '-' || resolve_aliases) *flags = FONTFLAG_SERVERSIDE; else *flags = FONTFLAG_SERVERALIAS; @@ -672,11 +813,12 @@ newname : name); } } } - gdk_font_unref(font); + XFreeFont(disp, xfs); + return NULL; /* something went wrong */ } static char *x11font_scale_fontname(GtkWidget *widget, const char *name, int size) @@ -692,16 +834,20 @@ #if defined PANGO_PRE_1POINT4 && !defined PANGO_PRE_1POINT6 #define PANGO_PRE_1POINT6 /* make life easier for pre-1.4 folk */ #endif +static int pangofont_has_glyph(unifont *font, wchar_t glyph); static void pangofont_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font, - int x, int y, const char *string, int len, + int x, int y, const wchar_t *string, int len, int wide, int bold, int cellwidth); static unifont *pangofont_create(GtkWidget *widget, const char *name, int wide, int bold, int shadowoffset, int shadowalways); +static unifont *pangofont_create_fallback(GtkWidget *widget, int height, + int wide, int bold, + int shadowoffset, int shadowalways); static void pangofont_destroy(unifont *font); static void pangofont_enum_fonts(GtkWidget *widget, fontsel_add_entry callback, void *callback_ctx); static char *pangofont_canonify_fontname(GtkWidget *widget, const char *name, int *size, int *flags, @@ -726,11 +872,13 @@ int bold, shadowoffset, shadowalways; }; static const struct unifont_vtable pangofont_vtable = { pangofont_create, + pangofont_create_fallback, pangofont_destroy, + pangofont_has_glyph, pangofont_draw_text, pangofont_enum_fonts, pangofont_canonify_fontname, pangofont_scale_fontname, "client", @@ -772,46 +920,34 @@ pango_context_list_families(ctx, &families, &nfamilies); #endif matched = FALSE; for (i = 0; i < nfamilies; i++) { - if (!g_strcasecmp(pango_font_family_get_name(families[i]), - pango_font_description_get_family(desc))) { + if (!g_ascii_strcasecmp(pango_font_family_get_name(families[i]), + pango_font_description_get_family(desc))) { matched = TRUE; break; } } g_free(families); return matched; } -static unifont *pangofont_create(GtkWidget *widget, const char *name, - int wide, int bold, - int shadowoffset, int shadowalways) +static unifont *pangofont_create_internal(GtkWidget *widget, + PangoContext *ctx, + PangoFontDescription *desc, + int wide, int bold, + int shadowoffset, int shadowalways) { struct pangofont *pfont; - PangoContext *ctx; #ifndef PANGO_PRE_1POINT6 PangoFontMap *map; #endif - PangoFontDescription *desc; PangoFontset *fset; PangoFontMetrics *metrics; - desc = pango_font_description_from_string(name); - if (!desc) - return NULL; - ctx = gtk_widget_get_pango_context(widget); - if (!ctx) { - pango_font_description_free(desc); - return NULL; - } - if (!pangofont_check_desc_makes_sense(ctx, desc)) { - pango_font_description_free(desc); - return NULL; - } #ifndef PANGO_PRE_1POINT6 map = pango_context_get_font_map(ctx); if (!map) { pango_font_description_free(desc); return NULL; @@ -839,13 +975,13 @@ pfont->u.width = PANGO_PIXELS(pango_font_metrics_get_approximate_digit_width(metrics)); pfont->u.ascent = PANGO_PIXELS(pango_font_metrics_get_ascent(metrics)); pfont->u.descent = PANGO_PIXELS(pango_font_metrics_get_descent(metrics)); pfont->u.height = pfont->u.ascent + pfont->u.descent; + pfont->u.want_fallback = FALSE; /* The Pango API is hardwired to UTF-8 */ pfont->u.public_charset = CS_UTF8; - pfont->u.real_charset = CS_UTF8; pfont->desc = desc; pfont->fset = fset; pfont->widget = widget; pfont->bold = bold; pfont->shadowoffset = shadowoffset; @@ -853,26 +989,77 @@ pango_font_metrics_unref(metrics); return (unifont *)pfont; } + +static unifont *pangofont_create(GtkWidget *widget, const char *name, + int wide, int bold, + int shadowoffset, int shadowalways) +{ + PangoContext *ctx; + PangoFontDescription *desc; + + desc = pango_font_description_from_string(name); + if (!desc) + return NULL; + ctx = gtk_widget_get_pango_context(widget); + if (!ctx) { + pango_font_description_free(desc); + return NULL; + } + if (!pangofont_check_desc_makes_sense(ctx, desc)) { + pango_font_description_free(desc); + return NULL; + } + return pangofont_create_internal(widget, ctx, desc, wide, bold, + shadowoffset, shadowalways); +} + +static unifont *pangofont_create_fallback(GtkWidget *widget, int height, + int wide, int bold, + int shadowoffset, int shadowalways) +{ + PangoContext *ctx; + PangoFontDescription *desc; + + desc = pango_font_description_from_string("Monospace"); + if (!desc) + return NULL; + ctx = gtk_widget_get_pango_context(widget); + if (!ctx) { + pango_font_description_free(desc); + return NULL; + } + pango_font_description_set_absolute_size(desc, height * PANGO_SCALE); + return pangofont_create_internal(widget, ctx, desc, wide, bold, + shadowoffset, shadowalways); +} static void pangofont_destroy(unifont *font) { struct pangofont *pfont = (struct pangofont *)font; pango_font_description_free(pfont->desc); g_object_unref(pfont->fset); sfree(font); } + +static int pangofont_has_glyph(unifont *font, wchar_t glyph) +{ + /* Pango implements font fallback, so assume it has everything */ + return TRUE; +} static void pangofont_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font, - int x, int y, const char *string, int len, + int x, int y, const wchar_t *string, int len, int wide, int bold, int cellwidth) { struct pangofont *pfont = (struct pangofont *)font; PangoLayout *layout; PangoRectangle rect; + char *utfstring, *utfptr; + int utflen; int shadowbold = FALSE; if (wide) cellwidth *= 2; @@ -889,11 +1076,20 @@ pango_font_description_set_weight(desc2, PANGO_WEIGHT_BOLD); pango_layout_set_font_description(layout, desc2); } } - while (len > 0) { + /* + * Pango always expects UTF-8, so convert the input wide character + * string to UTF-8. + */ + utfstring = snewn(len*6+1, char); /* UTF-8 has max 6 bytes/char */ + utflen = wc_to_mb(CS_UTF8, 0, string, len, + utfstring, len*6+1, ".", NULL, NULL); + + utfptr = utfstring; + while (utflen > 0) { int clen, n; /* * We want to display every character from this string in * the centre of its own character cell. In the worst case, @@ -921,57 +1117,68 @@ /* * Start by extracting a single UTF-8 character from the * string. */ clen = 1; - while (clen < len && - (unsigned char)string[clen] >= 0x80 && - (unsigned char)string[clen] < 0xC0) + while (clen < utflen && + (unsigned char)utfptr[clen] >= 0x80 && + (unsigned char)utfptr[clen] < 0xC0) clen++; n = 1; - /* - * See if that character has the width we expect. - */ - pango_layout_set_text(layout, string, clen); - pango_layout_get_pixel_extents(layout, NULL, &rect); - - if (rect.width == cellwidth) { - /* - * Try extracting more characters, for as long as they - * stay well-behaved. - */ - while (clen < len) { - int oldclen = clen; - clen++; /* skip UTF-8 introducer byte */ - while (clen < len && - (unsigned char)string[clen] >= 0x80 && - (unsigned char)string[clen] < 0xC0) - clen++; - n++; - pango_layout_set_text(layout, string, clen); - pango_layout_get_pixel_extents(layout, NULL, &rect); - if (rect.width != n * cellwidth) { - clen = oldclen; - n--; - break; - } - } - } - - pango_layout_set_text(layout, string, clen); + /* + * If it's a right-to-left character, we must display it on + * its own, to stop Pango helpfully re-reversing our already + * reversed text. + */ + if (!is_rtl(string[0])) { + + /* + * See if that character has the width we expect. + */ + pango_layout_set_text(layout, utfptr, clen); + pango_layout_get_pixel_extents(layout, NULL, &rect); + + if (rect.width == cellwidth) { + /* + * Try extracting more characters, for as long as they + * stay well-behaved. + */ + while (clen < utflen) { + int oldclen = clen; + clen++; /* skip UTF-8 introducer byte */ + while (clen < utflen && + (unsigned char)utfptr[clen] >= 0x80 && + (unsigned char)utfptr[clen] < 0xC0) + clen++; + n++; + pango_layout_set_text(layout, utfptr, clen); + pango_layout_get_pixel_extents(layout, NULL, &rect); + if (rect.width != n * cellwidth) { + clen = oldclen; + n--; + break; + } + } + } + } + + pango_layout_set_text(layout, utfptr, clen); pango_layout_get_pixel_extents(layout, NULL, &rect); gdk_draw_layout(target, gc, x + (n*cellwidth - rect.width)/2, y + (pfont->u.height - rect.height)/2, layout); if (shadowbold) gdk_draw_layout(target, gc, x + (n*cellwidth - rect.width)/2 + pfont->shadowoffset, y + (pfont->u.height - rect.height)/2, layout); - len -= clen; - string += clen; + utflen -= clen; + utfptr += clen; + string += n; x += n * cellwidth; } + + sfree(utfstring); g_object_unref(layout); } /* @@ -1231,10 +1438,12 @@ * Complete list of font-type subclasses. Listed in preference * order for unifont_create(). (That is, in the extremely unlikely * event that the same font name is valid as both a Pango and an * X11 font, it will be interpreted as the former in the absence * of an explicit type-disambiguating prefix.) + * + * The 'multifont' subclass is omitted here, as discussed above. */ static const struct unifont_vtable *unifont_types[] = { #if GTK_CHECK_VERSION(2,0,0) &pangofont_vtable, #endif @@ -1307,16 +1516,134 @@ { font->vt->destroy(font); } void unifont_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font, - int x, int y, const char *string, int len, + int x, int y, const wchar_t *string, int len, int wide, int bold, int cellwidth) { font->vt->draw_text(target, gc, font, x, y, string, len, wide, bold, cellwidth); } + +/* ---------------------------------------------------------------------- + * Multiple-font wrapper. This is a type of unifont which encapsulates + * up to two other unifonts, permitting missing glyphs in the main + * font to be filled in by a fallback font. + * + * This is a type of unifont just like the previous two, but it has a + * separate constructor which is manually called by the client, so it + * doesn't appear in the list of available font types enumerated by + * unifont_create. This means it's not used by unifontsel either, so + * it doesn't need to support any methods except draw_text and + * destroy. + */ + +static void multifont_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font, + int x, int y, const wchar_t *string, int len, + int wide, int bold, int cellwidth); +static void multifont_destroy(unifont *font); + +struct multifont { + struct unifont u; + unifont *main; + unifont *fallback; +}; + +static const struct unifont_vtable multifont_vtable = { + NULL, /* creation is done specially */ + NULL, + multifont_destroy, + NULL, + multifont_draw_text, + NULL, + NULL, + NULL, + "client", +}; + +unifont *multifont_create(GtkWidget *widget, const char *name, + int wide, int bold, + int shadowoffset, int shadowalways) +{ + int i; + unifont *font, *fallback; + struct multifont *mfont; + + font = unifont_create(widget, name, wide, bold, + shadowoffset, shadowalways); + if (!font) + return NULL; + + fallback = NULL; + if (font->want_fallback) { + for (i = 0; i < lenof(unifont_types); i++) { + if (unifont_types[i]->create_fallback) { + fallback = unifont_types[i]->create_fallback + (widget, font->height, wide, bold, + shadowoffset, shadowalways); + if (fallback) + break; + } + } + } + + /* + * Construct our multifont. Public members are all copied from the + * primary font we're wrapping. + */ + mfont = snew(struct multifont); + mfont->u.vt = &multifont_vtable; + mfont->u.width = font->width; + mfont->u.ascent = font->ascent; + mfont->u.descent = font->descent; + mfont->u.height = font->height; + mfont->u.public_charset = font->public_charset; + mfont->u.want_fallback = FALSE; /* shouldn't be needed, but just in case */ + mfont->main = font; + mfont->fallback = fallback; + + return (unifont *)mfont; +} + +static void multifont_destroy(unifont *font) +{ + struct multifont *mfont = (struct multifont *)font; + unifont_destroy(mfont->main); + if (mfont->fallback) + unifont_destroy(mfont->fallback); + sfree(font); +} + +static void multifont_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font, + int x, int y, const wchar_t *string, int len, + int wide, int bold, int cellwidth) +{ + struct multifont *mfont = (struct multifont *)font; + int ok, i; + + while (len > 0) { + /* + * Find a maximal sequence of characters which are, or are + * not, supported by our main font. + */ + ok = mfont->main->vt->has_glyph(mfont->main, string[0]); + for (i = 1; + i < len && + !mfont->main->vt->has_glyph(mfont->main, string[i]) == !ok; + i++); + + /* + * Now display it. + */ + unifont_draw_text(target, gc, ok ? mfont->main : mfont->fallback, + x, y, string, i, wide, bold, cellwidth); + string += i; + len -= i; + x += i * cellwidth; + } +} #if GTK_CHECK_VERSION(2,0,0) /* ---------------------------------------------------------------------- * Implementation of a unified font selector. Used on GTK 2 only; @@ -1391,11 +1718,11 @@ return 0; /* * Otherwise, ordinary strcasecmp. */ - return g_strcasecmp(a, b); + return g_ascii_strcasecmp(a, b); } static int fontinfo_realname_compare(void *av, void *bv) { fontinfo *a = (fontinfo *)av; @@ -1690,15 +2017,15 @@ * that they go to the effort of selecting their font * and _then_ realise it was a mistake. */ info->fontclass->draw_text(fs->preview_pixmap, gc, font, 0, font->ascent, - "bankrupt jilted showmen quiz convex fogey", + L"bankrupt jilted showmen quiz convex fogey", 41, FALSE, FALSE, font->width); info->fontclass->draw_text(fs->preview_pixmap, gc, font, 0, font->ascent + font->height, - "BANKRUPT JILTED SHOWMEN QUIZ CONVEX FOGEY", + L"BANKRUPT JILTED SHOWMEN QUIZ CONVEX FOGEY", 41, FALSE, FALSE, font->width); /* * The ordering of punctuation here is also selected * with some specific aims in mind. I put ` and ' * together because some software (and people) still @@ -1710,11 +2037,11 @@ * alphabetic character (since that's how it's often * used in practice, at least by programmers). */ info->fontclass->draw_text(fs->preview_pixmap, gc, font, 0, font->ascent + font->height * 2, - "0123456789!?,.:;<>()[]{}\\/`'\"+*-=~#_@|%&^$", + L"0123456789!?,.:;<>()[]{}\\/`'\"+*-=~#_@|%&^$", 42, FALSE, FALSE, font->width); } gdk_gc_unref(gc); gdk_window_invalidate_rect(fs->preview_area->window, NULL, FALSE); } @@ -1961,18 +2288,20 @@ * Search in the tree to find the fontinfo structure which * best approximates the size the user last requested. */ below = findrelpos234(fs->fonts_by_selorder, &info2, NULL, REL234_LE, &pos); + if (!below) + pos = -1; above = index234(fs->fonts_by_selorder, pos+1); /* * See if we've found it exactly, which is an easy special * case. If we have, it'll be in `below' and not `above', * because we did a REL234_LE rather than REL234_LT search. */ - if (!fontinfo_selorder_compare(&info2, below)) + if (below && !fontinfo_selorder_compare(&info2, below)) return below; /* * Now we've either found two suitable fonts, one smaller and * one larger, or we're at one or other extreme end of the Index: unix/gtkfont.h ================================================================== --- unix/gtkfont.h +++ unix/gtkfont.h @@ -19,33 +19,47 @@ */ /* * public_charset is the charset used when the user asks for * `Use font encoding'. - * - * real_charset is the charset used when translating text into - * a form suitable for sending to unifont_draw_text(). - * - * They can differ. For example, public_charset might be - * CS_ISO8859_1 while real_charset is CS_ISO8859_1_X11. */ - int public_charset, real_charset; + int public_charset; /* * Font dimensions needed by clients. */ int width, height, ascent, descent; + + /* + * Indicates whether this font is capable of handling all glyphs + * (Pango fonts can do this because Pango automatically supplies + * missing glyphs from other fonts), or whether it would like a + * fallback font to cope with missing glyphs. + */ + int want_fallback; } unifont; unifont *unifont_create(GtkWidget *widget, const char *name, int wide, int bold, int shadowoffset, int shadowalways); void unifont_destroy(unifont *font); void unifont_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font, - int x, int y, const char *string, int len, + int x, int y, const wchar_t *string, int len, int wide, int bold, int cellwidth); +/* + * This function behaves exactly like the low-level unifont_create, + * except that as well as the requested font it also allocates (if + * necessary) a fallback font for filling in replacement glyphs. + * + * Return value is usable with unifont_destroy and unifont_draw_text + * as if it were an ordinary unifont. + */ +unifont *multifont_create(GtkWidget *widget, const char *name, + int wide, int bold, + int shadowoffset, int shadowalways); + /* * Unified font selector dialog. I can't be bothered to do a * proper GTK subclassing today, so this will just be an ordinary * data structure with some useful members. * Index: unix/gtkwin.c ================================================================== --- unix/gtkwin.c +++ unix/gtkwin.c @@ -11,10 +11,11 @@ #include #include #include #include #include +#include #include #include #include #include #include @@ -21,12 +22,18 @@ #include #include #include #include #include + +#if GTK_CHECK_VERSION(2,0,0) +#include +#endif #define PUTTY_DO_GLOBALS /* actually _define_ globals */ + +#define MAY_REFER_TO_GTK_IN_HEADERS #include "putty.h" #include "terminal.h" #include "gtkfont.h" @@ -43,11 +50,10 @@ #define LONG_TO_GPOINTER(l) ((gpointer)(long)(l)) #define GPOINTER_TO_LONG(p) ((long)(p)) #endif /* Colours come in two flavours: configurable, and xterm-extended. */ -#define NCFGCOLOURS (lenof(((Config *)0)->colours)) #define NEXTCOLOURS 240 /* 216 colour-cube plus 24 shades of grey */ #define NALLCOLOURS (NCFGCOLOURS + NEXTCOLOURS) GdkAtom compound_text_atom, utf8_string_atom; @@ -67,10 +73,13 @@ GtkAdjustment *sbar_adjust; GtkWidget *menu, *specialsmenu, *specialsitem1, *specialsitem2, *restartitem; GtkWidget *sessionsmenu; GdkPixmap *pixmap; +#if GTK_CHECK_VERSION(2,0,0) + GtkIMContext *imc; +#endif unifont *fonts[4]; /* normal, bold, wide, widebold */ int xpos, ypos, gotpos, gravity; GdkCursor *rawcursor, *textcursor, *blankcursor, *waitcursor, *currcursor; GdkColor cols[NALLCOLOURS]; GdkColormap *colmap; @@ -82,31 +91,41 @@ int font_width, font_height; int width, height; int ignore_sbar; int mouseptr_visible; int busy_status; - guint term_paste_idle_id; - guint term_exit_idle_id; + guint toplevel_callback_idle_id; int alt_keycode; int alt_digits; - char wintitle[sizeof(((Config *)0)->wintitle)]; - char icontitle[sizeof(((Config *)0)->wintitle)]; + char *wintitle; + char *icontitle; int master_fd, master_func_id; void *ldisc; Backend *back; void *backhandle; Terminal *term; void *logctx; int exited; struct unicode_data ucsdata; - Config cfg; + Conf *conf; void *eventlogstuff; char *progname, **gtkargvstart; int ngtkargs; guint32 input_event_time; /* Timestamp of the most recent input event. */ int reconfiguring; + /* Cached things out of conf that we refer to a lot */ + int bold_style; + int window_border; + int cursor_type; }; + +static void cache_conf_values(struct gui_data *inst) +{ + inst->bold_style = conf_get_int(inst->conf, CONF_bold_style); + inst->window_border = conf_get_int(inst->conf, CONF_window_border); + inst->cursor_type = conf_get_int(inst->conf, CONF_cursor_type); +} struct draw_ctx { GdkGC *gc; struct gui_data *inst; }; @@ -114,10 +133,11 @@ static int send_raw_mouse; static char *app_name = "pterm"; static void start_backend(struct gui_data *inst); +static void exit_callback(void *vinst); char *x_get_default(const char *key) { return XGetDefault(GDK_DISPLAY(), app_name, key); } @@ -129,38 +149,33 @@ va_list ap; char *msg; va_start(ap, p); msg = dupvprintf(p, ap); va_end(ap); - inst->exited = TRUE; fatal_message_box(inst->window, msg); sfree(msg); - if (inst->cfg.close_on_exit == FORCE_ON) - cleanup_exit(1); + + queue_toplevel_callback(exit_callback, inst); } /* * Default settings that are specific to pterm. */ -FontSpec platform_default_fontspec(const char *name) +FontSpec *platform_default_fontspec(const char *name) { - FontSpec ret; if (!strcmp(name, "Font")) - strcpy(ret.name, "server:fixed"); + return fontspec_new("server:fixed"); else - *ret.name = '\0'; - return ret; + return fontspec_new(""); } -Filename platform_default_filename(const char *name) +Filename *platform_default_filename(const char *name) { - Filename ret; if (!strcmp(name, "LogFileName")) - strcpy(ret.path, "putty.log"); + return filename_from_str("putty.log"); else - *ret.path = '\0'; - return ret; + return filename_from_str(""); } char *platform_default_s(const char *name) { if (!strcmp(name, "SerialLine")) @@ -197,10 +212,15 @@ int from_backend_untrusted(void *frontend, const char *data, int len) { struct gui_data *inst = (struct gui_data *)frontend; return term_data_untrusted(inst->term, data, len); } + +int from_backend_eof(void *frontend) +{ + return TRUE; /* do respond to incoming EOF with outgoing */ +} int get_userpass_input(prompts_t *p, unsigned char *in, int inlen) { struct gui_data *inst = (struct gui_data *)p->frontend; int ret; @@ -392,11 +412,11 @@ } gint delete_window(GtkWidget *widget, GdkEvent *event, gpointer data) { struct gui_data *inst = (struct gui_data *)data; - if (!inst->exited && inst->cfg.warn_on_close) { + if (!inst->exited && conf_get_int(inst->conf, CONF_warn_on_close)) { if (!reallyclose(inst)) return TRUE; } return FALSE; } @@ -423,11 +443,11 @@ } } static void show_mouseptr(struct gui_data *inst, int show) { - if (!inst->cfg.hide_mouseptr) + if (!conf_get_int(inst->conf, CONF_hide_mouseptr)) show = 1; inst->mouseptr_visible = show; update_mouseptr(inst); } @@ -434,12 +454,12 @@ void draw_backing_rect(struct gui_data *inst) { GdkGC *gc = gdk_gc_new(inst->area->window); gdk_gc_set_foreground(gc, &inst->cols[258]); /* default background */ gdk_draw_rectangle(inst->pixmap, gc, 1, 0, 0, - inst->cfg.width * inst->font_width + 2*inst->cfg.window_border, - inst->cfg.height * inst->font_height + 2*inst->cfg.window_border); + inst->width * inst->font_width + 2*inst->window_border, + inst->height * inst->font_height + 2*inst->window_border); gdk_gc_unref(gc); } gint configure_area(GtkWidget *widget, GdkEventConfigure *event, gpointer data) { @@ -448,37 +468,41 @@ /* * See if the terminal size has changed, in which case we must * let the terminal know. */ - w = (event->width - 2*inst->cfg.window_border) / inst->font_width; - h = (event->height - 2*inst->cfg.window_border) / inst->font_height; + w = (event->width - 2*inst->window_border) / inst->font_width; + h = (event->height - 2*inst->window_border) / inst->font_height; if (w != inst->width || h != inst->height) { - inst->cfg.width = inst->width = w; - inst->cfg.height = inst->height = h; + inst->width = w; + inst->height = h; + conf_set_int(inst->conf, CONF_width, inst->width); + conf_set_int(inst->conf, CONF_height, inst->height); need_size = 1; } if (inst->pixmap) { gdk_pixmap_unref(inst->pixmap); inst->pixmap = NULL; } inst->pixmap = gdk_pixmap_new(widget->window, - (inst->cfg.width * inst->font_width + - 2*inst->cfg.window_border), - (inst->cfg.height * inst->font_height + - 2*inst->cfg.window_border), -1); + (w * inst->font_width + 2*inst->window_border), + (h * inst->font_height + 2*inst->window_border), -1); draw_backing_rect(inst); if (need_size && inst->term) { - term_size(inst->term, h, w, inst->cfg.savelines); + term_size(inst->term, h, w, conf_get_int(inst->conf, CONF_savelines)); } if (inst->term) term_invalidate(inst->term); + +#if GTK_CHECK_VERSION(2,0,0) + gtk_im_context_set_client_window(inst->imc, widget->window); +#endif return TRUE; } gint expose_area(GtkWidget *widget, GdkEventExpose *event, gpointer data) @@ -507,10 +531,11 @@ { struct gui_data *inst = (struct gui_data *)data; char output[256]; wchar_t ucsoutput[2]; int ucsval, start, end, special, output_charset, use_ucsoutput; + int nethack_mode, app_keypad_mode; /* Remember the timestamp. */ inst->input_event_time = event->time; /* By default, nothing is generated. */ @@ -526,25 +551,30 @@ * pressed - I don't think Alt+NumPad4 should be ^D or that * Alt+NumPad3 should be ^C, for example. There's no serious * inconvenience in having to type a zero before a single-digit * character code. */ - if (event->type == GDK_KEY_RELEASE && - (event->keyval == GDK_Meta_L || event->keyval == GDK_Alt_L || - event->keyval == GDK_Meta_R || event->keyval == GDK_Alt_R) && - inst->alt_keycode >= 0 && inst->alt_digits > 1) { + if (event->type == GDK_KEY_RELEASE) { + if ((event->keyval == GDK_Meta_L || event->keyval == GDK_Alt_L || + event->keyval == GDK_Meta_R || event->keyval == GDK_Alt_R) && + inst->alt_keycode >= 0 && inst->alt_digits > 1) { #ifdef KEY_DEBUGGING - printf("Alt key up, keycode = %d\n", inst->alt_keycode); + printf("Alt key up, keycode = %d\n", inst->alt_keycode); #endif - /* - * FIXME: we might usefully try to do something clever here - * about interpreting the generated key code in a way that's - * appropriate to the line code page. - */ - output[0] = inst->alt_keycode; - end = 1; - goto done; + /* + * FIXME: we might usefully try to do something clever here + * about interpreting the generated key code in a way that's + * appropriate to the line code page. + */ + output[0] = inst->alt_keycode; + end = 1; + goto done; + } +#if GTK_CHECK_VERSION(2,0,0) + if (gtk_im_context_filter_keypress(inst->imc, event)) + return TRUE; +#endif } if (event->type == GDK_KEY_PRESS) { #ifdef KEY_DEBUGGING { @@ -616,19 +646,19 @@ /* * Shift-PgUp and Shift-PgDn don't even generate keystrokes * at all. */ if (event->keyval == GDK_Page_Up && (event->state & GDK_SHIFT_MASK)) { - term_scroll(inst->term, 0, -inst->cfg.height/2); + term_scroll(inst->term, 0, -inst->height/2); return TRUE; } if (event->keyval == GDK_Page_Up && (event->state & GDK_CONTROL_MASK)) { term_scroll(inst->term, 0, -1); return TRUE; } if (event->keyval == GDK_Page_Down && (event->state & GDK_SHIFT_MASK)) { - term_scroll(inst->term, 0, +inst->cfg.height/2); + term_scroll(inst->term, 0, +inst->height/2); return TRUE; } if (event->keyval == GDK_Page_Down && (event->state & GDK_CONTROL_MASK)) { term_scroll(inst->term, 0, +1); return TRUE; @@ -643,10 +673,14 @@ } special = FALSE; use_ucsoutput = FALSE; + nethack_mode = conf_get_int(inst->conf, CONF_nethack_keypad); + app_keypad_mode = (inst->term->app_keypad_keys && + !conf_get_int(inst->conf, CONF_no_applic_k)); + /* ALT+things gives leading Escape. */ output[0] = '\033'; #if !GTK_CHECK_VERSION(2,0,0) /* * In vanilla X, and hence also GDK 1.2, the string received @@ -656,17 +690,77 @@ * confirmation.) */ output_charset = CS_ISO8859_1; strncpy(output+1, event->string, lenof(output)-1); #else + /* + * Most things can now be passed to + * gtk_im_context_filter_keypress without breaking anything + * below this point. An exception is the numeric keypad if + * we're in Nethack or application mode: the IM will eat + * numeric keypad presses if Num Lock is on, but we don't want + * it to. + */ + if (app_keypad_mode && + (event->keyval == GDK_Num_Lock || + event->keyval == GDK_KP_Divide || + event->keyval == GDK_KP_Multiply || + event->keyval == GDK_KP_Subtract || + event->keyval == GDK_KP_Add || + event->keyval == GDK_KP_Enter || + event->keyval == GDK_KP_0 || + event->keyval == GDK_KP_Insert || + event->keyval == GDK_KP_1 || + event->keyval == GDK_KP_End || + event->keyval == GDK_KP_2 || + event->keyval == GDK_KP_Down || + event->keyval == GDK_KP_3 || + event->keyval == GDK_KP_Page_Down || + event->keyval == GDK_KP_4 || + event->keyval == GDK_KP_Left || + event->keyval == GDK_KP_5 || + event->keyval == GDK_KP_Begin || + event->keyval == GDK_KP_6 || + event->keyval == GDK_KP_Right || + event->keyval == GDK_KP_7 || + event->keyval == GDK_KP_Home || + event->keyval == GDK_KP_8 || + event->keyval == GDK_KP_Up || + event->keyval == GDK_KP_9 || + event->keyval == GDK_KP_Page_Up || + event->keyval == GDK_KP_Decimal || + event->keyval == GDK_KP_Delete)) { + /* app keypad; do nothing */ + } else if (nethack_mode && + (event->keyval == GDK_KP_1 || + event->keyval == GDK_KP_End || + event->keyval == GDK_KP_2 || + event->keyval == GDK_KP_Down || + event->keyval == GDK_KP_3 || + event->keyval == GDK_KP_Page_Down || + event->keyval == GDK_KP_4 || + event->keyval == GDK_KP_Left || + event->keyval == GDK_KP_5 || + event->keyval == GDK_KP_Begin || + event->keyval == GDK_KP_6 || + event->keyval == GDK_KP_Right || + event->keyval == GDK_KP_7 || + event->keyval == GDK_KP_Home || + event->keyval == GDK_KP_8 || + event->keyval == GDK_KP_Up || + event->keyval == GDK_KP_9 || + event->keyval == GDK_KP_Page_Up)) { + /* nethack mode; do nothing */ + } else { + if (gtk_im_context_filter_keypress(inst->imc, event)) + return TRUE; + } + /* * GDK 2.0 arranges to have done some translation for us: in * GDK 2.0, event->string is encoded in the current locale. * - * (However, it's also deprecated; we really ought to be - * using a GTKIMContext.) - * * So we use the standard C library function mbstowcs() to * convert from the current locale into Unicode; from there * we can convert to whatever PuTTY is currently working in. * (In fact I convert straight back to UTF-8 from * wide-character Unicode, for the sake of simplicity: that @@ -673,11 +767,12 @@ * way we can still use exactly the same code to manipulate * the string, such as prefixing ESC.) */ output_charset = CS_UTF8; { - wchar_t widedata[32], *wp; + wchar_t widedata[32]; + const wchar_t *wp; int wlen; int ulen; wlen = mb_to_wc(DEFAULT_CODEPAGE, 0, event->string, strlen(event->string), @@ -753,19 +848,21 @@ } /* We don't let GTK tell us what Backspace is! We know better. */ if (event->keyval == GDK_BackSpace && !(event->state & GDK_SHIFT_MASK)) { - output[1] = inst->cfg.bksp_is_delete ? '\x7F' : '\x08'; + output[1] = conf_get_int(inst->conf, CONF_bksp_is_delete) ? + '\x7F' : '\x08'; use_ucsoutput = FALSE; end = 2; special = TRUE; } /* For Shift Backspace, do opposite of what is configured. */ if (event->keyval == GDK_BackSpace && (event->state & GDK_SHIFT_MASK)) { - output[1] = inst->cfg.bksp_is_delete ? '\x08' : '\x7F'; + output[1] = conf_get_int(inst->conf, CONF_bksp_is_delete) ? + '\x08' : '\x7F'; use_ucsoutput = FALSE; end = 2; special = TRUE; } @@ -784,11 +881,11 @@ } /* * NetHack keypad mode. */ - if (inst->cfg.nethack_keypad) { + if (nethack_mode) { char *keys = NULL; switch (event->keyval) { case GDK_KP_1: case GDK_KP_End: keys = "bB\002"; break; case GDK_KP_2: case GDK_KP_Down: keys = "jJ\012"; break; case GDK_KP_3: case GDK_KP_Page_Down: keys = "nN\016"; break; @@ -813,11 +910,11 @@ } /* * Application keypad mode. */ - if (inst->term->app_keypad_keys && !inst->cfg.no_applic_k) { + if (app_keypad_mode) { int xkey = 0; switch (event->keyval) { case GDK_Num_Lock: xkey = 'P'; break; case GDK_KP_Divide: xkey = 'Q'; break; case GDK_KP_Multiply: xkey = 'R'; break; @@ -827,11 +924,11 @@ * be taken up on the VT100 by _two_ keys; so we * let Shift select between the two. Worse still, * in xterm function key mode we change which two... */ case GDK_KP_Add: - if (inst->cfg.funky_type == FUNKY_XTERM) { + if (conf_get_int(inst->conf, CONF_funky_type) == FUNKY_XTERM) { if (event->state & GDK_SHIFT_MASK) xkey = 'l'; else xkey = 'k'; } else if (event->state & GDK_SHIFT_MASK) @@ -874,10 +971,11 @@ * does replace Home and End (1~ and 4~) by ESC [ H and ESC O w * respectively. */ { int code = 0; + int funky_type = conf_get_int(inst->conf, CONF_funky_type); switch (event->keyval) { case GDK_F1: code = (event->state & GDK_SHIFT_MASK ? 23 : 11); break; case GDK_F2: @@ -957,20 +1055,20 @@ case GDK_Page_Down: case GDK_KP_Page_Down: code = 6; break; } /* Reorder edit keys to physical order */ - if (inst->cfg.funky_type == FUNKY_VT400 && code <= 6) + if (funky_type == FUNKY_VT400 && code <= 6) code = "\0\2\1\4\5\3\6"[code]; if (inst->term->vt52_mode && code > 0 && code <= 6) { end = 1 + sprintf(output+1, "\x1B%c", " HLMEIG"[code]); use_ucsoutput = FALSE; goto done; } - if (inst->cfg.funky_type == FUNKY_SCO && /* SCO function keys */ + if (funky_type == FUNKY_SCO && /* SCO function keys */ code >= 11 && code <= 34) { char codes[] = "MNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz@[\\]^_`{"; int index = 0; switch (event->keyval) { case GDK_F1: index = 0; break; @@ -990,11 +1088,11 @@ if (event->state & GDK_CONTROL_MASK) index += 24; end = 1 + sprintf(output+1, "\x1B[%c", codes[index]); use_ucsoutput = FALSE; goto done; } - if (inst->cfg.funky_type == FUNKY_SCO && /* SCO small keypad */ + if (funky_type == FUNKY_SCO && /* SCO small keypad */ code >= 1 && code <= 6) { char codes[] = "HL.FIG"; if (code == 3) { output[1] = '\x7F'; end = 2; @@ -1002,11 +1100,11 @@ end = 1 + sprintf(output+1, "\x1B[%c", codes[code-1]); } use_ucsoutput = FALSE; goto done; } - if ((inst->term->vt52_mode || inst->cfg.funky_type == FUNKY_VT100P) && + if ((inst->term->vt52_mode || funky_type == FUNKY_VT100P) && code >= 11 && code <= 24) { int offt = 0; if (code > 15) offt++; if (code > 21) @@ -1018,24 +1116,25 @@ end = 1 + sprintf(output+1, "\x1BO%c", code + 'P' - 11 - offt); use_ucsoutput = FALSE; goto done; } - if (inst->cfg.funky_type == FUNKY_LINUX && code >= 11 && code <= 15) { + if (funky_type == FUNKY_LINUX && code >= 11 && code <= 15) { end = 1 + sprintf(output+1, "\x1B[[%c", code + 'A' - 11); use_ucsoutput = FALSE; goto done; } - if (inst->cfg.funky_type == FUNKY_XTERM && code >= 11 && code <= 14) { + if (funky_type == FUNKY_XTERM && code >= 11 && code <= 14) { if (inst->term->vt52_mode) end = 1 + sprintf(output+1, "\x1B%c", code + 'P' - 11); else end = 1 + sprintf(output+1, "\x1BO%c", code + 'P' - 11); use_ucsoutput = FALSE; goto done; } - if (inst->cfg.rxvt_homeend && (code == 1 || code == 4)) { + if ((code == 1 || code == 4) && + conf_get_int(inst->conf, CONF_rxvt_homeend)) { end = 1 + sprintf(output+1, code == 1 ? "\x1B[H" : "\x1BOw"); use_ucsoutput = FALSE; goto done; } if (code) { @@ -1116,10 +1215,21 @@ term_seen_key_event(inst->term); } return TRUE; } + +#if GTK_CHECK_VERSION(2,0,0) +void input_method_commit_event(GtkIMContext *imc, gchar *str, gpointer data) +{ + struct gui_data *inst = (struct gui_data *)data; + if (inst->ldisc) + lpage_send(inst->ldisc, CS_UTF8, str, strlen(str), 1); + show_mouseptr(inst, 0); + term_seen_key_event(inst->term); +} +#endif gboolean button_internal(struct gui_data *inst, guint32 timestamp, GdkEventType type, guint ebutton, guint state, gdouble ex, gdouble ey) { @@ -1164,16 +1274,17 @@ case GDK_2BUTTON_PRESS: act = MA_2CLK; break; case GDK_3BUTTON_PRESS: act = MA_3CLK; break; default: return FALSE; /* don't know this event type */ } - if (send_raw_mouse && !(inst->cfg.mouse_override && shift) && + if (send_raw_mouse && !(shift && conf_get_int(inst->conf, + CONF_mouse_override)) && act != MA_CLICK && act != MA_RELEASE) return TRUE; /* we ignore these in raw mouse mode */ - x = (ex - inst->cfg.window_border) / inst->font_width; - y = (ey - inst->cfg.window_border) / inst->font_height; + x = (ex - inst->window_border) / inst->font_width; + y = (ey - inst->window_border) / inst->font_height; term_mouse(inst->term, button, translate_button(button), act, x, y, shift, ctrl, alt); return TRUE; @@ -1229,12 +1340,12 @@ else if (event->state & GDK_BUTTON3_MASK) button = MBT_RIGHT; else return FALSE; /* don't even know what button! */ - x = (event->x - inst->cfg.window_border) / inst->font_width; - y = (event->y - inst->cfg.window_border) / inst->font_height; + x = (event->x - inst->window_border) / inst->font_width; + y = (event->y - inst->window_border) / inst->font_height; term_mouse(inst->term, button, translate_button(button), MA_DRAG, x, y, shift, ctrl, alt); return TRUE; @@ -1247,58 +1358,100 @@ /* * If our child process has exited but not closed, terminate on * any keypress. */ if (inst->exited) - exit(0); + cleanup_exit(0); } -static gint idle_exit_func(gpointer data) +static void exit_callback(void *vinst) { - struct gui_data *inst = (struct gui_data *)data; - int exitcode; + struct gui_data *inst = (struct gui_data *)vinst; + int exitcode, close_on_exit; if (!inst->exited && (exitcode = inst->back->exitcode(inst->backhandle)) >= 0) { inst->exited = TRUE; - if (inst->cfg.close_on_exit == FORCE_ON || - (inst->cfg.close_on_exit == AUTO && exitcode == 0)) + close_on_exit = conf_get_int(inst->conf, CONF_close_on_exit); + if (close_on_exit == FORCE_ON || + (close_on_exit == AUTO && exitcode == 0)) gtk_main_quit(); /* just go */ if (inst->ldisc) { ldisc_free(inst->ldisc); inst->ldisc = NULL; } - if (inst->back) { - inst->back->free(inst->backhandle); - inst->backhandle = NULL; - inst->back = NULL; - term_provide_resize_fn(inst->term, NULL, NULL); - update_specials_menu(inst); - } + inst->back->free(inst->backhandle); + inst->backhandle = NULL; + inst->back = NULL; + term_provide_resize_fn(inst->term, NULL, NULL); + update_specials_menu(inst); gtk_widget_set_sensitive(inst->restartitem, TRUE); } - - gtk_idle_remove(inst->term_exit_idle_id); - return TRUE; } void notify_remote_exit(void *frontend) { struct gui_data *inst = (struct gui_data *)frontend; - inst->term_exit_idle_id = gtk_idle_add(idle_exit_func, inst); + queue_toplevel_callback(exit_callback, inst); +} + +static void notify_toplevel_callback(void *frontend); + +static gint quit_toplevel_callback_func(gpointer data) +{ + struct gui_data *inst = (struct gui_data *)data; + + notify_toplevel_callback(inst); + + return 0; +} + +static gint idle_toplevel_callback_func(gpointer data) +{ + struct gui_data *inst = (struct gui_data *)data; + + if (gtk_main_level() > 1) { + /* + * We don't run the callbacks if we're in the middle of a + * subsidiary gtk_main. Instead, ask for a callback when we + * get back out of the subsidiary main loop, so we can + * reschedule ourself then. + */ + gtk_quit_add(2, quit_toplevel_callback_func, inst); + } else { + run_toplevel_callbacks(); + } + + if (!toplevel_callback_pending()) + gtk_idle_remove(inst->toplevel_callback_idle_id); + + return TRUE; +} + +static void notify_toplevel_callback(void *frontend) +{ + struct gui_data *inst = (struct gui_data *)frontend; + + inst->toplevel_callback_idle_id = + gtk_idle_add(idle_toplevel_callback_func, inst); } static gint timer_trigger(gpointer data) { - long now = GPOINTER_TO_LONG(data); - long next; + unsigned long now = GPOINTER_TO_LONG(data); + unsigned long next, then; long ticks; if (run_timers(now, &next)) { - ticks = next - GETTICKCOUNT(); - timer_id = gtk_timeout_add(ticks > 0 ? ticks : 1, timer_trigger, + then = now; + now = GETTICKCOUNT(); + if (now - then > next - then) + ticks = 0; + else + ticks = next - now; + timer_id = gtk_timeout_add(ticks, timer_trigger, LONG_TO_GPOINTER(next)); } /* * Never let a timer resume. If we need another one, we've @@ -1305,11 +1458,11 @@ * asked for it explicitly above. */ return FALSE; } -void timer_change_notify(long next) +void timer_change_notify(unsigned long next) { long ticks; if (timer_id) gtk_timeout_remove(timer_id); @@ -1362,11 +1515,11 @@ * set or clear the "raw mouse message" mode */ void set_raw_mouse_mode(void *frontend, int activate) { struct gui_data *inst = (struct gui_data *)frontend; - activate = activate && !inst->cfg.no_mouse_rep; + activate = activate && !conf_get_int(inst->conf, CONF_no_mouse_rep); send_raw_mouse = activate; update_mouseptr(inst); } void request_resize(void *frontend, int w, int h) @@ -1410,12 +1563,12 @@ gtk_widget_size_request(inst->window, &outer); offset_x = outer.width - inner.width; offset_y = outer.height - inner.height; - area_x = inst->font_width * w + 2*inst->cfg.window_border; - area_y = inst->font_height * h + 2*inst->cfg.window_border; + area_x = inst->font_width * w + 2*inst->window_border; + area_y = inst->font_height * h + 2*inst->window_border; /* * Now we must set the size request on the drawing area back to * something sensible before we commit the real resize. Best * way to do this, I think, is to set it to what the size is @@ -1478,11 +1631,11 @@ void palette_set(void *frontend, int n, int r, int g, int b) { struct gui_data *inst = (struct gui_data *)frontend; if (n >= 16) n += 256 - 16; - if (n > NALLCOLOURS) + if (n >= NALLCOLOURS) return; real_palette_set(inst, n, r, g, b); if (n == 258) { /* Default Background changed. Ensure space between text area and * window border is redrawn */ @@ -1493,11 +1646,11 @@ } void palette_reset(void *frontend) { struct gui_data *inst = (struct gui_data *)frontend; - /* This maps colour indices in inst->cfg to those used in inst->cols. */ + /* This maps colour indices in inst->conf to those used in inst->cols. */ static const int ww[] = { 256, 257, 258, 259, 260, 261, 0, 8, 1, 9, 2, 10, 3, 11, 4, 12, 5, 13, 6, 14, 7, 15 }; @@ -1511,13 +1664,16 @@ } else { gdk_colormap_free_colors(inst->colmap, inst->cols, NALLCOLOURS); } for (i = 0; i < NCFGCOLOURS; i++) { - inst->cols[ww[i]].red = inst->cfg.colours[i][0] * 0x0101; - inst->cols[ww[i]].green = inst->cfg.colours[i][1] * 0x0101; - inst->cols[ww[i]].blue = inst->cfg.colours[i][2] * 0x0101; + inst->cols[ww[i]].red = + conf_get_int_int(inst->conf, CONF_colours, i*3+0) * 0x0101; + inst->cols[ww[i]].green = + conf_get_int_int(inst->conf, CONF_colours, i*3+1) * 0x0101; + inst->cols[ww[i]].blue = + conf_get_int_int(inst->conf, CONF_colours, i*3+2) * 0x0101; } for (i = 0; i < NEXTCOLOURS; i++) { if (i < 216) { int r = i / 36, g = (i / 6) % 6, b = i % 6; @@ -1535,12 +1691,14 @@ gdk_colormap_alloc_colors(inst->colmap, inst->cols, NALLCOLOURS, FALSE, TRUE, success); for (i = 0; i < NALLCOLOURS; i++) { if (!success[i]) g_error("%s: couldn't allocate colour %d (#%02x%02x%02x)\n", - appname, i, inst->cfg.colours[i][0], - inst->cfg.colours[i][1], inst->cfg.colours[i][2]); + appname, i, + conf_get_int_int(inst->conf, CONF_colours, i*3+0), + conf_get_int_int(inst->conf, CONF_colours, i*3+1), + conf_get_int_int(inst->conf, CONF_colours, i*3+2)); } /* Since Default Background may have changed, ensure that space * between text area and window border is refreshed. */ set_window_background(inst); @@ -1609,11 +1767,11 @@ /* * Set up UTF-8 and compound text paste data. This only happens * if we aren't in direct-to-font mode using the D800 hack. */ if (!inst->direct_to_font) { - wchar_t *tmp = data; + const wchar_t *tmp = data; int tmplen = len; XTextProperty tp; char *list[1]; inst->pasteout_data_utf8 = snewn(len*6, char); @@ -1856,31 +2014,15 @@ mb_to_wc(charset, 0, text, length, inst->pastein_data, inst->pastein_data_len); term_do_paste(inst->term); - if (term_paste_pending(inst->term)) - inst->term_paste_idle_id = gtk_idle_add(idle_paste_func, inst); - if (free_list_required) XFreeStringList(list); if (free_required) XFree(text); } - -gint idle_paste_func(gpointer data) -{ - struct gui_data *inst = (struct gui_data *)data; - - if (term_paste_pending(inst->term)) - term_paste(inst->term); - else - gtk_idle_remove(inst->term_paste_idle_id); - - return TRUE; -} - void get_clip(void *frontend, wchar_t ** p, int *len) { struct gui_data *inst = (struct gui_data *)frontend; @@ -1896,34 +2038,44 @@ * We must always call set_icon_name after calling set_title, * since set_title will write both names. Irritating, but such * is life. */ gtk_window_set_title(GTK_WINDOW(inst->window), inst->wintitle); - if (!inst->cfg.win_name_always) + if (!conf_get_int(inst->conf, CONF_win_name_always)) gdk_window_set_icon_name(inst->window->window, inst->icontitle); } void set_title(void *frontend, char *title) { struct gui_data *inst = (struct gui_data *)frontend; - strncpy(inst->wintitle, title, lenof(inst->wintitle)); - inst->wintitle[lenof(inst->wintitle)-1] = '\0'; + sfree(inst->wintitle); + inst->wintitle = dupstr(title); set_window_titles(inst); } void set_icon(void *frontend, char *title) { struct gui_data *inst = (struct gui_data *)frontend; - strncpy(inst->icontitle, title, lenof(inst->icontitle)); - inst->icontitle[lenof(inst->icontitle)-1] = '\0'; + sfree(inst->icontitle); + inst->icontitle = dupstr(title); + set_window_titles(inst); +} + +void set_title_and_icon(void *frontend, char *title, char *icon) +{ + struct gui_data *inst = (struct gui_data *)frontend; + sfree(inst->wintitle); + inst->wintitle = dupstr(title); + sfree(inst->icontitle); + inst->icontitle = dupstr(icon); set_window_titles(inst); } void set_sbar(void *frontend, int total, int start, int page) { struct gui_data *inst = (struct gui_data *)frontend; - if (!inst->cfg.scrollbar) + if (!conf_get_int(inst->conf, CONF_scrollbar)) return; inst->sbar_adjust->lower = 0; inst->sbar_adjust->upper = total; inst->sbar_adjust->value = start; inst->sbar_adjust->page_size = page; @@ -1936,11 +2088,11 @@ void scrollbar_moved(GtkAdjustment *adj, gpointer data) { struct gui_data *inst = (struct gui_data *)data; - if (!inst->cfg.scrollbar) + if (!conf_get_int(inst->conf, CONF_scrollbar)) return; if (!inst->ignore_sbar) term_scroll(inst->term, 1, (int)adj->value); } @@ -2025,15 +2177,15 @@ if (!!(attr & ATTR_REVERSE) ^ (monochrome && (attr & TATTR_ACTCURS))) { t = nfg; nfg = nbg; nbg = t; } - if (inst->cfg.bold_colour && (attr & ATTR_BOLD)) { + if ((inst->bold_style & 2) && (attr & ATTR_BOLD)) { if (nfg < 16) nfg |= 8; else if (nfg >= 256) nfg |= 1; } - if (inst->cfg.bold_colour && (attr & ATTR_BLINK)) { + if ((inst->bold_style & 2) && (attr & ATTR_BLINK)) { if (nbg < 16) nbg |= 8; else if (nbg >= 256) nbg |= 1; } if ((attr & TATTR_ACTCURS) && !monochrome) { nfg = 260; @@ -2047,11 +2199,11 @@ fontid |= 2; } else { widefactor = 1; } - if ((attr & ATTR_BOLD) && !inst->cfg.bold_colour) { + if ((attr & ATTR_BOLD) && (inst->bold_style & 1)) { bold = 1; fontid |= 1; } else { bold = 0; } @@ -2084,56 +2236,40 @@ rlen = len; { GdkRectangle r; - r.x = x*inst->font_width+inst->cfg.window_border; - r.y = y*inst->font_height+inst->cfg.window_border; + r.x = x*inst->font_width+inst->window_border; + r.y = y*inst->font_height+inst->window_border; r.width = rlen*widefactor*inst->font_width; r.height = inst->font_height; gdk_gc_set_clip_rectangle(gc, &r); } gdk_gc_set_foreground(gc, &inst->cols[nbg]); gdk_draw_rectangle(inst->pixmap, gc, 1, - x*inst->font_width+inst->cfg.window_border, - y*inst->font_height+inst->cfg.window_border, + x*inst->font_width+inst->window_border, + y*inst->font_height+inst->window_border, rlen*widefactor*inst->font_width, inst->font_height); gdk_gc_set_foreground(gc, &inst->cols[nfg]); - { - gchar *gcs; - - /* - * FIXME: this length is hardwired on the assumption that - * conversions from wide to multibyte characters will - * never generate more than 10 bytes for a single wide - * character. - */ - gcs = snewn(len*10+1, gchar); - - for (combining = 0; combining < ncombining; combining++) { - int mblen = wc_to_mb(inst->fonts[fontid]->real_charset, 0, - text + combining, len, gcs, len*10+1, ".", - NULL, NULL); - unifont_draw_text(inst->pixmap, gc, inst->fonts[fontid], - x*inst->font_width+inst->cfg.window_border, - y*inst->font_height+inst->cfg.window_border+inst->fonts[0]->ascent, - gcs, mblen, widefactor > 1, bold, inst->font_width); - } - - sfree(gcs); + for (combining = 0; combining < ncombining; combining++) { + unifont_draw_text(inst->pixmap, gc, inst->fonts[fontid], + x*inst->font_width+inst->window_border, + y*inst->font_height+inst->window_border+inst->fonts[0]->ascent, + text + combining, len, widefactor > 1, + bold, inst->font_width); } if (attr & ATTR_UNDER) { int uheight = inst->fonts[0]->ascent + 1; if (uheight >= inst->font_height) uheight = inst->font_height - 1; - gdk_draw_line(inst->pixmap, gc, x*inst->font_width+inst->cfg.window_border, - y*inst->font_height + uheight + inst->cfg.window_border, - (x+len)*widefactor*inst->font_width-1+inst->cfg.window_border, - y*inst->font_height + uheight + inst->cfg.window_border); + gdk_draw_line(inst->pixmap, gc, x*inst->font_width+inst->window_border, + y*inst->font_height + uheight + inst->window_border, + (x+len)*widefactor*inst->font_width-1+inst->window_border, + y*inst->font_height + uheight + inst->window_border); } if ((lattr & LATTR_MODE) != LATTR_NORM) { /* * I can't find any plausible StretchBlt equivalent in the @@ -2144,14 +2280,14 @@ * try thinking of a better way. :-( */ int i; for (i = 0; i < len * widefactor * inst->font_width; i++) { gdk_draw_pixmap(inst->pixmap, gc, inst->pixmap, - x*inst->font_width+inst->cfg.window_border + 2*i, - y*inst->font_height+inst->cfg.window_border, - x*inst->font_width+inst->cfg.window_border + 2*i+1, - y*inst->font_height+inst->cfg.window_border, + x*inst->font_width+inst->window_border + 2*i, + y*inst->font_height+inst->window_border, + x*inst->font_width+inst->window_border + 2*i+1, + y*inst->font_height+inst->window_border, len * widefactor * inst->font_width - i, inst->font_height); } len *= 2; if ((lattr & LATTR_MODE) != LATTR_WIDE) { int dt, db; @@ -2160,14 +2296,14 @@ dt = 0, db = 1; else dt = 1, db = 0; for (i = 0; i < inst->font_height; i+=2) { gdk_draw_pixmap(inst->pixmap, gc, inst->pixmap, - x*inst->font_width+inst->cfg.window_border, - y*inst->font_height+inst->cfg.window_border+dt*i+db, - x*inst->font_width+inst->cfg.window_border, - y*inst->font_height+inst->cfg.window_border+dt*(i+1), + x*inst->font_width+inst->window_border, + y*inst->font_height+inst->window_border+dt*i+db, + x*inst->font_width+inst->window_border, + y*inst->font_height+inst->window_border+dt*(i+1), len * widefactor * inst->font_width, inst->font_height-i-1); } } } } @@ -2196,14 +2332,14 @@ len = (inst->term->cols-x)/2/widefactor;/* trim to LH half */ len *= 2; } gdk_draw_pixmap(inst->area->window, gc, inst->pixmap, - x*inst->font_width+inst->cfg.window_border, - y*inst->font_height+inst->cfg.window_border, - x*inst->font_width+inst->cfg.window_border, - y*inst->font_height+inst->cfg.window_border, + x*inst->font_width+inst->window_border, + y*inst->font_height+inst->window_border, + x*inst->font_width+inst->window_border, + y*inst->font_height+inst->window_border, len*widefactor*inst->font_width, inst->font_height); } void do_cursor(Context ctx, int x, int y, wchar_t *text, int len, unsigned long attr, int lattr) @@ -2217,11 +2353,11 @@ if (attr & TATTR_PASCURS) { attr &= ~TATTR_PASCURS; passive = 1; } else passive = 0; - if ((attr & TATTR_ACTCURS) && inst->cfg.cursor_type != 0) { + if ((attr & TATTR_ACTCURS) && inst->cursor_type != 0) { attr &= ~TATTR_ACTCURS; active = 1; } else active = 0; do_text_internal(ctx, x, y, text, len, attr, lattr); @@ -2242,21 +2378,21 @@ if (x + len*2*widefactor > inst->term->cols) len = (inst->term->cols-x)/2/widefactor;/* trim to LH half */ len *= 2; } - if (inst->cfg.cursor_type == 0) { + if (inst->cursor_type == 0) { /* * An active block cursor will already have been done by * the above do_text call, so we only need to do anything * if it's passive. */ if (passive) { gdk_gc_set_foreground(gc, &inst->cols[261]); gdk_draw_rectangle(inst->pixmap, gc, 0, - x*inst->font_width+inst->cfg.window_border, - y*inst->font_height+inst->cfg.window_border, + x*inst->font_width+inst->window_border, + y*inst->font_height+inst->window_border, len*widefactor*inst->font_width-1, inst->font_height-1); } } else { int uheight; int startx, starty, dx, dy, length, i; @@ -2266,26 +2402,26 @@ if ((attr & ATTR_WIDE) || (lattr & LATTR_MODE) != LATTR_NORM) char_width = 2*inst->font_width; else char_width = inst->font_width; - if (inst->cfg.cursor_type == 1) { + if (inst->cursor_type == 1) { uheight = inst->fonts[0]->ascent + 1; if (uheight >= inst->font_height) uheight = inst->font_height - 1; - startx = x * inst->font_width + inst->cfg.window_border; - starty = y * inst->font_height + inst->cfg.window_border + uheight; + startx = x * inst->font_width + inst->window_border; + starty = y * inst->font_height + inst->window_border + uheight; dx = 1; dy = 0; length = len * widefactor * char_width; } else { int xadjust = 0; if (attr & TATTR_RIGHTCURS) xadjust = char_width - 1; - startx = x * inst->font_width + inst->cfg.window_border + xadjust; - starty = y * inst->font_height + inst->cfg.window_border; + startx = x * inst->font_width + inst->window_border + xadjust; + starty = y * inst->font_height + inst->window_border; dx = 0; dy = 1; length = inst->font_height; } @@ -2303,15 +2439,26 @@ startx + (length-1) * dx, starty + (length-1) * dy); } /* else no cursor (e.g., blinked off) */ } gdk_draw_pixmap(inst->area->window, gc, inst->pixmap, - x*inst->font_width+inst->cfg.window_border, - y*inst->font_height+inst->cfg.window_border, - x*inst->font_width+inst->cfg.window_border, - y*inst->font_height+inst->cfg.window_border, + x*inst->font_width+inst->window_border, + y*inst->font_height+inst->window_border, + x*inst->font_width+inst->window_border, + y*inst->font_height+inst->window_border, len*widefactor*inst->font_width, inst->font_height); + +#if GTK_CHECK_VERSION(2,0,0) + { + GdkRectangle cursorrect; + cursorrect.x = x*inst->font_width+inst->window_border; + cursorrect.y = y*inst->font_height+inst->window_border; + cursorrect.width = len*widefactor*inst->font_width; + cursorrect.height = inst->font_height; + gtk_im_context_set_cursor_location(inst->imc, &cursorrect); + } +#endif } GdkCursor *make_mouse_ptr(struct gui_data *inst, int cursor_val) { /* @@ -2464,13 +2611,20 @@ ) < 0 || fflush(fp) < 0) { perror("output error"); exit(1); } } + +static void version(FILE *fp) { + if(fprintf(fp, "%s: %s\n", appname, ver) < 0 || fflush(fp) < 0) { + perror("output error"); + exit(1); + } +} int do_cmdline(int argc, char **argv, int do_everything, int *allow_launch, - struct gui_data *inst, Config *cfg) + struct gui_data *inst, Conf *conf) { int err = 0; char *val; /* @@ -2505,11 +2659,11 @@ if ((cmdline_tooltype & TOOLTYPE_NONNETWORK) && !strcmp(p, "-T")) p = "-title"; ret = cmdline_process_param(p, (argc > 1 ? argv[1] : NULL), - do_everything ? 1 : -1, cfg); + do_everything ? 1 : -1, conf); if (ret == -2) { cmdline_error("option \"%s\" requires an argument", p); } else if (ret == 2) { --argc, ++argv; /* skip next argument */ @@ -2517,50 +2671,57 @@ } else if (ret == 1) { continue; } if (!strcmp(p, "-fn") || !strcmp(p, "-font")) { + FontSpec *fs; EXPECTS_ARG; SECOND_PASS_ONLY; - strncpy(cfg->font.name, val, sizeof(cfg->font.name)); - cfg->font.name[sizeof(cfg->font.name)-1] = '\0'; + fs = fontspec_new(val); + conf_set_fontspec(conf, CONF_font, fs); + fontspec_free(fs); } else if (!strcmp(p, "-fb")) { + FontSpec *fs; EXPECTS_ARG; SECOND_PASS_ONLY; - strncpy(cfg->boldfont.name, val, sizeof(cfg->boldfont.name)); - cfg->boldfont.name[sizeof(cfg->boldfont.name)-1] = '\0'; + fs = fontspec_new(val); + conf_set_fontspec(conf, CONF_boldfont, fs); + fontspec_free(fs); } else if (!strcmp(p, "-fw")) { + FontSpec *fs; EXPECTS_ARG; SECOND_PASS_ONLY; - strncpy(cfg->widefont.name, val, sizeof(cfg->widefont.name)); - cfg->widefont.name[sizeof(cfg->widefont.name)-1] = '\0'; + fs = fontspec_new(val); + conf_set_fontspec(conf, CONF_widefont, fs); + fontspec_free(fs); } else if (!strcmp(p, "-fwb")) { + FontSpec *fs; EXPECTS_ARG; SECOND_PASS_ONLY; - strncpy(cfg->wideboldfont.name, val, sizeof(cfg->wideboldfont.name)); - cfg->wideboldfont.name[sizeof(cfg->wideboldfont.name)-1] = '\0'; + fs = fontspec_new(val); + conf_set_fontspec(conf, CONF_wideboldfont, fs); + fontspec_free(fs); } else if (!strcmp(p, "-cs")) { EXPECTS_ARG; SECOND_PASS_ONLY; - strncpy(cfg->line_codepage, val, sizeof(cfg->line_codepage)); - cfg->line_codepage[sizeof(cfg->line_codepage)-1] = '\0'; + conf_set_str(conf, CONF_line_codepage, val); } else if (!strcmp(p, "-geometry")) { int flags, x, y; unsigned int w, h; EXPECTS_ARG; SECOND_PASS_ONLY; flags = XParseGeometry(val, &x, &y, &w, &h); if (flags & WidthValue) - cfg->width = (int)w; + conf_set_int(conf, CONF_width, w); if (flags & HeightValue) - cfg->height = (int)h; + conf_set_int(conf, CONF_height, h); if (flags & (XValue | YValue)) { inst->xpos = x; inst->ypos = y; inst->gotpos = TRUE; @@ -2569,11 +2730,11 @@ } } else if (!strcmp(p, "-sl")) { EXPECTS_ARG; SECOND_PASS_ONLY; - cfg->savelines = atoi(val); + conf_set_int(conf, CONF_savelines, atoi(val)); } else if (!strcmp(p, "-fg") || !strcmp(p, "-bg") || !strcmp(p, "-bfg") || !strcmp(p, "-bbg") || !strcmp(p, "-cfg") || !strcmp(p, "-cbg")) { GdkColor col; @@ -2591,13 +2752,13 @@ !strcmp(p, "-bfg") ? 1 : !strcmp(p, "-bbg") ? 3 : !strcmp(p, "-cfg") ? 4 : !strcmp(p, "-cbg") ? 5 : -1); assert(index != -1); - cfg->colours[index][0] = col.red / 256; - cfg->colours[index][1] = col.green / 256; - cfg->colours[index][2] = col.blue / 256; + conf_set_int_int(conf, CONF_colours, index*3+0, col.red / 256); + conf_set_int_int(conf, CONF_colours, index*3+1,col.green/ 256); + conf_set_int_int(conf, CONF_colours, index*3+2, col.blue/ 256); } } else if (use_pty_argv && !strcmp(p, "-e")) { /* This option swallows all further arguments. */ if (!do_everything) @@ -2616,47 +2777,48 @@ appname); } else if (!strcmp(p, "-title")) { EXPECTS_ARG; SECOND_PASS_ONLY; - strncpy(cfg->wintitle, val, sizeof(cfg->wintitle)); - cfg->wintitle[sizeof(cfg->wintitle)-1] = '\0'; + conf_set_str(conf, CONF_wintitle, val); } else if (!strcmp(p, "-log")) { + Filename *fn; EXPECTS_ARG; SECOND_PASS_ONLY; - strncpy(cfg->logfilename.path, val, sizeof(cfg->logfilename.path)); - cfg->logfilename.path[sizeof(cfg->logfilename.path)-1] = '\0'; - cfg->logtype = LGTYP_DEBUG; + fn = filename_from_str(val); + conf_set_filename(conf, CONF_logfilename, fn); + conf_set_int(conf, CONF_logtype, LGTYP_DEBUG); + filename_free(fn); } else if (!strcmp(p, "-ut-") || !strcmp(p, "+ut")) { SECOND_PASS_ONLY; - cfg->stamp_utmp = 0; + conf_set_int(conf, CONF_stamp_utmp, 0); } else if (!strcmp(p, "-ut")) { SECOND_PASS_ONLY; - cfg->stamp_utmp = 1; + conf_set_int(conf, CONF_stamp_utmp, 1); } else if (!strcmp(p, "-ls-") || !strcmp(p, "+ls")) { SECOND_PASS_ONLY; - cfg->login_shell = 0; + conf_set_int(conf, CONF_login_shell, 0); } else if (!strcmp(p, "-ls")) { SECOND_PASS_ONLY; - cfg->login_shell = 1; + conf_set_int(conf, CONF_login_shell, 1); } else if (!strcmp(p, "-nethack")) { SECOND_PASS_ONLY; - cfg->nethack_keypad = 1; + conf_set_int(conf, CONF_nethack_keypad, 1); } else if (!strcmp(p, "-sb-") || !strcmp(p, "+sb")) { SECOND_PASS_ONLY; - cfg->scrollbar = 0; + conf_set_int(conf, CONF_scrollbar, 0); } else if (!strcmp(p, "-sb")) { SECOND_PASS_ONLY; - cfg->scrollbar = 0; + conf_set_int(conf, CONF_scrollbar, 1); } else if (!strcmp(p, "-name")) { EXPECTS_ARG; app_name = val; @@ -2665,17 +2827,21 @@ provide_xrm_string(val); } else if(!strcmp(p, "-help") || !strcmp(p, "--help")) { help(stdout); exit(0); + + } else if(!strcmp(p, "-version") || !strcmp(p, "--version")) { + version(stdout); + exit(0); } else if (!strcmp(p, "-pgpfp")) { pgp_fingerprints(); exit(1); } else if(p[0] != '-' && (!do_everything || - process_nonoption_arg(p, cfg, + process_nonoption_arg(p, conf, allow_launch))) { /* do nothing */ } else { err = 1; @@ -2697,90 +2863,97 @@ void uxsel_input_remove(int id) { gdk_input_remove(id); } -void setup_fonts_ucs(struct gui_data *inst) -{ - if (inst->fonts[0]) - unifont_destroy(inst->fonts[0]); - if (inst->fonts[1]) - unifont_destroy(inst->fonts[1]); - if (inst->fonts[2]) - unifont_destroy(inst->fonts[2]); - if (inst->fonts[3]) - unifont_destroy(inst->fonts[3]); - - inst->fonts[0] = unifont_create(inst->area, inst->cfg.font.name, - FALSE, FALSE, - inst->cfg.shadowboldoffset, - inst->cfg.shadowbold); - if (!inst->fonts[0]) { - fprintf(stderr, "%s: unable to load font \"%s\"\n", appname, - inst->cfg.font.name); - exit(1); - } - - if (inst->cfg.shadowbold || !inst->cfg.boldfont.name[0]) { - inst->fonts[1] = NULL; - } else { - inst->fonts[1] = unifont_create(inst->area, inst->cfg.boldfont.name, - FALSE, TRUE, - inst->cfg.shadowboldoffset, - inst->cfg.shadowbold); - if (!inst->fonts[1]) { - fprintf(stderr, "%s: unable to load bold font \"%s\"\n", appname, - inst->cfg.boldfont.name); - exit(1); - } - } - - if (inst->cfg.widefont.name[0]) { - inst->fonts[2] = unifont_create(inst->area, inst->cfg.widefont.name, - TRUE, FALSE, - inst->cfg.shadowboldoffset, - inst->cfg.shadowbold); - if (!inst->fonts[2]) { - fprintf(stderr, "%s: unable to load wide font \"%s\"\n", appname, - inst->cfg.widefont.name); - exit(1); - } - } else { - inst->fonts[2] = NULL; - } - - if (inst->cfg.shadowbold || !inst->cfg.wideboldfont.name[0]) { - inst->fonts[3] = NULL; - } else { - inst->fonts[3] = unifont_create(inst->area, - inst->cfg.wideboldfont.name, TRUE, - TRUE, inst->cfg.shadowboldoffset, - inst->cfg.shadowbold); - if (!inst->fonts[3]) { - fprintf(stderr, "%s: unable to load wide bold font \"%s\"\n", appname, - inst->cfg.boldfont.name); - exit(1); - } +char *setup_fonts_ucs(struct gui_data *inst) +{ + int shadowbold = conf_get_int(inst->conf, CONF_shadowbold); + int shadowboldoffset = conf_get_int(inst->conf, CONF_shadowboldoffset); + FontSpec *fs; + unifont *fonts[4]; + int i; + + fs = conf_get_fontspec(inst->conf, CONF_font); + fonts[0] = multifont_create(inst->area, fs->name, FALSE, FALSE, + shadowboldoffset, shadowbold); + if (!fonts[0]) { + return dupprintf("unable to load font \"%s\"", fs->name); + } + + fs = conf_get_fontspec(inst->conf, CONF_boldfont); + if (shadowbold || !fs->name[0]) { + fonts[1] = NULL; + } else { + fonts[1] = multifont_create(inst->area, fs->name, FALSE, TRUE, + shadowboldoffset, shadowbold); + if (!fonts[1]) { + if (fonts[0]) + unifont_destroy(fonts[0]); + return dupprintf("unable to load bold font \"%s\"", fs->name); + } + } + + fs = conf_get_fontspec(inst->conf, CONF_widefont); + if (fs->name[0]) { + fonts[2] = multifont_create(inst->area, fs->name, TRUE, FALSE, + shadowboldoffset, shadowbold); + if (!fonts[2]) { + for (i = 0; i < 2; i++) + if (fonts[i]) + unifont_destroy(fonts[i]); + return dupprintf("unable to load wide font \"%s\"", fs->name); + } + } else { + fonts[2] = NULL; + } + + fs = conf_get_fontspec(inst->conf, CONF_wideboldfont); + if (shadowbold || !fs->name[0]) { + fonts[3] = NULL; + } else { + fonts[3] = multifont_create(inst->area, fs->name, TRUE, TRUE, + shadowboldoffset, shadowbold); + if (!fonts[3]) { + for (i = 0; i < 3; i++) + if (fonts[i]) + unifont_destroy(fonts[i]); + return dupprintf("unable to load wide bold font \"%s\"", fs->name); + } + } + + /* + * Now we've got past all the possible error conditions, we can + * actually update our state. + */ + + for (i = 0; i < 4; i++) { + if (inst->fonts[i]) + unifont_destroy(inst->fonts[i]); + inst->fonts[i] = fonts[i]; } inst->font_width = inst->fonts[0]->width; inst->font_height = inst->fonts[0]->height; - inst->direct_to_font = init_ucs(&inst->ucsdata, inst->cfg.line_codepage, - inst->cfg.utf8_override, + inst->direct_to_font = init_ucs(&inst->ucsdata, + conf_get_str(inst->conf, CONF_line_codepage), + conf_get_int(inst->conf, CONF_utf8_override), inst->fonts[0]->public_charset, - inst->cfg.vtmode); + conf_get_int(inst->conf, CONF_vtmode)); + + return NULL; } void set_geom_hints(struct gui_data *inst) { GdkGeometry geom; - geom.min_width = inst->font_width + 2*inst->cfg.window_border; - geom.min_height = inst->font_height + 2*inst->cfg.window_border; + geom.min_width = inst->font_width + 2*inst->window_border; + geom.min_height = inst->font_height + 2*inst->window_border; geom.max_width = geom.max_height = -1; - geom.base_width = 2*inst->cfg.window_border; - geom.base_height = 2*inst->cfg.window_border; + geom.base_width = 2*inst->window_border; + geom.base_height = 2*inst->window_border; geom.width_inc = inst->font_width; geom.height_inc = inst->font_height; geom.min_aspect = geom.max_aspect = 0; gtk_window_set_geometry_hints(GTK_WINDOW(inst->window), inst->area, &geom, GDK_HINT_MIN_SIZE | GDK_HINT_BASE_SIZE | @@ -2829,72 +3002,80 @@ showeventlog(inst->eventlogstuff, inst->window); } void change_settings_menuitem(GtkMenuItem *item, gpointer data) { - /* This maps colour indices in inst->cfg to those used in inst->cols. */ + /* This maps colour indices in inst->conf to those used in inst->cols. */ static const int ww[] = { 256, 257, 258, 259, 260, 261, 0, 8, 1, 9, 2, 10, 3, 11, 4, 12, 5, 13, 6, 14, 7, 15 }; struct gui_data *inst = (struct gui_data *)data; - char *title = dupcat(appname, " Reconfiguration", NULL); - Config cfg2, oldcfg; - int i, need_size; + char *title; + Conf *oldconf, *newconf; + int i, j, need_size; assert(lenof(ww) == NCFGCOLOURS); if (inst->reconfiguring) return; else inst->reconfiguring = TRUE; - cfg2 = inst->cfg; /* structure copy */ + title = dupcat(appname, " Reconfiguration", NULL); - if (do_config_box(title, &cfg2, 1, + oldconf = inst->conf; + newconf = conf_copy(inst->conf); + + if (do_config_box(title, newconf, 1, inst->back?inst->back->cfg_info(inst->backhandle):0)) { - - oldcfg = inst->cfg; /* structure copy */ - inst->cfg = cfg2; /* structure copy */ + inst->conf = newconf; /* Pass new config data to the logging module */ - log_reconfig(inst->logctx, &cfg2); + log_reconfig(inst->logctx, inst->conf); /* * Flush the line discipline's edit buffer in the case * where local editing has just been disabled. */ - if (inst->ldisc) + if (inst->ldisc) { + ldisc_configure(inst->ldisc, inst->conf); ldisc_send(inst->ldisc, NULL, 0, 0); + } /* Pass new config data to the terminal */ - term_reconfig(inst->term, &cfg2); + term_reconfig(inst->term, inst->conf); /* Pass new config data to the back end */ if (inst->back) - inst->back->reconfig(inst->backhandle, &cfg2); + inst->back->reconfig(inst->backhandle, inst->conf); + + cache_conf_values(inst); /* - * Just setting inst->cfg is sufficient to cause colour + * Just setting inst->conf is sufficient to cause colour * setting changes to appear on the next ESC]R palette * reset. But we should also check whether any colour - * settings have been changed, and revert the ones that - * have to the new default, on the assumption that the user - * is most likely to want an immediate update. + * settings have been changed, and revert the ones that have + * to the new default, on the assumption that the user is + * most likely to want an immediate update. */ for (i = 0; i < NCFGCOLOURS; i++) { - if (oldcfg.colours[i][0] != cfg2.colours[i][0] || - oldcfg.colours[i][1] != cfg2.colours[i][1] || - oldcfg.colours[i][2] != cfg2.colours[i][2]) { - real_palette_set(inst, ww[i], cfg2.colours[i][0], - cfg2.colours[i][1], - cfg2.colours[i][2]); + for (j = 0; j < 3; j++) + if (conf_get_int_int(oldconf, CONF_colours, i*3+j) != + conf_get_int_int(newconf, CONF_colours, i*3+j)) + break; + if (j < 3) { + real_palette_set(inst, ww[i], + conf_get_int_int(newconf,CONF_colours,i*3+0), + conf_get_int_int(newconf,CONF_colours,i*3+1), + conf_get_int_int(newconf,CONF_colours,i*3+2)); /* * If the default background has changed, we must * repaint the space in between the window border * and the text area. */ - if (i == 258) { + if (ww[i] == 258) { set_window_background(inst); draw_backing_rect(inst); } } } @@ -2901,71 +3082,109 @@ /* * If the scrollbar needs to be shown, hidden, or moved * from one end to the other of the window, do so now. */ - if (oldcfg.scrollbar != cfg2.scrollbar) { - if (cfg2.scrollbar) + if (conf_get_int(oldconf, CONF_scrollbar) != + conf_get_int(newconf, CONF_scrollbar)) { + if (conf_get_int(newconf, CONF_scrollbar)) gtk_widget_show(inst->sbar); else gtk_widget_hide(inst->sbar); } - if (oldcfg.scrollbar_on_left != cfg2.scrollbar_on_left) { + if (conf_get_int(oldconf, CONF_scrollbar_on_left) != + conf_get_int(newconf, CONF_scrollbar_on_left)) { gtk_box_reorder_child(inst->hbox, inst->sbar, - cfg2.scrollbar_on_left ? 0 : 1); + conf_get_int(newconf, CONF_scrollbar_on_left) + ? 0 : 1); } /* * Change the window title, if required. */ - if (strcmp(oldcfg.wintitle, cfg2.wintitle)) - set_title(inst, cfg2.wintitle); + if (strcmp(conf_get_str(oldconf, CONF_wintitle), + conf_get_str(newconf, CONF_wintitle))) + set_title(inst, conf_get_str(newconf, CONF_wintitle)); set_window_titles(inst); /* * Redo the whole tangled fonts and Unicode mess if * necessary. */ - if (strcmp(oldcfg.font.name, cfg2.font.name) || - strcmp(oldcfg.boldfont.name, cfg2.boldfont.name) || - strcmp(oldcfg.widefont.name, cfg2.widefont.name) || - strcmp(oldcfg.wideboldfont.name, cfg2.wideboldfont.name) || - strcmp(oldcfg.line_codepage, cfg2.line_codepage) || - oldcfg.vtmode != cfg2.vtmode || - oldcfg.shadowbold != cfg2.shadowbold) { - setup_fonts_ucs(inst); - need_size = 1; - } else - need_size = 0; + need_size = FALSE; + if (strcmp(conf_get_fontspec(oldconf, CONF_font)->name, + conf_get_fontspec(newconf, CONF_font)->name) || + strcmp(conf_get_fontspec(oldconf, CONF_boldfont)->name, + conf_get_fontspec(newconf, CONF_boldfont)->name) || + strcmp(conf_get_fontspec(oldconf, CONF_widefont)->name, + conf_get_fontspec(newconf, CONF_widefont)->name) || + strcmp(conf_get_fontspec(oldconf, CONF_wideboldfont)->name, + conf_get_fontspec(newconf, CONF_wideboldfont)->name) || + strcmp(conf_get_str(oldconf, CONF_line_codepage), + conf_get_str(newconf, CONF_line_codepage)) || + conf_get_int(oldconf, CONF_utf8_override) != + conf_get_int(newconf, CONF_utf8_override) || + conf_get_int(oldconf, CONF_vtmode) != + conf_get_int(newconf, CONF_vtmode) || + conf_get_int(oldconf, CONF_shadowbold) != + conf_get_int(newconf, CONF_shadowbold) || + conf_get_int(oldconf, CONF_shadowboldoffset) != + conf_get_int(newconf, CONF_shadowboldoffset)) { + char *errmsg = setup_fonts_ucs(inst); + if (errmsg) { + char *msgboxtext = + dupprintf("Could not change fonts in terminal window: %s\n", + errmsg); + messagebox(inst->window, "Font setup error", msgboxtext, + string_width("Could not change fonts in terminal window:"), + "OK", 'o', +1, 1, + NULL); + sfree(msgboxtext); + sfree(errmsg); + } else { + need_size = TRUE; + } + } /* * Resize the window. */ - if (oldcfg.width != cfg2.width || oldcfg.height != cfg2.height || - oldcfg.window_border != cfg2.window_border || need_size) { + if (conf_get_int(oldconf, CONF_width) != + conf_get_int(newconf, CONF_width) || + conf_get_int(oldconf, CONF_height) != + conf_get_int(newconf, CONF_height) || + conf_get_int(oldconf, CONF_window_border) != + conf_get_int(newconf, CONF_window_border) || + need_size) { set_geom_hints(inst); - request_resize(inst, cfg2.width, cfg2.height); + request_resize(inst, conf_get_int(newconf, CONF_width), + conf_get_int(newconf, CONF_height)); } else { /* * The above will have caused a call to term_size() for * us if it happened. If the user has fiddled with only * the scrollback size, the above will not have * happened and we will need an explicit term_size() * here. */ - if (oldcfg.savelines != cfg2.savelines) + if (conf_get_int(oldconf, CONF_savelines) != + conf_get_int(newconf, CONF_savelines)) term_size(inst->term, inst->term->rows, inst->term->cols, - cfg2.savelines); + conf_get_int(newconf, CONF_savelines)); } term_invalidate(inst->term); /* * We do an explicit full redraw here to ensure the window * border has been redrawn as well as the text area. */ gtk_widget_queue_draw(inst->area); + + conf_free(oldconf); + } else { + conf_free(newconf); } sfree(title); inst->reconfiguring = FALSE; } @@ -3012,10 +3231,11 @@ * Do the double fork. */ pid = fork(); if (pid < 0) { perror("fork"); + sfree(args); return; } if (pid == 0) { int pid2 = fork(); @@ -3045,52 +3265,53 @@ perror("exec"); _exit(127); } else { int status; + sfree(args); waitpid(pid, &status, 0); } } void dup_session_menuitem(GtkMenuItem *item, gpointer gdata) { struct gui_data *inst = (struct gui_data *)gdata; /* - * For this feature we must marshal cfg and (possibly) pty_argv + * For this feature we must marshal conf and (possibly) pty_argv * into a byte stream, create a pipe, and send this byte stream * to the child through the pipe. */ - int i, ret, size; + int i, ret, sersize, size; char *data; char option[80]; int pipefd[2]; if (pipe(pipefd) < 0) { perror("pipe"); return; } - size = sizeof(inst->cfg); + size = sersize = conf_serialised_size(inst->conf); if (use_pty_argv && pty_argv) { for (i = 0; pty_argv[i]; i++) size += strlen(pty_argv[i]) + 1; } data = snewn(size, char); - memcpy(data, &inst->cfg, sizeof(inst->cfg)); + conf_serialise(inst->conf, data); if (use_pty_argv && pty_argv) { - int p = sizeof(inst->cfg); + int p = sersize; for (i = 0; pty_argv[i]; i++) { strcpy(data + p, pty_argv[i]); p += strlen(pty_argv[i]) + 1; } assert(p == size); } sprintf(option, "---[%d,%d]", pipefd[0], size); - fcntl(pipefd[0], F_SETFD, 0); + noncloexec(pipefd[0]); fork_and_exec_self(inst, pipefd[1], option, NULL); close(pipefd[0]); i = ret = 0; while (i < size && (ret = write(pipefd[1], data + i, size - i)) > 0) @@ -3099,13 +3320,13 @@ perror("write to pipe"); close(pipefd[1]); sfree(data); } -int read_dupsession_data(struct gui_data *inst, Config *cfg, char *arg) +int read_dupsession_data(struct gui_data *inst, Conf *conf, char *arg) { - int fd, i, ret, size; + int fd, i, ret, size, size_used; char *data; if (sscanf(arg, "---[%d,%d]", &fd, &size) != 2) { fprintf(stderr, "%s: malformed magic argument `%s'\n", appname, arg); exit(1); @@ -3122,14 +3343,14 @@ fprintf(stderr, "%s: unexpected EOF in Duplicate Session data\n", appname); exit(1); } - memcpy(cfg, data, sizeof(Config)); - if (use_pty_argv && size > sizeof(Config)) { + size_used = conf_deserialise(conf, data, size); + if (use_pty_argv && size > size_used) { int n = 0; - i = sizeof(Config); + i = size_used; while (i < size) { while (i < size && data[i]) i++; if (i >= size) { fprintf(stderr, "%s: malformed Duplicate Session data\n", appname); @@ -3139,19 +3360,21 @@ n++; } pty_argv = snewn(n+1, char *); pty_argv[n] = NULL; n = 0; - i = sizeof(Config); + i = size_used; while (i < size) { char *p = data + i; while (i < size && data[i]) i++; assert(i < size); i++; pty_argv[n++] = dupstr(p); } } + + sfree(data); return 0; } void new_session_menuitem(GtkMenuItem *item, gpointer data) @@ -3318,64 +3541,71 @@ } } static void start_backend(struct gui_data *inst) { - extern Backend *select_backend(Config *cfg); + extern Backend *select_backend(Conf *conf); char *realhost; const char *error; + char *s; - inst->back = select_backend(&inst->cfg); + inst->back = select_backend(inst->conf); error = inst->back->init((void *)inst, &inst->backhandle, - &inst->cfg, inst->cfg.host, inst->cfg.port, - &realhost, inst->cfg.tcp_nodelay, - inst->cfg.tcp_keepalives); + inst->conf, + conf_get_str(inst->conf, CONF_host), + conf_get_int(inst->conf, CONF_port), + &realhost, + conf_get_int(inst->conf, CONF_tcp_nodelay), + conf_get_int(inst->conf, CONF_tcp_keepalives)); if (error) { char *msg = dupprintf("Unable to open connection to %s:\n%s", - inst->cfg.host, error); + conf_get_str(inst->conf, CONF_host), error); inst->exited = TRUE; fatal_message_box(inst->window, msg); sfree(msg); exit(0); } - if (inst->cfg.wintitle[0]) { - set_title(inst, inst->cfg.wintitle); - set_icon(inst, inst->cfg.wintitle); + s = conf_get_str(inst->conf, CONF_wintitle); + if (s[0]) { + set_title_and_icon(inst, s, s); } else { char *title = make_default_wintitle(realhost); - set_title(inst, title); - set_icon(inst, title); + set_title_and_icon(inst, title, title); sfree(title); } sfree(realhost); inst->back->provide_logctx(inst->backhandle, inst->logctx); term_provide_resize_fn(inst->term, inst->back->size, inst->backhandle); inst->ldisc = - ldisc_create(&inst->cfg, inst->term, inst->back, inst->backhandle, + ldisc_create(inst->conf, inst->term, inst->back, inst->backhandle, inst); gtk_widget_set_sensitive(inst->restartitem, FALSE); } int pt_main(int argc, char **argv) { - extern int cfgbox(Config *cfg); + extern int cfgbox(Conf *conf); struct gui_data *inst; + + setlocale(LC_CTYPE, ""); /* * Create an instance structure and initialise to zeroes */ inst = snew(struct gui_data); memset(inst, 0, sizeof(*inst)); inst->alt_keycode = -1; /* this one needs _not_ to be zero */ inst->busy_status = BUSY_NOT; + inst->conf = conf_new(); + inst->wintitle = inst->icontitle = NULL; /* defer any child exit handling until we're ready to deal with * it */ block_signal(SIGCHLD, 1); @@ -3393,31 +3623,31 @@ gtk_init(&argc, &argv); inst->ngtkargs = oldargc - argc; } if (argc > 1 && !strncmp(argv[1], "---", 3)) { - read_dupsession_data(inst, &inst->cfg, argv[1]); + read_dupsession_data(inst, inst->conf, argv[1]); /* Splatter this argument so it doesn't clutter a ps listing */ - memset(argv[1], 0, strlen(argv[1])); + smemclr(argv[1], strlen(argv[1])); } else { /* By default, we bring up the config dialog, rather than launching * a session. This gets set to TRUE if something happens to change * that (e.g., a hostname is specified on the command-line). */ int allow_launch = FALSE; - if (do_cmdline(argc, argv, 0, &allow_launch, inst, &inst->cfg)) + if (do_cmdline(argc, argv, 0, &allow_launch, inst, inst->conf)) exit(1); /* pre-defaults pass to get -class */ - do_defaults(NULL, &inst->cfg); - if (do_cmdline(argc, argv, 1, &allow_launch, inst, &inst->cfg)) + do_defaults(NULL, inst->conf); + if (do_cmdline(argc, argv, 1, &allow_launch, inst, inst->conf)) exit(1); /* post-defaults, do everything */ - cmdline_run_saved(&inst->cfg); + cmdline_run_saved(inst->conf); if (loaded_session) allow_launch = TRUE; - if ((!allow_launch || !cfg_launchable(&inst->cfg)) && - !cfgbox(&inst->cfg)) + if ((!allow_launch || !conf_launchable(inst->conf)) && + !cfgbox(inst->conf)) exit(0); /* config box hit Cancel */ } if (!compound_text_atom) compound_text_atom = gdk_atom_intern("COMPOUND_TEXT", FALSE); @@ -3424,49 +3654,63 @@ if (!utf8_string_atom) utf8_string_atom = gdk_atom_intern("UTF8_STRING", FALSE); inst->area = gtk_drawing_area_new(); - setup_fonts_ucs(inst); +#if GTK_CHECK_VERSION(2,0,0) + inst->imc = gtk_im_multicontext_new(); +#endif + + { + char *errmsg = setup_fonts_ucs(inst); + if (errmsg) { + fprintf(stderr, "%s: %s\n", appname, errmsg); + exit(1); + } + } init_cutbuffers(); inst->window = gtk_window_new(GTK_WINDOW_TOPLEVEL); - if (inst->cfg.winclass[0]) - gtk_window_set_wmclass(GTK_WINDOW(inst->window), - inst->cfg.winclass, inst->cfg.winclass); + { + const char *winclass = conf_get_str(inst->conf, CONF_winclass); + if (*winclass) + gtk_window_set_wmclass(GTK_WINDOW(inst->window), + winclass, winclass); + } /* * Set up the colour map. */ palette_reset(inst); - inst->width = inst->cfg.width; - inst->height = inst->cfg.height; + inst->width = conf_get_int(inst->conf, CONF_width); + inst->height = conf_get_int(inst->conf, CONF_height); + cache_conf_values(inst); gtk_drawing_area_size(GTK_DRAWING_AREA(inst->area), - inst->font_width * inst->cfg.width + 2*inst->cfg.window_border, - inst->font_height * inst->cfg.height + 2*inst->cfg.window_border); + inst->font_width * inst->width + 2*inst->window_border, + inst->font_height * inst->height + 2*inst->window_border); inst->sbar_adjust = GTK_ADJUSTMENT(gtk_adjustment_new(0,0,0,0,0,0)); inst->sbar = gtk_vscrollbar_new(inst->sbar_adjust); inst->hbox = GTK_BOX(gtk_hbox_new(FALSE, 0)); /* * We always create the scrollbar; it remains invisible if * unwanted, so we can pop it up quickly if it suddenly becomes * desirable. */ - if (inst->cfg.scrollbar_on_left) + if (conf_get_int(inst->conf, CONF_scrollbar_on_left)) gtk_box_pack_start(inst->hbox, inst->sbar, FALSE, FALSE, 0); gtk_box_pack_start(inst->hbox, inst->area, TRUE, TRUE, 0); - if (!inst->cfg.scrollbar_on_left) + if (!conf_get_int(inst->conf, CONF_scrollbar_on_left)) gtk_box_pack_start(inst->hbox, inst->sbar, FALSE, FALSE, 0); gtk_container_add(GTK_CONTAINER(inst->window), GTK_WIDGET(inst->hbox)); set_geom_hints(inst); gtk_widget_show(inst->area); - if (inst->cfg.scrollbar) + if (conf_get_int(inst->conf, CONF_scrollbar)) gtk_widget_show(inst->sbar); else gtk_widget_hide(inst->sbar); gtk_widget_show(GTK_WIDGET(inst->hbox)); @@ -3510,11 +3754,15 @@ GTK_SIGNAL_FUNC(selection_received), inst); gtk_signal_connect(GTK_OBJECT(inst->area), "selection_get", GTK_SIGNAL_FUNC(selection_get), inst); gtk_signal_connect(GTK_OBJECT(inst->area), "selection_clear_event", GTK_SIGNAL_FUNC(selection_clear), inst); - if (inst->cfg.scrollbar) +#if GTK_CHECK_VERSION(2,0,0) + g_signal_connect(G_OBJECT(inst->imc), "commit", + G_CALLBACK(input_method_commit_event), inst); +#endif + if (conf_get_int(inst->conf, CONF_scrollbar)) gtk_signal_connect(GTK_OBJECT(inst->sbar_adjust), "value_changed", GTK_SIGNAL_FUNC(scrollbar_moved), inst); gtk_widget_add_events(GTK_WIDGET(inst->area), GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | @@ -3610,17 +3858,20 @@ inst->currcursor = inst->textcursor; show_mouseptr(inst, 1); inst->eventlogstuff = eventlogstuff_new(); - inst->term = term_init(&inst->cfg, &inst->ucsdata, inst); - inst->logctx = log_init(inst, &inst->cfg); + request_callback_notifications(notify_toplevel_callback, inst); + + inst->term = term_init(inst->conf, &inst->ucsdata, inst); + inst->logctx = log_init(inst, inst->conf); term_provide_logctx(inst->term, inst->logctx); uxsel_init(); - term_size(inst->term, inst->cfg.height, inst->cfg.width, inst->cfg.savelines); + term_size(inst->term, inst->height, inst->width, + conf_get_int(inst->conf, CONF_savelines)); start_backend(inst); ldisc_send(inst->ldisc, NULL, 0, 0);/* cause ldisc to notice changes */ Index: unix/unix.h ================================================================== --- unix/unix.h +++ unix/unix.h @@ -11,23 +11,21 @@ #include /* Dynamic library loading */ #endif /* NO_LIBDL */ #include "charset.h" struct Filename { - char path[FILENAME_MAX]; + char *path; }; -FILE *f_open(struct Filename, char const *, int); +FILE *f_open(const struct Filename *, char const *, int); struct FontSpec { - char name[256]; + char *name; /* may be "" to indicate no selected font at all */ }; +struct FontSpec *fontspec_new(const char *name); typedef void *Context; /* FIXME: probably needs changing */ -typedef int OSSocket; -#define OSSOCKET_DEFINED /* stop network.h using its default */ - extern Backend pty_backend; typedef uint32_t uint32; /* C99: uint32_t defined in stdint.h */ #define PUTTY_UINT32_DEFINED @@ -57,15 +55,10 @@ /* Simple wraparound timer function */ unsigned long getticks(void); /* based on gettimeofday(2) */ #define GETTICKCOUNT getticks #define TICKSPERSEC 1000 /* we choose to use milliseconds */ #define CURSORBLINK 450 /* no standard way to set this */ -/* getticks() works using gettimeofday(), so it's vulnerable to system clock - * changes causing chaos. Therefore, we provide a compensation mechanism. */ -#define TIMING_SYNC -#define TIMING_SYNC_ANOW -extern long tickcount_offset; #define WCHAR wchar_t #define BYTE unsigned char /* @@ -87,22 +80,27 @@ /* Things gtkdlg.c needs from pterm.c */ void *get_window(void *frontend); /* void * to avoid depending on gtk.h */ /* Things pterm.c needs from gtkdlg.c */ -int do_config_box(const char *title, Config *cfg, +int do_config_box(const char *title, Conf *conf, int midsession, int protcfginfo); void fatal_message_box(void *window, char *msg); +void nonfatal_message_box(void *window, char *msg); void about_box(void *window); void *eventlogstuff_new(void); void showeventlog(void *estuff, void *parentwin); void logevent_dlg(void *estuff, const char *string); int reallyclose(void *frontend); +#ifdef MAY_REFER_TO_GTK_IN_HEADERS +int messagebox(GtkWidget *parentwin, char *title, char *msg, int minwid, ...); +int string_width(char *text); +#endif /* Things pterm.c needs from {ptermm,uxputty}.c */ char *make_default_wintitle(char *hostname); -int process_nonoption_arg(char *arg, Config *cfg, int *allow_launch); +int process_nonoption_arg(char *arg, Conf *conf, int *allow_launch); /* pterm.c needs this special function in xkeysym.c */ int keysym_to_unicode(int keysym); /* Things uxstore.c needs from pterm.c */ @@ -153,11 +151,14 @@ /* BSD-semantics version of signal(), and another helpful function */ void (*putty_signal(int sig, void (*func)(int)))(int); void block_signal(int sig, int block_it); /* uxmisc.c */ -int cloexec(int); +void cloexec(int); +void noncloexec(int); +int nonblock(int); +int no_nonblock(int); /* * Exports from unicode.c. */ struct unicode_data; Index: unix/ux_x11.c ================================================================== --- unix/ux_x11.c +++ unix/ux_x11.c @@ -10,11 +10,11 @@ #include "putty.h" #include "ssh.h" #include "network.h" -void platform_get_x11_auth(struct X11Display *disp, const Config *cfg) +void platform_get_x11_auth(struct X11Display *disp, Conf *conf) { char *xauthfile; int needs_free; /* Index: unix/uxagentc.c ================================================================== --- unix/uxagentc.c +++ unix/uxagentc.c @@ -15,11 +15,12 @@ #include "tree234.h" #include "puttymem.h" int agent_exists(void) { - if (getenv("SSH_AUTH_SOCK") != NULL) + const char *p = getenv("SSH_AUTH_SOCK"); + if (p && *p) return TRUE; return FALSE; } static tree234 *agent_connections; @@ -72,17 +73,16 @@ conn->retlen = 0; goto done; } conn->retlen += ret; if (conn->retsize == 4 && conn->retlen == 4) { - conn->retsize = GET_32BIT(conn->retbuf); + conn->retsize = toint(GET_32BIT(conn->retbuf) + 4); if (conn->retsize <= 0) { conn->retbuf = NULL; conn->retlen = 0; goto done; } - conn->retsize += 4; assert(conn->retbuf == conn->sizebuf); conn->retbuf = snewn(conn->retsize, char); memcpy(conn->retbuf, conn->sizebuf, 4); } Index: unix/uxcfg.c ================================================================== --- unix/uxcfg.c +++ unix/uxcfg.c @@ -14,15 +14,15 @@ { struct controlset *s; union control *c; /* - * The Config structure contains two Unix-specific elements - * which are not configured in here: stamp_utmp and - * login_shell. This is because pterm does not put up a - * configuration box right at the start, which is the only time - * when these elements would be useful to configure. + * The Conf structure contains two Unix-specific elements which + * are not configured in here: stamp_utmp and login_shell. This + * is because pterm does not put up a configuration box right at + * the start, which is the only time when these elements would + * be useful to configure. */ /* * On Unix, we don't have a drop-down list for the printer * control. @@ -39,12 +39,12 @@ int i; s = ctrl_getset(b, "Connection/Proxy", "basics", NULL); for (i = 0; i < s->ncontrols; i++) { c = s->ctrls[i]; if (c->generic.type == CTRL_RADIO && - c->generic.context.i == offsetof(Config, proxy_type)) { - assert(c->generic.handler == dlg_stdradiobutton_handler); + c->generic.context.i == CONF_proxy_type) { + assert(c->generic.handler == conf_radiobutton_handler); c->radio.nbuttons++; c->radio.buttons = sresize(c->radio.buttons, c->radio.nbuttons, char *); c->radio.buttons[c->radio.nbuttons-1] = dupstr("Local"); @@ -56,13 +56,12 @@ } for (i = 0; i < s->ncontrols; i++) { c = s->ctrls[i]; if (c->generic.type == CTRL_EDITBOX && - c->generic.context.i == - offsetof(Config, proxy_telnet_command)) { - assert(c->generic.handler == dlg_stdeditbox_handler); + c->generic.context.i == CONF_proxy_telnet_command) { + assert(c->generic.handler == conf_editbox_handler); sfree(c->generic.label); c->generic.label = dupstr("Telnet command, or local" " proxy command"); break; } Index: unix/uxcons.c ================================================================== --- unix/uxcons.c +++ unix/uxcons.c @@ -5,12 +5,14 @@ #include #include #include #include + #include #include +#include #include "putty.h" #include "storage.h" #include "ssh.h" @@ -66,11 +68,11 @@ void notify_remote_exit(void *frontend) { } -void timer_change_notify(long next) +void timer_change_notify(unsigned long next) { } int verify_ssh_host_key(void *frontend, char *host, int port, char *keytype, char *keystr, char *fingerprint, @@ -231,11 +233,11 @@ /* * Ask whether to wipe a session log file before writing to it. * Returns 2 for wipe, 1 for append, 0 for cancel (don't log). */ -int askappend(void *frontend, Filename filename, +int askappend(void *frontend, Filename *filename, void (*callback)(void *ctx, int result), void *ctx) { static const char msgtemplate[] = "The session log file \"%.*s\" already exists.\n" "You can overwrite it with a new session log,\n" @@ -252,15 +254,15 @@ char line[32]; struct termios cf; premsg(&cf); if (console_batch_mode) { - fprintf(stderr, msgtemplate_batch, FILENAME_MAX, filename.path); + fprintf(stderr, msgtemplate_batch, FILENAME_MAX, filename->path); fflush(stderr); return 0; } - fprintf(stderr, msgtemplate, FILENAME_MAX, filename.path); + fprintf(stderr, msgtemplate, FILENAME_MAX, filename->path); fflush(stderr); { struct termios oldmode, newmode; tcgetattr(0, &oldmode); @@ -317,105 +319,139 @@ } void logevent(void *frontend, const char *string) { struct termios cf; - premsg(&cf); + if ((flags & FLAG_STDERR) && (flags & FLAG_VERBOSE)) + premsg(&cf); if (console_logctx) log_eventlog(console_logctx, string); - postmsg(&cf); + if ((flags & FLAG_STDERR) && (flags & FLAG_VERBOSE)) + postmsg(&cf); } /* - * Special function to print text to the console for password - * prompts and the like. Uses /dev/tty or stderr, in that order of - * preference; also sanitises escape sequences out of the text, on + * Special functions to read and print to the console for password + * prompts and the like. Uses /dev/tty or stdin/stderr, in that order + * of preference; also sanitises escape sequences out of the text, on * the basis that it might have been sent by a hostile SSH server * doing malicious keyboard-interactive. */ -static void console_prompt_text(FILE **confp, const char *data, int len) +static void console_open(FILE **outfp, int *infd) +{ + int fd; + + if ((fd = open("/dev/tty", O_RDWR)) >= 0) { + *infd = fd; + *outfp = fdopen(*infd, "w"); + } else { + *infd = 0; + *outfp = stderr; + } +} +static void console_close(FILE *outfp, int infd) +{ + if (outfp != stderr) + fclose(outfp); /* will automatically close infd too */ +} + +static void console_prompt_text(FILE *outfp, const char *data, int len) { int i; - if (!*confp) { - if ((*confp = fopen("/dev/tty", "w")) == NULL) - *confp = stderr; - } - for (i = 0; i < len; i++) if ((data[i] & 0x60) || (data[i] == '\n')) - fputc(data[i], *confp); - fflush(*confp); + fputc(data[i], outfp); + fflush(outfp); } int console_get_userpass_input(prompts_t *p, unsigned char *in, int inlen) { size_t curr_prompt; - FILE *confp = NULL; + FILE *outfp = NULL; + int infd; /* * Zero all the results, in case we abort half-way through. */ { int i; for (i = 0; i < p->n_prompts; i++) - memset(p->prompts[i]->result, 0, p->prompts[i]->result_len); + prompt_set_result(p->prompts[i], ""); } if (p->n_prompts && console_batch_mode) return 0; + + console_open(&outfp, &infd); /* * Preamble. */ /* We only print the `name' caption if we have to... */ if (p->name_reqd && p->name) { size_t l = strlen(p->name); - console_prompt_text(&confp, p->name, l); + console_prompt_text(outfp, p->name, l); if (p->name[l-1] != '\n') - console_prompt_text(&confp, "\n", 1); + console_prompt_text(outfp, "\n", 1); } /* ...but we always print any `instruction'. */ if (p->instruction) { size_t l = strlen(p->instruction); - console_prompt_text(&confp, p->instruction, l); + console_prompt_text(outfp, p->instruction, l); if (p->instruction[l-1] != '\n') - console_prompt_text(&confp, "\n", 1); + console_prompt_text(outfp, "\n", 1); } for (curr_prompt = 0; curr_prompt < p->n_prompts; curr_prompt++) { struct termios oldmode, newmode; - int i; + int len; prompt_t *pr = p->prompts[curr_prompt]; - tcgetattr(0, &oldmode); + tcgetattr(infd, &oldmode); newmode = oldmode; newmode.c_lflag |= ISIG | ICANON; if (!pr->echo) newmode.c_lflag &= ~ECHO; else newmode.c_lflag |= ECHO; - tcsetattr(0, TCSANOW, &newmode); - - console_prompt_text(&confp, pr->prompt, strlen(pr->prompt)); - - i = read(0, pr->result, pr->result_len - 1); - - tcsetattr(0, TCSANOW, &oldmode); - - if (i > 0 && pr->result[i-1] == '\n') - i--; - pr->result[i] = '\0'; + tcsetattr(infd, TCSANOW, &newmode); + + console_prompt_text(outfp, pr->prompt, strlen(pr->prompt)); + + len = 0; + while (1) { + int ret; + + prompt_ensure_result_size(pr, len * 5 / 4 + 512); + ret = read(infd, pr->result + len, pr->resultsize - len - 1); + if (ret <= 0) { + len = -1; + break; + } + len += ret; + if (pr->result[len - 1] == '\n') { + len--; + break; + } + } + + tcsetattr(infd, TCSANOW, &oldmode); if (!pr->echo) - console_prompt_text(&confp, "\n", 1); + console_prompt_text(outfp, "\n", 1); + + if (len < 0) { + console_close(outfp, infd); + return 0; /* failure due to read error */ + } + pr->result[len] = '\0'; } - if (confp && confp != stderr) - fclose(confp); + console_close(outfp, infd); return 1; /* success */ } void frontend_keypress(void *handle) Index: unix/uxgen.c ================================================================== --- unix/uxgen.c +++ unix/uxgen.c @@ -24,10 +24,11 @@ ngot = 0; while (ngot < len) { ret = read(fd, buf+ngot, len-ngot); if (ret < 0) { close(fd); + sfree(buf); perror("puttygen: unable to read /dev/random"); return NULL; } ngot += ret; } Index: unix/uxgss.c ================================================================== --- unix/uxgss.c +++ unix/uxgss.c @@ -51,13 +51,14 @@ ssh_gssapi_bind_fns(lib); } /* Dynamically load gssapi libs. */ -struct ssh_gss_liblist *ssh_gss_setup(const Config *cfg) +struct ssh_gss_liblist *ssh_gss_setup(Conf *conf) { void *gsslib; + char *gsspath; struct ssh_gss_liblist *list = snew(struct ssh_gss_liblist); list->libraries = snewn(4, struct ssh_gss_library); list->nlibraries = 0; @@ -75,15 +76,15 @@ if ((gsslib = dlopen("libgss.so.1", RTLD_LAZY)) != NULL) gss_init(&list->libraries[list->nlibraries++], gsslib, 2, "Using GSSAPI from libgss.so.1"); /* User-specified GSSAPI library */ - if (cfg->ssh_gss_custom.path[0] && - (gsslib = dlopen(cfg->ssh_gss_custom.path, RTLD_LAZY)) != NULL) + gsspath = conf_get_filename(conf, CONF_ssh_gss_custom)->path; + if (*gsspath && (gsslib = dlopen(gsspath, RTLD_LAZY)) != NULL) gss_init(&list->libraries[list->nlibraries++], gsslib, 3, dupprintf("Using GSSAPI from user-specified" - " library '%s'", cfg->ssh_gss_custom.path)); + " library '%s'", gsspath)); return list; } void ssh_gss_cleanup(struct ssh_gss_liblist *list) @@ -127,11 +128,11 @@ */ #include /* Dynamically load gssapi libs. */ -struct ssh_gss_liblist *ssh_gss_setup(const Config *cfg) +struct ssh_gss_liblist *ssh_gss_setup(Conf *conf) { struct ssh_gss_liblist *list = snew(struct ssh_gss_liblist); list->libraries = snew(struct ssh_gss_library); list->nlibraries = 1; Index: unix/uxmisc.c ================================================================== --- unix/uxmisc.c +++ unix/uxmisc.c @@ -4,52 +4,96 @@ #include #include #include #include +#include #include +#include #include #include #include #include "putty.h" -long tickcount_offset = 0; - unsigned long getticks(void) { - struct timeval tv; - gettimeofday(&tv, NULL); /* - * We want to use milliseconds rather than microseconds, - * because we need a decent number of them to fit into a 32-bit - * word so it can be used for keepalives. + * We want to use milliseconds rather than the microseconds or + * nanoseconds given by the underlying clock functions, because we + * need a decent number of them to fit into a 32-bit word so it + * can be used for keepalives. */ - return tv.tv_sec * 1000 + tv.tv_usec / 1000 + tickcount_offset; -} - -Filename filename_from_str(const char *str) -{ - Filename ret; - strncpy(ret.path, str, sizeof(ret.path)); - ret.path[sizeof(ret.path)-1] = '\0'; +#if defined HAVE_CLOCK_GETTIME && defined HAVE_DECL_CLOCK_MONOTONIC + { + /* Use CLOCK_MONOTONIC if available, so as to be unconfused if + * the system clock changes. */ + struct timespec ts; + if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) + return ts.tv_sec * TICKSPERSEC + + ts.tv_nsec / (1000000000 / TICKSPERSEC); + } +#endif + { + struct timeval tv; + gettimeofday(&tv, NULL); + return tv.tv_sec * TICKSPERSEC + tv.tv_usec / (1000000 / TICKSPERSEC); + } +} + +Filename *filename_from_str(const char *str) +{ + Filename *ret = snew(Filename); + ret->path = dupstr(str); return ret; } + +Filename *filename_copy(const Filename *fn) +{ + return filename_from_str(fn->path); +} const char *filename_to_str(const Filename *fn) { return fn->path; } -int filename_equal(Filename f1, Filename f2) -{ - return !strcmp(f1.path, f2.path); -} - -int filename_is_null(Filename fn) -{ - return !*fn.path; +int filename_equal(const Filename *f1, const Filename *f2) +{ + return !strcmp(f1->path, f2->path); +} + +int filename_is_null(const Filename *fn) +{ + return !fn->path[0]; +} + +void filename_free(Filename *fn) +{ + sfree(fn->path); + sfree(fn); +} + +int filename_serialise(const Filename *f, void *vdata) +{ + char *data = (char *)vdata; + int len = strlen(f->path) + 1; /* include trailing NUL */ + if (data) { + strcpy(data, f->path); + } + return len; +} +Filename *filename_deserialise(void *vdata, int maxsize, int *used) +{ + char *data = (char *)vdata; + char *end; + end = memchr(data, '\0', maxsize); + if (!end) + return NULL; + end++; + *used = end - data; + return filename_from_str(data); } #ifdef DEBUG static FILE *debug_fp = NULL; @@ -123,30 +167,117 @@ "PuTTY Master Key (DSA), 1024-bit:\n" " " PGP_DSA_MASTER_KEY_FP "\n", stdout); } /* - * Set FD_CLOEXEC on a file descriptor + * Set and clear fcntl options on a file descriptor. We don't + * realistically expect any of these operations to fail (the most + * plausible error condition is EBADF, but we always believe ourselves + * to be passing a valid fd so even that's an assertion-fail sort of + * response), so we don't make any effort to return sensible error + * codes to the caller - we just log to standard error and die + * unceremoniously. However, nonblock and no_nonblock do return the + * previous state of O_NONBLOCK. */ -int cloexec(int fd) { +void cloexec(int fd) { + int fdflags; + + fdflags = fcntl(fd, F_GETFD); + if (fdflags < 0) { + fprintf(stderr, "%d: fcntl(F_GETFD): %s\n", fd, strerror(errno)); + exit(1); + } + if (fcntl(fd, F_SETFD, fdflags | FD_CLOEXEC) < 0) { + fprintf(stderr, "%d: fcntl(F_SETFD): %s\n", fd, strerror(errno)); + exit(1); + } +} +void noncloexec(int fd) { int fdflags; fdflags = fcntl(fd, F_GETFD); - if (fdflags == -1) return -1; - return fcntl(fd, F_SETFD, fdflags | FD_CLOEXEC); + if (fdflags < 0) { + fprintf(stderr, "%d: fcntl(F_GETFD): %s\n", fd, strerror(errno)); + exit(1); + } + if (fcntl(fd, F_SETFD, fdflags & ~FD_CLOEXEC) < 0) { + fprintf(stderr, "%d: fcntl(F_SETFD): %s\n", fd, strerror(errno)); + exit(1); + } +} +int nonblock(int fd) { + int fdflags; + + fdflags = fcntl(fd, F_GETFL); + if (fdflags < 0) { + fprintf(stderr, "%d: fcntl(F_GETFL): %s\n", fd, strerror(errno)); + exit(1); + } + if (fcntl(fd, F_SETFL, fdflags | O_NONBLOCK) < 0) { + fprintf(stderr, "%d: fcntl(F_SETFL): %s\n", fd, strerror(errno)); + exit(1); + } + + return fdflags & O_NONBLOCK; +} +int no_nonblock(int fd) { + int fdflags; + + fdflags = fcntl(fd, F_GETFL); + if (fdflags < 0) { + fprintf(stderr, "%d: fcntl(F_GETFL): %s\n", fd, strerror(errno)); + exit(1); + } + if (fcntl(fd, F_SETFL, fdflags & ~O_NONBLOCK) < 0) { + fprintf(stderr, "%d: fcntl(F_SETFL): %s\n", fd, strerror(errno)); + exit(1); + } + + return fdflags & O_NONBLOCK; } -FILE *f_open(struct Filename filename, char const *mode, int is_private) +FILE *f_open(const Filename *filename, char const *mode, int is_private) { if (!is_private) { - return fopen(filename.path, mode); + return fopen(filename->path, mode); } else { int fd; assert(mode[0] == 'w'); /* is_private is meaningless for read, and tricky for append */ - fd = open(filename.path, O_WRONLY | O_CREAT | O_TRUNC, - 0700); + fd = open(filename->path, O_WRONLY | O_CREAT | O_TRUNC, 0600); if (fd < 0) return NULL; return fdopen(fd, mode); } } + +FontSpec *fontspec_new(const char *name) +{ + FontSpec *f = snew(FontSpec); + f->name = dupstr(name); + return f; +} +FontSpec *fontspec_copy(const FontSpec *f) +{ + return fontspec_new(f->name); +} +void fontspec_free(FontSpec *f) +{ + sfree(f->name); + sfree(f); +} +int fontspec_serialise(FontSpec *f, void *data) +{ + int len = strlen(f->name); + if (data) + strcpy(data, f->name); + return len + 1; /* include trailing NUL */ +} +FontSpec *fontspec_deserialise(void *vdata, int maxsize, int *used) +{ + char *data = (char *)vdata; + char *end = memchr(data, '\0', maxsize); + if (!end) + return NULL; + *used = end - data + 1; + return fontspec_new(data); +} Index: unix/uxnet.c ================================================================== --- unix/uxnet.c +++ unix/uxnet.c @@ -73,22 +73,21 @@ struct socket_function_table *fn; /* the above variable absolutely *must* be the first in this structure */ const char *error; int s; Plug plug; - void *private_ptr; bufchain output_data; int connected; /* irrelevant for listening sockets */ int writable; int frozen; /* this causes readability notifications to be ignored */ - int frozen_readable; /* this means we missed at least one readability - * notification while we were frozen */ int localhost_only; /* for listening sockets */ char oobdata[1]; int sending_oob; int oobpending; /* is there OOB data available to read? */ int oobinline; + enum { EOF_NO, EOF_PENDING, EOF_SENT } outgoingeof; + int incomingeof; int pending_error; /* in case send() returns error */ int listener; int nodelay, keepalive; /* for connect()-type sockets */ int privport, port; /* and again */ SockAddr addr; @@ -314,14 +313,11 @@ #endif } void sk_getaddr(SockAddr addr, char *buf, int buflen) { - /* XXX not clear what we should return for Unix-domain sockets; let's - * hope the question never arises */ - assert(addr->superfamily != UNIX); - if (addr->superfamily == UNRESOLVED) { + if (addr->superfamily == UNRESOLVED || addr->superfamily == UNIX) { strncpy(buf, addr->hostname, buflen); buf[buflen-1] = '\0'; } else { #ifndef NO_IPV6 if (getnameinfo(addr->ais->ai_addr, addr->ais->ai_addrlen, buf, buflen, @@ -339,11 +335,20 @@ buf[buflen-1] = '\0'; #endif } } -int sk_hostname_is_local(char *name) +int sk_addr_needs_port(SockAddr addr) +{ + if (addr->superfamily == UNRESOLVED || addr->superfamily == UNIX) { + return FALSE; + } else { + return TRUE; + } +} + +int sk_hostname_is_local(const char *name) { return !strcmp(name, "localhost") || !strcmp(name, "::1") || !strncmp(name, "127.", 4); } @@ -385,10 +390,15 @@ a.s_addr = htonl(addr->addresses[0]); return ipv4_is_loopback(a); #endif } } + +int sk_address_is_special_local(SockAddr addr) +{ + return addr->superfamily == UNIX; +} int sk_addrtype(SockAddr addr) { SockAddrStep step; int family; @@ -464,29 +474,28 @@ } static void sk_tcp_close(Socket s); static int sk_tcp_write(Socket s, const char *data, int len); static int sk_tcp_write_oob(Socket s, const char *data, int len); -static void sk_tcp_set_private_ptr(Socket s, void *ptr); -static void *sk_tcp_get_private_ptr(Socket s); +static void sk_tcp_write_eof(Socket s); static void sk_tcp_set_frozen(Socket s, int is_frozen); static const char *sk_tcp_socket_error(Socket s); static struct socket_function_table tcp_fn_table = { sk_tcp_plug, sk_tcp_close, sk_tcp_write, sk_tcp_write_oob, + sk_tcp_write_eof, sk_tcp_flush, - sk_tcp_set_private_ptr, - sk_tcp_get_private_ptr, sk_tcp_set_frozen, sk_tcp_socket_error }; -Socket sk_register(OSSocket sockfd, Plug plug) +static Socket sk_tcp_accept(accept_ctx_t ctx, Plug plug) { + int sockfd = ctx.i; Actual_Socket ret; /* * Create Socket structure. */ @@ -496,14 +505,15 @@ ret->plug = plug; bufchain_init(&ret->output_data); ret->writable = 1; /* to start with */ ret->sending_oob = 0; ret->frozen = 1; - ret->frozen_readable = 0; ret->localhost_only = 0; /* unused, but best init anyway */ ret->pending_error = 0; ret->oobpending = FALSE; + ret->outgoingeof = EOF_NO; + ret->incomingeof = FALSE; ret->listener = 0; ret->parent = ret->child = NULL; ret->addr = NULL; ret->connected = 1; @@ -527,11 +537,11 @@ int s; union sockaddr_union u; const union sockaddr_union *sa; int err = 0; short localport; - int fl, salen, family; + int salen, family; /* * Remove the socket from the tree before we overwrite its * internal socket id, because that forms part of the tree's * sorting criterion. We'll add it back before exiting this @@ -559,21 +569,36 @@ cloexec(s); if (sock->oobinline) { int b = TRUE; - setsockopt(s, SOL_SOCKET, SO_OOBINLINE, (void *) &b, sizeof(b)); + if (setsockopt(s, SOL_SOCKET, SO_OOBINLINE, + (void *) &b, sizeof(b)) < 0) { + err = errno; + close(s); + goto ret; + } } if (sock->nodelay) { int b = TRUE; - setsockopt(s, IPPROTO_TCP, TCP_NODELAY, (void *) &b, sizeof(b)); + if (setsockopt(s, IPPROTO_TCP, TCP_NODELAY, + (void *) &b, sizeof(b)) < 0) { + err = errno; + close(s); + goto ret; + } } if (sock->keepalive) { int b = TRUE; - setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, (void *) &b, sizeof(b)); + if (setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, + (void *) &b, sizeof(b)) < 0) { + err = errno; + close(s); + goto ret; + } } /* * Bind to local address. */ @@ -667,13 +692,11 @@ default: assert(0 && "unknown address family"); exit(1); /* XXX: GCC doesn't understand assert() on some systems. */ } - fl = fcntl(s, F_GETFL); - if (fl != -1) - fcntl(s, F_SETFL, fl | O_NONBLOCK); + nonblock(s); if ((connect(s, &(sa->sa), salen)) < 0) { if ( errno != EINPROGRESS ) { err = errno; goto ret; @@ -717,15 +740,16 @@ bufchain_init(&ret->output_data); ret->connected = 0; /* to start with */ ret->writable = 0; /* to start with */ ret->sending_oob = 0; ret->frozen = 0; - ret->frozen_readable = 0; ret->localhost_only = 0; /* unused, but best init anyway */ ret->pending_error = 0; ret->parent = ret->child = NULL; ret->oobpending = FALSE; + ret->outgoingeof = EOF_NO; + ret->incomingeof = FALSE; ret->listener = 0; ret->addr = addr; START_STEP(ret->addr, ret->step); ret->s = -1; ret->oobinline = oobinline; @@ -769,17 +793,19 @@ ret->plug = plug; bufchain_init(&ret->output_data); ret->writable = 0; /* to start with */ ret->sending_oob = 0; ret->frozen = 0; - ret->frozen_readable = 0; ret->localhost_only = local_host_only; ret->pending_error = 0; ret->parent = ret->child = NULL; ret->oobpending = FALSE; + ret->outgoingeof = EOF_NO; + ret->incomingeof = FALSE; ret->listener = 1; ret->addr = NULL; + ret->s = -1; /* * Translate address_family from platform-independent constants * into local reality. */ @@ -818,11 +844,16 @@ cloexec(s); ret->oobinline = 0; - setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (const char *)&on, sizeof(on)); + if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, + (const char *)&on, sizeof(on)) < 0) { + ret->error = strerror(errno); + close(s); + return (Socket) ret; + } retcode = -1; addr = NULL; addrlen = -1; /* placate optimiser */ if (srcaddr != NULL) { @@ -994,10 +1025,30 @@ return NULL; } return buf; } + +/* + * Deal with socket errors detected in try_send(). + */ +static void socket_error_callback(void *vs) +{ + Actual_Socket s = (Actual_Socket)vs; + + /* + * Just in case other socket work has caused this socket to vanish + * or become somehow non-erroneous before this callback arrived... + */ + if (!find234(sktree, s, NULL) || !s->pending_error) + return; + + /* + * An error has occurred on this socket. Pass it to the plug. + */ + plug_closing(s->plug, strerror(s->pending_error), s->pending_error, 0); +} /* * The function which tries to send on a socket once it's deemed * writable. */ @@ -1036,10 +1087,21 @@ * reentrant. Instead we flag a pending error on * the socket, to be dealt with (by calling * plug_closing()) at some suitable future moment. */ s->pending_error = err; + /* + * Immediately cease selecting on this socket, so that + * we don't tight-loop repeatedly trying to do + * whatever it was that went wrong. + */ + uxsel_tell(s); + /* + * Arrange to be called back from the top level to + * deal with the error condition on this socket. + */ + queue_toplevel_callback(socket_error_callback, s); return; } } else { if (s->sending_oob) { if (nsent < len) { @@ -1051,16 +1113,32 @@ } else { bufchain_consume(&s->output_data, nsent); } } } + + /* + * If we reach here, we've finished sending everything we might + * have needed to send. Send EOF, if we need to. + */ + if (s->outgoingeof == EOF_PENDING) { + shutdown(s->s, SHUT_WR); + s->outgoingeof = EOF_SENT; + } + + /* + * Also update the select status, because we don't need to select + * for writing any more. + */ uxsel_tell(s); } static int sk_tcp_write(Socket sock, const char *buf, int len) { Actual_Socket s = (Actual_Socket) sock; + + assert(s->outgoingeof == EOF_NO); /* * Add the data to the buffer list on the socket. */ bufchain_add(&s->output_data, buf, len); @@ -1082,10 +1160,12 @@ static int sk_tcp_write_oob(Socket sock, const char *buf, int len) { Actual_Socket s = (Actual_Socket) sock; + assert(s->outgoingeof == EOF_NO); + /* * Replace the buffer list on the socket with the data. */ bufchain_clear(&s->output_data); assert(len <= sizeof(s->oobdata)); @@ -1104,10 +1184,34 @@ */ uxsel_tell(s); return s->sending_oob; } + +static void sk_tcp_write_eof(Socket sock) +{ + Actual_Socket s = (Actual_Socket) sock; + + assert(s->outgoingeof == EOF_NO); + + /* + * Mark the socket as pending outgoing EOF. + */ + s->outgoingeof = EOF_PENDING; + + /* + * Now try sending from the start of the buffer list. + */ + if (s->writable) + try_send(s); + + /* + * Update the select() status to correctly reflect whether or + * not we should be selecting for write. + */ + uxsel_tell(s); +} static int net_select_result(int fd, int event) { int ret; char buf[20480]; /* nice big buffer for plenty of speed */ @@ -1165,27 +1269,26 @@ * On a listening socket, the readability event means a * connection is ready to be accepted. */ union sockaddr_union su; socklen_t addrlen = sizeof(su); + accept_ctx_t actx; int t; /* socket of connection */ - int fl; memset(&su, 0, addrlen); t = accept(s->s, &su.sa, &addrlen); if (t < 0) { break; } - fl = fcntl(t, F_GETFL); - if (fl != -1) - fcntl(t, F_SETFL, fl | O_NONBLOCK); + nonblock(t); + actx.i = t; - if (s->localhost_only && - !sockaddr_is_loopback(&su.sa)) { + if ((!s->addr || s->addr->superfamily != UNIX) && + s->localhost_only && !sockaddr_is_loopback(&su.sa)) { close(t); /* someone let nonlocal through?! */ - } else if (plug_accepting(s->plug, t)) { + } else if (plug_accepting(s->plug, sk_tcp_accept, actx)) { close(t); /* denied or error */ } break; } @@ -1193,14 +1296,12 @@ * If we reach here, this is not a listening socket, so * readability really means readability. */ /* In the case the socket is still frozen, we don't even bother */ - if (s->frozen) { - s->frozen_readable = 1; + if (s->frozen) break; - } /* * We have received data on the socket. For an oobinline * socket, this might be data _before_ an urgent pointer, * in which case we send it to the back end with type==1 @@ -1235,10 +1336,12 @@ } } if (err != 0) return plug_closing(s->plug, strerror(err), err, 0); } else if (0 == ret) { + s->incomingeof = TRUE; /* stop trying to read now */ + uxsel_tell(s); return plug_closing(s->plug, NULL, 0, 0); } else { /* * Receiving actual data on a socket means we can * stop falling back through the candidate @@ -1273,63 +1376,10 @@ } return 1; } -/* - * Deal with socket errors detected in try_send(). - */ -void net_pending_errors(void) -{ - int i; - Actual_Socket s; - - /* - * This might be a fiddly business, because it's just possible - * that handling a pending error on one socket might cause - * others to be closed. (I can't think of any reason this might - * happen in current SSH implementation, but to maintain - * generality of this network layer I'll assume the worst.) - * - * So what we'll do is search the socket list for _one_ socket - * with a pending error, and then handle it, and then search - * the list again _from the beginning_. Repeat until we make a - * pass with no socket errors present. That way we are - * protected against the socket list changing under our feet. - */ - - do { - for (i = 0; (s = index234(sktree, i)) != NULL; i++) { - if (s->pending_error) { - /* - * An error has occurred on this socket. Pass it to the - * plug. - */ - plug_closing(s->plug, strerror(s->pending_error), - s->pending_error, 0); - break; - } - } - } while (s); -} - -/* - * Each socket abstraction contains a `void *' private field in - * which the client can keep state. - */ -static void sk_tcp_set_private_ptr(Socket sock, void *ptr) -{ - Actual_Socket s = (Actual_Socket) sock; - s->private_ptr = ptr; -} - -static void *sk_tcp_get_private_ptr(Socket sock) -{ - Actual_Socket s = (Actual_Socket) sock; - return s->private_ptr; -} - /* * Special error values are returned from sk_namelookup and sk_new * if there's a problem. These functions extract an error message, * or return NULL if there's no problem. */ @@ -1347,30 +1397,27 @@ { Actual_Socket s = (Actual_Socket) sock; if (s->frozen == is_frozen) return; s->frozen = is_frozen; - if (!is_frozen && s->frozen_readable) { - char c; - recv(s->s, &c, 1, MSG_PEEK); - } - s->frozen_readable = 0; uxsel_tell(s); } static void uxsel_tell(Actual_Socket s) { int rwx = 0; - if (s->listener) { - rwx |= 1; /* read == accept */ - } else { - if (!s->connected) - rwx |= 2; /* write == connect */ - if (s->connected && !s->frozen) - rwx |= 1 | 4; /* read, except */ - if (bufchain_size(&s->output_data)) - rwx |= 2; /* write */ + if (!s->pending_error) { + if (s->listener) { + rwx |= 1; /* read == accept */ + } else { + if (!s->connected) + rwx |= 2; /* write == connect */ + if (s->connected && !s->frozen && !s->incomingeof) + rwx |= 1 | 4; /* read, except */ + if (bufchain_size(&s->output_data)) + rwx |= 2; /* write */ + } } uxsel_set(s->s, rwx, net_select_result); } int net_service_lookup(char *service) @@ -1431,5 +1478,107 @@ ret->naddresses = 0; #endif ret->refcount = 1; return ret; } + +SockAddr unix_sock_addr(const char *path) +{ + SockAddr ret = snew(struct SockAddr_tag); + int n; + + memset(ret, 0, sizeof *ret); + ret->superfamily = UNIX; + n = snprintf(ret->hostname, sizeof ret->hostname, "%s", path); + + if (n < 0) + ret->error = "snprintf failed"; + else if (n >= sizeof ret->hostname) + ret->error = "socket pathname too long"; + +#ifndef NO_IPV6 + ret->ais = NULL; +#else + ret->addresses = NULL; + ret->naddresses = 0; +#endif + ret->refcount = 1; + return ret; +} + +Socket new_unix_listener(SockAddr listenaddr, Plug plug) +{ + int s; + union sockaddr_union u; + union sockaddr_union *addr; + int addrlen; + Actual_Socket ret; + int retcode; + + /* + * Create Socket structure. + */ + ret = snew(struct Socket_tag); + ret->fn = &tcp_fn_table; + ret->error = NULL; + ret->plug = plug; + bufchain_init(&ret->output_data); + ret->writable = 0; /* to start with */ + ret->sending_oob = 0; + ret->frozen = 0; + ret->localhost_only = TRUE; + ret->pending_error = 0; + ret->parent = ret->child = NULL; + ret->oobpending = FALSE; + ret->outgoingeof = EOF_NO; + ret->incomingeof = FALSE; + ret->listener = 1; + ret->addr = listenaddr; + ret->s = -1; + + assert(listenaddr->superfamily == UNIX); + + /* + * Open socket. + */ + s = socket(AF_UNIX, SOCK_STREAM, 0); + if (s < 0) { + ret->error = strerror(errno); + return (Socket) ret; + } + + cloexec(s); + + ret->oobinline = 0; + + memset(&u, '\0', sizeof(u)); + u.su.sun_family = AF_UNIX; + strncpy(u.su.sun_path, listenaddr->hostname, sizeof(u.su.sun_path)-1); + addr = &u; + addrlen = sizeof(u.su); + + if (unlink(u.su.sun_path) < 0 && errno != ENOENT) { + close(s); + ret->error = strerror(errno); + return (Socket) ret; + } + + retcode = bind(s, &addr->sa, addrlen); + if (retcode < 0) { + close(s); + ret->error = strerror(errno); + return (Socket) ret; + } + + if (listen(s, SOMAXCONN) < 0) { + close(s); + ret->error = strerror(errno); + return (Socket) ret; + } + + ret->s = s; + + uxsel_tell(ret); + add234(sktree, ret); + + return (Socket) ret; +} Index: unix/uxplink.c ================================================================== --- unix/uxplink.c +++ unix/uxplink.c @@ -60,10 +60,22 @@ if (logctx) { log_free(logctx); logctx = NULL; } cleanup_exit(1); +} +void nonfatal(char *p, ...) +{ + struct termios cf; + va_list ap; + premsg(&cf); + fprintf(stderr, "ERROR: "); + va_start(ap, p); + vfprintf(stderr, p, ap); + va_end(ap); + fputc('\n', stderr); + postmsg(&cf); } void connection_fatal(void *frontend, char *p, ...) { struct termios cf; va_list ap; @@ -96,11 +108,11 @@ static int local_tty = FALSE; /* do we have a local tty? */ static Backend *back; static void *backhandle; -static Config cfg; +static Conf *conf; /* * Default settings that are specific to pterm. */ char *platform_default_s(const char *name) @@ -114,34 +126,24 @@ return NULL; } int platform_default_i(const char *name, int def) { - if (!strcmp(name, "TermWidth") || - !strcmp(name, "TermHeight")) { - struct winsize size; - if (ioctl(STDIN_FILENO, TIOCGWINSZ, (void *)&size) >= 0) - return (!strcmp(name, "TermWidth") ? size.ws_col : size.ws_row); - } return def; } -FontSpec platform_default_fontspec(const char *name) -{ - FontSpec ret; - *ret.name = '\0'; - return ret; -} - -Filename platform_default_filename(const char *name) -{ - Filename ret; +FontSpec *platform_default_fontspec(const char *name) +{ + return fontspec_new(""); +} + +Filename *platform_default_filename(const char *name) +{ if (!strcmp(name, "LogFileName")) - strcpy(ret.path, "putty.log"); + return filename_from_str("putty.log"); else - *ret.path = '\0'; - return ret; + return filename_from_str(""); } char *x_get_default(const char *key) { return NULL; /* this is a stub */ @@ -200,11 +202,11 @@ static char *get_ttychar(struct termios *t, int index) { cc_t c = t->c_cc[index]; #if defined(_POSIX_VDISABLE) if (c == _POSIX_VDISABLE) - return dupprintf(""); + return dupstr(""); #endif return dupprintf("^<%d>", c); } char *get_ttymode(void *frontend, const char *mode) @@ -381,35 +383,37 @@ if (local_tty) tcsetattr(STDIN_FILENO, TCSANOW, &orig_termios); } bufchain stdout_data, stderr_data; +enum { EOF_NO, EOF_PENDING, EOF_SENT } outgoingeof; int try_output(int is_stderr) { bufchain *chain = (is_stderr ? &stderr_data : &stdout_data); int fd = (is_stderr ? STDERR_FILENO : STDOUT_FILENO); void *senddata; - int sendlen, ret, fl; - - if (bufchain_size(chain) == 0) - return bufchain_size(&stdout_data) + bufchain_size(&stderr_data); - - fl = fcntl(fd, F_GETFL); - if (fl != -1 && !(fl & O_NONBLOCK)) - fcntl(fd, F_SETFL, fl | O_NONBLOCK); - do { - bufchain_prefix(chain, &senddata, &sendlen); - ret = write(fd, senddata, sendlen); - if (ret > 0) - bufchain_consume(chain, ret); - } while (ret == sendlen && bufchain_size(chain) != 0); - if (fl != -1 && !(fl & O_NONBLOCK)) - fcntl(fd, F_SETFL, fl); - if (ret < 0 && errno != EAGAIN) { - perror(is_stderr ? "stderr: write" : "stdout: write"); - exit(1); + int sendlen, ret; + + if (bufchain_size(chain) > 0) { + int prev_nonblock = nonblock(fd); + do { + bufchain_prefix(chain, &senddata, &sendlen); + ret = write(fd, senddata, sendlen); + if (ret > 0) + bufchain_consume(chain, ret); + } while (ret == sendlen && bufchain_size(chain) != 0); + if (!prev_nonblock) + no_nonblock(fd); + if (ret < 0 && errno != EAGAIN) { + perror(is_stderr ? "stderr: write" : "stdout: write"); + exit(1); + } + } + if (outgoingeof == EOF_PENDING && bufchain_size(&stdout_data) == 0) { + close(STDOUT_FILENO); + outgoingeof = EOF_SENT; } return bufchain_size(&stdout_data) + bufchain_size(&stderr_data); } int from_backend(void *frontend_handle, int is_stderr, @@ -417,10 +421,11 @@ { if (is_stderr) { bufchain_add(&stderr_data, data, len); return try_output(TRUE); } else { + assert(outgoingeof == EOF_NO); bufchain_add(&stdout_data, data, len); return try_output(FALSE); } } @@ -431,10 +436,18 @@ * currently, it's all diverted by FLAG_STDERR). */ assert(!"Unexpected call to from_backend_untrusted()"); return 0; /* not reached */ } + +int from_backend_eof(void *frontend_handle) +{ + assert(outgoingeof == EOF_NO); + outgoingeof = EOF_PENDING; + try_output(FALSE); + return FALSE; /* do not respond to incoming EOF with outgoing */ +} int get_userpass_input(prompts_t *p, unsigned char *in, int inlen) { int ret; ret = cmdline_get_passwd_input(p, in, inlen); @@ -574,10 +587,15 @@ { printf("plink: %s\n", ver); exit(1); } +void frontend_net_error_pending(void) {} + +const int share_can_be_downstream = TRUE; +const int share_can_be_upstream = TRUE; + int main(int argc, char **argv) { int sending; int portnumber = -1; int *fdlist; @@ -586,50 +604,58 @@ int connopen; int exitcode; int errors; int use_subsystem = 0; int got_host = FALSE; - long now; + unsigned long now; + struct winsize size; fdlist = NULL; fdcount = fdsize = 0; /* * Initialise port and protocol to sensible defaults. (These * will be overridden by more or less anything.) */ default_protocol = PROT_SSH; default_port = 22; + + bufchain_init(&stdout_data); + bufchain_init(&stderr_data); + outgoingeof = EOF_NO; flags = FLAG_STDERR | FLAG_STDERR_TTY; stderr_tty_init(); /* * Process the command line. */ - do_defaults(NULL, &cfg); + conf = conf_new(); + do_defaults(NULL, conf); loaded_session = FALSE; - default_protocol = cfg.protocol; - default_port = cfg.port; + default_protocol = conf_get_int(conf, CONF_protocol); + default_port = conf_get_int(conf, CONF_port); errors = 0; { /* * Override the default protocol if PLINK_PROTOCOL is set. */ char *p = getenv("PLINK_PROTOCOL"); if (p) { const Backend *b = backend_from_name(p); if (b) { - default_protocol = cfg.protocol = b->protocol; - default_port = cfg.port = b->default_port; + default_protocol = b->protocol; + default_port = b->default_port; + conf_set_int(conf, CONF_protocol, default_protocol); + conf_set_int(conf, CONF_port, default_port); } } } while (--argc) { char *p = *++argv; if (*p == '-') { int ret = cmdline_process_param(p, (argc > 1 ? argv[1] : NULL), - 1, &cfg); + 1, conf); if (ret == -2) { fprintf(stderr, "plink: option \"%s\" requires an argument\n", p); errors = 1; } else if (ret == 2) { @@ -637,14 +663,17 @@ } else if (ret == 1) { continue; } else if (!strcmp(p, "-batch")) { console_batch_mode = 1; } else if (!strcmp(p, "-s")) { - /* Save status to write to cfg later. */ + /* Save status to write to conf later. */ use_subsystem = 1; - } else if (!strcmp(p, "-V")) { + } else if (!strcmp(p, "-V") || !strcmp(p, "--version")) { version(); + } else if (!strcmp(p, "--help")) { + usage(); + exit(0); } else if (!strcmp(p, "-pgpfp")) { pgp_fingerprints(); exit(1); } else if (!strcmp(p, "-o")) { if (argc <= 1) { @@ -658,11 +687,11 @@ } else { fprintf(stderr, "plink: unknown option \"%s\"\n", p); errors = 1; } } else if (*p) { - if (!cfg_launchable(&cfg) || !(got_host || loaded_session)) { + if (!conf_launchable(conf) || !(got_host || loaded_session)) { char *q = p; /* * If the hostname starts with "telnet:", set the * protocol to Telnet and process the string as a @@ -672,23 +701,22 @@ char c; q += 7; if (q[0] == '/' && q[1] == '/') q += 2; - cfg.protocol = PROT_TELNET; + conf_set_int(conf, CONF_protocol, PROT_TELNET); p = q; while (*p && *p != ':' && *p != '/') p++; c = *p; if (*p) *p++ = '\0'; if (c == ':') - cfg.port = atoi(p); + conf_set_int(conf, CONF_port, atoi(p)); else - cfg.port = -1; - strncpy(cfg.host, q, sizeof(cfg.host) - 1); - cfg.host[sizeof(cfg.host) - 1] = '\0'; + conf_set_int(conf, CONF_port, -1); + conf_set_str(conf, CONF_host, q); got_host = TRUE; } else { char *r, *user, *host; /* * Before we process the [user@]host string, we @@ -699,11 +727,13 @@ if (r) { const Backend *b; *r = '\0'; b = backend_from_name(p); if (b) { - default_protocol = cfg.protocol = b->protocol; + default_protocol = b->protocol; + conf_set_int(conf, CONF_protocol, + default_protocol); portnumber = b->default_port; } p = r + 1; } @@ -726,30 +756,28 @@ /* * Now attempt to load a saved session with the * same name as the hostname. */ { - Config cfg2; - do_defaults(host, &cfg2); - if (loaded_session || !cfg_launchable(&cfg2)) { + Conf *conf2 = conf_new(); + do_defaults(host, conf2); + if (loaded_session || !conf_launchable(conf2)) { /* No settings for this host; use defaults */ /* (or session was already loaded with -load) */ - strncpy(cfg.host, host, sizeof(cfg.host) - 1); - cfg.host[sizeof(cfg.host) - 1] = '\0'; - cfg.port = default_port; + conf_set_str(conf, CONF_host, host); + conf_set_int(conf, CONF_port, default_port); got_host = TRUE; } else { - cfg = cfg2; + conf_copy_into(conf, conf2); loaded_session = TRUE; } + conf_free(conf2); } if (user) { /* Patch in specified username. */ - strncpy(cfg.username, user, - sizeof(cfg.username) - 1); - cfg.username[sizeof(cfg.username) - 1] = '\0'; + conf_set_str(conf, CONF_username, user); } } } else { char *command; @@ -772,86 +800,94 @@ command[cmdlen++]=' '; /* always add trailing space */ if (--argc) p = *++argv; } if (cmdlen) command[--cmdlen]='\0'; /* change trailing blank to NUL */ - cfg.remote_cmd_ptr = command; - cfg.remote_cmd_ptr2 = NULL; - cfg.nopty = TRUE; /* command => no terminal */ + conf_set_str(conf, CONF_remote_cmd, command); + conf_set_str(conf, CONF_remote_cmd2, ""); + conf_set_int(conf, CONF_nopty, TRUE); /* command => no tty */ break; /* done with cmdline */ } } } if (errors) return 1; - if (!cfg_launchable(&cfg) || !(got_host || loaded_session)) { + if (!conf_launchable(conf) || !(got_host || loaded_session)) { usage(); } /* - * Trim leading whitespace off the hostname if it's there. + * Muck about with the hostname in various ways. */ { - int space = strspn(cfg.host, " \t"); - memmove(cfg.host, cfg.host+space, 1+strlen(cfg.host)-space); - } - - /* See if host is of the form user@host */ - if (cfg.host[0] != '\0') { - char *atsign = strrchr(cfg.host, '@'); - /* Make sure we're not overflowing the user field */ - if (atsign) { - if (atsign - cfg.host < sizeof cfg.username) { - strncpy(cfg.username, cfg.host, atsign - cfg.host); - cfg.username[atsign - cfg.host] = '\0'; - } - memmove(cfg.host, atsign + 1, 1 + strlen(atsign + 1)); - } + char *hostbuf = dupstr(conf_get_str(conf, CONF_host)); + char *host = hostbuf; + char *p, *q; + + /* + * Trim leading whitespace. + */ + host += strspn(host, " \t"); + + /* + * See if host is of the form user@host, and separate out + * the username if so. + */ + if (host[0] != '\0') { + char *atsign = strrchr(host, '@'); + if (atsign) { + *atsign = '\0'; + conf_set_str(conf, CONF_username, host); + host = atsign + 1; + } + } + + /* + * Trim off a colon suffix if it's there. + */ + host[strcspn(host, ":")] = '\0'; + + /* + * Remove any remaining whitespace. + */ + p = hostbuf; + q = host; + while (*q) { + if (*q != ' ' && *q != '\t') + *p++ = *q; + q++; + } + *p = '\0'; + + conf_set_str(conf, CONF_host, hostbuf); + sfree(hostbuf); } /* * Perform command-line overrides on session configuration. */ - cmdline_run_saved(&cfg); + cmdline_run_saved(conf); /* * Apply subsystem status. */ if (use_subsystem) - cfg.ssh_subsys = TRUE; - - /* - * Trim a colon suffix off the hostname if it's there. - */ - cfg.host[strcspn(cfg.host, ":")] = '\0'; - - /* - * Remove any remaining whitespace from the hostname. - */ - { - int p1 = 0, p2 = 0; - while (cfg.host[p2] != '\0') { - if (cfg.host[p2] != ' ' && cfg.host[p2] != '\t') { - cfg.host[p1] = cfg.host[p2]; - p1++; - } - p2++; - } - cfg.host[p1] = '\0'; - } - - if (!cfg.remote_cmd_ptr && !*cfg.remote_cmd && !*cfg.ssh_nc_host) + conf_set_int(conf, CONF_ssh_subsys, TRUE); + + if (!*conf_get_str(conf, CONF_remote_cmd) && + !*conf_get_str(conf, CONF_remote_cmd2) && + !*conf_get_str(conf, CONF_ssh_nc_host)) flags |= FLAG_INTERACTIVE; /* * Select protocol. This is farmed out into a table in a * separate file to enable an ssh-free variant. */ - back = backend_from_proto(cfg.protocol); + back = backend_from_proto(conf_get_int(conf, CONF_protocol)); if (back == NULL) { fprintf(stderr, "Internal fault: Unsupported protocol found\n"); return 1; } @@ -858,51 +894,72 @@ /* * Select port. */ if (portnumber != -1) - cfg.port = portnumber; + conf_set_int(conf, CONF_port, portnumber); + + /* + * Block SIGPIPE, so that we'll get EPIPE individually on + * particular network connections that go wrong. + */ + putty_signal(SIGPIPE, SIG_IGN); /* * Set up the pipe we'll use to tell us about SIGWINCH. */ if (pipe(signalpipe) < 0) { perror("pipe"); exit(1); } putty_signal(SIGWINCH, sigwinch); + + /* + * Now that we've got the SIGWINCH handler installed, try to find + * out the initial terminal size. + */ + if (ioctl(STDIN_FILENO, TIOCGWINSZ, &size) >= 0) { + conf_set_int(conf, CONF_width, size.ws_col); + conf_set_int(conf, CONF_height, size.ws_row); + } sk_init(); uxsel_init(); /* * Unix Plink doesn't provide any way to add forwardings after the * connection is set up, so if there are none now, we can safely set * the "simple" flag. */ - if (cfg.protocol == PROT_SSH && !cfg.x11_forward && !cfg.agentfwd && - cfg.portfwd[0] == '\0' && cfg.portfwd[1] == '\0') - cfg.ssh_simple = TRUE; + if (conf_get_int(conf, CONF_protocol) == PROT_SSH && + !conf_get_int(conf, CONF_x11_forward) && + !conf_get_int(conf, CONF_agentfwd) && + !conf_get_str_nthstrkey(conf, CONF_portfwd, 0)) + conf_set_int(conf, CONF_ssh_simple, TRUE); + /* * Start up the connection. */ - logctx = log_init(NULL, &cfg); + logctx = log_init(NULL, conf); console_provide_logctx(logctx); { const char *error; char *realhost; /* nodelay is only useful if stdin is a terminal device */ - int nodelay = cfg.tcp_nodelay && isatty(0); + int nodelay = conf_get_int(conf, CONF_tcp_nodelay) && isatty(0); - error = back->init(NULL, &backhandle, &cfg, cfg.host, cfg.port, - &realhost, nodelay, cfg.tcp_keepalives); + error = back->init(NULL, &backhandle, conf, + conf_get_str(conf, CONF_host), + conf_get_int(conf, CONF_port), + &realhost, nodelay, + conf_get_int(conf, CONF_tcp_keepalives)); if (error) { fprintf(stderr, "Unable to open connection:\n%s\n", error); return 1; } back->provide_logctx(backhandle, logctx); - ldisc_create(&cfg, NULL, back, backhandle, NULL); + ldisc_create(conf, NULL, back, backhandle, NULL); sfree(realhost); } connopen = 1; /* @@ -919,10 +976,11 @@ while (1) { fd_set rset, wset, xset; int maxfd; int rwx; int ret; + unsigned long next; FD_ZERO(&rset); FD_ZERO(&wset); FD_ZERO(&xset); maxfd = 0; @@ -972,48 +1030,38 @@ FD_SET_MAX(fd, maxfd, wset); if (rwx & 4) FD_SET_MAX(fd, maxfd, xset); } - do { - long next, ticks; - struct timeval tv, *ptv; + if (toplevel_callback_pending()) { + struct timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 0; + ret = select(maxfd, &rset, &wset, &xset, &tv); + } else if (run_timers(now, &next)) { + do { + unsigned long then; + long ticks; + struct timeval tv; - if (run_timers(now, &next)) { - ticks = next - GETTICKCOUNT(); - if (ticks < 0) ticks = 0; /* just in case */ + then = now; + now = GETTICKCOUNT(); + if (now - then > next - then) + ticks = 0; + else + ticks = next - now; tv.tv_sec = ticks / 1000; tv.tv_usec = ticks % 1000 * 1000; - ptv = &tv; - } else { - ptv = NULL; - } - ret = select(maxfd, &rset, &wset, &xset, ptv); - if (ret == 0) - now = next; - else { - long newnow = GETTICKCOUNT(); - /* - * Check to see whether the system clock has - * changed massively during the select. - */ - if (newnow - now < 0 || newnow - now > next - now) { - /* - * If so, look at the elapsed time in the - * select and use it to compute a new - * tickcount_offset. - */ - long othernow = now + tv.tv_sec * 1000 + tv.tv_usec / 1000; - /* So we'd like GETTICKCOUNT to have returned othernow, - * but instead it return newnow. Hence ... */ - tickcount_offset += othernow - newnow; - now = othernow; - } else { - now = newnow; - } - } - } while (ret < 0 && errno == EINTR); + ret = select(maxfd, &rset, &wset, &xset, &tv); + if (ret == 0) + now = next; + else + now = GETTICKCOUNT(); + } while (ret < 0 && errno == EINTR); + } else { + ret = select(maxfd, &rset, &wset, &xset, NULL); + } if (ret < 0) { perror("select"); exit(1); } @@ -1037,11 +1085,11 @@ char c[1]; struct winsize size; if (read(signalpipe[0], c, 1) <= 0) /* ignore error */; /* ignore its value; it'll be `x' */ - if (ioctl(0, TIOCGWINSZ, (void *)&size) >= 0) + if (ioctl(STDIN_FILENO, TIOCGWINSZ, (void *)&size) >= 0) back->size(backhandle, size.ws_col, size.ws_row); } if (FD_ISSET(STDIN_FILENO, &rset)) { char buf[4096]; @@ -1069,10 +1117,12 @@ } if (FD_ISSET(STDERR_FILENO, &wset)) { back->unthrottle(backhandle, try_output(TRUE)); } + + run_toplevel_callbacks(); if ((!connopen || !back->connected(backhandle)) && bufchain_size(&stdout_data) == 0 && bufchain_size(&stderr_data) == 0) break; /* we closed the connection */ Index: unix/uxproxy.c ================================================================== --- unix/uxproxy.c +++ unix/uxproxy.c @@ -27,12 +27,11 @@ Plug plug; bufchain pending_output_data; bufchain pending_input_data; - - void *privptr; + enum { EOF_NO, EOF_PENDING, EOF_SENT } outgoingeof; }; static int localproxy_select_result(int fd, int event); /* @@ -92,17 +91,19 @@ } static void sk_localproxy_close (Socket s) { Local_Proxy_Socket ps = (Local_Proxy_Socket) s; + + if (ps->to_cmd >= 0) { + del234(localproxy_by_tofd, ps); + uxsel_del(ps->to_cmd); + close(ps->to_cmd); + } del234(localproxy_by_fromfd, ps); - del234(localproxy_by_tofd, ps); - - uxsel_del(ps->to_cmd); uxsel_del(ps->from_cmd); - close(ps->to_cmd); close(ps->from_cmd); sfree(ps); } @@ -126,10 +127,18 @@ } else { bufchain_consume(&ps->pending_output_data, ret); sent += ret; } } + + if (ps->outgoingeof == EOF_PENDING) { + del234(localproxy_by_tofd, ps); + close(ps->to_cmd); + uxsel_del(ps->to_cmd); + ps->to_cmd = -1; + ps->outgoingeof = EOF_SENT; + } if (bufchain_size(&ps->pending_output_data) == 0) uxsel_del(ps->to_cmd); else uxsel_set(ps->to_cmd, 2, localproxy_select_result); @@ -138,10 +147,12 @@ } static int sk_localproxy_write (Socket s, const char *data, int len) { Local_Proxy_Socket ps = (Local_Proxy_Socket) s; + + assert(ps->outgoingeof == EOF_NO); bufchain_add(&ps->pending_output_data, data, len); localproxy_try_send(ps); @@ -154,29 +165,27 @@ * oob data is treated as inband; nasty, but nothing really * better we can do */ return sk_localproxy_write(s, data, len); } + +static void sk_localproxy_write_eof (Socket s) +{ + Local_Proxy_Socket ps = (Local_Proxy_Socket) s; + + assert(ps->outgoingeof == EOF_NO); + ps->outgoingeof = EOF_PENDING; + + localproxy_try_send(ps); +} static void sk_localproxy_flush (Socket s) { /* Local_Proxy_Socket ps = (Local_Proxy_Socket) s; */ /* do nothing */ } -static void sk_localproxy_set_private_ptr (Socket s, void *ptr) -{ - Local_Proxy_Socket ps = (Local_Proxy_Socket) s; - ps->privptr = ptr; -} - -static void * sk_localproxy_get_private_ptr (Socket s) -{ - Local_Proxy_Socket ps = (Local_Proxy_Socket) s; - return ps->privptr; -} - static void sk_localproxy_set_frozen (Socket s, int is_frozen) { Local_Proxy_Socket ps = (Local_Proxy_Socket) s; if (is_frozen) @@ -222,38 +231,38 @@ } Socket platform_new_connection(SockAddr addr, char *hostname, int port, int privport, int oobinline, int nodelay, int keepalive, - Plug plug, const Config *cfg) + Plug plug, Conf *conf) { char *cmd; static const struct socket_function_table socket_fn_table = { sk_localproxy_plug, sk_localproxy_close, sk_localproxy_write, sk_localproxy_write_oob, + sk_localproxy_write_eof, sk_localproxy_flush, - sk_localproxy_set_private_ptr, - sk_localproxy_get_private_ptr, sk_localproxy_set_frozen, sk_localproxy_socket_error }; Local_Proxy_Socket ret; int to_cmd_pipe[2], from_cmd_pipe[2], pid; - if (cfg->proxy_type != PROXY_CMD) + if (conf_get_int(conf, CONF_proxy_type) != PROXY_CMD) return NULL; - cmd = format_telnet_command(addr, port, cfg); + cmd = format_telnet_command(addr, port, conf); ret = snew(struct Socket_localproxy_tag); ret->fn = &socket_fn_table; ret->plug = plug; ret->error = NULL; + ret->outgoingeof = EOF_NO; bufchain_init(&ret->pending_input_data); bufchain_init(&ret->pending_output_data); /* @@ -261,29 +270,31 @@ * command process. */ if (pipe(to_cmd_pipe) < 0 || pipe(from_cmd_pipe) < 0) { ret->error = dupprintf("pipe: %s", strerror(errno)); + sfree(cmd); return (Socket)ret; } cloexec(to_cmd_pipe[1]); cloexec(from_cmd_pipe[0]); pid = fork(); if (pid < 0) { ret->error = dupprintf("fork: %s", strerror(errno)); + sfree(cmd); return (Socket)ret; } else if (pid == 0) { close(0); close(1); dup2(to_cmd_pipe[0], 0); dup2(from_cmd_pipe[1], 1); close(to_cmd_pipe[0]); close(from_cmd_pipe[1]); - fcntl(0, F_SETFD, 0); - fcntl(1, F_SETFD, 0); + noncloexec(0); + noncloexec(1); execl("/bin/sh", "sh", "-c", cmd, (void *)NULL); _exit(255); } sfree(cmd); Index: unix/uxpterm.c ================================================================== --- unix/uxpterm.c +++ unix/uxpterm.c @@ -10,32 +10,32 @@ const char *const appname = "pterm"; const int use_event_log = 0; /* pterm doesn't need it */ const int new_session = 0, saved_sessions = 0; /* or these */ const int use_pty_argv = TRUE; -Backend *select_backend(Config *cfg) +Backend *select_backend(Conf *conf) { return &pty_backend; } -int cfgbox(Config *cfg) +int cfgbox(Conf *conf) { /* * This is a no-op in pterm, except that we'll ensure the * protocol is set to -1 to inhibit the useless Connection * panel in the config box. */ - cfg->protocol = -1; + conf_set_int(conf, CONF_protocol, -1); return 1; } void cleanup_exit(int code) { exit(code); } -int process_nonoption_arg(char *arg, Config *cfg, int *allow_launch) +int process_nonoption_arg(char *arg, Conf *conf, int *allow_launch) { return 0; /* pterm doesn't have any. */ } char *make_default_wintitle(char *hostname) @@ -45,13 +45,16 @@ int main(int argc, char **argv) { extern int pt_main(int argc, char **argv); extern void pty_pre_init(void); /* declared in pty.c */ + int ret; cmdline_tooltype = TOOLTYPE_NONNETWORK; default_protocol = -1; pty_pre_init(); - return pt_main(argc, argv); + ret = pt_main(argc, argv); + cleanup_exit(ret); + return ret; /* not reached, but placates optimisers */ } Index: unix/uxpty.c ================================================================== --- unix/uxpty.c +++ unix/uxpty.c @@ -74,11 +74,11 @@ * process-global rather than session-specific. */ static int pty_signal_pipe[2] = { -1, -1 }; /* obviously bogus initial val */ struct pty_tag { - Config cfg; + Conf *conf; int master_fd, slave_fd; void *frontend; char name[FILENAME_MAX]; pid_t child_pid; int term_width, term_height; @@ -165,12 +165,12 @@ * here. */ static Pty single_pty = NULL; #ifndef OMIT_UTMP -static pid_t pty_utmp_helper_pid; -static int pty_utmp_helper_pipe; +static pid_t pty_utmp_helper_pid = -1; +static int pty_utmp_helper_pipe = -1; static int pty_stamped_utmp; static struct utmpx utmp_entry; #endif /* @@ -268,11 +268,10 @@ #ifndef OMIT_UTMP static void fatal_sig_handler(int signum) { putty_signal(signum, SIG_DFL); cleanup_utmp(); - setuid(getuid()); raise(signum); } #endif static int pty_open_slave(Pty pty) @@ -333,16 +332,32 @@ /* We need to chown/chmod the /dev/ttyXX device. */ gp = getgrnam("tty"); chown(pty->name, getuid(), gp ? gp->gr_gid : -1); chmod(pty->name, 0600); #else - pty->master_fd = open("/dev/ptmx", O_RDWR); + + const int flags = O_RDWR +#ifdef O_NOCTTY + | O_NOCTTY +#endif + ; + +#ifdef HAVE_POSIX_OPENPT + pty->master_fd = posix_openpt(flags); + + if (pty->master_fd < 0) { + perror("posix_openpt"); + exit(1); + } +#else + pty->master_fd = open("/dev/ptmx", flags); if (pty->master_fd < 0) { perror("/dev/ptmx: open"); exit(1); } +#endif if (grantpt(pty->master_fd) < 0) { perror("grantpt"); exit(1); } @@ -356,19 +371,11 @@ pty->name[FILENAME_MAX-1] = '\0'; strncpy(pty->name, ptsname(pty->master_fd), FILENAME_MAX-1); #endif - { - /* - * Set the pty master into non-blocking mode. - */ - int fl; - fl = fcntl(pty->master_fd, F_GETFL); - if (fl != -1 && !(fl & O_NONBLOCK)) - fcntl(pty->master_fd, F_SETFL, fl | O_NONBLOCK); - } + nonblock(pty->master_fd); if (!ptys_by_fd) ptys_by_fd = newtree234(pty_compare_by_fd); add234(ptys_by_fd, pty); } @@ -394,10 +401,11 @@ pid_t pid; int pipefd[2]; #endif pty = single_pty = snew(struct pty_tag); + pty->conf = NULL; bufchain_init(&pty->output_data); /* set the child signal handler straight away; it needs to be set * before we ever fork. */ putty_signal(SIGCHLD, sigchld_handler); @@ -406,122 +414,134 @@ pty_stamped_utmp = FALSE; #endif if (geteuid() != getuid() || getegid() != getgid()) { pty_open_master(pty); - } #ifndef OMIT_UTMP - /* - * Fork off the utmp helper. - */ - if (pipe(pipefd) < 0) { - perror("pterm: pipe"); - exit(1); - } - cloexec(pipefd[0]); - cloexec(pipefd[1]); - pid = fork(); - if (pid < 0) { - perror("pterm: fork"); - exit(1); - } else if (pid == 0) { - char display[128], buffer[128]; - int dlen, ret; - - close(pipefd[1]); - /* - * Now sit here until we receive a display name from the - * other end of the pipe, and then stamp utmp. Unstamp utmp - * again, and exit, when the pipe closes. - */ - - dlen = 0; - while (1) { - - ret = read(pipefd[0], buffer, lenof(buffer)); - if (ret <= 0) { - cleanup_utmp(); - _exit(0); - } else if (!pty_stamped_utmp) { - if (dlen < lenof(display)) - memcpy(display+dlen, buffer, - min(ret, lenof(display)-dlen)); - if (buffer[ret-1] == '\0') { - /* - * Now we have a display name. NUL-terminate - * it, and stamp utmp. - */ - display[lenof(display)-1] = '\0'; - /* - * Trap as many fatal signals as we can in the - * hope of having the best possible chance to - * clean up utmp before termination. We are - * unfortunately unprotected against SIGKILL, - * but that's life. - */ - putty_signal(SIGHUP, fatal_sig_handler); - putty_signal(SIGINT, fatal_sig_handler); - putty_signal(SIGQUIT, fatal_sig_handler); - putty_signal(SIGILL, fatal_sig_handler); - putty_signal(SIGABRT, fatal_sig_handler); - putty_signal(SIGFPE, fatal_sig_handler); - putty_signal(SIGPIPE, fatal_sig_handler); - putty_signal(SIGALRM, fatal_sig_handler); - putty_signal(SIGTERM, fatal_sig_handler); - putty_signal(SIGSEGV, fatal_sig_handler); - putty_signal(SIGUSR1, fatal_sig_handler); - putty_signal(SIGUSR2, fatal_sig_handler); -#ifdef SIGBUS - putty_signal(SIGBUS, fatal_sig_handler); -#endif -#ifdef SIGPOLL - putty_signal(SIGPOLL, fatal_sig_handler); + /* + * Fork off the utmp helper. + */ + if (pipe(pipefd) < 0) { + perror("pterm: pipe"); + exit(1); + } + cloexec(pipefd[0]); + cloexec(pipefd[1]); + pid = fork(); + if (pid < 0) { + perror("pterm: fork"); + exit(1); + } else if (pid == 0) { + char display[128], buffer[128]; + int dlen, ret; + + close(pipefd[1]); + /* + * Now sit here until we receive a display name from the + * other end of the pipe, and then stamp utmp. Unstamp utmp + * again, and exit, when the pipe closes. + */ + + dlen = 0; + while (1) { + + ret = read(pipefd[0], buffer, lenof(buffer)); + if (ret <= 0) { + cleanup_utmp(); + _exit(0); + } else if (!pty_stamped_utmp) { + if (dlen < lenof(display)) + memcpy(display+dlen, buffer, + min(ret, lenof(display)-dlen)); + if (buffer[ret-1] == '\0') { + /* + * Now we have a display name. NUL-terminate + * it, and stamp utmp. + */ + display[lenof(display)-1] = '\0'; + /* + * Trap as many fatal signals as we can in the + * hope of having the best possible chance to + * clean up utmp before termination. We are + * unfortunately unprotected against SIGKILL, + * but that's life. + */ + putty_signal(SIGHUP, fatal_sig_handler); + putty_signal(SIGINT, fatal_sig_handler); + putty_signal(SIGQUIT, fatal_sig_handler); + putty_signal(SIGILL, fatal_sig_handler); + putty_signal(SIGABRT, fatal_sig_handler); + putty_signal(SIGFPE, fatal_sig_handler); + putty_signal(SIGPIPE, fatal_sig_handler); + putty_signal(SIGALRM, fatal_sig_handler); + putty_signal(SIGTERM, fatal_sig_handler); + putty_signal(SIGSEGV, fatal_sig_handler); + putty_signal(SIGUSR1, fatal_sig_handler); + putty_signal(SIGUSR2, fatal_sig_handler); +#ifdef SIGBUS + putty_signal(SIGBUS, fatal_sig_handler); +#endif +#ifdef SIGPOLL + putty_signal(SIGPOLL, fatal_sig_handler); #endif #ifdef SIGPROF - putty_signal(SIGPROF, fatal_sig_handler); + putty_signal(SIGPROF, fatal_sig_handler); #endif #ifdef SIGSYS - putty_signal(SIGSYS, fatal_sig_handler); + putty_signal(SIGSYS, fatal_sig_handler); #endif #ifdef SIGTRAP - putty_signal(SIGTRAP, fatal_sig_handler); + putty_signal(SIGTRAP, fatal_sig_handler); #endif #ifdef SIGVTALRM - putty_signal(SIGVTALRM, fatal_sig_handler); + putty_signal(SIGVTALRM, fatal_sig_handler); #endif #ifdef SIGXCPU - putty_signal(SIGXCPU, fatal_sig_handler); + putty_signal(SIGXCPU, fatal_sig_handler); #endif #ifdef SIGXFSZ - putty_signal(SIGXFSZ, fatal_sig_handler); + putty_signal(SIGXFSZ, fatal_sig_handler); #endif #ifdef SIGIO - putty_signal(SIGIO, fatal_sig_handler); -#endif - setup_utmp(pty->name, display); - } - } - } - } else { - close(pipefd[0]); - pty_utmp_helper_pid = pid; - pty_utmp_helper_pipe = pipefd[1]; - } -#endif + putty_signal(SIGIO, fatal_sig_handler); +#endif + setup_utmp(pty->name, display); + } + } + } + } else { + close(pipefd[0]); + pty_utmp_helper_pid = pid; + pty_utmp_helper_pipe = pipefd[1]; + } +#endif + } /* Drop privs. */ { #ifndef HAVE_NO_SETRESUID int gid = getgid(), uid = getuid(); int setresgid(gid_t, gid_t, gid_t); int setresuid(uid_t, uid_t, uid_t); - setresgid(gid, gid, gid); - setresuid(uid, uid, uid); + if (setresgid(gid, gid, gid) < 0) { + perror("setresgid"); + exit(1); + } + if (setresuid(uid, uid, uid) < 0) { + perror("setresuid"); + exit(1); + } #else - setgid(getgid()); - setuid(getuid()); + if (setgid(getgid()) < 0) { + perror("setgid"); + exit(1); + } + if (setuid(getuid()) < 0) { + perror("setuid"); + exit(1); + } #endif } } int pty_real_select_result(Pty pty, int event, int status) @@ -586,10 +606,12 @@ pty_try_write(pty); } } if (finished && !pty->finished) { + int close_on_exit; + uxsel_del(pty->master_fd); pty_close(pty); pty->master_fd = -1; pty->finished = TRUE; @@ -598,13 +620,15 @@ * This is a slight layering-violation sort of hack: only * if we're not closing on exit (COE is set to Never, or to * Only On Clean and it wasn't a clean exit) do we output a * `terminated' message. */ - if (pty->cfg.close_on_exit == FORCE_OFF || - (pty->cfg.close_on_exit == AUTO && pty->exit_code != 0)) { + close_on_exit = conf_get_int(pty->conf, CONF_close_on_exit); + if (close_on_exit == FORCE_OFF || + (close_on_exit == AUTO && pty->exit_code != 0)) { char message[512]; + message[0] = '\0'; if (WIFEXITED(pty->exit_code)) sprintf(message, "\r\n[pterm: process terminated with exit" " code %d]\r\n", WEXITSTATUS(pty->exit_code)); else if (WIFSIGNALED(pty->exit_code)) #ifdef HAVE_NO_STRSIGNAL @@ -679,11 +703,11 @@ * Returns an error message, or NULL on success. * * Also places the canonical host name into `realhost'. It must be * freed by the caller. */ -static const char *pty_init(void *frontend, void **backend_handle, Config *cfg, +static const char *pty_init(void *frontend, void **backend_handle, Conf *conf, char *host, int port, char **realhost, int nodelay, int keepalive) { int slavefd; pid_t pid, pgrp; @@ -692,10 +716,11 @@ #endif Pty pty; if (single_pty) { pty = single_pty; + assert(pty->conf == NULL); } else { pty = snew(struct pty_tag); pty->master_fd = pty->slave_fd = -1; #ifndef OMIT_UTMP pty_stamped_utmp = FALSE; @@ -703,13 +728,13 @@ } pty->frontend = frontend; *backend_handle = NULL; /* we can't sensibly use this, sadly */ - pty->cfg = *cfg; /* structure copy */ - pty->term_width = cfg->width; - pty->term_height = cfg->height; + pty->conf = conf_copy(conf); + pty->term_width = conf_get_int(conf, CONF_width); + pty->term_height = conf_get_int(conf, CONF_height); if (pty->master_fd < 0) pty_open_master(pty); /* @@ -717,34 +742,37 @@ * specified by bksp_is_delete. */ { struct termios attrs; tcgetattr(pty->master_fd, &attrs); - attrs.c_cc[VERASE] = cfg->bksp_is_delete ? '\177' : '\010'; + attrs.c_cc[VERASE] = conf_get_int(conf, CONF_bksp_is_delete) + ? '\177' : '\010'; tcsetattr(pty->master_fd, TCSANOW, &attrs); } #ifndef OMIT_UTMP /* * Stamp utmp (that is, tell the utmp helper process to do so), * or not. */ - if (!cfg->stamp_utmp) { - close(pty_utmp_helper_pipe); /* just let the child process die */ - pty_utmp_helper_pipe = -1; - } else { - char *location = get_x_display(pty->frontend); - int len = strlen(location)+1, pos = 0; /* +1 to include NUL */ - while (pos < len) { - int ret = write(pty_utmp_helper_pipe, location+pos, len - pos); - if (ret < 0) { - perror("pterm: writing to utmp helper process"); - close(pty_utmp_helper_pipe); /* arrgh, just give up */ - pty_utmp_helper_pipe = -1; - break; - } - pos += ret; + if (pty_utmp_helper_pipe >= 0) { /* if it's < 0, we can't anyway */ + if (!conf_get_int(conf, CONF_stamp_utmp)) { + close(pty_utmp_helper_pipe); /* just let the child process die */ + pty_utmp_helper_pipe = -1; + } else { + char *location = get_x_display(pty->frontend); + int len = strlen(location)+1, pos = 0; /* +1 to include NUL */ + while (pos < len) { + int ret = write(pty_utmp_helper_pipe, location+pos, len - pos); + if (ret < 0) { + perror("pterm: writing to utmp helper process"); + close(pty_utmp_helper_pipe); /* arrgh, just give up */ + pty_utmp_helper_pipe = -1; + break; + } + pos += ret; + } } } #endif #ifndef NOT_X_WINDOWS /* for Mac OS X native compilation */ @@ -770,11 +798,11 @@ perror("slave pty: open"); _exit(1); } close(pty->master_fd); - fcntl(slavefd, F_SETFD, 0); /* don't close on exec */ + noncloexec(slavefd); dup2(slavefd, 0); dup2(slavefd, 1); dup2(slavefd, 2); close(slavefd); setsid(); @@ -782,14 +810,19 @@ ioctl(0, TIOCSCTTY, 1); #endif pgrp = getpid(); tcsetpgrp(0, pgrp); setpgid(pgrp, pgrp); - close(open(pty->name, O_WRONLY, 0)); + { + int ptyfd = open(pty->name, O_WRONLY, 0); + if (ptyfd >= 0) + close(ptyfd); + } setpgid(pgrp, pgrp); { - char *term_env_var = dupprintf("TERM=%s", cfg->termtype); + char *term_env_var = dupprintf("TERM=%s", + conf_get_str(conf, CONF_termtype)); putenv(term_env_var); /* We mustn't free term_env_var, as putenv links it into the * environment in place. */ } @@ -801,22 +834,16 @@ * environment in place. */ } #endif { - 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); + char *key, *val; + + for (val = conf_get_str_strs(conf, CONF_environmt, NULL, &key); + val != NULL; + val = conf_get_str_strs(conf, CONF_environmt, key, &key)) { + char *varval = dupcat(key, "=", val, NULL); 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 @@ -834,16 +861,48 @@ */ putty_signal(SIGINT, SIG_DFL); putty_signal(SIGQUIT, SIG_DFL); putty_signal(SIGPIPE, SIG_DFL); block_signal(SIGCHLD, 0); - if (pty_argv) + if (pty_argv) { + /* + * Exec the exact argument list we were given. + */ execvp(pty_argv[0], pty_argv); - else { + /* + * If that fails, and if we had exactly one argument, pass + * that argument to $SHELL -c. + * + * This arranges that we can _either_ follow 'pterm -e' + * with a list of argv elements to be fed directly to + * exec, _or_ with a single argument containing a command + * to be parsed by a shell (but, in cases of doubt, the + * former is more reliable). + * + * A quick survey of other terminal emulators' -e options + * (as of Debian squeeze) suggests that: + * + * - xterm supports both modes, more or less like this + * - gnome-terminal will only accept a one-string shell command + * - Eterm, kterm and rxvt will only accept a list of + * argv elements (as did older versions of pterm). + * + * It therefore seems important to support both usage + * modes in order to be a drop-in replacement for either + * xterm or gnome-terminal, and hence for anyone's + * plausible uses of the Debian-style alias + * 'x-terminal-emulator'... + */ + if (pty_argv[1] == NULL) { + char *shell = getenv("SHELL"); + if (shell) + execl(shell, shell, "-c", pty_argv[0], (void *)NULL); + } + } else { char *shell = getenv("SHELL"); char *shellname; - if (cfg->login_shell) { + if (conf_get_int(conf, CONF_login_shell)) { char *p = strrchr(shell, '/'); shellname = snewn(2+strlen(shell), char); p = p ? p+1 : shell; sprintf(shellname, "-%s", p); } else @@ -877,24 +936,24 @@ } pty_uxsel_setup(pty); *backend_handle = pty; - *realhost = dupprintf("\0"); + *realhost = dupstr(""); return NULL; } -static void pty_reconfig(void *handle, Config *cfg) +static void pty_reconfig(void *handle, Conf *conf) { Pty pty = (Pty)handle; /* * We don't have much need to reconfigure this backend, but * unfortunately we do need to pick up the setting of Close On * Exit so we know whether to give a `terminated' message. */ - pty->cfg = *cfg; /* structure copy */ + conf_copy_into(pty->conf, conf); } /* * Stub routine (never called in pterm). */ @@ -904,11 +963,21 @@ /* Either of these may fail `not found'. That's fine with us. */ del234(ptys_by_pid, pty); del234(ptys_by_fd, pty); - sfree(pty); + conf_free(pty->conf); + pty->conf = NULL; + + if (pty == single_pty) { + /* + * Leave this structure around in case we need to Restart + * Session. + */ + } else { + sfree(pty); + } } static void pty_try_write(Pty pty) { void *data; Index: unix/uxputty.c ================================================================== --- unix/uxputty.c +++ unix/uxputty.c @@ -29,30 +29,30 @@ sk_cleanup(); random_save_seed(); exit(code); } -Backend *select_backend(Config *cfg) +Backend *select_backend(Conf *conf) { - Backend *back = backend_from_proto(cfg->protocol); + Backend *back = backend_from_proto(conf_get_int(conf, CONF_protocol)); assert(back != NULL); return back; } -int cfgbox(Config *cfg) +int cfgbox(Conf *conf) { char *title = dupcat(appname, " Configuration", NULL); - int ret = do_config_box(title, cfg, 0, 0); + int ret = do_config_box(title, conf, 0, 0); sfree(title); return ret; } static int got_host = 0; const int use_event_log = 1, new_session = 1, saved_sessions = 1; -int process_nonoption_arg(char *arg, Config *cfg, int *allow_launch) +int process_nonoption_arg(char *arg, Conf *conf, int *allow_launch) { char *p, *q = arg; if (got_host) { /* @@ -59,11 +59,11 @@ * If we already have a host name, treat this argument as a * port number. NB we have to treat this as a saved -P * argument, so that it will be deferred until it's a good * moment to run it. */ - int ret = cmdline_process_param("-P", arg, 1, cfg); + int ret = cmdline_process_param("-P", arg, 1, conf); assert(ret == 2); } else if (!strncmp(q, "telnet:", 7)) { /* * If the hostname starts with "telnet:", * set the protocol to Telnet and process @@ -72,23 +72,22 @@ char c; q += 7; if (q[0] == '/' && q[1] == '/') q += 2; - cfg->protocol = PROT_TELNET; + conf_set_int(conf, CONF_protocol, PROT_TELNET); p = q; while (*p && *p != ':' && *p != '/') p++; c = *p; if (*p) *p++ = '\0'; if (c == ':') - cfg->port = atoi(p); + conf_set_int(conf, CONF_port, atoi(p)); else - cfg->port = -1; - strncpy(cfg->host, q, sizeof(cfg->host) - 1); - cfg->host[sizeof(cfg->host) - 1] = '\0'; + conf_set_int(conf, CONF_port, -1); + conf_set_str(conf, CONF_host, q); got_host = 1; } else { /* * Otherwise, treat this argument as a host name. */ @@ -95,12 +94,11 @@ p = arg; while (*p && !isspace((unsigned char)*p)) p++; if (*p) *p++ = '\0'; - strncpy(cfg->host, q, sizeof(cfg->host) - 1); - cfg->host[sizeof(cfg->host) - 1] = '\0'; + conf_set_str(conf, CONF_host, q); got_host = 1; } if (got_host) *allow_launch = TRUE; return 1; @@ -122,13 +120,18 @@ /* fall back to traditional method */ display = getenv("DISPLAY"); return dupstr(display); } +const int share_can_be_downstream = TRUE; +const int share_can_be_upstream = TRUE; + int main(int argc, char **argv) { extern int pt_main(int argc, char **argv); + int ret; + sk_init(); flags = FLAG_VERBOSE | FLAG_INTERACTIVE; default_protocol = be_default_protocol; /* Find the appropriate default port. */ { @@ -135,7 +138,9 @@ Backend *b = backend_from_proto(default_protocol); default_port = 0; /* illegal */ if (b) default_port = b->default_port; } - return pt_main(argc, argv); + ret = pt_main(argc, argv); + cleanup_exit(ret); + return ret; /* not reached, but placates optimisers */ } Index: unix/uxser.c ================================================================== --- unix/uxser.c +++ unix/uxser.c @@ -58,14 +58,14 @@ static int serial_select_result(int fd, int event); static void serial_uxsel_setup(Serial serial); static void serial_try_write(Serial serial); -static const char *serial_configure(Serial serial, Config *cfg) +static const char *serial_configure(Serial serial, Conf *conf) { struct termios options; - int bflag, bval; + int bflag, bval, speed, flow, parity; const char *str; char *msg; if (serial->fd < 0) return "Unable to reconfigure already-closed serial connection"; @@ -73,12 +73,13 @@ tcgetattr(serial->fd, &options); /* * Find the appropriate baud rate flag. */ + speed = conf_get_int(conf, CONF_serspeed); #define SETBAUD(x) (bflag = B ## x, bval = x) -#define CHECKBAUD(x) do { if (cfg->serspeed >= x) SETBAUD(x); } while (0) +#define CHECKBAUD(x) do { if (speed >= x) SETBAUD(x); } while (0) SETBAUD(50); #ifdef B75 CHECKBAUD(75); #endif #ifdef B110 @@ -181,22 +182,23 @@ msg = dupprintf("Configuring baud rate %d", bval); logevent(serial->frontend, msg); sfree(msg); options.c_cflag &= ~CSIZE; - switch (cfg->serdatabits) { + switch (conf_get_int(conf, CONF_serdatabits)) { case 5: options.c_cflag |= CS5; break; case 6: options.c_cflag |= CS6; break; case 7: options.c_cflag |= CS7; break; case 8: options.c_cflag |= CS8; break; default: return "Invalid number of data bits (need 5, 6, 7 or 8)"; } - msg = dupprintf("Configuring %d data bits", cfg->serdatabits); + msg = dupprintf("Configuring %d data bits", + conf_get_int(conf, CONF_serdatabits)); logevent(serial->frontend, msg); sfree(msg); - if (cfg->serstopbits >= 4) { + if (conf_get_int(conf, CONF_serstopbits) >= 4) { options.c_cflag |= CSTOPB; } else { options.c_cflag &= ~CSTOPB; } msg = dupprintf("Configuring %d stop bits", @@ -209,14 +211,15 @@ options.c_cflag &= ~CRTSCTS; #endif #ifdef CNEW_RTSCTS options.c_cflag &= ~CNEW_RTSCTS; #endif - if (cfg->serflow == SER_FLOW_XONXOFF) { + flow = conf_get_int(conf, CONF_serflow); + if (flow == SER_FLOW_XONXOFF) { options.c_iflag |= IXON | IXOFF; str = "XON/XOFF"; - } else if (cfg->serflow == SER_FLOW_RTSCTS) { + } else if (flow == SER_FLOW_RTSCTS) { #ifdef CRTSCTS options.c_cflag |= CRTSCTS; #endif #ifdef CNEW_RTSCTS options.c_cflag |= CNEW_RTSCTS; @@ -227,15 +230,16 @@ msg = dupprintf("Configuring %s flow control", str); logevent(serial->frontend, msg); sfree(msg); /* Parity */ - if (cfg->serparity == SER_PAR_ODD) { + parity = conf_get_int(conf, CONF_serparity); + if (parity == SER_PAR_ODD) { options.c_cflag |= PARENB; options.c_cflag |= PARODD; str = "odd"; - } else if (cfg->serparity == SER_PAR_EVEN) { + } else if (parity == SER_PAR_EVEN) { options.c_cflag |= PARENB; options.c_cflag &= ~PARODD; str = "even"; } else { options.c_cflag &= ~PARENB; @@ -282,41 +286,44 @@ * * Also places the canonical host name into `realhost'. It must be * freed by the caller. */ static const char *serial_init(void *frontend_handle, void **backend_handle, - Config *cfg, + Conf *conf, char *host, int port, char **realhost, int nodelay, int keepalive) { Serial serial; const char *err; + char *line; serial = snew(struct serial_backend_data); *backend_handle = serial; serial->frontend = frontend_handle; serial->finished = FALSE; serial->inbufsize = 0; bufchain_init(&serial->output_data); + line = conf_get_str(conf, CONF_serline); { - char *msg = dupprintf("Opening serial device %s", cfg->serline); + char *msg = dupprintf("Opening serial device %s", line); logevent(serial->frontend, msg); + sfree(msg); } - serial->fd = open(cfg->serline, O_RDWR | O_NOCTTY | O_NDELAY | O_NONBLOCK); + serial->fd = open(line, O_RDWR | O_NOCTTY | O_NDELAY | O_NONBLOCK); if (serial->fd < 0) return "Unable to open serial port"; cloexec(serial->fd); - err = serial_configure(serial, cfg); + err = serial_configure(serial, conf); if (err) return err; - *realhost = dupstr(cfg->serline); + *realhost = dupstr(line); if (!serial_by_fd) serial_by_fd = newtree234(serial_compare_by_fd); add234(serial_by_fd, serial); @@ -347,18 +354,18 @@ bufchain_clear(&serial->output_data); sfree(serial); } -static void serial_reconfig(void *handle, Config *cfg) +static void serial_reconfig(void *handle, Conf *conf) { Serial serial = (Serial) handle; /* * FIXME: what should we do if this returns an error? */ - serial_configure(serial, cfg); + serial_configure(serial, conf); } static int serial_select_result(int fd, int event) { Serial serial; Index: unix/uxsftp.c ================================================================== --- unix/uxsftp.c +++ unix/uxsftp.c @@ -32,11 +32,11 @@ char *x_get_default(const char *key) { return NULL; /* this is a stub */ } -void platform_get_x11_auth(struct X11Display *display, const Config *cfg) +void platform_get_x11_auth(struct X11Display *display, Conf *conf) { /* Do nothing, therefore no auth. */ } const int platform_uses_x11_unix_by_default = TRUE; @@ -51,25 +51,21 @@ int platform_default_i(const char *name, int def) { return def; } -FontSpec platform_default_fontspec(const char *name) +FontSpec *platform_default_fontspec(const char *name) { - FontSpec ret; - *ret.name = '\0'; - return ret; + return fontspec_new(""); } -Filename platform_default_filename(const char *name) +Filename *platform_default_filename(const char *name) { - Filename ret; if (!strcmp(name, "LogFileName")) - strcpy(ret.path, "putty.log"); + return filename_from_str("putty.log"); else - *ret.path = '\0'; - return ret; + return filename_from_str(""); } char *get_ttymode(void *frontend, const char *mode) { return NULL; } int get_userpass_input(prompts_t *p, unsigned char *in, int inlen) @@ -123,11 +119,12 @@ struct RFile { int fd; }; RFile *open_existing_file(char *name, uint64 *size, - unsigned long *mtime, unsigned long *atime) + unsigned long *mtime, unsigned long *atime, + long *perms) { int fd; RFile *ret; fd = open(name, O_RDONLY); @@ -135,11 +132,11 @@ return NULL; ret = snew(RFile); ret->fd = fd; - if (size || mtime || atime) { + if (size || mtime || atime || perms) { struct stat statbuf; if (fstat(fd, &statbuf) < 0) { fprintf(stderr, "%s: stat: %s\n", name, strerror(errno)); memset(&statbuf, 0, sizeof(statbuf)); } @@ -151,10 +148,13 @@ if (mtime) *mtime = statbuf.st_mtime; if (atime) *atime = statbuf.st_atime; + + if (perms) + *perms = statbuf.st_mode; } return ret; } @@ -172,16 +172,17 @@ struct WFile { int fd; char *name; }; -WFile *open_new_file(char *name) +WFile *open_new_file(char *name, long perms) { int fd; WFile *ret; - fd = open(name, O_CREAT | O_TRUNC | O_WRONLY, 0666); + fd = open(name, O_CREAT | O_TRUNC | O_WRONLY, + (mode_t)(perms ? perms : 0666)); if (fd < 0) return NULL; ret = snew(WFile); ret->fd = fd; @@ -440,11 +441,12 @@ static int ssh_sftp_do_select(int include_stdin, int no_fds_ok) { fd_set rset, wset, xset; int i, fdcount, fdsize, *fdlist; int fd, fdstate, rwx, ret, maxfd; - long now = GETTICKCOUNT(); + unsigned long now = GETTICKCOUNT(); + unsigned long next; fdlist = NULL; fdcount = fdsize = 0; do { @@ -485,49 +487,40 @@ } if (include_stdin) FD_SET_MAX(0, maxfd, rset); - do { - long next, ticks; - struct timeval tv, *ptv; - - if (run_timers(now, &next)) { - ticks = next - GETTICKCOUNT(); - if (ticks <= 0) - ticks = 1; /* just in case */ + if (toplevel_callback_pending()) { + struct timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 0; + ret = select(maxfd, &rset, &wset, &xset, &tv); + if (ret == 0) + run_toplevel_callbacks(); + } else if (run_timers(now, &next)) { + do { + unsigned long then; + long ticks; + struct timeval tv; + + then = now; + now = GETTICKCOUNT(); + if (now - then > next - then) + ticks = 0; + else + ticks = next - now; tv.tv_sec = ticks / 1000; tv.tv_usec = ticks % 1000 * 1000; - ptv = &tv; - } else { - ptv = NULL; - } - ret = select(maxfd, &rset, &wset, &xset, ptv); - if (ret == 0) - now = next; - else { - long newnow = GETTICKCOUNT(); - /* - * Check to see whether the system clock has - * changed massively during the select. - */ - if (newnow - now < 0 || newnow - now > next - now) { - /* - * If so, look at the elapsed time in the - * select and use it to compute a new - * tickcount_offset. - */ - long othernow = now + tv.tv_sec * 1000 + tv.tv_usec / 1000; - /* So we'd like GETTICKCOUNT to have returned othernow, - * but instead it return newnow. Hence ... */ - tickcount_offset += othernow - newnow; - now = othernow; - } else { - now = newnow; - } - } - } while (ret < 0 && errno != EINTR); + ret = select(maxfd, &rset, &wset, &xset, &tv); + if (ret == 0) + now = next; + else + now = GETTICKCOUNT(); + } while (ret < 0 && errno == EINTR); + } else { + ret = select(maxfd, &rset, &wset, &xset, NULL); + } } while (ret == 0); if (ret < 0) { perror("select"); exit(1); @@ -547,10 +540,12 @@ if (FD_ISSET(fd, &wset)) select_result(fd, 2); } sfree(fdlist); + + run_toplevel_callbacks(); return FD_ISSET(0, &rset) ? 1 : 0; } /* @@ -577,10 +572,11 @@ while (1) { ret = ssh_sftp_do_select(TRUE, no_fds_ok); if (ret < 0) { printf("connection died\n"); + sfree(buf); return NULL; /* woop woop */ } if (ret > 0) { if (buflen >= bufsize) { bufsize = buflen + 512; @@ -587,14 +583,16 @@ buf = sresize(buf, bufsize, char); } ret = read(0, buf+buflen, 1); if (ret < 0) { perror("read"); + sfree(buf); return NULL; } if (ret == 0) { /* eof on stdin; no error, but no answer either */ + sfree(buf); return NULL; } if (buf[buflen++] == '\n') { /* we have a full line */ @@ -601,10 +599,12 @@ return buf; } } } } + +void frontend_net_error_pending(void) {} /* * Main program: do platform-specific initialisation and then call * psftp_main(). */ ADDED unix/uxshare.c Index: unix/uxshare.c ================================================================== --- /dev/null +++ unix/uxshare.c @@ -0,0 +1,228 @@ +/* + * Unix implementation of SSH connection-sharing IPC setup. + */ + +#include +#include +#include + +#include +#include +#include +#include +#include + +#define DEFINE_PLUG_METHOD_MACROS +#include "tree234.h" +#include "putty.h" +#include "network.h" +#include "proxy.h" +#include "ssh.h" + +#define CONNSHARE_SOCKETDIR_PREFIX "/tmp/putty-connshare" + +/* + * Functions provided by uxnet.c to help connection sharing. + */ +SockAddr unix_sock_addr(const char *path); +Socket new_unix_listener(SockAddr listenaddr, Plug plug); + +static char *make_dirname(const char *name, char **parent_out) +{ + char *username, *dirname, *parent; + + username = get_username(); + parent = dupprintf("%s.%s", CONNSHARE_SOCKETDIR_PREFIX, username); + sfree(username); + assert(*parent == '/'); + + dirname = dupprintf("%s/%s", parent, name); + + if (parent_out) + *parent_out = parent; + else + sfree(parent); + + return dirname; +} + +static char *make_dir_and_check_ours(const char *dirname) +{ + struct stat st; + + /* + * Create the directory. We might have created it before, so + * EEXIST is an OK error; but anything else is doom. + */ + if (mkdir(dirname, 0700) < 0 && errno != EEXIST) + return dupprintf("%s: mkdir: %s", dirname, strerror(errno)); + + /* + * Now check that that directory is _owned by us_ and not writable + * by anybody else. This protects us against somebody else + * previously having created the directory in a way that's + * writable to us, and thus manipulating us into creating the + * actual socket in a directory they can see so that they can + * connect to it and use our authenticated SSH sessions. + */ + if (stat(dirname, &st) < 0) + return dupprintf("%s: stat: %s", dirname, strerror(errno)); + if (st.st_uid != getuid()) + return dupprintf("%s: directory owned by uid %d, not by us", + dirname, st.st_uid); + if ((st.st_mode & 077) != 0) + return dupprintf("%s: directory has overgenerous permissions %03o" + " (expected 700)", dirname, st.st_mode & 0777); + + return NULL; +} + +int platform_ssh_share(const char *pi_name, Conf *conf, + Plug downplug, Plug upplug, Socket *sock, + char **logtext, char **ds_err, char **us_err, + int can_upstream, int can_downstream) +{ + char *name, *parentdirname, *dirname, *lockname, *sockname, *err; + int lockfd; + Socket retsock; + + /* + * Transform the platform-independent version of the connection + * identifier into something valid for a Unix socket, by escaping + * slashes (and, while we're here, any control characters). + */ + { + const char *p; + char *q; + + name = snewn(1+3*strlen(pi_name), char); + + for (p = pi_name, q = name; *p; p++) { + if (*p == '/' || *p == '%' || + (unsigned char)*p < 0x20 || *p == 0x7f) { + q += sprintf(q, "%%%02x", (unsigned char)*p); + } else { + *q++ = *p; + } + } + *q = '\0'; + } + + /* + * First, make sure our subdirectory exists. We must create two + * levels of directory - the one for this particular connection, + * and the containing one for our username. + */ + dirname = make_dirname(name, &parentdirname); + if ((err = make_dir_and_check_ours(parentdirname)) != NULL) { + *logtext = err; + sfree(dirname); + sfree(parentdirname); + sfree(name); + return SHARE_NONE; + } + sfree(parentdirname); + if ((err = make_dir_and_check_ours(dirname)) != NULL) { + *logtext = err; + sfree(dirname); + sfree(name); + return SHARE_NONE; + } + + /* + * Acquire a lock on a file in that directory. + */ + lockname = dupcat(dirname, "/lock", (char *)NULL); + lockfd = open(lockname, O_CREAT | O_RDWR | O_TRUNC, 0600); + if (lockfd < 0) { + *logtext = dupprintf("%s: open: %s", lockname, strerror(errno)); + sfree(dirname); + sfree(lockname); + sfree(name); + return SHARE_NONE; + } + if (flock(lockfd, LOCK_EX) < 0) { + *logtext = dupprintf("%s: flock(LOCK_EX): %s", + lockname, strerror(errno)); + sfree(dirname); + sfree(lockname); + close(lockfd); + sfree(name); + return SHARE_NONE; + } + + sockname = dupprintf("%s/socket", dirname); + + *logtext = NULL; + + if (can_downstream) { + retsock = new_connection(unix_sock_addr(sockname), + "", 0, 0, 1, 0, 0, downplug, conf); + if (sk_socket_error(retsock) == NULL) { + sfree(*logtext); + *logtext = sockname; + *sock = retsock; + sfree(dirname); + sfree(lockname); + close(lockfd); + sfree(name); + return SHARE_DOWNSTREAM; + } + sfree(*ds_err); + *ds_err = dupprintf("%s: %s", sockname, sk_socket_error(retsock)); + sk_close(retsock); + } + + if (can_upstream) { + retsock = new_unix_listener(unix_sock_addr(sockname), upplug); + if (sk_socket_error(retsock) == NULL) { + sfree(*logtext); + *logtext = sockname; + *sock = retsock; + sfree(dirname); + sfree(lockname); + close(lockfd); + sfree(name); + return SHARE_UPSTREAM; + } + sfree(*us_err); + *us_err = dupprintf("%s: %s", sockname, sk_socket_error(retsock)); + sk_close(retsock); + } + + /* One of the above clauses ought to have happened. */ + assert(*logtext || *ds_err || *us_err); + + sfree(dirname); + sfree(lockname); + sfree(sockname); + close(lockfd); + sfree(name); + return SHARE_NONE; +} + +void platform_ssh_share_cleanup(const char *name) +{ + char *dirname, *filename; + + dirname = make_dirname(name, NULL); + + filename = dupcat(dirname, "/socket", (char *)NULL); + remove(filename); + sfree(filename); + + filename = dupcat(dirname, "/lock", (char *)NULL); + remove(filename); + sfree(filename); + + rmdir(dirname); + + /* + * We deliberately _don't_ clean up the parent directory + * /tmp/putty-connshare., because if we leave it around + * then it reduces the ability for other users to be a nuisance by + * putting their own directory in the way of it. + */ + + sfree(dirname); +} Index: unix/uxstore.c ================================================================== --- unix/uxstore.c +++ unix/uxstore.c @@ -164,29 +164,35 @@ *errmsg = NULL; /* * Start by making sure the .putty directory and its sessions - * subdir actually exist. Ignore error returns from mkdir since - * they're perfectly likely to be `already exists', and any - * other error will trip us up later on so there's no real need - * to catch it now. + * subdir actually exist. */ + filename = make_filename(INDEX_DIR, NULL); + if (mkdir(filename, 0700) < 0 && errno != EEXIST) { + *errmsg = dupprintf("Unable to save session: mkdir(\"%s\") " + "returned '%s'", filename, strerror(errno)); + sfree(filename); + return NULL; + } + sfree(filename); + filename = make_filename(INDEX_SESSIONDIR, NULL); - if (mkdir(filename, 0700) != 0) { - char *filename2 = make_filename(INDEX_DIR, NULL); - mkdir(filename2, 0700); - sfree(filename2); - mkdir(filename, 0700); + if (mkdir(filename, 0700) < 0 && errno != EEXIST) { + *errmsg = dupprintf("Unable to save session: mkdir(\"%s\") " + "returned '%s'", filename, strerror(errno)); + sfree(filename); + return NULL; } sfree(filename); filename = make_filename(INDEX_SESSION, sessionname); fp = fopen(filename, "w"); if (!fp) { - *errmsg = dupprintf("Unable to create %s: %s", - filename, strerror(errno)); + *errmsg = dupprintf("Unable to save session: open(\"%s\") " + "returned '%s'", filename, strerror(errno)); sfree(filename); return NULL; /* can't open */ } sfree(filename); return fp; @@ -297,12 +303,14 @@ while ( (line = fgetline(fp)) ) { char *value = strchr(line, '='); struct skeyval *kv; - if (!value) + if (!value) { + sfree(line); continue; + } *value++ = '\0'; value[strcspn(value, "\r\n")] = '\0'; /* trim trailing NL */ kv = snew(struct skeyval); kv->key = dupstr(line); @@ -315,11 +323,11 @@ fclose(fp); return ret; } -char *read_setting_s(void *handle, const char *key, char *buffer, int buflen) +char *read_setting_s(void *handle, const char *key) { tree234 *tree = (tree234 *)handle; const char *val; struct skeyval tmp, *kv; @@ -331,15 +339,12 @@ } else val = get_setting(key); if (!val) return NULL; - else { - strncpy(buffer, val, buflen); - buffer[buflen-1] = '\0'; - return buffer; - } + else + return dupstr(val); } int read_setting_i(void *handle, const char *key, int defvalue) { tree234 *tree = (tree234 *)handle; @@ -358,11 +363,11 @@ return defvalue; else return atoi(val); } -int read_setting_fontspec(void *handle, const char *name, FontSpec *result) +FontSpec *read_setting_fontspec(void *handle, const char *name) { /* * In GTK1-only PuTTY, we used to store font names simply as a * valid X font description string (logical or alias), under a * bare key such as "Font". @@ -373,46 +378,58 @@ * "server:" prefix, so we change the storage key from the * provided name string (e.g. "Font") to a suffixed one * ("FontName"). */ char *suffname = dupcat(name, "Name", NULL); - if (read_setting_s(handle, suffname, result->name, sizeof(result->name))) { + char *tmp; + + if ((tmp = read_setting_s(handle, suffname)) != NULL) { + FontSpec *fs = fontspec_new(tmp); sfree(suffname); - return TRUE; /* got new-style name */ + sfree(tmp); + return fs; /* got new-style name */ } sfree(suffname); /* Fall back to old-style name. */ - memcpy(result->name, "server:", 7); - if (!read_setting_s(handle, name, - result->name + 7, sizeof(result->name) - 7) || - !result->name[7]) { - result->name[0] = '\0'; - return FALSE; + tmp = read_setting_s(handle, name); + if (tmp && *tmp) { + char *tmp2 = dupcat("server:", tmp, NULL); + FontSpec *fs = fontspec_new(tmp2); + sfree(tmp2); + sfree(tmp); + return fs; } else { - return TRUE; + sfree(tmp); + return NULL; } } -int read_setting_filename(void *handle, const char *name, Filename *result) +Filename *read_setting_filename(void *handle, const char *name) { - return !!read_setting_s(handle, name, result->path, sizeof(result->path)); + char *tmp = read_setting_s(handle, name); + if (tmp) { + Filename *ret = filename_from_str(tmp); + sfree(tmp); + return ret; + } else + return NULL; } -void write_setting_fontspec(void *handle, const char *name, FontSpec result) +void write_setting_fontspec(void *handle, const char *name, FontSpec *fs) { /* * read_setting_fontspec had to handle two cases, but when * writing our settings back out we simply always generate the * new-style name. */ char *suffname = dupcat(name, "Name", NULL); - write_setting_s(handle, suffname, result.name); + write_setting_s(handle, suffname, fs->name); sfree(suffname); } -void write_setting_filename(void *handle, const char *name, Filename result) +void write_setting_filename(void *handle, const char *name, Filename *result) { - write_setting_s(handle, name, result.path); + write_setting_s(handle, name, result->path); } void close_settings_r(void *handle) { tree234 *tree = (tree234 *)handle; @@ -578,42 +595,53 @@ FILE *rfp, *wfp; char *newtext, *line; int headerlen; char *filename, *tmpfilename; - newtext = dupprintf("%s@%d:%s %s\n", keytype, port, hostname, key); - headerlen = 1 + strcspn(newtext, " "); /* count the space too */ - /* * Open both the old file and a new file. */ tmpfilename = make_filename(INDEX_HOSTKEYS_TMP, NULL); wfp = fopen(tmpfilename, "w"); - if (!wfp) { + if (!wfp && errno == ENOENT) { char *dir; dir = make_filename(INDEX_DIR, NULL); - mkdir(dir, 0700); + if (mkdir(dir, 0700) < 0) { + char *msg = dupprintf("Unable to store host key: mkdir(\"%s\") " + "returned '%s'", dir, strerror(errno)); + nonfatal(msg); + sfree(dir); + sfree(tmpfilename); + return; + } sfree(dir); wfp = fopen(tmpfilename, "w"); } if (!wfp) { - sfree(tmpfilename); - return; + char *msg = dupprintf("Unable to store host key: open(\"%s\") " + "returned '%s'", tmpfilename, strerror(errno)); + nonfatal(msg); + sfree(tmpfilename); + return; } filename = make_filename(INDEX_HOSTKEYS, NULL); rfp = fopen(filename, "r"); + + newtext = dupprintf("%s@%d:%s %s\n", keytype, port, hostname, key); + headerlen = 1 + strcspn(newtext, " "); /* count the space too */ /* * Copy all lines from the old file to the new one that _don't_ * involve the same host key identifier as the one we're adding. */ if (rfp) { while ( (line = fgetline(rfp)) ) { if (strncmp(line, newtext, headerlen)) fputs(line, wfp); + sfree(line); } fclose(rfp); } /* @@ -621,11 +649,16 @@ */ fputs(newtext, wfp); fclose(wfp); - rename(tmpfilename, filename); + if (rename(tmpfilename, filename) < 0) { + char *msg = dupprintf("Unable to store host key: rename(\"%s\",\"%s\")" + " returned '%s'", tmpfilename, filename, + strerror(errno)); + nonfatal(msg); + } sfree(tmpfilename); sfree(filename); sfree(newtext); } @@ -658,22 +691,52 @@ * something goes wrong half way through writing it, it would * be better to leave the old data there than to leave it empty. */ fd = open(fname, O_CREAT | O_WRONLY, 0600); if (fd < 0) { + if (errno != ENOENT) { + char *msg = dupprintf("Unable to write random seed: open(\"%s\") " + "returned '%s'", fname, strerror(errno)); + nonfatal(msg); + sfree(msg); + sfree(fname); + return; + } char *dir; dir = make_filename(INDEX_DIR, NULL); - mkdir(dir, 0700); + if (mkdir(dir, 0700) < 0) { + char *msg = dupprintf("Unable to write random seed: mkdir(\"%s\") " + "returned '%s'", dir, strerror(errno)); + nonfatal(msg); + sfree(msg); + sfree(fname); + sfree(dir); + return; + } sfree(dir); fd = open(fname, O_CREAT | O_WRONLY, 0600); + if (fd < 0) { + char *msg = dupprintf("Unable to write random seed: open(\"%s\") " + "returned '%s'", fname, strerror(errno)); + nonfatal(msg); + sfree(msg); + sfree(fname); + return; + } } while (len > 0) { int ret = write(fd, data, len); - if (ret <= 0) break; + if (ret < 0) { + char *msg = dupprintf("Unable to write random seed: write " + "returned '%s'", strerror(errno)); + nonfatal(msg); + sfree(msg); + break; + } len -= ret; data = (char *)data + len; } close(fd); Index: unix/uxucs.c ================================================================== --- unix/uxucs.c +++ unix/uxucs.c @@ -19,19 +19,18 @@ int is_dbcs_leadbyte(int codepage, char byte) { return 0; /* we don't do DBCS */ } -int mb_to_wc(int codepage, int flags, char *mbstr, int mblen, +int mb_to_wc(int codepage, int flags, const char *mbstr, int mblen, wchar_t *wcstr, int wclen) { if (codepage == DEFAULT_CODEPAGE) { int n = 0; mbstate_t state; memset(&state, 0, sizeof state); - setlocale(LC_CTYPE, ""); while (mblen > 0) { size_t i = mbrtowc(wcstr+n, mbstr, (size_t)mblen, &state); if (i == (size_t)-1 || i == (size_t)-2) break; @@ -38,12 +37,10 @@ n++; mbstr += i; mblen -= i; } - setlocale(LC_CTYPE, "C"); - return n; } else if (codepage == CS_NONE) { int n = 0; while (mblen > 0) { @@ -57,11 +54,11 @@ } else return charset_to_unicode(&mbstr, &mblen, wcstr, wclen, codepage, NULL, NULL, 0); } -int wc_to_mb(int codepage, int flags, wchar_t *wcstr, int wclen, +int wc_to_mb(int codepage, int flags, const wchar_t *wcstr, int wclen, char *mbstr, int mblen, char *defchr, int *defused, struct unicode_data *ucsdata) { /* FIXME: we should remove the defused param completely... */ if (defused) @@ -71,11 +68,10 @@ char output[MB_LEN_MAX]; mbstate_t state; int n = 0; memset(&state, 0, sizeof state); - setlocale(LC_CTYPE, ""); while (wclen > 0) { int i = wcrtomb(output, wcstr[0], &state); if (i == (size_t)-1 || i > n - mblen) break; @@ -83,12 +79,10 @@ n += i; wcstr++; wclen--; } - setlocale(LC_CTYPE, "C"); - return n; } else if (codepage == CS_NONE) { int n = 0; while (wclen > 0 && n < mblen) { if (*wcstr >= 0xD800 && *wcstr < 0xD900) @@ -137,11 +131,11 @@ } } /* * Failing that, line_codepage should be decoded from the - * specification in cfg. + * specification in conf. */ if (ucsdata->line_codepage == CS_NONE) ucsdata->line_codepage = decode_codepage(linecharset); /* @@ -160,11 +154,12 @@ /* * Set up unitab_line, by translating each individual character * in the line codepage into Unicode. */ for (i = 0; i < 256; i++) { - char c[1], *p; + char c[1]; + const char *p; wchar_t wc[1]; int len; c[0] = i; p = c; len = 1; @@ -214,11 +209,12 @@ /* * Set up unitab_scoacs. The SCO Alternate Character Set is * simply CP437. */ for (i = 0; i < 256; i++) { - char c[1], *p; + char c[1]; + const char *p; wchar_t wc[1]; int len; c[0] = i; p = c; len = 1; @@ -255,19 +251,21 @@ } const char *cp_enumerate(int index) { int charset; - if (index == 0) - return "Use font encoding"; - charset = charset_localenc_nth(index-1); - if (charset == CS_NONE) + charset = charset_localenc_nth(index); + if (charset == CS_NONE) { + /* "Use font encoding" comes after all the named charsets */ + if (charset_localenc_nth(index-1) != CS_NONE) + return "Use font encoding"; return NULL; + } return charset_to_localenc(charset); } int decode_codepage(char *cp_name) { - if (!*cp_name) - return CS_NONE; /* use font encoding */ + if (!cp_name || !*cp_name) + return CS_UTF8; return charset_from_localenc(cp_name); } Index: version.c ================================================================== --- version.c +++ version.c @@ -2,10 +2,26 @@ * PuTTY version numbering */ #define STR1(x) #x #define STR(x) STR1(x) + +#ifdef INCLUDE_EMPTY_H +/* + * Horrible hack to force version.o to be rebuilt unconditionally in + * the automake world: empty.h is an empty header file, created by the + * makefile and forcibly updated every time make is run. Including it + * here causes automake to track it as a dependency, which will cause + * version.o to be rebuilt too. + * + * The space between # and include causes mkfiles.pl's dependency + * scanner (for all other makefile types) to ignore this include, + * which is correct because only the automake makefile passes + * -DINCLUDE_EMPTY_H to enable it. + */ +# include "empty.h" +#endif #if defined SNAPSHOT #if defined SVN_REV #define SNAPSHOT_TEXT STR(SNAPSHOT) ":r" STR(SVN_REV) Index: wcwidth.c ================================================================== --- wcwidth.c +++ wcwidth.c @@ -48,11 +48,11 @@ * but also of each presentation form, something the author of these * routines has avoided to do so far. * * http://www.unicode.org/unicode/reports/tr11/ * - * Markus Kuhn -- 2003-05-20 (Unicode 4.0) + * Markus Kuhn -- 2007-05-26 (Unicode 5.0) * * Permission to use, copy, modify, and distribute this software * for any purpose and without fee is hereby granted. The author * disclaims all warranties with regard to this software. * @@ -62,16 +62,16 @@ #include #include "putty.h" /* for prototypes */ struct interval { - int first; - int last; + unsigned int first; + unsigned int last; }; /* auxiliary function for binary search in interval table */ -static int bisearch(wchar_t ucs, const struct interval *table, int max) { +static int bisearch(unsigned int ucs, const struct interval *table, int max) { int min = 0; int mid; if (ucs < table[0].first || ucs > table[max].last) return 0; @@ -119,56 +119,63 @@ * * This implementation assumes that wchar_t characters are encoded * in ISO 10646. */ -int mk_wcwidth(wchar_t ucs) +int mk_wcwidth(unsigned int ucs) { /* sorted list of non-overlapping intervals of non-spacing characters */ /* generated by "uniset +cat=Me +cat=Mn +cat=Cf -00AD +1160-11FF +200B c" */ static const struct interval combining[] = { - { 0x0300, 0x0357 }, { 0x035D, 0x036F }, { 0x0483, 0x0486 }, - { 0x0488, 0x0489 }, { 0x0591, 0x05A1 }, { 0x05A3, 0x05B9 }, - { 0x05BB, 0x05BD }, { 0x05BF, 0x05BF }, { 0x05C1, 0x05C2 }, - { 0x05C4, 0x05C4 }, { 0x0600, 0x0603 }, { 0x0610, 0x0615 }, - { 0x064B, 0x0658 }, { 0x0670, 0x0670 }, { 0x06D6, 0x06E4 }, - { 0x06E7, 0x06E8 }, { 0x06EA, 0x06ED }, { 0x070F, 0x070F }, - { 0x0711, 0x0711 }, { 0x0730, 0x074A }, { 0x07A6, 0x07B0 }, - { 0x0901, 0x0902 }, { 0x093C, 0x093C }, { 0x0941, 0x0948 }, - { 0x094D, 0x094D }, { 0x0951, 0x0954 }, { 0x0962, 0x0963 }, - { 0x0981, 0x0981 }, { 0x09BC, 0x09BC }, { 0x09C1, 0x09C4 }, - { 0x09CD, 0x09CD }, { 0x09E2, 0x09E3 }, { 0x0A01, 0x0A02 }, - { 0x0A3C, 0x0A3C }, { 0x0A41, 0x0A42 }, { 0x0A47, 0x0A48 }, - { 0x0A4B, 0x0A4D }, { 0x0A70, 0x0A71 }, { 0x0A81, 0x0A82 }, - { 0x0ABC, 0x0ABC }, { 0x0AC1, 0x0AC5 }, { 0x0AC7, 0x0AC8 }, - { 0x0ACD, 0x0ACD }, { 0x0AE2, 0x0AE3 }, { 0x0B01, 0x0B01 }, - { 0x0B3C, 0x0B3C }, { 0x0B3F, 0x0B3F }, { 0x0B41, 0x0B43 }, - { 0x0B4D, 0x0B4D }, { 0x0B56, 0x0B56 }, { 0x0B82, 0x0B82 }, - { 0x0BC0, 0x0BC0 }, { 0x0BCD, 0x0BCD }, { 0x0C3E, 0x0C40 }, - { 0x0C46, 0x0C48 }, { 0x0C4A, 0x0C4D }, { 0x0C55, 0x0C56 }, - { 0x0CBC, 0x0CBC }, { 0x0CBF, 0x0CBF }, { 0x0CC6, 0x0CC6 }, - { 0x0CCC, 0x0CCD }, { 0x0D41, 0x0D43 }, { 0x0D4D, 0x0D4D }, + { 0x0300, 0x036F }, { 0x0483, 0x0486 }, { 0x0488, 0x0489 }, + { 0x0591, 0x05BD }, { 0x05BF, 0x05BF }, { 0x05C1, 0x05C2 }, + { 0x05C4, 0x05C5 }, { 0x05C7, 0x05C7 }, { 0x0600, 0x0603 }, + { 0x0610, 0x0615 }, { 0x064B, 0x065E }, { 0x0670, 0x0670 }, + { 0x06D6, 0x06E4 }, { 0x06E7, 0x06E8 }, { 0x06EA, 0x06ED }, + { 0x070F, 0x070F }, { 0x0711, 0x0711 }, { 0x0730, 0x074A }, + { 0x07A6, 0x07B0 }, { 0x07EB, 0x07F3 }, { 0x0901, 0x0902 }, + { 0x093C, 0x093C }, { 0x0941, 0x0948 }, { 0x094D, 0x094D }, + { 0x0951, 0x0954 }, { 0x0962, 0x0963 }, { 0x0981, 0x0981 }, + { 0x09BC, 0x09BC }, { 0x09C1, 0x09C4 }, { 0x09CD, 0x09CD }, + { 0x09E2, 0x09E3 }, { 0x0A01, 0x0A02 }, { 0x0A3C, 0x0A3C }, + { 0x0A41, 0x0A42 }, { 0x0A47, 0x0A48 }, { 0x0A4B, 0x0A4D }, + { 0x0A70, 0x0A71 }, { 0x0A81, 0x0A82 }, { 0x0ABC, 0x0ABC }, + { 0x0AC1, 0x0AC5 }, { 0x0AC7, 0x0AC8 }, { 0x0ACD, 0x0ACD }, + { 0x0AE2, 0x0AE3 }, { 0x0B01, 0x0B01 }, { 0x0B3C, 0x0B3C }, + { 0x0B3F, 0x0B3F }, { 0x0B41, 0x0B43 }, { 0x0B4D, 0x0B4D }, + { 0x0B56, 0x0B56 }, { 0x0B82, 0x0B82 }, { 0x0BC0, 0x0BC0 }, + { 0x0BCD, 0x0BCD }, { 0x0C3E, 0x0C40 }, { 0x0C46, 0x0C48 }, + { 0x0C4A, 0x0C4D }, { 0x0C55, 0x0C56 }, { 0x0CBC, 0x0CBC }, + { 0x0CBF, 0x0CBF }, { 0x0CC6, 0x0CC6 }, { 0x0CCC, 0x0CCD }, + { 0x0CE2, 0x0CE3 }, { 0x0D41, 0x0D43 }, { 0x0D4D, 0x0D4D }, { 0x0DCA, 0x0DCA }, { 0x0DD2, 0x0DD4 }, { 0x0DD6, 0x0DD6 }, { 0x0E31, 0x0E31 }, { 0x0E34, 0x0E3A }, { 0x0E47, 0x0E4E }, { 0x0EB1, 0x0EB1 }, { 0x0EB4, 0x0EB9 }, { 0x0EBB, 0x0EBC }, { 0x0EC8, 0x0ECD }, { 0x0F18, 0x0F19 }, { 0x0F35, 0x0F35 }, { 0x0F37, 0x0F37 }, { 0x0F39, 0x0F39 }, { 0x0F71, 0x0F7E }, { 0x0F80, 0x0F84 }, { 0x0F86, 0x0F87 }, { 0x0F90, 0x0F97 }, { 0x0F99, 0x0FBC }, { 0x0FC6, 0x0FC6 }, { 0x102D, 0x1030 }, { 0x1032, 0x1032 }, { 0x1036, 0x1037 }, { 0x1039, 0x1039 }, - { 0x1058, 0x1059 }, { 0x1160, 0x11FF }, { 0x1712, 0x1714 }, - { 0x1732, 0x1734 }, { 0x1752, 0x1753 }, { 0x1772, 0x1773 }, - { 0x17B4, 0x17B5 }, { 0x17B7, 0x17BD }, { 0x17C6, 0x17C6 }, - { 0x17C9, 0x17D3 }, { 0x17DD, 0x17DD }, { 0x180B, 0x180D }, - { 0x18A9, 0x18A9 }, { 0x1920, 0x1922 }, { 0x1927, 0x1928 }, - { 0x1932, 0x1932 }, { 0x1939, 0x193B }, { 0x200B, 0x200F }, - { 0x202A, 0x202E }, { 0x2060, 0x2063 }, { 0x206A, 0x206F }, - { 0x20D0, 0x20EA }, { 0x302A, 0x302F }, { 0x3099, 0x309A }, - { 0xFB1E, 0xFB1E }, { 0xFE00, 0xFE0F }, { 0xFE20, 0xFE23 }, - { 0xFEFF, 0xFEFF }, { 0xFFF9, 0xFFFB }, { 0x1D167, 0x1D169 }, + { 0x1058, 0x1059 }, { 0x1160, 0x11FF }, { 0x135F, 0x135F }, + { 0x1712, 0x1714 }, { 0x1732, 0x1734 }, { 0x1752, 0x1753 }, + { 0x1772, 0x1773 }, { 0x17B4, 0x17B5 }, { 0x17B7, 0x17BD }, + { 0x17C6, 0x17C6 }, { 0x17C9, 0x17D3 }, { 0x17DD, 0x17DD }, + { 0x180B, 0x180D }, { 0x18A9, 0x18A9 }, { 0x1920, 0x1922 }, + { 0x1927, 0x1928 }, { 0x1932, 0x1932 }, { 0x1939, 0x193B }, + { 0x1A17, 0x1A18 }, { 0x1B00, 0x1B03 }, { 0x1B34, 0x1B34 }, + { 0x1B36, 0x1B3A }, { 0x1B3C, 0x1B3C }, { 0x1B42, 0x1B42 }, + { 0x1B6B, 0x1B73 }, { 0x1DC0, 0x1DCA }, { 0x1DFE, 0x1DFF }, + { 0x200B, 0x200F }, { 0x202A, 0x202E }, { 0x2060, 0x2063 }, + { 0x206A, 0x206F }, { 0x20D0, 0x20EF }, { 0x302A, 0x302F }, + { 0x3099, 0x309A }, { 0xA806, 0xA806 }, { 0xA80B, 0xA80B }, + { 0xA825, 0xA826 }, { 0xFB1E, 0xFB1E }, { 0xFE00, 0xFE0F }, + { 0xFE20, 0xFE23 }, { 0xFEFF, 0xFEFF }, { 0xFFF9, 0xFFFB }, + { 0x10A01, 0x10A03 }, { 0x10A05, 0x10A06 }, { 0x10A0C, 0x10A0F }, + { 0x10A38, 0x10A3A }, { 0x10A3F, 0x10A3F }, { 0x1D167, 0x1D169 }, { 0x1D173, 0x1D182 }, { 0x1D185, 0x1D18B }, { 0x1D1AA, 0x1D1AD }, - { 0xE0001, 0xE0001 }, { 0xE0020, 0xE007F }, { 0xE0100, 0xE01EF } + { 0x1D242, 0x1D244 }, { 0xE0001, 0xE0001 }, { 0xE0020, 0xE007F }, + { 0xE0100, 0xE01EF } }; /* test for 8-bit control characters */ if (ucs == 0) return 0; @@ -188,19 +195,20 @@ ucs == 0x2329 || ucs == 0x232a || (ucs >= 0x2e80 && ucs <= 0xa4cf && ucs != 0x303f) || /* CJK ... Yi */ (ucs >= 0xac00 && ucs <= 0xd7a3) || /* Hangul Syllables */ (ucs >= 0xf900 && ucs <= 0xfaff) || /* CJK Compatibility Ideographs */ + (ucs >= 0xfe10 && ucs <= 0xfe19) || /* Vertical forms */ (ucs >= 0xfe30 && ucs <= 0xfe6f) || /* CJK Compatibility Forms */ (ucs >= 0xff00 && ucs <= 0xff60) || /* Fullwidth Forms */ (ucs >= 0xffe0 && ucs <= 0xffe6) || (ucs >= 0x20000 && ucs <= 0x2fffd) || (ucs >= 0x30000 && ucs <= 0x3fffd))); } -int mk_wcswidth(const wchar_t *pwcs, size_t n) +int mk_wcswidth(const unsigned int *pwcs, size_t n) { int w, width = 0; for (;*pwcs && n-- > 0; pwcs++) if ((w = mk_wcwidth(*pwcs)) < 0) @@ -212,18 +220,18 @@ } /* * The following functions are the same as mk_wcwidth() and - * mk_wcwidth_cjk(), except that spacing characters in the East Asian + * mk_wcswidth(), except that spacing characters in the East Asian * Ambiguous (A) category as defined in Unicode Technical Report #11 * have a column width of 2. This variant might be useful for users of * CJK legacy encodings who want to migrate to UCS without changing * the traditional terminal character-width behaviour. It is not * otherwise recommended for general use. */ -int mk_wcwidth_cjk(wchar_t ucs) +int mk_wcwidth_cjk(unsigned int ucs) { /* sorted list of non-overlapping intervals of East Asian Ambiguous * characters, generated by "uniset +WIDTH-A -cat=Me -cat=Mn -cat=Cf c" */ static const struct interval ambiguous[] = { { 0x00A1, 0x00A1 }, { 0x00A4, 0x00A4 }, { 0x00A7, 0x00A8 }, @@ -287,11 +295,11 @@ return mk_wcwidth(ucs); } -int mk_wcswidth_cjk(const wchar_t *pwcs, size_t n) +int mk_wcswidth_cjk(const unsigned int *pwcs, size_t n) { int w, width = 0; for (;*pwcs && n-- > 0; pwcs++) if ((w = mk_wcwidth_cjk(*pwcs)) < 0) Index: wildcard.c ================================================================== --- wildcard.c +++ wildcard.c @@ -324,11 +324,12 @@ if (output) *output++ = *wildcard; wildcard++; } } - *output = '\0'; + if (output) + *output = '\0'; return 1; /* it's clean */ } #ifdef TESTMODE Index: windows/pageant.rc ================================================================== --- windows/pageant.rc +++ windows/pageant.rc @@ -26,16 +26,14 @@ STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "Pageant Key List" FONT 8, "MS Shell Dlg" BEGIN LISTBOX 100, 10, 10, 310, 155, - LBS_NOTIFY | LBS_EXTENDEDSEL | LBS_HASSTRINGS | LBS_USETABSTOPS | WS_VSCROLL | WS_TABSTOP + LBS_EXTENDEDSEL | LBS_HASSTRINGS | LBS_USETABSTOPS | WS_VSCROLL | WS_TABSTOP PUSHBUTTON "&Add Key", 101, 75, 162, 60, 14 PUSHBUTTON "&Remove Key", 102, 195, 162, 60, 14 PUSHBUTTON "&Help", 103, 10, 182, 50, 14 -// id x y w h - PUSHBUTTON "Add CAPI Cert", 110, 130, 182, 70, 14 DEFPUSHBUTTON "&Close", IDOK, 270, 182, 50, 14 END /* Accelerators used: cl */ 213 DIALOG DISCARDABLE 140, 40, 136, 70 @@ -45,11 +43,11 @@ BEGIN DEFPUSHBUTTON "&Close", IDOK, 82, 52, 48, 14 PUSHBUTTON "View &Licence", 101, 6, 52, 70, 14 CTEXT "Pageant", 102, 10, 6, 120, 8 CTEXT "", 100, 10, 16, 120, 16 - CTEXT "\251 1997-2011 Simon Tatham. All rights reserved.", + CTEXT "\251 1997-2013 Simon Tatham. All rights reserved.", 103, 10, 34, 120, 16 END /* No accelerators used */ 214 DIALOG DISCARDABLE 50, 50, 226, 263 @@ -57,16 +55,16 @@ CAPTION "PuTTY Licence" FONT 8, "MS Shell Dlg" BEGIN DEFPUSHBUTTON "OK", IDOK, 98, 243, 44, 14 - LTEXT "Copyright \251 1997-2011 Simon Tatham", 1000, 10, 10, 206, 8 + LTEXT "Copyright \251 1997-2013 Simon Tatham", 1000, 10, 10, 206, 8 LTEXT "Portions copyright Robert de Bath, Joris van Rantwijk, Delian", 1001, 10, 26, 206, 8 LTEXT "Delchev, Andreas Schultz, Jeroen Massar, Wez Furlong, Nicolas", 1002, 10, 34, 206, 8 LTEXT "Barry, Justin Bradford, Ben Harris, Malcolm Smith, Ahmad Khalifa,", 1003, 10, 42, 206, 8 - LTEXT "Markus Kuhn, Pascal Buchbinder, Daniel Risacher, Colin Watson, and CORE SDI S.A.", 1004, 10, 50, 206, 8 + LTEXT "Markus Kuhn, Colin Watson, and CORE SDI S.A.", 1004, 10, 50, 206, 8 LTEXT "Permission is hereby granted, free of charge, to any person", 1005, 10, 66, 206, 8 LTEXT "obtaining a copy of this software and associated documentation", 1006, 10, 74, 206, 8 LTEXT "files (the ""Software""), to deal in the Software without restriction,", 1007, 10, 82, 206, 8 LTEXT "including without limitation the rights to use, copy, modify, merge,", 1008, 10, 90, 206, 8 @@ -93,17 +91,5 @@ #include "version.rc2" #ifndef NO_MANIFESTS 1 RT_MANIFEST "pageant.mft" #endif /* NO_MANIFESTS */ - -215 DIALOG DISCARDABLE 0, 0, 140, 60 -STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU -CAPTION "Pageant: Enter Passphrase" -FONT 8, "MS Shell Dlg" -BEGIN - CTEXT "Enter passphrase for key", 100, 10, 6, 120, 8 - CTEXT "", 101, 10, 16, 120, 8 - EDITTEXT 102, 10, 26, 120, 12, ES_PASSWORD | ES_AUTOHSCROLL - DEFPUSHBUTTON "O&K", IDOK, 20, 42, 40, 14 - PUSHBUTTON "Save", IDCANCEL, 80, 42, 40, 14 -END Index: windows/putty.iss ================================================================== --- windows/putty.iss +++ windows/putty.iss @@ -1,7 +1,7 @@ ; -*- no -*- -; $Id: putty.iss 9366 2011-12-10 12:08:09Z simon $ +; $Id: putty.iss 9998 2013-08-06 17:09:07Z simon $ ; ; -- Inno Setup installer script for PuTTY and its related tools. ; Last tested with Inno Setup 5.0.8. ; ; TODO for future releases: @@ -12,14 +12,14 @@ ; - Maybe a "custom" installation might be useful? Hassle with ; UninstallDisplayIcon, though. [Setup] AppName=PuTTY -AppVerName=PuTTY version 0.62 -VersionInfoTextVersion=Release 0.62 -AppVersion=0.62 -VersionInfoVersion=0.62.0.0 +AppVerName=PuTTY version 0.63 +VersionInfoTextVersion=Release 0.63 +AppVersion=0.63 +VersionInfoVersion=0.63.0.0 AppPublisher=Simon Tatham AppPublisherURL=http://www.chiark.greenend.org.uk/~sgtatham/putty/ AppReadmeFile={app}\README.txt DefaultDirName={pf}\PuTTY DefaultGroupName=PuTTY Index: windows/puttygen.rc ================================================================== --- windows/puttygen.rc +++ windows/puttygen.rc @@ -36,11 +36,11 @@ BEGIN DEFPUSHBUTTON "&Close", IDOK, 82, 52, 48, 14 PUSHBUTTON "View &Licence", 101, 6, 52, 70, 14 CTEXT "PuTTYgen", 102, 10, 6, 120, 8 CTEXT "", 100, 10, 16, 120, 16 - CTEXT "\251 1997-2011 Simon Tatham. All rights reserved.", + CTEXT "\251 1997-2013 Simon Tatham. All rights reserved.", 103, 10, 34, 120, 16 END /* No accelerators used */ 214 DIALOG DISCARDABLE 50, 50, 226, 263 @@ -48,11 +48,11 @@ CAPTION "PuTTY Licence" FONT 8, "MS Shell Dlg" BEGIN DEFPUSHBUTTON "OK", IDOK, 98, 243, 44, 14 - LTEXT "Copyright \251 1997-2011 Simon Tatham", 1000, 10, 10, 206, 8 + LTEXT "Copyright \251 1997-2013 Simon Tatham", 1000, 10, 10, 206, 8 LTEXT "Portions copyright Robert de Bath, Joris van Rantwijk, Delian", 1001, 10, 26, 206, 8 LTEXT "Delchev, Andreas Schultz, Jeroen Massar, Wez Furlong, Nicolas", 1002, 10, 34, 206, 8 LTEXT "Barry, Justin Bradford, Ben Harris, Malcolm Smith, Ahmad Khalifa,", 1003, 10, 42, 206, 8 LTEXT "Markus Kuhn, Colin Watson, and CORE SDI S.A.", 1004, 10, 50, 206, 8 Index: windows/version.rc2 ================================================================== --- windows/version.rc2 +++ windows/version.rc2 @@ -37,11 +37,11 @@ #define STR1(x) #x #define STR(x) STR1(x) /* We keep this around even for snapshots, for monotonicity of version * numbering. It needs to be kept up to date. NB _comma_-separated. */ -#define BASE_VERSION 0,62 +#define BASE_VERSION 0,63 #if defined SNAPSHOT /* Make SVN_REV mandatory for snapshots, to avoid issuing binary * version numbers that look like full releases. */ @@ -111,18 +111,18 @@ BLOCK "StringFileInfo" BEGIN /* "lang-charset" LLLLCCCC = (UK English, Unicode) */ BLOCK "080904B0" BEGIN - VALUE "CompanyName", "Daniel Risacher" /* required :/ */ - VALUE "ProductName", "PuTTY-CAC suite" + VALUE "CompanyName", "Simon Tatham" /* required :/ */ + VALUE "ProductName", "PuTTY suite" VALUE "FileDescription", APPDESC VALUE "InternalName", APPNAME VALUE "OriginalFilename", APPNAME VALUE "FileVersion", VERSION_TEXT VALUE "ProductVersion", VERSION_TEXT - VALUE "LegalCopyright", "Copyright \251 1997-2011 Simon Tatham." + VALUE "LegalCopyright", "Copyright \251 1997-2013 Simon Tatham." #if (!defined SNAPSHOT) && (!defined RELEASE) /* Only if VS_FF_PRIVATEBUILD. */ VALUE "PrivateBuild", VERSION_TEXT /* NBI */ #endif END Index: windows/win_res.rc2 ================================================================== --- windows/win_res.rc2 +++ windows/win_res.rc2 @@ -16,35 +16,35 @@ IDI_CFGICON ICON "puttycfg.ico" /* Accelerators used: clw */ IDD_ABOUTBOX DIALOG DISCARDABLE 140, 40, 214, 70 STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU -CAPTION "About PuTTY-CAC" +CAPTION "About PuTTY" FONT 8, "MS Shell Dlg" BEGIN DEFPUSHBUTTON "&Close", IDOK, 160, 52, 48, 14 PUSHBUTTON "View &Licence", IDA_LICENCE, 6, 52, 70, 14 PUSHBUTTON "Visit &Web Site", IDA_WEB, 84, 52, 70, 14 - CTEXT "PuTTY-CAC", IDA_TEXT1, 10, 6, 194, 8 + CTEXT "PuTTY", IDA_TEXT1, 10, 6, 194, 8 CTEXT "", IDA_VERSION, 10, 16, 194, 16 - CTEXT "\251 1997-2012 Dan Risacher, Simon Tatham, and others.", + CTEXT "\251 1997-2013 Simon Tatham. All rights reserved.", IDA_TEXT2, 10, 34, 194, 16 END /* Accelerators used: aco */ IDD_MAINBOX DIALOG DISCARDABLE 0, 0, 300, 252 STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU -CAPTION "PuTTY CAC Configuration" +CAPTION "PuTTY Configuration" FONT 8, "MS Shell Dlg" CLASS "PuTTYConfigBox" BEGIN END /* Accelerators used: co */ IDD_LOGBOX DIALOG DISCARDABLE 100, 20, 300, 119 STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU -CAPTION "PuTTY CAC Event Log" +CAPTION "PuTTY Event Log" FONT 8, "MS Shell Dlg" BEGIN DEFPUSHBUTTON "&Close", IDOK, 135, 102, 44, 14 PUSHBUTTON "C&opy", IDN_COPY, 81, 102, 44, 14 LISTBOX IDN_LIST, 3, 3, 294, 95, LBS_HASSTRINGS | LBS_USETABSTOPS | WS_VSCROLL | LBS_EXTENDEDSEL @@ -56,16 +56,16 @@ CAPTION "PuTTY Licence" FONT 8, "MS Shell Dlg" BEGIN DEFPUSHBUTTON "OK", IDOK, 98, 243, 44, 14 - LTEXT "Copyright \251 1997-2011 Dan Risacher, Simon Tatham", 1000, 10, 10, 206, 8 + LTEXT "Copyright \251 1997-2013 Simon Tatham", 1000, 10, 10, 206, 8 LTEXT "Portions copyright Robert de Bath, Joris van Rantwijk, Delian", 1001, 10, 26, 206, 8 LTEXT "Delchev, Andreas Schultz, Jeroen Massar, Wez Furlong, Nicolas", 1002, 10, 34, 206, 8 LTEXT "Barry, Justin Bradford, Ben Harris, Malcolm Smith, Ahmad Khalifa,", 1003, 10, 42, 206, 8 - LTEXT "Markus Kuhn, Pascal Buchbinder, Colin Watson, and CORE SDI S.A.", 1004, 10, 50, 206, 8 + LTEXT "Markus Kuhn, Colin Watson, and CORE SDI S.A.", 1004, 10, 50, 206, 8 LTEXT "Permission is hereby granted, free of charge, to any person", 1005, 10, 66, 206, 8 LTEXT "obtaining a copy of this software and associated documentation", 1006, 10, 74, 206, 8 LTEXT "files (the ""Software""), to deal in the Software without restriction,", 1007, 10, 82, 206, 8 LTEXT "including without limitation the rights to use, copy, modify, merge,", 1008, 10, 90, 206, 8 Index: windows/wincfg.c ================================================================== --- windows/wincfg.c +++ windows/wincfg.c @@ -68,22 +68,22 @@ */ s = ctrl_getset(b, "Window", "scrollback", "Control the scrollback in the window"); ctrl_checkbox(s, "Display scrollbar in full screen mode", 'i', HELPCTX(window_scrollback), - dlg_stdcheckbox_handler, - I(offsetof(Config,scrollbar_in_fullscreen))); + conf_checkbox_handler, + I(CONF_scrollbar_in_fullscreen)); /* * Really this wants to go just after `Display scrollbar'. See * if we can find that control, and do some shuffling. */ { int i; for (i = 0; i < s->ncontrols; i++) { c = s->ctrls[i]; if (c->generic.type == CTRL_CHECKBOX && - c->generic.context.i == offsetof(Config,scrollbar)) { + c->generic.context.i == CONF_scrollbar) { /* * Control i is the scrollbar checkbox. * Control s->ncontrols-1 is the scrollbar-in-FS one. */ if (i < s->ncontrols-2) { @@ -103,14 +103,14 @@ */ s = ctrl_getset(b, "Terminal/Keyboard", "features", "Enable extra keyboard features:"); ctrl_checkbox(s, "AltGr acts as Compose key", 't', HELPCTX(keyboard_compose), - dlg_stdcheckbox_handler, I(offsetof(Config,compose_key))); + conf_checkbox_handler, I(CONF_compose_key)); ctrl_checkbox(s, "Control-Alt is different from AltGr", 'd', HELPCTX(keyboard_ctrlalt), - dlg_stdcheckbox_handler, I(offsetof(Config,ctrlaltkeys))); + conf_checkbox_handler, I(CONF_ctrlaltkeys)); /* * Windows allows an arbitrary .WAV to be played as a bell, and * also the use of the PC speaker. For this we must search the * existing controlset for the radio-button set controlling the @@ -131,12 +131,12 @@ { int i; for (i = 0; i < s->ncontrols; i++) { c = s->ctrls[i]; if (c->generic.type == CTRL_RADIO && - c->generic.context.i == offsetof(Config, beep)) { - assert(c->generic.handler == dlg_stdradiobutton_handler); + c->generic.context.i == CONF_beep) { + assert(c->generic.handler == conf_radiobutton_handler); c->radio.nbuttons += 2; c->radio.buttons = sresize(c->radio.buttons, c->radio.nbuttons, char *); c->radio.buttons[c->radio.nbuttons-1] = dupstr("Play a custom sound file"); @@ -157,20 +157,20 @@ } } ctrl_filesel(s, "Custom sound file to play as a bell:", NO_SHORTCUT, FILTER_WAVE_FILES, FALSE, "Select bell sound file", HELPCTX(bell_style), - dlg_stdfilesel_handler, I(offsetof(Config, bell_wavefile))); + conf_filesel_handler, I(CONF_bell_wavefile)); /* * While we've got this box open, taskbar flashing on a bell is * also Windows-specific. */ ctrl_radiobuttons(s, "Taskbar/caption indication on bell:", 'i', 3, HELPCTX(bell_taskbar), - dlg_stdradiobutton_handler, - I(offsetof(Config, beep_ind)), + conf_radiobutton_handler, + I(CONF_beep_ind), "Disabled", I(B_IND_DISABLED), "Flashing", I(B_IND_FLASH), "Steady", I(B_IND_STEADY), NULL); /* @@ -178,11 +178,11 @@ */ s = ctrl_getset(b, "Window/Appearance", "border", "Adjust the window border"); ctrl_checkbox(s, "Sunken-edge border (slightly thicker)", 's', HELPCTX(appearance_border), - dlg_stdcheckbox_handler, I(offsetof(Config,sunken_edge))); + conf_checkbox_handler, I(CONF_sunken_edge)); /* * Configurable font quality settings for Windows. */ s = ctrl_getset(b, "Window/Appearance", "font", @@ -189,12 +189,12 @@ "Font settings"); ctrl_checkbox(s, "Allow selection of variable-pitch fonts", NO_SHORTCUT, HELPCTX(appearance_font), variable_pitch_handler, I(0)); ctrl_radiobuttons(s, "Font quality:", 'q', 2, HELPCTX(appearance_font), - dlg_stdradiobutton_handler, - I(offsetof(Config, font_quality)), + conf_radiobutton_handler, + I(CONF_font_quality), "Antialiased", I(FQ_ANTIALIASED), "Non-Antialiased", I(FQ_NONANTIALIASED), "ClearType", I(FQ_CLEARTYPE), "Default", I(FQ_DEFAULT), NULL); @@ -204,12 +204,12 @@ * platform (at least unless someone fixes it!). */ s = ctrl_getset(b, "Window/Translation", "tweaks", NULL); ctrl_checkbox(s, "Caps Lock acts as Cyrillic switch", 's', HELPCTX(translation_cyrillic), - dlg_stdcheckbox_handler, - I(offsetof(Config,xlat_capslockcyr))); + conf_checkbox_handler, + I(CONF_xlat_capslockcyr)); /* * On Windows we can use but not enumerate translation tables * from the operating system. Briefly document this. */ @@ -230,12 +230,12 @@ { int i; for (i = 0; i < s->ncontrols; i++) { c = s->ctrls[i]; if (c->generic.type == CTRL_RADIO && - c->generic.context.i == offsetof(Config, vtmode)) { - assert(c->generic.handler == dlg_stdradiobutton_handler); + c->generic.context.i == CONF_vtmode) { + assert(c->generic.handler == conf_radiobutton_handler); c->radio.nbuttons += 3; c->radio.buttons = sresize(c->radio.buttons, c->radio.nbuttons, char *); c->radio.buttons[c->radio.nbuttons-3] = dupstr("Font has XWindows encoding"); @@ -270,11 +270,11 @@ */ s = ctrl_getset(b, "Window/Selection", "format", "Formatting of pasted characters"); ctrl_checkbox(s, "Paste to clipboard in RTF as well as plain text", 'f', HELPCTX(selection_rtf), - dlg_stdcheckbox_handler, I(offsetof(Config,rtf_paste))); + conf_checkbox_handler, I(CONF_rtf_paste)); /* * Windows often has no middle button, so we supply a selection * mode in which the more critical Paste action is available on * the right button instead. @@ -281,12 +281,12 @@ */ s = ctrl_getset(b, "Window/Selection", "mouse", "Control use of mouse"); ctrl_radiobuttons(s, "Action of mouse buttons:", 'm', 1, HELPCTX(selection_buttons), - dlg_stdradiobutton_handler, - I(offsetof(Config, mouse_is_xterm)), + conf_radiobutton_handler, + I(CONF_mouse_is_xterm), "Windows (Middle extends, Right brings up menu)", I(2), "Compromise (Middle extends, Right pastes)", I(0), "xterm (Right extends, Middle pastes)", I(1), NULL); /* * This really ought to go at the _top_ of its box, not the @@ -302,24 +302,24 @@ */ s = ctrl_getset(b, "Window/Colours", "general", "General options for colour usage"); ctrl_checkbox(s, "Attempt to use logical palettes", 'l', HELPCTX(colours_logpal), - dlg_stdcheckbox_handler, I(offsetof(Config,try_palette))); + conf_checkbox_handler, I(CONF_try_palette)); ctrl_checkbox(s, "Use system colours", 's', HELPCTX(colours_system), - dlg_stdcheckbox_handler, I(offsetof(Config,system_colour))); + conf_checkbox_handler, I(CONF_system_colour)); /* * Resize-by-changing-font is a Windows insanity. */ s = ctrl_getset(b, "Window", "size", "Set the size of the window"); ctrl_radiobuttons(s, "When window is resized:", 'z', 1, HELPCTX(window_resize), - dlg_stdradiobutton_handler, - I(offsetof(Config, resize_action)), + conf_radiobutton_handler, + I(CONF_resize_action), "Change the number of rows and columns", I(RESIZE_TERM), "Change the size of the font", I(RESIZE_FONT), "Change font size only when maximised", I(RESIZE_EITHER), "Forbid resizing completely", I(RESIZE_DISABLED), NULL); @@ -329,24 +329,24 @@ * most of these options are Windows-specific. */ s = ctrl_getset(b, "Window/Behaviour", "main", NULL); ctrl_checkbox(s, "Window closes on ALT-F4", '4', HELPCTX(behaviour_altf4), - dlg_stdcheckbox_handler, I(offsetof(Config,alt_f4))); + conf_checkbox_handler, I(CONF_alt_f4)); ctrl_checkbox(s, "System menu appears on ALT-Space", 'y', HELPCTX(behaviour_altspace), - dlg_stdcheckbox_handler, I(offsetof(Config,alt_space))); + conf_checkbox_handler, I(CONF_alt_space)); ctrl_checkbox(s, "System menu appears on ALT alone", 'l', HELPCTX(behaviour_altonly), - dlg_stdcheckbox_handler, I(offsetof(Config,alt_only))); + conf_checkbox_handler, I(CONF_alt_only)); ctrl_checkbox(s, "Ensure window is always on top", 'e', HELPCTX(behaviour_alwaysontop), - dlg_stdcheckbox_handler, I(offsetof(Config,alwaysontop))); + conf_checkbox_handler, I(CONF_alwaysontop)); ctrl_checkbox(s, "Full screen on Alt-Enter", 'f', HELPCTX(behaviour_altenter), - dlg_stdcheckbox_handler, - I(offsetof(Config,fullscreenonaltenter))); + conf_checkbox_handler, + I(CONF_fullscreenonaltenter)); /* * Windows supports a local-command proxy. This also means we * must adjust the text on the `Telnet command' control. */ @@ -354,12 +354,12 @@ int i; s = ctrl_getset(b, "Connection/Proxy", "basics", NULL); for (i = 0; i < s->ncontrols; i++) { c = s->ctrls[i]; if (c->generic.type == CTRL_RADIO && - c->generic.context.i == offsetof(Config, proxy_type)) { - assert(c->generic.handler == dlg_stdradiobutton_handler); + c->generic.context.i == CONF_proxy_type) { + assert(c->generic.handler == conf_radiobutton_handler); c->radio.nbuttons++; c->radio.buttons = sresize(c->radio.buttons, c->radio.nbuttons, char *); c->radio.buttons[c->radio.nbuttons-1] = dupstr("Local"); @@ -371,13 +371,12 @@ } for (i = 0; i < s->ncontrols; i++) { c = s->ctrls[i]; if (c->generic.type == CTRL_EDITBOX && - c->generic.context.i == - offsetof(Config, proxy_telnet_command)) { - assert(c->generic.handler == dlg_stdeditbox_handler); + c->generic.context.i == CONF_proxy_telnet_command) { + assert(c->generic.handler == conf_editbox_handler); sfree(c->generic.label); c->generic.label = dupstr("Telnet command, or local" " proxy command"); break; } @@ -397,8 +396,8 @@ if (!midsession && backend_from_proto(PROT_SSH)) { s = ctrl_getset(b, "Connection/SSH/X11", "x11", "X11 forwarding"); ctrl_filesel(s, "X authority file for local display", 't', NULL, FALSE, "Select X authority file", HELPCTX(ssh_tunnels_xauthority), - dlg_stdfilesel_handler, I(offsetof(Config, xauthfile))); + conf_filesel_handler, I(CONF_xauthfile)); } } Index: windows/wincons.c ================================================================== --- windows/wincons.c +++ windows/wincons.c @@ -39,11 +39,11 @@ void notify_remote_exit(void *frontend) { } -void timer_change_notify(long next) +void timer_change_notify(unsigned long next) { } int verify_ssh_host_key(void *frontend, char *host, int port, char *keytype, char *keystr, char *fingerprint, @@ -199,11 +199,11 @@ /* * Ask whether to wipe a session log file before writing to it. * Returns 2 for wipe, 1 for append, 0 for cancel (don't log). */ -int askappend(void *frontend, Filename filename, +int askappend(void *frontend, Filename *filename, void (*callback)(void *ctx, int result), void *ctx) { HANDLE hin; DWORD savemode, i; @@ -221,15 +221,15 @@ "Logging will not be enabled.\n"; char line[32]; if (console_batch_mode) { - fprintf(stderr, msgtemplate_batch, FILENAME_MAX, filename.path); + fprintf(stderr, msgtemplate_batch, FILENAME_MAX, filename->path); fflush(stderr); return 0; } - fprintf(stderr, msgtemplate, FILENAME_MAX, filename.path); + fprintf(stderr, msgtemplate, FILENAME_MAX, filename->path); fflush(stderr); hin = GetStdHandle(STD_INPUT_HANDLE); GetConsoleMode(hin, &savemode); SetConsoleMode(hin, (savemode | ENABLE_ECHO_INPUT | @@ -313,11 +313,11 @@ * Zero all the results, in case we abort half-way through. */ { int i; for (i = 0; i < (int)p->n_prompts; i++) - memset(p->prompts[i]->result, 0, p->prompts[i]->result_len); + prompt_set_result(p->prompts[i], ""); } /* * The prompts_t might contain a message to be displayed but no * actual prompt. More usually, though, it will contain @@ -363,13 +363,13 @@ console_data_untrusted(hout, "\n", 1); } for (curr_prompt = 0; curr_prompt < p->n_prompts; curr_prompt++) { - DWORD savemode, newmode, i = 0; + DWORD savemode, newmode; + int len; prompt_t *pr = p->prompts[curr_prompt]; - BOOL r; GetConsoleMode(hin, &savemode); newmode = savemode | ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT; if (!pr->echo) newmode &= ~ENABLE_ECHO_INPUT; @@ -377,33 +377,52 @@ newmode |= ENABLE_ECHO_INPUT; SetConsoleMode(hin, newmode); console_data_untrusted(hout, pr->prompt, strlen(pr->prompt)); - r = ReadFile(hin, pr->result, pr->result_len - 1, &i, NULL); + len = 0; + while (1) { + DWORD ret = 0; + BOOL r; + + prompt_ensure_result_size(pr, len * 5 / 4 + 512); + + r = ReadFile(hin, pr->result + len, pr->resultsize - len - 1, + &ret, NULL); + + if (!r || ret == 0) { + len = -1; + break; + } + len += ret; + if (pr->result[len - 1] == '\n') { + len--; + if (pr->result[len - 1] == '\r') + len--; + break; + } + } SetConsoleMode(hin, savemode); - if ((int) i > pr->result_len) - i = pr->result_len - 1; - else - i = i - 2; - pr->result[i] = '\0'; - if (!pr->echo) { DWORD dummy; WriteFile(hout, "\r\n", 2, &dummy, NULL); } + if (len < 0) { + return 0; /* failure due to read error */ + } + + pr->result[len] = '\0'; } return 1; /* success */ - } void frontend_keypress(void *handle) { /* * This is nothing but a stub, in console code. */ return; } Index: windows/winctrls.c ================================================================== --- windows/winctrls.c +++ windows/winctrls.c @@ -446,10 +446,12 @@ SelectObject(hdc, oldfont); ReleaseDC(cp->hwnd, hdc); if (lines) *lines = nlines; + sfree(pwidths); + return ret; } /* * A single standalone static text control. @@ -1637,12 +1639,12 @@ escaped = shortcut_escape(ctrl->fontselect.label, ctrl->fontselect.shortcut); shortcuts[nshortcuts++] = ctrl->fontselect.shortcut; statictext(&pos, escaped, 1, base_id); staticbtn(&pos, "", base_id+1, "Change...", base_id+2); + data = fontspec_new("", 0, 0, 0); sfree(escaped); - data = snew(FontSpec); break; default: assert(!"Can't happen"); num_ids = 0; /* placate gcc */ break; @@ -1663,11 +1665,13 @@ memcpy(c->shortcuts, shortcuts, sizeof(shortcuts)); winctrl_add(wc, c); winctrl_add_shortcuts(dp, c); if (actual_base_id == base_id) base_id += num_ids; - } + } else { + sfree(data); + } if (colstart >= 0) { /* * Update the ypos in all columns crossed by this * control. @@ -1932,25 +1936,25 @@ (HIWORD(wParam) == BN_CLICKED || HIWORD(wParam) == BN_DOUBLECLICKED))) { CHOOSEFONT cf; LOGFONT lf; HDC hdc; - FontSpec fs = *(FontSpec *)c->data; - + FontSpec *fs = (FontSpec *)c->data; + hdc = GetDC(0); - lf.lfHeight = -MulDiv(fs.height, + lf.lfHeight = -MulDiv(fs->height, GetDeviceCaps(hdc, LOGPIXELSY), 72); ReleaseDC(0, hdc); lf.lfWidth = lf.lfEscapement = lf.lfOrientation = 0; lf.lfItalic = lf.lfUnderline = lf.lfStrikeOut = 0; - lf.lfWeight = (fs.isbold ? FW_BOLD : 0); - lf.lfCharSet = fs.charset; + lf.lfWeight = (fs->isbold ? FW_BOLD : 0); + lf.lfCharSet = fs->charset; lf.lfOutPrecision = OUT_DEFAULT_PRECIS; lf.lfClipPrecision = CLIP_DEFAULT_PRECIS; lf.lfQuality = DEFAULT_QUALITY; lf.lfPitchAndFamily = FIXED_PITCH | FF_DONTCARE; - strncpy(lf.lfFaceName, fs.name, + strncpy(lf.lfFaceName, fs->name, sizeof(lf.lfFaceName) - 1); lf.lfFaceName[sizeof(lf.lfFaceName) - 1] = '\0'; cf.lStructSize = sizeof(cf); cf.hwndOwner = dp->hwnd; @@ -1957,17 +1961,15 @@ cf.lpLogFont = &lf; cf.Flags = (dp->fixed_pitch_fonts ? CF_FIXEDPITCHONLY : 0) | CF_FORCEFONTEXIST | CF_INITTOLOGFONTSTRUCT | CF_SCREENFONTS; if (ChooseFont(&cf)) { - strncpy(fs.name, lf.lfFaceName, - sizeof(fs.name) - 1); - fs.name[sizeof(fs.name) - 1] = '\0'; - fs.isbold = (lf.lfWeight == FW_BOLD); - fs.charset = lf.lfCharSet; - fs.height = cf.iPointSize / 10; + fs = fontspec_new(lf.lfFaceName, (lf.lfWeight == FW_BOLD), + cf.iPointSize / 10, lf.lfCharSet); dlg_fontsel_set(ctrl, dp, fs); + fontspec_free(fs); + ctrl->generic.handler(ctrl, dp, dp->data, EVENT_VALCHANGE); } } break; } @@ -2098,17 +2100,16 @@ struct winctrl *c = dlg_findbyctrl(dp, ctrl); assert(c && c->ctrl->generic.type == CTRL_EDITBOX); SetDlgItemText(dp->hwnd, c->base_id+1, text); } -void dlg_editbox_get(union control *ctrl, void *dlg, char *buffer, int length) +char *dlg_editbox_get(union control *ctrl, void *dlg) { struct dlgparam *dp = (struct dlgparam *)dlg; struct winctrl *c = dlg_findbyctrl(dp, ctrl); assert(c && c->ctrl->generic.type == CTRL_EDITBOX); - GetDlgItemText(dp->hwnd, c->base_id+1, buffer, length); - buffer[length-1] = '\0'; + return GetDlgItemText_alloc(dp->hwnd, c->base_id+1); } /* The `listbox' functions can also apply to combo boxes. */ void dlg_listbox_clear(union control *ctrl, void *dlg) { @@ -2284,55 +2285,60 @@ SetDlgItemText(dp->hwnd, id, escaped); sfree(escaped); } } -void dlg_filesel_set(union control *ctrl, void *dlg, Filename fn) +void dlg_filesel_set(union control *ctrl, void *dlg, Filename *fn) { struct dlgparam *dp = (struct dlgparam *)dlg; struct winctrl *c = dlg_findbyctrl(dp, ctrl); assert(c && c->ctrl->generic.type == CTRL_FILESELECT); - SetDlgItemText(dp->hwnd, c->base_id+1, fn.path); + SetDlgItemText(dp->hwnd, c->base_id+1, fn->path); } -void dlg_filesel_get(union control *ctrl, void *dlg, Filename *fn) +Filename *dlg_filesel_get(union control *ctrl, void *dlg) { struct dlgparam *dp = (struct dlgparam *)dlg; struct winctrl *c = dlg_findbyctrl(dp, ctrl); + char *tmp; + Filename *ret; assert(c && c->ctrl->generic.type == CTRL_FILESELECT); - GetDlgItemText(dp->hwnd, c->base_id+1, fn->path, lenof(fn->path)); - fn->path[lenof(fn->path)-1] = '\0'; + tmp = GetDlgItemText_alloc(dp->hwnd, c->base_id+1); + ret = filename_from_str(tmp); + sfree(tmp); + return ret; } -void dlg_fontsel_set(union control *ctrl, void *dlg, FontSpec fs) +void dlg_fontsel_set(union control *ctrl, void *dlg, FontSpec *fs) { char *buf, *boldstr; struct dlgparam *dp = (struct dlgparam *)dlg; struct winctrl *c = dlg_findbyctrl(dp, ctrl); assert(c && c->ctrl->generic.type == CTRL_FONTSELECT); - *(FontSpec *)c->data = fs; /* structure copy */ + fontspec_free((FontSpec *)c->data); + c->data = fontspec_copy(fs); - boldstr = (fs.isbold ? "bold, " : ""); - if (fs.height == 0) - buf = dupprintf("Font: %s, %sdefault height", fs.name, boldstr); + boldstr = (fs->isbold ? "bold, " : ""); + if (fs->height == 0) + buf = dupprintf("Font: %s, %sdefault height", fs->name, boldstr); else - buf = dupprintf("Font: %s, %s%d-%s", fs.name, boldstr, - (fs.height < 0 ? -fs.height : fs.height), - (fs.height < 0 ? "pixel" : "point")); + buf = dupprintf("Font: %s, %s%d-%s", fs->name, boldstr, + (fs->height < 0 ? -fs->height : fs->height), + (fs->height < 0 ? "pixel" : "point")); SetDlgItemText(dp->hwnd, c->base_id+1, buf); sfree(buf); dlg_auto_set_fixed_pitch_flag(dp); } -void dlg_fontsel_get(union control *ctrl, void *dlg, FontSpec *fs) +FontSpec *dlg_fontsel_get(union control *ctrl, void *dlg) { struct dlgparam *dp = (struct dlgparam *)dlg; struct winctrl *c = dlg_findbyctrl(dp, ctrl); assert(c && c->ctrl->generic.type == CTRL_FONTSELECT); - *fs = *(FontSpec *)c->data; /* structure copy */ + return fontspec_copy((FontSpec *)c->data); } /* * Bracketing a large set of updates in these two functions will * cause the front end (if possible) to delay updating the screen @@ -2362,10 +2368,12 @@ { struct dlgparam *dp = (struct dlgparam *)dlg; struct winctrl *c = dlg_findbyctrl(dp, ctrl); int id; HWND ctl; + if (!c) + return; switch (ctrl->generic.type) { case CTRL_EDITBOX: id = c->base_id + 1; break; case CTRL_RADIO: for (id = c->base_id + ctrl->radio.nbuttons; id > 1; id--) if (IsDlgButtonChecked(dp->hwnd, id)) @@ -2469,12 +2477,14 @@ } void dlg_auto_set_fixed_pitch_flag(void *dlg) { struct dlgparam *dp = (struct dlgparam *)dlg; - Config *cfg = (Config *)dp->data; - HFONT font; + Conf *conf = (Conf *)dp->data; + FontSpec *fs; + int quality; + HFONT hfont; HDC hdc; TEXTMETRIC tm; int is_var; /* @@ -2481,29 +2491,32 @@ * Attempt to load the current font, and see if it's * variable-pitch. If so, start off the fixed-pitch flag for the * dialog box as false. * * We assume here that any client of the dlg_* mechanism which is - * using font selectors at all is also using a normal 'Config *' + * using font selectors at all is also using a normal 'Conf *' * as dp->data. */ - font = CreateFont(0, 0, 0, 0, FW_DONTCARE, FALSE, FALSE, FALSE, - DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, - CLIP_DEFAULT_PRECIS, FONT_QUALITY(cfg->font_quality), - FIXED_PITCH | FF_DONTCARE, cfg->font.name); + quality = conf_get_int(conf, CONF_font_quality); + fs = conf_get_fontspec(conf, CONF_font); + + hfont = CreateFont(0, 0, 0, 0, FW_DONTCARE, FALSE, FALSE, FALSE, + DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, + CLIP_DEFAULT_PRECIS, FONT_QUALITY(quality), + FIXED_PITCH | FF_DONTCARE, fs->name); hdc = GetDC(NULL); - if (font && hdc && SelectObject(hdc, font) && GetTextMetrics(hdc, &tm)) { + if (hdc && SelectObject(hdc, hfont) && GetTextMetrics(hdc, &tm)) { /* Note that the TMPF_FIXED_PITCH bit is defined upside down :-( */ is_var = (tm.tmPitchAndFamily & TMPF_FIXED_PITCH); } else { is_var = FALSE; /* assume it's basically normal */ } if (hdc) ReleaseDC(NULL, hdc); - if (font) - DeleteObject(font); + if (hfont) + DeleteObject(hfont); if (is_var) dp->fixed_pitch_fonts = FALSE; } Index: windows/windefs.c ================================================================== --- windows/windefs.c +++ windows/windefs.c @@ -4,32 +4,24 @@ #include "putty.h" #include -FontSpec platform_default_fontspec(const char *name) -{ - FontSpec ret; - if (!strcmp(name, "Font")) { - strcpy(ret.name, "Courier New"); - ret.isbold = 0; - ret.charset = ANSI_CHARSET; - ret.height = 10; - } else { - ret.name[0] = '\0'; - } - return ret; -} - -Filename platform_default_filename(const char *name) -{ - Filename ret; - if (!strcmp(name, "LogFileName")) - strcpy(ret.path, "putty.log"); - else - *ret.path = '\0'; - return ret; +FontSpec *platform_default_fontspec(const char *name) +{ + if (!strcmp(name, "Font")) + return fontspec_new("Courier New", 0, 10, ANSI_CHARSET); + else + return fontspec_new("", 0, 0, 0); +} + +Filename *platform_default_filename(const char *name) +{ + if (!strcmp(name, "LogFileName")) + return filename_from_str("putty.log"); + else + return filename_from_str(""); } char *platform_default_s(const char *name) { if (!strcmp(name, "SerialLine")) Index: windows/windlg.c ================================================================== --- windows/windlg.c +++ windows/windlg.c @@ -42,11 +42,11 @@ static struct dlgparam dp; static char **events = NULL; static int nevents = 0, negsize = 0; -extern Config cfg; /* defined in window.c */ +extern Conf *conf; /* defined in window.c */ #define PRINTER_DISABLED_STRING "None (printing disabled)" void force_normal(HWND hwnd) { @@ -215,11 +215,11 @@ return 0; case IDA_WEB: /* Load web browser */ ShellExecute(hwnd, "open", - "http://www.risacher.org/putty-cac/", + "http://www.chiark.greenend.org.uk/~sgtatham/putty/", 0, 0, SW_SHOWDEFAULT); return 0; } return 0; case WM_CLOSE: @@ -646,11 +646,11 @@ winctrl_init(&ctrls_panel); dp_add_tree(&dp, &ctrls_base); dp_add_tree(&dp, &ctrls_panel); dp.wintitle = dupprintf("%s Configuration", appname); dp.errtitle = dupprintf("%s Error", appname); - dp.data = &cfg; + dp.data = conf; dlg_auto_set_fixed_pitch_flag(&dp); dp.shortcuts['g'] = TRUE; /* the treeview: `Cate&gory' */ ret = SaneDialogBox(hinst, MAKEINTRESOURCE(IDD_MAINBOX), NULL, @@ -664,27 +664,27 @@ return ret; } int do_reconfig(HWND hwnd, int protcfginfo) { - Config backup_cfg; - int ret; + Conf *backup_conf; + int ret, protocol; - backup_cfg = cfg; /* structure copy */ + backup_conf = conf_copy(conf); ctrlbox = ctrl_new_box(); - setup_config_box(ctrlbox, TRUE, cfg.protocol, protcfginfo); - win_setup_config_box(ctrlbox, &dp.hwnd, has_help(), TRUE, - cfg.protocol); + protocol = conf_get_int(conf, CONF_protocol); + setup_config_box(ctrlbox, TRUE, protocol, protcfginfo); + win_setup_config_box(ctrlbox, &dp.hwnd, has_help(), TRUE, protocol); dp_init(&dp); winctrl_init(&ctrls_base); winctrl_init(&ctrls_panel); dp_add_tree(&dp, &ctrls_base); dp_add_tree(&dp, &ctrls_panel); dp.wintitle = dupprintf("%s Reconfiguration", appname); dp.errtitle = dupprintf("%s Error", appname); - dp.data = &cfg; + dp.data = conf; dlg_auto_set_fixed_pitch_flag(&dp); dp.shortcuts['g'] = TRUE; /* the treeview: `Cate&gory' */ ret = SaneDialogBox(hinst, MAKEINTRESOURCE(IDD_MAINBOX), NULL, GenericMainDlgProc); @@ -693,11 +693,13 @@ winctrl_cleanup(&ctrls_base); winctrl_cleanup(&ctrls_panel); dp_cleanup(&dp); if (!ret) - cfg = backup_cfg; /* structure copy */ + conf_copy_into(conf, backup_conf); + + conf_free(backup_conf); return ret; } void logevent(void *frontend, const char *string) @@ -854,11 +856,11 @@ /* * Ask whether to wipe a session log file before writing to it. * Returns 2 for wipe, 1 for append, 0 for cancel (don't log). */ -int askappend(void *frontend, Filename filename, +int askappend(void *frontend, Filename *filename, void (*callback)(void *ctx, int result), void *ctx) { static const char msgtemplate[] = "The session log file \"%.*s\" already exists.\n" "You can overwrite it with a new session log,\n" @@ -868,11 +870,11 @@ "or Cancel to disable logging."; char *message; char *mbtitle; int mbret; - message = dupprintf(msgtemplate, FILENAME_MAX, filename.path); + message = dupprintf(msgtemplate, FILENAME_MAX, filename->path); mbtitle = dupprintf("%s Log to File", appname); mbret = MessageBox(NULL, message, mbtitle, MB_ICONQUESTION | MB_YESNOCANCEL | MB_DEFBUTTON3); Index: windows/window.c ================================================================== --- windows/window.c +++ windows/window.c @@ -78,19 +78,19 @@ static Mouse_Button translate_button(Mouse_Button button); static LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam, unsigned char *output); -static void cfgtopalette(void); +static void conftopalette(void); static void systopalette(void); static void init_palette(void); static void init_fonts(int, int); static void another_font(int); static void deinit_fonts(void); static void set_input_locale(HKL); static void update_savedsess_menu(void); -static void init_flashwindow(void); +static void init_winfuncs(void); static int is_full_screen(void); static void make_full_screen(void); static void clear_full_screen(void); static void flip_full_screen(void); @@ -102,14 +102,10 @@ static int font_width, font_height, font_dualwidth, font_varpitch; static int offset_width, offset_height; static int was_zoomed = 0; static int prev_rows, prev_cols; -static int pending_netevent = 0; -static WPARAM pend_netevent_wParam = 0; -static LPARAM pend_netevent_lParam = 0; -static void enact_pending_netevent(void); static void flash_window(int mode); static void sys_cursor_update(void); static int get_fullscreen_rect(RECT * ss); static int caret_x = -1, caret_y = -1; @@ -119,11 +115,11 @@ static void *ldisc; static Backend *back; static void *backhandle; static struct unicode_data ucsdata; -static int must_close_session, session_closed; +static int session_closed; static int reconfiguring = FALSE; static const struct telnet_special *specials = NULL; static HMENU specials_menu = NULL; static int n_specials = 0; @@ -138,11 +134,21 @@ HMENU menu; } popup_menus[2]; enum { SYSMENU, CTXMENU }; static HMENU savedsess_menu; -Config cfg; /* exported to windlg.c */ +struct wm_netevent_params { + /* Used to pass data to wm_netevent_callback */ + WPARAM wParam; + LPARAM lParam; +}; + +Conf *conf; /* exported to windlg.c */ + +static void conf_cache_data(void); +int cursor_type; +int vtmode; static struct sesslist sesslist; /* for saved-session menu */ struct agent_callback { void (*callback)(void *, void *, int); @@ -162,18 +168,19 @@ #define FONT_OEM 0x20 #define FONT_OEMBOLD 0x21 #define FONT_OEMUND 0x22 #define FONT_OEMBOLDUND 0x23 -#define FONT_MAXNO 0x2F +#define FONT_MAXNO 0x40 #define FONT_SHIFT 5 static HFONT fonts[FONT_MAXNO]; static LOGFONT lfont; static int fontflag[FONT_MAXNO]; static enum { - BOLD_COLOURS, BOLD_SHADOW, BOLD_FONT -} bold_mode; + BOLD_NONE, BOLD_SHADOW, BOLD_FONT +} bold_font_mode; +static int bold_colours; static enum { UND_LINE, UND_FONT } und_mode; static int descent; @@ -200,10 +207,19 @@ static int compose_state = 0; static UINT wm_mousewheel = WM_MOUSEWHEEL; +#define IS_HIGH_VARSEL(wch1, wch2) \ + ((wch1) == 0xDB40 && ((wch2) >= 0xDD00 && (wch2) <= 0xDDEF)) +#define IS_LOW_VARSEL(wch) \ + (((wch) >= 0x180B && (wch) <= 0x180D) || /* MONGOLIAN FREE VARIATION SELECTOR */ \ + ((wch) >= 0xFE00 && (wch) <= 0xFE0F)) /* VARIATION SELECTOR 1-16 */ + +const int share_can_be_downstream = TRUE; +const int share_can_be_upstream = TRUE; + /* Dummy routine, only required in plink. */ void ldisc_update(void *frontend, int echo, int edit) { } @@ -221,35 +237,37 @@ /* * Select protocol. This is farmed out into a table in a * separate file to enable an ssh-free variant. */ - back = backend_from_proto(cfg.protocol); + back = backend_from_proto(conf_get_int(conf, CONF_protocol)); if (back == NULL) { char *str = dupprintf("%s Internal Error", appname); MessageBox(NULL, "Unsupported protocol number found", str, MB_OK | MB_ICONEXCLAMATION); sfree(str); cleanup_exit(1); } - error = back->init(NULL, &backhandle, &cfg, - cfg.host, cfg.port, &realhost, cfg.tcp_nodelay, - cfg.tcp_keepalives); + error = back->init(NULL, &backhandle, conf, + conf_get_str(conf, CONF_host), + conf_get_int(conf, CONF_port), + &realhost, + conf_get_int(conf, CONF_tcp_nodelay), + conf_get_int(conf, CONF_tcp_keepalives)); back->provide_logctx(backhandle, logctx); if (error) { char *str = dupprintf("%s Error", appname); sprintf(msg, "Unable to open connection to\n" - "%.800s\n" "%s", cfg_dest(&cfg), error); + "%.800s\n" "%s", conf_dest(conf), error); MessageBox(NULL, msg, str, MB_ICONERROR | MB_OK); sfree(str); exit(0); } window_name = icon_name = NULL; - if (*cfg.wintitle) { - title = cfg.wintitle; - } else { + title = conf_get_str(conf, CONF_wintitle); + if (!*title) { sprintf(msg, "%s - %s", realhost, appname); title = msg; } sfree(realhost); set_title(NULL, title); @@ -261,11 +279,11 @@ term_provide_resize_fn(term, back->size, backhandle); /* * Set up a line discipline. */ - ldisc = ldisc_create(&cfg, term, back, backhandle, NULL); + ldisc = ldisc_create(conf, term, back, backhandle, NULL); /* * Destroy the Restart Session menu item. (This will return * failure if it's already absent, as it will be the very first * time we call this function. We ignore that, because as long @@ -274,15 +292,14 @@ */ for (i = 0; i < lenof(popup_menus); i++) { DeleteMenu(popup_menus[i].menu, IDM_RESTART, MF_BYCOMMAND); } - must_close_session = FALSE; session_closed = FALSE; } -static void close_session(void) +static void close_session(void *ignored_context) { char morestuff[100]; int i; session_closed = TRUE; @@ -309,19 +326,10 @@ for (i = 0; i < lenof(popup_menus); i++) { DeleteMenu(popup_menus[i].menu, IDM_RESTART, MF_BYCOMMAND); InsertMenu(popup_menus[i].menu, IDM_DUPSESS, MF_BYCOMMAND | MF_ENABLED, IDM_RESTART, "&Restart Session"); } - - /* - * Unset the 'must_close_session' flag, or else we'll come - * straight back here the next time we go round the main message - * loop - which, worse still, will be immediately (without - * blocking) because we've just triggered a WM_SETTEXT by the - * window title change above. - */ - must_close_session = FALSE; } int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) { WNDCLASS wndclass; @@ -360,11 +368,13 @@ osVersion.dwPlatformId != VER_PLATFORM_WIN32_NT)) wm_mousewheel = RegisterWindowMessage("MSWHEEL_ROLLMSG"); init_help(); - init_flashwindow(); + init_winfuncs(); + + conf = conf_new(); /* * Initialize COM. */ hr = CoInitialize(NULL); @@ -393,13 +403,13 @@ Backend *b = backend_from_proto(default_protocol); default_port = 0; /* illegal */ if (b) default_port = b->default_port; } - cfg.logtype = LGTYP_NONE; + conf_set_int(conf, CONF_logtype, LGTYP_NONE); - do_defaults(NULL, &cfg); + do_defaults(NULL, conf); p = cmdline; /* * Process a couple of command-line options which are more @@ -420,28 +430,29 @@ */ int i = strlen(p); while (i > 1 && isspace(p[i - 1])) i--; p[i] = '\0'; - do_defaults(p + 1, &cfg); - if (!cfg_launchable(&cfg) && !do_config()) { + do_defaults(p + 1, conf); + if (!conf_launchable(conf) && !do_config()) { cleanup_exit(0); } allow_launch = TRUE; /* allow it to be launched directly */ } else if (*p == '&') { /* * An initial & means we've been given a command line * containing the hex value of a HANDLE for a file - * mapping object, which we must then extract as a - * config. + * mapping object, which we must then interpret as a + * serialised Conf. */ HANDLE filemap; - Config *cp; - if (sscanf(p + 1, "%p", &filemap) == 1 && + void *cp; + unsigned cpsize; + if (sscanf(p + 1, "%p:%u", &filemap, &cpsize) == 2 && (cp = MapViewOfFile(filemap, FILE_MAP_READ, - 0, 0, sizeof(Config))) != NULL) { - cfg = *cp; + 0, 0, cpsize)) != NULL) { + conf_deserialise(conf, cp, cpsize); UnmapViewOfFile(cp); CloseHandle(filemap); } else if (!do_config()) { cleanup_exit(0); } @@ -459,11 +470,11 @@ for (i = 0; i < argc; i++) { char *p = argv[i]; int ret; ret = cmdline_process_param(p, i+1 r.right - r.left) guess_width = r.right - r.left; @@ -672,49 +689,51 @@ } { int winmode = WS_OVERLAPPEDWINDOW | WS_VSCROLL; int exwinmode = 0; - if (!cfg.scrollbar) + if (!conf_get_int(conf, CONF_scrollbar)) winmode &= ~(WS_VSCROLL); - if (cfg.resize_action == RESIZE_DISABLED) + if (conf_get_int(conf, CONF_resize_action) == RESIZE_DISABLED) winmode &= ~(WS_THICKFRAME | WS_MAXIMIZEBOX); - if (cfg.alwaysontop) + if (conf_get_int(conf, CONF_alwaysontop)) exwinmode |= WS_EX_TOPMOST; - if (cfg.sunken_edge) + if (conf_get_int(conf, CONF_sunken_edge)) exwinmode |= WS_EX_CLIENTEDGE; hwnd = CreateWindowEx(exwinmode, appname, appname, winmode, CW_USEDEFAULT, CW_USEDEFAULT, guess_width, guess_height, NULL, NULL, inst, NULL); } + /* + * Initialise the fonts, simultaneously correcting the guesses + * for font_{width,height}. + */ + init_fonts(0,0); + /* * Initialise the terminal. (We have to do this _after_ * creating the window, since the terminal is the first thing * which will call schedule_timer(), which will in turn call * timer_change_notify() which will expect hwnd to exist.) */ - term = term_init(&cfg, &ucsdata, NULL); - logctx = log_init(NULL, &cfg); + term = term_init(conf, &ucsdata, NULL); + logctx = log_init(NULL, conf); term_provide_logctx(term, logctx); - term_size(term, cfg.height, cfg.width, cfg.savelines); - - /* - * Initialise the fonts, simultaneously correcting the guesses - * for font_{width,height}. - */ - init_fonts(0,0); + term_size(term, conf_get_int(conf, CONF_height), + conf_get_int(conf, CONF_width), + conf_get_int(conf, CONF_savelines)); /* * Correct the guesses for extra_{width,height}. */ { RECT cr, wr; GetWindowRect(hwnd, &wr); GetClientRect(hwnd, &cr); - offset_width = offset_height = cfg.window_border; + offset_width = offset_height = conf_get_int(conf, CONF_window_border); extra_width = wr.right - wr.left - cr.right + cr.left + offset_width*2; extra_height = wr.bottom - wr.top - cr.bottom + cr.top +offset_height*2; } /* @@ -791,12 +810,13 @@ AppendMenu(m, MF_SEPARATOR, 0, 0); AppendMenu(m, MF_ENABLED, IDM_COPYALL, "C&opy All to Clipboard"); AppendMenu(m, MF_ENABLED, IDM_CLRSB, "C&lear Scrollback"); AppendMenu(m, MF_ENABLED, IDM_RESET, "Rese&t Terminal"); AppendMenu(m, MF_SEPARATOR, 0, 0); - AppendMenu(m, (cfg.resize_action == RESIZE_DISABLED) ? - MF_GRAYED : MF_ENABLED, IDM_FULLSCREEN, "&Full Screen"); + AppendMenu(m, (conf_get_int(conf, CONF_resize_action) + == RESIZE_DISABLED) ? MF_GRAYED : MF_ENABLED, + IDM_FULLSCREEN, "&Full Screen"); AppendMenu(m, MF_SEPARATOR, 0, 0); if (has_help()) AppendMenu(m, MF_ENABLED, IDM_HELP, "&Help"); str = dupprintf("&About %s", appname); AppendMenu(m, MF_ENABLED, IDM_ABOUT, str); @@ -828,47 +848,58 @@ UpdateWindow(hwnd); while (1) { HANDLE *handles; int nhandles, n; + DWORD timeout; + + if (toplevel_callback_pending() || + PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE)) { + /* + * If we have anything we'd like to do immediately, set + * the timeout for MsgWaitForMultipleObjects to zero so + * that we'll only do a quick check of our handles and + * then get on with whatever that was. + * + * One such option is a pending toplevel callback. The + * other is a non-empty Windows message queue, which you'd + * think we could leave to MsgWaitForMultipleObjects to + * check for us along with all the handles, but in fact we + * can't because once PeekMessage in one iteration of this + * loop has removed a message from the queue, the whole + * queue is considered uninteresting by the next + * invocation of MWFMO. So we check ourselves whether the + * message queue is non-empty, and if so, set this timeout + * to zero to ensure MWFMO doesn't block. + */ + timeout = 0; + } else { + timeout = INFINITE; + /* The messages seem unreliable; especially if we're being tricky */ + term_set_focus(term, GetForegroundWindow() == hwnd); + } handles = handle_get_events(&nhandles); - n = MsgWaitForMultipleObjects(nhandles, handles, FALSE, INFINITE, - QS_ALLINPUT); + n = MsgWaitForMultipleObjects(nhandles, handles, FALSE, + timeout, QS_ALLINPUT); if ((unsigned)(n - WAIT_OBJECT_0) < (unsigned)nhandles) { handle_got_event(handles[n - WAIT_OBJECT_0]); sfree(handles); - if (must_close_session) - close_session(); } else sfree(handles); - while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { + if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { if (msg.message == WM_QUIT) goto finished; /* two-level break */ if (!(IsWindow(logbox) && IsDialogMessage(logbox, &msg))) DispatchMessage(&msg); - /* Send the paste buffer if there's anything to send */ - term_paste(term); - /* If there's nothing new in the queue then we can do everything - * we've delayed, reading the socket, writing, and repainting - * the window. - */ - if (must_close_session) - close_session(); - } - - /* The messages seem unreliable; especially if we're being tricky */ - term_set_focus(term, GetForegroundWindow() == hwnd); - - if (pending_netevent) - enact_pending_netevent(); - - net_pending_errors(); + } + + run_toplevel_callbacks(); } finished: cleanup_exit(msg.wParam); /* this doesn't return... */ return msg.wParam; /* ... but optimiser doesn't know */ @@ -886,11 +917,11 @@ sfree(logpal); if (pal) DeleteObject(pal); sk_cleanup(); - if (cfg.protocol == PROT_SSH) { + if (conf_get_int(conf, CONF_protocol) == PROT_SSH) { random_save_seed(); #ifdef MSCRYPTOAPI crypto_wrapup(); #endif } @@ -1064,11 +1095,11 @@ /* * set or clear the "raw mouse message" mode */ void set_raw_mouse_mode(void *frontend, int activate) { - activate = activate && !cfg.no_mouse_rep; + activate = activate && !conf_get_int(conf, CONF_no_mouse_rep); send_raw_mouse = activate; update_mouse_pointer(); } /* @@ -1084,14 +1115,14 @@ va_end(ap); sprintf(morestuff, "%.70s Fatal Error", appname); MessageBox(hwnd, stuff, morestuff, MB_ICONERROR | MB_OK); sfree(stuff); - if (cfg.close_on_exit == FORCE_ON) + if (conf_get_int(conf, CONF_close_on_exit) == FORCE_ON) PostQuitMessage(1); else { - must_close_session = TRUE; + queue_toplevel_callback(close_session, NULL); } } /* * Report an error at the command-line parsing stage. @@ -1111,30 +1142,22 @@ } /* * Actually do the job requested by a WM_NETEVENT */ -static void enact_pending_netevent(void) -{ - static int reentering = 0; - extern int select_result(WPARAM, LPARAM); - - if (reentering) - return; /* don't unpend the pending */ - - pending_netevent = FALSE; - - reentering = 1; - select_result(pend_netevent_wParam, pend_netevent_lParam); - reentering = 0; +static void wm_netevent_callback(void *vctx) +{ + struct wm_netevent_params *params = (struct wm_netevent_params *)vctx; + select_result(params->wParam, params->lParam); + sfree(vctx); } /* * Copy the colour palette from the configuration data into defpal. * This is non-trivial because the colour indices are different. */ -static void cfgtopalette(void) +static void conftopalette(void) { int i; static const int ww[] = { 256, 257, 258, 259, 260, 261, 0, 8, 1, 9, 2, 10, 3, 11, @@ -1141,13 +1164,13 @@ 4, 12, 5, 13, 6, 14, 7, 15 }; for (i = 0; i < 22; i++) { int w = ww[i]; - defpal[w].rgbtRed = cfg.colours[i][0]; - defpal[w].rgbtGreen = cfg.colours[i][1]; - defpal[w].rgbtBlue = cfg.colours[i][2]; + defpal[w].rgbtRed = conf_get_int_int(conf, CONF_colours, i*3+0); + defpal[w].rgbtGreen = conf_get_int_int(conf, CONF_colours, i*3+1); + defpal[w].rgbtBlue = conf_get_int_int(conf, CONF_colours, i*3+2); } for (i = 0; i < NEXTCOLOURS; i++) { if (i < 216) { int r = i / 36, g = (i / 6) % 6, b = i % 6; defpal[i+16].rgbtRed = r ? r * 40 + 55 : 0; @@ -1160,11 +1183,11 @@ defpal[i+16].rgbtBlue = shade; } } /* Override with system colours if appropriate */ - if (cfg.system_colour) + if (conf_get_int(conf, CONF_system_colour)) systopalette(); } /* * Override bit of defpal with colours from the system. @@ -1200,11 +1223,12 @@ static void init_palette(void) { int i; HDC hdc = GetDC(hwnd); if (hdc) { - if (cfg.try_palette && GetDeviceCaps(hdc, RASTERCAPS) & RC_PALETTE) { + if (conf_get_int(conf, CONF_try_palette) && + GetDeviceCaps(hdc, RASTERCAPS) & RC_PALETTE) { /* * This is a genuine case where we must use smalloc * because the snew macros can't cope. */ logpal = smalloc(sizeof(*logpal) @@ -1383,22 +1407,27 @@ */ static void init_fonts(int pick_width, int pick_height) { TEXTMETRIC tm; CPINFO cpinfo; + FontSpec *font; int fontsize[3]; int i; + int quality; HDC hdc; int fw_dontcare, fw_bold; for (i = 0; i < FONT_MAXNO; i++) fonts[i] = NULL; - bold_mode = cfg.bold_colour ? BOLD_COLOURS : BOLD_FONT; + bold_font_mode = conf_get_int(conf, CONF_bold_style) & 1 ? + BOLD_FONT : BOLD_NONE; + bold_colours = conf_get_int(conf, CONF_bold_style) & 2 ? TRUE : FALSE; und_mode = UND_FONT; - if (cfg.font.isbold) { + font = conf_get_fontspec(conf, CONF_font); + if (font->isbold) { fw_dontcare = FW_BOLD; fw_bold = FW_HEAVY; } else { fw_dontcare = FW_DONTCARE; fw_bold = FW_BOLD; @@ -1407,25 +1436,26 @@ hdc = GetDC(hwnd); if (pick_height) font_height = pick_height; else { - font_height = cfg.font.height; + font_height = font->height; if (font_height > 0) { font_height = -MulDiv(font_height, GetDeviceCaps(hdc, LOGPIXELSY), 72); } } font_width = pick_width; + quality = conf_get_int(conf, CONF_font_quality); #define f(i,c,w,u) \ fonts[i] = CreateFont (font_height, font_width, 0, 0, w, FALSE, u, FALSE, \ c, OUT_DEFAULT_PRECIS, \ - CLIP_DEFAULT_PRECIS, FONT_QUALITY(cfg.font_quality), \ - FIXED_PITCH | FF_DONTCARE, cfg.font.name) + CLIP_DEFAULT_PRECIS, FONT_QUALITY(quality), \ + FIXED_PITCH | FF_DONTCARE, font->name) - f(FONT_NORMAL, cfg.font.charset, fw_dontcare, FALSE); + f(FONT_NORMAL, font->charset, fw_dontcare, FALSE); SelectObject(hdc, fonts[FONT_NORMAL]); GetTextMetrics(hdc, &tm); GetObject(fonts[FONT_NORMAL], sizeof(LOGFONT), &lfont); @@ -1464,11 +1494,11 @@ GetCPInfo(ucsdata.font_codepage, &cpinfo); ucsdata.dbcs_screenfont = (cpinfo.MaxCharSize > 1); } - f(FONT_UNDERLINE, cfg.font.charset, fw_dontcare, TRUE); + f(FONT_UNDERLINE, font->charset, fw_dontcare, TRUE); /* * Some fonts, e.g. 9-pt Courier, draw their underlines * outside their character cell. We successfully prevent * screen corruption by clipping the text output, but then @@ -1514,12 +1544,12 @@ DeleteObject(fonts[FONT_UNDERLINE]); fonts[FONT_UNDERLINE] = 0; } } - if (bold_mode == BOLD_FONT) { - f(FONT_BOLD, cfg.font.charset, fw_bold, FALSE); + if (bold_font_mode == BOLD_FONT) { + f(FONT_BOLD, font->charset, fw_bold, FALSE); } #undef f descent = tm.tmAscent + 1; if (descent >= font_height) @@ -1541,47 +1571,50 @@ und_mode = UND_LINE; DeleteObject(fonts[FONT_UNDERLINE]); fonts[FONT_UNDERLINE] = 0; } - if (bold_mode == BOLD_FONT && + if (bold_font_mode == BOLD_FONT && fontsize[FONT_BOLD] != fontsize[FONT_NORMAL]) { - bold_mode = BOLD_SHADOW; + bold_font_mode = BOLD_SHADOW; DeleteObject(fonts[FONT_BOLD]); fonts[FONT_BOLD] = 0; } fontflag[0] = fontflag[1] = fontflag[2] = 1; - init_ucs(&cfg, &ucsdata); + init_ucs(conf, &ucsdata); } static void another_font(int fontno) { int basefont; - int fw_dontcare, fw_bold; + int fw_dontcare, fw_bold, quality; int c, u, w, x; char *s; + FontSpec *font; if (fontno < 0 || fontno >= FONT_MAXNO || fontflag[fontno]) return; basefont = (fontno & ~(FONT_BOLDUND)); if (basefont != fontno && !fontflag[basefont]) another_font(basefont); - if (cfg.font.isbold) { + font = conf_get_fontspec(conf, CONF_font); + + if (font->isbold) { fw_dontcare = FW_BOLD; fw_bold = FW_HEAVY; } else { fw_dontcare = FW_DONTCARE; fw_bold = FW_BOLD; } - c = cfg.font.charset; + c = font->charset; w = fw_dontcare; u = FALSE; - s = cfg.font.name; + s = font->name; x = font_width; if (fontno & FONT_WIDE) x *= 2; if (fontno & FONT_NARROW) @@ -1591,14 +1624,16 @@ if (fontno & FONT_BOLD) w = fw_bold; if (fontno & FONT_UNDERLINE) u = TRUE; + quality = conf_get_int(conf, CONF_font_quality); + fonts[fontno] = CreateFont(font_height * (1 + !!(fontno & FONT_HIGH)), x, 0, 0, w, FALSE, u, FALSE, c, OUT_DEFAULT_PRECIS, - CLIP_DEFAULT_PRECIS, FONT_QUALITY(cfg.font_quality), + CLIP_DEFAULT_PRECIS, FONT_QUALITY(quality), DEFAULT_PITCH | FF_DONTCARE, s); fontflag[fontno] = 1; } @@ -1617,15 +1652,15 @@ { int width, height; /* If the window is maximized supress resizing attempts */ if (IsZoomed(hwnd)) { - if (cfg.resize_action == RESIZE_TERM) + if (conf_get_int(conf, CONF_resize_action) == RESIZE_TERM) return; } - if (cfg.resize_action == RESIZE_DISABLED) return; + if (conf_get_int(conf, CONF_resize_action) == RESIZE_DISABLED) return; if (h == term->rows && w == term->cols) return; /* Sanity checks ... */ { static int first_time = 1; @@ -1652,13 +1687,14 @@ if (h < 1) h = 1; } } - term_size(term, h, w, cfg.savelines); + term_size(term, h, w, conf_get_int(conf, CONF_savelines)); - if (cfg.resize_action != RESIZE_FONT && !IsZoomed(hwnd)) { + if (conf_get_int(conf, CONF_resize_action) != RESIZE_FONT && + !IsZoomed(hwnd)) { width = extra_width + font_width * w; height = extra_height + font_height * h; SetWindowPos(hwnd, NULL, 0, 0, width, height, SWP_NOACTIVATE | SWP_NOCOPYBITS | @@ -1675,11 +1711,11 @@ * user changes something. * * This function doesn't like to change the terminal size but if the * font size is locked that may be it's only soluion. */ - int win_width, win_height; + int win_width, win_height, resize_action, window_border; RECT cr, wr; #ifdef RDB_DEBUG_PATCH debug((27, "reset_window()")); #endif @@ -1689,11 +1725,15 @@ GetClientRect(hwnd, &cr); win_width = cr.right - cr.left; win_height = cr.bottom - cr.top; - if (cfg.resize_action == RESIZE_DISABLED) reinit = 2; + resize_action = conf_get_int(conf, CONF_resize_action); + window_border = conf_get_int(conf, CONF_window_border); + + if (resize_action == RESIZE_DISABLED) + reinit = 2; /* Are we being forced to reload the fonts ? */ if (reinit>1) { #ifdef RDB_DEBUG_PATCH debug((27, "reset_window() -- Forced deinit")); @@ -1724,13 +1764,13 @@ */ extra_width = wr.right - wr.left - cr.right + cr.left; extra_height = wr.bottom - wr.top - cr.bottom + cr.top; - if (cfg.resize_action != RESIZE_TERM) { - if ( font_width != win_width/term->cols || - font_height != win_height/term->rows) { + if (resize_action != RESIZE_TERM) { + if (font_width != win_width/term->cols || + font_height != win_height/term->rows) { deinit_fonts(); init_fonts(win_width/term->cols, win_height/term->rows); offset_width = (win_width-font_width*term->cols)/2; offset_height = (win_height-font_height*term->rows)/2; InvalidateRect(hwnd, NULL, TRUE); @@ -1738,17 +1778,17 @@ debug((25, "reset_window() -> Z font resize to (%d, %d)", font_width, font_height)); #endif } } else { - if ( font_width * term->cols != win_width || - font_height * term->rows != win_height) { + if (font_width * term->cols != win_width || + font_height * term->rows != win_height) { /* Our only choice at this point is to change the * size of the terminal; Oh well. */ term_size(term, win_height/font_height, win_width/font_width, - cfg.savelines); + conf_get_int(conf, CONF_savelines)); offset_width = (win_width-font_width*term->cols)/2; offset_height = (win_height-font_height*term->rows)/2; InvalidateRect(hwnd, NULL, TRUE); #ifdef RDB_DEBUG_PATCH debug((27, "reset_window() -> Zoomed term_size")); @@ -1764,11 +1804,11 @@ if (reinit>0) { #ifdef RDB_DEBUG_PATCH debug((27, "reset_window() -> Forced re-init")); #endif - offset_width = offset_height = cfg.window_border; + offset_width = offset_height = window_border; extra_width = wr.right - wr.left - cr.right + cr.left + offset_width*2; extra_height = wr.bottom - wr.top - cr.bottom + cr.top +offset_height*2; if (win_width != font_width*term->cols + offset_width*2 || win_height != font_height*term->rows + offset_height*2) { @@ -1789,14 +1829,14 @@ /* Okay the user doesn't want us to change the font so we try the * window. But that may be too big for the screen which forces us * to change the terminal. */ - if ((cfg.resize_action == RESIZE_TERM && reinit<=0) || - (cfg.resize_action == RESIZE_EITHER && reinit<0) || + if ((resize_action == RESIZE_TERM && reinit<=0) || + (resize_action == RESIZE_EITHER && reinit<0) || reinit>0) { - offset_width = offset_height = cfg.window_border; + offset_width = offset_height = window_border; extra_width = wr.right - wr.left - cr.right + cr.left + offset_width*2; extra_height = wr.bottom - wr.top - cr.bottom + cr.top +offset_height*2; if (win_width != font_width*term->cols + offset_width*2 || win_height != font_height*term->rows + offset_height*2) { @@ -1809,11 +1849,11 @@ width = (ss.right - ss.left - extra_width) / font_width; height = (ss.bottom - ss.top - extra_height) / font_height; /* Grrr too big */ if ( term->rows > height || term->cols > width ) { - if (cfg.resize_action == RESIZE_EITHER) { + if (resize_action == RESIZE_EITHER) { /* Make the font the biggest we can */ if (term->cols > width) font_width = (ss.right - ss.left - extra_width) / term->cols; if (term->rows > height) @@ -1826,11 +1866,12 @@ width = (ss.right - ss.left - extra_width) / font_width; height = (ss.bottom - ss.top - extra_height) / font_height; } else { if ( height > term->rows ) height = term->rows; if ( width > term->cols ) width = term->cols; - term_size(term, height, width, cfg.savelines); + term_size(term, height, width, + conf_get_int(conf, CONF_savelines)); #ifdef RDB_DEBUG_PATCH debug((27, "reset_window() -> term resize to (%d,%d)", height, width)); #endif } @@ -1851,16 +1892,16 @@ return; } /* We're allowed to or must change the font but do we want to ? */ - if (font_width != (win_width-cfg.window_border*2)/term->cols || - font_height != (win_height-cfg.window_border*2)/term->rows) { + if (font_width != (win_width-window_border*2)/term->cols || + font_height != (win_height-window_border*2)/term->rows) { deinit_fonts(); - init_fonts((win_width-cfg.window_border*2)/term->cols, - (win_height-cfg.window_border*2)/term->rows); + init_fonts((win_width-window_border*2)/term->cols, + (win_height-window_border*2)/term->rows); offset_width = (win_width-font_width*term->cols)/2; offset_height = (win_height-font_height*term->rows)/2; extra_width = wr.right - wr.left - cr.right + cr.left +offset_width*2; extra_height = wr.bottom - wr.top - cr.bottom + cr.top+offset_height*2; @@ -1885,11 +1926,12 @@ static void click(Mouse_Button b, int x, int y, int shift, int ctrl, int alt) { int thistime = GetMessageTime(); - if (send_raw_mouse && !(cfg.mouse_override && shift)) { + if (send_raw_mouse && + !(shift && conf_get_int(conf, CONF_mouse_override))) { lastbtn = MBT_NOTHING; term_mouse(term, b, translate_button(b), MA_CLICK, x, y, shift, ctrl, alt); return; } @@ -1915,23 +1957,25 @@ static Mouse_Button translate_button(Mouse_Button button) { if (button == MBT_LEFT) return MBT_SELECT; if (button == MBT_MIDDLE) - return cfg.mouse_is_xterm == 1 ? MBT_PASTE : MBT_EXTEND; + return conf_get_int(conf, CONF_mouse_is_xterm) == 1 ? + MBT_PASTE : MBT_EXTEND; if (button == MBT_RIGHT) - return cfg.mouse_is_xterm == 1 ? MBT_EXTEND : MBT_PASTE; + return conf_get_int(conf, CONF_mouse_is_xterm) == 1 ? + MBT_EXTEND : MBT_PASTE; return 0; /* shouldn't happen */ } static void show_mouseptr(int show) { /* NB that the counter in ShowCursor() is also frobbed by * update_mouse_pointer() */ static int cursor_visible = 1; - if (!cfg.hide_mouseptr) /* override if this feature disabled */ - show = 1; + if (!conf_get_int(conf, CONF_hide_mouseptr)) + show = 1; /* override if this feature disabled */ if (cursor_visible && !show) ShowCursor(FALSE); else if (!cursor_visible && show) ShowCursor(TRUE); cursor_visible = show; @@ -1952,21 +1996,22 @@ static int resizing; void notify_remote_exit(void *fe) { - int exitcode; + int exitcode, close_on_exit; if (!session_closed && (exitcode = back->exitcode(backhandle)) >= 0) { + close_on_exit = conf_get_int(conf, CONF_close_on_exit); /* Abnormal exits will already have set session_closed and taken * appropriate action. */ - if (cfg.close_on_exit == FORCE_ON || - (cfg.close_on_exit == AUTO && exitcode != INT_MAX)) { + if (close_on_exit == FORCE_ON || + (close_on_exit == AUTO && exitcode != INT_MAX)) { PostQuitMessage(0); } else { - must_close_session = TRUE; + queue_toplevel_callback(close_session, NULL); session_closed = TRUE; /* exitcode == INT_MAX indicates that the connection was closed * by a fatal error, so an error box will be coming our way and * we should not generate this informational one. */ if (exitcode != INT_MAX) @@ -1974,18 +2019,29 @@ appname, MB_OK | MB_ICONINFORMATION); } } } -void timer_change_notify(long next) +void timer_change_notify(unsigned long next) { - long ticks = next - GETTICKCOUNT(); - if (ticks <= 0) ticks = 1; /* just in case */ + unsigned long now = GETTICKCOUNT(); + long ticks; + if (now - next < INT_MAX) + ticks = 0; + else + ticks = next - now; KillTimer(hwnd, TIMING_TIMER_ID); SetTimer(hwnd, TIMING_TIMER_ID, ticks, NULL); timing_next_time = next; } + +static void conf_cache_data(void) +{ + /* Cache some items from conf to speed lookups in very hot code */ + cursor_type = conf_get_int(conf, CONF_cursor_type); + vtmode = conf_get_int(conf, CONF_vtmode); +} static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { HDC hdc; @@ -1992,15 +2048,16 @@ static int ignore_clip = FALSE; static int need_backend_resize = FALSE; static int fullscr_on_max = FALSE; static int processed_resize = FALSE; static UINT last_mousemove = 0; + int resize_action; switch (message) { case WM_TIMER: if ((UINT_PTR)wParam == TIMING_TIMER_ID) { - long next; + unsigned long next; KillTimer(hwnd, TIMING_TIMER_ID); if (run_timers(timing_next_time, &next)) { timer_change_notify(next); } else { @@ -2012,11 +2069,11 @@ case WM_CLOSE: { char *str; show_mouseptr(1); str = dupprintf("%s Exit Confirmation", appname); - if (!cfg.warn_on_close || session_closed || + if (session_closed || !conf_get_int(conf, CONF_warn_on_close) || MessageBox(hwnd, "Are you sure you want to close this session?", str, MB_ICONWARNING | MB_OKCANCEL | MB_DEFBUTTON1) == IDOK) DestroyWindow(hwnd); @@ -2059,30 +2116,31 @@ /* * Allocate a file-mapping memory chunk for the * config structure. */ SECURITY_ATTRIBUTES sa; - Config *p; + void *p; + int size; + + size = conf_serialised_size(conf); sa.nLength = sizeof(sa); sa.lpSecurityDescriptor = NULL; sa.bInheritHandle = TRUE; filemap = CreateFileMapping(INVALID_HANDLE_VALUE, &sa, PAGE_READWRITE, - 0, sizeof(Config), NULL); + 0, size, NULL); if (filemap && filemap != INVALID_HANDLE_VALUE) { - p = (Config *) MapViewOfFile(filemap, - FILE_MAP_WRITE, - 0, 0, sizeof(Config)); + p = MapViewOfFile(filemap, FILE_MAP_WRITE, 0, 0, size); if (p) { - *p = cfg; /* structure copy */ + conf_serialise(conf, p); UnmapViewOfFile(p); } } inherit_handles = TRUE; - sprintf(c, "putty &%p", filemap); + sprintf(c, "putty &%p:%u", filemap, (unsigned)size); cl = c; } else if (wParam == IDM_SAVEDSESS) { unsigned int sessno = ((lParam - IDM_SAVED_MIN) / MENU_SAVED_STEP) + 1; if (sessno < (unsigned)sesslist.nsessions) { @@ -2105,10 +2163,12 @@ si.dwFlags = 0; si.cbReserved2 = 0; si.lpReserved2 = NULL; CreateProcess(b, cl, NULL, NULL, inherit_handles, NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi); + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); if (filemap) CloseHandle(filemap); if (freecl) sfree(cl); @@ -2122,113 +2182,131 @@ } break; case IDM_RECONF: { - Config prev_cfg; + Conf *prev_conf; int init_lvl = 1; int reconfig_result; if (reconfiguring) break; else reconfiguring = TRUE; - GetWindowText(hwnd, cfg.wintitle, sizeof(cfg.wintitle)); - prev_cfg = cfg; + /* + * Copy the current window title into the stored + * previous configuration, so that doing nothing to + * the window title field in the config box doesn't + * reset the title to its startup state. + */ + conf_set_str(conf, CONF_wintitle, window_name); + + prev_conf = conf_copy(conf); reconfig_result = do_reconfig(hwnd, back ? back->cfg_info(backhandle) : 0); reconfiguring = FALSE; - if (!reconfig_result) + if (!reconfig_result) { + conf_free(prev_conf); break; + } + conf_cache_data(); + + resize_action = conf_get_int(conf, CONF_resize_action); { /* Disable full-screen if resizing forbidden */ int i; for (i = 0; i < lenof(popup_menus); i++) EnableMenuItem(popup_menus[i].menu, IDM_FULLSCREEN, MF_BYCOMMAND | - (cfg.resize_action == RESIZE_DISABLED) + (resize_action == RESIZE_DISABLED) ? MF_GRAYED : MF_ENABLED); /* Gracefully unzoom if necessary */ - if (IsZoomed(hwnd) && - (cfg.resize_action == RESIZE_DISABLED)) { + if (IsZoomed(hwnd) && (resize_action == RESIZE_DISABLED)) ShowWindow(hwnd, SW_RESTORE); - } } /* Pass new config data to the logging module */ - log_reconfig(logctx, &cfg); + log_reconfig(logctx, conf); sfree(logpal); /* * Flush the line discipline's edit buffer in the * case where local editing has just been disabled. */ + ldisc_configure(ldisc, conf); if (ldisc) ldisc_send(ldisc, NULL, 0, 0); if (pal) DeleteObject(pal); logpal = NULL; pal = NULL; - cfgtopalette(); + conftopalette(); init_palette(); /* Pass new config data to the terminal */ - term_reconfig(term, &cfg); + term_reconfig(term, conf); /* Pass new config data to the back end */ if (back) - back->reconfig(backhandle, &cfg); + back->reconfig(backhandle, conf); /* Screen size changed ? */ - if (cfg.height != prev_cfg.height || - cfg.width != prev_cfg.width || - cfg.savelines != prev_cfg.savelines || - cfg.resize_action == RESIZE_FONT || - (cfg.resize_action == RESIZE_EITHER && IsZoomed(hwnd)) || - cfg.resize_action == RESIZE_DISABLED) - term_size(term, cfg.height, cfg.width, cfg.savelines); + if (conf_get_int(conf, CONF_height) != + conf_get_int(prev_conf, CONF_height) || + conf_get_int(conf, CONF_width) != + conf_get_int(prev_conf, CONF_width) || + conf_get_int(conf, CONF_savelines) != + conf_get_int(prev_conf, CONF_savelines) || + resize_action == RESIZE_FONT || + (resize_action == RESIZE_EITHER && IsZoomed(hwnd)) || + resize_action == RESIZE_DISABLED) + term_size(term, conf_get_int(conf, CONF_height), + conf_get_int(conf, CONF_width), + conf_get_int(conf, CONF_savelines)); /* Enable or disable the scroll bar, etc */ { LONG nflg, flag = GetWindowLongPtr(hwnd, GWL_STYLE); LONG nexflag, exflag = GetWindowLongPtr(hwnd, GWL_EXSTYLE); nexflag = exflag; - if (cfg.alwaysontop != prev_cfg.alwaysontop) { - if (cfg.alwaysontop) { + if (conf_get_int(conf, CONF_alwaysontop) != + conf_get_int(prev_conf, CONF_alwaysontop)) { + if (conf_get_int(conf, CONF_alwaysontop)) { nexflag |= WS_EX_TOPMOST; SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); } else { nexflag &= ~(WS_EX_TOPMOST); SetWindowPos(hwnd, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); } } - if (cfg.sunken_edge) + if (conf_get_int(conf, CONF_sunken_edge)) nexflag |= WS_EX_CLIENTEDGE; else nexflag &= ~(WS_EX_CLIENTEDGE); nflg = flag; - if (is_full_screen() ? - cfg.scrollbar_in_fullscreen : cfg.scrollbar) + if (conf_get_int(conf, is_full_screen() ? + CONF_scrollbar_in_fullscreen : + CONF_scrollbar)) nflg |= WS_VSCROLL; else nflg &= ~WS_VSCROLL; - if (cfg.resize_action == RESIZE_DISABLED || + if (resize_action == RESIZE_DISABLED || is_full_screen()) nflg &= ~WS_THICKFRAME; else nflg |= WS_THICKFRAME; - if (cfg.resize_action == RESIZE_DISABLED) + if (resize_action == RESIZE_DISABLED) nflg &= ~WS_MAXIMIZEBOX; else nflg |= WS_MAXIMIZEBOX; if (nflg != flag || nexflag != exflag) { @@ -2245,38 +2323,50 @@ init_lvl = 2; } } /* Oops */ - if (cfg.resize_action == RESIZE_DISABLED && IsZoomed(hwnd)) { + if (resize_action == RESIZE_DISABLED && IsZoomed(hwnd)) { force_normal(hwnd); init_lvl = 2; } - set_title(NULL, cfg.wintitle); + set_title(NULL, conf_get_str(conf, CONF_wintitle)); if (IsIconic(hwnd)) { SetWindowText(hwnd, - cfg.win_name_always ? window_name : - icon_name); - } - - if (strcmp(cfg.font.name, prev_cfg.font.name) != 0 || - strcmp(cfg.line_codepage, prev_cfg.line_codepage) != 0 || - cfg.font.isbold != prev_cfg.font.isbold || - cfg.font.height != prev_cfg.font.height || - cfg.font.charset != prev_cfg.font.charset || - cfg.font_quality != prev_cfg.font_quality || - cfg.vtmode != prev_cfg.vtmode || - cfg.bold_colour != prev_cfg.bold_colour || - cfg.resize_action == RESIZE_DISABLED || - cfg.resize_action == RESIZE_EITHER || - (cfg.resize_action != prev_cfg.resize_action)) - init_lvl = 2; + conf_get_int(conf, CONF_win_name_always) ? + window_name : icon_name); + } + + { + FontSpec *font = conf_get_fontspec(conf, CONF_font); + FontSpec *prev_font = conf_get_fontspec(prev_conf, + CONF_font); + + if (!strcmp(font->name, prev_font->name) || + !strcmp(conf_get_str(conf, CONF_line_codepage), + conf_get_str(prev_conf, CONF_line_codepage)) || + font->isbold != prev_font->isbold || + font->height != prev_font->height || + font->charset != prev_font->charset || + conf_get_int(conf, CONF_font_quality) != + conf_get_int(prev_conf, CONF_font_quality) || + conf_get_int(conf, CONF_vtmode) != + conf_get_int(prev_conf, CONF_vtmode) || + conf_get_int(conf, CONF_bold_style) != + conf_get_int(prev_conf, CONF_bold_style) || + resize_action == RESIZE_DISABLED || + resize_action == RESIZE_EITHER || + resize_action != conf_get_int(prev_conf, + CONF_resize_action)) + init_lvl = 2; + } InvalidateRect(hwnd, NULL, TRUE); reset_window(init_lvl); - net_pending_errors(); + + conf_free(prev_conf); } break; case IDM_COPYALL: term_copyall(term); break; @@ -2333,11 +2423,10 @@ */ if (i >= n_specials) break; if (back) back->special(backhandle, specials[i].code); - net_pending_errors(); } } break; #define X_POS(l) ((int)(short)LOWORD(l)) @@ -2350,11 +2439,12 @@ case WM_RBUTTONDOWN: case WM_LBUTTONUP: case WM_MBUTTONUP: case WM_RBUTTONUP: if (message == WM_RBUTTONDOWN && - ((wParam & MK_CONTROL) || (cfg.mouse_is_xterm == 2))) { + ((wParam & MK_CONTROL) || + (conf_get_int(conf, CONF_mouse_is_xterm) == 2))) { POINT cursorpos; show_mouseptr(1); /* make sure pointer is visible */ GetCursorPos(&cursorpos); TrackPopupMenu(popup_menus[CTXMENU].menu, @@ -2612,24 +2702,24 @@ EndPaint(hwnd, &p); ShowCaret(hwnd); } return 0; case WM_NETEVENT: - /* Notice we can get multiple netevents, FD_READ, FD_WRITE etc - * but the only one that's likely to try to overload us is FD_READ. - * This means buffering just one is fine. - */ - if (pending_netevent) - enact_pending_netevent(); - - pending_netevent = TRUE; - pend_netevent_wParam = wParam; - pend_netevent_lParam = lParam; - if (WSAGETSELECTEVENT(lParam) != FD_READ) - enact_pending_netevent(); - - net_pending_errors(); + { + /* + * To protect against re-entrancy when Windows's recv() + * immediately triggers a new WSAAsyncSelect window + * message, we don't call select_result directly from this + * handler but instead wait until we're back out at the + * top level of the message loop. + */ + struct wm_netevent_params *params = + snew(struct wm_netevent_params); + params->wParam = wParam; + params->lParam = lParam; + queue_toplevel_callback(wm_netevent_callback, params); + } return 0; case WM_SETFOCUS: term_set_focus(term, TRUE); CreateCaret(hwnd, caretbm, font_width, font_height); ShowCaret(hwnd); @@ -2657,40 +2747,42 @@ resizing = FALSE; #ifdef RDB_DEBUG_PATCH debug((27, "WM_EXITSIZEMOVE")); #endif if (need_backend_resize) { - term_size(term, cfg.height, cfg.width, cfg.savelines); + term_size(term, conf_get_int(conf, CONF_height), + conf_get_int(conf, CONF_width), + conf_get_int(conf, CONF_savelines)); InvalidateRect(hwnd, NULL, TRUE); } break; case WM_SIZING: /* * This does two jobs: * 1) Keep the sizetip uptodate * 2) Make sure the window size is _stepped_ in units of the font size. */ - if (cfg.resize_action == RESIZE_TERM || - (cfg.resize_action == RESIZE_EITHER && !is_alt_pressed())) { + resize_action = conf_get_int(conf, CONF_resize_action); + if (resize_action == RESIZE_TERM || + (resize_action == RESIZE_EITHER && !is_alt_pressed())) { int width, height, w, h, ew, eh; LPRECT r = (LPRECT) lParam; - if ( !need_backend_resize && cfg.resize_action == RESIZE_EITHER && - (cfg.height != term->rows || cfg.width != term->cols )) { + if (!need_backend_resize && resize_action == RESIZE_EITHER && + (conf_get_int(conf, CONF_height) != term->rows || + conf_get_int(conf, CONF_width) != term->cols)) { /* * Great! It seems that both the terminal size and the * font size have been changed and the user is now dragging. * * It will now be difficult to get back to the configured * font size! * * This would be easier but it seems to be too confusing. - - term_size(term, cfg.height, cfg.width, cfg.savelines); - reset_window(2); */ - cfg.height=term->rows; cfg.width=term->cols; + conf_set_int(conf, CONF_height, term->rows); + conf_set_int(conf, CONF_width, term->cols); InvalidateRect(hwnd, NULL, TRUE); need_backend_resize = TRUE; } @@ -2723,12 +2815,13 @@ return 1; else return 0; } else { int width, height, w, h, rv = 0; - int ex_width = extra_width + (cfg.window_border - offset_width) * 2; - int ex_height = extra_height + (cfg.window_border - offset_height) * 2; + int window_border = conf_get_int(conf, CONF_window_border); + int ex_width = extra_width + (window_border - offset_width) * 2; + int ex_height = extra_height + (window_border - offset_height) * 2; LPRECT r = (LPRECT) lParam; width = r->right - r->left - ex_width; height = r->bottom - r->top - ex_height; @@ -2760,10 +2853,11 @@ break; case WM_MOVE: sys_cursor_update(); break; case WM_SIZE: + resize_action = conf_get_int(conf, CONF_resize_action); #ifdef RDB_DEBUG_PATCH debug((27, "WM_SIZE %s (%d,%d)", (wParam == SIZE_MINIMIZED) ? "SIZE_MINIMIZED": (wParam == SIZE_MAXIMIZED) ? "SIZE_MAXIMIZED": (wParam == SIZE_RESTORED && resizing) ? "to": @@ -2771,11 +2865,12 @@ "...", LOWORD(lParam), HIWORD(lParam))); #endif if (wParam == SIZE_MINIMIZED) SetWindowText(hwnd, - cfg.win_name_always ? window_name : icon_name); + conf_get_int(conf, CONF_win_name_always) ? + window_name : icon_name); if (wParam == SIZE_RESTORED || wParam == SIZE_MAXIMIZED) SetWindowText(hwnd, window_name); if (wParam == SIZE_RESTORED) { processed_resize = FALSE; clear_full_screen(); @@ -2804,54 +2899,69 @@ } } processed_resize = TRUE; - if (cfg.resize_action == RESIZE_DISABLED) { + if (resize_action == RESIZE_DISABLED) { /* A resize, well it better be a minimize. */ reset_window(-1); } else { int width, height, w, h; + int window_border = conf_get_int(conf, CONF_window_border); width = LOWORD(lParam); height = HIWORD(lParam); if (wParam == SIZE_MAXIMIZED && !was_zoomed) { was_zoomed = 1; prev_rows = term->rows; prev_cols = term->cols; - if (cfg.resize_action == RESIZE_TERM) { + if (resize_action == RESIZE_TERM) { w = width / font_width; if (w < 1) w = 1; h = height / font_height; if (h < 1) h = 1; - term_size(term, h, w, cfg.savelines); + if (resizing) { + /* + * As below, if we're in the middle of an + * interactive resize we don't call + * back->size. In Windows 7, this case can + * arise in maximisation as well via the Aero + * snap UI. + */ + need_backend_resize = TRUE; + conf_set_int(conf, CONF_height, h); + conf_set_int(conf, CONF_width, w); + } else { + term_size(term, h, w, + conf_get_int(conf, CONF_savelines)); + } } reset_window(0); } else if (wParam == SIZE_RESTORED && was_zoomed) { was_zoomed = 0; - if (cfg.resize_action == RESIZE_TERM) { - w = (width-cfg.window_border*2) / font_width; + if (resize_action == RESIZE_TERM) { + w = (width-window_border*2) / font_width; if (w < 1) w = 1; - h = (height-cfg.window_border*2) / font_height; + h = (height-window_border*2) / font_height; if (h < 1) h = 1; - term_size(term, h, w, cfg.savelines); + term_size(term, h, w, conf_get_int(conf, CONF_savelines)); reset_window(2); - } else if (cfg.resize_action != RESIZE_FONT) + } else if (resize_action != RESIZE_FONT) reset_window(2); else reset_window(0); } else if (wParam == SIZE_MINIMIZED) { /* do nothing */ - } else if (cfg.resize_action == RESIZE_TERM || - (cfg.resize_action == RESIZE_EITHER && + } else if (resize_action == RESIZE_TERM || + (resize_action == RESIZE_EITHER && !is_alt_pressed())) { - w = (width-cfg.window_border*2) / font_width; + w = (width-window_border*2) / font_width; if (w < 1) w = 1; - h = (height-cfg.window_border*2) / font_height; + h = (height-window_border*2) / font_height; if (h < 1) h = 1; if (resizing) { /* * Don't call back->size in mid-resize. (To @@ -2858,14 +2968,14 @@ * prevent massive numbers of resize events * getting sent down the connection during an NT * opaque drag.) */ need_backend_resize = TRUE; - cfg.height = h; - cfg.width = w; + conf_set_int(conf, CONF_height, h); + conf_set_int(conf, CONF_width, w); } else { - term_size(term, h, w, cfg.savelines); + term_size(term, h, w, conf_get_int(conf, CONF_savelines)); } } else { reset_window(0); } } @@ -2891,11 +3001,23 @@ case SB_PAGEUP: term_scroll(term, 0, -term->rows / 2); break; case SB_THUMBPOSITION: case SB_THUMBTRACK: - term_scroll(term, 1, HIWORD(wParam)); + /* + * Use GetScrollInfo instead of HIWORD(wParam) to get + * 32-bit scroll position. + */ + { + SCROLLINFO si; + + si.cbSize = sizeof(si); + si.fMask = SIF_TRACKPOS; + if (GetScrollInfo(hwnd, SB_VERT, &si) == 0) + si.nTrackPos = HIWORD(wParam); + term_scroll(term, 1, si.nTrackPos); + } break; } break; case WM_PALETTECHANGED: if ((HWND) wParam != hwnd && pal != NULL) { @@ -2952,18 +3074,10 @@ len = TranslateKey(message, wParam, lParam, buf); if (len == -1) return DefWindowProc(hwnd, message, wParam, lParam); if (len != 0) { - /* - * Interrupt an ongoing paste. I'm not sure - * this is sensible, but for the moment it's - * preferable to having to faff about buffering - * things. - */ - term_nopaste(term); - /* * We need not bother about stdin backlogs * here, because in GUI PuTTY we can't do * anything about it anyway; there's no means * of asking Windows to hold off on KEYDOWN @@ -2975,11 +3089,10 @@ ldisc_send(ldisc, buf, len, 1); show_mouseptr(0); } } } - net_pending_errors(); return 0; case WM_INPUTLANGCHANGE: /* wParam == Font number */ /* lParam == Locale */ set_input_locale((HKL)lParam); @@ -3016,13 +3129,24 @@ * input doesn't work correctly if we do a single * luni_send() covering the whole of buff. So * instead we luni_send the characters one by one. */ term_seen_key_event(term); - for (i = 0; i < n; i += 2) { - if (ldisc) + /* don't divide SURROGATE PAIR */ + if (ldisc) { + for (i = 0; i < n; i += 2) { + WCHAR hs = *(unsigned short *)(buff+i); + if (IS_HIGH_SURROGATE(hs) && i+2 < n) { + WCHAR ls = *(unsigned short *)(buff+i+2); + if (IS_LOW_SURROGATE(ls)) { + luni_send(ldisc, (unsigned short *)(buff+i), 2, 1); + i += 2; + continue; + } + } luni_send(ldisc, (unsigned short *)(buff+i), 1, 1); + } } free(buff); } ImmReleaseContext(hwnd, hIMC); return 1; @@ -3058,11 +3182,11 @@ if (ldisc) lpage_send(ldisc, CP_ACP, &c, 1, 1); } return 0; case WM_SYSCOLORCHANGE: - if (cfg.system_colour) { + if (conf_get_int(conf, CONF_system_colour)) { /* Refresh palette from system colours. */ /* XXX actually this zaps the entire palette. */ systopalette(); init_palette(); /* Force a repaint of the terminal window. */ @@ -3110,11 +3234,12 @@ wheel_accumulator += WHEEL_DELTA; } else break; if (send_raw_mouse && - !(cfg.mouse_override && shift_pressed)) { + !(conf_get_int(conf, CONF_mouse_override) && + shift_pressed)) { /* Mouse wheel position is in screen coordinates for * some reason */ POINT p; p.x = X_POS(lParam); p.y = Y_POS(lParam); if (ScreenToClient(hwnd, &p)) { @@ -3217,13 +3342,15 @@ int force_manual_underline = 0; int fnt_width, char_width; int text_adjust = 0; int xoffset = 0; int maxlen, remaining, opaque; + int is_cursor = FALSE; static int *lpDx = NULL; static int lpDx_len = 0; int *lpDx_maybe; + int len2; /* for SURROGATE PAIR */ lattr &= LATTR_MODE; char_width = fnt_width = font_width * (1 + (lattr != LATTR_NORM)); @@ -3237,21 +3364,19 @@ x *= fnt_width; y *= font_height; x += offset_width; y += offset_height; - if ((attr & TATTR_ACTCURS) && (cfg.cursor_type == 0 || term->big_cursor)) { + if ((attr & TATTR_ACTCURS) && (cursor_type == 0 || term->big_cursor)) { attr &= ~(ATTR_REVERSE|ATTR_BLINK|ATTR_COLOURS); - if (bold_mode == BOLD_COLOURS) - attr &= ~ATTR_BOLD; - /* cursor fg and bg */ attr |= (260 << ATTR_FGSHIFT) | (261 << ATTR_BGSHIFT); + is_cursor = TRUE; } nfont = 0; - if (cfg.vtmode == VT_POORMAN && lattr != LATTR_NORM) { + if (vtmode == VT_POORMAN && lattr != LATTR_NORM) { /* Assume a poorman font is borken in other ways too. */ lattr = LATTR_WIDE; } else switch (lattr) { case LATTR_NORM: @@ -3264,10 +3389,11 @@ break; } if (attr & ATTR_NARROW) nfont |= FONT_NARROW; +#ifdef USES_VTLINE_HACK /* Special hack for the VT100 linedraw glyphs. */ if (text[0] >= 0x23BA && text[0] <= 0x23BD) { switch ((unsigned char) (text[0])) { case 0xBA: text_adjust = -2 * font_height / 5; @@ -3288,13 +3414,15 @@ if (attr & ATTR_UNDER) { attr &= ~ATTR_UNDER; force_manual_underline = 1; } } +#endif /* Anything left as an original character set is unprintable. */ - if (DIRECT_CHAR(text[0])) { + if (DIRECT_CHAR(text[0]) && + (len < 2 || !IS_SURROGATE_PAIR(text[0], text[1]))) { int i; for (i = 0; i < len; i++) text[i] = 0xFFFD; } @@ -3302,11 +3430,11 @@ if ((text[0] & CSET_MASK) == CSET_OEMCP) nfont |= FONT_OEM; nfg = ((attr & ATTR_FGMASK) >> ATTR_FGSHIFT); nbg = ((attr & ATTR_BGMASK) >> ATTR_BGSHIFT); - if (bold_mode == BOLD_FONT && (attr & ATTR_BOLD)) + if (bold_font_mode == BOLD_FONT && (attr & ATTR_BOLD)) nfont |= FONT_BOLD; if (und_mode == UND_FONT && (attr & ATTR_UNDER)) nfont |= FONT_UNDERLINE; another_font(nfont); if (!fonts[nfont]) { @@ -3322,15 +3450,15 @@ if (attr & ATTR_REVERSE) { t = nfg; nfg = nbg; nbg = t; } - if (bold_mode == BOLD_COLOURS && (attr & ATTR_BOLD)) { + if (bold_colours && (attr & ATTR_BOLD) && !is_cursor) { if (nfg < 16) nfg |= 8; else if (nfg >= 256) nfg |= 1; } - if (bold_mode == BOLD_COLOURS && (attr & ATTR_BLINK)) { + if (bold_colours && (attr & ATTR_BLINK)) { if (nbg < 16) nbg |= 8; else if (nbg >= 256) nbg |= 1; } fg = colours[nfg]; bg = colours[nbg]; @@ -3343,10 +3471,28 @@ SetBkMode(hdc, OPAQUE); line_box.left = x; line_box.top = y; line_box.right = x + char_width * len; line_box.bottom = y + font_height; + /* adjust line_box.right for SURROGATE PAIR & VARIATION SELECTOR */ + { + int i; + int rc_width = 0; + for (i = 0; i < len ; i++) { + if (i+1 < len && IS_HIGH_VARSEL(text[i], text[i+1])) { + i++; + } else if (i+1 < len && IS_SURROGATE_PAIR(text[i], text[i+1])) { + rc_width += char_width; + i++; + } else if (IS_LOW_VARSEL(text[i])) { + /* do nothing */ + } else { + rc_width += char_width; + } + } + line_box.right = line_box.left + rc_width; + } /* Only want the left half of double width lines */ if (line_box.right > font_width*term->cols+offset_width) line_box.right = font_width*term->cols+offset_width; @@ -3373,23 +3519,51 @@ maxlen = len; } opaque = TRUE; /* start by erasing the rectangle */ for (remaining = len; remaining > 0; - text += len, remaining -= len, x += char_width * len) { + text += len, remaining -= len, x += char_width * len2) { len = (maxlen < remaining ? maxlen : remaining); - - if (len > lpDx_len) { - if (len > lpDx_len) { - lpDx_len = len * 9 / 8 + 16; - lpDx = sresize(lpDx, lpDx_len, int); - } + /* don't divide SURROGATE PAIR and VARIATION SELECTOR */ + len2 = len; + if (maxlen == 1) { + if (remaining >= 1 && IS_SURROGATE_PAIR(text[0], text[1])) + len++; + if (remaining-len >= 1 && IS_LOW_VARSEL(text[len])) + len++; + else if (remaining-len >= 2 && + IS_HIGH_VARSEL(text[len], text[len+1])) + len += 2; } + + if (len > lpDx_len) { + lpDx_len = len * 9 / 8 + 16; + lpDx = sresize(lpDx, lpDx_len, int); + + if (lpDx_maybe) lpDx_maybe = lpDx; + } + { int i; - for (i = 0; i < len; i++) + /* only last char has dx width in SURROGATE PAIR and + * VARIATION sequence */ + for (i = 0; i < len; i++) { lpDx[i] = char_width; + if (i+1 < len && IS_HIGH_VARSEL(text[i], text[i+1])) { + if (i > 0) lpDx[i-1] = 0; + lpDx[i] = 0; + i++; + lpDx[i] = char_width; + } else if (i+1 < len && IS_SURROGATE_PAIR(text[i],text[i+1])) { + lpDx[i] = 0; + i++; + lpDx[i] = char_width; + } else if (IS_LOW_VARSEL(text[i])) { + if (i > 0) lpDx[i-1] = 0; + lpDx[i] = char_width; + } + } } /* We're using a private area for direct to font. (512 chars.) */ if (ucsdata.dbcs_screenfont && (text[0] & CSET_MASK) == CSET_ACP) { /* Ho Hum, dbcs fonts are a PITA! */ @@ -3430,11 +3604,11 @@ ExtTextOutW(hdc, x + xoffset, y - font_height * (lattr == LATTR_BOT) + text_adjust, ETO_CLIPPED | (opaque ? ETO_OPAQUE : 0), &line_box, uni_buf, nlen, lpDx_maybe); - if (bold_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) { + if (bold_font_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) { SetBkMode(hdc, TRANSPARENT); ExtTextOutW(hdc, x + xoffset - 1, y - font_height * (lattr == LATTR_BOT) + text_adjust, ETO_CLIPPED, &line_box, uni_buf, nlen, lpDx_maybe); @@ -3455,11 +3629,11 @@ ExtTextOut(hdc, x + xoffset, y - font_height * (lattr == LATTR_BOT) + text_adjust, ETO_CLIPPED | (opaque ? ETO_OPAQUE : 0), &line_box, directbuf, len, lpDx_maybe); - if (bold_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) { + if (bold_font_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) { SetBkMode(hdc, TRANSPARENT); /* GRR: This draws the character outside its box and * can leave 'droppings' even with the clip box! I * suppose I could loop it one character at a time ... @@ -3494,11 +3668,11 @@ y - font_height * (lattr==LATTR_BOT) + text_adjust, &line_box, wbuf, len, lpDx, opaque && !(attr & TATTR_COMBINING)); /* And the shadow bold hack. */ - if (bold_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) { + if (bold_font_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) { SetBkMode(hdc, TRANSPARENT); ExtTextOutW(hdc, x + xoffset - 1, y - font_height * (lattr == LATTR_BOT) + text_adjust, ETO_CLIPPED, &line_box, wbuf, len, lpDx_maybe); @@ -3534,13 +3708,39 @@ void do_text(Context ctx, int x, int y, wchar_t *text, int len, unsigned long attr, int lattr) { if (attr & TATTR_COMBINING) { unsigned long a = 0; - attr &= ~TATTR_COMBINING; + int len0 = 1; + /* don't divide SURROGATE PAIR and VARIATION SELECTOR */ + if (len >= 2 && IS_SURROGATE_PAIR(text[0], text[1])) + len0 = 2; + if (len-len0 >= 1 && IS_LOW_VARSEL(text[len0])) { + attr &= ~TATTR_COMBINING; + do_text_internal(ctx, x, y, text, len0+1, attr, lattr); + text += len0+1; + len -= len0+1; + a = TATTR_COMBINING; + } else if (len-len0 >= 2 && IS_HIGH_VARSEL(text[len0], text[len0+1])) { + attr &= ~TATTR_COMBINING; + do_text_internal(ctx, x, y, text, len0+2, attr, lattr); + text += len0+2; + len -= len0+2; + a = TATTR_COMBINING; + } else { + attr &= ~TATTR_COMBINING; + } + while (len--) { - do_text_internal(ctx, x, y, text, 1, attr | a, lattr); + if (len >= 1 && IS_SURROGATE_PAIR(text[0], text[1])) { + do_text_internal(ctx, x, y, text, 2, attr | a, lattr); + len--; + text++; + } else { + do_text_internal(ctx, x, y, text, 1, attr | a, lattr); + } + text++; a = TATTR_COMBINING; } } else do_text_internal(ctx, x, y, text, len, attr, lattr); @@ -3551,11 +3751,11 @@ { int fnt_width; int char_width; HDC hdc = ctx; - int ctype = cfg.cursor_type; + int ctype = cursor_type; lattr &= LATTR_MODE; if ((attr & TATTR_ACTCURS) && (ctype == 0 || term->big_cursor)) { if (*text != UCSWIDE) { @@ -3681,10 +3881,21 @@ ibuf += font_width / 2 -1; ibuf /= font_width; return ibuf; } + +DECL_WINDOWS_FUNCTION(static, BOOL, FlashWindowEx, (PFLASHWINFO)); +DECL_WINDOWS_FUNCTION(static, BOOL, ToUnicodeEx, + (UINT, UINT, const BYTE *, LPWSTR, int, UINT, HKL)); + +static void init_winfuncs(void) +{ + HMODULE user32_module = load_system32_dll("user32.dll"); + GET_WINDOWS_FUNCTION(user32_module, FlashWindowEx); + GET_WINDOWS_FUNCTION(user32_module, ToUnicodeEx); +} /* * Translate a WM_(SYS)?KEY(UP|DOWN) message into a string of ASCII * codes. Returns number of bytes used, zero to drop the message, * -1 to forward the message to Windows, or another negative number @@ -3696,17 +3907,20 @@ BYTE keystate[256]; int scan, left_alt = 0, key_down, shift_state; int r, i, code; unsigned char *p = output; static int alt_sum = 0; + int funky_type = conf_get_int(conf, CONF_funky_type); + int no_applic_k = conf_get_int(conf, CONF_no_applic_k); + int ctrlaltkeys = conf_get_int(conf, CONF_ctrlaltkeys); + int nethack_keypad = conf_get_int(conf, CONF_nethack_keypad); HKL kbd_layout = GetKeyboardLayout(0); - /* keys is for ToAsciiEx. There's some ick here, see below. */ - static WORD keys[3]; + static wchar_t keys_unicode[3]; static int compose_char = 0; - static WPARAM compose_key = 0; + static WPARAM compose_keycode = 0; r = GetKeyboardState(keystate); if (!r) memset(keystate, 0, sizeof(keystate)); else { @@ -3752,16 +3966,16 @@ if (ch >= ' ' && ch <= '~') debug((", '%c'", ch)); else if (ch) debug((", $%02x", ch)); - if (keys[0]) - debug((", KB0=%02x", keys[0])); - if (keys[1]) - debug((", KB1=%02x", keys[1])); - if (keys[2]) - debug((", KB2=%02x", keys[2])); + if (keys_unicode[0]) + debug((", KB0=%04x", keys_unicode[0])); + if (keys_unicode[1]) + debug((", KB1=%04x", keys_unicode[1])); + if (keys_unicode[2]) + debug((", KB2=%04x", keys_unicode[2])); if ((keystate[VK_SHIFT] & 0x80) != 0) debug((", S")); if ((keystate[VK_CONTROL] & 0x80) != 0) debug((", C")); @@ -3789,13 +4003,13 @@ keystate[VK_RMENU] = keystate[VK_MENU]; } /* Nastyness with NUMLock - Shift-NUMLock is left alone though */ - if ((cfg.funky_type == FUNKY_VT400 || - (cfg.funky_type <= FUNKY_LINUX && term->app_keypad_keys && - !cfg.no_applic_k)) + if ((funky_type == FUNKY_VT400 || + (funky_type <= FUNKY_LINUX && term->app_keypad_keys && + !no_applic_k)) && wParam == VK_NUMLOCK && !(keystate[VK_SHIFT] & 0x80)) { wParam = VK_EXECUTE; /* UnToggle NUMLock */ @@ -3817,11 +4031,11 @@ key_down = ((HIWORD(lParam) & KF_UP) == 0); /* Make sure Ctrl-ALT is not the same as AltGr for ToAscii unless told. */ if (left_alt && (keystate[VK_CONTROL] & 0x80)) { - if (cfg.ctrlaltkeys) + if (ctrlaltkeys) keystate[VK_MENU] = 0; else { keystate[VK_RMENU] = 0x80; left_alt = 0; } @@ -3831,20 +4045,20 @@ shift_state = ((keystate[VK_SHIFT] & 0x80) != 0) + ((keystate[VK_CONTROL] & 0x80) != 0) * 2; /* Note if AltGr was pressed and if it was used as a compose key */ if (!compose_state) { - compose_key = 0x100; - if (cfg.compose_key) { + compose_keycode = 0x100; + if (conf_get_int(conf, CONF_compose_key)) { if (wParam == VK_MENU && (HIWORD(lParam) & KF_EXTENDED)) - compose_key = wParam; + compose_keycode = wParam; } if (wParam == VK_APPS) - compose_key = wParam; + compose_keycode = wParam; } - if (wParam == compose_key) { + if (wParam == compose_keycode) { if (compose_state == 0 && (HIWORD(lParam) & (KF_UP | KF_REPEAT)) == 0) compose_state = 1; else if (compose_state == 1 && (HIWORD(lParam) & KF_UP)) compose_state = 2; @@ -3855,13 +4069,13 @@ if (compose_state > 1 && left_alt) compose_state = 0; /* Sanitize the number pad if not using a PC NumPad */ - if (left_alt || (term->app_keypad_keys && !cfg.no_applic_k - && cfg.funky_type != FUNKY_XTERM) - || cfg.funky_type == FUNKY_VT400 || cfg.nethack_keypad || compose_state) { + if (left_alt || (term->app_keypad_keys && !no_applic_k + && funky_type != FUNKY_XTERM) + || funky_type == FUNKY_VT400 || nethack_keypad || compose_state) { if ((HIWORD(lParam) & KF_EXTENDED) == 0) { int nParam = 0; switch (wParam) { case VK_INSERT: nParam = VK_NUMPAD0; @@ -3934,19 +4148,21 @@ } if (wParam == VK_INSERT && shift_state == 1) { request_paste(NULL); return 0; } - if (left_alt && wParam == VK_F4 && cfg.alt_f4) { + if (left_alt && wParam == VK_F4 && conf_get_int(conf, CONF_alt_f4)) { return -1; } - if (left_alt && wParam == VK_SPACE && cfg.alt_space) { + if (left_alt && wParam == VK_SPACE && conf_get_int(conf, + CONF_alt_space)) { SendMessage(hwnd, WM_SYSCOMMAND, SC_KEYMENU, 0); return -1; } - if (left_alt && wParam == VK_RETURN && cfg.fullscreenonaltenter && - (cfg.resize_action != RESIZE_DISABLED)) { + if (left_alt && wParam == VK_RETURN && + conf_get_int(conf, CONF_fullscreenonaltenter) && + (conf_get_int(conf, CONF_resize_action) != RESIZE_DISABLED)) { if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) != KF_REPEAT) flip_full_screen(); return -1; } /* Control-Numlock for app-keypad mode switch */ @@ -3954,11 +4170,11 @@ term->app_keypad_keys ^= 1; return 0; } /* Nethack keypad */ - if (cfg.nethack_keypad && !left_alt) { + if (nethack_keypad && !left_alt) { switch (wParam) { case VK_NUMPAD1: *p++ = "bB\002\002"[shift_state & 3]; return p - output; case VK_NUMPAD2: @@ -3990,13 +4206,13 @@ /* Application Keypad */ if (!left_alt) { int xkey = 0; - if (cfg.funky_type == FUNKY_VT400 || - (cfg.funky_type <= FUNKY_LINUX && - term->app_keypad_keys && !cfg.no_applic_k)) switch (wParam) { + if (funky_type == FUNKY_VT400 || + (funky_type <= FUNKY_LINUX && + term->app_keypad_keys && !no_applic_k)) switch (wParam) { case VK_EXECUTE: xkey = 'P'; break; case VK_DIVIDE: xkey = 'Q'; @@ -4006,11 +4222,11 @@ break; case VK_SUBTRACT: xkey = 'S'; break; } - if (term->app_keypad_keys && !cfg.no_applic_k) + if (term->app_keypad_keys && !no_applic_k) switch (wParam) { case VK_NUMPAD0: xkey = 'p'; break; case VK_NUMPAD1: @@ -4043,11 +4259,11 @@ case VK_DECIMAL: xkey = 'n'; break; case VK_ADD: - if (cfg.funky_type == FUNKY_XTERM) { + if (funky_type == FUNKY_XTERM) { if (shift_state) xkey = 'l'; else xkey = 'k'; } else if (shift_state) @@ -4055,19 +4271,19 @@ else xkey = 'l'; break; case VK_DIVIDE: - if (cfg.funky_type == FUNKY_XTERM) + if (funky_type == FUNKY_XTERM) xkey = 'o'; break; case VK_MULTIPLY: - if (cfg.funky_type == FUNKY_XTERM) + if (funky_type == FUNKY_XTERM) xkey = 'j'; break; case VK_SUBTRACT: - if (cfg.funky_type == FUNKY_XTERM) + if (funky_type == FUNKY_XTERM) xkey = 'm'; break; case VK_RETURN: if (HIWORD(lParam) & KF_EXTENDED) @@ -4085,17 +4301,17 @@ return p - output; } } if (wParam == VK_BACK && shift_state == 0) { /* Backspace */ - *p++ = (cfg.bksp_is_delete ? 0x7F : 0x08); + *p++ = (conf_get_int(conf, CONF_bksp_is_delete) ? 0x7F : 0x08); *p++ = 0; return -2; } if (wParam == VK_BACK && shift_state == 1) { /* Shift Backspace */ /* We do the opposite of what is configured */ - *p++ = (cfg.bksp_is_delete ? 0x08 : 0x7F); + *p++ = (conf_get_int(conf, CONF_bksp_is_delete) ? 0x08 : 0x7F); *p++ = 0; return -2; } if (wParam == VK_TAB && shift_state == 1) { /* Shift tab */ *p++ = 0x1B; @@ -4235,20 +4451,20 @@ case VK_NEXT: code = 6; break; } /* Reorder edit keys to physical order */ - if (cfg.funky_type == FUNKY_VT400 && code <= 6) + if (funky_type == FUNKY_VT400 && code <= 6) code = "\0\2\1\4\5\3\6"[code]; if (term->vt52_mode && code > 0 && code <= 6) { p += sprintf((char *) p, "\x1B%c", " HLMEIG"[code]); return p - output; } - if (cfg.funky_type == FUNKY_SCO && /* SCO function keys */ - code >= 11 && code <= 34) { + if (funky_type == FUNKY_SCO && code >= 11 && code <= 34) { + /* SCO function keys */ char codes[] = "MNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz@[\\]^_`{"; int index = 0; switch (wParam) { case VK_F1: index = 0; break; case VK_F2: index = 1; break; @@ -4266,21 +4482,21 @@ if (keystate[VK_SHIFT] & 0x80) index += 12; if (keystate[VK_CONTROL] & 0x80) index += 24; p += sprintf((char *) p, "\x1B[%c", codes[index]); return p - output; } - if (cfg.funky_type == FUNKY_SCO && /* SCO small keypad */ + if (funky_type == FUNKY_SCO && /* SCO small keypad */ code >= 1 && code <= 6) { char codes[] = "HL.FIG"; if (code == 3) { *p++ = '\x7F'; } else { p += sprintf((char *) p, "\x1B[%c", codes[code-1]); } return p - output; } - if ((term->vt52_mode || cfg.funky_type == FUNKY_VT100P) && code >= 11 && code <= 24) { + if ((term->vt52_mode || funky_type == FUNKY_VT100P) && code >= 11 && code <= 24) { int offt = 0; if (code > 15) offt++; if (code > 21) offt++; @@ -4289,22 +4505,23 @@ else p += sprintf((char *) p, "\x1BO%c", code + 'P' - 11 - offt); return p - output; } - if (cfg.funky_type == FUNKY_LINUX && code >= 11 && code <= 15) { + if (funky_type == FUNKY_LINUX && code >= 11 && code <= 15) { p += sprintf((char *) p, "\x1B[[%c", code + 'A' - 11); return p - output; } - if (cfg.funky_type == FUNKY_XTERM && code >= 11 && code <= 14) { + if (funky_type == FUNKY_XTERM && code >= 11 && code <= 14) { if (term->vt52_mode) p += sprintf((char *) p, "\x1B%c", code + 'P' - 11); else p += sprintf((char *) p, "\x1BO%c", code + 'P' - 11); return p - output; } - if (cfg.rxvt_homeend && (code == 1 || code == 4)) { + if ((code == 1 || code == 4) && + conf_get_int(conf, CONF_rxvt_homeend)) { p += sprintf((char *) p, code == 1 ? "\x1B[H" : "\x1BOw"); return p - output; } if (code) { p += sprintf((char *) p, "\x1B[%d~", code); @@ -4360,19 +4577,23 @@ * the boring stuff */ { BOOL capsOn=0; /* helg: clear CAPS LOCK state if caps lock switches to cyrillic */ - if(cfg.xlat_capslockcyr && keystate[VK_CAPITAL] != 0) { + if(keystate[VK_CAPITAL] != 0 && + conf_get_int(conf, CONF_xlat_capslockcyr)) { capsOn= !left_alt; keystate[VK_CAPITAL] = 0; } /* XXX how do we know what the max size of the keys array should * be is? There's indication on MS' website of an Inquire/InquireEx * functioning returning a KBINFO structure which tells us. */ - if (osVersion.dwPlatformId == VER_PLATFORM_WIN32_NT) { + if (osVersion.dwPlatformId == VER_PLATFORM_WIN32_NT && p_ToUnicodeEx) { + r = p_ToUnicodeEx(wParam, scan, keystate, keys_unicode, + lenof(keys_unicode), 0, kbd_layout); + } else { /* XXX 'keys' parameter is declared in MSDN documentation as * 'LPWORD lpChar'. * The experience of a French user indicates that on * Win98, WORD[] should be passed in, but on Win2K, it should * be BYTE[]. German WinXP and my Win2K with "US International" @@ -4379,60 +4600,58 @@ * driver corroborate this. * Experimentally I've conditionalised the behaviour on the * Win9x/NT split, but I suspect it's worse than that. * See wishlist item `win-dead-keys' for more horrible detail * and speculations. */ - BYTE keybs[3]; int i; - r = ToAsciiEx(wParam, scan, keystate, (LPWORD)keybs, 0, kbd_layout); - for (i=0; i<3; i++) keys[i] = keybs[i]; - } else { + static WORD keys[3]; + static BYTE keysb[3]; r = ToAsciiEx(wParam, scan, keystate, keys, 0, kbd_layout); + if (r > 0) { + for (i = 0; i < r; i++) { + keysb[i] = (BYTE)keys[i]; + } + MultiByteToWideChar(CP_ACP, 0, (LPCSTR)keysb, r, + keys_unicode, lenof(keys_unicode)); + } } #ifdef SHOW_TOASCII_RESULT if (r == 1 && !key_down) { if (alt_sum) { if (in_utf(term) || ucsdata.dbcs_screenfont) debug((", (U+%04x)", alt_sum)); else debug((", LCH(%d)", alt_sum)); } else { - debug((", ACH(%d)", keys[0])); + debug((", ACH(%d)", keys_unicode[0])); } } else if (r > 0) { int r1; debug((", ASC(")); for (r1 = 0; r1 < r; r1++) { - debug(("%s%d", r1 ? "," : "", keys[r1])); + debug(("%s%d", r1 ? "," : "", keys_unicode[r1])); } debug((")")); } #endif if (r > 0) { WCHAR keybuf; - /* - * Interrupt an ongoing paste. I'm not sure this is - * sensible, but for the moment it's preferable to - * having to faff about buffering things. - */ - term_nopaste(term); - p = output; for (i = 0; i < r; i++) { - unsigned char ch = (unsigned char) keys[i]; + wchar_t wch = keys_unicode[i]; - if (compose_state == 2 && (ch & 0x80) == 0 && ch > ' ') { - compose_char = ch; + if (compose_state == 2 && wch >= ' ' && wch < 0x80) { + compose_char = wch; compose_state++; continue; } - if (compose_state == 3 && (ch & 0x80) == 0 && ch > ' ') { + if (compose_state == 3 && wch >= ' ' && wch < 0x80) { int nc; compose_state = 0; - if ((nc = check_compose(compose_char, ch)) == -1) { + if ((nc = check_compose(compose_char, wch)) == -1) { MessageBeep(MB_ICONHAND); return 0; } keybuf = nc; term_seen_key_event(term); @@ -4449,11 +4668,11 @@ keybuf = alt_sum; term_seen_key_event(term); if (ldisc) luni_send(ldisc, &keybuf, 1, 1); } else { - ch = (char) alt_sum; + char ch = (char) alt_sum; /* * We need not bother about stdin * backlogs here, because in GUI PuTTY * we can't do anything about it * anyway; there's no means of asking @@ -4467,54 +4686,53 @@ } alt_sum = 0; } else { term_seen_key_event(term); if (ldisc) - lpage_send(ldisc, kbd_codepage, &ch, 1, 1); + luni_send(ldisc, &wch, 1, 1); } } else { - if(capsOn && ch < 0x80) { + if(capsOn && wch < 0x80) { WCHAR cbuf[2]; cbuf[0] = 27; - cbuf[1] = xlat_uskbd2cyrllic(ch); + cbuf[1] = xlat_uskbd2cyrllic(wch); term_seen_key_event(term); if (ldisc) luni_send(ldisc, cbuf+!left_alt, 1+!!left_alt, 1); } else { - char cbuf[2]; + WCHAR cbuf[2]; cbuf[0] = '\033'; - cbuf[1] = ch; + cbuf[1] = wch; term_seen_key_event(term); if (ldisc) - lpage_send(ldisc, kbd_codepage, - cbuf+!left_alt, 1+!!left_alt, 1); + luni_send(ldisc, cbuf +!left_alt, 1+!!left_alt, 1); } } show_mouseptr(0); } /* This is so the ALT-Numpad and dead keys work correctly. */ - keys[0] = 0; + keys_unicode[0] = 0; return p - output; } /* If we're definitly not building up an ALT-54321 then clear it */ if (!left_alt) - keys[0] = 0; + keys_unicode[0] = 0; /* If we will be using alt_sum fix the 256s */ - else if (keys[0] && (in_utf(term) || ucsdata.dbcs_screenfont)) - keys[0] = 10; + else if (keys_unicode[0] && (in_utf(term) || ucsdata.dbcs_screenfont)) + keys_unicode[0] = 10; } /* * ALT alone may or may not want to bring up the System menu. * If it's not meant to, we return 0 on presses or releases of * ALT, to show that we've swallowed the keystroke. Otherwise * we return -1, which means Windows will give the keystroke * its default handling (i.e. bring up the System menu). */ - if (wParam == VK_MENU && !cfg.alt_only) + if (wParam == VK_MENU && !conf_get_int(conf, CONF_alt_only)) return 0; return -1; } @@ -4521,28 +4739,29 @@ void set_title(void *frontend, char *title) { sfree(window_name); window_name = snewn(1 + strlen(title), char); strcpy(window_name, title); - if (cfg.win_name_always || !IsIconic(hwnd)) + if (conf_get_int(conf, CONF_win_name_always) || !IsIconic(hwnd)) SetWindowText(hwnd, title); } void set_icon(void *frontend, char *title) { sfree(icon_name); icon_name = snewn(1 + strlen(title), char); strcpy(icon_name, title); - if (!cfg.win_name_always && IsIconic(hwnd)) + if (!conf_get_int(conf, CONF_win_name_always) && IsIconic(hwnd)) SetWindowText(hwnd, title); } void set_sbar(void *frontend, int total, int start, int page) { SCROLLINFO si; - if (is_full_screen() ? !cfg.scrollbar_in_fullscreen : !cfg.scrollbar) + if (!conf_get_int(conf, is_full_screen() ? + CONF_scrollbar_in_fullscreen : CONF_scrollbar)) return; si.cbSize = sizeof(si); si.fMask = SIF_ALL | SIF_DISABLENOSCROLL; si.nMin = 0; @@ -4586,11 +4805,11 @@ void palette_set(void *frontend, int n, int r, int g, int b) { if (n >= 16) n += 256 - 16; - if (n > NALLCOLOURS) + if (n >= NALLCOLOURS) return; real_palette_set(n, r, g, b); if (pal) { HDC hdc = get_ctx(frontend); UnrealizeObject(pal); @@ -4686,19 +4905,26 @@ GlobalFree(clipdata); if (clipdata2) GlobalFree(clipdata2); return; } - if (!(lock = GlobalLock(clipdata))) + if (!(lock = GlobalLock(clipdata))) { + GlobalFree(clipdata); + GlobalFree(clipdata2); return; - if (!(lock2 = GlobalLock(clipdata2))) + } + if (!(lock2 = GlobalLock(clipdata2))) { + GlobalUnlock(clipdata); + GlobalFree(clipdata); + GlobalFree(clipdata2); return; + } memcpy(lock, data, len * sizeof(wchar_t)); WideCharToMultiByte(CP_ACP, 0, data, len, lock2, len2, NULL, NULL); - if (cfg.rtf_paste) { + if (conf_get_int(conf, CONF_rtf_paste)) { wchar_t unitab[256]; char *rtf = NULL; unsigned char *tdata = (unsigned char *)lock2; wchar_t *udata = (wchar_t *)lock; int rtflen = 0, uindex = 0, tindex = 0; @@ -4709,17 +4935,18 @@ int bgcolour, lastbgcolour = 0; int attrBold, lastAttrBold = 0; int attrUnder, lastAttrUnder = 0; int palette[NALLCOLOURS]; int numcolours; + FontSpec *font = conf_get_fontspec(conf, CONF_font); get_unitab(CP_ACP, unitab, 0); - rtfsize = 100 + strlen(cfg.font.name); + rtfsize = 100 + strlen(font->name); rtf = snewn(rtfsize, char); rtflen = sprintf(rtf, "{\\rtf1\\ansi\\deff0{\\fonttbl\\f0\\fmodern %s;}\\f0\\fs%d", - cfg.font.name, cfg.font.height*2); + font->name, font->height*2); /* * Add colour palette * {\colortbl ;\red255\green0\blue0;\red0\green0\blue128;} */ @@ -4738,11 +4965,11 @@ int tmpcolour = fgcolour; /* Swap foreground and background */ fgcolour = bgcolour; bgcolour = tmpcolour; } - if (bold_mode == BOLD_COLOURS && (attr[i] & ATTR_BOLD)) { + if (bold_colours && (attr[i] & ATTR_BOLD)) { if (fgcolour < 8) /* ANSI colours */ fgcolour += 8; else if (fgcolour >= 256) /* Default colours */ fgcolour ++; } @@ -4829,11 +5056,11 @@ int tmpcolour = fgcolour; /* Swap foreground and background */ fgcolour = bgcolour; bgcolour = tmpcolour; } - if (bold_mode == BOLD_COLOURS && (attr[tindex] & ATTR_BOLD)) { + if (bold_colours && (attr[tindex] & ATTR_BOLD)) { if (fgcolour < 8) /* ANSI colours */ fgcolour += 8; else if (fgcolour >= 256) /* Default colours */ fgcolour ++; } @@ -4846,11 +5073,11 @@ } /* * Collect other attributes */ - if (bold_mode != BOLD_COLOURS) + if (bold_font_mode != BOLD_NONE) attrBold = attr[tindex] & ATTR_BOLD; else attrBold = 0; attrUnder = attr[tindex] & ATTR_UNDER; @@ -4865,11 +5092,11 @@ if (!(attr[tindex] & ATTR_REVERSE)) { if (bgcolour >= 256) /* Default color */ bgcolour = -1; /* No coloring */ if (fgcolour >= 256) { /* Default colour */ - if (bold_mode == BOLD_COLOURS && (fgcolour & 1) && bgcolour == -1) + if (bold_colours && (fgcolour & 1) && bgcolour == -1) attrBold = ATTR_BOLD; /* Emphasize text with bold attribute */ fgcolour = -1; /* No coloring */ } } @@ -5130,16 +5357,24 @@ MB_SYSTEMMODAL | MB_ICONERROR | MB_OK); sfree(stuff); cleanup_exit(1); } -DECL_WINDOWS_FUNCTION(static, BOOL, FlashWindowEx, (PFLASHWINFO)); - -static void init_flashwindow(void) +/* + * Print a message box and don't close the connection. + */ +void nonfatal(char *fmt, ...) { - HMODULE user32_module = load_system32_dll("user32.dll"); - GET_WINDOWS_FUNCTION(user32_module, FlashWindowEx); + va_list ap; + char *stuff, morestuff[100]; + + va_start(ap, fmt); + stuff = dupvprintf(fmt, ap); + va_end(ap); + sprintf(morestuff, "%.70s Error", appname); + MessageBox(hwnd, stuff, morestuff, MB_ICONERROR | MB_OK); + sfree(stuff); } static BOOL flash_window_ex(DWORD dwFlags, UINT uCount, DWORD dwTimeout) { if (p_FlashWindowEx) { @@ -5161,13 +5396,13 @@ /* * Timer for platforms where we must maintain window flashing manually * (e.g., Win95). */ -static void flash_window_timer(void *ctx, long now) +static void flash_window_timer(void *ctx, unsigned long now) { - if (flashing && now - next_flash >= 0) { + if (flashing && now == next_flash) { flash_window(1); } } /* @@ -5174,11 +5409,12 @@ * Manage window caption / taskbar flashing, if enabled. * 0 = stop, 1 = maintain, 2 = start */ static void flash_window(int mode) { - if ((mode == 0) || (cfg.beep_ind == B_IND_DISABLED)) { + int beep_ind = conf_get_int(conf, CONF_beep_ind); + if ((mode == 0) || (beep_ind == B_IND_DISABLED)) { /* stop */ if (flashing) { flashing = 0; if (p_FlashWindowEx) flash_window_ex(FLASHW_STOP, 0, 0); @@ -5196,20 +5432,20 @@ * by user notifications (e.g., by Explorer). * uCount=0 appears to enable continuous flashing, per * "flashing" mode, although I haven't seen this * documented. */ flash_window_ex(FLASHW_ALL | FLASHW_TIMER, - (cfg.beep_ind == B_IND_FLASH ? 0 : 2), + (beep_ind == B_IND_FLASH ? 0 : 2), 0 /* system cursor blink rate */); /* No need to schedule timer */ } else { FlashWindow(hwnd, TRUE); next_flash = schedule_timer(450, flash_window_timer, hwnd); } } - } else if ((mode == 1) && (cfg.beep_ind == B_IND_FLASH)) { + } else if ((mode == 1) && (beep_ind == B_IND_FLASH)) { /* maintain */ if (flashing && !p_FlashWindowEx) { FlashWindow(hwnd, TRUE); /* toggle */ next_flash = schedule_timer(450, flash_window_timer, hwnd); } @@ -5239,20 +5475,21 @@ * The above MessageBeep call takes time, so we record the * time _after_ it finishes rather than before it starts. */ lastbeep = GetTickCount(); } else if (mode == BELL_WAVEFILE) { - if (!PlaySound(cfg.bell_wavefile.path, NULL, + Filename *bell_wavefile = conf_get_filename(conf, CONF_bell_wavefile); + if (!PlaySound(bell_wavefile->path, NULL, SND_ASYNC | SND_FILENAME)) { - char buf[sizeof(cfg.bell_wavefile.path) + 80]; + char buf[sizeof(bell_wavefile->path) + 80]; char otherbuf[100]; sprintf(buf, "Unable to play sound file\n%s\n" - "Using default sound instead", cfg.bell_wavefile.path); + "Using default sound instead", bell_wavefile->path); sprintf(otherbuf, "%.70s Sound Error", appname); MessageBox(hwnd, buf, otherbuf, MB_OK | MB_ICONEXCLAMATION); - cfg.beep = BELL_DEFAULT; + conf_set_int(conf, CONF_beep, BELL_DEFAULT); } } else if (mode == BELL_PCSPEAKER) { static long lastbeep = 0; long beepdiff; @@ -5294,12 +5531,13 @@ /* * Move the window in response to a server-side request. */ void move_window(void *frontend, int x, int y) { - if (cfg.resize_action == RESIZE_DISABLED || - cfg.resize_action == RESIZE_FONT || + int resize_action = conf_get_int(conf, CONF_resize_action); + if (resize_action == RESIZE_DISABLED || + resize_action == RESIZE_FONT || IsZoomed(hwnd)) return; SetWindowPos(hwnd, NULL, x, y, 0, 0, SWP_NOSIZE | SWP_NOZORDER); } @@ -5308,11 +5546,11 @@ * Move the window to the top or bottom of the z-order in response * to a server-side request. */ void set_zorder(void *frontend, int top) { - if (cfg.alwaysontop) + if (conf_get_int(conf, CONF_alwaysontop)) return; /* ignore */ SetWindowPos(hwnd, top ? HWND_TOP : HWND_BOTTOM, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); } @@ -5430,11 +5668,11 @@ return; /* Remove the window furniture. */ style = GetWindowLongPtr(hwnd, GWL_STYLE); style &= ~(WS_CAPTION | WS_BORDER | WS_THICKFRAME); - if (cfg.scrollbar_in_fullscreen) + if (conf_get_int(conf, CONF_scrollbar_in_fullscreen)) style |= WS_VSCROLL; else style &= ~WS_VSCROLL; SetWindowLongPtr(hwnd, GWL_STYLE, style); @@ -5465,15 +5703,15 @@ DWORD oldstyle, style; /* Reinstate the window furniture. */ style = oldstyle = GetWindowLongPtr(hwnd, GWL_STYLE); style |= WS_CAPTION | WS_BORDER; - if (cfg.resize_action == RESIZE_DISABLED) + if (conf_get_int(conf, CONF_resize_action) == RESIZE_DISABLED) style &= ~WS_THICKFRAME; else style |= WS_THICKFRAME; - if (cfg.scrollbar) + if (conf_get_int(conf, CONF_scrollbar)) style |= WS_VSCROLL; else style &= ~WS_VSCROLL; if (style != oldstyle) { SetWindowLongPtr(hwnd, GWL_STYLE, style); @@ -5523,10 +5761,15 @@ int from_backend_untrusted(void *frontend, const char *data, int len) { return term_data_untrusted(term, data, len); } + +int from_backend_eof(void *frontend) +{ + return TRUE; /* do respond to incoming EOF with outgoing */ +} int get_userpass_input(prompts_t *p, unsigned char *in, int inlen) { int ret; ret = cmdline_get_passwd_input(p, in, inlen); Index: windows/wingss.c ================================================================== --- windows/wingss.c +++ windows/wingss.c @@ -63,15 +63,16 @@ const char *gsslogmsg = NULL; static void ssh_sspi_bind_fns(struct ssh_gss_library *lib); -struct ssh_gss_liblist *ssh_gss_setup(const Config *cfg) +struct ssh_gss_liblist *ssh_gss_setup(Conf *conf) { HMODULE module; HKEY regkey; struct ssh_gss_liblist *list = snew(struct ssh_gss_liblist); + char *path; list->libraries = snewn(3, struct ssh_gss_library); list->nlibraries = 0; /* MIT Kerberos GSSAPI implementation */ @@ -146,20 +147,21 @@ /* * Custom GSSAPI DLL. */ module = NULL; - if (cfg->ssh_gss_custom.path[0]) { - module = LoadLibrary(cfg->ssh_gss_custom.path); + path = conf_get_filename(conf, CONF_ssh_gss_custom)->path; + if (*path) { + module = LoadLibrary(path); } if (module) { struct ssh_gss_library *lib = &list->libraries[list->nlibraries++]; lib->id = 2; lib->gsslogmsg = dupprintf("Using GSSAPI from user-specified" - " library '%s'", cfg->ssh_gss_custom.path); + " library '%s'", path); lib->handle = (void *)module; #define BIND_GSS_FN(name) \ lib->u.gssapi.name = (t_gss_##name) GetProcAddress(module, "gss_" #name) Index: windows/winhandl.c ================================================================== --- windows/winhandl.c +++ windows/winhandl.c @@ -62,10 +62,12 @@ int done; /* request subthread to terminate */ int defunct; /* has the subthread already gone? */ int busy; /* operation currently in progress? */ void *privdata; /* for client to remember who they are */ }; + +typedef enum { INPUT, OUTPUT, FOREIGN } HandleType; /* ---------------------------------------------------------------------- * Input threads. */ @@ -248,10 +250,11 @@ /* * Data only ever read or written by the main thread. */ bufchain queued_data; /* data still waiting to be written */ + enum { EOF_NO, EOF_PENDING, EOF_SENT } outgoingeof; /* * Callback function called when the backlog in the bufchain * drops. */ @@ -318,23 +321,56 @@ bufchain_prefix(&ctx->queued_data, &senddata, &sendlen); ctx->buffer = senddata; ctx->len = sendlen; SetEvent(ctx->ev_from_main); ctx->busy = TRUE; + } else if (!ctx->busy && bufchain_size(&ctx->queued_data) == 0 && + ctx->outgoingeof == EOF_PENDING) { + CloseHandle(ctx->h); + ctx->h = INVALID_HANDLE_VALUE; + ctx->outgoingeof = EOF_SENT; } } + +/* ---------------------------------------------------------------------- + * 'Foreign events'. These are handle structures which just contain a + * single event object passed to us by another module such as + * winnps.c, so that they can make use of our handle_get_events / + * handle_got_event mechanism for communicating with application main + * loops. + */ +struct handle_foreign { + /* + * Copy of the handle_generic structure. + */ + HANDLE h; /* the handle itself */ + HANDLE ev_to_main; /* event used to signal main thread */ + HANDLE ev_from_main; /* event used to signal back to us */ + int moribund; /* are we going to kill this soon? */ + int done; /* request subthread to terminate */ + int defunct; /* has the subthread already gone? */ + int busy; /* operation currently in progress? */ + void *privdata; /* for client to remember who they are */ + + /* + * Our own data, just consisting of knowledge of who to call back. + */ + void (*callback)(void *); + void *ctx; +}; /* ---------------------------------------------------------------------- * Unified code handling both input and output threads. */ struct handle { - int output; + HandleType type; union { struct handle_generic g; struct handle_input i; struct handle_output o; + struct handle_foreign f; } u; }; static tree234 *handles_by_evtomain; @@ -368,11 +404,11 @@ void *privdata, int flags) { struct handle *h = snew(struct handle); DWORD in_threadid; /* required for Win9x */ - h->output = FALSE; + h->type = INPUT; h->u.i.h = handle; h->u.i.ev_to_main = CreateEvent(NULL, FALSE, FALSE, NULL); h->u.i.ev_from_main = CreateEvent(NULL, FALSE, FALSE, NULL); h->u.i.gotdata = gotdata; h->u.i.defunct = FALSE; @@ -396,20 +432,21 @@ void *privdata, int flags) { struct handle *h = snew(struct handle); DWORD out_threadid; /* required for Win9x */ - h->output = TRUE; + h->type = OUTPUT; h->u.o.h = handle; h->u.o.ev_to_main = CreateEvent(NULL, FALSE, FALSE, NULL); h->u.o.ev_from_main = CreateEvent(NULL, FALSE, FALSE, NULL); h->u.o.busy = FALSE; h->u.o.defunct = FALSE; h->u.o.moribund = FALSE; h->u.o.done = FALSE; h->u.o.privdata = privdata; bufchain_init(&h->u.o.queued_data); + h->u.o.outgoingeof = EOF_NO; h->u.o.sentdata = sentdata; h->u.o.flags = flags; if (!handles_by_evtomain) handles_by_evtomain = newtree234(handle_cmp_evtomain); @@ -418,18 +455,59 @@ CreateThread(NULL, 0, handle_output_threadfunc, &h->u.o, 0, &out_threadid); return h; } + +struct handle *handle_add_foreign_event(HANDLE event, + void (*callback)(void *), void *ctx) +{ + struct handle *h = snew(struct handle); + + h->type = FOREIGN; + h->u.f.h = INVALID_HANDLE_VALUE; + h->u.f.ev_to_main = event; + h->u.f.ev_from_main = INVALID_HANDLE_VALUE; + h->u.f.defunct = TRUE; /* we have no thread in the first place */ + h->u.f.moribund = FALSE; + h->u.f.done = FALSE; + h->u.f.privdata = NULL; + h->u.f.callback = callback; + h->u.f.ctx = ctx; + h->u.f.busy = TRUE; + + if (!handles_by_evtomain) + handles_by_evtomain = newtree234(handle_cmp_evtomain); + add234(handles_by_evtomain, h); + + return h; +} int handle_write(struct handle *h, const void *data, int len) { - assert(h->output); + assert(h->type == OUTPUT); + assert(h->u.o.outgoingeof == EOF_NO); bufchain_add(&h->u.o.queued_data, data, len); handle_try_output(&h->u.o); return bufchain_size(&h->u.o.queued_data); } + +void handle_write_eof(struct handle *h) +{ + /* + * This function is called when we want to proactively send an + * end-of-file notification on the handle. We can only do this by + * actually closing the handle - so never call this on a + * bidirectional handle if we're still interested in its incoming + * direction! + */ + assert(h->type == OUTPUT); + if (!h->u.o.outgoingeof == EOF_NO) { + h->u.o.outgoingeof = EOF_PENDING; + handle_try_output(&h->u.o); + } +} HANDLE *handle_get_events(int *nevents) { HANDLE *ret; struct handle *h; @@ -457,11 +535,11 @@ return ret; } static void handle_destroy(struct handle *h) { - if (h->output) + if (h->type == OUTPUT) bufchain_clear(&h->u.o.queued_data); CloseHandle(h->u.g.ev_from_main); CloseHandle(h->u.g.ev_to_main); del234(handles_by_evtomain, h); sfree(h); @@ -534,13 +612,14 @@ SetEvent(h->u.g.ev_from_main); } return; } - if (!h->output) { + switch (h->type) { int backlog; + case INPUT: h->u.i.busy = FALSE; /* * A signal on an input handle means data has arrived. */ @@ -552,11 +631,13 @@ h->u.i.defunct = TRUE; } else { backlog = h->u.i.gotdata(h, h->u.i.buffer, h->u.i.len); handle_throttle(&h->u.i, backlog); } - } else { + break; + + case OUTPUT: h->u.o.busy = FALSE; /* * A signal on an output handle means we have completed a * write. Call the callback to indicate that the output @@ -573,24 +654,30 @@ } else { bufchain_consume(&h->u.o.queued_data, h->u.o.lenwritten); h->u.o.sentdata(h, bufchain_size(&h->u.o.queued_data)); handle_try_output(&h->u.o); } + break; + + case FOREIGN: + /* Just call the callback. */ + h->u.f.callback(h->u.f.ctx); + break; } } void handle_unthrottle(struct handle *h, int backlog) { - assert(!h->output); + assert(h->type == INPUT); handle_throttle(&h->u.i, backlog); } int handle_backlog(struct handle *h) { - assert(h->output); + assert(h->type == OUTPUT); return bufchain_size(&h->u.o.queued_data); } void *handle_get_privdata(struct handle *h) { return h->u.g.privdata; } Index: windows/winhelp.c ================================================================== --- windows/winhelp.c +++ windows/winhelp.c @@ -79,11 +79,11 @@ * FIXME: it would be nice here to disregard help_path on * platforms that didn't have WINHLP32. But that's probably * unrealistic, since even Vista will have it if the user * specifically downloads it. */ - return (help_path + return (help_path != NULL #ifndef NO_HTMLHELP || chm_path #endif /* NO_HTMLHELP */ ); } Index: windows/winhelp.h ================================================================== --- windows/winhelp.h +++ windows/winhelp.h @@ -97,10 +97,11 @@ #define WINHELP_CTX_ssh_noshell "ssh.noshell:config-ssh-noshell" #define WINHELP_CTX_ssh_ciphers "ssh.ciphers:config-ssh-encryption" #define WINHELP_CTX_ssh_protocol "ssh.protocol:config-ssh-prot" #define WINHELP_CTX_ssh_command "ssh.command:config-command" #define WINHELP_CTX_ssh_compress "ssh.compress:config-ssh-comp" +#define WINHELP_CTX_ssh_share "ssh.sharing:config-ssh-sharing" #define WINHELP_CTX_ssh_kexlist "ssh.kex.order:config-ssh-kex-order" #define WINHELP_CTX_ssh_kex_repeat "ssh.kex.repeat:config-ssh-kex-rekey" #define WINHELP_CTX_ssh_auth_bypass "ssh.auth.bypass:config-ssh-noauth" #define WINHELP_CTX_ssh_auth_banner "ssh.auth.banner:config-ssh-banner" #define WINHELP_CTX_ssh_auth_privkey "ssh.auth.privkey:config-ssh-privkey" @@ -142,10 +143,11 @@ #define WINHELP_CTX_ssh_bugs_derivekey2 "ssh.bugs.derivekey2:config-ssh-bug-derivekey2" #define WINHELP_CTX_ssh_bugs_rsapad2 "ssh.bugs.rsapad2:config-ssh-bug-sig" #define WINHELP_CTX_ssh_bugs_pksessid2 "ssh.bugs.pksessid2:config-ssh-bug-pksessid2" #define WINHELP_CTX_ssh_bugs_rekey2 "ssh.bugs.rekey2:config-ssh-bug-rekey" #define WINHELP_CTX_ssh_bugs_maxpkt2 "ssh.bugs.maxpkt2:config-ssh-bug-maxpkt2" +#define WINHELP_CTX_ssh_bugs_winadj "ssh.bugs.winadj:config-ssh-bug-winadj" #define WINHELP_CTX_serial_line "serial.line:config-serial-line" #define WINHELP_CTX_serial_speed "serial.speed:config-serial-speed" #define WINHELP_CTX_serial_databits "serial.databits:config-serial-databits" #define WINHELP_CTX_serial_stopbits "serial.stopbits:config-serial-stopbits" #define WINHELP_CTX_serial_parity "serial.parity:config-serial-parity" @@ -166,23 +168,10 @@ #define WINHELP_CTX_puttygen_savepriv "puttygen.savepriv:puttygen-savepriv" #define WINHELP_CTX_puttygen_savepub "puttygen.savepub:puttygen-savepub" #define WINHELP_CTX_puttygen_pastekey "puttygen.pastekey:puttygen-pastekey" #define WINHELP_CTX_puttygen_load "puttygen.load:puttygen-load" #define WINHELP_CTX_puttygen_conversions "puttygen.conversions:puttygen-conversions" -/* PuTTY SC start */ -#define WINHELP_CTX_ssh_write_syslog "ssh.write.syslog" -#define WINHELP_CTX_ssh_auth_pkcs11 "ssh.auth.pkcs11" -#define WINHELP_CTX_ssh_auth_pkcs11_libfile "ssh.auth.pkcs11libfile" -#define WINHELP_CTX_ssh_auth_pkcs11_token_label "ssh.auth.pkcs11tokenlabel" -#define WINHELP_CTX_ssh_auth_pkcs11_cert_label "ssh.auth.pkcs11certlabel" -/* PuTTY SC end */ -/* PuTTY CAPI start */ -#ifdef _WINDOWS -#define WINHELP_CTX_ssh_auth_capi "ssh.auth.capi" -#define WINHELP_CTX_ssh_auth_capi_certstore_label "ssh.auth.capicertstorelabel" -#endif -/* PuTTY CAPI end */ /* These are used in Windows-specific bits of the frontend. * We (ab)use "help context identifiers" (dwContextId) to identify them. */ #define HELPCTXID(x) WINHELP_CTXID_ ## x ADDED windows/winhsock.c Index: windows/winhsock.c ================================================================== --- /dev/null +++ windows/winhsock.c @@ -0,0 +1,267 @@ +/* + * General mechanism for wrapping up reading/writing of Windows + * HANDLEs into a PuTTY Socket abstraction. + */ + +#include +#include +#include + +#define DEFINE_PLUG_METHOD_MACROS +#include "tree234.h" +#include "putty.h" +#include "network.h" + +typedef struct Socket_handle_tag *Handle_Socket; + +struct Socket_handle_tag { + const struct socket_function_table *fn; + /* the above variable absolutely *must* be the first in this structure */ + + HANDLE send_H, recv_H; + struct handle *send_h, *recv_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; + + char *error; + + Plug plug; +}; + +static int handle_gotdata(struct handle *h, void *data, int len) +{ + Handle_Socket ps = (Handle_Socket) handle_get_privdata(h); + + if (len < 0) { + return plug_closing(ps->plug, "Read error from handle", + 0, 0); + } else if (len == 0) { + return plug_closing(ps->plug, NULL, 0, 0); + } else { + assert(ps->frozen != FREEZING && ps->frozen != THAWING); + if (ps->frozen == FREEZING) { + /* + * If we've received data while this socket is supposed to + * be frozen (because the read winhandl.c started before + * sk_set_frozen was called has now returned) then buffer + * the data for when we unfreeze. + */ + bufchain_add(&ps->inputdata, data, len); + + /* + * And return a very large backlog, to prevent further + * data arriving from winhandl until we unfreeze. + */ + return INT_MAX; + } else { + return plug_receive(ps->plug, 0, data, len); + } + } +} + +static void handle_sentdata(struct handle *h, int new_backlog) +{ + Handle_Socket ps = (Handle_Socket) handle_get_privdata(h); + + plug_sent(ps->plug, new_backlog); +} + +static Plug sk_handle_plug(Socket s, Plug p) +{ + Handle_Socket ps = (Handle_Socket) s; + Plug ret = ps->plug; + if (p) + ps->plug = p; + return ret; +} + +static void sk_handle_close(Socket s) +{ + Handle_Socket ps = (Handle_Socket) s; + + handle_free(ps->send_h); + handle_free(ps->recv_h); + CloseHandle(ps->send_H); + if (ps->recv_H != ps->send_H) + CloseHandle(ps->recv_H); + bufchain_clear(&ps->inputdata); + + sfree(ps); +} + +static int sk_handle_write(Socket s, const char *data, int len) +{ + Handle_Socket ps = (Handle_Socket) s; + + return handle_write(ps->send_h, data, len); +} + +static int sk_handle_write_oob(Socket s, const char *data, int len) +{ + /* + * oob data is treated as inband; nasty, but nothing really + * better we can do + */ + return sk_handle_write(s, data, len); +} + +static void sk_handle_write_eof(Socket s) +{ + Handle_Socket ps = (Handle_Socket) s; + + handle_write_eof(ps->send_h); +} + +static void sk_handle_flush(Socket s) +{ + /* Handle_Socket ps = (Handle_Socket) s; */ + /* do nothing */ +} + +static void handle_socket_unfreeze(void *psv) +{ + Handle_Socket ps = (Handle_Socket) psv; + void *data; + int len, new_backlog; + + /* + * If we've been put into a state other than THAWING since the + * last callback, then we're done. + */ + if (ps->frozen != THAWING) + return; + + /* + * Get some of the data we've buffered. + */ + bufchain_prefix(&ps->inputdata, &data, &len); + assert(len > 0); + + /* + * Hand it off to the plug. + */ + new_backlog = plug_receive(ps->plug, 0, data, len); + + if (bufchain_size(&ps->inputdata) > 0) { + /* + * If there's still data in our buffer, stay in THAWING state, + * and reschedule ourself. + */ + queue_toplevel_callback(handle_socket_unfreeze, ps); + } else { + /* + * Otherwise, we've successfully thawed! + */ + ps->frozen = UNFROZEN; + handle_unthrottle(ps->recv_h, new_backlog); + } +} + +static void sk_handle_set_frozen(Socket s, int is_frozen) +{ + Handle_Socket ps = (Handle_Socket) s; + + if (is_frozen) { + switch (ps->frozen) { + case FREEZING: + case FROZEN: + return; /* nothing to do */ + + case THAWING: + /* + * We were in the middle of emptying our bufchain, and got + * frozen again. In that case, winhandl.c is already + * throttled, so just return to FROZEN state. The toplevel + * callback will notice and disable itself. + */ + ps->frozen = FROZEN; + break; + + case UNFROZEN: + /* + * The normal case. Go to FREEZING, and expect one more + * load of data from winhandl if we're unlucky. + */ + ps->frozen = FREEZING; + break; + } + } else { + switch (ps->frozen) { + case UNFROZEN: + case THAWING: + return; /* nothing to do */ + + case FREEZING: + /* + * If winhandl didn't send us any data throughout the time + * we were frozen, then we'll still be in this state and + * can just unfreeze in the trivial way. + */ + assert(bufchain_size(&ps->inputdata) == 0); + ps->frozen = UNFROZEN; + break; + + case FROZEN: + /* + * If we have buffered data, go to THAWING and start + * releasing it in top-level callbacks. + */ + ps->frozen = THAWING; + queue_toplevel_callback(handle_socket_unfreeze, ps); + } + } +} + +static const char *sk_handle_socket_error(Socket s) +{ + Handle_Socket ps = (Handle_Socket) s; + return ps->error; +} + +Socket make_handle_socket(HANDLE send_H, HANDLE recv_H, Plug plug, + int overlapped) +{ + static const struct socket_function_table socket_fn_table = { + sk_handle_plug, + sk_handle_close, + sk_handle_write, + sk_handle_write_oob, + sk_handle_write_eof, + sk_handle_flush, + sk_handle_set_frozen, + sk_handle_socket_error + }; + + Handle_Socket ret; + int flags = (overlapped ? HANDLE_FLAG_OVERLAPPED : 0); + + ret = snew(struct Socket_handle_tag); + ret->fn = &socket_fn_table; + ret->plug = plug; + ret->error = NULL; + ret->frozen = UNFROZEN; + bufchain_init(&ret->inputdata); + + ret->recv_H = recv_H; + ret->recv_h = handle_input_new(ret->recv_H, handle_gotdata, ret, flags); + ret->send_H = send_H; + ret->send_h = handle_output_new(ret->send_H, handle_sentdata, ret, flags); + + return (Socket) ret; +} Index: windows/winjump.c ================================================================== --- windows/winjump.c +++ windows/winjump.c @@ -351,16 +351,16 @@ static const PROPERTYKEY PKEY_Title = { {0xf29f85e0, 0x4ff9, 0x1068, {0xab,0x91,0x08,0x00,0x2b,0x27,0xb3,0xd9}}, 0x00000002 }; -/* Type-checking macro to provide arguments for CoCreateInstance() etc. - * The pointer arithmetic is a compile-time pointer type check that 'obj' - * really is a 'type **', but is intended to have no effect at runtime. */ +/* Type-checking macro to provide arguments for CoCreateInstance() + * etc, ensuring that 'obj' really is a 'type **'. */ +#define typecheck(checkexpr, result) \ + (sizeof(checkexpr) ? (result) : (result)) #define COMPTR(type, obj) &IID_##type, \ - (void **)(void *)((obj) + (sizeof((obj)-(type **)(obj))) \ - - (sizeof((obj)-(type **)(obj)))) + typecheck((obj)-(type **)(obj), (void **)(void *)(obj)) static char putty_path[2048]; /* * Function to make an IShellLink describing a particular PuTTY @@ -408,20 +408,24 @@ } /* Check if this is a valid session, otherwise don't add. */ if (sessionname) { psettings_tmp = open_settings_r(sessionname); - if (!psettings_tmp) + if (!psettings_tmp) { + sfree(app_path); return NULL; + } close_settings_r(psettings_tmp); } /* Create the new item. */ if (!SUCCEEDED(CoCreateInstance(&CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, - COMPTR(IShellLink, &ret)))) + COMPTR(IShellLink, &ret)))) { + sfree(app_path); return NULL; + } /* Set path, parameters, icon and description. */ ret->lpVtbl->SetPath(ret, app_path); if (sessionname) { Index: windows/winmisc.c ================================================================== --- windows/winmisc.c +++ windows/winmisc.c @@ -12,32 +12,73 @@ char *platform_get_x_display(void) { /* We may as well check for DISPLAY in case it's useful. */ return dupstr(getenv("DISPLAY")); } -Filename filename_from_str(const char *str) +Filename *filename_from_str(const char *str) { - Filename ret; - strncpy(ret.path, str, sizeof(ret.path)); - ret.path[sizeof(ret.path)-1] = '\0'; + Filename *ret = snew(Filename); + ret->path = dupstr(str); return ret; } + +Filename *filename_copy(const Filename *fn) +{ + return filename_from_str(fn->path); +} const char *filename_to_str(const Filename *fn) { return fn->path; } -int filename_equal(Filename f1, Filename f2) +int filename_equal(const Filename *f1, const Filename *f2) +{ + return !strcmp(f1->path, f2->path); +} + +int filename_is_null(const Filename *fn) +{ + return !*fn->path; +} + +void filename_free(Filename *fn) +{ + sfree(fn->path); + sfree(fn); +} + +int filename_serialise(const Filename *f, void *vdata) +{ + char *data = (char *)vdata; + int len = strlen(f->path) + 1; /* include trailing NUL */ + if (data) { + strcpy(data, f->path); + } + return len; +} +Filename *filename_deserialise(void *vdata, int maxsize, int *used) { - return !strcmp(f1.path, f2.path); + char *data = (char *)vdata; + char *end; + end = memchr(data, '\0', maxsize); + if (!end) + return NULL; + end++; + *used = end - data; + return filename_from_str(data); } -int filename_is_null(Filename fn) -{ - return !*fn.path; +#ifndef NO_SECUREZEROMEMORY +/* + * Windows implementation of smemclr (see misc.c) using SecureZeroMemory. + */ +void smemclr(void *b, size_t n) { + if (b && n > 0) + SecureZeroMemory(b, n); } +#endif char *get_username(void) { DWORD namelen; char *user; @@ -129,10 +170,71 @@ fullpath = dupcat(sysdir, "\\", libname, NULL); ret = LoadLibrary(fullpath); sfree(fullpath); return ret; } + +/* + * A tree234 containing mappings from system error codes to strings. + */ + +struct errstring { + int error; + char *text; +}; + +static int errstring_find(void *av, void *bv) +{ + int *a = (int *)av; + struct errstring *b = (struct errstring *)bv; + if (*a < b->error) + return -1; + if (*a > b->error) + return +1; + return 0; +} +static int errstring_compare(void *av, void *bv) +{ + struct errstring *a = (struct errstring *)av; + return errstring_find(&a->error, bv); +} + +static tree234 *errstrings = NULL; + +const char *win_strerror(int error) +{ + struct errstring *es; + + if (!errstrings) + errstrings = newtree234(errstring_compare); + + es = find234(errstrings, &error, errstring_find); + + if (!es) { + int bufsize; + char msgtext[65536]; /* maximum size for FormatMessage is 64K */ + + es = snew(struct errstring); + es->error = error; + if (!FormatMessage((FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS), NULL, error, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + msgtext, lenof(msgtext)-1, NULL)) { + sprintf(msgtext, + "(unable to format: FormatMessage returned %d)", + error, GetLastError()); + } else { + int len = strlen(msgtext); + if (len > 0 && msgtext[len-1] == '\n') + msgtext[len-1] = '\0'; + } + es->text = dupprintf("Error %d: %s", error, msgtext); + add234(errstrings, es); + } + + return es->text; +} #ifdef DEBUG static FILE *debug_fp = NULL; static HANDLE debug_hdl = INVALID_HANDLE_VALUE; static int debug_got_console = 0; @@ -377,5 +479,53 @@ minefield_free(p); return q; } #endif /* MINEFIELD */ + +FontSpec *fontspec_new(const char *name, + int bold, int height, int charset) +{ + FontSpec *f = snew(FontSpec); + f->name = dupstr(name); + f->isbold = bold; + f->height = height; + f->charset = charset; + return f; +} +FontSpec *fontspec_copy(const FontSpec *f) +{ + return fontspec_new(f->name, f->isbold, f->height, f->charset); +} +void fontspec_free(FontSpec *f) +{ + sfree(f->name); + sfree(f); +} +int fontspec_serialise(FontSpec *f, void *vdata) +{ + char *data = (char *)vdata; + int len = strlen(f->name) + 1; /* include trailing NUL */ + if (data) { + strcpy(data, f->name); + PUT_32BIT_MSB_FIRST(data + len, f->isbold); + PUT_32BIT_MSB_FIRST(data + len + 4, f->height); + PUT_32BIT_MSB_FIRST(data + len + 8, f->charset); + } + return len + 12; /* also include three 4-byte ints */ +} +FontSpec *fontspec_deserialise(void *vdata, int maxsize, int *used) +{ + char *data = (char *)vdata; + char *end; + if (maxsize < 13) + return NULL; + end = memchr(data, '\0', maxsize-12); + if (!end) + return NULL; + end++; + *used = end - data + 12; + return fontspec_new(data, + GET_32BIT_MSB_FIRST(end), + GET_32BIT_MSB_FIRST(end + 4), + GET_32BIT_MSB_FIRST(end + 8)); +} Index: windows/winnet.c ================================================================== --- windows/winnet.c +++ windows/winnet.c @@ -51,11 +51,10 @@ const struct socket_function_table *fn; /* the above variable absolutely *must* be the first in this structure */ char *error; SOCKET s; Plug plug; - void *private_ptr; bufchain output_data; int connected; int writable; int frozen; /* this causes readability notifications to be ignored */ int frozen_readable; /* this means we missed at least one readability @@ -62,10 +61,11 @@ * notification while we were frozen */ int localhost_only; /* for listening sockets */ char oobdata[1]; int sending_oob; int oobinline, nodelay, keepalive, privport; + enum { EOF_NO, EOF_PENDING, EOF_SENT } outgoingeof; SockAddr addr; SockAddrStep step; int port; int pending_error; /* in case send() returns error */ /* @@ -79,10 +79,12 @@ struct SockAddr_tag { int refcount; char *error; int resolved; + int namedpipe; /* indicates that this SockAddr is phony, holding a Windows + * named pipe pathname instead of a network address */ #ifndef NO_IPV6 struct addrinfo *ais; /* Addresses IPv6 style. */ #endif unsigned long *addresses; /* Addresses IPv4 style. */ int naddresses; @@ -165,10 +167,11 @@ DECL_WINDOWS_FUNCTION(static, int, setsockopt, (SOCKET, int, int, const char FAR *, int)); DECL_WINDOWS_FUNCTION(static, SOCKET, socket, (int, int, int)); DECL_WINDOWS_FUNCTION(static, int, listen, (SOCKET, int)); DECL_WINDOWS_FUNCTION(static, int, send, (SOCKET, const char FAR *, int, int)); +DECL_WINDOWS_FUNCTION(static, int, shutdown, (SOCKET, int)); DECL_WINDOWS_FUNCTION(static, int, ioctlsocket, (SOCKET, long, u_long FAR *)); DECL_WINDOWS_FUNCTION(static, SOCKET, accept, (SOCKET, struct sockaddr FAR *, int FAR *)); DECL_WINDOWS_FUNCTION(static, int, recv, (SOCKET, char FAR *, int, int)); @@ -289,10 +292,11 @@ GET_WINDOWS_FUNCTION(winsock_module, bind); GET_WINDOWS_FUNCTION(winsock_module, setsockopt); GET_WINDOWS_FUNCTION(winsock_module, socket); GET_WINDOWS_FUNCTION(winsock_module, listen); GET_WINDOWS_FUNCTION(winsock_module, send); + GET_WINDOWS_FUNCTION(winsock_module, shutdown); GET_WINDOWS_FUNCTION(winsock_module, ioctlsocket); GET_WINDOWS_FUNCTION(winsock_module, accept); GET_WINDOWS_FUNCTION(winsock_module, recv); GET_WINDOWS_FUNCTION(winsock_module, WSAIoctl); @@ -327,12 +331,42 @@ if (wship6_module) FreeLibrary(wship6_module); #endif } +struct errstring { + int error; + char *text; +}; + +static int errstring_find(void *av, void *bv) +{ + int *a = (int *)av; + struct errstring *b = (struct errstring *)bv; + if (*a < b->error) + return -1; + if (*a > b->error) + return +1; + return 0; +} +static int errstring_compare(void *av, void *bv) +{ + struct errstring *a = (struct errstring *)av; + return errstring_find(&a->error, bv); +} + +static tree234 *errstrings = NULL; + char *winsock_error_string(int error) { + const char prefix[] = "Network error: "; + struct errstring *es; + + /* + * Error codes we know about and have historically had reasonably + * sensible error messages for. + */ switch (error) { case WSAEACCES: return "Network error: Permission denied"; case WSAEADDRINUSE: return "Network error: Address already in use"; @@ -401,13 +435,57 @@ return "Network error: Connection timed out"; case WSAEWOULDBLOCK: return "Network error: Resource temporarily unavailable"; case WSAEDISCON: return "Network error: Graceful shutdown in progress"; - default: - return "Unknown network error"; + } + + /* + * Generic code to handle any other error. + * + * Slightly nasty hack here: we want to return a static string + * which the caller will never have to worry about freeing, but on + * the other hand if we call FormatMessage to get it then it will + * want to either allocate a buffer or write into one we own. + * + * So what we do is to maintain a tree234 of error strings we've + * already used. New ones are allocated from the heap, but then + * put in this tree and kept forever. + */ + + if (!errstrings) + errstrings = newtree234(errstring_compare); + + es = find234(errstrings, &error, errstring_find); + + if (!es) { + int bufsize, bufused; + + es = snew(struct errstring); + es->error = error; + /* maximum size for FormatMessage is 64K */ + bufsize = 65535 + sizeof(prefix); + es->text = snewn(bufsize, char); + strcpy(es->text, prefix); + bufused = strlen(es->text); + if (!FormatMessage((FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS), NULL, error, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + es->text + bufused, bufsize - bufused, NULL)) { + sprintf(es->text + bufused, + "Windows error code %d (and FormatMessage returned %d)", + error, GetLastError()); + } else { + int len = strlen(es->text); + if (len > 0 && es->text[len-1] == '\n') + es->text[len-1] = '\0'; + } + es->text = sresize(es->text, strlen(es->text) + 1, char); + add234(errstrings, es); } + + return es->text; } SockAddr sk_namelookup(const char *host, char **canonicalname, int address_family) { @@ -426,10 +504,11 @@ /* Clear the structure and default to IPv4. */ memset(ret, 0, sizeof(struct SockAddr_tag)); #ifndef NO_IPV6 ret->ais = NULL; #endif + ret->namedpipe = FALSE; ret->addresses = NULL; ret->resolved = FALSE; ret->refcount = 1; *realhost = '\0'; @@ -532,17 +611,35 @@ ret->error = NULL; ret->resolved = FALSE; #ifndef NO_IPV6 ret->ais = NULL; #endif + ret->namedpipe = FALSE; ret->addresses = NULL; ret->naddresses = 0; ret->refcount = 1; strncpy(ret->hostname, host, lenof(ret->hostname)); ret->hostname[lenof(ret->hostname)-1] = '\0'; return ret; } + +SockAddr sk_namedpipe_addr(const char *pipename) +{ + SockAddr ret = snew(struct SockAddr_tag); + ret->error = NULL; + ret->resolved = FALSE; +#ifndef NO_IPV6 + ret->ais = NULL; +#endif + ret->namedpipe = TRUE; + ret->addresses = NULL; + ret->naddresses = 0; + ret->refcount = 1; + strncpy(ret->hostname, pipename, lenof(ret->hostname)); + ret->hostname[lenof(ret->hostname)-1] = '\0'; + return ret; +} int sk_nextaddr(SockAddr addr, SockAddrStep *step) { #ifndef NO_IPV6 if (step->ai) { @@ -593,11 +690,16 @@ strncpy(buf, addr->hostname, buflen); buf[buflen-1] = '\0'; } } -int sk_hostname_is_local(char *name) +int sk_addr_needs_port(SockAddr addr) +{ + return addr->namedpipe ? FALSE : TRUE; +} + +int sk_hostname_is_local(const char *name) { return !strcmp(name, "localhost") || !strcmp(name, "::1") || !strncmp(name, "127.", 4); } @@ -640,11 +742,11 @@ START_STEP(addr, step); family = SOCKADDR_FAMILY(addr, step); #ifndef NO_IPV6 if (family == AF_INET6) { - return IN6_IS_ADDR_LOOPBACK((const struct in6_addr *)step.ai->ai_addr); + return IN6_IS_ADDR_LOOPBACK(&((const struct sockaddr_in6 *)step.ai->ai_addr)->sin6_addr); } else #endif if (family == AF_INET) { #ifndef NO_IPV6 if (step.ai) { @@ -661,10 +763,15 @@ } else { assert(family == AF_UNSPEC); return 0; /* we don't know; assume not */ } } + +int sk_address_is_special_local(SockAddr addr) +{ + return 0; /* no Unix-domain socket analogue here */ +} int sk_addrtype(SockAddr addr) { SockAddrStep step; int family; @@ -743,27 +850,25 @@ } static void sk_tcp_close(Socket s); static int sk_tcp_write(Socket s, const char *data, int len); static int sk_tcp_write_oob(Socket s, const char *data, int len); -static void sk_tcp_set_private_ptr(Socket s, void *ptr); -static void *sk_tcp_get_private_ptr(Socket s); +static void sk_tcp_write_eof(Socket s); static void sk_tcp_set_frozen(Socket s, int is_frozen); static const char *sk_tcp_socket_error(Socket s); extern char *do_select(SOCKET skt, int startup); -Socket sk_register(void *sock, Plug plug) +static Socket sk_tcp_accept(accept_ctx_t ctx, Plug plug) { static const struct socket_function_table fn_table = { sk_tcp_plug, sk_tcp_close, sk_tcp_write, sk_tcp_write_oob, + sk_tcp_write_eof, sk_tcp_flush, - sk_tcp_set_private_ptr, - sk_tcp_get_private_ptr, sk_tcp_set_frozen, sk_tcp_socket_error }; DWORD err; @@ -778,18 +883,19 @@ ret->error = NULL; ret->plug = plug; bufchain_init(&ret->output_data); ret->writable = 1; /* to start with */ ret->sending_oob = 0; + ret->outgoingeof = EOF_NO; ret->frozen = 1; ret->frozen_readable = 0; ret->localhost_only = 0; /* unused, but best init anyway */ ret->pending_error = 0; ret->parent = ret->child = NULL; ret->addr = NULL; - ret->s = (SOCKET)sock; + ret->s = (SOCKET)ctx.p; if (ret->s == INVALID_SOCKET) { err = p_WSAGetLastError(); ret->error = winsock_error_string(err); return (Socket) ret; @@ -1005,13 +1111,12 @@ static const struct socket_function_table fn_table = { sk_tcp_plug, sk_tcp_close, sk_tcp_write, sk_tcp_write_oob, + sk_tcp_write_eof, sk_tcp_flush, - sk_tcp_set_private_ptr, - sk_tcp_get_private_ptr, sk_tcp_set_frozen, sk_tcp_socket_error }; Actual_Socket ret; @@ -1026,10 +1131,11 @@ ret->plug = plug; bufchain_init(&ret->output_data); ret->connected = 0; /* to start with */ ret->writable = 0; /* to start with */ ret->sending_oob = 0; + ret->outgoingeof = EOF_NO; ret->frozen = 0; ret->frozen_readable = 0; ret->localhost_only = 0; /* unused, but best init anyway */ ret->pending_error = 0; ret->parent = ret->child = NULL; @@ -1056,13 +1162,12 @@ static const struct socket_function_table fn_table = { sk_tcp_plug, sk_tcp_close, sk_tcp_write, sk_tcp_write_oob, + sk_tcp_write_eof, sk_tcp_flush, - sk_tcp_set_private_ptr, - sk_tcp_get_private_ptr, sk_tcp_set_frozen, sk_tcp_socket_error }; SOCKET s; @@ -1087,10 +1192,11 @@ ret->error = NULL; ret->plug = plug; bufchain_init(&ret->output_data); ret->writable = 0; /* to start with */ ret->sending_oob = 0; + ret->outgoingeof = EOF_NO; ret->frozen = 0; ret->frozen_readable = 0; ret->localhost_only = local_host_only; ret->pending_error = 0; ret->parent = ret->child = NULL; @@ -1198,11 +1304,11 @@ } if (p_listen(s, SOMAXCONN) == SOCKET_ERROR) { p_closesocket(s); - ret->error = winsock_error_string(err); + ret->error = winsock_error_string(p_WSAGetLastError()); return (Socket) ret; } /* Set up a select mechanism. This could be an AsyncSelect on a * window, or an EventSelect on an event object. */ @@ -1253,10 +1359,31 @@ p_closesocket(s->s); if (s->addr) sk_addr_free(s->addr); sfree(s); } + +/* + * Deal with socket errors detected in try_send(). + */ +static void socket_error_callback(void *vs) +{ + Actual_Socket s = (Actual_Socket)vs; + + /* + * Just in case other socket work has caused this socket to vanish + * or become somehow non-erroneous before this callback arrived... + */ + if (!find234(sktree, s, NULL) || !s->pending_error) + return; + + /* + * An error has occurred on this socket. Pass it to the plug. + */ + plug_closing(s->plug, winsock_error_string(s->pending_error), + s->pending_error, 0); +} /* * The function which tries to send on a socket once it's deemed * writable. */ @@ -1303,10 +1430,11 @@ * reentrant. Instead we flag a pending error on * the socket, to be dealt with (by calling * plug_closing()) at some suitable future moment. */ s->pending_error = err; + queue_toplevel_callback(socket_error_callback, s); return; } else { /* We're inside the Windows frontend here, so we know * that the frontend handle is unnecessary. */ logevent(NULL, winsock_error_string(err)); @@ -1323,15 +1451,26 @@ } else { bufchain_consume(&s->output_data, nsent); } } } + + /* + * If we reach here, we've finished sending everything we might + * have needed to send. Send EOF, if we need to. + */ + if (s->outgoingeof == EOF_PENDING) { + p_shutdown(s->s, SD_SEND); + s->outgoingeof = EOF_SENT; + } } static int sk_tcp_write(Socket sock, const char *buf, int len) { Actual_Socket s = (Actual_Socket) sock; + + assert(s->outgoingeof == EOF_NO); /* * Add the data to the buffer list on the socket. */ bufchain_add(&s->output_data, buf, len); @@ -1347,10 +1486,12 @@ static int sk_tcp_write_oob(Socket sock, const char *buf, int len) { Actual_Socket s = (Actual_Socket) sock; + assert(s->outgoingeof == EOF_NO); + /* * Replace the buffer list on the socket with the data. */ bufchain_clear(&s->output_data); assert(len <= sizeof(s->oobdata)); @@ -1363,10 +1504,28 @@ if (s->writable) try_send(s); return s->sending_oob; } + +static void sk_tcp_write_eof(Socket sock) +{ + Actual_Socket s = (Actual_Socket) sock; + + assert(s->outgoingeof == EOF_NO); + + /* + * Mark the socket as pending outgoing EOF. + */ + s->outgoingeof = EOF_PENDING; + + /* + * Now try sending from the start of the buffer list. + */ + if (s->writable) + try_send(s); +} int select_result(WPARAM wParam, LPARAM lParam) { int ret, open; DWORD err; @@ -1516,10 +1675,11 @@ #else struct sockaddr_storage isa; #endif int addrlen = sizeof(isa); SOCKET t; /* socket of connection */ + accept_ctx_t actx; memset(&isa, 0, sizeof(isa)); err = 0; t = p_accept(s->s,(struct sockaddr *)&isa,&addrlen); if (t == INVALID_SOCKET) @@ -1526,82 +1686,31 @@ { err = p_WSAGetLastError(); if (err == WSATRY_AGAIN) break; } + + actx.p = (void *)t; + #ifndef NO_IPV6 if (isa.ss_family == AF_INET && s->localhost_only && !ipv4_is_local_addr(((struct sockaddr_in *)&isa)->sin_addr)) #else if (s->localhost_only && !ipv4_is_local_addr(isa.sin_addr)) #endif { p_closesocket(t); /* dodgy WinSock let nonlocal through */ - } else if (plug_accepting(s->plug, (void*)t)) { + } else if (plug_accepting(s->plug, sk_tcp_accept, actx)) { p_closesocket(t); /* denied or error */ } } } return 1; } -/* - * Deal with socket errors detected in try_send(). - */ -void net_pending_errors(void) -{ - int i; - Actual_Socket s; - - /* - * This might be a fiddly business, because it's just possible - * that handling a pending error on one socket might cause - * others to be closed. (I can't think of any reason this might - * happen in current SSH implementation, but to maintain - * generality of this network layer I'll assume the worst.) - * - * So what we'll do is search the socket list for _one_ socket - * with a pending error, and then handle it, and then search - * the list again _from the beginning_. Repeat until we make a - * pass with no socket errors present. That way we are - * protected against the socket list changing under our feet. - */ - - do { - for (i = 0; (s = index234(sktree, i)) != NULL; i++) { - if (s->pending_error) { - /* - * An error has occurred on this socket. Pass it to the - * plug. - */ - plug_closing(s->plug, - winsock_error_string(s->pending_error), - s->pending_error, 0); - break; - } - } - } while (s); -} - -/* - * Each socket abstraction contains a `void *' private field in - * which the client can keep state. - */ -static void sk_tcp_set_private_ptr(Socket sock, void *ptr) -{ - Actual_Socket s = (Actual_Socket) sock; - s->private_ptr = ptr; -} - -static void *sk_tcp_get_private_ptr(Socket sock) -{ - Actual_Socket s = (Actual_Socket) sock; - return s->private_ptr; -} - /* * Special error values are returned from sk_namelookup and sk_new * if there's a problem. These functions extract an error message, * or return NULL if there's no problem. */ Index: windows/winnoise.c ================================================================== --- windows/winnoise.c +++ windows/winnoise.c @@ -7,21 +7,30 @@ #include "putty.h" #include "ssh.h" #include "storage.h" +#include + +DECL_WINDOWS_FUNCTION(static, BOOL, CryptAcquireContextA, + (HCRYPTPROV *, LPCTSTR, LPCTSTR, DWORD, DWORD)); +DECL_WINDOWS_FUNCTION(static, BOOL, CryptGenRandom, + (HCRYPTPROV, DWORD, BYTE *)); +DECL_WINDOWS_FUNCTION(static, BOOL, CryptReleaseContext, + (HCRYPTPROV, DWORD)); +static HMODULE wincrypt_module = NULL; + /* - * This function is called once, at PuTTY startup, and will do some - * seriously silly things like listing directories and getting disk - * free space and a process snapshot. + * This function is called once, at PuTTY startup. */ void noise_get_heavy(void (*func) (void *, int)) { HANDLE srch; WIN32_FIND_DATA finddata; DWORD pid; + HCRYPTPROV crypt_provider; char winpath[MAX_PATH + 3]; GetWindowsDirectory(winpath, sizeof(winpath)); strcat(winpath, "\\*"); srch = FindFirstFile(winpath, &finddata); @@ -32,10 +41,28 @@ FindClose(srch); } pid = GetCurrentProcessId(); func(&pid, sizeof(pid)); + + if (!wincrypt_module) { + wincrypt_module = load_system32_dll("advapi32.dll"); + GET_WINDOWS_FUNCTION(wincrypt_module, CryptAcquireContextA); + GET_WINDOWS_FUNCTION(wincrypt_module, CryptGenRandom); + GET_WINDOWS_FUNCTION(wincrypt_module, CryptReleaseContext); + } + + if (wincrypt_module && p_CryptAcquireContextA && + p_CryptGenRandom && p_CryptReleaseContext && + p_CryptAcquireContextA(&crypt_provider, NULL, NULL, PROV_RSA_FULL, + CRYPT_VERIFYCONTEXT)) { + BYTE buf[32]; + if (p_CryptGenRandom(crypt_provider, 32, buf)) { + func(buf, sizeof(buf)); + } + p_CryptReleaseContext(crypt_provider, 0); + } read_random_seed(func); /* Update the seed immediately, in case another instance uses it. */ random_save_seed(); } ADDED windows/winnpc.c Index: windows/winnpc.c ================================================================== --- /dev/null +++ windows/winnpc.c @@ -0,0 +1,102 @@ +/* + * Windows support module which deals with being a named-pipe client. + */ + +#include +#include + +#define DEFINE_PLUG_METHOD_MACROS +#include "tree234.h" +#include "putty.h" +#include "network.h" +#include "proxy.h" +#include "ssh.h" + +#if !defined NO_SECURITY + +#include "winsecur.h" + +Socket make_handle_socket(HANDLE send_H, HANDLE recv_H, Plug plug, + int overlapped); + +Socket new_named_pipe_client(const char *pipename, Plug plug) +{ + HANDLE pipehandle; + PSID usersid, pipeowner; + PSECURITY_DESCRIPTOR psd; + char *err; + Socket ret; + + assert(strncmp(pipename, "\\\\.\\pipe\\", 9) == 0); + assert(strchr(pipename + 9, '\\') == NULL); + + while (1) { + pipehandle = CreateFile(pipename, GENERIC_READ | GENERIC_WRITE, + 0, NULL, OPEN_EXISTING, + FILE_FLAG_OVERLAPPED, NULL); + + if (pipehandle != INVALID_HANDLE_VALUE) + break; + + if (GetLastError() != ERROR_PIPE_BUSY) { + err = dupprintf("Unable to open named pipe '%s': %s", + pipename, win_strerror(GetLastError())); + ret = new_error_socket(err, plug); + sfree(err); + return ret; + } + + /* + * If we got ERROR_PIPE_BUSY, wait for the server to + * create a new pipe instance. (Since the server is + * expected to be winnps.c, which will do that immediately + * after a previous connection is accepted, that shouldn't + * take excessively long.) + */ + if (!WaitNamedPipe(pipename, NMPWAIT_USE_DEFAULT_WAIT)) { + err = dupprintf("Error waiting for named pipe '%s': %s", + pipename, win_strerror(GetLastError())); + ret = new_error_socket(err, plug); + sfree(err); + return ret; + } + } + + if ((usersid = get_user_sid()) == NULL) { + CloseHandle(pipehandle); + err = dupprintf("Unable to get user SID"); + ret = new_error_socket(err, plug); + sfree(err); + return ret; + } + + if (p_GetSecurityInfo(pipehandle, SE_KERNEL_OBJECT, + OWNER_SECURITY_INFORMATION, + &pipeowner, NULL, NULL, NULL, + &psd) != ERROR_SUCCESS) { + err = dupprintf("Unable to get named pipe security information: %s", + win_strerror(GetLastError())); + ret = new_error_socket(err, plug); + sfree(err); + CloseHandle(pipehandle); + sfree(usersid); + return ret; + } + + if (!EqualSid(pipeowner, usersid)) { + err = dupprintf("Owner of named pipe '%s' is not us", pipename); + ret = new_error_socket(err, plug); + sfree(err); + CloseHandle(pipehandle); + LocalFree(psd); + sfree(usersid); + return ret; + } + + LocalFree(psd); + sfree(usersid); + + return make_handle_socket(pipehandle, pipehandle, plug, TRUE); +} + +#endif /* !defined NO_SECURITY */ ADDED windows/winnps.c Index: windows/winnps.c ================================================================== --- /dev/null +++ windows/winnps.c @@ -0,0 +1,253 @@ +/* + * Windows support module which deals with being a named-pipe server. + */ + +#include +#include + +#define DEFINE_PLUG_METHOD_MACROS +#include "tree234.h" +#include "putty.h" +#include "network.h" +#include "proxy.h" +#include "ssh.h" + +#if !defined NO_SECURITY + +#include "winsecur.h" + +Socket make_handle_socket(HANDLE send_H, HANDLE recv_H, Plug plug, + int overlapped); + +typedef struct Socket_named_pipe_server_tag *Named_Pipe_Server_Socket; +struct Socket_named_pipe_server_tag { + const struct socket_function_table *fn; + /* the above variable absolutely *must* be the first in this structure */ + + /* Parameters for (repeated) creation of named pipe objects */ + PSECURITY_DESCRIPTOR psd; + PSID networksid; + PACL acl; + char *pipename; + + /* The current named pipe object + attempt to connect to it */ + HANDLE pipehandle; + OVERLAPPED connect_ovl; + + /* PuTTY Socket machinery */ + Plug plug; + char *error; +}; + +static Plug sk_namedpipeserver_plug(Socket s, Plug p) +{ + Named_Pipe_Server_Socket ps = (Named_Pipe_Server_Socket) s; + Plug ret = ps->plug; + if (p) + ps->plug = p; + return ret; +} + +static void sk_namedpipeserver_close(Socket s) +{ + Named_Pipe_Server_Socket ps = (Named_Pipe_Server_Socket) s; + + CloseHandle(ps->pipehandle); + CloseHandle(ps->connect_ovl.hEvent); + sfree(ps->error); + sfree(ps->pipename); + if (ps->networksid) + LocalFree(ps->networksid); + if (ps->acl) + LocalFree(ps->acl); + if (ps->psd) + LocalFree(ps->psd); + sfree(ps); +} + +static const char *sk_namedpipeserver_socket_error(Socket s) +{ + Named_Pipe_Server_Socket ps = (Named_Pipe_Server_Socket) s; + return ps->error; +} + +static int create_named_pipe(Named_Pipe_Server_Socket ps, int first_instance) +{ + SECURITY_ATTRIBUTES sa; + + memset(&sa, 0, sizeof(sa)); + sa.nLength = sizeof(sa); + sa.lpSecurityDescriptor = ps->psd; + sa.bInheritHandle = FALSE; + + ps->pipehandle = CreateNamedPipe + (/* lpName */ + ps->pipename, + + /* dwOpenMode */ + PIPE_ACCESS_DUPLEX | + FILE_FLAG_OVERLAPPED | + (first_instance ? FILE_FLAG_FIRST_PIPE_INSTANCE : 0), + + /* dwPipeMode */ + PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT +#ifdef PIPE_REJECT_REMOTE_CLIENTS + | PIPE_REJECT_REMOTE_CLIENTS +#endif + , + + /* nMaxInstances */ + PIPE_UNLIMITED_INSTANCES, + + /* nOutBufferSize, nInBufferSize */ + 4096, 4096, /* FIXME: think harder about buffer sizes? */ + + /* nDefaultTimeOut */ + 0 /* default timeout */, + + /* lpSecurityAttributes */ + &sa); + + return ps->pipehandle != INVALID_HANDLE_VALUE; +} + +static Socket named_pipe_accept(accept_ctx_t ctx, Plug plug) +{ + HANDLE conn = (HANDLE)ctx.p; + + return make_handle_socket(conn, conn, plug, TRUE); +} + +/* + * Dummy SockAddr type which just holds a named pipe address. Only + * used for calling plug_log from named_pipe_accept_loop() here. + */ +SockAddr sk_namedpipe_addr(const char *pipename); + +static void named_pipe_accept_loop(Named_Pipe_Server_Socket ps, + int got_one_already) +{ + while (1) { + int error; + char *errmsg; + + if (got_one_already) { + /* If we were called with a connection already waiting, + * skip this step. */ + got_one_already = FALSE; + error = 0; + } else { + /* + * Call ConnectNamedPipe, which might succeed or might + * tell us that an overlapped operation is in progress and + * we should wait for our event object. + */ + if (ConnectNamedPipe(ps->pipehandle, &ps->connect_ovl)) + error = 0; + else + error = GetLastError(); + + if (error == ERROR_IO_PENDING) + return; + } + + if (error == 0 || error == ERROR_PIPE_CONNECTED) { + /* + * We've successfully retrieved an incoming connection, so + * ps->pipehandle now refers to that connection. So + * convert that handle into a separate connection-type + * Socket, and create a fresh one to be the new listening + * pipe. + */ + HANDLE conn = ps->pipehandle; + accept_ctx_t actx; + + actx.p = (void *)conn; + if (plug_accepting(ps->plug, named_pipe_accept, actx)) { + /* + * If the plug didn't want the connection, might as + * well close this handle. + */ + CloseHandle(conn); + } + + if (!create_named_pipe(ps, FALSE)) { + error = GetLastError(); + } else { + /* + * Go round again to see if more connections can be + * got, or to begin waiting on the event object. + */ + continue; + } + } + + errmsg = dupprintf("Error while listening to named pipe: %s", + win_strerror(error)); + plug_log(ps->plug, 1, sk_namedpipe_addr(ps->pipename), 0, + errmsg, error); + sfree(errmsg); + break; + } +} + +static void named_pipe_connect_callback(void *vps) +{ + Named_Pipe_Server_Socket ps = (Named_Pipe_Server_Socket)vps; + named_pipe_accept_loop(ps, TRUE); +} + +Socket new_named_pipe_listener(const char *pipename, Plug plug) +{ + /* + * This socket type is only used for listening, so it should never + * be asked to write or flush or set_frozen. + */ + static const struct socket_function_table socket_fn_table = { + sk_namedpipeserver_plug, + sk_namedpipeserver_close, + NULL /* write */, + NULL /* write_oob */, + NULL /* write_eof */, + NULL /* flush */, + NULL /* set_frozen */, + sk_namedpipeserver_socket_error + }; + + Named_Pipe_Server_Socket ret; + + ret = snew(struct Socket_named_pipe_server_tag); + ret->fn = &socket_fn_table; + ret->plug = plug; + ret->error = NULL; + ret->psd = NULL; + ret->pipename = dupstr(pipename); + ret->networksid = NULL; + ret->acl = NULL; + + assert(strncmp(pipename, "\\\\.\\pipe\\", 9) == 0); + assert(strchr(pipename + 9, '\\') == NULL); + + if (!make_private_security_descriptor(GENERIC_READ | GENERIC_WRITE, + &ret->psd, &ret->networksid, + &ret->acl, &ret->error)) { + goto cleanup; + } + + if (!create_named_pipe(ret, TRUE)) { + ret->error = dupprintf("unable to create named pipe '%s': %s", + pipename, win_strerror(GetLastError())); + goto cleanup; + } + + memset(&ret->connect_ovl, 0, sizeof(ret->connect_ovl)); + ret->connect_ovl.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + handle_add_foreign_event(ret->connect_ovl.hEvent, + named_pipe_connect_callback, ret); + named_pipe_accept_loop(ret, FALSE); + + cleanup: + return (Socket) ret; +} + +#endif /* !defined NO_SECURITY */ Index: windows/winpgen.c ================================================================== --- windows/winpgen.c +++ windows/winpgen.c @@ -3,10 +3,11 @@ */ #include #include #include +#include #define PUTTY_DO_GLOBALS #include "putty.h" #include "ssh.h" @@ -17,11 +18,11 @@ #define ICON_BIG 1 #endif #define WM_DONEKEY (WM_APP + 1) -#define DEFAULT_KEYSIZE 1024 +#define DEFAULT_KEYSIZE 2048 static char *cmdline_keyfile = NULL; /* * Print a modal (Really Bad) message box and perform a fatal exit. @@ -37,10 +38,26 @@ MessageBox(NULL, stuff, "PuTTYgen Fatal Error", MB_SYSTEMMODAL | MB_ICONERROR | MB_OK); sfree(stuff); exit(1); } + +/* + * Print a non-fatal message box and do not exit. + */ +void nonfatal(char *fmt, ...) +{ + va_list ap; + char *stuff; + + va_start(ap, fmt); + stuff = dupvprintf(fmt, ap); + va_end(ap); + MessageBox(NULL, stuff, "PuTTYgen Error", + MB_SYSTEMMODAL | MB_ICONERROR | MB_OK); + sfree(stuff); +} /* ---------------------------------------------------------------------- * Progress report code. This is really horrible :-) */ #define PROGRESSRANGE 65535 @@ -114,24 +131,22 @@ } } extern char ver[]; -#define PASSPHRASE_MAXLEN 512 - struct PassphraseProcStruct { - char *passphrase; + char **passphrase; char *comment; }; /* * Dialog-box function for the passphrase box. */ static int CALLBACK PassphraseProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { - static char *passphrase = NULL; + static char **passphrase = NULL; struct PassphraseProcStruct *p; switch (msg) { case WM_INITDIALOG: SetForegroundWindow(hwnd); @@ -155,12 +170,13 @@ p = (struct PassphraseProcStruct *) lParam; passphrase = p->passphrase; if (p->comment) SetDlgItemText(hwnd, 101, p->comment); - *passphrase = 0; - SetDlgItemText(hwnd, 102, passphrase); + burnstr(*passphrase); + *passphrase = dupstr(""); + SetDlgItemText(hwnd, 102, *passphrase); return 0; case WM_COMMAND: switch (LOWORD(wParam)) { case IDOK: if (*passphrase) @@ -171,13 +187,12 @@ case IDCANCEL: EndDialog(hwnd, 0); return 0; case 102: /* edit box */ if ((HIWORD(wParam) == EN_CHANGE) && passphrase) { - GetDlgItemText(hwnd, 102, passphrase, - PASSPHRASE_MAXLEN - 1); - passphrase[PASSPHRASE_MAXLEN - 1] = '\0'; + burnstr(*passphrase); + *passphrase = GetDlgItemText_alloc(hwnd, 102); } return 0; } return 0; case WM_CLOSE: @@ -403,15 +418,15 @@ static int save_ssh1_pubkey(char *filename, struct RSAKey *key) { char *dec1, *dec2; FILE *fp; - dec1 = bignum_decimal(key->exponent); - dec2 = bignum_decimal(key->modulus); fp = fopen(filename, "wb"); if (!fp) return 0; + dec1 = bignum_decimal(key->exponent); + dec2 = bignum_decimal(key->modulus); fprintf(fp, "%d %s %s %s\n", bignum_bitcount(key->modulus), dec1, dec2, key->comment); fclose(fp); sfree(dec1); sfree(dec2); @@ -613,23 +628,22 @@ break; } } void load_key_file(HWND hwnd, struct MainDlgState *state, - Filename filename, int was_import_cmd) + Filename *filename, int was_import_cmd) { - char passphrase[PASSPHRASE_MAXLEN]; + char *passphrase; int needs_pass; int type, realtype; int ret; const char *errmsg = NULL; char *comment; - struct PassphraseProcStruct pps; struct RSAKey newkey1; struct ssh2_userkey *newkey2 = NULL; - type = realtype = key_type(&filename); + type = realtype = key_type(filename); if (type != SSH_KEYTYPE_SSH1 && type != SSH_KEYTYPE_SSH2 && !import_possible(type)) { char *msg = dupprintf("Couldn't load private key (%s)", key_type_to_str(type)); @@ -644,47 +658,48 @@ realtype = type; type = import_target_type(type); } comment = NULL; + passphrase = NULL; if (realtype == SSH_KEYTYPE_SSH1) - needs_pass = rsakey_encrypted(&filename, &comment); + needs_pass = rsakey_encrypted(filename, &comment); else if (realtype == SSH_KEYTYPE_SSH2) - needs_pass = - ssh2_userkey_encrypted(&filename, &comment); + needs_pass = ssh2_userkey_encrypted(filename, &comment); else - needs_pass = import_encrypted(&filename, realtype, - &comment); - pps.passphrase = passphrase; - pps.comment = comment; + needs_pass = import_encrypted(filename, realtype, &comment); do { + burnstr(passphrase); + passphrase = NULL; + if (needs_pass) { int dlgret; + struct PassphraseProcStruct pps; + pps.passphrase = &passphrase; + pps.comment = comment; dlgret = DialogBoxParam(hinst, MAKEINTRESOURCE(210), NULL, PassphraseProc, (LPARAM) &pps); if (!dlgret) { ret = -2; break; } + assert(passphrase != NULL); } else - *passphrase = '\0'; + passphrase = dupstr(""); if (type == SSH_KEYTYPE_SSH1) { if (realtype == type) - ret = loadrsakey(&filename, &newkey1, - passphrase, &errmsg); + ret = loadrsakey(filename, &newkey1, passphrase, &errmsg); else - ret = import_ssh1(&filename, realtype, - &newkey1, passphrase, &errmsg); + ret = import_ssh1(filename, realtype, &newkey1, + passphrase, &errmsg); } else { if (realtype == type) - newkey2 = ssh2_load_userkey(&filename, - passphrase, &errmsg); + newkey2 = ssh2_load_userkey(filename, passphrase, &errmsg); else - newkey2 = import_ssh2(&filename, realtype, - passphrase, &errmsg); + newkey2 = import_ssh2(filename, realtype, passphrase, &errmsg); if (newkey2 == SSH2_WRONG_PASSPHRASE) ret = -1; else if (!newkey2) ret = 0; else @@ -782,10 +797,11 @@ key_type_to_str(realtype)); MessageBox(NULL, msg, "PuTTYgen Notice", MB_OK | MB_ICONINFORMATION); } } + burnstr(passphrase); } /* * Dialog-box function for the main PuTTYgen dialog box. */ @@ -936,12 +952,15 @@ ui_set_state(hwnd, state, 0); /* * Load a key file if one was provided on the command line. */ - if (cmdline_keyfile) - load_key_file(hwnd, state, filename_from_str(cmdline_keyfile), 0); + if (cmdline_keyfile) { + Filename *fn = filename_from_str(cmdline_keyfile); + load_key_file(hwnd, state, fn, 0); + filename_free(fn); + } return 1; case WM_MOUSEMOVE: state = (struct MainDlgState *) GetWindowLongPtr(hwnd, GWLP_USERDATA); if (state->collecting_entropy && @@ -956,11 +975,11 @@ /* * Seed the entropy pool */ random_add_heavynoise(state->entropy, state->entropy_size); - memset(state->entropy, 0, state->entropy_size); + smemclr(state->entropy, state->entropy_size); sfree(state->entropy); state->collecting_entropy = FALSE; SetDlgItemText(hwnd, IDC_GENERATING, generating_msg); SendDlgItemMessage(hwnd, IDC_PROGRESS, PBM_SETRANGE, 0, @@ -1100,12 +1119,11 @@ break; state = (struct MainDlgState *) GetWindowLongPtr(hwnd, GWLP_USERDATA); if (state->key_exists) { char filename[FILENAME_MAX]; - char passphrase[PASSPHRASE_MAXLEN]; - char passphrase2[PASSPHRASE_MAXLEN]; + char *passphrase, *passphrase2; int type, realtype; if (state->ssh2) realtype = SSH_KEYTYPE_SSH2; else @@ -1127,29 +1145,32 @@ MessageBox(hwnd, msg, "PuTTYgen Error", MB_OK | MB_ICONERROR); break; } - GetDlgItemText(hwnd, IDC_PASSPHRASE1EDIT, - passphrase, sizeof(passphrase)); - GetDlgItemText(hwnd, IDC_PASSPHRASE2EDIT, - passphrase2, sizeof(passphrase2)); + passphrase = GetDlgItemText_alloc(hwnd, IDC_PASSPHRASE1EDIT); + passphrase2 = GetDlgItemText_alloc(hwnd, IDC_PASSPHRASE2EDIT); if (strcmp(passphrase, passphrase2)) { MessageBox(hwnd, "The two passphrases given do not match.", "PuTTYgen Error", MB_OK | MB_ICONERROR); + burnstr(passphrase); + burnstr(passphrase2); break; } + burnstr(passphrase2); if (!*passphrase) { int ret; ret = MessageBox(hwnd, "Are you sure you want to save this key\n" "without a passphrase to protect it?", "PuTTYgen Warning", MB_YESNO | MB_ICONWARNING); - if (ret != IDYES) - break; + if (ret != IDYES) { + burnstr(passphrase); + break; + } } if (prompt_keyfile(hwnd, "Save private key as:", filename, 1, (type == realtype))) { int ret; FILE *fp = fopen(filename, "r"); @@ -1159,37 +1180,42 @@ buffer = dupprintf("Overwrite existing file\n%s?", filename); ret = MessageBox(hwnd, buffer, "PuTTYgen Warning", MB_YESNO | MB_ICONWARNING); sfree(buffer); - if (ret != IDYES) + if (ret != IDYES) { + burnstr(passphrase); break; + } } if (state->ssh2) { - Filename fn = filename_from_str(filename); + Filename *fn = filename_from_str(filename); if (type != realtype) - ret = export_ssh2(&fn, type, &state->ssh2key, + ret = export_ssh2(fn, type, &state->ssh2key, *passphrase ? passphrase : NULL); else - ret = ssh2_save_userkey(&fn, &state->ssh2key, + ret = ssh2_save_userkey(fn, &state->ssh2key, *passphrase ? passphrase : NULL); + filename_free(fn); } else { - Filename fn = filename_from_str(filename); + Filename *fn = filename_from_str(filename); if (type != realtype) - ret = export_ssh1(&fn, type, &state->key, + ret = export_ssh1(fn, type, &state->key, *passphrase ? passphrase : NULL); else - ret = saversakey(&fn, &state->key, + ret = saversakey(fn, &state->key, *passphrase ? passphrase : NULL); + filename_free(fn); } if (ret <= 0) { MessageBox(hwnd, "Unable to save key file", "PuTTYgen Error", MB_OK | MB_ICONERROR); } } + burnstr(passphrase); } break; case IDC_SAVEPUB: if (HIWORD(wParam) != BN_CLICKED) break; @@ -1231,13 +1257,15 @@ state = (struct MainDlgState *) GetWindowLongPtr(hwnd, GWLP_USERDATA); if (!state->generation_thread_exists) { char filename[FILENAME_MAX]; if (prompt_keyfile(hwnd, "Load private key:", - filename, 0, LOWORD(wParam)==IDC_LOAD)) - load_key_file(hwnd, state, filename_from_str(filename), - LOWORD(wParam) != IDC_LOAD); + filename, 0, LOWORD(wParam)==IDC_LOAD)) { + Filename *fn = filename_from_str(filename); + load_key_file(hwnd, state, fn, LOWORD(wParam) != IDC_LOAD); + filename_free(fn); + } } break; } return 0; case WM_DONEKEY: Index: windows/winpgnt.c ================================================================== --- windows/winpgnt.c +++ windows/winpgnt.c @@ -12,28 +12,14 @@ #include "putty.h" #include "ssh.h" #include "misc.h" #include "tree234.h" +#include "winsecur.h" #include -/* PuTTY SC start */ -#include -#include "storage.h" -#include "sc.h" -/* PuTTY SC end */ - -/* PuTTY CAPI start */ -#ifdef _WINDOWS -#include -#include -#include -#include "capi.h" -#endif -/* PuTTY CAPI end */ - #ifndef NO_SECURITY #include #ifdef DEBUG_IPC #define _WIN32_WINNT 0x0500 /* for ConvertSidToStringSid */ #include @@ -126,19 +112,13 @@ } *out = '\0'; return; } -static tree234 *rsakeys, *ssh2keys, *capikeys; +static tree234 *rsakeys, *ssh2keys; static int has_security; -#ifndef NO_SECURITY -DECL_WINDOWS_FUNCTION(extern, DWORD, GetSecurityInfo, - (HANDLE, SE_OBJECT_TYPE, SECURITY_INFORMATION, - PSID *, PSID *, PACL *, PACL *, - PSECURITY_DESCRIPTOR *)); -#endif /* * Forward references */ static void *make_keylist1(int *length); @@ -172,14 +152,12 @@ unsigned char *blob; int len; }; static int cmpkeys_ssh2_asymm(void *av, void *bv); -#define PASSPHRASE_MAXLEN 512 - struct PassphraseProcStruct { - char *passphrase; + char **passphrase; char *comment; }; static tree234 *passphrases = NULL; @@ -189,11 +167,11 @@ */ static void forget_passphrases(void) { while (count234(passphrases) > 0) { char *pp = index234(passphrases, 0); - memset(pp, 0, strlen(pp)); + smemclr(pp, strlen(pp)); delpos234(passphrases, 0); free(pp); } } @@ -254,84 +232,17 @@ return 0; } static HWND passphrase_box; -/* PuTTY SC start */ -sc_lib *sclib = NULL; -char pkcs11_token_label[70]; -char pkcs11_cert_label[70]; -char sc_save_passphrase[PASSPHRASE_MAXLEN]; -int sc_activate_pwd_cache = 0; -void logevent(void *f, const char *msg) { -} -static int CALLBACK sc_PassphraseProc(HWND hwnd, UINT msg, - WPARAM wParam, LPARAM lParam) { - static char *passphrase = NULL; - struct PassphraseProcStruct *p; - switch (msg) { - case WM_INITDIALOG: - passphrase_box = hwnd; - /* - * Centre the window. - */ - { /* centre the window */ - RECT rs, rd; - HWND hw; - hw = GetDesktopWindow(); - if (GetWindowRect(hw, &rs) && GetWindowRect(hwnd, &rd)) - MoveWindow(hwnd, - (rs.right + rs.left + rd.left - rd.right) / 2, - (rs.bottom + rs.top + rd.top - rd.bottom) / 2, - rd.right - rd.left, rd.bottom - rd.top, TRUE); - } - SetForegroundWindow(hwnd); - SetWindowPos(hwnd, HWND_TOP, 0, 0, 0, 0, - SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW); - p = (struct PassphraseProcStruct *) lParam; - passphrase = p->passphrase; - if (p->comment) - SetDlgItemText(hwnd, 101, p->comment); - *passphrase = 0; - SetDlgItemText(hwnd, 102, passphrase); - return 0; - case WM_COMMAND: - switch (LOWORD(wParam)) { - case IDOK: - if (*passphrase) - EndDialog(hwnd, 1); - else - MessageBeep(0); - return 0; - case IDCANCEL: - sc_activate_pwd_cache = 1; - EndDialog(hwnd, 0); - return 0; - case 102: /* edit box */ - if ((HIWORD(wParam) == EN_CHANGE) && passphrase) { - GetDlgItemText(hwnd, 102, passphrase, - PASSPHRASE_MAXLEN - 1); - passphrase[PASSPHRASE_MAXLEN - 1] = '\0'; - } - return 0; - } - return 0; - case WM_CLOSE: - EndDialog(hwnd, 0); - return 0; - } - return 0; -} -/* PuTTY SC end */ - /* * Dialog-box function for the passphrase box. */ static int CALLBACK PassphraseProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { - static char *passphrase = NULL; + static char **passphrase = NULL; struct PassphraseProcStruct *p; switch (msg) { case WM_INITDIALOG: passphrase_box = hwnd; @@ -355,12 +266,13 @@ SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW); p = (struct PassphraseProcStruct *) lParam; passphrase = p->passphrase; if (p->comment) SetDlgItemText(hwnd, 101, p->comment); - *passphrase = 0; - SetDlgItemText(hwnd, 102, passphrase); + burnstr(*passphrase); + *passphrase = dupstr(""); + SetDlgItemText(hwnd, 102, *passphrase); return 0; case WM_COMMAND: switch (LOWORD(wParam)) { case IDOK: if (*passphrase) @@ -371,13 +283,12 @@ case IDCANCEL: EndDialog(hwnd, 0); return 0; case 102: /* edit box */ if ((HIWORD(wParam) == EN_CHANGE) && passphrase) { - GetDlgItemText(hwnd, 102, passphrase, - PASSPHRASE_MAXLEN - 1); - passphrase[PASSPHRASE_MAXLEN - 1] = '\0'; + burnstr(*passphrase); + *passphrase = GetDlgItemText_alloc(hwnd, 102); } return 0; } return 0; case WM_CLOSE: @@ -413,13 +324,10 @@ static void keylist_update(void) { struct RSAKey *rkey; struct ssh2_userkey *skey; int i; -/* PuTTY CAPI start */ - struct CAPI_userkey *ckey; -/* PuTTY CAPI end */ if (keylist) { SendDlgItemMessage(keylist, 100, LB_RESETCONTENT, 0, 0); for (i = 0; NULL != (rkey = index234(rsakeys, i)); i++) { char listentry[512], *p; @@ -438,220 +346,61 @@ *p = '\t'; SendDlgItemMessage(keylist, 100, LB_ADDSTRING, 0, (LPARAM) listentry); } for (i = 0; NULL != (skey = index234(ssh2keys, i)); i++) { - char listentry[512], *p; - int len; + char *listentry, *p; + int fp_len; /* * Replace two spaces in the fingerprint with tabs, for * nice alignment in the box. */ p = skey->alg->fingerprint(skey->data); - strncpy(listentry, p, sizeof(listentry)); + listentry = dupprintf("%s\t%s", p, skey->comment); + fp_len = strlen(listentry); + sfree(p); + p = strchr(listentry, ' '); - if (p) + if (p && p < listentry + fp_len) *p = '\t'; p = strchr(listentry, ' '); - if (p) + if (p && p < listentry + fp_len) *p = '\t'; - len = strlen(listentry); - if (len < sizeof(listentry) - 2) { - listentry[len] = '\t'; - strncpy(listentry + len + 1, skey->comment, - sizeof(listentry) - len - 1); - } + SendDlgItemMessage(keylist, 100, LB_ADDSTRING, 0, (LPARAM) listentry); - } -/* PuTTY CAPI start */ - for (i = 0; NULL != (ckey = index234(capikeys, i)); i++) { - char listentry[512]; - memset(listentry, 0, sizeof(listentry)); - _snprintf(listentry, sizeof(listentry)-1, "CAPI\t%s", ckey->certID); - SendDlgItemMessage(keylist, 100, LB_ADDSTRING, 0, (LPARAM) listentry); - } -/* PuTTY CAPI end */ + sfree(listentry); + } SendDlgItemMessage(keylist, 100, LB_SETCURSEL, (WPARAM) - 1, 0); } } -/* PuTTY CAPI start */ -typedef BOOL (WINAPI *PCertSelectCertificateA)( -__inout PCERT_SELECT_STRUCT_A pCertSelectInfo -); - -static void prompt_add_CAPIkey(HWND hwnd) { - HCERTSTORE hStore = NULL; - CERT_SELECT_STRUCT_A* css = NULL; - CERT_CONTEXT** acc = NULL; - unsigned int tmpSHA1size = 0, dwCertStoreUser; - unsigned char tmpSHA1[20]; - char tmpSHA1hex[41] = ""; - char tmpCertID[100] = ""; - char* tmpCertID_alloced = NULL; - HMODULE hCertDlgDLL = NULL; - PCertSelectCertificateA f_csca = NULL; - int i = 0; // TODO: Let the user choose this - struct CAPI_userkey* ckey = NULL; - - if ((hCertDlgDLL = LoadLibrary("CryptDlg.dll")) == NULL) - goto cleanup; - if ((f_csca = (PCertSelectCertificateA) GetProcAddress(hCertDlgDLL, "CertSelectCertificateA")) == NULL) - goto cleanup; - - dwCertStoreUser = CERT_SYSTEM_STORE_CURRENT_USER; - if (i == 1) - dwCertStoreUser = CERT_SYSTEM_STORE_LOCAL_MACHINE; - - if ((hStore = CertOpenStore(CERT_STORE_PROV_SYSTEM_A, PKCS_7_ASN_ENCODING | X509_ASN_ENCODING, 0 /*hCryptProv*/, dwCertStoreUser | CERT_STORE_READONLY_FLAG | CERT_STORE_OPEN_EXISTING_FLAG | CERT_STORE_ENUM_ARCHIVED_FLAG, "MY")) == NULL) - goto cleanup; - - acc = (CERT_CONTEXT**) malloc(sizeof(CERT_CONTEXT*)); - acc[0] = NULL; - css = (CERT_SELECT_STRUCT_A*) malloc(sizeof(CERT_SELECT_STRUCT_A)); - memset(css, 0, sizeof(CERT_SELECT_STRUCT_A)); - css->dwSize = sizeof(CERT_SELECT_STRUCT_A); - css->hwndParent = hwnd; - css->hInstance = NULL; - css->pTemplateName = NULL; - css->dwFlags = 0; - css->szTitle = "PuTTY: Select Certificate for CAPI Auth"; - css->cCertStore = 1; - css->arrayCertStore = &hStore; - css->szPurposeOid = szOID_PKIX_KP_CLIENT_AUTH; - css->cCertContext = 1; // count of arrayCertContext indexes allocated - css->arrayCertContext = acc; - - if (!f_csca(css)) // GetProcAddress(hCertDlgDLL, "CertSelectCertificateA") - goto cleanup; - - if (css->cCertContext != 1) - goto cleanup; - if (acc[0] == NULL) - goto cleanup; - - tmpSHA1size = sizeof(tmpSHA1); - if (!CertGetCertificateContextProperty(acc[0], CERT_HASH_PROP_ID, tmpSHA1, &tmpSHA1size)) - memset(tmpSHA1, 0, sizeof(tmpSHA1)); - _snprintf(tmpSHA1hex, sizeof(tmpSHA1hex)-1, "%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X", tmpSHA1[0], tmpSHA1[1], tmpSHA1[2], tmpSHA1[3], tmpSHA1[4], tmpSHA1[5], tmpSHA1[6], tmpSHA1[7], tmpSHA1[8], tmpSHA1[9], tmpSHA1[10], tmpSHA1[11], tmpSHA1[12], tmpSHA1[13], tmpSHA1[14], tmpSHA1[15], tmpSHA1[16], tmpSHA1[17], tmpSHA1[18], tmpSHA1[19]); - tmpSHA1hex[sizeof(tmpSHA1hex)-1] = '\0'; - - _snprintf(tmpCertID, sizeof(tmpCertID)-1, "%s\\%s", i == 1 ? "Machine\\MY" : "User\\MY", tmpSHA1hex); - tmpCertID[sizeof(tmpCertID)-1] = '\0'; - - if ((ckey = Create_CAPI_userkey(tmpCertID, acc[0])) == NULL) - goto cleanup; - if (add234(capikeys, ckey) != ckey) - Free_CAPI_userkey(ckey); - - keylist_update(); - -cleanup: - if (hCertDlgDLL) { - FreeLibrary(hCertDlgDLL); - f_csca = NULL; - hCertDlgDLL = NULL; - } - if (acc) { - if (acc[0]) - CertFreeCertificateContext(acc[0]); - acc[0] = NULL; - free(acc); - acc = NULL; - } - - if (css) - free(css); - css = NULL; - - if (hStore) - CertCloseStore(hStore, 0); - hStore = NULL; - - return; -} -// Key comparison function for the 2-3-4 tree of CAPI keys (struct CAPI_userkey). -int cmpkeys_capi(void *av, void *bv) { - struct CAPI_userkey *a, *b; - a = (struct CAPI_userkey *) av; - b = (struct CAPI_userkey *) bv; - return strcmp(a->certID, b->certID); -} -// Key comparison function for the 2-3-4 tree of CAPI keys (struct CAPI_userkey) where the first argument is a blob. -static int cmpkeys_capi_blob(void *av, void *bv) { - struct blob *a = (struct blob *) av; - struct CAPI_userkey *b = (struct CAPI_userkey *) bv; - int i; - int c; - - // Compare purely by public blob. - c = 0; - for (i = 0; i < a->len && i < b->bloblen; i++) { - if (a->blob[i] < b->blob[i]) { - c = -1; - break; - } else if (a->blob[i] > b->blob[i]) { - c = +1; - break; - } - } - if (c == 0 && i < a->len) - c = +1; /* a is longer */ - if (c == 0 && i < b->bloblen) - c = -1; /* b is longer */ - - return c; -} -/* PuTTY CAPI end */ - /* * This function loads a key from a file and adds it. */ -static void add_keyfile(Filename filename) +static void add_keyfile(Filename *filename) { - char passphrase[PASSPHRASE_MAXLEN]; + char *passphrase; struct RSAKey *rkey = NULL; struct ssh2_userkey *skey = NULL; int needs_pass; int ret; int attempts; char *comment; const char *error = NULL; - struct PassphraseProcStruct pps; int type; int original_pass; -/* PuTTY CAPI start */ - BOOL CAPI_KEY = FALSE; - struct CAPI_userkey *ckey = NULL; - - if (strnicmp(filename_to_str(&filename), "CAPI:", 5) == 0) { - const char *fn = filename_to_str(&filename); - const char *certID = &fn[5]; - CAPI_KEY = TRUE; - type = SSH_KEYTYPE_SSH2; - if ((ckey = Create_CAPI_userkey(certID, NULL)) == NULL) { - char *msg = dupprintf("Couldn't load CAPI certificate/key: %s", certID); - message_box(msg, APPNAME, MB_OK | MB_ICONERROR, HELPCTXID(errors_cantloadkey)); - sfree(msg); - return; - } - } - else { -/* PuTTY CAPI end */ - type = key_type(&filename); + + type = key_type(filename); if (type != SSH_KEYTYPE_SSH1 && type != SSH_KEYTYPE_SSH2) { char *msg = dupprintf("Couldn't load this key (%s)", key_type_to_str(type)); message_box(msg, APPNAME, MB_OK | MB_ICONERROR, HELPCTXID(errors_cantloadkey)); sfree(msg); return; } -/* PuTTY CAPI start */ - } -/* PuTTY CAPI end */ /* * See if the key is already loaded (in the primary Pageant, * which may or may not be us). */ @@ -659,52 +408,34 @@ void *blob; unsigned char *keylist, *p; int i, nkeys, bloblen, keylistlen; if (type == SSH_KEYTYPE_SSH1) { - if (!rsakey_pubblob(&filename, &blob, &bloblen, NULL, &error)) { + if (!rsakey_pubblob(filename, &blob, &bloblen, NULL, &error)) { char *msg = dupprintf("Couldn't load private key (%s)", error); message_box(msg, APPNAME, MB_OK | MB_ICONERROR, HELPCTXID(errors_cantloadkey)); sfree(msg); return; } keylist = get_keylist1(&keylistlen); } else { unsigned char *blob2; -/* PuTTY CAPI start */ - if (CAPI_KEY) { - bloblen = ckey->bloblen; - } - else { -/* PuTTY CAPI end */ - blob = ssh2_userkey_loadpub(&filename, NULL, &bloblen, + blob = ssh2_userkey_loadpub(filename, NULL, &bloblen, NULL, &error); if (!blob) { char *msg = dupprintf("Couldn't load private key (%s)", error); message_box(msg, APPNAME, MB_OK | MB_ICONERROR, HELPCTXID(errors_cantloadkey)); sfree(msg); return; } -/* PuTTY CAPI start */ - } -/* PuTTY CAPI end */ /* For our purposes we want the blob prefixed with its length */ blob2 = snewn(bloblen+4, unsigned char); PUT_32BIT(blob2, bloblen); -/* PuTTY CAPI start */ - if (CAPI_KEY) { - memcpy(blob2 + 4, ckey->blob, ckey->bloblen); - } - else { -/* PuTTY CAPI end */ memcpy(blob2 + 4, blob, bloblen); sfree(blob); -/* PuTTY CAPI start */ - } -/* PuTTY CAPI end */ blob = blob2; keylist = get_keylist2(&keylistlen); } if (keylist) { @@ -711,11 +442,16 @@ if (keylistlen < 4) { MessageBox(NULL, "Received broken key list?!", APPNAME, MB_OK | MB_ICONERROR); return; } - nkeys = GET_32BIT(keylist); + nkeys = toint(GET_32BIT(keylist)); + if (nkeys < 0) { + MessageBox(NULL, "Received broken key list?!", APPNAME, + MB_OK | MB_ICONERROR); + return; + } p = keylist + 4; keylistlen -= 4; for (i = 0; i < nkeys; i++) { if (!memcmp(blob, p, bloblen)) { @@ -739,12 +475,12 @@ if (keylistlen < 4) { MessageBox(NULL, "Received broken key list?!", APPNAME, MB_OK | MB_ICONERROR); return; } - n = 4 + GET_32BIT(p); - if (keylistlen < n) { + n = toint(4 + GET_32BIT(p)); + if (n < 0 || keylistlen < n) { MessageBox(NULL, "Received broken key list?!", APPNAME, MB_OK | MB_ICONERROR); return; } p += n; @@ -756,12 +492,12 @@ if (keylistlen < 4) { MessageBox(NULL, "Received broken key list?!", APPNAME, MB_OK | MB_ICONERROR); return; } - n = 4 + GET_32BIT(p); - if (keylistlen < n) { + n = toint(4 + GET_32BIT(p)); + if (n < 0 || keylistlen < n) { MessageBox(NULL, "Received broken key list?!", APPNAME, MB_OK | MB_ICONERROR); return; } p += n; @@ -773,75 +509,36 @@ } sfree(blob); } -/* PuTTY CAPI start */ - // if we've reached this far, the key is not already loaded.... - if (CAPI_KEY) { - if (already_running) { - // need to comm with the main pageant... - unsigned char *request, *response, *p; - int reqlen, resplen, ret; - - - reqlen = 4 + 1 + // length, message type - 4 + 4 + // length, "CAPI" - 4 + strlen(ckey->certID); // length + certID string - - p = request = snewn(reqlen, unsigned char); - PUT_32BIT(p, reqlen - 4); - p[4] = SSH2_AGENTC_ADD_IDENTITY; - p += 5; - - PUT_32BIT(p, 4); - p += 4; - memcpy(p, "CAPI", 4); - p += 4; - - PUT_32BIT(p, strlen(ckey->certID)); - p += 4; - memcpy(p, ckey->certID, strlen(ckey->certID)); - p += strlen(ckey->certID); - - ret = agent_query(request, reqlen, &response, &resplen, NULL, NULL); - assert(ret == 1); - if (resplen < 5 || response[4] != SSH_AGENT_SUCCESS) - MessageBox(NULL, "The already running Pageant refused to add the capi cert/key.", APPNAME, MB_OK | MB_ICONERROR); - - sfree(request); - sfree(response); - } - else { - // we are the main pageant - if (add234(capikeys, ckey) != ckey) { - Free_CAPI_userkey(ckey); /* already present, don't waste RAM */ - } - } - return; - } -/* PuTTY CAPI end */ - error = NULL; if (type == SSH_KEYTYPE_SSH1) - needs_pass = rsakey_encrypted(&filename, &comment); + needs_pass = rsakey_encrypted(filename, &comment); else - needs_pass = ssh2_userkey_encrypted(&filename, &comment); + needs_pass = ssh2_userkey_encrypted(filename, &comment); attempts = 0; if (type == SSH_KEYTYPE_SSH1) rkey = snew(struct RSAKey); - pps.passphrase = passphrase; - pps.comment = comment; + passphrase = NULL; original_pass = 0; do { + burnstr(passphrase); + passphrase = NULL; + if (needs_pass) { /* try all the remembered passphrases first */ char *pp = index234(passphrases, attempts); if(pp) { - strcpy(passphrase, pp); + passphrase = dupstr(pp); } else { int dlgret; + struct PassphraseProcStruct pps; + + pps.passphrase = &passphrase; + pps.comment = comment; + original_pass = 1; dlgret = DialogBoxParam(hinst, MAKEINTRESOURCE(210), NULL, PassphraseProc, (LPARAM) &pps); passphrase_box = NULL; if (!dlgret) { @@ -849,17 +546,20 @@ sfree(comment); if (type == SSH_KEYTYPE_SSH1) sfree(rkey); return; /* operation cancelled */ } + + assert(passphrase != NULL); } } else - *passphrase = '\0'; + passphrase = dupstr(""); + if (type == SSH_KEYTYPE_SSH1) - ret = loadrsakey(&filename, rkey, passphrase, &error); + ret = loadrsakey(filename, rkey, passphrase, &error); else { - skey = ssh2_load_userkey(&filename, passphrase, &error); + skey = ssh2_load_userkey(filename, passphrase, &error); if (skey == SSH2_WRONG_PASSPHRASE) ret = -1; else if (!skey) ret = 0; else @@ -866,15 +566,18 @@ ret = 1; } attempts++; } while (ret == -1); - /* if they typed in an ok passphrase, remember it */ if(original_pass && ret) { - char *pp = dupstr(passphrase); - addpos234(passphrases, pp, 0); + /* If they typed in an ok passphrase, remember it */ + addpos234(passphrases, passphrase, 0); + } else { + /* Otherwise, destroy it */ + burnstr(passphrase); } + passphrase = NULL; if (comment) sfree(comment); if (ret == 0) { char *msg = dupprintf("Couldn't load private key (%s)", error); @@ -1037,14 +740,10 @@ * Create an SSH-2 key list in a malloc'ed buffer; return its * length. */ static void *make_keylist2(int *length) { -/* PuTTY CAPI start */ - struct CAPI_userkey *ckey; - char *comment; -/* PuTTY CAPI end */ struct ssh2_userkey *key; int i, len, nkeys; unsigned char *blob, *p, *ret; int bloblen; @@ -1060,19 +759,10 @@ len += bloblen; sfree(blob); len += 4 + strlen(key->comment); } -/* PuTTY CAPI start */ - for (i = 0; NULL != (ckey = index234(capikeys, i)); i++) { - nkeys++; - len += 4; /* length field */ - len += ckey->bloblen; - len += 4 + CAPI_userkey_Comment_Length(ckey); - } -/* PuTTY CAPI end */ - /* Allocate the buffer. */ p = ret = snewn(len, unsigned char); if (length) *length = len; /* @@ -1090,23 +780,10 @@ sfree(blob); PUT_32BIT(p, strlen(key->comment)); memcpy(p + 4, key->comment, strlen(key->comment)); p += 4 + strlen(key->comment); } -/* PuTTY CAPI start */ - for (i = 0; NULL != (ckey = index234(capikeys, i)); i++) { - PUT_32BIT(p, ckey->bloblen); - p += 4; - memcpy(p, ckey->blob, ckey->bloblen); - p += ckey->bloblen; - comment = CAPI_userkey_GetComment(ckey); - PUT_32BIT(p, strlen(comment)); - memcpy(p + 4, comment, strlen(comment)); - p += 4 + strlen(comment); - free(comment); - } -/* PuTTY CAPI end */ assert(p - ret == len); return ret; } @@ -1127,12 +804,14 @@ PUT_32BIT(request, 4); retval = agent_query(request, 5, &vresponse, &resplen, NULL, NULL); assert(retval == 1); response = vresponse; - if (resplen < 5 || response[4] != SSH1_AGENT_RSA_IDENTITIES_ANSWER) + if (resplen < 5 || response[4] != SSH1_AGENT_RSA_IDENTITIES_ANSWER) { + sfree(response); return NULL; + } ret = snewn(resplen-5, unsigned char); memcpy(ret, response+5, resplen-5); sfree(response); @@ -1162,12 +841,14 @@ PUT_32BIT(request, 4); retval = agent_query(request, 5, &vresponse, &resplen, NULL, NULL); assert(retval == 1); response = vresponse; - if (resplen < 5 || response[4] != SSH2_AGENT_IDENTITIES_ANSWER) + if (resplen < 5 || response[4] != SSH2_AGENT_IDENTITIES_ANSWER) { + sfree(response); return NULL; + } ret = snewn(resplen-5, unsigned char); memcpy(ret, response+5, resplen-5); sfree(response); @@ -1258,16 +939,21 @@ i = ssh1_read_bignum(p, msgend - p, &reqkey.exponent); if (i < 0) goto failure; p += i; i = ssh1_read_bignum(p, msgend - p, &reqkey.modulus); - if (i < 0) + if (i < 0) { + freebn(reqkey.exponent); goto failure; + } p += i; i = ssh1_read_bignum(p, msgend - p, &challenge); - if (i < 0) + if (i < 0) { + freebn(reqkey.exponent); + freebn(reqkey.modulus); goto failure; + } p += i; if (msgend < p+16) { freebn(reqkey.exponent); freebn(reqkey.modulus); freebn(challenge); @@ -1288,11 +974,11 @@ response_source[i] = bignum_byte(response, 31 - i); MD5Init(&md5c); MD5Update(&md5c, response_source, 48); MD5Final(response_md5, &md5c); - memset(response_source, 0, 48); /* burn the evidence */ + smemclr(response_source, 48); /* burn the evidence */ freebn(response); /* and that evidence */ freebn(challenge); /* yes, and that evidence */ freebn(reqkey.exponent); /* and free some memory ... */ freebn(reqkey.modulus); /* ... while we're at it. */ @@ -1311,71 +997,34 @@ * Reply with either SSH2_AGENT_SIGN_RESPONSE or * SSH_AGENT_FAILURE, depending on whether we have that key * or not. */ { -/* PuTTY CAPI start */ - struct CAPI_userkey *ckey; -/* PuTTY CAPI end */ struct ssh2_userkey *key; struct blob b; unsigned char *data, *signature; int datalen, siglen, len; if (msgend < p+4) goto failure; - b.len = GET_32BIT(p); + b.len = toint(GET_32BIT(p)); + if (b.len < 0 || b.len > msgend - (p+4)) + goto failure; p += 4; - if (msgend < p+b.len) - goto failure; b.blob = p; p += b.len; if (msgend < p+4) goto failure; - datalen = GET_32BIT(p); + datalen = toint(GET_32BIT(p)); p += 4; - if (msgend < p+datalen) + if (datalen < 0 || datalen > msgend - p) goto failure; data = p; -/* PuTTY CAPI start */ - ckey = find234(capikeys, &b, cmpkeys_capi_blob); - if (ckey) { - if ((signature = capi_sig_certID(ckey->certID, data, datalen, &siglen)) == NULL) - goto failure; - } - else { -/* PuTTY CAPI end */ key = find234(ssh2keys, &b, cmpkeys_ssh2_asymm); if (!key) goto failure; - /* PuTTY SC start */ - if((sclib != NULL) && (strcmp(key->comment, pkcs11_cert_label) == 0)) { - char passphrase[PASSPHRASE_MAXLEN]; - struct PassphraseProcStruct pps1; - pps1.passphrase = passphrase; - pps1.comment = key->comment; - if(strlen(sc_save_passphrase) == 0) { - /* password not in cache */ - sc_activate_pwd_cache = 0; - DialogBoxParam(hinst, MAKEINTRESOURCE(215), NULL, sc_PassphraseProc, (LPARAM) &pps1); - } else { - /* re-use existing pwd */ - strcpy(pps1.passphrase, sc_save_passphrase); - } - signature = sc_sig(NULL, 0, sclib, pkcs11_token_label, pps1.passphrase, data, datalen, &siglen); - if(siglen > 1 && sc_activate_pwd_cache) { - /* store password in cache (if requested and valid) */ - strcpy(sc_save_passphrase, pps1.passphrase); - } - memset(pps1.passphrase, 0, PASSPHRASE_MAXLEN); - } - else - /* PuTTY SC end */ signature = key->alg->sign(key->data, data, datalen, &siglen); -/* PuTTY CAPI start */ - } -/* PuTTY CAPI end */ len = 5 + 4 + siglen; PUT_32BIT(ret, len - 4); ret[4] = SSH2_AGENT_SIGN_RESPONSE; PUT_32BIT(ret + 5, siglen); memcpy(ret + 5 + 4, signature, siglen); @@ -1438,13 +1087,13 @@ if (msgend < p+4) { freersakey(key); sfree(key); goto failure; } - commentlen = GET_32BIT(p); + commentlen = toint(GET_32BIT(p)); - if (msgend < p+commentlen) { + if (commentlen < 0 || commentlen > msgend - p) { freersakey(key); sfree(key); goto failure; } @@ -1477,43 +1126,17 @@ int bloblen; if (msgend < p+4) goto failure; - alglen = GET_32BIT(p); + alglen = toint(GET_32BIT(p)); p += 4; - if (msgend < p+alglen) + if (alglen < 0 || alglen > msgend - p) goto failure; alg = p; p += alglen; -/* PuTTY CAPI start */ - if (alglen == 4 && memcmp(alg, "CAPI", 4) == 0) { - struct CAPI_userkey *ckey; - char *certID; - int certIDlen; - - if (msgend < p+4) - goto failure; - certIDlen = GET_32BIT(p); - p += 4; - - if (msgend < p+certIDlen) - goto failure; - certID = p; - - if ((ckey = Create_CAPI_userkey(certID, NULL)) == NULL) - goto failure; - if (add234(capikeys, ckey) != ckey) - Free_CAPI_userkey(ckey); // already loaded, free our (unused) copy - - PUT_32BIT(ret, 1); - ret[4] = SSH_AGENT_SUCCESS; - keylist_update(); - break; - } -/* PuTTY CAPI end */ key = snew(struct ssh2_userkey); /* Add further algorithm names here. */ if (alglen == 7 && !memcmp(alg, "ssh-rsa", 7)) key->alg = &ssh_rsa; else if (alglen == 7 && !memcmp(alg, "ssh-dss", 7)) @@ -1539,14 +1162,14 @@ if (msgend < p+4) { key->alg->freekey(key->data); sfree(key); goto failure; } - commlen = GET_32BIT(p); + commlen = toint(GET_32BIT(p)); p += 4; - if (msgend < p+commlen) { + if (commlen < 0 || commlen > msgend - p) { key->alg->freekey(key->data); sfree(key); goto failure; } comment = snewn(commlen + 1, char); @@ -1606,14 +1229,14 @@ struct ssh2_userkey *key; struct blob b; if (msgend < p+4) goto failure; - b.len = GET_32BIT(p); + b.len = toint(GET_32BIT(p)); p += 4; - if (msgend < p+b.len) + if (b.len < 0 || b.len > msgend - p) goto failure; b.blob = p; p += b.len; key = find234(ssh2keys, &b, cmpkeys_ssh2_asymm); @@ -1816,24 +1439,28 @@ of.nMaxFile = 8192; of.lpstrFileTitle = NULL; of.lpstrTitle = "Select Private Key File"; of.Flags = OFN_ALLOWMULTISELECT | OFN_EXPLORER; if (request_file(keypath, &of, TRUE, FALSE)) { - if(strlen(filelist) > of.nFileOffset) + if(strlen(filelist) > of.nFileOffset) { /* Only one filename returned? */ - add_keyfile(filename_from_str(filelist)); - else { + Filename *fn = filename_from_str(filelist); + add_keyfile(fn); + filename_free(fn); + } else { /* we are returned a bunch of strings, end to * end. first string is the directory, the * rest the filenames. terminated with an * empty string. */ char *dir = filelist; char *filewalker = filelist + strlen(dir) + 1; while (*filewalker != '\0') { char *filename = dupcat(dir, "\\", filewalker, NULL); - add_keyfile(filename_from_str(filename)); + Filename *fn = filename_from_str(filename); + add_keyfile(fn); + filename_free(fn); sfree(filename); filewalker += strlen(filewalker) + 1; } } @@ -1849,13 +1476,10 @@ static int CALLBACK KeyListProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { struct RSAKey *rkey; struct ssh2_userkey *skey; -/* PuTTY CAPI start */ - struct CAPI_userkey *ckey; -/* PuTTY CAPI end */ switch (msg) { case WM_INITDIALOG: /* * Centre the window. @@ -1911,11 +1535,11 @@ return 0; case 102: /* remove key */ if (HIWORD(wParam) == BN_CLICKED || HIWORD(wParam) == BN_DOUBLECLICKED) { int i; - int rCount, sCount, cCount; /* PuTTY CAPI marker */ + int rCount, sCount; int *selectedArray; /* our counter within the array of selected items */ int itemNum; @@ -1935,30 +1559,16 @@ numSelected, (WPARAM)selectedArray); itemNum = numSelected - 1; rCount = count234(rsakeys); sCount = count234(ssh2keys); -/* PuTTY CAPI start */ - cCount = count234(capikeys); -/* PuTTY CAPI end */ /* go through the non-rsakeys until we've covered them all, * and/or we're out of selected items to check. note that * we go *backwards*, to avoid complications from deleting * things hence altering the offset of subsequent items */ -/* PuTTY CAPI start */ - for (i = cCount - 1; (itemNum >= 0) && (i >= 0); i--) { - ckey = index234(capikeys, i); - - if (selectedArray[itemNum] == rCount + sCount + i) { - del234(capikeys, ckey); - Free_CAPI_userkey(ckey); - itemNum--; - } - } -/* PuTTY CAPI end */ for (i = sCount - 1; (itemNum >= 0) && (i >= 0); i--) { skey = index234(ssh2keys, i); if (selectedArray[itemNum] == rCount + i) { del234(ssh2keys, skey); @@ -1988,51 +1598,12 @@ if (HIWORD(wParam) == BN_CLICKED || HIWORD(wParam) == BN_DOUBLECLICKED) { launch_help(hwnd, WINHELP_CTX_pageant_general); } return 0; -/* PuTTY CAPI start */ - case 100: { // listbox - if (HIWORD(wParam) == LBN_DBLCLK) { - int i, rCount, sCount, cCount; - int selected; - int numSelected; - - numSelected = SendDlgItemMessage(hwnd, 100, LB_GETSELCOUNT, 0, 0); - if (numSelected != 1) - break; - SendDlgItemMessage(hwnd, 100, LB_GETSELITEMS, numSelected, (WPARAM) &selected); - - rCount = count234(rsakeys); - sCount = count234(ssh2keys); - cCount = count234(capikeys); - - // since numSelected is garunteed to be 1, we can skip a few checks... - for (i = cCount - 1; i >= 0; i--) { - if (selected == rCount + sCount + i) { - PCCERT_CONTEXT pCertContext = NULL; - ckey = index234(capikeys, i); -//BOOL capi_get_pubkey_int(void *f /*frontend*/, char* certID, unsigned char** pubkey, char **algorithm, int *blob_len, PCCERT_CONTEXT* oCertContext) { - capi_display_cert_ui(hwnd, ckey->certID, L"Pageant Certificate"); - } - } - } - return 0; - } - case 110: { - if (HIWORD(wParam) == BN_CLICKED || HIWORD(wParam) == BN_DOUBLECLICKED) { - if (passphrase_box) { - MessageBeep(MB_ICONERROR); - SetForegroundWindow(passphrase_box); - break; - } - prompt_add_CAPIkey(hwnd); - } - return 0; - } -/* PuTTY CAPI end */ - } + } + return 0; case WM_HELP: { int id = ((LPHELPINFO)lParam)->iCtrlId; char *topic = NULL; switch (id) { @@ -2071,11 +1642,11 @@ tnid.hWnd = hwnd; tnid.uID = 1; /* unique within this systray use */ tnid.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP; tnid.uCallbackMessage = WM_SYSTRAY; tnid.hIcon = hicon = LoadIcon(hinst, MAKEINTRESOURCE(201)); - strcpy(tnid.szTip, "Pageant (PuTTY SC authentication agent)"); + strcpy(tnid.szTip, "Pageant (PuTTY authentication agent)"); res = Shell_NotifyIcon(NIM_ADD, &tnid); if (hicon) DestroyIcon(hicon); @@ -2343,17 +1914,20 @@ if (has_security) { if ((ourself = get_user_sid()) == NULL) { #ifdef DEBUG_IPC debug(("couldn't get user SID\n")); #endif + CloseHandle(filemap); return 0; } if ((ourself2 = get_default_sid()) == NULL) { #ifdef DEBUG_IPC debug(("couldn't get default SID\n")); #endif + CloseHandle(filemap); + sfree(ourself); return 0; } if ((rc = p_GetSecurityInfo(filemap, SE_KERNEL_OBJECT, OWNER_SECURITY_INFORMATION, @@ -2361,10 +1935,13 @@ &psd) != ERROR_SUCCESS)) { #ifdef DEBUG_IPC debug(("couldn't get owner info for filemap: %d\n", rc)); #endif + CloseHandle(filemap); + sfree(ourself); + sfree(ourself2); return 0; } #ifdef DEBUG_IPC { LPTSTR ours, ours2, theirs; @@ -2379,10 +1956,13 @@ } #endif if (!EqualSid(mapowner, ourself) && !EqualSid(mapowner, ourself2)) { CloseHandle(filemap); + LocalFree(psd); + sfree(ourself); + sfree(ourself2); return 0; /* security ID mismatch! */ } #ifdef DEBUG_IPC debug(("security stuff matched\n")); #endif @@ -2452,20 +2032,15 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) { WNDCLASS wndclass; MSG msg; - HMODULE advapi; char *command = NULL; int added_keys = 0; int argc, i; char **argv, **argstart; - /* PuTTY SC start */ - HKEY hkey; - /* PuTTY SC end */ - hinst = inst; hwnd = NULL; /* * Determine whether we're an NT system (should have security @@ -2483,11 +2058,11 @@ if (has_security) { #ifndef NO_SECURITY /* * Attempt to get the security API we need. */ - if (!init_advapi()) { + if (!got_advapi()) { MessageBox(NULL, "Unable to access security APIs. Pageant will\n" "not run, in case it causes a security breach.", "Pageant Fatal Error", MB_ICONERROR | MB_OK); return 1; @@ -2497,12 +2072,11 @@ "This program has been compiled for Win9X and will\n" "not run on NT, in case it causes a security breach.", "Pageant Fatal Error", MB_ICONERROR | MB_OK); return 1; #endif - } else - advapi = NULL; + } /* * See if we can find our Help file. */ init_help(); @@ -2537,13 +2111,10 @@ * Initialise storage for RSA keys. */ if (!already_running) { rsakeys = newtree234(cmpkeys_rsa); ssh2keys = newtree234(cmpkeys_ssh2); -/* PuTTY CAPI start */ - capikeys = newtree234(cmpkeys_capi); -/* PuTTY CAPI end */ } /* * Initialise storage for short-term passphrase cache. */ @@ -2554,12 +2125,10 @@ */ split_into_argv(cmdline, &argc, &argv, &argstart); for (i = 0; i < argc; i++) { if (!strcmp(argv[i], "-pgpfp")) { pgp_fingerprints(); - if (advapi) - FreeLibrary(advapi); return 1; } else if (!strcmp(argv[i], "-c")) { /* * If we see `-c', then the rest of the * command line should be treated as a @@ -2569,11 +2138,13 @@ command = argstart[i+1]; else command = ""; break; } else { - add_keyfile(filename_from_str(argv[i])); + Filename *fn = filename_from_str(argv[i]); + add_keyfile(fn); + filename_free(fn); added_keys = TRUE; } } /* @@ -2603,12 +2174,10 @@ if (already_running) { if (!command && !added_keys) { MessageBox(NULL, "Pageant is already running", "Pageant Error", MB_ICONERROR | MB_OK); } - if (advapi) - FreeLibrary(advapi); return 0; } if (!prev) { wndclass.style = 0; @@ -2625,70 +2194,10 @@ RegisterClass(&wndclass); } keylist = NULL; - /* PuTTY SC start */ - if(ERROR_SUCCESS == RegOpenKey(HKEY_CURRENT_USER, PUTTY_REGKEY, &hkey)) { - TCHAR buf[MAX_PATH + 1]; - int index_key = 0; - memset(sc_save_passphrase, 0, PASSPHRASE_MAXLEN); - while(ERROR_SUCCESS == RegEnumKey(hkey, index_key, buf, MAX_PATH)) { - char kn[1024]; - HKEY sesskey; - sprintf(kn, "%s\\%s", PUTTY_REGKEY, buf); - if(ERROR_SUCCESS == RegOpenKey(HKEY_CURRENT_USER, kn, &sesskey)) { - Filename pkcs11_libfile; - int ln = sizeof(pkcs11_token_label); - if(read_setting_s(sesskey, "PKCS11CertLabel", pkcs11_cert_label, ln)) { - if(strlen(pkcs11_cert_label) > 0) { - read_setting_filename(sesskey, "PKCS11LibFile", &pkcs11_libfile); - read_setting_s(sesskey, "PKCS11TokenLabel", pkcs11_token_label, ln); - { - sclib = calloc(sizeof(sc_lib), 1); - if(sc_init_library(NULL, 1, sclib, &pkcs11_libfile)) { - int bloblen; - char *algorithm; - unsigned char *blob = (unsigned char *)sc_get_pub(NULL, 0, sclib, - pkcs11_token_label, - pkcs11_cert_label, - &algorithm, - &bloblen); - if(blob == NULL) { - sc_free_sclib(sclib); - sclib = NULL; - } else { - struct RSAKey *rkey = snew(struct RSAKey); - struct ssh2_userkey *newKey = snew(struct ssh2_userkey); - - rkey->exponent = sclib->rsakey->exponent; - rkey->modulus = sclib->rsakey->modulus; - newKey->data = rkey; - newKey->comment = pkcs11_cert_label; - newKey->alg = find_pubkey_alg("ssh-rsa"); - - if(add234(ssh2keys, newKey) != newKey) { - MessageBox(NULL, "Failed to add token key", "Pageant Error", - MB_ICONERROR | MB_OK); - } - break; - // todo support multiple keys - // -------------------- - } - } - } - RegCloseKey(sesskey); - } - } - RegCloseKey(sesskey); - } - index_key++; - } - RegCloseKey(hkey); - } - /* PuTTY SC end */ - hwnd = CreateWindow(APPNAME, APPNAME, WS_OVERLAPPEDWINDOW | WS_VSCROLL, CW_USEDEFAULT, CW_USEDEFAULT, 100, 100, NULL, NULL, inst, NULL); @@ -2744,11 +2253,8 @@ DestroyMenu(systray_menu); } if (keypath) filereq_free(keypath); - if (advapi) - FreeLibrary(advapi); - cleanup_exit(msg.wParam); return msg.wParam; /* just in case optimiser complains */ } Index: windows/winpgntc.c ================================================================== --- windows/winpgntc.c +++ windows/winpgntc.c @@ -6,11 +6,11 @@ #include #include "putty.h" #ifndef NO_SECURITY -#include +#include "winsecur.h" #endif #define AGENT_COPYDATA_ID 0x804e50ba /* random goop */ #define AGENT_MAX_MSGLEN 8192 @@ -68,92 +68,10 @@ return 0; } #endif -/* - * Dynamically load advapi32.dll for SID manipulation. In its absence, - * we degrade gracefully. - */ -#ifndef NO_SECURITY -int advapi_initialised = FALSE; -static HMODULE advapi; -DECL_WINDOWS_FUNCTION(static, BOOL, OpenProcessToken, - (HANDLE, DWORD, PHANDLE)); -DECL_WINDOWS_FUNCTION(static, BOOL, GetTokenInformation, - (HANDLE, TOKEN_INFORMATION_CLASS, - LPVOID, DWORD, PDWORD)); -DECL_WINDOWS_FUNCTION(static, BOOL, InitializeSecurityDescriptor, - (PSECURITY_DESCRIPTOR, DWORD)); -DECL_WINDOWS_FUNCTION(static, BOOL, SetSecurityDescriptorOwner, - (PSECURITY_DESCRIPTOR, PSID, BOOL)); -DECL_WINDOWS_FUNCTION(, DWORD, GetSecurityInfo, - (HANDLE, SE_OBJECT_TYPE, SECURITY_INFORMATION, - PSID *, PSID *, PACL *, PACL *, - PSECURITY_DESCRIPTOR *)); -int init_advapi(void) -{ - advapi = load_system32_dll("advapi32.dll"); - return advapi && - GET_WINDOWS_FUNCTION(advapi, GetSecurityInfo) && - GET_WINDOWS_FUNCTION(advapi, OpenProcessToken) && - GET_WINDOWS_FUNCTION(advapi, GetTokenInformation) && - GET_WINDOWS_FUNCTION(advapi, InitializeSecurityDescriptor) && - GET_WINDOWS_FUNCTION(advapi, SetSecurityDescriptorOwner); -} - -PSID get_user_sid(void) -{ - HANDLE proc = NULL, tok = NULL; - TOKEN_USER *user = NULL; - DWORD toklen, sidlen; - PSID sid = NULL, ret = NULL; - - if ((proc = OpenProcess(MAXIMUM_ALLOWED, FALSE, - GetCurrentProcessId())) == NULL) - goto cleanup; - - if (!p_OpenProcessToken(proc, TOKEN_QUERY, &tok)) - goto cleanup; - - if (!p_GetTokenInformation(tok, TokenUser, NULL, 0, &toklen) && - GetLastError() != ERROR_INSUFFICIENT_BUFFER) - goto cleanup; - - if ((user = (TOKEN_USER *)LocalAlloc(LPTR, toklen)) == NULL) - goto cleanup; - - if (!p_GetTokenInformation(tok, TokenUser, user, toklen, &toklen)) - goto cleanup; - - sidlen = GetLengthSid(user->User.Sid); - - sid = (PSID)smalloc(sidlen); - - if (!CopySid(sidlen, sid, user->User.Sid)) - goto cleanup; - - /* Success. Move sid into the return value slot, and null it out - * to stop the cleanup code freeing it. */ - ret = sid; - sid = NULL; - - cleanup: - if (proc != NULL) - CloseHandle(proc); - if (tok != NULL) - CloseHandle(tok); - if (user != NULL) - LocalFree(user); - if (sid != NULL) - sfree(sid); - - return ret; -} - -#endif - int agent_query(void *in, int inlen, void **out, int *outlen, void (*callback)(void *, void *, int), void *callback_ctx) { HWND hwnd; char *mapname; @@ -171,12 +89,13 @@ hwnd = FindWindow("Pageant", "Pageant"); if (!hwnd) return 1; /* *out == NULL, so failure */ mapname = dupprintf("PageantRequest%08x", (unsigned)GetCurrentThreadId()); + psa = NULL; #ifndef NO_SECURITY - if (advapi_initialised || init_advapi()) { + if (got_advapi()) { /* * Make the file mapping we create for communication with * Pageant owned by the user SID rather than the default. This * should make communication between processes with slightly * different contexts more reliable: in particular, command @@ -184,11 +103,10 @@ * run PSFTPs which refer back to the owning user's * unprivileged Pageant. */ usersid = get_user_sid(); - psa = NULL; if (usersid) { psd = (PSECURITY_DESCRIPTOR) LocalAlloc(LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH); if (psd) { if (p_InitializeSecurityDescriptor @@ -207,12 +125,14 @@ } #endif /* NO_SECURITY */ filemap = CreateFileMapping(INVALID_HANDLE_VALUE, psa, PAGE_READWRITE, 0, AGENT_MAX_MSGLEN, mapname); - if (filemap == NULL || filemap == INVALID_HANDLE_VALUE) + if (filemap == NULL || filemap == INVALID_HANDLE_VALUE) { + sfree(mapname); return 1; /* *out == NULL, so failure */ + } p = MapViewOfFile(filemap, FILE_MAP_WRITE, 0, 0, 0); memcpy(p, in, inlen); cds.dwData = AGENT_COPYDATA_ID; cds.cbData = 1 + strlen(mapname); cds.lpData = mapname; @@ -235,10 +155,11 @@ data->callback_ctx = callback_ctx; data->cds = cds; /* structure copy */ data->hwnd = hwnd; if (CreateThread(NULL, 0, agent_query_thread, data, 0, &threadid)) return 0; + sfree(mapname); sfree(data); } #endif /* @@ -256,10 +177,11 @@ *outlen = retlen; } } UnmapViewOfFile(p); CloseHandle(filemap); + sfree(mapname); if (psd) LocalFree(psd); sfree(usersid); return 1; } Index: windows/winplink.c ================================================================== --- windows/winplink.c +++ windows/winplink.c @@ -46,10 +46,19 @@ if (logctx) { log_free(logctx); logctx = NULL; } cleanup_exit(1); +} +void nonfatal(char *p, ...) +{ + va_list ap; + fprintf(stderr, "ERROR: "); + va_start(ap, p); + vfprintf(stderr, p, ap); + va_end(ap); + fputc('\n', stderr); } void connection_fatal(void *frontend, char *p, ...) { va_list ap; fprintf(stderr, "FATAL ERROR: "); @@ -81,11 +90,11 @@ WSAEVENT netevent; static Backend *back; static void *backhandle; -static Config cfg; +static Conf *conf; int term_ldisc(Terminal *term, int mode) { return FALSE; } @@ -127,10 +136,16 @@ * currently, it's all diverted by FLAG_STDERR). */ assert(!"Unexpected call to from_backend_untrusted()"); return 0; /* not reached */ } + +int from_backend_eof(void *frontend_handle) +{ + handle_write_eof(stdout_handle); + return FALSE; /* do not respond to incoming EOF with outgoing */ +} int get_userpass_input(prompts_t *p, unsigned char *in, int inlen) { int ret; ret = cmdline_get_passwd_input(p, in, inlen); @@ -271,10 +286,13 @@ back->unthrottle(backhandle, (handle_backlog(stdout_handle) + handle_backlog(stderr_handle))); } } +const int share_can_be_downstream = TRUE; +const int share_can_be_upstream = TRUE; + int main(int argc, char **argv) { int sending; int portnumber = -1; SOCKET *sklist; @@ -281,11 +299,11 @@ int skcount, sksize; int exitcode; int errors; int got_host = FALSE; int use_subsystem = 0; - long now, next; + unsigned long now, next, then; sklist = NULL; skcount = sksize = 0; /* * Initialise port and protocol to sensible defaults. (These @@ -296,33 +314,36 @@ flags = FLAG_STDERR; /* * Process the command line. */ - do_defaults(NULL, &cfg); + conf = conf_new(); + do_defaults(NULL, conf); loaded_session = FALSE; - default_protocol = cfg.protocol; - default_port = cfg.port; + default_protocol = conf_get_int(conf, CONF_protocol); + default_port = conf_get_int(conf, CONF_port); errors = 0; { /* * Override the default protocol if PLINK_PROTOCOL is set. */ char *p = getenv("PLINK_PROTOCOL"); if (p) { const Backend *b = backend_from_name(p); if (b) { - default_protocol = cfg.protocol = b->protocol; - default_port = cfg.port = b->default_port; + default_protocol = b->protocol; + default_port = b->default_port; + conf_set_int(conf, CONF_protocol, default_protocol); + conf_set_int(conf, CONF_port, default_port); } } } while (--argc) { char *p = *++argv; if (*p == '-') { int ret = cmdline_process_param(p, (argc > 1 ? argv[1] : NULL), - 1, &cfg); + 1, conf); if (ret == -2) { fprintf(stderr, "plink: option \"%s\" requires an argument\n", p); errors = 1; } else if (ret == 2) { @@ -330,23 +351,25 @@ } else if (ret == 1) { continue; } else if (!strcmp(p, "-batch")) { console_batch_mode = 1; } else if (!strcmp(p, "-s")) { - /* Save status to write to cfg later. */ + /* Save status to write to conf later. */ use_subsystem = 1; - } else if (!strcmp(p, "-V")) { + } else if (!strcmp(p, "-V") || !strcmp(p, "--version")) { version(); + } else if (!strcmp(p, "--help")) { + usage(); } else if (!strcmp(p, "-pgpfp")) { pgp_fingerprints(); exit(1); } else { fprintf(stderr, "plink: unknown option \"%s\"\n", p); errors = 1; } } else if (*p) { - if (!cfg_launchable(&cfg) || !(got_host || loaded_session)) { + if (!conf_launchable(conf) || !(got_host || loaded_session)) { char *q = p; /* * If the hostname starts with "telnet:", set the * protocol to Telnet and process the string as a * Telnet URL. @@ -355,23 +378,22 @@ char c; q += 7; if (q[0] == '/' && q[1] == '/') q += 2; - cfg.protocol = PROT_TELNET; + conf_set_int(conf, CONF_protocol, PROT_TELNET); p = q; while (*p && *p != ':' && *p != '/') p++; c = *p; if (*p) *p++ = '\0'; if (c == ':') - cfg.port = atoi(p); + conf_set_int(conf, CONF_port, atoi(p)); else - cfg.port = -1; - strncpy(cfg.host, q, sizeof(cfg.host) - 1); - cfg.host[sizeof(cfg.host) - 1] = '\0'; + conf_set_int(conf, CONF_port, -1); + conf_set_str(conf, CONF_host, q); got_host = TRUE; } else { char *r, *user, *host; /* * Before we process the [user@]host string, we @@ -382,11 +404,13 @@ if (r) { const Backend *b; *r = '\0'; b = backend_from_name(p); if (b) { - default_protocol = cfg.protocol = b->protocol; + default_protocol = b->protocol; + conf_set_int(conf, CONF_protocol, + default_protocol); portnumber = b->default_port; } p = r + 1; } @@ -409,30 +433,28 @@ /* * Now attempt to load a saved session with the * same name as the hostname. */ { - Config cfg2; - do_defaults(host, &cfg2); - if (loaded_session || !cfg_launchable(&cfg2)) { + Conf *conf2 = conf_new(); + do_defaults(host, conf2); + if (loaded_session || !conf_launchable(conf2)) { /* No settings for this host; use defaults */ /* (or session was already loaded with -load) */ - strncpy(cfg.host, host, sizeof(cfg.host) - 1); - cfg.host[sizeof(cfg.host) - 1] = '\0'; - cfg.port = default_port; + conf_set_str(conf, CONF_host, host); + conf_set_int(conf, CONF_port, default_port); got_host = TRUE; } else { - cfg = cfg2; + conf_copy_into(conf, conf2); loaded_session = TRUE; } + conf_free(conf2); } if (user) { /* Patch in specified username. */ - strncpy(cfg.username, user, - sizeof(cfg.username) - 1); - cfg.username[sizeof(cfg.username) - 1] = '\0'; + conf_set_str(conf, CONF_username, user); } } } else { char *command; @@ -455,86 +477,94 @@ command[cmdlen++]=' '; /* always add trailing space */ if (--argc) p = *++argv; } if (cmdlen) command[--cmdlen]='\0'; /* change trailing blank to NUL */ - cfg.remote_cmd_ptr = command; - cfg.remote_cmd_ptr2 = NULL; - cfg.nopty = TRUE; /* command => no terminal */ + conf_set_str(conf, CONF_remote_cmd, command); + conf_set_str(conf, CONF_remote_cmd2, ""); + conf_set_int(conf, CONF_nopty, TRUE); /* command => no tty */ break; /* done with cmdline */ } } } if (errors) return 1; - if (!cfg_launchable(&cfg) || !(got_host || loaded_session)) { + if (!conf_launchable(conf) || !(got_host || loaded_session)) { usage(); } /* - * Trim leading whitespace off the hostname if it's there. + * Muck about with the hostname in various ways. */ { - int space = strspn(cfg.host, " \t"); - memmove(cfg.host, cfg.host+space, 1+strlen(cfg.host)-space); - } - - /* See if host is of the form user@host */ - if (cfg_launchable(&cfg)) { - char *atsign = strrchr(cfg.host, '@'); - /* Make sure we're not overflowing the user field */ - if (atsign) { - if (atsign - cfg.host < sizeof cfg.username) { - strncpy(cfg.username, cfg.host, atsign - cfg.host); - cfg.username[atsign - cfg.host] = '\0'; - } - memmove(cfg.host, atsign + 1, 1 + strlen(atsign + 1)); - } + char *hostbuf = dupstr(conf_get_str(conf, CONF_host)); + char *host = hostbuf; + char *p, *q; + + /* + * Trim leading whitespace. + */ + host += strspn(host, " \t"); + + /* + * See if host is of the form user@host, and separate out + * the username if so. + */ + if (host[0] != '\0') { + char *atsign = strrchr(host, '@'); + if (atsign) { + *atsign = '\0'; + conf_set_str(conf, CONF_username, host); + host = atsign + 1; + } + } + + /* + * Trim off a colon suffix if it's there. + */ + host[strcspn(host, ":")] = '\0'; + + /* + * Remove any remaining whitespace. + */ + p = hostbuf; + q = host; + while (*q) { + if (*q != ' ' && *q != '\t') + *p++ = *q; + q++; + } + *p = '\0'; + + conf_set_str(conf, CONF_host, hostbuf); + sfree(hostbuf); } /* * Perform command-line overrides on session configuration. */ - cmdline_run_saved(&cfg); + cmdline_run_saved(conf); /* * Apply subsystem status. */ if (use_subsystem) - cfg.ssh_subsys = TRUE; - - /* - * Trim a colon suffix off the hostname if it's there. - */ - cfg.host[strcspn(cfg.host, ":")] = '\0'; - - /* - * Remove any remaining whitespace from the hostname. - */ - { - int p1 = 0, p2 = 0; - while (cfg.host[p2] != '\0') { - if (cfg.host[p2] != ' ' && cfg.host[p2] != '\t') { - cfg.host[p1] = cfg.host[p2]; - p1++; - } - p2++; - } - cfg.host[p1] = '\0'; - } - - if (!cfg.remote_cmd_ptr && !*cfg.remote_cmd && !*cfg.ssh_nc_host) + conf_set_int(conf, CONF_ssh_subsys, TRUE); + + if (!*conf_get_str(conf, CONF_remote_cmd) && + !*conf_get_str(conf, CONF_remote_cmd2) && + !*conf_get_str(conf, CONF_ssh_nc_host)) flags |= FLAG_INTERACTIVE; /* * Select protocol. This is farmed out into a table in a * separate file to enable an ssh-free variant. */ - back = backend_from_proto(cfg.protocol); + back = backend_from_proto(conf_get_int(conf, CONF_protocol)); if (back == NULL) { fprintf(stderr, "Internal fault: Unsupported protocol found\n"); return 1; } @@ -541,19 +571,19 @@ /* * Select port. */ if (portnumber != -1) - cfg.port = portnumber; + conf_set_int(conf, CONF_port, portnumber); sk_init(); if (p_WSAEventSelect == NULL) { fprintf(stderr, "Plink requires WinSock 2\n"); return 1; } - logctx = log_init(NULL, &cfg); + logctx = log_init(NULL, conf); console_provide_logctx(logctx); /* * Start up the connection. */ @@ -560,15 +590,18 @@ netevent = CreateEvent(NULL, FALSE, FALSE, NULL); { const char *error; char *realhost; /* nodelay is only useful if stdin is a character device (console) */ - int nodelay = cfg.tcp_nodelay && + int nodelay = conf_get_int(conf, CONF_tcp_nodelay) && (GetFileType(GetStdHandle(STD_INPUT_HANDLE)) == FILE_TYPE_CHAR); - error = back->init(NULL, &backhandle, &cfg, cfg.host, cfg.port, - &realhost, nodelay, cfg.tcp_keepalives); + error = back->init(NULL, &backhandle, conf, + conf_get_str(conf, CONF_host), + conf_get_int(conf, CONF_port), + &realhost, nodelay, + conf_get_int(conf, CONF_tcp_keepalives)); if (error) { fprintf(stderr, "Unable to open connection:\n%s", error); return 1; } back->provide_logctx(backhandle, logctx); @@ -612,13 +645,19 @@ stdin_handle = handle_input_new(inhandle, stdin_gotdata, NULL, 0); sending = TRUE; } - if (run_timers(now, &next)) { - ticks = next - GETTICKCOUNT(); - if (ticks < 0) ticks = 0; /* just in case */ + if (toplevel_callback_pending()) { + ticks = 0; + } else if (run_timers(now, &next)) { + then = now; + now = GETTICKCOUNT(); + if (now - then > next - then) + ticks = 0; + else + ticks = next - now; } else { ticks = INFINITE; } handles = handle_get_events(&nhandles); @@ -697,10 +736,12 @@ struct agent_callback *c = (struct agent_callback *)msg.lParam; c->callback(c->callback_ctx, c->data, c->len); sfree(c); } } + + run_toplevel_callbacks(); if (n == WAIT_TIMEOUT) { now = next; } else { now = GETTICKCOUNT(); Index: windows/winprint.c ================================================================== --- windows/winprint.c +++ windows/winprint.c @@ -16,43 +16,43 @@ struct printer_job_tag { HANDLE hprinter; }; -static char *printer_add_enum(int param, DWORD level, char *buffer, - int offset, int *nprinters_ptr) +static int printer_add_enum(int param, DWORD level, char **buffer, + int offset, int *nprinters_ptr) { DWORD needed = 0, nprinters = 0; - buffer = sresize(buffer, offset+512, char); + *buffer = sresize(*buffer, offset+512, char); /* * Exploratory call to EnumPrinters to determine how much space * we'll need for the output. Discard the return value since it * will almost certainly be a failure due to lack of space. */ - EnumPrinters(param, NULL, level, buffer+offset, 512, + EnumPrinters(param, NULL, level, (*buffer)+offset, 512, &needed, &nprinters); if (needed < 512) needed = 512; - buffer = sresize(buffer, offset+needed, char); + *buffer = sresize(*buffer, offset+needed, char); - if (EnumPrinters(param, NULL, level, buffer+offset, + if (EnumPrinters(param, NULL, level, (*buffer)+offset, needed, &needed, &nprinters) == 0) - return NULL; + return FALSE; *nprinters_ptr += nprinters; - return buffer; + return TRUE; } printer_enum *printer_start_enum(int *nprinters_ptr) { printer_enum *ret = snew(printer_enum); - char *buffer = NULL, *retval; + char *buffer = NULL; *nprinters_ptr = 0; /* default return value */ buffer = snewn(512, char); /* @@ -69,16 +69,13 @@ ret->enum_level = 5; } else { ret->enum_level = 4; } - retval = printer_add_enum(PRINTER_ENUM_LOCAL | PRINTER_ENUM_CONNECTIONS, - ret->enum_level, buffer, 0, nprinters_ptr); - if (!retval) + if (!printer_add_enum(PRINTER_ENUM_LOCAL | PRINTER_ENUM_CONNECTIONS, + ret->enum_level, &buffer, 0, nprinters_ptr)) goto error; - else - buffer = retval; switch (ret->enum_level) { case 4: ret->info.i4 = (LPPRINTER_INFO_4)buffer; break; Index: windows/winproxy.c ================================================================== --- windows/winproxy.c +++ windows/winproxy.c @@ -11,177 +11,61 @@ #include "tree234.h" #include "putty.h" #include "network.h" #include "proxy.h" -typedef struct Socket_localproxy_tag *Local_Proxy_Socket; - -struct Socket_localproxy_tag { - const struct socket_function_table *fn; - /* the above variable absolutely *must* be the first in this structure */ - - HANDLE to_cmd_H, from_cmd_H; - struct handle *to_cmd_h, *from_cmd_h; - - char *error; - - Plug plug; - - void *privptr; -}; - -int localproxy_gotdata(struct handle *h, void *data, int len) -{ - Local_Proxy_Socket ps = (Local_Proxy_Socket) handle_get_privdata(h); - - if (len < 0) { - return plug_closing(ps->plug, "Read error from local proxy command", - 0, 0); - } else if (len == 0) { - return plug_closing(ps->plug, NULL, 0, 0); - } else { - return plug_receive(ps->plug, 0, data, len); - } -} - -void localproxy_sentdata(struct handle *h, int new_backlog) -{ - Local_Proxy_Socket ps = (Local_Proxy_Socket) handle_get_privdata(h); - - plug_sent(ps->plug, new_backlog); -} - -static Plug sk_localproxy_plug (Socket s, Plug p) -{ - Local_Proxy_Socket ps = (Local_Proxy_Socket) s; - Plug ret = ps->plug; - if (p) - ps->plug = p; - return ret; -} - -static void sk_localproxy_close (Socket s) -{ - Local_Proxy_Socket ps = (Local_Proxy_Socket) s; - - handle_free(ps->to_cmd_h); - handle_free(ps->from_cmd_h); - CloseHandle(ps->to_cmd_H); - CloseHandle(ps->from_cmd_H); - - sfree(ps); -} - -static int sk_localproxy_write (Socket s, const char *data, int len) -{ - Local_Proxy_Socket ps = (Local_Proxy_Socket) s; - - return handle_write(ps->to_cmd_h, data, len); -} - -static int sk_localproxy_write_oob(Socket s, const char *data, int len) -{ - /* - * oob data is treated as inband; nasty, but nothing really - * better we can do - */ - return sk_localproxy_write(s, data, len); -} - -static void sk_localproxy_flush(Socket s) -{ - /* Local_Proxy_Socket ps = (Local_Proxy_Socket) s; */ - /* do nothing */ -} - -static void sk_localproxy_set_private_ptr(Socket s, void *ptr) -{ - Local_Proxy_Socket ps = (Local_Proxy_Socket) s; - ps->privptr = ptr; -} - -static void *sk_localproxy_get_private_ptr(Socket s) -{ - Local_Proxy_Socket ps = (Local_Proxy_Socket) s; - return ps->privptr; -} - -static void sk_localproxy_set_frozen(Socket s, int is_frozen) -{ - Local_Proxy_Socket ps = (Local_Proxy_Socket) s; - - /* - * FIXME - */ -} - -static const char *sk_localproxy_socket_error(Socket s) -{ - Local_Proxy_Socket ps = (Local_Proxy_Socket) s; - return ps->error; -} +Socket make_handle_socket(HANDLE send_H, HANDLE recv_H, Plug plug, + int overlapped); Socket platform_new_connection(SockAddr addr, char *hostname, int port, int privport, int oobinline, int nodelay, int keepalive, - Plug plug, const Config *cfg) + Plug plug, Conf *conf) { char *cmd; - - static const struct socket_function_table socket_fn_table = { - sk_localproxy_plug, - sk_localproxy_close, - sk_localproxy_write, - sk_localproxy_write_oob, - sk_localproxy_flush, - sk_localproxy_set_private_ptr, - sk_localproxy_get_private_ptr, - sk_localproxy_set_frozen, - sk_localproxy_socket_error - }; - - Local_Proxy_Socket ret; HANDLE us_to_cmd, us_from_cmd, cmd_to_us, cmd_from_us; SECURITY_ATTRIBUTES sa; STARTUPINFO si; PROCESS_INFORMATION pi; - if (cfg->proxy_type != PROXY_CMD) + if (conf_get_int(conf, CONF_proxy_type) != PROXY_CMD) return NULL; - cmd = format_telnet_command(addr, port, cfg); + cmd = format_telnet_command(addr, port, conf); + + /* We are responsible for this and don't need it any more */ + sk_addr_free(addr); { char *msg = dupprintf("Starting local proxy command: %s", cmd); /* We're allowed to pass NULL here, because we're part of the Windows * front end so we know logevent doesn't expect any data. */ logevent(NULL, msg); sfree(msg); } - ret = snew(struct Socket_localproxy_tag); - ret->fn = &socket_fn_table; - ret->plug = plug; - ret->error = NULL; - /* * Create the pipes to the proxy command, and spawn the proxy * command process. */ sa.nLength = sizeof(sa); sa.lpSecurityDescriptor = NULL; /* default */ sa.bInheritHandle = TRUE; if (!CreatePipe(&us_from_cmd, &cmd_to_us, &sa, 0)) { - ret->error = dupprintf("Unable to create pipes for proxy command"); - return (Socket)ret; + Socket ret = + new_error_socket("Unable to create pipes for proxy command", plug); + sfree(cmd); + return ret; } if (!CreatePipe(&cmd_from_us, &us_to_cmd, &sa, 0)) { + Socket ret = + new_error_socket("Unable to create pipes for proxy command", plug); + sfree(cmd); CloseHandle(us_from_cmd); CloseHandle(cmd_to_us); - ret->error = dupprintf("Unable to create pipes for proxy command"); - return (Socket)ret; + return ret; } SetHandleInformation(us_to_cmd, HANDLE_FLAG_INHERIT, 0); SetHandleInformation(us_from_cmd, HANDLE_FLAG_INHERIT, 0); @@ -196,24 +80,15 @@ si.hStdOutput = cmd_to_us; si.hStdError = NULL; CreateProcess(NULL, cmd, NULL, NULL, TRUE, CREATE_NO_WINDOW | NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi); + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); sfree(cmd); CloseHandle(cmd_from_us); CloseHandle(cmd_to_us); - ret->to_cmd_H = us_to_cmd; - ret->from_cmd_H = us_from_cmd; - - ret->from_cmd_h = handle_input_new(ret->from_cmd_H, localproxy_gotdata, - ret, 0); - ret->to_cmd_h = handle_output_new(ret->to_cmd_H, localproxy_sentdata, - ret, 0); - - /* We are responsible for this and don't need it any more */ - sk_addr_free(addr); - - return (Socket) ret; + return make_handle_socket(us_to_cmd, us_from_cmd, plug, FALSE); } ADDED windows/winsecur.c Index: windows/winsecur.c ================================================================== --- /dev/null +++ windows/winsecur.c @@ -0,0 +1,196 @@ +/* + * winsecur.c: implementation of winsecur.h. + */ + +#include +#include + +#include "putty.h" + +#if !defined NO_SECURITY + +#define WINSECUR_GLOBAL +#include "winsecur.h" + +int got_advapi(void) +{ + static int attempted = FALSE; + static int successful; + static HMODULE advapi; + + if (!attempted) { + attempted = TRUE; + advapi = load_system32_dll("advapi32.dll"); + successful = advapi && + GET_WINDOWS_FUNCTION(advapi, GetSecurityInfo) && + GET_WINDOWS_FUNCTION(advapi, OpenProcessToken) && + GET_WINDOWS_FUNCTION(advapi, GetTokenInformation) && + GET_WINDOWS_FUNCTION(advapi, InitializeSecurityDescriptor) && + GET_WINDOWS_FUNCTION(advapi, SetSecurityDescriptorOwner) && + GET_WINDOWS_FUNCTION(advapi, SetEntriesInAclA); + } + return successful; +} + +int got_crypt(void) +{ + static int attempted = FALSE; + static int successful; + static HMODULE crypt; + + if (!attempted) { + attempted = TRUE; + crypt = load_system32_dll("crypt32.dll"); + successful = crypt && + GET_WINDOWS_FUNCTION(crypt, CryptProtectMemory); + } + return successful; +} + +PSID get_user_sid(void) +{ + HANDLE proc = NULL, tok = NULL; + TOKEN_USER *user = NULL; + DWORD toklen, sidlen; + PSID sid = NULL, ret = NULL; + + if (!got_advapi()) + goto cleanup; + + if ((proc = OpenProcess(MAXIMUM_ALLOWED, FALSE, + GetCurrentProcessId())) == NULL) + goto cleanup; + + if (!p_OpenProcessToken(proc, TOKEN_QUERY, &tok)) + goto cleanup; + + if (!p_GetTokenInformation(tok, TokenUser, NULL, 0, &toklen) && + GetLastError() != ERROR_INSUFFICIENT_BUFFER) + goto cleanup; + + if ((user = (TOKEN_USER *)LocalAlloc(LPTR, toklen)) == NULL) + goto cleanup; + + if (!p_GetTokenInformation(tok, TokenUser, user, toklen, &toklen)) + goto cleanup; + + sidlen = GetLengthSid(user->User.Sid); + + sid = (PSID)smalloc(sidlen); + + if (!CopySid(sidlen, sid, user->User.Sid)) + goto cleanup; + + /* Success. Move sid into the return value slot, and null it out + * to stop the cleanup code freeing it. */ + ret = sid; + sid = NULL; + + cleanup: + if (proc != NULL) + CloseHandle(proc); + if (tok != NULL) + CloseHandle(tok); + if (user != NULL) + LocalFree(user); + if (sid != NULL) + sfree(sid); + + return ret; +} + +int make_private_security_descriptor(DWORD permissions, + PSECURITY_DESCRIPTOR *psd, + PSID *networksid, + PACL *acl, + char **error) +{ + SID_IDENTIFIER_AUTHORITY nt_auth = SECURITY_NT_AUTHORITY; + EXPLICIT_ACCESS ea[3]; + int ret = FALSE; + + *psd = NULL; + *networksid = NULL; + *acl = NULL; + *error = NULL; + + if (!got_advapi()) { + *error = dupprintf("unable to load advapi32.dll"); + goto cleanup; + } + + if (!AllocateAndInitializeSid(&nt_auth, 1, SECURITY_NETWORK_RID, + 0, 0, 0, 0, 0, 0, 0, networksid)) { + *error = dupprintf("unable to construct SID for " + "local same-user access only: %s", + win_strerror(GetLastError())); + goto cleanup; + } + + memset(ea, 0, sizeof(ea)); + ea[0].grfAccessPermissions = permissions; + ea[0].grfAccessMode = REVOKE_ACCESS; + ea[0].grfInheritance = NO_INHERITANCE; + ea[0].Trustee.TrusteeForm = TRUSTEE_IS_NAME; + ea[0].Trustee.ptstrName = "EVERYONE"; + ea[1].grfAccessPermissions = permissions; + ea[1].grfAccessMode = GRANT_ACCESS; + ea[1].grfInheritance = NO_INHERITANCE; + ea[1].Trustee.TrusteeForm = TRUSTEE_IS_NAME; + ea[1].Trustee.ptstrName = "CURRENT_USER"; + ea[2].grfAccessPermissions = permissions; + ea[2].grfAccessMode = REVOKE_ACCESS; + ea[2].grfInheritance = NO_INHERITANCE; + ea[2].Trustee.TrusteeForm = TRUSTEE_IS_SID; + ea[2].Trustee.ptstrName = (LPTSTR)*networksid; + + if (p_SetEntriesInAclA(2, ea, NULL, acl) != ERROR_SUCCESS || *acl == NULL) { + *error = dupprintf("unable to construct ACL: %s", + win_strerror(GetLastError())); + goto cleanup; + } + + *psd = (PSECURITY_DESCRIPTOR) + LocalAlloc(LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH); + if (!*psd) { + *error = dupprintf("unable to allocate security descriptor: %s", + win_strerror(GetLastError())); + goto cleanup; + } + + if (!InitializeSecurityDescriptor(*psd, SECURITY_DESCRIPTOR_REVISION)) { + *error = dupprintf("unable to initialise security descriptor: %s", + win_strerror(GetLastError())); + goto cleanup; + } + + if (!SetSecurityDescriptorDacl(*psd, TRUE, *acl, FALSE)) { + *error = dupprintf("unable to set DACL in security descriptor: %s", + win_strerror(GetLastError())); + goto cleanup; + } + + ret = TRUE; + + cleanup: + if (!ret) { + if (*psd) { + LocalFree(*psd); + *psd = NULL; + } + if (*networksid) { + LocalFree(*networksid); + *networksid = NULL; + } + if (*acl) { + LocalFree(*acl); + *acl = NULL; + } + } else { + sfree(*error); + *error = NULL; + } + return ret; +} + +#endif /* !defined NO_SECURITY */ ADDED windows/winsecur.h Index: windows/winsecur.h ================================================================== --- /dev/null +++ windows/winsecur.h @@ -0,0 +1,65 @@ +/* + * winsecur.h: some miscellaneous security-related helper functions, + * defined in winsecur.c, that use the advapi32 library. Also + * centralises the machinery for dynamically loading that library. + */ + +#if !defined NO_SECURITY + +#include + +#ifndef WINSECUR_GLOBAL +#define WINSECUR_GLOBAL extern +#endif + +/* + * Functions loaded from advapi32.dll. + */ +DECL_WINDOWS_FUNCTION(WINSECUR_GLOBAL, BOOL, OpenProcessToken, + (HANDLE, DWORD, PHANDLE)); +DECL_WINDOWS_FUNCTION(WINSECUR_GLOBAL, BOOL, GetTokenInformation, + (HANDLE, TOKEN_INFORMATION_CLASS, + LPVOID, DWORD, PDWORD)); +DECL_WINDOWS_FUNCTION(WINSECUR_GLOBAL, BOOL, InitializeSecurityDescriptor, + (PSECURITY_DESCRIPTOR, DWORD)); +DECL_WINDOWS_FUNCTION(WINSECUR_GLOBAL, BOOL, SetSecurityDescriptorOwner, + (PSECURITY_DESCRIPTOR, PSID, BOOL)); +DECL_WINDOWS_FUNCTION(WINSECUR_GLOBAL, DWORD, GetSecurityInfo, + (HANDLE, SE_OBJECT_TYPE, SECURITY_INFORMATION, + PSID *, PSID *, PACL *, PACL *, + PSECURITY_DESCRIPTOR *)); +DECL_WINDOWS_FUNCTION(WINSECUR_GLOBAL, DWORD, SetEntriesInAclA, + (ULONG, PEXPLICIT_ACCESS, PACL, PACL *)); +int got_advapi(void); + +/* + * Functions loaded from crypt32.dll. + */ +DECL_WINDOWS_FUNCTION(WINSECUR_GLOBAL, BOOL, CryptProtectMemory, + (LPVOID, DWORD, DWORD)); +int got_crypt(void); + +/* + * Find the SID describing the current user. The return value (if not + * NULL for some error-related reason) is smalloced. + */ +PSID get_user_sid(void); + +/* + * Construct a PSECURITY_DESCRIPTOR of the type used for named pipe + * servers, i.e. allowing access only to the current user id and also + * only local (i.e. not over SMB) connections. + * + * If this function returns TRUE, then 'psd', 'networksid' and 'acl' + * will all have been filled in with memory allocated using LocalAlloc + * (and hence must be freed later using LocalFree). If it returns + * FALSE, then instead 'error' has been filled with a dynamically + * allocated error message. + */ +int make_private_security_descriptor(DWORD permissions, + PSECURITY_DESCRIPTOR *psd, + PSID *networksid, + PACL *acl, + char **error); + +#endif Index: windows/winser.c ================================================================== --- windows/winser.c +++ windows/winser.c @@ -85,11 +85,11 @@ } else { serial->bufsize = new_backlog; } } -static const char *serial_configure(Serial serial, HANDLE serport, Config *cfg) +static const char *serial_configure(Serial serial, HANDLE serport, Conf *conf) { DCB dcb; COMMTIMEOUTS timeouts; /* @@ -119,31 +119,31 @@ dcb.fOutxDsrFlow = FALSE; /* * Configurable parameters. */ - dcb.BaudRate = cfg->serspeed; - msg = dupprintf("Configuring baud rate %d", cfg->serspeed); + dcb.BaudRate = conf_get_int(conf, CONF_serspeed); + msg = dupprintf("Configuring baud rate %d", dcb.BaudRate); logevent(serial->frontend, msg); sfree(msg); - dcb.ByteSize = cfg->serdatabits; - msg = dupprintf("Configuring %d data bits", cfg->serdatabits); + dcb.ByteSize = conf_get_int(conf, CONF_serdatabits); + msg = dupprintf("Configuring %d data bits", dcb.ByteSize); logevent(serial->frontend, msg); sfree(msg); - switch (cfg->serstopbits) { + switch (conf_get_int(conf, CONF_serstopbits)) { case 2: dcb.StopBits = ONESTOPBIT; str = "1"; break; case 3: dcb.StopBits = ONE5STOPBITS; str = "1.5"; break; case 4: dcb.StopBits = TWOSTOPBITS; str = "2"; break; default: return "Invalid number of stop bits (need 1, 1.5 or 2)"; } msg = dupprintf("Configuring %s data bits", str); logevent(serial->frontend, msg); sfree(msg); - switch (cfg->serparity) { + switch (conf_get_int(conf, CONF_serparity)) { case SER_PAR_NONE: dcb.Parity = NOPARITY; str = "no"; break; case SER_PAR_ODD: dcb.Parity = ODDPARITY; str = "odd"; break; case SER_PAR_EVEN: dcb.Parity = EVENPARITY; str = "even"; break; case SER_PAR_MARK: dcb.Parity = MARKPARITY; str = "mark"; break; case SER_PAR_SPACE: dcb.Parity = SPACEPARITY; str = "space"; break; @@ -150,11 +150,11 @@ } msg = dupprintf("Configuring %s parity", str); logevent(serial->frontend, msg); sfree(msg); - switch (cfg->serflow) { + switch (conf_get_int(conf, CONF_serflow)) { case SER_FLOW_NONE: str = "no"; break; case SER_FLOW_XONXOFF: dcb.fOutX = dcb.fInX = TRUE; @@ -197,17 +197,17 @@ * * Also places the canonical host name into `realhost'. It must be * freed by the caller. */ static const char *serial_init(void *frontend_handle, void **backend_handle, - Config *cfg, - char *host, int port, char **realhost, int nodelay, - int keepalive) + Conf *conf, char *host, int port, + char **realhost, int nodelay, int keepalive) { Serial serial; HANDLE serport; const char *err; + char *serline; serial = snew(struct serial_backend_data); serial->port = INVALID_HANDLE_VALUE; serial->out = serial->in = NULL; serial->bufsize = 0; @@ -214,12 +214,13 @@ serial->break_in_progress = FALSE; *backend_handle = serial; serial->frontend = frontend_handle; + serline = conf_get_str(conf, CONF_serline); { - char *msg = dupprintf("Opening serial device %s", cfg->serline); + char *msg = dupprintf("Opening serial device %s", serline); logevent(serial->frontend, msg); } { /* @@ -244,22 +245,20 @@ * talk to that doesn't exist under there, if the serial line * contains a backslash, we use it verbatim. (This also lets * existing configurations using \\.\ continue working.) */ char *serfilename = - dupprintf("%s%s", - strchr(cfg->serline, '\\') ? "" : "\\\\.\\", - cfg->serline); + dupprintf("%s%s", strchr(serline, '\\') ? "" : "\\\\.\\", serline); serport = CreateFile(serfilename, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL); sfree(serfilename); } if (serport == INVALID_HANDLE_VALUE) return "Unable to open serial port"; - err = serial_configure(serial, serport, cfg); + err = serial_configure(serial, serport, conf); if (err) return err; serial->port = serport; serial->out = handle_output_new(serport, serial_sentdata, serial, @@ -267,11 +266,11 @@ serial->in = handle_input_new(serport, serial_gotdata, serial, HANDLE_FLAG_OVERLAPPED | HANDLE_FLAG_IGNOREEOF | HANDLE_FLAG_UNITBUFFER); - *realhost = dupstr(cfg->serline); + *realhost = dupstr(serline); /* * Specials are always available. */ update_specials_menu(serial->frontend); @@ -286,16 +285,16 @@ serial_terminate(serial); expire_timer_context(serial); sfree(serial); } -static void serial_reconfig(void *handle, Config *cfg) +static void serial_reconfig(void *handle, Conf *conf) { Serial serial = (Serial) handle; const char *err; - err = serial_configure(serial, serial->port, cfg); + err = serial_configure(serial, serial->port, conf); /* * FIXME: what should we do if err returns something? */ } @@ -330,15 +329,15 @@ { /* Do nothing! */ return; } -static void serbreak_timer(void *ctx, long now) +static void serbreak_timer(void *ctx, unsigned long now) { Serial serial = (Serial)ctx; - if (now >= serial->clearbreak_time && serial->port) { + if (now == serial->clearbreak_time && serial->port) { ClearCommBreak(serial->port); serial->break_in_progress = FALSE; logevent(serial->frontend, "Finished serial break"); } } Index: windows/winsftp.c ================================================================== --- windows/winsftp.c +++ windows/winsftp.c @@ -18,11 +18,11 @@ if (ret == -1) ret = console_get_userpass_input(p, in, inlen); return ret; } -void platform_get_x11_auth(struct X11Display *display, const Config *cfg) +void platform_get_x11_auth(struct X11Display *display, Conf *conf) { /* Do nothing, therefore no auth. */ } const int platform_uses_x11_unix_by_default = TRUE; @@ -86,11 +86,12 @@ struct RFile { HANDLE h; }; RFile *open_existing_file(char *name, uint64 *size, - unsigned long *mtime, unsigned long *atime) + unsigned long *mtime, unsigned long *atime, + long *perms) { HANDLE h; RFile *ret; h = CreateFile(name, GENERIC_READ, FILE_SHARE_READ, NULL, @@ -111,10 +112,13 @@ TIME_WIN_TO_POSIX(actime, *atime); if (mtime) TIME_WIN_TO_POSIX(wrtime, *mtime); } + if (perms) + *perms = -1; + return ret; } int read_from_file(RFile *f, void *buffer, int length) { @@ -135,11 +139,11 @@ struct WFile { HANDLE h; }; -WFile *open_new_file(char *name) +WFile *open_new_file(char *name, long perms) { HANDLE h; WFile *ret; h = CreateFile(name, GENERIC_WRITE, 0, NULL, @@ -480,19 +484,26 @@ extern int select_result(WPARAM, LPARAM); int do_eventsel_loop(HANDLE other_event) { int n, nhandles, nallhandles, netindex, otherindex; - long next, ticks; + unsigned long next, then; + long ticks; HANDLE *handles; SOCKET *sklist; int skcount; - long now = GETTICKCOUNT(); + unsigned long now = GETTICKCOUNT(); - if (run_timers(now, &next)) { - ticks = next - GETTICKCOUNT(); - if (ticks < 0) ticks = 0; /* just in case */ + if (toplevel_callback_pending()) { + ticks = 0; + } else if (run_timers(now, &next)) { + then = now; + now = GETTICKCOUNT(); + if (now - then > next - then) + ticks = 0; + else + ticks = next - now; } else { ticks = INFINITE; } handles = handle_get_events(&nhandles); @@ -574,10 +585,12 @@ sfree(sklist); } sfree(handles); + run_toplevel_callbacks(); + if (n == WAIT_TIMEOUT) { now = next; } else { now = GETTICKCOUNT(); } @@ -600,26 +613,30 @@ int ssh_sftp_loop_iteration(void) { if (p_WSAEventSelect == NULL) { fd_set readfds; int ret; - long now = GETTICKCOUNT(); + unsigned long now = GETTICKCOUNT(), then; if (sftp_ssh_socket == INVALID_SOCKET) return -1; /* doom */ if (socket_writable(sftp_ssh_socket)) select_result((WPARAM) sftp_ssh_socket, (LPARAM) FD_WRITE); do { - long next, ticks; + unsigned long next; + long ticks; struct timeval tv, *ptv; if (run_timers(now, &next)) { - ticks = next - GETTICKCOUNT(); - if (ticks <= 0) - ticks = 1; /* just in case */ + then = now; + now = GETTICKCOUNT(); + if (now - then > next - then) + ticks = 0; + else + ticks = next - now; tv.tv_sec = ticks / 1000; tv.tv_usec = ticks % 1000 * 1000; ptv = &tv; } else { ptv = NULL; ADDED windows/winshare.c Index: windows/winshare.c ================================================================== --- /dev/null +++ windows/winshare.c @@ -0,0 +1,228 @@ +/* + * Windows implementation of SSH connection-sharing IPC setup. + */ + +#include +#include + +#define DEFINE_PLUG_METHOD_MACROS +#include "tree234.h" +#include "putty.h" +#include "network.h" +#include "proxy.h" +#include "ssh.h" + +#if !defined NO_SECURITY + +#include "winsecur.h" + +#define CONNSHARE_PIPE_PREFIX "\\\\.\\pipe\\putty-connshare" +#define CONNSHARE_MUTEX_PREFIX "Local\\putty-connshare-mutex" + +static char *obfuscate_name(const char *realname) +{ + /* + * Windows's named pipes all live in the same namespace, so one + * user can see what pipes another user has open. This is an + * undesirable privacy leak and in particular permits one user to + * know what username@host another user is SSHing to, so we + * protect that information by using CryptProtectMemory (which + * uses a key built in to each user's account). + */ + char *cryptdata; + int cryptlen; + SHA256_State sha; + unsigned char lenbuf[4]; + unsigned char digest[32]; + char retbuf[65]; + int i; + + cryptlen = strlen(realname) + 1; + cryptlen += CRYPTPROTECTMEMORY_BLOCK_SIZE - 1; + cryptlen /= CRYPTPROTECTMEMORY_BLOCK_SIZE; + cryptlen *= CRYPTPROTECTMEMORY_BLOCK_SIZE; + + cryptdata = snewn(cryptlen, char); + memset(cryptdata, 0, cryptlen); + strcpy(cryptdata, realname); + + /* + * CRYPTPROTECTMEMORY_CROSS_PROCESS causes CryptProtectMemory to + * use the same key in all processes with this user id, meaning + * that the next PuTTY process calling this function with the same + * input will get the same data. + * + * (Contrast with CryptProtectData, which invents a new session + * key every time since its API permits returning more data than + * was input, so calling _that_ and hashing the output would not + * be stable.) + * + * We don't worry too much if this doesn't work for some reason. + * Omitting this step still has _some_ privacy value (in that + * another user can test-hash things to confirm guesses as to + * where you might be connecting to, but cannot invert SHA-256 in + * the absence of any plausible guess). So we don't abort if we + * can't call CryptProtectMemory at all, or if it fails. + */ + if (got_crypt()) + p_CryptProtectMemory(cryptdata, cryptlen, + CRYPTPROTECTMEMORY_CROSS_PROCESS); + + /* + * We don't want to give away the length of the hostname either, + * so having got it back out of CryptProtectMemory we now hash it. + */ + SHA256_Init(&sha); + PUT_32BIT_MSB_FIRST(lenbuf, cryptlen); + SHA256_Bytes(&sha, lenbuf, 4); + SHA256_Bytes(&sha, cryptdata, cryptlen); + SHA256_Final(&sha, digest); + + sfree(cryptdata); + + /* + * Finally, make printable. + */ + for (i = 0; i < 32; i++) { + sprintf(retbuf + 2*i, "%02x", digest[i]); + /* the last of those will also write the trailing NUL */ + } + + return dupstr(retbuf); +} + +static char *make_name(const char *prefix, const char *name) +{ + char *username, *retname; + + username = get_username(); + retname = dupprintf("%s.%s.%s", prefix, username, name); + sfree(username); + + return retname; +} + +Socket new_named_pipe_client(const char *pipename, Plug plug); +Socket new_named_pipe_listener(const char *pipename, Plug plug); + +int platform_ssh_share(const char *pi_name, Conf *conf, + Plug downplug, Plug upplug, Socket *sock, + char **logtext, char **ds_err, char **us_err, + int can_upstream, int can_downstream) +{ + char *name, *mutexname, *pipename; + HANDLE mutex; + Socket retsock; + PSECURITY_DESCRIPTOR psd; + PACL acl; + PSID networksid; + + /* + * Transform the platform-independent version of the connection + * identifier into the obfuscated version we'll use for our + * Windows named pipe and mutex. A side effect of doing this is + * that it also eliminates any characters illegal in Windows pipe + * names. + */ + name = obfuscate_name(pi_name); + if (!name) { + *logtext = dupprintf("Unable to call CryptProtectMemory: %s", + win_strerror(GetLastError())); + return SHARE_NONE; + } + + /* + * Make a mutex name out of the connection identifier, and lock it + * while we decide whether to be upstream or downstream. + */ + { + SECURITY_ATTRIBUTES sa; + + mutexname = make_name(CONNSHARE_MUTEX_PREFIX, name); + if (!make_private_security_descriptor(MUTEX_ALL_ACCESS, + &psd, &networksid, + &acl, logtext)) { + sfree(mutexname); + return SHARE_NONE; + } + + memset(&sa, 0, sizeof(sa)); + sa.nLength = sizeof(sa); + sa.lpSecurityDescriptor = psd; + sa.bInheritHandle = FALSE; + + mutex = CreateMutex(&sa, FALSE, mutexname); + + if (!mutex) { + *logtext = dupprintf("CreateMutex(\"%s\") failed: %s", + mutexname, win_strerror(GetLastError())); + sfree(mutexname); + LocalFree(psd); + LocalFree(networksid); + LocalFree(acl); + return SHARE_NONE; + } + + sfree(mutexname); + LocalFree(psd); + LocalFree(networksid); + LocalFree(acl); + + WaitForSingleObject(mutex, INFINITE); + } + + pipename = make_name(CONNSHARE_PIPE_PREFIX, name); + + *logtext = NULL; + + if (can_downstream) { + retsock = new_named_pipe_client(pipename, downplug); + if (sk_socket_error(retsock) == NULL) { + sfree(*logtext); + *logtext = pipename; + *sock = retsock; + sfree(name); + ReleaseMutex(mutex); + CloseHandle(mutex); + return SHARE_DOWNSTREAM; + } + sfree(*ds_err); + *ds_err = dupprintf("%s: %s", pipename, sk_socket_error(retsock)); + sk_close(retsock); + } + + if (can_upstream) { + retsock = new_named_pipe_listener(pipename, upplug); + if (sk_socket_error(retsock) == NULL) { + sfree(*logtext); + *logtext = pipename; + *sock = retsock; + sfree(name); + ReleaseMutex(mutex); + CloseHandle(mutex); + return SHARE_UPSTREAM; + } + sfree(*us_err); + *us_err = dupprintf("%s: %s", pipename, sk_socket_error(retsock)); + sk_close(retsock); + } + + /* One of the above clauses ought to have happened. */ + assert(*logtext || *ds_err || *us_err); + + sfree(pipename); + sfree(name); + ReleaseMutex(mutex); + CloseHandle(mutex); + return SHARE_NONE; +} + +void platform_ssh_share_cleanup(const char *name) +{ +} + +#else /* !defined NO_SECURITY */ + +#include "noshare.c" + +#endif /* !defined NO_SECURITY */ Index: windows/winstore.c ================================================================== --- windows/winstore.c +++ windows/winstore.c @@ -148,21 +148,33 @@ sfree(p); return (void *) sesskey; } -char *read_setting_s(void *handle, const char *key, char *buffer, int buflen) +char *read_setting_s(void *handle, const char *key) { DWORD type, size; - size = buflen; - - if (!handle || - RegQueryValueEx((HKEY) handle, key, 0, - &type, buffer, &size) != ERROR_SUCCESS || - type != REG_SZ) return NULL; - else - return buffer; + char *ret; + + if (!handle) + return NULL; + + /* Find out the type and size of the data. */ + if (RegQueryValueEx((HKEY) handle, key, 0, + &type, NULL, &size) != ERROR_SUCCESS || + type != REG_SZ) + return NULL; + + ret = snewn(size+1, char); + if (RegQueryValueEx((HKEY) handle, key, 0, + &type, ret, &size) != ERROR_SUCCESS || + type != REG_SZ) { + sfree(ret); + return NULL; + } + + return ret; } int read_setting_i(void *handle, const char *key, int defvalue) { DWORD type, val, size; @@ -175,57 +187,80 @@ return defvalue; else return val; } -int read_setting_fontspec(void *handle, const char *name, FontSpec *result) -{ - char *settingname; - FontSpec ret; - - if (!read_setting_s(handle, name, ret.name, sizeof(ret.name))) - return 0; - settingname = dupcat(name, "IsBold", NULL); - ret.isbold = read_setting_i(handle, settingname, -1); - sfree(settingname); - if (ret.isbold == -1) return 0; - settingname = dupcat(name, "CharSet", NULL); - ret.charset = read_setting_i(handle, settingname, -1); - sfree(settingname); - if (ret.charset == -1) return 0; - settingname = dupcat(name, "Height", NULL); - ret.height = read_setting_i(handle, settingname, INT_MIN); - sfree(settingname); - if (ret.height == INT_MIN) return 0; - *result = ret; - return 1; -} - -void write_setting_fontspec(void *handle, const char *name, FontSpec font) -{ - char *settingname; - - write_setting_s(handle, name, font.name); - settingname = dupcat(name, "IsBold", NULL); - write_setting_i(handle, settingname, font.isbold); - sfree(settingname); - settingname = dupcat(name, "CharSet", NULL); - write_setting_i(handle, settingname, font.charset); - sfree(settingname); - settingname = dupcat(name, "Height", NULL); - write_setting_i(handle, settingname, font.height); - sfree(settingname); -} - -int read_setting_filename(void *handle, const char *name, Filename *result) -{ - return !!read_setting_s(handle, name, result->path, sizeof(result->path)); -} - -void write_setting_filename(void *handle, const char *name, Filename result) -{ - write_setting_s(handle, name, result.path); +FontSpec *read_setting_fontspec(void *handle, const char *name) +{ + char *settingname; + char *fontname; + FontSpec *ret; + int isbold, height, charset; + + fontname = read_setting_s(handle, name); + if (!fontname) + return NULL; + + settingname = dupcat(name, "IsBold", NULL); + isbold = read_setting_i(handle, settingname, -1); + sfree(settingname); + if (isbold == -1) { + sfree(fontname); + return NULL; + } + + settingname = dupcat(name, "CharSet", NULL); + charset = read_setting_i(handle, settingname, -1); + sfree(settingname); + if (charset == -1) { + sfree(fontname); + return NULL; + } + + settingname = dupcat(name, "Height", NULL); + height = read_setting_i(handle, settingname, INT_MIN); + sfree(settingname); + if (height == INT_MIN) { + sfree(fontname); + return NULL; + } + + ret = fontspec_new(fontname, isbold, height, charset); + sfree(fontname); + return ret; +} + +void write_setting_fontspec(void *handle, const char *name, FontSpec *font) +{ + char *settingname; + + write_setting_s(handle, name, font->name); + settingname = dupcat(name, "IsBold", NULL); + write_setting_i(handle, settingname, font->isbold); + sfree(settingname); + settingname = dupcat(name, "CharSet", NULL); + write_setting_i(handle, settingname, font->charset); + sfree(settingname); + settingname = dupcat(name, "Height", NULL); + write_setting_i(handle, settingname, font->height); + sfree(settingname); +} + +Filename *read_setting_filename(void *handle, const char *name) +{ + char *tmp = read_setting_s(handle, name); + if (tmp) { + Filename *ret = filename_from_str(tmp); + sfree(tmp); + return ret; + } else + return NULL; +} + +void write_setting_filename(void *handle, const char *name, Filename *result) +{ + write_setting_s(handle, name, result->path); } void close_settings_r(void *handle) { RegCloseKey((HKEY) handle); @@ -318,20 +353,22 @@ /* * Now read a saved key in from the registry and see what it * says. */ - otherstr = snewn(len, char); regname = snewn(3 * (strlen(hostname) + strlen(keytype)) + 15, char); hostkey_regname(regname, hostname, port, keytype); if (RegOpenKey(HKEY_CURRENT_USER, PUTTY_REG_POS "\\SshHostKeys", - &rkey) != ERROR_SUCCESS) + &rkey) != ERROR_SUCCESS) { + sfree(regname); return 1; /* key does not exist in registry */ + } readlen = len; + otherstr = snewn(len, char); ret = RegQueryValueEx(rkey, regname, NULL, &type, otherstr, &readlen); if (ret != ERROR_SUCCESS && ret != ERROR_MORE_DATA && !strcmp(keytype, "rsa")) { /* @@ -390,10 +427,12 @@ */ if (!strcmp(otherstr, key)) RegSetValueEx(rkey, regname, 0, REG_SZ, otherstr, strlen(otherstr) + 1); } + + sfree(oldstyle); } RegCloseKey(rkey); compare = strcmp(otherstr, key); @@ -434,11 +473,14 @@ */ enum { DEL, OPEN_R, OPEN_W }; static int try_random_seed(char const *path, int action, HANDLE *ret) { if (action == DEL) { - remove(path); + if (!DeleteFile(path) && GetLastError() != ERROR_FILE_NOT_FOUND) { + nonfatal("Unable to delete '%s': %s", path, + win_strerror(GetLastError())); + } *ret = INVALID_HANDLE_VALUE; return FALSE; /* so we'll do the next ones too */ } *ret = CreateFile(path, @@ -705,11 +747,11 @@ ret = ERROR_SUCCESS; /* * Either return or free the result. */ - if (out) + if (out && ret == ERROR_SUCCESS) *out = old_value; else sfree(old_value); /* Clean up and return. */ @@ -738,11 +780,11 @@ * the returned pointer. */ char *get_jumplist_registry_entries (void) { char *list_value; - if (transform_jumplist_registry(NULL,NULL,&list_value) != ERROR_SUCCESS) { + if (transform_jumplist_registry(NULL,NULL,&list_value) != JUMPLISTREG_OK) { list_value = snewn(2, char); *list_value = '\0'; *(list_value + 1) = '\0'; } return list_value; Index: windows/winstuff.h ================================================================== --- windows/winstuff.h +++ windows/winstuff.h @@ -14,20 +14,22 @@ #include "tree234.h" #include "winhelp.h" struct Filename { - char path[FILENAME_MAX]; + char *path; }; -#define f_open(filename, mode, isprivate) ( fopen((filename).path, (mode)) ) +#define f_open(filename, mode, isprivate) ( fopen((filename)->path, (mode)) ) struct FontSpec { - char name[64]; + char *name; int isbold; int height; int charset; }; +struct FontSpec *fontspec_new(const char *name, + int bold, int height, int charset); #ifndef CLEARTYPE_QUALITY #define CLEARTYPE_QUALITY 5 #endif #define FONT_QUALITY(fq) ( \ @@ -71,10 +73,14 @@ #define BOXFLAGS DLGWINDOWEXTRA #define BOXRESULT (DLGWINDOWEXTRA + sizeof(LONG_PTR)) #define DF_END 0x0001 +#ifndef NO_SECUREZEROMEMORY +#define PLATFORM_HAS_SMEMCLR /* inhibit cross-platform one in misc.c */ +#endif + /* * Dynamically linked functions. These come in two flavours: * * - GET_WINDOWS_FUNCTION does not expose "name" to the preprocessor, * so will always dynamically link against exactly what is specified @@ -113,11 +119,11 @@ #endif #endif #ifndef DONE_TYPEDEFS #define DONE_TYPEDEFS -typedef struct config_tag Config; +typedef struct conf_tag Conf; typedef struct backend_tag Backend; typedef struct terminal_tag Terminal; #endif #define PUTTY_REG_POS "Software\\SimonTatham\\PuTTY" @@ -141,10 +147,11 @@ #define GETTICKCOUNT GetTickCount #define CURSORBLINK GetCaretBlinkTime() #define TICKSPERSEC 1000 /* GetTickCount returns milliseconds */ #define DEFAULT_CODEPAGE CP_ACP +#define USES_VTLINE_HACK typedef HDC Context; typedef unsigned int uint32; /* int is 32-bits on Win32 and Win64. */ #define PUTTY_UINT32_DEFINED @@ -232,17 +239,13 @@ "All Files (*.*)\0*\0\0\0") #define FILTER_DYNLIB_FILES ("Dynamic Library Files (*.dll)\0*.dll\0" \ "All Files (*.*)\0*\0\0\0") /* - * On some versions of Windows, it has been known for WM_TIMER to - * occasionally get its callback time simply wrong, and call us - * back several minutes early. Defining these symbols enables - * compensation code in timing.c. + * Exports from winnet.c. */ -#define TIMING_SYNC -#define TIMING_SYNC_TICKCOUNT +extern int select_result(WPARAM, LPARAM); /* * winnet.c dynamically loads WinSock 2 or WinSock 1 depending on * what it can get, which means any WinSock routines used outside * that module must be exported from it as function pointers. So @@ -283,10 +286,11 @@ typedef struct filereq_tag filereq; /* cwd for file requester */ BOOL request_file(filereq *state, OPENFILENAME *of, int preserve, int save); filereq *filereq_new(void); void filereq_free(filereq *state); int message_box(LPCTSTR text, LPCTSTR caption, DWORD style, DWORD helpctxid); +char *GetDlgItemText_alloc(HWND hwnd, int id); void split_into_argv(char *, int *, char ***, char ***); /* * Private structure for prefslist state. Only in the header file * so that we can delegate allocation to callers. @@ -460,10 +464,11 @@ * Exports from winmisc.c. */ extern OSVERSIONINFO osVersion; BOOL init_winver(void); HMODULE load_system32_dll(const char *libname); +const char *win_strerror(int error); /* * Exports from sizetip.c. */ void UpdateSizeTip(HWND src, int cx, int cy); @@ -471,11 +476,11 @@ /* * Exports from unicode.c. */ struct unicode_data; -void init_ucs(Config *, struct unicode_data *); +void init_ucs(Conf *, struct unicode_data *); /* * Exports from winhandl.c. */ #define HANDLE_FLAG_OVERLAPPED 1 @@ -487,16 +492,19 @@ struct handle *handle_input_new(HANDLE handle, handle_inputfn_t gotdata, void *privdata, int flags); struct handle *handle_output_new(HANDLE handle, handle_outputfn_t sentdata, void *privdata, int flags); int handle_write(struct handle *h, const void *data, int len); +void handle_write_eof(struct handle *h); HANDLE *handle_get_events(int *nevents); void handle_free(struct handle *h); void handle_got_event(HANDLE event); void handle_unthrottle(struct handle *h, int backlog); int handle_backlog(struct handle *h); void *handle_get_privdata(struct handle *h); +struct handle *handle_add_foreign_event(HANDLE event, + void (*callback)(void *), void *ctx); /* * winpgntc.c needs to schedule callbacks for asynchronous agent * requests. This has to be done differently in GUI and console, so * there's an exported function used for the purpose. @@ -506,18 +514,10 @@ */ void agent_schedule_callback(void (*callback)(void *, void *, int), void *callback_ctx, void *data, int len); #define FLAG_SYNCAGENT 0x1000 -/* - * winpgntc.c also exports these two functions which are used by the - * server side of Pageant as well, to get the user SID for comparing - * with clients'. - */ -int init_advapi(void); /* initialises everything needed by get_user_sid */ -PSID get_user_sid(void); - /* * Exports from winser.c. */ extern Backend serial_backend; Index: windows/winucs.c ================================================================== --- windows/winucs.c +++ windows/winucs.c @@ -388,10 +388,12 @@ int cp_size; const wchar_t *cp_table; }; static const struct cp_list_item cp_list[] = { + {"UTF-8", CP_UTF8}, + {"ISO-8859-1:1998 (Latin-1, West Europe)", 0, 96, iso_8859_1}, {"ISO-8859-2:1999 (Latin-2, East Europe)", 0, 96, iso_8859_2}, {"ISO-8859-3:1999 (Latin-3, South Europe)", 0, 96, iso_8859_3}, {"ISO-8859-4:1998 (Latin-4, North Europe)", 0, 96, iso_8859_4}, {"ISO-8859-5:1999 (Latin/Cyrillic)", 0, 96, iso_8859_5}, @@ -404,12 +406,10 @@ {"ISO-8859-13:1998 (Latin-7, Baltic)", 0, 96, iso_8859_13}, {"ISO-8859-14:1998 (Latin-8, Celtic)", 0, 96, iso_8859_14}, {"ISO-8859-15:1999 (Latin-9, \"euro\")", 0, 96, iso_8859_15}, {"ISO-8859-16:2001 (Latin-10, Balkan)", 0, 96, iso_8859_16}, - {"UTF-8", CP_UTF8}, - {"KOI8-U", 0, 128, koi8_u}, {"KOI8-R", 20866}, {"HP-ROMAN8", 0, 96, roman8}, {"VSCII", 0, 256, vscii}, {"DEC-MCS", 0, 96, dec_mcs}, @@ -425,37 +425,41 @@ {"Win1258 (Vietnamese)", 1258}, {"CP437", 437}, {"CP620 (Mazovia)", 0, 128, mazovia}, {"CP819", 28591}, + {"CP852", 852}, {"CP878", 20866}, {"Use font encoding", -1}, {0, 0} }; static void link_font(WCHAR * line_tbl, WCHAR * font_tbl, WCHAR attr); -void init_ucs(Config *cfg, struct unicode_data *ucsdata) +void init_ucs(Conf *conf, struct unicode_data *ucsdata) { int i, j; int used_dtf = 0; char tbuf[256]; + int vtmode; for (i = 0; i < 256; i++) tbuf[i] = i; /* Decide on the Line and Font codepages */ - ucsdata->line_codepage = decode_codepage(cfg->line_codepage); + ucsdata->line_codepage = decode_codepage(conf_get_str(conf, + CONF_line_codepage)); if (ucsdata->font_codepage <= 0) { ucsdata->font_codepage=0; ucsdata->dbcs_screenfont=0; } - if (cfg->vtmode == VT_OEMONLY) { + vtmode = conf_get_int(conf, CONF_vtmode); + if (vtmode == VT_OEMONLY) { ucsdata->font_codepage = 437; ucsdata->dbcs_screenfont = 0; if (ucsdata->line_codepage <= 0) ucsdata->line_codepage = GetACP(); } else if (ucsdata->line_codepage <= 0) @@ -471,28 +475,28 @@ /* CP437 fonts are often broken ... */ if (ucsdata->font_codepage == 437) ucsdata->unitab_font[0] = ucsdata->unitab_font[255] = 0xFFFF; } - if (cfg->vtmode == VT_XWINDOWS) + if (vtmode == VT_XWINDOWS) memcpy(ucsdata->unitab_font + 1, unitab_xterm_std, sizeof(unitab_xterm_std)); /* Collect OEMCP ucs table */ get_unitab(CP_OEMCP, ucsdata->unitab_oemcp, 1); /* Collect CP437 ucs table for SCO acs */ - if (cfg->vtmode == VT_OEMANSI || cfg->vtmode == VT_XWINDOWS) + if (vtmode == VT_OEMANSI || vtmode == VT_XWINDOWS) memcpy(ucsdata->unitab_scoacs, ucsdata->unitab_oemcp, sizeof(ucsdata->unitab_scoacs)); else get_unitab(437, ucsdata->unitab_scoacs, 1); /* Collect line set ucs table */ if (ucsdata->line_codepage == ucsdata->font_codepage && (ucsdata->dbcs_screenfont || - cfg->vtmode == VT_POORMAN || ucsdata->font_codepage==0)) { + vtmode == VT_POORMAN || ucsdata->font_codepage==0)) { /* For DBCS and POOR fonts force direct to font */ used_dtf = 1; for (i = 0; i < 32; i++) ucsdata->unitab_line[i] = (WCHAR) i; @@ -558,18 +562,18 @@ ucsdata->unitab_ctrl[i] = i; else ucsdata->unitab_ctrl[i] = 0xFF; /* Generate line->screen direct conversion links. */ - if (cfg->vtmode == VT_OEMANSI || cfg->vtmode == VT_XWINDOWS) + if (vtmode == VT_OEMANSI || vtmode == VT_XWINDOWS) link_font(ucsdata->unitab_scoacs, ucsdata->unitab_oemcp, CSET_OEMCP); link_font(ucsdata->unitab_line, ucsdata->unitab_font, CSET_ACP); link_font(ucsdata->unitab_scoacs, ucsdata->unitab_font, CSET_ACP); link_font(ucsdata->unitab_xterm, ucsdata->unitab_font, CSET_ACP); - if (cfg->vtmode == VT_OEMANSI || cfg->vtmode == VT_XWINDOWS) { + if (vtmode == VT_OEMANSI || vtmode == VT_XWINDOWS) { link_font(ucsdata->unitab_line, ucsdata->unitab_oemcp, CSET_OEMCP); link_font(ucsdata->unitab_xterm, ucsdata->unitab_oemcp, CSET_OEMCP); } if (ucsdata->dbcs_screenfont && @@ -579,11 +583,11 @@ * still given as U+005C not the correct U+00A5. */ ucsdata->unitab_line['\\'] = CSET_OEMCP + '\\'; } /* Last chance, if !unicode then try poorman links. */ - if (cfg->vtmode != VT_UNICODE) { + if (vtmode != VT_UNICODE) { static const char poorman_scoacs[] = "CueaaaaceeeiiiAAE**ooouuyOUc$YPsaiounNao?++**!<>###||||++||++++++--|-+||++--|-+----++++++++##||#aBTPEsyt******EN=+><++-=... n2* "; static const char poorman_latin1[] = " !cL.Y|S\"Ca<--R~o+23'u|.,1o>///?AAAAAAACEEEEIIIIDNOOOOOxOUUUUYPBaaaaaaaceeeeiiiionooooo/ouuuuypy"; static const char poorman_vt100[] = "*#****o~**+++++-----++++|****L."; @@ -1010,99 +1014,58 @@ char *s, *d; const struct cp_list_item *cpi; int codepage = -1; CPINFO cpinfo; - if (!*cp_name) { - /* - * Here we select a plausible default code page based on - * the locale the user is in. We wish to select an ISO code - * page or appropriate local default _rather_ than go with - * the Win125* series, because it's more important to have - * CSI and friends enabled by default than the ghastly - * Windows extra quote characters, and because it's more - * likely the user is connecting to a remote server that - * does something Unixy or VMSy and hence standards- - * compliant than that they're connecting back to a Windows - * box using horrible nonstandard charsets. - * - * Accordingly, Robert de Bath suggests a method for - * picking a default character set that runs as follows: - * first call GetACP to get the system's ANSI code page - * identifier, and translate as follows: - * - * 1250 -> ISO 8859-2 - * 1251 -> KOI8-U - * 1252 -> ISO 8859-1 - * 1253 -> ISO 8859-7 - * 1254 -> ISO 8859-9 - * 1255 -> ISO 8859-8 - * 1256 -> ISO 8859-6 - * 1257 -> ISO 8859-13 (changed from 8859-4 on advice of a Lithuanian) - * - * and for anything else, choose direct-to-font. - */ - int cp = GetACP(); - switch (cp) { - case 1250: cp_name = "ISO-8859-2"; break; - case 1251: cp_name = "KOI8-U"; break; - case 1252: cp_name = "ISO-8859-1"; break; - case 1253: cp_name = "ISO-8859-7"; break; - case 1254: cp_name = "ISO-8859-9"; break; - case 1255: cp_name = "ISO-8859-8"; break; - case 1256: cp_name = "ISO-8859-6"; break; - case 1257: cp_name = "ISO-8859-13"; break; - /* default: leave it blank, which will select -1, direct->font */ - } - } - - if (cp_name && *cp_name) - for (cpi = cp_list; cpi->name; cpi++) { - s = cp_name; - d = cpi->name; - for (;;) { - while (*s && !isalnum(*s) && *s != ':') - s++; - while (*d && !isalnum(*d) && *d != ':') - d++; - if (*s == 0) { - codepage = cpi->codepage; - if (codepage == CP_UTF8) - goto break_break; - if (codepage == -1) - return codepage; - if (codepage == 0) { - codepage = 65536 + (cpi - cp_list); - goto break_break; - } - - if (GetCPInfo(codepage, &cpinfo) != 0) - goto break_break; - } - if (tolower(*s++) != tolower(*d++)) - break; - } - } - - if (cp_name && *cp_name) { - d = cp_name; - if (tolower(d[0]) == 'c' && tolower(d[1]) == 'p') - d += 2; - if (tolower(d[0]) == 'i' && tolower(d[1]) == 'b' - && tolower(d[2]) == 'm') - d += 3; - for (s = d; *s >= '0' && *s <= '9'; s++); - if (*s == 0 && s != d) - codepage = atoi(d); /* CP999 or IBM999 */ - - if (codepage == CP_ACP) - codepage = GetACP(); - if (codepage == CP_OEMCP) - codepage = GetOEMCP(); - if (codepage > 65535) - codepage = -2; - } + if (!cp_name || !*cp_name) + return CP_UTF8; /* default */ + + for (cpi = cp_list; cpi->name; cpi++) { + s = cp_name; + d = cpi->name; + for (;;) { + while (*s && !isalnum(*s) && *s != ':') + s++; + while (*d && !isalnum(*d) && *d != ':') + d++; + if (*s == 0) { + codepage = cpi->codepage; + if (codepage == CP_UTF8) + goto break_break; + if (codepage == -1) + return codepage; + if (codepage == 0) { + codepage = 65536 + (cpi - cp_list); + goto break_break; + } + + if (GetCPInfo(codepage, &cpinfo) != 0) + goto break_break; + } + if (tolower((unsigned char)*s++) != tolower((unsigned char)*d++)) + break; + } + } + + d = cp_name; + if (tolower((unsigned char)d[0]) == 'c' && + tolower((unsigned char)d[1]) == 'p') + d += 2; + if (tolower((unsigned char)d[0]) == 'i' && + tolower((unsigned char)d[1]) == 'b' && + tolower((unsigned char)d[2]) == 'm') + d += 3; + for (s = d; *s >= '0' && *s <= '9'; s++); + if (*s == 0 && s != d) + codepage = atoi(d); /* CP999 or IBM999 */ + + if (codepage == CP_ACP) + codepage = GetACP(); + if (codepage == CP_OEMCP) + codepage = GetOEMCP(); + if (codepage > 65535) + codepage = -2; break_break:; if (codepage != -1) { if (codepage != CP_UTF8 && codepage < 65536) { if (GetCPInfo(codepage, &cpinfo) == 0) { @@ -1199,11 +1162,11 @@ for (i = j; i < max; i++) unitab[i] = cp_list[codepage & 0xFFFF].cp_table[i - j]; } } -int wc_to_mb(int codepage, int flags, wchar_t *wcstr, int wclen, +int wc_to_mb(int codepage, int flags, const wchar_t *wcstr, int wclen, char *mbstr, int mblen, char *defchr, int *defused, struct unicode_data *ucsdata) { char *p; int i; @@ -1237,15 +1200,15 @@ } else return WideCharToMultiByte(codepage, flags, wcstr, wclen, mbstr, mblen, defchr, defused); } -int mb_to_wc(int codepage, int flags, char *mbstr, int mblen, +int mb_to_wc(int codepage, int flags, const char *mbstr, int mblen, wchar_t *wcstr, int wclen) { return MultiByteToWideChar(codepage, flags, mbstr, mblen, wcstr, wclen); } int is_dbcs_leadbyte(int codepage, char byte) { return IsDBCSLeadByteEx(codepage, byte); } Index: windows/winutils.c ================================================================== --- windows/winutils.c +++ windows/winutils.c @@ -149,16 +149,35 @@ "PGP fingerprints", MB_ICONINFORMATION | MB_OK, HELPCTXID(pgp_fingerprints)); } /* - * Split a complete command line into argc/argv, attempting to do - * it exactly the same way Windows itself would do it (so that - * console utilities, which receive argc and argv from Windows, - * will have their command lines processed in the same way as GUI - * utilities which get a whole command line and must break it - * themselves). + * Handy wrapper around GetDlgItemText which doesn't make you invent + * an arbitrary length limit on the output string. Returned string is + * dynamically allocated; caller must free. + */ +char *GetDlgItemText_alloc(HWND hwnd, int id) +{ + char *ret = NULL; + int size = 0; + + do { + size = size * 4 / 3 + 512; + ret = sresize(ret, size, char); + GetDlgItemText(hwnd, id, ret, size); + } while (!memchr(ret, '\0', size-1)); + + return ret; +} + +/* + * Split a complete command line into argc/argv, attempting to do it + * exactly the same way the Visual Studio C library would do it (so + * that our console utilities, which receive argc and argv already + * broken apart by the C library, will have their command lines + * processed in the same way as the GUI utilities which get a whole + * command line and must call this function). * * Does not modify the input command line. * * The final parameter (argstart) is used to return a second array * of char * pointers, the same length as argv, each one pointing @@ -175,11 +194,21 @@ char *outputline, *q; char **outputargv, **outputargstart; int outputargc; /* - * At first glance the rules appeared to be: + * These argument-breaking rules apply to Visual Studio 7, which + * is currently the compiler expected to be used for PuTTY. Visual + * Studio 10 has different rules, lacking the curious mod 3 + * behaviour of consecutive quotes described below; I presume they + * fixed a bug. As and when we migrate to a newer compiler, we'll + * have to adjust this to match; however, for the moment we + * faithfully imitate in our GUI utilities what our CLI utilities + * can't be prevented from doing. + * + * When I investigated this, at first glance the rules appeared to + * be: * * - Single quotes are not special characters. * * - Double quotes are removed, but within them spaces cease * to be special. Index: windows/winx11.c ================================================================== --- windows/winx11.c +++ windows/winx11.c @@ -1,18 +1,19 @@ -/* - * winx11.c: fetch local auth data for X forwarding. - */ - -#include -#include -#include - -#include "putty.h" -#include "ssh.h" - -void platform_get_x11_auth(struct X11Display *disp, const Config *cfg) -{ - if (cfg->xauthfile.path[0]) - x11_get_auth_from_authfile(disp, cfg->xauthfile.path); -} - -const int platform_uses_x11_unix_by_default = FALSE; +/* + * winx11.c: fetch local auth data for X forwarding. + */ + +#include +#include +#include + +#include "putty.h" +#include "ssh.h" + +void platform_get_x11_auth(struct X11Display *disp, Conf *conf) +{ + char *xauthpath = conf_get_filename(conf, CONF_xauthfile)->path; + if (xauthpath[0]) + x11_get_auth_from_authfile(disp, xauthpath); +} + +const int platform_uses_x11_unix_by_default = FALSE; Index: x11fwd.c ================================================================== --- x11fwd.c +++ x11fwd.c @@ -24,23 +24,25 @@ struct XDMSeen { unsigned int time; unsigned char clientid[6]; }; -struct X11Private { +struct X11Connection { const struct plug_function_table *fn; /* the above variable absolutely *must* be the first in this structure */ unsigned char firstpkt[12]; /* first X data packet */ + tree234 *authtree; struct X11Display *disp; char *auth_protocol; unsigned char *auth_data; int data_read, auth_plen, auth_psize, auth_dlen, auth_dsize; int verified; int throttled, throttle_override; - unsigned long peer_ip; + int no_data_sent_to_x_client; + char *peer_addr; int peer_port; - void *c; /* data used by ssh.c */ + struct ssh_channel *c; /* channel structure held by ssh.c */ Socket s; }; static int xdmseen_cmp(void *a, void *b) { @@ -60,22 +62,140 @@ (Plug p, const char *error_msg, int error_code, int calling_back) { return 1; } static int dummy_plug_receive(Plug p, int urgent, char *data, int len) { return 1; } static void dummy_plug_sent(Plug p, int bufsize) { } -static int dummy_plug_accepting(Plug p, OSSocket sock) { return 1; } +static int dummy_plug_accepting(Plug p, accept_fn_t constructor, accept_ctx_t ctx) { return 1; } static const struct plug_function_table dummy_plug = { dummy_plug_log, dummy_plug_closing, dummy_plug_receive, dummy_plug_sent, dummy_plug_accepting }; -struct X11Display *x11_setup_display(char *display, int authtype, - const Config *cfg) +struct X11FakeAuth *x11_invent_fake_auth(tree234 *authtree, int authtype) +{ + struct X11FakeAuth *auth = snew(struct X11FakeAuth); + int i; + + /* + * This function has the job of inventing a set of X11 fake auth + * data, and adding it to 'authtree'. We must preserve the + * property that for any given actual authorisation attempt, _at + * most one_ thing in the tree can possibly match it. + * + * For MIT-MAGIC-COOKIE-1, that's not too difficult: the match + * criterion is simply that the entire cookie is correct, so we + * just have to make sure we don't make up two cookies the same. + * (Vanishingly unlikely, but we check anyway to be sure, and go + * round again inventing a new cookie if add234 tells us the one + * we thought of is already in use.) + * + * For XDM-AUTHORIZATION-1, it's a little more fiddly. The setup + * with XA1 is that half the cookie is used as a DES key with + * which to CBC-encrypt an assortment of stuff. Happily, the stuff + * encrypted _begins_ with the other half of the cookie, and the + * IV is always zero, which means that any valid XA1 authorisation + * attempt for a given cookie must begin with the same cipher + * block, consisting of the DES ECB encryption of the first half + * of the cookie using the second half as a key. So we compute + * that cipher block here and now, and use it as the sorting key + * for distinguishing XA1 entries in the tree. + */ + + if (authtype == X11_MIT) { + auth->proto = X11_MIT; + + /* MIT-MAGIC-COOKIE-1. Cookie size is 128 bits (16 bytes). */ + auth->datalen = 16; + auth->data = snewn(auth->datalen, unsigned char); + auth->xa1_firstblock = NULL; + + while (1) { + for (i = 0; i < auth->datalen; i++) + auth->data[i] = random_byte(); + if (add234(authtree, auth) == auth) + break; + } + + auth->xdmseen = NULL; + } else { + assert(authtype == X11_XDM); + auth->proto = X11_XDM; + + /* XDM-AUTHORIZATION-1. Cookie size is 16 bytes; byte 8 is zero. */ + auth->datalen = 16; + auth->data = snewn(auth->datalen, unsigned char); + auth->xa1_firstblock = snewn(8, unsigned char); + memset(auth->xa1_firstblock, 0, 8); + + while (1) { + for (i = 0; i < auth->datalen; i++) + auth->data[i] = (i == 8 ? 0 : random_byte()); + memcpy(auth->xa1_firstblock, auth->data, 8); + des_encrypt_xdmauth(auth->data + 9, auth->xa1_firstblock, 8); + if (add234(authtree, auth) == auth) + break; + } + + auth->xdmseen = newtree234(xdmseen_cmp); + } + auth->protoname = dupstr(x11_authnames[auth->proto]); + auth->datastring = snewn(auth->datalen * 2 + 1, char); + for (i = 0; i < auth->datalen; i++) + sprintf(auth->datastring + i*2, "%02x", + auth->data[i]); + + auth->disp = NULL; + auth->share_cs = auth->share_chan = NULL; + + return auth; +} + +void x11_free_fake_auth(struct X11FakeAuth *auth) +{ + if (auth->data) + smemclr(auth->data, auth->datalen); + sfree(auth->data); + sfree(auth->protoname); + sfree(auth->datastring); + sfree(auth->xa1_firstblock); + if (auth->xdmseen != NULL) { + struct XDMSeen *seen; + while ((seen = delpos234(auth->xdmseen, 0)) != NULL) + sfree(seen); + freetree234(auth->xdmseen); + } + sfree(auth); +} + +int x11_authcmp(void *av, void *bv) +{ + struct X11FakeAuth *a = (struct X11FakeAuth *)av; + struct X11FakeAuth *b = (struct X11FakeAuth *)bv; + + if (a->proto < b->proto) + return -1; + else if (a->proto > b->proto) + return +1; + + if (a->proto == X11_MIT) { + if (a->datalen < b->datalen) + return -1; + else if (a->datalen > b->datalen) + return +1; + + return memcmp(a->data, b->data, a->datalen); + } else { + assert(a->proto == X11_XDM); + + return memcmp(a->xa1_firstblock, b->xa1_firstblock, 8); + } +} + +struct X11Display *x11_setup_display(char *display, Conf *conf) { struct X11Display *disp = snew(struct X11Display); char *localcopy; - int i; if (!display || !*display) { localcopy = platform_get_x_display(); if (!localcopy || !*localcopy) { sfree(localcopy); @@ -164,16 +284,17 @@ if (!disp->unixdomain) { const char *err; disp->port = 6000 + disp->displaynum; disp->addr = name_lookup(disp->hostname, disp->port, - &disp->realhost, cfg, ADDRTYPE_UNSPEC); + &disp->realhost, conf, ADDRTYPE_UNSPEC); if ((err = sk_addr_error(disp->addr)) != NULL) { sk_addr_free(disp->addr); sfree(disp->hostname); sfree(disp->unixsocketpath); + sfree(disp); return NULL; /* FIXME: report an error */ } } /* @@ -210,100 +331,87 @@ else disp->realhost = dupprintf("unix:%d", disp->displaynum); disp->port = 0; } - /* - * Invent the remote authorisation details. - */ - if (authtype == X11_MIT) { - disp->remoteauthproto = X11_MIT; - - /* MIT-MAGIC-COOKIE-1. Cookie size is 128 bits (16 bytes). */ - disp->remoteauthdata = snewn(16, unsigned char); - for (i = 0; i < 16; i++) - disp->remoteauthdata[i] = random_byte(); - disp->remoteauthdatalen = 16; - - disp->xdmseen = NULL; - } else { - assert(authtype == X11_XDM); - disp->remoteauthproto = X11_XDM; - - /* XDM-AUTHORIZATION-1. Cookie size is 16 bytes; byte 8 is zero. */ - disp->remoteauthdata = snewn(16, unsigned char); - for (i = 0; i < 16; i++) - disp->remoteauthdata[i] = (i == 8 ? 0 : random_byte()); - disp->remoteauthdatalen = 16; - - disp->xdmseen = newtree234(xdmseen_cmp); - } - disp->remoteauthprotoname = dupstr(x11_authnames[disp->remoteauthproto]); - disp->remoteauthdatastring = snewn(disp->remoteauthdatalen * 2 + 1, char); - for (i = 0; i < disp->remoteauthdatalen; i++) - sprintf(disp->remoteauthdatastring + i*2, "%02x", - disp->remoteauthdata[i]); - /* * Fetch the local authorisation details. */ disp->localauthproto = X11_NO_AUTH; disp->localauthdata = NULL; disp->localauthdatalen = 0; - platform_get_x11_auth(disp, cfg); + platform_get_x11_auth(disp, conf); return disp; } void x11_free_display(struct X11Display *disp) { - if (disp->xdmseen != NULL) { - struct XDMSeen *seen; - while ((seen = delpos234(disp->xdmseen, 0)) != NULL) - sfree(seen); - freetree234(disp->xdmseen); - } sfree(disp->hostname); sfree(disp->unixsocketpath); if (disp->localauthdata) - memset(disp->localauthdata, 0, disp->localauthdatalen); + smemclr(disp->localauthdata, disp->localauthdatalen); sfree(disp->localauthdata); - if (disp->remoteauthdata) - memset(disp->remoteauthdata, 0, disp->remoteauthdatalen); - sfree(disp->remoteauthdata); - sfree(disp->remoteauthprotoname); - sfree(disp->remoteauthdatastring); sk_addr_free(disp->addr); sfree(disp); } #define XDM_MAXSKEW 20*60 /* 20 minute clock skew should be OK */ static char *x11_verify(unsigned long peer_ip, int peer_port, - struct X11Display *disp, char *proto, - unsigned char *data, int dlen) -{ - if (strcmp(proto, x11_authnames[disp->remoteauthproto]) != 0) - return "wrong authorisation protocol attempted"; - if (disp->remoteauthproto == X11_MIT) { - if (dlen != disp->remoteauthdatalen) - return "MIT-MAGIC-COOKIE-1 data was wrong length"; - if (memcmp(disp->remoteauthdata, data, dlen) != 0) - return "MIT-MAGIC-COOKIE-1 data did not match"; - } - if (disp->remoteauthproto == X11_XDM) { + tree234 *authtree, char *proto, + unsigned char *data, int dlen, + struct X11FakeAuth **auth_ret) +{ + struct X11FakeAuth match_dummy; /* for passing to find234 */ + struct X11FakeAuth *auth; + + /* + * First, do a lookup in our tree to find the only authorisation + * record that _might_ match. + */ + if (!strcmp(proto, x11_authnames[X11_MIT])) { + /* + * Just look up the whole cookie that was presented to us, + * which x11_authcmp will compare against the cookies we + * currently believe in. + */ + match_dummy.proto = X11_MIT; + match_dummy.datalen = dlen; + match_dummy.data = data; + } else if (!strcmp(proto, x11_authnames[X11_XDM])) { + /* + * Look up the first cipher block, against the stored first + * cipher blocks for the XDM-AUTHORIZATION-1 cookies we + * currently know. (See comment in x11_invent_fake_auth.) + */ + match_dummy.proto = X11_XDM; + match_dummy.xa1_firstblock = data; + } else { + return "Unsupported authorisation protocol"; + } + + if ((auth = find234(authtree, &match_dummy, 0)) == NULL) + return "Authorisation not recognised"; + + /* + * If we're using MIT-MAGIC-COOKIE-1, that was all we needed. If + * we're doing XDM-AUTHORIZATION-1, though, we have to check the + * rest of the auth data. + */ + if (auth->proto == X11_XDM) { unsigned long t; time_t tim; int i; struct XDMSeen *seen, *ret; if (dlen != 24) return "XDM-AUTHORIZATION-1 data was wrong length"; if (peer_port == -1) return "cannot do XDM-AUTHORIZATION-1 without remote address data"; - des_decrypt_xdmauth(disp->remoteauthdata+9, data, 24); - if (memcmp(disp->remoteauthdata, data, 8) != 0) + des_decrypt_xdmauth(auth->data+9, data, 24); + if (memcmp(auth->data, data, 8) != 0) return "XDM-AUTHORIZATION-1 data failed check"; /* cookie wrong */ if (GET_32BIT_MSB_FIRST(data+8) != peer_ip) return "XDM-AUTHORIZATION-1 data failed check"; /* IP wrong */ if ((int)GET_16BIT_MSB_FIRST(data+12) != peer_port) return "XDM-AUTHORIZATION-1 data failed check"; /* port wrong */ @@ -315,26 +423,28 @@ if (abs(t - tim) > XDM_MAXSKEW) return "XDM-AUTHORIZATION-1 time stamp was too far out"; seen = snew(struct XDMSeen); seen->time = t; memcpy(seen->clientid, data+8, 6); - assert(disp->xdmseen != NULL); - ret = add234(disp->xdmseen, seen); + assert(auth->xdmseen != NULL); + ret = add234(auth->xdmseen, seen); if (ret != seen) { sfree(seen); return "XDM-AUTHORIZATION-1 data replayed"; } /* While we're here, purge entries too old to be replayed. */ for (;;) { - seen = index234(disp->xdmseen, 0); + seen = index234(auth->xdmseen, 0); assert(seen != NULL); if (t - seen->time <= XDM_MAXSKEW) break; - sfree(delpos234(disp->xdmseen, 0)); + sfree(delpos234(auth->xdmseen, 0)); } } /* implement other protocols here if ever required */ + + *auth_ret = auth; return NULL; } void x11_get_auth_from_authfile(struct X11Display *disp, const char *authfilename) @@ -342,11 +452,11 @@ FILE *authfp; char *buf, *ptr, *str[4]; int len[4]; int family, protocol; int ideal_match = FALSE; - char *ourhostname = get_hostname(); + char *ourhostname; /* * Normally we should look for precisely the details specified in * `disp'. However, there's an oddity when the display is local: * displays like "localhost:0" usually have their details stored @@ -370,10 +480,12 @@ int localhost = !disp->unixdomain && sk_address_is_local(disp->addr); authfp = fopen(authfilename, "rb"); if (!authfp) return; + + ourhostname = get_hostname(); /* Records in .Xauthority contain four strings of up to 64K each */ buf = snewn(65537 * 4, char); while (!ideal_match) { @@ -486,53 +598,76 @@ } } done: fclose(authfp); - memset(buf, 0, 65537 * 4); + smemclr(buf, 65537 * 4); sfree(buf); sfree(ourhostname); } static void x11_log(Plug p, int type, SockAddr addr, int port, const char *error_msg, int error_code) { /* We have no interface to the logging module here, so we drop these. */ } + +static void x11_send_init_error(struct X11Connection *conn, + const char *err_message); static int x11_closing(Plug plug, const char *error_msg, int error_code, int calling_back) { - struct X11Private *pr = (struct X11Private *) plug; - - /* - * We have no way to communicate down the forwarded connection, - * so if an error occurred on the socket, we just ignore it - * and treat it like a proper close. - */ - sshfwd_close(pr->c); - x11_close(pr->s); + struct X11Connection *xconn = (struct X11Connection *) plug; + + if (error_msg) { + /* + * Socket error. If we're still at the connection setup stage, + * construct an X11 error packet passing on the problem. + */ + if (xconn->no_data_sent_to_x_client) { + char *err_message = dupprintf("unable to connect to forwarded " + "X server: %s", error_msg); + x11_send_init_error(xconn, err_message); + sfree(err_message); + } + + /* + * Whether we did that or not, now we slam the connection + * shut. + */ + sshfwd_unclean_close(xconn->c, error_msg); + } else { + /* + * Ordinary EOF received on socket. Send an EOF on the SSH + * channel. + */ + if (xconn->c) + sshfwd_write_eof(xconn->c); + } + return 1; } static int x11_receive(Plug plug, int urgent, char *data, int len) { - struct X11Private *pr = (struct X11Private *) plug; + struct X11Connection *xconn = (struct X11Connection *) plug; - if (sshfwd_write(pr->c, data, len) > 0) { - pr->throttled = 1; - sk_set_frozen(pr->s, 1); + if (sshfwd_write(xconn->c, data, len) > 0) { + xconn->throttled = 1; + xconn->no_data_sent_to_x_client = FALSE; + sk_set_frozen(xconn->s, 1); } return 1; } static void x11_sent(Plug plug, int bufsize) { - struct X11Private *pr = (struct X11Private *) plug; + struct X11Connection *xconn = (struct X11Connection *) plug; - sshfwd_unthrottle(pr->c, bufsize); + sshfwd_unthrottle(xconn->c, bufsize); } /* * When setting up X forwarding, we should send the screen number * from the specified local display. This function extracts it from @@ -550,242 +685,404 @@ return 0; return atoi(display + n + 1); } /* - * Called to set up the raw connection. - * - * Returns an error message, or NULL on success. - * also, fills the SocketsStructure + * Called to set up the X11Connection structure, though this does not + * yet connect to an actual server. */ -extern const char *x11_init(Socket *s, struct X11Display *disp, void *c, - const char *peeraddr, int peerport, - const Config *cfg) +struct X11Connection *x11_init(tree234 *authtree, void *c, + const char *peeraddr, int peerport) { static const struct plug_function_table fn_table = { x11_log, x11_closing, x11_receive, x11_sent, NULL }; - const char *err; - struct X11Private *pr; + struct X11Connection *xconn; /* * Open socket. */ - pr = snew(struct X11Private); - pr->fn = &fn_table; - pr->auth_protocol = NULL; - pr->disp = disp; - pr->verified = 0; - pr->data_read = 0; - pr->throttled = pr->throttle_override = 0; - pr->c = c; - - pr->s = *s = new_connection(sk_addr_dup(disp->addr), - disp->realhost, disp->port, - 0, 1, 0, 0, (Plug) pr, cfg); - if ((err = sk_socket_error(*s)) != NULL) { - sfree(pr); - return err; - } + xconn = snew(struct X11Connection); + xconn->fn = &fn_table; + xconn->auth_protocol = NULL; + xconn->authtree = authtree; + xconn->verified = 0; + xconn->data_read = 0; + xconn->throttled = xconn->throttle_override = 0; + xconn->no_data_sent_to_x_client = TRUE; + xconn->c = c; + + /* + * We don't actually open a local socket to the X server just yet, + * because we don't know which one it is. Instead, we'll wait + * until we see the incoming authentication data, which may tell + * us what display to connect to, or whether we have to divert + * this X forwarding channel to a connection-sharing downstream + * rather than handling it ourself. + */ + xconn->disp = NULL; + xconn->s = NULL; + + /* + * Stash the peer address we were given in its original text form. + */ + xconn->peer_addr = peeraddr ? dupstr(peeraddr) : NULL; + xconn->peer_port = peerport; + + return xconn; +} + +void x11_close(struct X11Connection *xconn) +{ + if (!xconn) + return; + + if (xconn->auth_protocol) { + sfree(xconn->auth_protocol); + sfree(xconn->auth_data); + } + + if (xconn->s) + sk_close(xconn->s); + + sfree(xconn->peer_addr); + sfree(xconn); +} + +void x11_unthrottle(struct X11Connection *xconn) +{ + if (!xconn) + return; + + xconn->throttled = 0; + if (xconn->s) + sk_set_frozen(xconn->s, xconn->throttled || xconn->throttle_override); +} + +void x11_override_throttle(struct X11Connection *xconn, int enable) +{ + if (!xconn) + return; + + xconn->throttle_override = enable; + if (xconn->s) + sk_set_frozen(xconn->s, xconn->throttled || xconn->throttle_override); +} + +static void x11_send_init_error(struct X11Connection *xconn, + const char *err_message) +{ + char *full_message; + int msglen, msgsize; + unsigned char *reply; + + full_message = dupprintf("%s X11 proxy: %s\n", appname, err_message); + + msglen = strlen(full_message); + reply = snewn(8 + msglen+1 + 4, unsigned char); /* include zero */ + msgsize = (msglen + 3) & ~3; + reply[0] = 0; /* failure */ + reply[1] = msglen; /* length of reason string */ + memcpy(reply + 2, xconn->firstpkt + 2, 4); /* major/minor proto vsn */ + PUT_16BIT(xconn->firstpkt[0], reply + 6, msgsize >> 2);/* data len */ + memset(reply + 8, 0, msgsize); + memcpy(reply + 8, full_message, msglen); + sshfwd_write(xconn->c, (char *)reply, 8 + msgsize); + sshfwd_write_eof(xconn->c); + xconn->no_data_sent_to_x_client = FALSE; + sfree(reply); + sfree(full_message); +} + +static int x11_parse_ip(const char *addr_string, unsigned long *ip) +{ /* - * See if we can make sense of the peer address we were given. + * See if we can make sense of this string as an IPv4 address, for + * XDM-AUTHORIZATION-1 purposes. */ - { - int i[4]; - if (peeraddr && - 4 == sscanf(peeraddr, "%d.%d.%d.%d", i+0, i+1, i+2, i+3)) { - pr->peer_ip = (i[0] << 24) | (i[1] << 16) | (i[2] << 8) | i[3]; - pr->peer_port = peerport; - } else { - pr->peer_ip = 0; - pr->peer_port = -1; - } - } - - sk_set_private_ptr(*s, pr); - return NULL; -} - -void x11_close(Socket s) -{ - struct X11Private *pr; - if (!s) - return; - pr = (struct X11Private *) sk_get_private_ptr(s); - if (pr->auth_protocol) { - sfree(pr->auth_protocol); - sfree(pr->auth_data); - } - - sfree(pr); - - sk_close(s); -} - -void x11_unthrottle(Socket s) -{ - struct X11Private *pr; - if (!s) - return; - pr = (struct X11Private *) sk_get_private_ptr(s); - - pr->throttled = 0; - sk_set_frozen(s, pr->throttled || pr->throttle_override); -} - -void x11_override_throttle(Socket s, int enable) -{ - struct X11Private *pr; - if (!s) - return; - pr = (struct X11Private *) sk_get_private_ptr(s); - - pr->throttle_override = enable; - sk_set_frozen(s, pr->throttled || pr->throttle_override); + int i[4]; + if (addr_string && + 4 == sscanf(addr_string, "%d.%d.%d.%d", i+0, i+1, i+2, i+3)) { + *ip = (i[0] << 24) | (i[1] << 16) | (i[2] << 8) | i[3]; + return TRUE; + } else { + return FALSE; + } } /* * Called to send data down the raw connection. */ -int x11_send(Socket s, char *data, int len) +int x11_send(struct X11Connection *xconn, char *data, int len) { - struct X11Private *pr; - if (!s) + if (!xconn) return 0; - pr = (struct X11Private *) sk_get_private_ptr(s); /* * Read the first packet. */ - while (len > 0 && pr->data_read < 12) - pr->firstpkt[pr->data_read++] = (unsigned char) (len--, *data++); - if (pr->data_read < 12) + while (len > 0 && xconn->data_read < 12) + xconn->firstpkt[xconn->data_read++] = (unsigned char) (len--, *data++); + if (xconn->data_read < 12) return 0; /* * If we have not allocated the auth_protocol and auth_data * strings, do so now. */ - if (!pr->auth_protocol) { - pr->auth_plen = GET_16BIT(pr->firstpkt[0], pr->firstpkt + 6); - pr->auth_dlen = GET_16BIT(pr->firstpkt[0], pr->firstpkt + 8); - pr->auth_psize = (pr->auth_plen + 3) & ~3; - pr->auth_dsize = (pr->auth_dlen + 3) & ~3; + if (!xconn->auth_protocol) { + xconn->auth_plen = GET_16BIT(xconn->firstpkt[0], xconn->firstpkt + 6); + xconn->auth_dlen = GET_16BIT(xconn->firstpkt[0], xconn->firstpkt + 8); + xconn->auth_psize = (xconn->auth_plen + 3) & ~3; + xconn->auth_dsize = (xconn->auth_dlen + 3) & ~3; /* Leave room for a terminating zero, to make our lives easier. */ - pr->auth_protocol = snewn(pr->auth_psize + 1, char); - pr->auth_data = snewn(pr->auth_dsize, unsigned char); + xconn->auth_protocol = snewn(xconn->auth_psize + 1, char); + xconn->auth_data = snewn(xconn->auth_dsize, unsigned char); } /* * Read the auth_protocol and auth_data strings. */ - while (len > 0 && pr->data_read < 12 + pr->auth_psize) - pr->auth_protocol[pr->data_read++ - 12] = (len--, *data++); - while (len > 0 && pr->data_read < 12 + pr->auth_psize + pr->auth_dsize) - pr->auth_data[pr->data_read++ - 12 - - pr->auth_psize] = (unsigned char) (len--, *data++); - if (pr->data_read < 12 + pr->auth_psize + pr->auth_dsize) + while (len > 0 && + xconn->data_read < 12 + xconn->auth_psize) + xconn->auth_protocol[xconn->data_read++ - 12] = (len--, *data++); + while (len > 0 && + xconn->data_read < 12 + xconn->auth_psize + xconn->auth_dsize) + xconn->auth_data[xconn->data_read++ - 12 - + xconn->auth_psize] = (unsigned char) (len--, *data++); + if (xconn->data_read < 12 + xconn->auth_psize + xconn->auth_dsize) return 0; /* * If we haven't verified the authorisation, do so now. */ - if (!pr->verified) { - char *err; - - pr->auth_protocol[pr->auth_plen] = '\0'; /* ASCIZ */ - err = x11_verify(pr->peer_ip, pr->peer_port, - pr->disp, pr->auth_protocol, - pr->auth_data, pr->auth_dlen); - - /* - * If authorisation failed, construct and send an error - * packet, then terminate the connection. - */ - if (err) { - char *message; - int msglen, msgsize; - unsigned char *reply; - - message = dupprintf("%s X11 proxy: %s", appname, err); - msglen = strlen(message); - reply = snewn(8 + msglen+1 + 4, unsigned char); /* include zero */ - msgsize = (msglen + 3) & ~3; - reply[0] = 0; /* failure */ - reply[1] = msglen; /* length of reason string */ - memcpy(reply + 2, pr->firstpkt + 2, 4); /* major/minor proto vsn */ - PUT_16BIT(pr->firstpkt[0], reply + 6, msgsize >> 2);/* data len */ - memset(reply + 8, 0, msgsize); - memcpy(reply + 8, message, msglen); - sshfwd_write(pr->c, (char *)reply, 8 + msgsize); - sshfwd_close(pr->c); - x11_close(s); - sfree(reply); - sfree(message); - return 0; - } - - /* - * Now we know we're going to accept the connection. Strip - * the fake auth data, and optionally put real auth data in - * instead. - */ - { - char realauthdata[64]; - int realauthlen = 0; - int authstrlen = strlen(x11_authnames[pr->disp->localauthproto]); - int buflen = 0; /* initialise to placate optimiser */ - static const char zeroes[4] = { 0,0,0,0 }; - void *buf; - - if (pr->disp->localauthproto == X11_MIT) { - assert(pr->disp->localauthdatalen <= lenof(realauthdata)); - realauthlen = pr->disp->localauthdatalen; - memcpy(realauthdata, pr->disp->localauthdata, realauthlen); - } else if (pr->disp->localauthproto == X11_XDM && - pr->disp->localauthdatalen == 16 && - ((buf = sk_getxdmdata(s, &buflen))!=0)) { - time_t t; - realauthlen = (buflen+12+7) & ~7; - assert(realauthlen <= lenof(realauthdata)); - memset(realauthdata, 0, realauthlen); - memcpy(realauthdata, pr->disp->localauthdata, 8); - memcpy(realauthdata+8, buf, buflen); - t = time(NULL); - PUT_32BIT_MSB_FIRST(realauthdata+8+buflen, t); - des_encrypt_xdmauth(pr->disp->localauthdata+9, - (unsigned char *)realauthdata, - realauthlen); - sfree(buf); - } - /* implement other auth methods here if required */ - - PUT_16BIT(pr->firstpkt[0], pr->firstpkt + 6, authstrlen); - PUT_16BIT(pr->firstpkt[0], pr->firstpkt + 8, realauthlen); - - sk_write(s, (char *)pr->firstpkt, 12); - - if (authstrlen) { - sk_write(s, x11_authnames[pr->disp->localauthproto], - authstrlen); - sk_write(s, zeroes, 3 & (-authstrlen)); - } - if (realauthlen) { - sk_write(s, realauthdata, realauthlen); - sk_write(s, zeroes, 3 & (-realauthlen)); - } - } - pr->verified = 1; + if (!xconn->verified) { + const char *err; + struct X11FakeAuth *auth_matched = NULL; + unsigned long peer_ip; + int peer_port; + int protomajor, protominor; + void *greeting; + int greeting_len; + unsigned char *socketdata; + int socketdatalen; + char new_peer_addr[32]; + int new_peer_port; + + protomajor = GET_16BIT(xconn->firstpkt[0], xconn->firstpkt + 2); + protominor = GET_16BIT(xconn->firstpkt[0], xconn->firstpkt + 4); + + assert(!xconn->s); + + xconn->auth_protocol[xconn->auth_plen] = '\0'; /* ASCIZ */ + + peer_ip = 0; /* placate optimiser */ + if (x11_parse_ip(xconn->peer_addr, &peer_ip)) + peer_port = xconn->peer_port; + else + peer_port = -1; /* signal no peer address data available */ + + err = x11_verify(peer_ip, peer_port, + xconn->authtree, xconn->auth_protocol, + xconn->auth_data, xconn->auth_dlen, &auth_matched); + if (err) { + x11_send_init_error(xconn, err); + return 0; + } + assert(auth_matched); + + /* + * If this auth points to a connection-sharing downstream + * rather than an X display we know how to connect to + * directly, pass it off to the sharing module now. + */ + if (auth_matched->share_cs) { + sshfwd_x11_sharing_handover(xconn->c, auth_matched->share_cs, + auth_matched->share_chan, + xconn->peer_addr, xconn->peer_port, + xconn->firstpkt[0], + protomajor, protominor, data, len); + return 0; + } + + /* + * Now we know we're going to accept the connection, and what + * X display to connect to. Actually connect to it. + */ + sshfwd_x11_is_local(xconn->c); + xconn->disp = auth_matched->disp; + xconn->s = new_connection(sk_addr_dup(xconn->disp->addr), + xconn->disp->realhost, xconn->disp->port, + 0, 1, 0, 0, (Plug) xconn, + sshfwd_get_conf(xconn->c)); + if ((err = sk_socket_error(xconn->s)) != NULL) { + char *err_message = dupprintf("unable to connect to" + " forwarded X server: %s", err); + x11_send_init_error(xconn, err_message); + sfree(err_message); + return 0; + } + + /* + * Write a new connection header containing our replacement + * auth data. + */ + + socketdata = sk_getxdmdata(xconn->s, &socketdatalen); + if (socketdata && socketdatalen==6) { + sprintf(new_peer_addr, "%d.%d.%d.%d", socketdata[0], + socketdata[1], socketdata[2], socketdata[3]); + new_peer_port = GET_16BIT_MSB_FIRST(socketdata + 4); + } else { + strcpy(new_peer_addr, "0.0.0.0"); + new_peer_port = 0; + } + + greeting = x11_make_greeting(xconn->firstpkt[0], + protomajor, protominor, + xconn->disp->localauthproto, + xconn->disp->localauthdata, + xconn->disp->localauthdatalen, + new_peer_addr, new_peer_port, + &greeting_len); + + sk_write(xconn->s, greeting, greeting_len); + + smemclr(greeting, greeting_len); + sfree(greeting); + + /* + * Now we're done. + */ + xconn->verified = 1; } /* * After initialisation, just copy data simply. */ - return sk_write(s, data, len); + return sk_write(xconn->s, data, len); +} + +void x11_send_eof(struct X11Connection *xconn) +{ + if (xconn->s) { + sk_write_eof(xconn->s); + } else { + /* + * If EOF is received from the X client before we've got to + * the point of actually connecting to an X server, then we + * should send an EOF back to the client so that the + * forwarded channel will be terminated. + */ + if (xconn->c) + sshfwd_write_eof(xconn->c); + } +} + +/* + * Utility functions used by connection sharing to convert textual + * representations of an X11 auth protocol name + hex cookie into our + * usual integer protocol id and binary auth data. + */ +int x11_identify_auth_proto(const char *protoname) +{ + int protocol; + + for (protocol = 1; protocol < lenof(x11_authnames); protocol++) + if (!strcmp(protoname, x11_authnames[protocol])) + return protocol; + return -1; +} + +void *x11_dehexify(const char *hex, int *outlen) +{ + int len, i; + unsigned char *ret; + + len = strlen(hex) / 2; + ret = snewn(len, unsigned char); + + for (i = 0; i < len; i++) { + char bytestr[3]; + unsigned val = 0; + bytestr[0] = hex[2*i]; + bytestr[1] = hex[2*i+1]; + bytestr[2] = '\0'; + sscanf(bytestr, "%x", &val); + ret[i] = val; + } + + *outlen = len; + return ret; +} + +/* + * Construct an X11 greeting packet, including making up the right + * authorisation data. + */ +void *x11_make_greeting(int endian, int protomajor, int protominor, + int auth_proto, const void *auth_data, int auth_len, + const char *peer_addr, int peer_port, + int *outlen) +{ + unsigned char *greeting; + unsigned char realauthdata[64]; + const char *authname; + const unsigned char *authdata; + int authnamelen, authnamelen_pad; + int authdatalen, authdatalen_pad; + int greeting_len; + + authname = x11_authnames[auth_proto]; + authnamelen = strlen(authname); + authnamelen_pad = (authnamelen + 3) & ~3; + + if (auth_proto == X11_MIT) { + authdata = auth_data; + authdatalen = auth_len; + } else if (auth_proto == X11_XDM && auth_len == 16) { + time_t t; + unsigned long peer_ip = 0; + + x11_parse_ip(peer_addr, &peer_ip); + + authdata = realauthdata; + authdatalen = 24; + memset(realauthdata, 0, authdatalen); + memcpy(realauthdata, auth_data, 8); + PUT_32BIT_MSB_FIRST(realauthdata+8, peer_ip); + PUT_16BIT_MSB_FIRST(realauthdata+12, peer_port); + t = time(NULL); + PUT_32BIT_MSB_FIRST(realauthdata+14, t); + + des_encrypt_xdmauth((const unsigned char *)auth_data + 9, + realauthdata, authdatalen); + } else { + authdata = realauthdata; + authdatalen = 0; + } + + authdatalen_pad = (authdatalen + 3) & ~3; + greeting_len = 12 + authnamelen_pad + authdatalen_pad; + + greeting = snewn(greeting_len, unsigned char); + memset(greeting, 0, greeting_len); + greeting[0] = endian; + PUT_16BIT(endian, greeting+2, protomajor); + PUT_16BIT(endian, greeting+4, protominor); + PUT_16BIT(endian, greeting+6, authnamelen); + PUT_16BIT(endian, greeting+8, authdatalen); + memcpy(greeting+12, authname, authnamelen); + memcpy(greeting+12+authnamelen_pad, authdata, authdatalen); + + smemclr(realauthdata, sizeof(realauthdata)); + + *outlen = greeting_len; + return greeting; }