--- /dev/null
+# -*- sh -*-
+# Build script to construct a full distribution directory of PuTTY.
+
+module putty
+
+# Set up the arguments for the main make command.
+set Makever -DSVN_REV=$(revision)
+ifneq "$(!numeric $(revision))" "yes" set Makever $(Makever) -DMODIFIED
+ifneq "$(RELEASE)" "" set Makever $(Makever) -DRELEASE=$(RELEASE)
+ifneq "$(date)" "" set Makever $(Makever) -DSNAPSHOT=$(date)
+set Makeargs VER="$(Makever)"
+ifneq "$(XFLAGS)" "" set Makeargs $(Makeargs) XFLAGS="$(XFLAGS)"
+ifneq "$(MAKEARGS)" "" set Makeargs $(Makeargs) $(MAKEARGS)
+
+# Set up the version string for the docs build.
+set Docmakeargs VERSION="PuTTY revision $(revision)"
+ifneq "$(RELEASE)" "" set Docmakeargs VERSION="PuTTY release $(RELEASE)"
+ifneq "$(date)" "" set Docmakeargs VERSION="PuTTY development snapshot $(date)"
+
+# Set up the version string for the Unix source archive.
+set Unxver r$(revision)
+ifneq "$(RELEASE)" "" set Unxver $(RELEASE)
+ifneq "$(date)" "" set Unxver $(date)
+
+# Set up the various version strings for the installer.
+set Iversion r$(revision)
+set Iname PuTTY revision $(revision)
+set Ivertext Revision $(revision)
+set Irev $(revision)
+set Ifilename putty-$(Iversion)-installer.exe
+ifneq "$(RELEASE)" "" set Iversion $(RELEASE)
+ifneq "$(RELEASE)" "" set Iname PuTTY version $(RELEASE)
+ifneq "$(RELEASE)" "" set Ivertext Release $(RELEASE)
+ifneq "$(RELEASE)" "" set Irev 0
+ifneq "$(RELEASE)" "" set Ifilename putty-$(RELEASE)-installer.exe
+ifneq "$(date)" "" set Iversion $(date):r$(revision)
+ifneq "$(date)" "" set Iname PuTTY development snapshot $(date):r$(revision)
+ifneq "$(date)" "" set Ivertext Development snapshot $(date):r$(revision)
+ifneq "$(date)" "" set Ifilename putty-$(date)-installer.exe
+
+# Set up the version string for the installer.
+set Iversion r$(revision)
+ifneq "$(RELEASE)" "" set Iversion $(RELEASE)
+ifneq "$(date)" "" set Iversion $(date):r$(revision)
+
+in putty do ./mksrcarc.sh
+in putty do ./mkunxarc.sh $(Unxver)
+in putty do perl mkfiles.pl
+in putty/doc do make $(Docmakeargs) putty.hlp
+in putty/doc do make $(Docmakeargs) chm
+
+# Munge the installer script locally so that it reports the version
+# we're really building.
+in putty/windows do perl -i~ -pe 'BEGIN{$$a=shift@ARGV;}s/^(AppVerName=).*$$/$$1$$a/' '$(Iname)' putty.iss
+in putty/windows do perl -i~ -pe 'BEGIN{$$a=shift@ARGV;}s/^(VersionInfoTextVersion=).*$$/$$1$$a/' '$(Ivertext)' putty.iss
+in putty/windows do perl -i~ -pe 'BEGIN{$$a=shift@ARGV;}s/^(AppVersion=).*$$/$$1$$a/' '$(Iversion)' putty.iss
+in putty/windows do perl -i~ -pe 'BEGIN{$$a=shift@ARGV;$$a=~s/M//;}s/^(VersionInfoVersion=\d+\.\d+\.)\d+(\.\d+)\r?$$/$$1$$a$$2/' '$(Irev)' putty.iss
+
+# Windowsify LICENCE, since it's going in the Windows installer.
+in putty do perl -i~ -pe 'y/\015//d;s/$$/\015/' LICENCE
+
+delegate windows
+ # FIXME: Cygwin alternative?
+ in putty/windows do cmd /c vcvars32 \& nmake -f Makefile.vc $(Makeargs)
+ # Ignore exit code from hhc, in favour of seeing whether the .chm
+ # file was created. (Yuck; but hhc appears to return non-zero
+ # exit codes on whim.)
+ in putty/doc do hhc putty.hhp; test -f putty.chm
+ in putty/windows do iscc putty.iss
+ return putty/windows/*.exe
+ return putty/windows/*.map
+ return putty/doc/putty.chm
+ return putty/windows/Output/setup.exe
+enddelegate
+in putty/doc do make mostlyclean
+in putty/doc do make $(Docmakeargs)
+in putty/windows do zip -k -j putty.zip `ls *.exe | grep -v puttytel` ../doc/putty.chm ../doc/putty.hlp ../doc/putty.cnt
+in putty/doc do zip puttydoc.zip *.html
+
+# Deliver the actual PuTTY release directory into a subdir `putty'.
+deliver putty/windows/*.exe putty/x86/$@
+deliver putty/windows/putty.zip putty/x86/$@
+deliver putty/windows/Output/setup.exe putty/x86/$(Ifilename)
+deliver putty/doc/puttydoc.zip putty/$@
+deliver putty/doc/putty.chm putty/$@
+deliver putty/doc/putty.hlp putty/$@
+deliver putty/doc/putty.cnt putty/$@
+deliver putty/doc/puttydoc.txt putty/$@
+deliver putty/doc/*.html putty/htmldoc/$@
+deliver putty/putty-src.zip putty/$@
+deliver putty/*.tar.gz putty/$@
+
+# Deliver the map files alongside the `proper' release deliverables.
+deliver putty/windows/*.map maps-x86/$@
+
+# Deliver sign.sh, so that whoever has just built PuTTY (the
+# snapshot scripts or me, depending) can conveniently sign it with
+# whatever key they want.
+deliver putty/sign.sh $@
+
+# Create files of cryptographic checksums, which will be signed along
+# with the files they verify. We've provided MD5 checksums for a
+# while, but now MD5 is looking iffy, we're expanding our selection.
+#
+# Creating these files is most easily done in the destination
+# directory, where all the files we're delivering are already in their
+# final relative layout.
+in-dest putty do a=`\find * -type f -print`; md5sum $$a > md5sums && sha1sum $$a > sha1sums && sha256sum $$a > sha256sums && sha512sum $$a > sha512sums
+
+# And construct .htaccess files. One in the top-level directory,
+# setting the MIME types for Windows help files and providing an
+# appropriate link to the source archive:
+in-dest putty do echo "AddType application/octet-stream .chm" >> .htaccess
+in-dest putty do echo "AddType application/octet-stream .hlp" >> .htaccess
+in-dest putty do echo "AddType application/octet-stream .cnt" >> .htaccess
+in-dest putty do set -- putty*.tar.gz; for k in '' .DSA .RSA; do echo RedirectMatch temp '(.*/)'putty.tar.gz$$k\$$ '$$1'"$$1$$k" >> .htaccess; done
+# And one in the x86 directory, providing a link for the installer.
+in-dest putty/x86 do set -- putty*installer.exe; for k in '' .DSA .RSA; do echo RedirectMatch temp '(.*/)'putty-installer.exe$$k\$$ '$$1'"$$1$$k" >> .htaccess; done
--- /dev/null
+Checklists for PuTTY administrative procedures
+==============================================
+
+Locations of the licence
+------------------------
+
+The PuTTY copyright notice and licence are stored in quite a few
+places. At the start of a new year, the copyright year needs
+updating in all of them; and when someone sends a massive patch,
+their name needs adding in all of them too.
+
+The LICENCE file in the main source distribution:
+
+ - putty/LICENCE
+
+The resource files:
+
+ - putty/windows/pageant.rc
+ + the copyright date appears twice, once in the About box and
+ once in the Licence box. Don't forget to change both!
+ - putty/windows/puttygen.rc
+ + the copyright date appears twice, once in the About box and
+ once in the Licence box. Don't forget to change both!
+ - putty/windows/win_res.rc2
+ + the copyright date appears twice, once in the About box and
+ once in the Licence box. Don't forget to change both!
+ - putty/windows/version.rc2
+ + the copyright date appears once only.
+ - putty/unix/gtkdlg.c
+ + the copyright date appears twice, once in the About box and
+ once in the Licence box. Don't forget to change both!
+
+The documentation (both the preamble blurb and the licence appendix):
+
+ - putty/doc/blurb.but
+ - putty/doc/licence.but
+
+The website:
+
+ - putty-website/licence.html
+
+Before tagging a release
+------------------------
+
+ - First of all, go through the source (including the documentation),
+ and the website, and review anything tagged with a comment
+ containing the word XXX-REVIEW-BEFORE-RELEASE.
+ (Any such comments should state clearly what needs to be done.)
+
+ - Also, do some testing of the Windows version with Minefield, and
+ of the Unix version with valgrind or efence or both. In
+ particular, any headline features for the release should get a
+ workout with memory checking enabled!
+
+For a long time we got away with never checking the current version
+number in at all - all version numbers were passed into the build
+system on the compiler command line, and the _only_ place version
+numbers showed up in the source files was in the tag information.
+
+Unfortunately, those halcyon days are gone, and we do need the
+version number checked in in a couple of places. These must be updated
+_before_ tagging a new release.
+
+The file used to generate the Unix snapshot version numbers (which
+are <previousrelease>-<date> so that the Debian versioning system
+orders them correctly with respect to releases):
+
+ - putty/LATEST.VER
+
+The Windows installer script (_four_ times, on consecutive lines):
+
+ - putty/windows/putty.iss
+
+The Windows resource file (used to generate the binary bit of the
+VERSIONINFO resources -- the strings are supplied by the usual means):
+
+ - putty/windows/version.rc2 (BASE_VERSION; NB, _comma_-separated)
+
+It might also be worth going through the documentation looking for
+version numbers - we have a couple of transcripts showing the help
+text from the command-line tools, and it would be nice to ensure the
+whole transcripts (certainly including the version numbers) are up
+to date. Sometimes these are marked in between releases as `0.XX', so
+it's worth grepping for that too.
+
+ - putty/doc/pscp.but
+ - putty/doc/plink.but
+ - putty/doc/psftp.but (in case it ever acquires a similar thing)
+
+The actual release procedure
+----------------------------
+
+This is the procedure I (SGT) currently follow (or _should_ follow
+:-) when actually making a release, once I'm happy with the position
+of the tag.
+
+ - Double-check that we have removed anything tagged with a comment
+ containing the words XXX-REMOVE-BEFORE-RELEASE or
+ XXX-REVIEW-BEFORE-RELEASE.
+
+ - Write a release announcement (basically a summary of the changes
+ since the last release). Squirrel it away in
+ ixion:src/putty/local/announce-<ver> in case it's needed again
+ within days of the release going out.
+
+ - Build the release: `bob putty-0.XX RELEASE=0.XX'. This should
+ generate a basically valid release directory as
+ `build.out/putty', and provide link maps and sign.sh alongside
+ that in build.out.
+
+ - Do a bit of checking that the release binaries basically work,
+ report their version numbers accurately, and so on. Test the
+ installer and the Unix source tarball.
+
+ - Save the link maps. Currently I keep these on ixion, in
+ src/putty/local/maps-<version>.
+
+ - Sign the release: in the `build.out' directory, type `./sign.sh
+ putty Releases', and enter the passphrases a lot of times.
+
+ - Now the whole release directory should be present and correct.
+ Upload it to ixion:www/putty/<ver>.
+
+ - Do final checks on the release directory:
+ + verify all the signatures:
+ for i in `find . -name '*.*SA'`; do case $i in *md5sums*) gpg --verify $i;; *) gpg --verify $i ${i%%.?SA};; esac; done
+ + check the md5sums:
+ md5sum -c md5sums
+
+ - Having double-checked the release, copy it from ixion to
+ chiark:ftp/putty-<ver> and to the:www/putty/<ver>.
+
+ - Check the permissions! Actually try downloading from the, to make
+ sure it really works.
+
+ - Update the HTTP redirects.
+ + Update the one at the:www/putty/htaccess which points the
+ virtual subdir `latest' at the actual latest release dir. TEST
+ THIS ONE - it's quite important.
+ + ixion:www/putty/.htaccess has an individual redirect for each
+ version number. Add a new one.
+
+ - Update the FTP symlink (chiark:ftp/putty-latest -> putty-<ver>).
+
+ - Update web site.
+ + Adjust front page (`the latest version is <ver>').
+ + Adjust Download page similarly.
+ + Adjust filenames of installer and Unix tarball on links in
+ Download page.
+ + Adjust header text on Changelog page. (That includes changing
+ `are new' in previous version to `were new'!)
+
+ - Update the wishlist. This can be done without touching individual
+ items by editing the @releases array in control/bugs2html.
+
+ - Check the Docs page links correctly to the release docs. (It
+ should do this automatically, owing to the `latest' HTTP
+ redirect.)
+
+ - Check that the web server attaches the right content type to .HLP
+ and .CNT files.
+
+ - Run webupdate, so that all the changes on ixion propagate to
+ chiark. Important to do this _before_ announcing that the release
+ is available.
+
+ - After running webupdate, run update-rsync on chiark and verify
+ that the rsync mirror package correctly identifies the new
+ version.
+
+ - Announce the release!
+ + Mail the announcement to putty-announce.
+ * Set a Reply-To on the mail so that people don't keep
+ replying to my personal address.
+ + Post it to comp.security.ssh.
+ + Mention it in <TDHTT> on mono.
+
+ - Relax (slightly).
+
+After the release
+-----------------
+
+The following want doing some time soon after a release has been made:
+
+ - If the release was made from a branch, make sure the version number
+ on the _trunk_ is up to date in all the locations listed above, so
+ that (e.g.) Unix snapshots come out right.
--- /dev/null
+PuTTY is copyright 1997-2011 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.
+
+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,
+and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE
+FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--- /dev/null
+This is the README for the source archive of PuTTY, a free Win32
+and Unix Telnet and SSH client.
+
+If you want to rebuild PuTTY from source, we provide a variety of
+Makefiles and equivalents. (If you have fetched the source from
+Subversion, you'll have to generate the Makefiles yourself -- see
+below.)
+
+There are various compile-time directives that you can use to
+disable or modify certain features; it may be necessary to do this
+in some environments. They are documented in `Recipe', and in
+comments in many of the generated Makefiles.
+
+For building on Windows:
+
+ - windows/Makefile.vc is for command-line builds on MS Visual C++
+ systems. Change into the `windows' subdirectory and type `nmake
+ -f Makefile.vc' to build all the PuTTY binaries.
+
+ Last time we checked, PuTTY built with vanilla VC7, or VC6 with
+ an up-to-date Platform SDK. (It might still be possible to build
+ with vanilla VC6, but you'll certainly have to remove some
+ functionality with directives such as NO_IPV6.)
+
+ (We've also had reports of success building with the
+ OpenWatcom compiler -- www.openwatcom.org -- using Makefile.vc
+ with `wmake -ms -f makefile.vc' and NO_MULTIMON, although we
+ haven't tried this ourselves. Version 1.3 is reported to work.)
+
+ - Inside the windows/MSVC subdirectory are MS Visual Studio project
+ files for doing GUI-based builds of the various PuTTY utilities.
+ These have been tested on Visual Studio 6.
+
+ You should be able to build each PuTTY utility by loading the
+ corresponding .dsp file in Visual Studio. For example,
+ MSVC/putty/putty.dsp builds PuTTY itself, MSVC/plink/plink.dsp
+ builds Plink, and so on.
+
+ - 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
+ `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
+ excluded from the Cygwin build, since at the time of writing Cygwin
+ doesn't include the necessary headers.
+
+ - windows/Makefile.lcc is for lcc-win32. Type `make -f
+ Makefile.lcc' while in the `windows' subdirectory. (You will
+ probably need to specify COMPAT=-DNO_MULTIMON.)
+
+ - Inside the windows/DEVCPP subdirectory are Dev-C++ project
+ files for doing GUI-based builds of the various PuTTY utilities.
+
+The PuTTY team actively use Makefile.vc (with VC7) and Makefile.cyg
+(with mingw32), so we'll probably notice problems with those
+toolchains fairly quickly. Please report any problems with the other
+toolchains mentioned above.
+
+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'.
+
+ 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.
+
+ - unix/Makefile.gtk and unix/Makefile.ux are for non-autoconfigured
+ builds. These makefiles expect you to change into the `unix'
+ subdirectory, then run `make -f Makefile.gtk' or `make -f
+ 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.
+
+ - 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.
+
+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.
+
+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.
+
+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
+found at <http://www.chiark.greenend.org.uk/~sgtatham/halibut/>.
+
+The PuTTY home web site is
+
+ http://www.chiark.greenend.org.uk/~sgtatham/putty/
+
+If you want to send bug reports or feature requests, please read the
+Feedback section of the web site before doing so. Sending one-line
+reports saying `it doesn't work' will waste your time as much as
+ours.
+
+See the file LICENCE for the licence conditions.
--- /dev/null
+# -*- makefile -*-
+#
+# This file describes which PuTTY programs are made up from which
+# object and resource files. It is processed into the various
+# Makefiles by means of a Perl script. Makefile changes should
+# really be made by editing this file and/or the Perl script, not
+# by editing the actual Makefiles.
+
+# ------------------------------------------------------------
+# Top-level configuration.
+
+# Overall project name.
+!name putty
+# Locations and types of output Makefiles.
+!makefile vc windows/Makefile.vc
+!makefile vcproj windows/MSVC
+!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 osx macosx/Makefile
+!makefile devcppproj windows/DEVCPP
+# Source directories.
+!srcdir charset/
+!srcdir windows/
+!srcdir unix/
+!srcdir macosx/
+
+# Help text added to the top of each Makefile, with /D converted
+# into -D as appropriate for the particular Makefile.
+
+!begin help
+#
+# Extra options you can set:
+#
+# - VER="/DSNAPSHOT=1999-01-25 /DSVN_REV=1234"
+# Generates executables whose About box report them as being a
+# development snapshot. SVN_REV is a Subversion revision number.
+#
+# - VER=/DRELEASE=0.43
+# Generates executables whose About box report them as being a
+# release version.
+#
+# - COMPAT=/DAUTO_WINSOCK (Windows only)
+# Causes PuTTY to assume that <windows.h> includes its own WinSock
+# header file, so that it won't try to include <winsock.h>.
+#
+# - COMPAT=/DWINSOCK_TWO (Windows only)
+# Causes the PuTTY utilities to include <winsock2.h> instead of
+# <winsock.h>, except Plink which _needs_ WinSock 2 so it already
+# does this.
+#
+# - COMPAT=/DNO_SECURITY (Windows only)
+# Disables Pageant's use of <aclapi.h>, which is not available
+# with some development environments (such as older versions of
+# the Cygwin/mingw GNU toolchain). This means that Pageant
+# won't care about the local user ID of processes accessing it; a
+# version of Pageant built with this option will therefore refuse
+# to run under NT-series OSes on security grounds (although it
+# will run fine on Win95-series OSes where there is no access
+# control anyway).
+#
+# - COMPAT=/DNO_MULTIMON (Windows only)
+# Disables PuTTY's use of <multimon.h>, which is not available
+# with some development environments. This means that PuTTY's
+# full-screen mode (configurable to work on Alt-Enter) will
+# not behave usefully in a multi-monitor environment.
+#
+# Note that this definition is always enabled in the Cygwin
+# build, since at the time of writing this <multimon.h> is
+# known not to be available in Cygwin.
+#
+# - COMPAT=/DNO_HTMLHELP (Windows only)
+# Disables PuTTY's use of <htmlhelp.h>, which is not available
+# with some development environments. The resulting binary
+# will only look for an old-style WinHelp file (.HLP/.CNT), and
+# will ignore any .CHM file.
+#
+# Note that this definition is always enabled in the Cygwin
+# build, since at the time of writing this <htmlhelp.h> is
+# known not to be available in Cygwin (although you can use
+# the htmlhelp.h supplied with HTML Help Workshop).
+#
+# - RCFL=/DNO_MANIFESTS (Windows only)
+# Disables inclusion of XML application manifests in the PuTTY
+# binaries. This may be necessary to build for 64-bit Windows;
+# the manifests are only included to use the XP GUI style on
+# Windows XP, and the architecture tags are a lie on 64-bit.
+#
+# - COMPAT=/DNO_IPV6
+# Disables PuTTY's ability to make IPv6 connections, enabling
+# it to compile under development environments which do not
+# support IPv6 in their header files.
+#
+# - COMPAT=/DNO_GSSAPI
+# Disables PuTTY's ability to use GSSAPI functions for
+# authentication and key exchange.
+#
+# - COMPAT=/DSTATIC_GSSAPI
+# Causes PuTTY to try to link statically against the GSSAPI
+# library instead of the default of doing it at run time.
+#
+# - COMPAT=/DMSVC4 (Windows only)
+# - RCFL=/DMSVC4
+# Makes a couple of minor changes so that PuTTY compiles using
+# MSVC 4. You will also need /DNO_SECURITY and /DNO_MULTIMON.
+#
+# - RCFL=/DASCIICTLS (Windows only)
+# 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.
+#
+# - 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.
+#
+# - XFLAGS=/DDEBUG
+# Causes PuTTY to enable internal debugging.
+#
+# - XFLAGS=/DMALLOC_LOG
+# Causes PuTTY to emit a file called putty_mem.log, logging every
+# memory allocation and free, so you can track memory leaks.
+#
+# - XFLAGS=/DMINEFIELD (Windows only)
+# Causes PuTTY to use a custom memory allocator, similar in
+# concept to Electric Fence, in place of regular malloc(). Wastes
+# huge amounts of RAM, but should cause heap-corruption bugs to
+# show up as GPFs at the point of failure rather than appearing
+# later on as second-level damage.
+#
+!end
+
+# ------------------------------------------------------------
+# Additional text added verbatim to each individual Makefile.
+
+# Hack to force version.o to be rebuilt always.
+!begin vc
+version.obj: *.c *.h *.rc
+ cl $(VER) $(CFLAGS) /c ..\version.c
+!end
+!specialobj vc version
+!begin cygwin
+version.o: FORCE
+ $(CC) $(COMPAT) $(XFLAGS) $(CFLAGS) $(VER) -c ../version.c
+!end
+!specialobj cygwin version
+!begin borland
+version.obj: FORCE
+ bcc32 $(VER) $(CFLAGS) /c ..\version.c
+!end
+!specialobj borland version
+!begin lcc
+version.obj: FORCE
+ lcc $(VER) $(CFLAGS) /c ..\version.c
+!end
+!specialobj lcc version
+# For Unix, we also need the gross MD5 hack that causes automatic
+# version number selection in release source archives.
+!begin gtk
+version.o: FORCE
+ if test -z "$(VER)" && (cd ..; md5sum -c manifest); then \
+ $(CC) $(COMPAT) $(XFLAGS) $(CFLAGS) `cat ../version.def` -c ../version.c; \
+ else \
+ $(CC) $(COMPAT) $(XFLAGS) $(CFLAGS) $(VER) -c ../version.c; \
+ fi
+!end
+!specialobj gtk version
+
+# 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
+# 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.
+RCFLAGS = $(RCFLAGS) $(VER)
+!end
+!begin lcc vars
+RCFLAGS += $(VER)
+!end
+!forceobj putty.res
+!forceobj puttytel.res
+!forceobj plink.res
+!forceobj pscp.res
+!forceobj psftp.res
+!forceobj pageant.res
+!forceobj puttygen.res
+
+# `make install' target for Unix.
+!begin gtk
+install:
+ mkdir -p $(DESTDIR)$(bindir) $(DESTDIR)$(man1dir)
+ $(INSTALL_PROGRAM) -m 755 plink $(DESTDIR)$(bindir)/plink
+ $(INSTALL_PROGRAM) -m 755 pscp $(DESTDIR)$(bindir)/pscp
+ $(INSTALL_PROGRAM) -m 755 psftp $(DESTDIR)$(bindir)/psftp
+ $(INSTALL_PROGRAM) -m 755 pterm $(DESTDIR)$(bindir)/pterm
+ if test -n "$(UTMP_GROUP)"; then \
+ chgrp $(UTMP_GROUP) $(DESTDIR)$(bindir)/pterm && \
+ chmod 2755 $(DESTDIR)$(bindir)/pterm; \
+ elif test -n "$(UTMP_USER)"; then \
+ chown $(UTMP_USER) $(DESTDIR)$(bindir)/pterm && \
+ chmod 4755 $(DESTDIR)$(bindir)/pterm; \
+ fi
+ $(INSTALL_PROGRAM) -m 755 putty $(DESTDIR)$(bindir)/putty
+ $(INSTALL_PROGRAM) -m 755 puttygen $(DESTDIR)$(bindir)/puttygen
+ $(INSTALL_PROGRAM) -m 755 puttytel $(DESTDIR)$(bindir)/puttytel
+ $(INSTALL_DATA) -m 644 ../doc/plink.1 $(DESTDIR)$(man1dir)/plink.1
+ $(INSTALL_DATA) -m 644 ../doc/pscp.1 $(DESTDIR)$(man1dir)/pscp.1
+ $(INSTALL_DATA) -m 644 ../doc/psftp.1 $(DESTDIR)$(man1dir)/psftp.1
+ $(INSTALL_DATA) -m 644 ../doc/pterm.1 $(DESTDIR)$(man1dir)/pterm.1
+ $(INSTALL_DATA) -m 644 ../doc/putty.1 $(DESTDIR)$(man1dir)/putty.1
+ $(INSTALL_DATA) -m 644 ../doc/puttygen.1 $(DESTDIR)$(man1dir)/puttygen.1
+ $(INSTALL_DATA) -m 644 ../doc/puttytel.1 $(DESTDIR)$(man1dir)/puttytel.1
+
+install-strip:
+ $(MAKE) install INSTALL_PROGRAM="$(INSTALL_PROGRAM) -s"
+!end
+!begin osx vars
+CFLAGS += -DMACOSX
+!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().
+# WINVER etc perform a similar function for FlashWindowEx().
+CFLAGS += -D_WIN32_IE=0x0500
+CFLAGS += -DWINVER=0x0500 -D_WIN32_WINDOWS=0x0410 -D_WIN32_WINNT=0x0500
+!end
+
+# ------------------------------------------------------------
+# Definitions of object groups. A group name, followed by an =,
+# followed by any number of objects or other already-defined group
+# 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
+
+# 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
+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
+
+# 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
+
+# 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
+WINMISC = MISC winstore winnet winhandl cmdline windefs winmisc winproxy
+ + wintime
+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
+
+# Standard libraries.
+LIBS = advapi32.lib user32.lib gdi32.lib comctl32.lib comdlg32.lib
+ + shell32.lib winmm.lib imm32.lib winspool.lib ole32.lib
+
+# Network backend sets. This also brings in the relevant attachment
+# to proxy.c depending on whether we're crypto-avoidant or not.
+BE_ALL = be_all cproxy
+BE_NOSSH = be_nossh nocproxy
+BE_SSH = be_none cproxy
+BE_NONE = be_none nocproxy
+# More backend sets, with the additional Windows serial-port module.
+W_BE_ALL = be_all_s winser cproxy
+W_BE_NOSSH = be_nos_s winser nocproxy
+# And with the Unix serial-port module.
+U_BE_ALL = be_all_s uxser cproxy
+U_BE_NOSSH = be_nos_s uxser nocproxy
+
+# ------------------------------------------------------------
+# Definitions of actual programs. The program name, followed by a
+# colon, followed by a list of objects. Also in the list may be the
+# keywords [G] for Windows GUI app, [C] for Console app, [X] for
+# 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
+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
+
+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
+
+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
+ + uxsignal CHARSET uxputty NONSSH UXSSH UXMISC ux_x11 xpmputty
+ + xpmpucfg
+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
+
+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
+
+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
--- /dev/null
+/*
+ * Linking module for PuTTY proper: list the available backends
+ * including ssh.
+ */
+
+#include <stdio.h>
+#include "putty.h"
+
+/*
+ * This appname is not strictly in the right place, since Plink
+ * also uses this module. However, Plink doesn't currently use any
+ * of the dialog-box sorts of things that make use of appname, so
+ * it shouldn't do any harm here. I'm trying to avoid having to
+ * have tiny little source modules containing nothing but
+ * declarations of appname, for as long as I can...
+ */
+const char *const appname = "PuTTY";
+
+#ifdef TELNET_DEFAULT
+const int be_default_protocol = PROT_TELNET;
+#else
+const int be_default_protocol = PROT_SSH;
+#endif
+
+Backend *backends[] = {
+ &ssh_backend,
+ &telnet_backend,
+ &rlogin_backend,
+ &raw_backend,
+ NULL
+};
--- /dev/null
+/*
+ * Linking module for PuTTY proper: list the available backends
+ * including ssh, plus the serial backend.
+ */
+
+#include <stdio.h>
+#include "putty.h"
+
+/*
+ * This appname is not strictly in the right place, since Plink
+ * also uses this module. However, Plink doesn't currently use any
+ * of the dialog-box sorts of things that make use of appname, so
+ * it shouldn't do any harm here. I'm trying to avoid having to
+ * have tiny little source modules containing nothing but
+ * declarations of appname, for as long as I can...
+ */
+const char *const appname = "PuTTY";
+
+#ifdef TELNET_DEFAULT
+const int be_default_protocol = PROT_TELNET;
+#else
+const int be_default_protocol = PROT_SSH;
+#endif
+
+Backend *backends[] = {
+ &ssh_backend,
+ &telnet_backend,
+ &rlogin_backend,
+ &raw_backend,
+ &serial_backend,
+ NULL
+};
--- /dev/null
+/*
+ * Linking module for programs that do not support selection of backend
+ * (such as pscp or pterm).
+ */
+
+#include <stdio.h>
+#include "putty.h"
+
+Backend *backends[] = {
+ NULL
+};
--- /dev/null
+/*
+ * Linking module for PuTTYtel: list the available backends not
+ * including ssh.
+ */
+
+#include <stdio.h>
+#include "putty.h"
+
+const int be_default_protocol = PROT_TELNET;
+
+const char *const appname = "PuTTYtel";
+
+Backend *backends[] = {
+ &telnet_backend,
+ &rlogin_backend,
+ &raw_backend,
+ &serial_backend,
+ NULL
+};
+
+/*
+ * Stub implementations of functions not used in non-ssh versions.
+ */
+void random_save_seed(void)
+{
+}
+
+void random_destroy_seed(void)
+{
+}
+
+void noise_ultralight(unsigned long data)
+{
+}
--- /dev/null
+/*
+ * Linking module for PuTTYtel: list the available backends not
+ * including ssh.
+ */
+
+#include <stdio.h>
+#include "putty.h"
+
+const int be_default_protocol = PROT_TELNET;
+
+const char *const appname = "PuTTYtel";
+
+Backend *backends[] = {
+ &telnet_backend,
+ &rlogin_backend,
+ &raw_backend,
+ NULL
+};
+
+/*
+ * Stub implementations of functions not used in non-ssh versions.
+ */
+void random_save_seed(void)
+{
+}
+
+void random_destroy_seed(void)
+{
+}
+
+void noise_ultralight(unsigned long data)
+{
+}
--- /dev/null
+This subdirectory contains a general character-set conversion
+library, used in the Unix port of PuTTY, and available for use in
+other ports if it should happen to be useful.
+
+This is a variant of a library that's currently used in some other
+programs such as Timber and Halibut. At some future date, we would
+like to merge the two libraries, so that all programs use the same
+libcharset.
+
+It is therefore a _strong_ design goal that this library should remain
+perfectly general, and not tied to particulars of PuTTY. It must not
+reference any code outside its own subdirectory; it should not have
+PuTTY-specific helper routines added to it unless they can be
+documented in a general manner which might make them useful in other
+circumstances as well.
--- /dev/null
+/*
+ * charset.h - header file for general character set conversion
+ * routines.
+ */
+
+#ifndef charset_charset_h
+#define charset_charset_h
+
+#include <stddef.h>
+
+/*
+ * Enumeration that lists all the multibyte or single-byte
+ * character sets known to this library.
+ */
+typedef enum {
+ CS_NONE, /* used for reporting errors, etc */
+ CS_ISO8859_1,
+ CS_ISO8859_1_X11, /* X font encoding with VT100 glyphs */
+ CS_ISO8859_2,
+ CS_ISO8859_3,
+ CS_ISO8859_4,
+ CS_ISO8859_5,
+ CS_ISO8859_6,
+ CS_ISO8859_7,
+ CS_ISO8859_8,
+ CS_ISO8859_9,
+ CS_ISO8859_10,
+ CS_ISO8859_11,
+ CS_ISO8859_13,
+ CS_ISO8859_14,
+ CS_ISO8859_15,
+ CS_ISO8859_16,
+ CS_CP437,
+ CS_CP850,
+ CS_CP866,
+ CS_CP1250,
+ CS_CP1251,
+ CS_CP1252,
+ CS_CP1253,
+ CS_CP1254,
+ CS_CP1255,
+ CS_CP1256,
+ CS_CP1257,
+ CS_CP1258,
+ CS_KOI8_R,
+ CS_KOI8_U,
+ CS_MAC_ROMAN,
+ CS_MAC_TURKISH,
+ CS_MAC_CROATIAN,
+ CS_MAC_ICELAND,
+ CS_MAC_ROMANIAN,
+ CS_MAC_GREEK,
+ CS_MAC_CYRILLIC,
+ CS_MAC_THAI,
+ CS_MAC_CENTEURO,
+ CS_MAC_SYMBOL,
+ CS_MAC_DINGBATS,
+ CS_MAC_ROMAN_OLD,
+ CS_MAC_CROATIAN_OLD,
+ CS_MAC_ICELAND_OLD,
+ CS_MAC_ROMANIAN_OLD,
+ CS_MAC_GREEK_OLD,
+ CS_MAC_CYRILLIC_OLD,
+ CS_MAC_UKRAINE,
+ CS_MAC_VT100,
+ CS_MAC_VT100_OLD,
+ CS_VISCII,
+ CS_HP_ROMAN8,
+ CS_DEC_MCS,
+ CS_UTF8
+} charset_t;
+
+typedef struct {
+ unsigned long s0;
+} charset_state;
+
+/*
+ * Routine to convert a MB/SB character set to Unicode.
+ *
+ * This routine accepts some number of bytes, updates a state
+ * variable, and outputs some number of Unicode characters. There
+ * are no guarantees. You can't even guarantee that at most one
+ * Unicode character will be output per byte you feed in; for
+ * example, suppose you're reading UTF-8, you've seen E1 80, and
+ * then you suddenly see FE. Now you need to output _two_ error
+ * characters - one for the incomplete sequence E1 80, and one for
+ * the completely invalid UTF-8 byte FE.
+ *
+ * Returns the number of wide characters output; will never output
+ * more than the size of the buffer (as specified on input).
+ * Advances the `input' pointer and decrements `inlen', to indicate
+ * how far along the input string it got.
+ *
+ * The sequence of `errlen' wide characters pointed to by `errstr'
+ * will be used to indicate a conversion error. If `errstr' is
+ * 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, charset_state *state,
+ const wchar_t *errstr, int errlen);
+
+/*
+ * Routine to convert Unicode to an MB/SB character set.
+ *
+ * This routine accepts some number of Unicode characters, updates
+ * a state variable, and outputs some number of bytes.
+ *
+ * Returns the number of bytes characters output; will never output
+ * more than the size of the buffer (as specified on input), and
+ * will never output a partial MB character. Advances the `input'
+ * pointer and decrements `inlen', to indicate how far along the
+ * input string it got.
+ *
+ * The sequence of `errlen' characters pointed to by `errstr' will
+ * be used to indicate a conversion error. If `errstr' is NULL,
+ * `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, charset_state *state,
+ const char *errstr, int errlen);
+
+/*
+ * Convert X11 encoding names to and from our charset identifiers.
+ */
+const char *charset_to_xenc(int charset);
+int charset_from_xenc(const char *name);
+
+/*
+ * Convert MIME encoding names to and from our charset identifiers.
+ */
+const char *charset_to_mimeenc(int charset);
+int charset_from_mimeenc(const char *name);
+
+/*
+ * Convert our own encoding names to and from our charset
+ * identifiers.
+ */
+const char *charset_to_localenc(int charset);
+int charset_from_localenc(const char *name);
+int charset_localenc_nth(int n);
+
+/*
+ * Convert Mac OS script/region/font to our charset identifiers.
+ */
+int charset_from_macenc(int script, int region, int sysvers,
+ const char *fontname);
+
+#endif /* charset_charset_h */
--- /dev/null
+/*
+ * enum.c - enumerate all charsets defined by the library.
+ *
+ * This file maintains a list of every other source file which
+ * contains ENUM_CHARSET definitions. It #includes each one with
+ * ENUM_CHARSETS defined, which causes those source files to do
+ * nothing at all except call the ENUM_CHARSET macro on each
+ * charset they define.
+ *
+ * This file in turn is included from various other places, with
+ * the ENUM_CHARSET macro defined to various different things. This
+ * allows us to have multiple implementations of the master charset
+ * lookup table (a static one and a dynamic one).
+ */
+
+#define ENUM_CHARSETS
+#include "sbcsdat.c"
+#include "utf8.c"
+#undef ENUM_CHARSETS
--- /dev/null
+/*
+ * fromucs.c - convert Unicode to other character sets.
+ */
+
+#include "charset.h"
+#include "internal.h"
+
+struct charset_emit_param {
+ char *output;
+ int outlen;
+ const char *errstr;
+ int errlen;
+ int stopped;
+};
+
+static void charset_emit(void *ctx, long int output)
+{
+ struct charset_emit_param *param = (struct charset_emit_param *)ctx;
+ char outval;
+ char const *p;
+ int outlen;
+
+ if (output == ERROR) {
+ p = param->errstr;
+ outlen = param->errlen;
+ } else {
+ outval = output;
+ p = &outval;
+ outlen = 1;
+ }
+
+ if (param->outlen >= outlen) {
+ while (outlen > 0) {
+ *param->output++ = *p++;
+ param->outlen--;
+ outlen--;
+ }
+ } else {
+ param->stopped = 1;
+ }
+}
+
+int charset_from_unicode(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;
+ struct charset_emit_param param;
+
+ param.output = output;
+ param.outlen = outlen;
+ param.stopped = 0;
+
+ /*
+ * charset_emit will expect a valid errstr.
+ */
+ if (!errstr) {
+ /* *shrug* this is good enough, and consistent across all SBCS... */
+ param.errstr = ".";
+ param.errlen = 1;
+ }
+ param.errstr = errstr;
+ param.errlen = errlen;
+
+ if (!state) {
+ localstate.s0 = 0;
+ } else {
+ localstate = *state; /* structure copy */
+ }
+ state = &localstate;
+
+ while (*inlen > 0) {
+ int lenbefore = param.output - output;
+ spec->write(spec, **input, &localstate, charset_emit, ¶m);
+ if (param.stopped) {
+ /*
+ * The emit function has _tried_ to output some
+ * characters, but ran up against the end of the
+ * buffer. Leave immediately, and return what happened
+ * _before_ attempting to process this character.
+ */
+ return lenbefore;
+ }
+ if (state)
+ *state = localstate; /* structure copy */
+ (*input)++;
+ (*inlen)--;
+ }
+ return param.output - output;
+}
--- /dev/null
+/*
+ * internal.h - internal header stuff for the charset library.
+ */
+
+#ifndef charset_internal_h
+#define charset_internal_h
+
+/* This invariably comes in handy */
+#define lenof(x) ( sizeof((x)) / sizeof(*(x)) )
+
+/* This is an invalid Unicode value used to indicate an error. */
+#define ERROR 0xFFFFL /* Unicode value representing error */
+
+typedef struct charset_spec charset_spec;
+typedef struct sbcs_data sbcs_data;
+
+struct charset_spec {
+ int charset; /* numeric identifier */
+
+ /*
+ * A function to read the character set and output Unicode
+ * characters. The `emit' function expects to get Unicode chars
+ * passed to it; it should be sent ERROR for any encoding error
+ * on the input.
+ */
+ void (*read)(charset_spec const *charset, long int input_chr,
+ charset_state *state,
+ void (*emit)(void *ctx, long int output), void *emitctx);
+ /*
+ * A function to read Unicode characters and output in this
+ * character set. The `emit' function expects to get byte
+ * values passed to it; it should be sent ERROR for any
+ * non-representable characters on the input.
+ */
+ void (*write)(charset_spec const *charset, long int input_chr,
+ charset_state *state,
+ void (*emit)(void *ctx, long int output), void *emitctx);
+ void const *data;
+};
+
+/*
+ * This is the format of `data' used by the SBCS read and write
+ * functions; so it's the format used in all SBCS definitions.
+ */
+struct sbcs_data {
+ /*
+ * This is a simple mapping table converting each SBCS position
+ * to a Unicode code point. Some positions may contain ERROR,
+ * indicating that that byte value is not defined in the SBCS
+ * in question and its occurrence in input is an error.
+ */
+ unsigned long sbcs2ucs[256];
+
+ /*
+ * This lookup table is used to convert Unicode back to the
+ * SBCS. It consists of the valid byte values in the SBCS,
+ * sorted in order of their Unicode translation. So given a
+ * Unicode value U, you can do a binary search on this table
+ * using the above table as a lookup: when testing the Xth
+ * position in this table, you branch according to whether
+ * sbcs2ucs[ucs2sbcs[X]] is less than, greater than, or equal
+ * to U.
+ *
+ * Note that since there may be fewer than 256 valid byte
+ * values in a particular SBCS, we must supply the length of
+ * this table as well as the contents.
+ */
+ unsigned char ucs2sbcs[256];
+ int nvalid;
+};
+
+/*
+ * Prototypes for internal library functions.
+ */
+charset_spec const *charset_find_spec(int charset);
+void read_sbcs(charset_spec const *charset, long int input_chr,
+ charset_state *state,
+ void (*emit)(void *ctx, long int output), void *emitctx);
+void write_sbcs(charset_spec const *charset, long int input_chr,
+ charset_state *state,
+ void (*emit)(void *ctx, long int output), void *emitctx);
+
+/*
+ * Placate compiler warning about unused parameters, of which we
+ * expect to have some in this library.
+ */
+#define UNUSEDARG(x) ( (x) = (x) )
+
+#endif /* charset_internal_h */
--- /dev/null
+/*
+ * local.c - translate our internal character set codes to and from
+ * our own set of plausibly legible character-set names. Also
+ * provides a canonical name for each encoding (useful for software
+ * announcing what character set it will be using), and a set of
+ * enumeration functions which return a list of supported
+ * encodings one by one.
+ *
+ * charset_from_localenc will attempt all other text translations
+ * as well as this table, to maximise the number of different ways
+ * you can select a supported charset.
+ */
+
+#include <ctype.h>
+#include "charset.h"
+#include "internal.h"
+
+static const struct {
+ const char *name;
+ int charset;
+ int return_in_enum; /* enumeration misses some charsets */
+} localencs[] = {
+ { "<UNKNOWN>", CS_NONE, 0 },
+ { "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 },
+ { "ISO-8859-5", CS_ISO8859_5, 1 },
+ { "ISO-8859-6", CS_ISO8859_6, 1 },
+ { "ISO-8859-7", CS_ISO8859_7, 1 },
+ { "ISO-8859-8", CS_ISO8859_8, 1 },
+ { "ISO-8859-9", CS_ISO8859_9, 1 },
+ { "ISO-8859-10", CS_ISO8859_10, 1 },
+ { "ISO-8859-11", CS_ISO8859_11, 1 },
+ { "ISO-8859-13", CS_ISO8859_13, 1 },
+ { "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 },
+ { "CP866", CS_CP866, 1 },
+ { "CP1250", CS_CP1250, 1 },
+ { "CP1251", CS_CP1251, 1 },
+ { "CP1252", CS_CP1252, 1 },
+ { "CP1253", CS_CP1253, 1 },
+ { "CP1254", CS_CP1254, 1 },
+ { "CP1255", CS_CP1255, 1 },
+ { "CP1256", CS_CP1256, 1 },
+ { "CP1257", CS_CP1257, 1 },
+ { "CP1258", CS_CP1258, 1 },
+ { "KOI8-R", CS_KOI8_R, 1 },
+ { "KOI8-U", CS_KOI8_U, 1 },
+ { "Mac Roman", CS_MAC_ROMAN, 1 },
+ { "Mac Turkish", CS_MAC_TURKISH, 1 },
+ { "Mac Croatian", CS_MAC_CROATIAN, 1 },
+ { "Mac Iceland", CS_MAC_ICELAND, 1 },
+ { "Mac Romanian", CS_MAC_ROMANIAN, 1 },
+ { "Mac Greek", CS_MAC_GREEK, 1 },
+ { "Mac Cyrillic", CS_MAC_CYRILLIC, 1 },
+ { "Mac Thai", CS_MAC_THAI, 1 },
+ { "Mac Centeuro", CS_MAC_CENTEURO, 1 },
+ { "Mac Symbol", CS_MAC_SYMBOL, 1 },
+ { "Mac Dingbats", CS_MAC_DINGBATS, 1 },
+ { "Mac Roman (old)", CS_MAC_ROMAN_OLD, 0 },
+ { "Mac Croatian (old)", CS_MAC_CROATIAN_OLD, 0 },
+ { "Mac Iceland (old)", CS_MAC_ICELAND_OLD, 0 },
+ { "Mac Romanian (old)", CS_MAC_ROMANIAN_OLD, 0 },
+ { "Mac Greek (old)", CS_MAC_GREEK_OLD, 0 },
+ { "Mac Cyrillic (old)", CS_MAC_CYRILLIC_OLD, 0 },
+ { "Mac Ukraine", CS_MAC_UKRAINE, 1 },
+ { "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;
+
+ for (i = 0; i < (int)lenof(localencs); i++)
+ if (charset == localencs[i].charset)
+ return localencs[i].name;
+
+ return NULL; /* not found */
+}
+
+int charset_from_localenc(const char *name)
+{
+ int i;
+
+ if ( (i = charset_from_mimeenc(name)) != CS_NONE)
+ return i;
+ if ( (i = charset_from_xenc(name)) != CS_NONE)
+ return i;
+
+ for (i = 0; i < (int)lenof(localencs); i++) {
+ const char *p, *q;
+ p = name;
+ q = localencs[i].name;
+ while (*p || *q) {
+ if (tolower((unsigned char)*p) != tolower((unsigned char)*q))
+ break;
+ p++; q++;
+ }
+ if (!*p && !*q)
+ return localencs[i].charset;
+ }
+
+ return CS_NONE; /* not found */
+}
+
+int charset_localenc_nth(int n)
+{
+ int i;
+
+ for (i = 0; i < (int)lenof(localencs); i++)
+ if (localencs[i].return_in_enum && !n--)
+ return localencs[i].charset;
+
+ return CS_NONE; /* end of list */
+}
--- /dev/null
+/* $Id$ */
+/*
+ * Copyright (c) 2003 Ben Harris
+ * All rights reserved.
+ *
+ * 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, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
+ * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+ * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+/*
+ * macenc.c -- Convert a Mac OS script/region/font combination to our
+ * internal charset code.
+ */
+
+#include <string.h>
+
+#include "charset.h"
+#include "internal.h"
+
+/*
+ * These are defined by Mac OS's <Script.h>, but we'd like to be
+ * independent of that.
+ */
+
+#define smRoman 0
+#define smJapanese 1
+#define smTradChinese 2
+#define smKorean 3
+#define smArabic 4
+#define smHebrew 5
+#define smCyrillic 7
+#define smDevenagari 9
+#define smGurmukhi 10
+#define smGujurati 11
+#define smThai 21
+#define smSimpChinese 25
+#define smTibetan 26
+#define smEthiopic 28
+#define smCentralEuroRoman 29
+
+#define verGreece 20
+#define verIceland 21
+#define verTurkey 24
+#define verYugoCroatian 25
+#define verRomania 39
+#define verFaroeIsl 47
+#define verIran 48
+#define verRussia 49
+#define verSlovenian 66
+#define verCroatia 68
+#define verBulgaria 72
+#define verScottishGaelic 75
+#define verManxGaelic 76
+#define verBreton 77
+#define verNunavut 78
+#define verWelsh 79
+#define verIrishGaelicScript 81
+
+static const struct {
+ int script;
+ int region;
+ int sysvermin;
+ char const *fontname;
+ int charset;
+} macencs[] = {
+ { smRoman, -1, 0x850, "VT100", CS_MAC_VT100 },
+ { smRoman, -1, 0, "VT100", CS_MAC_VT100_OLD },
+ /*
+ * From here on, this table is largely derived from
+ * <http://www.unicode.org/Public/MAPPINGS/VENDORS/APPLE/README.TXT>,
+ * with _OLD version added based on the comments in individual
+ * mapping files.
+ */
+ { smRoman, -1, 0, "Symbol", CS_MAC_SYMBOL },
+ { smRoman, -1, 0, "Zapf Dingbats", CS_MAC_DINGBATS },
+ { smRoman, verTurkey, 0, NULL, CS_MAC_TURKISH },
+ { smRoman, verYugoCroatian, 0x850, NULL, CS_MAC_CROATIAN },
+ { smRoman, verYugoCroatian, 0, NULL, CS_MAC_CROATIAN_OLD },
+ { smRoman, verSlovenian, 0x850, NULL, CS_MAC_CROATIAN },
+ { smRoman, verSlovenian, 0, NULL, CS_MAC_CROATIAN_OLD },
+ { smRoman, verCroatia, 0x850, NULL, CS_MAC_CROATIAN },
+ { smRoman, verCroatia, 0, NULL, CS_MAC_CROATIAN_OLD },
+ { smRoman, verIceland, 0x850, NULL, CS_MAC_ICELAND },
+ { smRoman, verIceland, 0, NULL, CS_MAC_ICELAND_OLD },
+ { smRoman, verFaroeIsl, 0x850, NULL, CS_MAC_ICELAND },
+ { smRoman, verFaroeIsl, 0, NULL, CS_MAC_ICELAND_OLD },
+ { smRoman, verRomania, 0x850, NULL, CS_MAC_ROMANIAN },
+ { smRoman, verRomania, 0, NULL, CS_MAC_ROMANIAN_OLD },
+#if 0 /* No mapping table on ftp.unicode.org */
+ { smRoman, verIreland, 0x850, NULL, CS_MAC_CELTIC },
+ { smRoman, verIreland, 0, NULL, CS_MAC_CELTIC_OLD },
+ { smRoman, verScottishGaelic, 0x850, NULL, CS_MAC_CELTIC },
+ { smRoman, verScottishGaelic, 0, NULL, CS_MAC_CELTIC_OLD },
+ { smRoman, verManxGaelic, 0x850, NULL, CS_MAC_CELTIC },
+ { smRoman, verManxGaelic, 0, NULL, CS_MAC_CELTIC_OLD },
+ { smRoman, verBreton, 0x850, NULL, CS_MAC_CELTIC },
+ { smRoman, verBreton, 0, NULL, CS_MAC_CELTIC_OLD },
+ { smRoman, verWelsh, 0x850, NULL, CS_MAC_CELTIC },
+ { smRoman, verWelsh, 0, NULL, CS_MAC_CELTIC_OLD },
+ { smRoman, verIrishGaelicScript, 0x850, NULL, CS_MAC_GAELIC },
+ { smRoman, verIrishGaelicScript, 0, NULL, CS_MAC_GAELIC_OLD },
+#endif
+ { smRoman, verGreece, 0x922, NULL, CS_MAC_GREEK },
+ { smRoman, verGreece, 0, NULL, CS_MAC_GREEK_OLD },
+ { smRoman, -1, 0x850, NULL, CS_MAC_ROMAN },
+ { smRoman, -1, 0, NULL, CS_MAC_ROMAN_OLD },
+#if 0 /* Multi-byte encodings, not yet supported */
+ { smJapanese, -1, 0, NULL, CS_MAC_JAPANESE },
+ { smTradChinese, -1, 0, NULL, CS_MAC_CHINTRAD },
+ { smKorean, -1, 0, NULL, CS_MAC_KOREAN },
+#endif
+#if 0 /* Bidirectional encodings, not yet supported */
+ { smArabic, verIran, 0, NULL, CS_MAC_FARSI },
+ { smArabic, -1, 0, NULL, CS_MAC_ARABIC },
+ { smHebrew, -1, 0, NULL, CS_MAC_HEBREW },
+#endif
+ { smCyrillic, -1, 0x900, NULL, CS_MAC_CYRILLIC },
+ { smCyrillic, verRussia, 0, NULL, CS_MAC_CYRILLIC_OLD },
+ { smCyrillic, verBulgaria, 0, NULL, CS_MAC_CYRILLIC_OLD },
+ { smCyrillic, -1, 0, NULL, CS_MAC_UKRAINE },
+#if 0 /* Complex Indic scripts, not yet supported */
+ { smDevanagari, -1, 0, NULL, CS_MAC_DEVENAGA },
+ { smGurmukhi, -1, 0, NULL, CS_MAC_GURMUKHI },
+ { smGujurati, -1, 0, NULL, CS_MAC_GUJURATI },
+#endif
+ { smThai, -1, 0, NULL, CS_MAC_THAI },
+#if 0 /* Multi-byte encoding, not yet supported */
+ { smSimpChinese, -1, 0, NULL, CS_MAC_CHINSIMP },
+#endif
+#if 0 /* No mapping table on ftp.unicode.org */
+ { smTibetan, -1, 0, NULL, CS_MAC_TIBETAN },
+ { smEthiopic, -1, 0, NULL, CS_MAC_ETHIOPIC },
+ { smEthiopic, verNanavut, 0, NULL, CS_MAC_INUIT },
+#endif
+ { smCentralEuroRoman, -1, 0, NULL, CS_MAC_CENTEURO },
+};
+
+int charset_from_macenc(int script, int region, int sysvers,
+ char const *fontname)
+{
+ int i;
+
+ for (i = 0; i < (int)lenof(macencs); i++)
+ if ((macencs[i].script == script) &&
+ (macencs[i].region < 0 || macencs[i].region == region) &&
+ (macencs[i].sysvermin <= sysvers) &&
+ (macencs[i].fontname == NULL ||
+ (fontname != NULL && strcmp(macencs[i].fontname, fontname) == 0)))
+ return macencs[i].charset;
+
+ return CS_NONE;
+}
--- /dev/null
+/*
+ * mimeenc.c - translate our internal character set codes to and
+ * from MIME standard character-set names.
+ *
+ */
+
+#include <ctype.h>
+#include "charset.h"
+#include "internal.h"
+
+static const struct {
+ const char *name;
+ int charset;
+} mimeencs[] = {
+ /*
+ * These names are taken from
+ *
+ * http://www.iana.org/assignments/character-sets
+ *
+ * Where multiple encoding names map to the same encoding id
+ * (such as the variety of aliases for ISO-8859-1), the first
+ * is considered canonical and will be returned when
+ * translating the id to a string.
+ */
+ { "ISO-8859-1", CS_ISO8859_1 },
+ { "iso-ir-100", CS_ISO8859_1 },
+ { "ISO_8859-1", CS_ISO8859_1 },
+ { "ISO_8859-1:1987", CS_ISO8859_1 },
+ { "latin1", CS_ISO8859_1 },
+ { "l1", CS_ISO8859_1 },
+ { "IBM819", CS_ISO8859_1 },
+ { "CP819", CS_ISO8859_1 },
+ { "csISOLatin1", CS_ISO8859_1 },
+
+ { "ISO-8859-2", CS_ISO8859_2 },
+ { "ISO_8859-2:1987", CS_ISO8859_2 },
+ { "iso-ir-101", CS_ISO8859_2 },
+ { "ISO_8859-2", CS_ISO8859_2 },
+ { "latin2", CS_ISO8859_2 },
+ { "l2", CS_ISO8859_2 },
+ { "csISOLatin2", CS_ISO8859_2 },
+
+ { "ISO-8859-3", CS_ISO8859_3 },
+ { "ISO_8859-3:1988", CS_ISO8859_3 },
+ { "iso-ir-109", CS_ISO8859_3 },
+ { "ISO_8859-3", CS_ISO8859_3 },
+ { "latin3", CS_ISO8859_3 },
+ { "l3", CS_ISO8859_3 },
+ { "csISOLatin3", CS_ISO8859_3 },
+
+ { "ISO-8859-4", CS_ISO8859_4 },
+ { "ISO_8859-4:1988", CS_ISO8859_4 },
+ { "iso-ir-110", CS_ISO8859_4 },
+ { "ISO_8859-4", CS_ISO8859_4 },
+ { "latin4", CS_ISO8859_4 },
+ { "l4", CS_ISO8859_4 },
+ { "csISOLatin4", CS_ISO8859_4 },
+
+ { "ISO-8859-5", CS_ISO8859_5 },
+ { "ISO_8859-5:1988", CS_ISO8859_5 },
+ { "iso-ir-144", CS_ISO8859_5 },
+ { "ISO_8859-5", CS_ISO8859_5 },
+ { "cyrillic", CS_ISO8859_5 },
+ { "csISOLatinCyrillic", CS_ISO8859_5 },
+
+ { "ISO-8859-6", CS_ISO8859_6 },
+ { "ISO_8859-6:1987", CS_ISO8859_6 },
+ { "iso-ir-127", CS_ISO8859_6 },
+ { "ISO_8859-6", CS_ISO8859_6 },
+ { "ECMA-114", CS_ISO8859_6 },
+ { "ASMO-708", CS_ISO8859_6 },
+ { "arabic", CS_ISO8859_6 },
+ { "csISOLatinArabic", CS_ISO8859_6 },
+
+ { "ISO-8859-7", CS_ISO8859_7 },
+ { "ISO_8859-7:1987", CS_ISO8859_7 },
+ { "iso-ir-126", CS_ISO8859_7 },
+ { "ISO_8859-7", CS_ISO8859_7 },
+ { "ELOT_928", CS_ISO8859_7 },
+ { "ECMA-118", CS_ISO8859_7 },
+ { "greek", CS_ISO8859_7 },
+ { "greek8", CS_ISO8859_7 },
+ { "csISOLatinGreek", CS_ISO8859_7 },
+
+ { "ISO-8859-8", CS_ISO8859_8 },
+ { "ISO_8859-8:1988", CS_ISO8859_8 },
+ { "iso-ir-138", CS_ISO8859_8 },
+ { "ISO_8859-8", CS_ISO8859_8 },
+ { "hebrew", CS_ISO8859_8 },
+ { "csISOLatinHebrew", CS_ISO8859_8 },
+
+ { "ISO-8859-9", CS_ISO8859_9 },
+ { "ISO_8859-9:1989", CS_ISO8859_9 },
+ { "iso-ir-148", CS_ISO8859_9 },
+ { "ISO_8859-9", CS_ISO8859_9 },
+ { "latin5", CS_ISO8859_9 },
+ { "l5", CS_ISO8859_9 },
+ { "csISOLatin5", CS_ISO8859_9 },
+
+ { "ISO-8859-10", CS_ISO8859_10 },
+ { "iso-ir-157", CS_ISO8859_10 },
+ { "l6", CS_ISO8859_10 },
+ { "ISO_8859-10:1992", CS_ISO8859_10 },
+ { "csISOLatin6", CS_ISO8859_10 },
+ { "latin6", CS_ISO8859_10 },
+
+ { "ISO-8859-13", CS_ISO8859_13 },
+
+ { "ISO-8859-14", CS_ISO8859_14 },
+ { "iso-ir-199", CS_ISO8859_14 },
+ { "ISO_8859-14:1998", CS_ISO8859_14 },
+ { "ISO_8859-14", CS_ISO8859_14 },
+ { "latin8", CS_ISO8859_14 },
+ { "iso-celtic", CS_ISO8859_14 },
+ { "l8", CS_ISO8859_14 },
+
+ { "ISO-8859-15", CS_ISO8859_15 },
+ { "ISO_8859-15", CS_ISO8859_15 },
+ { "Latin-9", CS_ISO8859_15 },
+
+ { "ISO-8859-16", CS_ISO8859_16 },
+ { "iso-ir-226", CS_ISO8859_16 },
+ { "ISO_8859-16", CS_ISO8859_16 },
+ { "ISO_8859-16:2001", CS_ISO8859_16 },
+ { "latin10", CS_ISO8859_16 },
+ { "l10", CS_ISO8859_16 },
+
+ { "IBM437", CS_CP437 },
+ { "cp437", CS_CP437 },
+ { "437", CS_CP437 },
+ { "csPC8CodePage437", CS_CP437 },
+
+ { "IBM850", CS_CP850 },
+ { "cp850", CS_CP850 },
+ { "850", CS_CP850 },
+ { "csPC850Multilingual", CS_CP850 },
+
+ { "IBM866", CS_CP866 },
+ { "cp866", CS_CP866 },
+ { "866", CS_CP866 },
+ { "csIBM866", CS_CP866 },
+
+ { "windows-1250", CS_CP1250 },
+
+ { "windows-1251", CS_CP1251 },
+
+ { "windows-1252", CS_CP1252 },
+
+ { "windows-1253", CS_CP1253 },
+
+ { "windows-1254", CS_CP1254 },
+
+ { "windows-1255", CS_CP1255 },
+
+ { "windows-1256", CS_CP1256 },
+
+ { "windows-1257", CS_CP1257 },
+
+ { "windows-1258", CS_CP1258 },
+
+ { "KOI8-R", CS_KOI8_R },
+ { "csKOI8R", CS_KOI8_R },
+
+ { "KOI8-U", CS_KOI8_U },
+
+ { "macintosh", CS_MAC_ROMAN_OLD },
+ { "mac", CS_MAC_ROMAN_OLD },
+ { "csMacintosh", CS_MAC_ROMAN_OLD },
+
+ { "VISCII", CS_VISCII },
+ { "csVISCII", CS_VISCII },
+
+ { "hp-roman8", CS_HP_ROMAN8 },
+ { "roman8", CS_HP_ROMAN8 },
+ { "r8", CS_HP_ROMAN8 },
+ { "csHPRoman8", CS_HP_ROMAN8 },
+
+ { "DEC-MCS", CS_DEC_MCS },
+ { "dec", CS_DEC_MCS },
+ { "csDECMCS", CS_DEC_MCS },
+
+ { "UTF-8", CS_UTF8 },
+};
+
+const char *charset_to_mimeenc(int charset)
+{
+ int i;
+
+ for (i = 0; i < (int)lenof(mimeencs); i++)
+ if (charset == mimeencs[i].charset)
+ return mimeencs[i].name;
+
+ return NULL; /* not found */
+}
+
+int charset_from_mimeenc(const char *name)
+{
+ int i;
+
+ for (i = 0; i < (int)lenof(mimeencs); i++) {
+ const char *p, *q;
+ p = name;
+ q = mimeencs[i].name;
+ while (*p || *q) {
+ if (tolower((unsigned char)*p) != tolower((unsigned char)*q))
+ break;
+ p++; q++;
+ }
+ if (!*p && !*q)
+ return mimeencs[i].charset;
+ }
+
+ return CS_NONE; /* not found */
+}
--- /dev/null
+/*
+ * sbcs.c - routines to handle single-byte character sets.
+ */
+
+#include "charset.h"
+#include "internal.h"
+
+/*
+ * The charset_spec for any single-byte character set should
+ * provide read_sbcs() as its read function, and its `data' field
+ * should be a wchar_t string constant containing the 256 entries
+ * of the translation table.
+ */
+
+void read_sbcs(charset_spec const *charset, long int input_chr,
+ charset_state *state,
+ void (*emit)(void *ctx, long int output), void *emitctx)
+{
+ const struct sbcs_data *sd = charset->data;
+
+ UNUSEDARG(state);
+
+ emit(emitctx, sd->sbcs2ucs[input_chr]);
+}
+
+void write_sbcs(charset_spec const *charset, long int input_chr,
+ charset_state *state,
+ void (*emit)(void *ctx, long int output), void *emitctx)
+{
+ const struct sbcs_data *sd = charset->data;
+ int i, j, k, c;
+
+ UNUSEDARG(state);
+
+ /*
+ * Binary-search in the ucs2sbcs table.
+ */
+ i = -1;
+ j = sd->nvalid;
+ while (i+1 < j) {
+ k = (i+j)/2;
+ c = sd->ucs2sbcs[k];
+ if (input_chr < sd->sbcs2ucs[c])
+ j = k;
+ else if (input_chr > sd->sbcs2ucs[c])
+ i = k;
+ else {
+ emit(emitctx, c);
+ return;
+ }
+ }
+ emit(emitctx, ERROR);
+}
--- /dev/null
+ Data file defining single-byte character sets.
+
+ All lines which begin with whitespace are considered comments.
+
+ To generate an SBCS table from a unicode.org mapping table:
+
+ gensbcs() {
+ wget -q -O - "$1" | tr '\r' '\n' | \
+ perl -ne '/^(0x.*)\s+(0x.*)\s+/ and $a[hex $1]=sprintf "%04x", hex $2;' \
+ -e 'BEGIN{for($i=0;$i<256;$i++){$a[$i]="XXXX";' \
+ -e ' if ($i < 32 or $i == 127) {$a[$i]=sprintf "%04x", $i}}}' \
+ -e 'END{for($i=0;$i<256;$i++){printf"%s%s",$a[$i],$i%16==15?"\n":" "}}'
+ }
+
+ (A couple of noteworthy ickinesses here. For a start, any
+ undefined characters in the control-code regions (00-1F and 7F)
+ are assumed to be the Unicode code point corresponding to their
+ index, since the Mac Roman mapping table declines to define them
+ but realistically you don't want to be messing with that sort of
+ thing. Secondly, the Mac mapping tables are shipped with Mac line
+ endings, so note the `tr' to turn them into something legible to
+ Perl...)
+
+ Here are the ISO-8859-x tables, generated by this piece of Bourne
+ shell:
+
+ for i in 1 2 3 4 5 6 7 8 9 10 11 13 14 15 16; do
+ echo charset CS_ISO8859_$i
+ gensbcs http://www.unicode.org/Public/MAPPINGS/ISO8859/8859-$i.TXT
+ echo
+ done
+
+charset CS_ISO8859_1
+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
+0080 0081 0082 0083 0084 0085 0086 0087 0088 0089 008a 008b 008c 008d 008e 008f
+0090 0091 0092 0093 0094 0095 0096 0097 0098 0099 009a 009b 009c 009d 009e 009f
+00a0 00a1 00a2 00a3 00a4 00a5 00a6 00a7 00a8 00a9 00aa 00ab 00ac 00ad 00ae 00af
+00b0 00b1 00b2 00b3 00b4 00b5 00b6 00b7 00b8 00b9 00ba 00bb 00bc 00bd 00be 00bf
+00c0 00c1 00c2 00c3 00c4 00c5 00c6 00c7 00c8 00c9 00ca 00cb 00cc 00cd 00ce 00cf
+00d0 00d1 00d2 00d3 00d4 00d5 00d6 00d7 00d8 00d9 00da 00db 00dc 00dd 00de 00df
+00e0 00e1 00e2 00e3 00e4 00e5 00e6 00e7 00e8 00e9 00ea 00eb 00ec 00ed 00ee 00ef
+00f0 00f1 00f2 00f3 00f4 00f5 00f6 00f7 00f8 00f9 00fa 00fb 00fc 00fd 00fe 00ff
+
+charset CS_ISO8859_2
+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
+0080 0081 0082 0083 0084 0085 0086 0087 0088 0089 008a 008b 008c 008d 008e 008f
+0090 0091 0092 0093 0094 0095 0096 0097 0098 0099 009a 009b 009c 009d 009e 009f
+00a0 0104 02d8 0141 00a4 013d 015a 00a7 00a8 0160 015e 0164 0179 00ad 017d 017b
+00b0 0105 02db 0142 00b4 013e 015b 02c7 00b8 0161 015f 0165 017a 02dd 017e 017c
+0154 00c1 00c2 0102 00c4 0139 0106 00c7 010c 00c9 0118 00cb 011a 00cd 00ce 010e
+0110 0143 0147 00d3 00d4 0150 00d6 00d7 0158 016e 00da 0170 00dc 00dd 0162 00df
+0155 00e1 00e2 0103 00e4 013a 0107 00e7 010d 00e9 0119 00eb 011b 00ed 00ee 010f
+0111 0144 0148 00f3 00f4 0151 00f6 00f7 0159 016f 00fa 0171 00fc 00fd 0163 02d9
+
+charset CS_ISO8859_3
+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
+0080 0081 0082 0083 0084 0085 0086 0087 0088 0089 008a 008b 008c 008d 008e 008f
+0090 0091 0092 0093 0094 0095 0096 0097 0098 0099 009a 009b 009c 009d 009e 009f
+00a0 0126 02d8 00a3 00a4 XXXX 0124 00a7 00a8 0130 015e 011e 0134 00ad XXXX 017b
+00b0 0127 00b2 00b3 00b4 00b5 0125 00b7 00b8 0131 015f 011f 0135 00bd XXXX 017c
+00c0 00c1 00c2 XXXX 00c4 010a 0108 00c7 00c8 00c9 00ca 00cb 00cc 00cd 00ce 00cf
+XXXX 00d1 00d2 00d3 00d4 0120 00d6 00d7 011c 00d9 00da 00db 00dc 016c 015c 00df
+00e0 00e1 00e2 XXXX 00e4 010b 0109 00e7 00e8 00e9 00ea 00eb 00ec 00ed 00ee 00ef
+XXXX 00f1 00f2 00f3 00f4 0121 00f6 00f7 011d 00f9 00fa 00fb 00fc 016d 015d 02d9
+
+charset CS_ISO8859_4
+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
+0080 0081 0082 0083 0084 0085 0086 0087 0088 0089 008a 008b 008c 008d 008e 008f
+0090 0091 0092 0093 0094 0095 0096 0097 0098 0099 009a 009b 009c 009d 009e 009f
+00a0 0104 0138 0156 00a4 0128 013b 00a7 00a8 0160 0112 0122 0166 00ad 017d 00af
+00b0 0105 02db 0157 00b4 0129 013c 02c7 00b8 0161 0113 0123 0167 014a 017e 014b
+0100 00c1 00c2 00c3 00c4 00c5 00c6 012e 010c 00c9 0118 00cb 0116 00cd 00ce 012a
+0110 0145 014c 0136 00d4 00d5 00d6 00d7 00d8 0172 00da 00db 00dc 0168 016a 00df
+0101 00e1 00e2 00e3 00e4 00e5 00e6 012f 010d 00e9 0119 00eb 0117 00ed 00ee 012b
+0111 0146 014d 0137 00f4 00f5 00f6 00f7 00f8 0173 00fa 00fb 00fc 0169 016b 02d9
+
+charset CS_ISO8859_5
+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
+0080 0081 0082 0083 0084 0085 0086 0087 0088 0089 008a 008b 008c 008d 008e 008f
+0090 0091 0092 0093 0094 0095 0096 0097 0098 0099 009a 009b 009c 009d 009e 009f
+00a0 0401 0402 0403 0404 0405 0406 0407 0408 0409 040a 040b 040c 00ad 040e 040f
+0410 0411 0412 0413 0414 0415 0416 0417 0418 0419 041a 041b 041c 041d 041e 041f
+0420 0421 0422 0423 0424 0425 0426 0427 0428 0429 042a 042b 042c 042d 042e 042f
+0430 0431 0432 0433 0434 0435 0436 0437 0438 0439 043a 043b 043c 043d 043e 043f
+0440 0441 0442 0443 0444 0445 0446 0447 0448 0449 044a 044b 044c 044d 044e 044f
+2116 0451 0452 0453 0454 0455 0456 0457 0458 0459 045a 045b 045c 00a7 045e 045f
+
+charset CS_ISO8859_6
+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
+0080 0081 0082 0083 0084 0085 0086 0087 0088 0089 008a 008b 008c 008d 008e 008f
+0090 0091 0092 0093 0094 0095 0096 0097 0098 0099 009a 009b 009c 009d 009e 009f
+00a0 XXXX XXXX XXXX 00a4 XXXX XXXX XXXX XXXX XXXX XXXX XXXX 060c 00ad XXXX XXXX
+XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX 061b XXXX XXXX XXXX 061f
+XXXX 0621 0622 0623 0624 0625 0626 0627 0628 0629 062a 062b 062c 062d 062e 062f
+0630 0631 0632 0633 0634 0635 0636 0637 0638 0639 063a XXXX XXXX XXXX XXXX XXXX
+0640 0641 0642 0643 0644 0645 0646 0647 0648 0649 064a 064b 064c 064d 064e 064f
+0650 0651 0652 XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX
+
+charset CS_ISO8859_7
+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
+0080 0081 0082 0083 0084 0085 0086 0087 0088 0089 008a 008b 008c 008d 008e 008f
+0090 0091 0092 0093 0094 0095 0096 0097 0098 0099 009a 009b 009c 009d 009e 009f
+00a0 2018 2019 00a3 XXXX XXXX 00a6 00a7 00a8 00a9 XXXX 00ab 00ac 00ad XXXX 2015
+00b0 00b1 00b2 00b3 0384 0385 0386 00b7 0388 0389 038a 00bb 038c 00bd 038e 038f
+0390 0391 0392 0393 0394 0395 0396 0397 0398 0399 039a 039b 039c 039d 039e 039f
+03a0 03a1 XXXX 03a3 03a4 03a5 03a6 03a7 03a8 03a9 03aa 03ab 03ac 03ad 03ae 03af
+03b0 03b1 03b2 03b3 03b4 03b5 03b6 03b7 03b8 03b9 03ba 03bb 03bc 03bd 03be 03bf
+03c0 03c1 03c2 03c3 03c4 03c5 03c6 03c7 03c8 03c9 03ca 03cb 03cc 03cd 03ce XXXX
+
+charset CS_ISO8859_8
+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
+0080 0081 0082 0083 0084 0085 0086 0087 0088 0089 008a 008b 008c 008d 008e 008f
+0090 0091 0092 0093 0094 0095 0096 0097 0098 0099 009a 009b 009c 009d 009e 009f
+00a0 XXXX 00a2 00a3 00a4 00a5 00a6 00a7 00a8 00a9 00d7 00ab 00ac 00ad 00ae 00af
+00b0 00b1 00b2 00b3 00b4 00b5 00b6 00b7 00b8 00b9 00f7 00bb 00bc 00bd 00be XXXX
+XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX
+XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX 2017
+05d0 05d1 05d2 05d3 05d4 05d5 05d6 05d7 05d8 05d9 05da 05db 05dc 05dd 05de 05df
+05e0 05e1 05e2 05e3 05e4 05e5 05e6 05e7 05e8 05e9 05ea XXXX XXXX 200e 200f XXXX
+
+charset CS_ISO8859_9
+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
+0080 0081 0082 0083 0084 0085 0086 0087 0088 0089 008a 008b 008c 008d 008e 008f
+0090 0091 0092 0093 0094 0095 0096 0097 0098 0099 009a 009b 009c 009d 009e 009f
+00a0 00a1 00a2 00a3 00a4 00a5 00a6 00a7 00a8 00a9 00aa 00ab 00ac 00ad 00ae 00af
+00b0 00b1 00b2 00b3 00b4 00b5 00b6 00b7 00b8 00b9 00ba 00bb 00bc 00bd 00be 00bf
+00c0 00c1 00c2 00c3 00c4 00c5 00c6 00c7 00c8 00c9 00ca 00cb 00cc 00cd 00ce 00cf
+011e 00d1 00d2 00d3 00d4 00d5 00d6 00d7 00d8 00d9 00da 00db 00dc 0130 015e 00df
+00e0 00e1 00e2 00e3 00e4 00e5 00e6 00e7 00e8 00e9 00ea 00eb 00ec 00ed 00ee 00ef
+011f 00f1 00f2 00f3 00f4 00f5 00f6 00f7 00f8 00f9 00fa 00fb 00fc 0131 015f 00ff
+
+charset CS_ISO8859_10
+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
+0080 0081 0082 0083 0084 0085 0086 0087 0088 0089 008a 008b 008c 008d 008e 008f
+0090 0091 0092 0093 0094 0095 0096 0097 0098 0099 009a 009b 009c 009d 009e 009f
+00a0 0104 0112 0122 012a 0128 0136 00a7 013b 0110 0160 0166 017d 00ad 016a 014a
+00b0 0105 0113 0123 012b 0129 0137 00b7 013c 0111 0161 0167 017e 2015 016b 014b
+0100 00c1 00c2 00c3 00c4 00c5 00c6 012e 010c 00c9 0118 00cb 0116 00cd 00ce 00cf
+00d0 0145 014c 00d3 00d4 00d5 00d6 0168 00d8 0172 00da 00db 00dc 00dd 00de 00df
+0101 00e1 00e2 00e3 00e4 00e5 00e6 012f 010d 00e9 0119 00eb 0117 00ed 00ee 00ef
+00f0 0146 014d 00f3 00f4 00f5 00f6 0169 00f8 0173 00fa 00fb 00fc 00fd 00fe 0138
+
+charset CS_ISO8859_11
+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
+0080 0081 0082 0083 0084 0085 0086 0087 0088 0089 008a 008b 008c 008d 008e 008f
+0090 0091 0092 0093 0094 0095 0096 0097 0098 0099 009a 009b 009c 009d 009e 009f
+00a0 0e01 0e02 0e03 0e04 0e05 0e06 0e07 0e08 0e09 0e0a 0e0b 0e0c 0e0d 0e0e 0e0f
+0e10 0e11 0e12 0e13 0e14 0e15 0e16 0e17 0e18 0e19 0e1a 0e1b 0e1c 0e1d 0e1e 0e1f
+0e20 0e21 0e22 0e23 0e24 0e25 0e26 0e27 0e28 0e29 0e2a 0e2b 0e2c 0e2d 0e2e 0e2f
+0e30 0e31 0e32 0e33 0e34 0e35 0e36 0e37 0e38 0e39 0e3a XXXX XXXX XXXX XXXX 0e3f
+0e40 0e41 0e42 0e43 0e44 0e45 0e46 0e47 0e48 0e49 0e4a 0e4b 0e4c 0e4d 0e4e 0e4f
+0e50 0e51 0e52 0e53 0e54 0e55 0e56 0e57 0e58 0e59 0e5a 0e5b XXXX XXXX XXXX XXXX
+
+charset CS_ISO8859_13
+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
+0080 0081 0082 0083 0084 0085 0086 0087 0088 0089 008a 008b 008c 008d 008e 008f
+0090 0091 0092 0093 0094 0095 0096 0097 0098 0099 009a 009b 009c 009d 009e 009f
+00a0 201d 00a2 00a3 00a4 201e 00a6 00a7 00d8 00a9 0156 00ab 00ac 00ad 00ae 00c6
+00b0 00b1 00b2 00b3 201c 00b5 00b6 00b7 00f8 00b9 0157 00bb 00bc 00bd 00be 00e6
+0104 012e 0100 0106 00c4 00c5 0118 0112 010c 00c9 0179 0116 0122 0136 012a 013b
+0160 0143 0145 00d3 014c 00d5 00d6 00d7 0172 0141 015a 016a 00dc 017b 017d 00df
+0105 012f 0101 0107 00e4 00e5 0119 0113 010d 00e9 017a 0117 0123 0137 012b 013c
+0161 0144 0146 00f3 014d 00f5 00f6 00f7 0173 0142 015b 016b 00fc 017c 017e 2019
+
+charset CS_ISO8859_14
+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
+0080 0081 0082 0083 0084 0085 0086 0087 0088 0089 008a 008b 008c 008d 008e 008f
+0090 0091 0092 0093 0094 0095 0096 0097 0098 0099 009a 009b 009c 009d 009e 009f
+00a0 1e02 1e03 00a3 010a 010b 1e0a 00a7 1e80 00a9 1e82 1e0b 1ef2 00ad 00ae 0178
+1e1e 1e1f 0120 0121 1e40 1e41 00b6 1e56 1e81 1e57 1e83 1e60 1ef3 1e84 1e85 1e61
+00c0 00c1 00c2 00c3 00c4 00c5 00c6 00c7 00c8 00c9 00ca 00cb 00cc 00cd 00ce 00cf
+0174 00d1 00d2 00d3 00d4 00d5 00d6 1e6a 00d8 00d9 00da 00db 00dc 00dd 0176 00df
+00e0 00e1 00e2 00e3 00e4 00e5 00e6 00e7 00e8 00e9 00ea 00eb 00ec 00ed 00ee 00ef
+0175 00f1 00f2 00f3 00f4 00f5 00f6 1e6b 00f8 00f9 00fa 00fb 00fc 00fd 0177 00ff
+
+charset CS_ISO8859_15
+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
+0080 0081 0082 0083 0084 0085 0086 0087 0088 0089 008a 008b 008c 008d 008e 008f
+0090 0091 0092 0093 0094 0095 0096 0097 0098 0099 009a 009b 009c 009d 009e 009f
+00a0 00a1 00a2 00a3 20ac 00a5 0160 00a7 0161 00a9 00aa 00ab 00ac 00ad 00ae 00af
+00b0 00b1 00b2 00b3 017d 00b5 00b6 00b7 017e 00b9 00ba 00bb 0152 0153 0178 00bf
+00c0 00c1 00c2 00c3 00c4 00c5 00c6 00c7 00c8 00c9 00ca 00cb 00cc 00cd 00ce 00cf
+00d0 00d1 00d2 00d3 00d4 00d5 00d6 00d7 00d8 00d9 00da 00db 00dc 00dd 00de 00df
+00e0 00e1 00e2 00e3 00e4 00e5 00e6 00e7 00e8 00e9 00ea 00eb 00ec 00ed 00ee 00ef
+00f0 00f1 00f2 00f3 00f4 00f5 00f6 00f7 00f8 00f9 00fa 00fb 00fc 00fd 00fe 00ff
+
+charset CS_ISO8859_16
+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
+0080 0081 0082 0083 0084 0085 0086 0087 0088 0089 008a 008b 008c 008d 008e 008f
+0090 0091 0092 0093 0094 0095 0096 0097 0098 0099 009a 009b 009c 009d 009e 009f
+00a0 0104 0105 0141 20ac 201e 0160 00a7 0161 00a9 0218 00ab 0179 00ad 017a 017b
+00b0 00b1 010c 0142 017d 201d 00b6 00b7 017e 010d 0219 00bb 0152 0153 0178 017c
+00c0 00c1 00c2 0102 00c4 0106 00c6 00c7 00c8 00c9 00ca 00cb 00cc 00cd 00ce 00cf
+0110 0143 00d2 00d3 00d4 0150 00d6 015a 0170 00d9 00da 00db 00dc 0118 021a 00df
+00e0 00e1 00e2 0103 00e4 0107 00e6 00e7 00e8 00e9 00ea 00eb 00ec 00ed 00ee 00ef
+0111 0144 00f2 00f3 00f4 0151 00f6 015b 0171 00f9 00fa 00fb 00fc 0119 021b 00ff
+
+ Some X fonts are encoded in a variant form of ISO8859-1:
+ everything above 0x20 (space) is as normal, but the first 32
+ characters contain the VT100 line drawing glyphs as they would
+ appear from positions 0x5F to 0x7E inclusive. Here is the modified
+ ISO8859-1 code table.
+
+ Since this table contains a few duplicated positions, we use the
+ `sortpriority' hint to indicate that things in the main part of
+ the code table (0x20-0xFF) should be generated preferentially when
+ converting _from_ Unicode. Hence, U+00b0 (for example) will yield
+ 0xb0 rather than 0x07.
+
+charset CS_ISO8859_1_X11
+sortpriority 00-1F -1
+0020 2666 2592 2409 240c 240d 240a 00b0 00b1 2424 240b 2518 2510 250c 2514 253c
+23ba 23bb 2500 23bc 23bd 251c 2524 2534 252c 2502 2264 2265 03c0 2260 00a3 00b7
+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
+0080 0081 0082 0083 0084 0085 0086 0087 0088 0089 008a 008b 008c 008d 008e 008f
+0090 0091 0092 0093 0094 0095 0096 0097 0098 0099 009a 009b 009c 009d 009e 009f
+00a0 00a1 00a2 00a3 00a4 00a5 00a6 00a7 00a8 00a9 00aa 00ab 00ac 00ad 00ae 00af
+00b0 00b1 00b2 00b3 00b4 00b5 00b6 00b7 00b8 00b9 00ba 00bb 00bc 00bd 00be 00bf
+00c0 00c1 00c2 00c3 00c4 00c5 00c6 00c7 00c8 00c9 00ca 00cb 00cc 00cd 00ce 00cf
+00d0 00d1 00d2 00d3 00d4 00d5 00d6 00d7 00d8 00d9 00da 00db 00dc 00dd 00de 00df
+00e0 00e1 00e2 00e3 00e4 00e5 00e6 00e7 00e8 00e9 00ea 00eb 00ec 00ed 00ee 00ef
+00f0 00f1 00f2 00f3 00f4 00f5 00f6 00f7 00f8 00f9 00fa 00fb 00fc 00fd 00fe 00ff
+
+ Here are some PC (old DOS) code pages, generated by this piece of
+ Bourne shell:
+
+ for i in 437 850 866; do
+ echo charset CS_CP$i
+ gensbcs http://www.unicode.org/Public/MAPPINGS/VENDORS/MICSFT/PC/CP$i.TXT
+ echo
+ done
+
+charset CS_CP437
+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 00e0 00e5 00e7 00ea 00eb 00e8 00ef 00ee 00ec 00c4 00c5
+00c9 00e6 00c6 00f4 00f6 00f2 00fb 00f9 00ff 00d6 00dc 00a2 00a3 00a5 20a7 0192
+00e1 00ed 00f3 00fa 00f1 00d1 00aa 00ba 00bf 2310 00ac 00bd 00bc 00a1 00ab 00bb
+2591 2592 2593 2502 2524 2561 2562 2556 2555 2563 2551 2557 255d 255c 255b 2510
+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
+03b1 00df 0393 03c0 03a3 03c3 00b5 03c4 03a6 0398 03a9 03b4 221e 03c6 03b5 2229
+2261 00b1 2265 2264 2320 2321 00f7 2248 00b0 2219 00b7 221a 207f 00b2 25a0 00a0
+
+charset CS_CP850
+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 00e0 00e5 00e7 00ea 00eb 00e8 00ef 00ee 00ec 00c4 00c5
+00c9 00e6 00c6 00f4 00f6 00f2 00fb 00f9 00ff 00d6 00dc 00f8 00a3 00d8 00d7 0192
+00e1 00ed 00f3 00fa 00f1 00d1 00aa 00ba 00bf 00ae 00ac 00bd 00bc 00a1 00ab 00bb
+2591 2592 2593 2502 2524 00c1 00c2 00c0 00a9 2563 2551 2557 255d 00a2 00a5 2510
+2514 2534 252c 251c 2500 253c 00e3 00c3 255a 2554 2569 2566 2560 2550 256c 00a4
+00f0 00d0 00ca 00cb 00c8 0131 00cd 00ce 00cf 2518 250c 2588 2584 00a6 00cc 2580
+00d3 00df 00d4 00d2 00f5 00d5 00b5 00fe 00de 00da 00db 00d9 00fd 00dd 00af 00b4
+00ad 00b1 2017 00be 00b6 00a7 00f7 00b8 00b0 00a8 00b7 00b9 00b3 00b2 25a0 00a0
+
+charset CS_CP866
+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
+0410 0411 0412 0413 0414 0415 0416 0417 0418 0419 041a 041b 041c 041d 041e 041f
+0420 0421 0422 0423 0424 0425 0426 0427 0428 0429 042a 042b 042c 042d 042e 042f
+0430 0431 0432 0433 0434 0435 0436 0437 0438 0439 043a 043b 043c 043d 043e 043f
+2591 2592 2593 2502 2524 2561 2562 2556 2555 2563 2551 2557 255d 255c 255b 2510
+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
+
+ 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
+ gensbcs http://www.unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/CP$i.TXT
+ echo
+ done
+
+charset CS_CP1250
+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
+20ac XXXX 201a XXXX 201e 2026 2020 2021 XXXX 2030 0160 2039 015a 0164 017d 0179
+XXXX 2018 2019 201c 201d 2022 2013 2014 XXXX 2122 0161 203a 015b 0165 017e 017a
+00a0 02c7 02d8 0141 00a4 0104 00a6 00a7 00a8 00a9 015e 00ab 00ac 00ad 00ae 017b
+00b0 00b1 02db 0142 00b4 00b5 00b6 00b7 00b8 0105 015f 00bb 013d 02dd 013e 017c
+0154 00c1 00c2 0102 00c4 0139 0106 00c7 010c 00c9 0118 00cb 011a 00cd 00ce 010e
+0110 0143 0147 00d3 00d4 0150 00d6 00d7 0158 016e 00da 0170 00dc 00dd 0162 00df
+0155 00e1 00e2 0103 00e4 013a 0107 00e7 010d 00e9 0119 00eb 011b 00ed 00ee 010f
+0111 0144 0148 00f3 00f4 0151 00f6 00f7 0159 016f 00fa 0171 00fc 00fd 0163 02d9
+
+charset CS_CP1251
+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
+0402 0403 201a 0453 201e 2026 2020 2021 20ac 2030 0409 2039 040a 040c 040b 040f
+0452 2018 2019 201c 201d 2022 2013 2014 XXXX 2122 0459 203a 045a 045c 045b 045f
+00a0 040e 045e 0408 00a4 0490 00a6 00a7 0401 00a9 0404 00ab 00ac 00ad 00ae 0407
+00b0 00b1 0406 0456 0491 00b5 00b6 00b7 0451 2116 0454 00bb 0458 0405 0455 0457
+0410 0411 0412 0413 0414 0415 0416 0417 0418 0419 041a 041b 041c 041d 041e 041f
+0420 0421 0422 0423 0424 0425 0426 0427 0428 0429 042a 042b 042c 042d 042e 042f
+0430 0431 0432 0433 0434 0435 0436 0437 0438 0439 043a 043b 043c 043d 043e 043f
+0440 0441 0442 0443 0444 0445 0446 0447 0448 0449 044a 044b 044c 044d 044e 044f
+
+charset CS_CP1252
+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
+20ac XXXX 201a 0192 201e 2026 2020 2021 02c6 2030 0160 2039 0152 XXXX 017d XXXX
+XXXX 2018 2019 201c 201d 2022 2013 2014 02dc 2122 0161 203a 0153 XXXX 017e 0178
+00a0 00a1 00a2 00a3 00a4 00a5 00a6 00a7 00a8 00a9 00aa 00ab 00ac 00ad 00ae 00af
+00b0 00b1 00b2 00b3 00b4 00b5 00b6 00b7 00b8 00b9 00ba 00bb 00bc 00bd 00be 00bf
+00c0 00c1 00c2 00c3 00c4 00c5 00c6 00c7 00c8 00c9 00ca 00cb 00cc 00cd 00ce 00cf
+00d0 00d1 00d2 00d3 00d4 00d5 00d6 00d7 00d8 00d9 00da 00db 00dc 00dd 00de 00df
+00e0 00e1 00e2 00e3 00e4 00e5 00e6 00e7 00e8 00e9 00ea 00eb 00ec 00ed 00ee 00ef
+00f0 00f1 00f2 00f3 00f4 00f5 00f6 00f7 00f8 00f9 00fa 00fb 00fc 00fd 00fe 00ff
+
+charset CS_CP1253
+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
+20ac XXXX 201a 0192 201e 2026 2020 2021 XXXX 2030 XXXX 2039 XXXX XXXX XXXX XXXX
+XXXX 2018 2019 201c 201d 2022 2013 2014 XXXX 2122 XXXX 203a XXXX XXXX XXXX XXXX
+00a0 0385 0386 00a3 00a4 00a5 00a6 00a7 00a8 00a9 XXXX 00ab 00ac 00ad 00ae 2015
+00b0 00b1 00b2 00b3 0384 00b5 00b6 00b7 0388 0389 038a 00bb 038c 00bd 038e 038f
+0390 0391 0392 0393 0394 0395 0396 0397 0398 0399 039a 039b 039c 039d 039e 039f
+03a0 03a1 XXXX 03a3 03a4 03a5 03a6 03a7 03a8 03a9 03aa 03ab 03ac 03ad 03ae 03af
+03b0 03b1 03b2 03b3 03b4 03b5 03b6 03b7 03b8 03b9 03ba 03bb 03bc 03bd 03be 03bf
+03c0 03c1 03c2 03c3 03c4 03c5 03c6 03c7 03c8 03c9 03ca 03cb 03cc 03cd 03ce XXXX
+
+charset CS_CP1254
+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
+20ac XXXX 201a 0192 201e 2026 2020 2021 02c6 2030 0160 2039 0152 XXXX XXXX XXXX
+XXXX 2018 2019 201c 201d 2022 2013 2014 02dc 2122 0161 203a 0153 XXXX XXXX 0178
+00a0 00a1 00a2 00a3 00a4 00a5 00a6 00a7 00a8 00a9 00aa 00ab 00ac 00ad 00ae 00af
+00b0 00b1 00b2 00b3 00b4 00b5 00b6 00b7 00b8 00b9 00ba 00bb 00bc 00bd 00be 00bf
+00c0 00c1 00c2 00c3 00c4 00c5 00c6 00c7 00c8 00c9 00ca 00cb 00cc 00cd 00ce 00cf
+011e 00d1 00d2 00d3 00d4 00d5 00d6 00d7 00d8 00d9 00da 00db 00dc 0130 015e 00df
+00e0 00e1 00e2 00e3 00e4 00e5 00e6 00e7 00e8 00e9 00ea 00eb 00ec 00ed 00ee 00ef
+011f 00f1 00f2 00f3 00f4 00f5 00f6 00f7 00f8 00f9 00fa 00fb 00fc 0131 015f 00ff
+
+charset CS_CP1255
+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
+20ac XXXX 201a 0192 201e 2026 2020 2021 02c6 2030 XXXX 2039 XXXX XXXX XXXX XXXX
+XXXX 2018 2019 201c 201d 2022 2013 2014 02dc 2122 XXXX 203a XXXX XXXX XXXX XXXX
+00a0 00a1 00a2 00a3 20aa 00a5 00a6 00a7 00a8 00a9 00d7 00ab 00ac 00ad 00ae 00af
+00b0 00b1 00b2 00b3 00b4 00b5 00b6 00b7 00b8 00b9 00f7 00bb 00bc 00bd 00be 00bf
+05b0 05b1 05b2 05b3 05b4 05b5 05b6 05b7 05b8 05b9 XXXX 05bb 05bc 05bd 05be 05bf
+05c0 05c1 05c2 05c3 05f0 05f1 05f2 05f3 05f4 XXXX XXXX XXXX XXXX XXXX XXXX XXXX
+05d0 05d1 05d2 05d3 05d4 05d5 05d6 05d7 05d8 05d9 05da 05db 05dc 05dd 05de 05df
+05e0 05e1 05e2 05e3 05e4 05e5 05e6 05e7 05e8 05e9 05ea XXXX XXXX 200e 200f XXXX
+
+charset CS_CP1256
+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
+20ac 067e 201a 0192 201e 2026 2020 2021 02c6 2030 0679 2039 0152 0686 0698 0688
+06af 2018 2019 201c 201d 2022 2013 2014 06a9 2122 0691 203a 0153 200c 200d 06ba
+00a0 060c 00a2 00a3 00a4 00a5 00a6 00a7 00a8 00a9 06be 00ab 00ac 00ad 00ae 00af
+00b0 00b1 00b2 00b3 00b4 00b5 00b6 00b7 00b8 00b9 061b 00bb 00bc 00bd 00be 061f
+06c1 0621 0622 0623 0624 0625 0626 0627 0628 0629 062a 062b 062c 062d 062e 062f
+0630 0631 0632 0633 0634 0635 0636 00d7 0637 0638 0639 063a 0640 0641 0642 0643
+00e0 0644 00e2 0645 0646 0647 0648 00e7 00e8 00e9 00ea 00eb 0649 064a 00ee 00ef
+064b 064c 064d 064e 00f4 064f 0650 00f7 0651 00f9 0652 00fb 00fc 200e 200f 06d2
+
+charset CS_CP1257
+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
+20ac XXXX 201a XXXX 201e 2026 2020 2021 XXXX 2030 XXXX 2039 XXXX 00a8 02c7 00b8
+XXXX 2018 2019 201c 201d 2022 2013 2014 XXXX 2122 XXXX 203a XXXX 00af 02db XXXX
+00a0 XXXX 00a2 00a3 00a4 XXXX 00a6 00a7 00d8 00a9 0156 00ab 00ac 00ad 00ae 00c6
+00b0 00b1 00b2 00b3 00b4 00b5 00b6 00b7 00f8 00b9 0157 00bb 00bc 00bd 00be 00e6
+0104 012e 0100 0106 00c4 00c5 0118 0112 010c 00c9 0179 0116 0122 0136 012a 013b
+0160 0143 0145 00d3 014c 00d5 00d6 00d7 0172 0141 015a 016a 00dc 017b 017d 00df
+0105 012f 0101 0107 00e4 00e5 0119 0113 010d 00e9 017a 0117 0123 0137 012b 013c
+0161 0144 0146 00f3 014d 00f5 00f6 00f7 0173 0142 015b 016b 00fc 017c 017e 02d9
+
+charset CS_CP1258
+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
+20ac XXXX 201a 0192 201e 2026 2020 2021 02c6 2030 XXXX 2039 0152 XXXX XXXX XXXX
+XXXX 2018 2019 201c 201d 2022 2013 2014 02dc 2122 XXXX 203a 0153 XXXX XXXX 0178
+00a0 00a1 00a2 00a3 00a4 00a5 00a6 00a7 00a8 00a9 00aa 00ab 00ac 00ad 00ae 00af
+00b0 00b1 00b2 00b3 00b4 00b5 00b6 00b7 00b8 00b9 00ba 00bb 00bc 00bd 00be 00bf
+00c0 00c1 00c2 0102 00c4 00c5 00c6 00c7 00c8 00c9 00ca 00cb 0300 00cd 00ce 00cf
+0110 00d1 0309 00d3 00d4 01a0 00d6 00d7 00d8 00d9 00da 00db 00dc 01af 0303 00df
+00e0 00e1 00e2 0103 00e4 00e5 00e6 00e7 00e8 00e9 00ea 00eb 0301 00ed 00ee 00ef
+0111 00f1 0323 00f3 00f4 01a1 00f6 00f7 00f8 00f9 00fa 00fb 00fc 01b0 20ab 00ff
+
+ KOI8-R, generated by this code:
+
+ { echo charset CS_KOI8_R;
+ gensbcs http://www.unicode.org/Public/MAPPINGS/VENDORS/MISC/KOI8-R.TXT; }
+
+charset CS_KOI8_R
+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
+2500 2502 250c 2510 2514 2518 251c 2524 252c 2534 253c 2580 2584 2588 258c 2590
+2591 2592 2593 2320 25a0 2219 221a 2248 2264 2265 00a0 2321 00b0 00b2 00b7 00f7
+2550 2551 2552 0451 2553 2554 2555 2556 2557 2558 2559 255a 255b 255c 255d 255e
+255f 2560 2561 0401 2562 2563 2564 2565 2566 2567 2568 2569 256a 256b 256c 00a9
+044e 0430 0431 0446 0434 0435 0444 0433 0445 0438 0439 043a 043b 043c 043d 043e
+043f 044f 0440 0441 0442 0443 0436 0432 044c 044b 0437 0448 044d 0449 0447 044a
+042e 0410 0411 0426 0414 0415 0424 0413 0425 0418 0419 041a 041b 041c 041d 041e
+041f 042f 0420 0421 0422 0423 0416 0412 042c 042b 0417 0428 042d 0429 0427 042a
+
+ KOI8-U: I can't find an easily machine-processable mapping table
+ for this one, so I've created it by hand-editing the KOI8-R
+ mapping table in accordance with the list of differences specified
+ in RFC2319. Note that RFC2319 has an apparent error: position B4
+ is listed as U+0404 in the main character set list, but as U+0403
+ in Appendix A (differences from KOI8-R). Both agree that it should
+ be CYRILLIC CAPITAL LETTER UKRAINIAN IE, however, and the Unicode
+ character database says that therefore U+0404 is the correct value.
+
+charset CS_KOI8_U
+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
+2500 2502 250c 2510 2514 2518 251c 2524 252c 2534 253c 2580 2584 2588 258c 2590
+2591 2592 2593 2320 25a0 2219 221a 2248 2264 2265 00a0 2321 00b0 00b2 00b7 00f7
+2550 2551 2552 0451 0454 2554 0456 0457 2557 2558 2559 255a 255b 0491 255d 255e
+255f 2560 2561 0401 0404 2563 0406 0407 2566 2567 2568 2569 256a 0490 256c 00a9
+044e 0430 0431 0446 0434 0435 0444 0433 0445 0438 0439 043a 043b 043c 043d 043e
+043f 044f 0440 0441 0442 0443 0436 0432 044c 044b 0437 0448 044d 0449 0447 044a
+042e 0410 0411 0426 0414 0415 0424 0413 0425 0418 0419 041a 041b 041c 041d 041e
+041f 042f 0420 0421 0422 0423 0416 0412 042c 042b 0417 0428 042d 0429 0427 042a
+
+ Various Mac character sets, generated by:
+
+ for i in ROMAN TURKISH CROATIAN ICELAND ROMANIAN GREEK CYRILLIC THAI \
+ CENTEURO SYMBOL DINGBATS; do
+ echo charset CS_MAC_$i
+ gensbcs http://www.unicode.org/Public/MAPPINGS/VENDORS/APPLE/$i.TXT | \
+ sed s/f8a0/XXXX/
+ echo
+ done
+
+ The code point F8FF at position F0 in Mac OS Roman an interesting
+ one. In Unicode, it's the last of the Private Use section. The
+ mapping table states that it should be an Apple logo. I suppose we
+ should just leave it as it is; there's bound to be some software out
+ there that understands U+F8FF to be an Apple logo!
+
+ Code point F8A0 at position F5 in Mac OS Turkish is actually just an
+ undefined character, so we make it properly undefined.
+
+ Many of the positions 80-9F in Mac OS Thai are for presentation
+ forms of other characters. When converting from Unicode, we use
+ `sortpriority' to avoid them.
+
+ Positions E2-E4 in Mac OS Symbol are for sans-serif variants of
+ other characters. Similarly, we avoid them.
+
+charset CS_MAC_ROMAN
+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
+00c4 00c5 00c7 00c9 00d1 00d6 00dc 00e1 00e0 00e2 00e4 00e3 00e5 00e7 00e9 00e8
+00ea 00eb 00ed 00ec 00ee 00ef 00f1 00f3 00f2 00f4 00f6 00f5 00fa 00f9 00fb 00fc
+2020 00b0 00a2 00a3 00a7 2022 00b6 00df 00ae 00a9 2122 00b4 00a8 2260 00c6 00d8
+221e 00b1 2264 2265 00a5 00b5 2202 2211 220f 03c0 222b 00aa 00ba 03a9 00e6 00f8
+00bf 00a1 00ac 221a 0192 2248 2206 00ab 00bb 2026 00a0 00c0 00c3 00d5 0152 0153
+2013 2014 201c 201d 2018 2019 00f7 25ca 00ff 0178 2044 20ac 2039 203a fb01 fb02
+2021 00b7 201a 201e 2030 00c2 00ca 00c1 00cb 00c8 00cd 00ce 00cf 00cc 00d3 00d4
+f8ff 00d2 00da 00db 00d9 0131 02c6 02dc 00af 02d8 02d9 02da 00b8 02dd 02db 02c7
+
+charset CS_MAC_TURKISH
+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
+00c4 00c5 00c7 00c9 00d1 00d6 00dc 00e1 00e0 00e2 00e4 00e3 00e5 00e7 00e9 00e8
+00ea 00eb 00ed 00ec 00ee 00ef 00f1 00f3 00f2 00f4 00f6 00f5 00fa 00f9 00fb 00fc
+2020 00b0 00a2 00a3 00a7 2022 00b6 00df 00ae 00a9 2122 00b4 00a8 2260 00c6 00d8
+221e 00b1 2264 2265 00a5 00b5 2202 2211 220f 03c0 222b 00aa 00ba 03a9 00e6 00f8
+00bf 00a1 00ac 221a 0192 2248 2206 00ab 00bb 2026 00a0 00c0 00c3 00d5 0152 0153
+2013 2014 201c 201d 2018 2019 00f7 25ca 00ff 0178 011e 011f 0130 0131 015e 015f
+2021 00b7 201a 201e 2030 00c2 00ca 00c1 00cb 00c8 00cd 00ce 00cf 00cc 00d3 00d4
+f8ff 00d2 00da 00db 00d9 XXXX 02c6 02dc 00af 02d8 02d9 02da 00b8 02dd 02db 02c7
+
+charset CS_MAC_CROATIAN
+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
+00c4 00c5 00c7 00c9 00d1 00d6 00dc 00e1 00e0 00e2 00e4 00e3 00e5 00e7 00e9 00e8
+00ea 00eb 00ed 00ec 00ee 00ef 00f1 00f3 00f2 00f4 00f6 00f5 00fa 00f9 00fb 00fc
+2020 00b0 00a2 00a3 00a7 2022 00b6 00df 00ae 0160 2122 00b4 00a8 2260 017d 00d8
+221e 00b1 2264 2265 2206 00b5 2202 2211 220f 0161 222b 00aa 00ba 03a9 017e 00f8
+00bf 00a1 00ac 221a 0192 2248 0106 00ab 010c 2026 00a0 00c0 00c3 00d5 0152 0153
+0110 2014 201c 201d 2018 2019 00f7 25ca f8ff 00a9 2044 20ac 2039 203a 00c6 00bb
+2013 00b7 201a 201e 2030 00c2 0107 00c1 010d 00c8 00cd 00ce 00cf 00cc 00d3 00d4
+0111 00d2 00da 00db 00d9 0131 02c6 02dc 00af 03c0 00cb 02da 00b8 00ca 00e6 02c7
+
+charset CS_MAC_ICELAND
+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
+00c4 00c5 00c7 00c9 00d1 00d6 00dc 00e1 00e0 00e2 00e4 00e3 00e5 00e7 00e9 00e8
+00ea 00eb 00ed 00ec 00ee 00ef 00f1 00f3 00f2 00f4 00f6 00f5 00fa 00f9 00fb 00fc
+00dd 00b0 00a2 00a3 00a7 2022 00b6 00df 00ae 00a9 2122 00b4 00a8 2260 00c6 00d8
+221e 00b1 2264 2265 00a5 00b5 2202 2211 220f 03c0 222b 00aa 00ba 03a9 00e6 00f8
+00bf 00a1 00ac 221a 0192 2248 2206 00ab 00bb 2026 00a0 00c0 00c3 00d5 0152 0153
+2013 2014 201c 201d 2018 2019 00f7 25ca 00ff 0178 2044 20ac 00d0 00f0 00de 00fe
+00fd 00b7 201a 201e 2030 00c2 00ca 00c1 00cb 00c8 00cd 00ce 00cf 00cc 00d3 00d4
+f8ff 00d2 00da 00db 00d9 0131 02c6 02dc 00af 02d8 02d9 02da 00b8 02dd 02db 02c7
+
+charset CS_MAC_ROMANIAN
+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
+00c4 00c5 00c7 00c9 00d1 00d6 00dc 00e1 00e0 00e2 00e4 00e3 00e5 00e7 00e9 00e8
+00ea 00eb 00ed 00ec 00ee 00ef 00f1 00f3 00f2 00f4 00f6 00f5 00fa 00f9 00fb 00fc
+2020 00b0 00a2 00a3 00a7 2022 00b6 00df 00ae 00a9 2122 00b4 00a8 2260 0102 0218
+221e 00b1 2264 2265 00a5 00b5 2202 2211 220f 03c0 222b 00aa 00ba 03a9 0103 0219
+00bf 00a1 00ac 221a 0192 2248 2206 00ab 00bb 2026 00a0 00c0 00c3 00d5 0152 0153
+2013 2014 201c 201d 2018 2019 00f7 25ca 00ff 0178 2044 20ac 2039 203a 021a 021b
+2021 00b7 201a 201e 2030 00c2 00ca 00c1 00cb 00c8 00cd 00ce 00cf 00cc 00d3 00d4
+f8ff 00d2 00da 00db 00d9 0131 02c6 02dc 00af 02d8 02d9 02da 00b8 02dd 02db 02c7
+
+charset CS_MAC_GREEK
+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
+00c4 00b9 00b2 00c9 00b3 00d6 00dc 0385 00e0 00e2 00e4 0384 00a8 00e7 00e9 00e8
+00ea 00eb 00a3 2122 00ee 00ef 2022 00bd 2030 00f4 00f6 00a6 20ac 00f9 00fb 00fc
+2020 0393 0394 0398 039b 039e 03a0 00df 00ae 00a9 03a3 03aa 00a7 2260 00b0 00b7
+0391 00b1 2264 2265 00a5 0392 0395 0396 0397 0399 039a 039c 03a6 03ab 03a8 03a9
+03ac 039d 00ac 039f 03a1 2248 03a4 00ab 00bb 2026 00a0 03a5 03a7 0386 0388 0153
+2013 2015 201c 201d 2018 2019 00f7 0389 038a 038c 038e 03ad 03ae 03af 03cc 038f
+03cd 03b1 03b2 03c8 03b4 03b5 03c6 03b3 03b7 03b9 03be 03ba 03bb 03bc 03bd 03bf
+03c0 03ce 03c1 03c3 03c4 03b8 03c9 03c2 03c7 03c5 03b6 03ca 03cb 0390 03b0 00ad
+
+charset CS_MAC_CYRILLIC
+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
+0410 0411 0412 0413 0414 0415 0416 0417 0418 0419 041a 041b 041c 041d 041e 041f
+0420 0421 0422 0423 0424 0425 0426 0427 0428 0429 042a 042b 042c 042d 042e 042f
+2020 00b0 0490 00a3 00a7 2022 00b6 0406 00ae 00a9 2122 0402 0452 2260 0403 0453
+221e 00b1 2264 2265 0456 00b5 0491 0408 0404 0454 0407 0457 0409 0459 040a 045a
+0458 0405 00ac 221a 0192 2248 2206 00ab 00bb 2026 00a0 040b 045b 040c 045c 0455
+2013 2014 201c 201d 2018 2019 00f7 201e 040e 045e 040f 045f 2116 0401 0451 044f
+0430 0431 0432 0433 0434 0435 0436 0437 0438 0439 043a 043b 043c 043d 043e 043f
+0440 0441 0442 0443 0444 0445 0446 0447 0448 0449 044a 044b 044c 044d 044e 20ac
+
+charset CS_MAC_THAI
+sortpriority 83-8C -1
+sortpriority 8F-8F -1
+sortpriority 92-9C -1
+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
+00ab 00bb 2026 0e48 0e49 0e4a 0e4b 0e4c 0e48 0e49 0e4a 0e4b 0e4c 201c 201d 0e4d
+XXXX 2022 0e31 0e47 0e34 0e35 0e36 0e37 0e48 0e49 0e4a 0e4b 0e4c 2018 2019 XXXX
+00a0 0e01 0e02 0e03 0e04 0e05 0e06 0e07 0e08 0e09 0e0a 0e0b 0e0c 0e0d 0e0e 0e0f
+0e10 0e11 0e12 0e13 0e14 0e15 0e16 0e17 0e18 0e19 0e1a 0e1b 0e1c 0e1d 0e1e 0e1f
+0e20 0e21 0e22 0e23 0e24 0e25 0e26 0e27 0e28 0e29 0e2a 0e2b 0e2c 0e2d 0e2e 0e2f
+0e30 0e31 0e32 0e33 0e34 0e35 0e36 0e37 0e38 0e39 0e3a 2060 200b 2013 2014 0e3f
+0e40 0e41 0e42 0e43 0e44 0e45 0e46 0e47 0e48 0e49 0e4a 0e4b 0e4c 0e4d 2122 0e4f
+0e50 0e51 0e52 0e53 0e54 0e55 0e56 0e57 0e58 0e59 00ae 00a9 XXXX XXXX XXXX XXXX
+
+charset CS_MAC_CENTEURO
+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
+00c4 0100 0101 00c9 0104 00d6 00dc 00e1 0105 010c 00e4 010d 0106 0107 00e9 0179
+017a 010e 00ed 010f 0112 0113 0116 00f3 0117 00f4 00f6 00f5 00fa 011a 011b 00fc
+2020 00b0 0118 00a3 00a7 2022 00b6 00df 00ae 00a9 2122 0119 00a8 2260 0123 012e
+012f 012a 2264 2265 012b 0136 2202 2211 0142 013b 013c 013d 013e 0139 013a 0145
+0146 0143 00ac 221a 0144 0147 2206 00ab 00bb 2026 00a0 0148 0150 00d5 0151 014c
+2013 2014 201c 201d 2018 2019 00f7 25ca 014d 0154 0155 0158 2039 203a 0159 0156
+0157 0160 201a 201e 0161 015a 015b 00c1 0164 0165 00cd 017d 017e 016a 00d3 00d4
+016b 016e 00da 016f 0170 0171 0172 0173 00dd 00fd 0137 017b 0141 017c 0122 02c7
+
+charset CS_MAC_SYMBOL
+sortpriority E2-E4 -1
+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 2200 0023 2203 0025 0026 220d 0028 0029 2217 002b 002c 2212 002e 002f
+0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f
+2245 0391 0392 03a7 0394 0395 03a6 0393 0397 0399 03d1 039a 039b 039c 039d 039f
+03a0 0398 03a1 03a3 03a4 03a5 03c2 03a9 039e 03a8 0396 005b 2234 005d 22a5 005f
+f8e5 03b1 03b2 03c7 03b4 03b5 03c6 03b3 03b7 03b9 03d5 03ba 03bb 03bc 03bd 03bf
+03c0 03b8 03c1 03c3 03c4 03c5 03d6 03c9 03be 03c8 03b6 007b 007c 007d 223c 007f
+XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX
+XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX
+20ac 03d2 2032 2264 2044 221e 0192 2663 2666 2665 2660 2194 2190 2191 2192 2193
+00b0 00b1 2033 2265 00d7 221d 2202 2022 00f7 2260 2261 2248 2026 f8e6 23af 21b5
+2135 2111 211c 2118 2297 2295 2205 2229 222a 2283 2287 2284 2282 2286 2208 2209
+2220 2207 00ae 00a9 2122 220f 221a 22c5 00ac 2227 2228 21d4 21d0 21d1 21d2 21d3
+22c4 3008 00ae 00a9 2122 2211 239b 239c 239d 23a1 23a2 23a3 23a7 23a8 23a9 23aa
+f8ff 3009 222b 2320 23ae 2321 239e 239f 23a0 23a4 23a5 23a6 23ab 23ac 23ad XXXX
+
+charset CS_MAC_DINGBATS
+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 2701 2702 2703 2704 260e 2706 2707 2708 2709 261b 261e 270c 270d 270e 270f
+2710 2711 2712 2713 2714 2715 2716 2717 2718 2719 271a 271b 271c 271d 271e 271f
+2720 2721 2722 2723 2724 2725 2726 2727 2605 2729 272a 272b 272c 272d 272e 272f
+2730 2731 2732 2733 2734 2735 2736 2737 2738 2739 273a 273b 273c 273d 273e 273f
+2740 2741 2742 2743 2744 2745 2746 2747 2748 2749 274a 274b 25cf 274d 25a0 274f
+2750 2751 2752 25b2 25bc 25c6 2756 25d7 2758 2759 275a 275b 275c 275d 275e 007f
+2768 2769 276a 276b 276c 276d 276e 276f 2770 2771 2772 2773 2774 2775 XXXX XXXX
+XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX
+XXXX 2761 2762 2763 2764 2765 2766 2767 2663 2666 2665 2660 2460 2461 2462 2463
+2464 2465 2466 2467 2468 2469 2776 2777 2778 2779 277a 277b 277c 277d 277e 277f
+2780 2781 2782 2783 2784 2785 2786 2787 2788 2789 278a 278b 278c 278d 278e 278f
+2790 2791 2792 2793 2794 2192 2194 2195 2798 2799 279a 279b 279c 279d 279e 279f
+27a0 27a1 27a2 27a3 27a4 27a5 27a6 27a7 27a8 27a9 27aa 27ab 27ac 27ad 27ae 27af
+XXXX 27b1 27b2 27b3 27b4 27b5 27b6 27b7 27b8 27b9 27ba 27bb 27bc 27bd 27be XXXX
+
+ Various Mac character sets have older (usually pre-Euro) variants
+ which are documented in the comments in their mapping tables. I've
+ manually applied these changes below.
+
+ Mac OS Roman variants before Mac OS 8.5 (CURRENCY SIGN rather than
+ EURO SIGN):
+
+charset CS_MAC_ROMAN_OLD
+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
+00c4 00c5 00c7 00c9 00d1 00d6 00dc 00e1 00e0 00e2 00e4 00e3 00e5 00e7 00e9 00e8
+00ea 00eb 00ed 00ec 00ee 00ef 00f1 00f3 00f2 00f4 00f6 00f5 00fa 00f9 00fb 00fc
+2020 00b0 00a2 00a3 00a7 2022 00b6 00df 00ae 00a9 2122 00b4 00a8 2260 00c6 00d8
+221e 00b1 2264 2265 00a5 00b5 2202 2211 220f 03c0 222b 00aa 00ba 03a9 00e6 00f8
+00bf 00a1 00ac 221a 0192 2248 2206 00ab 00bb 2026 00a0 00c0 00c3 00d5 0152 0153
+2013 2014 201c 201d 2018 2019 00f7 25ca 00ff 0178 2044 00a4 2039 203a fb01 fb02
+2021 00b7 201a 201e 2030 00c2 00ca 00c1 00cb 00c8 00cd 00ce 00cf 00cc 00d3 00d4
+f8ff 00d2 00da 00db 00d9 0131 02c6 02dc 00af 02d8 02d9 02da 00b8 02dd 02db 02c7
+
+charset CS_MAC_CROATIAN_OLD
+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
+00c4 00c5 00c7 00c9 00d1 00d6 00dc 00e1 00e0 00e2 00e4 00e3 00e5 00e7 00e9 00e8
+00ea 00eb 00ed 00ec 00ee 00ef 00f1 00f3 00f2 00f4 00f6 00f5 00fa 00f9 00fb 00fc
+2020 00b0 00a2 00a3 00a7 2022 00b6 00df 00ae 0160 2122 00b4 00a8 2260 017d 00d8
+221e 00b1 2264 2265 2206 00b5 2202 2211 220f 0161 222b 00aa 00ba 03a9 017e 00f8
+00bf 00a1 00ac 221a 0192 2248 0106 00ab 010c 2026 00a0 00c0 00c3 00d5 0152 0153
+0110 2014 201c 201d 2018 2019 00f7 25ca f8ff 00a9 2044 00a4 2039 203a 00c6 00bb
+2013 00b7 201a 201e 2030 00c2 0107 00c1 010d 00c8 00cd 00ce 00cf 00cc 00d3 00d4
+0111 00d2 00da 00db 00d9 0131 02c6 02dc 00af 03c0 00cb 02da 00b8 00ca 00e6 02c7
+
+charset CS_MAC_ICELAND_OLD
+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
+00c4 00c5 00c7 00c9 00d1 00d6 00dc 00e1 00e0 00e2 00e4 00e3 00e5 00e7 00e9 00e8
+00ea 00eb 00ed 00ec 00ee 00ef 00f1 00f3 00f2 00f4 00f6 00f5 00fa 00f9 00fb 00fc
+00dd 00b0 00a2 00a3 00a7 2022 00b6 00df 00ae 00a9 2122 00b4 00a8 2260 00c6 00d8
+221e 00b1 2264 2265 00a5 00b5 2202 2211 220f 03c0 222b 00aa 00ba 03a9 00e6 00f8
+00bf 00a1 00ac 221a 0192 2248 2206 00ab 00bb 2026 00a0 00c0 00c3 00d5 0152 0153
+2013 2014 201c 201d 2018 2019 00f7 25ca 00ff 0178 2044 00a4 00d0 00f0 00de 00fe
+00fd 00b7 201a 201e 2030 00c2 00ca 00c1 00cb 00c8 00cd 00ce 00cf 00cc 00d3 00d4
+f8ff 00d2 00da 00db 00d9 0131 02c6 02dc 00af 02d8 02d9 02da 00b8 02dd 02db 02c7
+
+charset CS_MAC_ROMANIAN_OLD
+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
+00c4 00c5 00c7 00c9 00d1 00d6 00dc 00e1 00e0 00e2 00e4 00e3 00e5 00e7 00e9 00e8
+00ea 00eb 00ed 00ec 00ee 00ef 00f1 00f3 00f2 00f4 00f6 00f5 00fa 00f9 00fb 00fc
+2020 00b0 00a2 00a3 00a7 2022 00b6 00df 00ae 00a9 2122 00b4 00a8 2260 0102 0218
+221e 00b1 2264 2265 00a5 00b5 2202 2211 220f 03c0 222b 00aa 00ba 03a9 0103 0219
+00bf 00a1 00ac 221a 0192 2248 2206 00ab 00bb 2026 00a0 00c0 00c3 00d5 0152 0153
+2013 2014 201c 201d 2018 2019 00f7 25ca 00ff 0178 2044 00a4 2039 203a 021a 021b
+2021 00b7 201a 201e 2030 00c2 00ca 00c1 00cb 00c8 00cd 00ce 00cf 00cc 00d3 00d4
+f8ff 00d2 00da 00db 00d9 0131 02c6 02dc 00af 02d8 02d9 02da 00b8 02dd 02db 02c7
+
+ Mac OS Greek before Mac OS 9.2.2 (SOFT HYPHEN instead of EURO SIGN,
+ and undefined instead of SOFT HYPHEN).
+
+charset CS_MAC_GREEK_OLD
+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
+00c4 00b9 00b2 00c9 00b3 00d6 00dc 0385 00e0 00e2 00e4 0384 00a8 00e7 00e9 00e8
+00ea 00eb 00a3 2122 00ee 00ef 2022 00bd 2030 00f4 00f6 00a6 00ad 00f9 00fb 00fc
+2020 0393 0394 0398 039b 039e 03a0 00df 00ae 00a9 03a3 03aa 00a7 2260 00b0 00b7
+0391 00b1 2264 2265 00a5 0392 0395 0396 0397 0399 039a 039c 03a6 03ab 03a8 03a9
+03ac 039d 00ac 039f 03a1 2248 03a4 00ab 00bb 2026 00a0 03a5 03a7 0386 0388 0153
+2013 2015 201c 201d 2018 2019 00f7 0389 038a 038c 038e 03ad 03ae 03af 03cc 038f
+03cd 03b1 03b2 03c8 03b4 03b5 03c6 03b3 03b7 03b9 03be 03ba 03bb 03bc 03bd 03bf
+03c0 03ce 03c1 03c3 03c4 03b8 03c9 03c2 03c7 03c5 03b6 03ca 03cb 0390 03b0 XXXX
+
+ Mac OS Cyrillic before Mac OS 9.0 (CENT SIGN instead of CYRILLIC
+ CAPITAL LETTER GHE WITH UPTURN, PARTIAL DIFFERENTIAL instead of
+ CYRILLIC SMALL LETTER GHE WITH UPTURN, CURRENCY SIGN instead of EURO
+ SIGN):
+
+charset CS_MAC_CYRILLIC_OLD
+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
+0410 0411 0412 0413 0414 0415 0416 0417 0418 0419 041a 041b 041c 041d 041e 041f
+0420 0421 0422 0423 0424 0425 0426 0427 0428 0429 042a 042b 042c 042d 042e 042f
+2020 00b0 00a2 00a3 00a7 2022 00b6 0406 00ae 00a9 2122 0402 0452 2260 0403 0453
+221e 00b1 2264 2265 0456 00b5 2022 0408 0404 0454 0407 0457 0409 0459 040a 045a
+0458 0405 00ac 221a 0192 2248 2206 00ab 00bb 2026 00a0 040b 045b 040c 045c 0455
+2013 2014 201c 201d 2018 2019 00f7 201e 040e 045e 040f 045f 2116 0401 0451 044f
+0430 0431 0432 0433 0434 0435 0436 0437 0438 0439 043a 043b 043c 043d 043e 043f
+0440 0441 0442 0443 0444 0445 0446 0447 0448 0449 044a 044b 044c 044d 044e 00a4
+
+ Mac OS Ukrainian (now Cyrillic) before Mac OS 9.0 (CURRENCY SIGN
+ instead of EURO SIGN):
+
+charset CS_MAC_UKRAINE
+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
+0410 0411 0412 0413 0414 0415 0416 0417 0418 0419 041a 041b 041c 041d 041e 041f
+0420 0421 0422 0423 0424 0425 0426 0427 0428 0429 042a 042b 042c 042d 042e 042f
+2020 00b0 0490 00a3 00a7 2022 00b6 0406 00ae 00a9 2122 0402 0452 2260 0403 0453
+221e 00b1 2264 2265 0456 00b5 0491 0408 0404 0454 0407 0457 0409 0459 040a 045a
+0458 0405 00ac 221a 0192 2248 2206 00ab 00bb 2026 00a0 040b 045b 040c 045c 0455
+2013 2014 201c 201d 2018 2019 00f7 201e 040e 045e 040f 045f 2116 0401 0451 044f
+0430 0431 0432 0433 0434 0435 0436 0437 0438 0439 043a 043b 043c 043d 043e 043f
+0440 0441 0442 0443 0444 0445 0446 0447 0448 0449 044a 044b 044c 044d 044e 00a4
+
+ Mac OS VT100 character set, as used by the "VT100" font. Basically
+ Mac OS Roman hacked about to give it an almost-Latin1 repertoire and
+ most of the VT100 line-drawing set too.
+
+ Point CA is the backward question-mark used for silo overflows.
+
+ This table was derived by pasting the relevant part of 'utom' 140
+ from the "Western Language Encodings" file shipped with TEC 1.5 and
+ then manually fixing up the scan line characters to use the Unicode
+ 3.2 HORIZONTAL SCAN LINE characters rather than UPPER ONE EIGHTH
+ BLOCK and LOWER ONE EIGHTH BLOCK with transcoding hints.
+
+charset CS_MAC_VT100
+2400 2401 2402 2403 2404 2405 2406 2407 2408 2409 240a 240b 240c 240d 240e 240f
+2410 2411 2412 2413 2414 2415 2416 2417 2418 2419 241a 241b 241c 241d 241e 241f
+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 2421
+00c4 00c5 00c7 00c9 00d1 00d6 00dc 00e1 00e0 00e2 00e4 00e3 00e5 00e7 00e9 00e8
+00ea 00eb 00ed 00ec 00ee 00ef 00f1 00f3 00f2 00f4 00f6 00f5 00fa 00f9 00fb 00fc
+00dd 00b0 00a2 00a3 00a7 00b8 00b6 00df 00ae 00a9 2122 00b4 00a8 2260 00c6 00d8
+00d7 00b1 2264 2265 00a5 00b5 00b9 00b2 00b3 03c0 00a6 00aa 00ba 2592 00e6 00f8
+00bf 00a1 00ac 00bd 0192 00bc 00be 00ab 00bb 2026 XXXX 00c0 00c3 00d5 0152 0153
+2013 2014 2518 2510 250c 2514 00f7 2022 00ff 0178 253c 20ac 00d0 00f0 00fe 00de
+00fd 00b7 23ba 23bb 2500 00c2 00ca 00c1 00cb 00c8 00cd 00ce 00cf 00cc 00d3 00d4
+XXXX 00d2 00da 00db 00d9 23bc 23bd 251c 2524 2534 252c 2502 XXXX XXXX XXXX XXXX
+
+ As with so many others, before Mac OS 8.5 this font had CURRENCY
+ SIGN rather than EURO SIGN.
+
+charset CS_MAC_VT100_OLD
+2400 2401 2402 2403 2404 2405 2406 2407 2408 2409 240a 240b 240c 240d 240e 240f
+2410 2411 2412 2413 2414 2415 2416 2417 2418 2419 241a 241b 241c 241d 241e 241f
+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 2421
+00c4 00c5 00c7 00c9 00d1 00d6 00dc 00e1 00e0 00e2 00e4 00e3 00e5 00e7 00e9 00e8
+00ea 00eb 00ed 00ec 00ee 00ef 00f1 00f3 00f2 00f4 00f6 00f5 00fa 00f9 00fb 00fc
+00dd 00b0 00a2 00a3 00a7 00b8 00b6 00df 00ae 00a9 2122 00b4 00a8 2260 00c6 00d8
+00d7 00b1 2264 2265 00a5 00b5 00b9 00b2 00b3 03c0 00a6 00aa 00ba 2592 00e6 00f8
+00bf 00a1 00ac 00bd 0192 00bc 00be 00ab 00bb 2026 XXXX 00c0 00c3 00d5 0152 0153
+2013 2014 2518 2510 250c 2514 00f7 2022 00ff 0178 253c 00a4 00d0 00f0 00fe 00de
+00fd 00b7 23ba 23bb 2500 00c2 00ca 00c1 00cb 00c8 00cd 00ce 00cf 00cc 00d3 00d4
+XXXX 00d2 00da 00db 00d9 23bc 23bd 251c 2524 2534 252c 2502 XXXX XXXX XXXX XXXX
+
+ Roman Czyborra's web site (http://czyborra.com/) has a variety of
+ other useful mapping tables, in a slightly different format (and
+ gzipped). Here's a shell/Perl function to generate an SBCS table
+ from a Czyborra mapping table:
+
+ gensbcs_c() {
+ wget -q -O - "$1" | gzip -d | \
+ perl -ne '/^=(.*)\s+U\+(.*)\s+/ and $a[hex $1]=sprintf "%04x", hex $2;' \
+ -e 'BEGIN{for($i=0;$i<256;$i++){$a[$i]="XXXX";' \
+ -e 'if ($i < 32 or ($i >=127 and $i < 160)) {$a[$i]=sprintf "%04x", $i}}}' \
+ -e 'END{for($i=0;$i<256;$i++){printf"%s%s",$a[$i],$i%16==15?"\n":" "}}'
+ }
+
+ So here we have some character sets generated from Czyborra
+ mapping tables: VISCII, HP-Roman8, and the DEC Multinational
+ Character Set.
+
+ { echo charset CS_VISCII;
+ gensbcs_c http://czyborra.com/charsets/viscii.txt.gz; echo;
+ echo charset CS_HP_ROMAN8;
+ gensbcs_c http://czyborra.com/charsets/hp-roman8.txt.gz; echo;
+ echo charset CS_DEC_MCS;
+ gensbcs_c http://czyborra.com/charsets/dec-mcs.txt.gz; echo; }
+
+charset CS_VISCII
+0000 0001 1eb2 0003 0004 1eb4 1eaa 0007 0008 0009 000a 000b 000c 000d 000e 000f
+0010 0011 0012 0013 1ef6 0015 0016 0017 0018 1ef8 001a 001b 001c 001d 1ef4 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
+1ea0 1eae 1eb0 1eb6 1ea4 1ea6 1ea8 1eac 1ebc 1eb8 1ebe 1ec0 1ec2 1ec4 1ec6 1ed0
+1ed2 1ed4 1ed6 1ed8 1ee2 1eda 1edc 1ede 1eca 1ece 1ecc 1ec8 1ee6 0168 1ee4 1ef2
+00d5 1eaf 1eb1 1eb7 1ea5 1ea7 1ea8 1ead 1ebd 1eb9 1ebf 1ec1 1ec3 1ec5 1ec7 1ed1
+1ed3 1ed5 1ed7 1ee0 01a0 1ed9 1edd 1edf 1ecb 1ef0 1ee8 1eea 1eec 01a1 1edb 01af
+00c0 00c1 00c2 00c3 1ea2 0102 1eb3 1eb5 00c8 00c9 00ca 1eba 00cc 00cd 0128 1ef3
+0110 1ee9 00d2 00d3 00d4 1ea1 1ef7 1eeb 1eed 00d9 00da 1ef9 1ef5 00dd 1ee1 01b0
+00e0 00e1 00e2 00e3 1ea3 0103 1eef 1eab 00e8 00e9 00ea 1ebb 00ec 00ed 0129 1ec9
+0111 1ef1 00f2 00f3 00f4 00f5 1ecf 1ecd 1ee5 00f9 00fa 0169 1ee7 00fd 1ee3 1eee
+
+charset CS_HP_ROMAN8
+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
+0080 0081 0082 0083 0084 0085 0086 0087 0088 0089 008a 008b 008c 008d 008e 008f
+0090 0091 0092 0093 0094 0095 0096 0097 0098 0099 009a 009b 009c 009d 009e 009f
+00a0 00c0 00c2 00c8 00ca 00cb 00ce 00cf 00b4 02cb 02c6 00a8 02dc 00d9 00db 20a4
+00af 00dd 00fd 00b0 00c7 00e7 00d1 00f1 00a1 00bf 00a4 00a3 00a5 00a7 0192 00a2
+00e2 00ea 00f4 00fb 00e1 00e9 00f3 00fa 00e0 00e8 00f2 00f9 00e4 00eb 00f6 00fc
+00c5 00ee 00d8 00c6 00e5 00ed 00f8 00e6 00c4 00ec 00d6 00dc 00c9 00ef 00df 00d4
+00c1 00c3 00e3 00d0 00f0 00cd 00cc 00d3 00d2 00d5 00f5 0160 0161 00da 0178 00ff
+00de 00fe 00b7 00b5 00b6 00be 2014 00bc 00bd 00aa 00ba 00ab 25a0 00bb 00b1 XXXX
+
+charset CS_DEC_MCS
+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
+0080 0081 0082 0083 0084 0085 0086 0087 0088 0089 008a 008b 008c 008d 008e 008f
+0090 0091 0092 0093 0094 0095 0096 0097 0098 0099 009a 009b 009c 009d 009e 009f
+XXXX 00a1 00a2 00a3 XXXX 00a5 XXXX 00a7 00a4 00a9 00aa 00ab XXXX XXXX XXXX XXXX
+00b0 00b1 00b2 00b3 XXXX 00b5 00b6 00b7 XXXX 00b9 00ba 00bb 00bc 00bd XXXX 00bf
+00c0 00c1 00c2 00c3 00c4 00c5 00c6 00c7 00c8 00c9 00ca 00cb 00cc 00cd 00ce 00cf
+XXXX 00d1 00d2 00d3 00d4 00d5 00d6 0152 00d8 00d9 00da 00db 00dc 0178 XXXX 00df
+00e0 00e1 00e2 00e3 00e4 00e5 00e6 00e7 00e8 00e9 00ea 00eb 00ec 00ed 00ee 00ef
+XXXX 00f1 00f2 00f3 00f4 00f5 00f6 0153 00f8 00f9 00fa 00fb 00fc 00ff XXXX XXXX
--- /dev/null
+#!/usr/bin/env perl -w
+
+# This script generates sbcsdat.c (the data for all the SBCSes) from its
+# source form sbcs.dat.
+
+$infile = "sbcs.dat";
+$outfile = "sbcsdat.c";
+
+open FOO, $infile;
+open BAR, ">$outfile";
+select BAR;
+
+print "/*\n";
+print " * sbcsdat.c - data definitions for single-byte character sets.\n";
+print " *\n";
+print " * Generated by sbcsgen.pl from sbcs.dat.\n";
+print " * You should edit those files rather than editing this one.\n";
+print " */\n";
+print "\n";
+print "#ifndef ENUM_CHARSETS\n";
+print "\n";
+print "#include \"charset.h\"\n";
+print "#include \"internal.h\"\n";
+print "\n";
+
+my $charsetname = undef;
+my @vals = ();
+
+my @charsetnames = ();
+my @sortpriority = ();
+
+while (<FOO>) {
+ chomp;
+ if (/^charset (.*)$/) {
+ $charsetname = $1;
+ @vals = ();
+ @sortpriority = map { 0 } 0..255;
+ } elsif (/^sortpriority ([^-]*)-([^-]*) (.*)$/) {
+ for ($i = hex $1; $i <= hex $2; $i++) {
+ $sortpriority[$i] += $3;
+ }
+ } elsif (/^[0-9a-fA-FX]/) {
+ push @vals, map { $_ eq "XXXX" ? -1 : hex $_ } split / +/, $_;
+ if (scalar @vals > 256) {
+ die "$infile:$.: charset $charsetname has more than 256 values\n";
+ } elsif (scalar @vals == 256) {
+ &outcharset($charsetname, \@vals, \@sortpriority);
+ push @charsetnames, $charsetname;
+ $charsetname = undef;
+ @vals = ();
+ @sortpriority = map { 0 } 0..255;
+ }
+ }
+}
+
+print "#else /* ENUM_CHARSETS */\n";
+print "\n";
+
+foreach $i (@charsetnames) {
+ print "ENUM_CHARSET($i)\n";
+}
+
+print "\n";
+print "#endif /* ENUM_CHARSETS */\n";
+
+sub outcharset($$$) {
+ my ($name, $vals, $sortpriority) = @_;
+ my ($prefix, $i, @sorted);
+
+ print "static const sbcs_data data_$name = {\n";
+ print " {\n";
+ $prefix = " ";
+ @sorted = ();
+ for ($i = 0; $i < 256; $i++) {
+ if ($vals->[$i] < 0) {
+ printf "%sERROR ", $prefix;
+ } else {
+ printf "%s0x%04x", $prefix, $vals->[$i];
+ die "ooh? $i\n" unless defined $sortpriority->[$i];
+ push @sorted, [$i, $vals->[$i], 0+$sortpriority->[$i]];
+ }
+ if ($i % 8 == 7) {
+ $prefix = ",\n ";
+ } else {
+ $prefix = ", ";
+ }
+ }
+ print "\n },\n {\n";
+ @sorted = sort { ($a->[1] == $b->[1] ?
+ $b->[2] <=> $a->[2] :
+ $a->[1] <=> $b->[1]) ||
+ $a->[0] <=> $b->[0] } @sorted;
+ $prefix = " ";
+ $uval = -1;
+ for ($i = $j = 0; $i < scalar @sorted; $i++) {
+ next if ($uval == $sorted[$i]->[1]); # low-priority alternative
+ $uval = $sorted[$i]->[1];
+ printf "%s0x%02x", $prefix, $sorted[$i]->[0];
+ if ($j % 8 == 7) {
+ $prefix = ",\n ";
+ } else {
+ $prefix = ", ";
+ }
+ $j++;
+ }
+ printf "\n },\n %d\n", $j;
+ print "};\n";
+ print "const charset_spec charset_$name = {\n" .
+ " $name, read_sbcs, write_sbcs, &data_$name\n};\n\n";
+}
--- /dev/null
+/*
+ * slookup.c - static lookup of character sets.
+ */
+
+#include "charset.h"
+#include "internal.h"
+
+#define ENUM_CHARSET(x) extern charset_spec const charset_##x;
+#include "enum.c"
+#undef ENUM_CHARSET
+
+static charset_spec const *const cs_table[] = {
+
+#define ENUM_CHARSET(x) &charset_##x,
+#include "enum.c"
+#undef ENUM_CHARSET
+
+};
+
+charset_spec const *charset_find_spec(int charset)
+{
+ int i;
+
+ for (i = 0; i < (int)lenof(cs_table); i++)
+ if (cs_table[i]->charset == charset)
+ return cs_table[i];
+
+ return NULL;
+}
--- /dev/null
+/*
+ * toucs.c - convert charsets to Unicode.
+ */
+
+#include "charset.h"
+#include "internal.h"
+
+struct unicode_emit_param {
+ wchar_t *output;
+ int outlen;
+ const wchar_t *errstr;
+ int errlen;
+ int stopped;
+};
+
+static void unicode_emit(void *ctx, long int output)
+{
+ struct unicode_emit_param *param = (struct unicode_emit_param *)ctx;
+ wchar_t outval;
+ wchar_t const *p;
+ int outlen;
+
+ if (output == ERROR) {
+ if (param->errstr) {
+ p = param->errstr;
+ outlen = param->errlen;
+ } else {
+ outval = 0xFFFD; /* U+FFFD REPLACEMENT CHARACTER */
+ p = &outval;
+ outlen = 1;
+ }
+ } else {
+ outval = output;
+ p = &outval;
+ outlen = 1;
+ }
+
+ if (param->outlen >= outlen) {
+ while (outlen > 0) {
+ *param->output++ = *p++;
+ param->outlen--;
+ outlen--;
+ }
+ } else {
+ param->stopped = 1;
+ }
+}
+
+int charset_to_unicode(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;
+ struct unicode_emit_param param;
+
+ param.output = output;
+ param.outlen = outlen;
+ param.errstr = errstr;
+ param.errlen = errlen;
+ param.stopped = 0;
+
+ if (!state) {
+ localstate.s0 = 0;
+ } else {
+ localstate = *state; /* structure copy */
+ }
+
+ while (*inlen > 0) {
+ int lenbefore = param.output - output;
+ spec->read(spec, (unsigned char)**input, &localstate,
+ unicode_emit, ¶m);
+ if (param.stopped) {
+ /*
+ * The emit function has _tried_ to output some
+ * characters, but ran up against the end of the
+ * buffer. Leave immediately, and return what happened
+ * _before_ attempting to process this character.
+ */
+ return lenbefore;
+ }
+ if (state)
+ *state = localstate; /* structure copy */
+ (*input)++;
+ (*inlen)--;
+ }
+
+ return param.output - output;
+}
--- /dev/null
+/*
+ * utf8.c - routines to handle UTF-8.
+ */
+
+#ifndef ENUM_CHARSETS
+
+#include "charset.h"
+#include "internal.h"
+
+void read_utf8(charset_spec const *, long int, charset_state *,
+ void (*)(void *, long int), void *);
+void write_utf8(charset_spec const *, long int,
+ charset_state *, void (*)(void *, long int), void *);
+
+/*
+ * UTF-8 has no associated data, so `charset' may be ignored.
+ */
+
+void read_utf8(charset_spec const *charset, long int input_chr,
+ charset_state *state,
+ void (*emit)(void *ctx, long int output), void *emitctx)
+{
+ UNUSEDARG(charset);
+
+ /*
+ * For reading UTF-8, the `state' word contains:
+ *
+ * - in bits 29-31, the number of bytes expected to be in the
+ * current multibyte character (which we can tell instantly
+ * from the first byte, of course).
+ *
+ * - in bits 26-28, the number of bytes _seen so far_ in the
+ * current multibyte character.
+ *
+ * - in the remainder of the word, the current value of the
+ * character, which is shifted upwards by 6 bits to
+ * accommodate each new byte.
+ *
+ * As required, the state is zero when we are not in the middle
+ * of a multibyte character at all.
+ *
+ * For example, when reading E9 8D 8B, starting at state=0:
+ *
+ * - after E9, the state is 0x64000009
+ * - after 8D, the state is 0x6800024d
+ * - after 8B, the state conceptually becomes 0x6c00934b, at
+ * which point we notice we've got as many characters as we
+ * were expecting, output U+934B, and reset the state to
+ * zero.
+ *
+ * Note that the maximum number of bits we might need to store
+ * in the character value field is 25 (U+7FFFFFFF contains 31
+ * bits, but we will never actually store its full value
+ * because when we receive the last 6 bits in the final
+ * continuation byte we will output it and revert the state to
+ * zero). Hence the character value field never collides with
+ * the byte counts.
+ */
+
+ if (input_chr < 0x80) {
+ /*
+ * Single-byte character. If the state is nonzero before
+ * coming here, output an error for an incomplete sequence.
+ * Then output the character.
+ */
+ if (state->s0 != 0) {
+ emit(emitctx, ERROR);
+ state->s0 = 0;
+ }
+ emit(emitctx, input_chr);
+ } else if (input_chr == 0xFE || input_chr == 0xFF) {
+ /*
+ * FE and FF bytes should _never_ occur in UTF-8. They are
+ * automatic errors; if the state was nonzero to start
+ * with, output a further error for an incomplete sequence.
+ */
+ if (state->s0 != 0) {
+ emit(emitctx, ERROR);
+ state->s0 = 0;
+ }
+ emit(emitctx, ERROR);
+ } else if (input_chr >= 0x80 && input_chr < 0xC0) {
+ /*
+ * Continuation byte. Output an error for an unexpected
+ * continuation byte, if the state is zero.
+ */
+ if (state->s0 == 0) {
+ emit(emitctx, ERROR);
+ } else {
+ unsigned long charval;
+ unsigned long topstuff;
+ int bytes;
+
+ /*
+ * Otherwise, accumulate more of the character value.
+ */
+ charval = state->s0 & 0x03ffffffL;
+ charval = (charval << 6) | (input_chr & 0x3F);
+
+ /*
+ * Check the byte counts; if we have not reached the
+ * end of the character, update the state and return.
+ */
+ topstuff = state->s0 & 0xfc000000L;
+ topstuff += 0x04000000L; /* add one to the byte count */
+ if (((topstuff << 3) ^ topstuff) & 0xe0000000L) {
+ state->s0 = topstuff | charval;
+ return;
+ }
+
+ /*
+ * Now we know we've reached the end of the character.
+ * `charval' is the Unicode value. We should check for
+ * various invalid things, and then either output
+ * charval or an error. In all cases we reset the state
+ * to zero.
+ */
+ bytes = topstuff >> 29;
+ state->s0 = 0;
+
+ if (charval >= 0xD800 && charval < 0xE000) {
+ /*
+ * Surrogates (0xD800-0xDFFF) may never be encoded
+ * in UTF-8. A surrogate pair in Unicode should
+ * have been encoded as a single UTF-8 character
+ * occupying more than three bytes.
+ */
+ emit(emitctx, ERROR);
+ } else if (charval == 0xFFFE || charval == 0xFFFF) {
+ /*
+ * U+FFFE and U+FFFF are invalid Unicode characters
+ * and may never be encoded in UTF-8. (This is one
+ * reason why U+FFFF is our way of signalling an
+ * error to our `emit' function :-)
+ */
+ emit(emitctx, ERROR);
+ } else if ((charval <= 0x7FL /* && bytes > 1 */) ||
+ (charval <= 0x7FFL && bytes > 2) ||
+ (charval <= 0xFFFFL && bytes > 3) ||
+ (charval <= 0x1FFFFFL && bytes > 4) ||
+ (charval <= 0x3FFFFFFL && bytes > 5)) {
+ /*
+ * Overlong sequences are not to be tolerated,
+ * under any circumstances.
+ */
+ emit(emitctx, ERROR);
+ } else {
+ /*
+ * Oh, all right. We'll let this one off.
+ */
+ emit(emitctx, charval);
+ }
+ }
+
+ } else {
+ /*
+ * Lead byte. First output an error for an incomplete
+ * sequence, if the state is nonzero.
+ */
+ if (state->s0 != 0)
+ emit(emitctx, ERROR);
+
+ /*
+ * Now deal with the lead byte: work out the number of
+ * bytes we expect to see in this character, and extract
+ * the initial bits of it too.
+ */
+ if (input_chr >= 0xC0 && input_chr < 0xE0) {
+ state->s0 = 0x44000000L | (input_chr & 0x1F);
+ } else if (input_chr >= 0xE0 && input_chr < 0xF0) {
+ state->s0 = 0x64000000L | (input_chr & 0x0F);
+ } else if (input_chr >= 0xF0 && input_chr < 0xF8) {
+ state->s0 = 0x84000000L | (input_chr & 0x07);
+ } else if (input_chr >= 0xF8 && input_chr < 0xFC) {
+ state->s0 = 0xa4000000L | (input_chr & 0x03);
+ } else if (input_chr >= 0xFC && input_chr < 0xFE) {
+ state->s0 = 0xc4000000L | (input_chr & 0x01);
+ }
+ }
+}
+
+/*
+ * UTF-8 is a stateless multi-byte encoding (in the sense that just
+ * after any character has been completed, the state is always the
+ * same); hence when writing it, there is no need to use the
+ * charset_state.
+ */
+
+void write_utf8(charset_spec const *charset, long int input_chr,
+ charset_state *state,
+ void (*emit)(void *ctx, long int output), void *emitctx)
+{
+ UNUSEDARG(charset);
+ UNUSEDARG(state);
+
+ /*
+ * Refuse to output any illegal code points.
+ */
+ if (input_chr == 0xFFFE || input_chr == 0xFFFF ||
+ (input_chr >= 0xD800 && input_chr < 0xE000)) {
+ emit(emitctx, ERROR);
+ } else if (input_chr < 0x80) { /* one-byte character */
+ emit(emitctx, input_chr);
+ } else if (input_chr < 0x800) { /* two-byte character */
+ emit(emitctx, 0xC0 | (0x1F & (input_chr >> 6)));
+ emit(emitctx, 0x80 | (0x3F & (input_chr )));
+ } else if (input_chr < 0x10000) { /* three-byte character */
+ emit(emitctx, 0xE0 | (0x0F & (input_chr >> 12)));
+ emit(emitctx, 0x80 | (0x3F & (input_chr >> 6)));
+ emit(emitctx, 0x80 | (0x3F & (input_chr )));
+ } else if (input_chr < 0x200000) { /* four-byte character */
+ emit(emitctx, 0xF0 | (0x07 & (input_chr >> 18)));
+ emit(emitctx, 0x80 | (0x3F & (input_chr >> 12)));
+ emit(emitctx, 0x80 | (0x3F & (input_chr >> 6)));
+ emit(emitctx, 0x80 | (0x3F & (input_chr )));
+ } else if (input_chr < 0x4000000) {/* five-byte character */
+ emit(emitctx, 0xF8 | (0x03 & (input_chr >> 24)));
+ emit(emitctx, 0x80 | (0x3F & (input_chr >> 18)));
+ emit(emitctx, 0x80 | (0x3F & (input_chr >> 12)));
+ emit(emitctx, 0x80 | (0x3F & (input_chr >> 6)));
+ emit(emitctx, 0x80 | (0x3F & (input_chr )));
+ } else { /* six-byte character */
+ emit(emitctx, 0xFC | (0x01 & (input_chr >> 30)));
+ emit(emitctx, 0x80 | (0x3F & (input_chr >> 24)));
+ emit(emitctx, 0x80 | (0x3F & (input_chr >> 18)));
+ emit(emitctx, 0x80 | (0x3F & (input_chr >> 12)));
+ emit(emitctx, 0x80 | (0x3F & (input_chr >> 6)));
+ emit(emitctx, 0x80 | (0x3F & (input_chr )));
+ }
+}
+
+#ifdef TESTMODE
+
+#include <stdio.h>
+#include <stdarg.h>
+
+int total_errs = 0;
+
+void utf8_emit(void *ctx, long output)
+{
+ wchar_t **p = (wchar_t **)ctx;
+ *(*p)++ = output;
+}
+
+void utf8_read_test(int line, char *input, int inlen, ...)
+{
+ va_list ap;
+ wchar_t *p, str[512];
+ int i;
+ charset_state state;
+ unsigned long l;
+
+ state.s0 = 0;
+ p = str;
+
+ for (i = 0; i < inlen; i++)
+ read_utf8(NULL, input[i] & 0xFF, &state, utf8_emit, &p);
+
+ va_start(ap, inlen);
+ l = 0;
+ for (i = 0; i < p - str; i++) {
+ l = va_arg(ap, long int);
+ if (l == -1) {
+ printf("%d: correct string shorter than output\n", line);
+ total_errs++;
+ break;
+ }
+ if (l != str[i]) {
+ printf("%d: char %d came out as %08x, should be %08x\n",
+ line, i, str[i], l);
+ total_errs++;
+ }
+ }
+ if (l != -1) {
+ l = va_arg(ap, long int);
+ if (l != -1) {
+ printf("%d: correct string longer than output\n", line);
+ total_errs++;
+ }
+ }
+ va_end(ap);
+}
+
+void utf8_write_test(int line, const long *input, int inlen, ...)
+{
+ va_list ap;
+ wchar_t *p, str[512];
+ int i;
+ charset_state state;
+ unsigned long l;
+
+ state.s0 = 0;
+ p = str;
+
+ for (i = 0; i < inlen; i++)
+ write_utf8(NULL, input[i], &state, utf8_emit, &p);
+
+ va_start(ap, inlen);
+ l = 0;
+ for (i = 0; i < p - str; i++) {
+ l = va_arg(ap, long int);
+ if (l == -1) {
+ printf("%d: correct string shorter than output\n", line);
+ total_errs++;
+ break;
+ }
+ if (l != str[i]) {
+ printf("%d: char %d came out as %08x, should be %08x\n",
+ line, i, str[i], l);
+ total_errs++;
+ }
+ }
+ if (l != -1) {
+ l = va_arg(ap, long int);
+ if (l != -1) {
+ printf("%d: correct string longer than output\n", line);
+ total_errs++;
+ }
+ }
+ va_end(ap);
+}
+
+/* Macro to concoct the first three parameters of utf8_read_test. */
+#define TESTSTR(x) __LINE__, x, lenof(x)
+
+int main(void)
+{
+ printf("read tests beginning\n");
+ utf8_read_test(TESTSTR("\xCE\xBA\xE1\xBD\xB9\xCF\x83\xCE\xBC\xCE\xB5"),
+ 0x000003BA, /* GREEK SMALL LETTER KAPPA */
+ 0x00001F79, /* GREEK SMALL LETTER OMICRON WITH OXIA */
+ 0x000003C3, /* GREEK SMALL LETTER SIGMA */
+ 0x000003BC, /* GREEK SMALL LETTER MU */
+ 0x000003B5, /* GREEK SMALL LETTER EPSILON */
+ 0, -1);
+ utf8_read_test(TESTSTR("\x00"),
+ 0x00000000, /* <control> */
+ 0, -1);
+ utf8_read_test(TESTSTR("\xC2\x80"),
+ 0x00000080, /* <control> */
+ 0, -1);
+ utf8_read_test(TESTSTR("\xE0\xA0\x80"),
+ 0x00000800, /* <no name available> */
+ 0, -1);
+ utf8_read_test(TESTSTR("\xF0\x90\x80\x80"),
+ 0x00010000, /* <no name available> */
+ 0, -1);
+ utf8_read_test(TESTSTR("\xF8\x88\x80\x80\x80"),
+ 0x00200000, /* <no name available> */
+ 0, -1);
+ utf8_read_test(TESTSTR("\xFC\x84\x80\x80\x80\x80"),
+ 0x04000000, /* <no name available> */
+ 0, -1);
+ utf8_read_test(TESTSTR("\x7F"),
+ 0x0000007F, /* <control> */
+ 0, -1);
+ utf8_read_test(TESTSTR("\xDF\xBF"),
+ 0x000007FF, /* <no name available> */
+ 0, -1);
+ utf8_read_test(TESTSTR("\xEF\xBF\xBD"),
+ 0x0000FFFD, /* REPLACEMENT CHARACTER */
+ 0, -1);
+ utf8_read_test(TESTSTR("\xEF\xBF\xBF"),
+ ERROR, /* <no name available> (invalid char) */
+ 0, -1);
+ utf8_read_test(TESTSTR("\xF7\xBF\xBF\xBF"),
+ 0x001FFFFF, /* <no name available> */
+ 0, -1);
+ utf8_read_test(TESTSTR("\xFB\xBF\xBF\xBF\xBF"),
+ 0x03FFFFFF, /* <no name available> */
+ 0, -1);
+ utf8_read_test(TESTSTR("\xFD\xBF\xBF\xBF\xBF\xBF"),
+ 0x7FFFFFFF, /* <no name available> */
+ 0, -1);
+ utf8_read_test(TESTSTR("\xED\x9F\xBF"),
+ 0x0000D7FF, /* <no name available> */
+ 0, -1);
+ utf8_read_test(TESTSTR("\xEE\x80\x80"),
+ 0x0000E000, /* <Private Use, First> */
+ 0, -1);
+ utf8_read_test(TESTSTR("\xEF\xBF\xBD"),
+ 0x0000FFFD, /* REPLACEMENT CHARACTER */
+ 0, -1);
+ utf8_read_test(TESTSTR("\xF4\x8F\xBF\xBF"),
+ 0x0010FFFF, /* <no name available> */
+ 0, -1);
+ utf8_read_test(TESTSTR("\xF4\x90\x80\x80"),
+ 0x00110000, /* <no name available> */
+ 0, -1);
+ utf8_read_test(TESTSTR("\x80"),
+ ERROR, /* (unexpected continuation byte) */
+ 0, -1);
+ utf8_read_test(TESTSTR("\xBF"),
+ ERROR, /* (unexpected continuation byte) */
+ 0, -1);
+ utf8_read_test(TESTSTR("\x80\xBF"),
+ ERROR, /* (unexpected continuation byte) */
+ ERROR, /* (unexpected continuation byte) */
+ 0, -1);
+ utf8_read_test(TESTSTR("\x80\xBF\x80"),
+ ERROR, /* (unexpected continuation byte) */
+ ERROR, /* (unexpected continuation byte) */
+ ERROR, /* (unexpected continuation byte) */
+ 0, -1);
+ utf8_read_test(TESTSTR("\x80\xBF\x80\xBF"),
+ ERROR, /* (unexpected continuation byte) */
+ ERROR, /* (unexpected continuation byte) */
+ ERROR, /* (unexpected continuation byte) */
+ ERROR, /* (unexpected continuation byte) */
+ 0, -1);
+ utf8_read_test(TESTSTR("\x80\xBF\x80\xBF\x80"),
+ ERROR, /* (unexpected continuation byte) */
+ ERROR, /* (unexpected continuation byte) */
+ ERROR, /* (unexpected continuation byte) */
+ ERROR, /* (unexpected continuation byte) */
+ ERROR, /* (unexpected continuation byte) */
+ 0, -1);
+ utf8_read_test(TESTSTR("\x80\xBF\x80\xBF\x80\xBF"),
+ ERROR, /* (unexpected continuation byte) */
+ ERROR, /* (unexpected continuation byte) */
+ ERROR, /* (unexpected continuation byte) */
+ ERROR, /* (unexpected continuation byte) */
+ ERROR, /* (unexpected continuation byte) */
+ ERROR, /* (unexpected continuation byte) */
+ 0, -1);
+ utf8_read_test(TESTSTR("\x80\xBF\x80\xBF\x80\xBF\x80"),
+ ERROR, /* (unexpected continuation byte) */
+ ERROR, /* (unexpected continuation byte) */
+ ERROR, /* (unexpected continuation byte) */
+ ERROR, /* (unexpected continuation byte) */
+ ERROR, /* (unexpected continuation byte) */
+ ERROR, /* (unexpected continuation byte) */
+ ERROR, /* (unexpected continuation byte) */
+ 0, -1);
+ utf8_read_test(TESTSTR("\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8A\x8B\x8C\x8D\x8E\x8F\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9A\x9B\x9C\x9D\x9E\x9F\xA0\xA1\xA2\xA3\xA4\xA5\xA6\xA7\xA8\xA9\xAA\xAB\xAC\xAD\xAE\xAF\xB0\xB1\xB2\xB3\xB4\xB5\xB6\xB7\xB8\xB9\xBA\xBB\xBC\xBD\xBE\xBF"),
+ ERROR, /* (unexpected continuation byte) */
+ ERROR, /* (unexpected continuation byte) */
+ ERROR, /* (unexpected continuation byte) */
+ ERROR, /* (unexpected continuation byte) */
+ ERROR, /* (unexpected continuation byte) */
+ ERROR, /* (unexpected continuation byte) */
+ ERROR, /* (unexpected continuation byte) */
+ ERROR, /* (unexpected continuation byte) */
+ ERROR, /* (unexpected continuation byte) */
+ ERROR, /* (unexpected continuation byte) */
+ ERROR, /* (unexpected continuation byte) */
+ ERROR, /* (unexpected continuation byte) */
+ ERROR, /* (unexpected continuation byte) */
+ ERROR, /* (unexpected continuation byte) */
+ ERROR, /* (unexpected continuation byte) */
+ ERROR, /* (unexpected continuation byte) */
+ ERROR, /* (unexpected continuation byte) */
+ ERROR, /* (unexpected continuation byte) */
+ ERROR, /* (unexpected continuation byte) */
+ ERROR, /* (unexpected continuation byte) */
+ ERROR, /* (unexpected continuation byte) */
+ ERROR, /* (unexpected continuation byte) */
+ ERROR, /* (unexpected continuation byte) */
+ ERROR, /* (unexpected continuation byte) */
+ ERROR, /* (unexpected continuation byte) */
+ ERROR, /* (unexpected continuation byte) */
+ ERROR, /* (unexpected continuation byte) */
+ ERROR, /* (unexpected continuation byte) */
+ ERROR, /* (unexpected continuation byte) */
+ ERROR, /* (unexpected continuation byte) */
+ ERROR, /* (unexpected continuation byte) */
+ ERROR, /* (unexpected continuation byte) */
+ ERROR, /* (unexpected continuation byte) */
+ ERROR, /* (unexpected continuation byte) */
+ ERROR, /* (unexpected continuation byte) */
+ ERROR, /* (unexpected continuation byte) */
+ ERROR, /* (unexpected continuation byte) */
+ ERROR, /* (unexpected continuation byte) */
+ ERROR, /* (unexpected continuation byte) */
+ ERROR, /* (unexpected continuation byte) */
+ ERROR, /* (unexpected continuation byte) */
+ ERROR, /* (unexpected continuation byte) */
+ ERROR, /* (unexpected continuation byte) */
+ ERROR, /* (unexpected continuation byte) */
+ ERROR, /* (unexpected continuation byte) */
+ ERROR, /* (unexpected continuation byte) */
+ ERROR, /* (unexpected continuation byte) */
+ ERROR, /* (unexpected continuation byte) */
+ ERROR, /* (unexpected continuation byte) */
+ ERROR, /* (unexpected continuation byte) */
+ ERROR, /* (unexpected continuation byte) */
+ ERROR, /* (unexpected continuation byte) */
+ ERROR, /* (unexpected continuation byte) */
+ ERROR, /* (unexpected continuation byte) */
+ ERROR, /* (unexpected continuation byte) */
+ ERROR, /* (unexpected continuation byte) */
+ ERROR, /* (unexpected continuation byte) */
+ ERROR, /* (unexpected continuation byte) */
+ ERROR, /* (unexpected continuation byte) */
+ ERROR, /* (unexpected continuation byte) */
+ ERROR, /* (unexpected continuation byte) */
+ ERROR, /* (unexpected continuation byte) */
+ ERROR, /* (unexpected continuation byte) */
+ ERROR, /* (unexpected continuation byte) */
+ 0, -1);
+ utf8_read_test(TESTSTR("\xC0\x20\xC1\x20\xC2\x20\xC3\x20\xC4\x20\xC5\x20\xC6\x20\xC7\x20"),
+ ERROR, /* (incomplete sequence) */
+ 0x00000020, /* SPACE */
+ ERROR, /* (incomplete sequence) */
+ 0x00000020, /* SPACE */
+ ERROR, /* (incomplete sequence) */
+ 0x00000020, /* SPACE */
+ ERROR, /* (incomplete sequence) */
+ 0x00000020, /* SPACE */
+ ERROR, /* (incomplete sequence) */
+ 0x00000020, /* SPACE */
+ ERROR, /* (incomplete sequence) */
+ 0x00000020, /* SPACE */
+ ERROR, /* (incomplete sequence) */
+ 0x00000020, /* SPACE */
+ ERROR, /* (incomplete sequence) */
+ 0x00000020, /* SPACE */
+ 0, -1);
+ utf8_read_test(TESTSTR("\xE0\x20\xE1\x20\xE2\x20\xE3\x20\xE4\x20\xE5\x20\xE6\x20\xE7\x20\xE8\x20\xE9\x20\xEA\x20\xEB\x20\xEC\x20\xED\x20\xEE\x20\xEF\x20"),
+ ERROR, /* (incomplete sequence) */
+ 0x00000020, /* SPACE */
+ ERROR, /* (incomplete sequence) */
+ 0x00000020, /* SPACE */
+ ERROR, /* (incomplete sequence) */
+ 0x00000020, /* SPACE */
+ ERROR, /* (incomplete sequence) */
+ 0x00000020, /* SPACE */
+ ERROR, /* (incomplete sequence) */
+ 0x00000020, /* SPACE */
+ ERROR, /* (incomplete sequence) */
+ 0x00000020, /* SPACE */
+ ERROR, /* (incomplete sequence) */
+ 0x00000020, /* SPACE */
+ ERROR, /* (incomplete sequence) */
+ 0x00000020, /* SPACE */
+ ERROR, /* (incomplete sequence) */
+ 0x00000020, /* SPACE */
+ ERROR, /* (incomplete sequence) */
+ 0x00000020, /* SPACE */
+ ERROR, /* (incomplete sequence) */
+ 0x00000020, /* SPACE */
+ ERROR, /* (incomplete sequence) */
+ 0x00000020, /* SPACE */
+ ERROR, /* (incomplete sequence) */
+ 0x00000020, /* SPACE */
+ ERROR, /* (incomplete sequence) */
+ 0x00000020, /* SPACE */
+ ERROR, /* (incomplete sequence) */
+ 0x00000020, /* SPACE */
+ ERROR, /* (incomplete sequence) */
+ 0x00000020, /* SPACE */
+ 0, -1);
+ utf8_read_test(TESTSTR("\xF0\x20\xF1\x20\xF2\x20\xF3\x20\xF4\x20\xF5\x20\xF6\x20\xF7\x20"),
+ ERROR, /* (incomplete sequence) */
+ 0x00000020, /* SPACE */
+ ERROR, /* (incomplete sequence) */
+ 0x00000020, /* SPACE */
+ ERROR, /* (incomplete sequence) */
+ 0x00000020, /* SPACE */
+ ERROR, /* (incomplete sequence) */
+ 0x00000020, /* SPACE */
+ ERROR, /* (incomplete sequence) */
+ 0x00000020, /* SPACE */
+ ERROR, /* (incomplete sequence) */
+ 0x00000020, /* SPACE */
+ ERROR, /* (incomplete sequence) */
+ 0x00000020, /* SPACE */
+ ERROR, /* (incomplete sequence) */
+ 0x00000020, /* SPACE */
+ 0, -1);
+ utf8_read_test(TESTSTR("\xF8\x20\xF9\x20\xFA\x20\xFB\x20"),
+ ERROR, /* (incomplete sequence) */
+ 0x00000020, /* SPACE */
+ ERROR, /* (incomplete sequence) */
+ 0x00000020, /* SPACE */
+ ERROR, /* (incomplete sequence) */
+ 0x00000020, /* SPACE */
+ ERROR, /* (incomplete sequence) */
+ 0x00000020, /* SPACE */
+ 0, -1);
+ utf8_read_test(TESTSTR("\xFC\x20\xFD\x20"),
+ ERROR, /* (incomplete sequence) */
+ 0x00000020, /* SPACE */
+ ERROR, /* (incomplete sequence) */
+ 0x00000020, /* SPACE */
+ 0, -1);
+ utf8_read_test(TESTSTR("\xC0"),
+ ERROR, /* (incomplete sequence) */
+ 0, -1);
+ utf8_read_test(TESTSTR("\xE0\x80"),
+ ERROR, /* (incomplete sequence) */
+ 0, -1);
+ utf8_read_test(TESTSTR("\xF0\x80\x80"),
+ ERROR, /* (incomplete sequence) */
+ 0, -1);
+ utf8_read_test(TESTSTR("\xF8\x80\x80\x80"),
+ ERROR, /* (incomplete sequence) */
+ 0, -1);
+ utf8_read_test(TESTSTR("\xFC\x80\x80\x80\x80"),
+ ERROR, /* (incomplete sequence) */
+ 0, -1);
+ utf8_read_test(TESTSTR("\xDF"),
+ ERROR, /* (incomplete sequence) */
+ 0, -1);
+ utf8_read_test(TESTSTR("\xEF\xBF"),
+ ERROR, /* (incomplete sequence) */
+ 0, -1);
+ utf8_read_test(TESTSTR("\xF7\xBF\xBF"),
+ ERROR, /* (incomplete sequence) */
+ 0, -1);
+ utf8_read_test(TESTSTR("\xFB\xBF\xBF\xBF"),
+ ERROR, /* (incomplete sequence) */
+ 0, -1);
+ utf8_read_test(TESTSTR("\xFD\xBF\xBF\xBF\xBF"),
+ ERROR, /* (incomplete sequence) */
+ 0, -1);
+ utf8_read_test(TESTSTR("\xC0\xE0\x80\xF0\x80\x80\xF8\x80\x80\x80\xFC\x80\x80\x80\x80\xDF\xEF\xBF\xF7\xBF\xBF\xFB\xBF\xBF\xBF\xFD\xBF\xBF\xBF\xBF"),
+ ERROR, /* (incomplete sequence) */
+ ERROR, /* (incomplete sequence) */
+ ERROR, /* (incomplete sequence) */
+ ERROR, /* (incomplete sequence) */
+ ERROR, /* (incomplete sequence) */
+ ERROR, /* (incomplete sequence) */
+ ERROR, /* (incomplete sequence) */
+ ERROR, /* (incomplete sequence) */
+ ERROR, /* (incomplete sequence) */
+ ERROR, /* (incomplete sequence) */
+ 0, -1);
+ utf8_read_test(TESTSTR("\xFE"),
+ ERROR, /* (invalid UTF-8 byte) */
+ 0, -1);
+ utf8_read_test(TESTSTR("\xFF"),
+ ERROR, /* (invalid UTF-8 byte) */
+ 0, -1);
+ utf8_read_test(TESTSTR("\xFE\xFE\xFF\xFF"),
+ ERROR, /* (invalid UTF-8 byte) */
+ ERROR, /* (invalid UTF-8 byte) */
+ ERROR, /* (invalid UTF-8 byte) */
+ ERROR, /* (invalid UTF-8 byte) */
+ 0, -1);
+ utf8_read_test(TESTSTR("\xC0\xAF"),
+ ERROR, /* SOLIDUS (overlong form of 2F) */
+ 0, -1);
+ utf8_read_test(TESTSTR("\xE0\x80\xAF"),
+ ERROR, /* SOLIDUS (overlong form of 2F) */
+ 0, -1);
+ utf8_read_test(TESTSTR("\xF0\x80\x80\xAF"),
+ ERROR, /* SOLIDUS (overlong form of 2F) */
+ 0, -1);
+ utf8_read_test(TESTSTR("\xF8\x80\x80\x80\xAF"),
+ ERROR, /* SOLIDUS (overlong form of 2F) */
+ 0, -1);
+ utf8_read_test(TESTSTR("\xFC\x80\x80\x80\x80\xAF"),
+ ERROR, /* SOLIDUS (overlong form of 2F) */
+ 0, -1);
+ utf8_read_test(TESTSTR("\xC1\xBF"),
+ ERROR, /* <control> (overlong form of 7F) */
+ 0, -1);
+ utf8_read_test(TESTSTR("\xE0\x9F\xBF"),
+ ERROR, /* <no name available> (overlong form of DF BF) */
+ 0, -1);
+ utf8_read_test(TESTSTR("\xF0\x8F\xBF\xBF"),
+ ERROR, /* <no name available> (overlong form of EF BF BF) (invalid char) */
+ 0, -1);
+ utf8_read_test(TESTSTR("\xF8\x87\xBF\xBF\xBF"),
+ ERROR, /* <no name available> (overlong form of F7 BF BF BF) */
+ 0, -1);
+ utf8_read_test(TESTSTR("\xFC\x83\xBF\xBF\xBF\xBF"),
+ ERROR, /* <no name available> (overlong form of FB BF BF BF BF) */
+ 0, -1);
+ utf8_read_test(TESTSTR("\xC0\x80"),
+ ERROR, /* <control> (overlong form of 00) */
+ 0, -1);
+ utf8_read_test(TESTSTR("\xE0\x80\x80"),
+ ERROR, /* <control> (overlong form of 00) */
+ 0, -1);
+ utf8_read_test(TESTSTR("\xF0\x80\x80\x80"),
+ ERROR, /* <control> (overlong form of 00) */
+ 0, -1);
+ utf8_read_test(TESTSTR("\xF8\x80\x80\x80\x80"),
+ ERROR, /* <control> (overlong form of 00) */
+ 0, -1);
+ utf8_read_test(TESTSTR("\xFC\x80\x80\x80\x80\x80"),
+ ERROR, /* <control> (overlong form of 00) */
+ 0, -1);
+ utf8_read_test(TESTSTR("\xED\xA0\x80"),
+ ERROR, /* <Non Private Use High Surrogate, First> (surrogate) */
+ 0, -1);
+ utf8_read_test(TESTSTR("\xED\xAD\xBF"),
+ ERROR, /* <Non Private Use High Surrogate, Last> (surrogate) */
+ 0, -1);
+ utf8_read_test(TESTSTR("\xED\xAE\x80"),
+ ERROR, /* <Private Use High Surrogate, First> (surrogate) */
+ 0, -1);
+ utf8_read_test(TESTSTR("\xED\xAF\xBF"),
+ ERROR, /* <Private Use High Surrogate, Last> (surrogate) */
+ 0, -1);
+ utf8_read_test(TESTSTR("\xED\xB0\x80"),
+ ERROR, /* <Low Surrogate, First> (surrogate) */
+ 0, -1);
+ utf8_read_test(TESTSTR("\xED\xBE\x80"),
+ ERROR, /* <no name available> (surrogate) */
+ 0, -1);
+ utf8_read_test(TESTSTR("\xED\xBF\xBF"),
+ ERROR, /* <Low Surrogate, Last> (surrogate) */
+ 0, -1);
+ utf8_read_test(TESTSTR("\xED\xA0\x80\xED\xB0\x80"),
+ ERROR, /* <Non Private Use High Surrogate, First> (surrogate) */
+ ERROR, /* <Low Surrogate, First> (surrogate) */
+ 0, -1);
+ utf8_read_test(TESTSTR("\xED\xA0\x80\xED\xBF\xBF"),
+ ERROR, /* <Non Private Use High Surrogate, First> (surrogate) */
+ ERROR, /* <Low Surrogate, Last> (surrogate) */
+ 0, -1);
+ utf8_read_test(TESTSTR("\xED\xAD\xBF\xED\xB0\x80"),
+ ERROR, /* <Non Private Use High Surrogate, Last> (surrogate) */
+ ERROR, /* <Low Surrogate, First> (surrogate) */
+ 0, -1);
+ utf8_read_test(TESTSTR("\xED\xAD\xBF\xED\xBF\xBF"),
+ ERROR, /* <Non Private Use High Surrogate, Last> (surrogate) */
+ ERROR, /* <Low Surrogate, Last> (surrogate) */
+ 0, -1);
+ utf8_read_test(TESTSTR("\xED\xAE\x80\xED\xB0\x80"),
+ ERROR, /* <Private Use High Surrogate, First> (surrogate) */
+ ERROR, /* <Low Surrogate, First> (surrogate) */
+ 0, -1);
+ utf8_read_test(TESTSTR("\xED\xAE\x80\xED\xBF\xBF"),
+ ERROR, /* <Private Use High Surrogate, First> (surrogate) */
+ ERROR, /* <Low Surrogate, Last> (surrogate) */
+ 0, -1);
+ utf8_read_test(TESTSTR("\xED\xAF\xBF\xED\xB0\x80"),
+ ERROR, /* <Private Use High Surrogate, Last> (surrogate) */
+ ERROR, /* <Low Surrogate, First> (surrogate) */
+ 0, -1);
+ utf8_read_test(TESTSTR("\xED\xAF\xBF\xED\xBF\xBF"),
+ ERROR, /* <Private Use High Surrogate, Last> (surrogate) */
+ ERROR, /* <Low Surrogate, Last> (surrogate) */
+ 0, -1);
+ utf8_read_test(TESTSTR("\xEF\xBF\xBE"),
+ ERROR, /* <no name available> (invalid char) */
+ 0, -1);
+ utf8_read_test(TESTSTR("\xEF\xBF\xBF"),
+ ERROR, /* <no name available> (invalid char) */
+ 0, -1);
+ printf("read tests completed\n");
+ printf("write tests beginning\n");
+ {
+ const static long str[] =
+ {0x03BAL, 0x1F79L, 0x03C3L, 0x03BCL, 0x03B5L, 0};
+ utf8_write_test(TESTSTR(str),
+ 0xCE, 0xBA,
+ 0xE1, 0xBD, 0xB9,
+ 0xCF, 0x83,
+ 0xCE, 0xBC,
+ 0xCE, 0xB5,
+ 0, -1);
+ }
+ {
+ const static long str[] = {0x0000L, 0};
+ utf8_write_test(TESTSTR(str),
+ 0x00,
+ 0, -1);
+ }
+ {
+ const static long str[] = {0x0080L, 0};
+ utf8_write_test(TESTSTR(str),
+ 0xC2, 0x80,
+ 0, -1);
+ }
+ {
+ const static long str[] = {0x0800L, 0};
+ utf8_write_test(TESTSTR(str),
+ 0xE0, 0xA0, 0x80,
+ 0, -1);
+ }
+ {
+ const static long str[] = {0x00010000L, 0};
+ utf8_write_test(TESTSTR(str),
+ 0xF0, 0x90, 0x80, 0x80,
+ 0, -1);
+ }
+ {
+ const static long str[] = {0x00200000L, 0};
+ utf8_write_test(TESTSTR(str),
+ 0xF8, 0x88, 0x80, 0x80, 0x80,
+ 0, -1);
+ }
+ {
+ const static long str[] = {0x04000000L, 0};
+ utf8_write_test(TESTSTR(str),
+ 0xFC, 0x84, 0x80, 0x80, 0x80, 0x80,
+ 0, -1);
+ }
+ {
+ const static long str[] = {0x007FL, 0};
+ utf8_write_test(TESTSTR(str),
+ 0x7F,
+ 0, -1);
+ }
+ {
+ const static long str[] = {0x07FFL, 0};
+ utf8_write_test(TESTSTR(str),
+ 0xDF, 0xBF,
+ 0, -1);
+ }
+ {
+ const static long str[] = {0xFFFDL, 0};
+ utf8_write_test(TESTSTR(str),
+ 0xEF, 0xBF, 0xBD,
+ 0, -1);
+ }
+ {
+ const static long str[] = {0xFFFFL, 0};
+ utf8_write_test(TESTSTR(str),
+ ERROR,
+ 0, -1);
+ }
+ {
+ const static long str[] = {0x001FFFFFL, 0};
+ utf8_write_test(TESTSTR(str),
+ 0xF7, 0xBF, 0xBF, 0xBF,
+ 0, -1);
+ }
+ {
+ const static long str[] = {0x03FFFFFFL, 0};
+ utf8_write_test(TESTSTR(str),
+ 0xFB, 0xBF, 0xBF, 0xBF, 0xBF,
+ 0, -1);
+ }
+ {
+ const static long str[] = {0x7FFFFFFFL, 0};
+ utf8_write_test(TESTSTR(str),
+ 0xFD, 0xBF, 0xBF, 0xBF, 0xBF, 0xBF,
+ 0, -1);
+ }
+ {
+ const static long str[] = {0xD7FFL, 0};
+ utf8_write_test(TESTSTR(str),
+ 0xED, 0x9F, 0xBF,
+ 0, -1);
+ }
+ {
+ const static long str[] = {0xD800L, 0};
+ utf8_write_test(TESTSTR(str),
+ ERROR,
+ 0, -1);
+ }
+ {
+ const static long str[] = {0xD800L, 0xDC00L, 0};
+ utf8_write_test(TESTSTR(str),
+ ERROR,
+ ERROR,
+ 0, -1);
+ }
+ {
+ const static long str[] = {0xDFFFL, 0};
+ utf8_write_test(TESTSTR(str),
+ ERROR,
+ 0, -1);
+ }
+ {
+ const static long str[] = {0xE000L, 0};
+ utf8_write_test(TESTSTR(str),
+ 0xEE, 0x80, 0x80,
+ 0, -1);
+ }
+ printf("write tests completed\n");
+
+ printf("total: %d errors\n", total_errs);
+ return (total_errs != 0);
+}
+#endif /* TESTMODE */
+
+const charset_spec charset_CS_UTF8 = {
+ CS_UTF8, read_utf8, write_utf8, NULL
+};
+
+#else /* ENUM_CHARSETS */
+
+ENUM_CHARSET(CS_UTF8)
+
+#endif /* ENUM_CHARSETS */
--- /dev/null
+/*
+ * xenc.c - translate our internal character set codes to and from
+ * X11 character encoding names.
+ *
+ */
+
+#include <ctype.h>
+#include "charset.h"
+#include "internal.h"
+
+static const struct {
+ const char *name;
+ int charset;
+} xencs[] = {
+ /*
+ * Officially registered encoding names. This list is derived
+ * from the font encodings section of
+ *
+ * http://ftp.x.org/pub/DOCS/registry
+ *
+ * Where multiple encoding names map to the same encoding id
+ * (such as iso8859-15 and fcd8859-15), the first is considered
+ * canonical and will be returned when translating the id to a
+ * string.
+ */
+ { "iso8859-1", CS_ISO8859_1 },
+ { "iso8859-2", CS_ISO8859_2 },
+ { "iso8859-3", CS_ISO8859_3 },
+ { "iso8859-4", CS_ISO8859_4 },
+ { "iso8859-5", CS_ISO8859_5 },
+ { "iso8859-6", CS_ISO8859_6 },
+ { "iso8859-7", CS_ISO8859_7 },
+ { "iso8859-8", CS_ISO8859_8 },
+ { "iso8859-9", CS_ISO8859_9 },
+ { "iso8859-10", CS_ISO8859_10 },
+ { "iso8859-13", CS_ISO8859_13 },
+ { "iso8859-14", CS_ISO8859_14 },
+ { "iso8859-15", CS_ISO8859_15 },
+ { "fcd8859-15", CS_ISO8859_15 },
+ { "hp-roman8", CS_HP_ROMAN8 },
+ { "koi8-r", CS_KOI8_R },
+ /*
+ * Unofficial encoding names found in the wild.
+ */
+ { "iso8859-16", CS_ISO8859_16 },
+ { "koi8-u", CS_KOI8_U },
+ { "ibm-cp437", CS_CP437 },
+ { "ibm-cp850", CS_CP850 },
+ { "ibm-cp866", CS_CP866 },
+ { "microsoft-cp1250", CS_CP1250 },
+ { "microsoft-cp1251", CS_CP1251 },
+ { "microsoft-cp1252", CS_CP1252 },
+ { "microsoft-cp1253", CS_CP1253 },
+ { "microsoft-cp1254", CS_CP1254 },
+ { "microsoft-cp1255", CS_CP1255 },
+ { "microsoft-cp1256", CS_CP1256 },
+ { "microsoft-cp1257", CS_CP1257 },
+ { "microsoft-cp1258", CS_CP1258 },
+ { "mac-roman", CS_MAC_ROMAN },
+ { "viscii1.1-1", CS_VISCII },
+ { "viscii1-1", CS_VISCII },
+};
+
+const char *charset_to_xenc(int charset)
+{
+ int i;
+
+ for (i = 0; i < (int)lenof(xencs); i++)
+ if (charset == xencs[i].charset)
+ return xencs[i].name;
+
+ return NULL; /* not found */
+}
+
+int charset_from_xenc(const char *name)
+{
+ int i;
+
+ for (i = 0; i < (int)lenof(xencs); i++) {
+ const char *p, *q;
+ p = name;
+ q = xencs[i].name;
+ while (*p || *q) {
+ if (tolower((unsigned char)*p) != tolower((unsigned char)*q))
+ break;
+ p++; q++;
+ }
+ if (!*p && !*q)
+ return xencs[i].charset;
+ }
+
+ return CS_NONE; /* not found */
+}
--- /dev/null
+/*
+ * cmdgen.c - command-line form of PuTTYgen
+ */
+
+#define PUTTY_DO_GLOBALS
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <limits.h>
+#include <assert.h>
+#include <time.h>
+
+#include "putty.h"
+#include "ssh.h"
+
+#ifdef TEST_CMDGEN
+/*
+ * This section overrides some definitions below for test purposes.
+ * When compiled with -DTEST_CMDGEN:
+ *
+ * - Calls to get_random_data() are replaced with the diagnostic
+ * function below (I #define the name so that I can still link
+ * with the original set of modules without symbol clash), in
+ * order to avoid depleting the test system's /dev/random
+ * unnecessarily.
+ *
+ * - Calls to console_get_userpass_input() are replaced with the
+ * diagnostic function below, so that I can run tests in an
+ * automated manner and provide their interactive passphrase
+ * inputs.
+ *
+ * - main() is renamed to cmdgen_main(); at the bottom of the file
+ * I define another main() which calls the former repeatedly to
+ * run tests.
+ */
+#define get_random_data get_random_data_diagnostic
+char *get_random_data(int len)
+{
+ char *buf = snewn(len, char);
+ memset(buf, 'x', len);
+ return buf;
+}
+#define console_get_userpass_input console_get_userpass_input_diagnostic
+int nprompts, promptsgot;
+const char *prompts[3];
+int console_get_userpass_input(prompts_t *p, unsigned char *in, int inlen)
+{
+ size_t i;
+ int ret = 1;
+ for (i = 0; i < p->n_prompts; i++) {
+ if (promptsgot < nprompts) {
+ assert(strlen(prompts[promptsgot]) < p->prompts[i]->result_len);
+ strcpy(p->prompts[i]->result, prompts[promptsgot++]);
+ } else {
+ promptsgot++; /* track number of requests anyway */
+ ret = 0;
+ }
+ }
+ return ret;
+}
+#define main cmdgen_main
+#endif
+
+struct progress {
+ int phase, current;
+};
+
+static void progress_update(void *param, int action, int phase, int iprogress)
+{
+ struct progress *p = (struct progress *)param;
+ if (action != PROGFN_PROGRESS)
+ return;
+ if (phase > p->phase) {
+ if (p->phase >= 0)
+ fputc('\n', stderr);
+ p->phase = phase;
+ if (iprogress >= 0)
+ p->current = iprogress - 1;
+ else
+ p->current = iprogress;
+ }
+ while (p->current < iprogress) {
+ fputc('+', stdout);
+ p->current++;
+ }
+ fflush(stdout);
+}
+
+static void no_progress(void *param, int action, int phase, int iprogress)
+{
+}
+
+void modalfatalbox(char *p, ...)
+{
+ va_list ap;
+ fprintf(stderr, "FATAL ERROR: ");
+ va_start(ap, p);
+ vfprintf(stderr, p, ap);
+ va_end(ap);
+ fputc('\n', stderr);
+ cleanup_exit(1);
+}
+
+/*
+ * Stubs to let everything else link sensibly.
+ */
+void log_eventlog(void *handle, const char *event)
+{
+}
+char *x_get_default(const char *key)
+{
+ return NULL;
+}
+void sk_cleanup(void)
+{
+}
+
+void showversion(void)
+{
+ char *verstr = dupstr(ver);
+ verstr[0] = tolower((unsigned char)verstr[0]);
+ printf("PuTTYgen %s\n", verstr);
+ sfree(verstr);
+}
+
+void usage(int standalone)
+{
+ fprintf(stderr,
+ "Usage: puttygen ( keyfile | -t type [ -b bits ] )\n"
+ " [ -C comment ] [ -P ] [ -q ]\n"
+ " [ -o output-keyfile ] [ -O type | -l | -L"
+ " | -p ]\n");
+ if (standalone)
+ fprintf(stderr,
+ "Use \"puttygen --help\" for more detail.\n");
+}
+
+void help(void)
+{
+ /*
+ * Help message is an extended version of the usage message. So
+ * start with that, plus a version heading.
+ */
+ showversion();
+ usage(FALSE);
+ fprintf(stderr,
+ " -t specify key type when generating (rsa, dsa, rsa1)\n"
+ " -b specify number of bits when generating key\n"
+ " -C change or specify key comment\n"
+ " -P change key passphrase\n"
+ " -q quiet: do not display progress bar\n"
+ " -O specify output type:\n"
+ " private output PuTTY private key format\n"
+ " private-openssh export OpenSSH private key\n"
+ " private-sshcom export ssh.com private key\n"
+ " public standard / ssh.com public key\n"
+ " public-openssh OpenSSH public key\n"
+ " fingerprint output the key fingerprint\n"
+ " -o specify output file\n"
+ " -l equivalent to `-O fingerprint'\n"
+ " -L equivalent to `-O public-openssh'\n"
+ " -p equivalent to `-O public'\n"
+ );
+}
+
+static int save_ssh2_pubkey(char *filename, char *comment,
+ void *v_pub_blob, int pub_len)
+{
+ unsigned char *pub_blob = (unsigned char *)v_pub_blob;
+ char *p;
+ int i, column;
+ FILE *fp;
+
+ if (filename) {
+ fp = fopen(filename, "wb");
+ if (!fp)
+ return 0;
+ } else
+ fp = stdout;
+
+ fprintf(fp, "---- BEGIN SSH2 PUBLIC KEY ----\n");
+
+ if (comment) {
+ fprintf(fp, "Comment: \"");
+ for (p = comment; *p; p++) {
+ if (*p == '\\' || *p == '\"')
+ fputc('\\', fp);
+ fputc(*p, fp);
+ }
+ fprintf(fp, "\"\n");
+ }
+
+ i = 0;
+ column = 0;
+ while (i < pub_len) {
+ char buf[5];
+ int n = (pub_len - i < 3 ? pub_len - i : 3);
+ base64_encode_atom(pub_blob + i, n, buf);
+ i += n;
+ buf[4] = '\0';
+ fputs(buf, fp);
+ if (++column >= 16) {
+ fputc('\n', fp);
+ column = 0;
+ }
+ }
+ if (column > 0)
+ fputc('\n', fp);
+
+ fprintf(fp, "---- END SSH2 PUBLIC KEY ----\n");
+ if (filename)
+ fclose(fp);
+ return 1;
+}
+
+static int move(char *from, char *to)
+{
+ int ret;
+
+ ret = rename(from, to);
+ if (ret) {
+ /*
+ * This OS may require us to remove the original file first.
+ */
+ remove(to);
+ ret = rename(from, to);
+ }
+ if (ret) {
+ perror("puttygen: cannot move new file on to old one");
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static char *blobfp(char *alg, int bits, unsigned char *blob, int bloblen)
+{
+ char buffer[128];
+ unsigned char digest[16];
+ struct MD5Context md5c;
+ int i;
+
+ MD5Init(&md5c);
+ MD5Update(&md5c, blob, bloblen);
+ MD5Final(digest, &md5c);
+
+ sprintf(buffer, "%s ", alg);
+ if (bits > 0)
+ sprintf(buffer + strlen(buffer), "%d ", bits);
+ for (i = 0; i < 16; i++)
+ sprintf(buffer + strlen(buffer), "%s%02x", i ? ":" : "",
+ digest[i]);
+
+ return dupstr(buffer);
+}
+
+int main(int argc, char **argv)
+{
+ char *infile = NULL;
+ Filename infilename;
+ 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;
+ char *comment = NULL, *origcomment = NULL;
+ int change_passphrase = FALSE;
+ int errs = FALSE, nogo = FALSE;
+ int intype = SSH_KEYTYPE_UNOPENABLE;
+ int sshver = 0;
+ struct ssh2_userkey *ssh2key = NULL;
+ struct RSAKey *ssh1key = NULL;
+ unsigned char *ssh2blob = NULL;
+ char *ssh2alg = NULL;
+ const struct ssh_signkey *ssh2algf = NULL;
+ int ssh2bloblen;
+ char *passphrase = NULL;
+ int load_encrypted;
+ progfn_t progressfn = is_interactive() ? progress_update : no_progress;
+
+ /* ------------------------------------------------------------------
+ * Parse the command line to figure out what we've been asked to do.
+ */
+
+ /*
+ * If run with no arguments at all, print the usage message and
+ * return success.
+ */
+ if (argc <= 1) {
+ usage(TRUE);
+ return 0;
+ }
+
+ /*
+ * Parse command line arguments.
+ */
+ while (--argc) {
+ char *p = *++argv;
+ if (*p == '-') {
+ /*
+ * An option.
+ */
+ while (p && *++p) {
+ char c = *p;
+ switch (c) {
+ case '-':
+ /*
+ * Long option.
+ */
+ {
+ char *opt, *val;
+ opt = p++; /* opt will have _one_ leading - */
+ while (*p && *p != '=')
+ p++; /* find end of option */
+ if (*p == '=') {
+ *p++ = '\0';
+ val = p;
+ } else
+ val = NULL;
+
+ if (!strcmp(opt, "-help")) {
+ if (val) {
+ errs = TRUE;
+ fprintf(stderr, "puttygen: option `-%s'"
+ " expects no argument\n", opt);
+ } else {
+ help();
+ nogo = TRUE;
+ }
+ } else if (!strcmp(opt, "-version")) {
+ if (val) {
+ errs = TRUE;
+ fprintf(stderr, "puttygen: option `-%s'"
+ " expects no argument\n", opt);
+ } else {
+ showversion();
+ nogo = TRUE;
+ }
+ } else if (!strcmp(opt, "-pgpfp")) {
+ if (val) {
+ errs = TRUE;
+ fprintf(stderr, "puttygen: option `-%s'"
+ " expects no argument\n", opt);
+ } else {
+ /* support --pgpfp for consistency */
+ pgp_fingerprints();
+ nogo = TRUE;
+ }
+ }
+ /*
+ * For long options requiring an argument, add
+ * code along the lines of
+ *
+ * else if (!strcmp(opt, "-output")) {
+ * if (!val) {
+ * errs = TRUE;
+ * fprintf(stderr, "puttygen: option `-%s'"
+ * " expects an argument\n", opt);
+ * } else
+ * ofile = val;
+ * }
+ */
+ else {
+ errs = TRUE;
+ fprintf(stderr,
+ "puttygen: no such option `-%s'\n", opt);
+ }
+ }
+ p = NULL;
+ break;
+ case 'h':
+ case 'V':
+ case 'P':
+ case 'l':
+ case 'L':
+ case 'p':
+ case 'q':
+ /*
+ * Option requiring no parameter.
+ */
+ switch (c) {
+ case 'h':
+ help();
+ nogo = TRUE;
+ break;
+ case 'V':
+ showversion();
+ nogo = TRUE;
+ break;
+ case 'P':
+ change_passphrase = TRUE;
+ break;
+ case 'l':
+ outtype = FP;
+ break;
+ case 'L':
+ outtype = PUBLICO;
+ break;
+ case 'p':
+ outtype = PUBLIC;
+ break;
+ case 'q':
+ progressfn = no_progress;
+ break;
+ }
+ break;
+ case 't':
+ case 'b':
+ case 'C':
+ case 'O':
+ case 'o':
+ /*
+ * Option requiring parameter.
+ */
+ p++;
+ if (!*p && argc > 1)
+ --argc, p = *++argv;
+ else if (!*p) {
+ fprintf(stderr, "puttygen: option `-%c' expects a"
+ " parameter\n", c);
+ errs = TRUE;
+ }
+ /*
+ * Now c is the option and p is the parameter.
+ */
+ switch (c) {
+ case 't':
+ if (!strcmp(p, "rsa") || !strcmp(p, "rsa2"))
+ keytype = RSA2, sshver = 2;
+ else if (!strcmp(p, "rsa1"))
+ keytype = RSA1, sshver = 1;
+ else if (!strcmp(p, "dsa") || !strcmp(p, "dss"))
+ keytype = DSA, sshver = 2;
+ else {
+ fprintf(stderr,
+ "puttygen: unknown key type `%s'\n", p);
+ errs = TRUE;
+ }
+ break;
+ case 'b':
+ bits = atoi(p);
+ break;
+ case 'C':
+ comment = p;
+ break;
+ case 'O':
+ if (!strcmp(p, "public"))
+ outtype = PUBLIC;
+ else if (!strcmp(p, "public-openssh"))
+ outtype = PUBLICO;
+ else if (!strcmp(p, "private"))
+ outtype = PRIVATE;
+ else if (!strcmp(p, "fingerprint"))
+ outtype = FP;
+ else if (!strcmp(p, "private-openssh"))
+ outtype = OPENSSH, sshver = 2;
+ else if (!strcmp(p, "private-sshcom"))
+ outtype = SSHCOM, sshver = 2;
+ else {
+ fprintf(stderr,
+ "puttygen: unknown output type `%s'\n", p);
+ errs = TRUE;
+ }
+ break;
+ case 'o':
+ outfile = p;
+ break;
+ }
+ p = NULL; /* prevent continued processing */
+ break;
+ default:
+ /*
+ * Unrecognised option.
+ */
+ errs = TRUE;
+ fprintf(stderr, "puttygen: no such option `-%c'\n", c);
+ break;
+ }
+ }
+ } else {
+ /*
+ * A non-option argument.
+ */
+ if (!infile)
+ infile = p;
+ else {
+ errs = TRUE;
+ fprintf(stderr, "puttygen: cannot handle more than one"
+ " input file\n");
+ }
+ }
+ }
+
+ if (errs)
+ return 1;
+
+ if (nogo)
+ return 0;
+
+ /*
+ * If run with at least one argument _but_ not the required
+ * ones, print the usage message and return failure.
+ */
+ if (!infile && keytype == NOKEYGEN) {
+ usage(TRUE);
+ return 1;
+ }
+
+ /* ------------------------------------------------------------------
+ * Figure out further details of exactly what we're going to do.
+ */
+
+ /*
+ * Bomb out if we've been asked to both load and generate a
+ * key.
+ */
+ if (keytype != NOKEYGEN && infile) {
+ fprintf(stderr, "puttygen: cannot both load and generate a key\n");
+ return 1;
+ }
+
+ /*
+ * We must save the private part when generating a new key.
+ */
+ if (keytype != NOKEYGEN &&
+ (outtype != PRIVATE && outtype != OPENSSH && outtype != SSHCOM)) {
+ fprintf(stderr, "puttygen: this would generate a new key but "
+ "discard the private part\n");
+ return 1;
+ }
+
+ /*
+ * Analyse the type of the input file, in case this affects our
+ * course of action.
+ */
+ if (infile) {
+ infilename = filename_from_str(infile);
+
+ 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)
+ * convert them to other public key types, (b) print
+ * out their fingerprints. Or, I suppose, for real
+ * orthogonality, (c) change their comment!
+ *
+ * In fact this opens some interesting possibilities.
+ * Suppose ssh2_userkey_loadpub() were able to load
+ * public key files as well as extracting the public
+ * key from private ones. And suppose I did the thing
+ * I've been wanting to do, where specifying a
+ * particular private key file for authentication
+ * causes any _other_ key in the agent to be discarded.
+ * Then, if you had an agent forwarded to the machine
+ * you were running Unix PuTTY or Plink on, and you
+ * needed to specify which of the keys in the agent it
+ * should use, you could do that by supplying a
+ * _public_ key file, thus not needing to trust even
+ * your encrypted private key file to the network. Ooh!
+ */
+
+ case SSH_KEYTYPE_UNOPENABLE:
+ case SSH_KEYTYPE_UNKNOWN:
+ fprintf(stderr, "puttygen: unable to load file `%s': %s\n",
+ infile, key_type_to_str(intype));
+ return 1;
+
+ case SSH_KEYTYPE_SSH1:
+ if (sshver == 2) {
+ fprintf(stderr, "puttygen: conversion from SSH-1 to SSH-2 keys"
+ " not supported\n");
+ return 1;
+ }
+ sshver = 1;
+ break;
+
+ case SSH_KEYTYPE_SSH2:
+ case SSH_KEYTYPE_OPENSSH:
+ case SSH_KEYTYPE_SSHCOM:
+ if (sshver == 1) {
+ fprintf(stderr, "puttygen: conversion from SSH-2 to SSH-1 keys"
+ " not supported\n");
+ return 1;
+ }
+ sshver = 2;
+ break;
+ }
+ }
+
+ /*
+ * Determine the default output file, if none is provided.
+ *
+ * This will usually be equal to stdout, except that if the
+ * input and output file formats are the same then the default
+ * output is to overwrite the input.
+ *
+ * Also in this code, we bomb out if the input and output file
+ * formats are the same and no other action is performed.
+ */
+ if ((intype == SSH_KEYTYPE_SSH1 && outtype == PRIVATE) ||
+ (intype == SSH_KEYTYPE_SSH2 && outtype == PRIVATE) ||
+ (intype == SSH_KEYTYPE_OPENSSH && outtype == OPENSSH) ||
+ (intype == SSH_KEYTYPE_SSHCOM && outtype == SSHCOM)) {
+ if (!outfile) {
+ outfile = infile;
+ outfiletmp = dupcat(outfile, ".tmp", NULL);
+ }
+
+ if (!change_passphrase && !comment) {
+ fprintf(stderr, "puttygen: this command would perform no useful"
+ " action\n");
+ return 1;
+ }
+ } else {
+ if (!outfile) {
+ /*
+ * Bomb out rather than automatically choosing to write
+ * a private key file to stdout.
+ */
+ if (outtype==PRIVATE || outtype==OPENSSH || outtype==SSHCOM) {
+ fprintf(stderr, "puttygen: need to specify an output file\n");
+ return 1;
+ }
+ }
+ }
+
+ /*
+ * Figure out whether we need to load the encrypted part of the
+ * key. This will be the case if either (a) we need to write
+ * out a private key format, or (b) the entire input key file
+ * is encrypted.
+ */
+ if (outtype == PRIVATE || outtype == OPENSSH || outtype == SSHCOM ||
+ intype == SSH_KEYTYPE_OPENSSH || intype == SSH_KEYTYPE_SSHCOM)
+ load_encrypted = TRUE;
+ else
+ load_encrypted = FALSE;
+
+ /* ------------------------------------------------------------------
+ * Now we're ready to actually do some stuff.
+ */
+
+ /*
+ * Either load or generate a key.
+ */
+ if (keytype != NOKEYGEN) {
+ char *entropy;
+ char default_comment[80];
+ struct tm tm;
+ struct progress prog;
+
+ prog.phase = -1;
+ prog.current = -1;
+
+ tm = ltime();
+ if (keytype == DSA)
+ strftime(default_comment, 30, "dsa-key-%Y%m%d", &tm);
+ else
+ strftime(default_comment, 30, "rsa-key-%Y%m%d", &tm);
+
+ random_ref();
+ entropy = get_random_data(bits / 8);
+ if (!entropy) {
+ 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);
+ sfree(entropy);
+
+ if (keytype == DSA) {
+ struct dss_key *dsskey = snew(struct dss_key);
+ dsa_generate(dsskey, bits, progressfn, &prog);
+ ssh2key = snew(struct ssh2_userkey);
+ ssh2key->data = dsskey;
+ ssh2key->alg = &ssh_dss;
+ ssh1key = NULL;
+ } else {
+ struct RSAKey *rsakey = snew(struct RSAKey);
+ rsa_generate(rsakey, bits, progressfn, &prog);
+ rsakey->comment = NULL;
+ if (keytype == RSA1) {
+ ssh1key = rsakey;
+ } else {
+ ssh2key = snew(struct ssh2_userkey);
+ ssh2key->data = rsakey;
+ ssh2key->alg = &ssh_rsa;
+ }
+ }
+ progressfn(&prog, PROGFN_PROGRESS, INT_MAX, -1);
+
+ if (ssh2key)
+ ssh2key->comment = dupstr(default_comment);
+ if (ssh1key)
+ ssh1key->comment = dupstr(default_comment);
+
+ } else {
+ const char *error = NULL;
+ int encrypted;
+
+ assert(infile != NULL);
+
+ /*
+ * Find out whether the input key is encrypted.
+ */
+ if (intype == SSH_KEYTYPE_SSH1)
+ encrypted = rsakey_encrypted(&infilename, &origcomment);
+ else if (intype == SSH_KEYTYPE_SSH2)
+ encrypted = ssh2_userkey_encrypted(&infilename, &origcomment);
+ else
+ 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);
+ ret = console_get_userpass_input(p, NULL, 0);
+ assert(ret >= 0);
+ if (!ret) {
+ free_prompts(p);
+ perror("puttygen: unable to read passphrase");
+ return 1;
+ } else {
+ passphrase = dupstr(p->prompts[0]->result);
+ free_prompts(p);
+ }
+ } else {
+ passphrase = NULL;
+ }
+
+ switch (intype) {
+ int ret;
+
+ case SSH_KEYTYPE_SSH1:
+ ssh1key = snew(struct RSAKey);
+ if (!load_encrypted) {
+ void *vblob;
+ unsigned char *blob;
+ int n, l, bloblen;
+
+ ret = rsakey_pubblob(&infilename, &vblob, &bloblen,
+ &origcomment, &error);
+ blob = (unsigned char *)vblob;
+
+ n = 4; /* skip modulus bits */
+
+ l = ssh1_read_bignum(blob + n, bloblen - n,
+ &ssh1key->exponent);
+ if (l < 0) {
+ error = "SSH-1 public key blob was too short";
+ } else {
+ n += l;
+ l = ssh1_read_bignum(blob + n, bloblen - n,
+ &ssh1key->modulus);
+ if (l < 0) {
+ error = "SSH-1 public key blob was too short";
+ } else
+ n += l;
+ }
+ ssh1key->comment = dupstr(origcomment);
+ ssh1key->private_exponent = NULL;
+ } else {
+ 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,
+ &ssh2bloblen, NULL, &error);
+ ssh2algf = find_pubkey_alg(ssh2alg);
+ if (ssh2algf)
+ bits = ssh2algf->pubkey_bits(ssh2blob, ssh2bloblen);
+ else
+ bits = -1;
+ } else {
+ ssh2key = ssh2_load_userkey(&infilename, passphrase, &error);
+ }
+ if ((ssh2key && ssh2key != SSH2_WRONG_PASSPHRASE) || ssh2blob)
+ error = NULL;
+ else if (!error) {
+ if (ssh2key == SSH2_WRONG_PASSPHRASE)
+ error = "wrong passphrase";
+ else
+ error = "unknown error";
+ }
+ break;
+
+ case SSH_KEYTYPE_OPENSSH:
+ case SSH_KEYTYPE_SSHCOM:
+ ssh2key = import_ssh2(&infilename, intype, passphrase, &error);
+ if (ssh2key) {
+ if (ssh2key != SSH2_WRONG_PASSPHRASE)
+ error = NULL;
+ else
+ error = "wrong passphrase";
+ } else if (!error)
+ error = "unknown error";
+ break;
+
+ default:
+ assert(0);
+ }
+
+ if (error) {
+ fprintf(stderr, "puttygen: error loading `%s': %s\n",
+ infile, error);
+ return 1;
+ }
+ }
+
+ /*
+ * Change the comment if asked to.
+ */
+ if (comment) {
+ if (sshver == 1) {
+ assert(ssh1key);
+ sfree(ssh1key->comment);
+ ssh1key->comment = dupstr(comment);
+ } else {
+ assert(ssh2key);
+ sfree(ssh2key->comment);
+ ssh2key->comment = dupstr(comment);
+ }
+ }
+
+ /*
+ * Prompt for a new passphrase if we have been asked to, or if
+ * we have just generated a key.
+ */
+ if (change_passphrase || keytype != NOKEYGEN) {
+ 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);
+ ret = console_get_userpass_input(p, NULL, 0);
+ assert(ret >= 0);
+ if (!ret) {
+ free_prompts(p);
+ perror("puttygen: unable to read new passphrase");
+ return 1;
+ } else {
+ if (strcmp(p->prompts[0]->result, p->prompts[1]->result)) {
+ free_prompts(p);
+ fprintf(stderr, "puttygen: passphrases do not match\n");
+ return 1;
+ }
+ if (passphrase) {
+ memset(passphrase, 0, strlen(passphrase));
+ sfree(passphrase);
+ }
+ passphrase = dupstr(p->prompts[0]->result);
+ free_prompts(p);
+ if (!*passphrase) {
+ sfree(passphrase);
+ passphrase = NULL;
+ }
+ }
+ }
+
+ /*
+ * Write output.
+ *
+ * (In the case where outfile and outfiletmp are both NULL,
+ * there is no semantic reason to initialise outfilename at
+ * all; but we have to write _something_ to it or some compiler
+ * will probably complain that it might be used uninitialised.)
+ */
+ if (outfiletmp)
+ outfilename = filename_from_str(outfiletmp);
+ else
+ outfilename = filename_from_str(outfile ? outfile : "");
+
+ switch (outtype) {
+ int ret;
+
+ case PRIVATE:
+ if (sshver == 1) {
+ assert(ssh1key);
+ 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);
+ if (!ret) {
+ fprintf(stderr, "puttygen: unable to save SSH-2 private key\n");
+ return 1;
+ }
+ }
+ if (outfiletmp) {
+ if (!move(outfiletmp, outfile))
+ return 1; /* rename failed */
+ }
+ break;
+
+ case PUBLIC:
+ case PUBLICO:
+ if (sshver == 1) {
+ FILE *fp;
+ char *dec1, *dec2;
+
+ assert(ssh1key);
+
+ if (outfile)
+ fp = f_open(outfilename, "w", FALSE);
+ else
+ fp = stdout;
+ dec1 = bignum_decimal(ssh1key->exponent);
+ dec2 = bignum_decimal(ssh1key->modulus);
+ fprintf(fp, "%d %s %s %s\n", bignum_bitcount(ssh1key->modulus),
+ dec1, dec2, ssh1key->comment);
+ sfree(dec1);
+ sfree(dec2);
+ if (outfile)
+ fclose(fp);
+ } else if (outtype == PUBLIC) {
+ if (!ssh2blob) {
+ assert(ssh2key);
+ ssh2blob = ssh2key->alg->public_blob(ssh2key->data,
+ &ssh2bloblen);
+ }
+ save_ssh2_pubkey(outfile, ssh2key ? ssh2key->comment : origcomment,
+ ssh2blob, ssh2bloblen);
+ } else if (outtype == PUBLICO) {
+ char *buffer, *p;
+ int i;
+ FILE *fp;
+
+ if (!ssh2blob) {
+ assert(ssh2key);
+ ssh2blob = ssh2key->alg->public_blob(ssh2key->data,
+ &ssh2bloblen);
+ }
+ if (!ssh2alg) {
+ assert(ssh2key);
+ ssh2alg = ssh2key->alg->name;
+ }
+ if (ssh2key)
+ comment = ssh2key->comment;
+ else
+ comment = origcomment;
+
+ buffer = snewn(strlen(ssh2alg) +
+ 4 * ((ssh2bloblen+2) / 3) +
+ strlen(comment) + 3, char);
+ strcpy(buffer, ssh2alg);
+ p = buffer + strlen(buffer);
+ *p++ = ' ';
+ i = 0;
+ while (i < ssh2bloblen) {
+ int n = (ssh2bloblen - i < 3 ? ssh2bloblen - i : 3);
+ base64_encode_atom(ssh2blob + i, n, p);
+ i += n;
+ p += 4;
+ }
+ if (*comment) {
+ *p++ = ' ';
+ strcpy(p, comment);
+ } else
+ *p++ = '\0';
+
+ if (outfile)
+ fp = f_open(outfilename, "w", FALSE);
+ else
+ fp = stdout;
+ fprintf(fp, "%s\n", buffer);
+ if (outfile)
+ fclose(fp);
+
+ sfree(buffer);
+ }
+ break;
+
+ case FP:
+ {
+ FILE *fp;
+ char *fingerprint;
+
+ if (sshver == 1) {
+ assert(ssh1key);
+ fingerprint = snewn(128, char);
+ rsa_fingerprint(fingerprint, 128, ssh1key);
+ } else {
+ if (ssh2key) {
+ fingerprint = ssh2key->alg->fingerprint(ssh2key->data);
+ } else {
+ assert(ssh2blob);
+ fingerprint = blobfp(ssh2alg, bits, ssh2blob, ssh2bloblen);
+ }
+ }
+
+ if (outfile)
+ fp = f_open(outfilename, "w", FALSE);
+ else
+ fp = stdout;
+ fprintf(fp, "%s\n", fingerprint);
+ if (outfile)
+ fclose(fp);
+
+ sfree(fingerprint);
+ }
+ break;
+
+ case OPENSSH:
+ case SSHCOM:
+ assert(sshver == 2);
+ assert(ssh2key);
+ ret = export_ssh2(&outfilename, outtype, ssh2key, passphrase);
+ if (!ret) {
+ fprintf(stderr, "puttygen: unable to export key\n");
+ return 1;
+ }
+ if (outfiletmp) {
+ if (!move(outfiletmp, outfile))
+ return 1; /* rename failed */
+ }
+ break;
+ }
+
+ if (passphrase) {
+ memset(passphrase, 0, strlen(passphrase));
+ sfree(passphrase);
+ }
+
+ if (ssh1key)
+ freersakey(ssh1key);
+ if (ssh2key) {
+ ssh2key->alg->freekey(ssh2key->data);
+ sfree(ssh2key);
+ }
+
+ return 0;
+}
+
+#ifdef TEST_CMDGEN
+
+#undef main
+
+#include <stdarg.h>
+
+int passes, fails;
+
+void setup_passphrases(char *first, ...)
+{
+ va_list ap;
+ char *next;
+
+ nprompts = 0;
+ if (first) {
+ prompts[nprompts++] = first;
+ va_start(ap, first);
+ while ((next = va_arg(ap, char *)) != NULL) {
+ assert(nprompts < lenof(prompts));
+ prompts[nprompts++] = next;
+ }
+ va_end(ap);
+ }
+}
+
+void test(int retval, ...)
+{
+ va_list ap;
+ int i, argc, ret;
+ char **argv;
+
+ argc = 0;
+ va_start(ap, retval);
+ while (va_arg(ap, char *) != NULL)
+ argc++;
+ va_end(ap);
+
+ argv = snewn(argc+1, char *);
+ va_start(ap, retval);
+ for (i = 0; i <= argc; i++)
+ argv[i] = va_arg(ap, char *);
+ va_end(ap);
+
+ promptsgot = 0;
+ ret = cmdgen_main(argc, argv);
+
+ if (ret != retval) {
+ printf("FAILED retval (exp %d got %d):", retval, ret);
+ for (i = 0; i < argc; i++)
+ printf(" %s", argv[i]);
+ printf("\n");
+ fails++;
+ } else if (promptsgot != nprompts) {
+ printf("FAILED nprompts (exp %d got %d):", nprompts, promptsgot);
+ for (i = 0; i < argc; i++)
+ printf(" %s", argv[i]);
+ printf("\n");
+ fails++;
+ } else {
+ passes++;
+ }
+}
+
+void filecmp(char *file1, char *file2, char *fmt, ...)
+{
+ /*
+ * Ideally I should do file comparison myself, to maximise the
+ * portability of this test suite once this application begins
+ * running on non-Unix platforms. For the moment, though,
+ * calling Unix diff is perfectly adequate.
+ */
+ char *buf;
+ int ret;
+
+ buf = dupprintf("diff -q '%s' '%s'", file1, file2);
+ ret = system(buf);
+ sfree(buf);
+
+ if (ret) {
+ va_list ap;
+
+ printf("FAILED diff (ret=%d): ", ret);
+
+ va_start(ap, fmt);
+ vprintf(fmt, ap);
+ va_end(ap);
+
+ printf("\n");
+
+ fails++;
+ } else
+ passes++;
+}
+
+char *cleanup_fp(char *s)
+{
+ char *p;
+
+ if (!strncmp(s, "ssh-", 4)) {
+ s += strcspn(s, " \n\t");
+ s += strspn(s, " \n\t");
+ }
+
+ p = s;
+ s += strcspn(s, " \n\t");
+ s += strspn(s, " \n\t");
+ s += strcspn(s, " \n\t");
+
+ return dupprintf("%.*s", s - p, p);
+}
+
+char *get_fp(char *filename)
+{
+ FILE *fp;
+ char buf[256], *ret;
+
+ fp = fopen(filename, "r");
+ if (!fp)
+ return NULL;
+ ret = fgets(buf, sizeof(buf), fp);
+ fclose(fp);
+ if (!ret)
+ return NULL;
+ return cleanup_fp(buf);
+}
+
+void check_fp(char *filename, char *fp, char *fmt, ...)
+{
+ char *newfp;
+
+ if (!fp)
+ return;
+
+ newfp = get_fp(filename);
+
+ if (!strcmp(fp, newfp)) {
+ passes++;
+ } else {
+ va_list ap;
+
+ printf("FAILED check_fp ['%s' != '%s']: ", newfp, fp);
+
+ va_start(ap, fmt);
+ vprintf(fmt, ap);
+ va_end(ap);
+
+ printf("\n");
+
+ fails++;
+ }
+
+ sfree(newfp);
+}
+
+int main(int argc, char **argv)
+{
+ int i;
+ static char *const keytypes[] = { "rsa1", "dsa", "rsa" };
+
+ /*
+ * Even when this thing is compiled for automatic test mode,
+ * it's helpful to be able to invoke it with command-line
+ * options for _manual_ tests.
+ */
+ if (argc > 1)
+ return cmdgen_main(argc, argv);
+
+ passes = fails = 0;
+
+ for (i = 0; i < lenof(keytypes); i++) {
+ char filename[128], osfilename[128], scfilename[128];
+ char pubfilename[128], tmpfilename1[128], tmpfilename2[128];
+ char *fp;
+
+ sprintf(filename, "test-%s.ppk", keytypes[i]);
+ sprintf(pubfilename, "test-%s.pub", keytypes[i]);
+ sprintf(osfilename, "test-%s.os", keytypes[i]);
+ sprintf(scfilename, "test-%s.sc", keytypes[i]);
+ sprintf(tmpfilename1, "test-%s.tmp1", keytypes[i]);
+ sprintf(tmpfilename2, "test-%s.tmp2", keytypes[i]);
+
+ /*
+ * Create an encrypted key.
+ */
+ setup_passphrases("sponge", "sponge", NULL);
+ test(0, "puttygen", "-t", keytypes[i], "-o", filename, NULL);
+
+ /*
+ * List the public key in OpenSSH format.
+ */
+ setup_passphrases(NULL);
+ test(0, "puttygen", "-L", filename, "-o", pubfilename, NULL);
+ {
+ char cmdbuf[256];
+ fp = NULL;
+ sprintf(cmdbuf, "ssh-keygen -l -f '%s' > '%s'",
+ pubfilename, tmpfilename1);
+ if (system(cmdbuf) ||
+ (fp = get_fp(tmpfilename1)) == NULL) {
+ printf("UNABLE to test fingerprint matching against OpenSSH");
+ }
+ }
+
+ /*
+ * List the public key in IETF/ssh.com format.
+ */
+ setup_passphrases(NULL);
+ test(0, "puttygen", "-p", filename, NULL);
+
+ /*
+ * List the fingerprint of the key.
+ */
+ setup_passphrases(NULL);
+ test(0, "puttygen", "-l", filename, "-o", tmpfilename1, NULL);
+ if (!fp) {
+ /*
+ * If we can't test fingerprints against OpenSSH, we
+ * can at the very least test equality of all the
+ * fingerprints we generate of this key throughout
+ * testing.
+ */
+ fp = get_fp(tmpfilename1);
+ } else {
+ check_fp(tmpfilename1, fp, "%s initial fp", keytypes[i]);
+ }
+
+ /*
+ * Change the comment of the key; this _does_ require a
+ * passphrase owing to the tamperproofing.
+ *
+ * NOTE: In SSH-1, this only requires a passphrase because
+ * of inadequacies of the loading and saving mechanisms. In
+ * _principle_, it should be perfectly possible to modify
+ * the comment on an SSH-1 key without requiring a
+ * passphrase; the only reason I can't do it is because my
+ * loading and saving mechanisms don't include a method of
+ * loading all the key data without also trying to decrypt
+ * the private section.
+ *
+ * I don't consider this to be a problem worth solving,
+ * because (a) to fix it would probably end up bloating
+ * PuTTY proper, and (b) SSH-1 is on the way out anyway so
+ * it shouldn't be highly significant. If it seriously
+ * bothers anyone then perhaps I _might_ be persuadable.
+ */
+ setup_passphrases("sponge", NULL);
+ test(0, "puttygen", "-C", "new-comment", filename, NULL);
+
+ /*
+ * Change the passphrase to nothing.
+ */
+ setup_passphrases("sponge", "", "", NULL);
+ test(0, "puttygen", "-P", filename, NULL);
+
+ /*
+ * Change the comment of the key again; this time we expect no
+ * passphrase to be required.
+ */
+ setup_passphrases(NULL);
+ test(0, "puttygen", "-C", "new-comment-2", filename, NULL);
+
+ /*
+ * Export the private key into OpenSSH format; no passphrase
+ * should be required since the key is currently unencrypted.
+ * For RSA1 keys, this should give an error.
+ */
+ setup_passphrases(NULL);
+ test((i==0), "puttygen", "-O", "private-openssh", "-o", osfilename,
+ filename, NULL);
+
+ if (i) {
+ /*
+ * List the fingerprint of the OpenSSH-formatted key.
+ */
+ setup_passphrases(NULL);
+ test(0, "puttygen", "-l", osfilename, "-o", tmpfilename1, NULL);
+ check_fp(tmpfilename1, fp, "%s openssh clear fp", keytypes[i]);
+
+ /*
+ * List the public half of the OpenSSH-formatted key in
+ * OpenSSH format.
+ */
+ setup_passphrases(NULL);
+ test(0, "puttygen", "-L", osfilename, NULL);
+
+ /*
+ * List the public half of the OpenSSH-formatted key in
+ * IETF/ssh.com format.
+ */
+ setup_passphrases(NULL);
+ test(0, "puttygen", "-p", osfilename, NULL);
+ }
+
+ /*
+ * Export the private key into ssh.com format; no passphrase
+ * should be required since the key is currently unencrypted.
+ * For RSA1 keys, this should give an error.
+ */
+ setup_passphrases(NULL);
+ test((i==0), "puttygen", "-O", "private-sshcom", "-o", scfilename,
+ filename, NULL);
+
+ if (i) {
+ /*
+ * List the fingerprint of the ssh.com-formatted key.
+ */
+ setup_passphrases(NULL);
+ test(0, "puttygen", "-l", scfilename, "-o", tmpfilename1, NULL);
+ check_fp(tmpfilename1, fp, "%s ssh.com clear fp", keytypes[i]);
+
+ /*
+ * List the public half of the ssh.com-formatted key in
+ * OpenSSH format.
+ */
+ setup_passphrases(NULL);
+ test(0, "puttygen", "-L", scfilename, NULL);
+
+ /*
+ * List the public half of the ssh.com-formatted key in
+ * IETF/ssh.com format.
+ */
+ setup_passphrases(NULL);
+ test(0, "puttygen", "-p", scfilename, NULL);
+ }
+
+ if (i) {
+ /*
+ * Convert from OpenSSH into ssh.com.
+ */
+ setup_passphrases(NULL);
+ test(0, "puttygen", osfilename, "-o", tmpfilename1,
+ "-O", "private-sshcom", NULL);
+
+ /*
+ * Convert from ssh.com back into a PuTTY key,
+ * supplying the same comment as we had before we
+ * started to ensure the comparison works.
+ */
+ setup_passphrases(NULL);
+ test(0, "puttygen", tmpfilename1, "-C", "new-comment-2",
+ "-o", tmpfilename2, NULL);
+
+ /*
+ * See if the PuTTY key thus generated is the same as
+ * the original.
+ */
+ filecmp(filename, tmpfilename2,
+ "p->o->s->p clear %s", keytypes[i]);
+
+ /*
+ * Convert from ssh.com to OpenSSH.
+ */
+ setup_passphrases(NULL);
+ test(0, "puttygen", scfilename, "-o", tmpfilename1,
+ "-O", "private-openssh", NULL);
+
+ /*
+ * Convert from OpenSSH back into a PuTTY key,
+ * supplying the same comment as we had before we
+ * started to ensure the comparison works.
+ */
+ setup_passphrases(NULL);
+ test(0, "puttygen", tmpfilename1, "-C", "new-comment-2",
+ "-o", tmpfilename2, NULL);
+
+ /*
+ * See if the PuTTY key thus generated is the same as
+ * the original.
+ */
+ filecmp(filename, tmpfilename2,
+ "p->s->o->p clear %s", keytypes[i]);
+
+ /*
+ * Finally, do a round-trip conversion between PuTTY
+ * and ssh.com without involving OpenSSH, to test that
+ * the key comment is preserved in that case.
+ */
+ setup_passphrases(NULL);
+ test(0, "puttygen", "-O", "private-sshcom", "-o", tmpfilename1,
+ filename, NULL);
+ setup_passphrases(NULL);
+ test(0, "puttygen", tmpfilename1, "-o", tmpfilename2, NULL);
+ filecmp(filename, tmpfilename2,
+ "p->s->p clear %s", keytypes[i]);
+ }
+
+ /*
+ * Check that mismatched passphrases cause an error.
+ */
+ setup_passphrases("sponge2", "sponge3", NULL);
+ test(1, "puttygen", "-P", filename, NULL);
+
+ /*
+ * Put a passphrase back on.
+ */
+ setup_passphrases("sponge2", "sponge2", NULL);
+ test(0, "puttygen", "-P", filename, NULL);
+
+ /*
+ * Export the private key into OpenSSH format, this time
+ * while encrypted. For RSA1 keys, this should give an
+ * error.
+ */
+ if (i == 0)
+ setup_passphrases(NULL); /* error, hence no passphrase read */
+ else
+ setup_passphrases("sponge2", NULL);
+ test((i==0), "puttygen", "-O", "private-openssh", "-o", osfilename,
+ filename, NULL);
+
+ if (i) {
+ /*
+ * List the fingerprint of the OpenSSH-formatted key.
+ */
+ setup_passphrases("sponge2", NULL);
+ test(0, "puttygen", "-l", osfilename, "-o", tmpfilename1, NULL);
+ check_fp(tmpfilename1, fp, "%s openssh encrypted fp", keytypes[i]);
+
+ /*
+ * List the public half of the OpenSSH-formatted key in
+ * OpenSSH format.
+ */
+ setup_passphrases("sponge2", NULL);
+ test(0, "puttygen", "-L", osfilename, NULL);
+
+ /*
+ * List the public half of the OpenSSH-formatted key in
+ * IETF/ssh.com format.
+ */
+ setup_passphrases("sponge2", NULL);
+ test(0, "puttygen", "-p", osfilename, NULL);
+ }
+
+ /*
+ * Export the private key into ssh.com format, this time
+ * while encrypted. For RSA1 keys, this should give an
+ * error.
+ */
+ if (i == 0)
+ setup_passphrases(NULL); /* error, hence no passphrase read */
+ else
+ setup_passphrases("sponge2", NULL);
+ test((i==0), "puttygen", "-O", "private-sshcom", "-o", scfilename,
+ filename, NULL);
+
+ if (i) {
+ /*
+ * List the fingerprint of the ssh.com-formatted key.
+ */
+ setup_passphrases("sponge2", NULL);
+ test(0, "puttygen", "-l", scfilename, "-o", tmpfilename1, NULL);
+ check_fp(tmpfilename1, fp, "%s ssh.com encrypted fp", keytypes[i]);
+
+ /*
+ * List the public half of the ssh.com-formatted key in
+ * OpenSSH format.
+ */
+ setup_passphrases("sponge2", NULL);
+ test(0, "puttygen", "-L", scfilename, NULL);
+
+ /*
+ * List the public half of the ssh.com-formatted key in
+ * IETF/ssh.com format.
+ */
+ setup_passphrases("sponge2", NULL);
+ test(0, "puttygen", "-p", scfilename, NULL);
+ }
+
+ if (i) {
+ /*
+ * Convert from OpenSSH into ssh.com.
+ */
+ setup_passphrases("sponge2", NULL);
+ test(0, "puttygen", osfilename, "-o", tmpfilename1,
+ "-O", "private-sshcom", NULL);
+
+ /*
+ * Convert from ssh.com back into a PuTTY key,
+ * supplying the same comment as we had before we
+ * started to ensure the comparison works.
+ */
+ setup_passphrases("sponge2", NULL);
+ test(0, "puttygen", tmpfilename1, "-C", "new-comment-2",
+ "-o", tmpfilename2, NULL);
+
+ /*
+ * See if the PuTTY key thus generated is the same as
+ * the original.
+ */
+ filecmp(filename, tmpfilename2,
+ "p->o->s->p encrypted %s", keytypes[i]);
+
+ /*
+ * Convert from ssh.com to OpenSSH.
+ */
+ setup_passphrases("sponge2", NULL);
+ test(0, "puttygen", scfilename, "-o", tmpfilename1,
+ "-O", "private-openssh", NULL);
+
+ /*
+ * Convert from OpenSSH back into a PuTTY key,
+ * supplying the same comment as we had before we
+ * started to ensure the comparison works.
+ */
+ setup_passphrases("sponge2", NULL);
+ test(0, "puttygen", tmpfilename1, "-C", "new-comment-2",
+ "-o", tmpfilename2, NULL);
+
+ /*
+ * See if the PuTTY key thus generated is the same as
+ * the original.
+ */
+ filecmp(filename, tmpfilename2,
+ "p->s->o->p encrypted %s", keytypes[i]);
+
+ /*
+ * Finally, do a round-trip conversion between PuTTY
+ * and ssh.com without involving OpenSSH, to test that
+ * the key comment is preserved in that case.
+ */
+ setup_passphrases("sponge2", NULL);
+ test(0, "puttygen", "-O", "private-sshcom", "-o", tmpfilename1,
+ filename, NULL);
+ setup_passphrases("sponge2", NULL);
+ test(0, "puttygen", tmpfilename1, "-o", tmpfilename2, NULL);
+ filecmp(filename, tmpfilename2,
+ "p->s->p encrypted %s", keytypes[i]);
+ }
+
+ /*
+ * Load with the wrong passphrase.
+ */
+ setup_passphrases("sponge8", NULL);
+ test(1, "puttygen", "-C", "spurious-new-comment", filename, NULL);
+
+ /*
+ * Load a totally bogus file.
+ */
+ setup_passphrases(NULL);
+ test(1, "puttygen", "-C", "spurious-new-comment", pubfilename, NULL);
+ }
+ printf("%d passes, %d fails\n", passes, fails);
+ return 0;
+}
+
+#endif
--- /dev/null
+/*
+ * cmdline.c - command-line parsing shared between many of the
+ * PuTTY applications
+ */
+
+#include <stdio.h>
+#include <assert.h>
+#include <stdlib.h>
+#include "putty.h"
+
+/*
+ * Some command-line parameters need to be saved up until after
+ * we've loaded the saved session which will form the basis of our
+ * eventual running configuration. For this we use the macro
+ * SAVEABLE, which notices if the `need_save' parameter is set and
+ * saves the parameter and value on a list.
+ *
+ * We also assign priorities to saved parameters, just to slightly
+ * ameliorate silly ordering problems. For example, if you specify
+ * a saved session to load, it will be loaded _before_ all your
+ * local modifications such as -L are evaluated; and if you specify
+ * a protocol and a port, the protocol is set up first so that the
+ * port can override its choice of port number.
+ *
+ * (In fact -load is not saved at all, since in at least Plink the
+ * processing of further command-line options depends on whether or
+ * not the loaded session contained a hostname. So it must be
+ * executed immediately.)
+ */
+
+#define NPRIORITIES 2
+
+struct cmdline_saved_param {
+ char *p, *value;
+};
+struct cmdline_saved_param_set {
+ struct cmdline_saved_param *params;
+ int nsaved, savesize;
+};
+
+/*
+ * C guarantees this structure will be initialised to all zero at
+ * program start, which is exactly what we want.
+ */
+static struct cmdline_saved_param_set saves[NPRIORITIES];
+
+static void cmdline_save_param(char *p, char *value, int pri)
+{
+ if (saves[pri].nsaved >= saves[pri].savesize) {
+ saves[pri].savesize = saves[pri].nsaved + 32;
+ saves[pri].params = sresize(saves[pri].params, saves[pri].savesize,
+ struct cmdline_saved_param);
+ }
+ saves[pri].params[saves[pri].nsaved].p = p;
+ saves[pri].params[saves[pri].nsaved].value = value;
+ saves[pri].nsaved++;
+}
+
+static char *cmdline_password = NULL;
+
+void cmdline_cleanup(void)
+{
+ int pri;
+
+ if (cmdline_password) {
+ memset(cmdline_password, 0, strlen(cmdline_password));
+ sfree(cmdline_password);
+ cmdline_password = NULL;
+ }
+
+ for (pri = 0; pri < NPRIORITIES; pri++) {
+ sfree(saves[pri].params);
+ saves[pri].params = NULL;
+ saves[pri].savesize = 0;
+ saves[pri].nsaved = 0;
+ }
+}
+
+#define SAVEABLE(pri) do { \
+ if (need_save) { cmdline_save_param(p, value, pri); return ret; } \
+} while (0)
+
+/*
+ * Similar interface to get_userpass_input(), except that here a -1
+ * return means that we aren't capable of processing the prompt and
+ * someone else should do it.
+ */
+int cmdline_get_passwd_input(prompts_t *p, unsigned char *in, int inlen) {
+
+ static int tried_once = 0;
+
+ /*
+ * We only handle prompts which don't echo (which we assume to be
+ * passwords), and (currently) we only cope with a password prompt
+ * that comes in a prompt-set on its own.
+ */
+ if (!cmdline_password || in || p->n_prompts != 1 || p->prompts[0]->echo) {
+ return -1;
+ }
+
+ /*
+ * If we've tried once, return utter failure (no more passwords left
+ * 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));
+ 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
+ * refuse certain command-line options if a particular tool
+ * inherently can't do anything sensible. For example, the file
+ * transfer tools (psftp, pscp) can't do a great deal with protocol
+ * selections (ever tried running scp over telnet?) or with port
+ * forwarding (even if it wasn't a hideously bad idea, they don't
+ * have the select() infrastructure to make them work).
+ */
+int cmdline_tooltype = 0;
+
+static int cmdline_check_unavailable(int flag, char *p)
+{
+ if (cmdline_tooltype & flag) {
+ cmdline_error("option \"%s\" not available in this tool", p);
+ return 1;
+ }
+ return 0;
+}
+
+#define UNAVAILABLE_IN(flag) do { \
+ if (cmdline_check_unavailable(flag, p)) return ret; \
+} while (0)
+
+/*
+ * Process a standard command-line parameter. `p' is the parameter
+ * in question; `value' is the subsequent element of argv, which
+ * may or may not be required as an operand to the parameter.
+ * If `need_save' is 1, arguments which need to be saved as
+ * described at this top of this file are, for later execution;
+ * if 0, they are processed normally. (-1 is a special value used
+ * by pterm to count arguments for a preliminary pass through the
+ * argument list; it causes immediate return with an appropriate
+ * value with no action taken.)
+ * Return value is 2 if both arguments were used; 1 if only p was
+ * used; 0 if the parameter wasn't one we recognised; -2 if it
+ * should have been 2 but value was NULL.
+ */
+
+#define RETURN(x) do { \
+ 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 ret = 0;
+
+ if (!strcmp(p, "-load")) {
+ RETURN(2);
+ /* This parameter must be processed immediately rather than being
+ * saved. */
+ do_defaults(value, cfg);
+ 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;
+ 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;
+ 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;
+ return 1;
+ }
+ if (!strcmp(p, "-raw")) {
+ RETURN(1);
+ UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK);
+ SAVEABLE(0);
+ default_protocol = cfg->protocol = PROT_RAW;
+ }
+ 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';
+ }
+ 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';
+ }
+ 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';
+ }
+ if ((!strcmp(p, "-L") || !strcmp(p, "-R") || !strcmp(p, "-D"))) {
+ char *fwd, *ptr, *q, *qq;
+ int dynamic, i=0;
+ 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) {
+ /*
+ * 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.
+ */
+ q = qq = strchr(ptr, ':');
+ while (qq) {
+ char *qqq = strchr(qq+1, ':');
+ if (qqq)
+ q = qq;
+ qq = qqq;
+ }
+ if (q) *q = '\t'; /* replace second-last colon with \t */
+ }
+ cfg->portfwd[sizeof(cfg->portfwd) - 1] = '\0';
+ cfg->portfwd[sizeof(cfg->portfwd) - 2] = '\0';
+ ptr[strlen(ptr)+1] = '\000'; /* append 2nd '\000' */
+ }
+ 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 {
+ cmdline_error("-nc expects argument of form 'host:port'");
+ return ret;
+ }
+ }
+ if (!strcmp(p, "-m")) {
+ char *filename, *command;
+ int cmdlen, cmdsize;
+ FILE *fp;
+ int c, d;
+
+ RETURN(2);
+ UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK);
+ SAVEABLE(0);
+
+ filename = value;
+
+ cmdlen = cmdsize = 0;
+ command = NULL;
+ fp = fopen(filename, "r");
+ if (!fp) {
+ cmdline_error("unable to open command "
+ "file \"%s\"", filename);
+ return ret;
+ }
+ do {
+ c = fgetc(fp);
+ d = c;
+ if (c == EOF)
+ d = 0;
+ if (cmdlen >= cmdsize) {
+ 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);
+ }
+ if (!strcmp(p, "-P")) {
+ RETURN(2);
+ UNAVAILABLE_IN(TOOLTYPE_NONNETWORK);
+ SAVEABLE(1); /* lower priority than -ssh,-telnet */
+ cfg->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)
+ 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));
+ }
+ }
+
+ if (!strcmp(p, "-agent") || !strcmp(p, "-pagent") ||
+ !strcmp(p, "-pageant")) {
+ RETURN(1);
+ UNAVAILABLE_IN(TOOLTYPE_NONNETWORK);
+ SAVEABLE(0);
+ cfg->tryagent = TRUE;
+ }
+ if (!strcmp(p, "-noagent") || !strcmp(p, "-nopagent") ||
+ !strcmp(p, "-nopageant")) {
+ RETURN(1);
+ UNAVAILABLE_IN(TOOLTYPE_NONNETWORK);
+ SAVEABLE(0);
+ cfg->tryagent = FALSE;
+ }
+
+ if (!strcmp(p, "-A")) {
+ RETURN(1);
+ UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK);
+ SAVEABLE(0);
+ cfg->agentfwd = 1;
+ }
+ if (!strcmp(p, "-a")) {
+ RETURN(1);
+ UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK);
+ SAVEABLE(0);
+ cfg->agentfwd = 0;
+ }
+
+ if (!strcmp(p, "-X")) {
+ RETURN(1);
+ UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK);
+ SAVEABLE(0);
+ cfg->x11_forward = 1;
+ }
+ if (!strcmp(p, "-x")) {
+ RETURN(1);
+ UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK);
+ SAVEABLE(0);
+ cfg->x11_forward = 0;
+ }
+
+ if (!strcmp(p, "-t")) {
+ RETURN(1);
+ UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK);
+ SAVEABLE(1); /* lower priority than -m */
+ cfg->nopty = 0;
+ }
+ if (!strcmp(p, "-T")) {
+ RETURN(1);
+ UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK);
+ SAVEABLE(1);
+ cfg->nopty = 1;
+ }
+
+ if (!strcmp(p, "-N")) {
+ RETURN(1);
+ UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK);
+ SAVEABLE(0);
+ cfg->ssh_no_shell = 1;
+ }
+
+ if (!strcmp(p, "-C")) {
+ RETURN(1);
+ UNAVAILABLE_IN(TOOLTYPE_NONNETWORK);
+ SAVEABLE(0);
+ cfg->compression = 1;
+ }
+
+ if (!strcmp(p, "-1")) {
+ RETURN(1);
+ UNAVAILABLE_IN(TOOLTYPE_NONNETWORK);
+ SAVEABLE(0);
+ cfg->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 */
+ }
+
+ if (!strcmp(p, "-i")) {
+ RETURN(2);
+ UNAVAILABLE_IN(TOOLTYPE_NONNETWORK);
+ SAVEABLE(0);
+ cfg->keyfile = filename_from_str(value);
+ }
+
+ if (!strcmp(p, "-4") || !strcmp(p, "-ipv4")) {
+ RETURN(1);
+ SAVEABLE(1);
+ cfg->addressfamily = ADDRTYPE_IPV4;
+ }
+ if (!strcmp(p, "-6") || !strcmp(p, "-ipv6")) {
+ RETURN(1);
+ SAVEABLE(1);
+ cfg->addressfamily = ADDRTYPE_IPV6;
+ }
+ if (!strcmp(p, "-sercfg")) {
+ char* nextitem;
+ RETURN(2);
+ UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK);
+ SAVEABLE(1);
+ if (cfg->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') {
+ int length, skip;
+ char *end = strchr(nextitem, ',');
+ if (!end) {
+ length = strlen(nextitem);
+ skip = 0;
+ } else {
+ length = end - nextitem;
+ nextitem[length] = '\0';
+ skip = 1;
+ }
+ if (length == 1) {
+ switch (*nextitem) {
+ case '1':
+ cfg->serstopbits = 2;
+ break;
+ case '2':
+ cfg->serstopbits = 4;
+ 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;
+ break;
+
+ case 'n':
+ cfg->serparity = SER_PAR_NONE;
+ break;
+ case 'o':
+ cfg->serparity = SER_PAR_ODD;
+ break;
+ case 'e':
+ cfg->serparity = SER_PAR_EVEN;
+ break;
+ case 'm':
+ cfg->serparity = SER_PAR_MARK;
+ break;
+ case 's':
+ cfg->serparity = SER_PAR_SPACE;
+ break;
+
+ case 'N':
+ cfg->serflow = SER_FLOW_NONE;
+ break;
+ case 'X':
+ cfg->serflow = SER_FLOW_XONXOFF;
+ break;
+ case 'R':
+ cfg->serflow = SER_FLOW_RTSCTS;
+ break;
+ case 'D':
+ cfg->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;
+ } else {
+ int serspeed = atoi(nextitem);
+ if (serspeed != 0) {
+ cfg->serspeed = serspeed;
+ } else {
+ cmdline_error("Unrecognised suboption \"-sercfg %s\"",
+ nextitem);
+ }
+ }
+ nextitem += length + skip;
+ }
+ }
+ return ret; /* unrecognised */
+}
+
+void cmdline_run_saved(Config *cfg)
+{
+ 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);
+}
--- /dev/null
+/*
+ * config.c - the platform-independent parts of the PuTTY
+ * configuration box.
+ */
+
+#include <assert.h>
+#include <stdlib.h>
+
+#include "putty.h"
+#include "dialog.h"
+#include "storage.h"
+
+#define PRINTER_DISABLED_STRING "None (printing disabled)"
+
+#define HOST_BOX_TITLE "Host Name (or IP address)"
+#define PORT_BOX_TITLE "Port"
+
+static void config_host_handler(union control *ctrl, void *dlg,
+ void *data, int event)
+{
+ Config *cfg = (Config *)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) {
+ /*
+ * 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);
+ } else {
+ dlg_label_change(ctrl, dlg, HOST_BOX_TITLE);
+ dlg_editbox_set(ctrl, dlg, cfg->host);
+ }
+ } else if (event == EVENT_VALCHANGE) {
+ if (cfg->protocol == PROT_SERIAL)
+ dlg_editbox_get(ctrl, dlg, cfg->serline, lenof(cfg->serline));
+ else
+ dlg_editbox_get(ctrl, dlg, cfg->host, lenof(cfg->host));
+ }
+}
+
+static void config_port_handler(union control *ctrl, void *dlg,
+ void *data, int event)
+{
+ Config *cfg = (Config *)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) {
+ /*
+ * 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);
+ } else {
+ dlg_label_change(ctrl, dlg, PORT_BOX_TITLE);
+ if (cfg->port != 0)
+ sprintf(buf, "%d", cfg->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);
+ else
+ cfg->port = atoi(buf);
+ }
+}
+
+struct hostport {
+ union control *host, *port;
+};
+
+/*
+ * We export this function so that platform-specific config
+ * routines can use it to conveniently identify the protocol radio
+ * buttons in order to add to them.
+ */
+void config_protocolbuttons_handler(union control *ctrl, void *dlg,
+ void *data, int event)
+{
+ int button;
+ Config *cfg = (Config *)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
+ * 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) {
+ for (button = 0; button < ctrl->radio.nbuttons; button++)
+ if (cfg->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;
+ button = dlg_radiobutton_get(ctrl, dlg);
+ assert(button >= 0 && button < ctrl->radio.nbuttons);
+ cfg->protocol = ctrl->radio.buttondata[button].i;
+ if (oldproto != cfg->protocol) {
+ Backend *ob = backend_from_proto(oldproto);
+ Backend *nb = backend_from_proto(cfg->protocol);
+ 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
+ * sensible default for that protocol; in this case it's
+ * 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;
+ }
+ dlg_refresh(hp->host, dlg);
+ dlg_refresh(hp->port, dlg);
+ }
+}
+
+static void loggingbuttons_handler(union control *ctrl, void *dlg,
+ void *data, int event)
+{
+ int button;
+ Config *cfg = (Config *)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) {
+ for (button = 0; button < ctrl->radio.nbuttons; button++)
+ if (cfg->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);
+ } 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;
+ }
+}
+
+static void numeric_keypad_handler(union control *ctrl, void *dlg,
+ void *data, int event)
+{
+ int button;
+ Config *cfg = (Config *)data;
+ /*
+ * This function works much like the standard radio button
+ * handler, but it has to handle two fields in Config.
+ */
+ if (event == EVENT_REFRESH) {
+ if (cfg->nethack_keypad)
+ button = 2;
+ else if (cfg->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;
+ } else {
+ cfg->app_keypad = (button != 0);
+ cfg->nethack_keypad = FALSE;
+ }
+ }
+}
+
+static void cipherlist_handler(union control *ctrl, void *dlg,
+ void *data, int event)
+{
+ Config *cfg = (Config *)data;
+ if (event == EVENT_REFRESH) {
+ int i;
+
+ static const struct { char *s; int c; } ciphers[] = {
+ { "3DES", CIPHER_3DES },
+ { "Blowfish", CIPHER_BLOWFISH },
+ { "DES", CIPHER_DES },
+ { "AES (SSH-2 only)", CIPHER_AES },
+ { "Arcfour (SSH-2 only)", CIPHER_ARCFOUR },
+ { "-- warn below here --", CIPHER_WARN }
+ };
+
+ /* 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 j;
+ char *cstr = NULL;
+ for (j = 0; j < (sizeof ciphers) / (sizeof ciphers[0]); j++) {
+ if (ciphers[j].c == c) {
+ cstr = ciphers[j].s;
+ break;
+ }
+ }
+ dlg_listbox_addwithid(ctrl, dlg, cstr, c);
+ }
+ dlg_update_done(ctrl, dlg);
+
+ } 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);
+
+ }
+}
+
+#ifndef NO_GSSAPI
+static void gsslist_handler(union control *ctrl, void *dlg,
+ void *data, int event)
+{
+ Config *cfg = (Config *)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];
+ assert(id >= 0 && id < ngsslibs);
+ dlg_listbox_addwithid(ctrl, dlg, gsslibnames[id], id);
+ }
+ dlg_update_done(ctrl, dlg);
+
+ } 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);
+ }
+}
+#endif
+
+static void kexlist_handler(union control *ctrl, void *dlg,
+ void *data, int event)
+{
+ Config *cfg = (Config *)data;
+ if (event == EVENT_REFRESH) {
+ int i;
+
+ static const struct { char *s; int k; } kexes[] = {
+ { "Diffie-Hellman group 1", KEX_DHGROUP1 },
+ { "Diffie-Hellman group 14", KEX_DHGROUP14 },
+ { "Diffie-Hellman group exchange", KEX_DHGEX },
+ { "RSA-based key exchange", KEX_RSA },
+ { "-- warn below here --", KEX_WARN }
+ };
+
+ /* 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 j;
+ char *kstr = NULL;
+ for (j = 0; j < (sizeof kexes) / (sizeof kexes[0]); j++) {
+ if (kexes[j].k == k) {
+ kstr = kexes[j].s;
+ break;
+ }
+ }
+ dlg_listbox_addwithid(ctrl, dlg, kstr, k);
+ }
+ dlg_update_done(ctrl, dlg);
+
+ } 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);
+
+ }
+}
+
+static void printerbox_handler(union control *ctrl, void *dlg,
+ void *data, int event)
+{
+ Config *cfg = (Config *)data;
+ if (event == EVENT_REFRESH) {
+ int nprinters, i;
+ printer_enum *pe;
+
+ dlg_update_start(ctrl, dlg);
+ /*
+ * Some backends may wish to disable the drop-down list on
+ * this edit box. Be prepared for this.
+ */
+ if (ctrl->editbox.has_list) {
+ dlg_listbox_clear(ctrl, dlg);
+ dlg_listbox_add(ctrl, dlg, PRINTER_DISABLED_STRING);
+ 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));
+ 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';
+ }
+}
+
+static void codepage_handler(union control *ctrl, void *dlg,
+ void *data, int event)
+{
+ Config *cfg = (Config *)data;
+ if (event == EVENT_REFRESH) {
+ int i;
+ const char *cp, *thiscp;
+ dlg_update_start(ctrl, dlg);
+ thiscp = cp_name(decode_codepage(cfg->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);
+ 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)));
+ }
+}
+
+static void sshbug_handler(union control *ctrl, void *dlg,
+ void *data, int event)
+{
+ if (event == EVENT_REFRESH) {
+ 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)) {
+ 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);
+ } else if (event == EVENT_SELCHANGE) {
+ 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;
+ }
+}
+
+#define SAVEDSESSION_LEN 2048
+
+struct sessionsaver_data {
+ union control *editbox, *listbox, *loadbutton, *savebutton, *delbutton;
+ union control *okbutton, *cancelbutton;
+ struct sesslist sesslist;
+ int midsession;
+};
+
+/*
+ * 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)
+{
+ 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;
+ }
+ 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;
+}
+
+static void sessionsaver_handler(union control *ctrl, void *dlg,
+ void *data, int event)
+{
+ Config *cfg = (Config *)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);
+ } 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++)
+ dlg_listbox_add(ctrl, dlg, ssd->sesslist.sessions[i]);
+ 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);
+ top = ssd->sesslist.nsessions;
+ bottom = -1;
+ while (top-bottom > 1) {
+ halfway = (top+bottom)/2;
+ i = strcmp(savedsession, ssd->sesslist.sessions[halfway]);
+ if (i <= 0 ) {
+ top = halfway;
+ } else {
+ bottom = halfway;
+ }
+ }
+ if (top == ssd->sesslist.nsessions) {
+ top -= 1;
+ }
+ dlg_listbox_select(ssd->listbox, dlg, top);
+ }
+ } else if (event == EVENT_ACTION) {
+ int mbl = FALSE;
+ if (!ssd->midsession &&
+ (ctrl == ssd->listbox ||
+ (ssd->loadbutton && ctrl == ssd->loadbutton))) {
+ /*
+ * The user has double-clicked a session, or hit Load.
+ * 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))) {
+ 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 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';
+ }
+ }
+ {
+ char *errmsg = save_settings(savedsession, cfg);
+ if (errmsg) {
+ dlg_error_msg(dlg, errmsg);
+ sfree(errmsg);
+ }
+ }
+ get_sesslist(&ssd->sesslist, FALSE);
+ get_sesslist(&ssd->sesslist, TRUE);
+ dlg_refresh(ssd->editbox, dlg);
+ dlg_refresh(ssd->listbox, dlg);
+ } else if (!ssd->midsession &&
+ ssd->delbutton && ctrl == ssd->delbutton) {
+ int i = dlg_listbox_index(ssd->listbox, dlg);
+ if (i <= 0) {
+ dlg_beep(dlg);
+ } else {
+ del_settings(ssd->sesslist.sessions[i]);
+ get_sesslist(&ssd->sesslist, FALSE);
+ get_sesslist(&ssd->sesslist, TRUE);
+ dlg_refresh(ssd->listbox, dlg);
+ }
+ } else if (ctrl == ssd->okbutton) {
+ if (ssd->midsession) {
+ /* In a mid-session Change Settings, Apply is always OK. */
+ dlg_end(dlg, 1);
+ return;
+ }
+ /*
+ * Annoying special case. If the `Open' button is
+ * pressed while no host name is currently set, _and_
+ * 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;
+ int mbl = FALSE;
+ if (!load_selected_session(ssd, savedsession, dlg,
+ &cfg2, &mbl)) {
+ dlg_beep(dlg);
+ 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;
+ dlg_end(dlg, 1);
+ } else
+ dlg_beep(dlg);
+ return;
+ }
+
+ /*
+ * Otherwise, do the normal thing: if we have a valid
+ * session, get going.
+ */
+ if (cfg_launchable(cfg)) {
+ dlg_end(dlg, 1);
+ } else
+ dlg_beep(dlg);
+ } else if (ctrl == ssd->cancelbutton) {
+ dlg_end(dlg, 0);
+ }
+ }
+}
+
+struct charclass_data {
+ union control *listbox, *editbox, *button;
+};
+
+static void charclass_handler(union control *ctrl, void *dlg,
+ void *data, int event)
+{
+ Config *cfg = (Config *)data;
+ struct charclass_data *ccd =
+ (struct charclass_data *)ctrl->generic.context.p;
+
+ if (event == EVENT_REFRESH) {
+ if (ctrl == ccd->listbox) {
+ int i;
+ 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]);
+ dlg_listbox_add(ctrl, dlg, str);
+ }
+ dlg_update_done(ctrl, dlg);
+ }
+ } else if (event == EVENT_ACTION) {
+ if (ctrl == ccd->button) {
+ char str[100];
+ int i, n;
+ dlg_editbox_get(ccd->editbox, dlg, str, sizeof(str));
+ n = atoi(str);
+ for (i = 0; i < 128; i++) {
+ if (dlg_listbox_issel(ccd->listbox, dlg, i))
+ cfg->wordness[i] = n;
+ }
+ dlg_refresh(ccd->listbox, dlg);
+ }
+ }
+}
+
+struct colour_data {
+ union control *listbox, *redit, *gedit, *bedit, *button;
+};
+
+static const char *const colours[] = {
+ "Default Foreground", "Default Bold Foreground",
+ "Default Background", "Default Bold Background",
+ "Cursor Text", "Cursor Colour",
+ "ANSI Black", "ANSI Black Bold",
+ "ANSI Red", "ANSI Red Bold",
+ "ANSI Green", "ANSI Green Bold",
+ "ANSI Yellow", "ANSI Yellow Bold",
+ "ANSI Blue", "ANSI Blue Bold",
+ "ANSI Magenta", "ANSI Magenta Bold",
+ "ANSI Cyan", "ANSI Cyan Bold",
+ "ANSI White", "ANSI White Bold"
+};
+
+static void colour_handler(union control *ctrl, void *dlg,
+ void *data, int event)
+{
+ Config *cfg = (Config *)data;
+ struct colour_data *cd =
+ (struct colour_data *)ctrl->generic.context.p;
+ int update = FALSE, clear = FALSE, r, g, b;
+
+ if (event == EVENT_REFRESH) {
+ if (ctrl == cd->listbox) {
+ int i;
+ dlg_update_start(ctrl, dlg);
+ dlg_listbox_clear(ctrl, dlg);
+ for (i = 0; i < lenof(colours); i++)
+ dlg_listbox_add(ctrl, dlg, colours[i]);
+ dlg_update_done(ctrl, dlg);
+ clear = TRUE;
+ update = TRUE;
+ }
+ } else if (event == EVENT_SELCHANGE) {
+ if (ctrl == cd->listbox) {
+ /* The user has selected a colour. Update the RGB text. */
+ 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];
+ }
+ 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];
+ int i, cval;
+
+ dlg_editbox_get(ctrl, dlg, buf, lenof(buf));
+ cval = atoi(buf);
+ 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;
+ else if (ctrl == cd->gedit)
+ cfg->colours[i][1] = cval;
+ else if (ctrl == cd->bedit)
+ cfg->colours[i][2] = cval;
+ }
+ }
+ } else if (event == EVENT_ACTION) {
+ if (ctrl == cd->button) {
+ int i = dlg_listbox_index(cd->listbox, dlg);
+ if (i < 0) {
+ dlg_beep(dlg);
+ return;
+ }
+ /*
+ * 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]);
+ }
+ } else if (event == EVENT_CALLBACK) {
+ if (ctrl == cd->button) {
+ int i = dlg_listbox_index(cd->listbox, dlg);
+ /*
+ * 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;
+ clear = FALSE;
+ update = TRUE;
+ }
+ }
+ }
+
+ if (update) {
+ if (clear) {
+ dlg_editbox_set(cd->redit, dlg, "");
+ dlg_editbox_set(cd->gedit, dlg, "");
+ dlg_editbox_set(cd->bedit, dlg, "");
+ } else {
+ char buf[40];
+ sprintf(buf, "%d", r); dlg_editbox_set(cd->redit, dlg, buf);
+ sprintf(buf, "%d", g); dlg_editbox_set(cd->gedit, dlg, buf);
+ sprintf(buf, "%d", b); dlg_editbox_set(cd->bedit, dlg, buf);
+ }
+ }
+}
+
+struct ttymodes_data {
+ union control *modelist, *valradio, *valbox;
+ union control *addbutton, *rembutton, *listbox;
+};
+
+static void ttymodes_handler(union control *ctrl, void *dlg,
+ void *data, int event)
+{
+ Config *cfg = (Config *)data;
+ struct ttymodes_data *td =
+ (struct ttymodes_data *)ctrl->generic.context.p;
+
+ if (event == EVENT_REFRESH) {
+ if (ctrl == td->listbox) {
+ char *p = cfg->ttymodes;
+ 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);
+ dlg_listbox_add(ctrl, dlg, disp);
+ p += strlen(p) + 1;
+ sfree(disp);
+ }
+ dlg_update_done(ctrl, dlg);
+ } else if (ctrl == td->modelist) {
+ int i;
+ dlg_update_start(ctrl, dlg);
+ dlg_listbox_clear(ctrl, dlg);
+ for (i = 0; ttymodes[i]; i++)
+ dlg_listbox_add(ctrl, dlg, ttymodes[i]);
+ dlg_listbox_select(ctrl, dlg, 0); /* *shrug* */
+ dlg_update_done(ctrl, dlg);
+ } else if (ctrl == td->valradio) {
+ dlg_radiobutton_set(ctrl, dlg, 0);
+ }
+ } 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)];
+ /* 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);
+ 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;
+ 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);
+ dlg_refresh(td->listbox, dlg);
+ }
+ }
+}
+
+struct environ_data {
+ union control *varbox, *valbox, *addbutton, *rembutton, *listbox;
+};
+
+static void environ_handler(union control *ctrl, void *dlg,
+ void *data, int event)
+{
+ Config *cfg = (Config *)data;
+ struct environ_data *ed =
+ (struct environ_data *)ctrl->generic.context.p;
+
+ if (event == EVENT_REFRESH) {
+ if (ctrl == ed->listbox) {
+ char *p = cfg->environmt;
+ dlg_update_start(ctrl, dlg);
+ dlg_listbox_clear(ctrl, dlg);
+ while (*p) {
+ dlg_listbox_add(ctrl, dlg, p);
+ p += strlen(p) + 1;
+ }
+ 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");
+ }
+ } 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:;
+ }
+ }
+ }
+}
+
+struct portfwd_data {
+ union control *addbutton, *rembutton, *listbox;
+ union control *sourcebox, *destbox, *direction;
+#ifndef NO_IPV6
+ union control *addressfamily;
+#endif
+};
+
+static void portfwd_handler(union control *ctrl, void *dlg,
+ void *data, int event)
+{
+ Config *cfg = (Config *)data;
+ struct portfwd_data *pfd =
+ (struct portfwd_data *)ctrl->generic.context.p;
+
+ if (event == EVENT_REFRESH) {
+ if (ctrl == pfd->listbox) {
+ char *p = cfg->portfwd;
+ dlg_update_start(ctrl, dlg);
+ dlg_listbox_clear(ctrl, dlg);
+ while (*p) {
+ dlg_listbox_add(ctrl, dlg, p);
+ p += strlen(p) + 1;
+ }
+ dlg_update_done(ctrl, dlg);
+ } else if (ctrl == pfd->direction) {
+ /*
+ * Default is Local.
+ */
+ dlg_radiobutton_set(ctrl, dlg, 0);
+#ifndef NO_IPV6
+ } else if (ctrl == pfd->addressfamily) {
+ 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;
+ int whichbutton;
+
+ i = 0;
+#ifndef NO_IPV6
+ whichbutton = dlg_radiobutton_get(pfd->addressfamily, dlg);
+ if (whichbutton == 1)
+ str[i++] = '4';
+ else if (whichbutton == 2)
+ str[i++] = '6';
+#endif
+
+ whichbutton = dlg_radiobutton_get(pfd->direction, dlg);
+ if (whichbutton == 0)
+ type = 'L';
+ else if (whichbutton == 1)
+ type = 'R';
+ else
+ type = 'D';
+ str[i++] = type;
+
+ dlg_editbox_get(pfd->sourcebox, dlg, str+i, sizeof(str) - i);
+ if (!str[i]) {
+ dlg_error_msg(dlg, "You need to specify a source port number");
+ 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, ':')) {
+ dlg_error_msg(dlg,
+ "You need to specify a destination address\n"
+ "in the form \"host.name:port\"");
+ 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 if (ctrl == pfd->rembutton) {
+ int i = dlg_listbox_index(pfd->listbox, dlg);
+ 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. */
+ {
+ static const char *const afs = "A46";
+ char *afp = strchr(afs, *p);
+#ifndef NO_IPV6
+ int 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;
+ 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:;
+ }
+ }
+ }
+}
+
+void setup_config_box(struct controlbox *b, int midsession,
+ int protocol, int protcfginfo)
+{
+ struct controlset *s;
+ struct sessionsaver_data *ssd;
+ struct charclass_data *ccd;
+ struct colour_data *cd;
+ 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));
+ memset(ssd, 0, sizeof(*ssd));
+ ssd->midsession = midsession;
+
+ /*
+ * The standard panel that appears at the bottom of all panels:
+ * Open, Cancel, Apply etc.
+ */
+ s = ctrl_getset(b, "", "", "");
+ ctrl_columns(s, 5, 20, 20, 20, 20, 20);
+ ssd->okbutton = ctrl_pushbutton(s,
+ (midsession ? "Apply" : "Open"),
+ (char)(midsession ? 'a' : 'o'),
+ HELPCTX(no_help),
+ sessionsaver_handler, P(ssd));
+ ssd->okbutton->button.isdefault = TRUE;
+ ssd->okbutton->generic.column = 3;
+ ssd->cancelbutton = ctrl_pushbutton(s, "Cancel", 'c', HELPCTX(no_help),
+ sessionsaver_handler, P(ssd));
+ ssd->cancelbutton->button.iscancel = TRUE;
+ ssd->cancelbutton->generic.column = 4;
+ /* We carefully don't close the 5-column part, so that platform-
+ * specific add-ons can put extra buttons alongside Open and Cancel. */
+
+ /*
+ * The Session panel.
+ */
+ str = dupprintf("Basic options for your %s session", appname);
+ ctrl_settitle(b, "Session", str);
+ sfree(str);
+
+ if (!midsession) {
+ struct hostport *hp = (struct hostport *)
+ ctrl_alloc(b, sizeof(struct hostport));
+
+ s = ctrl_getset(b, "Session", "hostport",
+ "Specify the destination you want to connect to");
+ ctrl_columns(s, 2, 75, 25);
+ c = ctrl_editbox(s, HOST_BOX_TITLE, 'n', 100,
+ HELPCTX(session_hostname),
+ config_host_handler, I(0), I(0));
+ c->generic.column = 0;
+ hp->host = c;
+ c = ctrl_editbox(s, PORT_BOX_TITLE, 'p', 100,
+ HELPCTX(session_hostname),
+ config_port_handler, I(0), I(0));
+ c->generic.column = 1;
+ hp->port = c;
+ ctrl_columns(s, 1, 100);
+
+ if (!backend_from_proto(PROT_SSH)) {
+ ctrl_radiobuttons(s, "Connection type:", NO_SHORTCUT, 3,
+ HELPCTX(session_hostname),
+ config_protocolbuttons_handler, P(hp),
+ "Raw", 'w', I(PROT_RAW),
+ "Telnet", 't', I(PROT_TELNET),
+ "Rlogin", 'i', I(PROT_RLOGIN),
+ NULL);
+ } else {
+ ctrl_radiobuttons(s, "Connection type:", NO_SHORTCUT, 4,
+ HELPCTX(session_hostname),
+ config_protocolbuttons_handler, P(hp),
+ "Raw", 'w', I(PROT_RAW),
+ "Telnet", 't', I(PROT_TELNET),
+ "Rlogin", 'i', I(PROT_RLOGIN),
+ "SSH", 's', I(PROT_SSH),
+ NULL);
+ }
+ }
+
+ /*
+ * The Load/Save panel is available even in mid-session.
+ */
+ s = ctrl_getset(b, "Session", "savedsessions",
+ midsession ? "Save the current session settings" :
+ "Load, save or delete a stored session");
+ ctrl_columns(s, 2, 75, 25);
+ get_sesslist(&ssd->sesslist, TRUE);
+ ssd->editbox = ctrl_editbox(s, "Saved Sessions", 'e', 100,
+ HELPCTX(session_saved),
+ sessionsaver_handler, P(ssd), P(NULL));
+ ssd->editbox->generic.column = 0;
+ /* Reset columns so that the buttons are alongside the list, rather
+ * than alongside that edit box. */
+ ctrl_columns(s, 1, 100);
+ ctrl_columns(s, 2, 75, 25);
+ ssd->listbox = ctrl_listbox(s, NULL, NO_SHORTCUT,
+ HELPCTX(session_saved),
+ sessionsaver_handler, P(ssd));
+ ssd->listbox->generic.column = 0;
+ ssd->listbox->listbox.height = 7;
+ if (!midsession) {
+ ssd->loadbutton = ctrl_pushbutton(s, "Load", 'l',
+ HELPCTX(session_saved),
+ sessionsaver_handler, P(ssd));
+ ssd->loadbutton->generic.column = 1;
+ } else {
+ /* We can't offer the Load button mid-session, as it would allow the
+ * user to load and subsequently save settings they can't see. (And
+ * also change otherwise immutable settings underfoot; that probably
+ * shouldn't be a problem, but.) */
+ ssd->loadbutton = NULL;
+ }
+ /* "Save" button is permitted mid-session. */
+ ssd->savebutton = ctrl_pushbutton(s, "Save", 'v',
+ HELPCTX(session_saved),
+ sessionsaver_handler, P(ssd));
+ ssd->savebutton->generic.column = 1;
+ if (!midsession) {
+ ssd->delbutton = ctrl_pushbutton(s, "Delete", 'd',
+ HELPCTX(session_saved),
+ sessionsaver_handler, P(ssd));
+ ssd->delbutton->generic.column = 1;
+ } else {
+ /* Disable the Delete button mid-session too, for UI consistency. */
+ 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);
+
+ /*
+ * The Session/Logging panel.
+ */
+ ctrl_settitle(b, "Session/Logging", "Options controlling session logging");
+
+ s = ctrl_getset(b, "Session/Logging", "main", NULL);
+ /*
+ * The logging buttons change depending on whether SSH packet
+ * logging can sensibly be available.
+ */
+ {
+ char *sshlogname, *sshrawlogname;
+ if ((midsession && protocol == PROT_SSH) ||
+ (!midsession && backend_from_proto(PROT_SSH))) {
+ sshlogname = "SSH packets";
+ sshrawlogname = "SSH packets and raw data";
+ } else {
+ sshlogname = NULL; /* this will disable both buttons */
+ sshrawlogname = NULL; /* this will just placate optimisers */
+ }
+ ctrl_radiobuttons(s, "Session logging:", NO_SHORTCUT, 2,
+ HELPCTX(logging_main),
+ loggingbuttons_handler,
+ I(offsetof(Config, 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),
+ 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)));
+ 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)),
+ "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)));
+
+ 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)));
+ ctrl_checkbox(s, "Omit session data", 'd',
+ HELPCTX(logging_ssh_omit_data),
+ dlg_stdcheckbox_handler, I(offsetof(Config,logomitdata)));
+ }
+
+ /*
+ * The Terminal panel.
+ */
+ 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)));
+ ctrl_checkbox(s, "DEC Origin Mode initially on", 'd',
+ HELPCTX(terminal_decom),
+ dlg_stdcheckbox_handler, I(offsetof(Config,dec_om)));
+ ctrl_checkbox(s, "Implicit CR in every LF", 'r',
+ HELPCTX(terminal_lfhascr),
+ dlg_stdcheckbox_handler, I(offsetof(Config,lfhascr)));
+ ctrl_checkbox(s, "Implicit LF in every CR", 'f',
+ HELPCTX(terminal_crhaslf),
+ dlg_stdcheckbox_handler, I(offsetof(Config,crhaslf)));
+ ctrl_checkbox(s, "Use background colour to erase screen", 'e',
+ HELPCTX(terminal_bce),
+ dlg_stdcheckbox_handler, I(offsetof(Config,bce)));
+ ctrl_checkbox(s, "Enable blinking text", 'n',
+ HELPCTX(terminal_blink),
+ dlg_stdcheckbox_handler, I(offsetof(Config,blinktext)));
+ ctrl_editbox(s, "Answerback to ^E:", 's', 100,
+ HELPCTX(terminal_answerback),
+ dlg_stdeditbox_handler, I(offsetof(Config,answerback)),
+ I(sizeof(((Config *)0)->answerback)));
+
+ 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)),
+ "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)),
+ "Auto", I(AUTO),
+ "Force on", I(FORCE_ON),
+ "Force off", I(FORCE_OFF), NULL);
+
+ s = ctrl_getset(b, "Terminal", "printing", "Remote-controlled printing");
+ ctrl_combobox(s, "Printer to send ANSI printer output to:", 'p', 100,
+ HELPCTX(terminal_printing),
+ printerbox_handler, P(NULL), P(NULL));
+
+ /*
+ * The Terminal/Keyboard panel.
+ */
+ ctrl_settitle(b, "Terminal/Keyboard",
+ "Options controlling the effects of keys");
+
+ 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)),
+ "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)),
+ "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)),
+ "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)),
+ "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),
+ NULL);
+
+ /*
+ * The Terminal/Bell panel.
+ */
+ ctrl_settitle(b, "Terminal/Bell",
+ "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)),
+ "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)));
+ ctrl_editbox(s, "Over-use means this many bells...", 'm', 20,
+ HELPCTX(bell_overload),
+ dlg_stdeditbox_handler, I(offsetof(Config,bellovl_n)), I(-1));
+ ctrl_editbox(s, "... in this many seconds", 't', 20,
+ HELPCTX(bell_overload),
+ dlg_stdeditbox_handler, I(offsetof(Config,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)),
+ I(-TICKSPERSEC));
+
+ /*
+ * The Terminal/Features panel.
+ */
+ ctrl_settitle(b, "Terminal/Features",
+ "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)));
+ ctrl_checkbox(s, "Disable application keypad mode", 'k',
+ HELPCTX(features_application),
+ dlg_stdcheckbox_handler, I(offsetof(Config,no_applic_k)));
+ ctrl_checkbox(s, "Disable xterm-style mouse reporting", 'x',
+ HELPCTX(features_mouse),
+ dlg_stdcheckbox_handler, I(offsetof(Config,no_mouse_rep)));
+ ctrl_checkbox(s, "Disable remote-controlled terminal resizing", 's',
+ HELPCTX(features_resize),
+ dlg_stdcheckbox_handler,
+ I(offsetof(Config,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)));
+ ctrl_checkbox(s, "Disable remote-controlled window title changing", 't',
+ HELPCTX(features_retitle),
+ dlg_stdcheckbox_handler,
+ I(offsetof(Config,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)),
+ "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)));
+ ctrl_checkbox(s, "Disable remote-controlled character set configuration",
+ 'r', HELPCTX(features_charset), dlg_stdcheckbox_handler,
+ I(offsetof(Config,no_remote_charset)));
+ ctrl_checkbox(s, "Disable Arabic text shaping",
+ 'l', HELPCTX(features_arabicshaping), dlg_stdcheckbox_handler,
+ I(offsetof(Config, arabicshaping)));
+ ctrl_checkbox(s, "Disable bidirectional text display",
+ 'd', HELPCTX(features_bidi), dlg_stdcheckbox_handler,
+ I(offsetof(Config, bidi)));
+
+ /*
+ * The Window panel.
+ */
+ str = dupprintf("Options controlling %s's window", appname);
+ ctrl_settitle(b, "Window", str);
+ sfree(str);
+
+ 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));
+ c->generic.column = 0;
+ c = ctrl_editbox(s, "Rows", 'r', 100,
+ HELPCTX(window_size),
+ dlg_stdeditbox_handler, I(offsetof(Config,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));
+ ctrl_checkbox(s, "Display scrollbar", 'd',
+ HELPCTX(window_scrollback),
+ dlg_stdcheckbox_handler, I(offsetof(Config,scrollbar)));
+ ctrl_checkbox(s, "Reset scrollback on keypress", 'k',
+ HELPCTX(window_scrollback),
+ dlg_stdcheckbox_handler, I(offsetof(Config,scroll_on_key)));
+ ctrl_checkbox(s, "Reset scrollback on display activity", 'p',
+ HELPCTX(window_scrollback),
+ dlg_stdcheckbox_handler, I(offsetof(Config,scroll_on_disp)));
+ ctrl_checkbox(s, "Push erased text into scrollback", 'e',
+ HELPCTX(window_erased),
+ dlg_stdcheckbox_handler,
+ I(offsetof(Config,erase_to_scrollback)));
+
+ /*
+ * The Window/Appearance panel.
+ */
+ str = dupprintf("Configure the appearance of %s's window", appname);
+ ctrl_settitle(b, "Window/Appearance", str);
+ sfree(str);
+
+ 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)),
+ "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)));
+
+ 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)));
+
+ 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)));
+
+ 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));
+
+ /*
+ * The Window/Behaviour panel.
+ */
+ str = dupprintf("Configure the behaviour of %s's window", appname);
+ ctrl_settitle(b, "Window/Behaviour", str);
+ sfree(str);
+
+ 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)));
+ ctrl_checkbox(s, "Separate window and icon titles", 'i',
+ HELPCTX(appearance_title),
+ dlg_stdcheckbox_handler,
+ I(CHECKBOX_INVERT | offsetof(Config,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)));
+
+ /*
+ * The Window/Translation panel.
+ */
+ ctrl_settitle(b, "Window/Translation",
+ "Options controlling character set translation");
+
+ s = ctrl_getset(b, "Window/Translation", "trans",
+ "Character set translation");
+ ctrl_combobox(s, "Remote character set:",
+ 'r', 100, HELPCTX(translation_codepage),
+ 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)));
+
+ 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)),
+ "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)));
+
+ /*
+ * The Window/Selection panel.
+ */
+ ctrl_settitle(b, "Window/Selection", "Options controlling copy and paste");
+
+ 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)));
+ 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)),
+ "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");
+ ccd = (struct charclass_data *)
+ ctrl_alloc(b, sizeof(struct charclass_data));
+ ccd->listbox = ctrl_listbox(s, "Character classes:", 'e',
+ HELPCTX(selection_charclasses),
+ charclass_handler, P(ccd));
+ ccd->listbox->listbox.multisel = 1;
+ ccd->listbox->listbox.ncols = 4;
+ ccd->listbox->listbox.percentages = snewn(4, int);
+ ccd->listbox->listbox.percentages[0] = 15;
+ ccd->listbox->listbox.percentages[1] = 25;
+ ccd->listbox->listbox.percentages[2] = 20;
+ ccd->listbox->listbox.percentages[3] = 40;
+ ctrl_columns(s, 2, 67, 33);
+ ccd->editbox = ctrl_editbox(s, "Set to class", 't', 50,
+ HELPCTX(selection_charclasses),
+ charclass_handler, P(ccd), P(NULL));
+ ccd->editbox->generic.column = 0;
+ ccd->button = ctrl_pushbutton(s, "Set", 's',
+ HELPCTX(selection_charclasses),
+ charclass_handler, P(ccd));
+ ccd->button->generic.column = 1;
+ ctrl_columns(s, 1, 100);
+
+ /*
+ * The Window/Colours panel.
+ */
+ ctrl_settitle(b, "Window/Colours", "Options controlling use of colours");
+
+ 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)));
+ 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)));
+
+ 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"
+ " Modify button to change its appearance.",
+ HELPCTX(colours_config));
+ ctrl_columns(s, 2, 67, 33);
+ cd = (struct colour_data *)ctrl_alloc(b, sizeof(struct colour_data));
+ cd->listbox = ctrl_listbox(s, "Select a colour to adjust:", 'u',
+ HELPCTX(colours_config), colour_handler, P(cd));
+ cd->listbox->generic.column = 0;
+ cd->listbox->listbox.height = 7;
+ c = ctrl_text(s, "RGB value:", HELPCTX(colours_config));
+ c->generic.column = 1;
+ cd->redit = ctrl_editbox(s, "Red", 'r', 50, HELPCTX(colours_config),
+ colour_handler, P(cd), P(NULL));
+ cd->redit->generic.column = 1;
+ cd->gedit = ctrl_editbox(s, "Green", 'n', 50, HELPCTX(colours_config),
+ colour_handler, P(cd), P(NULL));
+ cd->gedit->generic.column = 1;
+ cd->bedit = ctrl_editbox(s, "Blue", 'e', 50, HELPCTX(colours_config),
+ colour_handler, P(cd), P(NULL));
+ cd->bedit->generic.column = 1;
+ cd->button = ctrl_pushbutton(s, "Modify", 'm', HELPCTX(colours_config),
+ colour_handler, P(cd));
+ cd->button->generic.column = 1;
+ ctrl_columns(s, 1, 100);
+
+ /*
+ * The Connection panel. This doesn't show up if we're in a
+ * non-network utility such as pterm. We tell this by being
+ * passed a protocol < 0.
+ */
+ if (protocol >= 0) {
+ ctrl_settitle(b, "Connection", "Options controlling the connection");
+
+ 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)),
+ 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)));
+ ctrl_checkbox(s, "Enable TCP keepalives (SO_KEEPALIVE option)",
+ 'p', HELPCTX(connection_tcpkeepalive),
+ dlg_stdcheckbox_handler,
+ I(offsetof(Config,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)),
+ "Auto", 'u', I(ADDRTYPE_UNSPEC),
+ "IPv4", '4', I(ADDRTYPE_IPV4),
+ "IPv6", '6', I(ADDRTYPE_IPV6),
+ NULL);
+#endif
+
+ {
+ char *label = backend_from_proto(PROT_SSH) ?
+ "Logical name of remote host (e.g. for SSH key lookup):" :
+ "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)));
+ }
+ }
+
+ /*
+ * A sub-panel Connection/Data, containing options that
+ * decide on data to send to the server.
+ */
+ if (!midsession) {
+ ctrl_settitle(b, "Connection/Data", "Data to send to the server");
+
+ 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)));
+ {
+ /* 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)),
+ "Prompt", I(FALSE),
+ userlabel, I(TRUE),
+ NULL);
+ sfree(userlabel);
+ }
+
+ 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)));
+ ctrl_editbox(s, "Terminal speeds", 's', 50,
+ HELPCTX(connection_termspeed),
+ dlg_stdeditbox_handler, I(offsetof(Config,termspeed)),
+ I(sizeof(((Config *)0)->termspeed)));
+
+ s = ctrl_getset(b, "Connection/Data", "env",
+ "Environment variables");
+ ctrl_columns(s, 2, 80, 20);
+ ed = (struct environ_data *)
+ ctrl_alloc(b, sizeof(struct environ_data));
+ ed->varbox = ctrl_editbox(s, "Variable", 'v', 60,
+ HELPCTX(telnet_environ),
+ environ_handler, P(ed), P(NULL));
+ ed->varbox->generic.column = 0;
+ ed->valbox = ctrl_editbox(s, "Value", 'l', 60,
+ HELPCTX(telnet_environ),
+ environ_handler, P(ed), P(NULL));
+ ed->valbox->generic.column = 0;
+ ed->addbutton = ctrl_pushbutton(s, "Add", 'd',
+ HELPCTX(telnet_environ),
+ environ_handler, P(ed));
+ ed->addbutton->generic.column = 1;
+ ed->rembutton = ctrl_pushbutton(s, "Remove", 'r',
+ HELPCTX(telnet_environ),
+ environ_handler, P(ed));
+ ed->rembutton->generic.column = 1;
+ ctrl_columns(s, 1, 100);
+ ed->listbox = ctrl_listbox(s, NULL, NO_SHORTCUT,
+ HELPCTX(telnet_environ),
+ environ_handler, P(ed));
+ ed->listbox->listbox.height = 3;
+ ed->listbox->listbox.ncols = 2;
+ ed->listbox->listbox.percentages = snewn(2, int);
+ ed->listbox->listbox.percentages[0] = 30;
+ ed->listbox->listbox.percentages[1] = 70;
+ }
+
+ }
+
+ if (!midsession) {
+ /*
+ * The Connection/Proxy panel.
+ */
+ ctrl_settitle(b, "Connection/Proxy",
+ "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)),
+ "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)));
+ c->generic.column = 0;
+ c = ctrl_editbox(s, "Port", 'p', 100,
+ HELPCTX(proxy_main),
+ dlg_stdeditbox_handler,
+ I(offsetof(Config,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)));
+ ctrl_checkbox(s, "Consider proxying local host connections", 'x',
+ HELPCTX(proxy_exclude),
+ dlg_stdcheckbox_handler,
+ I(offsetof(Config,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)),
+ "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)));
+ c = ctrl_editbox(s, "Password", 'w', 60,
+ HELPCTX(proxy_auth),
+ dlg_stdeditbox_handler,
+ I(offsetof(Config,proxy_password)),
+ I(sizeof(((Config *)0)->proxy_password)));
+ 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)));
+ }
+
+ /*
+ * The Telnet panel exists in the base config box, and in a
+ * mid-session reconfig box _if_ we're using Telnet.
+ */
+ if (!midsession || protocol == PROT_TELNET) {
+ /*
+ * The Connection/Telnet panel.
+ */
+ ctrl_settitle(b, "Connection/Telnet",
+ "Options controlling Telnet connections");
+
+ s = ctrl_getset(b, "Connection/Telnet", "protocol",
+ "Telnet protocol adjustments");
+
+ if (!midsession) {
+ ctrl_radiobuttons(s, "Handling of OLD_ENVIRON ambiguity:",
+ NO_SHORTCUT, 2,
+ HELPCTX(telnet_oldenviron),
+ dlg_stdradiobutton_handler,
+ I(offsetof(Config, 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)),
+ "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)));
+ ctrl_checkbox(s, "Return key sends Telnet New Line instead of ^M",
+ 'm', HELPCTX(telnet_newline),
+ dlg_stdcheckbox_handler,
+ I(offsetof(Config,telnet_newline)));
+ }
+
+ if (!midsession) {
+
+ /*
+ * The Connection/Rlogin panel.
+ */
+ ctrl_settitle(b, "Connection/Rlogin",
+ "Options controlling Rlogin connections");
+
+ 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)));
+
+ }
+
+ /*
+ * All the SSH stuff is omitted in PuTTYtel, or in a reconfig
+ * when we're not doing SSH.
+ */
+
+ if (backend_from_proto(PROT_SSH) && (!midsession || protocol == PROT_SSH)) {
+
+ /*
+ * The Connection/SSH panel.
+ */
+ ctrl_settitle(b, "Connection/SSH",
+ "Options controlling SSH connections");
+
+ if (midsession && protcfginfo == 1) {
+ s = ctrl_getset(b, "Connection/SSH", "disclaimer", NULL);
+ ctrl_text(s, "Nothing on this panel may be reconfigured in mid-"
+ "session; it is only here so that sub-panels of it can "
+ "exist without looking strange.", HELPCTX(no_help));
+ }
+
+ if (!midsession) {
+
+ 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)));
+
+ 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)));
+ }
+
+ 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)));
+ }
+
+ 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)),
+ "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.)
+ */
+ if (protcfginfo != 1) {
+ ctrl_settitle(b, "Connection/SSH/Kex",
+ "Options controlling SSH key exchange");
+
+ s = ctrl_getset(b, "Connection/SSH/Kex", "main",
+ "Key exchange algorithm options");
+ c = ctrl_draglist(s, "Algorithm selection policy:", 's',
+ HELPCTX(ssh_kexlist),
+ kexlist_handler, P(NULL));
+ c->listbox.height = 5;
+
+ 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)),
+ 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)),
+ I(16));
+ ctrl_text(s, "(Use 1M for 1 megabyte, 1G for 1 gigabyte etc)",
+ HELPCTX(ssh_kex_repeat));
+ }
+
+ if (!midsession) {
+
+ /*
+ * The Connection/SSH/Auth panel.
+ */
+ ctrl_settitle(b, "Connection/SSH/Auth",
+ "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)));
+ ctrl_checkbox(s, "Display pre-authentication banner (SSH-2 only)",
+ 'd', HELPCTX(ssh_auth_banner),
+ dlg_stdcheckbox_handler,
+ I(offsetof(Config,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)));
+ ctrl_checkbox(s, "Attempt TIS or CryptoCard auth (SSH-1)", 'm',
+ HELPCTX(ssh_auth_tis),
+ dlg_stdcheckbox_handler,
+ I(offsetof(Config,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)));
+
+ 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)));
+ 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)));
+ 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)));
+
+#ifndef NO_GSSAPI
+ /*
+ * Connection/SSH/Auth/GSSAPI, which sadly won't fit on
+ * the main Auth panel.
+ */
+ ctrl_settitle(b, "Connection/SSH/Auth/GSSAPI",
+ "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)));
+
+ ctrl_checkbox(s, "Allow GSSAPI credential delegation", 'l',
+ HELPCTX(ssh_gssapi_delegation),
+ dlg_stdcheckbox_handler,
+ I(offsetof(Config,gssapifwd)));
+
+ /*
+ * GSSAPI library selection.
+ */
+ if (ngsslibs > 1) {
+ c = ctrl_draglist(s, "Preference order for GSSAPI libraries:",
+ 'p', HELPCTX(ssh_gssapi_libraries),
+ gsslist_handler, P(NULL));
+ c->listbox.height = ngsslibs;
+
+ /*
+ * I currently assume that if more than one GSS
+ * library option is available, then one of them is
+ * 'user-supplied' and so we should present the
+ * following file selector. This is at least half-
+ * reasonable, because if we're using statically
+ * linked GSSAPI then there will only be one option
+ * and no way to load from a user-supplied library,
+ * whereas if we're using dynamic libraries then
+ * there will almost certainly be some default
+ * option in addition to a user-supplied path. If
+ * anyone ever ports PuTTY to a system on which
+ * dynamic-library GSSAPI is available but there is
+ * absolutely no consensus on where to keep the
+ * libraries, there'll need to be a flag alongside
+ * ngsslibs to control whether the file selector is
+ * displayed.
+ */
+
+ 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)));
+ }
+#endif
+ }
+
+ if (!midsession) {
+ /*
+ * The Connection/SSH/TTY panel.
+ */
+ 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)));
+
+ s = ctrl_getset(b, "Connection/SSH/TTY", "ttymodes",
+ "Terminal modes");
+ td = (struct ttymodes_data *)
+ ctrl_alloc(b, sizeof(struct ttymodes_data));
+ ctrl_columns(s, 2, 75, 25);
+ c = ctrl_text(s, "Terminal modes to send:", HELPCTX(ssh_ttymodes));
+ c->generic.column = 0;
+ td->rembutton = ctrl_pushbutton(s, "Remove", 'r',
+ HELPCTX(ssh_ttymodes),
+ ttymodes_handler, P(td));
+ td->rembutton->generic.column = 1;
+ td->rembutton->generic.tabdelay = 1;
+ ctrl_columns(s, 1, 100);
+ td->listbox = ctrl_listbox(s, NULL, NO_SHORTCUT,
+ HELPCTX(ssh_ttymodes),
+ ttymodes_handler, P(td));
+ td->listbox->listbox.multisel = 1;
+ td->listbox->listbox.height = 4;
+ td->listbox->listbox.ncols = 2;
+ td->listbox->listbox.percentages = snewn(2, int);
+ td->listbox->listbox.percentages[0] = 40;
+ td->listbox->listbox.percentages[1] = 60;
+ ctrl_tabdelay(s, td->rembutton);
+ ctrl_columns(s, 2, 75, 25);
+ td->modelist = ctrl_droplist(s, "Mode:", 'm', 67,
+ HELPCTX(ssh_ttymodes),
+ ttymodes_handler, P(td));
+ td->modelist->generic.column = 0;
+ td->addbutton = ctrl_pushbutton(s, "Add", 'd',
+ HELPCTX(ssh_ttymodes),
+ ttymodes_handler, P(td));
+ td->addbutton->generic.column = 1;
+ td->addbutton->generic.tabdelay = 1;
+ ctrl_columns(s, 1, 100); /* column break */
+ /* Bit of a hack to get the value radio buttons and
+ * edit-box on the same row. */
+ ctrl_columns(s, 3, 25, 50, 25);
+ c = ctrl_text(s, "Value:", HELPCTX(ssh_ttymodes));
+ c->generic.column = 0;
+ td->valradio = ctrl_radiobuttons(s, NULL, NO_SHORTCUT, 2,
+ HELPCTX(ssh_ttymodes),
+ ttymodes_handler, P(td),
+ "Auto", NO_SHORTCUT, P(NULL),
+ "This:", NO_SHORTCUT, P(NULL),
+ NULL);
+ td->valradio->generic.column = 1;
+ td->valbox = ctrl_editbox(s, NULL, NO_SHORTCUT, 100,
+ HELPCTX(ssh_ttymodes),
+ ttymodes_handler, P(td), P(NULL));
+ td->valbox->generic.column = 2;
+ ctrl_tabdelay(s, td->addbutton);
+
+ }
+
+ if (!midsession) {
+ /*
+ * The Connection/SSH/X11 panel.
+ */
+ ctrl_settitle(b, "Connection/SSH/X11",
+ "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)));
+ 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)));
+ ctrl_radiobuttons(s, "Remote X11 authentication protocol", 'u', 2,
+ HELPCTX(ssh_tunnels_x11auth),
+ dlg_stdradiobutton_handler,
+ I(offsetof(Config, x11_auth)),
+ "MIT-Magic-Cookie-1", I(X11_MIT),
+ "XDM-Authorization-1", I(X11_XDM), NULL);
+ }
+
+ /*
+ * The Tunnels panel _is_ still available in mid-session.
+ */
+ ctrl_settitle(b, "Connection/SSH/Tunnels",
+ "Options controlling SSH port forwarding");
+
+ 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)));
+ 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)));
+
+ 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
+ * should be that way round. */
+ pfd = (struct portfwd_data *)ctrl_alloc(b,sizeof(struct portfwd_data));
+ pfd->rembutton = ctrl_pushbutton(s, "Remove", 'r',
+ HELPCTX(ssh_tunnels_portfwd),
+ portfwd_handler, P(pfd));
+ pfd->rembutton->generic.column = 2;
+ pfd->rembutton->generic.tabdelay = 1;
+ pfd->listbox = ctrl_listbox(s, NULL, NO_SHORTCUT,
+ HELPCTX(ssh_tunnels_portfwd),
+ portfwd_handler, P(pfd));
+ pfd->listbox->listbox.height = 3;
+ pfd->listbox->listbox.ncols = 2;
+ pfd->listbox->listbox.percentages = snewn(2, int);
+ pfd->listbox->listbox.percentages[0] = 20;
+ pfd->listbox->listbox.percentages[1] = 80;
+ ctrl_tabdelay(s, pfd->rembutton);
+ ctrl_text(s, "Add new forwarded port:", HELPCTX(ssh_tunnels_portfwd));
+ /* You want to enter source, destination and type, _then_ hit Add.
+ * Again, we adjust the tab order to reflect this. */
+ pfd->addbutton = ctrl_pushbutton(s, "Add", 'd',
+ HELPCTX(ssh_tunnels_portfwd),
+ portfwd_handler, P(pfd));
+ pfd->addbutton->generic.column = 2;
+ pfd->addbutton->generic.tabdelay = 1;
+ pfd->sourcebox = ctrl_editbox(s, "Source port", 's', 40,
+ HELPCTX(ssh_tunnels_portfwd),
+ portfwd_handler, P(pfd), P(NULL));
+ pfd->sourcebox->generic.column = 0;
+ pfd->destbox = ctrl_editbox(s, "Destination", 'i', 67,
+ HELPCTX(ssh_tunnels_portfwd),
+ portfwd_handler, P(pfd), P(NULL));
+ pfd->direction = ctrl_radiobuttons(s, NULL, NO_SHORTCUT, 3,
+ HELPCTX(ssh_tunnels_portfwd),
+ portfwd_handler, P(pfd),
+ "Local", 'l', P(NULL),
+ "Remote", 'm', P(NULL),
+ "Dynamic", 'y', P(NULL),
+ NULL);
+#ifndef NO_IPV6
+ pfd->addressfamily =
+ ctrl_radiobuttons(s, NULL, NO_SHORTCUT, 3,
+ HELPCTX(ssh_tunnels_portfwd_ipversion),
+ portfwd_handler, P(pfd),
+ "Auto", 'u', I(ADDRTYPE_UNSPEC),
+ "IPv4", '4', I(ADDRTYPE_IPV4),
+ "IPv6", '6', I(ADDRTYPE_IPV6),
+ NULL);
+#endif
+ ctrl_tabdelay(s, pfd->addbutton);
+ ctrl_columns(s, 1, 100);
+
+ if (!midsession) {
+ /*
+ * The Connection/SSH/Bugs panel.
+ */
+ ctrl_settitle(b, "Connection/SSH/Bugs",
+ "Workarounds for SSH server bugs");
+
+ 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)));
+ ctrl_droplist(s, "Refuses all SSH-1 password camouflage", 's', 20,
+ HELPCTX(ssh_bugs_plainpw1),
+ sshbug_handler, I(offsetof(Config,sshbug_plainpw1)));
+ ctrl_droplist(s, "Chokes on SSH-1 RSA authentication", 'r', 20,
+ HELPCTX(ssh_bugs_rsa1),
+ sshbug_handler, I(offsetof(Config,sshbug_rsa1)));
+ ctrl_droplist(s, "Chokes on SSH-2 ignore messages", '2', 20,
+ HELPCTX(ssh_bugs_ignore2),
+ sshbug_handler, I(offsetof(Config,sshbug_ignore2)));
+ ctrl_droplist(s, "Miscomputes SSH-2 HMAC keys", 'm', 20,
+ HELPCTX(ssh_bugs_hmac2),
+ sshbug_handler, I(offsetof(Config,sshbug_hmac2)));
+ ctrl_droplist(s, "Miscomputes SSH-2 encryption keys", 'e', 20,
+ HELPCTX(ssh_bugs_derivekey2),
+ sshbug_handler, I(offsetof(Config,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)));
+ 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)));
+ ctrl_droplist(s, "Handles SSH-2 key re-exchange badly", 'k', 20,
+ HELPCTX(ssh_bugs_rekey2),
+ sshbug_handler, I(offsetof(Config,sshbug_rekey2)));
+ ctrl_droplist(s, "Ignores SSH-2 maximum packet size", 'x', 20,
+ HELPCTX(ssh_bugs_maxpkt2),
+ sshbug_handler, I(offsetof(Config,sshbug_maxpkt2)));
+ }
+ }
+}
--- /dev/null
+cygtermd.exe: main.c sel.c telnet.c pty.c malloc.c
+ gcc -o cygtermd.exe main.c sel.c telnet.c pty.c malloc.c
--- /dev/null
+This directory contains 'cygtermd', a small and specialist Telnet
+server designed to act as middleware between PuTTY and a Cygwin shell
+session running on the same machine, so that PuTTY can act as an
+xterm-alike for Cygwin.
+
+To install it, you must compile it from source using Cygwin gcc,
+install it in Cygwin's /bin, and configure PuTTY to use it as a local
+proxy process. For detailed instructions, see the PuTTY Wishlist page
+at
+
+http://www.chiark.greenend.org.uk/~sgtatham/putty/wishlist/cygwin-terminal-window.html
--- /dev/null
+/*
+ * Main program.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <stdarg.h>
+#include <signal.h>
+#include <string.h>
+#include <errno.h>
+
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include "sel.h"
+#include "pty.h"
+#include "telnet.h"
+
+int signalpipe[2];
+
+sel *asel;
+sel_rfd *netr, *ptyr, *sigr;
+int ptyfd;
+sel_wfd *netw, *ptyw;
+Telnet telnet;
+
+#define BUF 65536
+
+void sigchld(int signum)
+{
+ write(signalpipe[1], "C", 1);
+}
+
+void fatal(const char *fmt, ...)
+{
+ va_list ap;
+ fprintf(stderr, "FIXME: ");
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+ fprintf(stderr, "\n");
+ exit(1);
+}
+
+void net_readdata(sel_rfd *rfd, void *data, size_t len)
+{
+ if (len == 0)
+ exit(0); /* EOF on network - client went away */
+ telnet_from_net(telnet, data, len);
+ if (sel_write(netw, NULL, 0) > BUF)
+ sel_rfd_freeze(ptyr);
+ if (sel_write(ptyw, NULL, 0) > BUF)
+ sel_rfd_freeze(netr);
+}
+
+void net_readerr(sel_rfd *rfd, int error)
+{
+ fprintf(stderr, "standard input: read: %s\n", strerror(errno));
+ exit(1);
+}
+
+void net_written(sel_wfd *wfd, size_t bufsize)
+{
+ if (bufsize < BUF)
+ sel_rfd_unfreeze(ptyr);
+}
+
+void net_writeerr(sel_wfd *wfd, int error)
+{
+ fprintf(stderr, "standard input: write: %s\n", strerror(errno));
+ exit(1);
+}
+
+void pty_readdata(sel_rfd *rfd, void *data, size_t len)
+{
+ if (len == 0)
+ exit(0); /* EOF on pty */
+ telnet_from_pty(telnet, data, len);
+ if (sel_write(netw, NULL, 0) > BUF)
+ sel_rfd_freeze(ptyr);
+ if (sel_write(ptyw, NULL, 0) > BUF)
+ sel_rfd_freeze(netr);
+}
+
+void pty_readerr(sel_rfd *rfd, int error)
+{
+ if (error == EIO) /* means EOF, on a pty */
+ exit(0);
+ fprintf(stderr, "pty: read: %s\n", strerror(errno));
+ exit(1);
+}
+
+void pty_written(sel_wfd *wfd, size_t bufsize)
+{
+ if (bufsize < BUF)
+ sel_rfd_unfreeze(netr);
+}
+
+void pty_writeerr(sel_wfd *wfd, int error)
+{
+ fprintf(stderr, "pty: write: %s\n", strerror(errno));
+ exit(1);
+}
+
+void sig_readdata(sel_rfd *rfd, void *data, size_t len)
+{
+ char *p = data;
+
+ while (len > 0) {
+ if (*p == 'C') {
+ int status;
+ pid_t pid = waitpid(-1, &status, WNOHANG);
+ if (WIFEXITED(status) || WIFSIGNALED(status))
+ exit(0); /* child process vanished */
+ }
+ }
+}
+
+void sig_readerr(sel_rfd *rfd, int error)
+{
+ fprintf(stderr, "signal pipe: read: %s\n", strerror(errno));
+ exit(1);
+}
+
+int main(int argc, char **argv)
+{
+ int ret;
+ int shell_started = 0;
+ char *directory = NULL;
+ char **program_args = NULL;
+
+ if (argc > 1 && argv[1][0]) {
+ directory = argv[1];
+ argc--, argv++;
+ }
+ if (argc > 1) {
+ program_args = argv + 1;
+ }
+
+ pty_preinit();
+
+ asel = sel_new(NULL);
+ netr = sel_rfd_add(asel, 0, net_readdata, net_readerr, NULL);
+ netw = sel_wfd_add(asel, 1, net_written, net_writeerr, NULL);
+ ptyr = sel_rfd_add(asel, -1, pty_readdata, pty_readerr, NULL);
+ ptyw = sel_wfd_add(asel, -1, pty_written, pty_writeerr, NULL);
+
+ telnet = telnet_new(netw, ptyw);
+
+ if (pipe(signalpipe) < 0) {
+ perror("pipe");
+ return 1;
+ }
+ sigr = sel_rfd_add(asel, signalpipe[0], sig_readdata,
+ sig_readerr, NULL);
+
+ signal(SIGCHLD, sigchld);
+
+ do {
+ struct shell_data shdata;
+
+ ret = sel_iterate(asel, -1);
+ if (!shell_started && telnet_shell_ok(telnet, &shdata)) {
+ ptyfd = run_program_in_pty(&shdata, directory, program_args);
+ sel_rfd_setfd(ptyr, ptyfd);
+ sel_wfd_setfd(ptyw, ptyfd);
+ shell_started = 1;
+ }
+ } while (ret == 0);
+
+ return 0;
+}
--- /dev/null
+/*
+ * malloc.c: implementation of malloc.h
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "malloc.h"
+
+extern void fatal(const char *, ...);
+
+void *smalloc(size_t size) {
+ void *p;
+ p = malloc(size);
+ if (!p) {
+ fatal("out of memory");
+ }
+ return p;
+}
+
+void sfree(void *p) {
+ if (p) {
+ free(p);
+ }
+}
+
+void *srealloc(void *p, size_t size) {
+ void *q;
+ if (p) {
+ q = realloc(p, size);
+ } else {
+ q = malloc(size);
+ }
+ if (!q)
+ fatal("out of memory");
+ return q;
+}
+
+char *dupstr(const char *s) {
+ char *r = smalloc(1+strlen(s));
+ strcpy(r,s);
+ return r;
+}
--- /dev/null
+/*
+ * malloc.h: safe wrappers around malloc, realloc, free, strdup
+ */
+
+#ifndef UMLWRAP_MALLOC_H
+#define UMLWRAP_MALLOC_H
+
+#include <stddef.h>
+
+/*
+ * smalloc should guarantee to return a useful pointer - Halibut
+ * can do nothing except die when it's out of memory anyway.
+ */
+void *smalloc(size_t size);
+
+/*
+ * srealloc should guaranteeably be able to realloc NULL
+ */
+void *srealloc(void *p, size_t size);
+
+/*
+ * sfree should guaranteeably deal gracefully with freeing NULL
+ */
+void sfree(void *p);
+
+/*
+ * dupstr is like strdup, but with the never-return-NULL property
+ * of smalloc (and also reliably defined in all environments :-)
+ */
+char *dupstr(const char *s);
+
+/*
+ * snew allocates one instance of a given type, and casts the
+ * result so as to type-check that you're assigning it to the
+ * right kind of pointer. Protects against allocation bugs
+ * involving allocating the wrong size of thing.
+ */
+#define snew(type) \
+ ( (type *) smalloc (sizeof (type)) )
+
+/*
+ * snewn allocates n instances of a given type, for arrays.
+ */
+#define snewn(number, type) \
+ ( (type *) smalloc ((number) * sizeof (type)) )
+
+/*
+ * sresize wraps realloc so that you specify the new number of
+ * elements and the type of the element, with the same type-
+ * checking advantages. Also type-checks the input pointer.
+ */
+#define sresize(array, number, type) \
+ ( (void)sizeof((array)-(type *)0), \
+ (type *) srealloc ((array), (number) * sizeof (type)) )
+
+#endif /* UMLWRAP_MALLOC_H */
--- /dev/null
+/*
+ * pty.c - pseudo-terminal handling
+ */
+
+#define _XOPEN_SOURCE
+#include <features.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <termios.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <pwd.h>
+
+#include "pty.h"
+#include "malloc.h"
+
+static char ptyname[FILENAME_MAX];
+int master = -1;
+
+void pty_preinit(void)
+{
+ /*
+ * Allocate the pty.
+ */
+ master = open("/dev/ptmx", O_RDWR);
+ if (master < 0) {
+ perror("/dev/ptmx: open");
+ exit(1);
+ }
+
+ if (grantpt(master) < 0) {
+ perror("grantpt");
+ exit(1);
+ }
+
+ if (unlockpt(master) < 0) {
+ perror("unlockpt");
+ exit(1);
+ }
+}
+
+void pty_resize(int w, int h)
+{
+ struct winsize sz;
+
+ assert(master >= 0);
+
+ sz.ws_row = h;
+ sz.ws_col = w;
+ sz.ws_xpixel = sz.ws_ypixel = 0;
+ ioctl(master, TIOCSWINSZ, &sz);
+}
+
+int run_program_in_pty(const struct shell_data *shdata,
+ char *directory, char **program_args)
+{
+ int slave, pid;
+ char *fallback_args[2];
+
+ assert(master >= 0);
+
+ ptyname[FILENAME_MAX-1] = '\0';
+ strncpy(ptyname, ptsname(master), FILENAME_MAX-1);
+
+#if 0
+ {
+ struct winsize ws;
+ struct termios ts;
+
+ /*
+ * FIXME: think up some good defaults here
+ */
+
+ if (!ioctl(0, TIOCGWINSZ, &ws))
+ ioctl(master, TIOCSWINSZ, &ws);
+ if (!tcgetattr(0, &ts))
+ tcsetattr(master, TCSANOW, &ts);
+ }
+#endif
+
+ slave = open(ptyname, O_RDWR | O_NOCTTY);
+ if (slave < 0) {
+ perror("slave pty: open");
+ return 1;
+ }
+
+ /*
+ * Fork and execute the command.
+ */
+ pid = fork();
+ if (pid < 0) {
+ perror("fork");
+ return 1;
+ }
+
+ if (pid == 0) {
+ int i, fd;
+
+ /*
+ * We are the child.
+ */
+ close(master);
+
+ fcntl(slave, F_SETFD, 0); /* don't close on exec */
+ dup2(slave, 0);
+ dup2(slave, 1);
+ if (slave != 0 && slave != 1)
+ close(slave);
+ dup2(1, 2);
+ setsid();
+ setpgrp();
+ i = 0;
+#ifdef TIOCNOTTY
+ if ((fd = open("/dev/tty", O_RDWR)) >= 0) {
+ ioctl(fd, TIOCNOTTY, &i);
+ close(fd);
+ }
+#endif
+#ifdef TIOCSCTTY
+ ioctl(0, TIOCSCTTY, &i);
+#endif
+ tcsetpgrp(0, getpgrp());
+
+ for (i = 0; i < shdata->nenvvars; i++)
+ putenv(shdata->envvars[i]);
+ if (shdata->termtype)
+ putenv(shdata->termtype);
+
+ if (directory)
+ chdir(directory);
+
+ /*
+ * Use the provided shell program name, if the user gave
+ * one. Failing that, use $SHELL; failing that, look up
+ * the user's default shell in the password file; failing
+ * _that_, revert to the bog-standard /bin/sh.
+ */
+ if (!program_args) {
+ char *shell;
+
+ shell = getenv("SHELL");
+ if (!shell) {
+ const char *login;
+ uid_t uid;
+ struct passwd *pwd;
+
+ /*
+ * For maximum generality in the face of multiple
+ * /etc/passwd entries with different login names and
+ * shells but a shared uid, we start by using
+ * getpwnam(getlogin()) if it's available - but we
+ * insist that its uid must match our real one, or we
+ * give up and fall back to getpwuid(getuid()).
+ */
+ uid = getuid();
+ login = getlogin();
+ if (login && (pwd = getpwnam(login)) && pwd->pw_uid == uid)
+ shell = pwd->pw_shell;
+ else if ((pwd = getpwuid(uid)))
+ shell = pwd->pw_shell;
+ }
+ if (!shell)
+ shell = "/bin/sh";
+
+ fallback_args[0] = shell;
+ fallback_args[1] = NULL;
+ program_args = fallback_args;
+ }
+
+ execv(program_args[0], program_args);
+
+ /*
+ * If we're here, exec has gone badly foom.
+ */
+ perror("exec");
+ exit(127);
+ }
+
+ close(slave);
+
+ return master;
+}
--- /dev/null
+/*
+ * pty.h - FIXME
+ */
+
+#ifndef FIXME_PTY_H
+#define FIXME_PTY_H
+
+#include "telnet.h" /* for struct shdata */
+
+/*
+ * Called at program startup to actually allocate a pty, so that
+ * we can start passing in resize events as soon as they arrive.
+ */
+void pty_preinit(void);
+
+/*
+ * Set the terminal size for the pty.
+ */
+void pty_resize(int w, int h);
+
+/*
+ * Start a program in a subprocess running in the pty we allocated.
+ * Returns the fd of the pty master.
+ */
+int run_program_in_pty(const struct shell_data *shdata,
+ char *directory, char **program_args);
+
+#endif /* FIXME_PTY_H */
--- /dev/null
+/*
+ * sel.c: implementation of sel.h.
+ */
+
+#include <stddef.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/select.h>
+
+#include "sel.h"
+#include "malloc.h"
+
+/* ----------------------------------------------------------------------
+ * Chunk of code lifted from PuTTY's misc.c to manage buffers of
+ * data to be written to an fd.
+ */
+
+#define BUFFER_GRANULE 512
+
+typedef struct bufchain_tag {
+ struct bufchain_granule *head, *tail;
+ size_t buffersize; /* current amount of buffered data */
+} bufchain;
+struct bufchain_granule {
+ struct bufchain_granule *next;
+ size_t buflen, bufpos;
+ char buf[BUFFER_GRANULE];
+};
+
+static void bufchain_init(bufchain *ch)
+{
+ ch->head = ch->tail = NULL;
+ ch->buffersize = 0;
+}
+
+static void bufchain_clear(bufchain *ch)
+{
+ struct bufchain_granule *b;
+ while (ch->head) {
+ b = ch->head;
+ ch->head = ch->head->next;
+ sfree(b);
+ }
+ ch->tail = NULL;
+ ch->buffersize = 0;
+}
+
+static size_t bufchain_size(bufchain *ch)
+{
+ return ch->buffersize;
+}
+
+static void bufchain_add(bufchain *ch, const void *data, size_t len)
+{
+ const char *buf = (const char *)data;
+
+ if (len == 0) return;
+
+ ch->buffersize += len;
+
+ if (ch->tail && ch->tail->buflen < BUFFER_GRANULE) {
+ size_t copylen = BUFFER_GRANULE - ch->tail->buflen;
+ if (copylen > len)
+ copylen = len;
+ memcpy(ch->tail->buf + ch->tail->buflen, buf, copylen);
+ buf += copylen;
+ len -= copylen;
+ ch->tail->buflen += copylen;
+ }
+ while (len > 0) {
+ struct bufchain_granule *newbuf;
+ size_t grainlen = BUFFER_GRANULE;
+ if (grainlen > len)
+ grainlen = len;
+ 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;
+ }
+}
+
+static void bufchain_consume(bufchain *ch, size_t len)
+{
+ struct bufchain_granule *tmp;
+
+ assert(ch->buffersize >= len);
+ while (len > 0) {
+ size_t remlen = len;
+ assert(ch->head != NULL);
+ if (remlen >= ch->head->buflen - ch->head->bufpos) {
+ remlen = ch->head->buflen - ch->head->bufpos;
+ tmp = ch->head;
+ ch->head = tmp->next;
+ sfree(tmp);
+ if (!ch->head)
+ ch->tail = NULL;
+ } else
+ ch->head->bufpos += remlen;
+ ch->buffersize -= remlen;
+ len -= remlen;
+ }
+}
+
+static void bufchain_prefix(bufchain *ch, void **data, size_t *len)
+{
+ *len = ch->head->buflen - ch->head->bufpos;
+ *data = ch->head->buf + ch->head->bufpos;
+}
+
+/* ----------------------------------------------------------------------
+ * The actual implementation of the sel interface.
+ */
+
+struct sel {
+ void *ctx;
+ sel_rfd *rhead, *rtail;
+ sel_wfd *whead, *wtail;
+};
+
+struct sel_rfd {
+ sel *parent;
+ sel_rfd *prev, *next;
+ sel_readdata_fn_t readdata;
+ sel_readerr_fn_t readerr;
+ void *ctx;
+ int fd;
+ int frozen;
+};
+
+struct sel_wfd {
+ sel *parent;
+ sel_wfd *prev, *next;
+ sel_written_fn_t written;
+ sel_writeerr_fn_t writeerr;
+ void *ctx;
+ int fd;
+ bufchain buf;
+};
+
+sel *sel_new(void *ctx)
+{
+ sel *sel = snew(struct sel);
+
+ sel->ctx = ctx;
+ sel->rhead = sel->rtail = NULL;
+ sel->whead = sel->wtail = NULL;
+
+ return sel;
+}
+
+sel_wfd *sel_wfd_add(sel *sel, int fd,
+ sel_written_fn_t written, sel_writeerr_fn_t writeerr,
+ void *ctx)
+{
+ sel_wfd *wfd = snew(sel_wfd);
+
+ wfd->written = written;
+ wfd->writeerr = writeerr;
+ wfd->ctx = ctx;
+ wfd->fd = fd;
+ bufchain_init(&wfd->buf);
+
+ wfd->next = NULL;
+ wfd->prev = sel->wtail;
+ if (sel->wtail)
+ sel->wtail->next = wfd;
+ else
+ sel->whead = wfd;
+ sel->wtail = wfd;
+ wfd->parent = sel;
+
+ return wfd;
+}
+
+sel_rfd *sel_rfd_add(sel *sel, int fd,
+ sel_readdata_fn_t readdata, sel_readerr_fn_t readerr,
+ void *ctx)
+{
+ sel_rfd *rfd = snew(sel_rfd);
+
+ rfd->readdata = readdata;
+ rfd->readerr = readerr;
+ rfd->ctx = ctx;
+ rfd->fd = fd;
+ rfd->frozen = 0;
+
+ rfd->next = NULL;
+ rfd->prev = sel->rtail;
+ if (sel->rtail)
+ sel->rtail->next = rfd;
+ else
+ sel->rhead = rfd;
+ sel->rtail = rfd;
+ rfd->parent = sel;
+
+ return rfd;
+}
+
+size_t sel_write(sel_wfd *wfd, const void *data, size_t len)
+{
+ bufchain_add(&wfd->buf, data, len);
+ return bufchain_size(&wfd->buf);
+}
+
+void sel_wfd_setfd(sel_wfd *wfd, int fd)
+{
+ wfd->fd = fd;
+}
+
+void sel_rfd_setfd(sel_rfd *rfd, int fd)
+{
+ rfd->fd = fd;
+}
+
+void sel_rfd_freeze(sel_rfd *rfd)
+{
+ rfd->frozen = 1;
+}
+
+void sel_rfd_unfreeze(sel_rfd *rfd)
+{
+ rfd->frozen = 0;
+}
+
+int sel_wfd_delete(sel_wfd *wfd)
+{
+ sel *sel = wfd->parent;
+ int ret;
+
+ if (wfd->prev)
+ wfd->prev->next = wfd->next;
+ else
+ sel->whead = wfd->next;
+ if (wfd->next)
+ wfd->next->prev = wfd->prev;
+ else
+ sel->wtail = wfd->prev;
+
+ bufchain_clear(&wfd->buf);
+
+ ret = wfd->fd;
+ sfree(wfd);
+ return ret;
+}
+
+int sel_rfd_delete(sel_rfd *rfd)
+{
+ sel *sel = rfd->parent;
+ int ret;
+
+ if (rfd->prev)
+ rfd->prev->next = rfd->next;
+ else
+ sel->rhead = rfd->next;
+ if (rfd->next)
+ rfd->next->prev = rfd->prev;
+ else
+ sel->rtail = rfd->prev;
+
+ ret = rfd->fd;
+ sfree(rfd);
+ return ret;
+}
+
+void sel_free(sel *sel)
+{
+ while (sel->whead)
+ sel_wfd_delete(sel->whead);
+ while (sel->rhead)
+ sel_rfd_delete(sel->rhead);
+ sfree(sel);
+}
+
+void *sel_get_ctx(sel *sel) { return sel->ctx; }
+void sel_set_ctx(sel *sel, void *ctx) { sel->ctx = ctx; }
+void *sel_wfd_get_ctx(sel_wfd *wfd) { return wfd->ctx; }
+void sel_wfd_set_ctx(sel_wfd *wfd, void *ctx) { wfd->ctx = ctx; }
+void *sel_rfd_get_ctx(sel_rfd *rfd) { return rfd->ctx; }
+void sel_rfd_set_ctx(sel_rfd *rfd, void *ctx) { rfd->ctx = ctx; }
+
+int sel_iterate(sel *sel, long timeout)
+{
+ sel_rfd *rfd;
+ sel_wfd *wfd;
+ fd_set rset, wset;
+ int maxfd = 0;
+ struct timeval tv, *ptv;
+ char buf[65536];
+ int ret;
+
+ FD_ZERO(&rset);
+ FD_ZERO(&wset);
+
+ for (rfd = sel->rhead; rfd; rfd = rfd->next) {
+ if (rfd->fd >= 0 && !rfd->frozen) {
+ FD_SET(rfd->fd, &rset);
+ if (maxfd < rfd->fd + 1)
+ maxfd = rfd->fd + 1;
+ }
+ }
+
+ for (wfd = sel->whead; wfd; wfd = wfd->next) {
+ if (wfd->fd >= 0 && bufchain_size(&wfd->buf)) {
+ FD_SET(wfd->fd, &wset);
+ if (maxfd < wfd->fd + 1)
+ maxfd = wfd->fd + 1;
+ }
+ }
+
+ if (timeout < 0) {
+ ptv = NULL;
+ } else {
+ ptv = &tv;
+ tv.tv_sec = timeout / 1000;
+ tv.tv_usec = 1000 * (timeout % 1000);
+ }
+
+ do {
+ ret = select(maxfd, &rset, &wset, NULL, ptv);
+ } while (ret < 0 && (errno == EINTR || errno == EAGAIN));
+
+ if (ret < 0)
+ return errno;
+
+ /*
+ * Just in case one of the callbacks destroys an rfd or wfd we
+ * had yet to get round to, we must loop from the start every
+ * single time. Algorithmically irritating, but necessary
+ * unless we want to store the rfd structures in a heavyweight
+ * tree sorted by fd. And let's face it, if we cared about
+ * good algorithmic complexity it's not at all clear we'd be
+ * using select in the first place.
+ */
+ do {
+ for (wfd = sel->whead; wfd; wfd = wfd->next)
+ if (wfd->fd >= 0 && FD_ISSET(wfd->fd, &wset)) {
+ void *data;
+ size_t len;
+
+ FD_CLR(wfd->fd, &wset);
+ bufchain_prefix(&wfd->buf, &data, &len);
+ ret = write(wfd->fd, data, len);
+ assert(ret != 0);
+ if (ret < 0) {
+ if (wfd->writeerr)
+ wfd->writeerr(wfd, errno);
+ } else {
+ bufchain_consume(&wfd->buf, len);
+ if (wfd->written)
+ wfd->written(wfd, bufchain_size(&wfd->buf));
+ }
+ break;
+ }
+ } while (wfd);
+ do {
+ for (rfd = sel->rhead; rfd; rfd = rfd->next)
+ if (rfd->fd >= 0 && !rfd->frozen && FD_ISSET(rfd->fd, &rset)) {
+ FD_CLR(rfd->fd, &rset);
+ ret = read(rfd->fd, buf, sizeof(buf));
+ if (ret < 0) {
+ if (rfd->readerr)
+ rfd->readerr(rfd, errno);
+ } else {
+ if (rfd->readdata)
+ rfd->readdata(rfd, buf, ret);
+ }
+ break;
+ }
+ } while (rfd);
+
+ return 0;
+}
--- /dev/null
+/*
+ * sel.h: subsystem to manage the grubby details of a select loop,
+ * buffering data to write, and performing the actual writes and
+ * reads.
+ */
+
+#ifndef FIXME_SEL_H
+#define FIXME_SEL_H
+
+typedef struct sel sel;
+typedef struct sel_wfd sel_wfd;
+typedef struct sel_rfd sel_rfd;
+
+/*
+ * Callback called when some data is written to a wfd. "bufsize"
+ * is the remaining quantity of data buffered in that wfd.
+ */
+typedef void (*sel_written_fn_t)(sel_wfd *wfd, size_t bufsize);
+
+/*
+ * Callback called when an error occurs on a wfd, preventing
+ * further writing to it. "error" is the errno value.
+ */
+typedef void (*sel_writeerr_fn_t)(sel_wfd *wfd, int error);
+
+/*
+ * Callback called when some data is read from an rfd. On EOF,
+ * this will be called with len==0.
+ */
+typedef void (*sel_readdata_fn_t)(sel_rfd *rfd, void *data, size_t len);
+
+/*
+ * Callback called when an error occurs on an rfd, preventing
+ * further reading from it. "error" is the errno value.
+ */
+typedef void (*sel_readerr_fn_t)(sel_rfd *rfd, int error);
+
+/*
+ * Create a sel structure, which will oversee a select loop.
+ *
+ * "ctx" is user-supplied data stored in the sel structure; it can
+ * be read and written with sel_get_ctx() and sel_set_ctx().
+ */
+sel *sel_new(void *ctx);
+
+/*
+ * Add a new fd for writing. Returns a sel_wfd which identifies
+ * that fd in the sel structure, e.g. for putting data into its
+ * output buffer.
+ *
+ * "ctx" is user-supplied data stored in the sel structure; it can
+ * be read and written with sel_wfd_get_ctx() and sel_wfd_set_ctx().
+ *
+ * "written" and "writeerr" are called from the event loop when
+ * things happen.
+ *
+ * The fd passed in can be -1, in which case it will be assumed to
+ * be unwritable at all times. An actual fd can be passed in later
+ * using sel_wfd_setfd.
+ */
+sel_wfd *sel_wfd_add(sel *sel, int fd,
+ sel_written_fn_t written, sel_writeerr_fn_t writeerr,
+ void *ctx);
+
+/*
+ * Add a new fd for reading. Returns a sel_rfd which identifies
+ * that fd in the sel structure.
+ *
+ * "ctx" is user-supplied data stored in the sel structure; it can
+ * be read and written with sel_rfd_get_ctx() and sel_rfd_set_ctx().
+ *
+ * "readdata" and "readerr" are called from the event loop when
+ * things happen. "ctx" is passed to both of them.
+ */
+sel_rfd *sel_rfd_add(sel *sel, int fd,
+ sel_readdata_fn_t readdata, sel_readerr_fn_t readerr,
+ void *ctx);
+
+/*
+ * Write data into the output buffer of a wfd. Returns the new
+ * size of the output buffer. (You can call it with len==0 if you
+ * just want to know the buffer size; in that situation data==NULL
+ * is also safe.)
+ */
+size_t sel_write(sel_wfd *wfd, const void *data, size_t len);
+
+/*
+ * Freeze and unfreeze an rfd. When frozen, sel will temporarily
+ * not attempt to read from it, but all its state is retained so
+ * it can be conveniently unfrozen later. (You might use this
+ * facility, for instance, if what you were doing with the
+ * incoming data could only accept it at a certain rate: freeze
+ * the rfd when you've got lots of backlog, and unfreeze it again
+ * when things get calmer.)
+ */
+void sel_rfd_freeze(sel_rfd *rfd);
+void sel_rfd_unfreeze(sel_rfd *rfd);
+
+/*
+ * Delete a wfd structure from its containing sel. Returns the
+ * underlying fd, which the client may now consider itself to own
+ * once more.
+ */
+int sel_wfd_delete(sel_wfd *wfd);
+
+/*
+ * Delete an rfd structure from its containing sel. Returns the
+ * underlying fd, which the client may now consider itself to own
+ * once more.
+ */
+int sel_rfd_delete(sel_rfd *rfd);
+
+/*
+ * NOT IMPLEMENTED YET: useful functions here might be ones which
+ * enumerated all the wfds/rfds in a sel structure in some
+ * fashion, so you could go through them and remove them all while
+ * doing sensible things to them. Or, at the very least, just
+ * return an arbitrary one of the wfds/rfds.
+ */
+
+/*
+ * Free a sel structure and all its remaining wfds and rfds.
+ */
+void sel_free(sel *sel);
+
+/*
+ * Read and write the ctx parameters in sel, sel_wfd and sel_rfd.
+ */
+void *sel_get_ctx(sel *sel);
+void sel_set_ctx(sel *sel, void *ctx);
+void *sel_wfd_get_ctx(sel_wfd *wfd);
+void sel_wfd_set_ctx(sel_wfd *wfd, void *ctx);
+void *sel_rfd_get_ctx(sel_rfd *rfd);
+void sel_rfd_set_ctx(sel_rfd *rfd, void *ctx);
+
+/*
+ * Run one iteration of the sel event loop, calling callbacks as
+ * necessary. Returns zero on success; in the event of a fatal
+ * error, returns the errno value.
+ *
+ * "timeout" is a value in microseconds to limit the length of the
+ * select call. Less than zero means to wait indefinitely.
+ */
+int sel_iterate(sel *sel, long timeout);
+
+/*
+ * Change the underlying fd in a wfd. If set to -1, no write
+ * attempts will take place and the wfd's buffer will simply store
+ * everything passed to sel_write(). If later set to something
+ * other than -1, all that buffered data will become eligible for
+ * real writing.
+ */
+void sel_wfd_setfd(sel_wfd *wfd, int fd);
+
+/*
+ * Change the underlying fd in a rfd. If set to -1, no read
+ * attempts will take place.
+ */
+void sel_rfd_setfd(sel_rfd *rfd, int fd);
+
+#endif /* FIXME_SEL_H */
--- /dev/null
+/*
+ * Simple Telnet server code, adapted from PuTTY's own Telnet
+ * client code for use as a Cygwin local pty proxy.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "sel.h"
+#include "telnet.h"
+#include "malloc.h"
+#include "pty.h"
+
+#ifndef FALSE
+#define FALSE 0
+#endif
+#ifndef TRUE
+#define TRUE 1
+#endif
+
+#define IAC 255 /* interpret as command: */
+#define DONT 254 /* you are not to use option */
+#define DO 253 /* please, you use option */
+#define WONT 252 /* I won't use option */
+#define WILL 251 /* I will use option */
+#define SB 250 /* interpret as subnegotiation */
+#define SE 240 /* end sub negotiation */
+
+#define GA 249 /* you may reverse the line */
+#define EL 248 /* erase the current line */
+#define EC 247 /* erase the current character */
+#define AYT 246 /* are you there */
+#define AO 245 /* abort output--but let prog finish */
+#define IP 244 /* interrupt process--permanently */
+#define BREAK 243 /* break */
+#define DM 242 /* data mark--for connect. cleaning */
+#define NOP 241 /* nop */
+#define EOR 239 /* end of record (transparent mode) */
+#define ABORT 238 /* Abort process */
+#define SUSP 237 /* Suspend process */
+#define xEOF 236 /* End of file: EOF is already used... */
+
+#define TELOPTS(X) \
+ X(BINARY, 0) /* 8-bit data path */ \
+ X(ECHO, 1) /* echo */ \
+ X(RCP, 2) /* prepare to reconnect */ \
+ X(SGA, 3) /* suppress go ahead */ \
+ X(NAMS, 4) /* approximate message size */ \
+ X(STATUS, 5) /* give status */ \
+ X(TM, 6) /* timing mark */ \
+ X(RCTE, 7) /* remote controlled transmission and echo */ \
+ X(NAOL, 8) /* negotiate about output line width */ \
+ X(NAOP, 9) /* negotiate about output page size */ \
+ X(NAOCRD, 10) /* negotiate about CR disposition */ \
+ X(NAOHTS, 11) /* negotiate about horizontal tabstops */ \
+ X(NAOHTD, 12) /* negotiate about horizontal tab disposition */ \
+ X(NAOFFD, 13) /* negotiate about formfeed disposition */ \
+ X(NAOVTS, 14) /* negotiate about vertical tab stops */ \
+ X(NAOVTD, 15) /* negotiate about vertical tab disposition */ \
+ X(NAOLFD, 16) /* negotiate about output LF disposition */ \
+ X(XASCII, 17) /* extended ascic character set */ \
+ X(LOGOUT, 18) /* force logout */ \
+ X(BM, 19) /* byte macro */ \
+ X(DET, 20) /* data entry terminal */ \
+ X(SUPDUP, 21) /* supdup protocol */ \
+ X(SUPDUPOUTPUT, 22) /* supdup output */ \
+ X(SNDLOC, 23) /* send location */ \
+ X(TTYPE, 24) /* terminal type */ \
+ X(EOR, 25) /* end or record */ \
+ X(TUID, 26) /* TACACS user identification */ \
+ X(OUTMRK, 27) /* output marking */ \
+ X(TTYLOC, 28) /* terminal location number */ \
+ X(3270REGIME, 29) /* 3270 regime */ \
+ X(X3PAD, 30) /* X.3 PAD */ \
+ X(NAWS, 31) /* window size */ \
+ X(TSPEED, 32) /* terminal speed */ \
+ X(LFLOW, 33) /* remote flow control */ \
+ X(LINEMODE, 34) /* Linemode option */ \
+ X(XDISPLOC, 35) /* X Display Location */ \
+ X(OLD_ENVIRON, 36) /* Old - Environment variables */ \
+ X(AUTHENTICATION, 37) /* Authenticate */ \
+ X(ENCRYPT, 38) /* Encryption option */ \
+ X(NEW_ENVIRON, 39) /* New - Environment variables */ \
+ X(TN3270E, 40) /* TN3270 enhancements */ \
+ X(XAUTH, 41) \
+ X(CHARSET, 42) /* Character set */ \
+ X(RSP, 43) /* Remote serial port */ \
+ X(COM_PORT_OPTION, 44) /* Com port control */ \
+ X(SLE, 45) /* Suppress local echo */ \
+ X(STARTTLS, 46) /* Start TLS */ \
+ X(KERMIT, 47) /* Automatic Kermit file transfer */ \
+ X(SEND_URL, 48) \
+ X(FORWARD_X, 49) \
+ X(PRAGMA_LOGON, 138) \
+ X(SSPI_LOGON, 139) \
+ X(PRAGMA_HEARTBEAT, 140) \
+ X(EXOPL, 255) /* extended-options-list */
+
+#define telnet_enum(x,y) TELOPT_##x = y,
+enum { TELOPTS(telnet_enum) dummy=0 };
+#undef telnet_enum
+
+#define TELQUAL_IS 0 /* option is... */
+#define TELQUAL_SEND 1 /* send option */
+#define TELQUAL_INFO 2 /* ENVIRON: informational version of IS */
+#define BSD_VAR 1
+#define BSD_VALUE 0
+#define RFC_VAR 0
+#define RFC_VALUE 1
+
+#define CR 13
+#define LF 10
+#define NUL 0
+
+#define iswritable(x) ( (x) != IAC && (x) != CR )
+
+static char *telopt(int opt)
+{
+#define telnet_str(x,y) case TELOPT_##x: return #x;
+ switch (opt) {
+ TELOPTS(telnet_str)
+ default:
+ return "<unknown>";
+ }
+#undef telnet_str
+}
+
+static void telnet_size(void *handle, int width, int height);
+
+struct Opt {
+ int send; /* what we initially send */
+ int nsend; /* -ve send if requested to stop it */
+ int ack, nak; /* +ve and -ve acknowledgements */
+ int option; /* the option code */
+ int index; /* index into telnet->opt_states[] */
+ enum {
+ REQUESTED, ACTIVE, INACTIVE, REALLY_INACTIVE
+ } initial_state;
+};
+
+enum {
+ OPTINDEX_NAWS,
+ OPTINDEX_TSPEED,
+ OPTINDEX_TTYPE,
+ OPTINDEX_OENV,
+ OPTINDEX_NENV,
+ OPTINDEX_ECHO,
+ OPTINDEX_WE_SGA,
+ OPTINDEX_THEY_SGA,
+ OPTINDEX_WE_BIN,
+ OPTINDEX_THEY_BIN,
+ NUM_OPTS
+};
+
+static const struct Opt o_naws =
+ { DO, DONT, WILL, WONT, TELOPT_NAWS, OPTINDEX_NAWS, REQUESTED };
+static const struct Opt o_ttype =
+ { DO, DONT, WILL, WONT, TELOPT_TTYPE, OPTINDEX_TTYPE, REQUESTED };
+static const struct Opt o_oenv =
+ { DO, DONT, WILL, WONT, TELOPT_OLD_ENVIRON, OPTINDEX_OENV, INACTIVE };
+static const struct Opt o_nenv =
+ { DO, DONT, WILL, WONT, TELOPT_NEW_ENVIRON, OPTINDEX_NENV, REQUESTED };
+static const struct Opt o_echo =
+ { WILL, WONT, DO, DONT, TELOPT_ECHO, OPTINDEX_ECHO, REQUESTED };
+static const struct Opt o_they_sga =
+ { DO, DONT, WILL, WONT, TELOPT_SGA, OPTINDEX_WE_SGA, REQUESTED };
+static const struct Opt o_we_sga =
+ { WILL, WONT, DO, DONT, TELOPT_SGA, OPTINDEX_THEY_SGA, REQUESTED };
+
+static const struct Opt *const opts[] = {
+ &o_echo, &o_we_sga, &o_they_sga, &o_naws, &o_ttype, &o_oenv, &o_nenv, NULL
+};
+
+struct telnet_tag {
+ int opt_states[NUM_OPTS];
+
+ int sb_opt, sb_len;
+ unsigned char *sb_buf;
+ int sb_size;
+
+ enum {
+ TOP_LEVEL, SEENIAC, SEENWILL, SEENWONT, SEENDO, SEENDONT,
+ SEENSB, SUBNEGOT, SUBNEG_IAC, SEENCR
+ } state;
+
+ sel_wfd *net, *pty;
+
+ /*
+ * Options we must finish processing before launching the shell
+ */
+ int old_environ_done, new_environ_done, ttype_done;
+
+ /*
+ * Ready to start shell?
+ */
+ int shell_ok;
+ int envvarsize;
+ struct shell_data shdata;
+};
+
+#define TELNET_MAX_BACKLOG 4096
+
+#define SB_DELTA 1024
+
+static void send_opt(Telnet telnet, int cmd, int option)
+{
+ unsigned char b[3];
+
+ b[0] = IAC;
+ b[1] = cmd;
+ b[2] = option;
+ sel_write(telnet->net, (char *)b, 3);
+}
+
+static void deactivate_option(Telnet telnet, const struct Opt *o)
+{
+ if (telnet->opt_states[o->index] == REQUESTED ||
+ telnet->opt_states[o->index] == ACTIVE)
+ send_opt(telnet, o->nsend, o->option);
+ telnet->opt_states[o->index] = REALLY_INACTIVE;
+}
+
+/*
+ * Generate side effects of enabling or disabling an option.
+ */
+static void option_side_effects(Telnet telnet, const struct Opt *o, int enabled)
+{
+}
+
+static void activate_option(Telnet telnet, const struct Opt *o)
+{
+ if (o->option == TELOPT_NEW_ENVIRON ||
+ o->option == TELOPT_OLD_ENVIRON ||
+ o->option == TELOPT_TTYPE) {
+ char buf[6];
+ buf[0] = IAC;
+ buf[1] = SB;
+ buf[2] = o->option;
+ buf[3] = TELQUAL_SEND;
+ buf[4] = IAC;
+ buf[5] = SE;
+ sel_write(telnet->net, buf, 6);
+ }
+ option_side_effects(telnet, o, 1);
+}
+
+static void done_option(Telnet telnet, int option)
+{
+ if (option == TELOPT_OLD_ENVIRON)
+ telnet->old_environ_done = 1;
+ else if (option == TELOPT_NEW_ENVIRON)
+ telnet->new_environ_done = 1;
+ else if (option == TELOPT_TTYPE)
+ telnet->ttype_done = 1;
+
+ if (telnet->old_environ_done && telnet->new_environ_done &&
+ telnet->ttype_done) {
+ telnet->shell_ok = 1;
+ }
+}
+
+static void refused_option(Telnet telnet, const struct Opt *o)
+{
+ done_option(telnet, o->option);
+ if (o->send == WILL && o->option == TELOPT_NEW_ENVIRON &&
+ telnet->opt_states[o_oenv.index] == INACTIVE) {
+ send_opt(telnet, WILL, TELOPT_OLD_ENVIRON);
+ telnet->opt_states[o_oenv.index] = REQUESTED;
+ telnet->old_environ_done = 0;
+ }
+ option_side_effects(telnet, o, 0);
+}
+
+static void proc_rec_opt(Telnet telnet, int cmd, int option)
+{
+ const struct Opt *const *o;
+
+ for (o = opts; *o; o++) {
+ if ((*o)->option == option && (*o)->ack == cmd) {
+ switch (telnet->opt_states[(*o)->index]) {
+ case REQUESTED:
+ telnet->opt_states[(*o)->index] = ACTIVE;
+ activate_option(telnet, *o);
+ break;
+ case ACTIVE:
+ break;
+ case INACTIVE:
+ telnet->opt_states[(*o)->index] = ACTIVE;
+ send_opt(telnet, (*o)->send, option);
+ activate_option(telnet, *o);
+ break;
+ case REALLY_INACTIVE:
+ send_opt(telnet, (*o)->nsend, option);
+ break;
+ }
+ return;
+ } else if ((*o)->option == option && (*o)->nak == cmd) {
+ switch (telnet->opt_states[(*o)->index]) {
+ case REQUESTED:
+ telnet->opt_states[(*o)->index] = INACTIVE;
+ refused_option(telnet, *o);
+ break;
+ case ACTIVE:
+ telnet->opt_states[(*o)->index] = INACTIVE;
+ send_opt(telnet, (*o)->nsend, option);
+ option_side_effects(telnet, *o, 0);
+ break;
+ case INACTIVE:
+ case REALLY_INACTIVE:
+ break;
+ }
+ return;
+ }
+ }
+ /*
+ * If we reach here, the option was one we weren't prepared to
+ * cope with. If the request was positive (WILL or DO), we send
+ * a negative ack to indicate refusal. If the request was
+ * negative (WONT / DONT), we must do nothing.
+ */
+ if (cmd == WILL || cmd == DO)
+ 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;
+
+ switch (telnet->sb_opt) {
+ case TELOPT_OLD_ENVIRON:
+ case TELOPT_NEW_ENVIRON:
+ if (telnet->sb_buf[0] == TELQUAL_IS) {
+ if (telnet->sb_opt == TELOPT_NEW_ENVIRON) {
+ var = RFC_VAR;
+ value = RFC_VALUE;
+ } else {
+ if (telnet->sb_len > 1 && !(telnet->sb_buf[0] &~ 1)) {
+ var = telnet->sb_buf[0];
+ value = BSD_VAR ^ BSD_VALUE ^ var;
+ } else {
+ var = BSD_VAR;
+ value = BSD_VALUE;
+ }
+ }
+ }
+ n = 1;
+ while (n < telnet->sb_len && telnet->sb_buf[n] == var) {
+ int varpos, varlen, valpos, vallen;
+ char *result;
+
+ varpos = ++n;
+ while (n < telnet->sb_len && telnet->sb_buf[n] != value)
+ n++;
+ if (n == telnet->sb_len)
+ break;
+ varlen = n - varpos;
+ valpos = ++n;
+ while (n < telnet->sb_len && telnet->sb_buf[n] != var)
+ n++;
+ vallen = n - valpos;
+
+ result = snewn(varlen + vallen + 2, char);
+ sprintf(result, "%.*s=%.*s",
+ varlen, telnet->sb_buf+varpos,
+ vallen, telnet->sb_buf+valpos);
+ if (telnet->shdata.nenvvars >= telnet->envvarsize) {
+ telnet->envvarsize = telnet->shdata.nenvvars * 3 / 2 + 16;
+ telnet->shdata.envvars = sresize(telnet->shdata.envvars,
+ telnet->envvarsize, char *);
+ }
+ telnet->shdata.envvars[telnet->shdata.nenvvars++] = result;
+ }
+ done_option(telnet, telnet->sb_opt);
+ break;
+ case TELOPT_TTYPE:
+ if (telnet->sb_len >= 1 && telnet->sb_buf[0] == TELQUAL_IS) {
+ telnet->shdata.termtype = snewn(5 + telnet->sb_len, char);
+ strcpy(telnet->shdata.termtype, "TERM=");
+ for (n = 0; n < telnet->sb_len-1; n++) {
+ char c = telnet->sb_buf[n+1];
+ if (c >= 'A' && c <= 'Z')
+ c = c + 'a' - 'A';
+ telnet->shdata.termtype[n+5] = c;
+ }
+ telnet->shdata.termtype[telnet->sb_len+5-1] = '\0';
+ }
+ done_option(telnet, telnet->sb_opt);
+ break;
+ case TELOPT_NAWS:
+ if (telnet->sb_len == 4) {
+ int w, h;
+ w = (unsigned char)telnet->sb_buf[0];
+ w = (w << 8) | (unsigned char)telnet->sb_buf[1];
+ h = (unsigned char)telnet->sb_buf[2];
+ h = (h << 8) | (unsigned char)telnet->sb_buf[3];
+ pty_resize(w, h);
+ }
+ break;
+ }
+}
+
+void telnet_from_net(Telnet telnet, char *buf, int len)
+{
+ while (len--) {
+ int c = (unsigned char) *buf++;
+
+ switch (telnet->state) {
+ case TOP_LEVEL:
+ case SEENCR:
+ /*
+ * PuTTY sends Telnet's new line sequence (CR LF on
+ * the wire) in response to the return key. We must
+ * therefore treat that as equivalent to CR NUL, and
+ * send CR to the pty.
+ */
+ if ((c == NUL || c == '\n') && telnet->state == SEENCR)
+ telnet->state = TOP_LEVEL;
+ else if (c == IAC)
+ telnet->state = SEENIAC;
+ else {
+ char cc = c;
+ sel_write(telnet->pty, &cc, 1);
+
+ telnet->state = SEENCR;
+ }
+ break;
+ case SEENIAC:
+ if (c == DO)
+ telnet->state = SEENDO;
+ else if (c == DONT)
+ telnet->state = SEENDONT;
+ else if (c == WILL)
+ telnet->state = SEENWILL;
+ else if (c == WONT)
+ telnet->state = SEENWONT;
+ else if (c == SB)
+ telnet->state = SEENSB;
+ else if (c == DM)
+ telnet->state = TOP_LEVEL;
+ else {
+ /* ignore everything else; print it if it's IAC */
+ if (c == IAC) {
+ char cc = c;
+ sel_write(telnet->pty, &cc, 1);
+ }
+ telnet->state = TOP_LEVEL;
+ }
+ break;
+ case SEENWILL:
+ proc_rec_opt(telnet, WILL, c);
+ telnet->state = TOP_LEVEL;
+ break;
+ case SEENWONT:
+ proc_rec_opt(telnet, WONT, c);
+ telnet->state = TOP_LEVEL;
+ break;
+ case SEENDO:
+ proc_rec_opt(telnet, DO, c);
+ telnet->state = TOP_LEVEL;
+ break;
+ case SEENDONT:
+ proc_rec_opt(telnet, DONT, c);
+ telnet->state = TOP_LEVEL;
+ break;
+ case SEENSB:
+ telnet->sb_opt = c;
+ telnet->sb_len = 0;
+ telnet->state = SUBNEGOT;
+ break;
+ case SUBNEGOT:
+ if (c == IAC)
+ telnet->state = SUBNEG_IAC;
+ else {
+ subneg_addchar:
+ if (telnet->sb_len >= telnet->sb_size) {
+ telnet->sb_size += SB_DELTA;
+ telnet->sb_buf = sresize(telnet->sb_buf, telnet->sb_size,
+ unsigned char);
+ }
+ telnet->sb_buf[telnet->sb_len++] = c;
+ telnet->state = SUBNEGOT; /* in case we came here by goto */
+ }
+ break;
+ case SUBNEG_IAC:
+ if (c != SE)
+ goto subneg_addchar; /* yes, it's a hack, I know, but... */
+ else {
+ process_subneg(telnet);
+ telnet->state = TOP_LEVEL;
+ }
+ break;
+ }
+ }
+}
+
+Telnet telnet_new(sel_wfd *net, sel_wfd *pty)
+{
+ Telnet telnet;
+
+ telnet = snew(struct telnet_tag);
+ telnet->sb_buf = NULL;
+ telnet->sb_size = 0;
+ telnet->state = TOP_LEVEL;
+ telnet->net = net;
+ telnet->pty = pty;
+ telnet->shdata.envvars = NULL;
+ telnet->shdata.nenvvars = telnet->envvarsize = 0;
+ telnet->shdata.termtype = NULL;
+
+ /*
+ * Initialise option states.
+ */
+ {
+ const struct Opt *const *o;
+
+ for (o = opts; *o; o++) {
+ telnet->opt_states[(*o)->index] = (*o)->initial_state;
+ if (telnet->opt_states[(*o)->index] == REQUESTED)
+ send_opt(telnet, (*o)->send, (*o)->option);
+ }
+ }
+
+ telnet->old_environ_done = 1; /* initially don't want to bother */
+ telnet->new_environ_done = 0;
+ telnet->ttype_done = 0;
+ telnet->shell_ok = 0;
+
+ return telnet;
+}
+
+void telnet_free(Telnet telnet)
+{
+ sfree(telnet->sb_buf);
+ sfree(telnet);
+}
+
+void telnet_from_pty(Telnet telnet, char *buf, int len)
+{
+ unsigned char *p, *end;
+ static const unsigned char iac[2] = { IAC, IAC };
+ static const unsigned char cr[2] = { CR, NUL };
+#if 0
+ static const unsigned char nl[2] = { CR, LF };
+#endif
+
+ p = (unsigned char *)buf;
+ end = (unsigned char *)(buf + len);
+ while (p < end) {
+ unsigned char *q = p;
+
+ while (p < end && iswritable(*p))
+ p++;
+ sel_write(telnet->net, (char *)q, p - q);
+
+ while (p < end && !iswritable(*p)) {
+ sel_write(telnet->net, (char *)(*p == IAC ? iac : cr), 2);
+ p++;
+ }
+ }
+}
+
+int telnet_shell_ok(Telnet telnet, struct shell_data *shdata)
+{
+ if (telnet->shell_ok)
+ *shdata = telnet->shdata; /* structure copy */
+ return telnet->shell_ok;
+}
--- /dev/null
+/*
+ * Header declaring Telnet-handling functions.
+ */
+
+#ifndef FIXME_TELNET_H
+#define FIXME_TELNET_H
+
+#include "sel.h"
+
+typedef struct telnet_tag *Telnet;
+
+struct shell_data {
+ char **envvars; /* array of "VAR=value" terms */
+ int nenvvars;
+ char *termtype;
+};
+
+/*
+ * Create and destroy a Telnet structure.
+ */
+Telnet telnet_new(sel_wfd *net, sel_wfd *pty);
+void telnet_free(Telnet telnet);
+
+/*
+ * Process data read from the pty.
+ */
+void telnet_from_pty(Telnet telnet, char *buf, int len);
+
+/*
+ * Process Telnet protocol data received from the network.
+ */
+void telnet_from_net(Telnet telnet, char *buf, int len);
+
+/*
+ * Return true if pre-shell-startup negotiations are complete and
+ * it's safe to start the shell subprocess now. On a true return,
+ * also fills in the shell_data structure.
+ */
+int telnet_shell_ok(Telnet telnet, struct shell_data *shdata);
+
+#endif /* FIXME_TELNET_H */
--- /dev/null
+#! /usr/bin/env python
+
+# $Id$
+# Convert OpenSSH known_hosts and known_hosts2 files to "new format" PuTTY
+# host keys.
+# usage:
+# kh2reg.py [ --win ] known_hosts1 2 3 4 ... > hosts.reg
+# Creates a Windows .REG file (double-click to install).
+# kh2reg.py --unix known_hosts1 2 3 4 ... > sshhostkeys
+# Creates data suitable for storing in ~/.putty/sshhostkeys (Unix).
+# Line endings are someone else's problem as is traditional.
+# Developed for Python 1.5.2.
+
+import fileinput
+import base64
+import struct
+import string
+import re
+import sys
+import getopt
+
+def winmungestr(s):
+ "Duplicate of PuTTY's mungestr() in winstore.c:1.10 for Registry keys"
+ candot = 0
+ r = ""
+ for c in s:
+ if c in ' \*?%~' or ord(c)<ord(' ') or (c == '.' and not candot):
+ r = r + ("%%%02X" % ord(c))
+ else:
+ r = r + c
+ candot = 1
+ return r
+
+def strtolong(s):
+ "Convert arbitrary-length big-endian binary data to a Python long"
+ bytes = struct.unpack(">%luB" % len(s), s)
+ return reduce ((lambda a, b: (long(a) << 8) + long(b)), bytes)
+
+def longtohex(n):
+ """Convert long int to lower-case hex.
+
+ Ick, Python (at least in 1.5.2) doesn't appear to have a way to
+ turn a long int into an unadorned hex string -- % gets upset if the
+ number is too big, and raw hex() uses uppercase (sometimes), and
+ adds unwanted "0x...L" around it."""
+
+ plain=string.lower(re.match(r"0x([0-9A-Fa-f]*)l?$", hex(n), re.I).group(1))
+ return "0x" + plain
+
+output_type = 'windows'
+
+try:
+ optlist, args = getopt.getopt(sys.argv[1:], '', [ 'win', 'unix' ])
+ if filter(lambda x: x[0] == '--unix', optlist):
+ output_type = 'unix'
+except getopt.error, e:
+ sys.stderr.write(str(e) + "\n")
+ sys.exit(1)
+
+if output_type == 'windows':
+ # Output REG file header.
+ sys.stdout.write("""REGEDIT4
+
+[HKEY_CURRENT_USER\Software\SimonTatham\PuTTY\SshHostKeys]
+""")
+
+# Now process all known_hosts input.
+for line in fileinput.input(args):
+
+ try:
+ # Remove leading/trailing whitespace (should zap CR and LF)
+ line = string.strip (line)
+
+ # Skip blanks and comments
+ if line == '' or line[0] == '#':
+ raise "Skipping input line"
+
+ # Split line on spaces.
+ fields = string.split (line, ' ')
+
+ # Common fields
+ hostpat = fields[0]
+ magicnumbers = [] # placeholder
+ keytype = "" # placeholder
+
+ # Grotty heuristic to distinguish known_hosts from known_hosts2:
+ # is second field entirely decimal digits?
+ if re.match (r"\d*$", fields[1]):
+
+ # Treat as SSH-1-type host key.
+ # Format: hostpat bits10 exp10 mod10 comment...
+ # (PuTTY doesn't store the number of bits.)
+ magicnumbers = map (long, fields[2:4])
+ keytype = "rsa"
+
+ else:
+
+ # Treat as SSH-2-type host key.
+ # Format: hostpat keytype keyblob64 comment...
+ sshkeytype, blob = fields[1], base64.decodestring (fields[2])
+
+ # 'blob' consists of a number of
+ # uint32 N (big-endian)
+ # uint8[N] field_data
+ subfields = []
+ while blob:
+ sizefmt = ">L"
+ (size,) = struct.unpack (sizefmt, blob[0:4])
+ size = int(size) # req'd for slicage
+ (data,) = struct.unpack (">%lus" % size, blob[4:size+4])
+ subfields.append(data)
+ blob = blob [struct.calcsize(sizefmt) + size : ]
+
+ # The first field is keytype again, and the rest we can treat as
+ # an opaque list of bignums (same numbers and order as stored
+ # by PuTTY). (currently embedded keytype is ignored entirely)
+ magicnumbers = map (strtolong, subfields[1:])
+
+ # Translate key type into something PuTTY can use.
+ if sshkeytype == "ssh-rsa": keytype = "rsa2"
+ elif sshkeytype == "ssh-dss": keytype = "dss"
+ else:
+ raise "Unknown SSH key type", sshkeytype
+
+ # Now print out one line per host pattern, discarding wildcards.
+ for host in string.split (hostpat, ','):
+ if re.search (r"[*?!]", host):
+ sys.stderr.write("Skipping wildcard host pattern '%s'\n"
+ % host)
+ continue
+ elif re.match (r"\|", host):
+ sys.stderr.write("Skipping hashed hostname '%s'\n" % host)
+ continue
+ else:
+ m = re.match (r"\[([^]]*)\]:(\d*)$", host)
+ if m:
+ (host, port) = m.group(1,2)
+ port = int(port)
+ else:
+ port = 22
+ # Slightly bizarre output key format: 'type@port:hostname'
+ # XXX: does PuTTY do anything useful with literal IP[v4]s?
+ key = keytype + ("@%d:%s" % (port, host))
+ value = string.join (map (longtohex, magicnumbers), ',')
+ if output_type == 'unix':
+ # Unix format.
+ sys.stdout.write('%s %s\n' % (key, value))
+ else:
+ # Windows format.
+ # XXX: worry about double quotes?
+ sys.stdout.write("\"%s\"=\"%s\"\n"
+ % (winmungestr(key), value))
+
+ except "Unknown SSH key type", k:
+ sys.stderr.write("Unknown SSH key type '%s', skipping\n" % k)
+ except "Skipping input line":
+ pass
--- /dev/null
+/*
+ * Routines to do cryptographic interaction with proxies in PuTTY.
+ * This is in a separate module from proxy.c, so that it can be
+ * conveniently removed in PuTTYtel by replacing this module with
+ * the stub version nocproxy.c.
+ */
+
+#include <assert.h>
+#include <ctype.h>
+#include <string.h>
+
+#define DEFINE_PLUG_METHOD_MACROS
+#include "putty.h"
+#include "ssh.h" /* For MD5 support */
+#include "network.h"
+#include "proxy.h"
+
+static void hmacmd5_chap(const unsigned char *challenge, int challen,
+ const char *passwd, unsigned char *response)
+{
+ void *hmacmd5_ctx;
+ int pwlen;
+
+ hmacmd5_ctx = hmacmd5_make_context();
+
+ pwlen = strlen(passwd);
+ if (pwlen>64) {
+ unsigned char md5buf[16];
+ MD5Simple(passwd, pwlen, md5buf);
+ hmacmd5_key(hmacmd5_ctx, md5buf, 16);
+ } else {
+ hmacmd5_key(hmacmd5_ctx, passwd, pwlen);
+ }
+
+ hmacmd5_do_hmac(hmacmd5_ctx, challenge, challen, response);
+ hmacmd5_free_context(hmacmd5_ctx);
+}
+
+void proxy_socks5_offerencryptedauth(char *command, int *len)
+{
+ command[*len] = 0x03; /* CHAP */
+ (*len)++;
+}
+
+int proxy_socks5_handlechap (Proxy_Socket p)
+{
+
+ /* CHAP authentication reply format:
+ * version number (1 bytes) = 1
+ * number of commands (1 byte)
+ *
+ * For each command:
+ * command identifier (1 byte)
+ * data length (1 byte)
+ */
+ unsigned char data[260];
+ unsigned char outbuf[20];
+
+ while(p->chap_num_attributes == 0 ||
+ p->chap_num_attributes_processed < p->chap_num_attributes) {
+ if (p->chap_num_attributes == 0 ||
+ p->chap_current_attribute == -1) {
+ /* CHAP normally reads in two bytes, either at the
+ * beginning or for each attribute/value pair. But if
+ * we're waiting for the value's data, we might not want
+ * to read 2 bytes.
+ */
+
+ if (bufchain_size(&p->pending_input_data) < 2)
+ return 1; /* not got anything yet */
+
+ /* get the response */
+ bufchain_fetch(&p->pending_input_data, data, 2);
+ bufchain_consume(&p->pending_input_data, 2);
+ }
+
+ if (p->chap_num_attributes == 0) {
+ /* If there are no attributes, this is our first msg
+ * with the server, where we negotiate version and
+ * number of attributes
+ */
+ if (data[0] != 0x01) {
+ plug_closing(p->plug, "Proxy error: SOCKS proxy wants"
+ " a different CHAP version",
+ PROXY_ERROR_GENERAL, 0);
+ return 1;
+ }
+ if (data[1] == 0x00) {
+ plug_closing(p->plug, "Proxy error: SOCKS proxy won't"
+ " negotiate CHAP with us",
+ PROXY_ERROR_GENERAL, 0);
+ return 1;
+ }
+ p->chap_num_attributes = data[1];
+ } else {
+ if (p->chap_current_attribute == -1) {
+ /* We have to read in each attribute/value pair -
+ * those we don't understand can be ignored, but
+ * there are a few we'll need to handle.
+ */
+ p->chap_current_attribute = data[0];
+ p->chap_current_datalen = data[1];
+ }
+ if (bufchain_size(&p->pending_input_data) <
+ p->chap_current_datalen)
+ return 1; /* not got everything yet */
+
+ /* get the response */
+ bufchain_fetch(&p->pending_input_data, data,
+ p->chap_current_datalen);
+
+ bufchain_consume(&p->pending_input_data,
+ p->chap_current_datalen);
+
+ switch (p->chap_current_attribute) {
+ case 0x00:
+ /* Successful authentication */
+ if (data[0] == 0x00)
+ p->state = 2;
+ else {
+ plug_closing(p->plug, "Proxy error: SOCKS proxy"
+ " refused CHAP authentication",
+ PROXY_ERROR_GENERAL, 0);
+ return 1;
+ }
+ break;
+ case 0x03:
+ 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]);
+ sk_write(p->sub_socket, (char *)outbuf, 20);
+ break;
+ case 0x11:
+ /* Chose a protocol */
+ if (data[0] != 0x85) {
+ plug_closing(p->plug, "Proxy error: Server chose "
+ "CHAP of other than HMAC-MD5 but we "
+ "didn't offer it!",
+ PROXY_ERROR_GENERAL, 0);
+ return 1;
+ }
+ break;
+ }
+ p->chap_current_attribute = -1;
+ p->chap_num_attributes_processed++;
+ }
+ if (p->state == 8 &&
+ p->chap_num_attributes_processed >= p->chap_num_attributes) {
+ p->chap_num_attributes = 0;
+ p->chap_num_attributes_processed = 0;
+ p->chap_current_datalen = 0;
+ }
+ }
+ return 0;
+}
+
+int proxy_socks5_selectchap(Proxy_Socket p)
+{
+ if (p->cfg.proxy_username[0] || p->cfg.proxy_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);
+ if (ulen > 255) ulen = 255; if (ulen < 1) ulen = 1;
+
+ chapbuf[6] = ulen;
+ memcpy(chapbuf+7, p->cfg.proxy_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;
+ p->chap_current_datalen = 0;
+
+ p->state = 8;
+ } else
+ plug_closing(p->plug, "Proxy error: Server chose "
+ "CHAP authentication but we didn't offer it!",
+ PROXY_ERROR_GENERAL, 0);
+ return 1;
+}
--- /dev/null
+/*
+ * dialog.c - a reasonably platform-independent mechanism for
+ * describing dialog boxes.
+ */
+
+#include <assert.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stdlib.h>
+
+#define DEFINE_INTORPTR_FNS
+
+#include "putty.h"
+#include "dialog.h"
+
+int ctrl_path_elements(char *path)
+{
+ int i = 1;
+ while (*path) {
+ if (*path == '/') i++;
+ path++;
+ }
+ return i;
+}
+
+/* Return the number of matching path elements at the starts of p1 and p2,
+ * or INT_MAX if the paths are identical. */
+int ctrl_path_compare(char *p1, char *p2)
+{
+ int i = 0;
+ while (*p1 || *p2) {
+ if ((*p1 == '/' || *p1 == '\0') &&
+ (*p2 == '/' || *p2 == '\0'))
+ i++; /* a whole element matches, ooh */
+ if (*p1 != *p2)
+ return i; /* mismatch */
+ p1++, p2++;
+ }
+ return INT_MAX; /* exact match */
+}
+
+struct controlbox *ctrl_new_box(void)
+{
+ struct controlbox *ret = snew(struct controlbox);
+
+ ret->nctrlsets = ret->ctrlsetsize = 0;
+ ret->ctrlsets = NULL;
+ ret->nfrees = ret->freesize = 0;
+ ret->frees = NULL;
+
+ return ret;
+}
+
+void ctrl_free_box(struct controlbox *b)
+{
+ int i;
+
+ for (i = 0; i < b->nctrlsets; i++) {
+ ctrl_free_set(b->ctrlsets[i]);
+ }
+ for (i = 0; i < b->nfrees; i++)
+ sfree(b->frees[i]);
+ sfree(b->ctrlsets);
+ sfree(b->frees);
+ sfree(b);
+}
+
+void ctrl_free_set(struct controlset *s)
+{
+ int i;
+
+ sfree(s->pathname);
+ sfree(s->boxname);
+ sfree(s->boxtitle);
+ for (i = 0; i < s->ncontrols; i++) {
+ ctrl_free(s->ctrls[i]);
+ }
+ sfree(s->ctrls);
+ sfree(s);
+}
+
+/*
+ * Find the index of first controlset in a controlbox for a given
+ * path. If that path doesn't exist, return the index where it
+ * should be inserted.
+ */
+static int ctrl_find_set(struct controlbox *b, char *path, int start)
+{
+ int i, last, thisone;
+
+ last = 0;
+ for (i = 0; i < b->nctrlsets; i++) {
+ thisone = ctrl_path_compare(path, b->ctrlsets[i]->pathname);
+ /*
+ * If `start' is true and there exists a controlset with
+ * exactly the path we've been given, we should return the
+ * index of the first such controlset we find. Otherwise,
+ * we should return the index of the first entry in which
+ * _fewer_ path elements match than they did last time.
+ */
+ if ((start && thisone == INT_MAX) || thisone < last)
+ return i;
+ last = thisone;
+ }
+ return b->nctrlsets; /* insert at end */
+}
+
+/*
+ * Find the index of next controlset in a controlbox for a given
+ * path, or -1 if no such controlset exists. If -1 is passed as
+ * input, finds the first.
+ */
+int ctrl_find_path(struct controlbox *b, char *path, int index)
+{
+ if (index < 0)
+ index = ctrl_find_set(b, path, 1);
+ else
+ index++;
+
+ if (index < b->nctrlsets && !strcmp(path, b->ctrlsets[index]->pathname))
+ return index;
+ else
+ return -1;
+}
+
+/* Set up a panel title. */
+struct controlset *ctrl_settitle(struct controlbox *b,
+ char *path, char *title)
+{
+
+ struct controlset *s = snew(struct controlset);
+ int index = ctrl_find_set(b, path, 1);
+ s->pathname = dupstr(path);
+ s->boxname = NULL;
+ s->boxtitle = dupstr(title);
+ s->ncontrols = s->ctrlsize = 0;
+ s->ncolumns = 0; /* this is a title! */
+ s->ctrls = NULL;
+ if (b->nctrlsets >= b->ctrlsetsize) {
+ b->ctrlsetsize = b->nctrlsets + 32;
+ b->ctrlsets = sresize(b->ctrlsets, b->ctrlsetsize,struct controlset *);
+ }
+ if (index < b->nctrlsets)
+ memmove(&b->ctrlsets[index+1], &b->ctrlsets[index],
+ (b->nctrlsets-index) * sizeof(*b->ctrlsets));
+ b->ctrlsets[index] = s;
+ b->nctrlsets++;
+ return s;
+}
+
+/* Retrieve a pointer to a controlset, creating it if absent. */
+struct controlset *ctrl_getset(struct controlbox *b,
+ char *path, char *name, char *boxtitle)
+{
+ struct controlset *s;
+ int index = ctrl_find_set(b, path, 1);
+ while (index < b->nctrlsets &&
+ !strcmp(b->ctrlsets[index]->pathname, path)) {
+ if (b->ctrlsets[index]->boxname &&
+ !strcmp(b->ctrlsets[index]->boxname, name))
+ return b->ctrlsets[index];
+ index++;
+ }
+ s = snew(struct controlset);
+ s->pathname = dupstr(path);
+ s->boxname = dupstr(name);
+ s->boxtitle = boxtitle ? dupstr(boxtitle) : NULL;
+ s->ncolumns = 1;
+ s->ncontrols = s->ctrlsize = 0;
+ s->ctrls = NULL;
+ if (b->nctrlsets >= b->ctrlsetsize) {
+ b->ctrlsetsize = b->nctrlsets + 32;
+ b->ctrlsets = sresize(b->ctrlsets, b->ctrlsetsize,struct controlset *);
+ }
+ if (index < b->nctrlsets)
+ memmove(&b->ctrlsets[index+1], &b->ctrlsets[index],
+ (b->nctrlsets-index) * sizeof(*b->ctrlsets));
+ b->ctrlsets[index] = s;
+ b->nctrlsets++;
+ return s;
+}
+
+/* Allocate some private data in a controlbox. */
+void *ctrl_alloc(struct controlbox *b, size_t size)
+{
+ void *p;
+ /*
+ * This is an internal allocation routine, so it's allowed to
+ * use smalloc directly.
+ */
+ p = smalloc(size);
+ if (b->nfrees >= b->freesize) {
+ b->freesize = b->nfrees + 32;
+ b->frees = sresize(b->frees, b->freesize, void *);
+ }
+ b->frees[b->nfrees++] = p;
+ return p;
+}
+
+static union control *ctrl_new(struct controlset *s, int type,
+ intorptr helpctx, handler_fn handler,
+ intorptr context)
+{
+ union control *c = snew(union control);
+ if (s->ncontrols >= s->ctrlsize) {
+ s->ctrlsize = s->ncontrols + 32;
+ s->ctrls = sresize(s->ctrls, s->ctrlsize, union control *);
+ }
+ s->ctrls[s->ncontrols++] = c;
+ /*
+ * Fill in the standard fields.
+ */
+ c->generic.type = type;
+ c->generic.tabdelay = 0;
+ c->generic.column = COLUMN_FIELD(0, s->ncolumns);
+ c->generic.helpctx = helpctx;
+ c->generic.handler = handler;
+ c->generic.context = context;
+ c->generic.label = NULL;
+ return c;
+}
+
+/* `ncolumns' is followed by that many percentages, as integers. */
+union control *ctrl_columns(struct controlset *s, int ncolumns, ...)
+{
+ union control *c = ctrl_new(s, CTRL_COLUMNS, P(NULL), NULL, P(NULL));
+ assert(s->ncolumns == 1 || ncolumns == 1);
+ c->columns.ncols = ncolumns;
+ s->ncolumns = ncolumns;
+ if (ncolumns == 1) {
+ c->columns.percentages = NULL;
+ } else {
+ va_list ap;
+ int i;
+ c->columns.percentages = snewn(ncolumns, int);
+ va_start(ap, ncolumns);
+ for (i = 0; i < ncolumns; i++)
+ c->columns.percentages[i] = va_arg(ap, int);
+ va_end(ap);
+ }
+ return c;
+}
+
+union control *ctrl_editbox(struct controlset *s, char *label, char shortcut,
+ int percentage,
+ intorptr helpctx, handler_fn handler,
+ intorptr context, intorptr context2)
+{
+ union control *c = ctrl_new(s, CTRL_EDITBOX, helpctx, handler, context);
+ c->editbox.label = label ? dupstr(label) : NULL;
+ c->editbox.shortcut = shortcut;
+ c->editbox.percentwidth = percentage;
+ c->editbox.password = 0;
+ c->editbox.has_list = 0;
+ c->editbox.context2 = context2;
+ return c;
+}
+
+union control *ctrl_combobox(struct controlset *s, char *label, char shortcut,
+ int percentage,
+ intorptr helpctx, handler_fn handler,
+ intorptr context, intorptr context2)
+{
+ union control *c = ctrl_new(s, CTRL_EDITBOX, helpctx, handler, context);
+ c->editbox.label = label ? dupstr(label) : NULL;
+ c->editbox.shortcut = shortcut;
+ c->editbox.percentwidth = percentage;
+ c->editbox.password = 0;
+ c->editbox.has_list = 1;
+ c->editbox.context2 = context2;
+ return c;
+}
+
+/*
+ * `ncolumns' is followed by (alternately) radio button titles and
+ * intorptrs, until a NULL in place of a title string is seen. Each
+ * title is expected to be followed by a shortcut _iff_ `shortcut'
+ * is NO_SHORTCUT.
+ */
+union control *ctrl_radiobuttons(struct controlset *s, char *label,
+ char shortcut, int ncolumns, intorptr helpctx,
+ handler_fn handler, intorptr context, ...)
+{
+ va_list ap;
+ int i;
+ union control *c = ctrl_new(s, CTRL_RADIO, helpctx, handler, context);
+ c->radio.label = label ? dupstr(label) : NULL;
+ c->radio.shortcut = shortcut;
+ c->radio.ncolumns = ncolumns;
+ /*
+ * Initial pass along variable argument list to count the
+ * buttons.
+ */
+ va_start(ap, context);
+ i = 0;
+ while (va_arg(ap, char *) != NULL) {
+ i++;
+ if (c->radio.shortcut == NO_SHORTCUT)
+ (void)va_arg(ap, int); /* char promotes to int in arg lists */
+ (void)va_arg(ap, intorptr);
+ }
+ va_end(ap);
+ c->radio.nbuttons = i;
+ if (c->radio.shortcut == NO_SHORTCUT)
+ c->radio.shortcuts = snewn(c->radio.nbuttons, char);
+ else
+ c->radio.shortcuts = NULL;
+ c->radio.buttons = snewn(c->radio.nbuttons, char *);
+ c->radio.buttondata = snewn(c->radio.nbuttons, intorptr);
+ /*
+ * Second pass along variable argument list to actually fill in
+ * the structure.
+ */
+ va_start(ap, context);
+ for (i = 0; i < c->radio.nbuttons; i++) {
+ c->radio.buttons[i] = dupstr(va_arg(ap, char *));
+ if (c->radio.shortcut == NO_SHORTCUT)
+ c->radio.shortcuts[i] = va_arg(ap, int);
+ /* char promotes to int in arg lists */
+ c->radio.buttondata[i] = va_arg(ap, intorptr);
+ }
+ va_end(ap);
+ return c;
+}
+
+union control *ctrl_pushbutton(struct controlset *s,char *label,char shortcut,
+ intorptr helpctx, handler_fn handler,
+ intorptr context)
+{
+ union control *c = ctrl_new(s, CTRL_BUTTON, helpctx, handler, context);
+ c->button.label = label ? dupstr(label) : NULL;
+ c->button.shortcut = shortcut;
+ c->button.isdefault = 0;
+ c->button.iscancel = 0;
+ return c;
+}
+
+union control *ctrl_listbox(struct controlset *s,char *label,char shortcut,
+ intorptr helpctx, handler_fn handler,
+ intorptr context)
+{
+ union control *c = ctrl_new(s, CTRL_LISTBOX, helpctx, handler, context);
+ c->listbox.label = label ? dupstr(label) : NULL;
+ c->listbox.shortcut = shortcut;
+ c->listbox.height = 5; /* *shrug* a plausible default */
+ c->listbox.draglist = 0;
+ c->listbox.multisel = 0;
+ c->listbox.percentwidth = 100;
+ c->listbox.ncols = 0;
+ c->listbox.percentages = NULL;
+ return c;
+}
+
+union control *ctrl_droplist(struct controlset *s, char *label, char shortcut,
+ int percentage, intorptr helpctx,
+ handler_fn handler, intorptr context)
+{
+ union control *c = ctrl_new(s, CTRL_LISTBOX, helpctx, handler, context);
+ c->listbox.label = label ? dupstr(label) : NULL;
+ c->listbox.shortcut = shortcut;
+ c->listbox.height = 0; /* means it's a drop-down list */
+ c->listbox.draglist = 0;
+ c->listbox.multisel = 0;
+ c->listbox.percentwidth = percentage;
+ c->listbox.ncols = 0;
+ c->listbox.percentages = NULL;
+ return c;
+}
+
+union control *ctrl_draglist(struct controlset *s,char *label,char shortcut,
+ intorptr helpctx, handler_fn handler,
+ intorptr context)
+{
+ union control *c = ctrl_new(s, CTRL_LISTBOX, helpctx, handler, context);
+ c->listbox.label = label ? dupstr(label) : NULL;
+ c->listbox.shortcut = shortcut;
+ c->listbox.height = 5; /* *shrug* a plausible default */
+ c->listbox.draglist = 1;
+ c->listbox.multisel = 0;
+ c->listbox.percentwidth = 100;
+ c->listbox.ncols = 0;
+ c->listbox.percentages = NULL;
+ return c;
+}
+
+union control *ctrl_filesel(struct controlset *s,char *label,char shortcut,
+ char const *filter, int write, char *title,
+ intorptr helpctx, handler_fn handler,
+ intorptr context)
+{
+ union control *c = ctrl_new(s, CTRL_FILESELECT, helpctx, handler, context);
+ c->fileselect.label = label ? dupstr(label) : NULL;
+ c->fileselect.shortcut = shortcut;
+ c->fileselect.filter = filter;
+ c->fileselect.for_writing = write;
+ c->fileselect.title = dupstr(title);
+ return c;
+}
+
+union control *ctrl_fontsel(struct controlset *s,char *label,char shortcut,
+ intorptr helpctx, handler_fn handler,
+ intorptr context)
+{
+ union control *c = ctrl_new(s, CTRL_FONTSELECT, helpctx, handler, context);
+ c->fontselect.label = label ? dupstr(label) : NULL;
+ c->fontselect.shortcut = shortcut;
+ return c;
+}
+
+union control *ctrl_tabdelay(struct controlset *s, union control *ctrl)
+{
+ union control *c = ctrl_new(s, CTRL_TABDELAY, P(NULL), NULL, P(NULL));
+ c->tabdelay.ctrl = ctrl;
+ return c;
+}
+
+union control *ctrl_text(struct controlset *s, char *text, intorptr helpctx)
+{
+ union control *c = ctrl_new(s, CTRL_TEXT, helpctx, NULL, P(NULL));
+ c->text.label = dupstr(text);
+ return c;
+}
+
+union control *ctrl_checkbox(struct controlset *s, char *label, char shortcut,
+ intorptr helpctx, handler_fn handler,
+ intorptr context)
+{
+ union control *c = ctrl_new(s, CTRL_CHECKBOX, helpctx, handler, context);
+ c->checkbox.label = label ? dupstr(label) : NULL;
+ c->checkbox.shortcut = shortcut;
+ return c;
+}
+
+void ctrl_free(union control *ctrl)
+{
+ int i;
+
+ sfree(ctrl->generic.label);
+ switch (ctrl->generic.type) {
+ case CTRL_RADIO:
+ for (i = 0; i < ctrl->radio.nbuttons; i++)
+ sfree(ctrl->radio.buttons[i]);
+ sfree(ctrl->radio.buttons);
+ sfree(ctrl->radio.shortcuts);
+ sfree(ctrl->radio.buttondata);
+ break;
+ case CTRL_COLUMNS:
+ sfree(ctrl->columns.percentages);
+ break;
+ case CTRL_LISTBOX:
+ sfree(ctrl->listbox.percentages);
+ break;
+ case CTRL_FILESELECT:
+ 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));
+ }
+}
+
+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));
+ }
+}
--- /dev/null
+/*
+ * Exports and types from dialog.c.
+ */
+
+/*
+ * This will come in handy for generic control handlers. Anyone
+ * knows how to make this more portable, let me know :-)
+ */
+#define ATOFFSET(data, offset) ( (void *) ( (char *)(data) + (offset) ) )
+
+/*
+ * This is the big union which defines a single control, of any
+ * type.
+ *
+ * General principles:
+ * - _All_ pointers in this structure are expected to point to
+ * dynamically allocated things, unless otherwise indicated.
+ * - `char' fields giving keyboard shortcuts are expected to be
+ * NO_SHORTCUT if no shortcut is desired for a particular control.
+ * - The `label' field can often be NULL, which will cause the
+ * control to not have a label at all. This doesn't apply to
+ * checkboxes and push buttons, in which the label is not
+ * separate from the control.
+ */
+
+#define NO_SHORTCUT '\0'
+
+enum {
+ CTRL_TEXT, /* just a static line of text */
+ CTRL_EDITBOX, /* label plus edit box */
+ CTRL_RADIO, /* label plus radio buttons */
+ CTRL_CHECKBOX, /* checkbox (contains own label) */
+ CTRL_BUTTON, /* simple push button (no label) */
+ CTRL_LISTBOX, /* label plus list box */
+ CTRL_COLUMNS, /* divide window into columns */
+ CTRL_FILESELECT, /* label plus filename selector */
+ CTRL_FONTSELECT, /* label plus font selector */
+ CTRL_TABDELAY /* see `tabdelay' below */
+};
+
+/*
+ * Many controls have `intorptr' unions for storing user data,
+ * since the user might reasonably want to store either an integer
+ * or a void * pointer. Here I define a union, and two convenience
+ * functions to create that union from actual integers or pointers.
+ *
+ * The convenience functions are declared as inline if possible.
+ * Otherwise, they're declared here and defined when this header is
+ * included with DEFINE_INTORPTR_FNS defined. This is a total pain,
+ * but such is life.
+ */
+typedef union { void *p; int i; } intorptr;
+
+#ifndef INLINE
+intorptr I(int i);
+intorptr P(void *p);
+#endif
+
+#if defined DEFINE_INTORPTR_FNS || defined INLINE
+#ifdef INLINE
+#define PREFIX INLINE
+#else
+#define PREFIX
+#endif
+PREFIX intorptr I(int i) { intorptr ret; ret.i = i; return ret; }
+PREFIX intorptr P(void *p) { intorptr ret; ret.p = p; return ret; }
+#undef PREFIX
+#endif
+
+/*
+ * Each control has an `int' field specifying which columns it
+ * occupies in a multi-column part of the dialog box. These macros
+ * pack and unpack that field.
+ *
+ * If a control belongs in exactly one column, just specifying the
+ * column number is perfectly adequate.
+ */
+#define COLUMN_FIELD(start, span) ( (((span)-1) << 16) + (start) )
+#define COLUMN_START(field) ( (field) & 0xFFFF )
+#define COLUMN_SPAN(field) ( (((field) >> 16) & 0xFFFF) + 1 )
+
+union control;
+
+/*
+ * The number of event types is being deliberately kept small, on
+ * the grounds that not all platforms might be able to report a
+ * large number of subtle events. We have:
+ * - the special REFRESH event, called when a control's value
+ * needs setting
+ * - the ACTION event, called when the user does something that
+ * positively requests action (double-clicking a list box item,
+ * or pushing a push-button)
+ * - the VALCHANGE event, called when the user alters the setting
+ * of the control in a way that is usually considered to alter
+ * the underlying data (toggling a checkbox or radio button,
+ * moving the items around in a drag-list, editing an edit
+ * control)
+ * - the SELCHANGE event, called when the user alters the setting
+ * of the control in a more minor way (changing the selected
+ * item in a list box).
+ * - the CALLBACK event, which happens after the handler routine
+ * has requested a subdialog (file selector, font selector,
+ * colour selector) and it has come back with information.
+ */
+enum {
+ EVENT_REFRESH,
+ EVENT_ACTION,
+ EVENT_VALCHANGE,
+ EVENT_SELCHANGE,
+ EVENT_CALLBACK
+};
+typedef void (*handler_fn)(union control *ctrl, void *dlg,
+ void *data, int event);
+
+#define STANDARD_PREFIX \
+ int type; \
+ char *label; \
+ int tabdelay; \
+ int column; \
+ handler_fn handler; \
+ intorptr context; \
+ intorptr helpctx
+
+union control {
+ /*
+ * The first possibility in this union is the generic header
+ * shared by all the structures, which we are therefore allowed
+ * to access through any one of them.
+ */
+ struct {
+ int type;
+ /*
+ * Every control except CTRL_COLUMNS has _some_ sort of
+ * label. By putting it in the `generic' union as well as
+ * everywhere else, we avoid having to have an irritating
+ * switch statement when we go through and deallocate all
+ * the memory in a config-box structure.
+ *
+ * Yes, this does mean that any non-NULL value in this
+ * field is expected to be dynamically allocated and
+ * freeable.
+ *
+ * For CTRL_COLUMNS, this field MUST be NULL.
+ */
+ char *label;
+ /*
+ * If `tabdelay' is non-zero, it indicates that this
+ * particular control should not yet appear in the tab
+ * order. A subsequent CTRL_TABDELAY entry will place it.
+ */
+ int tabdelay;
+ /*
+ * Indicate which column(s) this control occupies. This can
+ * be unpacked into starting column and column span by the
+ * COLUMN macros above.
+ */
+ int column;
+ /*
+ * Most controls need to provide a function which gets
+ * 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
+ * necessarily.
+ *
+ * The `dlg' parameter is passed back to the platform-
+ * specific routines to read and write the actual control
+ * state.
+ */
+ handler_fn handler;
+ /*
+ * Almost all of the above functions will find it useful to
+ * be able to store a piece of `void *' or `int' data.
+ */
+ intorptr context;
+ /*
+ * For any control, we also allow the storage of a piece of
+ * data for use by context-sensitive help. For example, on
+ * Windows you can click the magic question mark and then
+ * click a control, and help for that control should spring
+ * up. Hence, here is a slot in which to store per-control
+ * data that a particular platform-specific driver can use
+ * to ensure it brings up the right piece of help text.
+ */
+ intorptr helpctx;
+ } generic;
+ struct {
+ STANDARD_PREFIX;
+ union control *ctrl;
+ } tabdelay;
+ struct {
+ STANDARD_PREFIX;
+ } text;
+ struct {
+ STANDARD_PREFIX;
+ char shortcut; /* keyboard shortcut */
+ /*
+ * Percentage of the dialog-box width used by the edit box.
+ * If this is set to 100, the label is on its own line;
+ * otherwise the label is on the same line as the box
+ * itself.
+ */
+ int percentwidth;
+ int password; /* details of input are hidden */
+ /*
+ * A special case of the edit box is the combo box, which
+ * has a drop-down list built in. (Note that a _non_-
+ * editable drop-down list is done as a special case of a
+ * list box.)
+ *
+ * Don't try setting has_list and password on the same
+ * control; front ends are not required to support that
+ * combination.
+ */
+ int has_list;
+ /*
+ * Edit boxes tend to need two items of context, so here's
+ * a spare.
+ */
+ intorptr context2;
+ } editbox;
+ struct {
+ STANDARD_PREFIX;
+ /*
+ * `shortcut' here is a single keyboard shortcut which is
+ * expected to select the whole group of radio buttons. It
+ * can be NO_SHORTCUT if required, and there is also a way
+ * to place individual shortcuts on each button; see below.
+ */
+ char shortcut;
+ /*
+ * There are separate fields for `ncolumns' and `nbuttons'
+ * for several reasons.
+ *
+ * Firstly, we sometimes want the last of a set of buttons
+ * to have a longer label than the rest; we achieve this by
+ * setting `ncolumns' higher than `nbuttons', and the
+ * layout code is expected to understand that the final
+ * button should be given all the remaining space on the
+ * line. This sounds like a ludicrously specific special
+ * case (if we're doing this sort of thing, why not have
+ * the general ability to have a particular button span
+ * more than one column whether it's the last one or not?)
+ * but actually it's reasonably common for the sort of
+ * three-way control you get a lot of in PuTTY: `yes'
+ * versus `no' versus `some more complex way to decide'.
+ *
+ * Secondly, setting `nbuttons' higher than `ncolumns' lets
+ * us have more than one line of radio buttons for a single
+ * setting. A very important special case of this is
+ * setting `ncolumns' to 1, so that each button is on its
+ * own line.
+ */
+ int ncolumns;
+ int nbuttons;
+ /*
+ * This points to a dynamically allocated array of `char *'
+ * pointers, each of which points to a dynamically
+ * allocated string.
+ */
+ char **buttons; /* `nbuttons' button labels */
+ /*
+ * This points to a dynamically allocated array of `char'
+ * giving the individual keyboard shortcuts for each radio
+ * button. The array may be NULL if none are required.
+ */
+ char *shortcuts; /* `nbuttons' shortcuts; may be NULL */
+ /*
+ * This points to a dynamically allocated array of
+ * intorptr, giving helpful data for each button.
+ */
+ intorptr *buttondata; /* `nbuttons' entries; may be NULL */
+ } radio;
+ struct {
+ STANDARD_PREFIX;
+ char shortcut;
+ } checkbox;
+ struct {
+ STANDARD_PREFIX;
+ char shortcut;
+ /*
+ * At least Windows has the concept of a `default push
+ * button', which gets implicitly pressed when you hit
+ * Return even if it doesn't have the input focus.
+ */
+ int isdefault;
+ /*
+ * Also, the reverse of this: a default cancel-type button,
+ * which is implicitly pressed when you hit Escape.
+ */
+ int iscancel;
+ } button;
+ struct {
+ STANDARD_PREFIX;
+ char shortcut; /* keyboard shortcut */
+ /*
+ * Height of the list box, in approximate number of lines.
+ * If this is zero, the list is a drop-down list.
+ */
+ int height; /* height in lines */
+ /*
+ * If this is set, the list elements can be reordered by
+ * the user (by drag-and-drop or by Up and Down buttons,
+ * whatever the per-platform implementation feels
+ * comfortable with). This is not guaranteed to work on a
+ * drop-down list, so don't try it!
+ */
+ int draglist;
+ /*
+ * If this is non-zero, the list can have more than one
+ * element selected at a time. This is not guaranteed to
+ * work on a drop-down list, so don't try it!
+ *
+ * Different non-zero values request slightly different
+ * types of multi-selection (this may well be meaningful
+ * only in GTK, so everyone else can ignore it if they
+ * want). 1 means the list box expects to have individual
+ * items selected, whereas 2 means it expects the user to
+ * want to select a large contiguous range at a time.
+ */
+ int multisel;
+ /*
+ * Percentage of the dialog-box width used by the list box.
+ * If this is set to 100, the label is on its own line;
+ * otherwise the label is on the same line as the box
+ * itself. Setting this to anything other than 100 is not
+ * guaranteed to work on a _non_-drop-down list, so don't
+ * try it!
+ */
+ int percentwidth;
+ /*
+ * Some list boxes contain strings that contain tab
+ * characters. If `ncols' is greater than 0, then
+ * `percentages' is expected to be non-zero and to contain
+ * the respective widths of `ncols' columns, which together
+ * will exactly fit the width of the list box. Otherwise
+ * `percentages' must be NULL.
+ *
+ * There should never be more than one column in a
+ * drop-down list (one with height==0), because front ends
+ * may have to implement it as a special case of an
+ * editable combo box.
+ */
+ int ncols; /* number of columns */
+ int *percentages; /* % width of each column */
+ } listbox;
+ struct {
+ STANDARD_PREFIX;
+ char shortcut;
+ /*
+ * `filter' dictates what type of files will be selected by
+ * default; for example, when selecting private key files
+ * the file selector would do well to only show .PPK files
+ * (on those systems where this is the chosen extension).
+ *
+ * The precise contents of `filter' are platform-defined,
+ * unfortunately. The special value NULL means `all files'
+ * and is always a valid fallback.
+ *
+ * Unlike almost all strings in this structure, this value
+ * is NOT expected to require freeing (although of course
+ * you can always use ctrl_alloc if you do need to create
+ * one on the fly). This is because the likely mode of use
+ * is to define string constants in a platform-specific
+ * header file, and directly reference those. Or worse, a
+ * particular platform might choose to cast integers into
+ * this pointer type...
+ */
+ char const *filter;
+ /*
+ * Some systems like to know whether a file selector is
+ * choosing a file to read or one to write (and possibly
+ * create).
+ */
+ int for_writing;
+ /*
+ * On at least some platforms, the file selector is a
+ * separate dialog box, and contains a user-settable title.
+ *
+ * This value _is_ expected to require freeing.
+ */
+ char *title;
+ } fileselect;
+ struct {
+ /* In this variant, `label' MUST be NULL. */
+ STANDARD_PREFIX;
+ int ncols; /* number of columns */
+ int *percentages; /* % width of each column */
+ /*
+ * Every time this control type appears, exactly one of
+ * `ncols' and the previous number of columns MUST be one.
+ * Attempting to allow a seamless transition from a four-
+ * to a five-column layout, for example, would be way more
+ * trouble than it was worth. If you must lay things out
+ * like that, define eight unevenly sized columns and use
+ * column-spanning a lot. But better still, just don't.
+ *
+ * `percentages' may be NULL if ncols==1, to save space.
+ */
+ } columns;
+ struct {
+ STANDARD_PREFIX;
+ char shortcut;
+ } fontselect;
+};
+
+#undef STANDARD_PREFIX
+
+/*
+ * `controlset' is a container holding an array of `union control'
+ * structures, together with a panel name and a title for the whole
+ * set. In Windows and any similar-looking GUI, each `controlset'
+ * in the config will be a container box within a panel.
+ *
+ * Special case: if `boxname' is NULL, the control set gives an
+ * overall title for an entire panel of controls.
+ */
+struct controlset {
+ char *pathname; /* panel path, e.g. "SSH/Tunnels" */
+ char *boxname; /* internal short name of controlset */
+ char *boxtitle; /* title of container box */
+ int ncolumns; /* current no. of columns at bottom */
+ int ncontrols; /* number of `union control' in array */
+ int ctrlsize; /* allocated size of array */
+ union control **ctrls; /* actual array */
+};
+
+/*
+ * This is the container structure which holds a complete set of
+ * controls.
+ */
+struct controlbox {
+ int nctrlsets; /* number of ctrlsets */
+ int ctrlsetsize; /* ctrlset size */
+ struct controlset **ctrlsets; /* actual array of ctrlsets */
+ int nfrees;
+ int freesize;
+ void **frees; /* array of aux data areas to free */
+};
+
+struct controlbox *ctrl_new_box(void);
+void ctrl_free_box(struct controlbox *);
+
+/*
+ * Standard functions used for populating a controlbox structure.
+ */
+
+/* Set up a panel title. */
+struct controlset *ctrl_settitle(struct controlbox *,
+ char *path, char *title);
+/* Retrieve a pointer to a controlset, creating it if absent. */
+struct controlset *ctrl_getset(struct controlbox *,
+ char *path, char *name, char *boxtitle);
+void ctrl_free_set(struct controlset *);
+
+void ctrl_free(union control *);
+
+/*
+ * This function works like `malloc', but the memory it returns
+ * 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.
+ */
+void *ctrl_alloc(struct controlbox *b, size_t size);
+
+/*
+ * Individual routines to create `union control' structures in a controlset.
+ *
+ * Most of these routines allow the most common fields to be set
+ * directly, and put default values in the rest. Each one returns a
+ * pointer to the `union control' it created, so that final tweaks
+ * can be made.
+ */
+
+/* `ncolumns' is followed by that many percentages, as integers. */
+union control *ctrl_columns(struct controlset *, int ncolumns, ...);
+union control *ctrl_editbox(struct controlset *, char *label, char shortcut,
+ int percentage, intorptr helpctx,
+ handler_fn handler,
+ intorptr context, intorptr context2);
+union control *ctrl_combobox(struct controlset *, char *label, char shortcut,
+ int percentage, intorptr helpctx,
+ handler_fn handler,
+ intorptr context, intorptr context2);
+/*
+ * `ncolumns' is followed by (alternately) radio button titles and
+ * intorptrs, until a NULL in place of a title string is seen. Each
+ * title is expected to be followed by a shortcut _iff_ `shortcut'
+ * is NO_SHORTCUT.
+ */
+union control *ctrl_radiobuttons(struct controlset *, char *label,
+ char shortcut, int ncolumns,
+ intorptr helpctx,
+ handler_fn handler, intorptr context, ...);
+union control *ctrl_pushbutton(struct controlset *,char *label,char shortcut,
+ intorptr helpctx,
+ handler_fn handler, intorptr context);
+union control *ctrl_listbox(struct controlset *,char *label,char shortcut,
+ intorptr helpctx,
+ handler_fn handler, intorptr context);
+union control *ctrl_droplist(struct controlset *, char *label, char shortcut,
+ int percentage, intorptr helpctx,
+ handler_fn handler, intorptr context);
+union control *ctrl_draglist(struct controlset *,char *label,char shortcut,
+ intorptr helpctx,
+ handler_fn handler, intorptr context);
+union control *ctrl_filesel(struct controlset *,char *label,char shortcut,
+ char const *filter, int write, char *title,
+ intorptr helpctx,
+ handler_fn handler, intorptr context);
+union control *ctrl_fontsel(struct controlset *,char *label,char shortcut,
+ intorptr helpctx,
+ handler_fn handler, intorptr context);
+union control *ctrl_text(struct controlset *, char *text, intorptr helpctx);
+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);
+/*
+ * 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);
+/* 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);
+/*
+ * Each listbox entry may have a numeric id associated with it.
+ * Note that some front ends only permit a string to be stored at
+ * each position, which means that _if_ you put two identical
+ * strings in any listbox then you MUST not assign them different
+ * IDs and expect to get meaningful results back.
+ */
+void dlg_listbox_addwithid(union control *ctrl, void *dlg,
+ char const *text, int id);
+int dlg_listbox_getid(union control *ctrl, void *dlg, int index);
+/* 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);
+/*
+ * 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.
+ */
+void dlg_update_start(union control *ctrl, void *dlg);
+void dlg_update_done(union control *ctrl, void *dlg);
+/*
+ * Set input focus into a particular control.
+ */
+void dlg_set_focus(union control *ctrl, void *dlg);
+/*
+ * Change the label text on a control.
+ */
+void dlg_label_change(union control *ctrl, void *dlg, char const *text);
+/*
+ * Return the `ctrl' structure for the most recent control that had
+ * the input focus apart from the one mentioned. This is NOT
+ * GUARANTEED to work on all platforms, so don't base any critical
+ * functionality on it!
+ */
+union control *dlg_last_focused(union control *ctrl, void *dlg);
+/*
+ * During event processing, you might well want to give an error
+ * indication to the user. dlg_beep() is a quick and easy generic
+ * error; dlg_error() puts up a message-box or equivalent.
+ */
+void dlg_beep(void *dlg);
+void dlg_error_msg(void *dlg, char *msg);
+/*
+ * This function signals to the front end that the dialog's
+ * processing is completed, and passes an integer value (typically
+ * a success status).
+ */
+void dlg_end(void *dlg, int value);
+
+/*
+ * Routines to manage a (per-platform) colour selector.
+ * dlg_coloursel_start() is called in an event handler, and
+ * schedules the running of a colour selector after the event
+ * handler returns. The colour selector will send EVENT_CALLBACK to
+ * the control that spawned it, when it's finished;
+ * dlg_coloursel_results() fetches the results, as integers from 0
+ * to 255; it returns nonzero on success, or zero if the colour
+ * selector was dismissed by hitting Cancel or similar.
+ *
+ * dlg_coloursel_start() accepts an RGB triple which is used to
+ * initialise the colour selector to its starting value.
+ */
+void dlg_coloursel_start(union control *ctrl, void *dlg,
+ int r, int g, int b);
+int dlg_coloursel_results(union control *ctrl, void *dlg,
+ int *r, int *g, int *b);
+
+/*
+ * This routine is used by the platform-independent code to
+ * indicate that the value of a particular control is likely to
+ * have changed. It triggers a call of the handler for that control
+ * with `event' set to EVENT_REFRESH.
+ *
+ * If `ctrl' is NULL, _all_ controls in the dialog get refreshed
+ * (for loading or saving entire sets of settings).
+ */
+void dlg_refresh(union control *ctrl, void *dlg);
+
+/*
+ * It's perfectly possible that individual controls might need to
+ * allocate or store per-dialog-instance data, so here's a
+ * mechanism.
+ *
+ * `dlg_get_privdata' and `dlg_set_privdata' allow the user to get
+ * and set a void * pointer associated with the control in
+ * question. `dlg_alloc_privdata' will allocate memory, store a
+ * pointer to that memory in the private data field, and arrange
+ * for it to be automatically deallocated on dialog cleanup.
+ */
+void *dlg_get_privdata(union control *ctrl, void *dlg);
+void dlg_set_privdata(union control *ctrl, void *dlg, void *ptr);
+void *dlg_alloc_privdata(union control *ctrl, void *dlg, size_t size);
+
+/*
+ * Standard helper functions for reading a controlbox structure.
+ */
+
+/*
+ * Find the index of next controlset in a controlbox for a given
+ * path, or -1 if no such controlset exists. If -1 is passed as
+ * input, finds the first. Intended usage is something like
+ *
+ * for (index=-1; (index=ctrl_find_path(ctrlbox, index, path)) >= 0 ;) {
+ * ... process this controlset ...
+ * }
+ */
+int ctrl_find_path(struct controlbox *b, char *path, int index);
+int ctrl_path_elements(char *path);
+/* Return the number of matching path elements at the starts of p1 and p2,
+ * or INT_MAX if the paths are identical. */
+int ctrl_path_compare(char *p1, char *p2);
--- /dev/null
+all: man index.html
+
+# Decide on the versionid policy.
+#
+# If the user has passed in $(VERSION) on the command line (`make
+# VERSION="Release 0.56"'), we use that as an explicit version
+# string. Otherwise, we use `svnversion' to examine the checked-out
+# documentation source, and if that returns a single revision
+# number then we invent a version string reflecting just that
+# number. Failing _that_, we resort to versionids.but which shows a
+# $Id for each individual file.
+#
+# So here, we define VERSION using svnversion if it isn't already
+# defined ...
+ifndef VERSION
+SVNVERSION=$(shell test -d .svn && svnversion .)
+BADCHARS=$(findstring :,$(SVNVERSION))$(findstring S,$(SVNVERSION))
+ifeq ($(BADCHARS),)
+ifneq ($(SVNVERSION),)
+ifneq ($(SVNVERSION),exported)
+VERSION=Built from revision $(patsubst M,,$(SVNVERSION))
+endif
+endif
+endif
+endif
+# ... and now, we condition our build behaviour on whether or not
+# VERSION _is_ defined.
+ifdef VERSION
+VERSIONIDS=vstr
+vstr.but: FORCE
+ echo \\versionid $(VERSION) > vstr.but
+FORCE:;
+else
+VERSIONIDS=vids
+endif
+
+CHAPTERS := $(SITE) blurb intro gs using config pscp psftp plink pubkey
+CHAPTERS += pageant errors faq feedback licence udp pgpkeys sshnames
+CHAPTERS += index $(VERSIONIDS)
+
+INPUTS = $(patsubst %,%.but,$(CHAPTERS))
+
+# This is temporary. Hack it locally or something.
+HALIBUT = halibut
+
+index.html: $(INPUTS)
+ $(HALIBUT) --text --html --winhelp $(INPUTS)
+
+# During formal builds it's useful to be able to build this one alone.
+putty.hlp: $(INPUTS)
+ $(HALIBUT) --winhelp $(INPUTS)
+
+putty.info: $(INPUTS)
+ $(HALIBUT) --info $(INPUTS)
+
+chm: putty.hhp
+putty.hhp: $(INPUTS) chm.but
+ $(HALIBUT) --html $(INPUTS) chm.but
+
+MKMAN = $(HALIBUT) --man=$@ mancfg.but $<
+MANPAGES = putty.1 puttygen.1 plink.1 pscp.1 psftp.1 puttytel.1 pterm.1
+man: $(MANPAGES)
+
+putty.1: man-putt.but mancfg.but; $(MKMAN)
+puttygen.1: man-pg.but mancfg.but; $(MKMAN)
+plink.1: man-pl.but mancfg.but; $(MKMAN)
+pscp.1: man-pscp.but mancfg.but; $(MKMAN)
+psftp.1: man-psft.but mancfg.but; $(MKMAN)
+puttytel.1: man-ptel.but mancfg.but; $(MKMAN)
+pterm.1: man-pter.but mancfg.but; $(MKMAN)
+
+mostlyclean:
+ rm -f *.html *.txt *.hlp *.cnt *.1 *.info vstr.but *.hh[pck]
+clean: mostlyclean
+ rm -f *.chm
--- /dev/null
+\define{versionidblurb} \versionid $Id$
+
+\title PuTTY User Manual
+
+\cfg{xhtml-leaf-level}{1}
+\cfg{xhtml-leaf-smallest-contents}{2}
+\cfg{xhtml-leaf-contains-contents}{true}
+\cfg{xhtml-body-end}{<p>If you want to provide feedback on this manual
+or on the PuTTY tools themselves, see the
+<a href="http://www.chiark.greenend.org.uk/~sgtatham/putty/feedback.html">Feedback
+page</a>.</p>}
+
+\cfg{html-template-fragment}{%k}{%b}
+
+\cfg{info-max-file-size}{0}
+
+\cfg{xhtml-contents-filename}{index.html}
+\cfg{text-filename}{puttydoc.txt}
+\cfg{winhelp-filename}{putty.hlp}
+\cfg{info-filename}{putty.info}
+
+PuTTY is a free (MIT-licensed) Win32 Telnet and SSH client. This
+manual documents PuTTY, and its companion utilities PSCP, PSFTP,
+Plink, Pageant and PuTTYgen.
+
+\e{Note to Unix users:} this manual currently primarily documents the
+Windows versions of the PuTTY utilities. Some options are therefore
+mentioned that are absent from the \i{Unix version}; the Unix version has
+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
+rights reserved. You may distribute this documentation under the MIT
+licence. See \k{licence} for the licence text in full.
--- /dev/null
+\# File containing the magic HTML configuration directives to create
+\# an MS HTML Help project. We put this on the end of the PuTTY
+\# docs build command line to build the HHP and friends.
+
+\cfg{html-leaf-level}{infinite}
+\cfg{html-leaf-contains-contents}{false}
+\cfg{html-suppress-navlinks}{true}
+\cfg{html-suppress-address}{true}
+
+\cfg{html-contents-filename}{index.html}
+\cfg{html-template-filename}{%k.html}
+\cfg{html-template-fragment}{%k}
+
+\cfg{html-mshtmlhelp-chm}{putty.chm}
+\cfg{html-mshtmlhelp-project}{putty.hhp}
+\cfg{html-mshtmlhelp-contents}{putty.hhc}
+\cfg{html-mshtmlhelp-index}{putty.hhk}
+
+\cfg{html-body-end}{}
+
+\cfg{html-head-end}{<link rel="stylesheet" type="text/css" href="chm.css">}
+
+\versionid $Id$
--- /dev/null
+/* Stylesheet for a Windows .CHM help file */
+
+body { font-size: 75%; font-family: Verdana, Arial, Helvetica, Sans-Serif; }
+
+h1 { font-weight: bold; font-size: 150%; }
+h2 { font-weight: bold; font-size: 130%; }
+h3 { font-weight: bold; font-size: 120%; }
--- /dev/null
+\define{versionidconfig} \versionid $Id$
+
+\C{config} Configuring PuTTY
+
+This chapter describes all the \i{configuration options} in PuTTY.
+
+PuTTY is configured using the control panel that comes up before you
+start a session. Some options can also be changed in the middle of a
+session, by selecting \q{Change Settings} from the window menu.
+
+\H{config-session} The Session panel
+
+The Session configuration panel contains the basic options you need
+to specify in order to open a session at all, and also allows you to
+save your settings to be reloaded later.
+
+\S{config-hostname} The \i{host name} section
+
+\cfg{winhelp-topic}{session.hostname}
+
+The top box on the Session panel, labelled \q{Specify your
+connection by host name}, contains the details that need to be
+filled in before PuTTY can open a session at all.
+
+\b The \q{Host Name} box is where you type the name, or the \i{IP
+address}, of the server you want to connect to.
+
+\b The \q{Connection type} radio buttons let you choose what type of
+connection you want to make: a \I{raw TCP connections}raw
+connection, a \i{Telnet} connection, an \i{Rlogin} connection, an
+\i{SSH} connection, or a connection to a local \i{serial line}. (See
+\k{which-one} for a summary of the differences between SSH, Telnet
+and rlogin; see \k{using-rawprot} for an explanation of \q{raw}
+connections; see \k{using-serial} for information about using a
+serial line.)
+
+\b The \q{Port} box lets you specify which \i{port number} on the
+server to connect to. If you select Telnet, Rlogin, or SSH, this box
+will be filled in automatically to the usual value, and you will
+only need to change it if you have an unusual server. If you select
+Raw mode, you will almost certainly need to fill in the \q{Port} box
+yourself.
+
+If you select \q{Serial} from the \q{Connection type} radio buttons,
+the \q{Host Name} and \q{Port} boxes are replaced by \q{Serial line}
+and \q{Speed}; see \k{config-serial} for more details of these.
+
+\S{config-saving} \ii{Loading and storing saved sessions}
+
+\cfg{winhelp-topic}{session.saved}
+
+The next part of the Session configuration panel allows you to save
+your preferred PuTTY options so they will appear automatically the
+next time you start PuTTY. It also allows you to create \e{saved
+sessions}, which contain a full set of configuration options plus a
+host name and protocol. A saved session contains all the information
+PuTTY needs to start exactly the session you want.
+
+\b To save your default settings: first set up the settings the way
+you want them saved. Then come back to the Session panel. Select the
+\q{\i{Default Settings}} entry in the saved sessions list, with a single
+click. Then press the \q{Save} button.
+
+If there is a specific host you want to store the details of how to
+connect to, you should create a saved session, which will be
+separate from the Default Settings.
+
+\b To save a session: first go through the rest of the configuration
+box setting up all the options you want. Then come back to the
+Session panel. Enter a name for the saved session in the \q{Saved
+Sessions} input box. (The server name is often a good choice for a
+saved session name.) Then press the \q{Save} button. Your saved
+session name should now appear in the list box.
+
+\lcont{
+You can also save settings in mid-session, from the \q{Change Settings}
+dialog. Settings changed since the start of the session will be saved
+with their current values; as well as settings changed through the
+dialog, this includes changes in window size, window title changes
+sent by the server, and so on.
+}
+
+\b To reload a saved session: single-click to select the session
+name in the list box, and then press the \q{Load} button. Your saved
+settings should all appear in the configuration panel.
+
+\b To modify a saved session: first load it as described above. Then
+make the changes you want. Come back to the Session panel, and press
+the \q{Save} button. The new settings will be saved over the top of
+the old ones.
+
+\lcont{
+To save the new settings under a different name, you can enter the new
+name in the \q{Saved Sessions} box, or single-click to select a
+session name in the list box to overwrite that session. To save
+\q{Default Settings}, you must single-click the name before saving.
+}
+
+\b To start a saved session immediately: double-click on the session
+name in the list box.
+
+\b To delete a saved session: single-click to select the session
+name in the list box, and then press the \q{Delete} button.
+
+Each saved session is independent of the Default Settings
+configuration. If you change your preferences and update Default
+Settings, you must also update every saved session separately.
+
+Saved sessions are stored in the \i{Registry}, at the location
+
+\c HKEY_CURRENT_USER\Software\SimonTatham\PuTTY\Sessions
+
+If you need to store them in a file, you could try the method
+described in \k{config-file}.
+
+\S{config-closeonexit} \q{\ii{Close Window} on Exit}
+
+\cfg{winhelp-topic}{session.coe}
+
+Finally in the Session panel, there is an option labelled \q{Close
+Window on Exit}. This controls whether the PuTTY \i{terminal window}
+disappears as soon as the session inside it terminates. If you are
+likely to want to copy and paste text out of the session after it
+has terminated, or restart the session, you should arrange for this
+option to be off.
+
+\q{Close Window On Exit} has three settings. \q{Always} means always
+close the window on exit; \q{Never} means never close on exit
+(always leave the window open, but \I{inactive window}inactive). The
+third setting, and the default one, is \q{Only on clean exit}. In this
+mode, a session which terminates normally will cause its window to
+close, but one which is aborted unexpectedly by network trouble or a
+confusing message from the server will leave the window up.
+
+\H{config-logging} The Logging panel
+
+\cfg{winhelp-topic}{logging.main}
+
+The Logging configuration panel allows you to save \i{log file}s of your
+PuTTY sessions, for debugging, analysis or future reference.
+
+The main option is a radio-button set that specifies whether PuTTY
+will log anything at all. The options are:
+
+\b \q{None}. This is the default option; in this mode PuTTY will not
+create a log file at all.
+
+\b \q{Printable output}. In this mode, a log file will be
+created and written to, but only printable text will be saved into
+it. The various terminal control codes that are typically sent down
+an interactive session alongside the printable text will be omitted.
+This might be a useful mode if you want to read a log file in a text
+editor and hope to be able to make sense of it.
+
+\b \q{All session output}. In this mode, \e{everything} sent by
+the server into your terminal session is logged. If you view the log
+file in a text editor, therefore, you may well find it full of
+strange control characters. This is a particularly useful mode if
+you are experiencing problems with PuTTY's terminal handling: you
+can record everything that went to the terminal, so that someone
+else can replay the session later in slow motion and watch to see
+what went wrong.
+
+\b \I{SSH packet log}\q{SSH packets}. In this mode (which is only used
+by SSH connections), the SSH message packets sent over the encrypted
+connection are written to the log file (as well as \i{Event Log}
+entries). You might need this to debug a network-level problem, or
+more likely to send to the PuTTY authors as part of a bug report.
+\e{BE WARNED} that if you log in using a password, the password can
+appear in the log file; see \k{config-logssh} for options that may
+help to remove sensitive material from the log file before you send it
+to anyone else.
+
+\b \q{SSH packets and raw data}. In this mode, as well as the
+decrypted packets (as in the previous mode), the \e{raw} (encrypted,
+compressed, etc) packets are \e{also} logged. This could be useful to
+diagnose corruption in transit. (The same caveats as the previous mode
+apply, of course.)
+
+Note that the non-SSH logging options (\q{Printable output} and
+\q{All session output}) only work with PuTTY proper; in programs
+without terminal emulation (such as Plink), they will have no effect,
+even if enabled via saved settings.
+
+\S{config-logfilename} \q{Log file name}
+
+\cfg{winhelp-topic}{logging.filename}
+
+In this edit box you enter the name of the file you want to log the
+session to. The \q{Browse} button will let you look around your file
+system to find the right place to put the file; or if you already
+know exactly where you want it to go, you can just type a pathname
+into the edit box.
+
+There are a few special features in this box. If you use the \c{&}
+character in the file name box, PuTTY will insert details of the
+current session in the name of the file it actually opens. The
+precise replacements it will do are:
+
+\b \c{&Y} will be replaced by the current year, as four digits.
+
+\b \c{&M} will be replaced by the current month, as two digits.
+
+\b \c{&D} will be replaced by the current day of the month, as two
+digits.
+
+\b \c{&T} will be replaced by the current time, as six digits
+(HHMMSS) with no punctuation.
+
+\b \c{&H} will be replaced by the host name you are connecting to.
+
+For example, if you enter the host name
+\c{c:\\puttylogs\\log-&h-&y&m&d-&t.dat}, you will end up with files looking
+like
+
+\c log-server1.example.com-20010528-110859.dat
+\c log-unixbox.somewhere.org-20010611-221001.dat
+
+\S{config-logfileexists} \q{What to do if the log file already exists}
+
+\cfg{winhelp-topic}{logging.exists}
+
+This control allows you to specify what PuTTY should do if it tries
+to start writing to a log file and it finds the file already exists.
+You might want to automatically destroy the existing log file and
+start a new one with the same name. Alternatively, you might want to
+open the existing log file and add data to the \e{end} of it.
+Finally (the default option), you might not want to have any
+automatic behaviour, but to ask the user every time the problem
+comes up.
+
+\S{config-logflush} \I{log file, flushing}\q{Flush log file frequently}
+
+\cfg{winhelp-topic}{logging.flush}
+
+This option allows you to control how frequently logged data is
+flushed to disc. By default, PuTTY will flush data as soon as it is
+displayed, so that if you view the log file while a session is still
+open, it will be up to date; and if the client system crashes, there's
+a greater chance that the data will be preserved.
+
+However, this can incur a performance penalty. If PuTTY is running
+slowly with logging enabled, you could try unchecking this option. Be
+warned that the log file may not always be up to date as a result
+(although it will of course be flushed when it is closed, for instance
+at the end of a session).
+
+\S{config-logssh} Options specific to \i{SSH packet log}ging
+
+These options only apply if SSH packet data is being logged.
+
+The following options allow particularly sensitive portions of
+unencrypted packets to be automatically left out of the log file.
+They are only intended to deter casual nosiness; an attacker could
+glean a lot of useful information from even these obfuscated logs
+(e.g., length of password).
+
+\S2{config-logssh-omitpw} \q{Omit known password fields}
+
+\cfg{winhelp-topic}{logging.ssh.omitpassword}
+
+When checked, decrypted password fields are removed from the log of
+transmitted packets. (This includes any user responses to
+challenge-response authentication methods such as
+\q{keyboard-interactive}.) This does not include X11 authentication
+data if using X11 forwarding.
+
+Note that this will only omit data that PuTTY \e{knows} to be a
+password. However, if you start another login session within your
+PuTTY session, for instance, any password used will appear in the
+clear in the packet log. The next option may be of use to protect
+against this.
+
+This option is enabled by default.
+
+\S2{config-logssh-omitdata} \q{Omit session data}
+
+\cfg{winhelp-topic}{logging.ssh.omitdata}
+
+When checked, all decrypted \q{session data} is omitted; this is
+defined as data in terminal sessions and in forwarded channels (TCP,
+X11, and authentication agent). This will usually substantially reduce
+the size of the resulting log file.
+
+This option is disabled by default.
+
+\H{config-terminal} The Terminal panel
+
+The Terminal configuration panel allows you to control the behaviour
+of PuTTY's \i{terminal emulation}.
+
+\S{config-autowrap} \q{Auto wrap mode initially on}
+
+\cfg{winhelp-topic}{terminal.autowrap}
+
+\ii{Auto wrap mode} controls what happens when text printed in a PuTTY
+window reaches the right-hand edge of the window.
+
+With auto wrap mode on, if a long line of text reaches the
+right-hand edge, it will wrap over on to the next line so you can
+still see all the text. With auto wrap mode off, the cursor will
+stay at the right-hand edge of the screen, and all the characters in
+the line will be printed on top of each other.
+
+If you are running a full-screen application and you occasionally
+find the screen scrolling up when it looks as if it shouldn't, you
+could try turning this option off.
+
+Auto wrap mode can be turned on and off by \i{control sequence}s sent by
+the server. This configuration option controls the \e{default}
+state, which will be restored when you reset the terminal (see
+\k{reset-terminal}). However, if you modify this option in
+mid-session using \q{Change Settings}, it will take effect
+immediately.
+
+\S{config-decom} \q{DEC Origin Mode initially on}
+
+\cfg{winhelp-topic}{terminal.decom}
+
+\i{DEC Origin Mode} is a minor option which controls how PuTTY
+interprets cursor-position \i{control sequence}s sent by the server.
+
+The server can send a control sequence that restricts the \i{scrolling
+region} of the display. For example, in an editor, the server might
+reserve a line at the top of the screen and a line at the bottom,
+and might send a control sequence that causes scrolling operations
+to affect only the remaining lines.
+
+With DEC Origin Mode on, \i{cursor coordinates} are counted from the top
+of the scrolling region. With it turned off, cursor coordinates are
+counted from the top of the whole screen regardless of the scrolling
+region.
+
+It is unlikely you would need to change this option, but if you find
+a full-screen application is displaying pieces of text in what looks
+like the wrong part of the screen, you could try turning DEC Origin
+Mode on to see whether that helps.
+
+DEC Origin Mode can be turned on and off by control sequences sent
+by the server. This configuration option controls the \e{default}
+state, which will be restored when you reset the terminal (see
+\k{reset-terminal}). However, if you modify this option in
+mid-session using \q{Change Settings}, it will take effect
+immediately.
+
+\S{config-crlf} \q{Implicit CR in every LF}
+
+\cfg{winhelp-topic}{terminal.lfhascr}
+
+Most servers send two control characters, \i{CR} and \i{LF}, to start a
+\i{new line} of the screen. The CR character makes the cursor return to the
+left-hand side of the screen. The LF character makes the cursor move
+one line down (and might make the screen scroll).
+
+Some servers only send LF, and expect the terminal to move the
+cursor over to the left automatically. If you come across a server
+that does this, you will see a \I{stair-stepping}stepped effect on the
+screen, like this:
+
+\c First line of text
+\c Second line
+\c Third line
+
+If this happens to you, try enabling the \q{Implicit CR in every LF}
+option, and things might go back to normal:
+
+\c First line of text
+\c Second line
+\c Third line
+
+\S{config-lfcr} \q{Implicit LF in every CR}
+
+\cfg{winhelp-topic}{terminal.crhaslf}
+
+Most servers send two control characters, \i{CR} and \i{LF}, to start a
+\i{new line} of the screen. The CR character makes the cursor return to the
+left-hand side of the screen. The LF character makes the cursor move
+one line down (and might make the screen scroll).
+
+Some servers only send CR, and so the newly
+written line is overwritten by the following line. This option causes
+a line feed so that all lines are displayed.
+
+\S{config-erase} \q{Use \i{background colour} to erase screen}
+
+\cfg{winhelp-topic}{terminal.bce}
+
+Not all terminals agree on what colour to turn the screen when the
+server sends a \q{\i{clear screen}} sequence. Some terminals believe the
+screen should always be cleared to the \e{default} background
+colour. Others believe the screen should be cleared to whatever the
+server has selected as a background colour.
+
+There exist applications that expect both kinds of behaviour.
+Therefore, PuTTY can be configured to do either.
+
+With this option disabled, screen clearing is always done in the
+default background colour. With this option enabled, it is done in
+the \e{current} background colour.
+
+Background-colour erase can be turned on and off by \i{control
+sequences} sent by the server. This configuration option controls the
+\e{default} state, which will be restored when you reset the
+terminal (see \k{reset-terminal}). However, if you modify this
+option in mid-session using \q{Change Settings}, it will take effect
+immediately.
+
+\S{config-blink} \q{Enable \i{blinking text}}
+
+\cfg{winhelp-topic}{terminal.blink}
+
+The server can ask PuTTY to display text that blinks on and off.
+This is very distracting, so PuTTY allows you to turn blinking text
+off completely.
+
+When blinking text is disabled and the server attempts to make some
+text blink, PuTTY will instead display the text with a \I{background
+colour, bright}bolded background colour.
+
+Blinking text can be turned on and off by \i{control sequence}s sent by
+the server. This configuration option controls the \e{default}
+state, which will be restored when you reset the terminal (see
+\k{reset-terminal}). However, if you modify this option in
+mid-session using \q{Change Settings}, it will take effect
+immediately.
+
+\S{config-answerback} \q{\ii{Answerback} to ^E}
+
+\cfg{winhelp-topic}{terminal.answerback}
+
+This option controls what PuTTY will send back to the server if the
+server sends it the ^E \i{enquiry character}. Normally it just sends
+the string \q{PuTTY}.
+
+If you accidentally write the contents of a binary file to your
+terminal, you will probably find that it contains more than one ^E
+character, and as a result your next command line will probably read
+\q{PuTTYPuTTYPuTTY...} as if you had typed the answerback string
+multiple times at the keyboard. If you set the answerback string to
+be empty, this problem should go away, but doing so might cause
+other problems.
+
+Note that this is \e{not} the feature of PuTTY which the server will
+typically use to determine your terminal type. That feature is the
+\q{\ii{Terminal-type} string} in the Connection panel; see
+\k{config-termtype} for details.
+
+You can include control characters in the answerback string using
+\c{^C} notation. (Use \c{^~} to get a literal \c{^}.)
+
+\S{config-localecho} \q{\ii{Local echo}}
+
+\cfg{winhelp-topic}{terminal.localecho}
+
+With local echo disabled, characters you type into the PuTTY window
+are not echoed in the window \e{by PuTTY}. They are simply sent to
+the server. (The \e{server} might choose to \I{remote echo}echo them
+back to you; this can't be controlled from the PuTTY control panel.)
+
+Some types of session need local echo, and many do not. In its
+default mode, PuTTY will automatically attempt to deduce whether or
+not local echo is appropriate for the session you are working in. If
+you find it has made the wrong decision, you can use this
+configuration option to override its choice: you can force local
+echo to be turned on, or force it to be turned off, instead of
+relying on the automatic detection.
+
+\S{config-localedit} \q{\ii{Local line editing}}
+
+\cfg{winhelp-topic}{terminal.localedit}
+
+Normally, every character you type into the PuTTY window is sent
+immediately to the server the moment you type it.
+
+If you enable local line editing, this changes. PuTTY will let you
+edit a whole line at a time locally, and the line will only be sent
+to the server when you press Return. If you make a mistake, you can
+use the Backspace key to correct it before you press Return, and the
+server will never see the mistake.
+
+Since it is hard to edit a line locally without being able to see
+it, local line editing is mostly used in conjunction with \i{local echo}
+(\k{config-localecho}). This makes it ideal for use in raw mode
+\#{FIXME} or when connecting to \i{MUD}s or \i{talker}s. (Although some more
+advanced MUDs do occasionally turn local line editing on and turn
+local echo off, in order to accept a password from the user.)
+
+Some types of session need local line editing, and many do not. In
+its default mode, PuTTY will automatically attempt to deduce whether
+or not local line editing is appropriate for the session you are
+working in. If you find it has made the wrong decision, you can use
+this configuration option to override its choice: you can force
+local line editing to be turned on, or force it to be turned off,
+instead of relying on the automatic detection.
+
+\S{config-printing} \ii{Remote-controlled printing}
+
+\cfg{winhelp-topic}{terminal.printing}
+
+A lot of VT100-compatible terminals support printing under control
+of the remote server. PuTTY supports this feature as well, but it is
+turned off by default.
+
+To enable remote-controlled printing, choose a printer from the
+\q{Printer to send ANSI printer output to} drop-down list box. This
+should allow you to select from all the printers you have installed
+drivers for on your computer. Alternatively, you can type the
+network name of a networked printer (for example,
+\c{\\\\printserver\\printer1}) even if you haven't already
+installed a driver for it on your own machine.
+
+When the remote server attempts to print some data, PuTTY will send
+that data to the printer \e{raw} - without translating it,
+attempting to format it, or doing anything else to it. It is up to
+you to ensure your remote server knows what type of printer it is
+talking to.
+
+Since PuTTY sends data to the printer raw, it cannot offer options
+such as portrait versus landscape, print quality, or paper tray
+selection. All these things would be done by your PC printer driver
+(which PuTTY bypasses); if you need them done, you will have to find
+a way to configure your remote server to do them.
+
+To disable remote printing again, choose \q{None (printing
+disabled)} from the printer selection list. This is the default
+state.
+
+\H{config-keyboard} The Keyboard panel
+
+The Keyboard configuration panel allows you to control the behaviour
+of the \i{keyboard} in PuTTY. The correct state for many of these
+settings depends on what the server to which PuTTY is connecting
+expects. With a \i{Unix} server, this is likely to depend on the
+\i\c{termcap} or \i\c{terminfo} entry it uses, which in turn is likely to
+be controlled by the \q{\ii{Terminal-type} string} setting in the Connection
+panel; see \k{config-termtype} for details. If none of the settings here
+seems to help, you may find \k{faq-keyboard} to be useful.
+
+\S{config-backspace} Changing the action of the \ii{Backspace key}
+
+\cfg{winhelp-topic}{keyboard.backspace}
+
+Some terminals believe that the Backspace key should send the same
+thing to the server as \i{Control-H} (ASCII code 8). Other terminals
+believe that the Backspace key should send ASCII code 127 (usually
+known as \i{Control-?}) so that it can be distinguished from Control-H.
+This option allows you to choose which code PuTTY generates when you
+press Backspace.
+
+If you are connecting over SSH, PuTTY by default tells the server
+the value of this option (see \k{config-ttymodes}), so you may find
+that the Backspace key does the right thing either way. Similarly,
+if you are connecting to a \i{Unix} system, you will probably find that
+the Unix \i\c{stty} command lets you configure which the server
+expects to see, so again you might not need to change which one PuTTY
+generates. On other systems, the server's expectation might be fixed
+and you might have no choice but to configure PuTTY.
+
+If you do have the choice, we recommend configuring PuTTY to
+generate Control-? and configuring the server to expect it, because
+that allows applications such as \c{emacs} to use Control-H for
+help.
+
+(Typing \i{Shift-Backspace} will cause PuTTY to send whichever code
+isn't configured here as the default.)
+
+\S{config-homeend} Changing the action of the \i{Home and End keys}
+
+\cfg{winhelp-topic}{keyboard.homeend}
+
+The Unix terminal emulator \i\c{rxvt} disagrees with the rest of the
+world about what character sequences should be sent to the server by
+the Home and End keys.
+
+\i\c{xterm}, and other terminals, send \c{ESC [1~} for the Home key,
+and \c{ESC [4~} for the End key. \c{rxvt} sends \c{ESC [H} for the
+Home key and \c{ESC [Ow} for the End key.
+
+If you find an application on which the Home and End keys aren't
+working, you could try switching this option to see if it helps.
+
+\S{config-funkeys} Changing the action of the \i{function keys} and
+\i{keypad}
+
+\cfg{winhelp-topic}{keyboard.funkeys}
+
+This option affects the function keys (F1 to F12) and the top row of
+the numeric keypad.
+
+\b In the default mode, labelled \c{ESC [n~}, the function keys
+generate sequences like \c{ESC [11~}, \c{ESC [12~} and so on. This
+matches the general behaviour of Digital's terminals.
+
+\b In Linux mode, F6 to F12 behave just like the default mode, but
+F1 to F5 generate \c{ESC [[A} through to \c{ESC [[E}. This mimics the
+\i{Linux virtual console}.
+
+\b In \I{xterm}Xterm R6 mode, F5 to F12 behave like the default mode, but F1
+to F4 generate \c{ESC OP} through to \c{ESC OS}, which are the
+sequences produced by the top row of the \e{keypad} on Digital's
+terminals.
+
+\b In \i{VT400} mode, all the function keys behave like the default
+mode, but the actual top row of the numeric keypad generates \c{ESC
+OP} through to \c{ESC OS}.
+
+\b In \i{VT100+} mode, the function keys generate \c{ESC OP} through to
+\c{ESC O[}
+
+\b In \i{SCO} mode, the function keys F1 to F12 generate \c{ESC [M}
+through to \c{ESC [X}. Together with shift, they generate \c{ESC [Y}
+through to \c{ESC [j}. With control they generate \c{ESC [k} through
+to \c{ESC [v}, and with shift and control together they generate
+\c{ESC [w} through to \c{ESC [\{}.
+
+If you don't know what any of this means, you probably don't need to
+fiddle with it.
+
+\S{config-appcursor} Controlling \i{Application Cursor Keys} mode
+
+\cfg{winhelp-topic}{keyboard.appcursor}
+
+Application Cursor Keys mode is a way for the server to change the
+control sequences sent by the arrow keys. In normal mode, the arrow
+keys send \c{ESC [A} through to \c{ESC [D}. In application mode,
+they send \c{ESC OA} through to \c{ESC OD}.
+
+Application Cursor Keys mode can be turned on and off by the server,
+depending on the application. PuTTY allows you to configure the
+initial state.
+
+You can also disable application cursor keys mode completely, using
+the \q{Features} configuration panel; see
+\k{config-features-application}.
+
+\S{config-appkeypad} Controlling \i{Application Keypad} mode
+
+\cfg{winhelp-topic}{keyboard.appkeypad}
+
+Application Keypad mode is a way for the server to change the
+behaviour of the numeric keypad.
+
+In normal mode, the keypad behaves like a normal Windows keypad:
+with \i{NumLock} on, the number keys generate numbers, and with NumLock
+off they act like the arrow keys and Home, End etc.
+
+In application mode, all the keypad keys send special control
+sequences, \e{including} Num Lock. Num Lock stops behaving like Num
+Lock and becomes another function key.
+
+Depending on which version of Windows you run, you may find the Num
+Lock light still flashes on and off every time you press Num Lock,
+even when application mode is active and Num Lock is acting like a
+function key. This is unavoidable.
+
+Application keypad mode can be turned on and off by the server,
+depending on the application. PuTTY allows you to configure the
+initial state.
+
+You can also disable application keypad mode completely, using the
+\q{Features} configuration panel; see
+\k{config-features-application}.
+
+\S{config-nethack} Using \i{NetHack keypad mode}
+
+\cfg{winhelp-topic}{keyboard.nethack}
+
+PuTTY has a special mode for playing NetHack. You can enable it by
+selecting \q{NetHack} in the \q{Initial state of numeric keypad}
+control.
+
+In this mode, the numeric keypad keys 1-9 generate the NetHack
+movement commands (\cw{hjklyubn}). The 5 key generates the \c{.}
+command (do nothing).
+
+In addition, pressing Shift or Ctrl with the keypad keys generate
+the Shift- or Ctrl-keys you would expect (e.g. keypad-7 generates
+\cq{y}, so Shift-keypad-7 generates \cq{Y} and Ctrl-keypad-7
+generates Ctrl-Y); these commands tell NetHack to keep moving you in
+the same direction until you encounter something interesting.
+
+For some reason, this feature only works properly when \i{Num Lock} is
+on. We don't know why.
+
+\S{config-compose} Enabling a DEC-like \ii{Compose key}
+
+\cfg{winhelp-topic}{keyboard.compose}
+
+DEC terminals have a Compose key, which provides an easy-to-remember
+way of typing \i{accented characters}. You press Compose and then type
+two more characters. The two characters are \q{combined} to produce
+an accented character. The choices of character are designed to be
+easy to remember; for example, composing \q{e} and \q{`} produces
+the \q{\u00e8{e-grave}} character.
+
+If your keyboard has a Windows \i{Application key}, it acts as a Compose
+key in PuTTY. Alternatively, if you enable the \q{\i{AltGr} acts as
+Compose key} option, the AltGr key will become a Compose key.
+
+\S{config-ctrlalt} \q{Control-Alt is different from \i{AltGr}}
+
+\cfg{winhelp-topic}{keyboard.ctrlalt}
+
+Some old keyboards do not have an AltGr key, which can make it
+difficult to type some characters. PuTTY can be configured to treat
+the key combination Ctrl + Left Alt the same way as the AltGr key.
+
+By default, this checkbox is checked, and the key combination Ctrl +
+Left Alt does something completely different. PuTTY's usual handling
+of the left Alt key is to prefix the Escape (Control-\cw{[})
+character to whatever character sequence the rest of the keypress
+would generate. For example, Alt-A generates Escape followed by
+\c{a}. So Alt-Ctrl-A would generate Escape, followed by Control-A.
+
+If you uncheck this box, Ctrl-Alt will become a synonym for AltGr,
+so you can use it to type extra graphic characters if your keyboard
+has any.
+
+(However, Ctrl-Alt will never act as a Compose key, regardless of the
+setting of \q{AltGr acts as Compose key} described in
+\k{config-compose}.)
+
+\H{config-bell} The Bell panel
+
+The Bell panel controls the \i{terminal bell} feature: the server's
+ability to cause PuTTY to beep at you.
+
+In the default configuration, when the server sends the character
+with ASCII code 7 (Control-G), PuTTY will play the \i{Windows Default
+Beep} sound. This is not always what you want the terminal bell
+feature to do; the Bell panel allows you to configure alternative
+actions.
+
+\S{config-bellstyle} \q{Set the style of bell}
+
+\cfg{winhelp-topic}{bell.style}
+
+This control allows you to select various different actions to occur
+on a terminal bell:
+
+\b Selecting \q{None} \I{terminal bell, disabling}disables the bell
+completely. In this mode, the server can send as many Control-G
+characters as it likes and nothing at all will happen.
+
+\b \q{Make default system alert sound} is the default setting. It
+causes the Windows \q{Default Beep} sound to be played. To change
+what this sound is, or to test it if nothing seems to be happening,
+use the Sound configurer in the Windows Control Panel.
+
+\b \q{\ii{Visual bell}} is a silent alternative to a beeping computer. In
+this mode, when the server sends a Control-G, the whole PuTTY window
+will flash white for a fraction of a second.
+
+\b \q{Beep using the \i{PC speaker}} is self-explanatory.
+
+\b \q{Play a custom \i{sound file}} allows you to specify a particular
+sound file to be used by PuTTY alone, or even by a particular
+individual PuTTY session. This allows you to distinguish your PuTTY
+beeps from any other beeps on the system. If you select this option,
+you will also need to enter the name of your sound file in the edit
+control \q{Custom sound file to play as a bell}.
+
+\S{config-belltaskbar} \q{\ii{Taskbar}/\I{window caption}caption
+indication on bell}
+
+\cfg{winhelp-topic}{bell.taskbar}
+
+This feature controls what happens to the PuTTY window's entry in
+the Windows Taskbar if a bell occurs while the window does not have
+the input focus.
+
+In the default state (\q{Disabled}) nothing unusual happens.
+
+If you select \q{Steady}, then when a bell occurs and the window is
+not in focus, the window's Taskbar entry and its title bar will
+change colour to let you know that PuTTY session is asking for your
+attention. The change of colour will persist until you select the
+window, so you can leave several PuTTY windows minimised in your
+terminal, go away from your keyboard, and be sure not to have missed
+any important beeps when you get back.
+
+\q{Flashing} is even more eye-catching: the Taskbar entry will
+continuously flash on and off until you select the window.
+
+\S{config-bellovl} \q{Control the \i{bell overload} behaviour}
+
+\cfg{winhelp-topic}{bell.overload}
+
+A common user error in a terminal session is to accidentally run the
+Unix command \c{cat} (or equivalent) on an inappropriate file type,
+such as an executable, image file, or ZIP file. This produces a huge
+stream of non-text characters sent to the terminal, which typically
+includes a lot of bell characters. As a result of this the terminal
+often doesn't stop beeping for ten minutes, and everybody else in
+the office gets annoyed.
+
+To try to avoid this behaviour, or any other cause of excessive
+beeping, PuTTY includes a bell overload management feature. In the
+default configuration, receiving more than five bell characters in a
+two-second period will cause the overload feature to activate. Once
+the overload feature is active, further bells will \I{terminal bell,
+disabling} have no effect at all, so the rest of your binary file
+will be sent to the screen in silence. After a period of five seconds
+during which no further bells are received, the overload feature will
+turn itself off again and bells will be re-enabled.
+
+If you want this feature completely disabled, you can turn it off
+using the checkbox \q{Bell is temporarily disabled when over-used}.
+
+Alternatively, if you like the bell overload feature but don't agree
+with the settings, you can configure the details: how many bells
+constitute an overload, how short a time period they have to arrive
+in to do so, and how much silent time is required before the
+overload feature will deactivate itself.
+
+Bell overload mode is always deactivated by any keypress in the
+terminal. This means it can respond to large unexpected streams of
+data, but does not interfere with ordinary command-line activities
+that generate beeps (such as filename completion).
+
+\H{config-features} The Features panel
+
+PuTTY's \i{terminal emulation} is very highly featured, and can do a lot
+of things under remote server control. Some of these features can
+cause problems due to buggy or strangely configured server
+applications.
+
+The Features configuration panel allows you to disable some of
+PuTTY's more advanced terminal features, in case they cause trouble.
+
+\S{config-features-application} Disabling application keypad and cursor keys
+
+\cfg{winhelp-topic}{features.application}
+
+\I{Application Keypad}Application keypad mode (see
+\k{config-appkeypad}) and \I{Application Cursor Keys}application
+cursor keys mode (see \k{config-appcursor}) alter the behaviour of
+the keypad and cursor keys. Some applications enable these modes but
+then do not deal correctly with the modified keys. You can force
+these modes to be permanently disabled no matter what the server
+tries to do.
+
+\S{config-features-mouse} Disabling \cw{xterm}-style \i{mouse reporting}
+
+\cfg{winhelp-topic}{features.mouse}
+
+PuTTY allows the server to send \i{control codes} that let it take over
+the mouse and use it for purposes other than \i{copy and paste}.
+Applications which use this feature include the text-mode web
+browser \i\c{links}, the Usenet newsreader \i\c{trn} version 4, and the
+file manager \i\c{mc} (Midnight Commander).
+
+If you find this feature inconvenient, you can disable it using the
+\q{Disable xterm-style mouse reporting} control. With this box
+ticked, the mouse will \e{always} do copy and paste in the normal
+way.
+
+Note that even if the application takes over the mouse, you can
+still manage PuTTY's copy and paste by holding down the Shift key
+while you select and paste, unless you have deliberately turned this
+feature off (see \k{config-mouseshift}).
+
+\S{config-features-resize} Disabling remote \i{terminal resizing}
+
+\cfg{winhelp-topic}{features.resize}
+
+PuTTY has the ability to change the terminal's size and position in
+response to commands from the server. If you find PuTTY is doing
+this unexpectedly or inconveniently, you can tell PuTTY not to
+respond to those server commands.
+
+\S{config-features-altscreen} Disabling switching to the \i{alternate screen}
+
+\cfg{winhelp-topic}{features.altscreen}
+
+Many terminals, including PuTTY, support an \q{alternate screen}.
+This is the same size as the ordinary terminal screen, but separate.
+Typically a screen-based program such as a text editor might switch
+the terminal to the alternate screen before starting up. Then at the
+end of the run, it switches back to the primary screen, and you see
+the screen contents just as they were before starting the editor.
+
+Some people prefer this not to happen. If you want your editor to
+run in the same screen as the rest of your terminal activity, you
+can disable the alternate screen feature completely.
+
+\S{config-features-retitle} Disabling remote \i{window title} changing
+
+\cfg{winhelp-topic}{features.retitle}
+
+PuTTY has the ability to change the window title in response to
+commands from the server. If you find PuTTY is doing this
+unexpectedly or inconveniently, you can tell PuTTY not to respond to
+those server commands.
+
+\S{config-features-qtitle} Response to remote \i{window title} querying
+
+\cfg{winhelp-topic}{features.qtitle}
+
+PuTTY can optionally provide the xterm service of allowing server
+applications to find out the local window title. This feature is
+disabled by default, but you can turn it on if you really want it.
+
+NOTE that this feature is a \e{potential \i{security hazard}}. If a
+malicious application can write data to your terminal (for example,
+if you merely \c{cat} a file owned by someone else on the server
+machine), it can change your window title (unless you have disabled
+this as mentioned in \k{config-features-retitle}) and then use this
+service to have the new window title sent back to the server as if
+typed at the keyboard. This allows an attacker to fake keypresses
+and potentially cause your server-side applications to do things you
+didn't want. Therefore this feature is disabled by default, and we
+recommend you do not set it to \q{Window title} unless you \e{really}
+know what you are doing.
+
+There are three settings for this option:
+
+\dt \q{None}
+
+\dd PuTTY makes no response whatsoever to the relevant escape
+sequence. This may upset server-side software that is expecting some
+sort of response.
+
+\dt \q{Empty string}
+
+\dd PuTTY makes a well-formed response, but leaves it blank. Thus,
+server-side software that expects a response is kept happy, but an
+attacker cannot influence the response string. This is probably the
+setting you want if you have no better ideas.
+
+\dt \q{Window title}
+
+\dd PuTTY responds with the actual window title. This is dangerous for
+the reasons described above.
+
+\S{config-features-dbackspace} Disabling \i{destructive backspace}
+
+\cfg{winhelp-topic}{features.dbackspace}
+
+Normally, when PuTTY receives character 127 (^?) from the server, it
+will perform a \q{destructive backspace}: move the cursor one space
+left and delete the character under it. This can apparently cause
+problems in some applications, so PuTTY provides the ability to
+configure character 127 to perform a normal backspace (without
+deleting a character) instead.
+
+\S{config-features-charset} Disabling remote \i{character set}
+configuration
+
+\cfg{winhelp-topic}{features.charset}
+
+PuTTY has the ability to change its character set configuration in
+response to commands from the server. Some programs send these
+commands unexpectedly or inconveniently. In particular, \i{BitchX} (an
+IRC client) seems to have a habit of reconfiguring the character set
+to something other than the user intended.
+
+If you find that accented characters are not showing up the way you
+expect them to, particularly if you're running BitchX, you could try
+disabling the remote character set configuration commands.
+
+\S{config-features-shaping} Disabling \i{Arabic text shaping}
+
+\cfg{winhelp-topic}{features.arabicshaping}
+
+PuTTY supports shaping of Arabic text, which means that if your
+server sends text written in the basic \i{Unicode} Arabic alphabet then
+it will convert it to the correct display forms before printing it
+on the screen.
+
+If you are using full-screen software which was not expecting this
+to happen (especially if you are not an Arabic speaker and you
+unexpectedly find yourself dealing with Arabic text files in
+applications which are not Arabic-aware), you might find that the
+\i{display becomes corrupted}. By ticking this box, you can disable
+Arabic text shaping so that PuTTY displays precisely the characters
+it is told to display.
+
+You may also find you need to disable bidirectional text display;
+see \k{config-features-bidi}.
+
+\S{config-features-bidi} Disabling \i{bidirectional text} display
+
+\cfg{winhelp-topic}{features.bidi}
+
+PuTTY supports bidirectional text display, which means that if your
+server sends text written in a language which is usually displayed
+from right to left (such as \i{Arabic} or \i{Hebrew}) then PuTTY will
+automatically flip it round so that it is displayed in the right
+direction on the screen.
+
+If you are using full-screen software which was not expecting this
+to happen (especially if you are not an Arabic speaker and you
+unexpectedly find yourself dealing with Arabic text files in
+applications which are not Arabic-aware), you might find that the
+\i{display becomes corrupted}. By ticking this box, you can disable
+bidirectional text display, so that PuTTY displays text from left to
+right in all situations.
+
+You may also find you need to disable Arabic text shaping;
+see \k{config-features-shaping}.
+
+\H{config-window} The Window panel
+
+The Window configuration panel allows you to control aspects of the
+\i{PuTTY window}.
+
+\S{config-winsize} Setting the \I{window size}size of the PuTTY window
+
+\cfg{winhelp-topic}{window.size}
+
+The \q{\ii{Columns}} and \q{\ii{Rows}} boxes let you set the PuTTY
+window to a precise size. Of course you can also \I{window resizing}drag
+the window to a new size while a session is running.
+
+\S{config-winsizelock} What to do when the window is resized
+
+\cfg{winhelp-topic}{window.resize}
+
+These options allow you to control what happens when the user tries
+to \I{window resizing}resize the PuTTY window using its window furniture.
+
+There are four options here:
+
+\b \q{Change the number of rows and columns}: the font size will not
+change. (This is the default.)
+
+\b \q{Change the size of the font}: the number of rows and columns in
+the terminal will stay the same, and the \i{font size} will change.
+
+\b \q{Change font size when maximised}: when the window is resized,
+the number of rows and columns will change, \e{except} when the window
+is \i{maximise}d (or restored), when the font size will change. (In
+this mode, holding down the Alt key while resizing will also cause the
+font size to change.)
+
+\b \q{Forbid resizing completely}: the terminal will refuse to be
+resized at all.
+
+\S{config-scrollback} Controlling \i{scrollback}
+
+\cfg{winhelp-topic}{window.scrollback}
+
+These options let you configure the way PuTTY keeps text after it
+scrolls off the top of the screen (see \k{using-scrollback}).
+
+The \q{Lines of scrollback} box lets you configure how many lines of
+text PuTTY keeps. The \q{Display scrollbar} options allow you to
+hide the \i{scrollbar} (although you can still view the scrollback using
+the keyboard as described in \k{using-scrollback}). You can separately
+configure whether the scrollbar is shown in \i{full-screen} mode and in
+normal modes.
+
+If you are viewing part of the scrollback when the server sends more
+text to PuTTY, the screen will revert to showing the current
+terminal contents. You can disable this behaviour by turning off
+\q{Reset scrollback on display activity}. You can also make the
+screen revert when you press a key, by turning on \q{Reset
+scrollback on keypress}.
+
+\S{config-erasetoscrollback} \q{Push erased text into scrollback}
+
+\cfg{winhelp-topic}{window.erased}
+
+When this option is enabled, the contents of the terminal screen
+will be pushed into the scrollback when a server-side application
+clears the screen, so that your scrollback will contain a better
+record of what was on your screen in the past.
+
+If the application switches to the \i{alternate screen} (see
+\k{config-features-altscreen} for more about this), then the
+contents of the primary screen will be visible in the scrollback
+until the application switches back again.
+
+This option is enabled by default.
+
+\H{config-appearance} The Appearance panel
+
+The Appearance configuration panel allows you to control aspects of
+the appearance of \I{PuTTY window}PuTTY's window.
+
+\S{config-cursor} Controlling the appearance of the \i{cursor}
+
+\cfg{winhelp-topic}{appearance.cursor}
+
+The \q{Cursor appearance} option lets you configure the cursor to be
+a block, an underline, or a vertical line. A block cursor becomes an
+empty box when the window loses focus; an underline or a vertical
+line becomes dotted.
+
+The \q{\ii{Cursor blinks}} option makes the cursor blink on and off. This
+works in any of the cursor modes.
+
+\S{config-font} Controlling the \i{font} used in the terminal window
+
+\cfg{winhelp-topic}{appearance.font}
+
+This option allows you to choose what font, in what \I{font size}size,
+the PuTTY terminal window uses to display the text in the session.
+
+By default, you will be offered a choice from all the fixed-width
+fonts installed on the system, since VT100-style terminal handling
+expects a fixed-width font. If you tick the box marked \q{Allow
+selection of variable-pitch fonts}, however, PuTTY will offer
+variable-width fonts as well: if you select one of these, the font
+will be coerced into fixed-size character cells, which will probably
+not look very good (but can work OK with some fonts).
+
+\S{config-mouseptr} \q{Hide \i{mouse pointer} when typing in window}
+
+\cfg{winhelp-topic}{appearance.hidemouse}
+
+If you enable this option, the mouse pointer will disappear if the
+PuTTY window is selected and you press a key. This way, it will not
+obscure any of the text in the window while you work in your
+session. As soon as you move the mouse, the pointer will reappear.
+
+This option is disabled by default, so the mouse pointer remains
+visible at all times.
+
+\S{config-winborder} Controlling the \i{window border}
+
+\cfg{winhelp-topic}{appearance.border}
+
+PuTTY allows you to configure the appearance of the window border to
+some extent.
+
+The checkbox marked \q{Sunken-edge border} changes the appearance of
+the window border to something more like a DOS box: the inside edge
+of the border is highlighted as if it sank down to meet the surface
+inside the window. This makes the border a little bit thicker as
+well. It's hard to describe well. Try it and see if you like it.
+
+You can also configure a completely blank gap between the text in
+the window and the border, using the \q{Gap between text and window
+edge} control. By default this is set at one pixel. You can reduce
+it to zero, or increase it further.
+
+\H{config-behaviour} The Behaviour panel
+
+The Behaviour configuration panel allows you to control aspects of
+the behaviour of \I{PuTTY window}PuTTY's window.
+
+\S{config-title} Controlling the \i{window title}
+
+\cfg{winhelp-topic}{appearance.title}
+
+The \q{Window title} edit box allows you to set the title of the
+PuTTY window. By default the window title will contain the \i{host name}
+followed by \q{PuTTY}, for example \c{server1.example.com - PuTTY}.
+If you want a different window title, this is where to set it.
+
+PuTTY allows the server to send \c{xterm} \i{control sequence}s which
+modify the title of the window in mid-session (unless this is disabled -
+see \k{config-features-retitle}); the title string set here
+is therefore only the \e{initial} window title.
+
+As well as the \e{window} title, there is also an \c{xterm}
+sequence to modify the \I{icon title}title of the window's \e{icon}.
+This makes sense in a windowing system where the window becomes an
+icon when minimised, such as Windows 3.1 or most X Window System
+setups; but in the Windows 95-like user interface it isn't as
+applicable.
+
+By default, PuTTY only uses the server-supplied \e{window} title, and
+ignores the icon title entirely. If for some reason you want to see
+both titles, check the box marked \q{Separate window and icon titles}.
+If you do this, PuTTY's window title and Taskbar \I{window caption}caption will
+change into the server-supplied icon title if you \i{minimise} the PuTTY
+window, and change back to the server-supplied window title if you
+restore it. (If the server has not bothered to supply a window or
+icon title, none of this will happen.)
+
+\S{config-warnonclose} \q{Warn before \i{closing window}}
+
+\cfg{winhelp-topic}{behaviour.closewarn}
+
+If you press the \i{Close button} in a PuTTY window that contains a
+running session, PuTTY will put up a warning window asking if you
+really meant to close the window. A window whose session has already
+terminated can always be closed without a warning.
+
+If you want to be able to close a window quickly, you can disable
+the \q{Warn before closing window} option.
+
+\S{config-altf4} \q{Window closes on \i{ALT-F4}}
+
+\cfg{winhelp-topic}{behaviour.altf4}
+
+By default, pressing ALT-F4 causes the \I{closing window}window to
+close (or a warning box to appear; see \k{config-warnonclose}). If you
+disable the \q{Window closes on ALT-F4} option, then pressing ALT-F4
+will simply send a key sequence to the server.
+
+\S{config-altspace} \q{\ii{System menu} appears on \i{ALT-Space}}
+
+\cfg{winhelp-topic}{behaviour.altspace}
+
+If this option is enabled, then pressing ALT-Space will bring up the
+PuTTY window's menu, like clicking on the top left corner. If it is
+disabled, then pressing ALT-Space will just send \c{ESC SPACE} to
+the server.
+
+Some \i{accessibility} programs for Windows may need this option
+enabling to be able to control PuTTY's window successfully. For
+instance, \i{Dragon NaturallySpeaking} requires it both to open the
+system menu via voice, and to close, minimise, maximise and restore
+the window.
+
+\S{config-altonly} \q{\ii{System menu} appears on \i{Alt} alone}
+
+\cfg{winhelp-topic}{behaviour.altonly}
+
+If this option is enabled, then pressing and releasing ALT will
+bring up the PuTTY window's menu, like clicking on the top left
+corner. If it is disabled, then pressing and releasing ALT will have
+no effect.
+
+\S{config-alwaysontop} \q{Ensure window is \i{always on top}}
+
+\cfg{winhelp-topic}{behaviour.alwaysontop}
+
+If this option is enabled, the PuTTY window will stay on top of all
+other windows.
+
+\S{config-fullscreen} \q{\ii{Full screen} on Alt-Enter}
+
+\cfg{winhelp-topic}{behaviour.altenter}
+
+If this option is enabled, then pressing Alt-Enter will cause the
+PuTTY window to become full-screen. Pressing Alt-Enter again will
+restore the previous window size.
+
+The full-screen feature is also available from the \ii{System menu}, even
+when it is configured not to be available on the Alt-Enter key. See
+\k{using-fullscreen}.
+
+\H{config-translation} The Translation panel
+
+The Translation configuration panel allows you to control the
+translation between the \i{character set} understood by the server and
+the character set understood by PuTTY.
+
+\S{config-charset} Controlling character set translation
+
+\cfg{winhelp-topic}{translation.codepage}
+
+During an interactive session, PuTTY receives a stream of 8-bit
+bytes from the server, and in order to display them on the screen it
+needs to know what character set to interpret them in. Similarly,
+PuTTY needs to know how to translate your keystrokes into the encoding
+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.
+
+A few 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.
+
+\b The \i{Win125x} series are defined by Microsoft, for similar
+purposes. In particular Win1252 is almost equivalent to ISO-8859-1,
+but contains a few extra characters such as matched quotes and the
+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.
+
+\S{config-cjk-ambig-wide} \q{Treat \i{CJK} ambiguous characters as wide}
+
+\cfg{winhelp-topic}{translation.cjkambigwide}
+
+There are \I{East Asian Ambiguous characters}some Unicode characters
+whose \I{character width}width is not well-defined. In most contexts, such
+characters should be treated as single-width for the purposes of \I{wrapping,
+terminal}wrapping and so on; however, in some CJK contexts, they are better
+treated as double-width for historical reasons, and some server-side
+applications may expect them to be displayed as such. Setting this option
+will cause PuTTY to take the double-width interpretation.
+
+If you use legacy CJK applications, and you find your lines are
+wrapping in the wrong places, or you are having other display
+problems, you might want to play with this setting.
+
+This option only has any effect in \i{UTF-8} mode (see \k{config-charset}).
+
+\S{config-cyr} \q{\i{Caps Lock} acts as \i{Cyrillic} switch}
+
+\cfg{winhelp-topic}{translation.cyrillic}
+
+This feature allows you to switch between a US/UK keyboard layout
+and a Cyrillic keyboard layout by using the Caps Lock key, if you
+need to type (for example) \i{Russian} and English side by side in the
+same document.
+
+Currently this feature is not expected to work properly if your
+native keyboard layout is not US or UK.
+
+\S{config-linedraw} Controlling display of \i{line-drawing characters}
+
+\cfg{winhelp-topic}{translation.linedraw}
+
+VT100-series terminals allow the server to send \i{control sequence}s that
+shift temporarily into a separate character set for drawing simple
+lines and boxes. However, there are a variety of ways in which PuTTY
+can attempt to find appropriate characters, and the right one to use
+depends on the locally configured \i{font}. In general you should probably
+try lots of options until you find one that your particular font
+supports.
+
+\b \q{Use Unicode line drawing code points} tries to use the box
+characters that are present in \i{Unicode}. For good Unicode-supporting
+fonts this is probably the most reliable and functional option.
+
+\b \q{Poor man's line drawing} assumes that the font \e{cannot}
+generate the line and box characters at all, so it will use the
+\c{+}, \c{-} and \c{|} characters to draw approximations to boxes.
+You should use this option if none of the other options works.
+
+\b \q{Font has XWindows encoding} is for use with fonts that have a
+special encoding, where the lowest 32 character positions (below the
+ASCII printable range) contain the line-drawing characters. This is
+unlikely to be the case with any standard Windows font; it will
+probably only apply to custom-built fonts or fonts that have been
+automatically converted from the X Window System.
+
+\b \q{Use font in both ANSI and OEM modes} tries to use the same
+font in two different character sets, to obtain a wider range of
+characters. This doesn't always work; some fonts claim to be a
+different size depending on which character set you try to use.
+
+\b \q{Use font in OEM mode only} is more reliable than that, but can
+miss out other characters from the main character set.
+
+\S{config-linedrawpaste} Controlling \i{copy and paste} of line drawing
+characters
+
+\cfg{winhelp-topic}{selection.linedraw}
+
+By default, when you copy and paste a piece of the PuTTY screen that
+contains VT100 line and box drawing characters, PuTTY will paste
+them in the form they appear on the screen: either \i{Unicode} line
+drawing code points, or the \q{poor man's} line-drawing characters
+\c{+}, \c{-} and \c{|}. The checkbox \q{Copy and paste VT100 line
+drawing chars as lqqqk} disables this feature, so line-drawing
+characters will be pasted as the \i{ASCII} characters that were printed
+to produce them. This will typically mean they come out mostly as
+\c{q} and \c{x}, with a scattering of \c{jklmntuvw} at the corners.
+This might be useful if you were trying to recreate the same box
+layout in another program, for example.
+
+Note that this option only applies to line-drawing characters which
+\e{were} printed by using the VT100 mechanism. Line-drawing
+characters that were received as Unicode code points will paste as
+Unicode always.
+
+\H{config-selection} The Selection panel
+
+The Selection panel allows you to control the way \i{copy and paste}
+work in the PuTTY window.
+
+\S{config-rtfpaste} Pasting in \i{Rich Text Format}
+
+\cfg{winhelp-topic}{selection.rtf}
+
+If you enable \q{Paste to clipboard in RTF as well as plain text},
+PuTTY will write formatting information to the clipboard as well as
+the actual text you copy. The effect of this is
+that if you paste into (say) a word processor, the text will appear
+in the word processor in the same \i{font}, \i{colour}, and style
+(e.g. bold, underline) PuTTY was using to display it.
+
+This option can easily be inconvenient, so by default it is
+disabled.
+
+\S{config-mouse} Changing the actions of the mouse buttons
+
+\cfg{winhelp-topic}{selection.buttons}
+
+PuTTY's copy and paste mechanism is by default modelled on the Unix
+\c{xterm} application. The X Window System uses a three-button mouse,
+and the convention is that the \i{left button} \I{selecting text}selects,
+the \i{right button} extends an existing selection, and the
+\i{middle button} pastes.
+
+Windows often only has two mouse buttons, so in PuTTY's default
+configuration (\q{Compromise}), the \e{right} button pastes, and the
+\e{middle} button (if you have one) \I{adjusting a selection}extends
+a selection.
+
+If you have a \i{three-button mouse} and you are already used to the
+\c{xterm} arrangement, you can select it using the \q{Action of
+mouse buttons} control.
+
+Alternatively, with the \q{Windows} option selected, the middle
+button extends, and the right button brings up a \i{context menu} (on
+which one of the options is \q{Paste}). (This context menu is always
+available by holding down Ctrl and right-clicking, regardless of the
+setting of this option.)
+
+\S{config-mouseshift} \q{Shift overrides application's use of mouse}
+
+\cfg{winhelp-topic}{selection.shiftdrag}
+
+PuTTY allows the server to send \i{control codes} that let it
+\I{mouse reporting}take over the mouse and use it for purposes other
+than \i{copy and paste}.
+Applications which use this feature include the text-mode web
+browser \c{links}, the Usenet newsreader \c{trn} version 4, and the
+file manager \c{mc} (Midnight Commander).
+
+When running one of these applications, pressing the mouse buttons
+no longer performs copy and paste. If you do need to copy and paste,
+you can still do so if you hold down Shift while you do your mouse
+clicks.
+
+However, it is possible in theory for applications to even detect
+and make use of Shift + mouse clicks. We don't know of any
+applications that do this, but in case someone ever writes one,
+unchecking the \q{Shift overrides application's use of mouse}
+checkbox will cause Shift + mouse clicks to go to the server as well
+(so that mouse-driven copy and paste will be completely disabled).
+
+If you want to prevent the application from taking over the mouse at
+all, you can do this using the Features control panel; see
+\k{config-features-mouse}.
+
+\S{config-rectselect} Default selection mode
+
+\cfg{winhelp-topic}{selection.rect}
+
+As described in \k{using-selection}, PuTTY has two modes of
+selecting text to be copied to the clipboard. In the default mode
+(\q{Normal}), dragging the mouse from point A to point B selects to
+the end of the line containing A, all the lines in between, and from
+the very beginning of the line containing B. In the other mode
+(\q{Rectangular block}), dragging the mouse between two points
+defines a rectangle, and everything within that rectangle is copied.
+
+Normally, you have to hold down Alt while dragging the mouse to
+select a rectangular block. Using the \q{Default selection mode}
+control, you can set \i{rectangular selection} as the default, and then
+you have to hold down Alt to get the \e{normal} behaviour.
+
+\S{config-charclasses} Configuring \i{word-by-word selection}
+
+\cfg{winhelp-topic}{selection.charclasses}
+
+PuTTY will select a word at a time in the terminal window if you
+\i{double-click} to begin the drag. This panel allows you to control
+precisely what is considered to be a word.
+
+Each character is given a \e{class}, which is a small number
+(typically 0, 1 or 2). PuTTY considers a single word to be any
+number of adjacent characters in the same class. So by modifying the
+assignment of characters to classes, you can modify the word-by-word
+selection behaviour.
+
+In the default configuration, the \i{character classes} are:
+
+\b Class 0 contains \i{white space} and control characters.
+
+\b Class 1 contains most \i{punctuation}.
+
+\b Class 2 contains letters, numbers and a few pieces of punctuation
+(the double quote, minus sign, period, forward slash and
+underscore).
+
+So, for example, if you assign the \c{@} symbol into character class
+2, you will be able to select an e-mail address with just a double
+click.
+
+In order to adjust these assignments, you start by selecting a group
+of characters in the list box. Then enter a class number in the edit
+box below, and press the \q{Set} button.
+
+This mechanism currently only covers ASCII characters, because it
+isn't feasible to expand the list to cover the whole of Unicode.
+
+Character class definitions can be modified by \i{control sequence}s
+sent by the server. This configuration option controls the
+\e{default} state, which will be restored when you reset the
+terminal (see \k{reset-terminal}). However, if you modify this
+option in mid-session using \q{Change Settings}, it will take effect
+immediately.
+
+\H{config-colours} The Colours panel
+
+The Colours panel allows you to control PuTTY's use of \i{colour}.
+
+\S{config-ansicolour} \q{Allow terminal to specify \i{ANSI colours}}
+
+\cfg{winhelp-topic}{colours.ansi}
+
+This option is enabled by default. If it is disabled, PuTTY will
+ignore any \i{control sequence}s sent by the server to request coloured
+text.
+
+If you have a particularly garish application, you might want to
+turn this option off and make PuTTY only use the default foreground
+and background colours.
+
+\S{config-xtermcolour} \q{Allow terminal to use xterm \i{256-colour mode}}
+
+\cfg{winhelp-topic}{colours.xterm256}
+
+This option is enabled by default. If it is disabled, PuTTY will
+ignore any control sequences sent by the server which use the
+extended 256-colour mode supported by recent versions of \cw{xterm}.
+
+If you have an application which is supposed to use 256-colour mode
+and it isn't working, you may find you need to tell your server that
+your terminal supports 256 colours. On Unix, you do this by ensuring
+that the setting of \i\cw{TERM} describes a 256-colour-capable
+terminal. You can check this using a command such as \c{infocmp}:
+
+\c $ infocmp | grep colors
+\c colors#256, cols#80, it#8, lines#24, pairs#256,
+\e bbbbbbbbbb
+
+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}
+
+\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.
+
+\S{config-logpalette} \q{Attempt to use \i{logical palettes}}
+
+\cfg{winhelp-topic}{colours.logpal}
+
+Logical palettes are a mechanism by which a Windows application
+running on an \i{8-bit colour} display can select precisely the colours
+it wants instead of going with the Windows standard defaults.
+
+If you are not getting the colours you ask for on an 8-bit display,
+you can try enabling this option. However, be warned that it's never
+worked very well.
+
+\S{config-syscolour} \q{Use \i{system colours}}
+
+\cfg{winhelp-topic}{colours.system}
+
+Enabling this option will cause PuTTY to ignore the configured colours
+for \I{default background}\I{default foreground}\q{Default
+Background/Foreground} and \I{cursor colour}\q{Cursor Colour/Text} (see
+\k{config-colourcfg}), instead going with the system-wide defaults.
+
+Note that non-bold and \i{bold text} will be the same colour if this
+option is enabled. You might want to change to indicating bold text
+by font changes (see \k{config-boldcolour}).
+
+\S{config-colourcfg} Adjusting the colours in the \i{terminal window}
+
+\cfg{winhelp-topic}{colours.config}
+
+The main colour control allows you to specify exactly what colours
+things should be displayed in. To modify one of the PuTTY colours,
+use the list box to select which colour you want to modify. The \i{RGB
+values} for that colour will appear on the right-hand side of the
+list box. Now, if you press the \q{Modify} button, you will be
+presented with a colour selector, in which you can choose a new
+colour to go in place of the old one. (You may also edit the RGB
+values directly in the edit boxes, if you wish; each value is an
+integer from 0 to 255.)
+
+PuTTY allows you to set the \i{cursor colour}, the \i{default foreground}
+and \I{default background}background, and the precise shades of all the
+\I{ANSI colours}ANSI configurable colours (black, red, green, yellow, blue,
+magenta, cyan, and white). You can also modify the precise shades used for
+the \i{bold} versions of these colours; these are used to display bold text
+if you have selected \q{Bolded text is a different colour}, and can also be
+used if the server asks specifically to use them. (Note that \q{Default
+Bold Background} is \e{not} the background colour used for bold text;
+it is only used if the server specifically asks for a bold
+background.)
+
+\H{config-connection} The Connection panel
+
+The Connection panel allows you to configure options that apply to
+more than one type of \i{connection}.
+
+\S{config-keepalive} Using \i{keepalives} to prevent disconnection
+
+\cfg{winhelp-topic}{connection.keepalive}
+
+If you find your sessions are closing unexpectedly (most often with
+\q{Connection reset by peer}) after they have been idle for a while,
+you might want to try using this option.
+
+Some network \i{routers} and \i{firewalls} need to keep track of all
+connections through them. Usually, these firewalls will assume a
+connection is dead if no data is transferred in either direction
+after a certain time interval. This can cause PuTTY sessions to be
+unexpectedly closed by the firewall if no traffic is seen in the
+session for some time.
+
+The keepalive option (\q{Seconds between keepalives}) allows you to
+configure PuTTY to send data through the session at regular
+intervals, in a way that does not disrupt the actual terminal
+session. If you find your firewall is cutting \i{idle connections} off,
+you can try entering a non-zero value in this field. The value is
+measured in seconds; so, for example, if your firewall cuts
+connections off after ten minutes then you might want to enter 300
+seconds (5 minutes) in the box.
+
+Note that keepalives are not always helpful. They help if you have a
+firewall which drops your connection after an idle period; but if
+the network between you and the server suffers from \i{breaks in
+connectivity} then keepalives can actually make things worse. If a
+session is idle, and connectivity is temporarily lost between the
+endpoints, but the connectivity is restored before either side tries
+to send anything, then there will be no problem - neither endpoint
+will notice that anything was wrong. However, if one side does send
+something during the break, it will repeatedly try to re-send, and
+eventually give up and abandon the connection. Then when
+connectivity is restored, the other side will find that the first
+side doesn't believe there is an open connection any more.
+Keepalives can make this sort of problem worse, because they
+increase the probability that PuTTY will attempt to send data during
+a break in connectivity. (Other types of periodic network activity
+can cause this behaviour; in particular, SSH-2 re-keys can have
+this effect. See \k{config-ssh-kex-rekey}.)
+
+Therefore, you might find that keepalives help
+connection loss, or you might find they make it worse, depending on
+what \e{kind} of network problems you have between you and the
+server.
+
+Keepalives are only supported in Telnet and SSH; the Rlogin and Raw
+protocols offer no way of implementing them. (For an alternative, see
+\k{config-tcp-keepalives}.)
+
+Note that if you are using \i{SSH-1} and the server has a bug that makes
+it unable to deal with SSH-1 ignore messages (see
+\k{config-ssh-bug-ignore1}), enabling keepalives will have no effect.
+
+\S{config-nodelay} \q{Disable \i{Nagle's algorithm}}
+
+\cfg{winhelp-topic}{connection.nodelay}
+
+Nagle's algorithm is a detail of TCP/IP implementations that tries
+to minimise the number of small data packets sent down a network
+connection. With Nagle's algorithm enabled, PuTTY's \i{bandwidth} usage
+will be slightly more efficient; with it disabled, you may find you
+get a faster response to your keystrokes when connecting to some
+types of server.
+
+The Nagle algorithm is disabled by default for \i{interactive connections}.
+
+\S{config-tcp-keepalives} \q{Enable \i{TCP keepalives}}
+
+\cfg{winhelp-topic}{connection.tcpkeepalive}
+
+\e{NOTE:} TCP keepalives should not be confused with the
+application-level keepalives described in \k{config-keepalive}. If in
+doubt, you probably want application-level keepalives; TCP keepalives
+are provided for completeness.
+
+The idea of TCP keepalives is similar to application-level keepalives,
+and the same caveats apply. The main differences are:
+
+\b TCP keepalives are available on \e{all} connection types, including
+Raw and Rlogin.
+
+\b The interval between TCP keepalives is usually much longer,
+typically two hours; this is set by the operating system, and cannot
+be configured within PuTTY.
+
+\b If the operating system does not receive a response to a keepalive,
+it may send out more in quick succession and terminate the connection
+if no response is received.
+
+TCP keepalives may be more useful for ensuring that \i{half-open connections}
+are terminated than for keeping a connection alive.
+
+TCP keepalives are disabled by default.
+
+\S{config-address-family} \I{Internet protocol version}\q{Internet protocol}
+
+\cfg{winhelp-topic}{connection.ipversion}
+
+This option allows the user to select between the old and new
+Internet protocols and addressing schemes (\i{IPv4} and \i{IPv6}).
+The selected protocol will be used for most outgoing network
+connections (including connections to \I{proxy}proxies); however,
+tunnels have their own configuration, for which see
+\k{config-ssh-portfwd-address-family}.
+
+The default setting is \q{Auto}, which means PuTTY will do something
+sensible and try to guess which protocol you wanted. (If you specify
+a literal \i{Internet address}, it will use whichever protocol that
+address implies. If you provide a \i{hostname}, it will see what kinds
+of address exist for that hostname; it will use IPv6 if there is an
+IPv6 address available, and fall back to IPv4 if not.)
+
+If you need to force PuTTY to use a particular protocol, you can
+explicitly set this to \q{IPv4} or \q{IPv6}.
+
+\S{config-loghost} \I{logical host name}\q{Logical name of remote host}
+
+\cfg{winhelp-topic}{connection.loghost}
+
+This allows you to tell PuTTY that the host it will really end up
+connecting to is different from where it thinks it is making a
+network connection.
+
+You might use this, for instance, if you had set up an SSH port
+forwarding in one PuTTY session so that connections to some
+arbitrary port (say, \cw{localhost} port 10022) were forwarded to a
+second machine's SSH port (say, \cw{foovax} port 22), and then
+started a second PuTTY connecting to the forwarded port.
+
+In normal usage, the second PuTTY will access the host key cache
+under the host name and port it actually connected to (i.e.
+\cw{localhost} port 10022 in this example). Using the logical host
+name option, however, you can configure the second PuTTY to cache
+the host key under the name of the host \e{you} know that it's
+\e{really} going to end up talking to (here \c{foovax}).
+
+This can be useful if you expect to connect to the same actual
+server through many different channels (perhaps because your port
+forwarding arrangements keep changing): by consistently setting the
+logical host name, you can arrange that PuTTY will not keep asking
+you to reconfirm its host key. Conversely, if you expect to use the
+same local port number for port forwardings to lots of different
+servers, you probably didn't want any particular server's host key
+cached under that local port number.
+
+If you just enter a host name for this option, PuTTY will cache the
+SSH host key under the default SSH port for that host, irrespective
+of the port you really connected to (since the typical scenario is
+like the above example: you connect to a silly real port number and
+your connection ends up forwarded to the normal port-22 SSH server
+of some other machine). To override this, you can append a port
+number to the logical host name, separated by a colon. E.g. entering
+\cq{foovax:2200} as the logical host name will cause the host key to
+be cached as if you had connected to port 2200 of \c{foovax}.
+
+If you provide a host name using this option, it is also displayed
+in other locations which contain the remote host name, such as the
+default window title and the default SSH password prompt. This
+reflects the fact that this is the host you're \e{really} connecting
+to, which is more important than the mere means you happen to be
+using to contact that host. (This applies even if you're using a
+protocol other than SSH.)
+
+\H{config-data} The Data panel
+
+The Data panel allows you to configure various pieces of data which
+can be sent to the server to affect your connection at the far end.
+
+Each option on this panel applies to more than one protocol.
+Options which apply to only one protocol appear on that protocol's
+configuration panels.
+
+\S{config-username} \q{\ii{Auto-login username}}
+
+\cfg{winhelp-topic}{connection.username}
+
+All three of the SSH, Telnet and Rlogin protocols allow you to
+specify what user name you want to log in as, without having to type
+it explicitly every time. (Some Telnet servers don't support this.)
+
+In this box you can type that user name.
+
+\S{config-username-from-env} Use of system username
+
+\cfg{winhelp-topic}{connection.usernamefromenv}
+
+When the previous box (\k{config-username}) is left blank, by default,
+PuTTY will prompt for a username at the time you make a connection.
+
+In some environments, such as the networks of large organisations
+implementing \i{single sign-on}, a more sensible default may be to use
+the name of the user logged in to the local operating system (if any);
+this is particularly likely to be useful with \i{GSSAPI} authentication
+(see \k{config-ssh-auth-gssapi}). This control allows you to change
+the default behaviour.
+
+The current system username is displayed in the dialog as a
+convenience. It is not saved in the configuration; if a saved session
+is later used by a different user, that user's name will be used.
+
+\S{config-termtype} \q{\ii{Terminal-type} string}
+
+\cfg{winhelp-topic}{connection.termtype}
+
+Most servers you might connect to with PuTTY are designed to be
+connected to from lots of different types of terminal. In order to
+send the right \i{control sequence}s to each one, the server will need
+to know what type of terminal it is dealing with. Therefore, each of
+the SSH, Telnet and Rlogin protocols allow a text string to be sent
+down the connection describing the terminal. On a \i{Unix} server,
+this selects an entry from the \i\c{termcap} or \i\c{terminfo} database
+that tells applications what \i{control sequences} to send to the
+terminal, and what character sequences to expect the \i{keyboard}
+to generate.
+
+PuTTY attempts to emulate the Unix \i\c{xterm} program, and by default
+it reflects this by sending \c{xterm} as a terminal-type string. If
+you find this is not doing what you want - perhaps the remote
+system reports \q{Unknown terminal type} - you could try setting
+this to something different, such as \i\c{vt220}.
+
+If you're not sure whether a problem is due to the terminal type
+setting or not, you probably need to consult the manual for your
+application or your server.
+
+\S{config-termspeed} \q{\ii{Terminal speed}s}
+
+\cfg{winhelp-topic}{connection.termspeed}
+
+The Telnet, Rlogin, and SSH protocols allow the client to specify
+terminal speeds to the server.
+
+This parameter does \e{not} affect the actual speed of the connection,
+which is always \q{as fast as possible}; it is just a hint that is
+sometimes used by server software to modify its behaviour. For
+instance, if a slow speed is indicated, the server may switch to a
+less \i{bandwidth}-hungry display mode.
+
+The value is usually meaningless in a network environment, but
+PuTTY lets you configure it, in case you find the server is reacting
+badly to the default value.
+
+The format is a pair of numbers separated by a comma, for instance,
+\c{38400,38400}. The first number represents the output speed
+(\e{from} the server) in bits per second, and the second is the input
+speed (\e{to} the server). (Only the first is used in the Rlogin
+protocol.)
+
+This option has no effect on Raw connections.
+
+\S{config-environ} Setting \i{environment variables} on the server
+
+\cfg{winhelp-topic}{telnet.environ}
+
+The Telnet protocol provides a means for the client to pass
+environment variables to the server. Many Telnet servers have
+stopped supporting this feature due to security flaws, but PuTTY
+still supports it for the benefit of any servers which have found
+other ways around the security problems than just disabling the
+whole mechanism.
+
+Version 2 of the SSH protocol also provides a similar mechanism,
+which is easier to implement without security flaws. Newer \i{SSH-2}
+servers are more likely to support it than older ones.
+
+This configuration data is not used in the SSH-1, rlogin or raw
+protocols.
+
+To add an environment variable to the list transmitted down the
+connection, you enter the variable name in the \q{Variable} box,
+enter its value in the \q{Value} box, and press the \q{Add} button.
+To remove one from the list, select it in the list box and press
+\q{Remove}.
+
+\H{config-proxy} The Proxy panel
+
+\cfg{winhelp-topic}{proxy.main}
+
+The \ii{Proxy} panel allows you to configure PuTTY to use various types
+of proxy in order to make its network connections. The settings in
+this panel affect the primary network connection forming your PuTTY
+session, and also any extra connections made as a result of SSH \i{port
+forwarding} (see \k{using-port-forwarding}).
+
+Note that unlike some software (such as web browsers), PuTTY does not
+attempt to automatically determine whether to use a proxy and (if so)
+which one to use for a given destination. If you need to use a proxy,
+it must always be explicitly configured.
+
+\S{config-proxy-type} Setting the proxy type
+
+\cfg{winhelp-topic}{proxy.type}
+
+The \q{Proxy type} radio buttons allow you to configure what type of
+proxy you want PuTTY to use for its network connections. The default
+setting is \q{None}; in this mode no proxy is used for any
+connection.
+
+\b Selecting \I{HTTP proxy}\q{HTTP} allows you to proxy your connections
+through a web server supporting the HTTP \cw{CONNECT} command, as documented
+in \W{http://www.ietf.org/rfc/rfc2817.txt}{RFC 2817}.
+
+\b Selecting \q{SOCKS 4} or \q{SOCKS 5} allows you to proxy your
+connections through a \i{SOCKS server}.
+
+\b Many firewalls implement a less formal type of proxy in which a
+user can make a Telnet connection directly to the firewall machine
+and enter a command such as \c{connect myhost.com 22} to connect
+through to an external host. Selecting \I{Telnet proxy}\q{Telnet}
+allows you to tell PuTTY to use this type of proxy.
+
+\b Selecting \I{Local proxy}\q{Local} allows you to specify an arbitrary
+command on the local machine to act as a proxy. When the session is
+started, instead of creating a TCP connection, PuTTY runs the command
+(specified in \k{config-proxy-command}), and uses its standard input and
+output streams.
+
+\lcont{
+This could be used, for instance, to talk to some kind of network proxy
+that PuTTY does not natively support; or you could tunnel a connection
+over something other than TCP/IP entirely.
+
+If you want your local proxy command to make a secondary SSH
+connection to a proxy host and then tunnel the primary connection
+over that, you might well want the \c{-nc} command-line option in
+Plink. See \k{using-cmdline-ncmode} for more information.
+}
+
+\S{config-proxy-exclude} Excluding parts of the network from proxying
+
+\cfg{winhelp-topic}{proxy.exclude}
+
+Typically you will only need to use a proxy to connect to non-local
+parts of your network; for example, your proxy might be required for
+connections outside your company's internal network. In the
+\q{Exclude Hosts/IPs} box you can enter ranges of IP addresses, or
+ranges of DNS names, for which PuTTY will avoid using the proxy and
+make a direct connection instead.
+
+The \q{Exclude Hosts/IPs} box may contain more than one exclusion
+range, separated by commas. Each range can be an IP address or a DNS
+name, with a \c{*} character allowing wildcards. For example:
+
+\c *.example.com
+
+This excludes any host with a name ending in \c{.example.com} from
+proxying.
+
+\c 192.168.88.*
+
+This excludes any host with an IP address starting with 192.168.88
+from proxying.
+
+\c 192.168.88.*,*.example.com
+
+This excludes both of the above ranges at once.
+
+Connections to the local host (the host name \i\c{localhost}, and any
+\i{loopback IP address}) are never proxied, even if the proxy exclude
+list does not explicitly contain them. It is very unlikely that this
+behaviour would ever cause problems, but if it does you can change
+it by enabling \q{Consider proxying local host connections}.
+
+Note that if you are doing \I{proxy DNS}DNS at the proxy (see
+\k{config-proxy-dns}), you should make sure that your proxy
+exclusion settings do not depend on knowing the IP address of a
+host. If the name is passed on to the proxy without PuTTY looking it
+up, it will never know the IP address and cannot check it against
+your list.
+
+\S{config-proxy-dns} \I{proxy DNS}\ii{Name resolution} when using a proxy
+
+\cfg{winhelp-topic}{proxy.dns}
+
+If you are using a proxy to access a private network, it can make a
+difference whether \i{DNS} name resolution is performed by PuTTY itself
+(on the client machine) or performed by the proxy.
+
+The \q{Do DNS name lookup at proxy end} configuration option allows
+you to control this. If you set it to \q{No}, PuTTY will always do
+its own DNS, and will always pass an IP address to the proxy. If you
+set it to \q{Yes}, PuTTY will always pass host names straight to the
+proxy without trying to look them up first.
+
+If you set this option to \q{Auto} (the default), PuTTY will do
+something it considers appropriate for each type of proxy. Telnet,
+HTTP, and SOCKS5 proxies will have host names passed straight to
+them; SOCKS4 proxies will not.
+
+Note that if you are doing DNS at the proxy, you should make sure
+that your proxy exclusion settings (see \k{config-proxy-exclude}) do
+not depend on knowing the IP address of a host. If the name is
+passed on to the proxy without PuTTY looking it up, it will never
+know the IP address and cannot check it against your list.
+
+The original SOCKS 4 protocol does not support proxy-side DNS. There
+is a protocol extension (SOCKS 4A) which does support it, but not
+all SOCKS 4 servers provide this extension. If you enable proxy DNS
+and your SOCKS 4 server cannot deal with it, this might be why.
+
+\S{config-proxy-auth} \I{proxy username}Username and \I{proxy password}password
+
+\cfg{winhelp-topic}{proxy.auth}
+
+If your proxy requires \I{proxy authentication}authentication, you can
+enter a username and a password in the \q{Username} and \q{Password} boxes.
+
+\I{security hazard}Note that if you save your session, the proxy
+password will be saved in plain text, so anyone who can access your PuTTY
+configuration data will be able to discover it.
+
+Authentication is not fully supported for all forms of proxy:
+
+\b Username and password authentication is supported for HTTP
+proxies and SOCKS 5 proxies.
+
+\lcont{
+
+\b With SOCKS 5, authentication is via \i{CHAP} if the proxy
+supports it (this is not supported in \i{PuTTYtel}); otherwise the
+password is sent to the proxy in \I{plaintext password}plain text.
+
+\b With HTTP proxying, the only currently supported authentication
+method is \I{HTTP basic}\q{basic}, where the password is sent to the proxy
+in \I{plaintext password}plain text.
+
+}
+
+\b SOCKS 4 can use the \q{Username} field, but does not support
+passwords.
+
+\b You can specify a way to include a username and password in the
+Telnet/Local proxy command (see \k{config-proxy-command}).
+
+\S{config-proxy-command} Specifying the Telnet or Local proxy command
+
+\cfg{winhelp-topic}{proxy.command}
+
+If you are using the \i{Telnet proxy} type, the usual command required
+by the firewall's Telnet server is \c{connect}, followed by a host
+name and a port number. If your proxy needs a different command,
+you can enter an alternative here.
+
+If you are using the \i{Local proxy} type, the local command to run
+is specified here.
+
+In this string, you can use \c{\\n} to represent a new-line, \c{\\r}
+to represent a carriage return, \c{\\t} to represent a tab
+character, and \c{\\x} followed by two hex digits to represent any
+other character. \c{\\\\} is used to encode the \c{\\} character
+itself.
+
+Also, the special strings \c{%host} and \c{%port} will be replaced
+by the host name and port number you want to connect to. The strings
+\c{%user} and \c{%pass} will be replaced by the proxy username and
+password you specify. The strings \c{%proxyhost} and \c{%proxyport}
+will be replaced by the host details specified on the \e{Proxy} panel,
+if any (this is most likely to be useful for the Local proxy type).
+To get a literal \c{%} sign, enter \c{%%}.
+
+If a Telnet proxy server prompts for a username and password
+before commands can be sent, you can use a command such as:
+
+\c %user\n%pass\nconnect %host %port\n
+
+This will send your username and password as the first two lines to
+the proxy, followed by a command to connect to the desired host and
+port. Note that if you do not include the \c{%user} or \c{%pass}
+tokens in the Telnet command, then the \q{Username} and \q{Password}
+configuration fields will be ignored.
+
+\H{config-telnet} The \i{Telnet} panel
+
+The Telnet panel allows you to configure options that only apply to
+Telnet sessions.
+
+\S{config-oldenviron} \q{Handling of OLD_ENVIRON ambiguity}
+
+\cfg{winhelp-topic}{telnet.oldenviron}
+
+The original Telnet mechanism for passing \i{environment variables} was
+badly specified. At the time the standard (RFC 1408) was written,
+BSD telnet implementations were already supporting the feature, and
+the intention of the standard was to describe the behaviour the BSD
+implementations were already using.
+
+Sadly there was a typing error in the standard when it was issued,
+and two vital function codes were specified the wrong way round. BSD
+implementations did not change, and the standard was not corrected.
+Therefore, it's possible you might find either \i{BSD} or \i{RFC}-compliant
+implementations out there. This switch allows you to choose which
+one PuTTY claims to be.
+
+The problem was solved by issuing a second standard, defining a new
+Telnet mechanism called \i\cw{NEW_ENVIRON}, which behaved exactly like
+the original \i\cw{OLD_ENVIRON} but was not encumbered by existing
+implementations. Most Telnet servers now support this, and it's
+unambiguous. This feature should only be needed if you have trouble
+passing environment variables to quite an old server.
+
+\S{config-ptelnet} Passive and active \i{Telnet negotiation} modes
+
+\cfg{winhelp-topic}{telnet.passive}
+
+In a Telnet connection, there are two types of data passed between
+the client and the server: actual text, and \e{negotiations} about
+which Telnet extra features to use.
+
+PuTTY can use two different strategies for negotiation:
+
+\b In \I{active Telnet negotiation}\e{active} mode, PuTTY starts to send
+negotiations as soon as the connection is opened.
+
+\b In \I{passive Telnet negotiation}\e{passive} mode, PuTTY will wait to
+negotiate until it sees a negotiation from the server.
+
+The obvious disadvantage of passive mode is that if the server is
+also operating in a passive mode, then negotiation will never begin
+at all. For this reason PuTTY defaults to active mode.
+
+However, sometimes passive mode is required in order to successfully
+get through certain types of firewall and \i{Telnet proxy} server. If
+you have confusing trouble with a \i{firewall}, you could try enabling
+passive mode to see if it helps.
+
+\S{config-telnetkey} \q{Keyboard sends \i{Telnet special commands}}
+
+\cfg{winhelp-topic}{telnet.specialkeys}
+
+If this box is checked, several key sequences will have their normal
+actions modified:
+
+\b the Backspace key on the keyboard will send the \I{Erase Character,
+Telnet special command}Telnet special backspace code;
+
+\b Control-C will send the Telnet special \I{Interrupt Process, Telnet
+special command}Interrupt Process code;
+
+\b Control-Z will send the Telnet special \I{Suspend Process, Telnet
+special command}Suspend Process code.
+
+You probably shouldn't enable this
+unless you know what you're doing.
+
+\S{config-telnetnl} \q{Return key sends \i{Telnet New Line} instead of ^M}
+
+\cfg{winhelp-topic}{telnet.newline}
+
+Unlike most other remote login protocols, the Telnet protocol has a
+special \q{\i{new line}} code that is not the same as the usual line
+endings of Control-M or Control-J. By default, PuTTY sends the
+Telnet New Line code when you press Return, instead of sending
+Control-M as it does in most other protocols.
+
+Most Unix-style Telnet servers don't mind whether they receive
+Telnet New Line or Control-M; some servers do expect New Line, and
+some servers prefer to see ^M. If you are seeing surprising
+behaviour when you press Return in a Telnet session, you might try
+turning this option off to see if it helps.
+
+\H{config-rlogin} The Rlogin panel
+
+The \i{Rlogin} panel allows you to configure options that only apply to
+Rlogin sessions.
+
+\S{config-rlogin-localuser} \I{local username in Rlogin}\q{Local username}
+
+\cfg{winhelp-topic}{rlogin.localuser}
+
+Rlogin allows an automated (password-free) form of login by means of
+a file called \i\c{.rhosts} on the server. You put a line in your
+\c{.rhosts} file saying something like \c{jbloggs@pc1.example.com},
+and then when you make an Rlogin connection the client transmits the
+username of the user running the Rlogin client. The server checks
+the username and hostname against \c{.rhosts}, and if they match it
+\I{passwordless login}does not ask for a password.
+
+This only works because Unix systems contain a safeguard to stop a
+user from pretending to be another user in an Rlogin connection.
+Rlogin connections have to come from \I{privileged port}port numbers below
+1024, and Unix systems prohibit this to unprivileged processes; so when the
+server sees a connection from a low-numbered port, it assumes the
+client end of the connection is held by a privileged (and therefore
+trusted) process, so it believes the claim of who the user is.
+
+Windows does not have this restriction: \e{any} user can initiate an
+outgoing connection from a low-numbered port. Hence, the Rlogin
+\c{.rhosts} mechanism is completely useless for securely
+distinguishing several different users on a Windows machine. If you
+have a \c{.rhosts} entry pointing at a Windows PC, you should assume
+that \e{anyone} using that PC can \i{spoof} your username in
+an Rlogin connection and access your account on the server.
+
+The \q{Local username} control allows you to specify what user name
+PuTTY should claim you have, in case it doesn't match your \i{Windows
+user name} (or in case you didn't bother to set up a Windows user
+name).
+
+\H{config-ssh} The SSH panel
+
+The \i{SSH} panel allows you to configure options that only apply to
+SSH sessions.
+
+\S{config-command} Executing a specific command on the server
+
+\cfg{winhelp-topic}{ssh.command}
+
+In SSH, you don't have to run a general shell session on the server.
+Instead, you can choose to run a single specific command (such as a
+mail user agent, for example). If you want to do this, enter the
+command in the \q{\ii{Remote command}} box.
+
+Note that most servers will close the session after executing the
+command.
+
+\S{config-ssh-noshell} \q{Don't start a \I{remote shell}shell or
+\I{remote command}command at all}
+
+\cfg{winhelp-topic}{ssh.noshell}
+
+If you tick this box, PuTTY will not attempt to run a shell or
+command after connecting to the remote server. You might want to use
+this option if you are only using the SSH connection for \i{port
+forwarding}, and your user account on the server does not have the
+ability to run a shell.
+
+This feature is only available in \i{SSH protocol version 2} (since the
+version 1 protocol assumes you will always want to run a shell).
+
+This feature can also be enabled using the \c{-N} command-line
+option; see \k{using-cmdline-noshell}.
+
+If you use this feature in Plink, you will not be able to terminate
+the Plink process by any graceful means; the only way to kill it
+will be by pressing Control-C or sending a kill signal from another
+program.
+
+\S{config-ssh-comp} \q{Enable \i{compression}}
+
+\cfg{winhelp-topic}{ssh.compress}
+
+This enables data compression in the SSH connection: data sent by
+the server is compressed before sending, and decompressed at the
+client end. Likewise, data sent by PuTTY to the server is compressed
+first and the server decompresses it at the other end. This can help
+make the most of a low-\i{bandwidth} connection.
+
+\S{config-ssh-prot} \q{Preferred \i{SSH protocol version}}
+
+\cfg{winhelp-topic}{ssh.protocol}
+
+This allows you to select whether you would like to use \i{SSH protocol
+version 1} or \I{SSH-2}version 2. \#{FIXME: say something about this elsewhere?}
+
+PuTTY will attempt to use protocol 1 if the server you connect to
+does not offer protocol 2, and vice versa.
+
+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.
+
+\H{config-ssh-kex} The Kex panel
+
+\# FIXME: This whole section is draft. Feel free to revise.
+
+The Kex panel (short for \q{\i{key exchange}}) allows you to configure
+options related to SSH-2 key exchange.
+
+Key exchange occurs at the start of an SSH connection (and
+occasionally thereafter); it establishes a \i{shared secret} that is used
+as the basis for all of SSH's security features. It is therefore very
+important for the security of the connection that the key exchange is
+secure.
+
+Key exchange is a cryptographically intensive process; if either the
+client or the server is a relatively slow machine, the slower methods
+may take several tens of seconds to complete.
+
+If connection startup is too slow, or the connection hangs
+periodically, you may want to try changing these settings.
+
+If you don't understand what any of this means, it's safe to leave
+these settings alone.
+
+This entire panel is only relevant to SSH protocol version 2; none of
+these settings affect SSH-1 at all.
+
+\S{config-ssh-kex-order} \ii{Key exchange algorithm} selection
+
+\cfg{winhelp-topic}{ssh.kex.order}
+
+PuTTY supports a variety of SSH-2 key exchange methods, and allows you
+to choose which one you prefer to use; configuration is similar to
+cipher selection (see \k{config-ssh-encryption}).
+
+PuTTY currently supports the following varieties of \i{Diffie-Hellman key
+exchange}:
+
+\b \q{Group 14}: a well-known 2048-bit group.
+
+\b \q{Group 1}: a well-known 1024-bit group. This is less secure
+\#{FIXME better words} than group 14, but may be faster with slow
+client or server machines, and may be the only method supported by
+older server software.
+
+\b \q{\ii{Group exchange}}: with this method, instead of using a fixed
+group, PuTTY requests that the server suggest a group to use for key
+exchange; the server can avoid groups known to be weak, and possibly
+invent new ones over time, without any changes required to PuTTY's
+configuration. We recommend use of this method, if possible.
+
+In addition, PuTTY supports \i{RSA key exchange}, which requires much less
+computational effort on the part of the client, and somewhat less on
+the part of the server, than Diffie-Hellman key exchange.
+
+If the first algorithm PuTTY finds is below the \q{warn below here}
+line, you will see a warning box when you make the connection, similar
+to that for cipher selection (see \k{config-ssh-encryption}).
+
+\S{config-ssh-kex-rekey} \ii{Repeat key exchange}
+
+\cfg{winhelp-topic}{ssh.kex.repeat}
+
+If the session key negotiated at connection startup is used too much
+or for too long, it may become feasible to mount attacks against the
+SSH connection. Therefore, the SSH-2 protocol specifies that a new key
+exchange should take place every so often; this can be initiated by
+either the client or the server.
+
+While this renegotiation is taking place, no data can pass through
+the SSH connection, so it may appear to \q{freeze}. (The occurrence of
+repeat key exchange is noted in the Event Log; see
+\k{using-eventlog}.) Usually the same algorithm is used as at the
+start of the connection, with a similar overhead.
+
+These options control how often PuTTY will initiate a repeat key
+exchange (\q{rekey}). You can also force a key exchange at any time
+from the Special Commands menu (see \k{using-specials}).
+
+\# FIXME: do we have any additions to the SSH-2 specs' advice on
+these values? Do we want to enforce any limits?
+
+\b \q{Max minutes before rekey} specifies the amount of time that is
+allowed to elapse before a rekey is initiated. If this is set to zero,
+PuTTY will not rekey due to elapsed time. The SSH-2 protocol
+specification recommends a timeout of at most 60 minutes.
+
+You might have a need to disable time-based rekeys completely for the same
+reasons that \i{keepalives} aren't always helpful. If you anticipate
+suffering a network dropout of several hours in the middle of an SSH
+connection, but were not actually planning to send \e{data} down
+that connection during those hours, then an attempted rekey in the
+middle of the dropout will probably cause the connection to be
+abandoned, whereas if rekeys are disabled then the connection should
+in principle survive (in the absence of interfering \i{firewalls}). See
+\k{config-keepalive} for more discussion of these issues; for these
+purposes, rekeys have much the same properties as keepalives.
+(Except that rekeys have cryptographic value in themselves, so you
+should bear that in mind when deciding whether to turn them off.)
+Note, however, the the SSH \e{server} can still initiate rekeys.
+
+\b \q{Max data before rekey} specifies the amount of data (in bytes)
+that is permitted to flow in either direction before a rekey is
+initiated. If this is set to zero, PuTTY will not rekey due to
+transferred data. The SSH-2 protocol specification recommends a limit
+of at most 1 gigabyte.
+
+\lcont{
+
+As well as specifying a value in bytes, the following shorthand can be
+used:
+
+\b \cq{1k} specifies 1 kilobyte (1024 bytes).
+
+\b \cq{1M} specifies 1 megabyte (1024 kilobytes).
+
+\b \cq{1G} specifies 1 gigabyte (1024 megabytes).
+
+}
+
+Disabling data-based rekeys entirely is a bad idea. The \i{integrity},
+and to a lesser extent, \i{confidentiality} of the SSH-2 protocol depend
+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-auth} The Auth panel
+
+The Auth panel allows you to configure \i{authentication} options for
+SSH sessions.
+
+\S{config-ssh-noauth} \q{Bypass authentication entirely}
+
+\cfg{winhelp-topic}{ssh.auth.bypass}
+
+In SSH-2, it is possible to establish a connection without using SSH's
+mechanisms to identify or authenticate oneself to the server. Some
+servers may prefer to handle authentication in the data channel, for
+instance, or may simply require no authentication whatsoever.
+
+By default, PuTTY assumes the server requires authentication (most
+do), and thus must provide a username. If you find you are getting
+unwanted username prompts, you could try checking this option.
+
+This option only affects SSH-2 connections. SSH-1 connections always
+require an authentication step.
+
+\S{config-ssh-banner} \q{Display pre-authentication banner}
+
+\cfg{winhelp-topic}{ssh.auth.banner}
+
+SSH-2 servers can provide a message for clients to display to the
+prospective user before the user logs in; this is sometimes known as a
+pre-authentication \q{\i{banner}}. Typically this is used to provide
+information about the server and legal notices.
+
+By default, PuTTY displays this message before prompting for a
+password or similar credentials (although, unfortunately, not before
+prompting for a login name, due to the nature of the protocol design).
+By unchecking this option, display of the banner can be suppressed
+entirely.
+
+\S{config-ssh-tryagent} \q{Attempt authentication using Pageant}
+
+\cfg{winhelp-topic}{ssh.auth.pageant}
+
+If this option is enabled, then PuTTY will look for Pageant (the SSH
+private-key storage agent) and attempt to authenticate with any
+suitable public keys Pageant currently holds.
+
+This behaviour is almost always desirable, and is therefore enabled
+by default. In rare cases you might need to turn it off in order to
+force authentication by some non-public-key method such as
+passwords.
+
+This option can also be controlled using the \c{-noagent}
+command-line option. See \k{using-cmdline-agentauth}.
+
+See \k{pageant} for more information about Pageant in general.
+
+\S{config-ssh-tis} \q{Attempt \I{TIS authentication}TIS or
+\i{CryptoCard authentication}}
+
+\cfg{winhelp-topic}{ssh.auth.tis}
+
+TIS and CryptoCard authentication are (despite their names) generic
+forms of simple \I{challenge/response authentication}challenge/response
+authentication available in SSH protocol version 1 only. You might use
+them if you were using \i{S/Key} \i{one-time passwords}, for example,
+or if you had a physical \i{security token} that generated responses
+to authentication challenges. They can even be used to prompt for
+simple passwords.
+
+With this switch enabled, PuTTY will attempt these forms of
+authentication if the server is willing to try them. You will be
+presented with a challenge string (which may be different every
+time) and must supply the correct response in order to log in. If
+your server supports this, you should talk to your system
+administrator about precisely what form these challenges and
+responses take.
+
+\S{config-ssh-ki} \q{Attempt \i{keyboard-interactive authentication}}
+
+\cfg{winhelp-topic}{ssh.auth.ki}
+
+The SSH-2 equivalent of TIS authentication is called
+\q{keyboard-interactive}. It is a flexible authentication method
+using an arbitrary sequence of requests and responses; so it is not
+only useful for \I{challenge/response authentication}challenge/response
+mechanisms such as \i{S/Key}, but it can also be used for (for example)
+asking the user for a \I{password expiry}new password when the old one
+has expired.
+
+PuTTY leaves this option enabled by default, but supplies a switch
+to turn it off in case you should have trouble with it.
+
+\S{config-ssh-agentfwd} \q{Allow \i{agent forwarding}}
+
+\cfg{winhelp-topic}{ssh.auth.agentfwd}
+
+This option allows the SSH server to open forwarded connections back
+to your local copy of \i{Pageant}. If you are not running Pageant, this
+option will do nothing.
+
+See \k{pageant} for general information on Pageant, and
+\k{pageant-forward} for information on agent forwarding. Note that
+there is a security risk involved with enabling this option; see
+\k{pageant-security} for details.
+
+\S{config-ssh-changeuser} \q{Allow attempted \i{changes of username} in SSH-2}
+
+\cfg{winhelp-topic}{ssh.auth.changeuser}
+
+In the SSH-1 protocol, it is impossible to change username after
+failing to authenticate. So if you mis-type your username at the
+PuTTY \q{login as:} prompt, you will not be able to change it except
+by restarting PuTTY.
+
+The SSH-2 protocol \e{does} allow changes of username, in principle,
+but does not make it mandatory for SSH-2 servers to accept them. In
+particular, \i{OpenSSH} does not accept a change of username; once you
+have sent one username, it will reject attempts to try to
+authenticate as another user. (Depending on the version of OpenSSH,
+it may quietly return failure for all login attempts, or it may send
+an error message.)
+
+For this reason, PuTTY will by default not prompt you for your
+username more than once, in case the server complains. If you know
+your server can cope with it, you can enable the \q{Allow attempted
+changes of username} option to modify PuTTY's behaviour.
+
+\S{config-ssh-privkey} \q{\ii{Private key} file for authentication}
+
+\cfg{winhelp-topic}{ssh.auth.privkey}
+
+This box is where you enter the name of your private key file if you
+are using \i{public key authentication}. See \k{pubkey} for information
+about public key authentication in SSH.
+
+This key must be in PuTTY's native format (\c{*.\i{PPK}}). If you have a
+private key in another format that you want to use with PuTTY, see
+\k{puttygen-conversions}.
+
+If a key file is specified here, and \i{Pageant} is running (see
+\k{pageant}), PuTTY will first try asking Pageant to authenticate with
+that key, and ignore any other keys Pageant may have. If that fails,
+PuTTY will ask for a passphrase as normal.
+
+\H{config-ssh-auth-gssapi} The \i{GSSAPI} panel
+
+\cfg{winhelp-topic}{ssh.auth.gssapi}
+
+The \q{GSSAPI} subpanel of the \q{Auth} panel controls the use of
+GSSAPI authentication. This is a mechanism which delegates the
+authentication exchange to a library elsewhere on the client
+machine, which in principle can authenticate in many different ways
+but in practice is usually used with the \i{Kerberos} \i{single sign-on}
+protocol.
+
+GSSAPI is only available in the SSH-2 protocol.
+
+The topmost control on the GSSAPI subpanel is the checkbox labelled
+\q{Attempt GSSAPI authentication}. If this is disabled, GSSAPI will
+not be attempted at all and the rest of this panel is unused. If it
+is enabled, GSSAPI authentication will be attempted, and (typically)
+if your client machine has valid Kerberos credentials loaded, then
+PuTTY should be able to authenticate automatically to servers that
+support Kerberos logins.
+
+\S{config-ssh-auth-gssapi-delegation} \q{Allow GSSAPI credential
+delegation}
+
+\cfg{winhelp-topic}{ssh.auth.gssapi.delegation}
+
+\i{GSSAPI credential delegation} is a mechanism for passing on your
+Kerberos (or other) identity to the session on the SSH server. If
+you enable this option, then not only will PuTTY be able to log in
+automatically to a server that accepts your Kerberos credentials,
+but also you will be able to connect out from that server to other
+Kerberos-supporting services and use the same credentials just as
+automatically.
+
+(This option is the Kerberos analogue of SSH agent forwarding; see
+\k{pageant-forward} for some information on that.)
+
+Note that, like SSH agent forwarding, there is a security
+implication in the use of this option: the administrator of the
+server you connect to, or anyone else who has cracked the
+administrator account on that server, could fake your identity when
+connecting to further Kerberos-supporting services. However,
+Kerberos sites are typically run by a central authority, so the
+administrator of one server is likely to already have access to the
+other services too; so this would typically be less of a risk than
+SSH agent forwarding.
+
+\S{config-ssh-auth-gssapi-libraries} Preference order for GSSAPI
+libraries
+
+\cfg{winhelp-topic}{ssh.auth.gssapi.libraries}
+
+GSSAPI is a mechanism which allows more than one authentication
+method to be accessed through the same interface. Therefore, more
+than one authentication library may exist on your system which can
+be accessed using GSSAPI.
+
+PuTTY contains native support for a few well-known such libraries,
+and will look for all of them on your system and use whichever it
+finds. If more than one exists on your system and you need to use a
+specific one, you can adjust the order in which it will search using
+this preference list control.
+
+One of the options in the preference list is to use a user-specified
+GSSAPI library. If the library you want to use is not mentioned by
+name in PuTTY's list of options, you can enter its full pathname in
+the \q{User-supplied GSSAPI library path} field, and move the
+\q{User-supplied GSSAPI library} option in the preference list to
+make sure it is selected before anything else.
+
+\H{config-ssh-tty} The TTY panel
+
+The TTY panel lets you configure the remote pseudo-terminal.
+
+\S{config-ssh-pty} \I{pseudo-terminal allocation}\q{Don't allocate
+a pseudo-terminal}
+
+\cfg{winhelp-topic}{ssh.nopty}
+
+When connecting to a \i{Unix} system, most \I{interactive
+connections}interactive shell sessions are run in a \e{pseudo-terminal},
+which allows the Unix system to pretend it's talking to a real physical
+terminal device but allows the SSH server to catch all the data coming
+from that fake device and send it back to the client.
+
+Occasionally you might find you have a need to run a session \e{not}
+in a pseudo-terminal. In PuTTY, this is generally only useful for
+very specialist purposes; although in Plink (see \k{plink}) it is
+the usual way of working.
+
+\S{config-ttymodes} Sending \i{terminal modes}
+
+\cfg{winhelp-topic}{ssh.ttymodes}
+
+The SSH protocol allows the client to send \q{terminal modes} for
+the remote pseudo-terminal. These usually control the server's
+expectation of the local terminal's behaviour.
+
+If your server does not have sensible defaults for these modes, you
+may find that changing them here helps. If you don't understand any of
+this, it's safe to leave these settings alone.
+
+(None of these settings will have any effect if no pseudo-terminal
+is requested or allocated.)
+
+You can add or modify a mode by selecting it from the drop-down list,
+choosing whether it's set automatically or to a specific value with
+the radio buttons and edit box, and hitting \q{Add}. A mode (or
+several) can be removed from the list by selecting them and hitting
+\q{Remove}. The effect of the mode list is as follows:
+
+\b If a mode is not on the list, it will not be specified to the
+server under any circumstances.
+
+\b If a mode is on the list:
+
+\lcont{
+
+\b If the \q{Auto} option is selected, the PuTTY tools will decide
+whether to specify that mode to the server, and if so, will send
+a sensible value.
+
+\lcont{
+
+PuTTY proper will send modes that it has an opinion on (currently only
+the code for the Backspace key, \cw{ERASE}). Plink on Unix
+will propagate appropriate modes from the local terminal, if any.
+
+}
+
+\b If a value is specified, it will be sent to the server under all
+circumstances. The precise syntax of the value box depends on the
+mode.
+
+}
+
+By default, all of the available modes are listed as \q{Auto},
+which should do the right thing in most circumstances.
+
+The precise effect of each setting, if any, is up to the server. Their
+names come from \i{POSIX} and other Unix systems, and they are most
+likely to have a useful effect on such systems. (These are the same
+settings that can usually be changed using the \i\c{stty} command once
+logged in to such servers.)
+
+Some notable modes are described below; for fuller explanations, see
+your server documentation.
+
+\b \I{ERASE special character}\cw{ERASE} is the character that when typed
+by the user will delete one space to the left. When set to \q{Auto}
+(the default setting), this follows the setting of the local Backspace
+key in PuTTY (see \k{config-backspace}).
+
+\lcont{
+This and other \i{special character}s are specified using \c{^C} notation
+for Ctrl-C, and so on. Use \c{^<27>} or \c{^<0x1B>} to specify a
+character numerically, and \c{^~} to get a literal \c{^}. Other
+non-control characters are denoted by themselves. Leaving the box
+entirely blank indicates that \e{no} character should be assigned to
+the specified function, although this may not be supported by all
+servers.
+}
+
+\b \I{QUIT special character}\cw{QUIT} is a special character that
+usually forcefully ends the current process on the server
+(\cw{SIGQUIT}). On many servers its default setting is Ctrl-backslash
+(\c{^\\}), which is easy to accidentally invoke on many keyboards. If
+this is getting in your way, you may want to change it to another
+character or turn it off entirely.
+
+\b Boolean modes such as \cw{ECHO} and \cw{ICANON} can be specified in
+PuTTY in a variety of ways, such as \cw{true}/\cw{false},
+\cw{yes}/\cw{no}, and \cw{0}/\cw{1}.
+
+\b Terminal speeds are configured elsewhere; see \k{config-termspeed}.
+
+\H{config-ssh-x11} The X11 panel
+
+\cfg{winhelp-topic}{ssh.tunnels.x11}
+
+The X11 panel allows you to configure \i{forwarding of X11} over an
+SSH connection.
+
+If your server lets you run X Window System applications, X11
+forwarding allows you to securely give those applications access to
+a local X display on your PC.
+
+To enable X11 forwarding, check the \q{Enable X11 forwarding} box.
+If your X display is somewhere unusual, you will need to enter its
+location in the \q{X display location} box; if this is left blank,
+PuTTY will try to find a sensible default in the environment, or use the
+primary local display (\c{:0}) if that fails.
+
+See \k{using-x-forwarding} for more information about X11
+forwarding.
+
+\S{config-ssh-x11auth} Remote \i{X11 authentication}
+
+\cfg{winhelp-topic}{ssh.tunnels.x11auth}
+
+If you are using X11 forwarding, the virtual X server created on the
+SSH server machine will be protected by authorisation data. This
+data is invented, and checked, by PuTTY.
+
+The usual authorisation method used for this is called
+\i\cw{MIT-MAGIC-COOKIE-1}. This is a simple password-style protocol:
+the X client sends some cookie data to the server, and the server
+checks that it matches the real cookie. The cookie data is sent over
+an unencrypted X11 connection; so if you allow a client on a third
+machine to access the virtual X server, then the cookie will be sent
+in the clear.
+
+PuTTY offers the alternative protocol \i\cw{XDM-AUTHORIZATION-1}. This
+is a cryptographically authenticated protocol: the data sent by the
+X client is different every time, and it depends on the IP address
+and port of the client's end of the connection and is also stamped
+with the current time. So an eavesdropper who captures an
+\cw{XDM-AUTHORIZATION-1} string cannot immediately re-use it for
+their own X connection.
+
+PuTTY's support for \cw{XDM-AUTHORIZATION-1} is a somewhat
+experimental feature, and may encounter several problems:
+
+\b Some X clients probably do not even support
+\cw{XDM-AUTHORIZATION-1}, so they will not know what to do with the
+data PuTTY has provided.
+
+\b This authentication mechanism will only work in SSH-2. In SSH-1,
+the SSH server does not tell the client the source address of
+a forwarded connection in a machine-readable format, so it's
+impossible to verify the \cw{XDM-AUTHORIZATION-1} data.
+
+\b You may find this feature causes problems with some SSH servers,
+which will not clean up \cw{XDM-AUTHORIZATION-1} data after a
+session, so that if you then connect to the same server using
+a client which only does \cw{MIT-MAGIC-COOKIE-1} and are allocated
+the same remote display number, you might find that out-of-date
+authentication data is still present on your server and your X
+connections fail.
+
+PuTTY's default is \cw{MIT-MAGIC-COOKIE-1}. If you change it, you
+should be sure you know what you're doing.
+
+\S{config-ssh-xauthority} X authority file for local display
+
+\cfg{winhelp-topic}{ssh.tunnels.xauthority}
+
+If you are using X11 forwarding, the local X server to which your
+forwarded connections are eventually directed may itself require
+authorisation.
+
+Some Windows X servers do not require this: they do authorisation by
+simpler means, such as accepting any connection from the local
+machine but not from anywhere else. However, if your X server does
+require authorisation, then PuTTY needs to know what authorisation
+is required.
+
+One way in which this data might be made available is for the X
+server to store it somewhere in a file which has the same format
+as the Unix \c{.Xauthority} file. If this is how your Windows X
+server works, then you can tell PuTTY where to find this file by
+configuring this option. By default, PuTTY will not attempt to find
+any authorisation for your local display.
+
+\H{config-ssh-portfwd} \I{port forwarding}The Tunnels panel
+
+\cfg{winhelp-topic}{ssh.tunnels.portfwd}
+
+The Tunnels panel allows you to configure tunnelling of arbitrary
+connection types through an SSH connection.
+
+Port forwarding allows you to tunnel other types of \i{network
+connection} down an SSH session. See \k{using-port-forwarding} for a
+general discussion of port forwarding and how it works.
+
+The port forwarding section in the Tunnels panel shows a list of all
+the port forwardings that PuTTY will try to set up when it connects
+to the server. By default no port forwardings are set up, so this
+list is empty.
+
+To add a port forwarding:
+
+\b Set one of the \q{Local} or \q{Remote} radio buttons, depending
+on whether you want to \I{local port forwarding}forward a local port
+to a remote destination (\q{Local}) or \I{remote port forwarding}forward
+a remote port to a local destination (\q{Remote}). Alternatively,
+select \q{Dynamic} if you want PuTTY to \I{dynamic port forwarding}provide
+a local SOCKS 4/4A/5 proxy on a local port (note that this proxy only
+supports TCP connections; the SSH protocol does not support forwarding
+\i{UDP}).
+
+\b Enter a source \i{port number} into the \q{Source port} box. For
+local forwardings, PuTTY will listen on this port of your PC. For
+remote forwardings, your SSH server will listen on this port of the
+remote machine. Note that most servers will not allow you to listen
+on \I{privileged port}port numbers less than 1024.
+
+\b If you have selected \q{Local} or \q{Remote} (this step is not
+needed with \q{Dynamic}), enter a hostname and port number separated
+by a colon, in the \q{Destination} box. Connections received on the
+source port will be directed to this destination. For example, to
+connect to a POP-3 server, you might enter
+\c{popserver.example.com:110}.
+
+\b Click the \q{Add} button. Your forwarding details should appear
+in the list box.
+
+To remove a port forwarding, simply select its details in the list
+box, and click the \q{Remove} button.
+
+In the \q{Source port} box, you can also optionally enter an \I{listen
+address}IP address to listen on, by specifying (for instance)
+\c{127.0.0.5:79}.
+See \k{using-port-forwarding} for more information on how this
+works and its restrictions.
+
+In place of port numbers, you can enter \i{service names}, if they are
+known to the local system. For instance, in the \q{Destination} box,
+you could enter \c{popserver.example.com:pop3}.
+
+You can \I{port forwarding, changing mid-session}modify the currently
+active set of port forwardings in mid-session using \q{Change
+Settings} (see \k{using-changesettings}). If you delete a local or
+dynamic port forwarding in mid-session, PuTTY will stop listening for
+connections on that port, so it can be re-used by another program. If
+you delete a remote port forwarding, note that:
+
+\b The SSH-1 protocol contains no mechanism for asking the server to
+stop listening on a remote port.
+
+\b The SSH-2 protocol does contain such a mechanism, but not all SSH
+servers support it. (In particular, \i{OpenSSH} does not support it in
+any version earlier than 3.9.)
+
+If you ask to delete a remote port forwarding and PuTTY cannot make
+the server actually stop listening on the port, it will instead just
+start refusing incoming connections on that port. Therefore,
+although the port cannot be reused by another program, you can at
+least be reasonably sure that server-side programs can no longer
+access the service at your end of the port forwarding.
+
+If you delete a forwarding, any existing connections established using
+that forwarding remain open. Similarly, changes to global settings
+such as \q{Local ports accept connections from other hosts} only take
+effect on new forwardings.
+
+If the connection you are forwarding over SSH is itself a second SSH
+connection made by another copy of PuTTY, you might find the
+\q{logical host name} configuration option useful to warn PuTTY of
+which host key it should be expecting. See \k{config-loghost} for
+details of this.
+
+\S{config-ssh-portfwd-localhost} Controlling the visibility of
+forwarded ports
+
+\cfg{winhelp-topic}{ssh.tunnels.portfwd.localhost}
+
+The source port for a forwarded connection usually does not accept
+connections from any machine except the \I{localhost}SSH client or
+server machine itself (for local and remote forwardings respectively).
+There are controls in the Tunnels panel to change this:
+
+\b The \q{Local ports accept connections from other hosts} option
+allows you to set up local-to-remote port forwardings in such a way
+that machines other than your client PC can connect to the forwarded
+port. (This also applies to dynamic SOCKS forwarding.)
+
+\b The \q{Remote ports do the same} option does the same thing for
+remote-to-local port forwardings (so that machines other than the
+SSH server machine can connect to the forwarded port.) Note that
+this feature is only available in the SSH-2 protocol, and not all
+SSH-2 servers support it (\i{OpenSSH} 3.0 does not, for example).
+
+\S{config-ssh-portfwd-address-family} Selecting \i{Internet protocol
+version} for forwarded ports
+
+\cfg{winhelp-topic}{ssh.tunnels.portfwd.ipversion}
+
+This switch allows you to select a specific Internet protocol (\i{IPv4}
+or \i{IPv6}) for the local end of a forwarded port. By default, it is
+set on \q{Auto}, which means that:
+
+\b for a local-to-remote port forwarding, PuTTY will listen for
+incoming connections in both IPv4 and (if available) IPv6
+
+\b for a remote-to-local port forwarding, PuTTY will choose a
+sensible protocol for the outgoing connection.
+
+This overrides the general Internet protocol version preference
+on the Connection panel (see \k{config-address-family}).
+
+Note that some operating systems may listen for incoming connections
+in IPv4 even if you specifically asked for IPv6, because their IPv4
+and IPv6 protocol stacks are linked together. Apparently \i{Linux} does
+this, and Windows does not. So if you're running PuTTY on Windows
+and you tick \q{IPv6} for a local or dynamic port forwarding, it
+will \e{only} be usable by connecting to it using IPv6; whereas if
+you do the same on Linux, you can also use it with IPv4. However,
+ticking \q{Auto} should always give you a port which you can connect
+to using either protocol.
+
+\H{config-ssh-bugs} \I{SSH server bugs}The Bugs panel
+
+Not all SSH servers work properly. Various existing servers have
+bugs in them, which can make it impossible for a client to talk to
+them unless it knows about the bug and works around it.
+
+Since most servers announce their software version number at the
+beginning of the SSH connection, PuTTY will attempt to detect which
+bugs it can expect to see in the server and automatically enable
+workarounds. However, sometimes it will make mistakes; if the server
+has been deliberately configured to conceal its version number, or
+if the server is a version which PuTTY's bug database does not know
+about, then PuTTY will not know what bugs to expect.
+
+The Bugs panel allows you to manually configure the bugs PuTTY
+expects to see in the server. Each bug can be configured in three
+states:
+
+\b \q{Off}: PuTTY will assume the server does not have the bug.
+
+\b \q{On}: PuTTY will assume the server \e{does} have the bug.
+
+\b \q{Auto}: PuTTY will use the server's version number announcement
+to try to guess whether or not the server has the bug.
+
+\S{config-ssh-bug-ignore1} \q{Chokes on SSH-1 \i{ignore message}s}
+
+\cfg{winhelp-topic}{ssh.bugs.ignore1}
+
+An ignore message (SSH_MSG_IGNORE) is a message in the SSH protocol
+which can be sent from the client to the server, or from the server
+to the client, at any time. Either side is required to ignore the
+message whenever it receives it. PuTTY uses ignore messages to
+\I{password camouflage}hide the password packet in SSH-1, so that
+a listener cannot tell the length of the user's password; it also
+uses ignore messages for connection \i{keepalives} (see
+\k{config-keepalive}).
+
+If this bug is detected, PuTTY will stop using ignore messages. This
+means that keepalives will stop working, and PuTTY will have to fall
+back to a secondary defence against SSH-1 password-length
+eavesdropping. See \k{config-ssh-bug-plainpw1}. If this bug is
+enabled when talking to a correct server, the session will succeed,
+but keepalives will not work and the session might be more
+vulnerable to eavesdroppers than it could be.
+
+\S{config-ssh-bug-plainpw1} \q{Refuses all SSH-1 \i{password camouflage}}
+
+\cfg{winhelp-topic}{ssh.bugs.plainpw1}
+
+When talking to an SSH-1 server which cannot deal with ignore
+messages (see \k{config-ssh-bug-ignore1}), PuTTY will attempt to
+disguise the length of the user's password by sending additional
+padding \e{within} the password packet. This is technically a
+violation of the SSH-1 specification, and so PuTTY will only do it
+when it cannot use standards-compliant ignore messages as
+camouflage. In this sense, for a server to refuse to accept a padded
+password packet is not really a bug, but it does make life
+inconvenient if the server can also not handle ignore messages.
+
+If this \q{bug} is detected, PuTTY will assume that neither ignore
+messages nor padding are acceptable, and that it thus has no choice
+but to send the user's password with no form of camouflage, so that
+an eavesdropping user will be easily able to find out the exact length
+of the password. If this bug is enabled when talking to a correct
+server, the session will succeed, but will be more vulnerable to
+eavesdroppers than it could be.
+
+This is an SSH-1-specific bug. SSH-2 is secure against this type of
+attack.
+
+\S{config-ssh-bug-rsa1} \q{Chokes on SSH-1 \i{RSA} authentication}
+
+\cfg{winhelp-topic}{ssh.bugs.rsa1}
+
+Some SSH-1 servers cannot deal with RSA authentication messages at
+all. If \i{Pageant} is running and contains any SSH-1 keys, PuTTY will
+normally automatically try RSA authentication before falling back to
+passwords, so these servers will crash when they see the RSA attempt.
+
+If this bug is detected, PuTTY will go straight to password
+authentication. If this bug is enabled when talking to a correct
+server, the session will succeed, but of course RSA authentication
+will be impossible.
+
+This is an SSH-1-specific bug.
+
+\S{config-ssh-bug-ignore2} \q{Chokes on SSH-2 \i{ignore message}s}
+
+\cfg{winhelp-topic}{ssh.bugs.ignore2}
+
+An ignore message (SSH_MSG_IGNORE) is a message in the SSH protocol
+which can be sent from the client to the server, or from the server
+to the client, at any time. Either side is required to ignore the
+message whenever it receives it. PuTTY uses ignore messages in SSH-2
+to confuse the encrypted data stream and make it harder to
+cryptanalyse. It also uses ignore messages for connection
+\i{keepalives} (see \k{config-keepalive}).
+
+If it believes the server to have this bug, PuTTY will stop using
+ignore messages. If this bug is enabled when talking to a correct
+server, the session will succeed, but keepalives will not work and
+the session might be less cryptographically secure than it could be.
+
+\S{config-ssh-bug-hmac2} \q{Miscomputes SSH-2 HMAC keys}
+
+\cfg{winhelp-topic}{ssh.bugs.hmac2}
+
+Versions 2.3.0 and below of the SSH server software from
+\cw{ssh.com} compute the keys for their \i{HMAC} \i{message authentication
+code}s incorrectly. A typical symptom of this problem is that PuTTY
+dies unexpectedly at the beginning of the session, saying
+\q{Incorrect MAC received on packet}.
+
+If this bug is detected, PuTTY will compute its HMAC keys in the
+same way as the buggy server, so that communication will still be
+possible. If this bug is enabled when talking to a correct server,
+communication will fail.
+
+This is an SSH-2-specific bug.
+
+\S{config-ssh-bug-derivekey2} \q{Miscomputes SSH-2 \i{encryption} keys}
+
+\cfg{winhelp-topic}{ssh.bugs.derivekey2}
+
+Versions below 2.0.11 of the SSH server software from \i\cw{ssh.com}
+compute the keys for the session encryption incorrectly. This
+problem can cause various error messages, such as \q{Incoming packet
+was garbled on decryption}, or possibly even \q{Out of memory}.
+
+If this bug is detected, PuTTY will compute its encryption keys in
+the same way as the buggy server, so that communication will still
+be possible. If this bug is enabled when talking to a correct
+server, communication will fail.
+
+This is an SSH-2-specific bug.
+
+\S{config-ssh-bug-sig} \q{Requires padding on SSH-2 \i{RSA} \i{signatures}}
+
+\cfg{winhelp-topic}{ssh.bugs.rsapad2}
+
+Versions below 3.3 of \i{OpenSSH} require SSH-2 RSA signatures to be
+padded with zero bytes to the same length as the RSA key modulus.
+The SSH-2 specification says that an unpadded signature MUST be
+accepted, so this is a bug. A typical symptom of this problem is
+that PuTTY mysteriously fails RSA authentication once in every few
+hundred attempts, and falls back to passwords.
+
+If this bug is detected, PuTTY will pad its signatures in the way
+OpenSSH expects. If this bug is enabled when talking to a correct
+server, it is likely that no damage will be done, since correct
+servers usually still accept padded signatures because they're used
+to talking to OpenSSH.
+
+This is an SSH-2-specific bug.
+
+\S{config-ssh-bug-pksessid2} \q{Misuses the \i{session ID} in SSH-2 PK auth}
+
+\cfg{winhelp-topic}{ssh.bugs.pksessid2}
+
+Versions below 2.3 of \i{OpenSSH} require SSH-2 \i{public-key authentication}
+to be done slightly differently: the data to be signed by the client
+contains the session ID formatted in a different way. If public-key
+authentication mysteriously does not work but the Event Log (see
+\k{using-eventlog}) thinks it has successfully sent a signature, it
+might be worth enabling the workaround for this bug to see if it
+helps.
+
+If this bug is detected, PuTTY will sign data in the way OpenSSH
+expects. If this bug is enabled when talking to a correct server,
+SSH-2 public-key authentication will fail.
+
+This is an SSH-2-specific bug.
+
+\S{config-ssh-bug-rekey} \q{Handles SSH-2 key re-exchange badly}
+
+\cfg{winhelp-topic}{ssh.bugs.rekey2}
+
+Some SSH servers cannot cope with \i{repeat key exchange} at
+all, and will ignore attempts by the client to start one. Since
+PuTTY pauses the session while performing a repeat key exchange, the
+effect of this would be to cause the session to hang after an hour
+(unless you have your rekey timeout set differently; see
+\k{config-ssh-kex-rekey} for more about rekeys).
+Other, very old, SSH servers handle repeat key exchange even more
+badly, and disconnect upon receiving a repeat key exchange request.
+
+If this bug is detected, PuTTY will never initiate a repeat key
+exchange. If this bug is enabled when talking to a correct server,
+the session should still function, but may be less secure than you
+would expect.
+
+This is an SSH-2-specific bug.
+
+\S{config-ssh-bug-maxpkt2} \q{Ignores SSH-2 \i{maximum packet size}}
+
+\cfg{winhelp-topic}{ssh.bugs.maxpkt2}
+
+When an SSH-2 channel is set up, each end announces the maximum size
+of data packet that it is willing to receive for that channel. Some
+servers ignore PuTTY's announcement and send packets larger than PuTTY
+is willing to accept, causing it to report \q{Incoming packet was
+garbled on decryption}.
+
+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.
+
+\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}.
+
+\S{config-serial-line} Selecting a serial line to connect to
+
+\cfg{winhelp-topic}{serial.line}
+
+The \q{Serial line to connect to} box allows you to choose which
+serial line you want PuTTY to talk to, if your computer has more
+than one serial port.
+
+On Windows, the first serial line is called \i\cw{COM1}, and if there
+is a second it is called \cw{COM2}, and so on.
+
+This configuration setting is also visible on the Session panel,
+where it replaces the \q{Host Name} box (see \k{config-hostname}) if
+the connection type is set to \q{Serial}.
+
+\S{config-serial-speed} Selecting the speed of your serial line
+
+\cfg{winhelp-topic}{serial.speed}
+
+The \q{Speed} box allows you to choose the speed (or \q{baud rate})
+at which to talk to the serial line. Typical values might be 9600,
+19200, 38400 or 57600. Which one you need will depend on the device
+at the other end of the serial cable; consult the manual for that
+device if you are in doubt.
+
+This configuration setting is also visible on the Session panel,
+where it replaces the \q{Port} box (see \k{config-hostname}) if the
+connection type is set to \q{Serial}.
+
+\S{config-serial-databits} Selecting the number of data bits
+
+\cfg{winhelp-topic}{serial.databits}
+
+The \q{Data bits} box allows you to choose how many data bits are
+transmitted in each byte sent or received through the serial line.
+Typical values are 7 or 8.
+
+\S{config-serial-stopbits} Selecting the number of stop bits
+
+\cfg{winhelp-topic}{serial.stopbits}
+
+The \q{Stop bits} box allows you to choose how many stop bits are
+used in the serial line protocol. Typical values are 1, 1.5 or 2.
+
+\S{config-serial-parity} Selecting the serial parity checking scheme
+
+\cfg{winhelp-topic}{serial.parity}
+
+The \q{Parity} box allows you to choose what type of parity checking
+is used on the serial line. The settings are:
+
+\b \q{None}: no parity bit is sent at all.
+
+\b \q{Odd}: an extra parity bit is sent alongside each byte, and
+arranged so that the total number of 1 bits is odd.
+
+\b \q{Even}: an extra parity bit is sent alongside each byte, and
+arranged so that the total number of 1 bits is even.
+
+\b \q{Mark}: an extra parity bit is sent alongside each byte, and
+always set to 1.
+
+\b \q{Space}: an extra parity bit is sent alongside each byte, and
+always set to 0.
+
+\S{config-serial-flow} Selecting the serial flow control scheme
+
+\cfg{winhelp-topic}{serial.flow}
+
+The \q{Flow control} box allows you to choose what type of flow
+control checking is used on the serial line. The settings are:
+
+\b \q{None}: no flow control is done. Data may be lost if either
+side attempts to send faster than the serial line permits.
+
+\b \q{XON/XOFF}: flow control is done by sending XON and XOFF
+characters within the data stream.
+
+\b \q{RTS/CTS}: flow control is done using the RTS and CTS wires on
+the serial line.
+
+\b \q{DSR/DTR}: flow control is done using the DSR and DTR wires on
+the serial line.
+
+\H{config-file} \ii{Storing configuration in a file}
+
+PuTTY does not currently support storing its configuration in a file
+instead of the \i{Registry}. However, you can work around this with a
+couple of \i{batch file}s.
+
+You will need a file called (say) \c{PUTTY.BAT} which imports the
+contents of a file into the Registry, then runs PuTTY, exports the
+contents of the Registry back into the file, and deletes the
+Registry entries. This can all be done using the Regedit command
+line options, so it's all automatic. Here is what you need in
+\c{PUTTY.BAT}:
+
+\c @ECHO OFF
+\c regedit /s putty.reg
+\c regedit /s puttyrnd.reg
+\c start /w putty.exe
+\c regedit /ea new.reg HKEY_CURRENT_USER\Software\SimonTatham\PuTTY
+\c copy new.reg putty.reg
+\c del new.reg
+\c regedit /s puttydel.reg
+
+This batch file needs two auxiliary files: \c{PUTTYRND.REG} which
+sets up an initial safe location for the \c{PUTTY.RND} random seed
+file, and \c{PUTTYDEL.REG} which destroys everything in the Registry
+once it's been successfully saved back to the file.
+
+Here is \c{PUTTYDEL.REG}:
+
+\c REGEDIT4
+\c
+\c [-HKEY_CURRENT_USER\Software\SimonTatham\PuTTY]
+
+Here is an example \c{PUTTYRND.REG} file:
+
+\c REGEDIT4
+\c
+\c [HKEY_CURRENT_USER\Software\SimonTatham\PuTTY]
+\c "RandSeedFile"="a:\\putty.rnd"
+
+You should replace \c{a:\\putty.rnd} with the location where you
+want to store your random number data. If the aim is to carry around
+PuTTY and its settings on one floppy, you probably want to store it
+on the floppy.
--- /dev/null
+\define{versioniderrors} \versionid $Id$
+
+\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
+more detail.
+
+We do not attempt to list \e{all} error messages here: there are
+many which should never occur, and some which should be
+self-explanatory. If you get an error message which is not listed in
+this chapter and which you don't understand, report it to us as a
+bug (see \k{feedback}) and we will add documentation for it.
+
+\H{errors-hostkey-absent} \q{The server's host key is not cached in
+the registry}
+
+\cfg{winhelp-topic}{errors.hostkey.absent}
+
+This error message occurs when PuTTY connects to a new SSH server.
+Every server identifies itself by means of a host key; once PuTTY
+knows the host key for a server, it will be able to detect if a
+malicious attacker redirects your connection to another machine.
+
+If you see this message, it means that PuTTY has not seen this host
+key before, and has no way of knowing whether it is correct or not.
+You should attempt to verify the host key by other means, such as
+asking the machine's administrator.
+
+If you see this message and you know that your installation of PuTTY
+\e{has} connected to the same server before, it may have been
+recently upgraded to SSH protocol version 2. SSH protocols 1 and 2
+use separate host keys, so when you first use \i{SSH-2} with a server
+you have only used SSH-1 with before, you will see this message
+again. You should verify the correctness of the key as before.
+
+See \k{gs-hostkey} for more information on host keys.
+
+\H{errors-hostkey-wrong} \q{WARNING - POTENTIAL SECURITY BREACH!}
+
+\cfg{winhelp-topic}{errors.hostkey.changed}
+
+This message, followed by \q{The server's host key does not match
+the one PuTTY has cached in the registry}, means that PuTTY has
+connected to the SSH server before, knows what its host key
+\e{should} be, but has found a different one.
+
+This may mean that a malicious attacker has replaced your server
+with a different one, or has redirected your network connection to
+their own machine. On the other hand, it may simply mean that the
+administrator of your server has accidentally changed the key while
+upgrading the SSH software; this \e{shouldn't} happen but it is
+unfortunately possible.
+
+You should contact your server's administrator and see whether they
+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
+puts up this warning only for \ii{single-DES} and \i{Arcfour} encryption.
+
+See \k{config-ssh-encryption} for more information on this message.
+
+\H{errors-toomanyauth} \q{Server sent disconnect message type 2
+(protocol error): "Too many authentication failures for root"}
+
+This message is produced by an \i{OpenSSH} (or \i{Sun SSH}) server if it
+receives more failed authentication attempts than it is willing to
+tolerate.
+
+This can easily happen if you are using Pageant and have a
+large number of keys loaded into it, since these servers count each
+offer of a public key as an authentication attempt. This can be worked
+around by specifying the key that's required for the authentication in
+the PuTTY configuration (see \k{config-ssh-privkey}); PuTTY will ignore
+any other keys Pageant may have, but will ask Pageant to do the
+authentication, so that you don't have to type your passphrase.
+
+On the server, this can be worked around by disabling public-key
+authentication or (for Sun SSH only) by increasing \c{MaxAuthTries} in
+\c{sshd_config}.
+
+\H{errors-memory} \q{\ii{Out of memory}}
+
+This occurs when PuTTY tries to allocate more memory than the system
+can give it. This \e{may} happen for genuine reasons: if the
+computer really has run out of memory, or if you have configured an
+extremely large number of lines of scrollback in your terminal.
+PuTTY is not able to recover from running out of memory; it will
+terminate immediately after giving this error.
+
+However, this error can also occur when memory is not running out at
+all, because PuTTY receives data in the wrong format. In SSH-2 and
+also in SFTP, the server sends the length of each message before the
+message itself; so PuTTY will receive the length, try to allocate
+space for the message, and then receive the rest of the message. If
+the length PuTTY receives is garbage, it will try to allocate a
+ridiculous amount of memory, and will terminate with an \q{Out of
+memory} error.
+
+This can happen in SSH-2, if PuTTY and the server have not enabled
+encryption in the same way (see \k{faq-outofmem} in the FAQ). Some
+versions of \i{OpenSSH} have a known problem with this: see
+\k{faq-openssh-bad-openssl}.
+
+This can also happen in PSCP or PSFTP, if your \i{login scripts} on the
+server generate output: the client program will be expecting an SFTP
+message starting with a length, and if it receives some text from
+your login scripts instead it will try to interpret them as a
+message length. See \k{faq-outofmem2} for details of this.
+
+\H{errors-internal} \q{\ii{Internal error}}, \q{\ii{Internal fault}},
+\q{\ii{Assertion failed}}
+
+Any error beginning with the word \q{Internal} should \e{never}
+occur. If it does, there is a bug in PuTTY by definition; please see
+\k{feedback} and report it to us.
+
+Similarly, any error message starting with \q{Assertion failed} is a
+bug in PuTTY. Please report it to us, and include the exact text
+from the error message box.
+
+\H{errors-cant-load-key} \q{Unable to use this private key file},
+\q{Couldn't load private key}, \q{Key is of wrong type}
+
+\cfg{winhelp-topic}{errors.cantloadkey}
+
+Various forms of this error are printed in the PuTTY window, or
+written to the PuTTY Event Log (see \k{using-eventlog}) when trying
+public-key authentication, or given by Pageant when trying to load a
+private key.
+
+If you see one of these messages, it often indicates that you've tried
+to load a key of an inappropriate type into PuTTY, Plink, PSCP, PSFTP,
+or Pageant.
+
+You may have specified a key that's inappropriate for the connection
+you're making. The SSH-1 and SSH-2 protocols require different private
+key formats, and a SSH-1 key can't be used for a SSH-2 connection (or
+vice versa).
+
+Alternatively, you may have tried to load an SSH-2 key in a \q{foreign}
+format (OpenSSH or \cw{ssh.com}) directly into one of the PuTTY tools,
+in which case you need to import it into PuTTY's native format
+(\c{*.PPK}) using PuTTYgen - see \k{puttygen-conversions}.
+
+\H{errors-refused} \q{Server refused our public key} or \q{Key
+refused}
+
+Various forms of this error are printed in the PuTTY window, or
+written to the PuTTY Event Log (see \k{using-eventlog}) when trying
+public-key authentication.
+
+If you see one of these messages, it means that PuTTY has sent a
+public key to the server and offered to authenticate with it, and
+the server has refused to accept authentication. This usually means
+that the server is not configured to accept this key to authenticate
+this user.
+
+This is almost certainly not a problem with PuTTY. If you see this
+type of message, the first thing you should do is check your
+\e{server} configuration carefully. Common errors include having
+the wrong permissions or ownership set on the public key or the
+user's home directory on the server. Also, read the PuTTY Event Log;
+the server may have sent diagnostic messages explaining exactly what
+problem it had with your setup.
+
+\K{pubkey-gettingready} has some hints on server-side public key
+setup.
+
+\H{errors-access-denied} \q{Access denied}, \q{Authentication refused}
+
+Various forms of this error are printed in the PuTTY window, or
+written to the PuTTY Event Log (see \k{using-eventlog}) during
+authentication.
+
+If you see one of these messages, it means that the server has refused
+all the forms of authentication PuTTY has tried and it has no further
+ideas.
+
+It may be worth checking the Event Log for diagnostic messages from
+the server giving more detail.
+
+This error can be caused by buggy SSH-1 servers that fail to cope with
+the various strategies we use for camouflaging passwords in transit.
+Upgrade your server, or use the workarounds described in
+\k{config-ssh-bug-ignore1} and possibly \k{config-ssh-bug-plainpw1}.
+
+\H{errors-no-auth} \q{No supported authentication methods available}
+
+This error indicates that PuTTY has run out of ways to authenticate
+you to an SSH server. This may be because PuTTY has TIS or
+keyboard-interactive authentication disabled, in which case
+\k{config-ssh-tis} and \k{config-ssh-ki}.
+
+\H{errors-crc} \q{Incorrect \i{CRC} received on packet} or \q{Incorrect
+\i{MAC} received on packet}
+
+This error occurs when PuTTY decrypts an SSH packet and its checksum
+is not correct. This probably means something has gone wrong in the
+encryption or decryption process. It's difficult to tell from this
+error message whether the problem is in the client, in the server,
+or in between.
+
+In particular, if the network is corrupting data at the TCP level, it
+may only be obvious with cryptographic protocols such as SSH, which
+explicitly check the integrity of the transferred data and complain
+loudly if the checks fail. Corruption of protocols without integrity
+protection (such as HTTP) will manifest in more subtle failures (such
+as misdisplayed text or images in a web browser) which may not be
+noticed.
+
+A known server problem which can cause this error is described in
+\k{faq-openssh-bad-openssl} in the FAQ.
+
+\H{errors-garbled} \q{Incoming packet was garbled on decryption}
+
+This error occurs when PuTTY decrypts an SSH packet and the
+decrypted data makes no sense. This probably means something has
+gone wrong in the encryption or decryption process. It's difficult
+to tell from this error message whether the problem is in the client,
+in the server, or in between.
+
+If you get this error, one thing you could try would be to fiddle with
+the setting of \q{Miscomputes SSH-2 encryption keys} (see
+\k{config-ssh-bug-derivekey2}) or \q{Ignores SSH-2 maximum packet
+size} (see \k{config-ssh-bug-maxpkt2}) on the Bugs panel .
+
+Another known server problem which can cause this error is described
+in \k{faq-openssh-bad-openssl} in the FAQ.
+
+\H{errors-x11-proxy} \q{PuTTY X11 proxy: \e{various errors}}
+
+This family of errors are reported when PuTTY is doing X forwarding.
+They are sent back to the X application running on the SSH server,
+which will usually report the error to the user.
+
+When PuTTY enables X forwarding (see \k{using-x-forwarding}) it
+creates a virtual X display running on the SSH server. This display
+requires authentication to connect to it (this is how PuTTY prevents
+other users on your server machine from connecting through the PuTTY
+proxy to your real X display). PuTTY also sends the server the
+details it needs to enable clients to connect, and the server should
+put this mechanism in place automatically, so your X applications
+should just work.
+
+A common reason why people see one of these messages is because they
+used SSH to log in as one user (let's say \q{fred}), and then used
+the Unix \c{su} command to become another user (typically \q{root}).
+The original user, \q{fred}, has access to the X authentication data
+provided by the SSH server, and can run X applications which are
+forwarded over the SSH connection. However, the second user
+(\q{root}) does not automatically have the authentication data
+passed on to it, so attempting to run an X application as that user
+often fails with this error.
+
+If this happens, \e{it is not a problem with PuTTY}. You need to
+arrange for your X authentication data to be passed from the user
+you logged in as to the user you used \c{su} to become. How you do
+this depends on your particular system; in fact many modern versions
+of \c{su} do it automatically.
+
+\H{errors-connaborted} \q{Network error: Software caused connection
+abort}
+
+This is a generic error produced by the Windows network code when it
+kills an established connection for some reason. For example, it might
+happen if you pull the network cable out of the back of an
+Ethernet-connected computer, or if Windows has any other similar
+reason to believe the entire network has become unreachable.
+
+Windows also generates this error if it has given up on the machine
+at the other end of the connection ever responding to it. If the
+network between your client and server goes down and your client
+then tries to send some data, Windows will make several attempts to
+send the data and will then give up and kill the connection. In
+particular, this can occur even if you didn't type anything, if you
+are using SSH-2 and PuTTY attempts a key re-exchange. (See
+\k{config-ssh-kex-rekey} for more about key re-exchange.)
+
+(It can also occur if you are using keepalives in your connection.
+Other people have reported that keepalives \e{fix} this error for
+them. See \k{config-keepalive} for a discussion of the pros and cons
+of keepalives.)
+
+We are not aware of any reason why this error might occur that would
+represent a bug in PuTTY. The problem is between you, your Windows
+system, your network and the remote system.
+
+\H{errors-connreset} \q{Network error: Connection reset by peer}
+
+This error occurs when the machines at each end of a network
+connection lose track of the state of the connection between them.
+For example, you might see it if your SSH server crashes, and
+manages to reboot fully before you next attempt to send data to it.
+
+However, the most common reason to see this message is if you are
+connecting through a \i{firewall} or a \i{NAT router} which has timed the
+connection out. See \k{faq-idleout} in the FAQ for more details. You
+may be able to improve the situation by using keepalives; see
+\k{config-keepalive} for details on this.
+
+Note that Windows can produce this error in some circumstances without
+seeing a connection reset from the server, for instance if the
+connection to the network is lost.
+
+\H{errors-connrefused} \q{Network error: Connection refused}
+
+This error means that the network connection PuTTY tried to make to
+your server was rejected by the server. Usually this happens because
+the server does not provide the service which PuTTY is trying to
+access.
+
+Check that you are connecting with the correct protocol (SSH, Telnet
+or Rlogin), and check that the port number is correct. If that
+fails, consult the administrator of your server.
+
+\H{errors-conntimedout} \q{Network error: Connection timed out}
+
+This error means that the network connection PuTTY tried to make to
+your server received no response at all from the server. Usually
+this happens because the server machine is completely isolated from
+the network, or because it is turned off.
+
+Check that you have correctly entered the host name or IP address of
+your server machine. If that fails, consult the administrator of
+your server.
+
+\i{Unix} also generates this error when it tries to send data down a
+connection and contact with the server has been completely lost
+during a connection. (There is a delay of minutes before Unix gives
+up on receiving a reply from the server.) This can occur if you type
+things into PuTTY while the network is down, but it can also occur
+if PuTTY decides of its own accord to send data: due to a repeat key
+exchange in SSH-2 (see \k{config-ssh-kex-rekey}) or due to
+keepalives (\k{config-keepalive}).
--- /dev/null
+\define{versionidfaq} \versionid $Id$
+
+\A{faq} PuTTY \i{FAQ}
+
+This FAQ is published on the PuTTY web site, and also provided as an
+appendix in the manual.
+
+\H{faq-intro} Introduction
+
+\S{faq-what}{Question} What is PuTTY?
+
+PuTTY is a client program for the SSH, Telnet and Rlogin network
+protocols.
+
+These protocols are all used to run a remote session on a computer,
+over a network. PuTTY implements the client end of that session: the
+end at which the session is displayed, rather than the end at which
+it runs.
+
+In really simple terms: you run PuTTY on a Windows machine, and tell
+it to connect to (for example) a Unix machine. PuTTY opens a window.
+Then, anything you type into that window is sent straight to the
+Unix machine, and everything the Unix machine sends back is
+displayed in the window. So you can work on the Unix machine as if
+you were sitting at its console, while actually sitting somewhere
+else.
+
+\H{faq-support} Features supported in PuTTY
+
+\I{supported features}In general, if you want to know if PuTTY supports
+a particular feature, you should look for it on the
+\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/}{PuTTY web site}.
+In particular:
+
+\b try the
+\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/changes.html}{changes
+page}, and see if you can find the feature on there. If a feature is
+listed there, it's been implemented. If it's listed as a change made
+\e{since} the latest version, it should be available in the
+development snapshots, in which case testing will be very welcome.
+
+\b try the
+\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/wishlist/}{Wishlist
+page}, and see if you can find the feature there. If it's on there,
+and not in the \q{Recently fixed} section, it probably \e{hasn't} been
+implemented.
+
+\S{faq-ssh2}{Question} Does PuTTY support SSH-2?
+
+Yes. SSH-2 support has been available in PuTTY since version 0.50.
+
+Public key authentication (both RSA and DSA) in SSH-2 is new in
+version 0.52.
+
+\S{faq-ssh2-keyfmt}{Question} Does PuTTY support reading OpenSSH or
+\cw{ssh.com} SSH-2 private key files?
+
+PuTTY doesn't support this natively (see
+\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/wishlist/key-formats-natively.html}{the wishlist entry}
+for reasons why not), but as of 0.53
+PuTTYgen can convert both OpenSSH and \cw{ssh.com} private key
+files into PuTTY's format.
+
+\S{faq-ssh1}{Question} Does PuTTY support SSH-1?
+
+Yes. SSH-1 support has always been available in PuTTY.
+
+\S{faq-localecho}{Question} Does PuTTY support \i{local echo}?
+
+Yes. Version 0.52 has proper support for local echo.
+
+In version 0.51 and before, local echo could not be separated from
+local line editing (where you type a line of text locally, and it is
+not sent to the server until you press Return, so you have the
+chance to edit it and correct mistakes \e{before} the server sees
+it). New in version 0.52, local echo and local line editing are
+separate options, and by default PuTTY will try to determine
+automatically whether to enable them or not, based on which protocol
+you have selected and also based on hints from the server. If you
+have a problem with PuTTY's default choice, you can force each
+option to be enabled or disabled as you choose. The controls are in
+the Terminal panel, in the section marked \q{Line discipline
+options}.
+
+\S{faq-savedsettings}{Question} Does PuTTY support storing settings,
+so I don't have to change them every time?
+
+Yes, all of PuTTY's settings can be saved in named session profiles.
+You can also change the default settings that are used for new sessions.
+See \k{config-saving} in the documentation for how to do this.
+
+\S{faq-disksettings}{Question} Does PuTTY support storing its
+settings in a disk file?
+
+Not at present, although \k{config-file} in the documentation gives
+a method of achieving the same effect.
+
+\S{faq-fullscreen}{Question} Does PuTTY support full-screen mode,
+like a DOS box?
+
+Yes; this is a new feature in version 0.52.
+
+\S{faq-password-remember}{Question} Does PuTTY have the ability to
+\i{remember my password} so I don't have to type it every time?
+
+No, it doesn't.
+
+Remembering your password is a bad plan for obvious security
+reasons: anyone who gains access to your machine while you're away
+from your desk can find out the remembered password, and use it,
+abuse it or change it.
+
+In addition, it's not even \e{possible} for PuTTY to automatically
+send your password in a Telnet session, because Telnet doesn't give
+the client software any indication of which part of the login
+process is the password prompt. PuTTY would have to guess, by
+looking for words like \q{password} in the session data; and if your
+login program is written in something other than English, this won't
+work.
+
+In SSH, remembering your password would be possible in theory, but
+there doesn't seem to be much point since SSH supports public key
+authentication, which is more flexible and more secure. See
+\k{pubkey} in the documentation for a full discussion of public key
+authentication.
+
+\S{faq-hostkeys}{Question} Is there an option to turn off the
+\I{verifying the host key}annoying host key prompts?
+
+No, there isn't. And there won't be. Even if you write it yourself
+and send us the patch, we won't accept it.
+
+Those annoying host key prompts are the \e{whole point} of SSH.
+Without them, all the cryptographic technology SSH uses to secure
+your session is doing nothing more than making an attacker's job
+slightly harder; instead of sitting between you and the server with
+a packet sniffer, the attacker must actually subvert a router and
+start modifying the packets going back and forth. But that's not all
+that much harder than just sniffing; and without host key checking,
+it will go completely undetected by client or server.
+
+Host key checking is your guarantee that the encryption you put on
+your data at the client end is the \e{same} encryption taken off the
+data at the server end; it's your guarantee that it hasn't been
+removed and replaced somewhere on the way. Host key checking makes
+the attacker's job \e{astronomically} hard, compared to packet
+sniffing, and even compared to subverting a router. Instead of
+applying a little intelligence and keeping an eye on Bugtraq, the
+attacker must now perform a brute-force attack against at least one
+military-strength cipher. That insignificant host key prompt really
+does make \e{that} much difference.
+
+If you're having a specific problem with host key checking - perhaps
+you want an automated batch job to make use of PSCP or Plink, and
+the interactive host key prompt is hanging the batch process - then
+the right way to fix it is to add the correct host key to the
+Registry in advance. That way, you retain the \e{important} feature
+of host key checking: the right key will be accepted and the wrong
+ones will not. Adding an option to turn host key checking off
+completely is the wrong solution and we will not do it.
+
+If you have host keys available in the common \i\c{known_hosts} format,
+we have a script called
+\W{http://svn.tartarus.org/sgt/putty/contrib/kh2reg.py?view=markup}\c{kh2reg.py}
+to convert them to a Windows .REG file, which can be installed ahead of
+time by double-clicking or using \c{REGEDIT}.
+
+\S{faq-server}{Question} Will you write an SSH server for the PuTTY
+suite, to go with the client?
+
+No. The only reason we might want to would be if we could easily
+re-use existing code and significantly cut down the effort. We don't
+believe this is the case; there just isn't enough common ground
+between an SSH client and server to make it worthwhile.
+
+If someone else wants to use bits of PuTTY in the process of writing
+a Windows SSH server, they'd be perfectly welcome to of course, but
+I really can't see it being a lot less effort for us to do that than
+it would be for us to write a server from the ground up. We don't
+have time, and we don't have motivation. The code is available if
+anyone else wants to try it.
+
+\S{faq-pscp-ascii}{Question} Can PSCP or PSFTP transfer files in
+\i{ASCII} mode?
+
+Unfortunately not.
+
+Until recently, this was a limitation of the file transfer protocols:
+the SCP and SFTP protocols had no notion of transferring a file in
+anything other than binary mode. (This is still true of SCP.)
+
+The current draft protocol spec of SFTP proposes a means of
+implementing ASCII transfer. At some point PSCP/PSFTP may implement
+this proposal.
+
+\H{faq-ports} Ports to other operating systems
+
+The eventual goal is for PuTTY to be a multi-platform program, able
+to run on at least Windows, Mac OS and Unix.
+
+Porting will become easier once PuTTY has a generalised porting
+layer, drawing a clear line between platform-dependent and
+platform-independent code. The general intention was for this
+porting layer to evolve naturally as part of the process of doing
+the first port; a Unix port has now been released and the plan
+seems to be working so far.
+
+\S{faq-ports-general}{Question} What ports of PuTTY exist?
+
+Currently, release versions of PuTTY tools only run on full Win32
+systems and Unix. \q{\i{Win32}} includes versions of Windows from
+Windows 95 onwards (as opposed to the 16-bit Windows 3.1; see
+\k{faq-win31}), up to and including Windows 7; and we know of no
+reason why PuTTY should not continue to work on future versions
+of Windows.
+
+The Windows executables we provide are for the 32-bit \q{\i{x86}}
+processor architecture, but they should work fine on 64-bit
+processors that are backward-compatible with that architecture.
+(We used to also provide executables for Windows for the Alpha
+processor, but stopped after 0.58 due to lack of interest.)
+
+In the development code, partial ports to the Mac OSes exist (see
+\k{faq-mac-port}).
+
+Currently PuTTY does \e{not} run on Windows CE (see \k{faq-wince}).
+
+We do not have release-quality ports for any other systems at the
+present time. If anyone told you we had an EPOC port, or an iPaq port,
+or any other port of PuTTY, they were mistaken. We don't.
+
+There are some third-party ports to various platforms, mentioned
+on the
+\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/links.html}{Links page of our website}.
+
+\S{faq-unix}{Question} \I{Unix version}Is there a port to Unix?
+
+As of 0.54, there are Unix ports of most of the traditional PuTTY
+tools, and also one entirely new application.
+
+If you look at the source release, you should find a \c{unix}
+subdirectory. There are a couple of ways of building it,
+including the usual \c{configure}/\c{make}; see the file \c{README}
+in the source distribution. This should build you Unix
+ports of Plink, PuTTY itself, PuTTYgen, PSCP, PSFTP, and also
+\i\c{pterm} - an \cw{xterm}-type program which supports the same
+terminal emulation as PuTTY. We do not yet have a Unix port of
+Pageant.
+
+If you don't have \i{Gtk}, you should still be able to build the
+command-line tools.
+
+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.
+
+\S{faq-unix-why}{Question} What's the point of the Unix port? Unix
+has OpenSSH.
+
+All sorts of little things. \c{pterm} is directly useful to anyone
+who prefers PuTTY's terminal emulation to \c{xterm}'s, which at
+least some people do. Unix Plink has apparently found a niche among
+people who find the complexity of OpenSSL makes OpenSSH hard to
+install (and who don't mind Plink not having as many features). Some
+users want to generate a large number of SSH keys on Unix and then
+copy them all into PuTTY, and the Unix PuTTYgen should allow them to
+automate that conversion process.
+
+There were development advantages as well; porting PuTTY to Unix was
+a valuable path-finding effort for other future ports, and also
+allowed us to use the excellent Linux tool
+\W{http://valgrind.kde.org/}{Valgrind} to help with debugging, which
+has already improved PuTTY's stability on \e{all} platforms.
+
+However, if you're a Unix user and you can see no reason to switch
+from OpenSSH to PuTTY/Plink, then you're probably right. We don't
+expect our Unix port to be the right thing for everybody.
+
+\S{faq-wince}{Question} Will there be a port to Windows CE or PocketPC?
+
+We have done some work on such a port, but it only reached an early
+stage, and certainly not a useful one. It's no longer being actively
+worked on.
+
+However, there's a third-party port at
+\W{http://www.pocketputty.net/}\c{http://www.pocketputty.net/}.
+
+\S{faq-win31}{Question} Is there a port to \i{Windows 3.1}?
+
+PuTTY is a 32-bit application from the ground up, so it won't run on
+Windows 3.1 as a native 16-bit program; and it would be \e{very}
+hard to port it to do so, because of Windows 3.1's vile memory
+allocation mechanisms.
+
+However, it is possible in theory to compile the existing PuTTY
+source in such a way that it will run under \i{Win32s} (an extension to
+Windows 3.1 to let you run 32-bit programs). In order to do this
+you'll need the right kind of C compiler - modern versions of Visual
+C at least have stopped being backwards compatible to Win32s. Also,
+the last time we tried this it didn't work very well.
+
+If you're interested in running PuTTY under Windows 3.1, help and
+testing in this area would be very welcome!
+
+\S{faq-mac-port}{Question} Will there be a port to the \I{Mac OS}Mac?
+
+There are several answers to this question:
+
+\b The Unix/Gtk port is already fully working under Mac OS X as an X11
+application.
+
+\b A native (Cocoa) Mac OS X port has been started. It's just about
+usable, but is of nowhere near release quality yet, and is likely to
+behave in unexpected ways. Currently it's unlikely to be completed
+unless someone steps in to help.
+
+\b A separate port to the classic Mac OS (pre-OSX) is also in
+progress; it too is not ready yet.
+
+\S{faq-epoc}{Question} Will there be a port to EPOC?
+
+I hope so, but given that ports aren't really progressing very fast
+even on systems the developers \e{do} already know how to program
+for, it might be a long time before any of us get round to learning
+a new system and doing the port for that.
+
+However, some of the work has been done by other people; see the
+\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/links.html}{Links page of our website}
+for various third-party ports.
+
+\S{faq-iphone}{Question} Will there be a port to the iPhone?
+
+We have no plans to write such a port ourselves; none of us has an
+iPhone, and developing and publishing applications for it looks
+awkward and expensive. Such a port would probably depend upon the
+stalled Mac OS X port (see \k{faq-mac-port}).
+
+However, there is a third-party SSH client for the iPhone and
+iPod\_Touch called \W{http://www.instantcocoa.com/products/pTerm/}{pTerm},
+which is apparently based on PuTTY. (This is nothing to do with our
+similarly-named \c{pterm}, which is a standalone terminal emulator for
+Unix systems; see \k{faq-unix}.)
+
+\H{faq-embedding} Embedding PuTTY in other programs
+
+\S{faq-dll}{Question} Is the SSH or Telnet code available as a DLL?
+
+No, it isn't. It would take a reasonable amount of rewriting for
+this to be possible, and since the PuTTY project itself doesn't
+believe in DLLs (they make installation more error-prone) none of us
+has taken the time to do it.
+
+Most of the code cleanup work would be a good thing to happen in
+general, so if anyone feels like helping, we wouldn't say no.
+
+See also
+\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/wishlist/dll-frontend.html}{the wishlist entry}.
+
+\S{faq-vb}{Question} Is the SSH or Telnet code available as a Visual
+Basic component?
+
+No, it isn't. None of the PuTTY team uses Visual Basic, and none of
+us has any particular need to make SSH connections from a Visual
+Basic application. In addition, all the preliminary work to turn it
+into a DLL would be necessary first; and furthermore, we don't even
+know how to write VB components.
+
+If someone offers to do some of this work for us, we might consider
+it, but unless that happens I can't see VB integration being
+anywhere other than the very bottom of our priority list.
+
+\S{faq-ipc}{Question} How can I use PuTTY to make an SSH connection
+from within another program?
+
+Probably your best bet is to use Plink, the command-line connection
+tool. If you can start Plink as a second Windows process, and
+arrange for your primary process to be able to send data to the
+Plink process, and receive data from it, through pipes, then you
+should be able to make SSH connections from your program.
+
+This is what CVS for Windows does, for example.
+
+\H{faq-details} Details of PuTTY's operation
+
+\S{faq-term}{Question} What \i{terminal type} does PuTTY use?
+
+For most purposes, PuTTY can be considered to be an \cw{xterm}
+terminal.
+
+PuTTY also supports some terminal \i{control sequences} not supported by
+the real \cw{xterm}: notably the Linux console sequences that
+reconfigure the colour palette, and the title bar control sequences
+used by \i\cw{DECterm} (which are different from the \cw{xterm} ones;
+PuTTY supports both).
+
+By default, PuTTY announces its terminal type to the server as
+\c{xterm}. If you have a problem with this, you can reconfigure it
+to say something else; \c{vt220} might help if you have trouble.
+
+\S{faq-settings}{Question} Where does PuTTY store its data?
+
+On Windows, PuTTY stores most of its data (saved sessions, SSH host
+keys) in the \i{Registry}. The precise location is
+
+\c HKEY_CURRENT_USER\Software\SimonTatham\PuTTY
+
+and within that area, saved sessions are stored under \c{Sessions}
+while host keys are stored under \c{SshHostKeys}.
+
+PuTTY also requires a random number seed file, to improve the
+unpredictability of randomly chosen data needed as part of the SSH
+cryptography. This is stored by default in a file called \i\c{PUTTY.RND};
+this is stored by default in the \q{Application Data} directory,
+or failing that, one of a number of fallback locations. If you
+want to change the location of the random number seed file, you can
+put your chosen pathname in the Registry, at
+
+\c HKEY_CURRENT_USER\Software\SimonTatham\PuTTY\RandSeedFile
+
+You can ask PuTTY to delete all this data; see \k{faq-cleanup}.
+
+On Unix, PuTTY stores all of this data in a directory \cw{~/.putty}.
+
+\H{faq-howto} HOWTO questions
+
+\S{faq-login}{Question} What login name / password should I use?
+
+This is not a question you should be asking \e{us}.
+
+PuTTY is a communications tool, for making connections to other
+computers. We maintain the tool; we \e{don't} administer any computers
+that you're likely to be able to use, in the same way that the people
+who make web browsers aren't responsible for most of the content you can
+view in them. \#{FIXME: less technical analogy?} We cannot help with
+questions of this sort.
+
+If you know the name of the computer you want to connect to, but don't
+know what login name or password to use, you should talk to whoever
+administers that computer. If you don't know who that is, see the next
+question for some possible ways to find out.
+
+\# FIXME: some people ask us to provide them with a login name
+apparently as random members of the public rather than in the
+belief that we run a server belonging to an organisation they already
+have some relationship with. Not sure what to say to such people.
+
+\S{faq-commands}{Question} \I{commands on the server}What commands
+can I type into my PuTTY terminal window?
+
+Again, this is not a question you should be asking \e{us}. You need
+to read the manuals, or ask the administrator, of \e{the computer
+you have connected to}.
+
+PuTTY does not process the commands you type into it. It's only a
+communications tool. It makes a connection to another computer; it
+passes the commands you type to that other computer; and it passes
+the other computer's responses back to you. Therefore, the precise
+range of commands you can use will not depend on PuTTY, but on what
+kind of computer you have connected to and what software is running
+on it. The PuTTY team cannot help you with that.
+
+(Think of PuTTY as being a bit like a telephone. If you phone
+somebody up and you don't know what language to speak to make them
+understand you, it isn't \e{the telephone company}'s job to find
+that out for you. We just provide the means for you to get in touch;
+making yourself understood is somebody else's problem.)
+
+If you are unsure of where to start looking for the administrator of
+your server, a good place to start might be to remember how you
+found out the host name in the PuTTY configuration. If you were
+given that host name by e-mail, for example, you could try asking
+the person who sent you that e-mail. If your company's IT department
+provided you with ready-made PuTTY saved sessions, then that IT
+department can probably also tell you something about what commands
+you can type during those sessions. But the PuTTY maintainer team
+does not administer any server you are likely to be connecting to,
+and cannot help you with questions of this type.
+
+\S{faq-startmax}{Question} How can I make PuTTY start up \i{maximise}d?
+
+Create a Windows shortcut to start PuTTY from, and set it as \q{Run
+Maximized}.
+
+\S{faq-startsess}{Question} How can I create a \i{Windows shortcut} to
+start a particular saved session directly?
+
+To run a PuTTY session saved under the name \q{\cw{mysession}},
+create a Windows shortcut that invokes PuTTY with a command line
+like
+
+\c \path\name\to\putty.exe -load "mysession"
+
+(Note: prior to 0.53, the syntax was \c{@session}. This is now
+deprecated and may be removed at some point.)
+
+\S{faq-startssh}{Question} How can I start an SSH session straight
+from the command line?
+
+Use the command line \c{putty -ssh host.name}. Alternatively, create
+a saved session that specifies the SSH protocol, and start the saved
+session as shown in \k{faq-startsess}.
+
+\S{faq-cutpaste}{Question} How do I \i{copy and paste} between PuTTY and
+other Windows applications?
+
+Copy and paste works similarly to the X Window System. You use the
+left mouse button to select text in the PuTTY window. The act of
+selection \e{automatically} copies the text to the clipboard: there
+is no need to press Ctrl-Ins or Ctrl-C or anything else. In fact,
+pressing Ctrl-C will send a Ctrl-C character to the other end of
+your connection (just like it does the rest of the time), which may
+have unpleasant effects. The \e{only} thing you need to do, to copy
+text to the clipboard, is to select it.
+
+To paste the clipboard contents into a PuTTY window, by default you
+click the right mouse button. If you have a three-button mouse and
+are used to X applications, you can configure pasting to be done by
+the middle button instead, but this is not the default because most
+Windows users don't have a middle button at all.
+
+You can also paste by pressing Shift-Ins.
+
+\S{faq-options}{Question} How do I use all PuTTY's features (public
+keys, proxying, cipher selection, etc.) in PSCP, PSFTP and Plink?
+
+Most major features (e.g., public keys, port forwarding) are available
+through command line options. See the documentation.
+
+Not all features are accessible from the command line yet, although
+we'd like to fix this. In the meantime, you can use most of
+PuTTY's features if you create a PuTTY saved session, and then use
+the name of the saved session on the command line in place of a
+hostname. This works for PSCP, PSFTP and Plink (but don't expect
+port forwarding in the file transfer applications!).
+
+\S{faq-pscp}{Question} How do I use PSCP.EXE? When I double-click it
+gives me a command prompt window which then closes instantly.
+
+PSCP is a command-line application, not a GUI application. If you
+run it without arguments, it will simply print a help message and
+terminate.
+
+To use PSCP properly, run it from a Command Prompt window. See
+\k{pscp} in the documentation for more details.
+
+\S{faq-pscp-spaces}{Question} \I{spaces in filenames}How do I use
+PSCP to copy a file whose name has spaces in?
+
+If PSCP is using the traditional SCP protocol, this is confusing. If
+you're specifying a file at the local end, you just use one set of
+quotes as you would normally do:
+
+\c pscp "local filename with spaces" user@host:
+\c pscp user@host:myfile "local filename with spaces"
+
+But if the filename you're specifying is on the \e{remote} side, you
+have to use backslashes and two sets of quotes:
+
+\c pscp user@host:"\"remote filename with spaces\"" local_filename
+\c pscp local_filename user@host:"\"remote filename with spaces\""
+
+Worse still, in a remote-to-local copy you have to specify the local
+file name explicitly, otherwise PSCP will complain that they don't
+match (unless you specified the \c{-unsafe} option). The following
+command will give an error message:
+
+\c c:\>pscp user@host:"\"oo er\"" .
+\c warning: remote host tried to write to a file called 'oo er'
+\c when we requested a file called '"oo er"'.
+
+Instead, you need to specify the local file name in full:
+
+\c c:\>pscp user@host:"\"oo er\"" "oo er"
+
+If PSCP is using the newer SFTP protocol, none of this is a problem,
+and all filenames with spaces in are specified using a single pair
+of quotes in the obvious way:
+
+\c pscp "local file" user@host:
+\c pscp user@host:"remote file" .
+
+\H{faq-trouble} Troubleshooting
+
+\S{faq-incorrect-mac}{Question} Why do I see \q{Incorrect MAC
+received on packet}?
+
+One possible cause of this that used to be common is a bug in old
+SSH-2 servers distributed by \cw{ssh.com}. (This is not the only
+possible cause; see \k{errors-crc} in the documentation.)
+Version 2.3.0 and below of their SSH-2 server
+constructs Message Authentication Codes in the wrong way, and
+expects the client to construct them in the same wrong way. PuTTY
+constructs the MACs correctly by default, and hence these old
+servers will fail to work with it.
+
+If you are using PuTTY version 0.52 or better, this should work
+automatically: PuTTY should detect the buggy servers from their
+version number announcement, and automatically start to construct
+its MACs in the same incorrect manner as they do, so it will be able
+to work with them.
+
+If you are using PuTTY version 0.51 or below, you can enable the
+workaround by going to the SSH panel and ticking the box labelled
+\q{Imitate SSH2 MAC bug}. It's possible that you might have to do
+this with 0.52 as well, if a buggy server exists that PuTTY doesn't
+know about.
+
+In this context MAC stands for \ii{Message Authentication Code}. It's a
+cryptographic term, and it has nothing at all to do with Ethernet
+MAC (Media Access Control) addresses.
+
+\S{faq-pscp-protocol}{Question} Why do I see \q{Fatal: Protocol
+error: Expected control record} in PSCP?
+
+This happens because PSCP was expecting to see data from the server
+that was part of the PSCP protocol exchange, and instead it saw data
+that it couldn't make any sense of at all.
+
+This almost always happens because the \i{startup scripts} in your
+account on the server machine are generating output. This is
+impossible for PSCP, or any other SCP client, to work around. You
+should never use startup files (\c{.bashrc}, \c{.cshrc} and so on)
+which generate output in non-interactive sessions.
+
+This is not actually a PuTTY problem. If PSCP fails in this way,
+then all other SCP clients are likely to fail in exactly the same
+way. The problem is at the server end.
+
+\S{faq-colours}{Question} I clicked on a colour in the \ii{Colours}
+panel, and the colour didn't change in my terminal.
+
+That isn't how you're supposed to use the Colours panel.
+
+During the course of a session, PuTTY potentially uses \e{all} the
+colours listed in the Colours panel. It's not a question of using
+only one of them and you choosing which one; PuTTY will use them
+\e{all}. The purpose of the Colours panel is to let you adjust the
+appearance of all the colours. So to change the colour of the
+cursor, for example, you would select \q{Cursor Colour}, press the
+\q{Modify} button, and select a new colour from the dialog box that
+appeared. Similarly, if you want your session to appear in green,
+you should select \q{Default Foreground} and press \q{Modify}.
+Clicking on \q{ANSI Green} won't turn your session green; it will
+only allow you to adjust the \e{shade} of green used when PuTTY is
+instructed by the server to display green text.
+
+\S{faq-winsock2}{Question} Plink on \i{Windows 95} says it can't find
+\i\cw{WS2_32.DLL}.
+
+Plink requires the extended Windows network library, WinSock version
+2. This is installed as standard on Windows 98 and above, and on
+Windows NT, and even on later versions of Windows 95; but early
+Win95 installations don't have it.
+
+In order to use Plink on these systems, you will need to download
+the
+\W{http://www.microsoft.com/windows95/downloads/contents/wuadmintools/s_wunetworkingtools/w95sockets2/}{WinSock 2 upgrade}:
+
+\c http://www.microsoft.com/windows95/downloads/contents/
+\c wuadmintools/s_wunetworkingtools/w95sockets2/
+
+\S{faq-outofmem}{Question} After trying to establish an SSH-2
+connection, PuTTY says \q{\ii{Out of memory}} and dies.
+
+If this happens just while the connection is starting up, this often
+indicates that for some reason the client and server have failed to
+establish a session encryption key. Somehow, they have performed
+calculations that should have given each of them the same key, but
+have ended up with different keys; so data encrypted by one and
+decrypted by the other looks like random garbage.
+
+This causes an \q{out of memory} error because the first encrypted
+data PuTTY expects to see is the length of an SSH message. Normally
+this will be something well under 100 bytes. If the decryption has
+failed, PuTTY will see a completely random length in the region of
+two \e{gigabytes}, and will try to allocate enough memory to store
+this non-existent message. This will immediately lead to it thinking
+it doesn't have enough memory, and panicking.
+
+If this happens to you, it is quite likely to still be a PuTTY bug
+and you should report it (although it might be a bug in your SSH
+server instead); but it doesn't necessarily mean you've actually run
+out of memory.
+
+\S{faq-outofmem2}{Question} When attempting a file transfer, either
+PSCP or PSFTP says \q{\ii{Out of memory}} and dies.
+
+This is almost always caused by your \i{login scripts} on the server
+generating output. PSCP or PSFTP will receive that output when they
+were expecting to see the start of a file transfer protocol, and
+they will attempt to interpret the output as file-transfer protocol.
+This will usually lead to an \q{out of memory} error for much the
+same reasons as given in \k{faq-outofmem}.
+
+This is a setup problem in your account on your server, \e{not} a
+PSCP/PSFTP bug. Your login scripts should \e{never} generate output
+during non-interactive sessions; secure file transfer is not the
+only form of remote access that will break if they do.
+
+On Unix, a simple fix is to ensure that all the parts of your login
+script that might generate output are in \c{.profile} (if you use a
+Bourne shell derivative) or \c{.login} (if you use a C shell).
+Putting them in more general files such as \c{.bashrc} or \c{.cshrc}
+is liable to lead to problems.
+
+\S{faq-psftp-slow}{Question} PSFTP transfers files much slower than PSCP.
+
+The throughput of PSFTP 0.54 should be much better than 0.53b and
+prior; we've added code to the SFTP backend to queue several blocks
+of data rather than waiting for an acknowledgement for each. (The
+SCP backend did not suffer from this performance issue because SCP
+is a much simpler protocol.)
+
+\S{faq-bce}{Question} When I run full-colour applications, I see
+areas of black space where colour ought to be, or vice versa.
+
+You almost certainly need to change the \q{Use \i{background colour} to
+erase screen} setting in the Terminal panel. If there is too much
+black space (the commoner situation), you should enable it, while if
+there is too much colour, you should disable it. (See \k{config-erase}.)
+
+In old versions of PuTTY, this was disabled by default, and would not
+take effect until you reset the terminal (see \k{faq-resetterm}).
+Since 0.54, it is enabled by default, and changes take effect
+immediately.
+
+\S{faq-resetterm}{Question} When I change some terminal settings,
+nothing happens.
+
+Some of the terminal options (notably \ii{Auto Wrap} and
+background-colour screen erase) actually represent the \e{default}
+setting, rather than the currently active setting. The server can
+send sequences that modify these options in mid-session, but when
+the terminal is reset (by server action, or by you choosing \q{Reset
+Terminal} from the System menu) the defaults are restored.
+
+In versions 0.53b and prior, if you change one of these options in
+the middle of a session, you will find that the change does not
+immediately take effect. It will only take effect once you reset
+the terminal.
+
+In version 0.54, the behaviour has changed - changes to these
+settings take effect immediately.
+
+\S{faq-idleout}{Question} My PuTTY sessions unexpectedly close after
+they are \I{idle connections}idle for a while.
+
+Some types of \i{firewall}, and almost any router doing Network Address
+Translation (\i{NAT}, also known as IP masquerading), will forget about
+a connection through them if the connection does nothing for too
+long. This will cause the connection to be rudely cut off when
+contact is resumed.
+
+You can try to combat this by telling PuTTY to send \e{keepalives}:
+packets of data which have no effect on the actual session, but
+which reassure the router or firewall that the network connection is
+still active and worth remembering about.
+
+Keepalives don't solve everything, unfortunately; although they
+cause greater robustness against this sort of router, they can also
+cause a \e{loss} of robustness against network dropouts. See
+\k{config-keepalive} in the documentation for more discussion of
+this.
+
+\S{faq-timeout}{Question} PuTTY's network connections time out too
+quickly when \I{breaks in connectivity}network connectivity is
+temporarily lost.
+
+This is a Windows problem, not a PuTTY problem. The timeout value
+can't be set on per application or per session basis. To increase
+the TCP timeout globally, you need to tinker with the Registry.
+
+On Windows 95, 98 or ME, the registry key you need to create or
+change is
+
+\c HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\VxD\
+\c MSTCP\MaxDataRetries
+
+(it must be of type DWORD in Win95, or String in Win98/ME).
+(See MS Knowledge Base article
+\W{http://support.microsoft.com/default.aspx?scid=kb;en-us;158474}{158474}
+for more information.)
+
+On Windows NT, 2000, or XP, the registry key to create or change is
+
+\c HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\
+\c Parameters\TcpMaxDataRetransmissions
+
+and it must be of type DWORD.
+(See MS Knowledge Base articles
+\W{http://support.microsoft.com/default.aspx?scid=kb;en-us;120642}{120642}
+and
+\W{http://support.microsoft.com/default.aspx?scid=kb;en-us;314053}{314053}
+for more information.)
+
+Set the key's value to something like 10. This will cause Windows to
+try harder to keep connections alive instead of abandoning them.
+
+\S{faq-puttyputty}{Question} When I \cw{cat} a binary file, I get
+\q{PuTTYPuTTYPuTTY} on my command line.
+
+Don't do that, then.
+
+This is designed behaviour; when PuTTY receives the character
+Control-E from the remote server, it interprets it as a request to
+identify itself, and so it sends back the string \q{\cw{PuTTY}} as
+if that string had been entered at the keyboard. Control-E should
+only be sent by programs that are prepared to deal with the
+response. Writing a binary file to your terminal is likely to output
+many Control-E characters, and cause this behaviour. Don't do it.
+It's a bad plan.
+
+To mitigate the effects, you could configure the answerback string
+to be empty (see \k{config-answerback}); but writing binary files to
+your terminal is likely to cause various other unpleasant behaviour,
+so this is only a small remedy.
+
+\S{faq-wintitle}{Question} When I \cw{cat} a binary file, my \i{window
+title} changes to a nonsense string.
+
+Don't do that, then.
+
+It is designed behaviour that PuTTY should have the ability to
+adjust the window title on instructions from the server. Normally
+the control sequence that does this should only be sent
+deliberately, by programs that know what they are doing and intend
+to put meaningful text in the window title. Writing a binary file to
+your terminal runs the risk of sending the same control sequence by
+accident, and cause unexpected changes in the window title. Don't do
+it.
+
+\S{faq-password-fails}{Question} My \i{keyboard} stops working once
+PuTTY displays the \i{password prompt}.
+
+No, it doesn't. PuTTY just doesn't display the password you type, so
+that someone looking at your screen can't see what it is.
+
+Unlike the Windows login prompts, PuTTY doesn't display the password
+as a row of asterisks either. This is so that someone looking at
+your screen can't even tell how \e{long} your password is, which
+might be valuable information.
+
+\S{faq-keyboard}{Question} One or more \I{keyboard}\i{function keys}
+don't do what I expected in a server-side application.
+
+If you've already tried all the relevant options in the PuTTY
+Keyboard panel, you may need to mail the PuTTY maintainers and ask.
+
+It is \e{not} usually helpful just to tell us which application,
+which server operating system, and which key isn't working; in order
+to replicate the problem we would need to have a copy of every
+operating system, and every application, that anyone has ever
+complained about.
+
+PuTTY responds to function key presses by sending a sequence of
+control characters to the server. If a function key isn't doing what
+you expect, it's likely that the character sequence your application
+is expecting to receive is not the same as the one PuTTY is sending.
+Therefore what we really need to know is \e{what} sequence the
+application is expecting.
+
+The simplest way to investigate this is to find some other terminal
+environment, in which that function key \e{does} work; and then
+investigate what sequence the function key is sending in that
+situation. One reasonably easy way to do this on a \i{Unix} system is to
+type the command \i\c{cat}, and then press the function key. This is
+likely to produce output of the form \c{^[[11~}. You can also do
+this in PuTTY, to find out what sequence the function key is
+producing in that. Then you can mail the PuTTY maintainers and tell
+us \q{I wanted the F1 key to send \c{^[[11~}, but instead it's
+sending \c{^[OP}, can this be done?}, or something similar.
+
+You should still read the
+\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/feedback.html}{Feedback
+page} on the PuTTY website (also provided as \k{feedback} in the
+manual), and follow the guidelines contained in that.
+
+\S{faq-openssh-bad-openssl}{Question} Since my SSH server was upgraded
+to \i{OpenSSH} 3.1p1/3.4p1, I can no longer connect with PuTTY.
+
+There is a known problem when OpenSSH has been built against an
+incorrect version of OpenSSL; the quick workaround is to configure
+PuTTY to use SSH protocol 2 and the Blowfish cipher.
+
+For more details and OpenSSH patches, see
+\W{http://bugzilla.mindrot.org/show_bug.cgi?id=138}{bug 138} in the
+OpenSSH BTS.
+
+This is not a PuTTY-specific problem; if you try to connect with
+another client you'll likely have similar problems. (Although PuTTY's
+default cipher differs from many other clients.)
+
+\e{OpenSSH 3.1p1:} configurations known to be broken (and symptoms):
+
+\b SSH-2 with AES cipher (PuTTY says \q{Assertion failed! Expression:
+(len & 15) == 0} in \cw{sshaes.c}, or \q{Out of memory}, or crashes)
+
+\b SSH-2 with 3DES (PuTTY says \q{Incorrect MAC received on packet})
+
+\b SSH-1 with Blowfish (PuTTY says \q{Incorrect CRC received on
+packet})
+
+\b SSH-1 with 3DES
+
+\e{OpenSSH 3.4p1:} as of 3.4p1, only the problem with SSH-1 and
+Blowfish remains. Rebuild your server, apply the patch linked to from
+bug 138 above, or use another cipher (e.g., 3DES) instead.
+
+\e{Other versions:} we occasionally get reports of the same symptom
+and workarounds with older versions of OpenSSH, although it's not
+clear the underlying cause is the same.
+
+\S{faq-ssh2key-ssh1conn}{Question} Why do I see \q{Couldn't load
+private key from ...}? Why can PuTTYgen load my key but not PuTTY?
+
+It's likely that you've generated an SSH protocol 2 key with PuTTYgen,
+but you're trying to use it in an SSH-1 connection. SSH-1 and SSH-2 keys
+have different formats, and (at least in 0.52) PuTTY's reporting of a
+key in the wrong format isn't optimal.
+
+To connect using SSH-2 to a server that supports both versions, you
+need to change the configuration from the default (see \k{faq-ssh2}).
+
+\S{faq-rh8-utf8}{Question} When I'm connected to a \i{Red Hat Linux} 8.0
+system, some characters don't display properly.
+
+A common complaint is that hyphens in man pages show up as a-acute.
+
+With release 8.0, Red Hat appear to have made \i{UTF-8} the default
+character set. There appears to be no way for terminal emulators such
+as PuTTY to know this (as far as we know, the appropriate escape
+sequence to switch into UTF-8 mode isn't sent).
+
+A fix is to configure sessions to RH8 systems to use UTF-8
+translation - see \k{config-charset} in the documentation. (Note that
+if you use \q{Change Settings}, changes may not take place immediately
+- see \k{faq-resetterm}.)
+
+If you really want to change the character set used by the server, the
+right place is \c{/etc/sysconfig/i18n}, but this shouldn't be
+necessary.
+
+\S{faq-screen}{Question} Since I upgraded to PuTTY 0.54, the
+scrollback has stopped working when I run \c{screen}.
+
+PuTTY's terminal emulator has always had the policy that when the
+\q{\i{alternate screen}} is in use, nothing is added to the scrollback.
+This is because the usual sorts of programs which use the alternate
+screen are things like text editors, which tend to scroll back and
+forth in the same document a lot; so (a) they would fill up the
+scrollback with a large amount of unhelpfully disordered text, and
+(b) they contain their \e{own} method for the user to scroll back to
+the bit they were interested in. We have generally found this policy
+to do the Right Thing in almost all situations.
+
+Unfortunately, \c{screen} is one exception: it uses the alternate
+screen, but it's still usually helpful to have PuTTY's scrollback
+continue working. The simplest solution is to go to the Features
+control panel and tick \q{Disable switching to alternate terminal
+screen}. (See \k{config-features-altscreen} for more details.)
+Alternatively, you can tell \c{screen} itself not to use the
+alternate screen: the
+\W{http://www4.informatik.uni-erlangen.de/~jnweiger/screen-faq.html}{\c{screen}
+FAQ} suggests adding the line \cq{termcapinfo xterm ti@:te@} to your
+\cw{.screenrc} file.
+
+The reason why this only started to be a problem in 0.54 is because
+\c{screen} typically uses an unusual control sequence to switch to
+the alternate screen, and previous versions of PuTTY did not support
+this sequence.
+
+\S{faq-alternate-localhost}{Question} Since I upgraded \i{Windows XP}
+to Service Pack 2, I can't use addresses like \cw{127.0.0.2}.
+
+Some people who ask PuTTY to listen on \i{localhost} addresses other
+than \cw{127.0.0.1} to forward services such as \i{SMB} and \i{Windows
+Terminal Services} have found that doing so no longer works since
+they upgraded to WinXP SP2.
+
+This is apparently an issue with SP2 that is acknowledged by Microsoft
+in MS Knowledge Base article
+\W{http://support.microsoft.com/default.aspx?scid=kb;en-us;884020}{884020}.
+The article links to a fix you can download.
+
+(\e{However}, we've been told that SP2 \e{also} fixes the bug that
+means you need to use non-\cw{127.0.0.1} addresses to forward
+Terminal Services in the first place.)
+
+\S{faq-missing-slash}{Question} PSFTP commands seem to be missing a
+directory separator (slash).
+
+Some people have reported the following incorrect behaviour with
+PSFTP:
+
+\c psftp> pwd
+\e iii
+\c Remote directory is /dir1/dir2
+\c psftp> get filename.ext
+\e iiiiiiiiiiiiiiii
+\c /dir1/dir2filename.ext: no such file or directory
+
+This is not a bug in PSFTP. There is a known bug in some versions of
+portable \i{OpenSSH}
+(\W{http://bugzilla.mindrot.org/show_bug.cgi?id=697}{bug 697}) that
+causes these symptoms; it appears to have been introduced around
+3.7.x. It manifests only on certain platforms (AIX is what has been
+reported to us).
+
+There is a patch for OpenSSH attached to that bug; it's also fixed in
+recent versions of portable OpenSSH (from around 3.8).
+
+\S{faq-connaborted}{Question} Do you want to hear about \q{Software
+caused connection abort}?
+
+In the documentation for PuTTY 0.53 and 0.53b, we mentioned that we'd
+like to hear about any occurrences of this error. Since the release
+of PuTTY 0.54, however, we've been convinced that this error doesn't
+indicate that PuTTY's doing anything wrong, and we don't need to hear
+about further occurrences. See \k{errors-connaborted} for our current
+documentation of this error.
+
+\S{faq-rekey}{Question} My SSH-2 session \I{locking up, SSH-2
+sessions}locks up for a few seconds every so often.
+
+Recent versions of PuTTY automatically initiate \i{repeat key
+exchange} once per hour, to improve session security. If your client
+or server machine is slow, you may experience this as a delay of
+anything up to thirty seconds or so.
+
+These \I{delays, in SSH-2 sessions}delays are inconvenient, but they
+are there for your protection. If they really cause you a problem,
+you can choose to turn off periodic rekeying using the \q{Kex}
+configuration panel (see \k{config-ssh-kex}), but be aware that you
+will be sacrificing security for this. (Falling back to SSH-1 would
+also remove the delays, but would lose a \e{lot} more security
+still. We do not recommend it.)
+
+\S{faq-xpwontrun}{Question} PuTTY fails to start up. Windows claims that
+\q{the application configuration is incorrect}.
+
+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.
+
+\H{faq-secure} Security questions
+
+\S{faq-publicpc}{Question} Is it safe for me to download PuTTY and
+use it on a public PC?
+
+It depends on whether you trust that PC. If you don't trust the
+public PC, don't use PuTTY on it, and don't use any other software
+you plan to type passwords into either. It might be watching your
+keystrokes, or it might tamper with the PuTTY binary you download.
+There is \e{no} program safe enough that you can run it on an
+actively malicious PC and get away with typing passwords into it.
+
+If you do trust the PC, then it's probably OK to use PuTTY on it
+(but if you don't trust the network, then the PuTTY download might
+be tampered with, so it would be better to carry PuTTY with you on a
+floppy).
+
+\S{faq-cleanup}{Question} What does PuTTY leave on a system? How can
+I \i{clean up} after it?
+
+PuTTY will leave some Registry entries, and a random seed file, on
+the PC (see \k{faq-settings}). If you are using PuTTY on a public
+PC, or somebody else's PC, you might want to clean these up when you
+leave. You can do that automatically, by running the command
+\c{putty -cleanup}. (Note that this only removes settings for
+the currently logged-in user on \i{multi-user systems}.)
+
+If PuTTY was installed from the installer package, it will also
+appear in \q{Add/Remove Programs}. Older versions of the uninstaller
+do not remove the above-mentioned registry entries and file.
+
+\S{faq-dsa}{Question} How come PuTTY now supports \i{DSA}, when the
+website used to say how insecure it was?
+
+DSA has a major weakness \e{if badly implemented}: it relies on a
+random number generator to far too great an extent. If the random
+number generator produces a number an attacker can predict, the DSA
+private key is exposed - meaning that the attacker can log in as you
+on all systems that accept that key.
+
+The PuTTY policy changed because the developers were informed of
+ways to implement DSA which do not suffer nearly as badly from this
+weakness, and indeed which don't need to rely on random numbers at
+all. For this reason we now believe PuTTY's DSA implementation is
+probably OK. However, if you have the choice, we still recommend you
+use RSA instead.
+
+\S{faq-virtuallock}{Question} Couldn't Pageant use
+\cw{VirtualLock()} to stop private keys being written to disk?
+
+Unfortunately not. The \cw{VirtualLock()} function in the Windows
+API doesn't do a proper job: it may prevent small pieces of a
+process's memory from being paged to disk while the process is
+running, but it doesn't stop the process's memory as a whole from
+being swapped completely out to disk when the process is long-term
+inactive. And Pageant spends most of its time inactive.
+
+\H{faq-admin} Administrative questions
+
+\S{faq-domain}{Question} Would you like me to register you a nicer
+domain name?
+
+No, thank you. Even if you can find one (most of them seem to have
+been registered already, by people who didn't ask whether we
+actually wanted it before they applied), we're happy with the PuTTY
+web site being exactly where it is. It's not hard to find (just type
+\q{putty} into \W{http://www.google.com/}{google.com} and we're the
+first link returned), and we don't believe the administrative hassle
+of moving the site would be worth the benefit.
+
+In addition, if we \e{did} want a custom domain name, we would want
+to run it ourselves, so we knew for certain that it would continue
+to point where we wanted it, and wouldn't suddenly change or do
+strange things. Having it registered for us by a third party who we
+don't even know is not the best way to achieve this.
+
+\S{faq-webhosting}{Question} Would you like free web hosting for the
+PuTTY web site?
+
+We already have some, thanks.
+
+\S{faq-link}{Question} Would you link to my web site from the PuTTY
+web site?
+
+Only if the content of your web page is of definite direct interest
+to PuTTY users. If your content is unrelated, or only tangentially
+related, to PuTTY, then the link would simply be advertising for
+you.
+
+One very nice effect of the Google ranking mechanism is that by and
+large, the most popular web sites get the highest rankings. This
+means that when an ordinary person does a search, the top item in
+the search is very likely to be a high-quality site or the site they
+actually wanted, rather than the site which paid the most money for
+its ranking.
+
+The PuTTY web site is held in high esteem by Google, for precisely
+this reason: lots of people have linked to it simply because they
+like PuTTY, without us ever having to ask anyone to link to us. We
+feel that it would be an abuse of this esteem to use it to boost the
+ranking of random advertisers' web sites. If you want your web site
+to have a high Google ranking, we'd prefer that you achieve this the
+way we did - by being good enough at what you do that people will
+link to you simply because they like you.
+
+In particular, we aren't interested in trading links for money (see
+above), and we \e{certainly} aren't interested in trading links for
+other links (since we have no advertising on our web site, our
+Google ranking is not even directly worth anything to us). If we
+don't want to link to you for free, then we probably won't want to
+link to you at all.
+
+If you have software based on PuTTY, or specifically designed to
+interoperate with PuTTY, or in some other way of genuine interest to
+PuTTY users, then we will probably be happy to add a link to you on
+our Links page. And if you're running a particularly valuable mirror
+of the PuTTY web site, we might be interested in linking to you from
+our Mirrors page.
+
+\S{faq-sourceforge}{Question} Why don't you move PuTTY to
+SourceForge?
+
+Partly, because we don't want to move the web site location (see
+\k{faq-domain}).
+
+Also, security reasons. PuTTY is a security product, and as such it
+is particularly important to guard the code and the web site against
+unauthorised modifications which might introduce subtle security
+flaws. Therefore, we prefer that the Subversion repository, web site and
+FTP site remain where they are, under the direct control of system
+administrators we know and trust personally, rather than being run
+by a large organisation full of people we've never met and which is
+known to have had breakins in the past.
+
+No offence to SourceForge; I think they do a wonderful job. But
+they're not ideal for everyone, and in particular they're not ideal
+for us.
+
+\S{faq-mailinglist1}{Question} Why can't I subscribe to the
+putty-bugs mailing list?
+
+Because you're not a member of the PuTTY core development team. The
+putty-bugs mailing list is not a general newsgroup-like discussion
+forum; it's a contact address for the core developers, and an
+\e{internal} mailing list for us to discuss things among ourselves.
+If we opened it up for everybody to subscribe to, it would turn into
+something more like a newsgroup and we would be completely
+overwhelmed by the volume of traffic. It's hard enough to keep up
+with the list as it is.
+
+\S{faq-mailinglist2}{Question} If putty-bugs isn't a
+general-subscription mailing list, what is?
+
+There isn't one, that we know of.
+
+If someone else wants to set up a mailing list or other forum for
+PuTTY users to help each other with common problems, that would be
+fine with us, though the PuTTY team would almost certainly not have the
+time to read it. It's probably better to use one of the established
+newsgroups for this purpose (see \k{feedback-other-fora}).
+
+\S{faq-donations}{Question} How can I donate to PuTTY development?
+
+Please, \e{please} don't feel you have to. PuTTY is completely free
+software, and not shareware. We think it's very important that
+\e{everybody} who wants to use PuTTY should be able to, whether they
+have any money or not; so the last thing we would want is for a
+PuTTY user to feel guilty because they haven't paid us any money. If
+you want to keep your money, please do keep it. We wouldn't dream of
+asking for any.
+
+Having said all that, if you still really \e{want} to give us money,
+we won't argue :-) The easiest way for us to accept donations is if
+you send money to \cw{<anakin@pobox.com>} using PayPal
+(\W{http://www.paypal.com/}\cw{www.paypal.com}). If you don't like
+PayPal, talk to us; we can probably arrange some alternative means.
+
+Small donations (tens of dollars or tens of euros) will probably be
+spent on beer or curry, which helps motivate our volunteer team to
+continue doing this for the world. Larger donations will be spent on
+something that actually helps development, if we can find anything
+(perhaps new hardware, or a copy of Windows XP), but if we can't
+find anything then we'll just distribute the money among the
+developers. If you want to be sure your donation is going towards
+something worthwhile, ask us first. If you don't like these terms,
+feel perfectly free not to donate. We don't mind.
+
+\S{faq-permission}{Question} Can I have permission to put PuTTY on a
+cover disk / distribute it with other software / etc?
+
+Yes. For most things, you need not bother asking us explicitly for
+permission; our licence already grants you permission.
+
+See \k{feedback-permission} for more details.
+
+\S{faq-indemnity}{Question} Can you sign an agreement indemnifying
+us against security problems in PuTTY?
+
+No!
+
+A vendor of physical security products (e.g. locks) might plausibly
+be willing to accept financial liability for a product that failed
+to perform as advertised and resulted in damage (e.g. valuables
+being stolen). The reason they can afford to do this is because they
+sell a \e{lot} of units, and only a small proportion of them will
+fail; so they can meet their financial liability out of the income
+from all the rest of their sales, and still have enough left over to
+make a profit. Financial liability is intrinsically linked to
+selling your product for money.
+
+There are two reasons why PuTTY is not analogous to a physical lock
+in this context. One is that software products don't exhibit random
+variation: \e{if} PuTTY has a security hole (which does happen,
+although we do our utmost to prevent it and to respond quickly when
+it does), every copy of PuTTY will have the same hole, so it's
+likely to affect all the users at the same time. So even if our
+users were all paying us to use PuTTY, we wouldn't be able to
+\e{simultaneously} pay every affected user compensation in excess of
+the amount they had paid us in the first place. It just wouldn't
+work.
+
+The second, much more important, reason is that PuTTY users
+\e{don't} pay us. The PuTTY team does not have an income; it's a
+volunteer effort composed of people spending their spare time to try
+to write useful software. We aren't even a company or any kind of
+legally recognised organisation. We're just a bunch of people who
+happen to do some stuff in our spare time.
+
+Therefore, to ask us to assume financial liability is to ask us to
+assume a risk of having to pay it out of our own \e{personal}
+pockets: out of the same budget from which we buy food and clothes
+and pay our rent. That's more than we're willing to give. We're
+already giving a lot of our spare \e{time} to developing software
+for free; if we had to pay our own \e{money} to do it as well, we'd
+start to wonder why we were bothering.
+
+Free software fundamentally does not work on the basis of financial
+guarantees. Your guarantee of the software functioning correctly is
+simply that you have the source code and can check it before you use
+it. If you want to be sure there aren't any security holes, do a
+security audit of the PuTTY code, or hire a security engineer if you
+don't have the necessary skills yourself: instead of trying to
+ensure you can get compensation in the event of a disaster, try to
+ensure there isn't a disaster in the first place.
+
+If you \e{really} want financial security, see if you can find a
+security engineer who will take financial responsibility for the
+correctness of their review. (This might be less likely to suffer
+from the everything-failing-at-once problem mentioned above, because
+such an engineer would probably be reviewing a lot of \e{different}
+products which would tend to fail independently.) Failing that, see
+if you can persuade an insurance company to insure you against
+security incidents, and if the insurer demands it as a condition
+then get our code reviewed by a security engineer they're happy
+with.
+
+\S{faq-permission-form}{Question} Can you sign this form granting us
+permission to use/distribute PuTTY?
+
+If your form contains any clause along the lines of \q{the
+undersigned represents and warrants}, we're not going to sign it.
+This is particularly true if it asks us to warrant that PuTTY is
+secure; see \k{faq-indemnity} for more discussion of this. But it
+doesn't really matter what we're supposed to be warranting: even if
+it's something we already believe is true, such as that we don't
+infringe any third-party copyright, we will not sign a document
+accepting any legal or financial liability. This is simply because
+the PuTTY development project has no income out of which to satisfy
+that liability, or pay legal costs, should it become necessary. We
+cannot afford to be sued. We are assuring you that \e{we have done
+our best}; if that isn't good enough for you, tough.
+
+The existing PuTTY licence document already gives you permission to
+use or distribute PuTTY in pretty much any way which does not
+involve pretending you wrote it or suing us if it goes wrong. We
+think that really ought to be enough for anybody.
+
+See also \k{faq-permission-general} for another reason why we don't
+want to do this sort of thing.
+
+\S{faq-permission-future}{Question} Can you write us a formal notice
+of permission to use PuTTY?
+
+We could, in principle, but it isn't clear what use it would be. If
+you think there's a serious chance of one of the PuTTY copyright
+holders suing you (which we don't!), you would presumably want a
+signed notice from \e{all} of them; and we couldn't provide that
+even if we wanted to, because many of the copyright holders are
+people who contributed some code in the past and with whom we
+subsequently lost contact. Therefore the best we would be able to do
+\e{even in theory} would be to have the core development team sign
+the document, which wouldn't guarantee you that some other copyright
+holder might not sue.
+
+See also \k{faq-permission-general} for another reason why we don't
+want to do this sort of thing.
+
+\S{faq-permission-general}{Question} Can you sign \e{anything} for
+us?
+
+Not unless there's an incredibly good reason.
+
+We are generally unwilling to set a precedent that involves us
+having to enter into individual agreements with PuTTY users. We
+estimate that we have literally \e{millions} of users, and we
+absolutely would not have time to go round signing specific
+agreements with every one of them. So if you want us to sign
+something specific for you, you might usefully stop to consider
+whether there's anything special that distinguishes you from 999,999
+other users, and therefore any reason we should be willing to sign
+something for you without it setting such a precedent.
+
+If your company policy requires you to have an individual agreement
+with the supplier of any software you use, then your company policy
+is simply not well suited to using popular free software, and we
+urge you to consider this as a flaw in your policy.
+
+\S{faq-permission-assurance}{Question} If you won't sign anything,
+can you give us some sort of assurance that you won't make PuTTY
+closed-source in future?
+
+Yes and no.
+
+If what you want is an assurance that some \e{current version} of
+PuTTY which you've already downloaded will remain free, then you
+already have that assurance: it's called the PuTTY Licence. It
+grants you permission to use, distribute and copy the software to
+which it applies; once we've granted that permission (which we
+have), we can't just revoke it.
+
+On the other hand, if you want an assurance that \e{future} versions
+of PuTTY won't be closed-source, that's more difficult. We could in
+principle sign a document stating that we would never release a
+closed-source PuTTY, but that wouldn't assure you that we \e{would}
+keep releasing \e{open}-source PuTTYs: we would still have the
+option of ceasing to develop PuTTY at all, which would surely be
+even worse for you than making it closed-source! (And we almost
+certainly wouldn't \e{want} to sign a document guaranteeing that we
+would actually continue to do development work on PuTTY; we
+certainly wouldn't sign it for free. Documents like that are called
+contracts of employment, and are generally not signed except in
+return for a sizeable salary.)
+
+If we \e{were} to stop developing PuTTY, or to decide to make all
+future releases closed-source, then you would still be free to copy
+the last open release in accordance with the current licence, and in
+particular you could start your own fork of the project from that
+release. If this happened, I confidently predict that \e{somebody}
+would do that, and that some kind of a free PuTTY would continue to
+be developed. There's already precedent for that sort of thing
+happening in free software. We can't guarantee that somebody
+\e{other than you} would do it, of course; you might have to do it
+yourself. But we can assure you that there would be nothing
+\e{preventing} anyone from continuing free development if we
+stopped.
+
+(Finally, we can also confidently predict that if we made PuTTY
+closed-source and someone made an open-source fork, most people
+would switch to the latter. Therefore, it would be pretty stupid of
+us to try it.)
+
+\S{faq-export-cert}{Question} Can you provide us with export control
+information / FIPS certification for PuTTY?
+
+Some people have asked us for an Export Control Classification Number
+(ECCN) for PuTTY. We don't know whether we have one, and as a team of
+free software developers based in the UK we don't have the time,
+money, or effort to deal with US bureaucracy to investigate any
+further. We believe that PuTTY falls under 5D002 on the US Commerce
+Control List, but that shouldn't be taken as definitive. If you need
+to know more you should seek professional legal advice. The same
+applies to any other country's legal requirements and restrictions.
+
+Similarly, some people have asked us for FIPS certification of the
+PuTTY tools. Unless someone else is prepared to do the necessary work
+and pay any costs, we can't provide this.
+
+\H{faq-misc} Miscellaneous questions
+
+\S{faq-openssh}{Question} Is PuTTY a port of \i{OpenSSH}, or based on
+OpenSSH or OpenSSL?
+
+No, it isn't. PuTTY is almost completely composed of code written
+from scratch for PuTTY. The only code we share with OpenSSH is the
+detector for SSH-1 CRC compensation attacks, written by CORE SDI
+S.A; we share no code at all with OpenSSL.
+
+\S{faq-sillyputty}{Question} Where can I buy silly putty?
+
+You're looking at the wrong web site; the only PuTTY we know about
+here is the name of a computer program.
+
+If you want the kind of putty you can buy as an executive toy, the
+PuTTY team can personally recommend Thinking Putty, which you can
+buy from Crazy Aaron's Putty World, at
+\W{http://www.puttyworld.com}\cw{www.puttyworld.com}.
+
+\S{faq-meaning}{Question} What does \q{PuTTY} mean?
+
+It's the name of a popular SSH and Telnet client. Any other meaning
+is in the eye of the beholder. It's been rumoured that \q{PuTTY}
+is the antonym of \q{\cw{getty}}, or that it's the stuff that makes your
+Windows useful, or that it's a kind of plutonium Teletype. We
+couldn't possibly comment on such allegations.
+
+\S{faq-pronounce}{Question} How do I pronounce \q{PuTTY}?
+
+Exactly like the English word \q{putty}, which we pronounce
+/\u02C8{'}p\u028C{V}ti/.
--- /dev/null
+\define{versionidfeedback} \versionid $Id$
+
+\A{feedback} \ii{Feedback} and \i{bug reporting}
+
+This is a guide to providing feedback to the PuTTY development team.
+It is provided as both a web page on the PuTTY site, and an appendix
+in the PuTTY manual.
+
+\K{feedback-general} gives some general guidelines for sending any
+kind of e-mail to the development team. Following sections give more
+specific guidelines for particular types of e-mail, such as bug
+reports and feature requests.
+
+\H{feedback-general} General guidelines
+
+The PuTTY development team gets a \e{lot} of mail. If you can
+possibly solve your own problem by reading the manual, reading the
+FAQ, reading the web site, asking a fellow user, perhaps posting to a
+newsgroup (see \k{feedback-other-fora}), or some other means, then it
+would make our lives much easier.
+
+We get so much e-mail that we literally do not have time to answer
+it all. We regret this, but there's nothing we can do about it. So
+if you can \e{possibly} avoid sending mail to the PuTTY team, we
+recommend you do so. In particular, support requests
+(\k{feedback-support}) are probably better sent to newsgroups, or
+passed to a local expert if possible.
+
+The PuTTY contact email address is a private \i{mailing list} containing
+four or five core developers. Don't be put off by it being a mailing
+list: if you need to send confidential data as part of a bug report,
+you can trust the people on the list to respect that confidence.
+Also, the archives aren't publicly available, so you shouldn't be
+letting yourself in for any spam by sending us mail.
+
+Please use a meaningful subject line on your message. We get a lot of
+mail, and it's hard to find the message we're looking for if they all
+have subject lines like \q{PuTTY bug}.
+
+\S{feedback-largefiles} Sending large attachments
+
+Since the PuTTY contact address is a mailing list, e-mails larger
+than 40Kb will be held for inspection by the list administrator, and
+will not be allowed through unless they really appear to be worth
+their large size.
+
+If you are considering sending any kind of large data file to the
+PuTTY team, it's almost always a bad idea, or at the very least it
+would be better to ask us first whether we actually need the file.
+Alternatively, you could put the file on a web site and just send us
+the URL; that way, we don't have to download it unless we decide we
+actually need it, and only one of us needs to download it instead of
+it being automatically copied to all the developers.
+
+Some people like to send mail in MS Word format. Please \e{don't}
+send us bug reports, or any other mail, as a Word document. Word
+documents are roughly fifty times larger than writing the same
+report in plain text. In addition, most of the PuTTY team read their
+e-mail on Unix machines, so copying the file to a Windows box to run
+Word is very inconvenient. Not only that, but several of us don't
+even \e{have} a copy of Word!
+
+Some people like to send us screen shots when demonstrating a
+problem. Please don't do this without checking with us first - we
+almost never actually need the information in the screen shot.
+Sending a screen shot of an error box is almost certainly
+unnecessary when you could just tell us in plain text what the error
+was. (On some versions of Windows, pressing Ctrl-C when the error
+box is displayed will copy the text of the message to the clipboard.)
+Sending a full-screen shot is \e{occasionally} useful, but it's
+probably still wise to check whether we need it before sending it.
+
+If you \e{must} mail a screen shot, don't send it as a \cw{.BMP}
+file. \cw{BMP}s have no compression and they are \e{much} larger
+than other image formats such as PNG, TIFF and GIF. Convert the file
+to a properly compressed image format before sending it.
+
+Please don't mail us executables, at all. Our mail server blocks all
+incoming e-mail containing executables, as a defence against the
+vast numbers of e-mail viruses we receive every day. If you mail us
+an executable, it will just bounce.
+
+If you have made a tiny modification to the PuTTY code, please send
+us a \e{patch} to the source code if possible, rather than sending
+us a huge \cw{.ZIP} file containing the complete sources plus your
+modification. If you've only changed 10 lines, we'd prefer to
+receive a mail that's 30 lines long than one containing multiple
+megabytes of data we already have.
+
+\S{feedback-other-fora} Other places to ask for help
+
+There are two Usenet newsgroups that are particularly relevant to the
+PuTTY tools:
+
+\b \W{news:comp.security.ssh}\c{comp.security.ssh}, for questions
+specific to using the SSH protocol;
+
+\b \W{news:comp.terminals}\c{comp.terminals}, for issues relating to
+terminal emulation (for instance, keyboard problems).
+
+Please use the newsgroup most appropriate to your query, and remember
+that these are general newsgroups, not specifically about PuTTY.
+
+If you don't have direct access to Usenet, you can access these
+newsgroups through Google Groups
+(\W{http://groups.google.com/}\cw{groups.google.com}).
+
+\H{feedback-bugs} Reporting bugs
+
+If you think you have found a bug in PuTTY, your first steps should
+be:
+
+\b Check the
+\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/wishlist/}{Wishlist
+page} on the PuTTY website, and see if we already know about the
+problem. If we do, it is almost certainly not necessary to mail us
+about it, unless you think you have extra information that might be
+helpful to us in fixing it. (Of course, if we actually \e{need}
+specific extra information about a particular bug, the Wishlist page
+will say so.)
+
+\b Check the
+\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/changes.html}{Change
+Log} on the PuTTY website, and see if we have already fixed the bug
+in the \i{development snapshots}.
+
+\b Check the
+\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/faq.html}{FAQ}
+on the PuTTY website (also provided as \k{faq} in the manual), and
+see if it answers your question. The FAQ lists the most common
+things which people think are bugs, but which aren't bugs.
+
+\b Download the latest development snapshot and see if the problem
+still happens with that. This really is worth doing. As a general
+rule we aren't very interested in bugs that appear in the release
+version but not in the development version, because that usually
+means they are bugs we have \e{already fixed}. On the other hand, if
+you can find a bug in the development version that doesn't appear in
+the release, that's likely to be a new bug we've introduced since
+the release and we're definitely interested in it.
+
+If none of those options solved your problem, and you still need to
+report a bug to us, it is useful if you include some general
+information:
+
+\b Tell us what \i{version of PuTTY} you are running. To find this out,
+use the \q{About PuTTY} option from the System menu. Please \e{do
+not} just tell us \q{I'm running the latest version}; e-mail can be
+delayed and it may not be obvious which version was the latest at
+the time you sent the message.
+
+\b PuTTY is a multi-platform application; tell us what version of what
+OS you are running PuTTY on. (If you're running on Unix, or Windows
+for Alpha, tell us, or we'll assume you're running on Windows for
+Intel as this is overwhelmingly the case.)
+
+\b Tell us what protocol you are connecting with: SSH, Telnet,
+Rlogin or Raw mode.
+
+\b Tell us what kind of server you are connecting to; what OS, and
+if possible what SSH server (if you're using SSH). You can get some
+of this information from the PuTTY Event Log (see \k{using-eventlog}
+in the manual).
+
+\b Send us the contents of the PuTTY Event Log, unless you
+have a specific reason not to (for example, if it contains
+confidential information that you think we should be able to solve
+your problem without needing to know).
+
+\b Try to give us as much information as you can to help us
+see the problem for ourselves. If possible, give us a step-by-step
+sequence of \e{precise} instructions for reproducing the fault.
+
+\b Don't just tell us that PuTTY \q{does the wrong thing}; tell us
+exactly and precisely what it did, and also tell us exactly and
+precisely what you think it should have done instead. Some people
+tell us PuTTY does the wrong thing, and it turns out that it was
+doing the right thing and their expectations were wrong. Help to
+avoid this problem by telling us exactly what you think it should
+have done, and exactly what it did do.
+
+\b If you think you can, you're welcome to try to fix the problem
+yourself. A \i{patch} to the code which fixes a bug is an excellent
+addition to a bug report. However, a patch is never a \e{substitute}
+for a good bug report; if your patch is wrong or inappropriate, and
+you haven't supplied us with full information about the actual bug,
+then we won't be able to find a better solution.
+
+\b
+\W{http://www.chiark.greenend.org.uk/~sgtatham/bugs.html}\cw{http://www.chiark.greenend.org.uk/~sgtatham/bugs.html}
+is an article on how to report bugs effectively in general. If your
+bug report is \e{particularly} unclear, we may ask you to go away,
+read this article, and then report the bug again.
+
+It is reasonable to report bugs in PuTTY's documentation, if you
+think the documentation is unclear or unhelpful. But we do need to
+be given exact details of \e{what} you think the documentation has
+failed to tell you, or \e{how} you think it could be made clearer.
+If your problem is simply that you don't \e{understand} the
+documentation, we suggest posting to a newsgroup (see
+\k{feedback-other-fora}) and seeing if someone
+will explain what you need to know. \e{Then}, if you think the
+documentation could usefully have told you that, send us a bug
+report and explain how you think we should change it.
+
+\H{feedback-features} Requesting extra features
+
+If you want to request a new feature in PuTTY, the very first things
+you should do are:
+
+\b Check the
+\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/wishlist/}{Wishlist
+page} on the PuTTY website, and see if your feature is already on
+the list. If it is, it probably won't achieve very much to repeat
+the request. (But see \k{feedback-feature-priority} if you want to
+persuade us to give your particular feature higher priority.)
+
+\b Check the Wishlist and
+\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/changes.html}{Change
+Log} on the PuTTY website, and see if we have already added your
+feature in the development snapshots. If it isn't clear, download
+the latest development snapshot and see if the feature is present.
+If it is, then it will also be in the next release and there is no
+need to mail us at all.
+
+If you can't find your feature in either the development snapshots
+\e{or} the Wishlist, then you probably do need to submit a feature
+request. Since the PuTTY authors are very busy, it helps if you try
+to do some of the work for us:
+
+\b Do as much of the design as you can. Think about \q{corner
+cases}; think about how your feature interacts with other existing
+features. Think about the user interface; if you can't come up with
+a simple and intuitive interface to your feature, you shouldn't be
+surprised if we can't either. Always imagine whether it's possible
+for there to be more than one, or less than one, of something you'd
+assumed there would be one of. (For example, if you were to want
+PuTTY to put an icon in the System tray rather than the Taskbar, you
+should think about what happens if there's more than one PuTTY
+active; how would the user tell which was which?)
+
+\b If you can program, it may be worth offering to write the feature
+yourself and send us a patch. However, it is likely to be helpful
+if you confer with us first; there may be design issues you haven't
+thought of, or we may be about to make big changes to the code which
+your patch would clash with, or something. If you check with the
+maintainers first, there is a better chance of your code actually
+being usable. Also, read the design principles listed in \k{udp}: if
+you do not conform to them, we will probably not be able to accept
+your patch.
+
+\H{feedback-feature-priority} Requesting features that have already
+been requested
+
+If a feature is already listed on the Wishlist, then it usually
+means we would like to add it to PuTTY at some point. However, this
+may not be in the near future. If there's a feature on the Wishlist
+which you would like to see in the \e{near} future, there are
+several things you can do to try to increase its priority level:
+
+\b Mail us and vote for it. (Be sure to mention that you've seen it
+on the Wishlist, or we might think you haven't even \e{read} the
+Wishlist). This probably won't have very \e{much} effect; if a huge
+number of people vote for something then it may make a difference,
+but one or two extra votes for a particular feature are unlikely to
+change our priority list immediately. Offering a new and compelling
+justification might help. Also, don't expect a reply.
+
+\b Offer us money if we do the work sooner rather than later. This
+sometimes works, but not always. The PuTTY team all have full-time
+jobs and we're doing all of this work in our free time; we may
+sometimes be willing to give up some more of our free time in
+exchange for some money, but if you try to bribe us for a \e{big}
+feature it's entirely possible that we simply won't have the time to
+spare - whether you pay us or not. (Also, we don't accept bribes to
+add \e{bad} features to the Wishlist, because our desire to provide
+high-quality software to the users comes first.)
+
+\b Offer to help us write the code. This is probably the \e{only}
+way to get a feature implemented quickly, if it's a big one that we
+don't have time to do ourselves.
+
+\H{feedback-support} \ii{Support requests}
+
+If you're trying to make PuTTY do something for you and it isn't
+working, but you're not sure whether it's a bug or not, then
+\e{please} consider looking for help somewhere else. This is one of
+the most common types of mail the PuTTY team receives, and we simply
+don't have time to answer all the questions. Questions of this type
+include:
+
+\b If you want to do something with PuTTY but have no idea where to
+start, and reading the manual hasn't helped, try posting to a
+newsgroup (see \k{feedback-other-fora}) and see if someone can explain
+it to you.
+
+\b If you have tried to do something with PuTTY but it hasn't
+worked, and you aren't sure whether it's a bug in PuTTY or a bug in
+your SSH server or simply that you're not doing it right, then try
+posting to a newsgroup (see \k{feedback-other-fora}) and see
+if someone can solve your problem. Or try doing the same thing with
+a different SSH client and see if it works with that. Please do not
+report it as a PuTTY bug unless you are really sure it \e{is} a bug
+in PuTTY.
+
+\b If someone else installed PuTTY for you, or you're using PuTTY on
+someone else's computer, try asking them for help first. They're more
+likely to understand how they installed it and what they expected you
+to use it for than we are.
+
+\b If you have successfully made a connection to your server and now
+need to know what to type at the server's command prompt, or other
+details of how to use the server-end software, talk to your server's
+system administrator. This is not the PuTTY team's problem. PuTTY is
+only a communications tool, like a telephone; if you can't speak the
+same language as the person at the other end of the phone, it isn't
+the telephone company's job to teach it to you.
+
+If you absolutely cannot get a support question answered any other
+way, you can try mailing it to us, but we can't guarantee to have
+time to answer it.
+
+\H{feedback-webadmin} Web server administration
+
+If the PuTTY \i{web site} is down (Connection Timed Out), please don't
+bother mailing us to tell us about it. Most of us read our e-mail on
+the same machines that host the web site, so if those machines are
+down then we will notice \e{before} we read our e-mail. So there's
+no point telling us our servers are down.
+
+Of course, if the web site has some other error (Connection Refused,
+404 Not Found, 403 Forbidden, or something else) then we might
+\e{not} have noticed and it might still be worth telling us about it.
+
+If you want to report a problem with our web site, check that you're
+looking at our \e{real} web site and not a mirror. The real web site
+is at
+\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/}\c{http://www.chiark.greenend.org.uk/~sgtatham/putty/};
+if that's not where you're reading this, then don't report the
+problem to us until you've checked that it's really a problem with
+the main site. If it's only a problem with the mirror, you should
+try to contact the administrator of that mirror site first, and only
+contact us if that doesn't solve the problem (in case we need to
+remove the mirror from our list).
+
+\H{feedback-permission} Asking permission for things
+
+PuTTY is distributed under the MIT Licence (see \k{licence} for
+details). This means you can do almost \e{anything} you like with
+our software, our source code, and our documentation. The only
+things you aren't allowed to do are to remove our copyright notices
+or the licence text itself, or to hold us legally responsible if
+something goes wrong.
+
+So if you want permission to include PuTTY on a magazine cover disk,
+or as part of a collection of useful software on a CD or a web site,
+then \e{permission is already granted}. You don't have to mail us
+and ask. Just go ahead and do it. We don't mind.
+
+(If you want to distribute PuTTY alongside your own application for
+use with that application, or if you want to distribute PuTTY within
+your own organisation, then we recommend, but do not insist, that
+you offer your own first-line technical support, to answer questions
+about the interaction of PuTTY with your environment. If your users
+mail us directly, we won't be able to tell them anything useful about
+your specific setup.)
+
+If you want to use parts of the PuTTY source code in another
+program, then it might be worth mailing us to talk about technical
+details, but if all you want is to ask permission then you don't
+need to bother. You already have permission.
+
+If you just want to link to our web site, just go ahead. (It's not
+clear that we \e{could} stop you doing this, even if we wanted to!)
+
+\H{feedback-mirrors} Mirroring the PuTTY web site
+
+\# the next two paragraphs also on the Mirrors page itself, with
+\# minor context changes
+
+If you want to set up a mirror of the PuTTY website, go ahead and
+set one up. Please don't bother asking us for permission before
+setting up a mirror. You already have permission.
+
+If the mirror is in a country where we don't already have plenty of
+mirrors, we may be willing to add it to the list on our
+\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/mirrors.html}{mirrors
+page}. Read the guidelines on that page, make sure your mirror
+works, and email us the information listed at the bottom of the
+page.
+
+Note that we do not \e{promise} to list your mirror: we get a lot of
+mirror notifications and yours may not happen to find its way to the
+top of the list.
+
+Also note that we link to all our mirror sites using the
+\c{rel="nofollow"} attribute. Running a PuTTY mirror is not intended
+to be a cheap way to gain search rankings.
+
+If you have technical questions about the process of mirroring, then
+you might want to mail us before setting up the mirror (see also the
+\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/mirrors.html#guidelines}{guidelines on the Mirrors page});
+but if you just want to ask for permission, you don't need to. You
+already have permission.
+
+\H{feedback-compliments} Praise and compliments
+
+One of the most rewarding things about maintaining free software is
+getting e-mails that just say \q{thanks}. We are always happy to
+receive e-mails of this type.
+
+Regrettably we don't have time to answer them all in person. If you
+mail us a compliment and don't receive a reply, \e{please} don't
+think we've ignored you. We did receive it and we were happy about
+it; we just didn't have time to tell you so personally.
+
+To everyone who's ever sent us praise and compliments, in the past
+and the future: \e{you're welcome}!
+
+\H{feedback-address} E-mail address
+
+The actual address to mail is
+\cw{<\W{mailto:putty@projects.tartarus.org}{putty@projects.tartarus.org}>}.
--- /dev/null
+\define{versionidgs} \versionid $Id$
+
+\C{gs} Getting started with PuTTY
+
+This chapter gives a quick guide to the simplest types of
+interactive login session using PuTTY.
+
+\H{gs-insecure} \ii{Starting a session}
+
+When you start PuTTY, you will see a \i{dialog box}. This dialog box
+allows you to control everything PuTTY can do. See \k{config} for
+details of all the things you can control.
+
+You don't usually need to change most of the configuration options.
+To start the simplest kind of session, all you need to do is to
+enter a few basic parameters.
+
+In the \q{Host Name} box, enter the Internet \i{host name} of the server
+you want to connect to. You should have been told this by the
+provider of your login account.
+
+Now select a login \i{protocol} to use, from the \q{Connection type}
+buttons. For a login session, you should select \i{Telnet},
+\i{Rlogin} or \i{SSH}. See \k{which-one} for a description of the
+differences between the three protocols, and advice on which one to
+use. The fourth protocol, \I{raw protocol}\e{Raw}, is not used for
+interactive login sessions; you would usually use this for debugging
+other Internet services (see \k{using-rawprot}). The fifth option,
+\e{Serial}, is used for connecting to a local serial line, and works
+somewhat differently: see \k{using-serial} for more information on
+this.
+
+When you change the selected protocol, the number in the \q{Port}
+box will change. This is normal: it happens because the various
+login services are usually provided on different network ports by
+the server machine. Most servers will use the standard port numbers,
+so you will not need to change the port setting. If your server
+provides login services on a non-standard port, your system
+administrator should have told you which one. (For example, many
+\i{MUDs} run Telnet service on a port other than 23.)
+
+Once you have filled in the \q{Host Name}, \q{Protocol}, and
+possibly \q{Port} settings, you are ready to connect. Press the
+\q{Open} button at the bottom of the dialog box, and PuTTY will
+begin trying to connect you to the server.
+
+\H{gs-hostkey} \ii{Verifying the host key} (SSH only)
+
+If you are not using the \i{SSH} protocol, you can skip this
+section.
+
+If you are using SSH to connect to a server for the first time, you
+will probably see a message looking something like this:
+
+\c The server's host key is not cached in the registry. You
+\c have no guarantee that the server is the computer you
+\c think it is.
+\c The server's rsa2 key fingerprint is:
+\c ssh-rsa 1024 7b:e5:6f:a7:f4:f9:81:62:5c:e3:1f:bf:8b:57:6c:5a
+\c If you trust this host, hit Yes to add the key to
+\c PuTTY's cache and carry on connecting.
+\c If you want to carry on connecting just once, without
+\c adding the key to the cache, hit No.
+\c If you do not trust this host, hit Cancel to abandon the
+\c connection.
+
+This is a feature of the SSH protocol. It is designed to protect you
+against a network attack known as \i\e{spoofing}: secretly
+redirecting your connection to a different computer, so that you
+send your password to the wrong machine. Using this technique, an
+attacker would be able to learn the password that guards your login
+account, and could then log in as if they were you and use the
+account for their own purposes.
+
+To prevent this attack, each server has a unique identifying code,
+called a \e{host key}. These keys are created in a way that prevents
+one server from forging another server's key. So if you connect to a
+server and it sends you a different host key from the one you were
+expecting, PuTTY can warn you that the server may have been switched
+and that a spoofing attack might be in progress.
+
+PuTTY records the host key for each server you connect to, in the
+Windows \i{Registry}. Every time you connect to a server, it checks
+that the host key presented by the server is the same host key as it
+was the last time you connected. If it is not, you will see a
+warning, and you will have the chance to abandon your connection
+before you type any private information (such as a password) into
+it.
+
+However, when you connect to a server you have not connected to
+before, PuTTY has no way of telling whether the host key is the
+right one or not. So it gives the warning shown above, and asks you
+whether you want to \I{trusting host keys}trust this host key or
+not.
+
+Whether or not to trust the host key is your choice. If you are
+connecting within a company network, you might feel that all the
+network users are on the same side and spoofing attacks are
+unlikely, so you might choose to trust the key without checking it.
+If you are connecting across a hostile network (such as the
+Internet), you should check with your system administrator, perhaps
+by telephone or in person. (Some modern servers have more than one
+host key. If the system administrator sends you more than one
+\I{host key fingerprint}fingerprint, you should make sure the one
+PuTTY shows you is on the list, but it doesn't matter which one it is.)
+
+\# FIXME: this is all very fine but of course in practice the world
+doesn't work that way. Ask the team if they have any good ideas for
+changes to this section!
+
+\H{gs-login} \ii{Logging in}
+
+After you have connected, and perhaps verified the server's host
+key, you will be asked to log in, probably using a \i{username} and
+a \i{password}. Your system administrator should have provided you
+with these. Enter the username and the password, and the server
+should grant you access and begin your session. If you have
+\I{mistyping a password}mistyped your password, most servers will
+give you several chances to get it right.
+
+If you are using SSH, be careful not to type your username wrongly,
+because you will not have a chance to correct it after you press
+Return; many SSH servers do not permit you to make two login attempts
+using \i{different usernames}. If you type your username wrongly, you
+must close PuTTY and start again.
+
+If your password is refused but you are sure you have typed it
+correctly, check that Caps Lock is not enabled. Many login servers,
+particularly Unix computers, treat upper case and lower case as
+different when checking your password; so if Caps Lock is on, your
+password will probably be refused.
+
+\H{gs-session} After logging in
+
+After you log in to the server, what happens next is up to the
+server! Most servers will print some sort of login message and then
+present a \i{prompt}, at which you can type
+\I{commands on the server}commands which the
+server will carry out. Some servers will offer you on-line help;
+others might not. If you are in doubt about what to do next, consult
+your system administrator.
+
+\H{gs-logout} \ii{Logging out}
+
+When you have finished your session, you should log out by typing
+the server's own logout command. This might vary between servers; if
+in doubt, try \c{logout} or \c{exit}, or consult a manual or your
+system administrator. When the server processes your logout command,
+the PuTTY window should close itself automatically.
+
+You \e{can} close a PuTTY session using the \i{Close button} in the
+window border, but this might confuse the server - a bit like
+hanging up a telephone unexpectedly in the middle of a conversation.
+We recommend you do not do this unless the server has stopped
+responding to you and you cannot close the window any other way.
--- /dev/null
+\define{versionidindex} \versionid $Id$
+
+\IM{Unix version} Unix version of PuTTY tools
+\IM{Unix version} Linux version of PuTTY tools
+
+\IM{Unix} Unix
+\IM{Unix} Linux
+
+\IM{Command Prompt}{command prompt window}{MS-DOS Prompt}{console window} Command Prompt
+\IM{Command Prompt}{command prompt window}{MS-DOS Prompt}{console window} MS-DOS Prompt
+\IM{Command Prompt}{command prompt window}{MS-DOS Prompt}{console window} console window
+
+\IM{spoof}{spoofed}{spoofing} spoofing
+
+\IM{verifying the host key} verifying the host key
+\IM{verifying the host key} host key, verifying
+
+\IM{trusting host keys} trusting host keys
+\IM{trusting host keys} host keys, trusting
+
+\IM{host key fingerprint} fingerprint, of SSH host key
+\IM{host key fingerprint} host key fingerprint (SSH)
+\IM{host key fingerprint} SSH host key fingerprint
+
+\IM{starting a session} starting a session
+\IM{starting a session} session, starting
+
+\IM{commands on the server}{remote command} commands on the server
+\IM{commands on the server}{remote command} remote commands
+\IM{commands on the server}{remote command} server, commands on
+
+\IM{mistyping a password} mistyping a password
+\IM{mistyping a password} password, mistyping
+
+\IM{different usernames}{changes of username} different user names
+\IM{different usernames}{changes of username} changing user names
+\IM{different usernames}{changes of username} user names, different
+\IM{different usernames}{changes of username} login names, different
+\IM{different usernames}{changes of username} account names, different
+
+\IM{differences between SSH, Telnet and Rlogin} differences between
+SSH, Telnet and Rlogin
+\IM{differences between SSH, Telnet and Rlogin} protocols,
+differences between
+\IM{differences between SSH, Telnet and Rlogin} SSH, differences
+from Telnet and Rlogin
+\IM{differences between SSH, Telnet and Rlogin} Telnet, differences
+from SSH and Rlogin
+\IM{differences between SSH, Telnet and Rlogin} Rlogin, differences
+from SSH and Telnet
+\IM{differences between SSH, Telnet and Rlogin} selecting a protocol
+\IM{differences between SSH, Telnet and Rlogin} choosing a protocol
+
+\IM{MUD}{MUDs} MUDs
+
+\IM{talker}{talker systems} talker systems
+
+\IM{security hazard}{security risk} security hazard
+
+\IM{SSH-1}{SSH protocol version 1} SSH-1
+\IM{SSH-2}{SSH protocol version 2} SSH-2
+
+\IM{terminal window}{PuTTY window} terminal window
+\IM{terminal window}{PuTTY window} PuTTY terminal window
+\IM{terminal window}{PuTTY window} window, terminal
+
+\IM{copy and paste} copy and paste
+\IM{copy and paste} cut and paste
+\IM{copy and paste} paste, copy and
+
+\IM{three-button mouse} three-button mouse
+\IM{three-button mouse} mouse, three-button
+
+\IM{left mouse button}{left button} left mouse button
+\IM{middle mouse button}{middle button} middle mouse button
+\IM{right mouse button}{right button} right mouse button
+
+\IM{selecting words}{word-by-word selection} selecting whole words
+\IM{selecting words}{word-by-word selection} words, selecting
+
+\IM{selecting lines} selecting whole lines
+\IM{selecting lines} lines, selecting
+
+\IM{rectangular selection} rectangular selection
+\IM{rectangular selection} selection, rectangular
+
+\IM{adjusting a selection} adjusting a selection
+\IM{adjusting a selection} extending a selection
+\IM{adjusting a selection} selection, adjusting
+
+\IM{right mouse button, with Ctrl} right mouse button, with Ctrl
+\IM{right mouse button, with Ctrl} Ctrl, with right mouse button
+
+\IM{system menu} system menu
+\IM{system menu} menu, system
+\IM{system menu} window menu
+
+\IM{context menu} context menu
+\IM{context menu} menu, context
+\IM{context menu} right mouse button menu
+
+\IM{Event Log} Event Log
+\IM{Event Log} PuTTY Event Log
+\IM{Event Log} Log, Event
+
+\IM{Telnet special commands} Telnet special commands
+\IM{Telnet special commands} special commands, in Telnet
+
+\IM{SSH special commands} SSH special commands
+\IM{SSH special commands} special commands, in SSH
+
+\IM{Repeat key exchange, SSH special command} Repeat key exchange, SSH special command
+\IM{Repeat key exchange, SSH special command} key exchange, forcing repeat
+\IM{Repeat key exchange, SSH special command} SSH key exchange, forcing repeat
+
+\IM{accented characters} accented characters
+\IM{accented characters} characters, accented
+
+\IM{line-drawing characters} line-drawing characters
+\IM{line-drawing characters} box-drawing characters
+\IM{line-drawing characters} characters, line-drawing
+\IM{line-drawing characters} ANSI graphics
+
+\IM{port forwarding}{port forwardings} port forwarding in SSH
+\IM{port forwarding}{port forwardings} SSH port forwarding
+\IM{port forwarding}{port forwardings} forwarding ports in SSH
+\IM{port forwarding}{port forwardings} tunnelling using SSH
+\IM{port forwarding}{port forwardings} SSH tunnelling
+
+\IM{port forwarding, changing mid-session} port forwarding in SSH, changing mid-session
+\IM{port forwarding, changing mid-session} SSH port forwarding, changing mid-session
+\IM{port forwarding, changing mid-session} forwarding ports in SSH, changing mid-session
+\IM{port forwarding, changing mid-session} tunnelling using SSH, changing mid-session
+\IM{port forwarding, changing mid-session} SSH tunnelling, changing mid-session
+
+\IM{local port forwarding} local-to-remote port forwarding
+\IM{remote port forwarding} remote-to-local port forwarding
+
+\IM{dynamic port forwarding} dynamic port forwarding
+\IM{dynamic port forwarding} SOCKS port forwarding
+
+\IM{debugging Internet protocols} debugging Internet protocols
+\IM{debugging Internet protocols} Internet protocols, debugging
+\IM{debugging Internet protocols} protocols, debugging
+
+\IM{Internet protocol version} Internet Protocol version
+\IM{Internet protocol version} version, of Internet Protocol
+
+\IM{raw TCP connections} raw TCP connections
+\IM{raw TCP connections} TCP connections, raw
+
+\IM{command-line arguments} command-line arguments
+\IM{command-line arguments} arguments, command-line
+\IM{command-line arguments} options, command-line
+\IM{command-line arguments} switches, command-line
+
+\IM{Windows shortcut} Windows shortcut
+\IM{Windows shortcut} shortcut, Windows
+
+\IM{telnet URLs} Telnet URLs
+\IM{telnet URLs} URLs, Telnet
+
+\IM{saved sessions, loading from command line} saved sessions,
+loading from command line
+\IM{saved sessions, loading from command line} loading saved
+sessions from command line
+\IM{saved sessions, loading from command line} command line, loading
+saved sessions from
+
+\IM{putty @sessionname} \c{putty @sessionname}
+\IM{putty @sessionname} \c{@sessionname} command-line argument
+
+\IM{protocol selection} protocol selection
+\IM{protocol selection} selecting a protocol
+\IM{protocol selection} choosing a protocol
+
+\IM{login name}{username} login name
+\IM{login name}{username} user name
+\IM{login name}{username} account name
+
+\IM{reading commands from a file} reading commands from a file
+\IM{reading commands from a file} commands, reading from a file
+
+\IM{agent forwarding} agent forwarding
+\IM{agent forwarding} authentication agent forwarding
+\IM{agent forwarding} SSH agent forwarding
+\IM{agent forwarding} forwarding, SSH agent
+
+\IM{X11 forwarding}{forwarding of X11} X11 forwarding
+\IM{X11 forwarding}{forwarding of X11} SSH X11 forwarding
+\IM{X11 forwarding}{forwarding of X11} forwarding, of X11
+
+\IM{X11 authentication} X11 authentication
+\IM{X11 authentication} authentication, X11
+
+\IM{pseudo-terminal allocation} pseudo-terminal allocation
+\IM{pseudo-terminal allocation} pty allocation
+\IM{pseudo-terminal allocation} allocation, of pseudo-terminal
+
+\IM{ERASE special character} \cw{ERASE}, special character
+\IM{ERASE special character} \cw{VERASE}, special character
+\IM{QUIT special character} \cw{QUIT}, special character
+\IM{QUIT special character} \cw{VQUIT}, special character
+
+\IM{-telnet} \c{-telnet} command-line option
+\IM{-raw} \c{-raw} command-line option
+\IM{-rlogin} \c{-rlogin} command-line option
+\IM{-ssh} \c{-ssh} command-line option
+\IM{-serial} \c{-serial} command-line option
+\IM{-cleanup} \c{-cleanup} command-line option
+\IM{-load} \c{-load} command-line option
+\IM{-v} \c{-v} command-line option
+\IM{-l} \c{-l} command-line option
+\IM{-L-upper} \c{-L} command-line option
+\IM{-R-upper} \c{-R} command-line option
+\IM{-D-upper} \c{-D} command-line option
+\IM{-m} \c{-m} command-line option
+\IM{-P-upper} \c{-P} command-line option
+\IM{-pw} \c{-pw} command-line option
+\IM{-A-upper} \c{-A} command-line option
+\IM{-a} \c{-a} command-line option
+\IM{-X-upper} \c{-X} command-line option
+\IM{-x} \c{-x} command-line option
+\IM{-T-upper} \c{-T} command-line option
+\IM{-t} \c{-t} command-line option
+\IM{-C-upper} \c{-C} command-line option
+\IM{-N-upper} \c{-N} command-line option
+\IM{-1} \c{-1} command-line option
+\IM{-2} \c{-2} command-line option
+\IM{-i} \c{-i} command-line option
+\IM{-pgpfp} \c{-pgpfp} command-line option
+\IM{-sercfg} \c{-sercfg} command-line option
+
+\IM{removing registry entries} removing registry entries
+\IM{removing registry entries} registry entries, removing
+
+\IM{random seed file} random seed file
+\IM{random seed file} \c{putty.rnd} (random seed file)
+
+\IM{putty.rnd} \c{putty.rnd} (random seed file)
+
+\IM{suppressing remote shell} remote shell, suppressing
+\IM{suppressing remote shell} shell, remote, suppressing
+
+\IM{SSH protocol version} SSH protocol version
+\IM{SSH protocol version} protocol version, SSH
+\IM{SSH protocol version} version, of SSH protocol
+
+\IM{PPK} \cw{PPK} file
+\IM{PPK} private key file, PuTTY
+
+\IM{PGP key fingerprint} PGP key fingerprint
+\IM{PGP key fingerprint} fingerprint, of PGP key
+
+\IM{verifying new versions} verifying new versions of PuTTY
+\IM{verifying new versions} new version, verifying
+\IM{verifying new versions} upgraded version, verifying
+
+\IM{connection}{network connection} network connection
+\IM{connection}{network connection} connection, network
+
+\IM{host name}{hostname} host name
+\IM{host name}{hostname} DNS name
+\IM{host name}{hostname} server name
+
+\IM{IP address}{Internet address} IP address
+\IM{IP address}{Internet address} address, IP
+
+\IM{localhost} \c{localhost}
+
+\IM{loopback IP address}{loopback address} loopback IP address
+\IM{loopback IP address}{loopback address} IP address, loopback
+
+\IM{listen address} listen address
+\IM{listen address} bind address
+
+\IM{DNS} DNS
+\IM{DNS} Domain Name System
+
+\IM{name resolution} name resolution
+\IM{name resolution} DNS resolution
+\IM{name resolution} host name resolution
+\IM{name resolution} server name resolution
+
+\IM{loading and storing saved sessions} sessions, loading and storing
+\IM{loading and storing saved sessions} settings, loading and storing
+\IM{loading and storing saved sessions} saving settings
+\IM{loading and storing saved sessions} storing settings
+\IM{loading and storing saved sessions} loading settings
+
+\IM{Default Settings} Default Settings
+\IM{Default Settings} settings, default
+
+\IM{Registry} Registry (Windows)
+\IM{Registry} Windows Registry
+
+\IM{inactive window} inactive window
+\IM{inactive window} window, inactive
+\IM{inactive window} terminal window, inactive
+
+\IM{SSH packet log} SSH packet log
+\IM{SSH packet log} packet log, SSH
+
+\IM{auto wrap mode}{auto wrap} auto wrap mode
+\IM{auto wrap mode}{auto wrap} wrapping, automatic
+\IM{auto wrap mode}{auto wrap} line wrapping, automatic
+
+\IM{control sequence}{control codes} control sequences
+\IM{control sequence}{control codes} terminal control sequences
+\IM{control sequence}{control codes} escape sequences
+
+\IM{cursor coordinates} cursor coordinates
+\IM{cursor coordinates} coordinates, cursor
+
+\IM{CR} CR (Carriage Return)
+\IM{CR} Carriage Return
+
+\IM{LF} LF (Line Feed)
+\IM{LF} Line Feed
+
+\IM{clear screen} clear screen
+\IM{clear screen} erase screen
+\IM{clear screen} screen, clearing
+
+\IM{blinking text} blinking text
+\IM{blinking text} flashing text
+
+\IM{answerback} answerback string
+
+\IM{local echo} local echo
+\IM{local echo} echo, local
+
+\IM{remote echo} remote echo
+\IM{remote echo} echo, remote
+
+\IM{local line editing} local line editing
+\IM{local line editing} line editing, local
+
+\IM{remote-controlled printing} ANSI printing
+\IM{remote-controlled printing} remote-controlled printing
+\IM{remote-controlled printing} printing, remote-controlled
+
+\IM{Home and End keys} Home key
+\IM{Home and End keys} End key
+
+\IM{keypad} keypad, numeric
+\IM{keypad} numeric keypad
+
+\IM{Application Cursor Keys} Application Cursor Keys
+\IM{Application Cursor Keys} cursor keys, \q{Application} mode
+
+\IM{Application Keypad} Application Keypad
+\IM{Application Keypad} keypad, \q{Application} mode
+\IM{Application Keypad} numeric keypad, \q{Application} mode
+
+\IM{Num Lock}{NumLock} Num Lock
+
+\IM{NetHack keypad mode} NetHack keypad mode
+\IM{NetHack keypad mode} keypad, NetHack mode
+
+\IM{compose key} Compose key
+\IM{compose key} DEC Compose key
+
+\IM{terminal bell} terminal bell
+\IM{terminal bell} bell, terminal
+\IM{terminal bell} beep, terminal
+\IM{terminal bell} feep
+
+\IM{Windows Default Beep} Windows Default Beep sound
+\IM{Windows Default Beep} Default Beep sound, Windows
+
+\IM{terminal bell, disabling} terminal bell, disabling
+\IM{terminal bell, disabling} bell, disabling
+
+\IM{visual bell} visual bell
+\IM{visual bell} bell, visual
+
+\IM{PC speaker} PC speaker
+\IM{PC speaker} beep, with PC speaker
+
+\IM{sound file} sound file
+\IM{sound file} \cw{WAV} file
+
+\IM{bell overload} bell overload mode
+\IM{bell overload} terminal bell overload mode
+
+\IM{mouse reporting} mouse reporting
+\IM{mouse reporting} \c{xterm} mouse reporting
+
+\IM{links} \c{links} (web browser)
+
+\IM{mc} \c{mc}
+\IM{mc} Midnight Commander
+
+\IM{terminal resizing}{window resizing} terminal resizing
+\IM{terminal resizing}{window resizing} window resizing
+\IM{terminal resizing}{window resizing} resizing, terminal
+
+\IM{destructive backspace} destructive backspace
+\IM{destructive backspace} non-destructive backspace
+\IM{destructive backspace} backspace, destructive
+
+\IM{Arabic text shaping} Arabic text shaping
+\IM{Arabic text shaping} shaping, of Arabic text
+
+\IM{Unicode} Unicode
+\IM{Unicode} ISO-10646 (Unicode)
+
+\IM{ASCII} ASCII
+\IM{ASCII} US-ASCII
+
+\IM{bidirectional text} bidirectional text
+\IM{bidirectional text} right-to-left text
+
+\IM{display becomes corrupted} display corruption
+\IM{display becomes corrupted} corruption, of display
+
+\IM{rows} rows, in terminal window
+\IM{columns} columns, in terminal window
+
+\IM{window size} window size
+\IM{window size} size, of window
+
+\IM{font size} font size
+\IM{font size} size, of font
+
+\IM{full screen}{full-screen} full-screen mode
+
+\IM{cursor blinks} blinking cursor
+\IM{cursor blinks} flashing cursor
+\IM{cursor blinks} cursor, blinking
+
+\IM{font} font
+\IM{font} typeface
+
+\IM{minimise} minimise window
+\IM{minimise} window, minimising
+
+\IM{maximise} maximise window
+\IM{maximise} window, maximising
+
+\IM{closing window}{close window} closing window
+\IM{closing window}{close window} window, closing
+
+\IM{Dragon NaturallySpeaking} Dragon NaturallySpeaking
+\IM{Dragon NaturallySpeaking} NaturallySpeaking
+
+\IM{AltGr} \q{AltGr} key
+\IM{Alt} \q{Alt} key
+
+\IM{CJK} CJK
+\IM{CJK} Chinese
+\IM{CJK} Japanese
+\IM{CJK} Korean
+
+\IM{East Asian Ambiguous characters} East Asian Ambiguous characters
+\IM{East Asian Ambiguous characters} CJK ambiguous characters
+
+\IM{character width} character width
+\IM{character width} single-width character
+\IM{character width} double-width character
+
+\IM{Rich Text Format} Rich Text Format
+\IM{Rich Text Format} RTF
+
+\IM{bold}{bold text} bold text
+
+\IM{colour}{colours} colour
+
+\IM{8-bit colour} 8-bit colour
+\IM{8-bit colour} colour, 8-bit
+
+\IM{system colours} system colours
+\IM{system colours} colours, system
+
+\IM{ANSI colours} ANSI colours
+\IM{ANSI colours} colours, ANSI
+
+\IM{cursor colour} cursor colour
+\IM{cursor colour} colour, of cursor
+
+\IM{default background} background colour, default
+\IM{default background} colour, background, default
+
+\IM{default foreground} foreground colour, default
+\IM{default foreground} colour, foreground, default
+
+\IM{TERM} \cw{TERM} environment variable
+
+\IM{logical palettes} logical palettes
+\IM{logical palettes} palettes, logical
+
+\IM{breaks in connectivity} connectivity, breaks in
+\IM{breaks in connectivity} intermittent connectivity
+
+\IM{idle connections} idle connections
+\IM{idle connections} timeout, of connections
+\IM{idle connections} connections, idle
+
+\IM{interactive connections}{interactive session} interactive connections
+\IM{interactive connections}{interactive session} connections, interactive
+
+\IM{keepalives} keepalives, application
+
+\IM{Nagle's algorithm} Nagle's algorithm
+\IM{Nagle's algorithm} \cw{TCP_NODELAY}
+
+\IM{TCP keepalives} TCP keepalives
+\IM{TCP keepalives} keepalives, TCP
+\IM{TCP keepalives} \cw{SO_KEEPALIVE}
+
+\IM{half-open connections} half-open connections
+\IM{half-open connections} connections, half-open
+
+\IM{auto-login username} user name, for auto-login
+\IM{auto-login username} login name, for auto-login
+\IM{auto-login username} account name, for auto-login
+
+\IM{terminal emulation}{terminal-type} terminal emulation
+\IM{terminal emulation}{terminal-type} emulation, terminal
+
+\IM{terminal speed} terminal speed
+\IM{terminal speed} speed, terminal
+\IM{terminal speed} baud rate, of terminal
+
+\IM{environment variables} environment variables
+\IM{environment variables} variables, environment
+
+\IM{proxy} proxy server
+\IM{proxy} server, proxy
+
+\IM{HTTP proxy} HTTP proxy
+\IM{HTTP proxy} proxy, HTTP
+\IM{HTTP proxy} server, HTTP
+\IM{HTTP proxy} \cw{CONNECT} proxy (HTTP)
+
+\IM{SOCKS server} SOCKS proxy
+\IM{SOCKS server} server, SOCKS
+\IM{SOCKS server} proxy, SOCKS
+
+\IM{Telnet proxy} Telnet proxy
+\IM{Telnet proxy} TCP proxy
+\IM{Telnet proxy} ad-hoc proxy
+\IM{Telnet proxy} proxy, Telnet
+
+\IM{Local proxy} local proxy
+\IM{Local proxy} proxy command
+\IM{Local proxy} command, proxy
+
+\IM{proxy DNS} proxy DNS
+\IM{proxy DNS} DNS, with proxy
+\IM{proxy DNS} name resolution, with proxy
+\IM{proxy DNS} host name resolution, with proxy
+\IM{proxy DNS} server name resolution, with proxy
+
+\IM{proxy username} proxy user name
+\IM{proxy username} user name, for proxy
+\IM{proxy username} login name, for proxy
+\IM{proxy username} account name, for proxy
+
+\IM{proxy password} proxy password
+\IM{proxy password} password, for proxy
+
+\IM{proxy authentication} proxy authentication
+\IM{proxy authentication} authentication, to proxy
+
+\IM{HTTP basic} HTTP \q{basic} authentication
+\IM{HTTP basic} \q{basic} authentication (HTTP)
+
+\IM{plaintext password} plain text password
+\IM{plaintext password} password, plain text
+
+\IM{Telnet negotiation} Telnet option negotiation
+\IM{Telnet negotiation} option negotiation, Telnet
+\IM{Telnet negotiation} negotiation, of Telnet options
+
+\IM{firewall}{firewalls} firewalls
+
+\IM{NAT router}{NAT} NAT routers
+\IM{NAT router}{NAT} routers, NAT
+\IM{NAT router}{NAT} Network Address Translation
+\IM{NAT router}{NAT} IP masquerading
+
+\IM{Telnet New Line} Telnet New Line
+\IM{Telnet New Line} new line, in Telnet
+
+\IM{.rhosts} \c{.rhosts} file
+\IM{.rhosts} \q{rhosts} file
+
+\IM{passwordless login} passwordless login
+\IM{passwordless login} login, passwordless
+
+\IM{Windows user name} local user name, in Windows
+\IM{Windows user name} user name, local, in Windows
+\IM{Windows user name} login name, local, in Windows
+\IM{Windows user name} account name, local, in Windows
+
+\IM{local username in Rlogin} local user name, in Rlogin
+\IM{local username in Rlogin} user name, local, in Rlogin
+\IM{local username in Rlogin} login name, local, in Rlogin
+\IM{local username in Rlogin} account name, local, in Rlogin
+
+\IM{privileged port} privileged port
+\IM{privileged port} low-numbered port
+\IM{privileged port} port, privileged
+
+\IM{remote shell} shell, remote
+\IM{remote shell} remote shell
+
+\IM{encryption}{encrypted}{encrypt} encryption
+
+\IM{encryption algorithm} encryption algorithm
+\IM{encryption algorithm} cipher algorithm
+\IM{encryption algorithm} symmetric-key algorithm
+\IM{encryption algorithm} algorithm, encryption
+
+\IM{AES} AES
+\IM{AES} Advanced Encryption Standard
+\IM{AES} Rijndael
+
+\IM{Arcfour} Arcfour
+\IM{Arcfour} RC4
+
+\IM{triple-DES} triple-DES
+
+\IM{single-DES} single-DES
+\IM{single-DES} DES
+
+\IM{key exchange} key exchange
+\IM{key exchange} kex
+
+\IM{shared secret} shared secret
+\IM{shared secret} secret, shared
+
+\IM{key exchange algorithm} key exchange algorithm
+\IM{key exchange algorithm} algorithm, key exchange
+
+\IM{Diffie-Hellman key exchange} Diffie-Hellman key exchange
+\IM{Diffie-Hellman key exchange} key exchange, Diffie-Hellman
+
+\IM{group exchange} Diffie-Hellman group exchange
+\IM{group exchange} group exchange, Diffie-Hellman
+
+\IM{repeat key exchange} repeat key exchange
+\IM{repeat key exchange} key exchange, repeat
+
+\IM{challenge/response authentication} challenge/response authentication
+\IM{challenge/response authentication} authentication, challenge/response
+
+\IM{security token} security token
+\IM{security token} token, security
+
+\IM{one-time passwords} one-time passwords
+\IM{one-time passwords} password, one-time
+
+\IM{keyboard-interactive authentication} keyboard-interactive authentication
+\IM{keyboard-interactive authentication} authentication, keyboard-interactive
+
+\IM{password expiry} password expiry
+\IM{password expiry} expiry, of passwords
+
+\IM{public key authentication}{public-key authentication} public key authentication
+\IM{public key authentication}{public-key authentication} RSA authentication
+\IM{public key authentication}{public-key authentication} DSA authentication
+\IM{public key authentication}{public-key authentication} authentication, public key
+
+\IM{MIT-MAGIC-COOKIE-1} \cw{MIT-MAGIC-COOKIE-1}
+\IM{MIT-MAGIC-COOKIE-1} magic cookie
+\IM{MIT-MAGIC-COOKIE-1} cookie, magic
+
+\IM{SSH server bugs} SSH server bugs
+\IM{SSH server bugs} bugs, in SSH servers
+
+\IM{ignore message} SSH \q{ignore} messages
+\IM{ignore message} \q{ignore} messages, in SSH
+
+\IM{message authentication code}{MAC} message authentication code (MAC)
+\IM{message authentication code}{MAC} MAC (message authentication code)
+
+\IM{signatures} signature
+\IM{signatures} digital signature
+
+\IM{storing configuration in a file} storing settings in a file
+\IM{storing configuration in a file} saving settings in a file
+\IM{storing configuration in a file} loading settings from a file
+
+\IM{transferring files} transferring files
+\IM{transferring files} files, transferring
+
+\IM{receiving files}{download a file} receiving files
+\IM{receiving files}{download a file} files, receiving
+\IM{receiving files}{download a file} downloading files
+
+\IM{sending files}{upload a file} sending files
+\IM{sending files}{upload a file} files, sending
+\IM{sending files}{upload a file} uploading files
+
+\IM{listing files} listing files
+\IM{listing files} files, listing
+
+\IM{wildcard}{wildcards} wildcards
+\IM{wildcard}{wildcards} glob (wildcard)
+
+\IM{PATH} \c{PATH} environment variable
+
+\IM{SFTP} SFTP
+\IM{SFTP} SSH file transfer protocol
+
+\IM{-unsafe} \c{-unsafe} PSCP command-line option
+\IM{-ls-PSCP} \c{-ls} PSCP command-line option
+\IM{-p-PSCP} \c{-p} PSCP command-line option
+\IM{-q-PSCP} \c{-q} PSCP command-line option
+\IM{-r-PSCP} \c{-r} PSCP command-line option
+\IM{-batch-PSCP} \c{-batch} PSCP command-line option
+\IM{-sftp} \c{-sftp} PSCP command-line option
+\IM{-scp} \c{-scp} PSCP command-line option
+
+\IM{return value} return value
+\IM{return value} exit value
+
+\IM{-b-PSFTP} \c{-b} PSFTP command-line option
+\IM{-bc-PSFTP} \c{-bc} PSFTP command-line option
+\IM{-be-PSFTP} \c{-be} PSFTP command-line option
+\IM{-batch-PSFTP} \c{-batch} PSFTP command-line option
+
+\IM{spaces in filenames} spaces in filenames
+\IM{spaces in filenames} filenames containing spaces
+
+\IM{working directory} working directory
+\IM{working directory} current working directory
+
+\IM{resuming file transfers} resuming file transfers
+\IM{resuming file transfers} files, resuming transfer of
+
+\IM{changing permissions on files} changing permissions on files
+\IM{changing permissions on files} permissions on files, changing
+\IM{changing permissions on files} files, changing permissions on
+\IM{changing permissions on files} modes of files, changing
+\IM{changing permissions on files} access to files, changing
+
+\IM{deleting files} deleting files
+\IM{deleting files} files, deleting
+\IM{deleting files} removing files
+
+\IM{create a directory} creating directories
+\IM{create a directory} directories, creating
+
+\IM{remove a directory} removing directories
+\IM{remove a directory} directories, removing
+\IM{remove a directory} deleting directories
+
+\IM{rename remote files} renaming files
+\IM{rename remote files} files, renaming and moving
+\IM{rename remote files} moving files
+
+\IM{local Windows command} local Windows command
+\IM{local Windows command} Windows command
+
+\IM{PLINK_PROTOCOL} \c{PLINK_PROTOCOL} environment variable
+
+\IM{-batch-plink} \c{-batch} Plink command-line option
+\IM{-s-plink} \c{-s} Plink command-line option
+
+\IM{subsystem} subsystem, SSH
+\IM{subsystem} SSH subsystem
+
+\IM{batch file}{batch files} batch files
+
+\IM{CVS_RSH} \c{CVS_RSH} environment variable
+
+\IM{DSA} DSA
+\IM{DSA} Digital Signature Standard
+
+\IM{public-key algorithm} public-key algorithm
+\IM{public-key algorithm} asymmetric key algorithm
+\IM{public-key algorithm} algorithm, public-key
+
+\IM{generating keys} generating key pairs
+\IM{generating keys} creating key pairs
+\IM{generating keys} key pairs, generating
+\IM{generating keys} public keys, generating
+\IM{generating keys} private keys, generating
+
+\IM{authorized_keys file}{authorized_keys} \cw{authorized_keys} file
+
+\IM{key fingerprint} fingerprint, of SSH authentication key
+\IM{key fingerprint} public key fingerprint (SSH)
+\IM{key fingerprint} SSH public key fingerprint
+
+\IM{SSH-2 public key format} SSH-2 public key file format
+\IM{SSH-2 public key format} public key file, SSH-2
+
+\IM{OpenSSH private key format} OpenSSH private key file format
+\IM{OpenSSH private key format} private key file, OpenSSH
+
+\IM{ssh.com private key format} \cw{ssh.com} private key file format
+\IM{ssh.com private key format} private key file, \cw{ssh.com}
+
+\IM{importing keys} importing private keys
+\IM{importing keys} loading private keys
+
+\IM{export private keys} exporting private keys
+\IM{export private keys} saving private keys
+
+\IM{.ssh} \c{.ssh} directory
+
+\IM{.ssh2} \c{.ssh2} directory
+
+\IM{authentication agent} authentication agent
+\IM{authentication agent} agent, authentication
+
+\IM{-c-pageant} \c{-c} Pageant command-line option
+
+\IM{FAQ} FAQ
+\IM{FAQ} Frequently Asked Questions
+
+\IM{supported features} supported features
+\IM{supported features} features, supported
+
+\IM{remember my password} storing passwords
+\IM{remember my password} password, storing
+
+\IM{login scripts}{startup scripts} login scripts
+\IM{login scripts}{startup scripts} startup scripts
+
+\IM{WS2_32.DLL} \cw{WS2_32.DLL}
+\IM{WS2_32.DLL} WinSock version 2
+
+\IM{Red Hat Linux} Red Hat Linux
+\IM{Red Hat Linux} Linux, Red Hat
+
+\IM{SMB} SMB
+\IM{SMB} Windows file sharing
+
+\IM{clean up} clean up after PuTTY
+\IM{clean up} uninstalling
+
+\IM{version of PuTTY} version, of PuTTY
+
+\IM{PGP signatures} PGP signatures, of PuTTY binaries
+\IM{PGP signatures} signatures, of PuTTY binaries
+
+\IM{logical host name} logical host name
+\IM{logical host name} host name, logical
+\IM{logical host name} host key, caching policy
+
+\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
--- /dev/null
+\define{versionidintro} \versionid $Id$
+
+\C{intro} Introduction to PuTTY
+
+PuTTY is a free SSH, Telnet and Rlogin client for 32-bit Windows
+systems.
+
+\H{you-what} What are SSH, Telnet and Rlogin?
+
+If you already know what SSH, Telnet and Rlogin are, you can safely
+skip on to the next section.
+
+SSH, Telnet and Rlogin are three ways of doing the same thing:
+logging in to a multi-user computer from another computer, over a
+network.
+
+Multi-user operating systems, such as Unix and VMS, usually present
+a \i{command-line interface} to the user, much like the \q{\i{Command
+Prompt}} or \q{\i{MS-DOS Prompt}} in Windows. The system prints a
+prompt, and you type commands which the system will obey.
+
+Using this type of interface, there is no need for you to be sitting
+at the same machine you are typing commands to. The commands, and
+responses, can be sent over a network, so you can sit at one
+computer and give commands to another one, or even to more than one.
+
+SSH, Telnet and Rlogin are \i\e{network protocols} that allow you to
+do this. On the computer you sit at, you run a \i\e{client}, which
+makes a network connection to the other computer (the \i\e{server}).
+The network connection carries your keystrokes and commands from the
+client to the server, and carries the server's responses back to
+you.
+
+These protocols can also be used for other types of keyboard-based
+interactive session. In particular, there are a lot of bulletin
+boards, \i{talker systems} and \i{MUDs} (Multi-User Dungeons) which support
+access using Telnet. There are even a few that support SSH.
+
+You might want to use SSH, Telnet or Rlogin if:
+
+\b you have an account on a Unix or VMS system which you want to be
+able to access from somewhere else
+
+\b your Internet Service Provider provides you with a login account
+on a \i{web server}. (This might also be known as a \i\e{shell account}.
+A \e{shell} is the program that runs on the server and interprets
+your commands for you.)
+
+\b you want to use a \i{bulletin board system}, talker or MUD which can
+be accessed using Telnet.
+
+You probably do \e{not} want to use SSH, Telnet or Rlogin if:
+
+\b you only use Windows. Windows computers have their own
+ways of networking between themselves, and unless you are doing
+something fairly unusual, you will not need to use any of these
+remote login protocols.
+
+\H{which-one} How do SSH, Telnet and Rlogin differ?
+
+This list summarises some of the \i{differences between SSH, Telnet
+and Rlogin}.
+
+\b SSH (which stands for \q{\i{secure shell}}) is a recently designed,
+high-security protocol. It uses strong cryptography to protect your
+connection against eavesdropping, hijacking and other attacks. Telnet
+and Rlogin are both older protocols offering minimal security.
+
+\b SSH and Rlogin both allow you to \I{passwordless login}log in to the
+server without having to type a password. (Rlogin's method of doing this is
+insecure, and can allow an attacker to access your account on the
+server. SSH's method is much more secure, and typically breaking the
+security requires the attacker to have gained access to your actual
+client machine.)
+
+\b SSH allows you to connect to the server and automatically send a
+command, so that the server will run that command and then
+disconnect. So you can use it in automated processing.
+
+The Internet is a hostile environment and security is everybody's
+responsibility. If you are connecting across the open Internet, then
+we recommend you use SSH. If the server you want to connect to
+doesn't support SSH, it might be worth trying to persuade the
+administrator to install it.
+
+If your client and server are both behind the same (good) firewall,
+it is more likely to be safe to use Telnet or Rlogin, but we still
+recommend you use SSH.
--- /dev/null
+\define{versionidlicence} \versionid $Id$
+
+\A{licence} PuTTY \ii{Licence}
+
+PuTTY is \i{copyright} 1997-2011 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.
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation files
+(the \q{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,
+and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED \q{AS IS}, WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE
+FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--- /dev/null
+\cfg{man-identity}{puttygen}{1}{2004-03-24}{PuTTY tool suite}{PuTTY tool suite}
+
+\H{puttygen-manpage} Man page for PuTTYgen
+
+\S{puttygen-manpage-name} NAME
+
+\cw{puttygen} - public-key generator for the PuTTY tools
+
+\S{puttygen-manpage-synopsis} SYNOPSIS
+
+\c puttygen ( keyfile | -t keytype [ -b bits ] )
+\e bbbbbbbb iiiiiii bb iiiiiii bb iiii
+\c [ -C new-comment ] [ -P ] [ -q ]
+\e bb iiiiiiiiiii bb bb
+\c [ -O output-type | -l | -L | -p ]
+\e bb iiiiiiiiiii bb bb bb
+\c [ -o output-file ]
+\e bb iiiiiiiiiii
+
+\S{puttygen-manpage-description} DESCRIPTION
+
+\c{puttygen} is a tool to generate and manipulate SSH public and
+private key pairs. It is part of the PuTTY suite, although it can
+also interoperate with the private key formats used by some other
+SSH clients.
+
+When you run \c{puttygen}, it does three things. Firstly, it either
+loads an existing key file (if you specified \e{keyfile}), or
+generates a new key (if you specified \e{keytype}). Then, it
+optionally makes modifications to the key (changing the comment
+and/or the passphrase); finally, it outputs the key, or some
+information about the key, to a file.
+
+All three of these phases are controlled by the options described in
+the following section.
+
+\S{puttygen-manpage-options} OPTIONS
+
+In the first phase, \c{puttygen} either loads or generates a key.
+Note that generating a key requires random data (from
+\c{/dev/random}), which can cause \c{puttygen} to pause, possibly for
+some time if your system does not have much randomness available.
+
+The options to control this phase are:
+
+\dt \e{keyfile}
+
+\dd Specify a private key file to be loaded. This private key file can
+be in the (de facto standard) SSH-1 key format, or in PuTTY's SSH-2
+key format, or in either of the SSH-2 private key formats used by
+OpenSSH and ssh.com's implementation.
+
+\dt \cw{\-t} \e{keytype}
+
+\dd Specify a type of key to generate. The acceptable values here are
+\c{rsa} and \c{dsa} (to generate SSH-2 keys), and \c{rsa1} (to
+generate SSH-1 keys).
+
+\dt \cw{\-b} \e{bits}
+
+\dd Specify the size of the key to generate, in bits. Default is 1024.
+
+\dt \cw{\-q}
+
+\dd Suppress the progress display when generating a new key.
+
+In the second phase, \c{puttygen} optionally alters properties of
+the key it has loaded or generated. The options to control this are:
+
+\dt \cw{\-C} \e{new\-comment}
+
+\dd Specify a comment string to describe the key. This comment string
+will be used by PuTTY to identify the key to you (when asking you to
+enter the passphrase, for example, so that you know which passphrase
+to type).
+
+\dt \cw{\-P}
+
+\dd Indicate that you want to change the key's passphrase. This is
+automatic when you are generating a new key, but not when you are
+modifying an existing key.
+
+In the third phase, \c{puttygen} saves the key or information
+about it. The options to control this are:
+
+\dt \cw{\-O} \e{output\-type}
+
+\dd Specify the type of output you want \c{puttygen} to produce.
+Acceptable options are:
+
+\lcont{
+
+\dt \cw{private}
+
+\dd Save the private key in a format usable by PuTTY. This will either
+be the standard SSH-1 key format, or PuTTY's own SSH-2 key format.
+
+\dt \cw{public}
+
+\dd Save the public key only. For SSH-1 keys, the standard public key
+format will be used (\q{\cw{1024 37 5698745}...}). For SSH-2 keys, the
+public key will be output in the format specified by RFC 4716,
+which is a multi-line text file beginning with the line
+\q{\cw{---- BEGIN SSH2 PUBLIC KEY ----}}.
+
+\dt \cw{public-openssh}
+
+\dd Save the public key only, in a format usable by OpenSSH. For SSH-1
+keys, this output format behaves identically to \c{public}. For
+SSH-2 keys, the public key will be output in the OpenSSH format,
+which is a single line (\q{\cw{ssh-rsa AAAAB3NzaC1yc2}...}).
+
+\dt \cw{fingerprint}
+
+\dd Print the fingerprint of the public key. All fingerprinting
+algorithms are believed compatible with OpenSSH.
+
+\dt \cw{private-openssh}
+
+\dd Save an SSH-2 private key in OpenSSH's format. This option is not
+permitted for SSH-1 keys.
+
+\dt \cw{private-sshcom}
+
+\dd Save an SSH-2 private key in ssh.com's format. This option is not
+permitted for SSH-1 keys.
+
+If no output type is specified, the default is \c{private}.
+
+}
+
+\dt \cw{\-o} \e{output\-file}
+
+\dd Specify the file where \c{puttygen} should write its output. If
+this option is not specified, \c{puttygen} will assume you want to
+overwrite the original file if the input and output file types are
+the same (changing a comment or passphrase), and will assume you
+want to output to stdout if you are asking for a public key or
+fingerprint. Otherwise, the \c{\-o} option is required.
+
+\dt \cw{\-l}
+
+\dd Synonym for \q{\cw{-O fingerprint}}.
+
+\dt \cw{\-L}
+
+\dd Synonym for \q{\cw{-O public-openssh}}.
+
+\dt \cw{\-p}
+
+\dd Synonym for \q{\cw{-O public}}.
+
+The following options do not run PuTTYgen as normal, but print
+informational messages and then quit:
+
+\dt \cw{\-h}, \cw{\-\-help}
+
+\dd Display a message summarizing the available options.
+
+\dt \cw{\-V}, \cw{\-\-version}
+
+\dd Display the version of PuTTYgen.
+
+\dt \cw{\-\-pgpfp}
+
+\dd Display the fingerprints of the PuTTY PGP Master Keys, to aid
+in verifying new files released by the PuTTY team.
+
+\S{puttygen-manpage-examples} EXAMPLES
+
+To generate an SSH-2 RSA key pair and save it in PuTTY's own format
+(you will be prompted for the passphrase):
+
+\c puttygen -t rsa -C "my home key" -o mykey.ppk
+
+To generate a larger (2048-bit) key:
+
+\c puttygen -t rsa -b 2048 -C "my home key" -o mykey.ppk
+
+To change the passphrase on a key (you will be prompted for the old
+and new passphrases):
+
+\c puttygen -P mykey.ppk
+
+To change the comment on a key:
+
+\c puttygen -C "new comment" mykey.ppk
+
+To convert a key into OpenSSH's private key format:
+
+\c puttygen mykey.ppk -O private-openssh -o my-openssh-key
+
+To convert a key \e{from} another format (\c{puttygen} will
+automatically detect the input key type):
+
+\c puttygen my-ssh.com-key -o mykey.ppk
+
+To display the fingerprint of a key (some key types require a
+passphrase to extract even this much information):
+
+\c puttygen -l mykey.ppk
+
+To add the OpenSSH-format public half of a key to your authorised
+keys file:
+
+\c puttygen -L mykey.ppk >> $HOME/.ssh/authorized_keys
+
+\S{puttygen-manpage-bugs} BUGS
+
+There's currently no way to supply passphrases in batch mode, or
+even just to specify that you don't want a passphrase at all.
--- /dev/null
+\cfg{man-identity}{plink}{1}{2004-03-24}{PuTTY tool suite}{PuTTY tool suite}
+
+\H{plink-manpage} Man page for Plink
+
+\S{plink-manpage-name} NAME
+
+\cw{plink} \- PuTTY link, command line network connection tool
+
+\S{plink-manpage-synopsis} SYNOPSIS
+
+\c plink [options] [user@]host [command]
+\e bbbbb iiiiiii iiiib iiii iiiiiii
+
+\S{plink-manpage-description} DESCRIPTION
+
+\cw{plink} is a network connection tool supporting several protocols.
+
+\S{plink-manpage-options} OPTIONS
+
+The command-line options supported by \cw{plink} are:
+
+\dt \cw{-V}
+
+\dd Show version information and exit.
+
+\dt \cw{-pgpfp}
+
+\dd Display the fingerprints of the PuTTY PGP Master Keys and exit,
+to aid in verifying new files released by the PuTTY team.
+
+\dt \cw{-v}
+
+\dd Show verbose messages.
+
+\dt \cw{-load} \e{session}
+
+\dd Load settings from saved session.
+
+\dt \cw{-ssh}
+
+\dd Force use of SSH protocol (default).
+
+\dt \cw{-telnet}
+
+\dd Force use of Telnet protocol.
+
+\dt \cw{-rlogin}
+
+\dd Force use of rlogin protocol.
+
+\dt \cw{-raw}
+
+\dd Force raw mode.
+
+\dt \cw{-serial}
+
+\dd Force serial mode.
+
+\dt \cw{-P} \e{port}
+
+\dd Connect to port \e{port}.
+
+\dt \cw{-l} \e{user}
+
+\dd Set remote username to \e{user}.
+
+\dt \cw{-m} \e{path}
+
+\dd Read remote command(s) from local file \e{path}.
+
+\dt \cw{-batch}
+
+\dd Disable interactive prompts.
+
+\dt \cw{-pw} \e{password}
+
+\dd Set remote password to \e{password}. \e{CAUTION:} this will likely
+make the password visible to other users of the local machine (via
+commands such as \q{\c{w}}).
+
+\dt \cw{\-L} \cw{[}\e{srcaddr}\cw{:]}\e{srcport}\cw{:}\e{desthost}\cw{:}\e{destport}
+
+\dd Set up a local port forwarding: listen on \e{srcport} (or
+\e{srcaddr}:\e{srcport} if specified), and forward any connections
+over the SSH connection to the destination address
+\e{desthost}:\e{destport}. Only works in SSH.
+
+\dt \cw{\-R} \cw{[}\e{srcaddr}\cw{:]}\e{srcport}\cw{:}\e{desthost}\cw{:}\e{destport}
+
+\dd Set up a remote port forwarding: ask the SSH server to listen on
+\e{srcport} (or \e{srcaddr}:\e{srcport} if specified), and to
+forward any connections back over the SSH connection where the
+client will pass them on to the destination address
+\e{desthost}:\e{destport}. Only works in SSH.
+
+\dt \cw{\-D} [\e{srcaddr}:]\e{srcport}
+
+\dd Set up dynamic port forwarding. The client listens on
+\e{srcport} (or \e{srcaddr}:\e{srcport} if specified), and
+implements a SOCKS server. So you can point SOCKS-aware applications
+at this port and they will automatically use the SSH connection to
+tunnel all their connections. Only works in SSH.
+
+\dt \cw{-X}
+
+\dd Enable X11 forwarding.
+
+\dt \cw{-x}
+
+\dd Disable X11 forwarding (default).
+
+\dt \cw{-A}
+
+\dd Enable agent forwarding.
+
+\dt \cw{-a}
+
+\dd Disable agent forwarding (default).
+
+\dt \cw{-t}
+
+\dd Enable pty allocation (default if a command is NOT specified).
+
+\dt \cw{-T}
+
+\dd Disable pty allocation (default if a command is specified).
+
+\dt \cw{-1}
+
+\dd Force use of SSH protocol version 1.
+
+\dt \cw{-2}
+
+\dd Force use of SSH protocol version 2.
+
+\dt \cw{-C}
+
+\dd Enable SSH compression.
+
+\dt \cw{-i} \e{path}
+
+\dd Private key file for authentication.
+
+\dt \cw{-s}
+
+\dd Remote command is SSH subsystem (SSH-2 only).
+
+\dt \cw{-N}
+
+\dd Don't start a remote command or shell at all (SSH-2 only).
+
+\dt \cw{\-sercfg} \e{configuration-string}
+
+\dd Specify the configuration parameters for the serial port, in
+\cw{-serial} mode. \e{configuration-string} should be a
+comma-separated list of configuration parameters as follows:
+
+\lcont{
+
+\b Any single digit from 5 to 9 sets the number of data bits.
+
+\b \cq{1}, \cq{1.5} or \cq{2} sets the number of stop bits.
+
+\b Any other numeric string is interpreted as a baud rate.
+
+\b A single lower-case letter specifies the parity: \cq{n} for none,
+\cq{o} for odd, \cq{e} for even, \cq{m} for mark and \cq{s} for space.
+
+\b A single upper-case letter specifies the flow control: \cq{N} for
+none, \cq{X} for XON/XOFF, \cq{R} for RTS/CTS and \cq{D} for
+DSR/DTR.
+
+}
+
+\S{plink-manpage-more-information} MORE INFORMATION
+
+For more information on plink, it's probably best to go and look at
+the manual on the PuTTY web page:
+
+\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/}\cw{http://www.chiark.greenend.org.uk/~sgtatham/putty/}
+
+\S{plink-manpage-bugs} BUGS
+
+This man page isn't terribly complete. See the above web link for
+better documentation.
--- /dev/null
+\cfg{man-identity}{pscp}{1}{2004-03-24}{PuTTY tool suite}{PuTTY tool suite}
+
+\H{pscp-manpage} Man page for PSCP
+
+\S{pscp-manpage-name} NAME
+
+\cw{pscp} \- command-line SCP (secure copy) / SFTP client
+
+\S{pscp-manpage-synopsis} SYNOPSIS
+
+\c pscp [options] [user@]host:source target
+\e bbbb iiiiiii iiiib iiiibiiiiii iiiiii
+\c pscp [options] source [source...] [user@]host:target
+\e bbbb iiiiiii iiiiii iiiiii iiiib iiiibiiiiii
+\c pscp [options] -ls [user@]host:filespec
+\e bbbb iiiiiii bbb iiiib iiiibiiiiiiii
+
+\S{pscp-manpage-description} DESCRIPTION
+
+\cw{pscp} is a command-line client for the SSH-based SCP (secure
+copy) and SFTP (secure file transfer protocol) protocols.
+
+\S{pscp-manpage-options} OPTIONS
+
+The command-line options supported by \e{pscp} are:
+
+\dt \cw{-V}
+
+\dd Show version information and exit.
+
+\dt \cw{-pgpfp}
+
+\dd Display the fingerprints of the PuTTY PGP Master Keys and exit,
+to aid in verifying new files released by the PuTTY team.
+
+\dt \cw{-ls}
+
+\dd Remote directory listing.
+
+\dt \cw{-p}
+
+\dd Preserve file attributes.
+
+\dt \cw{-q}
+
+\dd Quiet, don't show statistics.
+
+\dt \cw{-r}
+
+\dd Copy directories recursively.
+
+\dt \cw{-unsafe}
+
+\dd Allow server-side wildcards (DANGEROUS).
+
+\dt \cw{-v}
+
+\dd Show verbose messages.
+
+\dt \cw{-load} \e{session}
+
+\dd Load settings from saved session.
+
+\dt \cw{-P} \e{port}
+
+\dd Connect to port \e{port}.
+
+\dt \cw{-l} \e{user}
+
+\dd Set remote username to \e{user}.
+
+\dt \cw{-batch}
+
+\dd Disable interactive prompts.
+
+\dt \cw{-pw} \e{password}
+
+\dd Set remote password to \e{password}. \e{CAUTION:} this will likely
+make the password visible to other users of the local machine (via
+commands such as \q{\c{w}}).
+
+\dt \cw{-1}
+
+\dd Force use of SSH protocol version 1.
+
+\dt \cw{-2}
+
+\dd Force use of SSH protocol version 2.
+
+\dt \cw{-C}
+
+\dd Enable SSH compression.
+
+\dt \cw{-i} \e{path}
+
+\dd Private key file for authentication.
+
+\dt \cw{-scp}
+
+\dd Force use of SCP protocol.
+
+\dt \cw{-sftp}
+
+\dd Force use of SFTP protocol.
+
+\S{pscp-manpage-more-information} MORE INFORMATION
+
+For more information on \cw{pscp} it's probably best to go and look at
+the manual on the PuTTY web page:
+
+\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/}\cw{http://www.chiark.greenend.org.uk/~sgtatham/putty/}
+
+\S{pscp-manpage-bugs} BUGS
+
+This man page isn't terribly complete. See the above web link for
+better documentation.
--- /dev/null
+\cfg{man-identity}{psftp}{1}{2004-03-24}{PuTTY tool suite}{PuTTY tool suite}
+
+\H{psftp-manpage} Man page for PSFTP
+
+\S{psftp-manpage-name} NAME
+
+\cw{psftp} \- interactive SFTP (secure file transfer protocol) client
+
+\S{psftp-manpage-synopsis} SYNOPSIS
+
+\c psftp [options] [user@]host
+\e bbbbb iiiiiii iiiib iiii
+
+\S{psftp-manpage-description} DESCRIPTION
+
+\cw{psftp} is an interactive text-based client for the SSH-based SFTP
+(secure file transfer) protocol.
+
+\S{psftp-manpage-options} OPTIONS
+
+The command-line options supported by \cw{psftp} are:
+
+\dt \cw{-V}
+
+\dd Show version information and exit.
+
+\dt \cw{-pgpfp}
+
+\dd Display the fingerprints of the PuTTY PGP Master Keys and exit,
+to aid in verifying new files released by the PuTTY team.
+
+\dt \cw{-b} \e{batchfile}
+
+\dd Use specified batchfile.
+
+\dt \cw{-bc}
+
+\dd Output batchfile commands.
+
+\dt \cw{-be}
+
+\dd Don't stop batchfile processing on errors.
+
+\dt \cw{-v}
+
+\dd Show verbose messages.
+
+\dt \cw{-load} \e{session}
+
+\dd Load settings from saved session.
+
+\dt \cw{-P} \e{port}
+
+\dd Connect to port \e{port}.
+
+\dt \cw{-l} \e{user}
+
+\dd Set remote username to \e{user}.
+
+\dt \cw{-batch}
+
+\dd Disable interactive prompts.
+
+\dt \cw{-pw} \e{password}
+
+\dd Set remote password to \e{password}. \e{CAUTION:} this will likely
+make the password visible to other users of the local machine (via
+commands such as \q{\c{w}}).
+
+\dt \cw{-1}
+
+\dd Force use of SSH protocol version 1.
+
+\dt \cw{-2}
+
+\dd Force use of SSH protocol version 2.
+
+\dt \cw{-C}
+
+\dd Enable SSH compression.
+
+\dt \cw{-i} \e{path}
+
+\dd Private key file for authentication.
+
+\S{psftp-manpage-commands} COMMANDS
+
+For a list of commands available inside \cw{psftp}, type \cw{help}
+at the \cw{psftp>} prompt.
+
+\S{psftp-manpage-more-information} MORE INFORMATION
+
+For more information on \cw{psftp} it's probably best to go and look at
+the manual on the PuTTY web page:
+
+\cw{http://www.chiark.greenend.org.uk/~sgtatham/putty/}
+
+\S{psftp-manpage-bugs} BUGS
+
+This man page isn't terribly complete. See the above web link for
+better documentation.
--- /dev/null
+\cfg{man-identity}{puttytel}{1}{2004-03-24}{PuTTY tool suite}{PuTTY tool suite}
+
+\H{puttytel-manpage} Man page for PuTTYtel
+
+\S{puttytel-manpage-name} NAME
+
+\cw{puttytel} \- GUI Telnet and Rlogin client for X
+
+\S{puttytel-manpage-synopsis} SYNOPSIS
+
+\c puttytel [ options ] [ host ]
+\e bbbbbbbb iiiiiii iiii
+
+\S{puttytel-manpage-description} DESCRIPTION
+
+\cw{puttytel} is a graphical Telnet and Rlogin client for X. It
+is a direct port of the Windows Telnet and Rlogin client of the same
+name, and a cut-down cryptography-free version of PuTTY.
+
+\S{puttytel-manpage-options} OPTIONS
+
+The command-line options supported by \cw{puttytel} are:
+
+\dt \cw{\-\-display} \e{display\-name}
+
+\dd Specify the X display on which to open \cw{puttytel}. (Note this
+option has a double minus sign, even though none of the others do.
+This is because this option is supplied automatically by GTK.
+Sorry.)
+
+\dt \cw{\-fn} \e{font-name}
+
+\dd Specify the font to use for normal text displayed in the terminal.
+
+\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
+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}
+
+\dd Specify the font to use for double-width characters (typically
+Chinese, Japanese and Korean text) displayed in the terminal.
+
+\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.
+
+\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
+specifications.
+
+\dt \cw{\-sl} \e{lines}
+
+\dd Specify the number of lines of scrollback to save off the top of the
+terminal.
+
+\dt \cw{\-fg} \e{colour}
+
+\dd Specify the foreground colour to use for normal text.
+
+\dt \cw{\-bg} \e{colour}
+
+\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).
+
+\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
+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}
+
+\dd Specify the foreground colour to use for text covered by the cursor.
+
+\dt \cw{\-cbg} \e{colour}
+
+\dd Specify the background colour to use for text covered by the cursor.
+In other words, this is the main colour of the cursor.
+
+\dt \cw{\-title} \e{title}
+
+\dd Specify the initial title of the terminal window. (This can be
+changed under control of the server.)
+
+\dt \cw{\-sb\-} or \cw{+sb}
+
+\dd Tells \cw{puttytel} not to display a scroll bar.
+
+\dt \cw{\-sb}
+
+\dd Tells \cw{puttytel} to display a scroll bar: this is the opposite of
+\cw{\-sb\-}. This is the default option: you will probably only need
+to specify it explicitly if you have changed the default using the
+\cw{ScrollBar} resource.
+
+\dt \cw{\-log} \e{filename}
+
+\dd This option makes \cw{puttytel} log all the terminal output to a file
+as well as displaying it in the terminal.
+
+\dt \cw{\-cs} \e{charset}
+
+\dd This option specifies the character set in which \cw{puttytel}
+should assume the session is operating. This character set will be
+used to interpret all the data received from the session, and all
+input you type or paste into \cw{puttytel} will be converted into
+this character set before being sent to the session.
+
+\lcont{ Any character set name which is valid in a MIME header (and
+supported by \cw{puttytel}) should be valid here (examples are
+\q{\cw{ISO-8859-1}}, \q{\cw{windows-1252}} or \q{\cw{UTF-8}}). Also,
+any character encoding which is valid in an X logical font
+description should be valid (\q{\cw{ibm-cp437}}, for example).
+
+\cw{puttytel}'s default behaviour is to use the same character
+encoding as its primary font. If you supply a Unicode
+(\cw{iso10646-1}) font, it will default to the UTF-8 character set.
+
+Character set names are case-insensitive.
+}
+
+\dt \cw{\-nethack}
+
+\dd Tells \cw{puttytel} to enable NetHack keypad mode, in which the
+numeric keypad generates the NetHack \c{hjklyubn} direction keys.
+This enables you to play NetHack with the numeric keypad without
+having to use the NetHack \c{number_pad} option (which requires you
+to press \q{\cw{n}} before any repeat count). So you can move with
+the numeric keypad, and enter repeat counts with the normal number
+keys.
+
+\dt \cw{\-help}, \cw{\-\-help}
+
+\dd Display a message summarizing the available options.
+
+\dt \cw{\-pgpfp}
+
+\dd Display the fingerprints of the PuTTY PGP Master Keys, to aid
+in verifying new files released by the PuTTY team.
+
+\dt \cw{\-load} \e{session}
+
+\dd Load a saved session by name. This allows you to run a saved session
+straight from the command line without having to go through the
+configuration box first.
+
+\dt \cw{\-telnet}, \cw{\-rlogin}, \cw{\-raw}
+
+\dd Select the protocol \cw{puttytel} will use to make the connection.
+
+\dt \cw{\-l} \e{username}
+
+\dd Specify the username to use when logging in to the server.
+
+\dt \cw{\-P} \e{port}
+
+\dd Specify the port to connect to the server on.
+
+\S{puttytel-manpage-saved-sessions} SAVED SESSIONS
+
+Saved sessions are stored in a \cw{.putty/sessions} subdirectory in
+your home directory.
+
+\S{puttytel-manpage-more-information} MORE INFORMATION
+
+For more information on PuTTY and PuTTYtel, it's probably best to go
+and look at the manual on the web page:
+
+\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/}\cw{http://www.chiark.greenend.org.uk/~sgtatham/putty/}
+
+\S{puttytel-manpage-bugs} BUGS
+
+This man page isn't terribly complete.
--- /dev/null
+\cfg{man-identity}{pterm}{1}{2004-03-24}{PuTTY tool suite}{PuTTY tool suite}
+
+\H{pterm-manpage} Man page for pterm
+
+\S{pterm-manpage-name} NAME
+
+pterm \- yet another X terminal emulator
+
+\S{pterm-manpage-synopsis} SYNOPSIS
+
+\c pterm [ options ]
+\e bbbbb iiiiiii
+
+\S{pterm-manpage-description} DESCRIPTION
+
+\cw{pterm} is a terminal emulator for X. It is based on a port of
+the terminal emulation engine in the Windows SSH client PuTTY.
+
+\S{pterm-manpage-options} OPTIONS
+
+The command-line options supported by \cw{pterm} are:
+
+\dt \cw{\-e} \e{command} [ \e{arguments} ]
+
+\dd Specify a command to be executed in the new terminal. Everything on
+the command line after this option will be passed straight to the
+\cw{execvp} system call; so if you need the command to redirect its
+input or output, you will have to use \cw{sh}:
+
+\lcont{
+
+\c pterm -e sh -c 'mycommand < inputfile'
+
+}
+
+\dt \cw{\-\-display} \e{display\-name}
+
+\dd Specify the X display on which to open \cw{pterm}. (Note this
+option has a double minus sign, even though none of the others do.
+This is because this option is supplied automatically by GTK.
+Sorry.)
+
+\dt \cw{\-name} \e{name}
+
+\dd Specify the name under which \cw{pterm} looks up X resources.
+Normally it will look them up as (for example) \cw{pterm.Font}. If
+you specify \q{\cw{\-name xyz}}, it will look them up as
+\cw{xyz.Font} instead. This allows you to set up several different
+sets of defaults and choose between them.
+
+\dt \cw{\-fn} \e{font-name}
+
+\dd Specify the font to use for normal text displayed in the terminal.
+
+\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
+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}
+
+\dd Specify the font to use for double-width characters (typically
+Chinese, Japanese and Korean text) displayed in the terminal.
+
+\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.
+
+\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
+specifications.
+
+\dt \cw{\-sl} \e{lines}
+
+\dd Specify the number of lines of scrollback to save off the top of the
+terminal.
+
+\dt \cw{\-fg} \e{colour}
+
+\dd Specify the foreground colour to use for normal text.
+
+\dt \cw{\-bg} \e{colour}
+
+\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).
+
+\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
+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}
+
+\dd Specify the foreground colour to use for text covered by the cursor.
+
+\dt \cw{\-cbg} \e{colour}
+
+\dd Specify the background colour to use for text covered by the cursor.
+In other words, this is the main colour of the cursor.
+
+\dt \cw{\-title} \e{title}
+
+\dd Specify the initial title of the terminal window. (This can be
+changed under control of the server.)
+
+\dt \cw{\-ut\-} or \cw{+ut}
+
+\dd Tells \cw{pterm} not to record your login in the \cw{utmp},
+\cw{wtmp} and \cw{lastlog} system log files; so you will not show
+up on \cw{finger} or \cw{who} listings, for example.
+
+\dt \cw{\-ut}
+
+\dd Tells \cw{pterm} to record your login in \cw{utmp}, \cw{wtmp} and
+\cw{lastlog}: this is the opposite of \cw{\-ut\-}. This is the
+default option: you will probably only need to specify it explicitly
+if you have changed the default using the \cw{StampUtmp} resource.
+
+\dt \cw{\-ls\-} or \cw{+ls}
+
+\dd Tells \cw{pterm} not to execute your shell as a login shell.
+
+\dt \cw{\-ls}
+
+\dd Tells \cw{pterm} to execute your shell as a login shell: this is
+the opposite of \cw{\-ls\-}. This is the default option: you will
+probably only need to specify it explicitly if you have changed the
+default using the \cw{LoginShell} resource.
+
+\dt \cw{\-sb\-} or \cw{+sb}
+
+\dd Tells \cw{pterm} not to display a scroll bar.
+
+\dt \cw{\-sb}
+
+\dd Tells \cw{pterm} to display a scroll bar: this is the opposite of
+\cw{\-sb\-}. This is the default option: you will probably only need
+to specify it explicitly if you have changed the default using the
+\cw{ScrollBar} resource.
+
+\dt \cw{\-log} \e{filename}
+
+\dd This option makes \cw{pterm} log all the terminal output to a file
+as well as displaying it in the terminal.
+
+\dt \cw{\-cs} \e{charset}
+
+\dd This option specifies the character set in which \cw{pterm} should
+assume the session is operating. This character set will be used to
+interpret all the data received from the session, and all input you
+type or paste into \cw{pterm} will be converted into this character
+set before being sent to the session.
+
+\lcont{ Any character set name which is valid in a MIME header (and
+supported by \cw{pterm}) should be valid here (examples are
+\q{\cw{ISO-8859-1}}, \q{\cw{windows-1252}} or \q{\cw{UTF-8}}). Also,
+any character encoding which is valid in an X logical font
+description should be valid (\q{\cw{ibm-cp437}}, for example).
+
+\cw{pterm}'s default behaviour is to use the same character encoding
+as its primary font. If you supply a Unicode (\cw{iso10646-1}) font,
+it will default to the UTF-8 character set.
+
+Character set names are case-insensitive.
+}
+
+\dt \cw{\-nethack}
+
+\dd Tells \cw{pterm} to enable NetHack keypad mode, in which the
+numeric keypad generates the NetHack \c{hjklyubn} direction keys.
+This enables you to play NetHack with the numeric keypad without
+having to use the NetHack \c{number_pad} option (which requires you
+to press \q{\cw{n}} before any repeat count). So you can move with
+the numeric keypad, and enter repeat counts with the normal number
+keys.
+
+\dt \cw{\-xrm} \e{resource-string}
+
+\dd This option specifies an X resource string. Useful for setting
+resources which do not have their own command-line options. For
+example:
+
+\lcont{
+
+\c pterm -xrm 'ScrollbarOnLeft: 1'
+
+}
+
+\dt \cw{\-help}, \cw{\-\-help}
+
+\dd Display a message summarizing the available options.
+
+\dt \cw{\-pgpfp}
+
+\dd Display the fingerprints of the PuTTY PGP Master Keys, to aid
+in verifying new files released by the PuTTY team.
+
+\S{pterm-manpage-x-resources} X RESOURCES
+
+\cw{pterm} can be more completely configured by means of X
+resources. All of these resources are of the form \cw{pterm.FOO} for
+some \cw{FOO}; you can make \cw{pterm} look them up under another
+name, such as \cw{xyz.FOO}, by specifying the command-line option
+\q{\cw{\-name xyz}}.
+
+\dt \cw{pterm.CloseOnExit}
+
+\dd This option should be set to 0, 1 or 2; the default is 2. It
+controls what \cw{pterm} does when the process running inside it
+terminates. When set to 2 (the default), \cw{pterm} will close its
+window as soon as the process inside it terminates. When set to 0,
+\cw{pterm} will print the process's exit status, and the window
+will remain present until a key is pressed (allowing you to inspect
+the scrollback, and copy and paste text out of it).
+
+\lcont{
+
+When this setting is set to 1, \cw{pterm} will close
+immediately if the process exits cleanly (with an exit status of
+zero), but the window will stay around if the process exits with a
+non-zero code or on a signal. This enables you to see what went
+wrong if the process suffers an error, but not to have to bother
+closing the window in normal circumstances.
+
+}
+
+\dt \cw{pterm.WarnOnClose}
+
+\dd This option should be set to either 0 or 1; the default is 1.
+When set to 1, \cw{pterm} will ask for confirmation before closing
+its window when you press the close button.
+
+\dt \cw{pterm.TerminalType}
+
+\dd This controls the value set in the \cw{TERM} environment
+variable inside the new terminal. The default is \q{\cw{xterm}}.
+
+\dt \cw{pterm.BackspaceIsDelete}
+
+\dd This option should be set to either 0 or 1; the default is 1.
+When set to 0, the ordinary Backspace key generates the Backspace
+character (\cw{^H}); when set to 1, it generates the Delete
+character (\cw{^?}). Whichever one you set, the terminal device
+inside \cw{pterm} will be set up to expect it.
+
+\dt \cw{pterm.RXVTHomeEnd}
+
+\dd This option should be set to either 0 or 1; the default is 0. When
+it is set to 1, the Home and End keys generate the control sequences
+they would generate in the \cw{rxvt} terminal emulator, instead of
+the more usual ones generated by other emulators.
+
+\dt \cw{pterm.LinuxFunctionKeys}
+
+\dd This option can be set to any number between 0 and 5 inclusive;
+the default is 0. The modes vary the control sequences sent by the
+function keys; for more complete documentation, it is probably
+simplest to try each option in \q{\cw{pterm \-e cat}}, and press the
+keys to see what they generate.
+
+\dt \cw{pterm.NoApplicationKeys}
+
+\dd This option should be set to either 0 or 1; the default is 0. When
+set to 1, it stops the server from ever switching the numeric keypad
+into application mode (where the keys send function-key-like
+sequences instead of numbers or arrow keys). You probably only need
+this if some application is making a nuisance of itself.
+
+\dt \cw{pterm.NoApplicationCursors}
+
+\dd This option should be set to either 0 or 1; the default is 0. When
+set to 1, it stops the server from ever switching the cursor keys
+into application mode (where the keys send slightly different
+sequences). You probably only need this if some application is
+making a nuisance of itself.
+
+\dt \cw{pterm.NoMouseReporting}
+
+\dd This option should be set to either 0 or 1; the default is 0. When
+set to 1, it stops the server from ever enabling mouse reporting
+mode (where mouse clicks are sent to the application instead of
+controlling cut and paste).
+
+\dt \cw{pterm.NoRemoteResize}
+
+\dd This option should be set to either 0 or 1; the default is 0. When
+set to 1, it stops the server from being able to remotely control
+the size of the \cw{pterm} window.
+
+\dt \cw{pterm.NoAltScreen}
+
+\dd This option should be set to either 0 or 1; the default is 0. When
+set to 1, it stops the server from using the \q{alternate screen}
+terminal feature, which lets full-screen applications leave the
+screen exactly the way they found it.
+
+\dt \cw{pterm.NoRemoteWinTitle}
+
+\dd This option should be set to either 0 or 1; the default is 0. When
+set to 1, it stops the server from remotely controlling the title of
+the \cw{pterm} window.
+
+\dt \cw{pterm.NoRemoteQTitle}
+
+\dd This option should be set to either 0 or 1; the default is 1. When
+set to 1, it stops the server from remotely requesting the title of
+the \cw{pterm} window.
+
+\lcont{
+This feature is a \e{POTENTIAL SECURITY HAZARD}. If a malicious
+application can write data to your terminal (for example, if you
+merely \cw{cat} a file owned by someone else on the server
+machine), it can change your window title (unless you have disabled
+this using the \cw{NoRemoteWinTitle} resource) and then use this
+service to have the new window title sent back to the server as if
+typed at the keyboard. This allows an attacker to fake keypresses
+and potentially cause your server-side applications to do things you
+didn't want. Therefore this feature is disabled by default, and we
+recommend you do not turn it on unless you \e{really} know what
+you are doing.
+}
+
+\dt \cw{pterm.NoDBackspace}
+
+\dd This option should be set to either 0 or 1; the default is 0.
+When set to 1, it disables the normal action of the Delete (\cw{^?})
+character when sent from the server to the terminal, which is to
+move the cursor left by one space and erase the character now under
+it.
+
+\dt \cw{pterm.ApplicationCursorKeys}
+
+\dd This option should be set to either 0 or 1; the default is 0. When
+set to 1, the default initial state of the cursor keys are
+application mode (where the keys send function-key-like sequences
+instead of numbers or arrow keys). When set to 0, the default state
+is the normal one.
+
+\dt \cw{pterm.ApplicationKeypad}
+
+\dd This option should be set to either 0 or 1; the default is 0. When
+set to 1, the default initial state of the numeric keypad is
+application mode (where the keys send function-key-like sequences
+instead of numbers or arrow keys). When set to 0, the default state
+is the normal one.
+
+\dt \cw{pterm.NetHackKeypad}
+
+\dd This option should be set to either 0 or 1; the default is 0. When
+set to 1, the numeric keypad operates in NetHack mode. This is
+equivalent to the \cw{\-nethack} command-line option.
+
+\dt \cw{pterm.Answerback}
+
+\dd This option controls the string which the terminal sends in
+response to receiving the \cw{^E} character (\q{tell me about
+yourself}). By default this string is \q{\cw{PuTTY}}.
+
+\dt \cw{pterm.HideMousePtr}
+
+\dd This option should be set to either 0 or 1; the default is 0. When
+it is set to 1, the mouse pointer will disappear if it is over the
+\cw{pterm} window and you press a key. It will reappear as soon as
+you move it.
+
+\dt \cw{pterm.WindowBorder}
+
+\dd This option controls the number of pixels of space between the text
+in the \cw{pterm} window and the window frame. The default is 1.
+You can increase this value, but decreasing it to 0 is not
+recommended because it can cause the window manager's size hints to
+work incorrectly.
+
+\dt \cw{pterm.CurType}
+
+\dd This option should be set to either 0, 1 or 2; the default is 0.
+When set to 0, the text cursor displayed in the window is a
+rectangular block. When set to 1, the cursor is an underline; when
+set to 2, it is a vertical line.
+
+\dt \cw{pterm.BlinkCur}
+
+\dd This option should be set to either 0 or 1; the default is 0. When
+it is set to 1, the text cursor will blink when the window is active.
+
+\dt \cw{pterm.Beep}
+
+\dd This option should be set to either 0 or 2 (yes, 2); the default
+is 0. When it is set to 2, \cw{pterm} will respond to a bell
+character (\cw{^G}) by flashing the window instead of beeping.
+
+\dt \cw{pterm.BellOverload}
+
+\dd This option should be set to either 0 or 1; the default is 0. When
+it is set to 1, \cw{pterm} will watch out for large numbers of
+bells arriving in a short time and will temporarily disable the bell
+until they stop. The idea is that if you \cw{cat} a binary file,
+the frantic beeping will mostly be silenced by this feature and will
+not drive you crazy.
+
+\lcont{
+The bell overload mode is activated by receiving N bells in time T;
+after a further time S without any bells, overload mode will turn
+itself off again.
+
+Bell overload mode is always deactivated by any keypress in the
+terminal. This means it can respond to large unexpected streams of
+data, but does not interfere with ordinary command-line activities
+that generate beeps (such as filename completion).
+}
+
+\dt \cw{pterm.BellOverloadN}
+
+\dd This option counts the number of bell characters which will activate
+bell overload if they are received within a length of time T. The
+default is 5.
+
+\dt \cw{pterm.BellOverloadT}
+
+\dd This option specifies the time period in which receiving N or more
+bells will activate bell overload mode. It is measured in
+microseconds, so (for example) set it to 1000000 for one second. The
+default is 2000000 (two seconds).
+
+\dt \cw{pterm.BellOverloadS}
+
+\dd This option specifies the time period of silence required to turn
+off bell overload mode. It is measured in microseconds, so (for
+example) set it to 1000000 for one second. The default is 5000000
+(five seconds of silence).
+
+\dt \cw{pterm.ScrollbackLines}
+
+\dd This option specifies how many lines of scrollback to save above the
+visible terminal screen. The default is 200. This resource is
+equivalent to the \cw{\-sl} command-line option.
+
+\dt \cw{pterm.DECOriginMode}
+
+\dd This option should be set to either 0 or 1; the default is 0. It
+specifies the default state of DEC Origin Mode. (If you don't know
+what that means, you probably don't need to mess with it.)
+
+\dt \cw{pterm.AutoWrapMode}
+
+\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, very
+long lines will wrap over to the next line on the terminal; when set
+to 0, long lines will be squashed against the right-hand edge of the
+screen.
+
+\dt \cw{pterm.LFImpliesCR}
+
+\dd This option should be set to either 0 or 1; the default is 0. When
+set to 1, the terminal will return the cursor to the left side of
+the screen when it receives a line feed character.
+
+\dt \cw{pterm.WinTitle}
+
+\dd This resource is the same as the \cw{\-T} command-line option:
+it controls the initial title of the window. The default is
+\q{\cw{pterm}}.
+
+\dt \cw{pterm.TermWidth}
+
+\dd This resource is the same as the width part of the \cw{\-geometry}
+command-line option: it controls the number of columns of text in
+the window. The default is 80.
+
+\dt \cw{pterm.TermHeight}
+
+\dd This resource is the same as the width part of the \cw{\-geometry}
+command-line option: it controls the number of columns of text in
+the window. The defaults is 24.
+
+\dt \cw{pterm.Font}
+
+\dd This resource is the same as the \cw{\-fn} command-line option: it
+controls the font used to display normal text. The default is
+\q{\cw{fixed}}.
+
+\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
+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
+controls the font used to display double-width characters. The
+default is unset (double-width characters cannot be displayed).
+
+\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
+(double-width characters are displayed in bold by printing them
+twice at a one-pixel offset).
+
+\dt \cw{pterm.ShadowBoldOffset}
+
+\dd This resource can be set to an integer; the default is \-1. It
+specifies the offset at which text is overprinted when using
+\q{shadow bold} mode. The default (1) means that the text will be
+printed in the normal place, and also one character to the right;
+this seems to work well for most X bitmap fonts, which have a blank
+line of pixels down the right-hand side. For some fonts, you may
+need to set this to \-1, so that the text is overprinted one pixel
+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
+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.
+
+\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
+of decimal numbers giving red, green and blue values: so that black
+is \q{\cw{0,0,0}}, white is \q{\cw{255,255,255}}, red is
+\q{\cw{255,0,0}} and so on.
+
+\lcont{
+
+Colours 0 and 1 specify the foreground colour and its bold
+equivalent (the \cw{\-fg} and \cw{\-bfg} command-line options).
+Colours 2 and 3 specify the background colour and its bold
+equivalent (the \cw{\-bg} and \cw{\-bbg} command-line options).
+Colours 4 and 5 specify the text and block colours used for the
+cursor (the \cw{\-cfg} and \cw{\-cbg} command-line options). Each
+even number from 6 to 20 inclusive specifies the colour to be used
+for one of the ANSI primary colour specifications (black, red,
+green, yellow, blue, magenta, cyan, white, in that order); the odd
+numbers from 7 to 21 inclusive specify the bold version of each
+colour, in the same order. The defaults are:
+
+\c pterm.Colour0: 187,187,187
+\c pterm.Colour1: 255,255,255
+\c pterm.Colour2: 0,0,0
+\c pterm.Colour3: 85,85,85
+\c pterm.Colour4: 0,0,0
+\c pterm.Colour5: 0,255,0
+\c pterm.Colour6: 0,0,0
+\c pterm.Colour7: 85,85,85
+\c pterm.Colour8: 187,0,0
+\c pterm.Colour9: 255,85,85
+\c pterm.Colour10: 0,187,0
+\c pterm.Colour11: 85,255,85
+\c pterm.Colour12: 187,187,0
+\c pterm.Colour13: 255,255,85
+\c pterm.Colour14: 0,0,187
+\c pterm.Colour15: 85,85,255
+\c pterm.Colour16: 187,0,187
+\c pterm.Colour17: 255,85,255
+\c pterm.Colour18: 0,187,187
+\c pterm.Colour19: 85,255,255
+\c pterm.Colour20: 187,187,187
+\c pterm.Colour21: 255,255,255
+
+}
+
+\dt \cw{pterm.RectSelect}
+
+\dd This option should be set to either 0 or 1; the default is 0. When
+set to 0, dragging the mouse over several lines selects to the end
+of each line and from the beginning of the next; when set to 1,
+dragging the mouse over several lines selects a rectangular region.
+In each case, holding down Alt while dragging gives the other
+behaviour.
+
+\dt \cw{pterm.MouseOverride}
+
+\dd This option should be set to either 0 or 1; the default is 1. When
+set to 1, if the application requests mouse tracking (so that mouse
+clicks are sent to it instead of doing selection), holding down
+Shift will revert the mouse to normal selection. When set to 0,
+mouse tracking completely disables selection.
+
+\dt \cw{pterm.Printer}
+
+\dd This option is unset by default. If you set it, then
+server-controlled printing is enabled: the server can send control
+sequences to request data to be sent to a printer. That data will be
+piped into the command you specify here; so you might want to set it
+to \q{\cw{lpr}}, for example, or \q{\cw{lpr \-Pmyprinter}}.
+
+\dt \cw{pterm.ScrollBar}
+
+\dd This option should be set to either 0 or 1; the default is 1. When
+set to 0, the scrollbar is hidden (although Shift-PageUp and
+Shift-PageDown still work). This is the same as the \cw{\-sb}
+command-line option.
+
+\dt \cw{pterm.ScrollbarOnLeft}
+
+\dd This option should be set to either 0 or 1; the default is 0. When
+set to 1, the scrollbar will be displayed on the left of the
+terminal instead of on the right.
+
+\dt \cw{pterm.ScrollOnKey}
+
+\dd This option should be set to either 0 or 1; the default is 0. When
+set to 1, any keypress causes the position of the scrollback to be
+reset to the very bottom.
+
+\dt \cw{pterm.ScrollOnDisp}
+
+\dd This option should be set to either 0 or 1; the default is 1. When
+set to 1, any activity in the display causes the position of the
+scrollback to be reset to the very bottom.
+
+\dt \cw{pterm.LineCodePage}
+
+\dd This option specifies the character set to be used for the session.
+This is the same as the \cw{\-cs} command-line option.
+
+\dt \cw{pterm.NoRemoteCharset}
+
+\dd This option disables the terminal's ability to change its character
+set when it receives escape sequences telling it to. You might need
+to do this to interoperate with programs which incorrectly change
+the character set to something they think is sensible.
+
+\dt \cw{pterm.BCE}
+
+\dd This option should be set to either 0 or 1; the default is 1. When
+set to 1, the various control sequences that erase parts of the
+terminal display will erase in whatever the current background
+colour is; when set to 0, they will erase in black always.
+
+\dt \cw{pterm.BlinkText}
+
+\dd This option should be set to either 0 or 1; the default is 0. When
+set to 1, text specified as blinking by the server will actually
+blink on and off; when set to 0, \cw{pterm} will use the less
+distracting approach of making the text's background colour bold.
+
+\dt \cw{pterm.StampUtmp}
+
+\dd This option should be set to either 0 or 1; the default is 1. When
+set to 1, \cw{pterm} will log the login in the various system log
+files. This resource is equivalent to the \cw{\-ut} command-line
+option.
+
+\dt \cw{pterm.LoginShell}
+
+\dd This option should be set to either 0 or 1; the default is 1. When
+set to 1, \cw{pterm} will execute your shell as a login shell. This
+resource is equivalent to the \cw{\-ls} command-line option.
+
+\S{pterm-manpage-bugs} BUGS
+
+Most of the X resources have silly names. (Historical reasons from
+PuTTY, mostly.)
--- /dev/null
+\cfg{man-identity}{putty}{1}{2004-03-24}{PuTTY tool suite}{PuTTY tool suite}
+
+\H{putty-manpage} Man page for PuTTY
+
+\S{putty-manpage-name} NAME
+
+\cw{putty} - GUI SSH, Telnet and Rlogin client for X
+
+\S{putty-manpage-synopsis} SYNOPSIS
+
+\c putty [ options ] [ host ]
+\e bbbbb iiiiiii iiii
+
+\S{putty-manpage-description} DESCRIPTION
+
+\cw{putty} is a graphical SSH, Telnet and Rlogin client for X. It is
+a direct port of the Windows SSH client of the same name.
+
+\S{putty-manpage-options} OPTIONS
+
+The command-line options supported by \cw{putty} are:
+
+\dt \cw{\-\-display} \e{display\-name}
+
+\dd Specify the X display on which to open \cw{putty}. (Note this
+option has a double minus sign, even though none of the others do.
+This is because this option is supplied automatically by GTK.
+Sorry.)
+
+\dt \cw{\-fn} \e{font-name}
+
+\dd Specify the font to use for normal text displayed in the terminal.
+
+\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 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
+Chinese, Japanese and Korean text) displayed in the terminal.
+
+\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.
+
+\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
+specifications.
+
+\dt \cw{\-sl} \e{lines}
+
+\dd Specify the number of lines of scrollback to save off the top of the
+terminal.
+
+\dt \cw{\-fg} \e{colour}
+
+\dd Specify the foreground colour to use for normal text.
+
+\dt \cw{\-bg} \e{colour}
+
+\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).
+
+\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 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}
+
+\dd Specify the foreground colour to use for text covered by the cursor.
+
+\dt \cw{\-cbg} \e{colour}
+
+\dd Specify the background colour to use for text covered by the cursor.
+In other words, this is the main colour of the cursor.
+
+\dt \cw{\-title} \e{title}
+
+\dd Specify the initial title of the terminal window. (This can be
+changed under control of the server.)
+
+\dt \cw{\-sb\-} or \cw{+sb}
+
+\dd Tells \cw{putty} not to display a scroll bar.
+
+\dt \cw{\-sb}
+
+\dd Tells \cw{putty} to display a scroll bar: this is the opposite of
+\cw{\-sb\-}. This is the default option: you will probably only need
+to specify it explicitly if you have changed the default using the
+\cw{ScrollBar} resource.
+
+\dt \cw{\-log} \e{filename}
+
+\dd This option makes \cw{putty} log all the terminal output to a file
+as well as displaying it in the terminal.
+
+
+\dt \cw{\-cs} \e{charset}
+
+\dd This option specifies the character set in which \cw{putty}
+should assume the session is operating. This character set will be
+used to interpret all the data received from the session, and all
+input you type or paste into \cw{putty} will be converted into
+this character set before being sent to the session.
+
+\lcont{ Any character set name which is valid in a MIME header (and
+supported by \cw{putty}) should be valid here (examples are
+\q{\cw{ISO-8859-1}}, \q{\cw{windows-1252}} or \q{\cw{UTF-8}}). Also,
+any character encoding which is valid in an X logical font
+description should be valid (\q{\cw{ibm-cp437}}, for example).
+
+\cw{putty}'s default behaviour is to use the same character
+encoding as its primary font. If you supply a Unicode
+(\cw{iso10646-1}) font, it will default to the UTF-8 character set.
+
+Character set names are case-insensitive.
+}
+
+\dt \cw{\-nethack}
+
+\dd Tells \cw{putty} to enable NetHack keypad mode, in which the
+numeric keypad generates the NetHack \c{hjklyubn} direction keys.
+This enables you to play NetHack with the numeric keypad without
+having to use the NetHack \c{number_pad} option (which requires you
+to press \q{\cw{n}} before any repeat count). So you can move with
+the numeric keypad, and enter repeat counts with the normal number
+keys.
+
+\dt \cw{\-help}, \cw{\-\-help}
+
+\dd Display a message summarizing the available options.
+
+\dt \cw{\-pgpfp}
+
+\dd Display the fingerprints of the PuTTY PGP Master Keys, to aid
+in verifying new files released by the PuTTY team.
+
+\dt \cw{\-load} \e{session}
+
+\dd Load a saved session by name. This allows you to run a saved session
+straight from the command line without having to go through the
+configuration box first.
+
+\dt \cw{\-ssh}, \cw{\-telnet}, \cw{\-rlogin}, \cw{\-raw}, \cw{\-serial}
+
+\dd Select the protocol \cw{putty} will use to make the connection.
+
+\dt \cw{\-l} \e{username}
+
+\dd Specify the username to use when logging in to the server.
+
+\dt \cw{\-L} \cw{[}\e{srcaddr}\cw{:]}\e{srcport}\cw{:}\e{desthost}\cw{:}\e{destport}
+
+\dd Set up a local port forwarding: listen on \e{srcport} (or
+\e{srcaddr}:\e{srcport} if specified), and forward any connections
+over the SSH connection to the destination address
+\e{desthost}:\e{destport}. Only works in SSH.
+
+\dt \cw{\-R} \cw{[}\e{srcaddr}\cw{:]}\e{srcport}\cw{:}\e{desthost}\cw{:}\e{destport}
+
+\dd Set up a remote port forwarding: ask the SSH server to listen on
+\e{srcport} (or \e{srcaddr}:\e{srcport} if specified), and to
+forward any connections back over the SSH connection where the
+client will pass them on to the destination address
+\e{desthost}:\e{destport}. Only works in SSH.
+
+\dt \cw{\-D} [\e{srcaddr}:]\e{srcport}
+
+\dd Set up dynamic port forwarding. The client listens on
+\e{srcport} (or \e{srcaddr}:\e{srcport} if specified), and
+implements a SOCKS server. So you can point SOCKS-aware applications
+at this port and they will automatically use the SSH connection to
+tunnel all their connections. Only works in SSH.
+
+\dt \cw{\-P} \e{port}
+
+\dd Specify the port to connect to the server on.
+
+\dt \cw{\-A}, \cw{\-a}
+
+\dd Enable (\cw{\-A}) or disable (\cw{\-a}) SSH agent forwarding.
+Currently this only works with OpenSSH and SSH-1.
+
+\dt \cw{\-X}, \cw{\-x}
+
+\dd Enable (\cw{\-X}) or disable (\cw{\-x}) X11 forwarding.
+
+\dt \cw{\-T}, \cw{\-t}
+
+\dd Enable (\cw{\-t}) or disable (\cw{\-T}) the allocation of a
+pseudo-terminal at the server end.
+
+\dt \cw{\-C}
+
+\dd Enable zlib-style compression on the connection.
+
+\dt \cw{\-1}, \cw{\-2}
+
+\dd Select SSH protocol version 1 or 2.
+
+\dt \cw{\-i} \e{keyfile}
+
+\dd Specify a private key file to use for authentication. For SSH-2
+keys, this key file must be in PuTTY's format, not OpenSSH's or
+anyone else's.
+
+\dt \cw{\-sercfg} \e{configuration-string}
+
+\dd Specify the configuration parameters for the serial port, in
+\cw{-serial} mode. \e{configuration-string} should be a
+comma-separated list of configuration parameters as follows:
+
+\lcont{
+
+\b Any single digit from 5 to 9 sets the number of data bits.
+
+\b \cq{1}, \cq{1.5} or \cq{2} sets the number of stop bits.
+
+\b Any other numeric string is interpreted as a baud rate.
+
+\b A single lower-case letter specifies the parity: \cq{n} for none,
+\cq{o} for odd, \cq{e} for even, \cq{m} for mark and \cq{s} for space.
+
+\b A single upper-case letter specifies the flow control: \cq{N} for
+none, \cq{X} for XON/XOFF, \cq{R} for RTS/CTS and \cq{D} for
+DSR/DTR.
+
+}
+
+\S{putty-manpage-saved-sessions} SAVED SESSIONS
+
+Saved sessions are stored in a \cw{.putty/sessions} subdirectory in
+your home directory.
+
+\S{putty-manpage-more-information} MORE INFORMATION
+
+For more information on PuTTY, it's probably best to go and look at
+the manual on the web page:
+
+\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/}\cw{http://www.chiark.greenend.org.uk/~sgtatham/putty/}
+
+\S{putty-manpage-bugs} BUGS
+
+This man page isn't terribly complete.
--- /dev/null
+\cfg{man-mindepth}{2}
+
+\C{not-shown} Chapter title which is not shown
--- /dev/null
+\A{man-pages} Man pages for Unix PuTTY
+
+This appendix contains all the man pages for Unix PuTTY.
--- /dev/null
+\define{versionidpageant} \versionid $Id$
+
+\C{pageant} Using \i{Pageant} for authentication
+
+\cfg{winhelp-topic}{pageant.general}
+
+Pageant is an SSH \i{authentication agent}. It holds your \i{private key}s
+in memory, already decoded, so that you can use them often
+\I{passwordless login}without needing to type a \i{passphrase}.
+
+\H{pageant-start} Getting started with Pageant
+
+Before you run Pageant, you need to have a private key in \c{*.\i{PPK}}
+format. See \k{pubkey} to find out how to generate and use one.
+
+When you run Pageant, it will put an icon of a computer wearing a
+hat into the \ii{System tray}. It will then sit and do nothing, until you
+load a private key into it.
+
+If you click the Pageant icon with the right mouse button, you will
+see a menu. Select \q{View Keys} from this menu. The Pageant main
+window will appear. (You can also bring this window up by
+double-clicking on the Pageant icon.)
+
+The Pageant window contains a list box. This shows the private keys
+Pageant is holding. When you start Pageant, it has no keys, so the
+list box will be empty. After you add one or more keys, they will
+show up in the list box.
+
+To add a key to Pageant, press the \q{Add Key} button. Pageant will
+bring up a file dialog, labelled \q{Select Private Key File}. Find
+your private key file in this dialog, and press \q{Open}.
+
+Pageant will now load the private key. If the key is protected by a
+passphrase, Pageant will ask you to type the passphrase. When the
+key has been loaded, it will appear in the list in the Pageant
+window.
+
+Now start PuTTY and open an SSH session to a site that accepts your
+key. PuTTY will notice that Pageant is running, retrieve the key
+automatically from Pageant, and use it to authenticate. You can now
+open as many PuTTY sessions as you like without having to type your
+passphrase again.
+
+(PuTTY can be configured not to try to use Pageant, but it will try
+by default. See \k{config-ssh-tryagent} and
+\k{using-cmdline-agentauth} for more information.)
+
+When you want to shut down Pageant, click the right button on the
+Pageant icon in the System tray, and select \q{Exit} from the menu.
+Closing the Pageant main window does \e{not} shut down Pageant.
+
+\H{pageant-mainwin} The Pageant main window
+
+The Pageant main window appears when you left-click on the Pageant
+system tray icon, or alternatively right-click and select \q{View
+Keys} from the menu. You can use it to keep track of what keys are
+currently loaded into Pageant, and to add new ones or remove the
+existing keys.
+
+\S{pageant-mainwin-keylist} The key list box
+
+\cfg{winhelp-topic}{pageant.keylist}
+
+The large list box in the Pageant main window lists the private keys
+that are currently loaded into Pageant. The list might look
+something like this:
+
+\c ssh1 1024 22:c3:68:3b:09:41:36:c3:39:83:91:ae:71:b2:0f:04 k1
+\c ssh-rsa 1023 74:63:08:82:95:75:e1:7c:33:31:bb:cb:00:c0:89:8b k2
+
+For each key, the list box will tell you:
+
+\b The type of the key. Currently, this can be \c{ssh1} (an RSA key
+for use with the SSH-1 protocol), \c{ssh-rsa} (an RSA key for use
+with the SSH-2 protocol), or \c{ssh-dss} (a DSA key for use with
+the SSH-2 protocol).
+
+\b The size (in bits) of the key.
+
+\b The \I{key fingerprint}fingerprint for the public key. This should be
+the same fingerprint given by PuTTYgen, and (hopefully) also the same
+fingerprint shown by remote utilities such as \i\c{ssh-keygen} when
+applied to your \c{authorized_keys} file.
+
+\b The comment attached to the key.
+
+\S{pageant-mainwin-addkey} The \q{Add Key} button
+
+\cfg{winhelp-topic}{pageant.addkey}
+
+To add a key to Pageant by reading it out of a local disk file,
+press the \q{Add Key} button in the Pageant main window, or
+alternatively right-click on the Pageant icon in the system tray and
+select \q{Add Key} from there.
+
+Pageant will bring up a file dialog, labelled \q{Select Private Key
+File}. Find your private key file in this dialog, and press
+\q{Open}. If you want to add more than one key at once, you can
+select multiple files using Shift-click (to select several adjacent
+files) or Ctrl-click (to select non-adjacent files).
+
+Pageant will now load the private key(s). If a key is protected by a
+passphrase, Pageant will ask you to type the passphrase.
+
+(This is not the only way to add a private key to Pageant. You can
+also add one from a remote system by using agent forwarding; see
+\k{pageant-forward} for details.)
+
+\S{pageant-mainwin-remkey} The \q{Remove Key} button
+
+\cfg{winhelp-topic}{pageant.remkey}
+
+If you need to remove a key from Pageant, select that key in the
+list box, and press the \q{Remove Key} button. Pageant will remove
+the key from its memory.
+
+You can apply this to keys you added using the \q{Add Key} button,
+or to keys you added remotely using agent forwarding (see
+\k{pageant-forward}); it makes no difference.
+
+\H{pageant-cmdline} The Pageant command line
+
+Pageant can be made to do things automatically when it starts up, by
+\I{command-line arguments}specifying instructions on its command line.
+If you're starting Pageant from the Windows GUI, you can arrange this
+by editing the properties of the \i{Windows shortcut} that it was
+started from.
+
+If Pageant is already running, invoking it again with the options
+below causes actions to be performed with the existing instance, not a
+new one.
+
+\S{pageant-cmdline-loadkey} Making Pageant automatically load keys
+on startup
+
+Pageant can automatically load one or more private keys when it
+starts up, if you provide them on the Pageant command line. Your
+command line might then look like:
+
+\c C:\PuTTY\pageant.exe d:\main.ppk d:\secondary.ppk
+
+If the keys are stored encrypted, Pageant will request the
+passphrases on startup.
+
+If Pageant is already running, this syntax loads keys into the
+existing Pageant.
+
+\S{pageant-cmdline-command} Making Pageant run another program
+
+You can arrange for Pageant to start another program once it has
+initialised itself and loaded any keys specified on its command
+line. This program (perhaps a PuTTY, or a WinCVS making use of
+Plink, or whatever) will then be able to use the keys Pageant has
+loaded.
+
+You do this by specifying the \I{-c-pageant}\c{-c} option followed
+by the command, like this:
+
+\c C:\PuTTY\pageant.exe d:\main.ppk -c C:\PuTTY\putty.exe
+
+\H{pageant-forward} Using \i{agent forwarding}
+
+Agent forwarding is a mechanism that allows applications on your SSH
+server machine to talk to the agent on your client machine.
+
+Note that at present, agent forwarding in SSH-2 is only available
+when your SSH server is \i{OpenSSH}. The \i\cw{ssh.com} server uses a
+different agent protocol, which PuTTY does not yet support.
+
+To enable agent forwarding, first start Pageant. Then set up a PuTTY
+SSH session in which \q{Allow agent forwarding} is enabled (see
+\k{config-ssh-agentfwd}). Open the session as normal. (Alternatively,
+you can use the \c{-A} command line option; see
+\k{using-cmdline-agent} for details.)
+
+If this has worked, your applications on the server should now have
+access to a Unix domain socket which the SSH server will forward
+back to PuTTY, and PuTTY will forward on to the agent. To check that
+this has actually happened, you can try this command on Unix server
+machines:
+
+\c unixbox:~$ echo $SSH_AUTH_SOCK
+\c /tmp/ssh-XXNP18Jz/agent.28794
+\c unixbox:~$
+
+If the result line comes up blank, agent forwarding has not been
+enabled at all.
+
+Now if you run \c{ssh} on the server and use it to connect through
+to another server that accepts one of the keys in Pageant, you
+should be able to log in without a password:
+
+\c unixbox:~$ ssh -v otherunixbox
+\c [...]
+\c debug: next auth method to try is publickey
+\c debug: userauth_pubkey_agent: trying agent key my-putty-key
+\c debug: ssh-userauth2 successful: method publickey
+\c [...]
+
+If you enable agent forwarding on \e{that} SSH connection as well
+(see the manual for your server-side SSH client to find out how to
+do this), your authentication keys will still be available on the
+next machine you connect to - two SSH connections away from where
+they're actually stored.
+
+In addition, if you have a private key on one of the SSH servers,
+you can send it all the way back to Pageant using the local
+\i\c{ssh-add} command:
+
+\c unixbox:~$ ssh-add ~/.ssh/id_rsa
+\c Need passphrase for /home/fred/.ssh/id_rsa
+\c Enter passphrase for /home/fred/.ssh/id_rsa:
+\c Identity added: /home/fred/.ssh/id_rsa (/home/simon/.ssh/id_rsa)
+\c unixbox:~$
+
+and then it's available to every machine that has agent forwarding
+available (not just the ones downstream of the place you added it).
+
+\H{pageant-security} Security considerations
+
+\I{security risk}Using Pageant for public-key authentication gives you the
+convenience of being able to open multiple SSH sessions without
+having to type a passphrase every time, but also gives you the
+security benefit of never storing a decrypted private key on disk.
+Many people feel this is a good compromise between security and
+convenience.
+
+It \e{is} a compromise, however. Holding your decrypted private keys
+in Pageant is better than storing them in easy-to-find disk files,
+but still less secure than not storing them anywhere at all. This is
+for two reasons:
+
+\b Windows unfortunately provides no way to protect pieces of memory
+from being written to the system \i{swap file}. So if Pageant is holding
+your private keys for a long period of time, it's possible that
+decrypted private key data may be written to the system swap file,
+and an attacker who gained access to your hard disk later on might
+be able to recover that data. (However, if you stored an unencrypted
+key in a disk file they would \e{certainly} be able to recover it.)
+
+\b Although, like most modern operating systems, Windows prevents
+programs from accidentally accessing one another's memory space, it
+does allow programs to access one another's memory space
+deliberately, for special purposes such as debugging. This means
+that if you allow a virus, trojan, or other malicious program on to
+your Windows system while Pageant is running, it could access the
+memory of the Pageant process, extract your decrypted authentication
+keys, and send them back to its master.
+
+Similarly, use of agent \e{forwarding} is a security improvement on
+other methods of one-touch authentication, but not perfect. Holding
+your keys in Pageant on your Windows box has a security advantage
+over holding them on the remote server machine itself (either in an
+agent or just unencrypted on disk), because if the server machine
+ever sees your unencrypted private key then the sysadmin or anyone
+who cracks the machine can steal the keys and pretend to be you for
+as long as they want.
+
+However, the sysadmin of the server machine can always pretend to be
+you \e{on that machine}. So if you forward your agent to a server
+machine, then the sysadmin of that machine can access the forwarded
+agent connection and request signatures from your private keys, and
+can therefore log in to other machines as you. They can only do this
+to a limited extent - when the agent forwarding disappears they lose
+the ability - but using Pageant doesn't actually \e{prevent} the
+sysadmin (or hackers) on the server from doing this.
+
+Therefore, if you don't trust the sysadmin of a server machine, you
+should \e{never} use agent forwarding to that machine. (Of course
+you also shouldn't store private keys on that machine, type
+passphrases into it, or log into other machines from it in any way
+at all; Pageant is hardly unique in this respect.)
--- /dev/null
+\define{versionidpgpkeys} \versionid $Id$
+
+\A{pgpkeys} PuTTY download keys and signatures
+
+\cfg{winhelp-topic}{pgpfingerprints}
+
+\I{verifying new versions}We create \i{PGP signatures} for all the PuTTY
+files distributed from our web site, so that users can be confident
+that the files have not been tampered with. Here we identify
+our public keys, and explain our signature policy so you can have an
+accurate idea of what each signature guarantees.
+This description is provided as both a web page on the PuTTY site, and
+an appendix in the PuTTY manual.
+
+As of release 0.58, all of the PuTTY executables contain fingerprint
+material (usually accessed via the \i\c{-pgpfp} command-line
+option), such that if you have an executable you trust, you can use
+it to establish a trust path, for instance to a newer version
+downloaded from the Internet.
+
+(Note that none of the keys, signatures, etc mentioned here have
+anything to do with keys used with SSH - they are purely for verifying
+the origin of files distributed by the PuTTY team.)
+
+\H{pgpkeys-pubkey} Public keys
+
+We supply two complete sets of keys. We supply a set of RSA keys,
+compatible with both \W{http://www.gnupg.org/}{GnuPG} and PGP2,
+and also a set of DSA keys compatible with GnuPG.
+
+In each format, we have three keys:
+
+\b A Development Snapshots key, used to sign the nightly builds.
+
+\b A Releases key, used to sign actual releases.
+
+\b A Master Key. The Master Key is used to sign the other two keys, and
+they sign it in return.
+
+Therefore, we have six public keys in total:
+
+\b RSA:
+\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/keys/master-rsa.asc}{Master Key},
+\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/keys/release-rsa.asc}{Release key},
+\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/keys/snapshot-rsa.asc}{Snapshot key}
+
+\lcont{
+Master Key: 1024-bit; \I{PGP key fingerprint}fingerprint:
+\cw{8F\_15\_97\_DA\_25\_30\_AB\_0D\_\_88\_D1\_92\_54\_11\_CF\_0C\_4C}
+}
+
+\b DSA:
+\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/keys/master-dsa.asc}{Master Key},
+\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/keys/release-dsa.asc}{Release key},
+\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/keys/snapshot-dsa.asc}{Snapshot key}
+
+\lcont{
+Master Key: 1024-bit; fingerprint:
+\cw{313C\_3E76\_4B74\_C2C5\_F2AE\_\_83A8\_4F5E\_6DF5\_6A93\_B34E}
+}
+
+\H{pgpkeys-security} Security details
+
+The various keys have various different security levels. This
+section explains what those security levels are, and how far you can
+expect to trust each key.
+
+\S{pgpkeys-snapshot} The Development Snapshots keys
+
+These keys are stored \e{without passphrases}. This is
+necessary, because the snapshots are generated every night without
+human intervention, so nobody would be able to type a passphrase.
+
+The actual snapshots are built on a team member's home Windows box.
+The keys themselves are stored on an independently run Unix box
+(the same one that hosts our Subversion repository). After
+being built, the binaries are uploaded to this Unix box and then
+signed automatically.
+
+Therefore, a signature from one of the Development Snapshots keys
+\e{DOES} protect you against:
+
+\b People tampering with the PuTTY binaries between the PuTTY web site
+and you.
+
+But it \e{DOES NOT} protect you against:
+
+\b People tampering with the binaries before they are uploaded to the
+independent Unix box.
+
+\b The sysadmin of the independent Unix box using his root privilege to
+steal the private keys and abuse them, or tampering with the
+binaries before they are signed.
+
+\b Somebody getting root on the Unix box.
+
+Of course, we don't believe any of those things is very likely. We
+know our sysadmin personally and trust him (both to be competent and
+to be non-malicious), and we take all reasonable precautions to
+guard the build machine. But when you see a signature, you should
+always be certain of precisely what it guarantees and precisely what
+it does not.
+
+\S{pgpkeys-release} The Releases keys
+
+The Release keys have passphrases and we can be more careful about
+how we use them.
+
+The Release keys are kept safe on the developers' own local
+machines, and only used to sign releases that have been built by
+hand. A signature from a Release key protects you from almost any
+plausible attack.
+
+(Some of the developers' machines have cable modem connections and
+might in theory be crackable, but of course the private keys are
+still encrypted, so the crack would have to go unnoticed for long
+enough to steal a passphrase.)
+
+\S{pgpkeys-master} The Master Keys
+
+The Master Keys sign almost nothing. Their purpose is to bind the
+other keys together and certify that they are all owned by the same
+people and part of the same integrated setup. The only signatures
+produced by the Master Keys, \e{ever}, should be the signatures
+on the other keys.
+
+We intend to arrange for the Master Keys to sign each other, to
+certify that the DSA keys and RSA keys are part of the same setup.
+We have not yet got round to this at the time of writing.
+
+We have collected a few third-party signatures on the Master Keys,
+in order to increase the chances that you can find a suitable trust
+path to them. We intend to collect more. (Note that the keys on the
+keyservers appear to have also collected some signatures from people
+who haven't performed any verification of the Master Keys.)
+
+We have uploaded our various keys to public keyservers, so that
+even if you don't know any of the people who have signed our
+keys, you can still be reasonably confident that an attacker would
+find it hard to substitute fake keys on all the public keyservers at
+once.
--- /dev/null
+\define{versionidplink} \versionid $Id$
+
+\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
+making CVS access a repository on a remote server.
+
+Plink is probably not what you want if you want to run an
+\i{interactive session} in a console window.
+
+\H{plink-starting} Starting Plink
+
+Plink is a command line application. This means that you cannot just
+double-click on its icon to run it and instead you have to bring up
+a \i{console window}. In Windows 95, 98, and ME, this is called an
+\q{MS-DOS Prompt}, and in Windows NT, 2000, and XP, it is called a
+\q{Command Prompt}. It should be available from the Programs section
+of your Start Menu.
+
+In order to use Plink, the file \c{plink.exe} will need either to be
+on your \i{\c{PATH}} or in your current directory. To add the
+directory containing Plink to your \c{PATH} environment variable,
+type into the console window:
+
+\c set PATH=C:\path\to\putty\directory;%PATH%
+
+This will only work for the lifetime of that particular console
+window. To set your \c{PATH} more permanently on Windows NT, 2000,
+and XP, use the Environment tab of the System Control Panel. On
+Windows 95, 98, and ME, you will need to edit your \i\c{AUTOEXEC.BAT}
+to include a \c{set} command like the one above.
+
+\H{plink-usage} Using Plink
+
+This section describes the basics of how to use Plink for
+interactive logins and for automated processes.
+
+Once you've got a console window to type into, you can just type
+\c{plink} on its own to bring up a usage message. This tells you the
+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.61
+\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
+\c -v show verbose messages
+\c -load sessname Load settings from saved session
+\c -ssh -telnet -rlogin -raw -serial
+\c force use of a particular protocol
+\c -P port connect to specified port
+\c -l user connect with specified username
+\c -batch disable all interactive prompts
+\c The following options only apply to SSH connections:
+\c -pw passw login with specified password
+\c -D [listen-IP:]listen-port
+\c Dynamic SOCKS-based port forwarding
+\c -L [listen-IP:]listen-port:host:port
+\c Forward local port to remote address
+\c -R [listen-IP:]listen-port:host:port
+\c Forward remote port to local address
+\c -X -x enable / disable X11 forwarding
+\c -A -a enable / disable agent forwarding
+\c -t -T enable / disable pty allocation
+\c -1 -2 force use of particular protocol version
+\c -4 -6 force use of IPv4 or IPv6
+\c -C enable compression
+\c -i key private key file for authentication
+\c -noagent disable use of Pageant
+\c -agent enable use of Pageant
+\c -m file read remote command(s) from file
+\c -s remote command is an SSH subsystem (SSH-2 only)
+\c -N don't start a shell/command (SSH-2 only)
+\c -nc host:port
+\c open tunnel in place of session (SSH-2 only)
+\c -sercfg configuration-string (e.g. 19200,8,n,1,X)
+\c Specify the serial configuration (serial only)
+
+Once this works, you are ready to use Plink.
+
+\S{plink-usage-interactive} Using Plink for interactive logins
+
+To make a simple interactive connection to a remote server, just
+type \c{plink} and then the host name:
+
+\c Z:\sysosd>plink login.example.com
+\c
+\c Debian GNU/Linux 2.2 flunky.example.com
+\c flunky login:
+
+You should then be able to log in as normal and run a session. The
+output sent by the server will be written straight to your command
+prompt window, which will most likely not interpret terminal \i{control
+codes} in the way the server expects it to. So if you run any
+full-screen applications, for example, you can expect to see strange
+characters appearing in your window. Interactive connections like
+this are not the main point of Plink.
+
+In order to connect with a different protocol, you can give the
+command line options \c{-ssh}, \c{-telnet}, \c{-rlogin} or \c{-raw}.
+To make an SSH connection, for example:
+
+\c Z:\sysosd>plink -ssh login.example.com
+\c login as:
+
+If you have already set up a PuTTY saved session, then instead of
+supplying a host name, you can give the saved session name. This
+allows you to use public-key authentication, specify a user name,
+and use most of the other features of PuTTY:
+
+\c Z:\sysosd>plink my-ssh-session
+\c Sent username "fred"
+\c Authenticating with public key "fred@winbox"
+\c Last login: Thu Dec 6 19:25:33 2001 from :0.0
+\c fred@flunky:~$
+
+(You can also use the \c{-load} command-line option to load a saved
+session; see \k{using-cmdline-load}. If you use \c{-load}, the saved
+session exists, and it specifies a hostname, you cannot also specify a
+\c{host} or \c{user@host} argument - it will be treated as part of the
+remote command.)
+
+\S{plink-usage-batch} Using Plink for automated connections
+
+More typically Plink is used with the SSH protocol, to enable you to
+talk directly to a program running on the server. To do this you
+have to ensure Plink is \e{using} the SSH protocol. You can do this
+in several ways:
+
+\b Use the \c{-ssh} option as described in
+\k{plink-usage-interactive}.
+
+\b Set up a PuTTY saved session that describes the server you are
+connecting to, and that also specifies the protocol as SSH.
+
+\b Set the Windows environment variable \i\c{PLINK_PROTOCOL} to the
+word \c{ssh}.
+
+Usually Plink is not invoked directly by a user, but run
+automatically by another process. Therefore you typically do not
+want Plink to prompt you for a user name or a password.
+
+Next, you are likely to need to avoid the various interactive
+prompts Plink can produce. You might be prompted to verify the host
+key of the server you're connecting to, to enter a user name, or to
+enter a password.
+
+To avoid being prompted for the server host key when using Plink for
+an automated connection, you should first make a \e{manual}
+connection (using either of PuTTY or Plink) to the same server,
+verify the host key (see \k{gs-hostkey} for more information), and
+select Yes to add the host key to the Registry. After that, Plink
+commands connecting to that server should not give a host key prompt
+unless the host key changes.
+
+To avoid being prompted for a user name, you can:
+
+\b Use the \c{-l} option to specify a user name on the command line.
+For example, \c{plink login.example.com -l fred}.
+
+\b Set up a PuTTY saved session that describes the server you are
+connecting to, and that also specifies the username to log in as
+(see \k{config-username}).
+
+To avoid being prompted for a password, you should almost certainly
+set up \i{public-key authentication}. (See \k{pubkey} for a general
+introduction to public-key authentication.) Again, you can do this
+in two ways:
+
+\b Set up a PuTTY saved session that describes the server you are
+connecting to, and that also specifies a private key file (see
+\k{config-ssh-privkey}). For this to work without prompting, your
+private key will need to have no passphrase.
+
+\b Store the private key in Pageant. See \k{pageant} for further
+information.
+
+Once you have done all this, you should be able to run a remote
+command on the SSH server machine and have it execute automatically
+with no prompting:
+
+\c Z:\sysosd>plink login.example.com -l fred echo hello, world
+\c hello, world
+\c
+\c Z:\sysosd>
+
+Or, if you have set up a saved session with all the connection
+details:
+
+\c Z:\sysosd>plink mysession echo hello, world
+\c hello, world
+\c
+\c Z:\sysosd>
+
+Then you can set up other programs to run this Plink command and
+talk to it as if it were a process on the server machine.
+
+\S{plink-options} Plink command line options
+
+Plink accepts all the general command line options supported by the
+PuTTY tools. See \k{using-general-opts} for a description of these
+options.
+
+Plink also supports some of its own options. The following sections
+describe Plink's specific command-line options.
+
+\S2{plink-option-batch} \I{-batch-plink}\c{-batch}: disable all
+interactive prompts
+
+If you use the \c{-batch} option, Plink will never give an
+interactive prompt while establishing the connection. If the
+server's host key is invalid, for example (see \k{gs-hostkey}), then
+the connection will simply be abandoned instead of asking you what
+to do next.
+
+This may help Plink's behaviour when it is used in automated
+scripts: using \c{-batch}, if something goes wrong at connection
+time, the batch job will fail rather than hang.
+
+\S2{plink-option-s} \I{-s-plink}\c{-s}: remote command is SSH subsystem
+
+If you specify the \c{-s} option, Plink passes the specified command
+as the name of an SSH \q{\i{subsystem}} rather than an ordinary command
+line.
+
+(This option is only meaningful with the SSH-2 protocol.)
+
+\H{plink-batch} Using Plink in \i{batch files} and \i{scripts}
+
+Once you have set up Plink to be able to log in to a remote server
+without any interactive prompting (see \k{plink-usage-batch}), you
+can use it for lots of scripting and batch purposes. For example, to
+start a backup on a remote machine, you might use a command like:
+
+\c plink root@myserver /etc/backups/do-backup.sh
+
+Or perhaps you want to fetch all system log lines relating to a
+particular web area:
+
+\c plink mysession grep /~fred/ /var/log/httpd/access.log > fredlog
+
+Any non-interactive command you could usefully run on the server
+command line, you can run in a batch file using Plink in this way.
+
+\H{plink-cvs} Using Plink with \i{CVS}
+
+To use Plink with CVS, you need to set the environment variable
+\i\c{CVS_RSH} to point to Plink:
+
+\c set CVS_RSH=\path\to\plink.exe
+
+You also need to arrange to be able to connect to a remote host
+without any interactive prompts, as described in
+\k{plink-usage-batch}.
+
+You should then be able to run CVS as follows:
+
+\c cvs -d :ext:user@sessionname:/path/to/repository co module
+
+If you specified a username in your saved session, you don't even
+need to specify the \q{user} part of this, and you can just say:
+
+\c cvs -d :ext:sessionname:/path/to/repository co module
+
+\H{plink-wincvs} Using Plink with \i{WinCVS}
+
+Plink can also be used with WinCVS. Firstly, arrange for Plink to be
+able to connect to a remote host non-interactively, as described in
+\k{plink-usage-batch}.
+
+Then, in WinCVS, bring up the \q{Preferences} dialogue box from the
+\e{Admin} menu, and switch to the \q{Ports} tab. Tick the box there
+labelled \q{Check for an alternate \cw{rsh} name} and in the text
+entry field to the right enter the full path to \c{plink.exe}.
+Select \q{OK} on the \q{Preferences} dialogue box.
+
+Next, select \q{Command Line} from the WinCVS \q{Admin} menu, and type
+a CVS command as in \k{plink-cvs}, for example:
+
+\c cvs -d :ext:user@hostname:/path/to/repository co module
+
+or (if you're using a saved session):
+
+\c cvs -d :ext:user@sessionname:/path/to/repository co module
+
+Select the folder you want to check out to with the \q{Change Folder}
+button, and click \q{OK} to check out your module. Once you've got
+modules checked out, WinCVS will happily invoke plink from the GUI for
+CVS operations.
+
+\# \H{plink-whatelse} Using Plink with... ?
--- /dev/null
+\define{versionidpscp} \versionid $Id$
+
+\#FIXME: Need examples
+
+\C{pscp} Using \i{PSCP} to transfer files securely
+
+\i{PSCP}, the PuTTY Secure Copy client, is a tool for \i{transferring files}
+securely between computers using an SSH connection.
+
+If you have an SSH-2 server, you might prefer PSFTP (see \k{psftp})
+for interactive use. PSFTP does not in general work with SSH-1
+servers, however.
+
+\H{pscp-starting} Starting PSCP
+
+PSCP is a command line application. This means that you cannot just
+double-click on its icon to run it and instead you have to bring up a
+\i{console window}. With Windows 95, 98, and ME, this is called an
+\q{MS-DOS Prompt} and with Windows NT, 2000, and XP, it is called a
+\q{Command Prompt}. It should be available from the Programs section
+of your \i{Start Menu}.
+
+To start PSCP it will need either to be on your \i{\c{PATH}} or in your
+current directory. To add the directory containing PSCP to your
+\c{PATH} environment variable, type into the console window:
+
+\c set PATH=C:\path\to\putty\directory;%PATH%
+
+This will only work for the lifetime of that particular console
+window. To set your \c{PATH} more permanently on Windows NT, 2000,
+and XP, use the Environment tab of the System Control Panel. On
+Windows 95, 98, and ME, you will need to edit your \i\c{AUTOEXEC.BAT}
+to include a \c{set} command like the one above.
+
+\H{pscp-usage} PSCP Usage
+
+Once you've got a console window to type into, you can just type
+\c{pscp} on its own to bring up a usage message. This tells you the
+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.61
+\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
+\c -pgpfp print PGP key fingerprints and exit
+\c -p preserve file attributes
+\c -q quiet, don't show statistics
+\c -r copy directories recursively
+\c -v show verbose messages
+\c -load sessname Load settings from saved session
+\c -P port connect to specified port
+\c -l user connect with specified username
+\c -pw passw login with specified password
+\c -1 -2 force use of particular SSH protocol version
+\c -4 -6 force use of IPv4 or IPv6
+\c -C enable compression
+\c -i key private key file for authentication
+\c -noagent disable use of Pageant
+\c -agent enable use of Pageant
+\c -batch disable all interactive prompts
+\c -unsafe allow server-side wildcards (DANGEROUS)
+\c -sftp force use of SFTP protocol
+\c -scp force use of SCP protocol
+
+(PSCP's interface is much like the Unix \c{scp} command, if you're
+familiar with that.)
+
+\S{pscp-usage-basics} The basics
+
+To \I{receiving files}receive (a) file(s) from a remote server:
+
+\c pscp [options] [user@]host:source target
+
+So to copy the file \c{/etc/hosts} from the server \c{example.com} as
+user \c{fred} to the file \c{c:\\temp\\example-hosts.txt}, you would type:
+
+\c pscp fred@example.com:/etc/hosts c:\temp\example-hosts.txt
+
+To \I{sending files}send (a) file(s) to a remote server:
+
+\c pscp [options] source [source...] [user@]host:target
+
+So to copy the local file \c{c:\\documents\\foo.txt} to the server
+\c{example.com} as user \c{fred} to the file \c{/tmp/foo} you would
+type:
+
+\c pscp c:\documents\foo.txt fred@example.com:/tmp/foo
+
+You can use \i{wildcards} to transfer multiple files in either
+direction, like this:
+
+\c pscp c:\documents\*.doc fred@example.com:docfiles
+\c pscp fred@example.com:source/*.c c:\source
+
+However, in the second case (using a wildcard for multiple remote
+files) you may see a warning saying something like \q{warning:
+remote host tried to write to a file called \cq{terminal.c} when we
+requested a file called \cq{*.c}. If this is a wildcard, consider
+upgrading to SSH-2 or using the \cq{-unsafe} option. Renaming of
+this file has been disallowed}.
+
+This is due to a \I{security risk}fundamental insecurity in the old-style
+\i{SCP protocol}: the client sends the wildcard string (\c{*.c}) to the
+server, and the server sends back a sequence of file names that
+match the wildcard pattern. However, there is nothing to stop the
+server sending back a \e{different} pattern and writing over one of
+your other files: if you request \c{*.c}, the server might send back
+the file name \c{AUTOEXEC.BAT} and install a virus for you. Since
+the wildcard matching rules are decided by the server, the client
+cannot reliably verify that the filenames sent back match the
+pattern.
+
+PSCP will attempt to use the newer \i{SFTP} protocol (part of SSH-2)
+where possible, which does not suffer from this security flaw. If
+you are talking to an SSH-2 server which supports SFTP, you will
+never see this warning. (You can force use of the SFTP protocol,
+if available, with \c{-sftp} - see \k{pscp-usage-options-backend}.)
+
+If you really need to use a server-side wildcard with an SSH-1
+server, you can use the \i\c{-unsafe} command line option with PSCP:
+
+\c pscp -unsafe fred@example.com:source/*.c c:\source
+
+This will suppress the warning message and the file transfer will
+happen. However, you should be aware that by using this option you
+are giving the server the ability to write to \e{any} file in the
+target directory, so you should only use this option if you trust
+the server administrator not to be malicious (and not to let the
+server machine be cracked by malicious people). Alternatively, do
+any such download in a newly created empty directory. (Even in
+\q{unsafe} mode, PSCP will still protect you against the server
+trying to get out of that directory using pathnames including
+\cq{..}.)
+
+\S2{pscp-usage-basics-user} \c{user}
+
+The \i{login name} on the remote server. If this is omitted, and \c{host}
+is a PuTTY saved session, PSCP will use any username specified by that
+saved session. Otherwise, PSCP will attempt to use the local Windows
+username.
+
+\S2{pscp-usage-basics-host} \I{hostname}\c{host}
+
+The name of the remote server, or the name of an existing PuTTY saved
+session. In the latter case, the session's settings for hostname, port
+number, cipher type and username will be used.
+
+\S2{pscp-usage-basics-source} \c{source}
+
+One or more source files. \ii{Wildcards} are allowed. The syntax of
+wildcards depends on the system to which they apply, so if you are
+copying \e{from} a Windows system \e{to} a UNIX system, you should use
+Windows wildcard syntax (e.g. \c{*.*}), but if you are copying \e{from}
+a UNIX system \e{to} a Windows system, you would use the wildcard
+syntax allowed by your UNIX shell (e.g. \c{*}).
+
+If the source is a remote server and you do not specify a full
+pathname (in UNIX, a pathname beginning with a \c{/} (slash)
+character), what you specify as a source will be interpreted relative
+to your \i{home directory} on the remote server.
+
+\S2{pscp-usage-basics-target} \c{target}
+
+The filename or directory to put the file(s). When copying from a
+remote server to a local host, you may wish simply to place the
+file(s) in the current directory. To do this, you should specify a
+target of \c{.}. For example:
+
+\c pscp fred@example.com:/home/tom/.emacs .
+
+...would copy \c{/home/tom/.emacs} on the remote server to the current
+directory.
+
+As with the \c{source} parameter, if the target is on a remote server
+and is not a full path name, it is interpreted relative to your home
+directory on the remote server.
+
+\S{pscp-usage-options} Options
+
+PSCP accepts all the general command line options supported by the
+PuTTY tools, except the ones which make no sense in a file transfer
+utility. See \k{using-general-opts} for a description of these
+options. (The ones not supported by PSCP are clearly marked.)
+
+PSCP also supports some of its own options. The following sections
+describe PSCP's specific command-line options.
+
+\S2{pscp-usage-options-ls}\I{-ls-PSCP}\c{-ls} \I{listing files}list remote files
+
+If the \c{-ls} option is given, no files are transferred; instead,
+remote files are listed. Only a hostname specification and
+optional remote file specification need be given. For example:
+
+\c pscp -ls fred@example.com:dir1
+
+The SCP protocol does not contain within itself a means of listing
+files. If SCP is in use, this option therefore assumes that the
+server responds appropriately to the command \c{ls\_-la};
+this may not work with all servers.
+
+If SFTP is in use, this option should work with all servers.
+
+\S2{pscp-usage-options-p}\I{-p-PSCP}\c{-p} \i{preserve file attributes}
+
+By default, files copied with PSCP are \i{timestamp}ed with the date and
+time they were copied. The \c{-p} option preserves the original
+timestamp on copied files.
+
+\S2{pscp-usage-options-q}\I{-q-PSCP}\c{-q} quiet, don't show \i{statistics}
+
+By default, PSCP displays a meter displaying the progress of the
+current transfer:
+
+\c mibs.tar | 168 kB | 84.0 kB/s | ETA: 00:00:13 | 13%
+
+The fields in this display are (from left to right), filename, size
+(in kilobytes) of file transferred so far, estimate of how fast the
+file is being transferred (in kilobytes per second), estimated time
+that the transfer will be complete, and percentage of the file so far
+transferred. The \c{-q} option to PSCP suppresses the printing of
+these statistics.
+
+\S2{pscp-usage-options-r}\I{-r-PSCP}\c{-r} copies directories \i{recursive}ly
+
+By default, PSCP will only copy files. Any directories you specify to
+copy will be skipped, as will their contents. The \c{-r} option tells
+PSCP to descend into any directories you specify, and to copy them and
+their contents. This allows you to use PSCP to transfer whole
+directory structures between machines.
+
+\S2{pscp-usage-options-batch}\I{-batch-PSCP}\c{-batch} avoid interactive prompts
+
+If you use the \c{-batch} option, PSCP will never give an
+interactive prompt while establishing the connection. If the
+server's host key is invalid, for example (see \k{gs-hostkey}), then
+the connection will simply be abandoned instead of asking you what
+to do next.
+
+This may help PSCP's behaviour when it is used in automated
+scripts: using \c{-batch}, if something goes wrong at connection
+time, the batch job will fail rather than hang.
+
+\S2{pscp-usage-options-backend}\i\c{-sftp}, \i\c{-scp} force use of
+particular protocol
+
+As mentioned in \k{pscp-usage-basics}, there are two different file
+transfer protocols in use with SSH. Despite its name, PSCP (like many
+other ostensible \cw{scp} clients) can use either of these protocols.
+
+The older \i{SCP protocol} does not have a written specification and
+leaves a lot of detail to the server platform. \ii{Wildcards} are expanded
+on the server. The simple design means that any wildcard specification
+supported by the server platform (such as brace expansion) can be
+used, but also leads to interoperability issues such as with filename
+quoting (for instance, where filenames contain spaces), and also the
+security issue described in \k{pscp-usage-basics}.
+
+The newer \i{SFTP} protocol, which is usually associated with SSH-2
+servers, is specified in a more platform independent way, and leaves
+issues such as wildcard syntax up to the client. (PuTTY's SFTP
+wildcard syntax is described in \k{psftp-wildcards}.) This makes it
+more consistent across platforms, more suitable for scripting and
+automation, and avoids security issues with wildcard matching.
+
+Normally PSCP will attempt to use the SFTP protocol, and only fall
+back to the SCP protocol if SFTP is not available on the server.
+
+The \c{-scp} option forces PSCP to use the SCP protocol or quit.
+
+The \c{-sftp} option forces PSCP to use the SFTP protocol or quit.
+When this option is specified, PSCP looks harder for an SFTP server,
+which may allow use of SFTP with SSH-1 depending on server setup.
+
+\S{pscp-retval} \ii{Return value}
+
+PSCP returns an \i\cw{ERRORLEVEL} of zero (success) only if the files
+were correctly transferred. You can test for this in a \i{batch file},
+using code such as this:
+
+\c pscp file*.* user@hostname:
+\c if errorlevel 1 echo There was an error
+
+\S{pscp-pubkey} Using \i{public key authentication} with PSCP
+
+Like PuTTY, PSCP can authenticate using a public key instead of a
+password. There are three ways you can do this.
+
+Firstly, PSCP can use PuTTY saved sessions in place of hostnames
+(see \k{pscp-usage-basics-host}). So you would do this:
+
+\b Run PuTTY, and create a PuTTY saved session (see
+\k{config-saving}) which specifies your private key file (see
+\k{config-ssh-privkey}). You will probably also want to specify a
+username to log in as (see \k{config-username}).
+
+\b In PSCP, you can now use the name of the session instead of a
+hostname: type \c{pscp sessionname:file localfile}, where
+\c{sessionname} is replaced by the name of your saved session.
+
+Secondly, you can supply the name of a private key file on the command
+line, with the \c{-i} option. See \k{using-cmdline-identity} for more
+information.
+
+Thirdly, PSCP will attempt to authenticate using Pageant if Pageant
+is running (see \k{pageant}). So you would do this:
+
+\b Ensure Pageant is running, and has your private key stored in it.
+
+\b Specify a user and host name to PSCP as normal. PSCP will
+automatically detect Pageant and try to use the keys within it.
+
+For more general information on public-key authentication, see
+\k{pubkey}.
--- /dev/null
+\define{versionidpsftp} \versionid $Id$
+
+\C{psftp} Using \i{PSFTP} to transfer files securely
+
+\i{PSFTP}, the PuTTY SFTP client, is a tool for \i{transferring files}
+securely between computers using an SSH connection.
+
+PSFTP differs from PSCP in the following ways:
+
+\b PSCP should work on virtually every SSH server. PSFTP uses the
+new \i{SFTP} protocol, which is a feature of SSH-2 only. (PSCP will also
+use this protocol if it can, but there is an SSH-1 equivalent it can
+fall back to if it cannot.)
+
+\b PSFTP allows you to run an interactive file transfer session,
+much like the Windows \i\c{ftp} program. You can list the contents of
+directories, browse around the file system, issue multiple \c{get}
+and \c{put} commands, and eventually log out. By contrast, PSCP is
+designed to do a single file transfer operation and immediately
+terminate.
+
+\H{psftp-starting} Starting PSFTP
+
+The usual way to start PSFTP is from a command prompt, much like
+PSCP. To do this, it will need either to be on your \i{\c{PATH}} or
+in your current directory. To add the directory containing PSFTP to
+your \c{PATH} environment variable, type into the console window:
+
+\c set PATH=C:\path\to\putty\directory;%PATH%
+
+Unlike PSCP, however, PSFTP has no complex command-line syntax; you
+just specify a host name and perhaps a user name:
+
+\c psftp server.example.com
+
+or perhaps
+
+\c psftp fred@server.example.com
+
+Alternatively, if you just type \c{psftp} on its own (or
+double-click the PSFTP icon in the Windows GUI), you will see the
+PSFTP prompt, and a message telling you PSFTP has not connected to
+any server:
+
+\c C:\>psftp
+\c psftp: no hostname specified; use "open host.name" to connect
+\c psftp>
+
+At this point you can type \c{open server.example.com} or \c{open
+fred@server.example.com} to start a session.
+
+PSFTP accepts all the general command line options supported by the
+PuTTY tools, except the ones which make no sense in a file transfer
+utility. See \k{using-general-opts} for a description of these
+options. (The ones not supported by PSFTP are clearly marked.)
+
+PSFTP also supports some of its own options. The following sections
+describe PSFTP's specific command-line options.
+
+\S{psftp-option-b} \I{-b-PSFTP}\c{-b}: specify a file containing batch commands
+
+In normal operation, PSFTP is an interactive program which displays
+a command line and accepts commands from the keyboard.
+
+If you need to do automated tasks with PSFTP, you would probably
+prefer to \I{batch scripts in PSFTP}specify a set of commands in
+advance and have them executed automatically. The \c{-b} option
+allows you to do this. You use it with a file name containing batch
+commands. For example, you might create a file called \c{myscript.scr}
+containing lines like this:
+
+\c cd /home/ftp/users/jeff
+\c del jam-old.tar.gz
+\c ren jam.tar.gz jam-old.tar.gz
+\c put jam.tar.gz
+\c chmod a+r jam.tar.gz
+
+and then you could run the script by typing
+
+\c psftp user@hostname -b myscript.scr
+
+When you run a batch script in this way, PSFTP will abort the script
+if any command fails to complete successfully. To change this
+behaviour, you can add the \c{-be} option (\k{psftp-option-be}).
+
+PSFTP will terminate after it finishes executing the batch script.
+
+\S{psftp-option-bc} \I{-bc-PSFTP}\c{-bc}: display batch commands as they are run
+
+The \c{-bc} option alters what PSFTP displays while processing a
+batch script specified with \c{-b}. With the \c{-bc} option, PSFTP
+will display prompts and commands just as if the commands had been
+typed at the keyboard. So instead of seeing this:
+
+\c C:\>psftp fred@hostname -b batchfile
+\c Sent username "fred"
+\c Remote working directory is /home/fred
+\c Listing directory /home/fred/lib
+\c drwxrwsr-x 4 fred fred 1024 Sep 6 10:42 .
+\c drwxr-sr-x 25 fred fred 2048 Dec 14 09:36 ..
+\c drwxrwsr-x 3 fred fred 1024 Apr 17 2000 jed
+\c lrwxrwxrwx 1 fred fred 24 Apr 17 2000 timber
+\c drwxrwsr-x 2 fred fred 1024 Mar 13 2000 trn
+
+you might see this:
+
+\c C:\>psftp fred@hostname -bc -b batchfile
+\c Sent username "fred"
+\c Remote working directory is /home/fred
+\c psftp> dir lib
+\c Listing directory /home/fred/lib
+\c drwxrwsr-x 4 fred fred 1024 Sep 6 10:42 .
+\c drwxr-sr-x 25 fred fred 2048 Dec 14 09:36 ..
+\c drwxrwsr-x 3 fred fred 1024 Apr 17 2000 jed
+\c lrwxrwxrwx 1 fred fred 24 Apr 17 2000 timber
+\c drwxrwsr-x 2 fred fred 1024 Mar 13 2000 trn
+\c psftp> quit
+
+\S{psftp-option-be} \I{-be-PSFTP}\c{-be}: continue batch processing on errors
+
+When running a batch file, this additional option causes PSFTP to
+continue processing even if a command fails to complete successfully.
+
+You might want this to happen if you wanted to delete a file and
+didn't care if it was already not present, for example.
+
+\S{psftp-usage-options-batch} \I{-batch-PSFTP}\c{-batch}: avoid
+interactive prompts
+
+If you use the \c{-batch} option, PSFTP will never give an
+interactive prompt while establishing the connection. If the
+server's host key is invalid, for example (see \k{gs-hostkey}), then
+the connection will simply be abandoned instead of asking you what
+to do next.
+
+This may help PSFTP's behaviour when it is used in automated
+scripts: using \c{-batch}, if something goes wrong at connection
+time, the batch job will fail rather than hang.
+
+\H{psftp-commands} Running PSFTP
+
+Once you have started your PSFTP session, you will see a \c{psftp>}
+prompt. You can now type commands to perform file-transfer
+functions. This section lists all the available commands.
+
+Any line starting with a \cw{#} will be treated as a \i{comment}
+and ignored.
+
+\S{psftp-quoting} \I{quoting, in PSFTP}General quoting rules for PSFTP commands
+
+Most PSFTP commands are considered by the PSFTP command interpreter
+as a sequence of words, separated by spaces. For example, the
+command \c{ren oldfilename newfilename} splits up into three words:
+\c{ren} (the command name), \c{oldfilename} (the name of the file to
+be renamed), and \c{newfilename} (the new name to give the file).
+
+Sometimes you will need to specify \I{spaces in filenames}file names
+that \e{contain} spaces. In order to do this, you can surround
+the file name with double quotes. This works equally well for
+local file names and remote file names:
+
+\c psftp> get "spacey file name.txt" "save it under this name.txt"
+
+The double quotes themselves will not appear as part of the file
+names; they are removed by PSFTP and their only effect is to stop
+the spaces inside them from acting as word separators.
+
+If you need to \e{use} a double quote (on some types of remote
+system, such as Unix, you are allowed to use double quotes in file
+names), you can do this by doubling it. This works both inside and
+outside double quotes. For example, this command
+
+\c psftp> ren ""this"" "a file with ""quotes"" in it"
+
+will take a file whose current name is \c{"this"} (with a double
+quote character at the beginning and the end) and rename it to a
+file whose name is \c{a file with "quotes" in it}.
+
+(The one exception to the PSFTP quoting rules is the \c{!} command,
+which passes its command line straight to Windows without splitting
+it up into words at all. See \k{psftp-cmd-pling}.)
+
+\S{psftp-wildcards} Wildcards in PSFTP
+
+Several commands in PSFTP support \q{\i{wildcards}} to select multiple
+files.
+
+For \e{local} file specifications (such as the first argument to
+\c{put}), wildcard rules for the local operating system are used. For
+instance, PSFTP running on Windows might require the use of \c{*.*}
+where PSFTP on Unix would need \c{*}.
+
+For \e{remote} file specifications (such as the first argument to
+\c{get}), PSFTP uses a standard wildcard syntax (similar to \i{POSIX}
+wildcards):
+
+\b \c{*} matches any sequence of characters (including a zero-length
+sequence).
+
+\b \c{?} matches exactly one character.
+
+\b \c{[abc]} matches exactly one character which can be \cw{a},
+\cw{b}, or \cw{c}.
+
+\lcont{
+
+\c{[a-z]} matches any character in the range \cw{a} to \cw{z}.
+
+\c{[^abc]} matches a single character that is \e{not} \cw{a}, \cw{b},
+or \cw{c}.
+
+Special cases: \c{[-a]} matches a literal hyphen (\cw{-}) or \cw{a};
+\c{[^-a]} matches all other characters. \c{[a^]} matches a literal
+caret (\cw{^}) or \cw{a}.
+
+}
+
+\b \c{\\} (backslash) before any of the above characters (or itself)
+removes that character's special meaning.
+
+A leading period (\cw{.}) on a filename is not treated specially,
+unlike in some Unix contexts; \c{get *} will fetch all files, whether
+or not they start with a leading period.
+
+\S{psftp-cmd-open} The \c{open} command: start a session
+
+If you started PSFTP by double-clicking in the GUI, or just by
+typing \c{psftp} at the command line, you will need to open a
+connection to an SFTP server before you can issue any other
+commands (except \c{help} and \c{quit}).
+
+To create a connection, type \c{open host.name}, or if you need to
+specify a user name as well you can type \c{open user@host.name}.
+You can optionally specify a port as well:
+\c{open user@host.name 22}.
+
+Once you have issued this command, you will not be able to issue it
+again, \e{even} if the command fails (for example, if you mistype
+the host name or the connection times out). So if the connection is
+not opened successfully, PSFTP will terminate immediately.
+
+\S{psftp-cmd-quit} The \c{quit} command: end your session
+
+When you have finished your session, type the command \c{quit} to
+close the connection, terminate PSFTP and return to the command line
+(or just close the PSFTP console window if you started it from the
+GUI).
+
+You can also use the \c{bye} and \c{exit} commands, which have
+exactly the same effect.
+
+\S{psftp-cmd-close} The \c{close} command: close your connection
+
+If you just want to close the network connection but keep PSFTP
+running, you can use the \c{close} command. You can then use the
+\c{open} command to open a new connection.
+
+\S{psftp-cmd-help} The \c{help} command: get quick online help
+
+If you type \c{help}, PSFTP will give a short list of the available
+commands.
+
+If you type \c{help} with a command name - for example, \c{help get}
+- then PSFTP will give a short piece of help on that particular
+command.
+
+\S{psftp-cmd-cd} The \c{cd} and \c{pwd} commands: changing the
+remote \i{working directory}
+
+PSFTP maintains a notion of your \q{working directory} on the
+server. This is the default directory that other commands will
+operate on. For example, if you type \c{get filename.dat} then PSFTP
+will look for \c{filename.dat} in your remote working directory on
+the server.
+
+To change your remote working directory, use the \c{cd} command. If
+you don't provide an argument, \c{cd} will return you to your home
+directory on the server (more precisely, the remote directory you were
+in at the start of the connection).
+
+To display your current remote working directory, type \c{pwd}.
+
+\S{psftp-cmd-lcd} The \c{lcd} and \c{lpwd} commands: changing the
+local \i{working directory}
+
+As well as having a working directory on the remote server, PSFTP
+also has a working directory on your local machine (just like any
+other Windows process). This is the default local directory that
+other commands will operate on. For example, if you type \c{get
+filename.dat} then PSFTP will save the resulting file as
+\c{filename.dat} in your local working directory.
+
+To change your local working directory, use the \c{lcd} command. To
+display your current local working directory, type \c{lpwd}.
+
+\S{psftp-cmd-get} The \c{get} command: fetch a file from the server
+
+To \i{download a file} from the server and store it on your local PC,
+you use the \c{get} command.
+
+In its simplest form, you just use this with a file name:
+
+\c get myfile.dat
+
+If you want to store the file locally under a different name,
+specify the local file name after the remote one:
+
+\c get myfile.dat newname.dat
+
+This will fetch the file on the server called \c{myfile.dat}, but
+will save it to your local machine under the name \c{newname.dat}.
+
+To fetch an entire directory \i{recursive}ly, you can use the \c{-r}
+option:
+
+\c get -r mydir
+\c get -r mydir newname
+
+(If you want to fetch a file whose name starts with a hyphen, you
+may have to use the \c{--} special argument, which stops \c{get}
+from interpreting anything as a switch after it. For example,
+\cq{get -- -silly-name-}.)
+
+\S{psftp-cmd-put} The \c{put} command: send a file to the server
+
+To \i{upload a file} to the server from your local PC, you use the
+\c{put} command.
+
+In its simplest form, you just use this with a file name:
+
+\c put myfile.dat
+
+If you want to store the file remotely under a different name,
+specify the remote file name after the local one:
+
+\c put myfile.dat newname.dat
+
+This will send the local file called \c{myfile.dat}, but will store
+it on the server under the name \c{newname.dat}.
+
+To send an entire directory \i{recursive}ly, you can use the \c{-r}
+option:
+
+\c put -r mydir
+\c put -r mydir newname
+
+(If you want to send a file whose name starts with a hyphen, you may
+have to use the \c{--} special argument, which stops \c{put} from
+interpreting anything as a switch after it. For example, \cq{put --
+-silly-name-}.)
+
+\S{psftp-cmd-mgetput} The \c{mget} and \c{mput} commands: fetch or
+send multiple files
+
+\c{mget} works almost exactly like \c{get}, except that it allows
+you to specify more than one file to fetch at once. You can do this
+in two ways:
+
+\b by giving two or more explicit file names (\cq{mget file1.txt
+file2.txt})
+
+\b by using a wildcard (\cq{mget *.txt}).
+
+Every argument to \c{mget} is treated as the name of a file to fetch
+(unlike \c{get}, which will interpret at most one argument like
+that, and a second argument will be treated as an alternative name
+under which to store the retrieved file), or a \i{wildcard} expression
+matching more than one file.
+
+The \c{-r} and \c{--} options from \c{get} are also available with
+\c{mget}.
+
+\c{mput} is similar to \c{put}, with the same differences.
+
+\S{psftp-cmd-regetput} The \c{reget} and \c{reput} commands:
+\i{resuming file transfers}
+
+If a file transfer fails half way through, and you end up with half
+the file stored on your disk, you can resume the file transfer using
+the \c{reget} and \c{reput} commands. These work exactly like the
+\c{get} and \c{put} commands, but they check for the presence of the
+half-written destination file and start transferring from where the
+last attempt left off.
+
+The syntax of \c{reget} and \c{reput} is exactly the same as the
+syntax of \c{get} and \c{put}:
+
+\c reget myfile.dat
+\c reget myfile.dat newname.dat
+\c reget -r mydir
+
+These commands are intended mainly for resuming interrupted transfers.
+They assume that the remote file or directory structure has not
+changed in any way; if there have been changes, you may end up with
+corrupted files. In particular, the \c{-r} option will not pick up
+changes to files or directories already transferred in full.
+
+\S{psftp-cmd-dir} The \c{dir} command: \I{listing files}list remote files
+
+To list the files in your remote working directory, just type
+\c{dir}.
+
+You can also list the contents of a different directory by typing
+\c{dir} followed by the directory name:
+
+\c dir /home/fred
+\c dir sources
+
+And you can list a subset of the contents of a directory by
+providing a wildcard:
+
+\c dir /home/fred/*.txt
+\c dir sources/*.c
+
+The \c{ls} command works exactly the same way as \c{dir}.
+
+\S{psftp-cmd-chmod} The \c{chmod} command: change permissions on
+remote files
+
+\I{changing permissions on files}PSFTP
+allows you to modify the file permissions on files and
+directories on the server. You do this using the \c{chmod} command,
+which works very much like the Unix \c{chmod} command.
+
+The basic syntax is \c{chmod modes file}, where \c{modes} represents
+a modification to the file permissions, and \c{file} is the filename
+to modify. You can specify multiple files or wildcards. For example:
+
+\c chmod go-rwx,u+w privatefile
+\c chmod a+r public*
+\c chmod 640 groupfile1 groupfile2
+
+The \c{modes} parameter can be a set of octal digits in the Unix
+style. (If you don't know what this means, you probably don't want
+to be using it!) Alternatively, it can be a list of permission
+modifications, separated by commas. Each modification consists of:
+
+\b The people affected by the modification. This can be \c{u} (the
+owning user), \c{g} (members of the owning group), or \c{o}
+(everybody else - \q{others}), or some combination of those. It can
+also be \c{a} (\q{all}) to affect everybody at once.
+
+\b A \c{+} or \c{-} sign, indicating whether permissions are to be
+added or removed.
+
+\b The actual permissions being added or removed. These can be
+\I{read permission}\c{r} (permission to read the file),
+\I{write permission}\c{w} (permission to write to the file), and
+\I{execute permission}\c{x} (permission to execute the file, or in
+the case of a directory, permission to access files within the
+directory).
+
+So the above examples would do:
+
+\b The first example: \c{go-rwx} removes read, write and execute
+permissions for members of the owning group and everybody else (so
+the only permissions left are the ones for the file owner). \c{u+w}
+adds write permission for the file owner.
+
+\b The second example: \c{a+r} adds read permission for everybody to
+all files and directories starting with \q{public}.
+
+In addition to all this, there are a few extra special cases for
+\i{Unix} systems. On non-Unix systems these are unlikely to be useful:
+
+\b You can specify \c{u+s} and \c{u-s} to add or remove the Unix
+\i{set-user-ID bit}. This is typically only useful for special purposes;
+refer to your Unix documentation if you're not sure about it.
+
+\b You can specify \c{g+s} and \c{g-s} to add or remove the Unix
+\i{set-group-ID bit}. On a file, this works similarly to the set-user-ID
+bit (see your Unix documentation again); on a directory it ensures
+that files created in the directory are accessible by members of the
+group that owns the directory.
+
+\b You can specify \c{+t} and \c{-t} to add or remove the Unix
+\q{\i{sticky bit}}. When applied to a directory, this means that the
+owner of a file in that directory can delete the file (whereas
+normally only the owner of the \e{directory} would be allowed to).
+
+\S{psftp-cmd-del} The \c{del} command: delete remote files
+
+To \I{deleting files}delete a file on the server, type \c{del} and
+then the filename or filenames:
+
+\c del oldfile.dat
+\c del file1.txt file2.txt
+\c del *.o
+
+Files will be deleted without further prompting, even if multiple files
+are specified.
+
+\c{del} will only delete files. You cannot use it to delete
+directories; use \c{rmdir} for that.
+
+The \c{rm} command works exactly the same way as \c{del}.
+
+\S{psftp-cmd-mkdir} The \c{mkdir} command: create remote directories
+
+To \i{create a directory} on the server, type \c{mkdir} and then the
+directory name:
+
+\c mkdir newstuff
+
+You can specify multiple directories to create at once:
+
+\c mkdir dir1 dir2 dir3
+
+\S{psftp-cmd-rmdir} The \c{rmdir} command: remove remote directories
+
+To \i{remove a directory} on the server, type \c{rmdir} and then the
+directory name or names:
+
+\c rmdir oldstuff
+\c rmdir *.old ancient
+
+Directories will be deleted without further prompting, even if
+multiple directories are specified.
+
+Most SFTP servers will probably refuse to remove a directory if the
+directory has anything in it, so you will need to delete the
+contents first.
+
+\S{psftp-cmd-mv} The \c{mv} command: move and \i{rename remote files}
+
+To rename a single file on the server, type \c{mv}, then the current
+file name, and then the new file name:
+
+\c mv oldfile newname
+
+You can also move the file into a different directory and change the
+name:
+
+\c mv oldfile dir/newname
+
+To move one or more files into an existing subdirectory, specify the
+files (using wildcards if desired), and then the destination
+directory:
+
+\c mv file dir
+\c mv file1 dir1/file2 dir2
+\c mv *.c *.h ..
+
+The \c{rename} and \c{ren} commands work exactly the same way as
+\c{mv}.
+
+\S{psftp-cmd-pling} The \c{!} command: run a \i{local Windows command}
+
+You can run local Windows commands using the \c{!} command. This is
+the only PSFTP command that is not subject to the command quoting
+rules given in \k{psftp-quoting}. If any command line begins with
+the \c{!} character, then the rest of the line will be passed
+straight to Windows without further translation.
+
+For example, if you want to move an existing copy of a file out of
+the way before downloading an updated version, you might type:
+
+\c psftp> !ren myfile.dat myfile.bak
+\c psftp> get myfile.dat
+
+using the Windows \c{ren} command to rename files on your local PC.
+
+\H{psftp-pubkey} Using \i{public key authentication} with PSFTP
+
+Like PuTTY, PSFTP can authenticate using a public key instead of a
+password. There are three ways you can do this.
+
+Firstly, PSFTP can use PuTTY saved sessions in place of hostnames.
+So you might do this:
+
+\b Run PuTTY, and create a PuTTY saved session (see
+\k{config-saving}) which specifies your private key file (see
+\k{config-ssh-privkey}). You will probably also want to specify a
+username to log in as (see \k{config-username}).
+
+\b In PSFTP, you can now use the name of the session instead of a
+hostname: type \c{psftp sessionname}, where \c{sessionname} is
+replaced by the name of your saved session.
+
+Secondly, you can supply the name of a private key file on the command
+line, with the \c{-i} option. See \k{using-cmdline-identity} for more
+information.
+
+Thirdly, PSFTP will attempt to authenticate using Pageant if Pageant
+is running (see \k{pageant}). So you would do this:
+
+\b Ensure Pageant is running, and has your private key stored in it.
+
+\b Specify a user and host name to PSFTP as normal. PSFTP will
+automatically detect Pageant and try to use the keys within it.
+
+For more general information on public-key authentication, see
+\k{pubkey}.
--- /dev/null
+\define{versionidpubkey} \versionid $Id$
+
+\C{pubkey} Using public keys for SSH authentication
+
+\H{pubkey-intro} \ii{Public key authentication} - an introduction
+
+Public key authentication is an alternative means of identifying
+yourself to a login server, instead of typing a password. It is more
+secure and more flexible, but more difficult to set up.
+
+In conventional password authentication, you prove you are who you
+claim to be by proving that you know the correct password. The only
+way to prove you know the password is to tell the server what you
+think the password is. This means that if the server has been
+hacked, or \i\e{spoofed} (see \k{gs-hostkey}), an attacker can learn
+your password.
+
+Public key authentication solves this problem. You generate a \i\e{key
+pair}, consisting of a \i{public key} (which everybody is allowed to
+know) and a \i{private key} (which you keep secret and do not give to
+anybody). The private key is able to generate \i\e{signatures}.
+A signature created using your private key cannot be forged by
+anybody who does not have that key; but anybody who has your public
+key can verify that a particular signature is genuine.
+
+So you generate a key pair on your own computer, and you copy the
+public key to the server. Then, when the server asks you to prove
+who you are, PuTTY can generate a signature using your private key.
+The server can verify that signature (since it has your public key)
+and allow you to log in. Now if the server is hacked or spoofed, the
+attacker does not gain your private key or password; they only gain
+one signature. And signatures cannot be re-used, so they have gained
+nothing.
+
+There is a problem with this: if your private key is stored
+unprotected on your own computer, then anybody who gains access to
+\e{that} will be able to generate signatures as if they were you. So
+they will be able to log in to your server under your account. For
+this reason, your private key is usually \i\e{encrypted} when it is
+stored on your local machine, using a \i{passphrase} of your choice. In
+order to generate a signature, PuTTY must decrypt the key, so you
+have to type your passphrase.
+
+This can make public-key authentication less convenient than
+password authentication: every time you log in to the server,
+instead of typing a short password, you have to type a longer
+passphrase. One solution to this is to use an \i\e{authentication
+agent}, a separate program which holds decrypted private keys and
+generates signatures on request. PuTTY's authentication agent is
+called \i{Pageant}. When you begin a Windows session, you start Pageant
+and load your private key into it (typing your passphrase once). For
+the rest of your session, you can start PuTTY any number of times
+and Pageant will automatically generate signatures without you
+having to do anything. When you close your Windows session, Pageant
+shuts down, without ever having stored your decrypted private key on
+disk. Many people feel this is a good compromise between security
+and convenience. See \k{pageant} for further details.
+
+There is more than one \i{public-key algorithm} available. The most
+common is \i{RSA}, but others exist, notably \i{DSA} (otherwise known as
+DSS), the USA's federal Digital Signature Standard. The key types
+supported by PuTTY are described in \k{puttygen-keytype}.
+
+\H{pubkey-puttygen} Using \i{PuTTYgen}, the PuTTY key generator
+
+\cfg{winhelp-topic}{puttygen.general}
+
+PuTTYgen is a key generator. It \I{generating keys}generates pairs of
+public and private keys to be used with PuTTY, PSCP, and Plink, as well
+as the PuTTY authentication agent, Pageant (see \k{pageant}). PuTTYgen
+generates RSA and DSA keys.
+
+When you run PuTTYgen you will see a window where you have two
+choices: \q{Generate}, to generate a new public/private key pair, or
+\q{Load} to load in an existing private key.
+
+\S{puttygen-generating} Generating a new key
+
+This is a general outline of the procedure for generating a new key
+pair. The following sections describe the process in more detail.
+
+\b First, you need to select which type of key you want to generate,
+and also select the strength of the key. This is described in more
+detail in \k{puttygen-keytype} and
+\k{puttygen-strength}.
+
+\b Then press the \q{Generate} button, to actually generate the key.
+\K{puttygen-generate} describes this step.
+
+\b Once you have generated the key, select a comment field
+(\k{puttygen-comment}) and a passphrase (\k{puttygen-passphrase}).
+
+\b Now you're ready to save the private key to disk; press the
+\q{Save private key} button. (See \k{puttygen-savepriv}).
+
+Your key pair is now ready for use. You may also want to copy the
+public key to your server, either by copying it out of the \q{Public
+key for pasting into authorized_keys file} box (see
+\k{puttygen-pastekey}), or by using the \q{Save public key} button
+(\k{puttygen-savepub}). However, you don't need to do this
+immediately; if you want, you can load the private key back into
+PuTTYgen later (see \k{puttygen-load}) and the public key will be
+available for copying and pasting again.
+
+\K{pubkey-gettingready} describes the typical process of configuring
+PuTTY to attempt public-key authentication, and configuring your SSH
+server to accept it.
+
+\S{puttygen-keytype} Selecting the type of key
+
+\cfg{winhelp-topic}{puttygen.keytype}
+
+Before generating a key pair using PuTTYgen, you need to select
+which type of key you need. PuTTYgen currently supports three types
+of key:
+
+\b An \i{RSA} key for use with the SSH-1 protocol.
+
+\b An RSA key for use with the SSH-2 protocol.
+
+\b A \i{DSA} key for use with the SSH-2 protocol.
+
+The SSH-1 protocol only supports RSA keys; if you will be connecting
+using the SSH-1 protocol, you must select the first key type or your
+key will be completely useless.
+
+The SSH-2 protocol supports more than one key type. The two types
+supported by PuTTY are RSA and DSA.
+
+The PuTTY developers \e{strongly} recommend you use RSA.
+\I{security risk}\i{DSA} has an intrinsic weakness which makes it very
+easy to create a signature which contains enough information to give
+away the \e{private} key!
+This would allow an attacker to pretend to be you for any number of
+future sessions. PuTTY's implementation has taken very careful
+precautions to avoid this weakness, but we cannot be 100% certain we
+have managed it, and if you have the choice we strongly recommend
+using RSA keys instead.
+
+If you really need to connect to an SSH server which only supports
+DSA, then you probably have no choice but to use DSA. If you do use
+DSA, we recommend you do not use the same key to authenticate with
+more than one server.
+
+\S{puttygen-strength} Selecting the size (strength) of the key
+
+\cfg{winhelp-topic}{puttygen.bits}
+
+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
+the key, press the \q{Generate} button and PuTTYgen will begin the
+process of actually generating the key.
+
+First, a progress bar will appear and PuTTYgen will ask you to move
+the mouse around to generate randomness. Wave the mouse in circles
+over the blank area in the PuTTYgen window, and the progress bar
+will gradually fill up as PuTTYgen collects enough randomness. You
+don't need to wave the mouse in particularly imaginative patterns
+(although it can't hurt); PuTTYgen will collect enough randomness
+just from the fine detail of \e{exactly} how far the mouse has moved
+each time Windows samples its position.
+
+When the progress bar reaches the end, PuTTYgen will begin creating
+the key. The progress bar will reset to the start, and gradually
+move up again to track the progress of the key generation. It will
+not move evenly, and may occasionally slow down to a stop; this is
+unfortunately unavoidable, because key generation is a random
+process and it is impossible to reliably predict how long it will
+take.
+
+When the key generation is complete, a new set of controls will
+appear in the window to indicate this.
+
+\S{puttygen-fingerprint} The \q{\ii{Key fingerprint}} box
+
+\cfg{winhelp-topic}{puttygen.fingerprint}
+
+The \q{Key fingerprint} box shows you a fingerprint value for the
+generated key. This is derived cryptographically from the \e{public}
+key value, so it doesn't need to be kept secret.
+
+The fingerprint value is intended to be cryptographically secure, in
+the sense that it is computationally infeasible for someone to
+invent a second key with the same fingerprint, or to find a key with
+a particular fingerprint. So some utilities, such as the Pageant key
+list box (see \k{pageant-mainwin-keylist}) and the Unix \c{ssh-add}
+utility, will list key fingerprints rather than the whole public key.
+
+\S{puttygen-comment} Setting a comment for your key
+
+\cfg{winhelp-topic}{puttygen.comment}
+
+If you have more than one key and use them for different purposes,
+you don't need to memorise the key fingerprints in order to tell
+them apart. PuTTYgen allows you to enter a \e{comment} for your key,
+which will be displayed whenever PuTTY or Pageant asks you for the
+passphrase.
+
+The default comment format, if you don't specify one, contains the
+key type and the date of generation, such as \c{rsa-key-20011212}.
+Another commonly used approach is to use your name and the name of
+the computer the key will be used on, such as \c{simon@simons-pc}.
+
+To alter the key comment, just type your comment text into the
+\q{Key comment} box before saving the private key. If you want to
+change the comment later, you can load the private key back into
+PuTTYgen, change the comment, and save it again.
+
+\S{puttygen-passphrase} Setting a \i{passphrase} for your key
+
+\cfg{winhelp-topic}{puttygen.passphrase}
+
+The \q{Key passphrase} and \q{Confirm passphrase} boxes allow you to
+choose a passphrase for your key. The passphrase will be used to
+\i{encrypt} the key on disk, so you will not be able to use the key
+without first entering the passphrase.
+
+When you save the key, PuTTYgen will check that the \q{Key passphrase}
+and \q{Confirm passphrase} boxes both contain exactly the same
+passphrase, and will refuse to save the key otherwise.
+
+If you leave the passphrase fields blank, the key will be saved
+unencrypted. You should \e{not} do this without good reason; if you
+do, your private key file on disk will be all an attacker needs to
+gain access to any machine configured to accept that key. If you
+want to be able to \I{passwordless login}log in without having to
+type a passphrase every time, you should consider using Pageant
+(\k{pageant}) so that your decrypted key is only held in memory
+rather than on disk.
+
+Under special circumstances you may genuinely \e{need} to use a key
+with no passphrase; for example, if you need to run an automated
+batch script that needs to make an SSH connection, you can't be
+there to type the passphrase. In this case we recommend you generate
+a special key for each specific batch script (or whatever) that
+needs one, and on the server side you should arrange that each key
+is \e{restricted} so that it can only be used for that specific
+purpose. The documentation for your SSH server should explain how to
+do this (it will probably vary between servers).
+
+Choosing a good passphrase is difficult. Just as you shouldn't use a
+dictionary word as a password because it's easy for an attacker to
+run through a whole dictionary, you should not use a song lyric,
+quotation or other well-known sentence as a passphrase. \i{DiceWare}
+(\W{http://www.diceware.com/}\cw{www.diceware.com}) recommends using
+at least five words each generated randomly by rolling five dice,
+which gives over 2^64 possible passphrases and is probably not a bad
+scheme. If you want your passphrase to make grammatical sense, this
+cuts down the possibilities a lot and you should use a longer one as
+a result.
+
+\e{Do not forget your passphrase}. There is no way to recover it.
+
+\S{puttygen-savepriv} Saving your private key to a disk file
+
+\cfg{winhelp-topic}{puttygen.savepriv}
+
+Once you have generated a key, set a comment field and set a
+passphrase, you are ready to save your private key to disk.
+
+Press the \q{Save private key} button. PuTTYgen will put up a dialog
+box asking you where to save the file. Select a directory, type in a
+file name, and press \q{Save}.
+
+This file is in PuTTY's native format (\c{*.\i{PPK}}); it is the one you
+will need to tell PuTTY to use for authentication (see
+\k{config-ssh-privkey}) or tell Pageant to load (see
+\k{pageant-mainwin-addkey}).
+
+\S{puttygen-savepub} Saving your public key to a disk file
+
+\cfg{winhelp-topic}{puttygen.savepub}
+
+RFC 4716 specifies a \I{SSH-2 public key format}standard format for
+storing SSH-2 public keys on disk. Some SSH servers (such as
+\i\cw{ssh.com}'s) require a public key in this format in order to accept
+authentication with the corresponding private key. (Others, such as
+OpenSSH, use a different format; see \k{puttygen-pastekey}.)
+
+To save your public key in the SSH-2 standard format, press the
+\q{Save public key} button in PuTTYgen. PuTTYgen will put up a
+dialog box asking you where to save the file. Select a directory,
+type in a file name, and press \q{Save}.
+
+You will then probably want to copy the public key file to your SSH
+server machine. See \k{pubkey-gettingready} for general instructions
+on configuring public-key authentication once you have generated a
+key.
+
+If you use this option with an SSH-1 key, the file PuTTYgen saves
+will contain exactly the same text that appears in the \q{Public key
+for pasting} box. This is the only existing standard for SSH-1
+public keys.
+
+\S{puttygen-pastekey} \q{Public key for pasting into \i{authorized_keys
+file}}
+
+\cfg{winhelp-topic}{puttygen.pastekey}
+
+All SSH-1 servers require your public key to be given to it in a
+one-line format before it will accept authentication with your
+private key. The \i{OpenSSH} server also requires this for SSH-2.
+
+The \q{Public key for pasting into authorized_keys file} gives the
+public-key data in the correct one-line format. Typically you will
+want to select the entire contents of the box using the mouse, press
+Ctrl+C to copy it to the clipboard, and then paste the data into a
+PuTTY session which is already connected to the server.
+
+See \k{pubkey-gettingready} for general instructions on configuring
+public-key authentication once you have generated a key.
+
+\S{puttygen-load} Reloading a private key
+
+\cfg{winhelp-topic}{puttygen.load}
+
+PuTTYgen allows you to load an existing private key file into
+memory. If you do this, you can then change the passphrase and
+comment before saving it again; you can also make extra copies of
+the public key.
+
+To load an existing key, press the \q{Load} button. PuTTYgen will
+put up a dialog box where you can browse around the file system and
+find your key file. Once you select the file, PuTTYgen will ask you
+for a passphrase (if necessary) and will then display the key
+details in the same way as if it had just generated the key.
+
+If you use the Load command to load a foreign key format, it will
+work, but you will see a message box warning you that the key you
+have loaded is not a PuTTY native key. See \k{puttygen-conversions}
+for information about importing foreign key formats.
+
+\S{puttygen-conversions} Dealing with private keys in other formats
+
+\cfg{winhelp-topic}{puttygen.conversions}
+
+Most SSH-1 clients use a standard format for storing private keys on
+disk. PuTTY uses this format as well; so if you have generated an
+SSH-1 private key using OpenSSH or \cw{ssh.com}'s client, you can use
+it with PuTTY, and vice versa.
+
+However, SSH-2 private keys have no standard format. \I{OpenSSH private
+key format}OpenSSH and \I{ssh.com private key format}\cw{ssh.com} have
+different formats, and PuTTY's is different again.
+So a key generated with one client cannot immediately be used with
+another.
+
+Using the \I{importing keys}\q{Import} command from the \q{Conversions}
+menu, PuTTYgen can load SSH-2 private keys in OpenSSH's format and
+\cw{ssh.com}'s format. Once you have loaded one of these key types, you
+can then save it back out as a PuTTY-format key (\c{*.\i{PPK}}) so that
+you can use it with the PuTTY suite. The passphrase will be unchanged by this
+process (unless you deliberately change it). You may want to change
+the key comment before you save the key, since OpenSSH's SSH-2 key
+format contains no space for a comment and \cw{ssh.com}'s default
+comment format is long and verbose.
+
+PuTTYgen can also \i{export private keys} in OpenSSH format and in
+\cw{ssh.com} format. To do so, select one of the \q{Export} options
+from the \q{Conversions} menu. Exporting a key works exactly like
+saving it (see \k{puttygen-savepriv}) - you need to have typed your
+passphrase in beforehand, and you will be warned if you are about to
+save a key without a passphrase.
+
+Note that since only SSH-2 keys come in different formats, the export
+options are not available if you have generated an SSH-1 key.
+
+\H{pubkey-gettingready} Getting ready for public key authentication
+
+Connect to your SSH server using PuTTY with the SSH protocol. When the
+connection succeeds you will be prompted for your user name and
+password to login. Once logged in, you must configure the server to
+accept your public key for authentication:
+
+\b If your server is using the SSH-1 protocol, you should change
+into the \i\c{.ssh} directory and open the file \i\c{authorized_keys}
+with your favourite editor. (You may have to create this file if
+this is the first key you have put in it). Then switch to the
+PuTTYgen window, select all of the text in the \q{Public key for
+pasting into authorized_keys file} box (see \k{puttygen-pastekey}),
+and copy it to the clipboard (\c{Ctrl+C}). Then, switch back to the
+PuTTY window and insert the data into the open file, making sure it
+ends up all on one line. Save the file.
+
+\b If your server is \i{OpenSSH} and is using the SSH-2 protocol, you
+should follow the same instructions, except that in earlier versions
+of OpenSSH 2 the file might be called \c{authorized_keys2}. (In
+modern versions the same \c{authorized_keys} file is used for both
+SSH-1 and SSH-2 keys.)
+
+\b If your server is \i\cw{ssh.com}'s product and is using SSH-2, you
+need to save a \e{public} key file from PuTTYgen (see
+\k{puttygen-savepub}), and copy that into the \i\c{.ssh2} directory on
+the server. Then you should go into that \c{.ssh2} directory, and edit
+(or create) a file called \c{authorization}. In this file you should
+put a line like \c{Key mykey.pub}, with \c{mykey.pub} replaced by the
+name of your key file.
+
+\b For other SSH server software, you should refer to the manual for
+that server.
+
+You may also need to ensure that your home directory, your \c{.ssh}
+directory, and any other files involved (such as
+\c{authorized_keys}, \c{authorized_keys2} or \c{authorization}) are
+not group-writable or world-writable. You can typically do this by
+using a command such as
+
+\c chmod go-w $HOME $HOME/.ssh $HOME/.ssh/authorized_keys
+
+Your server should now be configured to accept authentication using
+your private key. Now you need to configure PuTTY to \e{attempt}
+authentication using your private key. You can do this in any of
+three ways:
+
+\b Select the private key in PuTTY's configuration. See
+\k{config-ssh-privkey} for details.
+
+\b Specify the key file on the command line with the \c{-i} option.
+See \k{using-cmdline-identity} for details.
+
+\b Load the private key into Pageant (see \k{pageant}). In this case
+PuTTY will automatically try to use it for authentication if it can.
--- /dev/null
+\# Additional configuration for the version of the PuTTY docs
+\# actually published as HTML on the website.
+
+\cfg{xhtml-head-end}{<link rel='stylesheet' href='sitestyle.css' type='text/css' />}
--- /dev/null
+\define{versionidsshnames} \versionid $Id$
+
+\A{sshnames} SSH-2 names specified for PuTTY
+
+There are various parts of the SSH-2 protocol where things are specified
+using a textual name. Names ending in \cw{@putty.projects.tartarus.org}
+are reserved for allocation by the PuTTY team. Allocated names are
+documented here.
+
+\H{sshnames-channel} Connection protocol channel request names
+
+These names can be sent in a \cw{SSH_MSG_CHANNEL_REQUEST} message.
+
+\dt \cw{simple@putty.projects.tartarus.org}
+
+\dd This is sent by a client to announce that it will not have more than
+one channel open at a time in the current connection (that one being
+the one the request is sent on). The intention is that the server,
+knowing this, can set the window on that one channel to something very
+large, and leave flow control to TCP. There is no message-specific data.
+
+\dt \cw{winadj@putty.projects.tartarus.org}
+
+\dd PuTTY sends this request along with some
+\cw{SSH_MSG_CHANNEL_WINDOW_ADJUST} messages as part of its window-size
+tuning. It can be sent on any type of channel. There is no
+message-specific data. Servers MUST treat it as an unrecognised request
+and respond with \cw{SSH_MSG_CHANNEL_FAILURE}.
+
+\H{sshnames-kex} Key exchange method names
+
+\dt \cw{rsa-sha1-draft-00@putty.projects.tartarus.org}
+
+\dt \cw{rsa-sha256-draft-00@putty.projects.tartarus.org}
+
+\dt \cw{rsa1024-sha1-draft-01@putty.projects.tartarus.org}
+
+\dt \cw{rsa1024-sha256-draft-01@putty.projects.tartarus.org}
+
+\dt \cw{rsa2048-sha256-draft-01@putty.projects.tartarus.org}
+
+\dt \cw{rsa1024-sha1-draft-02@putty.projects.tartarus.org}
+
+\dt \cw{rsa2048-sha512-draft-02@putty.projects.tartarus.org}
+
+\dt \cw{rsa1024-sha1-draft-03@putty.projects.tartarus.org}
+
+\dt \cw{rsa2048-sha256-draft-03@putty.projects.tartarus.org}
+
+\dt \cw{rsa1024-sha1-draft-04@putty.projects.tartarus.org}
+
+\dt \cw{rsa2048-sha256-draft-04@putty.projects.tartarus.org}
+
+\dd These appeared in various drafts of what eventually became RFC\_4432.
+They have been superseded by \cw{rsa1024-sha1} and \cw{rsa2048-sha256}.
+
+\H{sshnames-encrypt} Encryption algorithm names
+
+\dt \cw{arcfour128-draft-00@putty.projects.tartarus.org}
+
+\dt \cw{arcfour256-draft-00@putty.projects.tartarus.org}
+
+\dd These were used in drafts of what eventually became RFC\_4345.
+They have been superseded by \cw{arcfour128} and \cw{arcfour256}.
--- /dev/null
+\# This file is so named for tradition's sake: it contains what we
+\# always used to refer to, before they were written down, as
+\# PuTTY's `unwritten design principles'. It has nothing to do with
+\# the User Datagram Protocol.
+
+\define{versionidudp} \versionid $Id$
+
+\A{udp} PuTTY hacking guide
+
+This appendix lists a selection of the design principles applying to
+the PuTTY source code. If you are planning to send code
+contributions, you should read this first.
+
+\H{udp-portability} Cross-OS portability
+
+Despite Windows being its main area of fame, PuTTY is no longer a
+Windows-only application suite. It has a working Unix port; a Mac
+port is in progress; more ports may or may not happen at a later
+date.
+
+Therefore, embedding Windows-specific code in core modules such as
+\cw{ssh.c} is not acceptable. We went to great lengths to \e{remove}
+all the Windows-specific stuff from our core modules, and to shift
+it out into Windows-specific modules. Adding large amounts of
+Windows-specific stuff in parts of the code that should be portable
+is almost guaranteed to make us reject a contribution.
+
+The PuTTY source base is divided into platform-specific modules and
+platform-generic modules. The Unix-specific modules are all in the
+\c{unix} subdirectory; the Mac-specific modules are in the \c{mac}
+subdirectory; the Windows-specific modules are in the \c{windows}
+subdirectory.
+
+All the modules in the main source directory - notably \e{all} of
+the code for the various back ends - are platform-generic. We want
+to keep them that way.
+
+This also means you should stick to what you are guaranteed by
+ANSI/ISO C (that is, the original C89/C90 standard, not C99). Try
+not to make assumptions about the precise size of basic types such
+as \c{int} and \c{long int}; don't use pointer casts to do
+endianness-dependent operations, and so on.
+
+(There are one or two aspects of ANSI C portability which we
+\e{don't} care about. In particular, we expect PuTTY to be compiled
+on 32-bit architectures \e{or bigger}; so it's safe to assume that
+\c{int} is at least 32 bits wide, not just the 16 you are guaranteed
+by ANSI C. Similarly, we assume that the execution character
+encoding is a superset of the printable characters of ASCII, though
+we don't assume the numeric values of control characters,
+particularly \cw{'\\n'} and \cw{'\\r'}.)
+
+\H{udp-multi-backend} Multiple backends treated equally
+
+PuTTY is not an SSH client with some other stuff tacked on the side.
+PuTTY is a generic, multiple-backend, remote VT-terminal client
+which happens to support one backend which is larger, more popular
+and more useful than the rest. Any extra feature which can possibly
+be general across all backends should be so: localising features
+unnecessarily into the SSH back end is a design error. (For example,
+we had several code submissions for proxy support which worked by
+hacking \cw{ssh.c}. Clearly this is completely wrong: the
+\cw{network.h} abstraction is the place to put it, so that it will
+apply to all back ends equally, and indeed we eventually put it
+there after another contributor sent a better patch.)
+
+The rest of PuTTY should try to avoid knowing anything about
+specific back ends if at all possible. To support a feature which is
+only available in one network protocol, for example, the back end
+interface should be extended in a general manner such that \e{any}
+back end which is able to provide that feature can do so. If it so
+happens that only one back end actually does, that's just the way it
+is, but it shouldn't be relied upon by any code.
+
+\H{udp-globals} Multiple sessions per process on some platforms
+
+Some ports of PuTTY - notably the in-progress Mac port - are
+constrained by the operating system to run as a single process
+potentially managing multiple sessions.
+
+Therefore, the platform-independent parts of PuTTY never use global
+variables to store per-session data. The global variables that do
+exist are tolerated because they are not specific to a particular
+login session: \c{flags} defines properties that are expected to
+apply equally to \e{all} the sessions run by a single PuTTY process,
+the random number state in \cw{sshrand.c} and the timer list in
+\cw{timing.c} serve all sessions equally, and so on. But most data
+is specific to a particular network session, and is therefore stored
+in dynamically allocated data structures, and pointers to these
+structures are passed around between functions.
+
+Platform-specific code can reverse this decision if it likes. The
+Windows code, for historical reasons, stores most of its data as
+global variables. That's OK, because \e{on Windows} we know there is
+only one session per PuTTY process, so it's safe to do that. But
+changes to the platform-independent code should avoid introducing
+global variables, unless they are genuinely cross-session.
+
+\H{udp-pure-c} C, not C++
+
+PuTTY is written entirely in C, not in C++.
+
+We have made \e{some} effort to make it easy to compile our code
+using a C++ compiler: notably, our \c{snew}, \c{snewn} and
+\c{sresize} macros explicitly cast the return values of \cw{malloc}
+and \cw{realloc} to the target type. (This has type checking
+advantages even in C: it means you never accidentally allocate the
+wrong size piece of memory for the pointer type you're assigning it
+to. C++ friendliness is really a side benefit.)
+
+We want PuTTY to continue being pure C, at least in the
+platform-independent parts and the currently existing ports. Patches
+which switch the Makefiles to compile it as C++ and start using
+classes will not be accepted. Also, in particular, we disapprove of
+\cw{//} comments, at least for the moment. (Perhaps once C99 becomes
+genuinely widespread we might be more lenient.)
+
+The one exception: a port to a new platform may use languages other
+than C if they are necessary to code on that platform. If your
+favourite PDA has a GUI with a C++ API, then there's no way you can
+do a port of PuTTY without using C++, so go ahead and use it. But
+keep the C++ restricted to that platform's subdirectory; if your
+changes force the Unix or Windows ports to be compiled as C++, they
+will be unacceptable to us.
+
+\H{udp-security} Security-conscious coding
+
+PuTTY is a network application and a security application. Assume
+your code will end up being fed deliberately malicious data by
+attackers, and try to code in a way that makes it unlikely to be a
+security risk.
+
+In particular, try not to use fixed-size buffers for variable-size
+data such as strings received from the network (or even the user).
+We provide functions such as \cw{dupcat} and \cw{dupprintf}, which
+dynamically allocate buffers of the right size for the string they
+construct. Use these wherever possible.
+
+\H{udp-multi-compiler} Independence of specific compiler
+
+Windows PuTTY can currently be compiled with any of four Windows
+compilers: MS Visual C, Borland's freely downloadable C compiler,
+the Cygwin / \cw{mingw32} GNU tools, and \cw{lcc-win32}.
+
+This is a really useful property of PuTTY, because it means people
+who want to contribute to the coding don't depend on having a
+specific compiler; so they don't have to fork out money for MSVC if
+they don't already have it, but on the other hand if they \e{do}
+have it they also don't have to spend effort installing \cw{gcc}
+alongside it. They can use whichever compiler they happen to have
+available, or install whichever is cheapest and easiest if they
+don't have one.
+
+Therefore, we don't want PuTTY to start depending on which compiler
+you're using. Using GNU extensions to the C language, for example,
+would ruin this useful property (not that anyone's ever tried it!);
+and more realistically, depending on an MS-specific library function
+supplied by the MSVC C library (\cw{_snprintf}, for example) is a
+mistake, because that function won't be available under the other
+compilers. Any function supplied in an official Windows DLL as part
+of the Windows API is fine, and anything defined in the C library
+standard is also fine, because those should be available
+irrespective of compilation environment. But things in between,
+available as non-standard library and language extensions in only
+one compiler, are disallowed.
+
+(\cw{_snprintf} in particular should be unnecessary, since we
+provide \cw{dupprintf}; see \k{udp-security}.)
+
+Compiler independence should apply on all platforms, of course, not
+just on Windows.
+
+\H{udp-small} Small code size
+
+PuTTY is tiny, compared to many other Windows applications. And it's
+easy to install: it depends on no DLLs, no other applications, no
+service packs or system upgrades. It's just one executable. You
+install that executable wherever you want to, and run it.
+
+We want to keep both these properties - the small size, and the ease
+of installation - if at all possible. So code contributions that
+depend critically on external DLLs, or that add a huge amount to the
+code size for a feature which is only useful to a small minority of
+users, are likely to be thrown out immediately.
+
+We do vaguely intend to introduce a DLL plugin interface for PuTTY,
+whereby seriously large extra features can be implemented in plugin
+modules. The important thing, though, is that those DLLs will be
+\e{optional}; if PuTTY can't find them on startup, it should run
+perfectly happily and just won't provide those particular features.
+A full installation of PuTTY might one day contain ten or twenty
+little DLL plugins, which would cut down a little on the ease of
+installation - but if you really needed ease of installation you
+\e{could} still just install the one PuTTY binary, or just the DLLs
+you really needed, and it would still work fine.
+
+Depending on \e{external} DLLs is something we'd like to avoid if at
+all possible (though for some purposes, such as complex SSH
+authentication mechanisms, it may be unavoidable). If it can't be
+avoided, the important thing is to follow the same principle of
+graceful degradation: if a DLL can't be found, then PuTTY should run
+happily and just not supply the feature that depended on it.
+
+\H{udp-single-threaded} Single-threaded code
+
+PuTTY and its supporting tools, or at least the vast majority of
+them, run in only one OS thread.
+
+This means that if you're devising some piece of internal mechanism,
+there's no need to use locks to make sure it doesn't get called by
+two threads at once. The only way code can be called re-entrantly is
+by recursion.
+
+That said, most of Windows PuTTY's network handling is triggered off
+Windows messages requested by \cw{WSAAsyncSelect()}, so if you call
+\cw{MessageBox()} deep within some network event handling code you
+should be aware that you might be re-entered if a network event
+comes in and is passed on to our window procedure by the
+\cw{MessageBox()} message loop.
+
+Also, the front ends (in particular Windows Plink) can use multiple
+threads if they like. However, Windows Plink keeps \e{very} tight
+control of its auxiliary threads, and uses them pretty much
+exclusively as a form of \cw{select()}. Pretty much all the code
+outside \cw{windows/winplink.c} is \e{only} ever called from the one
+primary thread; the others just loop round blocking on file handles
+and send messages to the main thread when some real work needs
+doing. This is not considered a portability hazard because that bit
+of \cw{windows/winplink.c} will need rewriting on other platforms in
+any case.
+
+One important consequence of this: PuTTY has only one thread in
+which to do everything. That \q{everything} may include managing
+more than one login session (\k{udp-globals}), managing multiple
+data channels within an SSH session, responding to GUI events even
+when nothing is happening on the network, and responding to network
+requests from the server (such as repeat key exchange) even when the
+program is dealing with complex user interaction such as the
+re-configuration dialog box. This means that \e{almost none} of the
+PuTTY code can safely block.
+
+\H{udp-keystrokes} Keystrokes sent to the server wherever possible
+
+In almost all cases, PuTTY sends keystrokes to the server. Even
+weird keystrokes that you think should be hot keys controlling
+PuTTY. Even Alt-F4 or Alt-Space, for example. If a keystroke has a
+well-defined escape sequence that it could usefully be sending to
+the server, then it should do so, or at the very least it should be
+configurably able to do so.
+
+To unconditionally turn a key combination into a hot key to control
+PuTTY is almost always a design error. If a hot key is really truly
+required, then try to find a key combination for it which \e{isn't}
+already used in existing PuTTYs (either it sends nothing to the
+server, or it sends the same thing as some other combination). Even
+then, be prepared for the possibility that one day that key
+combination might end up being needed to send something to the
+server - so make sure that there's an alternative way to invoke
+whatever PuTTY feature it controls.
+
+\H{udp-640x480} 640\u00D7{x}480 friendliness in configuration panels
+
+There's a reason we have lots of tiny configuration panels instead
+of a few huge ones, and that reason is that not everyone has a
+1600\u00D7{x}1200 desktop. 640\u00D7{x}480 is still a viable
+resolution for running Windows (and indeed it's still the default if
+you start up in safe mode), so it's still a resolution we care
+about.
+
+Accordingly, the PuTTY configuration box, and the PuTTYgen control
+window, are deliberately kept just small enough to fit comfortably
+on a 640\u00D7{x}480 display. If you're adding controls to either of
+these boxes and you find yourself wanting to increase the size of
+the whole box, \e{don't}. Split it into more panels instead.
+
+\H{udp-makefiles-auto} Automatically generated \cw{Makefile}s
+
+PuTTY is intended to compile on multiple platforms, and with
+multiple compilers. It would be horrifying to try to maintain a
+single \cw{Makefile} which handled all possible situations, and just
+as painful to try to directly maintain a set of matching
+\cw{Makefile}s for each different compilation environment.
+
+Therefore, we have moved the problem up by one level. In the PuTTY
+source archive is a file called \c{Recipe}, which lists which source
+files combine to produce which binaries; and there is also a script
+called \cw{mkfiles.pl}, which reads \c{Recipe} and writes out the
+real \cw{Makefile}s. (The script also reads all the source files and
+analyses their dependencies on header files, so we get an extra
+benefit from doing it this way, which is that we can supply correct
+dependency information even in environments where it's difficult to
+set up an automated \c{make depend} phase.)
+
+You should \e{never} edit any of the PuTTY \cw{Makefile}s directly.
+They are not stored in our source repository at all. They are
+automatically generated by \cw{mkfiles.pl} from the file \c{Recipe}.
+
+If you need to add a new object file to a particular binary, the
+right thing to do is to edit \c{Recipe} and re-run \cw{mkfiles.pl}.
+This will cause the new object file to be added in every tool that
+requires it, on every platform where it matters, in every
+\cw{Makefile} to which it is relevant, \e{and} to get all the
+dependency data right.
+
+If you send us a patch that modifies one of the \cw{Makefile}s, you
+just waste our time, because we will have to convert it into a
+change to \c{Recipe}. If you send us a patch that modifies \e{all}
+of the \cw{Makefile}s, you will have wasted a lot of \e{your} time
+as well!
+
+(There is a comment at the top of every \cw{Makefile} in the PuTTY
+source archive saying this, but many people don't seem to read it,
+so it's worth repeating here.)
+
+\H{udp-ssh-coroutines} Coroutines in \cw{ssh.c}
+
+Large parts of the code in \cw{ssh.c} are structured using a set of
+macros that implement (something close to) Donald Knuth's
+\q{coroutines} concept in C.
+
+Essentially, the purpose of these macros are to arrange that a
+function can call \cw{crReturn()} to return to its caller, and the
+next time it is called control will resume from just after that
+\cw{crReturn} statement.
+
+This means that any local (automatic) variables declared in such a
+function will be corrupted every time you call \cw{crReturn}. If you
+need a variable to persist for longer than that, you \e{must} make
+it a field in one of the persistent state structures: either the
+local state structures \c{s} or \c{st} in each function, or the
+backend-wide structure \c{ssh}.
+
+See
+\W{http://www.chiark.greenend.org.uk/~sgtatham/coroutines.html}\c{http://www.chiark.greenend.org.uk/~sgtatham/coroutines.html}
+for a more in-depth discussion of what these macros are for and how
+they work.
+
+\H{udp-compile-once} Single compilation of each source file
+
+The PuTTY build system for any given platform works on the following
+very simple model:
+
+\b Each source file is compiled precisely once, to produce a single
+object file.
+
+\b Each binary is created by linking together some combination of
+those object files.
+
+Therefore, if you need to introduce functionality to a particular
+module which is only available in some of the tool binaries (for
+example, a cryptographic proxy authentication mechanism which needs
+to be left out of PuTTYtel to maintain its usability in
+crypto-hostile jurisdictions), the \e{wrong} way to do it is by
+adding \cw{#ifdef}s in (say) \cw{proxy.c}. This would require
+separate compilation of \cw{proxy.c} for PuTTY and PuTTYtel, which
+means that the entire \cw{Makefile}-generation architecture (see
+\k{udp-makefiles-auto}) would have to be significantly redesigned.
+Unless you are prepared to do that redesign yourself, \e{and}
+guarantee that it will still port to any future platforms we might
+decide to run on, you should not attempt this!
+
+The \e{right} way to introduce a feature like this is to put the new
+code in a separate source file, and (if necessary) introduce a
+second new source file defining the same set of functions, but
+defining them as stubs which don't provide the feature. Then the
+module whose behaviour needs to vary (\cw{proxy.c} in this example)
+can call the functions defined in these two modules, and it will
+either provide the new feature or not provide it according to which
+of your new modules it is linked with.
+
+Of course, object files are never shared \e{between} platforms; so
+it is allowable to use \cw{#ifdef} to select between platforms. This
+happens in \cw{puttyps.h} (choosing which of the platform-specific
+include files to use), and also in \cw{misc.c} (the Windows-specific
+\q{Minefield} memory diagnostic system). It should be used
+sparingly, though, if at all.
+
+\H{udp-perfection} Do as we say, not as we do
+
+The current PuTTY code probably does not conform strictly to \e{all}
+of the principles listed above. There may be the occasional
+SSH-specific piece of code in what should be a backend-independent
+module, or the occasional dependence on a non-standard X library
+function under Unix.
+
+This should not be taken as a licence to go ahead and violate the
+rules. Where we violate them ourselves, we're not happy about it,
+and we would welcome patches that fix any existing problems. Please
+try to help us make our code better, not worse!
--- /dev/null
+\define{versionidusing} \versionid $Id$
+
+\C{using} Using PuTTY
+
+This chapter provides a general introduction to some more advanced
+features of PuTTY. For extreme detail and reference purposes,
+\k{config} is likely to contain more information.
+
+\H{using-session} During your session
+
+A lot of PuTTY's complexity and features are in the configuration
+panel. Once you have worked your way through that and started
+a session, things should be reasonably simple after that.
+Nevertheless, there are a few more useful features available.
+
+\S{using-selection} Copying and pasting text
+
+\I{copy and paste}Often in a PuTTY session you will find text on
+your terminal screen which you want to type in again. Like most
+other terminal emulators, PuTTY allows you to copy and paste the
+text rather than having to type it again. Also, copy and paste uses
+the \I{Windows clipboard}Windows \i{clipboard}, so that you can
+paste (for example) URLs into a web browser, or paste from a word
+processor or spreadsheet into your terminal session.
+
+PuTTY's copy and paste works entirely with the \i{mouse}. In order
+to copy text to the clipboard, you just click the \i{left mouse
+button} in the \i{terminal window}, and drag to \I{selecting text}select
+text. When you let go of the button, the text is \e{automatically}
+copied to the clipboard. You do not need to press Ctrl-C or
+Ctrl-Ins; in fact, if you do press Ctrl-C, PuTTY will send a Ctrl-C
+character down your session to the server where it will probably
+cause a process to be interrupted.
+
+Pasting is done using the right button (or the middle mouse button,
+if you have a \i{three-button mouse} and have set it up; see
+\k{config-mouse}). (Pressing \i{Shift-Ins}, or selecting \q{Paste}
+from the \I{right mouse button, with Ctrl}Ctrl+right-click
+\i{context menu}, have the same effect.) When
+you click the \i{right mouse button}, PuTTY will read whatever is in
+the Windows clipboard and paste it into your session, \e{exactly} as
+if it had been typed at the keyboard. (Therefore, be careful of
+pasting formatted text into an editor that does automatic indenting;
+you may find that the spaces pasted from the clipboard plus the
+spaces added by the editor add up to too many spaces and ruin the
+formatting. There is nothing PuTTY can do about this.)
+
+If you \i{double-click} the left mouse button, PuTTY will
+\I{selecting words}select a whole word. If you double-click, hold
+down the second click, and drag the mouse, PuTTY will select a
+sequence of whole words. (You can adjust precisely what PuTTY
+considers to be part of a word; see \k{config-charclasses}.)
+If you \e{triple}-click, or \i{triple-click} and drag, then
+PuTTY will \I{selecting lines}select a whole line or sequence of lines.
+
+If you want to select a \I{rectangular selection}rectangular region
+instead of selecting to the end of each line, you can do this by
+holding down Alt when you make your selection. You can also
+configure rectangular selection to be the default, and then holding
+down Alt gives the normal behaviour instead: see
+\k{config-rectselect} for details.
+
+(In some Unix environments, Alt+drag is intercepted by the window
+manager. Shift+Alt+drag should work for rectangular selection as
+well, so you could try that instead.)
+
+If you have a \i{middle mouse button}, then you can use it to
+\I{adjusting a selection}adjust an existing selection if you
+selected something slightly wrong. (If you have configured the
+middle mouse button to paste, then the right mouse button does this
+instead.) Click the button on the screen, and you can pick up the
+nearest end of the selection and drag it to somewhere else.
+
+It's possible for the server to ask to \I{mouse reporting}handle mouse
+clicks in the PuTTY window itself. If this happens, the \i{mouse pointer}
+will turn into an arrow, and using the mouse to copy and paste will only
+work if you hold down Shift. See \k{config-features-mouse} and
+\k{config-mouseshift} for details of this feature and how to configure
+it.
+
+\S{using-scrollback} \I{scrollback}Scrolling the screen back
+
+PuTTY keeps track of text that has scrolled up off the top of the
+terminal. So if something appears on the screen that you want to
+read, but it scrolls too fast and it's gone by the time you try to
+look for it, you can use the \i{scrollbar} on the right side of the
+window to look back up the session \i{history} and find it again.
+
+As well as using the scrollbar, you can also page the scrollback up
+and down by pressing \i{Shift-PgUp} and \i{Shift-PgDn}. You can
+scroll a line at a time using \i{Ctrl-PgUp} and \i{Ctrl-PgDn}. These
+are still available if you configure the scrollbar to be invisible.
+
+By default the last 200 lines scrolled off the top are
+preserved for you to look at. You can increase (or decrease) this
+value using the configuration box; see \k{config-scrollback}.
+
+\S{using-sysmenu} The \ii{System menu}
+
+If you click the left mouse button on the icon in the top left
+corner of PuTTY's terminal window, or click the right mouse button
+on the title bar, you will see the standard Windows system menu
+containing items like Minimise, Move, Size and Close.
+
+PuTTY's system menu contains extra program features in addition to
+the Windows standard options. These extra menu commands are
+described below.
+
+(These options are also available in a \i{context menu} brought up
+by holding Ctrl and clicking with the right mouse button anywhere
+in the \i{PuTTY window}.)
+
+\S2{using-eventlog} The PuTTY \i{Event Log}
+
+If you choose \q{Event Log} from the system menu, a small window
+will pop up in which PuTTY logs significant events during the
+connection. Most of the events in the log will probably take place
+during session startup, but a few can occur at any point in the
+session, and one or two occur right at the end.
+
+You can use the mouse to select one or more lines of the Event Log,
+and hit the Copy button to copy them to the \i{clipboard}. If you
+are reporting a bug, it's often useful to paste the contents of the
+Event Log into your bug report.
+
+\S2{using-specials} \ii{Special commands}
+
+Depending on the protocol used for the current session, there may be
+a submenu of \q{special commands}. These are protocol-specific
+tokens, such as a \q{break} signal, that can be sent down a
+connection in addition to normal data. Their precise effect is usually
+up to the server. Currently only Telnet, SSH, and serial connections
+have special commands.
+
+The \q{break} signal can also be invoked from the keyboard with
+\i{Ctrl-Break}.
+
+The following \I{Telnet special commands}special commands are
+available in Telnet:
+
+\b \I{Are You There, Telnet special command}Are You There
+
+\b \I{Break, Telnet special command}Break
+
+\b \I{Synch, Telnet special command}Synch
+
+\b \I{Erase Character, Telnet special command}Erase Character
+
+\lcont{
+PuTTY can also be configured to send this when the Backspace key is
+pressed; see \k{config-telnetkey}.
+}
+
+\b \I{Erase Line, Telnet special command}Erase Line
+
+\b \I{Go Ahead, Telnet special command}Go Ahead
+
+\b \I{No Operation, Telnet special command}No Operation
+
+\lcont{
+Should have no effect.
+}
+
+\b \I{Abort Process, Telnet special command}Abort Process
+
+\b \I{Abort Output, Telnet special command}Abort Output
+
+\b \I{Interrupt Process, Telnet special command}Interrupt Process
+
+\lcont{
+PuTTY can also be configured to send this when Ctrl-C is typed; see
+\k{config-telnetkey}.
+}
+
+\b \I{Suspend Process, Telnet special command}Suspend Process
+
+\lcont{
+PuTTY can also be configured to send this when Ctrl-Z is typed; see
+\k{config-telnetkey}.
+}
+
+\b \I{End Of Record, Telnet special command}End Of Record
+
+\b \I{End Of File, Telnet special command}End Of File
+
+In an SSH connection, the following \I{SSH special commands}special
+commands are available:
+
+\b \I{IGNORE message, SSH special command}\I{No-op, in SSH}\ii{IGNORE message}
+
+\lcont{
+Should have no effect.
+}
+
+\b \I{Repeat key exchange, SSH special command}Repeat key exchange
+
+\lcont{
+Only available in SSH-2. Forces a \i{repeat key exchange} immediately (and
+resets associated timers and counters). For more information about
+repeat key exchanges, see \k{config-ssh-kex-rekey}.
+}
+
+\b \I{Break, SSH special command}Break
+
+\lcont{
+Only available in SSH-2, and only during a session. Optional
+extension; may not be supported by server. PuTTY requests the server's
+default break length.
+}
+
+\b \I{Signal, SSH special command}Signals (SIGINT, SIGTERM etc)
+
+\lcont{
+Only available in SSH-2, and only during a session. Sends various
+POSIX signals. Not honoured by all servers.
+}
+
+With a serial connection, the only available special command is
+\I{Break, serial special command}\q{Break}.
+
+\S2{using-newsession} Starting new sessions
+
+PuTTY's system menu provides some shortcut ways to start new
+sessions:
+
+\b Selecting \i{\q{New Session}} will start a completely new
+instance of PuTTY, and bring up the configuration box as normal.
+
+\b Selecting \i{\q{Duplicate Session}} will start a session in a
+new window with precisely the same options as your current one -
+connecting to the same host using the same protocol, with all the
+same terminal settings and everything.
+
+\b In an inactive window, selecting \i{\q{Restart Session}} will
+do the same as \q{Duplicate Session}, but in the current window.
+
+\b The \i{\q{Saved Sessions} submenu} gives you quick access to any
+sets of stored session details you have previously saved. See
+\k{config-saving} for details of how to create saved sessions.
+
+\S2{using-changesettings} \I{settings, changing}Changing your
+session settings
+
+If you select \i{\q{Change Settings}} from the system menu, PuTTY will
+display a cut-down version of its initial configuration box. This
+allows you to adjust most properties of your current session. You
+can change the terminal size, the font, the actions of various
+keypresses, the colours, and so on.
+
+Some of the options that are available in the main configuration box
+are not shown in the cut-down Change Settings box. These are usually
+options which don't make sense to change in the middle of a session
+(for example, you can't switch from SSH to Telnet in mid-session).
+
+You can save the current settings to a saved session for future use
+from this dialog box. See \k{config-saving} for more on saved
+sessions.
+
+\S2{using-copyall} \i{Copy All to Clipboard}
+
+This system menu option provides a convenient way to copy the whole
+contents of the terminal screen (up to the last nonempty line) and
+scrollback to the \i{clipboard} in one go.
+
+\S2{reset-terminal} \I{scrollback, clearing}Clearing and
+\I{terminal, resetting}resetting the terminal
+
+The \i{\q{Clear Scrollback}} option on the system menu tells PuTTY
+to discard all the lines of text that have been kept after they
+scrolled off the top of the screen. This might be useful, for
+example, if you displayed sensitive information and wanted to make
+sure nobody could look over your shoulder and see it. (Note that
+this only prevents a casual user from using the scrollbar to view
+the information; the text is not guaranteed not to still be in
+PuTTY's memory.)
+
+The \i{\q{Reset Terminal}} option causes a full reset of the
+\i{terminal emulation}. A VT-series terminal is a complex piece of
+software and can easily get into a state where all the text printed
+becomes unreadable. (This can happen, for example, if you
+accidentally output a binary file to your terminal.) If this
+happens, selecting Reset Terminal should sort it out.
+
+\S2{using-fullscreen} \ii{Full screen} mode
+
+If you find the title bar on a maximised window to be ugly or
+distracting, you can select Full Screen mode to maximise PuTTY
+\q{even more}. When you select this, PuTTY will expand to fill the
+whole screen and its borders, title bar and scrollbar will
+disappear. (You can configure the scrollbar not to disappear in
+full-screen mode if you want to keep it; see \k{config-scrollback}.)
+
+When you are in full-screen mode, you can still access the \i{system
+menu} if you click the left mouse button in the \e{extreme} top left
+corner of the screen.
+
+\H{using-logging} Creating a \i{log file} of your \I{session
+log}session
+
+For some purposes you may find you want to log everything that
+appears on your screen. You can do this using the \q{Logging}
+panel in the configuration box.
+
+To begin a session log, select \q{Change Settings} from the system
+menu and go to the Logging panel. Enter a log file name, and select
+a logging mode. (You can log all session output including the
+terminal \i{control sequence}s, or you can just log the printable text.
+It depends what you want the log for.) Click \q{Apply} and your log
+will be started. Later on, you can go back to the Logging panel and
+select \q{Logging turned off completely} to stop logging; then PuTTY
+will close the log file and you can safely read it.
+
+See \k{config-logging} for more details and options.
+
+\H{using-translation} Altering your \i{character set} configuration
+
+If you find that special characters (\i{accented characters}, for
+example, or \i{line-drawing characters}) are not being displayed
+correctly in your PuTTY session, it may be that PuTTY is interpreting
+the characters sent by the server according to the wrong \e{character
+set}. There are a lot of different character sets available, so it's
+entirely possible for this to happen.
+
+If you click \q{Change Settings} and look at the \q{Translation}
+panel, you should see a large number of character sets which you can
+select, and other related options. Now all you need is to find out
+which of them you want! (See \k{config-translation} for more
+information.)
+
+\H{using-x-forwarding} Using \i{X11 forwarding} in SSH
+
+The SSH protocol has the ability to securely forward X Window System
+applications over your encrypted SSH connection, so that you can run
+an application on the SSH server machine and have it put its windows
+up on your local machine without sending any X network traffic in
+the clear.
+
+In order to use this feature, you will need an X display server for
+your Windows machine, such as Cygwin/X, X-Win32, or Exceed. This will probably
+install itself as display number 0 on your local machine; if it
+doesn't, the manual for the \i{X server} should tell you what it
+does do.
+
+You should then tick the \q{Enable X11 forwarding} box in the
+X11 panel (see \k{config-ssh-x11}) before starting your SSH
+session. The \i{\q{X display location}} box is blank by default, which
+means that PuTTY will try to use a sensible default such as \c{:0},
+which is the usual display location where your X server will be
+installed. If that needs changing, then change it.
+
+Now you should be able to log in to the SSH server as normal. To
+check that X forwarding has been successfully negotiated during
+connection startup, you can check the PuTTY Event Log (see
+\k{using-eventlog}). It should say something like this:
+
+\c 2001-12-05 17:22:01 Requesting X11 forwarding
+\c 2001-12-05 17:22:02 X11 forwarding enabled
+
+If the remote system is Unix or Unix-like, you should also be able
+to see that the \i{\c{DISPLAY} environment variable} has been set to
+point at display 10 or above on the SSH server machine itself:
+
+\c fred@unixbox:~$ echo $DISPLAY
+\c unixbox:10.0
+
+If this works, you should then be able to run X applications in the
+remote session and have them display their windows on your PC.
+
+For more options relating to X11 forwarding, see \k{config-ssh-x11}.
+
+\H{using-port-forwarding} Using \i{port forwarding} in SSH
+
+The SSH protocol has the ability to forward arbitrary \i{network
+connection}s over your encrypted SSH connection, to avoid the network
+traffic being sent in clear. For example, you could use this to
+connect from your home computer to a \i{POP-3} server on a remote
+machine without your POP-3 password being visible to network
+sniffers.
+
+In order to use port forwarding to \I{local port forwarding}connect
+from your local machine to a port on a remote server, you need to:
+
+\b Choose a \i{port number} on your local machine where PuTTY should
+listen for incoming connections. There are likely to be plenty of
+unused port numbers above 3000. (You can also use a local loopback
+address here; see below for more details.)
+
+\b Now, before you start your SSH connection, go to the Tunnels
+panel (see \k{config-ssh-portfwd}). Make sure the \q{Local} radio
+button is set. Enter the local port number into the \q{Source port}
+box. Enter the destination host name and port number into the
+\q{Destination} box, separated by a colon (for example,
+\c{popserver.example.com:110} to connect to a POP-3 server).
+
+\b Now click the \q{Add} button. The details of your port forwarding
+should appear in the list box.
+
+Now start your session and log in. (Port forwarding will not be
+enabled until after you have logged in; otherwise it would be easy
+to perform completely anonymous network attacks, and gain access to
+anyone's virtual private network.) To check that PuTTY has set up
+the port forwarding correctly, you can look at the PuTTY Event Log
+(see \k{using-eventlog}). It should say something like this:
+
+\c 2001-12-05 17:22:10 Local port 3110 forwarding to
+\c popserver.example.com:110
+
+Now if you connect to the source port number on your local PC, you
+should find that it answers you exactly as if it were the service
+running on the destination machine. So in this example, you could
+then configure an e-mail client to use \c{localhost:3110} as a POP-3
+server instead of \c{popserver.example.com:110}. (Of course, the
+forwarding will stop happening when your PuTTY session closes down.)
+
+You can also forward ports in the other direction: arrange for a
+particular port number on the \e{server} machine to be \I{remote
+port forwarding}forwarded back to your PC as a connection to a
+service on your PC or near it.
+To do this, just select the \q{Remote} radio button instead of the
+\q{Local} one. The \q{Source port} box will now specify a port
+number on the \e{server} (note that most servers will not allow you
+to use \I{privileged port}port numbers under 1024 for this purpose).
+
+An alternative way to forward local connections to remote hosts is
+to use \I{dynamic port forwarding}dynamic SOCKS proxying. In this
+mode, PuTTY acts as a SOCKS server, which SOCKS-aware programs can
+connect to and open forwarded connections to the destination of their
+choice, so this can be an alternative to long lists of static
+forwardings. To use this mode, you will need to select the \q{Dynamic}
+radio button instead of \q{Local}, and then you should not enter
+anything into the \q{Destination} box (it will be ignored). PuTTY will
+then listen for SOCKS connections on the port you have specified.
+Most \i{web browsers} can be configured to connect to this SOCKS proxy
+service; also, you can forward other PuTTY connections through it by
+setting up the Proxy control panel (see \k{config-proxy} for details).
+
+The source port for a forwarded connection usually does not accept
+connections from any machine except the \I{localhost}SSH client or
+server machine itself (for local and remote forwardings respectively).
+There are controls in the Tunnels panel to change this:
+
+\b The \q{Local ports accept connections from other hosts} option
+allows you to set up local-to-remote port forwardings (including
+dynamic port forwardings) in such a way that machines other than
+your client PC can connect to the forwarded port.
+
+\b The \q{Remote ports do the same} option does the same thing for
+remote-to-local port forwardings (so that machines other than the
+SSH server machine can connect to the forwarded port.) Note that
+this feature is only available in the SSH-2 protocol, and not all
+SSH-2 servers honour it (in \i{OpenSSH}, for example, it's usually
+disabled by default).
+
+You can also specify an \i{IP address} to \I{listen address}listen
+on. Typically a Windows machine can be asked to listen on any single
+IP address in the \cw{127.*.*.*} range, and all of these are
+\i{loopback address}es available only to the local machine. So if
+you forward (for example) \c{127.0.0.5:79} to a remote machine's
+\i\cw{finger} port, then you should be able to run commands such as
+\c{finger fred@127.0.0.5}.
+This can be useful if the program connecting to the forwarded port
+doesn't allow you to change the port number it uses. This feature is
+available for local-to-remote forwarded ports; SSH-1 is unable to
+support it for remote-to-local ports, while SSH-2 can support it in
+theory but servers will not necessarily cooperate.
+
+(Note that if you're using Windows XP Service Pack 2, you may need
+to obtain a fix from Microsoft in order to use addresses like
+\cw{127.0.0.5} - see \k{faq-alternate-localhost}.)
+
+For more options relating to port forwarding, see
+\k{config-ssh-portfwd}.
+
+If the connection you are forwarding over SSH is itself a second SSH
+connection made by another copy of PuTTY, you might find the
+\q{logical host name} configuration option useful to warn PuTTY of
+which host key it should be expecting. See \k{config-loghost} for
+details of this.
+
+\H{using-rawprot} Making \i{raw TCP connections}
+
+A lot of \I{debugging Internet protocols}Internet protocols are
+composed of commands and responses in plain text. For example,
+\i{SMTP} (the protocol used to transfer e-mail), \i{NNTP} (the
+protocol used to transfer Usenet news), and \i{HTTP} (the protocol
+used to serve Web pages) all consist of commands in readable plain
+text.
+
+Sometimes it can be useful to connect directly to one of these
+services and speak the protocol \q{by hand}, by typing protocol
+commands and watching the responses. On Unix machines, you can do
+this using the system's \c{telnet} command to connect to the right
+port number. For example, \c{telnet mailserver.example.com 25} might
+enable you to talk directly to the SMTP service running on a mail
+server.
+
+Although the Unix \c{telnet} program provides this functionality,
+the protocol being used is not really Telnet. Really there is no
+actual protocol at all; the bytes sent down the connection are
+exactly the ones you type, and the bytes shown on the screen are
+exactly the ones sent by the server. Unix \c{telnet} will attempt to
+detect or guess whether the service it is talking to is a real
+Telnet service or not; PuTTY prefers to be told for certain.
+
+In order to make a debugging connection to a service of this type,
+you simply select the fourth protocol name, \I{\q{Raw}
+protocol}\q{Raw}, from the \q{Protocol} buttons in the \q{Session}
+configuration panel. (See \k{config-hostname}.) You can then enter a
+host name and a port number, and make the connection.
+
+\H{using-serial} Connecting to a local serial line
+
+PuTTY can connect directly to a local serial line as an alternative
+to making a network connection. In this mode, text typed into the
+PuTTY window will be sent straight out of your computer's serial
+port, and data received through that port will be displayed in the
+PuTTY window. You might use this mode, for example, if your serial
+port is connected to another computer which has a serial connection.
+
+To make a connection of this type, simply select \q{Serial} from the
+\q{Connection type} radio buttons on the \q{Session} configuration
+panel (see \k{config-hostname}). The \q{Host Name} and \q{Port}
+boxes will transform into \q{Serial line} and \q{Speed}, allowing
+you to specify which serial line to use (if your computer has more
+than one) and what speed (baud rate) to use when transferring data.
+For further configuration options (data bits, stop bits, parity,
+flow control), you can use the \q{Serial} configuration panel (see
+\k{config-serial}).
+
+After you start up PuTTY in serial mode, you might find that you
+have to make the first move, by sending some data out of the serial
+line in order to notify the device at the other end that someone is
+there for it to talk to. This probably depends on the device. If you
+start up a PuTTY serial session and nothing appears in the window,
+try pressing Return a few times and see if that helps.
+
+A serial line provides no well defined means for one end of the
+connection to notify the other that the connection is finished.
+Therefore, PuTTY in serial mode will remain connected until you
+close the window using the close button.
+
+\H{using-cmdline} The PuTTY command line
+
+PuTTY can be made to do various things without user intervention by
+supplying \i{command-line arguments} (e.g., from a \i{command prompt
+window}, or a \i{Windows shortcut}).
+
+\S{using-cmdline-session} Starting a session from the command line
+
+\I\c{-ssh}\I\c{-telnet}\I\c{-rlogin}\I\c{-raw}\I\c{-serial}These
+options allow you to bypass the configuration window and launch
+straight into a session.
+
+To start a connection to a server called \c{host}:
+
+\c putty.exe [-ssh | -telnet | -rlogin | -raw] [user@]host
+
+If this syntax is used, settings are taken from the \i{Default Settings}
+(see \k{config-saving}); \c{user} overrides these settings if
+supplied. Also, you can specify a protocol, which will override the
+default protocol (see \k{using-cmdline-protocol}).
+
+For telnet sessions, the following alternative syntax is supported
+(this makes PuTTY suitable for use as a URL handler for \i{telnet
+URLs} in \i{web browsers}):
+
+\c putty.exe telnet://host[:port]/
+
+To start a connection to a serial port, e.g. COM1:
+
+\c putty.exe -serial com1
+
+In order to start an existing saved session called \c{sessionname},
+use the \c{-load} option (described in \k{using-cmdline-load}).
+
+\c putty.exe -load "session name"
+
+\S{using-cleanup} \i\c{-cleanup}
+
+\cfg{winhelp-topic}{options.cleanup}
+
+If invoked with the \c{-cleanup} option, rather than running as
+normal, PuTTY will remove its \I{removing registry entries}registry
+entries and \i{random seed file} from the local machine (after
+confirming with the user).
+
+Note that on \i{multi-user systems}, \c{-cleanup} only removes
+registry entries and files associated with the currently logged-in
+user.
+
+\S{using-general-opts} Standard command-line options
+
+PuTTY and its associated tools support a range of command-line
+options, most of which are consistent across all the tools. This
+section lists the available options in all tools. Options which are
+specific to a particular tool are covered in the chapter about that
+tool.
+
+\S2{using-cmdline-load} \i\c{-load}: load a saved session
+
+\I{saved sessions, loading from command line}The \c{-load} option
+causes PuTTY to load configuration details out of a saved session.
+If these details include a host name, then this option is all you
+need to make PuTTY start a session.
+
+You need double quotes around the session name if it contains spaces.
+
+If you want to create a \i{Windows shortcut} to start a PuTTY saved
+session, this is the option you should use: your shortcut should
+call something like
+
+\c d:\path\to\putty.exe -load "my session"
+
+(Note that PuTTY itself supports an alternative form of this option,
+for backwards compatibility. If you execute \i\c{putty @sessionname}
+it will have the same effect as \c{putty -load "sessionname"}. With
+the \c{@} form, no double quotes are required, and the \c{@} sign
+must be the very first thing on the command line. This form of the
+option is deprecated.)
+
+\S2{using-cmdline-protocol} Selecting a protocol: \c{-ssh},
+\c{-telnet}, \c{-rlogin}, \c{-raw} \c{-serial}
+
+To choose which protocol you want to connect with, you can use one
+of these options:
+
+\b \i\c{-ssh} selects the SSH protocol.
+
+\b \i\c{-telnet} selects the Telnet protocol.
+
+\b \i\c{-rlogin} selects the Rlogin protocol.
+
+\b \i\c{-raw} selects the raw protocol.
+
+\b \i\c{-serial} selects a serial connection.
+
+These options are not available in the file transfer tools PSCP and
+PSFTP (which only work with the SSH protocol).
+
+These options are equivalent to the \i{protocol selection} buttons
+in the Session panel of the PuTTY configuration box (see
+\k{config-hostname}).
+
+\S2{using-cmdline-v} \i\c{-v}: increase verbosity
+
+\I{verbose mode}Most of the PuTTY tools can be made to tell you more
+about what they are doing by supplying the \c{-v} option. If you are
+having trouble when making a connection, or you're simply curious,
+you can turn this switch on and hope to find out more about what is
+happening.
+
+\S2{using-cmdline-l} \i\c{-l}: specify a \i{login name}
+
+You can specify the user name to log in as on the remote server
+using the \c{-l} option. For example, \c{plink login.example.com -l
+fred}.
+
+These options are equivalent to the username selection box in the
+Connection panel of the PuTTY configuration box (see
+\k{config-username}).
+
+\S2{using-cmdline-portfwd} \I{-L-upper}\c{-L}, \I{-R-upper}\c{-R}
+and \I{-D-upper}\c{-D}: set up \i{port forwardings}
+
+As well as setting up port forwardings in the PuTTY configuration
+(see \k{config-ssh-portfwd}), you can also set up forwardings on the
+command line. The command-line options work just like the ones in
+Unix \c{ssh} programs.
+
+To \I{local port forwarding}forward a local port (say 5110) to a
+remote destination (say \cw{popserver.example.com} port 110), you
+can write something like one of these:
+
+\c putty -L 5110:popserver.example.com:110 -load mysession
+\c plink mysession -L 5110:popserver.example.com:110
+
+To forward a \I{remote port forwarding}remote port to a local
+destination, just use the \c{-R} option instead of \c{-L}:
+
+\c putty -R 5023:mytelnetserver.myhouse.org:23 -load mysession
+\c plink mysession -R 5023:mytelnetserver.myhouse.org:23
+
+To \I{listen address}specify an IP address for the listening end of the
+tunnel, prepend it to the argument:
+
+\c plink -L 127.0.0.5:23:localhost:23 myhost
+
+To set up \I{dynamic port forwarding}SOCKS-based dynamic port
+forwarding on a local port, use the \c{-D} option. For this one you
+only have to pass the port number:
+
+\c putty -D 4096 -load mysession
+
+For general information on port forwarding, see
+\k{using-port-forwarding}.
+
+These options are not available in the file transfer tools PSCP and
+PSFTP.
+
+\S2{using-cmdline-m} \i\c{-m}: \I{reading commands from a file}read
+a remote command or script from a file
+
+The \i\c{-m} option performs a similar function to the \q{\ii{Remote
+command}} box in the SSH panel of the PuTTY configuration box (see
+\k{config-command}). However, the \c{-m} option expects to be given
+a local file name, and it will read a command from that file.
+
+With some servers (particularly Unix systems), you can even put
+multiple lines in this file and execute more than one command in
+sequence, or a whole shell script; but this is arguably an abuse, and
+cannot be expected to work on all servers. In particular, it is known
+\e{not} to work with certain \q{embedded} servers, such as \i{Cisco}
+routers.
+
+This option is not available in the file transfer tools PSCP and
+PSFTP.
+
+\S2{using-cmdline-p} \I{-P-upper}\c{-P}: specify a \i{port number}
+
+The \c{-P} option is used to specify the port number to connect to. If
+you have a Telnet server running on port 9696 of a machine instead of
+port 23, for example:
+
+\c putty -telnet -P 9696 host.name
+\c plink -telnet -P 9696 host.name
+
+(Note that this option is more useful in Plink than in PuTTY,
+because in PuTTY you can write \c{putty -telnet host.name 9696} in
+any case.)
+
+This option is equivalent to the port number control in the Session
+panel of the PuTTY configuration box (see \k{config-hostname}).
+
+\S2{using-cmdline-pw} \i\c{-pw}: specify a \i{password}
+
+A simple way to automate a remote login is to supply your password
+on the command line. This is \e{not recommended} for reasons of
+security. If you possibly can, we recommend you set up public-key
+authentication instead. See \k{pubkey} for details.
+
+Note that the \c{-pw} option only works when you are using the SSH
+protocol. Due to fundamental limitations of Telnet and Rlogin, these
+protocols do not support automated password authentication.
+
+\S2{using-cmdline-agentauth} \i\c{-agent} and \i\c{-noagent}:
+control use of Pageant for authentication
+
+The \c{-agent} option turns on SSH authentication using Pageant, and
+\c{-noagent} turns it off. These options are only meaningful if you
+are using SSH.
+
+See \k{pageant} for general information on \i{Pageant}.
+
+These options are equivalent to the agent authentication checkbox in
+the Auth panel of the PuTTY configuration box (see
+\k{config-ssh-tryagent}).
+
+\S2{using-cmdline-agent} \I{-A-upper}\c{-A} and \i\c{-a}: control \i{agent
+forwarding}
+
+The \c{-A} option turns on SSH agent forwarding, and \c{-a} turns it
+off. These options are only meaningful if you are using SSH.
+
+See \k{pageant} for general information on \i{Pageant}, and
+\k{pageant-forward} for information on agent forwarding. Note that
+there is a security risk involved with enabling this option; see
+\k{pageant-security} for details.
+
+These options are equivalent to the agent forwarding checkbox in the
+Auth panel of the PuTTY configuration box (see \k{config-ssh-agentfwd}).
+
+These options are not available in the file transfer tools PSCP and
+PSFTP.
+
+\S2{using-cmdline-x11} \I{-X-upper}\c{-X} and \i\c{-x}: control \i{X11
+forwarding}
+
+The \c{-X} option turns on X11 forwarding in SSH, and \c{-x} turns
+it off. These options are only meaningful if you are using SSH.
+
+For information on X11 forwarding, see \k{using-x-forwarding}.
+
+These options are equivalent to the X11 forwarding checkbox in the
+X11 panel of the PuTTY configuration box (see \k{config-ssh-x11}).
+
+These options are not available in the file transfer tools PSCP and
+PSFTP.
+
+\S2{using-cmdline-pty} \i\c{-t} and \I{-T-upper}\c{-T}: control
+\i{pseudo-terminal allocation}
+
+The \c{-t} option ensures PuTTY attempts to allocate a
+pseudo-terminal at the server, and \c{-T} stops it from allocating
+one. These options are only meaningful if you are using SSH.
+
+These options are equivalent to the \q{Don't allocate a
+pseudo-terminal} checkbox in the SSH panel of the PuTTY
+configuration box (see \k{config-ssh-pty}).
+
+These options are not available in the file transfer tools PSCP and
+PSFTP.
+
+\S2{using-cmdline-noshell} \I{-N-upper}\c{-N}: suppress starting a
+\I{suppressing remote shell}shell or command
+
+The \c{-N} option prevents PuTTY from attempting to start a shell or
+command on the remote server. You might want to use this option if
+you are only using the SSH connection for port forwarding, and your
+user account on the server does not have the ability to run a shell.
+
+This feature is only available in SSH protocol version 2 (since the
+version 1 protocol assumes you will always want to run a shell).
+
+This option is equivalent to the \q{Don't start a shell or command
+at all} checkbox in the SSH panel of the PuTTY configuration box
+(see \k{config-ssh-noshell}).
+
+This option is not available in the file transfer tools PSCP and
+PSFTP.
+
+\S2{using-cmdline-ncmode} \I{-nc}\c{-nc}: make a \i{remote network
+connection} in place of a remote shell or command
+
+The \c{-nc} option prevents Plink (or PuTTY) from attempting to
+start a shell or command on the remote server. Instead, it will
+instruct the remote server to open a network connection to a host
+name and port number specified by you, and treat that network
+connection as if it were the main session.
+
+You specify a host and port as an argument to the \c{-nc} option,
+with a colon separating the host name from the port number, like
+this:
+
+\c plink host1.example.com -nc host2.example.com:1234
+
+You might want to use this feature if you needed to make an SSH
+connection to a target host which you can only reach by going
+through a proxy host, and rather than using port forwarding you
+prefer to use the local proxy feature (see \k{config-proxy-type} for
+more about local proxies). In this situation you might select
+\q{Local} proxy type, set your local proxy command to be \cq{plink
+%proxyhost -nc %host:%port}, enter the target host name on the
+Session panel, and enter the directly reachable proxy host name on
+the Proxy panel.
+
+This feature is only available in SSH protocol version 2 (since the
+version 1 protocol assumes you will always want to run a shell). It
+is not available in the file transfer tools PSCP and PSFTP. It is
+available in PuTTY itself, although it is unlikely to be very useful
+in any tool other than Plink. Also, \c{-nc} uses the same server
+functionality as port forwarding, so it will not work if your server
+administrator has disabled port forwarding.
+
+(The option is named \c{-nc} after the Unix program
+\W{http://www.vulnwatch.org/netcat/}\c{nc}, short for \q{netcat}.
+The command \cq{plink host1 -nc host2:port} is very similar in
+functionality to \cq{plink host1 nc host2 port}, which invokes
+\c{nc} on the server and tells it to connect to the specified
+destination. However, Plink's built-in \c{-nc} option does not
+depend on the \c{nc} program being installed on the server.)
+
+\S2{using-cmdline-compress} \I{-C-upper}\c{-C}: enable \i{compression}
+
+The \c{-C} option enables compression of the data sent across the
+network. This option is only meaningful if you are using SSH.
+
+This option is equivalent to the \q{Enable compression} checkbox in
+the SSH panel of the PuTTY configuration box (see
+\k{config-ssh-comp}).
+
+\S2{using-cmdline-sshprot} \i\c{-1} and \i\c{-2}: specify an \i{SSH
+protocol version}
+
+The \c{-1} and \c{-2} options force PuTTY to use version \I{SSH-1}1
+or version \I{SSH-2}2 of the SSH protocol. These options are only
+meaningful if you are using SSH.
+
+These options are equivalent to selecting your preferred SSH
+protocol version as \q{1 only} or \q{2 only} in the SSH panel of the
+PuTTY configuration box (see \k{config-ssh-prot}).
+
+\S2{using-cmdline-ipversion} \i\c{-4} and \i\c{-6}: specify an
+\i{Internet protocol version}
+
+The \c{-4} and \c{-6} options force PuTTY to use the older Internet
+protocol \i{IPv4} or the newer \i{IPv6} for most outgoing
+connections.
+
+These options are equivalent to selecting your preferred Internet
+protocol version as \q{IPv4} or \q{IPv6} in the Connection panel of
+the PuTTY configuration box (see \k{config-address-family}).
+
+\S2{using-cmdline-identity} \i\c{-i}: specify an SSH \i{private key}
+
+The \c{-i} option allows you to specify the name of a private key
+file in \c{*.\i{PPK}} format which PuTTY will use to authenticate with the
+server. This option is only meaningful if you are using SSH.
+
+For general information on \i{public-key authentication}, see
+\k{pubkey}.
+
+This option is equivalent to the \q{Private key file for
+authentication} box in the Auth panel of the PuTTY configuration box
+(see \k{config-ssh-privkey}).
+
+\S2{using-cmdline-loghost} \i\c{-loghost}: specify a \i{logical host
+name}
+
+This option overrides PuTTY's normal SSH host key caching policy by
+telling it the name of the host you expect your connection to end up
+at (in cases where this differs from the location PuTTY thinks it's
+connecting to). It can be a plain host name, or a host name followed
+by a colon and a port number. See \k{config-loghost} for more detail
+on this.
+
+\S2{using-cmdline-pgpfp} \i\c{-pgpfp}: display \i{PGP key fingerprint}s
+
+This option causes the PuTTY tools not to run as normal, but instead
+to display the fingerprints of the PuTTY PGP Master Keys, in order to
+aid with \i{verifying new versions}. See \k{pgpkeys} for more information.
+
+\S2{using-cmdline-sercfg} \i\c{-sercfg}: specify serial port
+\i{configuration}
+
+This option specifies the configuration parameters for the serial
+port (baud rate, stop bits etc). Its argument is interpreted as a
+comma-separated list of configuration options, which can be as
+follows:
+
+\b Any single digit from 5 to 9 sets the number of data bits.
+
+\b \cq{1}, \cq{1.5} or \cq{2} sets the number of stop bits.
+
+\b Any other numeric string is interpreted as a baud rate.
+
+\b A single lower-case letter specifies the parity: \cq{n} for none,
+\cq{o} for odd, \cq{e} for even, \cq{m} for mark and \cq{s} for space.
+
+\b A single upper-case letter specifies the flow control: \cq{N} for
+none, \cq{X} for XON/XOFF, \cq{R} for RTS/CTS and \cq{D} for
+DSR/DTR.
+
+For example, \cq{-sercfg 19200,8,n,1,N} denotes a baud rate of
+19200, 8 data bits, no parity, 1 stop bit and no flow control.
--- /dev/null
+\# Invoke the versionid macros defined in all the other manual
+\# chapter files.
+
+\versionidblurb
+
+\versionidintro
+
+\versionidgs
+
+\versionidusing
+
+\versionidconfig
+
+\versionidpscp
+
+\versionidpsftp
+
+\versionidplink
+
+\versionidpubkey
+
+\versionidpageant
+
+\versioniderrors
+
+\versionidfaq
+
+\versionidfeedback
+
+\versionidlicence
+
+\versionidudp
+
+\versionidpgpkeys
+
+\versionidindex
--- /dev/null
+# Makefile for the PuTTY icon suite.
+
+ICONS = putty puttycfg puttygen pscp pageant pterm ptermcfg puttyins
+SIZES = 16 32 48
+
+MODE = # override to -it on command line for opaque testing
+
+PNGS = $(foreach I,$(ICONS),$(foreach S,$(SIZES),$(I)-$(S).png))
+MONOPNGS = $(foreach I,$(ICONS),$(foreach S,$(SIZES),$(I)-$(S)-mono.png))
+TRUEPNGS = $(foreach I,$(ICONS),$(foreach S,$(SIZES),$(I)-$(S)-true.png))
+
+ICOS = putty.ico puttygen.ico pscp.ico pageant.ico pageants.ico puttycfg.ico \
+ puttyins.ico
+CICONS = xpmputty.c xpmpucfg.c xpmpterm.c xpmptcfg.c
+
+base: icos cicons
+
+all: pngs monopngs base # truepngs currently disabled by default
+
+pngs: $(PNGS)
+monopngs: $(MONOPNGS)
+truepngs: $(TRUEPNGS)
+
+icos: $(ICOS)
+cicons: $(CICONS)
+
+install: icos cicons
+ cp $(ICOS) ../windows
+ cp $(CICONS) ../unix
+
+$(PNGS): %.png: mkicon.py
+ ./mkicon.py $(MODE) $(join $(subst -, ,$(basename $@)),_icon) $@
+
+$(MONOPNGS): %.png: mkicon.py
+ ./mkicon.py -2 $(MODE) $(join $(subst -, ,$(subst -mono,,$(basename $@))),_icon) $@
+
+$(TRUEPNGS): %.png: mkicon.py
+ ./mkicon.py -T $(MODE) $(join $(subst -, ,$(subst -true,,$(basename $@))),_icon) $@
+
+putty.ico: putty-16.png putty-32.png putty-48.png \
+ putty-16-mono.png putty-32-mono.png putty-48-mono.png
+ ./icon.pl -4 $(filter-out %-mono.png, $^) -1 $(filter %-mono.png, $^) > $@
+
+puttycfg.ico: puttycfg-16.png puttycfg-32.png puttycfg-48.png \
+ puttycfg-16-mono.png puttycfg-32-mono.png puttycfg-48-mono.png
+ ./icon.pl -4 $(filter-out %-mono.png, $^) -1 $(filter %-mono.png, $^) > $@
+
+puttygen.ico: puttygen-16.png puttygen-32.png puttygen-48.png \
+ puttygen-16-mono.png puttygen-32-mono.png puttygen-48-mono.png
+ ./icon.pl -4 $(filter-out %-mono.png, $^) -1 $(filter %-mono.png, $^) > $@
+
+pageant.ico: pageant-16.png pageant-32.png pageant-48.png \
+ pageant-16-mono.png pageant-32-mono.png pageant-48-mono.png
+ ./icon.pl -4 $(filter-out %-mono.png, $^) -1 $(filter %-mono.png, $^) > $@
+
+pageants.ico: pageant-16.png pageant-16-mono.png
+ ./icon.pl -4 $(filter-out %-mono.png, $^) -1 $(filter %-mono.png, $^) > $@
+
+pscp.ico: pscp-16.png pscp-32.png pscp-48.png \
+ pscp-16-mono.png pscp-32-mono.png pscp-48-mono.png
+ ./icon.pl -4 $(filter-out %-mono.png, $^) -1 $(filter %-mono.png, $^) > $@
+
+# Because the installer icon makes heavy use of brown when drawing
+# the cardboard box, it's worth having 8-bit versions of it in
+# addition to the 4- and 1-bit ones.
+puttyins.ico: puttyins-16.png puttyins-32.png puttyins-48.png \
+ puttyins-16-mono.png puttyins-32-mono.png \
+ puttyins-48-mono.png \
+ puttyins-16-true.png puttyins-32-true.png \
+ puttyins-48-true.png
+ ./icon.pl -8 $(filter %-true.png, $^) \
+ -4 $(filter-out %-true.png, $(filter-out %-mono.png, $^)) \
+ -1 $(filter %-mono.png, $^) > $@
+
+# Icon for the website. (This isn't linked into "make all".)
+website.ico: putty-16.png
+ ./icon.pl -4 $^ >$@
+
+xpmputty.c: putty-16.png putty-32.png putty-48.png
+ ./cicon.pl main_icon $^ > $@
+
+xpmpucfg.c: puttycfg-16.png puttycfg-32.png puttycfg-48.png
+ ./cicon.pl cfg_icon $^ > $@
+
+xpmpterm.c: pterm-16.png pterm-32.png pterm-48.png
+ ./cicon.pl main_icon $^ > $@
+
+xpmptcfg.c: ptermcfg-16.png ptermcfg-32.png ptermcfg-48.png
+ ./cicon.pl cfg_icon $^ > $@
+
+clean:
+ rm -f *.png *.ico *.c
--- /dev/null
+#!/usr/bin/perl
+
+# Given a list of input PNGs, create a C source file containing a
+# const array of XPMs, named by a given C identifier.
+
+$id = shift @ARGV;
+$k = 0;
+@xpms = ();
+foreach $f (@ARGV) {
+ # XPM format is generated directly by ImageMagick, so that's easy
+ # enough. We just have to adjust the declaration line so that it
+ # has the right name, linkage and storage class.
+ @lines = ();
+ open XPM, "convert $f xpm:- |";
+ push @lines, $_ while <XPM>;
+ close XPM;
+ die "XPM from $f in unexpected format\n" unless $lines[1] =~ /^static.*\{$/;
+ $lines[1] = "static const char *const ${id}_$k"."[] = {\n";
+ $k++;
+ push @xpms, @lines, "\n";
+}
+
+# Now output.
+foreach $line (@xpms) { print $line; }
+print "const char *const *const ${id}[] = {\n";
+for ($i = 0; $i < $k; $i++) { print " ${id}_$i,\n"; }
+print "};\n";
+print "const int n_${id} = $k;\n";
--- /dev/null
+#!/usr/bin/perl
+
+# Take a collection of input image files and convert them into a
+# multi-resolution Windows .ICO icon file.
+#
+# The input images can be treated as having four different colour
+# depths:
+#
+# - 24-bit true colour
+# - 8-bit with custom palette
+# - 4-bit using the Windows 16-colour palette (see comment below
+# for details)
+# - 1-bit using black and white only.
+#
+# The images can be supplied in any input format acceptable to
+# ImageMagick, but their actual colour usage must already be
+# appropriate for the specified mode; this script will not do any
+# substantive conversion. So if an image intended to be used in 4-
+# or 1-bit mode contains any colour not in the appropriate fixed
+# palette, that's a fatal error; if an image to be used in 8-bit
+# mode contains more than 256 distinct colours, that's also a fatal
+# error.
+#
+# Command-line syntax is:
+#
+# icon.pl -depth imagefile [imagefile...] [-depth imagefile [imagefile...]]
+#
+# where `-depth' is one of `-24', `-8', `-4' or `-1', and tells the
+# script how to treat all the image files given after that option
+# until the next depth option. For example, you might execute
+#
+# icon.pl -24 48x48x24.png 32x32x24.png -8 32x32x8.png -1 monochrome.png
+#
+# to build an icon file containing two differently sized 24-bit
+# images, one 8-bit image and one black and white image.
+#
+# Windows .ICO files support a 1-bit alpha channel on all these
+# image types. That is, any pixel can be either opaque or fully
+# transparent, but not partially transparent. The alpha channel is
+# separate from the main image data, meaning that `transparent' is
+# not required to take up a palette entry. (So an 8-bit image can
+# have 256 distinct _opaque_ colours, plus transparent pixels as
+# well.) If the input images have alpha channels, they will be used
+# to determine which pixels of the icon are transparent, by simple
+# quantisation half way up (e.g. in a PNG image with an 8-bit alpha
+# channel, alpha values of 00-7F will be mapped to transparent
+# pixels, and 80-FF will become opaque).
+
+# The Windows 16-colour palette consists of:
+# - the eight corners of the colour cube (000000, 0000FF, 00FF00,
+# 00FFFF, FF0000, FF00FF, FFFF00, FFFFFF)
+# - dim versions of the seven non-black corners, at 128/255 of the
+# brightness (000080, 008000, 008080, 800000, 800080, 808000,
+# 808080)
+# - light grey at 192/255 of full brightness (C0C0C0).
+%win16pal = (
+ "\x00\x00\x00\x00" => 0,
+ "\x00\x00\x80\x00" => 1,
+ "\x00\x80\x00\x00" => 2,
+ "\x00\x80\x80\x00" => 3,
+ "\x80\x00\x00\x00" => 4,
+ "\x80\x00\x80\x00" => 5,
+ "\x80\x80\x00\x00" => 6,
+ "\xC0\xC0\xC0\x00" => 7,
+ "\x80\x80\x80\x00" => 8,
+ "\x00\x00\xFF\x00" => 9,
+ "\x00\xFF\x00\x00" => 10,
+ "\x00\xFF\xFF\x00" => 11,
+ "\xFF\x00\x00\x00" => 12,
+ "\xFF\x00\xFF\x00" => 13,
+ "\xFF\xFF\x00\x00" => 14,
+ "\xFF\xFF\xFF\x00" => 15,
+);
+@win16pal = sort { $win16pal{$a} <=> $win16pal{$b} } keys %win16pal;
+
+# The black and white palette consists of black (000000) and white
+# (FFFFFF), obviously.
+%win2pal = (
+ "\x00\x00\x00\x00" => 0,
+ "\xFF\xFF\xFF\x00" => 1,
+);
+@win2pal = sort { $win16pal{$a} <=> $win2pal{$b} } keys %win2pal;
+
+@hdr = ();
+@dat = ();
+
+$depth = undef;
+foreach $_ (@ARGV) {
+ if (/^-(24|8|4|1)$/) {
+ $depth = $1;
+ } elsif (defined $depth) {
+ &readicon($_, $depth);
+ } else {
+ $usage = 1;
+ }
+}
+if ($usage || length @hdr == 0) {
+ print "usage: icon.pl ( -24 | -8 | -4 | -1 ) image [image...]\n";
+ print " [ ( -24 | -8 | -4 | -1 ) image [image...] ...]\n";
+ exit 0;
+}
+
+# Now write out the output icon file.
+print pack "vvv", 0, 1, scalar @hdr; # file-level header
+$filepos = 6 + 16 * scalar @hdr;
+for ($i = 0; $i < scalar @hdr; $i++) {
+ print $hdr[$i];
+ print pack "V", $filepos;
+ $filepos += length($dat[$i]);
+}
+for ($i = 0; $i < scalar @hdr; $i++) {
+ print $dat[$i];
+}
+
+sub readicon {
+ my $filename = shift @_;
+ my $depth = shift @_;
+ my $pix;
+ my $i;
+ my %pal;
+
+ # Determine the icon's width and height.
+ my $w = `identify -format %w $filename`;
+ my $h = `identify -format %h $filename`;
+
+ # Read the file in as RGBA data. We flip vertically at this
+ # point, to avoid having to do it ourselves (.BMP and hence
+ # .ICO are bottom-up).
+ my $data = [];
+ open IDATA, "convert -flip -depth 8 $filename rgba:- |";
+ push @$data, $rgb while (read IDATA,$rgb,4,0) == 4;
+ close IDATA;
+ # Check we have the right amount of data.
+ $xl = $w * $h;
+ $al = scalar @$data;
+ die "wrong amount of image data ($al, expected $xl) from $filename\n"
+ unless $al == $xl;
+
+ # Build the alpha channel now, so we can exclude transparent
+ # pixels from the palette analysis. We replace transparent
+ # pixels with undef in the data array.
+ #
+ # We quantise the alpha channel half way up, so that alpha of
+ # 0x80 or more is taken to be fully opaque and 0x7F or less is
+ # fully transparent. Nasty, but the best we can do without
+ # dithering (and don't even suggest we do that!).
+ my $x;
+ my $y;
+ my $alpha = "";
+
+ for ($y = 0; $y < $h; $y++) {
+ my $currbyte = 0, $currbits = 0;
+ for ($x = 0; $x < (($w+31)|31)-31; $x++) {
+ $pix = ($x < $w ? $data->[$y*$w+$x] : "\x00\x00\x00\xFF");
+ my @rgba = unpack "CCCC", $pix;
+ $currbyte <<= 1;
+ $currbits++;
+ if ($rgba[3] < 0x80) {
+ if ($x < $w) {
+ $data->[$y*$w+$x] = undef;
+ }
+ $currbyte |= 1; # MS has the alpha channel inverted :-)
+ } else {
+ # Might as well flip RGBA into BGR0 while we're here.
+ if ($x < $w) {
+ $data->[$y*$w+$x] = pack "CCCC",
+ $rgba[2], $rgba[1], $rgba[0], 0;
+ }
+ }
+ if ($currbits >= 8) {
+ $alpha .= pack "C", $currbyte;
+ $currbits -= 8;
+ }
+ }
+ }
+
+ # For an 8-bit image, check we have at most 256 distinct
+ # colours, and build the palette.
+ %pal = ();
+ if ($depth == 8) {
+ my $palindex = 0;
+ foreach $pix (@$data) {
+ next unless defined $pix;
+ $pal{$pix} = $palindex++ unless defined $pal{$pix};
+ }
+ die "too many colours in 8-bit image $filename\n" unless $palindex <= 256;
+ } elsif ($depth == 4) {
+ %pal = %win16pal;
+ } elsif ($depth == 1) {
+ %pal = %win2pal;
+ }
+
+ my $raster = "";
+ if ($depth < 24) {
+ # For a non-24-bit image, flatten the image into one palette
+ # index per pixel.
+ $pad = 32 / $depth; # number of pixels to pad scanline to 4-byte align
+ $pmask = $pad-1;
+ for ($y = 0; $y < $h; $y++) {
+ my $currbyte = 0, $currbits = 0;
+ for ($x = 0; $x < (($w+$pmask)|$pmask)-$pmask; $x++) {
+ $currbyte <<= $depth;
+ $currbits += $depth;
+ if ($x < $w && defined ($pix = $data->[$y*$w+$x])) {
+ if (!defined $pal{$pix}) {
+ $pixhex = sprintf "%02x%02x%02x", unpack "CCC", $pix;
+ die "illegal colour value $pixhex at pixel ($x,$y) in $filename\n";
+ }
+ $currbyte |= $pal{$pix};
+ }
+ if ($currbits >= 8) {
+ $raster .= pack "C", $currbyte;
+ $currbits -= 8;
+ }
+ }
+ }
+ } else {
+ # For a 24-bit image, reverse the order of the R,G,B values
+ # and stick a padding zero on the end.
+ #
+ # (In this loop we don't need to bother padding the
+ # scanline out to a multiple of four bytes, because every
+ # pixel takes four whole bytes anyway.)
+ for ($i = 0; $i < scalar @$data; $i++) {
+ if (defined $data->[$i]) {
+ $raster .= $data->[$i];
+ } else {
+ $raster .= "\x00\x00\x00\x00";
+ }
+ }
+ $depth = 32; # and adjust this
+ }
+
+ # Prepare the icon data. First the header...
+ my $data = pack "VVVvvVVVVVV",
+ 40, # size of bitmap info header
+ $w, # icon width
+ $h*2, # icon height (x2 to indicate the subsequent alpha channel)
+ 1, # 1 plane (common to all MS image formats)
+ $depth, # bits per pixel
+ 0, # no compression
+ length $raster, # image size
+ 0, 0, 0, 0; # resolution, colours used, colours important (ignored)
+ # ... then the palette ...
+ if ($depth <= 8) {
+ my $ncols = (1 << $depth);
+ my $palette = "\x00\x00\x00\x00" x $ncols;
+ foreach $i (keys %pal) {
+ substr($palette, $pal{$i}*4, 4) = $i;
+ }
+ $data .= $palette;
+ }
+ # ... the raster data we already had ready ...
+ $data .= $raster;
+ # ... and the alpha channel we already had as well.
+ $data .= $alpha;
+
+ # Prepare the header which will represent this image in the
+ # icon file.
+ my $header = pack "CCCCvvV",
+ $w, $h, # width and height (this time the real height)
+ 1 << $depth, # number of colours, if less than 256
+ 0, # reserved
+ 1, # planes
+ $depth, # bits per pixel
+ length $data; # size of real icon data
+
+ push @hdr, $header;
+ push @dat, $data;
+}
--- /dev/null
+#!/usr/bin/env python
+
+import math
+
+# Python code which draws the PuTTY icon components at a range of
+# sizes.
+
+# TODO
+# ----
+#
+# - use of alpha blending
+# + try for variable-transparency borders
+#
+# - can we integrate the Mac icons into all this? Do we want to?
+
+def pixel(x, y, colour, canvas):
+ canvas[(int(x),int(y))] = colour
+
+def overlay(src, x, y, dst):
+ x = int(x)
+ y = int(y)
+ for (sx, sy), colour in src.items():
+ dst[sx+x, sy+y] = blend(colour, dst.get((sx+x, sy+y), cT))
+
+def finalise(canvas):
+ for k in canvas.keys():
+ canvas[k] = finalisepix(canvas[k])
+
+def bbox(canvas):
+ minx, miny, maxx, maxy = None, None, None, None
+ for (x, y) in canvas.keys():
+ if minx == None:
+ minx, miny, maxx, maxy = x, y, x+1, y+1
+ else:
+ minx = min(minx, x)
+ miny = min(miny, y)
+ maxx = max(maxx, x+1)
+ maxy = max(maxy, y+1)
+ return (minx, miny, maxx, maxy)
+
+def topy(canvas):
+ miny = {}
+ for (x, y) in canvas.keys():
+ miny[x] = min(miny.get(x, y), y)
+ return miny
+
+def render(canvas, minx, miny, maxx, maxy):
+ w = maxx - minx
+ h = maxy - miny
+ ret = []
+ for y in range(h):
+ ret.append([outpix(cT)] * w)
+ for (x, y), colour in canvas.items():
+ if x >= minx and x < maxx and y >= miny and y < maxy:
+ ret[y-miny][x-minx] = outpix(colour)
+ return ret
+
+# Code to actually draw pieces of icon. These don't generally worry
+# about positioning within a canvas; they just draw at a standard
+# location, return some useful coordinates, and leave composition
+# to other pieces of code.
+
+sqrthash = {}
+def memoisedsqrt(x):
+ if not sqrthash.has_key(x):
+ sqrthash[x] = math.sqrt(x)
+ return sqrthash[x]
+
+BR, TR, BL, TL = range(4) # enumeration of quadrants for border()
+
+def border(canvas, thickness, squarecorners, out={}):
+ # I haven't yet worked out exactly how to do borders in a
+ # properly alpha-blended fashion.
+ #
+ # When you have two shades of dark available (half-dark H and
+ # full-dark F), the right sequence of circular border sections
+ # around a pixel x starts off with these two layouts:
+ #
+ # H F
+ # HxH FxF
+ # H F
+ #
+ # Where it goes after that I'm not entirely sure, but I'm
+ # absolutely sure those are the right places to start. However,
+ # every automated algorithm I've tried has always started off
+ # with the two layouts
+ #
+ # H HHH
+ # HxH HxH
+ # H HHH
+ #
+ # which looks much worse. This is true whether you do
+ # pixel-centre sampling (define an inner circle and an outer
+ # circle with radii differing by 1, set any pixel whose centre
+ # is inside the inner circle to F, any pixel whose centre is
+ # outside the outer one to nothing, interpolate between the two
+ # and round sensibly), _or_ whether you plot a notional circle
+ # of a given radius and measure the actual _proportion_ of each
+ # pixel square taken up by it.
+ #
+ # It's not clear what I should be doing to prevent this. One
+ # option is to attempt error-diffusion: Ian Jackson proved on
+ # paper that if you round each pixel's ideal value to the
+ # nearest of the available output values, then measure the
+ # error at each pixel, propagate that error outwards into the
+ # original values of the surrounding pixels, and re-round
+ # everything, you do get the correct second stage. However, I
+ # haven't tried it at a proper range of radii.
+ #
+ # Another option is that the automated mechanisms described
+ # above would be entirely adequate if it weren't for the fact
+ # that the human visual centres are adapted to detect
+ # horizontal and vertical lines in particular, so the only
+ # place you have to behave a bit differently is at the ends of
+ # the top and bottom row of pixels in the circle, and the top
+ # and bottom of the extreme columns.
+ #
+ # For the moment, what I have below is a very simple mechanism
+ # which always uses only one alpha level for any given border
+ # thickness, and which seems to work well enough for Windows
+ # 16-colour icons. Everything else will have to wait.
+
+ thickness = memoisedsqrt(thickness)
+
+ if thickness < 0.9:
+ darkness = 0.5
+ else:
+ darkness = 1
+ if thickness < 1: thickness = 1
+ thickness = round(thickness - 0.5) + 0.3
+
+ out["borderthickness"] = thickness
+
+ dmax = int(round(thickness))
+ if dmax < thickness: dmax = dmax + 1
+
+ cquadrant = [[0] * (dmax+1) for x in range(dmax+1)]
+ squadrant = [[0] * (dmax+1) for x in range(dmax+1)]
+
+ for x in range(dmax+1):
+ for y in range(dmax+1):
+ if max(x, y) < thickness:
+ squadrant[x][y] = darkness
+ if memoisedsqrt(x*x+y*y) < thickness:
+ cquadrant[x][y] = darkness
+
+ bvalues = {}
+ for (x, y), colour in canvas.items():
+ for dx in range(-dmax, dmax+1):
+ for dy in range(-dmax, dmax+1):
+ quadrant = 2 * (dx < 0) + (dy < 0)
+ if (x, y, quadrant) in squarecorners:
+ bval = squadrant[abs(dx)][abs(dy)]
+ else:
+ bval = cquadrant[abs(dx)][abs(dy)]
+ if bvalues.get((x+dx,y+dy),0) < bval:
+ bvalues[(x+dx,y+dy)] = bval
+
+ for (x, y), value in bvalues.items():
+ if not canvas.has_key((x,y)):
+ canvas[(x,y)] = dark(value)
+
+def sysbox(size, out={}):
+ canvas = {}
+
+ # The system box of the computer.
+
+ height = int(round(3.6*size))
+ width = int(round(16.51*size))
+ depth = int(round(2*size))
+ highlight = int(round(1*size))
+ bothighlight = int(round(1*size))
+
+ out["sysboxheight"] = height
+
+ floppystart = int(round(19*size)) # measured in half-pixels
+ floppyend = int(round(29*size)) # measured in half-pixels
+ floppybottom = height - bothighlight
+ floppyrheight = 0.7 * size
+ floppyheight = int(round(floppyrheight))
+ if floppyheight < 1:
+ floppyheight = 1
+ floppytop = floppybottom - floppyheight
+
+ # The front panel is rectangular.
+ for x in range(width):
+ for y in range(height):
+ grey = 3
+ if x < highlight or y < highlight:
+ grey = grey + 1
+ if x >= width-highlight or y >= height-bothighlight:
+ grey = grey - 1
+ if y < highlight and x >= width-highlight:
+ v = (highlight-1-y) - (x-(width-highlight))
+ if v < 0:
+ grey = grey - 1
+ elif v > 0:
+ grey = grey + 1
+ if y >= floppytop and y < floppybottom and \
+ 2*x+2 > floppystart and 2*x < floppyend:
+ if 2*x >= floppystart and 2*x+2 <= floppyend and \
+ floppyrheight >= 0.7:
+ grey = 0
+ else:
+ grey = 2
+ pixel(x, y, greypix(grey/4.0), canvas)
+
+ # The side panel is a parallelogram.
+ for x in range(depth):
+ for y in range(height):
+ pixel(x+width, y-(x+1), greypix(0.5), canvas)
+
+ # The top panel is another parallelogram.
+ for x in range(width-1):
+ for y in range(depth):
+ grey = 3
+ if x >= width-1 - highlight:
+ grey = grey + 1
+ pixel(x+(y+1), -(y+1), greypix(grey/4.0), canvas)
+
+ # And draw a border.
+ border(canvas, size, [], out)
+
+ return canvas
+
+def monitor(size):
+ canvas = {}
+
+ # The computer's monitor.
+
+ height = int(round(9.55*size))
+ width = int(round(11.49*size))
+ surround = int(round(1*size))
+ botsurround = int(round(2*size))
+ sheight = height - surround - botsurround
+ swidth = width - 2*surround
+ depth = int(round(2*size))
+ highlight = int(round(math.sqrt(size)))
+ shadow = int(round(0.55*size))
+
+ # The front panel is rectangular.
+ for x in range(width):
+ for y in range(height):
+ if x >= surround and y >= surround and \
+ x < surround+swidth and y < surround+sheight:
+ # Screen.
+ sx = (float(x-surround) - swidth/3) / swidth
+ sy = (float(y-surround) - sheight/3) / sheight
+ shighlight = 1.0 - (sx*sx+sy*sy)*0.27
+ pix = bluepix(shighlight)
+ if x < surround+shadow or y < surround+shadow:
+ pix = blend(cD, pix) # sharp-edged shadow on top and left
+ else:
+ # Complicated double bevel on the screen surround.
+
+ # First, the outer bevel. We compute the distance
+ # from this pixel to each edge of the front
+ # rectangle.
+ list = [
+ (x, +1),
+ (y, +1),
+ (width-1-x, -1),
+ (height-1-y, -1)
+ ]
+ # Now sort the list to find the distance to the
+ # _nearest_ edge, or the two joint nearest.
+ list.sort()
+ # If there's one nearest edge, that determines our
+ # bevel colour. If there are two joint nearest, our
+ # bevel colour is their shared one if they agree,
+ # and neutral otherwise.
+ outerbevel = 0
+ if list[0][0] < list[1][0] or list[0][1] == list[1][1]:
+ if list[0][0] < highlight:
+ outerbevel = list[0][1]
+
+ # Now, the inner bevel. We compute the distance
+ # from this pixel to each edge of the screen
+ # itself.
+ list = [
+ (surround-1-x, -1),
+ (surround-1-y, -1),
+ (x-(surround+swidth), +1),
+ (y-(surround+sheight), +1)
+ ]
+ # Now we sort to find the _maximum_ distance, which
+ # conveniently ignores any less than zero.
+ list.sort()
+ # And now the strategy is pretty much the same as
+ # above, only we're working from the opposite end
+ # of the list.
+ innerbevel = 0
+ if list[-1][0] > list[-2][0] or list[-1][1] == list[-2][1]:
+ if list[-1][0] >= 0 and list[-1][0] < highlight:
+ innerbevel = list[-1][1]
+
+ # Now we know the adjustment we want to make to the
+ # pixel's overall grey shade due to the outer
+ # bevel, and due to the inner one. We break a tie
+ # in favour of a light outer bevel, but otherwise
+ # add.
+ grey = 3
+ if outerbevel > 0 or outerbevel == innerbevel:
+ innerbevel = 0
+ grey = grey + outerbevel + innerbevel
+
+ pix = greypix(grey / 4.0)
+
+ pixel(x, y, pix, canvas)
+
+ # The side panel is a parallelogram.
+ for x in range(depth):
+ for y in range(height):
+ pixel(x+width, y-x, greypix(0.5), canvas)
+
+ # The top panel is another parallelogram.
+ for x in range(width):
+ for y in range(depth-1):
+ pixel(x+(y+1), -(y+1), greypix(0.75), canvas)
+
+ # And draw a border.
+ border(canvas, size, [(0,int(height-1),BL)])
+
+ return canvas
+
+def computer(size):
+ # Monitor plus sysbox.
+ out = {}
+ m = monitor(size)
+ s = sysbox(size, out)
+ x = int(round((2+size/(size+1))*size))
+ y = int(out["sysboxheight"] + out["borderthickness"])
+ mb = bbox(m)
+ sb = bbox(s)
+ xoff = sb[0] - mb[0] + x
+ yoff = sb[3] - mb[3] - y
+ overlay(m, xoff, yoff, s)
+ return s
+
+def lightning(size):
+ canvas = {}
+
+ # The lightning bolt motif.
+
+ # We always want this to be an even number of pixels in height,
+ # and an odd number in width.
+ width = round(7*size) * 2 - 1
+ height = round(8*size) * 2
+
+ # The outer edge of each side of the bolt goes to this point.
+ outery = round(8.4*size)
+ outerx = round(11*size)
+
+ # And the inner edge goes to this point.
+ innery = height - 1 - outery
+ innerx = round(7*size)
+
+ for y in range(int(height)):
+ list = []
+ if y <= outery:
+ list.append(width-1-int(outerx * float(y) / outery + 0.3))
+ if y <= innery:
+ list.append(width-1-int(innerx * float(y) / innery + 0.3))
+ y0 = height-1-y
+ if y0 <= outery:
+ list.append(int(outerx * float(y0) / outery + 0.3))
+ if y0 <= innery:
+ list.append(int(innerx * float(y0) / innery + 0.3))
+ list.sort()
+ for x in range(int(list[0]), int(list[-1]+1)):
+ pixel(x, y, cY, canvas)
+
+ # And draw a border.
+ border(canvas, size, [(int(width-1),0,TR), (0,int(height-1),BL)])
+
+ return canvas
+
+def document(size):
+ canvas = {}
+
+ # The document used in the PSCP/PSFTP icon.
+
+ width = round(13*size)
+ height = round(16*size)
+
+ lineht = round(1*size)
+ if lineht < 1: lineht = 1
+ linespc = round(0.7*size)
+ if linespc < 1: linespc = 1
+ nlines = int((height-linespc)/(lineht+linespc))
+ height = nlines*(lineht+linespc)+linespc # round this so it fits better
+
+ # Start by drawing a big white rectangle.
+ for y in range(int(height)):
+ for x in range(int(width)):
+ pixel(x, y, cW, canvas)
+
+ # Now draw lines of text.
+ for line in range(nlines):
+ # Decide where this line of text begins.
+ if line == 0:
+ start = round(4*size)
+ elif line < 5*nlines/7:
+ start = round((line - (nlines/7)) * size)
+ else:
+ start = round(1*size)
+ if start < round(1*size):
+ start = round(1*size)
+ # Decide where it ends.
+ endpoints = [10, 8, 11, 6, 5, 7, 5]
+ ey = line * 6.0 / (nlines-1)
+ eyf = math.floor(ey)
+ eyc = math.ceil(ey)
+ exf = endpoints[int(eyf)]
+ exc = endpoints[int(eyc)]
+ if eyf == eyc:
+ end = exf
+ else:
+ end = exf * (eyc-ey) + exc * (ey-eyf)
+ end = round(end * size)
+
+ liney = height - (lineht+linespc) * (line+1)
+ for x in range(int(start), int(end)):
+ for y in range(int(lineht)):
+ pixel(x, y+liney, cK, canvas)
+
+ # And draw a border.
+ border(canvas, size, \
+ [(0,0,TL),(int(width-1),0,TR),(0,int(height-1),BL), \
+ (int(width-1),int(height-1),BR)])
+
+ return canvas
+
+def hat(size):
+ canvas = {}
+
+ # The secret-agent hat in the Pageant icon.
+
+ topa = [6]*9+[5,3,1,0,0,1,2,2,1,1,1,9,9,10,10,11,11,12,12]
+ topa = [round(x*size) for x in topa]
+ botl = round(topa[0]+2.4*math.sqrt(size))
+ botr = round(topa[-1]+2.4*math.sqrt(size))
+ width = round(len(topa)*size)
+
+ # Line equations for the top and bottom of the hat brim, in the
+ # form y=mx+c. c, of course, needs scaling by size, but m is
+ # independent of size.
+ brimm = 1.0 / 3.75
+ brimtopc = round(4*size/3)
+ brimbotc = round(10*size/3)
+
+ for x in range(int(width)):
+ xs = float(x) * (len(topa)-1) / (width-1)
+ xf = math.floor(xs)
+ xc = math.ceil(xs)
+ topf = topa[int(xf)]
+ topc = topa[int(xc)]
+ if xf == xc:
+ top = topf
+ else:
+ top = topf * (xc-xs) + topc * (xs-xf)
+ top = math.floor(top)
+ bot = round(botl + (botr-botl) * x/(width-1))
+
+ for y in range(int(top), int(bot)):
+ pixel(x, y, cK, canvas)
+
+ # Now draw the brim.
+ for x in range(int(width)):
+ brimtop = brimtopc + brimm * x
+ brimbot = brimbotc + brimm * x
+ for y in range(int(math.floor(brimtop)), int(math.ceil(brimbot))):
+ tophere = max(min(brimtop - y, 1), 0)
+ bothere = max(min(brimbot - y, 1), 0)
+ grey = bothere - tophere
+ # Only draw brim pixels over pixels which are (a) part
+ # of the main hat, and (b) not right on its edge.
+ if canvas.has_key((x,y)) and \
+ canvas.has_key((x,y-1)) and \
+ canvas.has_key((x,y+1)) and \
+ canvas.has_key((x-1,y)) and \
+ canvas.has_key((x+1,y)):
+ pixel(x, y, greypix(grey), canvas)
+
+ return canvas
+
+def key(size):
+ canvas = {}
+
+ # The key in the PuTTYgen icon.
+
+ keyheadw = round(9.5*size)
+ keyheadh = round(12*size)
+ keyholed = round(4*size)
+ keyholeoff = round(2*size)
+ # Ensure keyheadh and keyshafth have the same parity.
+ keyshafth = round((2*size - (int(keyheadh)&1)) / 2) * 2 + (int(keyheadh)&1)
+ keyshaftw = round(18.5*size)
+ keyhead = [round(x*size) for x in [12,11,8,10,9,8,11,12]]
+
+ squarepix = []
+
+ # Ellipse for the key head, minus an off-centre circular hole.
+ for y in range(int(keyheadh)):
+ dy = (y-(keyheadh-1)/2.0) / (keyheadh/2.0)
+ dyh = (y-(keyheadh-1)/2.0) / (keyholed/2.0)
+ for x in range(int(keyheadw)):
+ dx = (x-(keyheadw-1)/2.0) / (keyheadw/2.0)
+ dxh = (x-(keyheadw-1)/2.0-keyholeoff) / (keyholed/2.0)
+ if dy*dy+dx*dx <= 1 and dyh*dyh+dxh*dxh > 1:
+ pixel(x + keyshaftw, y, cy, canvas)
+
+ # Rectangle for the key shaft, extended at the bottom for the
+ # key head detail.
+ for x in range(int(keyshaftw)):
+ top = round((keyheadh - keyshafth) / 2)
+ bot = round((keyheadh + keyshafth) / 2)
+ xs = float(x) * (len(keyhead)-1) / round((len(keyhead)-1)*size)
+ xf = math.floor(xs)
+ xc = math.ceil(xs)
+ in_head = 0
+ if xc < len(keyhead):
+ in_head = 1
+ yf = keyhead[int(xf)]
+ yc = keyhead[int(xc)]
+ if xf == xc:
+ bot = yf
+ else:
+ bot = yf * (xc-xs) + yc * (xs-xf)
+ for y in range(int(top),int(bot)):
+ pixel(x, y, cy, canvas)
+ if in_head:
+ last = (x, y)
+ if x == 0:
+ squarepix.append((x, int(top), TL))
+ if x == 0:
+ squarepix.append(last + (BL,))
+ if last != None and not in_head:
+ squarepix.append(last + (BR,))
+ last = None
+
+ # And draw a border.
+ border(canvas, size, squarepix)
+
+ return canvas
+
+def linedist(x1,y1, x2,y2, x,y):
+ # Compute the distance from the point x,y to the line segment
+ # joining x1,y1 to x2,y2. Returns the distance vector, measured
+ # with x,y at the origin.
+
+ vectors = []
+
+ # Special case: if x1,y1 and x2,y2 are the same point, we
+ # don't attempt to extrapolate it into a line at all.
+ if x1 != x2 or y1 != y2:
+ # First, find the nearest point to x,y on the infinite
+ # projection of the line segment. So we construct a vector
+ # n perpendicular to that segment...
+ nx = y2-y1
+ ny = x1-x2
+ # ... compute the dot product of (x1,y1)-(x,y) with that
+ # vector...
+ nd = (x1-x)*nx + (y1-y)*ny
+ # ... multiply by the vector we first thought of...
+ ndx = nd * nx
+ ndy = nd * ny
+ # ... and divide twice by the length of n.
+ ndx = ndx / (nx*nx+ny*ny)
+ ndy = ndy / (nx*nx+ny*ny)
+ # That gives us a displacement vector from x,y to the
+ # nearest point. See if it's within the range of the line
+ # segment.
+ cx = x + ndx
+ cy = y + ndy
+ if cx >= min(x1,x2) and cx <= max(x1,x2) and \
+ cy >= min(y1,y2) and cy <= max(y1,y2):
+ vectors.append((ndx,ndy))
+
+ # Now we have up to three candidate result vectors: (ndx,ndy)
+ # as computed just above, and the two vectors to the ends of
+ # the line segment, (x1-x,y1-y) and (x2-x,y2-y). Pick the
+ # shortest.
+ vectors = vectors + [(x1-x,y1-y), (x2-x,y2-y)]
+ bestlen, best = None, None
+ for v in vectors:
+ vlen = v[0]*v[0]+v[1]*v[1]
+ if bestlen == None or bestlen > vlen:
+ bestlen = vlen
+ best = v
+ return best
+
+def spanner(size):
+ canvas = {}
+
+ # The spanner in the config box icon.
+
+ headcentre = 0.5 + round(4*size)
+ headradius = headcentre + 0.1
+ headhighlight = round(1.5*size)
+ holecentre = 0.5 + round(3*size)
+ holeradius = round(2*size)
+ holehighlight = round(1.5*size)
+ shaftend = 0.5 + round(25*size)
+ shaftwidth = round(2*size)
+ shafthighlight = round(1.5*size)
+ cmax = shaftend + shaftwidth
+
+ # Define three line segments, such that the shortest distance
+ # vectors from any point to each of these segments determines
+ # everything we need to know about where it is on the spanner
+ # shape.
+ segments = [
+ ((0,0), (holecentre, holecentre)),
+ ((headcentre, headcentre), (headcentre, headcentre)),
+ ((headcentre+headradius/math.sqrt(2), headcentre+headradius/math.sqrt(2)),
+ (cmax, cmax))
+ ]
+
+ for y in range(int(cmax)):
+ for x in range(int(cmax)):
+ vectors = [linedist(a,b,c,d,x,y) for ((a,b),(c,d)) in segments]
+ dists = [memoisedsqrt(vx*vx+vy*vy) for (vx,vy) in vectors]
+
+ # If the distance to the hole line is less than
+ # holeradius, we're not part of the spanner.
+ if dists[0] < holeradius:
+ continue
+ # If the distance to the head `line' is less than
+ # headradius, we are part of the spanner; likewise if
+ # the distance to the shaft line is less than
+ # shaftwidth _and_ the resulting shaft point isn't
+ # beyond the shaft end.
+ if dists[1] > headradius and \
+ (dists[2] > shaftwidth or x+vectors[2][0] >= shaftend):
+ continue
+
+ # We're part of the spanner. Now compute the highlight
+ # on this pixel. We do this by computing a `slope
+ # vector', which points from this pixel in the
+ # direction of its nearest edge. We store an array of
+ # slope vectors, in polar coordinates.
+ angles = [math.atan2(vy,vx) for (vx,vy) in vectors]
+ slopes = []
+ if dists[0] < holeradius + holehighlight:
+ slopes.append(((dists[0]-holeradius)/holehighlight,angles[0]))
+ if dists[1]/headradius < dists[2]/shaftwidth:
+ if dists[1] > headradius - headhighlight and dists[1] < headradius:
+ slopes.append(((headradius-dists[1])/headhighlight,math.pi+angles[1]))
+ else:
+ if dists[2] > shaftwidth - shafthighlight and dists[2] < shaftwidth:
+ slopes.append(((shaftwidth-dists[2])/shafthighlight,math.pi+angles[2]))
+ # Now we find the smallest distance in that array, if
+ # any, and that gives us a notional position on a
+ # sphere which we can use to compute the final
+ # highlight level.
+ bestdist = None
+ bestangle = 0
+ for dist, angle in slopes:
+ if bestdist == None or bestdist > dist:
+ bestdist = dist
+ bestangle = angle
+ if bestdist == None:
+ bestdist = 1.0
+ sx = (1.0-bestdist) * math.cos(bestangle)
+ sy = (1.0-bestdist) * math.sin(bestangle)
+ sz = math.sqrt(1.0 - sx*sx - sy*sy)
+ shade = sx-sy+sz / math.sqrt(3) # can range from -1 to +1
+ shade = 1.0 - (1-shade)/3
+
+ pixel(x, y, yellowpix(shade), canvas)
+
+ # And draw a border.
+ border(canvas, size, [])
+
+ return canvas
+
+def box(size, back):
+ canvas = {}
+
+ # The back side of the cardboard box in the installer icon.
+
+ boxwidth = round(15 * size)
+ boxheight = round(12 * size)
+ boxdepth = round(4 * size)
+ boxfrontflapheight = round(5 * size)
+ boxrightflapheight = round(3 * size)
+
+ # Three shades of basically acceptable brown, all achieved by
+ # halftoning between two of the Windows-16 colours. I'm quite
+ # pleased that was feasible at all!
+ dark = halftone(cr, cK)
+ med = halftone(cr, cy)
+ light = halftone(cr, cY)
+ # We define our halftoning parity in such a way that the black
+ # pixels along the RHS of the visible part of the box back
+ # match up with the one-pixel black outline around the
+ # right-hand side of the box. In other words, we want the pixel
+ # at (-1, boxwidth-1) to be black, and hence the one at (0,
+ # boxwidth) too.
+ parityadjust = int(boxwidth) % 2
+
+ # The entire back of the box.
+ if back:
+ for x in range(int(boxwidth + boxdepth)):
+ ytop = max(-x-1, -boxdepth-1)
+ ybot = min(boxheight, boxheight+boxwidth-1-x)
+ for y in range(int(ytop), int(ybot)):
+ pixel(x, y, dark[(x+y+parityadjust) % 2], canvas)
+
+ # Even when drawing the back of the box, we still draw the
+ # whole shape, because that means we get the right overall size
+ # (the flaps make the box front larger than the box back) and
+ # it'll all be overwritten anyway.
+
+ # The front face of the box.
+ for x in range(int(boxwidth)):
+ for y in range(int(boxheight)):
+ pixel(x, y, med[(x+y+parityadjust) % 2], canvas)
+ # The right face of the box.
+ for x in range(int(boxwidth), int(boxwidth+boxdepth)):
+ ybot = boxheight + boxwidth-x
+ ytop = ybot - boxheight
+ for y in range(int(ytop), int(ybot)):
+ pixel(x, y, dark[(x+y+parityadjust) % 2], canvas)
+ # The front flap of the box.
+ for y in range(int(boxfrontflapheight)):
+ xadj = int(round(-0.5*y))
+ for x in range(int(xadj), int(xadj+boxwidth)):
+ pixel(x, y, light[(x+y+parityadjust) % 2], canvas)
+ # The right flap of the box.
+ for x in range(int(boxwidth), int(boxwidth + boxdepth + boxrightflapheight + 1)):
+ ytop = max(boxwidth - 1 - x, x - boxwidth - 2*boxdepth - 1)
+ ybot = min(x - boxwidth - 1, boxwidth + 2*boxrightflapheight - 1 - x)
+ for y in range(int(ytop), int(ybot+1)):
+ pixel(x, y, med[(x+y+parityadjust) % 2], canvas)
+
+ # And draw a border.
+ border(canvas, size, [(0, int(boxheight)-1, BL)])
+
+ return canvas
+
+def boxback(size):
+ return box(size, 1)
+def boxfront(size):
+ return box(size, 0)
+
+# Functions to draw entire icons by composing the above components.
+
+def xybolt(c1, c2, size, boltoffx=0, boltoffy=0, aux={}):
+ # Two unspecified objects and a lightning bolt.
+
+ canvas = {}
+ w = h = round(32 * size)
+
+ bolt = lightning(size)
+
+ # Position c2 against the top right of the icon.
+ bb = bbox(c2)
+ assert bb[2]-bb[0] <= w and bb[3]-bb[1] <= h
+ overlay(c2, w-bb[2], 0-bb[1], canvas)
+ aux["c2pos"] = (w-bb[2], 0-bb[1])
+ # Position c1 against the bottom left of the icon.
+ bb = bbox(c1)
+ assert bb[2]-bb[0] <= w and bb[3]-bb[1] <= h
+ overlay(c1, 0-bb[0], h-bb[3], canvas)
+ aux["c1pos"] = (0-bb[0], h-bb[3])
+ # Place the lightning bolt artistically off-centre. (The
+ # rationale for this positioning is that it's centred on the
+ # midpoint between the centres of the two monitors in the PuTTY
+ # icon proper, but it's not really feasible to _base_ the
+ # calculation here on that.)
+ bb = bbox(bolt)
+ assert bb[2]-bb[0] <= w and bb[3]-bb[1] <= h
+ overlay(bolt, (w-bb[0]-bb[2])/2 + round(boltoffx*size), \
+ (h-bb[1]-bb[3])/2 + round((boltoffy-2)*size), canvas)
+
+ return canvas
+
+def putty_icon(size):
+ return xybolt(computer(size), computer(size), size)
+
+def puttycfg_icon(size):
+ w = h = round(32 * size)
+ s = spanner(size)
+ canvas = putty_icon(size)
+ # Centre the spanner.
+ bb = bbox(s)
+ overlay(s, (w-bb[0]-bb[2])/2, (h-bb[1]-bb[3])/2, canvas)
+ return canvas
+
+def puttygen_icon(size):
+ return xybolt(computer(size), key(size), size, boltoffx=2)
+
+def pscp_icon(size):
+ return xybolt(document(size), computer(size), size)
+
+def puttyins_icon(size):
+ aret = {}
+ # The box back goes behind the lightning bolt.
+ canvas = xybolt(boxback(size), computer(size), size, boltoffx=-2, boltoffy=+1, aux=aret)
+ # But the box front goes over the top, so that the lightning
+ # bolt appears to come _out_ of the box. Here it's useful to
+ # know the exact coordinates where xybolt placed the box back,
+ # so we can overlay the box front exactly on top of it.
+ c1x, c1y = aret["c1pos"]
+ overlay(boxfront(size), c1x, c1y, canvas)
+ return canvas
+
+def pterm_icon(size):
+ # Just a really big computer.
+
+ canvas = {}
+ w = h = round(32 * size)
+
+ c = computer(size * 1.4)
+
+ # Centre c in the return canvas.
+ bb = bbox(c)
+ assert bb[2]-bb[0] <= w and bb[3]-bb[1] <= h
+ overlay(c, (w-bb[0]-bb[2])/2, (h-bb[1]-bb[3])/2, canvas)
+
+ return canvas
+
+def ptermcfg_icon(size):
+ w = h = round(32 * size)
+ s = spanner(size)
+ canvas = pterm_icon(size)
+ # Centre the spanner.
+ bb = bbox(s)
+ overlay(s, (w-bb[0]-bb[2])/2, (h-bb[1]-bb[3])/2, canvas)
+ return canvas
+
+def pageant_icon(size):
+ # A biggish computer, in a hat.
+
+ canvas = {}
+ w = h = round(32 * size)
+
+ c = computer(size * 1.2)
+ ht = hat(size)
+
+ cbb = bbox(c)
+ hbb = bbox(ht)
+
+ # Determine the relative y-coordinates of the computer and hat.
+ # We just centre the one on the other.
+ xrel = (cbb[0]+cbb[2]-hbb[0]-hbb[2])/2
+
+ # Determine the relative y-coordinates of the computer and hat.
+ # We do this by sitting the hat as low down on the computer as
+ # possible without any computer showing over the top. To do
+ # this we first have to find the minimum x coordinate at each
+ # y-coordinate of both components.
+ cty = topy(c)
+ hty = topy(ht)
+ yrelmin = None
+ for cx in cty.keys():
+ hx = cx - xrel
+ assert hty.has_key(hx)
+ yrel = cty[cx] - hty[hx]
+ if yrelmin == None:
+ yrelmin = yrel
+ else:
+ yrelmin = min(yrelmin, yrel)
+
+ # Overlay the hat on the computer.
+ overlay(ht, xrel, yrelmin, c)
+
+ # And centre the result in the main icon canvas.
+ bb = bbox(c)
+ assert bb[2]-bb[0] <= w and bb[3]-bb[1] <= h
+ overlay(c, (w-bb[0]-bb[2])/2, (h-bb[1]-bb[3])/2, canvas)
+
+ return canvas
+
+# Test and output functions.
+
+import os
+import sys
+
+def testrun(func, fname):
+ canvases = []
+ for size in [0.5, 0.6, 1.0, 1.2, 1.5, 4.0]:
+ canvases.append(func(size))
+ wid = 0
+ ht = 0
+ for canvas in canvases:
+ minx, miny, maxx, maxy = bbox(canvas)
+ wid = max(wid, maxx-minx+4)
+ ht = ht + maxy-miny+4
+ block = []
+ for canvas in canvases:
+ minx, miny, maxx, maxy = bbox(canvas)
+ block.extend(render(canvas, minx-2, miny-2, minx-2+wid, maxy+2))
+ p = os.popen("convert -depth 8 -size %dx%d rgb:- %s" % (wid,ht,fname), "w")
+ assert len(block) == ht
+ for line in block:
+ assert len(line) == wid
+ for r, g, b, a in line:
+ # Composite on to orange.
+ r = int(round((r * a + 255 * (255-a)) / 255.0))
+ g = int(round((g * a + 128 * (255-a)) / 255.0))
+ b = int(round((b * a + 0 * (255-a)) / 255.0))
+ p.write("%c%c%c" % (r,g,b))
+ p.close()
+
+def drawicon(func, width, fname, orangebackground = 0):
+ canvas = func(width / 32.0)
+ finalise(canvas)
+ minx, miny, maxx, maxy = bbox(canvas)
+ assert minx >= 0 and miny >= 0 and maxx <= width and maxy <= width
+
+ block = render(canvas, 0, 0, width, width)
+ p = os.popen("convert -depth 8 -size %dx%d rgba:- %s" % (width,width,fname), "w")
+ assert len(block) == width
+ for line in block:
+ assert len(line) == width
+ for r, g, b, a in line:
+ if orangebackground:
+ # Composite on to orange.
+ r = int(round((r * a + 255 * (255-a)) / 255.0))
+ g = int(round((g * a + 128 * (255-a)) / 255.0))
+ b = int(round((b * a + 0 * (255-a)) / 255.0))
+ a = 255
+ p.write("%c%c%c%c" % (r,g,b,a))
+ p.close()
+
+args = sys.argv[1:]
+
+orangebackground = test = 0
+colours = 1 # 0=mono, 1=16col, 2=truecol
+doingargs = 1
+
+realargs = []
+for arg in args:
+ if doingargs and arg[0] == "-":
+ if arg == "-t":
+ test = 1
+ elif arg == "-it":
+ orangebackground = 1
+ elif arg == "-2":
+ colours = 0
+ elif arg == "-T":
+ colours = 2
+ elif arg == "--":
+ doingargs = 0
+ else:
+ sys.stderr.write("unrecognised option '%s'\n" % arg)
+ sys.exit(1)
+ else:
+ realargs.append(arg)
+
+if colours == 0:
+ # Monochrome.
+ cK=cr=cg=cb=cm=cc=cP=cw=cR=cG=cB=cM=cC=cD = 0
+ cY=cy=cW = 1
+ cT = -1
+ def greypix(value):
+ return [cK,cW][int(round(value))]
+ def yellowpix(value):
+ return [cK,cW][int(round(value))]
+ def bluepix(value):
+ return cK
+ def dark(value):
+ return [cT,cK][int(round(value))]
+ def blend(col1, col2):
+ if col1 == cT:
+ return col2
+ else:
+ return col1
+ pixvals = [
+ (0x00, 0x00, 0x00, 0xFF), # cK
+ (0xFF, 0xFF, 0xFF, 0xFF), # cW
+ (0x00, 0x00, 0x00, 0x00), # cT
+ ]
+ def outpix(colour):
+ return pixvals[colour]
+ def finalisepix(colour):
+ return colour
+ def halftone(col1, col2):
+ return (col1, col2)
+elif colours == 1:
+ # Windows 16-colour palette.
+ cK,cr,cg,cy,cb,cm,cc,cP,cw,cR,cG,cY,cB,cM,cC,cW = range(16)
+ cT = -1
+ cD = -2 # special translucent half-darkening value used internally
+ def greypix(value):
+ return [cK,cw,cw,cP,cW][int(round(4*value))]
+ def yellowpix(value):
+ return [cK,cy,cY][int(round(2*value))]
+ def bluepix(value):
+ return [cK,cb,cB][int(round(2*value))]
+ def dark(value):
+ return [cT,cD,cK][int(round(2*value))]
+ def blend(col1, col2):
+ if col1 == cT:
+ return col2
+ elif col1 == cD:
+ return [cK,cK,cK,cK,cK,cK,cK,cw,cK,cr,cg,cy,cb,cm,cc,cw,cD,cD][col2]
+ else:
+ return col1
+ pixvals = [
+ (0x00, 0x00, 0x00, 0xFF), # cK
+ (0x80, 0x00, 0x00, 0xFF), # cr
+ (0x00, 0x80, 0x00, 0xFF), # cg
+ (0x80, 0x80, 0x00, 0xFF), # cy
+ (0x00, 0x00, 0x80, 0xFF), # cb
+ (0x80, 0x00, 0x80, 0xFF), # cm
+ (0x00, 0x80, 0x80, 0xFF), # cc
+ (0xC0, 0xC0, 0xC0, 0xFF), # cP
+ (0x80, 0x80, 0x80, 0xFF), # cw
+ (0xFF, 0x00, 0x00, 0xFF), # cR
+ (0x00, 0xFF, 0x00, 0xFF), # cG
+ (0xFF, 0xFF, 0x00, 0xFF), # cY
+ (0x00, 0x00, 0xFF, 0xFF), # cB
+ (0xFF, 0x00, 0xFF, 0xFF), # cM
+ (0x00, 0xFF, 0xFF, 0xFF), # cC
+ (0xFF, 0xFF, 0xFF, 0xFF), # cW
+ (0x00, 0x00, 0x00, 0x80), # cD
+ (0x00, 0x00, 0x00, 0x00), # cT
+ ]
+ def outpix(colour):
+ return pixvals[colour]
+ def finalisepix(colour):
+ # cD is used internally, but can't be output. Convert to cK.
+ if colour == cD:
+ return cK
+ return colour
+ def halftone(col1, col2):
+ return (col1, col2)
+else:
+ # True colour.
+ cK = (0x00, 0x00, 0x00, 0xFF)
+ cr = (0x80, 0x00, 0x00, 0xFF)
+ cg = (0x00, 0x80, 0x00, 0xFF)
+ cy = (0x80, 0x80, 0x00, 0xFF)
+ cb = (0x00, 0x00, 0x80, 0xFF)
+ cm = (0x80, 0x00, 0x80, 0xFF)
+ cc = (0x00, 0x80, 0x80, 0xFF)
+ cP = (0xC0, 0xC0, 0xC0, 0xFF)
+ cw = (0x80, 0x80, 0x80, 0xFF)
+ cR = (0xFF, 0x00, 0x00, 0xFF)
+ cG = (0x00, 0xFF, 0x00, 0xFF)
+ cY = (0xFF, 0xFF, 0x00, 0xFF)
+ cB = (0x00, 0x00, 0xFF, 0xFF)
+ cM = (0xFF, 0x00, 0xFF, 0xFF)
+ cC = (0x00, 0xFF, 0xFF, 0xFF)
+ cW = (0xFF, 0xFF, 0xFF, 0xFF)
+ cD = (0x00, 0x00, 0x00, 0x80)
+ cT = (0x00, 0x00, 0x00, 0x00)
+ def greypix(value):
+ value = max(min(value, 1), 0)
+ return (int(round(0xFF*value)),) * 3 + (0xFF,)
+ def yellowpix(value):
+ value = max(min(value, 1), 0)
+ return (int(round(0xFF*value)),) * 2 + (0, 0xFF)
+ def bluepix(value):
+ value = max(min(value, 1), 0)
+ return (0, 0, int(round(0xFF*value)), 0xFF)
+ def dark(value):
+ value = max(min(value, 1), 0)
+ return (0, 0, 0, int(round(0xFF*value)))
+ def blend(col1, col2):
+ r1,g1,b1,a1 = col1
+ r2,g2,b2,a2 = col2
+ r = int(round((r1*a1 + r2*(0xFF-a1)) / 255.0))
+ g = int(round((g1*a1 + g2*(0xFF-a1)) / 255.0))
+ b = int(round((b1*a1 + b2*(0xFF-a1)) / 255.0))
+ a = int(round((255*a1 + a2*(0xFF-a1)) / 255.0))
+ return r, g, b, a
+ def outpix(colour):
+ return colour
+ if colours == 2:
+ # True colour with no alpha blending: we still have to
+ # finalise half-dark pixels to black.
+ def finalisepix(colour):
+ if colour[3] > 0:
+ return colour[:3] + (0xFF,)
+ return colour
+ else:
+ def finalisepix(colour):
+ return colour
+ def halftone(col1, col2):
+ r1,g1,b1,a1 = col1
+ r2,g2,b2,a2 = col2
+ colret = (int(r1+r2)/2, int(g1+g2)/2, int(b1+b2)/2, int(a1+a2)/2)
+ return (colret, colret)
+
+if test:
+ testrun(eval(realargs[0]), realargs[1])
+else:
+ drawicon(eval(realargs[0]), int(realargs[1]), realargs[2], orangebackground)
--- /dev/null
+/*
+ * Code for PuTTY to import and export private key files in other
+ * SSH clients' formats.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <ctype.h>
+
+#include "putty.h"
+#include "ssh.h"
+#include "misc.h"
+
+int openssh_encrypted(const Filename *filename);
+struct ssh2_userkey *openssh_read(const Filename *filename, char *passphrase,
+ const char **errmsg_p);
+int openssh_write(const Filename *filename, struct ssh2_userkey *key,
+ char *passphrase);
+
+int sshcom_encrypted(const Filename *filename, char **comment);
+struct ssh2_userkey *sshcom_read(const Filename *filename, char *passphrase,
+ const char **errmsg_p);
+int sshcom_write(const Filename *filename, struct ssh2_userkey *key,
+ char *passphrase);
+
+/*
+ * Given a key type, determine whether we know how to import it.
+ */
+int import_possible(int type)
+{
+ if (type == SSH_KEYTYPE_OPENSSH)
+ return 1;
+ if (type == SSH_KEYTYPE_SSHCOM)
+ return 1;
+ return 0;
+}
+
+/*
+ * Given a key type, determine what native key type
+ * (SSH_KEYTYPE_SSH1 or SSH_KEYTYPE_SSH2) it will come out as once
+ * we've imported it.
+ */
+int import_target_type(int type)
+{
+ /*
+ * There are no known foreign SSH-1 key formats.
+ */
+ return SSH_KEYTYPE_SSH2;
+}
+
+/*
+ * Determine whether a foreign key is encrypted.
+ */
+int import_encrypted(const Filename *filename, int type, char **comment)
+{
+ if (type == SSH_KEYTYPE_OPENSSH) {
+ /* OpenSSH doesn't do key comments */
+ *comment = dupstr(filename_to_str(filename));
+ return openssh_encrypted(filename);
+ }
+ if (type == SSH_KEYTYPE_SSHCOM) {
+ return sshcom_encrypted(filename, comment);
+ }
+ return 0;
+}
+
+/*
+ * Import an SSH-1 key.
+ */
+int import_ssh1(const Filename *filename, int type,
+ struct RSAKey *key, char *passphrase, const char **errmsg_p)
+{
+ return 0;
+}
+
+/*
+ * Import an SSH-2 key.
+ */
+struct ssh2_userkey *import_ssh2(const Filename *filename, int type,
+ char *passphrase, const char **errmsg_p)
+{
+ if (type == SSH_KEYTYPE_OPENSSH)
+ return openssh_read(filename, passphrase, errmsg_p);
+ if (type == SSH_KEYTYPE_SSHCOM)
+ return sshcom_read(filename, passphrase, errmsg_p);
+ return NULL;
+}
+
+/*
+ * Export an SSH-1 key.
+ */
+int export_ssh1(const Filename *filename, int type, struct RSAKey *key,
+ char *passphrase)
+{
+ return 0;
+}
+
+/*
+ * Export an SSH-2 key.
+ */
+int export_ssh2(const Filename *filename, int type,
+ struct ssh2_userkey *key, char *passphrase)
+{
+ if (type == SSH_KEYTYPE_OPENSSH)
+ return openssh_write(filename, key, passphrase);
+ if (type == SSH_KEYTYPE_SSHCOM)
+ return sshcom_write(filename, key, passphrase);
+ return 0;
+}
+
+/*
+ * Strip trailing CRs and LFs at the end of a line of text.
+ */
+void strip_crlf(char *str)
+{
+ char *p = str + strlen(str);
+
+ while (p > str && (p[-1] == '\r' || p[-1] == '\n'))
+ *--p = '\0';
+}
+
+/* ----------------------------------------------------------------------
+ * Helper routines. (The base64 ones are defined in sshpubk.c.)
+ */
+
+#define isbase64(c) ( ((c) >= 'A' && (c) <= 'Z') || \
+ ((c) >= 'a' && (c) <= 'z') || \
+ ((c) >= '0' && (c) <= '9') || \
+ (c) == '+' || (c) == '/' || (c) == '=' \
+ )
+
+/*
+ * Read an ASN.1/BER identifier and length pair.
+ *
+ * Flags are a combination of the #defines listed below.
+ *
+ * Returns -1 if unsuccessful; otherwise returns the number of
+ * bytes used out of the source data.
+ */
+
+/* ASN.1 tag classes. */
+#define ASN1_CLASS_UNIVERSAL (0 << 6)
+#define ASN1_CLASS_APPLICATION (1 << 6)
+#define ASN1_CLASS_CONTEXT_SPECIFIC (2 << 6)
+#define ASN1_CLASS_PRIVATE (3 << 6)
+#define ASN1_CLASS_MASK (3 << 6)
+
+/* Primitive versus constructed bit. */
+#define ASN1_CONSTRUCTED (1 << 5)
+
+static int ber_read_id_len(void *source, int sourcelen,
+ int *id, int *length, int *flags)
+{
+ unsigned char *p = (unsigned char *) source;
+
+ if (sourcelen == 0)
+ return -1;
+
+ *flags = (*p & 0xE0);
+ if ((*p & 0x1F) == 0x1F) {
+ *id = 0;
+ while (*p & 0x80) {
+ p++, sourcelen--;
+ if (sourcelen == 0)
+ return -1;
+ *id = (*id << 7) | (*p & 0x7F);
+ }
+ p++, sourcelen--;
+ } else {
+ *id = *p & 0x1F;
+ p++, sourcelen--;
+ }
+
+ if (sourcelen == 0)
+ return -1;
+
+ if (*p & 0x80) {
+ int n = *p & 0x7F;
+ p++, sourcelen--;
+ if (sourcelen < n)
+ return -1;
+ *length = 0;
+ while (n--)
+ *length = (*length << 8) | (*p++);
+ sourcelen -= n;
+ } else {
+ *length = *p;
+ p++, sourcelen--;
+ }
+
+ return p - (unsigned char *) source;
+}
+
+/*
+ * Write an ASN.1/BER identifier and length pair. Returns the
+ * number of bytes consumed. Assumes dest contains enough space.
+ * Will avoid writing anything if dest is NULL, but still return
+ * amount of space required.
+ */
+static int ber_write_id_len(void *dest, int id, int length, int flags)
+{
+ unsigned char *d = (unsigned char *)dest;
+ int len = 0;
+
+ if (id <= 30) {
+ /*
+ * Identifier is one byte.
+ */
+ len++;
+ if (d) *d++ = id | flags;
+ } else {
+ int n;
+ /*
+ * Identifier is multiple bytes: the first byte is 11111
+ * plus the flags, and subsequent bytes encode the value of
+ * the identifier, 7 bits at a time, with the top bit of
+ * each byte 1 except the last one which is 0.
+ */
+ len++;
+ if (d) *d++ = 0x1F | flags;
+ for (n = 1; (id >> (7*n)) > 0; n++)
+ continue; /* count the bytes */
+ while (n--) {
+ len++;
+ if (d) *d++ = (n ? 0x80 : 0) | ((id >> (7*n)) & 0x7F);
+ }
+ }
+
+ if (length < 128) {
+ /*
+ * Length is one byte.
+ */
+ len++;
+ if (d) *d++ = length;
+ } else {
+ int n;
+ /*
+ * Length is multiple bytes. The first is 0x80 plus the
+ * number of subsequent bytes, and the subsequent bytes
+ * encode the actual length.
+ */
+ for (n = 1; (length >> (8*n)) > 0; n++)
+ continue; /* count the bytes */
+ len++;
+ if (d) *d++ = 0x80 | n;
+ while (n--) {
+ len++;
+ if (d) *d++ = (length >> (8*n)) & 0xFF;
+ }
+ }
+
+ return len;
+}
+
+static int put_string(void *target, void *data, int len)
+{
+ unsigned char *d = (unsigned char *)target;
+
+ PUT_32BIT(d, len);
+ memcpy(d+4, data, len);
+ return len+4;
+}
+
+static int put_mp(void *target, void *data, int len)
+{
+ unsigned char *d = (unsigned char *)target;
+ unsigned char *i = (unsigned char *)data;
+
+ if (*i & 0x80) {
+ PUT_32BIT(d, len+1);
+ d[4] = 0;
+ memcpy(d+5, data, len);
+ return len+5;
+ } else {
+ PUT_32BIT(d, len);
+ memcpy(d+4, data, len);
+ return len+4;
+ }
+}
+
+/* Simple structure to point to an mp-int within a blob. */
+struct mpint_pos { void *start; int bytes; };
+
+static int ssh2_read_mpint(void *data, int len, struct mpint_pos *ret)
+{
+ int bytes;
+ unsigned char *d = (unsigned char *) data;
+
+ if (len < 4)
+ goto error;
+ bytes = GET_32BIT(d);
+ if (len < 4+bytes)
+ goto error;
+
+ ret->start = d + 4;
+ ret->bytes = bytes;
+ return bytes+4;
+
+ error:
+ ret->start = NULL;
+ ret->bytes = -1;
+ return len; /* ensure further calls fail as well */
+}
+
+/* ----------------------------------------------------------------------
+ * Code to read and write OpenSSH private keys.
+ */
+
+enum { OSSH_DSA, OSSH_RSA };
+enum { OSSH_ENC_3DES, OSSH_ENC_AES };
+struct openssh_key {
+ int type;
+ int encrypted, encryption;
+ char iv[32];
+ unsigned char *keyblob;
+ int keyblob_len, keyblob_size;
+};
+
+static struct openssh_key *load_openssh_key(const Filename *filename,
+ const char **errmsg_p)
+{
+ struct openssh_key *ret;
+ FILE *fp;
+ char *line = NULL;
+ char *errmsg, *p;
+ int headers_done;
+ char base64_bit[4];
+ int base64_chars = 0;
+
+ ret = snew(struct openssh_key);
+ 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);
+ if (!fp) {
+ errmsg = "unable to open key file";
+ goto error;
+ }
+
+ if (!(line = fgetline(fp))) {
+ errmsg = "unexpected end of file";
+ goto error;
+ }
+ strip_crlf(line);
+ if (0 != strncmp(line, "-----BEGIN ", 11) ||
+ 0 != strcmp(line+strlen(line)-16, "PRIVATE KEY-----")) {
+ errmsg = "file does not begin with OpenSSH key header";
+ goto error;
+ }
+ if (!strcmp(line, "-----BEGIN RSA PRIVATE KEY-----"))
+ ret->type = OSSH_RSA;
+ else if (!strcmp(line, "-----BEGIN DSA PRIVATE KEY-----"))
+ ret->type = OSSH_DSA;
+ else {
+ errmsg = "unrecognised key type";
+ goto error;
+ }
+ memset(line, 0, strlen(line));
+ sfree(line);
+ line = NULL;
+
+ headers_done = 0;
+ while (1) {
+ if (!(line = fgetline(fp))) {
+ errmsg = "unexpected end of file";
+ goto error;
+ }
+ strip_crlf(line);
+ if (0 == strncmp(line, "-----END ", 9) &&
+ 0 == strcmp(line+strlen(line)-16, "PRIVATE KEY-----"))
+ break; /* done */
+ if ((p = strchr(line, ':')) != NULL) {
+ if (headers_done) {
+ errmsg = "header found in body of key data";
+ goto error;
+ }
+ *p++ = '\0';
+ while (*p && isspace((unsigned char)*p)) p++;
+ if (!strcmp(line, "Proc-Type")) {
+ if (p[0] != '4' || p[1] != ',') {
+ errmsg = "Proc-Type is not 4 (only 4 is supported)";
+ goto error;
+ }
+ p += 2;
+ if (!strcmp(p, "ENCRYPTED"))
+ ret->encrypted = 1;
+ } else if (!strcmp(line, "DEK-Info")) {
+ int i, j, ivlen;
+
+ if (!strncmp(p, "DES-EDE3-CBC,", 13)) {
+ ret->encryption = OSSH_ENC_3DES;
+ ivlen = 8;
+ } else if (!strncmp(p, "AES-128-CBC,", 12)) {
+ ret->encryption = OSSH_ENC_AES;
+ ivlen = 16;
+ } else {
+ errmsg = "unsupported cipher";
+ goto error;
+ }
+ p = strchr(p, ',') + 1;/* always non-NULL, by above checks */
+ for (i = 0; i < ivlen; i++) {
+ if (1 != sscanf(p, "%2x", &j)) {
+ errmsg = "expected more iv data in DEK-Info";
+ goto error;
+ }
+ ret->iv[i] = j;
+ p += 2;
+ }
+ if (*p) {
+ errmsg = "more iv data than expected in DEK-Info";
+ goto error;
+ }
+ }
+ } else {
+ headers_done = 1;
+
+ p = line;
+ while (isbase64(*p)) {
+ base64_bit[base64_chars++] = *p;
+ if (base64_chars == 4) {
+ unsigned char out[3];
+ int len;
+
+ base64_chars = 0;
+
+ len = base64_decode_atom(base64_bit, out);
+
+ if (len <= 0) {
+ errmsg = "invalid base64 encoding";
+ goto error;
+ }
+
+ if (ret->keyblob_len + len > ret->keyblob_size) {
+ ret->keyblob_size = ret->keyblob_len + len + 256;
+ ret->keyblob = sresize(ret->keyblob, ret->keyblob_size,
+ unsigned char);
+ }
+
+ memcpy(ret->keyblob + ret->keyblob_len, out, len);
+ ret->keyblob_len += len;
+
+ memset(out, 0, sizeof(out));
+ }
+
+ p++;
+ }
+ }
+ memset(line, 0, strlen(line));
+ sfree(line);
+ line = NULL;
+ }
+
+ if (ret->keyblob_len == 0 || !ret->keyblob) {
+ errmsg = "key body not present";
+ goto error;
+ }
+
+ 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));
+ if (errmsg_p) *errmsg_p = NULL;
+ return ret;
+
+ error:
+ if (line) {
+ memset(line, 0, strlen(line));
+ sfree(line);
+ line = NULL;
+ }
+ memset(base64_bit, 0, sizeof(base64_bit));
+ if (ret) {
+ if (ret->keyblob) {
+ memset(ret->keyblob, 0, ret->keyblob_size);
+ sfree(ret->keyblob);
+ }
+ memset(ret, 0, sizeof(*ret));
+ sfree(ret);
+ }
+ if (errmsg_p) *errmsg_p = errmsg;
+ return NULL;
+}
+
+int openssh_encrypted(const Filename *filename)
+{
+ struct openssh_key *key = load_openssh_key(filename, NULL);
+ int ret;
+
+ if (!key)
+ return 0;
+ ret = key->encrypted;
+ memset(key->keyblob, 0, key->keyblob_size);
+ sfree(key->keyblob);
+ memset(key, 0, sizeof(*key));
+ sfree(key);
+ return ret;
+}
+
+struct ssh2_userkey *openssh_read(const Filename *filename, char *passphrase,
+ const char **errmsg_p)
+{
+ struct openssh_key *key = load_openssh_key(filename, errmsg_p);
+ struct ssh2_userkey *retkey;
+ unsigned char *p;
+ int ret, id, len, flags;
+ int i, num_integers;
+ struct ssh2_userkey *retval = NULL;
+ char *errmsg;
+ unsigned char *blob;
+ int blobsize = 0, blobptr, privptr;
+ char *modptr = NULL;
+ int modlen = 0;
+
+ blob = NULL;
+
+ if (!key)
+ return NULL;
+
+ if (key->encrypted) {
+ /*
+ * Derive encryption key from passphrase and iv/salt:
+ *
+ * - let block A equal MD5(passphrase || iv)
+ * - let block B equal MD5(A || passphrase || iv)
+ * - block C would be MD5(B || passphrase || iv) and so on
+ * - encryption key is the first N bytes of A || B
+ *
+ * (Note that only 8 bytes of the iv are used for key
+ * derivation, even when the key is encrypted with AES and
+ * hence there are 16 bytes available.)
+ */
+ struct MD5Context md5c;
+ unsigned char keybuf[32];
+
+ MD5Init(&md5c);
+ MD5Update(&md5c, (unsigned char *)passphrase, strlen(passphrase));
+ MD5Update(&md5c, (unsigned char *)key->iv, 8);
+ MD5Final(keybuf, &md5c);
+
+ MD5Init(&md5c);
+ MD5Update(&md5c, keybuf, 16);
+ MD5Update(&md5c, (unsigned char *)passphrase, strlen(passphrase));
+ MD5Update(&md5c, (unsigned char *)key->iv, 8);
+ MD5Final(keybuf+16, &md5c);
+
+ /*
+ * Now decrypt the key blob.
+ */
+ if (key->encryption == OSSH_ENC_3DES)
+ des3_decrypt_pubkey_ossh(keybuf, (unsigned char *)key->iv,
+ key->keyblob, key->keyblob_len);
+ else {
+ void *ctx;
+ assert(key->encryption == OSSH_ENC_AES);
+ ctx = aes_make_context();
+ aes128_key(ctx, keybuf);
+ 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));
+ }
+
+ /*
+ * Now we have a decrypted key blob, which contains an ASN.1
+ * encoded private key. We must now untangle the ASN.1.
+ *
+ * We expect the whole key blob to be formatted as a SEQUENCE
+ * (0x30 followed by a length code indicating that the rest of
+ * the blob is part of the sequence). Within that SEQUENCE we
+ * expect to see a bunch of INTEGERs. What those integers mean
+ * depends on the key type:
+ *
+ * - For RSA, we expect the integers to be 0, n, e, d, p, q,
+ * dmp1, dmq1, iqmp in that order. (The last three are d mod
+ * (p-1), d mod (q-1), inverse of q mod p respectively.)
+ *
+ * - For DSA, we expect them to be 0, p, q, g, y, x in that
+ * order.
+ */
+
+ p = key->keyblob;
+
+ /* Expect the SEQUENCE header. Take its absence as a failure to decrypt. */
+ 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;
+ goto error;
+ }
+
+ /* Expect a load of INTEGERs. */
+ if (key->type == OSSH_RSA)
+ num_integers = 9;
+ else if (key->type == OSSH_DSA)
+ num_integers = 6;
+ else
+ num_integers = 0; /* placate compiler warnings */
+
+ /*
+ * Space to create key blob in.
+ */
+ blobsize = 256+key->keyblob_len;
+ blob = snewn(blobsize, unsigned char);
+ PUT_32BIT(blob, 7);
+ if (key->type == OSSH_DSA)
+ memcpy(blob+4, "ssh-dss", 7);
+ else if (key->type == OSSH_RSA)
+ memcpy(blob+4, "ssh-rsa", 7);
+ blobptr = 4+7;
+ privptr = -1;
+
+ for (i = 0; i < num_integers; i++) {
+ ret = ber_read_id_len(p, key->keyblob+key->keyblob_len-p,
+ &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;
+ goto error;
+ }
+
+ if (i == 0) {
+ /*
+ * The first integer should be zero always (I think
+ * this is some sort of version indication).
+ */
+ if (len != 1 || p[0] != 0) {
+ errmsg = "version number mismatch";
+ goto error;
+ }
+ } else if (key->type == OSSH_RSA) {
+ /*
+ * Integers 1 and 2 go into the public blob but in the
+ * opposite order; integers 3, 4, 5 and 8 go into the
+ * private blob. The other two (6 and 7) are ignored.
+ */
+ if (i == 1) {
+ /* Save the details for after we deal with number 2. */
+ modptr = (char *)p;
+ modlen = len;
+ } else if (i != 6 && i != 7) {
+ PUT_32BIT(blob+blobptr, len);
+ memcpy(blob+blobptr+4, p, len);
+ blobptr += 4+len;
+ if (i == 2) {
+ PUT_32BIT(blob+blobptr, modlen);
+ memcpy(blob+blobptr+4, modptr, modlen);
+ blobptr += 4+modlen;
+ privptr = blobptr;
+ }
+ }
+ } else if (key->type == OSSH_DSA) {
+ /*
+ * Integers 1-4 go into the public blob; integer 5 goes
+ * into the private blob.
+ */
+ PUT_32BIT(blob+blobptr, len);
+ memcpy(blob+blobptr+4, p, len);
+ blobptr += 4+len;
+ if (i == 4)
+ privptr = blobptr;
+ }
+
+ /* Skip past the number. */
+ p += len;
+ }
+
+ /*
+ * Now put together the actual key. Simplest way to do this is
+ * to assemble our own key blobs and feed them to the createkey
+ * functions; this is a bit faffy but it does mean we get all
+ * the sanity checks for free.
+ */
+ assert(privptr > 0); /* should have bombed by now if not */
+ retkey = snew(struct ssh2_userkey);
+ retkey->alg = (key->type == OSSH_RSA ? &ssh_rsa : &ssh_dss);
+ retkey->data = retkey->alg->createkey(blob, privptr,
+ blob+privptr, blobptr-privptr);
+ if (!retkey->data) {
+ sfree(retkey);
+ errmsg = "unable to create key data structure";
+ goto error;
+ }
+
+ retkey->comment = dupstr("imported-openssh-key");
+ errmsg = NULL; /* no error */
+ retval = retkey;
+
+ error:
+ if (blob) {
+ memset(blob, 0, blobsize);
+ sfree(blob);
+ }
+ memset(key->keyblob, 0, key->keyblob_size);
+ sfree(key->keyblob);
+ memset(key, 0, sizeof(*key));
+ sfree(key);
+ if (errmsg_p) *errmsg_p = errmsg;
+ return retval;
+}
+
+int openssh_write(const Filename *filename, struct ssh2_userkey *key,
+ char *passphrase)
+{
+ unsigned char *pubblob, *privblob, *spareblob;
+ int publen, privlen, sparelen = 0;
+ unsigned char *outblob;
+ int outlen;
+ struct mpint_pos numbers[9];
+ int nnumbers, pos, len, seqlen, i;
+ char *header, *footer;
+ char zero[1];
+ unsigned char iv[8];
+ int ret = 0;
+ FILE *fp;
+
+ /*
+ * Fetch the key blobs.
+ */
+ pubblob = key->alg->public_blob(key->data, &publen);
+ privblob = key->alg->private_blob(key->data, &privlen);
+ spareblob = outblob = NULL;
+
+ /*
+ * Find the sequence of integers to be encoded into the OpenSSH
+ * key blob, and also decide on the header line.
+ */
+ if (key->alg == &ssh_rsa) {
+ int pos;
+ struct mpint_pos n, e, d, p, q, iqmp, dmp1, dmq1;
+ Bignum bd, bp, bq, bdmp1, bdmq1;
+
+ 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);
+ pos += ssh2_read_mpint(privblob+pos, privlen-pos, &p);
+ pos += ssh2_read_mpint(privblob+pos, privlen-pos, &q);
+ pos += ssh2_read_mpint(privblob+pos, privlen-pos, &iqmp);
+
+ assert(e.start && iqmp.start); /* can't go wrong */
+
+ /* We also need d mod (p-1) and d mod (q-1). */
+ bd = bignum_from_bytes(d.start, d.bytes);
+ bp = bignum_from_bytes(p.start, p.bytes);
+ bq = bignum_from_bytes(q.start, q.bytes);
+ decbn(bp);
+ decbn(bq);
+ bdmp1 = bigmod(bd, bp);
+ bdmq1 = bigmod(bd, bq);
+ freebn(bd);
+ freebn(bp);
+ freebn(bq);
+
+ dmp1.bytes = (bignum_bitcount(bdmp1)+8)/8;
+ dmq1.bytes = (bignum_bitcount(bdmq1)+8)/8;
+ sparelen = dmp1.bytes + dmq1.bytes;
+ spareblob = snewn(sparelen, unsigned char);
+ dmp1.start = spareblob;
+ dmq1.start = spareblob + dmp1.bytes;
+ for (i = 0; i < dmp1.bytes; i++)
+ spareblob[i] = bignum_byte(bdmp1, dmp1.bytes-1 - i);
+ for (i = 0; i < dmq1.bytes; i++)
+ spareblob[i+dmp1.bytes] = bignum_byte(bdmq1, dmq1.bytes-1 - i);
+ freebn(bdmp1);
+ freebn(bdmq1);
+
+ numbers[0].start = zero; numbers[0].bytes = 1; zero[0] = '\0';
+ numbers[1] = n;
+ numbers[2] = e;
+ numbers[3] = d;
+ numbers[4] = p;
+ numbers[5] = q;
+ numbers[6] = dmp1;
+ numbers[7] = dmq1;
+ numbers[8] = iqmp;
+
+ nnumbers = 9;
+ header = "-----BEGIN RSA PRIVATE KEY-----\n";
+ footer = "-----END RSA PRIVATE KEY-----\n";
+ } else if (key->alg == &ssh_dss) {
+ int pos;
+ struct mpint_pos p, q, g, y, x;
+
+ 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);
+ pos = 0;
+ pos += ssh2_read_mpint(privblob+pos, privlen-pos, &x);
+
+ assert(y.start && x.start); /* can't go wrong */
+
+ numbers[0].start = zero; numbers[0].bytes = 1; zero[0] = '\0';
+ numbers[1] = p;
+ numbers[2] = q;
+ numbers[3] = g;
+ numbers[4] = y;
+ numbers[5] = x;
+
+ nnumbers = 6;
+ header = "-----BEGIN DSA PRIVATE KEY-----\n";
+ footer = "-----END DSA PRIVATE KEY-----\n";
+ } else {
+ assert(0); /* zoinks! */
+ exit(1); /* XXX: GCC doesn't understand assert() on some systems. */
+ }
+
+ /*
+ * Now count up the total size of the ASN.1 encoded integers,
+ * so as to determine the length of the containing SEQUENCE.
+ */
+ len = 0;
+ for (i = 0; i < nnumbers; i++) {
+ len += ber_write_id_len(NULL, 2, numbers[i].bytes, 0);
+ len += numbers[i].bytes;
+ }
+ seqlen = len;
+ /* Now add on the SEQUENCE header. */
+ len += ber_write_id_len(NULL, 16, seqlen, ASN1_CONSTRUCTED);
+ /* Round up to the cipher block size, ensuring we have at least one
+ * byte of padding (see below). */
+ outlen = len;
+ if (passphrase)
+ outlen = (outlen+8) &~ 7;
+
+ /*
+ * Now we know how big outblob needs to be. Allocate it.
+ */
+ outblob = snewn(outlen, unsigned char);
+
+ /*
+ * And write the data into it.
+ */
+ pos = 0;
+ pos += ber_write_id_len(outblob+pos, 16, seqlen, ASN1_CONSTRUCTED);
+ for (i = 0; i < nnumbers; i++) {
+ pos += ber_write_id_len(outblob+pos, 2, numbers[i].bytes, 0);
+ memcpy(outblob+pos, numbers[i].start, numbers[i].bytes);
+ pos += numbers[i].bytes;
+ }
+
+ /*
+ * Padding on OpenSSH keys is deterministic. The number of
+ * padding bytes is always more than zero, and always at most
+ * the cipher block length. The value of each padding byte is
+ * equal to the number of padding bytes. So a plaintext that's
+ * an exact multiple of the block size will be padded with 08
+ * 08 08 08 08 08 08 08 (assuming a 64-bit block cipher); a
+ * plaintext one byte less than a multiple of the block size
+ * will be padded with just 01.
+ *
+ * This enables the OpenSSL key decryption function to strip
+ * off the padding algorithmically and return the unpadded
+ * plaintext to the next layer: it looks at the final byte, and
+ * then expects to find that many bytes at the end of the data
+ * with the same value. Those are all removed and the rest is
+ * returned.
+ */
+ assert(pos == len);
+ while (pos < outlen) {
+ outblob[pos++] = outlen - len;
+ }
+
+ /*
+ * Encrypt the key.
+ *
+ * For the moment, we still encrypt our OpenSSH keys using
+ * old-style 3DES.
+ */
+ if (passphrase) {
+ /*
+ * Invent an iv. Then derive encryption key from passphrase
+ * and iv/salt:
+ *
+ * - let block A equal MD5(passphrase || iv)
+ * - let block B equal MD5(A || passphrase || iv)
+ * - block C would be MD5(B || passphrase || iv) and so on
+ * - encryption key is the first N bytes of A || B
+ */
+ struct MD5Context md5c;
+ unsigned char keybuf[32];
+
+ for (i = 0; i < 8; i++) iv[i] = random_byte();
+
+ MD5Init(&md5c);
+ MD5Update(&md5c, (unsigned char *)passphrase, strlen(passphrase));
+ MD5Update(&md5c, iv, 8);
+ MD5Final(keybuf, &md5c);
+
+ MD5Init(&md5c);
+ MD5Update(&md5c, keybuf, 16);
+ MD5Update(&md5c, (unsigned char *)passphrase, strlen(passphrase));
+ MD5Update(&md5c, iv, 8);
+ MD5Final(keybuf+16, &md5c);
+
+ /*
+ * Now encrypt the key blob.
+ */
+ des3_encrypt_pubkey_ossh(keybuf, iv, outblob, outlen);
+
+ memset(&md5c, 0, sizeof(md5c));
+ memset(keybuf, 0, 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 */
+ if (!fp)
+ goto error;
+ fputs(header, fp);
+ if (passphrase) {
+ fprintf(fp, "Proc-Type: 4,ENCRYPTED\nDEK-Info: DES-EDE3-CBC,");
+ for (i = 0; i < 8; i++)
+ fprintf(fp, "%02X", iv[i]);
+ fprintf(fp, "\n\n");
+ }
+ base64_encode(fp, outblob, outlen, 64);
+ fputs(footer, fp);
+ fclose(fp);
+ ret = 1;
+
+ error:
+ if (outblob) {
+ memset(outblob, 0, outlen);
+ sfree(outblob);
+ }
+ if (spareblob) {
+ memset(spareblob, 0, sparelen);
+ sfree(spareblob);
+ }
+ if (privblob) {
+ memset(privblob, 0, privlen);
+ sfree(privblob);
+ }
+ if (pubblob) {
+ memset(pubblob, 0, publen);
+ sfree(pubblob);
+ }
+ return ret;
+}
+
+/* ----------------------------------------------------------------------
+ * Code to read ssh.com private keys.
+ */
+
+/*
+ * The format of the base64 blob is largely SSH-2-packet-formatted,
+ * except that mpints are a bit different: they're more like the
+ * old SSH-1 mpint. You have a 32-bit bit count N, followed by
+ * (N+7)/8 bytes of data.
+ *
+ * So. The blob contains:
+ *
+ * - uint32 0x3f6ff9eb (magic number)
+ * - uint32 size (total blob size)
+ * - string key-type (see below)
+ * - string cipher-type (tells you if key is encrypted)
+ * - string encrypted-blob
+ *
+ * (The first size field includes the size field itself and the
+ * magic number before it. All other size fields are ordinary SSH-2
+ * strings, so the size field indicates how much data is to
+ * _follow_.)
+ *
+ * The encrypted blob, once decrypted, contains a single string
+ * which in turn contains the payload. (This allows padding to be
+ * added after that string while still making it clear where the
+ * real payload ends. Also it probably makes for a reasonable
+ * decryption check.)
+ *
+ * The payload blob, for an RSA key, contains:
+ * - mpint e
+ * - mpint d
+ * - mpint n (yes, the public and private stuff is intermixed)
+ * - mpint u (presumably inverse of p mod q)
+ * - mpint p (p is the smaller prime)
+ * - mpint q (q is the larger)
+ *
+ * For a DSA key, the payload blob contains:
+ * - uint32 0
+ * - mpint p
+ * - mpint g
+ * - mpint q
+ * - mpint y
+ * - mpint x
+ *
+ * Alternatively, if the parameters are `predefined', that
+ * (0,p,g,q) sequence can be replaced by a uint32 1 and a string
+ * containing some predefined parameter specification. *shudder*,
+ * but I doubt we'll encounter this in real life.
+ *
+ * The key type strings are ghastly. The RSA key I looked at had a
+ * type string of
+ *
+ * `if-modn{sign{rsa-pkcs1-sha1},encrypt{rsa-pkcs1v2-oaep}}'
+ *
+ * and the DSA key wasn't much better:
+ *
+ * `dl-modp{sign{dsa-nist-sha1},dh{plain}}'
+ *
+ * It isn't clear that these will always be the same. I think it
+ * might be wise just to look at the `if-modn{sign{rsa' and
+ * `dl-modp{sign{dsa' prefixes.
+ *
+ * Finally, the encryption. The cipher-type string appears to be
+ * either `none' or `3des-cbc'. Looks as if this is SSH-2-style
+ * 3des-cbc (i.e. outer cbc rather than inner). The key is created
+ * from the passphrase by means of yet another hashing faff:
+ *
+ * - first 16 bytes are MD5(passphrase)
+ * - next 16 bytes are MD5(passphrase || first 16 bytes)
+ * - if there were more, they'd be MD5(passphrase || first 32),
+ * and so on.
+ */
+
+#define SSHCOM_MAGIC_NUMBER 0x3f6ff9eb
+
+struct sshcom_key {
+ char comment[256]; /* allowing any length is overkill */
+ unsigned char *keyblob;
+ int keyblob_len, keyblob_size;
+};
+
+static struct sshcom_key *load_sshcom_key(const Filename *filename,
+ const char **errmsg_p)
+{
+ struct sshcom_key *ret;
+ FILE *fp;
+ char *line = NULL;
+ int hdrstart, len;
+ char *errmsg, *p;
+ int headers_done;
+ char base64_bit[4];
+ int base64_chars = 0;
+
+ 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);
+ if (!fp) {
+ errmsg = "unable to open key file";
+ goto error;
+ }
+ if (!(line = fgetline(fp))) {
+ errmsg = "unexpected end of file";
+ goto error;
+ }
+ 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));
+ sfree(line);
+ line = NULL;
+
+ headers_done = 0;
+ while (1) {
+ if (!(line = fgetline(fp))) {
+ errmsg = "unexpected end of file";
+ goto error;
+ }
+ strip_crlf(line);
+ if (!strcmp(line, "---- END SSH2 ENCRYPTED PRIVATE KEY ----"))
+ break; /* done */
+ if ((p = strchr(line, ':')) != NULL) {
+ if (headers_done) {
+ errmsg = "header found in body of key data";
+ goto error;
+ }
+ *p++ = '\0';
+ while (*p && isspace((unsigned char)*p)) p++;
+ hdrstart = p - line;
+
+ /*
+ * Header lines can end in a trailing backslash for
+ * continuation.
+ */
+ len = hdrstart + strlen(line+hdrstart);
+ assert(!line[len]);
+ while (line[len-1] == '\\') {
+ char *line2;
+ int line2len;
+
+ line2 = fgetline(fp);
+ if (!line2) {
+ errmsg = "unexpected end of file";
+ goto error;
+ }
+ strip_crlf(line2);
+
+ line2len = strlen(line2);
+ line = sresize(line, len + line2len + 1, char);
+ strcpy(line + len - 1, line2);
+ len += line2len - 1;
+ assert(!line[len]);
+
+ memset(line2, 0, strlen(line2));
+ sfree(line2);
+ line2 = NULL;
+ }
+ p = line + hdrstart;
+ strip_crlf(p);
+ if (!strcmp(line, "Comment")) {
+ /* Strip quotes in comment if present. */
+ if (p[0] == '"' && p[strlen(p)-1] == '"') {
+ p++;
+ p[strlen(p)-1] = '\0';
+ }
+ strncpy(ret->comment, p, sizeof(ret->comment));
+ ret->comment[sizeof(ret->comment)-1] = '\0';
+ }
+ } else {
+ headers_done = 1;
+
+ p = line;
+ while (isbase64(*p)) {
+ base64_bit[base64_chars++] = *p;
+ if (base64_chars == 4) {
+ unsigned char out[3];
+
+ base64_chars = 0;
+
+ len = base64_decode_atom(base64_bit, out);
+
+ if (len <= 0) {
+ errmsg = "invalid base64 encoding";
+ goto error;
+ }
+
+ if (ret->keyblob_len + len > ret->keyblob_size) {
+ ret->keyblob_size = ret->keyblob_len + len + 256;
+ ret->keyblob = sresize(ret->keyblob, ret->keyblob_size,
+ unsigned char);
+ }
+
+ memcpy(ret->keyblob + ret->keyblob_len, out, len);
+ ret->keyblob_len += len;
+ }
+
+ p++;
+ }
+ }
+ memset(line, 0, strlen(line));
+ sfree(line);
+ line = NULL;
+ }
+
+ if (ret->keyblob_len == 0 || !ret->keyblob) {
+ errmsg = "key body not present";
+ goto error;
+ }
+
+ if (errmsg_p) *errmsg_p = NULL;
+ return ret;
+
+ error:
+ if (line) {
+ memset(line, 0, strlen(line));
+ sfree(line);
+ line = NULL;
+ }
+ if (ret) {
+ if (ret->keyblob) {
+ memset(ret->keyblob, 0, ret->keyblob_size);
+ sfree(ret->keyblob);
+ }
+ memset(ret, 0, sizeof(*ret));
+ sfree(ret);
+ }
+ if (errmsg_p) *errmsg_p = errmsg;
+ return NULL;
+}
+
+int sshcom_encrypted(const Filename *filename, char **comment)
+{
+ struct sshcom_key *key = load_sshcom_key(filename, NULL);
+ int pos, len, answer;
+
+ *comment = NULL;
+ if (!key)
+ return 0;
+
+ /*
+ * Check magic number.
+ */
+ if (GET_32BIT(key->keyblob) != 0x3f6ff9eb)
+ return 0; /* 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)
+ goto done; /* key is far too short */
+ len = GET_32BIT(key->keyblob + pos); /* find cipher-type length */
+ if (key->keyblob_len < pos+4+len)
+ 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);
+ return answer;
+}
+
+static int sshcom_read_mpint(void *data, int len, struct mpint_pos *ret)
+{
+ int bits;
+ int bytes;
+ unsigned char *d = (unsigned char *) data;
+
+ if (len < 4)
+ goto error;
+ bits = GET_32BIT(d);
+
+ bytes = (bits + 7) / 8;
+ if (len < 4+bytes)
+ goto error;
+
+ ret->start = d + 4;
+ ret->bytes = bytes;
+ return bytes+4;
+
+ error:
+ ret->start = NULL;
+ ret->bytes = -1;
+ return len; /* ensure further calls fail as well */
+}
+
+static int sshcom_put_mpint(void *target, void *data, int len)
+{
+ unsigned char *d = (unsigned char *)target;
+ unsigned char *i = (unsigned char *)data;
+ int bits = len * 8 - 1;
+
+ while (bits > 0) {
+ if (*i & (1 << (bits & 7)))
+ break;
+ if (!(bits-- & 7))
+ i++, len--;
+ }
+
+ PUT_32BIT(d, bits+1);
+ memcpy(d+4, i, len);
+ return len+4;
+}
+
+struct ssh2_userkey *sshcom_read(const Filename *filename, char *passphrase,
+ const char **errmsg_p)
+{
+ struct sshcom_key *key = load_sshcom_key(filename, errmsg_p);
+ char *errmsg;
+ int pos, len;
+ const char prefix_rsa[] = "if-modn{sign{rsa";
+ const char prefix_dsa[] = "dl-modp{sign{dsa";
+ enum { RSA, DSA } type;
+ int encrypted;
+ char *ciphertext;
+ int cipherlen;
+ struct ssh2_userkey *ret = NULL, *retkey;
+ const struct ssh_signkey *alg;
+ unsigned char *blob = NULL;
+ int blobsize = 0, publen, privlen;
+
+ if (!key)
+ return NULL;
+
+ /*
+ * Check magic number.
+ */
+ if (GET_32BIT(key->keyblob) != SSHCOM_MAGIC_NUMBER) {
+ errmsg = "key does not begin with magic number";
+ goto error;
+ }
+
+ /*
+ * Determine the key type.
+ */
+ pos = 8;
+ if (key->keyblob_len < pos+4 ||
+ (len = GET_32BIT(key->keyblob + pos)) > 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)) {
+ type = RSA;
+ } else if (len > sizeof(prefix_dsa) - 1 &&
+ !memcmp(key->keyblob+pos+4, prefix_dsa, sizeof(prefix_dsa) - 1)) {
+ type = DSA;
+ } else {
+ errmsg = "key is of unknown type";
+ goto error;
+ }
+ pos += 4+len;
+
+ /*
+ * Determine the cipher type.
+ */
+ if (key->keyblob_len < pos+4 ||
+ (len = GET_32BIT(key->keyblob + pos)) > 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;
+ else if (len == 8 && !memcmp(key->keyblob+pos+4, "3des-cbc", 8))
+ encrypted = 1;
+ else {
+ errmsg = "key encryption is of unknown type";
+ goto error;
+ }
+ pos += 4+len;
+
+ /*
+ * 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) {
+ errmsg = "key blob does not contain actual key data";
+ goto error;
+ }
+ ciphertext = (char *)key->keyblob + pos + 4;
+ cipherlen = len;
+ if (cipherlen == 0) {
+ errmsg = "length of key data is zero";
+ goto error;
+ }
+
+ /*
+ * Decrypt it if necessary.
+ */
+ if (encrypted) {
+ /*
+ * Derive encryption key from passphrase and iv/salt:
+ *
+ * - let block A equal MD5(passphrase)
+ * - let block B equal MD5(passphrase || A)
+ * - block C would be MD5(passphrase || A || B) and so on
+ * - encryption key is the first N bytes of A || B
+ */
+ struct MD5Context md5c;
+ unsigned char keybuf[32], iv[8];
+
+ if (cipherlen % 8 != 0) {
+ errmsg = "encrypted part of key is not a multiple of cipher block"
+ " size";
+ goto error;
+ }
+
+ MD5Init(&md5c);
+ MD5Update(&md5c, (unsigned char *)passphrase, strlen(passphrase));
+ MD5Final(keybuf, &md5c);
+
+ MD5Init(&md5c);
+ MD5Update(&md5c, (unsigned char *)passphrase, strlen(passphrase));
+ MD5Update(&md5c, keybuf, 16);
+ MD5Final(keybuf+16, &md5c);
+
+ /*
+ * Now decrypt the key blob.
+ */
+ 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));
+
+ /*
+ * 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
+ * automatic doom.)
+ */
+ if (encrypted)
+ ret = SSH2_WRONG_PASSPHRASE;
+ }
+
+ /*
+ * Strip away the containing string to get to the real meat.
+ */
+ len = GET_32BIT(ciphertext);
+ if (len < 0 || len > cipherlen-4) {
+ errmsg = "containing string was ill-formed";
+ goto error;
+ }
+ ciphertext += 4;
+ cipherlen = len;
+
+ /*
+ * Now we break down into RSA versus DSA. In either case we'll
+ * construct public and private blobs in our own format, and
+ * end up feeding them to alg->createkey().
+ */
+ blobsize = cipherlen + 256;
+ blob = snewn(blobsize, unsigned char);
+ privlen = 0;
+ if (type == RSA) {
+ struct mpint_pos n, e, d, u, p, q;
+ int pos = 0;
+ pos += sshcom_read_mpint(ciphertext+pos, cipherlen-pos, &e);
+ pos += sshcom_read_mpint(ciphertext+pos, cipherlen-pos, &d);
+ pos += sshcom_read_mpint(ciphertext+pos, cipherlen-pos, &n);
+ pos += sshcom_read_mpint(ciphertext+pos, cipherlen-pos, &u);
+ pos += sshcom_read_mpint(ciphertext+pos, cipherlen-pos, &p);
+ pos += sshcom_read_mpint(ciphertext+pos, cipherlen-pos, &q);
+ if (!q.start) {
+ errmsg = "key data did not contain six integers";
+ goto error;
+ }
+
+ alg = &ssh_rsa;
+ pos = 0;
+ pos += put_string(blob+pos, "ssh-rsa", 7);
+ pos += put_mp(blob+pos, e.start, e.bytes);
+ pos += put_mp(blob+pos, n.start, n.bytes);
+ publen = pos;
+ 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) {
+ struct mpint_pos p, q, g, x, y;
+ int pos = 4;
+ if (GET_32BIT(ciphertext) != 0) {
+ errmsg = "predefined DSA parameters not supported";
+ goto error;
+ }
+ pos += sshcom_read_mpint(ciphertext+pos, cipherlen-pos, &p);
+ pos += sshcom_read_mpint(ciphertext+pos, cipherlen-pos, &g);
+ pos += sshcom_read_mpint(ciphertext+pos, cipherlen-pos, &q);
+ pos += sshcom_read_mpint(ciphertext+pos, cipherlen-pos, &y);
+ pos += sshcom_read_mpint(ciphertext+pos, cipherlen-pos, &x);
+ if (!x.start) {
+ errmsg = "key data did not contain five integers";
+ goto error;
+ }
+
+ alg = &ssh_dss;
+ pos = 0;
+ pos += put_string(blob+pos, "ssh-dss", 7);
+ pos += put_mp(blob+pos, p.start, p.bytes);
+ pos += put_mp(blob+pos, q.start, q.bytes);
+ 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;
+ retkey->data = alg->createkey(blob, publen, blob+publen, privlen);
+ if (!retkey->data) {
+ sfree(retkey);
+ errmsg = "unable to create key data structure";
+ goto error;
+ }
+ retkey->comment = dupstr(key->comment);
+
+ errmsg = NULL; /* no error */
+ ret = retkey;
+
+ error:
+ if (blob) {
+ memset(blob, 0, blobsize);
+ sfree(blob);
+ }
+ memset(key->keyblob, 0, key->keyblob_size);
+ sfree(key->keyblob);
+ memset(key, 0, sizeof(*key));
+ sfree(key);
+ if (errmsg_p) *errmsg_p = errmsg;
+ return ret;
+}
+
+int sshcom_write(const Filename *filename, struct ssh2_userkey *key,
+ char *passphrase)
+{
+ unsigned char *pubblob, *privblob;
+ int publen, privlen;
+ unsigned char *outblob;
+ int outlen;
+ struct mpint_pos numbers[6];
+ int nnumbers, initial_zero, pos, lenpos, i;
+ char *type;
+ char *ciphertext;
+ int cipherlen;
+ int ret = 0;
+ FILE *fp;
+
+ /*
+ * Fetch the key blobs.
+ */
+ pubblob = key->alg->public_blob(key->data, &publen);
+ privblob = key->alg->private_blob(key->data, &privlen);
+ outblob = NULL;
+
+ /*
+ * Find the sequence of integers to be encoded into the OpenSSH
+ * key blob, and also decide on the header line.
+ */
+ if (key->alg == &ssh_rsa) {
+ int pos;
+ struct mpint_pos n, e, d, p, q, iqmp;
+
+ 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);
+ pos += ssh2_read_mpint(privblob+pos, privlen-pos, &p);
+ pos += ssh2_read_mpint(privblob+pos, privlen-pos, &q);
+ pos += ssh2_read_mpint(privblob+pos, privlen-pos, &iqmp);
+
+ assert(e.start && iqmp.start); /* can't go wrong */
+
+ numbers[0] = e;
+ numbers[1] = d;
+ numbers[2] = n;
+ numbers[3] = iqmp;
+ numbers[4] = q;
+ numbers[5] = p;
+
+ nnumbers = 6;
+ initial_zero = 0;
+ 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;
+
+ 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);
+ pos = 0;
+ pos += ssh2_read_mpint(privblob+pos, privlen-pos, &x);
+
+ assert(y.start && x.start); /* can't go wrong */
+
+ numbers[0] = p;
+ numbers[1] = g;
+ numbers[2] = q;
+ numbers[3] = y;
+ numbers[4] = x;
+
+ nnumbers = 5;
+ initial_zero = 1;
+ type = "dl-modp{sign{dsa-nist-sha1},dh{plain}}";
+ } else {
+ assert(0); /* zoinks! */
+ exit(1); /* XXX: GCC doesn't understand assert() on some systems. */
+ }
+
+ /*
+ * Total size of key blob will be somewhere under 512 plus
+ * combined length of integers. We'll calculate the more
+ * precise size as we construct the blob.
+ */
+ outlen = 512;
+ for (i = 0; i < nnumbers; i++)
+ outlen += 4 + numbers[i].bytes;
+ outblob = snewn(outlen, unsigned char);
+
+ /*
+ * Create the unencrypted key blob.
+ */
+ pos = 0;
+ PUT_32BIT(outblob+pos, SSHCOM_MAGIC_NUMBER); pos += 4;
+ pos += 4; /* length field, fill in later */
+ pos += put_string(outblob+pos, type, strlen(type));
+ {
+ char *ciphertype = passphrase ? "3des-cbc" : "none";
+ pos += put_string(outblob+pos, ciphertype, strlen(ciphertype));
+ }
+ lenpos = pos; /* remember this position */
+ pos += 4; /* encrypted-blob size */
+ pos += 4; /* encrypted-payload size */
+ if (initial_zero) {
+ PUT_32BIT(outblob+pos, 0);
+ pos += 4;
+ }
+ for (i = 0; i < nnumbers; i++)
+ pos += sshcom_put_mpint(outblob+pos,
+ numbers[i].start, numbers[i].bytes);
+ /* Now wrap up the encrypted payload. */
+ PUT_32BIT(outblob+lenpos+4, pos - (lenpos+8));
+ /* Pad encrypted blob to a multiple of cipher block size. */
+ if (passphrase) {
+ int padding = -(pos - (lenpos+4)) & 7;
+ while (padding--)
+ outblob[pos++] = random_byte();
+ }
+ ciphertext = (char *)outblob+lenpos+4;
+ cipherlen = pos - (lenpos+4);
+ assert(!passphrase || cipherlen % 8 == 0);
+ /* Wrap up the encrypted blob string. */
+ PUT_32BIT(outblob+lenpos, cipherlen);
+ /* And finally fill in the total length field. */
+ PUT_32BIT(outblob+4, pos);
+
+ assert(pos < outlen);
+
+ /*
+ * Encrypt the key.
+ */
+ if (passphrase) {
+ /*
+ * Derive encryption key from passphrase and iv/salt:
+ *
+ * - let block A equal MD5(passphrase)
+ * - let block B equal MD5(passphrase || A)
+ * - block C would be MD5(passphrase || A || B) and so on
+ * - encryption key is the first N bytes of A || B
+ */
+ struct MD5Context md5c;
+ unsigned char keybuf[32], iv[8];
+
+ MD5Init(&md5c);
+ MD5Update(&md5c, (unsigned char *)passphrase, strlen(passphrase));
+ MD5Final(keybuf, &md5c);
+
+ MD5Init(&md5c);
+ MD5Update(&md5c, (unsigned char *)passphrase, strlen(passphrase));
+ MD5Update(&md5c, keybuf, 16);
+ MD5Final(keybuf+16, &md5c);
+
+ /*
+ * Now decrypt the key blob.
+ */
+ 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));
+ }
+
+ /*
+ * 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 */
+ if (!fp)
+ goto error;
+ fputs("---- BEGIN SSH2 ENCRYPTED PRIVATE KEY ----\n", fp);
+ fprintf(fp, "Comment: \"");
+ /*
+ * Comment header is broken with backslash-newline if it goes
+ * over 70 chars. Although it's surrounded by quotes, it
+ * _doesn't_ escape backslashes or quotes within the string.
+ * Don't ask me, I didn't design it.
+ */
+ {
+ int slen = 60; /* starts at 60 due to "Comment: " */
+ char *c = key->comment;
+ while ((int)strlen(c) > slen) {
+ fprintf(fp, "%.*s\\\n", slen, c);
+ c += slen;
+ slen = 70; /* allow 70 chars on subsequent lines */
+ }
+ fprintf(fp, "%s\"\n", c);
+ }
+ base64_encode(fp, outblob, pos, 70);
+ fputs("---- END SSH2 ENCRYPTED PRIVATE KEY ----\n", fp);
+ fclose(fp);
+ ret = 1;
+
+ error:
+ if (outblob) {
+ memset(outblob, 0, outlen);
+ sfree(outblob);
+ }
+ if (privblob) {
+ memset(privblob, 0, privlen);
+ sfree(privblob);
+ }
+ if (pubblob) {
+ memset(pubblob, 0, publen);
+ sfree(pubblob);
+ }
+ return ret;
+}
--- /dev/null
+/*
+ * Handling of the int64 and uint64 types. Done in 32-bit integers,
+ * for (pre-C99) portability. Hopefully once C99 becomes widespread
+ * we can kiss this lot goodbye...
+ */
+
+#include <assert.h>
+#include <string.h>
+
+#include "int64.h"
+
+uint64 uint64_div10(uint64 x, int *remainder)
+{
+ uint64 y;
+ unsigned int rem, r2;
+ y.hi = x.hi / 10;
+ y.lo = x.lo / 10;
+ rem = x.lo % 10;
+ /*
+ * Now we have to add in the remainder left over from x.hi.
+ */
+ r2 = x.hi % 10;
+ y.lo += r2 * 429496729;
+ rem += r2 * 6;
+ y.lo += rem / 10;
+ rem %= 10;
+
+ if (remainder)
+ *remainder = rem;
+ return y;
+}
+
+void uint64_decimal(uint64 x, char *buffer)
+{
+ char buf[20];
+ int start = 20;
+ int d;
+
+ do {
+ x = uint64_div10(x, &d);
+ assert(start > 0);
+ buf[--start] = d + '0';
+ } while (x.hi || x.lo);
+
+ memcpy(buffer, buf + start, sizeof(buf) - start);
+ buffer[sizeof(buf) - start] = '\0';
+}
+
+uint64 uint64_make(unsigned long hi, unsigned long lo)
+{
+ uint64 y;
+ y.hi = hi & 0xFFFFFFFFU;
+ y.lo = lo & 0xFFFFFFFFU;
+ return y;
+}
+
+uint64 uint64_add(uint64 x, uint64 y)
+{
+ x.lo = (x.lo + y.lo) & 0xFFFFFFFFU;
+ x.hi += y.hi + (x.lo < y.lo ? 1 : 0);
+ return x;
+}
+
+uint64 uint64_add32(uint64 x, unsigned long y)
+{
+ uint64 yy;
+ yy.hi = 0;
+ yy.lo = y;
+ return uint64_add(x, yy);
+}
+
+int uint64_compare(uint64 x, uint64 y)
+{
+ if (x.hi != y.hi)
+ return x.hi < y.hi ? -1 : +1;
+ if (x.lo != y.lo)
+ return x.lo < y.lo ? -1 : +1;
+ return 0;
+}
+
+uint64 uint64_subtract(uint64 x, uint64 y)
+{
+ x.lo = (x.lo - y.lo) & 0xFFFFFFFFU;
+ x.hi = (x.hi - y.hi - (x.lo > (y.lo ^ 0xFFFFFFFFU) ? 1 : 0)) & 0xFFFFFFFFU;
+ return x;
+}
+
+double uint64_to_double(uint64 x)
+{
+ return (4294967296.0 * x.hi) + (double)x.lo;
+}
+
+uint64 uint64_shift_right(uint64 x, int shift)
+{
+ if (shift < 32) {
+ x.lo >>= shift;
+ x.lo |= (x.hi << (32-shift)) & 0xFFFFFFFFU;
+ x.hi >>= shift;
+ } else {
+ x.lo = x.hi >> (shift-32);
+ x.hi = 0;
+ }
+ return x;
+}
+
+uint64 uint64_shift_left(uint64 x, int shift)
+{
+ if (shift < 32) {
+ x.hi = (x.hi << shift) & 0xFFFFFFFFU;
+ x.hi |= (x.lo >> (32-shift));
+ x.lo = (x.lo << shift) & 0xFFFFFFFFU;
+ } else {
+ x.hi = (x.lo << (shift-32)) & 0xFFFFFFFFU;
+ x.lo = 0;
+ }
+ return x;
+}
+
+uint64 uint64_from_decimal(char *str)
+{
+ uint64 ret;
+ ret.hi = ret.lo = 0;
+ while (*str >= '0' && *str <= '9') {
+ ret = uint64_add(uint64_shift_left(ret, 3),
+ uint64_shift_left(ret, 1));
+ ret = uint64_add32(ret, *str - '0');
+ str++;
+ }
+ return ret;
+}
+
+#ifdef TESTMODE
+
+#include <stdio.h>
+
+int main(void)
+{
+ uint64 x, y, z;
+ char buf[80];
+
+ x = uint64_make(0x3456789AUL, 0xDEF01234UL);
+ printf("%08lx.%08lx\n", x.hi, x.lo);
+ uint64_decimal(x, buf);
+ printf("%s\n", buf);
+
+ y = uint64_add32(x, 0xFFFFFFFFU);
+ printf("%08lx.%08lx\n", y.hi, y.lo);
+ uint64_decimal(y, buf);
+ printf("%s\n", buf);
+
+ z = uint64_subtract(y, x);
+ printf("%08lx.%08lx\n", z.hi, z.lo);
+ uint64_decimal(z, buf);
+ printf("%s\n", buf);
+
+ z = uint64_subtract(x, y);
+ printf("%08lx.%08lx\n", z.hi, z.lo);
+ uint64_decimal(z, buf);
+ printf("%s\n", buf);
+
+ y = uint64_shift_right(x, 4);
+ printf("%08lx.%08lx\n", y.hi, y.lo);
+
+ y = uint64_shift_right(x, 36);
+ printf("%08lx.%08lx\n", y.hi, y.lo);
+
+ y = uint64_shift_left(x, 4);
+ printf("%08lx.%08lx\n", x.hi, x.lo);
+
+ y = uint64_shift_left(x, 36);
+ printf("%08lx.%08lx\n", x.hi, x.lo);
+
+ return 0;
+}
+#endif
--- /dev/null
+/*
+ * Header for int64.c.
+ */
+
+#ifndef PUTTY_INT64_H
+#define PUTTY_INT64_H
+
+typedef struct {
+ unsigned long hi, lo;
+} uint64;
+
+uint64 uint64_div10(uint64 x, int *remainder);
+void uint64_decimal(uint64 x, char *buffer);
+uint64 uint64_make(unsigned long hi, unsigned long lo);
+uint64 uint64_add(uint64 x, uint64 y);
+uint64 uint64_add32(uint64 x, unsigned long y);
+int uint64_compare(uint64 x, uint64 y);
+uint64 uint64_subtract(uint64 x, uint64 y);
+double uint64_to_double(uint64 x);
+uint64 uint64_shift_right(uint64 x, int shift);
+uint64 uint64_shift_left(uint64 x, int shift);
+uint64 uint64_from_decimal(char *str);
+
+#endif
--- /dev/null
+/*
+ * ldisc.c: PuTTY line discipline. Sits between the input coming
+ * from keypresses in the window, and the output channel leading to
+ * the back end. Implements echo and/or local line editing,
+ * depending on what's currently configured.
+ */
+
+#include <stdio.h>
+#include <ctype.h>
+
+#include "putty.h"
+#include "terminal.h"
+#include "ldisc.h"
+
+#define ECHOING (ldisc->cfg->localecho == FORCE_ON || \
+ (ldisc->cfg->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 && \
+ (ldisc->back->ldisc(ldisc->backhandle, LD_EDIT) || \
+ term_ldisc(ldisc->term, LD_EDIT))))
+
+static void c_write(Ldisc ldisc, char *buf, int len)
+{
+ from_backend(ldisc->frontend, 0, buf, len);
+}
+
+static int plen(Ldisc ldisc, unsigned char c)
+{
+ if ((c >= 32 && c <= 126) || (c >= 160 && !in_utf(ldisc->term)))
+ return 1;
+ else if (c < 128)
+ return 2; /* ^x for some x */
+ else if (in_utf(ldisc->term) && c >= 0xC0)
+ return 1; /* UTF-8 introducer character
+ * (FIXME: combining / wide chars) */
+ else if (in_utf(ldisc->term) && c >= 0x80 && c < 0xC0)
+ return 0; /* UTF-8 followup character */
+ else
+ return 4; /* <XY> hex representation */
+}
+
+static void pwrite(Ldisc ldisc, unsigned char c)
+{
+ if ((c >= 32 && c <= 126) ||
+ (!in_utf(ldisc->term) && c >= 0xA0) ||
+ (in_utf(ldisc->term) && c >= 0x80)) {
+ c_write(ldisc, (char *)&c, 1);
+ } else if (c < 128) {
+ char cc[2];
+ cc[1] = (c == 127 ? '?' : c + 0x40);
+ cc[0] = '^';
+ c_write(ldisc, cc, 2);
+ } else {
+ char cc[5];
+ sprintf(cc, "<%02X>", c);
+ c_write(ldisc, cc, 4);
+ }
+}
+
+static int char_start(Ldisc ldisc, unsigned char c)
+{
+ if (in_utf(ldisc->term))
+ return (c < 0x80 || c >= 0xC0);
+ else
+ return 1;
+}
+
+static void bsb(Ldisc ldisc, int n)
+{
+ while (n--)
+ c_write(ldisc, "\010 \010", 3);
+}
+
+#define CTRL(x) (x^'@')
+#define KCTRL(x) ((x^'@') | 0x100)
+
+void *ldisc_create(Config *mycfg, Terminal *term,
+ Backend *back, void *backhandle,
+ void *frontend)
+{
+ Ldisc ldisc = snew(struct ldisc_tag);
+
+ 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;
+
+ /* Link ourselves into the backend and the terminal */
+ if (term)
+ term->ldisc = ldisc;
+ if (back)
+ back->provide_ldisc(backhandle, ldisc);
+
+ return ldisc;
+}
+
+void ldisc_free(void *handle)
+{
+ Ldisc ldisc = (Ldisc) handle;
+
+ if (ldisc->term)
+ ldisc->term->ldisc = NULL;
+ if (ldisc->back)
+ ldisc->back->provide_ldisc(ldisc->backhandle, NULL);
+ if (ldisc->buf)
+ sfree(ldisc->buf);
+ sfree(ldisc);
+}
+
+void ldisc_send(void *handle, char *buf, int len, int interactive)
+{
+ Ldisc ldisc = (Ldisc) handle;
+ int keyflag = 0;
+ /*
+ * Called with len=0 when the options change. We must inform
+ * the front end in case it needs to know.
+ */
+ if (len == 0) {
+ ldisc_update(ldisc->frontend, ECHOING, EDITING);
+ return;
+ }
+ /*
+ * 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);
+
+ /*
+ * Less than zero means null terminated special string.
+ */
+ if (len < 0) {
+ len = strlen(buf);
+ keyflag = KCTRL('@');
+ }
+ /*
+ * Either perform local editing, or just send characters.
+ */
+ if (EDITING) {
+ while (len--) {
+ int c;
+ c = (unsigned char)(*buf++) + keyflag;
+ if (!interactive && c == '\r')
+ c += KCTRL('@');
+ switch (ldisc->quotenext ? ' ' : c) {
+ /*
+ * ^h/^?: delete, and output BSBs, to return to
+ * last character boundary (in UTF-8 mode this may
+ * be more than one byte)
+ * ^w: delete, and output BSBs, to return to last
+ * space/nonspace boundary
+ * ^u: delete, and output BSBs, to return to BOL
+ * ^c: Do a ^u then send a telnet IP
+ * ^z: Do a ^u then send a telnet SUSP
+ * ^\: Do a ^u then send a telnet ABORT
+ * ^r: echo "^R\n" and redraw line
+ * ^v: quote next char
+ * ^d: if at BOL, end of file and close connection,
+ * else send line and reset to BOL
+ * ^m: send line-plus-\r\n and reset to BOL
+ */
+ case KCTRL('H'):
+ case KCTRL('?'): /* backspace/delete */
+ if (ldisc->buflen > 0) {
+ do {
+ if (ECHOING)
+ bsb(ldisc, plen(ldisc, ldisc->buf[ldisc->buflen - 1]));
+ ldisc->buflen--;
+ } while (!char_start(ldisc, ldisc->buf[ldisc->buflen]));
+ }
+ break;
+ case CTRL('W'): /* delete word */
+ while (ldisc->buflen > 0) {
+ if (ECHOING)
+ bsb(ldisc, plen(ldisc, ldisc->buf[ldisc->buflen - 1]));
+ ldisc->buflen--;
+ if (ldisc->buflen > 0 &&
+ isspace((unsigned char)ldisc->buf[ldisc->buflen-1]) &&
+ !isspace((unsigned char)ldisc->buf[ldisc->buflen]))
+ break;
+ }
+ break;
+ case CTRL('U'): /* delete line */
+ case CTRL('C'): /* Send IP */
+ case CTRL('\\'): /* Quit */
+ case CTRL('Z'): /* Suspend */
+ while (ldisc->buflen > 0) {
+ if (ECHOING)
+ bsb(ldisc, plen(ldisc, ldisc->buf[ldisc->buflen - 1]));
+ ldisc->buflen--;
+ }
+ ldisc->back->special(ldisc->backhandle, TS_EL);
+ /*
+ * 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)
+ 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);
+ if (c == CTRL('\\'))
+ ldisc->back->special(ldisc->backhandle, TS_ABORT);
+ break;
+ case CTRL('R'): /* redraw line */
+ if (ECHOING) {
+ int i;
+ c_write(ldisc, "^R\r\n", 4);
+ for (i = 0; i < ldisc->buflen; i++)
+ pwrite(ldisc, ldisc->buf[i]);
+ }
+ break;
+ case CTRL('V'): /* quote next char */
+ ldisc->quotenext = TRUE;
+ break;
+ case CTRL('D'): /* logout or send */
+ if (ldisc->buflen == 0) {
+ ldisc->back->special(ldisc->backhandle, TS_EOF);
+ } else {
+ ldisc->back->send(ldisc->backhandle, ldisc->buf, ldisc->buflen);
+ ldisc->buflen = 0;
+ }
+ break;
+ /*
+ * This particularly hideous bit of code from RDB
+ * allows ordinary ^M^J to do the same thing as
+ * magic-^M when in Raw protocol. The line `case
+ * KCTRL('M'):' is _inside_ the if block. Thus:
+ *
+ * - receiving regular ^M goes straight to the
+ * default clause and inserts as a literal ^M.
+ * - receiving regular ^J _not_ directly after a
+ * literal ^M (or not in Raw protocol) fails the
+ * if condition, leaps to the bottom of the if,
+ * and falls through into the default clause
+ * again.
+ * - receiving regular ^J just after a literal ^M
+ * in Raw protocol passes the if condition,
+ * deletes the literal ^M, and falls through
+ * into the magic-^M code
+ * - receiving a magic-^M empties the line buffer,
+ * signals end-of-line in one of the various
+ * 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 &&
+ 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)
+ ldisc->back->send(ldisc->backhandle, "\r\n", 2);
+ else if (ldisc->cfg->protocol == PROT_TELNET && ldisc->cfg->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);
+ ldisc->buflen = 0;
+ break;
+ }
+ /* FALLTHROUGH */
+ default: /* get to this label from ^V handler */
+ default_case:
+ if (ldisc->buflen >= ldisc->bufsiz) {
+ ldisc->bufsiz = ldisc->buflen + 256;
+ ldisc->buf = sresize(ldisc->buf, ldisc->bufsiz, char);
+ }
+ ldisc->buf[ldisc->buflen++] = c;
+ if (ECHOING)
+ pwrite(ldisc, (unsigned char) c);
+ ldisc->quotenext = FALSE;
+ break;
+ }
+ }
+ } else {
+ if (ldisc->buflen != 0) {
+ ldisc->back->send(ldisc->backhandle, ldisc->buf, ldisc->buflen);
+ while (ldisc->buflen > 0) {
+ bsb(ldisc, plen(ldisc, ldisc->buf[ldisc->buflen - 1]));
+ ldisc->buflen--;
+ }
+ }
+ if (len > 0) {
+ if (ECHOING)
+ c_write(ldisc, buf, len);
+ if (keyflag && ldisc->cfg->protocol == PROT_TELNET && len == 1) {
+ switch (buf[0]) {
+ case CTRL('M'):
+ if (ldisc->cfg->protocol == PROT_TELNET && ldisc->cfg->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) {
+ ldisc->back->special(ldisc->backhandle, TS_EC);
+ break;
+ }
+ case CTRL('C'):
+ if (ldisc->cfg->telnet_keyboard) {
+ ldisc->back->special(ldisc->backhandle, TS_IP);
+ break;
+ }
+ case CTRL('Z'):
+ if (ldisc->cfg->telnet_keyboard) {
+ ldisc->back->special(ldisc->backhandle, TS_SUSP);
+ break;
+ }
+
+ default:
+ ldisc->back->send(ldisc->backhandle, buf, len);
+ break;
+ }
+ } else
+ ldisc->back->send(ldisc->backhandle, buf, len);
+ }
+ }
+}
--- /dev/null
+/*
+ * ldisc.h: defines the Ldisc data structure used by ldisc.c and
+ * ldiscucs.c. (Unfortunately it was necessary to split the ldisc
+ * module in two, to avoid unnecessarily linking in the Unicode
+ * stuff in tools that don't require it.)
+ */
+
+#ifndef PUTTY_LDISC_H
+#define PUTTY_LDISC_H
+
+typedef struct ldisc_tag {
+ Terminal *term;
+ Backend *back;
+ Config *cfg;
+ void *backhandle;
+ void *frontend;
+
+ char *buf;
+ int buflen, bufsiz, quotenext;
+} *Ldisc;
+
+#endif /* PUTTY_LDISC_H */
--- /dev/null
+/*
+ * ldisc.c: PuTTY line discipline. Sits between the input coming
+ * from keypresses in the window, and the output channel leading to
+ * the back end. Implements echo and/or local line editing,
+ * depending on what's currently configured.
+ */
+
+#include <stdio.h>
+#include <ctype.h>
+
+#include "putty.h"
+#include "terminal.h"
+#include "ldisc.h"
+
+void lpage_send(void *handle,
+ int codepage, char *buf, int len, int interactive)
+{
+ Ldisc ldisc = (Ldisc)handle;
+ wchar_t *widebuffer = 0;
+ int widesize = 0;
+ int wclen;
+
+ if (codepage < 0) {
+ ldisc_send(ldisc, buf, len, interactive);
+ return;
+ }
+
+ widesize = len * 2;
+ widebuffer = snewn(widesize, wchar_t);
+
+ wclen = mb_to_wc(codepage, 0, buf, len, widebuffer, widesize);
+ luni_send(ldisc, widebuffer, wclen, interactive);
+
+ sfree(widebuffer);
+}
+
+void luni_send(void *handle, wchar_t * widebuf, int len, int interactive)
+{
+ Ldisc ldisc = (Ldisc)handle;
+ int ratio = (in_utf(ldisc->term))?3:1;
+ char *linebuffer;
+ int linesize;
+ int i;
+ char *p;
+
+ linesize = len * ratio * 2;
+ linebuffer = snewn(linesize, char);
+
+ 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) {
+#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);
+ i++;
+ }
+ } else
+#endif
+ {
+ /* Unrecognised UTF-16 sequence */
+ ch = '.';
+ }
+ }
+
+ if (ch < 0x80) {
+ *p++ = (char) (ch);
+ } else if (ch < 0x800) {
+ *p++ = (char) (0xC0 | (ch >> 6));
+ *p++ = (char) (0x80 | (ch & 0x3F));
+ } else if (ch < 0x10000) {
+ *p++ = (char) (0xE0 | (ch >> 12));
+ *p++ = (char) (0x80 | ((ch >> 6) & 0x3F));
+ *p++ = (char) (0x80 | (ch & 0x3F));
+ } else {
+ *p++ = (char) (0xF0 | (ch >> 18));
+ *p++ = (char) (0x80 | ((ch >> 12) & 0x3F));
+ *p++ = (char) (0x80 | ((ch >> 6) & 0x3F));
+ *p++ = (char) (0x80 | (ch & 0x3F));
+ }
+ }
+ } else {
+ int rv;
+ rv = wc_to_mb(ldisc->term->ucsdata->line_codepage, 0, widebuf, len,
+ linebuffer, linesize, NULL, NULL, ldisc->term->ucsdata);
+ if (rv >= 0)
+ p = linebuffer + rv;
+ else
+ p = linebuffer;
+ }
+ if (p > linebuffer)
+ ldisc_send(ldisc, linebuffer, p - linebuffer, interactive);
+
+ sfree(linebuffer);
+}
--- /dev/null
+/*
+ * Session logging.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+
+#include <time.h>
+#include <assert.h>
+
+#include "putty.h"
+
+/* log session to file stuff ... */
+struct LogContext {
+ FILE *lgfp;
+ enum { L_CLOSED, L_OPENING, L_OPEN, L_ERROR } state;
+ bufchain queue;
+ Filename currlogfilename;
+ void *frontend;
+ Config cfg;
+};
+
+static void xlatlognam(Filename *d, 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
+ * opened asynchronously, etc.
+ */
+static void logwrite(struct LogContext *ctx, void *data, int len)
+{
+ /*
+ * In state L_CLOSED, we call logfopen, which will set the state
+ * to one of L_OPENING, L_OPEN or L_ERROR. Hence we process all of
+ * those three _after_ processing L_CLOSED.
+ */
+ if (ctx->state == L_CLOSED)
+ logfopen(ctx);
+
+ if (ctx->state == L_OPENING) {
+ bufchain_add(&ctx->queue, data, len);
+ } else if (ctx->state == L_OPEN) {
+ assert(ctx->lgfp);
+ if (fwrite(data, 1, len, ctx->lgfp) < (size_t)len) {
+ logfclose(ctx);
+ ctx->state = L_ERROR;
+ /* Log state is L_ERROR so this won't cause a loop */
+ logevent(ctx->frontend,
+ "Disabled writing session log due to error while writing");
+ }
+ } /* else L_ERROR, so ignore the write */
+}
+
+/*
+ * Convenience wrapper on logwrite() which printf-formats the
+ * string.
+ */
+static void logprintf(struct LogContext *ctx, const char *fmt, ...)
+{
+ va_list ap;
+ char *data;
+
+ va_start(ap, fmt);
+ data = dupvprintf(fmt, ap);
+ va_end(ap);
+
+ logwrite(ctx, data, strlen(data));
+ sfree(data);
+}
+
+/*
+ * Flush any open log file.
+ */
+void logflush(void *handle) {
+ struct LogContext *ctx = (struct LogContext *)handle;
+ if (ctx->cfg.logtype > 0)
+ if (ctx->state == L_OPEN)
+ fflush(ctx->lgfp);
+}
+
+static void logfopen_callback(void *handle, int mode)
+{
+ struct LogContext *ctx = (struct LogContext *)handle;
+ char buf[256], *event;
+ struct tm tm;
+ const char *fmode;
+
+ if (mode == 0) {
+ ctx->state = L_ERROR; /* disable logging */
+ } else {
+ fmode = (mode == 1 ? "ab" : "wb");
+ ctx->lgfp = f_open(ctx->currlogfilename, fmode, FALSE);
+ if (ctx->lgfp)
+ ctx->state = L_OPEN;
+ else
+ ctx->state = L_ERROR;
+ }
+
+ if (ctx->state == L_OPEN) {
+ /* Write header line into log file. */
+ tm = ltime();
+ strftime(buf, 24, "%Y.%m.%d %H:%M:%S", &tm);
+ logprintf(ctx, "=~=~=~=~=~=~=~=~=~=~=~= PuTTY log %s"
+ " =~=~=~=~=~=~=~=~=~=~=~=\r\n", buf);
+ }
+
+ 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" :
+ "unknown"),
+ filename_to_str(&ctx->currlogfilename));
+ logevent(ctx->frontend, event);
+ sfree(event);
+
+ /*
+ * Having either succeeded or failed in opening the log file,
+ * we should write any queued data out.
+ */
+ assert(ctx->state != L_OPENING); /* make _sure_ it won't be requeued */
+ while (bufchain_size(&ctx->queue)) {
+ void *data;
+ int len;
+ bufchain_prefix(&ctx->queue, &data, &len);
+ logwrite(ctx, data, len);
+ bufchain_consume(&ctx->queue, len);
+ }
+}
+
+/*
+ * Open the log file. Takes care of detecting an already-existing
+ * file and asking the user whether they want to append, overwrite
+ * or cancel logging.
+ */
+void logfopen(void *handle)
+{
+ struct LogContext *ctx = (struct LogContext *)handle;
+ struct tm tm;
+ int mode;
+
+ /* Prevent repeat calls */
+ if (ctx->state != L_CLOSED)
+ return;
+
+ if (!ctx->cfg.logtype)
+ return;
+
+ tm = ltime();
+
+ /* substitute special codes in file name */
+ xlatlognam(&ctx->currlogfilename, ctx->cfg.logfilename,ctx->cfg.host, &tm);
+
+ ctx->lgfp = f_open(ctx->currlogfilename, "r", FALSE); /* file already present? */
+ if (ctx->lgfp) {
+ fclose(ctx->lgfp);
+ if (ctx->cfg.logxfovr != LGXF_ASK) {
+ mode = ((ctx->cfg.logxfovr == LGXF_OVR) ? 2 : 1);
+ } else
+ mode = askappend(ctx->frontend, ctx->currlogfilename,
+ logfopen_callback, ctx);
+ } else
+ mode = 2; /* create == overwrite */
+
+ if (mode < 0)
+ ctx->state = L_OPENING;
+ else
+ logfopen_callback(ctx, mode); /* open the file */
+}
+
+void logfclose(void *handle)
+{
+ struct LogContext *ctx = (struct LogContext *)handle;
+ if (ctx->lgfp) {
+ fclose(ctx->lgfp);
+ ctx->lgfp = NULL;
+ }
+ ctx->state = L_CLOSED;
+}
+
+/*
+ * 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)
+ logwrite(ctx, &c, 1);
+ }
+}
+
+/*
+ * Log an Event Log entry. Used in SSH packet logging mode; this is
+ * also as convenient a place as any to put the output of Event Log
+ * entries to stderr when a command-line tool is in verbose mode.
+ * (In particular, this is a better place to put it than in the
+ * front ends, because it only has to be done once for all
+ * platforms. Platforms which don't have a meaningful stderr can
+ * just avoid defining FLAG_STDERR.
+ */
+void log_eventlog(void *handle, const char *event)
+{
+ struct LogContext *ctx = (struct LogContext *)handle;
+ if ((flags & FLAG_STDERR) && (flags & FLAG_VERBOSE)) {
+ fprintf(stderr, "%s\n", event);
+ 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)
+ return;
+ logprintf(ctx, "Event Log: %s\r\n", event);
+ logflush(ctx);
+}
+
+/*
+ * Log an SSH packet.
+ * If n_blanks != 0, blank or omit some parts.
+ * 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)
+{
+ 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)))
+ 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");
+ }
+
+ /*
+ * Output a hex/ASCII dump of the packet body, blanking/omitting
+ * parts as specified.
+ */
+ while (p < len) {
+ int blktype;
+
+ /* Move to a current entry in the blanking array. */
+ while ((b < n_blanks) &&
+ (p >= blanks[b].offset + blanks[b].len))
+ b++;
+ /* Work out what type of blanking to apply to
+ * this byte. */
+ blktype = PKTLOG_EMIT; /* default */
+ if ((b < n_blanks) &&
+ (p >= blanks[b].offset) &&
+ (p < blanks[b].offset + blanks[b].len))
+ blktype = blanks[b].type;
+
+ /* If we're about to stop omitting, it's time to say how
+ * much we omitted. */
+ if ((blktype != PKTLOG_OMIT) && omitted) {
+ logprintf(ctx, " (%d byte%s omitted)\r\n",
+ omitted, (omitted==1?"":"s"));
+ omitted = 0;
+ }
+
+ /* (Re-)initialise dumpdata as necessary
+ * (start of row, or if we've just stopped omitting) */
+ if (!output_pos && !omitted)
+ sprintf(dumpdata, " %08x%*s\r\n", p-(p%16), 1+3*16+2+16, "");
+
+ /* Deal with the current byte. */
+ if (blktype == PKTLOG_OMIT) {
+ omitted++;
+ } else {
+ int c;
+ if (blktype == PKTLOG_BLANK) {
+ c = 'X';
+ sprintf(smalldata, "XX");
+ } else { /* PKTLOG_EMIT */
+ c = ((unsigned char *)data)[p];
+ sprintf(smalldata, "%02x", c);
+ }
+ dumpdata[10+2+3*(p%16)] = smalldata[0];
+ dumpdata[10+2+3*(p%16)+1] = smalldata[1];
+ dumpdata[10+1+3*16+2+(p%16)] = (isprint(c) ? c : '.');
+ output_pos = (p%16) + 1;
+ }
+
+ p++;
+
+ /* Flush row if necessary */
+ if (((p % 16) == 0) || (p == len) || omitted) {
+ if (output_pos) {
+ strcpy(dumpdata + 10+1+3*16+2+output_pos, "\r\n");
+ logwrite(ctx, dumpdata, strlen(dumpdata));
+ output_pos = 0;
+ }
+ }
+
+ }
+
+ /* Tidy up */
+ if (omitted)
+ logprintf(ctx, " (%d byte%s omitted)\r\n",
+ omitted, (omitted==1?"":"s"));
+ logflush(ctx);
+}
+
+void *log_init(void *frontend, Config *cfg)
+{
+ struct LogContext *ctx = snew(struct LogContext);
+ ctx->lgfp = NULL;
+ ctx->state = L_CLOSED;
+ ctx->frontend = frontend;
+ ctx->cfg = *cfg; /* STRUCTURE COPY */
+ bufchain_init(&ctx->queue);
+ return ctx;
+}
+
+void log_free(void *handle)
+{
+ struct LogContext *ctx = (struct LogContext *)handle;
+
+ logfclose(ctx);
+ bufchain_clear(&ctx->queue);
+ sfree(ctx);
+}
+
+void log_reconfig(void *handle, Config *cfg)
+{
+ struct LogContext *ctx = (struct LogContext *)handle;
+ int reset_logging;
+
+ if (!filename_equal(ctx->cfg.logfilename, cfg->logfilename) ||
+ ctx->cfg.logtype != cfg->logtype)
+ reset_logging = TRUE;
+ else
+ reset_logging = FALSE;
+
+ if (reset_logging)
+ logfclose(ctx);
+
+ ctx->cfg = *cfg; /* STRUCTURE COPY */
+
+ if (reset_logging)
+ logfopen(ctx);
+}
+
+/*
+ * translate format codes into time/date strings
+ * and insert them into log file name
+ *
+ * "&Y":YYYY "&m":MM "&d":DD "&T":hhmmss "&h":<hostname> "&&":&
+ */
+static void xlatlognam(Filename *dest, Filename src,
+ char *hostname, struct tm *tm) {
+ char buf[10], *bufp;
+ int size;
+ char buffer[FILENAME_MAX];
+ int len = sizeof(buffer)-1;
+ char *d;
+ const char *s;
+
+ d = buffer;
+ s = filename_to_str(&src);
+
+ while (*s) {
+ /* Let (bufp, len) be the string to append. */
+ bufp = buf; /* don't usually override this */
+ if (*s == '&') {
+ char c;
+ s++;
+ size = 0;
+ if (*s) switch (c = *s++, tolower((unsigned char)c)) {
+ case 'y':
+ size = strftime(buf, sizeof(buf), "%Y", tm);
+ break;
+ case 'm':
+ size = strftime(buf, sizeof(buf), "%m", tm);
+ break;
+ case 'd':
+ size = strftime(buf, sizeof(buf), "%d", tm);
+ break;
+ case 't':
+ size = strftime(buf, sizeof(buf), "%H%M%S", tm);
+ break;
+ case 'h':
+ bufp = hostname;
+ size = strlen(bufp);
+ break;
+ default:
+ buf[0] = '&';
+ size = 1;
+ if (c != '&')
+ buf[size++] = c;
+ }
+ } 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);
+}
--- /dev/null
+This directory contains a Mac OS X port of PuTTY/pterm, running as a
+native Aqua GUI application.
+
+THIS PORT IS CURRENTLY UNFINISHED AND EXPERIMENTAL. It is _not_
+considered to be of release quality, even if you've found it (and
+are reading this) in a PuTTY release source archive. You are welcome
+to try using it, but don't be surprised at unexpected behaviour. I'm
+not kidding.
+
+In particular, I have not yet decided where OS X PuTTY should store
+its configuration data. Options include storing it in ~/.putty to be
+compatible with Unix PuTTY, storing it wherever is compatible with
+Mac Classic PuTTY, storing it in a natively OS X location, or
+sorting out the `config-locations' wishlist item and doing all
+three. Therefore, if you start using this port and create a whole
+load of saved sessions, you should not be surprised if a future
+version of the port decides to look somewhere completely different
+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:
+
+Missing terminal window features
+--------------------------------
+
+ - terminal display is horribly slow
+
+ - fonts aren't configurable
+
+ - several features are unimplemented in the terminal display:
+ underlining, non-solid-block cursors, double-width and
+ double-height line attributes, bold as font rather than as
+ colour, wide (CJK) characters, combining characters.
+
+ - there's no scrollbar
+
+ - terminal window resizing isn't implemented yet
+
+ - proper window placement (cascading down and right from the
+ starting position, plus remembering previous window positions per
+ the Apple HIG) is not implemented
+
+Missing alert box features
+--------------------------
+
+ - warn-on-close isn't implemented
+
+Missing input features
+----------------------
+
+ - use of Alt+numberpad to enter arbitrary numeric character codes
+ is not yet supported
+
+ - there's no Meta key yet. (I'd like to at least have the
+ possibility of using Command rather than Option as the Meta key,
+ since the latter is necessary to send some characters, including
+ the rather important # on Apple UK keyboards; but trapping
+ Command-<key> and sending it to the window rather than the
+ application menu requires me to make a positive effort of some
+ sort and I haven't got round to it yet. For those Mac users who
+ consider their Command key sacrosanct, don't worry, this option
+ _will_ be configurable and _will_ be off by default.)
+
+ - there's no specials menu
+
+ - mouse activity isn't supported (neither cut-and-paste nor xterm
+ mouse tracking)
+
+Missing terminal emulation features
+-----------------------------------
+
+ - currently no support for server-side window management requests
+ (i.e. escape sequences to minimise or maximise the window,
+ request or change its position and size, change its title etc)
+
+ - window title is currently fixed
+
+Other missing features
+----------------------
+
+ - no Event Log
+
+ - no mid-session Change Settings
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleIconFile</key>
+ <string>PuTTY.icns</string>
+</dict>
+</plist>
--- /dev/null
+#ifndef PUTTY_OSX_H
+#define PUTTY_OSX_H
+
+/*
+ * Cocoa defines `FontSpec' itself, so we must change its name.
+ * (Arrgh.)
+ */
+#define FontSpec FontSpec_OSX_Proof
+
+/*
+ * Define the various compatibility symbols to make uxpty.c compile
+ * correctly on OS X.
+ */
+#define BSD_PTYS
+#define OMIT_UTMP
+#define HAVE_NO_SETRESUID
+#define NOT_X_WINDOWS
+
+/*
+ * OS X is largely just Unix, so we can include most of this
+ * unchanged.
+ */
+#include "unix.h"
+
+/*
+ * Functions exported by osxsel.m. (Both of these functions are
+ * expected to be called in the _main_ thread: the select subthread
+ * is an implementation detail of osxsel.m and ideally should not
+ * be visible at all outside it.)
+ */
+void osxsel_init(void); /* call this to kick things off */
+void osxsel_process_results(void); /* call this on receipt of a netevent */
+
+#endif
--- /dev/null
+/*
+ * Header file for the Objective-C parts of Mac OS X PuTTY. This
+ * file contains the class definitions, which would cause compile
+ * failures in the pure C modules if they appeared in osx.h.
+ */
+
+#ifndef PUTTY_OSXCLASS_H
+#define PUTTY_OSXCLASS_H
+
+#include "putty.h"
+
+/*
+ * The application controller class, defined in osxmain.m.
+ */
+@interface AppController : NSObject
+{
+ NSTimer *timer;
+}
+- (void)newSessionConfig:(id)sender;
+- (void)newTerminal:(id)sender;
+- (void)newSessionWithConfig:(id)cfg;
+- (void)setTimer:(long)next;
+@end
+extern AppController *controller;
+
+/*
+ * The SessionWindow class, defined in osxwin.m.
+ */
+
+struct alert_queue {
+ struct alert_queue *next;
+ NSAlert *alert;
+ void (*callback)(void *, int);
+ void *ctx;
+};
+
+@class SessionWindow;
+@class TerminalView;
+
+@interface SessionWindow : NSWindow
+{
+ Terminal *term;
+ TerminalView *termview;
+ struct unicode_data ucsdata;
+ void *logctx;
+ Config cfg;
+ void *ldisc;
+ Backend *back;
+ void *backhandle;
+ int exited;
+ /*
+ * The following two members relate to the currently active
+ * alert sheet, if any. They are NULL if there isn't one.
+ */
+ void (*alert_callback)(void *, int);
+ void *alert_ctx;
+ /* This queues future alerts that need to be shown. */
+ struct alert_queue *alert_qhead, *alert_qtail;
+}
+- (id)initWithConfig:(Config)cfg;
+- (void)drawStartFinish:(BOOL)start;
+- (void)setColour:(int)n r:(float)r g:(float)g b:(float)b;
+- (Config *)cfg;
+- (void)doText:(wchar_t *)text len:(int)len x:(int)x y:(int)y
+ attr:(unsigned long)attr lattr:(int)lattr;
+- (int)fromBackend:(const char *)data len:(int)len isStderr:(int)is_stderr;
+- (int)fromBackendUntrusted:(const char *)data len:(int)len;
+- (void)startAlert:(NSAlert *)alert
+ withCallback:(void (*)(void *, int))callback andCtx:(void *)ctx;
+- (void)endSession:(int)clean;
+- (void)notifyRemoteExit;
+- (Terminal *)term;
+@end
+
+/*
+ * The ConfigWindow class, defined in osxdlg.m.
+ */
+
+@class ConfigWindow;
+
+@interface ConfigWindow : NSWindow
+{
+ NSOutlineView *treeview;
+ struct controlbox *ctrlbox;
+ void *dv;
+ Config cfg;
+}
+- (id)initWithConfig:(Config)cfg;
+@end
+
+/*
+ * Functions exported by osxctrls.m. (They have to go in this
+ * header file and not osx.h, because some of them have Cocoa class
+ * types in their prototypes.)
+ */
+#define HSPACING 12 /* needed in osxdlg.m and osxctrls.m */
+#define VSPACING 8
+
+void *fe_dlg_init(void *data, NSWindow *window, NSObject *target, SEL action);
+void fe_dlg_free(void *dv);
+void create_ctrls(void *dv, NSView *parent, struct controlset *s,
+ int *minw, int *minh);
+int place_ctrls(void *dv, struct controlset *s, int leftx, int topy,
+ int width); /* returns height used */
+void select_panel(void *dv, struct controlbox *b, const char *name);
+
+#endif /* PUTTY_OSXCLASS_H */
--- /dev/null
+/*
+ * osxctrls.m: OS X implementation of the dialog.h interface.
+ */
+
+#import <Cocoa/Cocoa.h>
+#include "putty.h"
+#include "dialog.h"
+#include "osxclass.h"
+#include "tree234.h"
+
+/*
+ * Still to be implemented:
+ *
+ * - file selectors (NSOpenPanel / NSSavePanel)
+ *
+ * - font selectors
+ * - colour selectors
+ * * both of these have a conceptual oddity in Cocoa that
+ * you're only supposed to have one per application. But I
+ * currently expect to be able to have multiple PuTTY config
+ * boxes on screen at once; what happens if you trigger the
+ * font selector in each one at the same time?
+ * * if it comes to that, the _font_ selector can probably be
+ * managed by other means: nobody is forcing me to implement
+ * a font selector using a `Change...' button. The portable
+ * dialog interface gives me the flexibility to do this how I
+ * want.
+ * * The colour selector interface, in its present form, is
+ * more interesting and _if_ a radical change of plan is
+ * required then it may stretch across the interface into the
+ * portable side.
+ * * Before I do anything rash I should start by looking at the
+ * Mac Classic port and see how it's done there, on the basis
+ * that Apple seem reasonably unlikely to have invented this
+ * crazy restriction specifically for OS X.
+ *
+ * - focus management
+ * * I tried using makeFirstResponder to give keyboard focus,
+ * but it appeared not to work. Try again, and work out how
+ * it should be done.
+ * * also look into tab order. Currently pressing Tab suggests
+ * that only edit boxes and list boxes can get the keyboard
+ * focus, and that buttons (in all their forms) are unable to
+ * be driven by the keyboard. Find out for sure.
+ *
+ * - dlg_error_msg
+ * * this may run into the usual aggro with modal dialog boxes.
+ */
+
+/*
+ * For Cocoa control layout, I need a two-stage process. In stage
+ * one, I allocate all the controls and measure their natural
+ * sizes, which allows me to compute the _minimum_ width and height
+ * of a given section of dialog. Then, in stage two, I lay out the
+ * dialog box as a whole, decide how much each section of the box
+ * needs to receive, and assign it its final size.
+ */
+
+/*
+ * As yet unsolved issues [FIXME]:
+ *
+ * - Sometimes the height returned from create_ctrls and the
+ * height returned from place_ctrls differ. Find out why. It may
+ * be harmless (e.g. results of NSTextView being odd), but I
+ * want to know.
+ *
+ * - NSTextViews are indented a bit. It'd be nice to put their
+ * left margin at the same place as everything else's.
+ *
+ * - I don't yet know whether we even _can_ support tab order or
+ * keyboard shortcuts. If we can't, then fair enough, we can't.
+ * But if we can, we should.
+ *
+ * - I would _really_ like to know of a better way to correct
+ * NSButton's stupid size estimates than by subclassing it and
+ * overriding sizeToFit with hard-wired sensible values!
+ *
+ * - Speaking of stupid size estimates, the amount by which I'm
+ * adjusting a titled NSBox (currently equal to the point size
+ * of its title font) looks as if it isn't _quite_ enough.
+ * Figure out what the real amount should be and use it.
+ *
+ * - I don't understand why there's always a scrollbar displayed
+ * in each list box. I thought I told it to autohide scrollers?
+ *
+ * - Why do I have to fudge list box heights by adding one? (Might
+ * it be to do with the missing header view?)
+ */
+
+/*
+ * Subclass of NSButton which corrects the fact that the normal
+ * one's sizeToFit method persistently returns 32 as its height,
+ * which is simply a lie. I have yet to work out a better
+ * alternative than hard-coding the real heights.
+ */
+@interface MyButton : NSButton
+{
+ int minht;
+}
+@end
+@implementation MyButton
+- (id)initWithFrame:(NSRect)r
+{
+ self = [super initWithFrame:r];
+ minht = 25;
+ return self;
+}
+- (void)setButtonType:(NSButtonType)t
+{
+ if (t == NSRadioButton || t == NSSwitchButton)
+ minht = 18;
+ else
+ minht = 25;
+ [super setButtonType:t];
+}
+- (void)sizeToFit
+{
+ NSRect r;
+ [super sizeToFit];
+ r = [self frame];
+ r.size.height = minht;
+ [self setFrame:r];
+}
+@end
+
+/*
+ * Class used as the data source for NSTableViews.
+ */
+@interface MyTableSource : NSObject
+{
+ tree234 *tree;
+}
+- (id)init;
+- (void)add:(const char *)str withId:(int)id;
+- (int)getid:(int)index;
+- (void)swap:(int)index1 with:(int)index2;
+- (void)removestr:(int)index;
+- (void)clear;
+@end
+@implementation MyTableSource
+- (id)init
+{
+ self = [super init];
+ tree = newtree234(NULL);
+ return self;
+}
+- (void)dealloc
+{
+ char *p;
+ while ((p = delpos234(tree, 0)) != NULL)
+ sfree(p);
+ freetree234(tree);
+ [super dealloc];
+}
+- (void)add:(const char *)str withId:(int)id
+{
+ addpos234(tree, dupprintf("%d\t%s", id, str), count234(tree));
+}
+- (int)getid:(int)index
+{
+ char *p = index234(tree, index);
+ return atoi(p);
+}
+- (void)removestr:(int)index
+{
+ char *p = delpos234(tree, index);
+ sfree(p);
+}
+- (void)swap:(int)index1 with:(int)index2
+{
+ char *p1, *p2;
+
+ if (index1 > index2) {
+ int t = index1; index1 = index2; index2 = t;
+ }
+
+ /* delete later one first so it doesn't affect index of earlier one */
+ p2 = delpos234(tree, index2);
+ p1 = delpos234(tree, index1);
+
+ /* now insert earlier one before later one for the inverse reason */
+ addpos234(tree, p2, index1);
+ addpos234(tree, p1, index2);
+}
+- (void)clear
+{
+ char *p;
+ while ((p = delpos234(tree, 0)) != NULL)
+ sfree(p);
+}
+- (int)numberOfRowsInTableView:(NSTableView *)aTableView
+{
+ return count234(tree);
+}
+- (id)tableView:(NSTableView *)aTableView
+ objectValueForTableColumn:(NSTableColumn *)aTableColumn
+ row:(int)rowIndex
+{
+ int j = [[aTableColumn identifier] intValue];
+ char *p = index234(tree, rowIndex);
+
+ while (j >= 0) {
+ p += strcspn(p, "\t");
+ if (*p) p++;
+ j--;
+ }
+
+ return [NSString stringWithCString:p length:strcspn(p, "\t")];
+}
+@end
+
+/*
+ * Object to receive messages from various control classes.
+ */
+@class Receiver;
+
+struct fe_dlg {
+ NSWindow *window;
+ NSObject *target;
+ SEL action;
+ tree234 *byctrl;
+ tree234 *bywidget;
+ tree234 *boxes;
+ void *data; /* passed to portable side */
+ Receiver *rec;
+};
+
+@interface Receiver : NSObject
+{
+ struct fe_dlg *d;
+}
+- (id)initWithStruct:(struct fe_dlg *)aStruct;
+@end
+
+struct fe_ctrl {
+ union control *ctrl;
+ NSButton *button, *button2;
+ NSTextField *label, *editbox;
+ NSComboBox *combobox;
+ NSButton **radiobuttons;
+ NSTextView *textview;
+ NSPopUpButton *popupbutton;
+ NSTableView *tableview;
+ NSScrollView *scrollview;
+ int nradiobuttons;
+ void *privdata;
+ int privdata_needs_free;
+};
+
+static int fe_ctrl_cmp_by_ctrl(void *av, void *bv)
+{
+ struct fe_ctrl *a = (struct fe_ctrl *)av;
+ struct fe_ctrl *b = (struct fe_ctrl *)bv;
+
+ if (a->ctrl < b->ctrl)
+ return -1;
+ if (a->ctrl > b->ctrl)
+ return +1;
+ return 0;
+}
+
+static int fe_ctrl_find_by_ctrl(void *av, void *bv)
+{
+ union control *a = (union control *)av;
+ struct fe_ctrl *b = (struct fe_ctrl *)bv;
+
+ if (a < b->ctrl)
+ return -1;
+ if (a > b->ctrl)
+ return +1;
+ return 0;
+}
+
+struct fe_box {
+ struct controlset *s;
+ id box;
+};
+
+static int fe_boxcmp(void *av, void *bv)
+{
+ struct fe_box *a = (struct fe_box *)av;
+ struct fe_box *b = (struct fe_box *)bv;
+
+ if (a->s < b->s)
+ return -1;
+ if (a->s > b->s)
+ return +1;
+ return 0;
+}
+
+static int fe_boxfind(void *av, void *bv)
+{
+ struct controlset *a = (struct controlset *)av;
+ struct fe_box *b = (struct fe_box *)bv;
+
+ if (a < b->s)
+ return -1;
+ if (a > b->s)
+ return +1;
+ return 0;
+}
+
+struct fe_backwards { /* map Cocoa widgets back to fe_ctrls */
+ id widget;
+ struct fe_ctrl *c;
+};
+
+static int fe_backwards_cmp_by_widget(void *av, void *bv)
+{
+ struct fe_backwards *a = (struct fe_backwards *)av;
+ struct fe_backwards *b = (struct fe_backwards *)bv;
+
+ if (a->widget < b->widget)
+ return -1;
+ if (a->widget > b->widget)
+ return +1;
+ return 0;
+}
+
+static int fe_backwards_find_by_widget(void *av, void *bv)
+{
+ id a = (id)av;
+ struct fe_backwards *b = (struct fe_backwards *)bv;
+
+ if (a < b->widget)
+ return -1;
+ if (a > b->widget)
+ return +1;
+ return 0;
+}
+
+static struct fe_ctrl *fe_ctrl_new(union control *ctrl)
+{
+ struct fe_ctrl *c;
+
+ c = snew(struct fe_ctrl);
+ c->ctrl = ctrl;
+
+ c->button = c->button2 = nil;
+ c->label = nil;
+ c->editbox = nil;
+ c->combobox = nil;
+ c->textview = nil;
+ c->popupbutton = nil;
+ c->tableview = nil;
+ c->scrollview = nil;
+ c->radiobuttons = NULL;
+ c->nradiobuttons = 0;
+ c->privdata = NULL;
+ c->privdata_needs_free = FALSE;
+
+ return c;
+}
+
+static void fe_ctrl_free(struct fe_ctrl *c)
+{
+ if (c->privdata_needs_free)
+ sfree(c->privdata);
+ sfree(c->radiobuttons);
+ sfree(c);
+}
+
+static struct fe_ctrl *fe_ctrl_byctrl(struct fe_dlg *d, union control *ctrl)
+{
+ return find234(d->byctrl, ctrl, fe_ctrl_find_by_ctrl);
+}
+
+static void add_box(struct fe_dlg *d, struct controlset *s, id box)
+{
+ struct fe_box *b = snew(struct fe_box);
+ b->box = box;
+ b->s = s;
+ add234(d->boxes, b);
+}
+
+static id find_box(struct fe_dlg *d, struct controlset *s)
+{
+ struct fe_box *b = find234(d->boxes, s, fe_boxfind);
+ return b ? b->box : NULL;
+}
+
+static void add_widget(struct fe_dlg *d, struct fe_ctrl *c, id widget)
+{
+ struct fe_backwards *b = snew(struct fe_backwards);
+ b->widget = widget;
+ b->c = c;
+ add234(d->bywidget, b);
+}
+
+static struct fe_ctrl *find_widget(struct fe_dlg *d, id widget)
+{
+ struct fe_backwards *b = find234(d->bywidget, widget,
+ fe_backwards_find_by_widget);
+ return b ? b->c : NULL;
+}
+
+void *fe_dlg_init(void *data, NSWindow *window, NSObject *target, SEL action)
+{
+ struct fe_dlg *d;
+
+ d = snew(struct fe_dlg);
+ d->window = window;
+ d->target = target;
+ d->action = action;
+ d->byctrl = newtree234(fe_ctrl_cmp_by_ctrl);
+ d->bywidget = newtree234(fe_backwards_cmp_by_widget);
+ d->boxes = newtree234(fe_boxcmp);
+ d->data = data;
+ d->rec = [[Receiver alloc] initWithStruct:d];
+
+ return d;
+}
+
+void fe_dlg_free(void *dv)
+{
+ struct fe_dlg *d = (struct fe_dlg *)dv;
+ struct fe_ctrl *c;
+ struct fe_box *b;
+
+ while ( (c = delpos234(d->byctrl, 0)) != NULL )
+ fe_ctrl_free(c);
+ freetree234(d->byctrl);
+
+ while ( (c = delpos234(d->bywidget, 0)) != NULL )
+ sfree(c);
+ freetree234(d->bywidget);
+
+ while ( (b = delpos234(d->boxes, 0)) != NULL )
+ sfree(b);
+ freetree234(d->boxes);
+
+ [d->rec release];
+
+ sfree(d);
+}
+
+@implementation Receiver
+- (id)initWithStruct:(struct fe_dlg *)aStruct
+{
+ self = [super init];
+ d = aStruct;
+ return self;
+}
+- (void)buttonPushed:(id)sender
+{
+ struct fe_ctrl *c = find_widget(d, sender);
+
+ assert(c && c->ctrl->generic.type == CTRL_BUTTON);
+ c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_ACTION);
+}
+- (void)checkboxChanged:(id)sender
+{
+ struct fe_ctrl *c = find_widget(d, sender);
+
+ assert(c && c->ctrl->generic.type == CTRL_CHECKBOX);
+ c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_VALCHANGE);
+}
+- (void)radioChanged:(id)sender
+{
+ struct fe_ctrl *c = find_widget(d, sender);
+ int j;
+
+ assert(c && c->radiobuttons);
+ for (j = 0; j < c->nradiobuttons; j++)
+ if (sender != c->radiobuttons[j])
+ [c->radiobuttons[j] setState:NSOffState];
+ c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_VALCHANGE);
+}
+- (void)popupMenuSelected:(id)sender
+{
+ struct fe_ctrl *c = find_widget(d, sender);
+ c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_VALCHANGE);
+}
+- (void)controlTextDidChange:(NSNotification *)notification
+{
+ id widget = [notification object];
+ struct fe_ctrl *c = find_widget(d, widget);
+ assert(c && c->ctrl->generic.type == CTRL_EDITBOX);
+ c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_VALCHANGE);
+}
+- (void)controlTextDidEndEditing:(NSNotification *)notification
+{
+ id widget = [notification object];
+ struct fe_ctrl *c = find_widget(d, widget);
+ assert(c && c->ctrl->generic.type == CTRL_EDITBOX);
+ c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_REFRESH);
+}
+- (void)tableViewSelectionDidChange:(NSNotification *)notification
+{
+ id widget = [notification object];
+ struct fe_ctrl *c = find_widget(d, widget);
+ assert(c && c->ctrl->generic.type == CTRL_LISTBOX);
+ c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_SELCHANGE);
+}
+- (BOOL)tableView:(NSTableView *)aTableView
+ shouldEditTableColumn:(NSTableColumn *)aTableColumn
+ row:(int)rowIndex
+{
+ return NO; /* no editing permitted */
+}
+- (void)listDoubleClicked:(id)sender
+{
+ struct fe_ctrl *c = find_widget(d, sender);
+ assert(c && c->ctrl->generic.type == CTRL_LISTBOX);
+ c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_ACTION);
+}
+- (void)dragListButton:(id)sender
+{
+ struct fe_ctrl *c = find_widget(d, sender);
+ int direction, row, nrows;
+ assert(c && c->ctrl->generic.type == CTRL_LISTBOX &&
+ c->ctrl->listbox.draglist);
+
+ if (sender == c->button)
+ direction = -1; /* up */
+ else
+ direction = +1; /* down */
+
+ row = [c->tableview selectedRow];
+ nrows = [c->tableview numberOfRows];
+
+ if (row + direction < 0 || row + direction >= nrows) {
+ NSBeep();
+ return;
+ }
+
+ [[c->tableview dataSource] swap:row with:row+direction];
+ [c->tableview reloadData];
+ [c->tableview selectRow:row+direction byExtendingSelection:NO];
+
+ c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_VALCHANGE);
+}
+@end
+
+void create_ctrls(void *dv, NSView *parent, struct controlset *s,
+ int *minw, int *minh)
+{
+ struct fe_dlg *d = (struct fe_dlg *)dv;
+ int ccw[100]; /* cumulative column widths */
+ int cypos[100];
+ int ncols;
+ int wmin = 0, hmin = 0;
+ int i, j, cw, ch;
+ NSRect rect;
+ NSFont *textviewfont = nil;
+ int boxh = 0, boxw = 0;
+
+ if (!s->boxname && s->boxtitle) {
+ /* This controlset is a panel title. */
+
+ NSTextField *tf;
+
+ tf = [[NSTextField alloc] initWithFrame:NSMakeRect(0,0,1,1)];
+ [tf setEditable:NO];
+ [tf setSelectable:NO];
+ [tf setBordered:NO];
+ [tf setDrawsBackground:NO];
+ [tf setStringValue:[NSString stringWithCString:s->boxtitle]];
+ [tf sizeToFit];
+ rect = [tf frame];
+ [parent addSubview:tf];
+
+ /*
+ * I'm going to store this NSTextField in the boxes tree,
+ * because I really can't face having a special tree234
+ * mapping controlsets to panel titles.
+ */
+ add_box(d, s, tf);
+
+ *minw = rect.size.width;
+ *minh = rect.size.height;
+
+ return;
+ }
+
+ if (*s->boxname) {
+ /*
+ * Create an NSBox to contain this subset of controls.
+ */
+ NSBox *box;
+ NSRect tmprect;
+
+ box = [[NSBox alloc] initWithFrame:NSMakeRect(0,0,1,1)];
+ if (s->boxtitle)
+ [box setTitle:[NSString stringWithCString:s->boxtitle]];
+ else
+ [box setTitlePosition:NSNoTitle];
+ add_box(d, s, box);
+ tmprect = [box frame];
+ [box setContentViewMargins:NSMakeSize(20,20)];
+ [box setFrameFromContentFrame:NSMakeRect(100,100,100,100)];
+ rect = [box frame];
+ [box setFrame:tmprect];
+ boxh = (int)(rect.size.height - 100);
+ boxw = (int)(rect.size.width - 100);
+ [parent addSubview:box];
+
+ if (s->boxtitle)
+ boxh += [[box titleFont] pointSize];
+
+ /*
+ * All subsequent controls will be placed within this box.
+ */
+ parent = box;
+ }
+
+ ncols = 1;
+ ccw[0] = 0;
+ ccw[1] = 100;
+ cypos[0] = 0;
+
+ /*
+ * Now iterate through the controls themselves, create them,
+ * and add their width and height to the overall width/height
+ * calculation.
+ */
+ for (i = 0; i < s->ncontrols; i++) {
+ union control *ctrl = s->ctrls[i];
+ struct fe_ctrl *c;
+ int colstart = COLUMN_START(ctrl->generic.column);
+ int colspan = COLUMN_SPAN(ctrl->generic.column);
+ int colend = colstart + colspan;
+ int ytop, wthis;
+
+ switch (ctrl->generic.type) {
+ case CTRL_COLUMNS:
+ for (j = 1; j < ncols; j++)
+ if (cypos[0] < cypos[j])
+ cypos[0] = cypos[j];
+
+ assert(ctrl->columns.ncols < lenof(ccw));
+
+ ccw[0] = 0;
+ for (j = 0; j < ctrl->columns.ncols; j++) {
+ ccw[j+1] = ccw[j] + (ctrl->columns.percentages ?
+ ctrl->columns.percentages[j] : 100);
+ cypos[j] = cypos[0];
+ }
+
+ ncols = ctrl->columns.ncols;
+
+ continue; /* no actual control created */
+ case CTRL_TABDELAY:
+ /*
+ * I'm currently uncertain that we can implement tab
+ * order in OS X.
+ */
+ continue; /* no actual control created */
+ }
+
+ c = fe_ctrl_new(ctrl);
+ add234(d->byctrl, c);
+
+ cw = ch = 0;
+
+ switch (ctrl->generic.type) {
+ case CTRL_BUTTON:
+ case CTRL_CHECKBOX:
+ {
+ NSButton *b;
+
+ b = [[MyButton alloc] initWithFrame:NSMakeRect(0, 0, 1, 1)];
+ [b setBezelStyle:NSRoundedBezelStyle];
+ if (ctrl->generic.type == CTRL_CHECKBOX)
+ [b setButtonType:NSSwitchButton];
+ [b setTitle:[NSString stringWithCString:ctrl->generic.label]];
+ if (ctrl->button.isdefault)
+ [b setKeyEquivalent:@"\r"];
+ else if (ctrl->button.iscancel)
+ [b setKeyEquivalent:@"\033"];
+ [b sizeToFit];
+ rect = [b frame];
+
+ [parent addSubview:b];
+
+ [b setTarget:d->rec];
+ if (ctrl->generic.type == CTRL_CHECKBOX)
+ [b setAction:@selector(checkboxChanged:)];
+ else
+ [b setAction:@selector(buttonPushed:)];
+ add_widget(d, c, b);
+
+ c->button = b;
+
+ cw = rect.size.width;
+ ch = rect.size.height;
+ }
+ break;
+ case CTRL_EDITBOX:
+ {
+ int editp = ctrl->editbox.percentwidth;
+ int labelp = editp == 100 ? 100 : 100 - editp;
+ NSTextField *tf;
+ NSComboBox *cb;
+
+ tf = [[NSTextField alloc] initWithFrame:NSMakeRect(0,0,1,1)];
+ [tf setEditable:NO];
+ [tf setSelectable:NO];
+ [tf setBordered:NO];
+ [tf setDrawsBackground:NO];
+ [tf setStringValue:[NSString
+ stringWithCString:ctrl->generic.label]];
+ [tf sizeToFit];
+ rect = [tf frame];
+ [parent addSubview:tf];
+ c->label = tf;
+
+ cw = rect.size.width * 100 / labelp;
+ ch = rect.size.height;
+
+ if (ctrl->editbox.has_list) {
+ cb = [[NSComboBox alloc]
+ initWithFrame:NSMakeRect(0,0,1,1)];
+ [cb setStringValue:@"x"];
+ [cb sizeToFit];
+ rect = [cb frame];
+ [parent addSubview:cb];
+ c->combobox = cb;
+ } else {
+ if (ctrl->editbox.password)
+ tf = [NSSecureTextField alloc];
+ else
+ tf = [NSTextField alloc];
+
+ tf = [tf initWithFrame:NSMakeRect(0,0,1,1)];
+ [tf setEditable:YES];
+ [tf setSelectable:YES];
+ [tf setBordered:YES];
+ [tf setStringValue:@"x"];
+ [tf sizeToFit];
+ rect = [tf frame];
+ [parent addSubview:tf];
+ c->editbox = tf;
+
+ [tf setDelegate:d->rec];
+ add_widget(d, c, tf);
+ }
+
+ if (editp == 100) {
+ /* the edit box and its label are vertically separated */
+ ch += VSPACING + rect.size.height;
+ } else {
+ /* the edit box and its label are horizontally separated */
+ if (ch < rect.size.height)
+ ch = rect.size.height;
+ }
+
+ if (cw < rect.size.width * 100 / editp)
+ cw = rect.size.width * 100 / editp;
+ }
+ break;
+ case CTRL_TEXT:
+ {
+ NSTextView *tv;
+ int testwid;
+
+ if (!textviewfont) {
+ NSTextField *tf;
+ tf = [[NSTextField alloc] init];
+ textviewfont = [tf font];
+ [tf release];
+ }
+
+ testwid = (ccw[colend] - ccw[colstart]) * 3;
+
+ tv = [[NSTextView alloc]
+ initWithFrame:NSMakeRect(0,0,testwid,1)];
+ [tv setEditable:NO];
+ [tv setSelectable:NO];
+ //[tv setBordered:NO];
+ [tv setDrawsBackground:NO];
+ [tv setFont:textviewfont];
+ [tv setString:
+ [NSString stringWithCString:ctrl->generic.label]];
+ rect = [tv frame];
+ [tv sizeToFit];
+ [parent addSubview:tv];
+ c->textview = tv;
+
+ cw = rect.size.width;
+ ch = rect.size.height;
+ }
+ break;
+ case CTRL_RADIO:
+ {
+ NSTextField *tf;
+ int j;
+
+ if (ctrl->generic.label) {
+ tf = [[NSTextField alloc]
+ initWithFrame:NSMakeRect(0,0,1,1)];
+ [tf setEditable:NO];
+ [tf setSelectable:NO];
+ [tf setBordered:NO];
+ [tf setDrawsBackground:NO];
+ [tf setStringValue:
+ [NSString stringWithCString:ctrl->generic.label]];
+ [tf sizeToFit];
+ rect = [tf frame];
+ [parent addSubview:tf];
+ c->label = tf;
+
+ cw = rect.size.width;
+ ch = rect.size.height;
+ } else {
+ cw = 0;
+ ch = -VSPACING; /* compensate for next advance */
+ }
+
+ c->nradiobuttons = ctrl->radio.nbuttons;
+ c->radiobuttons = snewn(ctrl->radio.nbuttons, NSButton *);
+
+ for (j = 0; j < ctrl->radio.nbuttons; j++) {
+ NSButton *b;
+ int ncols;
+
+ b = [[MyButton alloc] initWithFrame:NSMakeRect(0,0,1,1)];
+ [b setBezelStyle:NSRoundedBezelStyle];
+ [b setButtonType:NSRadioButton];
+ [b setTitle:[NSString
+ stringWithCString:ctrl->radio.buttons[j]]];
+ [b sizeToFit];
+ rect = [b frame];
+ [parent addSubview:b];
+
+ c->radiobuttons[j] = b;
+
+ [b setTarget:d->rec];
+ [b setAction:@selector(radioChanged:)];
+ add_widget(d, c, b);
+
+ /*
+ * Add to the height every time we place a
+ * button in column 0.
+ */
+ if (j % ctrl->radio.ncolumns == 0) {
+ ch += rect.size.height + VSPACING;
+ }
+
+ /*
+ * Add to the width by working out how many
+ * columns this button spans.
+ */
+ if (j == ctrl->radio.nbuttons - 1)
+ ncols = (ctrl->radio.ncolumns -
+ (j % ctrl->radio.ncolumns));
+ else
+ ncols = 1;
+
+ if (cw < rect.size.width * ctrl->radio.ncolumns / ncols)
+ cw = rect.size.width * ctrl->radio.ncolumns / ncols;
+ }
+ }
+ break;
+ case CTRL_FILESELECT:
+ case CTRL_FONTSELECT:
+ {
+ NSTextField *tf;
+ NSButton *b;
+ int kh;
+
+ tf = [[NSTextField alloc] initWithFrame:NSMakeRect(0,0,1,1)];
+ [tf setEditable:NO];
+ [tf setSelectable:NO];
+ [tf setBordered:NO];
+ [tf setDrawsBackground:NO];
+ [tf setStringValue:[NSString
+ stringWithCString:ctrl->generic.label]];
+ [tf sizeToFit];
+ rect = [tf frame];
+ [parent addSubview:tf];
+ c->label = tf;
+
+ cw = rect.size.width;
+ ch = rect.size.height;
+
+ tf = [NSTextField alloc];
+ tf = [tf initWithFrame:NSMakeRect(0,0,1,1)];
+ if (ctrl->generic.type == CTRL_FILESELECT) {
+ [tf setEditable:YES];
+ [tf setSelectable:YES];
+ [tf setBordered:YES];
+ } else {
+ [tf setEditable:NO];
+ [tf setSelectable:NO];
+ [tf setBordered:NO];
+ [tf setDrawsBackground:NO];
+ }
+ [tf setStringValue:@"x"];
+ [tf sizeToFit];
+ rect = [tf frame];
+ [parent addSubview:tf];
+ c->editbox = tf;
+
+ kh = rect.size.height;
+ if (cw < rect.size.width * 4 / 3)
+ cw = rect.size.width * 4 / 3;
+
+ b = [[MyButton alloc] initWithFrame:NSMakeRect(0, 0, 1, 1)];
+ [b setBezelStyle:NSRoundedBezelStyle];
+ if (ctrl->generic.type == CTRL_FILESELECT)
+ [b setTitle:@"Browse..."];
+ else
+ [b setTitle:@"Change..."];
+ // [b setKeyEquivalent:somethingorother];
+ // [b setTarget:somethingorother];
+ // [b setAction:somethingorother];
+ [b sizeToFit];
+ rect = [b frame];
+ [parent addSubview:b];
+
+ c->button = b;
+
+ if (kh < rect.size.height)
+ kh = rect.size.height;
+ ch += VSPACING + kh;
+ if (cw < rect.size.width * 4)
+ cw = rect.size.width * 4;
+ }
+ break;
+ case CTRL_LISTBOX:
+ {
+ int listp = ctrl->listbox.percentwidth;
+ int labelp = listp == 100 ? 100 : 100 - listp;
+ NSTextField *tf;
+ NSPopUpButton *pb;
+ NSTableView *tv;
+ NSScrollView *sv;
+
+ if (ctrl->generic.label) {
+ tf = [[NSTextField alloc]
+ initWithFrame:NSMakeRect(0,0,1,1)];
+ [tf setEditable:NO];
+ [tf setSelectable:NO];
+ [tf setBordered:NO];
+ [tf setDrawsBackground:NO];
+ [tf setStringValue:
+ [NSString stringWithCString:ctrl->generic.label]];
+ [tf sizeToFit];
+ rect = [tf frame];
+ [parent addSubview:tf];
+ c->label = tf;
+
+ cw = rect.size.width;
+ ch = rect.size.height;
+ } else {
+ cw = 0;
+ ch = -VSPACING; /* compensate for next advance */
+ }
+
+ if (ctrl->listbox.height == 0) {
+ pb = [[NSPopUpButton alloc]
+ initWithFrame:NSMakeRect(0,0,1,1)];
+ [pb sizeToFit];
+ rect = [pb frame];
+ [parent addSubview:pb];
+ c->popupbutton = pb;
+
+ [pb setTarget:d->rec];
+ [pb setAction:@selector(popupMenuSelected:)];
+ add_widget(d, c, pb);
+ } else {
+ assert(listp == 100);
+ if (ctrl->listbox.draglist) {
+ int bi;
+
+ listp = 75;
+
+ for (bi = 0; bi < 2; bi++) {
+ NSButton *b;
+ b = [[MyButton alloc]
+ initWithFrame:NSMakeRect(0, 0, 1, 1)];
+ [b setBezelStyle:NSRoundedBezelStyle];
+ if (bi == 0)
+ [b setTitle:@"Up"];
+ else
+ [b setTitle:@"Down"];
+ [b sizeToFit];
+ rect = [b frame];
+ [parent addSubview:b];
+
+ if (bi == 0)
+ c->button = b;
+ else
+ c->button2 = b;
+
+ [b setTarget:d->rec];
+ [b setAction:@selector(dragListButton:)];
+ add_widget(d, c, b);
+
+ if (cw < rect.size.width * 4)
+ cw = rect.size.width * 4;
+ }
+ }
+
+ sv = [[NSScrollView alloc] initWithFrame:
+ NSMakeRect(20,20,10,10)];
+ [sv setBorderType:NSLineBorder];
+ tv = [[NSTableView alloc] initWithFrame:[sv frame]];
+ [[tv headerView] setFrame:NSMakeRect(0,0,0,0)];
+ [sv setDocumentView:tv];
+ [parent addSubview:sv];
+ [sv setHasVerticalScroller:YES];
+ [sv setAutohidesScrollers:YES];
+ [tv setAllowsColumnReordering:NO];
+ [tv setAllowsColumnResizing:NO];
+ [tv setAllowsMultipleSelection:ctrl->listbox.multisel];
+ [tv setAllowsEmptySelection:YES];
+ [tv setAllowsColumnSelection:YES];
+ [tv setDataSource:[[MyTableSource alloc] init]];
+ rect = [tv frame];
+ /*
+ * For some reason this consistently comes out
+ * one short. Add one.
+ */
+ rect.size.height = (ctrl->listbox.height+1)*[tv rowHeight];
+ [sv setFrame:rect];
+ c->tableview = tv;
+ c->scrollview = sv;
+
+ [tv setDelegate:d->rec];
+ [tv setTarget:d->rec];
+ [tv setDoubleAction:@selector(listDoubleClicked:)];
+ add_widget(d, c, tv);
+ }
+
+ if (c->tableview) {
+ int ncols, *percentages;
+ int hundred = 100;
+
+ if (ctrl->listbox.ncols) {
+ ncols = ctrl->listbox.ncols;
+ percentages = ctrl->listbox.percentages;
+ } else {
+ ncols = 1;
+ percentages = &hundred;
+ }
+
+ for (j = 0; j < ncols; j++) {
+ NSTableColumn *col;
+
+ col = [[NSTableColumn alloc] initWithIdentifier:
+ [NSNumber numberWithInt:j]];
+ [c->tableview addTableColumn:col];
+ }
+ }
+
+ if (labelp == 100) {
+ /* the list and its label are vertically separated */
+ ch += VSPACING + rect.size.height;
+ } else {
+ /* the list and its label are horizontally separated */
+ if (ch < rect.size.height)
+ ch = rect.size.height;
+ }
+
+ if (cw < rect.size.width * 100 / listp)
+ cw = rect.size.width * 100 / listp;
+ }
+ break;
+ }
+
+ /*
+ * Update the width and height data for the control we've
+ * just created.
+ */
+ ytop = 0;
+
+ for (j = colstart; j < colend; j++) {
+ if (ytop < cypos[j])
+ ytop = cypos[j];
+ }
+
+ for (j = colstart; j < colend; j++)
+ cypos[j] = ytop + ch + VSPACING;
+
+ if (hmin < ytop + ch)
+ hmin = ytop + ch;
+
+ wthis = (cw + HSPACING) * 100 / (ccw[colend] - ccw[colstart]);
+ wthis -= HSPACING;
+
+ if (wmin < wthis)
+ wmin = wthis;
+ }
+
+ if (*s->boxname) {
+ /*
+ * Add a bit to the width and height for the box.
+ */
+ wmin += boxw;
+ hmin += boxh;
+ }
+
+ //printf("For controlset %s/%s, returning w=%d h=%d\n",
+ // s->pathname, s->boxname, wmin, hmin);
+ *minw = wmin;
+ *minh = hmin;
+}
+
+int place_ctrls(void *dv, struct controlset *s, int leftx, int topy,
+ int width)
+{
+ struct fe_dlg *d = (struct fe_dlg *)dv;
+ int ccw[100]; /* cumulative column widths */
+ int cypos[100];
+ int ncols;
+ int i, j, ret;
+ int boxh = 0, boxw = 0;
+
+ if (!s->boxname && s->boxtitle) {
+ /* Size and place the panel title. */
+
+ NSTextField *tf = find_box(d, s);
+ NSRect rect;
+
+ rect = [tf frame];
+ [tf setFrame:NSMakeRect(leftx, topy-rect.size.height,
+ width, rect.size.height)];
+ return rect.size.height;
+ }
+
+ if (*s->boxname) {
+ NSRect rect, tmprect;
+ NSBox *box = find_box(d, s);
+
+ assert(box != NULL);
+ tmprect = [box frame];
+ [box setFrameFromContentFrame:NSMakeRect(100,100,100,100)];
+ rect = [box frame];
+ [box setFrame:tmprect];
+ boxw = rect.size.width - 100;
+ boxh = rect.size.height - 100;
+ if (s->boxtitle)
+ boxh += [[box titleFont] pointSize];
+ topy -= boxh;
+ width -= boxw;
+ }
+
+ ncols = 1;
+ ccw[0] = 0;
+ ccw[1] = 100;
+ cypos[0] = topy;
+ ret = 0;
+
+ /*
+ * Now iterate through the controls themselves, placing them
+ * appropriately.
+ */
+ for (i = 0; i < s->ncontrols; i++) {
+ union control *ctrl = s->ctrls[i];
+ struct fe_ctrl *c;
+ int colstart = COLUMN_START(ctrl->generic.column);
+ int colspan = COLUMN_SPAN(ctrl->generic.column);
+ int colend = colstart + colspan;
+ int xthis, ythis, wthis, ch;
+ NSRect rect;
+
+ switch (ctrl->generic.type) {
+ case CTRL_COLUMNS:
+ for (j = 1; j < ncols; j++)
+ if (cypos[0] > cypos[j])
+ cypos[0] = cypos[j];
+
+ assert(ctrl->columns.ncols < lenof(ccw));
+
+ ccw[0] = 0;
+ for (j = 0; j < ctrl->columns.ncols; j++) {
+ ccw[j+1] = ccw[j] + (ctrl->columns.percentages ?
+ ctrl->columns.percentages[j] : 100);
+ cypos[j] = cypos[0];
+ }
+
+ ncols = ctrl->columns.ncols;
+
+ continue; /* no actual control created */
+ case CTRL_TABDELAY:
+ continue; /* nothing to do here, move along */
+ }
+
+ c = fe_ctrl_byctrl(d, ctrl);
+
+ ch = 0;
+ ythis = topy;
+
+ for (j = colstart; j < colend; j++) {
+ if (ythis > cypos[j])
+ ythis = cypos[j];
+ }
+
+ xthis = (width + HSPACING) * ccw[colstart] / 100;
+ wthis = (width + HSPACING) * ccw[colend] / 100 - HSPACING - xthis;
+ xthis += leftx;
+
+ switch (ctrl->generic.type) {
+ case CTRL_BUTTON:
+ case CTRL_CHECKBOX:
+ rect = [c->button frame];
+ [c->button setFrame:NSMakeRect(xthis,ythis-rect.size.height,wthis,
+ rect.size.height)];
+ ch = rect.size.height;
+ break;
+ case CTRL_EDITBOX:
+ {
+ int editp = ctrl->editbox.percentwidth;
+ int labelp = editp == 100 ? 100 : 100 - editp;
+ int lheight, theight, rheight, ynext, editw;
+ NSControl *edit = (c->editbox ? c->editbox : c->combobox);
+
+ rect = [c->label frame];
+ lheight = rect.size.height;
+ rect = [edit frame];
+ theight = rect.size.height;
+
+ if (editp == 100)
+ rheight = lheight;
+ else
+ rheight = (lheight < theight ? theight : lheight);
+
+ [c->label setFrame:
+ NSMakeRect(xthis, ythis-(rheight+lheight)/2,
+ (wthis + HSPACING) * labelp / 100 - HSPACING,
+ lheight)];
+ if (editp == 100) {
+ ynext = ythis - rheight - VSPACING;
+ rheight = theight;
+ } else {
+ ynext = ythis;
+ }
+
+ editw = (wthis + HSPACING) * editp / 100 - HSPACING;
+
+ [edit setFrame:
+ NSMakeRect(xthis+wthis-editw, ynext-(rheight+theight)/2,
+ editw, theight)];
+
+ ch = (ythis - ynext) + theight;
+ }
+ break;
+ case CTRL_TEXT:
+ [c->textview setFrame:NSMakeRect(xthis, 0, wthis, 1)];
+ [c->textview sizeToFit];
+ rect = [c->textview frame];
+ [c->textview setFrame:NSMakeRect(xthis, ythis-rect.size.height,
+ wthis, rect.size.height)];
+ ch = rect.size.height;
+ break;
+ case CTRL_RADIO:
+ {
+ int j, ynext;
+
+ if (c->label) {
+ rect = [c->label frame];
+ [c->label setFrame:NSMakeRect(xthis,ythis-rect.size.height,
+ wthis,rect.size.height)];
+ ynext = ythis - rect.size.height - VSPACING;
+ } else
+ ynext = ythis;
+
+ for (j = 0; j < ctrl->radio.nbuttons; j++) {
+ int col = j % ctrl->radio.ncolumns;
+ int ncols;
+ int lx,rx;
+
+ if (j == ctrl->radio.nbuttons - 1)
+ ncols = ctrl->radio.ncolumns - col;
+ else
+ ncols = 1;
+
+ lx = (wthis + HSPACING) * col / ctrl->radio.ncolumns;
+ rx = ((wthis + HSPACING) *
+ (col+ncols) / ctrl->radio.ncolumns) - HSPACING;
+
+ /*
+ * Set the frame size.
+ */
+ rect = [c->radiobuttons[j] frame];
+ [c->radiobuttons[j] setFrame:
+ NSMakeRect(lx+xthis, ynext-rect.size.height,
+ rx-lx, rect.size.height)];
+
+ /*
+ * Advance to next line if we're in the last
+ * column.
+ */
+ if (col + ncols == ctrl->radio.ncolumns)
+ ynext -= rect.size.height + VSPACING;
+ }
+ ch = (ythis - ynext) - VSPACING;
+ }
+ break;
+ case CTRL_FILESELECT:
+ case CTRL_FONTSELECT:
+ {
+ int ynext, eh, bh, th, mx;
+
+ rect = [c->label frame];
+ [c->label setFrame:NSMakeRect(xthis,ythis-rect.size.height,
+ wthis,rect.size.height)];
+ ynext = ythis - rect.size.height - VSPACING;
+
+ rect = [c->editbox frame];
+ eh = rect.size.height;
+ rect = [c->button frame];
+ bh = rect.size.height;
+ th = (eh > bh ? eh : bh);
+
+ mx = (wthis + HSPACING) * 3 / 4 - HSPACING;
+
+ [c->editbox setFrame:
+ NSMakeRect(xthis, ynext-(th+eh)/2, mx, eh)];
+ [c->button setFrame:
+ NSMakeRect(xthis+mx+HSPACING, ynext-(th+bh)/2,
+ wthis-mx-HSPACING, bh)];
+
+ ch = (ythis - ynext) + th + VSPACING;
+ }
+ break;
+ case CTRL_LISTBOX:
+ {
+ int listp = ctrl->listbox.percentwidth;
+ int labelp = listp == 100 ? 100 : 100 - listp;
+ int lheight, theight, rheight, ynext, listw, xlist;
+ NSControl *list = (c->scrollview ? (id)c->scrollview :
+ (id)c->popupbutton);
+
+ if (ctrl->listbox.draglist) {
+ assert(listp == 100);
+ listp = 75;
+ }
+
+ rect = [list frame];
+ theight = rect.size.height;
+
+ if (c->label) {
+ rect = [c->label frame];
+ lheight = rect.size.height;
+
+ if (labelp == 100)
+ rheight = lheight;
+ else
+ rheight = (lheight < theight ? theight : lheight);
+
+ [c->label setFrame:
+ NSMakeRect(xthis, ythis-(rheight+lheight)/2,
+ (wthis + HSPACING) * labelp / 100 - HSPACING,
+ lheight)];
+ if (labelp == 100) {
+ ynext = ythis - rheight - VSPACING;
+ rheight = theight;
+ } else {
+ ynext = ythis;
+ }
+ } else {
+ ynext = ythis;
+ rheight = theight;
+ }
+
+ listw = (wthis + HSPACING) * listp / 100 - HSPACING;
+
+ if (labelp == 100)
+ xlist = xthis;
+ else
+ xlist = xthis+wthis-listw;
+
+ [list setFrame: NSMakeRect(xlist, ynext-(rheight+theight)/2,
+ listw, theight)];
+
+ /*
+ * Size the columns for the table view.
+ */
+ if (c->tableview) {
+ int ncols, *percentages;
+ int hundred = 100;
+ int cpercent = 0, cpixels = 0;
+ NSArray *cols;
+
+ if (ctrl->listbox.ncols) {
+ ncols = ctrl->listbox.ncols;
+ percentages = ctrl->listbox.percentages;
+ } else {
+ ncols = 1;
+ percentages = &hundred;
+ }
+
+ cols = [c->tableview tableColumns];
+
+ for (j = 0; j < ncols; j++) {
+ NSTableColumn *col = [cols objectAtIndex:j];
+ int newcpixels;
+
+ cpercent += percentages[j];
+ newcpixels = listw * cpercent / 100;
+ [col setWidth:newcpixels-cpixels];
+ cpixels = newcpixels;
+ }
+ }
+
+ ch = (ythis - ynext) + theight;
+
+ if (c->button) {
+ int b2height, centre;
+ int bx, bw;
+
+ /*
+ * Place the Up and Down buttons for a drag list.
+ */
+ assert(c->button2);
+
+ rect = [c->button frame];
+ b2height = VSPACING + 2 * rect.size.height;
+
+ centre = ynext - rheight/2;
+
+ bx = (wthis + HSPACING) * 3 / 4;
+ bw = wthis - bx;
+ bx += leftx;
+
+ [c->button setFrame:
+ NSMakeRect(bx, centre+b2height/2-rect.size.height,
+ bw, rect.size.height)];
+ [c->button2 setFrame:
+ NSMakeRect(bx, centre-b2height/2,
+ bw, rect.size.height)];
+ }
+ }
+ break;
+ }
+
+ for (j = colstart; j < colend; j++)
+ cypos[j] = ythis - ch - VSPACING;
+ if (ret < topy - (ythis - ch))
+ ret = topy - (ythis - ch);
+ }
+
+ if (*s->boxname) {
+ NSBox *box = find_box(d, s);
+ assert(box != NULL);
+ [box sizeToFit];
+
+ if (s->boxtitle) {
+ NSRect rect = [box frame];
+ rect.size.height += [[box titleFont] pointSize];
+ [box setFrame:rect];
+ }
+
+ ret += boxh;
+ }
+
+ //printf("For controlset %s/%s, returning ret=%d\n",
+ // s->pathname, s->boxname, ret);
+ return ret;
+}
+
+void select_panel(void *dv, struct controlbox *b, const char *name)
+{
+ struct fe_dlg *d = (struct fe_dlg *)dv;
+ int i, j, hidden;
+ struct controlset *s;
+ union control *ctrl;
+ struct fe_ctrl *c;
+ NSBox *box;
+
+ for (i = 0; i < b->nctrlsets; i++) {
+ s = b->ctrlsets[i];
+
+ if (*s->pathname) {
+ hidden = !strcmp(s->pathname, name) ? NO : YES;
+
+ if ((box = find_box(d, s)) != NULL) {
+ [box setHidden:hidden];
+ } else {
+ for (j = 0; j < s->ncontrols; j++) {
+ ctrl = s->ctrls[j];
+ c = fe_ctrl_byctrl(d, ctrl);
+
+ if (!c)
+ continue;
+
+ if (c->label)
+ [c->label setHidden:hidden];
+ if (c->button)
+ [c->button setHidden:hidden];
+ if (c->button2)
+ [c->button2 setHidden:hidden];
+ if (c->editbox)
+ [c->editbox setHidden:hidden];
+ if (c->combobox)
+ [c->combobox setHidden:hidden];
+ if (c->textview)
+ [c->textview setHidden:hidden];
+ if (c->tableview)
+ [c->tableview setHidden:hidden];
+ if (c->scrollview)
+ [c->scrollview setHidden:hidden];
+ if (c->popupbutton)
+ [c->popupbutton setHidden:hidden];
+ if (c->radiobuttons) {
+ int j;
+ for (j = 0; j < c->nradiobuttons; j++)
+ [c->radiobuttons[j] setHidden:hidden];
+ }
+ break;
+ }
+ }
+ }
+ }
+}
+
+void dlg_radiobutton_set(union control *ctrl, void *dv, int whichbutton)
+{
+ struct fe_dlg *d = (struct fe_dlg *)dv;
+ struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
+ int j;
+
+ assert(c->radiobuttons);
+ for (j = 0; j < c->nradiobuttons; j++)
+ [c->radiobuttons[j] setState:
+ (j == whichbutton ? NSOnState : NSOffState)];
+}
+
+int dlg_radiobutton_get(union control *ctrl, void *dv)
+{
+ struct fe_dlg *d = (struct fe_dlg *)dv;
+ struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
+ int j;
+
+ assert(c->radiobuttons);
+ for (j = 0; j < c->nradiobuttons; j++)
+ if ([c->radiobuttons[j] state] == NSOnState)
+ return j;
+
+ return 0; /* should never reach here */
+}
+
+void dlg_checkbox_set(union control *ctrl, void *dv, int checked)
+{
+ struct fe_dlg *d = (struct fe_dlg *)dv;
+ struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
+
+ assert(c->button);
+ [c->button setState:(checked ? NSOnState : NSOffState)];
+}
+
+int dlg_checkbox_get(union control *ctrl, void *dv)
+{
+ struct fe_dlg *d = (struct fe_dlg *)dv;
+ struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
+
+ assert(c->button);
+ return ([c->button state] == NSOnState);
+}
+
+void dlg_editbox_set(union control *ctrl, void *dv, char const *text)
+{
+ struct fe_dlg *d = (struct fe_dlg *)dv;
+ struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
+
+ if (c->editbox) {
+ [c->editbox setStringValue:[NSString stringWithCString:text]];
+ } else {
+ assert(c->combobox);
+ [c->combobox setStringValue:[NSString stringWithCString:text]];
+ }
+}
+
+void dlg_editbox_get(union control *ctrl, void *dv, char *buffer, int length)
+{
+ struct fe_dlg *d = (struct fe_dlg *)dv;
+ struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
+ NSString *str;
+
+ if (c->editbox) {
+ str = [c->editbox stringValue];
+ } else {
+ assert(c->combobox);
+ str = [c->combobox stringValue];
+ }
+ if (!str)
+ str = @"";
+
+ /* The length parameter to this method doesn't include a trailing NUL */
+ [str getCString:buffer maxLength:length-1];
+}
+
+void dlg_listbox_clear(union control *ctrl, void *dv)
+{
+ struct fe_dlg *d = (struct fe_dlg *)dv;
+ struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
+
+ if (c->tableview) {
+ [[c->tableview dataSource] clear];
+ [c->tableview reloadData];
+ } else {
+ [c->popupbutton removeAllItems];
+ }
+}
+
+void dlg_listbox_del(union control *ctrl, void *dv, int index)
+{
+ struct fe_dlg *d = (struct fe_dlg *)dv;
+ struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
+
+ if (c->tableview) {
+ [[c->tableview dataSource] removestr:index];
+ [c->tableview reloadData];
+ } else {
+ [c->popupbutton removeItemAtIndex:index];
+ }
+}
+
+void dlg_listbox_addwithid(union control *ctrl, void *dv,
+ char const *text, int id)
+{
+ struct fe_dlg *d = (struct fe_dlg *)dv;
+ struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
+
+ if (c->tableview) {
+ [[c->tableview dataSource] add:text withId:id];
+ [c->tableview reloadData];
+ } else {
+ [c->popupbutton addItemWithTitle:[NSString stringWithCString:text]];
+ [[c->popupbutton lastItem] setTag:id];
+ }
+}
+
+void dlg_listbox_add(union control *ctrl, void *dv, char const *text)
+{
+ dlg_listbox_addwithid(ctrl, dv, text, -1);
+}
+
+int dlg_listbox_getid(union control *ctrl, void *dv, int index)
+{
+ struct fe_dlg *d = (struct fe_dlg *)dv;
+ struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
+
+ if (c->tableview) {
+ return [[c->tableview dataSource] getid:index];
+ } else {
+ return [[c->popupbutton itemAtIndex:index] tag];
+ }
+}
+
+int dlg_listbox_index(union control *ctrl, void *dv)
+{
+ struct fe_dlg *d = (struct fe_dlg *)dv;
+ struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
+
+ if (c->tableview) {
+ return [c->tableview selectedRow];
+ } else {
+ return [c->popupbutton indexOfSelectedItem];
+ }
+}
+
+int dlg_listbox_issel(union control *ctrl, void *dv, int index)
+{
+ struct fe_dlg *d = (struct fe_dlg *)dv;
+ struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
+
+ if (c->tableview) {
+ return [c->tableview isRowSelected:index];
+ } else {
+ return [c->popupbutton indexOfSelectedItem] == index;
+ }
+}
+
+void dlg_listbox_select(union control *ctrl, void *dv, int index)
+{
+ struct fe_dlg *d = (struct fe_dlg *)dv;
+ struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
+
+ if (c->tableview) {
+ [c->tableview selectRow:index byExtendingSelection:NO];
+ } else {
+ [c->popupbutton selectItemAtIndex:index];
+ }
+}
+
+void dlg_text_set(union control *ctrl, void *dv, char const *text)
+{
+ struct fe_dlg *d = (struct fe_dlg *)dv;
+ struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
+
+ assert(c->textview);
+ [c->textview setString:[NSString stringWithCString:text]];
+}
+
+void dlg_label_change(union control *ctrl, void *dlg, char const *text)
+{
+ /*
+ * This function is currently only used by the config box to
+ * switch the labels on the host and port boxes between serial
+ * and network modes. Since OS X does not (yet?) have a serial
+ * back end, this function can safely do nothing for the
+ * moment.
+ */
+}
+
+void dlg_filesel_set(union control *ctrl, void *dv, Filename fn)
+{
+ /* FIXME */
+}
+
+void dlg_filesel_get(union control *ctrl, void *dv, Filename *fn)
+{
+ /* FIXME */
+}
+
+void dlg_fontsel_set(union control *ctrl, void *dv, FontSpec fn)
+{
+ /* FIXME */
+}
+
+void dlg_fontsel_get(union control *ctrl, void *dv, FontSpec *fn)
+{
+ /* FIXME */
+}
+
+void dlg_update_start(union control *ctrl, void *dv)
+{
+ /* FIXME */
+}
+
+void dlg_update_done(union control *ctrl, void *dv)
+{
+ /* FIXME */
+}
+
+void dlg_set_focus(union control *ctrl, void *dv)
+{
+ /* FIXME */
+}
+
+union control *dlg_last_focused(union control *ctrl, void *dv)
+{
+ return NULL; /* FIXME */
+}
+
+void dlg_beep(void *dv)
+{
+ NSBeep();
+}
+
+void dlg_error_msg(void *dv, char *msg)
+{
+ /* FIXME */
+}
+
+void dlg_end(void *dv, int value)
+{
+ struct fe_dlg *d = (struct fe_dlg *)dv;
+ [d->target performSelector:d->action
+ withObject:[NSNumber numberWithInt:value]];
+}
+
+void dlg_coloursel_start(union control *ctrl, void *dv,
+ int r, int g, int b)
+{
+ /* FIXME */
+}
+
+int dlg_coloursel_results(union control *ctrl, void *dv,
+ int *r, int *g, int *b)
+{
+ return 0; /* FIXME */
+}
+
+void dlg_refresh(union control *ctrl, void *dv)
+{
+ struct fe_dlg *d = (struct fe_dlg *)dv;
+ struct fe_ctrl *c;
+
+ if (ctrl) {
+ if (ctrl->generic.handler != NULL)
+ ctrl->generic.handler(ctrl, d, d->data, EVENT_REFRESH);
+ } else {
+ int i;
+
+ for (i = 0; (c = index234(d->byctrl, i)) != NULL; i++) {
+ assert(c->ctrl != NULL);
+ if (c->ctrl->generic.handler != NULL)
+ c->ctrl->generic.handler(c->ctrl, d,
+ d->data, EVENT_REFRESH);
+ }
+ }
+}
+
+void *dlg_get_privdata(union control *ctrl, void *dv)
+{
+ struct fe_dlg *d = (struct fe_dlg *)dv;
+ struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
+ return c->privdata;
+}
+
+void dlg_set_privdata(union control *ctrl, void *dv, void *ptr)
+{
+ struct fe_dlg *d = (struct fe_dlg *)dv;
+ struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
+ c->privdata = ptr;
+ c->privdata_needs_free = FALSE;
+}
+
+void *dlg_alloc_privdata(union control *ctrl, void *dv, size_t size)
+{
+ struct fe_dlg *d = (struct fe_dlg *)dv;
+ struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
+ /*
+ * This is an internal allocation routine, so it's allowed to
+ * use smalloc directly.
+ */
+ c->privdata = smalloc(size);
+ c->privdata_needs_free = TRUE;
+ return c->privdata;
+}
--- /dev/null
+/*
+ * osxdlg.m: various PuTTY dialog boxes for OS X.
+ */
+
+#import <Cocoa/Cocoa.h>
+#include "putty.h"
+#include "storage.h"
+#include "dialog.h"
+#include "osxclass.h"
+
+/*
+ * The `ConfigWindow' class is used to start up a new PuTTY
+ * session.
+ */
+
+@class ConfigTree;
+@interface ConfigTree : NSObject
+{
+ NSString **paths;
+ int *levels;
+ int nitems, itemsize;
+}
+- (void)addPath:(char *)path;
+@end
+
+@implementation ConfigTree
+- (id)init
+{
+ self = [super init];
+ paths = NULL;
+ levels = NULL;
+ nitems = itemsize = 0;
+ return self;
+}
+- (void)addPath:(char *)path
+{
+ if (nitems >= itemsize) {
+ itemsize += 32;
+ paths = sresize(paths, itemsize, NSString *);
+ levels = sresize(levels, itemsize, int);
+ }
+ paths[nitems] = [[NSString stringWithCString:path] retain];
+ levels[nitems] = ctrl_path_elements(path) - 1;
+ nitems++;
+}
+- (void)dealloc
+{
+ int i;
+
+ for (i = 0; i < nitems; i++)
+ [paths[i] release];
+
+ sfree(paths);
+ sfree(levels);
+
+ [super dealloc];
+}
+- (id)iterateChildren:(int)index ofItem:(id)item count:(int *)count
+{
+ int i, plevel;
+
+ if (item) {
+ for (i = 0; i < nitems; i++)
+ if (paths[i] == item)
+ break;
+ assert(i < nitems);
+ plevel = levels[i];
+ i++;
+ } else {
+ i = 0;
+ plevel = -1;
+ }
+
+ if (count)
+ *count = 0;
+
+ while (index > 0) {
+ if (i >= nitems || levels[i] != plevel+1)
+ return nil;
+ if (count)
+ (*count)++;
+ do {
+ i++;
+ } while (i < nitems && levels[i] > plevel+1);
+ index--;
+ }
+
+ return paths[i];
+}
+- (id)outlineView:(NSOutlineView *)outlineView child:(int)index ofItem:(id)item
+{
+ return [self iterateChildren:index ofItem:item count:NULL];
+}
+- (int)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item
+{
+ int count = 0;
+ /* pass nitems+1 to ensure we run off the end */
+ [self iterateChildren:nitems+1 ofItem:item count:&count];
+ return count;
+}
+- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item
+{
+ return [self outlineView:outlineView numberOfChildrenOfItem:item] > 0;
+}
+- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item
+{
+ /*
+ * Trim off all path elements except the last one.
+ */
+ NSArray *components = [item componentsSeparatedByString:@"/"];
+ return [components objectAtIndex:[components count]-1];
+}
+@end
+
+@implementation ConfigWindow
+- (id)initWithConfig:(Config)aCfg
+{
+ NSScrollView *scrollview;
+ NSTableColumn *col;
+ ConfigTree *treedata;
+ int by = 0, mby = 0;
+ int wmin = 0;
+ int hmin = 0;
+ int panelht = 0;
+
+ ctrlbox = ctrl_new_box();
+ setup_config_box(ctrlbox, FALSE /*midsession*/, aCfg.protocol,
+ 0 /* protcfginfo */);
+ unix_setup_config_box(ctrlbox, FALSE /*midsession*/, aCfg.protocol);
+
+ cfg = aCfg; /* structure copy */
+
+ self = [super initWithContentRect:NSMakeRect(0,0,300,300)
+ styleMask:(NSTitledWindowMask | NSMiniaturizableWindowMask |
+ NSClosableWindowMask)
+ backing:NSBackingStoreBuffered
+ defer:YES];
+ [self setTitle:@"PuTTY Configuration"];
+
+ [self setIgnoresMouseEvents:NO];
+
+ dv = fe_dlg_init(&cfg, self, self, @selector(configBoxFinished:));
+
+ scrollview = [[NSScrollView alloc] initWithFrame:NSMakeRect(20,20,10,10)];
+ treeview = [[NSOutlineView alloc] initWithFrame:[scrollview frame]];
+ [scrollview setBorderType:NSLineBorder];
+ [scrollview setDocumentView:treeview];
+ [[self contentView] addSubview:scrollview];
+ [scrollview setHasVerticalScroller:YES];
+ [scrollview setAutohidesScrollers:YES];
+ /* FIXME: the below is untested. Test it then remove this notice. */
+ [treeview setAllowsColumnReordering:NO];
+ [treeview setAllowsColumnResizing:NO];
+ [treeview setAllowsMultipleSelection:NO];
+ [treeview setAllowsEmptySelection:NO];
+ [treeview setAllowsColumnSelection:YES];
+
+ treedata = [[[ConfigTree alloc] init] retain];
+
+ col = [[NSTableColumn alloc] initWithIdentifier:nil];
+ [treeview addTableColumn:col];
+ [treeview setOutlineTableColumn:col];
+
+ [[treeview headerView] setFrame:NSMakeRect(0,0,0,0)];
+
+ /*
+ * Create the controls.
+ */
+ {
+ int i;
+ char *path = NULL;
+
+ for (i = 0; i < ctrlbox->nctrlsets; i++) {
+ struct controlset *s = ctrlbox->ctrlsets[i];
+ int mw, mh;
+
+ if (!*s->pathname) {
+
+ create_ctrls(dv, [self contentView], s, &mw, &mh);
+
+ by += 20 + mh;
+
+ if (wmin < mw + 40)
+ wmin = mw + 40;
+ } else {
+ int j = path ? ctrl_path_compare(s->pathname, path) : 0;
+
+ if (j != INT_MAX) { /* add to treeview, start new panel */
+ char *c;
+
+ /*
+ * We expect never to find an implicit path
+ * component. For example, we expect never to
+ * see A/B/C followed by A/D/E, because that
+ * would _implicitly_ create A/D. All our path
+ * prefixes are expected to contain actual
+ * controls and be selectable in the treeview;
+ * so we would expect to see A/D _explicitly_
+ * before encountering A/D/E.
+ */
+ assert(j == ctrl_path_elements(s->pathname) - 1);
+
+ c = strrchr(s->pathname, '/');
+ if (!c)
+ c = s->pathname;
+ else
+ c++;
+
+ [treedata addPath:s->pathname];
+ path = s->pathname;
+
+ panelht = 0;
+ }
+
+ create_ctrls(dv, [self contentView], s, &mw, &mh);
+ if (wmin < mw + 3*20+150)
+ wmin = mw + 3*20+150;
+ panelht += mh + 20;
+ if (hmin < panelht - 20)
+ hmin = panelht - 20;
+ }
+ }
+ }
+
+ {
+ int i;
+ NSRect r;
+
+ [treeview setDataSource:treedata];
+ for (i = [treeview numberOfRows]; i-- ;)
+ [treeview expandItem:[treeview itemAtRow:i] expandChildren:YES];
+
+ [treeview sizeToFit];
+ r = [treeview frame];
+ if (hmin < r.size.height)
+ hmin = r.size.height;
+ }
+
+ [self setContentSize:NSMakeSize(wmin, hmin+60+by)];
+ [scrollview setFrame:NSMakeRect(20, 40+by, 150, hmin)];
+ [treeview setDelegate:self];
+ mby = by;
+
+ /*
+ * Now place the controls.
+ */
+ {
+ int i;
+ char *path = NULL;
+ panelht = 0;
+
+ for (i = 0; i < ctrlbox->nctrlsets; i++) {
+ struct controlset *s = ctrlbox->ctrlsets[i];
+
+ if (!*s->pathname) {
+ by -= VSPACING + place_ctrls(dv, s, 20, by, wmin-40);
+ } else {
+ if (!path || strcmp(s->pathname, path))
+ panelht = 0;
+
+ panelht += VSPACING + place_ctrls(dv, s, 2*20+150,
+ 40+mby+hmin-panelht,
+ wmin - (3*20+150));
+
+ path = s->pathname;
+ }
+ }
+ }
+
+ select_panel(dv, ctrlbox, [[treeview itemAtRow:0] cString]);
+
+ [treeview reloadData];
+
+ dlg_refresh(NULL, dv);
+
+ [self center]; /* :-) */
+
+ return self;
+}
+- (void)configBoxFinished:(id)object
+{
+ int ret = [object intValue]; /* it'll be an NSNumber */
+ if (ret) {
+ [controller performSelectorOnMainThread:
+ @selector(newSessionWithConfig:)
+ withObject:[NSData dataWithBytes:&cfg length:sizeof(cfg)]
+ waitUntilDone:NO];
+ }
+ [self close];
+}
+- (void)outlineViewSelectionDidChange:(NSNotification *)notification
+{
+ const char *path = [[treeview itemAtRow:[treeview selectedRow]] cString];
+ select_panel(dv, ctrlbox, path);
+}
+- (BOOL)outlineView:(NSOutlineView *)outlineView
+ shouldEditTableColumn:(NSTableColumn *)tableColumn item:(id)item
+{
+ return NO; /* no editing! */
+}
+@end
+
+/* ----------------------------------------------------------------------
+ * Various special-purpose dialog boxes.
+ */
+
+struct appendstate {
+ void (*callback)(void *ctx, int result);
+ void *ctx;
+};
+
+static void askappend_callback(void *ctx, int result)
+{
+ struct appendstate *state = (struct appendstate *)ctx;
+
+ state->callback(state->ctx, (result == NSAlertFirstButtonReturn ? 2 :
+ result == NSAlertSecondButtonReturn ? 1 : 0));
+ sfree(state);
+}
+
+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, "
+ "append your session log to the end of it, "
+ "or disable session logging for this session.";
+
+ char *text;
+ SessionWindow *win = (SessionWindow *)frontend;
+ struct appendstate *state;
+ NSAlert *alert;
+
+ text = dupprintf(msgtemplate, filename.path);
+
+ state = snew(struct appendstate);
+ state->callback = callback;
+ state->ctx = ctx;
+
+ alert = [[NSAlert alloc] init];
+ [alert setInformativeText:[NSString stringWithCString:text]];
+ [alert addButtonWithTitle:@"Overwrite"];
+ [alert addButtonWithTitle:@"Append"];
+ [alert addButtonWithTitle:@"Disable"];
+ [win startAlert:alert withCallback:askappend_callback andCtx:state];
+
+ return -1;
+}
+
+struct algstate {
+ void (*callback)(void *ctx, int result);
+ void *ctx;
+};
+
+static void askalg_callback(void *ctx, int result)
+{
+ struct algstate *state = (struct algstate *)ctx;
+
+ state->callback(state->ctx, result == NSAlertFirstButtonReturn);
+ sfree(state);
+}
+
+int askalg(void *frontend, const char *algtype, const char *algname,
+ void (*callback)(void *ctx, int result), void *ctx)
+{
+ static const char msg[] =
+ "The first %s supported by the server is "
+ "%s, which is below the configured warning threshold.\n"
+ "Continue with connection?";
+
+ char *text;
+ SessionWindow *win = (SessionWindow *)frontend;
+ struct algstate *state;
+ NSAlert *alert;
+
+ text = dupprintf(msg, algtype, algname);
+
+ state = snew(struct algstate);
+ state->callback = callback;
+ state->ctx = ctx;
+
+ alert = [[NSAlert alloc] init];
+ [alert setInformativeText:[NSString stringWithCString:text]];
+ [alert addButtonWithTitle:@"Yes"];
+ [alert addButtonWithTitle:@"No"];
+ [win startAlert:alert withCallback:askalg_callback andCtx:state];
+
+ return -1;
+}
+
+struct hostkeystate {
+ char *host, *keytype, *keystr;
+ int port;
+ void (*callback)(void *ctx, int result);
+ void *ctx;
+};
+
+static void verify_ssh_host_key_callback(void *ctx, int result)
+{
+ struct hostkeystate *state = (struct hostkeystate *)ctx;
+
+ if (result == NSAlertThirdButtonReturn) /* `Accept' */
+ store_host_key(state->host, state->port,
+ state->keytype, state->keystr);
+ state->callback(state->ctx, result != NSAlertFirstButtonReturn);
+ sfree(state->host);
+ sfree(state->keytype);
+ sfree(state->keystr);
+ sfree(state);
+}
+
+int verify_ssh_host_key(void *frontend, char *host, int port, char *keytype,
+ char *keystr, char *fingerprint,
+ void (*callback)(void *ctx, int result), void *ctx)
+{
+ static const char absenttxt[] =
+ "The server's host key is not cached. You have no guarantee "
+ "that the server is the computer you think it is.\n"
+ "The server's %s key fingerprint is:\n"
+ "%s\n"
+ "If you trust this host, press \"Accept\" to add the key to "
+ "PuTTY's cache and carry on connecting.\n"
+ "If you want to carry on connecting just once, without "
+ "adding the key to the cache, press \"Connect Once\".\n"
+ "If you do not trust this host, press \"Cancel\" to abandon the "
+ "connection.";
+ static const char wrongtxt[] =
+ "WARNING - POTENTIAL SECURITY BREACH!\n"
+ "The server's host key does not match the one PuTTY has "
+ "cached. This means that either the server administrator "
+ "has changed the host key, or you have actually connected "
+ "to another computer pretending to be the server.\n"
+ "The new %s key fingerprint is:\n"
+ "%s\n"
+ "If you were expecting this change and trust the new key, "
+ "press \"Accept\" to update PuTTY's cache and continue connecting.\n"
+ "If you want to carry on connecting but without updating "
+ "the cache, press \"Connect Once\".\n"
+ "If you want to abandon the connection completely, press "
+ "\"Cancel\" to cancel. Pressing \"Cancel\" is the ONLY guaranteed "
+ "safe choice.";
+
+ int ret;
+ char *text;
+ SessionWindow *win = (SessionWindow *)frontend;
+ struct hostkeystate *state;
+ NSAlert *alert;
+
+ /*
+ * Verify the key.
+ */
+ ret = verify_host_key(host, port, keytype, keystr);
+
+ if (ret == 0)
+ return 1;
+
+ text = dupprintf((ret == 2 ? wrongtxt : absenttxt), keytype, fingerprint);
+
+ state = snew(struct hostkeystate);
+ state->callback = callback;
+ state->ctx = ctx;
+ state->host = dupstr(host);
+ state->port = port;
+ state->keytype = dupstr(keytype);
+ state->keystr = dupstr(keystr);
+
+ alert = [[NSAlert alloc] init];
+ [alert setInformativeText:[NSString stringWithCString:text]];
+ [alert addButtonWithTitle:@"Cancel"];
+ [alert addButtonWithTitle:@"Connect Once"];
+ [alert addButtonWithTitle:@"Accept"];
+ [win startAlert:alert withCallback:verify_ssh_host_key_callback
+ andCtx:state];
+
+ return -1;
+}
+
+void old_keyfile_warning(void)
+{
+ /*
+ * This should never happen on OS X. We hope.
+ */
+}
+
+static void connection_fatal_callback(void *ctx, int result)
+{
+ SessionWindow *win = (SessionWindow *)ctx;
+
+ [win endSession:FALSE];
+}
+
+void connection_fatal(void *frontend, char *p, ...)
+{
+ SessionWindow *win = (SessionWindow *)frontend;
+ va_list ap;
+ char *msg;
+ NSAlert *alert;
+
+ va_start(ap, p);
+ msg = dupvprintf(p, ap);
+ va_end(ap);
+
+ alert = [[NSAlert alloc] init];
+ [alert setInformativeText:[NSString stringWithCString:msg]];
+ [alert addButtonWithTitle:@"Proceed"];
+ [win startAlert:alert withCallback:connection_fatal_callback
+ andCtx:win];
+}
--- /dev/null
+/*
+ * osxmain.m: main-program file of Mac OS X PuTTY.
+ */
+
+#import <Cocoa/Cocoa.h>
+
+#define PUTTY_DO_GLOBALS /* actually _define_ globals */
+
+#include "putty.h"
+#include "osxclass.h"
+
+/* ----------------------------------------------------------------------
+ * Global variables.
+ */
+
+AppController *controller;
+
+/* ----------------------------------------------------------------------
+ * Miscellaneous elements of the interface to the cross-platform
+ * and Unix PuTTY code.
+ */
+
+char *platform_get_x_display(void) {
+ return NULL;
+}
+
+FontSpec platform_default_fontspec(const char *name)
+{
+ FontSpec ret;
+ /* FIXME */
+ 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;
+}
+
+char *platform_default_s(const char *name)
+{
+ return NULL;
+}
+
+int platform_default_i(const char *name, int def)
+{
+ if (!strcmp(name, "CloseOnExit"))
+ return 2; /* maps to FORCE_ON after painful rearrangement :-( */
+ return def;
+}
+
+char *x_get_default(const char *key)
+{
+ return NULL; /* this is a stub */
+}
+
+static void commonfatalbox(char *p, va_list ap)
+{
+ char errorbuf[2048];
+ NSAlert *alert;
+
+ /*
+ * We may have come here because we ran out of memory, in which
+ * case it's entirely likely that that further memory
+ * allocations will fail. So (a) we use vsnprintf to format the
+ * error message rather than the usual dupvprintf; and (b) we
+ * have a fallback way to get the message out via stderr if
+ * even creating an NSAlert fails.
+ */
+ vsnprintf(errorbuf, lenof(errorbuf), p, ap);
+
+ alert = [NSAlert alloc];
+ if (!alert) {
+ fprintf(stderr, "fatal error (and NSAlert failed): %s\n", errorbuf);
+ } else {
+ alert = [[alert init] autorelease];
+ [alert addButtonWithTitle:@"Terminate"];
+ [alert setInformativeText:[NSString stringWithCString:errorbuf]];
+ [alert runModal];
+ }
+ exit(1);
+}
+
+void fatalbox(char *p, ...)
+{
+ va_list ap;
+ va_start(ap, p);
+ commonfatalbox(p, ap);
+ va_end(ap);
+}
+
+void modalfatalbox(char *p, ...)
+{
+ va_list ap;
+ va_start(ap, p);
+ commonfatalbox(p, ap);
+ va_end(ap);
+}
+
+void cmdline_error(char *p, ...)
+{
+ va_list ap;
+ fprintf(stderr, "%s: ", appname);
+ va_start(ap, p);
+ vfprintf(stderr, p, ap);
+ va_end(ap);
+ fputc('\n', stderr);
+ exit(1);
+}
+
+/*
+ * Clean up and exit.
+ */
+void cleanup_exit(int code)
+{
+ /*
+ * Clean up.
+ */
+ sk_cleanup();
+ random_save_seed();
+ exit(code);
+}
+
+/* ----------------------------------------------------------------------
+ * Tiny extension to NSMenuItem which carries a payload of a `void
+ * *', allowing several menu items to invoke the same message but
+ * pass different data through it.
+ */
+@interface DataMenuItem : NSMenuItem
+{
+ void *payload;
+}
+- (void)setPayload:(void *)d;
+- (void *)getPayload;
+@end
+@implementation DataMenuItem
+- (void)setPayload:(void *)d
+{
+ payload = d;
+}
+- (void *)getPayload
+{
+ return payload;
+}
+@end
+
+/* ----------------------------------------------------------------------
+ * Utility routines for constructing OS X menus.
+ */
+
+NSMenu *newmenu(const char *title)
+{
+ return [[[NSMenu allocWithZone:[NSMenu menuZone]]
+ initWithTitle:[NSString stringWithCString:title]]
+ autorelease];
+}
+
+NSMenu *newsubmenu(NSMenu *parent, const char *title)
+{
+ NSMenuItem *item;
+ NSMenu *child;
+
+ item = [[[NSMenuItem allocWithZone:[NSMenu menuZone]]
+ initWithTitle:[NSString stringWithCString:title]
+ action:NULL
+ keyEquivalent:@""]
+ autorelease];
+ child = newmenu(title);
+ [item setEnabled:YES];
+ [item setSubmenu:child];
+ [parent addItem:item];
+ return child;
+}
+
+id initnewitem(NSMenuItem *item, NSMenu *parent, const char *title,
+ const char *key, id target, SEL action)
+{
+ unsigned mask = NSCommandKeyMask;
+
+ if (key[strcspn(key, "-")]) {
+ while (*key && *key != '-') {
+ int c = tolower((unsigned char)*key);
+ if (c == 's') {
+ mask |= NSShiftKeyMask;
+ } else if (c == 'o' || c == 'a') {
+ mask |= NSAlternateKeyMask;
+ }
+ key++;
+ }
+ if (*key)
+ key++;
+ }
+
+ item = [[item initWithTitle:[NSString stringWithCString:title]
+ action:NULL
+ keyEquivalent:[NSString stringWithCString:key]]
+ autorelease];
+
+ if (*key)
+ [item setKeyEquivalentModifierMask: mask];
+
+ [item setEnabled:YES];
+ [item setTarget:target];
+ [item setAction:action];
+
+ [parent addItem:item];
+
+ return item;
+}
+
+NSMenuItem *newitem(NSMenu *parent, char *title, char *key,
+ id target, SEL action)
+{
+ return initnewitem([NSMenuItem allocWithZone:[NSMenu menuZone]],
+ parent, title, key, target, action);
+}
+
+/* ----------------------------------------------------------------------
+ * AppController: the object which receives the messages from all
+ * menu selections that aren't standard OS X functions.
+ */
+@implementation AppController
+
+- (id)init
+{
+ self = [super init];
+ timer = NULL;
+ return self;
+}
+
+- (void)newTerminal:(id)sender
+{
+ id win;
+ Config cfg;
+
+ do_defaults(NULL, &cfg);
+
+ cfg.protocol = -1; /* PROT_TERMINAL */
+
+ win = [[SessionWindow alloc] initWithConfig:cfg];
+ [win makeKeyAndOrderFront:self];
+}
+
+- (void)newSessionConfig:(id)sender
+{
+ id win;
+ Config cfg;
+
+ do_defaults(NULL, &cfg);
+
+ win = [[ConfigWindow alloc] initWithConfig:cfg];
+ [win makeKeyAndOrderFront:self];
+}
+
+- (void)newSessionWithConfig:(id)vdata
+{
+ id win;
+ Config cfg;
+ NSData *data = (NSData *)vdata;
+
+ assert([data length] == sizeof(cfg));
+ [data getBytes:&cfg];
+
+ win = [[SessionWindow alloc] initWithConfig:cfg];
+ [win makeKeyAndOrderFront:self];
+}
+
+- (NSMenu *)applicationDockMenu:(NSApplication *)sender
+{
+ NSMenu *menu = newmenu("Dock Menu");
+ /*
+ * FIXME: Add some useful things to this, probably including
+ * the saved session list.
+ */
+ return menu;
+}
+
+- (void)timerFired:(id)sender
+{
+ long now, next;
+
+ assert(sender == timer);
+
+ /* `sender' is the timer itself, so its userInfo is an NSNumber. */
+ now = [(NSNumber *)[sender userInfo] longValue];
+
+ [sender invalidate];
+
+ timer = NULL;
+
+ if (run_timers(now, &next))
+ [self setTimer:next];
+}
+
+- (void)setTimer:(long)next
+{
+ long interval = next - GETTICKCOUNT();
+ float finterval;
+
+ if (interval <= 0)
+ interval = 1; /* just in case */
+
+ finterval = interval / (float)TICKSPERSEC;
+
+ if (timer) {
+ [timer invalidate];
+ }
+
+ timer = [NSTimer scheduledTimerWithTimeInterval:finterval
+ target:self selector:@selector(timerFired:)
+ userInfo:[NSNumber numberWithLong:next] repeats:NO];
+}
+
+@end
+
+void timer_change_notify(long next)
+{
+ [controller setTimer:next];
+}
+
+/* ----------------------------------------------------------------------
+ * Annoyingly, it looks as if I have to actually subclass
+ * NSApplication if I want to catch NSApplicationDefined events. So
+ * here goes.
+ */
+@interface MyApplication : NSApplication
+{
+}
+@end
+@implementation MyApplication
+- (void)sendEvent:(NSEvent *)ev
+{
+ if ([ev type] == NSApplicationDefined)
+ osxsel_process_results();
+
+ [super sendEvent:ev];
+}
+@end
+
+/* ----------------------------------------------------------------------
+ * Main program. Constructs the menus and runs the application.
+ */
+int main(int argc, char **argv)
+{
+ NSAutoreleasePool *pool;
+ NSMenu *menu;
+ NSMenuItem *item;
+ NSImage *icon;
+
+ pool = [[NSAutoreleasePool alloc] init];
+
+ icon = [NSImage imageNamed:@"NSApplicationIcon"];
+ [MyApplication sharedApplication];
+ [NSApp setApplicationIconImage:icon];
+
+ controller = [[[AppController alloc] init] autorelease];
+ [NSApp setDelegate:controller];
+
+ [NSApp setMainMenu: newmenu("Main Menu")];
+
+ menu = newsubmenu([NSApp mainMenu], "Apple Menu");
+ [NSApp setServicesMenu:newsubmenu(menu, "Services")];
+ [menu addItem:[NSMenuItem separatorItem]];
+ item = newitem(menu, "Hide PuTTY", "h", NSApp, @selector(hide:));
+ item = newitem(menu, "Hide Others", "o-h", NSApp, @selector(hideOtherApplications:));
+ item = newitem(menu, "Show All", "", NSApp, @selector(unhideAllApplications:));
+ [menu addItem:[NSMenuItem separatorItem]];
+ item = newitem(menu, "Quit", "q", NSApp, @selector(terminate:));
+ [NSApp setAppleMenu: menu];
+
+ menu = newsubmenu([NSApp mainMenu], "File");
+ item = newitem(menu, "New", "n", NULL, @selector(newSessionConfig:));
+ item = newitem(menu, "New Terminal", "t", NULL, @selector(newTerminal:));
+ item = newitem(menu, "Close", "w", NULL, @selector(performClose:));
+
+ menu = newsubmenu([NSApp mainMenu], "Window");
+ [NSApp setWindowsMenu: menu];
+ item = newitem(menu, "Minimise Window", "m", NULL, @selector(performMiniaturize:));
+
+// menu = newsubmenu([NSApp mainMenu], "Help");
+// item = newitem(menu, "PuTTY Help", "?", NSApp, @selector(showHelp:));
+
+ /*
+ * Start up the sub-thread doing select().
+ */
+ osxsel_init();
+
+ /*
+ * Start up networking.
+ */
+ sk_init();
+
+ /*
+ * FIXME: To make initial debugging more convenient I'm going
+ * to start by opening a session window unconditionally. This
+ * will probably change later on.
+ */
+ [controller newSessionConfig:nil];
+
+ [NSApp run];
+ [pool release];
+
+ return 0;
+}
--- /dev/null
+/*
+ * osxsel.m: OS X implementation of the front end interface to uxsel.
+ */
+
+#import <Cocoa/Cocoa.h>
+#include <unistd.h>
+#include "putty.h"
+#include "osxclass.h"
+
+/*
+ * The unofficial Cocoa FAQ at
+ *
+ * http://www.alastairs-place.net/cocoa/faq.txt
+ *
+ * says that Cocoa has the native ability to be given an fd and
+ * tell you when it becomes readable, but cannot tell you when it
+ * becomes _writable_. This is unacceptable to PuTTY, which depends
+ * for correct functioning on being told both. Therefore, I can't
+ * use the Cocoa native mechanism.
+ *
+ * Instead, I'm going to resort to threads. I start a second thread
+ * whose job is to do selects. At the termination of every select,
+ * it posts a Cocoa event into the main thread's event queue, so
+ * that the main thread gets select results interleaved with other
+ * GUI operations. Communication from the main thread _to_ the
+ * select thread is performed by writing to a pipe whose other end
+ * is one of the file descriptors being selected on. (This is the
+ * only sensible way, because we have to be able to interrupt a
+ * select in order to provide a new fd list.)
+ */
+
+/*
+ * In more detail, the select thread must:
+ *
+ * - start off by listening to _just_ the pipe, waiting to be told
+ * to begin a select.
+ *
+ * - when it receives the `start' command, it should read the
+ * shared uxsel data (which is protected by a mutex), set up its
+ * select, and begin it.
+ *
+ * - when the select terminates, it should write the results
+ * (perhaps minus the inter-thread pipe if it's there) into
+ * shared memory and dispatch a GUI event to let the main thread
+ * know.
+ *
+ * - the main thread will then think about it, do some processing,
+ * and _then_ send a command saying `now restart select'. Before
+ * sending that command it might easily have tinkered with the
+ * uxsel structures, which is why it waited before sending it.
+ *
+ * - EOF on the inter-thread pipe, of course, means the process
+ * has finished completely, so the select thread terminates.
+ *
+ * - The main thread may wish to adjust the uxsel settings in the
+ * middle of a select. In this situation it first writes the new
+ * data to the shared memory area, then notifies the select
+ * thread by writing to the inter-thread pipe.
+ *
+ * So the upshot is that the sequence of operations performed in
+ * the select thread must be:
+ *
+ * - read a byte from the pipe (which may block)
+ *
+ * - read the shared uxsel data and perform a select
+ *
+ * - notify the main thread of interesting select results (if any)
+ *
+ * - loop round again from the top.
+ *
+ * This is sufficient. Notifying the select thread asynchronously
+ * by writing to the pipe will cause its select to terminate and
+ * another to begin immediately without blocking. If the select
+ * thread's select terminates due to network data, its subsequent
+ * pipe read will block until the main thread is ready to let it
+ * loose again.
+ */
+
+static int osxsel_pipe[2];
+
+static NSLock *osxsel_inlock;
+static fd_set osxsel_rfds_in;
+static fd_set osxsel_wfds_in;
+static fd_set osxsel_xfds_in;
+static int osxsel_inmax;
+
+static NSLock *osxsel_outlock;
+static fd_set osxsel_rfds_out;
+static fd_set osxsel_wfds_out;
+static fd_set osxsel_xfds_out;
+static int osxsel_outmax;
+
+static int inhibit_start_select;
+
+/*
+ * NSThread requires an object method as its thread procedure, so
+ * here I define a trivial holding class.
+ */
+@class OSXSel;
+@interface OSXSel : NSObject
+{
+}
+- (void)runThread:(id)arg;
+@end
+@implementation OSXSel
+- (void)runThread:(id)arg
+{
+ char c;
+ fd_set r, w, x;
+ int n, ret;
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+
+ while (1) {
+ /*
+ * Read one byte from the pipe.
+ */
+ ret = read(osxsel_pipe[0], &c, 1);
+
+ if (ret <= 0)
+ return; /* terminate the thread */
+
+ /*
+ * Now set up the select data.
+ */
+ [osxsel_inlock lock];
+ memcpy(&r, &osxsel_rfds_in, sizeof(fd_set));
+ memcpy(&w, &osxsel_wfds_in, sizeof(fd_set));
+ memcpy(&x, &osxsel_xfds_in, sizeof(fd_set));
+ n = osxsel_inmax;
+ [osxsel_inlock unlock];
+ FD_SET(osxsel_pipe[0], &r);
+ if (n < osxsel_pipe[0]+1)
+ n = osxsel_pipe[0]+1;
+
+ /*
+ * Perform the select.
+ */
+ ret = select(n, &r, &w, &x, NULL);
+
+ /*
+ * Detect the one special case in which the only
+ * interesting fd was the inter-thread pipe. In that
+ * situation only we are interested - the main thread will
+ * not be!
+ */
+ if (ret == 1 && FD_ISSET(osxsel_pipe[0], &r))
+ continue; /* just loop round again */
+
+ /*
+ * Write the select results to shared data.
+ *
+ * I _think_ we don't need this data to be lock-protected:
+ * it won't be read by the main thread until after we send
+ * a message indicating that we've finished writing it, and
+ * we won't start another select (hence potentially writing
+ * it again) until the main thread notifies us in return.
+ *
+ * However, I'm scared of multithreading and not totally
+ * convinced of my reasoning, so I'm going to lock it
+ * anyway.
+ */
+ [osxsel_outlock lock];
+ memcpy(&osxsel_rfds_out, &r, sizeof(fd_set));
+ memcpy(&osxsel_wfds_out, &w, sizeof(fd_set));
+ memcpy(&osxsel_xfds_out, &x, sizeof(fd_set));
+ osxsel_outmax = n;
+ [osxsel_outlock unlock];
+
+ /*
+ * Post a message to the main thread's message queue
+ * telling it that select data is available.
+ */
+ [NSApp postEvent:[NSEvent otherEventWithType:NSApplicationDefined
+ location:NSMakePoint(0,0)
+ modifierFlags:0
+ timestamp:0
+ windowNumber:0
+ context:nil
+ subtype:0
+ data1:0
+ data2:0]
+ atStart:NO];
+ }
+
+ [pool release];
+}
+@end
+
+void osxsel_init(void)
+{
+ uxsel_init();
+
+ if (pipe(osxsel_pipe) < 0) {
+ fatalbox("Unable to set up inter-thread pipe for select");
+ }
+ [NSThread detachNewThreadSelector:@selector(runThread:)
+ toTarget:[[[OSXSel alloc] init] retain] withObject:nil];
+ /*
+ * Also initialise (i.e. clear) the input fd_sets. Need not
+ * start a select just yet - the select thread will block until
+ * we have at least one fd for it!
+ */
+ FD_ZERO(&osxsel_rfds_in);
+ FD_ZERO(&osxsel_wfds_in);
+ FD_ZERO(&osxsel_xfds_in);
+ osxsel_inmax = 0;
+ /*
+ * Initialise the mutex locks used to protect the data passed
+ * between threads.
+ */
+ osxsel_inlock = [[[NSLock alloc] init] retain];
+ osxsel_outlock = [[[NSLock alloc] init] retain];
+}
+
+static void osxsel_start_select(void)
+{
+ char c = 'g'; /* for `Go!' :-) but it's never used */
+
+ if (!inhibit_start_select)
+ write(osxsel_pipe[1], &c, 1);
+}
+
+int uxsel_input_add(int fd, int rwx)
+{
+ /*
+ * Add the new fd to the appropriate input fd_sets, then write
+ * to the inter-thread pipe.
+ */
+ [osxsel_inlock lock];
+ if (rwx & 1)
+ FD_SET(fd, &osxsel_rfds_in);
+ else
+ FD_CLR(fd, &osxsel_rfds_in);
+ if (rwx & 2)
+ FD_SET(fd, &osxsel_wfds_in);
+ else
+ FD_CLR(fd, &osxsel_wfds_in);
+ if (rwx & 4)
+ FD_SET(fd, &osxsel_xfds_in);
+ else
+ FD_CLR(fd, &osxsel_xfds_in);
+ if (osxsel_inmax < fd+1)
+ osxsel_inmax = fd+1;
+ [osxsel_inlock unlock];
+ osxsel_start_select();
+
+ /*
+ * We must return an `id' which will be passed back to us at
+ * the time of uxsel_input_remove. Since we have no need to
+ * store ids in that sense, we might as well go with the fd
+ * itself.
+ */
+ return fd;
+}
+
+void uxsel_input_remove(int id)
+{
+ /*
+ * Remove the fd from all the input fd_sets. In this
+ * implementation, the simplest way to do that is to call
+ * uxsel_input_add with rwx==0!
+ */
+ uxsel_input_add(id, 0);
+}
+
+/*
+ * Function called in the main thread to process results. It will
+ * have to read the output fd_sets, go through them, call back to
+ * uxsel with the results, and then write to the inter-thread pipe.
+ *
+ * This function will have to be called from an event handler in
+ * osxmain.m, which will therefore necessarily contain a small part
+ * of this mechanism (along with calling osxsel_init).
+ */
+void osxsel_process_results(void)
+{
+ int i;
+
+ /*
+ * We must write to the pipe to start a fresh select _even if_
+ * there were no changes. So for efficiency, we set a flag here
+ * which inhibits uxsel_input_{add,remove} from writing to the
+ * pipe; then once we finish processing, we clear the flag
+ * again and write a single byte ourselves. It's cleaner,
+ * because it wakes up the select thread fewer times.
+ */
+ inhibit_start_select = TRUE;
+
+ [osxsel_outlock lock];
+
+ for (i = 0; i < osxsel_outmax; i++) {
+ if (FD_ISSET(i, &osxsel_xfds_out))
+ select_result(i, 4);
+ }
+ for (i = 0; i < osxsel_outmax; i++) {
+ if (FD_ISSET(i, &osxsel_rfds_out))
+ select_result(i, 1);
+ }
+ for (i = 0; i < osxsel_outmax; i++) {
+ if (FD_ISSET(i, &osxsel_wfds_out))
+ select_result(i, 2);
+ }
+
+ [osxsel_outlock unlock];
+
+ inhibit_start_select = FALSE;
+ osxsel_start_select();
+}
--- /dev/null
+/*
+ * osxwin.m: code to manage a session window in Mac OS X PuTTY.
+ */
+
+#import <Cocoa/Cocoa.h>
+#include "putty.h"
+#include "terminal.h"
+#include "osxclass.h"
+
+/* 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)
+
+/*
+ * The key component of the per-session data is the SessionWindow
+ * class. A pointer to this is used as the frontend handle, to be
+ * passed to all the platform-independent subsystems that require
+ * one.
+ */
+
+@interface TerminalView : NSImageView
+{
+ NSFont *font;
+ NSImage *image;
+ Terminal *term;
+ Config cfg;
+ NSColor *colours[NALLCOLOURS];
+ float fw, fasc, fdesc, fh;
+}
+- (void)drawStartFinish:(BOOL)start;
+- (void)setColour:(int)n r:(float)r g:(float)g b:(float)b;
+- (void)doText:(wchar_t *)text len:(int)len x:(int)x y:(int)y
+ attr:(unsigned long)attr lattr:(int)lattr;
+@end
+
+@implementation TerminalView
+- (BOOL)isFlipped
+{
+ return YES;
+}
+- (id)initWithTerminal:(Terminal *)aTerm config:(Config)aCfg
+{
+ float w, h;
+
+ self = [self initWithFrame:NSMakeRect(0,0,100,100)];
+
+ term = aTerm;
+ cfg = aCfg;
+
+ /*
+ * Initialise the fonts we're going to use.
+ *
+ * FIXME: for the moment I'm sticking with exactly one default font.
+ */
+ font = [NSFont userFixedPitchFontOfSize:0];
+
+ /*
+ * Now determine the size of the primary font.
+ *
+ * FIXME: If we have multiple fonts, we may need to set fasc
+ * and fdesc to the _maximum_ asc and desc out of all the
+ * fonts, _before_ adding them together to get fh.
+ */
+ fw = [font widthOfString:@"A"];
+ fasc = [font ascender];
+ fdesc = -[font descender];
+ fh = fasc + fdesc;
+ fh = (int)fh + (fh > (int)fh); /* round up, ickily */
+
+ /*
+ * Use this to figure out the size of the terminal view.
+ */
+ w = fw * term->cols;
+ h = fh * term->rows;
+
+ /*
+ * And set our size and subimage.
+ */
+ image = [[NSImage alloc] initWithSize:NSMakeSize(w,h)];
+ [image setFlipped:YES];
+ [self setImage:image];
+ [self setFrame:NSMakeRect(0,0,w,h)];
+
+ term_invalidate(term);
+
+ return self;
+}
+- (void)drawStartFinish:(BOOL)start
+{
+ if (start)
+ [image lockFocus];
+ else
+ [image unlockFocus];
+}
+- (void)doText:(wchar_t *)text len:(int)len x:(int)x y:(int)y
+ attr:(unsigned long)attr lattr:(int)lattr
+{
+ int nfg, nbg, rlen, widefactor;
+ float ox, oy, tw, th;
+ NSDictionary *attrdict;
+
+ /* FIXME: TATTR_COMBINING */
+
+ nfg = ((attr & ATTR_FGMASK) >> ATTR_FGSHIFT);
+ nbg = ((attr & ATTR_BGMASK) >> ATTR_BGSHIFT);
+ if (attr & ATTR_REVERSE) {
+ int t = nfg;
+ nfg = nbg;
+ nbg = t;
+ }
+ if (cfg.bold_colour && (attr & ATTR_BOLD)) {
+ if (nfg < 16) nfg |= 8;
+ else if (nfg >= 256) nfg |= 1;
+ }
+ if (cfg.bold_colour && (attr & ATTR_BLINK)) {
+ if (nbg < 16) nbg |= 8;
+ else if (nbg >= 256) nbg |= 1;
+ }
+ if (attr & TATTR_ACTCURS) {
+ nfg = 260;
+ nbg = 261;
+ }
+
+ if (attr & ATTR_WIDE) {
+ widefactor = 2;
+ /* FIXME: what do we actually have to do about wide characters? */
+ } else {
+ widefactor = 1;
+ }
+
+ /* FIXME: ATTR_BOLD without cfg.bold_colour */
+
+ if ((lattr & LATTR_MODE) != LATTR_NORM) {
+ x *= 2;
+ if (x >= term->cols)
+ return;
+ if (x + len*2*widefactor > term->cols)
+ len = (term->cols-x)/2/widefactor;/* trim to LH half */
+ rlen = len * 2;
+ } else
+ rlen = len;
+
+ /* FIXME: how do we actually implement double-{width,height} lattrs? */
+
+ ox = x * fw;
+ oy = y * fh;
+ tw = rlen * widefactor * fw;
+ th = fh;
+
+ /*
+ * Set the clipping rectangle.
+ */
+ [[NSGraphicsContext currentContext] saveGraphicsState];
+ [NSBezierPath clipRect:NSMakeRect(ox, oy, tw, th)];
+
+ attrdict = [NSDictionary dictionaryWithObjectsAndKeys:
+ colours[nfg], NSForegroundColorAttributeName,
+ colours[nbg], NSBackgroundColorAttributeName,
+ font, NSFontAttributeName, nil];
+
+ /*
+ * Create an NSString and draw it.
+ *
+ * Annoyingly, although our input is wchar_t which is four
+ * bytes wide on OS X and terminal.c supports 32-bit Unicode,
+ * we must convert into the two-byte type `unichar' to store in
+ * NSString, so we lose display capability for extra-BMP stuff
+ * at this point.
+ */
+ {
+ NSString *string;
+ unichar *utext;
+ int i;
+
+ utext = snewn(len, unichar);
+ for (i = 0; i < len; i++)
+ utext[i] = (text[i] >= 0x10000 ? 0xFFFD : text[i]);
+
+ string = [NSString stringWithCharacters:utext length:len];
+ [string drawAtPoint:NSMakePoint(ox, oy) withAttributes:attrdict];
+
+ sfree(utext);
+ }
+
+ /*
+ * Restore the graphics state from before the clipRect: call.
+ */
+ [[NSGraphicsContext currentContext] restoreGraphicsState];
+
+ /*
+ * And flag this area as needing display.
+ */
+ [self setNeedsDisplayInRect:NSMakeRect(ox, oy, tw, th)];
+}
+
+- (void)setColour:(int)n r:(float)r g:(float)g b:(float)b
+{
+ assert(n >= 0 && n < lenof(colours));
+ colours[n] = [[NSColor colorWithDeviceRed:r green:g blue:b alpha:1.0]
+ retain];
+}
+@end
+
+@implementation SessionWindow
+- (id)initWithConfig:(Config)aCfg
+{
+ NSRect rect = { {0,0}, {0,0} };
+
+ alert_ctx = NULL;
+
+ cfg = aCfg; /* structure copy */
+
+ init_ucs(&ucsdata, cfg.line_codepage, cfg.utf8_override,
+ CS_UTF8, cfg.vtmode);
+ term = term_init(&cfg, &ucsdata, self);
+ logctx = log_init(self, &cfg);
+ term_provide_logctx(term, logctx);
+ term_size(term, cfg.height, cfg.width, cfg.savelines);
+
+ termview = [[[TerminalView alloc] initWithTerminal:term config:cfg]
+ autorelease];
+
+ /*
+ * Now work out the size of the window.
+ */
+ rect = [termview frame];
+ rect.origin = NSMakePoint(0,0);
+ rect.size.width += 2 * cfg.window_border;
+ rect.size.height += 2 * cfg.window_border;
+
+ /*
+ * Set up a backend.
+ */
+ back = backend_from_proto(cfg.protocol);
+ if (!back)
+ back = &pty_backend;
+
+ {
+ const char *error;
+ char *realhost = NULL;
+ error = back->init(self, &backhandle, &cfg, cfg.host, cfg.port,
+ &realhost, cfg.tcp_nodelay, cfg.tcp_keepalives);
+ if (error) {
+ fatalbox("%s\n", error); /* FIXME: connection_fatal at worst */
+ }
+
+ if (realhost)
+ sfree(realhost); /* FIXME: do something with this */
+ }
+ back->provide_logctx(backhandle, logctx);
+
+ /*
+ * Create a line discipline. (This must be done after creating
+ * the terminal _and_ the backend, since it needs to be passed
+ * pointers to both.)
+ */
+ ldisc = ldisc_create(&cfg, term, back, backhandle, self);
+
+ /*
+ * FIXME: Set up a scrollbar.
+ */
+
+ self = [super initWithContentRect:rect
+ styleMask:(NSTitledWindowMask | NSMiniaturizableWindowMask |
+ NSClosableWindowMask)
+ backing:NSBackingStoreBuffered
+ defer:YES];
+ [self setTitle:@"PuTTY"];
+
+ [self setIgnoresMouseEvents:NO];
+
+ /*
+ * Put the terminal view in the window.
+ */
+ rect = [termview frame];
+ rect.origin = NSMakePoint(cfg.window_border, cfg.window_border);
+ [termview setFrame:rect];
+ [[self contentView] addSubview:termview];
+
+ /*
+ * Set up the colour palette.
+ */
+ palette_reset(self);
+
+ /*
+ * FIXME: Only the _first_ document window should be centred.
+ * The subsequent ones should appear down and to the right of
+ * it, probably using the cascade function provided by Cocoa.
+ * Also we're apparently required by the HIG to remember and
+ * reuse previous positions of windows, although I'm not sure
+ * how that works if the user opens more than one of the same
+ * session type.
+ */
+ [self center]; /* :-) */
+
+ exited = FALSE;
+
+ return self;
+}
+
+- (void)dealloc
+{
+ /*
+ * FIXME: Here we must deallocate all sorts of stuff: the
+ * terminal, the backend, the ldisc, the logctx, you name it.
+ * Do so.
+ */
+ sfree(alert_ctx);
+ if (back)
+ back->free(backhandle);
+ if (ldisc)
+ ldisc_free(ldisc);
+ /* ldisc must be freed before term, since ldisc_free expects term
+ * still to be around. */
+ if (logctx)
+ log_free(logctx);
+ if (term)
+ term_free(term);
+ [super dealloc];
+}
+
+- (void)drawStartFinish:(BOOL)start
+{
+ [termview drawStartFinish:start];
+}
+
+- (void)setColour:(int)n r:(float)r g:(float)g b:(float)b
+{
+ [termview setColour:n r:r g:g b:b];
+}
+
+- (void)doText:(wchar_t *)text len:(int)len x:(int)x y:(int)y
+ attr:(unsigned long)attr lattr:(int)lattr
+{
+ /* Pass this straight on to the TerminalView. */
+ [termview doText:text len:len x:x y:y attr:attr lattr:lattr];
+}
+
+- (Config *)cfg
+{
+ return &cfg;
+}
+
+- (void)keyDown:(NSEvent *)ev
+{
+ NSString *s = [ev characters];
+ int i;
+ int n = [s length], c = [s characterAtIndex:0], m = [ev modifierFlags];
+ int cm = [[ev charactersIgnoringModifiers] characterAtIndex:0];
+ wchar_t output[32];
+ char coutput[32];
+ int use_coutput = FALSE, special = FALSE, start, end;
+
+//printf("n=%d c=U+%04x cm=U+%04x m=%08x\n", n, c, cm, m);
+
+ /*
+ * FIXME: Alt+numberpad codes.
+ */
+
+ /*
+ * Shift and Ctrl with PageUp/PageDown for scrollback.
+ */
+ if (n == 1 && c == NSPageUpFunctionKey && (m & NSShiftKeyMask)) {
+ term_scroll(term, 0, -term->rows/2);
+ return;
+ }
+ if (n == 1 && c == NSPageUpFunctionKey && (m & NSControlKeyMask)) {
+ term_scroll(term, 0, -1);
+ return;
+ }
+ if (n == 1 && c == NSPageDownFunctionKey && (m & NSShiftKeyMask)) {
+ term_scroll(term, 0, +term->rows/2);
+ return;
+ }
+ if (n == 1 && c == NSPageDownFunctionKey && (m & NSControlKeyMask)) {
+ term_scroll(term, 0, +1);
+ return;
+ }
+
+ /*
+ * FIXME: Shift-Ins for paste? Or is that not Maccy enough?
+ */
+
+ /*
+ * FIXME: Alt (Option? Command?) prefix in general.
+ *
+ * (Note that Alt-Shift-thing will work just by looking at
+ * charactersIgnoringModifiers; but Alt-Ctrl-thing will need
+ * processing properly, and Alt-as-in-Option won't happen at
+ * all. Hmmm.)
+ *
+ * (Note also that we need to be able to override menu key
+ * equivalents before this is particularly useful.)
+ */
+ start = 1;
+ end = start;
+
+ /*
+ * Ctrl-` is the same as Ctrl-\, unless we already have a
+ * better idea.
+ */
+ if ((m & NSControlKeyMask) && n == 1 && cm == '`' && c == '`') {
+ output[1] = '\x1c';
+ end = 2;
+ }
+
+ /* We handle Return ourselves, because it needs to be flagged as
+ * special to ldisc. */
+ if (n == 1 && c == '\015') {
+ coutput[1] = '\015';
+ use_coutput = TRUE;
+ end = 2;
+ special = TRUE;
+ }
+
+ /* Control-Shift-Space is 160 (ISO8859 nonbreaking space) */
+ if (n == 1 && (m & NSControlKeyMask) && (m & NSShiftKeyMask) &&
+ cm == ' ') {
+ output[1] = '\240';
+ end = 2;
+ }
+
+ /* Control-2, Control-Space and Control-@ are all NUL. */
+ if ((m & NSControlKeyMask) && n == 1 &&
+ (cm == '2' || cm == '@' || cm == ' ') && c == cm) {
+ output[1] = '\0';
+ end = 2;
+ }
+
+ /* We don't let MacOS tell us what Backspace is! We know better. */
+ if (cm == 0x7F && !(m & NSShiftKeyMask)) {
+ coutput[1] = cfg.bksp_is_delete ? '\x7F' : '\x08';
+ end = 2;
+ use_coutput = special = TRUE;
+ }
+ /* For Shift Backspace, do opposite of what is configured. */
+ if (cm == 0x7F && (m & NSShiftKeyMask)) {
+ coutput[1] = cfg.bksp_is_delete ? '\x08' : '\x7F';
+ end = 2;
+ use_coutput = special = TRUE;
+ }
+
+ /* Shift-Tab is ESC [ Z. Oddly, this combination generates ^Y by
+ * default on MacOS! */
+ if (cm == 0x19 && (m & NSShiftKeyMask) && !(m & NSControlKeyMask)) {
+ end = 1;
+ output[end++] = '\033';
+ output[end++] = '[';
+ output[end++] = 'Z';
+ }
+
+ /*
+ * NetHack keypad mode.
+ */
+ if (cfg.nethack_keypad && (m & NSNumericPadKeyMask)) {
+ wchar_t *keys = NULL;
+ switch (cm) {
+ case '1': keys = L"bB"; break;
+ case '2': keys = L"jJ"; break;
+ case '3': keys = L"nN"; break;
+ case '4': keys = L"hH"; break;
+ case '5': keys = L".."; break;
+ case '6': keys = L"lL"; break;
+ case '7': keys = L"yY"; break;
+ case '8': keys = L"kK"; break;
+ case '9': keys = L"uU"; break;
+ }
+ if (keys) {
+ end = 2;
+ if (m & NSShiftKeyMask)
+ output[1] = keys[1];
+ else
+ output[1] = keys[0];
+ goto done;
+ }
+ }
+
+ /*
+ * Application keypad mode.
+ */
+ if (term->app_keypad_keys && !cfg.no_applic_k &&
+ (m & NSNumericPadKeyMask)) {
+ int xkey = 0;
+ switch (cm) {
+ case NSClearLineFunctionKey: xkey = 'P'; break;
+ case '=': xkey = 'Q'; break;
+ case '/': xkey = 'R'; break;
+ case '*': xkey = 'S'; break;
+ /*
+ * FIXME: keypad - and + need to be mapped to ESC O l
+ * and ESC O k, or ESC O l and ESC O m, depending on
+ * xterm function key mode, and I can't remember which
+ * goes where.
+ */
+ case '\003': xkey = 'M'; break;
+ case '0': xkey = 'p'; break;
+ case '1': xkey = 'q'; break;
+ case '2': xkey = 'r'; break;
+ case '3': xkey = 's'; break;
+ case '4': xkey = 't'; break;
+ case '5': xkey = 'u'; break;
+ case '6': xkey = 'v'; break;
+ case '7': xkey = 'w'; break;
+ case '8': xkey = 'x'; break;
+ case '9': xkey = 'y'; break;
+ case '.': xkey = 'n'; break;
+ }
+ if (xkey) {
+ if (term->vt52_mode) {
+ if (xkey >= 'P' && xkey <= 'S') {
+ output[end++] = '\033';
+ output[end++] = xkey;
+ } else {
+ output[end++] = '\033';
+ output[end++] = '?';
+ output[end++] = xkey;
+ }
+ } else {
+ output[end++] = '\033';
+ output[end++] = 'O';
+ output[end++] = xkey;
+ }
+ goto done;
+ }
+ }
+
+ /*
+ * Next, all the keys that do tilde codes. (ESC '[' nn '~',
+ * for integer decimal nn.)
+ *
+ * We also deal with the weird ones here. Linux VCs replace F1
+ * to F5 by ESC [ [ A to ESC [ [ E. rxvt doesn't do _that_, but
+ * does replace Home and End (1~ and 4~) by ESC [ H and ESC O w
+ * respectively.
+ */
+ {
+ int code = 0;
+ switch (cm) {
+ case NSF1FunctionKey:
+ code = (m & NSShiftKeyMask ? 23 : 11);
+ break;
+ case NSF2FunctionKey:
+ code = (m & NSShiftKeyMask ? 24 : 12);
+ break;
+ case NSF3FunctionKey:
+ code = (m & NSShiftKeyMask ? 25 : 13);
+ break;
+ case NSF4FunctionKey:
+ code = (m & NSShiftKeyMask ? 26 : 14);
+ break;
+ case NSF5FunctionKey:
+ code = (m & NSShiftKeyMask ? 28 : 15);
+ break;
+ case NSF6FunctionKey:
+ code = (m & NSShiftKeyMask ? 29 : 17);
+ break;
+ case NSF7FunctionKey:
+ code = (m & NSShiftKeyMask ? 31 : 18);
+ break;
+ case NSF8FunctionKey:
+ code = (m & NSShiftKeyMask ? 32 : 19);
+ break;
+ case NSF9FunctionKey:
+ code = (m & NSShiftKeyMask ? 33 : 20);
+ break;
+ case NSF10FunctionKey:
+ code = (m & NSShiftKeyMask ? 34 : 21);
+ break;
+ case NSF11FunctionKey:
+ code = 23;
+ break;
+ case NSF12FunctionKey:
+ code = 24;
+ break;
+ case NSF13FunctionKey:
+ code = 25;
+ break;
+ case NSF14FunctionKey:
+ code = 26;
+ break;
+ case NSF15FunctionKey:
+ code = 28;
+ break;
+ case NSF16FunctionKey:
+ code = 29;
+ break;
+ case NSF17FunctionKey:
+ code = 31;
+ break;
+ case NSF18FunctionKey:
+ code = 32;
+ break;
+ case NSF19FunctionKey:
+ code = 33;
+ break;
+ case NSF20FunctionKey:
+ code = 34;
+ break;
+ }
+ if (!(m & NSControlKeyMask)) switch (cm) {
+ case NSHomeFunctionKey:
+ code = 1;
+ break;
+#ifdef FIXME
+ case GDK_Insert: case GDK_KP_Insert:
+ code = 2;
+ break;
+#endif
+ case NSDeleteFunctionKey:
+ code = 3;
+ break;
+ case NSEndFunctionKey:
+ code = 4;
+ break;
+ case NSPageUpFunctionKey:
+ code = 5;
+ break;
+ case NSPageDownFunctionKey:
+ code = 6;
+ break;
+ }
+ /* Reorder edit keys to physical order */
+ if (cfg.funky_type == FUNKY_VT400 && code <= 6)
+ code = "\0\2\1\4\5\3\6"[code];
+
+ if (term->vt52_mode && code > 0 && code <= 6) {
+ output[end++] = '\033';
+ output[end++] = " HLMEIG"[code];
+ goto done;
+ }
+
+ if (cfg.funky_type == FUNKY_SCO && /* SCO function keys */
+ code >= 11 && code <= 34) {
+ char codes[] = "MNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz@[\\]^_`{";
+ int index = 0;
+ switch (cm) {
+ case NSF1FunctionKey: index = 0; break;
+ case NSF2FunctionKey: index = 1; break;
+ case NSF3FunctionKey: index = 2; break;
+ case NSF4FunctionKey: index = 3; break;
+ case NSF5FunctionKey: index = 4; break;
+ case NSF6FunctionKey: index = 5; break;
+ case NSF7FunctionKey: index = 6; break;
+ case NSF8FunctionKey: index = 7; break;
+ case NSF9FunctionKey: index = 8; break;
+ case NSF10FunctionKey: index = 9; break;
+ case NSF11FunctionKey: index = 10; break;
+ case NSF12FunctionKey: index = 11; break;
+ }
+ if (m & NSShiftKeyMask) index += 12;
+ if (m & NSControlKeyMask) index += 24;
+ output[end++] = '\033';
+ output[end++] = '[';
+ output[end++] = codes[index];
+ goto done;
+ }
+ if (cfg.funky_type == FUNKY_SCO && /* SCO small keypad */
+ code >= 1 && code <= 6) {
+ char codes[] = "HL.FIG";
+ if (code == 3) {
+ output[1] = '\x7F';
+ end = 2;
+ } else {
+ output[end++] = '\033';
+ output[end++] = '[';
+ output[end++] = codes[code-1];
+ }
+ goto done;
+ }
+ if ((term->vt52_mode || cfg.funky_type == FUNKY_VT100P) &&
+ code >= 11 && code <= 24) {
+ int offt = 0;
+ if (code > 15)
+ offt++;
+ if (code > 21)
+ offt++;
+ if (term->vt52_mode) {
+ output[end++] = '\033';
+ output[end++] = code + 'P' - 11 - offt;
+ } else {
+ output[end++] = '\033';
+ output[end++] = 'O';
+ output[end++] = code + 'P' - 11 - offt;
+ }
+ goto done;
+ }
+ if (cfg.funky_type == FUNKY_LINUX && code >= 11 && code <= 15) {
+ output[end++] = '\033';
+ output[end++] = '[';
+ output[end++] = '[';
+ output[end++] = code + 'A' - 11;
+ goto done;
+ }
+ if (cfg.funky_type == FUNKY_XTERM && code >= 11 && code <= 14) {
+ if (term->vt52_mode) {
+ output[end++] = '\033';
+ output[end++] = code + 'P' - 11;
+ } else {
+ output[end++] = '\033';
+ output[end++] = 'O';
+ output[end++] = code + 'P' - 11;
+ }
+ goto done;
+ }
+ if (cfg.rxvt_homeend && (code == 1 || code == 4)) {
+ if (code == 1) {
+ output[end++] = '\033';
+ output[end++] = '[';
+ output[end++] = 'H';
+ } else {
+ output[end++] = '\033';
+ output[end++] = 'O';
+ output[end++] = 'w';
+ }
+ goto done;
+ }
+ if (code) {
+ char buf[20];
+ sprintf(buf, "\x1B[%d~", code);
+ for (i = 0; buf[i]; i++)
+ output[end++] = buf[i];
+ goto done;
+ }
+ }
+
+ /*
+ * Cursor keys. (This includes the numberpad cursor keys,
+ * if we haven't already done them due to app keypad mode.)
+ */
+ {
+ int xkey = 0;
+ switch (cm) {
+ case NSUpArrowFunctionKey: xkey = 'A'; break;
+ case NSDownArrowFunctionKey: xkey = 'B'; break;
+ case NSRightArrowFunctionKey: xkey = 'C'; break;
+ case NSLeftArrowFunctionKey: xkey = 'D'; break;
+ }
+ if (xkey) {
+ end += format_arrow_key(output+end, term, xkey,
+ m & NSControlKeyMask);
+ goto done;
+ }
+ }
+
+ done:
+
+ /*
+ * Failing everything else, send the exact Unicode we got from
+ * OS X.
+ */
+ if (end == start) {
+ if (n > lenof(output)-start)
+ n = lenof(output)-start; /* _shouldn't_ happen! */
+ for (i = 0; i < n; i++) {
+ output[i+start] = [s characterAtIndex:i];
+ }
+ end = n+start;
+ }
+
+ if (use_coutput) {
+ assert(special);
+ assert(end < lenof(coutput));
+ coutput[end] = '\0';
+ ldisc_send(ldisc, coutput+start, -2, TRUE);
+ } else {
+ luni_send(ldisc, output+start, end-start, TRUE);
+ }
+}
+
+- (int)fromBackend:(const char *)data len:(int)len isStderr:(int)is_stderr
+{
+ return term_data(term, is_stderr, data, len);
+}
+
+- (int)fromBackendUntrusted:(const char *)data len:(int)len
+{
+ return term_data_untrusted(term, data, len);
+}
+
+- (void)startAlert:(NSAlert *)alert
+ withCallback:(void (*)(void *, int))callback andCtx:(void *)ctx
+{
+ if (alert_ctx || alert_qhead) {
+ /*
+ * Queue this alert to be shown later.
+ */
+ struct alert_queue *qitem = snew(struct alert_queue);
+ qitem->next = NULL;
+ qitem->alert = alert;
+ qitem->callback = callback;
+ qitem->ctx = ctx;
+ if (alert_qtail)
+ alert_qtail->next = qitem;
+ else
+ alert_qhead = qitem;
+ alert_qtail = qitem;
+ } else {
+ alert_callback = callback;
+ alert_ctx = ctx; /* NB this is assumed to need freeing! */
+ [alert beginSheetModalForWindow:self modalDelegate:self
+ didEndSelector:@selector(alertSheetDidEnd:returnCode:contextInfo:)
+ contextInfo:NULL];
+ }
+}
+
+- (void)alertSheetDidEnd:(NSAlert *)alert returnCode:(int)returnCode
+ contextInfo:(void *)contextInfo
+{
+ [self performSelectorOnMainThread:
+ @selector(alertSheetDidFinishEnding:)
+ withObject:[NSNumber numberWithInt:returnCode]
+ waitUntilDone:NO];
+}
+
+- (void)alertSheetDidFinishEnding:(id)object
+{
+ int returnCode = [object intValue];
+
+ alert_callback(alert_ctx, returnCode); /* transfers ownership of ctx */
+
+ /*
+ * If there's an alert in our queue (either already or because
+ * the callback just queued it), start it.
+ */
+ if (alert_qhead) {
+ struct alert_queue *qnext;
+
+ alert_callback = alert_qhead->callback;
+ alert_ctx = alert_qhead->ctx;
+ [alert_qhead->alert beginSheetModalForWindow:self modalDelegate:self
+ didEndSelector:@selector(alertSheetDidEnd:returnCode:contextInfo:)
+ contextInfo:NULL];
+
+ qnext = alert_qhead->next;
+ sfree(alert_qhead);
+ alert_qhead = qnext;
+ if (!qnext)
+ alert_qtail = NULL;
+ } else {
+ alert_ctx = NULL;
+ }
+}
+
+- (void)notifyRemoteExit
+{
+ int exitcode;
+
+ if (!exited && (exitcode = back->exitcode(backhandle)) >= 0)
+ [self endSession:(exitcode == 0)];
+}
+
+- (void)endSession:(int)clean
+{
+ exited = TRUE;
+ if (ldisc) {
+ ldisc_free(ldisc);
+ ldisc = NULL;
+ }
+ if (back) {
+ back->free(backhandle);
+ backhandle = NULL;
+ back = NULL;
+ //FIXME: update specials menu;
+ }
+ if (cfg.close_on_exit == FORCE_ON ||
+ (cfg.close_on_exit == AUTO && clean))
+ [self close];
+ // FIXME: else show restart menu item
+}
+
+- (Terminal *)term
+{
+ return term;
+}
+
+@end
+
+int from_backend(void *frontend, int is_stderr, const char *data, int len)
+{
+ SessionWindow *win = (SessionWindow *)frontend;
+ return [win fromBackend:data len:len isStderr:is_stderr];
+}
+
+int from_backend_untrusted(void *frontend, const char *data, int len)
+{
+ SessionWindow *win = (SessionWindow *)frontend;
+ return [win fromBackendUntrusted:data len:len];
+}
+
+int get_userpass_input(prompts_t *p, unsigned char *in, int inlen)
+{
+ SessionWindow *win = (SessionWindow *)p->frontend;
+ Terminal *term = [win term];
+ return term_get_userpass_input(term, p, in, inlen);
+}
+
+void frontend_keypress(void *handle)
+{
+ /* FIXME */
+}
+
+void notify_remote_exit(void *frontend)
+{
+ SessionWindow *win = (SessionWindow *)frontend;
+
+ [win notifyRemoteExit];
+}
+
+void ldisc_update(void *frontend, int echo, int edit)
+{
+ //SessionWindow *win = (SessionWindow *)frontend;
+ /*
+ * In a GUI front end, this need do nothing.
+ */
+}
+
+char *get_ttymode(void *frontend, const char *mode)
+{
+ SessionWindow *win = (SessionWindow *)frontend;
+ Terminal *term = [win term];
+ return term_get_ttymode(term, mode);
+}
+
+void update_specials_menu(void *frontend)
+{
+ //SessionWindow *win = (SessionWindow *)frontend;
+ /* FIXME */
+}
+
+/*
+ * This is still called when mode==BELL_VISUAL, even though the
+ * visual bell is handled entirely within terminal.c, because we
+ * may want to perform additional actions on any kind of bell (for
+ * example, taskbar flashing in Windows).
+ */
+void do_beep(void *frontend, int mode)
+{
+ //SessionWindow *win = (SessionWindow *)frontend;
+ if (mode != BELL_VISUAL)
+ NSBeep();
+}
+
+int char_width(Context ctx, int uc)
+{
+ /*
+ * Under X, any fixed-width font really _is_ fixed-width.
+ * Double-width characters will be dealt with using a separate
+ * font. For the moment we can simply return 1.
+ */
+ return 1;
+}
+
+void palette_set(void *frontend, int n, int r, int g, int b)
+{
+ SessionWindow *win = (SessionWindow *)frontend;
+
+ if (n >= 16)
+ n += 256 - 16;
+ 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?
+ */
+}
+
+void palette_reset(void *frontend)
+{
+ SessionWindow *win = (SessionWindow *)frontend;
+ Config *cfg = [win cfg];
+
+ /* This maps colour indices in cfg to those used in colours[]. */
+ 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
+ };
+
+ int i;
+
+ for (i = 0; i < NCFGCOLOURS; i++) {
+ [win setColour:ww[i] r:cfg->colours[i][0]/255.0
+ g:cfg->colours[i][1]/255.0 b:cfg->colours[i][2]/255.0];
+ }
+
+ for (i = 0; i < NEXTCOLOURS; i++) {
+ if (i < 216) {
+ int r = i / 36, g = (i / 6) % 6, b = i % 6;
+ r = r ? r*40+55 : 0; g = g ? b*40+55 : 0; b = b ? b*40+55 : 0;
+ [win setColour:i+16 r:r/255.0 g:g/255.0 b:b/255.0];
+ } else {
+ int shade = i - 216;
+ float fshade = (shade * 10 + 8) / 255.0;
+ [win setColour:i+16 r:fshade g:fshade b:fshade];
+ }
+ }
+
+ /*
+ * FIXME: do we need an OS X equivalent of set_window_background?
+ */
+}
+
+Context get_ctx(void *frontend)
+{
+ SessionWindow *win = (SessionWindow *)frontend;
+
+ /*
+ * Lock the drawing focus on the image inside the TerminalView.
+ */
+ [win drawStartFinish:YES];
+
+ [[NSGraphicsContext currentContext] setShouldAntialias:YES];
+
+ /*
+ * Cocoa drawing functions don't take a graphics context: that
+ * parameter is implicit. Therefore, we'll use the frontend
+ * handle itself as the context, on the grounds that it's as
+ * good a thing to use as any.
+ */
+ return frontend;
+}
+
+void free_ctx(Context ctx)
+{
+ SessionWindow *win = (SessionWindow *)ctx;
+
+ [win drawStartFinish:NO];
+}
+
+void do_text(Context ctx, int x, int y, wchar_t *text, int len,
+ unsigned long attr, int lattr)
+{
+ SessionWindow *win = (SessionWindow *)ctx;
+
+ [win doText:text len:len x:x y:y attr:attr lattr:lattr];
+}
+
+void do_cursor(Context ctx, int x, int y, wchar_t *text, int len,
+ unsigned long attr, int lattr)
+{
+ SessionWindow *win = (SessionWindow *)ctx;
+ Config *cfg = [win cfg];
+ int active, passive;
+
+ if (attr & TATTR_PASCURS) {
+ attr &= ~TATTR_PASCURS;
+ passive = 1;
+ } else
+ passive = 0;
+ if ((attr & TATTR_ACTCURS) && cfg->cursor_type != 0) {
+ attr &= ~TATTR_ACTCURS;
+ active = 1;
+ } else
+ active = 0;
+
+ [win doText:text len:len x:x y:y attr:attr lattr:lattr];
+
+ /*
+ * FIXME: now draw the various cursor types (both passive and
+ * active underlines and vertical lines, plus passive blocks).
+ */
+}
+
+/*
+ * Minimise or restore the window in response to a server-side
+ * request.
+ */
+void set_iconic(void *frontend, int iconic)
+{
+ //SessionWindow *win = (SessionWindow *)frontend;
+ /* FIXME */
+}
+
+/*
+ * Move the window in response to a server-side request.
+ */
+void move_window(void *frontend, int x, int y)
+{
+ //SessionWindow *win = (SessionWindow *)frontend;
+ /* FIXME */
+}
+
+/*
+ * 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)
+{
+ //SessionWindow *win = (SessionWindow *)frontend;
+ /* FIXME */
+}
+
+/*
+ * Refresh the window in response to a server-side request.
+ */
+void refresh_window(void *frontend)
+{
+ //SessionWindow *win = (SessionWindow *)frontend;
+ /* FIXME */
+}
+
+/*
+ * Maximise or restore the window in response to a server-side
+ * request.
+ */
+void set_zoomed(void *frontend, int zoomed)
+{
+ //SessionWindow *win = (SessionWindow *)frontend;
+ /* FIXME */
+}
+
+/*
+ * Report whether the window is iconic, for terminal reports.
+ */
+int is_iconic(void *frontend)
+{
+ //SessionWindow *win = (SessionWindow *)frontend;
+ return NO; /* FIXME */
+}
+
+/*
+ * Report the window's position, for terminal reports.
+ */
+void get_window_pos(void *frontend, int *x, int *y)
+{
+ //SessionWindow *win = (SessionWindow *)frontend;
+ /* FIXME */
+}
+
+/*
+ * Report the window's pixel size, for terminal reports.
+ */
+void get_window_pixels(void *frontend, int *x, int *y)
+{
+ //SessionWindow *win = (SessionWindow *)frontend;
+ /* FIXME */
+}
+
+/*
+ * Return the window or icon title.
+ */
+char *get_window_title(void *frontend, int icon)
+{
+ //SessionWindow *win = (SessionWindow *)frontend;
+ return NULL; /* FIXME */
+}
+
+void set_title(void *frontend, char *title)
+{
+ //SessionWindow *win = (SessionWindow *)frontend;
+ /* FIXME */
+}
+
+void set_icon(void *frontend, char *title)
+{
+ //SessionWindow *win = (SessionWindow *)frontend;
+ /* FIXME */
+}
+
+void set_sbar(void *frontend, int total, int start, int page)
+{
+ //SessionWindow *win = (SessionWindow *)frontend;
+ /* FIXME */
+}
+
+void get_clip(void *frontend, wchar_t ** p, int *len)
+{
+ //SessionWindow *win = (SessionWindow *)frontend;
+ /* FIXME */
+}
+
+void write_clip(void *frontend, wchar_t *data, int *attr, int len, int must_deselect)
+{
+ //SessionWindow *win = (SessionWindow *)frontend;
+ /* FIXME */
+}
+
+void request_paste(void *frontend)
+{
+ //SessionWindow *win = (SessionWindow *)frontend;
+ /* FIXME */
+}
+
+void set_raw_mouse_mode(void *frontend, int activate)
+{
+ //SessionWindow *win = (SessionWindow *)frontend;
+ /* FIXME */
+}
+
+void request_resize(void *frontend, int w, int h)
+{
+ //SessionWindow *win = (SessionWindow *)frontend;
+ /* FIXME */
+}
+
+void sys_cursor(void *frontend, int x, int y)
+{
+ //SessionWindow *win = (SessionWindow *)frontend;
+ /*
+ * This is probably meaningless under OS X. FIXME: find out for
+ * sure.
+ */
+}
+
+void logevent(void *frontend, const char *string)
+{
+ //SessionWindow *win = (SessionWindow *)frontend;
+ /* FIXME */
+printf("logevent: %s\n", string);
+}
+
+int font_dimension(void *frontend, int which)/* 0 for width, 1 for height */
+{
+ //SessionWindow *win = (SessionWindow *)frontend;
+ return 1; /* FIXME */
+}
+
+void set_busy_status(void *frontend, int status)
+{
+ /*
+ * We need do nothing here: the OS X `application is busy'
+ * beachball pointer appears _automatically_ when the
+ * application isn't responding to GUI messages.
+ */
+}
--- /dev/null
+/************************************************************************
+ * $Id$
+ *
+ * ------------
+ * Description:
+ * ------------
+ * This is an implemention of Unicode's Bidirectional Algorithm
+ * (known as UAX #9).
+ *
+ * http://www.unicode.org/reports/tr9/
+ *
+ * Author: Ahmad Khalifa
+ *
+ * -----------------
+ * Revision Details: (Updated by Revision Control System)
+ * -----------------
+ * $Date$
+ * $Author$
+ * $Revision$
+ *
+ * (www.arabeyes.org - under MIT license)
+ *
+ ************************************************************************/
+
+/*
+ * TODO:
+ * =====
+ * - Explicit marks need to be handled (they are not 100% now)
+ * - Ligatures
+ */
+
+#include <stdlib.h> /* definition of wchar_t*/
+
+#include "misc.h"
+
+#define LMASK 0x3F /* Embedding Level mask */
+#define OMASK 0xC0 /* Override mask */
+#define OISL 0x80 /* Override is L */
+#define OISR 0x40 /* Override is R */
+
+/* For standalone compilation in a testing mode.
+ * Still depends on the PuTTY headers for snewn and sfree, but can avoid
+ * _linking_ with any other PuTTY code. */
+#ifdef TEST_GETTYPE
+#define safemalloc malloc
+#define safefree free
+#endif
+
+/* Shaping Helpers */
+#define STYPE(xh) ((((xh) >= SHAPE_FIRST) && ((xh) <= SHAPE_LAST)) ? \
+shapetypes[(xh)-SHAPE_FIRST].type : SU) /*))*/
+#define SISOLATED(xh) (shapetypes[(xh)-SHAPE_FIRST].form_b)
+#define SFINAL(xh) ((xh)+1)
+#define SINITIAL(xh) ((xh)+2)
+#define SMEDIAL(ch) ((ch)+3)
+
+#define leastGreaterOdd(x) ( ((x)+1) | 1 )
+#define leastGreaterEven(x) ( ((x)+2) &~ 1 )
+
+typedef struct bidi_char {
+ wchar_t origwc, wc;
+ unsigned short index;
+} bidi_char;
+
+/* function declarations */
+void flipThisRun(bidi_char *from, unsigned char* level, int max, int count);
+int findIndexOfRun(unsigned char* level , int start, int count, int tlevel);
+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);
+
+/* character types */
+enum {
+ L,
+ LRE,
+ LRO,
+ R,
+ AL,
+ RLE,
+ RLO,
+ PDF,
+ EN,
+ ES,
+ ET,
+ AN,
+ CS,
+ NSM,
+ BN,
+ B,
+ S,
+ WS,
+ ON
+};
+
+/* Shaping Types */
+enum {
+ SL, /* Left-Joining, doesnt exist in U+0600 - U+06FF */
+ SR, /* Right-Joining, ie has Isolated, Final */
+ SD, /* Dual-Joining, ie has Isolated, Final, Initial, Medial */
+ SU, /* Non-Joining */
+ SC /* Join-Causing, like U+0640 (TATWEEL) */
+};
+
+typedef struct {
+ char type;
+ wchar_t form_b;
+} shape_node;
+
+/* Kept near the actual table, for verification. */
+#define SHAPE_FIRST 0x621
+#define SHAPE_LAST (SHAPE_FIRST + lenof(shapetypes) - 1)
+
+const shape_node shapetypes[] = {
+ /* index, Typ, Iso, Ligature Index*/
+ /* 621 */ {SU, 0xFE80},
+ /* 622 */ {SR, 0xFE81},
+ /* 623 */ {SR, 0xFE83},
+ /* 624 */ {SR, 0xFE85},
+ /* 625 */ {SR, 0xFE87},
+ /* 626 */ {SD, 0xFE89},
+ /* 627 */ {SR, 0xFE8D},
+ /* 628 */ {SD, 0xFE8F},
+ /* 629 */ {SR, 0xFE93},
+ /* 62A */ {SD, 0xFE95},
+ /* 62B */ {SD, 0xFE99},
+ /* 62C */ {SD, 0xFE9D},
+ /* 62D */ {SD, 0xFEA1},
+ /* 62E */ {SD, 0xFEA5},
+ /* 62F */ {SR, 0xFEA9},
+ /* 630 */ {SR, 0xFEAB},
+ /* 631 */ {SR, 0xFEAD},
+ /* 632 */ {SR, 0xFEAF},
+ /* 633 */ {SD, 0xFEB1},
+ /* 634 */ {SD, 0xFEB5},
+ /* 635 */ {SD, 0xFEB9},
+ /* 636 */ {SD, 0xFEBD},
+ /* 637 */ {SD, 0xFEC1},
+ /* 638 */ {SD, 0xFEC5},
+ /* 639 */ {SD, 0xFEC9},
+ /* 63A */ {SD, 0xFECD},
+ /* 63B */ {SU, 0x0},
+ /* 63C */ {SU, 0x0},
+ /* 63D */ {SU, 0x0},
+ /* 63E */ {SU, 0x0},
+ /* 63F */ {SU, 0x0},
+ /* 640 */ {SC, 0x0},
+ /* 641 */ {SD, 0xFED1},
+ /* 642 */ {SD, 0xFED5},
+ /* 643 */ {SD, 0xFED9},
+ /* 644 */ {SD, 0xFEDD},
+ /* 645 */ {SD, 0xFEE1},
+ /* 646 */ {SD, 0xFEE5},
+ /* 647 */ {SD, 0xFEE9},
+ /* 648 */ {SR, 0xFEED},
+ /* 649 */ {SR, 0xFEEF}, /* SD */
+ /* 64A */ {SD, 0xFEF1},
+ /* 64B */ {SU, 0x0},
+ /* 64C */ {SU, 0x0},
+ /* 64D */ {SU, 0x0},
+ /* 64E */ {SU, 0x0},
+ /* 64F */ {SU, 0x0},
+ /* 650 */ {SU, 0x0},
+ /* 651 */ {SU, 0x0},
+ /* 652 */ {SU, 0x0},
+ /* 653 */ {SU, 0x0},
+ /* 654 */ {SU, 0x0},
+ /* 655 */ {SU, 0x0},
+ /* 656 */ {SU, 0x0},
+ /* 657 */ {SU, 0x0},
+ /* 658 */ {SU, 0x0},
+ /* 659 */ {SU, 0x0},
+ /* 65A */ {SU, 0x0},
+ /* 65B */ {SU, 0x0},
+ /* 65C */ {SU, 0x0},
+ /* 65D */ {SU, 0x0},
+ /* 65E */ {SU, 0x0},
+ /* 65F */ {SU, 0x0},
+ /* 660 */ {SU, 0x0},
+ /* 661 */ {SU, 0x0},
+ /* 662 */ {SU, 0x0},
+ /* 663 */ {SU, 0x0},
+ /* 664 */ {SU, 0x0},
+ /* 665 */ {SU, 0x0},
+ /* 666 */ {SU, 0x0},
+ /* 667 */ {SU, 0x0},
+ /* 668 */ {SU, 0x0},
+ /* 669 */ {SU, 0x0},
+ /* 66A */ {SU, 0x0},
+ /* 66B */ {SU, 0x0},
+ /* 66C */ {SU, 0x0},
+ /* 66D */ {SU, 0x0},
+ /* 66E */ {SU, 0x0},
+ /* 66F */ {SU, 0x0},
+ /* 670 */ {SU, 0x0},
+ /* 671 */ {SR, 0xFB50},
+ /* 672 */ {SU, 0x0},
+ /* 673 */ {SU, 0x0},
+ /* 674 */ {SU, 0x0},
+ /* 675 */ {SU, 0x0},
+ /* 676 */ {SU, 0x0},
+ /* 677 */ {SU, 0x0},
+ /* 678 */ {SU, 0x0},
+ /* 679 */ {SD, 0xFB66},
+ /* 67A */ {SD, 0xFB5E},
+ /* 67B */ {SD, 0xFB52},
+ /* 67C */ {SU, 0x0},
+ /* 67D */ {SU, 0x0},
+ /* 67E */ {SD, 0xFB56},
+ /* 67F */ {SD, 0xFB62},
+ /* 680 */ {SD, 0xFB5A},
+ /* 681 */ {SU, 0x0},
+ /* 682 */ {SU, 0x0},
+ /* 683 */ {SD, 0xFB76},
+ /* 684 */ {SD, 0xFB72},
+ /* 685 */ {SU, 0x0},
+ /* 686 */ {SD, 0xFB7A},
+ /* 687 */ {SD, 0xFB7E},
+ /* 688 */ {SR, 0xFB88},
+ /* 689 */ {SU, 0x0},
+ /* 68A */ {SU, 0x0},
+ /* 68B */ {SU, 0x0},
+ /* 68C */ {SR, 0xFB84},
+ /* 68D */ {SR, 0xFB82},
+ /* 68E */ {SR, 0xFB86},
+ /* 68F */ {SU, 0x0},
+ /* 690 */ {SU, 0x0},
+ /* 691 */ {SR, 0xFB8C},
+ /* 692 */ {SU, 0x0},
+ /* 693 */ {SU, 0x0},
+ /* 694 */ {SU, 0x0},
+ /* 695 */ {SU, 0x0},
+ /* 696 */ {SU, 0x0},
+ /* 697 */ {SU, 0x0},
+ /* 698 */ {SR, 0xFB8A},
+ /* 699 */ {SU, 0x0},
+ /* 69A */ {SU, 0x0},
+ /* 69B */ {SU, 0x0},
+ /* 69C */ {SU, 0x0},
+ /* 69D */ {SU, 0x0},
+ /* 69E */ {SU, 0x0},
+ /* 69F */ {SU, 0x0},
+ /* 6A0 */ {SU, 0x0},
+ /* 6A1 */ {SU, 0x0},
+ /* 6A2 */ {SU, 0x0},
+ /* 6A3 */ {SU, 0x0},
+ /* 6A4 */ {SD, 0xFB6A},
+ /* 6A5 */ {SU, 0x0},
+ /* 6A6 */ {SD, 0xFB6E},
+ /* 6A7 */ {SU, 0x0},
+ /* 6A8 */ {SU, 0x0},
+ /* 6A9 */ {SD, 0xFB8E},
+ /* 6AA */ {SU, 0x0},
+ /* 6AB */ {SU, 0x0},
+ /* 6AC */ {SU, 0x0},
+ /* 6AD */ {SD, 0xFBD3},
+ /* 6AE */ {SU, 0x0},
+ /* 6AF */ {SD, 0xFB92},
+ /* 6B0 */ {SU, 0x0},
+ /* 6B1 */ {SD, 0xFB9A},
+ /* 6B2 */ {SU, 0x0},
+ /* 6B3 */ {SD, 0xFB96},
+ /* 6B4 */ {SU, 0x0},
+ /* 6B5 */ {SU, 0x0},
+ /* 6B6 */ {SU, 0x0},
+ /* 6B7 */ {SU, 0x0},
+ /* 6B8 */ {SU, 0x0},
+ /* 6B9 */ {SU, 0x0},
+ /* 6BA */ {SR, 0xFB9E},
+ /* 6BB */ {SD, 0xFBA0},
+ /* 6BC */ {SU, 0x0},
+ /* 6BD */ {SU, 0x0},
+ /* 6BE */ {SD, 0xFBAA},
+ /* 6BF */ {SU, 0x0},
+ /* 6C0 */ {SR, 0xFBA4},
+ /* 6C1 */ {SD, 0xFBA6},
+ /* 6C2 */ {SU, 0x0},
+ /* 6C3 */ {SU, 0x0},
+ /* 6C4 */ {SU, 0x0},
+ /* 6C5 */ {SR, 0xFBE0},
+ /* 6C6 */ {SR, 0xFBD9},
+ /* 6C7 */ {SR, 0xFBD7},
+ /* 6C8 */ {SR, 0xFBDB},
+ /* 6C9 */ {SR, 0xFBE2},
+ /* 6CA */ {SU, 0x0},
+ /* 6CB */ {SR, 0xFBDE},
+ /* 6CC */ {SD, 0xFBFC},
+ /* 6CD */ {SU, 0x0},
+ /* 6CE */ {SU, 0x0},
+ /* 6CF */ {SU, 0x0},
+ /* 6D0 */ {SU, 0x0},
+ /* 6D1 */ {SU, 0x0},
+ /* 6D2 */ {SR, 0xFBAE},
+};
+
+/*
+ * Flips the text buffer, according to max level, and
+ * all higher levels
+ *
+ * Input:
+ * from: text buffer, on which to apply flipping
+ * level: resolved levels buffer
+ * max: the maximum level found in this line (should be unsigned char)
+ * count: line size in bidi_char
+ */
+void flipThisRun(bidi_char *from, unsigned char *level, int max, int count)
+{
+ int i, j, k, tlevel;
+ bidi_char temp;
+
+ j = i = 0;
+ while (i<count && j<count) {
+
+ /* find the start of the run of level=max */
+ tlevel = max;
+ i = j = findIndexOfRun(level, i, count, max);
+ /* find the end of the run */
+ while (i<count && tlevel <= level[i]) {
+ i++;
+ }
+ for (k = i - 1; k > j; k--, j++) {
+ temp = from[k];
+ from[k] = from[j];
+ from[j] = temp;
+ }
+ }
+}
+
+/*
+ * Finds the index of a run with level equals tlevel
+ */
+int findIndexOfRun(unsigned char* level , int start, int count, int tlevel)
+{
+ int i;
+ for (i=start; i<count; i++) {
+ if (tlevel == level[i]) {
+ return i;
+ }
+ }
+ return count;
+}
+
+/*
+ * Returns the bidi character type of ch.
+ *
+ * The data table in this function is constructed from the Unicode
+ * Character Database, downloadable from unicode.org at the URL
+ *
+ * http://www.unicode.org/Public/UNIDATA/UnicodeData.txt
+ *
+ * by the following fragment of Perl:
+
+perl -ne 'split ";"; $num = hex $_[0]; $type = $_[4];' \
+ -e '$fl = ($_[1] =~ /First/ ? 1 : $_[1] =~ /Last/ ? 2 : 0);' \
+ -e 'if ($type eq $runtype and ($runend == $num-1 or ' \
+ -e ' ($fl==2 and $pfl==1))) {$runend = $num;} else { &reset; }' \
+ -e '$pfl=$fl; END { &reset }; sub reset {' \
+ -e 'printf" {0x%04x, 0x%04x, %s},\n",$runstart,$runend,$runtype' \
+ -e ' if defined $runstart and $runtype ne "ON";' \
+ -e '$runstart=$runend=$num; $runtype=$type;}' \
+ UnicodeData.txt
+
+ */
+unsigned char getType(int ch)
+{
+ static const struct {
+ int first, last, type;
+ } lookup[] = {
+ {0x0000, 0x0008, BN},
+ {0x0009, 0x0009, S},
+ {0x000a, 0x000a, B},
+ {0x000b, 0x000b, S},
+ {0x000c, 0x000c, WS},
+ {0x000d, 0x000d, B},
+ {0x000e, 0x001b, BN},
+ {0x001c, 0x001e, B},
+ {0x001f, 0x001f, S},
+ {0x0020, 0x0020, WS},
+ {0x0023, 0x0025, ET},
+ {0x002b, 0x002b, ES},
+ {0x002c, 0x002c, CS},
+ {0x002d, 0x002d, ES},
+ {0x002e, 0x002f, CS},
+ {0x0030, 0x0039, EN},
+ {0x003a, 0x003a, CS},
+ {0x0041, 0x005a, L},
+ {0x0061, 0x007a, L},
+ {0x007f, 0x0084, BN},
+ {0x0085, 0x0085, B},
+ {0x0086, 0x009f, BN},
+ {0x00a0, 0x00a0, CS},
+ {0x00a2, 0x00a5, ET},
+ {0x00aa, 0x00aa, L},
+ {0x00ad, 0x00ad, BN},
+ {0x00b0, 0x00b1, ET},
+ {0x00b2, 0x00b3, EN},
+ {0x00b5, 0x00b5, L},
+ {0x00b9, 0x00b9, EN},
+ {0x00ba, 0x00ba, L},
+ {0x00c0, 0x00d6, L},
+ {0x00d8, 0x00f6, L},
+ {0x00f8, 0x0236, L},
+ {0x0250, 0x02b8, L},
+ {0x02bb, 0x02c1, L},
+ {0x02d0, 0x02d1, L},
+ {0x02e0, 0x02e4, L},
+ {0x02ee, 0x02ee, L},
+ {0x0300, 0x0357, NSM},
+ {0x035d, 0x036f, NSM},
+ {0x037a, 0x037a, L},
+ {0x0386, 0x0386, L},
+ {0x0388, 0x038a, L},
+ {0x038c, 0x038c, L},
+ {0x038e, 0x03a1, L},
+ {0x03a3, 0x03ce, L},
+ {0x03d0, 0x03f5, L},
+ {0x03f7, 0x03fb, L},
+ {0x0400, 0x0482, L},
+ {0x0483, 0x0486, NSM},
+ {0x0488, 0x0489, NSM},
+ {0x048a, 0x04ce, L},
+ {0x04d0, 0x04f5, L},
+ {0x04f8, 0x04f9, L},
+ {0x0500, 0x050f, L},
+ {0x0531, 0x0556, L},
+ {0x0559, 0x055f, L},
+ {0x0561, 0x0587, L},
+ {0x0589, 0x0589, L},
+ {0x0591, 0x05a1, NSM},
+ {0x05a3, 0x05b9, NSM},
+ {0x05bb, 0x05bd, NSM},
+ {0x05be, 0x05be, R},
+ {0x05bf, 0x05bf, NSM},
+ {0x05c0, 0x05c0, R},
+ {0x05c1, 0x05c2, NSM},
+ {0x05c3, 0x05c3, R},
+ {0x05c4, 0x05c4, NSM},
+ {0x05d0, 0x05ea, R},
+ {0x05f0, 0x05f4, R},
+ {0x0600, 0x0603, AL},
+ {0x060c, 0x060c, CS},
+ {0x060d, 0x060d, AL},
+ {0x0610, 0x0615, NSM},
+ {0x061b, 0x061b, AL},
+ {0x061f, 0x061f, AL},
+ {0x0621, 0x063a, AL},
+ {0x0640, 0x064a, AL},
+ {0x064b, 0x0658, NSM},
+ {0x0660, 0x0669, AN},
+ {0x066a, 0x066a, ET},
+ {0x066b, 0x066c, AN},
+ {0x066d, 0x066f, AL},
+ {0x0670, 0x0670, NSM},
+ {0x0671, 0x06d5, AL},
+ {0x06d6, 0x06dc, NSM},
+ {0x06dd, 0x06dd, AL},
+ {0x06de, 0x06e4, NSM},
+ {0x06e5, 0x06e6, AL},
+ {0x06e7, 0x06e8, NSM},
+ {0x06ea, 0x06ed, NSM},
+ {0x06ee, 0x06ef, AL},
+ {0x06f0, 0x06f9, EN},
+ {0x06fa, 0x070d, AL},
+ {0x070f, 0x070f, BN},
+ {0x0710, 0x0710, AL},
+ {0x0711, 0x0711, NSM},
+ {0x0712, 0x072f, AL},
+ {0x0730, 0x074a, NSM},
+ {0x074d, 0x074f, AL},
+ {0x0780, 0x07a5, AL},
+ {0x07a6, 0x07b0, NSM},
+ {0x07b1, 0x07b1, AL},
+ {0x0901, 0x0902, NSM},
+ {0x0903, 0x0939, L},
+ {0x093c, 0x093c, NSM},
+ {0x093d, 0x0940, L},
+ {0x0941, 0x0948, NSM},
+ {0x0949, 0x094c, L},
+ {0x094d, 0x094d, NSM},
+ {0x0950, 0x0950, L},
+ {0x0951, 0x0954, NSM},
+ {0x0958, 0x0961, L},
+ {0x0962, 0x0963, NSM},
+ {0x0964, 0x0970, L},
+ {0x0981, 0x0981, NSM},
+ {0x0982, 0x0983, L},
+ {0x0985, 0x098c, L},
+ {0x098f, 0x0990, L},
+ {0x0993, 0x09a8, L},
+ {0x09aa, 0x09b0, L},
+ {0x09b2, 0x09b2, L},
+ {0x09b6, 0x09b9, L},
+ {0x09bc, 0x09bc, NSM},
+ {0x09bd, 0x09c0, L},
+ {0x09c1, 0x09c4, NSM},
+ {0x09c7, 0x09c8, L},
+ {0x09cb, 0x09cc, L},
+ {0x09cd, 0x09cd, NSM},
+ {0x09d7, 0x09d7, L},
+ {0x09dc, 0x09dd, L},
+ {0x09df, 0x09e1, L},
+ {0x09e2, 0x09e3, NSM},
+ {0x09e6, 0x09f1, L},
+ {0x09f2, 0x09f3, ET},
+ {0x09f4, 0x09fa, L},
+ {0x0a01, 0x0a02, NSM},
+ {0x0a03, 0x0a03, L},
+ {0x0a05, 0x0a0a, L},
+ {0x0a0f, 0x0a10, L},
+ {0x0a13, 0x0a28, L},
+ {0x0a2a, 0x0a30, L},
+ {0x0a32, 0x0a33, L},
+ {0x0a35, 0x0a36, L},
+ {0x0a38, 0x0a39, L},
+ {0x0a3c, 0x0a3c, NSM},
+ {0x0a3e, 0x0a40, L},
+ {0x0a41, 0x0a42, NSM},
+ {0x0a47, 0x0a48, NSM},
+ {0x0a4b, 0x0a4d, NSM},
+ {0x0a59, 0x0a5c, L},
+ {0x0a5e, 0x0a5e, L},
+ {0x0a66, 0x0a6f, L},
+ {0x0a70, 0x0a71, NSM},
+ {0x0a72, 0x0a74, L},
+ {0x0a81, 0x0a82, NSM},
+ {0x0a83, 0x0a83, L},
+ {0x0a85, 0x0a8d, L},
+ {0x0a8f, 0x0a91, L},
+ {0x0a93, 0x0aa8, L},
+ {0x0aaa, 0x0ab0, L},
+ {0x0ab2, 0x0ab3, L},
+ {0x0ab5, 0x0ab9, L},
+ {0x0abc, 0x0abc, NSM},
+ {0x0abd, 0x0ac0, L},
+ {0x0ac1, 0x0ac5, NSM},
+ {0x0ac7, 0x0ac8, NSM},
+ {0x0ac9, 0x0ac9, L},
+ {0x0acb, 0x0acc, L},
+ {0x0acd, 0x0acd, NSM},
+ {0x0ad0, 0x0ad0, L},
+ {0x0ae0, 0x0ae1, L},
+ {0x0ae2, 0x0ae3, NSM},
+ {0x0ae6, 0x0aef, L},
+ {0x0af1, 0x0af1, ET},
+ {0x0b01, 0x0b01, NSM},
+ {0x0b02, 0x0b03, L},
+ {0x0b05, 0x0b0c, L},
+ {0x0b0f, 0x0b10, L},
+ {0x0b13, 0x0b28, L},
+ {0x0b2a, 0x0b30, L},
+ {0x0b32, 0x0b33, L},
+ {0x0b35, 0x0b39, L},
+ {0x0b3c, 0x0b3c, NSM},
+ {0x0b3d, 0x0b3e, L},
+ {0x0b3f, 0x0b3f, NSM},
+ {0x0b40, 0x0b40, L},
+ {0x0b41, 0x0b43, NSM},
+ {0x0b47, 0x0b48, L},
+ {0x0b4b, 0x0b4c, L},
+ {0x0b4d, 0x0b4d, NSM},
+ {0x0b56, 0x0b56, NSM},
+ {0x0b57, 0x0b57, L},
+ {0x0b5c, 0x0b5d, L},
+ {0x0b5f, 0x0b61, L},
+ {0x0b66, 0x0b71, L},
+ {0x0b82, 0x0b82, NSM},
+ {0x0b83, 0x0b83, L},
+ {0x0b85, 0x0b8a, L},
+ {0x0b8e, 0x0b90, L},
+ {0x0b92, 0x0b95, L},
+ {0x0b99, 0x0b9a, L},
+ {0x0b9c, 0x0b9c, L},
+ {0x0b9e, 0x0b9f, L},
+ {0x0ba3, 0x0ba4, L},
+ {0x0ba8, 0x0baa, L},
+ {0x0bae, 0x0bb5, L},
+ {0x0bb7, 0x0bb9, L},
+ {0x0bbe, 0x0bbf, L},
+ {0x0bc0, 0x0bc0, NSM},
+ {0x0bc1, 0x0bc2, L},
+ {0x0bc6, 0x0bc8, L},
+ {0x0bca, 0x0bcc, L},
+ {0x0bcd, 0x0bcd, NSM},
+ {0x0bd7, 0x0bd7, L},
+ {0x0be7, 0x0bf2, L},
+ {0x0bf9, 0x0bf9, ET},
+ {0x0c01, 0x0c03, L},
+ {0x0c05, 0x0c0c, L},
+ {0x0c0e, 0x0c10, L},
+ {0x0c12, 0x0c28, L},
+ {0x0c2a, 0x0c33, L},
+ {0x0c35, 0x0c39, L},
+ {0x0c3e, 0x0c40, NSM},
+ {0x0c41, 0x0c44, L},
+ {0x0c46, 0x0c48, NSM},
+ {0x0c4a, 0x0c4d, NSM},
+ {0x0c55, 0x0c56, NSM},
+ {0x0c60, 0x0c61, L},
+ {0x0c66, 0x0c6f, L},
+ {0x0c82, 0x0c83, L},
+ {0x0c85, 0x0c8c, L},
+ {0x0c8e, 0x0c90, L},
+ {0x0c92, 0x0ca8, L},
+ {0x0caa, 0x0cb3, L},
+ {0x0cb5, 0x0cb9, L},
+ {0x0cbc, 0x0cbc, NSM},
+ {0x0cbd, 0x0cc4, L},
+ {0x0cc6, 0x0cc8, L},
+ {0x0cca, 0x0ccb, L},
+ {0x0ccc, 0x0ccd, NSM},
+ {0x0cd5, 0x0cd6, L},
+ {0x0cde, 0x0cde, L},
+ {0x0ce0, 0x0ce1, L},
+ {0x0ce6, 0x0cef, L},
+ {0x0d02, 0x0d03, L},
+ {0x0d05, 0x0d0c, L},
+ {0x0d0e, 0x0d10, L},
+ {0x0d12, 0x0d28, L},
+ {0x0d2a, 0x0d39, L},
+ {0x0d3e, 0x0d40, L},
+ {0x0d41, 0x0d43, NSM},
+ {0x0d46, 0x0d48, L},
+ {0x0d4a, 0x0d4c, L},
+ {0x0d4d, 0x0d4d, NSM},
+ {0x0d57, 0x0d57, L},
+ {0x0d60, 0x0d61, L},
+ {0x0d66, 0x0d6f, L},
+ {0x0d82, 0x0d83, L},
+ {0x0d85, 0x0d96, L},
+ {0x0d9a, 0x0db1, L},
+ {0x0db3, 0x0dbb, L},
+ {0x0dbd, 0x0dbd, L},
+ {0x0dc0, 0x0dc6, L},
+ {0x0dca, 0x0dca, NSM},
+ {0x0dcf, 0x0dd1, L},
+ {0x0dd2, 0x0dd4, NSM},
+ {0x0dd6, 0x0dd6, NSM},
+ {0x0dd8, 0x0ddf, L},
+ {0x0df2, 0x0df4, L},
+ {0x0e01, 0x0e30, L},
+ {0x0e31, 0x0e31, NSM},
+ {0x0e32, 0x0e33, L},
+ {0x0e34, 0x0e3a, NSM},
+ {0x0e3f, 0x0e3f, ET},
+ {0x0e40, 0x0e46, L},
+ {0x0e47, 0x0e4e, NSM},
+ {0x0e4f, 0x0e5b, L},
+ {0x0e81, 0x0e82, L},
+ {0x0e84, 0x0e84, L},
+ {0x0e87, 0x0e88, L},
+ {0x0e8a, 0x0e8a, L},
+ {0x0e8d, 0x0e8d, L},
+ {0x0e94, 0x0e97, L},
+ {0x0e99, 0x0e9f, L},
+ {0x0ea1, 0x0ea3, L},
+ {0x0ea5, 0x0ea5, L},
+ {0x0ea7, 0x0ea7, L},
+ {0x0eaa, 0x0eab, L},
+ {0x0ead, 0x0eb0, L},
+ {0x0eb1, 0x0eb1, NSM},
+ {0x0eb2, 0x0eb3, L},
+ {0x0eb4, 0x0eb9, NSM},
+ {0x0ebb, 0x0ebc, NSM},
+ {0x0ebd, 0x0ebd, L},
+ {0x0ec0, 0x0ec4, L},
+ {0x0ec6, 0x0ec6, L},
+ {0x0ec8, 0x0ecd, NSM},
+ {0x0ed0, 0x0ed9, L},
+ {0x0edc, 0x0edd, L},
+ {0x0f00, 0x0f17, L},
+ {0x0f18, 0x0f19, NSM},
+ {0x0f1a, 0x0f34, L},
+ {0x0f35, 0x0f35, NSM},
+ {0x0f36, 0x0f36, L},
+ {0x0f37, 0x0f37, NSM},
+ {0x0f38, 0x0f38, L},
+ {0x0f39, 0x0f39, NSM},
+ {0x0f3e, 0x0f47, L},
+ {0x0f49, 0x0f6a, L},
+ {0x0f71, 0x0f7e, NSM},
+ {0x0f7f, 0x0f7f, L},
+ {0x0f80, 0x0f84, NSM},
+ {0x0f85, 0x0f85, L},
+ {0x0f86, 0x0f87, NSM},
+ {0x0f88, 0x0f8b, L},
+ {0x0f90, 0x0f97, NSM},
+ {0x0f99, 0x0fbc, NSM},
+ {0x0fbe, 0x0fc5, L},
+ {0x0fc6, 0x0fc6, NSM},
+ {0x0fc7, 0x0fcc, L},
+ {0x0fcf, 0x0fcf, L},
+ {0x1000, 0x1021, L},
+ {0x1023, 0x1027, L},
+ {0x1029, 0x102a, L},
+ {0x102c, 0x102c, L},
+ {0x102d, 0x1030, NSM},
+ {0x1031, 0x1031, L},
+ {0x1032, 0x1032, NSM},
+ {0x1036, 0x1037, NSM},
+ {0x1038, 0x1038, L},
+ {0x1039, 0x1039, NSM},
+ {0x1040, 0x1057, L},
+ {0x1058, 0x1059, NSM},
+ {0x10a0, 0x10c5, L},
+ {0x10d0, 0x10f8, L},
+ {0x10fb, 0x10fb, L},
+ {0x1100, 0x1159, L},
+ {0x115f, 0x11a2, L},
+ {0x11a8, 0x11f9, L},
+ {0x1200, 0x1206, L},
+ {0x1208, 0x1246, L},
+ {0x1248, 0x1248, L},
+ {0x124a, 0x124d, L},
+ {0x1250, 0x1256, L},
+ {0x1258, 0x1258, L},
+ {0x125a, 0x125d, L},
+ {0x1260, 0x1286, L},
+ {0x1288, 0x1288, L},
+ {0x128a, 0x128d, L},
+ {0x1290, 0x12ae, L},
+ {0x12b0, 0x12b0, L},
+ {0x12b2, 0x12b5, L},
+ {0x12b8, 0x12be, L},
+ {0x12c0, 0x12c0, L},
+ {0x12c2, 0x12c5, L},
+ {0x12c8, 0x12ce, L},
+ {0x12d0, 0x12d6, L},
+ {0x12d8, 0x12ee, L},
+ {0x12f0, 0x130e, L},
+ {0x1310, 0x1310, L},
+ {0x1312, 0x1315, L},
+ {0x1318, 0x131e, L},
+ {0x1320, 0x1346, L},
+ {0x1348, 0x135a, L},
+ {0x1361, 0x137c, L},
+ {0x13a0, 0x13f4, L},
+ {0x1401, 0x1676, L},
+ {0x1680, 0x1680, WS},
+ {0x1681, 0x169a, L},
+ {0x16a0, 0x16f0, L},
+ {0x1700, 0x170c, L},
+ {0x170e, 0x1711, L},
+ {0x1712, 0x1714, NSM},
+ {0x1720, 0x1731, L},
+ {0x1732, 0x1734, NSM},
+ {0x1735, 0x1736, L},
+ {0x1740, 0x1751, L},
+ {0x1752, 0x1753, NSM},
+ {0x1760, 0x176c, L},
+ {0x176e, 0x1770, L},
+ {0x1772, 0x1773, NSM},
+ {0x1780, 0x17b6, L},
+ {0x17b7, 0x17bd, NSM},
+ {0x17be, 0x17c5, L},
+ {0x17c6, 0x17c6, NSM},
+ {0x17c7, 0x17c8, L},
+ {0x17c9, 0x17d3, NSM},
+ {0x17d4, 0x17da, L},
+ {0x17db, 0x17db, ET},
+ {0x17dc, 0x17dc, L},
+ {0x17dd, 0x17dd, NSM},
+ {0x17e0, 0x17e9, L},
+ {0x180b, 0x180d, NSM},
+ {0x180e, 0x180e, WS},
+ {0x1810, 0x1819, L},
+ {0x1820, 0x1877, L},
+ {0x1880, 0x18a8, L},
+ {0x18a9, 0x18a9, NSM},
+ {0x1900, 0x191c, L},
+ {0x1920, 0x1922, NSM},
+ {0x1923, 0x1926, L},
+ {0x1927, 0x192b, NSM},
+ {0x1930, 0x1931, L},
+ {0x1932, 0x1932, NSM},
+ {0x1933, 0x1938, L},
+ {0x1939, 0x193b, NSM},
+ {0x1946, 0x196d, L},
+ {0x1970, 0x1974, L},
+ {0x1d00, 0x1d6b, L},
+ {0x1e00, 0x1e9b, L},
+ {0x1ea0, 0x1ef9, L},
+ {0x1f00, 0x1f15, L},
+ {0x1f18, 0x1f1d, L},
+ {0x1f20, 0x1f45, L},
+ {0x1f48, 0x1f4d, L},
+ {0x1f50, 0x1f57, L},
+ {0x1f59, 0x1f59, L},
+ {0x1f5b, 0x1f5b, L},
+ {0x1f5d, 0x1f5d, L},
+ {0x1f5f, 0x1f7d, L},
+ {0x1f80, 0x1fb4, L},
+ {0x1fb6, 0x1fbc, L},
+ {0x1fbe, 0x1fbe, L},
+ {0x1fc2, 0x1fc4, L},
+ {0x1fc6, 0x1fcc, L},
+ {0x1fd0, 0x1fd3, L},
+ {0x1fd6, 0x1fdb, L},
+ {0x1fe0, 0x1fec, L},
+ {0x1ff2, 0x1ff4, L},
+ {0x1ff6, 0x1ffc, L},
+ {0x2000, 0x200a, WS},
+ {0x200b, 0x200d, BN},
+ {0x200e, 0x200e, L},
+ {0x200f, 0x200f, R},
+ {0x2028, 0x2028, WS},
+ {0x2029, 0x2029, B},
+ {0x202a, 0x202a, LRE},
+ {0x202b, 0x202b, RLE},
+ {0x202c, 0x202c, PDF},
+ {0x202d, 0x202d, LRO},
+ {0x202e, 0x202e, RLO},
+ {0x202f, 0x202f, WS},
+ {0x2030, 0x2034, ET},
+ {0x2044, 0x2044, CS},
+ {0x205f, 0x205f, WS},
+ {0x2060, 0x2063, BN},
+ {0x206a, 0x206f, BN},
+ {0x2070, 0x2070, EN},
+ {0x2071, 0x2071, L},
+ {0x2074, 0x2079, EN},
+ {0x207a, 0x207b, ET},
+ {0x207f, 0x207f, L},
+ {0x2080, 0x2089, EN},
+ {0x208a, 0x208b, ET},
+ {0x20a0, 0x20b1, ET},
+ {0x20d0, 0x20ea, NSM},
+ {0x2102, 0x2102, L},
+ {0x2107, 0x2107, L},
+ {0x210a, 0x2113, L},
+ {0x2115, 0x2115, L},
+ {0x2119, 0x211d, L},
+ {0x2124, 0x2124, L},
+ {0x2126, 0x2126, L},
+ {0x2128, 0x2128, L},
+ {0x212a, 0x212d, L},
+ {0x212e, 0x212e, ET},
+ {0x212f, 0x2131, L},
+ {0x2133, 0x2139, L},
+ {0x213d, 0x213f, L},
+ {0x2145, 0x2149, L},
+ {0x2160, 0x2183, L},
+ {0x2212, 0x2213, ET},
+ {0x2336, 0x237a, L},
+ {0x2395, 0x2395, L},
+ {0x2488, 0x249b, EN},
+ {0x249c, 0x24e9, L},
+ {0x2800, 0x28ff, L},
+ {0x3000, 0x3000, WS},
+ {0x3005, 0x3007, L},
+ {0x3021, 0x3029, L},
+ {0x302a, 0x302f, NSM},
+ {0x3031, 0x3035, L},
+ {0x3038, 0x303c, L},
+ {0x3041, 0x3096, L},
+ {0x3099, 0x309a, NSM},
+ {0x309d, 0x309f, L},
+ {0x30a1, 0x30fa, L},
+ {0x30fc, 0x30ff, L},
+ {0x3105, 0x312c, L},
+ {0x3131, 0x318e, L},
+ {0x3190, 0x31b7, L},
+ {0x31f0, 0x321c, L},
+ {0x3220, 0x3243, L},
+ {0x3260, 0x327b, L},
+ {0x327f, 0x32b0, L},
+ {0x32c0, 0x32cb, L},
+ {0x32d0, 0x32fe, L},
+ {0x3300, 0x3376, L},
+ {0x337b, 0x33dd, L},
+ {0x33e0, 0x33fe, L},
+ {0x3400, 0x4db5, L},
+ {0x4e00, 0x9fa5, L},
+ {0xa000, 0xa48c, L},
+ {0xac00, 0xd7a3, L},
+ {0xd800, 0xfa2d, L},
+ {0xfa30, 0xfa6a, L},
+ {0xfb00, 0xfb06, L},
+ {0xfb13, 0xfb17, L},
+ {0xfb1d, 0xfb1d, R},
+ {0xfb1e, 0xfb1e, NSM},
+ {0xfb1f, 0xfb28, R},
+ {0xfb29, 0xfb29, ET},
+ {0xfb2a, 0xfb36, R},
+ {0xfb38, 0xfb3c, R},
+ {0xfb3e, 0xfb3e, R},
+ {0xfb40, 0xfb41, R},
+ {0xfb43, 0xfb44, R},
+ {0xfb46, 0xfb4f, R},
+ {0xfb50, 0xfbb1, AL},
+ {0xfbd3, 0xfd3d, AL},
+ {0xfd50, 0xfd8f, AL},
+ {0xfd92, 0xfdc7, AL},
+ {0xfdf0, 0xfdfc, AL},
+ {0xfe00, 0xfe0f, NSM},
+ {0xfe20, 0xfe23, NSM},
+ {0xfe50, 0xfe50, CS},
+ {0xfe52, 0xfe52, CS},
+ {0xfe55, 0xfe55, CS},
+ {0xfe5f, 0xfe5f, ET},
+ {0xfe62, 0xfe63, ET},
+ {0xfe69, 0xfe6a, ET},
+ {0xfe70, 0xfe74, AL},
+ {0xfe76, 0xfefc, AL},
+ {0xfeff, 0xfeff, BN},
+ {0xff03, 0xff05, ET},
+ {0xff0b, 0xff0b, ET},
+ {0xff0c, 0xff0c, CS},
+ {0xff0d, 0xff0d, ET},
+ {0xff0e, 0xff0e, CS},
+ {0xff0f, 0xff0f, ES},
+ {0xff10, 0xff19, EN},
+ {0xff1a, 0xff1a, CS},
+ {0xff21, 0xff3a, L},
+ {0xff41, 0xff5a, L},
+ {0xff66, 0xffbe, L},
+ {0xffc2, 0xffc7, L},
+ {0xffca, 0xffcf, L},
+ {0xffd2, 0xffd7, L},
+ {0xffda, 0xffdc, L},
+ {0xffe0, 0xffe1, ET},
+ {0xffe5, 0xffe6, ET},
+ {0x10000, 0x1000b, L},
+ {0x1000d, 0x10026, L},
+ {0x10028, 0x1003a, L},
+ {0x1003c, 0x1003d, L},
+ {0x1003f, 0x1004d, L},
+ {0x10050, 0x1005d, L},
+ {0x10080, 0x100fa, L},
+ {0x10100, 0x10100, L},
+ {0x10102, 0x10102, L},
+ {0x10107, 0x10133, L},
+ {0x10137, 0x1013f, L},
+ {0x10300, 0x1031e, L},
+ {0x10320, 0x10323, L},
+ {0x10330, 0x1034a, L},
+ {0x10380, 0x1039d, L},
+ {0x1039f, 0x1039f, L},
+ {0x10400, 0x1049d, L},
+ {0x104a0, 0x104a9, L},
+ {0x10800, 0x10805, R},
+ {0x10808, 0x10808, R},
+ {0x1080a, 0x10835, R},
+ {0x10837, 0x10838, R},
+ {0x1083c, 0x1083c, R},
+ {0x1083f, 0x1083f, R},
+ {0x1d000, 0x1d0f5, L},
+ {0x1d100, 0x1d126, L},
+ {0x1d12a, 0x1d166, L},
+ {0x1d167, 0x1d169, NSM},
+ {0x1d16a, 0x1d172, L},
+ {0x1d173, 0x1d17a, BN},
+ {0x1d17b, 0x1d182, NSM},
+ {0x1d183, 0x1d184, L},
+ {0x1d185, 0x1d18b, NSM},
+ {0x1d18c, 0x1d1a9, L},
+ {0x1d1aa, 0x1d1ad, NSM},
+ {0x1d1ae, 0x1d1dd, L},
+ {0x1d400, 0x1d454, L},
+ {0x1d456, 0x1d49c, L},
+ {0x1d49e, 0x1d49f, L},
+ {0x1d4a2, 0x1d4a2, L},
+ {0x1d4a5, 0x1d4a6, L},
+ {0x1d4a9, 0x1d4ac, L},
+ {0x1d4ae, 0x1d4b9, L},
+ {0x1d4bb, 0x1d4bb, L},
+ {0x1d4bd, 0x1d4c3, L},
+ {0x1d4c5, 0x1d505, L},
+ {0x1d507, 0x1d50a, L},
+ {0x1d50d, 0x1d514, L},
+ {0x1d516, 0x1d51c, L},
+ {0x1d51e, 0x1d539, L},
+ {0x1d53b, 0x1d53e, L},
+ {0x1d540, 0x1d544, L},
+ {0x1d546, 0x1d546, L},
+ {0x1d54a, 0x1d550, L},
+ {0x1d552, 0x1d6a3, L},
+ {0x1d6a8, 0x1d7c9, L},
+ {0x1d7ce, 0x1d7ff, EN},
+ {0x20000, 0x2a6d6, L},
+ {0x2f800, 0x2fa1d, L},
+ {0xe0001, 0xe0001, BN},
+ {0xe0020, 0xe007f, BN},
+ {0xe0100, 0xe01ef, NSM},
+ {0xf0000, 0xffffd, L},
+ {0x100000, 0x10fffd, L}
+ };
+
+ int i, j, k;
+
+ i = -1;
+ j = lenof(lookup);
+
+ while (j - i > 1) {
+ k = (i + j) / 2;
+ if (ch < lookup[k].first)
+ j = k;
+ else if (ch > lookup[k].last)
+ i = k;
+ else
+ return lookup[k].type;
+ }
+
+ /*
+ * If we reach here, the character was not in any of the
+ * intervals listed in the lookup table. This means we return
+ * ON (`Other Neutrals'). This is the appropriate code for any
+ * character genuinely not listed in the Unicode table, and
+ * also the table above has deliberately left out any
+ * characters _explicitly_ listed as ON (to save space!).
+ */
+ return ON;
+}
+
+/*
+ * Function exported to front ends to allow them to identify
+ * bidi-active characters (in case, for example, the platform's
+ * text display function can't conveniently be prevented from doing
+ * its own bidi and so special treatment is required for characters
+ * that would cause the bidi algorithm to activate).
+ *
+ * This function is passed a single Unicode code point, and returns
+ * nonzero if the presence of this code point can possibly cause
+ * the bidi algorithm to do any reordering. Thus, any string
+ * composed entirely of characters for which is_rtl() returns zero
+ * should be safe to pass to a bidi-active platform display
+ * function without fear.
+ *
+ * (is_rtl() must therefore also return true for any character
+ * which would be affected by Arabic shaping, but this isn't
+ * important because all such characters are right-to-left so it
+ * would have flagged them anyway.)
+ */
+int is_rtl(int c)
+{
+ /*
+ * After careful reading of the Unicode bidi algorithm (URL as
+ * given at the top of this file) I believe that the only
+ * character classes which can possibly cause trouble are R,
+ * AL, RLE and RLO. I think that any string containing no
+ * character in any of those classes will be displayed
+ * uniformly left-to-right by the Unicode bidi algorithm.
+ */
+ const int mask = (1<<R) | (1<<AL) | (1<<RLE) | (1<<RLO);
+
+ return mask & (1 << (getType(c)));
+}
+
+/*
+ * The most significant 2 bits of each level are used to store
+ * Override status of each character
+ * This function sets the override bits of level according
+ * to the value in override, and reurns the new byte.
+ */
+unsigned char setOverrideBits(unsigned char level, unsigned char override)
+{
+ if (override == ON)
+ return level;
+ else if (override == R)
+ return level | OISR;
+ else if (override == L)
+ return level | OISL;
+ return level;
+}
+
+/*
+ * Find the most recent run of the same value in `level', and
+ * return the value _before_ it. Used to process U+202C POP
+ * DIRECTIONAL FORMATTING.
+ */
+int getPreviousLevel(unsigned char* level, int from)
+{
+ if (from > 0) {
+ unsigned char current = level[--from];
+
+ while (from >= 0 && level[from] == current)
+ from--;
+
+ if (from >= 0)
+ return level[from];
+
+ return -1;
+ } else
+ return -1;
+}
+
+/* The Main shaping function, and the only one to be used
+ * by the outside world.
+ *
+ * line: buffer to apply shaping to. this must be passed by doBidi() first
+ * to: output buffer for the shaped data
+ * count: number of characters in line
+ */
+int do_shape(bidi_char *line, bidi_char *to, int count)
+{
+ int i, tempShape, ligFlag;
+
+ for (ligFlag=i=0; i<count; i++) {
+ to[i] = line[i];
+ tempShape = STYPE(line[i].wc);
+ switch (tempShape) {
+ case SC:
+ break;
+
+ case SU:
+ break;
+
+ case SR:
+ tempShape = (i+1 < count ? STYPE(line[i+1].wc) : SU);
+ if ((tempShape == SL) || (tempShape == SD) || (tempShape == SC))
+ to[i].wc = SFINAL((SISOLATED(line[i].wc)));
+ else
+ to[i].wc = SISOLATED(line[i].wc);
+ break;
+
+
+ case SD:
+ /* Make Ligatures */
+ tempShape = (i+1 < count ? STYPE(line[i+1].wc) : SU);
+ if (line[i].wc == 0x644) {
+ if (i > 0) switch (line[i-1].wc) {
+ case 0x622:
+ ligFlag = 1;
+ if ((tempShape == SL) || (tempShape == SD) || (tempShape == SC))
+ to[i].wc = 0xFEF6;
+ else
+ to[i].wc = 0xFEF5;
+ break;
+ case 0x623:
+ ligFlag = 1;
+ if ((tempShape == SL) || (tempShape == SD) || (tempShape == SC))
+ to[i].wc = 0xFEF8;
+ else
+ to[i].wc = 0xFEF7;
+ break;
+ case 0x625:
+ ligFlag = 1;
+ if ((tempShape == SL) || (tempShape == SD) || (tempShape == SC))
+ to[i].wc = 0xFEFA;
+ else
+ to[i].wc = 0xFEF9;
+ break;
+ case 0x627:
+ ligFlag = 1;
+ if ((tempShape == SL) || (tempShape == SD) || (tempShape == SC))
+ to[i].wc = 0xFEFC;
+ else
+ to[i].wc = 0xFEFB;
+ break;
+ }
+ if (ligFlag) {
+ to[i-1].wc = 0x20;
+ ligFlag = 0;
+ break;
+ }
+ }
+
+ if ((tempShape == SL) || (tempShape == SD) || (tempShape == SC)) {
+ tempShape = (i > 0 ? STYPE(line[i-1].wc) : SU);
+ if ((tempShape == SR) || (tempShape == SD) || (tempShape == SC))
+ to[i].wc = SMEDIAL((SISOLATED(line[i].wc)));
+ else
+ to[i].wc = SFINAL((SISOLATED(line[i].wc)));
+ break;
+ }
+
+ tempShape = (i > 0 ? STYPE(line[i-1].wc) : SU);
+ if ((tempShape == SR) || (tempShape == SD) || (tempShape == SC))
+ to[i].wc = SINITIAL((SISOLATED(line[i].wc)));
+ else
+ to[i].wc = SISOLATED(line[i].wc);
+ break;
+
+
+ }
+ }
+ return 1;
+}
+
+/*
+ * The Main Bidi Function, and the only function that should
+ * be used by the outside world.
+ *
+ * line: a buffer of size count containing text to apply
+ * the Bidirectional algorithm to.
+ */
+
+int do_bidi(bidi_char *line, int count)
+{
+ unsigned char* types;
+ unsigned char* levels;
+ unsigned char paragraphLevel;
+ unsigned char currentEmbedding;
+ unsigned char currentOverride;
+ unsigned char tempType;
+ int i, j, yes, bover;
+
+ /* Check the presence of R or AL types as optimization */
+ yes = 0;
+ for (i=0; i<count; i++) {
+ int type = getType(line[i].wc);
+ if (type == R || type == AL) {
+ yes = 1;
+ break;
+ }
+ }
+ if (yes == 0)
+ return L;
+
+ /* Initialize types, levels */
+ types = snewn(count, unsigned char);
+ levels = snewn(count, unsigned char);
+
+ /* Rule (P1) NOT IMPLEMENTED
+ * P1. Split the text into separate paragraphs. A paragraph separator is
+ * kept with the previous paragraph. Within each paragraph, apply all the
+ * other rules of this algorithm.
+ */
+
+ /* Rule (P2), (P3)
+ * P2. In each paragraph, find the first character of type L, AL, or R.
+ * P3. If a character is found in P2 and it is of type AL or R, then set
+ * the paragraph embedding level to one; otherwise, set it to zero.
+ */
+ paragraphLevel = 0;
+ for (i=0; i<count ; i++) {
+ int type = getType(line[i].wc);
+ if (type == R || type == AL) {
+ paragraphLevel = 1;
+ break;
+ } else if (type == L)
+ break;
+ }
+
+ /* Rule (X1)
+ * X1. Begin by setting the current embedding level to the paragraph
+ * embedding level. Set the directional override status to neutral.
+ */
+ currentEmbedding = paragraphLevel;
+ currentOverride = ON;
+
+ /* Rule (X2), (X3), (X4), (X5), (X6), (X7), (X8)
+ * X2. With each RLE, compute the least greater odd embedding level.
+ * X3. With each LRE, compute the least greater even embedding level.
+ * X4. With each RLO, compute the least greater odd embedding level.
+ * X5. With each LRO, compute the least greater even embedding level.
+ * X6. For all types besides RLE, LRE, RLO, LRO, and PDF:
+ * a. Set the level of the current character to the current
+ * embedding level.
+ * b. Whenever the directional override status is not neutral,
+ * reset the current character type to the directional
+ * override status.
+ * X7. With each PDF, determine the matching embedding or override code.
+ * If there was a valid matching code, restore (pop) the last
+ * remembered (pushed) embedding level and directional override.
+ * X8. All explicit directional embeddings and overrides are completely
+ * terminated at the end of each paragraph. Paragraph separators are not
+ * included in the embedding. (Useless here) NOT IMPLEMENTED
+ */
+ bover = 0;
+ for (i=0; i<count; i++) {
+ tempType = getType(line[i].wc);
+ switch (tempType) {
+ case RLE:
+ currentEmbedding = levels[i] = leastGreaterOdd(currentEmbedding);
+ levels[i] = setOverrideBits(levels[i], currentOverride);
+ currentOverride = ON;
+ break;
+
+ case LRE:
+ currentEmbedding = levels[i] = leastGreaterEven(currentEmbedding);
+ levels[i] = setOverrideBits(levels[i], currentOverride);
+ currentOverride = ON;
+ break;
+
+ case RLO:
+ currentEmbedding = levels[i] = leastGreaterOdd(currentEmbedding);
+ tempType = currentOverride = R;
+ bover = 1;
+ break;
+
+ case LRO:
+ currentEmbedding = levels[i] = leastGreaterEven(currentEmbedding);
+ tempType = currentOverride = L;
+ bover = 1;
+ break;
+
+ case PDF:
+ {
+ int prevlevel = getPreviousLevel(levels, i);
+
+ if (prevlevel == -1) {
+ currentEmbedding = paragraphLevel;
+ currentOverride = ON;
+ } else {
+ currentOverride = currentEmbedding & OMASK;
+ currentEmbedding = currentEmbedding & ~OMASK;
+ }
+ }
+ levels[i] = currentEmbedding;
+ break;
+
+ /* Whitespace is treated as neutral for now */
+ case WS:
+ case S:
+ levels[i] = currentEmbedding;
+ tempType = ON;
+ if (currentOverride != ON)
+ tempType = currentOverride;
+ break;
+
+ default:
+ levels[i] = currentEmbedding;
+ if (currentOverride != ON)
+ tempType = currentOverride;
+ break;
+
+ }
+ types[i] = tempType;
+ }
+ /* this clears out all overrides, so we can use levels safely... */
+ /* checks bover first */
+ if (bover)
+ for (i=0; i<count; i++)
+ levels[i] = levels[i] & LMASK;
+
+ /* Rule (X9)
+ * X9. Remove all RLE, LRE, RLO, LRO, PDF, and BN codes.
+ * Here, they're converted to BN.
+ */
+ for (i=0; i<count; i++) {
+ switch (types[i]) {
+ case RLE:
+ case LRE:
+ case RLO:
+ case LRO:
+ case PDF:
+ types[i] = BN;
+ break;
+ }
+ }
+
+ /* Rule (W1)
+ * W1. Examine each non-spacing mark (NSM) in the level run, and change
+ * the type of the NSM to the type of the previous character. If the NSM
+ * is at the start of the level run, it will get the type of sor.
+ */
+ if (types[0] == NSM)
+ types[0] = paragraphLevel;
+
+ for (i=1; i<count; i++) {
+ if (types[i] == NSM)
+ types[i] = types[i-1];
+ /* Is this a safe assumption?
+ * I assumed the previous, IS a character.
+ */
+ }
+
+ /* Rule (W2)
+ * W2. Search backwards from each instance of a European number until the
+ * first strong type (R, L, AL, or sor) is found. If an AL is found,
+ * change the type of the European number to Arabic number.
+ */
+ for (i=0; i<count; i++) {
+ if (types[i] == EN) {
+ j=i;
+ while (j >= 0) {
+ if (types[j] == AL) {
+ types[i] = AN;
+ break;
+ } else if (types[j] == R || types[j] == L) {
+ break;
+ }
+ j--;
+ }
+ }
+ }
+
+ /* Rule (W3)
+ * W3. Change all ALs to R.
+ *
+ * Optimization: on Rule Xn, we might set a flag on AL type
+ * to prevent this loop in L R lines only...
+ */
+ for (i=0; i<count; i++) {
+ if (types[i] == AL)
+ types[i] = R;
+ }
+
+ /* Rule (W4)
+ * W4. A single European separator between two European numbers changes
+ * to a European number. A single common separator between two numbers
+ * of the same type changes to that type.
+ */
+ for (i=1; i<(count-1); i++) {
+ if (types[i] == ES) {
+ if (types[i-1] == EN && types[i+1] == EN)
+ types[i] = EN;
+ } else if (types[i] == CS) {
+ if (types[i-1] == EN && types[i+1] == EN)
+ types[i] = EN;
+ else if (types[i-1] == AN && types[i+1] == AN)
+ types[i] = AN;
+ }
+ }
+
+ /* Rule (W5)
+ * W5. A sequence of European terminators adjacent to European numbers
+ * changes to all European numbers.
+ *
+ * Optimization: lots here... else ifs need rearrangement
+ */
+ for (i=0; i<count; i++) {
+ if (types[i] == ET) {
+ if (i > 0 && types[i-1] == EN) {
+ types[i] = EN;
+ continue;
+ } else if (i < count-1 && types[i+1] == EN) {
+ types[i] = EN;
+ continue;
+ } else if (i < count-1 && types[i+1] == ET) {
+ j=i;
+ while (j <count && types[j] == ET) {
+ j++;
+ }
+ if (types[j] == EN)
+ types[i] = EN;
+ }
+ }
+ }
+
+ /* Rule (W6)
+ * W6. Otherwise, separators and terminators change to Other Neutral:
+ */
+ for (i=0; i<count; i++) {
+ switch (types[i]) {
+ case ES:
+ case ET:
+ case CS:
+ types[i] = ON;
+ break;
+ }
+ }
+
+ /* Rule (W7)
+ * W7. Search backwards from each instance of a European number until
+ * the first strong type (R, L, or sor) is found. If an L is found,
+ * then change the type of the European number to L.
+ */
+ for (i=0; i<count; i++) {
+ if (types[i] == EN) {
+ j=i;
+ while (j >= 0) {
+ if (types[j] == L) {
+ types[i] = L;
+ break;
+ } else if (types[j] == R || types[j] == AL) {
+ break;
+ }
+ j--;
+ }
+ }
+ }
+
+ /* Rule (N1)
+ * N1. A sequence of neutrals takes the direction of the surrounding
+ * strong text if the text on both sides has the same direction. European
+ * and Arabic numbers are treated as though they were R.
+ */
+ if (count >= 2 && types[0] == ON) {
+ if ((types[1] == R) || (types[1] == EN) || (types[1] == AN))
+ types[0] = R;
+ else if (types[1] == L)
+ types[0] = L;
+ }
+ for (i=1; i<(count-1); i++) {
+ if (types[i] == ON) {
+ if (types[i-1] == L) {
+ j=i;
+ while (j<(count-1) && types[j] == ON) {
+ j++;
+ }
+ if (types[j] == L) {
+ while (i<j) {
+ types[i] = L;
+ i++;
+ }
+ }
+
+ } else if ((types[i-1] == R) ||
+ (types[i-1] == EN) ||
+ (types[i-1] == AN)) {
+ j=i;
+ while (j<(count-1) && types[j] == ON) {
+ j++;
+ }
+ if ((types[j] == R) ||
+ (types[j] == EN) ||
+ (types[j] == AN)) {
+ while (i<j) {
+ types[i] = R;
+ i++;
+ }
+ }
+ }
+ }
+ }
+ if (count >= 2 && types[count-1] == ON) {
+ if (types[count-2] == R || types[count-2] == EN || types[count-2] == AN)
+ types[count-1] = R;
+ else if (types[count-2] == L)
+ types[count-1] = L;
+ }
+
+ /* Rule (N2)
+ * N2. Any remaining neutrals take the embedding direction.
+ */
+ for (i=0; i<count; i++) {
+ if (types[i] == ON) {
+ if ((levels[i] % 2) == 0)
+ types[i] = L;
+ else
+ types[i] = R;
+ }
+ }
+
+ /* Rule (I1)
+ * I1. For all characters with an even (left-to-right) embedding
+ * direction, those of type R go up one level and those of type AN or
+ * EN go up two levels.
+ */
+ for (i=0; i<count; i++) {
+ if ((levels[i] % 2) == 0) {
+ if (types[i] == R)
+ levels[i] += 1;
+ else if (types[i] == AN || types[i] == EN)
+ levels[i] += 2;
+ }
+ }
+
+ /* Rule (I2)
+ * I2. For all characters with an odd (right-to-left) embedding direction,
+ * those of type L, EN or AN go up one level.
+ */
+ for (i=0; i<count; i++) {
+ if ((levels[i] % 2) == 1) {
+ if (types[i] == L || types[i] == EN || types[i] == AN)
+ levels[i] += 1;
+ }
+ }
+
+ /* Rule (L1)
+ * L1. On each line, reset the embedding level of the following characters
+ * to the paragraph embedding level:
+ * (1)segment separators, (2)paragraph separators,
+ * (3)any sequence of whitespace characters preceding
+ * a segment separator or paragraph separator,
+ * (4)and any sequence of white space characters
+ * at the end of the line.
+ * The types of characters used here are the original types, not those
+ * modified by the previous phase.
+ */
+ j=count-1;
+ while (j>0 && (getType(line[j].wc) == WS)) {
+ j--;
+ }
+ if (j < (count-1)) {
+ for (j++; j<count; j++)
+ levels[j] = paragraphLevel;
+ }
+ for (i=0; i<count; i++) {
+ tempType = getType(line[i].wc);
+ if (tempType == WS) {
+ j=i;
+ while (j<count && (getType(line[j].wc) == WS)) {
+ j++;
+ }
+ if (j==count || getType(line[j].wc) == B ||
+ getType(line[j].wc) == S) {
+ for (j--; j>=i ; j--) {
+ levels[j] = paragraphLevel;
+ }
+ }
+ } else if (tempType == B || tempType == S) {
+ levels[i] = paragraphLevel;
+ }
+ }
+
+ /* Rule (L4) NOT IMPLEMENTED
+ * L4. A character that possesses the mirrored property as specified by
+ * Section 4.7, Mirrored, must be depicted by a mirrored glyph if the
+ * resolved directionality of that character is R.
+ */
+ /* Note: this is implemented before L2 for efficiency */
+ for (i=0; i<count; i++)
+ if ((levels[i] % 2) == 1)
+ doMirror(&line[i].wc);
+
+ /* Rule (L2)
+ * L2. From the highest level found in the text to the lowest odd level on
+ * each line, including intermediate levels not actually present in the
+ * text, reverse any contiguous sequence of characters that are at that
+ * level or higher
+ */
+ /* we flip the character string and leave the level array */
+ i=0;
+ tempType = levels[0];
+ while (i < count) {
+ if (levels[i] > tempType)
+ tempType = levels[i];
+ i++;
+ }
+ /* maximum level in tempType. */
+ while (tempType > 0) { /* loop from highest level to the least odd, */
+ /* which i assume is 1 */
+ flipThisRun(line, levels, tempType, count);
+ tempType--;
+ }
+
+ /* Rule (L3) NOT IMPLEMENTED
+ * L3. Combining marks applied to a right-to-left base character will at
+ * this point precede their base character. If the rendering engine
+ * expects them to follow the base characters in the final display
+ * process, then the ordering of the marks and the base character must
+ * be reversed.
+ */
+ sfree(types);
+ sfree(levels);
+ return R;
+}
+
+
+/*
+ * Bad, Horrible function
+ * takes a pointer to a character that is checked for
+ * having a mirror glyph.
+ */
+void doMirror(wchar_t* ch)
+{
+ if ((*ch & 0xFF00) == 0) {
+ switch (*ch) {
+ case 0x0028: *ch = 0x0029; break;
+ case 0x0029: *ch = 0x0028; break;
+ case 0x003C: *ch = 0x003E; break;
+ case 0x003E: *ch = 0x003C; break;
+ case 0x005B: *ch = 0x005D; break;
+ case 0x005D: *ch = 0x005B; break;
+ case 0x007B: *ch = 0x007D; break;
+ case 0x007D: *ch = 0x007B; break;
+ case 0x00AB: *ch = 0x00BB; break;
+ case 0x00BB: *ch = 0x00AB; break;
+ }
+ } else if ((*ch & 0xFF00) == 0x2000) {
+ switch (*ch) {
+ case 0x2039: *ch = 0x203A; break;
+ case 0x203A: *ch = 0x2039; break;
+ case 0x2045: *ch = 0x2046; break;
+ case 0x2046: *ch = 0x2045; break;
+ case 0x207D: *ch = 0x207E; break;
+ case 0x207E: *ch = 0x207D; break;
+ case 0x208D: *ch = 0x208E; break;
+ case 0x208E: *ch = 0x208D; break;
+ }
+ } else if ((*ch & 0xFF00) == 0x2200) {
+ switch (*ch) {
+ case 0x2208: *ch = 0x220B; break;
+ case 0x2209: *ch = 0x220C; break;
+ case 0x220A: *ch = 0x220D; break;
+ case 0x220B: *ch = 0x2208; break;
+ case 0x220C: *ch = 0x2209; break;
+ case 0x220D: *ch = 0x220A; break;
+ case 0x2215: *ch = 0x29F5; break;
+ case 0x223C: *ch = 0x223D; break;
+ case 0x223D: *ch = 0x223C; break;
+ case 0x2243: *ch = 0x22CD; break;
+ case 0x2252: *ch = 0x2253; break;
+ case 0x2253: *ch = 0x2252; break;
+ case 0x2254: *ch = 0x2255; break;
+ case 0x2255: *ch = 0x2254; break;
+ case 0x2264: *ch = 0x2265; break;
+ case 0x2265: *ch = 0x2264; break;
+ case 0x2266: *ch = 0x2267; break;
+ case 0x2267: *ch = 0x2266; break;
+ case 0x2268: *ch = 0x2269; break;
+ case 0x2269: *ch = 0x2268; break;
+ case 0x226A: *ch = 0x226B; break;
+ case 0x226B: *ch = 0x226A; break;
+ case 0x226E: *ch = 0x226F; break;
+ case 0x226F: *ch = 0x226E; break;
+ case 0x2270: *ch = 0x2271; break;
+ case 0x2271: *ch = 0x2270; break;
+ case 0x2272: *ch = 0x2273; break;
+ case 0x2273: *ch = 0x2272; break;
+ case 0x2274: *ch = 0x2275; break;
+ case 0x2275: *ch = 0x2274; break;
+ case 0x2276: *ch = 0x2277; break;
+ case 0x2277: *ch = 0x2276; break;
+ case 0x2278: *ch = 0x2279; break;
+ case 0x2279: *ch = 0x2278; break;
+ case 0x227A: *ch = 0x227B; break;
+ case 0x227B: *ch = 0x227A; break;
+ case 0x227C: *ch = 0x227D; break;
+ case 0x227D: *ch = 0x227C; break;
+ case 0x227E: *ch = 0x227F; break;
+ case 0x227F: *ch = 0x227E; break;
+ case 0x2280: *ch = 0x2281; break;
+ case 0x2281: *ch = 0x2280; break;
+ case 0x2282: *ch = 0x2283; break;
+ case 0x2283: *ch = 0x2282; break;
+ case 0x2284: *ch = 0x2285; break;
+ case 0x2285: *ch = 0x2284; break;
+ case 0x2286: *ch = 0x2287; break;
+ case 0x2287: *ch = 0x2286; break;
+ case 0x2288: *ch = 0x2289; break;
+ case 0x2289: *ch = 0x2288; break;
+ case 0x228A: *ch = 0x228B; break;
+ case 0x228B: *ch = 0x228A; break;
+ case 0x228F: *ch = 0x2290; break;
+ case 0x2290: *ch = 0x228F; break;
+ case 0x2291: *ch = 0x2292; break;
+ case 0x2292: *ch = 0x2291; break;
+ case 0x2298: *ch = 0x29B8; break;
+ case 0x22A2: *ch = 0x22A3; break;
+ case 0x22A3: *ch = 0x22A2; break;
+ case 0x22A6: *ch = 0x2ADE; break;
+ case 0x22A8: *ch = 0x2AE4; break;
+ case 0x22A9: *ch = 0x2AE3; break;
+ case 0x22AB: *ch = 0x2AE5; break;
+ case 0x22B0: *ch = 0x22B1; break;
+ case 0x22B1: *ch = 0x22B0; break;
+ case 0x22B2: *ch = 0x22B3; break;
+ case 0x22B3: *ch = 0x22B2; break;
+ case 0x22B4: *ch = 0x22B5; break;
+ case 0x22B5: *ch = 0x22B4; break;
+ case 0x22B6: *ch = 0x22B7; break;
+ case 0x22B7: *ch = 0x22B6; break;
+ case 0x22C9: *ch = 0x22CA; break;
+ case 0x22CA: *ch = 0x22C9; break;
+ case 0x22CB: *ch = 0x22CC; break;
+ case 0x22CC: *ch = 0x22CB; break;
+ case 0x22CD: *ch = 0x2243; break;
+ case 0x22D0: *ch = 0x22D1; break;
+ case 0x22D1: *ch = 0x22D0; break;
+ case 0x22D6: *ch = 0x22D7; break;
+ case 0x22D7: *ch = 0x22D6; break;
+ case 0x22D8: *ch = 0x22D9; break;
+ case 0x22D9: *ch = 0x22D8; break;
+ case 0x22DA: *ch = 0x22DB; break;
+ case 0x22DB: *ch = 0x22DA; break;
+ case 0x22DC: *ch = 0x22DD; break;
+ case 0x22DD: *ch = 0x22DC; break;
+ case 0x22DE: *ch = 0x22DF; break;
+ case 0x22DF: *ch = 0x22DE; break;
+ case 0x22E0: *ch = 0x22E1; break;
+ case 0x22E1: *ch = 0x22E0; break;
+ case 0x22E2: *ch = 0x22E3; break;
+ case 0x22E3: *ch = 0x22E2; break;
+ case 0x22E4: *ch = 0x22E5; break;
+ case 0x22E5: *ch = 0x22E4; break;
+ case 0x22E6: *ch = 0x22E7; break;
+ case 0x22E7: *ch = 0x22E6; break;
+ case 0x22E8: *ch = 0x22E9; break;
+ case 0x22E9: *ch = 0x22E8; break;
+ case 0x22EA: *ch = 0x22EB; break;
+ case 0x22EB: *ch = 0x22EA; break;
+ case 0x22EC: *ch = 0x22ED; break;
+ case 0x22ED: *ch = 0x22EC; break;
+ case 0x22F0: *ch = 0x22F1; break;
+ case 0x22F1: *ch = 0x22F0; break;
+ case 0x22F2: *ch = 0x22FA; break;
+ case 0x22F3: *ch = 0x22FB; break;
+ case 0x22F4: *ch = 0x22FC; break;
+ case 0x22F6: *ch = 0x22FD; break;
+ case 0x22F7: *ch = 0x22FE; break;
+ case 0x22FA: *ch = 0x22F2; break;
+ case 0x22FB: *ch = 0x22F3; break;
+ case 0x22FC: *ch = 0x22F4; break;
+ case 0x22FD: *ch = 0x22F6; break;
+ case 0x22FE: *ch = 0x22F7; break;
+ }
+ } else if ((*ch & 0xFF00) == 0x2300) {
+ switch (*ch) {
+ case 0x2308: *ch = 0x2309; break;
+ case 0x2309: *ch = 0x2308; break;
+ case 0x230A: *ch = 0x230B; break;
+ case 0x230B: *ch = 0x230A; break;
+ case 0x2329: *ch = 0x232A; break;
+ case 0x232A: *ch = 0x2329; break;
+ }
+ } else if ((*ch & 0xFF00) == 0x2700) {
+ switch (*ch) {
+ case 0x2768: *ch = 0x2769; break;
+ case 0x2769: *ch = 0x2768; break;
+ case 0x276A: *ch = 0x276B; break;
+ case 0x276B: *ch = 0x276A; break;
+ case 0x276C: *ch = 0x276D; break;
+ case 0x276D: *ch = 0x276C; break;
+ case 0x276E: *ch = 0x276F; break;
+ case 0x276F: *ch = 0x276E; break;
+ case 0x2770: *ch = 0x2771; break;
+ case 0x2771: *ch = 0x2770; break;
+ case 0x2772: *ch = 0x2773; break;
+ case 0x2773: *ch = 0x2772; break;
+ case 0x2774: *ch = 0x2775; break;
+ case 0x2775: *ch = 0x2774; break;
+ case 0x27D5: *ch = 0x27D6; break;
+ case 0x27D6: *ch = 0x27D5; break;
+ case 0x27DD: *ch = 0x27DE; break;
+ case 0x27DE: *ch = 0x27DD; break;
+ case 0x27E2: *ch = 0x27E3; break;
+ case 0x27E3: *ch = 0x27E2; break;
+ case 0x27E4: *ch = 0x27E5; break;
+ case 0x27E5: *ch = 0x27E4; break;
+ case 0x27E6: *ch = 0x27E7; break;
+ case 0x27E7: *ch = 0x27E6; break;
+ case 0x27E8: *ch = 0x27E9; break;
+ case 0x27E9: *ch = 0x27E8; break;
+ case 0x27EA: *ch = 0x27EB; break;
+ case 0x27EB: *ch = 0x27EA; break;
+ }
+ } else if ((*ch & 0xFF00) == 0x2900) {
+ switch (*ch) {
+ case 0x2983: *ch = 0x2984; break;
+ case 0x2984: *ch = 0x2983; break;
+ case 0x2985: *ch = 0x2986; break;
+ case 0x2986: *ch = 0x2985; break;
+ case 0x2987: *ch = 0x2988; break;
+ case 0x2988: *ch = 0x2987; break;
+ case 0x2989: *ch = 0x298A; break;
+ case 0x298A: *ch = 0x2989; break;
+ case 0x298B: *ch = 0x298C; break;
+ case 0x298C: *ch = 0x298B; break;
+ case 0x298D: *ch = 0x2990; break;
+ case 0x298E: *ch = 0x298F; break;
+ case 0x298F: *ch = 0x298E; break;
+ case 0x2990: *ch = 0x298D; break;
+ case 0x2991: *ch = 0x2992; break;
+ case 0x2992: *ch = 0x2991; break;
+ case 0x2993: *ch = 0x2994; break;
+ case 0x2994: *ch = 0x2993; break;
+ case 0x2995: *ch = 0x2996; break;
+ case 0x2996: *ch = 0x2995; break;
+ case 0x2997: *ch = 0x2998; break;
+ case 0x2998: *ch = 0x2997; break;
+ case 0x29B8: *ch = 0x2298; break;
+ case 0x29C0: *ch = 0x29C1; break;
+ case 0x29C1: *ch = 0x29C0; break;
+ case 0x29C4: *ch = 0x29C5; break;
+ case 0x29C5: *ch = 0x29C4; break;
+ case 0x29CF: *ch = 0x29D0; break;
+ case 0x29D0: *ch = 0x29CF; break;
+ case 0x29D1: *ch = 0x29D2; break;
+ case 0x29D2: *ch = 0x29D1; break;
+ case 0x29D4: *ch = 0x29D5; break;
+ case 0x29D5: *ch = 0x29D4; break;
+ case 0x29D8: *ch = 0x29D9; break;
+ case 0x29D9: *ch = 0x29D8; break;
+ case 0x29DA: *ch = 0x29DB; break;
+ case 0x29DB: *ch = 0x29DA; break;
+ case 0x29F5: *ch = 0x2215; break;
+ case 0x29F8: *ch = 0x29F9; break;
+ case 0x29F9: *ch = 0x29F8; break;
+ case 0x29FC: *ch = 0x29FD; break;
+ case 0x29FD: *ch = 0x29FC; break;
+ }
+ } else if ((*ch & 0xFF00) == 0x2A00) {
+ switch (*ch) {
+ case 0x2A2B: *ch = 0x2A2C; break;
+ case 0x2A2C: *ch = 0x2A2B; break;
+ case 0x2A2D: *ch = 0x2A2C; break;
+ case 0x2A2E: *ch = 0x2A2D; break;
+ case 0x2A34: *ch = 0x2A35; break;
+ case 0x2A35: *ch = 0x2A34; break;
+ case 0x2A3C: *ch = 0x2A3D; break;
+ case 0x2A3D: *ch = 0x2A3C; break;
+ case 0x2A64: *ch = 0x2A65; break;
+ case 0x2A65: *ch = 0x2A64; break;
+ case 0x2A79: *ch = 0x2A7A; break;
+ case 0x2A7A: *ch = 0x2A79; break;
+ case 0x2A7D: *ch = 0x2A7E; break;
+ case 0x2A7E: *ch = 0x2A7D; break;
+ case 0x2A7F: *ch = 0x2A80; break;
+ case 0x2A80: *ch = 0x2A7F; break;
+ case 0x2A81: *ch = 0x2A82; break;
+ case 0x2A82: *ch = 0x2A81; break;
+ case 0x2A83: *ch = 0x2A84; break;
+ case 0x2A84: *ch = 0x2A83; break;
+ case 0x2A8B: *ch = 0x2A8C; break;
+ case 0x2A8C: *ch = 0x2A8B; break;
+ case 0x2A91: *ch = 0x2A92; break;
+ case 0x2A92: *ch = 0x2A91; break;
+ case 0x2A93: *ch = 0x2A94; break;
+ case 0x2A94: *ch = 0x2A93; break;
+ case 0x2A95: *ch = 0x2A96; break;
+ case 0x2A96: *ch = 0x2A95; break;
+ case 0x2A97: *ch = 0x2A98; break;
+ case 0x2A98: *ch = 0x2A97; break;
+ case 0x2A99: *ch = 0x2A9A; break;
+ case 0x2A9A: *ch = 0x2A99; break;
+ case 0x2A9B: *ch = 0x2A9C; break;
+ case 0x2A9C: *ch = 0x2A9B; break;
+ case 0x2AA1: *ch = 0x2AA2; break;
+ case 0x2AA2: *ch = 0x2AA1; break;
+ case 0x2AA6: *ch = 0x2AA7; break;
+ case 0x2AA7: *ch = 0x2AA6; break;
+ case 0x2AA8: *ch = 0x2AA9; break;
+ case 0x2AA9: *ch = 0x2AA8; break;
+ case 0x2AAA: *ch = 0x2AAB; break;
+ case 0x2AAB: *ch = 0x2AAA; break;
+ case 0x2AAC: *ch = 0x2AAD; break;
+ case 0x2AAD: *ch = 0x2AAC; break;
+ case 0x2AAF: *ch = 0x2AB0; break;
+ case 0x2AB0: *ch = 0x2AAF; break;
+ case 0x2AB3: *ch = 0x2AB4; break;
+ case 0x2AB4: *ch = 0x2AB3; break;
+ case 0x2ABB: *ch = 0x2ABC; break;
+ case 0x2ABC: *ch = 0x2ABB; break;
+ case 0x2ABD: *ch = 0x2ABE; break;
+ case 0x2ABE: *ch = 0x2ABD; break;
+ case 0x2ABF: *ch = 0x2AC0; break;
+ case 0x2AC0: *ch = 0x2ABF; break;
+ case 0x2AC1: *ch = 0x2AC2; break;
+ case 0x2AC2: *ch = 0x2AC1; break;
+ case 0x2AC3: *ch = 0x2AC4; break;
+ case 0x2AC4: *ch = 0x2AC3; break;
+ case 0x2AC5: *ch = 0x2AC6; break;
+ case 0x2AC6: *ch = 0x2AC5; break;
+ case 0x2ACD: *ch = 0x2ACE; break;
+ case 0x2ACE: *ch = 0x2ACD; break;
+ case 0x2ACF: *ch = 0x2AD0; break;
+ case 0x2AD0: *ch = 0x2ACF; break;
+ case 0x2AD1: *ch = 0x2AD2; break;
+ case 0x2AD2: *ch = 0x2AD1; break;
+ case 0x2AD3: *ch = 0x2AD4; break;
+ case 0x2AD4: *ch = 0x2AD3; break;
+ case 0x2AD5: *ch = 0x2AD6; break;
+ case 0x2AD6: *ch = 0x2AD5; break;
+ case 0x2ADE: *ch = 0x22A6; break;
+ case 0x2AE3: *ch = 0x22A9; break;
+ case 0x2AE4: *ch = 0x22A8; break;
+ case 0x2AE5: *ch = 0x22AB; break;
+ case 0x2AEC: *ch = 0x2AED; break;
+ case 0x2AED: *ch = 0x2AEC; break;
+ case 0x2AF7: *ch = 0x2AF8; break;
+ case 0x2AF8: *ch = 0x2AF7; break;
+ case 0x2AF9: *ch = 0x2AFA; break;
+ case 0x2AFA: *ch = 0x2AF9; break;
+ }
+ } else if ((*ch & 0xFF00) == 0x3000) {
+ switch (*ch) {
+ case 0x3008: *ch = 0x3009; break;
+ case 0x3009: *ch = 0x3008; break;
+ case 0x300A: *ch = 0x300B; break;
+ case 0x300B: *ch = 0x300A; break;
+ case 0x300C: *ch = 0x300D; break;
+ case 0x300D: *ch = 0x300C; break;
+ case 0x300E: *ch = 0x300F; break;
+ case 0x300F: *ch = 0x300E; break;
+ case 0x3010: *ch = 0x3011; break;
+ case 0x3011: *ch = 0x3010; break;
+ case 0x3014: *ch = 0x3015; break;
+ case 0x3015: *ch = 0x3014; break;
+ case 0x3016: *ch = 0x3017; break;
+ case 0x3017: *ch = 0x3016; break;
+ case 0x3018: *ch = 0x3019; break;
+ case 0x3019: *ch = 0x3018; break;
+ case 0x301A: *ch = 0x301B; break;
+ case 0x301B: *ch = 0x301A; break;
+ }
+ } else if ((*ch & 0xFF00) == 0xFF00) {
+ switch (*ch) {
+ case 0xFF08: *ch = 0xFF09; break;
+ case 0xFF09: *ch = 0xFF08; break;
+ case 0xFF1C: *ch = 0xFF1E; break;
+ case 0xFF1E: *ch = 0xFF1C; break;
+ case 0xFF3B: *ch = 0xFF3D; break;
+ case 0xFF3D: *ch = 0xFF3B; break;
+ case 0xFF5B: *ch = 0xFF5D; break;
+ case 0xFF5D: *ch = 0xFF5B; break;
+ case 0xFF5F: *ch = 0xFF60; break;
+ case 0xFF60: *ch = 0xFF5F; break;
+ case 0xFF62: *ch = 0xFF63; break;
+ case 0xFF63: *ch = 0xFF62; break;
+ }
+ }
+}
+
+#ifdef TEST_GETTYPE
+
+#include <stdio.h>
+#include <assert.h>
+
+int main(int argc, char **argv)
+{
+ static const struct { int type; char *name; } typetoname[] = {
+#define TYPETONAME(X) { X , #X }
+ TYPETONAME(L),
+ TYPETONAME(LRE),
+ TYPETONAME(LRO),
+ TYPETONAME(R),
+ TYPETONAME(AL),
+ TYPETONAME(RLE),
+ TYPETONAME(RLO),
+ TYPETONAME(PDF),
+ TYPETONAME(EN),
+ TYPETONAME(ES),
+ TYPETONAME(ET),
+ TYPETONAME(AN),
+ TYPETONAME(CS),
+ TYPETONAME(NSM),
+ TYPETONAME(BN),
+ TYPETONAME(B),
+ TYPETONAME(S),
+ TYPETONAME(WS),
+ TYPETONAME(ON),
+#undef TYPETONAME
+ };
+ int i;
+
+ for (i = 1; i < argc; i++) {
+ unsigned long chr = strtoul(argv[i], NULL, 0);
+ int type = getType(chr);
+ assert(typetoname[type].type == type);
+ printf("U+%04x: %s\n", chr, typetoname[type].name);
+ }
+
+ return 0;
+}
+
+#endif
--- /dev/null
+/*
+ * Platform-independent routines shared between all PuTTY programs.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <limits.h>
+#include <ctype.h>
+#include <assert.h>
+#include "putty.h"
+
+/*
+ * Parse a string block size specification. This is approximately a
+ * subset of the block size specs supported by GNU fileutils:
+ * "nk" = n kilobytes
+ * "nM" = n megabytes
+ * "nG" = n gigabytes
+ * All numbers are decimal, and suffixes refer to powers of two.
+ * Case-insensitive.
+ */
+unsigned long parse_blocksize(const char *bs)
+{
+ char *suf;
+ unsigned long r = strtoul(bs, &suf, 10);
+ if (*suf != '\0') {
+ while (*suf && isspace((unsigned char)*suf)) suf++;
+ switch (*suf) {
+ case 'k': case 'K':
+ r *= 1024ul;
+ break;
+ case 'm': case 'M':
+ r *= 1024ul * 1024ul;
+ break;
+ case 'g': case 'G':
+ r *= 1024ul * 1024ul * 1024ul;
+ break;
+ case '\0':
+ default:
+ break;
+ }
+ }
+ return r;
+}
+
+/*
+ * Parse a ^C style character specification.
+ * Returns NULL in `next' if we didn't recognise it as a control character,
+ * in which case `c' should be ignored.
+ * The precise current parsing is an oddity inherited from the terminal
+ * answerback-string parsing code. All sequences start with ^; all except
+ * ^<123> are two characters. The ones that are worth keeping are probably:
+ * ^? 127
+ * ^@A-Z[\]^_ 0-31
+ * a-z 1-26
+ * <num> specified by number (decimal, 0octal, 0xHEX)
+ * ~ ^ escape
+ */
+char ctrlparse(char *s, char **next)
+{
+ char c = 0;
+ if (*s != '^') {
+ *next = NULL;
+ } else {
+ s++;
+ if (*s == '\0') {
+ *next = NULL;
+ } else if (*s == '<') {
+ s++;
+ c = (char)strtol(s, next, 0);
+ if ((*next == s) || (**next != '>')) {
+ c = 0;
+ *next = NULL;
+ } else
+ (*next)++;
+ } else if (*s >= 'a' && *s <= 'z') {
+ c = (*s - ('a' - 1));
+ *next = s+1;
+ } else if ((*s >= '@' && *s <= '_') || *s == '?' || (*s & 0x80)) {
+ c = ('@' ^ *s);
+ *next = s+1;
+ } else if (*s == '~') {
+ c = '^';
+ *next = s+1;
+ }
+ }
+ return c;
+}
+
+prompts_t *new_prompts(void *frontend)
+{
+ prompts_t *p = snew(prompts_t);
+ p->prompts = NULL;
+ p->n_prompts = 0;
+ p->frontend = frontend;
+ p->data = NULL;
+ 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)
+{
+ prompt_t *pr = snew(prompt_t);
+ char *result = snewn(len, char);
+ pr->prompt = promptstr;
+ pr->echo = echo;
+ pr->result = result;
+ pr->result_len = len;
+ p->n_prompts++;
+ p->prompts = sresize(p->prompts, p->n_prompts, prompt_t *);
+ p->prompts[p->n_prompts-1] = pr;
+}
+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 */
+ sfree(pr->result);
+ sfree(pr->prompt);
+ sfree(pr);
+ }
+ sfree(p->prompts);
+ sfree(p->name);
+ sfree(p->instruction);
+ sfree(p);
+}
+
+/* ----------------------------------------------------------------------
+ * String handling routines.
+ */
+
+char *dupstr(const char *s)
+{
+ char *p = NULL;
+ if (s) {
+ int len = strlen(s);
+ p = snewn(len + 1, char);
+ strcpy(p, s);
+ }
+ return p;
+}
+
+/* Allocate the concatenation of N strings. Terminate arg list with NULL. */
+char *dupcat(const char *s1, ...)
+{
+ int len;
+ char *p, *q, *sn;
+ va_list ap;
+
+ len = strlen(s1);
+ va_start(ap, s1);
+ while (1) {
+ sn = va_arg(ap, char *);
+ if (!sn)
+ break;
+ len += strlen(sn);
+ }
+ va_end(ap);
+
+ p = snewn(len + 1, char);
+ strcpy(p, s1);
+ q = p + strlen(p);
+
+ va_start(ap, s1);
+ while (1) {
+ sn = va_arg(ap, char *);
+ if (!sn)
+ break;
+ strcpy(q, sn);
+ q += strlen(q);
+ }
+ va_end(ap);
+
+ return p;
+}
+
+/*
+ * Do an sprintf(), but into a custom-allocated buffer.
+ *
+ * Currently I'm doing this via vsnprintf. This has worked so far,
+ * but it's not good, because vsnprintf is not available on all
+ * platforms. There's an ifdef to use `_vsnprintf', which seems
+ * to be the local name for it on Windows. Other platforms may
+ * lack it completely, in which case it'll be time to rewrite
+ * this function in a totally different way.
+ *
+ * The only `properly' portable solution I can think of is to
+ * implement my own format string scanner, which figures out an
+ * upper bound for the length of each formatting directive,
+ * allocates the buffer as it goes along, and calls sprintf() to
+ * actually process each directive. If I ever need to actually do
+ * this, some caveats:
+ *
+ * - It's very hard to find a reliable upper bound for
+ * floating-point values. %f, in particular, when supplied with
+ * a number near to the upper or lower limit of representable
+ * numbers, could easily take several hundred characters. It's
+ * probably feasible to predict this statically using the
+ * constants in <float.h>, or even to predict it dynamically by
+ * looking at the exponent of the specific float provided, but
+ * it won't be fun.
+ *
+ * - Don't forget to _check_, after calling sprintf, that it's
+ * used at most the amount of space we had available.
+ *
+ * - Fault any formatting directive we don't fully understand. The
+ * aim here is to _guarantee_ that we never overflow the buffer,
+ * because this is a security-critical function. If we see a
+ * directive we don't know about, we should panic and die rather
+ * than run any risk.
+ */
+char *dupprintf(const char *fmt, ...)
+{
+ char *ret;
+ va_list ap;
+ va_start(ap, fmt);
+ ret = dupvprintf(fmt, ap);
+ va_end(ap);
+ return ret;
+}
+char *dupvprintf(const char *fmt, va_list ap)
+{
+ char *buf;
+ int len, size;
+
+ buf = snewn(512, char);
+ size = 512;
+
+ while (1) {
+#ifdef _WINDOWS
+#define vsnprintf _vsnprintf
+#endif
+#ifdef va_copy
+ /* Use the `va_copy' macro mandated by C99, if present.
+ * XXX some environments may have this as __va_copy() */
+ va_list aq;
+ va_copy(aq, ap);
+ len = vsnprintf(buf, size, fmt, aq);
+ va_end(aq);
+#else
+ /* Ugh. No va_copy macro, so do something nasty.
+ * Technically, you can't reuse a va_list like this: it is left
+ * unspecified whether advancing a va_list pointer modifies its
+ * value or something it points to, so on some platforms calling
+ * vsnprintf twice on the same va_list might fail hideously
+ * (indeed, it has been observed to).
+ * XXX the autoconf manual suggests that using memcpy() will give
+ * "maximum portability". */
+ len = vsnprintf(buf, size, fmt, ap);
+#endif
+ if (len >= 0 && len < size) {
+ /* This is the C99-specified criterion for snprintf to have
+ * been completely successful. */
+ return buf;
+ } else if (len > 0) {
+ /* This is the C99 error condition: the returned length is
+ * the required buffer size not counting the NUL. */
+ size = len + 1;
+ } else {
+ /* This is the pre-C99 glibc error condition: <0 means the
+ * buffer wasn't big enough, so we enlarge it a bit and hope. */
+ size += 512;
+ }
+ buf = sresize(buf, size, char);
+ }
+}
+
+/*
+ * Read an entire line of text from a file. Return a buffer
+ * malloced to be as big as necessary (caller must free).
+ */
+char *fgetline(FILE *fp)
+{
+ char *ret = snewn(512, char);
+ int size = 512, len = 0;
+ while (fgets(ret + len, size - len, fp)) {
+ len += strlen(ret + len);
+ if (ret[len-1] == '\n')
+ break; /* got a newline, we're done */
+ size = len + 512;
+ ret = sresize(ret, size, char);
+ }
+ if (len == 0) { /* first fgets returned NULL */
+ sfree(ret);
+ return NULL;
+ }
+ ret[len] = '\0';
+ return ret;
+}
+
+/* ----------------------------------------------------------------------
+ * Base64 encoding routine. This is required in public-key writing
+ * but also in HTTP proxy handling, so it's centralised here.
+ */
+
+void base64_encode_atom(unsigned char *data, int n, char *out)
+{
+ static const char base64_chars[] =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+ unsigned word;
+
+ word = data[0] << 16;
+ if (n > 1)
+ word |= data[1] << 8;
+ if (n > 2)
+ word |= data[2];
+ out[0] = base64_chars[(word >> 18) & 0x3F];
+ out[1] = base64_chars[(word >> 12) & 0x3F];
+ if (n > 1)
+ out[2] = base64_chars[(word >> 6) & 0x3F];
+ else
+ out[2] = '=';
+ if (n > 2)
+ out[3] = base64_chars[word & 0x3F];
+ else
+ out[3] = '=';
+}
+
+/* ----------------------------------------------------------------------
+ * Generic routines to deal with send buffers: a linked list of
+ * smallish blocks, with the operations
+ *
+ * - add an arbitrary amount of data to the end of the list
+ * - remove the first N bytes from the list
+ * - return a (pointer,length) pair giving some initial data in
+ * the list, suitable for passing to a send or write system
+ * 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
+
+struct bufchain_granule {
+ struct bufchain_granule *next;
+ int buflen, bufpos;
+ char buf[BUFFER_GRANULE];
+};
+
+void bufchain_init(bufchain *ch)
+{
+ ch->head = ch->tail = NULL;
+ ch->buffersize = 0;
+}
+
+void bufchain_clear(bufchain *ch)
+{
+ struct bufchain_granule *b;
+ while (ch->head) {
+ b = ch->head;
+ ch->head = ch->head->next;
+ sfree(b);
+ }
+ ch->tail = NULL;
+ ch->buffersize = 0;
+}
+
+int bufchain_size(bufchain *ch)
+{
+ return ch->buffersize;
+}
+
+void bufchain_add(bufchain *ch, const void *data, int len)
+{
+ const char *buf = (const char *)data;
+
+ 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;
+ }
+}
+
+void bufchain_consume(bufchain *ch, int len)
+{
+ struct bufchain_granule *tmp;
+
+ 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;
+ tmp = ch->head;
+ ch->head = tmp->next;
+ sfree(tmp);
+ if (!ch->head)
+ ch->tail = NULL;
+ } 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;
+}
+
+void bufchain_fetch(bufchain *ch, void *data, int len)
+{
+ struct bufchain_granule *tmp;
+ char *data_c = (char *)data;
+
+ tmp = ch->head;
+
+ 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);
+
+ tmp = tmp->next;
+ len -= remlen;
+ data_c += remlen;
+ }
+}
+
+/* ----------------------------------------------------------------------
+ * My own versions of malloc, realloc and free. Because I want
+ * malloc and realloc to bomb out and exit the program if they run
+ * out of memory, realloc to reliably call malloc if passed a NULL
+ * pointer, and free to reliably do nothing if passed a NULL
+ * pointer. We can also put trace printouts in, if we need to; and
+ * we can also replace the allocator with an ElectricFence-like
+ * one.
+ */
+
+#ifdef MINEFIELD
+void *minefield_c_malloc(size_t size);
+void minefield_c_free(void *p);
+void *minefield_c_realloc(void *p, size_t size);
+#endif
+
+#ifdef MALLOC_LOG
+static FILE *fp = NULL;
+
+static char *mlog_file = NULL;
+static int mlog_line = 0;
+
+void mlog(char *file, int line)
+{
+ mlog_file = file;
+ mlog_line = line;
+ if (!fp) {
+ fp = fopen("putty_mem.log", "w");
+ setvbuf(fp, NULL, _IONBF, BUFSIZ);
+ }
+ if (fp)
+ fprintf(fp, "%s:%d: ", file, line);
+}
+#endif
+
+void *safemalloc(size_t n, size_t size)
+{
+ void *p;
+
+ if (n > INT_MAX / size) {
+ p = NULL;
+ } else {
+ size *= n;
+ if (size == 0) size = 1;
+#ifdef MINEFIELD
+ p = minefield_c_malloc(size);
+#else
+ p = malloc(size);
+#endif
+ }
+
+ if (!p) {
+ char str[200];
+#ifdef MALLOC_LOG
+ sprintf(str, "Out of memory! (%s:%d, size=%d)",
+ mlog_file, mlog_line, size);
+ fprintf(fp, "*** %s\n", str);
+ fclose(fp);
+#else
+ strcpy(str, "Out of memory!");
+#endif
+ modalfatalbox(str);
+ }
+#ifdef MALLOC_LOG
+ if (fp)
+ fprintf(fp, "malloc(%d) returns %p\n", size, p);
+#endif
+ return p;
+}
+
+void *saferealloc(void *ptr, size_t n, size_t size)
+{
+ void *p;
+
+ if (n > INT_MAX / size) {
+ p = NULL;
+ } else {
+ size *= n;
+ if (!ptr) {
+#ifdef MINEFIELD
+ p = minefield_c_malloc(size);
+#else
+ p = malloc(size);
+#endif
+ } else {
+#ifdef MINEFIELD
+ p = minefield_c_realloc(ptr, size);
+#else
+ p = realloc(ptr, size);
+#endif
+ }
+ }
+
+ if (!p) {
+ char str[200];
+#ifdef MALLOC_LOG
+ sprintf(str, "Out of memory! (%s:%d, size=%d)",
+ mlog_file, mlog_line, size);
+ fprintf(fp, "*** %s\n", str);
+ fclose(fp);
+#else
+ strcpy(str, "Out of memory!");
+#endif
+ modalfatalbox(str);
+ }
+#ifdef MALLOC_LOG
+ if (fp)
+ fprintf(fp, "realloc(%p,%d) returns %p\n", ptr, size, p);
+#endif
+ return p;
+}
+
+void safefree(void *ptr)
+{
+ if (ptr) {
+#ifdef MALLOC_LOG
+ if (fp)
+ fprintf(fp, "free(%p)\n", ptr);
+#endif
+#ifdef MINEFIELD
+ minefield_c_free(ptr);
+#else
+ free(ptr);
+#endif
+ }
+#ifdef MALLOC_LOG
+ else if (fp)
+ fprintf(fp, "freeing null pointer - no action taken\n");
+#endif
+}
+
+/* ----------------------------------------------------------------------
+ * Debugging routines.
+ */
+
+#ifdef DEBUG
+extern void dputs(char *); /* defined in per-platform *misc.c */
+
+void debug_printf(char *fmt, ...)
+{
+ char *buf;
+ va_list ap;
+
+ va_start(ap, fmt);
+ buf = dupvprintf(fmt, ap);
+ dputs(buf);
+ sfree(buf);
+ va_end(ap);
+}
+
+
+void debug_memdump(void *buf, int len, int L)
+{
+ int i;
+ unsigned char *p = buf;
+ char foo[17];
+ if (L) {
+ int delta;
+ debug_printf("\t%d (0x%x) bytes:\n", len, len);
+ delta = 15 & (unsigned long int) p;
+ p -= delta;
+ len += delta;
+ }
+ for (; 0 < len; p += 16, len -= 16) {
+ dputs(" ");
+ if (L)
+ debug_printf("%p: ", p);
+ strcpy(foo, "................"); /* sixteen dots */
+ for (i = 0; i < 16 && i < len; ++i) {
+ if (&p[i] < (unsigned char *) buf) {
+ dputs(" "); /* 3 spaces */
+ foo[i] = ' ';
+ } else {
+ debug_printf("%c%02.2x",
+ &p[i] != (unsigned char *) buf
+ && i % 4 ? '.' : ' ', p[i]
+ );
+ if (p[i] >= ' ' && p[i] <= '~')
+ foo[i] = (char) p[i];
+ }
+ }
+ foo[i] = '\0';
+ debug_printf("%*s%s\n", (16 - i) * 3 + 2, "", foo);
+ }
+}
+
+#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;
+}
--- /dev/null
+/*
+ * Header for misc.c.
+ */
+
+#ifndef PUTTY_MISC_H
+#define PUTTY_MISC_H
+
+#include "puttymem.h"
+
+#include <stdio.h> /* for FILE * */
+#include <stdarg.h> /* for va_list */
+#include <time.h> /* for struct tm */
+
+#ifndef FALSE
+#define FALSE 0
+#endif
+#ifndef TRUE
+#define TRUE 1
+#endif
+
+typedef struct Filename Filename;
+typedef struct FontSpec FontSpec;
+
+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 *dupvprintf(const char *fmt, va_list ap);
+
+char *fgetline(FILE *fp);
+
+void base64_encode_atom(unsigned char *data, int n, char *out);
+
+struct bufchain_granule;
+typedef struct bufchain_tag {
+ struct bufchain_granule *head, *tail;
+ int buffersize; /* current amount of buffered data */
+} bufchain;
+
+void bufchain_init(bufchain *ch);
+void bufchain_clear(bufchain *ch);
+int bufchain_size(bufchain *ch);
+void bufchain_add(bufchain *ch, const void *data, int len);
+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);
+
+/*
+ * Debugging functions.
+ *
+ * Output goes to debug.log
+ *
+ * debug(()) (note the double brackets) is like printf().
+ *
+ * dmemdump() and dmemdumpl() both do memory dumps. The difference
+ * is that dmemdumpl() is more suited for when the memory address is
+ * important (say because you'll be recording pointer values later
+ * on). dmemdump() is more concise.
+ */
+
+#ifdef DEBUG
+void debug_printf(char *fmt, ...);
+void debug_memdump(void *buf, int len, int L);
+#define debug(x) (debug_printf x)
+#define dmemdump(buf,len) debug_memdump (buf, len, 0);
+#define dmemdumpl(buf,len) debug_memdump (buf, len, 1);
+#else
+#define debug(x)
+#define dmemdump(buf,len)
+#define dmemdumpl(buf,len)
+#endif
+
+#ifndef lenof
+#define lenof(x) ( (sizeof((x))) / (sizeof(*(x))))
+#endif
+
+#ifndef min
+#define min(x,y) ( (x) < (y) ? (x) : (y) )
+#endif
+#ifndef max
+#define max(x,y) ( (x) > (y) ? (x) : (y) )
+#endif
+
+#define GET_32BIT_LSB_FIRST(cp) \
+ (((unsigned long)(unsigned char)(cp)[0]) | \
+ ((unsigned long)(unsigned char)(cp)[1] << 8) | \
+ ((unsigned long)(unsigned char)(cp)[2] << 16) | \
+ ((unsigned long)(unsigned char)(cp)[3] << 24))
+
+#define PUT_32BIT_LSB_FIRST(cp, value) ( \
+ (cp)[0] = (unsigned char)(value), \
+ (cp)[1] = (unsigned char)((value) >> 8), \
+ (cp)[2] = (unsigned char)((value) >> 16), \
+ (cp)[3] = (unsigned char)((value) >> 24) )
+
+#define GET_16BIT_LSB_FIRST(cp) \
+ (((unsigned long)(unsigned char)(cp)[0]) | \
+ ((unsigned long)(unsigned char)(cp)[1] << 8))
+
+#define PUT_16BIT_LSB_FIRST(cp, value) ( \
+ (cp)[0] = (unsigned char)(value), \
+ (cp)[1] = (unsigned char)((value) >> 8) )
+
+#define GET_32BIT_MSB_FIRST(cp) \
+ (((unsigned long)(unsigned char)(cp)[0] << 24) | \
+ ((unsigned long)(unsigned char)(cp)[1] << 16) | \
+ ((unsigned long)(unsigned char)(cp)[2] << 8) | \
+ ((unsigned long)(unsigned char)(cp)[3]))
+
+#define GET_32BIT(cp) GET_32BIT_MSB_FIRST(cp)
+
+#define PUT_32BIT_MSB_FIRST(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) )
+
+#define PUT_32BIT(cp, value) PUT_32BIT_MSB_FIRST(cp, value)
+
+#define GET_16BIT_MSB_FIRST(cp) \
+ (((unsigned long)(unsigned char)(cp)[0] << 8) | \
+ ((unsigned long)(unsigned char)(cp)[1]))
+
+#define PUT_16BIT_MSB_FIRST(cp, value) ( \
+ (cp)[0] = (unsigned char)((value) >> 8), \
+ (cp)[1] = (unsigned char)(value) )
+
+#endif
--- /dev/null
+#! /bin/sh
+# This script makes the autoconf mechanism for the Unix port work.
+# It's separate from mkfiles.pl because it won't work (and isn't needed)
+# on a non-Unix system.
+
+# 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 <stdio.h>
+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)
--- /dev/null
+#!/usr/bin/env perl
+#
+# Cross-platform Makefile generator.
+#
+# Reads the file `Recipe' to determine the list of generated
+# executables and their component objects. Then reads the source
+# files to compute #include dependencies. Finally, writes out the
+# various target Makefiles.
+
+# PuTTY specifics which could still do with removing:
+# - Mac makefile is not portabilised at all. Include directories
+# are hardwired, and also the libraries are fixed. This is
+# mainly because I was too scared to go anywhere near it.
+# - sbcsgen.pl is still run at startup.
+#
+# FIXME: no attempt made to handle !forceobj in the project files.
+
+use warnings;
+use FileHandle;
+use Cwd;
+
+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.
+ chdir "..";
+ open IN, "Recipe" or die "unable to open Recipe file\n";
+};
+
+# 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 ".."';
+
+@srcdirs = ("./");
+
+$divert = undef; # ref to scalar in which text is currently being put
+$help = ""; # list of newline-free lines of help text
+$project_name = "project"; # this is a good enough default
+%makefiles = (); # maps makefile types to output makefile pathnames
+%makefile_extra = (); # maps makefile types to extra Makefile text
+%programs = (); # maps prog name + type letter to listref of objects/resources
+%groups = (); # maps group name to listref of objects/resources
+
+while (<IN>) {
+ chomp;
+ @_ = split;
+
+ # If we're gathering help text, keep doing so.
+ if (defined $divert) {
+ if ((defined $_[0]) && $_[0] eq "!end") {
+ $divert = undef;
+ } else {
+ ${$divert} .= "$_\n";
+ }
+ next;
+ }
+ # Skip comments and blank lines.
+ next if /^\s*#/ or scalar @_ == 0;
+
+ if ($_[0] eq "!begin" and $_[1] eq "help") { $divert = \$help; next; }
+ 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 "!forceobj") { $forceobj{$_[1]} = 1; next; }
+ if ($_[0] eq "!begin") {
+ if (&mfval($_[1])) {
+ $sect = $_[2] ? $_[2] : "end";
+ $divert = \($makefile_extra{$_[1]}->{$sect});
+ } else {
+ $dummy = '';
+ $divert = \$dummy;
+ }
+ next;
+ }
+ # If we're gathering help/verbatim text, keep doing so.
+ if (defined $divert) { ${$divert} .= "$_\n"; next; }
+ # Ignore blank lines.
+ next if scalar @_ == 0;
+
+ # Now we have an ordinary line. See if it's an = line, a : line
+ # or a + line.
+ @objs = @_;
+
+ if ($_[0] eq "+") {
+ $listref = $lastlistref;
+ $prog = undef;
+ die "$.: unexpected + line\n" if !defined $lastlistref;
+ } elsif ($_[1] eq "=") {
+ $groups{$_[0]} = [] if !defined $groups{$_[0]};
+ $listref = $groups{$_[0]};
+ $prog = undef;
+ shift @objs; # eat the group name
+ } elsif ($_[1] eq ":") {
+ $listref = [];
+ $prog = $_[0];
+ shift @objs; # eat the program name
+ } else {
+ die "$.: unrecognised line type\n";
+ }
+ shift @objs; # eat the +, the = or the :
+
+ while (scalar @objs > 0) {
+ $i = shift @objs;
+ if ($groups{$i}) {
+ foreach $j (@{$groups{$i}}) { unshift @objs, $j; }
+ } elsif (($i eq "[G]" or $i eq "[C]" or $i eq "[M]" or
+ $i eq "[X]" or $i eq "[U]" or $i eq "[MX]") and defined $prog) {
+ $type = substr($i,1,(length $i)-2);
+ } else {
+ push @$listref, $i;
+ }
+ }
+ if ($prog and $type) {
+ die "multiple program entries for $prog [$type]\n"
+ if defined $programs{$prog . "," . $type};
+ $programs{$prog . "," . $type} = $listref;
+ }
+ $lastlistref = $listref;
+}
+
+close IN;
+
+# 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;
+%depends = ();
+@scanlist = ();
+foreach $i (@prognames) {
+ ($prog, $type) = split ",", $i;
+ # Strip duplicate object names.
+ $prev = '';
+ @list = grep { $status = ($prev ne $_); $prev=$_; $status }
+ sort @{$programs{$i}};
+ $programs{$i} = [@list];
+ foreach $j (@list) {
+ # Dependencies for "x" start with "x.c" or "x.m" (depending on
+ # which one exists).
+ # Dependencies for "x.res" start with "x.rc".
+ # Dependencies for "x.rsrc" start with "x.r".
+ # Both types of file are pushed on the list of files to scan.
+ # Libraries (.lib) don't have dependencies at all.
+ if ($j =~ /^(.*)\.res$/) {
+ $file = "$1.rc";
+ $depends{$j} = [$file];
+ push @scanlist, $file;
+ } elsif ($j =~ /^(.*)\.rsrc$/) {
+ $file = "$1.r";
+ $depends{$j} = [$file];
+ push @scanlist, $file;
+ } elsif ($j !~ /\./) {
+ $file = "$j.c";
+ $file = "$j.m" unless &findfile($file);
+ $depends{$j} = [$file];
+ push @scanlist, $file;
+ }
+ }
+}
+
+# Scan each file on @scanlist and find further inclusions.
+# Inclusions are given by lines of the form `#include "otherfile"'
+# (system headers are automatically ignored by this because they'll
+# be given in angle brackets). Files included by this method are
+# added back on to @scanlist to be scanned in turn (if not already
+# done).
+#
+# Resource scripts (.rc) can also include a file by means of:
+# - a line # ending `ICON "filename"';
+# - a line ending `RT_MANIFEST "filename"'.
+# Files included by this method are not added to @scanlist because
+# they can never include further files.
+#
+# In this pass we write out a hash %further which maps a source
+# file name into a listref containing further source file names.
+
+%further = ();
+while (scalar @scanlist > 0) {
+ $file = shift @scanlist;
+ next if defined $further{$file}; # skip if we've already done it
+ $further{$file} = [];
+ $dirfile = &findfile($file);
+ open IN, "$dirfile" or die "unable to open source file $file\n";
+ while (<IN>) {
+ chomp;
+ /^\s*#include\s+\"([^\"]+)\"/ and do {
+ push @{$further{$file}}, $1;
+ push @scanlist, $1;
+ next;
+ };
+ /(RT_MANIFEST|ICON)\s+\"([^\"]+)\"\s*$/ and do {
+ push @{$further{$file}}, $2;
+ next;
+ }
+ }
+ close IN;
+}
+
+# Now we're ready to generate the final dependencies section. For
+# each key in %depends, we must expand the dependencies list by
+# iteratively adding entries from %further.
+foreach $i (keys %depends) {
+ %dep = ();
+ @scanlist = @{$depends{$i}};
+ foreach $i (@scanlist) { $dep{$i} = 1; }
+ while (scalar @scanlist > 0) {
+ $file = shift @scanlist;
+ foreach $j (@{$further{$file}}) {
+ if (!$dep{$j}) {
+ $dep{$j} = 1;
+ push @{$depends{$i}}, $j;
+ push @scanlist, $j;
+ }
+ }
+ }
+# printf "%s: %s\n", $i, join ' ',@{$depends{$i}};
+}
+
+# Validation of input.
+
+sub mfval($) {
+ 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",)) {
+ return 1;
+ }
+ warn "$.:unknown makefile type '$type'\n";
+ return 0;
+}
+
+# Utility routines while writing out the Makefiles.
+
+sub def {
+ my ($x) = shift @_;
+ return (defined $x) ? $x : "";
+}
+
+sub dirpfx {
+ my ($path) = shift @_;
+ my ($sep) = shift @_;
+ my $ret = "";
+ my $i;
+
+ while (($i = index $path, $sep) >= 0 ||
+ ($j = index $path, "/") >= 0) {
+ if ($i >= 0 and ($j < 0 or $i < $j)) {
+ $path = substr $path, ($i + length $sep);
+ } else {
+ $path = substr $path, ($j + 1);
+ }
+ $ret .= "..$sep";
+ }
+ return $ret;
+}
+
+sub findfile {
+ my ($name) = @_;
+ my $dir = '';
+ my $i;
+ my $outdir = undef;
+ unless (defined $findfilecache{$name}) {
+ $i = 0;
+ foreach $dir (@srcdirs) {
+ if (-f "$dir$name") {
+ $outdir = $dir;
+ $i++;
+ $outdir =~ s/^\.\///;
+ }
+ }
+ die "multiple instances of source file $name\n" if $i > 1;
+ $findfilecache{$name} = (defined $outdir ? $outdir . $name : undef);
+ }
+ return $findfilecache{$name};
+}
+
+sub objects {
+ my ($prog, $otmpl, $rtmpl, $ltmpl, $prefix, $dirsep) = @_;
+ my @ret;
+ my ($i, $x, $y);
+ ($otmpl, $rtmpl, $ltmpl) = map { defined $_ ? $_ : "" } ($otmpl, $rtmpl, $ltmpl);
+ @ret = ();
+ foreach $i (@{$programs{$prog}}) {
+ $x = "";
+ if ($i =~ /^(.*)\.(res|rsrc)/) {
+ $y = $1;
+ ($x = $rtmpl) =~ s/X/$y/;
+ } elsif ($i =~ /^(.*)\.lib/) {
+ $y = $1;
+ ($x = $ltmpl) =~ s/X/$y/;
+ } elsif ($i !~ /\./) {
+ ($x = $otmpl) =~ s/X/$i/;
+ }
+ push @ret, $x if $x ne "";
+ }
+ return join " ", @ret;
+}
+
+sub special {
+ my ($prog, $suffix) = @_;
+ my @ret;
+ my ($i, $x, $y);
+ ($otmpl, $rtmpl, $ltmpl) = map { defined $_ ? $_ : "" } ($otmpl, $rtmpl, $ltmpl);
+ @ret = ();
+ foreach $i (@{$programs{$prog}}) {
+ if (substr($i, (length $i) - (length $suffix)) eq $suffix) {
+ push @ret, $i;
+ }
+ }
+ return (scalar @ret) ? (join " ", @ret) : undef;
+}
+
+sub splitline {
+ my ($line, $width, $splitchar) = @_;
+ my $result = "";
+ my $len;
+ $len = (defined $width ? $width : 76);
+ $splitchar = (defined $splitchar ? $splitchar : '\\');
+ while (length $line > $len) {
+ $line =~ /^(.{0,$len})\s(.*)$/ or $line =~ /^(.{$len,}?\s(.*)$/;
+ $result .= $1;
+ $result .= " ${splitchar}\n\t\t" if $2 ne '';
+ $line = $2;
+ $len = 60;
+ }
+ return $result . $line;
+}
+
+sub deps {
+ my ($otmpl, $rtmpl, $prefix, $dirsep, $mftyp, $depchar, $splitchar) = @_;
+ my ($i, $x, $y);
+ my @deps;
+ my @ret;
+ @ret = ();
+ $depchar ||= ':';
+ foreach $i (sort keys %depends) {
+ next if $specialobj{$mftyp}->{$i};
+ if ($i =~ /^(.*)\.(res|rsrc)/) {
+ next if !defined $rtmpl;
+ $y = $1;
+ ($x = $rtmpl) =~ s/X/$y/;
+ } else {
+ ($x = $otmpl) =~ s/X/$i/;
+ }
+ @deps = @{$depends{$i}};
+ @deps = map {
+ $_ = &findfile($_);
+ s/\//$dirsep/g;
+ $_ = $prefix . $_;
+ } @deps;
+ push @ret, {obj => $x, obj_orig => $i, deps => [@deps]};
+ }
+ return @ret;
+}
+
+sub prognames {
+ my ($types) = @_;
+ my ($n, $prog, $type);
+ my @ret;
+ @ret = ();
+ foreach $n (@prognames) {
+ ($prog, $type) = split ",", $n;
+ push @ret, $n if index(":$types:", ":$type:") >= 0;
+ }
+ return @ret;
+}
+
+sub progrealnames {
+ my ($types) = @_;
+ my ($n, $prog, $type);
+ my @ret;
+ @ret = ();
+ foreach $n (@prognames) {
+ ($prog, $type) = split ",", $n;
+ push @ret, $prog if index(":$types:", ":$type:") >= 0;
+ }
+ return @ret;
+}
+
+sub manpages {
+ my ($types,$suffix) = @_;
+
+ # assume that all UNIX programs have a man page
+ if($suffix eq "1" && $types =~ /:X:/) {
+ return map("$_.1", &progrealnames($types));
+ }
+ return ();
+}
+
+# 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".
+ "#\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".
+ "# You can define this path to point at your tools if you need to\n".
+ "# TOOLPATH = c:\\cygwin\\bin\\ # or similar, if you're running Windows\n".
+ "# TOOLPATH = /pkg/mingw32msvc/i386-mingw32msvc/bin/\n".
+ "CC = \$(TOOLPATH)gcc\n".
+ "RC = \$(TOOLPATH)windres\n".
+ "# Uncomment the following two lines to compile under Winelib\n".
+ "# CC = winegcc\n".
+ "# 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 " .
+ (join " ", map {"-I$dirpfx$_"} @srcdirs)) .
+ "\n".
+ "LDFLAGS = -mno-cygwin -s\n".
+ &splitline("RCFLAGS = \$(RCINC) --define WIN32=1 --define _WIN32=1".
+ " --define WINVER=0x0400")."\n".
+ "\n".
+ $makefile_extra{'cygwin'}->{'vars'} .
+ "\n".
+ ".SUFFIXES:\n".
+ "\n";
+ print &splitline("all:" . join "", map { " $_.exe" } &progrealnames("G:C"));
+ print "\n\n";
+ foreach $p (&prognames("G:C")) {
+ ($prog, $type) = split ",", $p;
+ $objstr = &objects($p, "X.o", "X.res.o", undef);
+ print &splitline($prog . ".exe: " . $objstr), "\n";
+ my $mw = $type eq "G" ? " -mwindows" : "";
+ $libstr = &objects($p, undef, undef, "-lX");
+ print &splitline("\t\$(CC)" . $mw . " \$(LDFLAGS) -o \$@ " .
+ "-Wl,-Map,$prog.map " .
+ $objstr . " $libstr", 69), "\n\n";
+ }
+ foreach $d (&deps("X.o", "X.res.o", $dirpfx, "/", "cygwin")) {
+ if ($forceobj{$d->{obj_orig}}) {
+ printf ("%s: FORCE\n", $d->{obj});
+ } 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";
+ } 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".
+ "\n".
+ "FORCE:\n";
+ select STDOUT; close OUT;
+
+}
+
+##-- Borland makefile
+if (defined $makefiles{'borland'}) {
+ $dirpfx = &dirpfx($makefiles{'borland'}, "\\");
+
+ %stdlibs = ( # Borland provides many Win32 API libraries intrinsically
+ "advapi32" => 1,
+ "comctl32" => 1,
+ "comdlg32" => 1,
+ "gdi32" => 1,
+ "imm32" => 1,
+ "shell32" => 1,
+ "user32" => 1,
+ "winmm" => 1,
+ "winspool" => 1,
+ "wsock32" => 1,
+ );
+ open OUT, ">$makefiles{'borland'}"; select OUT;
+ print
+ "# Makefile for $project_name under Borland C.\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";
+ # bcc32 command line option is -D not /D
+ ($_ = $help) =~ s/([=" ])\/D/$1-D/gs;
+ print $_;
+ print
+ "\n".
+ "# If you rename this file to `Makefile', you should change this line,\n".
+ "# so that the .rsp files still depend on the correct makefile.\n".
+ "MAKEFILE = Makefile.bor\n".
+ "\n".
+ "# C compilation flags\n".
+ "CFLAGS = -D_WINDOWS -DWINVER=0x0500\n".
+ "# Resource compilation flags\n".
+ "RCFLAGS = -DNO_WINRESRC_H -DWIN32 -D_WIN32 -DWINVER=0x0401\n".
+ "\n".
+ "# Get include directory for resource compiler\n".
+ "!if !\$d(BCB)\n".
+ "BCB = \$(MAKEDIR)\\..\n".
+ "!endif\n".
+ "\n".
+ $makefile_extra{'borland'}->{'vars'} .
+ "\n".
+ ".c.obj:\n".
+ &splitline("\tbcc32 -w-aus -w-ccc -w-par -w-pia \$(COMPAT)".
+ " \$(CFLAGS) \$(XFLAGS) ".
+ (join " ", map {"-I$dirpfx$_"} @srcdirs) .
+ " /c \$*.c",69)."\n".
+ ".rc.res:\n".
+ &splitline("\tbrcc32 \$(RCFL) -i \$(BCB)\\include -r".
+ " \$(RCFLAGS) \$*.rc",69)."\n".
+ "\n";
+ print &splitline("all:" . join "", map { " $_.exe" } &progrealnames("G:C"));
+ print "\n\n";
+ foreach $p (&prognames("G:C")) {
+ ($prog, $type) = split ",", $p;
+ $objstr = &objects($p, "X.obj", "X.res", undef);
+ print &splitline("$prog.exe: " . $objstr . " $prog.rsp"), "\n";
+ my $ap = ($type eq "G") ? "-aa" : "-ap";
+ print "\tilink32 $ap -Gn -L\$(BCB)\\lib \@$prog.rsp\n\n";
+ }
+ foreach $p (&prognames("G:C")) {
+ ($prog, $type) = split ",", $p;
+ print $prog, ".rsp: \$(MAKEFILE)\n";
+ $objstr = &objects($p, "X.obj", undef, undef);
+ @objlist = split " ", $objstr;
+ @objlines = ("");
+ foreach $i (@objlist) {
+ if (length($objlines[$#objlines] . " $i") > 50) {
+ push @objlines, "";
+ }
+ $objlines[$#objlines] .= " $i";
+ }
+ $c0w = ($type eq "G") ? "c0w32" : "c0x32";
+ print "\techo $c0w + > $prog.rsp\n";
+ for ($i=0; $i<=$#objlines; $i++) {
+ $plus = ($i < $#objlines ? " +" : "");
+ print "\techo$objlines[$i]$plus >> $prog.rsp\n";
+ }
+ print "\techo $prog.exe >> $prog.rsp\n";
+ $objstr = &objects($p, "X.obj", "X.res", undef);
+ @libs = split " ", &objects($p, undef, undef, "X");
+ @libs = grep { !$stdlibs{$_} } @libs;
+ unshift @libs, "cw32", "import32";
+ $libstr = join ' ', @libs;
+ print "\techo nul,$libstr, >> $prog.rsp\n";
+ print "\techo " . &objects($p, undef, "X.res", undef) . " >> $prog.rsp\n";
+ print "\n";
+ }
+ foreach $d (&deps("X.obj", "X.res", $dirpfx, "\\", "borland")) {
+ if ($forceobj{$d->{obj_orig}}) {
+ printf("%s: FORCE\n", $d->{obj});
+ } else {
+ print &splitline(sprintf("%s: %s", $d->{obj},
+ join " ", @{$d->{deps}})), "\n";
+ }
+ }
+ print "\n";
+ print $makefile_extra{'borland'}->{'end'};
+ print "\nclean:\n".
+ "\t-del *.obj\n".
+ "\t-del *.exe\n".
+ "\t-del *.res\n".
+ "\t-del *.pch\n".
+ "\t-del *.aps\n".
+ "\t-del *.il*\n".
+ "\t-del *.pdb\n".
+ "\t-del *.rsp\n".
+ "\t-del *.tds\n".
+ "\t-del *.\$\$\$\$\$\$\n".
+ "\n".
+ "FORCE:\n".
+ "\t-rem dummy command\n";
+ select STDOUT; close OUT;
+}
+
+if (defined $makefiles{'vc'}) {
+ $dirpfx = &dirpfx($makefiles{'vc'}, "\\");
+
+ ##-- Visual C++ makefile
+ open OUT, ">$makefiles{'vc'}"; select OUT;
+ print
+ "# Makefile for $project_name under Visual C.\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";
+ print $help;
+ print
+ "\n".
+ "# If you rename this file to `Makefile', you should change this line,\n".
+ "# so that the .rsp files still depend on the correct makefile.\n".
+ "MAKEFILE = Makefile.vc\n".
+ "\n".
+ "# C compilation flags\n".
+ "CFLAGS = /nologo /W3 /O1 " .
+ (join " ", map {"-I$dirpfx$_"} @srcdirs) .
+ " /D_WINDOWS /D_WIN32_WINDOWS=0x500 /DWINVER=0x500\n".
+ "LFLAGS = /incremental:no /fixed\n".
+ "RCFLAGS = -DWIN32 -D_WIN32 -DWINVER=0x0400\n".
+ "\n".
+ $makefile_extra{'vc'}->{'vars'} .
+ "\n".
+ "\n";
+ print &splitline("all:" . join "", map { " $_.exe" } &progrealnames("G:C"));
+ print "\n\n";
+ foreach $p (&prognames("G:C")) {
+ ($prog, $type) = split ",", $p;
+ $objstr = &objects($p, "X.obj", "X.res", undef);
+ print &splitline("$prog.exe: " . $objstr . " $prog.rsp"), "\n";
+ print "\tlink \$(LFLAGS) \$(XLFLAGS) -out:$prog.exe -map:$prog.map \@$prog.rsp\n\n";
+ }
+ foreach $p (&prognames("G:C")) {
+ ($prog, $type) = split ",", $p;
+ print $prog, ".rsp: \$(MAKEFILE)\n";
+ $objstr = &objects($p, "X.obj", "X.res", "X.lib");
+ @objlist = split " ", $objstr;
+ @objlines = ("");
+ foreach $i (@objlist) {
+ if (length($objlines[$#objlines] . " $i") > 50) {
+ push @objlines, "";
+ }
+ $objlines[$#objlines] .= " $i";
+ }
+ $subsys = ($type eq "G") ? "windows" : "console";
+ print "\techo /nologo /subsystem:$subsys > $prog.rsp\n";
+ for ($i=0; $i<=$#objlines; $i++) {
+ print "\techo$objlines[$i] >> $prog.rsp\n";
+ }
+ print "\n";
+ }
+ foreach $d (&deps("X.obj", "X.res", $dirpfx, "\\", "vc")) {
+ $extradeps = $forceobj{$d->{obj_orig}} ? ["*.c","*.h","*.rc"] : [];
+ print &splitline(sprintf("%s: %s", $d->{obj},
+ join " ", @$extradeps, @{$d->{deps}})), "\n";
+ if ($d->{obj} =~ /.obj$/) {
+ print "\tcl \$(COMPAT) \$(CFLAGS) \$(XFLAGS) /c ".$d->{deps}->[0],"\n\n";
+ } else {
+ print "\trc \$(RCFL) -r \$(RCFLAGS) ".$d->{deps}->[0],"\n\n";
+ }
+ }
+ print "\n";
+ print $makefile_extra{'vc'}->{'end'};
+ print "\nclean: tidy\n".
+ "\t-del *.exe\n\n".
+ "tidy:\n".
+ "\t-del *.obj\n".
+ "\t-del *.res\n".
+ "\t-del *.pch\n".
+ "\t-del *.aps\n".
+ "\t-del *.ilk\n".
+ "\t-del *.pdb\n".
+ "\t-del *.rsp\n".
+ "\t-del *.dsp\n".
+ "\t-del *.dsw\n".
+ "\t-del *.ncb\n".
+ "\t-del *.opt\n".
+ "\t-del *.plg\n".
+ "\t-del *.map\n".
+ "\t-del *.idb\n".
+ "\t-del debug.log\n";
+ select STDOUT; close OUT;
+}
+
+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
+ # CRLF. Hence, in order for mkfiles.pl to generate usable project
+ # files even when run from Unix, I make sure all files are binary
+ # and explicitly write the CRLFs.
+ #
+ # Create directories if necessary
+ mkdir $makefiles{'vcproj'}
+ if(! -d $makefiles{'vcproj'});
+ chdir $makefiles{'vcproj'};
+ @deps = &deps("X.obj", "X.res", $dirpfx, "\\", "vcproj");
+ %all_object_deps = map {$_->{obj} => $_->{deps}} @deps;
+ # Create the project files
+ # Get names of all Windows projects (GUI and console)
+ my @prognames = &prognames("G:C");
+ foreach $progname (@prognames) {
+ create_vc_project(\%all_object_deps, $progname);
+ }
+ # Create the workspace file
+ open OUT, ">$project_name.dsw"; binmode OUT; select OUT;
+ print
+ "Microsoft Developer Studio Workspace File, Format Version 6.00\r\n".
+ "# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE!\r\n".
+ "\r\n".
+ "###############################################################################\r\n".
+ "\r\n";
+ # List projects
+ foreach $progname (@prognames) {
+ ($windows_project, $type) = split ",", $progname;
+ print "Project: \"$windows_project\"=\".\\$windows_project\\$windows_project.dsp\" - Package Owner=<4>\r\n";
+ }
+ print
+ "\r\n".
+ "Package=<5>\r\n".
+ "{{{\r\n".
+ "}}}\r\n".
+ "\r\n".
+ "Package=<4>\r\n".
+ "{{{\r\n".
+ "}}}\r\n".
+ "\r\n".
+ "###############################################################################\r\n".
+ "\r\n".
+ "Global:\r\n".
+ "\r\n".
+ "Package=<5>\r\n".
+ "{{{\r\n".
+ "}}}\r\n".
+ "\r\n".
+ "Package=<3>\r\n".
+ "{{{\r\n".
+ "}}}\r\n".
+ "\r\n".
+ "###############################################################################\r\n".
+ "\r\n";
+ select STDOUT; close OUT;
+ chdir $orig_dir;
+
+ sub create_vc_project {
+ my ($all_object_deps, $progname) = @_;
+ # Construct program's dependency info
+ %seen_objects = ();
+ %lib_files = ();
+ %source_files = ();
+ %header_files = ();
+ %resource_files = ();
+ @object_files = split " ", &objects($progname, "X.obj", "X.res", "X.lib");
+ foreach $object_file (@object_files) {
+ next if defined $seen_objects{$object_file};
+ $seen_objects{$object_file} = 1;
+ if($object_file =~ /\.lib$/io) {
+ $lib_files{$object_file} = 1;
+ next;
+ }
+ $object_deps = $all_object_deps{$object_file};
+ foreach $object_dep (@$object_deps) {
+ if($object_dep =~ /\.c$/io) {
+ $source_files{$object_dep} = 1;
+ next;
+ }
+ if($object_dep =~ /\.h$/io) {
+ $header_files{$object_dep} = 1;
+ next;
+ }
+ if($object_dep =~ /\.(rc|ico)$/io) {
+ $resource_files{$object_dep} = 1;
+ next;
+ }
+ }
+ }
+ $libs = join " ", sort keys %lib_files;
+ @source_files = sort keys %source_files;
+ @header_files = sort keys %header_files;
+ @resources = sort keys %resource_files;
+ ($windows_project, $type) = split ",", $progname;
+ mkdir $windows_project
+ if(! -d $windows_project);
+ chdir $windows_project;
+ $subsys = ($type eq "G") ? "windows" : "console";
+ open OUT, ">$windows_project.dsp"; binmode OUT; select OUT;
+ print
+ "# Microsoft Developer Studio Project File - Name=\"$windows_project\" - Package Owner=<4>\r\n".
+ "# Microsoft Developer Studio Generated Build File, Format Version 6.00\r\n".
+ "# ** DO NOT EDIT **\r\n".
+ "\r\n".
+ "# TARGTYPE \"Win32 (x86) Application\" 0x0101\r\n".
+ "\r\n".
+ "CFG=$windows_project - Win32 Debug\r\n".
+ "!MESSAGE This is not a valid makefile. To build this project using NMAKE,\r\n".
+ "!MESSAGE use the Export Makefile command and run\r\n".
+ "!MESSAGE \r\n".
+ "!MESSAGE NMAKE /f \"$windows_project.mak\".\r\n".
+ "!MESSAGE \r\n".
+ "!MESSAGE You can specify a configuration when running NMAKE\r\n".
+ "!MESSAGE by defining the macro CFG on the command line. For example:\r\n".
+ "!MESSAGE \r\n".
+ "!MESSAGE NMAKE /f \"$windows_project.mak\" CFG=\"$windows_project - Win32 Debug\"\r\n".
+ "!MESSAGE \r\n".
+ "!MESSAGE Possible choices for configuration are:\r\n".
+ "!MESSAGE \r\n".
+ "!MESSAGE \"$windows_project - Win32 Release\" (based on \"Win32 (x86) Application\")\r\n".
+ "!MESSAGE \"$windows_project - Win32 Debug\" (based on \"Win32 (x86) Application\")\r\n".
+ "!MESSAGE \r\n".
+ "\r\n".
+ "# Begin Project\r\n".
+ "# PROP AllowPerConfigDependencies 0\r\n".
+ "# PROP Scc_ProjName \"\"\r\n".
+ "# PROP Scc_LocalPath \"\"\r\n".
+ "CPP=cl.exe\r\n".
+ "MTL=midl.exe\r\n".
+ "RSC=rc.exe\r\n".
+ "\r\n".
+ "!IF \"\$(CFG)\" == \"$windows_project - Win32 Release\"\r\n".
+ "\r\n".
+ "# PROP BASE Use_MFC 0\r\n".
+ "# PROP BASE Use_Debug_Libraries 0\r\n".
+ "# PROP BASE Output_Dir \"Release\"\r\n".
+ "# PROP BASE Intermediate_Dir \"Release\"\r\n".
+ "# PROP BASE Target_Dir \"\"\r\n".
+ "# PROP Use_MFC 0\r\n".
+ "# PROP Use_Debug_Libraries 0\r\n".
+ "# PROP Output_Dir \"Release\"\r\n".
+ "# PROP Intermediate_Dir \"Release\"\r\n".
+ "# PROP Ignore_Export_Lib 0\r\n".
+ "# PROP Target_Dir \"\"\r\n".
+ "# ADD BASE CPP /nologo /W3 /GX /O2 ".
+ (join " ", map {"/I \"..\\..\\$dirpfx$_\""} @srcdirs) .
+ " /D \"WIN32\" /D \"NDEBUG\" /D \"_WINDOWS\" /D \"_MBCS\" /YX /FD /c\r\n".
+ "# ADD CPP /nologo /W3 /GX /O2 ".
+ (join " ", map {"/I \"..\\..\\$dirpfx$_\""} @srcdirs) .
+ " /D \"WIN32\" /D \"NDEBUG\" /D \"_WINDOWS\" /D \"_MBCS\" /YX /FD /c\r\n".
+ "# ADD BASE MTL /nologo /D \"NDEBUG\" /mktyplib203 /win32\r\n".
+ "# ADD MTL /nologo /D \"NDEBUG\" /mktyplib203 /win32\r\n".
+ "# ADD BASE RSC /l 0x809 /d \"NDEBUG\"\r\n".
+ "# ADD RSC /l 0x809 /d \"NDEBUG\"\r\n".
+ "BSC32=bscmake.exe\r\n".
+ "# ADD BASE BSC32 /nologo\r\n".
+ "# ADD BSC32 /nologo\r\n".
+ "LINK32=link.exe\r\n".
+ "# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:$subsys /machine:I386\r\n".
+ "# ADD LINK32 $libs /nologo /subsystem:$subsys /machine:I386\r\n".
+ "# SUBTRACT LINK32 /pdb:none\r\n".
+ "\r\n".
+ "!ELSEIF \"\$(CFG)\" == \"$windows_project - Win32 Debug\"\r\n".
+ "\r\n".
+ "# PROP BASE Use_MFC 0\r\n".
+ "# PROP BASE Use_Debug_Libraries 1\r\n".
+ "# PROP BASE Output_Dir \"Debug\"\r\n".
+ "# PROP BASE Intermediate_Dir \"Debug\"\r\n".
+ "# PROP BASE Target_Dir \"\"\r\n".
+ "# PROP Use_MFC 0\r\n".
+ "# PROP Use_Debug_Libraries 1\r\n".
+ "# PROP Output_Dir \"Debug\"\r\n".
+ "# PROP Intermediate_Dir \"Debug\"\r\n".
+ "# PROP Ignore_Export_Lib 0\r\n".
+ "# PROP Target_Dir \"\"\r\n".
+ "# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od ".
+ (join " ", map {"/I \"..\\..\\$dirpfx$_\""} @srcdirs) .
+ " /D \"WIN32\" /D \"_DEBUG\" /D \"_WINDOWS\" /D \"_MBCS\" /YX /FD /GZ /c\r\n".
+ "# ADD CPP /nologo /W3 /Gm /GX /ZI /Od ".
+ (join " ", map {"/I \"..\\..\\$dirpfx$_\""} @srcdirs) .
+ " /D \"WIN32\" /D \"_DEBUG\" /D \"_WINDOWS\" /D \"_MBCS\" /YX /FD /GZ /c\r\n".
+ "# ADD BASE MTL /nologo /D \"_DEBUG\" /mktyplib203 /win32\r\n".
+ "# ADD MTL /nologo /D \"_DEBUG\" /mktyplib203 /win32\r\n".
+ "# ADD BASE RSC /l 0x809 /d \"_DEBUG\"\r\n".
+ "# ADD RSC /l 0x809 /d \"_DEBUG\"\r\n".
+ "BSC32=bscmake.exe\r\n".
+ "# ADD BASE BSC32 /nologo\r\n".
+ "# ADD BSC32 /nologo\r\n".
+ "LINK32=link.exe\r\n".
+ "# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:$subsys /debug /machine:I386 /pdbtype:sept\r\n".
+ "# ADD LINK32 $libs /nologo /subsystem:$subsys /debug /machine:I386 /pdbtype:sept\r\n".
+ "# SUBTRACT LINK32 /pdb:none\r\n".
+ "\r\n".
+ "!ENDIF \r\n".
+ "\r\n".
+ "# Begin Target\r\n".
+ "\r\n".
+ "# Name \"$windows_project - Win32 Release\"\r\n".
+ "# Name \"$windows_project - Win32 Debug\"\r\n".
+ "# Begin Group \"Source Files\"\r\n".
+ "\r\n".
+ "# PROP Default_Filter \"cpp;c;cxx;rc;def;r;odl;idl;hpj;bat\"\r\n";
+ foreach $source_file (@source_files) {
+ print
+ "# Begin Source File\r\n".
+ "\r\n".
+ "SOURCE=..\\..\\$source_file\r\n";
+ if($source_file =~ /ssh\.c/io) {
+ # Disable 'Edit and continue' as Visual Studio can't handle the macros
+ print
+ "\r\n".
+ "!IF \"\$(CFG)\" == \"$windows_project - Win32 Release\"\r\n".
+ "\r\n".
+ "!ELSEIF \"\$(CFG)\" == \"$windows_project - Win32 Debug\"\r\n".
+ "\r\n".
+ "# ADD CPP /Zi\r\n".
+ "\r\n".
+ "!ENDIF \r\n".
+ "\r\n";
+ }
+ print "# End Source File\r\n";
+ }
+ print
+ "# End Group\r\n".
+ "# Begin Group \"Header Files\"\r\n".
+ "\r\n".
+ "# PROP Default_Filter \"h;hpp;hxx;hm;inl\"\r\n";
+ foreach $header_file (@header_files) {
+ print
+ "# Begin Source File\r\n".
+ "\r\n".
+ "SOURCE=..\\..\\$header_file\r\n".
+ "# End Source File\r\n";
+ }
+ print
+ "# End Group\r\n".
+ "# Begin Group \"Resource Files\"\r\n".
+ "\r\n".
+ "# PROP Default_Filter \"ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe\"\r\n";
+ foreach $resource_file (@resources) {
+ print
+ "# Begin Source File\r\n".
+ "\r\n".
+ "SOURCE=..\\..\\$resource_file\r\n".
+ "# End Source File\r\n";
+ }
+ print
+ "# End Group\r\n".
+ "# End Target\r\n".
+ "# End Project\r\n";
+ select STDOUT; close OUT;
+ chdir "..";
+ }
+}
+
+if (defined $makefiles{'gtk'}) {
+ $dirpfx = &dirpfx($makefiles{'gtk'}, "/");
+
+ ##-- X/GTK/Unix makefile
+ open OUT, ">$makefiles{'gtk'}"; select OUT;
+ print
+ "# Makefile for $project_name under X/GTK and Unix.\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".
+ "# You can define this path to point at your tools if you need to\n".
+ "# TOOLPATH = /opt/gcc/bin\n".
+ "CC = \$(TOOLPATH)cc\n".
+ "# If necessary set the path to krb5-config here\n".
+ "KRB5CONFIG=krb5-config\n".
+ "# You can manually set this to `gtk-config' or `pkg-config gtk+-1.2'\n".
+ "# (depending on what works on your system) if you want to enforce\n".
+ "# building with GTK 1.2, or you can set it to `pkg-config gtk+-2.0 x11'\n".
+ "# if you want to enforce 2.0. The default is to try 2.0 and fall back\n".
+ "# to 1.2 if it isn't found.\n".
+ "GTK_CONFIG = sh -c 'pkg-config gtk+-2.0 x11 \$\$0 2>/dev/null || gtk-config \$\$0'\n".
+ "\n".
+ "-include Makefile.local\n".
+ "\n".
+ "unexport CFLAGS # work around a weird issue with krb5-config\n".
+ "\n".
+ &splitline("CFLAGS = -O2 -Wall -Werror -g " .
+ (join " ", map {"-I$dirpfx$_"} @srcdirs) .
+ " \$(shell \$(GTK_CONFIG) --cflags)").
+ " -D _FILE_OFFSET_BITS=64\n".
+ "XLDFLAGS = \$(LDFLAGS) \$(shell \$(GTK_CONFIG) --libs)\n".
+ "ULDFLAGS = \$(LDFLAGS)\n".
+ "ifeq (,\$(findstring NO_GSSAPI,\$(COMPAT)))\n".
+ "ifeq (,\$(findstring STATIC_GSSAPI,\$(COMPAT)))\n".
+ "XLDFLAGS+= -ldl\n".
+ "ULDFLAGS+= -ldl\n".
+ "else\n".
+ "CFLAGS+= -DNO_LIBDL \$(shell \$(KRB5CONFIG) --cflags gssapi)\n".
+ "XLDFLAGS+= \$(shell \$(KRB5CONFIG) --libs gssapi)\n".
+ "ULDFLAGS+= \$(shell \$(KRB5CONFIG) --libs gssapi)\n".
+ "endif\n".
+ "endif\n".
+ "INSTALL=install\n".
+ "INSTALL_PROGRAM=\$(INSTALL)\n".
+ "INSTALL_DATA=\$(INSTALL)\n".
+ "prefix=/usr/local\n".
+ "exec_prefix=\$(prefix)\n".
+ "bindir=\$(exec_prefix)/bin\n".
+ "mandir=\$(prefix)/man\n".
+ "man1dir=\$(mandir)/man1\n".
+ "\n".
+ &def($makefile_extra{'gtk'}->{'vars'}) .
+ "\n".
+ ".SUFFIXES:\n".
+ "\n".
+ "\n";
+ print &splitline("all:" . join "", map { " $_" } &progrealnames("X:U"));
+ print "\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 "\nFORCE:\n";
+ select STDOUT; close OUT;
+}
+
+if (defined $makefiles{'unix'}) {
+ $dirpfx = &dirpfx($makefiles{'unix'}, "/");
+
+ ##-- GTK-free pure-Unix makefile for non-GUI apps only
+ open OUT, ">$makefiles{'unix'}"; select OUT;
+ print
+ "# Makefile for $project_name under Unix.\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".
+ "# You can define this path to point at your tools if you need to\n".
+ "# TOOLPATH = /opt/gcc/bin\n".
+ "CC = \$(TOOLPATH)cc\n".
+ "\n".
+ "-include Makefile.local\n".
+ "\n".
+ "unexport CFLAGS # work around a weird issue with krb5-config\n".
+ "\n".
+ &splitline("CFLAGS = -O2 -Wall -Werror -g " .
+ (join " ", map {"-I$dirpfx$_"} @srcdirs)).
+ " -D _FILE_OFFSET_BITS=64\n".
+ "ULDFLAGS = \$(LDFLAGS)\n".
+ "INSTALL=install\n".
+ "INSTALL_PROGRAM=\$(INSTALL)\n".
+ "INSTALL_DATA=\$(INSTALL)\n".
+ "prefix=/usr/local\n".
+ "exec_prefix=\$(prefix)\n".
+ "bindir=\$(exec_prefix)/bin\n".
+ "mandir=\$(prefix)/man\n".
+ "man1dir=\$(mandir)/man1\n".
+ "\n".
+ &def($makefile_extra{'unix'}->{'vars'}) .
+ "\n".
+ ".SUFFIXES:\n".
+ "\n".
+ "\n";
+ print &splitline("all:" . join "", map { " $_" } &progrealnames("U"));
+ print "\n\n";
+ foreach $p (&prognames("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, "/", "unix")) {
+ 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 &def($makefile_extra{'unix'}->{'end'});
+ print "\nclean:\n".
+ "\trm -f *.o". (join "", map { " $_" } &progrealnames("U")) . "\n";
+ print "\nFORCE:\n";
+ select STDOUT; close OUT;
+}
+
+if (defined $makefiles{'ac'}) {
+ $dirpfx = &dirpfx($makefiles{'ac'}, "/");
+
+ ##-- Unix/autoconf makefile
+ open OUT, ">$makefiles{'ac'}"; select OUT;
+ print
+ "# Makefile.in for $project_name under Unix with Autoconf.\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";
+ 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";
+ select STDOUT; close OUT;
+}
+
+if (defined $makefiles{'lcc'}) {
+ $dirpfx = &dirpfx($makefiles{'lcc'}, "\\");
+
+ ##-- lcc makefile
+ open OUT, ">$makefiles{'lcc'}"; select OUT;
+ print
+ "# Makefile for $project_name under lcc.\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";
+ # lcc command line option is -D not /D
+ ($_ = $help) =~ s/([=" ])\/D/$1-D/gs;
+ print $_;
+ print
+ "\n".
+ "# If you rename this file to `Makefile', you should change this line,\n".
+ "# so that the .rsp files still depend on the correct makefile.\n".
+ "MAKEFILE = Makefile.lcc\n".
+ "\n".
+ "# C compilation flags\n".
+ "CFLAGS = -D_WINDOWS " .
+ (join " ", map {"-I$dirpfx$_"} @srcdirs) .
+ "\n".
+ "# Resource compilation flags\n".
+ "RCFLAGS = \n".
+ "\n".
+ "# Get include directory for resource compiler\n".
+ "\n".
+ $makefile_extra{'lcc'}->{'vars'} .
+ "\n";
+ print &splitline("all:" . join "", map { " $_.exe" } &progrealnames("G:C"));
+ print "\n\n";
+ foreach $p (&prognames("G:C")) {
+ ($prog, $type) = split ",", $p;
+ $objstr = &objects($p, "X.obj", "X.res", undef);
+ print &splitline("$prog.exe: " . $objstr ), "\n";
+ $subsystemtype = '';
+ if ($type eq "G") { $subsystemtype = "-subsystem windows"; }
+ my $libss = "shell32.lib wsock32.lib ws2_32.lib winspool.lib winmm.lib imm32.lib";
+ print &splitline("\tlcclnk $subsystemtype -o $prog.exe $objstr $libss");
+ print "\n\n";
+ }
+
+ foreach $d (&deps("X.obj", "X.res", $dirpfx, "\\", "lcc")) {
+ if ($forceobj{$d->{obj_orig}}) {
+ printf("%s: FORCE\n", $d->{obj});
+ } else {
+ print &splitline(sprintf("%s: %s", $d->{obj},
+ join " ", @{$d->{deps}})), "\n";
+ }
+ if ($d->{obj} =~ /\.obj$/) {
+ print &splitline("\tlcc -O -p6 \$(COMPAT)".
+ " \$(CFLAGS) \$(XFLAGS) ".$d->{deps}->[0],69)."\n";
+ } else {
+ print &splitline("\tlrc \$(RCFL) -r \$(RCFLAGS) ".
+ $d->{deps}->[0],69)."\n";
+ }
+ }
+ print "\n";
+ print $makefile_extra{'lcc'}->{'end'};
+ print "\nclean:\n".
+ "\t-del *.obj\n".
+ "\t-del *.exe\n".
+ "\t-del *.res\n".
+ "\n".
+ "FORCE:\n";
+
+ select STDOUT; close OUT;
+}
+
+if (defined $makefiles{'osx'}) {
+ $dirpfx = &dirpfx($makefiles{'osx'}, "/");
+
+ ##-- Mac OS X makefile
+ open OUT, ">$makefiles{'osx'}"; select OUT;
+ print
+ "# Makefile for $project_name under Mac OS X.\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
+ "CC = \$(TOOLPATH)gcc\n".
+ "\n".
+ &splitline("CFLAGS = -O2 -Wall -Werror -g " .
+ (join " ", map {"-I$dirpfx$_"} @srcdirs))."\n".
+ "MLDFLAGS = -framework Cocoa\n".
+ "ULDFLAGS =\n".
+ "\n" .
+ $makefile_extra{'osx'}->{'vars'} .
+ "\n" .
+ &splitline("all:" . join "", map { " $_" } &progrealnames("MX:U")) .
+ "\n";
+ foreach $p (&prognames("MX")) {
+ ($prog, $type) = split ",", $p;
+ $objstr = &objects($p, "X.o", undef, undef);
+ $icon = &special($p, ".icns");
+ $infoplist = &special($p, "info.plist");
+ print "${prog}.app:\n\tmkdir -p \$\@\n";
+ print "${prog}.app/Contents: ${prog}.app\n\tmkdir -p \$\@\n";
+ print "${prog}.app/Contents/MacOS: ${prog}.app/Contents\n\tmkdir -p \$\@\n";
+ $targets = "${prog}.app/Contents/MacOS/$prog";
+ if (defined $icon) {
+ print "${prog}.app/Contents/Resources: ${prog}.app/Contents\n\tmkdir -p \$\@\n";
+ print "${prog}.app/Contents/Resources/${prog}.icns: ${prog}.app/Contents/Resources $icon\n\tcp $icon \$\@\n";
+ $targets .= " ${prog}.app/Contents/Resources/${prog}.icns";
+ }
+ if (defined $infoplist) {
+ print "${prog}.app/Contents/Info.plist: ${prog}.app/Contents/Resources $infoplist\n\tcp $infoplist \$\@\n";
+ $targets .= " ${prog}.app/Contents/Info.plist";
+ }
+ $targets .= " \$(${prog}_extra)";
+ print &splitline("${prog}: $targets", 69) . "\n\n";
+ print &splitline("${prog}.app/Contents/MacOS/$prog: ".
+ "${prog}.app/Contents/MacOS " . $objstr), "\n";
+ $libstr = &objects($p, undef, undef, "-lX");
+ print &splitline("\t\$(CC) \$(MLDFLAGS) -o \$@ " .
+ $objstr . " $libstr", 69), "\n\n";
+ }
+ foreach $p (&prognames("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) \$(ULDFLAGS) -o \$@ " .
+ $objstr . " $libstr", 69), "\n\n";
+ }
+ foreach $d (&deps("X.o", undef, $dirpfx, "/", "osx")) {
+ if ($forceobj{$d->{obj_orig}}) {
+ printf("%s: FORCE\n", $d->{obj});
+ } else {
+ print &splitline(sprintf("%s: %s", $d->{obj},
+ join " ", @{$d->{deps}})), "\n";
+ }
+ $firstdep = $d->{deps}->[0];
+ if ($firstdep =~ /\.c$/) {
+ print "\t\$(CC) \$(COMPAT) \$(FWHACK) \$(CFLAGS) \$(XFLAGS) -c \$<\n";
+ } elsif ($firstdep =~ /\.m$/) {
+ print "\t\$(CC) -x objective-c \$(COMPAT) \$(FWHACK) \$(CFLAGS) \$(XFLAGS) -c \$<\n";
+ }
+ }
+ print "\n".&def($makefile_extra{'osx'}->{'end'});
+ print "\nclean:\n".
+ "\trm -f *.o *.dmg". (join "", map { " $_" } &progrealnames("U")) . "\n".
+ "\trm -rf *.app\n".
+ "\n".
+ "FORCE:\n";
+ select STDOUT; close OUT;
+}
+
+if (defined $makefiles{'devcppproj'}) {
+ $dirpfx = &dirpfx($makefiles{'devcppproj'}, "\\");
+ $orig_dir = cwd;
+
+ ##-- Dev-C++ 5 projects
+ #
+ # Note: All files created in this section are written in binary
+ # mode to prevent any posibility of misinterpreted line endings.
+ # I don't know if Dev-C++ is as touchy as MSVC with LF-only line
+ # endings. But however, CRLF line endings are the common way on
+ # Win32 machines where Dev-C++ is running.
+ # Hence, in order for mkfiles.pl to generate CRLF project files
+ # even when run from Unix, I make sure all files are binary and
+ # explicitly write the CRLFs.
+ #
+ # Create directories if necessary
+ mkdir $makefiles{'devcppproj'}
+ if(! -d $makefiles{'devcppproj'});
+ chdir $makefiles{'devcppproj'};
+ @deps = &deps("X.obj", "X.res", $dirpfx, "\\", "devcppproj");
+ %all_object_deps = map {$_->{obj} => $_->{deps}} @deps;
+ # Make dir names FAT/NTFS compatible
+ my @srcdirs = @srcdirs;
+ for ($i=0; $i<@srcdirs; $i++) {
+ $srcdirs[$i] =~ s/\//\\/g;
+ $srcdirs[$i] =~ s/\\$//;
+ }
+ # Create the project files
+ # Get names of all Windows projects (GUI and console)
+ my @prognames = &prognames("G:C");
+ foreach $progname (@prognames) {
+ create_devcpp_project(\%all_object_deps, $progname);
+ }
+
+ 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 = ();
+ %lib_files = ();
+ %source_files = ();
+ %header_files = ();
+ %resource_files = ();
+ @object_files = split " ", &objects($progname, "X.obj", "X.res", "X.lib");
+ foreach $object_file (@object_files) {
+ next if defined $seen_objects{$object_file};
+ $seen_objects{$object_file} = 1;
+ if($object_file =~ /\.lib$/io) {
+ $lib_files{$object_file} = 1;
+ next;
+ }
+ $object_deps = $all_object_deps{$object_file};
+ foreach $object_dep (@$object_deps) {
+ if($object_dep =~ /\.c$/io) {
+ $source_files{$object_dep} = 1;
+ next;
+ }
+ if($object_dep =~ /\.h$/io) {
+ $header_files{$object_dep} = 1;
+ next;
+ }
+ if($object_dep =~ /\.(rc|ico)$/io) {
+ $resource_files{$object_dep} = 1;
+ next;
+ }
+ }
+ }
+ $libs = join " ", sort keys %lib_files;
+ @source_files = sort keys %source_files;
+ @header_files = sort keys %header_files;
+ @resources = sort keys %resource_files;
+ ($windows_project, $type) = split ",", $progname;
+ mkdir $windows_project
+ if(! -d $windows_project);
+ chdir $windows_project;
+
+ $subsys = ($type eq "G") ? "0" : "1"; # 0 = Win32 GUI, 1 = Win32 Console
+ open OUT, ">$windows_project.dev"; binmode OUT; select OUT;
+ print
+ "# DEV-C++ 5 Project File - $windows_project.dev\r\n".
+ "# ** DO NOT EDIT **\r\n".
+ "\r\n".
+ # No difference between DEBUG and RELEASE here as in 'vcproj', because
+ # Dev-C++ does not support mutiple compilation profiles in one single project.
+ # (At least I can say this for Dev-C++ 5 Beta)
+ "[Project]\r\n".
+ "FileName=$windows_project.dev\r\n".
+ "Name=$windows_project\r\n".
+ "Ver=1\r\n".
+ "IsCpp=1\r\n".
+ "Type=$subsys\r\n".
+ # Multimon is disabled here, as Dev-C++ (Version 5 Beta) does not have multimon.h
+ "Compiler=-W -D__GNUWIN32__ -DWIN32 -DNDEBUG -D_WINDOWS -DNO_MULTIMON -D_MBCS_\@\@_\r\n".
+ "CppCompiler=-W -D__GNUWIN32__ -DWIN32 -DNDEBUG -D_WINDOWS -DNO_MULTIMON -D_MBCS_\@\@_\r\n".
+ "Includes=" . (join ";", map {"..\\..\\$dirpfx$_"} @srcdirs) . "\r\n".
+ "Linker=-ladvapi32 -lcomctl32 -lcomdlg32 -lgdi32 -limm32 -lshell32 -luser32 -lwinmm -lwinspool_\@\@_\r\n".
+ "Libs=\r\n".
+ "UnitCount=" . (@source_files + @header_files + @resources) . "\r\n".
+ "Folders=\"Header Files\",\"Resource Files\",\"Source Files\"\r\n".
+ "ObjFiles=\r\n".
+ "PrivateResource=${windows_project}_private.rc\r\n".
+ "ResourceIncludes=..\\..\\..\\WINDOWS\r\n".
+ "MakeIncludes=\r\n".
+ "Icon=\r\n". # It's ok to leave this blank.
+ "ExeOutput=\r\n".
+ "ObjectOutput=\r\n".
+ "OverrideOutput=0\r\n".
+ "OverrideOutputName=$windows_project.exe\r\n".
+ "HostApplication=\r\n".
+ "CommandLine=\r\n".
+ "UseCustomMakefile=0\r\n".
+ "CustomMakefile=\r\n".
+ "IncludeVersionInfo=0\r\n".
+ "SupportXPThemes=0\r\n".
+ "CompilerSet=0\r\n".
+ "CompilerSettings=0000000000000000000000\r\n".
+ "\r\n";
+ $unit_count = 1;
+ foreach $source_file (@source_files) {
+ print
+ "[Unit$unit_count]\r\n".
+ "FileName=..\\..\\$source_file\r\n".
+ "Folder=Source Files\r\n".
+ "Compile=1\r\n".
+ "CompileCpp=0\r\n".
+ "Link=1\r\n".
+ "Priority=1000\r\n".
+ "OverrideBuildCmd=0\r\n".
+ "BuildCmd=\r\n".
+ "\r\n";
+ $unit_count++;
+ }
+ foreach $header_file (@header_files) {
+ print
+ "[Unit$unit_count]\r\n".
+ "FileName=..\\..\\$header_file\r\n".
+ "Folder=Header Files\r\n".
+ "Compile=1\r\n".
+ "CompileCpp=1\r\n". # Dev-C++ want's to compile all header files with both compilers C and C++. It does not hurt.
+ "Link=1\r\n".
+ "Priority=1000\r\n".
+ "OverrideBuildCmd=0\r\n".
+ "BuildCmd=\r\n".
+ "\r\n";
+ $unit_count++;
+ }
+ foreach $resource_file (@resources) {
+ if ($resource_file =~ /.*\.(ico|cur|bmp|dlg|rc2|rct|bin|rgs|gif|jpg|jpeg|jpe)/io) { # Default filter as in 'vcproj'
+ $Compile = "0"; # Don't compile images and other binary resource files
+ $CompileCpp = "0";
+ } else {
+ $Compile = "1";
+ $CompileCpp = "1"; # Dev-C++ want's to compile all .rc files with both compilers C and C++. It does not hurt.
+ }
+ print
+ "[Unit$unit_count]\r\n".
+ "FileName=..\\..\\$resource_file\r\n".
+ "Folder=Resource Files\r\n".
+ "Compile=$Compile\r\n".
+ "CompileCpp=$CompileCpp\r\n".
+ "Link=0\r\n".
+ "Priority=1000\r\n".
+ "OverrideBuildCmd=0\r\n".
+ "BuildCmd=\r\n".
+ "\r\n";
+ $unit_count++;
+ }
+ #Note: By default, [VersionInfo] is not used.
+ print
+ "[VersionInfo]\r\n".
+ "Major=0\r\n".
+ "Minor=0\r\n".
+ "Release=1\r\n".
+ "Build=1\r\n".
+ "LanguageID=1033\r\n".
+ "CharsetID=1252\r\n".
+ "CompanyName=\r\n".
+ "FileVersion=0.1\r\n".
+ "FileDescription=\r\n".
+ "InternalName=\r\n".
+ "LegalCopyright=\r\n".
+ "LegalTrademarks=\r\n".
+ "OriginalFilename=$windows_project.exe\r\n".
+ "ProductName=$windows_project\r\n".
+ "ProductVersion=0.1\r\n".
+ "AutoIncBuildNr=0\r\n";
+ select STDOUT; close OUT;
+ chdir "..";
+ }
+}
--- /dev/null
+#!/bin/sh
+perl mkfiles.pl
+# These are text files.
+text=`{ find . -name CVS -prune -o \
+ -name .cvsignore -prune -o \
+ -name .svn -prune -o \
+ -name LATEST.VER -prune -o \
+ -name CHECKLST.txt -prune -o \
+ -name mksrcarc.sh -prune -o \
+ -name '*.dsp' -prune -o \
+ -name '*.dsw' -prune -o \
+ -type f -print | sed 's/^\.\///'; } | \
+ grep -ivE 'testdata/.*\.txt|MODULE|putty.iss|website.url' | grep -vF .ico | grep -vF .icns`
+# These are files which I'm _sure_ should be treated as text, but
+# which zip might complain about, so we direct its moans to
+# /dev/null! Apparently its heuristics are doubtful of UTF-8 text
+# files.
+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 putty-src.zip $bin > /dev/null
--- /dev/null
+#!/bin/sh
+
+# 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
+# tagged as a release; of the form `r1234' to have it tagged as a
+# 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"
+ ver="-DSNAPSHOT=$1"
+ docver=
+ ;;
+ r*)
+ arcsuffix="-$1"
+ ver="-DSVN_REV=$1"
+ docver=
+ ;;
+ '')
+ arcsuffix=
+ ver=
+ docver=
+ ;;
+ *)
+ case "$1" in *[!.0-9a-z]*) echo "Malformed release ID '$1'">&2;exit 1;;esac
+ arcsuffix="-$1"
+ 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
+find . -name uxarc -prune -o \
+ -name CVS -prune -o \
+ -name .svn -prune -o \
+ -name . -o \
+ -type d -exec mkdir uxarc/$arcname/{} \;
+find . -name uxarc -prune -o \
+ -name CVS -prune -o \
+ -name .cvsignore -prune -o \
+ -name .svn -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
+tar -C uxarc -chzof $arcname.tar.gz $arcname
+rm -rf uxarc
--- /dev/null
+/*
+ * Networking abstraction in PuTTY.
+ *
+ * The way this works is: a back end can choose to open any number
+ * of sockets - including zero, which might be necessary in some.
+ * It can register a bunch of callbacks (most notably for when
+ * data is received) for each socket, and it can call the networking
+ * abstraction to send data without having to worry about blocking.
+ * The stuff behind the abstraction takes care of selects and
+ * nonblocking writes and all that sort of painful gubbins.
+ */
+
+#ifndef PUTTY_NETWORK_H
+#define PUTTY_NETWORK_H
+
+#ifndef DONE_TYPEDEFS
+#define DONE_TYPEDEFS
+typedef struct config_tag Config;
+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 (*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);
+};
+
+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
+ * up the connection.
+ *
+ * - type==0 means we are about to try to connect to address
+ * `addr' (error_msg and error_code are ignored)
+ * - type==1 means we have failed to connect to address `addr'
+ * (error_msg and error_code are supplied). This is not a
+ * fatal error - we may well have other candidate addresses
+ * to fall back to. When it _is_ fatal, the closing()
+ * function will be called.
+ */
+ int (*closing)
+ (Plug p, const char *error_msg, int error_code, int calling_back);
+ /* error_msg is NULL iff it is not an error (ie it closed normally) */
+ /* calling_back != 0 iff there is a Plug function */
+ /* currently running (would cure the fixme in try_send()) */
+ int (*receive) (Plug p, int urgent, char *data, int len);
+ /*
+ * - urgent==0. `data' points to `len' bytes of perfectly
+ * ordinary data.
+ *
+ * - urgent==1. `data' points to `len' bytes of data,
+ * which were read from before an Urgent pointer.
+ *
+ * - urgent==2. `data' points to `len' bytes of data,
+ * the first of which was the one at the Urgent mark.
+ */
+ void (*sent) (Plug p, int bufsize);
+ /*
+ * 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);
+ /*
+ * returns 0 if the host at address addr is a valid host for connecting or error
+ */
+};
+
+/* 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);
+Socket new_listener(char *srcaddr, int port, Plug plug, int local_host_only,
+ const Config *cfg, int addressfamily);
+SockAddr name_lookup(char *host, int port, char **canonicalname,
+ const Config *cfg, int addressfamily);
+
+/* 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);
+
+/* 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_address_is_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
+ * physically _duplicate_ it: incrementing a reference count so that
+ * one more free is required before it disappears is an acceptable
+ * implementation. */
+SockAddr sk_addr_dup(SockAddr addr);
+
+/* NB, control of 'addr' is passed via sk_new, which takes responsibility
+ * for freeing it, as for new_connection() */
+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_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))
+#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.
+ */
+const char *sk_addr_error(SockAddr addr);
+#define sk_socket_error(s) (((*s)->socket_error) (s))
+
+/*
+ * Set the `frozen' flag on a socket. A frozen socket is one in
+ * which all READABLE notifications are ignored, so that data is
+ * not accepted from the peer until the socket is unfrozen. This
+ * exists for two purposes:
+ *
+ * - Port forwarding: when a local listening port receives a
+ * connection, we do not want to receive data from the new
+ * socket until we have somewhere to send it. Hence, we freeze
+ * the socket until its associated SSH channel is ready; then we
+ * unfreeze it and pending data is delivered.
+ *
+ * - Socket buffering: if an SSH channel (or the whole connection)
+ * backs up or presents a zero window, we must freeze the
+ * 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
+ * numeric port specifications.
+ */
+int net_service_lookup(char *service);
+
+/*
+ * Look up the local hostname; return value needs freeing.
+ * May return NULL.
+ */
+char *get_hostname(void);
+
+/********** SSL stuff **********/
+
+/*
+ * This section is subject to change, but you get the general idea
+ * of what it will eventually look like.
+ */
+
+typedef struct certificate *Certificate;
+typedef struct our_certificate *Our_Certificate;
+ /* to be defined somewhere else, somehow */
+
+typedef struct ssl_client_socket_function_table **SSL_Client_Socket;
+typedef struct ssl_client_plug_function_table **SSL_Client_Plug;
+
+struct ssl_client_socket_function_table {
+ struct socket_function_table base;
+ void (*renegotiate) (SSL_Client_Socket s);
+ /* renegotiate the cipher spec */
+};
+
+struct ssl_client_plug_function_table {
+ struct plug_function_table base;
+ int (*refuse_cert) (SSL_Client_Plug p, Certificate cert[]);
+ /* do we accept this certificate chain? If not, why not? */
+ /* cert[0] is the server's certificate, cert[] is NULL-terminated */
+ /* the last certificate may or may not be the root certificate */
+ Our_Certificate(*client_cert) (SSL_Client_Plug p);
+ /* the server wants us to identify ourselves */
+ /* may return NULL if we want anonymity */
+};
+
+SSL_Client_Socket sk_ssl_client_over(Socket s, /* pre-existing (tcp) connection */
+ SSL_Client_Plug p);
+
+#define sk_renegotiate(s) (((*s)->renegotiate) (s))
+
+#endif
--- /dev/null
+/*
+ * Routines to refuse to do cryptographic interaction with proxies
+ * in PuTTY. This is a stub implementation of the same interfaces
+ * provided by cproxy.c, for use in PuTTYtel.
+ */
+
+#include <assert.h>
+#include <ctype.h>
+#include <string.h>
+
+#define DEFINE_PLUG_METHOD_MACROS
+#include "putty.h"
+#include "network.h"
+#include "proxy.h"
+
+void proxy_socks5_offerencryptedauth(char * command, int * len)
+{
+ /* For telnet, don't add any new encrypted authentication routines */
+}
+
+int proxy_socks5_handlechap (Proxy_Socket p)
+{
+
+ plug_closing(p->plug, "Proxy error: Trying to handle a SOCKS5 CHAP request"
+ " in telnet-only build",
+ PROXY_ERROR_GENERAL, 0);
+ return 1;
+}
+
+int proxy_socks5_selectchap(Proxy_Socket p)
+{
+ plug_closing(p->plug, "Proxy error: Trying to handle a SOCKS5 CHAP request"
+ " in telnet-only build",
+ PROXY_ERROR_GENERAL, 0);
+ return 1;
+}
--- /dev/null
+/*
+ * Stub definitions of the GSSAPI library list, for Unix pterm and
+ * any other application that needs the symbols defined but has no
+ * use for them.
+ */
+
+#include "putty.h"
+
+const int ngsslibs = 0;
+const char *const gsslibnames[1] = { "dummy" };
+const struct keyvalwhere gsslibkeywords[1] = { { "dummy", 0, -1, -1 } };
--- /dev/null
+/*
+ * Stub implementation of the printing interface for PuTTY, for the
+ * benefit of non-printing terminal applications.
+ */
+
+#include <assert.h>
+#include <stdio.h>
+#include "putty.h"
+
+struct printer_job_tag {
+ int dummy;
+};
+
+printer_job *printer_start_job(char *printer)
+{
+ return NULL;
+}
+
+void printer_job_data(printer_job *pj, void *data, int len)
+{
+}
+
+void printer_finish_job(printer_job *pj)
+{
+}
+
+printer_enum *printer_start_enum(int *nprinters_ptr)
+{
+ *nprinters_ptr = 0;
+ return NULL;
+}
+char *printer_get_name(printer_enum *pe, int i)
+{
+ return NULL;
+}
+void printer_finish_enum(printer_enum *pe)
+{
+}
--- /dev/null
+/*
+ * notiming.c: stub version of timing API.
+ *
+ * Used in any tool which needs a subsystem linked against the
+ * timing API but doesn't want to actually provide timing. For
+ * example, key generation tools need the random number generator,
+ * but they don't want the hassle of calling noise_regular() at
+ * regular intervals - and they don't _need_ it either, since they
+ * have their own rigorous and different means of noise collection.
+ */
+
+#include "putty.h"
+
+long schedule_timer(int ticks, timer_fn_t fn, void *ctx)
+{
+ return 0;
+}
+
+void expire_timer_context(void *ctx)
+{
+}
--- /dev/null
+/* This file actually defines the GSSAPI function pointers for
+ * functions we plan to import from a GSSAPI library.
+ */
+#include "putty.h"
+
+#ifndef NO_GSSAPI
+
+#include "pgssapi.h"
+
+#ifndef NO_LIBDL
+
+/* Reserved static storage for GSS_oids. Comments are quotes from RFC 2744. */
+static const gss_OID_desc oids[] = {
+ /* The implementation must reserve static storage for a
+ * gss_OID_desc object containing the value */
+ {10, (void *)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x01"},
+ /* corresponding to an object-identifier value of
+ * {iso(1) member-body(2) United States(840) mit(113554)
+ * infosys(1) gssapi(2) generic(1) user_name(1)}. The constant
+ * GSS_C_NT_USER_NAME should be initialized to point
+ * to that gss_OID_desc.
+
+ * The implementation must reserve static storage for a
+ * gss_OID_desc object containing the value */
+ {10, (void *)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x02"},
+ /* corresponding to an object-identifier value of
+ * {iso(1) member-body(2) United States(840) mit(113554)
+ * infosys(1) gssapi(2) generic(1) machine_uid_name(2)}.
+ * The constant GSS_C_NT_MACHINE_UID_NAME should be
+ * initialized to point to that gss_OID_desc.
+
+ * The implementation must reserve static storage for a
+ * gss_OID_desc object containing the value */
+ {10, (void *)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x03"},
+ /* corresponding to an object-identifier value of
+ * {iso(1) member-body(2) United States(840) mit(113554)
+ * infosys(1) gssapi(2) generic(1) string_uid_name(3)}.
+ * The constant GSS_C_NT_STRING_UID_NAME should be
+ * initialized to point to that gss_OID_desc.
+ *
+ * The implementation must reserve static storage for a
+ * gss_OID_desc object containing the value */
+ {6, (void *)"\x2b\x06\x01\x05\x06\x02"},
+ /* corresponding to an object-identifier value of
+ * {iso(1) org(3) dod(6) internet(1) security(5)
+ * nametypes(6) gss-host-based-services(2)). The constant
+ * GSS_C_NT_HOSTBASED_SERVICE_X should be initialized to point
+ * to that gss_OID_desc. This is a deprecated OID value, and
+ * implementations wishing to support hostbased-service names
+ * should instead use the GSS_C_NT_HOSTBASED_SERVICE OID,
+ * defined below, to identify such names;
+ * GSS_C_NT_HOSTBASED_SERVICE_X should be accepted a synonym
+ * for GSS_C_NT_HOSTBASED_SERVICE when presented as an input
+ * parameter, but should not be emitted by GSS-API
+ * implementations
+ *
+ * The implementation must reserve static storage for a
+ * gss_OID_desc object containing the value */
+ {10, (void *)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x04"},
+ /* corresponding to an object-identifier value of {iso(1)
+ * member-body(2) Unites States(840) mit(113554) infosys(1)
+ * gssapi(2) generic(1) service_name(4)}. The constant
+ * GSS_C_NT_HOSTBASED_SERVICE should be initialized
+ * to point to that gss_OID_desc.
+ *
+ * The implementation must reserve static storage for a
+ * gss_OID_desc object containing the value */
+ {6, (void *)"\x2b\x06\01\x05\x06\x03"},
+ /* corresponding to an object identifier value of
+ * {1(iso), 3(org), 6(dod), 1(internet), 5(security),
+ * 6(nametypes), 3(gss-anonymous-name)}. The constant
+ * and GSS_C_NT_ANONYMOUS should be initialized to point
+ * to that gss_OID_desc.
+ *
+ * The implementation must reserve static storage for a
+ * gss_OID_desc object containing the value */
+ {6, (void *)"\x2b\x06\x01\x05\x06\x04"},
+ /* corresponding to an object-identifier value of
+ * {1(iso), 3(org), 6(dod), 1(internet), 5(security),
+ * 6(nametypes), 4(gss-api-exported-name)}. The constant
+ * GSS_C_NT_EXPORT_NAME should be initialized to point
+ * to that gss_OID_desc.
+ */
+};
+
+/* Here are the constants which point to the static structure above.
+ *
+ * Constants of the form GSS_C_NT_* are specified by rfc 2744.
+ */
+const_gss_OID GSS_C_NT_USER_NAME = oids+0;
+const_gss_OID GSS_C_NT_MACHINE_UID_NAME = oids+1;
+const_gss_OID GSS_C_NT_STRING_UID_NAME = oids+2;
+const_gss_OID GSS_C_NT_HOSTBASED_SERVICE_X = oids+3;
+const_gss_OID GSS_C_NT_HOSTBASED_SERVICE = oids+4;
+const_gss_OID GSS_C_NT_ANONYMOUS = oids+5;
+const_gss_OID GSS_C_NT_EXPORT_NAME = oids+6;
+
+#endif /* NO_LIBDL */
+
+static gss_OID_desc gss_mech_krb5_desc =
+{ 9, (void *)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x02" };
+/* iso(1) member-body(2) United States(840) mit(113554) infosys(1) gssapi(2) krb5(2)*/
+const gss_OID GSS_MECH_KRB5 = &gss_mech_krb5_desc;
+
+#endif /* NO_GSSAPI */
--- /dev/null
+#ifndef PUTTY_PGSSAPI_H
+#define PUTTY_PGSSAPI_H
+
+#include "putty.h"
+
+#ifndef NO_GSSAPI
+
+/*
+ * On Unix, if we're statically linking against GSSAPI, we leave the
+ * declaration of all this lot to the official header. If we're
+ * dynamically linking, we declare it ourselves, because that avoids
+ * us needing the official header at compile time.
+ *
+ * However, we still need the function pointer types, because even
+ * with statically linked GSSAPI we use the ssh_gss_library wrapper.
+ */
+#ifdef STATIC_GSSAPI
+#include <gssapi/gssapi.h>
+typedef gss_OID const_gss_OID; /* for our prototypes below */
+#else /* STATIC_GSSAPI */
+
+/*******************************************************************************
+ * GSSAPI Definitions, taken from RFC 2744
+ ******************************************************************************/
+
+/* GSSAPI Type Definitions */
+typedef uint32 OM_uint32;
+
+typedef struct gss_OID_desc_struct {
+ OM_uint32 length;
+ void *elements;
+} gss_OID_desc;
+typedef const gss_OID_desc *const_gss_OID;
+typedef gss_OID_desc *gss_OID;
+
+typedef struct gss_OID_set_desc_struct {
+ size_t count;
+ gss_OID elements;
+} gss_OID_set_desc;
+typedef const gss_OID_set_desc *const_gss_OID_set;
+typedef gss_OID_set_desc *gss_OID_set;
+
+typedef struct gss_buffer_desc_struct {
+ size_t length;
+ void *value;
+} gss_buffer_desc, *gss_buffer_t;
+
+typedef struct gss_channel_bindings_struct {
+ OM_uint32 initiator_addrtype;
+ gss_buffer_desc initiator_address;
+ OM_uint32 acceptor_addrtype;
+ gss_buffer_desc acceptor_address;
+ gss_buffer_desc application_data;
+} *gss_channel_bindings_t;
+
+typedef void * gss_ctx_id_t;
+typedef void * gss_name_t;
+typedef void * gss_cred_id_t;
+
+typedef OM_uint32 gss_qop_t;
+
+/* Flag bits for context-level services. */
+
+#define GSS_C_DELEG_FLAG 1
+#define GSS_C_MUTUAL_FLAG 2
+#define GSS_C_REPLAY_FLAG 4
+#define GSS_C_SEQUENCE_FLAG 8
+#define GSS_C_CONF_FLAG 16
+#define GSS_C_INTEG_FLAG 32
+#define GSS_C_ANON_FLAG 64
+#define GSS_C_PROT_READY_FLAG 128
+#define GSS_C_TRANS_FLAG 256
+
+/* Credential usage options */
+#define GSS_C_BOTH 0
+#define GSS_C_INITIATE 1
+#define GSS_C_ACCEPT 2
+
+/* Status code types for gss_display_status */
+#define GSS_C_GSS_CODE 1
+#define GSS_C_MECH_CODE 2
+
+/* The constant definitions for channel-bindings address families */
+#define GSS_C_AF_UNSPEC 0
+#define GSS_C_AF_LOCAL 1
+#define GSS_C_AF_INET 2
+#define GSS_C_AF_IMPLINK 3
+#define GSS_C_AF_PUP 4
+#define GSS_C_AF_CHAOS 5
+#define GSS_C_AF_NS 6
+#define GSS_C_AF_NBS 7
+#define GSS_C_AF_ECMA 8
+#define GSS_C_AF_DATAKIT 9
+#define GSS_C_AF_CCITT 10
+#define GSS_C_AF_SNA 11
+#define GSS_C_AF_DECnet 12
+#define GSS_C_AF_DLI 13
+#define GSS_C_AF_LAT 14
+#define GSS_C_AF_HYLINK 15
+#define GSS_C_AF_APPLETALK 16
+#define GSS_C_AF_BSC 17
+#define GSS_C_AF_DSS 18
+#define GSS_C_AF_OSI 19
+#define GSS_C_AF_X25 21
+
+#define GSS_C_AF_NULLADDR 255
+
+/* Various Null values */
+#define GSS_C_NO_NAME ((gss_name_t) 0)
+#define GSS_C_NO_BUFFER ((gss_buffer_t) 0)
+#define GSS_C_NO_OID ((gss_OID) 0)
+#define GSS_C_NO_OID_SET ((gss_OID_set) 0)
+#define GSS_C_NO_CONTEXT ((gss_ctx_id_t) 0)
+#define GSS_C_NO_CREDENTIAL ((gss_cred_id_t) 0)
+#define GSS_C_NO_CHANNEL_BINDINGS ((gss_channel_bindings_t) 0)
+#define GSS_C_EMPTY_BUFFER {0, NULL}
+
+/* Major status codes */
+#define GSS_S_COMPLETE 0
+
+/* Some "helper" definitions to make the status code macros obvious. */
+#define GSS_C_CALLING_ERROR_OFFSET 24
+#define GSS_C_ROUTINE_ERROR_OFFSET 16
+
+#define GSS_C_SUPPLEMENTARY_OFFSET 0
+#define GSS_C_CALLING_ERROR_MASK 0377ul
+#define GSS_C_ROUTINE_ERROR_MASK 0377ul
+#define GSS_C_SUPPLEMENTARY_MASK 0177777ul
+
+/*
+ * The macros that test status codes for error conditions.
+ * Note that the GSS_ERROR() macro has changed slightly from
+ * the V1 GSS-API so that it now evaluates its argument
+ * only once.
+ */
+#define GSS_CALLING_ERROR(x) \
+ (x & (GSS_C_CALLING_ERROR_MASK << GSS_C_CALLING_ERROR_OFFSET))
+#define GSS_ROUTINE_ERROR(x) \
+ (x & (GSS_C_ROUTINE_ERROR_MASK << GSS_C_ROUTINE_ERROR_OFFSET))
+#define GSS_SUPPLEMENTARY_INFO(x) \
+ (x & (GSS_C_SUPPLEMENTARY_MASK << GSS_C_SUPPLEMENTARY_OFFSET))
+#define GSS_ERROR(x) \
+ (x & ((GSS_C_CALLING_ERROR_MASK << GSS_C_CALLING_ERROR_OFFSET) | \
+ (GSS_C_ROUTINE_ERROR_MASK << GSS_C_ROUTINE_ERROR_OFFSET)))
+
+/* Now the actual status code definitions */
+
+/* Calling errors: */
+#define GSS_S_CALL_INACCESSIBLE_READ \
+ (1ul << GSS_C_CALLING_ERROR_OFFSET)
+#define GSS_S_CALL_INACCESSIBLE_WRITE \
+ (2ul << GSS_C_CALLING_ERROR_OFFSET)
+#define GSS_S_CALL_BAD_STRUCTURE \
+ (3ul << GSS_C_CALLING_ERROR_OFFSET)
+
+/* Routine errors: */
+#define GSS_S_BAD_MECH (1ul << \
+ GSS_C_ROUTINE_ERROR_OFFSET)
+#define GSS_S_BAD_NAME (2ul << \
+ GSS_C_ROUTINE_ERROR_OFFSET)
+#define GSS_S_BAD_NAMETYPE (3ul << \
+ GSS_C_ROUTINE_ERROR_OFFSET)
+#define GSS_S_BAD_BINDINGS (4ul << \
+ GSS_C_ROUTINE_ERROR_OFFSET)
+#define GSS_S_BAD_STATUS (5ul << \
+ GSS_C_ROUTINE_ERROR_OFFSET)
+#define GSS_S_BAD_SIG (6ul << \
+ GSS_C_ROUTINE_ERROR_OFFSET)
+#define GSS_S_BAD_MIC GSS_S_BAD_SIG
+#define GSS_S_NO_CRED (7ul << \
+ GSS_C_ROUTINE_ERROR_OFFSET)
+#define GSS_S_NO_CONTEXT (8ul << \
+ GSS_C_ROUTINE_ERROR_OFFSET)
+#define GSS_S_DEFECTIVE_TOKEN (9ul << \
+ GSS_C_ROUTINE_ERROR_OFFSET)
+#define GSS_S_DEFECTIVE_CREDENTIAL (10ul << \
+ GSS_C_ROUTINE_ERROR_OFFSET)
+#define GSS_S_CREDENTIALS_EXPIRED (11ul << \
+ GSS_C_ROUTINE_ERROR_OFFSET)
+#define GSS_S_CONTEXT_EXPIRED (12ul << \
+ GSS_C_ROUTINE_ERROR_OFFSET)
+#define GSS_S_FAILURE (13ul << \
+ GSS_C_ROUTINE_ERROR_OFFSET)
+#define GSS_S_BAD_QOP (14ul << \
+ GSS_C_ROUTINE_ERROR_OFFSET)
+#define GSS_S_UNAUTHORIZED (15ul << \
+ GSS_C_ROUTINE_ERROR_OFFSET)
+#define GSS_S_UNAVAILABLE (16ul << \
+ GSS_C_ROUTINE_ERROR_OFFSET)
+#define GSS_S_DUPLICATE_ELEMENT (17ul << \
+ GSS_C_ROUTINE_ERROR_OFFSET)
+#define GSS_S_NAME_NOT_MN (18ul << \
+ GSS_C_ROUTINE_ERROR_OFFSET)
+
+/* Supplementary info bits: */
+#define GSS_S_CONTINUE_NEEDED \
+ (1ul << (GSS_C_SUPPLEMENTARY_OFFSET + 0))
+#define GSS_S_DUPLICATE_TOKEN \
+ (1ul << (GSS_C_SUPPLEMENTARY_OFFSET + 1))
+#define GSS_S_OLD_TOKEN \
+ (1ul << (GSS_C_SUPPLEMENTARY_OFFSET + 2))
+#define GSS_S_UNSEQ_TOKEN \
+ (1ul << (GSS_C_SUPPLEMENTARY_OFFSET + 3))
+#define GSS_S_GAP_TOKEN \
+ (1ul << (GSS_C_SUPPLEMENTARY_OFFSET + 4))
+
+extern const_gss_OID GSS_C_NT_USER_NAME;
+extern const_gss_OID GSS_C_NT_MACHINE_UID_NAME;
+extern const_gss_OID GSS_C_NT_STRING_UID_NAME;
+extern const_gss_OID GSS_C_NT_HOSTBASED_SERVICE_X;
+extern const_gss_OID GSS_C_NT_HOSTBASED_SERVICE;
+extern const_gss_OID GSS_C_NT_ANONYMOUS;
+extern const_gss_OID GSS_C_NT_EXPORT_NAME;
+
+#endif /* STATIC_GSSAPI */
+
+extern const gss_OID GSS_MECH_KRB5;
+
+/* GSSAPI functions we use.
+ * TODO: Replace with all GSSAPI functions from RFC?
+ */
+
+/* Calling convention, just in case we need one. */
+#ifndef GSS_CC
+#define GSS_CC
+#endif /*GSS_CC*/
+
+typedef OM_uint32 (GSS_CC *t_gss_release_cred)
+ (OM_uint32 * /*minor_status*/,
+ gss_cred_id_t * /*cred_handle*/);
+
+typedef OM_uint32 (GSS_CC *t_gss_init_sec_context)
+ (OM_uint32 * /*minor_status*/,
+ const gss_cred_id_t /*initiator_cred_handle*/,
+ gss_ctx_id_t * /*context_handle*/,
+ const gss_name_t /*target_name*/,
+ const gss_OID /*mech_type*/,
+ OM_uint32 /*req_flags*/,
+ OM_uint32 /*time_req*/,
+ const gss_channel_bindings_t /*input_chan_bindings*/,
+ const gss_buffer_t /*input_token*/,
+ gss_OID * /*actual_mech_type*/,
+ gss_buffer_t /*output_token*/,
+ OM_uint32 * /*ret_flags*/,
+ OM_uint32 * /*time_rec*/);
+
+typedef OM_uint32 (GSS_CC *t_gss_delete_sec_context)
+ (OM_uint32 * /*minor_status*/,
+ gss_ctx_id_t * /*context_handle*/,
+ gss_buffer_t /*output_token*/);
+
+typedef OM_uint32 (GSS_CC *t_gss_get_mic)
+ (OM_uint32 * /*minor_status*/,
+ const gss_ctx_id_t /*context_handle*/,
+ gss_qop_t /*qop_req*/,
+ const gss_buffer_t /*message_buffer*/,
+ gss_buffer_t /*msg_token*/);
+
+typedef OM_uint32 (GSS_CC *t_gss_display_status)
+ (OM_uint32 * /*minor_status*/,
+ OM_uint32 /*status_value*/,
+ int /*status_type*/,
+ const gss_OID /*mech_type*/,
+ OM_uint32 * /*message_context*/,
+ gss_buffer_t /*status_string*/);
+
+
+typedef OM_uint32 (GSS_CC *t_gss_import_name)
+ (OM_uint32 * /*minor_status*/,
+ const gss_buffer_t /*input_name_buffer*/,
+ const_gss_OID /*input_name_type*/,
+ gss_name_t * /*output_name*/);
+
+
+typedef OM_uint32 (GSS_CC *t_gss_release_name)
+ (OM_uint32 * /*minor_status*/,
+ gss_name_t * /*name*/);
+
+typedef OM_uint32 (GSS_CC *t_gss_release_buffer)
+ (OM_uint32 * /*minor_status*/,
+ gss_buffer_t /*buffer*/);
+
+struct gssapi_functions {
+ t_gss_delete_sec_context delete_sec_context;
+ t_gss_display_status display_status;
+ t_gss_get_mic get_mic;
+ t_gss_import_name import_name;
+ t_gss_init_sec_context init_sec_context;
+ t_gss_release_buffer release_buffer;
+ t_gss_release_cred release_cred;
+ t_gss_release_name release_name;
+};
+
+#endif /* NO_GSSAPI */
+
+#endif /* PUTTY_PGSSAPI_H */
--- /dev/null
+/*
+ * pinger.c: centralised module that deals with sending TS_PING
+ * keepalives, to avoid replicating this code in multiple backends.
+ */
+
+#include "putty.h"
+
+struct pinger_tag {
+ int interval;
+ int pending;
+ long next;
+ Backend *back;
+ void *backhandle;
+};
+
+static void pinger_schedule(Pinger pinger);
+
+static void pinger_timer(void *ctx, long now)
+{
+ Pinger pinger = (Pinger)ctx;
+
+ if (pinger->pending && now - pinger->next >= 0) {
+ pinger->back->special(pinger->backhandle, TS_PING);
+ pinger->pending = FALSE;
+ pinger_schedule(pinger);
+ }
+}
+
+static void pinger_schedule(Pinger pinger)
+{
+ int next;
+
+ if (!pinger->interval) {
+ pinger->pending = FALSE; /* cancel any pending ping */
+ return;
+ }
+
+ next = schedule_timer(pinger->interval * TICKSPERSEC,
+ pinger_timer, pinger);
+ if (!pinger->pending || next < pinger->next) {
+ pinger->next = next;
+ pinger->pending = TRUE;
+ }
+}
+
+Pinger pinger_new(Config *cfg, Backend *back, void *backhandle)
+{
+ Pinger pinger = snew(struct pinger_tag);
+
+ pinger->interval = cfg->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)
+{
+ if (oldcfg->ping_interval != newcfg->ping_interval) {
+ pinger->interval = newcfg->ping_interval;
+ pinger_schedule(pinger);
+ }
+}
+
+void pinger_free(Pinger pinger)
+{
+ expire_timer_context(pinger);
+ sfree(pinger);
+}
--- /dev/null
+/*
+ * SSH port forwarding.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "putty.h"
+#include "ssh.h"
+
+#ifndef FALSE
+#define FALSE 0
+#endif
+#ifndef TRUE
+#define TRUE 1
+#endif
+
+struct PFwdPrivate {
+ 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 */
+ 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.
+ */
+ 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.
+ */
+ char hostname[256+8];
+ int port;
+ /*
+ * 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;
+};
+
+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 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);
+ return 1;
+}
+
+static int pfd_receive(Plug plug, int urgent, char *data, int len)
+{
+ struct PFwdPrivate *pr = (struct PFwdPrivate *) plug;
+ if (pr->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++;
+
+ /*
+ * 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) {
+ /*
+ * 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) {
+ /* 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);
+ return 1;
+ }
+ if (pr->port <= 8) continue; /* haven't started user/hostname */
+ if (pr->hostname[pr->port-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) {
+ /*
+ * 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 */
+ 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);
+ 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]);
+ goto connect;
+ }
+ }
+
+ if ((pr->dynamic == 1 || (pr->dynamic >> 12) == 5) &&
+ pr->hostname[0] == 5) {
+ /*
+ * SOCKS 5.
+ */
+ if (pr->dynamic == 1)
+ pr->dynamic = 0x5000;
+
+ if (pr->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])
+ continue; /* no methods yet */
+ method = 0xFF; /* invalid */
+ for (i = 0; i < (unsigned char)pr->hostname[1]; i++)
+ if (pr->hostname[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 */
+ continue;
+ }
+
+ if (pr->dynamic == 0x5001) {
+ /*
+ * We're receiving a SOCKS request.
+ */
+ unsigned char reply[10]; /* SOCKS5 atyp=1 reply */
+ int atype, alen = 0;
+
+ /*
+ * Pre-fill reply packet.
+ * In all cases, we set BND.{HOST,ADDR} to 0.0.0.0:0
+ * (atyp=1) in the reply; if we succeed, we don't know
+ * the right answers, and if we fail, they should be
+ * ignored.
+ */
+ 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 (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) {
+ /* Not CONNECT or reserved field nonzero - error */
+ reply[1] = 1; /* generic failure */
+ sk_write(pr->s, (char *) reply, lenof(reply));
+ pfd_close(pr->s);
+ return 1;
+ }
+ /*
+ * Now we have a viable connect request. Switch
+ * on atype.
+ */
+ pr->port = GET_16BIT_MSB_FIRST(pr->hostname+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]);
+ 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';
+ 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);
+ return 1;
+ }
+ }
+ }
+
+ /*
+ * 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);
+ return 1;
+ }
+ return 1;
+
+ /*
+ * We come here when we're ready to make an actual
+ * connection.
+ */
+ connect:
+
+ /*
+ * Freeze the socket until the SSH server confirms the
+ * connection.
+ */
+ sk_set_frozen(pr->s, 1);
+
+ pr->c = new_sock_channel(pr->backhandle, pr->s);
+ if (pr->c == NULL) {
+ pfd_close(pr->s);
+ return 1;
+ } else {
+ /* asks to forward to the specified host/port for this */
+ ssh_send_port_open(pr->c, pr->hostname, pr->port, "forwarding");
+ }
+ pr->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;
+ }
+ }
+ if (pr->ready) {
+ if (sshfwd_write(pr->c, data, len) > 0) {
+ pr->throttled = 1;
+ sk_set_frozen(pr->s, 1);
+ }
+ }
+ return 1;
+}
+
+static void pfd_sent(Plug plug, int bufsize)
+{
+ struct PFwdPrivate *pr = (struct PFwdPrivate *) plug;
+
+ if (pr->c)
+ sshfwd_unthrottle(pr->c, bufsize);
+}
+
+/*
+ * Called when receiving a PORT OPEN from the server
+ */
+const char *pfd_newconnect(Socket *s, char *hostname, int port,
+ void *c, const Config *cfg, int addressfamily)
+{
+ static const struct plug_function_table fn_table = {
+ pfd_log,
+ pfd_closing,
+ pfd_receive,
+ pfd_sent,
+ NULL
+ };
+
+ SockAddr addr;
+ const char *err;
+ char *dummy_realhost;
+ struct PFwdPrivate *pr;
+
+ /*
+ * Try to find host.
+ */
+ addr = name_lookup(hostname, port, &dummy_realhost, cfg, addressfamily);
+ if ((err = sk_addr_error(addr)) != NULL) {
+ sk_addr_free(addr);
+ return err;
+ }
+
+ /*
+ * 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);
+ return NULL;
+}
+
+/*
+ called when someone connects to the local port
+ */
+
+static int pfd_accepting(Plug p, OSSocket sock)
+{
+ static const struct plug_function_table fn_table = {
+ pfd_log,
+ pfd_closing,
+ pfd_receive,
+ pfd_sent,
+ NULL
+ };
+ struct PFwdPrivate *pr, *org;
+ 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);
+ if ((err = sk_socket_error(s)) != NULL) {
+ sfree(pr);
+ 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 */
+ 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);
+
+ if (pr->c == NULL) {
+ sfree(pr);
+ return 1;
+ } else {
+ /* asks to forward to the specified host/port for this */
+ ssh_send_port_open(pr->c, pr->hostname, pr->port, "forwarding");
+ }
+ }
+
+ return 0;
+}
+
+
+/* Add a new forwarding from port -> desthost:destport
+ sets up a listener on the local machine on (srcaddr:)port
+ */
+const char *pfd_addforward(char *desthost, int destport, char *srcaddr,
+ int port, void *backhandle, const Config *cfg,
+ void **sockdata, 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
+ };
+
+ const char *err;
+ struct PFwdPrivate *pr;
+ Socket s;
+
+ /*
+ * Open socket.
+ */
+ pr = snew(struct PFwdPrivate);
+ pr->buffer = NULL;
+ pr->fn = &fn_table;
+ pr->c = NULL;
+ if (desthost) {
+ strcpy(pr->hostname, desthost);
+ pr->port = destport;
+ pr->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;
+
+ return NULL;
+}
+
+void pfd_close(Socket s)
+{
+ struct PFwdPrivate *pr;
+
+ if (!s)
+ return;
+
+ pr = (struct PFwdPrivate *) sk_get_private_ptr(s);
+
+ sfree(pr->buffer);
+ sfree(pr);
+
+ sk_close(s);
+}
+
+/*
+ * 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);
+}
+
+/*
+ * Called to send data down the raw connection.
+ */
+int pfd_send(Socket s, char *data, int len)
+{
+ if (s == NULL)
+ return 0;
+ return sk_write(s, data, len);
+}
+
+
+void pfd_confirm(Socket s)
+{
+ struct PFwdPrivate *pr;
+
+ if (s == 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;
+ }
+}
--- /dev/null
+/*
+ * pproxy.c: dummy implementation of platform_new_connection(), to
+ * be supplanted on any platform which has its own local proxy
+ * method.
+ */
+
+#include "putty.h"
+#include "network.h"
+#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)
+{
+ return NULL;
+}
--- /dev/null
+/*
+ * Network proxy abstraction in PuTTY
+ *
+ * A proxy layer, if necessary, wedges itself between the network
+ * code and the higher level backend.
+ */
+
+#include <assert.h>
+#include <ctype.h>
+#include <string.h>
+
+#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))
+
+/*
+ * Call this when proxy negotiation is complete, so that this
+ * socket can begin working normally.
+ */
+void proxy_activate (Proxy_Socket p)
+{
+ void *data;
+ int len;
+ long output_before, output_after;
+
+ p->state = PROXY_STATE_ACTIVE;
+
+ /* we want to ignore new receive events until we have sent
+ * all of our buffered receive data.
+ */
+ sk_set_frozen(p->sub_socket, 1);
+
+ /* how many bytes of output have we buffered? */
+ output_before = bufchain_size(&p->pending_oob_output_data) +
+ bufchain_size(&p->pending_output_data);
+ /* and keep track of how many bytes do not get sent. */
+ output_after = 0;
+
+ /* send buffered OOB writes */
+ while (bufchain_size(&p->pending_oob_output_data) > 0) {
+ bufchain_prefix(&p->pending_oob_output_data, &data, &len);
+ output_after += sk_write_oob(p->sub_socket, data, len);
+ bufchain_consume(&p->pending_oob_output_data, len);
+ }
+
+ /* send buffered normal writes */
+ while (bufchain_size(&p->pending_output_data) > 0) {
+ bufchain_prefix(&p->pending_output_data, &data, &len);
+ output_after += sk_write(p->sub_socket, data, len);
+ bufchain_consume(&p->pending_output_data, len);
+ }
+
+ /* if we managed to send any data, let the higher levels know. */
+ if (output_after < output_before)
+ plug_sent(p->plug, output_after);
+
+ /* 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 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)
+ sk_set_frozen((Socket)p, 0);
+}
+
+/* basic proxy socket functions */
+
+static Plug sk_proxy_plug (Socket s, Plug p)
+{
+ Proxy_Socket ps = (Proxy_Socket) s;
+ Plug ret = ps->plug;
+ if (p)
+ ps->plug = p;
+ return ret;
+}
+
+static void sk_proxy_close (Socket s)
+{
+ Proxy_Socket ps = (Proxy_Socket) s;
+
+ sk_close(ps->sub_socket);
+ sk_addr_free(ps->remote_addr);
+ sfree(ps);
+}
+
+static int sk_proxy_write (Socket s, const char *data, int len)
+{
+ Proxy_Socket ps = (Proxy_Socket) s;
+
+ if (ps->state != PROXY_STATE_ACTIVE) {
+ bufchain_add(&ps->pending_output_data, data, len);
+ return bufchain_size(&ps->pending_output_data);
+ }
+ return sk_write(ps->sub_socket, data, len);
+}
+
+static int sk_proxy_write_oob (Socket s, const char *data, int len)
+{
+ Proxy_Socket ps = (Proxy_Socket) s;
+
+ if (ps->state != PROXY_STATE_ACTIVE) {
+ bufchain_clear(&ps->pending_output_data);
+ bufchain_clear(&ps->pending_oob_output_data);
+ bufchain_add(&ps->pending_oob_output_data, data, len);
+ return len;
+ }
+ return sk_write_oob(ps->sub_socket, data, len);
+}
+
+static void sk_proxy_flush (Socket s)
+{
+ Proxy_Socket ps = (Proxy_Socket) s;
+
+ if (ps->state != PROXY_STATE_ACTIVE) {
+ ps->pending_flush = 1;
+ 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) {
+ ps->freeze = is_frozen;
+ return;
+ }
+
+ /* handle any remaining buffered recv data first */
+ if (bufchain_size(&ps->pending_input_data) > 0) {
+ ps->freeze = is_frozen;
+
+ /* loop while we still have buffered data, and while we are
+ * unfrozen. the plug_receive call in the loop could result
+ * in a call back into this function refreezing the socket,
+ * so we have to check each time.
+ */
+ while (!ps->freeze && bufchain_size(&ps->pending_input_data) > 0) {
+ void *data;
+ char databuf[512];
+ int len;
+ bufchain_prefix(&ps->pending_input_data, &data, &len);
+ if (len > lenof(databuf))
+ len = lenof(databuf);
+ memcpy(databuf, data, len);
+ bufchain_consume(&ps->pending_input_data, len);
+ plug_receive(ps->plug, 0, databuf, len);
+ }
+
+ /* if we're still frozen, we'll have to wait for another
+ * call from the backend to finish unbuffering the data.
+ */
+ if (ps->freeze) return;
+ }
+
+ sk_set_frozen(ps->sub_socket, is_frozen);
+}
+
+static const char * sk_proxy_socket_error (Socket s)
+{
+ Proxy_Socket ps = (Proxy_Socket) s;
+ if (ps->error != NULL || ps->sub_socket == NULL) {
+ return ps->error;
+ }
+ return sk_socket_error(ps->sub_socket);
+}
+
+/* basic proxy plug functions */
+
+static void plug_proxy_log(Plug plug, int type, SockAddr addr, int port,
+ const char *error_msg, int error_code)
+{
+ Proxy_Plug pp = (Proxy_Plug) plug;
+ Proxy_Socket ps = pp->proxy_socket;
+
+ plug_log(ps->plug, type, addr, port, error_msg, error_code);
+}
+
+static int plug_proxy_closing (Plug p, const char *error_msg,
+ int error_code, int calling_back)
+{
+ Proxy_Plug pp = (Proxy_Plug) p;
+ Proxy_Socket ps = pp->proxy_socket;
+
+ if (ps->state != PROXY_STATE_ACTIVE) {
+ ps->closing_error_msg = error_msg;
+ ps->closing_error_code = error_code;
+ ps->closing_calling_back = calling_back;
+ return ps->negotiate(ps, PROXY_CHANGE_CLOSING);
+ }
+ return plug_closing(ps->plug, error_msg,
+ error_code, calling_back);
+}
+
+static int plug_proxy_receive (Plug p, int urgent, char *data, int len)
+{
+ Proxy_Plug pp = (Proxy_Plug) p;
+ Proxy_Socket ps = pp->proxy_socket;
+
+ if (ps->state != PROXY_STATE_ACTIVE) {
+ /* we will lose the urgentness of this data, but since most,
+ * if not all, of this data will be consumed by the negotiation
+ * process, hopefully it won't affect the protocol above us
+ */
+ bufchain_add(&ps->pending_input_data, data, len);
+ ps->receive_urgent = urgent;
+ ps->receive_data = data;
+ ps->receive_len = len;
+ return ps->negotiate(ps, PROXY_CHANGE_RECEIVE);
+ }
+ return plug_receive(ps->plug, urgent, data, len);
+}
+
+static void plug_proxy_sent (Plug p, int bufsize)
+{
+ Proxy_Plug pp = (Proxy_Plug) p;
+ Proxy_Socket ps = pp->proxy_socket;
+
+ if (ps->state != PROXY_STATE_ACTIVE) {
+ ps->sent_bufsize = bufsize;
+ ps->negotiate(ps, PROXY_CHANGE_SENT);
+ return;
+ }
+ plug_sent(ps->plug, bufsize);
+}
+
+static int plug_proxy_accepting (Plug p, OSSocket sock)
+{
+ Proxy_Plug pp = (Proxy_Plug) p;
+ Proxy_Socket ps = pp->proxy_socket;
+
+ if (ps->state != PROXY_STATE_ACTIVE) {
+ ps->accepting_sock = sock;
+ return ps->negotiate(ps, PROXY_CHANGE_ACCEPTING);
+ }
+ return plug_accepting(ps->plug, sock);
+}
+
+/*
+ * 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 s = 0, e = 0;
+ char hostip[64];
+ int hostip_len, hostname_len;
+ const char *exclude_list;
+
+ /*
+ * Check the host name and IP against the hard-coded
+ * representations of `localhost'.
+ */
+ if (!cfg->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 */
+ if (addr) {
+ sk_getaddr(addr, hostip, 64);
+ hostip_len = strlen(hostip);
+ } else
+ hostip_len = 0; /* placate gcc; shouldn't be required */
+
+ hostname_len = strlen(hostname);
+
+ exclude_list = cfg->proxy_exclude_list;
+
+ /* now parse the exclude list, and see if either our IP
+ * or hostname matches anything in it.
+ */
+
+ while (exclude_list[s]) {
+ while (exclude_list[s] &&
+ (isspace((unsigned char)exclude_list[s]) ||
+ exclude_list[s] == ',')) s++;
+
+ if (!exclude_list[s]) break;
+
+ e = s;
+
+ while (exclude_list[e] &&
+ (isalnum((unsigned char)exclude_list[e]) ||
+ exclude_list[e] == '-' ||
+ exclude_list[e] == '.' ||
+ exclude_list[e] == '*')) e++;
+
+ if (exclude_list[s] == '*') {
+ /* wildcard at beginning of entry */
+
+ if ((addr && strnicmp(hostip + hostip_len - (e - s - 1),
+ exclude_list + s + 1, e - s - 1) == 0) ||
+ strnicmp(hostname + hostname_len - (e - s - 1),
+ exclude_list + s + 1, e - s - 1) == 0)
+ return 0; /* IP/hostname range excluded. do not use proxy. */
+
+ } else if (exclude_list[e-1] == '*') {
+ /* wildcard at end of entry */
+
+ if ((addr && strnicmp(hostip, exclude_list + s, e - s - 1) == 0) ||
+ strnicmp(hostname, exclude_list + s, e - s - 1) == 0)
+ return 0; /* IP/hostname range excluded. do not use proxy. */
+
+ } else {
+ /* no wildcard at either end, so let's try an absolute
+ * match (ie. a specific IP)
+ */
+
+ if (addr && strnicmp(hostip, exclude_list + s, e - s) == 0)
+ return 0; /* IP/hostname excluded. do not use proxy. */
+ if (strnicmp(hostname, exclude_list + s, e - s) == 0)
+ return 0; /* IP/hostname excluded. do not use proxy. */
+ }
+
+ s = e;
+
+ /* Make sure we really have reached the next comma or end-of-string */
+ while (exclude_list[s] &&
+ !isspace((unsigned char)exclude_list[s]) &&
+ exclude_list[s] != ',') s++;
+ }
+
+ /* 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)
+{
+ if (cfg->proxy_type != PROXY_NONE &&
+ do_proxy_dns(cfg) &&
+ proxy_for_destination(NULL, host, port, cfg)) {
+ *canonicalname = dupstr(host);
+ return sk_nonamelookup(host);
+ }
+
+ return sk_namelookup(host, canonicalname, addressfamily);
+}
+
+Socket new_connection(SockAddr addr, char *hostname,
+ int port, int privport,
+ int oobinline, int nodelay, int keepalive,
+ Plug plug, const Config *cfg)
+{
+ static const struct socket_function_table socket_fn_table = {
+ sk_proxy_plug,
+ sk_proxy_close,
+ sk_proxy_write,
+ sk_proxy_write_oob,
+ 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 = {
+ plug_proxy_log,
+ plug_proxy_closing,
+ plug_proxy_receive,
+ plug_proxy_sent,
+ plug_proxy_accepting
+ };
+
+ if (cfg->proxy_type != PROXY_NONE &&
+ proxy_for_destination(addr, hostname, port, cfg))
+ {
+ Proxy_Socket ret;
+ Proxy_Plug pplug;
+ SockAddr proxy_addr;
+ char *proxy_canonical_name;
+ Socket sret;
+
+ if ((sret = platform_new_connection(addr, hostname, port, privport,
+ oobinline, nodelay, keepalive,
+ plug, cfg)) !=
+ NULL)
+ return sret;
+
+ ret = snew(struct Socket_proxy_tag);
+ ret->fn = &socket_fn_table;
+ ret->cfg = *cfg; /* STRUCTURE COPY */
+ 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->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) {
+ ret->negotiate = proxy_http_negotiate;
+ } else if (cfg->proxy_type == PROXY_SOCKS4) {
+ ret->negotiate = proxy_socks4_negotiate;
+ } else if (cfg->proxy_type == PROXY_SOCKS5) {
+ ret->negotiate = proxy_socks5_negotiate;
+ } else if (cfg->proxy_type == PROXY_TELNET) {
+ ret->negotiate = proxy_telnet_negotiate;
+ } else {
+ ret->error = "Proxy error: Unknown proxy method";
+ return (Socket) ret;
+ }
+
+ /* create the proxy plug to map calls from the actual
+ * socket into our proxy socket layer */
+ 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);
+ if (sk_addr_error(proxy_addr) != NULL) {
+ ret->error = "Proxy error: Unable to resolve proxy host name";
+ 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,
+ privport, oobinline,
+ nodelay, keepalive, (Plug) pplug);
+ if (sk_socket_error(ret->sub_socket) != NULL)
+ return (Socket) ret;
+
+ /* start the proxy negotiation process... */
+ sk_set_frozen(ret->sub_socket, 0);
+ ret->negotiate(ret, PROXY_CHANGE_NEW);
+
+ return (Socket) ret;
+ }
+
+ /* 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)
+{
+ /* TODO: SOCKS (and potentially others) support inbound
+ * TODO: connections via the proxy. support them.
+ */
+
+ return sk_newlistener(srcaddr, port, plug, local_host_only, addressfamily);
+}
+
+/* ----------------------------------------------------------------------
+ * HTTP CONNECT proxy type.
+ */
+
+static int get_line_end (char * data, int len)
+{
+ int off = 0;
+
+ while (off < len)
+ {
+ if (data[off] == '\n') {
+ /* we have a newline */
+ off++;
+
+ /* is that the only thing on this line? */
+ if (off <= 2) return off;
+
+ /* if not, then there is the possibility that this header
+ * continues onto the next line, if it starts with a space
+ * or a tab.
+ */
+
+ if (off + 1 < len &&
+ data[off+1] != ' ' &&
+ data[off+1] != '\t') return off;
+
+ /* the line does continue, so we have to keep going
+ * until we see an the header's "real" end of line.
+ */
+ off++;
+ }
+
+ off++;
+ }
+
+ return -1;
+}
+
+int proxy_http_negotiate (Proxy_Socket p, int change)
+{
+ if (p->state == PROXY_STATE_NEW) {
+ /* we are just beginning the proxy negotiate process,
+ * 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];
+
+ 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];
+ int i, j, len;
+ sprintf(buf, "%s:%s", p->cfg.proxy_username, p->cfg.proxy_password);
+ len = strlen(buf);
+ 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));
+ }
+
+ sk_write(p->sub_socket, "\r\n", 2);
+
+ p->state = 1;
+ return 0;
+ }
+
+ if (change == PROXY_CHANGE_CLOSING) {
+ /* if our proxy negotiation process involves closing and opening
+ * new sockets, then we would want to intercept this closing
+ * callback when we were expecting it. if we aren't anticipating
+ * a socket close, then some error must have occurred. we'll
+ * just pass those errors up to the backend.
+ */
+ return plug_closing(p->plug, p->closing_error_msg,
+ p->closing_error_code,
+ p->closing_calling_back);
+ }
+
+ if (change == PROXY_CHANGE_SENT) {
+ /* some (or all) of what we wrote to the proxy was sent.
+ * we don't do anything new, however, until we receive the
+ * proxy's response. we might want to set a timer so we can
+ * timeout the proxy negotiation after a while...
+ */
+ return 0;
+ }
+
+ if (change == PROXY_CHANGE_ACCEPTING) {
+ /* 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);
+ }
+
+ if (change == PROXY_CHANGE_RECEIVE) {
+ /* we have received data from the underlying socket, which
+ * we'll need to parse, process, and respond to appropriately.
+ */
+
+ char *data, *datap;
+ int len;
+ int eol;
+
+ if (p->state == 1) {
+
+ int min_ver, maj_ver, status;
+
+ /* get the status line */
+ len = bufchain_size(&p->pending_input_data);
+ assert(len > 0); /* or we wouldn't be here */
+ data = snewn(len+1, char);
+ bufchain_fetch(&p->pending_input_data, data, len);
+ /*
+ * We must NUL-terminate this data, because Windows
+ * sscanf appears to require a NUL at the end of the
+ * string because it strlens it _first_. Sigh.
+ */
+ data[len] = '\0';
+
+ eol = get_line_end(data, len);
+ if (eol < 0) {
+ sfree(data);
+ return 1;
+ }
+
+ status = -1;
+ /* We can't rely on whether the %n incremented the sscanf return */
+ if (sscanf((char *)data, "HTTP/%i.%i %n",
+ &maj_ver, &min_ver, &status) < 2 || status == -1) {
+ plug_closing(p->plug, "Proxy error: HTTP response was absent",
+ PROXY_ERROR_GENERAL, 0);
+ sfree(data);
+ return 1;
+ }
+
+ /* remove the status line from the input buffer. */
+ bufchain_consume(&p->pending_input_data, eol);
+ if (data[status] != '2') {
+ /* error */
+ char *buf;
+ data[eol] = '\0';
+ while (eol > status &&
+ (data[eol-1] == '\r' || data[eol-1] == '\n'))
+ data[--eol] = '\0';
+ buf = dupprintf("Proxy error: %s", data+status);
+ plug_closing(p->plug, buf, PROXY_ERROR_GENERAL, 0);
+ sfree(buf);
+ sfree(data);
+ return 1;
+ }
+
+ sfree(data);
+
+ p->state = 2;
+ }
+
+ if (p->state == 2) {
+
+ /* get headers. we're done when we get a
+ * header of length 2, (ie. just "\r\n")
+ */
+
+ len = bufchain_size(&p->pending_input_data);
+ assert(len > 0); /* or we wouldn't be here */
+ data = snewn(len, char);
+ datap = data;
+ bufchain_fetch(&p->pending_input_data, data, len);
+
+ eol = get_line_end(datap, len);
+ if (eol < 0) {
+ sfree(data);
+ return 1;
+ }
+ while (eol > 2)
+ {
+ bufchain_consume(&p->pending_input_data, eol);
+ datap += eol;
+ len -= eol;
+ eol = get_line_end(datap, len);
+ }
+
+ if (eol == 2) {
+ /* we're done */
+ bufchain_consume(&p->pending_input_data, 2);
+ proxy_activate(p);
+ /* proxy activate will have dealt with
+ * whatever is left of the buffer */
+ sfree(data);
+ return 1;
+ }
+
+ sfree(data);
+ return 1;
+ }
+ }
+
+ plug_closing(p->plug, "Proxy error: unexpected proxy error",
+ PROXY_ERROR_UNEXPECTED, 0);
+ return 1;
+}
+
+/* ----------------------------------------------------------------------
+ * SOCKS proxy type.
+ */
+
+/* SOCKS version 4 */
+int proxy_socks4_negotiate (Proxy_Socket p, int change)
+{
+ if (p->state == PROXY_CHANGE_NEW) {
+
+ /* request format:
+ * version number (1 byte) = 4
+ * command code (1 byte)
+ * 1 = CONNECT
+ * 2 = BIND
+ * dest. port (2 bytes) [network order]
+ * dest. address (4 bytes)
+ * user ID (variable length, null terminated string)
+ */
+
+ int length, type, namelen;
+ char *command, addr[4], hostname[512];
+
+ 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);
+ return 1;
+ } else if (type == ADDRTYPE_IPV4) {
+ namelen = 0;
+ sk_addrcopy(p->remote_addr, addr);
+ } else { /* type == ADDRTYPE_NAME */
+ assert(type == ADDRTYPE_NAME);
+ sk_getaddr(p->remote_addr, hostname, lenof(hostname));
+ 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;
+ command = snewn(length, char);
+ strcpy(command + 8, p->cfg.proxy_username);
+
+ command[0] = 4; /* version 4 */
+ command[1] = 1; /* CONNECT command */
+
+ /* port */
+ command[2] = (char) (p->remote_port >> 8) & 0xff;
+ command[3] = (char) p->remote_port & 0xff;
+
+ /* address */
+ memcpy(command + 4, addr, 4);
+
+ /* hostname */
+ memcpy(command + 8 + strlen(p->cfg.proxy_username) + 1,
+ hostname, namelen);
+
+ sk_write(p->sub_socket, command, length);
+ sfree(command);
+
+ p->state = 1;
+ return 0;
+ }
+
+ if (change == PROXY_CHANGE_CLOSING) {
+ /* if our proxy negotiation process involves closing and opening
+ * new sockets, then we would want to intercept this closing
+ * callback when we were expecting it. if we aren't anticipating
+ * a socket close, then some error must have occurred. we'll
+ * just pass those errors up to the backend.
+ */
+ return plug_closing(p->plug, p->closing_error_msg,
+ p->closing_error_code,
+ p->closing_calling_back);
+ }
+
+ if (change == PROXY_CHANGE_SENT) {
+ /* some (or all) of what we wrote to the proxy was sent.
+ * we don't do anything new, however, until we receive the
+ * proxy's response. we might want to set a timer so we can
+ * timeout the proxy negotiation after a while...
+ */
+ return 0;
+ }
+
+ if (change == PROXY_CHANGE_ACCEPTING) {
+ /* 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);
+ }
+
+ if (change == PROXY_CHANGE_RECEIVE) {
+ /* we have received data from the underlying socket, which
+ * we'll need to parse, process, and respond to appropriately.
+ */
+
+ if (p->state == 1) {
+ /* response format:
+ * version number (1 byte) = 4
+ * reply code (1 byte)
+ * 90 = request granted
+ * 91 = request rejected or failed
+ * 92 = request rejected due to lack of IDENTD on client
+ * 93 = request rejected due to difference in user ID
+ * (what we sent vs. what IDENTD said)
+ * dest. port (2 bytes)
+ * dest. address (4 bytes)
+ */
+
+ char data[8];
+
+ if (bufchain_size(&p->pending_input_data) < 8)
+ return 1; /* not got anything yet */
+
+ /* get the response */
+ bufchain_fetch(&p->pending_input_data, data, 8);
+
+ if (data[0] != 0) {
+ plug_closing(p->plug, "Proxy error: SOCKS proxy responded with "
+ "unexpected reply code version",
+ PROXY_ERROR_GENERAL, 0);
+ return 1;
+ }
+
+ if (data[1] != 90) {
+
+ switch (data[1]) {
+ case 92:
+ plug_closing(p->plug, "Proxy error: SOCKS server wanted IDENTD on client",
+ PROXY_ERROR_GENERAL, 0);
+ break;
+ case 93:
+ plug_closing(p->plug, "Proxy error: Username and IDENTD on client don't agree",
+ PROXY_ERROR_GENERAL, 0);
+ break;
+ case 91:
+ default:
+ plug_closing(p->plug, "Proxy error: Error while communicating with proxy",
+ PROXY_ERROR_GENERAL, 0);
+ break;
+ }
+
+ return 1;
+ }
+ bufchain_consume(&p->pending_input_data, 8);
+
+ /* we're done */
+ proxy_activate(p);
+ /* proxy activate will have dealt with
+ * whatever is left of the buffer */
+ return 1;
+ }
+ }
+
+ plug_closing(p->plug, "Proxy error: unexpected proxy error",
+ PROXY_ERROR_UNEXPECTED, 0);
+ return 1;
+}
+
+/* SOCKS version 5 */
+int proxy_socks5_negotiate (Proxy_Socket p, int change)
+{
+ if (p->state == PROXY_CHANGE_NEW) {
+
+ /* initial command:
+ * version number (1 byte) = 5
+ * number of available authentication methods (1 byte)
+ * available authentication methods (1 byte * previous value)
+ * authentication methods:
+ * 0x00 = no authentication
+ * 0x01 = GSSAPI
+ * 0x02 = username/password
+ * 0x03 = CHAP
+ */
+
+ char command[5];
+ int len;
+
+ command[0] = 5; /* version 5 */
+ if (p->cfg.proxy_username[0] || p->cfg.proxy_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 */
+ } else {
+ command[1] = 1; /* one methods supported: */
+ command[2] = 0x00; /* no authentication */
+ len = 3;
+ }
+
+ sk_write(p->sub_socket, command, len);
+
+ p->state = 1;
+ return 0;
+ }
+
+ if (change == PROXY_CHANGE_CLOSING) {
+ /* if our proxy negotiation process involves closing and opening
+ * new sockets, then we would want to intercept this closing
+ * callback when we were expecting it. if we aren't anticipating
+ * a socket close, then some error must have occurred. we'll
+ * just pass those errors up to the backend.
+ */
+ return plug_closing(p->plug, p->closing_error_msg,
+ p->closing_error_code,
+ p->closing_calling_back);
+ }
+
+ if (change == PROXY_CHANGE_SENT) {
+ /* some (or all) of what we wrote to the proxy was sent.
+ * we don't do anything new, however, until we receive the
+ * proxy's response. we might want to set a timer so we can
+ * timeout the proxy negotiation after a while...
+ */
+ return 0;
+ }
+
+ if (change == PROXY_CHANGE_ACCEPTING) {
+ /* 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);
+ }
+
+ if (change == PROXY_CHANGE_RECEIVE) {
+ /* we have received data from the underlying socket, which
+ * we'll need to parse, process, and respond to appropriately.
+ */
+
+ if (p->state == 1) {
+
+ /* initial response:
+ * version number (1 byte) = 5
+ * authentication method (1 byte)
+ * authentication methods:
+ * 0x00 = no authentication
+ * 0x01 = GSSAPI
+ * 0x02 = username/password
+ * 0x03 = CHAP
+ * 0xff = no acceptable methods
+ */
+ char data[2];
+
+ if (bufchain_size(&p->pending_input_data) < 2)
+ return 1; /* not got anything yet */
+
+ /* get the response */
+ bufchain_fetch(&p->pending_input_data, data, 2);
+
+ if (data[0] != 5) {
+ plug_closing(p->plug, "Proxy error: SOCKS proxy returned unexpected version",
+ PROXY_ERROR_GENERAL, 0);
+ return 1;
+ }
+
+ if (data[1] == 0x00) p->state = 2; /* no authentication needed */
+ else if (data[1] == 0x01) p->state = 4; /* GSSAPI authentication */
+ else if (data[1] == 0x02) p->state = 5; /* username/password authentication */
+ else if (data[1] == 0x03) p->state = 6; /* CHAP authentication */
+ else {
+ plug_closing(p->plug, "Proxy error: SOCKS proxy did not accept our authentication",
+ PROXY_ERROR_GENERAL, 0);
+ return 1;
+ }
+ bufchain_consume(&p->pending_input_data, 2);
+ }
+
+ if (p->state == 7) {
+
+ /* password authentication reply format:
+ * version number (1 bytes) = 1
+ * reply code (1 byte)
+ * 0 = succeeded
+ * >0 = failed
+ */
+ char data[2];
+
+ if (bufchain_size(&p->pending_input_data) < 2)
+ return 1; /* not got anything yet */
+
+ /* get the response */
+ bufchain_fetch(&p->pending_input_data, data, 2);
+
+ if (data[0] != 1) {
+ plug_closing(p->plug, "Proxy error: SOCKS password "
+ "subnegotiation contained wrong version number",
+ PROXY_ERROR_GENERAL, 0);
+ return 1;
+ }
+
+ if (data[1] != 0) {
+
+ plug_closing(p->plug, "Proxy error: SOCKS proxy refused"
+ " password authentication",
+ PROXY_ERROR_GENERAL, 0);
+ return 1;
+ }
+
+ bufchain_consume(&p->pending_input_data, 2);
+ p->state = 2; /* now proceed as authenticated */
+ }
+
+ if (p->state == 8) {
+ int ret;
+ ret = proxy_socks5_handlechap(p);
+ if (ret) return ret;
+ }
+
+ if (p->state == 2) {
+
+ /* request format:
+ * version number (1 byte) = 5
+ * command code (1 byte)
+ * 1 = CONNECT
+ * 2 = BIND
+ * 3 = UDP ASSOCIATE
+ * reserved (1 byte) = 0x00
+ * address type (1 byte)
+ * 1 = IPv4
+ * 3 = domainname (first byte has length, no terminating null)
+ * 4 = IPv6
+ * dest. address (variable)
+ * dest. port (2 bytes) [network order]
+ */
+
+ char command[512];
+ int len;
+ int type;
+
+ type = sk_addrtype(p->remote_addr);
+ if (type == ADDRTYPE_IPV4) {
+ len = 10; /* 4 hdr + 4 addr + 2 trailer */
+ command[3] = 1; /* IPv4 */
+ sk_addrcopy(p->remote_addr, command+4);
+ } else if (type == ADDRTYPE_IPV6) {
+ len = 22; /* 4 hdr + 16 addr + 2 trailer */
+ command[3] = 4; /* IPv6 */
+ sk_addrcopy(p->remote_addr, command+4);
+ } else {
+ assert(type == ADDRTYPE_NAME);
+ command[3] = 3;
+ sk_getaddr(p->remote_addr, command+5, 256);
+ command[4] = strlen(command+5);
+ len = 7 + command[4]; /* 4 hdr, 1 len, N addr, 2 trailer */
+ }
+
+ command[0] = 5; /* version 5 */
+ command[1] = 1; /* CONNECT command */
+ command[2] = 0x00;
+
+ /* port */
+ command[len-2] = (char) (p->remote_port >> 8) & 0xff;
+ command[len-1] = (char) p->remote_port & 0xff;
+
+ sk_write(p->sub_socket, command, len);
+
+ p->state = 3;
+ return 1;
+ }
+
+ if (p->state == 3) {
+
+ /* reply format:
+ * version number (1 bytes) = 5
+ * reply code (1 byte)
+ * 0 = succeeded
+ * 1 = general SOCKS server failure
+ * 2 = connection not allowed by ruleset
+ * 3 = network unreachable
+ * 4 = host unreachable
+ * 5 = connection refused
+ * 6 = TTL expired
+ * 7 = command not supported
+ * 8 = address type not supported
+ * reserved (1 byte) = x00
+ * address type (1 byte)
+ * 1 = IPv4
+ * 3 = domainname (first byte has length, no terminating null)
+ * 4 = IPv6
+ * server bound address (variable)
+ * server bound port (2 bytes) [network order]
+ */
+ char data[5];
+ int len;
+
+ /* First 5 bytes of packet are enough to tell its length. */
+ if (bufchain_size(&p->pending_input_data) < 5)
+ return 1; /* not got anything yet */
+
+ /* get the response */
+ bufchain_fetch(&p->pending_input_data, data, 5);
+
+ if (data[0] != 5) {
+ plug_closing(p->plug, "Proxy error: SOCKS proxy returned wrong version number",
+ PROXY_ERROR_GENERAL, 0);
+ return 1;
+ }
+
+ if (data[1] != 0) {
+ char buf[256];
+
+ strcpy(buf, "Proxy error: ");
+
+ switch (data[1]) {
+ case 1: strcat(buf, "General SOCKS server failure"); break;
+ case 2: strcat(buf, "Connection not allowed by ruleset"); break;
+ case 3: strcat(buf, "Network unreachable"); break;
+ case 4: strcat(buf, "Host unreachable"); break;
+ case 5: strcat(buf, "Connection refused"); break;
+ case 6: strcat(buf, "TTL expired"); break;
+ case 7: strcat(buf, "Command not supported"); break;
+ case 8: strcat(buf, "Address type not supported"); break;
+ default: sprintf(buf+strlen(buf),
+ "Unrecognised SOCKS error code %d",
+ data[1]);
+ break;
+ }
+ plug_closing(p->plug, buf, PROXY_ERROR_GENERAL, 0);
+
+ return 1;
+ }
+
+ /*
+ * Eat the rest of the reply packet.
+ */
+ len = 6; /* first 4 bytes, last 2 */
+ switch (data[3]) {
+ case 1: len += 4; break; /* IPv4 address */
+ case 4: len += 16; break;/* IPv6 address */
+ case 3: len += (unsigned char)data[4]; break; /* domain name */
+ default:
+ plug_closing(p->plug, "Proxy error: SOCKS proxy returned "
+ "unrecognised address format",
+ PROXY_ERROR_GENERAL, 0);
+ return 1;
+ }
+ if (bufchain_size(&p->pending_input_data) < len)
+ return 1; /* not got whole reply yet */
+ bufchain_consume(&p->pending_input_data, len);
+
+ /* we're done */
+ proxy_activate(p);
+ return 1;
+ }
+
+ if (p->state == 4) {
+ /* TODO: Handle GSSAPI authentication */
+ plug_closing(p->plug, "Proxy error: We don't support GSSAPI authentication",
+ PROXY_ERROR_GENERAL, 0);
+ return 1;
+ }
+
+ if (p->state == 5) {
+ if (p->cfg.proxy_username[0] || p->cfg.proxy_password[0]) {
+ char userpwbuf[514];
+ int ulen, plen;
+ ulen = strlen(p->cfg.proxy_username);
+ if (ulen > 255) ulen = 255; if (ulen < 1) ulen = 1;
+ plen = strlen(p->cfg.proxy_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);
+ userpwbuf[ulen+2] = plen;
+ memcpy(userpwbuf+ulen+3, p->cfg.proxy_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 "
+ "didn't offer it!",
+ PROXY_ERROR_GENERAL, 0);
+ return 1;
+ }
+
+ if (p->state == 6) {
+ int ret;
+ ret = proxy_socks5_selectchap(p);
+ if (ret) return ret;
+ }
+
+ }
+
+ plug_closing(p->plug, "Proxy error: Unexpected proxy error",
+ PROXY_ERROR_UNEXPECTED, 0);
+ return 1;
+}
+
+/* ----------------------------------------------------------------------
+ * `Telnet' proxy type.
+ *
+ * (This is for ad-hoc proxies where you connect to the proxy's
+ * 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 *ret = NULL;
+ int retlen = 0, retsize = 0;
+ int so = 0, eo = 0;
+#define ENSURE(n) do { \
+ if (retsize < retlen + n) { \
+ retsize = retlen + n + 512; \
+ ret = sresize(ret, retsize, char); \
+ } \
+} while (0)
+
+ /* we need to escape \\, \%, \r, \n, \t, \x??, \0???,
+ * %%, %host, %port, %user, and %pass
+ */
+
+ while (cfg->proxy_telnet_command[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++;
+
+ /* if we hit eol, break out of our escaping loop */
+ if (cfg->proxy_telnet_command[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);
+ 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 (cfg->proxy_telnet_command[so] == '\\') {
+
+ /* we recognize \\, \%, \r, \n, \t, \x??.
+ * anything else, we just send unescaped (including the \).
+ */
+
+ switch (cfg->proxy_telnet_command[eo]) {
+
+ case '\\':
+ ENSURE(1);
+ ret[retlen++] = '\\';
+ eo++;
+ break;
+
+ case '%':
+ ENSURE(1);
+ ret[retlen++] = '%';
+ eo++;
+ break;
+
+ case 'r':
+ ENSURE(1);
+ ret[retlen++] = '\r';
+ eo++;
+ break;
+
+ case 'n':
+ ENSURE(1);
+ ret[retlen++] = '\n';
+ eo++;
+ break;
+
+ case 't':
+ ENSURE(1);
+ ret[retlen++] = '\t';
+ eo++;
+ break;
+
+ case 'x':
+ case 'X':
+ {
+ /* escaped hexadecimal value (ie. \xff) */
+ 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;
+ else {
+ /* non hex character, so we abort and just
+ * send the whole thing unescaped (including \x)
+ */
+ ENSURE(1);
+ ret[retlen++] = '\\';
+ eo = so + 1;
+ break;
+ }
+
+ /* we only extract two hex characters */
+ if (i == 1) {
+ ENSURE(1);
+ ret[retlen++] = v;
+ eo++;
+ break;
+ }
+
+ i++;
+ v <<= 4;
+ }
+ }
+ break;
+
+ default:
+ ENSURE(2);
+ memcpy(ret+retlen, cfg->proxy_telnet_command + so, 2);
+ retlen += 2;
+ eo++;
+ break;
+ }
+ } else {
+
+ /* % escape. we recognize %%, %host, %port, %user, %pass.
+ * %proxyhost, %proxyport. Anything else we just send
+ * unescaped (including the %).
+ */
+
+ if (cfg->proxy_telnet_command[eo] == '%') {
+ ENSURE(1);
+ ret[retlen++] = '%';
+ eo++;
+ }
+ else if (strnicmp(cfg->proxy_telnet_command + 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) {
+ 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);
+ ENSURE(userlen);
+ memcpy(ret+retlen, cfg->proxy_username, userlen);
+ retlen += userlen;
+ eo += 4;
+ }
+ else if (strnicmp(cfg->proxy_telnet_command + eo,
+ "pass", 4) == 0) {
+ int passlen = strlen(cfg->proxy_password);
+ ENSURE(passlen);
+ memcpy(ret+retlen, cfg->proxy_password, passlen);
+ retlen += passlen;
+ eo += 4;
+ }
+ else if (strnicmp(cfg->proxy_telnet_command + eo,
+ "proxyhost", 9) == 0) {
+ int phlen = strlen(cfg->proxy_host);
+ ENSURE(phlen);
+ memcpy(ret+retlen, cfg->proxy_host, phlen);
+ retlen += phlen;
+ eo += 9;
+ }
+ else if (strnicmp(cfg->proxy_telnet_command + eo,
+ "proxyport", 9) == 0) {
+ char pport[50];
+ int pplen;
+ sprintf(pport, "%d", cfg->proxy_port);
+ pplen = strlen(pport);
+ ENSURE(pplen);
+ memcpy(ret+retlen, pport, pplen);
+ retlen += pplen;
+ eo += 9;
+ }
+ else {
+ /* we don't escape this, so send the % now, and
+ * don't advance eo, so that we'll consider the
+ * text immediately following the % as unescaped.
+ */
+ ENSURE(1);
+ ret[retlen++] = '%';
+ }
+ }
+
+ /* resume scanning for additional escapes after this one. */
+ so = eo;
+ }
+
+ /* 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);
+ retlen += eo - so;
+ }
+
+ ENSURE(1);
+ ret[retlen] = '\0';
+ return ret;
+
+#undef ENSURE
+}
+
+int proxy_telnet_negotiate (Proxy_Socket p, int change)
+{
+ if (p->state == PROXY_CHANGE_NEW) {
+ char *formatted_cmd;
+
+ formatted_cmd = format_telnet_command(p->remote_addr, p->remote_port,
+ &p->cfg);
+
+ sk_write(p->sub_socket, formatted_cmd, strlen(formatted_cmd));
+ sfree(formatted_cmd);
+
+ p->state = 1;
+ return 0;
+ }
+
+ if (change == PROXY_CHANGE_CLOSING) {
+ /* if our proxy negotiation process involves closing and opening
+ * new sockets, then we would want to intercept this closing
+ * callback when we were expecting it. if we aren't anticipating
+ * a socket close, then some error must have occurred. we'll
+ * just pass those errors up to the backend.
+ */
+ return plug_closing(p->plug, p->closing_error_msg,
+ p->closing_error_code,
+ p->closing_calling_back);
+ }
+
+ if (change == PROXY_CHANGE_SENT) {
+ /* some (or all) of what we wrote to the proxy was sent.
+ * we don't do anything new, however, until we receive the
+ * proxy's response. we might want to set a timer so we can
+ * timeout the proxy negotiation after a while...
+ */
+ return 0;
+ }
+
+ if (change == PROXY_CHANGE_ACCEPTING) {
+ /* 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);
+ }
+
+ if (change == PROXY_CHANGE_RECEIVE) {
+ /* we have received data from the underlying socket, which
+ * we'll need to parse, process, and respond to appropriately.
+ */
+
+ /* we're done */
+ proxy_activate(p);
+ /* proxy activate will have dealt with
+ * whatever is left of the buffer */
+ return 1;
+ }
+
+ plug_closing(p->plug, "Proxy error: Unexpected proxy error",
+ PROXY_ERROR_UNEXPECTED, 0);
+ return 1;
+}
--- /dev/null
+/*
+ * Network proxy abstraction in PuTTY
+ *
+ * A proxy layer, if necessary, wedges itself between the
+ * network code and the higher level backend.
+ *
+ * Supported proxies: HTTP CONNECT, generic telnet, SOCKS 4 & 5
+ */
+
+#ifndef PUTTY_PROXY_H
+#define PUTTY_PROXY_H
+
+#define PROXY_ERROR_GENERAL 8000
+#define PROXY_ERROR_UNEXPECTED 8001
+
+typedef struct Socket_proxy_tag * Proxy_Socket;
+
+struct Socket_proxy_tag {
+ const struct socket_function_table *fn;
+ /* the above variable absolutely *must* be the first in this structure */
+
+ char * error;
+
+ Socket sub_socket;
+ Plug plug;
+ SockAddr remote_addr;
+ int remote_port;
+
+ bufchain pending_output_data;
+ bufchain pending_oob_output_data;
+ int pending_flush;
+ bufchain pending_input_data;
+
+#define PROXY_STATE_NEW -1
+#define PROXY_STATE_ACTIVE 0
+
+ int state; /* proxy states greater than 0 are implementation
+ * dependent, but represent various stages/states
+ * of the initialization/setup/negotiation with the
+ * proxy server.
+ */
+ int freeze; /* should we freeze the underlying socket when
+ * we are done with the proxy negotiation? this
+ * simply caches the value of sk_set_frozen calls.
+ */
+
+#define PROXY_CHANGE_NEW -1
+#define PROXY_CHANGE_CLOSING 0
+#define PROXY_CHANGE_SENT 1
+#define PROXY_CHANGE_RECEIVE 2
+#define PROXY_CHANGE_ACCEPTING 3
+
+ /* something has changed (a call from the sub socket
+ * layer into our Proxy Plug layer, or we were just
+ * created, etc), so the proxy layer needs to handle
+ * this change (the type of which is the second argument)
+ * and further the proxy negotiation process.
+ */
+
+ int (*negotiate) (Proxy_Socket /* this */, int /* change type */);
+
+ /* current arguments of plug handlers
+ * (for use by proxy's negotiate function)
+ */
+
+ /* closing */
+ const char *closing_error_msg;
+ int closing_error_code;
+ int closing_calling_back;
+
+ /* receive */
+ int receive_urgent;
+ char *receive_data;
+ int receive_len;
+
+ /* sent */
+ int sent_bufsize;
+
+ /* accepting */
+ OSSocket accepting_sock;
+
+ /* configuration, used to look up proxy settings */
+ Config cfg;
+
+ /* CHAP transient data */
+ int chap_num_attributes;
+ int chap_num_attributes_processed;
+ int chap_current_attribute;
+ int chap_current_datalen;
+};
+
+typedef struct Plug_proxy_tag * Proxy_Plug;
+
+struct Plug_proxy_tag {
+ const struct plug_function_table *fn;
+ /* the above variable absolutely *must* be the first in this structure */
+
+ Proxy_Socket proxy_socket;
+
+};
+
+extern void proxy_activate (Proxy_Socket);
+
+extern int proxy_http_negotiate (Proxy_Socket, int);
+extern int proxy_telnet_negotiate (Proxy_Socket, int);
+extern int proxy_socks4_negotiate (Proxy_Socket, int);
+extern int proxy_socks5_negotiate (Proxy_Socket, int);
+
+/*
+ * This may be reused by local-command proxies on individual
+ * platforms.
+ */
+char *format_telnet_command(SockAddr addr, int port, const Config *cfg);
+
+/*
+ * These are implemented in cproxy.c or nocproxy.c, depending on
+ * whether encrypted proxy authentication is available.
+ */
+extern void proxy_socks5_offerencryptedauth(char *command, int *len);
+extern int proxy_socks5_handlechap (Proxy_Socket p);
+extern int proxy_socks5_selectchap(Proxy_Socket p);
+
+#endif
--- /dev/null
+/*
+ * scp.c - Scp (Secure Copy) client for PuTTY.
+ * Joris van Rantwijk, Simon Tatham
+ *
+ * This is mainly based on ssh-1.2.26/scp.c by Timo Rinne & Tatu Ylonen.
+ * They, in turn, used stuff from BSD rcp.
+ *
+ * (SGT, 2001-09-10: Joris van Rantwijk assures me that although
+ * this file as originally submitted was inspired by, and
+ * _structurally_ based on, ssh-1.2.26's scp.c, there wasn't any
+ * actual code duplicated, so the above comment shouldn't give rise
+ * to licensing issues.)
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <limits.h>
+#include <time.h>
+#include <assert.h>
+
+#define PUTTY_DO_GLOBALS
+#include "putty.h"
+#include "psftp.h"
+#include "ssh.h"
+#include "sftp.h"
+#include "storage.h"
+#include "int64.h"
+
+static int list = 0;
+static int verbose = 0;
+static int recursive = 0;
+static int preserve = 0;
+static int targetshouldbedirectory = 0;
+static int statistics = 1;
+static int prev_stats_len = 0;
+static int scp_unsafe_mode = 0;
+static int errs = 0;
+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 Backend *back;
+static void *backhandle;
+static Config cfg;
+
+static void source(char *src);
+static void rsource(char *src);
+static void sink(char *targ, char *src);
+
+const char *const appname = "PSCP";
+
+/*
+ * The maximum amount of queued data we accept before we stop and
+ * wait for the server to process some.
+ */
+#define MAX_SCP_BUFSIZE 16384
+
+void ldisc_send(void *handle, char *buf, int len, int interactive)
+{
+ /*
+ * This is only here because of the calls to ldisc_send(NULL,
+ * 0) in ssh.c. Nothing in PSCP actually needs to use the ldisc
+ * as an ldisc. So if we get called with any real data, I want
+ * to know about it.
+ */
+ assert(len == 0);
+}
+
+static void tell_char(FILE * stream, char c)
+{
+ fputc(c, stream);
+}
+
+static void tell_str(FILE * stream, char *str)
+{
+ unsigned int i;
+
+ for (i = 0; i < strlen(str); ++i)
+ tell_char(stream, str[i]);
+}
+
+static void tell_user(FILE * stream, char *fmt, ...)
+{
+ char *str, *str2;
+ va_list ap;
+ va_start(ap, fmt);
+ str = dupvprintf(fmt, ap);
+ va_end(ap);
+ str2 = dupcat(str, "\n", NULL);
+ sfree(str);
+ tell_str(stream, str2);
+ sfree(str2);
+}
+
+/*
+ * Print an error message and perform a fatal exit.
+ */
+void fatalbox(char *fmt, ...)
+{
+ char *str, *str2;
+ va_list ap;
+ va_start(ap, fmt);
+ str = dupvprintf(fmt, ap);
+ str2 = dupcat("Fatal: ", str, "\n", NULL);
+ sfree(str);
+ va_end(ap);
+ tell_str(stderr, str2);
+ sfree(str2);
+ errs++;
+
+ cleanup_exit(1);
+}
+void modalfatalbox(char *fmt, ...)
+{
+ char *str, *str2;
+ va_list ap;
+ va_start(ap, fmt);
+ str = dupvprintf(fmt, ap);
+ str2 = dupcat("Fatal: ", str, "\n", NULL);
+ sfree(str);
+ va_end(ap);
+ tell_str(stderr, str2);
+ sfree(str2);
+ errs++;
+
+ cleanup_exit(1);
+}
+void connection_fatal(void *frontend, char *fmt, ...)
+{
+ char *str, *str2;
+ va_list ap;
+ va_start(ap, fmt);
+ str = dupvprintf(fmt, ap);
+ str2 = dupcat("Fatal: ", str, "\n", NULL);
+ sfree(str);
+ va_end(ap);
+ tell_str(stderr, str2);
+ sfree(str2);
+ errs++;
+
+ cleanup_exit(1);
+}
+
+/*
+ * In pscp, all agent requests should be synchronous, so this is a
+ * never-called stub.
+ */
+void agent_schedule_callback(void (*callback)(void *, void *, int),
+ void *callback_ctx, void *data, int len)
+{
+ assert(!"We shouldn't be here");
+}
+
+/*
+ * Receive a block of data from the SSH link. Block until all data
+ * is available.
+ *
+ * To do this, we repeatedly call the SSH protocol module, with our
+ * own trap in from_backend() to catch the data that comes back. We
+ * do this until we have enough data.
+ */
+
+static unsigned char *outptr; /* where to put the data */
+static unsigned outlen; /* how much data required */
+static unsigned char *pending = NULL; /* any spare data */
+static unsigned pendlen = 0, pendsize = 0; /* length and phys. size of buffer */
+int from_backend(void *frontend, int is_stderr, const char *data, int datalen)
+{
+ unsigned char *p = (unsigned char *) data;
+ unsigned len = (unsigned) datalen;
+
+ /*
+ * stderr data is just spouted to local stderr and otherwise
+ * ignored.
+ */
+ if (is_stderr) {
+ if (len > 0)
+ if (fwrite(data, 1, len, stderr) < len)
+ /* oh well */;
+ return 0;
+ }
+
+ if ((outlen > 0) && (len > 0)) {
+ unsigned used = outlen;
+ if (used > len)
+ used = len;
+ memcpy(outptr, p, used);
+ outptr += used;
+ outlen -= used;
+ p += used;
+ len -= used;
+ }
+
+ if (len > 0) {
+ if (pendsize < pendlen + len) {
+ pendsize = pendlen + len + 4096;
+ pending = sresize(pending, pendsize, unsigned char);
+ }
+ memcpy(pending + pendlen, p, len);
+ pendlen += len;
+ }
+
+ return 0;
+}
+int from_backend_untrusted(void *frontend_handle, const char *data, int len)
+{
+ /*
+ * 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 */
+}
+static int ssh_scp_recv(unsigned char *buf, int len)
+{
+ outptr = buf;
+ outlen = len;
+
+ /*
+ * See if the pending-input block contains some of what we
+ * need.
+ */
+ if (pendlen > 0) {
+ unsigned pendused = pendlen;
+ if (pendused > outlen)
+ pendused = outlen;
+ memcpy(outptr, pending, pendused);
+ memmove(pending, pending + pendused, pendlen - pendused);
+ outptr += pendused;
+ outlen -= pendused;
+ pendlen -= pendused;
+ if (pendlen == 0) {
+ pendsize = 0;
+ sfree(pending);
+ pending = NULL;
+ }
+ if (outlen == 0)
+ return len;
+ }
+
+ while (outlen > 0) {
+ if (back->exitcode(backhandle) >= 0 || ssh_sftp_loop_iteration() < 0)
+ return 0; /* doom */
+ }
+
+ return len;
+}
+
+/*
+ * Loop through the ssh connection and authentication process.
+ */
+static void ssh_scp_init(void)
+{
+ while (!back->sendok(backhandle)) {
+ if (back->exitcode(backhandle) >= 0) {
+ errs++;
+ return;
+ }
+ if (ssh_sftp_loop_iteration() < 0) {
+ errs++;
+ return; /* doom */
+ }
+ }
+
+ /* Work out which backend we ended up using. */
+ if (!ssh_fallback_cmd(backhandle))
+ using_sftp = main_cmd_is_sftp;
+ else
+ using_sftp = fallback_cmd_is_sftp;
+
+ if (verbose) {
+ if (using_sftp)
+ tell_user(stderr, "Using SFTP");
+ else
+ tell_user(stderr, "Using SCP1");
+ }
+}
+
+/*
+ * Print an error message and exit after closing the SSH link.
+ */
+static void bump(char *fmt, ...)
+{
+ char *str, *str2;
+ va_list ap;
+ va_start(ap, fmt);
+ str = dupvprintf(fmt, ap);
+ va_end(ap);
+ str2 = dupcat(str, "\n", NULL);
+ sfree(str);
+ tell_str(stderr, str2);
+ sfree(str2);
+ errs++;
+
+ if (back != NULL && back->connected(backhandle)) {
+ char ch;
+ back->special(backhandle, TS_EOF);
+ ssh_scp_recv((unsigned char *) &ch, 1);
+ }
+
+ cleanup_exit(1);
+}
+
+/*
+ * Open an SSH connection to user@host and execute cmd.
+ */
+static void do_cmd(char *host, char *user, char *cmd)
+{
+ const char *err;
+ char *realhost;
+ void *logctx;
+
+ if (host == NULL || host[0] == '\0')
+ bump("Empty host name");
+
+ /*
+ * Remove fiddly bits of address: remove a colon suffix, and
+ * the square brackets around an IPv6 literal address.
+ */
+ if (host[0] == '[') {
+ host++;
+ host[strcspn(host, "]")] = '\0';
+ } else {
+ host[strcspn(host, ":")] = '\0';
+ }
+
+ /*
+ * 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') {
+ /* Settings present and include hostname */
+ /* Re-load data into the real config. */
+ do_defaults(host, &cfg);
+ } 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';
+ }
+ } else {
+ /* Patch in hostname `host' to session details. */
+ strncpy(cfg.host, host, sizeof(cfg.host) - 1);
+ cfg.host[sizeof(cfg.host) - 1] = '\0';
+ }
+
+ /*
+ * 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;
+ }
+
+ /*
+ * 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';
+ }
+
+ /* 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') {
+ 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';
+ 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;
+
+ /*
+ * 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;
+ if (try_sftp) {
+ /* First choice is SFTP subsystem. */
+ main_cmd_is_sftp = 1;
+ strcpy(cfg.remote_cmd, "sftp");
+ cfg.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;
+ } 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;
+ }
+ } 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;
+ }
+ cfg.nopty = TRUE;
+
+ back = &ssh_backend;
+
+ err = back->init(NULL, &backhandle, &cfg, cfg.host, cfg.port, &realhost,
+ 0, cfg.tcp_keepalives);
+ if (err != NULL)
+ bump("ssh_init: %s", err);
+ logctx = log_init(NULL, &cfg);
+ 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);
+ sfree(realhost);
+}
+
+/*
+ * Update statistic information about current file.
+ */
+static void print_stats(char *name, uint64 size, uint64 done,
+ time_t start, time_t now)
+{
+ float ratebs;
+ unsigned long eta;
+ char *etastr;
+ int pct;
+ int len;
+ int elap;
+ double donedbl;
+ double sizedbl;
+
+ elap = (unsigned long) difftime(now, start);
+
+ if (now > start)
+ ratebs = (float) (uint64_to_double(done) / elap);
+ else
+ ratebs = (float) uint64_to_double(done);
+
+ if (ratebs < 1.0)
+ eta = (unsigned long) (uint64_to_double(uint64_subtract(size, done)));
+ else {
+ eta = (unsigned long)
+ ((uint64_to_double(uint64_subtract(size, done)) / ratebs));
+ }
+
+ etastr = dupprintf("%02ld:%02ld:%02ld",
+ eta / 3600, (eta % 3600) / 60, eta % 60);
+
+ donedbl = uint64_to_double(done);
+ sizedbl = uint64_to_double(size);
+ pct = (int) (100 * (donedbl * 1.0 / sizedbl));
+
+ {
+ char donekb[40];
+ /* divide by 1024 to provide kB */
+ uint64_decimal(uint64_shift_right(done, 10), donekb);
+ len = printf("\r%-25.25s | %s kB | %5.1f kB/s | ETA: %8s | %3d%%",
+ name,
+ donekb, ratebs / 1024.0, etastr, pct);
+ if (len < prev_stats_len)
+ printf("%*s", prev_stats_len - len, "");
+ prev_stats_len = len;
+
+ if (uint64_compare(done, size) == 0)
+ printf("\n");
+
+ fflush(stdout);
+ }
+
+ free(etastr);
+}
+
+/*
+ * Find a colon in str and return a pointer to the colon.
+ * This is used to separate hostname from filename.
+ */
+static char *colon(char *str)
+{
+ /* We ignore a leading colon, since the hostname cannot be
+ empty. We also ignore a colon as second character because
+ of filenames like f:myfile.txt. */
+ if (str[0] == '\0' || str[0] == ':' ||
+ (str[0] != '[' && str[1] == ':'))
+ return (NULL);
+ while (*str != '\0' && *str != ':' && *str != '/' && *str != '\\') {
+ if (*str == '[') {
+ /* Skip over IPv6 literal addresses
+ * (eg: 'jeroen@[2001:db8::1]:myfile.txt') */
+ char *ipv6_end = strchr(str, ']');
+ if (ipv6_end) {
+ str = ipv6_end;
+ }
+ }
+ str++;
+ }
+ if (*str == ':')
+ return (str);
+ else
+ return (NULL);
+}
+
+/*
+ * Return a pointer to the portion of str that comes after the last
+ * slash (or backslash or colon, if `local' is TRUE).
+ */
+static char *stripslashes(char *str, int local)
+{
+ char *p;
+
+ if (local) {
+ p = strchr(str, ':');
+ if (p) str = p+1;
+ }
+
+ p = strrchr(str, '/');
+ if (p) str = p+1;
+
+ if (local) {
+ p = strrchr(str, '\\');
+ if (p) str = p+1;
+ }
+
+ return str;
+}
+
+/*
+ * Determine whether a string is entirely composed of dots.
+ */
+static int is_dots(char *str)
+{
+ return str[strspn(str, ".")] == '\0';
+}
+
+/*
+ * Wait for a response from the other side.
+ * Return 0 if ok, -1 if error.
+ */
+static int response(void)
+{
+ char ch, resp, rbuf[2048];
+ int p;
+
+ if (ssh_scp_recv((unsigned char *) &resp, 1) <= 0)
+ bump("Lost connection");
+
+ p = 0;
+ switch (resp) {
+ case 0: /* ok */
+ return (0);
+ default:
+ rbuf[p++] = resp;
+ /* fallthrough */
+ case 1: /* error */
+ case 2: /* fatal error */
+ do {
+ if (ssh_scp_recv((unsigned char *) &ch, 1) <= 0)
+ 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);
+ else
+ bump("%s", rbuf);
+ errs++;
+ return (-1);
+ }
+}
+
+int sftp_recvdata(char *buf, int len)
+{
+ return ssh_scp_recv((unsigned char *) buf, len);
+}
+int sftp_senddata(char *buf, int len)
+{
+ back->send(backhandle, buf, len);
+ return 1;
+}
+
+/* ----------------------------------------------------------------------
+ * sftp-based replacement for the hacky `pscp -ls'.
+ */
+static int sftp_ls_compare(const void *av, const void *bv)
+{
+ const struct fxp_name *a = (const struct fxp_name *) av;
+ const struct fxp_name *b = (const struct fxp_name *) bv;
+ return strcmp(a->filename, b->filename);
+}
+void scp_sftp_listdir(char *dirname)
+{
+ struct fxp_handle *dirh;
+ struct fxp_names *names;
+ struct fxp_name *ournames;
+ struct sftp_packet *pktin;
+ struct sftp_request *req, *rreq;
+ int nnames, namesize;
+ int i;
+
+ if (!fxp_init()) {
+ tell_user(stderr, "unable to initialise SFTP: %s", fxp_error());
+ errs++;
+ 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);
+
+ 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);
+
+ if (names == NULL) {
+ if (fxp_error_type() == SSH_FX_EOF)
+ break;
+ printf("Reading directory %s: %s\n", dirname, fxp_error());
+ break;
+ }
+ if (names->nnames == 0) {
+ fxp_free_names(names);
+ break;
+ }
+
+ if (nnames + names->nnames >= namesize) {
+ namesize += names->nnames + 128;
+ ournames = sresize(ournames, namesize, struct fxp_name);
+ }
+
+ 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);
+
+ /*
+ * 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);
+
+ /*
+ * And print them.
+ */
+ for (i = 0; i < nnames; i++)
+ printf("%s\n", ournames[i].longname);
+ }
+}
+
+/* ----------------------------------------------------------------------
+ * Helper routines that contain the actual SCP protocol elements,
+ * implemented both as SCP1 and SFTP.
+ */
+
+static struct scp_sftp_dirstack {
+ struct scp_sftp_dirstack *next;
+ struct fxp_name *names;
+ int namepos, namelen;
+ char *dirpath;
+ char *wildcard;
+ int matched_something; /* wildcard match set was non-empty */
+} *scp_sftp_dirstack_head;
+static char *scp_sftp_remotepath, *scp_sftp_currentname;
+static char *scp_sftp_wildcard;
+static int scp_sftp_targetisdir, scp_sftp_donethistarget;
+static int scp_sftp_preserve, scp_sftp_recursive;
+static unsigned long scp_sftp_mtime, scp_sftp_atime;
+static int scp_has_times;
+static struct fxp_handle *scp_sftp_filehandle;
+static struct fxp_xfer *scp_sftp_xfer;
+static uint64 scp_sftp_fileoffset;
+
+int scp_source_setup(char *target, int shouldbedir)
+{
+ if (using_sftp) {
+ /*
+ * Find out whether the target filespec is in fact a
+ * directory.
+ */
+ struct sftp_packet *pktin;
+ struct sftp_request *req, *rreq;
+ 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);
+
+ if (!ret || !(attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS))
+ scp_sftp_targetisdir = 0;
+ else
+ scp_sftp_targetisdir = (attrs.permissions & 0040000) != 0;
+
+ if (shouldbedir && !scp_sftp_targetisdir) {
+ bump("pscp: remote filespec %s: not a directory\n", target);
+ }
+
+ scp_sftp_remotepath = dupstr(target);
+
+ scp_has_times = 0;
+ } else {
+ (void) response();
+ }
+ return 0;
+}
+
+int scp_send_errmsg(char *str)
+{
+ if (using_sftp) {
+ /* do nothing; we never need to send our errors to the server */
+ } else {
+ back->send(backhandle, "\001", 1);/* scp protocol error prefix */
+ back->send(backhandle, str, strlen(str));
+ }
+ return 0; /* can't fail */
+}
+
+int scp_send_filetimes(unsigned long mtime, unsigned long atime)
+{
+ if (using_sftp) {
+ scp_sftp_mtime = mtime;
+ scp_sftp_atime = atime;
+ scp_has_times = 1;
+ return 0;
+ } else {
+ char buf[80];
+ sprintf(buf, "T%lu 0 %lu 0\n", mtime, atime);
+ back->send(backhandle, buf, strlen(buf));
+ return response();
+ }
+}
+
+int scp_send_filename(char *name, uint64 size, int modes)
+{
+ if (using_sftp) {
+ char *fullname;
+ struct sftp_packet *pktin;
+ struct sftp_request *req, *rreq;
+
+ 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);
+
+ if (!scp_sftp_filehandle) {
+ tell_user(stderr, "pscp: unable to open %s: %s",
+ fullname, fxp_error());
+ errs++;
+ return 1;
+ }
+ scp_sftp_fileoffset = uint64_make(0, 0);
+ scp_sftp_xfer = xfer_upload_init(scp_sftp_filehandle,
+ scp_sftp_fileoffset);
+ sfree(fullname);
+ return 0;
+ } else {
+ char buf[40];
+ char sizestr[40];
+ uint64_decimal(size, sizestr);
+ sprintf(buf, "C%04o %s ", modes, sizestr);
+ back->send(backhandle, buf, strlen(buf));
+ back->send(backhandle, name, strlen(name));
+ back->send(backhandle, "\n", 1);
+ return response();
+ }
+}
+
+int scp_send_filedata(char *data, int len)
+{
+ if (using_sftp) {
+ int ret;
+ struct sftp_packet *pktin;
+
+ if (!scp_sftp_filehandle) {
+ return 1;
+ }
+
+ 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());
+ errs++;
+ return 1;
+ }
+ }
+
+ xfer_upload_data(scp_sftp_xfer, data, len);
+
+ scp_sftp_fileoffset = uint64_add32(scp_sftp_fileoffset, len);
+ return 0;
+ } else {
+ int bufsize = back->send(backhandle, data, len);
+
+ /*
+ * If the network transfer is backing up - that is, the
+ * remote site is not accepting data as fast as we can
+ * produce it - then we must loop on network events until
+ * we have space in the buffer again.
+ */
+ while (bufsize > MAX_SCP_BUFSIZE) {
+ if (ssh_sftp_loop_iteration() < 0)
+ return 1;
+ bufsize = back->sendbuffer(backhandle);
+ }
+
+ return 0;
+ }
+}
+
+int scp_send_finish(void)
+{
+ if (using_sftp) {
+ struct fxp_attrs attrs;
+ struct sftp_packet *pktin;
+ struct sftp_request *req, *rreq;
+ int ret;
+
+ while (!xfer_done(scp_sftp_xfer)) {
+ pktin = sftp_recv();
+ xfer_upload_gotpkt(scp_sftp_xfer, pktin);
+ }
+ xfer_cleanup(scp_sftp_xfer);
+
+ if (!scp_sftp_filehandle) {
+ return 1;
+ }
+ 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);
+ if (!ret) {
+ tell_user(stderr, "unable to set file times: %s\n", 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);
+ scp_has_times = 0;
+ return 0;
+ } else {
+ back->send(backhandle, "", 1);
+ return response();
+ }
+}
+
+char *scp_save_remotepath(void)
+{
+ if (using_sftp)
+ return scp_sftp_remotepath;
+ else
+ return NULL;
+}
+
+void scp_restore_remotepath(char *data)
+{
+ if (using_sftp)
+ scp_sftp_remotepath = data;
+}
+
+int scp_send_dirname(char *name, int modes)
+{
+ if (using_sftp) {
+ char *fullname;
+ char const *err;
+ struct fxp_attrs attrs;
+ struct sftp_packet *pktin;
+ struct sftp_request *req, *rreq;
+ int ret;
+
+ if (scp_sftp_targetisdir) {
+ fullname = dupcat(scp_sftp_remotepath, "/", name, NULL);
+ } else {
+ fullname = dupstr(scp_sftp_remotepath);
+ }
+
+ /*
+ * We don't worry about whether we managed to create the
+ * 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);
+
+ 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);
+
+ if (!ret || !(attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS) ||
+ !(attrs.permissions & 0040000)) {
+ tell_user(stderr, "unable to create directory %s: %s",
+ fullname, err);
+ errs++;
+ return 1;
+ }
+
+ scp_sftp_remotepath = fullname;
+
+ return 0;
+ } else {
+ char buf[40];
+ sprintf(buf, "D%04o 0 ", modes);
+ back->send(backhandle, buf, strlen(buf));
+ back->send(backhandle, name, strlen(name));
+ back->send(backhandle, "\n", 1);
+ return response();
+ }
+}
+
+int scp_send_enddir(void)
+{
+ if (using_sftp) {
+ sfree(scp_sftp_remotepath);
+ return 0;
+ } else {
+ back->send(backhandle, "E\n", 2);
+ return response();
+ }
+}
+
+/*
+ * Yes, I know; I have an scp_sink_setup _and_ an scp_sink_init.
+ * That's bad. The difference is that scp_sink_setup is called once
+ * right at the start, whereas scp_sink_init is called to
+ * initialise every level of recursion in the protocol.
+ */
+int scp_sink_setup(char *source, int preserve, int recursive)
+{
+ if (using_sftp) {
+ char *newsource;
+
+ if (!fxp_init()) {
+ tell_user(stderr, "unable to initialise SFTP: %s", fxp_error());
+ errs++;
+ return 1;
+ }
+ /*
+ * It's possible that the source string we've been given
+ * contains a wildcard. If so, we must split the directory
+ * away from the wildcard itself (throwing an error if any
+ * wildcardness comes before the final slash) and arrange
+ * things so that a dirstack entry will be set up.
+ */
+ 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;
+ dupsource = dupstr(source);
+ lastpart = stripslashes(dupsource, 0);
+ wildcard = dupstr(lastpart);
+ *lastpart = '\0';
+ if (*dupsource && dupsource[1]) {
+ /*
+ * The remains of dupsource are at least two
+ * characters long, meaning the pathname wasn't
+ * empty or just `/'. Hence, we remove the trailing
+ * slash.
+ */
+ lastpart[-1] = '\0';
+ } else if (!*dupsource) {
+ /*
+ * The remains of dupsource are _empty_ - the whole
+ * pathname was a wildcard. Hence we need to
+ * replace it with ".".
+ */
+ sfree(dupsource);
+ dupsource = dupstr(".");
+ }
+
+ /*
+ * Now we have separated our string into dupsource (the
+ * directory part) and wildcard. Both of these will
+ * need freeing at some point. Next step is to remove
+ * wildcard escapes from the directory part, throwing
+ * an error if it contains a real wildcard.
+ */
+ dirpart = snewn(1+strlen(dupsource), char);
+ if (!wc_unescape(dirpart, dupsource)) {
+ tell_user(stderr, "%s: multiple-level wildcards unsupported",
+ source);
+ errs++;
+ sfree(dirpart);
+ sfree(wildcard);
+ sfree(dupsource);
+ return 1;
+ }
+
+ /*
+ * Now we have dirpart (unescaped, ie a valid remote
+ * path), and wildcard (a wildcard). This will be
+ * sufficient to arrange a dirstack entry.
+ */
+ scp_sftp_remotepath = dirpart;
+ scp_sftp_wildcard = wildcard;
+ sfree(dupsource);
+ } else {
+ scp_sftp_remotepath = newsource;
+ scp_sftp_wildcard = NULL;
+ }
+ scp_sftp_preserve = preserve;
+ scp_sftp_recursive = recursive;
+ scp_sftp_donethistarget = 0;
+ scp_sftp_dirstack_head = NULL;
+ }
+ return 0;
+}
+
+int scp_sink_init(void)
+{
+ if (!using_sftp) {
+ back->send(backhandle, "", 1);
+ }
+ return 0;
+}
+
+#define SCP_SINK_FILE 1
+#define SCP_SINK_DIR 2
+#define SCP_SINK_ENDDIR 3
+#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) */
+ uint64 size; /* file size (not ENDDIR) */
+ int settime; /* 1 if atime and mtime are filled */
+ unsigned long atime, mtime; /* access times for the file */
+};
+
+int scp_get_sink_action(struct scp_sink_action *act)
+{
+ if (using_sftp) {
+ char *fname;
+ int must_free_fname;
+ struct fxp_attrs attrs;
+ struct sftp_packet *pktin;
+ struct sftp_request *req, *rreq;
+ int ret;
+
+ if (!scp_sftp_dirstack_head) {
+ if (!scp_sftp_donethistarget) {
+ /*
+ * Simple case: we are only dealing with one file.
+ */
+ fname = scp_sftp_remotepath;
+ must_free_fname = 0;
+ scp_sftp_donethistarget = 1;
+ } else {
+ /*
+ * Even simpler case: one file _which we've done_.
+ * Return 1 (finished).
+ */
+ return 1;
+ }
+ } else {
+ /*
+ * We're now in the middle of stepping through a list
+ * of names returned from fxp_readdir(); so let's carry
+ * on.
+ */
+ struct scp_sftp_dirstack *head = scp_sftp_dirstack_head;
+ while (head->namepos < head->namelen &&
+ (is_dots(head->names[head->namepos].filename) ||
+ (head->wildcard &&
+ !wc_match(head->wildcard,
+ head->names[head->namepos].filename))))
+ head->namepos++; /* skip . and .. */
+ if (head->namepos < head->namelen) {
+ head->matched_something = 1;
+ fname = dupcat(head->dirpath, "/",
+ head->names[head->namepos++].filename,
+ NULL);
+ must_free_fname = 1;
+ } else {
+ /*
+ * We've come to the end of the list; pop it off
+ * the stack and return an ENDDIR action (or RETRY
+ * if this was a wildcard match).
+ */
+ if (head->wildcard) {
+ act->action = SCP_SINK_RETRY;
+ if (!head->matched_something) {
+ tell_user(stderr, "pscp: wildcard '%s' matched "
+ "no files", head->wildcard);
+ errs++;
+ }
+ sfree(head->wildcard);
+
+ } else {
+ act->action = SCP_SINK_ENDDIR;
+ }
+
+ sfree(head->dirpath);
+ sfree(head->names);
+ scp_sftp_dirstack_head = head->next;
+ sfree(head);
+
+ return 0;
+ }
+ }
+
+ /*
+ * 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);
+
+ if (!ret || !(attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS)) {
+ tell_user(stderr, "unable to identify %s: %s", fname,
+ ret ? "file type not supplied" : fxp_error());
+ errs++;
+ return 1;
+ }
+
+ if (attrs.permissions & 0040000) {
+ struct scp_sftp_dirstack *newitem;
+ struct fxp_handle *dirhandle;
+ int nnames, namesize;
+ struct fxp_name *ournames;
+ struct fxp_names *names;
+
+ /*
+ * It's a directory. If we're not in recursive mode,
+ * this merits a complaint (which is fatal if the name
+ * was specified directly, but not if it was matched by
+ * a wildcard).
+ *
+ * We skip this complaint completely if
+ * scp_sftp_wildcard is set, because that's an
+ * indication that we're not actually supposed to
+ * _recursively_ transfer the dir, just scan it for
+ * things matching the wildcard.
+ */
+ if (!scp_sftp_recursive && !scp_sftp_wildcard) {
+ tell_user(stderr, "pscp: %s: is a directory", fname);
+ errs++;
+ if (must_free_fname) sfree(fname);
+ if (scp_sftp_dirstack_head) {
+ act->action = SCP_SINK_RETRY;
+ return 0;
+ } else {
+ return 1;
+ }
+ }
+
+ /*
+ * Otherwise, the fun begins. We must fxp_opendir() the
+ * directory, slurp the filenames into memory, return
+ * SCP_SINK_DIR (unless this is a wildcard match), and
+ * set targetisdir. The next time we're called, we will
+ * run through the list of filenames one by one,
+ * matching them against a wildcard if present.
+ *
+ * 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);
+
+ if (!dirhandle) {
+ tell_user(stderr, "scp: unable to open directory %s: %s",
+ fname, fxp_error());
+ if (must_free_fname) sfree(fname);
+ errs++;
+ return 1;
+ }
+ 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);
+
+ if (names == NULL) {
+ if (fxp_error_type() == SSH_FX_EOF)
+ break;
+ tell_user(stderr, "scp: reading directory %s: %s\n",
+ fname, fxp_error());
+ if (must_free_fname) sfree(fname);
+ sfree(ournames);
+ errs++;
+ return 1;
+ }
+ if (names->nnames == 0) {
+ fxp_free_names(names);
+ break;
+ }
+ if (nnames + names->nnames >= namesize) {
+ namesize += names->nnames + 128;
+ ournames = sresize(ournames, namesize, struct fxp_name);
+ }
+ for (i = 0; i < names->nnames; i++) {
+ if (!strcmp(names->names[i].filename, ".") ||
+ !strcmp(names->names[i].filename, "..")) {
+ /*
+ * . and .. are normal consequences of
+ * 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",
+ 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);
+
+ newitem = snew(struct scp_sftp_dirstack);
+ newitem->next = scp_sftp_dirstack_head;
+ newitem->names = ournames;
+ newitem->namepos = 0;
+ newitem->namelen = nnames;
+ if (must_free_fname)
+ newitem->dirpath = fname;
+ else
+ newitem->dirpath = dupstr(fname);
+ if (scp_sftp_wildcard) {
+ newitem->wildcard = scp_sftp_wildcard;
+ newitem->matched_something = 0;
+ scp_sftp_wildcard = NULL;
+ } else {
+ newitem->wildcard = NULL;
+ }
+ scp_sftp_dirstack_head = newitem;
+
+ if (newitem->wildcard) {
+ act->action = SCP_SINK_RETRY;
+ } 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;
+ if (scp_sftp_preserve &&
+ (attrs.flags & SSH_FILEXFER_ATTR_ACMODTIME)) {
+ act->atime = attrs.atime;
+ act->mtime = attrs.mtime;
+ act->settime = 1;
+ } else
+ act->settime = 0;
+ }
+ return 0;
+
+ } else {
+ /*
+ * It's a file. Return SCP_SINK_FILE.
+ */
+ act->action = SCP_SINK_FILE;
+ act->buf = dupstr(stripslashes(fname, 0));
+ 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;
+ if (scp_sftp_preserve &&
+ (attrs.flags & SSH_FILEXFER_ATTR_ACMODTIME)) {
+ act->atime = attrs.atime;
+ act->mtime = attrs.mtime;
+ act->settime = 1;
+ } else
+ act->settime = 0;
+ if (must_free_fname)
+ scp_sftp_currentname = fname;
+ else
+ scp_sftp_currentname = dupstr(fname);
+ return 0;
+ }
+
+ } else {
+ int done = 0;
+ int i, bufsize;
+ int action;
+ char ch;
+
+ act->settime = 0;
+ act->buf = NULL;
+ bufsize = 0;
+
+ while (!done) {
+ if (ssh_scp_recv((unsigned char *) &ch, 1) <= 0)
+ return 1;
+ if (ch == '\n')
+ bump("Protocol error: Unexpected newline");
+ i = 0;
+ action = ch;
+ do {
+ if (ssh_scp_recv((unsigned char *) &ch, 1) <= 0)
+ bump("Lost connection");
+ if (i >= bufsize) {
+ bufsize = i + 128;
+ act->buf = sresize(act->buf, bufsize, char);
+ }
+ act->buf[i++] = ch;
+ } while (ch != '\n');
+ act->buf[i - 1] = '\0';
+ switch (action) {
+ case '\01': /* error */
+ tell_user(stderr, "%s\n", act->buf);
+ errs++;
+ continue; /* go round again */
+ case '\02': /* fatal error */
+ bump("%s", act->buf);
+ case 'E':
+ back->send(backhandle, "", 1);
+ act->action = SCP_SINK_ENDDIR;
+ return 0;
+ case 'T':
+ if (sscanf(act->buf, "%ld %*d %ld %*d",
+ &act->mtime, &act->atime) == 2) {
+ act->settime = 1;
+ back->send(backhandle, "", 1);
+ continue; /* go round again */
+ }
+ bump("Protocol error: Illegal time format");
+ case 'C':
+ case 'D':
+ act->action = (action == 'C' ? SCP_SINK_FILE : SCP_SINK_DIR);
+ break;
+ default:
+ bump("Protocol error: Expected control record");
+ }
+ /*
+ * We will go round this loop only once, unless we hit
+ * `continue' above.
+ */
+ done = 1;
+ }
+
+ /*
+ * If we get here, we must have seen SCP_SINK_FILE or
+ * SCP_SINK_DIR.
+ */
+ {
+ char sizestr[40];
+
+ if (sscanf(act->buf, "%o %s %n", &act->mode, sizestr, &i) != 2)
+ bump("Protocol error: Illegal file descriptor format");
+ act->size = uint64_from_decimal(sizestr);
+ act->name = act->buf + i;
+ return 0;
+ }
+ }
+}
+
+int scp_accept_filexfer(void)
+{
+ if (using_sftp) {
+ struct sftp_packet *pktin;
+ struct sftp_request *req, *rreq;
+
+ 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);
+
+ if (!scp_sftp_filehandle) {
+ tell_user(stderr, "pscp: unable to open %s: %s",
+ scp_sftp_currentname, fxp_error());
+ errs++;
+ return 1;
+ }
+ scp_sftp_fileoffset = uint64_make(0, 0);
+ scp_sftp_xfer = xfer_download_init(scp_sftp_filehandle,
+ scp_sftp_fileoffset);
+ sfree(scp_sftp_currentname);
+ return 0;
+ } else {
+ back->send(backhandle, "", 1);
+ return 0; /* can't fail */
+ }
+}
+
+int scp_recv_filedata(char *data, int len)
+{
+ if (using_sftp) {
+ struct sftp_packet *pktin;
+ int ret, actuallen;
+ void *vbuf;
+
+ xfer_download_queue(scp_sftp_xfer);
+ pktin = sftp_recv();
+ ret = xfer_download_gotpkt(scp_sftp_xfer, pktin);
+
+ if (ret < 0) {
+ tell_user(stderr, "pscp: error while reading: %s", fxp_error());
+ errs++;
+ return -1;
+ }
+
+ if (xfer_download_data(scp_sftp_xfer, &vbuf, &actuallen)) {
+ /*
+ * This assertion relies on the fact that the natural
+ * block size used in the xfer manager is at most that
+ * used in this module. I don't like crossing layers in
+ * this way, but it'll do for now.
+ */
+ assert(actuallen <= len);
+ memcpy(data, vbuf, actuallen);
+ sfree(vbuf);
+ } else
+ actuallen = 0;
+
+ scp_sftp_fileoffset = uint64_add32(scp_sftp_fileoffset, actuallen);
+
+ return actuallen;
+ } else {
+ return ssh_scp_recv((unsigned char *) data, len);
+ }
+}
+
+int scp_finish_filerecv(void)
+{
+ if (using_sftp) {
+ struct sftp_packet *pktin;
+ struct sftp_request *req, *rreq;
+
+ /*
+ * 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;
+
+ pktin = sftp_recv();
+ xfer_download_gotpkt(scp_sftp_xfer, pktin);
+ 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);
+ return 0;
+ } else {
+ back->send(backhandle, "", 1);
+ return response();
+ }
+}
+
+/* ----------------------------------------------------------------------
+ * Send an error message to the other side and to the screen.
+ * Increment error counter.
+ */
+static void run_err(const char *fmt, ...)
+{
+ char *str, *str2;
+ va_list ap;
+ va_start(ap, fmt);
+ errs++;
+ str = dupvprintf(fmt, ap);
+ str2 = dupcat("scp: ", str, "\n", NULL);
+ sfree(str);
+ scp_send_errmsg(str2);
+ tell_user(stderr, "%s", str2);
+ va_end(ap);
+ sfree(str2);
+}
+
+/*
+ * Execute the source part of the SCP protocol.
+ */
+static void source(char *src)
+{
+ uint64 size;
+ unsigned long mtime, atime;
+ char *last;
+ RFile *f;
+ int attr;
+ uint64 i;
+ uint64 stat_bytes;
+ time_t stat_starttime, stat_lasttime;
+
+ attr = file_type(src);
+ if (attr == FILE_TYPE_NONEXISTENT ||
+ attr == FILE_TYPE_WEIRD) {
+ run_err("%s: %s file or directory", src,
+ (attr == FILE_TYPE_WEIRD ? "Not a" : "No such"));
+ return;
+ }
+
+ if (attr == FILE_TYPE_DIRECTORY) {
+ if (recursive) {
+ /*
+ * Avoid . and .. directories.
+ */
+ char *p;
+ p = strrchr(src, '/');
+ if (!p)
+ p = strrchr(src, '\\');
+ if (!p)
+ p = src;
+ else
+ p++;
+ if (!strcmp(p, ".") || !strcmp(p, ".."))
+ /* skip . and .. */ ;
+ else
+ rsource(src);
+ } else {
+ run_err("%s: not a regular file", src);
+ }
+ return;
+ }
+
+ if ((last = strrchr(src, '/')) == NULL)
+ last = src;
+ else
+ last++;
+ 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);
+ if (f == NULL) {
+ run_err("%s: Cannot open file", src);
+ return;
+ }
+ if (preserve) {
+ if (scp_send_filetimes(mtime, atime))
+ 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))
+ return;
+
+ stat_bytes = uint64_make(0,0);
+ stat_starttime = time(NULL);
+ stat_lasttime = 0;
+
+ for (i = uint64_make(0,0);
+ uint64_compare(i,size) < 0;
+ i = uint64_add32(i,4096)) {
+ char transbuf[4096];
+ int j, k = 4096;
+
+ if (uint64_compare(uint64_add32(i, k),size) > 0) /* i + k > size */
+ k = (uint64_subtract(size, i)).lo; /* k = size - i; */
+ if ((j = read_from_file(f, transbuf, k)) != k) {
+ if (statistics)
+ printf("\n");
+ bump("%s: Read error", src);
+ }
+ if (scp_send_filedata(transbuf, k))
+ bump("%s: Network error occurred", src);
+
+ if (statistics) {
+ stat_bytes = uint64_add32(stat_bytes, k);
+ if (time(NULL) != stat_lasttime ||
+ (uint64_compare(uint64_add32(i, k), size) == 0)) {
+ stat_lasttime = time(NULL);
+ print_stats(last, size, stat_bytes,
+ stat_starttime, stat_lasttime);
+ }
+ }
+
+ }
+ close_rfile(f);
+
+ (void) scp_send_finish();
+}
+
+/*
+ * Recursively send the contents of a directory.
+ */
+static void rsource(char *src)
+{
+ char *last;
+ char *save_target;
+ DirHandle *dir;
+
+ if ((last = strrchr(src, '/')) == NULL)
+ last = src;
+ else
+ last++;
+ if (strrchr(last, '\\') != NULL)
+ last = strrchr(last, '\\') + 1;
+ if (last == src && strchr(src, ':') != NULL)
+ last = strchr(src, ':') + 1;
+
+ /* maybe send filetime */
+
+ save_target = scp_save_remotepath();
+
+ if (verbose)
+ tell_user(stderr, "Entering directory: %s", last);
+ if (scp_send_dirname(last, 0755))
+ return;
+
+ dir = open_directory(src);
+ if (dir != NULL) {
+ char *filename;
+ while ((filename = read_filename(dir)) != NULL) {
+ char *foundfile = dupcat(src, "/", filename, NULL);
+ source(foundfile);
+ sfree(foundfile);
+ sfree(filename);
+ }
+ }
+ close_directory(dir);
+
+ (void) scp_send_enddir();
+
+ scp_restore_remotepath(save_target);
+}
+
+/*
+ * Execute the sink part of the SCP protocol.
+ */
+static void sink(char *targ, char *src)
+{
+ char *destfname;
+ int targisdir = 0;
+ int exists;
+ int attr;
+ WFile *f;
+ uint64 received;
+ int wrerror = 0;
+ uint64 stat_bytes;
+ time_t stat_starttime, stat_lasttime;
+ char *stat_name;
+
+ attr = file_type(targ);
+ if (attr == FILE_TYPE_DIRECTORY)
+ targisdir = 1;
+
+ if (targetshouldbedirectory && !targisdir)
+ bump("%s: Not a directory", targ);
+
+ scp_sink_init();
+ while (1) {
+ struct scp_sink_action act;
+ if (scp_get_sink_action(&act))
+ return;
+
+ if (act.action == SCP_SINK_ENDDIR)
+ return;
+
+ if (act.action == SCP_SINK_RETRY)
+ continue;
+
+ if (targisdir) {
+ /*
+ * Prevent the remote side from maliciously writing to
+ * files outside the target area by sending a filename
+ * containing `../'. In fact, it shouldn't be sending
+ * filenames with any slashes or colons in at all; so
+ * we'll find the last slash, backslash or colon in the
+ * filename and use only the part after that. (And
+ * warn!)
+ *
+ * In addition, we also ensure here that if we're
+ * copying a single file and the target is a directory
+ * (common usage: `pscp host:filename .') the remote
+ * can't send us a _different_ file name. We can
+ * distinguish this case because `src' will be non-NULL
+ * and the last component of that will fail to match
+ * (the last component of) the name sent.
+ *
+ * Well, not always; if `src' is a wildcard, we do
+ * expect to get back filenames that don't correspond
+ * exactly to it. Ideally in this case, we would like
+ * to ensure that the returned filename actually
+ * matches the wildcard pattern - but one of SCP's
+ * protocol infelicities is that wildcard matching is
+ * done at the server end _by the server's rules_ and
+ * so in general this is infeasible. Hence, we only
+ * accept filenames that don't correspond to `src' if
+ * unsafe mode is enabled or we are using SFTP (which
+ * resolves remote wildcards on the client side and can
+ * be trusted).
+ */
+ char *striptarget, *stripsrc;
+
+ striptarget = stripslashes(act.name, 1);
+ if (striptarget != act.name) {
+ tell_user(stderr, "warning: remote host sent a compound"
+ " pathname '%s'", act.name);
+ tell_user(stderr, " renaming local file to '%s'",
+ striptarget);
+ }
+
+ /*
+ * Also check to see if the target filename is '.' or
+ * '..', or indeed '...' and so on because Windows
+ * appears to interpret those like '..'.
+ */
+ if (is_dots(striptarget)) {
+ bump("security violation: remote host attempted to write to"
+ " a '.' or '..' path!");
+ }
+
+ if (src) {
+ stripsrc = stripslashes(src, 1);
+ if (strcmp(striptarget, stripsrc) &&
+ !using_sftp && !scp_unsafe_mode) {
+ tell_user(stderr, "warning: remote host tried to write "
+ "to a file called '%s'", striptarget);
+ tell_user(stderr, " when we requested a file "
+ "called '%s'.", stripsrc);
+ tell_user(stderr, " If this is a wildcard, "
+ "consider upgrading to SSH-2 or using");
+ tell_user(stderr, " the '-unsafe' option. Renaming"
+ " of this file has been disallowed.");
+ /* Override the name the server provided with our own. */
+ striptarget = stripsrc;
+ }
+ }
+
+ if (targ[0] != '\0')
+ destfname = dir_file_cat(targ, striptarget);
+ else
+ destfname = dupstr(striptarget);
+ } else {
+ /*
+ * In this branch of the if, the target area is a
+ * single file with an explicitly specified name in any
+ * case, so there's no danger.
+ */
+ destfname = dupstr(targ);
+ }
+ attr = file_type(destfname);
+ exists = (attr != FILE_TYPE_NONEXISTENT);
+
+ if (act.action == SCP_SINK_DIR) {
+ if (exists && attr != FILE_TYPE_DIRECTORY) {
+ run_err("%s: Not a directory", destfname);
+ continue;
+ }
+ if (!exists) {
+ if (!create_directory(destfname)) {
+ run_err("%s: Cannot create directory", destfname);
+ continue;
+ }
+ }
+ sink(destfname, NULL);
+ /* can we set the timestamp for directories ? */
+ continue;
+ }
+
+ f = open_new_file(destfname);
+ if (f == NULL) {
+ run_err("%s: Cannot create file", destfname);
+ continue;
+ }
+
+ if (scp_accept_filexfer())
+ return;
+
+ stat_bytes = uint64_make(0, 0);
+ stat_starttime = time(NULL);
+ stat_lasttime = 0;
+ stat_name = stripslashes(destfname, 1);
+
+ received = uint64_make(0, 0);
+ while (uint64_compare(received,act.size) < 0) {
+ char transbuf[32768];
+ uint64 blksize;
+ int read;
+ blksize = uint64_make(0, 32768);
+ if (uint64_compare(blksize,uint64_subtract(act.size,received)) > 0)
+ blksize = uint64_subtract(act.size,received);
+ read = scp_recv_filedata(transbuf, (int)blksize.lo);
+ if (read <= 0)
+ bump("Lost connection");
+ if (wrerror)
+ continue;
+ if (write_to_file(f, transbuf, read) != (int)read) {
+ wrerror = 1;
+ /* FIXME: in sftp we can actually abort the transfer */
+ if (statistics)
+ printf("\r%-25.25s | %50s\n",
+ stat_name,
+ "Write error.. waiting for end of file");
+ continue;
+ }
+ if (statistics) {
+ stat_bytes = uint64_add32(stat_bytes,read);
+ if (time(NULL) > stat_lasttime ||
+ uint64_compare(uint64_add32(received, read), act.size) == 0) {
+ stat_lasttime = time(NULL);
+ print_stats(stat_name, act.size, stat_bytes,
+ stat_starttime, stat_lasttime);
+ }
+ }
+ received = uint64_add32(received, read);
+ }
+ if (act.settime) {
+ set_file_times(f, act.mtime, act.atime);
+ }
+
+ close_wfile(f);
+ if (wrerror) {
+ run_err("%s: Write error", destfname);
+ continue;
+ }
+ (void) scp_finish_filerecv();
+ sfree(destfname);
+ sfree(act.buf);
+ }
+}
+
+/*
+ * We will copy local files to a remote server.
+ */
+static void toremote(int argc, char *argv[])
+{
+ char *src, *targ, *host, *user;
+ char *cmd;
+ int i, wc_type;
+
+ targ = argv[argc - 1];
+
+ /* Separate host from filename */
+ host = targ;
+ targ = colon(targ);
+ if (targ == NULL)
+ bump("targ == NULL in toremote()");
+ *targ++ = '\0';
+ if (*targ == '\0')
+ targ = ".";
+ /* Substitute "." for empty target */
+
+ /* Separate host and username */
+ user = host;
+ host = strrchr(host, '@');
+ if (host == NULL) {
+ host = user;
+ user = NULL;
+ } else {
+ *host++ = '\0';
+ if (*user == '\0')
+ user = NULL;
+ }
+
+ if (argc == 2) {
+ if (colon(argv[0]) != NULL)
+ bump("%s: Remote to remote not supported", argv[0]);
+
+ wc_type = test_wildcard(argv[0], 1);
+ if (wc_type == WCTYPE_NONEXISTENT)
+ bump("%s: No such file or directory\n", argv[0]);
+ else if (wc_type == WCTYPE_WILDCARD)
+ targetshouldbedirectory = 1;
+ }
+
+ cmd = dupprintf("scp%s%s%s%s -t %s",
+ verbose ? " -v" : "",
+ recursive ? " -r" : "",
+ preserve ? " -p" : "",
+ targetshouldbedirectory ? " -d" : "", targ);
+ do_cmd(host, user, cmd);
+ sfree(cmd);
+
+ if (scp_source_setup(targ, targetshouldbedirectory))
+ return;
+
+ for (i = 0; i < argc - 1; i++) {
+ src = argv[i];
+ if (colon(src) != NULL) {
+ tell_user(stderr, "%s: Remote to remote not supported\n", src);
+ errs++;
+ continue;
+ }
+
+ wc_type = test_wildcard(src, 1);
+ if (wc_type == WCTYPE_NONEXISTENT) {
+ run_err("%s: No such file or directory", src);
+ continue;
+ } else if (wc_type == WCTYPE_FILENAME) {
+ source(src);
+ continue;
+ } else {
+ WildcardMatcher *wc;
+ char *filename;
+
+ wc = begin_wildcard_matching(src);
+ if (wc == NULL) {
+ run_err("%s: No such file or directory", src);
+ continue;
+ }
+
+ while ((filename = wildcard_get_filename(wc)) != NULL) {
+ source(filename);
+ sfree(filename);
+ }
+
+ finish_wildcard_matching(wc);
+ }
+ }
+}
+
+/*
+ * We will copy files from a remote server to the local machine.
+ */
+static void tolocal(int argc, char *argv[])
+{
+ char *src, *targ, *host, *user;
+ char *cmd;
+
+ if (argc != 2)
+ bump("More than one remote source not supported");
+
+ src = argv[0];
+ targ = argv[1];
+
+ /* Separate host from filename */
+ host = src;
+ src = colon(src);
+ if (src == NULL)
+ bump("Local to local copy not supported");
+ *src++ = '\0';
+ if (*src == '\0')
+ src = ".";
+ /* Substitute "." for empty filename */
+
+ /* Separate username and hostname */
+ user = host;
+ host = strrchr(host, '@');
+ if (host == NULL) {
+ host = user;
+ user = NULL;
+ } else {
+ *host++ = '\0';
+ if (*user == '\0')
+ user = NULL;
+ }
+
+ cmd = dupprintf("scp%s%s%s%s -f %s",
+ verbose ? " -v" : "",
+ recursive ? " -r" : "",
+ preserve ? " -p" : "",
+ targetshouldbedirectory ? " -d" : "", src);
+ do_cmd(host, user, cmd);
+ sfree(cmd);
+
+ if (scp_sink_setup(src, preserve, recursive))
+ return;
+
+ sink(targ, src);
+}
+
+/*
+ * We will issue a list command to get a remote directory.
+ */
+static void get_dir_list(int argc, char *argv[])
+{
+ char *src, *host, *user;
+ char *cmd, *p, *q;
+ char c;
+
+ src = argv[0];
+
+ /* Separate host from filename */
+ host = src;
+ src = colon(src);
+ if (src == NULL)
+ bump("Local file listing not supported");
+ *src++ = '\0';
+ if (*src == '\0')
+ src = ".";
+ /* Substitute "." for empty filename */
+
+ /* Separate username and hostname */
+ user = host;
+ host = strrchr(host, '@');
+ if (host == NULL) {
+ host = user;
+ user = NULL;
+ } else {
+ *host++ = '\0';
+ if (*user == '\0')
+ user = NULL;
+ }
+
+ cmd = snewn(4 * strlen(src) + 100, char);
+ strcpy(cmd, "ls -la '");
+ p = cmd + strlen(cmd);
+ for (q = src; *q; q++) {
+ if (*q == '\'') {
+ *p++ = '\'';
+ *p++ = '\\';
+ *p++ = '\'';
+ *p++ = '\'';
+ } else {
+ *p++ = *q;
+ }
+ }
+ *p++ = '\'';
+ *p = '\0';
+
+ do_cmd(host, user, cmd);
+ sfree(cmd);
+
+ if (using_sftp) {
+ scp_sftp_listdir(src);
+ } else {
+ while (ssh_scp_recv((unsigned char *) &c, 1) > 0)
+ tell_char(stdout, c);
+ }
+}
+
+/*
+ * Short description of parameters.
+ */
+static void usage(void)
+{
+ printf("PuTTY Secure Copy client\n");
+ printf("%s\n", ver);
+ printf("Usage: pscp [options] [user@]host:source target\n");
+ printf
+ (" pscp [options] source [source...] [user@]host:target\n");
+ printf(" pscp [options] -ls [user@]host:filespec\n");
+ printf("Options:\n");
+ printf(" -V print version information and exit\n");
+ printf(" -pgpfp print PGP key fingerprints and exit\n");
+ printf(" -p preserve file attributes\n");
+ printf(" -q quiet, don't show statistics\n");
+ printf(" -r copy directories recursively\n");
+ printf(" -v show verbose messages\n");
+ printf(" -load sessname Load settings from saved session\n");
+ printf(" -P port connect to specified port\n");
+ printf(" -l user connect with specified username\n");
+ printf(" -pw passw login with specified password\n");
+ printf(" -1 -2 force use of particular SSH protocol version\n");
+ printf(" -4 -6 force use of IPv4 or IPv6\n");
+ printf(" -C enable compression\n");
+ printf(" -i key private key file for authentication\n");
+ printf(" -noagent disable use of Pageant\n");
+ printf(" -agent enable use of Pageant\n");
+ printf(" -batch disable all interactive prompts\n");
+ printf(" -unsafe allow server-side wildcards (DANGEROUS)\n");
+ printf(" -sftp force use of SFTP protocol\n");
+ printf(" -scp force use of SCP protocol\n");
+#if 0
+ /*
+ * -gui is an internal option, used by GUI front ends to get
+ * pscp to pass progress reports back to them. It's not an
+ * ordinary user-accessible option, so it shouldn't be part of
+ * the command-line help. The only people who need to know
+ * about it are programmers, and they can read the source.
+ */
+ printf
+ (" -gui hWnd GUI mode with the windows handle for receiving messages\n");
+#endif
+ cleanup_exit(1);
+}
+
+void version(void)
+{
+ printf("pscp: %s\n", ver);
+ cleanup_exit(1);
+}
+
+void cmdline_error(char *p, ...)
+{
+ va_list ap;
+ fprintf(stderr, "pscp: ");
+ va_start(ap, p);
+ vfprintf(stderr, p, ap);
+ va_end(ap);
+ fprintf(stderr, "\n try typing just \"pscp\" for help\n");
+ exit(1);
+}
+
+/*
+ * 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[])
+{
+ int i;
+
+ default_protocol = PROT_TELNET;
+
+ flags = FLAG_STDERR
+#ifdef FLAG_SYNCAGENT
+ | FLAG_SYNCAGENT
+#endif
+ ;
+ cmdline_tooltype = TOOLTYPE_FILETRANSFER;
+ sk_init();
+
+ /* Load Default Settings before doing anything else. */
+ do_defaults(NULL, &cfg);
+ loaded_session = FALSE;
+
+ for (i = 1; i < argc; i++) {
+ int ret;
+ if (argv[i][0] != '-')
+ break;
+ ret = cmdline_process_param(argv[i], i+1<argc?argv[i+1]:NULL, 1, &cfg);
+ if (ret == -2) {
+ cmdline_error("option \"%s\" requires an argument", argv[i]);
+ } else if (ret == 2) {
+ i++; /* skip next argument */
+ } else if (ret == 1) {
+ /* We have our own verbosity in addition to `flags'. */
+ if (flags & FLAG_VERBOSE)
+ verbose = 1;
+ } else if (strcmp(argv[i], "-pgpfp") == 0) {
+ pgp_fingerprints();
+ return 1;
+ } else if (strcmp(argv[i], "-r") == 0) {
+ recursive = 1;
+ } else if (strcmp(argv[i], "-p") == 0) {
+ preserve = 1;
+ } else if (strcmp(argv[i], "-q") == 0) {
+ statistics = 0;
+ } else if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "-?") == 0) {
+ usage();
+ } else if (strcmp(argv[i], "-V") == 0) {
+ version();
+ } else if (strcmp(argv[i], "-ls") == 0) {
+ list = 1;
+ } else if (strcmp(argv[i], "-batch") == 0) {
+ console_batch_mode = 1;
+ } else if (strcmp(argv[i], "-unsafe") == 0) {
+ scp_unsafe_mode = 1;
+ } else if (strcmp(argv[i], "-sftp") == 0) {
+ try_scp = 0; try_sftp = 1;
+ } else if (strcmp(argv[i], "-scp") == 0) {
+ try_scp = 1; try_sftp = 0;
+ } else if (strcmp(argv[i], "--") == 0) {
+ i++;
+ break;
+ } else {
+ cmdline_error("unknown option \"%s\"", argv[i]);
+ }
+ }
+ argc -= i;
+ argv += i;
+ back = NULL;
+
+ if (list) {
+ if (argc != 1)
+ usage();
+ get_dir_list(argc, argv);
+
+ } else {
+
+ if (argc < 2)
+ usage();
+ if (argc > 2)
+ targetshouldbedirectory = 1;
+
+ if (colon(argv[argc - 1]) != NULL)
+ toremote(argc, argv);
+ else
+ tolocal(argc, argv);
+ }
+
+ if (back != NULL && back->connected(backhandle)) {
+ char ch;
+ back->special(backhandle, TS_EOF);
+ ssh_scp_recv((unsigned char *) &ch, 1);
+ }
+ random_save_seed();
+
+ cmdline_cleanup();
+ console_provide_logctx(NULL);
+ back->free(backhandle);
+ backhandle = NULL;
+ back = NULL;
+ sk_cleanup();
+ return (errs == 0 ? 0 : 1);
+}
+
+/* end */
--- /dev/null
+/*
+ * psftp.c: (platform-independent) front end for PSFTP.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <assert.h>
+#include <limits.h>
+
+#define PUTTY_DO_GLOBALS
+#include "putty.h"
+#include "psftp.h"
+#include "storage.h"
+#include "ssh.h"
+#include "sftp.h"
+#include "int64.h"
+
+const char *const appname = "PSFTP";
+
+/*
+ * Since SFTP is a request-response oriented protocol, it requires
+ * no buffer management: when we send data, we stop and wait for an
+ * acknowledgement _anyway_, and so we can't possibly overfill our
+ * send buffer.
+ */
+
+static int psftp_connect(char *userhost, char *user, int portnumber);
+static int do_sftp_init(void);
+void do_sftp_cleanup();
+
+/* ----------------------------------------------------------------------
+ * sftp client state.
+ */
+
+char *pwd, *homedir;
+static Backend *back;
+static void *backhandle;
+static Config cfg;
+
+/* ----------------------------------------------------------------------
+ * Higher-level helper functions used in commands.
+ */
+
+/*
+ * Attempt to canonify a pathname starting from the pwd. If
+ * canonification fails, at least fall back to returning a _valid_
+ * pathname (though it may be ugly, eg /home/simon/../foobar).
+ */
+char *canonify(char *name)
+{
+ char *fullname, *canonname;
+ struct sftp_packet *pktin;
+ struct sftp_request *req, *rreq;
+
+ if (name[0] == '/') {
+ fullname = dupstr(name);
+ } else {
+ char *slash;
+ if (pwd[strlen(pwd) - 1] == '/')
+ slash = "";
+ 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);
+
+ if (canonname) {
+ sfree(fullname);
+ return canonname;
+ } else {
+ /*
+ * Attempt number 2. Some FXP_REALPATH implementations
+ * (glibc-based ones, in particular) require the _whole_
+ * path to point to something that exists, whereas others
+ * (BSD-based) only require all but the last component to
+ * exist. So if the first call failed, we should strip off
+ * everything from the last slash onwards and try again,
+ * then put the final component back on.
+ *
+ * Special cases:
+ *
+ * - if the last component is "/." or "/..", then we don't
+ * bother trying this because there's no way it can work.
+ *
+ * - if the thing actually ends with a "/", we remove it
+ * before we start. Except if the string is "/" itself
+ * (although I can't see why we'd have got here if so,
+ * because surely "/" would have worked the first
+ * time?), in which case we don't bother.
+ *
+ * - if there's no slash in the string at all, give up in
+ * confusion (we expect at least one because of the way
+ * we constructed the string).
+ */
+
+ int i;
+ char *returnname;
+
+ i = strlen(fullname);
+ if (i > 2 && fullname[i - 1] == '/')
+ fullname[--i] = '\0'; /* strip trailing / unless at pos 0 */
+ while (i > 0 && fullname[--i] != '/');
+
+ /*
+ * Give up on special cases.
+ */
+ if (fullname[i] != '/' || /* no slash at all */
+ !strcmp(fullname + i, "/.") || /* ends in /. */
+ !strcmp(fullname + i, "/..") || /* ends in /.. */
+ !strcmp(fullname, "/")) {
+ return fullname;
+ }
+
+ /*
+ * 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("/"));
+ } else {
+ sftp_register(req = fxp_realpath_send(fullname));
+ }
+ rreq = sftp_find_request(pktin = sftp_recv());
+ assert(rreq == req);
+ canonname = fxp_realpath_recv(pktin, rreq);
+
+ if (!canonname) {
+ /* Even that failed. Restore our best guess at the
+ * constructed filename and give up */
+ fullname[i] = '/'; /* restore slash and last component */
+ return fullname;
+ }
+
+ /*
+ * We have a canonical name for all but the last path
+ * component. Concatenate the last component and return.
+ */
+ returnname = dupcat(canonname,
+ canonname[strlen(canonname) - 1] ==
+ '/' ? "" : "/", fullname + i + 1, NULL);
+ sfree(fullname);
+ sfree(canonname);
+ return returnname;
+ }
+}
+
+/*
+ * Return a pointer to the portion of str that comes after the last
+ * slash (or backslash or colon, if `local' is TRUE).
+ */
+static char *stripslashes(char *str, int local)
+{
+ char *p;
+
+ if (local) {
+ p = strchr(str, ':');
+ if (p) str = p+1;
+ }
+
+ p = strrchr(str, '/');
+ if (p) str = p+1;
+
+ if (local) {
+ p = strrchr(str, '\\');
+ if (p) str = p+1;
+ }
+
+ return str;
+}
+
+/*
+ * qsort comparison routine for fxp_name structures. Sorts by real
+ * file name.
+ */
+static int sftp_name_compare(const void *av, const void *bv)
+{
+ const struct fxp_name *const *a = (const struct fxp_name *const *) av;
+ const struct fxp_name *const *b = (const struct fxp_name *const *) bv;
+ return strcmp((*a)->filename, (*b)->filename);
+}
+
+/*
+ * Likewise, but for a bare char *.
+ */
+static int bare_name_compare(const void *av, const void *bv)
+{
+ const char **a = (const char **) av;
+ const char **b = (const char **) bv;
+ return strcmp(*a, *b);
+}
+
+static void not_connected(void)
+{
+ printf("psftp: not connected to a host; use \"open host.name\"\n");
+}
+
+/* ----------------------------------------------------------------------
+ * The meat of the `get' and `put' commands.
+ */
+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 fxp_xfer *xfer;
+ uint64 offset;
+ WFile *file;
+ int ret, shown_err = FALSE;
+
+ /*
+ * 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);
+
+ if (result &&
+ (attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS) &&
+ (attrs.permissions & 0040000)) {
+
+ struct fxp_handle *dirhandle;
+ int nnames, namesize;
+ struct fxp_name **ournames;
+ struct fxp_names *names;
+ int i;
+
+ /*
+ * First, attempt to create the destination directory,
+ * unless it already exists.
+ */
+ if (file_type(outfname) != FILE_TYPE_DIRECTORY &&
+ !create_directory(outfname)) {
+ printf("%s: Cannot create directory\n", outfname);
+ return 0;
+ }
+
+ /*
+ * 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);
+
+ if (!dirhandle) {
+ printf("%s: unable to open directory: %s\n",
+ fname, fxp_error());
+ return 0;
+ }
+ 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);
+
+ if (names == NULL) {
+ if (fxp_error_type() == SSH_FX_EOF)
+ break;
+ printf("%s: reading directory: %s\n", fname, fxp_error());
+ sfree(ournames);
+ return 0;
+ }
+ if (names->nnames == 0) {
+ fxp_free_names(names);
+ break;
+ }
+ if (nnames + names->nnames >= namesize) {
+ namesize += names->nnames + 128;
+ ournames = sresize(ournames, namesize, struct fxp_name *);
+ }
+ for (i = 0; i < names->nnames; i++)
+ if (strcmp(names->names[i].filename, ".") &&
+ strcmp(names->names[i].filename, "..")) {
+ if (!vet_filename(names->names[i].filename)) {
+ printf("ignoring potentially dangerous server-"
+ "supplied filename '%s'\n",
+ names->names[i].filename);
+ } else {
+ ournames[nnames++] =
+ 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);
+
+ /*
+ * 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 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
+ * anything on the previous files.
+ *
+ * If none of them exists, of course, we start at 0.
+ */
+ 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);
+ ret = (file_type(nextoutfname) == FILE_TYPE_NONEXISTENT);
+ sfree(nextoutfname);
+ if (ret)
+ break;
+ i++;
+ }
+ if (i > 0)
+ i--;
+ }
+
+ /*
+ * Now we're ready to recurse. Starting at ournames[i]
+ * and continuing on to the end of the list, we
+ * construct a new source and target file name, and
+ * call sftp_get_file again.
+ */
+ 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);
+ ret = sftp_get_file(nextfname, nextoutfname, recurse, restart);
+ restart = FALSE; /* after first partial file, do full */
+ sfree(nextoutfname);
+ sfree(nextfname);
+ if (!ret) {
+ for (i = 0; i < nnames; i++) {
+ fxp_free_name(ournames[i]);
+ }
+ sfree(ournames);
+ return 0;
+ }
+ }
+
+ /*
+ * Done this recursion level. Free everything.
+ */
+ for (i = 0; i < nnames; i++) {
+ fxp_free_name(ournames[i]);
+ }
+ sfree(ournames);
+
+ 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);
+
+ 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);
+ }
+
+ 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);
+
+ return 0;
+ }
+
+ if (restart) {
+ 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);
+
+ return 0;
+ }
+
+ offset = get_file_posn(file);
+ uint64_decimal(offset, decbuf);
+ printf("reget: restarting at file position %s\n", decbuf);
+ } else {
+ offset = uint64_make(0, 0);
+ }
+
+ printf("remote:%s => local:%s\n", fname, outfname);
+
+ /*
+ * FIXME: we can use FXP_FSTAT here to get the file size, and
+ * thus put up a progress bar.
+ */
+ ret = 1;
+ xfer = xfer_download_init(fh, offset);
+ while (!xfer_done(xfer)) {
+ void *vbuf;
+ int ret, len;
+ int wpos, wlen;
+
+ xfer_download_queue(xfer);
+ pktin = sftp_recv();
+ ret = xfer_download_gotpkt(xfer, pktin);
+
+ if (ret < 0) {
+ if (!shown_err) {
+ printf("error while reading: %s\n", fxp_error());
+ shown_err = TRUE;
+ }
+ ret = 0;
+ }
+
+ while (xfer_download_data(xfer, &vbuf, &len)) {
+ unsigned char *buf = (unsigned char *)vbuf;
+
+ wpos = 0;
+ while (wpos < len) {
+ wlen = write_to_file(file, buf + wpos, len - wpos);
+ if (wlen <= 0) {
+ printf("error while writing local file\n");
+ ret = 0;
+ xfer_set_error(xfer);
+ break;
+ }
+ wpos += wlen;
+ }
+ if (wpos < len) { /* we had an error */
+ ret = 0;
+ xfer_set_error(xfer);
+ }
+
+ sfree(vbuf);
+ }
+ }
+
+ 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);
+
+ 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;
+ uint64 offset;
+ RFile *file;
+ int ret, err, eof;
+
+ /*
+ * 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;
+
+ /*
+ * 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);
+ 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);
+
+ if (!result) {
+ printf("%s: create directory: %s\n",
+ outfname, fxp_error());
+ return 0;
+ }
+ }
+
+ /*
+ * Now get the list of filenames in the local directory.
+ */
+ nnames = namesize = 0;
+ ournames = NULL;
+
+ dh = open_directory(fname);
+ if (!dh) {
+ printf("%s: unable to open directory\n", fname);
+ return 0;
+ }
+ while ((name = read_filename(dh)) != NULL) {
+ if (nnames >= namesize) {
+ namesize += 128;
+ ournames = sresize(ournames, namesize, char *);
+ }
+ ournames[nnames++] = name;
+ }
+ close_directory(dh);
+
+ /*
+ * 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 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
+ * previous files.
+ *
+ * If none of them exists, of course, we start at 0.
+ */
+ 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);
+ sfree(nextoutfname);
+ if (!result)
+ break;
+ i++;
+ }
+ if (i > 0)
+ i--;
+ }
+
+ /*
+ * Now we're ready to recurse. Starting at ournames[i]
+ * and continuing on to the end of the list, we
+ * construct a new source and target file name, and
+ * call sftp_put_file again.
+ */
+ for (; i < nnames; i++) {
+ char *nextfname, *nextoutfname;
+ int ret;
+
+ if (fname)
+ nextfname = dir_file_cat(fname, ournames[i]);
+ else
+ nextfname = dupstr(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);
+ if (!ret) {
+ for (i = 0; i < nnames; i++) {
+ sfree(ournames[i]);
+ }
+ sfree(ournames);
+ return 0;
+ }
+ }
+
+ /*
+ * Done this recursion level. Free everything.
+ */
+ for (i = 0; i < nnames; i++) {
+ sfree(ournames[i]);
+ }
+ sfree(ournames);
+
+ return 1;
+ }
+
+ file = open_existing_file(fname, NULL, NULL, NULL);
+ if (!file) {
+ printf("local: unable to open %s\n", fname);
+ return 0;
+ }
+ if (restart) {
+ sftp_register(req = fxp_open_send(outfname, SSH_FXF_WRITE));
+ } else {
+ sftp_register(req = fxp_open_send(outfname, SSH_FXF_WRITE |
+ SSH_FXF_CREAT | SSH_FXF_TRUNC));
+ }
+ rreq = sftp_find_request(pktin = sftp_recv());
+ assert(rreq == req);
+ fh = fxp_open_recv(pktin, rreq);
+
+ if (!fh) {
+ close_rfile(file);
+ printf("%s: open for write: %s\n", outfname, fxp_error());
+ return 0;
+ }
+
+ 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);
+
+ if (!ret) {
+ close_rfile(file);
+ printf("read size of %s: %s\n", outfname, fxp_error());
+ return 0;
+ }
+ if (!(attrs.flags & SSH_FILEXFER_ATTR_SIZE)) {
+ close_rfile(file);
+ printf("read size of %s: size was not given\n", outfname);
+ return 0;
+ }
+ offset = attrs.size;
+ uint64_decimal(offset, decbuf);
+ printf("reput: restarting at file position %s\n", decbuf);
+
+ if (seek_file((WFile *)file, offset, FROM_START) != 0)
+ seek_file((WFile *)file, uint64_make(0,0), FROM_END); /* *shrug* */
+ } else {
+ offset = uint64_make(0, 0);
+ }
+
+ printf("local:%s => remote:%s\n", fname, outfname);
+
+ /*
+ * FIXME: we can use FXP_FSTAT here to get the file size, and
+ * thus put up a progress bar.
+ */
+ ret = 1;
+ xfer = xfer_upload_init(fh, offset);
+ err = eof = 0;
+ while ((!err && !eof) || !xfer_done(xfer)) {
+ char buffer[4096];
+ int len, ret;
+
+ while (xfer_upload_ready(xfer) && !err && !eof) {
+ len = read_from_file(file, buffer, sizeof(buffer));
+ if (len == -1) {
+ printf("error while reading local file\n");
+ err = 1;
+ } else if (len == 0) {
+ eof = 1;
+ } else {
+ xfer_upload_data(xfer, buffer, len);
+ }
+ }
+
+ 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;
+ }
+ }
+ }
+
+ 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);
+
+ close_rfile(file);
+
+ return ret;
+}
+
+/* ----------------------------------------------------------------------
+ * A remote wildcard matcher, providing a similar interface to the
+ * local one in psftp.h.
+ */
+
+typedef struct SftpWildcardMatcher {
+ struct fxp_handle *dirh;
+ struct fxp_names *names;
+ int namepos;
+ char *wildcard, *prefix;
+} SftpWildcardMatcher;
+
+SftpWildcardMatcher *sftp_begin_wildcard_matching(char *name)
+{
+ struct sftp_packet *pktin;
+ struct sftp_request *req, *rreq;
+ char *wildcard;
+ char *unwcdir, *tmpdir, *cdir;
+ int len, check;
+ SftpWildcardMatcher *swcm;
+ struct fxp_handle *dirh;
+
+ /*
+ * We don't handle multi-level wildcards; so we expect to find
+ * a fully specified directory part, followed by a wildcard
+ * after that.
+ */
+ wildcard = stripslashes(name, 0);
+
+ unwcdir = dupstr(name);
+ len = wildcard - name;
+ unwcdir[len] = '\0';
+ if (len > 0 && unwcdir[len-1] == '/')
+ unwcdir[len-1] = '\0';
+ tmpdir = snewn(1 + len, char);
+ check = wc_unescape(tmpdir, unwcdir);
+ sfree(tmpdir);
+
+ if (!check) {
+ printf("Multiple-level wildcards are not supported\n");
+ sfree(unwcdir);
+ 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);
+
+ if (dirh) {
+ swcm = snew(SftpWildcardMatcher);
+ swcm->dirh = dirh;
+ swcm->names = NULL;
+ swcm->wildcard = dupstr(wildcard);
+ swcm->prefix = unwcdir;
+ } else {
+ printf("Unable to open %s: %s\n", cdir, fxp_error());
+ swcm = NULL;
+ sfree(unwcdir);
+ }
+
+ sfree(cdir);
+
+ return swcm;
+}
+
+char *sftp_wildcard_get_filename(SftpWildcardMatcher *swcm)
+{
+ struct fxp_name *name;
+ struct sftp_packet *pktin;
+ struct sftp_request *req, *rreq;
+
+ 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);
+
+ if (!swcm->names) {
+ if (fxp_error_type() != SSH_FX_EOF)
+ printf("%s: reading directory: %s\n", swcm->prefix,
+ fxp_error());
+ return NULL;
+ }
+
+ swcm->namepos = 0;
+ }
+
+ assert(swcm->names && swcm->namepos < swcm->names->nnames);
+
+ name = &swcm->names->names[swcm->namepos++];
+
+ if (!strcmp(name->filename, ".") || !strcmp(name->filename, ".."))
+ continue; /* expected bad filenames */
+
+ if (!vet_filename(name->filename)) {
+ printf("ignoring potentially dangerous server-"
+ "supplied filename '%s'\n", name->filename);
+ continue; /* unexpected bad filename */
+ }
+
+ if (!wc_match(swcm->wildcard, name->filename))
+ continue; /* doesn't match the wildcard */
+
+ /*
+ * We have a working filename. Return it.
+ */
+ return dupprintf("%s%s%s", swcm->prefix,
+ (!swcm->prefix[0] ||
+ swcm->prefix[strlen(swcm->prefix)-1]=='/' ?
+ "" : "/"),
+ name->filename);
+ }
+}
+
+void sftp_finish_wildcard_matching(SftpWildcardMatcher *swcm)
+{
+ struct sftp_packet *pktin;
+ struct sftp_request *req, *rreq;
+
+ sftp_register(req = fxp_close_send(swcm->dirh));
+ rreq = sftp_find_request(pktin = sftp_recv());
+ assert(rreq == req);
+ fxp_close_recv(pktin, rreq);
+
+ if (swcm->names)
+ fxp_free_names(swcm->names);
+
+ sfree(swcm->prefix);
+ sfree(swcm->wildcard);
+
+ sfree(swcm);
+}
+
+/*
+ * General function to match a potential wildcard in a filename
+ * argument and iterate over every matching file. Used in several
+ * PSFTP commands (rmdir, rm, chmod, mv).
+ */
+int wildcard_iterate(char *filename, int (*func)(void *, char *), void *ctx)
+{
+ char *unwcfname, *newname, *cname;
+ int is_wc, ret;
+
+ unwcfname = snewn(strlen(filename)+1, char);
+ is_wc = !wc_unescape(unwcfname, filename);
+
+ if (is_wc) {
+ SftpWildcardMatcher *swcm = sftp_begin_wildcard_matching(filename);
+ int matched = FALSE;
+ sfree(unwcfname);
+
+ if (!swcm)
+ return 0;
+
+ ret = 1;
+
+ while ( (newname = sftp_wildcard_get_filename(swcm)) != NULL ) {
+ cname = canonify(newname);
+ if (!cname) {
+ printf("%s: canonify: %s\n", newname, fxp_error());
+ ret = 0;
+ }
+ matched = TRUE;
+ ret &= func(ctx, cname);
+ sfree(cname);
+ }
+
+ if (!matched) {
+ /* Politely warn the user that nothing matched. */
+ printf("%s: nothing matched\n", filename);
+ }
+
+ sftp_finish_wildcard_matching(swcm);
+ } else {
+ cname = canonify(unwcfname);
+ if (!cname) {
+ printf("%s: canonify: %s\n", filename, fxp_error());
+ ret = 0;
+ }
+ ret = func(ctx, cname);
+ sfree(cname);
+ sfree(unwcfname);
+ }
+
+ return ret;
+}
+
+/*
+ * Handy helper function.
+ */
+int is_wildcard(char *name)
+{
+ char *unwcfname = snewn(strlen(name)+1, char);
+ int is_wc = !wc_unescape(unwcfname, name);
+ sfree(unwcfname);
+ return is_wc;
+}
+
+/* ----------------------------------------------------------------------
+ * Actual sftp commands.
+ */
+struct sftp_command {
+ char **words;
+ int nwords, wordssize;
+ int (*obey) (struct sftp_command *); /* returns <0 to quit */
+};
+
+int sftp_cmd_null(struct sftp_command *cmd)
+{
+ return 1; /* success */
+}
+
+int sftp_cmd_unknown(struct sftp_command *cmd)
+{
+ printf("psftp: unknown command \"%s\"\n", cmd->words[0]);
+ return 0; /* failure */
+}
+
+int sftp_cmd_quit(struct sftp_command *cmd)
+{
+ return -1;
+}
+
+int sftp_cmd_close(struct sftp_command *cmd)
+{
+ if (back == NULL) {
+ not_connected();
+ return 0;
+ }
+
+ if (back != NULL && back->connected(backhandle)) {
+ char ch;
+ back->special(backhandle, TS_EOF);
+ sftp_recvdata(&ch, 1);
+ }
+ do_sftp_cleanup();
+
+ return 0;
+}
+
+/*
+ * List a directory. If no arguments are given, list pwd; otherwise
+ * list the directory given in words[1].
+ */
+int sftp_cmd_ls(struct sftp_command *cmd)
+{
+ struct fxp_handle *dirh;
+ 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;
+ int i;
+
+ if (back == NULL) {
+ not_connected();
+ return 0;
+ }
+
+ if (cmd->nwords < 2)
+ dir = ".";
+ else
+ dir = cmd->words[1];
+
+ unwcdir = snewn(1 + strlen(dir), char);
+ if (wc_unescape(unwcdir, dir)) {
+ dir = unwcdir;
+ wildcard = NULL;
+ } else {
+ char *tmpdir;
+ int len, check;
+
+ wildcard = stripslashes(dir, 0);
+ unwcdir = dupstr(dir);
+ len = wildcard - dir;
+ unwcdir[len] = '\0';
+ if (len > 0 && unwcdir[len-1] == '/')
+ unwcdir[len-1] = '\0';
+ tmpdir = snewn(1 + len, char);
+ check = wc_unescape(tmpdir, unwcdir);
+ sfree(tmpdir);
+ if (!check) {
+ printf("Multiple-level wildcards are not supported\n");
+ sfree(unwcdir);
+ return 0;
+ }
+ dir = unwcdir;
+ }
+
+ cdir = canonify(dir);
+ if (!cdir) {
+ printf("%s: canonify: %s\n", dir, fxp_error());
+ sfree(unwcdir);
+ 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);
+
+ 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);
+
+ if (names == NULL) {
+ if (fxp_error_type() == SSH_FX_EOF)
+ break;
+ printf("Reading directory %s: %s\n", dir, fxp_error());
+ break;
+ }
+ if (names->nnames == 0) {
+ fxp_free_names(names);
+ break;
+ }
+
+ if (nnames + names->nnames >= namesize) {
+ namesize += names->nnames + 128;
+ ournames = sresize(ournames, namesize, struct fxp_name *);
+ }
+
+ for (i = 0; i < names->nnames; i++)
+ 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);
+
+ /*
+ * 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);
+
+ /*
+ * And print them.
+ */
+ for (i = 0; i < nnames; i++) {
+ printf("%s\n", ournames[i]->longname);
+ fxp_free_name(ournames[i]);
+ }
+ sfree(ournames);
+ }
+
+ sfree(cdir);
+ sfree(unwcdir);
+
+ return 1;
+}
+
+/*
+ * Change directories. We do this by canonifying the new name, then
+ * trying to OPENDIR it. Only if that succeeds do we set the new pwd.
+ */
+int sftp_cmd_cd(struct sftp_command *cmd)
+{
+ struct fxp_handle *dirh;
+ struct sftp_packet *pktin;
+ struct sftp_request *req, *rreq;
+ char *dir;
+
+ if (back == NULL) {
+ not_connected();
+ return 0;
+ }
+
+ if (cmd->nwords < 2)
+ dir = dupstr(homedir);
+ else
+ dir = canonify(cmd->words[1]);
+
+ 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);
+
+ 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);
+
+ sfree(pwd);
+ pwd = dir;
+ printf("Remote directory is now %s\n", pwd);
+
+ return 1;
+}
+
+/*
+ * Print current directory. Easy as pie.
+ */
+int sftp_cmd_pwd(struct sftp_command *cmd)
+{
+ if (back == NULL) {
+ not_connected();
+ return 0;
+ }
+
+ printf("Remote directory is %s\n", pwd);
+ return 1;
+}
+
+/*
+ * Get a file and save it at the local end. We have three very
+ * similar commands here. The basic one is `get'; `reget' differs
+ * in that it checks for the existence of the destination file and
+ * starts from where a previous aborted transfer left off; `mget'
+ * differs in that it interprets all its arguments as files to
+ * transfer (never as a different local name for a remote file) and
+ * can handle wildcards.
+ */
+int sftp_general_get(struct sftp_command *cmd, int restart, int multiple)
+{
+ char *fname, *unwcfname, *origfname, *origwfname, *outfname;
+ int i, ret;
+ int recurse = FALSE;
+
+ if (back == NULL) {
+ not_connected();
+ return 0;
+ }
+
+ i = 1;
+ while (i < cmd->nwords && cmd->words[i][0] == '-') {
+ if (!strcmp(cmd->words[i], "--")) {
+ /* finish processing options */
+ i++;
+ break;
+ } else if (!strcmp(cmd->words[i], "-r")) {
+ recurse = TRUE;
+ } else {
+ printf("%s: unrecognised option '%s'\n", cmd->words[0], cmd->words[i]);
+ return 0;
+ }
+ i++;
+ }
+
+ if (i >= cmd->nwords) {
+ printf("%s: expects a filename\n", cmd->words[0]);
+ return 0;
+ }
+
+ ret = 1;
+ do {
+ SftpWildcardMatcher *swcm;
+
+ origfname = cmd->words[i++];
+ unwcfname = snewn(strlen(origfname)+1, char);
+
+ if (multiple && !wc_unescape(unwcfname, origfname)) {
+ swcm = sftp_begin_wildcard_matching(origfname);
+ if (!swcm) {
+ sfree(unwcfname);
+ continue;
+ }
+ origwfname = sftp_wildcard_get_filename(swcm);
+ if (!origwfname) {
+ /* Politely warn the user that nothing matched. */
+ printf("%s: nothing matched\n", origfname);
+ sftp_finish_wildcard_matching(swcm);
+ sfree(unwcfname);
+ continue;
+ }
+ } else {
+ origwfname = origfname;
+ swcm = NULL;
+ }
+
+ while (origwfname) {
+ fname = canonify(origwfname);
+
+ if (!fname) {
+ printf("%s: canonify: %s\n", origwfname, fxp_error());
+ sfree(unwcfname);
+ return 0;
+ }
+
+ if (!multiple && i < cmd->nwords)
+ outfname = cmd->words[i++];
+ else
+ outfname = stripslashes(origwfname, 0);
+
+ ret = sftp_get_file(fname, outfname, recurse, restart);
+
+ sfree(fname);
+
+ if (swcm) {
+ sfree(origwfname);
+ origwfname = sftp_wildcard_get_filename(swcm);
+ } else {
+ origwfname = NULL;
+ }
+ }
+ sfree(unwcfname);
+ if (swcm)
+ sftp_finish_wildcard_matching(swcm);
+ if (!ret)
+ return ret;
+
+ } while (multiple && i < cmd->nwords);
+
+ return ret;
+}
+int sftp_cmd_get(struct sftp_command *cmd)
+{
+ return sftp_general_get(cmd, 0, 0);
+}
+int sftp_cmd_mget(struct sftp_command *cmd)
+{
+ return sftp_general_get(cmd, 0, 1);
+}
+int sftp_cmd_reget(struct sftp_command *cmd)
+{
+ return sftp_general_get(cmd, 1, 0);
+}
+
+/*
+ * Send a file and store it at the remote end. We have three very
+ * similar commands here. The basic one is `put'; `reput' differs
+ * in that it checks for the existence of the destination file and
+ * starts from where a previous aborted transfer left off; `mput'
+ * differs in that it interprets all its arguments as files to
+ * transfer (never as a different remote name for a local file) and
+ * can handle wildcards.
+ */
+int sftp_general_put(struct sftp_command *cmd, int restart, int multiple)
+{
+ char *fname, *wfname, *origoutfname, *outfname;
+ int i, ret;
+ int recurse = FALSE;
+
+ if (back == NULL) {
+ not_connected();
+ return 0;
+ }
+
+ i = 1;
+ while (i < cmd->nwords && cmd->words[i][0] == '-') {
+ if (!strcmp(cmd->words[i], "--")) {
+ /* finish processing options */
+ i++;
+ break;
+ } else if (!strcmp(cmd->words[i], "-r")) {
+ recurse = TRUE;
+ } else {
+ printf("%s: unrecognised option '%s'\n", cmd->words[0], cmd->words[i]);
+ return 0;
+ }
+ i++;
+ }
+
+ if (i >= cmd->nwords) {
+ printf("%s: expects a filename\n", cmd->words[0]);
+ return 0;
+ }
+
+ ret = 1;
+ do {
+ WildcardMatcher *wcm;
+ fname = cmd->words[i++];
+
+ if (multiple && test_wildcard(fname, FALSE) == WCTYPE_WILDCARD) {
+ wcm = begin_wildcard_matching(fname);
+ wfname = wildcard_get_filename(wcm);
+ if (!wfname) {
+ /* Politely warn the user that nothing matched. */
+ printf("%s: nothing matched\n", fname);
+ finish_wildcard_matching(wcm);
+ continue;
+ }
+ } else {
+ wfname = fname;
+ wcm = NULL;
+ }
+
+ while (wfname) {
+ if (!multiple && i < cmd->nwords)
+ origoutfname = cmd->words[i++];
+ else
+ origoutfname = stripslashes(wfname, 1);
+
+ outfname = canonify(origoutfname);
+ if (!outfname) {
+ printf("%s: canonify: %s\n", origoutfname, fxp_error());
+ if (wcm) {
+ sfree(wfname);
+ finish_wildcard_matching(wcm);
+ }
+ return 0;
+ }
+ ret = sftp_put_file(wfname, outfname, recurse, restart);
+ sfree(outfname);
+
+ if (wcm) {
+ sfree(wfname);
+ wfname = wildcard_get_filename(wcm);
+ } else {
+ wfname = NULL;
+ }
+ }
+
+ if (wcm)
+ finish_wildcard_matching(wcm);
+
+ if (!ret)
+ return ret;
+
+ } while (multiple && i < cmd->nwords);
+
+ return ret;
+}
+int sftp_cmd_put(struct sftp_command *cmd)
+{
+ return sftp_general_put(cmd, 0, 0);
+}
+int sftp_cmd_mput(struct sftp_command *cmd)
+{
+ return sftp_general_put(cmd, 0, 1);
+}
+int sftp_cmd_reput(struct sftp_command *cmd)
+{
+ return sftp_general_put(cmd, 1, 0);
+}
+
+int sftp_cmd_mkdir(struct sftp_command *cmd)
+{
+ char *dir;
+ struct sftp_packet *pktin;
+ struct sftp_request *req, *rreq;
+ int result;
+ int i, ret;
+
+ if (back == NULL) {
+ not_connected();
+ return 0;
+ }
+
+ if (cmd->nwords < 2) {
+ printf("mkdir: expects a directory\n");
+ return 0;
+ }
+
+ ret = 1;
+ for (i = 1; i < cmd->nwords; i++) {
+ dir = canonify(cmd->words[i]);
+ 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);
+
+ if (!result) {
+ printf("mkdir %s: %s\n", dir, fxp_error());
+ ret = 0;
+ } else
+ printf("mkdir %s: OK\n", dir);
+
+ sfree(dir);
+ }
+
+ return ret;
+}
+
+static int sftp_action_rmdir(void *vctx, char *dir)
+{
+ struct sftp_packet *pktin;
+ struct sftp_request *req, *rreq;
+ 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);
+
+ if (!result) {
+ printf("rmdir %s: %s\n", dir, fxp_error());
+ return 0;
+ }
+
+ printf("rmdir %s: OK\n", dir);
+
+ return 1;
+}
+
+int sftp_cmd_rmdir(struct sftp_command *cmd)
+{
+ int i, ret;
+
+ if (back == NULL) {
+ not_connected();
+ return 0;
+ }
+
+ if (cmd->nwords < 2) {
+ printf("rmdir: expects a directory\n");
+ return 0;
+ }
+
+ ret = 1;
+ for (i = 1; i < cmd->nwords; i++)
+ ret &= wildcard_iterate(cmd->words[i], sftp_action_rmdir, NULL);
+
+ return ret;
+}
+
+static int sftp_action_rm(void *vctx, char *fname)
+{
+ struct sftp_packet *pktin;
+ struct sftp_request *req, *rreq;
+ 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);
+
+ if (!result) {
+ printf("rm %s: %s\n", fname, fxp_error());
+ return 0;
+ }
+
+ printf("rm %s: OK\n", fname);
+
+ return 1;
+}
+
+int sftp_cmd_rm(struct sftp_command *cmd)
+{
+ int i, ret;
+
+ if (back == NULL) {
+ not_connected();
+ return 0;
+ }
+
+ if (cmd->nwords < 2) {
+ printf("rm: expects a filename\n");
+ return 0;
+ }
+
+ ret = 1;
+ for (i = 1; i < cmd->nwords; i++)
+ ret &= wildcard_iterate(cmd->words[i], sftp_action_rm, NULL);
+
+ return ret;
+}
+
+static int check_is_dir(char *dstfname)
+{
+ struct sftp_packet *pktin;
+ struct sftp_request *req, *rreq;
+ 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);
+
+ if (result &&
+ (attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS) &&
+ (attrs.permissions & 0040000))
+ return TRUE;
+ else
+ return FALSE;
+}
+
+struct sftp_context_mv {
+ char *dstfname;
+ int dest_is_dir;
+};
+
+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;
+ const char *error;
+ char *finalfname, *newcanon = NULL;
+ int ret, result;
+
+ if (ctx->dest_is_dir) {
+ char *p;
+ char *newname;
+
+ p = srcfname + strlen(srcfname);
+ while (p > srcfname && p[-1] != '/') p--;
+ newname = dupcat(ctx->dstfname, "/", p, NULL);
+ newcanon = canonify(newname);
+ if (!newcanon) {
+ printf("%s: canonify: %s\n", newname, fxp_error());
+ sfree(newname);
+ return 0;
+ }
+ sfree(newname);
+
+ 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);
+
+ error = result ? NULL : fxp_error();
+
+ if (error) {
+ printf("mv %s %s: %s\n", srcfname, finalfname, error);
+ ret = 0;
+ } else {
+ printf("%s -> %s\n", srcfname, finalfname);
+ ret = 1;
+ }
+
+ sfree(newcanon);
+ return ret;
+}
+
+int sftp_cmd_mv(struct sftp_command *cmd)
+{
+ struct sftp_context_mv actx, *ctx = &actx;
+ int i, ret;
+
+ if (back == NULL) {
+ not_connected();
+ return 0;
+ }
+
+ if (cmd->nwords < 3) {
+ printf("mv: expects two filenames\n");
+ return 0;
+ }
+
+ ctx->dstfname = canonify(cmd->words[cmd->nwords-1]);
+ if (!ctx->dstfname) {
+ printf("%s: canonify: %s\n", ctx->dstfname, fxp_error());
+ return 0;
+ }
+
+ /*
+ * If there's more than one source argument, or one source
+ * argument which is a wildcard, we _require_ that the
+ * destination is a directory.
+ */
+ ctx->dest_is_dir = check_is_dir(ctx->dstfname);
+ if ((cmd->nwords > 3 || is_wildcard(cmd->words[1])) && !ctx->dest_is_dir) {
+ printf("mv: multiple or wildcard arguments require the destination"
+ " to be a directory\n");
+ sfree(ctx->dstfname);
+ return 0;
+ }
+
+ /*
+ * Now iterate over the source arguments.
+ */
+ ret = 1;
+ for (i = 1; i < cmd->nwords-1; i++)
+ ret &= wildcard_iterate(cmd->words[i], sftp_action_mv, ctx);
+
+ sfree(ctx->dstfname);
+ return ret;
+}
+
+struct sftp_context_chmod {
+ unsigned attrs_clr, attrs_xor;
+};
+
+static int sftp_action_chmod(void *vctx, char *fname)
+{
+ struct fxp_attrs attrs;
+ struct sftp_packet *pktin;
+ struct sftp_request *req, *rreq;
+ 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);
+
+ 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;
+ }
+
+ attrs.flags = SSH_FILEXFER_ATTR_PERMISSIONS; /* perms _only_ */
+ oldperms = attrs.permissions & 07777;
+ attrs.permissions &= ~ctx->attrs_clr;
+ attrs.permissions ^= ctx->attrs_xor;
+ 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);
+
+ if (!result) {
+ printf("set attrs for %s: %s\n", fname, fxp_error());
+ return 0;
+ }
+
+ printf("%s: %04o -> %04o\n", fname, oldperms, newperms);
+
+ return 1;
+}
+
+int sftp_cmd_chmod(struct sftp_command *cmd)
+{
+ char *mode;
+ int i, ret;
+ struct sftp_context_chmod actx, *ctx = &actx;
+
+ if (back == NULL) {
+ not_connected();
+ return 0;
+ }
+
+ if (cmd->nwords < 3) {
+ printf("chmod: expects a mode specifier and a filename\n");
+ return 0;
+ }
+
+ /*
+ * Attempt to parse the mode specifier in cmd->words[1]. We
+ * don't support the full horror of Unix chmod; instead we
+ * support a much simpler syntax in which the user can either
+ * specify an octal number, or a comma-separated sequence of
+ * [ugoa]*[-+=][rwxst]+. (The initial [ugoa] sequence may
+ * _only_ be omitted if the only attribute mentioned is t,
+ * since all others require a user/group/other specification.
+ * Additionally, the s attribute may not be specified for any
+ * [ugoa] specifications other than exactly u or exactly g.
+ */
+ ctx->attrs_clr = ctx->attrs_xor = 0;
+ mode = cmd->words[1];
+ if (mode[0] >= '0' && mode[0] <= '9') {
+ if (mode[strspn(mode, "01234567")]) {
+ printf("chmod: numeric file modes should"
+ " contain digits 0-7 only\n");
+ return 0;
+ }
+ ctx->attrs_clr = 07777;
+ sscanf(mode, "%o", &ctx->attrs_xor);
+ ctx->attrs_xor &= ctx->attrs_clr;
+ } else {
+ while (*mode) {
+ char *modebegin = mode;
+ unsigned subset, perms;
+ int action;
+
+ subset = 0;
+ while (*mode && *mode != ',' &&
+ *mode != '+' && *mode != '-' && *mode != '=') {
+ switch (*mode) {
+ case 'u': subset |= 04700; break; /* setuid, user perms */
+ case 'g': subset |= 02070; break; /* setgid, group perms */
+ case 'o': subset |= 00007; break; /* just other perms */
+ case 'a': subset |= 06777; break; /* all of the above */
+ default:
+ printf("chmod: file mode '%.*s' contains unrecognised"
+ " user/group/other specifier '%c'\n",
+ (int)strcspn(modebegin, ","), modebegin, *mode);
+ return 0;
+ }
+ mode++;
+ }
+ if (!*mode || *mode == ',') {
+ printf("chmod: file mode '%.*s' is incomplete\n",
+ (int)strcspn(modebegin, ","), modebegin);
+ return 0;
+ }
+ action = *mode++;
+ if (!*mode || *mode == ',') {
+ printf("chmod: file mode '%.*s' is incomplete\n",
+ (int)strcspn(modebegin, ","), modebegin);
+ return 0;
+ }
+ perms = 0;
+ while (*mode && *mode != ',') {
+ switch (*mode) {
+ case 'r': perms |= 00444; break;
+ case 'w': perms |= 00222; break;
+ case 'x': perms |= 00111; break;
+ case 't': perms |= 01000; subset |= 01000; break;
+ case 's':
+ if ((subset & 06777) != 04700 &&
+ (subset & 06777) != 02070) {
+ printf("chmod: file mode '%.*s': set[ug]id bit should"
+ " be used with exactly one of u or g only\n",
+ (int)strcspn(modebegin, ","), modebegin);
+ return 0;
+ }
+ perms |= 06000;
+ break;
+ default:
+ printf("chmod: file mode '%.*s' contains unrecognised"
+ " permission specifier '%c'\n",
+ (int)strcspn(modebegin, ","), modebegin, *mode);
+ return 0;
+ }
+ mode++;
+ }
+ if (!(subset & 06777) && (perms &~ subset)) {
+ printf("chmod: file mode '%.*s' contains no user/group/other"
+ " specifier and permissions other than 't' \n",
+ (int)strcspn(modebegin, ","), modebegin);
+ return 0;
+ }
+ perms &= subset;
+ switch (action) {
+ case '+':
+ ctx->attrs_clr |= perms;
+ ctx->attrs_xor |= perms;
+ break;
+ case '-':
+ ctx->attrs_clr |= perms;
+ ctx->attrs_xor &= ~perms;
+ break;
+ case '=':
+ ctx->attrs_clr |= subset;
+ ctx->attrs_xor |= perms;
+ break;
+ }
+ if (*mode) mode++; /* eat comma */
+ }
+ }
+
+ ret = 1;
+ for (i = 2; i < cmd->nwords; i++)
+ ret &= wildcard_iterate(cmd->words[i], sftp_action_chmod, ctx);
+
+ return ret;
+}
+
+static int sftp_cmd_open(struct sftp_command *cmd)
+{
+ int portnumber;
+
+ if (back != NULL) {
+ printf("psftp: already connected\n");
+ return 0;
+ }
+
+ if (cmd->nwords < 2) {
+ printf("open: expects a host name\n");
+ return 0;
+ }
+
+ if (cmd->nwords > 2) {
+ portnumber = atoi(cmd->words[2]);
+ if (portnumber == 0) {
+ printf("open: invalid port number\n");
+ return 0;
+ }
+ } else
+ portnumber = 0;
+
+ if (psftp_connect(cmd->words[1], NULL, portnumber)) {
+ back = NULL; /* connection is already closed */
+ return -1; /* this is fatal */
+ }
+ do_sftp_init();
+ return 1;
+}
+
+static int sftp_cmd_lcd(struct sftp_command *cmd)
+{
+ char *currdir, *errmsg;
+
+ if (cmd->nwords < 2) {
+ printf("lcd: expects a local directory name\n");
+ return 0;
+ }
+
+ errmsg = psftp_lcd(cmd->words[1]);
+ if (errmsg) {
+ printf("lcd: unable to change directory: %s\n", errmsg);
+ sfree(errmsg);
+ return 0;
+ }
+
+ currdir = psftp_getcwd();
+ printf("New local directory is %s\n", currdir);
+ sfree(currdir);
+
+ return 1;
+}
+
+static int sftp_cmd_lpwd(struct sftp_command *cmd)
+{
+ char *currdir;
+
+ currdir = psftp_getcwd();
+ printf("Current local directory is %s\n", currdir);
+ sfree(currdir);
+
+ return 1;
+}
+
+static int sftp_cmd_pling(struct sftp_command *cmd)
+{
+ int exitcode;
+
+ exitcode = system(cmd->words[1]);
+ return (exitcode == 0);
+}
+
+static int sftp_cmd_help(struct sftp_command *cmd);
+
+static struct sftp_cmd_lookup {
+ char *name;
+ /*
+ * For help purposes, there are two kinds of command:
+ *
+ * - primary commands, in which `longhelp' is non-NULL. In
+ * this case `shorthelp' is descriptive text, and `longhelp'
+ * is longer descriptive text intended to be printed after
+ * the command name.
+ *
+ * - alias commands, in which `longhelp' is NULL. In this case
+ * `shorthelp' is the name of a primary command, which
+ * contains the help that should double up for this command.
+ */
+ int listed; /* do we list this in primary help? */
+ char *shorthelp;
+ char *longhelp;
+ int (*obey) (struct sftp_command *);
+} sftp_lookup[] = {
+ /*
+ * List of sftp commands. This is binary-searched so it MUST be
+ * in ASCII order.
+ */
+ {
+ "!", TRUE, "run a local command",
+ "<command>\n"
+ /* FIXME: this example is crap for non-Windows. */
+ " Runs a local command. For example, \"!del myfile\".\n",
+ sftp_cmd_pling
+ },
+ {
+ "bye", TRUE, "finish your SFTP session",
+ "\n"
+ " Terminates your SFTP session and quits the PSFTP program.\n",
+ sftp_cmd_quit
+ },
+ {
+ "cd", TRUE, "change your remote working directory",
+ " [ <new working directory> ]\n"
+ " Change the remote working directory for your SFTP session.\n"
+ " If a new working directory is not supplied, you will be\n"
+ " returned to your home directory.\n",
+ sftp_cmd_cd
+ },
+ {
+ "chmod", TRUE, "change file permissions and modes",
+ " <modes> <filename-or-wildcard> [ <filename-or-wildcard>... ]\n"
+ " Change the file permissions on one or more remote files or\n"
+ " directories.\n"
+ " <modes> can be any octal Unix permission specifier.\n"
+ " Alternatively, <modes> can include the following modifiers:\n"
+ " u+r make file readable by owning user\n"
+ " u+w make file writable by owning user\n"
+ " u+x make file executable by owning user\n"
+ " u-r make file not readable by owning user\n"
+ " [also u-w, u-x]\n"
+ " g+r make file readable by members of owning group\n"
+ " [also g+w, g+x, g-r, g-w, g-x]\n"
+ " o+r make file readable by all other users\n"
+ " [also o+w, o+x, o-r, o-w, o-x]\n"
+ " a+r make file readable by absolutely everybody\n"
+ " [also a+w, a+x, a-r, a-w, a-x]\n"
+ " u+s enable the Unix set-user-ID bit\n"
+ " u-s disable the Unix set-user-ID bit\n"
+ " g+s enable the Unix set-group-ID bit\n"
+ " g-s disable the Unix set-group-ID bit\n"
+ " +t enable the Unix \"sticky bit\"\n"
+ " You can give more than one modifier for the same user (\"g-rwx\"), and\n"
+ " more than one user for the same modifier (\"ug+w\"). You can\n"
+ " use commas to separate different modifiers (\"u+rwx,g+s\").\n",
+ sftp_cmd_chmod
+ },
+ {
+ "close", TRUE, "finish your SFTP session but do not quit PSFTP",
+ "\n"
+ " Terminates your SFTP session, but does not quit the PSFTP\n"
+ " program. You can then use \"open\" to start another SFTP\n"
+ " session, to the same server or to a different one.\n",
+ sftp_cmd_close
+ },
+ {
+ "del", TRUE, "delete files on the remote server",
+ " <filename-or-wildcard> [ <filename-or-wildcard>... ]\n"
+ " Delete a file or files from the server.\n",
+ sftp_cmd_rm
+ },
+ {
+ "delete", FALSE, "del", NULL, sftp_cmd_rm
+ },
+ {
+ "dir", TRUE, "list remote files",
+ " [ <directory-name> ]/[ <wildcard> ]\n"
+ " List the contents of a specified directory on the server.\n"
+ " If <directory-name> is not given, the current working directory\n"
+ " is assumed.\n"
+ " If <wildcard> is given, it is treated as a set of files to\n"
+ " list; otherwise, all files are listed.\n",
+ sftp_cmd_ls
+ },
+ {
+ "exit", TRUE, "bye", NULL, sftp_cmd_quit
+ },
+ {
+ "get", TRUE, "download a file from the server to your local machine",
+ " [ -r ] [ -- ] <filename> [ <local-filename> ]\n"
+ " Downloads a file on the server and stores it locally under\n"
+ " the same name, or under a different one if you supply the\n"
+ " argument <local-filename>.\n"
+ " If -r specified, recursively fetch a directory.\n",
+ sftp_cmd_get
+ },
+ {
+ "help", TRUE, "give help",
+ " [ <command> [ <command> ... ] ]\n"
+ " Give general help if no commands are specified.\n"
+ " If one or more commands are specified, give specific help on\n"
+ " those particular commands.\n",
+ sftp_cmd_help
+ },
+ {
+ "lcd", TRUE, "change local working directory",
+ " <local-directory-name>\n"
+ " Change the local working directory of the PSFTP program (the\n"
+ " default location where the \"get\" command will save files).\n",
+ sftp_cmd_lcd
+ },
+ {
+ "lpwd", TRUE, "print local working directory",
+ "\n"
+ " Print the local working directory of the PSFTP program (the\n"
+ " default location where the \"get\" command will save files).\n",
+ sftp_cmd_lpwd
+ },
+ {
+ "ls", TRUE, "dir", NULL,
+ sftp_cmd_ls
+ },
+ {
+ "mget", TRUE, "download multiple files at once",
+ " [ -r ] [ -- ] <filename-or-wildcard> [ <filename-or-wildcard>... ]\n"
+ " Downloads many files from the server, storing each one under\n"
+ " the same name it has on the server side. You can use wildcards\n"
+ " such as \"*.c\" to specify lots of files at once.\n"
+ " If -r specified, recursively fetch files and directories.\n",
+ sftp_cmd_mget
+ },
+ {
+ "mkdir", TRUE, "create directories on the remote server",
+ " <directory-name> [ <directory-name>... ]\n"
+ " Creates directories with the given names on the server.\n",
+ sftp_cmd_mkdir
+ },
+ {
+ "mput", TRUE, "upload multiple files at once",
+ " [ -r ] [ -- ] <filename-or-wildcard> [ <filename-or-wildcard>... ]\n"
+ " Uploads many files to the server, storing each one under the\n"
+ " same name it has on the client side. You can use wildcards\n"
+ " such as \"*.c\" to specify lots of files at once.\n"
+ " If -r specified, recursively store files and directories.\n",
+ sftp_cmd_mput
+ },
+ {
+ "mv", TRUE, "move or rename file(s) on the remote server",
+ " <source> [ <source>... ] <destination>\n"
+ " Moves or renames <source>(s) on the server to <destination>,\n"
+ " also on the server.\n"
+ " If <destination> specifies an existing directory, then <source>\n"
+ " may be a wildcard, and multiple <source>s may be given; all\n"
+ " source files are moved into <destination>.\n"
+ " Otherwise, <source> must specify a single file, which is moved\n"
+ " or renamed so that it is accessible under the name <destination>.\n",
+ sftp_cmd_mv
+ },
+ {
+ "open", TRUE, "connect to a host",
+ " [<user>@]<hostname> [<port>]\n"
+ " Establishes an SFTP connection to a given host. Only usable\n"
+ " when you are not already connected to a server.\n",
+ sftp_cmd_open
+ },
+ {
+ "put", TRUE, "upload a file from your local machine to the server",
+ " [ -r ] [ -- ] <filename> [ <remote-filename> ]\n"
+ " Uploads a file to the server and stores it there under\n"
+ " the same name, or under a different one if you supply the\n"
+ " argument <remote-filename>.\n"
+ " If -r specified, recursively store a directory.\n",
+ sftp_cmd_put
+ },
+ {
+ "pwd", TRUE, "print your remote working directory",
+ "\n"
+ " Print the current remote working directory for your SFTP session.\n",
+ sftp_cmd_pwd
+ },
+ {
+ "quit", TRUE, "bye", NULL,
+ sftp_cmd_quit
+ },
+ {
+ "reget", TRUE, "continue downloading files",
+ " [ -r ] [ -- ] <filename> [ <local-filename> ]\n"
+ " Works exactly like the \"get\" command, but the local file\n"
+ " must already exist. The download will begin at the end of the\n"
+ " file. This is for resuming a download that was interrupted.\n"
+ " If -r specified, resume interrupted \"get -r\".\n",
+ sftp_cmd_reget
+ },
+ {
+ "ren", TRUE, "mv", NULL,
+ sftp_cmd_mv
+ },
+ {
+ "rename", FALSE, "mv", NULL,
+ sftp_cmd_mv
+ },
+ {
+ "reput", TRUE, "continue uploading files",
+ " [ -r ] [ -- ] <filename> [ <remote-filename> ]\n"
+ " Works exactly like the \"put\" command, but the remote file\n"
+ " must already exist. The upload will begin at the end of the\n"
+ " file. This is for resuming an upload that was interrupted.\n"
+ " If -r specified, resume interrupted \"put -r\".\n",
+ sftp_cmd_reput
+ },
+ {
+ "rm", TRUE, "del", NULL,
+ sftp_cmd_rm
+ },
+ {
+ "rmdir", TRUE, "remove directories on the remote server",
+ " <directory-name> [ <directory-name>... ]\n"
+ " Removes the directory with the given name on the server.\n"
+ " The directory will not be removed unless it is empty.\n"
+ " Wildcards may be used to specify multiple directories.\n",
+ sftp_cmd_rmdir
+ }
+};
+
+const struct sftp_cmd_lookup *lookup_command(char *name)
+{
+ int i, j, k, cmp;
+
+ i = -1;
+ j = sizeof(sftp_lookup) / sizeof(*sftp_lookup);
+ while (j - i > 1) {
+ k = (j + i) / 2;
+ cmp = strcmp(name, sftp_lookup[k].name);
+ if (cmp < 0)
+ j = k;
+ else if (cmp > 0)
+ i = k;
+ else {
+ return &sftp_lookup[k];
+ }
+ }
+ return NULL;
+}
+
+static int sftp_cmd_help(struct sftp_command *cmd)
+{
+ int i;
+ if (cmd->nwords == 1) {
+ /*
+ * Give short help on each command.
+ */
+ int maxlen;
+ maxlen = 0;
+ for (i = 0; i < sizeof(sftp_lookup) / sizeof(*sftp_lookup); i++) {
+ int len;
+ if (!sftp_lookup[i].listed)
+ continue;
+ len = strlen(sftp_lookup[i].name);
+ if (maxlen < len)
+ maxlen = len;
+ }
+ for (i = 0; i < sizeof(sftp_lookup) / sizeof(*sftp_lookup); i++) {
+ const struct sftp_cmd_lookup *lookup;
+ if (!sftp_lookup[i].listed)
+ continue;
+ lookup = &sftp_lookup[i];
+ printf("%-*s", maxlen+2, lookup->name);
+ if (lookup->longhelp == NULL)
+ lookup = lookup_command(lookup->shorthelp);
+ printf("%s\n", lookup->shorthelp);
+ }
+ } else {
+ /*
+ * Give long help on specific commands.
+ */
+ for (i = 1; i < cmd->nwords; i++) {
+ const struct sftp_cmd_lookup *lookup;
+ lookup = lookup_command(cmd->words[i]);
+ if (!lookup) {
+ printf("help: %s: command not found\n", cmd->words[i]);
+ } else {
+ printf("%s", lookup->name);
+ if (lookup->longhelp == NULL)
+ lookup = lookup_command(lookup->shorthelp);
+ printf("%s", lookup->longhelp);
+ }
+ }
+ }
+ return 1;
+}
+
+/* ----------------------------------------------------------------------
+ * Command line reading and parsing.
+ */
+struct sftp_command *sftp_getcmd(FILE *fp, int mode, int modeflags)
+{
+ char *line;
+ struct sftp_command *cmd;
+ char *p, *q, *r;
+ int quoting;
+
+ cmd = snew(struct sftp_command);
+ cmd->words = NULL;
+ cmd->nwords = 0;
+ cmd->wordssize = 0;
+
+ line = NULL;
+
+ if (fp) {
+ if (modeflags & 1)
+ printf("psftp> ");
+ line = fgetline(fp);
+ } else {
+ line = ssh_sftp_get_cmdline("psftp> ", back == NULL);
+ }
+
+ if (!line || !*line) {
+ cmd->obey = sftp_cmd_quit;
+ if ((mode == 0) || (modeflags & 1))
+ printf("quit\n");
+ return cmd; /* eof */
+ }
+
+ line[strcspn(line, "\r\n")] = '\0';
+
+ if (modeflags & 1) {
+ printf("%s\n", line);
+ }
+
+ p = line;
+ while (*p && (*p == ' ' || *p == '\t'))
+ p++;
+
+ if (*p == '!') {
+ /*
+ * Special case: the ! command. This is always parsed as
+ * exactly two words: one containing the !, and the second
+ * containing everything else on the line.
+ */
+ cmd->nwords = cmd->wordssize = 2;
+ cmd->words = sresize(cmd->words, cmd->wordssize, char *);
+ cmd->words[0] = dupstr("!");
+ cmd->words[1] = dupstr(p+1);
+ } else if (*p == '#') {
+ /*
+ * Special case: comment. Entire line is ignored.
+ */
+ cmd->nwords = cmd->wordssize = 0;
+ } else {
+
+ /*
+ * Parse the command line into words. The syntax is:
+ * - double quotes are removed, but cause spaces within to be
+ * treated as non-separating.
+ * - a double-doublequote pair is a literal double quote, inside
+ * _or_ outside quotes. Like this:
+ *
+ * firstword "second word" "this has ""quotes"" in" and""this""
+ *
+ * becomes
+ *
+ * >firstword<
+ * >second word<
+ * >this has "quotes" in<
+ * >and"this"<
+ */
+ while (*p) {
+ /* skip whitespace */
+ while (*p && (*p == ' ' || *p == '\t'))
+ p++;
+ /* mark start of word */
+ q = r = p; /* q sits at start, r writes word */
+ quoting = 0;
+ while (*p) {
+ if (!quoting && (*p == ' ' || *p == '\t'))
+ break; /* reached end of word */
+ else if (*p == '"' && p[1] == '"')
+ p += 2, *r++ = '"'; /* a literal quote */
+ else if (*p == '"')
+ p++, quoting = !quoting;
+ else
+ *r++ = *p++;
+ }
+ if (*p)
+ p++; /* skip over the whitespace */
+ *r = '\0';
+ if (cmd->nwords >= cmd->wordssize) {
+ cmd->wordssize = cmd->nwords + 16;
+ cmd->words = sresize(cmd->words, cmd->wordssize, char *);
+ }
+ cmd->words[cmd->nwords++] = dupstr(q);
+ }
+ }
+
+ sfree(line);
+
+ /*
+ * Now parse the first word and assign a function.
+ */
+
+ if (cmd->nwords == 0)
+ cmd->obey = sftp_cmd_null;
+ else {
+ const struct sftp_cmd_lookup *lookup;
+ lookup = lookup_command(cmd->words[0]);
+ if (!lookup)
+ cmd->obey = sftp_cmd_unknown;
+ else
+ cmd->obey = lookup->obey;
+ }
+
+ return cmd;
+}
+
+static int do_sftp_init(void)
+{
+ struct sftp_packet *pktin;
+ struct sftp_request *req, *rreq;
+
+ /*
+ * Do protocol initialisation.
+ */
+ if (!fxp_init()) {
+ fprintf(stderr,
+ "Fatal: unable to initialise SFTP: %s\n", fxp_error());
+ return 1; /* failure */
+ }
+
+ /*
+ * 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);
+
+ if (!homedir) {
+ fprintf(stderr,
+ "Warning: failed to resolve home directory: %s\n",
+ fxp_error());
+ homedir = dupstr(".");
+ } else {
+ printf("Remote working directory is %s\n", homedir);
+ }
+ pwd = dupstr(homedir);
+ return 0;
+}
+
+void do_sftp_cleanup()
+{
+ char ch;
+ if (back) {
+ back->special(backhandle, TS_EOF);
+ sftp_recvdata(&ch, 1);
+ back->free(backhandle);
+ sftp_cleanup_request();
+ back = NULL;
+ backhandle = NULL;
+ }
+ if (pwd) {
+ sfree(pwd);
+ pwd = NULL;
+ }
+ if (homedir) {
+ sfree(homedir);
+ homedir = NULL;
+ }
+}
+
+void do_sftp(int mode, int modeflags, char *batchfile)
+{
+ FILE *fp;
+ int ret;
+
+ /*
+ * Batch mode?
+ */
+ if (mode == 0) {
+
+ /* ------------------------------------------------------------------
+ * Now we're ready to do Real Stuff.
+ */
+ while (1) {
+ struct sftp_command *cmd;
+ cmd = sftp_getcmd(NULL, 0, 0);
+ if (!cmd)
+ break;
+ ret = cmd->obey(cmd);
+ if (cmd->words) {
+ int i;
+ for(i = 0; i < cmd->nwords; i++)
+ sfree(cmd->words[i]);
+ sfree(cmd->words);
+ }
+ sfree(cmd);
+ if (ret < 0)
+ break;
+ }
+ } else {
+ fp = fopen(batchfile, "r");
+ if (!fp) {
+ printf("Fatal: unable to open %s\n", batchfile);
+ return;
+ }
+ while (1) {
+ struct sftp_command *cmd;
+ cmd = sftp_getcmd(fp, mode, modeflags);
+ if (!cmd)
+ break;
+ ret = cmd->obey(cmd);
+ if (ret < 0)
+ break;
+ if (ret == 0) {
+ if (!(modeflags & 2))
+ break;
+ }
+ }
+ fclose(fp);
+
+ }
+}
+
+/* ----------------------------------------------------------------------
+ * Dirty bits: integration with PuTTY.
+ */
+
+static int verbose = 0;
+
+/*
+ * Print an error message and perform a fatal exit.
+ */
+void fatalbox(char *fmt, ...)
+{
+ char *str, *str2;
+ va_list ap;
+ va_start(ap, fmt);
+ str = dupvprintf(fmt, ap);
+ str2 = dupcat("Fatal: ", str, "\n", NULL);
+ sfree(str);
+ va_end(ap);
+ fputs(str2, stderr);
+ sfree(str2);
+
+ cleanup_exit(1);
+}
+void modalfatalbox(char *fmt, ...)
+{
+ char *str, *str2;
+ va_list ap;
+ va_start(ap, fmt);
+ str = dupvprintf(fmt, ap);
+ str2 = dupcat("Fatal: ", str, "\n", NULL);
+ sfree(str);
+ va_end(ap);
+ fputs(str2, stderr);
+ sfree(str2);
+
+ cleanup_exit(1);
+}
+void connection_fatal(void *frontend, char *fmt, ...)
+{
+ char *str, *str2;
+ va_list ap;
+ va_start(ap, fmt);
+ str = dupvprintf(fmt, ap);
+ str2 = dupcat("Fatal: ", str, "\n", NULL);
+ sfree(str);
+ va_end(ap);
+ fputs(str2, stderr);
+ sfree(str2);
+
+ cleanup_exit(1);
+}
+
+void ldisc_send(void *handle, char *buf, int len, int interactive)
+{
+ /*
+ * This is only here because of the calls to ldisc_send(NULL,
+ * 0) in ssh.c. Nothing in PSFTP actually needs to use the
+ * ldisc as an ldisc. So if we get called with any real data, I
+ * want to know about it.
+ */
+ assert(len == 0);
+}
+
+/*
+ * In psftp, all agent requests should be synchronous, so this is a
+ * never-called stub.
+ */
+void agent_schedule_callback(void (*callback)(void *, void *, int),
+ void *callback_ctx, void *data, int len)
+{
+ assert(!"We shouldn't be here");
+}
+
+/*
+ * Receive a block of data from the SSH link. Block until all data
+ * is available.
+ *
+ * To do this, we repeatedly call the SSH protocol module, with our
+ * own trap in from_backend() to catch the data that comes back. We
+ * do this until we have enough data.
+ */
+
+static unsigned char *outptr; /* where to put the data */
+static unsigned outlen; /* how much data required */
+static unsigned char *pending = NULL; /* any spare data */
+static unsigned pendlen = 0, pendsize = 0; /* length and phys. size of buffer */
+int from_backend(void *frontend, int is_stderr, const char *data, int datalen)
+{
+ unsigned char *p = (unsigned char *) data;
+ unsigned len = (unsigned) datalen;
+
+ /*
+ * stderr data is just spouted to local stderr and otherwise
+ * ignored.
+ */
+ if (is_stderr) {
+ if (len > 0)
+ if (fwrite(data, 1, len, stderr) < len)
+ /* oh well */;
+ return 0;
+ }
+
+ /*
+ * If this is before the real session begins, just return.
+ */
+ if (!outptr)
+ return 0;
+
+ if ((outlen > 0) && (len > 0)) {
+ unsigned used = outlen;
+ if (used > len)
+ used = len;
+ memcpy(outptr, p, used);
+ outptr += used;
+ outlen -= used;
+ p += used;
+ len -= used;
+ }
+
+ if (len > 0) {
+ if (pendsize < pendlen + len) {
+ pendsize = pendlen + len + 4096;
+ pending = sresize(pending, pendsize, unsigned char);
+ }
+ memcpy(pending + pendlen, p, len);
+ pendlen += len;
+ }
+
+ return 0;
+}
+int from_backend_untrusted(void *frontend_handle, const char *data, int len)
+{
+ /*
+ * 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 sftp_recvdata(char *buf, int len)
+{
+ outptr = (unsigned char *) buf;
+ outlen = len;
+
+ /*
+ * See if the pending-input block contains some of what we
+ * need.
+ */
+ if (pendlen > 0) {
+ unsigned pendused = pendlen;
+ if (pendused > outlen)
+ pendused = outlen;
+ memcpy(outptr, pending, pendused);
+ memmove(pending, pending + pendused, pendlen - pendused);
+ outptr += pendused;
+ outlen -= pendused;
+ pendlen -= pendused;
+ if (pendlen == 0) {
+ pendsize = 0;
+ sfree(pending);
+ pending = NULL;
+ }
+ if (outlen == 0)
+ return 1;
+ }
+
+ while (outlen > 0) {
+ if (back->exitcode(backhandle) >= 0 || ssh_sftp_loop_iteration() < 0)
+ return 0; /* doom */
+ }
+
+ return 1;
+}
+int sftp_senddata(char *buf, int len)
+{
+ back->send(backhandle, buf, len);
+ return 1;
+}
+
+/*
+ * Short description of parameters.
+ */
+static void usage(void)
+{
+ printf("PuTTY Secure File Transfer (SFTP) client\n");
+ printf("%s\n", ver);
+ printf("Usage: psftp [options] [user@]host\n");
+ printf("Options:\n");
+ printf(" -V print version information and exit\n");
+ printf(" -pgpfp print PGP key fingerprints and exit\n");
+ printf(" -b file use specified batchfile\n");
+ printf(" -bc output batchfile commands\n");
+ printf(" -be don't stop batchfile processing if errors\n");
+ printf(" -v show verbose messages\n");
+ printf(" -load sessname Load settings from saved session\n");
+ printf(" -l user connect with specified username\n");
+ printf(" -P port connect to specified port\n");
+ printf(" -pw passw login with specified password\n");
+ printf(" -1 -2 force use of particular SSH protocol version\n");
+ printf(" -4 -6 force use of IPv4 or IPv6\n");
+ printf(" -C enable compression\n");
+ printf(" -i key private key file for authentication\n");
+ printf(" -noagent disable use of Pageant\n");
+ printf(" -agent enable use of Pageant\n");
+ printf(" -batch disable all interactive prompts\n");
+ cleanup_exit(1);
+}
+
+static void version(void)
+{
+ printf("psftp: %s\n", ver);
+ cleanup_exit(1);
+}
+
+/*
+ * Connect to a host.
+ */
+static int psftp_connect(char *userhost, char *user, int portnumber)
+{
+ char *host, *realhost;
+ const char *err;
+ void *logctx;
+
+ /* Separate host and username */
+ host = userhost;
+ host = strrchr(host, '@');
+ if (host == NULL) {
+ host = userhost;
+ } else {
+ *host++ = '\0';
+ if (user) {
+ printf("psftp: multiple usernames specified; using \"%s\"\n",
+ user);
+ } else
+ user = userhost;
+ }
+
+ /*
+ * 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') {
+ /* Settings present and include hostname */
+ /* Re-load data into the real config. */
+ do_defaults(host, &cfg);
+ } 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';
+ }
+ } else {
+ /* Patch in hostname `host' to session details. */
+ strncpy(cfg.host, host, sizeof(cfg.host) - 1);
+ cfg.host[sizeof(cfg.host) - 1] = '\0';
+ }
+
+ /*
+ * 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 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;
+
+ /*
+ * 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';
+ }
+
+ /* Set username */
+ if (user != NULL && user[0] != '\0') {
+ strncpy(cfg.username, user, sizeof(cfg.username) - 1);
+ cfg.username[sizeof(cfg.username) - 1] = '\0';
+ }
+
+ if (portnumber)
+ cfg.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;
+
+ /* Set up subsystem name. */
+ strcpy(cfg.remote_cmd, "sftp");
+ cfg.ssh_subsys = TRUE;
+ cfg.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
+ * systems, and we use a kludgy piece of shellery which should
+ * try to find sftp-server in various places (the obvious
+ * systemwide spots /usr/lib and /usr/local/lib, and then the
+ * user's PATH) and finally give up.
+ *
+ * test -x /usr/lib/sftp-server && exec /usr/lib/sftp-server
+ * test -x /usr/local/lib/sftp-server && exec /usr/local/lib/sftp-server
+ * exec sftp-server
+ *
+ * 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;
+
+ back = &ssh_backend;
+
+ err = back->init(NULL, &backhandle, &cfg, cfg.host, cfg.port, &realhost,
+ 0, cfg.tcp_keepalives);
+ if (err != NULL) {
+ fprintf(stderr, "ssh_init: %s\n", err);
+ return 1;
+ }
+ logctx = log_init(NULL, &cfg);
+ back->provide_logctx(backhandle, logctx);
+ console_provide_logctx(logctx);
+ while (!back->sendok(backhandle)) {
+ if (back->exitcode(backhandle) >= 0)
+ return 1;
+ if (ssh_sftp_loop_iteration() < 0) {
+ fprintf(stderr, "ssh_init: error during SSH connection setup\n");
+ return 1;
+ }
+ }
+ if (verbose && realhost != NULL)
+ printf("Connected to %s\n", realhost);
+ if (realhost != NULL)
+ sfree(realhost);
+ return 0;
+}
+
+void cmdline_error(char *p, ...)
+{
+ va_list ap;
+ fprintf(stderr, "psftp: ");
+ va_start(ap, p);
+ vfprintf(stderr, p, ap);
+ va_end(ap);
+ fprintf(stderr, "\n try typing \"psftp -h\" for help\n");
+ exit(1);
+}
+
+/*
+ * Main program. Parse arguments etc.
+ */
+int psftp_main(int argc, char *argv[])
+{
+ int i;
+ int portnumber = 0;
+ char *userhost, *user;
+ int mode = 0;
+ int modeflags = 0;
+ char *batchfile = NULL;
+
+ flags = FLAG_STDERR | FLAG_INTERACTIVE
+#ifdef FLAG_SYNCAGENT
+ | FLAG_SYNCAGENT
+#endif
+ ;
+ cmdline_tooltype = TOOLTYPE_FILETRANSFER;
+ sk_init();
+
+ userhost = user = NULL;
+
+ /* Load Default Settings before doing anything else. */
+ do_defaults(NULL, &cfg);
+ loaded_session = FALSE;
+
+ for (i = 1; i < argc; i++) {
+ int ret;
+ if (argv[i][0] != '-') {
+ if (userhost)
+ usage();
+ else
+ userhost = dupstr(argv[i]);
+ continue;
+ }
+ ret = cmdline_process_param(argv[i], i+1<argc?argv[i+1]:NULL, 1, &cfg);
+ if (ret == -2) {
+ cmdline_error("option \"%s\" requires an argument", argv[i]);
+ } else if (ret == 2) {
+ i++; /* skip next argument */
+ } else if (ret == 1) {
+ /* We have our own verbosity in addition to `flags'. */
+ if (flags & FLAG_VERBOSE)
+ verbose = 1;
+ } else if (strcmp(argv[i], "-h") == 0 ||
+ strcmp(argv[i], "-?") == 0) {
+ usage();
+ } else if (strcmp(argv[i], "-pgpfp") == 0) {
+ pgp_fingerprints();
+ return 1;
+ } else if (strcmp(argv[i], "-V") == 0) {
+ version();
+ } else if (strcmp(argv[i], "-batch") == 0) {
+ console_batch_mode = 1;
+ } else if (strcmp(argv[i], "-b") == 0 && i + 1 < argc) {
+ mode = 1;
+ batchfile = argv[++i];
+ } else if (strcmp(argv[i], "-bc") == 0) {
+ modeflags = modeflags | 1;
+ } else if (strcmp(argv[i], "-be") == 0) {
+ modeflags = modeflags | 2;
+ } else if (strcmp(argv[i], "--") == 0) {
+ i++;
+ break;
+ } else {
+ cmdline_error("unknown option \"%s\"", argv[i]);
+ }
+ }
+ argc -= i;
+ argv += i;
+ back = NULL;
+
+ /*
+ * If the loaded session provides a hostname, and a hostname has not
+ * otherwise been specified, pop it in `userhost' so that
+ * `psftp -load sessname' is sufficient to start a session.
+ */
+ if (!userhost && cfg.host[0] != '\0') {
+ userhost = dupstr(cfg.host);
+ }
+
+ /*
+ * If a user@host string has already been provided, connect to
+ * it now.
+ */
+ if (userhost) {
+ int ret;
+ ret = psftp_connect(userhost, user, portnumber);
+ sfree(userhost);
+ if (ret)
+ return 1;
+ if (do_sftp_init())
+ return 1;
+ } else {
+ printf("psftp: no hostname specified; use \"open host.name\""
+ " to connect\n");
+ }
+
+ do_sftp(mode, modeflags, batchfile);
+
+ if (back != NULL && back->connected(backhandle)) {
+ char ch;
+ back->special(backhandle, TS_EOF);
+ sftp_recvdata(&ch, 1);
+ }
+ do_sftp_cleanup();
+ random_save_seed();
+ cmdline_cleanup();
+ console_provide_logctx(NULL);
+ sk_cleanup();
+
+ return 0;
+}
--- /dev/null
+/*
+ * psftp.h: interface between psftp.c / scp.c and each
+ * platform-specific SFTP module.
+ */
+
+#include "int64.h"
+
+#ifndef PUTTY_PSFTP_H
+#define PUTTY_PSFTP_H
+
+/*
+ * psftp_getcwd returns the local current directory. The returned
+ * string must be freed by the caller.
+ */
+char *psftp_getcwd(void);
+
+/*
+ * psftp_lcd changes the local current directory. The return value
+ * is NULL on success, or else an error message which must be freed
+ * by the caller.
+ */
+char *psftp_lcd(char *newdir);
+
+/*
+ * Retrieve file times on a local file. Must return two unsigned
+ * longs in POSIX time_t format.
+ */
+void get_file_times(char *filename, unsigned long *mtime,
+ unsigned long *atime);
+
+/*
+ * One iteration of the PSFTP event loop: wait for network data and
+ * process it, once.
+ */
+int ssh_sftp_loop_iteration(void);
+
+/*
+ * Read a command line for PSFTP from standard input. Caller must
+ * free.
+ *
+ * If `backend_required' is TRUE, should also listen for activity
+ * at the backend (rekeys, clientalives, unexpected closures etc)
+ * and respond as necessary, and if the backend closes it should
+ * treat this as a failure condition. If `backend_required' is
+ * FALSE, a back end is not (intentionally) active at all (e.g.
+ * psftp before an `open' command).
+ */
+char *ssh_sftp_get_cmdline(char *prompt, int backend_required);
+
+/*
+ * The main program in psftp.c. Called from main() in the platform-
+ * specific code, after doing any platform-specific initialisation.
+ */
+int psftp_main(int argc, char *argv[]);
+
+/*
+ * These functions are used by PSCP to transmit progress updates
+ * and error information to a GUI window managing it. This will
+ * probably only ever be supported on Windows, so these functions
+ * can safely be stubs on all other platforms.
+ */
+void gui_update_stats(char *name, unsigned long size,
+ int percentage, unsigned long elapsed,
+ unsigned long done, unsigned long eta,
+ unsigned long ratebs);
+void gui_send_errcount(int list, int errs);
+void gui_send_char(int is_stderr, int c);
+void gui_enable(char *arg);
+
+/*
+ * It's likely that a given platform's implementation of file
+ * transfer utilities is going to want to do things with them that
+ * aren't present in stdio. Hence we supply an alternative
+ * abstraction for file access functions.
+ *
+ * This abstraction tells you the size and access times when you
+ * open an existing file (platforms may choose the meaning of the
+ * file times if it's not clear; whatever they choose will be what
+ * PSCP sends to the server as mtime and atime), and lets you set
+ * the times when saving a new file.
+ *
+ * On the other hand, the abstraction is pretty simple: it supports
+ * 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 */
+RFile *open_existing_file(char *name, uint64 *size,
+ unsigned long *mtime, unsigned long *atime);
+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);
+/* 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);
+/* Seek offset bytes through file */
+enum { FROM_START, FROM_CURRENT, FROM_END };
+int seek_file(WFile *f, uint64 offset, int whence);
+/* Get file position */
+uint64 get_file_posn(WFile *f);
+/*
+ * Determine the type of a file: nonexistent, file, directory or
+ * weird. `weird' covers anything else - named pipes, Unix sockets,
+ * device files, fish, badgers, you name it. Things marked `weird'
+ * will be skipped over in recursive file transfers, so the only
+ * real reason for not lumping them in with `nonexistent' is that
+ * it allows a slightly more sane error message.
+ */
+enum {
+ FILE_TYPE_NONEXISTENT, FILE_TYPE_FILE, FILE_TYPE_DIRECTORY, FILE_TYPE_WEIRD
+};
+int file_type(char *name);
+
+/*
+ * Read all the file names out of a directory.
+ */
+typedef struct DirHandle DirHandle;
+DirHandle *open_directory(char *name);
+/* The string returned from this will need freeing if not NULL */
+char *read_filename(DirHandle *dir);
+void close_directory(DirHandle *dir);
+
+/*
+ * Test a filespec to see whether it's a local wildcard or not.
+ * Return values:
+ *
+ * - WCTYPE_WILDCARD (this is a wildcard).
+ * - WCTYPE_FILENAME (this is a single file name).
+ * - WCTYPE_NONEXISTENT (whichever it was, nothing of that name exists).
+ *
+ * Some platforms may choose not to support local wildcards when
+ * they come from the command line; in this case they simply never
+ * return WCTYPE_WILDCARD, but still test the file's existence.
+ * (However, all platforms will probably want to support wildcards
+ * inside the PSFTP CLI.)
+ */
+enum {
+ WCTYPE_NONEXISTENT, WCTYPE_FILENAME, WCTYPE_WILDCARD
+};
+int test_wildcard(char *name, int cmdline);
+
+/*
+ * Actually return matching file names for a local wildcard.
+ */
+typedef struct WildcardMatcher WildcardMatcher;
+WildcardMatcher *begin_wildcard_matching(char *name);
+/* The string returned from this will need freeing if not NULL */
+char *wildcard_get_filename(WildcardMatcher *dir);
+void finish_wildcard_matching(WildcardMatcher *dir);
+
+/*
+ * Vet a filename returned from the remote host, to ensure it isn't
+ * in some way malicious. The idea is that this function is applied
+ * to filenames returned from FXP_READDIR, which means we can panic
+ * if we see _anything_ resembling a directory separator.
+ *
+ * Returns TRUE if the filename is kosher, FALSE if dangerous.
+ */
+int vet_filename(char *name);
+
+/*
+ * Create a directory. Returns 0 on error, !=0 on success.
+ */
+int create_directory(char *name);
+
+/*
+ * Concatenate a directory name and a file name. The way this is
+ * done will depend on the OS.
+ */
+char *dir_file_cat(char *dir, char *file);
+
+#endif /* PUTTY_PSFTP_H */
--- /dev/null
+#ifndef PUTTY_PUTTY_H
+#define PUTTY_PUTTY_H
+
+#include <stddef.h> /* for wchar_t */
+
+/*
+ * Global variables. Most modules declare these `extern', but
+ * window.c will do `#define PUTTY_DO_GLOBALS' before including this
+ * module, and so will get them properly defined.
+ */
+#ifndef GLOBAL
+#ifdef PUTTY_DO_GLOBALS
+#define GLOBAL
+#else
+#define GLOBAL extern
+#endif
+#endif
+
+#ifndef DONE_TYPEDEFS
+#define DONE_TYPEDEFS
+typedef struct config_tag Config;
+typedef struct backend_tag Backend;
+typedef struct terminal_tag Terminal;
+#endif
+
+#include "puttyps.h"
+#include "network.h"
+#include "misc.h"
+
+/*
+ * Fingerprints of the PGP master keys that can be used to establish a trust
+ * path between an executable and other files.
+ */
+#define PGP_RSA_MASTER_KEY_FP \
+ "8F 15 97 DA 25 30 AB 0D 88 D1 92 54 11 CF 0C 4C"
+#define PGP_DSA_MASTER_KEY_FP \
+ "313C 3E76 4B74 C2C5 F2AE 83A8 4F5E 6DF5 6A93 B34E"
+
+/* Three attribute types:
+ * The ATTRs (normal attributes) are stored with the characters in
+ * the main display arrays
+ *
+ * The TATTRs (temporary attributes) are generated on the fly, they
+ * can overlap with characters but not with normal attributes.
+ *
+ * The LATTRs (line attributes) are an entirely disjoint space of
+ * flags.
+ *
+ * The DATTRs (display attributes) are internal to terminal.c (but
+ * defined here because their values have to match the others
+ * here); they reuse the TATTR_* space but are always masked off
+ * before sending to the front end.
+ *
+ * ATTR_INVALID is an illegal colour combination.
+ */
+
+#define TATTR_ACTCURS 0x40000000UL /* active cursor (block) */
+#define TATTR_PASCURS 0x20000000UL /* passive cursor (box) */
+#define TATTR_RIGHTCURS 0x10000000UL /* cursor-on-RHS */
+#define TATTR_COMBINING 0x80000000UL /* combining characters */
+
+#define DATTR_STARTRUN 0x80000000UL /* start of redraw run */
+
+#define TDATTR_MASK 0xF0000000UL
+#define TATTR_MASK (TDATTR_MASK)
+#define DATTR_MASK (TDATTR_MASK)
+
+#define LATTR_NORM 0x00000000UL
+#define LATTR_WIDE 0x00000001UL
+#define LATTR_TOP 0x00000002UL
+#define LATTR_BOT 0x00000003UL
+#define LATTR_MODE 0x00000003UL
+#define LATTR_WRAPPED 0x00000010UL /* this line wraps to next */
+#define LATTR_WRAPPED2 0x00000020UL /* with WRAPPED: CJK wide character
+ wrapped to next line, so last
+ single-width cell is empty */
+
+#define ATTR_INVALID 0x03FFFFU
+
+/* Like Linux use the F000 page for direct to font. */
+#define CSET_OEMCP 0x0000F000UL /* OEM Codepage DTF */
+#define CSET_ACP 0x0000F100UL /* Ansi Codepage DTF */
+
+/* These are internal use overlapping with the UTF-16 surrogates */
+#define CSET_ASCII 0x0000D800UL /* normal ASCII charset ESC ( B */
+#define CSET_LINEDRW 0x0000D900UL /* line drawing charset ESC ( 0 */
+#define CSET_SCOACS 0x0000DA00UL /* SCO Alternate charset */
+#define CSET_GBCHR 0x0000DB00UL /* UK variant charset ESC ( A */
+#define CSET_MASK 0xFFFFFF00UL /* Character set mask */
+
+#define DIRECT_CHAR(c) ((c&0xFFFFFC00)==0xD800)
+#define DIRECT_FONT(c) ((c&0xFFFFFE00)==0xF000)
+
+#define UCSERR (CSET_LINEDRW|'a') /* UCS Format error character. */
+/*
+ * UCSWIDE is a special value used in the terminal data to signify
+ * the character cell containing the right-hand half of a CJK wide
+ * character. We use 0xDFFF because it's part of the surrogate
+ * range and hence won't be used for anything else (it's impossible
+ * to input it via UTF-8 because our UTF-8 decoder correctly
+ * rejects surrogates).
+ */
+#define UCSWIDE 0xDFFF
+
+#define ATTR_NARROW 0x800000U
+#define ATTR_WIDE 0x400000U
+#define ATTR_BOLD 0x040000U
+#define ATTR_UNDER 0x080000U
+#define ATTR_REVERSE 0x100000U
+#define ATTR_BLINK 0x200000U
+#define ATTR_FGMASK 0x0001FFU
+#define ATTR_BGMASK 0x03FE00U
+#define ATTR_COLOURS 0x03FFFFU
+#define ATTR_FGSHIFT 0
+#define ATTR_BGSHIFT 9
+
+/*
+ * The definitive list of colour numbers stored in terminal
+ * attribute words is kept here. It is:
+ *
+ * - 0-7 are ANSI colours (KRGYBMCW).
+ * - 8-15 are the bold versions of those colours.
+ * - 16-255 are the remains of the xterm 256-colour mode (a
+ * 216-colour cube with R at most significant and B at least,
+ * followed by a uniform series of grey shades running between
+ * black and white but not including either on grounds of
+ * redundancy).
+ * - 256 is default foreground
+ * - 257 is default bold foreground
+ * - 258 is default background
+ * - 259 is default bold background
+ * - 260 is cursor foreground
+ * - 261 is cursor background
+ */
+
+#define ATTR_DEFFG (256 << ATTR_FGSHIFT)
+#define ATTR_DEFBG (258 << ATTR_BGSHIFT)
+#define ATTR_DEFAULT (ATTR_DEFFG | ATTR_DEFBG)
+
+struct sesslist {
+ int nsessions;
+ char **sessions;
+ char *buffer; /* so memory can be freed later */
+};
+
+struct unicode_data {
+ char **uni_tbl;
+ int dbcs_screenfont;
+ int font_codepage;
+ int line_codepage;
+ wchar_t unitab_scoacs[256];
+ wchar_t unitab_line[256];
+ wchar_t unitab_font[256];
+ wchar_t unitab_xterm[256];
+ wchar_t unitab_oemcp[256];
+ unsigned char unitab_ctrl[256];
+};
+
+#define LGXF_OVR 1 /* existing logfile overwrite */
+#define LGXF_APN 0 /* existing logfile append */
+#define LGXF_ASK -1 /* existing logfile ask */
+#define LGTYP_NONE 0 /* logmode: no logging */
+#define LGTYP_ASCII 1 /* logmode: pure ascii */
+#define LGTYP_DEBUG 2 /* logmode: all chars of traffic */
+#define LGTYP_PACKETS 3 /* logmode: SSH data packets */
+#define LGTYP_SSHRAW 4 /* logmode: SSH raw data */
+
+typedef enum {
+ /* Actual special commands. Originally Telnet, but some codes have
+ * been re-used for similar specials in other protocols. */
+ TS_AYT, TS_BRK, TS_SYNCH, TS_EC, TS_EL, TS_GA, TS_NOP, TS_ABORT,
+ TS_AO, TS_IP, TS_SUSP, TS_EOR, TS_EOF, TS_LECHO, TS_RECHO, TS_PING,
+ TS_EOL,
+ /* Special command for SSH. */
+ TS_REKEY,
+ /* POSIX-style signals. (not Telnet) */
+ TS_SIGABRT, TS_SIGALRM, TS_SIGFPE, TS_SIGHUP, TS_SIGILL,
+ TS_SIGINT, TS_SIGKILL, TS_SIGPIPE, TS_SIGQUIT, TS_SIGSEGV,
+ TS_SIGTERM, TS_SIGUSR1, TS_SIGUSR2,
+ /* Pseudo-specials used for constructing the specials menu. */
+ TS_SEP, /* Separator */
+ TS_SUBMENU, /* Start a new submenu with specified name */
+ TS_EXITMENU /* Exit current submenu or end of specials */
+} Telnet_Special;
+
+struct telnet_special {
+ const char *name;
+ int code;
+};
+
+typedef enum {
+ MBT_NOTHING,
+ MBT_LEFT, MBT_MIDDLE, MBT_RIGHT, /* `raw' button designations */
+ MBT_SELECT, MBT_EXTEND, MBT_PASTE, /* `cooked' button designations */
+ MBT_WHEEL_UP, MBT_WHEEL_DOWN /* mouse wheel */
+} Mouse_Button;
+
+typedef enum {
+ MA_NOTHING, MA_CLICK, MA_2CLK, MA_3CLK, MA_DRAG, MA_RELEASE
+} Mouse_Action;
+
+/* Keyboard modifiers -- keys the user is actually holding down */
+
+#define PKM_SHIFT 0x01
+#define PKM_CONTROL 0x02
+#define PKM_META 0x04
+#define PKM_ALT 0x08
+
+/* Keyboard flags that aren't really modifiers */
+#define PKF_CAPSLOCK 0x10
+#define PKF_NUMLOCK 0x20
+#define PKF_REPEAT 0x40
+
+/* Stand-alone keysyms for function keys */
+
+typedef enum {
+ PK_NULL, /* No symbol for this key */
+ /* Main keypad keys */
+ PK_ESCAPE, PK_TAB, PK_BACKSPACE, PK_RETURN, PK_COMPOSE,
+ /* Editing keys */
+ PK_HOME, PK_INSERT, PK_DELETE, PK_END, PK_PAGEUP, PK_PAGEDOWN,
+ /* Cursor keys */
+ PK_UP, PK_DOWN, PK_RIGHT, PK_LEFT, PK_REST,
+ /* Numeric keypad */ /* Real one looks like: */
+ PK_PF1, PK_PF2, PK_PF3, PK_PF4, /* PF1 PF2 PF3 PF4 */
+ PK_KPCOMMA, PK_KPMINUS, PK_KPDECIMAL, /* 7 8 9 - */
+ PK_KP0, PK_KP1, PK_KP2, PK_KP3, PK_KP4, /* 4 5 6 , */
+ PK_KP5, PK_KP6, PK_KP7, PK_KP8, PK_KP9, /* 1 2 3 en- */
+ PK_KPBIGPLUS, PK_KPENTER, /* 0 . ter */
+ /* Top row */
+ PK_F1, PK_F2, PK_F3, PK_F4, PK_F5,
+ PK_F6, PK_F7, PK_F8, PK_F9, PK_F10,
+ PK_F11, PK_F12, PK_F13, PK_F14, PK_F15,
+ PK_F16, PK_F17, PK_F18, PK_F19, PK_F20,
+ PK_PAUSE
+} Key_Sym;
+
+#define PK_ISEDITING(k) ((k) >= PK_HOME && (k) <= PK_PAGEDOWN)
+#define PK_ISCURSOR(k) ((k) >= PK_UP && (k) <= PK_REST)
+#define PK_ISKEYPAD(k) ((k) >= PK_PF1 && (k) <= PK_KPENTER)
+#define PK_ISFKEY(k) ((k) >= PK_F1 && (k) <= PK_F20)
+
+enum {
+ VT_XWINDOWS, VT_OEMANSI, VT_OEMONLY, VT_POORMAN, VT_UNICODE
+};
+
+enum {
+ /*
+ * SSH-2 key exchange algorithms
+ */
+ KEX_WARN,
+ KEX_DHGROUP1,
+ KEX_DHGROUP14,
+ KEX_DHGEX,
+ KEX_RSA,
+ KEX_MAX
+};
+
+enum {
+ /*
+ * SSH ciphers (both SSH-1 and SSH-2)
+ */
+ CIPHER_WARN, /* pseudo 'cipher' */
+ CIPHER_3DES,
+ CIPHER_BLOWFISH,
+ CIPHER_AES, /* (SSH-2 only) */
+ CIPHER_DES,
+ CIPHER_ARCFOUR,
+ CIPHER_MAX /* no. ciphers (inc warn) */
+};
+
+enum {
+ /*
+ * Several different bits of the PuTTY configuration seem to be
+ * three-way settings whose values are `always yes', `always
+ * no', and `decide by some more complex automated means'. This
+ * is true of line discipline options (local echo and line
+ * editing), proxy DNS, Close On Exit, and SSH server bug
+ * workarounds. Accordingly I supply a single enum here to deal
+ * with them all.
+ */
+ FORCE_ON, FORCE_OFF, AUTO
+};
+
+enum {
+ /*
+ * Proxy types.
+ */
+ PROXY_NONE, PROXY_SOCKS4, PROXY_SOCKS5,
+ PROXY_HTTP, PROXY_TELNET, PROXY_CMD
+};
+
+enum {
+ /*
+ * Line discipline options which the backend might try to control.
+ */
+ LD_EDIT, /* local line editing */
+ LD_ECHO /* local echo */
+};
+
+enum {
+ /* Actions on remote window title query */
+ TITLE_NONE, TITLE_EMPTY, TITLE_REAL
+};
+
+enum {
+ /* Protocol back ends. (cfg.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_DISABLED, BELL_DEFAULT, BELL_VISUAL, BELL_WAVEFILE, BELL_PCSPEAKER
+};
+
+enum {
+ /* Taskbar flashing indication on bell (cfg.beep_ind) */
+ B_IND_DISABLED, B_IND_FLASH, B_IND_STEADY
+};
+
+enum {
+ /* Resize actions (cfg.resize_action) */
+ RESIZE_TERM, RESIZE_DISABLED, RESIZE_FONT, RESIZE_EITHER
+};
+
+enum {
+ /* Function key types (cfg.funky_type) */
+ FUNKY_TILDE,
+ FUNKY_LINUX,
+ FUNKY_XTERM,
+ FUNKY_VT400,
+ FUNKY_VT100P,
+ FUNKY_SCO
+};
+
+enum {
+ FQ_DEFAULT, FQ_ANTIALIASED, FQ_NONANTIALIASED, FQ_CLEARTYPE
+};
+
+enum {
+ SER_PAR_NONE, SER_PAR_ODD, SER_PAR_EVEN, SER_PAR_MARK, SER_PAR_SPACE
+};
+
+enum {
+ SER_FLOW_NONE, SER_FLOW_XONXOFF, SER_FLOW_RTSCTS, SER_FLOW_DSRDTR
+};
+
+/*
+ * Tables of string <-> enum value mappings used in settings.c.
+ * Defined here so that backends can export their GSS library tables
+ * to the cross-platform settings code.
+ */
+struct keyvalwhere {
+ /*
+ * Two fields which define a string and enum value to be
+ * equivalent to each other.
+ */
+ char *s;
+ int v;
+
+ /*
+ * The next pair of fields are used by gprefs() in settings.c to
+ * arrange that when it reads a list of strings representing a
+ * preference list and translates it into the corresponding list
+ * of integers, strings not appearing in the list are entered in a
+ * configurable position rather than uniformly at the end.
+ */
+
+ /*
+ * 'vrel' indicates which other value in the list to place this
+ * element relative to. It should be a value that has occurred in
+ * a 'v' field of some other element of the array, or -1 to
+ * indicate that we simply place relative to one or other end of
+ * the list.
+ *
+ * gprefs will try to process the elements in an order which makes
+ * this field work (i.e. so that the element referenced has been
+ * added before processing this one).
+ */
+ int vrel;
+
+ /*
+ * 'where' indicates whether to place the new value before or
+ * after the one referred to by vrel. -1 means before; +1 means
+ * after.
+ *
+ * When vrel is -1, this also implicitly indicates which end of
+ * the array to use. So vrel=-1, where=-1 means to place _before_
+ * some end of the list (hence, at the last element); vrel=-1,
+ * where=+1 means to place _after_ an end (hence, at the first).
+ */
+ int where;
+};
+
+#ifndef NO_GSSAPI
+extern const int ngsslibs;
+extern const char *const gsslibnames[]; /* for displaying in configuration */
+extern const struct keyvalwhere gsslibkeywords[]; /* for settings.c */
+#endif
+
+extern const char *const ttymodes[];
+
+enum {
+ /*
+ * Network address types. Used for specifying choice of IPv4/v6
+ * in config; also used in proxy.c to indicate whether a given
+ * host name has already been resolved or will be resolved at
+ * the proxy end.
+ */
+ 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);
+ void (*free) (void *handle);
+ /* back->reconfig() passes in a replacement configuration. */
+ void (*reconfig) (void *handle, Config *cfg);
+ /* 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);
+ void (*special) (void *handle, Telnet_Special code);
+ const struct telnet_special *(*get_specials) (void *handle);
+ int (*connected) (void *handle);
+ int (*exitcode) (void *handle);
+ /* If back->sendok() returns FALSE, data sent to it from the frontend
+ * may be lost. */
+ int (*sendok) (void *handle);
+ int (*ldisc) (void *handle, int);
+ void (*provide_ldisc) (void *handle, void *ldisc);
+ void (*provide_logctx) (void *handle, void *logctx);
+ /*
+ * back->unthrottle() tells the back end that the front end
+ * buffer is clearing.
+ */
+ void (*unthrottle) (void *handle, int);
+ int (*cfg_info) (void *handle);
+ char *name;
+ int protocol;
+ int default_port;
+};
+
+extern Backend *backends[];
+
+/*
+ * Suggested default protocol provided by the backend link module.
+ * The application is free to ignore this.
+ */
+extern const int be_default_protocol;
+
+/*
+ * 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;
+ 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.
+ *
+ * FLAG_STDERR is set in command-line applications (which have a
+ * functioning stderr that it makes sense to write to) and not in
+ * GUI applications (which don't).
+ *
+ * FLAG_INTERACTIVE is set when a full interactive shell session is
+ * being run, _either_ because no remote command has been provided
+ * _or_ because the application is GUI and can't run non-
+ * interactively.
+ *
+ * These flags describe the type of _application_ - they wouldn't
+ * vary between individual sessions - and so it's OK to have this
+ * variable be GLOBAL.
+ *
+ * Note that additional flags may be defined in platform-specific
+ * headers. It's probably best if those ones start from 0x1000, to
+ * avoid collision.
+ */
+#define FLAG_VERBOSE 0x0001
+#define FLAG_STDERR 0x0002
+#define FLAG_INTERACTIVE 0x0004
+GLOBAL int flags;
+
+/*
+ * Likewise, these two variables are set up when the application
+ * initialises, and inform all default-settings accesses after
+ * that.
+ */
+GLOBAL int default_protocol;
+GLOBAL int default_port;
+
+/*
+ * This is set TRUE by cmdline.c iff a session is loaded with "-load".
+ */
+GLOBAL int loaded_session;
+/*
+ * This is set to the name of the loaded session.
+ */
+GLOBAL char *cmdline_session_name;
+
+struct RSAKey; /* be a little careful of scope */
+
+/*
+ * Mechanism for getting text strings such as usernames and passwords
+ * from the front-end.
+ * The fields are mostly modelled after SSH's keyboard-interactive auth.
+ * FIXME We should probably mandate a character set/encoding (probably UTF-8).
+ *
+ * Since many of the pieces of text involved may be chosen by the server,
+ * the caller must take care to ensure that the server can't spoof locally-
+ * generated prompts such as key passphrase prompts. Some ground rules:
+ * - If the front-end needs to truncate a string, it should lop off the
+ * end.
+ * - The front-end should filter out any dangerous characters and
+ * generally not trust the strings. (But \n is required to behave
+ * vaguely sensibly, at least in `instruction', and ideally in
+ * `prompt[]' too.)
+ */
+typedef struct {
+ char *prompt;
+ int echo;
+ char *result; /* allocated/freed by caller */
+ size_t result_len;
+} 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.
+ * This is a hint only; the front-end is at liberty not to use this
+ * information (so the caller should ensure that the supplied text is
+ * sufficient).
+ */
+ int to_server;
+ char *name; /* Short description, perhaps for dialog box title */
+ int name_reqd; /* Display of `name' required or optional? */
+ char *instruction; /* Long description, maybe with embedded newlines */
+ int instr_reqd; /* Display of `instruction' required or optional? */
+ size_t n_prompts; /* May be zero (in which case display the foregoing,
+ * if any, and return success) */
+ prompt_t **prompts;
+ 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);
+/* Burn the evidence. (Assumes _all_ strings want free()ing.) */
+void free_prompts(prompts_t *p);
+
+/*
+ * Exports from the front end.
+ */
+void request_resize(void *frontend, int, int);
+void do_text(Context, int, int, wchar_t *, int, unsigned long, int);
+void do_cursor(Context, int, int, wchar_t *, int, unsigned long, int);
+int char_width(Context ctx, int uc);
+#ifdef OPTIMISE_SCROLL
+void do_scroll(Context, int, int, int);
+#endif
+void set_title(void *frontend, char *);
+void set_icon(void *frontend, char *);
+void set_sbar(void *frontend, int, int, int);
+Context get_ctx(void *frontend);
+void free_ctx(Context);
+void palette_set(void *frontend, int, int, int, int);
+void palette_reset(void *frontend);
+void write_aclip(void *frontend, char *, int, int);
+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 fatalbox(char *, ...);
+void modalfatalbox(char *, ...);
+#ifdef macintosh
+#pragma noreturn(fatalbox)
+#pragma noreturn(modalfatalbox)
+#endif
+void do_beep(void *frontend, int);
+void begin_session(void *frontend);
+void sys_cursor(void *frontend, int x, int y);
+void request_paste(void *frontend);
+void frontend_keypress(void *frontend);
+void ldisc_update(void *frontend, int echo, int edit);
+/* It's the backend's responsibility to invoke this at the start of a
+ * connection, if necessary; it can also invoke it later if the set of
+ * special commands changes. It does not need to invoke it at session
+ * shutdown. */
+void update_specials_menu(void *frontend);
+int from_backend(void *frontend, int is_stderr, const char *data, int len);
+int from_backend_untrusted(void *frontend, const char *data, int len);
+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);
+/*
+ * >0 = `got all results, carry on'
+ * 0 = `user cancelled' (FIXME distinguish "give up entirely" and "next auth"?)
+ * <0 = `please call back later with more in/inlen'
+ */
+int get_userpass_input(prompts_t *p, unsigned char *in, int inlen);
+#define OPTIMISE_IS_SCROLL 1
+
+void set_iconic(void *frontend, int iconic);
+void move_window(void *frontend, int x, int y);
+void set_zorder(void *frontend, int top);
+void refresh_window(void *frontend);
+void set_zoomed(void *frontend, int zoomed);
+int is_iconic(void *frontend);
+void get_window_pos(void *frontend, int *x, int *y);
+void get_window_pixels(void *frontend, int *x, int *y);
+char *get_window_title(void *frontend, int icon);
+/* Hint from backend to frontend about time-consuming operations.
+ * Initial state is assumed to be BUSY_NOT. */
+enum {
+ BUSY_NOT, /* Not busy, all user interaction OK */
+ BUSY_WAITING, /* Waiting for something; local event loops still running
+ so some local interaction (e.g. menus) OK, but network
+ stuff is suspended */
+ BUSY_CPU /* Locally busy (e.g. crypto); user interaction suspended */
+};
+void set_busy_status(void *frontend, int status);
+
+void cleanup_exit(int);
+
+/*
+ * Exports from noise.c.
+ */
+void noise_get_heavy(void (*func) (void *, int));
+void noise_get_light(void (*func) (void *, int));
+void noise_regular(void);
+void noise_ultralight(unsigned long data);
+void random_save_seed(void);
+void random_destroy_seed(void);
+
+/*
+ * 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);
+void get_sesslist(struct sesslist *, int allocate);
+void do_defaults(char *, Config *);
+void registry_cleanup(void);
+
+/*
+ * Functions used by settings.c to provide platform-specific
+ * default settings.
+ *
+ * (The integer one is expected to return `def' if it has no clear
+ * 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.)
+ */
+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);
+
+/*
+ * Exports from terminal.c.
+ */
+
+Terminal *term_init(Config *, 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);
+void term_pwron(Terminal *, int);
+void term_clrsb(Terminal *);
+void term_mouse(Terminal *, Mouse_Button, Mouse_Button, Mouse_Action,
+ int,int,int,int,int);
+void term_key(Terminal *, Key_Sym, wchar_t *, size_t, unsigned int,
+ unsigned int);
+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_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),
+ void *resize_ctx);
+void term_provide_logctx(Terminal *term, void *logctx);
+void term_set_focus(Terminal *term, int has_focus);
+char *term_get_ttymode(Terminal *term, const char *mode);
+int term_get_userpass_input(Terminal *term, prompts_t *p,
+ unsigned char *in, int inlen);
+
+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_free(void *logctx);
+void log_reconfig(void *logctx, Config *cfg);
+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);
+enum { PKT_INCOMING, PKT_OUTGOING };
+enum { PKTLOG_EMIT, PKTLOG_BLANK, PKTLOG_OMIT };
+struct logblank_t {
+ int offset;
+ int len;
+ 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);
+
+/*
+ * Exports from testback.c
+ */
+
+extern Backend null_backend;
+extern Backend loop_backend;
+
+/*
+ * Exports from raw.c.
+ */
+
+extern Backend raw_backend;
+
+/*
+ * Exports from rlogin.c.
+ */
+
+extern Backend rlogin_backend;
+
+/*
+ * Exports from telnet.c.
+ */
+
+extern Backend telnet_backend;
+
+/*
+ * Exports from ssh.c.
+ */
+extern Backend ssh_backend;
+
+/*
+ * Exports from ldisc.c.
+ */
+void *ldisc_create(Config *, Terminal *, Backend *, void *, void *);
+void ldisc_free(void *);
+void ldisc_send(void *handle, char *buf, int len, int interactive);
+
+/*
+ * Exports from ldiscucs.c.
+ */
+void lpage_send(void *, int codepage, char *buf, int len, int interactive);
+void luni_send(void *, wchar_t * widebuf, int len, int interactive);
+
+/*
+ * Exports from sshrand.c.
+ */
+
+void random_add_noise(void *noise, int length);
+int random_byte(void);
+void random_get_savedata(void **data, int *len);
+extern int random_active;
+/* The random number subsystem is activated if at least one other entity
+ * within the program expresses an interest in it. So each SSH session
+ * calls random_ref on startup and random_unref on shutdown. */
+void random_ref(void);
+void random_unref(void);
+
+/*
+ * 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);
+void pinger_free(Pinger);
+
+/*
+ * Exports from misc.c.
+ */
+
+#include "misc.h"
+int cfg_launchable(const Config *cfg);
+char const *cfg_dest(const Config *cfg);
+
+/*
+ * Exports from sercfg.c.
+ */
+void ser_setup_config_box(struct controlbox *b, int midsession,
+ int parity_mask, int flow_mask);
+
+/*
+ * Exports from version.c.
+ */
+extern char ver[];
+
+/*
+ * Exports from unicode.c.
+ */
+#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,
+ wchar_t *wcstr, int wclen);
+int wc_to_mb(int codepage, int flags, 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);
+const char *cp_enumerate (int index);
+const char *cp_name(int codepage);
+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);
+
+/*
+ * Exports from mscrypto.c
+ */
+#ifdef MSCRYPTOAPI
+int crypto_startup();
+void crypto_wrapup();
+#endif
+
+/*
+ * Exports from pageantc.c.
+ *
+ * agent_query returns 1 for here's-a-response, and 0 for query-in-
+ * progress. In the latter case there will be a call to `callback'
+ * at some future point, passing callback_ctx as the first
+ * parameter and the actual reply data as the second and third.
+ *
+ * The response may be a NULL pointer (in either of the synchronous
+ * or asynchronous cases), which indicates failure to receive a
+ * response.
+ */
+int agent_query(void *in, int inlen, void **out, int *outlen,
+ void (*callback)(void *, void *, int), void *callback_ctx);
+int agent_exists(void);
+
+/*
+ * Exports from wildcard.c
+ */
+const char *wc_error(int value);
+int wc_match(const char *wildcard, const char *target);
+int wc_unescape(char *output, const char *wildcard);
+
+/*
+ * Exports from frontend (windlg.c etc)
+ */
+void logevent(void *frontend, const char *);
+void pgp_fingerprints(void);
+/*
+ * verify_ssh_host_key() can return one of three values:
+ *
+ * - +1 means `key was OK' (either already known or the user just
+ * approved it) `so continue with the connection'
+ *
+ * - 0 means `key was not OK, abandon the connection'
+ *
+ * - -1 means `I've initiated enquiries, please wait to be called
+ * back via the provided function with a result that's either 0
+ * or +1'.
+ */
+int verify_ssh_host_key(void *frontend, char *host, int port, char *keytype,
+ char *keystr, char *fingerprint,
+ void (*callback)(void *ctx, int result), void *ctx);
+/*
+ * askalg has the same set of return values as verify_ssh_host_key.
+ */
+int askalg(void *frontend, const char *algtype, const char *algname,
+ void (*callback)(void *ctx, int result), void *ctx);
+/*
+ * askappend can return four values:
+ *
+ * - 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,
+ 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.
+ */
+extern int console_batch_mode;
+int console_get_userpass_input(prompts_t *p, unsigned char *in, int inlen);
+void console_provide_logctx(void *logctx);
+int is_interactive(void);
+
+/*
+ * Exports from printing.c.
+ */
+typedef struct printer_enum_tag printer_enum;
+typedef struct printer_job_tag printer_job;
+printer_enum *printer_start_enum(int *nprinters);
+char *printer_get_name(printer_enum *, int);
+void printer_finish_enum(printer_enum *);
+printer_job *printer_start_job(char *printer);
+void printer_job_data(printer_job *, void *, int);
+void printer_finish_job(printer_job *);
+
+/*
+ * 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 *);
+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;
+
+void cmdline_error(char *, ...);
+
+/*
+ * Exports from config.c.
+ */
+struct controlbox;
+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 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);
+
+/*
+ * X11 auth mechanisms we know about.
+ */
+enum {
+ X11_NO_AUTH,
+ X11_MIT, /* MIT-MAGIC-COOKIE-1 */
+ X11_XDM, /* XDM-AUTHORIZATION-1 */
+ X11_NAUTHS
+};
+extern const char *const x11_authnames[]; /* declared in x11fwd.c */
+
+/*
+ * Miscellaneous exports from the platform-specific code.
+ */
+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);
+char *get_username(void); /* return value needs freeing */
+char *get_random_data(int bytes); /* used in cmdgen.c */
+
+/*
+ * Exports and imports from timing.c.
+ *
+ * schedule_timer() asks the front end to schedule a callback to a
+ * timer function in a given number of ticks. The returned value is
+ * the time (in ticks since an arbitrary offset) at which the
+ * callback can be expected. This value will also be passed as the
+ * `now' parameter to the callback function. Hence, you can (for
+ * example) schedule an event at a particular time by calling
+ * schedule_timer() and storing the return value in your context
+ * structure as the time when that event is due. The first time a
+ * callback function gives you that value or more as `now', you do
+ * the thing.
+ *
+ * expire_timer_context() drops all current timers associated with
+ * a given value of ctx (for when you're about to free ctx).
+ *
+ * run_timers() is called from the front end when it has reason to
+ * think some timers have reached their moment, or when it simply
+ * needs to know how long to wait next. We pass it the time we
+ * think it is. It returns TRUE and places the time when the next
+ * timer needs to go off in `next', or alternatively it returns
+ * FALSE if there are no timers at all pending.
+ *
+ * timer_change_notify() must be supplied by the front end; it
+ * notifies the front end that a new timer has been added to the
+ * list which is sooner than any existing ones. It provides the
+ * time when that timer needs to go off.
+ *
+ * *** FRONT END IMPLEMENTORS NOTE:
+ *
+ * There's an important subtlety in the front-end implementation of
+ * the timer interface. When a front end is given a `next' value,
+ * either returned from run_timers() or via timer_change_notify(),
+ * it should ensure that it really passes _that value_ as the `now'
+ * parameter to its next run_timers call. It should _not_ simply
+ * call GETTICKCOUNT() to get the `now' parameter when invoking
+ * run_timers().
+ *
+ * The reason for this is that an OS's system clock might not agree
+ * exactly with the timing mechanisms it supplies to wait for a
+ * given interval. I'll illustrate this by the simple example of
+ * Unix Plink, which uses timeouts to select() in a way which for
+ * these purposes can simply be considered to be a wait() function.
+ * Suppose, for the sake of argument, that this wait() function
+ * tends to return early by 1%. Then a possible sequence of actions
+ * is:
+ *
+ * - run_timers() tells the front end that the next timer firing
+ * is 10000ms from now.
+ * - Front end calls wait(10000ms), but according to
+ * GETTICKCOUNT() it has only waited for 9900ms.
+ * - Front end calls run_timers() again, passing time T-100ms as
+ * `now'.
+ * - run_timers() does nothing, and says the next timer firing is
+ * still 100ms from now.
+ * - Front end calls wait(100ms), which only waits for 99ms.
+ * - Front end calls run_timers() yet again, passing time T-1ms.
+ * - run_timers() says there's still 1ms to wait.
+ * - Front end calls wait(1ms).
+ *
+ * If you're _lucky_ at this point, wait(1ms) will actually wait
+ * for 1ms and you'll only have woken the program up three times.
+ * If you're unlucky, wait(1ms) might do nothing at all due to
+ * being below some minimum threshold, and you might find your
+ * program spends the whole of the last millisecond tight-looping
+ * between wait() and run_timers().
+ *
+ * Instead, what you should do is to _save_ the precise `next'
+ * value provided by run_timers() or via timer_change_notify(), and
+ * use that precise value as the input to the next run_timers()
+ * call. So:
+ *
+ * - run_timers() tells the front end that the next timer firing
+ * is at time T, 10000ms from now.
+ * - Front end calls wait(10000ms).
+ * - Front end then immediately calls run_timers() and passes it
+ * time T, without stopping to check GETTICKCOUNT() at all.
+ *
+ * This guarantees that the program wakes up only as many times as
+ * there are actual timer actions to be taken, and that the timing
+ * mechanism will never send it into a tight loop.
+ *
+ * (It does also mean that the timer action in the above example
+ * will occur 100ms early, but this is not generally critical. And
+ * 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);
+void expire_timer_context(void *ctx);
+int run_timers(long now, long *next);
+void timer_change_notify(long next);
+
+/*
+ * 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
+ * end, but it'll do for the moment.)
+ */
+#ifndef JUMPLIST_SUPPORTED
+#define add_session_to_jumplist(x) ((void)0)
+#define remove_session_from_jumplist(x) ((void)0)
+#endif
+
+#endif
--- /dev/null
+/*
+ * PuTTY memory-handling header.
+ */
+
+#ifndef PUTTY_PUTTYMEM_H
+#define PUTTY_PUTTYMEM_H
+
+#include <stddef.h> /* for size_t */
+#include <string.h> /* for memcpy() */
+
+
+/* #define MALLOC_LOG do this if you suspect putty of leaking memory */
+#ifdef MALLOC_LOG
+#define smalloc(z) (mlog(__FILE__,__LINE__), safemalloc(z,1))
+#define snmalloc(z,s) (mlog(__FILE__,__LINE__), safemalloc(z,s))
+#define srealloc(y,z) (mlog(__FILE__,__LINE__), saferealloc(y,z,1))
+#define snrealloc(y,z,s) (mlog(__FILE__,__LINE__), saferealloc(y,z,s))
+#define sfree(z) (mlog(__FILE__,__LINE__), safefree(z))
+void mlog(char *, int);
+#else
+#define smalloc(z) safemalloc(z,1)
+#define snmalloc safemalloc
+#define srealloc(y,z) saferealloc(y,z,1)
+#define snrealloc saferealloc
+#define sfree safefree
+#endif
+
+void *safemalloc(size_t, size_t);
+void *saferealloc(void *, size_t, size_t);
+void safefree(void *);
+
+/*
+ * 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.
+ */
+#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)))
+
+#endif
--- /dev/null
+/*
+ * Find the platform-specific header for this platform.
+ */
+
+#ifndef PUTTY_PUTTYPS_H
+#define PUTTY_PUTTYPS_H
+
+#ifdef _WINDOWS
+
+#include "winstuff.h"
+
+#elif defined(MACOSX)
+
+#include "osx.h"
+
+#else
+
+#include "unix.h"
+
+#endif
+
+#endif
--- /dev/null
+/*
+ * "Raw" backend.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "putty.h"
+
+#ifndef FALSE
+#define FALSE 0
+#endif
+#ifndef TRUE
+#define TRUE 1
+#endif
+
+#define RAW_MAX_BACKLOG 4096
+
+typedef struct raw_backend_data {
+ const struct plug_function_table *fn;
+ /* the above field _must_ be first in the structure */
+
+ Socket s;
+ int bufsize;
+ void *frontend;
+} *Raw;
+
+static void raw_size(void *handle, int width, int height);
+
+static void c_write(Raw raw, char *buf, int len)
+{
+ int backlog = from_backend(raw->frontend, 0, buf, len);
+ sk_set_frozen(raw->s, backlog > RAW_MAX_BACKLOG);
+}
+
+static void raw_log(Plug plug, int type, SockAddr addr, int port,
+ const char *error_msg, int error_code)
+{
+ Raw raw = (Raw) 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(raw->frontend, msg);
+}
+
+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. */
+ return 0;
+}
+
+static int raw_receive(Plug plug, int urgent, char *data, int len)
+{
+ Raw raw = (Raw) plug;
+ c_write(raw, data, len);
+ return 1;
+}
+
+static void raw_sent(Plug plug, int bufsize)
+{
+ Raw raw = (Raw) plug;
+ raw->bufsize = bufsize;
+}
+
+/*
+ * Called to set up the raw connection.
+ *
+ * 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 *raw_init(void *frontend_handle, void **backend_handle,
+ Config *cfg,
+ char *host, int port, char **realhost, int nodelay,
+ int keepalive)
+{
+ static const struct plug_function_table fn_table = {
+ raw_log,
+ raw_closing,
+ raw_receive,
+ raw_sent
+ };
+ SockAddr addr;
+ const char *err;
+ Raw raw;
+
+ raw = snew(struct raw_backend_data);
+ raw->fn = &fn_table;
+ raw->s = NULL;
+ *backend_handle = raw;
+
+ raw->frontend = frontend_handle;
+
+ /*
+ * Try to find host.
+ */
+ {
+ char *buf;
+ buf = dupprintf("Looking up host \"%s\"%s", host,
+ (cfg->addressfamily == ADDRTYPE_IPV4 ? " (IPv4)" :
+ (cfg->addressfamily == ADDRTYPE_IPV6 ? " (IPv6)" :
+ "")));
+ logevent(raw->frontend, buf);
+ sfree(buf);
+ }
+ addr = name_lookup(host, port, realhost, cfg, cfg->addressfamily);
+ if ((err = sk_addr_error(addr)) != NULL) {
+ sk_addr_free(addr);
+ return err;
+ }
+
+ if (port < 0)
+ port = 23; /* default telnet port */
+
+ /*
+ * Open socket.
+ */
+ raw->s = new_connection(addr, *realhost, port, 0, 1, nodelay, keepalive,
+ (Plug) raw, cfg);
+ if ((err = sk_socket_error(raw->s)) != NULL)
+ return err;
+
+ if (*cfg->loghost) {
+ char *colon;
+
+ sfree(*realhost);
+ *realhost = dupstr(cfg->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
+ * with it.
+ */
+ *colon++ = '\0';
+ }
+ }
+
+ return NULL;
+}
+
+static void raw_free(void *handle)
+{
+ Raw raw = (Raw) handle;
+
+ if (raw->s)
+ sk_close(raw->s);
+ sfree(raw);
+}
+
+/*
+ * Stub routine (we don't have any need to reconfigure this backend).
+ */
+static void raw_reconfig(void *handle, Config *cfg)
+{
+}
+
+/*
+ * Called to send data down the raw connection.
+ */
+static int raw_send(void *handle, char *buf, int len)
+{
+ Raw raw = (Raw) handle;
+
+ if (raw->s == NULL)
+ return 0;
+
+ raw->bufsize = sk_write(raw->s, buf, len);
+
+ return raw->bufsize;
+}
+
+/*
+ * Called to query the current socket sendability status.
+ */
+static int raw_sendbuffer(void *handle)
+{
+ Raw raw = (Raw) handle;
+ return raw->bufsize;
+}
+
+/*
+ * Called to set the size of the window
+ */
+static void raw_size(void *handle, int width, int height)
+{
+ /* Do nothing! */
+ return;
+}
+
+/*
+ * Send raw special codes.
+ */
+static void raw_special(void *handle, Telnet_Special code)
+{
+ /* Do nothing! */
+ return;
+}
+
+/*
+ * Return a list of the special codes that make sense in this
+ * protocol.
+ */
+static const struct telnet_special *raw_get_specials(void *handle)
+{
+ return NULL;
+}
+
+static int raw_connected(void *handle)
+{
+ Raw raw = (Raw) handle;
+ return raw->s != NULL;
+}
+
+static int raw_sendok(void *handle)
+{
+ return 1;
+}
+
+static void raw_unthrottle(void *handle, int backlog)
+{
+ Raw raw = (Raw) handle;
+ sk_set_frozen(raw->s, backlog > RAW_MAX_BACKLOG);
+}
+
+static int raw_ldisc(void *handle, int option)
+{
+ if (option == LD_EDIT || option == LD_ECHO)
+ return 1;
+ return 0;
+}
+
+static void raw_provide_ldisc(void *handle, void *ldisc)
+{
+ /* This is a stub. */
+}
+
+static void raw_provide_logctx(void *handle, void *logctx)
+{
+ /* This is a stub. */
+}
+
+static int raw_exitcode(void *handle)
+{
+ Raw raw = (Raw) handle;
+ if (raw->s != NULL)
+ return -1; /* still connected */
+ else
+ /* Exit codes are a meaningless concept in the Raw protocol */
+ return 0;
+}
+
+/*
+ * cfg_info for Raw does nothing at all.
+ */
+static int raw_cfg_info(void *handle)
+{
+ return 0;
+}
+
+Backend raw_backend = {
+ raw_init,
+ raw_free,
+ raw_reconfig,
+ raw_send,
+ raw_sendbuffer,
+ raw_size,
+ raw_special,
+ raw_get_specials,
+ raw_connected,
+ raw_exitcode,
+ raw_sendok,
+ raw_ldisc,
+ raw_provide_ldisc,
+ raw_provide_logctx,
+ raw_unthrottle,
+ raw_cfg_info,
+ "raw",
+ PROT_RAW,
+ 0
+};
--- /dev/null
+//{{NO_DEPENDENCIES}}
+// Microsoft Developer Studio generated include file.
+// Used by win_res.rc
+//
+
+// Next default values for new objects
+//
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE 101
+#define _APS_NEXT_COMMAND_VALUE 40001
+#define _APS_NEXT_CONTROL_VALUE 1000
+#define _APS_NEXT_SYMED_VALUE 101
+#endif
+#endif
--- /dev/null
+/*
+ * Rlogin backend.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+
+#include "putty.h"
+
+#ifndef FALSE
+#define FALSE 0
+#endif
+#ifndef TRUE
+#define TRUE 1
+#endif
+
+#define RLOGIN_MAX_BACKLOG 4096
+
+typedef struct rlogin_tag {
+ const struct plug_function_table *fn;
+ /* the above field _must_ be first in the structure */
+
+ Socket s;
+ int bufsize;
+ int firstbyte;
+ int cansize;
+ int term_width, term_height;
+ void *frontend;
+
+ Config cfg;
+
+ /* In case we need to read a username from the terminal before starting */
+ prompts_t *prompt;
+} *Rlogin;
+
+static void rlogin_size(void *handle, int width, int height);
+
+static void c_write(Rlogin rlogin, char *buf, int len)
+{
+ int backlog = from_backend(rlogin->frontend, 0, buf, len);
+ sk_set_frozen(rlogin->s, backlog > RLOGIN_MAX_BACKLOG);
+}
+
+static void rlogin_log(Plug plug, int type, SockAddr addr, int port,
+ const char *error_msg, int error_code)
+{
+ Rlogin rlogin = (Rlogin) 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(rlogin->frontend, msg);
+}
+
+static int rlogin_closing(Plug plug, const char *error_msg, int error_code,
+ int calling_back)
+{
+ Rlogin rlogin = (Rlogin) plug;
+ if (rlogin->s) {
+ sk_close(rlogin->s);
+ rlogin->s = NULL;
+ notify_remote_exit(rlogin->frontend);
+ }
+ if (error_msg) {
+ /* A socket error has occurred. */
+ logevent(rlogin->frontend, error_msg);
+ connection_fatal(rlogin->frontend, "%s", error_msg);
+ } /* Otherwise, the remote side closed the connection normally. */
+ return 0;
+}
+
+static int rlogin_receive(Plug plug, int urgent, char *data, int len)
+{
+ Rlogin rlogin = (Rlogin) plug;
+ if (urgent == 2) {
+ char c;
+
+ c = *data++;
+ len--;
+ if (c == '\x80') {
+ rlogin->cansize = 1;
+ rlogin_size(rlogin, rlogin->term_width, rlogin->term_height);
+ }
+ /*
+ * We should flush everything (aka Telnet SYNCH) if we see
+ * 0x02, and we should turn off and on _local_ flow control
+ * on 0x10 and 0x20 respectively. I'm not convinced it's
+ * worth it...
+ */
+ } else {
+ /*
+ * Main rlogin protocol. This is really simple: the first
+ * byte is expected to be NULL and is ignored, and the rest
+ * is printed.
+ */
+ if (rlogin->firstbyte) {
+ if (data[0] == '\0') {
+ data++;
+ len--;
+ }
+ rlogin->firstbyte = 0;
+ }
+ if (len > 0)
+ c_write(rlogin, data, len);
+ }
+ return 1;
+}
+
+static void rlogin_sent(Plug plug, int bufsize)
+{
+ Rlogin rlogin = (Rlogin) plug;
+ rlogin->bufsize = bufsize;
+}
+
+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);
+ rlogin->bufsize = sk_write(rlogin->s, &z, 1);
+
+ rlogin->prompt = NULL;
+}
+
+/*
+ * Called to set up the rlogin connection.
+ *
+ * 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 *rlogin_init(void *frontend_handle, void **backend_handle,
+ Config *cfg,
+ char *host, int port, char **realhost,
+ int nodelay, int keepalive)
+{
+ static const struct plug_function_table fn_table = {
+ rlogin_log,
+ rlogin_closing,
+ rlogin_receive,
+ rlogin_sent
+ };
+ SockAddr addr;
+ const char *err;
+ Rlogin rlogin;
+ char ruser[sizeof(cfg->username)];
+
+ rlogin = snew(struct rlogin_tag);
+ rlogin->fn = &fn_table;
+ rlogin->s = NULL;
+ rlogin->frontend = frontend_handle;
+ rlogin->term_width = cfg->width;
+ rlogin->term_height = cfg->height;
+ rlogin->firstbyte = 1;
+ rlogin->cansize = 0;
+ rlogin->prompt = NULL;
+ rlogin->cfg = *cfg; /* STRUCTURE COPY */
+ *backend_handle = rlogin;
+
+ /*
+ * Try to find host.
+ */
+ {
+ char *buf;
+ buf = dupprintf("Looking up host \"%s\"%s", host,
+ (cfg->addressfamily == ADDRTYPE_IPV4 ? " (IPv4)" :
+ (cfg->addressfamily == ADDRTYPE_IPV6 ? " (IPv6)" :
+ "")));
+ logevent(rlogin->frontend, buf);
+ sfree(buf);
+ }
+ addr = name_lookup(host, port, realhost, cfg, cfg->addressfamily);
+ if ((err = sk_addr_error(addr)) != NULL) {
+ sk_addr_free(addr);
+ return err;
+ }
+
+ if (port < 0)
+ port = 513; /* default rlogin port */
+
+ /*
+ * Open socket.
+ */
+ rlogin->s = new_connection(addr, *realhost, port, 1, 0,
+ nodelay, keepalive, (Plug) rlogin, cfg);
+ if ((err = sk_socket_error(rlogin->s)) != NULL)
+ return err;
+
+ if (*cfg->loghost) {
+ char *colon;
+
+ sfree(*realhost);
+ *realhost = dupstr(cfg->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
+ * with it.
+ */
+ *colon++ = '\0';
+ }
+ }
+
+ /*
+ * 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))) {
+ rlogin_startup(rlogin, 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));
+ ret = get_userpass_input(rlogin->prompt, NULL, 0);
+ if (ret >= 0) {
+ rlogin_startup(rlogin, rlogin->prompt->prompts[0]->result);
+ }
+ }
+
+ return NULL;
+}
+
+static void rlogin_free(void *handle)
+{
+ Rlogin rlogin = (Rlogin) handle;
+
+ if (rlogin->prompt)
+ free_prompts(rlogin->prompt);
+ if (rlogin->s)
+ sk_close(rlogin->s);
+ sfree(rlogin);
+}
+
+/*
+ * Stub routine (we don't have any need to reconfigure this backend).
+ */
+static void rlogin_reconfig(void *handle, Config *cfg)
+{
+}
+
+/*
+ * Called to send data down the rlogin connection.
+ */
+static int rlogin_send(void *handle, char *buf, int len)
+{
+ Rlogin rlogin = (Rlogin) handle;
+
+ if (rlogin->s == NULL)
+ return 0;
+
+ if (rlogin->prompt) {
+ /*
+ * We're still prompting for a username, and aren't talking
+ * directly to the network connection yet.
+ */
+ int ret = get_userpass_input(rlogin->prompt,
+ (unsigned char *)buf, len);
+ if (ret >= 0) {
+ rlogin_startup(rlogin, rlogin->prompt->prompts[0]->result);
+ /* that nulls out rlogin->prompt, so then we'll start sending
+ * data down the wire in the obvious way */
+ }
+ } else {
+ rlogin->bufsize = sk_write(rlogin->s, buf, len);
+ }
+
+ return rlogin->bufsize;
+}
+
+/*
+ * Called to query the current socket sendability status.
+ */
+static int rlogin_sendbuffer(void *handle)
+{
+ Rlogin rlogin = (Rlogin) handle;
+ return rlogin->bufsize;
+}
+
+/*
+ * Called to set the size of the window
+ */
+static void rlogin_size(void *handle, int width, int height)
+{
+ Rlogin rlogin = (Rlogin) handle;
+ char b[12] = { '\xFF', '\xFF', 0x73, 0x73, 0, 0, 0, 0, 0, 0, 0, 0 };
+
+ rlogin->term_width = width;
+ rlogin->term_height = height;
+
+ if (rlogin->s == NULL || !rlogin->cansize)
+ return;
+
+ b[6] = rlogin->term_width >> 8;
+ b[7] = rlogin->term_width & 0xFF;
+ b[4] = rlogin->term_height >> 8;
+ b[5] = rlogin->term_height & 0xFF;
+ rlogin->bufsize = sk_write(rlogin->s, b, 12);
+ return;
+}
+
+/*
+ * Send rlogin special codes.
+ */
+static void rlogin_special(void *handle, Telnet_Special code)
+{
+ /* Do nothing! */
+ return;
+}
+
+/*
+ * Return a list of the special codes that make sense in this
+ * protocol.
+ */
+static const struct telnet_special *rlogin_get_specials(void *handle)
+{
+ return NULL;
+}
+
+static int rlogin_connected(void *handle)
+{
+ Rlogin rlogin = (Rlogin) handle;
+ return rlogin->s != NULL;
+}
+
+static int rlogin_sendok(void *handle)
+{
+ /* Rlogin rlogin = (Rlogin) handle; */
+ return 1;
+}
+
+static void rlogin_unthrottle(void *handle, int backlog)
+{
+ Rlogin rlogin = (Rlogin) handle;
+ sk_set_frozen(rlogin->s, backlog > RLOGIN_MAX_BACKLOG);
+}
+
+static int rlogin_ldisc(void *handle, int option)
+{
+ /* Rlogin rlogin = (Rlogin) handle; */
+ return 0;
+}
+
+static void rlogin_provide_ldisc(void *handle, void *ldisc)
+{
+ /* This is a stub. */
+}
+
+static void rlogin_provide_logctx(void *handle, void *logctx)
+{
+ /* This is a stub. */
+}
+
+static int rlogin_exitcode(void *handle)
+{
+ Rlogin rlogin = (Rlogin) handle;
+ if (rlogin->s != NULL)
+ return -1; /* still connected */
+ else
+ /* If we ever implement RSH, we'll probably need to do this properly */
+ return 0;
+}
+
+/*
+ * cfg_info for rlogin does nothing at all.
+ */
+static int rlogin_cfg_info(void *handle)
+{
+ return 0;
+}
+
+Backend rlogin_backend = {
+ rlogin_init,
+ rlogin_free,
+ rlogin_reconfig,
+ rlogin_send,
+ rlogin_sendbuffer,
+ rlogin_size,
+ rlogin_special,
+ rlogin_get_specials,
+ rlogin_connected,
+ rlogin_exitcode,
+ rlogin_sendok,
+ rlogin_ldisc,
+ rlogin_provide_ldisc,
+ rlogin_provide_logctx,
+ rlogin_unthrottle,
+ rlogin_cfg_info,
+ "rlogin",
+ PROT_RLOGIN,
+ 513
+};
--- /dev/null
+/*
+ * sercfg.c - the serial-port specific parts of the PuTTY
+ * configuration box. Centralised as cross-platform code because
+ * more than one platform will want to use it, but not part of the
+ * main configuration. The expectation is that each platform's
+ * local config function will call out to ser_setup_config_box() if
+ * it needs to set up the standard serial stuff. (Of course, it can
+ * then apply local tweaks after ser_setup_config_box() returns, if
+ * it needs to.)
+ */
+
+#include <assert.h>
+#include <stdlib.h>
+
+#include "putty.h"
+#include "dialog.h"
+#include "storage.h"
+
+static void serial_parity_handler(union control *ctrl, void *dlg,
+ void *data, int event)
+{
+ static const struct {
+ const char *name;
+ int val;
+ } parities[] = {
+ {"None", SER_PAR_NONE},
+ {"Odd", SER_PAR_ODD},
+ {"Even", SER_PAR_EVEN},
+ {"Mark", SER_PAR_MARK},
+ {"Space", SER_PAR_SPACE},
+ };
+ int mask = ctrl->listbox.context.i;
+ int i, j;
+ Config *cfg = (Config *)data;
+
+ if (event == EVENT_REFRESH) {
+ int oldparity = cfg->serparity;/* preserve past reentrant calls */
+ 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,
+ parities[i].val);
+ }
+ for (i = j = 0; i < lenof(parities); i++) {
+ if (mask & (1 << i)) {
+ if (oldparity == parities[i].val) {
+ dlg_listbox_select(ctrl, dlg, j);
+ break;
+ }
+ j++;
+ }
+ }
+ 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 */
+ } 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;
+ }
+}
+
+static void serial_flow_handler(union control *ctrl, void *dlg,
+ void *data, int event)
+{
+ static const struct {
+ const char *name;
+ int val;
+ } flows[] = {
+ {"None", SER_FLOW_NONE},
+ {"XON/XOFF", SER_FLOW_XONXOFF},
+ {"RTS/CTS", SER_FLOW_RTSCTS},
+ {"DSR/DTR", SER_FLOW_DSRDTR},
+ };
+ int mask = ctrl->listbox.context.i;
+ int i, j;
+ Config *cfg = (Config *)data;
+
+ if (event == EVENT_REFRESH) {
+ int oldflow = cfg->serflow; /* preserve past reentrant calls */
+ 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);
+ }
+ for (i = j = 0; i < lenof(flows); i++) {
+ if (mask & (1 << i)) {
+ if (oldflow == flows[i].val) {
+ dlg_listbox_select(ctrl, dlg, j);
+ break;
+ }
+ j++;
+ }
+ }
+ 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 */
+ } 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;
+ }
+}
+
+void ser_setup_config_box(struct controlbox *b, int midsession,
+ int parity_mask, int flow_mask)
+{
+ struct controlset *s;
+ union control *c;
+
+ if (!midsession) {
+ int i;
+ extern void config_protocolbuttons_handler(union control *, void *,
+ void *, int);
+
+ /*
+ * Add the serial back end to the protocols list at the
+ * top of the config box.
+ */
+ s = ctrl_getset(b, "Session", "hostport",
+ "Specify the destination you want to connect to");
+
+ for (i = 0; i < s->ncontrols; i++) {
+ c = s->ctrls[i];
+ if (c->generic.type == CTRL_RADIO &&
+ c->generic.handler == config_protocolbuttons_handler) {
+ c->radio.nbuttons++;
+ c->radio.ncolumns++;
+ c->radio.buttons =
+ sresize(c->radio.buttons, c->radio.nbuttons, char *);
+ c->radio.buttons[c->radio.nbuttons-1] =
+ dupstr("Serial");
+ c->radio.buttondata =
+ sresize(c->radio.buttondata, c->radio.nbuttons, intorptr);
+ c->radio.buttondata[c->radio.nbuttons-1] = I(PROT_SERIAL);
+ if (c->radio.shortcuts) {
+ c->radio.shortcuts =
+ sresize(c->radio.shortcuts, c->radio.nbuttons, char);
+ c->radio.shortcuts[c->radio.nbuttons-1] = 'r';
+ }
+ }
+ }
+ }
+
+ /*
+ * Entirely new Connection/Serial panel for serial port
+ * configuration.
+ */
+ ctrl_settitle(b, "Connection/Serial",
+ "Options controlling local serial lines");
+
+ if (!midsession) {
+ /*
+ * We don't permit switching to a different serial port in
+ * midflight, although we do allow all other
+ * reconfiguration.
+ */
+ 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)));
+ }
+
+ 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));
+ ctrl_editbox(s, "Data bits", 'b', 40,
+ HELPCTX(serial_databits),
+ dlg_stdeditbox_handler,I(offsetof(Config,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));
+ 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));
+}
--- /dev/null
+/*
+ * settings.c: read and write saved sessions. (platform-independent)
+ */
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "putty.h"
+#include "storage.h"
+
+/* The cipher order given here is the default order. */
+static const struct keyvalwhere ciphernames[] = {
+ { "aes", CIPHER_AES, -1, -1 },
+ { "blowfish", CIPHER_BLOWFISH, -1, -1 },
+ { "3des", CIPHER_3DES, -1, -1 },
+ { "WARN", CIPHER_WARN, -1, -1 },
+ { "arcfour", CIPHER_ARCFOUR, -1, -1 },
+ { "des", CIPHER_DES, -1, -1 }
+};
+
+static const struct keyvalwhere kexnames[] = {
+ { "dh-gex-sha1", KEX_DHGEX, -1, -1 },
+ { "dh-group14-sha1", KEX_DHGROUP14, -1, -1 },
+ { "dh-group1-sha1", KEX_DHGROUP1, -1, -1 },
+ { "rsa", KEX_RSA, KEX_WARN, -1 },
+ { "WARN", KEX_WARN, -1, -1 }
+};
+
+/*
+ * All the terminal modes that we know about for the "TerminalModes"
+ * setting. (Also used by config.c for the drop-down list.)
+ * This is currently precisely the same as the set in ssh.c, but could
+ * in principle differ if other backends started to support tty modes
+ * (e.g., the pty backend).
+ */
+const char *const ttymodes[] = {
+ "INTR", "QUIT", "ERASE", "KILL", "EOF",
+ "EOL", "EOL2", "START", "STOP", "SUSP",
+ "DSUSP", "REPRINT", "WERASE", "LNEXT", "FLUSH",
+ "SWTCH", "STATUS", "DISCARD", "IGNPAR", "PARMRK",
+ "INPCK", "ISTRIP", "INLCR", "IGNCR", "ICRNL",
+ "IUCLC", "IXON", "IXANY", "IXOFF", "IMAXBEL",
+ "ISIG", "ICANON", "XCASE", "ECHO", "ECHOE",
+ "ECHOK", "ECHONL", "NOFLSH", "TOSTOP", "IEXTEN",
+ "ECHOCTL", "ECHOKE", "PENDIN", "OPOST", "OLCUC",
+ "ONLCR", "OCRNL", "ONOCR", "ONLRET", "CS7",
+ "CS8", "PARENB", "PARODD", NULL
+};
+
+/*
+ * Convenience functions to access the backends[] array
+ * (which is only present in tools that manage settings).
+ */
+
+Backend *backend_from_name(const char *name)
+{
+ Backend **p;
+ for (p = backends; *p != NULL; p++)
+ if (!strcmp((*p)->name, name))
+ return *p;
+ return NULL;
+}
+
+Backend *backend_from_proto(int proto)
+{
+ Backend **p;
+ for (p = backends; *p != NULL; p++)
+ 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');
+}
+
+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';
+ }
+}
+
+/*
+ * gppfont and gppfile cannot have local defaults, since the very
+ * format of a Filename or Font 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 gppi(void *handle, char *name, int def, int *i)
+{
+ def = platform_default_i(name, def);
+ *i = read_setting_i(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)
+{
+ char *buf = snewn(2*len, char), *p, *q;
+ gpps(handle, name, def, buf, 2*len);
+ p = buf;
+ q = val;
+ while (*p) {
+ while (*p && *p != ',') {
+ int c = *p++;
+ if (c == '=')
+ c = '\t';
+ if (c == '\\')
+ c = *p++;
+ *q++ = c;
+ }
+ if (*p == ',')
+ p++;
+ *q++ = '\0';
+ }
+ *q = '\0';
+ sfree(buf);
+}
+
+/*
+ * Write a set of name/value pairs in the above format.
+ */
+static void wmap(void *handle, char const *key, char const *value, int len)
+{
+ char *buf = snewn(2*len, char), *p;
+ const char *q;
+ p = buf;
+ q = value;
+ while (*q) {
+ while (*q) {
+ int c = *q++;
+ if (c == '=' || c == ',' || c == '\\')
+ *p++ = '\\';
+ if (c == '\t')
+ c = '=';
+ *p++ = c;
+ }
+ *p++ = ',';
+ q++;
+ }
+ *p = '\0';
+ write_setting_s(handle, key, buf);
+ sfree(buf);
+}
+
+static int key2val(const struct keyvalwhere *mapping,
+ int nmaps, char *key)
+{
+ int i;
+ for (i = 0; i < nmaps; i++)
+ if (!strcmp(mapping[i].s, key)) return mapping[i].v;
+ return -1;
+}
+
+static const char *val2key(const struct keyvalwhere *mapping,
+ int nmaps, int val)
+{
+ int i;
+ for (i = 0; i < nmaps; i++)
+ if (mapping[i].v == val) return mapping[i].s;
+ return NULL;
+}
+
+/*
+ * Helper function to parse a comma-separated list of strings into
+ * a preference list array of values. Any missing values are added
+ * 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)
+{
+ char commalist[256];
+ 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));
+
+ /*
+ * Go through that list and convert it into values.
+ */
+ n = 0;
+ p = commalist;
+ while (1) {
+ while (*p && *p == ',') p++;
+ if (!*p)
+ break; /* no more words */
+
+ q = p;
+ while (*p && *p != ',') p++;
+ if (*p) *p++ = '\0';
+
+ v = key2val(mapping, nvals, q);
+ if (v != -1 && !(seen & (1 << v))) {
+ seen |= (1 << v);
+ array[n++] = v;
+ }
+ }
+
+ /*
+ * 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
+ * positions depend on them.
+ */
+ while (n < nvals) {
+ for (i = 0; i < nvals; i++) {
+ assert(mapping[i].v < 32);
+
+ if (!(seen & (1 << mapping[i].v))) {
+ /*
+ * This element needs adding. But can we add it yet?
+ */
+ if (mapping[i].vrel != -1 && !(seen & (1 << mapping[i].vrel)))
+ continue; /* nope */
+
+ /*
+ * OK, we can work out where to add this element, so
+ * do so.
+ */
+ 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)
+ break;
+ assert(j < n); /* implied by (seen & (1<<vrel)) */
+ pos = (mapping[i].where < 0 ? j : j+1);
+ }
+
+ /*
+ * And add it.
+ */
+ for (j = n-1; j >= pos; j--)
+ array[j+1] = array[j];
+ array[pos] = mapping[i].v;
+ n++;
+ }
+ }
+ }
+}
+
+/*
+ * Write out a preference list.
+ */
+static void wprefs(void *sesskey, char *name,
+ const struct keyvalwhere *mapping, int nvals,
+ int *array)
+{
+ char *buf, *p;
+ int i, maxlen;
+
+ for (maxlen = i = 0; i < nvals; i++) {
+ const char *s = val2key(mapping, nvals, array[i]);
+ if (s) {
+ maxlen += 1 + strlen(s);
+ }
+ }
+
+ buf = snewn(maxlen, char);
+ p = buf;
+
+ for (i = 0; i < nvals; i++) {
+ const char *s = val2key(mapping, nvals, array[i]);
+ if (s) {
+ p += sprintf(p, "%s%s", (p > buf ? "," : ""), s);
+ }
+ }
+
+ assert(p - buf == maxlen - 1); /* maxlen counted the NUL */
+
+ write_setting_s(sesskey, name, buf);
+
+ sfree(buf);
+}
+
+char *save_settings(char *section, Config * cfg)
+{
+ void *sesskey;
+ char *errmsg;
+
+ sesskey = open_settings_w(section, &errmsg);
+ if (!sesskey)
+ return errmsg;
+ save_open_settings(sesskey, cfg);
+ close_settings_w(sesskey);
+ return NULL;
+}
+
+void save_open_settings(void *sesskey, Config *cfg)
+{
+ 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);
+ 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);
+ 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]);
+ }
+ 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)
+{
+ void *sesskey;
+
+ sesskey = open_settings_r(section);
+ load_open_settings(sesskey, cfg);
+ close_settings_r(sesskey);
+
+ if (cfg_launchable(cfg))
+ add_session_to_jumplist(section);
+}
+
+void load_open_settings(void *sesskey, Config *cfg)
+{
+ 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;
+ {
+ const Backend *b = backend_from_name(prot);
+ if (b) {
+ cfg->protocol = b->protocol;
+ gppi(sesskey, "PortNumber", default_port, &cfg->port);
+ }
+ }
+
+ /* Address family selection */
+ gppi(sesskey, "AddressFamily", ADDRTYPE_UNSPEC, &cfg->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);
+ {
+ /* 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));
+ {
+ /* 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);
+ }
+
+ /* 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);
+ {
+ /* 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;
+ 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);
+#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);
+ 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);
+ {
+ /* Backward compatibility */
+ int no_remote_qtitle;
+ gppi(sesskey, "NoRemoteQTitle", 1, &no_remote_qtitle);
+ /* 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);
+
+ 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];
+ 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);
+ 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",
+ "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,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,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;
+ int j;
+ sprintf(buf, "Wordness%d", i);
+ gpps(sesskey, buf, defaults[i / 32], buf2, sizeof(buf2));
+ 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);
+ }
+ }
+ /*
+ * 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;
+ {
+ 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);
+}
+
+static int sessioncmp(const void *av, const void *bv)
+{
+ const char *a = *(const char *const *) av;
+ const char *b = *(const char *const *) bv;
+
+ /*
+ * Alphabetical order, except that "Default Settings" is a
+ * special case and comes first.
+ */
+ if (!strcmp(a, "Default Settings"))
+ return -1; /* a comes first */
+ if (!strcmp(b, "Default Settings"))
+ return +1; /* b comes first */
+ /*
+ * FIXME: perhaps we should ignore the first & in determining
+ * sort order.
+ */
+ return strcmp(a, b); /* otherwise, compare normally */
+}
+
+void get_sesslist(struct sesslist *list, int allocate)
+{
+ char otherbuf[2048];
+ int buflen, bufsize, i;
+ char *p, *ret;
+ void *handle;
+
+ if (allocate) {
+
+ buflen = bufsize = 0;
+ list->buffer = NULL;
+ if ((handle = enum_settings_start()) != NULL) {
+ do {
+ ret = enum_settings_next(handle, otherbuf, sizeof(otherbuf));
+ if (ret) {
+ int len = strlen(otherbuf) + 1;
+ if (bufsize < buflen + len) {
+ bufsize = buflen + len + 2048;
+ list->buffer = sresize(list->buffer, bufsize, char);
+ }
+ strcpy(list->buffer + buflen, otherbuf);
+ buflen += strlen(list->buffer + buflen) + 1;
+ }
+ } while (ret);
+ enum_settings_finish(handle);
+ }
+ list->buffer = sresize(list->buffer, buflen + 1, char);
+ list->buffer[buflen] = '\0';
+
+ /*
+ * Now set up the list of sessions. Note that "Default
+ * Settings" must always be claimed to exist, even if it
+ * doesn't really.
+ */
+
+ p = list->buffer;
+ list->nsessions = 1; /* "Default Settings" counts as one */
+ while (*p) {
+ if (strcmp(p, "Default Settings"))
+ list->nsessions++;
+ while (*p)
+ p++;
+ p++;
+ }
+
+ list->sessions = snewn(list->nsessions + 1, char *);
+ list->sessions[0] = "Default Settings";
+ p = list->buffer;
+ i = 1;
+ while (*p) {
+ if (strcmp(p, "Default Settings"))
+ list->sessions[i++] = p;
+ while (*p)
+ p++;
+ p++;
+ }
+
+ qsort(list->sessions, i, sizeof(char *), sessioncmp);
+ } else {
+ sfree(list->buffer);
+ sfree(list->sessions);
+ list->buffer = NULL;
+ list->sessions = NULL;
+ }
+}
--- /dev/null
+/*
+ * sftp.c: SFTP generic client code.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <limits.h>
+
+#include "misc.h"
+#include "int64.h"
+#include "tree234.h"
+#include "sftp.h"
+
+struct sftp_packet {
+ char *data;
+ unsigned length, maxlen;
+ unsigned savedpos;
+ int type;
+};
+
+static const char *fxp_error_message;
+static int fxp_errtype;
+
+static void fxp_internal_error(char *msg);
+
+/* ----------------------------------------------------------------------
+ * SFTP packet construction functions.
+ */
+static void sftp_pkt_ensure(struct sftp_packet *pkt, int length)
+{
+ if ((int)pkt->maxlen < length) {
+ pkt->maxlen = length + 256;
+ pkt->data = sresize(pkt->data, pkt->maxlen, char);
+ }
+}
+static void sftp_pkt_adddata(struct sftp_packet *pkt, void *data, int len)
+{
+ pkt->length += len;
+ sftp_pkt_ensure(pkt, pkt->length);
+ 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 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_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);
+ sftp_pkt_adddata(pkt, x, 8);
+}
+static void sftp_pkt_addstring_start(struct sftp_packet *pkt)
+{
+ sftp_pkt_adduint32(pkt, 0);
+ pkt->savedpos = pkt->length;
+}
+static void sftp_pkt_addstring_str(struct sftp_packet *pkt, char *data)
+{
+ sftp_pkt_adddata(pkt, data, strlen(data));
+ PUT_32BIT(pkt->data + pkt->savedpos - 4, pkt->length - pkt->savedpos);
+}
+static void sftp_pkt_addstring_data(struct sftp_packet *pkt,
+ char *data, int len)
+{
+ sftp_pkt_adddata(pkt, data, len);
+ PUT_32BIT(pkt->data + pkt->savedpos - 4, pkt->length - pkt->savedpos);
+}
+static void sftp_pkt_addstring(struct sftp_packet *pkt, char *data)
+{
+ sftp_pkt_addstring_start(pkt);
+ sftp_pkt_addstring_str(pkt, data);
+}
+static void sftp_pkt_addattrs(struct sftp_packet *pkt, struct fxp_attrs attrs)
+{
+ sftp_pkt_adduint32(pkt, attrs.flags);
+ if (attrs.flags & SSH_FILEXFER_ATTR_SIZE) {
+ sftp_pkt_adduint32(pkt, attrs.size.hi);
+ sftp_pkt_adduint32(pkt, attrs.size.lo);
+ }
+ if (attrs.flags & SSH_FILEXFER_ATTR_UIDGID) {
+ sftp_pkt_adduint32(pkt, attrs.uid);
+ sftp_pkt_adduint32(pkt, attrs.gid);
+ }
+ if (attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS) {
+ sftp_pkt_adduint32(pkt, attrs.permissions);
+ }
+ if (attrs.flags & SSH_FILEXFER_ATTR_ACMODTIME) {
+ sftp_pkt_adduint32(pkt, attrs.atime);
+ sftp_pkt_adduint32(pkt, attrs.mtime);
+ }
+ if (attrs.flags & SSH_FILEXFER_ATTR_EXTENDED) {
+ /*
+ * We currently don't support sending any extended
+ * attributes.
+ */
+ }
+}
+
+/* ----------------------------------------------------------------------
+ * SFTP packet decode functions.
+ */
+
+static int sftp_pkt_getbyte(struct sftp_packet *pkt, unsigned char *ret)
+{
+ if (pkt->length - pkt->savedpos < 1)
+ return 0;
+ *ret = (unsigned char) pkt->data[pkt->savedpos];
+ pkt->savedpos++;
+ return 1;
+}
+static int sftp_pkt_getuint32(struct sftp_packet *pkt, unsigned long *ret)
+{
+ if (pkt->length - pkt->savedpos < 4)
+ return 0;
+ *ret = GET_32BIT(pkt->data + pkt->savedpos);
+ pkt->savedpos += 4;
+ return 1;
+}
+static int sftp_pkt_getstring(struct sftp_packet *pkt,
+ char **p, int *length)
+{
+ *p = NULL;
+ if (pkt->length - pkt->savedpos < 4)
+ return 0;
+ *length = GET_32BIT(pkt->data + pkt->savedpos);
+ pkt->savedpos += 4;
+ if ((int)(pkt->length - pkt->savedpos) < *length || *length < 0) {
+ *length = 0;
+ return 0;
+ }
+ *p = pkt->data + pkt->savedpos;
+ pkt->savedpos += *length;
+ return 1;
+}
+static int sftp_pkt_getattrs(struct sftp_packet *pkt, struct fxp_attrs *ret)
+{
+ if (!sftp_pkt_getuint32(pkt, &ret->flags))
+ return 0;
+ if (ret->flags & SSH_FILEXFER_ATTR_SIZE) {
+ unsigned long hi, lo;
+ if (!sftp_pkt_getuint32(pkt, &hi) ||
+ !sftp_pkt_getuint32(pkt, &lo))
+ return 0;
+ ret->size = uint64_make(hi, lo);
+ }
+ if (ret->flags & SSH_FILEXFER_ATTR_UIDGID) {
+ if (!sftp_pkt_getuint32(pkt, &ret->uid) ||
+ !sftp_pkt_getuint32(pkt, &ret->gid))
+ return 0;
+ }
+ if (ret->flags & SSH_FILEXFER_ATTR_PERMISSIONS) {
+ if (!sftp_pkt_getuint32(pkt, &ret->permissions))
+ return 0;
+ }
+ if (ret->flags & SSH_FILEXFER_ATTR_ACMODTIME) {
+ if (!sftp_pkt_getuint32(pkt, &ret->atime) ||
+ !sftp_pkt_getuint32(pkt, &ret->mtime))
+ return 0;
+ }
+ if (ret->flags & SSH_FILEXFER_ATTR_EXTENDED) {
+ unsigned long count;
+ if (!sftp_pkt_getuint32(pkt, &count))
+ return 0;
+ while (count--) {
+ char *str;
+ int len;
+ /*
+ * We should try to analyse these, if we ever find one
+ * we recognise.
+ */
+ if (!sftp_pkt_getstring(pkt, &str, &len) ||
+ !sftp_pkt_getstring(pkt, &str, &len))
+ return 0;
+ }
+ }
+ return 1;
+}
+static void sftp_pkt_free(struct sftp_packet *pkt)
+{
+ if (pkt->data)
+ sfree(pkt->data);
+ sfree(pkt);
+}
+
+/* ----------------------------------------------------------------------
+ * 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));
+ sftp_pkt_free(pkt);
+ return ret;
+}
+struct sftp_packet *sftp_recv(void)
+{
+ struct sftp_packet *pkt;
+ char x[4];
+ unsigned char uc;
+
+ if (!sftp_recvdata(x, 4))
+ return NULL;
+
+ pkt = snew(struct sftp_packet);
+ pkt->savedpos = 0;
+ pkt->length = pkt->maxlen = GET_32BIT(x);
+ pkt->data = snewn(pkt->length, char);
+
+ if (!sftp_recvdata(pkt->data, pkt->length)) {
+ sftp_pkt_free(pkt);
+ return NULL;
+ }
+
+ if (!sftp_pkt_getbyte(pkt, &uc)) {
+ sftp_pkt_free(pkt);
+ return NULL;
+ } else {
+ pkt->type = uc;
+ }
+
+ return pkt;
+}
+
+/* ----------------------------------------------------------------------
+ * Request ID allocation and temporary dispatch routines.
+ */
+
+#define REQUEST_ID_OFFSET 256
+
+struct sftp_request {
+ unsigned id;
+ int registered;
+ void *userdata;
+};
+
+static int sftp_reqcmp(void *av, void *bv)
+{
+ struct sftp_request *a = (struct sftp_request *)av;
+ struct sftp_request *b = (struct sftp_request *)bv;
+ if (a->id < b->id)
+ return -1;
+ if (a->id > b->id)
+ return +1;
+ return 0;
+}
+static int sftp_reqfind(void *av, void *bv)
+{
+ unsigned *a = (unsigned *) av;
+ struct sftp_request *b = (struct sftp_request *)bv;
+ if (*a < b->id)
+ return -1;
+ if (*a > b->id)
+ return +1;
+ return 0;
+}
+
+static tree234 *sftp_requests;
+
+static struct sftp_request *sftp_alloc_request(void)
+{
+ unsigned low, high, mid;
+ int tsize;
+ struct sftp_request *r;
+
+ if (sftp_requests == NULL)
+ sftp_requests = newtree234(sftp_reqcmp);
+
+ /*
+ * First-fit allocation of request IDs: always pick the lowest
+ * unused one. To do this, binary-search using the counted
+ * B-tree to find the largest ID which is in a contiguous
+ * sequence from the beginning. (Precisely everything in that
+ * sequence must have ID equal to its tree index plus
+ * REQUEST_ID_OFFSET.)
+ */
+ tsize = count234(sftp_requests);
+
+ low = -1;
+ high = tsize;
+ while (high - low > 1) {
+ mid = (high + low) / 2;
+ r = index234(sftp_requests, mid);
+ if (r->id == mid + REQUEST_ID_OFFSET)
+ low = mid; /* this one is fine */
+ else
+ high = mid; /* this one is past it */
+ }
+ /*
+ * Now low points to either -1, or the tree index of the
+ * largest ID in the initial sequence.
+ */
+ {
+ unsigned i = low + 1 + REQUEST_ID_OFFSET;
+ assert(NULL == find234(sftp_requests, &i, sftp_reqfind));
+ }
+
+ /*
+ * So the request ID we need to create is
+ * low + 1 + REQUEST_ID_OFFSET.
+ */
+ r = snew(struct sftp_request);
+ r->id = low + 1 + REQUEST_ID_OFFSET;
+ r->registered = 0;
+ r->userdata = NULL;
+ add234(sftp_requests, r);
+ return r;
+}
+
+void sftp_cleanup_request(void)
+{
+ if (sftp_requests != NULL) {
+ freetree234(sftp_requests);
+ sftp_requests = NULL;
+ }
+}
+
+void sftp_register(struct sftp_request *req)
+{
+ req->registered = 1;
+}
+
+struct sftp_request *sftp_find_request(struct sftp_packet *pktin)
+{
+ unsigned long id;
+ struct sftp_request *req;
+
+ if (!pktin) {
+ fxp_internal_error("did not receive a valid SFTP packet\n");
+ return NULL;
+ }
+
+ if (!sftp_pkt_getuint32(pktin, &id)) {
+ fxp_internal_error("did not receive a valid SFTP packet\n");
+ return NULL;
+ }
+ 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);
+
+ return req;
+}
+
+/* ----------------------------------------------------------------------
+ * String handling routines.
+ */
+
+static char *mkstr(char *s, int len)
+{
+ char *p = snewn(len + 1, char);
+ memcpy(p, s, len);
+ p[len] = '\0';
+ return p;
+}
+
+/* ----------------------------------------------------------------------
+ * SFTP primitives.
+ */
+
+/*
+ * Deal with (and free) an FXP_STATUS packet. Return 1 if
+ * SSH_FX_OK, 0 if SSH_FX_EOF, and -1 for anything else (error).
+ * Also place the status into fxp_errtype.
+ */
+static int fxp_got_status(struct sftp_packet *pktin)
+{
+ static const char *const messages[] = {
+ /* SSH_FX_OK. The only time we will display a _message_ for this
+ * is if we were expecting something other than FXP_STATUS on
+ * success, so this is actually an error message! */
+ "unexpected OK response",
+ "end of file",
+ "no such file or directory",
+ "permission denied",
+ "failure",
+ "bad message",
+ "no connection",
+ "connection lost",
+ "operation unsupported",
+ };
+
+ if (pktin->type != SSH_FXP_STATUS) {
+ fxp_error_message = "expected FXP_STATUS packet";
+ fxp_errtype = -1;
+ } else {
+ unsigned long ul;
+ if (!sftp_pkt_getuint32(pktin, &ul)) {
+ fxp_error_message = "malformed FXP_STATUS packet";
+ fxp_errtype = -1;
+ } else {
+ fxp_errtype = ul;
+ if (fxp_errtype < 0 ||
+ fxp_errtype >= sizeof(messages) / sizeof(*messages))
+ fxp_error_message = "unknown error code";
+ else
+ fxp_error_message = messages[fxp_errtype];
+ }
+ }
+
+ if (fxp_errtype == SSH_FX_OK)
+ return 1;
+ else if (fxp_errtype == SSH_FX_EOF)
+ return 0;
+ else
+ return -1;
+}
+
+static void fxp_internal_error(char *msg)
+{
+ fxp_error_message = msg;
+ fxp_errtype = -1;
+}
+
+const char *fxp_error(void)
+{
+ return fxp_error_message;
+}
+
+int fxp_error_type(void)
+{
+ return fxp_errtype;
+}
+
+/*
+ * Perform exchange of init/version packets. Return 0 on failure.
+ */
+int fxp_init(void)
+{
+ struct sftp_packet *pktout, *pktin;
+ unsigned long remotever;
+
+ pktout = sftp_pkt_init(SSH_FXP_INIT);
+ sftp_pkt_adduint32(pktout, SFTP_PROTO_VERSION);
+ sftp_send(pktout);
+
+ pktin = sftp_recv();
+ if (!pktin) {
+ fxp_internal_error("could not connect");
+ return 0;
+ }
+ if (pktin->type != SSH_FXP_VERSION) {
+ fxp_internal_error("did not receive FXP_VERSION");
+ sftp_pkt_free(pktin);
+ return 0;
+ }
+ if (!sftp_pkt_getuint32(pktin, &remotever)) {
+ fxp_internal_error("malformed FXP_VERSION packet");
+ sftp_pkt_free(pktin);
+ return 0;
+ }
+ if (remotever > SFTP_PROTO_VERSION) {
+ fxp_internal_error
+ ("remote protocol is more advanced than we support");
+ sftp_pkt_free(pktin);
+ return 0;
+ }
+ /*
+ * In principle, this packet might also contain extension-
+ * string pairs. We should work through them and look for any
+ * we recognise. In practice we don't currently do so because
+ * we know we don't recognise _any_.
+ */
+ sftp_pkt_free(pktin);
+
+ return 1;
+}
+
+/*
+ * Canonify a pathname.
+ */
+struct sftp_request *fxp_realpath_send(char *path)
+{
+ struct sftp_request *req = sftp_alloc_request();
+ struct sftp_packet *pktout;
+
+ pktout = sftp_pkt_init(SSH_FXP_REALPATH);
+ sftp_pkt_adduint32(pktout, req->id);
+ sftp_pkt_addstring_start(pktout);
+ sftp_pkt_addstring_str(pktout, path);
+ sftp_send(pktout);
+
+ return req;
+}
+
+char *fxp_realpath_recv(struct sftp_packet *pktin, struct sftp_request *req)
+{
+ sfree(req);
+
+ if (pktin->type == SSH_FXP_NAME) {
+ unsigned long count;
+ char *path;
+ int len;
+
+ if (!sftp_pkt_getuint32(pktin, &count) || count != 1) {
+ fxp_internal_error("REALPATH did not return name count of 1\n");
+ sftp_pkt_free(pktin);
+ return NULL;
+ }
+ if (!sftp_pkt_getstring(pktin, &path, &len)) {
+ fxp_internal_error("REALPATH returned malformed FXP_NAME\n");
+ sftp_pkt_free(pktin);
+ return NULL;
+ }
+ path = mkstr(path, len);
+ sftp_pkt_free(pktin);
+ return path;
+ } else {
+ fxp_got_status(pktin);
+ sftp_pkt_free(pktin);
+ return NULL;
+ }
+}
+
+/*
+ * Open a file.
+ */
+struct sftp_request *fxp_open_send(char *path, int type)
+{
+ 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 */
+ sftp_send(pktout);
+
+ return req;
+}
+
+struct fxp_handle *fxp_open_recv(struct sftp_packet *pktin,
+ struct sftp_request *req)
+{
+ sfree(req);
+
+ if (pktin->type == SSH_FXP_HANDLE) {
+ char *hstring;
+ struct fxp_handle *handle;
+ int len;
+
+ if (!sftp_pkt_getstring(pktin, &hstring, &len)) {
+ fxp_internal_error("OPEN returned malformed FXP_HANDLE\n");
+ sftp_pkt_free(pktin);
+ return NULL;
+ }
+ handle = snew(struct fxp_handle);
+ handle->hstring = mkstr(hstring, len);
+ handle->hlen = len;
+ sftp_pkt_free(pktin);
+ return handle;
+ } else {
+ fxp_got_status(pktin);
+ sftp_pkt_free(pktin);
+ return NULL;
+ }
+}
+
+/*
+ * Open a directory.
+ */
+struct sftp_request *fxp_opendir_send(char *path)
+{
+ struct sftp_request *req = sftp_alloc_request();
+ struct sftp_packet *pktout;
+
+ pktout = sftp_pkt_init(SSH_FXP_OPENDIR);
+ sftp_pkt_adduint32(pktout, req->id);
+ sftp_pkt_addstring(pktout, path);
+ sftp_send(pktout);
+
+ return req;
+}
+
+struct fxp_handle *fxp_opendir_recv(struct sftp_packet *pktin,
+ struct sftp_request *req)
+{
+ sfree(req);
+ if (pktin->type == SSH_FXP_HANDLE) {
+ char *hstring;
+ struct fxp_handle *handle;
+ int len;
+
+ if (!sftp_pkt_getstring(pktin, &hstring, &len)) {
+ fxp_internal_error("OPENDIR returned malformed FXP_HANDLE\n");
+ sftp_pkt_free(pktin);
+ return NULL;
+ }
+ handle = snew(struct fxp_handle);
+ handle->hstring = mkstr(hstring, len);
+ handle->hlen = len;
+ sftp_pkt_free(pktin);
+ return handle;
+ } else {
+ fxp_got_status(pktin);
+ sftp_pkt_free(pktin);
+ return NULL;
+ }
+}
+
+/*
+ * Close a file/dir.
+ */
+struct sftp_request *fxp_close_send(struct fxp_handle *handle)
+{
+ struct sftp_request *req = sftp_alloc_request();
+ struct sftp_packet *pktout;
+
+ pktout = sftp_pkt_init(SSH_FXP_CLOSE);
+ sftp_pkt_adduint32(pktout, req->id);
+ sftp_pkt_addstring_start(pktout);
+ sftp_pkt_addstring_data(pktout, handle->hstring, handle->hlen);
+ sftp_send(pktout);
+
+ sfree(handle->hstring);
+ sfree(handle);
+
+ return req;
+}
+
+void fxp_close_recv(struct sftp_packet *pktin, struct sftp_request *req)
+{
+ sfree(req);
+ fxp_got_status(pktin);
+ sftp_pkt_free(pktin);
+}
+
+struct sftp_request *fxp_mkdir_send(char *path)
+{
+ struct sftp_request *req = sftp_alloc_request();
+ struct sftp_packet *pktout;
+
+ pktout = sftp_pkt_init(SSH_FXP_MKDIR);
+ sftp_pkt_adduint32(pktout, req->id);
+ sftp_pkt_addstring(pktout, path);
+ sftp_pkt_adduint32(pktout, 0); /* (FIXME) empty ATTRS structure */
+ sftp_send(pktout);
+
+ return req;
+}
+
+int fxp_mkdir_recv(struct sftp_packet *pktin, struct sftp_request *req)
+{
+ int id;
+ sfree(req);
+ id = fxp_got_status(pktin);
+ sftp_pkt_free(pktin);
+ if (id != 1) {
+ return 0;
+ }
+ return 1;
+}
+
+struct sftp_request *fxp_rmdir_send(char *path)
+{
+ struct sftp_request *req = sftp_alloc_request();
+ struct sftp_packet *pktout;
+
+ pktout = sftp_pkt_init(SSH_FXP_RMDIR);
+ sftp_pkt_adduint32(pktout, req->id);
+ sftp_pkt_addstring(pktout, path);
+ sftp_send(pktout);
+
+ return req;
+}
+
+int fxp_rmdir_recv(struct sftp_packet *pktin, struct sftp_request *req)
+{
+ int id;
+ sfree(req);
+ id = fxp_got_status(pktin);
+ sftp_pkt_free(pktin);
+ if (id != 1) {
+ return 0;
+ }
+ return 1;
+}
+
+struct sftp_request *fxp_remove_send(char *fname)
+{
+ struct sftp_request *req = sftp_alloc_request();
+ struct sftp_packet *pktout;
+
+ pktout = sftp_pkt_init(SSH_FXP_REMOVE);
+ sftp_pkt_adduint32(pktout, req->id);
+ sftp_pkt_addstring(pktout, fname);
+ sftp_send(pktout);
+
+ return req;
+}
+
+int fxp_remove_recv(struct sftp_packet *pktin, struct sftp_request *req)
+{
+ int id;
+ sfree(req);
+ id = fxp_got_status(pktin);
+ sftp_pkt_free(pktin);
+ if (id != 1) {
+ return 0;
+ }
+ return 1;
+}
+
+struct sftp_request *fxp_rename_send(char *srcfname, char *dstfname)
+{
+ struct sftp_request *req = sftp_alloc_request();
+ struct sftp_packet *pktout;
+
+ pktout = sftp_pkt_init(SSH_FXP_RENAME);
+ sftp_pkt_adduint32(pktout, req->id);
+ sftp_pkt_addstring(pktout, srcfname);
+ sftp_pkt_addstring(pktout, dstfname);
+ sftp_send(pktout);
+
+ return req;
+}
+
+int fxp_rename_recv(struct sftp_packet *pktin, struct sftp_request *req)
+{
+ int id;
+ sfree(req);
+ id = fxp_got_status(pktin);
+ sftp_pkt_free(pktin);
+ if (id != 1) {
+ return 0;
+ }
+ return 1;
+}
+
+/*
+ * Retrieve the attributes of a file. We have fxp_stat which works
+ * on filenames, and fxp_fstat which works on open file handles.
+ */
+struct sftp_request *fxp_stat_send(char *fname)
+{
+ struct sftp_request *req = sftp_alloc_request();
+ struct sftp_packet *pktout;
+
+ pktout = sftp_pkt_init(SSH_FXP_STAT);
+ sftp_pkt_adduint32(pktout, req->id);
+ sftp_pkt_addstring(pktout, fname);
+ sftp_send(pktout);
+
+ return req;
+}
+
+int fxp_stat_recv(struct sftp_packet *pktin, struct sftp_request *req,
+ struct fxp_attrs *attrs)
+{
+ sfree(req);
+ if (pktin->type == SSH_FXP_ATTRS) {
+ if (!sftp_pkt_getattrs(pktin, attrs)) {
+ fxp_internal_error("malformed SSH_FXP_ATTRS packet");
+ sftp_pkt_free(pktin);
+ return 0;
+ }
+ sftp_pkt_free(pktin);
+ return 1;
+ } else {
+ fxp_got_status(pktin);
+ sftp_pkt_free(pktin);
+ return 0;
+ }
+}
+
+struct sftp_request *fxp_fstat_send(struct fxp_handle *handle)
+{
+ struct sftp_request *req = sftp_alloc_request();
+ struct sftp_packet *pktout;
+
+ pktout = sftp_pkt_init(SSH_FXP_FSTAT);
+ sftp_pkt_adduint32(pktout, req->id);
+ sftp_pkt_addstring_start(pktout);
+ sftp_pkt_addstring_data(pktout, handle->hstring, handle->hlen);
+ sftp_send(pktout);
+
+ return req;
+}
+
+int fxp_fstat_recv(struct sftp_packet *pktin, struct sftp_request *req,
+ struct fxp_attrs *attrs)
+{
+ sfree(req);
+ if (pktin->type == SSH_FXP_ATTRS) {
+ if (!sftp_pkt_getattrs(pktin, attrs)) {
+ fxp_internal_error("malformed SSH_FXP_ATTRS packet");
+ sftp_pkt_free(pktin);
+ return 0;
+ }
+ sftp_pkt_free(pktin);
+ return 1;
+ } else {
+ fxp_got_status(pktin);
+ sftp_pkt_free(pktin);
+ return 0;
+ }
+}
+
+/*
+ * Set the attributes of a file.
+ */
+struct sftp_request *fxp_setstat_send(char *fname, struct fxp_attrs attrs)
+{
+ struct sftp_request *req = sftp_alloc_request();
+ struct sftp_packet *pktout;
+
+ pktout = sftp_pkt_init(SSH_FXP_SETSTAT);
+ sftp_pkt_adduint32(pktout, req->id);
+ sftp_pkt_addstring(pktout, fname);
+ sftp_pkt_addattrs(pktout, attrs);
+ sftp_send(pktout);
+
+ return req;
+}
+
+int fxp_setstat_recv(struct sftp_packet *pktin, struct sftp_request *req)
+{
+ int id;
+ sfree(req);
+ id = fxp_got_status(pktin);
+ sftp_pkt_free(pktin);
+ if (id != 1) {
+ return 0;
+ }
+ return 1;
+}
+
+struct sftp_request *fxp_fsetstat_send(struct fxp_handle *handle,
+ struct fxp_attrs attrs)
+{
+ struct sftp_request *req = sftp_alloc_request();
+ struct sftp_packet *pktout;
+
+ pktout = sftp_pkt_init(SSH_FXP_FSETSTAT);
+ sftp_pkt_adduint32(pktout, req->id);
+ sftp_pkt_addstring_start(pktout);
+ sftp_pkt_addstring_data(pktout, handle->hstring, handle->hlen);
+ sftp_pkt_addattrs(pktout, attrs);
+ sftp_send(pktout);
+
+ return req;
+}
+
+int fxp_fsetstat_recv(struct sftp_packet *pktin, struct sftp_request *req)
+{
+ int id;
+ sfree(req);
+ id = fxp_got_status(pktin);
+ sftp_pkt_free(pktin);
+ if (id != 1) {
+ return 0;
+ }
+ return 1;
+}
+
+/*
+ * Read from a file. Returns the number of bytes read, or -1 on an
+ * error, or possibly 0 if EOF. (I'm not entirely sure whether it
+ * will return 0 on EOF, or return -1 and store SSH_FX_EOF in the
+ * error indicator. It might even depend on the SFTP server.)
+ */
+struct sftp_request *fxp_read_send(struct fxp_handle *handle,
+ uint64 offset, int len)
+{
+ struct sftp_request *req = sftp_alloc_request();
+ struct sftp_packet *pktout;
+
+ pktout = sftp_pkt_init(SSH_FXP_READ);
+ sftp_pkt_adduint32(pktout, req->id);
+ sftp_pkt_addstring_start(pktout);
+ sftp_pkt_addstring_data(pktout, handle->hstring, handle->hlen);
+ sftp_pkt_adduint64(pktout, offset);
+ sftp_pkt_adduint32(pktout, len);
+ sftp_send(pktout);
+
+ return req;
+}
+
+int fxp_read_recv(struct sftp_packet *pktin, struct sftp_request *req,
+ char *buffer, int len)
+{
+ sfree(req);
+ if (pktin->type == SSH_FXP_DATA) {
+ char *str;
+ int rlen;
+
+ if (!sftp_pkt_getstring(pktin, &str, &rlen)) {
+ fxp_internal_error("READ returned malformed SSH_FXP_DATA packet");
+ sftp_pkt_free(pktin);
+ return -1;
+ }
+
+ if (rlen > len || rlen < 0) {
+ fxp_internal_error("READ returned more bytes than requested");
+ sftp_pkt_free(pktin);
+ return -1;
+ }
+
+ memcpy(buffer, str, rlen);
+ sftp_pkt_free(pktin);
+ return rlen;
+ } else {
+ fxp_got_status(pktin);
+ sftp_pkt_free(pktin);
+ return -1;
+ }
+}
+
+/*
+ * Read from a directory.
+ */
+struct sftp_request *fxp_readdir_send(struct fxp_handle *handle)
+{
+ struct sftp_request *req = sftp_alloc_request();
+ struct sftp_packet *pktout;
+
+ pktout = sftp_pkt_init(SSH_FXP_READDIR);
+ sftp_pkt_adduint32(pktout, req->id);
+ sftp_pkt_addstring_start(pktout);
+ sftp_pkt_addstring_data(pktout, handle->hstring, handle->hlen);
+ sftp_send(pktout);
+
+ return req;
+}
+
+struct fxp_names *fxp_readdir_recv(struct sftp_packet *pktin,
+ struct sftp_request *req)
+{
+ sfree(req);
+ if (pktin->type == SSH_FXP_NAME) {
+ struct fxp_names *ret;
+ unsigned long i;
+
+ /*
+ * Sanity-check the number of names. Minimum is obviously
+ * zero. Maximum is the remaining space in the packet
+ * divided by the very minimum length of a name, which is
+ * 12 bytes (4 for an empty filename, 4 for an empty
+ * longname, 4 for a set of attribute flags indicating that
+ * no other attributes are supplied).
+ */
+ if (!sftp_pkt_getuint32(pktin, &i) ||
+ i > (pktin->length-pktin->savedpos)/12) {
+ fxp_internal_error("malformed FXP_NAME packet");
+ sftp_pkt_free(pktin);
+ return NULL;
+ }
+
+ /*
+ * Ensure the implicit multiplication in the snewn() call
+ * doesn't suffer integer overflow and cause us to malloc
+ * too little space.
+ */
+ if (i > INT_MAX / sizeof(struct fxp_name)) {
+ fxp_internal_error("unreasonably large FXP_NAME packet");
+ sftp_pkt_free(pktin);
+ return NULL;
+ }
+
+ ret = snew(struct fxp_names);
+ ret->nnames = i;
+ ret->names = snewn(ret->nnames, struct fxp_name);
+ for (i = 0; i < (unsigned long)ret->nnames; i++) {
+ char *str1, *str2;
+ int len1, len2;
+ if (!sftp_pkt_getstring(pktin, &str1, &len1) ||
+ !sftp_pkt_getstring(pktin, &str2, &len2) ||
+ !sftp_pkt_getattrs(pktin, &ret->names[i].attrs)) {
+ fxp_internal_error("malformed FXP_NAME packet");
+ while (i--) {
+ sfree(ret->names[i].filename);
+ sfree(ret->names[i].longname);
+ }
+ sfree(ret->names);
+ sfree(ret);
+ sfree(pktin);
+ return NULL;
+ }
+ ret->names[i].filename = mkstr(str1, len1);
+ ret->names[i].longname = mkstr(str2, len2);
+ }
+ sftp_pkt_free(pktin);
+ return ret;
+ } else {
+ fxp_got_status(pktin);
+ sftp_pkt_free(pktin);
+ return NULL;
+ }
+}
+
+/*
+ * Write to a file. Returns 0 on error, 1 on OK.
+ */
+struct sftp_request *fxp_write_send(struct fxp_handle *handle,
+ char *buffer, uint64 offset, int len)
+{
+ struct sftp_request *req = sftp_alloc_request();
+ struct sftp_packet *pktout;
+
+ pktout = sftp_pkt_init(SSH_FXP_WRITE);
+ sftp_pkt_adduint32(pktout, req->id);
+ sftp_pkt_addstring_start(pktout);
+ sftp_pkt_addstring_data(pktout, handle->hstring, handle->hlen);
+ sftp_pkt_adduint64(pktout, offset);
+ sftp_pkt_addstring_start(pktout);
+ sftp_pkt_addstring_data(pktout, buffer, len);
+ sftp_send(pktout);
+
+ return req;
+}
+
+int fxp_write_recv(struct sftp_packet *pktin, struct sftp_request *req)
+{
+ sfree(req);
+ fxp_got_status(pktin);
+ sftp_pkt_free(pktin);
+ return fxp_errtype == SSH_FX_OK;
+}
+
+/*
+ * Free up an fxp_names structure.
+ */
+void fxp_free_names(struct fxp_names *names)
+{
+ int i;
+
+ for (i = 0; i < names->nnames; i++) {
+ sfree(names->names[i].filename);
+ sfree(names->names[i].longname);
+ }
+ sfree(names->names);
+ sfree(names);
+}
+
+/*
+ * Duplicate an fxp_name structure.
+ */
+struct fxp_name *fxp_dup_name(struct fxp_name *name)
+{
+ struct fxp_name *ret;
+ ret = snew(struct fxp_name);
+ ret->filename = dupstr(name->filename);
+ ret->longname = dupstr(name->longname);
+ ret->attrs = name->attrs; /* structure copy */
+ return ret;
+}
+
+/*
+ * Free up an fxp_name structure.
+ */
+void fxp_free_name(struct fxp_name *name)
+{
+ sfree(name->filename);
+ sfree(name->longname);
+ sfree(name);
+}
+
+/*
+ * Store user data in an sftp_request structure.
+ */
+void *fxp_get_userdata(struct sftp_request *req)
+{
+ return req->userdata;
+}
+
+void fxp_set_userdata(struct sftp_request *req, void *data)
+{
+ req->userdata = data;
+}
+
+/*
+ * A wrapper to go round fxp_read_* and fxp_write_*, which manages
+ * the queueing of multiple read/write requests.
+ */
+
+struct req {
+ char *buffer;
+ int len, retlen, complete;
+ uint64 offset;
+ struct req *next, *prev;
+};
+
+struct fxp_xfer {
+ uint64 offset, furthestdata, filesize;
+ int req_totalsize, req_maxsize, eof, err;
+ struct fxp_handle *fh;
+ struct req *head, *tail;
+};
+
+static struct fxp_xfer *xfer_init(struct fxp_handle *fh, uint64 offset)
+{
+ struct fxp_xfer *xfer = snew(struct fxp_xfer);
+
+ xfer->fh = fh;
+ xfer->offset = offset;
+ xfer->head = xfer->tail = NULL;
+ xfer->req_totalsize = 0;
+ xfer->req_maxsize = 1048576;
+ xfer->err = 0;
+ xfer->filesize = uint64_make(ULONG_MAX, ULONG_MAX);
+ xfer->furthestdata = uint64_make(0, 0);
+
+ return xfer;
+}
+
+int xfer_done(struct fxp_xfer *xfer)
+{
+ /*
+ * We're finished if we've seen EOF _and_ there are no
+ * outstanding requests.
+ */
+ return (xfer->eof || xfer->err) && !xfer->head;
+}
+
+void xfer_download_queue(struct fxp_xfer *xfer)
+{
+ while (xfer->req_totalsize < xfer->req_maxsize &&
+ !xfer->eof && !xfer->err) {
+ /*
+ * Queue a new read request.
+ */
+ struct req *rr;
+ struct sftp_request *req;
+
+ rr = snew(struct req);
+ rr->offset = xfer->offset;
+ rr->complete = 0;
+ if (xfer->tail) {
+ xfer->tail->next = rr;
+ rr->prev = xfer->tail;
+ } else {
+ xfer->head = rr;
+ rr->prev = NULL;
+ }
+ xfer->tail = rr;
+ rr->next = NULL;
+
+ rr->len = 32768;
+ rr->buffer = snewn(rr->len, char);
+ sftp_register(req = fxp_read_send(xfer->fh, rr->offset, rr->len));
+ fxp_set_userdata(req, rr);
+
+ xfer->offset = uint64_add32(xfer->offset, rr->len);
+ xfer->req_totalsize += rr->len;
+
+#ifdef DEBUG_DOWNLOAD
+ { char buf[40]; uint64_decimal(rr->offset, buf); printf("queueing read request %p at %s\n", rr, buf); }
+#endif
+ }
+}
+
+struct fxp_xfer *xfer_download_init(struct fxp_handle *fh, uint64 offset)
+{
+ struct fxp_xfer *xfer = xfer_init(fh, offset);
+
+ xfer->eof = FALSE;
+ xfer_download_queue(xfer);
+
+ return xfer;
+}
+
+int xfer_download_gotpkt(struct fxp_xfer *xfer, struct sftp_packet *pktin)
+{
+ struct sftp_request *rreq;
+ struct req *rr;
+
+ rreq = sftp_find_request(pktin);
+ rr = (struct req *)fxp_get_userdata(rreq);
+ if (!rr)
+ return 0; /* 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
+
+ if ((rr->retlen < 0 && fxp_error_type()==SSH_FX_EOF) || rr->retlen == 0) {
+ xfer->eof = TRUE;
+ rr->complete = -1;
+#ifdef DEBUG_DOWNLOAD
+ printf("setting eof\n");
+#endif
+ } else if (rr->retlen < 0) {
+ /* some error other than EOF; signal it back to caller */
+ xfer_set_error(xfer);
+ rr->complete = -1;
+ return -1;
+ }
+
+ rr->complete = 1;
+
+ /*
+ * Special case: if we have received fewer bytes than we
+ * actually read, we should do something. For the moment I'll
+ * just throw an ersatz FXP error to signal this; the SFTP
+ * draft I've got says that it can't happen except on special
+ * files, in which case seeking probably has very little
+ * meaning and so queueing an additional read request to fill
+ * up the gap sounds like the wrong answer. I'm not sure what I
+ * should be doing here - if it _was_ a special file, I suspect
+ * I simply shouldn't have been queueing multiple requests in
+ * the first place...
+ */
+ if (rr->retlen > 0 && uint64_compare(xfer->furthestdata, rr->offset) < 0) {
+ xfer->furthestdata = rr->offset;
+#ifdef DEBUG_DOWNLOAD
+ { char buf[40];
+ uint64_decimal(xfer->furthestdata, buf);
+ printf("setting furthestdata = %s\n", buf); }
+#endif
+ }
+
+ if (rr->retlen < rr->len) {
+ uint64 filesize = uint64_add32(rr->offset,
+ (rr->retlen < 0 ? 0 : rr->retlen));
+#ifdef DEBUG_DOWNLOAD
+ { char buf[40];
+ uint64_decimal(filesize, buf);
+ printf("short block! trying filesize = %s\n", buf); }
+#endif
+ if (uint64_compare(xfer->filesize, filesize) > 0) {
+ xfer->filesize = filesize;
+#ifdef DEBUG_DOWNLOAD
+ printf("actually changing filesize\n");
+#endif
+ }
+ }
+
+ if (uint64_compare(xfer->furthestdata, xfer->filesize) > 0) {
+ fxp_error_message = "received a short buffer from FXP_READ, but not"
+ " at EOF";
+ fxp_errtype = -1;
+ xfer_set_error(xfer);
+ return -1;
+ }
+
+ return 1;
+}
+
+void xfer_set_error(struct fxp_xfer *xfer)
+{
+ xfer->err = 1;
+}
+
+int xfer_download_data(struct fxp_xfer *xfer, void **buf, int *len)
+{
+ void *retbuf = NULL;
+ int retlen = 0;
+
+ /*
+ * Discard anything at the head of the rr queue with complete <
+ * 0; return the first thing with complete > 0.
+ */
+ while (xfer->head && xfer->head->complete && !retbuf) {
+ struct req *rr = xfer->head;
+
+ if (rr->complete > 0) {
+ retbuf = rr->buffer;
+ retlen = rr->retlen;
+#ifdef DEBUG_DOWNLOAD
+ printf("handing back data from read request %p\n", rr);
+#endif
+ }
+#ifdef DEBUG_DOWNLOAD
+ else
+ printf("skipping failed read request %p\n", rr);
+#endif
+
+ xfer->head = xfer->head->next;
+ if (xfer->head)
+ xfer->head->prev = NULL;
+ else
+ xfer->tail = NULL;
+ xfer->req_totalsize -= rr->len;
+ sfree(rr);
+ }
+
+ if (retbuf) {
+ *buf = retbuf;
+ *len = retlen;
+ return 1;
+ } else
+ return 0;
+}
+
+struct fxp_xfer *xfer_upload_init(struct fxp_handle *fh, uint64 offset)
+{
+ struct fxp_xfer *xfer = xfer_init(fh, offset);
+
+ /*
+ * We set `eof' to 1 because this will cause xfer_done() to
+ * return true iff there are no outstanding requests. During an
+ * upload, our caller will be responsible for working out
+ * whether all the data has been sent, so all it needs to know
+ * from us is whether the outstanding requests have been
+ * handled once that's done.
+ */
+ xfer->eof = 1;
+
+ return xfer;
+}
+
+int xfer_upload_ready(struct fxp_xfer *xfer)
+{
+ if (xfer->req_totalsize < xfer->req_maxsize)
+ return 1;
+ else
+ return 0;
+}
+
+void xfer_upload_data(struct fxp_xfer *xfer, char *buffer, int len)
+{
+ struct req *rr;
+ struct sftp_request *req;
+
+ rr = snew(struct req);
+ rr->offset = xfer->offset;
+ rr->complete = 0;
+ if (xfer->tail) {
+ xfer->tail->next = rr;
+ rr->prev = xfer->tail;
+ } else {
+ xfer->head = rr;
+ rr->prev = NULL;
+ }
+ xfer->tail = rr;
+ rr->next = NULL;
+
+ rr->len = len;
+ rr->buffer = NULL;
+ sftp_register(req = fxp_write_send(xfer->fh, buffer, rr->offset, len));
+ fxp_set_userdata(req, rr);
+
+ xfer->offset = uint64_add32(xfer->offset, rr->len);
+ xfer->req_totalsize += rr->len;
+
+#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
+}
+
+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);
+ rr = (struct req *)fxp_get_userdata(rreq);
+ if (!rr)
+ return 0; /* 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
+
+ /*
+ * Remove this one from the queue.
+ */
+ prev = rr->prev;
+ next = rr->next;
+ if (prev)
+ prev->next = next;
+ else
+ xfer->head = next;
+ if (next)
+ next->prev = prev;
+ else
+ xfer->tail = prev;
+ xfer->req_totalsize -= rr->len;
+ sfree(rr);
+
+ if (!ret)
+ return -1;
+
+ return 1;
+}
+
+void xfer_cleanup(struct fxp_xfer *xfer)
+{
+ struct req *rr;
+ while (xfer->head) {
+ rr = xfer->head;
+ xfer->head = xfer->head->next;
+ sfree(rr->buffer);
+ sfree(rr);
+ }
+ sfree(xfer);
+}
--- /dev/null
+/*
+ * sftp.h: definitions for SFTP and the sftp.c routines.
+ */
+
+#include "int64.h"
+
+#define SSH_FXP_INIT 1 /* 0x1 */
+#define SSH_FXP_VERSION 2 /* 0x2 */
+#define SSH_FXP_OPEN 3 /* 0x3 */
+#define SSH_FXP_CLOSE 4 /* 0x4 */
+#define SSH_FXP_READ 5 /* 0x5 */
+#define SSH_FXP_WRITE 6 /* 0x6 */
+#define SSH_FXP_LSTAT 7 /* 0x7 */
+#define SSH_FXP_FSTAT 8 /* 0x8 */
+#define SSH_FXP_SETSTAT 9 /* 0x9 */
+#define SSH_FXP_FSETSTAT 10 /* 0xa */
+#define SSH_FXP_OPENDIR 11 /* 0xb */
+#define SSH_FXP_READDIR 12 /* 0xc */
+#define SSH_FXP_REMOVE 13 /* 0xd */
+#define SSH_FXP_MKDIR 14 /* 0xe */
+#define SSH_FXP_RMDIR 15 /* 0xf */
+#define SSH_FXP_REALPATH 16 /* 0x10 */
+#define SSH_FXP_STAT 17 /* 0x11 */
+#define SSH_FXP_RENAME 18 /* 0x12 */
+#define SSH_FXP_STATUS 101 /* 0x65 */
+#define SSH_FXP_HANDLE 102 /* 0x66 */
+#define SSH_FXP_DATA 103 /* 0x67 */
+#define SSH_FXP_NAME 104 /* 0x68 */
+#define SSH_FXP_ATTRS 105 /* 0x69 */
+#define SSH_FXP_EXTENDED 200 /* 0xc8 */
+#define SSH_FXP_EXTENDED_REPLY 201 /* 0xc9 */
+
+#define SSH_FX_OK 0
+#define SSH_FX_EOF 1
+#define SSH_FX_NO_SUCH_FILE 2
+#define SSH_FX_PERMISSION_DENIED 3
+#define SSH_FX_FAILURE 4
+#define SSH_FX_BAD_MESSAGE 5
+#define SSH_FX_NO_CONNECTION 6
+#define SSH_FX_CONNECTION_LOST 7
+#define SSH_FX_OP_UNSUPPORTED 8
+
+#define SSH_FILEXFER_ATTR_SIZE 0x00000001
+#define SSH_FILEXFER_ATTR_UIDGID 0x00000002
+#define SSH_FILEXFER_ATTR_PERMISSIONS 0x00000004
+#define SSH_FILEXFER_ATTR_ACMODTIME 0x00000008
+#define SSH_FILEXFER_ATTR_EXTENDED 0x80000000
+
+#define SSH_FXF_READ 0x00000001
+#define SSH_FXF_WRITE 0x00000002
+#define SSH_FXF_APPEND 0x00000004
+#define SSH_FXF_CREAT 0x00000008
+#define SSH_FXF_TRUNC 0x00000010
+#define SSH_FXF_EXCL 0x00000020
+
+#define SFTP_PROTO_VERSION 3
+
+/*
+ * External references. The sftp client module sftp.c expects to be
+ * able to get at these functions.
+ *
+ * sftp_recvdata must never return less than len. It either blocks
+ * until len is available, or it returns failure.
+ *
+ * Both functions return 1 on success, 0 on failure.
+ */
+int sftp_senddata(char *data, int len);
+int sftp_recvdata(char *data, int len);
+
+/*
+ * Free sftp_requests
+ */
+void sftp_cleanup_request(void);
+
+struct fxp_attrs {
+ unsigned long flags;
+ uint64 size;
+ unsigned long uid;
+ unsigned long gid;
+ unsigned long permissions;
+ unsigned long atime;
+ unsigned long mtime;
+};
+
+struct fxp_handle {
+ char *hstring;
+ int hlen;
+};
+
+struct fxp_name {
+ char *filename, *longname;
+ struct fxp_attrs attrs;
+};
+
+struct fxp_names {
+ int nnames;
+ struct fxp_name *names;
+};
+
+struct sftp_request;
+struct sftp_packet;
+
+const char *fxp_error(void);
+int fxp_error_type(void);
+
+/*
+ * Perform exchange of init/version packets. Return 0 on failure.
+ */
+int fxp_init(void);
+
+/*
+ * Canonify a pathname. Concatenate the two given path elements
+ * with a separating slash, unless the second is NULL.
+ */
+struct sftp_request *fxp_realpath_send(char *path);
+char *fxp_realpath_recv(struct sftp_packet *pktin, struct sftp_request *req);
+
+/*
+ * Open a file.
+ */
+struct sftp_request *fxp_open_send(char *path, int type);
+struct fxp_handle *fxp_open_recv(struct sftp_packet *pktin,
+ struct sftp_request *req);
+
+/*
+ * Open a directory.
+ */
+struct sftp_request *fxp_opendir_send(char *path);
+struct fxp_handle *fxp_opendir_recv(struct sftp_packet *pktin,
+ struct sftp_request *req);
+
+/*
+ * Close a file/dir.
+ */
+struct sftp_request *fxp_close_send(struct fxp_handle *handle);
+void fxp_close_recv(struct sftp_packet *pktin, struct sftp_request *req);
+
+/*
+ * Make a directory.
+ */
+struct sftp_request *fxp_mkdir_send(char *path);
+int fxp_mkdir_recv(struct sftp_packet *pktin, struct sftp_request *req);
+
+/*
+ * Remove a directory.
+ */
+struct sftp_request *fxp_rmdir_send(char *path);
+int fxp_rmdir_recv(struct sftp_packet *pktin, struct sftp_request *req);
+
+/*
+ * Remove a file.
+ */
+struct sftp_request *fxp_remove_send(char *fname);
+int fxp_remove_recv(struct sftp_packet *pktin, struct sftp_request *req);
+
+/*
+ * Rename a file.
+ */
+struct sftp_request *fxp_rename_send(char *srcfname, char *dstfname);
+int fxp_rename_recv(struct sftp_packet *pktin, struct sftp_request *req);
+
+/*
+ * Return file attributes.
+ */
+struct sftp_request *fxp_stat_send(char *fname);
+int fxp_stat_recv(struct sftp_packet *pktin, struct sftp_request *req,
+ struct fxp_attrs *attrs);
+struct sftp_request *fxp_fstat_send(struct fxp_handle *handle);
+int fxp_fstat_recv(struct sftp_packet *pktin, struct sftp_request *req,
+ struct fxp_attrs *attrs);
+
+/*
+ * Set file attributes.
+ */
+struct sftp_request *fxp_setstat_send(char *fname, struct fxp_attrs attrs);
+int fxp_setstat_recv(struct sftp_packet *pktin, struct sftp_request *req);
+struct sftp_request *fxp_fsetstat_send(struct fxp_handle *handle,
+ struct fxp_attrs attrs);
+int fxp_fsetstat_recv(struct sftp_packet *pktin, struct sftp_request *req);
+
+/*
+ * Read from a file.
+ */
+struct sftp_request *fxp_read_send(struct fxp_handle *handle,
+ uint64 offset, int len);
+int fxp_read_recv(struct sftp_packet *pktin, struct sftp_request *req,
+ char *buffer, int len);
+
+/*
+ * Write to a file. Returns 0 on error, 1 on OK.
+ */
+struct sftp_request *fxp_write_send(struct fxp_handle *handle,
+ char *buffer, uint64 offset, int len);
+int fxp_write_recv(struct sftp_packet *pktin, struct sftp_request *req);
+
+/*
+ * Read from a directory.
+ */
+struct sftp_request *fxp_readdir_send(struct fxp_handle *handle);
+struct fxp_names *fxp_readdir_recv(struct sftp_packet *pktin,
+ struct sftp_request *req);
+
+/*
+ * Free up an fxp_names structure.
+ */
+void fxp_free_names(struct fxp_names *names);
+
+/*
+ * Duplicate and free fxp_name structures.
+ */
+struct fxp_name *fxp_dup_name(struct fxp_name *name);
+void fxp_free_name(struct fxp_name *name);
+
+/*
+ * Store user data in an sftp_request structure.
+ */
+void *fxp_get_userdata(struct sftp_request *req);
+void fxp_set_userdata(struct sftp_request *req, void *data);
+
+/*
+ * These functions might well be temporary placeholders to be
+ * replaced with more useful similar functions later. They form the
+ * main dispatch loop for processing incoming SFTP responses.
+ */
+void sftp_register(struct sftp_request *req);
+struct sftp_request *sftp_find_request(struct sftp_packet *pktin);
+struct sftp_packet *sftp_recv(void);
+
+/*
+ * A wrapper to go round fxp_read_* and fxp_write_*, which manages
+ * the queueing of multiple read/write requests.
+ */
+
+struct fxp_xfer;
+
+struct fxp_xfer *xfer_download_init(struct fxp_handle *fh, uint64 offset);
+void xfer_download_queue(struct fxp_xfer *xfer);
+int xfer_download_gotpkt(struct fxp_xfer *xfer, struct sftp_packet *pktin);
+int xfer_download_data(struct fxp_xfer *xfer, void **buf, int *len);
+
+struct fxp_xfer *xfer_upload_init(struct fxp_handle *fh, uint64 offset);
+int xfer_upload_ready(struct fxp_xfer *xfer);
+void xfer_upload_data(struct fxp_xfer *xfer, char *buffer, int len);
+int xfer_upload_gotpkt(struct fxp_xfer *xfer, struct sftp_packet *pktin);
+
+int xfer_done(struct fxp_xfer *xfer);
+void xfer_set_error(struct fxp_xfer *xfer);
+void xfer_cleanup(struct fxp_xfer *xfer);
--- /dev/null
+#!/bin/sh
+
+# Generate GPG signatures on a PuTTY release/snapshot directory as
+# delivered by Buildscr.
+
+# Usage: sh sign.sh <builddir> <keytype>
+# e.g. sh sign.sh putty Snapshots (probably in the build.out directory)
+# or sh sign.sh 0.60 Releases
+
+set -e
+
+sign() {
+ # Check for the prior existence of the signature, so we can
+ # re-run this script if it encounters an error part way
+ # through.
+ echo "----- Signing $2 with '$keyname'"
+ test -f "$3" || \
+ gpg --load-extension=idea "$1" -u "$keyname" -o "$3" "$2"
+}
+
+cd "$1"
+for t in DSA RSA; do
+ keyname="$2 ($t)"
+ echo "===== Signing with '$keyname'"
+ for i in putty*src.zip putty*.tar.gz x86/*.exe x86/*.zip; do
+ sign --detach-sign "$i" "$i.$t"
+ done
+ for i in md5sums sha1sums sha256sums sha512sums; do
+ sign --clearsign $i ${i}.$t
+ done
+done
--- /dev/null
+/*
+ * SSH backend.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <assert.h>
+#include <limits.h>
+#include <signal.h>
+
+#include "putty.h"
+#include "tree234.h"
+#include "ssh.h"
+#ifndef NO_GSSAPI
+#include "sshgssc.h"
+#include "sshgss.h"
+#endif
+
+#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 {
+ SSH2_PKTCTX_NOKEX,
+ SSH2_PKTCTX_DHGROUP,
+ SSH2_PKTCTX_DHGEX,
+ SSH2_PKTCTX_RSAKEX
+} Pkt_KCtx;
+typedef enum {
+ SSH2_PKTCTX_NOAUTH,
+ SSH2_PKTCTX_PUBLICKEY,
+ 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",
+ "host authentication failed",
+ "MAC error",
+ "compression error",
+ "service not available",
+ "protocol version not supported",
+ "host key not verifiable",
+ "connection lost",
+ "by application",
+ "too many connections",
+ "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
+#define BUG_NEEDS_SSH1_PLAIN_PASSWORD 4
+#define BUG_CHOKES_ON_RSA 8
+#define BUG_SSH2_RSA_PADDING 16
+#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
+
+/*
+ * Codes for terminal modes.
+ * Most of these are the same in SSH-1 and SSH-2.
+ * This list is derived from RFC 4254 and
+ * SSH-1 RFC-1.2.31.
+ */
+static const struct {
+ const char* const mode;
+ int opcode;
+ enum { TTY_OP_CHAR, TTY_OP_BOOL } type;
+} ssh_ttymodes[] = {
+ /* "V" prefix discarded for special characters relative to SSH specs */
+ { "INTR", 1, TTY_OP_CHAR },
+ { "QUIT", 2, TTY_OP_CHAR },
+ { "ERASE", 3, TTY_OP_CHAR },
+ { "KILL", 4, TTY_OP_CHAR },
+ { "EOF", 5, TTY_OP_CHAR },
+ { "EOL", 6, TTY_OP_CHAR },
+ { "EOL2", 7, TTY_OP_CHAR },
+ { "START", 8, TTY_OP_CHAR },
+ { "STOP", 9, TTY_OP_CHAR },
+ { "SUSP", 10, TTY_OP_CHAR },
+ { "DSUSP", 11, TTY_OP_CHAR },
+ { "REPRINT", 12, TTY_OP_CHAR },
+ { "WERASE", 13, TTY_OP_CHAR },
+ { "LNEXT", 14, TTY_OP_CHAR },
+ { "FLUSH", 15, TTY_OP_CHAR },
+ { "SWTCH", 16, TTY_OP_CHAR },
+ { "STATUS", 17, TTY_OP_CHAR },
+ { "DISCARD", 18, TTY_OP_CHAR },
+ { "IGNPAR", 30, TTY_OP_BOOL },
+ { "PARMRK", 31, TTY_OP_BOOL },
+ { "INPCK", 32, TTY_OP_BOOL },
+ { "ISTRIP", 33, TTY_OP_BOOL },
+ { "INLCR", 34, TTY_OP_BOOL },
+ { "IGNCR", 35, TTY_OP_BOOL },
+ { "ICRNL", 36, TTY_OP_BOOL },
+ { "IUCLC", 37, TTY_OP_BOOL },
+ { "IXON", 38, TTY_OP_BOOL },
+ { "IXANY", 39, TTY_OP_BOOL },
+ { "IXOFF", 40, TTY_OP_BOOL },
+ { "IMAXBEL", 41, TTY_OP_BOOL },
+ { "ISIG", 50, TTY_OP_BOOL },
+ { "ICANON", 51, TTY_OP_BOOL },
+ { "XCASE", 52, TTY_OP_BOOL },
+ { "ECHO", 53, TTY_OP_BOOL },
+ { "ECHOE", 54, TTY_OP_BOOL },
+ { "ECHOK", 55, TTY_OP_BOOL },
+ { "ECHONL", 56, TTY_OP_BOOL },
+ { "NOFLSH", 57, TTY_OP_BOOL },
+ { "TOSTOP", 58, TTY_OP_BOOL },
+ { "IEXTEN", 59, TTY_OP_BOOL },
+ { "ECHOCTL", 60, TTY_OP_BOOL },
+ { "ECHOKE", 61, TTY_OP_BOOL },
+ { "PENDIN", 62, TTY_OP_BOOL }, /* XXX is this a real mode? */
+ { "OPOST", 70, TTY_OP_BOOL },
+ { "OLCUC", 71, TTY_OP_BOOL },
+ { "ONLCR", 72, TTY_OP_BOOL },
+ { "OCRNL", 73, TTY_OP_BOOL },
+ { "ONOCR", 74, TTY_OP_BOOL },
+ { "ONLRET", 75, TTY_OP_BOOL },
+ { "CS7", 90, TTY_OP_BOOL },
+ { "CS8", 91, TTY_OP_BOOL },
+ { "PARENB", 92, TTY_OP_BOOL },
+ { "PARODD", 93, TTY_OP_BOOL }
+};
+
+/* Miscellaneous other tty-related constants. */
+#define SSH_TTY_OP_END 0
+/* The opcodes for ISPEED/OSPEED differ between SSH-1 and SSH-2. */
+#define SSH1_TTY_OP_ISPEED 192
+#define SSH1_TTY_OP_OSPEED 193
+#define SSH2_TTY_OP_ISPEED 128
+#define SSH2_TTY_OP_OSPEED 129
+
+/* Helper functions for parsing tty-related config. */
+static unsigned int ssh_tty_parse_specchar(char *s)
+{
+ unsigned int ret;
+ if (*s) {
+ char *next = NULL;
+ ret = ctrlparse(s, &next);
+ if (!next) ret = s[0];
+ } else {
+ ret = 255; /* special value meaning "don't set" */
+ }
+ return ret;
+}
+static unsigned int ssh_tty_parse_boolean(char *s)
+{
+ if (stricmp(s, "yes") == 0 ||
+ stricmp(s, "on") == 0 ||
+ stricmp(s, "true") == 0 ||
+ stricmp(s, "+") == 0)
+ return 1; /* true */
+ else if (stricmp(s, "no") == 0 ||
+ stricmp(s, "off") == 0 ||
+ stricmp(s, "false") == 0 ||
+ stricmp(s, "-") == 0)
+ return 0; /* false */
+ else
+ return (atoi(s) != 0);
+}
+
+#define translate(x) if (type == x) return #x
+#define translatek(x,ctx) if (type == x && (pkt_kctx == ctx)) return #x
+#define translatea(x,ctx) if (type == x && (pkt_actx == ctx)) return #x
+static char *ssh1_pkt_type(int type)
+{
+ translate(SSH1_MSG_DISCONNECT);
+ translate(SSH1_SMSG_PUBLIC_KEY);
+ translate(SSH1_CMSG_SESSION_KEY);
+ translate(SSH1_CMSG_USER);
+ translate(SSH1_CMSG_AUTH_RSA);
+ translate(SSH1_SMSG_AUTH_RSA_CHALLENGE);
+ translate(SSH1_CMSG_AUTH_RSA_RESPONSE);
+ translate(SSH1_CMSG_AUTH_PASSWORD);
+ translate(SSH1_CMSG_REQUEST_PTY);
+ translate(SSH1_CMSG_WINDOW_SIZE);
+ translate(SSH1_CMSG_EXEC_SHELL);
+ translate(SSH1_CMSG_EXEC_CMD);
+ translate(SSH1_SMSG_SUCCESS);
+ translate(SSH1_SMSG_FAILURE);
+ translate(SSH1_CMSG_STDIN_DATA);
+ translate(SSH1_SMSG_STDOUT_DATA);
+ translate(SSH1_SMSG_STDERR_DATA);
+ translate(SSH1_CMSG_EOF);
+ translate(SSH1_SMSG_EXIT_STATUS);
+ translate(SSH1_MSG_CHANNEL_OPEN_CONFIRMATION);
+ translate(SSH1_MSG_CHANNEL_OPEN_FAILURE);
+ translate(SSH1_MSG_CHANNEL_DATA);
+ translate(SSH1_MSG_CHANNEL_CLOSE);
+ translate(SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION);
+ translate(SSH1_SMSG_X11_OPEN);
+ translate(SSH1_CMSG_PORT_FORWARD_REQUEST);
+ translate(SSH1_MSG_PORT_OPEN);
+ translate(SSH1_CMSG_AGENT_REQUEST_FORWARDING);
+ translate(SSH1_SMSG_AGENT_OPEN);
+ translate(SSH1_MSG_IGNORE);
+ translate(SSH1_CMSG_EXIT_CONFIRMATION);
+ translate(SSH1_CMSG_X11_REQUEST_FORWARDING);
+ translate(SSH1_CMSG_AUTH_RHOSTS_RSA);
+ translate(SSH1_MSG_DEBUG);
+ translate(SSH1_CMSG_REQUEST_COMPRESSION);
+ translate(SSH1_CMSG_AUTH_TIS);
+ translate(SSH1_SMSG_AUTH_TIS_CHALLENGE);
+ translate(SSH1_CMSG_AUTH_TIS_RESPONSE);
+ translate(SSH1_CMSG_AUTH_CCARD);
+ translate(SSH1_SMSG_AUTH_CCARD_CHALLENGE);
+ translate(SSH1_CMSG_AUTH_CCARD_RESPONSE);
+ return "unknown";
+}
+static char *ssh2_pkt_type(Pkt_KCtx pkt_kctx, Pkt_ACtx pkt_actx, int type)
+{
+ translatea(SSH2_MSG_USERAUTH_GSSAPI_RESPONSE,SSH2_PKTCTX_GSSAPI);
+ translatea(SSH2_MSG_USERAUTH_GSSAPI_TOKEN,SSH2_PKTCTX_GSSAPI);
+ translatea(SSH2_MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE,SSH2_PKTCTX_GSSAPI);
+ translatea(SSH2_MSG_USERAUTH_GSSAPI_ERROR,SSH2_PKTCTX_GSSAPI);
+ translatea(SSH2_MSG_USERAUTH_GSSAPI_ERRTOK,SSH2_PKTCTX_GSSAPI);
+ translatea(SSH2_MSG_USERAUTH_GSSAPI_MIC, SSH2_PKTCTX_GSSAPI);
+ translate(SSH2_MSG_DISCONNECT);
+ translate(SSH2_MSG_IGNORE);
+ translate(SSH2_MSG_UNIMPLEMENTED);
+ translate(SSH2_MSG_DEBUG);
+ translate(SSH2_MSG_SERVICE_REQUEST);
+ translate(SSH2_MSG_SERVICE_ACCEPT);
+ translate(SSH2_MSG_KEXINIT);
+ translate(SSH2_MSG_NEWKEYS);
+ translatek(SSH2_MSG_KEXDH_INIT, SSH2_PKTCTX_DHGROUP);
+ translatek(SSH2_MSG_KEXDH_REPLY, SSH2_PKTCTX_DHGROUP);
+ translatek(SSH2_MSG_KEX_DH_GEX_REQUEST, SSH2_PKTCTX_DHGEX);
+ translatek(SSH2_MSG_KEX_DH_GEX_GROUP, SSH2_PKTCTX_DHGEX);
+ translatek(SSH2_MSG_KEX_DH_GEX_INIT, SSH2_PKTCTX_DHGEX);
+ translatek(SSH2_MSG_KEX_DH_GEX_REPLY, SSH2_PKTCTX_DHGEX);
+ translatek(SSH2_MSG_KEXRSA_PUBKEY, SSH2_PKTCTX_RSAKEX);
+ translatek(SSH2_MSG_KEXRSA_SECRET, SSH2_PKTCTX_RSAKEX);
+ translatek(SSH2_MSG_KEXRSA_DONE, SSH2_PKTCTX_RSAKEX);
+ translate(SSH2_MSG_USERAUTH_REQUEST);
+ translate(SSH2_MSG_USERAUTH_FAILURE);
+ translate(SSH2_MSG_USERAUTH_SUCCESS);
+ translate(SSH2_MSG_USERAUTH_BANNER);
+ translatea(SSH2_MSG_USERAUTH_PK_OK, SSH2_PKTCTX_PUBLICKEY);
+ translatea(SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ, SSH2_PKTCTX_PASSWORD);
+ translatea(SSH2_MSG_USERAUTH_INFO_REQUEST, SSH2_PKTCTX_KBDINTER);
+ translatea(SSH2_MSG_USERAUTH_INFO_RESPONSE, SSH2_PKTCTX_KBDINTER);
+ translate(SSH2_MSG_GLOBAL_REQUEST);
+ translate(SSH2_MSG_REQUEST_SUCCESS);
+ translate(SSH2_MSG_REQUEST_FAILURE);
+ translate(SSH2_MSG_CHANNEL_OPEN);
+ translate(SSH2_MSG_CHANNEL_OPEN_CONFIRMATION);
+ translate(SSH2_MSG_CHANNEL_OPEN_FAILURE);
+ translate(SSH2_MSG_CHANNEL_WINDOW_ADJUST);
+ translate(SSH2_MSG_CHANNEL_DATA);
+ translate(SSH2_MSG_CHANNEL_EXTENDED_DATA);
+ translate(SSH2_MSG_CHANNEL_EOF);
+ translate(SSH2_MSG_CHANNEL_CLOSE);
+ translate(SSH2_MSG_CHANNEL_REQUEST);
+ translate(SSH2_MSG_CHANNEL_SUCCESS);
+ translate(SSH2_MSG_CHANNEL_FAILURE);
+ return "unknown";
+}
+#undef translate
+#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
+ * read
+ *
+ * http://www.chiark.greenend.org.uk/~sgtatham/coroutines.html
+ *
+ * which explains the theory behind these macros.
+ *
+ * In particular, if you are getting `case expression not constant'
+ * errors when building with MS Visual Studio, this is because MS's
+ * Edit and Continue debugging feature causes their compiler to
+ * violate ANSI C. To disable Edit and Continue debugging:
+ *
+ * - right-click ssh.c in the FileView
+ * - click Settings
+ * - 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 crFinish(z) } *crLine = 0; return (z); }
+#define crFinishV } *crLine = 0; return; }
+#define crReturn(z) \
+ do {\
+ *crLine =__LINE__; return (z); case __LINE__:;\
+ } while (0)
+#define crReturnV \
+ do {\
+ *crLine=__LINE__; return; case __LINE__:;\
+ } while (0)
+#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;
+
+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_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 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 *);
+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);
+
+/*
+ * Buffer management constants. There are several of these for
+ * various different purposes:
+ *
+ * - SSH1_BUFFER_LIMIT is the amount of backlog that must build up
+ * on a local data stream before we throttle the whole SSH
+ * connection (in SSH-1 only). Throttling the whole connection is
+ * pretty drastic so we set this high in the hope it won't
+ * happen very often.
+ *
+ * - SSH_MAX_BACKLOG is the amount of backlog that must build up
+ * on the SSH connection itself before we defensively throttle
+ * _all_ local data streams. This is pretty drastic too (though
+ * thankfully unlikely in SSH-2 since the window mechanism should
+ * ensure that the server never has any need to throttle its end
+ * of the connection), so we set this high as well.
+ *
+ * - OUR_V2_WINSIZE is the maximum window size we present on SSH-2
+ * channels.
+ *
+ * - OUR_V2_BIGWIN is the window size we advertise for the only
+ * channel in a simple connection. It must be <= INT_MAX.
+ *
+ * - OUR_V2_MAXPKT is the official "maximum packet size" we send
+ * to the remote side. This actually has nothing to do with the
+ * size of the _packet_, but is instead a limit on the amount
+ * of data we're willing to receive in a single SSH2 channel
+ * data message.
+ *
+ * - OUR_V2_PACKETLIMIT is actually the maximum size of SSH
+ * _packet_ we're prepared to cope with. It must be a multiple
+ * of the cipher block size, and must be at least 35000.
+ */
+
+#define SSH1_BUFFER_LIMIT 32768
+#define SSH_MAX_BACKLOG 32768
+#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
+};
+const static struct ssh_mac *buggymacs[] = {
+ &ssh_hmac_sha1_buggy, &ssh_hmac_sha1_96_buggy, &ssh_hmac_md5
+};
+
+static void *ssh_comp_none_init(void)
+{
+ return NULL;
+}
+static void ssh_comp_none_cleanup(void *handle)
+{
+}
+static int ssh_comp_none_block(void *handle, unsigned char *block, int len,
+ unsigned char **outblock, int *outlen)
+{
+ return 0;
+}
+static int ssh_comp_none_disable(void *handle)
+{
+ return 0;
+}
+const static struct ssh_compress ssh_comp_none = {
+ "none", NULL,
+ ssh_comp_none_init, ssh_comp_none_cleanup, ssh_comp_none_block,
+ ssh_comp_none_init, ssh_comp_none_cleanup, ssh_comp_none_block,
+ ssh_comp_none_disable, NULL
+};
+extern const struct ssh_compress ssh_zlib;
+const static struct ssh_compress *compressions[] = {
+ &ssh_zlib, &ssh_comp_none
+};
+
+enum { /* channel types */
+ CHAN_MAINSESSION,
+ CHAN_X11,
+ CHAN_AGENT,
+ CHAN_SOCKDATA,
+ CHAN_SOCKDATA_DORMANT /* one the remote hasn't confirmed */
+};
+
+/*
+ * little structure to keep track of outstanding WINDOW_ADJUSTs
+ */
+struct winadj {
+ struct winadj *next;
+ unsigned size;
+};
+
+/*
+ * 2-3-4 tree storing channels.
+ */
+struct ssh_channel {
+ Ssh ssh; /* pointer back to main context */
+ unsigned remoteid, localid;
+ int type;
+ /* True if we opened this channel but server hasn't confirmed. */
+ int halfopen;
+ /*
+ * In SSH-1, this value contains four bits:
+ *
+ * 1 We have sent SSH1_MSG_CHANNEL_CLOSE.
+ * 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.
+ */
+ 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.
+ */
+ int pending_close;
+
+ /*
+ * True if this channel is causing the underlying connection to be
+ * throttled.
+ */
+ int throttling_conn;
+ union {
+ struct ssh2_data_channel {
+ bufchain outbuffer;
+ unsigned remwindow, remmaxpkt;
+ /* locwindow is signed so we can cope with excess data. */
+ int locwindow, locmaxwin;
+ /*
+ * remlocwin is the amount of local window that we think
+ * 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
+ * been acked.
+ */
+ struct winadj *winadj_head, *winadj_tail;
+ enum { THROTTLED, UNTHROTTLING, UNTHROTTLED } throttle_state;
+ } v2;
+ } v;
+ union {
+ struct ssh_agent_channel {
+ unsigned char *message;
+ unsigned char msglen[4];
+ unsigned lensofar, totallen;
+ } a;
+ struct ssh_x11_channel {
+ Socket s;
+ } x11;
+ struct ssh_pfd_channel {
+ Socket s;
+ } pfd;
+ } u;
+};
+
+/*
+ * 2-3-4 tree storing remote->local port forwardings. SSH-1 and SSH-2
+ * use this structure in different ways, reflecting SSH-2's
+ * altogether saner approach to port forwarding.
+ *
+ * In SSH-1, you arrange a remote forwarding by sending the server
+ * the remote port number, and the local destination host:port.
+ * When a connection comes in, the server sends you back that
+ * host:port pair, and you connect to it. This is a ready-made
+ * security hole if you're not on the ball: a malicious server
+ * could send you back _any_ host:port pair, so if you trustingly
+ * connect to the address it gives you then you've just opened the
+ * entire inside of your corporate network just by connecting
+ * through it to a dodgy SSH server. Hence, we must store a list of
+ * host:port pairs we _are_ trying to forward to, and reject a
+ * connection request from the server if it's not in the list.
+ *
+ * In SSH-2, each side of the connection minds its own business and
+ * doesn't send unnecessary information to the other. You arrange a
+ * remote forwarding by sending the server just the remote port
+ * number. When a connection comes in, the server tells you which
+ * of its ports was connected to; and _you_ have to remember what
+ * local host:port pair went with that port number.
+ *
+ * Hence, in SSH-1 this structure is indexed by destination
+ * host:port pair, whereas in SSH-2 it is indexed by source port.
+ */
+struct ssh_portfwd; /* forward declaration */
+
+struct ssh_rportfwd {
+ unsigned sport, dport;
+ char dhost[256];
+ char *sportdesc;
+ struct ssh_portfwd *pfrec;
+};
+#define free_rportfwd(pf) ( \
+ ((pf) ? (sfree((pf)->sportdesc)) : (void)0 ), 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,
+ * so that we can reconfigure in mid-session if the user requests
+ * it.
+ */
+struct ssh_portfwd {
+ enum { DESTROY, KEEP, CREATE } status;
+ int type;
+ unsigned sport, dport;
+ char *saddr, *daddr;
+ char *sserv, *dserv;
+ struct ssh_rportfwd *remote;
+ int addressfamily;
+ void *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 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 maxlen; /* amount of storage allocated for `data' */
+ long encrypted_len; /* for SSH-2 total-size counting */
+
+ /*
+ * State associated with packet logging
+ */
+ int logmode;
+ int nblanks;
+ struct logblank_t *blanks;
+};
+
+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 ssh1_protocol_setup(Ssh ssh);
+static void ssh2_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);
+static void ssh2_set_window(struct ssh_channel *c, int newwin);
+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);
+
+struct rdpkt1_state_tag {
+ long len, pad, biglen, to_read;
+ unsigned long realcrc, gotcrc;
+ unsigned char *p;
+ int i;
+ int chunk;
+ struct Packet *pktin;
+};
+
+struct rdpkt2_state_tag {
+ long len, pad, payload, packetlen, maclen;
+ int i;
+ 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 queued_handler;
+struct queued_handler {
+ int msg1, msg2;
+ chandler_fn_t handler;
+ void *ctx;
+ struct queued_handler *next;
+};
+
+struct ssh_tag {
+ const struct plug_function_table *fn;
+ /* the above field _must_ be first in the structure */
+
+ char *v_c, *v_s;
+ void *exhash;
+
+ Socket s;
+
+ void *ldisc;
+ void *logctx;
+
+ unsigned char session_key[32];
+ int v1_compressing;
+ int v1_remote_protoflags;
+ int v1_local_protoflags;
+ int agentfwd_enabled;
+ int X11_fwd_enabled;
+ int remote_bugs;
+ const struct ssh_cipher *cipher;
+ void *v1_cipher_ctx;
+ void *crcda_ctx;
+ const struct ssh2_cipher *cscipher, *sccipher;
+ void *cs_cipher_ctx, *sc_cipher_ctx;
+ const struct ssh_mac *csmac, *scmac;
+ 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;
+ unsigned char v2_session_id[SSH2_KEX_MAX_HASH_LEN];
+ int v2_session_id_len;
+ void *kex_ctx;
+
+ char *savedhost;
+ int savedport;
+ int send_ok;
+ int echoing, editing;
+
+ void *frontend;
+
+ int ospeed, ispeed; /* temporaries */
+ int term_width, term_height;
+
+ tree234 *channels; /* indexed by local id */
+ struct ssh_channel *mainchan; /* primary session channel */
+ int ncmode; /* is primary channel direct-tcpip? */
+ int exitcode;
+ int close_expected;
+ int clean_exit;
+
+ tree234 *rportfwds, *portfwds;
+
+ enum {
+ SSH_STATE_PREPACKET,
+ SSH_STATE_BEFORE_SIZE,
+ SSH_STATE_INTERMED,
+ SSH_STATE_SESSION,
+ SSH_STATE_CLOSED
+ } state;
+
+ int size_needed, eof_needed;
+
+ struct Packet **queue;
+ int queuelen, queuesize;
+ int queueing;
+ unsigned char *deferred_send_data;
+ int deferred_len, deferred_size;
+
+ /*
+ * Gross hack: pscp will try to start SFTP but fall back to
+ * scp1 if that fails. This variable is the means by which
+ * scp.c can reach into the SSH code and find out which one it
+ * got.
+ */
+ int fallback_cmd;
+
+ bufchain banner; /* accumulates banners during do_ssh2_authconn */
+
+ Pkt_KCtx pkt_kctx;
+ Pkt_ACtx pkt_actx;
+
+ struct X11Display *x11disp;
+
+ int version;
+ int conn_throttle_count;
+ int overall_bufsize;
+ int throttled_all;
+ int v1_stdout_throttling;
+ unsigned long v2_outgoing_sequence;
+
+ int ssh1_rdpkt_crstate;
+ int ssh2_rdpkt_crstate;
+ int do_ssh_init_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;
+
+ struct rdpkt1_state_tag rdpkt1_state;
+ struct rdpkt2_state_tag rdpkt2_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);
+
+ /*
+ * 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.
+ */
+ Config cfg;
+
+ /*
+ * Used to transfer data back from async callbacks.
+ */
+ void *agent_response;
+ int agent_response_len;
+ int user_response;
+
+ /*
+ * The SSH connection can be set as `frozen', meaning we are
+ * not currently accepting incoming data from the network. This
+ * is slightly more serious than setting the _socket_ as
+ * frozen, because we may already have had data passed to us
+ * from the network which we need to delay processing until
+ * after the freeze is lifted, so we also need a bufchain to
+ * store that data.
+ */
+ int frozen;
+ bufchain queued_incoming_data;
+
+ /*
+ * Dispatch table for packet types that we may have to deal
+ * with at any time.
+ */
+ handler_fn_t packet_dispatch[256];
+
+ /*
+ * Queues of one-off handler functions for success/failure
+ * indications from a request.
+ */
+ struct queued_handler *qhead, *qtail;
+
+ /*
+ * This module deals with sending keepalives.
+ */
+ Pinger pinger;
+
+ /*
+ * Track incoming and outgoing data sizes and time, for
+ * 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;
+ char *deferred_rekey_reason; /* points to STATIC string; don't free */
+
+ /*
+ * Fully qualified host name, which we need if doing GSSAPI.
+ */
+ char *fullhostname;
+
+#ifndef NO_GSSAPI
+ /*
+ * GSSAPI libraries for this session.
+ */
+ struct ssh_gss_liblist *gsslibs;
+#endif
+};
+
+#define logevent(s) logevent(ssh->frontend, s)
+
+/* logevent, only printf-formatted. */
+static void logeventf(Ssh ssh, const char *fmt, ...)
+{
+ va_list ap;
+ char *buf;
+
+ va_start(ap, fmt);
+ buf = dupvprintf(fmt, ap);
+ 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,
+ 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;
+ }
+}
+
+static int ssh_channelcmp(void *av, void *bv)
+{
+ struct ssh_channel *a = (struct ssh_channel *) av;
+ struct ssh_channel *b = (struct ssh_channel *) bv;
+ if (a->localid < b->localid)
+ return -1;
+ if (a->localid > b->localid)
+ return +1;
+ return 0;
+}
+static int ssh_channelfind(void *av, void *bv)
+{
+ unsigned *a = (unsigned *) av;
+ struct ssh_channel *b = (struct ssh_channel *) bv;
+ if (*a < b->localid)
+ return -1;
+ if (*a > b->localid)
+ return +1;
+ return 0;
+}
+
+static int ssh_rportcmp_ssh1(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->dhost, b->dhost)) != 0)
+ return i < 0 ? -1 : +1;
+ if (a->dport > b->dport)
+ return +1;
+ if (a->dport < b->dport)
+ return -1;
+ return 0;
+}
+
+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;
+
+ if (a->sport > b->sport)
+ return +1;
+ if (a->sport < b->sport)
+ return -1;
+ return 0;
+}
+
+/*
+ * Special form of strcmp which can cope with NULL inputs. NULL is
+ * defined to sort before even the empty string.
+ */
+static int nullstrcmp(const char *a, const char *b)
+{
+ if (a == NULL && b == NULL)
+ return 0;
+ if (a == NULL)
+ return -1;
+ if (b == NULL)
+ return +1;
+ return strcmp(a, b);
+}
+
+static int ssh_portcmp(void *av, void *bv)
+{
+ struct ssh_portfwd *a = (struct ssh_portfwd *) av;
+ struct ssh_portfwd *b = (struct ssh_portfwd *) bv;
+ int i;
+ if (a->type > b->type)
+ return +1;
+ if (a->type < b->type)
+ return -1;
+ if (a->addressfamily > b->addressfamily)
+ return +1;
+ if (a->addressfamily < b->addressfamily)
+ return -1;
+ if ( (i = nullstrcmp(a->saddr, b->saddr)) != 0)
+ return i < 0 ? -1 : +1;
+ if (a->sport > b->sport)
+ return +1;
+ if (a->sport < b->sport)
+ return -1;
+ if (a->type != 'D') {
+ if ( (i = nullstrcmp(a->daddr, b->daddr)) != 0)
+ return i < 0 ? -1 : +1;
+ if (a->dport > b->dport)
+ return +1;
+ if (a->dport < b->dport)
+ return -1;
+ }
+ return 0;
+}
+
+static int alloc_channel_id(Ssh ssh)
+{
+ const unsigned CHANNEL_NUMBER_OFFSET = 256;
+ unsigned low, high, mid;
+ int tsize;
+ struct ssh_channel *c;
+
+ /*
+ * First-fit allocation of channel numbers: always pick the
+ * lowest unused one. To do this, binary-search using the
+ * counted B-tree to find the largest channel ID which is in a
+ * contiguous sequence from the beginning. (Precisely
+ * everything in that sequence must have ID equal to its tree
+ * index plus CHANNEL_NUMBER_OFFSET.)
+ */
+ tsize = count234(ssh->channels);
+
+ low = -1;
+ high = tsize;
+ while (high - low > 1) {
+ mid = (high + low) / 2;
+ c = index234(ssh->channels, mid);
+ if (c->localid == mid + CHANNEL_NUMBER_OFFSET)
+ low = mid; /* this one is fine */
+ else
+ high = mid; /* this one is past it */
+ }
+ /*
+ * Now low points to either -1, or the tree index of the
+ * largest ID in the initial sequence.
+ */
+ {
+ unsigned i = low + 1 + CHANNEL_NUMBER_OFFSET;
+ assert(NULL == find234(ssh->channels, &i, ssh_channelfind));
+ }
+ return low + 1 + CHANNEL_NUMBER_OFFSET;
+}
+
+static void c_write_stderr(int trusted, const char *buf, int len)
+{
+ int i;
+ for (i = 0; i < len; i++)
+ if (buf[i] != '\r' && (trusted || buf[i] == '\n' || (buf[i] & 0x60)))
+ fputc(buf[i], stderr);
+}
+
+static void c_write(Ssh ssh, const char *buf, int len)
+{
+ if (flags & FLAG_STDERR)
+ c_write_stderr(1, buf, len);
+ else
+ from_backend(ssh->frontend, 1, buf, len);
+}
+
+static void c_write_untrusted(Ssh ssh, const char *buf, int len)
+{
+ if (flags & FLAG_STDERR)
+ c_write_stderr(0, buf, len);
+ else
+ from_backend_untrusted(ssh->frontend, buf, len);
+}
+
+static void c_write_str(Ssh ssh, const char *buf)
+{
+ c_write(ssh, buf, strlen(buf));
+}
+
+static void ssh_free_packet(struct Packet *pkt)
+{
+ sfree(pkt->data);
+ sfree(pkt);
+}
+static struct Packet *ssh_new_packet(void)
+{
+ 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;
+}
+
+/*
+ * 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.
+ * Update the *data and *datalen variables.
+ * Return a Packet structure when a packet is completed.
+ */
+static struct Packet *ssh1_rdpkt(Ssh ssh, unsigned char **data, int *datalen)
+{
+ struct rdpkt1_state_tag *st = &ssh->rdpkt1_state;
+
+ crBegin(ssh->ssh1_rdpkt_crstate);
+
+ st->pktin = ssh_new_packet();
+
+ st->pktin->type = 0;
+ st->pktin->length = 0;
+
+ for (st->i = st->len = 0; st->i < 4; st->i++) {
+ while ((*datalen) == 0)
+ crReturn(NULL);
+ st->len = (st->len << 8) + **data;
+ (*data)++, (*datalen)--;
+ }
+
+ st->pad = 8 - (st->len % 8);
+ st->biglen = st->len + st->pad;
+ st->pktin->length = st->len - 5;
+
+ if (st->biglen < 0) {
+ bombout(("Extremely large packet length from server suggests"
+ " data stream corruption"));
+ ssh_free_packet(st->pktin);
+ crStop(NULL);
+ }
+
+ st->pktin->maxlen = st->biglen;
+ st->pktin->data = snewn(st->biglen + APIEXTRA, unsigned char);
+
+ st->to_read = st->biglen;
+ st->p = st->pktin->data;
+ while (st->to_read > 0) {
+ st->chunk = st->to_read;
+ while ((*datalen) == 0)
+ crReturn(NULL);
+ if (st->chunk > (*datalen))
+ st->chunk = (*datalen);
+ memcpy(st->p, *data, st->chunk);
+ *data += st->chunk;
+ *datalen -= st->chunk;
+ st->p += st->chunk;
+ st->to_read -= st->chunk;
+ }
+
+ if (ssh->cipher && detect_attack(ssh->crcda_ctx, st->pktin->data,
+ st->biglen, NULL)) {
+ bombout(("Network attack (CRC compensation) detected!"));
+ ssh_free_packet(st->pktin);
+ crStop(NULL);
+ }
+
+ if (ssh->cipher)
+ ssh->cipher->decrypt(ssh->v1_cipher_ctx, st->pktin->data, st->biglen);
+
+ st->realcrc = crc32_compute(st->pktin->data, st->biglen - 4);
+ st->gotcrc = GET_32BIT(st->pktin->data + st->biglen - 4);
+ if (st->gotcrc != st->realcrc) {
+ bombout(("Incorrect CRC received on packet"));
+ 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,
+ st->pktin->body - 1, st->pktin->length + 1,
+ &decompblk, &decomplen)) {
+ bombout(("Zlib decompression encountered invalid data"));
+ ssh_free_packet(st->pktin);
+ crStop(NULL);
+ }
+
+ if (st->pktin->maxlen < st->pad + decomplen) {
+ st->pktin->maxlen = st->pad + decomplen;
+ st->pktin->data = sresize(st->pktin->data,
+ st->pktin->maxlen + APIEXTRA,
+ unsigned char);
+ st->pktin->body = st->pktin->data + st->pad + 1;
+ }
+
+ memcpy(st->pktin->body - 1, decompblk, decomplen);
+ sfree(decompblk);
+ st->pktin->length = decomplen - 1;
+ }
+
+ st->pktin->type = st->pktin->body[-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 == 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);
+ }
+
+ crFinish(st->pktin);
+}
+
+static struct Packet *ssh2_rdpkt(Ssh ssh, unsigned char **data, int *datalen)
+{
+ struct rdpkt2_state_tag *st = &ssh->rdpkt2_state;
+
+ crBegin(ssh->ssh2_rdpkt_crstate);
+
+ st->pktin = ssh_new_packet();
+
+ st->pktin->type = 0;
+ st->pktin->length = 0;
+ if (ssh->sccipher)
+ st->cipherblk = ssh->sccipher->blksize;
+ else
+ st->cipherblk = 8;
+ if (st->cipherblk < 8)
+ st->cipherblk = 8;
+ st->maclen = ssh->scmac ? ssh->scmac->len : 0;
+
+ if (ssh->sccipher && (ssh->sccipher->flags & SSH_CIPHER_IS_CBC) &&
+ ssh->scmac) {
+ /*
+ * When dealing with a CBC-mode cipher, we want to avoid the
+ * possibility of an attacker's tweaking the ciphertext stream
+ * so as to cause us to feed the same block to the block
+ * cipher more than once and thus leak information
+ * (VU#958563). The way we do this is not to take any
+ * decisions on the basis of anything we've decrypted until
+ * we've verified it with a MAC. That includes the packet
+ * length, so we just read data and check the MAC repeatedly,
+ * and when the MAC passes, see if the length we've got is
+ * plausible.
+ */
+
+ /* May as well allocate the whole lot now. */
+ st->pktin->data = snewn(OUR_V2_PACKETLIMIT + st->maclen + APIEXTRA,
+ unsigned char);
+
+ /* Read an amount corresponding to the MAC. */
+ for (st->i = 0; st->i < st->maclen; st->i++) {
+ while ((*datalen) == 0)
+ crReturn(NULL);
+ st->pktin->data[st->i] = *(*data)++;
+ (*datalen)--;
+ }
+
+ st->packetlen = 0;
+ {
+ unsigned char seq[4];
+ ssh->scmac->start(ssh->sc_mac_ctx);
+ PUT_32BIT(seq, st->incoming_sequence);
+ ssh->scmac->bytes(ssh->sc_mac_ctx, seq, 4);
+ }
+
+ for (;;) { /* Once around this loop per cipher block. */
+ /* Read another cipher-block's worth, and tack it onto the end. */
+ for (st->i = 0; st->i < st->cipherblk; st->i++) {
+ while ((*datalen) == 0)
+ crReturn(NULL);
+ st->pktin->data[st->packetlen+st->maclen+st->i] = *(*data)++;
+ (*datalen)--;
+ }
+ /* Decrypt one more block (a little further back in the stream). */
+ ssh->sccipher->decrypt(ssh->sc_cipher_ctx,
+ st->pktin->data + st->packetlen,
+ st->cipherblk);
+ /* Feed that block to the MAC. */
+ ssh->scmac->bytes(ssh->sc_mac_ctx,
+ 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)
+ break;
+ if (st->packetlen >= OUR_V2_PACKETLIMIT) {
+ bombout(("No valid incoming packet found"));
+ ssh_free_packet(st->pktin);
+ crStop(NULL);
+ }
+ }
+ st->pktin->maxlen = st->packetlen + st->maclen;
+ st->pktin->data = sresize(st->pktin->data,
+ st->pktin->maxlen + APIEXTRA,
+ unsigned char);
+ } else {
+ st->pktin->data = snewn(st->cipherblk + APIEXTRA, unsigned char);
+
+ /*
+ * Acquire and decrypt the first block of the packet. This will
+ * contain the length and padding details.
+ */
+ for (st->i = st->len = 0; st->i < st->cipherblk; st->i++) {
+ while ((*datalen) == 0)
+ crReturn(NULL);
+ st->pktin->data[st->i] = *(*data)++;
+ (*datalen)--;
+ }
+
+ if (ssh->sccipher)
+ ssh->sccipher->decrypt(ssh->sc_cipher_ctx,
+ st->pktin->data, st->cipherblk);
+
+ /*
+ * Now get the length figure.
+ */
+ st->len = GET_32BIT(st->pktin->data);
+
+ /*
+ * _Completely_ silly lengths should be stomped on before they
+ * do us any more damage.
+ */
+ if (st->len < 0 || st->len > OUR_V2_PACKETLIMIT ||
+ (st->len + 4) % st->cipherblk != 0) {
+ bombout(("Incoming packet was garbled on decryption"));
+ ssh_free_packet(st->pktin);
+ crStop(NULL);
+ }
+
+ /*
+ * So now we can work out the total packet length.
+ */
+ st->packetlen = st->len + 4;
+
+ /*
+ * Allocate memory for the rest of the packet.
+ */
+ st->pktin->maxlen = st->packetlen + st->maclen;
+ st->pktin->data = sresize(st->pktin->data,
+ st->pktin->maxlen + APIEXTRA,
+ unsigned char);
+
+ /*
+ * Read and decrypt the remainder of the packet.
+ */
+ for (st->i = st->cipherblk; st->i < st->packetlen + st->maclen;
+ st->i++) {
+ while ((*datalen) == 0)
+ crReturn(NULL);
+ st->pktin->data[st->i] = *(*data)++;
+ (*datalen)--;
+ }
+ /* Decrypt everything _except_ the MAC. */
+ if (ssh->sccipher)
+ ssh->sccipher->decrypt(ssh->sc_cipher_ctx,
+ st->pktin->data + st->cipherblk,
+ st->packetlen - st->cipherblk);
+
+ /*
+ * Check the MAC.
+ */
+ if (ssh->scmac
+ && !ssh->scmac->verify(ssh->sc_mac_ctx, st->pktin->data,
+ st->len + 4, st->incoming_sequence)) {
+ bombout(("Incorrect MAC received on packet"));
+ ssh_free_packet(st->pktin);
+ crStop(NULL);
+ }
+ }
+ /* Get and sanity-check the amount of random padding. */
+ st->pad = st->pktin->data[4];
+ if (st->pad < 4 || st->len - st->pad < 1) {
+ bombout(("Invalid padding length on received packet"));
+ ssh_free_packet(st->pktin);
+ crStop(NULL);
+ }
+ /*
+ * This enables us to deduce the payload length.
+ */
+ st->payload = st->len - st->pad - 1;
+
+ st->pktin->length = st->payload + 5;
+ st->pktin->encrypted_len = st->packetlen;
+
+ st->pktin->sequence = st->incoming_sequence++;
+
+ /*
+ * Decompress packet payload.
+ */
+ {
+ unsigned char *newpayload;
+ int newlen;
+ if (ssh->sccomp &&
+ ssh->sccomp->decompress(ssh->sc_comp_ctx,
+ st->pktin->data + 5, st->pktin->length - 5,
+ &newpayload, &newlen)) {
+ if (st->pktin->maxlen < newlen + 5) {
+ st->pktin->maxlen = newlen + 5;
+ st->pktin->data = sresize(st->pktin->data,
+ st->pktin->maxlen + APIEXTRA,
+ unsigned char);
+ }
+ st->pktin->length = 5 + newlen;
+ memcpy(st->pktin->data + 5, newpayload, newlen);
+ sfree(newpayload);
+ }
+ }
+
+ st->pktin->savedpos = 6;
+ st->pktin->body = st->pktin->data;
+ st->pktin->type = st->pktin->data[5];
+
+ /*
+ * 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);
+ }
+
+ crFinish(st->pktin);
+}
+
+static int s_wrpkt_prepare(Ssh ssh, struct Packet *pkt, int *offset_p)
+{
+ int pad, biglen, i, pktoffs;
+ unsigned long crc;
+#ifdef __SC__
+ /*
+ * XXX various versions of SC (including 8.8.4) screw up the
+ * register allocation in this function and use the same register
+ * (D6) for len and as a temporary, with predictable results. The
+ * following sledgehammer prevents this.
+ */
+ 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;
+
+ if (ssh->v1_compressing) {
+ unsigned char *compblk;
+ int complen;
+ zlib_compress_block(ssh->cs_comp_ctx,
+ pkt->data + 12, pkt->length - 12,
+ &compblk, &complen);
+ ssh_pkt_ensure(pkt, complen + 2); /* just in case it's got bigger */
+ memcpy(pkt->data + 12, compblk, complen);
+ sfree(compblk);
+ pkt->length = complen + 12;
+ }
+
+ ssh_pkt_ensure(pkt, pkt->length + 4); /* space for CRC */
+ pkt->length += 4;
+ len = pkt->length - 4 - 8; /* len(type+data+CRC) */
+ pad = 8 - (len % 8);
+ pktoffs = 8 - pad;
+ biglen = len + pad; /* len(padding+type+data+CRC) */
+
+ for (i = pktoffs; i < 4+8; i++)
+ pkt->data[i] = random_byte();
+ crc = crc32_compute(pkt->data + pktoffs + 4, biglen - 4); /* all ex len */
+ PUT_32BIT(pkt->data + pktoffs + 4 + biglen - 4, crc);
+ PUT_32BIT(pkt->data + pktoffs, len);
+
+ if (ssh->cipher)
+ ssh->cipher->encrypt(ssh->v1_cipher_ctx,
+ pkt->data + pktoffs + 4, biglen);
+
+ if (offset_p) *offset_p = pktoffs;
+ return biglen + 4; /* len(length+padding+type+data+CRC) */
+}
+
+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);
+ return sk_write(ssh->s, (char *)data, len);
+}
+
+static void s_wrpkt(Ssh ssh, struct Packet *pkt)
+{
+ int len, backlog, offset;
+ len = s_wrpkt_prepare(ssh, pkt, &offset);
+ backlog = s_write(ssh, pkt->data + offset, len);
+ if (backlog > SSH_MAX_BACKLOG)
+ ssh_throttle_all(ssh, 1, backlog);
+ ssh_free_packet(pkt);
+}
+
+static void s_wrpkt_defer(Ssh ssh, struct Packet *pkt)
+{
+ int len, offset;
+ len = s_wrpkt_prepare(ssh, pkt, &offset);
+ if (ssh->deferred_len + len > ssh->deferred_size) {
+ 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 + offset, len);
+ ssh->deferred_len += len;
+ ssh_free_packet(pkt);
+}
+
+/*
+ * Construct a SSH-1 packet with the specified contents.
+ * (This all-at-once interface used to be the only one, but now SSH-1
+ * packets can also be constructed incrementally.)
+ */
+static struct Packet *construct_packet(Ssh ssh, int pkttype, va_list ap)
+{
+ int argtype;
+ Bignum bn;
+ struct Packet *pkt;
+
+ pkt = ssh1_pkt_init(pkttype);
+
+ while ((argtype = va_arg(ap, int)) != PKT_END) {
+ unsigned char *argp, argchar;
+ char *sargp;
+ unsigned long argint;
+ int arglen;
+ switch (argtype) {
+ /* Actual fields in the packet */
+ case PKT_INT:
+ argint = va_arg(ap, int);
+ ssh_pkt_adduint32(pkt, argint);
+ break;
+ case PKT_CHAR:
+ argchar = (unsigned char) va_arg(ap, int);
+ ssh_pkt_addbyte(pkt, argchar);
+ break;
+ case PKT_DATA:
+ argp = va_arg(ap, unsigned char *);
+ arglen = va_arg(ap, int);
+ ssh_pkt_adddata(pkt, argp, arglen);
+ break;
+ case PKT_STR:
+ sargp = va_arg(ap, char *);
+ ssh_pkt_addstring(pkt, sargp);
+ 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;
+}
+
+static void send_packet(Ssh ssh, int pkttype, ...)
+{
+ struct Packet *pkt;
+ va_list ap;
+ va_start(ap, pkttype);
+ pkt = construct_packet(ssh, pkttype, ap);
+ va_end(ap);
+ s_wrpkt(ssh, pkt);
+}
+
+static void defer_packet(Ssh ssh, int pkttype, ...)
+{
+ struct Packet *pkt;
+ va_list ap;
+ va_start(ap, pkttype);
+ pkt = construct_packet(ssh, pkttype, ap);
+ va_end(ap);
+ s_wrpkt_defer(ssh, pkt);
+}
+
+static int ssh_versioncmp(char *a, char *b)
+{
+ char *ae, *be;
+ unsigned long av, bv;
+
+ av = strtoul(a, &ae, 10);
+ bv = strtoul(b, &be, 10);
+ if (av != bv)
+ return (av < bv ? -1 : +1);
+ if (*ae == '.')
+ ae++;
+ if (*be == '.')
+ be++;
+ av = strtoul(ae, &ae, 10);
+ bv = strtoul(be, &be, 10);
+ if (av != bv)
+ return (av < bv ? -1 : +1);
+ return 0;
+}
+
+/*
+ * Utility routines for putting an SSH-protocol `string' and
+ * `uint32' into a hash state.
+ */
+static void hash_string(const struct ssh_hash *h, void *s, void *str, int len)
+{
+ unsigned char lenblk[4];
+ PUT_32BIT(lenblk, len);
+ h->bytes(s, lenblk, 4);
+ h->bytes(s, str, len);
+}
+
+static void hash_uint32(const struct ssh_hash *h, void *s, unsigned i)
+{
+ unsigned char intblk[4];
+ PUT_32BIT(intblk, i);
+ h->bytes(s, intblk, 4);
+}
+
+/*
+ * Packet construction functions. Mostly shared between SSH-1 and SSH-2.
+ */
+static void ssh_pkt_ensure(struct Packet *pkt, int length)
+{
+ if (pkt->maxlen < length) {
+ unsigned char *body = pkt->body;
+ int offset = body ? body - pkt->data : 0;
+ 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;
+ }
+ 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)
+{
+ ssh_pkt_adddata(pkt, &byte, 1);
+}
+static void ssh2_pkt_addbool(struct Packet *pkt, unsigned char value)
+{
+ ssh_pkt_adddata(pkt, &value, 1);
+}
+static void ssh_pkt_adduint32(struct Packet *pkt, unsigned long value)
+{
+ unsigned char x[4];
+ PUT_32BIT(x, value);
+ ssh_pkt_adddata(pkt, x, 4);
+}
+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)
+{
+ 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)
+{
+ 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)
+{
+ ssh_pkt_addstring_start(pkt);
+ ssh_pkt_addstring_str(pkt, data);
+}
+static void ssh1_pkt_addmp(struct Packet *pkt, Bignum b)
+{
+ int len = ssh1_bignum_length(b);
+ unsigned char *data = snewn(len, unsigned char);
+ (void) ssh1_write_bignum(data, b);
+ ssh_pkt_adddata(pkt, data, len);
+ sfree(data);
+}
+static unsigned char *ssh2_mpint_fmt(Bignum b, int *len)
+{
+ unsigned char *p;
+ int i, n = (bignum_bitcount(b) + 7) / 8;
+ p = snewn(n + 1, unsigned char);
+ p[0] = 0;
+ for (i = 1; i <= n; i++)
+ p[i] = bignum_byte(b, n - i);
+ i = 0;
+ while (i <= n && p[i] == 0 && (p[i + 1] & 0x80) == 0)
+ i++;
+ memmove(p, p + i, n + 1 - i);
+ *len = n + 1 - i;
+ return p;
+}
+static void ssh2_pkt_addmp(struct Packet *pkt, Bignum b)
+{
+ unsigned char *p;
+ int len;
+ p = ssh2_mpint_fmt(b, &len);
+ ssh_pkt_addstring_start(pkt);
+ ssh_pkt_addstring_data(pkt, (char *)p, len);
+ sfree(p);
+}
+
+static struct Packet *ssh1_pkt_init(int pkt_type)
+{
+ 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;
+ 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)
+#define ssh2_pkt_adddata(pkt, data, len) ssh_pkt_adddata(pkt, data, len)
+#define ssh2_pkt_addbyte(pkt, byte) ssh_pkt_addbyte(pkt, byte)
+#define ssh2_pkt_adduint32(pkt, value) ssh_pkt_adduint32(pkt, value)
+#define ssh2_pkt_addstring_start(pkt) ssh_pkt_addstring_start(pkt)
+#define ssh2_pkt_addstring_str(pkt, data) ssh_pkt_addstring_str(pkt, data)
+#define ssh2_pkt_addstring_data(pkt, data, len) ssh_pkt_addstring_data(pkt, data, len)
+#define ssh2_pkt_addstring(pkt, data) ssh_pkt_addstring(pkt, data)
+
+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;
+ ssh_pkt_addbyte(pkt, (unsigned char) pkt_type);
+ pkt->body = pkt->data + pkt->length; /* after packet type */
+ return pkt;
+}
+
+/*
+ * Construct an SSH-2 final-form packet: compress it, encrypt it,
+ * put the MAC on it. Final packet, ready to be sent, is stored in
+ * pkt->data. Total length is returned.
+ */
+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;
+
+ /*
+ * Compress packet payload.
+ */
+ {
+ unsigned char *newpayload;
+ int newlen;
+ if (ssh->cscomp &&
+ ssh->cscomp->compress(ssh->cs_comp_ctx, pkt->data + 5,
+ pkt->length - 5,
+ &newpayload, &newlen)) {
+ pkt->length = 5;
+ ssh2_pkt_adddata(pkt, newpayload, newlen);
+ sfree(newpayload);
+ }
+ }
+
+ /*
+ * Add padding. At least four bytes, and must also bring total
+ * length (minus MAC) up to a multiple of the block size.
+ * If pkt->forcepad is set, make sure the packet is at least that size
+ * after padding.
+ */
+ cipherblk = ssh->cscipher ? ssh->cscipher->blksize : 8; /* block size */
+ cipherblk = cipherblk < 8 ? 8 : cipherblk; /* or 8 if blksize < 8 */
+ padding = 4;
+ if (pkt->length + padding < pkt->forcepad)
+ padding = pkt->forcepad - pkt->length;
+ padding +=
+ (cipherblk - (pkt->length + padding) % cipherblk) % cipherblk;
+ assert(padding <= 255);
+ maclen = ssh->csmac ? ssh->csmac->len : 0;
+ ssh2_pkt_ensure(pkt, pkt->length + padding + maclen);
+ pkt->data[4] = padding;
+ for (i = 0; i < padding; i++)
+ pkt->data[pkt->length + i] = random_byte();
+ PUT_32BIT(pkt->data, pkt->length + padding - 4);
+ if (ssh->csmac)
+ ssh->csmac->generate(ssh->cs_mac_ctx, pkt->data,
+ pkt->length + padding,
+ ssh->v2_outgoing_sequence);
+ ssh->v2_outgoing_sequence++; /* whether or not we MACed */
+
+ if (ssh->cscipher)
+ ssh->cscipher->encrypt(ssh->cs_cipher_ctx,
+ pkt->data, pkt->length + padding);
+
+ pkt->encrypted_len = pkt->length + padding;
+
+ /* Ready-to-send packet starts at pkt->data. We return length. */
+ return pkt->length + padding + maclen;
+}
+
+/*
+ * Routines called from the main SSH code to send packets. There
+ * are quite a few of these, because we have two separate
+ * mechanisms for delaying the sending of packets:
+ *
+ * - In order to send an IGNORE message and a password message in
+ * a single fixed-length blob, we require the ability to
+ * concatenate the encrypted forms of those two packets _into_ a
+ * single blob and then pass it to our <network.h> transport
+ * layer in one go. Hence, there's a deferment mechanism which
+ * works after packet encryption.
+ *
+ * - In order to avoid sending any connection-layer messages
+ * during repeat key exchange, we have to queue up any such
+ * outgoing messages _before_ they are encrypted (and in
+ * particular before they're allocated sequence numbers), and
+ * then send them once we've finished.
+ *
+ * I call these mechanisms `defer' and `queue' respectively, so as
+ * to distinguish them reasonably easily.
+ *
+ * The functions send_noqueue() and defer_noqueue() free the packet
+ * structure they are passed. Every outgoing packet goes through
+ * precisely one of these functions in its life; packets passed to
+ * ssh2_pkt_send() or ssh2_pkt_defer() either go straight to one of
+ * these or get queued, and then when the queue is later emptied
+ * the packets are all passed to defer_noqueue().
+ *
+ * When using a CBC-mode cipher, it's necessary to ensure that an
+ * attacker can't provide data to be encrypted using an IV that they
+ * know. We ensure this by prefixing each packet that might contain
+ * user data with an SSH_MSG_IGNORE. This is done using the deferral
+ * mechanism, so in this case send_noqueue() ends up redirecting to
+ * defer_noqueue(). If you don't like this inefficiency, don't use
+ * CBC.
+ */
+
+static void ssh2_pkt_defer_noqueue(Ssh, struct Packet *, int);
+static void ssh_pkt_defersend(Ssh);
+
+/*
+ * Send an SSH-2 packet immediately, without queuing or deferring.
+ */
+static void ssh2_pkt_send_noqueue(Ssh ssh, struct Packet *pkt)
+{
+ int len;
+ int backlog;
+ if (ssh->cscipher != NULL && (ssh->cscipher->flags & SSH_CIPHER_IS_CBC)) {
+ /* We need to send two packets, so use the deferral mechanism. */
+ 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);
+ if (backlog > SSH_MAX_BACKLOG)
+ ssh_throttle_all(ssh, 1, backlog);
+
+ ssh->outgoing_data_size += pkt->encrypted_len;
+ if (!ssh->kex_in_progress &&
+ 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);
+}
+
+/*
+ * Defer an SSH-2 packet.
+ */
+static void ssh2_pkt_defer_noqueue(Ssh ssh, struct Packet *pkt, int noignore)
+{
+ int len;
+ if (ssh->cscipher != NULL && (ssh->cscipher->flags & SSH_CIPHER_IS_CBC) &&
+ ssh->deferred_len == 0 && !noignore &&
+ !(ssh->remote_bugs & BUG_CHOKES_ON_SSH2_IGNORE)) {
+ /*
+ * Interpose an SSH_MSG_IGNORE to ensure that user data don't
+ * get encrypted with a known IV.
+ */
+ struct Packet *ipkt = ssh2_pkt_init(SSH2_MSG_IGNORE);
+ ssh2_pkt_addstring_start(ipkt);
+ ssh2_pkt_defer_noqueue(ssh, ipkt, TRUE);
+ }
+ len = ssh2_pkt_construct(ssh, pkt);
+ if (ssh->deferred_len + len > ssh->deferred_size) {
+ 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);
+ ssh->deferred_len += len;
+ ssh->deferred_data_size += pkt->encrypted_len;
+ ssh_free_packet(pkt);
+}
+
+/*
+ * Queue an SSH-2 packet.
+ */
+static void ssh2_pkt_queue(Ssh ssh, struct Packet *pkt)
+{
+ assert(ssh->queueing);
+
+ if (ssh->queuelen >= ssh->queuesize) {
+ ssh->queuesize = ssh->queuelen + 32;
+ ssh->queue = sresize(ssh->queue, ssh->queuesize, struct Packet *);
+ }
+
+ ssh->queue[ssh->queuelen++] = pkt;
+}
+
+/*
+ * Either queue or send a packet, depending on whether queueing is
+ * set.
+ */
+static void ssh2_pkt_send(Ssh ssh, struct Packet *pkt)
+{
+ if (ssh->queueing)
+ ssh2_pkt_queue(ssh, pkt);
+ else
+ ssh2_pkt_send_noqueue(ssh, pkt);
+}
+
+/*
+ * Either queue or defer a packet, depending on whether queueing is
+ * set.
+ */
+static void ssh2_pkt_defer(Ssh ssh, struct Packet *pkt)
+{
+ if (ssh->queueing)
+ ssh2_pkt_queue(ssh, pkt);
+ else
+ ssh2_pkt_defer_noqueue(ssh, pkt, FALSE);
+}
+
+/*
+ * Send the whole deferred data block constructed by
+ * ssh2_pkt_defer() or SSH-1's defer_packet().
+ *
+ * The expected use of the defer mechanism is that you call
+ * ssh2_pkt_defer() a few times, then call ssh_pkt_defersend(). If
+ * not currently queueing, this simply sets up deferred_send_data
+ * and then sends it. If we _are_ currently queueing, the calls to
+ * ssh2_pkt_defer() put the deferred packets on to the queue
+ * instead, and therefore ssh_pkt_defersend() has no deferred data
+ * to send. Hence, there's no need to make it conditional on
+ * ssh->queueing.
+ */
+static void ssh_pkt_defersend(Ssh ssh)
+{
+ int backlog;
+ backlog = s_write(ssh, ssh->deferred_send_data, ssh->deferred_len);
+ ssh->deferred_len = ssh->deferred_size = 0;
+ sfree(ssh->deferred_send_data);
+ ssh->deferred_send_data = NULL;
+ 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->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;
+}
+
+/*
+ * Send a packet whose length needs to be disguised (typically
+ * passwords or keyboard-interactive responses).
+ */
+static void ssh2_pkt_send_with_padding(Ssh ssh, struct Packet *pkt,
+ int padsize)
+{
+#if 0
+ if (0) {
+ /*
+ * The simplest way to do this is to adjust the
+ * variable-length padding field in the outgoing packet.
+ *
+ * Currently compiled out, because some Cisco SSH servers
+ * don't like excessively padded packets (bah, why's it
+ * always Cisco?)
+ */
+ pkt->forcepad = padsize;
+ ssh2_pkt_send(ssh, pkt);
+ } else
+#endif
+ {
+ /*
+ * If we can't do that, however, an alternative approach is
+ * to use the pkt_defer mechanism to bundle the packet
+ * tightly together with an SSH_MSG_IGNORE such that their
+ * combined length is a constant. So first we construct the
+ * final form of this packet and defer its sending.
+ */
+ ssh2_pkt_defer(ssh, pkt);
+
+ /*
+ * Now construct an SSH_MSG_IGNORE which includes a string
+ * that's an exact multiple of the cipher block size. (If
+ * the cipher is NULL so that the block size is
+ * unavailable, we don't do this trick at all, because we
+ * gain nothing by it.)
+ */
+ if (ssh->cscipher &&
+ !(ssh->remote_bugs & BUG_CHOKES_ON_SSH2_IGNORE)) {
+ int stringlen, i;
+
+ stringlen = (256 - ssh->deferred_len);
+ stringlen += ssh->cscipher->blksize - 1;
+ stringlen -= (stringlen % ssh->cscipher->blksize);
+ if (ssh->cscomp) {
+ /*
+ * Temporarily disable actual compression, so we
+ * can guarantee to get this string exactly the
+ * length we want it. The compression-disabling
+ * routine should return an integer indicating how
+ * many bytes we should adjust our string length
+ * by.
+ */
+ stringlen -=
+ ssh->cscomp->disable_compression(ssh->cs_comp_ctx);
+ }
+ pkt = ssh2_pkt_init(SSH2_MSG_IGNORE);
+ ssh2_pkt_addstring_start(pkt);
+ for (i = 0; i < stringlen; i++) {
+ char c = (char) random_byte();
+ ssh2_pkt_addstring_data(pkt, &c, 1);
+ }
+ ssh2_pkt_defer(ssh, pkt);
+ }
+ ssh_pkt_defersend(ssh);
+ }
+}
+
+/*
+ * Send all queued SSH-2 packets. We send them by means of
+ * ssh2_pkt_defer_noqueue(), in case they included a pair of
+ * packets that needed to be lumped together.
+ */
+static void ssh2_pkt_queuesend(Ssh ssh)
+{
+ int i;
+
+ assert(!ssh->queueing);
+
+ for (i = 0; i < ssh->queuelen; i++)
+ ssh2_pkt_defer_noqueue(ssh, ssh->queue[i], FALSE);
+ ssh->queuelen = 0;
+
+ ssh_pkt_defersend(ssh);
+}
+
+#if 0
+void bndebug(char *string, Bignum b)
+{
+ unsigned char *p;
+ int i, len;
+ p = ssh2_mpint_fmt(b, &len);
+ debug(("%s", string));
+ for (i = 0; i < len; i++)
+ debug((" %02x", p[i]));
+ debug(("\n"));
+ sfree(p);
+}
+#endif
+
+static void hash_mpint(const struct ssh_hash *h, void *s, Bignum b)
+{
+ unsigned char *p;
+ int len;
+ p = ssh2_mpint_fmt(b, &len);
+ hash_string(h, s, p, len);
+ sfree(p);
+}
+
+/*
+ * Packet decode functions for both SSH-1 and SSH-2.
+ */
+static unsigned long ssh_pkt_getuint32(struct Packet *pkt)
+{
+ unsigned long value;
+ if (pkt->length - pkt->savedpos < 4)
+ return 0; /* arrgh, no way to decline (FIXME?) */
+ value = GET_32BIT(pkt->body + pkt->savedpos);
+ pkt->savedpos += 4;
+ return value;
+}
+static int ssh2_pkt_getbool(struct Packet *pkt)
+{
+ unsigned long value;
+ if (pkt->length - pkt->savedpos < 1)
+ return 0; /* arrgh, no way to decline (FIXME?) */
+ value = pkt->body[pkt->savedpos] != 0;
+ pkt->savedpos++;
+ return value;
+}
+static void ssh_pkt_getstring(struct Packet *pkt, char **p, int *length)
+{
+ int len;
+ *p = NULL;
+ *length = 0;
+ if (pkt->length - pkt->savedpos < 4)
+ return;
+ len = GET_32BIT(pkt->body + pkt->savedpos);
+ if (len < 0)
+ return;
+ *length = len;
+ pkt->savedpos += 4;
+ if (pkt->length - pkt->savedpos < *length)
+ return;
+ *p = (char *)(pkt->body + pkt->savedpos);
+ pkt->savedpos += *length;
+}
+static void *ssh_pkt_getdata(struct Packet *pkt, int length)
+{
+ if (pkt->length - pkt->savedpos < length)
+ return NULL;
+ pkt->savedpos += length;
+ return pkt->body + (pkt->savedpos - length);
+}
+static int ssh1_pkt_getrsakey(struct Packet *pkt, struct RSAKey *key,
+ unsigned char **keystr)
+{
+ int j;
+
+ j = makekey(pkt->body + pkt->savedpos,
+ pkt->length - pkt->savedpos,
+ key, keystr, 0);
+
+ if (j < 0)
+ return FALSE;
+
+ pkt->savedpos += j;
+ assert(pkt->savedpos < pkt->length);
+
+ return TRUE;
+}
+static Bignum ssh1_pkt_getmp(struct Packet *pkt)
+{
+ int j;
+ Bignum b;
+
+ j = ssh1_read_bignum(pkt->body + pkt->savedpos,
+ pkt->length - pkt->savedpos, &b);
+
+ if (j < 0)
+ return NULL;
+
+ pkt->savedpos += j;
+ return b;
+}
+static Bignum ssh2_pkt_getmp(struct Packet *pkt)
+{
+ char *p;
+ int length;
+ Bignum b;
+
+ ssh_pkt_getstring(pkt, &p, &length);
+ if (!p)
+ return NULL;
+ if (p[0] & 0x80)
+ return NULL;
+ b = bignum_from_bytes((unsigned char *)p, length);
+ return b;
+}
+
+/*
+ * Helper function to add an SSH-2 signature blob to a packet.
+ * Expects to be shown the public key blob as well as the signature
+ * blob. Normally works just like ssh2_pkt_addstring, but will
+ * fiddle with the signature packet if necessary for
+ * BUG_SSH2_RSA_PADDING.
+ */
+static void ssh2_add_sigblob(Ssh ssh, struct Packet *pkt,
+ void *pkblob_v, int pkblob_len,
+ void *sigblob_v, int sigblob_len)
+{
+ unsigned char *pkblob = (unsigned char *)pkblob_v;
+ unsigned char *sigblob = (unsigned char *)sigblob_v;
+
+ /* dmemdump(pkblob, pkblob_len); */
+ /* dmemdump(sigblob, sigblob_len); */
+
+ /*
+ * 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) &&
+ (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 */
+ 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);
+ /* debug(("signature length is %d\n", siglen)); */
+
+ if (len != siglen) {
+ unsigned char newlen[4];
+ ssh2_pkt_addstring_start(pkt);
+ ssh2_pkt_addstring_data(pkt, (char *)sigblob, pos);
+ /* dmemdump(sigblob, pos); */
+ pos += 4; /* point to start of actual sig */
+ PUT_32BIT(newlen, len);
+ ssh2_pkt_addstring_data(pkt, (char *)newlen, 4);
+ /* dmemdump(newlen, 4); */
+ newlen[0] = 0;
+ while (len-- > siglen) {
+ ssh2_pkt_addstring_data(pkt, (char *)newlen, 1);
+ /* dmemdump(newlen, 1); */
+ }
+ ssh2_pkt_addstring_data(pkt, (char *)(sigblob+pos), siglen);
+ /* dmemdump(sigblob+pos, siglen); */
+ return;
+ }
+
+ /* Otherwise fall through and do it the easy way. */
+ }
+
+ ssh2_pkt_addstring_start(pkt);
+ ssh2_pkt_addstring_data(pkt, (char *)sigblob, sigblob_len);
+}
+
+/*
+ * Examine the remote side's version string and compare it against
+ * a list of known buggy implementations.
+ */
+static void ssh_detect_bugs(Ssh ssh, char *vstring)
+{
+ char *imp; /* pointer to implementation part */
+ imp = vstring;
+ imp += strcspn(imp, "-");
+ if (*imp) imp++;
+ imp += strcspn(imp, "-");
+ if (*imp) imp++;
+
+ ssh->remote_bugs = 0;
+
+ /*
+ * General notes on server version strings:
+ * - 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 &&
+ (!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")))) {
+ /*
+ * These versions don't support SSH1_MSG_IGNORE, so we have
+ * to use a different defence against password length
+ * sniffing.
+ */
+ 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 &&
+ (!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.
+ */
+ 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 &&
+ (!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.
+ */
+ 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 &&
+ !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)))) {
+ /*
+ * These versions have the HMAC bug.
+ */
+ 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 &&
+ !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
+ * generate the keys).
+ */
+ 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 &&
+ (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 &&
+ 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 &&
+ (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) ||
+ /* All versions <= 1.2.6 (they changed their format in 1.2.7) */
+ wc_match("WeOnlyDo-*", imp)))) {
+ /*
+ * These versions have the SSH-2 rekey bug.
+ */
+ 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 &&
+ (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) {
+ /*
+ * 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");
+ }
+}
+
+/*
+ * 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-<protoversion>-". */
+ assert(*str == 'S'); str++;
+ assert(*str == 'S'); str++;
+ assert(*str == 'H'); str++;
+ assert(*str == '-'); str++;
+ while (*str && *str != '-') str++;
+ assert(*str == '-'); str++;
+
+ /* Convert minus signs and spaces in the remaining string into
+ * underscores. */
+ while (*str) {
+ if (*str == '-' || *str == ' ')
+ *str = '_';
+ str++;
+ }
+}
+
+/*
+ * Send an appropriate SSH version string.
+ */
+static void ssh_send_verstring(Ssh ssh, char *svers)
+{
+ char *verstring;
+
+ if (ssh->version == 2) {
+ /*
+ * Construct a v2 version string.
+ */
+ verstring = dupprintf("SSH-2.0-%s\015\012", sshver);
+ } else {
+ /*
+ * Construct a v1 version string.
+ */
+ verstring = dupprintf("SSH-%s-%s\012",
+ (ssh_versioncmp(svers, "1.5") <= 0 ?
+ svers : "1.5"),
+ sshver);
+ }
+
+ ssh_fix_verstring(verstring);
+
+ if (ssh->version == 2) {
+ size_t len;
+ /*
+ * Record our version string.
+ */
+ len = strcspn(verstring, "\015\012");
+ ssh->v_c = snewn(len + 1, char);
+ memcpy(ssh->v_c, verstring, len);
+ ssh->v_c[len] = 0;
+ }
+
+ logeventf(ssh, "We claim version: %.*s",
+ strcspn(verstring, "\015\012"), verstring);
+ s_write(ssh, verstring, strlen(verstring));
+ sfree(verstring);
+}
+
+static int do_ssh_init(Ssh ssh, unsigned char c)
+{
+ struct do_ssh_init_state {
+ int vslen;
+ char version[10];
+ char *vstring;
+ int vstrsize;
+ int i;
+ int proto1, proto2;
+ };
+ crState(do_ssh_init_state);
+
+ crBegin(ssh->do_ssh_init_crstate);
+
+ /* Search for a line beginning with the string "SSH-" 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;
+ break;
+ no:
+ while (c != '\012')
+ crReturn(1);
+ crReturn(1);
+ }
+
+ s->vstrsize = 16;
+ s->vstring = snewn(s->vstrsize, char);
+ strcpy(s->vstring, "SSH-");
+ s->vslen = 4;
+ 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;
+ 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;
+ }
+
+ ssh->agentfwd_enabled = FALSE;
+ ssh->rdpkt2_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.
+ */
+
+ /* 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) {
+ bombout(("SSH protocol version 1 required by user but not provided by server"));
+ crStop(0);
+ }
+ if (ssh->cfg.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))
+ 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 (ssh->version == 2) {
+ size_t len;
+ /*
+ * Record their version string.
+ */
+ len = strcspn(s->vstring, "\015\012");
+ ssh->v_s = snewn(len + 1, char);
+ memcpy(ssh->v_s, s->vstring, len);
+ ssh->v_s[len] = 0;
+
+ /*
+ * Initialise SSH-2 protocol.
+ */
+ ssh->protocol = ssh2_protocol;
+ ssh2_protocol_setup(ssh);
+ ssh->s_rdpkt = ssh2_rdpkt;
+ } else {
+ /*
+ * Initialise SSH-1 protocol.
+ */
+ ssh->protocol = ssh1_protocol;
+ ssh1_protocol_setup(ssh);
+ ssh->s_rdpkt = ssh1_rdpkt;
+ }
+ 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);
+
+ sfree(s->vstring);
+
+ crFinish(0);
+}
+
+static void ssh_process_incoming_data(Ssh ssh,
+ unsigned char **data, int *datalen)
+{
+ struct Packet *pktin;
+
+ pktin = ssh->s_rdpkt(ssh, data, datalen);
+ if (pktin) {
+ ssh->protocol(ssh, NULL, 0, pktin);
+ ssh_free_packet(pktin);
+ }
+}
+
+static void ssh_queue_incoming_data(Ssh ssh,
+ unsigned char **data, int *datalen)
+{
+ bufchain_add(&ssh->queued_incoming_data, *data, *datalen);
+ *data += *datalen;
+ *datalen = 0;
+}
+
+static void ssh_process_queued_incoming_data(Ssh ssh)
+{
+ void *vdata;
+ unsigned char *data;
+ int len, origlen;
+
+ while (!ssh->frozen && bufchain_size(&ssh->queued_incoming_data)) {
+ bufchain_prefix(&ssh->queued_incoming_data, &vdata, &len);
+ data = vdata;
+ origlen = len;
+
+ while (!ssh->frozen && len > 0)
+ ssh_process_incoming_data(ssh, &data, &len);
+
+ if (origlen > len)
+ bufchain_consume(&ssh->queued_incoming_data, origlen - len);
+ }
+}
+
+static void ssh_set_frozen(Ssh ssh, int frozen)
+{
+ if (ssh->s)
+ sk_set_frozen(ssh->s, frozen);
+ ssh->frozen = frozen;
+}
+
+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);
+
+ crBegin(ssh->ssh_gotdata_crstate);
+
+ /*
+ * To begin with, feed the characters one by one to the
+ * protocol initialisation / selection function do_ssh_init().
+ * When that returns 0, we're done with the initial greeting
+ * exchange and can move on to packet discipline.
+ */
+ while (1) {
+ int ret; /* need not be kept across crReturn */
+ if (datalen == 0)
+ crReturnV; /* more data please */
+ ret = do_ssh_init(ssh, *data);
+ data++;
+ datalen--;
+ if (ret == 0)
+ break;
+ }
+
+ /*
+ * We emerge from that loop when the initial negotiation is
+ * over and we have selected an s_rdpkt function. Now pass
+ * everything to s_rdpkt, and then pass the resulting packets
+ * to the proper protocol handler.
+ */
+
+ while (1) {
+ while (bufchain_size(&ssh->queued_incoming_data) > 0 || datalen > 0) {
+ if (ssh->frozen) {
+ ssh_queue_incoming_data(ssh, &data, &datalen);
+ /* This uses up all data and cannot cause anything interesting
+ * to happen; indeed, for anything to happen at all, we must
+ * return, so break out. */
+ break;
+ } else if (bufchain_size(&ssh->queued_incoming_data) > 0) {
+ /* This uses up some or all data, and may freeze the
+ * session. */
+ ssh_process_queued_incoming_data(ssh);
+ } else {
+ /* This uses up some or all data, and may freeze the
+ * session. */
+ ssh_process_incoming_data(ssh, &data, &datalen);
+ }
+ /* FIXME this is probably EBW. */
+ if (ssh->state == SSH_STATE_CLOSED)
+ return;
+ }
+ /* We're out of data. Go and get some more. */
+ crReturnV;
+ }
+ crFinishV;
+}
+
+static int ssh_do_close(Ssh ssh, int notify_exit)
+{
+ int ret = 0;
+ struct ssh_channel *c;
+
+ ssh->state = SSH_STATE_CLOSED;
+ expire_timer_context(ssh);
+ if (ssh->s) {
+ sk_close(ssh->s);
+ ssh->s = NULL;
+ if (notify_exit)
+ notify_remote_exit(ssh->frontend);
+ else
+ ret = 1;
+ }
+ /*
+ * Now we must shut down any port- and X-forwarded channels going
+ * through this connection.
+ */
+ if (ssh->channels) {
+ while (NULL != (c = index234(ssh->channels, 0))) {
+ switch (c->type) {
+ case CHAN_X11:
+ x11_close(c->u.x11.s);
+ break;
+ case CHAN_SOCKDATA:
+ case CHAN_SOCKDATA_DORMANT:
+ pfd_close(c->u.pfd.s);
+ break;
+ }
+ del234(ssh->channels, c); /* moving next one to index 0 */
+ if (ssh->version == 2)
+ bufchain_clear(&c->v.v2.outbuffer);
+ sfree(c);
+ }
+ }
+ /*
+ * Go through port-forwardings, and close any associated
+ * listening sockets.
+ */
+ 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);
+ del234(ssh->portfwds, pf); /* moving next one to index 0 */
+ free_portfwd(pf);
+ }
+ freetree234(ssh->portfwds);
+ ssh->portfwds = NULL;
+ }
+
+ return ret;
+}
+
+static void ssh_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);
+}
+
+static int ssh_closing(Plug plug, const char *error_msg, int error_code,
+ int calling_back)
+{
+ Ssh ssh = (Ssh) plug;
+ int need_notify = ssh_do_close(ssh, FALSE);
+
+ if (!error_msg) {
+ if (!ssh->close_expected)
+ error_msg = "Server unexpectedly closed network connection";
+ else
+ error_msg = "Server closed network connection";
+ }
+
+ if (ssh->close_expected && ssh->clean_exit && ssh->exitcode < 0)
+ ssh->exitcode = 0;
+
+ if (need_notify)
+ notify_remote_exit(ssh->frontend);
+
+ if (error_msg)
+ logevent(error_msg);
+ if (!ssh->close_expected || !ssh->clean_exit)
+ connection_fatal(ssh->frontend, "%s", error_msg);
+ return 0;
+}
+
+static int ssh_receive(Plug plug, int urgent, char *data, int len)
+{
+ Ssh ssh = (Ssh) plug;
+ ssh_gotdata(ssh, (unsigned char *)data, len);
+ if (ssh->state == SSH_STATE_CLOSED) {
+ ssh_do_close(ssh, TRUE);
+ return 0;
+ }
+ return 1;
+}
+
+static void ssh_sent(Plug plug, int bufsize)
+{
+ Ssh ssh = (Ssh) plug;
+ /*
+ * If the send backlog on the SSH socket itself clears, we
+ * should unthrottle the whole world if it was throttled.
+ */
+ if (bufsize < SSH_MAX_BACKLOG)
+ ssh_throttle_all(ssh, 0, bufsize);
+}
+
+/*
+ * Connect to specified host and port.
+ * 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 *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_closing,
+ ssh_receive,
+ ssh_sent,
+ NULL
+ };
+
+ SockAddr addr;
+ const char *err;
+
+ if (*ssh->cfg.loghost) {
+ char *colon;
+
+ ssh->savedhost = dupstr(ssh->cfg.loghost);
+ ssh->savedport = 22; /* default ssh port */
+
+ /*
+ * A colon suffix on savedhost also lets us affect
+ * savedport.
+ *
+ * (FIXME: do something about IPv6 address literals here.)
+ */
+ colon = strrchr(ssh->savedhost, ':');
+ if (colon) {
+ *colon++ = '\0';
+ if (*colon)
+ ssh->savedport = atoi(colon);
+ }
+ } else {
+ ssh->savedhost = dupstr(host);
+ 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;
+ }
+
+ /*
+ * 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)
+ ssh->version = 1;
+ if (ssh->cfg.sshprot == 3) {
+ ssh->version = 2;
+ ssh_send_verstring(ssh, NULL);
+ }
+
+ /*
+ * loghost, if configured, overrides realhost.
+ */
+ if (*ssh->cfg.loghost) {
+ sfree(*realhost);
+ *realhost = dupstr(ssh->cfg.loghost);
+ }
+
+ return NULL;
+}
+
+/*
+ * Throttle or unthrottle the SSH connection.
+ */
+static void ssh_throttle_conn(Ssh ssh, int adjust)
+{
+ int old_count = ssh->conn_throttle_count;
+ ssh->conn_throttle_count += adjust;
+ assert(ssh->conn_throttle_count >= 0);
+ if (ssh->conn_throttle_count && !old_count) {
+ ssh_set_frozen(ssh, 1);
+ } else if (!ssh->conn_throttle_count && old_count) {
+ ssh_set_frozen(ssh, 0);
+ }
+}
+
+/*
+ * Throttle or unthrottle _all_ local data streams (for when sends
+ * on the SSH connection itself back up).
+ */
+static void ssh_throttle_all(Ssh ssh, int enable, int bufsize)
+{
+ int i;
+ struct ssh_channel *c;
+
+ if (enable == ssh->throttled_all)
+ return;
+ ssh->throttled_all = enable;
+ ssh->overall_bufsize = bufsize;
+ if (!ssh->channels)
+ return;
+ for (i = 0; NULL != (c = index234(ssh->channels, i)); i++) {
+ switch (c->type) {
+ case CHAN_MAINSESSION:
+ /*
+ * This is treated separately, outside the switch.
+ */
+ break;
+ case CHAN_X11:
+ x11_override_throttle(c->u.x11.s, enable);
+ break;
+ case CHAN_AGENT:
+ /* Agent channels require no buffer management. */
+ break;
+ case CHAN_SOCKDATA:
+ pfd_override_throttle(c->u.pfd.s, enable);
+ break;
+ }
+ }
+}
+
+static void ssh_agent_callback(void *sshv, void *reply, int replylen)
+{
+ Ssh ssh = (Ssh) sshv;
+
+ ssh->agent_response = reply;
+ ssh->agent_response_len = replylen;
+
+ if (ssh->version == 1)
+ do_ssh1_login(ssh, NULL, -1, NULL);
+ else
+ do_ssh2_authconn(ssh, NULL, -1, NULL);
+}
+
+static void ssh_dialog_callback(void *sshv, int ret)
+{
+ Ssh ssh = (Ssh) sshv;
+
+ ssh->user_response = ret;
+
+ if (ssh->version == 1)
+ do_ssh1_login(ssh, NULL, -1, NULL);
+ else
+ do_ssh2_transport(ssh, NULL, -1, NULL);
+
+ /*
+ * This may have unfrozen the SSH connection, so do a
+ * queued-data run.
+ */
+ ssh_process_queued_incoming_data(ssh);
+}
+
+static void ssh_agentf_callback(void *cv, void *reply, int replylen)
+{
+ struct ssh_channel *c = (struct ssh_channel *)cv;
+ Ssh ssh = c->ssh;
+ void *sentreply = reply;
+
+ if (!sentreply) {
+ /* Fake SSH_AGENT_FAILURE. */
+ sentreply = "\0\0\0\1\5";
+ replylen = 5;
+ }
+ if (ssh->version == 2) {
+ ssh2_add_channel_data(c, sentreply, replylen);
+ 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);
+}
+
+/*
+ * Client-initiated disconnection. Send a DISCONNECT if `wire_reason'
+ * non-NULL, otherwise just close the connection. `client_reason' == NULL
+ * => log `wire_reason'.
+ */
+static void ssh_disconnect(Ssh ssh, char *client_reason, char *wire_reason,
+ int code, int clean_exit)
+{
+ char *error;
+ if (!client_reason)
+ client_reason = wire_reason;
+ if (client_reason)
+ error = dupprintf("Disconnected: %s", client_reason);
+ else
+ error = dupstr("Disconnected");
+ if (wire_reason) {
+ if (ssh->version == 1) {
+ send_packet(ssh, SSH1_MSG_DISCONNECT, PKT_STR, wire_reason,
+ PKT_END);
+ } else if (ssh->version == 2) {
+ struct Packet *pktout = ssh2_pkt_init(SSH2_MSG_DISCONNECT);
+ ssh2_pkt_adduint32(pktout, code);
+ ssh2_pkt_addstring(pktout, wire_reason);
+ ssh2_pkt_addstring(pktout, "en"); /* language tag */
+ ssh2_pkt_send_noqueue(ssh, pktout);
+ }
+ }
+ ssh->close_expected = TRUE;
+ ssh->clean_exit = clean_exit;
+ ssh_closing((Plug)ssh, error, 0, 0);
+ sfree(error);
+}
+
+/*
+ * Handle the key exchange and user authentication phases.
+ */
+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 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;
+ char c;
+ int pwpkt_type;
+ unsigned char request[5], *response, *p;
+ int responselen;
+ int keyi, nkeys;
+ int authed;
+ struct RSAKey key;
+ Bignum challenge;
+ char *commentp;
+ int commentlen;
+ int dlgret;
+ };
+ crState(do_ssh1_login_state);
+
+ crBegin(ssh->do_ssh1_login_crstate);
+
+ if (!pktin)
+ crWaitUntil(pktin);
+
+ if (pktin->type != SSH1_SMSG_PUBLIC_KEY) {
+ bombout(("Public key packet not received"));
+ crStop(0);
+ }
+
+ logevent("Received public keys");
+
+ ptr = ssh_pkt_getdata(pktin, 8);
+ if (!ptr) {
+ 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)) {
+ bombout(("Failed to read SSH-1 public keys from public key packet"));
+ crStop(0);
+ }
+
+ /*
+ * Log the host key fingerprint.
+ */
+ {
+ char logmsg[80];
+ logevent("Host key fingerprint is:");
+ strcpy(logmsg, " ");
+ hostkey.comment = NULL;
+ rsa_fingerprint(logmsg + strlen(logmsg),
+ sizeof(logmsg) - strlen(logmsg), &hostkey);
+ logevent(logmsg);
+ }
+
+ ssh->v1_remote_protoflags = ssh_pkt_getuint32(pktin);
+ s->supported_ciphers_mask = ssh_pkt_getuint32(pktin);
+ s->supported_auths_mask = ssh_pkt_getuint32(pktin);
+ if ((ssh->remote_bugs & BUG_CHOKES_ON_RSA))
+ s->supported_auths_mask &= ~(1 << SSH1_AUTH_RSA);
+
+ 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, 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) {
+ bombout(("SSH-1 public keys were badly formatted"));
+ crStop(0);
+ }
+
+ s->len = (hostkey.bytes > servkey.bytes ? hostkey.bytes : servkey.bytes);
+
+ s->rsabuf = snewn(s->len, unsigned char);
+
+ /*
+ * Verify the host key.
+ */
+ {
+ /*
+ * First format the key into a string.
+ */
+ int len = rsastr_len(&hostkey);
+ char fingerprint[100];
+ char *keystr = snewn(len, char);
+ rsastr_fmt(keystr, &hostkey);
+ rsa_fingerprint(fingerprint, sizeof(fingerprint), &hostkey);
+
+ ssh_set_frozen(ssh, 1);
+ s->dlgret = verify_ssh_host_key(ssh->frontend,
+ ssh->savedhost, ssh->savedport,
+ "rsa", keystr, fingerprint,
+ ssh_dialog_callback, ssh);
+ sfree(keystr);
+ 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);
+ }
+ }
+
+ for (i = 0; i < 32; i++) {
+ 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 (!ret) {
+ bombout(("SSH-1 public key encryptions failed due to bad formatting"));
+ crStop(0);
+ }
+
+ logevent("Encrypted session key");
+
+ {
+ 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];
+ 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. */
+ logevent("AES not supported in SSH-1, skipping");
+ } else {
+ switch (next_cipher) {
+ case CIPHER_3DES: s->cipher_type = SSH_CIPHER_3DES;
+ cipher_string = "3DES"; break;
+ case CIPHER_BLOWFISH: s->cipher_type = SSH_CIPHER_BLOWFISH;
+ cipher_string = "Blowfish"; break;
+ case CIPHER_DES: s->cipher_type = SSH_CIPHER_DES;
+ cipher_string = "single-DES"; break;
+ }
+ if (s->supported_ciphers_mask & (1 << s->cipher_type))
+ cipher_chosen = 1;
+ }
+ }
+ if (!cipher_chosen) {
+ if ((s->supported_ciphers_mask & (1 << SSH_CIPHER_3DES)) == 0)
+ bombout(("Server violates SSH-1 protocol by not "
+ "supporting 3DES encryption"));
+ else
+ /* shouldn't happen */
+ bombout(("No supported ciphers found"));
+ crStop(0);
+ }
+
+ /* Warn about chosen cipher if necessary. */
+ if (warn) {
+ ssh_set_frozen(ssh, 1);
+ s->dlgret = askalg(ssh->frontend, "cipher", cipher_string,
+ ssh_dialog_callback, ssh);
+ if (s->dlgret < 0) {
+ do {
+ crReturn(0);
+ if (pktin) {
+ bombout(("Unexpected data from server while waiting"
+ " for user 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 cipher warning", NULL,
+ 0, TRUE);
+ crStop(0);
+ }
+ }
+ }
+
+ switch (s->cipher_type) {
+ case SSH_CIPHER_3DES:
+ logevent("Using 3DES encryption");
+ break;
+ case SSH_CIPHER_DES:
+ logevent("Using single-DES encryption");
+ break;
+ case SSH_CIPHER_BLOWFISH:
+ logevent("Using Blowfish encryption");
+ break;
+ }
+
+ send_packet(ssh, SSH1_CMSG_SESSION_KEY,
+ PKT_CHAR, s->cipher_type,
+ PKT_DATA, cookie, 8,
+ PKT_CHAR, (s->len * 8) >> 8, PKT_CHAR, (s->len * 8) & 0xFF,
+ PKT_DATA, s->rsabuf, s->len,
+ PKT_INT, ssh->v1_local_protoflags, PKT_END);
+
+ logevent("Trying to enable encryption...");
+
+ sfree(s->rsabuf);
+
+ ssh->cipher = (s->cipher_type == SSH_CIPHER_BLOWFISH ? &ssh_blowfish_ssh1 :
+ s->cipher_type == SSH_CIPHER_DES ? &ssh_des :
+ &ssh_3des);
+ ssh->v1_cipher_ctx = ssh->cipher->make_context();
+ ssh->cipher->sesskey(ssh->v1_cipher_ctx, ssh->session_key);
+ 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;
+ }
+ crWaitUntil(pktin);
+
+ if (pktin->type != SSH1_SMSG_SUCCESS) {
+ bombout(("Encryption not successfully enabled"));
+ crStop(0);
+ }
+
+ logevent("Successfully started encryption");
+
+ fflush(stdout); /* FIXME eh? */
+ {
+ if (!get_remote_username(&ssh->cfg, s->username,
+ sizeof(s->username))) {
+ 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));
+ 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);
+ ssh->send_ok = 0;
+ }
+ if (!ret) {
+ /*
+ * Failed to get a username. Terminate.
+ */
+ 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));
+ free_prompts(s->cur_prompt);
+ }
+
+ send_packet(ssh, SSH1_CMSG_USER, PKT_STR, s->username, PKT_END);
+ {
+ char *userlog = dupprintf("Sent username \"%s\"", s->username);
+ logevent(userlog);
+ if (flags & FLAG_INTERACTIVE &&
+ (!((flags & FLAG_STDERR) && (flags & FLAG_VERBOSE)))) {
+ c_write_str(ssh, userlog);
+ c_write_str(ssh, "\r\n");
+ }
+ sfree(userlog);
+ }
+ }
+
+ crWaitUntil(pktin);
+
+ if ((s->supported_auths_mask & (1 << SSH1_AUTH_RSA)) == 0) {
+ /* We must not attempt PK auth. Pretend we've already tried it. */
+ s->tried_publickey = s->tried_agent = 1;
+ } else {
+ s->tried_publickey = s->tried_agent = 0;
+ }
+ 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)) {
+ int keytype;
+ logeventf(ssh, "Reading private key file \"%.150s\"",
+ filename_to_str(&ssh->cfg.keyfile));
+ keytype = key_type(&ssh->cfg.keyfile);
+ if (keytype == SSH_KEYTYPE_SSH1) {
+ const char *error;
+ if (rsakey_pubblob(&ssh->cfg.keyfile,
+ &s->publickey_blob, &s->publickey_bloblen,
+ &s->publickey_comment, &error)) {
+ s->publickey_encrypted = rsakey_encrypted(&ssh->cfg.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),
+ error);
+ c_write_str(ssh, msgbuf);
+ sfree(msgbuf);
+ s->publickey_blob = NULL;
+ }
+ } else {
+ 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),
+ key_type_to_str(keytype));
+ c_write_str(ssh, msgbuf);
+ sfree(msgbuf);
+ s->publickey_blob = NULL;
+ }
+ } else
+ 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) {
+ /*
+ * Attempt RSA authentication using Pageant.
+ */
+ void *r;
+
+ s->authed = FALSE;
+ s->tried_agent = 1;
+ logevent("Pageant is running. Requesting keys.");
+
+ /* Request the keys held by the agent. */
+ PUT_32BIT(s->request, 1);
+ s->request[4] = SSH1_AGENTC_REQUEST_RSA_IDENTITIES;
+ if (!agent_query(s->request, 5, &r, &s->responselen,
+ ssh_agent_callback, ssh)) {
+ do {
+ crReturn(0);
+ if (pktin) {
+ bombout(("Unexpected data from server while waiting"
+ " for agent response"));
+ crStop(0);
+ }
+ } while (pktin || inlen > 0);
+ r = ssh->agent_response;
+ s->responselen = ssh->agent_response_len;
+ }
+ 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->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->key.exponent);
+ if (n < 0)
+ break;
+ s->p += n;
+ n = ssh1_read_bignum
+ (s->p, s->responselen-(s->p-s->response),
+ &s->key.modulus);
+ if (n < 0)
+ break;
+ s->p += n;
+ if (s->responselen - (s->p-s->response) < 4)
+ break;
+ s->commentlen = GET_32BIT(s->p);
+ s->p += 4;
+ if (s->responselen - (s->p-s->response) <
+ s->commentlen)
+ break;
+ s->commentp = (char *)s->p;
+ s->p += s->commentlen;
+ ok = TRUE;
+ } while (0);
+ if (!ok) {
+ logevent("Pageant key list packet was truncated");
+ break;
+ }
+ }
+ if (s->publickey_blob) {
+ if (!memcmp(pkblob, s->publickey_blob,
+ s->publickey_bloblen)) {
+ logeventf(ssh, "Pageant key #%d matches "
+ "configured key file", s->keyi);
+ s->tried_publickey = 1;
+ } else
+ /* Skip non-configured key */
+ continue;
+ }
+ logeventf(ssh, "Trying Pageant key #%d", s->keyi);
+ send_packet(ssh, SSH1_CMSG_AUTH_RSA,
+ PKT_BIGNUM, s->key.modulus, PKT_END);
+ crWaitUntil(pktin);
+ if (pktin->type != SSH1_SMSG_AUTH_RSA_CHALLENGE) {
+ logevent("Key refused");
+ continue;
+ }
+ logevent("Received RSA challenge");
+ if ((s->challenge = ssh1_pkt_getmp(pktin)) == NULL) {
+ bombout(("Server's RSA challenge was badly formatted"));
+ crStop(0);
+ }
+
+ {
+ char *agentreq, *q, *ret;
+ void *vret;
+ int len, retlen;
+ len = 1 + 4; /* message type, bit count */
+ len += ssh1_bignum_length(s->key.exponent);
+ len += ssh1_bignum_length(s->key.modulus);
+ len += ssh1_bignum_length(s->challenge);
+ len += 16; /* session id */
+ len += 4; /* response format */
+ agentreq = snewn(4 + len, char);
+ PUT_32BIT(agentreq, len);
+ q = agentreq + 4;
+ *q++ = SSH1_AGENTC_RSA_CHALLENGE;
+ PUT_32BIT(q, bignum_bitcount(s->key.modulus));
+ q += 4;
+ q += ssh1_write_bignum(q, s->key.exponent);
+ q += ssh1_write_bignum(q, s->key.modulus);
+ q += ssh1_write_bignum(q, s->challenge);
+ memcpy(q, s->session_id, 16);
+ q += 16;
+ PUT_32BIT(q, 1); /* response format */
+ if (!agent_query(agentreq, len + 4, &vret, &retlen,
+ ssh_agent_callback, ssh)) {
+ sfree(agentreq);
+ do {
+ crReturn(0);
+ if (pktin) {
+ bombout(("Unexpected data from server"
+ " while waiting for agent"
+ " response"));
+ crStop(0);
+ }
+ } while (pktin || inlen > 0);
+ vret = ssh->agent_response;
+ retlen = ssh->agent_response_len;
+ } else
+ sfree(agentreq);
+ ret = vret;
+ if (ret) {
+ if (ret[4] == SSH1_AGENT_RSA_RESPONSE) {
+ logevent("Sending Pageant's response");
+ send_packet(ssh, SSH1_CMSG_AUTH_RSA_RESPONSE,
+ PKT_DATA, ret + 5, 16,
+ PKT_END);
+ sfree(ret);
+ crWaitUntil(pktin);
+ if (pktin->type == SSH1_SMSG_SUCCESS) {
+ logevent
+ ("Pageant's response accepted");
+ if (flags & FLAG_VERBOSE) {
+ c_write_str(ssh, "Authenticated using"
+ " RSA key \"");
+ c_write(ssh, s->commentp,
+ s->commentlen);
+ c_write_str(ssh, "\" from agent\r\n");
+ }
+ s->authed = TRUE;
+ } else
+ logevent
+ ("Pageant's response not accepted");
+ } else {
+ logevent
+ ("Pageant failed to answer challenge");
+ sfree(ret);
+ }
+ } else {
+ logevent("No reply received from Pageant");
+ }
+ }
+ freebn(s->key.exponent);
+ freebn(s->key.modulus);
+ freebn(s->challenge);
+ if (s->authed)
+ break;
+ }
+ sfree(s->response);
+ if (s->publickey_blob && !s->tried_publickey)
+ logevent("Configured key file not in Pageant");
+ } else {
+ logevent("Failed to get reply from Pageant");
+ }
+ if (s->authed)
+ break;
+ }
+ if (s->publickey_blob && !s->tried_publickey) {
+ /*
+ * Try public key authentication with the specified
+ * 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");
+ logeventf(ssh, "Trying public key \"%s\"",
+ filename_to_str(&ssh->cfg.keyfile));
+ s->tried_publickey = 1;
+ got_passphrase = FALSE;
+ while (!got_passphrase) {
+ /*
+ * Get a passphrase, if necessary.
+ */
+ char *passphrase = NULL; /* only written after crReturn */
+ const char *error;
+ if (!s->publickey_encrypted) {
+ if (flags & FLAG_VERBOSE)
+ c_write_str(ssh, "No passphrase required.\r\n");
+ passphrase = NULL;
+ } else {
+ 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");
+ add_prompt(s->cur_prompt,
+ dupprintf("Passphrase for key \"%.100s\": ",
+ s->publickey_comment),
+ FALSE, SSH_MAX_PASSWORD_LEN);
+ 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);
+ ssh->send_ok = 0;
+ }
+ if (!ret) {
+ /* Failed to get a passphrase. Terminate. */
+ free_prompts(s->cur_prompt);
+ ssh_disconnect(ssh, NULL, "Unable to authenticate",
+ 0, TRUE);
+ crStop(0);
+ }
+ passphrase = dupstr(s->cur_prompt->prompts[0]->result);
+ free_prompts(s->cur_prompt);
+ }
+ /*
+ * Try decrypting key with passphrase.
+ */
+ ret = loadrsakey(&ssh->cfg.keyfile, &s->key, passphrase,
+ &error);
+ if (passphrase) {
+ memset(passphrase, 0, 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, " (");
+ c_write_str(ssh, error);
+ c_write_str(ssh, ").\r\n");
+ got_passphrase = FALSE;
+ break; /* go and try something else */
+ } else if (ret == -1) {
+ c_write_str(ssh, "Wrong passphrase.\r\n"); /* FIXME */
+ got_passphrase = FALSE;
+ /* and try again */
+ } else {
+ assert(0 && "unexpected return from loadrsakey()");
+ got_passphrase = FALSE; /* placate optimisers */
+ }
+ }
+
+ if (got_passphrase) {
+
+ /*
+ * Send a public key attempt.
+ */
+ send_packet(ssh, SSH1_CMSG_AUTH_RSA,
+ PKT_BIGNUM, s->key.modulus, PKT_END);
+
+ crWaitUntil(pktin);
+ if (pktin->type == SSH1_SMSG_FAILURE) {
+ c_write_str(ssh, "Server refused our public key.\r\n");
+ continue; /* go and try something else */
+ }
+ if (pktin->type != SSH1_SMSG_AUTH_RSA_CHALLENGE) {
+ bombout(("Bizarre response to offer of public key"));
+ crStop(0);
+ }
+
+ {
+ int i;
+ unsigned char buffer[32];
+ Bignum challenge, response;
+
+ if ((challenge = ssh1_pkt_getmp(pktin)) == NULL) {
+ bombout(("Server's RSA challenge was badly formatted"));
+ crStop(0);
+ }
+ response = rsadecrypt(challenge, &s->key);
+ freebn(s->key.private_exponent);/* burn the evidence */
+
+ for (i = 0; i < 32; i++) {
+ buffer[i] = bignum_byte(response, 31 - i);
+ }
+
+ MD5Init(&md5c);
+ MD5Update(&md5c, buffer, 32);
+ MD5Update(&md5c, s->session_id, 16);
+ MD5Final(buffer, &md5c);
+
+ send_packet(ssh, SSH1_CMSG_AUTH_RSA_RESPONSE,
+ PKT_DATA, buffer, 16, PKT_END);
+
+ freebn(challenge);
+ freebn(response);
+ }
+
+ crWaitUntil(pktin);
+ if (pktin->type == SSH1_SMSG_FAILURE) {
+ if (flags & FLAG_VERBOSE)
+ c_write_str(ssh, "Failed to authenticate with"
+ " our public key.\r\n");
+ continue; /* go and try something else */
+ } else if (pktin->type != SSH1_SMSG_SUCCESS) {
+ bombout(("Bizarre response to RSA authentication response"));
+ crStop(0);
+ }
+
+ break; /* we're through! */
+ }
+
+ }
+
+ /*
+ * Otherwise, try various forms of password-like authentication.
+ */
+ s->cur_prompt = new_prompts(ssh->frontend);
+
+ if (ssh->cfg.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);
+ crWaitUntil(pktin);
+ if (pktin->type != SSH1_SMSG_AUTH_TIS_CHALLENGE) {
+ logevent("TIS authentication declined");
+ if (flags & FLAG_INTERACTIVE)
+ c_write_str(ssh, "TIS authentication refused.\r\n");
+ s->tis_auth_refused = 1;
+ continue;
+ } else {
+ char *challenge;
+ int challengelen;
+ char *instr_suf, *prompt;
+
+ ssh_pkt_getstring(pktin, &challenge, &challengelen);
+ if (!challenge) {
+ bombout(("TIS challenge packet was badly formed"));
+ crStop(0);
+ }
+ logevent("Received TIS challenge");
+ s->cur_prompt->to_server = TRUE;
+ s->cur_prompt->name = dupstr("SSH TIS authentication");
+ /* Prompt heuristic comes from OpenSSH */
+ if (memchr(challenge, '\n', challengelen)) {
+ instr_suf = dupstr("");
+ prompt = dupprintf("%.*s", challengelen, challenge);
+ } else {
+ instr_suf = dupprintf("%.*s", challengelen, challenge);
+ prompt = dupstr("Response: ");
+ }
+ 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);
+ sfree(instr_suf);
+ }
+ }
+ if (ssh->cfg.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);
+ crWaitUntil(pktin);
+ if (pktin->type != SSH1_SMSG_AUTH_CCARD_CHALLENGE) {
+ logevent("CryptoCard authentication declined");
+ c_write_str(ssh, "CryptoCard authentication refused.\r\n");
+ s->ccard_auth_refused = 1;
+ continue;
+ } else {
+ char *challenge;
+ int challengelen;
+ char *instr_suf, *prompt;
+
+ ssh_pkt_getstring(pktin, &challenge, &challengelen);
+ if (!challenge) {
+ bombout(("CryptoCard challenge packet was badly formed"));
+ crStop(0);
+ }
+ logevent("Received CryptoCard challenge");
+ s->cur_prompt->to_server = TRUE;
+ s->cur_prompt->name = dupstr("SSH CryptoCard authentication");
+ s->cur_prompt->name_reqd = FALSE;
+ /* Prompt heuristic comes from OpenSSH */
+ if (memchr(challenge, '\n', challengelen)) {
+ instr_suf = dupstr("");
+ prompt = dupprintf("%.*s", challengelen, challenge);
+ } else {
+ instr_suf = dupprintf("%.*s", challengelen, challenge);
+ prompt = dupstr("Response: ");
+ }
+ 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);
+ sfree(instr_suf);
+ }
+ }
+ if (s->pwpkt_type == SSH1_CMSG_AUTH_PASSWORD) {
+ if ((s->supported_auths_mask & (1 << SSH1_AUTH_PASSWORD)) == 0) {
+ 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);
+ }
+
+ /*
+ * Show password prompt, having first obtained it via a TIS
+ * or CryptoCard exchange if we're doing TIS or CryptoCard
+ * authentication.
+ */
+ {
+ int ret; /* need not be kept over crReturn */
+ 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);
+ ssh->send_ok = 0;
+ }
+ if (!ret) {
+ /*
+ * Failed to get a password (for example
+ * because one was supplied on the command line
+ * which has already failed to work). Terminate.
+ */
+ free_prompts(s->cur_prompt);
+ ssh_disconnect(ssh, NULL, "Unable to authenticate", 0, TRUE);
+ crStop(0);
+ }
+ }
+
+ if (s->pwpkt_type == SSH1_CMSG_AUTH_PASSWORD) {
+ /*
+ * Defence against traffic analysis: we send a
+ * whole bunch of packets containing strings of
+ * different lengths. One of these strings is the
+ * password, in a SSH1_CMSG_AUTH_PASSWORD packet.
+ * The others are all random data in
+ * SSH1_MSG_IGNORE packets. This way a passive
+ * listener can't tell which is the password, and
+ * hence can't deduce the password length.
+ *
+ * Anybody with a password length greater than 16
+ * bytes is going to have enough entropy in their
+ * password that a listener won't find it _that_
+ * much help to know how long it is. So what we'll
+ * do is:
+ *
+ * - if password length < 16, we send 15 packets
+ * containing string lengths 1 through 15
+ *
+ * - otherwise, we let N be the nearest multiple
+ * of 8 below the password length, and send 8
+ * packets containing string lengths N through
+ * N+7. This won't obscure the order of
+ * magnitude of the password length, but it will
+ * introduce a bit of extra uncertainty.
+ *
+ * A few servers can't deal with SSH1_MSG_IGNORE, at
+ * least in this context. For these servers, we need
+ * an alternative defence. We make use of the fact
+ * that the password is interpreted as a C string:
+ * so we can append a NUL, then some random data.
+ *
+ * A few servers can deal with neither SSH1_MSG_IGNORE
+ * here _nor_ a padded password string.
+ * For these servers we are left with no defences
+ * against password length sniffing.
+ */
+ if (!(ssh->remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE) &&
+ !(ssh->remote_bugs & BUG_NEEDS_SSH1_PLAIN_PASSWORD)) {
+ /*
+ * The server can deal with SSH1_MSG_IGNORE, so
+ * we can use the primary defence.
+ */
+ int bottom, top, pwlen, i;
+ char *randomstr;
+
+ pwlen = strlen(s->cur_prompt->prompts[0]->result);
+ if (pwlen < 16) {
+ bottom = 0; /* zero length passwords are OK! :-) */
+ top = 15;
+ } else {
+ bottom = pwlen & ~7;
+ top = bottom + 7;
+ }
+
+ assert(pwlen >= bottom && pwlen <= top);
+
+ 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);
+ } else {
+ for (j = 0; j < i; j++) {
+ do {
+ randomstr[j] = random_byte();
+ } while (randomstr[j] == '\0');
+ }
+ randomstr[i] = '\0';
+ defer_packet(ssh, SSH1_MSG_IGNORE,
+ PKT_STR, randomstr, PKT_END);
+ }
+ }
+ logevent("Sending password with camouflage packets");
+ ssh_pkt_defersend(ssh);
+ sfree(randomstr);
+ }
+ else if (!(ssh->remote_bugs & BUG_NEEDS_SSH1_PLAIN_PASSWORD)) {
+ /*
+ * The server can't deal with SSH1_MSG_IGNORE
+ * but can deal with padded passwords, so we
+ * can use the secondary defence.
+ */
+ char string[64];
+ char *ss;
+ int len;
+
+ len = strlen(s->cur_prompt->prompts[0]->result);
+ if (len < sizeof(string)) {
+ ss = string;
+ strcpy(string, s->cur_prompt->prompts[0]->result);
+ len++; /* cover the zero byte */
+ while (len < sizeof(string)) {
+ string[len++] = (char) random_byte();
+ }
+ } else {
+ ss = s->cur_prompt->prompts[0]->result;
+ }
+ logevent("Sending length-padded password");
+ send_packet(ssh, s->pwpkt_type, PKTT_PASSWORD,
+ PKT_INT, len, PKT_DATA, ss, len,
+ PKTT_OTHER, 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_DATA, s->cur_prompt->prompts[0]->result, len,
+ PKTT_OTHER, PKT_END);
+ }
+ } else {
+ send_packet(ssh, s->pwpkt_type, PKTT_PASSWORD,
+ PKT_STR, s->cur_prompt->prompts[0]->result,
+ PKTT_OTHER, PKT_END);
+ }
+ logevent("Sent password");
+ free_prompts(s->cur_prompt);
+ crWaitUntil(pktin);
+ if (pktin->type == SSH1_SMSG_FAILURE) {
+ if (flags & FLAG_VERBOSE)
+ c_write_str(ssh, "Access denied\r\n");
+ logevent("Authentication refused");
+ } else if (pktin->type != SSH1_SMSG_SUCCESS) {
+ bombout(("Strange packet received, type %d", pktin->type));
+ crStop(0);
+ }
+ }
+
+ /* Clear up */
+ if (s->publickey_blob) {
+ sfree(s->publickey_blob);
+ sfree(s->publickey_comment);
+ }
+
+ logevent("Authentication successful");
+
+ crFinish(1);
+}
+
+void sshfwd_close(struct ssh_channel *c)
+{
+ 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");
+ }
+ }
+}
+
+int sshfwd_write(struct ssh_channel *c, char *buf, int len)
+{
+ Ssh ssh = c->ssh;
+
+ if (ssh->state == SSH_STATE_CLOSED)
+ 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);
+ /*
+ * 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
+ * _everything_ will be throttled as a whole.
+ */
+ return 0;
+ } else {
+ ssh2_add_channel_data(c, buf, len);
+ return ssh2_try_send(c);
+ }
+}
+
+void sshfwd_unthrottle(struct ssh_channel *c, int bufsize)
+{
+ Ssh ssh = c->ssh;
+ int buflimit;
+
+ if (ssh->state == SSH_STATE_CLOSED)
+ return;
+
+ if (ssh->version == 1) {
+ buflimit = SSH1_BUFFER_LIMIT;
+ } else {
+ buflimit = c->v.v2.locmaxwin;
+ ssh2_set_window(c, bufsize < buflimit ? buflimit - bufsize : 0);
+ }
+ if (c->throttling_conn && bufsize <= buflimit) {
+ c->throttling_conn = 0;
+ ssh_throttle_conn(ssh, -1);
+ }
+}
+
+static void ssh_queueing_handler(Ssh ssh, struct Packet *pktin)
+{
+ struct queued_handler *qh = ssh->qhead;
+
+ assert(qh != NULL);
+
+ 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;
+ }
+ if (qh->msg2 > 0) {
+ assert(ssh->packet_dispatch[qh->msg2] == ssh_queueing_handler);
+ ssh->packet_dispatch[qh->msg2] = NULL;
+ }
+
+ if (qh->next) {
+ ssh->qhead = qh->next;
+
+ if (ssh->qhead->msg1 > 0) {
+ assert(ssh->packet_dispatch[ssh->qhead->msg1] == NULL);
+ ssh->packet_dispatch[ssh->qhead->msg1] = ssh_queueing_handler;
+ }
+ if (ssh->qhead->msg2 > 0) {
+ assert(ssh->packet_dispatch[ssh->qhead->msg2] == NULL);
+ 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);
+}
+
+static void ssh_queue_handler(Ssh ssh, int msg1, int msg2,
+ chandler_fn_t handler, void *ctx)
+{
+ struct queued_handler *qh;
+
+ qh = snew(struct queued_handler);
+ qh->msg1 = msg1;
+ qh->msg2 = msg2;
+ qh->handler = handler;
+ qh->ctx = ctx;
+ qh->next = NULL;
+
+ if (ssh->qtail == NULL) {
+ ssh->qhead = qh;
+
+ if (qh->msg1 > 0) {
+ assert(ssh->packet_dispatch[qh->msg1] == NULL);
+ ssh->packet_dispatch[qh->msg1] = ssh_queueing_handler;
+ }
+ if (qh->msg2 > 0) {
+ assert(ssh->packet_dispatch[qh->msg2] == NULL);
+ ssh->packet_dispatch[qh->msg2] = ssh_queueing_handler;
+ }
+ } else {
+ ssh->qtail->next = qh;
+ }
+ ssh->qtail = qh;
+}
+
+static void ssh_rportfwd_succfail(Ssh ssh, struct Packet *pktin, void *ctx)
+{
+ struct ssh_rportfwd *rpf, *pf = (struct ssh_rportfwd *)ctx;
+
+ if (pktin->type == (ssh->version == 1 ? SSH1_SMSG_SUCCESS :
+ SSH2_MSG_REQUEST_SUCCESS)) {
+ logeventf(ssh, "Remote port forwarding from %s enabled",
+ pf->sportdesc);
+ } else {
+ logeventf(ssh, "Remote port forwarding from %s refused",
+ pf->sportdesc);
+
+ rpf = del234(ssh->rportfwds, pf);
+ assert(rpf == pf);
+ pf->pfrec->remote = NULL;
+ free_rportfwd(pf);
+ }
+}
+
+static void ssh_setup_portfwd(Ssh ssh, const Config *cfg)
+{
+ const char *portfwd_strptr = cfg->portfwd;
+ struct ssh_portfwd *epf;
+ int i;
+
+ if (!ssh->portfwds) {
+ ssh->portfwds = newtree234(ssh_portcmp);
+ } else {
+ /*
+ * Go through the existing port forwardings and tag them
+ * with status==DESTROY. Any that we want to keep will be
+ * re-enabled (status==KEEP) as we go through the
+ * configuration and find out which bits are the same as
+ * they were before.
+ */
+ struct ssh_portfwd *epf;
+ int i;
+ for (i = 0; (epf = index234(ssh->portfwds, i)) != NULL; i++)
+ epf->status = DESTROY;
+ }
+
+ while (*portfwd_strptr) {
+ char address_family, type;
+ int sport,dport,sserv,dserv;
+ char sports[256], dports[256], saddr[256], host[256];
+ int n;
+
+ 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);
+ }
+ }
+ } 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 */
+ }
+ sport = atoi(sports);
+ sserv = 0;
+ if (sport == 0) {
+ sserv = 1;
+ sport = net_service_lookup(sports);
+ if (!sport) {
+ logeventf(ssh, "Service lookup failed for source"
+ " port \"%s\"", sports);
+ }
+ }
+ 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->sserv = sserv ? dupstr(sports) : NULL;
+ pfrec->sport = sport;
+ pfrec->daddr = *host ? dupstr(host) : NULL;
+ pfrec->dserv = dserv ? dupstr(dports) : NULL;
+ pfrec->dport = dport;
+ pfrec->local = NULL;
+ pfrec->remote = NULL;
+ pfrec->addressfamily = (address_family == '4' ? ADDRTYPE_IPV4 :
+ address_family == '6' ? ADDRTYPE_IPV6 :
+ ADDRTYPE_UNSPEC);
+
+ epfrec = add234(ssh->portfwds, pfrec);
+ if (epfrec != pfrec) {
+ if (epfrec->status == DESTROY) {
+ /*
+ * We already have a port forwarding up and running
+ * with precisely these parameters. Hence, no need
+ * to do anything; simply re-tag the existing one
+ * as KEEP.
+ */
+ epfrec->status = KEEP;
+ }
+ /*
+ * Anything else indicates that there was a duplicate
+ * in our input, which we'll silently ignore.
+ */
+ free_portfwd(pfrec);
+ } else {
+ pfrec->status = CREATE;
+ }
+ }
+ }
+
+ /*
+ * Now go through and destroy any port forwardings which were
+ * not re-enabled.
+ */
+ for (i = 0; (epf = index234(ssh->portfwds, i)) != NULL; i++)
+ if (epf->status == DESTROY) {
+ char *message;
+
+ message = dupprintf("%s port forwarding from %s%s%d",
+ epf->type == 'L' ? "local" :
+ epf->type == 'R' ? "remote" : "dynamic",
+ epf->saddr ? epf->saddr : "",
+ epf->saddr ? ":" : "",
+ epf->sport);
+
+ if (epf->type != 'D') {
+ char *msg2 = dupprintf("%s to %s:%d", message,
+ epf->daddr, epf->dport);
+ sfree(message);
+ message = msg2;
+ }
+
+ logeventf(ssh, "Cancelling %s", message);
+ sfree(message);
+
+ /* epf->remote or epf->local may be NULL if setting up a
+ * forwarding failed. */
+ if (epf->remote) {
+ struct ssh_rportfwd *rpf = epf->remote;
+ struct Packet *pktout;
+
+ /*
+ * Cancel the port forwarding at the server
+ * end.
+ */
+ if (ssh->version == 1) {
+ /*
+ * We cannot cancel listening ports on the
+ * server side in SSH-1! There's no message
+ * to support it. Instead, we simply remove
+ * the rportfwd record from the local end
+ * so that any connections the server tries
+ * to make on it are rejected.
+ */
+ } else {
+ 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
+ * what was used to open the original connection,
+ * since it's reconfigurable. */
+ 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_send(ssh, pktout);
+ }
+
+ del234(ssh->rportfwds, rpf);
+ free_rportfwd(rpf);
+ } else if (epf->local) {
+ pfd_terminate(epf->local);
+ }
+
+ delpos234(ssh->portfwds, i);
+ free_portfwd(epf);
+ i--; /* so we don't skip one in the list */
+ }
+
+ /*
+ * And finally, set up any new port forwardings (status==CREATE).
+ */
+ for (i = 0; (epf = index234(ssh->portfwds, i)) != NULL; i++)
+ if (epf->status == CREATE) {
+ char *sportdesc, *dportdesc;
+ sportdesc = dupprintf("%s%s%s%s%d%s",
+ epf->saddr ? epf->saddr : "",
+ epf->saddr ? ":" : "",
+ epf->sserv ? epf->sserv : "",
+ epf->sserv ? "(" : "",
+ epf->sport,
+ epf->sserv ? ")" : "");
+ if (epf->type == 'D') {
+ dportdesc = NULL;
+ } else {
+ dportdesc = dupprintf("%s:%s%s%d%s",
+ epf->daddr,
+ epf->dserv ? epf->dserv : "",
+ epf->dserv ? "(" : "",
+ 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);
+
+ 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 : "");
+ } else if (epf->type == 'D') {
+ const char *err = pfd_addforward(NULL, -1,
+ epf->saddr, epf->sport,
+ ssh, cfg,
+ &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 : "");
+ } else {
+ struct ssh_rportfwd *pf;
+
+ /*
+ * Ensure the remote port forwardings tree exists.
+ */
+ if (!ssh->rportfwds) {
+ if (ssh->version == 1)
+ ssh->rportfwds = newtree234(ssh_rportcmp_ssh1);
+ 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->dport = epf->dport;
+ 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);
+ } else {
+ logeventf(ssh, "Requesting remote port %s"
+ " forward to %s", sportdesc, dportdesc);
+
+ pf->sportdesc = sportdesc;
+ sportdesc = NULL;
+ epf->remote = pf;
+ pf->pfrec = epf;
+
+ if (ssh->version == 1) {
+ send_packet(ssh, SSH1_CMSG_PORT_FORWARD_REQUEST,
+ PKT_INT, epf->sport,
+ PKT_STR, epf->daddr,
+ PKT_INT, epf->dport,
+ PKT_END);
+ ssh_queue_handler(ssh, SSH1_SMSG_SUCCESS,
+ SSH1_SMSG_FAILURE,
+ ssh_rportfwd_succfail, pf);
+ } 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_send(ssh, pktout);
+
+ ssh_queue_handler(ssh, SSH2_MSG_REQUEST_SUCCESS,
+ SSH2_MSG_REQUEST_FAILURE,
+ ssh_rportfwd_succfail, pf);
+ }
+ }
+ }
+ sfree(sportdesc);
+ sfree(dportdesc);
+ }
+}
+
+static void ssh1_smsg_stdout_stderr_data(Ssh ssh, struct Packet *pktin)
+{
+ char *string;
+ int stringlen, bufsize;
+
+ ssh_pkt_getstring(pktin, &string, &stringlen);
+ if (string == NULL) {
+ bombout(("Incoming terminal data packet was badly formed"));
+ return;
+ }
+
+ bufsize = from_backend(ssh->frontend, pktin->type == SSH1_SMSG_STDERR_DATA,
+ string, stringlen);
+ if (!ssh->v1_stdout_throttling && bufsize > SSH1_BUFFER_LIMIT) {
+ ssh->v1_stdout_throttling = 1;
+ ssh_throttle_conn(ssh, +1);
+ }
+}
+
+static void ssh1_smsg_x11_open(Ssh ssh, struct Packet *pktin)
+{
+ /* Remote side is trying to open a channel to talk to our
+ * X-Server. Give them back a local channel number. */
+ struct ssh_channel *c;
+ int remoteid = ssh_pkt_getuint32(pktin);
+
+ logevent("Received X11 connect request");
+ /* Refuse if X11 forwarding is disabled. */
+ if (!ssh->X11_fwd_enabled) {
+ send_packet(ssh, SSH1_MSG_CHANNEL_OPEN_FAILURE,
+ PKT_INT, remoteid, PKT_END);
+ 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");
+ }
+ }
+}
+
+static void ssh1_smsg_agent_open(Ssh ssh, struct Packet *pktin)
+{
+ /* Remote side is trying to open a channel to talk to our
+ * agent. Give them back a local channel number. */
+ struct ssh_channel *c;
+ int remoteid = ssh_pkt_getuint32(pktin);
+
+ /* Refuse if agent forwarding is disabled. */
+ if (!ssh->agentfwd_enabled) {
+ send_packet(ssh, SSH1_MSG_CHANNEL_OPEN_FAILURE,
+ PKT_INT, remoteid, PKT_END);
+ } else {
+ c = snew(struct ssh_channel);
+ c->ssh = ssh;
+ 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_AGENT; /* identify channel type */
+ c->u.a.lensofar = 0;
+ add234(ssh->channels, c);
+ send_packet(ssh, SSH1_MSG_CHANNEL_OPEN_CONFIRMATION,
+ PKT_INT, c->remoteid, PKT_INT, c->localid,
+ PKT_END);
+ }
+}
+
+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;
+
+ 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.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 {
+ 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);
+ 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->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");
+ }
+ }
+}
+
+static void ssh1_msg_channel_open_confirmation(Ssh ssh, struct Packet *pktin)
+{
+ unsigned int remoteid = ssh_pkt_getuint32(pktin);
+ unsigned int localid = ssh_pkt_getuint32(pktin);
+ struct ssh_channel *c;
+
+ c = find234(ssh->channels, &remoteid, ssh_channelfind);
+ 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);
+ }
+
+ if (c && 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.
+ */
+ send_packet(ssh, SSH1_MSG_CHANNEL_CLOSE,
+ PKT_INT, c->remoteid, PKT_END);
+ }
+}
+
+static void ssh1_msg_channel_open_failure(Ssh ssh, struct Packet *pktin)
+{
+ unsigned int remoteid = ssh_pkt_getuint32(pktin);
+ 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);
+ del234(ssh->channels, c);
+ sfree(c);
+ }
+}
+
+static void ssh1_msg_channel_close(Ssh ssh, struct Packet *pktin)
+{
+ /* 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);
+ }
+ } else {
+ bombout(("Received CHANNEL_CLOSE%s for %s channel %d\n",
+ pktin->type == SSH1_MSG_CHANNEL_CLOSE ? "" :
+ "_CONFIRMATION", c ? "half-open" : "nonexistent",
+ i));
+ }
+}
+
+static void ssh1_msg_channel_data(Ssh ssh, struct Packet *pktin)
+{
+ /* Data sent down one of our channels. */
+ int i = ssh_pkt_getuint32(pktin);
+ char *p;
+ int len;
+ struct ssh_channel *c;
+
+ ssh_pkt_getstring(pktin, &p, &len);
+
+ 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);
+ break;
+ case CHAN_SOCKDATA:
+ bufsize = pfd_send(c->u.pfd.s, p, len);
+ break;
+ case CHAN_AGENT:
+ /* Data for an agent message. Buffer it. */
+ while (len > 0) {
+ if (c->u.a.lensofar < 4) {
+ unsigned int l = min(4 - c->u.a.lensofar, (unsigned)len);
+ memcpy(c->u.a.msglen + c->u.a.lensofar, p,
+ l);
+ p += l;
+ len -= l;
+ c->u.a.lensofar += l;
+ }
+ if (c->u.a.lensofar == 4) {
+ c->u.a.totallen =
+ 4 + GET_32BIT(c->u.a.msglen);
+ c->u.a.message = snewn(c->u.a.totallen,
+ unsigned char);
+ memcpy(c->u.a.message, c->u.a.msglen, 4);
+ }
+ if (c->u.a.lensofar >= 4 && len > 0) {
+ unsigned int l =
+ min(c->u.a.totallen - c->u.a.lensofar,
+ (unsigned)len);
+ memcpy(c->u.a.message + c->u.a.lensofar, p,
+ l);
+ p += l;
+ len -= l;
+ c->u.a.lensofar += l;
+ }
+ if (c->u.a.lensofar == c->u.a.totallen) {
+ void *reply;
+ int replylen;
+ 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.lensofar = 0;
+ }
+ }
+ bufsize = 0; /* agent channels never back up */
+ break;
+ }
+ if (!c->throttling_conn && bufsize > SSH1_BUFFER_LIMIT) {
+ c->throttling_conn = 1;
+ ssh_throttle_conn(ssh, +1);
+ }
+ }
+}
+
+static void ssh1_smsg_exit_status(Ssh ssh, struct Packet *pktin)
+{
+ ssh->exitcode = ssh_pkt_getuint32(pktin);
+ logeventf(ssh, "Server sent command exit status %d", ssh->exitcode);
+ send_packet(ssh, SSH1_CMSG_EXIT_CONFIRMATION, PKT_END);
+ /*
+ * In case `helpful' firewalls or proxies tack
+ * extra human-readable text on the end of the
+ * session which we might mistake for another
+ * encrypted packet, we close the session once
+ * we've sent EXIT_CONFIRMATION.
+ */
+ ssh_disconnect(ssh, NULL, NULL, 0, TRUE);
+}
+
+/* Helper function to deal with sending tty modes for REQUEST_PTY */
+static void ssh1_send_ttymode(void *data, char *mode, char *val)
+{
+ struct Packet *pktout = (struct Packet *)data;
+ int i = 0;
+ unsigned int arg = 0;
+ while (strcmp(mode, ssh_ttymodes[i].mode) != 0) i++;
+ if (i == lenof(ssh_ttymodes)) return;
+ switch (ssh_ttymodes[i].type) {
+ case TTY_OP_CHAR:
+ arg = ssh_tty_parse_specchar(val);
+ break;
+ case TTY_OP_BOOL:
+ arg = ssh_tty_parse_boolean(val);
+ break;
+ }
+ ssh2_pkt_addbyte(pktout, ssh_ttymodes[i].opcode);
+ ssh2_pkt_addbyte(pktout, arg);
+}
+
+
+static void do_ssh1_connection(Ssh ssh, unsigned char *in, int inlen,
+ struct Packet *pktin)
+{
+ crBegin(ssh->do_ssh1_connection_crstate);
+
+ ssh->packet_dispatch[SSH1_SMSG_STDOUT_DATA] =
+ ssh->packet_dispatch[SSH1_SMSG_STDERR_DATA] =
+ ssh1_smsg_stdout_stderr_data;
+
+ ssh->packet_dispatch[SSH1_MSG_CHANNEL_OPEN_CONFIRMATION] =
+ ssh1_msg_channel_open_confirmation;
+ ssh->packet_dispatch[SSH1_MSG_CHANNEL_OPEN_FAILURE] =
+ ssh1_msg_channel_open_failure;
+ ssh->packet_dispatch[SSH1_MSG_CHANNEL_CLOSE] =
+ 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()) {
+ logevent("Requesting agent forwarding");
+ send_packet(ssh, SSH1_CMSG_AGENT_REQUEST_FORWARDING, 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("Agent forwarding refused");
+ } else {
+ logevent("Agent forwarding enabled");
+ 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);
+ ssh->packet_dispatch[SSH1_MSG_PORT_OPEN] = ssh1_msg_port_open;
+
+ if (!ssh->cfg.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);
+ /* Send the pty request. */
+ pkt = ssh1_pkt_init(SSH1_CMSG_REQUEST_PTY);
+ ssh_pkt_addstring(pkt, ssh->cfg.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);
+ 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);
+ s_wrpkt(ssh, pkt);
+ ssh->state = SSH_STATE_INTERMED;
+ 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) {
+ 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 {
+ ssh->editing = ssh->echoing = 1;
+ }
+
+ if (ssh->cfg.compression) {
+ send_packet(ssh, SSH1_CMSG_REQUEST_COMPRESSION, PKT_INT, 6, 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) {
+ c_write_str(ssh, "Server refused to compress\r\n");
+ }
+ logevent("Started compression");
+ ssh->v1_compressing = TRUE;
+ ssh->cs_comp_ctx = zlib_compress_init();
+ logevent("Initialised zlib (RFC1950) compression");
+ ssh->sc_comp_ctx = zlib_decompress_init();
+ logevent("Initialised zlib (RFC1950) decompression");
+ }
+
+ /*
+ * Start the shell or command.
+ *
+ * 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;
+
+ if (ssh->cfg.ssh_subsys && ssh->cfg.remote_cmd_ptr2) {
+ cmd = ssh->cfg.remote_cmd_ptr2;
+ ssh->fallback_cmd = TRUE;
+ }
+ if (*cmd)
+ send_packet(ssh, SSH1_CMSG_EXEC_CMD, PKT_STR, cmd, PKT_END);
+ else
+ send_packet(ssh, SSH1_CMSG_EXEC_SHELL, PKT_END);
+ logevent("Started session");
+ }
+
+ 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);
+
+ if (ssh->ldisc)
+ ldisc_send(ssh->ldisc, NULL, 0, 0);/* cause ldisc to notice changes */
+ ssh->send_ok = 1;
+ ssh->channels = newtree234(ssh_channelcmp);
+ while (1) {
+
+ /*
+ * By this point, most incoming packets are already being
+ * handled by the dispatch table, and we need only pay
+ * attention to the unusual ones.
+ */
+
+ crReturnV;
+ if (pktin) {
+ if (pktin->type == SSH1_SMSG_SUCCESS) {
+ /* may be from EXEC_SHELL on some servers */
+ } else if (pktin->type == SSH1_SMSG_FAILURE) {
+ /* may be from EXEC_SHELL on some servers
+ * if no pty is available or in other odd cases. Ignore */
+ } else {
+ bombout(("Strange packet received: type %d", pktin->type));
+ crStopV;
+ }
+ } 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);
+ in += len;
+ inlen -= len;
+ }
+ }
+ }
+
+ crFinishV;
+}
+
+/*
+ * Handle the top-level SSH-2 protocol.
+ */
+static void ssh1_msg_debug(Ssh ssh, struct Packet *pktin)
+{
+ char *msg;
+ int msglen;
+
+ ssh_pkt_getstring(pktin, &msg, &msglen);
+ logeventf(ssh, "Remote debug message: %.*s", msglen, msg);
+}
+
+static void ssh1_msg_disconnect(Ssh ssh, struct Packet *pktin)
+{
+ /* log reason code in disconnect message */
+ char *msg;
+ int msglen;
+
+ ssh_pkt_getstring(pktin, &msg, &msglen);
+ bombout(("Server sent disconnect message:\n\"%.*s\"", msglen, msg));
+}
+
+static void ssh_msg_ignore(Ssh ssh, struct Packet *pktin)
+{
+ /* Do nothing, because we're ignoring it! Duhh. */
+}
+
+static void ssh1_protocol_setup(Ssh ssh)
+{
+ int i;
+
+ /*
+ * Most messages are handled by the coroutines.
+ */
+ for (i = 0; i < 256; i++)
+ ssh->packet_dispatch[i] = NULL;
+
+ /*
+ * These special message types we install handlers for.
+ */
+ ssh->packet_dispatch[SSH1_MSG_DISCONNECT] = ssh1_msg_disconnect;
+ ssh->packet_dispatch[SSH1_MSG_IGNORE] = ssh_msg_ignore;
+ ssh->packet_dispatch[SSH1_MSG_DEBUG] = ssh1_msg_debug;
+}
+
+static void ssh1_protocol(Ssh ssh, void *vin, int inlen,
+ struct Packet *pktin)
+{
+ unsigned char *in=(unsigned char*)vin;
+ if (ssh->state == SSH_STATE_CLOSED)
+ return;
+
+ if (pktin && ssh->packet_dispatch[pktin->type]) {
+ ssh->packet_dispatch[pktin->type](ssh, pktin);
+ return;
+ }
+
+ if (!ssh->protocol_initial_phase_done) {
+ if (do_ssh1_login(ssh, in, inlen, pktin))
+ ssh->protocol_initial_phase_done = TRUE;
+ else
+ return;
+ }
+
+ do_ssh1_connection(ssh, in, inlen, pktin);
+}
+
+/*
+ * Utility routine for decoding comma-separated strings in KEXINIT.
+ */
+static int in_commasep_string(char *needle, char *haystack, int haylen)
+{
+ int needlen;
+ if (!needle || !haystack) /* protect against null pointers */
+ return 0;
+ needlen = strlen(needle);
+ while (1) {
+ /*
+ * Is it at the start of the string?
+ */
+ if (haylen >= needlen && /* haystack is long enough */
+ !memcmp(needle, haystack, needlen) && /* initial match */
+ (haylen == needlen || haystack[needlen] == ',')
+ /* either , or EOS follows */
+ )
+ return 1;
+ /*
+ * If not, search for the next comma and resume after that.
+ * If no comma found, terminate.
+ */
+ while (haylen > 0 && *haystack != ',')
+ haylen--, haystack++;
+ if (haylen == 0)
+ return 0;
+ haylen--, haystack++; /* skip over comma itself */
+ }
+}
+
+/*
+ * Similar routine for checking whether we have the first string in a list.
+ */
+static int first_in_commasep_string(char *needle, char *haystack, int haylen)
+{
+ int needlen;
+ if (!needle || !haystack) /* protect against null pointers */
+ return 0;
+ needlen = strlen(needle);
+ /*
+ * Is it at the start of the string?
+ */
+ if (haylen >= needlen && /* haystack is long enough */
+ !memcmp(needle, haystack, needlen) && /* initial match */
+ (haylen == needlen || haystack[needlen] == ',')
+ /* either , or EOS follows */
+ )
+ return 1;
+ return 0;
+}
+
+
+/*
+ * SSH-2 key creation method.
+ * (Currently assumes 2 lots of any hash are sufficient to generate
+ * keys/IVs for any cipher/MAC. SSH2_MKKEY_ITERS documents this assumption.)
+ */
+#define SSH2_MKKEY_ITERS (2)
+static void ssh2_mkkey(Ssh ssh, Bignum K, unsigned char *H, char chr,
+ unsigned char *keyspace)
+{
+ const struct ssh_hash *h = ssh->kex->hash;
+ void *s;
+ /* First hlen bytes. */
+ s = h->init();
+ if (!(ssh->remote_bugs & BUG_SSH2_DERIVEKEY))
+ hash_mpint(h, s, K);
+ h->bytes(s, H, h->hlen);
+ h->bytes(s, &chr, 1);
+ h->bytes(s, ssh->v2_session_id, ssh->v2_session_id_len);
+ h->final(s, keyspace);
+ /* Next hlen bytes. */
+ s = h->init();
+ if (!(ssh->remote_bugs & BUG_SSH2_DERIVEKEY))
+ hash_mpint(h, s, K);
+ h->bytes(s, H, h->hlen);
+ h->bytes(s, keyspace, h->hlen);
+ h->final(s, keyspace + h->hlen);
+}
+
+/*
+ * Handle the SSH-2 transport layer.
+ */
+static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
+ struct Packet *pktin)
+{
+ unsigned char *in = (unsigned char *)vin;
+ struct do_ssh2_transport_state {
+ 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;
+ const struct ssh_mac **maclist;
+ int nmacs;
+ const struct ssh2_cipher *cscipher_tobe;
+ const struct ssh2_cipher *sccipher_tobe;
+ const struct ssh_mac *csmac_tobe;
+ const struct ssh_mac *scmac_tobe;
+ const struct ssh_compress *cscomp_tobe;
+ const struct ssh_compress *sccomp_tobe;
+ char *hostkeydata, *sigdata, *rsakeydata, *keystr, *fingerprint;
+ int hostkeylen, siglen, rsakeylen;
+ void *hkey; /* actual host key */
+ void *rsakey; /* for RSA kex */
+ unsigned char exchange_hash[SSH2_KEX_MAX_HASH_LEN];
+ int n_preferred_kex;
+ const struct ssh_kexes *preferred_kex[KEX_MAX];
+ int n_preferred_ciphers;
+ const struct ssh2_ciphers *preferred_ciphers[CIPHER_MAX];
+ const struct ssh_compress *preferred_comp;
+ int userauth_succeeded; /* for delayed compression */
+ int pending_compression;
+ int got_session_id, activated_authconn;
+ struct Packet *pktout;
+ int dlgret;
+ int guessok;
+ int ignorepkt;
+ };
+ crState(do_ssh2_transport_state);
+
+ crBegin(ssh->do_ssh2_transport_crstate);
+
+ s->cscipher_tobe = s->sccipher_tobe = NULL;
+ s->csmac_tobe = s->scmac_tobe = NULL;
+ s->cscomp_tobe = s->sccomp_tobe = NULL;
+
+ s->got_session_id = s->activated_authconn = FALSE;
+ s->userauth_succeeded = FALSE;
+ s->pending_compression = FALSE;
+
+ /*
+ * Be prepared to work around the buggy MAC problem.
+ */
+ if (ssh->remote_bugs & BUG_SSH2_HMAC)
+ s->maclist = buggymacs, s->nmacs = lenof(buggymacs);
+ else
+ s->maclist = macs, s->nmacs = lenof(macs);
+
+ begin_key_exchange:
+ ssh->pkt_kctx = SSH2_PKTCTX_NOKEX;
+ {
+ int i, j, 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]) {
+ case KEX_DHGEX:
+ s->preferred_kex[s->n_preferred_kex++] =
+ &ssh_diffiehellman_gex;
+ break;
+ case KEX_DHGROUP14:
+ s->preferred_kex[s->n_preferred_kex++] =
+ &ssh_diffiehellman_group14;
+ break;
+ case KEX_DHGROUP1:
+ s->preferred_kex[s->n_preferred_kex++] =
+ &ssh_diffiehellman_group1;
+ break;
+ case KEX_RSA:
+ s->preferred_kex[s->n_preferred_kex++] =
+ &ssh_rsa_kex;
+ break;
+ case KEX_WARN:
+ /* Flag for later. Don't bother if it's the last in
+ * the list. */
+ if (i < KEX_MAX - 1) {
+ s->preferred_kex[s->n_preferred_kex++] = NULL;
+ }
+ break;
+ }
+ }
+
+ /*
+ * 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]) {
+ case CIPHER_BLOWFISH:
+ s->preferred_ciphers[s->n_preferred_ciphers++] = &ssh2_blowfish;
+ break;
+ case CIPHER_DES:
+ if (ssh->cfg.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;
+ break;
+ case CIPHER_AES:
+ s->preferred_ciphers[s->n_preferred_ciphers++] = &ssh2_aes;
+ break;
+ case CIPHER_ARCFOUR:
+ s->preferred_ciphers[s->n_preferred_ciphers++] = &ssh2_arcfour;
+ break;
+ case CIPHER_WARN:
+ /* Flag for later. Don't bother if it's the last in
+ * the list. */
+ if (i < CIPHER_MAX - 1) {
+ s->preferred_ciphers[s->n_preferred_ciphers++] = NULL;
+ }
+ break;
+ }
+ }
+
+ /*
+ * Set up preferred compression.
+ */
+ if (ssh->cfg.compression)
+ s->preferred_comp = &ssh_zlib;
+ else
+ s->preferred_comp = &ssh_comp_none;
+
+ /*
+ * Enable queueing of outgoing auth- or connection-layer
+ * packets while we are in the middle of a key exchange.
+ */
+ ssh->queueing = TRUE;
+
+ /*
+ * Flag that KEX is in progress.
+ */
+ ssh->kex_in_progress = TRUE;
+
+ /*
+ * Construct and send our key exchange packet.
+ */
+ s->pktout = ssh2_pkt_init(SSH2_MSG_KEXINIT);
+ for (i = 0; i < 16; i++)
+ ssh2_pkt_addbyte(s->pktout, (unsigned char) random_byte());
+ /* List key exchange algorithms. */
+ ssh2_pkt_addstring_start(s->pktout);
+ commalist_started = 0;
+ for (i = 0; i < s->n_preferred_kex; i++) {
+ const struct ssh_kexes *k = s->preferred_kex[i];
+ if (!k) continue; /* warning flag */
+ for (j = 0; j < k->nkexes; j++) {
+ if (commalist_started)
+ ssh2_pkt_addstring_str(s->pktout, ",");
+ 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, ",");
+ }
+ /* List client->server compression algorithms,
+ * then server->client compression algorithms. (We use the
+ * same set twice.) */
+ for (j = 0; j < 2; j++) {
+ ssh2_pkt_addstring_start(s->pktout);
+ assert(lenof(compressions) > 1);
+ /* Prefer non-delayed versions */
+ ssh2_pkt_addstring_str(s->pktout, s->preferred_comp->name);
+ /* We don't even list delayed versions of algorithms until
+ * they're allowed to be used, to avoid a race. See the end of
+ * this function. */
+ if (s->userauth_succeeded && s->preferred_comp->delayed_name) {
+ ssh2_pkt_addstring_str(s->pktout, ",");
+ ssh2_pkt_addstring_str(s->pktout,
+ s->preferred_comp->delayed_name);
+ }
+ for (i = 0; i < lenof(compressions); i++) {
+ const struct ssh_compress *c = compressions[i];
+ if (c != s->preferred_comp) {
+ ssh2_pkt_addstring_str(s->pktout, ",");
+ ssh2_pkt_addstring_str(s->pktout, c->name);
+ if (s->userauth_succeeded && c->delayed_name) {
+ ssh2_pkt_addstring_str(s->pktout, ",");
+ ssh2_pkt_addstring_str(s->pktout, c->delayed_name);
+ }
+ }
+ }
+ }
+ /* List client->server languages. Empty list. */
+ ssh2_pkt_addstring_start(s->pktout);
+ /* List server->client languages. Empty list. */
+ ssh2_pkt_addstring_start(s->pktout);
+ /* First KEX packet does _not_ follow, because we're not that brave. */
+ ssh2_pkt_addbool(s->pktout, FALSE);
+ /* Reserved. */
+ ssh2_pkt_adduint32(s->pktout, 0);
+ }
+
+ s->our_kexinitlen = s->pktout->length - 5;
+ s->our_kexinit = snewn(s->our_kexinitlen, unsigned char);
+ memcpy(s->our_kexinit, s->pktout->data + 5, s->our_kexinitlen);
+
+ ssh2_pkt_send_noqueue(ssh, s->pktout);
+
+ if (!pktin)
+ crWaitUntil(pktin);
+
+ /*
+ * Now examine the other side's KEXINIT to see what we're up
+ * to.
+ */
+ {
+ char *str, *preferred;
+ int i, j, len;
+
+ if (pktin->type != SSH2_MSG_KEXINIT) {
+ bombout(("expected key exchange packet from server"));
+ crStop(0);
+ }
+ ssh->kex = NULL;
+ ssh->hostkey = NULL;
+ s->cscipher_tobe = NULL;
+ s->sccipher_tobe = NULL;
+ s->csmac_tobe = NULL;
+ s->scmac_tobe = NULL;
+ s->cscomp_tobe = NULL;
+ s->sccomp_tobe = NULL;
+ s->warn_kex = s->warn_cscipher = s->warn_sccipher = FALSE;
+
+ pktin->savedpos += 16; /* skip garbage cookie */
+ ssh_pkt_getstring(pktin, &str, &len); /* key exchange algorithms */
+
+ preferred = NULL;
+ for (i = 0; i < s->n_preferred_kex; i++) {
+ const struct ssh_kexes *k = s->preferred_kex[i];
+ if (!k) {
+ s->warn_kex = TRUE;
+ } else {
+ for (j = 0; j < k->nkexes; j++) {
+ if (!preferred) preferred = k->list[j]->name;
+ if (in_commasep_string(k->list[j]->name, str, len)) {
+ ssh->kex = k->list[j];
+ break;
+ }
+ }
+ }
+ if (ssh->kex)
+ break;
+ }
+ if (!ssh->kex) {
+ bombout(("Couldn't agree a key exchange algorithm (available: %s)",
+ str ? str : "(null)"));
+ crStop(0);
+ }
+ /*
+ * 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.
+ */
+ s->guessok = first_in_commasep_string(preferred, str, len);
+ ssh_pkt_getstring(pktin, &str, &len); /* host key algorithms */
+ for (i = 0; i < lenof(hostkey_algs); i++) {
+ if (in_commasep_string(hostkey_algs[i]->name, str, len)) {
+ ssh->hostkey = hostkey_algs[i];
+ break;
+ }
+ }
+ 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];
+ if (!c) {
+ s->warn_cscipher = TRUE;
+ } else {
+ for (j = 0; j < c->nciphers; j++) {
+ if (in_commasep_string(c->list[j]->name, str, len)) {
+ s->cscipher_tobe = c->list[j];
+ break;
+ }
+ }
+ }
+ if (s->cscipher_tobe)
+ break;
+ }
+ if (!s->cscipher_tobe) {
+ bombout(("Couldn't agree a client-to-server cipher (available: %s)",
+ str ? str : "(null)"));
+ crStop(0);
+ }
+
+ 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];
+ if (!c) {
+ s->warn_sccipher = TRUE;
+ } else {
+ for (j = 0; j < c->nciphers; j++) {
+ if (in_commasep_string(c->list[j]->name, str, len)) {
+ s->sccipher_tobe = c->list[j];
+ break;
+ }
+ }
+ }
+ if (s->sccipher_tobe)
+ break;
+ }
+ if (!s->sccipher_tobe) {
+ bombout(("Couldn't agree a server-to-client cipher (available: %s)",
+ str ? str : "(null)"));
+ crStop(0);
+ }
+
+ 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)) {
+ s->csmac_tobe = s->maclist[i];
+ break;
+ }
+ }
+ ssh_pkt_getstring(pktin, &str, &len); /* server->client mac */
+ for (i = 0; i < s->nmacs; i++) {
+ if (in_commasep_string(s->maclist[i]->name, str, len)) {
+ s->scmac_tobe = s->maclist[i];
+ break;
+ }
+ }
+ ssh_pkt_getstring(pktin, &str, &len); /* client->server compression */
+ for (i = 0; i < lenof(compressions) + 1; i++) {
+ const struct ssh_compress *c =
+ i == 0 ? s->preferred_comp : compressions[i - 1];
+ if (in_commasep_string(c->name, str, len)) {
+ s->cscomp_tobe = c;
+ break;
+ } else if (in_commasep_string(c->delayed_name, str, len)) {
+ if (s->userauth_succeeded) {
+ s->cscomp_tobe = c;
+ break;
+ } else {
+ s->pending_compression = TRUE; /* try this later */
+ }
+ }
+ }
+ ssh_pkt_getstring(pktin, &str, &len); /* server->client compression */
+ for (i = 0; i < lenof(compressions) + 1; i++) {
+ const struct ssh_compress *c =
+ i == 0 ? s->preferred_comp : compressions[i - 1];
+ if (in_commasep_string(c->name, str, len)) {
+ s->sccomp_tobe = c;
+ break;
+ } else if (in_commasep_string(c->delayed_name, str, len)) {
+ if (s->userauth_succeeded) {
+ s->sccomp_tobe = c;
+ break;
+ } else {
+ s->pending_compression = TRUE; /* try this later */
+ }
+ }
+ }
+ if (s->pending_compression) {
+ logevent("Server supports delayed compression; "
+ "will try this later");
+ }
+ 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;
+
+ 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);
+ if (pktin) {
+ bombout(("Unexpected data from server while"
+ " waiting for user 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 kex warning", NULL,
+ 0, TRUE);
+ crStop(0);
+ }
+ }
+
+ if (s->warn_cscipher) {
+ ssh_set_frozen(ssh, 1);
+ s->dlgret = askalg(ssh->frontend,
+ "client-to-server cipher",
+ s->cscipher_tobe->name,
+ ssh_dialog_callback, ssh);
+ if (s->dlgret < 0) {
+ do {
+ crReturn(0);
+ if (pktin) {
+ bombout(("Unexpected data from server while"
+ " waiting for user 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 cipher warning", NULL,
+ 0, TRUE);
+ crStop(0);
+ }
+ }
+
+ if (s->warn_sccipher) {
+ ssh_set_frozen(ssh, 1);
+ s->dlgret = askalg(ssh->frontend,
+ "server-to-client cipher",
+ s->sccipher_tobe->name,
+ ssh_dialog_callback, ssh);
+ if (s->dlgret < 0) {
+ do {
+ crReturn(0);
+ if (pktin) {
+ bombout(("Unexpected data from server while"
+ " waiting for user 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 cipher warning", NULL,
+ 0, TRUE);
+ crStop(0);
+ }
+ }
+
+ 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 */
+ }
+
+ if (ssh->kex->main_type == KEXTYPE_DH) {
+ /*
+ * Work out the number of bits of key we will need from the
+ * key exchange. We start with the maximum key length of
+ * either cipher...
+ */
+ {
+ int csbits, scbits;
+
+ csbits = s->cscipher_tobe->keylen;
+ scbits = s->sccipher_tobe->keylen;
+ s->nbits = (csbits > scbits ? csbits : scbits);
+ }
+ /* The keys only have hlen-bit entropy, since they're based on
+ * a hash. So cap the key size at hlen bits. */
+ if (s->nbits > ssh->kex->hash->hlen * 8)
+ s->nbits = ssh->kex->hash->hlen * 8;
+
+ /*
+ * If we're doing Diffie-Hellman group exchange, start by
+ * requesting a group.
+ */
+ if (!ssh->kex->pdata) {
+ logevent("Doing Diffie-Hellman group exchange");
+ ssh->pkt_kctx = SSH2_PKTCTX_DHGEX;
+ /*
+ * Work out how big a DH group we will need to allow that
+ * much data.
+ */
+ 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);
+ if (pktin->type != SSH2_MSG_KEX_DH_GEX_GROUP) {
+ bombout(("expected key exchange group packet from server"));
+ crStop(0);
+ }
+ 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);
+ }
+ 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 {
+ ssh->pkt_kctx = SSH2_PKTCTX_DHGROUP;
+ ssh->kex_ctx = dh_setup_group(ssh->kex);
+ s->kex_init_value = SSH2_MSG_KEXDH_INIT;
+ s->kex_reply_value = SSH2_MSG_KEXDH_REPLY;
+ logeventf(ssh, "Using Diffie-Hellman with standard group \"%s\"",
+ ssh->kex->groupname);
+ }
+
+ logeventf(ssh, "Doing Diffie-Hellman key exchange with hash %s",
+ ssh->kex->hash->text_name);
+ /*
+ * Now generate and send e for Diffie-Hellman.
+ */
+ set_busy_status(ssh->frontend, BUSY_CPU); /* this can take a while */
+ s->e = dh_create_e(ssh->kex_ctx, s->nbits * 2);
+ 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);
+ if (pktin->type != s->kex_reply_value) {
+ bombout(("expected key exchange reply packet from server"));
+ crStop(0);
+ }
+ 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);
+ }
+ ssh_pkt_getstring(pktin, &s->sigdata, &s->siglen);
+
+ s->K = dh_find_K(ssh->kex_ctx, s->f);
+
+ /* We assume everything from now on will be quick, and it might
+ * involve user interaction. */
+ set_busy_status(ssh->frontend, BUSY_NOT);
+
+ hash_string(ssh->kex->hash, ssh->exhash, s->hostkeydata, s->hostkeylen);
+ if (!ssh->kex->pdata) {
+ hash_uint32(ssh->kex->hash, ssh->exhash, s->pbits);
+ hash_mpint(ssh->kex->hash, ssh->exhash, s->p);
+ hash_mpint(ssh->kex->hash, ssh->exhash, s->g);
+ }
+ hash_mpint(ssh->kex->hash, ssh->exhash, s->e);
+ hash_mpint(ssh->kex->hash, ssh->exhash, s->f);
+
+ dh_cleanup(ssh->kex_ctx);
+ freebn(s->f);
+ if (!ssh->kex->pdata) {
+ freebn(s->g);
+ freebn(s->p);
+ }
+ } else {
+ logeventf(ssh, "Doing RSA key exchange with hash %s",
+ ssh->kex->hash->text_name);
+ ssh->pkt_kctx = SSH2_PKTCTX_RSAKEX;
+ /*
+ * RSA key exchange. First expect a KEXRSA_PUBKEY packet
+ * from the server.
+ */
+ crWaitUntil(pktin);
+ if (pktin->type != SSH2_MSG_KEXRSA_PUBKEY) {
+ bombout(("expected RSA public key packet from server"));
+ crStop(0);
+ }
+
+ ssh_pkt_getstring(pktin, &s->hostkeydata, &s->hostkeylen);
+ hash_string(ssh->kex->hash, ssh->exhash,
+ s->hostkeydata, s->hostkeylen);
+ s->hkey = ssh->hostkey->newkey(s->hostkeydata, s->hostkeylen);
+
+ {
+ char *keydata;
+ ssh_pkt_getstring(pktin, &keydata, &s->rsakeylen);
+ s->rsakeydata = snewn(s->rsakeylen, char);
+ memcpy(s->rsakeydata, keydata, s->rsakeylen);
+ }
+
+ 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);
+ }
+
+ hash_string(ssh->kex->hash, ssh->exhash, s->rsakeydata, s->rsakeylen);
+
+ /*
+ * Next, set up a shared secret K, of precisely KLEN -
+ * 2*HLEN - 49 bits, where KLEN is the bit length of the
+ * RSA key modulus and HLEN is the bit length of the hash
+ * we're using.
+ */
+ {
+ int klen = ssh_rsakex_klen(s->rsakey);
+ int nbits = klen - (2*ssh->kex->hash->hlen*8 + 49);
+ int i, byte = 0;
+ unsigned char *kstr1, *kstr2, *outstr;
+ int kstr1len, kstr2len, outstrlen;
+
+ s->K = bn_power_2(nbits - 1);
+
+ for (i = 0; i < nbits; i++) {
+ if ((i & 7) == 0) {
+ byte = random_byte();
+ }
+ bignum_set_bit(s->K, i, (byte >> (i & 7)) & 1);
+ }
+
+ /*
+ * Encode this as an mpint.
+ */
+ kstr1 = ssh2_mpint_fmt(s->K, &kstr1len);
+ kstr2 = snewn(kstr2len = 4 + kstr1len, unsigned char);
+ PUT_32BIT(kstr2, kstr1len);
+ memcpy(kstr2 + 4, kstr1, kstr1len);
+
+ /*
+ * Encrypt it with the given RSA key.
+ */
+ outstrlen = (klen + 7) / 8;
+ outstr = snewn(outstrlen, unsigned char);
+ ssh_rsakex_encrypt(ssh->kex->hash, kstr2, kstr2len,
+ outstr, outstrlen, s->rsakey);
+
+ /*
+ * And send it off in a return packet.
+ */
+ s->pktout = ssh2_pkt_init(SSH2_MSG_KEXRSA_SECRET);
+ ssh2_pkt_addstring_start(s->pktout);
+ ssh2_pkt_addstring_data(s->pktout, (char *)outstr, outstrlen);
+ ssh2_pkt_send_noqueue(ssh, s->pktout);
+
+ hash_string(ssh->kex->hash, ssh->exhash, outstr, outstrlen);
+
+ sfree(kstr2);
+ sfree(kstr1);
+ sfree(outstr);
+ }
+
+ ssh_rsakex_freekey(s->rsakey);
+
+ crWaitUntil(pktin);
+ if (pktin->type != SSH2_MSG_KEXRSA_DONE) {
+ sfree(s->rsakeydata);
+ bombout(("expected signature packet from server"));
+ crStop(0);
+ }
+
+ ssh_pkt_getstring(pktin, &s->sigdata, &s->siglen);
+
+ sfree(s->rsakeydata);
+ }
+
+ hash_mpint(ssh->kex->hash, ssh->exhash, s->K);
+ assert(ssh->kex->hash->hlen <= sizeof(s->exchange_hash));
+ ssh->kex->hash->final(ssh->exhash, s->exchange_hash);
+
+ ssh->kex_ctx = NULL;
+
+#if 0
+ debug(("Exchange hash is:\n"));
+ dmemdump(s->exchange_hash, ssh->kex->hash->hlen);
+#endif
+
+ 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);
+ }
+
+ /*
+ * 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);
+ 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
+ * authentication.
+ */
+ if (!s->got_session_id) {
+ assert(sizeof(s->exchange_hash) <= sizeof(ssh->v2_session_id));
+ memcpy(ssh->v2_session_id, s->exchange_hash,
+ sizeof(s->exchange_hash));
+ ssh->v2_session_id_len = ssh->kex->hash->hlen;
+ assert(ssh->v2_session_id_len <= sizeof(ssh->v2_session_id));
+ s->got_session_id = TRUE;
+ }
+
+ /*
+ * Send SSH2_MSG_NEWKEYS.
+ */
+ s->pktout = ssh2_pkt_init(SSH2_MSG_NEWKEYS);
+ ssh2_pkt_send_noqueue(ssh, s->pktout);
+ ssh->outgoing_data_size = 0; /* start counting from here */
+
+ /*
+ * We've sent client NEWKEYS, so create and initialise
+ * client-to-server session keys.
+ */
+ if (ssh->cs_cipher_ctx)
+ ssh->cscipher->free_context(ssh->cs_cipher_ctx);
+ ssh->cscipher = s->cscipher_tobe;
+ ssh->cs_cipher_ctx = ssh->cscipher->make_context();
+
+ if (ssh->cs_mac_ctx)
+ ssh->csmac->free_context(ssh->cs_mac_ctx);
+ ssh->csmac = s->csmac_tobe;
+ ssh->cs_mac_ctx = ssh->csmac->make_context();
+
+ if (ssh->cs_comp_ctx)
+ ssh->cscomp->compress_cleanup(ssh->cs_comp_ctx);
+ ssh->cscomp = s->cscomp_tobe;
+ ssh->cs_comp_ctx = ssh->cscomp->compress_init();
+
+ /*
+ * Set IVs on client-to-server keys. Here we use the exchange
+ * hash from the _first_ key exchange.
+ */
+ {
+ unsigned char keyspace[SSH2_KEX_MAX_HASH_LEN * SSH2_MKKEY_ITERS];
+ assert(sizeof(keyspace) >= ssh->kex->hash->hlen * SSH2_MKKEY_ITERS);
+ ssh2_mkkey(ssh,s->K,s->exchange_hash,'C',keyspace);
+ assert((ssh->cscipher->keylen+7) / 8 <=
+ ssh->kex->hash->hlen * SSH2_MKKEY_ITERS);
+ ssh->cscipher->setkey(ssh->cs_cipher_ctx, keyspace);
+ ssh2_mkkey(ssh,s->K,s->exchange_hash,'A',keyspace);
+ assert(ssh->cscipher->blksize <=
+ ssh->kex->hash->hlen * SSH2_MKKEY_ITERS);
+ 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));
+ }
+
+ logeventf(ssh, "Initialised %.200s client->server encryption",
+ ssh->cscipher->text_name);
+ logeventf(ssh, "Initialised %.200s client->server MAC algorithm",
+ ssh->csmac->text_name);
+ if (ssh->cscomp->text_name)
+ logeventf(ssh, "Initialised %s compression",
+ ssh->cscomp->text_name);
+
+ /*
+ * Now our end of the key exchange is complete, we can send all
+ * our queued higher-layer packets.
+ */
+ ssh->queueing = FALSE;
+ ssh2_pkt_queuesend(ssh);
+
+ /*
+ * Expect SSH2_MSG_NEWKEYS from server.
+ */
+ crWaitUntil(pktin);
+ if (pktin->type != SSH2_MSG_NEWKEYS) {
+ bombout(("expected new-keys packet from server"));
+ crStop(0);
+ }
+ ssh->incoming_data_size = 0; /* start counting from here */
+
+ /*
+ * We've seen server NEWKEYS, so create and initialise
+ * server-to-client session keys.
+ */
+ if (ssh->sc_cipher_ctx)
+ ssh->sccipher->free_context(ssh->sc_cipher_ctx);
+ ssh->sccipher = s->sccipher_tobe;
+ ssh->sc_cipher_ctx = ssh->sccipher->make_context();
+
+ if (ssh->sc_mac_ctx)
+ ssh->scmac->free_context(ssh->sc_mac_ctx);
+ ssh->scmac = s->scmac_tobe;
+ ssh->sc_mac_ctx = ssh->scmac->make_context();
+
+ if (ssh->sc_comp_ctx)
+ ssh->sccomp->decompress_cleanup(ssh->sc_comp_ctx);
+ ssh->sccomp = s->sccomp_tobe;
+ ssh->sc_comp_ctx = ssh->sccomp->decompress_init();
+
+ /*
+ * Set IVs on server-to-client keys. Here we use the exchange
+ * hash from the _first_ key exchange.
+ */
+ {
+ unsigned char keyspace[SSH2_KEX_MAX_HASH_LEN * SSH2_MKKEY_ITERS];
+ assert(sizeof(keyspace) >= ssh->kex->hash->hlen * SSH2_MKKEY_ITERS);
+ ssh2_mkkey(ssh,s->K,s->exchange_hash,'D',keyspace);
+ assert((ssh->sccipher->keylen+7) / 8 <=
+ ssh->kex->hash->hlen * SSH2_MKKEY_ITERS);
+ ssh->sccipher->setkey(ssh->sc_cipher_ctx, keyspace);
+ ssh2_mkkey(ssh,s->K,s->exchange_hash,'B',keyspace);
+ assert(ssh->sccipher->blksize <=
+ ssh->kex->hash->hlen * SSH2_MKKEY_ITERS);
+ 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));
+ }
+ logeventf(ssh, "Initialised %.200s server->client encryption",
+ ssh->sccipher->text_name);
+ logeventf(ssh, "Initialised %.200s server->client MAC algorithm",
+ ssh->scmac->text_name);
+ if (ssh->sccomp->text_name)
+ logeventf(ssh, "Initialised %s decompression",
+ ssh->sccomp->text_name);
+
+ /*
+ * Free shared secret.
+ */
+ freebn(s->K);
+
+ /*
+ * Key exchange is over. Loop straight back round if we have a
+ * deferred rekey reason.
+ */
+ if (ssh->deferred_rekey_reason) {
+ logevent(ssh->deferred_rekey_reason);
+ pktin = NULL;
+ ssh->deferred_rekey_reason = NULL;
+ goto begin_key_exchange;
+ }
+
+ /*
+ * 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,
+ 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.
+ *
+ * We _also_ go back to the start if we see pktin==NULL and
+ * inlen negative, because this is a special signal meaning
+ * `initiate client-driven rekey', and `in' contains a message
+ * giving the reason for the rekey.
+ *
+ * inlen==-1 means always initiate a rekey;
+ * inlen==-2 means that userauth has completed successfully and
+ * we should consider rekeying (for delayed compression).
+ */
+ while (!((pktin && pktin->type == SSH2_MSG_KEXINIT) ||
+ (!pktin && inlen < 0))) {
+ wait_for_rekey:
+ crReturn(1);
+ }
+ if (pktin) {
+ logevent("Server initiated key re-exchange");
+ } else {
+ if (inlen == -2) {
+ /*
+ * authconn has seen a USERAUTH_SUCCEEDED. Time to enable
+ * delayed compression, if it's available.
+ *
+ * draft-miller-secsh-compression-delayed-00 says that you
+ * negotiate delayed compression in the first key exchange, and
+ * both sides start compressing when the server has sent
+ * USERAUTH_SUCCESS. This has a race condition -- the server
+ * can't know when the client has seen it, and thus which incoming
+ * packets it should treat as compressed.
+ *
+ * Instead, we do the initial key exchange without offering the
+ * delayed methods, but note if the server offers them; when we
+ * get here, if a delayed method was available that was higher
+ * on our list than what we got, we initiate a rekey in which we
+ * _do_ list the delayed methods (and hopefully get it as a
+ * result). Subsequent rekeys will do the same.
+ */
+ assert(!s->userauth_succeeded); /* should only happen once */
+ s->userauth_succeeded = TRUE;
+ if (!s->pending_compression)
+ /* Can't see any point rekeying. */
+ goto wait_for_rekey; /* this is utterly horrid */
+ /* else fall through to rekey... */
+ s->pending_compression = FALSE;
+ }
+ /*
+ * Now we've decided to rekey.
+ *
+ * Special case: if the server bug is set that doesn't
+ * allow rekeying, we give a different log message and
+ * continue waiting. (If such a server _initiates_ a rekey,
+ * we process it anyway!)
+ */
+ if ((ssh->remote_bugs & BUG_SSH2_REKEY)) {
+ logeventf(ssh, "Server bug prevents key re-exchange (%s)",
+ (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) {
+ ssh->next_rekey =
+ schedule_timer(ssh->cfg.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);
+}
+
+/*
+ * Add data to an SSH-2 channel output buffer.
+ */
+static void ssh2_add_channel_data(struct ssh_channel *c, char *buf,
+ int len)
+{
+ bufchain_add(&c->v.v2.outbuffer, buf, len);
+}
+
+/*
+ * Attempt to send data on an SSH-2 channel.
+ */
+static int ssh2_try_send(struct ssh_channel *c)
+{
+ Ssh ssh = c->ssh;
+ struct Packet *pktout;
+
+ 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);
+ if ((unsigned)len > c->v.v2.remwindow)
+ len = c->v.v2.remwindow;
+ 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);
+}
+
+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 */
+ 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);
+ break;
+ case CHAN_AGENT:
+ /* agent sockets are request/response and need no
+ * buffer management */
+ break;
+ case CHAN_SOCKDATA:
+ pfd_unthrottle(c->u.pfd.s);
+ break;
+ }
+ }
+
+ /*
+ * If we've emptied the channel's output buffer and there's a
+ * pending close event, start the channel-closing procedure.
+ */
+ 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;
+ }
+}
+
+/*
+ * Set up most of a new ssh_channel for SSH-2.
+ */
+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->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;
+ c->v.v2.throttle_state = UNTHROTTLED;
+ bufchain_init(&c->v.v2.outbuffer);
+}
+
+/*
+ * Potentially enlarge the window on an SSH-2 channel.
+ */
+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.
+ */
+ if (c->closes != 0)
+ 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.
+ *
+ * "Significant" is arbitrarily defined as half the window size.
+ */
+ if (newwin / 2 >= c->v.v2.locwindow) {
+ struct Packet *pktout;
+ struct winadj *wa;
+
+ /*
+ * 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
+ * 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);
+ 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;
+ c->v.v2.throttle_state = THROTTLED;
+ }
+ pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_WINDOW_ADJUST);
+ ssh2_pkt_adduint32(pktout, c->remoteid);
+ ssh2_pkt_adduint32(pktout, newwin - c->v.v2.locwindow);
+ ssh2_pkt_send(ssh, pktout);
+ c->v.v2.locwindow = newwin;
+ }
+}
+
+/*
+ * Find the channel associated with a message. If there's no channel,
+ * or it's not properly open, make a noise about it and return NULL.
+ */
+static struct ssh_channel *ssh2_channel_msg(Ssh ssh, struct Packet *pktin)
+{
+ 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 &&
+ 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);
+ ssh_disconnect(ssh, NULL, buf, SSH2_DISCONNECT_PROTOCOL_ERROR, FALSE);
+ sfree(buf);
+ 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);
+ /*
+ * 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_window_adjust(Ssh ssh, struct Packet *pktin)
+{
+ struct ssh_channel *c;
+ c = ssh2_channel_msg(ssh, pktin);
+ if (!c)
+ return;
+ if (!c->closes) {
+ c->v.v2.remwindow += ssh_pkt_getuint32(pktin);
+ ssh2_try_send_and_unthrottle(ssh, c);
+ }
+}
+
+static void ssh2_msg_channel_data(Ssh ssh, struct Packet *pktin)
+{
+ char *data;
+ int length;
+ struct ssh_channel *c;
+ c = ssh2_channel_msg(ssh, pktin);
+ if (!c)
+ 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) {
+ int bufsize = 0;
+ c->v.v2.locwindow -= length;
+ c->v.v2.remlocwin -= length;
+ switch (c->type) {
+ case CHAN_MAINSESSION:
+ bufsize =
+ 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);
+ break;
+ case CHAN_SOCKDATA:
+ bufsize = pfd_send(c->u.pfd.s, data, length);
+ break;
+ case CHAN_AGENT:
+ while (length > 0) {
+ if (c->u.a.lensofar < 4) {
+ unsigned int l = min(4 - c->u.a.lensofar,
+ (unsigned)length);
+ memcpy(c->u.a.msglen + c->u.a.lensofar,
+ data, l);
+ data += l;
+ length -= l;
+ c->u.a.lensofar += l;
+ }
+ if (c->u.a.lensofar == 4) {
+ c->u.a.totallen =
+ 4 + GET_32BIT(c->u.a.msglen);
+ c->u.a.message = snewn(c->u.a.totallen,
+ unsigned char);
+ memcpy(c->u.a.message, c->u.a.msglen, 4);
+ }
+ if (c->u.a.lensofar >= 4 && length > 0) {
+ unsigned int l =
+ min(c->u.a.totallen - c->u.a.lensofar,
+ (unsigned)length);
+ memcpy(c->u.a.message + c->u.a.lensofar,
+ data, l);
+ data += l;
+ length -= l;
+ c->u.a.lensofar += l;
+ }
+ if (c->u.a.lensofar == c->u.a.totallen) {
+ void *reply;
+ int replylen;
+ 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.lensofar = 0;
+ }
+ }
+ bufsize = 0;
+ break;
+ }
+ /*
+ * If it looks like the remote end hit the end of its window,
+ * and we didn't want it to do that, think about using a
+ * larger window.
+ */
+ if (c->v.v2.remlocwin <= 0 && c->v.v2.throttle_state == UNTHROTTLED &&
+ c->v.v2.locmaxwin < 0x40000000)
+ c->v.v2.locmaxwin += OUR_V2_WINSIZE;
+ /*
+ * If we are not buffering too much data,
+ * enlarge the window again at the remote side.
+ * If we are buffering too much, we may still
+ * need to adjust the window if the server's
+ * sent excess data.
+ */
+ ssh2_set_window(c, bufsize < c->v.v2.locmaxwin ?
+ c->v.v2.locmaxwin - bufsize : 0);
+ /*
+ * 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) {
+ c->throttling_conn = 1;
+ ssh_throttle_conn(ssh, +1);
+ }
+ }
+}
+
+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);
+ }
+}
+
+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);
+ }
+}
+
+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 */
+ 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);
+ }
+}
+
+static void ssh2_msg_channel_open_failure(Ssh ssh, struct Packet *pktin)
+{
+ static const char *const reasons[] = {
+ "<unknown reason code>",
+ "Administratively prohibited",
+ "Connect failed",
+ "Unknown channel type",
+ "Resource shortage",
+ };
+ 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);
+
+ del234(ssh->channels, c);
+ sfree(c);
+}
+
+static void ssh2_msg_channel_request(Ssh ssh, struct Packet *pktin)
+{
+ char *type;
+ int typelen, want_reply;
+ int reply = SSH2_MSG_CHANNEL_FAILURE; /* default */
+ struct ssh_channel *c;
+ struct Packet *pktout;
+
+ c = ssh2_channel_msg(ssh, pktin);
+ if (!c)
+ return;
+ ssh_pkt_getstring(pktin, &type, &typelen);
+ want_reply = ssh2_pkt_getbool(pktin);
+
+ /*
+ * Having got the channel number, we now look at
+ * the request type string to see if it's something
+ * we recognise.
+ */
+ if (c == ssh->mainchan) {
+ /*
+ * We recognise "exit-status" and "exit-signal" on
+ * the primary channel.
+ */
+ if (typelen == 11 &&
+ !memcmp(type, "exit-status", 11)) {
+
+ ssh->exitcode = ssh_pkt_getuint32(pktin);
+ logeventf(ssh, "Server sent command exit status %d",
+ ssh->exitcode);
+ reply = SSH2_MSG_CHANNEL_SUCCESS;
+
+ } else if (typelen == 11 &&
+ !memcmp(type, "exit-signal", 11)) {
+
+ int is_plausible = TRUE, is_int = FALSE;
+ char *fmt_sig = "", *fmt_msg = "";
+ char *msg;
+ int msglen = 0, core = FALSE;
+ /* ICK: older versions of OpenSSH (e.g. 3.4p1)
+ * provide an `int' for the signal, despite its
+ * having been a `string' in the drafts of RFC 4254 since at
+ * least 2001. (Fixed in session.c 1.147.) Try to
+ * infer which we can safely parse it as. */
+ {
+ unsigned char *p = pktin->body +
+ pktin->savedpos;
+ long len = pktin->length - pktin->savedpos;
+ unsigned long num = GET_32BIT(p); /* what is it? */
+ /* 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)
+ CHECK_HYPOTHESIS(4+1, maybe_int);
+ CHECK_HYPOTHESIS(4+num+1, maybe_str);
+#undef CHECK_HYPOTHESIS
+ if (maybe_int && !maybe_str)
+ is_int = TRUE;
+ else if (!maybe_int && maybe_str)
+ is_int = FALSE;
+ else
+ /* Crikey. Either or neither. Panic. */
+ is_plausible = FALSE;
+ }
+ }
+ ssh->exitcode = 128; /* means `unknown signal' */
+ if (is_plausible) {
+ if (is_int) {
+ /* Old non-standard OpenSSH. */
+ int signum = ssh_pkt_getuint32(pktin);
+ fmt_sig = dupprintf(" %d", signum);
+ ssh->exitcode = 128 + signum;
+ } else {
+ /* As per RFC 4254. */
+ char *sig;
+ int siglen;
+ ssh_pkt_getstring(pktin, &sig, &siglen);
+ /* Signal name isn't supposed to be blank, but
+ * let's cope gracefully if it is. */
+ if (siglen) {
+ fmt_sig = dupprintf(" \"%.*s\"",
+ siglen, sig);
+ }
+
+ /*
+ * Really hideous method of translating the
+ * signal description back into a locally
+ * meaningful number.
+ */
+
+ if (0)
+ ;
+#define TRANSLATE_SIGNAL(s) \
+ else if (siglen == lenof(#s)-1 && !memcmp(sig, #s, siglen)) \
+ ssh->exitcode = 128 + SIG ## s
+#ifdef SIGABRT
+ TRANSLATE_SIGNAL(ABRT);
+#endif
+#ifdef SIGALRM
+ TRANSLATE_SIGNAL(ALRM);
+#endif
+#ifdef SIGFPE
+ TRANSLATE_SIGNAL(FPE);
+#endif
+#ifdef SIGHUP
+ TRANSLATE_SIGNAL(HUP);
+#endif
+#ifdef SIGILL
+ TRANSLATE_SIGNAL(ILL);
+#endif
+#ifdef SIGINT
+ TRANSLATE_SIGNAL(INT);
+#endif
+#ifdef SIGKILL
+ TRANSLATE_SIGNAL(KILL);
+#endif
+#ifdef SIGPIPE
+ TRANSLATE_SIGNAL(PIPE);
+#endif
+#ifdef SIGQUIT
+ TRANSLATE_SIGNAL(QUIT);
+#endif
+#ifdef SIGSEGV
+ TRANSLATE_SIGNAL(SEGV);
+#endif
+#ifdef SIGTERM
+ TRANSLATE_SIGNAL(TERM);
+#endif
+#ifdef SIGUSR1
+ TRANSLATE_SIGNAL(USR1);
+#endif
+#ifdef SIGUSR2
+ TRANSLATE_SIGNAL(USR2);
+#endif
+#undef TRANSLATE_SIGNAL
+ else
+ ssh->exitcode = 128;
+ }
+ core = ssh2_pkt_getbool(pktin);
+ ssh_pkt_getstring(pktin, &msg, &msglen);
+ if (msglen) {
+ fmt_msg = dupprintf(" (\"%.*s\")", msglen, msg);
+ }
+ /* ignore lang tag */
+ } /* else don't attempt to parse */
+ logeventf(ssh, "Server exited on signal%s%s%s",
+ fmt_sig, core ? " (core dumped)" : "",
+ fmt_msg);
+ if (*fmt_sig) sfree(fmt_sig);
+ if (*fmt_msg) sfree(fmt_msg);
+ reply = SSH2_MSG_CHANNEL_SUCCESS;
+
+ }
+ } else {
+ /*
+ * This is a channel request we don't know
+ * about, so we now either ignore the request
+ * or respond with CHANNEL_FAILURE, depending
+ * on want_reply.
+ */
+ reply = SSH2_MSG_CHANNEL_FAILURE;
+ }
+ if (want_reply) {
+ pktout = ssh2_pkt_init(reply);
+ ssh2_pkt_adduint32(pktout, c->remoteid);
+ ssh2_pkt_send(ssh, pktout);
+ }
+}
+
+static void ssh2_msg_global_request(Ssh ssh, struct Packet *pktin)
+{
+ char *type;
+ int typelen, want_reply;
+ struct Packet *pktout;
+
+ ssh_pkt_getstring(pktin, &type, &typelen);
+ want_reply = ssh2_pkt_getbool(pktin);
+
+ /*
+ * We currently don't support any global requests
+ * at all, so we either ignore the request or
+ * respond with REQUEST_FAILURE, depending on
+ * want_reply.
+ */
+ if (want_reply) {
+ pktout = ssh2_pkt_init(SSH2_MSG_REQUEST_FAILURE);
+ ssh2_pkt_send(ssh, pktout);
+ }
+}
+
+static void ssh2_msg_channel_open(Ssh ssh, struct Packet *pktin)
+{
+ char *type;
+ int typelen;
+ char *peeraddr;
+ int peeraddrlen;
+ int peerport;
+ char *error = NULL;
+ struct ssh_channel *c;
+ unsigned remid, winsize, pktsize;
+ struct Packet *pktout;
+
+ ssh_pkt_getstring(pktin, &type, &typelen);
+ c = snew(struct ssh_channel);
+ c->ssh = ssh;
+
+ remid = ssh_pkt_getuint32(pktin);
+ 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';
+ peerport = ssh_pkt_getuint32(pktin);
+
+ logeventf(ssh, "Received X11 connect request from %s:%d",
+ addrstr, peerport);
+
+ if (!ssh->X11_fwd_enabled)
+ 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");
+ c->type = CHAN_X11;
+ }
+
+ 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 */
+ 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);
+ 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);
+ logeventf(ssh, "Attempting to forward remote port to "
+ "%s:%d", realpf->dhost, realpf->dport);
+ if (e != NULL) {
+ logeventf(ssh, "Port open failed: %s", e);
+ error = "Port open failed";
+ } else {
+ logevent("Forwarded port opened successfully");
+ c->type = CHAN_SOCKDATA;
+ }
+ }
+ } else if (typelen == 22 &&
+ !memcmp(type, "auth-agent@openssh.com", 22)) {
+ if (!ssh->agentfwd_enabled)
+ error = "Agent forwarding is not enabled";
+ else {
+ c->type = CHAN_AGENT; /* identify channel type */
+ c->u.a.lensofar = 0;
+ }
+ } else {
+ error = "Unsupported channel type requested";
+ }
+
+ c->remoteid = remid;
+ c->halfopen = FALSE;
+ if (error) {
+ pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_OPEN_FAILURE);
+ ssh2_pkt_adduint32(pktout, c->remoteid);
+ ssh2_pkt_adduint32(pktout, SSH2_OPEN_CONNECT_FAILED);
+ ssh2_pkt_addstring(pktout, error);
+ ssh2_pkt_addstring(pktout, "en"); /* language tag */
+ ssh2_pkt_send(ssh, pktout);
+ logeventf(ssh, "Rejected channel open: %s", error);
+ sfree(c);
+ } else {
+ ssh2_channel_init(c);
+ c->v.v2.remwindow = winsize;
+ c->v.v2.remmaxpkt = pktsize;
+ 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);
+ }
+}
+
+/*
+ * 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 &&
+ bufchain_size(&ssh->banner) <= 131072) {
+ char *banner = NULL;
+ int size = 0;
+ ssh_pkt_getstring(pktin, &banner, &size);
+ if (banner)
+ bufchain_add(&ssh->banner, banner, size);
+ }
+}
+
+/* Helper function to deal with sending tty modes for "pty-req" */
+static void ssh2_send_ttymode(void *data, char *mode, char *val)
+{
+ struct Packet *pktout = (struct Packet *)data;
+ int i = 0;
+ unsigned int arg = 0;
+ while (strcmp(mode, ssh_ttymodes[i].mode) != 0) i++;
+ if (i == lenof(ssh_ttymodes)) return;
+ switch (ssh_ttymodes[i].type) {
+ case TTY_OP_CHAR:
+ arg = ssh_tty_parse_specchar(val);
+ break;
+ case TTY_OP_BOOL:
+ arg = ssh_tty_parse_boolean(val);
+ break;
+ }
+ ssh2_pkt_addbyte(pktout, ssh_ttymodes[i].opcode);
+ ssh2_pkt_adduint32(pktout, arg);
+}
+
+/*
+ * Handle the SSH-2 userauth and connection layers.
+ */
+static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
+ struct Packet *pktin)
+{
+ struct do_ssh2_authconn_state {
+ enum {
+ AUTH_TYPE_NONE,
+ AUTH_TYPE_PUBLICKEY,
+ AUTH_TYPE_PUBLICKEY_OFFER_LOUD,
+ AUTH_TYPE_PUBLICKEY_OFFER_QUIET,
+ AUTH_TYPE_PASSWORD,
+ AUTH_TYPE_GSSAPI,
+ 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;
+ 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 *password;
+ int got_username;
+ void *publickey_blob;
+ int publickey_bloblen;
+ int publickey_encrypted;
+ char *publickey_algorithm;
+ char *publickey_comment;
+ unsigned char agent_request[5], *agent_response, *agentp;
+ int agent_responselen;
+ unsigned char *pkblob_in_agent;
+ int keyi, nkeys;
+ 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;
+#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;
+ Ssh_gss_name gss_srv_name;
+ Ssh_gss_stat gss_stat;
+#endif
+ };
+ crState(do_ssh2_authconn_state);
+
+ crBegin(ssh->do_ssh2_authconn_crstate);
+
+ s->done_service_req = FALSE;
+ s->we_are_in = s->userauth_success = FALSE;
+#ifndef NO_GSSAPI
+ s->tried_gssapi = FALSE;
+#endif
+
+ 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;
+ }
+ }
+
+ /* 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);
+ ssh->packet_dispatch[SSH2_MSG_USERAUTH_BANNER] =
+ ssh2_msg_userauth_banner;
+
+ /*
+ * Misc one-time setup for authentication.
+ */
+ s->publickey_blob = NULL;
+ if (!s->we_are_in) {
+
+ /*
+ * Load the public half of any configured public key file
+ * for later use.
+ */
+ if (!filename_is_null(ssh->cfg.keyfile)) {
+ int keytype;
+ logeventf(ssh, "Reading private key file \"%.150s\"",
+ filename_to_str(&ssh->cfg.keyfile));
+ keytype = key_type(&ssh->cfg.keyfile);
+ if (keytype == SSH_KEYTYPE_SSH2) {
+ const char *error;
+ s->publickey_blob =
+ ssh2_userkey_loadpub(&ssh->cfg.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);
+ } 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),
+ error);
+ c_write_str(ssh, msgbuf);
+ sfree(msgbuf);
+ }
+ } else {
+ 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),
+ key_type_to_str(keytype));
+ c_write_str(ssh, msgbuf);
+ sfree(msgbuf);
+ s->publickey_blob = NULL;
+ }
+ }
+
+ /*
+ * 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()) {
+
+ void *r;
+
+ logevent("Pageant is running. Requesting keys.");
+
+ /* Request the keys held by the agent. */
+ PUT_32BIT(s->agent_request, 1);
+ s->agent_request[4] = SSH2_AGENTC_REQUEST_IDENTITIES;
+ if (!agent_query(s->agent_request, 5, &r, &s->agent_responselen,
+ ssh_agent_callback, ssh)) {
+ do {
+ crReturnV;
+ if (pktin) {
+ bombout(("Unexpected data from server while"
+ " waiting for agent response"));
+ crStopV;
+ }
+ } while (pktin || inlen > 0);
+ r = ssh->agent_response;
+ s->agent_responselen = ssh->agent_response_len;
+ }
+ s->agent_response = (unsigned char *) r;
+ 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);
+ 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);
+ 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);
+ s->keyi = keyi;
+ s->pkblob_in_agent = p;
+ break;
+ }
+ p += 4 + s->pklen;
+ p += 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");
+ }
+ }
+
+ }
+
+ /*
+ * We repeat this whole loop, including the username prompt,
+ * until we manage a successful authentication. If the user
+ * types the wrong _password_, they can be sent back to the
+ * beginning to try another username, if this is configured on.
+ * (If they specify a username in the config, they are never
+ * asked, even if they do give a wrong password.)
+ *
+ * I think this best serves the needs of
+ *
+ * - the people who have no configuration, no keys, and just
+ * want to try repeated (username,password) pairs until they
+ * type both correctly
+ *
+ * - people who have keys and configuration but occasionally
+ * need to fall back to passwords
+ *
+ * - people with a key held in Pageant, who might not have
+ * logged in to a particular machine before; so they want to
+ * 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) {
+ /*
+ * 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))) {
+ 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));
+ 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);
+ ssh->send_ok = 0;
+ }
+ if (!ret) {
+ /*
+ * get_userpass_input() failed to get a username.
+ * Terminate.
+ */
+ 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));
+ free_prompts(s->cur_prompt);
+ } else {
+ char *stuff;
+ if ((flags & FLAG_VERBOSE) || (flags & FLAG_INTERACTIVE)) {
+ stuff = dupprintf("Using username \"%s\".\r\n", s->username);
+ c_write_str(ssh, stuff);
+ sfree(stuff);
+ }
+ }
+ s->got_username = TRUE;
+
+ /*
+ * Send an authentication request using method "none": (a)
+ * just in case it succeeds, and (b) so that we know what
+ * 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-connection");/* service requested */
+ ssh2_pkt_addstring(s->pktout, "none"); /* method */
+ ssh2_pkt_send(ssh, s->pktout);
+ s->type = AUTH_TYPE_NONE;
+ s->gotit = FALSE;
+ s->we_are_in = FALSE;
+
+ s->tried_pubkey_config = FALSE;
+ s->kbd_inter_refused = FALSE;
+
+ /* Reset agent request state. */
+ s->done_agent = FALSE;
+ if (s->agent_response) {
+ if (s->pkblob_in_agent) {
+ s->agentp = s->pkblob_in_agent;
+ } else {
+ s->agentp = s->agent_response + 5 + 4;
+ s->keyi = 0;
+ }
+ }
+
+ while (1) {
+ char *methods = NULL;
+ int methlen = 0;
+
+ /*
+ * Wait for the result of the last authentication request.
+ */
+ if (!s->gotit)
+ crWaitUntilV(pktin);
+ /*
+ * Now is a convenient point to spew any banner material
+ * that we've accumulated. (This should ensure that when
+ * we exit the auth loop, we haven't any left to deal
+ * with.)
+ */
+ {
+ int size = bufchain_size(&ssh->banner);
+ /*
+ * Don't show the banner if we're operating in
+ * non-verbose non-interactive mode. (It's probably
+ * a script, which means nobody will read the
+ * banner _anyway_, and moreover the printing of
+ * the banner will screw up processing on the
+ * output of (say) plink.)
+ */
+ if (size && (flags & (FLAG_VERBOSE | FLAG_INTERACTIVE))) {
+ char *banner = snewn(size, char);
+ bufchain_fetch(&ssh->banner, banner, size);
+ c_write_untrusted(ssh, banner, size);
+ sfree(banner);
+ }
+ bufchain_clear(&ssh->banner);
+ }
+ if (pktin->type == SSH2_MSG_USERAUTH_SUCCESS) {
+ logevent("Access granted");
+ s->we_are_in = s->userauth_success = TRUE;
+ break;
+ }
+
+ if (pktin->type != SSH2_MSG_USERAUTH_FAILURE && s->type != AUTH_TYPE_GSSAPI) {
+ bombout(("Strange packet received during authentication: "
+ "type %d", pktin->type));
+ crStopV;
+ }
+
+ s->gotit = FALSE;
+
+ /*
+ * OK, we're now sitting on a USERAUTH_FAILURE message, so
+ * we can look at the string in it and know what we can
+ * helpfully try next.
+ */
+ if (pktin->type == SSH2_MSG_USERAUTH_FAILURE) {
+ ssh_pkt_getstring(pktin, &methods, &methlen);
+ if (!ssh2_pkt_getbool(pktin)) {
+ /*
+ * We have received an unequivocal Access
+ * Denied. This can translate to a variety of
+ * messages:
+ *
+ * - if we'd just tried "none" authentication,
+ * it's not worth printing anything at all
+ *
+ * - if we'd just tried a public key _offer_,
+ * the message should be "Server refused our
+ * key" (or no message at all if the key
+ * came from Pageant)
+ *
+ * - if we'd just tried anything else, the
+ * message really should be "Access denied".
+ *
+ * Additionally, if we'd just tried password
+ * authentication, we should break out of this
+ * whole loop so as to go back to the username
+ * prompt (iff we're configured to allow
+ * username change attempts).
+ */
+ if (s->type == AUTH_TYPE_NONE) {
+ /* do nothing */
+ } else if (s->type == AUTH_TYPE_PUBLICKEY_OFFER_LOUD ||
+ s->type == AUTH_TYPE_PUBLICKEY_OFFER_QUIET) {
+ if (s->type == AUTH_TYPE_PUBLICKEY_OFFER_LOUD)
+ c_write_str(ssh, "Server refused our key\r\n");
+ logevent("Server refused public key");
+ } else if (s->type==AUTH_TYPE_KEYBOARD_INTERACTIVE_QUIET) {
+ /* server declined keyboard-interactive; ignore */
+ } else {
+ c_write_str(ssh, "Access denied\r\n");
+ logevent("Access denied");
+ if (s->type == AUTH_TYPE_PASSWORD &&
+ ssh->cfg.change_username) {
+ /* XXX perhaps we should allow
+ * keyboard-interactive to do this too? */
+ s->we_are_in = FALSE;
+ break;
+ }
+ }
+ } else {
+ c_write_str(ssh, "Further authentication required\r\n");
+ logevent("Further authentication required");
+ }
+
+ s->can_pubkey =
+ in_commasep_string("publickey", methods, methlen);
+ s->can_passwd =
+ in_commasep_string("password", methods, methlen);
+ s->can_keyb_inter = ssh->cfg.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 &&
+ in_commasep_string("gssapi-with-mic", methods, methlen) &&
+ ssh->gsslibs->nlibraries > 0;
+#endif
+ }
+
+ ssh->pkt_actx = SSH2_PKTCTX_NOAUTH;
+
+ if (s->can_pubkey && !s->done_agent && s->nkeys) {
+
+ /*
+ * Attempt public-key authentication using a key from Pageant.
+ */
+
+ 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->agentp += 4;
+ s->pkblob = (char *)s->agentp;
+ s->agentp += s->pklen;
+ s->alglen = GET_32BIT(s->pkblob);
+ s->alg = s->pkblob + 4;
+ s->commentlen = 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-connection");
+ /* service requested */
+ ssh2_pkt_addstring(s->pktout, "publickey");
+ /* method */
+ ssh2_pkt_addbool(s->pktout, FALSE); /* no signature included */
+ ssh2_pkt_addstring_start(s->pktout);
+ ssh2_pkt_addstring_data(s->pktout, s->alg, s->alglen);
+ ssh2_pkt_addstring_start(s->pktout);
+ ssh2_pkt_addstring_data(s->pktout, s->pkblob, s->pklen);
+ ssh2_pkt_send(ssh, s->pktout);
+ s->type = AUTH_TYPE_PUBLICKEY_OFFER_QUIET;
+
+ crWaitUntilV(pktin);
+ if (pktin->type != SSH2_MSG_USERAUTH_PK_OK) {
+
+ /* Offer of key refused. */
+ s->gotit = TRUE;
+
+ } else {
+
+ void *vret;
+
+ if (flags & FLAG_VERBOSE) {
+ c_write_str(ssh, "Authenticating with "
+ "public key \"");
+ c_write(ssh, s->commentp, s->commentlen);
+ c_write_str(ssh, "\" from agent\r\n");
+ }
+
+ /*
+ * 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-connection");
+ /* service requested */
+ ssh2_pkt_addstring(s->pktout, "publickey");
+ /* method */
+ ssh2_pkt_addbool(s->pktout, TRUE); /* signature included */
+ ssh2_pkt_addstring_start(s->pktout);
+ ssh2_pkt_addstring_data(s->pktout, s->alg, s->alglen);
+ ssh2_pkt_addstring_start(s->pktout);
+ ssh2_pkt_addstring_data(s->pktout, s->pkblob, s->pklen);
+
+ /* Ask agent for signature. */
+ s->siglen = s->pktout->length - 5 + 4 +
+ ssh->v2_session_id_len;
+ if (ssh->remote_bugs & BUG_SSH2_PK_SESSIONID)
+ s->siglen -= 4;
+ s->len = 1; /* message type */
+ s->len += 4 + s->pklen; /* key blob */
+ s->len += 4 + s->siglen; /* data to sign */
+ s->len += 4; /* flags */
+ s->agentreq = snewn(4 + s->len, char);
+ PUT_32BIT(s->agentreq, s->len);
+ s->q = s->agentreq + 4;
+ *s->q++ = SSH2_AGENTC_SIGN_REQUEST;
+ PUT_32BIT(s->q, s->pklen);
+ s->q += 4;
+ memcpy(s->q, s->pkblob, s->pklen);
+ s->q += s->pklen;
+ PUT_32BIT(s->q, s->siglen);
+ s->q += 4;
+ /* Now the data to be signed... */
+ if (!(ssh->remote_bugs & BUG_SSH2_PK_SESSIONID)) {
+ PUT_32BIT(s->q, ssh->v2_session_id_len);
+ s->q += 4;
+ }
+ memcpy(s->q, ssh->v2_session_id,
+ ssh->v2_session_id_len);
+ s->q += ssh->v2_session_id_len;
+ memcpy(s->q, s->pktout->data + 5,
+ s->pktout->length - 5);
+ s->q += s->pktout->length - 5;
+ /* And finally the (zero) flags word. */
+ PUT_32BIT(s->q, 0);
+ if (!agent_query(s->agentreq, s->len + 4,
+ &vret, &s->retlen,
+ ssh_agent_callback, ssh)) {
+ do {
+ crReturnV;
+ if (pktin) {
+ bombout(("Unexpected data from server"
+ " while waiting for agent"
+ " response"));
+ crStopV;
+ }
+ } while (pktin || inlen > 0);
+ vret = ssh->agent_response;
+ s->retlen = ssh->agent_response_len;
+ }
+ s->ret = vret;
+ sfree(s->agentreq);
+ if (s->ret) {
+ if (s->ret[4] == SSH2_AGENT_SIGN_RESPONSE) {
+ logevent("Sending Pageant's response");
+ ssh2_add_sigblob(ssh, s->pktout,
+ s->pkblob, s->pklen,
+ s->ret + 9,
+ GET_32BIT(s->ret + 5));
+ ssh2_pkt_send(ssh, s->pktout);
+ s->type = AUTH_TYPE_PUBLICKEY;
+ } else {
+ /* FIXME: less drastic response */
+ bombout(("Pageant failed to answer challenge"));
+ crStopV;
+ }
+ }
+ }
+
+ /* Do we have any keys left to try? */
+ if (s->pkblob_in_agent) {
+ s->done_agent = TRUE;
+ s->tried_pubkey_config = TRUE;
+ } else {
+ s->keyi++;
+ if (s->keyi >= s->nkeys)
+ s->done_agent = TRUE;
+ }
+
+ } 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 */
+
+ ssh->pkt_actx = SSH2_PKTCTX_PUBLICKEY;
+
+ s->tried_pubkey_config = TRUE;
+
+ /*
+ * Try the public key supplied in the configuration.
+ *
+ * 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-connection");
+ /* service requested */
+ ssh2_pkt_addstring(s->pktout, "publickey"); /* method */
+ ssh2_pkt_addbool(s->pktout, FALSE);
+ /* no signature included */
+ ssh2_pkt_addstring(s->pktout, s->publickey_algorithm);
+ ssh2_pkt_addstring_start(s->pktout);
+ ssh2_pkt_addstring_data(s->pktout,
+ (char *)s->publickey_blob,
+ s->publickey_bloblen);
+ ssh2_pkt_send(ssh, s->pktout);
+ logevent("Offered public key");
+
+ crWaitUntilV(pktin);
+ if (pktin->type != SSH2_MSG_USERAUTH_PK_OK) {
+ /* Key refused. Give up. */
+ s->gotit = TRUE; /* reconsider message next loop */
+ s->type = AUTH_TYPE_PUBLICKEY_OFFER_LOUD;
+ continue; /* process this new message */
+ }
+ logevent("Offer of public key accepted");
+
+ /*
+ * Actually attempt a serious authentication using
+ * the key.
+ */
+ if (flags & FLAG_VERBOSE) {
+ c_write_str(ssh, "Authenticating with public key \"");
+ c_write_str(ssh, s->publickey_comment);
+ c_write_str(ssh, "\"\r\n");
+ }
+ key = NULL;
+ while (!key) {
+ const char *error; /* not live over crReturn */
+ 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");
+ add_prompt(s->cur_prompt,
+ dupprintf("Passphrase for key \"%.100s\": ",
+ s->publickey_comment),
+ FALSE, SSH_MAX_PASSWORD_LEN);
+ 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);
+ ssh->send_ok = 0;
+ }
+ if (!ret) {
+ /* Failed to get a passphrase. Terminate. */
+ free_prompts(s->cur_prompt);
+ ssh_disconnect(ssh, NULL,
+ "Unable to authenticate",
+ SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER,
+ TRUE);
+ crStopV;
+ }
+ passphrase =
+ dupstr(s->cur_prompt->prompts[0]->result);
+ free_prompts(s->cur_prompt);
+ } else {
+ passphrase = NULL; /* no passphrase needed */
+ }
+
+ /*
+ * Try decrypting the key.
+ */
+ key = ssh2_load_userkey(&ssh->cfg.keyfile, passphrase,
+ &error);
+ if (passphrase) {
+ /* burn the evidence */
+ memset(passphrase, 0, strlen(passphrase));
+ sfree(passphrase);
+ }
+ if (key == SSH2_WRONG_PASSPHRASE || key == NULL) {
+ if (passphrase &&
+ (key == SSH2_WRONG_PASSPHRASE)) {
+ c_write_str(ssh, "Wrong passphrase\r\n");
+ key = NULL;
+ /* and loop again */
+ } else {
+ c_write_str(ssh, "Unable to load private key (");
+ c_write_str(ssh, error);
+ c_write_str(ssh, ")\r\n");
+ key = NULL;
+ break; /* try something else */
+ }
+ }
+ }
+
+ if (key) {
+ unsigned char *pkblob, *sigblob, *sigdata;
+ int pkblob_len, sigblob_len, sigdata_len;
+ int p;
+
+ /*
+ * 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-connection");
+ /* service requested */
+ ssh2_pkt_addstring(s->pktout, "publickey");
+ /* method */
+ ssh2_pkt_addbool(s->pktout, TRUE);
+ /* signature follows */
+ 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);
+
+ /*
+ * The data to be signed is:
+ *
+ * string session-id
+ *
+ * followed by everything so far placed in the
+ * outgoing packet.
+ */
+ sigdata_len = s->pktout->length - 5 + 4 +
+ ssh->v2_session_id_len;
+ if (ssh->remote_bugs & BUG_SSH2_PK_SESSIONID)
+ sigdata_len -= 4;
+ sigdata = snewn(sigdata_len, unsigned char);
+ p = 0;
+ if (!(ssh->remote_bugs & BUG_SSH2_PK_SESSIONID)) {
+ PUT_32BIT(sigdata+p, ssh->v2_session_id_len);
+ p += 4;
+ }
+ memcpy(sigdata+p, ssh->v2_session_id,
+ ssh->v2_session_id_len);
+ 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);
+ 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);
+ sfree(sigblob);
+ sfree(sigdata);
+
+ ssh2_pkt_send(ssh, s->pktout);
+ s->type = AUTH_TYPE_PUBLICKEY;
+ key->alg->freekey(key->data);
+ }
+
+#ifndef NO_GSSAPI
+ } else if (s->can_gssapi && !s->tried_gssapi) {
+
+ /* GSSAPI Authentication */
+
+ int micoffset, len;
+ char *data;
+ Ssh_gss_buf mic;
+ s->type = AUTH_TYPE_GSSAPI;
+ s->tried_gssapi = TRUE;
+ s->gotit = TRUE;
+ ssh->pkt_actx = SSH2_PKTCTX_GSSAPI;
+
+ /*
+ * Pick the highest GSS library on the preference
+ * list.
+ */
+ {
+ int i, j;
+ s->gsslib = NULL;
+ for (i = 0; i < ngsslibs; i++) {
+ int want_id = ssh->cfg.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 */
+ }
+ }
+ got_gsslib:
+ /*
+ * We always expect to have found something in
+ * the above loop: we only came here if there
+ * was at least one viable GSS library, and the
+ * preference list should always mention
+ * everything and only change the order.
+ */
+ assert(s->gsslib);
+ }
+
+ 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-connection");
+ ssh2_pkt_addstring(s->pktout, "gssapi-with-mic");
+
+ /* add mechanism info */
+ s->gsslib->indicate_mech(s->gsslib, &s->gss_buf);
+
+ /* number of GSSAPI mechanisms */
+ ssh2_pkt_adduint32(s->pktout,1);
+
+ /* length of OID + 2 */
+ ssh2_pkt_adduint32(s->pktout, s->gss_buf.length + 2);
+ ssh2_pkt_addbyte(s->pktout, SSH2_GSS_OIDTYPE);
+
+ /* length of OID */
+ ssh2_pkt_addbyte(s->pktout, (unsigned char) s->gss_buf.length);
+
+ ssh_pkt_adddata(s->pktout, s->gss_buf.value,
+ s->gss_buf.length);
+ ssh2_pkt_send(ssh, s->pktout);
+ crWaitUntilV(pktin);
+ if (pktin->type != SSH2_MSG_USERAUTH_GSSAPI_RESPONSE) {
+ logevent("GSSAPI authentication request refused");
+ continue;
+ }
+
+ /* check returned packet ... */
+
+ ssh_pkt_getstring(pktin, &data, &len);
+ s->gss_rcvtok.value = data;
+ s->gss_rcvtok.length = len;
+ if (s->gss_rcvtok.length != s->gss_buf.length + 2 ||
+ ((char *)s->gss_rcvtok.value)[0] != SSH2_GSS_OIDTYPE ||
+ ((char *)s->gss_rcvtok.value)[1] != s->gss_buf.length ||
+ memcmp((char *)s->gss_rcvtok.value + 2,
+ s->gss_buf.value,s->gss_buf.length) ) {
+ logevent("GSSAPI authentication - wrong response from server");
+ continue;
+ }
+
+ /* now start running */
+ s->gss_stat = s->gsslib->import_name(s->gsslib,
+ ssh->fullhostname,
+ &s->gss_srv_name);
+ if (s->gss_stat != SSH_GSS_OK) {
+ if (s->gss_stat == SSH_GSS_BAD_HOST_NAME)
+ logevent("GSSAPI import name failed - Bad service name");
+ else
+ logevent("GSSAPI import name failed");
+ continue;
+ }
+
+ /* fetch TGT into GSS engine */
+ s->gss_stat = s->gsslib->acquire_cred(s->gsslib, &s->gss_ctx);
+
+ if (s->gss_stat != SSH_GSS_OK) {
+ logevent("GSSAPI authentication failed to get credentials");
+ s->gsslib->release_name(s->gsslib, &s->gss_srv_name);
+ continue;
+ }
+
+ /* initial tokens are empty */
+ SSH_GSS_CLEAR_BUF(&s->gss_rcvtok);
+ SSH_GSS_CLEAR_BUF(&s->gss_sndtok);
+
+ /* now enter the loop */
+ do {
+ s->gss_stat = s->gsslib->init_sec_context
+ (s->gsslib,
+ &s->gss_ctx,
+ s->gss_srv_name,
+ ssh->cfg.gssapifwd,
+ &s->gss_rcvtok,
+ &s->gss_sndtok);
+
+ if (s->gss_stat!=SSH_GSS_S_COMPLETE &&
+ s->gss_stat!=SSH_GSS_S_CONTINUE_NEEDED) {
+ logevent("GSSAPI authentication initialisation failed");
+
+ if (s->gsslib->display_status(s->gsslib, s->gss_ctx,
+ &s->gss_buf) == SSH_GSS_OK) {
+ logevent(s->gss_buf.value);
+ sfree(s->gss_buf.value);
+ }
+
+ break;
+ }
+ logevent("GSSAPI authentication initialised");
+
+ /* Client and server now exchange tokens until GSSAPI
+ * no longer says CONTINUE_NEEDED */
+
+ if (s->gss_sndtok.length != 0) {
+ s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_GSSAPI_TOKEN);
+ ssh_pkt_addstring_start(s->pktout);
+ ssh_pkt_addstring_data(s->pktout,s->gss_sndtok.value,s->gss_sndtok.length);
+ ssh2_pkt_send(ssh, s->pktout);
+ s->gsslib->free_tok(s->gsslib, &s->gss_sndtok);
+ }
+
+ if (s->gss_stat == SSH_GSS_S_CONTINUE_NEEDED) {
+ crWaitUntilV(pktin);
+ if (pktin->type != SSH2_MSG_USERAUTH_GSSAPI_TOKEN) {
+ logevent("GSSAPI authentication - bad server response");
+ s->gss_stat = SSH_GSS_FAILURE;
+ break;
+ }
+ ssh_pkt_getstring(pktin, &data, &len);
+ s->gss_rcvtok.value = data;
+ s->gss_rcvtok.length = len;
+ }
+ } while (s-> gss_stat == SSH_GSS_S_CONTINUE_NEEDED);
+
+ if (s->gss_stat != SSH_GSS_OK) {
+ s->gsslib->release_name(s->gsslib, &s->gss_srv_name);
+ s->gsslib->release_cred(s->gsslib, &s->gss_ctx);
+ continue;
+ }
+ logevent("GSSAPI authentication loop finished OK");
+
+ /* Now send the MIC */
+
+ 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-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;
+
+ s->gsslib->get_mic(s->gsslib, s->gss_ctx, &s->gss_buf, &mic);
+ s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_GSSAPI_MIC);
+ ssh_pkt_addstring_start(s->pktout);
+ ssh_pkt_addstring_data(s->pktout, mic.value, mic.length);
+ ssh2_pkt_send(ssh, s->pktout);
+ s->gsslib->free_mic(s->gsslib, &mic);
+
+ s->gotit = FALSE;
+
+ s->gsslib->release_name(s->gsslib, &s->gss_srv_name);
+ s->gsslib->release_cred(s->gsslib, &s->gss_ctx);
+ continue;
+#endif
+ } else if (s->can_keyb_inter && !s->kbd_inter_refused) {
+
+ /*
+ * Keyboard-interactive authentication.
+ */
+
+ 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-connection");
+ /* service requested */
+ ssh2_pkt_addstring(s->pktout, "keyboard-interactive");
+ /* method */
+ ssh2_pkt_addstring(s->pktout, ""); /* lang */
+ ssh2_pkt_addstring(s->pktout, ""); /* submethods */
+ ssh2_pkt_send(ssh, s->pktout);
+
+ crWaitUntilV(pktin);
+ if (pktin->type != SSH2_MSG_USERAUTH_INFO_REQUEST) {
+ /* Server is not willing to do keyboard-interactive
+ * at all (or, bizarrely but legally, accepts the
+ * user without actually issuing any prompts).
+ * Give up on it entirely. */
+ s->gotit = TRUE;
+ if (pktin->type == SSH2_MSG_USERAUTH_FAILURE)
+ logevent("Keyboard-interactive authentication refused");
+ s->type = AUTH_TYPE_KEYBOARD_INTERACTIVE_QUIET;
+ s->kbd_inter_refused = TRUE; /* don't try it again */
+ continue;
+ }
+
+ /*
+ * Loop while the server continues to send INFO_REQUESTs.
+ */
+ while (pktin->type == SSH2_MSG_USERAUTH_INFO_REQUEST) {
+
+ char *name, *inst, *lang;
+ int name_len, inst_len, lang_len;
+ int i;
+
+ /*
+ * We've got a fresh USERAUTH_INFO_REQUEST.
+ * Get the preamble and start building a prompt.
+ */
+ ssh_pkt_getstring(pktin, &name, &name_len);
+ ssh_pkt_getstring(pktin, &inst, &inst_len);
+ ssh_pkt_getstring(pktin, &lang, &lang_len);
+ s->cur_prompt = new_prompts(ssh->frontend);
+ s->cur_prompt->to_server = TRUE;
+
+ /*
+ * Get any prompt(s) from the packet.
+ */
+ s->num_prompts = ssh_pkt_getuint32(pktin);
+ for (i = 0; i < s->num_prompts; i++) {
+ char *prompt;
+ int prompt_len;
+ int echo;
+ static char noprompt[] =
+ "<server failed to send prompt>: ";
+
+ ssh_pkt_getstring(pktin, &prompt, &prompt_len);
+ echo = ssh2_pkt_getbool(pktin);
+ if (!prompt_len) {
+ prompt = noprompt;
+ prompt_len = lenof(noprompt)-1;
+ }
+ add_prompt(s->cur_prompt,
+ dupprintf("%.*s", prompt_len, prompt),
+ echo, SSH_MAX_PASSWORD_LEN);
+ }
+
+ if (name_len) {
+ /* FIXME: better prefix to distinguish from
+ * local prompts? */
+ s->cur_prompt->name =
+ dupprintf("SSH server: %.*s", name_len, name);
+ s->cur_prompt->name_reqd = TRUE;
+ } else {
+ s->cur_prompt->name =
+ dupstr("SSH server authentication");
+ s->cur_prompt->name_reqd = FALSE;
+ }
+ /* We add a prefix to try to make it clear that a prompt
+ * has come from the server.
+ * FIXME: ugly to print "Using..." in prompt _every_
+ * time round. Can this be done more subtly? */
+ /* Special case: for reasons best known to themselves,
+ * some servers send k-i requests with no prompts and
+ * nothing to display. Keep quiet in this case. */
+ if (s->num_prompts || name_len || inst_len) {
+ s->cur_prompt->instruction =
+ dupprintf("Using keyboard-interactive authentication.%s%.*s",
+ inst_len ? "\n" : "", inst_len, inst);
+ s->cur_prompt->instr_reqd = TRUE;
+ } else {
+ s->cur_prompt->instr_reqd = FALSE;
+ }
+
+ /*
+ * Display any instructions, and get the user's
+ * response(s).
+ */
+ {
+ int ret; /* not live over crReturn */
+ 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);
+ ssh->send_ok = 0;
+ }
+ if (!ret) {
+ /*
+ * Failed to get responses. Terminate.
+ */
+ free_prompts(s->cur_prompt);
+ ssh_disconnect(ssh, NULL, "Unable to authenticate",
+ SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER,
+ TRUE);
+ crStopV;
+ }
+ }
+
+ /*
+ * 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);
+
+ /*
+ * Get the next packet in case it's another
+ * INFO_REQUEST.
+ */
+ crWaitUntilV(pktin);
+
+ }
+
+ /*
+ * We should have SUCCESS or FAILURE now.
+ */
+ s->gotit = TRUE;
+
+ } else if (s->can_passwd) {
+
+ /*
+ * Plain old password authentication.
+ */
+ int ret; /* not live over crReturn */
+ int changereq_first_time; /* not live over crReturn */
+
+ 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,
+ ssh->savedhost),
+ FALSE, SSH_MAX_PASSWORD_LEN);
+
+ 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);
+ ssh->send_ok = 0;
+ }
+ if (!ret) {
+ /*
+ * Failed to get responses. Terminate.
+ */
+ free_prompts(s->cur_prompt);
+ ssh_disconnect(ssh, NULL, "Unable to authenticate",
+ SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER,
+ TRUE);
+ crStopV;
+ }
+ /*
+ * Squirrel away the password. (We may need it later if
+ * asked to change it.)
+ */
+ s->password = dupstr(s->cur_prompt->prompts[0]->result);
+ free_prompts(s->cur_prompt);
+
+ /*
+ * Send the password packet.
+ *
+ * We pad out the password packet to 256 bytes to make
+ * it harder for an attacker to find the length of the
+ * user's password.
+ *
+ * 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-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;
+
+ /*
+ * Wait for next packet, in case it's a password change
+ * request.
+ */
+ crWaitUntilV(pktin);
+ changereq_first_time = TRUE;
+
+ while (pktin->type == SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ) {
+
+ /*
+ * We're being asked for a new password
+ * (perhaps not for the first time).
+ * Loop until the server accepts it.
+ */
+
+ int got_new = FALSE; /* not live over crReturn */
+ char *prompt; /* not live over crReturn */
+ int prompt_len; /* not live over crReturn */
+
+ {
+ char *msg;
+ if (changereq_first_time)
+ msg = "Server requested password change";
+ else
+ msg = "Server rejected new password";
+ logevent(msg);
+ c_write_str(ssh, msg);
+ c_write_str(ssh, "\r\n");
+ }
+
+ ssh_pkt_getstring(pktin, &prompt, &prompt_len);
+
+ s->cur_prompt = new_prompts(ssh->frontend);
+ s->cur_prompt->to_server = TRUE;
+ s->cur_prompt->name = dupstr("New SSH password");
+ s->cur_prompt->instruction =
+ dupprintf("%.*s", prompt_len, prompt);
+ s->cur_prompt->instr_reqd = TRUE;
+ /*
+ * There's no explicit requirement in the protocol
+ * for the "old" passwords in the original and
+ * password-change messages to be the same, and
+ * apparently some Cisco kit supports password change
+ * by the user entering a blank password originally
+ * and the real password subsequently, so,
+ * reluctantly, we prompt for the old password again.
+ *
+ * (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);
+ add_prompt(s->cur_prompt, dupstr("Enter new password: "),
+ FALSE, SSH_MAX_PASSWORD_LEN);
+ add_prompt(s->cur_prompt, dupstr("Confirm new password: "),
+ FALSE, SSH_MAX_PASSWORD_LEN);
+
+ /*
+ * Loop until the user manages to enter the same
+ * password twice.
+ */
+ while (!got_new) {
+
+ 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);
+ ssh->send_ok = 0;
+ }
+ if (!ret) {
+ /*
+ * Failed to get responses. Terminate.
+ */
+ /* burn the evidence */
+ free_prompts(s->cur_prompt);
+ memset(s->password, 0, strlen(s->password));
+ sfree(s->password);
+ ssh_disconnect(ssh, NULL, "Unable to authenticate",
+ SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER,
+ TRUE);
+ crStopV;
+ }
+
+ /*
+ * If the user specified a new original password
+ * (IYSWIM), overwrite any previously specified
+ * 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));
+ /* burn the evidence */
+ sfree(s->password);
+ s->password =
+ dupstr(s->cur_prompt->prompts[0]->result);
+ }
+
+ /*
+ * Check the two new passwords match.
+ */
+ got_new = (strcmp(s->cur_prompt->prompts[1]->result,
+ s->cur_prompt->prompts[2]->result)
+ == 0);
+ if (!got_new)
+ /* They don't. Silly user. */
+ c_write_str(ssh, "Passwords do not match\r\n");
+
+ }
+
+ /*
+ * 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-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.
+ * (If it's CHANGEREQ again, it's not happy with the
+ * new password.)
+ */
+ crWaitUntilV(pktin);
+ changereq_first_time = FALSE;
+
+ }
+
+ /*
+ * We need to reexamine the current pktin at the top
+ * of the loop. Either:
+ * - we weren't asked to change password at all, in
+ * which case it's a SUCCESS or FAILURE with the
+ * usual meaning
+ * - we sent a new password, and the server was
+ * either OK with it (SUCCESS or FAILURE w/partial
+ * success) or unhappy with the _old_ password
+ * (FAILURE w/o partial success)
+ * In any of these cases, we go back to the top of
+ * the loop and start again.
+ */
+ s->gotit = TRUE;
+
+ /*
+ * We don't need the old password any more, in any
+ * case. Burn the evidence.
+ */
+ memset(s->password, 0, strlen(s->password));
+ sfree(s->password);
+
+ } else {
+ char *str = dupprintf("No supported authentication methods available"
+ " (server sent: %.*s)",
+ methlen, methods);
+
+ ssh_disconnect(ssh, str,
+ "No supported authentication methods available",
+ SSH2_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE,
+ FALSE);
+ sfree(str);
+
+ crStopV;
+
+ }
+
+ }
+ }
+ ssh->packet_dispatch[SSH2_MSG_USERAUTH_BANNER] = NULL;
+
+ /* Clear up various bits and pieces from authentication. */
+ if (s->publickey_blob) {
+ sfree(s->publickey_blob);
+ sfree(s->publickey_comment);
+ }
+ if (s->agent_response)
+ sfree(s->agent_response);
+
+ if (s->userauth_success) {
+ /*
+ * We've just received USERAUTH_SUCCESS, and we haven't sent any
+ * packets since. Signal the transport layer to consider enacting
+ * delayed compression.
+ *
+ * (Relying on we_are_in is not sufficient, as
+ * draft-miller-secsh-compression-delayed is quite clear that it
+ * triggers on USERAUTH_SUCCESS specifically, and we_are_in can
+ * 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.
+ */
+ ssh->packet_dispatch[SSH2_MSG_CHANNEL_WINDOW_ADJUST] =
+ ssh2_msg_channel_window_adjust;
+ ssh->packet_dispatch[SSH2_MSG_GLOBAL_REQUEST] =
+ ssh2_msg_global_request;
+
+ /*
+ * Create the main session channel.
+ */
+ if (ssh->cfg.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.
+ */
+ 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);
+
+ crWaitUntilV(pktin);
+ if (pktin->type != SSH2_MSG_CHANNEL_OPEN_CONFIRMATION) {
+ bombout(("Server refused to open a direct-tcpip 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"));
+ 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 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;
+ }
+
+ /*
+ * Now we have a channel, make dispatch table entries for
+ * general channel-based messages.
+ */
+ ssh->packet_dispatch[SSH2_MSG_CHANNEL_DATA] =
+ ssh->packet_dispatch[SSH2_MSG_CHANNEL_EXTENDED_DATA] =
+ ssh2_msg_channel_data;
+ ssh->packet_dispatch[SSH2_MSG_CHANNEL_EOF] = ssh2_msg_channel_eof;
+ ssh->packet_dispatch[SSH2_MSG_CHANNEL_CLOSE] = ssh2_msg_channel_close;
+ ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN_CONFIRMATION] =
+ ssh2_msg_channel_open_confirmation;
+ ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN_FAILURE] =
+ 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;
+
+ if (ssh->mainchan && ssh->cfg.ssh_simple) {
+ /*
+ * 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;
+ }
+ }
+
+ /*
+ * 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->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 */
+ if (ssh->mainchan)
+ ssh->send_ok = 1;
+ while (1) {
+ crReturnV;
+ s->try_send = FALSE;
+ if (pktin) {
+
+ /*
+ * _All_ the connection-layer packets we expect to
+ * receive are now handled by the dispatch table.
+ * Anything that reaches here must be bogus.
+ */
+
+ bombout(("Strange packet received: type %d", pktin->type));
+ crStopV;
+ } else if (ssh->mainchan) {
+ /*
+ * We have spare data. Add it to the channel buffer.
+ */
+ ssh2_add_channel_data(ssh->mainchan, (char *)in, inlen);
+ s->try_send = TRUE;
+ }
+ if (s->try_send) {
+ int i;
+ struct ssh_channel *c;
+ /*
+ * Try to send data on all channels if we can.
+ */
+ for (i = 0; NULL != (c = index234(ssh->channels, i)); i++)
+ ssh2_try_send_and_unthrottle(ssh, c);
+ }
+ }
+
+ crFinishV;
+}
+
+/*
+ * Handlers for SSH-2 messages that might arrive at any moment.
+ */
+static void ssh2_msg_disconnect(Ssh ssh, struct Packet *pktin)
+{
+ /* log reason code in disconnect message */
+ char *buf, *msg;
+ int reason, msglen;
+
+ reason = ssh_pkt_getuint32(pktin);
+ ssh_pkt_getstring(pktin, &msg, &msglen);
+
+ if (reason > 0 && reason < lenof(ssh2_disconnect_reasons)) {
+ buf = dupprintf("Received disconnect message (%s)",
+ ssh2_disconnect_reasons[reason]);
+ } else {
+ buf = dupprintf("Received disconnect message (unknown"
+ " type %d)", reason);
+ }
+ logevent(buf);
+ sfree(buf);
+ buf = dupprintf("Disconnection message text: %.*s",
+ msglen, msg);
+ logevent(buf);
+ bombout(("Server sent disconnect message\ntype %d (%s):\n\"%.*s\"",
+ reason,
+ (reason > 0 && reason < lenof(ssh2_disconnect_reasons)) ?
+ ssh2_disconnect_reasons[reason] : "unknown",
+ msglen, msg));
+ sfree(buf);
+}
+
+static void ssh2_msg_debug(Ssh ssh, struct Packet *pktin)
+{
+ /* log the debug message */
+ char *msg;
+ int msglen;
+
+ /* XXX maybe we should actually take notice of the return value */
+ ssh2_pkt_getbool(pktin);
+ ssh_pkt_getstring(pktin, &msg, &msglen);
+
+ logeventf(ssh, "Remote debug message: %.*s", msglen, msg);
+}
+
+static void ssh2_msg_something_unimplemented(Ssh ssh, struct Packet *pktin)
+{
+ struct Packet *pktout;
+ pktout = ssh2_pkt_init(SSH2_MSG_UNIMPLEMENTED);
+ ssh2_pkt_adduint32(pktout, pktin->sequence);
+ /*
+ * UNIMPLEMENTED messages MUST appear in the same order as the
+ * messages they respond to. Hence, never queue them.
+ */
+ ssh2_pkt_send_noqueue(ssh, pktout);
+}
+
+/*
+ * Handle the top-level SSH-2 protocol.
+ */
+static void ssh2_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;
+
+ /*
+ * Any message we actually understand, we set to NULL so that
+ * the coroutines will get it.
+ */
+ 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;
+
+ /*
+ * These special message types we install handlers for.
+ */
+ 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)
+{
+ 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) {
+ do_ssh2_transport(ssh, "timeout", -1, NULL);
+ }
+}
+
+static void ssh2_protocol(Ssh ssh, void *vin, int inlen,
+ struct Packet *pktin)
+{
+ unsigned char *in = (unsigned char *)vin;
+ if (ssh->state == SSH_STATE_CLOSED)
+ return;
+
+ if (pktin) {
+ ssh->incoming_data_size += pktin->encrypted_len;
+ if (!ssh->kex_in_progress &&
+ 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]) {
+ ssh->packet_dispatch[pktin->type](ssh, pktin);
+ 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);
+ }
+}
+
+/*
+ * 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)
+{
+ const char *p;
+ Ssh ssh;
+
+ ssh = snew(struct ssh_tag);
+ ssh->cfg = *cfg; /* STRUCTURE COPY */
+ ssh->version = 0; /* when not ready yet */
+ ssh->s = NULL;
+ ssh->cipher = NULL;
+ ssh->v1_cipher_ctx = NULL;
+ ssh->crcda_ctx = NULL;
+ ssh->cscipher = NULL;
+ ssh->cs_cipher_ctx = NULL;
+ ssh->sccipher = NULL;
+ ssh->sc_cipher_ctx = NULL;
+ ssh->csmac = NULL;
+ ssh->cs_mac_ctx = NULL;
+ ssh->scmac = NULL;
+ ssh->sc_mac_ctx = NULL;
+ ssh->cscomp = NULL;
+ ssh->cs_comp_ctx = NULL;
+ ssh->sccomp = NULL;
+ ssh->sc_comp_ctx = NULL;
+ ssh->kex = NULL;
+ ssh->kex_ctx = NULL;
+ ssh->hostkey = NULL;
+ ssh->exitcode = -1;
+ ssh->close_expected = FALSE;
+ ssh->clean_exit = FALSE;
+ ssh->state = SSH_STATE_PREPACKET;
+ ssh->size_needed = FALSE;
+ ssh->eof_needed = FALSE;
+ ssh->ldisc = NULL;
+ ssh->logctx = NULL;
+ ssh->deferred_send_data = NULL;
+ ssh->deferred_len = 0;
+ ssh->deferred_size = 0;
+ ssh->fallback_cmd = 0;
+ ssh->pkt_kctx = SSH2_PKTCTX_NOKEX;
+ ssh->pkt_actx = SSH2_PKTCTX_NOAUTH;
+ ssh->x11disp = NULL;
+ 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->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_ssh1_login_state = NULL;
+ ssh->do_ssh2_transport_state = NULL;
+ ssh->do_ssh2_authconn_state = NULL;
+ ssh->v_c = NULL;
+ ssh->v_s = NULL;
+ ssh->mainchan = NULL;
+ ssh->throttled_all = 0;
+ ssh->v1_stdout_throttling = 0;
+ ssh->queue = NULL;
+ ssh->queuelen = ssh->queuesize = 0;
+ ssh->queueing = FALSE;
+ ssh->qhead = ssh->qtail = NULL;
+ ssh->deferred_rekey_reason = NULL;
+ bufchain_init(&ssh->queued_incoming_data);
+ ssh->frozen = 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->channels = NULL;
+ ssh->rportfwds = NULL;
+ ssh->portfwds = NULL;
+
+ ssh->send_ok = 0;
+ ssh->editing = 0;
+ ssh->echoing = 0;
+ ssh->conn_throttle_count = 0;
+ ssh->overall_bufsize = 0;
+ ssh->fallback_cmd = 0;
+
+ ssh->protocol = NULL;
+
+ ssh->protocol_initial_phase_done = FALSE;
+
+ 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->kex_in_progress = FALSE;
+
+#ifndef NO_GSSAPI
+ ssh->gsslibs = NULL;
+#endif
+
+ p = connect_to_host(ssh, host, port, realhost, nodelay, keepalive);
+ if (p != NULL)
+ return p;
+
+ random_ref();
+
+ return NULL;
+}
+
+static void ssh_free(void *handle)
+{
+ Ssh ssh = (Ssh) handle;
+ struct ssh_channel *c;
+ struct ssh_rportfwd *pf;
+
+ 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);
+ if (ssh->sc_cipher_ctx)
+ ssh->sccipher->free_context(ssh->sc_cipher_ctx);
+ if (ssh->cs_mac_ctx)
+ ssh->csmac->free_context(ssh->cs_mac_ctx);
+ if (ssh->sc_mac_ctx)
+ ssh->scmac->free_context(ssh->sc_mac_ctx);
+ if (ssh->cs_comp_ctx) {
+ if (ssh->cscomp)
+ ssh->cscomp->compress_cleanup(ssh->cs_comp_ctx);
+ else
+ zlib_compress_cleanup(ssh->cs_comp_ctx);
+ }
+ if (ssh->sc_comp_ctx) {
+ if (ssh->sccomp)
+ ssh->sccomp->decompress_cleanup(ssh->sc_comp_ctx);
+ else
+ zlib_decompress_cleanup(ssh->sc_comp_ctx);
+ }
+ if (ssh->kex_ctx)
+ dh_cleanup(ssh->kex_ctx);
+ sfree(ssh->savedhost);
+
+ while (ssh->queuelen-- > 0)
+ ssh_free_packet(ssh->queue[ssh->queuelen]);
+ sfree(ssh->queue);
+
+ while (ssh->qhead) {
+ struct queued_handler *qh = ssh->qhead;
+ ssh->qhead = qh->next;
+ sfree(ssh->qhead);
+ }
+ 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);
+ break;
+ case CHAN_SOCKDATA:
+ case CHAN_SOCKDATA_DORMANT:
+ if (c->u.pfd.s != NULL)
+ pfd_close(c->u.pfd.s);
+ break;
+ }
+ sfree(c);
+ }
+ freetree234(ssh->channels);
+ ssh->channels = NULL;
+ }
+
+ 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);
+ 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);
+ if (ssh->crcda_ctx) {
+ crcda_free_context(ssh->crcda_ctx);
+ ssh->crcda_ctx = NULL;
+ }
+ if (ssh->s)
+ ssh_do_close(ssh, TRUE);
+ expire_timer_context(ssh);
+ if (ssh->pinger)
+ pinger_free(ssh->pinger);
+ bufchain_clear(&ssh->queued_incoming_data);
+#ifndef NO_GSSAPI
+ if (ssh->gsslibs)
+ ssh_gss_cleanup(ssh->gsslibs);
+#endif
+ sfree(ssh);
+
+ random_unref();
+}
+
+/*
+ * Reconfigure the SSH backend.
+ */
+static void ssh_reconfig(void *handle, Config *cfg)
+{
+ Ssh ssh = (Ssh) handle;
+ char *rekeying = NULL, rekey_mandatory = FALSE;
+ unsigned long old_max_data_size;
+
+ pinger_reconfig(ssh->pinger, &ssh->cfg, cfg);
+ 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) {
+ 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);
+ 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) {
+ 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))) {
+ rekeying = "cipher settings changed";
+ rekey_mandatory = TRUE;
+ }
+
+ ssh->cfg = *cfg; /* STRUCTURE COPY */
+
+ if (rekeying) {
+ if (!ssh->kex_in_progress) {
+ do_ssh2_transport(ssh, rekeying, -1, NULL);
+ } else if (rekey_mandatory) {
+ ssh->deferred_rekey_reason = rekeying;
+ }
+ }
+}
+
+/*
+ * Called to send data down the SSH connection.
+ */
+static int ssh_send(void *handle, char *buf, int len)
+{
+ Ssh ssh = (Ssh) handle;
+
+ if (ssh == NULL || ssh->s == NULL || ssh->protocol == NULL)
+ return 0;
+
+ ssh->protocol(ssh, (unsigned char *)buf, len, 0);
+
+ return ssh_sendbuffer(ssh);
+}
+
+/*
+ * Called to query the current amount of buffered stdin data.
+ */
+static int ssh_sendbuffer(void *handle)
+{
+ Ssh ssh = (Ssh) handle;
+ int override_value;
+
+ if (ssh == NULL || ssh->s == NULL || ssh->protocol == NULL)
+ return 0;
+
+ /*
+ * If the SSH socket itself has backed up, add the total backup
+ * size on that to any individual buffer on the stdin channel.
+ */
+ override_value = 0;
+ if (ssh->throttled_all)
+ override_value = ssh->overall_bufsize;
+
+ if (ssh->version == 1) {
+ return override_value;
+ } else if (ssh->version == 2) {
+ if (!ssh->mainchan || ssh->mainchan->closes > 0)
+ return override_value;
+ else
+ return (override_value +
+ bufchain_size(&ssh->mainchan->v.v2.outbuffer));
+ }
+
+ return 0;
+}
+
+/*
+ * Called to set the size of the window from SSH's POV.
+ */
+static void ssh_size(void *handle, int width, int height)
+{
+ Ssh ssh = (Ssh) handle;
+ struct Packet *pktout;
+
+ ssh->term_width = width;
+ ssh->term_height = height;
+
+ switch (ssh->state) {
+ case SSH_STATE_BEFORE_SIZE:
+ case SSH_STATE_PREPACKET:
+ case SSH_STATE_CLOSED:
+ break; /* do nothing */
+ case SSH_STATE_INTERMED:
+ ssh->size_needed = TRUE; /* buffer for later */
+ break;
+ case SSH_STATE_SESSION:
+ if (!ssh->cfg.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);
+ 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);
+ }
+ }
+ break;
+ }
+}
+
+/*
+ * Return a list of the special codes that make sense in this
+ * protocol.
+ */
+static const struct telnet_special *ssh_get_specials(void *handle)
+{
+ static const struct telnet_special ssh1_ignore_special[] = {
+ {"IGNORE message", TS_NOP}
+ };
+ static const struct telnet_special ssh2_ignore_special[] = {
+ {"IGNORE message", TS_NOP},
+ };
+ static const struct telnet_special ssh2_rekey_special[] = {
+ {"Repeat key exchange", TS_REKEY},
+ };
+ static const struct telnet_special ssh2_session_specials[] = {
+ {NULL, TS_SEP},
+ {"Break", TS_BRK},
+ /* These are the signal names defined by RFC 4254.
+ * They include all the ISO C signals, but are a subset of the POSIX
+ * required signals. */
+ {"SIGINT (Interrupt)", TS_SIGINT},
+ {"SIGTERM (Terminate)", TS_SIGTERM},
+ {"SIGKILL (Kill)", TS_SIGKILL},
+ {"SIGQUIT (Quit)", TS_SIGQUIT},
+ {"SIGHUP (Hangup)", TS_SIGHUP},
+ {"More signals", TS_SUBMENU},
+ {"SIGABRT", TS_SIGABRT}, {"SIGALRM", TS_SIGALRM},
+ {"SIGFPE", TS_SIGFPE}, {"SIGILL", TS_SIGILL},
+ {"SIGPIPE", TS_SIGPIPE}, {"SIGSEGV", TS_SIGSEGV},
+ {"SIGUSR1", TS_SIGUSR1}, {"SIGUSR2", TS_SIGUSR2},
+ {NULL, TS_EXITMENU}
+ };
+ static const struct telnet_special specials_end[] = {
+ {NULL, TS_EXITMENU}
+ };
+ /* XXX review this length for any changes: */
+ static struct telnet_special ssh_specials[lenof(ssh2_ignore_special) +
+ lenof(ssh2_rekey_special) +
+ lenof(ssh2_session_specials) +
+ lenof(specials_end)];
+ Ssh ssh = (Ssh) handle;
+ int i = 0;
+#define ADD_SPECIALS(name) \
+ do { \
+ assert((i + lenof(name)) <= lenof(ssh_specials)); \
+ memcpy(&ssh_specials[i], name, sizeof name); \
+ i += lenof(name); \
+ } while(0)
+
+ if (ssh->version == 1) {
+ /* Don't bother offering IGNORE if we've decided the remote
+ * won't cope with it, since we wouldn't bother sending it if
+ * asked anyway. */
+ if (!(ssh->remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE))
+ ADD_SPECIALS(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))
+ ADD_SPECIALS(ssh2_rekey_special);
+ if (ssh->mainchan)
+ ADD_SPECIALS(ssh2_session_specials);
+ } /* else we're not ready yet */
+
+ if (i) {
+ ADD_SPECIALS(specials_end);
+ return ssh_specials;
+ } else {
+ return NULL;
+ }
+#undef ADD_SPECIALS
+}
+
+/*
+ * Send special codes. TS_EOF is useful for `plink', so you
+ * can send an EOF and collect resulting output (e.g. `plink
+ * hostname sort').
+ */
+static void ssh_special(void *handle, Telnet_Special code)
+{
+ Ssh ssh = (Ssh) handle;
+ struct Packet *pktout;
+
+ if (code == TS_EOF) {
+ if (ssh->state != SSH_STATE_SESSION) {
+ /*
+ * Buffer the EOF in case we are pre-SESSION, so we can
+ * send it as soon as we reach SESSION.
+ */
+ if (code == TS_EOF)
+ ssh->eof_needed = TRUE;
+ 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);
+ 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
+ || ssh->state == SSH_STATE_PREPACKET) return;
+ if (ssh->version == 1) {
+ if (!(ssh->remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE))
+ send_packet(ssh, SSH1_MSG_IGNORE, PKT_STR, "", PKT_END);
+ } else {
+ if (!(ssh->remote_bugs & BUG_CHOKES_ON_SSH2_IGNORE)) {
+ pktout = ssh2_pkt_init(SSH2_MSG_IGNORE);
+ ssh2_pkt_addstring_start(pktout);
+ ssh2_pkt_send_noqueue(ssh, pktout);
+ }
+ }
+ } else if (code == TS_REKEY) {
+ if (!ssh->kex_in_progress && 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);
+ ssh2_pkt_adduint32(pktout, 0); /* default break length */
+ ssh2_pkt_send(ssh, pktout);
+ }
+ } else {
+ /* Is is a POSIX signal? */
+ char *signame = NULL;
+ if (code == TS_SIGABRT) signame = "ABRT";
+ if (code == TS_SIGALRM) signame = "ALRM";
+ if (code == TS_SIGFPE) signame = "FPE";
+ if (code == TS_SIGHUP) signame = "HUP";
+ if (code == TS_SIGILL) signame = "ILL";
+ if (code == TS_SIGINT) signame = "INT";
+ if (code == TS_SIGKILL) signame = "KILL";
+ if (code == TS_SIGPIPE) signame = "PIPE";
+ if (code == TS_SIGQUIT) signame = "QUIT";
+ if (code == TS_SIGSEGV) signame = "SEGV";
+ if (code == TS_SIGTERM) signame = "TERM";
+ if (code == TS_SIGUSR1) signame = "USR1";
+ if (code == TS_SIGUSR2) signame = "USR2";
+ /* 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);
+ ssh2_pkt_addstring(pktout, signame);
+ ssh2_pkt_send(ssh, pktout);
+ logeventf(ssh, "Sent signal SIG%s", signame);
+ }
+ } else {
+ /* Never heard of it. Do nothing */
+ }
+ }
+}
+
+void *new_sock_channel(void *handle, Socket s)
+{
+ 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;
+ add234(ssh->channels, c);
+ return c;
+}
+
+/*
+ * This is called when stdout/stderr (the entity to which
+ * from_backend sends data) manages to clear some backlog.
+ */
+static void ssh_unthrottle(void *handle, int bufsize)
+{
+ Ssh ssh = (Ssh) handle;
+ int buflimit;
+
+ if (ssh->version == 1) {
+ if (ssh->v1_stdout_throttling && bufsize < SSH1_BUFFER_LIMIT) {
+ ssh->v1_stdout_throttling = 0;
+ ssh_throttle_conn(ssh, -1);
+ }
+ } 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)
+ 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);
+ }
+ }
+ }
+}
+
+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);
+
+ if (ssh->version == 1) {
+ send_packet(ssh, SSH1_MSG_PORT_OPEN,
+ PKT_INT, c->localid,
+ PKT_STR, hostname,
+ PKT_INT, port,
+ /* PKT_STR, <org:orgport>, */
+ 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 */
+ 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
+ * convinced the server should be told details like that
+ * about my local network configuration.
+ * The "originator IP address" is syntactically a numeric
+ * IP address, and some servers (e.g., Tectia) get upset
+ * if it doesn't match this syntax.
+ */
+ ssh2_pkt_addstring(pktout, "0.0.0.0");
+ ssh2_pkt_adduint32(pktout, 0);
+ ssh2_pkt_send(ssh, pktout);
+ }
+}
+
+static int ssh_connected(void *handle)
+{
+ Ssh ssh = (Ssh) handle;
+ return ssh->s != NULL;
+}
+
+static int ssh_sendok(void *handle)
+{
+ Ssh ssh = (Ssh) handle;
+ return ssh->send_ok;
+}
+
+static int ssh_ldisc(void *handle, int option)
+{
+ Ssh ssh = (Ssh) handle;
+ if (option == LD_ECHO)
+ return ssh->echoing;
+ if (option == LD_EDIT)
+ return ssh->editing;
+ return FALSE;
+}
+
+static void ssh_provide_ldisc(void *handle, void *ldisc)
+{
+ Ssh ssh = (Ssh) handle;
+ ssh->ldisc = ldisc;
+}
+
+static void ssh_provide_logctx(void *handle, void *logctx)
+{
+ Ssh ssh = (Ssh) handle;
+ ssh->logctx = logctx;
+}
+
+static int ssh_return_exitcode(void *handle)
+{
+ Ssh ssh = (Ssh) handle;
+ if (ssh->s != NULL)
+ return -1;
+ else
+ return (ssh->exitcode >= 0 ? ssh->exitcode : INT_MAX);
+}
+
+/*
+ * cfg_info for SSH is the currently running version of the
+ * protocol. (1 for 1; 2 for 2; 0 for not-decided-yet.)
+ */
+static int ssh_cfg_info(void *handle)
+{
+ Ssh ssh = (Ssh) handle;
+ return ssh->version;
+}
+
+/*
+ * Gross hack: pscp will try to start SFTP but fall back to scp1 if
+ * that fails. This variable is the means by which scp.c can reach
+ * into the SSH code and find out which one it got.
+ */
+extern int ssh_fallback_cmd(void *handle)
+{
+ Ssh ssh = (Ssh) handle;
+ return ssh->fallback_cmd;
+}
+
+Backend ssh_backend = {
+ ssh_init,
+ ssh_free,
+ ssh_reconfig,
+ ssh_send,
+ ssh_sendbuffer,
+ ssh_size,
+ ssh_special,
+ ssh_get_specials,
+ ssh_connected,
+ ssh_return_exitcode,
+ ssh_sendok,
+ ssh_ldisc,
+ ssh_provide_ldisc,
+ ssh_provide_logctx,
+ ssh_unthrottle,
+ ssh_cfg_info,
+ "ssh",
+ PROT_SSH,
+ 22
+};
--- /dev/null
+#include <stdio.h>
+#include <string.h>
+
+#include "puttymem.h"
+#include "tree234.h"
+#include "network.h"
+#include "int64.h"
+#include "misc.h"
+
+struct ssh_channel;
+
+extern void sshfwd_close(struct ssh_channel *c);
+extern int sshfwd_write(struct ssh_channel *c, char *, int);
+extern void sshfwd_unthrottle(struct ssh_channel *c, int bufsize);
+
+/*
+ * Useful thing.
+ */
+#ifndef lenof
+#define lenof(x) ( (sizeof((x))) / (sizeof(*(x))))
+#endif
+
+#define SSH_CIPHER_IDEA 1
+#define SSH_CIPHER_DES 2
+#define SSH_CIPHER_3DES 3
+#define SSH_CIPHER_BLOWFISH 6
+
+#ifdef MSCRYPTOAPI
+#define APIEXTRA 8
+#else
+#define APIEXTRA 0
+#endif
+
+#ifndef BIGNUM_INTERNAL
+typedef void *Bignum;
+#endif
+
+struct RSAKey {
+ int bits;
+ int bytes;
+#ifdef MSCRYPTOAPI
+ unsigned long exponent;
+ unsigned char *modulus;
+#else
+ Bignum modulus;
+ Bignum exponent;
+ Bignum private_exponent;
+ Bignum p;
+ Bignum q;
+ Bignum iqmp;
+#endif
+ char *comment;
+};
+
+struct dss_key {
+ Bignum p, q, g, y, x;
+};
+
+int makekey(unsigned char *data, int len, struct RSAKey *result,
+ unsigned char **keystr, int order);
+int makeprivate(unsigned char *data, int len, struct RSAKey *result);
+int rsaencrypt(unsigned char *data, int length, struct RSAKey *key);
+Bignum rsadecrypt(Bignum input, struct RSAKey *key);
+void rsasign(unsigned char *data, int length, struct RSAKey *key);
+void rsasanitise(struct RSAKey *key);
+int rsastr_len(struct RSAKey *key);
+void rsastr_fmt(char *str, struct RSAKey *key);
+void rsa_fingerprint(char *str, int len, struct RSAKey *key);
+int rsa_verify(struct RSAKey *key);
+unsigned char *rsa_public_blob(struct RSAKey *key, int *len);
+int rsa_public_blob_len(void *data, int maxlen);
+void freersakey(struct RSAKey *key);
+
+#ifndef PUTTY_UINT32_DEFINED
+/* This makes assumptions about the int type. */
+typedef unsigned int uint32;
+#define PUTTY_UINT32_DEFINED
+#endif
+typedef uint32 word32;
+
+unsigned long crc32_compute(const void *s, size_t len);
+unsigned long crc32_update(unsigned long crc_input, const void *s, size_t len);
+
+/* SSH CRC compensation attack detector */
+void *crcda_make_context(void);
+void crcda_free_context(void *handle);
+int detect_attack(void *handle, unsigned char *buf, uint32 len,
+ unsigned char *IV);
+
+/*
+ * SSH2 RSA key exchange functions
+ */
+struct ssh_hash;
+void *ssh_rsakex_newkey(char *data, int len);
+void ssh_rsakex_freekey(void *key);
+int ssh_rsakex_klen(void *key);
+void ssh_rsakex_encrypt(const struct ssh_hash *h, unsigned char *in, int inlen,
+ unsigned char *out, int outlen,
+ void *key);
+
+typedef struct {
+ uint32 h[4];
+} MD5_Core_State;
+
+struct MD5Context {
+#ifdef MSCRYPTOAPI
+ unsigned long hHash;
+#else
+ MD5_Core_State core;
+ unsigned char block[64];
+ int blkused;
+ uint32 lenhi, lenlo;
+#endif
+};
+
+void MD5Init(struct MD5Context *context);
+void MD5Update(struct MD5Context *context, unsigned char const *buf,
+ unsigned len);
+void MD5Final(unsigned char digest[16], struct MD5Context *context);
+void MD5Simple(void const *p, unsigned len, unsigned char output[16]);
+
+void *hmacmd5_make_context(void);
+void hmacmd5_free_context(void *handle);
+void hmacmd5_key(void *handle, void const *key, int len);
+void hmacmd5_do_hmac(void *handle, unsigned char const *blk, int len,
+ unsigned char *hmac);
+
+typedef struct {
+ uint32 h[5];
+ 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_Final(SHA_State * s, unsigned char *output);
+void SHA_Simple(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];
+ unsigned char block[64];
+ int blkused;
+ uint32 lenhi, lenlo;
+} SHA256_State;
+void SHA256_Init(SHA256_State * s);
+void SHA256_Bytes(SHA256_State * s, const void *p, int len);
+void SHA256_Final(SHA256_State * s, unsigned char *output);
+void SHA256_Simple(const void *p, int len, unsigned char *output);
+
+typedef struct {
+ uint64 h[8];
+ unsigned char block[128];
+ int blkused;
+ uint32 len[4];
+} SHA512_State;
+void SHA512_Init(SHA512_State * s);
+void SHA512_Bytes(SHA512_State * s, const void *p, int len);
+void SHA512_Final(SHA512_State * s, unsigned char *output);
+void SHA512_Simple(const void *p, int len, unsigned char *output);
+
+struct ssh_cipher {
+ void *(*make_context)(void);
+ void (*free_context)(void *);
+ void (*sesskey) (void *, unsigned char *key); /* for SSH-1 */
+ void (*encrypt) (void *, unsigned char *blk, int len);
+ void (*decrypt) (void *, unsigned char *blk, int len);
+ int blksize;
+ char *text_name;
+};
+
+struct ssh2_cipher {
+ void *(*make_context)(void);
+ void (*free_context)(void *);
+ void (*setiv) (void *, unsigned char *key); /* for SSH-2 */
+ void (*setkey) (void *, unsigned char *key);/* for SSH-2 */
+ void (*encrypt) (void *, unsigned char *blk, int len);
+ void (*decrypt) (void *, unsigned char *blk, int len);
+ char *name;
+ int blksize;
+ int keylen;
+ unsigned int flags;
+#define SSH_CIPHER_IS_CBC 1
+ char *text_name;
+};
+
+struct ssh2_ciphers {
+ int nciphers;
+ const struct ssh2_cipher *const *list;
+};
+
+struct ssh_mac {
+ void *(*make_context)(void);
+ void (*free_context)(void *);
+ void (*setkey) (void *, unsigned char *key);
+ /* whole-packet operations */
+ void (*generate) (void *, unsigned char *blk, int len, unsigned long seq);
+ int (*verify) (void *, unsigned char *blk, int len, unsigned long seq);
+ /* partial-packet operations */
+ void (*start) (void *);
+ void (*bytes) (void *, unsigned char const *, int);
+ void (*genresult) (void *, unsigned char *);
+ int (*verresult) (void *, unsigned char const *);
+ char *name;
+ int len;
+ char *text_name;
+};
+
+struct ssh_hash {
+ void *(*init)(void); /* also allocates context */
+ void (*bytes)(void *, void *, int);
+ void (*final)(void *, unsigned char *); /* also frees context */
+ int hlen; /* output length in bytes */
+ char *text_name;
+};
+
+struct ssh_kex {
+ char *name, *groupname;
+ enum { KEXTYPE_DH, KEXTYPE_RSA } main_type;
+ /* For DH */
+ const unsigned char *pdata, *gdata; /* NULL means group exchange */
+ int plen, glen;
+ const struct ssh_hash *hash;
+};
+
+struct ssh_kexes {
+ int nkexes;
+ const struct ssh_kex *const *list;
+};
+
+struct ssh_signkey {
+ void *(*newkey) (char *data, int len);
+ void (*freekey) (void *key);
+ char *(*fmtkey) (void *key);
+ unsigned char *(*public_blob) (void *key, int *len);
+ unsigned char *(*private_blob) (void *key, int *len);
+ void *(*createkey) (unsigned char *pub_blob, int pub_len,
+ unsigned char *priv_blob, int priv_len);
+ void *(*openssh_createkey) (unsigned char **blob, int *len);
+ int (*openssh_fmtkey) (void *key, unsigned char *blob, int len);
+ int (*pubkey_bits) (void *blob, int len);
+ char *(*fingerprint) (void *key);
+ int (*verifysig) (void *key, char *sig, int siglen,
+ char *data, int datalen);
+ unsigned char *(*sign) (void *key, char *data, int datalen,
+ int *siglen);
+ char *name;
+ char *keytype; /* for host key cache */
+};
+
+struct ssh_compress {
+ char *name;
+ /* For zlib@openssh.com: if non-NULL, this name will be considered once
+ * userauth has completed successfully. */
+ char *delayed_name;
+ void *(*compress_init) (void);
+ void (*compress_cleanup) (void *);
+ int (*compress) (void *, unsigned char *block, int len,
+ unsigned char **outblock, int *outlen);
+ void *(*decompress_init) (void);
+ void (*decompress_cleanup) (void *);
+ int (*decompress) (void *, unsigned char *block, int len,
+ unsigned char **outblock, int *outlen);
+ int (*disable_compression) (void *);
+ char *text_name;
+};
+
+struct ssh2_userkey {
+ const struct ssh_signkey *alg; /* the key algorithm */
+ void *data; /* the key data */
+ char *comment; /* the key comment */
+};
+
+/* The maximum length of any hash algorithm used in kex. (bytes) */
+#define SSH2_KEX_MAX_HASH_LEN (32) /* SHA-256 */
+
+extern const struct ssh_cipher ssh_3des;
+extern const struct ssh_cipher ssh_des;
+extern const struct ssh_cipher ssh_blowfish_ssh1;
+extern const struct ssh2_ciphers ssh2_3des;
+extern const struct ssh2_ciphers ssh2_des;
+extern const struct ssh2_ciphers ssh2_aes;
+extern const struct ssh2_ciphers ssh2_blowfish;
+extern const struct ssh2_ciphers ssh2_arcfour;
+extern const struct ssh_hash ssh_sha1;
+extern const struct ssh_hash ssh_sha256;
+extern const struct ssh_kexes ssh_diffiehellman_group1;
+extern const struct ssh_kexes ssh_diffiehellman_group14;
+extern const struct ssh_kexes ssh_diffiehellman_gex;
+extern const struct ssh_kexes ssh_rsa_kex;
+extern const struct ssh_signkey ssh_dss;
+extern const struct ssh_signkey ssh_rsa;
+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;
+
+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);
+void aes256_key(void *handle, unsigned char *key);
+void aes_iv(void *handle, unsigned char *iv);
+void aes_ssh2_encrypt_blk(void *handle, unsigned char *blk, int len);
+void aes_ssh2_decrypt_blk(void *handle, unsigned char *blk, int len);
+
+/*
+ * PuTTY version number formatted as an SSH version string.
+ */
+extern char sshver[];
+
+/*
+ * Gross hack: pscp will try to start SFTP but fall back to scp1 if
+ * that fails. This variable is the means by which scp.c can reach
+ * into the SSH code and find out which one it got.
+ */
+extern int ssh_fallback_cmd(void *handle);
+
+#ifndef MSCRYPTOAPI
+void SHATransform(word32 * digest, word32 * data);
+#endif
+
+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 *);
+
+/* Allocate and register a new channel for port forwarding */
+void *new_sock_channel(void *handle, Socket s);
+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);
+/* 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);
+
+/* Exports from x11fwd.c */
+enum {
+ X11_TRANS_IPV4 = 0, X11_TRANS_IPV6 = 6, X11_TRANS_UNIX = 256
+};
+struct X11Display {
+ /* Broken-down components of the display name itself */
+ int unixdomain;
+ char *hostname;
+ int displaynum;
+ int screennum;
+ /* OSX sometimes replaces all the above with a full Unix-socket pathname */
+ char *unixsocketpath;
+
+ /* PuTTY networking SockAddr to connect to the display, and associated
+ * 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;
+
+ /*
+ * Used inside x11fwd.c to remember recently seen
+ * XDM-AUTHORIZATION-1 strings, to avoid replay attacks.
+ */
+ tree234 *xdmseen;
+};
+/*
+ * 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 *);
+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);
+char *x11_display(const char *display);
+/* Platform-dependent X11 functions */
+extern void platform_get_x11_auth(struct X11Display *display,
+ const Config *);
+ /* 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 */
+char *platform_get_x_display(void);
+ /* allocated local X display string, if any */
+/* Callbacks in x11.c usable _by_ platform X11 functions */
+/*
+ * This function does the job of platform_get_x11_auth, provided
+ * it is told where to find a normally formatted .Xauthority file:
+ * it opens that file, parses it to find an auth record which
+ * matches the display details in "display", and fills in the
+ * localauth fields.
+ *
+ * It is expected that most implementations of
+ * platform_get_x11_auth() will work by finding their system's
+ * .Xauthority file, adjusting the display details if necessary
+ * 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);
+
+Bignum copybn(Bignum b);
+Bignum bn_power_2(int n);
+void bn_restore_invariant(Bignum b);
+Bignum bignum_from_long(unsigned long n);
+void freebn(Bignum b);
+Bignum modpow(Bignum base, Bignum exp, Bignum mod);
+Bignum modmul(Bignum a, Bignum b, Bignum mod);
+void decbn(Bignum n);
+extern Bignum Zero, One;
+Bignum bignum_from_bytes(const unsigned char *data, int nbytes);
+int ssh1_read_bignum(const unsigned char *data, int len, Bignum * result);
+int bignum_bitcount(Bignum bn);
+int ssh1_bignum_length(Bignum bn);
+int ssh2_bignum_length(Bignum bn);
+int bignum_byte(Bignum bn, int i);
+int bignum_bit(Bignum bn, int i);
+void bignum_set_bit(Bignum bn, int i, int value);
+int ssh1_write_bignum(void *data, Bignum bn);
+Bignum biggcd(Bignum a, Bignum b);
+unsigned short bignum_mod_short(Bignum number, unsigned short modulus);
+Bignum bignum_add_long(Bignum number, unsigned long addend);
+Bignum bigadd(Bignum a, Bignum b);
+Bignum bigsub(Bignum a, Bignum b);
+Bignum bigmul(Bignum a, Bignum b);
+Bignum bigmuladd(Bignum a, Bignum b, Bignum addend);
+Bignum bigdiv(Bignum a, Bignum b);
+Bignum bigmod(Bignum a, Bignum b);
+Bignum modinv(Bignum number, Bignum modulus);
+Bignum bignum_bitmask(Bignum number);
+Bignum bignum_rshift(Bignum number, int shift);
+int bignum_cmp(Bignum a, Bignum b);
+char *bignum_decimal(Bignum x);
+
+#ifdef DEBUG
+void diagbn(char *prefix, Bignum md);
+#endif
+
+void *dh_setup_group(const struct ssh_kex *kex);
+void *dh_setup_gex(Bignum pval, Bignum gval);
+void dh_cleanup(void *);
+Bignum dh_create_e(void *, int nbits);
+Bignum dh_find_K(void *, Bignum f);
+
+int loadrsakey(const Filename *filename, struct RSAKey *key,
+ char *passphrase, const char **errorstr);
+int rsakey_encrypted(const Filename *filename, char **comment);
+int rsakey_pubblob(const Filename *filename, void **blob, int *bloblen,
+ char **commentptr, const char **errorstr);
+
+int saversakey(const Filename *filename, struct RSAKey *key, char *passphrase);
+
+extern int base64_decode_atom(char *atom, unsigned char *out);
+extern int base64_lines(int datalen);
+extern void base64_encode_atom(unsigned char *data, int n, char *out);
+extern void base64_encode(FILE *fp, unsigned char *data, int datalen, int cpl);
+
+/* ssh2_load_userkey can return this as an error */
+extern struct ssh2_userkey ssh2_wrong_passphrase;
+#define SSH2_WRONG_PASSPHRASE (&ssh2_wrong_passphrase)
+
+int ssh2_userkey_encrypted(const Filename *filename, char **comment);
+struct ssh2_userkey *ssh2_load_userkey(const Filename *filename,
+ char *passphrase, const char **errorstr);
+unsigned char *ssh2_userkey_loadpub(const Filename *filename, char **algorithm,
+ int *pub_blob_len, char **commentptr,
+ const char **errorstr);
+int ssh2_save_userkey(const Filename *filename, struct ssh2_userkey *key,
+ char *passphrase);
+const struct ssh_signkey *find_pubkey_alg(const char *name);
+
+enum {
+ SSH_KEYTYPE_UNOPENABLE,
+ SSH_KEYTYPE_UNKNOWN,
+ SSH_KEYTYPE_SSH1, SSH_KEYTYPE_SSH2,
+ SSH_KEYTYPE_OPENSSH, SSH_KEYTYPE_SSHCOM
+};
+int key_type(const Filename *filename);
+char *key_type_to_str(int type);
+
+int import_possible(int type);
+int import_target_type(int type);
+int import_encrypted(const Filename *filename, int type, char **comment);
+int import_ssh1(const Filename *filename, int type,
+ struct RSAKey *key, char *passphrase, const char **errmsg_p);
+struct ssh2_userkey *import_ssh2(const Filename *filename, int type,
+ char *passphrase, const char **errmsg_p);
+int export_ssh1(const Filename *filename, int type,
+ struct RSAKey *key, char *passphrase);
+int export_ssh2(const Filename *filename, int type,
+ struct ssh2_userkey *key, char *passphrase);
+
+void des3_decrypt_pubkey(unsigned char *key, unsigned char *blk, int len);
+void des3_encrypt_pubkey(unsigned char *key, unsigned char *blk, int len);
+void des3_decrypt_pubkey_ossh(unsigned char *key, unsigned char *iv,
+ unsigned char *blk, int len);
+void des3_encrypt_pubkey_ossh(unsigned char *key, unsigned char *iv,
+ unsigned char *blk, int len);
+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);
+
+/*
+ * For progress updates in the key generation utility.
+ */
+#define PROGFN_INITIALISE 1
+#define PROGFN_LIN_PHASE 2
+#define PROGFN_EXP_PHASE 3
+#define PROGFN_PHASE_EXTENT 4
+#define PROGFN_READY 5
+#define PROGFN_PROGRESS 6
+typedef void (*progfn_t) (void *param, int action, int phase, int progress);
+
+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);
+
+
+/*
+ * zlib compression.
+ */
+void *zlib_compress_init(void);
+void zlib_compress_cleanup(void *);
+void *zlib_decompress_init(void);
+void zlib_decompress_cleanup(void *);
+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);
+
+/*
+ * SSH-1 agent messages.
+ */
+#define SSH1_AGENTC_REQUEST_RSA_IDENTITIES 1
+#define SSH1_AGENT_RSA_IDENTITIES_ANSWER 2
+#define SSH1_AGENTC_RSA_CHALLENGE 3
+#define SSH1_AGENT_RSA_RESPONSE 4
+#define SSH1_AGENTC_ADD_RSA_IDENTITY 7
+#define SSH1_AGENTC_REMOVE_RSA_IDENTITY 8
+#define SSH1_AGENTC_REMOVE_ALL_RSA_IDENTITIES 9 /* openssh private? */
+
+/*
+ * Messages common to SSH-1 and OpenSSH's SSH-2.
+ */
+#define SSH_AGENT_FAILURE 5
+#define SSH_AGENT_SUCCESS 6
+
+/*
+ * OpenSSH's SSH-2 agent messages.
+ */
+#define SSH2_AGENTC_REQUEST_IDENTITIES 11
+#define SSH2_AGENT_IDENTITIES_ANSWER 12
+#define SSH2_AGENTC_SIGN_REQUEST 13
+#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
+
+/*
+ * Need this to warn about support for the original SSH-2 keyfile
+ * format.
+ */
+void old_keyfile_warning(void);
--- /dev/null
+/*
+ * aes.c - implementation of AES / Rijndael
+ *
+ * AES is a flexible algorithm as regards endianness: it has no
+ * inherent preference as to which way round you should form words
+ * from the input byte stream. It talks endlessly of four-byte
+ * _vectors_, but never of 32-bit _words_ - there's no 32-bit
+ * addition at all, which would force an endianness by means of
+ * which way the carries went. So it would be possible to write a
+ * working AES that read words big-endian, and another working one
+ * that read them little-endian, just by computing a different set
+ * of tables - with no speed drop.
+ *
+ * It's therefore tempting to do just that, and remove the overhead
+ * of GET_32BIT_MSB_FIRST() et al, allowing every system to use its
+ * own endianness-native code; but I decided not to, partly for
+ * ease of testing, and mostly because I like the flexibility that
+ * allows you to encrypt a non-word-aligned block of memory (which
+ * many systems would stop being able to do if I went the
+ * endianness-dependent route).
+ *
+ * This implementation reads and stores words big-endian, but
+ * that's a minor implementation detail. By flipping the endianness
+ * of everything in the E0..E3, D0..D3 tables, and substituting
+ * GET_32BIT_LSB_FIRST for GET_32BIT_MSB_FIRST, I could create an
+ * implementation that worked internally little-endian and gave the
+ * same answers at the same speed.
+ */
+
+#include <assert.h>
+#include <stdlib.h>
+
+#include "ssh.h"
+
+#define MAX_NR 14 /* max no of rounds */
+#define MAX_NK 8 /* max no of words in input key */
+#define MAX_NB 8 /* max no of words in cipher blk */
+
+#define mulby2(x) ( ((x&0x7F) << 1) ^ (x & 0x80 ? 0x1B : 0) )
+
+typedef struct AESContext AESContext;
+
+struct AESContext {
+ word32 keysched[(MAX_NR + 1) * MAX_NB];
+ word32 invkeysched[(MAX_NR + 1) * MAX_NB];
+ void (*encrypt) (AESContext * ctx, word32 * block);
+ void (*decrypt) (AESContext * ctx, word32 * block);
+ word32 iv[MAX_NB];
+ int Nb, Nr;
+};
+
+static const unsigned char Sbox[256] = {
+ 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5,
+ 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,
+ 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0,
+ 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
+ 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc,
+ 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
+ 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a,
+ 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,
+ 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0,
+ 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,
+ 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b,
+ 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
+ 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85,
+ 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,
+ 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5,
+ 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,
+ 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17,
+ 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
+ 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88,
+ 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,
+ 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c,
+ 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
+ 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9,
+ 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
+ 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6,
+ 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a,
+ 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e,
+ 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,
+ 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94,
+ 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
+ 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68,
+ 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16
+};
+
+static const unsigned char Sboxinv[256] = {
+ 0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38,
+ 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb,
+ 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87,
+ 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb,
+ 0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d,
+ 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e,
+ 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2,
+ 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25,
+ 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16,
+ 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92,
+ 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda,
+ 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84,
+ 0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a,
+ 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06,
+ 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02,
+ 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b,
+ 0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea,
+ 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73,
+ 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85,
+ 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e,
+ 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89,
+ 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b,
+ 0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20,
+ 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4,
+ 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31,
+ 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f,
+ 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d,
+ 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef,
+ 0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0,
+ 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61,
+ 0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26,
+ 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d
+};
+
+static const word32 E0[256] = {
+ 0xc66363a5, 0xf87c7c84, 0xee777799, 0xf67b7b8d,
+ 0xfff2f20d, 0xd66b6bbd, 0xde6f6fb1, 0x91c5c554,
+ 0x60303050, 0x02010103, 0xce6767a9, 0x562b2b7d,
+ 0xe7fefe19, 0xb5d7d762, 0x4dababe6, 0xec76769a,
+ 0x8fcaca45, 0x1f82829d, 0x89c9c940, 0xfa7d7d87,
+ 0xeffafa15, 0xb25959eb, 0x8e4747c9, 0xfbf0f00b,
+ 0x41adadec, 0xb3d4d467, 0x5fa2a2fd, 0x45afafea,
+ 0x239c9cbf, 0x53a4a4f7, 0xe4727296, 0x9bc0c05b,
+ 0x75b7b7c2, 0xe1fdfd1c, 0x3d9393ae, 0x4c26266a,
+ 0x6c36365a, 0x7e3f3f41, 0xf5f7f702, 0x83cccc4f,
+ 0x6834345c, 0x51a5a5f4, 0xd1e5e534, 0xf9f1f108,
+ 0xe2717193, 0xabd8d873, 0x62313153, 0x2a15153f,
+ 0x0804040c, 0x95c7c752, 0x46232365, 0x9dc3c35e,
+ 0x30181828, 0x379696a1, 0x0a05050f, 0x2f9a9ab5,
+ 0x0e070709, 0x24121236, 0x1b80809b, 0xdfe2e23d,
+ 0xcdebeb26, 0x4e272769, 0x7fb2b2cd, 0xea75759f,
+ 0x1209091b, 0x1d83839e, 0x582c2c74, 0x341a1a2e,
+ 0x361b1b2d, 0xdc6e6eb2, 0xb45a5aee, 0x5ba0a0fb,
+ 0xa45252f6, 0x763b3b4d, 0xb7d6d661, 0x7db3b3ce,
+ 0x5229297b, 0xdde3e33e, 0x5e2f2f71, 0x13848497,
+ 0xa65353f5, 0xb9d1d168, 0x00000000, 0xc1eded2c,
+ 0x40202060, 0xe3fcfc1f, 0x79b1b1c8, 0xb65b5bed,
+ 0xd46a6abe, 0x8dcbcb46, 0x67bebed9, 0x7239394b,
+ 0x944a4ade, 0x984c4cd4, 0xb05858e8, 0x85cfcf4a,
+ 0xbbd0d06b, 0xc5efef2a, 0x4faaaae5, 0xedfbfb16,
+ 0x864343c5, 0x9a4d4dd7, 0x66333355, 0x11858594,
+ 0x8a4545cf, 0xe9f9f910, 0x04020206, 0xfe7f7f81,
+ 0xa05050f0, 0x783c3c44, 0x259f9fba, 0x4ba8a8e3,
+ 0xa25151f3, 0x5da3a3fe, 0x804040c0, 0x058f8f8a,
+ 0x3f9292ad, 0x219d9dbc, 0x70383848, 0xf1f5f504,
+ 0x63bcbcdf, 0x77b6b6c1, 0xafdada75, 0x42212163,
+ 0x20101030, 0xe5ffff1a, 0xfdf3f30e, 0xbfd2d26d,
+ 0x81cdcd4c, 0x180c0c14, 0x26131335, 0xc3ecec2f,
+ 0xbe5f5fe1, 0x359797a2, 0x884444cc, 0x2e171739,
+ 0x93c4c457, 0x55a7a7f2, 0xfc7e7e82, 0x7a3d3d47,
+ 0xc86464ac, 0xba5d5de7, 0x3219192b, 0xe6737395,
+ 0xc06060a0, 0x19818198, 0x9e4f4fd1, 0xa3dcdc7f,
+ 0x44222266, 0x542a2a7e, 0x3b9090ab, 0x0b888883,
+ 0x8c4646ca, 0xc7eeee29, 0x6bb8b8d3, 0x2814143c,
+ 0xa7dede79, 0xbc5e5ee2, 0x160b0b1d, 0xaddbdb76,
+ 0xdbe0e03b, 0x64323256, 0x743a3a4e, 0x140a0a1e,
+ 0x924949db, 0x0c06060a, 0x4824246c, 0xb85c5ce4,
+ 0x9fc2c25d, 0xbdd3d36e, 0x43acacef, 0xc46262a6,
+ 0x399191a8, 0x319595a4, 0xd3e4e437, 0xf279798b,
+ 0xd5e7e732, 0x8bc8c843, 0x6e373759, 0xda6d6db7,
+ 0x018d8d8c, 0xb1d5d564, 0x9c4e4ed2, 0x49a9a9e0,
+ 0xd86c6cb4, 0xac5656fa, 0xf3f4f407, 0xcfeaea25,
+ 0xca6565af, 0xf47a7a8e, 0x47aeaee9, 0x10080818,
+ 0x6fbabad5, 0xf0787888, 0x4a25256f, 0x5c2e2e72,
+ 0x381c1c24, 0x57a6a6f1, 0x73b4b4c7, 0x97c6c651,
+ 0xcbe8e823, 0xa1dddd7c, 0xe874749c, 0x3e1f1f21,
+ 0x964b4bdd, 0x61bdbddc, 0x0d8b8b86, 0x0f8a8a85,
+ 0xe0707090, 0x7c3e3e42, 0x71b5b5c4, 0xcc6666aa,
+ 0x904848d8, 0x06030305, 0xf7f6f601, 0x1c0e0e12,
+ 0xc26161a3, 0x6a35355f, 0xae5757f9, 0x69b9b9d0,
+ 0x17868691, 0x99c1c158, 0x3a1d1d27, 0x279e9eb9,
+ 0xd9e1e138, 0xebf8f813, 0x2b9898b3, 0x22111133,
+ 0xd26969bb, 0xa9d9d970, 0x078e8e89, 0x339494a7,
+ 0x2d9b9bb6, 0x3c1e1e22, 0x15878792, 0xc9e9e920,
+ 0x87cece49, 0xaa5555ff, 0x50282878, 0xa5dfdf7a,
+ 0x038c8c8f, 0x59a1a1f8, 0x09898980, 0x1a0d0d17,
+ 0x65bfbfda, 0xd7e6e631, 0x844242c6, 0xd06868b8,
+ 0x824141c3, 0x299999b0, 0x5a2d2d77, 0x1e0f0f11,
+ 0x7bb0b0cb, 0xa85454fc, 0x6dbbbbd6, 0x2c16163a,
+};
+static const word32 E1[256] = {
+ 0xa5c66363, 0x84f87c7c, 0x99ee7777, 0x8df67b7b,
+ 0x0dfff2f2, 0xbdd66b6b, 0xb1de6f6f, 0x5491c5c5,
+ 0x50603030, 0x03020101, 0xa9ce6767, 0x7d562b2b,
+ 0x19e7fefe, 0x62b5d7d7, 0xe64dabab, 0x9aec7676,
+ 0x458fcaca, 0x9d1f8282, 0x4089c9c9, 0x87fa7d7d,
+ 0x15effafa, 0xebb25959, 0xc98e4747, 0x0bfbf0f0,
+ 0xec41adad, 0x67b3d4d4, 0xfd5fa2a2, 0xea45afaf,
+ 0xbf239c9c, 0xf753a4a4, 0x96e47272, 0x5b9bc0c0,
+ 0xc275b7b7, 0x1ce1fdfd, 0xae3d9393, 0x6a4c2626,
+ 0x5a6c3636, 0x417e3f3f, 0x02f5f7f7, 0x4f83cccc,
+ 0x5c683434, 0xf451a5a5, 0x34d1e5e5, 0x08f9f1f1,
+ 0x93e27171, 0x73abd8d8, 0x53623131, 0x3f2a1515,
+ 0x0c080404, 0x5295c7c7, 0x65462323, 0x5e9dc3c3,
+ 0x28301818, 0xa1379696, 0x0f0a0505, 0xb52f9a9a,
+ 0x090e0707, 0x36241212, 0x9b1b8080, 0x3ddfe2e2,
+ 0x26cdebeb, 0x694e2727, 0xcd7fb2b2, 0x9fea7575,
+ 0x1b120909, 0x9e1d8383, 0x74582c2c, 0x2e341a1a,
+ 0x2d361b1b, 0xb2dc6e6e, 0xeeb45a5a, 0xfb5ba0a0,
+ 0xf6a45252, 0x4d763b3b, 0x61b7d6d6, 0xce7db3b3,
+ 0x7b522929, 0x3edde3e3, 0x715e2f2f, 0x97138484,
+ 0xf5a65353, 0x68b9d1d1, 0x00000000, 0x2cc1eded,
+ 0x60402020, 0x1fe3fcfc, 0xc879b1b1, 0xedb65b5b,
+ 0xbed46a6a, 0x468dcbcb, 0xd967bebe, 0x4b723939,
+ 0xde944a4a, 0xd4984c4c, 0xe8b05858, 0x4a85cfcf,
+ 0x6bbbd0d0, 0x2ac5efef, 0xe54faaaa, 0x16edfbfb,
+ 0xc5864343, 0xd79a4d4d, 0x55663333, 0x94118585,
+ 0xcf8a4545, 0x10e9f9f9, 0x06040202, 0x81fe7f7f,
+ 0xf0a05050, 0x44783c3c, 0xba259f9f, 0xe34ba8a8,
+ 0xf3a25151, 0xfe5da3a3, 0xc0804040, 0x8a058f8f,
+ 0xad3f9292, 0xbc219d9d, 0x48703838, 0x04f1f5f5,
+ 0xdf63bcbc, 0xc177b6b6, 0x75afdada, 0x63422121,
+ 0x30201010, 0x1ae5ffff, 0x0efdf3f3, 0x6dbfd2d2,
+ 0x4c81cdcd, 0x14180c0c, 0x35261313, 0x2fc3ecec,
+ 0xe1be5f5f, 0xa2359797, 0xcc884444, 0x392e1717,
+ 0x5793c4c4, 0xf255a7a7, 0x82fc7e7e, 0x477a3d3d,
+ 0xacc86464, 0xe7ba5d5d, 0x2b321919, 0x95e67373,
+ 0xa0c06060, 0x98198181, 0xd19e4f4f, 0x7fa3dcdc,
+ 0x66442222, 0x7e542a2a, 0xab3b9090, 0x830b8888,
+ 0xca8c4646, 0x29c7eeee, 0xd36bb8b8, 0x3c281414,
+ 0x79a7dede, 0xe2bc5e5e, 0x1d160b0b, 0x76addbdb,
+ 0x3bdbe0e0, 0x56643232, 0x4e743a3a, 0x1e140a0a,
+ 0xdb924949, 0x0a0c0606, 0x6c482424, 0xe4b85c5c,
+ 0x5d9fc2c2, 0x6ebdd3d3, 0xef43acac, 0xa6c46262,
+ 0xa8399191, 0xa4319595, 0x37d3e4e4, 0x8bf27979,
+ 0x32d5e7e7, 0x438bc8c8, 0x596e3737, 0xb7da6d6d,
+ 0x8c018d8d, 0x64b1d5d5, 0xd29c4e4e, 0xe049a9a9,
+ 0xb4d86c6c, 0xfaac5656, 0x07f3f4f4, 0x25cfeaea,
+ 0xafca6565, 0x8ef47a7a, 0xe947aeae, 0x18100808,
+ 0xd56fbaba, 0x88f07878, 0x6f4a2525, 0x725c2e2e,
+ 0x24381c1c, 0xf157a6a6, 0xc773b4b4, 0x5197c6c6,
+ 0x23cbe8e8, 0x7ca1dddd, 0x9ce87474, 0x213e1f1f,
+ 0xdd964b4b, 0xdc61bdbd, 0x860d8b8b, 0x850f8a8a,
+ 0x90e07070, 0x427c3e3e, 0xc471b5b5, 0xaacc6666,
+ 0xd8904848, 0x05060303, 0x01f7f6f6, 0x121c0e0e,
+ 0xa3c26161, 0x5f6a3535, 0xf9ae5757, 0xd069b9b9,
+ 0x91178686, 0x5899c1c1, 0x273a1d1d, 0xb9279e9e,
+ 0x38d9e1e1, 0x13ebf8f8, 0xb32b9898, 0x33221111,
+ 0xbbd26969, 0x70a9d9d9, 0x89078e8e, 0xa7339494,
+ 0xb62d9b9b, 0x223c1e1e, 0x92158787, 0x20c9e9e9,
+ 0x4987cece, 0xffaa5555, 0x78502828, 0x7aa5dfdf,
+ 0x8f038c8c, 0xf859a1a1, 0x80098989, 0x171a0d0d,
+ 0xda65bfbf, 0x31d7e6e6, 0xc6844242, 0xb8d06868,
+ 0xc3824141, 0xb0299999, 0x775a2d2d, 0x111e0f0f,
+ 0xcb7bb0b0, 0xfca85454, 0xd66dbbbb, 0x3a2c1616,
+};
+static const word32 E2[256] = {
+ 0x63a5c663, 0x7c84f87c, 0x7799ee77, 0x7b8df67b,
+ 0xf20dfff2, 0x6bbdd66b, 0x6fb1de6f, 0xc55491c5,
+ 0x30506030, 0x01030201, 0x67a9ce67, 0x2b7d562b,
+ 0xfe19e7fe, 0xd762b5d7, 0xabe64dab, 0x769aec76,
+ 0xca458fca, 0x829d1f82, 0xc94089c9, 0x7d87fa7d,
+ 0xfa15effa, 0x59ebb259, 0x47c98e47, 0xf00bfbf0,
+ 0xadec41ad, 0xd467b3d4, 0xa2fd5fa2, 0xafea45af,
+ 0x9cbf239c, 0xa4f753a4, 0x7296e472, 0xc05b9bc0,
+ 0xb7c275b7, 0xfd1ce1fd, 0x93ae3d93, 0x266a4c26,
+ 0x365a6c36, 0x3f417e3f, 0xf702f5f7, 0xcc4f83cc,
+ 0x345c6834, 0xa5f451a5, 0xe534d1e5, 0xf108f9f1,
+ 0x7193e271, 0xd873abd8, 0x31536231, 0x153f2a15,
+ 0x040c0804, 0xc75295c7, 0x23654623, 0xc35e9dc3,
+ 0x18283018, 0x96a13796, 0x050f0a05, 0x9ab52f9a,
+ 0x07090e07, 0x12362412, 0x809b1b80, 0xe23ddfe2,
+ 0xeb26cdeb, 0x27694e27, 0xb2cd7fb2, 0x759fea75,
+ 0x091b1209, 0x839e1d83, 0x2c74582c, 0x1a2e341a,
+ 0x1b2d361b, 0x6eb2dc6e, 0x5aeeb45a, 0xa0fb5ba0,
+ 0x52f6a452, 0x3b4d763b, 0xd661b7d6, 0xb3ce7db3,
+ 0x297b5229, 0xe33edde3, 0x2f715e2f, 0x84971384,
+ 0x53f5a653, 0xd168b9d1, 0x00000000, 0xed2cc1ed,
+ 0x20604020, 0xfc1fe3fc, 0xb1c879b1, 0x5bedb65b,
+ 0x6abed46a, 0xcb468dcb, 0xbed967be, 0x394b7239,
+ 0x4ade944a, 0x4cd4984c, 0x58e8b058, 0xcf4a85cf,
+ 0xd06bbbd0, 0xef2ac5ef, 0xaae54faa, 0xfb16edfb,
+ 0x43c58643, 0x4dd79a4d, 0x33556633, 0x85941185,
+ 0x45cf8a45, 0xf910e9f9, 0x02060402, 0x7f81fe7f,
+ 0x50f0a050, 0x3c44783c, 0x9fba259f, 0xa8e34ba8,
+ 0x51f3a251, 0xa3fe5da3, 0x40c08040, 0x8f8a058f,
+ 0x92ad3f92, 0x9dbc219d, 0x38487038, 0xf504f1f5,
+ 0xbcdf63bc, 0xb6c177b6, 0xda75afda, 0x21634221,
+ 0x10302010, 0xff1ae5ff, 0xf30efdf3, 0xd26dbfd2,
+ 0xcd4c81cd, 0x0c14180c, 0x13352613, 0xec2fc3ec,
+ 0x5fe1be5f, 0x97a23597, 0x44cc8844, 0x17392e17,
+ 0xc45793c4, 0xa7f255a7, 0x7e82fc7e, 0x3d477a3d,
+ 0x64acc864, 0x5de7ba5d, 0x192b3219, 0x7395e673,
+ 0x60a0c060, 0x81981981, 0x4fd19e4f, 0xdc7fa3dc,
+ 0x22664422, 0x2a7e542a, 0x90ab3b90, 0x88830b88,
+ 0x46ca8c46, 0xee29c7ee, 0xb8d36bb8, 0x143c2814,
+ 0xde79a7de, 0x5ee2bc5e, 0x0b1d160b, 0xdb76addb,
+ 0xe03bdbe0, 0x32566432, 0x3a4e743a, 0x0a1e140a,
+ 0x49db9249, 0x060a0c06, 0x246c4824, 0x5ce4b85c,
+ 0xc25d9fc2, 0xd36ebdd3, 0xacef43ac, 0x62a6c462,
+ 0x91a83991, 0x95a43195, 0xe437d3e4, 0x798bf279,
+ 0xe732d5e7, 0xc8438bc8, 0x37596e37, 0x6db7da6d,
+ 0x8d8c018d, 0xd564b1d5, 0x4ed29c4e, 0xa9e049a9,
+ 0x6cb4d86c, 0x56faac56, 0xf407f3f4, 0xea25cfea,
+ 0x65afca65, 0x7a8ef47a, 0xaee947ae, 0x08181008,
+ 0xbad56fba, 0x7888f078, 0x256f4a25, 0x2e725c2e,
+ 0x1c24381c, 0xa6f157a6, 0xb4c773b4, 0xc65197c6,
+ 0xe823cbe8, 0xdd7ca1dd, 0x749ce874, 0x1f213e1f,
+ 0x4bdd964b, 0xbddc61bd, 0x8b860d8b, 0x8a850f8a,
+ 0x7090e070, 0x3e427c3e, 0xb5c471b5, 0x66aacc66,
+ 0x48d89048, 0x03050603, 0xf601f7f6, 0x0e121c0e,
+ 0x61a3c261, 0x355f6a35, 0x57f9ae57, 0xb9d069b9,
+ 0x86911786, 0xc15899c1, 0x1d273a1d, 0x9eb9279e,
+ 0xe138d9e1, 0xf813ebf8, 0x98b32b98, 0x11332211,
+ 0x69bbd269, 0xd970a9d9, 0x8e89078e, 0x94a73394,
+ 0x9bb62d9b, 0x1e223c1e, 0x87921587, 0xe920c9e9,
+ 0xce4987ce, 0x55ffaa55, 0x28785028, 0xdf7aa5df,
+ 0x8c8f038c, 0xa1f859a1, 0x89800989, 0x0d171a0d,
+ 0xbfda65bf, 0xe631d7e6, 0x42c68442, 0x68b8d068,
+ 0x41c38241, 0x99b02999, 0x2d775a2d, 0x0f111e0f,
+ 0xb0cb7bb0, 0x54fca854, 0xbbd66dbb, 0x163a2c16,
+};
+static const word32 E3[256] = {
+ 0x6363a5c6, 0x7c7c84f8, 0x777799ee, 0x7b7b8df6,
+ 0xf2f20dff, 0x6b6bbdd6, 0x6f6fb1de, 0xc5c55491,
+ 0x30305060, 0x01010302, 0x6767a9ce, 0x2b2b7d56,
+ 0xfefe19e7, 0xd7d762b5, 0xababe64d, 0x76769aec,
+ 0xcaca458f, 0x82829d1f, 0xc9c94089, 0x7d7d87fa,
+ 0xfafa15ef, 0x5959ebb2, 0x4747c98e, 0xf0f00bfb,
+ 0xadadec41, 0xd4d467b3, 0xa2a2fd5f, 0xafafea45,
+ 0x9c9cbf23, 0xa4a4f753, 0x727296e4, 0xc0c05b9b,
+ 0xb7b7c275, 0xfdfd1ce1, 0x9393ae3d, 0x26266a4c,
+ 0x36365a6c, 0x3f3f417e, 0xf7f702f5, 0xcccc4f83,
+ 0x34345c68, 0xa5a5f451, 0xe5e534d1, 0xf1f108f9,
+ 0x717193e2, 0xd8d873ab, 0x31315362, 0x15153f2a,
+ 0x04040c08, 0xc7c75295, 0x23236546, 0xc3c35e9d,
+ 0x18182830, 0x9696a137, 0x05050f0a, 0x9a9ab52f,
+ 0x0707090e, 0x12123624, 0x80809b1b, 0xe2e23ddf,
+ 0xebeb26cd, 0x2727694e, 0xb2b2cd7f, 0x75759fea,
+ 0x09091b12, 0x83839e1d, 0x2c2c7458, 0x1a1a2e34,
+ 0x1b1b2d36, 0x6e6eb2dc, 0x5a5aeeb4, 0xa0a0fb5b,
+ 0x5252f6a4, 0x3b3b4d76, 0xd6d661b7, 0xb3b3ce7d,
+ 0x29297b52, 0xe3e33edd, 0x2f2f715e, 0x84849713,
+ 0x5353f5a6, 0xd1d168b9, 0x00000000, 0xeded2cc1,
+ 0x20206040, 0xfcfc1fe3, 0xb1b1c879, 0x5b5bedb6,
+ 0x6a6abed4, 0xcbcb468d, 0xbebed967, 0x39394b72,
+ 0x4a4ade94, 0x4c4cd498, 0x5858e8b0, 0xcfcf4a85,
+ 0xd0d06bbb, 0xefef2ac5, 0xaaaae54f, 0xfbfb16ed,
+ 0x4343c586, 0x4d4dd79a, 0x33335566, 0x85859411,
+ 0x4545cf8a, 0xf9f910e9, 0x02020604, 0x7f7f81fe,
+ 0x5050f0a0, 0x3c3c4478, 0x9f9fba25, 0xa8a8e34b,
+ 0x5151f3a2, 0xa3a3fe5d, 0x4040c080, 0x8f8f8a05,
+ 0x9292ad3f, 0x9d9dbc21, 0x38384870, 0xf5f504f1,
+ 0xbcbcdf63, 0xb6b6c177, 0xdada75af, 0x21216342,
+ 0x10103020, 0xffff1ae5, 0xf3f30efd, 0xd2d26dbf,
+ 0xcdcd4c81, 0x0c0c1418, 0x13133526, 0xecec2fc3,
+ 0x5f5fe1be, 0x9797a235, 0x4444cc88, 0x1717392e,
+ 0xc4c45793, 0xa7a7f255, 0x7e7e82fc, 0x3d3d477a,
+ 0x6464acc8, 0x5d5de7ba, 0x19192b32, 0x737395e6,
+ 0x6060a0c0, 0x81819819, 0x4f4fd19e, 0xdcdc7fa3,
+ 0x22226644, 0x2a2a7e54, 0x9090ab3b, 0x8888830b,
+ 0x4646ca8c, 0xeeee29c7, 0xb8b8d36b, 0x14143c28,
+ 0xdede79a7, 0x5e5ee2bc, 0x0b0b1d16, 0xdbdb76ad,
+ 0xe0e03bdb, 0x32325664, 0x3a3a4e74, 0x0a0a1e14,
+ 0x4949db92, 0x06060a0c, 0x24246c48, 0x5c5ce4b8,
+ 0xc2c25d9f, 0xd3d36ebd, 0xacacef43, 0x6262a6c4,
+ 0x9191a839, 0x9595a431, 0xe4e437d3, 0x79798bf2,
+ 0xe7e732d5, 0xc8c8438b, 0x3737596e, 0x6d6db7da,
+ 0x8d8d8c01, 0xd5d564b1, 0x4e4ed29c, 0xa9a9e049,
+ 0x6c6cb4d8, 0x5656faac, 0xf4f407f3, 0xeaea25cf,
+ 0x6565afca, 0x7a7a8ef4, 0xaeaee947, 0x08081810,
+ 0xbabad56f, 0x787888f0, 0x25256f4a, 0x2e2e725c,
+ 0x1c1c2438, 0xa6a6f157, 0xb4b4c773, 0xc6c65197,
+ 0xe8e823cb, 0xdddd7ca1, 0x74749ce8, 0x1f1f213e,
+ 0x4b4bdd96, 0xbdbddc61, 0x8b8b860d, 0x8a8a850f,
+ 0x707090e0, 0x3e3e427c, 0xb5b5c471, 0x6666aacc,
+ 0x4848d890, 0x03030506, 0xf6f601f7, 0x0e0e121c,
+ 0x6161a3c2, 0x35355f6a, 0x5757f9ae, 0xb9b9d069,
+ 0x86869117, 0xc1c15899, 0x1d1d273a, 0x9e9eb927,
+ 0xe1e138d9, 0xf8f813eb, 0x9898b32b, 0x11113322,
+ 0x6969bbd2, 0xd9d970a9, 0x8e8e8907, 0x9494a733,
+ 0x9b9bb62d, 0x1e1e223c, 0x87879215, 0xe9e920c9,
+ 0xcece4987, 0x5555ffaa, 0x28287850, 0xdfdf7aa5,
+ 0x8c8c8f03, 0xa1a1f859, 0x89898009, 0x0d0d171a,
+ 0xbfbfda65, 0xe6e631d7, 0x4242c684, 0x6868b8d0,
+ 0x4141c382, 0x9999b029, 0x2d2d775a, 0x0f0f111e,
+ 0xb0b0cb7b, 0x5454fca8, 0xbbbbd66d, 0x16163a2c,
+};
+static const word32 D0[256] = {
+ 0x51f4a750, 0x7e416553, 0x1a17a4c3, 0x3a275e96,
+ 0x3bab6bcb, 0x1f9d45f1, 0xacfa58ab, 0x4be30393,
+ 0x2030fa55, 0xad766df6, 0x88cc7691, 0xf5024c25,
+ 0x4fe5d7fc, 0xc52acbd7, 0x26354480, 0xb562a38f,
+ 0xdeb15a49, 0x25ba1b67, 0x45ea0e98, 0x5dfec0e1,
+ 0xc32f7502, 0x814cf012, 0x8d4697a3, 0x6bd3f9c6,
+ 0x038f5fe7, 0x15929c95, 0xbf6d7aeb, 0x955259da,
+ 0xd4be832d, 0x587421d3, 0x49e06929, 0x8ec9c844,
+ 0x75c2896a, 0xf48e7978, 0x99583e6b, 0x27b971dd,
+ 0xbee14fb6, 0xf088ad17, 0xc920ac66, 0x7dce3ab4,
+ 0x63df4a18, 0xe51a3182, 0x97513360, 0x62537f45,
+ 0xb16477e0, 0xbb6bae84, 0xfe81a01c, 0xf9082b94,
+ 0x70486858, 0x8f45fd19, 0x94de6c87, 0x527bf8b7,
+ 0xab73d323, 0x724b02e2, 0xe31f8f57, 0x6655ab2a,
+ 0xb2eb2807, 0x2fb5c203, 0x86c57b9a, 0xd33708a5,
+ 0x302887f2, 0x23bfa5b2, 0x02036aba, 0xed16825c,
+ 0x8acf1c2b, 0xa779b492, 0xf307f2f0, 0x4e69e2a1,
+ 0x65daf4cd, 0x0605bed5, 0xd134621f, 0xc4a6fe8a,
+ 0x342e539d, 0xa2f355a0, 0x058ae132, 0xa4f6eb75,
+ 0x0b83ec39, 0x4060efaa, 0x5e719f06, 0xbd6e1051,
+ 0x3e218af9, 0x96dd063d, 0xdd3e05ae, 0x4de6bd46,
+ 0x91548db5, 0x71c45d05, 0x0406d46f, 0x605015ff,
+ 0x1998fb24, 0xd6bde997, 0x894043cc, 0x67d99e77,
+ 0xb0e842bd, 0x07898b88, 0xe7195b38, 0x79c8eedb,
+ 0xa17c0a47, 0x7c420fe9, 0xf8841ec9, 0x00000000,
+ 0x09808683, 0x322bed48, 0x1e1170ac, 0x6c5a724e,
+ 0xfd0efffb, 0x0f853856, 0x3daed51e, 0x362d3927,
+ 0x0a0fd964, 0x685ca621, 0x9b5b54d1, 0x24362e3a,
+ 0x0c0a67b1, 0x9357e70f, 0xb4ee96d2, 0x1b9b919e,
+ 0x80c0c54f, 0x61dc20a2, 0x5a774b69, 0x1c121a16,
+ 0xe293ba0a, 0xc0a02ae5, 0x3c22e043, 0x121b171d,
+ 0x0e090d0b, 0xf28bc7ad, 0x2db6a8b9, 0x141ea9c8,
+ 0x57f11985, 0xaf75074c, 0xee99ddbb, 0xa37f60fd,
+ 0xf701269f, 0x5c72f5bc, 0x44663bc5, 0x5bfb7e34,
+ 0x8b432976, 0xcb23c6dc, 0xb6edfc68, 0xb8e4f163,
+ 0xd731dcca, 0x42638510, 0x13972240, 0x84c61120,
+ 0x854a247d, 0xd2bb3df8, 0xaef93211, 0xc729a16d,
+ 0x1d9e2f4b, 0xdcb230f3, 0x0d8652ec, 0x77c1e3d0,
+ 0x2bb3166c, 0xa970b999, 0x119448fa, 0x47e96422,
+ 0xa8fc8cc4, 0xa0f03f1a, 0x567d2cd8, 0x223390ef,
+ 0x87494ec7, 0xd938d1c1, 0x8ccaa2fe, 0x98d40b36,
+ 0xa6f581cf, 0xa57ade28, 0xdab78e26, 0x3fadbfa4,
+ 0x2c3a9de4, 0x5078920d, 0x6a5fcc9b, 0x547e4662,
+ 0xf68d13c2, 0x90d8b8e8, 0x2e39f75e, 0x82c3aff5,
+ 0x9f5d80be, 0x69d0937c, 0x6fd52da9, 0xcf2512b3,
+ 0xc8ac993b, 0x10187da7, 0xe89c636e, 0xdb3bbb7b,
+ 0xcd267809, 0x6e5918f4, 0xec9ab701, 0x834f9aa8,
+ 0xe6956e65, 0xaaffe67e, 0x21bccf08, 0xef15e8e6,
+ 0xbae79bd9, 0x4a6f36ce, 0xea9f09d4, 0x29b07cd6,
+ 0x31a4b2af, 0x2a3f2331, 0xc6a59430, 0x35a266c0,
+ 0x744ebc37, 0xfc82caa6, 0xe090d0b0, 0x33a7d815,
+ 0xf104984a, 0x41ecdaf7, 0x7fcd500e, 0x1791f62f,
+ 0x764dd68d, 0x43efb04d, 0xccaa4d54, 0xe49604df,
+ 0x9ed1b5e3, 0x4c6a881b, 0xc12c1fb8, 0x4665517f,
+ 0x9d5eea04, 0x018c355d, 0xfa877473, 0xfb0b412e,
+ 0xb3671d5a, 0x92dbd252, 0xe9105633, 0x6dd64713,
+ 0x9ad7618c, 0x37a10c7a, 0x59f8148e, 0xeb133c89,
+ 0xcea927ee, 0xb761c935, 0xe11ce5ed, 0x7a47b13c,
+ 0x9cd2df59, 0x55f2733f, 0x1814ce79, 0x73c737bf,
+ 0x53f7cdea, 0x5ffdaa5b, 0xdf3d6f14, 0x7844db86,
+ 0xcaaff381, 0xb968c43e, 0x3824342c, 0xc2a3405f,
+ 0x161dc372, 0xbce2250c, 0x283c498b, 0xff0d9541,
+ 0x39a80171, 0x080cb3de, 0xd8b4e49c, 0x6456c190,
+ 0x7bcb8461, 0xd532b670, 0x486c5c74, 0xd0b85742,
+};
+static const word32 D1[256] = {
+ 0x5051f4a7, 0x537e4165, 0xc31a17a4, 0x963a275e,
+ 0xcb3bab6b, 0xf11f9d45, 0xabacfa58, 0x934be303,
+ 0x552030fa, 0xf6ad766d, 0x9188cc76, 0x25f5024c,
+ 0xfc4fe5d7, 0xd7c52acb, 0x80263544, 0x8fb562a3,
+ 0x49deb15a, 0x6725ba1b, 0x9845ea0e, 0xe15dfec0,
+ 0x02c32f75, 0x12814cf0, 0xa38d4697, 0xc66bd3f9,
+ 0xe7038f5f, 0x9515929c, 0xebbf6d7a, 0xda955259,
+ 0x2dd4be83, 0xd3587421, 0x2949e069, 0x448ec9c8,
+ 0x6a75c289, 0x78f48e79, 0x6b99583e, 0xdd27b971,
+ 0xb6bee14f, 0x17f088ad, 0x66c920ac, 0xb47dce3a,
+ 0x1863df4a, 0x82e51a31, 0x60975133, 0x4562537f,
+ 0xe0b16477, 0x84bb6bae, 0x1cfe81a0, 0x94f9082b,
+ 0x58704868, 0x198f45fd, 0x8794de6c, 0xb7527bf8,
+ 0x23ab73d3, 0xe2724b02, 0x57e31f8f, 0x2a6655ab,
+ 0x07b2eb28, 0x032fb5c2, 0x9a86c57b, 0xa5d33708,
+ 0xf2302887, 0xb223bfa5, 0xba02036a, 0x5ced1682,
+ 0x2b8acf1c, 0x92a779b4, 0xf0f307f2, 0xa14e69e2,
+ 0xcd65daf4, 0xd50605be, 0x1fd13462, 0x8ac4a6fe,
+ 0x9d342e53, 0xa0a2f355, 0x32058ae1, 0x75a4f6eb,
+ 0x390b83ec, 0xaa4060ef, 0x065e719f, 0x51bd6e10,
+ 0xf93e218a, 0x3d96dd06, 0xaedd3e05, 0x464de6bd,
+ 0xb591548d, 0x0571c45d, 0x6f0406d4, 0xff605015,
+ 0x241998fb, 0x97d6bde9, 0xcc894043, 0x7767d99e,
+ 0xbdb0e842, 0x8807898b, 0x38e7195b, 0xdb79c8ee,
+ 0x47a17c0a, 0xe97c420f, 0xc9f8841e, 0x00000000,
+ 0x83098086, 0x48322bed, 0xac1e1170, 0x4e6c5a72,
+ 0xfbfd0eff, 0x560f8538, 0x1e3daed5, 0x27362d39,
+ 0x640a0fd9, 0x21685ca6, 0xd19b5b54, 0x3a24362e,
+ 0xb10c0a67, 0x0f9357e7, 0xd2b4ee96, 0x9e1b9b91,
+ 0x4f80c0c5, 0xa261dc20, 0x695a774b, 0x161c121a,
+ 0x0ae293ba, 0xe5c0a02a, 0x433c22e0, 0x1d121b17,
+ 0x0b0e090d, 0xadf28bc7, 0xb92db6a8, 0xc8141ea9,
+ 0x8557f119, 0x4caf7507, 0xbbee99dd, 0xfda37f60,
+ 0x9ff70126, 0xbc5c72f5, 0xc544663b, 0x345bfb7e,
+ 0x768b4329, 0xdccb23c6, 0x68b6edfc, 0x63b8e4f1,
+ 0xcad731dc, 0x10426385, 0x40139722, 0x2084c611,
+ 0x7d854a24, 0xf8d2bb3d, 0x11aef932, 0x6dc729a1,
+ 0x4b1d9e2f, 0xf3dcb230, 0xec0d8652, 0xd077c1e3,
+ 0x6c2bb316, 0x99a970b9, 0xfa119448, 0x2247e964,
+ 0xc4a8fc8c, 0x1aa0f03f, 0xd8567d2c, 0xef223390,
+ 0xc787494e, 0xc1d938d1, 0xfe8ccaa2, 0x3698d40b,
+ 0xcfa6f581, 0x28a57ade, 0x26dab78e, 0xa43fadbf,
+ 0xe42c3a9d, 0x0d507892, 0x9b6a5fcc, 0x62547e46,
+ 0xc2f68d13, 0xe890d8b8, 0x5e2e39f7, 0xf582c3af,
+ 0xbe9f5d80, 0x7c69d093, 0xa96fd52d, 0xb3cf2512,
+ 0x3bc8ac99, 0xa710187d, 0x6ee89c63, 0x7bdb3bbb,
+ 0x09cd2678, 0xf46e5918, 0x01ec9ab7, 0xa8834f9a,
+ 0x65e6956e, 0x7eaaffe6, 0x0821bccf, 0xe6ef15e8,
+ 0xd9bae79b, 0xce4a6f36, 0xd4ea9f09, 0xd629b07c,
+ 0xaf31a4b2, 0x312a3f23, 0x30c6a594, 0xc035a266,
+ 0x37744ebc, 0xa6fc82ca, 0xb0e090d0, 0x1533a7d8,
+ 0x4af10498, 0xf741ecda, 0x0e7fcd50, 0x2f1791f6,
+ 0x8d764dd6, 0x4d43efb0, 0x54ccaa4d, 0xdfe49604,
+ 0xe39ed1b5, 0x1b4c6a88, 0xb8c12c1f, 0x7f466551,
+ 0x049d5eea, 0x5d018c35, 0x73fa8774, 0x2efb0b41,
+ 0x5ab3671d, 0x5292dbd2, 0x33e91056, 0x136dd647,
+ 0x8c9ad761, 0x7a37a10c, 0x8e59f814, 0x89eb133c,
+ 0xeecea927, 0x35b761c9, 0xede11ce5, 0x3c7a47b1,
+ 0x599cd2df, 0x3f55f273, 0x791814ce, 0xbf73c737,
+ 0xea53f7cd, 0x5b5ffdaa, 0x14df3d6f, 0x867844db,
+ 0x81caaff3, 0x3eb968c4, 0x2c382434, 0x5fc2a340,
+ 0x72161dc3, 0x0cbce225, 0x8b283c49, 0x41ff0d95,
+ 0x7139a801, 0xde080cb3, 0x9cd8b4e4, 0x906456c1,
+ 0x617bcb84, 0x70d532b6, 0x74486c5c, 0x42d0b857,
+};
+static const word32 D2[256] = {
+ 0xa75051f4, 0x65537e41, 0xa4c31a17, 0x5e963a27,
+ 0x6bcb3bab, 0x45f11f9d, 0x58abacfa, 0x03934be3,
+ 0xfa552030, 0x6df6ad76, 0x769188cc, 0x4c25f502,
+ 0xd7fc4fe5, 0xcbd7c52a, 0x44802635, 0xa38fb562,
+ 0x5a49deb1, 0x1b6725ba, 0x0e9845ea, 0xc0e15dfe,
+ 0x7502c32f, 0xf012814c, 0x97a38d46, 0xf9c66bd3,
+ 0x5fe7038f, 0x9c951592, 0x7aebbf6d, 0x59da9552,
+ 0x832dd4be, 0x21d35874, 0x692949e0, 0xc8448ec9,
+ 0x896a75c2, 0x7978f48e, 0x3e6b9958, 0x71dd27b9,
+ 0x4fb6bee1, 0xad17f088, 0xac66c920, 0x3ab47dce,
+ 0x4a1863df, 0x3182e51a, 0x33609751, 0x7f456253,
+ 0x77e0b164, 0xae84bb6b, 0xa01cfe81, 0x2b94f908,
+ 0x68587048, 0xfd198f45, 0x6c8794de, 0xf8b7527b,
+ 0xd323ab73, 0x02e2724b, 0x8f57e31f, 0xab2a6655,
+ 0x2807b2eb, 0xc2032fb5, 0x7b9a86c5, 0x08a5d337,
+ 0x87f23028, 0xa5b223bf, 0x6aba0203, 0x825ced16,
+ 0x1c2b8acf, 0xb492a779, 0xf2f0f307, 0xe2a14e69,
+ 0xf4cd65da, 0xbed50605, 0x621fd134, 0xfe8ac4a6,
+ 0x539d342e, 0x55a0a2f3, 0xe132058a, 0xeb75a4f6,
+ 0xec390b83, 0xefaa4060, 0x9f065e71, 0x1051bd6e,
+ 0x8af93e21, 0x063d96dd, 0x05aedd3e, 0xbd464de6,
+ 0x8db59154, 0x5d0571c4, 0xd46f0406, 0x15ff6050,
+ 0xfb241998, 0xe997d6bd, 0x43cc8940, 0x9e7767d9,
+ 0x42bdb0e8, 0x8b880789, 0x5b38e719, 0xeedb79c8,
+ 0x0a47a17c, 0x0fe97c42, 0x1ec9f884, 0x00000000,
+ 0x86830980, 0xed48322b, 0x70ac1e11, 0x724e6c5a,
+ 0xfffbfd0e, 0x38560f85, 0xd51e3dae, 0x3927362d,
+ 0xd9640a0f, 0xa621685c, 0x54d19b5b, 0x2e3a2436,
+ 0x67b10c0a, 0xe70f9357, 0x96d2b4ee, 0x919e1b9b,
+ 0xc54f80c0, 0x20a261dc, 0x4b695a77, 0x1a161c12,
+ 0xba0ae293, 0x2ae5c0a0, 0xe0433c22, 0x171d121b,
+ 0x0d0b0e09, 0xc7adf28b, 0xa8b92db6, 0xa9c8141e,
+ 0x198557f1, 0x074caf75, 0xddbbee99, 0x60fda37f,
+ 0x269ff701, 0xf5bc5c72, 0x3bc54466, 0x7e345bfb,
+ 0x29768b43, 0xc6dccb23, 0xfc68b6ed, 0xf163b8e4,
+ 0xdccad731, 0x85104263, 0x22401397, 0x112084c6,
+ 0x247d854a, 0x3df8d2bb, 0x3211aef9, 0xa16dc729,
+ 0x2f4b1d9e, 0x30f3dcb2, 0x52ec0d86, 0xe3d077c1,
+ 0x166c2bb3, 0xb999a970, 0x48fa1194, 0x642247e9,
+ 0x8cc4a8fc, 0x3f1aa0f0, 0x2cd8567d, 0x90ef2233,
+ 0x4ec78749, 0xd1c1d938, 0xa2fe8cca, 0x0b3698d4,
+ 0x81cfa6f5, 0xde28a57a, 0x8e26dab7, 0xbfa43fad,
+ 0x9de42c3a, 0x920d5078, 0xcc9b6a5f, 0x4662547e,
+ 0x13c2f68d, 0xb8e890d8, 0xf75e2e39, 0xaff582c3,
+ 0x80be9f5d, 0x937c69d0, 0x2da96fd5, 0x12b3cf25,
+ 0x993bc8ac, 0x7da71018, 0x636ee89c, 0xbb7bdb3b,
+ 0x7809cd26, 0x18f46e59, 0xb701ec9a, 0x9aa8834f,
+ 0x6e65e695, 0xe67eaaff, 0xcf0821bc, 0xe8e6ef15,
+ 0x9bd9bae7, 0x36ce4a6f, 0x09d4ea9f, 0x7cd629b0,
+ 0xb2af31a4, 0x23312a3f, 0x9430c6a5, 0x66c035a2,
+ 0xbc37744e, 0xcaa6fc82, 0xd0b0e090, 0xd81533a7,
+ 0x984af104, 0xdaf741ec, 0x500e7fcd, 0xf62f1791,
+ 0xd68d764d, 0xb04d43ef, 0x4d54ccaa, 0x04dfe496,
+ 0xb5e39ed1, 0x881b4c6a, 0x1fb8c12c, 0x517f4665,
+ 0xea049d5e, 0x355d018c, 0x7473fa87, 0x412efb0b,
+ 0x1d5ab367, 0xd25292db, 0x5633e910, 0x47136dd6,
+ 0x618c9ad7, 0x0c7a37a1, 0x148e59f8, 0x3c89eb13,
+ 0x27eecea9, 0xc935b761, 0xe5ede11c, 0xb13c7a47,
+ 0xdf599cd2, 0x733f55f2, 0xce791814, 0x37bf73c7,
+ 0xcdea53f7, 0xaa5b5ffd, 0x6f14df3d, 0xdb867844,
+ 0xf381caaf, 0xc43eb968, 0x342c3824, 0x405fc2a3,
+ 0xc372161d, 0x250cbce2, 0x498b283c, 0x9541ff0d,
+ 0x017139a8, 0xb3de080c, 0xe49cd8b4, 0xc1906456,
+ 0x84617bcb, 0xb670d532, 0x5c74486c, 0x5742d0b8,
+};
+static const word32 D3[256] = {
+ 0xf4a75051, 0x4165537e, 0x17a4c31a, 0x275e963a,
+ 0xab6bcb3b, 0x9d45f11f, 0xfa58abac, 0xe303934b,
+ 0x30fa5520, 0x766df6ad, 0xcc769188, 0x024c25f5,
+ 0xe5d7fc4f, 0x2acbd7c5, 0x35448026, 0x62a38fb5,
+ 0xb15a49de, 0xba1b6725, 0xea0e9845, 0xfec0e15d,
+ 0x2f7502c3, 0x4cf01281, 0x4697a38d, 0xd3f9c66b,
+ 0x8f5fe703, 0x929c9515, 0x6d7aebbf, 0x5259da95,
+ 0xbe832dd4, 0x7421d358, 0xe0692949, 0xc9c8448e,
+ 0xc2896a75, 0x8e7978f4, 0x583e6b99, 0xb971dd27,
+ 0xe14fb6be, 0x88ad17f0, 0x20ac66c9, 0xce3ab47d,
+ 0xdf4a1863, 0x1a3182e5, 0x51336097, 0x537f4562,
+ 0x6477e0b1, 0x6bae84bb, 0x81a01cfe, 0x082b94f9,
+ 0x48685870, 0x45fd198f, 0xde6c8794, 0x7bf8b752,
+ 0x73d323ab, 0x4b02e272, 0x1f8f57e3, 0x55ab2a66,
+ 0xeb2807b2, 0xb5c2032f, 0xc57b9a86, 0x3708a5d3,
+ 0x2887f230, 0xbfa5b223, 0x036aba02, 0x16825ced,
+ 0xcf1c2b8a, 0x79b492a7, 0x07f2f0f3, 0x69e2a14e,
+ 0xdaf4cd65, 0x05bed506, 0x34621fd1, 0xa6fe8ac4,
+ 0x2e539d34, 0xf355a0a2, 0x8ae13205, 0xf6eb75a4,
+ 0x83ec390b, 0x60efaa40, 0x719f065e, 0x6e1051bd,
+ 0x218af93e, 0xdd063d96, 0x3e05aedd, 0xe6bd464d,
+ 0x548db591, 0xc45d0571, 0x06d46f04, 0x5015ff60,
+ 0x98fb2419, 0xbde997d6, 0x4043cc89, 0xd99e7767,
+ 0xe842bdb0, 0x898b8807, 0x195b38e7, 0xc8eedb79,
+ 0x7c0a47a1, 0x420fe97c, 0x841ec9f8, 0x00000000,
+ 0x80868309, 0x2bed4832, 0x1170ac1e, 0x5a724e6c,
+ 0x0efffbfd, 0x8538560f, 0xaed51e3d, 0x2d392736,
+ 0x0fd9640a, 0x5ca62168, 0x5b54d19b, 0x362e3a24,
+ 0x0a67b10c, 0x57e70f93, 0xee96d2b4, 0x9b919e1b,
+ 0xc0c54f80, 0xdc20a261, 0x774b695a, 0x121a161c,
+ 0x93ba0ae2, 0xa02ae5c0, 0x22e0433c, 0x1b171d12,
+ 0x090d0b0e, 0x8bc7adf2, 0xb6a8b92d, 0x1ea9c814,
+ 0xf1198557, 0x75074caf, 0x99ddbbee, 0x7f60fda3,
+ 0x01269ff7, 0x72f5bc5c, 0x663bc544, 0xfb7e345b,
+ 0x4329768b, 0x23c6dccb, 0xedfc68b6, 0xe4f163b8,
+ 0x31dccad7, 0x63851042, 0x97224013, 0xc6112084,
+ 0x4a247d85, 0xbb3df8d2, 0xf93211ae, 0x29a16dc7,
+ 0x9e2f4b1d, 0xb230f3dc, 0x8652ec0d, 0xc1e3d077,
+ 0xb3166c2b, 0x70b999a9, 0x9448fa11, 0xe9642247,
+ 0xfc8cc4a8, 0xf03f1aa0, 0x7d2cd856, 0x3390ef22,
+ 0x494ec787, 0x38d1c1d9, 0xcaa2fe8c, 0xd40b3698,
+ 0xf581cfa6, 0x7ade28a5, 0xb78e26da, 0xadbfa43f,
+ 0x3a9de42c, 0x78920d50, 0x5fcc9b6a, 0x7e466254,
+ 0x8d13c2f6, 0xd8b8e890, 0x39f75e2e, 0xc3aff582,
+ 0x5d80be9f, 0xd0937c69, 0xd52da96f, 0x2512b3cf,
+ 0xac993bc8, 0x187da710, 0x9c636ee8, 0x3bbb7bdb,
+ 0x267809cd, 0x5918f46e, 0x9ab701ec, 0x4f9aa883,
+ 0x956e65e6, 0xffe67eaa, 0xbccf0821, 0x15e8e6ef,
+ 0xe79bd9ba, 0x6f36ce4a, 0x9f09d4ea, 0xb07cd629,
+ 0xa4b2af31, 0x3f23312a, 0xa59430c6, 0xa266c035,
+ 0x4ebc3774, 0x82caa6fc, 0x90d0b0e0, 0xa7d81533,
+ 0x04984af1, 0xecdaf741, 0xcd500e7f, 0x91f62f17,
+ 0x4dd68d76, 0xefb04d43, 0xaa4d54cc, 0x9604dfe4,
+ 0xd1b5e39e, 0x6a881b4c, 0x2c1fb8c1, 0x65517f46,
+ 0x5eea049d, 0x8c355d01, 0x877473fa, 0x0b412efb,
+ 0x671d5ab3, 0xdbd25292, 0x105633e9, 0xd647136d,
+ 0xd7618c9a, 0xa10c7a37, 0xf8148e59, 0x133c89eb,
+ 0xa927eece, 0x61c935b7, 0x1ce5ede1, 0x47b13c7a,
+ 0xd2df599c, 0xf2733f55, 0x14ce7918, 0xc737bf73,
+ 0xf7cdea53, 0xfdaa5b5f, 0x3d6f14df, 0x44db8678,
+ 0xaff381ca, 0x68c43eb9, 0x24342c38, 0xa3405fc2,
+ 0x1dc37216, 0xe2250cbc, 0x3c498b28, 0x0d9541ff,
+ 0xa8017139, 0x0cb3de08, 0xb4e49cd8, 0x56c19064,
+ 0xcb84617b, 0x32b670d5, 0x6c5c7448, 0xb85742d0,
+};
+
+/*
+ * Common macros in both the encryption and decryption routines.
+ */
+#define ADD_ROUND_KEY_4 (block[0]^=*keysched++, block[1]^=*keysched++, \
+ block[2]^=*keysched++, block[3]^=*keysched++)
+#define ADD_ROUND_KEY_6 (block[0]^=*keysched++, block[1]^=*keysched++, \
+ block[2]^=*keysched++, block[3]^=*keysched++, \
+ block[4]^=*keysched++, block[5]^=*keysched++)
+#define ADD_ROUND_KEY_8 (block[0]^=*keysched++, block[1]^=*keysched++, \
+ block[2]^=*keysched++, block[3]^=*keysched++, \
+ block[4]^=*keysched++, block[5]^=*keysched++, \
+ block[6]^=*keysched++, block[7]^=*keysched++)
+#define MOVEWORD(i) ( block[i] = newstate[i] )
+
+/*
+ * Macros for the encryption routine. There are three encryption
+ * cores, for Nb=4,6,8.
+ */
+#define MAKEWORD(i) ( newstate[i] = (E0[(block[i] >> 24) & 0xFF] ^ \
+ E1[(block[(i+C1)%Nb] >> 16) & 0xFF] ^ \
+ E2[(block[(i+C2)%Nb] >> 8) & 0xFF] ^ \
+ E3[block[(i+C3)%Nb] & 0xFF]) )
+#define LASTWORD(i) ( newstate[i] = (Sbox[(block[i] >> 24) & 0xFF] << 24) | \
+ (Sbox[(block[(i+C1)%Nb] >> 16) & 0xFF] << 16) | \
+ (Sbox[(block[(i+C2)%Nb] >> 8) & 0xFF] << 8) | \
+ (Sbox[(block[(i+C3)%Nb] ) & 0xFF] ) )
+
+/*
+ * Core encrypt routines, expecting word32 inputs read big-endian
+ * from the byte-oriented input stream.
+ */
+static void aes_encrypt_nb_4(AESContext * ctx, word32 * block)
+{
+ int i;
+ static const int C1 = 1, C2 = 2, C3 = 3, Nb = 4;
+ word32 *keysched = ctx->keysched;
+ word32 newstate[4];
+ for (i = 0; i < ctx->Nr - 1; i++) {
+ ADD_ROUND_KEY_4;
+ MAKEWORD(0);
+ MAKEWORD(1);
+ MAKEWORD(2);
+ MAKEWORD(3);
+ MOVEWORD(0);
+ MOVEWORD(1);
+ MOVEWORD(2);
+ MOVEWORD(3);
+ }
+ ADD_ROUND_KEY_4;
+ LASTWORD(0);
+ LASTWORD(1);
+ LASTWORD(2);
+ LASTWORD(3);
+ MOVEWORD(0);
+ MOVEWORD(1);
+ MOVEWORD(2);
+ MOVEWORD(3);
+ ADD_ROUND_KEY_4;
+}
+static void aes_encrypt_nb_6(AESContext * ctx, word32 * block)
+{
+ int i;
+ static const int C1 = 1, C2 = 2, C3 = 3, Nb = 6;
+ word32 *keysched = ctx->keysched;
+ word32 newstate[6];
+ for (i = 0; i < ctx->Nr - 1; i++) {
+ ADD_ROUND_KEY_6;
+ MAKEWORD(0);
+ MAKEWORD(1);
+ MAKEWORD(2);
+ MAKEWORD(3);
+ MAKEWORD(4);
+ MAKEWORD(5);
+ MOVEWORD(0);
+ MOVEWORD(1);
+ MOVEWORD(2);
+ MOVEWORD(3);
+ MOVEWORD(4);
+ MOVEWORD(5);
+ }
+ ADD_ROUND_KEY_6;
+ LASTWORD(0);
+ LASTWORD(1);
+ LASTWORD(2);
+ LASTWORD(3);
+ LASTWORD(4);
+ LASTWORD(5);
+ MOVEWORD(0);
+ MOVEWORD(1);
+ MOVEWORD(2);
+ MOVEWORD(3);
+ MOVEWORD(4);
+ MOVEWORD(5);
+ ADD_ROUND_KEY_6;
+}
+static void aes_encrypt_nb_8(AESContext * ctx, word32 * block)
+{
+ int i;
+ static const int C1 = 1, C2 = 3, C3 = 4, Nb = 8;
+ word32 *keysched = ctx->keysched;
+ word32 newstate[8];
+ for (i = 0; i < ctx->Nr - 1; i++) {
+ ADD_ROUND_KEY_8;
+ MAKEWORD(0);
+ MAKEWORD(1);
+ MAKEWORD(2);
+ MAKEWORD(3);
+ MAKEWORD(4);
+ MAKEWORD(5);
+ MAKEWORD(6);
+ MAKEWORD(7);
+ MOVEWORD(0);
+ MOVEWORD(1);
+ MOVEWORD(2);
+ MOVEWORD(3);
+ MOVEWORD(4);
+ MOVEWORD(5);
+ MOVEWORD(6);
+ MOVEWORD(7);
+ }
+ ADD_ROUND_KEY_8;
+ LASTWORD(0);
+ LASTWORD(1);
+ LASTWORD(2);
+ LASTWORD(3);
+ LASTWORD(4);
+ LASTWORD(5);
+ LASTWORD(6);
+ LASTWORD(7);
+ MOVEWORD(0);
+ MOVEWORD(1);
+ MOVEWORD(2);
+ MOVEWORD(3);
+ MOVEWORD(4);
+ MOVEWORD(5);
+ MOVEWORD(6);
+ MOVEWORD(7);
+ ADD_ROUND_KEY_8;
+}
+
+#undef MAKEWORD
+#undef LASTWORD
+
+/*
+ * Macros for the decryption routine. There are three decryption
+ * cores, for Nb=4,6,8.
+ */
+#define MAKEWORD(i) ( newstate[i] = (D0[(block[i] >> 24) & 0xFF] ^ \
+ D1[(block[(i+C1)%Nb] >> 16) & 0xFF] ^ \
+ D2[(block[(i+C2)%Nb] >> 8) & 0xFF] ^ \
+ D3[block[(i+C3)%Nb] & 0xFF]) )
+#define LASTWORD(i) (newstate[i] = (Sboxinv[(block[i] >> 24) & 0xFF] << 24) | \
+ (Sboxinv[(block[(i+C1)%Nb] >> 16) & 0xFF] << 16) | \
+ (Sboxinv[(block[(i+C2)%Nb] >> 8) & 0xFF] << 8) | \
+ (Sboxinv[(block[(i+C3)%Nb] ) & 0xFF] ) )
+
+/*
+ * Core decrypt routines, expecting word32 inputs read big-endian
+ * from the byte-oriented input stream.
+ */
+static void aes_decrypt_nb_4(AESContext * ctx, word32 * block)
+{
+ int i;
+ static const int C1 = 4 - 1, C2 = 4 - 2, C3 = 4 - 3, Nb = 4;
+ word32 *keysched = ctx->invkeysched;
+ word32 newstate[4];
+ for (i = 0; i < ctx->Nr - 1; i++) {
+ ADD_ROUND_KEY_4;
+ MAKEWORD(0);
+ MAKEWORD(1);
+ MAKEWORD(2);
+ MAKEWORD(3);
+ MOVEWORD(0);
+ MOVEWORD(1);
+ MOVEWORD(2);
+ MOVEWORD(3);
+ }
+ ADD_ROUND_KEY_4;
+ LASTWORD(0);
+ LASTWORD(1);
+ LASTWORD(2);
+ LASTWORD(3);
+ MOVEWORD(0);
+ MOVEWORD(1);
+ MOVEWORD(2);
+ MOVEWORD(3);
+ ADD_ROUND_KEY_4;
+}
+static void aes_decrypt_nb_6(AESContext * ctx, word32 * block)
+{
+ int i;
+ static const int C1 = 6 - 1, C2 = 6 - 2, C3 = 6 - 3, Nb = 6;
+ word32 *keysched = ctx->invkeysched;
+ word32 newstate[6];
+ for (i = 0; i < ctx->Nr - 1; i++) {
+ ADD_ROUND_KEY_6;
+ MAKEWORD(0);
+ MAKEWORD(1);
+ MAKEWORD(2);
+ MAKEWORD(3);
+ MAKEWORD(4);
+ MAKEWORD(5);
+ MOVEWORD(0);
+ MOVEWORD(1);
+ MOVEWORD(2);
+ MOVEWORD(3);
+ MOVEWORD(4);
+ MOVEWORD(5);
+ }
+ ADD_ROUND_KEY_6;
+ LASTWORD(0);
+ LASTWORD(1);
+ LASTWORD(2);
+ LASTWORD(3);
+ LASTWORD(4);
+ LASTWORD(5);
+ MOVEWORD(0);
+ MOVEWORD(1);
+ MOVEWORD(2);
+ MOVEWORD(3);
+ MOVEWORD(4);
+ MOVEWORD(5);
+ ADD_ROUND_KEY_6;
+}
+static void aes_decrypt_nb_8(AESContext * ctx, word32 * block)
+{
+ int i;
+ static const int C1 = 8 - 1, C2 = 8 - 3, C3 = 8 - 4, Nb = 8;
+ word32 *keysched = ctx->invkeysched;
+ word32 newstate[8];
+ for (i = 0; i < ctx->Nr - 1; i++) {
+ ADD_ROUND_KEY_8;
+ MAKEWORD(0);
+ MAKEWORD(1);
+ MAKEWORD(2);
+ MAKEWORD(3);
+ MAKEWORD(4);
+ MAKEWORD(5);
+ MAKEWORD(6);
+ MAKEWORD(7);
+ MOVEWORD(0);
+ MOVEWORD(1);
+ MOVEWORD(2);
+ MOVEWORD(3);
+ MOVEWORD(4);
+ MOVEWORD(5);
+ MOVEWORD(6);
+ MOVEWORD(7);
+ }
+ ADD_ROUND_KEY_8;
+ LASTWORD(0);
+ LASTWORD(1);
+ LASTWORD(2);
+ LASTWORD(3);
+ LASTWORD(4);
+ LASTWORD(5);
+ LASTWORD(6);
+ LASTWORD(7);
+ MOVEWORD(0);
+ MOVEWORD(1);
+ MOVEWORD(2);
+ MOVEWORD(3);
+ MOVEWORD(4);
+ MOVEWORD(5);
+ MOVEWORD(6);
+ MOVEWORD(7);
+ ADD_ROUND_KEY_8;
+}
+
+#undef MAKEWORD
+#undef LASTWORD
+
+
+/*
+ * Set up an AESContext. `keylen' and `blocklen' are measured in
+ * bytes; each can be either 16 (128-bit), 24 (192-bit), or 32
+ * (256-bit).
+ */
+static void aes_setup(AESContext * ctx, int blocklen,
+ unsigned char *key, int keylen)
+{
+ int i, j, Nk, rconst;
+
+ assert(blocklen == 16 || blocklen == 24 || blocklen == 32);
+ assert(keylen == 16 || keylen == 24 || keylen == 32);
+
+ /*
+ * Basic parameters. Words per block, words in key, rounds.
+ */
+ Nk = keylen / 4;
+ ctx->Nb = blocklen / 4;
+ ctx->Nr = 6 + (ctx->Nb > Nk ? ctx->Nb : Nk);
+
+ /*
+ * Assign core-function pointers.
+ */
+ if (ctx->Nb == 8)
+ ctx->encrypt = aes_encrypt_nb_8, ctx->decrypt = aes_decrypt_nb_8;
+ else if (ctx->Nb == 6)
+ ctx->encrypt = aes_encrypt_nb_6, ctx->decrypt = aes_decrypt_nb_6;
+ else if (ctx->Nb == 4)
+ ctx->encrypt = aes_encrypt_nb_4, ctx->decrypt = aes_decrypt_nb_4;
+
+ /*
+ * Now do the key setup itself.
+ */
+ rconst = 1;
+ for (i = 0; i < (ctx->Nr + 1) * ctx->Nb; i++) {
+ if (i < Nk)
+ ctx->keysched[i] = GET_32BIT_MSB_FIRST(key + 4 * i);
+ else {
+ word32 temp = ctx->keysched[i - 1];
+ if (i % Nk == 0) {
+ int a, b, c, d;
+ a = (temp >> 16) & 0xFF;
+ b = (temp >> 8) & 0xFF;
+ c = (temp >> 0) & 0xFF;
+ d = (temp >> 24) & 0xFF;
+ temp = Sbox[a] ^ rconst;
+ temp = (temp << 8) | Sbox[b];
+ temp = (temp << 8) | Sbox[c];
+ temp = (temp << 8) | Sbox[d];
+ rconst = mulby2(rconst);
+ } else if (i % Nk == 4 && Nk > 6) {
+ int a, b, c, d;
+ a = (temp >> 24) & 0xFF;
+ b = (temp >> 16) & 0xFF;
+ c = (temp >> 8) & 0xFF;
+ d = (temp >> 0) & 0xFF;
+ temp = Sbox[a];
+ temp = (temp << 8) | Sbox[b];
+ temp = (temp << 8) | Sbox[c];
+ temp = (temp << 8) | Sbox[d];
+ }
+ ctx->keysched[i] = ctx->keysched[i - Nk] ^ temp;
+ }
+ }
+
+ /*
+ * Now prepare the modified keys for the inverse cipher.
+ */
+ for (i = 0; i <= ctx->Nr; i++) {
+ for (j = 0; j < ctx->Nb; j++) {
+ word32 temp;
+ temp = ctx->keysched[(ctx->Nr - i) * ctx->Nb + j];
+ if (i != 0 && i != ctx->Nr) {
+ /*
+ * Perform the InvMixColumn operation on i. The D
+ * tables give the result of InvMixColumn applied
+ * to Sboxinv on individual bytes, so we should
+ * compose Sbox with the D tables for this.
+ */
+ int a, b, c, d;
+ a = (temp >> 24) & 0xFF;
+ b = (temp >> 16) & 0xFF;
+ c = (temp >> 8) & 0xFF;
+ d = (temp >> 0) & 0xFF;
+ temp = D0[Sbox[a]];
+ temp ^= D1[Sbox[b]];
+ temp ^= D2[Sbox[c]];
+ temp ^= D3[Sbox[d]];
+ }
+ ctx->invkeysched[i * ctx->Nb + j] = temp;
+ }
+ }
+}
+
+static void aes_encrypt(AESContext * ctx, word32 * block)
+{
+ ctx->encrypt(ctx, block);
+}
+
+static void aes_decrypt(AESContext * ctx, word32 * block)
+{
+ ctx->decrypt(ctx, block);
+}
+
+static void aes_encrypt_cbc(unsigned char *blk, int len, AESContext * ctx)
+{
+ word32 iv[4];
+ int i;
+
+ assert((len & 15) == 0);
+
+ memcpy(iv, ctx->iv, sizeof(iv));
+
+ while (len > 0) {
+ for (i = 0; i < 4; i++)
+ iv[i] ^= GET_32BIT_MSB_FIRST(blk + 4 * i);
+ aes_encrypt(ctx, iv);
+ for (i = 0; i < 4; i++)
+ PUT_32BIT_MSB_FIRST(blk + 4 * i, iv[i]);
+ blk += 16;
+ len -= 16;
+ }
+
+ memcpy(ctx->iv, iv, sizeof(iv));
+}
+
+static void aes_decrypt_cbc(unsigned char *blk, int len, AESContext * ctx)
+{
+ word32 iv[4], x[4], ct[4];
+ int i;
+
+ assert((len & 15) == 0);
+
+ memcpy(iv, ctx->iv, sizeof(iv));
+
+ while (len > 0) {
+ for (i = 0; i < 4; i++)
+ x[i] = ct[i] = GET_32BIT_MSB_FIRST(blk + 4 * i);
+ aes_decrypt(ctx, x);
+ for (i = 0; i < 4; i++) {
+ PUT_32BIT_MSB_FIRST(blk + 4 * i, iv[i] ^ x[i]);
+ iv[i] = ct[i];
+ }
+ blk += 16;
+ len -= 16;
+ }
+
+ memcpy(ctx->iv, iv, sizeof(iv));
+}
+
+static void aes_sdctr(unsigned char *blk, int len, AESContext *ctx)
+{
+ word32 iv[4], b[4], tmp;
+ int i;
+
+ assert((len & 15) == 0);
+
+ memcpy(iv, ctx->iv, sizeof(iv));
+
+ while (len > 0) {
+ memcpy(b, iv, sizeof(b));
+ aes_encrypt(ctx, b);
+ for (i = 0; i < 4; i++) {
+ tmp = GET_32BIT_MSB_FIRST(blk + 4 * i);
+ PUT_32BIT_MSB_FIRST(blk + 4 * i, tmp ^ b[i]);
+ }
+ for (i = 3; i >= 0; i--)
+ if ((iv[i] = (iv[i] + 1) & 0xffffffff) != 0)
+ break;
+ blk += 16;
+ len -= 16;
+ }
+
+ memcpy(ctx->iv, iv, sizeof(iv));
+}
+
+void *aes_make_context(void)
+{
+ return snew(AESContext);
+}
+
+void aes_free_context(void *handle)
+{
+ sfree(handle);
+}
+
+void aes128_key(void *handle, unsigned char *key)
+{
+ AESContext *ctx = (AESContext *)handle;
+ aes_setup(ctx, 16, key, 16);
+}
+
+void aes192_key(void *handle, unsigned char *key)
+{
+ AESContext *ctx = (AESContext *)handle;
+ aes_setup(ctx, 16, key, 24);
+}
+
+void aes256_key(void *handle, unsigned char *key)
+{
+ AESContext *ctx = (AESContext *)handle;
+ aes_setup(ctx, 16, key, 32);
+}
+
+void aes_iv(void *handle, unsigned char *iv)
+{
+ AESContext *ctx = (AESContext *)handle;
+ int i;
+ for (i = 0; i < 4; i++)
+ ctx->iv[i] = GET_32BIT_MSB_FIRST(iv + 4 * i);
+}
+
+void aes_ssh2_encrypt_blk(void *handle, unsigned char *blk, int len)
+{
+ AESContext *ctx = (AESContext *)handle;
+ aes_encrypt_cbc(blk, len, ctx);
+}
+
+void aes_ssh2_decrypt_blk(void *handle, unsigned char *blk, int len)
+{
+ AESContext *ctx = (AESContext *)handle;
+ aes_decrypt_cbc(blk, len, ctx);
+}
+
+static void aes_ssh2_sdctr(void *handle, unsigned char *blk, int len)
+{
+ AESContext *ctx = (AESContext *)handle;
+ aes_sdctr(blk, len, ctx);
+}
+
+void aes256_encrypt_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_encrypt_cbc(blk, len, &ctx);
+ memset(&ctx, 0, 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));
+}
+
+static const struct ssh2_cipher ssh_aes128_ctr = {
+ aes_make_context, aes_free_context, aes_iv, aes128_key,
+ aes_ssh2_sdctr, aes_ssh2_sdctr,
+ "aes128-ctr",
+ 16, 128, 0, "AES-128 SDCTR"
+};
+
+static const struct ssh2_cipher ssh_aes192_ctr = {
+ aes_make_context, aes_free_context, aes_iv, aes192_key,
+ aes_ssh2_sdctr, aes_ssh2_sdctr,
+ "aes192-ctr",
+ 16, 192, 0, "AES-192 SDCTR"
+};
+
+static const struct ssh2_cipher ssh_aes256_ctr = {
+ aes_make_context, aes_free_context, aes_iv, aes256_key,
+ aes_ssh2_sdctr, aes_ssh2_sdctr,
+ "aes256-ctr",
+ 16, 256, 0, "AES-256 SDCTR"
+};
+
+static const struct ssh2_cipher ssh_aes128 = {
+ aes_make_context, aes_free_context, aes_iv, aes128_key,
+ aes_ssh2_encrypt_blk, aes_ssh2_decrypt_blk,
+ "aes128-cbc",
+ 16, 128, SSH_CIPHER_IS_CBC, "AES-128 CBC"
+};
+
+static const struct ssh2_cipher ssh_aes192 = {
+ aes_make_context, aes_free_context, aes_iv, aes192_key,
+ aes_ssh2_encrypt_blk, aes_ssh2_decrypt_blk,
+ "aes192-cbc",
+ 16, 192, SSH_CIPHER_IS_CBC, "AES-192 CBC"
+};
+
+static const struct ssh2_cipher ssh_aes256 = {
+ aes_make_context, aes_free_context, aes_iv, aes256_key,
+ aes_ssh2_encrypt_blk, aes_ssh2_decrypt_blk,
+ "aes256-cbc",
+ 16, 256, SSH_CIPHER_IS_CBC, "AES-256 CBC"
+};
+
+static const struct ssh2_cipher ssh_rijndael_lysator = {
+ aes_make_context, aes_free_context, aes_iv, aes256_key,
+ aes_ssh2_encrypt_blk, aes_ssh2_decrypt_blk,
+ "rijndael-cbc@lysator.liu.se",
+ 16, 256, SSH_CIPHER_IS_CBC, "AES-256 CBC"
+};
+
+static const struct ssh2_cipher *const aes_list[] = {
+ &ssh_aes256_ctr,
+ &ssh_aes256,
+ &ssh_rijndael_lysator,
+ &ssh_aes192_ctr,
+ &ssh_aes192,
+ &ssh_aes128_ctr,
+ &ssh_aes128,
+};
+
+const struct ssh2_ciphers ssh2_aes = {
+ sizeof(aes_list) / sizeof(*aes_list),
+ aes_list
+};
--- /dev/null
+/*
+ * Arcfour (RC4) implementation for PuTTY.
+ *
+ * Coded from Schneier.
+ */
+
+#include <assert.h>
+#include "ssh.h"
+
+typedef struct {
+ unsigned char i, j, s[256];
+} ArcfourContext;
+
+static void arcfour_block(void *handle, unsigned char *blk, int len)
+{
+ ArcfourContext *ctx = (ArcfourContext *)handle;
+ unsigned k;
+ unsigned char tmp, i, j, *s;
+
+ s = ctx->s;
+ i = ctx->i; j = ctx->j;
+ for (k = 0; (int)k < len; k++) {
+ i = (i + 1) & 0xff;
+ j = (j + s[i]) & 0xff;
+ tmp = s[i]; s[i] = s[j]; s[j] = tmp;
+ blk[k] ^= s[(s[i]+s[j]) & 0xff];
+ }
+ ctx->i = i; ctx->j = j;
+}
+
+static void arcfour_setkey(ArcfourContext *ctx, unsigned char const *key,
+ unsigned keybytes)
+{
+ unsigned char tmp, k[256], *s;
+ unsigned i, j;
+
+ s = ctx->s;
+ assert(keybytes <= 256);
+ ctx->i = ctx->j = 0;
+ for (i = 0; i < 256; i++) {
+ s[i] = i;
+ k[i] = key[i % keybytes];
+ }
+ j = 0;
+ for (i = 0; i < 256; i++) {
+ j = (j + s[i] + k[i]) & 0xff;
+ tmp = s[i]; s[i] = s[j]; s[j] = tmp;
+ }
+}
+
+/* -- Interface with PuTTY -- */
+
+/*
+ * We don't implement Arcfour in SSH-1 because it's utterly insecure in
+ * several ways. See CERT Vulnerability Notes VU#25309, VU#665372,
+ * and VU#565052.
+ *
+ * We don't implement the "arcfour" algorithm in SSH-2 because it doesn't
+ * stir the cipher state before emitting keystream, and hence is likely
+ * to leak data about the key.
+ */
+
+static void *arcfour_make_context(void)
+{
+ return snew(ArcfourContext);
+}
+
+static void arcfour_free_context(void *handle)
+{
+ sfree(handle);
+}
+
+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);
+ sfree(junk);
+}
+
+static void arcfour128_key(void *handle, unsigned char *key)
+{
+ ArcfourContext *ctx = (ArcfourContext *)handle;
+ arcfour_setkey(ctx, key, 16);
+ arcfour_stir(ctx);
+}
+
+static void arcfour256_key(void *handle, unsigned char *key)
+{
+ ArcfourContext *ctx = (ArcfourContext *)handle;
+ arcfour_setkey(ctx, key, 32);
+ arcfour_stir(ctx);
+}
+
+static void arcfour_iv(void *handle, unsigned char *key)
+{
+
+}
+
+const struct ssh2_cipher ssh_arcfour128_ssh2 = {
+ arcfour_make_context, arcfour_free_context, arcfour_iv, arcfour128_key,
+ arcfour_block, arcfour_block,
+ "arcfour128",
+ 1, 128, 0, "Arcfour-128"
+};
+
+const struct ssh2_cipher ssh_arcfour256_ssh2 = {
+ arcfour_make_context, arcfour_free_context, arcfour_iv, arcfour256_key,
+ arcfour_block, arcfour_block,
+ "arcfour256",
+ 1, 256, 0, "Arcfour-256"
+};
+
+static const struct ssh2_cipher *const arcfour_list[] = {
+ &ssh_arcfour256_ssh2,
+ &ssh_arcfour128_ssh2,
+};
+
+const struct ssh2_ciphers ssh2_arcfour = {
+ sizeof(arcfour_list) / sizeof(*arcfour_list),
+ arcfour_list
+};
--- /dev/null
+/*
+ * Blowfish implementation for PuTTY.
+ *
+ * Coded from scratch from the algorithm description.
+ */
+
+#include <assert.h>
+#include <stdio.h>
+#include "ssh.h"
+
+typedef struct {
+ word32 S0[256], S1[256], S2[256], S3[256], P[18];
+ word32 iv0, iv1; /* for CBC mode */
+} BlowfishContext;
+
+/*
+ * The Blowfish init data: hex digits of the fractional part of pi.
+ * (ie pi as a hex fraction is 3.243F6A8885A308D3...)
+ */
+static const word32 parray[] = {
+ 0x243F6A88, 0x85A308D3, 0x13198A2E, 0x03707344, 0xA4093822, 0x299F31D0,
+ 0x082EFA98, 0xEC4E6C89, 0x452821E6, 0x38D01377, 0xBE5466CF, 0x34E90C6C,
+ 0xC0AC29B7, 0xC97C50DD, 0x3F84D5B5, 0xB5470917, 0x9216D5D9, 0x8979FB1B,
+};
+
+static const word32 sbox0[] = {
+ 0xD1310BA6, 0x98DFB5AC, 0x2FFD72DB, 0xD01ADFB7, 0xB8E1AFED, 0x6A267E96,
+ 0xBA7C9045, 0xF12C7F99, 0x24A19947, 0xB3916CF7, 0x0801F2E2, 0x858EFC16,
+ 0x636920D8, 0x71574E69, 0xA458FEA3, 0xF4933D7E, 0x0D95748F, 0x728EB658,
+ 0x718BCD58, 0x82154AEE, 0x7B54A41D, 0xC25A59B5, 0x9C30D539, 0x2AF26013,
+ 0xC5D1B023, 0x286085F0, 0xCA417918, 0xB8DB38EF, 0x8E79DCB0, 0x603A180E,
+ 0x6C9E0E8B, 0xB01E8A3E, 0xD71577C1, 0xBD314B27, 0x78AF2FDA, 0x55605C60,
+ 0xE65525F3, 0xAA55AB94, 0x57489862, 0x63E81440, 0x55CA396A, 0x2AAB10B6,
+ 0xB4CC5C34, 0x1141E8CE, 0xA15486AF, 0x7C72E993, 0xB3EE1411, 0x636FBC2A,
+ 0x2BA9C55D, 0x741831F6, 0xCE5C3E16, 0x9B87931E, 0xAFD6BA33, 0x6C24CF5C,
+ 0x7A325381, 0x28958677, 0x3B8F4898, 0x6B4BB9AF, 0xC4BFE81B, 0x66282193,
+ 0x61D809CC, 0xFB21A991, 0x487CAC60, 0x5DEC8032, 0xEF845D5D, 0xE98575B1,
+ 0xDC262302, 0xEB651B88, 0x23893E81, 0xD396ACC5, 0x0F6D6FF3, 0x83F44239,
+ 0x2E0B4482, 0xA4842004, 0x69C8F04A, 0x9E1F9B5E, 0x21C66842, 0xF6E96C9A,
+ 0x670C9C61, 0xABD388F0, 0x6A51A0D2, 0xD8542F68, 0x960FA728, 0xAB5133A3,
+ 0x6EEF0B6C, 0x137A3BE4, 0xBA3BF050, 0x7EFB2A98, 0xA1F1651D, 0x39AF0176,
+ 0x66CA593E, 0x82430E88, 0x8CEE8619, 0x456F9FB4, 0x7D84A5C3, 0x3B8B5EBE,
+ 0xE06F75D8, 0x85C12073, 0x401A449F, 0x56C16AA6, 0x4ED3AA62, 0x363F7706,
+ 0x1BFEDF72, 0x429B023D, 0x37D0D724, 0xD00A1248, 0xDB0FEAD3, 0x49F1C09B,
+ 0x075372C9, 0x80991B7B, 0x25D479D8, 0xF6E8DEF7, 0xE3FE501A, 0xB6794C3B,
+ 0x976CE0BD, 0x04C006BA, 0xC1A94FB6, 0x409F60C4, 0x5E5C9EC2, 0x196A2463,
+ 0x68FB6FAF, 0x3E6C53B5, 0x1339B2EB, 0x3B52EC6F, 0x6DFC511F, 0x9B30952C,
+ 0xCC814544, 0xAF5EBD09, 0xBEE3D004, 0xDE334AFD, 0x660F2807, 0x192E4BB3,
+ 0xC0CBA857, 0x45C8740F, 0xD20B5F39, 0xB9D3FBDB, 0x5579C0BD, 0x1A60320A,
+ 0xD6A100C6, 0x402C7279, 0x679F25FE, 0xFB1FA3CC, 0x8EA5E9F8, 0xDB3222F8,
+ 0x3C7516DF, 0xFD616B15, 0x2F501EC8, 0xAD0552AB, 0x323DB5FA, 0xFD238760,
+ 0x53317B48, 0x3E00DF82, 0x9E5C57BB, 0xCA6F8CA0, 0x1A87562E, 0xDF1769DB,
+ 0xD542A8F6, 0x287EFFC3, 0xAC6732C6, 0x8C4F5573, 0x695B27B0, 0xBBCA58C8,
+ 0xE1FFA35D, 0xB8F011A0, 0x10FA3D98, 0xFD2183B8, 0x4AFCB56C, 0x2DD1D35B,
+ 0x9A53E479, 0xB6F84565, 0xD28E49BC, 0x4BFB9790, 0xE1DDF2DA, 0xA4CB7E33,
+ 0x62FB1341, 0xCEE4C6E8, 0xEF20CADA, 0x36774C01, 0xD07E9EFE, 0x2BF11FB4,
+ 0x95DBDA4D, 0xAE909198, 0xEAAD8E71, 0x6B93D5A0, 0xD08ED1D0, 0xAFC725E0,
+ 0x8E3C5B2F, 0x8E7594B7, 0x8FF6E2FB, 0xF2122B64, 0x8888B812, 0x900DF01C,
+ 0x4FAD5EA0, 0x688FC31C, 0xD1CFF191, 0xB3A8C1AD, 0x2F2F2218, 0xBE0E1777,
+ 0xEA752DFE, 0x8B021FA1, 0xE5A0CC0F, 0xB56F74E8, 0x18ACF3D6, 0xCE89E299,
+ 0xB4A84FE0, 0xFD13E0B7, 0x7CC43B81, 0xD2ADA8D9, 0x165FA266, 0x80957705,
+ 0x93CC7314, 0x211A1477, 0xE6AD2065, 0x77B5FA86, 0xC75442F5, 0xFB9D35CF,
+ 0xEBCDAF0C, 0x7B3E89A0, 0xD6411BD3, 0xAE1E7E49, 0x00250E2D, 0x2071B35E,
+ 0x226800BB, 0x57B8E0AF, 0x2464369B, 0xF009B91E, 0x5563911D, 0x59DFA6AA,
+ 0x78C14389, 0xD95A537F, 0x207D5BA2, 0x02E5B9C5, 0x83260376, 0x6295CFA9,
+ 0x11C81968, 0x4E734A41, 0xB3472DCA, 0x7B14A94A, 0x1B510052, 0x9A532915,
+ 0xD60F573F, 0xBC9BC6E4, 0x2B60A476, 0x81E67400, 0x08BA6FB5, 0x571BE91F,
+ 0xF296EC6B, 0x2A0DD915, 0xB6636521, 0xE7B9F9B6, 0xFF34052E, 0xC5855664,
+ 0x53B02D5D, 0xA99F8FA1, 0x08BA4799, 0x6E85076A,
+};
+
+static const word32 sbox1[] = {
+ 0x4B7A70E9, 0xB5B32944, 0xDB75092E, 0xC4192623, 0xAD6EA6B0, 0x49A7DF7D,
+ 0x9CEE60B8, 0x8FEDB266, 0xECAA8C71, 0x699A17FF, 0x5664526C, 0xC2B19EE1,
+ 0x193602A5, 0x75094C29, 0xA0591340, 0xE4183A3E, 0x3F54989A, 0x5B429D65,
+ 0x6B8FE4D6, 0x99F73FD6, 0xA1D29C07, 0xEFE830F5, 0x4D2D38E6, 0xF0255DC1,
+ 0x4CDD2086, 0x8470EB26, 0x6382E9C6, 0x021ECC5E, 0x09686B3F, 0x3EBAEFC9,
+ 0x3C971814, 0x6B6A70A1, 0x687F3584, 0x52A0E286, 0xB79C5305, 0xAA500737,
+ 0x3E07841C, 0x7FDEAE5C, 0x8E7D44EC, 0x5716F2B8, 0xB03ADA37, 0xF0500C0D,
+ 0xF01C1F04, 0x0200B3FF, 0xAE0CF51A, 0x3CB574B2, 0x25837A58, 0xDC0921BD,
+ 0xD19113F9, 0x7CA92FF6, 0x94324773, 0x22F54701, 0x3AE5E581, 0x37C2DADC,
+ 0xC8B57634, 0x9AF3DDA7, 0xA9446146, 0x0FD0030E, 0xECC8C73E, 0xA4751E41,
+ 0xE238CD99, 0x3BEA0E2F, 0x3280BBA1, 0x183EB331, 0x4E548B38, 0x4F6DB908,
+ 0x6F420D03, 0xF60A04BF, 0x2CB81290, 0x24977C79, 0x5679B072, 0xBCAF89AF,
+ 0xDE9A771F, 0xD9930810, 0xB38BAE12, 0xDCCF3F2E, 0x5512721F, 0x2E6B7124,
+ 0x501ADDE6, 0x9F84CD87, 0x7A584718, 0x7408DA17, 0xBC9F9ABC, 0xE94B7D8C,
+ 0xEC7AEC3A, 0xDB851DFA, 0x63094366, 0xC464C3D2, 0xEF1C1847, 0x3215D908,
+ 0xDD433B37, 0x24C2BA16, 0x12A14D43, 0x2A65C451, 0x50940002, 0x133AE4DD,
+ 0x71DFF89E, 0x10314E55, 0x81AC77D6, 0x5F11199B, 0x043556F1, 0xD7A3C76B,
+ 0x3C11183B, 0x5924A509, 0xF28FE6ED, 0x97F1FBFA, 0x9EBABF2C, 0x1E153C6E,
+ 0x86E34570, 0xEAE96FB1, 0x860E5E0A, 0x5A3E2AB3, 0x771FE71C, 0x4E3D06FA,
+ 0x2965DCB9, 0x99E71D0F, 0x803E89D6, 0x5266C825, 0x2E4CC978, 0x9C10B36A,
+ 0xC6150EBA, 0x94E2EA78, 0xA5FC3C53, 0x1E0A2DF4, 0xF2F74EA7, 0x361D2B3D,
+ 0x1939260F, 0x19C27960, 0x5223A708, 0xF71312B6, 0xEBADFE6E, 0xEAC31F66,
+ 0xE3BC4595, 0xA67BC883, 0xB17F37D1, 0x018CFF28, 0xC332DDEF, 0xBE6C5AA5,
+ 0x65582185, 0x68AB9802, 0xEECEA50F, 0xDB2F953B, 0x2AEF7DAD, 0x5B6E2F84,
+ 0x1521B628, 0x29076170, 0xECDD4775, 0x619F1510, 0x13CCA830, 0xEB61BD96,
+ 0x0334FE1E, 0xAA0363CF, 0xB5735C90, 0x4C70A239, 0xD59E9E0B, 0xCBAADE14,
+ 0xEECC86BC, 0x60622CA7, 0x9CAB5CAB, 0xB2F3846E, 0x648B1EAF, 0x19BDF0CA,
+ 0xA02369B9, 0x655ABB50, 0x40685A32, 0x3C2AB4B3, 0x319EE9D5, 0xC021B8F7,
+ 0x9B540B19, 0x875FA099, 0x95F7997E, 0x623D7DA8, 0xF837889A, 0x97E32D77,
+ 0x11ED935F, 0x16681281, 0x0E358829, 0xC7E61FD6, 0x96DEDFA1, 0x7858BA99,
+ 0x57F584A5, 0x1B227263, 0x9B83C3FF, 0x1AC24696, 0xCDB30AEB, 0x532E3054,
+ 0x8FD948E4, 0x6DBC3128, 0x58EBF2EF, 0x34C6FFEA, 0xFE28ED61, 0xEE7C3C73,
+ 0x5D4A14D9, 0xE864B7E3, 0x42105D14, 0x203E13E0, 0x45EEE2B6, 0xA3AAABEA,
+ 0xDB6C4F15, 0xFACB4FD0, 0xC742F442, 0xEF6ABBB5, 0x654F3B1D, 0x41CD2105,
+ 0xD81E799E, 0x86854DC7, 0xE44B476A, 0x3D816250, 0xCF62A1F2, 0x5B8D2646,
+ 0xFC8883A0, 0xC1C7B6A3, 0x7F1524C3, 0x69CB7492, 0x47848A0B, 0x5692B285,
+ 0x095BBF00, 0xAD19489D, 0x1462B174, 0x23820E00, 0x58428D2A, 0x0C55F5EA,
+ 0x1DADF43E, 0x233F7061, 0x3372F092, 0x8D937E41, 0xD65FECF1, 0x6C223BDB,
+ 0x7CDE3759, 0xCBEE7460, 0x4085F2A7, 0xCE77326E, 0xA6078084, 0x19F8509E,
+ 0xE8EFD855, 0x61D99735, 0xA969A7AA, 0xC50C06C2, 0x5A04ABFC, 0x800BCADC,
+ 0x9E447A2E, 0xC3453484, 0xFDD56705, 0x0E1E9EC9, 0xDB73DBD3, 0x105588CD,
+ 0x675FDA79, 0xE3674340, 0xC5C43465, 0x713E38D8, 0x3D28F89E, 0xF16DFF20,
+ 0x153E21E7, 0x8FB03D4A, 0xE6E39F2B, 0xDB83ADF7,
+};
+
+static const word32 sbox2[] = {
+ 0xE93D5A68, 0x948140F7, 0xF64C261C, 0x94692934, 0x411520F7, 0x7602D4F7,
+ 0xBCF46B2E, 0xD4A20068, 0xD4082471, 0x3320F46A, 0x43B7D4B7, 0x500061AF,
+ 0x1E39F62E, 0x97244546, 0x14214F74, 0xBF8B8840, 0x4D95FC1D, 0x96B591AF,
+ 0x70F4DDD3, 0x66A02F45, 0xBFBC09EC, 0x03BD9785, 0x7FAC6DD0, 0x31CB8504,
+ 0x96EB27B3, 0x55FD3941, 0xDA2547E6, 0xABCA0A9A, 0x28507825, 0x530429F4,
+ 0x0A2C86DA, 0xE9B66DFB, 0x68DC1462, 0xD7486900, 0x680EC0A4, 0x27A18DEE,
+ 0x4F3FFEA2, 0xE887AD8C, 0xB58CE006, 0x7AF4D6B6, 0xAACE1E7C, 0xD3375FEC,
+ 0xCE78A399, 0x406B2A42, 0x20FE9E35, 0xD9F385B9, 0xEE39D7AB, 0x3B124E8B,
+ 0x1DC9FAF7, 0x4B6D1856, 0x26A36631, 0xEAE397B2, 0x3A6EFA74, 0xDD5B4332,
+ 0x6841E7F7, 0xCA7820FB, 0xFB0AF54E, 0xD8FEB397, 0x454056AC, 0xBA489527,
+ 0x55533A3A, 0x20838D87, 0xFE6BA9B7, 0xD096954B, 0x55A867BC, 0xA1159A58,
+ 0xCCA92963, 0x99E1DB33, 0xA62A4A56, 0x3F3125F9, 0x5EF47E1C, 0x9029317C,
+ 0xFDF8E802, 0x04272F70, 0x80BB155C, 0x05282CE3, 0x95C11548, 0xE4C66D22,
+ 0x48C1133F, 0xC70F86DC, 0x07F9C9EE, 0x41041F0F, 0x404779A4, 0x5D886E17,
+ 0x325F51EB, 0xD59BC0D1, 0xF2BCC18F, 0x41113564, 0x257B7834, 0x602A9C60,
+ 0xDFF8E8A3, 0x1F636C1B, 0x0E12B4C2, 0x02E1329E, 0xAF664FD1, 0xCAD18115,
+ 0x6B2395E0, 0x333E92E1, 0x3B240B62, 0xEEBEB922, 0x85B2A20E, 0xE6BA0D99,
+ 0xDE720C8C, 0x2DA2F728, 0xD0127845, 0x95B794FD, 0x647D0862, 0xE7CCF5F0,
+ 0x5449A36F, 0x877D48FA, 0xC39DFD27, 0xF33E8D1E, 0x0A476341, 0x992EFF74,
+ 0x3A6F6EAB, 0xF4F8FD37, 0xA812DC60, 0xA1EBDDF8, 0x991BE14C, 0xDB6E6B0D,
+ 0xC67B5510, 0x6D672C37, 0x2765D43B, 0xDCD0E804, 0xF1290DC7, 0xCC00FFA3,
+ 0xB5390F92, 0x690FED0B, 0x667B9FFB, 0xCEDB7D9C, 0xA091CF0B, 0xD9155EA3,
+ 0xBB132F88, 0x515BAD24, 0x7B9479BF, 0x763BD6EB, 0x37392EB3, 0xCC115979,
+ 0x8026E297, 0xF42E312D, 0x6842ADA7, 0xC66A2B3B, 0x12754CCC, 0x782EF11C,
+ 0x6A124237, 0xB79251E7, 0x06A1BBE6, 0x4BFB6350, 0x1A6B1018, 0x11CAEDFA,
+ 0x3D25BDD8, 0xE2E1C3C9, 0x44421659, 0x0A121386, 0xD90CEC6E, 0xD5ABEA2A,
+ 0x64AF674E, 0xDA86A85F, 0xBEBFE988, 0x64E4C3FE, 0x9DBC8057, 0xF0F7C086,
+ 0x60787BF8, 0x6003604D, 0xD1FD8346, 0xF6381FB0, 0x7745AE04, 0xD736FCCC,
+ 0x83426B33, 0xF01EAB71, 0xB0804187, 0x3C005E5F, 0x77A057BE, 0xBDE8AE24,
+ 0x55464299, 0xBF582E61, 0x4E58F48F, 0xF2DDFDA2, 0xF474EF38, 0x8789BDC2,
+ 0x5366F9C3, 0xC8B38E74, 0xB475F255, 0x46FCD9B9, 0x7AEB2661, 0x8B1DDF84,
+ 0x846A0E79, 0x915F95E2, 0x466E598E, 0x20B45770, 0x8CD55591, 0xC902DE4C,
+ 0xB90BACE1, 0xBB8205D0, 0x11A86248, 0x7574A99E, 0xB77F19B6, 0xE0A9DC09,
+ 0x662D09A1, 0xC4324633, 0xE85A1F02, 0x09F0BE8C, 0x4A99A025, 0x1D6EFE10,
+ 0x1AB93D1D, 0x0BA5A4DF, 0xA186F20F, 0x2868F169, 0xDCB7DA83, 0x573906FE,
+ 0xA1E2CE9B, 0x4FCD7F52, 0x50115E01, 0xA70683FA, 0xA002B5C4, 0x0DE6D027,
+ 0x9AF88C27, 0x773F8641, 0xC3604C06, 0x61A806B5, 0xF0177A28, 0xC0F586E0,
+ 0x006058AA, 0x30DC7D62, 0x11E69ED7, 0x2338EA63, 0x53C2DD94, 0xC2C21634,
+ 0xBBCBEE56, 0x90BCB6DE, 0xEBFC7DA1, 0xCE591D76, 0x6F05E409, 0x4B7C0188,
+ 0x39720A3D, 0x7C927C24, 0x86E3725F, 0x724D9DB9, 0x1AC15BB4, 0xD39EB8FC,
+ 0xED545578, 0x08FCA5B5, 0xD83D7CD3, 0x4DAD0FC4, 0x1E50EF5E, 0xB161E6F8,
+ 0xA28514D9, 0x6C51133C, 0x6FD5C7E7, 0x56E14EC4, 0x362ABFCE, 0xDDC6C837,
+ 0xD79A3234, 0x92638212, 0x670EFA8E, 0x406000E0,
+};
+
+static const word32 sbox3[] = {
+ 0x3A39CE37, 0xD3FAF5CF, 0xABC27737, 0x5AC52D1B, 0x5CB0679E, 0x4FA33742,
+ 0xD3822740, 0x99BC9BBE, 0xD5118E9D, 0xBF0F7315, 0xD62D1C7E, 0xC700C47B,
+ 0xB78C1B6B, 0x21A19045, 0xB26EB1BE, 0x6A366EB4, 0x5748AB2F, 0xBC946E79,
+ 0xC6A376D2, 0x6549C2C8, 0x530FF8EE, 0x468DDE7D, 0xD5730A1D, 0x4CD04DC6,
+ 0x2939BBDB, 0xA9BA4650, 0xAC9526E8, 0xBE5EE304, 0xA1FAD5F0, 0x6A2D519A,
+ 0x63EF8CE2, 0x9A86EE22, 0xC089C2B8, 0x43242EF6, 0xA51E03AA, 0x9CF2D0A4,
+ 0x83C061BA, 0x9BE96A4D, 0x8FE51550, 0xBA645BD6, 0x2826A2F9, 0xA73A3AE1,
+ 0x4BA99586, 0xEF5562E9, 0xC72FEFD3, 0xF752F7DA, 0x3F046F69, 0x77FA0A59,
+ 0x80E4A915, 0x87B08601, 0x9B09E6AD, 0x3B3EE593, 0xE990FD5A, 0x9E34D797,
+ 0x2CF0B7D9, 0x022B8B51, 0x96D5AC3A, 0x017DA67D, 0xD1CF3ED6, 0x7C7D2D28,
+ 0x1F9F25CF, 0xADF2B89B, 0x5AD6B472, 0x5A88F54C, 0xE029AC71, 0xE019A5E6,
+ 0x47B0ACFD, 0xED93FA9B, 0xE8D3C48D, 0x283B57CC, 0xF8D56629, 0x79132E28,
+ 0x785F0191, 0xED756055, 0xF7960E44, 0xE3D35E8C, 0x15056DD4, 0x88F46DBA,
+ 0x03A16125, 0x0564F0BD, 0xC3EB9E15, 0x3C9057A2, 0x97271AEC, 0xA93A072A,
+ 0x1B3F6D9B, 0x1E6321F5, 0xF59C66FB, 0x26DCF319, 0x7533D928, 0xB155FDF5,
+ 0x03563482, 0x8ABA3CBB, 0x28517711, 0xC20AD9F8, 0xABCC5167, 0xCCAD925F,
+ 0x4DE81751, 0x3830DC8E, 0x379D5862, 0x9320F991, 0xEA7A90C2, 0xFB3E7BCE,
+ 0x5121CE64, 0x774FBE32, 0xA8B6E37E, 0xC3293D46, 0x48DE5369, 0x6413E680,
+ 0xA2AE0810, 0xDD6DB224, 0x69852DFD, 0x09072166, 0xB39A460A, 0x6445C0DD,
+ 0x586CDECF, 0x1C20C8AE, 0x5BBEF7DD, 0x1B588D40, 0xCCD2017F, 0x6BB4E3BB,
+ 0xDDA26A7E, 0x3A59FF45, 0x3E350A44, 0xBCB4CDD5, 0x72EACEA8, 0xFA6484BB,
+ 0x8D6612AE, 0xBF3C6F47, 0xD29BE463, 0x542F5D9E, 0xAEC2771B, 0xF64E6370,
+ 0x740E0D8D, 0xE75B1357, 0xF8721671, 0xAF537D5D, 0x4040CB08, 0x4EB4E2CC,
+ 0x34D2466A, 0x0115AF84, 0xE1B00428, 0x95983A1D, 0x06B89FB4, 0xCE6EA048,
+ 0x6F3F3B82, 0x3520AB82, 0x011A1D4B, 0x277227F8, 0x611560B1, 0xE7933FDC,
+ 0xBB3A792B, 0x344525BD, 0xA08839E1, 0x51CE794B, 0x2F32C9B7, 0xA01FBAC9,
+ 0xE01CC87E, 0xBCC7D1F6, 0xCF0111C3, 0xA1E8AAC7, 0x1A908749, 0xD44FBD9A,
+ 0xD0DADECB, 0xD50ADA38, 0x0339C32A, 0xC6913667, 0x8DF9317C, 0xE0B12B4F,
+ 0xF79E59B7, 0x43F5BB3A, 0xF2D519FF, 0x27D9459C, 0xBF97222C, 0x15E6FC2A,
+ 0x0F91FC71, 0x9B941525, 0xFAE59361, 0xCEB69CEB, 0xC2A86459, 0x12BAA8D1,
+ 0xB6C1075E, 0xE3056A0C, 0x10D25065, 0xCB03A442, 0xE0EC6E0E, 0x1698DB3B,
+ 0x4C98A0BE, 0x3278E964, 0x9F1F9532, 0xE0D392DF, 0xD3A0342B, 0x8971F21E,
+ 0x1B0A7441, 0x4BA3348C, 0xC5BE7120, 0xC37632D8, 0xDF359F8D, 0x9B992F2E,
+ 0xE60B6F47, 0x0FE3F11D, 0xE54CDA54, 0x1EDAD891, 0xCE6279CF, 0xCD3E7E6F,
+ 0x1618B166, 0xFD2C1D05, 0x848FD2C5, 0xF6FB2299, 0xF523F357, 0xA6327623,
+ 0x93A83531, 0x56CCCD02, 0xACF08162, 0x5A75EBB5, 0x6E163697, 0x88D273CC,
+ 0xDE966292, 0x81B949D0, 0x4C50901B, 0x71C65614, 0xE6C6C7BD, 0x327A140A,
+ 0x45E1D006, 0xC3F27B9A, 0xC9AA53FD, 0x62A80F00, 0xBB25BFE2, 0x35BDD2F6,
+ 0x71126905, 0xB2040222, 0xB6CBCF7C, 0xCD769C2B, 0x53113EC0, 0x1640E3D3,
+ 0x38ABBD60, 0x2547ADF0, 0xBA38209C, 0xF746CE76, 0x77AFA1C5, 0x20756060,
+ 0x85CBFE4E, 0x8AE88DD8, 0x7AAAF9B0, 0x4CF9AA7E, 0x1948C25C, 0x02FB8A8C,
+ 0x01C36AE4, 0xD6EBE1F9, 0x90D4F869, 0xA65CDEA0, 0x3F09252D, 0xC208E69F,
+ 0xB74E6132, 0xCE77E25B, 0x578FDFE3, 0x3AC372E6,
+};
+
+#define Fprime(a,b,c,d) ( ( (S0[a] + S1[b]) ^ S2[c] ) + S3[d] )
+#define F(x) Fprime( ((x>>24)&0xFF), ((x>>16)&0xFF), ((x>>8)&0xFF), (x&0xFF) )
+#define ROUND(n) ( xL ^= P[n], t = xL, xL = F(xL) ^ xR, xR = t )
+
+static void blowfish_encrypt(word32 xL, word32 xR, word32 * output,
+ BlowfishContext * ctx)
+{
+ word32 *S0 = ctx->S0;
+ word32 *S1 = ctx->S1;
+ word32 *S2 = ctx->S2;
+ word32 *S3 = ctx->S3;
+ word32 *P = ctx->P;
+ word32 t;
+
+ ROUND(0);
+ ROUND(1);
+ ROUND(2);
+ ROUND(3);
+ ROUND(4);
+ ROUND(5);
+ ROUND(6);
+ ROUND(7);
+ ROUND(8);
+ ROUND(9);
+ ROUND(10);
+ ROUND(11);
+ ROUND(12);
+ ROUND(13);
+ ROUND(14);
+ ROUND(15);
+ xL ^= P[16];
+ xR ^= P[17];
+
+ output[0] = xR;
+ output[1] = xL;
+}
+
+static void blowfish_decrypt(word32 xL, word32 xR, word32 * output,
+ BlowfishContext * ctx)
+{
+ word32 *S0 = ctx->S0;
+ word32 *S1 = ctx->S1;
+ word32 *S2 = ctx->S2;
+ word32 *S3 = ctx->S3;
+ word32 *P = ctx->P;
+ word32 t;
+
+ ROUND(17);
+ ROUND(16);
+ ROUND(15);
+ ROUND(14);
+ ROUND(13);
+ ROUND(12);
+ ROUND(11);
+ ROUND(10);
+ ROUND(9);
+ ROUND(8);
+ ROUND(7);
+ ROUND(6);
+ ROUND(5);
+ ROUND(4);
+ ROUND(3);
+ ROUND(2);
+ xL ^= P[1];
+ xR ^= P[0];
+
+ output[0] = xR;
+ output[1] = xL;
+}
+
+static void blowfish_lsb_encrypt_cbc(unsigned char *blk, int len,
+ BlowfishContext * ctx)
+{
+ word32 xL, xR, out[2], iv0, iv1;
+
+ assert((len & 7) == 0);
+
+ iv0 = ctx->iv0;
+ iv1 = ctx->iv1;
+
+ while (len > 0) {
+ xL = GET_32BIT_LSB_FIRST(blk);
+ xR = GET_32BIT_LSB_FIRST(blk + 4);
+ iv0 ^= xL;
+ iv1 ^= xR;
+ blowfish_encrypt(iv0, iv1, out, ctx);
+ iv0 = out[0];
+ iv1 = out[1];
+ PUT_32BIT_LSB_FIRST(blk, iv0);
+ PUT_32BIT_LSB_FIRST(blk + 4, iv1);
+ blk += 8;
+ len -= 8;
+ }
+
+ ctx->iv0 = iv0;
+ ctx->iv1 = iv1;
+}
+
+static void blowfish_lsb_decrypt_cbc(unsigned char *blk, int len,
+ BlowfishContext * ctx)
+{
+ word32 xL, xR, out[2], iv0, iv1;
+
+ assert((len & 7) == 0);
+
+ iv0 = ctx->iv0;
+ iv1 = ctx->iv1;
+
+ while (len > 0) {
+ xL = GET_32BIT_LSB_FIRST(blk);
+ xR = GET_32BIT_LSB_FIRST(blk + 4);
+ blowfish_decrypt(xL, xR, out, ctx);
+ iv0 ^= out[0];
+ iv1 ^= out[1];
+ PUT_32BIT_LSB_FIRST(blk, iv0);
+ PUT_32BIT_LSB_FIRST(blk + 4, iv1);
+ iv0 = xL;
+ iv1 = xR;
+ blk += 8;
+ len -= 8;
+ }
+
+ ctx->iv0 = iv0;
+ ctx->iv1 = iv1;
+}
+
+static void blowfish_msb_encrypt_cbc(unsigned char *blk, int len,
+ BlowfishContext * ctx)
+{
+ word32 xL, xR, out[2], iv0, iv1;
+
+ assert((len & 7) == 0);
+
+ iv0 = ctx->iv0;
+ iv1 = ctx->iv1;
+
+ while (len > 0) {
+ xL = GET_32BIT_MSB_FIRST(blk);
+ xR = GET_32BIT_MSB_FIRST(blk + 4);
+ iv0 ^= xL;
+ iv1 ^= xR;
+ blowfish_encrypt(iv0, iv1, out, ctx);
+ iv0 = out[0];
+ iv1 = out[1];
+ PUT_32BIT_MSB_FIRST(blk, iv0);
+ PUT_32BIT_MSB_FIRST(blk + 4, iv1);
+ blk += 8;
+ len -= 8;
+ }
+
+ ctx->iv0 = iv0;
+ ctx->iv1 = iv1;
+}
+
+static void blowfish_msb_decrypt_cbc(unsigned char *blk, int len,
+ BlowfishContext * ctx)
+{
+ word32 xL, xR, out[2], iv0, iv1;
+
+ assert((len & 7) == 0);
+
+ iv0 = ctx->iv0;
+ iv1 = ctx->iv1;
+
+ while (len > 0) {
+ xL = GET_32BIT_MSB_FIRST(blk);
+ xR = GET_32BIT_MSB_FIRST(blk + 4);
+ blowfish_decrypt(xL, xR, out, ctx);
+ iv0 ^= out[0];
+ iv1 ^= out[1];
+ PUT_32BIT_MSB_FIRST(blk, iv0);
+ PUT_32BIT_MSB_FIRST(blk + 4, iv1);
+ iv0 = xL;
+ iv1 = xR;
+ blk += 8;
+ len -= 8;
+ }
+
+ ctx->iv0 = iv0;
+ ctx->iv1 = iv1;
+}
+
+static void blowfish_msb_sdctr(unsigned char *blk, int len,
+ BlowfishContext * ctx)
+{
+ word32 b[2], iv0, iv1, tmp;
+
+ assert((len & 7) == 0);
+
+ iv0 = ctx->iv0;
+ iv1 = ctx->iv1;
+
+ while (len > 0) {
+ blowfish_encrypt(iv0, iv1, b, ctx);
+ tmp = GET_32BIT_MSB_FIRST(blk);
+ PUT_32BIT_MSB_FIRST(blk, tmp ^ b[0]);
+ tmp = GET_32BIT_MSB_FIRST(blk + 4);
+ PUT_32BIT_MSB_FIRST(blk + 4, tmp ^ b[1]);
+ if ((iv1 = (iv1 + 1) & 0xffffffff) == 0)
+ iv0 = (iv0 + 1) & 0xffffffff;
+ blk += 8;
+ len -= 8;
+ }
+
+ ctx->iv0 = iv0;
+ ctx->iv1 = iv1;
+}
+
+static void blowfish_setkey(BlowfishContext * ctx,
+ const unsigned char *key, short keybytes)
+{
+ word32 *S0 = ctx->S0;
+ word32 *S1 = ctx->S1;
+ word32 *S2 = ctx->S2;
+ word32 *S3 = ctx->S3;
+ word32 *P = ctx->P;
+ word32 str[2];
+ int i;
+
+ for (i = 0; i < 18; i++) {
+ P[i] = parray[i];
+ P[i] ^=
+ ((word32) (unsigned char) (key[(i * 4 + 0) % keybytes])) << 24;
+ P[i] ^=
+ ((word32) (unsigned char) (key[(i * 4 + 1) % keybytes])) << 16;
+ P[i] ^=
+ ((word32) (unsigned char) (key[(i * 4 + 2) % keybytes])) << 8;
+ P[i] ^= ((word32) (unsigned char) (key[(i * 4 + 3) % keybytes]));
+ }
+
+ for (i = 0; i < 256; i++) {
+ S0[i] = sbox0[i];
+ S1[i] = sbox1[i];
+ S2[i] = sbox2[i];
+ S3[i] = sbox3[i];
+ }
+
+ str[0] = str[1] = 0;
+
+ for (i = 0; i < 18; i += 2) {
+ blowfish_encrypt(str[0], str[1], str, ctx);
+ P[i] = str[0];
+ P[i + 1] = str[1];
+ }
+
+ for (i = 0; i < 256; i += 2) {
+ blowfish_encrypt(str[0], str[1], str, ctx);
+ S0[i] = str[0];
+ S0[i + 1] = str[1];
+ }
+ for (i = 0; i < 256; i += 2) {
+ blowfish_encrypt(str[0], str[1], str, ctx);
+ S1[i] = str[0];
+ S1[i + 1] = str[1];
+ }
+ for (i = 0; i < 256; i += 2) {
+ blowfish_encrypt(str[0], str[1], str, ctx);
+ S2[i] = str[0];
+ S2[i + 1] = str[1];
+ }
+ for (i = 0; i < 256; i += 2) {
+ blowfish_encrypt(str[0], str[1], str, ctx);
+ S3[i] = str[0];
+ S3[i + 1] = str[1];
+ }
+}
+
+/* -- Interface with PuTTY -- */
+
+#define SSH_SESSION_KEY_LENGTH 32
+
+static void *blowfish_make_context(void)
+{
+ return snew(BlowfishContext);
+}
+
+static void *blowfish_ssh1_make_context(void)
+{
+ /* In SSH-1, need one key for each direction */
+ return snewn(2, BlowfishContext);
+}
+
+static void blowfish_free_context(void *handle)
+{
+ sfree(handle);
+}
+
+static void blowfish_key(void *handle, unsigned char *key)
+{
+ BlowfishContext *ctx = (BlowfishContext *)handle;
+ blowfish_setkey(ctx, key, 16);
+}
+
+static void blowfish256_key(void *handle, unsigned char *key)
+{
+ BlowfishContext *ctx = (BlowfishContext *)handle;
+ blowfish_setkey(ctx, key, 32);
+}
+
+static void blowfish_iv(void *handle, unsigned char *key)
+{
+ BlowfishContext *ctx = (BlowfishContext *)handle;
+ ctx->iv0 = GET_32BIT_MSB_FIRST(key);
+ ctx->iv1 = GET_32BIT_MSB_FIRST(key + 4);
+}
+
+static void blowfish_sesskey(void *handle, unsigned char *key)
+{
+ BlowfishContext *ctx = (BlowfishContext *)handle;
+ blowfish_setkey(ctx, key, SSH_SESSION_KEY_LENGTH);
+ ctx->iv0 = 0;
+ ctx->iv1 = 0;
+ ctx[1] = ctx[0]; /* structure copy */
+}
+
+static void blowfish_ssh1_encrypt_blk(void *handle, unsigned char *blk,
+ int len)
+{
+ BlowfishContext *ctx = (BlowfishContext *)handle;
+ blowfish_lsb_encrypt_cbc(blk, len, ctx);
+}
+
+static void blowfish_ssh1_decrypt_blk(void *handle, unsigned char *blk,
+ int len)
+{
+ BlowfishContext *ctx = (BlowfishContext *)handle;
+ blowfish_lsb_decrypt_cbc(blk, len, ctx+1);
+}
+
+static void blowfish_ssh2_encrypt_blk(void *handle, unsigned char *blk,
+ int len)
+{
+ BlowfishContext *ctx = (BlowfishContext *)handle;
+ blowfish_msb_encrypt_cbc(blk, len, ctx);
+}
+
+static void blowfish_ssh2_decrypt_blk(void *handle, unsigned char *blk,
+ int len)
+{
+ BlowfishContext *ctx = (BlowfishContext *)handle;
+ blowfish_msb_decrypt_cbc(blk, len, ctx);
+}
+
+static void blowfish_ssh2_sdctr(void *handle, unsigned char *blk,
+ int len)
+{
+ BlowfishContext *ctx = (BlowfishContext *)handle;
+ blowfish_msb_sdctr(blk, len, ctx);
+}
+
+const struct ssh_cipher ssh_blowfish_ssh1 = {
+ blowfish_ssh1_make_context, blowfish_free_context, blowfish_sesskey,
+ blowfish_ssh1_encrypt_blk, blowfish_ssh1_decrypt_blk,
+ 8, "Blowfish-128 CBC"
+};
+
+static const struct ssh2_cipher ssh_blowfish_ssh2 = {
+ blowfish_make_context, blowfish_free_context, blowfish_iv, blowfish_key,
+ blowfish_ssh2_encrypt_blk, blowfish_ssh2_decrypt_blk,
+ "blowfish-cbc",
+ 8, 128, SSH_CIPHER_IS_CBC, "Blowfish-128 CBC"
+};
+
+static const struct ssh2_cipher ssh_blowfish_ssh2_ctr = {
+ blowfish_make_context, blowfish_free_context, blowfish_iv, blowfish256_key,
+ blowfish_ssh2_sdctr, blowfish_ssh2_sdctr,
+ "blowfish-ctr",
+ 8, 256, 0, "Blowfish-256 SDCTR"
+};
+
+static const struct ssh2_cipher *const blowfish_list[] = {
+ &ssh_blowfish_ssh2_ctr,
+ &ssh_blowfish_ssh2
+};
+
+const struct ssh2_ciphers ssh2_blowfish = {
+ sizeof(blowfish_list) / sizeof(*blowfish_list),
+ blowfish_list
+};
--- /dev/null
+/*
+ * Bignum routines for RSA and DH and stuff.
+ */
+
+#include <stdio.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "misc.h"
+
+/*
+ * Usage notes:
+ * * Do not call the DIVMOD_WORD macro with expressions such as array
+ * subscripts, as some implementations object to this (see below).
+ * * Note that none of the division methods below will cope if the
+ * quotient won't fit into BIGNUM_INT_BITS. Callers should be careful
+ * to avoid this case.
+ * If this condition occurs, in the case of the x86 DIV instruction,
+ * an overflow exception will occur, which (according to a correspondent)
+ * will manifest on Windows as something like
+ * 0xC0000095: Integer overflow
+ * The C variant won't give the right answer, either.
+ */
+
+#if defined __GNUC__ && defined __i386__
+typedef unsigned long BignumInt;
+typedef unsigned long long BignumDblInt;
+#define BIGNUM_INT_MASK 0xFFFFFFFFUL
+#define BIGNUM_TOP_BIT 0x80000000UL
+#define BIGNUM_INT_BITS 32
+#define MUL_WORD(w1, w2) ((BignumDblInt)w1 * w2)
+#define DIVMOD_WORD(q, r, hi, lo, w) \
+ __asm__("div %2" : \
+ "=d" (r), "=a" (q) : \
+ "r" (w), "d" (hi), "a" (lo))
+#elif defined _MSC_VER && defined _M_IX86
+typedef unsigned __int32 BignumInt;
+typedef unsigned __int64 BignumDblInt;
+#define BIGNUM_INT_MASK 0xFFFFFFFFUL
+#define BIGNUM_TOP_BIT 0x80000000UL
+#define BIGNUM_INT_BITS 32
+#define MUL_WORD(w1, w2) ((BignumDblInt)w1 * w2)
+/* Note: MASM interprets array subscripts in the macro arguments as
+ * assembler syntax, which gives the wrong answer. Don't supply them.
+ * <http://msdn2.microsoft.com/en-us/library/bf1dw62z.aspx> */
+#define DIVMOD_WORD(q, r, hi, lo, w) do { \
+ __asm mov edx, hi \
+ __asm mov eax, lo \
+ __asm div w \
+ __asm mov r, edx \
+ __asm mov q, eax \
+} while(0)
+#elif defined _LP64
+/* 64-bit architectures can do 32x32->64 chunks at a time */
+typedef unsigned int BignumInt;
+typedef unsigned long BignumDblInt;
+#define BIGNUM_INT_MASK 0xFFFFFFFFU
+#define BIGNUM_TOP_BIT 0x80000000U
+#define BIGNUM_INT_BITS 32
+#define MUL_WORD(w1, w2) ((BignumDblInt)w1 * w2)
+#define DIVMOD_WORD(q, r, hi, lo, w) do { \
+ BignumDblInt n = (((BignumDblInt)hi) << BIGNUM_INT_BITS) | lo; \
+ q = n / w; \
+ r = n % w; \
+} while (0)
+#elif defined _LLP64
+/* 64-bit architectures in which unsigned long is 32 bits, not 64 */
+typedef unsigned long BignumInt;
+typedef unsigned long long BignumDblInt;
+#define BIGNUM_INT_MASK 0xFFFFFFFFUL
+#define BIGNUM_TOP_BIT 0x80000000UL
+#define BIGNUM_INT_BITS 32
+#define MUL_WORD(w1, w2) ((BignumDblInt)w1 * w2)
+#define DIVMOD_WORD(q, r, hi, lo, w) do { \
+ BignumDblInt n = (((BignumDblInt)hi) << BIGNUM_INT_BITS) | lo; \
+ q = n / w; \
+ r = n % w; \
+} while (0)
+#else
+/* Fallback for all other cases */
+typedef unsigned short BignumInt;
+typedef unsigned long BignumDblInt;
+#define BIGNUM_INT_MASK 0xFFFFU
+#define BIGNUM_TOP_BIT 0x8000U
+#define BIGNUM_INT_BITS 16
+#define MUL_WORD(w1, w2) ((BignumDblInt)w1 * w2)
+#define DIVMOD_WORD(q, r, hi, lo, w) do { \
+ BignumDblInt n = (((BignumDblInt)hi) << BIGNUM_INT_BITS) | lo; \
+ q = n / w; \
+ r = n % w; \
+} while (0)
+#endif
+
+#define BIGNUM_INT_BYTES (BIGNUM_INT_BITS / 8)
+
+#define BIGNUM_INTERNAL
+typedef BignumInt *Bignum;
+
+#include "ssh.h"
+
+BignumInt bnZero[1] = { 0 };
+BignumInt bnOne[2] = { 1, 1 };
+
+/*
+ * The Bignum format is an array of `BignumInt'. The first
+ * element of the array counts the remaining elements. The
+ * remaining elements express the actual number, base 2^BIGNUM_INT_BITS, _least_
+ * significant digit first. (So it's trivial to extract the bit
+ * with value 2^n for any n.)
+ *
+ * All Bignums in this module are positive. Negative numbers must
+ * be dealt with outside it.
+ *
+ * INVARIANT: the most significant word of any Bignum must be
+ * nonzero.
+ */
+
+Bignum Zero = bnZero, One = bnOne;
+
+static Bignum newbn(int length)
+{
+ Bignum b = snewn(length + 1, BignumInt);
+ if (!b)
+ abort(); /* FIXME */
+ memset(b, 0, (length + 1) * sizeof(*b));
+ b[0] = length;
+ return b;
+}
+
+void bn_restore_invariant(Bignum b)
+{
+ while (b[0] > 1 && b[b[0]] == 0)
+ b[0]--;
+}
+
+Bignum copybn(Bignum orig)
+{
+ Bignum b = snewn(orig[0] + 1, BignumInt);
+ if (!b)
+ abort(); /* FIXME */
+ memcpy(b, orig, (orig[0] + 1) * sizeof(*b));
+ return b;
+}
+
+void freebn(Bignum b)
+{
+ /*
+ * Burn the evidence, just in case.
+ */
+ memset(b, 0, sizeof(b[0]) * (b[0] + 1));
+ sfree(b);
+}
+
+Bignum bn_power_2(int n)
+{
+ Bignum ret = newbn(n / BIGNUM_INT_BITS + 1);
+ bignum_set_bit(ret, n, 1);
+ return ret;
+}
+
+/*
+ * Internal addition. Sets c = a - b, where 'a', 'b' and 'c' are all
+ * big-endian arrays of 'len' BignumInts. Returns a BignumInt carried
+ * off the top.
+ */
+static BignumInt internal_add(const BignumInt *a, const BignumInt *b,
+ BignumInt *c, int len)
+{
+ int i;
+ BignumDblInt carry = 0;
+
+ for (i = len-1; i >= 0; i--) {
+ carry += (BignumDblInt)a[i] + b[i];
+ c[i] = (BignumInt)carry;
+ carry >>= BIGNUM_INT_BITS;
+ }
+
+ return (BignumInt)carry;
+}
+
+/*
+ * Internal subtraction. Sets c = a - b, where 'a', 'b' and 'c' are
+ * all big-endian arrays of 'len' BignumInts. Any borrow from the top
+ * is ignored.
+ */
+static void internal_sub(const BignumInt *a, const BignumInt *b,
+ BignumInt *c, int len)
+{
+ int i;
+ BignumDblInt carry = 1;
+
+ for (i = len-1; i >= 0; i--) {
+ carry += (BignumDblInt)a[i] + (b[i] ^ BIGNUM_INT_MASK);
+ c[i] = (BignumInt)carry;
+ carry >>= BIGNUM_INT_BITS;
+ }
+}
+
+/*
+ * Compute c = a * b.
+ * Input is in the first len words of a and b.
+ * Result is returned in the first 2*len words of c.
+ *
+ * 'scratch' must point to an array of BignumInt of size at least
+ * mul_compute_scratch(len). (This covers the needs of internal_mul
+ * and all its recursive calls to itself.)
+ */
+#define KARATSUBA_THRESHOLD 50
+static int mul_compute_scratch(int len)
+{
+ int ret = 0;
+ while (len > KARATSUBA_THRESHOLD) {
+ int toplen = len/2, botlen = len - toplen; /* botlen is the bigger */
+ int midlen = botlen + 1;
+ ret += 4*midlen;
+ len = midlen;
+ }
+ return ret;
+}
+static void internal_mul(const BignumInt *a, const BignumInt *b,
+ BignumInt *c, int len, BignumInt *scratch)
+{
+ if (len > KARATSUBA_THRESHOLD) {
+ int i;
+
+ /*
+ * Karatsuba divide-and-conquer algorithm. Cut each input in
+ * half, so that it's expressed as two big 'digits' in a giant
+ * base D:
+ *
+ * a = a_1 D + a_0
+ * b = b_1 D + b_0
+ *
+ * Then the product is of course
+ *
+ * ab = a_1 b_1 D^2 + (a_1 b_0 + a_0 b_1) D + a_0 b_0
+ *
+ * and we compute the three coefficients by recursively
+ * calling ourself to do half-length multiplications.
+ *
+ * The clever bit that makes this worth doing is that we only
+ * need _one_ half-length multiplication for the central
+ * coefficient rather than the two that it obviouly looks
+ * like, because we can use a single multiplication to compute
+ *
+ * (a_1 + a_0) (b_1 + b_0) = a_1 b_1 + a_1 b_0 + a_0 b_1 + a_0 b_0
+ *
+ * and then we subtract the other two coefficients (a_1 b_1
+ * and a_0 b_0) which we were computing anyway.
+ *
+ * Hence we get to multiply two numbers of length N in about
+ * three times as much work as it takes to multiply numbers of
+ * length N/2, which is obviously better than the four times
+ * as much work it would take if we just did a long
+ * conventional multiply.
+ */
+
+ int toplen = len/2, botlen = len - toplen; /* botlen is the bigger */
+ int midlen = botlen + 1;
+ BignumDblInt carry;
+#ifdef KARA_DEBUG
+ int i;
+#endif
+
+ /*
+ * The coefficients a_1 b_1 and a_0 b_0 just avoid overlapping
+ * in the output array, so we can compute them immediately in
+ * place.
+ */
+
+#ifdef KARA_DEBUG
+ printf("a1,a0 = 0x");
+ for (i = 0; i < len; i++) {
+ if (i == toplen) printf(", 0x");
+ printf("%0*x", BIGNUM_INT_BITS/4, a[i]);
+ }
+ printf("\n");
+ printf("b1,b0 = 0x");
+ for (i = 0; i < len; i++) {
+ if (i == toplen) printf(", 0x");
+ printf("%0*x", BIGNUM_INT_BITS/4, b[i]);
+ }
+ printf("\n");
+#endif
+
+ /* a_1 b_1 */
+ internal_mul(a, b, c, toplen, scratch);
+#ifdef KARA_DEBUG
+ printf("a1b1 = 0x");
+ for (i = 0; i < 2*toplen; i++) {
+ printf("%0*x", BIGNUM_INT_BITS/4, c[i]);
+ }
+ printf("\n");
+#endif
+
+ /* a_0 b_0 */
+ internal_mul(a + toplen, b + toplen, c + 2*toplen, botlen, scratch);
+#ifdef KARA_DEBUG
+ printf("a0b0 = 0x");
+ for (i = 0; i < 2*botlen; i++) {
+ printf("%0*x", BIGNUM_INT_BITS/4, c[2*toplen+i]);
+ }
+ printf("\n");
+#endif
+
+ /* Zero padding. midlen exceeds toplen by at most 2, so just
+ * zero the first two words of each input and the rest will be
+ * copied over. */
+ scratch[0] = scratch[1] = scratch[midlen] = scratch[midlen+1] = 0;
+
+ for (i = 0; i < toplen; i++) {
+ scratch[midlen - toplen + i] = a[i]; /* a_1 */
+ scratch[2*midlen - toplen + i] = b[i]; /* b_1 */
+ }
+
+ /* compute a_1 + a_0 */
+ scratch[0] = internal_add(scratch+1, a+toplen, scratch+1, botlen);
+#ifdef KARA_DEBUG
+ printf("a1plusa0 = 0x");
+ for (i = 0; i < midlen; i++) {
+ printf("%0*x", BIGNUM_INT_BITS/4, scratch[i]);
+ }
+ printf("\n");
+#endif
+ /* compute b_1 + b_0 */
+ scratch[midlen] = internal_add(scratch+midlen+1, b+toplen,
+ scratch+midlen+1, botlen);
+#ifdef KARA_DEBUG
+ printf("b1plusb0 = 0x");
+ for (i = 0; i < midlen; i++) {
+ printf("%0*x", BIGNUM_INT_BITS/4, scratch[midlen+i]);
+ }
+ printf("\n");
+#endif
+
+ /*
+ * Now we can do the third multiplication.
+ */
+ internal_mul(scratch, scratch + midlen, scratch + 2*midlen, midlen,
+ scratch + 4*midlen);
+#ifdef KARA_DEBUG
+ printf("a1plusa0timesb1plusb0 = 0x");
+ for (i = 0; i < 2*midlen; i++) {
+ printf("%0*x", BIGNUM_INT_BITS/4, scratch[2*midlen+i]);
+ }
+ printf("\n");
+#endif
+
+ /*
+ * Now we can reuse the first half of 'scratch' to compute the
+ * sum of the outer two coefficients, to subtract from that
+ * product to obtain the middle one.
+ */
+ scratch[0] = scratch[1] = scratch[2] = scratch[3] = 0;
+ for (i = 0; i < 2*toplen; i++)
+ scratch[2*midlen - 2*toplen + i] = c[i];
+ scratch[1] = internal_add(scratch+2, c + 2*toplen,
+ scratch+2, 2*botlen);
+#ifdef KARA_DEBUG
+ printf("a1b1plusa0b0 = 0x");
+ for (i = 0; i < 2*midlen; i++) {
+ printf("%0*x", BIGNUM_INT_BITS/4, scratch[i]);
+ }
+ printf("\n");
+#endif
+
+ internal_sub(scratch + 2*midlen, scratch,
+ scratch + 2*midlen, 2*midlen);
+#ifdef KARA_DEBUG
+ printf("a1b0plusa0b1 = 0x");
+ for (i = 0; i < 2*midlen; i++) {
+ printf("%0*x", BIGNUM_INT_BITS/4, scratch[2*midlen+i]);
+ }
+ printf("\n");
+#endif
+
+ /*
+ * And now all we need to do is to add that middle coefficient
+ * back into the output. We may have to propagate a carry
+ * further up the output, but we can be sure it won't
+ * propagate right the way off the top.
+ */
+ carry = internal_add(c + 2*len - botlen - 2*midlen,
+ scratch + 2*midlen,
+ c + 2*len - botlen - 2*midlen, 2*midlen);
+ i = 2*len - botlen - 2*midlen - 1;
+ while (carry) {
+ assert(i >= 0);
+ carry += c[i];
+ c[i] = (BignumInt)carry;
+ carry >>= BIGNUM_INT_BITS;
+ i--;
+ }
+#ifdef KARA_DEBUG
+ printf("ab = 0x");
+ for (i = 0; i < 2*len; i++) {
+ printf("%0*x", BIGNUM_INT_BITS/4, c[i]);
+ }
+ printf("\n");
+#endif
+
+ } else {
+ int i;
+ BignumInt carry;
+ BignumDblInt t;
+ const BignumInt *ap, *bp;
+ BignumInt *cp, *cps;
+
+ /*
+ * Multiply in the ordinary O(N^2) way.
+ */
+
+ for (i = 0; i < 2 * len; i++)
+ c[i] = 0;
+
+ for (cps = c + 2*len, ap = a + len; ap-- > a; cps--) {
+ carry = 0;
+ for (cp = cps, bp = b + len; cp--, bp-- > b ;) {
+ t = (MUL_WORD(*ap, *bp) + carry) + *cp;
+ *cp = (BignumInt) t;
+ carry = (BignumInt)(t >> BIGNUM_INT_BITS);
+ }
+ *cp = carry;
+ }
+ }
+}
+
+/*
+ * Variant form of internal_mul used for the initial step of
+ * Montgomery reduction. Only bothers outputting 'len' words
+ * (everything above that is thrown away).
+ */
+static void internal_mul_low(const BignumInt *a, const BignumInt *b,
+ BignumInt *c, int len, BignumInt *scratch)
+{
+ if (len > KARATSUBA_THRESHOLD) {
+ int i;
+
+ /*
+ * Karatsuba-aware version of internal_mul_low. As before, we
+ * express each input value as a shifted combination of two
+ * halves:
+ *
+ * a = a_1 D + a_0
+ * b = b_1 D + b_0
+ *
+ * Then the full product is, as before,
+ *
+ * ab = a_1 b_1 D^2 + (a_1 b_0 + a_0 b_1) D + a_0 b_0
+ *
+ * Provided we choose D on the large side (so that a_0 and b_0
+ * are _at least_ as long as a_1 and b_1), we don't need the
+ * topmost term at all, and we only need half of the middle
+ * term. So there's no point in doing the proper Karatsuba
+ * optimisation which computes the middle term using the top
+ * one, because we'd take as long computing the top one as
+ * just computing the middle one directly.
+ *
+ * So instead, we do a much more obvious thing: we call the
+ * fully optimised internal_mul to compute a_0 b_0, and we
+ * recursively call ourself to compute the _bottom halves_ of
+ * a_1 b_0 and a_0 b_1, each of which we add into the result
+ * in the obvious way.
+ *
+ * In other words, there's no actual Karatsuba _optimisation_
+ * in this function; the only benefit in doing it this way is
+ * that we call internal_mul proper for a large part of the
+ * work, and _that_ can optimise its operation.
+ */
+
+ int toplen = len/2, botlen = len - toplen; /* botlen is the bigger */
+
+ /*
+ * Scratch space for the various bits and pieces we're going
+ * to be adding together: we need botlen*2 words for a_0 b_0
+ * (though we may end up throwing away its topmost word), and
+ * toplen words for each of a_1 b_0 and a_0 b_1. That adds up
+ * to exactly 2*len.
+ */
+
+ /* a_0 b_0 */
+ internal_mul(a + toplen, b + toplen, scratch + 2*toplen, botlen,
+ scratch + 2*len);
+
+ /* a_1 b_0 */
+ internal_mul_low(a, b + len - toplen, scratch + toplen, toplen,
+ scratch + 2*len);
+
+ /* a_0 b_1 */
+ internal_mul_low(a + len - toplen, b, scratch, toplen,
+ scratch + 2*len);
+
+ /* Copy the bottom half of the big coefficient into place */
+ for (i = 0; i < botlen; i++)
+ c[toplen + i] = scratch[2*toplen + botlen + i];
+
+ /* Add the two small coefficients, throwing away the returned carry */
+ internal_add(scratch, scratch + toplen, scratch, toplen);
+
+ /* And add that to the large coefficient, leaving the result in c. */
+ internal_add(scratch, scratch + 2*toplen + botlen - toplen,
+ c, toplen);
+
+ } else {
+ int i;
+ BignumInt carry;
+ BignumDblInt t;
+ const BignumInt *ap, *bp;
+ BignumInt *cp, *cps;
+
+ /*
+ * Multiply in the ordinary O(N^2) way.
+ */
+
+ for (i = 0; i < len; i++)
+ c[i] = 0;
+
+ for (cps = c + len, ap = a + len; ap-- > a; cps--) {
+ carry = 0;
+ for (cp = cps, bp = b + len; bp--, cp-- > c ;) {
+ t = (MUL_WORD(*ap, *bp) + carry) + *cp;
+ *cp = (BignumInt) t;
+ carry = (BignumInt)(t >> BIGNUM_INT_BITS);
+ }
+ }
+ }
+}
+
+/*
+ * Montgomery reduction. Expects x to be a big-endian array of 2*len
+ * BignumInts whose value satisfies 0 <= x < rn (where r = 2^(len *
+ * BIGNUM_INT_BITS) is the Montgomery base). Returns in the same array
+ * a value x' which is congruent to xr^{-1} mod n, and satisfies 0 <=
+ * x' < n.
+ *
+ * 'n' and 'mninv' should be big-endian arrays of 'len' BignumInts
+ * each, containing respectively n and the multiplicative inverse of
+ * -n mod r.
+ *
+ * 'tmp' is an array of BignumInt used as scratch space, of length at
+ * least 3*len + mul_compute_scratch(len).
+ */
+static void monty_reduce(BignumInt *x, const BignumInt *n,
+ const BignumInt *mninv, BignumInt *tmp, int len)
+{
+ int i;
+ BignumInt carry;
+
+ /*
+ * Multiply x by (-n)^{-1} mod r. This gives us a value m such
+ * that mn is congruent to -x mod r. Hence, mn+x is an exact
+ * multiple of r, and is also (obviously) congruent to x mod n.
+ */
+ internal_mul_low(x + len, mninv, tmp, len, tmp + 3*len);
+
+ /*
+ * Compute t = (mn+x)/r in ordinary, non-modular, integer
+ * arithmetic. By construction this is exact, and is congruent mod
+ * n to x * r^{-1}, i.e. the answer we want.
+ *
+ * The following multiply leaves that answer in the _most_
+ * significant half of the 'x' array, so then we must shift it
+ * down.
+ */
+ internal_mul(tmp, n, tmp+len, len, tmp + 3*len);
+ carry = internal_add(x, tmp+len, x, 2*len);
+ for (i = 0; i < len; i++)
+ x[len + i] = x[i], x[i] = 0;
+
+ /*
+ * Reduce t mod n. This doesn't require a full-on division by n,
+ * but merely a test and single optional subtraction, since we can
+ * show that 0 <= t < 2n.
+ *
+ * Proof:
+ * + we computed m mod r, so 0 <= m < r.
+ * + so 0 <= mn < rn, obviously
+ * + hence we only need 0 <= x < rn to guarantee that 0 <= mn+x < 2rn
+ * + yielding 0 <= (mn+x)/r < 2n as required.
+ */
+ if (!carry) {
+ for (i = 0; i < len; i++)
+ if (x[len + i] != n[i])
+ break;
+ }
+ if (carry || i >= len || x[len + i] > n[i])
+ internal_sub(x+len, n, x+len, len);
+}
+
+static void internal_add_shifted(BignumInt *number,
+ unsigned n, int shift)
+{
+ int word = 1 + (shift / BIGNUM_INT_BITS);
+ int bshift = shift % BIGNUM_INT_BITS;
+ BignumDblInt addend;
+
+ addend = (BignumDblInt)n << bshift;
+
+ while (addend) {
+ addend += number[word];
+ number[word] = (BignumInt) addend & BIGNUM_INT_MASK;
+ addend >>= BIGNUM_INT_BITS;
+ word++;
+ }
+}
+
+/*
+ * Compute a = a % m.
+ * Input in first alen words of a and first mlen words of m.
+ * Output in first alen words of a
+ * (of which first alen-mlen words will be zero).
+ * The MSW of m MUST have its high bit set.
+ * Quotient is accumulated in the `quotient' array, which is a Bignum
+ * rather than the internal bigendian format. Quotient parts are shifted
+ * left by `qshift' before adding into quot.
+ */
+static void internal_mod(BignumInt *a, int alen,
+ BignumInt *m, int mlen,
+ BignumInt *quot, int qshift)
+{
+ BignumInt m0, m1;
+ unsigned int h;
+ int i, k;
+
+ m0 = m[0];
+ if (mlen > 1)
+ m1 = m[1];
+ else
+ m1 = 0;
+
+ for (i = 0; i <= alen - mlen; i++) {
+ BignumDblInt t;
+ unsigned int q, r, c, ai1;
+
+ if (i == 0) {
+ h = 0;
+ } else {
+ h = a[i - 1];
+ a[i - 1] = 0;
+ }
+
+ if (i == alen - 1)
+ ai1 = 0;
+ else
+ ai1 = a[i + 1];
+
+ /* Find q = h:a[i] / m0 */
+ if (h >= m0) {
+ /*
+ * Special case.
+ *
+ * To illustrate it, suppose a BignumInt is 8 bits, and
+ * we are dividing (say) A1:23:45:67 by A1:B2:C3. Then
+ * our initial division will be 0xA123 / 0xA1, which
+ * will give a quotient of 0x100 and a divide overflow.
+ * However, the invariants in this division algorithm
+ * are not violated, since the full number A1:23:... is
+ * _less_ than the quotient prefix A1:B2:... and so the
+ * following correction loop would have sorted it out.
+ *
+ * In this situation we set q to be the largest
+ * quotient we _can_ stomach (0xFF, of course).
+ */
+ q = BIGNUM_INT_MASK;
+ } else {
+ /* Macro doesn't want an array subscript expression passed
+ * into it (see definition), so use a temporary. */
+ BignumInt tmplo = a[i];
+ DIVMOD_WORD(q, r, h, tmplo, m0);
+
+ /* Refine our estimate of q by looking at
+ h:a[i]:a[i+1] / m0:m1 */
+ t = MUL_WORD(m1, q);
+ if (t > ((BignumDblInt) r << BIGNUM_INT_BITS) + ai1) {
+ q--;
+ t -= m1;
+ r = (r + m0) & BIGNUM_INT_MASK; /* overflow? */
+ if (r >= (BignumDblInt) m0 &&
+ t > ((BignumDblInt) r << BIGNUM_INT_BITS) + ai1) q--;
+ }
+ }
+
+ /* Subtract q * m from a[i...] */
+ c = 0;
+ for (k = mlen - 1; k >= 0; k--) {
+ t = MUL_WORD(q, m[k]);
+ t += c;
+ c = (unsigned)(t >> BIGNUM_INT_BITS);
+ if ((BignumInt) t > a[i + k])
+ c++;
+ a[i + k] -= (BignumInt) t;
+ }
+
+ /* Add back m in case of borrow */
+ if (c != h) {
+ t = 0;
+ for (k = mlen - 1; k >= 0; k--) {
+ t += m[k];
+ t += a[i + k];
+ a[i + k] = (BignumInt) t;
+ t = t >> BIGNUM_INT_BITS;
+ }
+ q--;
+ }
+ if (quot)
+ internal_add_shifted(quot, q, qshift + BIGNUM_INT_BITS * (alen - mlen - i));
+ }
+}
+
+/*
+ * Compute (base ^ exp) % mod, the pedestrian way.
+ */
+Bignum modpow_simple(Bignum base_in, Bignum exp, Bignum mod)
+{
+ BignumInt *a, *b, *n, *m, *scratch;
+ int mshift;
+ int mlen, scratchlen, i, j;
+ Bignum base, 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);
+
+ /*
+ * Make sure the base is smaller than the modulus, by reducing
+ * it modulo the modulus if not.
+ */
+ base = bigmod(base_in, mod);
+
+ /* 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++)
+ m[j] = mod[mod[0] - j];
+
+ /* Shift m left to make msb bit set */
+ for (mshift = 0; mshift < BIGNUM_INT_BITS-1; mshift++)
+ if ((m[0] << mshift) & BIGNUM_TOP_BIT)
+ break;
+ if (mshift) {
+ for (i = 0; i < mlen - 1; i++)
+ m[i] = (m[i] << mshift) | (m[i + 1] >> (BIGNUM_INT_BITS - mshift));
+ m[mlen - 1] = m[mlen - 1] << mshift;
+ }
+
+ /* Allocate n of size mlen, copy base to n */
+ n = snewn(mlen, BignumInt);
+ i = mlen - base[0];
+ for (j = 0; j < i; j++)
+ n[j] = 0;
+ for (j = 0; j < (int)base[0]; j++)
+ n[i + j] = base[base[0] - j];
+
+ /* Allocate a and b of size 2*mlen. Set a = 1 */
+ a = snewn(2 * mlen, BignumInt);
+ b = snewn(2 * mlen, BignumInt);
+ for (i = 0; i < 2 * mlen; i++)
+ a[i] = 0;
+ a[2 * mlen - 1] = 1;
+
+ /* Scratch space for multiplies */
+ scratchlen = mul_compute_scratch(mlen);
+ scratch = snewn(scratchlen, BignumInt);
+
+ /* Skip leading zero bits of exp. */
+ i = 0;
+ j = BIGNUM_INT_BITS-1;
+ while (i < (int)exp[0] && (exp[exp[0] - i] & (1 << j)) == 0) {
+ j--;
+ if (j < 0) {
+ i++;
+ j = BIGNUM_INT_BITS-1;
+ }
+ }
+
+ /* Main computation */
+ while (i < (int)exp[0]) {
+ while (j >= 0) {
+ internal_mul(a + mlen, a + mlen, b, mlen, scratch);
+ internal_mod(b, mlen * 2, m, mlen, NULL, 0);
+ if ((exp[exp[0] - i] & (1 << j)) != 0) {
+ internal_mul(b + mlen, n, a, mlen, scratch);
+ internal_mod(a, mlen * 2, m, mlen, NULL, 0);
+ } else {
+ BignumInt *t;
+ t = a;
+ a = b;
+ b = t;
+ }
+ j--;
+ }
+ i++;
+ j = BIGNUM_INT_BITS-1;
+ }
+
+ /* Fixup result in case the modulus was shifted */
+ if (mshift) {
+ for (i = mlen - 1; i < 2 * mlen - 1; i++)
+ a[i] = (a[i] << mshift) | (a[i + 1] >> (BIGNUM_INT_BITS - mshift));
+ a[2 * mlen - 1] = a[2 * mlen - 1] << mshift;
+ internal_mod(a, mlen * 2, m, mlen, NULL, 0);
+ for (i = 2 * mlen - 1; i >= mlen; i--)
+ a[i] = (a[i] >> mshift) | (a[i - 1] << (BIGNUM_INT_BITS - mshift));
+ }
+
+ /* Copy result to buffer */
+ result = newbn(mod[0]);
+ for (i = 0; i < mlen; i++)
+ 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;
+ sfree(a);
+ for (i = 0; i < scratchlen; i++)
+ scratch[i] = 0;
+ sfree(scratch);
+ for (i = 0; i < 2 * mlen; i++)
+ b[i] = 0;
+ sfree(b);
+ for (i = 0; i < mlen; i++)
+ m[i] = 0;
+ sfree(m);
+ for (i = 0; i < mlen; i++)
+ n[i] = 0;
+ sfree(n);
+
+ freebn(base);
+
+ return result;
+}
+
+/*
+ * Compute (base ^ exp) % mod. Uses the Montgomery multiplication
+ * technique where possible, falling back to modpow_simple otherwise.
+ */
+Bignum modpow(Bignum base_in, Bignum exp, Bignum mod)
+{
+ BignumInt *a, *b, *x, *n, *mninv, *scratch;
+ int len, scratchlen, i, j;
+ Bignum base, base2, r, rn, inv, 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);
+
+ /*
+ * mod had better be odd, or we can't do Montgomery multiplication
+ * using a power of two at all.
+ */
+ if (!(mod[1] & 1))
+ return modpow_simple(base_in, exp, mod);
+
+ /*
+ * Make sure the base is smaller than the modulus, by reducing
+ * it modulo the modulus if not.
+ */
+ base = bigmod(base_in, mod);
+
+ /*
+ * Compute the inverse of n mod r, for monty_reduce. (In fact we
+ * want the inverse of _minus_ n mod r, but we'll sort that out
+ * below.)
+ */
+ len = mod[0];
+ r = bn_power_2(BIGNUM_INT_BITS * len);
+ inv = modinv(mod, r);
+
+ /*
+ * Multiply the base by r mod n, to get it into Montgomery
+ * representation.
+ */
+ base2 = modmul(base, r, mod);
+ freebn(base);
+ base = base2;
+
+ rn = bigmod(r, mod); /* r mod n, i.e. Montgomerified 1 */
+
+ freebn(r); /* won't need this any more */
+
+ /*
+ * Set up internal arrays of the right lengths, in big-endian
+ * format, containing the base, the modulus, and the modulus's
+ * inverse.
+ */
+ n = snewn(len, BignumInt);
+ for (j = 0; j < len; j++)
+ n[len - 1 - j] = mod[j + 1];
+
+ mninv = snewn(len, BignumInt);
+ for (j = 0; j < len; j++)
+ mninv[len - 1 - j] = (j < (int)inv[0] ? inv[j + 1] : 0);
+ freebn(inv); /* we don't need this copy of it any more */
+ /* Now negate mninv mod r, so it's the inverse of -n rather than +n. */
+ x = snewn(len, BignumInt);
+ for (j = 0; j < len; j++)
+ x[j] = 0;
+ internal_sub(x, mninv, mninv, len);
+
+ /* x = snewn(len, BignumInt); */ /* already done above */
+ for (j = 0; j < len; j++)
+ x[len - 1 - j] = (j < (int)base[0] ? base[j + 1] : 0);
+ freebn(base); /* we don't need this copy of it any more */
+
+ a = snewn(2*len, BignumInt);
+ b = snewn(2*len, BignumInt);
+ for (j = 0; j < len; j++)
+ a[2*len - 1 - j] = (j < (int)rn[0] ? rn[j + 1] : 0);
+ freebn(rn);
+
+ /* Scratch space for multiplies */
+ scratchlen = 3*len + mul_compute_scratch(len);
+ scratch = snewn(scratchlen, BignumInt);
+
+ /* Skip leading zero bits of exp. */
+ i = 0;
+ j = BIGNUM_INT_BITS-1;
+ while (i < (int)exp[0] && (exp[exp[0] - i] & (1 << j)) == 0) {
+ j--;
+ if (j < 0) {
+ i++;
+ j = BIGNUM_INT_BITS-1;
+ }
+ }
+
+ /* Main computation */
+ while (i < (int)exp[0]) {
+ while (j >= 0) {
+ internal_mul(a + len, a + len, b, len, scratch);
+ monty_reduce(b, n, mninv, scratch, len);
+ if ((exp[exp[0] - i] & (1 << j)) != 0) {
+ internal_mul(b + len, x, a, len, scratch);
+ monty_reduce(a, n, mninv, scratch, len);
+ } else {
+ BignumInt *t;
+ t = a;
+ a = b;
+ b = t;
+ }
+ j--;
+ }
+ i++;
+ j = BIGNUM_INT_BITS-1;
+ }
+
+ /*
+ * Final monty_reduce to get back from the adjusted Montgomery
+ * representation.
+ */
+ monty_reduce(a, n, mninv, scratch, len);
+
+ /* Copy result to buffer */
+ result = newbn(mod[0]);
+ for (i = 0; i < len; i++)
+ 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;
+ sfree(scratch);
+ for (i = 0; i < 2 * len; i++)
+ a[i] = 0;
+ sfree(a);
+ for (i = 0; i < 2 * len; i++)
+ b[i] = 0;
+ sfree(b);
+ for (i = 0; i < len; i++)
+ mninv[i] = 0;
+ sfree(mninv);
+ for (i = 0; i < len; i++)
+ n[i] = 0;
+ sfree(n);
+ for (i = 0; i < len; i++)
+ x[i] = 0;
+ sfree(x);
+
+ return result;
+}
+
+/*
+ * Compute (p * q) % mod.
+ * The most significant word of mod MUST be non-zero.
+ * We assume that the result array is the same size as the mod array.
+ */
+Bignum modmul(Bignum p, Bignum q, Bignum mod)
+{
+ BignumInt *a, *n, *m, *o, *scratch;
+ int mshift, scratchlen;
+ int pqlen, mlen, rlen, i, j;
+ Bignum result;
+
+ /* 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++)
+ m[j] = mod[mod[0] - j];
+
+ /* Shift m left to make msb bit set */
+ for (mshift = 0; mshift < BIGNUM_INT_BITS-1; mshift++)
+ if ((m[0] << mshift) & BIGNUM_TOP_BIT)
+ break;
+ if (mshift) {
+ for (i = 0; i < mlen - 1; i++)
+ m[i] = (m[i] << mshift) | (m[i + 1] >> (BIGNUM_INT_BITS - mshift));
+ m[mlen - 1] = m[mlen - 1] << mshift;
+ }
+
+ pqlen = (p[0] > q[0] ? p[0] : q[0]);
+
+ /* 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;
+ for (j = 0; j < (int)p[0]; j++)
+ n[i + j] = p[p[0] - j];
+
+ /* Allocate o of size pqlen, copy q to o */
+ o = snewn(pqlen, BignumInt);
+ i = pqlen - q[0];
+ for (j = 0; j < i; j++)
+ o[j] = 0;
+ for (j = 0; j < (int)q[0]; j++)
+ o[i + j] = q[q[0] - j];
+
+ /* Allocate a of size 2*pqlen for result */
+ a = snewn(2 * pqlen, BignumInt);
+
+ /* Scratch space for multiplies */
+ scratchlen = mul_compute_scratch(pqlen);
+ scratch = snewn(scratchlen, BignumInt);
+
+ /* Main computation */
+ internal_mul(n, o, a, pqlen, scratch);
+ internal_mod(a, pqlen * 2, m, mlen, NULL, 0);
+
+ /* Fixup result in case the modulus was shifted */
+ if (mshift) {
+ for (i = 2 * pqlen - mlen - 1; i < 2 * pqlen - 1; i++)
+ a[i] = (a[i] << mshift) | (a[i + 1] >> (BIGNUM_INT_BITS - mshift));
+ a[2 * pqlen - 1] = a[2 * pqlen - 1] << mshift;
+ internal_mod(a, pqlen * 2, m, mlen, NULL, 0);
+ for (i = 2 * pqlen - 1; i >= 2 * pqlen - mlen; i--)
+ a[i] = (a[i] >> mshift) | (a[i - 1] << (BIGNUM_INT_BITS - mshift));
+ }
+
+ /* Copy result to buffer */
+ rlen = (mlen < pqlen * 2 ? mlen : pqlen * 2);
+ result = newbn(rlen);
+ for (i = 0; i < rlen; i++)
+ 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;
+ sfree(scratch);
+ for (i = 0; i < 2 * pqlen; i++)
+ a[i] = 0;
+ sfree(a);
+ for (i = 0; i < mlen; i++)
+ m[i] = 0;
+ sfree(m);
+ for (i = 0; i < pqlen; i++)
+ n[i] = 0;
+ sfree(n);
+ for (i = 0; i < pqlen; i++)
+ o[i] = 0;
+ sfree(o);
+
+ return result;
+}
+
+/*
+ * Compute p % mod.
+ * The most significant word of mod MUST be non-zero.
+ * We assume that the result array is the same size as the mod array.
+ * We optionally write out a quotient if `quotient' is non-NULL.
+ * We can avoid writing out the result if `result' is NULL.
+ */
+static void bigdivmod(Bignum p, Bignum mod, Bignum result, Bignum quotient)
+{
+ BignumInt *n, *m;
+ int mshift;
+ int plen, mlen, i, j;
+
+ /* 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++)
+ m[j] = mod[mod[0] - j];
+
+ /* Shift m left to make msb bit set */
+ for (mshift = 0; mshift < BIGNUM_INT_BITS-1; mshift++)
+ if ((m[0] << mshift) & BIGNUM_TOP_BIT)
+ break;
+ if (mshift) {
+ for (i = 0; i < mlen - 1; i++)
+ m[i] = (m[i] << mshift) | (m[i + 1] >> (BIGNUM_INT_BITS - mshift));
+ m[mlen - 1] = m[mlen - 1] << mshift;
+ }
+
+ plen = p[0];
+ /* Ensure plen > mlen */
+ if (plen <= mlen)
+ plen = mlen + 1;
+
+ /* Allocate n of size plen, copy p to n */
+ n = snewn(plen, BignumInt);
+ for (j = 0; j < plen; j++)
+ n[j] = 0;
+ for (j = 1; j <= (int)p[0]; j++)
+ n[plen - j] = p[j];
+
+ /* Main computation */
+ internal_mod(n, plen, m, mlen, quotient, mshift);
+
+ /* Fixup result in case the modulus was shifted */
+ if (mshift) {
+ for (i = plen - mlen - 1; i < plen - 1; i++)
+ n[i] = (n[i] << mshift) | (n[i + 1] >> (BIGNUM_INT_BITS - mshift));
+ n[plen - 1] = n[plen - 1] << mshift;
+ internal_mod(n, plen, m, mlen, quotient, 0);
+ for (i = plen - 1; i >= plen - mlen; i--)
+ n[i] = (n[i] >> mshift) | (n[i - 1] << (BIGNUM_INT_BITS - mshift));
+ }
+
+ /* Copy result to buffer */
+ if (result) {
+ for (i = 1; i <= (int)result[0]; i++) {
+ int j = plen - i;
+ result[i] = j >= 0 ? n[j] : 0;
+ }
+ }
+
+ /* Free temporary arrays */
+ for (i = 0; i < mlen; i++)
+ m[i] = 0;
+ sfree(m);
+ for (i = 0; i < plen; i++)
+ n[i] = 0;
+ sfree(n);
+}
+
+/*
+ * Decrement a number.
+ */
+void decbn(Bignum bn)
+{
+ int i = 1;
+ while (i < (int)bn[0] && bn[i] == 0)
+ bn[i++] = BIGNUM_INT_MASK;
+ bn[i]--;
+}
+
+Bignum bignum_from_bytes(const unsigned char *data, int nbytes)
+{
+ Bignum result;
+ int w, i;
+
+ w = (nbytes + BIGNUM_INT_BYTES - 1) / BIGNUM_INT_BYTES; /* bytes->words */
+
+ result = newbn(w);
+ for (i = 1; i <= w; i++)
+ result[i] = 0;
+ for (i = nbytes; i--;) {
+ unsigned char byte = *data++;
+ result[1 + i / BIGNUM_INT_BYTES] |= byte << (8*i % BIGNUM_INT_BITS);
+ }
+
+ while (result[0] > 1 && result[result[0]] == 0)
+ result[0]--;
+ return result;
+}
+
+/*
+ * Read an SSH-1-format bignum from a data buffer. Return the number
+ * of bytes consumed, or -1 if there wasn't enough data.
+ */
+int ssh1_read_bignum(const unsigned char *data, int len, Bignum * result)
+{
+ const unsigned char *p = data;
+ int i;
+ int w, b;
+
+ if (len < 2)
+ return -1;
+
+ w = 0;
+ for (i = 0; i < 2; i++)
+ w = (w << 8) + *p++;
+ b = (w + 7) / 8; /* bits -> bytes */
+
+ if (len < b+2)
+ return -1;
+
+ if (!result) /* just return length */
+ return b + 2;
+
+ *result = bignum_from_bytes(p, b);
+
+ return p + b - data;
+}
+
+/*
+ * Return the bit count of a bignum, for SSH-1 encoding.
+ */
+int bignum_bitcount(Bignum bn)
+{
+ int bitcount = bn[0] * BIGNUM_INT_BITS - 1;
+ while (bitcount >= 0
+ && (bn[bitcount / BIGNUM_INT_BITS + 1] >> (bitcount % BIGNUM_INT_BITS)) == 0) bitcount--;
+ return bitcount + 1;
+}
+
+/*
+ * Return the byte length of a bignum when SSH-1 encoded.
+ */
+int ssh1_bignum_length(Bignum bn)
+{
+ return 2 + (bignum_bitcount(bn) + 7) / 8;
+}
+
+/*
+ * Return the byte length of a bignum when SSH-2 encoded.
+ */
+int ssh2_bignum_length(Bignum bn)
+{
+ return 4 + (bignum_bitcount(bn) + 8) / 8;
+}
+
+/*
+ * 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]))
+ return 0; /* beyond the end */
+ else
+ return (bn[i / BIGNUM_INT_BYTES + 1] >>
+ ((i % BIGNUM_INT_BYTES)*8)) & 0xFF;
+}
+
+/*
+ * 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]))
+ return 0; /* beyond the end */
+ else
+ return (bn[i / BIGNUM_INT_BITS + 1] >> (i % BIGNUM_INT_BITS)) & 1;
+}
+
+/*
+ * 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]))
+ abort(); /* beyond the end */
+ else {
+ int v = bitnum / BIGNUM_INT_BITS + 1;
+ int mask = 1 << (bitnum % BIGNUM_INT_BITS);
+ if (value)
+ bn[v] |= mask;
+ else
+ bn[v] &= ~mask;
+ }
+}
+
+/*
+ * Write a SSH-1-format bignum into a buffer. It is assumed the
+ * buffer is big enough. Returns the number of bytes used.
+ */
+int ssh1_write_bignum(void *data, Bignum bn)
+{
+ unsigned char *p = data;
+ int len = ssh1_bignum_length(bn);
+ int i;
+ int bitc = bignum_bitcount(bn);
+
+ *p++ = (bitc >> 8) & 0xFF;
+ *p++ = (bitc) & 0xFF;
+ for (i = len - 2; i--;)
+ *p++ = bignum_byte(bn, i);
+ return len;
+}
+
+/*
+ * 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);
+ while (i) {
+ BignumInt aval = (i > amax ? 0 : a[i]);
+ BignumInt bval = (i > bmax ? 0 : b[i]);
+ if (aval < bval)
+ return -1;
+ if (aval > bval)
+ return +1;
+ i--;
+ }
+ return 0;
+}
+
+/*
+ * Right-shift one bignum to form another.
+ */
+Bignum bignum_rshift(Bignum a, int shift)
+{
+ Bignum ret;
+ int i, shiftw, shiftb, shiftbb, bits;
+ BignumInt ai, ai1;
+
+ bits = bignum_bitcount(a) - shift;
+ ret = newbn((bits + BIGNUM_INT_BITS - 1) / BIGNUM_INT_BITS);
+
+ if (ret) {
+ shiftw = shift / BIGNUM_INT_BITS;
+ shiftb = shift % BIGNUM_INT_BITS;
+ shiftbb = BIGNUM_INT_BITS - shiftb;
+
+ ai1 = a[shiftw + 1];
+ for (i = 1; i <= (int)ret[0]; i++) {
+ ai = ai1;
+ ai1 = (i + shiftw + 1 <= (int)a[0] ? a[i + shiftw + 1] : 0);
+ ret[i] = ((ai >> shiftb) | (ai1 << shiftbb)) & BIGNUM_INT_MASK;
+ }
+ }
+
+ return ret;
+}
+
+/*
+ * Non-modular multiplication and addition.
+ */
+Bignum bigmuladd(Bignum a, Bignum b, Bignum addend)
+{
+ int alen = a[0], blen = b[0];
+ int mlen = (alen > blen ? alen : blen);
+ int rlen, i, maxspot;
+ int wslen;
+ BignumInt *workspace;
+ Bignum ret;
+
+ /* mlen space for a, mlen space for b, 2*mlen for result,
+ * plus scratch space for multiplication */
+ wslen = mlen * 4 + mul_compute_scratch(mlen);
+ workspace = snewn(wslen, BignumInt);
+ for (i = 0; i < mlen; i++) {
+ workspace[0 * mlen + i] = (mlen - i <= (int)a[0] ? a[mlen - i] : 0);
+ workspace[1 * mlen + i] = (mlen - i <= (int)b[0] ? b[mlen - i] : 0);
+ }
+
+ internal_mul(workspace + 0 * mlen, workspace + 1 * mlen,
+ workspace + 2 * mlen, mlen, workspace + 4 * mlen);
+
+ /* now just copy the result back */
+ rlen = alen + blen + 1;
+ if (addend && rlen <= (int)addend[0])
+ rlen = addend[0] + 1;
+ ret = newbn(rlen);
+ maxspot = 0;
+ for (i = 1; i <= (int)ret[0]; i++) {
+ ret[i] = (i <= 2 * mlen ? workspace[4 * mlen - i] : 0);
+ if (ret[i] != 0)
+ maxspot = i;
+ }
+ ret[0] = maxspot;
+
+ /* now add in the addend, if any */
+ if (addend) {
+ BignumDblInt carry = 0;
+ for (i = 1; i <= rlen; i++) {
+ carry += (i <= (int)ret[0] ? ret[i] : 0);
+ carry += (i <= (int)addend[0] ? addend[i] : 0);
+ ret[i] = (BignumInt) carry & BIGNUM_INT_MASK;
+ carry >>= BIGNUM_INT_BITS;
+ if (ret[i] != 0 && i > maxspot)
+ maxspot = i;
+ }
+ }
+ ret[0] = maxspot;
+
+ for (i = 0; i < wslen; i++)
+ workspace[i] = 0;
+ sfree(workspace);
+ return ret;
+}
+
+/*
+ * Non-modular multiplication.
+ */
+Bignum bigmul(Bignum a, Bignum b)
+{
+ return bigmuladd(a, b, NULL);
+}
+
+/*
+ * Simple addition.
+ */
+Bignum bigadd(Bignum a, Bignum b)
+{
+ int alen = a[0], blen = b[0];
+ int rlen = (alen > blen ? alen : blen) + 1;
+ int i, maxspot;
+ Bignum ret;
+ BignumDblInt carry;
+
+ ret = newbn(rlen);
+
+ carry = 0;
+ maxspot = 0;
+ for (i = 1; i <= rlen; i++) {
+ carry += (i <= (int)a[0] ? a[i] : 0);
+ carry += (i <= (int)b[0] ? b[i] : 0);
+ ret[i] = (BignumInt) carry & BIGNUM_INT_MASK;
+ carry >>= BIGNUM_INT_BITS;
+ if (ret[i] != 0 && i > maxspot)
+ maxspot = i;
+ }
+ ret[0] = maxspot;
+
+ return ret;
+}
+
+/*
+ * Subtraction. Returns a-b, or NULL if the result would come out
+ * negative (recall that this entire bignum module only handles
+ * positive numbers).
+ */
+Bignum bigsub(Bignum a, Bignum b)
+{
+ int alen = a[0], blen = b[0];
+ int rlen = (alen > blen ? alen : blen);
+ int i, maxspot;
+ Bignum ret;
+ BignumDblInt carry;
+
+ ret = newbn(rlen);
+
+ carry = 1;
+ maxspot = 0;
+ for (i = 1; i <= rlen; i++) {
+ carry += (i <= (int)a[0] ? a[i] : 0);
+ carry += (i <= (int)b[0] ? b[i] ^ BIGNUM_INT_MASK : BIGNUM_INT_MASK);
+ ret[i] = (BignumInt) carry & BIGNUM_INT_MASK;
+ carry >>= BIGNUM_INT_BITS;
+ if (ret[i] != 0 && i > maxspot)
+ maxspot = i;
+ }
+ ret[0] = maxspot;
+
+ if (!carry) {
+ freebn(ret);
+ return NULL;
+ }
+
+ return ret;
+}
+
+/*
+ * Create a bignum which is the bitmask covering another one. That
+ * is, the smallest integer which is >= N and is also one less than
+ * a power of two.
+ */
+Bignum bignum_bitmask(Bignum n)
+{
+ Bignum ret = copybn(n);
+ int i;
+ BignumInt j;
+
+ i = ret[0];
+ while (n[i] == 0 && i > 0)
+ i--;
+ if (i <= 0)
+ return ret; /* input was zero */
+ j = 1;
+ while (j < n[i])
+ j = 2 * j + 1;
+ ret[i] = j;
+ while (--i > 0)
+ ret[i] = BIGNUM_INT_MASK;
+ return ret;
+}
+
+/*
+ * Convert a (max 32-bit) long into a bignum.
+ */
+Bignum bignum_from_long(unsigned long nn)
+{
+ Bignum ret;
+ BignumDblInt n = nn;
+
+ ret = newbn(3);
+ ret[1] = (BignumInt)(n & BIGNUM_INT_MASK);
+ ret[2] = (BignumInt)((n >> BIGNUM_INT_BITS) & BIGNUM_INT_MASK);
+ ret[3] = 0;
+ ret[0] = (ret[2] ? 2 : 1);
+ return ret;
+}
+
+/*
+ * Add a long to a bignum.
+ */
+Bignum bignum_add_long(Bignum number, unsigned long addendx)
+{
+ Bignum ret = newbn(number[0] + 1);
+ int i, maxspot = 0;
+ BignumDblInt carry = 0, addend = addendx;
+
+ for (i = 1; i <= (int)ret[0]; i++) {
+ carry += addend & BIGNUM_INT_MASK;
+ carry += (i <= (int)number[0] ? number[i] : 0);
+ addend >>= BIGNUM_INT_BITS;
+ ret[i] = (BignumInt) carry & BIGNUM_INT_MASK;
+ carry >>= BIGNUM_INT_BITS;
+ if (ret[i] != 0)
+ maxspot = i;
+ }
+ ret[0] = maxspot;
+ return ret;
+}
+
+/*
+ * Compute the residue of a bignum, modulo a (max 16-bit) short.
+ */
+unsigned short bignum_mod_short(Bignum number, unsigned short modulus)
+{
+ BignumDblInt mod, r;
+ int i;
+
+ r = 0;
+ mod = modulus;
+ for (i = number[0]; i > 0; i--)
+ r = (r * (BIGNUM_TOP_BIT % mod) * 2 + number[i] % mod) % mod;
+ return (unsigned short) r;
+}
+
+#ifdef DEBUG
+void diagbn(char *prefix, Bignum md)
+{
+ int i, nibbles, morenibbles;
+ static const char hex[] = "0123456789ABCDEF";
+
+ debug(("%s0x", prefix ? prefix : ""));
+
+ nibbles = (3 + bignum_bitcount(md)) / 4;
+ if (nibbles < 1)
+ nibbles = 1;
+ morenibbles = 4 * md[0] - nibbles;
+ for (i = 0; i < morenibbles; i++)
+ debug(("-"));
+ for (i = nibbles; i--;)
+ debug(("%c",
+ hex[(bignum_byte(md, i / 2) >> (4 * (i % 2))) & 0xF]));
+
+ if (prefix)
+ debug(("\n"));
+}
+#endif
+
+/*
+ * Simple division.
+ */
+Bignum bigdiv(Bignum a, Bignum b)
+{
+ Bignum q = newbn(a[0]);
+ bigdivmod(a, b, NULL, q);
+ return q;
+}
+
+/*
+ * Simple remainder.
+ */
+Bignum bigmod(Bignum a, Bignum b)
+{
+ Bignum r = newbn(b[0]);
+ bigdivmod(a, b, r, NULL);
+ return r;
+}
+
+/*
+ * Greatest common divisor.
+ */
+Bignum biggcd(Bignum av, Bignum bv)
+{
+ Bignum a = copybn(av);
+ Bignum b = copybn(bv);
+
+ while (bignum_cmp(b, Zero) != 0) {
+ Bignum t = newbn(b[0]);
+ bigdivmod(a, b, t, NULL);
+ while (t[0] > 1 && t[t[0]] == 0)
+ t[0]--;
+ freebn(a);
+ a = b;
+ b = t;
+ }
+
+ freebn(b);
+ return a;
+}
+
+/*
+ * Modular inverse, using Euclid's extended algorithm.
+ */
+Bignum modinv(Bignum number, Bignum modulus)
+{
+ Bignum a = copybn(modulus);
+ Bignum b = copybn(number);
+ Bignum xp = copybn(Zero);
+ Bignum x = copybn(One);
+ int sign = +1;
+
+ while (bignum_cmp(b, One) != 0) {
+ Bignum t = newbn(b[0]);
+ Bignum q = newbn(a[0]);
+ bigdivmod(a, b, t, q);
+ while (t[0] > 1 && t[t[0]] == 0)
+ t[0]--;
+ freebn(a);
+ a = b;
+ b = t;
+ t = xp;
+ xp = x;
+ x = bigmuladd(q, xp, t);
+ sign = -sign;
+ freebn(t);
+ freebn(q);
+ }
+
+ freebn(b);
+ freebn(a);
+ freebn(xp);
+
+ /* now we know that sign * x == 1, and that x < modulus */
+ if (sign < 0) {
+ /* set a new x to be modulus - x */
+ Bignum newx = newbn(modulus[0]);
+ BignumInt carry = 0;
+ int maxspot = 1;
+ int i;
+
+ for (i = 1; i <= (int)newx[0]; i++) {
+ BignumInt aword = (i <= (int)modulus[0] ? modulus[i] : 0);
+ BignumInt bword = (i <= (int)x[0] ? x[i] : 0);
+ newx[i] = aword - bword - carry;
+ bword = ~bword;
+ carry = carry ? (newx[i] >= bword) : (newx[i] > bword);
+ if (newx[i] != 0)
+ maxspot = i;
+ }
+ newx[0] = maxspot;
+ freebn(x);
+ x = newx;
+ }
+
+ /* and return. */
+ return x;
+}
+
+/*
+ * Render a bignum into decimal. Return a malloced string holding
+ * the decimal representation.
+ */
+char *bignum_decimal(Bignum x)
+{
+ int ndigits, ndigit;
+ int i, iszero;
+ BignumDblInt carry;
+ char *ret;
+ BignumInt *workspace;
+
+ /*
+ * First, estimate the number of digits. Since log(10)/log(2)
+ * is just greater than 93/28 (the joys of continued fraction
+ * approximations...) we know that for every 93 bits, we need
+ * at most 28 digits. This will tell us how much to malloc.
+ *
+ * Formally: if x has i bits, that means x is strictly less
+ * than 2^i. Since 2 is less than 10^(28/93), this is less than
+ * 10^(28i/93). We need an integer power of ten, so we must
+ * round up (rounding down might make it less than x again).
+ * Therefore if we multiply the bit count by 28/93, rounding
+ * up, we will have enough digits.
+ *
+ * i=0 (i.e., x=0) is an irritating special case.
+ */
+ i = bignum_bitcount(x);
+ if (!i)
+ ndigits = 1; /* x = 0 */
+ else
+ ndigits = (28 * i + 92) / 93; /* multiply by 28/93 and round up */
+ ndigits++; /* allow for trailing \0 */
+ ret = snewn(ndigits, char);
+
+ /*
+ * Now allocate some workspace to hold the binary form as we
+ * repeatedly divide it by ten. Initialise this to the
+ * big-endian form of the number.
+ */
+ workspace = snewn(x[0], BignumInt);
+ for (i = 0; i < (int)x[0]; i++)
+ workspace[i] = x[x[0] - i];
+
+ /*
+ * Next, write the decimal number starting with the last digit.
+ * We use ordinary short division, dividing 10 into the
+ * workspace.
+ */
+ ndigit = ndigits - 1;
+ ret[ndigit] = '\0';
+ do {
+ iszero = 1;
+ carry = 0;
+ for (i = 0; i < (int)x[0]; i++) {
+ carry = (carry << BIGNUM_INT_BITS) + workspace[i];
+ workspace[i] = (BignumInt) (carry / 10);
+ if (workspace[i])
+ iszero = 0;
+ carry %= 10;
+ }
+ ret[--ndigit] = (char) (carry + '0');
+ } while (!iszero);
+
+ /*
+ * There's a chance we've fallen short of the start of the
+ * string. Correct if so.
+ */
+ if (ndigit > 0)
+ memmove(ret, ret + ndigit, ndigits - ndigit);
+
+ /*
+ * Done.
+ */
+ sfree(workspace);
+ return ret;
+}
+
+#ifdef TESTBN
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+
+/*
+ * gcc -g -O0 -DTESTBN -o testbn sshbn.c misc.c -I unix -I charset
+ *
+ * Then feed to this program's standard input the output of
+ * testdata/bignum.py .
+ */
+
+void modalfatalbox(char *p, ...)
+{
+ va_list ap;
+ fprintf(stderr, "FATAL ERROR: ");
+ va_start(ap, p);
+ vfprintf(stderr, p, ap);
+ va_end(ap);
+ fputc('\n', stderr);
+ exit(1);
+}
+
+#define fromxdigit(c) ( (c)>'9' ? ((c)&0xDF) - 'A' + 10 : (c) - '0' )
+
+int main(int argc, char **argv)
+{
+ char *buf;
+ int line = 0;
+ int passes = 0, fails = 0;
+
+ while ((buf = fgetline(stdin)) != NULL) {
+ int maxlen = strlen(buf);
+ unsigned char *data = snewn(maxlen, unsigned char);
+ unsigned char *ptrs[5], *q;
+ int ptrnum;
+ char *bufp = buf;
+
+ line++;
+
+ q = data;
+ ptrnum = 0;
+
+ while (*bufp && !isspace((unsigned char)*bufp))
+ bufp++;
+ if (bufp)
+ *bufp++ = '\0';
+
+ while (*bufp) {
+ char *start, *end;
+ int i;
+
+ while (*bufp && !isxdigit((unsigned char)*bufp))
+ bufp++;
+ start = bufp;
+
+ if (!*bufp)
+ break;
+
+ while (*bufp && isxdigit((unsigned char)*bufp))
+ bufp++;
+ end = bufp;
+
+ if (ptrnum >= lenof(ptrs))
+ break;
+ ptrs[ptrnum++] = q;
+
+ for (i = -((end - start) & 1); i < end-start; i += 2) {
+ unsigned char val = (i < 0 ? 0 : fromxdigit(start[i]));
+ val = val * 16 + fromxdigit(start[i+1]);
+ *q++ = val;
+ }
+
+ ptrs[ptrnum] = q;
+ }
+
+ if (!strcmp(buf, "mul")) {
+ Bignum a, b, c, p;
+
+ if (ptrnum != 3) {
+ printf("%d: mul with %d parameters, expected 3\n", line);
+ 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]);
+ p = bigmul(a, b);
+
+ if (bignum_cmp(c, p) == 0) {
+ passes++;
+ } else {
+ char *as = bignum_decimal(a);
+ char *bs = bignum_decimal(b);
+ char *cs = bignum_decimal(c);
+ char *ps = bignum_decimal(p);
+
+ printf("%d: fail: %s * %s gave %s expected %s\n",
+ line, as, bs, ps, cs);
+ fails++;
+
+ sfree(as);
+ sfree(bs);
+ sfree(cs);
+ sfree(ps);
+ }
+ freebn(a);
+ freebn(b);
+ 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);
+ exit(1);
+ }
+
+ base = bignum_from_bytes(ptrs[0], ptrs[1]-ptrs[0]);
+ expt = bignum_from_bytes(ptrs[1], ptrs[2]-ptrs[1]);
+ modulus = bignum_from_bytes(ptrs[2], ptrs[3]-ptrs[2]);
+ expected = bignum_from_bytes(ptrs[3], ptrs[4]-ptrs[3]);
+ answer = modpow(base, expt, modulus);
+
+ if (bignum_cmp(expected, answer) == 0) {
+ passes++;
+ } else {
+ char *as = bignum_decimal(base);
+ char *bs = bignum_decimal(expt);
+ char *cs = bignum_decimal(modulus);
+ char *ds = bignum_decimal(answer);
+ char *ps = bignum_decimal(expected);
+
+ printf("%d: fail: %s ^ %s mod %s gave %s expected %s\n",
+ line, as, bs, cs, ds, ps);
+ fails++;
+
+ sfree(as);
+ sfree(bs);
+ sfree(cs);
+ sfree(ds);
+ sfree(ps);
+ }
+ freebn(base);
+ freebn(expt);
+ freebn(modulus);
+ freebn(expected);
+ freebn(answer);
+ } else {
+ printf("%d: unrecognised test keyword: '%s'\n", line, buf);
+ exit(1);
+ }
+
+ sfree(buf);
+ sfree(data);
+ }
+
+ printf("passed %d failed %d total %d\n", passes, fails, passes+fails);
+ return fails != 0;
+}
+
+#endif
--- /dev/null
+/*
+ * CRC32 implementation.
+ *
+ * The basic concept of a CRC is that you treat your bit-string
+ * abcdefg... as a ludicrously long polynomial M=a+bx+cx^2+dx^3+...
+ * over Z[2]. You then take a modulus polynomial P, and compute the
+ * remainder of M on division by P. Thus, an erroneous message N
+ * will only have the same CRC if the difference E = M-N is an
+ * exact multiple of P. (Note that as we are working over Z[2], M-N
+ * = N-M = M+N; but that's not very important.)
+ *
+ * What makes the CRC good is choosing P to have good properties:
+ *
+ * - If its first and last terms are both nonzero then it cannot
+ * be a factor of any single term x^i. Therefore if M and N
+ * differ by exactly one bit their CRCs will guaranteeably
+ * be distinct.
+ *
+ * - If it has a prime (irreducible) factor with three terms then
+ * it cannot divide a polynomial of the form x^i(1+x^j).
+ * Therefore if M and N differ by exactly _two_ bits they will
+ * have different CRCs.
+ *
+ * - If it has a factor (x+1) then it cannot divide a polynomial
+ * with an odd number of terms. Therefore if M and N differ by
+ * _any odd_ number of bits they will have different CRCs.
+ *
+ * - If the error term E is of the form x^i*B(x) where B(x) has
+ * order less than P (i.e. a short _burst_ of errors) then P
+ * cannot divide E (since no polynomial can divide a shorter
+ * one), so any such error burst will be spotted.
+ *
+ * The CRC32 standard polynomial is
+ * x^32+x^26+x^23+x^22+x^16+x^12+x^11+x^10+x^8+x^7+x^5+x^4+x^2+x^1+x^0
+ *
+ * In fact, we don't compute M mod P; we compute M*x^32 mod P.
+ *
+ * The concrete implementation of the CRC is this: we maintain at
+ * all times a 32-bit word which is the current remainder of the
+ * polynomial mod P. Whenever we receive an extra bit, we multiply
+ * the existing remainder by x, add (XOR) the x^32 term thus
+ * generated to the new x^32 term caused by the incoming bit, and
+ * remove the resulting combined x^32 term if present by replacing
+ * it with (P-x^32).
+ *
+ * Bit 0 of the word is the x^31 term and bit 31 is the x^0 term.
+ * Thus, multiplying by x means shifting right. So the actual
+ * algorithm goes like this:
+ *
+ * x32term = (crcword & 1) ^ newbit;
+ * crcword = (crcword >> 1) ^ (x32term * 0xEDB88320);
+ *
+ * In practice, we pre-compute what will happen to crcword on any
+ * given sequence of eight incoming bits, and store that in a table
+ * which we then use at run-time to do the job:
+ *
+ * outgoingplusnew = (crcword & 0xFF) ^ newbyte;
+ * crcword = (crcword >> 8) ^ table[outgoingplusnew];
+ *
+ * where table[outgoingplusnew] is computed by setting crcword=0
+ * and then iterating the first code fragment eight times (taking
+ * the incoming byte low bit first).
+ *
+ * Note that all shifts are rightward and thus no assumption is
+ * made about exact word length! (Although word length must be at
+ * _least_ 32 bits, but ANSI C guarantees this for `unsigned long'
+ * anyway.)
+ */
+
+#include <stdlib.h>
+
+#include "ssh.h"
+
+/* ----------------------------------------------------------------------
+ * Multi-function module. Can be compiled three ways.
+ *
+ * - Compile with no special #defines. Will generate a table
+ * that's already initialised at compile time, and one function
+ * crc32_compute(buf,len) that uses it. Normal usage.
+ *
+ * - Compile with INITFUNC defined. Will generate an uninitialised
+ * array as the table, and as well as crc32_compute(buf,len) it
+ * will also generate void crc32_init(void) which sets up the
+ * table at run time. Useful if binary size is important.
+ *
+ * - Compile with GENPROGRAM defined. Will create a standalone
+ * program that does the initialisation and outputs the table as
+ * C code.
+ */
+
+#define POLY (0xEDB88320L)
+
+#ifdef GENPROGRAM
+#define INITFUNC /* the gen program needs the init func :-) */
+#endif
+
+#ifdef INITFUNC
+
+/*
+ * This variant of the code generates the table at run-time from an
+ * init function.
+ */
+static unsigned long crc32_table[256];
+
+void crc32_init(void)
+{
+ unsigned long crcword;
+ int i;
+
+ for (i = 0; i < 256; i++) {
+ unsigned long newbyte, x32term;
+ int j;
+ crcword = 0;
+ newbyte = i;
+ for (j = 0; j < 8; j++) {
+ x32term = (crcword ^ newbyte) & 1;
+ crcword = (crcword >> 1) ^ (x32term * POLY);
+ newbyte >>= 1;
+ }
+ crc32_table[i] = crcword;
+ }
+}
+
+#else
+
+/*
+ * This variant of the code has the data already prepared.
+ */
+static const unsigned long crc32_table[256] = {
+ 0x00000000L, 0x77073096L, 0xEE0E612CL, 0x990951BAL,
+ 0x076DC419L, 0x706AF48FL, 0xE963A535L, 0x9E6495A3L,
+ 0x0EDB8832L, 0x79DCB8A4L, 0xE0D5E91EL, 0x97D2D988L,
+ 0x09B64C2BL, 0x7EB17CBDL, 0xE7B82D07L, 0x90BF1D91L,
+ 0x1DB71064L, 0x6AB020F2L, 0xF3B97148L, 0x84BE41DEL,
+ 0x1ADAD47DL, 0x6DDDE4EBL, 0xF4D4B551L, 0x83D385C7L,
+ 0x136C9856L, 0x646BA8C0L, 0xFD62F97AL, 0x8A65C9ECL,
+ 0x14015C4FL, 0x63066CD9L, 0xFA0F3D63L, 0x8D080DF5L,
+ 0x3B6E20C8L, 0x4C69105EL, 0xD56041E4L, 0xA2677172L,
+ 0x3C03E4D1L, 0x4B04D447L, 0xD20D85FDL, 0xA50AB56BL,
+ 0x35B5A8FAL, 0x42B2986CL, 0xDBBBC9D6L, 0xACBCF940L,
+ 0x32D86CE3L, 0x45DF5C75L, 0xDCD60DCFL, 0xABD13D59L,
+ 0x26D930ACL, 0x51DE003AL, 0xC8D75180L, 0xBFD06116L,
+ 0x21B4F4B5L, 0x56B3C423L, 0xCFBA9599L, 0xB8BDA50FL,
+ 0x2802B89EL, 0x5F058808L, 0xC60CD9B2L, 0xB10BE924L,
+ 0x2F6F7C87L, 0x58684C11L, 0xC1611DABL, 0xB6662D3DL,
+ 0x76DC4190L, 0x01DB7106L, 0x98D220BCL, 0xEFD5102AL,
+ 0x71B18589L, 0x06B6B51FL, 0x9FBFE4A5L, 0xE8B8D433L,
+ 0x7807C9A2L, 0x0F00F934L, 0x9609A88EL, 0xE10E9818L,
+ 0x7F6A0DBBL, 0x086D3D2DL, 0x91646C97L, 0xE6635C01L,
+ 0x6B6B51F4L, 0x1C6C6162L, 0x856530D8L, 0xF262004EL,
+ 0x6C0695EDL, 0x1B01A57BL, 0x8208F4C1L, 0xF50FC457L,
+ 0x65B0D9C6L, 0x12B7E950L, 0x8BBEB8EAL, 0xFCB9887CL,
+ 0x62DD1DDFL, 0x15DA2D49L, 0x8CD37CF3L, 0xFBD44C65L,
+ 0x4DB26158L, 0x3AB551CEL, 0xA3BC0074L, 0xD4BB30E2L,
+ 0x4ADFA541L, 0x3DD895D7L, 0xA4D1C46DL, 0xD3D6F4FBL,
+ 0x4369E96AL, 0x346ED9FCL, 0xAD678846L, 0xDA60B8D0L,
+ 0x44042D73L, 0x33031DE5L, 0xAA0A4C5FL, 0xDD0D7CC9L,
+ 0x5005713CL, 0x270241AAL, 0xBE0B1010L, 0xC90C2086L,
+ 0x5768B525L, 0x206F85B3L, 0xB966D409L, 0xCE61E49FL,
+ 0x5EDEF90EL, 0x29D9C998L, 0xB0D09822L, 0xC7D7A8B4L,
+ 0x59B33D17L, 0x2EB40D81L, 0xB7BD5C3BL, 0xC0BA6CADL,
+ 0xEDB88320L, 0x9ABFB3B6L, 0x03B6E20CL, 0x74B1D29AL,
+ 0xEAD54739L, 0x9DD277AFL, 0x04DB2615L, 0x73DC1683L,
+ 0xE3630B12L, 0x94643B84L, 0x0D6D6A3EL, 0x7A6A5AA8L,
+ 0xE40ECF0BL, 0x9309FF9DL, 0x0A00AE27L, 0x7D079EB1L,
+ 0xF00F9344L, 0x8708A3D2L, 0x1E01F268L, 0x6906C2FEL,
+ 0xF762575DL, 0x806567CBL, 0x196C3671L, 0x6E6B06E7L,
+ 0xFED41B76L, 0x89D32BE0L, 0x10DA7A5AL, 0x67DD4ACCL,
+ 0xF9B9DF6FL, 0x8EBEEFF9L, 0x17B7BE43L, 0x60B08ED5L,
+ 0xD6D6A3E8L, 0xA1D1937EL, 0x38D8C2C4L, 0x4FDFF252L,
+ 0xD1BB67F1L, 0xA6BC5767L, 0x3FB506DDL, 0x48B2364BL,
+ 0xD80D2BDAL, 0xAF0A1B4CL, 0x36034AF6L, 0x41047A60L,
+ 0xDF60EFC3L, 0xA867DF55L, 0x316E8EEFL, 0x4669BE79L,
+ 0xCB61B38CL, 0xBC66831AL, 0x256FD2A0L, 0x5268E236L,
+ 0xCC0C7795L, 0xBB0B4703L, 0x220216B9L, 0x5505262FL,
+ 0xC5BA3BBEL, 0xB2BD0B28L, 0x2BB45A92L, 0x5CB36A04L,
+ 0xC2D7FFA7L, 0xB5D0CF31L, 0x2CD99E8BL, 0x5BDEAE1DL,
+ 0x9B64C2B0L, 0xEC63F226L, 0x756AA39CL, 0x026D930AL,
+ 0x9C0906A9L, 0xEB0E363FL, 0x72076785L, 0x05005713L,
+ 0x95BF4A82L, 0xE2B87A14L, 0x7BB12BAEL, 0x0CB61B38L,
+ 0x92D28E9BL, 0xE5D5BE0DL, 0x7CDCEFB7L, 0x0BDBDF21L,
+ 0x86D3D2D4L, 0xF1D4E242L, 0x68DDB3F8L, 0x1FDA836EL,
+ 0x81BE16CDL, 0xF6B9265BL, 0x6FB077E1L, 0x18B74777L,
+ 0x88085AE6L, 0xFF0F6A70L, 0x66063BCAL, 0x11010B5CL,
+ 0x8F659EFFL, 0xF862AE69L, 0x616BFFD3L, 0x166CCF45L,
+ 0xA00AE278L, 0xD70DD2EEL, 0x4E048354L, 0x3903B3C2L,
+ 0xA7672661L, 0xD06016F7L, 0x4969474DL, 0x3E6E77DBL,
+ 0xAED16A4AL, 0xD9D65ADCL, 0x40DF0B66L, 0x37D83BF0L,
+ 0xA9BCAE53L, 0xDEBB9EC5L, 0x47B2CF7FL, 0x30B5FFE9L,
+ 0xBDBDF21CL, 0xCABAC28AL, 0x53B39330L, 0x24B4A3A6L,
+ 0xBAD03605L, 0xCDD70693L, 0x54DE5729L, 0x23D967BFL,
+ 0xB3667A2EL, 0xC4614AB8L, 0x5D681B02L, 0x2A6F2B94L,
+ 0xB40BBE37L, 0xC30C8EA1L, 0x5A05DF1BL, 0x2D02EF8DL
+};
+
+#endif
+
+#ifdef GENPROGRAM
+int main(void)
+{
+ unsigned long crcword;
+ int i;
+
+ crc32_init();
+ for (i = 0; i < 256; i++) {
+ printf("%s0x%08XL%s",
+ (i % 4 == 0 ? " " : " "),
+ crc32_table[i],
+ (i % 4 == 3 ? (i == 255 ? "\n" : ",\n") : ","));
+ }
+
+ return 0;
+}
+#endif
+
+unsigned long crc32_update(unsigned long crcword, const void *buf, size_t len)
+{
+ const unsigned char *p = (const unsigned char *) buf;
+ while (len--) {
+ unsigned long newbyte = *p++;
+ newbyte ^= crcword & 0xFFL;
+ crcword = (crcword >> 8) ^ crc32_table[newbyte];
+ }
+ return crcword;
+}
+
+unsigned long crc32_compute(const void *buf, size_t len)
+{
+ return crc32_update(0L, buf, len);
+}
--- /dev/null
+/* $OpenBSD: deattack.c,v 1.14 2001/06/23 15:12:18 itojun Exp $ */
+
+/*
+ * Cryptographic attack detector for ssh - source code
+ *
+ * Copyright (c) 1998 CORE SDI S.A., Buenos Aires, Argentina.
+ *
+ * All rights reserved. Redistribution and use in source and binary
+ * forms, with or without modification, are permitted provided that
+ * this copyright notice is retained.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES ARE DISCLAIMED. IN NO EVENT SHALL CORE SDI S.A. BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR
+ * CONSEQUENTIAL DAMAGES RESULTING FROM THE USE OR MISUSE OF THIS
+ * SOFTWARE.
+ *
+ * Ariel Futoransky <futo@core-sdi.com>
+ * <http://www.core-sdi.com>
+ *
+ * Modified for use in PuTTY by Simon Tatham
+ */
+
+#include <assert.h>
+#include "misc.h"
+#include "ssh.h"
+
+typedef unsigned char uchar;
+typedef unsigned short uint16;
+
+/* SSH Constants */
+#define SSH_MAXBLOCKS (32 * 1024)
+#define SSH_BLOCKSIZE (8)
+
+/* Hashing constants */
+#define HASH_MINSIZE (8 * 1024)
+#define HASH_ENTRYSIZE (sizeof(uint16))
+#define HASH_FACTOR(x) ((x)*3/2)
+#define HASH_UNUSEDCHAR (0xff)
+#define HASH_UNUSED (0xffff)
+#define HASH_IV (0xfffe)
+
+#define HASH_MINBLOCKS (7*SSH_BLOCKSIZE)
+
+/* Hash function (Input keys are cipher results) */
+#define HASH(x) GET_32BIT_MSB_FIRST(x)
+
+#define CMP(a, b) (memcmp(a, b, SSH_BLOCKSIZE))
+
+uchar ONE[4] = { 1, 0, 0, 0 };
+uchar ZERO[4] = { 0, 0, 0, 0 };
+
+struct crcda_ctx {
+ uint16 *h;
+ uint32 n;
+};
+
+void *crcda_make_context(void)
+{
+ struct crcda_ctx *ret = snew(struct crcda_ctx);
+ ret->h = NULL;
+ ret->n = HASH_MINSIZE / HASH_ENTRYSIZE;
+ return ret;
+}
+
+void crcda_free_context(void *handle)
+{
+ struct crcda_ctx *ctx = (struct crcda_ctx *)handle;
+ if (ctx) {
+ sfree(ctx->h);
+ ctx->h = NULL;
+ sfree(ctx);
+ }
+}
+
+static void crc_update(uint32 *a, void *b)
+{
+ *a = crc32_update(*a, b, 4);
+}
+
+/* detect if a block is used in a particular pattern */
+static int check_crc(uchar *S, uchar *buf, uint32 len, uchar *IV)
+{
+ uint32 crc;
+ uchar *c;
+
+ crc = 0;
+ if (IV && !CMP(S, IV)) {
+ crc_update(&crc, ONE);
+ crc_update(&crc, ZERO);
+ }
+ for (c = buf; c < buf + len; c += SSH_BLOCKSIZE) {
+ if (!CMP(S, c)) {
+ crc_update(&crc, ONE);
+ crc_update(&crc, ZERO);
+ } else {
+ crc_update(&crc, ZERO);
+ crc_update(&crc, ZERO);
+ }
+ }
+ return (crc == 0);
+}
+
+/* Detect a crc32 compensation attack on a packet */
+int detect_attack(void *handle, uchar *buf, uint32 len, uchar *IV)
+{
+ struct crcda_ctx *ctx = (struct crcda_ctx *)handle;
+ register uint32 i, j;
+ uint32 l;
+ register uchar *c;
+ uchar *d;
+
+ assert(!(len > (SSH_MAXBLOCKS * SSH_BLOCKSIZE) ||
+ len % SSH_BLOCKSIZE != 0));
+ for (l = ctx->n; l < HASH_FACTOR(len / SSH_BLOCKSIZE); l = l << 2)
+ ;
+
+ if (ctx->h == NULL) {
+ ctx->n = l;
+ ctx->h = snewn(ctx->n, uint16);
+ } else {
+ if (l > ctx->n) {
+ ctx->n = l;
+ ctx->h = sresize(ctx->h, ctx->n, uint16);
+ }
+ }
+
+ if (len <= HASH_MINBLOCKS) {
+ for (c = buf; c < buf + len; c += SSH_BLOCKSIZE) {
+ if (IV && (!CMP(c, IV))) {
+ if ((check_crc(c, buf, len, IV)))
+ return 1; /* attack detected */
+ else
+ break;
+ }
+ for (d = buf; d < c; d += SSH_BLOCKSIZE) {
+ if (!CMP(c, d)) {
+ if ((check_crc(c, buf, len, IV)))
+ return 1; /* attack detected */
+ else
+ break;
+ }
+ }
+ }
+ return 0; /* ok */
+ }
+ memset(ctx->h, HASH_UNUSEDCHAR, ctx->n * HASH_ENTRYSIZE);
+
+ if (IV)
+ ctx->h[HASH(IV) & (ctx->n - 1)] = HASH_IV;
+
+ for (c = buf, j = 0; c < (buf + len); c += SSH_BLOCKSIZE, j++) {
+ for (i = HASH(c) & (ctx->n - 1); ctx->h[i] != HASH_UNUSED;
+ i = (i + 1) & (ctx->n - 1)) {
+ if (ctx->h[i] == HASH_IV) {
+ if (!CMP(c, IV)) {
+ if (check_crc(c, buf, len, IV))
+ return 1; /* attack detected */
+ else
+ break;
+ }
+ } else if (!CMP(c, buf + ctx->h[i] * SSH_BLOCKSIZE)) {
+ if (check_crc(c, buf, len, IV))
+ return 1; /* attack detected */
+ else
+ break;
+ }
+ }
+ ctx->h[i] = j;
+ }
+ return 0; /* ok */
+}
--- /dev/null
+#include <assert.h>
+#include "ssh.h"
+
+
+/* des.c - implementation of DES
+ */
+
+/*
+ * Description of DES
+ * ------------------
+ *
+ * Unlike the description in FIPS 46, I'm going to use _sensible_ indices:
+ * bits in an n-bit word are numbered from 0 at the LSB to n-1 at the MSB.
+ * And S-boxes are indexed by six consecutive bits, not by the outer two
+ * followed by the middle four.
+ *
+ * The DES encryption routine requires a 64-bit input, and a key schedule K
+ * containing 16 48-bit elements.
+ *
+ * First the input is permuted by the initial permutation IP.
+ * Then the input is split into 32-bit words L and R. (L is the MSW.)
+ * Next, 16 rounds. In each round:
+ * (L, R) <- (R, L xor f(R, K[i]))
+ * Then the pre-output words L and R are swapped.
+ * Then L and R are glued back together into a 64-bit word. (L is the MSW,
+ * again, but since we just swapped them, the MSW is the R that came out
+ * of the last round.)
+ * The 64-bit output block is permuted by the inverse of IP and returned.
+ *
+ * Decryption is identical except that the elements of K are used in the
+ * opposite order. (This wouldn't work if that word swap didn't happen.)
+ *
+ * The function f, used in each round, accepts a 32-bit word R and a
+ * 48-bit key block K. It produces a 32-bit output.
+ *
+ * First R is expanded to 48 bits using the bit-selection function E.
+ * The resulting 48-bit block is XORed with the key block K to produce
+ * a 48-bit block X.
+ * This block X is split into eight groups of 6 bits. Each group of 6
+ * bits is then looked up in one of the eight S-boxes to convert
+ * it to 4 bits. These eight groups of 4 bits are glued back
+ * together to produce a 32-bit preoutput block.
+ * The preoutput block is permuted using the permutation P and returned.
+ *
+ * Key setup maps a 64-bit key word into a 16x48-bit key schedule. Although
+ * the approved input format for the key is a 64-bit word, eight of the
+ * bits are discarded, so the actual quantity of key used is 56 bits.
+ *
+ * First the input key is converted to two 28-bit words C and D using
+ * the bit-selection function PC1.
+ * Then 16 rounds of key setup occur. In each round, C and D are each
+ * rotated left by either 1 or 2 bits (depending on which round), and
+ * then converted into a key schedule element using the bit-selection
+ * function PC2.
+ *
+ * That's the actual algorithm. Now for the tedious details: all those
+ * painful permutations and lookup tables.
+ *
+ * IP is a 64-to-64 bit permutation. Its output contains the following
+ * bits of its input (listed in order MSB to LSB of output).
+ *
+ * 6 14 22 30 38 46 54 62 4 12 20 28 36 44 52 60
+ * 2 10 18 26 34 42 50 58 0 8 16 24 32 40 48 56
+ * 7 15 23 31 39 47 55 63 5 13 21 29 37 45 53 61
+ * 3 11 19 27 35 43 51 59 1 9 17 25 33 41 49 57
+ *
+ * E is a 32-to-48 bit selection function. Its output contains the following
+ * bits of its input (listed in order MSB to LSB of output).
+ *
+ * 0 31 30 29 28 27 28 27 26 25 24 23 24 23 22 21 20 19 20 19 18 17 16 15
+ * 16 15 14 13 12 11 12 11 10 9 8 7 8 7 6 5 4 3 4 3 2 1 0 31
+ *
+ * The S-boxes are arbitrary table-lookups each mapping a 6-bit input to a
+ * 4-bit output. In other words, each S-box is an array[64] of 4-bit numbers.
+ * The S-boxes are listed below. The first S-box listed is applied to the
+ * most significant six bits of the block X; the last one is applied to the
+ * least significant.
+ *
+ * 14 0 4 15 13 7 1 4 2 14 15 2 11 13 8 1
+ * 3 10 10 6 6 12 12 11 5 9 9 5 0 3 7 8
+ * 4 15 1 12 14 8 8 2 13 4 6 9 2 1 11 7
+ * 15 5 12 11 9 3 7 14 3 10 10 0 5 6 0 13
+ *
+ * 15 3 1 13 8 4 14 7 6 15 11 2 3 8 4 14
+ * 9 12 7 0 2 1 13 10 12 6 0 9 5 11 10 5
+ * 0 13 14 8 7 10 11 1 10 3 4 15 13 4 1 2
+ * 5 11 8 6 12 7 6 12 9 0 3 5 2 14 15 9
+ *
+ * 10 13 0 7 9 0 14 9 6 3 3 4 15 6 5 10
+ * 1 2 13 8 12 5 7 14 11 12 4 11 2 15 8 1
+ * 13 1 6 10 4 13 9 0 8 6 15 9 3 8 0 7
+ * 11 4 1 15 2 14 12 3 5 11 10 5 14 2 7 12
+ *
+ * 7 13 13 8 14 11 3 5 0 6 6 15 9 0 10 3
+ * 1 4 2 7 8 2 5 12 11 1 12 10 4 14 15 9
+ * 10 3 6 15 9 0 0 6 12 10 11 1 7 13 13 8
+ * 15 9 1 4 3 5 14 11 5 12 2 7 8 2 4 14
+ *
+ * 2 14 12 11 4 2 1 12 7 4 10 7 11 13 6 1
+ * 8 5 5 0 3 15 15 10 13 3 0 9 14 8 9 6
+ * 4 11 2 8 1 12 11 7 10 1 13 14 7 2 8 13
+ * 15 6 9 15 12 0 5 9 6 10 3 4 0 5 14 3
+ *
+ * 12 10 1 15 10 4 15 2 9 7 2 12 6 9 8 5
+ * 0 6 13 1 3 13 4 14 14 0 7 11 5 3 11 8
+ * 9 4 14 3 15 2 5 12 2 9 8 5 12 15 3 10
+ * 7 11 0 14 4 1 10 7 1 6 13 0 11 8 6 13
+ *
+ * 4 13 11 0 2 11 14 7 15 4 0 9 8 1 13 10
+ * 3 14 12 3 9 5 7 12 5 2 10 15 6 8 1 6
+ * 1 6 4 11 11 13 13 8 12 1 3 4 7 10 14 7
+ * 10 9 15 5 6 0 8 15 0 14 5 2 9 3 2 12
+ *
+ * 13 1 2 15 8 13 4 8 6 10 15 3 11 7 1 4
+ * 10 12 9 5 3 6 14 11 5 0 0 14 12 9 7 2
+ * 7 2 11 1 4 14 1 7 9 4 12 10 14 8 2 13
+ * 0 15 6 12 10 9 13 0 15 3 3 5 5 6 8 11
+ *
+ * P is a 32-to-32 bit permutation. Its output contains the following
+ * bits of its input (listed in order MSB to LSB of output).
+ *
+ * 16 25 12 11 3 20 4 15 31 17 9 6 27 14 1 22
+ * 30 24 8 18 0 5 29 23 13 19 2 26 10 21 28 7
+ *
+ * PC1 is a 64-to-56 bit selection function. Its output is in two words,
+ * C and D. The word C contains the following bits of its input (listed
+ * in order MSB to LSB of output).
+ *
+ * 7 15 23 31 39 47 55 63 6 14 22 30 38 46
+ * 54 62 5 13 21 29 37 45 53 61 4 12 20 28
+ *
+ * And the word D contains these bits.
+ *
+ * 1 9 17 25 33 41 49 57 2 10 18 26 34 42
+ * 50 58 3 11 19 27 35 43 51 59 36 44 52 60
+ *
+ * PC2 is a 56-to-48 bit selection function. Its input is in two words,
+ * C and D. These are treated as one 56-bit word (with C more significant,
+ * so that bits 55 to 28 of the word are bits 27 to 0 of C, and bits 27 to
+ * 0 of the word are bits 27 to 0 of D). The output contains the following
+ * bits of this 56-bit input word (listed in order MSB to LSB of output).
+ *
+ * 42 39 45 32 55 51 53 28 41 50 35 46 33 37 44 52 30 48 40 49 29 36 43 54
+ * 15 4 25 19 9 1 26 16 5 11 23 8 12 7 17 0 22 3 10 14 6 20 27 24
+ */
+
+/*
+ * Implementation details
+ * ----------------------
+ *
+ * If you look at the code in this module, you'll find it looks
+ * nothing _like_ the above algorithm. Here I explain the
+ * differences...
+ *
+ * Key setup has not been heavily optimised here. We are not
+ * concerned with key agility: we aren't codebreakers. We don't
+ * mind a little delay (and it really is a little one; it may be a
+ * factor of five or so slower than it could be but it's still not
+ * an appreciable length of time) while setting up. The only tweaks
+ * in the key setup are ones which change the format of the key
+ * schedule to speed up the actual encryption. I'll describe those
+ * below.
+ *
+ * The first and most obvious optimisation is the S-boxes. Since
+ * each S-box always targets the same four bits in the final 32-bit
+ * word, so the output from (for example) S-box 0 must always be
+ * shifted left 28 bits, we can store the already-shifted outputs
+ * in the lookup tables. This reduces lookup-and-shift to lookup,
+ * so the S-box step is now just a question of ORing together eight
+ * table lookups.
+ *
+ * The permutation P is just a bit order change; it's invariant
+ * with respect to OR, in that P(x)|P(y) = P(x|y). Therefore, we
+ * can apply P to every entry of the S-box tables and then we don't
+ * have to do it in the code of f(). This yields a set of tables
+ * which might be called SP-boxes.
+ *
+ * The bit-selection function E is our next target. Note that E is
+ * immediately followed by the operation of splitting into 6-bit
+ * chunks. Examining the 6-bit chunks coming out of E we notice
+ * they're all contiguous within the word (speaking cyclically -
+ * the end two wrap round); so we can extract those bit strings
+ * individually rather than explicitly running E. This would yield
+ * code such as
+ *
+ * y |= SPboxes[0][ (rotl(R, 5) ^ top6bitsofK) & 0x3F ];
+ * t |= SPboxes[1][ (rotl(R,11) ^ next6bitsofK) & 0x3F ];
+ *
+ * and so on; and the key schedule preparation would have to
+ * provide each 6-bit chunk separately.
+ *
+ * Really we'd like to XOR in the key schedule element before
+ * looking up bit strings in R. This we can't do, naively, because
+ * the 6-bit strings we want overlap. But look at the strings:
+ *
+ * 3322222222221111111111
+ * bit 10987654321098765432109876543210
+ *
+ * box0 XXXXX X
+ * box1 XXXXXX
+ * box2 XXXXXX
+ * box3 XXXXXX
+ * box4 XXXXXX
+ * box5 XXXXXX
+ * box6 XXXXXX
+ * box7 X XXXXX
+ *
+ * The bit strings we need to XOR in for boxes 0, 2, 4 and 6 don't
+ * overlap with each other. Neither do the ones for boxes 1, 3, 5
+ * and 7. So we could provide the key schedule in the form of two
+ * words that we can separately XOR into R, and then every S-box
+ * index is available as a (cyclically) contiguous 6-bit substring
+ * of one or the other of the results.
+ *
+ * The comments in Eric Young's libdes implementation point out
+ * that two of these bit strings require a rotation (rather than a
+ * simple shift) to extract. It's unavoidable that at least _one_
+ * must do; but we can actually run the whole inner algorithm (all
+ * 16 rounds) rotated one bit to the left, so that what the `real'
+ * DES description sees as L=0x80000001 we see as L=0x00000003.
+ * This requires rotating all our SP-box entries one bit to the
+ * left, and rotating each word of the key schedule elements one to
+ * the left, and rotating L and R one bit left just after IP and
+ * one bit right again just before FP. And in each round we convert
+ * a rotate into a shift, so we've saved a few per cent.
+ *
+ * That's about it for the inner loop; the SP-box tables as listed
+ * below are what I've described here (the original S value,
+ * shifted to its final place in the input to P, run through P, and
+ * then rotated one bit left). All that remains is to optimise the
+ * initial permutation IP.
+ *
+ * IP is not an arbitrary permutation. It has the nice property
+ * that if you take any bit number, write it in binary (6 bits),
+ * permute those 6 bits and invert some of them, you get the final
+ * position of that bit. Specifically, the bit whose initial
+ * position is given (in binary) as fedcba ends up in position
+ * AcbFED (where a capital letter denotes the inverse of a bit).
+ *
+ * We have the 64-bit data in two 32-bit words L and R, where bits
+ * in L are those with f=1 and bits in R are those with f=0. We
+ * note that we can do a simple transformation: suppose we exchange
+ * the bits with f=1,c=0 and the bits with f=0,c=1. This will cause
+ * the bit fedcba to be in position cedfba - we've `swapped' bits c
+ * and f in the position of each bit!
+ *
+ * Better still, this transformation is easy. In the example above,
+ * bits in L with c=0 are bits 0x0F0F0F0F, and those in R with c=1
+ * are 0xF0F0F0F0. So we can do
+ *
+ * difference = ((R >> 4) ^ L) & 0x0F0F0F0F
+ * R ^= (difference << 4)
+ * L ^= difference
+ *
+ * to perform the swap. Let's denote this by bitswap(4,0x0F0F0F0F).
+ * Also, we can invert the bit at the top just by exchanging L and
+ * R. So in a few swaps and a few of these bit operations we can
+ * do:
+ *
+ * Initially the position of bit fedcba is fedcba
+ * Swap L with R to make it Fedcba
+ * Perform bitswap( 4,0x0F0F0F0F) to make it cedFba
+ * Perform bitswap(16,0x0000FFFF) to make it ecdFba
+ * Swap L with R to make it EcdFba
+ * Perform bitswap( 2,0x33333333) to make it bcdFEa
+ * Perform bitswap( 8,0x00FF00FF) to make it dcbFEa
+ * Swap L with R to make it DcbFEa
+ * Perform bitswap( 1,0x55555555) to make it acbFED
+ * Swap L with R to make it AcbFED
+ *
+ * (In the actual code the four swaps are implicit: R and L are
+ * simply used the other way round in the first, second and last
+ * bitswap operations.)
+ *
+ * The final permutation is just the inverse of IP, so it can be
+ * performed by a similar set of operations.
+ */
+
+typedef struct {
+ word32 k0246[16], k1357[16];
+ word32 iv0, iv1;
+} DESContext;
+
+#define rotl(x, c) ( (x << c) | (x >> (32-c)) )
+#define rotl28(x, c) ( ( (x << c) | (x >> (28-c)) ) & 0x0FFFFFFF)
+
+static word32 bitsel(word32 * input, const int *bitnums, int size)
+{
+ word32 ret = 0;
+ while (size--) {
+ int bitpos = *bitnums++;
+ ret <<= 1;
+ if (bitpos >= 0)
+ ret |= 1 & (input[bitpos / 32] >> (bitpos % 32));
+ }
+ return ret;
+}
+
+static void des_key_setup(word32 key_msw, word32 key_lsw, DESContext * sched)
+{
+
+ static const int PC1_Cbits[] = {
+ 7, 15, 23, 31, 39, 47, 55, 63, 6, 14, 22, 30, 38, 46,
+ 54, 62, 5, 13, 21, 29, 37, 45, 53, 61, 4, 12, 20, 28
+ };
+ static const int PC1_Dbits[] = {
+ 1, 9, 17, 25, 33, 41, 49, 57, 2, 10, 18, 26, 34, 42,
+ 50, 58, 3, 11, 19, 27, 35, 43, 51, 59, 36, 44, 52, 60
+ };
+ /*
+ * The bit numbers in the two lists below don't correspond to
+ * the ones in the above description of PC2, because in the
+ * above description C and D are concatenated so `bit 28' means
+ * bit 0 of C. In this implementation we're using the standard
+ * `bitsel' function above and C is in the second word, so bit
+ * 0 of C is addressed by writing `32' here.
+ */
+ static const int PC2_0246[] = {
+ 49, 36, 59, 55, -1, -1, 37, 41, 48, 56, 34, 52, -1, -1, 15, 4,
+ 25, 19, 9, 1, -1, -1, 12, 7, 17, 0, 22, 3, -1, -1, 46, 43
+ };
+ static const int PC2_1357[] = {
+ -1, -1, 57, 32, 45, 54, 39, 50, -1, -1, 44, 53, 33, 40, 47, 58,
+ -1, -1, 26, 16, 5, 11, 23, 8, -1, -1, 10, 14, 6, 20, 27, 24
+ };
+ static const int leftshifts[] =
+ { 1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1 };
+
+ word32 C, D;
+ word32 buf[2];
+ int i;
+
+ buf[0] = key_lsw;
+ buf[1] = key_msw;
+
+ C = bitsel(buf, PC1_Cbits, 28);
+ D = bitsel(buf, PC1_Dbits, 28);
+
+ for (i = 0; i < 16; i++) {
+ C = rotl28(C, leftshifts[i]);
+ D = rotl28(D, leftshifts[i]);
+ buf[0] = D;
+ buf[1] = C;
+ sched->k0246[i] = bitsel(buf, PC2_0246, 32);
+ sched->k1357[i] = bitsel(buf, PC2_1357, 32);
+ }
+
+ sched->iv0 = sched->iv1 = 0;
+}
+
+static const word32 SPboxes[8][64] = {
+ {0x01010400, 0x00000000, 0x00010000, 0x01010404,
+ 0x01010004, 0x00010404, 0x00000004, 0x00010000,
+ 0x00000400, 0x01010400, 0x01010404, 0x00000400,
+ 0x01000404, 0x01010004, 0x01000000, 0x00000004,
+ 0x00000404, 0x01000400, 0x01000400, 0x00010400,
+ 0x00010400, 0x01010000, 0x01010000, 0x01000404,
+ 0x00010004, 0x01000004, 0x01000004, 0x00010004,
+ 0x00000000, 0x00000404, 0x00010404, 0x01000000,
+ 0x00010000, 0x01010404, 0x00000004, 0x01010000,
+ 0x01010400, 0x01000000, 0x01000000, 0x00000400,
+ 0x01010004, 0x00010000, 0x00010400, 0x01000004,
+ 0x00000400, 0x00000004, 0x01000404, 0x00010404,
+ 0x01010404, 0x00010004, 0x01010000, 0x01000404,
+ 0x01000004, 0x00000404, 0x00010404, 0x01010400,
+ 0x00000404, 0x01000400, 0x01000400, 0x00000000,
+ 0x00010004, 0x00010400, 0x00000000, 0x01010004L},
+
+ {0x80108020, 0x80008000, 0x00008000, 0x00108020,
+ 0x00100000, 0x00000020, 0x80100020, 0x80008020,
+ 0x80000020, 0x80108020, 0x80108000, 0x80000000,
+ 0x80008000, 0x00100000, 0x00000020, 0x80100020,
+ 0x00108000, 0x00100020, 0x80008020, 0x00000000,
+ 0x80000000, 0x00008000, 0x00108020, 0x80100000,
+ 0x00100020, 0x80000020, 0x00000000, 0x00108000,
+ 0x00008020, 0x80108000, 0x80100000, 0x00008020,
+ 0x00000000, 0x00108020, 0x80100020, 0x00100000,
+ 0x80008020, 0x80100000, 0x80108000, 0x00008000,
+ 0x80100000, 0x80008000, 0x00000020, 0x80108020,
+ 0x00108020, 0x00000020, 0x00008000, 0x80000000,
+ 0x00008020, 0x80108000, 0x00100000, 0x80000020,
+ 0x00100020, 0x80008020, 0x80000020, 0x00100020,
+ 0x00108000, 0x00000000, 0x80008000, 0x00008020,
+ 0x80000000, 0x80100020, 0x80108020, 0x00108000L},
+
+ {0x00000208, 0x08020200, 0x00000000, 0x08020008,
+ 0x08000200, 0x00000000, 0x00020208, 0x08000200,
+ 0x00020008, 0x08000008, 0x08000008, 0x00020000,
+ 0x08020208, 0x00020008, 0x08020000, 0x00000208,
+ 0x08000000, 0x00000008, 0x08020200, 0x00000200,
+ 0x00020200, 0x08020000, 0x08020008, 0x00020208,
+ 0x08000208, 0x00020200, 0x00020000, 0x08000208,
+ 0x00000008, 0x08020208, 0x00000200, 0x08000000,
+ 0x08020200, 0x08000000, 0x00020008, 0x00000208,
+ 0x00020000, 0x08020200, 0x08000200, 0x00000000,
+ 0x00000200, 0x00020008, 0x08020208, 0x08000200,
+ 0x08000008, 0x00000200, 0x00000000, 0x08020008,
+ 0x08000208, 0x00020000, 0x08000000, 0x08020208,
+ 0x00000008, 0x00020208, 0x00020200, 0x08000008,
+ 0x08020000, 0x08000208, 0x00000208, 0x08020000,
+ 0x00020208, 0x00000008, 0x08020008, 0x00020200L},
+
+ {0x00802001, 0x00002081, 0x00002081, 0x00000080,
+ 0x00802080, 0x00800081, 0x00800001, 0x00002001,
+ 0x00000000, 0x00802000, 0x00802000, 0x00802081,
+ 0x00000081, 0x00000000, 0x00800080, 0x00800001,
+ 0x00000001, 0x00002000, 0x00800000, 0x00802001,
+ 0x00000080, 0x00800000, 0x00002001, 0x00002080,
+ 0x00800081, 0x00000001, 0x00002080, 0x00800080,
+ 0x00002000, 0x00802080, 0x00802081, 0x00000081,
+ 0x00800080, 0x00800001, 0x00802000, 0x00802081,
+ 0x00000081, 0x00000000, 0x00000000, 0x00802000,
+ 0x00002080, 0x00800080, 0x00800081, 0x00000001,
+ 0x00802001, 0x00002081, 0x00002081, 0x00000080,
+ 0x00802081, 0x00000081, 0x00000001, 0x00002000,
+ 0x00800001, 0x00002001, 0x00802080, 0x00800081,
+ 0x00002001, 0x00002080, 0x00800000, 0x00802001,
+ 0x00000080, 0x00800000, 0x00002000, 0x00802080L},
+
+ {0x00000100, 0x02080100, 0x02080000, 0x42000100,
+ 0x00080000, 0x00000100, 0x40000000, 0x02080000,
+ 0x40080100, 0x00080000, 0x02000100, 0x40080100,
+ 0x42000100, 0x42080000, 0x00080100, 0x40000000,
+ 0x02000000, 0x40080000, 0x40080000, 0x00000000,
+ 0x40000100, 0x42080100, 0x42080100, 0x02000100,
+ 0x42080000, 0x40000100, 0x00000000, 0x42000000,
+ 0x02080100, 0x02000000, 0x42000000, 0x00080100,
+ 0x00080000, 0x42000100, 0x00000100, 0x02000000,
+ 0x40000000, 0x02080000, 0x42000100, 0x40080100,
+ 0x02000100, 0x40000000, 0x42080000, 0x02080100,
+ 0x40080100, 0x00000100, 0x02000000, 0x42080000,
+ 0x42080100, 0x00080100, 0x42000000, 0x42080100,
+ 0x02080000, 0x00000000, 0x40080000, 0x42000000,
+ 0x00080100, 0x02000100, 0x40000100, 0x00080000,
+ 0x00000000, 0x40080000, 0x02080100, 0x40000100L},
+
+ {0x20000010, 0x20400000, 0x00004000, 0x20404010,
+ 0x20400000, 0x00000010, 0x20404010, 0x00400000,
+ 0x20004000, 0x00404010, 0x00400000, 0x20000010,
+ 0x00400010, 0x20004000, 0x20000000, 0x00004010,
+ 0x00000000, 0x00400010, 0x20004010, 0x00004000,
+ 0x00404000, 0x20004010, 0x00000010, 0x20400010,
+ 0x20400010, 0x00000000, 0x00404010, 0x20404000,
+ 0x00004010, 0x00404000, 0x20404000, 0x20000000,
+ 0x20004000, 0x00000010, 0x20400010, 0x00404000,
+ 0x20404010, 0x00400000, 0x00004010, 0x20000010,
+ 0x00400000, 0x20004000, 0x20000000, 0x00004010,
+ 0x20000010, 0x20404010, 0x00404000, 0x20400000,
+ 0x00404010, 0x20404000, 0x00000000, 0x20400010,
+ 0x00000010, 0x00004000, 0x20400000, 0x00404010,
+ 0x00004000, 0x00400010, 0x20004010, 0x00000000,
+ 0x20404000, 0x20000000, 0x00400010, 0x20004010L},
+
+ {0x00200000, 0x04200002, 0x04000802, 0x00000000,
+ 0x00000800, 0x04000802, 0x00200802, 0x04200800,
+ 0x04200802, 0x00200000, 0x00000000, 0x04000002,
+ 0x00000002, 0x04000000, 0x04200002, 0x00000802,
+ 0x04000800, 0x00200802, 0x00200002, 0x04000800,
+ 0x04000002, 0x04200000, 0x04200800, 0x00200002,
+ 0x04200000, 0x00000800, 0x00000802, 0x04200802,
+ 0x00200800, 0x00000002, 0x04000000, 0x00200800,
+ 0x04000000, 0x00200800, 0x00200000, 0x04000802,
+ 0x04000802, 0x04200002, 0x04200002, 0x00000002,
+ 0x00200002, 0x04000000, 0x04000800, 0x00200000,
+ 0x04200800, 0x00000802, 0x00200802, 0x04200800,
+ 0x00000802, 0x04000002, 0x04200802, 0x04200000,
+ 0x00200800, 0x00000000, 0x00000002, 0x04200802,
+ 0x00000000, 0x00200802, 0x04200000, 0x00000800,
+ 0x04000002, 0x04000800, 0x00000800, 0x00200002L},
+
+ {0x10001040, 0x00001000, 0x00040000, 0x10041040,
+ 0x10000000, 0x10001040, 0x00000040, 0x10000000,
+ 0x00040040, 0x10040000, 0x10041040, 0x00041000,
+ 0x10041000, 0x00041040, 0x00001000, 0x00000040,
+ 0x10040000, 0x10000040, 0x10001000, 0x00001040,
+ 0x00041000, 0x00040040, 0x10040040, 0x10041000,
+ 0x00001040, 0x00000000, 0x00000000, 0x10040040,
+ 0x10000040, 0x10001000, 0x00041040, 0x00040000,
+ 0x00041040, 0x00040000, 0x10041000, 0x00001000,
+ 0x00000040, 0x10040040, 0x00001000, 0x00041040,
+ 0x10001000, 0x00000040, 0x10000040, 0x10040000,
+ 0x10040040, 0x10000000, 0x00040000, 0x10001040,
+ 0x00000000, 0x10041040, 0x00040040, 0x10000040,
+ 0x10040000, 0x10001000, 0x10001040, 0x00000000,
+ 0x10041040, 0x00041000, 0x00041000, 0x00001040,
+ 0x00001040, 0x00040040, 0x10000000, 0x10041000L}
+};
+
+#define f(R, K0246, K1357) (\
+ s0246 = R ^ K0246, \
+ s1357 = R ^ K1357, \
+ s0246 = rotl(s0246, 28), \
+ SPboxes[0] [(s0246 >> 24) & 0x3F] | \
+ SPboxes[1] [(s1357 >> 24) & 0x3F] | \
+ SPboxes[2] [(s0246 >> 16) & 0x3F] | \
+ SPboxes[3] [(s1357 >> 16) & 0x3F] | \
+ SPboxes[4] [(s0246 >> 8) & 0x3F] | \
+ SPboxes[5] [(s1357 >> 8) & 0x3F] | \
+ SPboxes[6] [(s0246 ) & 0x3F] | \
+ SPboxes[7] [(s1357 ) & 0x3F])
+
+#define bitswap(L, R, n, mask) (\
+ swap = mask & ( (R >> n) ^ L ), \
+ R ^= swap << n, \
+ L ^= swap)
+
+/* Initial permutation */
+#define IP(L, R) (\
+ bitswap(R, L, 4, 0x0F0F0F0F), \
+ bitswap(R, L, 16, 0x0000FFFF), \
+ bitswap(L, R, 2, 0x33333333), \
+ bitswap(L, R, 8, 0x00FF00FF), \
+ bitswap(R, L, 1, 0x55555555))
+
+/* Final permutation */
+#define FP(L, R) (\
+ bitswap(R, L, 1, 0x55555555), \
+ bitswap(L, R, 8, 0x00FF00FF), \
+ bitswap(L, R, 2, 0x33333333), \
+ bitswap(R, L, 16, 0x0000FFFF), \
+ bitswap(R, L, 4, 0x0F0F0F0F))
+
+static void des_encipher(word32 * output, word32 L, word32 R,
+ DESContext * sched)
+{
+ word32 swap, s0246, s1357;
+
+ IP(L, R);
+
+ L = rotl(L, 1);
+ R = rotl(R, 1);
+
+ L ^= f(R, sched->k0246[0], sched->k1357[0]);
+ R ^= f(L, sched->k0246[1], sched->k1357[1]);
+ L ^= f(R, sched->k0246[2], sched->k1357[2]);
+ R ^= f(L, sched->k0246[3], sched->k1357[3]);
+ L ^= f(R, sched->k0246[4], sched->k1357[4]);
+ R ^= f(L, sched->k0246[5], sched->k1357[5]);
+ L ^= f(R, sched->k0246[6], sched->k1357[6]);
+ R ^= f(L, sched->k0246[7], sched->k1357[7]);
+ L ^= f(R, sched->k0246[8], sched->k1357[8]);
+ R ^= f(L, sched->k0246[9], sched->k1357[9]);
+ L ^= f(R, sched->k0246[10], sched->k1357[10]);
+ R ^= f(L, sched->k0246[11], sched->k1357[11]);
+ L ^= f(R, sched->k0246[12], sched->k1357[12]);
+ R ^= f(L, sched->k0246[13], sched->k1357[13]);
+ L ^= f(R, sched->k0246[14], sched->k1357[14]);
+ R ^= f(L, sched->k0246[15], sched->k1357[15]);
+
+ L = rotl(L, 31);
+ R = rotl(R, 31);
+
+ swap = L;
+ L = R;
+ R = swap;
+
+ FP(L, R);
+
+ output[0] = L;
+ output[1] = R;
+}
+
+static void des_decipher(word32 * output, word32 L, word32 R,
+ DESContext * sched)
+{
+ word32 swap, s0246, s1357;
+
+ IP(L, R);
+
+ L = rotl(L, 1);
+ R = rotl(R, 1);
+
+ L ^= f(R, sched->k0246[15], sched->k1357[15]);
+ R ^= f(L, sched->k0246[14], sched->k1357[14]);
+ L ^= f(R, sched->k0246[13], sched->k1357[13]);
+ R ^= f(L, sched->k0246[12], sched->k1357[12]);
+ L ^= f(R, sched->k0246[11], sched->k1357[11]);
+ R ^= f(L, sched->k0246[10], sched->k1357[10]);
+ L ^= f(R, sched->k0246[9], sched->k1357[9]);
+ R ^= f(L, sched->k0246[8], sched->k1357[8]);
+ L ^= f(R, sched->k0246[7], sched->k1357[7]);
+ R ^= f(L, sched->k0246[6], sched->k1357[6]);
+ L ^= f(R, sched->k0246[5], sched->k1357[5]);
+ R ^= f(L, sched->k0246[4], sched->k1357[4]);
+ L ^= f(R, sched->k0246[3], sched->k1357[3]);
+ R ^= f(L, sched->k0246[2], sched->k1357[2]);
+ L ^= f(R, sched->k0246[1], sched->k1357[1]);
+ R ^= f(L, sched->k0246[0], sched->k1357[0]);
+
+ L = rotl(L, 31);
+ R = rotl(R, 31);
+
+ swap = L;
+ L = R;
+ R = swap;
+
+ FP(L, R);
+
+ output[0] = L;
+ output[1] = R;
+}
+
+static void des_cbc_encrypt(unsigned char *blk,
+ unsigned int len, DESContext * sched)
+{
+ word32 out[2], iv0, iv1;
+ unsigned int i;
+
+ assert((len & 7) == 0);
+
+ iv0 = sched->iv0;
+ iv1 = sched->iv1;
+ for (i = 0; i < len; i += 8) {
+ iv0 ^= GET_32BIT_MSB_FIRST(blk);
+ iv1 ^= GET_32BIT_MSB_FIRST(blk + 4);
+ des_encipher(out, iv0, iv1, sched);
+ iv0 = out[0];
+ iv1 = out[1];
+ PUT_32BIT_MSB_FIRST(blk, iv0);
+ PUT_32BIT_MSB_FIRST(blk + 4, iv1);
+ blk += 8;
+ }
+ sched->iv0 = iv0;
+ sched->iv1 = iv1;
+}
+
+static void des_cbc_decrypt(unsigned char *blk,
+ unsigned int len, DESContext * sched)
+{
+ word32 out[2], iv0, iv1, xL, xR;
+ unsigned int i;
+
+ assert((len & 7) == 0);
+
+ iv0 = sched->iv0;
+ iv1 = sched->iv1;
+ for (i = 0; i < len; i += 8) {
+ xL = GET_32BIT_MSB_FIRST(blk);
+ xR = GET_32BIT_MSB_FIRST(blk + 4);
+ des_decipher(out, xL, xR, sched);
+ iv0 ^= out[0];
+ iv1 ^= out[1];
+ PUT_32BIT_MSB_FIRST(blk, iv0);
+ PUT_32BIT_MSB_FIRST(blk + 4, iv1);
+ blk += 8;
+ iv0 = xL;
+ iv1 = xR;
+ }
+ sched->iv0 = iv0;
+ sched->iv1 = iv1;
+}
+
+static void des_3cbc_encrypt(unsigned char *blk,
+ unsigned int len, DESContext * scheds)
+{
+ des_cbc_encrypt(blk, len, &scheds[0]);
+ des_cbc_decrypt(blk, len, &scheds[1]);
+ des_cbc_encrypt(blk, len, &scheds[2]);
+}
+
+static void des_cbc3_encrypt(unsigned char *blk,
+ unsigned int len, DESContext * scheds)
+{
+ word32 out[2], iv0, iv1;
+ unsigned int i;
+
+ assert((len & 7) == 0);
+
+ iv0 = scheds->iv0;
+ iv1 = scheds->iv1;
+ for (i = 0; i < len; i += 8) {
+ iv0 ^= GET_32BIT_MSB_FIRST(blk);
+ iv1 ^= GET_32BIT_MSB_FIRST(blk + 4);
+ des_encipher(out, iv0, iv1, &scheds[0]);
+ des_decipher(out, out[0], out[1], &scheds[1]);
+ des_encipher(out, out[0], out[1], &scheds[2]);
+ iv0 = out[0];
+ iv1 = out[1];
+ PUT_32BIT_MSB_FIRST(blk, iv0);
+ PUT_32BIT_MSB_FIRST(blk + 4, iv1);
+ blk += 8;
+ }
+ scheds->iv0 = iv0;
+ scheds->iv1 = iv1;
+}
+
+static void des_3cbc_decrypt(unsigned char *blk,
+ unsigned int len, DESContext * scheds)
+{
+ des_cbc_decrypt(blk, len, &scheds[2]);
+ des_cbc_encrypt(blk, len, &scheds[1]);
+ des_cbc_decrypt(blk, len, &scheds[0]);
+}
+
+static void des_cbc3_decrypt(unsigned char *blk,
+ unsigned int len, DESContext * scheds)
+{
+ word32 out[2], iv0, iv1, xL, xR;
+ unsigned int i;
+
+ assert((len & 7) == 0);
+
+ iv0 = scheds->iv0;
+ iv1 = scheds->iv1;
+ for (i = 0; i < len; i += 8) {
+ xL = GET_32BIT_MSB_FIRST(blk);
+ xR = GET_32BIT_MSB_FIRST(blk + 4);
+ des_decipher(out, xL, xR, &scheds[2]);
+ des_encipher(out, out[0], out[1], &scheds[1]);
+ des_decipher(out, out[0], out[1], &scheds[0]);
+ iv0 ^= out[0];
+ iv1 ^= out[1];
+ PUT_32BIT_MSB_FIRST(blk, iv0);
+ PUT_32BIT_MSB_FIRST(blk + 4, iv1);
+ blk += 8;
+ iv0 = xL;
+ iv1 = xR;
+ }
+ scheds->iv0 = iv0;
+ scheds->iv1 = iv1;
+}
+
+static void des_sdctr3(unsigned char *blk,
+ unsigned int len, DESContext * scheds)
+{
+ word32 b[2], iv0, iv1, tmp;
+ unsigned int i;
+
+ assert((len & 7) == 0);
+
+ iv0 = scheds->iv0;
+ iv1 = scheds->iv1;
+ for (i = 0; i < len; i += 8) {
+ des_encipher(b, iv0, iv1, &scheds[0]);
+ des_decipher(b, b[0], b[1], &scheds[1]);
+ des_encipher(b, b[0], b[1], &scheds[2]);
+ tmp = GET_32BIT_MSB_FIRST(blk);
+ PUT_32BIT_MSB_FIRST(blk, tmp ^ b[0]);
+ blk += 4;
+ tmp = GET_32BIT_MSB_FIRST(blk);
+ PUT_32BIT_MSB_FIRST(blk, tmp ^ b[1]);
+ blk += 4;
+ if ((iv1 = (iv1 + 1) & 0xffffffff) == 0)
+ iv0 = (iv0 + 1) & 0xffffffff;
+ }
+ scheds->iv0 = iv0;
+ scheds->iv1 = iv1;
+}
+
+static void *des3_make_context(void)
+{
+ return snewn(3, DESContext);
+}
+
+static void *des3_ssh1_make_context(void)
+{
+ /* Need 3 keys for each direction, in SSH-1 */
+ return snewn(6, DESContext);
+}
+
+static void *des_make_context(void)
+{
+ return snew(DESContext);
+}
+
+static void *des_ssh1_make_context(void)
+{
+ /* Need one key for each direction, in SSH-1 */
+ return snewn(2, DESContext);
+}
+
+static void des3_free_context(void *handle) /* used for both 3DES and DES */
+{
+ sfree(handle);
+}
+
+static void des3_key(void *handle, unsigned char *key)
+{
+ DESContext *keys = (DESContext *) handle;
+ des_key_setup(GET_32BIT_MSB_FIRST(key),
+ GET_32BIT_MSB_FIRST(key + 4), &keys[0]);
+ des_key_setup(GET_32BIT_MSB_FIRST(key + 8),
+ GET_32BIT_MSB_FIRST(key + 12), &keys[1]);
+ des_key_setup(GET_32BIT_MSB_FIRST(key + 16),
+ GET_32BIT_MSB_FIRST(key + 20), &keys[2]);
+}
+
+static void des3_iv(void *handle, unsigned char *key)
+{
+ DESContext *keys = (DESContext *) handle;
+ keys[0].iv0 = GET_32BIT_MSB_FIRST(key);
+ keys[0].iv1 = GET_32BIT_MSB_FIRST(key + 4);
+}
+
+static void des_key(void *handle, unsigned char *key)
+{
+ DESContext *keys = (DESContext *) handle;
+ des_key_setup(GET_32BIT_MSB_FIRST(key),
+ GET_32BIT_MSB_FIRST(key + 4), &keys[0]);
+}
+
+static void des3_sesskey(void *handle, unsigned char *key)
+{
+ DESContext *keys = (DESContext *) handle;
+ des3_key(keys, key);
+ des3_key(keys+3, key);
+}
+
+static void des3_encrypt_blk(void *handle, unsigned char *blk, int len)
+{
+ DESContext *keys = (DESContext *) handle;
+ des_3cbc_encrypt(blk, len, keys);
+}
+
+static void des3_decrypt_blk(void *handle, unsigned char *blk, int len)
+{
+ DESContext *keys = (DESContext *) handle;
+ des_3cbc_decrypt(blk, len, keys+3);
+}
+
+static void des3_ssh2_encrypt_blk(void *handle, unsigned char *blk, int len)
+{
+ DESContext *keys = (DESContext *) handle;
+ des_cbc3_encrypt(blk, len, keys);
+}
+
+static void des3_ssh2_decrypt_blk(void *handle, unsigned char *blk, int len)
+{
+ DESContext *keys = (DESContext *) handle;
+ des_cbc3_decrypt(blk, len, keys);
+}
+
+static void des3_ssh2_sdctr(void *handle, unsigned char *blk, int len)
+{
+ DESContext *keys = (DESContext *) handle;
+ des_sdctr3(blk, len, keys);
+}
+
+static void des_ssh2_encrypt_blk(void *handle, unsigned char *blk, int len)
+{
+ DESContext *keys = (DESContext *) handle;
+ des_cbc_encrypt(blk, len, keys);
+}
+
+static void des_ssh2_decrypt_blk(void *handle, unsigned char *blk, int len)
+{
+ DESContext *keys = (DESContext *) handle;
+ des_cbc_decrypt(blk, len, keys);
+}
+
+void des3_decrypt_pubkey(unsigned char *key, unsigned char *blk, int len)
+{
+ DESContext ourkeys[3];
+ des_key_setup(GET_32BIT_MSB_FIRST(key),
+ GET_32BIT_MSB_FIRST(key + 4), &ourkeys[0]);
+ 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));
+}
+
+void des3_encrypt_pubkey(unsigned char *key, unsigned char *blk, int len)
+{
+ DESContext ourkeys[3];
+ des_key_setup(GET_32BIT_MSB_FIRST(key),
+ GET_32BIT_MSB_FIRST(key + 4), &ourkeys[0]);
+ 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));
+}
+
+void des3_decrypt_pubkey_ossh(unsigned char *key, unsigned char *iv,
+ unsigned char *blk, int len)
+{
+ DESContext ourkeys[3];
+ des_key_setup(GET_32BIT_MSB_FIRST(key),
+ GET_32BIT_MSB_FIRST(key + 4), &ourkeys[0]);
+ 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 + 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));
+}
+
+void des3_encrypt_pubkey_ossh(unsigned char *key, unsigned char *iv,
+ unsigned char *blk, int len)
+{
+ DESContext ourkeys[3];
+ des_key_setup(GET_32BIT_MSB_FIRST(key),
+ GET_32BIT_MSB_FIRST(key + 4), &ourkeys[0]);
+ 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 + 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));
+}
+
+static void des_keysetup_xdmauth(unsigned char *keydata, DESContext *dc)
+{
+ unsigned char key[8];
+ int i, nbits, j;
+ unsigned int bits;
+
+ bits = 0;
+ nbits = 0;
+ j = 0;
+ for (i = 0; i < 8; i++) {
+ if (nbits < 7) {
+ bits = (bits << 8) | keydata[j];
+ nbits += 8;
+ j++;
+ }
+ key[i] = (bits >> (nbits - 7)) << 1;
+ bits &= ~(0x7F << (nbits - 7));
+ nbits -= 7;
+ }
+
+ 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)
+{
+ DESContext dc;
+ des_keysetup_xdmauth(keydata, &dc);
+ des_cbc_encrypt(blk, 24, &dc);
+}
+
+void des_decrypt_xdmauth(unsigned char *keydata, unsigned char *blk, int len)
+{
+ DESContext dc;
+ des_keysetup_xdmauth(keydata, &dc);
+ des_cbc_decrypt(blk, 24, &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,
+ "3des-cbc",
+ 8, 168, SSH_CIPHER_IS_CBC, "triple-DES CBC"
+};
+
+static const struct ssh2_cipher ssh_3des_ssh2_ctr = {
+ des3_make_context, des3_free_context, des3_iv, des3_key,
+ des3_ssh2_sdctr, des3_ssh2_sdctr,
+ "3des-ctr",
+ 8, 168, 0, "triple-DES SDCTR"
+};
+
+/*
+ * Single DES in SSH-2. "des-cbc" is marked as HISTORIC in
+ * RFC 4250, referring to
+ * FIPS-46-3. ("Single DES (i.e., DES) will be permitted
+ * for legacy systems only.") , but ssh.com support it and
+ * apparently aren't the only people to do so, so we sigh
+ * and implement it anyway.
+ */
+static const struct ssh2_cipher ssh_des_ssh2 = {
+ des_make_context, des3_free_context, des3_iv, des_key,
+ des_ssh2_encrypt_blk, des_ssh2_decrypt_blk,
+ "des-cbc",
+ 8, 56, SSH_CIPHER_IS_CBC, "single-DES CBC"
+};
+
+static const struct ssh2_cipher ssh_des_sshcom_ssh2 = {
+ des_make_context, des3_free_context, des3_iv, des_key,
+ des_ssh2_encrypt_blk, des_ssh2_decrypt_blk,
+ "des-cbc@ssh.com",
+ 8, 56, SSH_CIPHER_IS_CBC, "single-DES CBC"
+};
+
+static const struct ssh2_cipher *const des3_list[] = {
+ &ssh_3des_ssh2_ctr,
+ &ssh_3des_ssh2
+};
+
+const struct ssh2_ciphers ssh2_3des = {
+ sizeof(des3_list) / sizeof(*des3_list),
+ des3_list
+};
+
+static const struct ssh2_cipher *const des_list[] = {
+ &ssh_des_ssh2,
+ &ssh_des_sshcom_ssh2
+};
+
+const struct ssh2_ciphers ssh2_des = {
+ sizeof(des_list) / sizeof(*des_list),
+ des_list
+};
+
+const struct ssh_cipher ssh_3des = {
+ des3_ssh1_make_context, des3_free_context, des3_sesskey,
+ des3_encrypt_blk, des3_decrypt_blk,
+ 8, "triple-DES inner-CBC"
+};
+
+static void des_sesskey(void *handle, unsigned char *key)
+{
+ DESContext *keys = (DESContext *) handle;
+ des_key(keys, key);
+ des_key(keys+1, key);
+}
+
+static void des_encrypt_blk(void *handle, unsigned char *blk, int len)
+{
+ DESContext *keys = (DESContext *) handle;
+ des_cbc_encrypt(blk, len, keys);
+}
+
+static void des_decrypt_blk(void *handle, unsigned char *blk, int len)
+{
+ DESContext *keys = (DESContext *) handle;
+ des_cbc_decrypt(blk, len, keys+1);
+}
+
+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"
+};
--- /dev/null
+/*
+ * Diffie-Hellman implementation for PuTTY.
+ */
+
+#include "ssh.h"
+
+/*
+ * The primes used in the group1 and group14 key exchange.
+ */
+static const unsigned char P1[] = {
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC9, 0x0F, 0xDA, 0xA2,
+ 0x21, 0x68, 0xC2, 0x34, 0xC4, 0xC6, 0x62, 0x8B, 0x80, 0xDC, 0x1C, 0xD1,
+ 0x29, 0x02, 0x4E, 0x08, 0x8A, 0x67, 0xCC, 0x74, 0x02, 0x0B, 0xBE, 0xA6,
+ 0x3B, 0x13, 0x9B, 0x22, 0x51, 0x4A, 0x08, 0x79, 0x8E, 0x34, 0x04, 0xDD,
+ 0xEF, 0x95, 0x19, 0xB3, 0xCD, 0x3A, 0x43, 0x1B, 0x30, 0x2B, 0x0A, 0x6D,
+ 0xF2, 0x5F, 0x14, 0x37, 0x4F, 0xE1, 0x35, 0x6D, 0x6D, 0x51, 0xC2, 0x45,
+ 0xE4, 0x85, 0xB5, 0x76, 0x62, 0x5E, 0x7E, 0xC6, 0xF4, 0x4C, 0x42, 0xE9,
+ 0xA6, 0x37, 0xED, 0x6B, 0x0B, 0xFF, 0x5C, 0xB6, 0xF4, 0x06, 0xB7, 0xED,
+ 0xEE, 0x38, 0x6B, 0xFB, 0x5A, 0x89, 0x9F, 0xA5, 0xAE, 0x9F, 0x24, 0x11,
+ 0x7C, 0x4B, 0x1F, 0xE6, 0x49, 0x28, 0x66, 0x51, 0xEC, 0xE6, 0x53, 0x81,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
+};
+static const unsigned char P14[] = {
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC9, 0x0F, 0xDA, 0xA2,
+ 0x21, 0x68, 0xC2, 0x34, 0xC4, 0xC6, 0x62, 0x8B, 0x80, 0xDC, 0x1C, 0xD1,
+ 0x29, 0x02, 0x4E, 0x08, 0x8A, 0x67, 0xCC, 0x74, 0x02, 0x0B, 0xBE, 0xA6,
+ 0x3B, 0x13, 0x9B, 0x22, 0x51, 0x4A, 0x08, 0x79, 0x8E, 0x34, 0x04, 0xDD,
+ 0xEF, 0x95, 0x19, 0xB3, 0xCD, 0x3A, 0x43, 0x1B, 0x30, 0x2B, 0x0A, 0x6D,
+ 0xF2, 0x5F, 0x14, 0x37, 0x4F, 0xE1, 0x35, 0x6D, 0x6D, 0x51, 0xC2, 0x45,
+ 0xE4, 0x85, 0xB5, 0x76, 0x62, 0x5E, 0x7E, 0xC6, 0xF4, 0x4C, 0x42, 0xE9,
+ 0xA6, 0x37, 0xED, 0x6B, 0x0B, 0xFF, 0x5C, 0xB6, 0xF4, 0x06, 0xB7, 0xED,
+ 0xEE, 0x38, 0x6B, 0xFB, 0x5A, 0x89, 0x9F, 0xA5, 0xAE, 0x9F, 0x24, 0x11,
+ 0x7C, 0x4B, 0x1F, 0xE6, 0x49, 0x28, 0x66, 0x51, 0xEC, 0xE4, 0x5B, 0x3D,
+ 0xC2, 0x00, 0x7C, 0xB8, 0xA1, 0x63, 0xBF, 0x05, 0x98, 0xDA, 0x48, 0x36,
+ 0x1C, 0x55, 0xD3, 0x9A, 0x69, 0x16, 0x3F, 0xA8, 0xFD, 0x24, 0xCF, 0x5F,
+ 0x83, 0x65, 0x5D, 0x23, 0xDC, 0xA3, 0xAD, 0x96, 0x1C, 0x62, 0xF3, 0x56,
+ 0x20, 0x85, 0x52, 0xBB, 0x9E, 0xD5, 0x29, 0x07, 0x70, 0x96, 0x96, 0x6D,
+ 0x67, 0x0C, 0x35, 0x4E, 0x4A, 0xBC, 0x98, 0x04, 0xF1, 0x74, 0x6C, 0x08,
+ 0xCA, 0x18, 0x21, 0x7C, 0x32, 0x90, 0x5E, 0x46, 0x2E, 0x36, 0xCE, 0x3B,
+ 0xE3, 0x9E, 0x77, 0x2C, 0x18, 0x0E, 0x86, 0x03, 0x9B, 0x27, 0x83, 0xA2,
+ 0xEC, 0x07, 0xA2, 0x8F, 0xB5, 0xC5, 0x5D, 0xF0, 0x6F, 0x4C, 0x52, 0xC9,
+ 0xDE, 0x2B, 0xCB, 0xF6, 0x95, 0x58, 0x17, 0x18, 0x39, 0x95, 0x49, 0x7C,
+ 0xEA, 0x95, 0x6A, 0xE5, 0x15, 0xD2, 0x26, 0x18, 0x98, 0xFA, 0x05, 0x10,
+ 0x15, 0x72, 0x8E, 0x5A, 0x8A, 0xAC, 0xAA, 0x68, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF
+};
+
+/*
+ * The generator g = 2 (used for both group1 and group14).
+ */
+static const unsigned char G[] = { 2 };
+
+static const struct ssh_kex ssh_diffiehellman_group1_sha1 = {
+ "diffie-hellman-group1-sha1", "group1",
+ KEXTYPE_DH, P1, G, lenof(P1), lenof(G), &ssh_sha1
+};
+
+static const struct ssh_kex *const group1_list[] = {
+ &ssh_diffiehellman_group1_sha1
+};
+
+const struct ssh_kexes ssh_diffiehellman_group1 = {
+ sizeof(group1_list) / sizeof(*group1_list),
+ group1_list
+};
+
+static const struct ssh_kex ssh_diffiehellman_group14_sha1 = {
+ "diffie-hellman-group14-sha1", "group14",
+ KEXTYPE_DH, P14, G, lenof(P14), lenof(G), &ssh_sha1
+};
+
+static const struct ssh_kex *const group14_list[] = {
+ &ssh_diffiehellman_group14_sha1
+};
+
+const struct ssh_kexes ssh_diffiehellman_group14 = {
+ sizeof(group14_list) / sizeof(*group14_list),
+ group14_list
+};
+
+static const struct ssh_kex ssh_diffiehellman_gex_sha256 = {
+ "diffie-hellman-group-exchange-sha256", NULL,
+ KEXTYPE_DH, NULL, NULL, 0, 0, &ssh_sha256
+};
+
+static const struct ssh_kex ssh_diffiehellman_gex_sha1 = {
+ "diffie-hellman-group-exchange-sha1", NULL,
+ KEXTYPE_DH, NULL, NULL, 0, 0, &ssh_sha1
+};
+
+static const struct ssh_kex *const gex_list[] = {
+ &ssh_diffiehellman_gex_sha256,
+ &ssh_diffiehellman_gex_sha1
+};
+
+const struct ssh_kexes ssh_diffiehellman_gex = {
+ sizeof(gex_list) / sizeof(*gex_list),
+ gex_list
+};
+
+/*
+ * Variables.
+ */
+struct dh_ctx {
+ Bignum x, e, p, q, qmask, g;
+};
+
+/*
+ * Common DH initialisation.
+ */
+static void dh_init(struct dh_ctx *ctx)
+{
+ ctx->q = bignum_rshift(ctx->p, 1);
+ ctx->qmask = bignum_bitmask(ctx->q);
+ ctx->x = ctx->e = NULL;
+}
+
+/*
+ * Initialise DH for a standard group.
+ */
+void *dh_setup_group(const struct ssh_kex *kex)
+{
+ struct dh_ctx *ctx = snew(struct dh_ctx);
+ ctx->p = bignum_from_bytes(kex->pdata, kex->plen);
+ ctx->g = bignum_from_bytes(kex->gdata, kex->glen);
+ dh_init(ctx);
+ return ctx;
+}
+
+/*
+ * Initialise DH for a server-supplied group.
+ */
+void *dh_setup_gex(Bignum pval, Bignum gval)
+{
+ struct dh_ctx *ctx = snew(struct dh_ctx);
+ ctx->p = copybn(pval);
+ ctx->g = copybn(gval);
+ dh_init(ctx);
+ return ctx;
+}
+
+/*
+ * Clean up and free a context.
+ */
+void dh_cleanup(void *handle)
+{
+ struct dh_ctx *ctx = (struct dh_ctx *)handle;
+ freebn(ctx->x);
+ freebn(ctx->e);
+ freebn(ctx->p);
+ freebn(ctx->g);
+ freebn(ctx->q);
+ freebn(ctx->qmask);
+ sfree(ctx);
+}
+
+/*
+ * DH stage 1: invent a number x between 1 and q, and compute e =
+ * g^x mod p. Return e.
+ *
+ * If `nbits' is greater than zero, it is used as an upper limit
+ * for the number of bits in x. This is safe provided that (a) you
+ * use twice as many bits in x as the number of bits you expect to
+ * use in your session key, and (b) the DH group is a safe prime
+ * (which SSH demands that it must be).
+ *
+ * P. C. van Oorschot, M. J. Wiener
+ * "On Diffie-Hellman Key Agreement with Short Exponents".
+ * Advances in Cryptology: Proceedings of Eurocrypt '96
+ * Springer-Verlag, May 1996.
+ */
+Bignum dh_create_e(void *handle, int nbits)
+{
+ struct dh_ctx *ctx = (struct dh_ctx *)handle;
+ int i;
+
+ int nbytes;
+ unsigned char *buf;
+
+ nbytes = ssh1_bignum_length(ctx->qmask);
+ buf = snewn(nbytes, unsigned char);
+
+ do {
+ /*
+ * Create a potential x, by ANDing a string of random bytes
+ * with qmask.
+ */
+ if (ctx->x)
+ freebn(ctx->x);
+ if (nbits == 0 || nbits > bignum_bitcount(ctx->qmask)) {
+ ssh1_write_bignum(buf, ctx->qmask);
+ for (i = 2; i < nbytes; i++)
+ buf[i] &= random_byte();
+ ssh1_read_bignum(buf, nbytes, &ctx->x); /* can't fail */
+ } else {
+ int b, nb;
+ ctx->x = bn_power_2(nbits);
+ b = nb = 0;
+ for (i = 0; i < nbits; i++) {
+ if (nb == 0) {
+ nb = 8;
+ b = random_byte();
+ }
+ bignum_set_bit(ctx->x, i, b & 1);
+ b >>= 1;
+ nb--;
+ }
+ }
+ } while (bignum_cmp(ctx->x, One) <= 0 || bignum_cmp(ctx->x, ctx->q) >= 0);
+
+ sfree(buf);
+
+ /*
+ * Done. Now compute e = g^x mod p.
+ */
+ ctx->e = modpow(ctx->g, ctx->x, ctx->p);
+
+ return ctx->e;
+}
+
+/*
+ * DH stage 2: given a number f, compute K = f^x mod p.
+ */
+Bignum dh_find_K(void *handle, Bignum f)
+{
+ struct dh_ctx *ctx = (struct dh_ctx *)handle;
+ Bignum ret;
+ ret = modpow(f, ctx->x, ctx->p);
+ return ret;
+}
--- /dev/null
+/*
+ * Digital Signature Standard implementation for PuTTY.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#include "ssh.h"
+#include "misc.h"
+
+static void sha_mpint(SHA_State * s, Bignum b)
+{
+ unsigned char lenbuf[4];
+ int len;
+ len = (bignum_bitcount(b) + 8) / 8;
+ PUT_32BIT(lenbuf, len);
+ SHA_Bytes(s, lenbuf, 4);
+ while (len-- > 0) {
+ lenbuf[0] = bignum_byte(b, len);
+ SHA_Bytes(s, lenbuf, 1);
+ }
+ memset(lenbuf, 0, sizeof(lenbuf));
+}
+
+static void sha512_mpint(SHA512_State * s, Bignum b)
+{
+ unsigned char lenbuf[4];
+ int len;
+ len = (bignum_bitcount(b) + 8) / 8;
+ PUT_32BIT(lenbuf, len);
+ SHA512_Bytes(s, lenbuf, 4);
+ while (len-- > 0) {
+ lenbuf[0] = bignum_byte(b, len);
+ SHA512_Bytes(s, lenbuf, 1);
+ }
+ memset(lenbuf, 0, sizeof(lenbuf));
+}
+
+static void getstring(char **data, int *datalen, char **p, int *length)
+{
+ *p = NULL;
+ if (*datalen < 4)
+ return;
+ *length = GET_32BIT(*data);
+ *datalen -= 4;
+ *data += 4;
+ if (*datalen < *length)
+ return;
+ *p = *data;
+ *data += *length;
+ *datalen -= *length;
+}
+static Bignum getmp(char **data, int *datalen)
+{
+ char *p;
+ int length;
+ Bignum b;
+
+ getstring(data, datalen, &p, &length);
+ if (!p)
+ return NULL;
+ if (p[0] & 0x80)
+ return NULL; /* negative mp */
+ b = bignum_from_bytes((unsigned char *)p, length);
+ return b;
+}
+
+static Bignum get160(char **data, int *datalen)
+{
+ Bignum b;
+
+ b = bignum_from_bytes((unsigned char *)*data, 20);
+ *data += 20;
+ *datalen -= 20;
+
+ return b;
+}
+
+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;
+ printf("key:");
+ for (i = 0; i < len; i++)
+ printf(" %02x", (unsigned char) (data[i]));
+ printf("\n");
+ }
+#endif
+
+ if (!p || 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);
+
+ 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);
+ sfree(dss);
+}
+
+static char *dss_fmtkey(void *key)
+{
+ struct dss_key *dss = (struct dss_key *) key;
+ char *p;
+ int len, i, pos, nibbles;
+ static const char hex[] = "0123456789abcdef";
+ if (!dss->p)
+ return NULL;
+ len = 8 + 4 + 1; /* 4 x "0x", punctuation, \0 */
+ len += 4 * (bignum_bitcount(dss->p) + 15) / 16;
+ len += 4 * (bignum_bitcount(dss->q) + 15) / 16;
+ len += 4 * (bignum_bitcount(dss->g) + 15) / 16;
+ len += 4 * (bignum_bitcount(dss->y) + 15) / 16;
+ p = snewn(len, char);
+ if (!p)
+ return NULL;
+
+ pos = 0;
+ pos += sprintf(p + pos, "0x");
+ nibbles = (3 + bignum_bitcount(dss->p)) / 4;
+ if (nibbles < 1)
+ nibbles = 1;
+ for (i = nibbles; i--;)
+ p[pos++] =
+ hex[(bignum_byte(dss->p, i / 2) >> (4 * (i % 2))) & 0xF];
+ pos += sprintf(p + pos, ",0x");
+ nibbles = (3 + bignum_bitcount(dss->q)) / 4;
+ if (nibbles < 1)
+ nibbles = 1;
+ for (i = nibbles; i--;)
+ p[pos++] =
+ hex[(bignum_byte(dss->q, i / 2) >> (4 * (i % 2))) & 0xF];
+ pos += sprintf(p + pos, ",0x");
+ nibbles = (3 + bignum_bitcount(dss->g)) / 4;
+ if (nibbles < 1)
+ nibbles = 1;
+ for (i = nibbles; i--;)
+ p[pos++] =
+ hex[(bignum_byte(dss->g, i / 2) >> (4 * (i % 2))) & 0xF];
+ pos += sprintf(p + pos, ",0x");
+ nibbles = (3 + bignum_bitcount(dss->y)) / 4;
+ if (nibbles < 1)
+ nibbles = 1;
+ for (i = nibbles; i--;)
+ p[pos++] =
+ hex[(bignum_byte(dss->y, i / 2) >> (4 * (i % 2))) & 0xF];
+ p[pos] = '\0';
+ return p;
+}
+
+static char *dss_fingerprint(void *key)
+{
+ struct dss_key *dss = (struct dss_key *) key;
+ struct MD5Context md5c;
+ unsigned char digest[16], lenbuf[4];
+ char buffer[16 * 3 + 40];
+ char *ret;
+ int numlen, i;
+
+ MD5Init(&md5c);
+ MD5Update(&md5c, (unsigned char *)"\0\0\0\7ssh-dss", 11);
+
+#define ADD_BIGNUM(bignum) \
+ numlen = (bignum_bitcount(bignum)+8)/8; \
+ PUT_32BIT(lenbuf, numlen); MD5Update(&md5c, lenbuf, 4); \
+ for (i = numlen; i-- ;) { \
+ unsigned char c = bignum_byte(bignum, i); \
+ MD5Update(&md5c, &c, 1); \
+ }
+ ADD_BIGNUM(dss->p);
+ ADD_BIGNUM(dss->q);
+ ADD_BIGNUM(dss->g);
+ ADD_BIGNUM(dss->y);
+#undef ADD_BIGNUM
+
+ MD5Final(digest, &md5c);
+
+ sprintf(buffer, "ssh-dss %d ", bignum_bitcount(dss->p));
+ for (i = 0; i < 16; i++)
+ sprintf(buffer + strlen(buffer), "%s%02x", i ? ":" : "",
+ digest[i]);
+ ret = snewn(strlen(buffer) + 1, char);
+ if (ret)
+ strcpy(ret, buffer);
+ return ret;
+}
+
+static int dss_verifysig(void *key, char *sig, int siglen,
+ char *data, int datalen)
+{
+ struct dss_key *dss = (struct dss_key *) key;
+ char *p;
+ int slen;
+ char hash[20];
+ Bignum r, s, w, gu1p, yu2p, gu1yu2p, u1, u2, sha, v;
+ int ret;
+
+ if (!dss->p)
+ return 0;
+
+#ifdef DEBUG_DSS
+ {
+ int i;
+ printf("sig:");
+ for (i = 0; i < siglen; i++)
+ printf(" %02x", (unsigned char) (sig[i]));
+ printf("\n");
+ }
+#endif
+ /*
+ * Commercial SSH (2.0.13) and OpenSSH disagree over the format
+ * of a DSA signature. OpenSSH is in line with RFC 4253:
+ * it uses a string "ssh-dss", followed by a 40-byte string
+ * containing two 160-bit integers end-to-end. Commercial SSH
+ * can't be bothered with the header bit, and considers a DSA
+ * signature blob to be _just_ the 40-byte string containing
+ * the two 160-bit integers. We tell them apart by measuring
+ * the length: length 40 means the commercial-SSH bug, anything
+ * else is assumed to be RFC-compliant.
+ */
+ if (siglen != 40) { /* bug not present; read admin fields */
+ getstring(&sig, &siglen, &p, &slen);
+ if (!p || slen != 7 || memcmp(p, "ssh-dss", 7)) {
+ return 0;
+ }
+ sig += 4, siglen -= 4; /* skip yet another length field */
+ }
+ r = get160(&sig, &siglen);
+ s = get160(&sig, &siglen);
+ if (!r || !s)
+ return 0;
+
+ /*
+ * Step 1. w <- s^-1 mod q.
+ */
+ w = modinv(s, dss->q);
+
+ /*
+ * Step 2. u1 <- SHA(message) * w mod q.
+ */
+ SHA_Simple(data, datalen, (unsigned char *)hash);
+ p = hash;
+ slen = 20;
+ sha = get160(&p, &slen);
+ u1 = modmul(sha, w, dss->q);
+
+ /*
+ * Step 3. u2 <- r * w mod q.
+ */
+ u2 = modmul(r, w, dss->q);
+
+ /*
+ * Step 4. v <- (g^u1 * y^u2 mod p) mod q.
+ */
+ gu1p = modpow(dss->g, u1, dss->p);
+ yu2p = modpow(dss->y, u2, dss->p);
+ gu1yu2p = modmul(gu1p, yu2p, dss->p);
+ v = modmul(gu1yu2p, One, dss->q);
+
+ /*
+ * Step 5. v should now be equal to r.
+ */
+
+ ret = !bignum_cmp(v, r);
+
+ freebn(w);
+ freebn(sha);
+ freebn(gu1p);
+ freebn(yu2p);
+ freebn(gu1yu2p);
+ freebn(v);
+ freebn(r);
+ freebn(s);
+
+ return ret;
+}
+
+static unsigned char *dss_public_blob(void *key, int *len)
+{
+ struct dss_key *dss = (struct dss_key *) key;
+ int plen, qlen, glen, ylen, bloblen;
+ int i;
+ unsigned char *blob, *p;
+
+ plen = (bignum_bitcount(dss->p) + 8) / 8;
+ qlen = (bignum_bitcount(dss->q) + 8) / 8;
+ glen = (bignum_bitcount(dss->g) + 8) / 8;
+ ylen = (bignum_bitcount(dss->y) + 8) / 8;
+
+ /*
+ * string "ssh-dss", mpint p, mpint q, mpint g, mpint y. Total
+ * 27 + sum of lengths. (five length fields, 20+7=27).
+ */
+ bloblen = 27 + plen + qlen + glen + ylen;
+ blob = snewn(bloblen, unsigned char);
+ p = blob;
+ PUT_32BIT(p, 7);
+ p += 4;
+ memcpy(p, "ssh-dss", 7);
+ p += 7;
+ PUT_32BIT(p, plen);
+ p += 4;
+ for (i = plen; i--;)
+ *p++ = bignum_byte(dss->p, i);
+ PUT_32BIT(p, qlen);
+ p += 4;
+ for (i = qlen; i--;)
+ *p++ = bignum_byte(dss->q, i);
+ PUT_32BIT(p, glen);
+ p += 4;
+ for (i = glen; i--;)
+ *p++ = bignum_byte(dss->g, i);
+ PUT_32BIT(p, ylen);
+ p += 4;
+ for (i = ylen; i--;)
+ *p++ = bignum_byte(dss->y, i);
+ assert(p == blob + bloblen);
+ *len = bloblen;
+ return blob;
+}
+
+static unsigned char *dss_private_blob(void *key, int *len)
+{
+ struct dss_key *dss = (struct dss_key *) key;
+ int xlen, bloblen;
+ int i;
+ unsigned char *blob, *p;
+
+ xlen = (bignum_bitcount(dss->x) + 8) / 8;
+
+ /*
+ * mpint x, string[20] the SHA of p||q||g. Total 4 + xlen.
+ */
+ bloblen = 4 + xlen;
+ blob = snewn(bloblen, unsigned char);
+ p = blob;
+ PUT_32BIT(p, xlen);
+ p += 4;
+ for (i = xlen; i--;)
+ *p++ = bignum_byte(dss->x, i);
+ assert(p == blob + bloblen);
+ *len = bloblen;
+ return blob;
+}
+
+static void *dss_createkey(unsigned char *pub_blob, int pub_len,
+ unsigned char *priv_blob, int priv_len)
+{
+ struct dss_key *dss;
+ char *pb = (char *) priv_blob;
+ char *hash;
+ int hashlen;
+ SHA_State s;
+ unsigned char digest[20];
+ Bignum ytest;
+
+ dss = dss_newkey((char *) pub_blob, pub_len);
+ dss->x = getmp(&pb, &priv_len);
+
+ /*
+ * Check the obsolete hash in the old DSS key format.
+ */
+ hashlen = -1;
+ getstring(&pb, &priv_len, &hash, &hashlen);
+ if (hashlen == 20) {
+ SHA_Init(&s);
+ sha_mpint(&s, dss->p);
+ sha_mpint(&s, dss->q);
+ sha_mpint(&s, dss->g);
+ SHA_Final(&s, digest);
+ if (0 != memcmp(hash, digest, 20)) {
+ dss_freekey(dss);
+ return NULL;
+ }
+ }
+
+ /*
+ * 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);
+ return NULL;
+ }
+ freebn(ytest);
+
+ return dss;
+}
+
+static void *dss_openssh_createkey(unsigned char **blob, int *len)
+{
+ 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;
+ }
+
+ return dss;
+}
+
+static int dss_openssh_fmtkey(void *key, unsigned char *blob, int len)
+{
+ struct dss_key *dss = (struct dss_key *) key;
+ int bloblen, i;
+
+ bloblen =
+ ssh2_bignum_length(dss->p) +
+ ssh2_bignum_length(dss->q) +
+ ssh2_bignum_length(dss->g) +
+ ssh2_bignum_length(dss->y) +
+ ssh2_bignum_length(dss->x);
+
+ if (bloblen > len)
+ return bloblen;
+
+ bloblen = 0;
+#define ENC(x) \
+ PUT_32BIT(blob+bloblen, ssh2_bignum_length((x))-4); bloblen += 4; \
+ for (i = ssh2_bignum_length((x))-4; i-- ;) blob[bloblen++]=bignum_byte((x),i);
+ ENC(dss->p);
+ ENC(dss->q);
+ ENC(dss->g);
+ ENC(dss->y);
+ ENC(dss->x);
+
+ return bloblen;
+}
+
+static int dss_pubkey_bits(void *blob, int len)
+{
+ struct dss_key *dss;
+ int ret;
+
+ dss = dss_newkey((char *) blob, len);
+ ret = bignum_bitcount(dss->p);
+ dss_freekey(dss);
+
+ return ret;
+}
+
+static unsigned char *dss_sign(void *key, char *data, int datalen, int *siglen)
+{
+ /*
+ * The basic DSS signing algorithm is:
+ *
+ * - invent a random k between 1 and q-1 (exclusive).
+ * - Compute r = (g^k mod p) mod q.
+ * - Compute s = k^-1 * (hash + x*r) mod q.
+ *
+ * This has the dangerous properties that:
+ *
+ * - if an attacker in possession of the public key _and_ the
+ * signature (for example, the host you just authenticated
+ * to) can guess your k, he can reverse the computation of s
+ * and work out x = r^-1 * (s*k - hash) mod q. That is, he
+ * can deduce the private half of your key, and masquerade
+ * as you for as long as the key is still valid.
+ *
+ * - since r is a function purely of k and the public key, if
+ * the attacker only has a _range of possibilities_ for k
+ * it's easy for him to work through them all and check each
+ * one against r; he'll never be unsure of whether he's got
+ * the right one.
+ *
+ * - if you ever sign two different hashes with the same k, it
+ * will be immediately obvious because the two signatures
+ * will have the same r, and moreover an attacker in
+ * possession of both signatures (and the public key of
+ * course) can compute k = (hash1-hash2) * (s1-s2)^-1 mod q,
+ * and from there deduce x as before.
+ *
+ * - the Bleichenbacher attack on DSA makes use of methods of
+ * generating k which are significantly non-uniformly
+ * distributed; in particular, generating a 160-bit random
+ * number and reducing it mod q is right out.
+ *
+ * For this reason we must be pretty careful about how we
+ * generate our k. Since this code runs on Windows, with no
+ * particularly good system entropy sources, we can't trust our
+ * RNG itself to produce properly unpredictable data. Hence, we
+ * use a totally different scheme instead.
+ *
+ * What we do is to take a SHA-512 (_big_) hash of the private
+ * key x, and then feed this into another SHA-512 hash that
+ * also includes the message hash being signed. That is:
+ *
+ * proto_k = SHA512 ( SHA512(x) || SHA160(message) )
+ *
+ * This number is 512 bits long, so reducing it mod q won't be
+ * noticeably non-uniform. So
+ *
+ * k = proto_k mod q
+ *
+ * This has the interesting property that it's _deterministic_:
+ * signing the same hash twice with the same key yields the
+ * same signature.
+ *
+ * Despite this determinism, it's still not predictable to an
+ * attacker, because in order to repeat the SHA-512
+ * construction that created it, the attacker would have to
+ * know the private key value x - and by assumption he doesn't,
+ * because if he knew that he wouldn't be attacking k!
+ *
+ * (This trick doesn't, _per se_, protect against reuse of k.
+ * Reuse of k is left to chance; all it does is prevent
+ * _excessively high_ chances of reuse of k due to entropy
+ * problems.)
+ *
+ * Thanks to Colin Plumb for the general idea of using x to
+ * ensure k is hard to guess, and to the Cambridge University
+ * Computer Security Group for helping to argue out all the
+ * fine details.
+ */
+ struct dss_key *dss = (struct dss_key *) key;
+ SHA512_State ss;
+ unsigned char digest[20], digest512[64];
+ Bignum proto_k, k, gkp, hash, kinv, hxr, r, s;
+ unsigned char *bytes;
+ int nbytes, i;
+
+ SHA_Simple(data, datalen, digest);
+
+ /*
+ * Hash some identifying text plus x.
+ */
+ SHA512_Init(&ss);
+ SHA512_Bytes(&ss, "DSA deterministic k generator", 30);
+ sha512_mpint(&ss, dss->x);
+ SHA512_Final(&ss, digest512);
+
+ /*
+ * 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));
+
+ /*
+ * 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(hash);
+
+ /*
+ * Signature blob is
+ *
+ * string "ssh-dss"
+ * string two 20-byte numbers r and s, end to end
+ *
+ * i.e. 4+7 + 4+40 bytes.
+ */
+ nbytes = 4 + 7 + 4 + 40;
+ bytes = snewn(nbytes, unsigned char);
+ PUT_32BIT(bytes, 7);
+ memcpy(bytes + 4, "ssh-dss", 7);
+ PUT_32BIT(bytes + 4 + 7, 40);
+ for (i = 0; i < 20; i++) {
+ bytes[4 + 7 + 4 + i] = bignum_byte(r, 19 - i);
+ bytes[4 + 7 + 4 + 20 + i] = bignum_byte(s, 19 - i);
+ }
+ freebn(r);
+ freebn(s);
+
+ *siglen = nbytes;
+ return bytes;
+}
+
+const struct ssh_signkey ssh_dss = {
+ dss_newkey,
+ dss_freekey,
+ dss_fmtkey,
+ dss_public_blob,
+ dss_private_blob,
+ dss_createkey,
+ dss_openssh_createkey,
+ dss_openssh_fmtkey,
+ dss_pubkey_bits,
+ dss_fingerprint,
+ dss_verifysig,
+ dss_sign,
+ "ssh-dss",
+ "dss"
+};
--- /dev/null
+/*
+ * DSS key generation.
+ */
+
+#include "misc.h"
+#include "ssh.h"
+
+int dsa_generate(struct dss_key *key, int bits, progfn_t pfn,
+ void *pfnparam)
+{
+ Bignum qm1, power, g, h, tmp;
+ int progress;
+
+ /*
+ * Set up the phase limits for the progress report. We do this
+ * by passing minus the phase number.
+ *
+ * For prime generation: our initial filter finds things
+ * coprime to everything below 2^16. Computing the product of
+ * (p-1)/p for all prime p below 2^16 gives about 20.33; so
+ * among B-bit integers, one in every 20.33 will get through
+ * the initial filter to be a candidate prime.
+ *
+ * Meanwhile, we are searching for primes in the region of 2^B;
+ * since pi(x) ~ x/log(x), when x is in the region of 2^B, the
+ * prime density will be d/dx pi(x) ~ 1/log(B), i.e. about
+ * 1/0.6931B. So the chance of any given candidate being prime
+ * is 20.33/0.6931B, which is roughly 29.34 divided by B.
+ *
+ * So now we have this probability P, we're looking at an
+ * exponential distribution with parameter P: we will manage in
+ * one attempt with probability P, in two with probability
+ * P(1-P), in three with probability P(1-P)^2, etc. The
+ * probability that we have still not managed to find a prime
+ * after N attempts is (1-P)^N.
+ *
+ * We therefore inform the progress indicator of the number B
+ * (29.34/B), so that it knows how much to increment by each
+ * time. We do this in 16-bit fixed point, so 29.34 becomes
+ * 0x1D.57C4.
+ */
+ pfn(pfnparam, PROGFN_PHASE_EXTENT, 1, 0x2800);
+ pfn(pfnparam, PROGFN_EXP_PHASE, 1, -0x1D57C4 / 160);
+ pfn(pfnparam, PROGFN_PHASE_EXTENT, 2, 0x40 * bits);
+ pfn(pfnparam, PROGFN_EXP_PHASE, 2, -0x1D57C4 / bits);
+
+ /*
+ * In phase three we are finding an order-q element of the
+ * multiplicative group of p, by finding an element whose order
+ * is _divisible_ by q and raising it to the power of (p-1)/q.
+ * _Most_ elements will have order divisible by q, since for a
+ * start phi(p) of them will be primitive roots. So
+ * realistically we don't need to set this much below 1 (64K).
+ * Still, we'll set it to 1/2 (32K) to be on the safe side.
+ */
+ pfn(pfnparam, PROGFN_PHASE_EXTENT, 3, 0x2000);
+ pfn(pfnparam, PROGFN_EXP_PHASE, 3, -32768);
+
+ /*
+ * In phase four we are finding an element x between 1 and q-1
+ * (exclusive), by inventing 160 random bits and hoping they
+ * come out to a plausible number; so assuming q is uniformly
+ * distributed between 2^159 and 2^160, the chance of any given
+ * attempt succeeding is somewhere between 0.5 and 1. Lacking
+ * the energy to arrange to be able to specify this probability
+ * _after_ generating q, we'll just set it to 0.75.
+ */
+ pfn(pfnparam, PROGFN_PHASE_EXTENT, 4, 0x2000);
+ pfn(pfnparam, PROGFN_EXP_PHASE, 4, -49152);
+
+ pfn(pfnparam, PROGFN_READY, 0, 0);
+
+ /*
+ * Generate q: a prime of length 160.
+ */
+ key->q = primegen(160, 2, 2, NULL, 1, pfn, pfnparam);
+ /*
+ * 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);
+
+ /*
+ * 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
+ * for g.
+ */
+ power = bigdiv(key->p, key->q); /* this is floor(p/q) == (p-1)/q */
+ h = bignum_from_long(1);
+ progress = 0;
+ while (1) {
+ pfn(pfnparam, PROGFN_PROGRESS, 3, ++progress);
+ g = modpow(h, power, key->p);
+ if (bignum_cmp(g, One) > 0)
+ break; /* got one */
+ tmp = h;
+ h = bignum_add_long(h, 1);
+ freebn(tmp);
+ }
+ key->g = g;
+ freebn(h);
+
+ /*
+ * Now we're nearly done. All we need now is our private key x,
+ * which should be a number between 1 and q-1 exclusive, and
+ * our public key y = g^x mod p.
+ */
+ qm1 = copybn(key->q);
+ decbn(qm1);
+ progress = 0;
+ while (1) {
+ int i, v, byte, bitsleft;
+ Bignum x;
+
+ pfn(pfnparam, PROGFN_PROGRESS, 4, ++progress);
+ x = bn_power_2(159);
+ byte = 0;
+ bitsleft = 0;
+
+ for (i = 0; i < 160; i++) {
+ if (bitsleft <= 0)
+ bitsleft = 8, byte = random_byte();
+ v = byte & 1;
+ byte >>= 1;
+ bitsleft--;
+ bignum_set_bit(x, i, v);
+ }
+
+ if (bignum_cmp(x, One) <= 0 || bignum_cmp(x, qm1) >= 0) {
+ freebn(x);
+ continue;
+ } else {
+ key->x = x;
+ break;
+ }
+ }
+ freebn(qm1);
+
+ key->y = modpow(key->g, key->x, key->p);
+
+ return 1;
+}
--- /dev/null
+#ifndef PUTTY_SSHGSS_H
+#define PUTTY_SSHGSS_H
+#include "putty.h"
+#include "pgssapi.h"
+
+#ifndef NO_GSSAPI
+
+#define SSH2_GSS_OIDTYPE 0x06
+typedef void *Ssh_gss_ctx;
+
+typedef enum Ssh_gss_stat {
+ SSH_GSS_OK = 0,
+ SSH_GSS_S_CONTINUE_NEEDED,
+ SSH_GSS_NO_MEM,
+ SSH_GSS_BAD_HOST_NAME,
+ SSH_GSS_FAILURE
+} Ssh_gss_stat;
+
+#define SSH_GSS_S_COMPLETE SSH_GSS_OK
+
+#define SSH_GSS_CLEAR_BUF(buf) do { \
+ (*buf).length = 0; \
+ (*buf).value = NULL; \
+} while (0)
+
+typedef gss_buffer_desc Ssh_gss_buf;
+typedef gss_name_t Ssh_gss_name;
+
+/* Functions, provided by either wingss.c or sshgssc.c */
+
+struct ssh_gss_library;
+
+/*
+ * Prepare a collection of GSSAPI libraries for use in a single SSH
+ * connection. Returns a structure containing a list of libraries,
+ * with their ids (see struct ssh_gss_library below) filled in so
+ * that the client can go through them in the SSH user's preferred
+ * order.
+ *
+ * Must always return non-NULL. (Even if no libraries are available,
+ * it must return an empty structure.)
+ *
+ * The free function cleans up the structure, and its associated
+ * libraries (if any).
+ */
+struct ssh_gss_liblist {
+ struct ssh_gss_library *libraries;
+ int nlibraries;
+};
+struct ssh_gss_liblist *ssh_gss_setup(const Config *cfg);
+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.
+ */
+typedef Ssh_gss_stat (*t_ssh_gss_indicate_mech)(struct ssh_gss_library *lib,
+ Ssh_gss_buf *buf);
+
+/*
+ * Converts a name such as a hostname into a GSSAPI internal form,
+ * which is placed in "out". The result should be freed by
+ * ssh_gss_release_name().
+ */
+typedef Ssh_gss_stat (*t_ssh_gss_import_name)(struct ssh_gss_library *lib,
+ char *in, Ssh_gss_name *out);
+
+/*
+ * Frees the contents of an Ssh_gss_name structure filled in by
+ * ssh_gss_import_name().
+ */
+typedef Ssh_gss_stat (*t_ssh_gss_release_name)(struct ssh_gss_library *lib,
+ Ssh_gss_name *name);
+
+/*
+ * The main GSSAPI security context setup function. The "out"
+ * parameter will need to be freed by ssh_gss_free_tok.
+ */
+typedef Ssh_gss_stat (*t_ssh_gss_init_sec_context)
+ (struct ssh_gss_library *lib,
+ Ssh_gss_ctx *ctx, Ssh_gss_name name, int delegate,
+ Ssh_gss_buf *in, Ssh_gss_buf *out);
+
+/*
+ * Frees the contents of an Ssh_gss_buf filled in by
+ * ssh_gss_init_sec_context(). Do not accidentally call this on
+ * something filled in by ssh_gss_get_mic() (which requires a
+ * different free function) or something filled in by any other
+ * way.
+ */
+typedef Ssh_gss_stat (*t_ssh_gss_free_tok)(struct ssh_gss_library *lib,
+ Ssh_gss_buf *);
+
+/*
+ * Acquires the credentials to perform authentication in the first
+ * place. Needs to be freed by ssh_gss_release_cred().
+ */
+typedef Ssh_gss_stat (*t_ssh_gss_acquire_cred)(struct ssh_gss_library *lib,
+ Ssh_gss_ctx *);
+
+/*
+ * Frees the contents of an Ssh_gss_ctx filled in by
+ * ssh_gss_acquire_cred().
+ */
+typedef Ssh_gss_stat (*t_ssh_gss_release_cred)(struct ssh_gss_library *lib,
+ Ssh_gss_ctx *);
+
+/*
+ * Gets a MIC for some input data. "out" needs to be freed by
+ * ssh_gss_free_mic().
+ */
+typedef Ssh_gss_stat (*t_ssh_gss_get_mic)(struct ssh_gss_library *lib,
+ Ssh_gss_ctx ctx, Ssh_gss_buf *in,
+ Ssh_gss_buf *out);
+
+/*
+ * Frees the contents of an Ssh_gss_buf filled in by
+ * ssh_gss_get_mic(). Do not accidentally call this on something
+ * filled in by ssh_gss_init_sec_context() (which requires a
+ * different free function) or something filled in by any other
+ * way.
+ */
+typedef Ssh_gss_stat (*t_ssh_gss_free_mic)(struct ssh_gss_library *lib,
+ Ssh_gss_buf *);
+
+/*
+ * Return an error message after authentication failed. The
+ * message string is returned in "buf", with buf->len giving the
+ * number of characters of printable message text and buf->data
+ * containing one more character which is a trailing NUL.
+ * buf->data should be manually freed by the caller.
+ */
+typedef Ssh_gss_stat (*t_ssh_gss_display_status)(struct ssh_gss_library *lib,
+ Ssh_gss_ctx, Ssh_gss_buf *buf);
+
+struct ssh_gss_library {
+ /*
+ * Identifying number in the enumeration used by the
+ * configuration code to specify a preference order.
+ */
+ int id;
+
+ /*
+ * Filled in at initialisation time, if there's anything
+ * interesting to say about how GSSAPI was initialised (e.g.
+ * which of a number of alternative libraries was used).
+ */
+ const char *gsslogmsg;
+
+ /*
+ * Function pointers implementing the SSH wrapper layer on top
+ * of GSSAPI. (Defined in sshgssc, typically, though Windows
+ * provides an alternative layer to sit on top of the annoyingly
+ * different SSPI.)
+ */
+ t_ssh_gss_indicate_mech indicate_mech;
+ t_ssh_gss_import_name import_name;
+ t_ssh_gss_release_name release_name;
+ t_ssh_gss_init_sec_context init_sec_context;
+ t_ssh_gss_free_tok free_tok;
+ t_ssh_gss_acquire_cred acquire_cred;
+ t_ssh_gss_release_cred release_cred;
+ t_ssh_gss_get_mic get_mic;
+ t_ssh_gss_free_mic free_mic;
+ t_ssh_gss_display_status display_status;
+
+ /*
+ * Additional data for the wrapper layers.
+ */
+ union {
+ struct gssapi_functions gssapi;
+ /*
+ * The SSPI wrappers don't need to store their Windows API
+ * function pointers in this structure, because there can't
+ * be more than one set of them available.
+ */
+ } u;
+
+ /*
+ * Wrapper layers will often also need to store a library handle
+ * of some sort for cleanup time.
+ */
+ void *handle;
+};
+
+#endif /* NO_GSSAPI */
+
+#endif /*PUTTY_SSHGSS_H*/
--- /dev/null
+#include "putty.h"
+
+#include <string.h>
+#include "sshgssc.h"
+#include "misc.h"
+
+#ifndef NO_GSSAPI
+
+static Ssh_gss_stat ssh_gssapi_indicate_mech(struct ssh_gss_library *lib,
+ Ssh_gss_buf *mech)
+{
+ /* Copy constant into mech */
+ mech->length = GSS_MECH_KRB5->length;
+ mech->value = GSS_MECH_KRB5->elements;
+ return SSH_GSS_OK;
+}
+
+static Ssh_gss_stat ssh_gssapi_import_name(struct ssh_gss_library *lib,
+ char *host,
+ Ssh_gss_name *srv_name)
+{
+ struct gssapi_functions *gss = &lib->u.gssapi;
+ OM_uint32 min_stat,maj_stat;
+ gss_buffer_desc host_buf;
+ char *pStr;
+
+ pStr = dupcat("host@", host, NULL);
+
+ host_buf.value = pStr;
+ host_buf.length = strlen(pStr);
+
+ maj_stat = gss->import_name(&min_stat, &host_buf,
+ GSS_C_NT_HOSTBASED_SERVICE, srv_name);
+ /* Release buffer */
+ sfree(pStr);
+ if (maj_stat == GSS_S_COMPLETE) return SSH_GSS_OK;
+ return SSH_GSS_FAILURE;
+}
+
+static Ssh_gss_stat ssh_gssapi_acquire_cred(struct ssh_gss_library *lib,
+ Ssh_gss_ctx *ctx)
+{
+ gssapi_ssh_gss_ctx *gssctx = snew(gssapi_ssh_gss_ctx);
+
+ gssctx->maj_stat = gssctx->min_stat = GSS_S_COMPLETE;
+ gssctx->ctx = GSS_C_NO_CONTEXT;
+ *ctx = (Ssh_gss_ctx) gssctx;
+
+ return SSH_GSS_OK;
+}
+
+static Ssh_gss_stat ssh_gssapi_init_sec_context(struct ssh_gss_library *lib,
+ Ssh_gss_ctx *ctx,
+ Ssh_gss_name srv_name,
+ int to_deleg,
+ Ssh_gss_buf *recv_tok,
+ Ssh_gss_buf *send_tok)
+{
+ struct gssapi_functions *gss = &lib->u.gssapi;
+ gssapi_ssh_gss_ctx *gssctx = (gssapi_ssh_gss_ctx*) *ctx;
+ OM_uint32 ret_flags;
+
+ if (to_deleg) to_deleg = GSS_C_DELEG_FLAG;
+ gssctx->maj_stat = gss->init_sec_context(&gssctx->min_stat,
+ GSS_C_NO_CREDENTIAL,
+ &gssctx->ctx,
+ srv_name,
+ (gss_OID) GSS_MECH_KRB5,
+ GSS_C_MUTUAL_FLAG |
+ GSS_C_INTEG_FLAG | to_deleg,
+ 0,
+ GSS_C_NO_CHANNEL_BINDINGS,
+ recv_tok,
+ NULL, /* ignore mech type */
+ send_tok,
+ &ret_flags,
+ NULL); /* ignore time_rec */
+
+ if (gssctx->maj_stat == GSS_S_COMPLETE) return SSH_GSS_S_COMPLETE;
+ if (gssctx->maj_stat == GSS_S_CONTINUE_NEEDED) return SSH_GSS_S_CONTINUE_NEEDED;
+ return SSH_GSS_FAILURE;
+}
+
+static Ssh_gss_stat ssh_gssapi_display_status(struct ssh_gss_library *lib,
+ Ssh_gss_ctx ctx,
+ Ssh_gss_buf *buf)
+{
+ struct gssapi_functions *gss = &lib->u.gssapi;
+ gssapi_ssh_gss_ctx *gssctx = (gssapi_ssh_gss_ctx *) ctx;
+ OM_uint32 lmin,lmax;
+ OM_uint32 ccc;
+ gss_buffer_desc msg_maj=GSS_C_EMPTY_BUFFER;
+ gss_buffer_desc msg_min=GSS_C_EMPTY_BUFFER;
+
+ /* Return empty buffer in case of failure */
+ SSH_GSS_CLEAR_BUF(buf);
+
+ /* get first mesg from GSS */
+ ccc=0;
+ lmax=gss->display_status(&lmin,gssctx->maj_stat,GSS_C_GSS_CODE,(gss_OID) GSS_MECH_KRB5,&ccc,&msg_maj);
+
+ if (lmax != GSS_S_COMPLETE) return SSH_GSS_FAILURE;
+
+ /* get first mesg from Kerberos */
+ ccc=0;
+ lmax=gss->display_status(&lmin,gssctx->min_stat,GSS_C_MECH_CODE,(gss_OID) GSS_MECH_KRB5,&ccc,&msg_min);
+
+ if (lmax != GSS_S_COMPLETE) {
+ gss->release_buffer(&lmin, &msg_maj);
+ return SSH_GSS_FAILURE;
+ }
+
+ /* copy data into buffer */
+ buf->length = msg_maj.length + msg_min.length + 1;
+ buf->value = snewn(buf->length + 1, char);
+
+ /* copy mem */
+ memcpy((char *)buf->value, msg_maj.value, msg_maj.length);
+ ((char *)buf->value)[msg_maj.length] = ' ';
+ memcpy((char *)buf->value + msg_maj.length + 1, msg_min.value, msg_min.length);
+ ((char *)buf->value)[buf->length] = 0;
+ /* free mem & exit */
+ gss->release_buffer(&lmin, &msg_maj);
+ gss->release_buffer(&lmin, &msg_min);
+ return SSH_GSS_OK;
+}
+
+static Ssh_gss_stat ssh_gssapi_free_tok(struct ssh_gss_library *lib,
+ Ssh_gss_buf *send_tok)
+{
+ struct gssapi_functions *gss = &lib->u.gssapi;
+ OM_uint32 min_stat,maj_stat;
+ maj_stat = gss->release_buffer(&min_stat, send_tok);
+
+ if (maj_stat == GSS_S_COMPLETE) return SSH_GSS_OK;
+ return SSH_GSS_FAILURE;
+}
+
+static Ssh_gss_stat ssh_gssapi_release_cred(struct ssh_gss_library *lib,
+ Ssh_gss_ctx *ctx)
+{
+ struct gssapi_functions *gss = &lib->u.gssapi;
+ gssapi_ssh_gss_ctx *gssctx = (gssapi_ssh_gss_ctx *) *ctx;
+ OM_uint32 min_stat;
+ OM_uint32 maj_stat=GSS_S_COMPLETE;
+
+ if (gssctx == NULL) return SSH_GSS_FAILURE;
+ if (gssctx->ctx != GSS_C_NO_CONTEXT)
+ maj_stat = gss->delete_sec_context(&min_stat,&gssctx->ctx,GSS_C_NO_BUFFER);
+ sfree(gssctx);
+
+ if (maj_stat == GSS_S_COMPLETE) return SSH_GSS_OK;
+ return SSH_GSS_FAILURE;
+}
+
+
+static Ssh_gss_stat ssh_gssapi_release_name(struct ssh_gss_library *lib,
+ Ssh_gss_name *srv_name)
+{
+ struct gssapi_functions *gss = &lib->u.gssapi;
+ OM_uint32 min_stat,maj_stat;
+ maj_stat = gss->release_name(&min_stat, srv_name);
+
+ if (maj_stat == GSS_S_COMPLETE) return SSH_GSS_OK;
+ return SSH_GSS_FAILURE;
+}
+
+static Ssh_gss_stat ssh_gssapi_get_mic(struct ssh_gss_library *lib,
+ Ssh_gss_ctx ctx, Ssh_gss_buf *buf,
+ Ssh_gss_buf *hash)
+{
+ struct gssapi_functions *gss = &lib->u.gssapi;
+ gssapi_ssh_gss_ctx *gssctx = (gssapi_ssh_gss_ctx *) ctx;
+ if (gssctx == NULL) return SSH_GSS_FAILURE;
+ return gss->get_mic(&(gssctx->min_stat), gssctx->ctx, 0, buf, hash);
+}
+
+static Ssh_gss_stat ssh_gssapi_free_mic(struct ssh_gss_library *lib,
+ Ssh_gss_buf *hash)
+{
+ /* On Unix this is the same freeing process as ssh_gssapi_free_tok. */
+ return ssh_gssapi_free_tok(lib, hash);
+}
+
+void ssh_gssapi_bind_fns(struct ssh_gss_library *lib)
+{
+ lib->indicate_mech = ssh_gssapi_indicate_mech;
+ lib->import_name = ssh_gssapi_import_name;
+ lib->release_name = ssh_gssapi_release_name;
+ lib->init_sec_context = ssh_gssapi_init_sec_context;
+ lib->free_tok = ssh_gssapi_free_tok;
+ lib->acquire_cred = ssh_gssapi_acquire_cred;
+ lib->release_cred = ssh_gssapi_release_cred;
+ lib->get_mic = ssh_gssapi_get_mic;
+ lib->free_mic = ssh_gssapi_free_mic;
+ lib->display_status = ssh_gssapi_display_status;
+}
+
+#else
+
+/* Dummy function so this source file defines something if NO_GSSAPI
+ is defined. */
+
+int ssh_gssapi_init(void)
+{
+ return 0;
+}
+
+#endif
--- /dev/null
+#ifndef PUTTY_SSHGSSC_H
+#define PUTTY_SSHGSSC_H
+#include "putty.h"
+#ifndef NO_GSSAPI
+
+#include "pgssapi.h"
+#include "sshgss.h"
+
+typedef struct gssapi_ssh_gss_ctx {
+ OM_uint32 maj_stat;
+ OM_uint32 min_stat;
+ gss_ctx_id_t ctx;
+} gssapi_ssh_gss_ctx;
+
+void ssh_gssapi_bind_fns(struct ssh_gss_library *lib);
+
+#else
+
+int ssh_gssapi_init(void);
+
+#endif /*NO_GSSAPI*/
+
+#endif /*PUTTY_SSHGSSC_H*/
--- /dev/null
+#include "ssh.h"
+
+/*
+ * MD5 implementation for PuTTY. Written directly from the spec by
+ * Simon Tatham.
+ */
+
+/* ----------------------------------------------------------------------
+ * Core MD5 algorithm: processes 16-word blocks into a message digest.
+ */
+
+#define F(x,y,z) ( ((x) & (y)) | ((~(x)) & (z)) )
+#define G(x,y,z) ( ((x) & (z)) | ((~(z)) & (y)) )
+#define H(x,y,z) ( (x) ^ (y) ^ (z) )
+#define I(x,y,z) ( (y) ^ ( (x) | ~(z) ) )
+
+#define rol(x,y) ( ((x) << (y)) | (((uint32)x) >> (32-y)) )
+
+#define subround(f,w,x,y,z,k,s,ti) \
+ w = x + rol(w + f(x,y,z) + block[k] + ti, s)
+
+static void MD5_Core_Init(MD5_Core_State * s)
+{
+ s->h[0] = 0x67452301;
+ s->h[1] = 0xefcdab89;
+ s->h[2] = 0x98badcfe;
+ s->h[3] = 0x10325476;
+}
+
+static void MD5_Block(MD5_Core_State * s, uint32 * block)
+{
+ uint32 a, b, c, d;
+
+ a = s->h[0];
+ b = s->h[1];
+ c = s->h[2];
+ d = s->h[3];
+
+ subround(F, a, b, c, d, 0, 7, 0xd76aa478);
+ subround(F, d, a, b, c, 1, 12, 0xe8c7b756);
+ subround(F, c, d, a, b, 2, 17, 0x242070db);
+ subround(F, b, c, d, a, 3, 22, 0xc1bdceee);
+ subround(F, a, b, c, d, 4, 7, 0xf57c0faf);
+ subround(F, d, a, b, c, 5, 12, 0x4787c62a);
+ subround(F, c, d, a, b, 6, 17, 0xa8304613);
+ subround(F, b, c, d, a, 7, 22, 0xfd469501);
+ subround(F, a, b, c, d, 8, 7, 0x698098d8);
+ subround(F, d, a, b, c, 9, 12, 0x8b44f7af);
+ subround(F, c, d, a, b, 10, 17, 0xffff5bb1);
+ subround(F, b, c, d, a, 11, 22, 0x895cd7be);
+ subround(F, a, b, c, d, 12, 7, 0x6b901122);
+ subround(F, d, a, b, c, 13, 12, 0xfd987193);
+ subround(F, c, d, a, b, 14, 17, 0xa679438e);
+ subround(F, b, c, d, a, 15, 22, 0x49b40821);
+ subround(G, a, b, c, d, 1, 5, 0xf61e2562);
+ subround(G, d, a, b, c, 6, 9, 0xc040b340);
+ subround(G, c, d, a, b, 11, 14, 0x265e5a51);
+ subround(G, b, c, d, a, 0, 20, 0xe9b6c7aa);
+ subround(G, a, b, c, d, 5, 5, 0xd62f105d);
+ subround(G, d, a, b, c, 10, 9, 0x02441453);
+ subround(G, c, d, a, b, 15, 14, 0xd8a1e681);
+ subround(G, b, c, d, a, 4, 20, 0xe7d3fbc8);
+ subround(G, a, b, c, d, 9, 5, 0x21e1cde6);
+ subround(G, d, a, b, c, 14, 9, 0xc33707d6);
+ subround(G, c, d, a, b, 3, 14, 0xf4d50d87);
+ subround(G, b, c, d, a, 8, 20, 0x455a14ed);
+ subround(G, a, b, c, d, 13, 5, 0xa9e3e905);
+ subround(G, d, a, b, c, 2, 9, 0xfcefa3f8);
+ subround(G, c, d, a, b, 7, 14, 0x676f02d9);
+ subround(G, b, c, d, a, 12, 20, 0x8d2a4c8a);
+ subround(H, a, b, c, d, 5, 4, 0xfffa3942);
+ subround(H, d, a, b, c, 8, 11, 0x8771f681);
+ subround(H, c, d, a, b, 11, 16, 0x6d9d6122);
+ subround(H, b, c, d, a, 14, 23, 0xfde5380c);
+ subround(H, a, b, c, d, 1, 4, 0xa4beea44);
+ subround(H, d, a, b, c, 4, 11, 0x4bdecfa9);
+ subround(H, c, d, a, b, 7, 16, 0xf6bb4b60);
+ subround(H, b, c, d, a, 10, 23, 0xbebfbc70);
+ subround(H, a, b, c, d, 13, 4, 0x289b7ec6);
+ subround(H, d, a, b, c, 0, 11, 0xeaa127fa);
+ subround(H, c, d, a, b, 3, 16, 0xd4ef3085);
+ subround(H, b, c, d, a, 6, 23, 0x04881d05);
+ subround(H, a, b, c, d, 9, 4, 0xd9d4d039);
+ subround(H, d, a, b, c, 12, 11, 0xe6db99e5);
+ subround(H, c, d, a, b, 15, 16, 0x1fa27cf8);
+ subround(H, b, c, d, a, 2, 23, 0xc4ac5665);
+ subround(I, a, b, c, d, 0, 6, 0xf4292244);
+ subround(I, d, a, b, c, 7, 10, 0x432aff97);
+ subround(I, c, d, a, b, 14, 15, 0xab9423a7);
+ subround(I, b, c, d, a, 5, 21, 0xfc93a039);
+ subround(I, a, b, c, d, 12, 6, 0x655b59c3);
+ subround(I, d, a, b, c, 3, 10, 0x8f0ccc92);
+ subround(I, c, d, a, b, 10, 15, 0xffeff47d);
+ subround(I, b, c, d, a, 1, 21, 0x85845dd1);
+ subround(I, a, b, c, d, 8, 6, 0x6fa87e4f);
+ subround(I, d, a, b, c, 15, 10, 0xfe2ce6e0);
+ subround(I, c, d, a, b, 6, 15, 0xa3014314);
+ subround(I, b, c, d, a, 13, 21, 0x4e0811a1);
+ subround(I, a, b, c, d, 4, 6, 0xf7537e82);
+ subround(I, d, a, b, c, 11, 10, 0xbd3af235);
+ subround(I, c, d, a, b, 2, 15, 0x2ad7d2bb);
+ subround(I, b, c, d, a, 9, 21, 0xeb86d391);
+
+ s->h[0] += a;
+ s->h[1] += b;
+ s->h[2] += c;
+ s->h[3] += d;
+}
+
+/* ----------------------------------------------------------------------
+ * Outer MD5 algorithm: take an arbitrary length byte string,
+ * convert it into 16-word blocks with the prescribed padding at
+ * the end, and pass those blocks to the core MD5 algorithm.
+ */
+
+#define BLKSIZE 64
+
+void MD5Init(struct MD5Context *s)
+{
+ MD5_Core_Init(&s->core);
+ s->blkused = 0;
+ s->lenhi = s->lenlo = 0;
+}
+
+void MD5Update(struct MD5Context *s, unsigned char const *p, unsigned len)
+{
+ unsigned char *q = (unsigned char *) p;
+ uint32 wordblock[16];
+ uint32 lenw = len;
+ int i;
+
+ /*
+ * Update the length field.
+ */
+ s->lenlo += lenw;
+ s->lenhi += (s->lenlo < lenw);
+
+ if (s->blkused + len < BLKSIZE) {
+ /*
+ * Trivial case: just add to the block.
+ */
+ memcpy(s->block + s->blkused, q, len);
+ s->blkused += len;
+ } else {
+ /*
+ * We must complete and process at least one block.
+ */
+ while (s->blkused + len >= BLKSIZE) {
+ memcpy(s->block + s->blkused, q, BLKSIZE - s->blkused);
+ q += BLKSIZE - s->blkused;
+ len -= BLKSIZE - s->blkused;
+ /* Now process the block. Gather bytes little-endian into words */
+ for (i = 0; i < 16; i++) {
+ wordblock[i] =
+ (((uint32) s->block[i * 4 + 3]) << 24) |
+ (((uint32) s->block[i * 4 + 2]) << 16) |
+ (((uint32) s->block[i * 4 + 1]) << 8) |
+ (((uint32) s->block[i * 4 + 0]) << 0);
+ }
+ MD5_Block(&s->core, wordblock);
+ s->blkused = 0;
+ }
+ memcpy(s->block, q, len);
+ s->blkused = len;
+ }
+}
+
+void MD5Final(unsigned char output[16], struct MD5Context *s)
+{
+ int i;
+ unsigned pad;
+ unsigned char c[64];
+ uint32 lenhi, lenlo;
+
+ if (s->blkused >= 56)
+ pad = 56 + 64 - s->blkused;
+ else
+ pad = 56 - s->blkused;
+
+ lenhi = (s->lenhi << 3) | (s->lenlo >> (32 - 3));
+ lenlo = (s->lenlo << 3);
+
+ memset(c, 0, pad);
+ c[0] = 0x80;
+ MD5Update(s, c, pad);
+
+ c[7] = (lenhi >> 24) & 0xFF;
+ c[6] = (lenhi >> 16) & 0xFF;
+ c[5] = (lenhi >> 8) & 0xFF;
+ c[4] = (lenhi >> 0) & 0xFF;
+ c[3] = (lenlo >> 24) & 0xFF;
+ c[2] = (lenlo >> 16) & 0xFF;
+ c[1] = (lenlo >> 8) & 0xFF;
+ c[0] = (lenlo >> 0) & 0xFF;
+
+ MD5Update(s, c, 8);
+
+ for (i = 0; i < 4; i++) {
+ output[4 * i + 3] = (s->core.h[i] >> 24) & 0xFF;
+ output[4 * i + 2] = (s->core.h[i] >> 16) & 0xFF;
+ output[4 * i + 1] = (s->core.h[i] >> 8) & 0xFF;
+ output[4 * i + 0] = (s->core.h[i] >> 0) & 0xFF;
+ }
+}
+
+void MD5Simple(void const *p, unsigned len, unsigned char output[16])
+{
+ struct MD5Context s;
+
+ MD5Init(&s);
+ MD5Update(&s, (unsigned char const *)p, len);
+ MD5Final(output, &s);
+}
+
+/* ----------------------------------------------------------------------
+ * The above is the MD5 algorithm itself. Now we implement the
+ * HMAC wrapper on it.
+ *
+ * Some of these functions are exported directly, because they are
+ * useful elsewhere (SOCKS5 CHAP authentication uses HMAC-MD5).
+ */
+
+void *hmacmd5_make_context(void)
+{
+ return snewn(3, struct MD5Context);
+}
+
+void hmacmd5_free_context(void *handle)
+{
+ sfree(handle);
+}
+
+void hmacmd5_key(void *handle, void const *keyv, int len)
+{
+ struct MD5Context *keys = (struct MD5Context *)handle;
+ unsigned char foo[64];
+ unsigned char const *key = (unsigned char const *)keyv;
+ int i;
+
+ memset(foo, 0x36, 64);
+ for (i = 0; i < len && i < 64; i++)
+ foo[i] ^= key[i];
+ MD5Init(&keys[0]);
+ MD5Update(&keys[0], foo, 64);
+
+ memset(foo, 0x5C, 64);
+ 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 */
+}
+
+static void hmacmd5_key_16(void *handle, unsigned char *key)
+{
+ hmacmd5_key(handle, key, 16);
+}
+
+static void hmacmd5_start(void *handle)
+{
+ struct MD5Context *keys = (struct MD5Context *)handle;
+
+ keys[2] = keys[0]; /* structure copy */
+}
+
+static void hmacmd5_bytes(void *handle, unsigned char const *blk, int len)
+{
+ struct MD5Context *keys = (struct MD5Context *)handle;
+ MD5Update(&keys[2], blk, len);
+}
+
+static void hmacmd5_genresult(void *handle, unsigned char *hmac)
+{
+ struct MD5Context *keys = (struct MD5Context *)handle;
+ struct MD5Context s;
+ unsigned char intermediate[16];
+
+ s = keys[2]; /* structure copy */
+ MD5Final(intermediate, &s);
+ s = keys[1]; /* structure copy */
+ MD5Update(&s, intermediate, 16);
+ MD5Final(hmac, &s);
+}
+
+static int hmacmd5_verresult(void *handle, unsigned char const *hmac)
+{
+ unsigned char correct[16];
+ hmacmd5_genresult(handle, correct);
+ return !memcmp(correct, hmac, 16);
+}
+
+static void hmacmd5_do_hmac_internal(void *handle,
+ unsigned char const *blk, int len,
+ unsigned char const *blk2, int len2,
+ unsigned char *hmac)
+{
+ hmacmd5_start(handle);
+ hmacmd5_bytes(handle, blk, len);
+ if (blk2) hmacmd5_bytes(handle, blk2, len2);
+ hmacmd5_genresult(handle, hmac);
+}
+
+void hmacmd5_do_hmac(void *handle, unsigned char const *blk, int len,
+ unsigned char *hmac)
+{
+ hmacmd5_do_hmac_internal(handle, blk, len, NULL, 0, hmac);
+}
+
+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);
+
+ hmacmd5_do_hmac_internal(handle, seqbuf, 4, blk, len, hmac);
+}
+
+static void hmacmd5_generate(void *handle, unsigned char *blk, int len,
+ unsigned long seq)
+{
+ hmacmd5_do_hmac_ssh(handle, blk, len, seq, blk + len);
+}
+
+static int hmacmd5_verify(void *handle, unsigned char *blk, int len,
+ unsigned long seq)
+{
+ unsigned char correct[16];
+ hmacmd5_do_hmac_ssh(handle, blk, len, seq, correct);
+ return !memcmp(correct, blk + len, 16);
+}
+
+const struct ssh_mac ssh_hmac_md5 = {
+ hmacmd5_make_context, hmacmd5_free_context, hmacmd5_key_16,
+ hmacmd5_generate, hmacmd5_verify,
+ hmacmd5_start, hmacmd5_bytes, hmacmd5_genresult, hmacmd5_verresult,
+ "hmac-md5",
+ 16,
+ "HMAC-MD5"
+};
--- /dev/null
+#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 *list = snew(struct ssh_gss_liblist *);
+ list->libraries = NULL;
+ list->nlibraries = 0;
+ return list;
+}
+
+void ssh_gss_cleanup(struct ssh_gss_liblist *list)
+{
+ sfree(list);
+}
+
+#endif /* NO_GSSAPI */
--- /dev/null
+/*
+ * Prime generation.
+ */
+
+#include <assert.h>
+#include "ssh.h"
+
+/*
+ * This prime generation algorithm is pretty much cribbed from
+ * OpenSSL. The algorithm is:
+ *
+ * - invent a B-bit random number and ensure the top and bottom
+ * bits are set (so it's definitely B-bit, and it's definitely
+ * odd)
+ *
+ * - see if it's coprime to all primes below 2^16; increment it by
+ * two until it is (this shouldn't take long in general)
+ *
+ * - perform the Miller-Rabin primality test enough times to
+ * ensure the probability of it being composite is 2^-80 or
+ * less
+ *
+ * - go back to square one if any M-R test fails.
+ */
+
+/*
+ * The Miller-Rabin primality test is an extension to the Fermat
+ * test. The Fermat test just checks that a^(p-1) == 1 mod p; this
+ * is vulnerable to Carmichael numbers. Miller-Rabin considers how
+ * that 1 is derived as well.
+ *
+ * Lemma: if a^2 == 1 (mod p), and p is prime, then either a == 1
+ * or a == -1 (mod p).
+ *
+ * Proof: p divides a^2-1, i.e. p divides (a+1)(a-1). Hence,
+ * since p is prime, either p divides (a+1) or p divides (a-1).
+ * But this is the same as saying that either a is congruent to
+ * -1 mod p or a is congruent to +1 mod p. []
+ *
+ * Comment: This fails when p is not prime. Consider p=mn, so
+ * that mn divides (a+1)(a-1). Now we could have m dividing (a+1)
+ * and n dividing (a-1), without the whole of mn dividing either.
+ * For example, consider a=10 and p=99. 99 = 9 * 11; 9 divides
+ * 10-1 and 11 divides 10+1, so a^2 is congruent to 1 mod p
+ * without a having to be congruent to either 1 or -1.
+ *
+ * So the Miller-Rabin test, as well as considering a^(p-1),
+ * considers a^((p-1)/2), a^((p-1)/4), and so on as far as it can
+ * go. In other words. we write p-1 as q * 2^k, with k as large as
+ * possible (i.e. q must be odd), and we consider the powers
+ *
+ * a^(q*2^0) a^(q*2^1) ... a^(q*2^(k-1)) a^(q*2^k)
+ * i.e. a^((n-1)/2^k) a^((n-1)/2^(k-1)) ... a^((n-1)/2) a^(n-1)
+ *
+ * If p is to be prime, the last of these must be 1. Therefore, by
+ * the above lemma, the one before it must be either 1 or -1. And
+ * _if_ it's 1, then the one before that must be either 1 or -1,
+ * and so on ... In other words, we expect to see a trailing chain
+ * of 1s preceded by a -1. (If we're unlucky, our trailing chain of
+ * 1s will be as long as the list so we'll never get to see what
+ * lies before it. This doesn't count as a test failure because it
+ * hasn't _proved_ that p is not prime.)
+ *
+ * For example, consider a=2 and p=1729. 1729 is a Carmichael
+ * number: although it's not prime, it satisfies a^(p-1) == 1 mod p
+ * for any a coprime to it. So the Fermat test wouldn't have a
+ * problem with it at all, unless we happened to stumble on an a
+ * which had a common factor.
+ *
+ * So. 1729 - 1 equals 27 * 2^6. So we look at
+ *
+ * 2^27 mod 1729 == 645
+ * 2^108 mod 1729 == 1065
+ * 2^216 mod 1729 == 1
+ * 2^432 mod 1729 == 1
+ * 2^864 mod 1729 == 1
+ * 2^1728 mod 1729 == 1
+ *
+ * We do have a trailing string of 1s, so the Fermat test would
+ * have been happy. But this trailing string of 1s is preceded by
+ * 1065; whereas if 1729 were prime, we'd expect to see it preceded
+ * by -1 (i.e. 1728.). Guards! Seize this impostor.
+ *
+ * (If we were unlucky, we might have tried a=16 instead of a=2;
+ * now 16^27 mod 1729 == 1, so we would have seen a long string of
+ * 1s and wouldn't have seen the thing _before_ the 1s. So, just
+ * like the Fermat test, for a given p there may well exist values
+ * of a which fail to show up its compositeness. So we try several,
+ * just like the Fermat test. The difference is that Miller-Rabin
+ * is not _in general_ fooled by Carmichael numbers.)
+ *
+ * Put simply, then, the Miller-Rabin test requires us to:
+ *
+ * 1. write p-1 as q * 2^k, with q odd
+ * 2. compute z = (a^q) mod p.
+ * 3. report success if z == 1 or z == -1.
+ * 4. square z at most k-1 times, and report success if it becomes
+ * -1 at any point.
+ * 5. report failure otherwise.
+ *
+ * (We expect z to become -1 after at most k-1 squarings, because
+ * if it became -1 after k squarings then a^(p-1) would fail to be
+ * 1. And we don't need to investigate what happens after we see a
+ * -1, because we _know_ that -1 squared is 1 modulo anything at
+ * all, so after we've seen a -1 we can be sure of seeing nothing
+ * but 1s.)
+ */
+
+/*
+ * The first few odd primes.
+ *
+ * import sys
+ * def sieve(n):
+ * z = []
+ * list = []
+ * for i in range(n): z.append(1)
+ * for i in range(2,n):
+ * if z[i]:
+ * list.append(i)
+ * for j in range(i,n,i): z[j] = 0
+ * 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,
+ 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,
+};
+
+#define NPRIMES (sizeof(primes) / sizeof(*primes))
+
+/*
+ * Generate a prime. We can deal with various extra properties of
+ * the prime:
+ *
+ * - to speed up use in RSA, we can arrange to select a prime with
+ * the property (prime % modulus) != residue.
+ *
+ * - 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.
+ */
+Bignum primegen(int bits, int modulus, int residue, Bignum factor,
+ int phase, progfn_t pfn, void *pfnparam)
+{
+ int i, k, v, byte, bitsleft, check, checks;
+ unsigned long delta;
+ unsigned long moduli[NPRIMES + 1];
+ unsigned long residues[NPRIMES + 1];
+ unsigned long multipliers[NPRIMES + 1];
+ Bignum p, pm1, q, wqp, wqp2;
+ int progress = 0;
+
+ byte = 0;
+ bitsleft = 0;
+
+ STARTOVER:
+
+ pfn(pfnparam, PROGFN_PROGRESS, phase, ++progress);
+
+ /*
+ * Generate a k-bit random number with top and bottom bits set.
+ * Alternatively, if `factor' is nonzero, generate a k-bit
+ * 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)
+ v = (i != 0 || !factor) ? 1 : 0;
+ else {
+ if (bitsleft <= 0)
+ bitsleft = 8, byte = random_byte();
+ v = byte & 1;
+ byte >>= 1;
+ bitsleft--;
+ }
+ bignum_set_bit(p, i, v);
+ }
+ if (factor) {
+ Bignum tmp = p;
+ p = bigmul(tmp, factor);
+ freebn(tmp);
+ assert(bignum_bit(p, 0) == 0);
+ bignum_set_bit(p, 0, 1);
+ }
+
+ /*
+ * Ensure this random number is coprime to the first few
+ * primes, by repeatedly adding either 2 or 2*factor to it
+ * until it is.
+ */
+ for (i = 0; i < NPRIMES; i++) {
+ moduli[i] = primes[i];
+ residues[i] = bignum_mod_short(p, primes[i]);
+ if (factor)
+ multipliers[i] = bignum_mod_short(factor, primes[i]);
+ else
+ multipliers[i] = 1;
+ }
+ moduli[NPRIMES] = modulus;
+ residues[NPRIMES] = (bignum_mod_short(p, (unsigned short) modulus)
+ + modulus - residue);
+ if (factor)
+ multipliers[NPRIMES] = bignum_mod_short(factor, modulus);
+ else
+ multipliers[NPRIMES] = 1;
+ delta = 0;
+ while (1) {
+ for (i = 0; i < (sizeof(moduli) / sizeof(*moduli)); i++)
+ if (!((residues[i] + delta * multipliers[i]) % moduli[i]))
+ break;
+ if (i < (sizeof(moduli) / sizeof(*moduli))) { /* we broke */
+ delta += 2;
+ if (delta > 65536) {
+ freebn(p);
+ goto STARTOVER;
+ }
+ continue;
+ }
+ break;
+ }
+ q = p;
+ if (factor) {
+ Bignum tmp;
+ tmp = bignum_from_long(delta);
+ p = bigmuladd(tmp, factor, q);
+ freebn(tmp);
+ } else {
+ p = bignum_add_long(q, delta);
+ }
+ freebn(q);
+
+ /*
+ * Now apply the Miller-Rabin primality test a few times. First
+ * work out how many checks are needed.
+ */
+ checks = 27;
+ if (bits >= 150)
+ checks = 18;
+ if (bits >= 200)
+ checks = 15;
+ if (bits >= 250)
+ checks = 12;
+ if (bits >= 300)
+ checks = 9;
+ if (bits >= 350)
+ checks = 8;
+ if (bits >= 400)
+ checks = 7;
+ if (bits >= 450)
+ checks = 6;
+ if (bits >= 550)
+ checks = 5;
+ if (bits >= 650)
+ checks = 4;
+ if (bits >= 850)
+ checks = 3;
+ if (bits >= 1300)
+ checks = 2;
+
+ /*
+ * Next, write p-1 as q*2^k.
+ */
+ for (k = 0; bignum_bit(p, k) == !k; k++)
+ continue; /* find first 1 bit in p-1 */
+ q = bignum_rshift(p, k);
+ /* And store p-1 itself, which we'll need. */
+ pm1 = copybn(p);
+ decbn(pm1);
+
+ /*
+ * Now, for each check ...
+ */
+ for (check = 0; check < checks; check++) {
+ Bignum w;
+
+ /*
+ * Invent a random number between 1 and p-1 inclusive.
+ */
+ while (1) {
+ w = bn_power_2(bits - 1);
+ for (i = 0; i < bits; i++) {
+ if (bitsleft <= 0)
+ bitsleft = 8, byte = random_byte();
+ v = byte & 1;
+ byte >>= 1;
+ bitsleft--;
+ bignum_set_bit(w, i, v);
+ }
+ bn_restore_invariant(w);
+ if (bignum_cmp(w, p) >= 0 || bignum_cmp(w, Zero) == 0) {
+ freebn(w);
+ continue;
+ }
+ break;
+ }
+
+ pfn(pfnparam, PROGFN_PROGRESS, phase, ++progress);
+
+ /*
+ * Compute w^q mod p.
+ */
+ wqp = modpow(w, q, p);
+ freebn(w);
+
+ /*
+ * See if this is 1, or if it is -1, or if it becomes -1
+ * when squared at most k-1 times.
+ */
+ if (bignum_cmp(wqp, One) == 0 || bignum_cmp(wqp, pm1) == 0) {
+ freebn(wqp);
+ continue;
+ }
+ for (i = 0; i < k - 1; i++) {
+ wqp2 = modmul(wqp, wqp, p);
+ freebn(wqp);
+ wqp = wqp2;
+ if (bignum_cmp(wqp, pm1) == 0)
+ break;
+ }
+ if (i < k - 1) {
+ freebn(wqp);
+ continue;
+ }
+
+ /*
+ * It didn't. Therefore, w is a witness for the
+ * compositeness of p.
+ */
+ freebn(wqp);
+ freebn(p);
+ freebn(pm1);
+ freebn(q);
+ goto STARTOVER;
+ }
+
+ /*
+ * We have a prime!
+ */
+ freebn(q);
+ freebn(pm1);
+ return p;
+}
--- /dev/null
+/*
+ * Generic SSH public-key handling operations. In particular,
+ * reading of SSH public-key files, and also the generic `sign'
+ * operation for SSH-2 (which checks the type of the key and
+ * dispatches to the appropriate key-type specific function).
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#include "putty.h"
+#include "ssh.h"
+#include "misc.h"
+
+#define rsa_signature "SSH PRIVATE KEY FILE FORMAT 1.1\n"
+
+#define BASE64_TOINT(x) ( (x)-'A'<26 ? (x)-'A'+0 :\
+ (x)-'a'<26 ? (x)-'a'+26 :\
+ (x)-'0'<10 ? (x)-'0'+52 :\
+ (x)=='+' ? 62 : \
+ (x)=='/' ? 63 : 0 )
+
+static int loadrsakey_main(FILE * fp, struct RSAKey *key, int pub_only,
+ char **commentptr, char *passphrase,
+ const char **error)
+{
+ unsigned char buf[16384];
+ unsigned char keybuf[16];
+ int len;
+ int i, j, ciphertype;
+ int ret = 0;
+ struct MD5Context md5c;
+ char *comment;
+
+ *error = NULL;
+
+ /* Slurp the whole file (minus the header) into a buffer. */
+ len = fread(buf, 1, sizeof(buf), fp);
+ fclose(fp);
+ if (len < 0 || len == sizeof(buf)) {
+ *error = "error reading file";
+ goto end; /* file too big or not read */
+ }
+
+ i = 0;
+ *error = "file format error";
+
+ /*
+ * A zero byte. (The signature includes a terminating NUL.)
+ */
+ if (len - i < 1 || buf[i] != 0)
+ goto end;
+ i++;
+
+ /* One byte giving encryption type, and one reserved uint32. */
+ if (len - i < 1)
+ goto end;
+ ciphertype = buf[i];
+ if (ciphertype != 0 && ciphertype != SSH_CIPHER_3DES)
+ goto end;
+ i++;
+ if (len - i < 4)
+ goto end; /* reserved field not present */
+ 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)
+ goto end; /* overran */
+
+ /* Next, the comment field. */
+ j = GET_32BIT(buf + i);
+ i += 4;
+ if (len - i < j)
+ goto end;
+ comment = snewn(j + 1, char);
+ if (comment) {
+ memcpy(comment, buf + i, j);
+ comment[j] = '\0';
+ }
+ i += j;
+ if (commentptr)
+ *commentptr = dupstr(comment);
+ if (key)
+ key->comment = comment;
+ else
+ sfree(comment);
+
+ if (pub_only) {
+ ret = 1;
+ goto end;
+ }
+
+ if (!key) {
+ ret = ciphertype != 0;
+ *error = NULL;
+ goto end;
+ }
+
+ /*
+ * Decrypt remainder of buffer.
+ */
+ 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 */
+ }
+
+ /*
+ * We are now in the secret part of the key. The first four
+ * bytes should be of the form a, b, a, b.
+ */
+ if (len - i < 4)
+ goto end;
+ if (buf[i] != buf[i + 2] || buf[i + 1] != buf[i + 3]) {
+ *error = "wrong passphrase";
+ ret = -1;
+ goto end;
+ }
+ i += 4;
+
+ /*
+ * After that, we have one further bignum which is our
+ * decryption exponent, and then the three auxiliary values
+ * (iqmp, q, p).
+ */
+ j = makeprivate(buf + i, len - i, key);
+ if (j < 0) goto end;
+ i += j;
+ j = ssh1_read_bignum(buf + i, len - i, &key->iqmp);
+ if (j < 0) goto end;
+ i += j;
+ j = ssh1_read_bignum(buf + i, len - i, &key->q);
+ if (j < 0) goto end;
+ i += j;
+ j = ssh1_read_bignum(buf + i, len - i, &key->p);
+ if (j < 0) goto end;
+ i += j;
+
+ if (!rsa_verify(key)) {
+ *error = "rsa_verify failed";
+ freersakey(key);
+ ret = 0;
+ } else
+ ret = 1;
+
+ end:
+ memset(buf, 0, sizeof(buf)); /* burn the evidence */
+ return ret;
+}
+
+int loadrsakey(const Filename *filename, struct RSAKey *key, char *passphrase,
+ const char **errorstr)
+{
+ FILE *fp;
+ char buf[64];
+ int ret = 0;
+ const char *error = NULL;
+
+ fp = f_open(*filename, "rb", FALSE);
+ if (!fp) {
+ error = "can't open file";
+ goto end;
+ }
+
+ /*
+ * Read the first line of the file and see if it's a v1 private
+ * key file.
+ */
+ if (fgets(buf, sizeof(buf), fp) && !strcmp(buf, rsa_signature)) {
+ /*
+ * This routine will take care of calling fclose() for us.
+ */
+ ret = loadrsakey_main(fp, key, FALSE, NULL, passphrase, &error);
+ fp = NULL;
+ goto end;
+ }
+
+ /*
+ * Otherwise, we have nothing. Return empty-handed.
+ */
+ error = "not an SSH-1 RSA file";
+
+ end:
+ if (fp)
+ fclose(fp);
+ if ((ret != 1) && errorstr)
+ *errorstr = error;
+ return ret;
+}
+
+/*
+ * See whether an RSA key is encrypted. Return its comment field as
+ * well.
+ */
+int rsakey_encrypted(const Filename *filename, char **comment)
+{
+ FILE *fp;
+ char buf[64];
+
+ 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
+ * key file.
+ */
+ if (fgets(buf, sizeof(buf), fp) && !strcmp(buf, rsa_signature)) {
+ const char *dummy;
+ /*
+ * This routine will take care of calling fclose() for us.
+ */
+ return loadrsakey_main(fp, NULL, FALSE, comment, NULL, &dummy);
+ }
+ fclose(fp);
+ return 0; /* wasn't the right kind of file */
+}
+
+/*
+ * Return a malloc'ed chunk of memory containing the public blob of
+ * an RSA key, as given in the agent protocol (modulus bits,
+ * exponent, modulus).
+ */
+int rsakey_pubblob(const Filename *filename, void **blob, int *bloblen,
+ char **commentptr, const char **errorstr)
+{
+ FILE *fp;
+ char buf[64];
+ struct RSAKey key;
+ int ret;
+ const char *error = NULL;
+
+ /* Default return if we fail. */
+ *blob = NULL;
+ *bloblen = 0;
+ ret = 0;
+
+ fp = f_open(*filename, "rb", FALSE);
+ if (!fp) {
+ error = "can't open file";
+ goto end;
+ }
+
+ /*
+ * Read the first line of the file and see if it's a v1 private
+ * key file.
+ */
+ if (fgets(buf, sizeof(buf), fp) && !strcmp(buf, rsa_signature)) {
+ 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;
+ }
+ } else {
+ error = "not an SSH-1 RSA file";
+ }
+
+ end:
+ if (fp)
+ fclose(fp);
+ if ((ret != 1) && errorstr)
+ *errorstr = error;
+ return ret;
+}
+
+/*
+ * Save an RSA key file. Return nonzero on success.
+ */
+int saversakey(const Filename *filename, struct RSAKey *key, char *passphrase)
+{
+ unsigned char buf[16384];
+ unsigned char keybuf[16];
+ struct MD5Context md5c;
+ unsigned char *p, *estart;
+ FILE *fp;
+
+ /*
+ * Write the initial signature.
+ */
+ p = buf;
+ memcpy(p, rsa_signature, sizeof(rsa_signature));
+ p += sizeof(rsa_signature);
+
+ /*
+ * One byte giving encryption type, and one reserved (zero)
+ * uint32.
+ */
+ *p++ = (passphrase ? SSH_CIPHER_3DES : 0);
+ PUT_32BIT(p, 0);
+ p += 4;
+
+ /*
+ * An ordinary SSH-1 public key consists of: a uint32
+ * containing the bit count, then two bignums containing the
+ * modulus and exponent respectively.
+ */
+ PUT_32BIT(p, bignum_bitcount(key->modulus));
+ p += 4;
+ p += ssh1_write_bignum(p, key->modulus);
+ p += ssh1_write_bignum(p, key->exponent);
+
+ /*
+ * A string containing the comment field.
+ */
+ if (key->comment) {
+ PUT_32BIT(p, strlen(key->comment));
+ p += 4;
+ memcpy(p, key->comment, strlen(key->comment));
+ p += strlen(key->comment);
+ } else {
+ PUT_32BIT(p, 0);
+ p += 4;
+ }
+
+ /*
+ * The encrypted portion starts here.
+ */
+ estart = p;
+
+ /*
+ * Two bytes, then the same two bytes repeated.
+ */
+ *p++ = random_byte();
+ *p++ = random_byte();
+ p[0] = p[-2];
+ p[1] = p[-1];
+ p += 2;
+
+ /*
+ * Four more bignums: the decryption exponent, then iqmp, then
+ * q, then p.
+ */
+ p += ssh1_write_bignum(p, key->private_exponent);
+ p += ssh1_write_bignum(p, key->iqmp);
+ p += ssh1_write_bignum(p, key->q);
+ p += ssh1_write_bignum(p, key->p);
+
+ /*
+ * Now write zeros until the encrypted portion is a multiple of
+ * 8 bytes.
+ */
+ while ((p - estart) % 8)
+ *p++ = '\0';
+
+ /*
+ * Now encrypt the encrypted portion.
+ */
+ 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 */
+ }
+
+ /*
+ * Done. Write the result to the file.
+ */
+ 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;
+ } else
+ return 0;
+}
+
+/* ----------------------------------------------------------------------
+ * SSH-2 private key load/store functions.
+ */
+
+/*
+ * PuTTY's own format for SSH-2 keys is as follows:
+ *
+ * The file is text. Lines are terminated by CRLF, although CR-only
+ * and LF-only are tolerated on input.
+ *
+ * The first line says "PuTTY-User-Key-File-2: " plus the name of the
+ * algorithm ("ssh-dss", "ssh-rsa" etc).
+ *
+ * The next line says "Encryption: " plus an encryption type.
+ * Currently the only supported encryption types are "aes256-cbc"
+ * and "none".
+ *
+ * The next line says "Comment: " plus the comment string.
+ *
+ * Next there is a line saying "Public-Lines: " plus a number N.
+ * The following N lines contain a base64 encoding of the public
+ * part of the key. This is encoded as the standard SSH-2 public key
+ * blob (with no initial length): so for RSA, for example, it will
+ * read
+ *
+ * string "ssh-rsa"
+ * mpint exponent
+ * mpint modulus
+ *
+ * Next, there is a line saying "Private-Lines: " plus a number N,
+ * and then N lines containing the (potentially encrypted) private
+ * part of the key. For the key type "ssh-rsa", this will be
+ * composed of
+ *
+ * mpint private_exponent
+ * mpint p (the larger of the two primes)
+ * mpint q (the smaller prime)
+ * mpint iqmp (the inverse of q modulo p)
+ * data padding (to reach a multiple of the cipher block size)
+ *
+ * And for "ssh-dss", it will be composed of
+ *
+ * mpint x (the private key parameter)
+ * [ string hash 20-byte hash of mpints p || q || g only in old format ]
+ *
+ * Finally, there is a line saying "Private-MAC: " plus a hex
+ * representation of a HMAC-SHA-1 of:
+ *
+ * string name of algorithm ("ssh-dss", "ssh-rsa")
+ * string encryption type
+ * string comment
+ * string public-blob
+ * string private-plaintext (the plaintext version of the
+ * private part, including the final
+ * padding)
+ *
+ * The key to the MAC is itself a SHA-1 hash of:
+ *
+ * data "putty-private-key-file-mac-key"
+ * data passphrase
+ *
+ * (An empty passphrase is used for unencrypted keys.)
+ *
+ * If the key is encrypted, the encryption key is derived from the
+ * passphrase by means of a succession of SHA-1 hashes. Each hash
+ * is the hash of:
+ *
+ * uint32 sequence-number
+ * data passphrase
+ *
+ * where the sequence-number increases from zero. As many of these
+ * hashes are used as necessary.
+ *
+ * For backwards compatibility with snapshots between 0.51 and
+ * 0.52, we also support the older key file format, which begins
+ * with "PuTTY-User-Key-File-1" (version number differs). In this
+ * format the Private-MAC: field only covers the private-plaintext
+ * field and nothing else (and without the 4-byte string length on
+ * the front too). Moreover, the Private-MAC: field can be replaced
+ * with a Private-Hash: field which is a plain SHA-1 hash instead of
+ * an HMAC (this was generated for unencrypted keys).
+ */
+
+static int read_header(FILE * fp, char *header)
+{
+ int len = 39;
+ int c;
+
+ while (len > 0) {
+ c = fgetc(fp);
+ if (c == '\n' || c == '\r' || c == EOF)
+ return 0; /* failure */
+ if (c == ':') {
+ c = fgetc(fp);
+ if (c != ' ')
+ return 0;
+ *header = '\0';
+ return 1; /* success! */
+ }
+ if (len == 0)
+ return 0; /* failure */
+ *header++ = c;
+ len--;
+ }
+ return 0; /* failure */
+}
+
+static char *read_body(FILE * fp)
+{
+ char *text;
+ int len;
+ int size;
+ int c;
+
+ size = 128;
+ text = snewn(size, char);
+ len = 0;
+ text[len] = '\0';
+
+ while (1) {
+ c = fgetc(fp);
+ if (c == '\r' || c == '\n' || c == EOF) {
+ if (c != EOF) {
+ c = fgetc(fp);
+ if (c != '\r' && c != '\n')
+ ungetc(c, fp);
+ }
+ return text;
+ }
+ if (len + 1 >= size) {
+ size += 128;
+ text = sresize(text, size, char);
+ }
+ text[len++] = c;
+ text[len] = '\0';
+ }
+}
+
+int base64_decode_atom(char *atom, unsigned char *out)
+{
+ int vals[4];
+ int i, v, len;
+ unsigned word;
+ char c;
+
+ for (i = 0; i < 4; i++) {
+ c = atom[i];
+ if (c >= 'A' && c <= 'Z')
+ v = c - 'A';
+ else if (c >= 'a' && c <= 'z')
+ v = c - 'a' + 26;
+ else if (c >= '0' && c <= '9')
+ v = c - '0' + 52;
+ else if (c == '+')
+ v = 62;
+ else if (c == '/')
+ v = 63;
+ else if (c == '=')
+ v = -1;
+ else
+ return 0; /* invalid atom */
+ vals[i] = v;
+ }
+
+ if (vals[0] == -1 || vals[1] == -1)
+ return 0;
+ if (vals[2] == -1 && vals[3] != -1)
+ return 0;
+
+ if (vals[3] != -1)
+ len = 3;
+ else if (vals[2] != -1)
+ len = 2;
+ else
+ len = 1;
+
+ word = ((vals[0] << 18) |
+ (vals[1] << 12) | ((vals[2] & 0x3F) << 6) | (vals[3] & 0x3F));
+ out[0] = (word >> 16) & 0xFF;
+ if (len > 1)
+ out[1] = (word >> 8) & 0xFF;
+ if (len > 2)
+ out[2] = word & 0xFF;
+ return len;
+}
+
+static unsigned char *read_blob(FILE * fp, int nlines, int *bloblen)
+{
+ unsigned char *blob;
+ char *line;
+ int linelen, len;
+ int i, j, k;
+
+ /* We expect at most 64 base64 characters, ie 48 real bytes, per line. */
+ blob = snewn(48 * nlines, unsigned char);
+ len = 0;
+ for (i = 0; i < nlines; i++) {
+ line = read_body(fp);
+ if (!line) {
+ sfree(blob);
+ return NULL;
+ }
+ linelen = strlen(line);
+ if (linelen % 4 != 0 || linelen > 64) {
+ sfree(blob);
+ sfree(line);
+ return NULL;
+ }
+ for (j = 0; j < linelen; j += 4) {
+ k = base64_decode_atom(line + j, blob + len);
+ if (!k) {
+ sfree(line);
+ sfree(blob);
+ return NULL;
+ }
+ len += k;
+ }
+ sfree(line);
+ }
+ *bloblen = len;
+ return blob;
+}
+
+/*
+ * Magic error return value for when the passphrase is wrong.
+ */
+struct ssh2_userkey ssh2_wrong_passphrase = {
+ NULL, NULL, NULL
+};
+
+const struct ssh_signkey *find_pubkey_alg(const char *name)
+{
+ if (!strcmp(name, "ssh-rsa"))
+ return &ssh_rsa;
+ else if (!strcmp(name, "ssh-dss"))
+ return &ssh_dss;
+ else
+ return NULL;
+}
+
+struct ssh2_userkey *ssh2_load_userkey(const Filename *filename,
+ char *passphrase, const char **errorstr)
+{
+ FILE *fp;
+ char header[40], *b, *encryption, *comment, *mac;
+ const struct ssh_signkey *alg;
+ struct ssh2_userkey *ret;
+ int cipher, cipherblk;
+ unsigned char *public_blob, *private_blob;
+ int public_blob_len, private_blob_len;
+ int i, is_mac, old_fmt;
+ int passlen = passphrase ? strlen(passphrase) : 0;
+ const char *error = NULL;
+
+ ret = NULL; /* return NULL for most errors */
+ encryption = comment = mac = NULL;
+ public_blob = private_blob = NULL;
+
+ 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))
+ goto error;
+ if (0 == strcmp(header, "PuTTY-User-Key-File-2")) {
+ 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 {
+ error = "not a PuTTY SSH-2 private key";
+ goto error;
+ }
+ error = "file format error";
+ if ((b = read_body(fp)) == NULL)
+ goto error;
+ /* Select key algorithm structure. */
+ alg = find_pubkey_alg(b);
+ if (!alg) {
+ sfree(b);
+ goto error;
+ }
+ sfree(b);
+
+ /* Read the Encryption header line. */
+ if (!read_header(fp, header) || 0 != strcmp(header, "Encryption"))
+ goto error;
+ if ((encryption = read_body(fp)) == NULL)
+ goto error;
+ if (!strcmp(encryption, "aes256-cbc")) {
+ cipher = 1;
+ 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"))
+ goto error;
+ if ((comment = read_body(fp)) == NULL)
+ goto error;
+
+ /* Read the Public-Lines header line and the public blob. */
+ if (!read_header(fp, header) || 0 != strcmp(header, "Public-Lines"))
+ goto error;
+ if ((b = read_body(fp)) == NULL)
+ goto error;
+ i = atoi(b);
+ sfree(b);
+ if ((public_blob = read_blob(fp, i, &public_blob_len)) == NULL)
+ goto error;
+
+ /* Read the Private-Lines header line and the Private blob. */
+ if (!read_header(fp, header) || 0 != strcmp(header, "Private-Lines"))
+ goto error;
+ if ((b = read_body(fp)) == NULL)
+ goto error;
+ i = atoi(b);
+ sfree(b);
+ if ((private_blob = read_blob(fp, i, &private_blob_len)) == NULL)
+ goto error;
+
+ /* Read the Private-MAC or Private-Hash header line. */
+ if (!read_header(fp, header))
+ goto error;
+ if (0 == strcmp(header, "Private-MAC")) {
+ if ((mac = read_body(fp)) == NULL)
+ goto error;
+ is_mac = 1;
+ } else if (0 == strcmp(header, "Private-Hash") && old_fmt) {
+ if ((mac = read_body(fp)) == NULL)
+ goto error;
+ is_mac = 0;
+ } else
+ goto error;
+
+ fclose(fp);
+ fp = NULL;
+
+ /*
+ * Decrypt the private blob.
+ */
+ if (cipher) {
+ unsigned char key[40];
+ SHA_State s;
+
+ if (!passphrase)
+ goto error;
+ if (private_blob_len % cipherblk)
+ goto error;
+
+ SHA_Init(&s);
+ SHA_Bytes(&s, "\0\0\0\0", 4);
+ SHA_Bytes(&s, passphrase, passlen);
+ SHA_Final(&s, key + 0);
+ SHA_Init(&s);
+ SHA_Bytes(&s, "\0\0\0\1", 4);
+ SHA_Bytes(&s, passphrase, passlen);
+ SHA_Final(&s, key + 20);
+ aes256_decrypt_pubkey(key, private_blob, private_blob_len);
+ }
+
+ /*
+ * Verify the MAC.
+ */
+ {
+ char realmac[41];
+ unsigned char binary[20];
+ unsigned char *macdata;
+ int maclen;
+ int free_macdata;
+
+ if (old_fmt) {
+ /* MAC (or hash) only covers the private blob. */
+ macdata = private_blob;
+ maclen = private_blob_len;
+ free_macdata = 0;
+ } else {
+ unsigned char *p;
+ int namelen = strlen(alg->name);
+ int enclen = strlen(encryption);
+ int commlen = strlen(comment);
+ maclen = (4 + namelen +
+ 4 + enclen +
+ 4 + commlen +
+ 4 + public_blob_len +
+ 4 + private_blob_len);
+ macdata = snewn(maclen, unsigned char);
+ p = macdata;
+#define DO_STR(s,len) PUT_32BIT(p,(len));memcpy(p+4,(s),(len));p+=4+(len)
+ DO_STR(alg->name, namelen);
+ DO_STR(encryption, enclen);
+ DO_STR(comment, commlen);
+ DO_STR(public_blob, public_blob_len);
+ DO_STR(private_blob, private_blob_len);
+
+ free_macdata = 1;
+ }
+
+ if (is_mac) {
+ SHA_State s;
+ unsigned char mackey[20];
+ char header[] = "putty-private-key-file-mac-key";
+
+ SHA_Init(&s);
+ SHA_Bytes(&s, header, sizeof(header)-1);
+ if (cipher && passphrase)
+ 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));
+ } else {
+ SHA_Simple(macdata, maclen, binary);
+ }
+
+ if (free_macdata) {
+ memset(macdata, 0, maclen);
+ sfree(macdata);
+ }
+
+ for (i = 0; i < 20; i++)
+ sprintf(realmac + 2 * i, "%02x", binary[i]);
+
+ if (strcmp(mac, realmac)) {
+ /* An incorrect MAC is an unconditional Error if the key is
+ * unencrypted. Otherwise, it means Wrong Passphrase. */
+ if (cipher) {
+ error = "wrong passphrase";
+ ret = SSH2_WRONG_PASSPHRASE;
+ } else {
+ error = "MAC failed";
+ ret = NULL;
+ }
+ goto error;
+ }
+ }
+ sfree(mac);
+
+ /*
+ * Create and return the key.
+ */
+ ret = snew(struct ssh2_userkey);
+ ret->alg = alg;
+ ret->comment = comment;
+ ret->data = alg->createkey(public_blob, public_blob_len,
+ private_blob, private_blob_len);
+ if (!ret->data) {
+ sfree(ret->comment);
+ sfree(ret);
+ ret = NULL;
+ error = "createkey failed";
+ goto error;
+ }
+ sfree(public_blob);
+ sfree(private_blob);
+ sfree(encryption);
+ if (errorstr)
+ *errorstr = NULL;
+ return ret;
+
+ /*
+ * Error processing.
+ */
+ error:
+ if (fp)
+ fclose(fp);
+ if (comment)
+ sfree(comment);
+ if (encryption)
+ sfree(encryption);
+ if (mac)
+ sfree(mac);
+ if (public_blob)
+ sfree(public_blob);
+ if (private_blob)
+ sfree(private_blob);
+ if (errorstr)
+ *errorstr = error;
+ return ret;
+}
+
+unsigned char *ssh2_userkey_loadpub(const Filename *filename, char **algorithm,
+ int *pub_blob_len, char **commentptr,
+ const char **errorstr)
+{
+ FILE *fp;
+ char header[40], *b;
+ const struct ssh_signkey *alg;
+ unsigned char *public_blob;
+ int public_blob_len;
+ int i;
+ const char *error = NULL;
+ char *comment;
+
+ public_blob = NULL;
+
+ 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";
+ goto error;
+ }
+ error = "file format error";
+ if ((b = read_body(fp)) == NULL)
+ goto error;
+ /* Select key algorithm structure. */
+ alg = find_pubkey_alg(b);
+ if (!alg) {
+ sfree(b);
+ goto error;
+ }
+ sfree(b);
+
+ /* Read the Encryption header line. */
+ if (!read_header(fp, header) || 0 != strcmp(header, "Encryption"))
+ goto error;
+ if ((b = read_body(fp)) == NULL)
+ goto error;
+ sfree(b); /* we don't care */
+
+ /* Read the Comment header line. */
+ if (!read_header(fp, header) || 0 != strcmp(header, "Comment"))
+ goto error;
+ if ((comment = read_body(fp)) == NULL)
+ goto error;
+
+ if (commentptr)
+ *commentptr = comment;
+ else
+ sfree(comment);
+
+ /* Read the Public-Lines header line and the public blob. */
+ if (!read_header(fp, header) || 0 != strcmp(header, "Public-Lines"))
+ goto error;
+ if ((b = read_body(fp)) == NULL)
+ goto error;
+ i = atoi(b);
+ sfree(b);
+ if ((public_blob = read_blob(fp, i, &public_blob_len)) == NULL)
+ goto error;
+
+ fclose(fp);
+ if (pub_blob_len)
+ *pub_blob_len = public_blob_len;
+ if (algorithm)
+ *algorithm = alg->name;
+ return public_blob;
+
+ /*
+ * Error processing.
+ */
+ error:
+ if (fp)
+ fclose(fp);
+ if (public_blob)
+ sfree(public_blob);
+ if (errorstr)
+ *errorstr = error;
+ return NULL;
+}
+
+int ssh2_userkey_encrypted(const Filename *filename, char **commentptr)
+{
+ FILE *fp;
+ char header[40], *b, *comment;
+ int ret;
+
+ if (commentptr)
+ *commentptr = NULL;
+
+ 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"))) {
+ fclose(fp);
+ return 0;
+ }
+ if ((b = read_body(fp)) == NULL) {
+ fclose(fp);
+ return 0;
+ }
+ sfree(b); /* we don't care about key type here */
+ /* Read the Encryption header line. */
+ if (!read_header(fp, header) || 0 != strcmp(header, "Encryption")) {
+ fclose(fp);
+ return 0;
+ }
+ if ((b = read_body(fp)) == NULL) {
+ fclose(fp);
+ return 0;
+ }
+
+ /* Read the Comment header line. */
+ if (!read_header(fp, header) || 0 != strcmp(header, "Comment")) {
+ fclose(fp);
+ sfree(b);
+ return 1;
+ }
+ if ((comment = read_body(fp)) == NULL) {
+ fclose(fp);
+ sfree(b);
+ return 1;
+ }
+
+ if (commentptr)
+ *commentptr = comment;
+
+ fclose(fp);
+ if (!strcmp(b, "aes256-cbc"))
+ ret = 1;
+ else
+ ret = 0;
+ sfree(b);
+ return ret;
+}
+
+int base64_lines(int datalen)
+{
+ /* When encoding, we use 64 chars/line, which equals 48 real chars. */
+ return (datalen + 47) / 48;
+}
+
+void base64_encode(FILE * fp, unsigned char *data, int datalen, int cpl)
+{
+ int linelen = 0;
+ char out[4];
+ int n, i;
+
+ while (datalen > 0) {
+ n = (datalen < 3 ? datalen : 3);
+ base64_encode_atom(data, n, out);
+ data += n;
+ datalen -= n;
+ for (i = 0; i < 4; i++) {
+ if (linelen >= cpl) {
+ linelen = 0;
+ fputc('\n', fp);
+ }
+ fputc(out[i], fp);
+ linelen++;
+ }
+ }
+ fputc('\n', fp);
+}
+
+int ssh2_save_userkey(const Filename *filename, struct ssh2_userkey *key,
+ char *passphrase)
+{
+ FILE *fp;
+ unsigned char *pub_blob, *priv_blob, *priv_blob_encrypted;
+ int pub_blob_len, priv_blob_len, priv_encrypted_len;
+ int passlen;
+ int cipherblk;
+ int i;
+ char *cipherstr;
+ unsigned char priv_mac[20];
+
+ /*
+ * Fetch the key component blobs.
+ */
+ pub_blob = key->alg->public_blob(key->data, &pub_blob_len);
+ priv_blob = key->alg->private_blob(key->data, &priv_blob_len);
+ if (!pub_blob || !priv_blob) {
+ sfree(pub_blob);
+ sfree(priv_blob);
+ return 0;
+ }
+
+ /*
+ * Determine encryption details, and encrypt the private blob.
+ */
+ if (passphrase) {
+ cipherstr = "aes256-cbc";
+ cipherblk = 16;
+ } else {
+ cipherstr = "none";
+ cipherblk = 1;
+ }
+ priv_encrypted_len = priv_blob_len + cipherblk - 1;
+ priv_encrypted_len -= priv_encrypted_len % cipherblk;
+ priv_blob_encrypted = snewn(priv_encrypted_len, unsigned char);
+ memset(priv_blob_encrypted, 0, priv_encrypted_len);
+ memcpy(priv_blob_encrypted, priv_blob, priv_blob_len);
+ /* Create padding based on the SHA hash of the unpadded blob. This prevents
+ * too easy a known-plaintext attack on the last block. */
+ SHA_Simple(priv_blob, priv_blob_len, priv_mac);
+ assert(priv_encrypted_len - priv_blob_len < 20);
+ memcpy(priv_blob_encrypted + priv_blob_len, priv_mac,
+ priv_encrypted_len - priv_blob_len);
+
+ /* Now create the MAC. */
+ {
+ unsigned char *macdata;
+ int maclen;
+ unsigned char *p;
+ int namelen = strlen(key->alg->name);
+ int enclen = strlen(cipherstr);
+ int commlen = strlen(key->comment);
+ SHA_State s;
+ unsigned char mackey[20];
+ char header[] = "putty-private-key-file-mac-key";
+
+ maclen = (4 + namelen +
+ 4 + enclen +
+ 4 + commlen +
+ 4 + pub_blob_len +
+ 4 + priv_encrypted_len);
+ macdata = snewn(maclen, unsigned char);
+ p = macdata;
+#define DO_STR(s,len) PUT_32BIT(p,(len));memcpy(p+4,(s),(len));p+=4+(len)
+ DO_STR(key->alg->name, namelen);
+ DO_STR(cipherstr, enclen);
+ DO_STR(key->comment, commlen);
+ DO_STR(pub_blob, pub_blob_len);
+ DO_STR(priv_blob_encrypted, priv_encrypted_len);
+
+ SHA_Init(&s);
+ 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);
+ sfree(macdata);
+ memset(mackey, 0, sizeof(mackey));
+ memset(&s, 0, sizeof(s));
+ }
+
+ if (passphrase) {
+ unsigned char key[40];
+ SHA_State s;
+
+ passlen = strlen(passphrase);
+
+ SHA_Init(&s);
+ SHA_Bytes(&s, "\0\0\0\0", 4);
+ SHA_Bytes(&s, passphrase, passlen);
+ SHA_Final(&s, key + 0);
+ SHA_Init(&s);
+ SHA_Bytes(&s, "\0\0\0\1", 4);
+ 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));
+ }
+
+ 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);
+ fprintf(fp, "Public-Lines: %d\n", base64_lines(pub_blob_len));
+ base64_encode(fp, pub_blob, pub_blob_len, 64);
+ fprintf(fp, "Private-Lines: %d\n", base64_lines(priv_encrypted_len));
+ base64_encode(fp, priv_blob_encrypted, priv_encrypted_len, 64);
+ fprintf(fp, "Private-MAC: ");
+ for (i = 0; i < 20; i++)
+ fprintf(fp, "%02x", priv_mac[i]);
+ fprintf(fp, "\n");
+ fclose(fp);
+
+ sfree(pub_blob);
+ memset(priv_blob, 0, priv_blob_len);
+ sfree(priv_blob);
+ sfree(priv_blob_encrypted);
+ return 1;
+}
+
+/* ----------------------------------------------------------------------
+ * A function to determine the type of a private key file. Returns
+ * 0 on failure, 1 or 2 on success.
+ */
+int key_type(const Filename *filename)
+{
+ FILE *fp;
+ char buf[32];
+ 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);
+ if (!fp)
+ return SSH_KEYTYPE_UNOPENABLE;
+ i = fread(buf, 1, sizeof(buf), fp);
+ fclose(fp);
+ if (i < 0)
+ return SSH_KEYTYPE_UNOPENABLE;
+ if (i < 32)
+ return SSH_KEYTYPE_UNKNOWN;
+ if (!memcmp(buf, rsa_signature, sizeof(rsa_signature)-1))
+ return SSH_KEYTYPE_SSH1;
+ if (!memcmp(buf, putty2_sig, sizeof(putty2_sig)-1))
+ return SSH_KEYTYPE_SSH2;
+ if (!memcmp(buf, openssh_sig, sizeof(openssh_sig)-1))
+ return SSH_KEYTYPE_OPENSSH;
+ if (!memcmp(buf, sshcom_sig, sizeof(sshcom_sig)-1))
+ return SSH_KEYTYPE_SSHCOM;
+ return SSH_KEYTYPE_UNKNOWN; /* unrecognised or EOF */
+}
+
+/*
+ * Convert the type word to a string, for `wrong type' error
+ * messages.
+ */
+char *key_type_to_str(int type)
+{
+ switch (type) {
+ case SSH_KEYTYPE_UNOPENABLE: return "unable to open file"; break;
+ case SSH_KEYTYPE_UNKNOWN: return "not a private key"; break;
+ case SSH_KEYTYPE_SSH1: return "SSH-1 private key"; break;
+ case SSH_KEYTYPE_SSH2: return "PuTTY SSH-2 private key"; break;
+ case SSH_KEYTYPE_OPENSSH: return "OpenSSH SSH-2 private key"; break;
+ case SSH_KEYTYPE_SSHCOM: return "ssh.com SSH-2 private key"; break;
+ default: return "INTERNAL ERROR"; break;
+ }
+}
--- /dev/null
+/*
+ * cryptographic random number generator for PuTTY's ssh client
+ */
+
+#include "putty.h"
+#include "ssh.h"
+#include <assert.h>
+
+/* Collect environmental noise every 5 minutes */
+#define NOISE_REGULAR_INTERVAL (5*60*TICKSPERSEC)
+
+void noise_get_heavy(void (*func) (void *, int));
+void noise_get_light(void (*func) (void *, int));
+
+/*
+ * `pool' itself is a pool of random data which we actually use: we
+ * return bytes from `pool', at position `poolpos', until `poolpos'
+ * reaches the end of the pool. At this point we generate more
+ * random data, by adding noise, stirring well, and resetting
+ * `poolpos' to point to just past the beginning of the pool (not
+ * _the_ beginning, since otherwise we'd give away the whole
+ * contents of our pool, and attackers would just have to guess the
+ * next lot of noise).
+ *
+ * `incomingb' buffers acquired noise data, until it gets full, at
+ * which point the acquired noise is SHA'ed into `incoming' and
+ * `incomingb' is cleared. The noise in `incoming' is used as part
+ * of the noise for each stirring of the pool, in addition to local
+ * time, process listings, and other such stuff.
+ */
+
+#define HASHINPUT 64 /* 64 bytes SHA input */
+#define HASHSIZE 20 /* 160 bits SHA output */
+#define POOLSIZE 1200 /* size of random pool */
+
+struct RandPool {
+ unsigned char pool[POOLSIZE];
+ int poolpos;
+
+ unsigned char incoming[HASHSIZE];
+
+ unsigned char incomingb[HASHINPUT];
+ int incomingpos;
+
+ int stir_pending;
+};
+
+static struct RandPool pool;
+int random_active = 0;
+long next_noise_collection;
+
+static void random_stir(void)
+{
+ word32 block[HASHINPUT / sizeof(word32)];
+ word32 digest[HASHSIZE / sizeof(word32)];
+ int i, j, k;
+
+ /*
+ * noise_get_light will call random_add_noise, which may call
+ * back to here. Prevent recursive stirs.
+ */
+ if (pool.stir_pending)
+ return;
+ pool.stir_pending = TRUE;
+
+ noise_get_light(random_add_noise);
+
+ SHATransform((word32 *) pool.incoming, (word32 *) pool.incomingb);
+ pool.incomingpos = 0;
+
+ /*
+ * Chunks of this code are blatantly endianness-dependent, but
+ * as it's all random bits anyway, WHO CARES?
+ */
+ memcpy(digest, pool.incoming, sizeof(digest));
+
+ /*
+ * Make two passes over the pool.
+ */
+ for (i = 0; i < 2; i++) {
+
+ /*
+ * We operate SHA in CFB mode, repeatedly adding the same
+ * block of data to the digest. But we're also fiddling
+ * with the digest-so-far, so this shouldn't be Bad or
+ * anything.
+ */
+ memcpy(block, pool.pool, sizeof(block));
+
+ /*
+ * Each pass processes the pool backwards in blocks of
+ * HASHSIZE, just so that in general we get the output of
+ * SHA before the corresponding input, in the hope that
+ * things will be that much less predictable that way
+ * round, when we subsequently return bytes ...
+ */
+ for (j = POOLSIZE; (j -= HASHSIZE) >= 0;) {
+ /*
+ * XOR the bit of the pool we're processing into the
+ * digest.
+ */
+
+ for (k = 0; k < sizeof(digest) / sizeof(*digest); k++)
+ digest[k] ^= ((word32 *) (pool.pool + j))[k];
+
+ /*
+ * Munge our unrevealed first block of the pool into
+ * it.
+ */
+ SHATransform(digest, block);
+
+ /*
+ * Stick the result back into the pool.
+ */
+
+ for (k = 0; k < sizeof(digest) / sizeof(*digest); k++)
+ ((word32 *) (pool.pool + j))[k] = digest[k];
+ }
+ }
+
+ /*
+ * Might as well save this value back into `incoming', just so
+ * there'll be some extra bizarreness there.
+ */
+ SHATransform(digest, block);
+ memcpy(pool.incoming, digest, sizeof(digest));
+
+ pool.poolpos = sizeof(pool.incoming);
+
+ pool.stir_pending = FALSE;
+}
+
+void random_add_noise(void *noise, int length)
+{
+ unsigned char *p = noise;
+ int i;
+
+ if (!random_active)
+ return;
+
+ /*
+ * This function processes HASHINPUT bytes into only HASHSIZE
+ * bytes, so _if_ we were getting incredibly high entropy
+ * sources then we would be throwing away valuable stuff.
+ */
+ while (length >= (HASHINPUT - pool.incomingpos)) {
+ memcpy(pool.incomingb + pool.incomingpos, p,
+ HASHINPUT - pool.incomingpos);
+ p += HASHINPUT - pool.incomingpos;
+ length -= HASHINPUT - pool.incomingpos;
+ SHATransform((word32 *) pool.incoming, (word32 *) pool.incomingb);
+ for (i = 0; i < HASHSIZE; i++) {
+ pool.pool[pool.poolpos++] ^= pool.incomingb[i];
+ if (pool.poolpos >= POOLSIZE)
+ pool.poolpos = 0;
+ }
+ if (pool.poolpos < HASHSIZE)
+ random_stir();
+
+ pool.incomingpos = 0;
+ }
+
+ memcpy(pool.incomingb + pool.incomingpos, p, length);
+ pool.incomingpos += length;
+}
+
+void random_add_heavynoise(void *noise, int length)
+{
+ unsigned char *p = noise;
+ int i;
+
+ while (length >= POOLSIZE) {
+ for (i = 0; i < POOLSIZE; i++)
+ pool.pool[i] ^= *p++;
+ random_stir();
+ length -= POOLSIZE;
+ }
+
+ for (i = 0; i < length; i++)
+ pool.pool[i] ^= *p++;
+ random_stir();
+}
+
+static void random_add_heavynoise_bitbybit(void *noise, int length)
+{
+ unsigned char *p = noise;
+ int i;
+
+ while (length >= POOLSIZE - pool.poolpos) {
+ for (i = 0; i < POOLSIZE - pool.poolpos; i++)
+ pool.pool[pool.poolpos + i] ^= *p++;
+ random_stir();
+ length -= POOLSIZE - pool.poolpos;
+ pool.poolpos = 0;
+ }
+
+ for (i = 0; i < length; i++)
+ pool.pool[i] ^= *p++;
+ pool.poolpos = i;
+}
+
+static void random_timer(void *ctx, long now)
+{
+ if (random_active > 0 && now - next_noise_collection >= 0) {
+ noise_regular();
+ next_noise_collection =
+ schedule_timer(NOISE_REGULAR_INTERVAL, random_timer, &pool);
+ }
+}
+
+void random_ref(void)
+{
+ if (!random_active) {
+ memset(&pool, 0, sizeof(pool)); /* just to start with */
+
+ noise_get_heavy(random_add_heavynoise_bitbybit);
+ random_stir();
+
+ next_noise_collection =
+ schedule_timer(NOISE_REGULAR_INTERVAL, random_timer, &pool);
+ }
+
+ random_active++;
+}
+
+void random_unref(void)
+{
+ random_active--;
+ assert(random_active >= 0);
+ if (random_active) return;
+
+ expire_timer_context(&pool);
+}
+
+int random_byte(void)
+{
+ if (pool.poolpos >= POOLSIZE)
+ random_stir();
+
+ return pool.pool[pool.poolpos++];
+}
+
+void random_get_savedata(void **data, int *len)
+{
+ void *buf = snewn(POOLSIZE / 2, char);
+ random_stir();
+ memcpy(buf, pool.pool + pool.poolpos, POOLSIZE / 2);
+ *len = POOLSIZE / 2;
+ *data = buf;
+ random_stir();
+}
--- /dev/null
+/*
+ * RSA implementation for PuTTY.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+#include "ssh.h"
+#include "misc.h"
+
+int makekey(unsigned char *data, int len, struct RSAKey *result,
+ unsigned char **keystr, int order)
+{
+ unsigned char *p = data;
+ int i, n;
+
+ if (len < 4)
+ return -1;
+
+ if (result) {
+ result->bits = 0;
+ for (i = 0; i < 4; i++)
+ result->bits = (result->bits << 8) + *p++;
+ } else
+ p += 4;
+
+ len -= 4;
+
+ /*
+ * order=0 means exponent then modulus (the keys sent by the
+ * server). order=1 means modulus then exponent (the keys
+ * stored in a keyfile).
+ */
+
+ if (order == 0) {
+ n = ssh1_read_bignum(p, len, result ? &result->exponent : NULL);
+ if (n < 0) return -1;
+ p += n;
+ len -= n;
+ }
+
+ n = ssh1_read_bignum(p, len, result ? &result->modulus : NULL);
+ if (n < 0 || (result && bignum_bitcount(result->modulus) == 0)) return -1;
+ if (result)
+ result->bytes = n - 2;
+ if (keystr)
+ *keystr = p + 2;
+ p += n;
+ len -= n;
+
+ if (order == 1) {
+ n = ssh1_read_bignum(p, len, result ? &result->exponent : NULL);
+ if (n < 0) return -1;
+ p += n;
+ len -= n;
+ }
+ return p - data;
+}
+
+int makeprivate(unsigned char *data, int len, struct RSAKey *result)
+{
+ return ssh1_read_bignum(data, len, &result->private_exponent);
+}
+
+int rsaencrypt(unsigned char *data, int length, struct RSAKey *key)
+{
+ Bignum b1, b2;
+ int i;
+ unsigned char *p;
+
+ if (key->bytes < length + 4)
+ return 0; /* RSA key too short! */
+
+ memmove(data + key->bytes - length, data, length);
+ data[0] = 0;
+ data[1] = 2;
+
+ for (i = 2; i < key->bytes - length - 1; i++) {
+ do {
+ data[i] = random_byte();
+ } while (data[i] == 0);
+ }
+ data[key->bytes - length - 1] = 0;
+
+ b1 = bignum_from_bytes(data, key->bytes);
+
+ b2 = modpow(b1, key->exponent, key->modulus);
+
+ p = data;
+ for (i = key->bytes; i--;) {
+ *p++ = bignum_byte(b2, i);
+ }
+
+ freebn(b1);
+ freebn(b2);
+
+ return 1;
+}
+
+static void sha512_mpint(SHA512_State * s, Bignum b)
+{
+ unsigned char lenbuf[4];
+ int len;
+ len = (bignum_bitcount(b) + 8) / 8;
+ PUT_32BIT(lenbuf, len);
+ SHA512_Bytes(s, lenbuf, 4);
+ while (len-- > 0) {
+ lenbuf[0] = bignum_byte(b, len);
+ SHA512_Bytes(s, lenbuf, 1);
+ }
+ memset(lenbuf, 0, 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.
+ * Uses Chinese Remainder Theorem to speed computation up over the
+ * obvious implementation of a single big modpow.
+ */
+Bignum crt_modpow(Bignum base, Bignum exp, Bignum mod,
+ Bignum p, Bignum q, Bignum iqmp)
+{
+ Bignum pm1, qm1, pexp, qexp, presult, qresult, diff, multiplier, ret0, ret;
+
+ /*
+ * Reduce the exponent mod phi(p) and phi(q), to save time when
+ * exponentiating mod p and mod q respectively. Of course, since p
+ * and q are prime, phi(p) == p-1 and similarly for q.
+ */
+ pm1 = copybn(p);
+ decbn(pm1);
+ qm1 = copybn(q);
+ decbn(qm1);
+ pexp = bigmod(exp, pm1);
+ qexp = bigmod(exp, qm1);
+
+ /*
+ * Do the two modpows.
+ */
+ presult = modpow(base, pexp, p);
+ qresult = modpow(base, qexp, q);
+
+ /*
+ * Recombine the results. We want a value which is congruent to
+ * qresult mod q, and to presult mod p.
+ *
+ * We know that iqmp * q is congruent to 1 * mod p (by definition
+ * of iqmp) and to 0 mod q (obviously). So we start with qresult
+ * (which is congruent to qresult mod both primes), and add on
+ * (presult-qresult) * (iqmp * q) which adjusts it to be congruent
+ * to presult mod p without affecting its value mod q.
+ */
+ if (bignum_cmp(presult, qresult) < 0) {
+ /*
+ * Can't subtract presult from qresult without first adding on
+ * p.
+ */
+ Bignum tmp = presult;
+ presult = bigadd(presult, p);
+ freebn(tmp);
+ }
+ diff = bigsub(presult, qresult);
+ multiplier = bigmul(iqmp, q);
+ ret0 = bigmuladd(multiplier, diff, qresult);
+
+ /*
+ * Finally, reduce the result mod n.
+ */
+ ret = bigmod(ret0, mod);
+
+ /*
+ * Free all the intermediate results before returning.
+ */
+ freebn(pm1);
+ freebn(qm1);
+ freebn(pexp);
+ freebn(qexp);
+ freebn(presult);
+ freebn(qresult);
+ freebn(diff);
+ freebn(multiplier);
+ freebn(ret0);
+
+ return ret;
+}
+
+/*
+ * This function is a wrapper on modpow(). It has the same effect as
+ * modpow(), but employs RSA blinding to protect against timing
+ * attacks and also uses the Chinese Remainder Theorem (implemented
+ * above, in crt_modpow()) to speed up the main operation.
+ */
+static Bignum rsa_privkey_op(Bignum input, struct RSAKey *key)
+{
+ Bignum random, random_encrypted, random_inverse;
+ Bignum input_blinded, ret_blinded;
+ Bignum ret;
+
+ SHA512_State ss;
+ unsigned char digest512[64];
+ int digestused = lenof(digest512);
+ int hashseq = 0;
+
+ /*
+ * Start by inventing a random number chosen uniformly from the
+ * range 2..modulus-1. (We do this by preparing a random number
+ * of the right length and retrying if it's greater than the
+ * modulus, to prevent any potential Bleichenbacher-like
+ * attacks making use of the uneven distribution within the
+ * range that would arise from just reducing our number mod n.
+ * There are timing implications to the potential retries, of
+ * course, but all they tell you is the modulus, which you
+ * already knew.)
+ *
+ * To preserve determinism and avoid Pageant needing to share
+ * the random number pool, we actually generate this `random'
+ * number by hashing stuff with the private key.
+ */
+ while (1) {
+ int bits, byte, bitsleft, v;
+ random = copybn(key->modulus);
+ /*
+ * Find the topmost set bit. (This function will return its
+ * index plus one.) Then we'll set all bits from that one
+ * downwards randomly.
+ */
+ bits = bignum_bitcount(random);
+ byte = 0;
+ bitsleft = 0;
+ while (bits--) {
+ if (bitsleft <= 0) {
+ bitsleft = 8;
+ /*
+ * Conceptually the following few lines are equivalent to
+ * byte = random_byte();
+ */
+ if (digestused >= lenof(digest512)) {
+ unsigned char seqbuf[4];
+ PUT_32BIT(seqbuf, hashseq);
+ SHA512_Init(&ss);
+ SHA512_Bytes(&ss, "RSA deterministic blinding", 26);
+ SHA512_Bytes(&ss, seqbuf, sizeof(seqbuf));
+ sha512_mpint(&ss, key->private_exponent);
+ SHA512_Final(&ss, digest512);
+ hashseq++;
+
+ /*
+ * Now hash that digest plus the signature
+ * input.
+ */
+ SHA512_Init(&ss);
+ SHA512_Bytes(&ss, digest512, sizeof(digest512));
+ sha512_mpint(&ss, input);
+ SHA512_Final(&ss, digest512);
+
+ digestused = 0;
+ }
+ byte = digest512[digestused++];
+ }
+ v = byte & 1;
+ byte >>= 1;
+ bitsleft--;
+ bignum_set_bit(random, bits, v);
+ }
+
+ /*
+ * Now check that this number is strictly greater than
+ * zero, and strictly less than modulus.
+ */
+ if (bignum_cmp(random, Zero) <= 0 ||
+ bignum_cmp(random, key->modulus) >= 0) {
+ freebn(random);
+ continue;
+ } else {
+ 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
+ * y and y^d; then we multiply x by y, raise to the power d mod
+ * n as usual, and divide by y^d to recover x^d. Thus an
+ * attacker can't correlate the timing of the modpow with the
+ * input, because they don't know anything about the number
+ * that was input to the actual modpow.
+ *
+ * The clever bit is that we don't have to do a huge modpow to
+ * get y and y^d; we will use the number we just invented as
+ * _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);
+
+ freebn(ret_blinded);
+ freebn(input_blinded);
+ freebn(random_inverse);
+ freebn(random_encrypted);
+ freebn(random);
+
+ return ret;
+}
+
+Bignum rsadecrypt(Bignum input, struct RSAKey *key)
+{
+ return rsa_privkey_op(input, key);
+}
+
+int rsastr_len(struct RSAKey *key)
+{
+ Bignum md, ex;
+ int mdlen, exlen;
+
+ md = key->modulus;
+ ex = key->exponent;
+ mdlen = (bignum_bitcount(md) + 15) / 16;
+ exlen = (bignum_bitcount(ex) + 15) / 16;
+ return 4 * (mdlen + exlen) + 20;
+}
+
+void rsastr_fmt(char *str, struct RSAKey *key)
+{
+ Bignum md, ex;
+ int len = 0, i, nibbles;
+ static const char hex[] = "0123456789abcdef";
+
+ md = key->modulus;
+ ex = key->exponent;
+
+ len += sprintf(str + len, "0x");
+
+ nibbles = (3 + bignum_bitcount(ex)) / 4;
+ if (nibbles < 1)
+ nibbles = 1;
+ for (i = nibbles; i--;)
+ str[len++] = hex[(bignum_byte(ex, i / 2) >> (4 * (i % 2))) & 0xF];
+
+ len += sprintf(str + len, ",0x");
+
+ nibbles = (3 + bignum_bitcount(md)) / 4;
+ if (nibbles < 1)
+ nibbles = 1;
+ for (i = nibbles; i--;)
+ str[len++] = hex[(bignum_byte(md, i / 2) >> (4 * (i % 2))) & 0xF];
+
+ str[len] = '\0';
+}
+
+/*
+ * Generate a fingerprint string for the key. Compatible with the
+ * OpenSSH fingerprint code.
+ */
+void rsa_fingerprint(char *str, int len, struct RSAKey *key)
+{
+ struct MD5Context md5c;
+ unsigned char digest[16];
+ char buffer[16 * 3 + 40];
+ int numlen, slen, i;
+
+ MD5Init(&md5c);
+ numlen = ssh1_bignum_length(key->modulus) - 2;
+ for (i = numlen; i--;) {
+ unsigned char c = bignum_byte(key->modulus, i);
+ MD5Update(&md5c, &c, 1);
+ }
+ numlen = ssh1_bignum_length(key->exponent) - 2;
+ for (i = numlen; i--;) {
+ unsigned char c = bignum_byte(key->exponent, i);
+ MD5Update(&md5c, &c, 1);
+ }
+ MD5Final(digest, &md5c);
+
+ sprintf(buffer, "%d ", bignum_bitcount(key->modulus));
+ for (i = 0; i < 16; i++)
+ sprintf(buffer + strlen(buffer), "%s%02x", i ? ":" : "",
+ digest[i]);
+ strncpy(str, buffer, len);
+ str[len - 1] = '\0';
+ slen = strlen(str);
+ if (key->comment && slen < len - 1) {
+ str[slen] = ' ';
+ strncpy(str + slen + 1, key->comment, len - slen - 1);
+ str[len - 1] = '\0';
+ }
+}
+
+/*
+ * Verify that the public data in an RSA key matches the private
+ * data. We also check the private data itself: we ensure that p >
+ * q and that iqmp really is the inverse of q mod p.
+ */
+int rsa_verify(struct RSAKey *key)
+{
+ Bignum n, ed, pm1, qm1;
+ int cmp;
+
+ /* n must equal pq. */
+ n = bigmul(key->p, key->q);
+ cmp = bignum_cmp(n, key->modulus);
+ freebn(n);
+ if (cmp != 0)
+ return 0;
+
+ /* 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);
+ cmp = bignum_cmp(ed, One);
+ sfree(ed);
+ if (cmp != 0)
+ return 0;
+
+ qm1 = copybn(key->q);
+ decbn(qm1);
+ ed = modmul(key->exponent, key->private_exponent, qm1);
+ cmp = bignum_cmp(ed, One);
+ sfree(ed);
+ if (cmp != 0)
+ return 0;
+
+ /*
+ * Ensure p > q.
+ *
+ * I have seen key blobs in the wild which were generated with
+ * p < q, so instead of rejecting the key in this case we
+ * should instead flip them round into the canonical order of
+ * p > q. This also involves regenerating iqmp.
+ */
+ if (bignum_cmp(key->p, key->q) <= 0) {
+ Bignum tmp = key->p;
+ key->p = key->q;
+ key->q = tmp;
+
+ freebn(key->iqmp);
+ key->iqmp = modinv(key->q, key->p);
+ }
+
+ /*
+ * Ensure iqmp * q is congruent to 1, modulo p.
+ */
+ n = modmul(key->iqmp, key->q, key->p);
+ cmp = bignum_cmp(n, One);
+ sfree(n);
+ if (cmp != 0)
+ return 0;
+
+ return 1;
+}
+
+/* Public key blob as used by Pageant: exponent before modulus. */
+unsigned char *rsa_public_blob(struct RSAKey *key, int *len)
+{
+ int length, pos;
+ unsigned char *ret;
+
+ length = (ssh1_bignum_length(key->modulus) +
+ ssh1_bignum_length(key->exponent) + 4);
+ ret = snewn(length, unsigned char);
+
+ PUT_32BIT(ret, bignum_bitcount(key->modulus));
+ pos = 4;
+ pos += ssh1_write_bignum(ret + pos, key->exponent);
+ pos += ssh1_write_bignum(ret + pos, key->modulus);
+
+ *len = length;
+ return ret;
+}
+
+/* Given a public blob, determine its length. */
+int rsa_public_blob_len(void *data, int maxlen)
+{
+ unsigned char *p = (unsigned char *)data;
+ int n;
+
+ if (maxlen < 4)
+ return -1;
+ p += 4; /* length word */
+ maxlen -= 4;
+
+ n = ssh1_read_bignum(p, maxlen, NULL); /* exponent */
+ if (n < 0)
+ return -1;
+ p += n;
+
+ n = ssh1_read_bignum(p, maxlen, NULL); /* modulus */
+ if (n < 0)
+ return -1;
+ p += n;
+
+ return p - (unsigned char *)data;
+}
+
+void freersakey(struct RSAKey *key)
+{
+ if (key->modulus)
+ freebn(key->modulus);
+ if (key->exponent)
+ freebn(key->exponent);
+ if (key->private_exponent)
+ freebn(key->private_exponent);
+ if (key->p)
+ freebn(key->p);
+ if (key->q)
+ freebn(key->q);
+ if (key->iqmp)
+ freebn(key->iqmp);
+ if (key->comment)
+ sfree(key->comment);
+}
+
+/* ----------------------------------------------------------------------
+ * Implementation of the ssh-rsa signing key type.
+ */
+
+static void getstring(char **data, int *datalen, char **p, int *length)
+{
+ *p = NULL;
+ if (*datalen < 4)
+ return;
+ *length = GET_32BIT(*data);
+ *datalen -= 4;
+ *data += 4;
+ if (*datalen < *length)
+ return;
+ *p = *data;
+ *data += *length;
+ *datalen -= *length;
+}
+static Bignum getmp(char **data, int *datalen)
+{
+ char *p;
+ int length;
+ Bignum b;
+
+ getstring(data, datalen, &p, &length);
+ if (!p)
+ return NULL;
+ b = bignum_from_bytes((unsigned char *)p, length);
+ return b;
+}
+
+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;
+ }
+ rsa->exponent = getmp(&data, &len);
+ rsa->modulus = getmp(&data, &len);
+ rsa->private_exponent = NULL;
+ rsa->p = rsa->q = rsa->iqmp = NULL;
+ rsa->comment = NULL;
+
+ return rsa;
+}
+
+static void rsa2_freekey(void *key)
+{
+ struct RSAKey *rsa = (struct RSAKey *) key;
+ freersakey(rsa);
+ sfree(rsa);
+}
+
+static char *rsa2_fmtkey(void *key)
+{
+ struct RSAKey *rsa = (struct RSAKey *) key;
+ char *p;
+ int len;
+
+ len = rsastr_len(rsa);
+ p = snewn(len, char);
+ rsastr_fmt(p, rsa);
+ return p;
+}
+
+static unsigned char *rsa2_public_blob(void *key, int *len)
+{
+ struct RSAKey *rsa = (struct RSAKey *) key;
+ int elen, mlen, bloblen;
+ int i;
+ unsigned char *blob, *p;
+
+ elen = (bignum_bitcount(rsa->exponent) + 8) / 8;
+ mlen = (bignum_bitcount(rsa->modulus) + 8) / 8;
+
+ /*
+ * string "ssh-rsa", mpint exp, mpint mod. Total 19+elen+mlen.
+ * (three length fields, 12+7=19).
+ */
+ bloblen = 19 + elen + mlen;
+ blob = snewn(bloblen, unsigned char);
+ p = blob;
+ PUT_32BIT(p, 7);
+ p += 4;
+ memcpy(p, "ssh-rsa", 7);
+ p += 7;
+ PUT_32BIT(p, elen);
+ p += 4;
+ for (i = elen; i--;)
+ *p++ = bignum_byte(rsa->exponent, i);
+ PUT_32BIT(p, mlen);
+ p += 4;
+ for (i = mlen; i--;)
+ *p++ = bignum_byte(rsa->modulus, i);
+ assert(p == blob + bloblen);
+ *len = bloblen;
+ return blob;
+}
+
+static unsigned char *rsa2_private_blob(void *key, int *len)
+{
+ struct RSAKey *rsa = (struct RSAKey *) key;
+ int dlen, plen, qlen, ulen, bloblen;
+ int i;
+ unsigned char *blob, *p;
+
+ dlen = (bignum_bitcount(rsa->private_exponent) + 8) / 8;
+ plen = (bignum_bitcount(rsa->p) + 8) / 8;
+ qlen = (bignum_bitcount(rsa->q) + 8) / 8;
+ ulen = (bignum_bitcount(rsa->iqmp) + 8) / 8;
+
+ /*
+ * mpint private_exp, mpint p, mpint q, mpint iqmp. Total 16 +
+ * sum of lengths.
+ */
+ bloblen = 16 + dlen + plen + qlen + ulen;
+ blob = snewn(bloblen, unsigned char);
+ p = blob;
+ PUT_32BIT(p, dlen);
+ p += 4;
+ for (i = dlen; i--;)
+ *p++ = bignum_byte(rsa->private_exponent, i);
+ PUT_32BIT(p, plen);
+ p += 4;
+ for (i = plen; i--;)
+ *p++ = bignum_byte(rsa->p, i);
+ PUT_32BIT(p, qlen);
+ p += 4;
+ for (i = qlen; i--;)
+ *p++ = bignum_byte(rsa->q, i);
+ PUT_32BIT(p, ulen);
+ p += 4;
+ for (i = ulen; i--;)
+ *p++ = bignum_byte(rsa->iqmp, i);
+ assert(p == blob + bloblen);
+ *len = bloblen;
+ return blob;
+}
+
+static void *rsa2_createkey(unsigned char *pub_blob, int pub_len,
+ unsigned char *priv_blob, int priv_len)
+{
+ struct RSAKey *rsa;
+ char *pb = (char *) priv_blob;
+
+ rsa = rsa2_newkey((char *) pub_blob, pub_len);
+ rsa->private_exponent = getmp(&pb, &priv_len);
+ rsa->p = getmp(&pb, &priv_len);
+ rsa->q = getmp(&pb, &priv_len);
+ rsa->iqmp = getmp(&pb, &priv_len);
+
+ if (!rsa_verify(rsa)) {
+ rsa2_freekey(rsa);
+ return NULL;
+ }
+
+ return rsa;
+}
+
+static void *rsa2_openssh_createkey(unsigned char **blob, int *len)
+{
+ 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);
+ rsa->iqmp = getmp(b, len);
+ 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);
+ return NULL;
+ }
+
+ return rsa;
+}
+
+static int rsa2_openssh_fmtkey(void *key, unsigned char *blob, int len)
+{
+ struct RSAKey *rsa = (struct RSAKey *) key;
+ int bloblen, i;
+
+ bloblen =
+ ssh2_bignum_length(rsa->modulus) +
+ ssh2_bignum_length(rsa->exponent) +
+ ssh2_bignum_length(rsa->private_exponent) +
+ ssh2_bignum_length(rsa->iqmp) +
+ ssh2_bignum_length(rsa->p) + ssh2_bignum_length(rsa->q);
+
+ if (bloblen > len)
+ return bloblen;
+
+ bloblen = 0;
+#define ENC(x) \
+ PUT_32BIT(blob+bloblen, ssh2_bignum_length((x))-4); bloblen += 4; \
+ for (i = ssh2_bignum_length((x))-4; i-- ;) blob[bloblen++]=bignum_byte((x),i);
+ ENC(rsa->modulus);
+ ENC(rsa->exponent);
+ ENC(rsa->private_exponent);
+ ENC(rsa->iqmp);
+ ENC(rsa->p);
+ ENC(rsa->q);
+
+ return bloblen;
+}
+
+static int rsa2_pubkey_bits(void *blob, int len)
+{
+ struct RSAKey *rsa;
+ int ret;
+
+ rsa = rsa2_newkey((char *) blob, len);
+ ret = bignum_bitcount(rsa->modulus);
+ rsa2_freekey(rsa);
+
+ return ret;
+}
+
+static char *rsa2_fingerprint(void *key)
+{
+ struct RSAKey *rsa = (struct RSAKey *) key;
+ struct MD5Context md5c;
+ unsigned char digest[16], lenbuf[4];
+ char buffer[16 * 3 + 40];
+ char *ret;
+ int numlen, i;
+
+ MD5Init(&md5c);
+ MD5Update(&md5c, (unsigned char *)"\0\0\0\7ssh-rsa", 11);
+
+#define ADD_BIGNUM(bignum) \
+ numlen = (bignum_bitcount(bignum)+8)/8; \
+ PUT_32BIT(lenbuf, numlen); MD5Update(&md5c, lenbuf, 4); \
+ for (i = numlen; i-- ;) { \
+ unsigned char c = bignum_byte(bignum, i); \
+ MD5Update(&md5c, &c, 1); \
+ }
+ ADD_BIGNUM(rsa->exponent);
+ ADD_BIGNUM(rsa->modulus);
+#undef ADD_BIGNUM
+
+ MD5Final(digest, &md5c);
+
+ sprintf(buffer, "ssh-rsa %d ", bignum_bitcount(rsa->modulus));
+ for (i = 0; i < 16; i++)
+ sprintf(buffer + strlen(buffer), "%s%02x", i ? ":" : "",
+ digest[i]);
+ ret = snewn(strlen(buffer) + 1, char);
+ if (ret)
+ strcpy(ret, buffer);
+ return ret;
+}
+
+/*
+ * This is the magic ASN.1/DER prefix that goes in the decoded
+ * signature, between the string of FFs and the actual SHA hash
+ * value. The meaning of it is:
+ *
+ * 00 -- this marks the end of the FFs; not part of the ASN.1 bit itself
+ *
+ * 30 21 -- a constructed SEQUENCE of length 0x21
+ * 30 09 -- a constructed sub-SEQUENCE of length 9
+ * 06 05 -- an object identifier, length 5
+ * 2B 0E 03 02 1A -- object id { 1 3 14 3 2 26 }
+ * (the 1,3 comes from 0x2B = 43 = 40*1+3)
+ * 05 00 -- NULL
+ * 04 14 -- a primitive OCTET STRING of length 0x14
+ * [0x14 bytes of hash data follows]
+ *
+ * The object id in the middle there is listed as `id-sha1' in
+ * ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-1/pkcs-1v2-1d2.asn (the
+ * ASN module for PKCS #1) and its expanded form is as follows:
+ *
+ * id-sha1 OBJECT IDENTIFIER ::= {
+ * iso(1) identified-organization(3) oiw(14) secsig(3)
+ * algorithms(2) 26 }
+ */
+static const unsigned char asn1_weird_stuff[] = {
+ 0x00, 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2B,
+ 0x0E, 0x03, 0x02, 0x1A, 0x05, 0x00, 0x04, 0x14,
+};
+
+#define ASN1_LEN ( (int) sizeof(asn1_weird_stuff) )
+
+static int rsa2_verifysig(void *key, char *sig, int siglen,
+ char *data, int datalen)
+{
+ struct RSAKey *rsa = (struct RSAKey *) key;
+ Bignum in, out;
+ char *p;
+ int slen;
+ int bytes, i, j, ret;
+ unsigned char hash[20];
+
+ getstring(&sig, &siglen, &p, &slen);
+ if (!p || slen != 7 || memcmp(p, "ssh-rsa", 7)) {
+ return 0;
+ }
+ in = getmp(&sig, &siglen);
+ out = modpow(in, rsa->exponent, rsa->modulus);
+ freebn(in);
+
+ ret = 1;
+
+ bytes = (bignum_bitcount(rsa->modulus)+7) / 8;
+ /* Top (partial) byte should be zero. */
+ if (bignum_byte(out, bytes - 1) != 0)
+ ret = 0;
+ /* First whole byte should be 1. */
+ if (bignum_byte(out, bytes - 2) != 1)
+ ret = 0;
+ /* Most of the rest should be FF. */
+ for (i = bytes - 3; i >= 20 + ASN1_LEN; i--) {
+ if (bignum_byte(out, i) != 0xFF)
+ ret = 0;
+ }
+ /* Then we expect to see the asn1_weird_stuff. */
+ for (i = 20 + ASN1_LEN - 1, j = 0; i >= 20; i--, j++) {
+ if (bignum_byte(out, i) != asn1_weird_stuff[j])
+ ret = 0;
+ }
+ /* Finally, we expect to see the SHA-1 hash of the signed data. */
+ SHA_Simple(data, datalen, hash);
+ for (i = 19, j = 0; i >= 0; i--, j++) {
+ if (bignum_byte(out, i) != hash[j])
+ ret = 0;
+ }
+ freebn(out);
+
+ return ret;
+}
+
+static unsigned char *rsa2_sign(void *key, char *data, int datalen,
+ int *siglen)
+{
+ struct RSAKey *rsa = (struct RSAKey *) key;
+ unsigned char *bytes;
+ int nbytes;
+ unsigned char hash[20];
+ Bignum in, out;
+ int i, j;
+
+ SHA_Simple(data, datalen, hash);
+
+ nbytes = (bignum_bitcount(rsa->modulus) - 1) / 8;
+ assert(1 <= nbytes - 20 - ASN1_LEN);
+ bytes = snewn(nbytes, unsigned char);
+
+ bytes[0] = 1;
+ for (i = 1; i < nbytes - 20 - ASN1_LEN; i++)
+ bytes[i] = 0xFF;
+ for (i = nbytes - 20 - ASN1_LEN, j = 0; i < nbytes - 20; i++, j++)
+ bytes[i] = asn1_weird_stuff[j];
+ for (i = nbytes - 20, j = 0; i < nbytes; i++, j++)
+ bytes[i] = hash[j];
+
+ in = bignum_from_bytes(bytes, nbytes);
+ sfree(bytes);
+
+ out = rsa_privkey_op(in, rsa);
+ freebn(in);
+
+ nbytes = (bignum_bitcount(out) + 7) / 8;
+ bytes = snewn(4 + 7 + 4 + nbytes, unsigned char);
+ PUT_32BIT(bytes, 7);
+ memcpy(bytes + 4, "ssh-rsa", 7);
+ PUT_32BIT(bytes + 4 + 7, nbytes);
+ for (i = 0; i < nbytes; i++)
+ bytes[4 + 7 + 4 + i] = bignum_byte(out, nbytes - 1 - i);
+ freebn(out);
+
+ *siglen = 4 + 7 + 4 + nbytes;
+ return bytes;
+}
+
+const struct ssh_signkey ssh_rsa = {
+ rsa2_newkey,
+ rsa2_freekey,
+ rsa2_fmtkey,
+ rsa2_public_blob,
+ rsa2_private_blob,
+ rsa2_createkey,
+ rsa2_openssh_createkey,
+ rsa2_openssh_fmtkey,
+ rsa2_pubkey_bits,
+ rsa2_fingerprint,
+ rsa2_verifysig,
+ rsa2_sign,
+ "ssh-rsa",
+ "rsa2"
+};
+
+void *ssh_rsakex_newkey(char *data, int len)
+{
+ return rsa2_newkey(data, len);
+}
+
+void ssh_rsakex_freekey(void *key)
+{
+ rsa2_freekey(key);
+}
+
+int ssh_rsakex_klen(void *key)
+{
+ struct RSAKey *rsa = (struct RSAKey *) key;
+
+ return bignum_bitcount(rsa->modulus);
+}
+
+static void oaep_mask(const struct ssh_hash *h, void *seed, int seedlen,
+ void *vdata, int datalen)
+{
+ unsigned char *data = (unsigned char *)vdata;
+ unsigned count = 0;
+
+ while (datalen > 0) {
+ int i, max = (datalen > h->hlen ? h->hlen : datalen);
+ void *s;
+ unsigned char counter[4], hash[SSH2_KEX_MAX_HASH_LEN];
+
+ assert(h->hlen <= SSH2_KEX_MAX_HASH_LEN);
+ PUT_32BIT(counter, count);
+ s = h->init();
+ h->bytes(s, seed, seedlen);
+ h->bytes(s, counter, 4);
+ h->final(s, hash);
+ count++;
+
+ for (i = 0; i < max; i++)
+ data[i] ^= hash[i];
+
+ data += max;
+ datalen -= max;
+ }
+}
+
+void ssh_rsakex_encrypt(const struct ssh_hash *h, unsigned char *in, int inlen,
+ unsigned char *out, int outlen,
+ void *key)
+{
+ Bignum b1, b2;
+ struct RSAKey *rsa = (struct RSAKey *) key;
+ int k, i;
+ char *p;
+ const int HLEN = h->hlen;
+
+ /*
+ * Here we encrypt using RSAES-OAEP. Essentially this means:
+ *
+ * - we have a SHA-based `mask generation function' which
+ * creates a pseudo-random stream of mask data
+ * deterministically from an input chunk of data.
+ *
+ * - we have a random chunk of data called a seed.
+ *
+ * - we use the seed to generate a mask which we XOR with our
+ * plaintext.
+ *
+ * - then we use _the masked plaintext_ to generate a mask
+ * which we XOR with the seed.
+ *
+ * - then we concatenate the masked seed and the masked
+ * plaintext, and RSA-encrypt that lot.
+ *
+ * The result is that the data input to the encryption function
+ * is random-looking and (hopefully) contains no exploitable
+ * structure such as PKCS1-v1_5 does.
+ *
+ * For a precise specification, see RFC 3447, section 7.1.1.
+ * Some of the variable names below are derived from that, so
+ * it'd probably help to read it anyway.
+ */
+
+ /* k denotes the length in octets of the RSA modulus. */
+ k = (7 + bignum_bitcount(rsa->modulus)) / 8;
+
+ /* The length of the input data must be at most k - 2hLen - 2. */
+ assert(inlen > 0 && inlen <= k - 2*HLEN - 2);
+
+ /* The length of the output data wants to be precisely k. */
+ assert(outlen == k);
+
+ /*
+ * Now perform EME-OAEP encoding. First set up all the unmasked
+ * output data.
+ */
+ /* Leading byte zero. */
+ out[0] = 0;
+ /* At position 1, the seed: HLEN bytes of random data. */
+ for (i = 0; i < HLEN; i++)
+ out[i + 1] = random_byte();
+ /* At position 1+HLEN, the data block DB, consisting of: */
+ /* The hash of the label (we only support an empty label here) */
+ h->final(h->init(), out + HLEN + 1);
+ /* A bunch of zero octets */
+ memset(out + 2*HLEN + 1, 0, outlen - (2*HLEN + 1));
+ /* A single 1 octet, followed by the input message data. */
+ out[outlen - inlen - 1] = 1;
+ memcpy(out + outlen - inlen, in, inlen);
+
+ /*
+ * Now use the seed data to mask the block DB.
+ */
+ oaep_mask(h, out+1, HLEN, out+HLEN+1, outlen-HLEN-1);
+
+ /*
+ * And now use the masked DB to mask the seed itself.
+ */
+ oaep_mask(h, out+HLEN+1, outlen-HLEN-1, out+1, HLEN);
+
+ /*
+ * Now `out' contains precisely the data we want to
+ * RSA-encrypt.
+ */
+ b1 = bignum_from_bytes(out, outlen);
+ b2 = modpow(b1, rsa->exponent, rsa->modulus);
+ p = (char *)out;
+ for (i = outlen; i--;) {
+ *p++ = bignum_byte(b2, i);
+ }
+ freebn(b1);
+ freebn(b2);
+
+ /*
+ * And we're done.
+ */
+}
+
+static const struct ssh_kex ssh_rsa_kex_sha1 = {
+ "rsa1024-sha1", NULL, KEXTYPE_RSA, NULL, NULL, 0, 0, &ssh_sha1
+};
+
+static const struct ssh_kex ssh_rsa_kex_sha256 = {
+ "rsa2048-sha256", NULL, KEXTYPE_RSA, NULL, NULL, 0, 0, &ssh_sha256
+};
+
+static const struct ssh_kex *const rsa_kex_list[] = {
+ &ssh_rsa_kex_sha256,
+ &ssh_rsa_kex_sha1
+};
+
+const struct ssh_kexes ssh_rsa_kex = {
+ sizeof(rsa_kex_list) / sizeof(*rsa_kex_list),
+ rsa_kex_list
+};
--- /dev/null
+/*
+ * RSA key generation.
+ */
+
+#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;
+
+ /*
+ * Set up the phase limits for the progress report. We do this
+ * by passing minus the phase number.
+ *
+ * For prime generation: our initial filter finds things
+ * coprime to everything below 2^16. Computing the product of
+ * (p-1)/p for all prime p below 2^16 gives about 20.33; so
+ * among B-bit integers, one in every 20.33 will get through
+ * the initial filter to be a candidate prime.
+ *
+ * Meanwhile, we are searching for primes in the region of 2^B;
+ * since pi(x) ~ x/log(x), when x is in the region of 2^B, the
+ * prime density will be d/dx pi(x) ~ 1/log(B), i.e. about
+ * 1/0.6931B. So the chance of any given candidate being prime
+ * is 20.33/0.6931B, which is roughly 29.34 divided by B.
+ *
+ * So now we have this probability P, we're looking at an
+ * exponential distribution with parameter P: we will manage in
+ * one attempt with probability P, in two with probability
+ * P(1-P), in three with probability P(1-P)^2, etc. The
+ * probability that we have still not managed to find a prime
+ * after N attempts is (1-P)^N.
+ *
+ * We therefore inform the progress indicator of the number B
+ * (29.34/B), so that it knows how much to increment by each
+ * time. We do this in 16-bit fixed point, so 29.34 becomes
+ * 0x1D.57C4.
+ */
+ pfn(pfnparam, PROGFN_PHASE_EXTENT, 1, 0x10000);
+ pfn(pfnparam, PROGFN_EXP_PHASE, 1, -0x1D57C4 / (bits / 2));
+ pfn(pfnparam, PROGFN_PHASE_EXTENT, 2, 0x10000);
+ pfn(pfnparam, PROGFN_EXP_PHASE, 2, -0x1D57C4 / (bits - bits / 2));
+ pfn(pfnparam, PROGFN_PHASE_EXTENT, 3, 0x4000);
+ pfn(pfnparam, PROGFN_LIN_PHASE, 3, 5);
+ pfn(pfnparam, PROGFN_READY, 0, 0);
+
+ /*
+ * We don't generate e; we just use a standard one always.
+ */
+ key->exponent = bignum_from_long(RSA_EXPONENT);
+
+ /*
+ * Generate p and q: primes with combined length `bits', not
+ * 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.)
+ */
+ key->p = primegen(bits / 2, RSA_EXPONENT, 1, NULL,
+ 1, pfn, pfnparam);
+ key->q = primegen(bits - bits / 2, RSA_EXPONENT, 1, NULL,
+ 2, pfn, pfnparam);
+
+ /*
+ * Ensure p > q, by swapping them if not.
+ */
+ if (bignum_cmp(key->p, key->q) < 0) {
+ Bignum t = key->p;
+ key->p = key->q;
+ key->q = t;
+ }
+
+ /*
+ * Now we have p, q and e. All we need to do now is work out
+ * the other helpful quantities: n=pq, d=e^-1 mod (p-1)(q-1),
+ * and (q^-1 mod p).
+ */
+ pfn(pfnparam, PROGFN_PROGRESS, 3, 1);
+ key->modulus = bigmul(key->p, key->q);
+ pfn(pfnparam, PROGFN_PROGRESS, 3, 2);
+ pm1 = copybn(key->p);
+ decbn(pm1);
+ qm1 = copybn(key->q);
+ decbn(qm1);
+ phi_n = bigmul(pm1, qm1);
+ pfn(pfnparam, PROGFN_PROGRESS, 3, 3);
+ freebn(pm1);
+ freebn(qm1);
+ key->private_exponent = modinv(key->exponent, phi_n);
+ pfn(pfnparam, PROGFN_PROGRESS, 3, 4);
+ key->iqmp = modinv(key->q, key->p);
+ pfn(pfnparam, PROGFN_PROGRESS, 3, 5);
+
+ /*
+ * Clean up temporary numbers.
+ */
+ freebn(phi_n);
+
+ return 1;
+}
--- /dev/null
+/*
+ * SHA-256 algorithm as described at
+ *
+ * http://csrc.nist.gov/cryptval/shs.html
+ */
+
+#include "ssh.h"
+
+/* ----------------------------------------------------------------------
+ * Core SHA256 algorithm: processes 16-word blocks into a message digest.
+ */
+
+#define ror(x,y) ( ((x) << (32-y)) | (((uint32)(x)) >> (y)) )
+#define shr(x,y) ( (((uint32)(x)) >> (y)) )
+#define Ch(x,y,z) ( ((x) & (y)) ^ (~(x) & (z)) )
+#define Maj(x,y,z) ( ((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z)) )
+#define bigsigma0(x) ( ror((x),2) ^ ror((x),13) ^ ror((x),22) )
+#define bigsigma1(x) ( ror((x),6) ^ ror((x),11) ^ ror((x),25) )
+#define smallsigma0(x) ( ror((x),7) ^ ror((x),18) ^ shr((x),3) )
+#define smallsigma1(x) ( ror((x),17) ^ ror((x),19) ^ shr((x),10) )
+
+void SHA256_Core_Init(SHA256_State *s) {
+ s->h[0] = 0x6a09e667;
+ s->h[1] = 0xbb67ae85;
+ s->h[2] = 0x3c6ef372;
+ s->h[3] = 0xa54ff53a;
+ s->h[4] = 0x510e527f;
+ s->h[5] = 0x9b05688c;
+ s->h[6] = 0x1f83d9ab;
+ s->h[7] = 0x5be0cd19;
+}
+
+void SHA256_Block(SHA256_State *s, uint32 *block) {
+ uint32 w[80];
+ uint32 a,b,c,d,e,f,g,h;
+ static const int k[] = {
+ 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
+ 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
+ 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
+ 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
+ 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
+ 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
+ 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
+ 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
+ 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
+ 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
+ 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
+ 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
+ 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
+ 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
+ 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
+ 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
+ };
+
+ int t;
+
+ for (t = 0; t < 16; t++)
+ w[t] = block[t];
+
+ for (t = 16; t < 64; t++)
+ w[t] = smallsigma1(w[t-2]) + w[t-7] + smallsigma0(w[t-15]) + w[t-16];
+
+ a = s->h[0]; b = s->h[1]; c = s->h[2]; d = s->h[3];
+ e = s->h[4]; f = s->h[5]; g = s->h[6]; h = s->h[7];
+
+ for (t = 0; t < 64; t+=8) {
+ uint32 t1, t2;
+
+#define ROUND(j,a,b,c,d,e,f,g,h) \
+ t1 = h + bigsigma1(e) + Ch(e,f,g) + k[j] + w[j]; \
+ t2 = bigsigma0(a) + Maj(a,b,c); \
+ d = d + t1; h = t1 + t2;
+
+ ROUND(t+0, a,b,c,d,e,f,g,h);
+ ROUND(t+1, h,a,b,c,d,e,f,g);
+ ROUND(t+2, g,h,a,b,c,d,e,f);
+ ROUND(t+3, f,g,h,a,b,c,d,e);
+ ROUND(t+4, e,f,g,h,a,b,c,d);
+ ROUND(t+5, d,e,f,g,h,a,b,c);
+ ROUND(t+6, c,d,e,f,g,h,a,b);
+ ROUND(t+7, b,c,d,e,f,g,h,a);
+ }
+
+ s->h[0] += a; s->h[1] += b; s->h[2] += c; s->h[3] += d;
+ s->h[4] += e; s->h[5] += f; s->h[6] += g; s->h[7] += h;
+}
+
+/* ----------------------------------------------------------------------
+ * Outer SHA256 algorithm: take an arbitrary length byte string,
+ * convert it into 16-word blocks with the prescribed padding at
+ * the end, and pass those blocks to the core SHA256 algorithm.
+ */
+
+#define BLKSIZE 64
+
+void SHA256_Init(SHA256_State *s) {
+ SHA256_Core_Init(s);
+ s->blkused = 0;
+ s->lenhi = s->lenlo = 0;
+}
+
+void SHA256_Bytes(SHA256_State *s, const void *p, int len) {
+ unsigned char *q = (unsigned char *)p;
+ uint32 wordblock[16];
+ uint32 lenw = len;
+ int i;
+
+ /*
+ * Update the length field.
+ */
+ s->lenlo += lenw;
+ s->lenhi += (s->lenlo < lenw);
+
+ if (s->blkused && s->blkused+len < BLKSIZE) {
+ /*
+ * Trivial case: just add to the block.
+ */
+ memcpy(s->block + s->blkused, q, len);
+ s->blkused += len;
+ } else {
+ /*
+ * We must complete and process at least one block.
+ */
+ while (s->blkused + len >= BLKSIZE) {
+ memcpy(s->block + s->blkused, q, BLKSIZE - s->blkused);
+ q += BLKSIZE - s->blkused;
+ len -= BLKSIZE - s->blkused;
+ /* Now process the block. Gather bytes big-endian into words */
+ for (i = 0; i < 16; i++) {
+ wordblock[i] =
+ ( ((uint32)s->block[i*4+0]) << 24 ) |
+ ( ((uint32)s->block[i*4+1]) << 16 ) |
+ ( ((uint32)s->block[i*4+2]) << 8 ) |
+ ( ((uint32)s->block[i*4+3]) << 0 );
+ }
+ SHA256_Block(s, wordblock);
+ s->blkused = 0;
+ }
+ memcpy(s->block, q, len);
+ s->blkused = len;
+ }
+}
+
+void SHA256_Final(SHA256_State *s, unsigned char *digest) {
+ int i;
+ int pad;
+ unsigned char c[64];
+ uint32 lenhi, lenlo;
+
+ if (s->blkused >= 56)
+ pad = 56 + 64 - s->blkused;
+ else
+ pad = 56 - s->blkused;
+
+ lenhi = (s->lenhi << 3) | (s->lenlo >> (32-3));
+ lenlo = (s->lenlo << 3);
+
+ memset(c, 0, pad);
+ c[0] = 0x80;
+ SHA256_Bytes(s, &c, pad);
+
+ c[0] = (lenhi >> 24) & 0xFF;
+ c[1] = (lenhi >> 16) & 0xFF;
+ c[2] = (lenhi >> 8) & 0xFF;
+ c[3] = (lenhi >> 0) & 0xFF;
+ c[4] = (lenlo >> 24) & 0xFF;
+ c[5] = (lenlo >> 16) & 0xFF;
+ c[6] = (lenlo >> 8) & 0xFF;
+ c[7] = (lenlo >> 0) & 0xFF;
+
+ SHA256_Bytes(s, &c, 8);
+
+ for (i = 0; i < 8; i++) {
+ digest[i*4+0] = (s->h[i] >> 24) & 0xFF;
+ digest[i*4+1] = (s->h[i] >> 16) & 0xFF;
+ digest[i*4+2] = (s->h[i] >> 8) & 0xFF;
+ digest[i*4+3] = (s->h[i] >> 0) & 0xFF;
+ }
+}
+
+void SHA256_Simple(const void *p, int len, unsigned char *output) {
+ SHA256_State s;
+
+ SHA256_Init(&s);
+ SHA256_Bytes(&s, p, len);
+ SHA256_Final(&s, output);
+}
+
+/*
+ * Thin abstraction for things where hashes are pluggable.
+ */
+
+static void *sha256_init(void)
+{
+ SHA256_State *s;
+
+ s = snew(SHA256_State);
+ SHA256_Init(s);
+ return s;
+}
+
+static void sha256_bytes(void *handle, void *p, int len)
+{
+ SHA256_State *s = handle;
+
+ SHA256_Bytes(s, p, len);
+}
+
+static void sha256_final(void *handle, unsigned char *output)
+{
+ SHA256_State *s = handle;
+
+ SHA256_Final(s, output);
+ sfree(s);
+}
+
+const struct ssh_hash ssh_sha256 = {
+ sha256_init, sha256_bytes, sha256_final, 32, "SHA-256"
+};
+
+#ifdef TEST
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+
+int main(void) {
+ unsigned char digest[32];
+ int i, j, errors;
+
+ struct {
+ const char *teststring;
+ unsigned char digest[32];
+ } tests[] = {
+ { "abc", {
+ 0xba, 0x78, 0x16, 0xbf, 0x8f, 0x01, 0xcf, 0xea,
+ 0x41, 0x41, 0x40, 0xde, 0x5d, 0xae, 0x22, 0x23,
+ 0xb0, 0x03, 0x61, 0xa3, 0x96, 0x17, 0x7a, 0x9c,
+ 0xb4, 0x10, 0xff, 0x61, 0xf2, 0x00, 0x15, 0xad,
+ } },
+ { "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", {
+ 0x24, 0x8d, 0x6a, 0x61, 0xd2, 0x06, 0x38, 0xb8,
+ 0xe5, 0xc0, 0x26, 0x93, 0x0c, 0x3e, 0x60, 0x39,
+ 0xa3, 0x3c, 0xe4, 0x59, 0x64, 0xff, 0x21, 0x67,
+ 0xf6, 0xec, 0xed, 0xd4, 0x19, 0xdb, 0x06, 0xc1,
+ } },
+ };
+
+ errors = 0;
+
+ for (i = 0; i < sizeof(tests) / sizeof(*tests); i++) {
+ SHA256_Simple(tests[i].teststring,
+ strlen(tests[i].teststring), digest);
+ for (j = 0; j < 32; j++) {
+ if (digest[j] != tests[i].digest[j]) {
+ fprintf(stderr,
+ "\"%s\" digest byte %d should be 0x%02x, is 0x%02x\n",
+ tests[i].teststring, j, tests[i].digest[j], digest[j]);
+ errors++;
+ }
+ }
+ }
+
+ printf("%d errors\n", errors);
+
+ return 0;
+}
+
+#endif
--- /dev/null
+/*
+ * SHA-512 algorithm as described at
+ *
+ * http://csrc.nist.gov/cryptval/shs.html
+ */
+
+#include "ssh.h"
+
+#define BLKSIZE 128
+
+/*
+ * Arithmetic implementations. Note that AND, XOR and NOT can
+ * overlap destination with one source, but the others can't.
+ */
+#define add(r,x,y) ( r.lo = y.lo + x.lo, \
+ r.hi = y.hi + x.hi + ((uint32)r.lo < (uint32)y.lo) )
+#define rorB(r,x,y) ( r.lo = ((uint32)x.hi >> ((y)-32)) | ((uint32)x.lo << (64-(y))), \
+ r.hi = ((uint32)x.lo >> ((y)-32)) | ((uint32)x.hi << (64-(y))) )
+#define rorL(r,x,y) ( r.lo = ((uint32)x.lo >> (y)) | ((uint32)x.hi << (32-(y))), \
+ r.hi = ((uint32)x.hi >> (y)) | ((uint32)x.lo << (32-(y))) )
+#define shrB(r,x,y) ( r.lo = (uint32)x.hi >> ((y)-32), r.hi = 0 )
+#define shrL(r,x,y) ( r.lo = ((uint32)x.lo >> (y)) | ((uint32)x.hi << (32-(y))), \
+ r.hi = (uint32)x.hi >> (y) )
+#define and(r,x,y) ( r.lo = x.lo & y.lo, r.hi = x.hi & y.hi )
+#define xor(r,x,y) ( r.lo = x.lo ^ y.lo, r.hi = x.hi ^ y.hi )
+#define not(r,x) ( r.lo = ~x.lo, r.hi = ~x.hi )
+#define INIT(h,l) { h, l }
+#define BUILD(r,h,l) ( r.hi = h, r.lo = l )
+#define EXTRACT(h,l,r) ( h = r.hi, l = r.lo )
+
+/* ----------------------------------------------------------------------
+ * Core SHA512 algorithm: processes 16-doubleword blocks into a
+ * message digest.
+ */
+
+#define Ch(r,t,x,y,z) ( not(t,x), and(r,t,z), and(t,x,y), xor(r,r,t) )
+#define Maj(r,t,x,y,z) ( and(r,x,y), and(t,x,z), xor(r,r,t), \
+ and(t,y,z), xor(r,r,t) )
+#define bigsigma0(r,t,x) ( rorL(r,x,28), rorB(t,x,34), xor(r,r,t), \
+ rorB(t,x,39), xor(r,r,t) )
+#define bigsigma1(r,t,x) ( rorL(r,x,14), rorL(t,x,18), xor(r,r,t), \
+ rorB(t,x,41), xor(r,r,t) )
+#define smallsigma0(r,t,x) ( rorL(r,x,1), rorL(t,x,8), xor(r,r,t), \
+ shrL(t,x,7), xor(r,r,t) )
+#define smallsigma1(r,t,x) ( rorL(r,x,19), rorB(t,x,61), xor(r,r,t), \
+ shrL(t,x,6), xor(r,r,t) )
+
+static void SHA512_Core_Init(SHA512_State *s) {
+ static const uint64 iv[] = {
+ INIT(0x6a09e667, 0xf3bcc908),
+ INIT(0xbb67ae85, 0x84caa73b),
+ INIT(0x3c6ef372, 0xfe94f82b),
+ INIT(0xa54ff53a, 0x5f1d36f1),
+ INIT(0x510e527f, 0xade682d1),
+ INIT(0x9b05688c, 0x2b3e6c1f),
+ INIT(0x1f83d9ab, 0xfb41bd6b),
+ INIT(0x5be0cd19, 0x137e2179),
+ };
+ int i;
+ for (i = 0; i < 8; i++)
+ s->h[i] = iv[i];
+}
+
+static void SHA512_Block(SHA512_State *s, uint64 *block) {
+ uint64 w[80];
+ uint64 a,b,c,d,e,f,g,h;
+ static const uint64 k[] = {
+ INIT(0x428a2f98, 0xd728ae22), INIT(0x71374491, 0x23ef65cd),
+ INIT(0xb5c0fbcf, 0xec4d3b2f), INIT(0xe9b5dba5, 0x8189dbbc),
+ INIT(0x3956c25b, 0xf348b538), INIT(0x59f111f1, 0xb605d019),
+ INIT(0x923f82a4, 0xaf194f9b), INIT(0xab1c5ed5, 0xda6d8118),
+ INIT(0xd807aa98, 0xa3030242), INIT(0x12835b01, 0x45706fbe),
+ INIT(0x243185be, 0x4ee4b28c), INIT(0x550c7dc3, 0xd5ffb4e2),
+ INIT(0x72be5d74, 0xf27b896f), INIT(0x80deb1fe, 0x3b1696b1),
+ INIT(0x9bdc06a7, 0x25c71235), INIT(0xc19bf174, 0xcf692694),
+ INIT(0xe49b69c1, 0x9ef14ad2), INIT(0xefbe4786, 0x384f25e3),
+ INIT(0x0fc19dc6, 0x8b8cd5b5), INIT(0x240ca1cc, 0x77ac9c65),
+ INIT(0x2de92c6f, 0x592b0275), INIT(0x4a7484aa, 0x6ea6e483),
+ INIT(0x5cb0a9dc, 0xbd41fbd4), INIT(0x76f988da, 0x831153b5),
+ INIT(0x983e5152, 0xee66dfab), INIT(0xa831c66d, 0x2db43210),
+ INIT(0xb00327c8, 0x98fb213f), INIT(0xbf597fc7, 0xbeef0ee4),
+ INIT(0xc6e00bf3, 0x3da88fc2), INIT(0xd5a79147, 0x930aa725),
+ INIT(0x06ca6351, 0xe003826f), INIT(0x14292967, 0x0a0e6e70),
+ INIT(0x27b70a85, 0x46d22ffc), INIT(0x2e1b2138, 0x5c26c926),
+ INIT(0x4d2c6dfc, 0x5ac42aed), INIT(0x53380d13, 0x9d95b3df),
+ INIT(0x650a7354, 0x8baf63de), INIT(0x766a0abb, 0x3c77b2a8),
+ INIT(0x81c2c92e, 0x47edaee6), INIT(0x92722c85, 0x1482353b),
+ INIT(0xa2bfe8a1, 0x4cf10364), INIT(0xa81a664b, 0xbc423001),
+ INIT(0xc24b8b70, 0xd0f89791), INIT(0xc76c51a3, 0x0654be30),
+ INIT(0xd192e819, 0xd6ef5218), INIT(0xd6990624, 0x5565a910),
+ INIT(0xf40e3585, 0x5771202a), INIT(0x106aa070, 0x32bbd1b8),
+ INIT(0x19a4c116, 0xb8d2d0c8), INIT(0x1e376c08, 0x5141ab53),
+ INIT(0x2748774c, 0xdf8eeb99), INIT(0x34b0bcb5, 0xe19b48a8),
+ INIT(0x391c0cb3, 0xc5c95a63), INIT(0x4ed8aa4a, 0xe3418acb),
+ INIT(0x5b9cca4f, 0x7763e373), INIT(0x682e6ff3, 0xd6b2b8a3),
+ INIT(0x748f82ee, 0x5defb2fc), INIT(0x78a5636f, 0x43172f60),
+ INIT(0x84c87814, 0xa1f0ab72), INIT(0x8cc70208, 0x1a6439ec),
+ INIT(0x90befffa, 0x23631e28), INIT(0xa4506ceb, 0xde82bde9),
+ INIT(0xbef9a3f7, 0xb2c67915), INIT(0xc67178f2, 0xe372532b),
+ INIT(0xca273ece, 0xea26619c), INIT(0xd186b8c7, 0x21c0c207),
+ INIT(0xeada7dd6, 0xcde0eb1e), INIT(0xf57d4f7f, 0xee6ed178),
+ INIT(0x06f067aa, 0x72176fba), INIT(0x0a637dc5, 0xa2c898a6),
+ INIT(0x113f9804, 0xbef90dae), INIT(0x1b710b35, 0x131c471b),
+ INIT(0x28db77f5, 0x23047d84), INIT(0x32caab7b, 0x40c72493),
+ INIT(0x3c9ebe0a, 0x15c9bebc), INIT(0x431d67c4, 0x9c100d4c),
+ INIT(0x4cc5d4be, 0xcb3e42b6), INIT(0x597f299c, 0xfc657e2a),
+ INIT(0x5fcb6fab, 0x3ad6faec), INIT(0x6c44198c, 0x4a475817),
+ };
+
+ int t;
+
+ for (t = 0; t < 16; t++)
+ w[t] = block[t];
+
+ for (t = 16; t < 80; t++) {
+ uint64 p, q, r, tmp;
+ smallsigma1(p, tmp, w[t-2]);
+ smallsigma0(q, tmp, w[t-15]);
+ add(r, p, q);
+ add(p, r, w[t-7]);
+ add(w[t], p, w[t-16]);
+ }
+
+ a = s->h[0]; b = s->h[1]; c = s->h[2]; d = s->h[3];
+ e = s->h[4]; f = s->h[5]; g = s->h[6]; h = s->h[7];
+
+ for (t = 0; t < 80; t+=8) {
+ uint64 tmp, p, q, r;
+
+#define ROUND(j,a,b,c,d,e,f,g,h) \
+ bigsigma1(p, tmp, e); \
+ Ch(q, tmp, e, f, g); \
+ add(r, p, q); \
+ add(p, r, k[j]) ; \
+ add(q, p, w[j]); \
+ add(r, q, h); \
+ bigsigma0(p, tmp, a); \
+ Maj(tmp, q, a, b, c); \
+ add(q, tmp, p); \
+ add(p, r, d); \
+ d = p; \
+ add(h, q, r);
+
+ ROUND(t+0, a,b,c,d,e,f,g,h);
+ ROUND(t+1, h,a,b,c,d,e,f,g);
+ ROUND(t+2, g,h,a,b,c,d,e,f);
+ ROUND(t+3, f,g,h,a,b,c,d,e);
+ ROUND(t+4, e,f,g,h,a,b,c,d);
+ ROUND(t+5, d,e,f,g,h,a,b,c);
+ ROUND(t+6, c,d,e,f,g,h,a,b);
+ ROUND(t+7, b,c,d,e,f,g,h,a);
+ }
+
+ {
+ uint64 tmp;
+#define UPDATE(state, local) ( tmp = state, add(state, tmp, local) )
+ UPDATE(s->h[0], a); UPDATE(s->h[1], b);
+ UPDATE(s->h[2], c); UPDATE(s->h[3], d);
+ UPDATE(s->h[4], e); UPDATE(s->h[5], f);
+ UPDATE(s->h[6], g); UPDATE(s->h[7], h);
+ }
+}
+
+/* ----------------------------------------------------------------------
+ * Outer SHA512 algorithm: take an arbitrary length byte string,
+ * convert it into 16-doubleword blocks with the prescribed padding
+ * at the end, and pass those blocks to the core SHA512 algorithm.
+ */
+
+void SHA512_Init(SHA512_State *s) {
+ int i;
+ SHA512_Core_Init(s);
+ s->blkused = 0;
+ for (i = 0; i < 4; i++)
+ s->len[i] = 0;
+}
+
+void SHA512_Bytes(SHA512_State *s, const void *p, int len) {
+ unsigned char *q = (unsigned char *)p;
+ uint64 wordblock[16];
+ uint32 lenw = len;
+ int i;
+
+ /*
+ * Update the length field.
+ */
+ for (i = 0; i < 4; i++) {
+ s->len[i] += lenw;
+ lenw = (s->len[i] < lenw);
+ }
+
+ if (s->blkused && s->blkused+len < BLKSIZE) {
+ /*
+ * Trivial case: just add to the block.
+ */
+ memcpy(s->block + s->blkused, q, len);
+ s->blkused += len;
+ } else {
+ /*
+ * We must complete and process at least one block.
+ */
+ while (s->blkused + len >= BLKSIZE) {
+ memcpy(s->block + s->blkused, q, BLKSIZE - s->blkused);
+ q += BLKSIZE - s->blkused;
+ len -= BLKSIZE - s->blkused;
+ /* Now process the block. Gather bytes big-endian into words */
+ for (i = 0; i < 16; i++) {
+ uint32 h, l;
+ h = ( ((uint32)s->block[i*8+0]) << 24 ) |
+ ( ((uint32)s->block[i*8+1]) << 16 ) |
+ ( ((uint32)s->block[i*8+2]) << 8 ) |
+ ( ((uint32)s->block[i*8+3]) << 0 );
+ l = ( ((uint32)s->block[i*8+4]) << 24 ) |
+ ( ((uint32)s->block[i*8+5]) << 16 ) |
+ ( ((uint32)s->block[i*8+6]) << 8 ) |
+ ( ((uint32)s->block[i*8+7]) << 0 );
+ BUILD(wordblock[i], h, l);
+ }
+ SHA512_Block(s, wordblock);
+ s->blkused = 0;
+ }
+ memcpy(s->block, q, len);
+ s->blkused = len;
+ }
+}
+
+void SHA512_Final(SHA512_State *s, unsigned char *digest) {
+ int i;
+ int pad;
+ unsigned char c[BLKSIZE];
+ uint32 len[4];
+
+ if (s->blkused >= BLKSIZE-16)
+ pad = (BLKSIZE-16) + BLKSIZE - s->blkused;
+ else
+ pad = (BLKSIZE-16) - s->blkused;
+
+ for (i = 4; i-- ;) {
+ uint32 lenhi = s->len[i];
+ uint32 lenlo = i > 0 ? s->len[i-1] : 0;
+ len[i] = (lenhi << 3) | (lenlo >> (32-3));
+ }
+
+ memset(c, 0, pad);
+ c[0] = 0x80;
+ SHA512_Bytes(s, &c, pad);
+
+ for (i = 0; i < 4; i++) {
+ c[i*4+0] = (len[3-i] >> 24) & 0xFF;
+ c[i*4+1] = (len[3-i] >> 16) & 0xFF;
+ c[i*4+2] = (len[3-i] >> 8) & 0xFF;
+ c[i*4+3] = (len[3-i] >> 0) & 0xFF;
+ }
+
+ SHA512_Bytes(s, &c, 16);
+
+ for (i = 0; i < 8; i++) {
+ uint32 h, l;
+ EXTRACT(h, l, s->h[i]);
+ digest[i*8+0] = (h >> 24) & 0xFF;
+ digest[i*8+1] = (h >> 16) & 0xFF;
+ digest[i*8+2] = (h >> 8) & 0xFF;
+ digest[i*8+3] = (h >> 0) & 0xFF;
+ digest[i*8+4] = (l >> 24) & 0xFF;
+ digest[i*8+5] = (l >> 16) & 0xFF;
+ digest[i*8+6] = (l >> 8) & 0xFF;
+ digest[i*8+7] = (l >> 0) & 0xFF;
+ }
+}
+
+void SHA512_Simple(const void *p, int len, unsigned char *output) {
+ SHA512_State s;
+
+ SHA512_Init(&s);
+ SHA512_Bytes(&s, p, len);
+ SHA512_Final(&s, output);
+}
+
+#ifdef TEST
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+
+int main(void) {
+ unsigned char digest[64];
+ int i, j, errors;
+
+ struct {
+ const char *teststring;
+ unsigned char digest512[64];
+ } tests[] = {
+ { "abc", {
+ 0xdd, 0xaf, 0x35, 0xa1, 0x93, 0x61, 0x7a, 0xba,
+ 0xcc, 0x41, 0x73, 0x49, 0xae, 0x20, 0x41, 0x31,
+ 0x12, 0xe6, 0xfa, 0x4e, 0x89, 0xa9, 0x7e, 0xa2,
+ 0x0a, 0x9e, 0xee, 0xe6, 0x4b, 0x55, 0xd3, 0x9a,
+ 0x21, 0x92, 0x99, 0x2a, 0x27, 0x4f, 0xc1, 0xa8,
+ 0x36, 0xba, 0x3c, 0x23, 0xa3, 0xfe, 0xeb, 0xbd,
+ 0x45, 0x4d, 0x44, 0x23, 0x64, 0x3c, 0xe8, 0x0e,
+ 0x2a, 0x9a, 0xc9, 0x4f, 0xa5, 0x4c, 0xa4, 0x9f,
+ } },
+ { "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmn"
+ "hijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu", {
+ 0x8e, 0x95, 0x9b, 0x75, 0xda, 0xe3, 0x13, 0xda,
+ 0x8c, 0xf4, 0xf7, 0x28, 0x14, 0xfc, 0x14, 0x3f,
+ 0x8f, 0x77, 0x79, 0xc6, 0xeb, 0x9f, 0x7f, 0xa1,
+ 0x72, 0x99, 0xae, 0xad, 0xb6, 0x88, 0x90, 0x18,
+ 0x50, 0x1d, 0x28, 0x9e, 0x49, 0x00, 0xf7, 0xe4,
+ 0x33, 0x1b, 0x99, 0xde, 0xc4, 0xb5, 0x43, 0x3a,
+ 0xc7, 0xd3, 0x29, 0xee, 0xb6, 0xdd, 0x26, 0x54,
+ 0x5e, 0x96, 0xe5, 0x5b, 0x87, 0x4b, 0xe9, 0x09,
+ } },
+ { NULL, {
+ 0xe7, 0x18, 0x48, 0x3d, 0x0c, 0xe7, 0x69, 0x64,
+ 0x4e, 0x2e, 0x42, 0xc7, 0xbc, 0x15, 0xb4, 0x63,
+ 0x8e, 0x1f, 0x98, 0xb1, 0x3b, 0x20, 0x44, 0x28,
+ 0x56, 0x32, 0xa8, 0x03, 0xaf, 0xa9, 0x73, 0xeb,
+ 0xde, 0x0f, 0xf2, 0x44, 0x87, 0x7e, 0xa6, 0x0a,
+ 0x4c, 0xb0, 0x43, 0x2c, 0xe5, 0x77, 0xc3, 0x1b,
+ 0xeb, 0x00, 0x9c, 0x5c, 0x2c, 0x49, 0xaa, 0x2e,
+ 0x4e, 0xad, 0xb2, 0x17, 0xad, 0x8c, 0xc0, 0x9b,
+ } },
+ };
+
+ errors = 0;
+
+ for (i = 0; i < sizeof(tests) / sizeof(*tests); i++) {
+ if (tests[i].teststring) {
+ SHA512_Simple(tests[i].teststring,
+ strlen(tests[i].teststring), digest);
+ } else {
+ SHA512_State s;
+ int n;
+ SHA512_Init(&s);
+ for (n = 0; n < 1000000 / 40; n++)
+ SHA512_Bytes(&s, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+ 40);
+ SHA512_Final(&s, digest);
+ }
+ for (j = 0; j < 64; j++) {
+ if (digest[j] != tests[i].digest512[j]) {
+ fprintf(stderr,
+ "\"%s\" digest512 byte %d should be 0x%02x, is 0x%02x\n",
+ tests[i].teststring, j, tests[i].digest512[j],
+ digest[j]);
+ errors++;
+ }
+ }
+
+ }
+
+ printf("%d errors\n", errors);
+
+ return 0;
+}
+
+#endif
--- /dev/null
+/*
+ * SHA1 hash algorithm. Used in SSH-2 as a MAC, and the transform is
+ * also used as a `stirring' function for the PuTTY random number
+ * pool. Implemented directly from the specification by Simon
+ * Tatham.
+ */
+
+#include "ssh.h"
+
+/* ----------------------------------------------------------------------
+ * Core SHA algorithm: processes 16-word blocks into a message digest.
+ */
+
+#define rol(x,y) ( ((x) << (y)) | (((uint32)x) >> (32-y)) )
+
+static void SHA_Core_Init(uint32 h[5])
+{
+ h[0] = 0x67452301;
+ h[1] = 0xefcdab89;
+ h[2] = 0x98badcfe;
+ h[3] = 0x10325476;
+ h[4] = 0xc3d2e1f0;
+}
+
+void SHATransform(word32 * digest, word32 * block)
+{
+ word32 w[80];
+ word32 a, b, c, d, e;
+ int t;
+
+ for (t = 0; t < 16; t++)
+ w[t] = block[t];
+
+ for (t = 16; t < 80; t++) {
+ word32 tmp = w[t - 3] ^ w[t - 8] ^ w[t - 14] ^ w[t - 16];
+ w[t] = rol(tmp, 1);
+ }
+
+ a = digest[0];
+ b = digest[1];
+ c = digest[2];
+ d = digest[3];
+ e = digest[4];
+
+ for (t = 0; t < 20; t++) {
+ word32 tmp =
+ rol(a, 5) + ((b & c) | (d & ~b)) + e + w[t] + 0x5a827999;
+ e = d;
+ d = c;
+ c = rol(b, 30);
+ b = a;
+ a = tmp;
+ }
+ for (t = 20; t < 40; t++) {
+ word32 tmp = rol(a, 5) + (b ^ c ^ d) + e + w[t] + 0x6ed9eba1;
+ e = d;
+ d = c;
+ c = rol(b, 30);
+ b = a;
+ a = tmp;
+ }
+ for (t = 40; t < 60; t++) {
+ word32 tmp = rol(a,
+ 5) + ((b & c) | (b & d) | (c & d)) + e + w[t] +
+ 0x8f1bbcdc;
+ e = d;
+ d = c;
+ c = rol(b, 30);
+ b = a;
+ a = tmp;
+ }
+ for (t = 60; t < 80; t++) {
+ word32 tmp = rol(a, 5) + (b ^ c ^ d) + e + w[t] + 0xca62c1d6;
+ e = d;
+ d = c;
+ c = rol(b, 30);
+ b = a;
+ a = tmp;
+ }
+
+ digest[0] += a;
+ digest[1] += b;
+ digest[2] += c;
+ digest[3] += d;
+ digest[4] += e;
+}
+
+/* ----------------------------------------------------------------------
+ * Outer SHA algorithm: take an arbitrary length byte string,
+ * convert it into 16-word blocks with the prescribed padding at
+ * the end, and pass those blocks to the core SHA algorithm.
+ */
+
+void SHA_Init(SHA_State * s)
+{
+ SHA_Core_Init(s->h);
+ s->blkused = 0;
+ s->lenhi = s->lenlo = 0;
+}
+
+void SHA_Bytes(SHA_State * s, void *p, int len)
+{
+ unsigned char *q = (unsigned char *) p;
+ uint32 wordblock[16];
+ uint32 lenw = len;
+ int i;
+
+ /*
+ * Update the length field.
+ */
+ s->lenlo += lenw;
+ s->lenhi += (s->lenlo < lenw);
+
+ if (s->blkused && s->blkused + len < 64) {
+ /*
+ * Trivial case: just add to the block.
+ */
+ memcpy(s->block + s->blkused, q, len);
+ s->blkused += len;
+ } else {
+ /*
+ * We must complete and process at least one block.
+ */
+ while (s->blkused + len >= 64) {
+ memcpy(s->block + s->blkused, q, 64 - s->blkused);
+ q += 64 - s->blkused;
+ len -= 64 - s->blkused;
+ /* Now process the block. Gather bytes big-endian into words */
+ for (i = 0; i < 16; i++) {
+ wordblock[i] =
+ (((uint32) s->block[i * 4 + 0]) << 24) |
+ (((uint32) s->block[i * 4 + 1]) << 16) |
+ (((uint32) s->block[i * 4 + 2]) << 8) |
+ (((uint32) s->block[i * 4 + 3]) << 0);
+ }
+ SHATransform(s->h, wordblock);
+ s->blkused = 0;
+ }
+ memcpy(s->block, q, len);
+ s->blkused = len;
+ }
+}
+
+void SHA_Final(SHA_State * s, unsigned char *output)
+{
+ int i;
+ int pad;
+ unsigned char c[64];
+ uint32 lenhi, lenlo;
+
+ if (s->blkused >= 56)
+ pad = 56 + 64 - s->blkused;
+ else
+ pad = 56 - s->blkused;
+
+ lenhi = (s->lenhi << 3) | (s->lenlo >> (32 - 3));
+ lenlo = (s->lenlo << 3);
+
+ memset(c, 0, pad);
+ c[0] = 0x80;
+ SHA_Bytes(s, &c, pad);
+
+ c[0] = (lenhi >> 24) & 0xFF;
+ c[1] = (lenhi >> 16) & 0xFF;
+ c[2] = (lenhi >> 8) & 0xFF;
+ c[3] = (lenhi >> 0) & 0xFF;
+ c[4] = (lenlo >> 24) & 0xFF;
+ c[5] = (lenlo >> 16) & 0xFF;
+ c[6] = (lenlo >> 8) & 0xFF;
+ c[7] = (lenlo >> 0) & 0xFF;
+
+ SHA_Bytes(s, &c, 8);
+
+ for (i = 0; i < 5; i++) {
+ output[i * 4] = (s->h[i] >> 24) & 0xFF;
+ output[i * 4 + 1] = (s->h[i] >> 16) & 0xFF;
+ 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)
+{
+ SHA_State s;
+
+ SHA_Init(&s);
+ SHA_Bytes(&s, p, len);
+ SHA_Final(&s, output);
+}
+
+/*
+ * Thin abstraction for things where hashes are pluggable.
+ */
+
+static void *sha1_init(void)
+{
+ SHA_State *s;
+
+ s = snew(SHA_State);
+ SHA_Init(s);
+ return s;
+}
+
+static void sha1_bytes(void *handle, void *p, int len)
+{
+ SHA_State *s = handle;
+
+ SHA_Bytes(s, p, len);
+}
+
+static void sha1_final(void *handle, unsigned char *output)
+{
+ SHA_State *s = handle;
+
+ SHA_Final(s, output);
+ sfree(s);
+}
+
+const struct ssh_hash ssh_sha1 = {
+ sha1_init, sha1_bytes, sha1_final, 20, "SHA-1"
+};
+
+/* ----------------------------------------------------------------------
+ * The above is the SHA-1 algorithm itself. Now we implement the
+ * HMAC wrapper on it.
+ */
+
+static void *sha1_make_context(void)
+{
+ return snewn(3, SHA_State);
+}
+
+static void sha1_free_context(void *handle)
+{
+ sfree(handle);
+}
+
+static void sha1_key_internal(void *handle, unsigned char *key, int len)
+{
+ SHA_State *keys = (SHA_State *)handle;
+ unsigned char foo[64];
+ int i;
+
+ memset(foo, 0x36, 64);
+ for (i = 0; i < len && i < 64; i++)
+ foo[i] ^= key[i];
+ SHA_Init(&keys[0]);
+ SHA_Bytes(&keys[0], foo, 64);
+
+ memset(foo, 0x5C, 64);
+ 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 */
+}
+
+static void sha1_key(void *handle, unsigned char *key)
+{
+ sha1_key_internal(handle, key, 20);
+}
+
+static void sha1_key_buggy(void *handle, unsigned char *key)
+{
+ sha1_key_internal(handle, key, 16);
+}
+
+static void hmacsha1_start(void *handle)
+{
+ SHA_State *keys = (SHA_State *)handle;
+
+ keys[2] = keys[0]; /* structure copy */
+}
+
+static void hmacsha1_bytes(void *handle, unsigned char const *blk, int len)
+{
+ SHA_State *keys = (SHA_State *)handle;
+ SHA_Bytes(&keys[2], (void *)blk, len);
+}
+
+static void hmacsha1_genresult(void *handle, unsigned char *hmac)
+{
+ SHA_State *keys = (SHA_State *)handle;
+ SHA_State s;
+ unsigned char intermediate[20];
+
+ s = keys[2]; /* structure copy */
+ SHA_Final(&s, intermediate);
+ s = keys[1]; /* structure copy */
+ SHA_Bytes(&s, intermediate, 20);
+ SHA_Final(&s, hmac);
+}
+
+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);
+
+ hmacsha1_start(handle);
+ hmacsha1_bytes(handle, seqbuf, 4);
+ hmacsha1_bytes(handle, blk, len);
+ hmacsha1_genresult(handle, hmac);
+}
+
+static void sha1_generate(void *handle, unsigned char *blk, int len,
+ unsigned long seq)
+{
+ sha1_do_hmac(handle, blk, len, seq, blk + len);
+}
+
+static int hmacsha1_verresult(void *handle, unsigned char const *hmac)
+{
+ unsigned char correct[20];
+ hmacsha1_genresult(handle, correct);
+ return !memcmp(correct, hmac, 20);
+}
+
+static int sha1_verify(void *handle, unsigned char *blk, int len,
+ unsigned long seq)
+{
+ unsigned char correct[20];
+ sha1_do_hmac(handle, blk, len, seq, correct);
+ return !memcmp(correct, blk + len, 20);
+}
+
+static void hmacsha1_96_genresult(void *handle, unsigned char *hmac)
+{
+ unsigned char full[20];
+ hmacsha1_genresult(handle, full);
+ memcpy(hmac, full, 12);
+}
+
+static void sha1_96_generate(void *handle, unsigned char *blk, int len,
+ unsigned long seq)
+{
+ unsigned char full[20];
+ sha1_do_hmac(handle, blk, len, seq, full);
+ memcpy(blk + len, full, 12);
+}
+
+static int hmacsha1_96_verresult(void *handle, unsigned char const *hmac)
+{
+ unsigned char correct[20];
+ hmacsha1_genresult(handle, correct);
+ return !memcmp(correct, hmac, 12);
+}
+
+static int sha1_96_verify(void *handle, unsigned char *blk, int len,
+ unsigned long seq)
+{
+ unsigned char correct[20];
+ sha1_do_hmac(handle, blk, len, seq, correct);
+ return !memcmp(correct, blk + len, 12);
+}
+
+void hmac_sha1_simple(void *key, int keylen, void *data, int datalen,
+ unsigned char *output) {
+ SHA_State states[2];
+ unsigned char intermediate[20];
+
+ sha1_key_internal(states, key, keylen);
+ SHA_Bytes(&states[0], data, datalen);
+ SHA_Final(&states[0], intermediate);
+
+ SHA_Bytes(&states[1], intermediate, 20);
+ SHA_Final(&states[1], output);
+}
+
+const struct ssh_mac ssh_hmac_sha1 = {
+ sha1_make_context, sha1_free_context, sha1_key,
+ sha1_generate, sha1_verify,
+ hmacsha1_start, hmacsha1_bytes, hmacsha1_genresult, hmacsha1_verresult,
+ "hmac-sha1",
+ 20,
+ "HMAC-SHA1"
+};
+
+const struct ssh_mac ssh_hmac_sha1_96 = {
+ sha1_make_context, sha1_free_context, sha1_key,
+ sha1_96_generate, sha1_96_verify,
+ hmacsha1_start, hmacsha1_bytes,
+ hmacsha1_96_genresult, hmacsha1_96_verresult,
+ "hmac-sha1-96",
+ 12,
+ "HMAC-SHA1-96"
+};
+
+const struct ssh_mac ssh_hmac_sha1_buggy = {
+ sha1_make_context, sha1_free_context, sha1_key_buggy,
+ sha1_generate, sha1_verify,
+ hmacsha1_start, hmacsha1_bytes, hmacsha1_genresult, hmacsha1_verresult,
+ "hmac-sha1",
+ 20,
+ "bug-compatible HMAC-SHA1"
+};
+
+const struct ssh_mac ssh_hmac_sha1_96_buggy = {
+ sha1_make_context, sha1_free_context, sha1_key_buggy,
+ sha1_96_generate, sha1_96_verify,
+ hmacsha1_start, hmacsha1_bytes,
+ hmacsha1_96_genresult, hmacsha1_96_verresult,
+ "hmac-sha1-96",
+ 12,
+ "bug-compatible HMAC-SHA1-96"
+};
--- /dev/null
+/*
+ * Zlib (RFC1950 / RFC1951) compression for PuTTY.
+ *
+ * There will no doubt be criticism of my decision to reimplement
+ * Zlib compression from scratch instead of using the existing zlib
+ * code. People will cry `reinventing the wheel'; they'll claim
+ * that the `fundamental basis of OSS' is code reuse; they'll want
+ * to see a really good reason for me having chosen not to use the
+ * existing code.
+ *
+ * Well, here are my reasons. Firstly, I don't want to link the
+ * whole of zlib into the PuTTY binary; PuTTY is justifiably proud
+ * of its small size and I think zlib contains a lot of unnecessary
+ * baggage for the kind of compression that SSH requires.
+ *
+ * Secondly, I also don't like the alternative of using zlib.dll.
+ * Another thing PuTTY is justifiably proud of is its ease of
+ * installation, and the last thing I want to do is to start
+ * mandating DLLs. Not only that, but there are two _kinds_ of
+ * zlib.dll kicking around, one with C calling conventions on the
+ * exported functions and another with WINAPI conventions, and
+ * there would be a significant danger of getting the wrong one.
+ *
+ * Thirdly, there seems to be a difference of opinion on the IETF
+ * secsh mailing list about the correct way to round off a
+ * compressed packet and start the next. In particular, there's
+ * some talk of switching to a mechanism zlib isn't currently
+ * capable of supporting (see below for an explanation). Given that
+ * sort of uncertainty, I thought it might be better to have code
+ * that will support even the zlib-incompatible worst case.
+ *
+ * Fourthly, it's a _second implementation_. Second implementations
+ * are fundamentally a Good Thing in standardisation efforts. The
+ * difference of opinion mentioned above has arisen _precisely_
+ * because there has been only one zlib implementation and
+ * everybody has used it. I don't intend that this should happen
+ * again.
+ */
+
+#include <stdlib.h>
+#include <assert.h>
+
+#ifdef ZLIB_STANDALONE
+
+/*
+ * This module also makes a handy zlib decoding tool for when
+ * you're picking apart Zip files or PDFs or PNGs. If you compile
+ * it with ZLIB_STANDALONE defined, it builds on its own and
+ * becomes a command-line utility.
+ *
+ * Therefore, here I provide a self-contained implementation of the
+ * macros required from the rest of the PuTTY sources.
+ */
+#define snew(type) ( (type *) malloc(sizeof(type)) )
+#define snewn(n, type) ( (type *) malloc((n) * sizeof(type)) )
+#define sresize(x, n, type) ( (type *) realloc((x), (n) * sizeof(type)) )
+#define sfree(x) ( free((x)) )
+
+#else
+#include "ssh.h"
+#endif
+
+#ifndef FALSE
+#define FALSE 0
+#define TRUE (!FALSE)
+#endif
+
+/* ----------------------------------------------------------------------
+ * Basic LZ77 code. This bit is designed modularly, so it could be
+ * ripped out and used in a different LZ77 compressor. Go to it,
+ * and good luck :-)
+ */
+
+struct LZ77InternalContext;
+struct LZ77Context {
+ struct LZ77InternalContext *ictx;
+ void *userdata;
+ void (*literal) (struct LZ77Context * ctx, unsigned char c);
+ void (*match) (struct LZ77Context * ctx, int distance, int len);
+};
+
+/*
+ * Initialise the private fields of an LZ77Context. It's up to the
+ * user to initialise the public fields.
+ */
+static int lz77_init(struct LZ77Context *ctx);
+
+/*
+ * Supply data to be compressed. Will update the private fields of
+ * the LZ77Context, and will call literal() and match() to output.
+ * If `compress' is FALSE, it will never emit a match, but will
+ * instead call literal() for everything.
+ */
+static void lz77_compress(struct LZ77Context *ctx,
+ unsigned char *data, int len, int compress);
+
+/*
+ * Modifiable parameters.
+ */
+#define WINSIZE 32768 /* window size. Must be power of 2! */
+#define HASHMAX 2039 /* one more than max hash value */
+#define MAXMATCH 32 /* how many matches we track */
+#define HASHCHARS 3 /* how many chars make a hash */
+
+/*
+ * This compressor takes a less slapdash approach than the
+ * gzip/zlib one. Rather than allowing our hash chains to fall into
+ * disuse near the far end, we keep them doubly linked so we can
+ * _find_ the far end, and then every time we add a new byte to the
+ * window (thus rolling round by one and removing the previous
+ * byte), we can carefully remove the hash chain entry.
+ */
+
+#define INVALID -1 /* invalid hash _and_ invalid offset */
+struct WindowEntry {
+ short next, prev; /* array indices within the window */
+ short hashval;
+};
+
+struct HashEntry {
+ short first; /* window index of first in chain */
+};
+
+struct Match {
+ int distance, len;
+};
+
+struct LZ77InternalContext {
+ struct WindowEntry win[WINSIZE];
+ unsigned char data[WINSIZE];
+ int winpos;
+ struct HashEntry hashtab[HASHMAX];
+ unsigned char pending[HASHCHARS];
+ int npending;
+};
+
+static int lz77_hash(unsigned char *data)
+{
+ return (257 * data[0] + 263 * data[1] + 269 * data[2]) % HASHMAX;
+}
+
+static int lz77_init(struct LZ77Context *ctx)
+{
+ struct LZ77InternalContext *st;
+ int i;
+
+ st = snew(struct LZ77InternalContext);
+ if (!st)
+ return 0;
+
+ ctx->ictx = st;
+
+ for (i = 0; i < WINSIZE; i++)
+ st->win[i].next = st->win[i].prev = st->win[i].hashval = INVALID;
+ for (i = 0; i < HASHMAX; i++)
+ st->hashtab[i].first = INVALID;
+ st->winpos = 0;
+
+ st->npending = 0;
+
+ return 1;
+}
+
+static void lz77_advance(struct LZ77InternalContext *st,
+ unsigned char c, int hash)
+{
+ int off;
+
+ /*
+ * Remove the hash entry at winpos from the tail of its chain,
+ * or empty the chain if it's the only thing on the chain.
+ */
+ if (st->win[st->winpos].prev != INVALID) {
+ st->win[st->win[st->winpos].prev].next = INVALID;
+ } else if (st->win[st->winpos].hashval != INVALID) {
+ st->hashtab[st->win[st->winpos].hashval].first = INVALID;
+ }
+
+ /*
+ * Create a new entry at winpos and add it to the head of its
+ * hash chain.
+ */
+ st->win[st->winpos].hashval = hash;
+ st->win[st->winpos].prev = INVALID;
+ off = st->win[st->winpos].next = st->hashtab[hash].first;
+ st->hashtab[hash].first = st->winpos;
+ if (off != INVALID)
+ st->win[off].prev = st->winpos;
+ st->data[st->winpos] = c;
+
+ /*
+ * Advance the window pointer.
+ */
+ st->winpos = (st->winpos + 1) & (WINSIZE - 1);
+}
+
+#define CHARAT(k) ( (k)<0 ? st->data[(st->winpos+k)&(WINSIZE-1)] : data[k] )
+
+static void lz77_compress(struct LZ77Context *ctx,
+ unsigned char *data, int len, int compress)
+{
+ struct LZ77InternalContext *st = ctx->ictx;
+ int i, hash, distance, off, nmatch, matchlen, advance;
+ struct Match defermatch, matches[MAXMATCH];
+ int deferchr;
+
+ /*
+ * Add any pending characters from last time to the window. (We
+ * might not be able to.)
+ */
+ for (i = 0; i < st->npending; i++) {
+ unsigned char foo[HASHCHARS];
+ int j;
+ if (len + st->npending - i < HASHCHARS) {
+ /* Update the pending array. */
+ for (j = i; j < st->npending; j++)
+ st->pending[j - i] = st->pending[j];
+ break;
+ }
+ for (j = 0; j < HASHCHARS; j++)
+ foo[j] = (i + j < st->npending ? st->pending[i + j] :
+ data[i + j - st->npending]);
+ lz77_advance(st, foo[0], lz77_hash(foo));
+ }
+ st->npending -= i;
+
+ defermatch.distance = 0; /* appease compiler */
+ defermatch.len = 0;
+ deferchr = '\0';
+ while (len > 0) {
+
+ /* Don't even look for a match, if we're not compressing. */
+ if (compress && len >= HASHCHARS) {
+ /*
+ * Hash the next few characters.
+ */
+ hash = lz77_hash(data);
+
+ /*
+ * Look the hash up in the corresponding hash chain and see
+ * what we can find.
+ */
+ nmatch = 0;
+ for (off = st->hashtab[hash].first;
+ off != INVALID; off = st->win[off].next) {
+ /* distance = 1 if off == st->winpos-1 */
+ /* distance = WINSIZE if off == st->winpos */
+ distance =
+ WINSIZE - (off + WINSIZE - st->winpos) % WINSIZE;
+ for (i = 0; i < HASHCHARS; i++)
+ if (CHARAT(i) != CHARAT(i - distance))
+ break;
+ if (i == HASHCHARS) {
+ matches[nmatch].distance = distance;
+ matches[nmatch].len = 3;
+ if (++nmatch >= MAXMATCH)
+ break;
+ }
+ }
+ } else {
+ nmatch = 0;
+ hash = INVALID;
+ }
+
+ if (nmatch > 0) {
+ /*
+ * We've now filled up matches[] with nmatch potential
+ * matches. Follow them down to find the longest. (We
+ * assume here that it's always worth favouring a
+ * longer match over a shorter one.)
+ */
+ matchlen = HASHCHARS;
+ while (matchlen < len) {
+ int j;
+ for (i = j = 0; i < nmatch; i++) {
+ if (CHARAT(matchlen) ==
+ CHARAT(matchlen - matches[i].distance)) {
+ matches[j++] = matches[i];
+ }
+ }
+ if (j == 0)
+ break;
+ matchlen++;
+ nmatch = j;
+ }
+
+ /*
+ * We've now got all the longest matches. We favour the
+ * shorter distances, which means we go with matches[0].
+ * So see if we want to defer it or throw it away.
+ */
+ matches[0].len = matchlen;
+ if (defermatch.len > 0) {
+ if (matches[0].len > defermatch.len + 1) {
+ /* We have a better match. Emit the deferred char,
+ * and defer this match. */
+ ctx->literal(ctx, (unsigned char) deferchr);
+ defermatch = matches[0];
+ deferchr = data[0];
+ advance = 1;
+ } else {
+ /* We don't have a better match. Do the deferred one. */
+ ctx->match(ctx, defermatch.distance, defermatch.len);
+ advance = defermatch.len - 1;
+ defermatch.len = 0;
+ }
+ } else {
+ /* There was no deferred match. Defer this one. */
+ defermatch = matches[0];
+ deferchr = data[0];
+ advance = 1;
+ }
+ } else {
+ /*
+ * We found no matches. Emit the deferred match, if
+ * any; otherwise emit a literal.
+ */
+ if (defermatch.len > 0) {
+ ctx->match(ctx, defermatch.distance, defermatch.len);
+ advance = defermatch.len - 1;
+ defermatch.len = 0;
+ } else {
+ ctx->literal(ctx, data[0]);
+ advance = 1;
+ }
+ }
+
+ /*
+ * Now advance the position by `advance' characters,
+ * keeping the window and hash chains consistent.
+ */
+ while (advance > 0) {
+ if (len >= HASHCHARS) {
+ lz77_advance(st, *data, lz77_hash(data));
+ } else {
+ st->pending[st->npending++] = *data;
+ }
+ data++;
+ len--;
+ advance--;
+ }
+ }
+}
+
+/* ----------------------------------------------------------------------
+ * Zlib compression. We always use the static Huffman tree option.
+ * Mostly this is because it's hard to scan a block in advance to
+ * work out better trees; dynamic trees are great when you're
+ * compressing a large file under no significant time constraint,
+ * but when you're compressing little bits in real time, things get
+ * hairier.
+ *
+ * I suppose it's possible that I could compute Huffman trees based
+ * on the frequencies in the _previous_ block, as a sort of
+ * heuristic, but I'm not confident that the gain would balance out
+ * having to transmit the trees.
+ */
+
+struct Outbuf {
+ unsigned char *outbuf;
+ int outlen, outsize;
+ unsigned long outbits;
+ int noutbits;
+ int firstblock;
+ int comp_disabled;
+};
+
+static void outbits(struct Outbuf *out, unsigned long bits, int nbits)
+{
+ assert(out->noutbits + nbits <= 32);
+ out->outbits |= bits << out->noutbits;
+ out->noutbits += nbits;
+ while (out->noutbits >= 8) {
+ if (out->outlen >= out->outsize) {
+ out->outsize = out->outlen + 64;
+ out->outbuf = sresize(out->outbuf, out->outsize, unsigned char);
+ }
+ out->outbuf[out->outlen++] = (unsigned char) (out->outbits & 0xFF);
+ out->outbits >>= 8;
+ out->noutbits -= 8;
+ }
+}
+
+static const unsigned char mirrorbytes[256] = {
+ 0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0,
+ 0x10, 0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70, 0xf0,
+ 0x08, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8,
+ 0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8,
+ 0x04, 0x84, 0x44, 0xc4, 0x24, 0xa4, 0x64, 0xe4,
+ 0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4,
+ 0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec,
+ 0x1c, 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc,
+ 0x02, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2,
+ 0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2,
+ 0x0a, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, 0xea,
+ 0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa,
+ 0x06, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6,
+ 0x16, 0x96, 0x56, 0xd6, 0x36, 0xb6, 0x76, 0xf6,
+ 0x0e, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee,
+ 0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe,
+ 0x01, 0x81, 0x41, 0xc1, 0x21, 0xa1, 0x61, 0xe1,
+ 0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1,
+ 0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9,
+ 0x19, 0x99, 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9,
+ 0x05, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5,
+ 0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5,
+ 0x0d, 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed,
+ 0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd,
+ 0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3,
+ 0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3,
+ 0x0b, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb,
+ 0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb,
+ 0x07, 0x87, 0x47, 0xc7, 0x27, 0xa7, 0x67, 0xe7,
+ 0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7,
+ 0x0f, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef,
+ 0x1f, 0x9f, 0x5f, 0xdf, 0x3f, 0xbf, 0x7f, 0xff,
+};
+
+typedef struct {
+ short code, extrabits;
+ int min, max;
+} coderecord;
+
+static const coderecord lencodes[] = {
+ {257, 0, 3, 3},
+ {258, 0, 4, 4},
+ {259, 0, 5, 5},
+ {260, 0, 6, 6},
+ {261, 0, 7, 7},
+ {262, 0, 8, 8},
+ {263, 0, 9, 9},
+ {264, 0, 10, 10},
+ {265, 1, 11, 12},
+ {266, 1, 13, 14},
+ {267, 1, 15, 16},
+ {268, 1, 17, 18},
+ {269, 2, 19, 22},
+ {270, 2, 23, 26},
+ {271, 2, 27, 30},
+ {272, 2, 31, 34},
+ {273, 3, 35, 42},
+ {274, 3, 43, 50},
+ {275, 3, 51, 58},
+ {276, 3, 59, 66},
+ {277, 4, 67, 82},
+ {278, 4, 83, 98},
+ {279, 4, 99, 114},
+ {280, 4, 115, 130},
+ {281, 5, 131, 162},
+ {282, 5, 163, 194},
+ {283, 5, 195, 226},
+ {284, 5, 227, 257},
+ {285, 0, 258, 258},
+};
+
+static const coderecord distcodes[] = {
+ {0, 0, 1, 1},
+ {1, 0, 2, 2},
+ {2, 0, 3, 3},
+ {3, 0, 4, 4},
+ {4, 1, 5, 6},
+ {5, 1, 7, 8},
+ {6, 2, 9, 12},
+ {7, 2, 13, 16},
+ {8, 3, 17, 24},
+ {9, 3, 25, 32},
+ {10, 4, 33, 48},
+ {11, 4, 49, 64},
+ {12, 5, 65, 96},
+ {13, 5, 97, 128},
+ {14, 6, 129, 192},
+ {15, 6, 193, 256},
+ {16, 7, 257, 384},
+ {17, 7, 385, 512},
+ {18, 8, 513, 768},
+ {19, 8, 769, 1024},
+ {20, 9, 1025, 1536},
+ {21, 9, 1537, 2048},
+ {22, 10, 2049, 3072},
+ {23, 10, 3073, 4096},
+ {24, 11, 4097, 6144},
+ {25, 11, 6145, 8192},
+ {26, 12, 8193, 12288},
+ {27, 12, 12289, 16384},
+ {28, 13, 16385, 24576},
+ {29, 13, 24577, 32768},
+};
+
+static void zlib_literal(struct LZ77Context *ectx, unsigned char c)
+{
+ struct Outbuf *out = (struct Outbuf *) ectx->userdata;
+
+ if (out->comp_disabled) {
+ /*
+ * We're in an uncompressed block, so just output the byte.
+ */
+ outbits(out, c, 8);
+ return;
+ }
+
+ if (c <= 143) {
+ /* 0 through 143 are 8 bits long starting at 00110000. */
+ outbits(out, mirrorbytes[0x30 + c], 8);
+ } else {
+ /* 144 through 255 are 9 bits long starting at 110010000. */
+ outbits(out, 1 + 2 * mirrorbytes[0x90 - 144 + c], 9);
+ }
+}
+
+static void zlib_match(struct LZ77Context *ectx, int distance, int len)
+{
+ const coderecord *d, *l;
+ int i, j, k;
+ struct Outbuf *out = (struct Outbuf *) ectx->userdata;
+
+ assert(!out->comp_disabled);
+
+ while (len > 0) {
+ int thislen;
+
+ /*
+ * We can transmit matches of lengths 3 through 258
+ * inclusive. So if len exceeds 258, we must transmit in
+ * several steps, with 258 or less in each step.
+ *
+ * Specifically: if len >= 261, we can transmit 258 and be
+ * sure of having at least 3 left for the next step. And if
+ * len <= 258, we can just transmit len. But if len == 259
+ * or 260, we must transmit len-3.
+ */
+ thislen = (len > 260 ? 258 : len <= 258 ? len : len - 3);
+ len -= thislen;
+
+ /*
+ * Binary-search to find which length code we're
+ * transmitting.
+ */
+ i = -1;
+ j = sizeof(lencodes) / sizeof(*lencodes);
+ while (1) {
+ assert(j - i >= 2);
+ k = (j + i) / 2;
+ if (thislen < lencodes[k].min)
+ j = k;
+ else if (thislen > lencodes[k].max)
+ i = k;
+ else {
+ l = &lencodes[k];
+ break; /* found it! */
+ }
+ }
+
+ /*
+ * Transmit the length code. 256-279 are seven bits
+ * starting at 0000000; 280-287 are eight bits starting at
+ * 11000000.
+ */
+ if (l->code <= 279) {
+ outbits(out, mirrorbytes[(l->code - 256) * 2], 7);
+ } else {
+ outbits(out, mirrorbytes[0xc0 - 280 + l->code], 8);
+ }
+
+ /*
+ * Transmit the extra bits.
+ */
+ if (l->extrabits)
+ outbits(out, thislen - l->min, l->extrabits);
+
+ /*
+ * Binary-search to find which distance code we're
+ * transmitting.
+ */
+ i = -1;
+ j = sizeof(distcodes) / sizeof(*distcodes);
+ while (1) {
+ assert(j - i >= 2);
+ k = (j + i) / 2;
+ if (distance < distcodes[k].min)
+ j = k;
+ else if (distance > distcodes[k].max)
+ i = k;
+ else {
+ d = &distcodes[k];
+ break; /* found it! */
+ }
+ }
+
+ /*
+ * Transmit the distance code. Five bits starting at 00000.
+ */
+ outbits(out, mirrorbytes[d->code * 8], 5);
+
+ /*
+ * Transmit the extra bits.
+ */
+ if (d->extrabits)
+ outbits(out, distance - d->min, d->extrabits);
+ }
+}
+
+void *zlib_compress_init(void)
+{
+ struct Outbuf *out;
+ struct LZ77Context *ectx = snew(struct LZ77Context);
+
+ lz77_init(ectx);
+ ectx->literal = zlib_literal;
+ ectx->match = zlib_match;
+
+ out = snew(struct Outbuf);
+ out->outbits = out->noutbits = 0;
+ out->firstblock = 1;
+ out->comp_disabled = FALSE;
+ ectx->userdata = out;
+
+ return ectx;
+}
+
+void zlib_compress_cleanup(void *handle)
+{
+ struct LZ77Context *ectx = (struct LZ77Context *)handle;
+ sfree(ectx->userdata);
+ sfree(ectx->ictx);
+ sfree(ectx);
+}
+
+/*
+ * Turn off actual LZ77 analysis for one block, to facilitate
+ * construction of a precise-length IGNORE packet. Returns the
+ * length adjustment (which is only valid for packets < 65536
+ * bytes, but that seems reasonable enough).
+ */
+static int zlib_disable_compression(void *handle)
+{
+ struct LZ77Context *ectx = (struct LZ77Context *)handle;
+ struct Outbuf *out = (struct Outbuf *) ectx->userdata;
+ int n;
+
+ out->comp_disabled = TRUE;
+
+ n = 0;
+ /*
+ * If this is the first block, we will start by outputting two
+ * header bytes, and then three bits to begin an uncompressed
+ * block. This will cost three bytes (because we will start on
+ * a byte boundary, this is certain).
+ */
+ if (out->firstblock) {
+ n = 3;
+ } else {
+ /*
+ * Otherwise, we will output seven bits to close the
+ * previous static block, and _then_ three bits to begin an
+ * uncompressed block, and then flush the current byte.
+ * This may cost two bytes or three, depending on noutbits.
+ */
+ n += (out->noutbits + 10) / 8;
+ }
+
+ /*
+ * Now we output four bytes for the length / ~length pair in
+ * the uncompressed block.
+ */
+ n += 4;
+
+ return n;
+}
+
+int zlib_compress_block(void *handle, unsigned char *block, int len,
+ unsigned char **outblock, int *outlen)
+{
+ struct LZ77Context *ectx = (struct LZ77Context *)handle;
+ struct Outbuf *out = (struct Outbuf *) ectx->userdata;
+ int in_block;
+
+ out->outbuf = NULL;
+ out->outlen = out->outsize = 0;
+
+ /*
+ * If this is the first block, output the Zlib (RFC1950) header
+ * bytes 78 9C. (Deflate compression, 32K window size, default
+ * algorithm.)
+ */
+ if (out->firstblock) {
+ outbits(out, 0x9C78, 16);
+ out->firstblock = 0;
+
+ in_block = FALSE;
+ } else
+ in_block = TRUE;
+
+ if (out->comp_disabled) {
+ if (in_block)
+ outbits(out, 0, 7); /* close static block */
+
+ while (len > 0) {
+ int blen = (len < 65535 ? len : 65535);
+
+ /*
+ * Start a Deflate (RFC1951) uncompressed block. We
+ * transmit a zero bit (BFINAL=0), followed by two more
+ * zero bits (BTYPE=00). Of course these are in the
+ * wrong order (00 0), not that it matters.
+ */
+ outbits(out, 0, 3);
+
+ /*
+ * Output zero bits to align to a byte boundary.
+ */
+ if (out->noutbits)
+ outbits(out, 0, 8 - out->noutbits);
+
+ /*
+ * Output the block length, and then its one's
+ * complement. They're little-endian, so all we need to
+ * do is pass them straight to outbits() with bit count
+ * 16.
+ */
+ outbits(out, blen, 16);
+ outbits(out, blen ^ 0xFFFF, 16);
+
+ /*
+ * Do the `compression': we need to pass the data to
+ * lz77_compress so that it will be taken into account
+ * for subsequent (distance,length) pairs. But
+ * lz77_compress is passed FALSE, which means it won't
+ * actually find (or even look for) any matches; so
+ * every character will be passed straight to
+ * zlib_literal which will spot out->comp_disabled and
+ * emit in the uncompressed format.
+ */
+ lz77_compress(ectx, block, blen, FALSE);
+
+ len -= blen;
+ block += blen;
+ }
+ outbits(out, 2, 3); /* open new block */
+ } else {
+ if (!in_block) {
+ /*
+ * Start a Deflate (RFC1951) fixed-trees block. We
+ * transmit a zero bit (BFINAL=0), followed by a zero
+ * bit and a one bit (BTYPE=01). Of course these are in
+ * the wrong order (01 0).
+ */
+ outbits(out, 2, 3);
+ }
+
+ /*
+ * Do the compression.
+ */
+ lz77_compress(ectx, block, len, TRUE);
+
+ /*
+ * End the block (by transmitting code 256, which is
+ * 0000000 in fixed-tree mode), and transmit some empty
+ * blocks to ensure we have emitted the byte containing the
+ * last piece of genuine data. There are three ways we can
+ * do this:
+ *
+ * - Minimal flush. Output end-of-block and then open a
+ * new static block. This takes 9 bits, which is
+ * guaranteed to flush out the last genuine code in the
+ * closed block; but allegedly zlib can't handle it.
+ *
+ * - Zlib partial flush. Output EOB, open and close an
+ * empty static block, and _then_ open the new block.
+ * This is the best zlib can handle.
+ *
+ * - Zlib sync flush. Output EOB, then an empty
+ * _uncompressed_ block (000, then sync to byte
+ * boundary, then send bytes 00 00 FF FF). Then open the
+ * new block.
+ *
+ * For the moment, we will use Zlib partial flush.
+ */
+ outbits(out, 0, 7); /* close block */
+ outbits(out, 2, 3 + 7); /* empty static block */
+ outbits(out, 2, 3); /* open new block */
+ }
+
+ out->comp_disabled = FALSE;
+
+ *outblock = out->outbuf;
+ *outlen = out->outlen;
+
+ return 1;
+}
+
+/* ----------------------------------------------------------------------
+ * Zlib decompression. Of course, even though our compressor always
+ * uses static trees, our _decompressor_ has to be capable of
+ * handling dynamic trees if it sees them.
+ */
+
+/*
+ * The way we work the Huffman decode is to have a table lookup on
+ * the first N bits of the input stream (in the order they arrive,
+ * of course, i.e. the first bit of the Huffman code is in bit 0).
+ * Each table entry lists the number of bits to consume, plus
+ * either an output code or a pointer to a secondary table.
+ */
+struct zlib_table;
+struct zlib_tableentry;
+
+struct zlib_tableentry {
+ unsigned char nbits;
+ short code;
+ struct zlib_table *nexttable;
+};
+
+struct zlib_table {
+ int mask; /* mask applied to input bit stream */
+ struct zlib_tableentry *table;
+};
+
+#define MAXCODELEN 16
+#define MAXSYMS 288
+
+/*
+ * Build a single-level decode table for elements
+ * [minlength,maxlength) of the provided code/length tables, and
+ * recurse to build subtables.
+ */
+static struct zlib_table *zlib_mkonetab(int *codes, unsigned char *lengths,
+ int nsyms,
+ int pfx, int pfxbits, int bits)
+{
+ struct zlib_table *tab = snew(struct zlib_table);
+ int pfxmask = (1 << pfxbits) - 1;
+ int nbits, i, j, code;
+
+ tab->table = snewn(1 << bits, struct zlib_tableentry);
+ tab->mask = (1 << bits) - 1;
+
+ for (code = 0; code <= tab->mask; code++) {
+ tab->table[code].code = -1;
+ tab->table[code].nbits = 0;
+ tab->table[code].nexttable = NULL;
+ }
+
+ for (i = 0; i < nsyms; i++) {
+ if (lengths[i] <= pfxbits || (codes[i] & pfxmask) != pfx)
+ continue;
+ code = (codes[i] >> pfxbits) & tab->mask;
+ for (j = code; j <= tab->mask; j += 1 << (lengths[i] - pfxbits)) {
+ tab->table[j].code = i;
+ nbits = lengths[i] - pfxbits;
+ if (tab->table[j].nbits < nbits)
+ tab->table[j].nbits = nbits;
+ }
+ }
+ for (code = 0; code <= tab->mask; code++) {
+ if (tab->table[code].nbits <= bits)
+ continue;
+ /* Generate a subtable. */
+ tab->table[code].code = -1;
+ nbits = tab->table[code].nbits - bits;
+ if (nbits > 7)
+ nbits = 7;
+ tab->table[code].nbits = bits;
+ tab->table[code].nexttable = zlib_mkonetab(codes, lengths, nsyms,
+ pfx | (code << pfxbits),
+ pfxbits + bits, nbits);
+ }
+
+ return tab;
+}
+
+/*
+ * Build a decode table, given a set of Huffman tree lengths.
+ */
+static struct zlib_table *zlib_mktable(unsigned char *lengths,
+ int nlengths)
+{
+ int count[MAXCODELEN], startcode[MAXCODELEN], codes[MAXSYMS];
+ int code, maxlen;
+ int i, j;
+
+ /* Count the codes of each length. */
+ maxlen = 0;
+ for (i = 1; i < MAXCODELEN; i++)
+ count[i] = 0;
+ for (i = 0; i < nlengths; i++) {
+ count[lengths[i]]++;
+ if (maxlen < lengths[i])
+ maxlen = lengths[i];
+ }
+ /* Determine the starting code for each length block. */
+ code = 0;
+ for (i = 1; i < MAXCODELEN; i++) {
+ startcode[i] = code;
+ code += count[i];
+ code <<= 1;
+ }
+ /* Determine the code for each symbol. Mirrored, of course. */
+ for (i = 0; i < nlengths; i++) {
+ code = startcode[lengths[i]]++;
+ codes[i] = 0;
+ for (j = 0; j < lengths[i]; j++) {
+ codes[i] = (codes[i] << 1) | (code & 1);
+ code >>= 1;
+ }
+ }
+
+ /*
+ * Now we have the complete list of Huffman codes. Build a
+ * table.
+ */
+ return zlib_mkonetab(codes, lengths, nlengths, 0, 0,
+ maxlen < 9 ? maxlen : 9);
+}
+
+static int zlib_freetable(struct zlib_table **ztab)
+{
+ struct zlib_table *tab;
+ int code;
+
+ if (ztab == NULL)
+ return -1;
+
+ if (*ztab == NULL)
+ return 0;
+
+ tab = *ztab;
+
+ for (code = 0; code <= tab->mask; code++)
+ if (tab->table[code].nexttable != NULL)
+ zlib_freetable(&tab->table[code].nexttable);
+
+ sfree(tab->table);
+ tab->table = NULL;
+
+ sfree(tab);
+ *ztab = NULL;
+
+ return (0);
+}
+
+struct zlib_decompress_ctx {
+ struct zlib_table *staticlentable, *staticdisttable;
+ struct zlib_table *currlentable, *currdisttable, *lenlentable;
+ enum {
+ START, OUTSIDEBLK,
+ TREES_HDR, TREES_LENLEN, TREES_LEN, TREES_LENREP,
+ INBLK, GOTLENSYM, GOTLEN, GOTDISTSYM,
+ UNCOMP_LEN, UNCOMP_NLEN, UNCOMP_DATA
+ } state;
+ int sym, hlit, hdist, hclen, lenptr, lenextrabits, lenaddon, len,
+ lenrep;
+ int uncomplen;
+ unsigned char lenlen[19];
+ unsigned char lengths[286 + 32];
+ unsigned long bits;
+ int nbits;
+ unsigned char window[WINSIZE];
+ int winpos;
+ unsigned char *outblk;
+ int outlen, outsize;
+};
+
+void *zlib_decompress_init(void)
+{
+ struct zlib_decompress_ctx *dctx = snew(struct zlib_decompress_ctx);
+ unsigned char lengths[288];
+
+ memset(lengths, 8, 144);
+ memset(lengths + 144, 9, 256 - 144);
+ memset(lengths + 256, 7, 280 - 256);
+ memset(lengths + 280, 8, 288 - 280);
+ dctx->staticlentable = zlib_mktable(lengths, 288);
+ memset(lengths, 5, 32);
+ dctx->staticdisttable = zlib_mktable(lengths, 32);
+ dctx->state = START; /* even before header */
+ dctx->currlentable = dctx->currdisttable = dctx->lenlentable = NULL;
+ dctx->bits = 0;
+ dctx->nbits = 0;
+ dctx->winpos = 0;
+
+ return dctx;
+}
+
+void zlib_decompress_cleanup(void *handle)
+{
+ struct zlib_decompress_ctx *dctx = (struct zlib_decompress_ctx *)handle;
+
+ if (dctx->currlentable && dctx->currlentable != dctx->staticlentable)
+ zlib_freetable(&dctx->currlentable);
+ if (dctx->currdisttable && dctx->currdisttable != dctx->staticdisttable)
+ zlib_freetable(&dctx->currdisttable);
+ if (dctx->lenlentable)
+ zlib_freetable(&dctx->lenlentable);
+ zlib_freetable(&dctx->staticlentable);
+ zlib_freetable(&dctx->staticdisttable);
+ sfree(dctx);
+}
+
+static int zlib_huflookup(unsigned long *bitsp, int *nbitsp,
+ struct zlib_table *tab)
+{
+ unsigned long bits = *bitsp;
+ int nbits = *nbitsp;
+ while (1) {
+ struct zlib_tableentry *ent;
+ ent = &tab->table[bits & tab->mask];
+ if (ent->nbits > nbits)
+ return -1; /* not enough data */
+ bits >>= ent->nbits;
+ nbits -= ent->nbits;
+ if (ent->code == -1)
+ tab = ent->nexttable;
+ else {
+ *bitsp = bits;
+ *nbitsp = nbits;
+ return ent->code;
+ }
+
+ if (!tab) {
+ /*
+ * There was a missing entry in the table, presumably
+ * due to an invalid Huffman table description, and the
+ * subsequent data has attempted to use the missing
+ * entry. Return a decoding failure.
+ */
+ return -2;
+ }
+ }
+}
+
+static void zlib_emit_char(struct zlib_decompress_ctx *dctx, int c)
+{
+ dctx->window[dctx->winpos] = c;
+ dctx->winpos = (dctx->winpos + 1) & (WINSIZE - 1);
+ if (dctx->outlen >= dctx->outsize) {
+ dctx->outsize = dctx->outlen + 512;
+ dctx->outblk = sresize(dctx->outblk, dctx->outsize, unsigned char);
+ }
+ dctx->outblk[dctx->outlen++] = c;
+}
+
+#define EATBITS(n) ( dctx->nbits -= (n), dctx->bits >>= (n) )
+
+int zlib_decompress_block(void *handle, unsigned char *block, int len,
+ unsigned char **outblock, int *outlen)
+{
+ struct zlib_decompress_ctx *dctx = (struct zlib_decompress_ctx *)handle;
+ const coderecord *rec;
+ int code, blktype, rep, dist, nlen, header;
+ static const unsigned char lenlenmap[] = {
+ 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15
+ };
+
+ dctx->outblk = snewn(256, unsigned char);
+ dctx->outsize = 256;
+ dctx->outlen = 0;
+
+ while (len > 0 || dctx->nbits > 0) {
+ while (dctx->nbits < 24 && len > 0) {
+ dctx->bits |= (*block++) << dctx->nbits;
+ dctx->nbits += 8;
+ len--;
+ }
+ switch (dctx->state) {
+ case START:
+ /* Expect 16-bit zlib header. */
+ if (dctx->nbits < 16)
+ goto finished; /* done all we can */
+
+ /*
+ * The header is stored as a big-endian 16-bit integer,
+ * in contrast to the general little-endian policy in
+ * the rest of the format :-(
+ */
+ header = (((dctx->bits & 0xFF00) >> 8) |
+ ((dctx->bits & 0x00FF) << 8));
+ EATBITS(16);
+
+ /*
+ * Check the header:
+ *
+ * - bits 8-11 should be 1000 (Deflate/RFC1951)
+ * - bits 12-15 should be at most 0111 (window size)
+ * - bit 5 should be zero (no dictionary present)
+ * - we don't care about bits 6-7 (compression rate)
+ * - bits 0-4 should be set up to make the whole thing
+ * a multiple of 31 (checksum).
+ */
+ if ((header & 0x0F00) != 0x0800 ||
+ (header & 0xF000) > 0x7000 ||
+ (header & 0x0020) != 0x0000 ||
+ (header % 31) != 0)
+ goto decode_error;
+
+ dctx->state = OUTSIDEBLK;
+ break;
+ case OUTSIDEBLK:
+ /* Expect 3-bit block header. */
+ if (dctx->nbits < 3)
+ goto finished; /* done all we can */
+ EATBITS(1);
+ blktype = dctx->bits & 3;
+ EATBITS(2);
+ if (blktype == 0) {
+ int to_eat = dctx->nbits & 7;
+ dctx->state = UNCOMP_LEN;
+ EATBITS(to_eat); /* align to byte boundary */
+ } else if (blktype == 1) {
+ dctx->currlentable = dctx->staticlentable;
+ dctx->currdisttable = dctx->staticdisttable;
+ dctx->state = INBLK;
+ } else if (blktype == 2) {
+ dctx->state = TREES_HDR;
+ }
+ break;
+ case TREES_HDR:
+ /*
+ * Dynamic block header. Five bits of HLIT, five of
+ * HDIST, four of HCLEN.
+ */
+ if (dctx->nbits < 5 + 5 + 4)
+ goto finished; /* done all we can */
+ dctx->hlit = 257 + (dctx->bits & 31);
+ EATBITS(5);
+ dctx->hdist = 1 + (dctx->bits & 31);
+ EATBITS(5);
+ dctx->hclen = 4 + (dctx->bits & 15);
+ EATBITS(4);
+ dctx->lenptr = 0;
+ dctx->state = TREES_LENLEN;
+ memset(dctx->lenlen, 0, sizeof(dctx->lenlen));
+ break;
+ case TREES_LENLEN:
+ if (dctx->nbits < 3)
+ goto finished;
+ while (dctx->lenptr < dctx->hclen && dctx->nbits >= 3) {
+ dctx->lenlen[lenlenmap[dctx->lenptr++]] =
+ (unsigned char) (dctx->bits & 7);
+ EATBITS(3);
+ }
+ if (dctx->lenptr == dctx->hclen) {
+ dctx->lenlentable = zlib_mktable(dctx->lenlen, 19);
+ dctx->state = TREES_LEN;
+ dctx->lenptr = 0;
+ }
+ break;
+ case TREES_LEN:
+ if (dctx->lenptr >= dctx->hlit + dctx->hdist) {
+ dctx->currlentable = zlib_mktable(dctx->lengths, dctx->hlit);
+ dctx->currdisttable = zlib_mktable(dctx->lengths + dctx->hlit,
+ dctx->hdist);
+ zlib_freetable(&dctx->lenlentable);
+ dctx->lenlentable = NULL;
+ dctx->state = INBLK;
+ break;
+ }
+ code =
+ zlib_huflookup(&dctx->bits, &dctx->nbits, dctx->lenlentable);
+ if (code == -1)
+ goto finished;
+ if (code == -2)
+ goto decode_error;
+ if (code < 16)
+ dctx->lengths[dctx->lenptr++] = code;
+ else {
+ dctx->lenextrabits = (code == 16 ? 2 : code == 17 ? 3 : 7);
+ dctx->lenaddon = (code == 18 ? 11 : 3);
+ dctx->lenrep = (code == 16 && dctx->lenptr > 0 ?
+ dctx->lengths[dctx->lenptr - 1] : 0);
+ dctx->state = TREES_LENREP;
+ }
+ break;
+ case TREES_LENREP:
+ if (dctx->nbits < dctx->lenextrabits)
+ goto finished;
+ rep =
+ dctx->lenaddon +
+ (dctx->bits & ((1 << dctx->lenextrabits) - 1));
+ EATBITS(dctx->lenextrabits);
+ while (rep > 0 && dctx->lenptr < dctx->hlit + dctx->hdist) {
+ dctx->lengths[dctx->lenptr] = dctx->lenrep;
+ dctx->lenptr++;
+ rep--;
+ }
+ dctx->state = TREES_LEN;
+ break;
+ case INBLK:
+ code =
+ zlib_huflookup(&dctx->bits, &dctx->nbits, dctx->currlentable);
+ if (code == -1)
+ goto finished;
+ if (code == -2)
+ goto decode_error;
+ if (code < 256)
+ zlib_emit_char(dctx, code);
+ else if (code == 256) {
+ dctx->state = OUTSIDEBLK;
+ if (dctx->currlentable != dctx->staticlentable) {
+ zlib_freetable(&dctx->currlentable);
+ dctx->currlentable = NULL;
+ }
+ if (dctx->currdisttable != dctx->staticdisttable) {
+ zlib_freetable(&dctx->currdisttable);
+ dctx->currdisttable = NULL;
+ }
+ } else if (code < 286) { /* static tree can give >285; ignore */
+ dctx->state = GOTLENSYM;
+ dctx->sym = code;
+ }
+ break;
+ case GOTLENSYM:
+ rec = &lencodes[dctx->sym - 257];
+ if (dctx->nbits < rec->extrabits)
+ goto finished;
+ dctx->len =
+ rec->min + (dctx->bits & ((1 << rec->extrabits) - 1));
+ EATBITS(rec->extrabits);
+ dctx->state = GOTLEN;
+ break;
+ case GOTLEN:
+ code =
+ zlib_huflookup(&dctx->bits, &dctx->nbits,
+ dctx->currdisttable);
+ if (code == -1)
+ goto finished;
+ if (code == -2)
+ goto decode_error;
+ dctx->state = GOTDISTSYM;
+ dctx->sym = code;
+ break;
+ case GOTDISTSYM:
+ rec = &distcodes[dctx->sym];
+ if (dctx->nbits < rec->extrabits)
+ goto finished;
+ dist = rec->min + (dctx->bits & ((1 << rec->extrabits) - 1));
+ EATBITS(rec->extrabits);
+ dctx->state = INBLK;
+ while (dctx->len--)
+ zlib_emit_char(dctx, dctx->window[(dctx->winpos - dist) &
+ (WINSIZE - 1)]);
+ break;
+ case UNCOMP_LEN:
+ /*
+ * Uncompressed block. We expect to see a 16-bit LEN.
+ */
+ if (dctx->nbits < 16)
+ goto finished;
+ dctx->uncomplen = dctx->bits & 0xFFFF;
+ EATBITS(16);
+ dctx->state = UNCOMP_NLEN;
+ break;
+ case UNCOMP_NLEN:
+ /*
+ * Uncompressed block. We expect to see a 16-bit NLEN,
+ * which should be the one's complement of the previous
+ * LEN.
+ */
+ if (dctx->nbits < 16)
+ goto finished;
+ nlen = dctx->bits & 0xFFFF;
+ EATBITS(16);
+ if (dctx->uncomplen != (nlen ^ 0xFFFF))
+ goto decode_error;
+ if (dctx->uncomplen == 0)
+ dctx->state = OUTSIDEBLK; /* block is empty */
+ else
+ dctx->state = UNCOMP_DATA;
+ break;
+ case UNCOMP_DATA:
+ if (dctx->nbits < 8)
+ goto finished;
+ zlib_emit_char(dctx, dctx->bits & 0xFF);
+ EATBITS(8);
+ if (--dctx->uncomplen == 0)
+ dctx->state = OUTSIDEBLK; /* end of uncompressed block */
+ break;
+ }
+ }
+
+ finished:
+ *outblock = dctx->outblk;
+ *outlen = dctx->outlen;
+ return 1;
+
+ decode_error:
+ sfree(dctx->outblk);
+ *outblock = dctx->outblk = NULL;
+ *outlen = 0;
+ return 0;
+}
+
+#ifdef ZLIB_STANDALONE
+
+#include <stdio.h>
+#include <string.h>
+
+int main(int argc, char **argv)
+{
+ unsigned char buf[16], *outbuf;
+ int ret, outlen;
+ void *handle;
+ int noheader = FALSE, opts = TRUE;
+ char *filename = NULL;
+ FILE *fp;
+
+ while (--argc) {
+ char *p = *++argv;
+
+ if (p[0] == '-' && opts) {
+ if (!strcmp(p, "-d"))
+ noheader = TRUE;
+ else if (!strcmp(p, "--"))
+ opts = FALSE; /* next thing is filename */
+ else {
+ fprintf(stderr, "unknown command line option '%s'\n", p);
+ return 1;
+ }
+ } else if (!filename) {
+ filename = p;
+ } else {
+ fprintf(stderr, "can only handle one filename\n");
+ return 1;
+ }
+ }
+
+ handle = zlib_decompress_init();
+
+ if (noheader) {
+ /*
+ * Provide missing zlib header if -d was specified.
+ */
+ zlib_decompress_block(handle, "\x78\x9C", 2, &outbuf, &outlen);
+ assert(outlen == 0);
+ }
+
+ if (filename)
+ fp = fopen(filename, "rb");
+ else
+ fp = stdin;
+
+ if (!fp) {
+ assert(filename);
+ fprintf(stderr, "unable to open '%s'\n", filename);
+ return 1;
+ }
+
+ while (1) {
+ ret = fread(buf, 1, sizeof(buf), fp);
+ if (ret <= 0)
+ break;
+ zlib_decompress_block(handle, buf, ret, &outbuf, &outlen);
+ if (outbuf) {
+ if (outlen)
+ fwrite(outbuf, 1, outlen, stdout);
+ sfree(outbuf);
+ } else {
+ fprintf(stderr, "decoding error\n");
+ return 1;
+ }
+ }
+
+ zlib_decompress_cleanup(handle);
+
+ if (filename)
+ fclose(fp);
+
+ return 0;
+}
+
+#else
+
+const struct ssh_compress ssh_zlib = {
+ "zlib",
+ "zlib@openssh.com", /* delayed version */
+ zlib_compress_init,
+ zlib_compress_cleanup,
+ zlib_compress_block,
+ zlib_decompress_init,
+ zlib_decompress_cleanup,
+ zlib_decompress_block,
+ zlib_disable_compression,
+ "zlib (RFC1950)"
+};
+
+#endif
--- /dev/null
+/*
+ * storage.h: interface defining functions for storage and recovery
+ * of PuTTY's persistent data.
+ */
+
+#ifndef PUTTY_STORAGE_H
+#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.
+ */
+
+/*
+ * Write a saved session. The caller is expected to call
+ * open_setting_w() to get a `void *' handle, then pass that to a
+ * number of calls to write_setting_s() and write_setting_i(), and
+ * then close it using close_settings_w(). At the end of this call
+ * sequence the settings should have been written to the PuTTY
+ * persistent storage area.
+ *
+ * A given key will be written at most once while saving a session.
+ * Keys may be up to 255 characters long. String values have no length
+ * limit.
+ *
+ * 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 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.
+ *
+ * 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);
+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);
+void close_settings_r(void *handle);
+
+/*
+ * Delete a whole saved session.
+ */
+void del_settings(const char *sessionname);
+
+/*
+ * Enumerate all saved sessions.
+ */
+void *enum_settings_start(void);
+char *enum_settings_next(void *handle, char *buffer, int buflen);
+void enum_settings_finish(void *handle);
+
+/* ----------------------------------------------------------------------
+ * Functions to access PuTTY's host key database.
+ */
+
+/*
+ * See if a host key matches the database entry. Return values can
+ * be 0 (entry matches database), 1 (entry is absent in database),
+ * or 2 (entry exists in database and is different).
+ */
+int verify_host_key(const char *hostname, int port,
+ const char *keytype, const char *key);
+
+/*
+ * Write a host key into the database, overwriting any previous
+ * entry that might have been there.
+ */
+void store_host_key(const char *hostname, int port,
+ const char *keytype, const char *key);
+
+/* ----------------------------------------------------------------------
+ * Functions to access PuTTY's random number seed file.
+ */
+
+typedef void (*noise_consumer_t) (void *data, int len);
+
+/*
+ * Read PuTTY's random seed file and pass its contents to a noise
+ * consumer function.
+ */
+void read_random_seed(noise_consumer_t consumer);
+
+/*
+ * Write PuTTY's random seed file from a given chunk of noise.
+ */
+void write_random_seed(void *data, int len);
+
+/* ----------------------------------------------------------------------
+ * Cleanup function: remove all of PuTTY's persistent state.
+ */
+void cleanup_all(void);
+
+#endif
--- /dev/null
+/*
+ * Telnet backend.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "putty.h"
+
+#ifndef FALSE
+#define FALSE 0
+#endif
+#ifndef TRUE
+#define TRUE 1
+#endif
+
+#define IAC 255 /* interpret as command: */
+#define DONT 254 /* you are not to use option */
+#define DO 253 /* please, you use option */
+#define WONT 252 /* I won't use option */
+#define WILL 251 /* I will use option */
+#define SB 250 /* interpret as subnegotiation */
+#define SE 240 /* end sub negotiation */
+
+#define GA 249 /* you may reverse the line */
+#define EL 248 /* erase the current line */
+#define EC 247 /* erase the current character */
+#define AYT 246 /* are you there */
+#define AO 245 /* abort output--but let prog finish */
+#define IP 244 /* interrupt process--permanently */
+#define BREAK 243 /* break */
+#define DM 242 /* data mark--for connect. cleaning */
+#define NOP 241 /* nop */
+#define EOR 239 /* end of record (transparent mode) */
+#define ABORT 238 /* Abort process */
+#define SUSP 237 /* Suspend process */
+#define xEOF 236 /* End of file: EOF is already used... */
+
+#define TELOPTS(X) \
+ X(BINARY, 0) /* 8-bit data path */ \
+ X(ECHO, 1) /* echo */ \
+ X(RCP, 2) /* prepare to reconnect */ \
+ X(SGA, 3) /* suppress go ahead */ \
+ X(NAMS, 4) /* approximate message size */ \
+ X(STATUS, 5) /* give status */ \
+ X(TM, 6) /* timing mark */ \
+ X(RCTE, 7) /* remote controlled transmission and echo */ \
+ X(NAOL, 8) /* negotiate about output line width */ \
+ X(NAOP, 9) /* negotiate about output page size */ \
+ X(NAOCRD, 10) /* negotiate about CR disposition */ \
+ X(NAOHTS, 11) /* negotiate about horizontal tabstops */ \
+ X(NAOHTD, 12) /* negotiate about horizontal tab disposition */ \
+ X(NAOFFD, 13) /* negotiate about formfeed disposition */ \
+ X(NAOVTS, 14) /* negotiate about vertical tab stops */ \
+ X(NAOVTD, 15) /* negotiate about vertical tab disposition */ \
+ X(NAOLFD, 16) /* negotiate about output LF disposition */ \
+ X(XASCII, 17) /* extended ascic character set */ \
+ X(LOGOUT, 18) /* force logout */ \
+ X(BM, 19) /* byte macro */ \
+ X(DET, 20) /* data entry terminal */ \
+ X(SUPDUP, 21) /* supdup protocol */ \
+ X(SUPDUPOUTPUT, 22) /* supdup output */ \
+ X(SNDLOC, 23) /* send location */ \
+ X(TTYPE, 24) /* terminal type */ \
+ X(EOR, 25) /* end or record */ \
+ X(TUID, 26) /* TACACS user identification */ \
+ X(OUTMRK, 27) /* output marking */ \
+ X(TTYLOC, 28) /* terminal location number */ \
+ X(3270REGIME, 29) /* 3270 regime */ \
+ X(X3PAD, 30) /* X.3 PAD */ \
+ X(NAWS, 31) /* window size */ \
+ X(TSPEED, 32) /* terminal speed */ \
+ X(LFLOW, 33) /* remote flow control */ \
+ X(LINEMODE, 34) /* Linemode option */ \
+ X(XDISPLOC, 35) /* X Display Location */ \
+ X(OLD_ENVIRON, 36) /* Old - Environment variables */ \
+ X(AUTHENTICATION, 37) /* Authenticate */ \
+ X(ENCRYPT, 38) /* Encryption option */ \
+ X(NEW_ENVIRON, 39) /* New - Environment variables */ \
+ X(TN3270E, 40) /* TN3270 enhancements */ \
+ X(XAUTH, 41) \
+ X(CHARSET, 42) /* Character set */ \
+ X(RSP, 43) /* Remote serial port */ \
+ X(COM_PORT_OPTION, 44) /* Com port control */ \
+ X(SLE, 45) /* Suppress local echo */ \
+ X(STARTTLS, 46) /* Start TLS */ \
+ X(KERMIT, 47) /* Automatic Kermit file transfer */ \
+ X(SEND_URL, 48) \
+ X(FORWARD_X, 49) \
+ X(PRAGMA_LOGON, 138) \
+ X(SSPI_LOGON, 139) \
+ X(PRAGMA_HEARTBEAT, 140) \
+ X(EXOPL, 255) /* extended-options-list */
+
+#define telnet_enum(x,y) TELOPT_##x = y,
+enum { TELOPTS(telnet_enum) dummy=0 };
+#undef telnet_enum
+
+#define TELQUAL_IS 0 /* option is... */
+#define TELQUAL_SEND 1 /* send option */
+#define TELQUAL_INFO 2 /* ENVIRON: informational version of IS */
+#define BSD_VAR 1
+#define BSD_VALUE 0
+#define RFC_VAR 0
+#define RFC_VALUE 1
+
+#define CR 13
+#define LF 10
+#define NUL 0
+
+#define iswritable(x) \
+ ( (x) != IAC && \
+ (telnet->opt_states[o_we_bin.index] == ACTIVE || (x) != CR))
+
+static char *telopt(int opt)
+{
+#define telnet_str(x,y) case TELOPT_##x: return #x;
+ switch (opt) {
+ TELOPTS(telnet_str)
+ default:
+ return "<unknown>";
+ }
+#undef telnet_str
+}
+
+static void telnet_size(void *handle, int width, int height);
+
+struct Opt {
+ int send; /* what we initially send */
+ int nsend; /* -ve send if requested to stop it */
+ int ack, nak; /* +ve and -ve acknowledgements */
+ int option; /* the option code */
+ int index; /* index into telnet->opt_states[] */
+ enum {
+ REQUESTED, ACTIVE, INACTIVE, REALLY_INACTIVE
+ } initial_state;
+};
+
+enum {
+ OPTINDEX_NAWS,
+ OPTINDEX_TSPEED,
+ OPTINDEX_TTYPE,
+ OPTINDEX_OENV,
+ OPTINDEX_NENV,
+ OPTINDEX_ECHO,
+ OPTINDEX_WE_SGA,
+ OPTINDEX_THEY_SGA,
+ OPTINDEX_WE_BIN,
+ OPTINDEX_THEY_BIN,
+ NUM_OPTS
+};
+
+static const struct Opt o_naws =
+ { WILL, WONT, DO, DONT, TELOPT_NAWS, OPTINDEX_NAWS, REQUESTED };
+static const struct Opt o_tspeed =
+ { WILL, WONT, DO, DONT, TELOPT_TSPEED, OPTINDEX_TSPEED, REQUESTED };
+static const struct Opt o_ttype =
+ { WILL, WONT, DO, DONT, TELOPT_TTYPE, OPTINDEX_TTYPE, REQUESTED };
+static const struct Opt o_oenv =
+ { WILL, WONT, DO, DONT, TELOPT_OLD_ENVIRON, OPTINDEX_OENV, INACTIVE };
+static const struct Opt o_nenv =
+ { WILL, WONT, DO, DONT, TELOPT_NEW_ENVIRON, OPTINDEX_NENV, REQUESTED };
+static const struct Opt o_echo =
+ { DO, DONT, WILL, WONT, TELOPT_ECHO, OPTINDEX_ECHO, REQUESTED };
+static const struct Opt o_we_sga =
+ { WILL, WONT, DO, DONT, TELOPT_SGA, OPTINDEX_WE_SGA, REQUESTED };
+static const struct Opt o_they_sga =
+ { DO, DONT, WILL, WONT, TELOPT_SGA, OPTINDEX_THEY_SGA, REQUESTED };
+static const struct Opt o_we_bin =
+ { WILL, WONT, DO, DONT, TELOPT_BINARY, OPTINDEX_WE_BIN, INACTIVE };
+static const struct Opt o_they_bin =
+ { DO, DONT, WILL, WONT, TELOPT_BINARY, OPTINDEX_THEY_BIN, INACTIVE };
+
+static const struct Opt *const opts[] = {
+ &o_naws, &o_tspeed, &o_ttype, &o_oenv, &o_nenv, &o_echo,
+ &o_we_sga, &o_they_sga, &o_we_bin, &o_they_bin, NULL
+};
+
+typedef struct telnet_tag {
+ const struct plug_function_table *fn;
+ /* the above field _must_ be first in the structure */
+
+ Socket s;
+
+ void *frontend;
+ void *ldisc;
+ int term_width, term_height;
+
+ int opt_states[NUM_OPTS];
+
+ int echoing, editing;
+ int activated;
+ int bufsize;
+ int in_synch;
+ int sb_opt, sb_len;
+ unsigned char *sb_buf;
+ int sb_size;
+
+ enum {
+ TOP_LEVEL, SEENIAC, SEENWILL, SEENWONT, SEENDO, SEENDONT,
+ SEENSB, SUBNEGOT, SUBNEG_IAC, SEENCR
+ } state;
+
+ Config cfg;
+
+ Pinger pinger;
+} *Telnet;
+
+#define TELNET_MAX_BACKLOG 4096
+
+#define SB_DELTA 1024
+
+static void c_write(Telnet telnet, char *buf, int len)
+{
+ int backlog;
+ backlog = from_backend(telnet->frontend, 0, buf, len);
+ sk_set_frozen(telnet->s, backlog > TELNET_MAX_BACKLOG);
+}
+
+static void log_option(Telnet telnet, char *sender, int cmd, int option)
+{
+ char *buf;
+ /*
+ * The strange-looking "<?""?>" below is there to avoid a
+ * trigraph - a double question mark followed by > maps to a
+ * closing brace character!
+ */
+ buf = dupprintf("%s:\t%s %s", sender,
+ (cmd == WILL ? "WILL" : cmd == WONT ? "WONT" :
+ cmd == DO ? "DO" : cmd == DONT ? "DONT" : "<?""?>"),
+ telopt(option));
+ logevent(telnet->frontend, buf);
+ sfree(buf);
+}
+
+static void send_opt(Telnet telnet, int cmd, int option)
+{
+ unsigned char b[3];
+
+ b[0] = IAC;
+ b[1] = cmd;
+ b[2] = option;
+ telnet->bufsize = sk_write(telnet->s, (char *)b, 3);
+ log_option(telnet, "client", cmd, option);
+}
+
+static void deactivate_option(Telnet telnet, const struct Opt *o)
+{
+ if (telnet->opt_states[o->index] == REQUESTED ||
+ telnet->opt_states[o->index] == ACTIVE)
+ send_opt(telnet, o->nsend, o->option);
+ telnet->opt_states[o->index] = REALLY_INACTIVE;
+}
+
+/*
+ * Generate side effects of enabling or disabling an option.
+ */
+static void option_side_effects(Telnet telnet, const struct Opt *o, int enabled)
+{
+ if (o->option == TELOPT_ECHO && o->send == DO)
+ telnet->echoing = !enabled;
+ else if (o->option == TELOPT_SGA && o->send == DO)
+ telnet->editing = !enabled;
+ if (telnet->ldisc) /* cause ldisc to notice the change */
+ ldisc_send(telnet->ldisc, NULL, 0, 0);
+
+ /* Ensure we get the minimum options */
+ if (!telnet->activated) {
+ if (telnet->opt_states[o_echo.index] == INACTIVE) {
+ telnet->opt_states[o_echo.index] = REQUESTED;
+ send_opt(telnet, o_echo.send, o_echo.option);
+ }
+ if (telnet->opt_states[o_we_sga.index] == INACTIVE) {
+ telnet->opt_states[o_we_sga.index] = REQUESTED;
+ send_opt(telnet, o_we_sga.send, o_we_sga.option);
+ }
+ if (telnet->opt_states[o_they_sga.index] == INACTIVE) {
+ telnet->opt_states[o_they_sga.index] = REQUESTED;
+ send_opt(telnet, o_they_sga.send, o_they_sga.option);
+ }
+ telnet->activated = TRUE;
+ }
+}
+
+static void activate_option(Telnet telnet, const struct Opt *o)
+{
+ if (o->send == WILL && o->option == TELOPT_NAWS)
+ telnet_size(telnet, telnet->term_width, telnet->term_height);
+ if (o->send == WILL &&
+ (o->option == TELOPT_NEW_ENVIRON ||
+ o->option == TELOPT_OLD_ENVIRON)) {
+ /*
+ * We may only have one kind of ENVIRON going at a time.
+ * This is a hack, but who cares.
+ */
+ deactivate_option(telnet, o->option ==
+ TELOPT_NEW_ENVIRON ? &o_oenv : &o_nenv);
+ }
+ option_side_effects(telnet, o, 1);
+}
+
+static void refused_option(Telnet telnet, const struct Opt *o)
+{
+ if (o->send == WILL && o->option == TELOPT_NEW_ENVIRON &&
+ telnet->opt_states[o_oenv.index] == INACTIVE) {
+ send_opt(telnet, WILL, TELOPT_OLD_ENVIRON);
+ telnet->opt_states[o_oenv.index] = REQUESTED;
+ }
+ option_side_effects(telnet, o, 0);
+}
+
+static void proc_rec_opt(Telnet telnet, int cmd, int option)
+{
+ const struct Opt *const *o;
+
+ log_option(telnet, "server", cmd, option);
+ for (o = opts; *o; o++) {
+ if ((*o)->option == option && (*o)->ack == cmd) {
+ switch (telnet->opt_states[(*o)->index]) {
+ case REQUESTED:
+ telnet->opt_states[(*o)->index] = ACTIVE;
+ activate_option(telnet, *o);
+ break;
+ case ACTIVE:
+ break;
+ case INACTIVE:
+ telnet->opt_states[(*o)->index] = ACTIVE;
+ send_opt(telnet, (*o)->send, option);
+ activate_option(telnet, *o);
+ break;
+ case REALLY_INACTIVE:
+ send_opt(telnet, (*o)->nsend, option);
+ break;
+ }
+ return;
+ } else if ((*o)->option == option && (*o)->nak == cmd) {
+ switch (telnet->opt_states[(*o)->index]) {
+ case REQUESTED:
+ telnet->opt_states[(*o)->index] = INACTIVE;
+ refused_option(telnet, *o);
+ break;
+ case ACTIVE:
+ telnet->opt_states[(*o)->index] = INACTIVE;
+ send_opt(telnet, (*o)->nsend, option);
+ option_side_effects(telnet, *o, 0);
+ break;
+ case INACTIVE:
+ case REALLY_INACTIVE:
+ break;
+ }
+ return;
+ }
+ }
+ /*
+ * If we reach here, the option was one we weren't prepared to
+ * cope with. If the request was positive (WILL or DO), we send
+ * a negative ack to indicate refusal. If the request was
+ * negative (WONT / DONT), we must do nothing.
+ */
+ if (cmd == WILL || cmd == DO)
+ 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;
+
+ switch (telnet->sb_opt) {
+ case TELOPT_TSPEED:
+ if (telnet->sb_len == 1 && telnet->sb_buf[0] == TELQUAL_SEND) {
+ char *logbuf;
+ 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);
+ 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);
+ logevent(telnet->frontend, logbuf);
+ sfree(logbuf);
+ } else
+ logevent(telnet->frontend, "server:\tSB TSPEED <something weird>");
+ break;
+ case TELOPT_TTYPE:
+ if (telnet->sb_len == 1 && telnet->sb_buf[0] == TELQUAL_SEND) {
+ char *logbuf;
+ 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]);
+ 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);
+ } else
+ logevent(telnet->frontend, "server:\tSB TTYPE <something weird>\r\n");
+ break;
+ case TELOPT_OLD_ENVIRON:
+ case TELOPT_NEW_ENVIRON:
+ p = telnet->sb_buf;
+ q = p + telnet->sb_len;
+ if (p < q && *p == TELQUAL_SEND) {
+ char *logbuf;
+ 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) {
+ value = RFC_VALUE;
+ var = RFC_VAR;
+ } else {
+ value = BSD_VALUE;
+ var = BSD_VAR;
+ }
+ /*
+ * Try to guess the sense of VAR and VALUE.
+ */
+ while (p < q) {
+ if (*p == RFC_VAR) {
+ value = RFC_VALUE;
+ var = RFC_VAR;
+ } else if (*p == BSD_VAR) {
+ value = BSD_VALUE;
+ var = BSD_VAR;
+ }
+ p++;
+ }
+ } else {
+ /*
+ * With NEW_ENVIRON, the sense of VAR and VALUE
+ * isn't in doubt.
+ */
+ value = RFC_VALUE;
+ var = RFC_VAR;
+ }
+ 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 ? "<nothing>" :
+ (*telnet->cfg.environmt ? "<stuff>" : ""));
+ logevent(telnet->frontend, logbuf);
+ sfree(logbuf);
+ }
+ }
+ break;
+ }
+}
+
+static void do_telnet_read(Telnet telnet, char *buf, int len)
+{
+ char *outbuf = NULL;
+ int outbuflen = 0, outbufsize = 0;
+
+#define ADDTOBUF(c) do { \
+ if (outbuflen >= outbufsize) { \
+ outbufsize = outbuflen + 256; \
+ outbuf = sresize(outbuf, outbufsize, char); \
+ } \
+ outbuf[outbuflen++] = (c); \
+} while (0)
+
+ while (len--) {
+ int c = (unsigned char) *buf++;
+
+ switch (telnet->state) {
+ case TOP_LEVEL:
+ case SEENCR:
+ if (c == NUL && telnet->state == SEENCR)
+ telnet->state = TOP_LEVEL;
+ else if (c == IAC)
+ telnet->state = SEENIAC;
+ else {
+ if (!telnet->in_synch)
+ ADDTOBUF(c);
+
+#if 1
+ /* I can't get the F***ing winsock to insert the urgent IAC
+ * into the right position! Even with SO_OOBINLINE it gives
+ * it to recv too soon. And of course the DM byte (that
+ * arrives in the same packet!) appears several K later!!
+ *
+ * Oh well, we do get the DM in the right place so I'll
+ * just stop hiding on the next 0xf2 and hope for the best.
+ */
+ else if (c == DM)
+ telnet->in_synch = 0;
+#endif
+ if (c == CR && telnet->opt_states[o_they_bin.index] != ACTIVE)
+ telnet->state = SEENCR;
+ else
+ telnet->state = TOP_LEVEL;
+ }
+ break;
+ case SEENIAC:
+ if (c == DO)
+ telnet->state = SEENDO;
+ else if (c == DONT)
+ telnet->state = SEENDONT;
+ else if (c == WILL)
+ telnet->state = SEENWILL;
+ else if (c == WONT)
+ telnet->state = SEENWONT;
+ else if (c == SB)
+ telnet->state = SEENSB;
+ else if (c == DM) {
+ telnet->in_synch = 0;
+ telnet->state = TOP_LEVEL;
+ } else {
+ /* ignore everything else; print it if it's IAC */
+ if (c == IAC) {
+ ADDTOBUF(c);
+ }
+ telnet->state = TOP_LEVEL;
+ }
+ break;
+ case SEENWILL:
+ proc_rec_opt(telnet, WILL, c);
+ telnet->state = TOP_LEVEL;
+ break;
+ case SEENWONT:
+ proc_rec_opt(telnet, WONT, c);
+ telnet->state = TOP_LEVEL;
+ break;
+ case SEENDO:
+ proc_rec_opt(telnet, DO, c);
+ telnet->state = TOP_LEVEL;
+ break;
+ case SEENDONT:
+ proc_rec_opt(telnet, DONT, c);
+ telnet->state = TOP_LEVEL;
+ break;
+ case SEENSB:
+ telnet->sb_opt = c;
+ telnet->sb_len = 0;
+ telnet->state = SUBNEGOT;
+ break;
+ case SUBNEGOT:
+ if (c == IAC)
+ telnet->state = SUBNEG_IAC;
+ else {
+ subneg_addchar:
+ if (telnet->sb_len >= telnet->sb_size) {
+ telnet->sb_size += SB_DELTA;
+ telnet->sb_buf = sresize(telnet->sb_buf, telnet->sb_size,
+ unsigned char);
+ }
+ telnet->sb_buf[telnet->sb_len++] = c;
+ telnet->state = SUBNEGOT; /* in case we came here by goto */
+ }
+ break;
+ case SUBNEG_IAC:
+ if (c != SE)
+ goto subneg_addchar; /* yes, it's a hack, I know, but... */
+ else {
+ process_subneg(telnet);
+ telnet->state = TOP_LEVEL;
+ }
+ break;
+ }
+ }
+
+ if (outbuflen)
+ c_write(telnet, outbuf, outbuflen);
+ sfree(outbuf);
+}
+
+static void telnet_log(Plug plug, int type, SockAddr addr, int port,
+ const char *error_msg, int error_code)
+{
+ Telnet telnet = (Telnet) 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(telnet->frontend, msg);
+}
+
+static int telnet_closing(Plug plug, const char *error_msg, int error_code,
+ int calling_back)
+{
+ Telnet telnet = (Telnet) plug;
+
+ if (telnet->s) {
+ sk_close(telnet->s);
+ telnet->s = NULL;
+ notify_remote_exit(telnet->frontend);
+ }
+ if (error_msg) {
+ logevent(telnet->frontend, error_msg);
+ connection_fatal(telnet->frontend, "%s", error_msg);
+ }
+ /* Otherwise, the remote side closed the connection normally. */
+ return 0;
+}
+
+static int telnet_receive(Plug plug, int urgent, char *data, int len)
+{
+ Telnet telnet = (Telnet) plug;
+ if (urgent)
+ telnet->in_synch = TRUE;
+ do_telnet_read(telnet, data, len);
+ return 1;
+}
+
+static void telnet_sent(Plug plug, int bufsize)
+{
+ Telnet telnet = (Telnet) plug;
+ telnet->bufsize = bufsize;
+}
+
+/*
+ * Called to set up the Telnet connection.
+ *
+ * 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 *telnet_init(void *frontend_handle, void **backend_handle,
+ Config *cfg,
+ char *host, int port, char **realhost,
+ int nodelay, int keepalive)
+{
+ static const struct plug_function_table fn_table = {
+ telnet_log,
+ telnet_closing,
+ telnet_receive,
+ telnet_sent
+ };
+ SockAddr addr;
+ const char *err;
+ Telnet telnet;
+
+ telnet = snew(struct telnet_tag);
+ telnet->fn = &fn_table;
+ telnet->cfg = *cfg; /* STRUCTURE COPY */
+ telnet->s = NULL;
+ 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->state = TOP_LEVEL;
+ telnet->ldisc = NULL;
+ telnet->pinger = NULL;
+ *backend_handle = telnet;
+
+ /*
+ * Try to find host.
+ */
+ {
+ char *buf;
+ buf = dupprintf("Looking up host \"%s\"%s", host,
+ (cfg->addressfamily == ADDRTYPE_IPV4 ? " (IPv4)" :
+ (cfg->addressfamily == ADDRTYPE_IPV6 ? " (IPv6)" :
+ "")));
+ logevent(telnet->frontend, buf);
+ sfree(buf);
+ }
+ addr = name_lookup(host, port, realhost, &telnet->cfg, cfg->addressfamily);
+ if ((err = sk_addr_error(addr)) != NULL) {
+ sk_addr_free(addr);
+ return err;
+ }
+
+ if (port < 0)
+ port = 23; /* default telnet port */
+
+ /*
+ * Open socket.
+ */
+ telnet->s = new_connection(addr, *realhost, port, 0, 1,
+ nodelay, keepalive, (Plug) telnet, &telnet->cfg);
+ if ((err = sk_socket_error(telnet->s)) != NULL)
+ return err;
+
+ telnet->pinger = pinger_new(&telnet->cfg, &telnet_backend, telnet);
+
+ /*
+ * Initialise option states.
+ */
+ if (telnet->cfg.passive_telnet) {
+ const struct Opt *const *o;
+
+ for (o = opts; *o; o++)
+ telnet->opt_states[(*o)->index] = INACTIVE;
+ } else {
+ const struct Opt *const *o;
+
+ for (o = opts; *o; o++) {
+ telnet->opt_states[(*o)->index] = (*o)->initial_state;
+ if (telnet->opt_states[(*o)->index] == REQUESTED)
+ send_opt(telnet, (*o)->send, (*o)->option);
+ }
+ telnet->activated = TRUE;
+ }
+
+ /*
+ * Set up SYNCH state.
+ */
+ telnet->in_synch = FALSE;
+
+ /*
+ * We can send special commands from the start.
+ */
+ update_specials_menu(telnet->frontend);
+
+ /*
+ * loghost overrides realhost, if specified.
+ */
+ if (*telnet->cfg.loghost) {
+ char *colon;
+
+ sfree(*realhost);
+ *realhost = dupstr(telnet->cfg.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
+ * with it.
+ */
+ *colon++ = '\0';
+ }
+ }
+
+ return NULL;
+}
+
+static void telnet_free(void *handle)
+{
+ Telnet telnet = (Telnet) handle;
+
+ sfree(telnet->sb_buf);
+ if (telnet->s)
+ sk_close(telnet->s);
+ if (telnet->pinger)
+ pinger_free(telnet->pinger);
+ 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)
+{
+ Telnet telnet = (Telnet) handle;
+ pinger_reconfig(telnet->pinger, &telnet->cfg, cfg);
+ telnet->cfg = *cfg; /* STRUCTURE COPY */
+}
+
+/*
+ * Called to send data down the Telnet connection.
+ */
+static int telnet_send(void *handle, char *buf, int len)
+{
+ Telnet telnet = (Telnet) handle;
+ unsigned char *p, *end;
+ static const unsigned char iac[2] = { IAC, IAC };
+ static const unsigned char cr[2] = { CR, NUL };
+#if 0
+ static const unsigned char nl[2] = { CR, LF };
+#endif
+
+ if (telnet->s == NULL)
+ return 0;
+
+ p = (unsigned char *)buf;
+ end = (unsigned char *)(buf + len);
+ while (p < end) {
+ unsigned char *q = p;
+
+ while (p < end && iswritable(*p))
+ p++;
+ telnet->bufsize = sk_write(telnet->s, (char *)q, p - q);
+
+ while (p < end && !iswritable(*p)) {
+ telnet->bufsize =
+ sk_write(telnet->s, (char *)(*p == IAC ? iac : cr), 2);
+ p++;
+ }
+ }
+
+ return telnet->bufsize;
+}
+
+/*
+ * Called to query the current socket sendability status.
+ */
+static int telnet_sendbuffer(void *handle)
+{
+ Telnet telnet = (Telnet) handle;
+ return telnet->bufsize;
+}
+
+/*
+ * Called to set the size of the window from Telnet's POV.
+ */
+static void telnet_size(void *handle, int width, int height)
+{
+ Telnet telnet = (Telnet) handle;
+ unsigned char b[24];
+ int n;
+ char *logbuf;
+
+ telnet->term_width = width;
+ telnet->term_height = height;
+
+ if (telnet->s == NULL || telnet->opt_states[o_naws.index] != ACTIVE)
+ return;
+ n = 0;
+ b[n++] = IAC;
+ b[n++] = SB;
+ b[n++] = TELOPT_NAWS;
+ b[n++] = telnet->term_width >> 8;
+ if (b[n-1] == IAC) b[n++] = IAC; /* duplicate any IAC byte occurs */
+ b[n++] = telnet->term_width & 0xFF;
+ if (b[n-1] == IAC) b[n++] = IAC; /* duplicate any IAC byte occurs */
+ b[n++] = telnet->term_height >> 8;
+ if (b[n-1] == IAC) b[n++] = IAC; /* duplicate any IAC byte occurs */
+ b[n++] = telnet->term_height & 0xFF;
+ if (b[n-1] == IAC) b[n++] = IAC; /* duplicate any IAC byte occurs */
+ b[n++] = IAC;
+ b[n++] = SE;
+ telnet->bufsize = sk_write(telnet->s, (char *)b, n);
+ logbuf = dupprintf("client:\tSB NAWS %d,%d",
+ telnet->term_width, telnet->term_height);
+ logevent(telnet->frontend, logbuf);
+ sfree(logbuf);
+}
+
+/*
+ * Send Telnet special codes.
+ */
+static void telnet_special(void *handle, Telnet_Special code)
+{
+ Telnet telnet = (Telnet) handle;
+ unsigned char b[2];
+
+ if (telnet->s == NULL)
+ return;
+
+ b[0] = IAC;
+ switch (code) {
+ case TS_AYT:
+ b[1] = AYT;
+ telnet->bufsize = sk_write(telnet->s, (char *)b, 2);
+ break;
+ case TS_BRK:
+ b[1] = BREAK;
+ telnet->bufsize = sk_write(telnet->s, (char *)b, 2);
+ break;
+ case TS_EC:
+ b[1] = EC;
+ telnet->bufsize = sk_write(telnet->s, (char *)b, 2);
+ break;
+ case TS_EL:
+ b[1] = EL;
+ telnet->bufsize = sk_write(telnet->s, (char *)b, 2);
+ break;
+ case TS_GA:
+ b[1] = GA;
+ telnet->bufsize = sk_write(telnet->s, (char *)b, 2);
+ break;
+ case TS_NOP:
+ b[1] = NOP;
+ telnet->bufsize = sk_write(telnet->s, (char *)b, 2);
+ break;
+ case TS_ABORT:
+ b[1] = ABORT;
+ telnet->bufsize = sk_write(telnet->s, (char *)b, 2);
+ break;
+ case TS_AO:
+ b[1] = AO;
+ telnet->bufsize = sk_write(telnet->s, (char *)b, 2);
+ break;
+ case TS_IP:
+ b[1] = IP;
+ telnet->bufsize = sk_write(telnet->s, (char *)b, 2);
+ break;
+ case TS_SUSP:
+ b[1] = SUSP;
+ telnet->bufsize = sk_write(telnet->s, (char *)b, 2);
+ break;
+ case TS_EOR:
+ b[1] = EOR;
+ telnet->bufsize = sk_write(telnet->s, (char *)b, 2);
+ break;
+ case TS_EOF:
+ b[1] = xEOF;
+ telnet->bufsize = sk_write(telnet->s, (char *)b, 2);
+ break;
+ case TS_EOL:
+ /* In BINARY mode, CR-LF becomes just CR -
+ * and without the NUL suffix too. */
+ if (telnet->opt_states[o_we_bin.index] == ACTIVE)
+ telnet->bufsize = sk_write(telnet->s, "\r", 1);
+ else
+ telnet->bufsize = sk_write(telnet->s, "\r\n", 2);
+ break;
+ case TS_SYNCH:
+ b[1] = DM;
+ telnet->bufsize = sk_write(telnet->s, (char *)b, 1);
+ telnet->bufsize = sk_write_oob(telnet->s, (char *)(b + 1), 1);
+ break;
+ case TS_RECHO:
+ if (telnet->opt_states[o_echo.index] == INACTIVE ||
+ telnet->opt_states[o_echo.index] == REALLY_INACTIVE) {
+ telnet->opt_states[o_echo.index] = REQUESTED;
+ send_opt(telnet, o_echo.send, o_echo.option);
+ }
+ break;
+ case TS_LECHO:
+ if (telnet->opt_states[o_echo.index] == ACTIVE) {
+ telnet->opt_states[o_echo.index] = REQUESTED;
+ send_opt(telnet, o_echo.nsend, o_echo.option);
+ }
+ break;
+ case TS_PING:
+ if (telnet->opt_states[o_they_sga.index] == ACTIVE) {
+ b[1] = NOP;
+ telnet->bufsize = sk_write(telnet->s, (char *)b, 2);
+ }
+ break;
+ default:
+ break; /* never heard of it */
+ }
+}
+
+static const struct telnet_special *telnet_get_specials(void *handle)
+{
+ static const struct telnet_special specials[] = {
+ {"Are You There", TS_AYT},
+ {"Break", TS_BRK},
+ {"Synch", TS_SYNCH},
+ {"Erase Character", TS_EC},
+ {"Erase Line", TS_EL},
+ {"Go Ahead", TS_GA},
+ {"No Operation", TS_NOP},
+ {NULL, TS_SEP},
+ {"Abort Process", TS_ABORT},
+ {"Abort Output", TS_AO},
+ {"Interrupt Process", TS_IP},
+ {"Suspend Process", TS_SUSP},
+ {NULL, TS_SEP},
+ {"End Of Record", TS_EOR},
+ {"End Of File", TS_EOF},
+ {NULL, TS_EXITMENU}
+ };
+ return specials;
+}
+
+static int telnet_connected(void *handle)
+{
+ Telnet telnet = (Telnet) handle;
+ return telnet->s != NULL;
+}
+
+static int telnet_sendok(void *handle)
+{
+ /* Telnet telnet = (Telnet) handle; */
+ return 1;
+}
+
+static void telnet_unthrottle(void *handle, int backlog)
+{
+ Telnet telnet = (Telnet) handle;
+ sk_set_frozen(telnet->s, backlog > TELNET_MAX_BACKLOG);
+}
+
+static int telnet_ldisc(void *handle, int option)
+{
+ Telnet telnet = (Telnet) handle;
+ if (option == LD_ECHO)
+ return telnet->echoing;
+ if (option == LD_EDIT)
+ return telnet->editing;
+ return FALSE;
+}
+
+static void telnet_provide_ldisc(void *handle, void *ldisc)
+{
+ Telnet telnet = (Telnet) handle;
+ telnet->ldisc = ldisc;
+}
+
+static void telnet_provide_logctx(void *handle, void *logctx)
+{
+ /* This is a stub. */
+}
+
+static int telnet_exitcode(void *handle)
+{
+ Telnet telnet = (Telnet) handle;
+ if (telnet->s != NULL)
+ return -1; /* still connected */
+ else
+ /* Telnet doesn't transmit exit codes back to the client */
+ return 0;
+}
+
+/*
+ * cfg_info for Telnet does nothing at all.
+ */
+static int telnet_cfg_info(void *handle)
+{
+ return 0;
+}
+
+Backend telnet_backend = {
+ telnet_init,
+ telnet_free,
+ telnet_reconfig,
+ telnet_send,
+ telnet_sendbuffer,
+ telnet_size,
+ telnet_special,
+ telnet_get_specials,
+ telnet_connected,
+ telnet_exitcode,
+ telnet_sendok,
+ telnet_ldisc,
+ telnet_provide_ldisc,
+ telnet_provide_logctx,
+ telnet_unthrottle,
+ telnet_cfg_info,
+ "telnet",
+ PROT_TELNET,
+ 23
+};
--- /dev/null
+/*
+ * Terminal emulator.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+
+#include <time.h>
+#include <assert.h>
+#include "putty.h"
+#include "terminal.h"
+
+#define poslt(p1,p2) ( (p1).y < (p2).y || ( (p1).y == (p2).y && (p1).x < (p2).x ) )
+#define posle(p1,p2) ( (p1).y < (p2).y || ( (p1).y == (p2).y && (p1).x <= (p2).x ) )
+#define poseq(p1,p2) ( (p1).y == (p2).y && (p1).x == (p2).x )
+#define posdiff(p1,p2) ( ((p1).y - (p2).y) * (term->cols+1) + (p1).x - (p2).x )
+
+/* Product-order comparisons for rectangular block selection. */
+#define posPlt(p1,p2) ( (p1).y <= (p2).y && (p1).x < (p2).x )
+#define posPle(p1,p2) ( (p1).y <= (p2).y && (p1).x <= (p2).x )
+
+#define incpos(p) ( (p).x == term->cols ? ((p).x = 0, (p).y++, 1) : ((p).x++, 0) )
+#define decpos(p) ( (p).x == 0 ? ((p).x = term->cols, (p).y--, 1) : ((p).x--, 0) )
+
+#define VT52_PLUS
+
+#define CL_ANSIMIN 0x0001 /* Codes in all ANSI like terminals. */
+#define CL_VT100 0x0002 /* VT100 */
+#define CL_VT100AVO 0x0004 /* VT100 +AVO; 132x24 (not 132x14) & attrs */
+#define CL_VT102 0x0008 /* VT102 */
+#define CL_VT220 0x0010 /* VT220 */
+#define CL_VT320 0x0020 /* VT320 */
+#define CL_VT420 0x0040 /* VT420 */
+#define CL_VT510 0x0080 /* VT510, NB VT510 includes ANSI */
+#define CL_VT340TEXT 0x0100 /* VT340 extensions that appear in the VT420 */
+#define CL_SCOANSI 0x1000 /* SCOANSI not in ANSIMIN. */
+#define CL_ANSI 0x2000 /* ANSI ECMA-48 not in the VT100..VT420 */
+#define CL_OTHER 0x4000 /* Others, Xterm, linux, putty, dunno, etc */
+
+#define TM_VT100 (CL_ANSIMIN|CL_VT100)
+#define TM_VT100AVO (TM_VT100|CL_VT100AVO)
+#define TM_VT102 (TM_VT100AVO|CL_VT102)
+#define TM_VT220 (TM_VT102|CL_VT220)
+#define TM_VTXXX (TM_VT220|CL_VT340TEXT|CL_VT510|CL_VT420|CL_VT320)
+#define TM_SCOANSI (CL_ANSIMIN|CL_SCOANSI)
+
+#define TM_PUTTY (0xFFFF)
+
+#define UPDATE_DELAY ((TICKSPERSEC+49)/50)/* ticks to defer window update */
+#define TBLINK_DELAY ((TICKSPERSEC*9+19)/20)/* ticks between text blinks*/
+#define CBLINK_DELAY (CURSORBLINK) /* ticks between cursor blinks */
+#define VBELL_DELAY (VBELL_TIMEOUT) /* visual bell timeout in ticks */
+
+#define compatibility(x) \
+ if ( ((CL_##x)&term->compatibility_level) == 0 ) { \
+ term->termstate=TOPLEVEL; \
+ break; \
+ }
+#define compatibility2(x,y) \
+ if ( ((CL_##x|CL_##y)&term->compatibility_level) == 0 ) { \
+ term->termstate=TOPLEVEL; \
+ break; \
+ }
+
+#define has_compat(x) ( ((CL_##x)&term->compatibility_level) != 0 )
+
+char *EMPTY_WINDOW_TITLE = "";
+
+const char sco2ansicolour[] = { 0, 4, 2, 6, 1, 5, 3, 7 };
+
+#define sel_nl_sz (sizeof(sel_nl)/sizeof(wchar_t))
+const wchar_t sel_nl[] = SEL_NL;
+
+/*
+ * Fetch the character at a particular position in a line array,
+ * for purposes of `wordtype'. The reason this isn't just a simple
+ * array reference is that if the character we find is UCSWIDE,
+ * then we must look one space further to the left.
+ */
+#define UCSGET(a, x) \
+ ( (x)>0 && (a)[(x)].chr == UCSWIDE ? (a)[(x)-1].chr : (a)[(x)].chr )
+
+/*
+ * Detect the various aliases of U+0020 SPACE.
+ */
+#define IS_SPACE_CHR(chr) \
+ ((chr) == 0x20 || (DIRECT_CHAR(chr) && ((chr) & 0xFF) == 0x20))
+
+/*
+ * Spot magic CSETs.
+ */
+#define CSET_OF(chr) (DIRECT_CHAR(chr)||DIRECT_FONT(chr) ? (chr)&CSET_MASK : 0)
+
+/*
+ * Internal prototypes.
+ */
+static void resizeline(Terminal *, termline *, int);
+static termline *lineptr(Terminal *, int, int, int);
+static void unlineptr(termline *);
+static void do_paint(Terminal *, Context, int);
+static void erase_lots(Terminal *, int, int, int);
+static int find_last_nonempty_line(Terminal *, tree234 *);
+static void swap_screen(Terminal *, int, int, int);
+static void update_sbar(Terminal *);
+static void deselect(Terminal *);
+static void term_print_finish(Terminal *);
+static void scroll(Terminal *, int, int, int, int);
+#ifdef OPTIMISE_SCROLL
+static void scroll_display(Terminal *, int, int, int);
+#endif /* OPTIMISE_SCROLL */
+
+static termline *newline(Terminal *term, int cols, int bce)
+{
+ termline *line;
+ int j;
+
+ line = snew(termline);
+ line->chars = snewn(cols, termchar);
+ for (j = 0; j < cols; j++)
+ line->chars[j] = (bce ? term->erase_char : term->basic_erase_char);
+ line->cols = line->size = cols;
+ line->lattr = LATTR_NORM;
+ line->temporary = FALSE;
+ line->cc_free = 0;
+
+ return line;
+}
+
+static void freeline(termline *line)
+{
+ if (line) {
+ sfree(line->chars);
+ sfree(line);
+ }
+}
+
+static void unlineptr(termline *line)
+{
+ if (line->temporary)
+ freeline(line);
+}
+
+#ifdef TERM_CC_DIAGS
+/*
+ * Diagnostic function: verify that a termline has a correct
+ * combining character structure.
+ *
+ * This is a performance-intensive check, so it's no longer enabled
+ * by default.
+ */
+static void cc_check(termline *line)
+{
+ unsigned char *flags;
+ int i, j;
+
+ assert(line->size >= line->cols);
+
+ flags = snewn(line->size, unsigned char);
+
+ for (i = 0; i < line->size; i++)
+ flags[i] = (i < line->cols);
+
+ for (i = 0; i < line->cols; i++) {
+ j = i;
+ while (line->chars[j].cc_next) {
+ j += line->chars[j].cc_next;
+ assert(j >= line->cols && j < line->size);
+ assert(!flags[j]);
+ flags[j] = TRUE;
+ }
+ }
+
+ j = line->cc_free;
+ if (j) {
+ while (1) {
+ assert(j >= line->cols && j < line->size);
+ assert(!flags[j]);
+ flags[j] = TRUE;
+ if (line->chars[j].cc_next)
+ j += line->chars[j].cc_next;
+ else
+ break;
+ }
+ }
+
+ j = 0;
+ for (i = 0; i < line->size; i++)
+ j += (flags[i] != 0);
+
+ assert(j == line->size);
+
+ sfree(flags);
+}
+#endif
+
+/*
+ * Add a combining character to a character cell.
+ */
+static void add_cc(termline *line, int col, unsigned long chr)
+{
+ int newcc;
+
+ assert(col >= 0 && col < line->cols);
+
+ /*
+ * Start by extending the cols array if the free list is empty.
+ */
+ if (!line->cc_free) {
+ int n = line->size;
+ line->size += 16 + (line->size - line->cols) / 2;
+ line->chars = sresize(line->chars, line->size, termchar);
+ line->cc_free = n;
+ while (n < line->size) {
+ if (n+1 < line->size)
+ line->chars[n].cc_next = 1;
+ else
+ line->chars[n].cc_next = 0;
+ n++;
+ }
+ }
+
+ /*
+ * Now walk the cc list of the cell in question.
+ */
+ while (line->chars[col].cc_next)
+ col += line->chars[col].cc_next;
+
+ /*
+ * `col' now points at the last cc currently in this cell; so
+ * we simply add another one.
+ */
+ newcc = line->cc_free;
+ if (line->chars[newcc].cc_next)
+ line->cc_free = newcc + line->chars[newcc].cc_next;
+ else
+ line->cc_free = 0;
+ line->chars[newcc].cc_next = 0;
+ line->chars[newcc].chr = chr;
+ line->chars[col].cc_next = newcc - col;
+
+#ifdef TERM_CC_DIAGS
+ cc_check(line);
+#endif
+}
+
+/*
+ * Clear the combining character list in a character cell.
+ */
+static void clear_cc(termline *line, int col)
+{
+ int oldfree, origcol = col;
+
+ assert(col >= 0 && col < line->cols);
+
+ if (!line->chars[col].cc_next)
+ return; /* nothing needs doing */
+
+ oldfree = line->cc_free;
+ line->cc_free = col + line->chars[col].cc_next;
+ while (line->chars[col].cc_next)
+ col += line->chars[col].cc_next;
+ if (oldfree)
+ line->chars[col].cc_next = oldfree - col;
+ else
+ line->chars[col].cc_next = 0;
+
+ line->chars[origcol].cc_next = 0;
+
+#ifdef TERM_CC_DIAGS
+ cc_check(line);
+#endif
+}
+
+/*
+ * Compare two character cells for equality. Special case required
+ * in do_paint() where we override what we expect the chr and attr
+ * fields to be.
+ */
+static int termchars_equal_override(termchar *a, termchar *b,
+ unsigned long bchr, unsigned long battr)
+{
+ /* FULL-TERMCHAR */
+ if (a->chr != bchr)
+ return FALSE;
+ if ((a->attr &~ DATTR_MASK) != (battr &~ DATTR_MASK))
+ return FALSE;
+ while (a->cc_next || b->cc_next) {
+ if (!a->cc_next || !b->cc_next)
+ return FALSE; /* one cc-list ends, other does not */
+ a += a->cc_next;
+ b += b->cc_next;
+ if (a->chr != b->chr)
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static int termchars_equal(termchar *a, termchar *b)
+{
+ return termchars_equal_override(a, b, b->chr, b->attr);
+}
+
+/*
+ * Copy a character cell. (Requires a pointer to the destination
+ * termline, so as to access its free list.)
+ */
+static void copy_termchar(termline *destline, int x, termchar *src)
+{
+ clear_cc(destline, x);
+
+ destline->chars[x] = *src; /* copy everything except cc-list */
+ destline->chars[x].cc_next = 0; /* and make sure this is zero */
+
+ while (src->cc_next) {
+ src += src->cc_next;
+ add_cc(destline, x, src->chr);
+ }
+
+#ifdef TERM_CC_DIAGS
+ cc_check(destline);
+#endif
+}
+
+/*
+ * Move a character cell within its termline.
+ */
+static void move_termchar(termline *line, termchar *dest, termchar *src)
+{
+ /* First clear the cc list from the original char, just in case. */
+ clear_cc(line, dest - line->chars);
+
+ /* Move the character cell and adjust its cc_next. */
+ *dest = *src; /* copy everything except cc-list */
+ if (src->cc_next)
+ dest->cc_next = src->cc_next - (dest-src);
+
+ /* Ensure the original cell doesn't have a cc list. */
+ src->cc_next = 0;
+
+#ifdef TERM_CC_DIAGS
+ cc_check(line);
+#endif
+}
+
+/*
+ * Compress and decompress a termline into an RLE-based format for
+ * storing in scrollback. (Since scrollback almost never needs to
+ * be modified and exists in huge quantities, this is a sensible
+ * tradeoff, particularly since it allows us to continue adding
+ * features to the main termchar structure without proportionally
+ * bloating the terminal emulator's memory footprint unless those
+ * features are in constant use.)
+ */
+struct buf {
+ unsigned char *data;
+ int len, size;
+};
+static void add(struct buf *b, unsigned char c)
+{
+ if (b->len >= b->size) {
+ b->size = (b->len * 3 / 2) + 512;
+ b->data = sresize(b->data, b->size, unsigned char);
+ }
+ b->data[b->len++] = c;
+}
+static int get(struct buf *b)
+{
+ return b->data[b->len++];
+}
+static void makerle(struct buf *b, termline *ldata,
+ void (*makeliteral)(struct buf *b, termchar *c,
+ unsigned long *state))
+{
+ int hdrpos, hdrsize, n, prevlen, prevpos, thislen, thispos, prev2;
+ termchar *c = ldata->chars;
+ unsigned long state = 0, oldstate;
+
+ n = ldata->cols;
+
+ hdrpos = b->len;
+ hdrsize = 0;
+ add(b, 0);
+ prevlen = prevpos = 0;
+ prev2 = FALSE;
+
+ while (n-- > 0) {
+ thispos = b->len;
+ makeliteral(b, c++, &state);
+ thislen = b->len - thispos;
+ if (thislen == prevlen &&
+ !memcmp(b->data + prevpos, b->data + thispos, thislen)) {
+ /*
+ * This literal precisely matches the previous one.
+ * Turn it into a run if it's worthwhile.
+ *
+ * With one-byte literals, it costs us two bytes to
+ * encode a run, plus another byte to write the header
+ * to resume normal output; so a three-element run is
+ * neutral, and anything beyond that is unconditionally
+ * worthwhile. With two-byte literals or more, even a
+ * 2-run is a win.
+ */
+ if (thislen > 1 || prev2) {
+ int runpos, runlen;
+
+ /*
+ * It's worth encoding a run. Start at prevpos,
+ * unless hdrsize==0 in which case we can back up
+ * another one and start by overwriting hdrpos.
+ */
+
+ hdrsize--; /* remove the literal at prevpos */
+ if (prev2) {
+ assert(hdrsize > 0);
+ hdrsize--;
+ prevpos -= prevlen;/* and possibly another one */
+ }
+
+ if (hdrsize == 0) {
+ assert(prevpos == hdrpos + 1);
+ runpos = hdrpos;
+ b->len = prevpos+prevlen;
+ } else {
+ memmove(b->data + prevpos+1, b->data + prevpos, prevlen);
+ runpos = prevpos;
+ b->len = prevpos+prevlen+1;
+ /*
+ * Terminate the previous run of ordinary
+ * literals.
+ */
+ assert(hdrsize >= 1 && hdrsize <= 128);
+ b->data[hdrpos] = hdrsize - 1;
+ }
+
+ runlen = prev2 ? 3 : 2;
+
+ while (n > 0 && runlen < 129) {
+ int tmppos, tmplen;
+ tmppos = b->len;
+ oldstate = state;
+ makeliteral(b, c, &state);
+ tmplen = b->len - tmppos;
+ b->len = tmppos;
+ if (tmplen != thislen ||
+ memcmp(b->data + runpos+1, b->data + tmppos, tmplen)) {
+ state = oldstate;
+ break; /* run over */
+ }
+ n--, c++, runlen++;
+ }
+
+ assert(runlen >= 2 && runlen <= 129);
+ b->data[runpos] = runlen + 0x80 - 2;
+
+ hdrpos = b->len;
+ hdrsize = 0;
+ add(b, 0);
+ /* And ensure this run doesn't interfere with the next. */
+ prevlen = prevpos = 0;
+ prev2 = FALSE;
+
+ continue;
+ } else {
+ /*
+ * Just flag that the previous two literals were
+ * identical, in case we find a third identical one
+ * we want to turn into a run.
+ */
+ prev2 = TRUE;
+ prevlen = thislen;
+ prevpos = thispos;
+ }
+ } else {
+ prev2 = FALSE;
+ prevlen = thislen;
+ prevpos = thispos;
+ }
+
+ /*
+ * This character isn't (yet) part of a run. Add it to
+ * hdrsize.
+ */
+ hdrsize++;
+ if (hdrsize == 128) {
+ b->data[hdrpos] = hdrsize - 1;
+ hdrpos = b->len;
+ hdrsize = 0;
+ add(b, 0);
+ prevlen = prevpos = 0;
+ prev2 = FALSE;
+ }
+ }
+
+ /*
+ * Clean up.
+ */
+ if (hdrsize > 0) {
+ assert(hdrsize <= 128);
+ b->data[hdrpos] = hdrsize - 1;
+ } else {
+ b->len = hdrpos;
+ }
+}
+static void makeliteral_chr(struct buf *b, termchar *c, unsigned long *state)
+{
+ /*
+ * My encoding for characters is UTF-8-like, in that it stores
+ * 7-bit ASCII in one byte and uses high-bit-set bytes as
+ * introducers to indicate a longer sequence. However, it's
+ * unlike UTF-8 in that it doesn't need to be able to
+ * resynchronise, and therefore I don't want to waste two bits
+ * per byte on having recognisable continuation characters.
+ * Also I don't want to rule out the possibility that I may one
+ * day use values 0x80000000-0xFFFFFFFF for interesting
+ * purposes, so unlike UTF-8 I need a full 32-bit range.
+ * Accordingly, here is my encoding:
+ *
+ * 00000000-0000007F: 0xxxxxxx (but see below)
+ * 00000080-00003FFF: 10xxxxxx xxxxxxxx
+ * 00004000-001FFFFF: 110xxxxx xxxxxxxx xxxxxxxx
+ * 00200000-0FFFFFFF: 1110xxxx xxxxxxxx xxxxxxxx xxxxxxxx
+ * 10000000-FFFFFFFF: 11110ZZZ xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx
+ *
+ * (`Z' is like `x' but is always going to be zero since the
+ * values I'm encoding don't go above 2^32. In principle the
+ * five-byte form of the encoding could extend to 2^35, and
+ * there could be six-, seven-, eight- and nine-byte forms as
+ * well to allow up to 64-bit values to be encoded. But that's
+ * completely unnecessary for these purposes!)
+ *
+ * The encoding as written above would be very simple, except
+ * that 7-bit ASCII can occur in several different ways in the
+ * terminal data; sometimes it crops up in the D800 page
+ * (CSET_ASCII) but at other times it's in the 0000 page (real
+ * Unicode). Therefore, this encoding is actually _stateful_:
+ * the one-byte encoding of 00-7F actually indicates `reuse the
+ * upper three bytes of the last character', and to encode an
+ * absolute value of 00-7F you need to use the two-byte form
+ * instead.
+ */
+ if ((c->chr & ~0x7F) == *state) {
+ add(b, (unsigned char)(c->chr & 0x7F));
+ } else if (c->chr < 0x4000) {
+ add(b, (unsigned char)(((c->chr >> 8) & 0x3F) | 0x80));
+ add(b, (unsigned char)(c->chr & 0xFF));
+ } else if (c->chr < 0x200000) {
+ add(b, (unsigned char)(((c->chr >> 16) & 0x1F) | 0xC0));
+ add(b, (unsigned char)((c->chr >> 8) & 0xFF));
+ add(b, (unsigned char)(c->chr & 0xFF));
+ } else if (c->chr < 0x10000000) {
+ add(b, (unsigned char)(((c->chr >> 24) & 0x0F) | 0xE0));
+ add(b, (unsigned char)((c->chr >> 16) & 0xFF));
+ add(b, (unsigned char)((c->chr >> 8) & 0xFF));
+ add(b, (unsigned char)(c->chr & 0xFF));
+ } else {
+ add(b, 0xF0);
+ add(b, (unsigned char)((c->chr >> 24) & 0xFF));
+ add(b, (unsigned char)((c->chr >> 16) & 0xFF));
+ add(b, (unsigned char)((c->chr >> 8) & 0xFF));
+ add(b, (unsigned char)(c->chr & 0xFF));
+ }
+ *state = c->chr & ~0xFF;
+}
+static void makeliteral_attr(struct buf *b, termchar *c, unsigned long *state)
+{
+ /*
+ * My encoding for attributes is 16-bit-granular and assumes
+ * that the top bit of the word is never required. I either
+ * store a two-byte value with the top bit clear (indicating
+ * just that value), or a four-byte value with the top bit set
+ * (indicating the same value with its top bit clear).
+ *
+ * However, first I permute the bits of the attribute value, so
+ * that the eight bits of colour (four in each of fg and bg)
+ * which are never non-zero unless xterm 256-colour mode is in
+ * use are placed higher up the word than everything else. This
+ * ensures that attribute values remain 16-bit _unless_ the
+ * user uses extended colour.
+ */
+ unsigned attr, colourbits;
+
+ attr = c->attr;
+
+ assert(ATTR_BGSHIFT > ATTR_FGSHIFT);
+
+ colourbits = (attr >> (ATTR_BGSHIFT + 4)) & 0xF;
+ colourbits <<= 4;
+ colourbits |= (attr >> (ATTR_FGSHIFT + 4)) & 0xF;
+
+ attr = (((attr >> (ATTR_BGSHIFT + 8)) << (ATTR_BGSHIFT + 4)) |
+ (attr & ((1 << (ATTR_BGSHIFT + 4))-1)));
+ attr = (((attr >> (ATTR_FGSHIFT + 8)) << (ATTR_FGSHIFT + 4)) |
+ (attr & ((1 << (ATTR_FGSHIFT + 4))-1)));
+
+ attr |= (colourbits << (32-9));
+
+ if (attr < 0x8000) {
+ add(b, (unsigned char)((attr >> 8) & 0xFF));
+ add(b, (unsigned char)(attr & 0xFF));
+ } else {
+ add(b, (unsigned char)(((attr >> 24) & 0x7F) | 0x80));
+ add(b, (unsigned char)((attr >> 16) & 0xFF));
+ add(b, (unsigned char)((attr >> 8) & 0xFF));
+ add(b, (unsigned char)(attr & 0xFF));
+ }
+}
+static void makeliteral_cc(struct buf *b, termchar *c, unsigned long *state)
+{
+ /*
+ * For combining characters, I just encode a bunch of ordinary
+ * chars using makeliteral_chr, and terminate with a \0
+ * character (which I know won't come up as a combining char
+ * itself).
+ *
+ * I don't use the stateful encoding in makeliteral_chr.
+ */
+ unsigned long zstate;
+ termchar z;
+
+ while (c->cc_next) {
+ c += c->cc_next;
+
+ assert(c->chr != 0);
+
+ zstate = 0;
+ makeliteral_chr(b, c, &zstate);
+ }
+
+ z.chr = 0;
+ zstate = 0;
+ makeliteral_chr(b, &z, &zstate);
+}
+
+static termline *decompressline(unsigned char *data, int *bytes_used);
+
+static unsigned char *compressline(termline *ldata)
+{
+ struct buf buffer = { NULL, 0, 0 }, *b = &buffer;
+
+ /*
+ * First, store the column count, 7 bits at a time, least
+ * significant `digit' first, with the high bit set on all but
+ * the last.
+ */
+ {
+ int n = ldata->cols;
+ while (n >= 128) {
+ add(b, (unsigned char)((n & 0x7F) | 0x80));
+ n >>= 7;
+ }
+ add(b, (unsigned char)(n));
+ }
+
+ /*
+ * Next store the lattrs; same principle.
+ */
+ {
+ int n = ldata->lattr;
+ while (n >= 128) {
+ add(b, (unsigned char)((n & 0x7F) | 0x80));
+ n >>= 7;
+ }
+ add(b, (unsigned char)(n));
+ }
+
+ /*
+ * Now we store a sequence of separate run-length encoded
+ * fragments, each containing exactly as many symbols as there
+ * are columns in the ldata.
+ *
+ * All of these have a common basic format:
+ *
+ * - a byte 00-7F indicates that X+1 literals follow it
+ * - a byte 80-FF indicates that a single literal follows it
+ * and expects to be repeated (X-0x80)+2 times.
+ *
+ * The format of the `literals' varies between the fragments.
+ */
+ makerle(b, ldata, makeliteral_chr);
+ makerle(b, ldata, makeliteral_attr);
+ makerle(b, ldata, makeliteral_cc);
+
+ /*
+ * Diagnostics: ensure that the compressed data really does
+ * decompress to the right thing.
+ *
+ * This is a bit performance-heavy for production code.
+ */
+#ifdef TERM_CC_DIAGS
+#ifndef CHECK_SB_COMPRESSION
+ {
+ int dused;
+ termline *dcl;
+ int i;
+
+#ifdef DIAGNOSTIC_SB_COMPRESSION
+ for (i = 0; i < b->len; i++) {
+ printf(" %02x ", b->data[i]);
+ }
+ printf("\n");
+#endif
+
+ dcl = decompressline(b->data, &dused);
+ assert(b->len == dused);
+ assert(ldata->cols == dcl->cols);
+ assert(ldata->lattr == dcl->lattr);
+ for (i = 0; i < ldata->cols; i++)
+ assert(termchars_equal(&ldata->chars[i], &dcl->chars[i]));
+
+#ifdef DIAGNOSTIC_SB_COMPRESSION
+ printf("%d cols (%d bytes) -> %d bytes (factor of %g)\n",
+ ldata->cols, 4 * ldata->cols, dused,
+ (double)dused / (4 * ldata->cols));
+#endif
+
+ freeline(dcl);
+ }
+#endif
+#endif /* TERM_CC_DIAGS */
+
+ /*
+ * Trim the allocated memory so we don't waste any, and return.
+ */
+ return sresize(b->data, b->len, unsigned char);
+}
+
+static void readrle(struct buf *b, termline *ldata,
+ void (*readliteral)(struct buf *b, termchar *c,
+ termline *ldata, unsigned long *state))
+{
+ int n = 0;
+ unsigned long state = 0;
+
+ while (n < ldata->cols) {
+ int hdr = get(b);
+
+ if (hdr >= 0x80) {
+ /* A run. */
+
+ int pos = b->len, count = hdr + 2 - 0x80;
+ while (count--) {
+ assert(n < ldata->cols);
+ b->len = pos;
+ readliteral(b, ldata->chars + n, ldata, &state);
+ n++;
+ }
+ } else {
+ /* Just a sequence of consecutive literals. */
+
+ int count = hdr + 1;
+ while (count--) {
+ assert(n < ldata->cols);
+ readliteral(b, ldata->chars + n, ldata, &state);
+ n++;
+ }
+ }
+ }
+
+ assert(n == ldata->cols);
+}
+static void readliteral_chr(struct buf *b, termchar *c, termline *ldata,
+ unsigned long *state)
+{
+ int byte;
+
+ /*
+ * 00000000-0000007F: 0xxxxxxx
+ * 00000080-00003FFF: 10xxxxxx xxxxxxxx
+ * 00004000-001FFFFF: 110xxxxx xxxxxxxx xxxxxxxx
+ * 00200000-0FFFFFFF: 1110xxxx xxxxxxxx xxxxxxxx xxxxxxxx
+ * 10000000-FFFFFFFF: 11110ZZZ xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx
+ */
+
+ byte = get(b);
+ if (byte < 0x80) {
+ c->chr = byte | *state;
+ } else if (byte < 0xC0) {
+ c->chr = (byte &~ 0xC0) << 8;
+ c->chr |= get(b);
+ } else if (byte < 0xE0) {
+ c->chr = (byte &~ 0xE0) << 16;
+ c->chr |= get(b) << 8;
+ c->chr |= get(b);
+ } else if (byte < 0xF0) {
+ c->chr = (byte &~ 0xF0) << 24;
+ c->chr |= get(b) << 16;
+ c->chr |= get(b) << 8;
+ c->chr |= get(b);
+ } else {
+ assert(byte == 0xF0);
+ c->chr = get(b) << 24;
+ c->chr |= get(b) << 16;
+ c->chr |= get(b) << 8;
+ c->chr |= get(b);
+ }
+ *state = c->chr & ~0xFF;
+}
+static void readliteral_attr(struct buf *b, termchar *c, termline *ldata,
+ unsigned long *state)
+{
+ unsigned val, attr, colourbits;
+
+ val = get(b) << 8;
+ val |= get(b);
+
+ if (val >= 0x8000) {
+ val &= ~0x8000;
+ val <<= 16;
+ val |= get(b) << 8;
+ val |= get(b);
+ }
+
+ colourbits = (val >> (32-9)) & 0xFF;
+ attr = (val & ((1<<(32-9))-1));
+
+ attr = (((attr >> (ATTR_FGSHIFT + 4)) << (ATTR_FGSHIFT + 8)) |
+ (attr & ((1 << (ATTR_FGSHIFT + 4))-1)));
+ attr = (((attr >> (ATTR_BGSHIFT + 4)) << (ATTR_BGSHIFT + 8)) |
+ (attr & ((1 << (ATTR_BGSHIFT + 4))-1)));
+
+ attr |= (colourbits >> 4) << (ATTR_BGSHIFT + 4);
+ attr |= (colourbits & 0xF) << (ATTR_FGSHIFT + 4);
+
+ c->attr = attr;
+}
+static void readliteral_cc(struct buf *b, termchar *c, termline *ldata,
+ unsigned long *state)
+{
+ termchar n;
+ unsigned long zstate;
+ int x = c - ldata->chars;
+
+ c->cc_next = 0;
+
+ while (1) {
+ zstate = 0;
+ readliteral_chr(b, &n, ldata, &zstate);
+ if (!n.chr)
+ break;
+ add_cc(ldata, x, n.chr);
+ }
+}
+
+static termline *decompressline(unsigned char *data, int *bytes_used)
+{
+ int ncols, byte, shift;
+ struct buf buffer, *b = &buffer;
+ termline *ldata;
+
+ b->data = data;
+ b->len = 0;
+
+ /*
+ * First read in the column count.
+ */
+ ncols = shift = 0;
+ do {
+ byte = get(b);
+ ncols |= (byte & 0x7F) << shift;
+ shift += 7;
+ } while (byte & 0x80);
+
+ /*
+ * Now create the output termline.
+ */
+ ldata = snew(termline);
+ ldata->chars = snewn(ncols, termchar);
+ ldata->cols = ldata->size = ncols;
+ ldata->temporary = TRUE;
+ ldata->cc_free = 0;
+
+ /*
+ * We must set all the cc pointers in ldata->chars to 0 right
+ * now, so that cc diagnostics that verify the integrity of the
+ * whole line will make sense while we're in the middle of
+ * building it up.
+ */
+ {
+ int i;
+ for (i = 0; i < ldata->cols; i++)
+ ldata->chars[i].cc_next = 0;
+ }
+
+ /*
+ * Now read in the lattr.
+ */
+ ldata->lattr = shift = 0;
+ do {
+ byte = get(b);
+ ldata->lattr |= (byte & 0x7F) << shift;
+ shift += 7;
+ } while (byte & 0x80);
+
+ /*
+ * Now we read in each of the RLE streams in turn.
+ */
+ readrle(b, ldata, readliteral_chr);
+ readrle(b, ldata, readliteral_attr);
+ readrle(b, ldata, readliteral_cc);
+
+ /* Return the number of bytes read, for diagnostic purposes. */
+ if (bytes_used)
+ *bytes_used = b->len;
+
+ return ldata;
+}
+
+/*
+ * Resize a line to make it `cols' columns wide.
+ */
+static void resizeline(Terminal *term, termline *line, int cols)
+{
+ int i, oldcols;
+
+ if (line->cols != cols) {
+
+ oldcols = line->cols;
+
+ /*
+ * This line is the wrong length, which probably means it
+ * hasn't been accessed since a resize. Resize it now.
+ *
+ * First, go through all the characters that will be thrown
+ * out in the resize (if we're shrinking the line) and
+ * return their cc lists to the cc free list.
+ */
+ for (i = cols; i < oldcols; i++)
+ clear_cc(line, i);
+
+ /*
+ * If we're shrinking the line, we now bodily move the
+ * entire cc section from where it started to where it now
+ * needs to be. (We have to do this before the resize, so
+ * that the data we're copying is still there. However, if
+ * we're expanding, we have to wait until _after_ the
+ * resize so that the space we're copying into is there.)
+ */
+ if (cols < oldcols)
+ memmove(line->chars + cols, line->chars + oldcols,
+ (line->size - line->cols) * TSIZE);
+
+ /*
+ * Now do the actual resize, leaving the _same_ amount of
+ * cc space as there was to begin with.
+ */
+ line->size += cols - oldcols;
+ line->chars = sresize(line->chars, line->size, TTYPE);
+ line->cols = cols;
+
+ /*
+ * If we're expanding the line, _now_ we move the cc
+ * section.
+ */
+ if (cols > oldcols)
+ memmove(line->chars + cols, line->chars + oldcols,
+ (line->size - line->cols) * TSIZE);
+
+ /*
+ * Go through what's left of the original line, and adjust
+ * the first cc_next pointer in each list. (All the
+ * subsequent ones are still valid because they are
+ * relative offsets within the cc block.) Also do the same
+ * to the head of the cc_free list.
+ */
+ for (i = 0; i < oldcols && i < cols; i++)
+ if (line->chars[i].cc_next)
+ line->chars[i].cc_next += cols - oldcols;
+ if (line->cc_free)
+ line->cc_free += cols - oldcols;
+
+ /*
+ * And finally fill in the new space with erase chars. (We
+ * don't have to worry about cc lists here, because we
+ * _know_ the erase char doesn't have one.)
+ */
+ for (i = oldcols; i < cols; i++)
+ line->chars[i] = term->basic_erase_char;
+
+#ifdef TERM_CC_DIAGS
+ cc_check(line);
+#endif
+ }
+}
+
+/*
+ * Get the number of lines in the scrollback.
+ */
+static int sblines(Terminal *term)
+{
+ int sblines = count234(term->scrollback);
+ if (term->cfg.erase_to_scrollback &&
+ term->alt_which && term->alt_screen) {
+ sblines += term->alt_sblines;
+ }
+ return sblines;
+}
+
+/*
+ * Retrieve a line of the screen or of the scrollback, according to
+ * whether the y coordinate is non-negative or negative
+ * (respectively).
+ */
+static termline *lineptr(Terminal *term, int y, int lineno, int screen)
+{
+ termline *line;
+ tree234 *whichtree;
+ int treeindex;
+
+ if (y >= 0) {
+ whichtree = term->screen;
+ treeindex = y;
+ } else {
+ int altlines = 0;
+
+ assert(!screen);
+
+ if (term->cfg.erase_to_scrollback &&
+ term->alt_which && term->alt_screen) {
+ altlines = term->alt_sblines;
+ }
+ if (y < -altlines) {
+ whichtree = term->scrollback;
+ treeindex = y + altlines + count234(term->scrollback);
+ } else {
+ whichtree = term->alt_screen;
+ treeindex = y + term->alt_sblines;
+ /* treeindex = y + count234(term->alt_screen); */
+ }
+ }
+ if (whichtree == term->scrollback) {
+ unsigned char *cline = index234(whichtree, treeindex);
+ line = decompressline(cline, NULL);
+ } else {
+ line = index234(whichtree, treeindex);
+ }
+
+ /* We assume that we don't screw up and retrieve something out of range. */
+ if (line == NULL) {
+ fatalbox("line==NULL in terminal.c\n"
+ "lineno=%d y=%d w=%d h=%d\n"
+ "count(scrollback=%p)=%d\n"
+ "count(screen=%p)=%d\n"
+ "count(alt=%p)=%d alt_sblines=%d\n"
+ "whichtree=%p treeindex=%d\n\n"
+ "Please contact <putty@projects.tartarus.org> "
+ "and pass on the above information.",
+ lineno, y, term->cols, term->rows,
+ term->scrollback, count234(term->scrollback),
+ term->screen, count234(term->screen),
+ term->alt_screen, count234(term->alt_screen), term->alt_sblines,
+ whichtree, treeindex);
+ }
+ assert(line != NULL);
+
+ resizeline(term, line, term->cols);
+ /* FIXME: should we sort the compressed scrollback out here? */
+
+ return line;
+}
+
+#define lineptr(x) (lineptr)(term,x,__LINE__,FALSE)
+#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)
+{
+ Terminal *term = (Terminal *)ctx;
+ int update = FALSE;
+
+ if (term->tblink_pending && now - term->next_tblink >= 0) {
+ term->tblinker = !term->tblinker;
+ term->tblink_pending = FALSE;
+ term_schedule_tblink(term);
+ update = TRUE;
+ }
+
+ if (term->cblink_pending && now - term->next_cblink >= 0) {
+ term->cblinker = !term->cblinker;
+ term->cblink_pending = FALSE;
+ term_schedule_cblink(term);
+ update = TRUE;
+ }
+
+ if (term->in_vbell && now - term->vbell_end >= 0) {
+ term->in_vbell = FALSE;
+ update = TRUE;
+ }
+
+ if (update ||
+ (term->window_update_pending && now - term->next_update >= 0))
+ term_update(term);
+}
+
+static void term_schedule_update(Terminal *term)
+{
+ if (!term->window_update_pending) {
+ term->window_update_pending = TRUE;
+ term->next_update = schedule_timer(UPDATE_DELAY, term_timer, term);
+ }
+}
+
+/*
+ * Call this whenever the terminal window state changes, to queue
+ * an update.
+ */
+static void seen_disp_event(Terminal *term)
+{
+ term->seen_disp_event = TRUE; /* for scrollback-reset-on-activity */
+ term_schedule_update(term);
+}
+
+/*
+ * Call when the terminal's blinking-text settings change, or when
+ * a text blink has just occurred.
+ */
+static void term_schedule_tblink(Terminal *term)
+{
+ if (term->blink_is_real) {
+ if (!term->tblink_pending)
+ term->next_tblink = schedule_timer(TBLINK_DELAY, term_timer, term);
+ term->tblink_pending = TRUE;
+ } else {
+ term->tblinker = 1; /* reset when not in use */
+ term->tblink_pending = FALSE;
+ }
+}
+
+/*
+ * Likewise with cursor blinks.
+ */
+static void term_schedule_cblink(Terminal *term)
+{
+ if (term->cfg.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 */
+ term->cblink_pending = FALSE;
+ }
+}
+
+/*
+ * Call to reset cursor blinking on new output.
+ */
+static void term_reset_cblink(Terminal *term)
+{
+ seen_disp_event(term);
+ term->cblinker = 1;
+ term->cblink_pending = FALSE;
+ term_schedule_cblink(term);
+}
+
+/*
+ * Call to begin a visual bell.
+ */
+static void term_schedule_vbell(Terminal *term, int already_started,
+ long startpoint)
+{
+ long ticks_already_gone;
+
+ if (already_started)
+ ticks_already_gone = GETTICKCOUNT() - startpoint;
+ else
+ ticks_already_gone = 0;
+
+ if (ticks_already_gone < VBELL_DELAY) {
+ term->in_vbell = TRUE;
+ term->vbell_end = schedule_timer(VBELL_DELAY - ticks_already_gone,
+ term_timer, term);
+ } else {
+ term->in_vbell = FALSE;
+ }
+}
+
+/*
+ * Set up power-on settings for the terminal.
+ * If 'clear' is false, don't actually clear the primary screen, and
+ * position the cursor below the last non-blank line (scrolling if
+ * necessary).
+ */
+static void power_on(Terminal *term, int clear)
+{
+ term->alt_x = term->alt_y = 0;
+ term->savecurs.x = term->savecurs.y = 0;
+ term->alt_savecurs.x = term->alt_savecurs.y = 0;
+ term->alt_t = term->marg_t = 0;
+ if (term->rows != -1)
+ term->alt_b = term->marg_b = term->rows - 1;
+ else
+ term->alt_b = term->marg_b = 0;
+ 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_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_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;
+ term->cset_attr[0] = term->cset_attr[1] =
+ term->save_csattr = term->alt_save_csattr = CSET_ASCII;
+ term->rvideo = 0;
+ term->in_vbell = FALSE;
+ 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->erase_char = term->basic_erase_char;
+ term->alt_which = 0;
+ term_print_finish(term);
+ term->xterm_mouse = 0;
+ set_raw_mouse_mode(term->frontend, FALSE);
+ {
+ int i;
+ for (i = 0; i < 256; i++)
+ term->wordness[i] = term->cfg.wordness[i];
+ }
+ if (term->screen) {
+ swap_screen(term, 1, FALSE, FALSE);
+ erase_lots(term, FALSE, TRUE, TRUE);
+ swap_screen(term, 0, FALSE, FALSE);
+ if (clear)
+ erase_lots(term, FALSE, TRUE, TRUE);
+ term->curs.y = find_last_nonempty_line(term, term->screen) + 1;
+ if (term->curs.y == term->rows) {
+ term->curs.y--;
+ scroll(term, 0, term->rows - 1, 1, TRUE);
+ }
+ } else {
+ term->curs.y = 0;
+ }
+ term->curs.x = 0;
+ term_schedule_tblink(term);
+ term_schedule_cblink(term);
+}
+
+/*
+ * Force a screen update.
+ */
+void term_update(Terminal *term)
+{
+ Context ctx;
+
+ 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) {
+ term->disptop = 0; /* return to main screen */
+ term->seen_disp_event = 0;
+ need_sbar_update = TRUE;
+ }
+
+ if (need_sbar_update)
+ update_sbar(term);
+ do_paint(term, ctx, TRUE);
+ sys_cursor(term->frontend, term->curs.x, term->curs.y - term->disptop);
+ free_ctx(ctx);
+ }
+}
+
+/*
+ * Called from front end when a keypress occurs, to trigger
+ * anything magical that needs to happen in that situation.
+ */
+void term_seen_key_event(Terminal *term)
+{
+ /*
+ * On any keypress, clear the bell overload mechanism
+ * completely, on the grounds that large numbers of
+ * beeps coming from deliberate key action are likely
+ * to be intended (e.g. beeps from filename completion
+ * blocking repeatedly).
+ */
+ term->beep_overloaded = FALSE;
+ while (term->beephead) {
+ struct beeptime *tmp = term->beephead;
+ term->beephead = tmp->next;
+ sfree(tmp);
+ }
+ term->beeptail = NULL;
+ term->nbeeps = 0;
+
+ /*
+ * Reset the scrollback on keypress, if we're doing that.
+ */
+ if (term->cfg.scroll_on_key) {
+ term->disptop = 0; /* return to main screen */
+ seen_disp_event(term);
+ }
+}
+
+/*
+ * Same as power_on(), but an external function.
+ */
+void term_pwron(Terminal *term, int clear)
+{
+ power_on(term, clear);
+ if (term->ldisc) /* cause ldisc to notice changes */
+ ldisc_send(term->ldisc, NULL, 0, 0);
+ term->disptop = 0;
+ deselect(term);
+ term_update(term);
+}
+
+static void set_erase_char(Terminal *term)
+{
+ term->erase_char = term->basic_erase_char;
+ if (term->use_bce)
+ term->erase_char.attr = (term->curr_attr &
+ (ATTR_FGMASK | ATTR_BGMASK));
+}
+
+/*
+ * 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)
+{
+ /*
+ * 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
+ * default one. The full list is: Auto wrap mode, DEC Origin
+ * 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_charclass = 0;
+ for (i = 0; i < lenof(term->cfg.wordness); i++)
+ if (term->cfg.wordness[i] != cfg->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) {
+ 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;
+ term->post_bidi_cache[i].width = -1;
+ term->post_bidi_cache[i].chars = NULL;
+ }
+ }
+
+ term->cfg = *cfg; /* STRUCTURE COPY */
+
+ if (reset_wrap)
+ term->alt_wrap = term->wrap = term->cfg.wrap_mode;
+ if (reset_decom)
+ term->alt_om = term->dec_om = term->cfg.dec_om;
+ if (reset_bce) {
+ term->use_bce = term->cfg.bce;
+ set_erase_char(term);
+ }
+ if (reset_tblink) {
+ term->blink_is_real = term->cfg.blinktext;
+ }
+ if (reset_charclass)
+ for (i = 0; i < 256; i++)
+ term->wordness[i] = term->cfg.wordness[i];
+
+ if (term->cfg.no_alt_screen)
+ swap_screen(term, 0, FALSE, FALSE);
+ if (term->cfg.no_mouse_rep) {
+ term->xterm_mouse = 0;
+ set_raw_mouse_mode(term->frontend, 0);
+ }
+ if (term->cfg.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) {
+ term_print_finish(term);
+ }
+ term_schedule_tblink(term);
+ term_schedule_cblink(term);
+}
+
+/*
+ * Clear the scrollback.
+ */
+void term_clrsb(Terminal *term)
+{
+ unsigned char *line;
+ term->disptop = 0;
+ while ((line = delpos234(term->scrollback, 0)) != NULL) {
+ sfree(line); /* this is compressed data, not a termline */
+ }
+ term->tempsblines = 0;
+ term->alt_sblines = 0;
+ update_sbar(term);
+}
+
+/*
+ * Initialise the terminal.
+ */
+Terminal *term_init(Config *mycfg, struct unicode_data *ucsdata,
+ void *frontend)
+{
+ Terminal *term;
+
+ /*
+ * Allocate a new Terminal structure and initialise the fields
+ * that need it.
+ */
+ term = snew(Terminal);
+ term->frontend = frontend;
+ term->ucsdata = ucsdata;
+ term->cfg = *mycfg; /* STRUCTURE COPY */
+ 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;
+ term->cr_lf_return = FALSE;
+ term->seen_disp_event = FALSE;
+ term->mouse_is_down = FALSE;
+ term->reset_132 = FALSE;
+ term->cblinker = term->tblinker = 0;
+ term->has_focus = 1;
+ term->repeat_off = FALSE;
+ term->termstate = TOPLEVEL;
+ term->selstate = NO_SELECTION;
+ term->curstype = 0;
+
+ term->screen = term->alt_screen = term->scrollback = NULL;
+ term->tempsblines = 0;
+ term->alt_sblines = 0;
+ term->disptop = 0;
+ term->disptext = NULL;
+ term->dispcursx = term->dispcursy = -1;
+ term->tabs = NULL;
+ deselect(term);
+ term->rows = term->cols = -1;
+ power_on(term, TRUE);
+ term->beephead = term->beeptail = NULL;
+#ifdef OPTIMISE_SCROLL
+ term->scrollhead = term->scrolltail = NULL;
+#endif /* OPTIMISE_SCROLL */
+ term->nbeeps = 0;
+ term->lastbeep = FALSE;
+ term->beep_overloaded = FALSE;
+ term->attr_mask = 0xffffffff;
+ term->resize_fn = NULL;
+ term->resize_ctx = NULL;
+ term->in_term_out = FALSE;
+ term->ltemp = NULL;
+ term->ltemp_size = 0;
+ term->wcFrom = NULL;
+ term->wcTo = NULL;
+ term->wcFromTo_size = 0;
+
+ term->window_update_pending = FALSE;
+
+ term->bidi_cache_size = 0;
+ term->pre_bidi_cache = term->post_bidi_cache = NULL;
+
+ /* FULL-TERMCHAR */
+ term->basic_erase_char.chr = CSET_ASCII | ' ';
+ term->basic_erase_char.attr = ATTR_DEFAULT;
+ term->basic_erase_char.cc_next = 0;
+ term->erase_char = term->basic_erase_char;
+
+ return term;
+}
+
+void term_free(Terminal *term)
+{
+ termline *line;
+ struct beeptime *beep;
+ int i;
+
+ while ((line = delpos234(term->scrollback, 0)) != NULL)
+ sfree(line); /* compressed data, not a termline */
+ freetree234(term->scrollback);
+ while ((line = delpos234(term->screen, 0)) != NULL)
+ freeline(line);
+ freetree234(term->screen);
+ while ((line = delpos234(term->alt_screen, 0)) != NULL)
+ freeline(line);
+ freetree234(term->alt_screen);
+ if (term->disptext) {
+ for (i = 0; i < term->rows; i++)
+ freeline(term->disptext[i]);
+ }
+ sfree(term->disptext);
+ while (term->beephead) {
+ beep = term->beephead;
+ term->beephead = beep->next;
+ sfree(beep);
+ }
+ bufchain_clear(&term->inbuf);
+ if(term->print_job)
+ printer_finish_job(term->print_job);
+ bufchain_clear(&term->printer_buf);
+ sfree(term->paste_buffer);
+ sfree(term->ltemp);
+ sfree(term->wcFrom);
+ 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->pre_bidi_cache);
+ sfree(term->post_bidi_cache);
+
+ expire_timer_context(term);
+
+ sfree(term);
+}
+
+/*
+ * Set up the terminal for a given size.
+ */
+void term_size(Terminal *term, int newrows, int newcols, int newsavelines)
+{
+ tree234 *newalt;
+ termline **newdisp, *line;
+ int i, j, oldrows = term->rows;
+ int sblen;
+ int save_alt_which = term->alt_which;
+
+ if (newrows == term->rows && newcols == term->cols &&
+ newsavelines == term->savelines)
+ return; /* nothing to do */
+
+ /* Behave sensibly if we're given zero (or negative) rows/cols */
+
+ if (newrows < 1) newrows = 1;
+ if (newcols < 1) newcols = 1;
+
+ deselect(term);
+ swap_screen(term, 0, FALSE, FALSE);
+
+ term->alt_t = term->marg_t = 0;
+ term->alt_b = term->marg_b = newrows - 1;
+
+ if (term->rows == -1) {
+ term->scrollback = newtree234(NULL);
+ term->screen = newtree234(NULL);
+ term->tempsblines = 0;
+ term->rows = 0;
+ }
+
+ /*
+ * Resize the screen and scrollback. We only need to shift
+ * lines around within our data structures, because lineptr()
+ * will take care of resizing each individual line if
+ * necessary. So:
+ *
+ * - If the new screen is longer, we shunt lines in from temporary
+ * scrollback if possible, otherwise we add new blank lines at
+ * the bottom.
+ *
+ * - If the new screen is shorter, we remove any blank lines at
+ * the bottom if possible, otherwise shunt lines above the cursor
+ * to scrollback if possible, otherwise delete lines below the
+ * cursor.
+ *
+ * - Then, if the new scrollback length is less than the
+ * amount of scrollback we actually have, we must throw some
+ * away.
+ */
+ sblen = count234(term->scrollback);
+ /* Do this loop to expand the screen if newrows > rows */
+ assert(term->rows == count234(term->screen));
+ while (term->rows < newrows) {
+ if (term->tempsblines > 0) {
+ unsigned char *cline;
+ /* Insert a line from the scrollback at the top of the screen. */
+ assert(sblen >= term->tempsblines);
+ cline = delpos234(term->scrollback, --sblen);
+ line = decompressline(cline, NULL);
+ sfree(cline);
+ line->temporary = FALSE; /* reconstituted line is now real */
+ term->tempsblines -= 1;
+ addpos234(term->screen, line, 0);
+ term->curs.y += 1;
+ term->savecurs.y += 1;
+ term->alt_y += 1;
+ term->alt_savecurs.y += 1;
+ } else {
+ /* Add a new blank line at the bottom of the screen. */
+ line = newline(term, newcols, FALSE);
+ addpos234(term->screen, line, count234(term->screen));
+ }
+ term->rows += 1;
+ }
+ /* 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));
+ } else {
+ /* push top row to scrollback */
+ line = delpos234(term->screen, 0);
+ addpos234(term->scrollback, compressline(line), sblen++);
+ freeline(line);
+ term->tempsblines += 1;
+ term->curs.y -= 1;
+ term->savecurs.y -= 1;
+ term->alt_y -= 1;
+ term->alt_savecurs.y -= 1;
+ }
+ term->rows -= 1;
+ }
+ assert(term->rows == newrows);
+ assert(count234(term->screen) == newrows);
+
+ /* Delete any excess lines from the scrollback. */
+ while (sblen > newsavelines) {
+ line = delpos234(term->scrollback, 0);
+ sfree(line);
+ sblen--;
+ }
+ if (sblen < term->tempsblines)
+ term->tempsblines = sblen;
+ assert(count234(term->scrollback) <= newsavelines);
+ assert(count234(term->scrollback) >= term->tempsblines);
+ term->disptop = 0;
+
+ /* Make a new displayed text buffer. */
+ newdisp = snewn(newrows, termline *);
+ for (i = 0; i < newrows; i++) {
+ newdisp[i] = newline(term, newcols, FALSE);
+ for (j = 0; j < newcols; j++)
+ newdisp[i]->chars[j].attr = ATTR_INVALID;
+ }
+ if (term->disptext) {
+ for (i = 0; i < oldrows; i++)
+ freeline(term->disptext[i]);
+ }
+ sfree(term->disptext);
+ term->disptext = newdisp;
+ term->dispcursx = term->dispcursy = -1;
+
+ /* Make a new alternate screen. */
+ newalt = newtree234(NULL);
+ for (i = 0; i < newrows; i++) {
+ line = newline(term, newcols, TRUE);
+ addpos234(newalt, line, i);
+ }
+ if (term->alt_screen) {
+ while (NULL != (line = delpos234(term->alt_screen, 0)))
+ freeline(line);
+ freetree234(term->alt_screen);
+ }
+ term->alt_screen = newalt;
+ term->alt_sblines = 0;
+
+ term->tabs = sresize(term->tabs, newcols, unsigned char);
+ {
+ int i;
+ for (i = (term->cols > 0 ? term->cols : 0); i < newcols; i++)
+ term->tabs[i] = (i % 8 == 0 ? TRUE : FALSE);
+ }
+
+ /* Check that the cursor positions are still valid. */
+ if (term->savecurs.y < 0)
+ term->savecurs.y = 0;
+ if (term->savecurs.y >= newrows)
+ term->savecurs.y = newrows - 1;
+ if (term->savecurs.x >= newcols)
+ term->savecurs.x = newcols - 1;
+ if (term->alt_savecurs.y < 0)
+ term->alt_savecurs.y = 0;
+ if (term->alt_savecurs.y >= newrows)
+ term->alt_savecurs.y = newrows - 1;
+ if (term->alt_savecurs.x >= newcols)
+ term->alt_savecurs.x = newcols - 1;
+ if (term->curs.y < 0)
+ term->curs.y = 0;
+ if (term->curs.y >= newrows)
+ term->curs.y = newrows - 1;
+ if (term->curs.x >= newcols)
+ term->curs.x = newcols - 1;
+ if (term->alt_y < 0)
+ term->alt_y = 0;
+ if (term->alt_y >= newrows)
+ term->alt_y = newrows - 1;
+ if (term->alt_x >= newcols)
+ term->alt_x = newcols - 1;
+ term->alt_x = term->alt_y = 0;
+ term->wrapnext = term->alt_wnext = FALSE;
+
+ term->rows = newrows;
+ term->cols = newcols;
+ term->savelines = newsavelines;
+
+ swap_screen(term, save_alt_which, FALSE, FALSE);
+
+ update_sbar(term);
+ term_update(term);
+ if (term->resize_fn)
+ term->resize_fn(term->resize_ctx, term->cols, term->rows);
+}
+
+/*
+ * Hand a function and context pointer to the terminal which it can
+ * use to notify a back end of resizes.
+ */
+void term_provide_resize_fn(Terminal *term,
+ void (*resize_fn)(void *, int, int),
+ void *resize_ctx)
+{
+ term->resize_fn = resize_fn;
+ term->resize_ctx = resize_ctx;
+ if (resize_fn && term->cols > 0 && term->rows > 0)
+ resize_fn(resize_ctx, term->cols, term->rows);
+}
+
+/* Find the bottom line on the screen that has any content.
+ * If only the top line has content, returns 0.
+ * If no lines have content, return -1.
+ */
+static int find_last_nonempty_line(Terminal * term, tree234 * screen)
+{
+ int i;
+ for (i = count234(screen) - 1; i >= 0; i--) {
+ termline *line = index234(screen, i);
+ int j;
+ for (j = 0; j < line->cols; j++)
+ if (!termchars_equal(&line->chars[j], &term->erase_char))
+ break;
+ if (j != line->cols) break;
+ }
+ return i;
+}
+
+/*
+ * Swap screens. If `reset' is TRUE and we have been asked to
+ * switch to the alternate screen, we must bring most of its
+ * configuration from the main screen and erase the contents of the
+ * alternate screen completely. (This is even true if we're already
+ * on it! Blame xterm.)
+ */
+static void swap_screen(Terminal *term, int which, int reset, int keep_cur_pos)
+{
+ int t;
+ pos tp;
+ tree234 *ttr;
+
+ if (!which)
+ reset = FALSE; /* do no weird resetting if which==0 */
+
+ if (which != term->alt_which) {
+ term->alt_which = which;
+
+ ttr = term->alt_screen;
+ term->alt_screen = term->screen;
+ term->screen = ttr;
+ term->alt_sblines = find_last_nonempty_line(term, term->alt_screen) + 1;
+ t = term->curs.x;
+ if (!reset && !keep_cur_pos)
+ term->curs.x = term->alt_x;
+ term->alt_x = t;
+ t = term->curs.y;
+ if (!reset && !keep_cur_pos)
+ term->curs.y = term->alt_y;
+ term->alt_y = t;
+ t = term->marg_t;
+ if (!reset) term->marg_t = term->alt_t;
+ term->alt_t = t;
+ t = term->marg_b;
+ if (!reset) term->marg_b = term->alt_b;
+ term->alt_b = t;
+ t = term->dec_om;
+ if (!reset) term->dec_om = term->alt_om;
+ term->alt_om = t;
+ t = term->wrap;
+ if (!reset) term->wrap = term->alt_wrap;
+ term->alt_wrap = t;
+ t = term->wrapnext;
+ if (!reset) term->wrapnext = term->alt_wnext;
+ term->alt_wnext = t;
+ t = term->insert;
+ if (!reset) term->insert = term->alt_ins;
+ term->alt_ins = t;
+ t = term->cset;
+ if (!reset) term->cset = term->alt_cset;
+ term->alt_cset = t;
+ t = term->utf;
+ if (!reset) term->utf = term->alt_utf;
+ term->alt_utf = t;
+ t = term->sco_acs;
+ if (!reset) term->sco_acs = term->alt_sco_acs;
+ term->alt_sco_acs = t;
+
+ tp = term->savecurs;
+ if (!reset && !keep_cur_pos)
+ term->savecurs = term->alt_savecurs;
+ term->alt_savecurs = tp;
+ t = term->save_cset;
+ if (!reset && !keep_cur_pos)
+ term->save_cset = term->alt_save_cset;
+ term->alt_save_cset = t;
+ t = term->save_csattr;
+ if (!reset && !keep_cur_pos)
+ term->save_csattr = term->alt_save_csattr;
+ term->alt_save_csattr = t;
+ t = term->save_attr;
+ if (!reset && !keep_cur_pos)
+ term->save_attr = term->alt_save_attr;
+ term->alt_save_attr = t;
+ t = term->save_utf;
+ if (!reset && !keep_cur_pos)
+ term->save_utf = term->alt_save_utf;
+ term->alt_save_utf = t;
+ t = term->save_wnext;
+ if (!reset && !keep_cur_pos)
+ term->save_wnext = term->alt_save_wnext;
+ term->alt_save_wnext = t;
+ t = term->save_sco_acs;
+ if (!reset && !keep_cur_pos)
+ term->save_sco_acs = term->alt_save_sco_acs;
+ term->alt_save_sco_acs = t;
+ }
+
+ if (reset && term->screen) {
+ /*
+ * Yes, this _is_ supposed to honour background-colour-erase.
+ */
+ erase_lots(term, FALSE, TRUE, TRUE);
+ }
+}
+
+/*
+ * Update the scroll bar.
+ */
+static void update_sbar(Terminal *term)
+{
+ int nscroll = sblines(term);
+ set_sbar(term->frontend, nscroll + term->rows,
+ nscroll + term->disptop, term->rows);
+}
+
+/*
+ * Check whether the region bounded by the two pointers intersects
+ * the scroll region, and de-select the on-screen selection if so.
+ */
+static void check_selection(Terminal *term, pos from, pos to)
+{
+ if (poslt(from, term->selend) && poslt(term->selstart, to))
+ deselect(term);
+}
+
+/*
+ * Scroll the screen. (`lines' is +ve for scrolling forward, -ve
+ * for backward.) `sb' is TRUE if the scrolling is permitted to
+ * affect the scrollback buffer.
+ */
+static void scroll(Terminal *term, int topline, int botline, int lines, int sb)
+{
+ termline *line;
+ int i, seltop;
+#ifdef OPTIMISE_SCROLL
+ int olddisptop, shift;
+#endif /* OPTIMISE_SCROLL */
+
+ if (topline != 0 || term->alt_which != 0)
+ sb = FALSE;
+
+#ifdef OPTIMISE_SCROLL
+ olddisptop = term->disptop;
+ shift = lines;
+#endif /* OPTIMISE_SCROLL */
+ if (lines < 0) {
+ 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;
+ addpos234(term->screen, line, topline);
+
+ if (term->selstart.y >= topline && term->selstart.y <= botline) {
+ term->selstart.y++;
+ if (term->selstart.y > botline) {
+ term->selstart.y = botline + 1;
+ term->selstart.x = 0;
+ }
+ }
+ if (term->selend.y >= topline && term->selend.y <= botline) {
+ term->selend.y++;
+ if (term->selend.y > botline) {
+ term->selend.y = botline + 1;
+ term->selend.x = 0;
+ }
+ }
+
+ lines++;
+ }
+ } else {
+ while (lines > 0) {
+ line = delpos234(term->screen, topline);
+#ifdef TERM_CC_DIAGS
+ cc_check(line);
+#endif
+ if (sb && term->savelines > 0) {
+ int sblen = count234(term->scrollback);
+ /*
+ * We must add this line to the scrollback. We'll
+ * remove a line from the top of the scrollback if
+ * the scrollback is full.
+ */
+ if (sblen == term->savelines) {
+ unsigned char *cline;
+
+ sblen--;
+ cline = delpos234(term->scrollback, 0);
+ sfree(cline);
+ } else
+ term->tempsblines += 1;
+
+ addpos234(term->scrollback, compressline(line), sblen);
+
+ /* now `line' itself can be reused as the bottom line */
+
+ /*
+ * If the user is currently looking at part of the
+ * scrollback, and they haven't enabled any options
+ * that are going to reset the scrollback as a
+ * result of this movement, then the chances are
+ * they'd like to keep looking at the same line. So
+ * we move their viewpoint at the same rate as the
+ * scroll, at least until their viewpoint hits the
+ * top end of the scrollback buffer, at which point
+ * we don't have the choice any more.
+ *
+ * Thanks to Jan Holmen Holsten for the idea and
+ * initial implementation.
+ */
+ if (term->disptop > -term->savelines && term->disptop < 0)
+ term->disptop--;
+ }
+ resizeline(term, line, term->cols);
+ for (i = 0; i < term->cols; i++)
+ copy_termchar(line, i, &term->erase_char);
+ line->lattr = LATTR_NORM;
+ addpos234(term->screen, line, botline);
+
+ /*
+ * If the selection endpoints move into the scrollback,
+ * we keep them moving until they hit the top. However,
+ * of course, if the line _hasn't_ moved into the
+ * scrollback then we don't do this, and cut them off
+ * at the top of the scroll region.
+ *
+ * This applies to selstart and selend (for an existing
+ * selection), and also selanchor (for one being
+ * selected as we speak).
+ */
+ seltop = sb ? -term->savelines : topline;
+
+ if (term->selstate != NO_SELECTION) {
+ if (term->selstart.y >= seltop &&
+ term->selstart.y <= botline) {
+ term->selstart.y--;
+ if (term->selstart.y < seltop) {
+ term->selstart.y = seltop;
+ term->selstart.x = 0;
+ }
+ }
+ if (term->selend.y >= seltop && term->selend.y <= botline) {
+ term->selend.y--;
+ if (term->selend.y < seltop) {
+ term->selend.y = seltop;
+ term->selend.x = 0;
+ }
+ }
+ if (term->selanchor.y >= seltop &&
+ term->selanchor.y <= botline) {
+ term->selanchor.y--;
+ if (term->selanchor.y < seltop) {
+ 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)
+ scroll_display(term, topline, botline, shift);
+#endif /* OPTIMISE_SCROLL */
+}
+
+#ifdef OPTIMISE_SCROLL
+/*
+ * Add a scroll of a region on the screen into the pending scroll list.
+ * `lines' is +ve for scrolling forward, -ve for backward.
+ *
+ * If the scroll is on the same area as the last scroll in the list,
+ * merge them.
+ */
+static void save_scroll(Terminal *term, int topline, int botline, int lines)
+{
+ struct scrollregion *newscroll;
+ if (term->scrolltail &&
+ term->scrolltail->topline == topline &&
+ term->scrolltail->botline == botline) {
+ term->scrolltail->lines += lines;
+ } else {
+ newscroll = snew(struct scrollregion);
+ newscroll->topline = topline;
+ newscroll->botline = botline;
+ newscroll->lines = lines;
+ newscroll->next = NULL;
+
+ if (!term->scrollhead)
+ term->scrollhead = newscroll;
+ else
+ term->scrolltail->next = newscroll;
+ term->scrolltail = newscroll;
+ }
+}
+
+/*
+ * Scroll the physical display, and our conception of it in disptext.
+ */
+static void scroll_display(Terminal *term, int topline, int botline, int lines)
+{
+ int distance, nlines, i, j;
+
+ distance = lines > 0 ? lines : -lines;
+ nlines = botline - topline + 1 - distance;
+ if (lines > 0) {
+ for (i = 0; i < nlines; i++)
+ for (j = 0; j < term->cols; j++)
+ copy_termchar(term->disptext[i], j,
+ term->disptext[i+distance]->chars+j);
+ if (term->dispcursy >= 0 &&
+ term->dispcursy >= topline + distance &&
+ term->dispcursy < topline + distance + nlines)
+ term->dispcursy -= distance;
+ for (i = 0; i < distance; i++)
+ for (j = 0; j < term->cols; j++)
+ term->disptext[nlines+i]->chars[j].attr |= ATTR_INVALID;
+ } else {
+ for (i = nlines; i-- ;)
+ for (j = 0; j < term->cols; j++)
+ copy_termchar(term->disptext[i+distance], j,
+ term->disptext[i]->chars+j);
+ if (term->dispcursy >= 0 &&
+ term->dispcursy >= topline &&
+ term->dispcursy < topline + nlines)
+ term->dispcursy += distance;
+ for (i = 0; i < distance; i++)
+ for (j = 0; j < term->cols; j++)
+ term->disptext[i]->chars[j].attr |= ATTR_INVALID;
+ }
+ save_scroll(term, topline, botline, lines);
+}
+#endif /* OPTIMISE_SCROLL */
+
+/*
+ * Move the cursor to a given position, clipping at boundaries. We
+ * may or may not want to clip at the scroll margin: marg_clip is 0
+ * not to, 1 to disallow _passing_ the margins, and 2 to disallow
+ * even _being_ outside the margins.
+ */
+static void move(Terminal *term, int x, int y, int marg_clip)
+{
+ if (x < 0)
+ x = 0;
+ if (x >= term->cols)
+ x = term->cols - 1;
+ if (marg_clip) {
+ if ((term->curs.y >= term->marg_t || marg_clip == 2) &&
+ y < term->marg_t)
+ y = term->marg_t;
+ if ((term->curs.y <= term->marg_b || marg_clip == 2) &&
+ y > term->marg_b)
+ y = term->marg_b;
+ }
+ if (y < 0)
+ y = 0;
+ if (y >= term->rows)
+ y = term->rows - 1;
+ term->curs.x = x;
+ term->curs.y = y;
+ term->wrapnext = FALSE;
+}
+
+/*
+ * Save or restore the cursor and SGR mode.
+ */
+static void save_cursor(Terminal *term, int save)
+{
+ if (save) {
+ term->savecurs = term->curs;
+ term->save_attr = term->curr_attr;
+ term->save_cset = term->cset;
+ term->save_utf = term->utf;
+ term->save_wnext = term->wrapnext;
+ term->save_csattr = term->cset_attr[term->cset];
+ term->save_sco_acs = term->sco_acs;
+ } else {
+ term->curs = term->savecurs;
+ /* Make sure the window hasn't shrunk since the save */
+ if (term->curs.x >= term->cols)
+ term->curs.x = term->cols - 1;
+ if (term->curs.y >= term->rows)
+ term->curs.y = term->rows - 1;
+
+ term->curr_attr = term->save_attr;
+ term->cset = term->save_cset;
+ term->utf = term->save_utf;
+ term->wrapnext = term->save_wnext;
+ /*
+ * wrapnext might reset to False if the x position is no
+ * longer at the rightmost edge.
+ */
+ if (term->wrapnext && term->curs.x < term->cols-1)
+ term->wrapnext = FALSE;
+ term->cset_attr[term->cset] = term->save_csattr;
+ term->sco_acs = term->save_sco_acs;
+ set_erase_char(term);
+ }
+}
+
+/*
+ * This function is called before doing _anything_ which affects
+ * only part of a line of text. It is used to mark the boundary
+ * between two character positions, and it indicates that some sort
+ * of effect is going to happen on only one side of that boundary.
+ *
+ * The effect of this function is to check whether a CJK
+ * double-width character is straddling the boundary, and to remove
+ * it and replace it with two spaces if so. (Of course, one or
+ * other of those spaces is then likely to be replaced with
+ * something else again, as a result of whatever happens next.)
+ *
+ * Also, if the boundary is at the right-hand _edge_ of the screen,
+ * it implies something deliberate is being done to the rightmost
+ * column position; hence we must clear LATTR_WRAPPED2.
+ *
+ * The input to the function is the coordinates of the _second_
+ * character of the pair.
+ */
+static void check_boundary(Terminal *term, int x, int y)
+{
+ termline *ldata;
+
+ /* Validate input coordinates, just in case. */
+ if (x == 0 || x > term->cols)
+ return;
+
+ ldata = scrlineptr(y);
+ if (x == term->cols) {
+ ldata->lattr &= ~LATTR_WRAPPED2;
+ } else {
+ if (ldata->chars[x].chr == UCSWIDE) {
+ clear_cc(ldata, x-1);
+ clear_cc(ldata, x);
+ ldata->chars[x-1].chr = ' ' | CSET_ASCII;
+ ldata->chars[x] = ldata->chars[x-1];
+ }
+ }
+}
+
+/*
+ * Erase a large portion of the screen: the whole screen, or the
+ * whole line, or parts thereof.
+ */
+static void erase_lots(Terminal *term,
+ int line_only, int from_begin, int to_end)
+{
+ pos start, end;
+ int erase_lattr;
+ int erasing_lines_from_top = 0;
+
+ if (line_only) {
+ start.y = term->curs.y;
+ start.x = 0;
+ end.y = term->curs.y + 1;
+ end.x = 0;
+ erase_lattr = FALSE;
+ } else {
+ start.y = 0;
+ start.x = 0;
+ end.y = term->rows;
+ end.x = 0;
+ erase_lattr = TRUE;
+ }
+ if (!from_begin) {
+ start = term->curs;
+ }
+ if (!to_end) {
+ end = term->curs;
+ incpos(end);
+ }
+ if (!from_begin || !to_end)
+ check_boundary(term, term->curs.x, term->curs.y);
+ check_selection(term, start, end);
+
+ /* Clear screen also forces a full window redraw, just in case. */
+ if (start.y == 0 && start.x == 0 && end.y == term->rows)
+ term_invalidate(term);
+
+ /* 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 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) {
+ /* Shrink until we find a non-empty row.*/
+ scrolllines = find_last_nonempty_line(term, term->screen) + 1;
+ }
+ if (scrolllines > 0)
+ scroll(term, 0, scrolllines - 1, scrolllines, TRUE);
+ } else {
+ termline *ldata = scrlineptr(start.y);
+ while (poslt(start, end)) {
+ if (start.x == term->cols) {
+ if (!erase_lattr)
+ ldata->lattr &= ~(LATTR_WRAPPED | LATTR_WRAPPED2);
+ else
+ ldata->lattr = LATTR_NORM;
+ } else {
+ copy_termchar(ldata, start.x, &term->erase_char);
+ }
+ if (incpos(start) && start.y < term->rows) {
+ ldata = scrlineptr(start.y);
+ }
+ }
+ }
+
+ /* After an erase of lines from the top of the screen, we shouldn't
+ * bring the lines back again if the terminal enlarges (since the user or
+ * application has explictly thrown them away). */
+ if (erasing_lines_from_top && !(term->alt_which))
+ term->tempsblines = 0;
+}
+
+/*
+ * Insert or delete characters within the current line. n is +ve if
+ * insertion is desired, and -ve for deletion.
+ */
+static void insch(Terminal *term, int n)
+{
+ int dir = (n < 0 ? -1 : +1);
+ int m, j;
+ pos cursplus;
+ termline *ldata;
+
+ n = (n < 0 ? -n : n);
+ if (n > term->cols - term->curs.x)
+ n = term->cols - term->curs.x;
+ m = term->cols - term->curs.x - n;
+ cursplus.y = term->curs.y;
+ cursplus.x = term->curs.x + n;
+ check_selection(term, term->curs, cursplus);
+ check_boundary(term, term->curs.x, term->curs.y);
+ if (dir < 0)
+ check_boundary(term, term->curs.x + n, term->curs.y);
+ ldata = scrlineptr(term->curs.y);
+ if (dir < 0) {
+ for (j = 0; j < m; j++)
+ move_termchar(ldata,
+ ldata->chars + term->curs.x + j,
+ ldata->chars + term->curs.x + j + n);
+ while (n--)
+ copy_termchar(ldata, term->curs.x + m++, &term->erase_char);
+ } else {
+ for (j = m; j-- ;)
+ move_termchar(ldata,
+ ldata->chars + term->curs.x + j + n,
+ ldata->chars + term->curs.x + j);
+ while (n--)
+ copy_termchar(ldata, term->curs.x + n, &term->erase_char);
+ }
+}
+
+/*
+ * Toggle terminal mode `mode' to state `state'. (`query' indicates
+ * whether the mode is a DEC private one or a normal one.)
+ */
+static void toggle_mode(Terminal *term, int mode, int query, int state)
+{
+ if (query)
+ switch (mode) {
+ case 1: /* DECCKM: application cursor keys */
+ term->app_cursor_keys = state;
+ break;
+ case 2: /* DECANM: VT52 mode */
+ 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_schedule_tblink(term);
+ break;
+ case 3: /* DECCOLM: 80/132 columns */
+ deselect(term);
+ if (!term->cfg.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);
+ erase_lots(term, FALSE, TRUE, TRUE);
+ break;
+ case 5: /* DECSCNM: reverse video */
+ /*
+ * Toggle reverse video. If we receive an OFF within the
+ * visual bell timeout period after an ON, we trigger an
+ * effective visual bell, so that ESC[?5hESC[?5l will
+ * always be an actually _visible_ visual bell.
+ */
+ if (term->rvideo && !state) {
+ /* This is an OFF, so set up a vbell */
+ term_schedule_vbell(term, TRUE, term->rvbell_startpoint);
+ } else if (!term->rvideo && state) {
+ /* This is an ON, so we notice the time and save it. */
+ term->rvbell_startpoint = GETTICKCOUNT();
+ }
+ term->rvideo = state;
+ seen_disp_event(term);
+ break;
+ case 6: /* DECOM: DEC origin mode */
+ term->dec_om = state;
+ break;
+ case 7: /* DECAWM: auto wrap */
+ term->wrap = state;
+ break;
+ case 8: /* DECARM: auto key repeat */
+ term->repeat_off = !state;
+ break;
+ case 10: /* DECEDM: set local edit mode */
+ term->term_editing = state;
+ if (term->ldisc) /* cause ldisc to notice changes */
+ ldisc_send(term->ldisc, NULL, 0, 0);
+ break;
+ case 25: /* DECTCEM: enable/disable cursor */
+ compatibility2(OTHER, VT220);
+ term->cursor_on = state;
+ 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);
+ 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 1047: /* alternate screen */
+ compatibility(OTHER);
+ deselect(term);
+ swap_screen(term, term->cfg.no_alt_screen ? 0 : state, TRUE, TRUE);
+ term->disptop = 0;
+ break;
+ case 1048: /* save/restore cursor */
+ if (!term->cfg.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)
+ 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)
+ save_cursor(term, state);
+ term->disptop = 0;
+ break;
+ } else
+ switch (mode) {
+ case 4: /* IRM: set insert mode */
+ compatibility(VT102);
+ term->insert = state;
+ break;
+ case 12: /* SRM: set echo mode */
+ term->term_echoing = !state;
+ if (term->ldisc) /* cause ldisc to notice changes */
+ ldisc_send(term->ldisc, NULL, 0, 0);
+ break;
+ case 20: /* LNM: Return sends ... */
+ term->cr_lf_return = state;
+ break;
+ case 34: /* WYULCURM: Make cursor BIG */
+ compatibility2(OTHER, VT220);
+ term->big_cursor = !state;
+ }
+}
+
+/*
+ * Process an OSC sequence: set window title or icon name.
+ */
+static void do_osc(Terminal *term)
+{
+ if (term->osc_w) {
+ while (term->osc_strlen--)
+ term->wordness[(unsigned char)
+ term->osc_string[term->osc_strlen]] = term->esc_args[0];
+ } else {
+ term->osc_string[term->osc_strlen] = '\0';
+ switch (term->esc_args[0]) {
+ case 0:
+ case 1:
+ if (!term->cfg.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)
+ set_title(term->frontend, term->osc_string);
+ break;
+ }
+ }
+}
+
+/*
+ * ANSI printing routines.
+ */
+static void term_print_setup(Terminal *term)
+{
+ bufchain_clear(&term->printer_buf);
+ term->print_job = printer_start_job(term->cfg.printer);
+}
+static void term_print_flush(Terminal *term)
+{
+ void *data;
+ int len;
+ int size;
+ while ((size = bufchain_size(&term->printer_buf)) > 5) {
+ bufchain_prefix(&term->printer_buf, &data, &len);
+ if (len > size-5)
+ len = size-5;
+ printer_job_data(term->print_job, data, len);
+ bufchain_consume(&term->printer_buf, len);
+ }
+}
+static void term_print_finish(Terminal *term)
+{
+ void *data;
+ int len, size;
+ char c;
+
+ if (!term->printing && !term->only_printing)
+ return; /* we need do nothing */
+
+ term_print_flush(term);
+ while ((size = bufchain_size(&term->printer_buf)) > 0) {
+ bufchain_prefix(&term->printer_buf, &data, &len);
+ c = *(char *)data;
+ if (c == '\033' || c == '\233') {
+ bufchain_consume(&term->printer_buf, size);
+ break;
+ } else {
+ printer_job_data(term->print_job, &c, 1);
+ bufchain_consume(&term->printer_buf, 1);
+ }
+ }
+ printer_finish_job(term->print_job);
+ term->print_job = NULL;
+ term->printing = term->only_printing = FALSE;
+}
+
+/*
+ * Remove everything currently in `inbuf' and stick it up on the
+ * in-memory display. There's a big state machine in here to
+ * process escape sequences...
+ */
+static void term_out(Terminal *term)
+{
+ unsigned long c;
+ int unget;
+ unsigned char localbuf[256], *chars;
+ int nchars = 0;
+
+ unget = -1;
+
+ chars = NULL; /* placate compiler warnings */
+ while (nchars > 0 || unget != -1 || bufchain_size(&term->inbuf) > 0) {
+ if (unget == -1) {
+ if (nchars == 0) {
+ void *ret;
+ bufchain_prefix(&term->inbuf, &ret, &nchars);
+ if (nchars > sizeof(localbuf))
+ nchars = sizeof(localbuf);
+ memcpy(localbuf, ret, nchars);
+ bufchain_consume(&term->inbuf, nchars);
+ chars = localbuf;
+ assert(chars != NULL);
+ }
+ c = *chars++;
+ nchars--;
+
+ /*
+ * 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)
+ logtraffic(term->logctx, (unsigned char) c, LGTYP_DEBUG);
+ } else {
+ c = unget;
+ unget = -1;
+ }
+
+ /* Note only VT220+ are 8-bit VT102 is seven bit, it shouldn't even
+ * be able to display 8-bit characters, but I'll let that go 'cause
+ * of i18n.
+ */
+
+ /*
+ * If we're printing, add the character to the printer
+ * buffer.
+ */
+ if (term->printing) {
+ bufchain_add(&term->printer_buf, &c, 1);
+
+ /*
+ * If we're in print-only mode, we use a much simpler
+ * state machine designed only to recognise the ESC[4i
+ * termination sequence.
+ */
+ if (term->only_printing) {
+ if (c == '\033')
+ term->print_state = 1;
+ else if (c == (unsigned char)'\233')
+ term->print_state = 2;
+ else if (c == '[' && term->print_state == 1)
+ term->print_state = 2;
+ else if (c == '4' && term->print_state == 2)
+ term->print_state = 3;
+ else if (c == 'i' && term->print_state == 3)
+ term->print_state = 4;
+ else
+ term->print_state = 0;
+ if (term->print_state == 4) {
+ term_print_finish(term);
+ }
+ continue;
+ }
+ }
+
+ /* First see about all those translations. */
+ if (term->termstate == TOPLEVEL) {
+ if (in_utf(term))
+ switch (term->utf_state) {
+ case 0:
+ if (c < 0x80) {
+ /* UTF-8 must be stateless so we ignore iso2022. */
+ if (term->ucsdata->unitab_ctrl[c] != 0xFF)
+ c = term->ucsdata->unitab_ctrl[c];
+ else c = ((unsigned char)c) | CSET_ASCII;
+ break;
+ } else if ((c & 0xe0) == 0xc0) {
+ term->utf_size = term->utf_state = 1;
+ term->utf_char = (c & 0x1f);
+ } else if ((c & 0xf0) == 0xe0) {
+ term->utf_size = term->utf_state = 2;
+ term->utf_char = (c & 0x0f);
+ } else if ((c & 0xf8) == 0xf0) {
+ term->utf_size = term->utf_state = 3;
+ term->utf_char = (c & 0x07);
+ } else if ((c & 0xfc) == 0xf8) {
+ term->utf_size = term->utf_state = 4;
+ term->utf_char = (c & 0x03);
+ } else if ((c & 0xfe) == 0xfc) {
+ term->utf_size = term->utf_state = 5;
+ term->utf_char = (c & 0x01);
+ } else {
+ c = UCSERR;
+ break;
+ }
+ continue;
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ if ((c & 0xC0) != 0x80) {
+ unget = c;
+ c = UCSERR;
+ term->utf_state = 0;
+ break;
+ }
+ term->utf_char = (term->utf_char << 6) | (c & 0x3f);
+ if (--term->utf_state)
+ continue;
+
+ c = term->utf_char;
+
+ /* Is somebody trying to be evil! */
+ if (c < 0x80 ||
+ (c < 0x800 && term->utf_size >= 2) ||
+ (c < 0x10000 && term->utf_size >= 3) ||
+ (c < 0x200000 && term->utf_size >= 4) ||
+ (c < 0x4000000 && term->utf_size >= 5))
+ c = UCSERR;
+
+ /* Unicode line separator and paragraph separator are CR-LF */
+ if (c == 0x2028 || c == 0x2029)
+ c = 0x85;
+
+ /* High controls are probably a Baaad idea too. */
+ if (c < 0xA0)
+ c = 0xFFFD;
+
+ /* The UTF-16 surrogates are not nice either. */
+ /* The standard give the option of decoding these:
+ * I don't want to! */
+ if (c >= 0xD800 && c < 0xE000)
+ c = UCSERR;
+
+ /* ISO 10646 characters now limited to UTF-16 range. */
+ if (c > 0x10FFFF)
+ c = UCSERR;
+
+ /* This is currently a TagPhobic application.. */
+ if (c >= 0xE0000 && c <= 0xE007F)
+ continue;
+
+ /* U+FEFF is best seen as a null. */
+ if (c == 0xFEFF)
+ continue;
+ /* But U+FFFE is an error. */
+ if (c == 0xFFFE || c == 0xFFFF)
+ c = UCSERR;
+
+ break;
+ }
+ /* Are we in the nasty ACS mode? Note: no sco in utf mode. */
+ else if(term->sco_acs &&
+ (c!='\033' && c!='\012' && c!='\015' && c!='\b'))
+ {
+ if (term->sco_acs == 2) c |= 0x80;
+ c |= CSET_SCOACS;
+ } else {
+ switch (term->cset_attr[term->cset]) {
+ /*
+ * Linedraw characters are different from 'ESC ( B'
+ * only for a small range. For ones outside that
+ * range, make sure we use the same font as well as
+ * the same encoding.
+ */
+ case CSET_LINEDRW:
+ if (term->ucsdata->unitab_ctrl[c] != 0xFF)
+ c = term->ucsdata->unitab_ctrl[c];
+ else
+ c = ((unsigned char) c) | CSET_LINEDRW;
+ break;
+
+ case CSET_GBCHR:
+ /* If UK-ASCII, make the '#' a LineDraw Pound */
+ if (c == '#') {
+ c = '}' | CSET_LINEDRW;
+ break;
+ }
+ /*FALLTHROUGH*/ case CSET_ASCII:
+ if (term->ucsdata->unitab_ctrl[c] != 0xFF)
+ c = term->ucsdata->unitab_ctrl[c];
+ else
+ c = ((unsigned char) c) | CSET_ASCII;
+ break;
+ case CSET_SCOACS:
+ if (c>=' ') c = ((unsigned char)c) | CSET_SCOACS;
+ break;
+ }
+ }
+ }
+
+ /*
+ * How about C1 controls?
+ * Explicitly ignore SCI (0x9a), which we don't translate to DECID.
+ */
+ if ((c & -32) == 0x80 && term->termstate < DO_CTRLS &&
+ !term->vt52_mode && has_compat(VT220)) {
+ if (c == 0x9a)
+ c = 0;
+ else {
+ term->termstate = SEEN_ESC;
+ term->esc_query = FALSE;
+ c = '@' + (c & 0x1F);
+ }
+ }
+
+ /* Or the GL control. */
+ 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) {
+ 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);
+ }
+ } else
+ /* Or normal C0 controls. */
+ if ((c & ~0x1F) == 0 && term->termstate < DO_CTRLS) {
+ switch (c) {
+ case '\005': /* ENQ: terminal type query */
+ /*
+ * Strictly speaking this is VT100 but a VT100 defaults to
+ * no response. Other terminals respond at their option.
+ *
+ * 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);
+ }
+ break;
+ case '\007': /* BEL: Bell */
+ {
+ struct beeptime *newbeep;
+ unsigned long ticks;
+
+ ticks = GETTICKCOUNT();
+
+ if (!term->beep_overloaded) {
+ newbeep = snew(struct beeptime);
+ newbeep->ticks = ticks;
+ newbeep->next = NULL;
+ if (!term->beephead)
+ term->beephead = newbeep;
+ else
+ term->beeptail->next = newbeep;
+ term->beeptail = newbeep;
+ term->nbeeps++;
+ }
+
+ /*
+ * Throw out any beeps that happened more than
+ * t seconds ago.
+ */
+ while (term->beephead &&
+ term->beephead->ticks < ticks - term->cfg.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 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) {
+ /*
+ * Now, if we have n or more beeps
+ * remaining in the queue, go into overload
+ * mode.
+ */
+ term->beep_overloaded = TRUE;
+ }
+ 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->cfg.beep == BELL_VISUAL) {
+ term_schedule_vbell(term, FALSE, 0);
+ }
+ }
+ seen_disp_event(term);
+ }
+ break;
+ case '\b': /* BS: Back space */
+ if (term->curs.x == 0 &&
+ (term->curs.y == 0 || term->wrap == 0))
+ /* do nothing */ ;
+ else if (term->curs.x == 0 && term->curs.y > 0)
+ term->curs.x = term->cols - 1, term->curs.y--;
+ else if (term->wrapnext)
+ term->wrapnext = FALSE;
+ else
+ term->curs.x--;
+ seen_disp_event(term);
+ break;
+ case '\016': /* LS1: Locking-shift one */
+ compatibility(VT100);
+ term->cset = 1;
+ break;
+ case '\017': /* LS0: Locking-shift zero */
+ compatibility(VT100);
+ term->cset = 0;
+ break;
+ case '\033': /* ESC: Escape */
+ if (term->vt52_mode)
+ term->termstate = VT52_ESC;
+ else {
+ compatibility(ANSIMIN);
+ term->termstate = SEEN_ESC;
+ term->esc_query = FALSE;
+ }
+ 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->logctx)
+ logtraffic(term->logctx, (unsigned char) c, LGTYP_ASCII);
+ break;
+ case '\014': /* FF: Form feed */
+ if (has_compat(SCOANSI)) {
+ move(term, 0, 0, 0);
+ erase_lots(term, FALSE, FALSE, TRUE);
+ term->disptop = 0;
+ term->wrapnext = FALSE;
+ seen_disp_event(term);
+ break;
+ }
+ case '\013': /* VT: Line tabulation */
+ compatibility(VT100);
+ 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)
+ 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 */
+ {
+ pos old_curs = term->curs;
+ termline *ldata = scrlineptr(term->curs.y);
+
+ do {
+ term->curs.x++;
+ } while (term->curs.x < term->cols - 1 &&
+ !term->tabs[term->curs.x]);
+
+ if ((ldata->lattr & LATTR_MODE) != LATTR_NORM) {
+ if (term->curs.x >= term->cols / 2)
+ term->curs.x = term->cols / 2 - 1;
+ } else {
+ if (term->curs.x >= term->cols)
+ term->curs.x = term->cols - 1;
+ }
+
+ check_selection(term, old_curs, term->curs);
+ }
+ seen_disp_event(term);
+ break;
+ }
+ } else
+ switch (term->termstate) {
+ case TOPLEVEL:
+ /* Only graphic characters get this far;
+ * ctrls are stripped above */
+ {
+ 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));
+
+ 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);
+ else if (term->curs.y < term->rows - 1)
+ term->curs.y++;
+ term->curs.x = 0;
+ term->wrapnext = FALSE;
+ cline = scrlineptr(term->curs.y);
+ }
+ if (term->insert && width > 0)
+ insch(term, width);
+ if (term->selstate != NO_SELECTION) {
+ pos cursplus = term->curs;
+ incpos(cursplus);
+ check_selection(term, term->curs, cursplus);
+ }
+ if (((c & CSET_MASK) == CSET_ASCII ||
+ (c & CSET_MASK) == 0) &&
+ term->logctx)
+ logtraffic(term->logctx, (unsigned char) c,
+ LGTYP_ASCII);
+
+ switch (width) {
+ case 2:
+ /*
+ * If we're about to display a double-width
+ * character starting in the rightmost
+ * column, then we do something special
+ * instead. We must print a space in the
+ * last column of the screen, then wrap;
+ * and we also set LATTR_WRAPPED2 which
+ * instructs subsequent cut-and-pasting not
+ * only to splice this line to the one
+ * after it, but to ignore the space in the
+ * last character position as well.
+ * (Because what was actually output to the
+ * terminal was presumably just a sequence
+ * of CJK characters, and we don't want a
+ * space to be pasted in the middle of
+ * those just because they had the
+ * misfortune to start in the wrong parity
+ * column. xterm concurs.)
+ */
+ check_boundary(term, term->curs.x, term->curs.y);
+ check_boundary(term, term->curs.x+2, term->curs.y);
+ if (term->curs.x == term->cols-1) {
+ copy_termchar(cline, term->curs.x,
+ &term->erase_char);
+ cline->lattr |= LATTR_WRAPPED | LATTR_WRAPPED2;
+ 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++;
+ term->curs.x = 0;
+ cline = scrlineptr(term->curs.y);
+ /* Now we must check_boundary again, of course. */
+ check_boundary(term, term->curs.x, term->curs.y);
+ check_boundary(term, term->curs.x+2, term->curs.y);
+ }
+
+ /* FULL-TERMCHAR */
+ clear_cc(cline, term->curs.x);
+ cline->chars[term->curs.x].chr = c;
+ cline->chars[term->curs.x].attr = term->curr_attr;
+
+ term->curs.x++;
+
+ /* FULL-TERMCHAR */
+ clear_cc(cline, term->curs.x);
+ cline->chars[term->curs.x].chr = UCSWIDE;
+ cline->chars[term->curs.x].attr = term->curr_attr;
+
+ break;
+ case 1:
+ check_boundary(term, term->curs.x, term->curs.y);
+ check_boundary(term, term->curs.x+1, term->curs.y);
+
+ /* FULL-TERMCHAR */
+ clear_cc(cline, term->curs.x);
+ cline->chars[term->curs.x].chr = c;
+ cline->chars[term->curs.x].attr = term->curr_attr;
+
+ break;
+ case 0:
+ if (term->curs.x > 0) {
+ int x = term->curs.x - 1;
+
+ /* If we're in wrapnext state, the character
+ * to combine with is _here_, not to our left. */
+ if (term->wrapnext)
+ x++;
+
+ /*
+ * If the previous character is
+ * UCSWIDE, back up another one.
+ */
+ if (cline->chars[x].chr == UCSWIDE) {
+ assert(x > 0);
+ x--;
+ }
+
+ add_cc(cline, x, c);
+ seen_disp_event(term);
+ }
+ continue;
+ default:
+ continue;
+ }
+ term->curs.x++;
+ if (term->curs.x == term->cols) {
+ term->curs.x--;
+ term->wrapnext = TRUE;
+ if (term->wrap && term->vt52_mode) {
+ cline->lattr |= LATTR_WRAPPED;
+ 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++;
+ term->curs.x = 0;
+ term->wrapnext = FALSE;
+ }
+ }
+ seen_disp_event(term);
+ }
+ break;
+
+ case OSC_MAYBE_ST:
+ /*
+ * This state is virtually identical to SEEN_ESC, with the
+ * exception that we have an OSC sequence in the pipeline,
+ * and _if_ we see a backslash, we process it.
+ */
+ if (c == '\\') {
+ do_osc(term);
+ term->termstate = TOPLEVEL;
+ break;
+ }
+ /* else fall through */
+ case SEEN_ESC:
+ if (c >= ' ' && c <= '/') {
+ if (term->esc_query)
+ term->esc_query = -1;
+ else
+ term->esc_query = c;
+ break;
+ }
+ term->termstate = TOPLEVEL;
+ switch (ANSI(c, term->esc_query)) {
+ case '[': /* enter CSI mode */
+ term->termstate = SEEN_CSI;
+ term->esc_nargs = 1;
+ term->esc_args[0] = ARG_DEFAULT;
+ term->esc_query = FALSE;
+ break;
+ case ']': /* OSC: xterm escape sequences */
+ /* Compatibility is nasty here, xterm, linux, decterm yuk! */
+ compatibility(OTHER);
+ term->termstate = SEEN_OSC;
+ term->esc_args[0] = 0;
+ break;
+ case '7': /* DECSC: save cursor */
+ compatibility(VT100);
+ save_cursor(term, TRUE);
+ break;
+ case '8': /* DECRC: restore cursor */
+ compatibility(VT100);
+ save_cursor(term, FALSE);
+ seen_disp_event(term);
+ break;
+ case '=': /* DECKPAM: Keypad application mode */
+ compatibility(VT100);
+ term->app_keypad_keys = TRUE;
+ break;
+ case '>': /* DECKPNM: Keypad numeric mode */
+ compatibility(VT100);
+ term->app_keypad_keys = FALSE;
+ break;
+ case 'D': /* IND: exactly equivalent to LF */
+ compatibility(VT100);
+ 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++;
+ term->wrapnext = FALSE;
+ seen_disp_event(term);
+ break;
+ case 'E': /* NEL: exactly equivalent to CR-LF */
+ compatibility(VT100);
+ term->curs.x = 0;
+ 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++;
+ term->wrapnext = FALSE;
+ seen_disp_event(term);
+ break;
+ case 'M': /* RI: reverse index - backwards LF */
+ compatibility(VT100);
+ if (term->curs.y == term->marg_t)
+ scroll(term, term->marg_t, term->marg_b, -1, TRUE);
+ else if (term->curs.y > 0)
+ term->curs.y--;
+ term->wrapnext = FALSE;
+ seen_disp_event(term);
+ break;
+ case 'Z': /* DECID: terminal type query */
+ compatibility(VT100);
+ if (term->ldisc)
+ ldisc_send(term->ldisc, term->id_string,
+ strlen(term->id_string), 0);
+ break;
+ case 'c': /* RIS: restore power-on settings */
+ 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)
+ request_resize(term->frontend, 80, term->rows);
+ term->reset_132 = 0;
+ }
+ term->disptop = 0;
+ seen_disp_event(term);
+ break;
+ case 'H': /* HTS: set a tab */
+ compatibility(VT100);
+ term->tabs[term->curs.x] = TRUE;
+ break;
+
+ case ANSI('8', '#'): /* DECALN: fills screen with Es :-) */
+ compatibility(VT100);
+ {
+ termline *ldata;
+ int i, j;
+ pos scrtop, scrbot;
+
+ for (i = 0; i < term->rows; i++) {
+ ldata = scrlineptr(i);
+ for (j = 0; j < term->cols; j++) {
+ copy_termchar(ldata, j,
+ &term->basic_erase_char);
+ ldata->chars[j].chr = 'E';
+ }
+ ldata->lattr = LATTR_NORM;
+ }
+ term->disptop = 0;
+ seen_disp_event(term);
+ scrtop.x = scrtop.y = 0;
+ scrbot.x = 0;
+ scrbot.y = term->rows;
+ check_selection(term, scrtop, scrbot);
+ }
+ break;
+
+ case ANSI('3', '#'):
+ case ANSI('4', '#'):
+ case ANSI('5', '#'):
+ case ANSI('6', '#'):
+ compatibility(VT100);
+ {
+ int nlattr;
+
+ switch (ANSI(c, term->esc_query)) {
+ case ANSI('3', '#'): /* DECDHL: 2*height, top */
+ nlattr = LATTR_TOP;
+ break;
+ case ANSI('4', '#'): /* DECDHL: 2*height, bottom */
+ nlattr = LATTR_BOT;
+ break;
+ case ANSI('5', '#'): /* DECSWL: normal */
+ nlattr = LATTR_NORM;
+ break;
+ default: /* case ANSI('6', '#'): DECDWL: 2*width */
+ nlattr = LATTR_WIDE;
+ break;
+ }
+ scrlineptr(term->curs.y)->lattr = nlattr;
+ }
+ break;
+ /* GZD4: G0 designate 94-set */
+ case ANSI('A', '('):
+ compatibility(VT100);
+ if (!term->cfg.no_remote_charset)
+ term->cset_attr[0] = CSET_GBCHR;
+ break;
+ case ANSI('B', '('):
+ compatibility(VT100);
+ if (!term->cfg.no_remote_charset)
+ term->cset_attr[0] = CSET_ASCII;
+ break;
+ case ANSI('0', '('):
+ compatibility(VT100);
+ if (!term->cfg.no_remote_charset)
+ term->cset_attr[0] = CSET_LINEDRW;
+ break;
+ case ANSI('U', '('):
+ compatibility(OTHER);
+ if (!term->cfg.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)
+ term->cset_attr[1] = CSET_GBCHR;
+ break;
+ case ANSI('B', ')'):
+ compatibility(VT100);
+ if (!term->cfg.no_remote_charset)
+ term->cset_attr[1] = CSET_ASCII;
+ break;
+ case ANSI('0', ')'):
+ compatibility(VT100);
+ if (!term->cfg.no_remote_charset)
+ term->cset_attr[1] = CSET_LINEDRW;
+ break;
+ case ANSI('U', ')'):
+ compatibility(OTHER);
+ if (!term->cfg.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)
+ term->utf = 1;
+ break;
+ case ANSI('@', '%'):
+ compatibility(OTHER);
+ if (!term->cfg.no_remote_charset)
+ term->utf = 0;
+ break;
+ }
+ break;
+ case SEEN_CSI:
+ term->termstate = TOPLEVEL; /* default */
+ if (isdigit(c)) {
+ if (term->esc_nargs <= ARGS_MAX) {
+ if (term->esc_args[term->esc_nargs - 1] == ARG_DEFAULT)
+ term->esc_args[term->esc_nargs - 1] = 0;
+ term->esc_args[term->esc_nargs - 1] =
+ 10 * term->esc_args[term->esc_nargs - 1] + c - '0';
+ }
+ term->termstate = SEEN_CSI;
+ } else if (c == ';') {
+ if (term->esc_nargs < ARGS_MAX)
+ term->esc_args[term->esc_nargs++] = ARG_DEFAULT;
+ term->termstate = SEEN_CSI;
+ } else if (c < '@') {
+ if (term->esc_query)
+ term->esc_query = -1;
+ else if (c == '?')
+ term->esc_query = TRUE;
+ else
+ term->esc_query = c;
+ term->termstate = SEEN_CSI;
+ } else
+ switch (ANSI(c, term->esc_query)) {
+ case 'A': /* CUU: move up N lines */
+ move(term, term->curs.x,
+ term->curs.y - def(term->esc_args[0], 1), 1);
+ seen_disp_event(term);
+ break;
+ case 'e': /* VPR: move down N lines */
+ compatibility(ANSI);
+ /* FALLTHROUGH */
+ case 'B': /* CUD: Cursor down */
+ move(term, term->curs.x,
+ term->curs.y + def(term->esc_args[0], 1), 1);
+ seen_disp_event(term);
+ break;
+ case ANSI('c', '>'): /* DA: report xterm version */
+ compatibility(OTHER);
+ /* this reports xterm version 136 so that VIM can
+ use the drag messages from the mouse reporting */
+ if (term->ldisc)
+ ldisc_send(term->ldisc, "\033[>0;136;0c", 11, 0);
+ break;
+ case 'a': /* HPR: move right N cols */
+ compatibility(ANSI);
+ /* FALLTHROUGH */
+ case 'C': /* CUF: Cursor right */
+ move(term, term->curs.x + def(term->esc_args[0], 1),
+ term->curs.y, 1);
+ seen_disp_event(term);
+ break;
+ case 'D': /* CUB: move left N cols */
+ move(term, term->curs.x - def(term->esc_args[0], 1),
+ term->curs.y, 1);
+ seen_disp_event(term);
+ break;
+ case 'E': /* CNL: move down N lines and CR */
+ compatibility(ANSI);
+ move(term, 0,
+ term->curs.y + def(term->esc_args[0], 1), 1);
+ seen_disp_event(term);
+ break;
+ case 'F': /* CPL: move up N lines and CR */
+ compatibility(ANSI);
+ move(term, 0,
+ term->curs.y - def(term->esc_args[0], 1), 1);
+ seen_disp_event(term);
+ break;
+ case 'G': /* CHA */
+ case '`': /* HPA: set horizontal posn */
+ compatibility(ANSI);
+ move(term, def(term->esc_args[0], 1) - 1,
+ term->curs.y, 0);
+ seen_disp_event(term);
+ break;
+ case 'd': /* VPA: set vertical posn */
+ compatibility(ANSI);
+ move(term, term->curs.x,
+ ((term->dec_om ? term->marg_t : 0) +
+ def(term->esc_args[0], 1) - 1),
+ (term->dec_om ? 2 : 0));
+ seen_disp_event(term);
+ break;
+ case 'H': /* CUP */
+ case 'f': /* HVP: set horz and vert posns at once */
+ if (term->esc_nargs < 2)
+ term->esc_args[1] = ARG_DEFAULT;
+ move(term, def(term->esc_args[1], 1) - 1,
+ ((term->dec_om ? term->marg_t : 0) +
+ def(term->esc_args[0], 1) - 1),
+ (term->dec_om ? 2 : 0));
+ seen_disp_event(term);
+ break;
+ case 'J': /* ED: erase screen or parts of it */
+ {
+ unsigned int i = def(term->esc_args[0], 0);
+ if (i == 3) {
+ /* Erase Saved Lines (xterm)
+ * This follows Thomas Dickey's xterm. */
+ term_clrsb(term);
+ } else {
+ i++;
+ if (i > 3)
+ i = 0;
+ erase_lots(term, FALSE, !!(i & 2), !!(i & 1));
+ }
+ }
+ term->disptop = 0;
+ seen_disp_event(term);
+ break;
+ case 'K': /* EL: erase line or parts of it */
+ {
+ unsigned int i = def(term->esc_args[0], 0) + 1;
+ if (i > 3)
+ i = 0;
+ erase_lots(term, TRUE, !!(i & 2), !!(i & 1));
+ }
+ seen_disp_event(term);
+ break;
+ case 'L': /* IL: insert lines */
+ compatibility(VT102);
+ if (term->curs.y <= term->marg_b)
+ scroll(term, term->curs.y, term->marg_b,
+ -def(term->esc_args[0], 1), FALSE);
+ seen_disp_event(term);
+ break;
+ case 'M': /* DL: delete lines */
+ compatibility(VT102);
+ if (term->curs.y <= term->marg_b)
+ scroll(term, term->curs.y, term->marg_b,
+ def(term->esc_args[0], 1),
+ TRUE);
+ seen_disp_event(term);
+ break;
+ case '@': /* ICH: insert chars */
+ /* XXX VTTEST says this is vt220, vt510 manual says vt102 */
+ compatibility(VT102);
+ insch(term, def(term->esc_args[0], 1));
+ seen_disp_event(term);
+ break;
+ case 'P': /* DCH: delete chars */
+ compatibility(VT102);
+ insch(term, -def(term->esc_args[0], 1));
+ seen_disp_event(term);
+ break;
+ case 'c': /* DA: terminal type query */
+ compatibility(VT100);
+ /* This is the response for a VT102 */
+ if (term->ldisc)
+ ldisc_send(term->ldisc, term->id_string,
+ strlen(term->id_string), 0);
+ break;
+ case 'n': /* DSR: cursor position query */
+ if (term->ldisc) {
+ if (term->esc_args[0] == 6) {
+ char buf[32];
+ sprintf(buf, "\033[%d;%dR", term->curs.y + 1,
+ term->curs.x + 1);
+ ldisc_send(term->ldisc, buf, strlen(buf), 0);
+ } else if (term->esc_args[0] == 5) {
+ ldisc_send(term->ldisc, "\033[0n", 4, 0);
+ }
+ }
+ break;
+ case 'h': /* SM: toggle modes to high */
+ case ANSI_QUE('h'):
+ compatibility(VT100);
+ {
+ int i;
+ for (i = 0; i < term->esc_nargs; i++)
+ toggle_mode(term, term->esc_args[i],
+ term->esc_query, TRUE);
+ }
+ break;
+ case 'i': /* MC: Media copy */
+ case ANSI_QUE('i'):
+ compatibility(VT100);
+ {
+ if (term->esc_nargs != 1) break;
+ if (term->esc_args[0] == 5 && *term->cfg.printer) {
+ term->printing = TRUE;
+ term->only_printing = !term->esc_query;
+ term->print_state = 0;
+ term_print_setup(term);
+ } else if (term->esc_args[0] == 4 &&
+ term->printing) {
+ term_print_finish(term);
+ }
+ }
+ break;
+ case 'l': /* RM: toggle modes to low */
+ case ANSI_QUE('l'):
+ compatibility(VT100);
+ {
+ int i;
+ for (i = 0; i < term->esc_nargs; i++)
+ toggle_mode(term, term->esc_args[i],
+ term->esc_query, FALSE);
+ }
+ break;
+ case 'g': /* TBC: clear tabs */
+ compatibility(VT100);
+ if (term->esc_nargs == 1) {
+ if (term->esc_args[0] == 0) {
+ term->tabs[term->curs.x] = FALSE;
+ } else if (term->esc_args[0] == 3) {
+ int i;
+ for (i = 0; i < term->cols; i++)
+ term->tabs[i] = FALSE;
+ }
+ }
+ break;
+ case 'r': /* DECSTBM: set scroll margins */
+ compatibility(VT100);
+ if (term->esc_nargs <= 2) {
+ int top, bot;
+ top = def(term->esc_args[0], 1) - 1;
+ bot = (term->esc_nargs <= 1
+ || term->esc_args[1] == 0 ?
+ term->rows :
+ def(term->esc_args[1], term->rows)) - 1;
+ if (bot >= term->rows)
+ bot = term->rows - 1;
+ /* VTTEST Bug 9 - if region is less than 2 lines
+ * don't change region.
+ */
+ if (bot - top > 0) {
+ term->marg_t = top;
+ term->marg_b = bot;
+ term->curs.x = 0;
+ /*
+ * I used to think the cursor should be
+ * placed at the top of the newly marginned
+ * area. Apparently not: VMS TPU falls over
+ * if so.
+ *
+ * Well actually it should for
+ * Origin mode - RDB
+ */
+ term->curs.y = (term->dec_om ?
+ term->marg_t : 0);
+ seen_disp_event(term);
+ }
+ }
+ break;
+ case 'm': /* SGR: set graphics rendition */
+ {
+ /*
+ * A VT100 without the AVO only had one
+ * attribute, either underline or
+ * reverse video depending on the
+ * cursor type, this was selected by
+ * CSI 7m.
+ *
+ * case 2:
+ * This is sometimes DIM, eg on the
+ * GIGI and Linux
+ * case 8:
+ * This is sometimes INVIS various ANSI.
+ * case 21:
+ * This like 22 disables BOLD, DIM and INVIS
+ *
+ * The ANSI colours appear on any
+ * terminal that has colour (obviously)
+ * but the interaction between sgr0 and
+ * the colours varies but is usually
+ * related to the background colour
+ * erase item. The interaction between
+ * colour attributes and the mono ones
+ * is also very implementation
+ * dependent.
+ *
+ * The 39 and 49 attributes are likely
+ * to be unimplemented.
+ */
+ int i;
+ for (i = 0; i < term->esc_nargs; i++) {
+ switch (def(term->esc_args[i], 0)) {
+ case 0: /* restore defaults */
+ term->curr_attr = term->default_attr;
+ break;
+ case 1: /* enable bold */
+ compatibility(VT100AVO);
+ term->curr_attr |= ATTR_BOLD;
+ break;
+ case 21: /* (enable double underline) */
+ compatibility(OTHER);
+ case 4: /* enable underline */
+ compatibility(VT100AVO);
+ term->curr_attr |= ATTR_UNDER;
+ break;
+ case 5: /* enable blink */
+ compatibility(VT100AVO);
+ term->curr_attr |= ATTR_BLINK;
+ break;
+ case 6: /* SCO light bkgrd */
+ compatibility(SCOANSI);
+ term->blink_is_real = FALSE;
+ term->curr_attr |= ATTR_BLINK;
+ term_schedule_tblink(term);
+ break;
+ 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;
+ term->sco_acs = 0; break;
+ case 11: /* SCO acs on */
+ compatibility(SCOANSI);
+ if (term->cfg.no_remote_charset) break;
+ term->sco_acs = 1; break;
+ case 12: /* SCO acs on, |0x80 */
+ compatibility(SCOANSI);
+ if (term->cfg.no_remote_charset) break;
+ term->sco_acs = 2; break;
+ case 22: /* disable bold */
+ compatibility2(OTHER, VT220);
+ term->curr_attr &= ~ATTR_BOLD;
+ break;
+ case 24: /* disable underline */
+ compatibility2(OTHER, VT220);
+ term->curr_attr &= ~ATTR_UNDER;
+ break;
+ case 25: /* disable blink */
+ compatibility2(OTHER, VT220);
+ term->curr_attr &= ~ATTR_BLINK;
+ break;
+ case 27: /* disable reverse video */
+ compatibility2(OTHER, VT220);
+ term->curr_attr &= ~ATTR_REVERSE;
+ break;
+ case 30:
+ case 31:
+ case 32:
+ case 33:
+ case 34:
+ case 35:
+ case 36:
+ case 37:
+ /* foreground */
+ term->curr_attr &= ~ATTR_FGMASK;
+ term->curr_attr |=
+ (term->esc_args[i] - 30)<<ATTR_FGSHIFT;
+ break;
+ case 90:
+ case 91:
+ case 92:
+ case 93:
+ case 94:
+ case 95:
+ case 96:
+ case 97:
+ /* aixterm-style bright foreground */
+ term->curr_attr &= ~ATTR_FGMASK;
+ term->curr_attr |=
+ ((term->esc_args[i] - 90 + 8)
+ << ATTR_FGSHIFT);
+ break;
+ case 39: /* default-foreground */
+ term->curr_attr &= ~ATTR_FGMASK;
+ term->curr_attr |= ATTR_DEFFG;
+ break;
+ case 40:
+ case 41:
+ case 42:
+ case 43:
+ case 44:
+ case 45:
+ case 46:
+ case 47:
+ /* background */
+ term->curr_attr &= ~ATTR_BGMASK;
+ term->curr_attr |=
+ (term->esc_args[i] - 40)<<ATTR_BGSHIFT;
+ break;
+ case 100:
+ case 101:
+ case 102:
+ case 103:
+ case 104:
+ case 105:
+ case 106:
+ case 107:
+ /* aixterm-style bright background */
+ term->curr_attr &= ~ATTR_BGMASK;
+ term->curr_attr |=
+ ((term->esc_args[i] - 100 + 8)
+ << ATTR_BGSHIFT);
+ break;
+ case 49: /* default-background */
+ term->curr_attr &= ~ATTR_BGMASK;
+ term->curr_attr |= ATTR_DEFBG;
+ break;
+ case 38: /* xterm 256-colour mode */
+ if (i+2 < term->esc_nargs &&
+ term->esc_args[i+1] == 5) {
+ term->curr_attr &= ~ATTR_FGMASK;
+ term->curr_attr |=
+ ((term->esc_args[i+2] & 0xFF)
+ << ATTR_FGSHIFT);
+ i += 2;
+ }
+ break;
+ case 48: /* xterm 256-colour mode */
+ if (i+2 < term->esc_nargs &&
+ term->esc_args[i+1] == 5) {
+ term->curr_attr &= ~ATTR_BGMASK;
+ term->curr_attr |=
+ ((term->esc_args[i+2] & 0xFF)
+ << ATTR_BGSHIFT);
+ i += 2;
+ }
+ break;
+ }
+ }
+ set_erase_char(term);
+ }
+ break;
+ case 's': /* save cursor */
+ save_cursor(term, TRUE);
+ break;
+ case 'u': /* restore cursor */
+ save_cursor(term, FALSE);
+ seen_disp_event(term);
+ break;
+ case 't': /* DECSLPP: set page size - ie window height */
+ /*
+ * VT340/VT420 sequence DECSLPP, DEC only allows values
+ * 24/25/36/48/72/144 other emulators (eg dtterm) use
+ * illegal values (eg first arg 1..9) for window changing
+ * and reports.
+ */
+ if (term->esc_nargs <= 1
+ && (term->esc_args[0] < 1 ||
+ term->esc_args[0] >= 24)) {
+ compatibility(VT340TEXT);
+ if (!term->cfg.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 &&
+ term->esc_args[0] < 24) {
+ compatibility(OTHER);
+
+ switch (term->esc_args[0]) {
+ int x, y, len;
+ char buf[80], *p;
+ case 1:
+ set_iconic(term->frontend, FALSE);
+ break;
+ case 2:
+ set_iconic(term->frontend, TRUE);
+ break;
+ case 3:
+ if (term->esc_nargs >= 3) {
+ if (!term->cfg.no_remote_resize)
+ move_window(term->frontend,
+ def(term->esc_args[1], 0),
+ def(term->esc_args[2], 0));
+ }
+ break;
+ case 4:
+ /* We should resize the window to a given
+ * size in pixels here, but currently our
+ * resizing code isn't healthy enough to
+ * manage it. */
+ break;
+ case 5:
+ /* move to top */
+ set_zorder(term->frontend, TRUE);
+ break;
+ case 6:
+ /* move to bottom */
+ set_zorder(term->frontend, FALSE);
+ break;
+ case 7:
+ refresh_window(term->frontend);
+ break;
+ case 8:
+ if (term->esc_nargs >= 3) {
+ if (!term->cfg.no_remote_resize)
+ request_resize(term->frontend,
+ def(term->esc_args[2], term->cfg.width),
+ def(term->esc_args[1], term->cfg.height));
+ }
+ break;
+ case 9:
+ if (term->esc_nargs >= 2)
+ set_zoomed(term->frontend,
+ term->esc_args[1] ?
+ TRUE : FALSE);
+ break;
+ case 11:
+ if (term->ldisc)
+ ldisc_send(term->ldisc,
+ is_iconic(term->frontend) ?
+ "\033[2t" : "\033[1t", 4, 0);
+ break;
+ case 13:
+ if (term->ldisc) {
+ get_window_pos(term->frontend, &x, &y);
+ len = sprintf(buf, "\033[3;%d;%dt", x, y);
+ ldisc_send(term->ldisc, buf, len, 0);
+ }
+ break;
+ case 14:
+ if (term->ldisc) {
+ get_window_pixels(term->frontend, &x, &y);
+ len = sprintf(buf, "\033[4;%d;%dt", y, x);
+ ldisc_send(term->ldisc, buf, len, 0);
+ }
+ break;
+ case 18:
+ if (term->ldisc) {
+ len = sprintf(buf, "\033[8;%d;%dt",
+ term->rows, term->cols);
+ ldisc_send(term->ldisc, buf, len, 0);
+ }
+ break;
+ case 19:
+ /*
+ * Hmmm. Strictly speaking we
+ * should return `the size of the
+ * screen in characters', but
+ * that's not easy: (a) window
+ * furniture being what it is it's
+ * hard to compute, and (b) in
+ * resize-font mode maximising the
+ * window wouldn't change the
+ * number of characters. *shrug*. I
+ * think we'll ignore it for the
+ * moment and see if anyone
+ * complains, and then ask them
+ * 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)
+ p = get_window_title(term->frontend, TRUE);
+ else
+ p = EMPTY_WINDOW_TITLE;
+ len = strlen(p);
+ ldisc_send(term->ldisc, "\033]L", 3, 0);
+ ldisc_send(term->ldisc, p, len, 0);
+ 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)
+ p = get_window_title(term->frontend, FALSE);
+ else
+ p = EMPTY_WINDOW_TITLE;
+ len = strlen(p);
+ ldisc_send(term->ldisc, "\033]l", 3, 0);
+ ldisc_send(term->ldisc, p, len, 0);
+ ldisc_send(term->ldisc, "\033\\", 2, 0);
+ }
+ break;
+ }
+ }
+ break;
+ case 'S': /* SU: Scroll up */
+ compatibility(SCOANSI);
+ scroll(term, term->marg_t, term->marg_b,
+ def(term->esc_args[0], 1), TRUE);
+ term->wrapnext = FALSE;
+ seen_disp_event(term);
+ break;
+ case 'T': /* SD: Scroll down */
+ compatibility(SCOANSI);
+ scroll(term, term->marg_t, term->marg_b,
+ -def(term->esc_args[0], 1), TRUE);
+ term->wrapnext = FALSE;
+ seen_disp_event(term);
+ break;
+ case ANSI('|', '*'): /* DECSNLS */
+ /*
+ * Set number of lines on screen
+ * VT420 uses VGA like hardware and can
+ * 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)
+ request_resize(term->frontend, term->cols,
+ def(term->esc_args[0],
+ term->cfg.height));
+ deselect(term);
+ }
+ break;
+ case ANSI('|', '$'): /* DECSCPP */
+ /*
+ * Set number of columns per page
+ * 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)
+ request_resize(term->frontend,
+ def(term->esc_args[0],
+ term->cfg.width), term->rows);
+ deselect(term);
+ }
+ break;
+ case 'X': /* ECH: write N spaces w/o moving cursor */
+ /* XXX VTTEST says this is vt220, vt510 manual
+ * says vt100 */
+ compatibility(ANSIMIN);
+ {
+ int n = def(term->esc_args[0], 1);
+ pos cursplus;
+ int p = term->curs.x;
+ termline *cline = scrlineptr(term->curs.y);
+
+ if (n > term->cols - term->curs.x)
+ n = term->cols - term->curs.x;
+ cursplus = term->curs;
+ cursplus.x += n;
+ check_boundary(term, term->curs.x, term->curs.y);
+ check_boundary(term, term->curs.x+n, term->curs.y);
+ check_selection(term, term->curs, cursplus);
+ while (n--)
+ copy_termchar(cline, p++,
+ &term->erase_char);
+ seen_disp_event(term);
+ }
+ break;
+ case 'x': /* DECREQTPARM: report terminal characteristics */
+ compatibility(VT100);
+ if (term->ldisc) {
+ char buf[32];
+ int i = def(term->esc_args[0], 0);
+ if (i == 0 || i == 1) {
+ strcpy(buf, "\033[2;1;1;112;112;1;0x");
+ buf[2] += i;
+ ldisc_send(term->ldisc, buf, 20, 0);
+ }
+ }
+ break;
+ case 'Z': /* CBT */
+ compatibility(OTHER);
+ {
+ int i = def(term->esc_args[0], 1);
+ pos old_curs = term->curs;
+
+ for(;i>0 && term->curs.x>0; i--) {
+ do {
+ term->curs.x--;
+ } while (term->curs.x >0 &&
+ !term->tabs[term->curs.x]);
+ }
+ check_selection(term, old_curs, term->curs);
+ }
+ break;
+ case ANSI('c', '='): /* Hide or Show Cursor */
+ compatibility(SCOANSI);
+ switch(term->esc_args[0]) {
+ case 0: /* hide cursor */
+ term->cursor_on = FALSE;
+ break;
+ case 1: /* restore cursor */
+ term->big_cursor = FALSE;
+ term->cursor_on = TRUE;
+ break;
+ case 2: /* block cursor */
+ term->big_cursor = TRUE;
+ term->cursor_on = TRUE;
+ break;
+ }
+ break;
+ case ANSI('C', '='):
+ /*
+ * set cursor start on scanline esc_args[0] and
+ * end on scanline esc_args[1].If you set
+ * the bottom scan line to a value less than
+ * the top scan line, the cursor will disappear.
+ */
+ compatibility(SCOANSI);
+ if (term->esc_nargs >= 2) {
+ if (term->esc_args[0] > term->esc_args[1])
+ term->cursor_on = FALSE;
+ else
+ term->cursor_on = TRUE;
+ }
+ break;
+ case ANSI('D', '='):
+ compatibility(SCOANSI);
+ term->blink_is_real = FALSE;
+ term_schedule_tblink(term);
+ if (term->esc_args[0]>=1)
+ term->curr_attr |= ATTR_BLINK;
+ else
+ term->curr_attr &= ~ATTR_BLINK;
+ break;
+ case ANSI('E', '='):
+ compatibility(SCOANSI);
+ term->blink_is_real = (term->esc_args[0] >= 1);
+ term_schedule_tblink(term);
+ break;
+ case ANSI('F', '='): /* set normal foreground */
+ compatibility(SCOANSI);
+ if (term->esc_args[0] >= 0 && term->esc_args[0] < 16) {
+ long colour =
+ (sco2ansicolour[term->esc_args[0] & 0x7] |
+ (term->esc_args[0] & 0x8)) <<
+ ATTR_FGSHIFT;
+ term->curr_attr &= ~ATTR_FGMASK;
+ term->curr_attr |= colour;
+ term->default_attr &= ~ATTR_FGMASK;
+ term->default_attr |= colour;
+ set_erase_char(term);
+ }
+ break;
+ case ANSI('G', '='): /* set normal background */
+ compatibility(SCOANSI);
+ if (term->esc_args[0] >= 0 && term->esc_args[0] < 16) {
+ long colour =
+ (sco2ansicolour[term->esc_args[0] & 0x7] |
+ (term->esc_args[0] & 0x8)) <<
+ ATTR_BGSHIFT;
+ term->curr_attr &= ~ATTR_BGMASK;
+ term->curr_attr |= colour;
+ term->default_attr &= ~ATTR_BGMASK;
+ term->default_attr |= colour;
+ set_erase_char(term);
+ }
+ break;
+ case ANSI('L', '='):
+ compatibility(SCOANSI);
+ term->use_bce = (term->esc_args[0] <= 0);
+ set_erase_char(term);
+ break;
+ case ANSI('p', '"'): /* DECSCL: set compat level */
+ /*
+ * Allow the host to make this emulator a
+ * 'perfect' VT102. This first appeared in
+ * the VT220, but we do need to get back to
+ * PuTTY mode so I won't check it.
+ *
+ * The arg in 40..42,50 are a PuTTY extension.
+ * The 2nd arg, 8bit vs 7bit is not checked.
+ *
+ * Setting VT102 mode should also change
+ * the Fkeys to generate PF* codes as a
+ * real VT102 has no Fkeys. The VT220 does
+ * this, F11..F13 become ESC,BS,LF other
+ * Fkeys send nothing.
+ *
+ * Note ESC c will NOT change this!
+ */
+
+ switch (term->esc_args[0]) {
+ case 61:
+ term->compatibility_level &= ~TM_VTXXX;
+ term->compatibility_level |= TM_VT102;
+ break;
+ case 62:
+ term->compatibility_level &= ~TM_VTXXX;
+ term->compatibility_level |= TM_VT220;
+ break;
+
+ default:
+ if (term->esc_args[0] > 60 &&
+ term->esc_args[0] < 70)
+ term->compatibility_level |= TM_VTXXX;
+ break;
+
+ case 40:
+ term->compatibility_level &= TM_VTXXX;
+ break;
+ case 41:
+ term->compatibility_level = TM_PUTTY;
+ break;
+ case 42:
+ term->compatibility_level = TM_SCOANSI;
+ break;
+
+ case ARG_DEFAULT:
+ term->compatibility_level = TM_PUTTY;
+ break;
+ case 50:
+ break;
+ }
+
+ /* Change the response to CSI c */
+ if (term->esc_args[0] == 50) {
+ int i;
+ char lbuf[64];
+ strcpy(term->id_string, "\033[?");
+ for (i = 1; i < term->esc_nargs; i++) {
+ if (i != 1)
+ strcat(term->id_string, ";");
+ sprintf(lbuf, "%d", term->esc_args[i]);
+ strcat(term->id_string, lbuf);
+ }
+ strcat(term->id_string, "c");
+ }
+#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->reset_132)
+ request_resize(132, 24);
+ else
+ request_resize(80, 24);
+ }
+ }
+#endif
+ break;
+ }
+ break;
+ case SEEN_OSC:
+ term->osc_w = FALSE;
+ switch (c) {
+ case 'P': /* Linux palette sequence */
+ term->termstate = SEEN_OSC_P;
+ term->osc_strlen = 0;
+ break;
+ case 'R': /* Linux palette reset */
+ palette_reset(term->frontend);
+ term_invalidate(term);
+ term->termstate = TOPLEVEL;
+ break;
+ case 'W': /* word-set */
+ term->termstate = SEEN_OSC_W;
+ term->osc_w = TRUE;
+ break;
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ term->esc_args[0] = 10 * term->esc_args[0] + c - '0';
+ break;
+ case 'L':
+ /*
+ * Grotty hack to support xterm and DECterm title
+ * sequences concurrently.
+ */
+ if (term->esc_args[0] == 2) {
+ term->esc_args[0] = 1;
+ break;
+ }
+ /* else fall through */
+ default:
+ term->termstate = OSC_STRING;
+ term->osc_strlen = 0;
+ }
+ break;
+ case OSC_STRING:
+ /*
+ * This OSC stuff is EVIL. It takes just one character to get into
+ * sysline mode and it's not initially obvious how to get out.
+ * So I've added CR and LF as string aborts.
+ * This shouldn't effect compatibility as I believe embedded
+ * control characters are supposed to be interpreted (maybe?)
+ * and they don't display anything useful anyway.
+ *
+ * -- RDB
+ */
+ if (c == '\012' || c == '\015') {
+ term->termstate = TOPLEVEL;
+ } else if (c == 0234 || c == '\007') {
+ /*
+ * These characters terminate the string; ST and BEL
+ * terminate the sequence and trigger instant
+ * processing of it, whereas ESC goes back to SEEN_ESC
+ * mode unless it is followed by \, in which case it is
+ * synonymous with ST in the first place.
+ */
+ do_osc(term);
+ term->termstate = TOPLEVEL;
+ } else if (c == '\033')
+ term->termstate = OSC_MAYBE_ST;
+ else if (term->osc_strlen < OSC_STR_MAX)
+ term->osc_string[term->osc_strlen++] = (char)c;
+ break;
+ case SEEN_OSC_P:
+ {
+ int max = (term->osc_strlen == 0 ? 21 : 15);
+ int val;
+ if ((int)c >= '0' && (int)c <= '9')
+ val = c - '0';
+ else if ((int)c >= 'A' && (int)c <= 'A' + max - 10)
+ val = c - 'A' + 10;
+ else if ((int)c >= 'a' && (int)c <= 'a' + max - 10)
+ val = c - 'a' + 10;
+ else {
+ term->termstate = TOPLEVEL;
+ break;
+ }
+ term->osc_string[term->osc_strlen++] = val;
+ if (term->osc_strlen >= 7) {
+ palette_set(term->frontend, term->osc_string[0],
+ term->osc_string[1] * 16 + term->osc_string[2],
+ term->osc_string[3] * 16 + term->osc_string[4],
+ term->osc_string[5] * 16 + term->osc_string[6]);
+ term_invalidate(term);
+ term->termstate = TOPLEVEL;
+ }
+ }
+ break;
+ case SEEN_OSC_W:
+ switch (c) {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ term->esc_args[0] = 10 * term->esc_args[0] + c - '0';
+ break;
+ default:
+ term->termstate = OSC_STRING;
+ term->osc_strlen = 0;
+ }
+ break;
+ case VT52_ESC:
+ term->termstate = TOPLEVEL;
+ seen_disp_event(term);
+ switch (c) {
+ case 'A':
+ move(term, term->curs.x, term->curs.y - 1, 1);
+ break;
+ case 'B':
+ move(term, term->curs.x, term->curs.y + 1, 1);
+ break;
+ case 'C':
+ move(term, term->curs.x + 1, term->curs.y, 1);
+ break;
+ case 'D':
+ move(term, term->curs.x - 1, term->curs.y, 1);
+ break;
+ /*
+ * From the VT100 Manual
+ * NOTE: The special graphics characters in the VT100
+ * are different from those in the VT52
+ *
+ * From VT102 manual:
+ * 137 _ Blank - Same
+ * 140 ` Reserved - Humm.
+ * 141 a Solid rectangle - Similar
+ * 142 b 1/ - Top half of fraction for the
+ * 143 c 3/ - subscript numbers below.
+ * 144 d 5/
+ * 145 e 7/
+ * 146 f Degrees - Same
+ * 147 g Plus or minus - Same
+ * 150 h Right arrow
+ * 151 i Ellipsis (dots)
+ * 152 j Divide by
+ * 153 k Down arrow
+ * 154 l Bar at scan 0
+ * 155 m Bar at scan 1
+ * 156 n Bar at scan 2
+ * 157 o Bar at scan 3 - Similar
+ * 160 p Bar at scan 4 - Similar
+ * 161 q Bar at scan 5 - Similar
+ * 162 r Bar at scan 6 - Same
+ * 163 s Bar at scan 7 - Similar
+ * 164 t Subscript 0
+ * 165 u Subscript 1
+ * 166 v Subscript 2
+ * 167 w Subscript 3
+ * 170 x Subscript 4
+ * 171 y Subscript 5
+ * 172 z Subscript 6
+ * 173 { Subscript 7
+ * 174 | Subscript 8
+ * 175 } Subscript 9
+ * 176 ~ Paragraph
+ *
+ */
+ case 'F':
+ term->cset_attr[term->cset = 0] = CSET_LINEDRW;
+ break;
+ case 'G':
+ term->cset_attr[term->cset = 0] = CSET_ASCII;
+ break;
+ case 'H':
+ move(term, 0, 0, 0);
+ break;
+ case 'I':
+ if (term->curs.y == 0)
+ scroll(term, 0, term->rows - 1, -1, TRUE);
+ else if (term->curs.y > 0)
+ term->curs.y--;
+ term->wrapnext = FALSE;
+ break;
+ case 'J':
+ erase_lots(term, FALSE, FALSE, TRUE);
+ term->disptop = 0;
+ break;
+ case 'K':
+ erase_lots(term, TRUE, FALSE, TRUE);
+ break;
+#if 0
+ case 'V':
+ /* XXX Print cursor line */
+ break;
+ case 'W':
+ /* XXX Start controller mode */
+ break;
+ case 'X':
+ /* XXX Stop controller mode */
+ break;
+#endif
+ case 'Y':
+ term->termstate = VT52_Y1;
+ break;
+ case 'Z':
+ if (term->ldisc)
+ ldisc_send(term->ldisc, "\033/Z", 3, 0);
+ break;
+ case '=':
+ term->app_keypad_keys = TRUE;
+ break;
+ case '>':
+ term->app_keypad_keys = FALSE;
+ break;
+ case '<':
+ /* 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_schedule_tblink(term);
+ break;
+#if 0
+ case '^':
+ /* XXX Enter auto print mode */
+ break;
+ case '_':
+ /* XXX Exit auto print mode */
+ break;
+ case ']':
+ /* XXX Print screen */
+ break;
+#endif
+
+#ifdef VT52_PLUS
+ case 'E':
+ /* compatibility(ATARI) */
+ move(term, 0, 0, 0);
+ erase_lots(term, FALSE, FALSE, TRUE);
+ term->disptop = 0;
+ break;
+ case 'L':
+ /* compatibility(ATARI) */
+ if (term->curs.y <= term->marg_b)
+ scroll(term, term->curs.y, term->marg_b, -1, FALSE);
+ break;
+ case 'M':
+ /* compatibility(ATARI) */
+ if (term->curs.y <= term->marg_b)
+ scroll(term, term->curs.y, term->marg_b, 1, TRUE);
+ break;
+ case 'b':
+ /* compatibility(ATARI) */
+ term->termstate = VT52_FG;
+ break;
+ case 'c':
+ /* compatibility(ATARI) */
+ term->termstate = VT52_BG;
+ break;
+ case 'd':
+ /* compatibility(ATARI) */
+ erase_lots(term, FALSE, TRUE, FALSE);
+ term->disptop = 0;
+ break;
+ case 'e':
+ /* compatibility(ATARI) */
+ term->cursor_on = TRUE;
+ break;
+ case 'f':
+ /* compatibility(ATARI) */
+ term->cursor_on = FALSE;
+ break;
+ /* case 'j': Save cursor position - broken on ST */
+ /* case 'k': Restore cursor position */
+ case 'l':
+ /* compatibility(ATARI) */
+ erase_lots(term, TRUE, TRUE, TRUE);
+ term->curs.x = 0;
+ term->wrapnext = FALSE;
+ break;
+ case 'o':
+ /* compatibility(ATARI) */
+ erase_lots(term, TRUE, TRUE, FALSE);
+ break;
+ case 'p':
+ /* compatibility(ATARI) */
+ term->curr_attr |= ATTR_REVERSE;
+ break;
+ case 'q':
+ /* compatibility(ATARI) */
+ term->curr_attr &= ~ATTR_REVERSE;
+ break;
+ case 'v': /* wrap Autowrap on - Wyse style */
+ /* compatibility(ATARI) */
+ term->wrap = 1;
+ break;
+ case 'w': /* Autowrap off */
+ /* compatibility(ATARI) */
+ term->wrap = 0;
+ break;
+
+ case 'R':
+ /* compatibility(OTHER) */
+ term->vt52_bold = FALSE;
+ term->curr_attr = ATTR_DEFAULT;
+ set_erase_char(term);
+ break;
+ case 'S':
+ /* compatibility(VI50) */
+ term->curr_attr |= ATTR_UNDER;
+ break;
+ case 'W':
+ /* compatibility(VI50) */
+ term->curr_attr &= ~ATTR_UNDER;
+ break;
+ case 'U':
+ /* compatibility(VI50) */
+ term->vt52_bold = TRUE;
+ term->curr_attr |= ATTR_BOLD;
+ break;
+ case 'T':
+ /* compatibility(VI50) */
+ term->vt52_bold = FALSE;
+ term->curr_attr &= ~ATTR_BOLD;
+ break;
+#endif
+ }
+ break;
+ case VT52_Y1:
+ term->termstate = VT52_Y2;
+ move(term, term->curs.x, c - ' ', 0);
+ break;
+ case VT52_Y2:
+ term->termstate = TOPLEVEL;
+ move(term, c - ' ', term->curs.y, 0);
+ break;
+
+#ifdef VT52_PLUS
+ case VT52_FG:
+ term->termstate = TOPLEVEL;
+ term->curr_attr &= ~ATTR_FGMASK;
+ term->curr_attr &= ~ATTR_BOLD;
+ term->curr_attr |= (c & 0xF) << ATTR_FGSHIFT;
+ set_erase_char(term);
+ break;
+ case VT52_BG:
+ term->termstate = TOPLEVEL;
+ term->curr_attr &= ~ATTR_BGMASK;
+ term->curr_attr &= ~ATTR_BLINK;
+ term->curr_attr |= (c & 0xF) << ATTR_BGSHIFT;
+ set_erase_char(term);
+ break;
+#endif
+ default: break; /* placate gcc warning about enum use */
+ }
+ if (term->selstate != NO_SELECTION) {
+ pos cursplus = term->curs;
+ incpos(cursplus);
+ check_selection(term, term->curs, cursplus);
+ }
+ }
+
+ term_print_flush(term);
+ if (term->cfg.logflush)
+ logflush(term->logctx);
+}
+
+/*
+ * To prevent having to run the reasonably tricky bidi algorithm
+ * too many times, we maintain a cache of the last lineful of data
+ * fed to the algorithm on each line of the display.
+ */
+static int term_bidi_cache_hit(Terminal *term, int line,
+ termchar *lbefore, int width)
+{
+ int i;
+
+ if (!term->pre_bidi_cache)
+ return FALSE; /* cache doesn't even exist yet! */
+
+ if (line >= term->bidi_cache_size)
+ return FALSE; /* cache doesn't have this many lines */
+
+ if (!term->pre_bidi_cache[line].chars)
+ return FALSE; /* cache doesn't contain _this_ line */
+
+ if (term->pre_bidi_cache[line].width != width)
+ return FALSE; /* line is wrong width */
+
+ for (i = 0; i < width; i++)
+ if (!termchars_equal(term->pre_bidi_cache[line].chars+i, lbefore+i))
+ return FALSE; /* line doesn't match cache */
+
+ return TRUE; /* it didn't match. */
+}
+
+static void term_bidi_cache_store(Terminal *term, int line, termchar *lbefore,
+ termchar *lafter, bidi_char *wcTo,
+ int width, int size)
+{
+ int i;
+
+ if (!term->pre_bidi_cache || term->bidi_cache_size <= line) {
+ int j = term->bidi_cache_size;
+ term->bidi_cache_size = line+1;
+ term->pre_bidi_cache = sresize(term->pre_bidi_cache,
+ term->bidi_cache_size,
+ struct bidi_cache_entry);
+ term->post_bidi_cache = sresize(term->post_bidi_cache,
+ term->bidi_cache_size,
+ struct bidi_cache_entry);
+ while (j < term->bidi_cache_size) {
+ term->pre_bidi_cache[j].chars =
+ term->post_bidi_cache[j].chars = NULL;
+ term->pre_bidi_cache[j].width =
+ term->post_bidi_cache[j].width = -1;
+ term->pre_bidi_cache[j].forward =
+ term->post_bidi_cache[j].forward = NULL;
+ term->pre_bidi_cache[j].backward =
+ term->post_bidi_cache[j].backward = NULL;
+ j++;
+ }
+ }
+
+ sfree(term->pre_bidi_cache[line].chars);
+ sfree(term->post_bidi_cache[line].chars);
+ sfree(term->post_bidi_cache[line].forward);
+ sfree(term->post_bidi_cache[line].backward);
+
+ term->pre_bidi_cache[line].width = width;
+ term->pre_bidi_cache[line].chars = snewn(size, termchar);
+ term->post_bidi_cache[line].width = width;
+ term->post_bidi_cache[line].chars = snewn(size, termchar);
+ term->post_bidi_cache[line].forward = snewn(width, int);
+ term->post_bidi_cache[line].backward = snewn(width, int);
+
+ memcpy(term->pre_bidi_cache[line].chars, lbefore, size * TSIZE);
+ memcpy(term->post_bidi_cache[line].chars, lafter, size * TSIZE);
+ memset(term->post_bidi_cache[line].forward, 0, width * sizeof(int));
+ memset(term->post_bidi_cache[line].backward, 0, width * sizeof(int));
+
+ for (i = 0; i < width; i++) {
+ int p = wcTo[i].index;
+
+ assert(0 <= p && p < width);
+
+ term->post_bidi_cache[line].backward[i] = p;
+ term->post_bidi_cache[line].forward[p] = i;
+ }
+}
+
+/*
+ * Prepare the bidi information for a screen line. Returns the
+ * transformed list of termchars, or NULL if no transformation at
+ * all took place (because bidi is disabled). If return was
+ * non-NULL, auxiliary information such as the forward and reverse
+ * mappings of permutation position are available in
+ * term->post_bidi_cache[scr_y].*.
+ */
+static termchar *term_bidi_line(Terminal *term, struct termline *ldata,
+ int scr_y)
+{
+ termchar *lchars;
+ int it;
+
+ /* Do Arabic shaping and bidi. */
+ if(!term->cfg.bidi || !term->cfg.arabicshaping) {
+
+ if (!term_bidi_cache_hit(term, scr_y, ldata->chars, term->cols)) {
+
+ if (term->wcFromTo_size < term->cols) {
+ term->wcFromTo_size = term->cols;
+ term->wcFrom = sresize(term->wcFrom, term->wcFromTo_size,
+ bidi_char);
+ term->wcTo = sresize(term->wcTo, term->wcFromTo_size,
+ bidi_char);
+ }
+
+ for(it=0; it<term->cols ; it++)
+ {
+ unsigned long uc = (ldata->chars[it].chr);
+
+ switch (uc & CSET_MASK) {
+ case CSET_LINEDRW:
+ if (!term->cfg.rawcnp) {
+ uc = term->ucsdata->unitab_xterm[uc & 0xFF];
+ break;
+ }
+ case CSET_ASCII:
+ uc = term->ucsdata->unitab_line[uc & 0xFF];
+ break;
+ case CSET_SCOACS:
+ uc = term->ucsdata->unitab_scoacs[uc&0xFF];
+ break;
+ }
+ switch (uc & CSET_MASK) {
+ case CSET_ACP:
+ uc = term->ucsdata->unitab_font[uc & 0xFF];
+ break;
+ case CSET_OEMCP:
+ uc = term->ucsdata->unitab_oemcp[uc & 0xFF];
+ break;
+ }
+
+ term->wcFrom[it].origwc = term->wcFrom[it].wc =
+ (wchar_t)uc;
+ term->wcFrom[it].index = it;
+ }
+
+ if(!term->cfg.bidi)
+ do_bidi(term->wcFrom, term->cols);
+
+ /* this is saved iff done from inside the shaping */
+ if(!term->cfg.bidi && term->cfg.arabicshaping)
+ for(it=0; it<term->cols; it++)
+ term->wcTo[it] = term->wcFrom[it];
+
+ if(!term->cfg.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,
+ termchar);
+ }
+
+ memcpy(term->ltemp, ldata->chars, ldata->size * TSIZE);
+
+ for(it=0; it<term->cols ; it++)
+ {
+ term->ltemp[it] = ldata->chars[term->wcTo[it].index];
+ if (term->ltemp[it].cc_next)
+ term->ltemp[it].cc_next -=
+ it - term->wcTo[it].index;
+
+ if (term->wcTo[it].origwc != term->wcTo[it].wc)
+ term->ltemp[it].chr = term->wcTo[it].wc;
+ }
+ term_bidi_cache_store(term, scr_y, ldata->chars,
+ term->ltemp, term->wcTo,
+ term->cols, ldata->size);
+
+ lchars = term->ltemp;
+ } else {
+ lchars = term->post_bidi_cache[scr_y].chars;
+ }
+ } else {
+ lchars = NULL;
+ }
+
+ return lchars;
+}
+
+/*
+ * Given a context, update the window. Out of paranoia, we don't
+ * allow WM_PAINT responses to do scrolling optimisations.
+ */
+static void do_paint(Terminal *term, Context ctx, int may_optimise)
+{
+ int i, j, our_curs_y, our_curs_x;
+ int rv, cursor;
+ pos scrpos;
+ wchar_t *ch;
+ int chlen;
+#ifdef OPTIMISE_SCROLL
+ struct scrollregion *sr;
+#endif /* OPTIMISE_SCROLL */
+ termchar *newline;
+
+ chlen = 1024;
+ ch = snewn(chlen, wchar_t);
+
+ newline = snewn(term->cols, termchar);
+
+ 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
+ */
+
+ /* Has the cursor position or type changed ? */
+ if (term->cursor_on) {
+ if (term->has_focus) {
+ if (term->cblinker || !term->cfg.blink_cur)
+ cursor = TATTR_ACTCURS;
+ else
+ cursor = 0;
+ } else
+ cursor = TATTR_PASCURS;
+ if (term->wrapnext)
+ cursor |= TATTR_RIGHTCURS;
+ } else
+ cursor = 0;
+ our_curs_y = term->curs.y - term->disptop;
+ {
+ /*
+ * Adjust the cursor position:
+ * - for bidi
+ * - in the case where it's resting on the right-hand half
+ * of a CJK wide character. xterm's behaviour here,
+ * which seems adequate to me, is to display the cursor
+ * covering the _whole_ character, exactly as if it were
+ * one space to the left.
+ */
+ termline *ldata = lineptr(term->curs.y);
+ termchar *lchars;
+
+ our_curs_x = term->curs.x;
+
+ if ( (lchars = term_bidi_line(term, ldata, our_curs_y)) != NULL) {
+ our_curs_x = term->post_bidi_cache[our_curs_y].forward[our_curs_x];
+ } else
+ lchars = ldata->chars;
+
+ if (our_curs_x > 0 &&
+ lchars[our_curs_x].chr == UCSWIDE)
+ our_curs_x--;
+
+ unlineptr(ldata);
+ }
+
+ /*
+ * If the cursor is not where it was last time we painted, and
+ * its previous position is visible on screen, invalidate its
+ * previous position.
+ */
+ if (term->dispcursy >= 0 &&
+ (term->curstype != cursor ||
+ term->dispcursy != our_curs_y ||
+ term->dispcursx != our_curs_x)) {
+ termchar *dispcurs = term->disptext[term->dispcursy]->chars +
+ term->dispcursx;
+
+ if (term->dispcursx > 0 && dispcurs->chr == UCSWIDE)
+ dispcurs[-1].attr |= ATTR_INVALID;
+ if (term->dispcursx < term->cols-1 && dispcurs[1].chr == UCSWIDE)
+ dispcurs[1].attr |= ATTR_INVALID;
+ dispcurs->attr |= ATTR_INVALID;
+
+ term->curstype = 0;
+ }
+ term->dispcursx = term->dispcursy = -1;
+
+#ifdef OPTIMISE_SCROLL
+ /* Do scrolls */
+ sr = term->scrollhead;
+ while (sr) {
+ struct scrollregion *next = sr->next;
+ do_scroll(ctx, sr->topline, sr->botline, sr->lines);
+ sfree(sr);
+ sr = next;
+ }
+ term->scrollhead = term->scrolltail = NULL;
+#endif /* OPTIMISE_SCROLL */
+
+ /* The normal screen data */
+ for (i = 0; i < term->rows; i++) {
+ termline *ldata;
+ termchar *lchars;
+ int dirty_line, dirty_run, selected;
+ unsigned long attr = 0, cset = 0;
+ int start = 0;
+ int ccount = 0;
+ int last_run_dirty = 0;
+ int laststart, dirtyrect;
+ int *backward;
+
+ scrpos.y = i + term->disptop;
+ ldata = lineptr(scrpos.y);
+
+ /* Do Arabic shaping and bidi. */
+ lchars = term_bidi_line(term, ldata, i);
+ if (lchars) {
+ backward = term->post_bidi_cache[i].backward;
+ } else {
+ lchars = ldata->chars;
+ backward = NULL;
+ }
+
+ /*
+ * First loop: work along the line deciding what we want
+ * each character cell to look like.
+ */
+ for (j = 0; j < term->cols; j++) {
+ unsigned long tattr, tchar;
+ termchar *d = lchars + j;
+ scrpos.x = backward ? backward[j] : j;
+
+ tchar = d->chr;
+ tattr = d->attr;
+
+ if (!term->cfg.ansi_colour)
+ tattr = (tattr & ~(ATTR_FGMASK | ATTR_BGMASK)) |
+ ATTR_DEFFG | ATTR_DEFBG;
+
+ if (!term->cfg.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;
+ if (colour >= 16 && colour < 256)
+ tattr = (tattr &~ ATTR_BGMASK) | ATTR_DEFBG;
+ }
+
+ switch (tchar & CSET_MASK) {
+ case CSET_ASCII:
+ tchar = term->ucsdata->unitab_line[tchar & 0xFF];
+ break;
+ case CSET_LINEDRW:
+ tchar = term->ucsdata->unitab_xterm[tchar & 0xFF];
+ break;
+ case CSET_SCOACS:
+ tchar = term->ucsdata->unitab_scoacs[tchar&0xFF];
+ break;
+ }
+ if (j < term->cols-1 && d[1].chr == UCSWIDE)
+ tattr |= ATTR_WIDE;
+
+ /* Video reversing things */
+ if (term->selstate == DRAGGING || term->selstate == SELECTED) {
+ if (term->seltype == LEXICOGRAPHIC)
+ selected = (posle(term->selstart, scrpos) &&
+ poslt(scrpos, term->selend));
+ else
+ selected = (posPle(term->selstart, scrpos) &&
+ posPlt(scrpos, term->selend));
+ } else
+ selected = FALSE;
+ tattr = (tattr ^ rv
+ ^ (selected ? ATTR_REVERSE : 0));
+
+ /* 'Real' blinking ? */
+ if (term->blink_is_real && (tattr & ATTR_BLINK)) {
+ if (term->has_focus && term->tblinker) {
+ tchar = term->ucsdata->unitab_line[(unsigned char)' '];
+ }
+ tattr &= ~ATTR_BLINK;
+ }
+
+ /*
+ * Check the font we'll _probably_ be using to see if
+ * the character is wide when we don't want it to be.
+ */
+ if (tchar != term->disptext[i]->chars[j].chr ||
+ tattr != (term->disptext[i]->chars[j].attr &~
+ (ATTR_NARROW | DATTR_MASK))) {
+ if ((tattr & ATTR_WIDE) == 0 && char_width(ctx, tchar) == 2)
+ tattr |= ATTR_NARROW;
+ } else if (term->disptext[i]->chars[j].attr & ATTR_NARROW)
+ tattr |= ATTR_NARROW;
+
+ if (i == our_curs_y && j == our_curs_x) {
+ tattr |= cursor;
+ term->curstype = cursor;
+ term->dispcursx = j;
+ term->dispcursy = i;
+ }
+
+ /* FULL-TERMCHAR */
+ newline[j].attr = tattr;
+ newline[j].chr = tchar;
+ /* Combining characters are still read from lchars */
+ newline[j].cc_next = 0;
+ }
+
+ /*
+ * Now loop over the line again, noting where things have
+ * changed.
+ *
+ * During this loop, we keep track of where we last saw
+ * DATTR_STARTRUN. Any mismatch automatically invalidates
+ * _all_ of the containing run that was last printed: that
+ * is, any rectangle that was drawn in one go in the
+ * previous update should be either left completely alone
+ * or overwritten in its entirety. This, along with the
+ * expectation that front ends clip all text runs to their
+ * bounding rectangle, should solve any possible problems
+ * with fonts that overflow their character cells.
+ */
+ laststart = 0;
+ dirtyrect = FALSE;
+ for (j = 0; j < term->cols; j++) {
+ if (term->disptext[i]->chars[j].attr & DATTR_STARTRUN) {
+ laststart = j;
+ dirtyrect = FALSE;
+ }
+
+ if (term->disptext[i]->chars[j].chr != newline[j].chr ||
+ (term->disptext[i]->chars[j].attr &~ DATTR_MASK)
+ != newline[j].attr) {
+ int k;
+
+ if (!dirtyrect) {
+ for (k = laststart; k < j; k++)
+ term->disptext[i]->chars[k].attr |= ATTR_INVALID;
+
+ dirtyrect = TRUE;
+ }
+ }
+
+ if (dirtyrect)
+ term->disptext[i]->chars[j].attr |= ATTR_INVALID;
+ }
+
+ /*
+ * Finally, loop once more and actually do the drawing.
+ */
+ dirty_run = dirty_line = (ldata->lattr !=
+ term->disptext[i]->lattr);
+ term->disptext[i]->lattr = ldata->lattr;
+
+ for (j = 0; j < term->cols; j++) {
+ unsigned long tattr, tchar;
+ int break_run, do_copy;
+ termchar *d = lchars + j;
+
+ tattr = newline[j].attr;
+ tchar = newline[j].chr;
+
+ if ((term->disptext[i]->chars[j].attr ^ tattr) & ATTR_WIDE)
+ dirty_line = TRUE;
+
+ break_run = ((tattr ^ attr) & term->attr_mask) != 0;
+
+ /* Special hack for VT100 Linedraw glyphs */
+ if (tchar >= 0x23BA && tchar <= 0x23BD)
+ break_run = TRUE;
+
+ /*
+ * Separate out sequences of characters that have the
+ * same CSET, if that CSET is a magic one.
+ */
+ if (CSET_OF(tchar) != cset)
+ break_run = TRUE;
+
+ /*
+ * Break on both sides of any combined-character cell.
+ */
+ if (d->cc_next != 0 ||
+ (j > 0 && d[-1].cc_next != 0))
+ break_run = TRUE;
+
+ if (!term->ucsdata->dbcs_screenfont && !dirty_line) {
+ if (term->disptext[i]->chars[j].chr == tchar &&
+ (term->disptext[i]->chars[j].attr &~ DATTR_MASK) == tattr)
+ break_run = TRUE;
+ else if (!dirty_run && ccount == 1)
+ break_run = TRUE;
+ }
+
+ if (break_run) {
+ if ((dirty_run || last_run_dirty) && ccount > 0) {
+ do_text(ctx, start, i, ch, ccount, attr,
+ ldata->lattr);
+ if (attr & (TATTR_ACTCURS | TATTR_PASCURS))
+ do_cursor(ctx, start, i, ch, ccount, attr,
+ ldata->lattr);
+ }
+ start = j;
+ ccount = 0;
+ attr = tattr;
+ cset = CSET_OF(tchar);
+ if (term->ucsdata->dbcs_screenfont)
+ last_run_dirty = dirty_run;
+ dirty_run = dirty_line;
+ }
+
+ do_copy = FALSE;
+ if (!termchars_equal_override(&term->disptext[i]->chars[j],
+ d, tchar, tattr)) {
+ do_copy = TRUE;
+ dirty_run = TRUE;
+ }
+
+ if (ccount >= chlen) {
+ chlen = ccount + 256;
+ ch = sresize(ch, chlen, wchar_t);
+ }
+ ch[ccount++] = (wchar_t) tchar;
+
+ if (d->cc_next) {
+ termchar *dd = d;
+
+ while (dd->cc_next) {
+ unsigned long schar;
+
+ dd += dd->cc_next;
+
+ schar = dd->chr;
+ switch (schar & CSET_MASK) {
+ case CSET_ASCII:
+ schar = term->ucsdata->unitab_line[schar & 0xFF];
+ break;
+ case CSET_LINEDRW:
+ schar = term->ucsdata->unitab_xterm[schar & 0xFF];
+ break;
+ case CSET_SCOACS:
+ schar = term->ucsdata->unitab_scoacs[schar&0xFF];
+ break;
+ }
+
+ if (ccount >= chlen) {
+ chlen = ccount + 256;
+ ch = sresize(ch, chlen, wchar_t);
+ }
+ ch[ccount++] = (wchar_t) schar;
+ }
+
+ attr |= TATTR_COMBINING;
+ }
+
+ if (do_copy) {
+ copy_termchar(term->disptext[i], j, d);
+ term->disptext[i]->chars[j].chr = tchar;
+ term->disptext[i]->chars[j].attr = tattr;
+ if (start == j)
+ term->disptext[i]->chars[j].attr |= DATTR_STARTRUN;
+ }
+
+ /* If it's a wide char step along to the next one. */
+ if (tattr & ATTR_WIDE) {
+ if (++j < term->cols) {
+ d++;
+ /*
+ * By construction above, the cursor should not
+ * be on the right-hand half of this character.
+ * Ever.
+ */
+ assert(!(i == our_curs_y && j == our_curs_x));
+ if (!termchars_equal(&term->disptext[i]->chars[j], d))
+ dirty_run = TRUE;
+ copy_termchar(term->disptext[i], j, d);
+ }
+ }
+ }
+ if (dirty_run && ccount > 0) {
+ do_text(ctx, start, i, ch, ccount, attr,
+ ldata->lattr);
+ if (attr & (TATTR_ACTCURS | TATTR_PASCURS))
+ do_cursor(ctx, start, i, ch, ccount, attr,
+ ldata->lattr);
+ }
+
+ unlineptr(ldata);
+ }
+
+ sfree(newline);
+ sfree(ch);
+}
+
+/*
+ * Invalidate the whole screen so it will be repainted in full.
+ */
+void term_invalidate(Terminal *term)
+{
+ int i, j;
+
+ for (i = 0; i < term->rows; i++)
+ for (j = 0; j < term->cols; j++)
+ term->disptext[i]->chars[j].attr |= ATTR_INVALID;
+
+ term_schedule_update(term);
+}
+
+/*
+ * Paint the window in response to a WM_PAINT message.
+ */
+void term_paint(Terminal *term, Context ctx,
+ int left, int top, int right, int bottom, int immediately)
+{
+ int i, j;
+ if (left < 0) left = 0;
+ if (top < 0) top = 0;
+ if (right >= term->cols) right = term->cols-1;
+ if (bottom >= term->rows) bottom = term->rows-1;
+
+ for (i = top; i <= bottom && i < term->rows; i++) {
+ if ((term->disptext[i]->lattr & LATTR_MODE) == LATTR_NORM)
+ for (j = left; j <= right && j < term->cols; j++)
+ term->disptext[i]->chars[j].attr |= ATTR_INVALID;
+ else
+ for (j = left / 2; j <= right / 2 + 1 && j < term->cols; j++)
+ term->disptext[i]->chars[j].attr |= ATTR_INVALID;
+ }
+
+ if (immediately) {
+ do_paint (term, ctx, FALSE);
+ } else {
+ term_schedule_update(term);
+ }
+}
+
+/*
+ * Attempt to scroll the scrollback. The second parameter gives the
+ * position we want to scroll to; the first is +1 to denote that
+ * this position is relative to the beginning of the scrollback, -1
+ * to denote it is relative to the end, and 0 to denote that it is
+ * relative to the current position.
+ */
+void term_scroll(Terminal *term, int rel, int where)
+{
+ int sbtop = -sblines(term);
+#ifdef OPTIMISE_SCROLL
+ int olddisptop = term->disptop;
+ int shift;
+#endif /* OPTIMISE_SCROLL */
+
+ term->disptop = (rel < 0 ? 0 : rel > 0 ? sbtop : term->disptop) + where;
+ if (term->disptop < sbtop)
+ term->disptop = sbtop;
+ if (term->disptop > 0)
+ term->disptop = 0;
+ update_sbar(term);
+#ifdef OPTIMISE_SCROLL
+ shift = (term->disptop - olddisptop);
+ if (shift < term->rows && shift > -term->rows)
+ scroll_display(term, 0, term->rows - 1, shift);
+#endif /* OPTIMISE_SCROLL */
+ term_update(term);
+}
+
+/*
+ * Scroll the scrollback to centre it on the beginning or end of the
+ * current selection, if any.
+ */
+void term_scroll_to_selection(Terminal *term, int which_end)
+{
+ pos target;
+ int y;
+ int sbtop = -sblines(term);
+
+ if (term->selstate != SELECTED)
+ return;
+ if (which_end)
+ target = term->selend;
+ else
+ target = term->selstart;
+
+ y = target.y - term->rows/2;
+ if (y < sbtop)
+ y = sbtop;
+ else if (y > 0)
+ y = 0;
+ term_scroll(term, -1, y);
+}
+
+/*
+ * Helper routine for clipme(): growing buffer.
+ */
+typedef struct {
+ int buflen; /* amount of allocated space in textbuf/attrbuf */
+ int bufpos; /* amount of actual data */
+ wchar_t *textbuf; /* buffer for copied text */
+ wchar_t *textptr; /* = textbuf + bufpos (current insertion point) */
+ int *attrbuf; /* buffer for copied attributes */
+ int *attrptr; /* = attrbuf + bufpos */
+} clip_workbuf;
+
+static void clip_addchar(clip_workbuf *b, wchar_t chr, int attr)
+{
+ if (b->bufpos >= b->buflen) {
+ b->buflen += 128;
+ b->textbuf = sresize(b->textbuf, b->buflen, wchar_t);
+ b->textptr = b->textbuf + b->bufpos;
+ b->attrbuf = sresize(b->attrbuf, b->buflen, int);
+ b->attrptr = b->attrbuf + b->bufpos;
+ }
+ *b->textptr++ = chr;
+ *b->attrptr++ = attr;
+ b->bufpos++;
+}
+
+static void clipme(Terminal *term, pos top, pos bottom, int rect, int desel)
+{
+ clip_workbuf buf;
+ int old_top_x;
+ int attr;
+
+ buf.buflen = 5120;
+ buf.bufpos = 0;
+ buf.textptr = buf.textbuf = snewn(buf.buflen, wchar_t);
+ buf.attrptr = buf.attrbuf = snewn(buf.buflen, int);
+
+ old_top_x = top.x; /* needed for rect==1 */
+
+ while (poslt(top, bottom)) {
+ int nl = FALSE;
+ termline *ldata = lineptr(top.y);
+ pos nlpos;
+
+ /*
+ * nlpos will point at the maximum position on this line we
+ * should copy up to. So we start it at the end of the
+ * line...
+ */
+ nlpos.y = top.y;
+ nlpos.x = term->cols;
+
+ /*
+ * ... move it backwards if there's unused space at the end
+ * of the line (and also set `nl' if this is the case,
+ * because in normal selection mode this means we need a
+ * newline at the end)...
+ */
+ if (!(ldata->lattr & LATTR_WRAPPED)) {
+ while (nlpos.x &&
+ IS_SPACE_CHR(ldata->chars[nlpos.x - 1].chr) &&
+ !ldata->chars[nlpos.x - 1].cc_next &&
+ poslt(top, nlpos))
+ decpos(nlpos);
+ if (poslt(nlpos, bottom))
+ nl = TRUE;
+ } else if (ldata->lattr & LATTR_WRAPPED2) {
+ /* Ignore the last char on the line in a WRAPPED2 line. */
+ decpos(nlpos);
+ }
+
+ /*
+ * ... and then clip it to the terminal x coordinate if
+ * we're doing rectangular selection. (In this case we
+ * still did the above, so that copying e.g. the right-hand
+ * column from a table doesn't fill with spaces on the
+ * right.)
+ */
+ if (rect) {
+ if (nlpos.x > bottom.x)
+ nlpos.x = bottom.x;
+ nl = (top.y < bottom.y);
+ }
+
+ while (poslt(top, bottom) && poslt(top, nlpos)) {
+#if 0
+ char cbuf[16], *p;
+ sprintf(cbuf, "<U+%04x>", (ldata[top.x] & 0xFFFF));
+#else
+ wchar_t cbuf[16], *p;
+ int c;
+ int x = top.x;
+
+ if (ldata->chars[x].chr == UCSWIDE) {
+ top.x++;
+ continue;
+ }
+
+ while (1) {
+ int uc = ldata->chars[x].chr;
+ attr = ldata->chars[x].attr;
+
+ switch (uc & CSET_MASK) {
+ case CSET_LINEDRW:
+ if (!term->cfg.rawcnp) {
+ uc = term->ucsdata->unitab_xterm[uc & 0xFF];
+ break;
+ }
+ case CSET_ASCII:
+ uc = term->ucsdata->unitab_line[uc & 0xFF];
+ break;
+ case CSET_SCOACS:
+ uc = term->ucsdata->unitab_scoacs[uc&0xFF];
+ break;
+ }
+ switch (uc & CSET_MASK) {
+ case CSET_ACP:
+ uc = term->ucsdata->unitab_font[uc & 0xFF];
+ break;
+ case CSET_OEMCP:
+ uc = term->ucsdata->unitab_oemcp[uc & 0xFF];
+ break;
+ }
+
+ c = (uc & ~CSET_MASK);
+#ifdef PLATFORM_IS_UTF16
+ if (uc > 0x10000 && uc < 0x110000) {
+ cbuf[0] = 0xD800 | ((uc - 0x10000) >> 10);
+ cbuf[1] = 0xDC00 | ((uc - 0x10000) & 0x3FF);
+ cbuf[2] = 0;
+ } else
+#endif
+ {
+ cbuf[0] = uc;
+ cbuf[1] = 0;
+ }
+
+ if (DIRECT_FONT(uc)) {
+ if (c >= ' ' && c != 0x7F) {
+ char buf[4];
+ WCHAR wbuf[4];
+ int rv;
+ if (is_dbcs_leadbyte(term->ucsdata->font_codepage, (BYTE) c)) {
+ buf[0] = c;
+ buf[1] = (char) (0xFF & ldata->chars[top.x + 1].chr);
+ rv = mb_to_wc(term->ucsdata->font_codepage, 0, buf, 2, wbuf, 4);
+ top.x++;
+ } else {
+ buf[0] = c;
+ rv = mb_to_wc(term->ucsdata->font_codepage, 0, buf, 1, wbuf, 4);
+ }
+
+ if (rv > 0) {
+ memcpy(cbuf, wbuf, rv * sizeof(wchar_t));
+ cbuf[rv] = 0;
+ }
+ }
+ }
+#endif
+
+ for (p = cbuf; *p; p++)
+ clip_addchar(&buf, *p, attr);
+
+ if (ldata->chars[x].cc_next)
+ x += ldata->chars[x].cc_next;
+ else
+ break;
+ }
+ top.x++;
+ }
+ if (nl) {
+ int i;
+ for (i = 0; i < sel_nl_sz; i++)
+ clip_addchar(&buf, sel_nl[i], 0);
+ }
+ top.y++;
+ top.x = rect ? old_top_x : 0;
+
+ unlineptr(ldata);
+ }
+#if SELECTION_NUL_TERMINATED
+ clip_addchar(&buf, 0, 0);
+#endif
+ /* Finally, transfer all that to the clipboard. */
+ write_clip(term->frontend, buf.textbuf, buf.attrbuf, buf.bufpos, desel);
+ sfree(buf.textbuf);
+ sfree(buf.attrbuf);
+}
+
+void term_copyall(Terminal *term)
+{
+ pos top;
+ pos bottom;
+ tree234 *screen = term->screen;
+ top.y = -sblines(term);
+ top.x = 0;
+ bottom.y = find_last_nonempty_line(term, screen);
+ bottom.x = term->cols;
+ clipme(term, top, bottom, 0, TRUE);
+}
+
+/*
+ * The wordness array is mainly for deciding the disposition of the
+ * US-ASCII characters.
+ */
+static int wordtype(Terminal *term, int uc)
+{
+ struct ucsword {
+ int start, end, ctype;
+ };
+ static const struct ucsword ucs_words[] = {
+ {
+ 128, 160, 0}, {
+ 161, 191, 1}, {
+ 215, 215, 1}, {
+ 247, 247, 1}, {
+ 0x037e, 0x037e, 1}, /* Greek question mark */
+ {
+ 0x0387, 0x0387, 1}, /* Greek ano teleia */
+ {
+ 0x055a, 0x055f, 1}, /* Armenian punctuation */
+ {
+ 0x0589, 0x0589, 1}, /* Armenian full stop */
+ {
+ 0x0700, 0x070d, 1}, /* Syriac punctuation */
+ {
+ 0x104a, 0x104f, 1}, /* Myanmar punctuation */
+ {
+ 0x10fb, 0x10fb, 1}, /* Georgian punctuation */
+ {
+ 0x1361, 0x1368, 1}, /* Ethiopic punctuation */
+ {
+ 0x166d, 0x166e, 1}, /* Canadian Syl. punctuation */
+ {
+ 0x17d4, 0x17dc, 1}, /* Khmer punctuation */
+ {
+ 0x1800, 0x180a, 1}, /* Mongolian punctuation */
+ {
+ 0x2000, 0x200a, 0}, /* Various spaces */
+ {
+ 0x2070, 0x207f, 2}, /* superscript */
+ {
+ 0x2080, 0x208f, 2}, /* subscript */
+ {
+ 0x200b, 0x27ff, 1}, /* punctuation and symbols */
+ {
+ 0x3000, 0x3000, 0}, /* ideographic space */
+ {
+ 0x3001, 0x3020, 1}, /* ideographic punctuation */
+ {
+ 0x303f, 0x309f, 3}, /* Hiragana */
+ {
+ 0x30a0, 0x30ff, 3}, /* Katakana */
+ {
+ 0x3300, 0x9fff, 3}, /* CJK Ideographs */
+ {
+ 0xac00, 0xd7a3, 3}, /* Hangul Syllables */
+ {
+ 0xf900, 0xfaff, 3}, /* CJK Ideographs */
+ {
+ 0xfe30, 0xfe6b, 1}, /* punctuation forms */
+ {
+ 0xff00, 0xff0f, 1}, /* half/fullwidth ASCII */
+ {
+ 0xff1a, 0xff20, 1}, /* half/fullwidth ASCII */
+ {
+ 0xff3b, 0xff40, 1}, /* half/fullwidth ASCII */
+ {
+ 0xff5b, 0xff64, 1}, /* half/fullwidth ASCII */
+ {
+ 0xfff0, 0xffff, 0}, /* half/fullwidth ASCII */
+ {
+ 0, 0, 0}
+ };
+ const struct ucsword *wptr;
+
+ switch (uc & CSET_MASK) {
+ case CSET_LINEDRW:
+ uc = term->ucsdata->unitab_xterm[uc & 0xFF];
+ break;
+ case CSET_ASCII:
+ uc = term->ucsdata->unitab_line[uc & 0xFF];
+ break;
+ case CSET_SCOACS:
+ uc = term->ucsdata->unitab_scoacs[uc&0xFF];
+ break;
+ }
+ switch (uc & CSET_MASK) {
+ case CSET_ACP:
+ uc = term->ucsdata->unitab_font[uc & 0xFF];
+ break;
+ case CSET_OEMCP:
+ uc = term->ucsdata->unitab_oemcp[uc & 0xFF];
+ break;
+ }
+
+ /* For DBCS fonts I can't do anything useful. Even this will sometimes
+ * fail as there's such a thing as a double width space. :-(
+ */
+ if (term->ucsdata->dbcs_screenfont &&
+ term->ucsdata->font_codepage == term->ucsdata->line_codepage)
+ return (uc != ' ');
+
+ if (uc < 0x80)
+ return term->wordness[uc];
+
+ for (wptr = ucs_words; wptr->start; wptr++) {
+ if (uc >= wptr->start && uc <= wptr->end)
+ return wptr->ctype;
+ }
+
+ return 2;
+}
+
+/*
+ * Spread the selection outwards according to the selection mode.
+ */
+static pos sel_spread_half(Terminal *term, pos p, int dir)
+{
+ termline *ldata;
+ short wvalue;
+ int topy = -sblines(term);
+
+ ldata = lineptr(p.y);
+
+ switch (term->selmode) {
+ case SM_CHAR:
+ /*
+ * In this mode, every character is a separate unit, except
+ * for runs of spaces at the end of a non-wrapping line.
+ */
+ if (!(ldata->lattr & LATTR_WRAPPED)) {
+ termchar *q = ldata->chars + term->cols;
+ while (q > ldata->chars &&
+ IS_SPACE_CHR(q[-1].chr) && !q[-1].cc_next)
+ q--;
+ if (q == ldata->chars + term->cols)
+ q--;
+ if (p.x >= q - ldata->chars)
+ p.x = (dir == -1 ? q - ldata->chars : term->cols - 1);
+ }
+ break;
+ case SM_WORD:
+ /*
+ * In this mode, the units are maximal runs of characters
+ * whose `wordness' has the same value.
+ */
+ wvalue = wordtype(term, UCSGET(ldata->chars, p.x));
+ if (dir == +1) {
+ while (1) {
+ int maxcols = (ldata->lattr & LATTR_WRAPPED2 ?
+ term->cols-1 : term->cols);
+ if (p.x < maxcols-1) {
+ if (wordtype(term, UCSGET(ldata->chars, p.x+1)) == wvalue)
+ p.x++;
+ else
+ break;
+ } else {
+ if (ldata->lattr & LATTR_WRAPPED) {
+ termline *ldata2;
+ ldata2 = lineptr(p.y+1);
+ if (wordtype(term, UCSGET(ldata2->chars, 0))
+ == wvalue) {
+ p.x = 0;
+ p.y++;
+ unlineptr(ldata);
+ ldata = ldata2;
+ } else {
+ unlineptr(ldata2);
+ break;
+ }
+ } else
+ break;
+ }
+ }
+ } else {
+ while (1) {
+ if (p.x > 0) {
+ if (wordtype(term, UCSGET(ldata->chars, p.x-1)) == wvalue)
+ p.x--;
+ else
+ break;
+ } else {
+ termline *ldata2;
+ int maxcols;
+ if (p.y <= topy)
+ break;
+ ldata2 = lineptr(p.y-1);
+ maxcols = (ldata2->lattr & LATTR_WRAPPED2 ?
+ term->cols-1 : term->cols);
+ if (ldata2->lattr & LATTR_WRAPPED) {
+ if (wordtype(term, UCSGET(ldata2->chars, maxcols-1))
+ == wvalue) {
+ p.x = maxcols-1;
+ p.y--;
+ unlineptr(ldata);
+ ldata = ldata2;
+ } else {
+ unlineptr(ldata2);
+ break;
+ }
+ } else
+ break;
+ }
+ }
+ }
+ break;
+ case SM_LINE:
+ /*
+ * In this mode, every line is a unit.
+ */
+ p.x = (dir == -1 ? 0 : term->cols - 1);
+ break;
+ }
+
+ unlineptr(ldata);
+ return p;
+}
+
+static void sel_spread(Terminal *term)
+{
+ if (term->seltype == LEXICOGRAPHIC) {
+ term->selstart = sel_spread_half(term, term->selstart, -1);
+ decpos(term->selend);
+ term->selend = sel_spread_half(term, term->selend, +1);
+ incpos(term->selend);
+ }
+}
+
+void term_do_paste(Terminal *term)
+{
+ wchar_t *data;
+ int len;
+
+ get_clip(term->frontend, &data, &len);
+ if (data && len > 0) {
+ wchar_t *p, *q;
+
+ 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);
+
+ p = q = data;
+ while (p < data + len) {
+ while (p < data + len &&
+ !(p <= data + len - sel_nl_sz &&
+ !memcmp(p, sel_nl, sizeof(sel_nl))))
+ p++;
+
+ {
+ int i;
+ for (i = 0; i < p - q; i++) {
+ term->paste_buffer[term->paste_len++] = q[i];
+ }
+ }
+
+ if (p <= data + len - sel_nl_sz &&
+ !memcmp(p, sel_nl, sizeof(sel_nl))) {
+ term->paste_buffer[term->paste_len++] = '\015';
+ p += sel_nl_sz;
+ }
+ q = p;
+ }
+
+ /* 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;
+ }
+ }
+ get_clip(term->frontend, NULL, NULL);
+}
+
+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));
+ int default_seltype;
+
+ if (y < 0) {
+ y = 0;
+ if (a == MA_DRAG && !raw_mouse)
+ term_scroll(term, 0, -1);
+ }
+ if (y >= term->rows) {
+ y = term->rows - 1;
+ if (a == MA_DRAG && !raw_mouse)
+ term_scroll(term, 0, +1);
+ }
+ if (x < 0) {
+ if (y > 0) {
+ x = term->cols - 1;
+ y--;
+ } else
+ x = 0;
+ }
+ if (x >= term->cols)
+ x = term->cols - 1;
+
+ selpoint.y = y + term->disptop;
+ ldata = lineptr(selpoint.y);
+
+ if ((ldata->lattr & LATTR_MODE) != LATTR_NORM)
+ x /= 2;
+
+ /*
+ * Transform x through the bidi algorithm to find the _logical_
+ * click point from the physical one.
+ */
+ if (term_bidi_line(term, ldata, y) != NULL) {
+ x = term->post_bidi_cache[y].backward[x];
+ }
+
+ selpoint.x = x;
+ unlineptr(ldata);
+
+ /*
+ * If we're in the middle of a selection operation, we ignore raw
+ * mouse mode until it's done (we must have been not in raw mouse
+ * mode when it started).
+ * This makes use of Shift for selection reliable, and avoids the
+ * host seeing mouse releases for which they never saw corresponding
+ * presses.
+ */
+ if (raw_mouse &&
+ (term->selstate != ABOUT_TO) && (term->selstate != DRAGGING)) {
+ int encstate = 0, r, c;
+ char abuf[16];
+
+ if (term->ldisc) {
+
+ switch (braw) {
+ case MBT_LEFT:
+ encstate = 0x20; /* left button down */
+ break;
+ case MBT_MIDDLE:
+ encstate = 0x21;
+ break;
+ case MBT_RIGHT:
+ encstate = 0x22;
+ break;
+ case MBT_WHEEL_UP:
+ encstate = 0x60;
+ break;
+ case MBT_WHEEL_DOWN:
+ encstate = 0x61;
+ break;
+ default: break; /* placate gcc warning about enum use */
+ }
+ switch (a) {
+ case MA_DRAG:
+ if (term->xterm_mouse == 1)
+ return;
+ encstate += 0x20;
+ break;
+ case MA_RELEASE:
+ encstate = 0x23;
+ term->mouse_is_down = 0;
+ break;
+ case MA_CLICK:
+ if (term->mouse_is_down == braw)
+ return;
+ term->mouse_is_down = braw;
+ break;
+ default: break; /* placate gcc warning about enum use */
+ }
+ if (shift)
+ encstate += 0x04;
+ if (ctrl)
+ encstate += 0x10;
+ r = y + 33;
+ c = x + 33;
+
+ sprintf(abuf, "\033[M%c%c%c", encstate, c, r);
+ ldisc_send(term->ldisc, abuf, 6, 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)
+ default_seltype = RECTANGULAR;
+ else
+ default_seltype = LEXICOGRAPHIC;
+
+ if (term->selstate == NO_SELECTION) {
+ term->seltype = default_seltype;
+ }
+
+ if (bcooked == MBT_SELECT && a == MA_CLICK) {
+ deselect(term);
+ term->selstate = ABOUT_TO;
+ term->seltype = default_seltype;
+ term->selanchor = selpoint;
+ term->selmode = SM_CHAR;
+ } else if (bcooked == MBT_SELECT && (a == MA_2CLK || a == MA_3CLK)) {
+ deselect(term);
+ term->selmode = (a == MA_2CLK ? SM_WORD : SM_LINE);
+ term->selstate = DRAGGING;
+ term->selstart = term->selanchor = selpoint;
+ term->selend = term->selstart;
+ incpos(term->selend);
+ sel_spread(term);
+ } else if ((bcooked == MBT_SELECT && a == MA_DRAG) ||
+ (bcooked == MBT_EXTEND && a != MA_RELEASE)) {
+ if (term->selstate == ABOUT_TO && poseq(term->selanchor, selpoint))
+ return;
+ if (bcooked == MBT_EXTEND && a != MA_DRAG &&
+ term->selstate == SELECTED) {
+ if (term->seltype == LEXICOGRAPHIC) {
+ /*
+ * For normal selection, we extend by moving
+ * whichever end of the current selection is closer
+ * to the mouse.
+ */
+ if (posdiff(selpoint, term->selstart) <
+ posdiff(term->selend, term->selstart) / 2) {
+ term->selanchor = term->selend;
+ decpos(term->selanchor);
+ } else {
+ term->selanchor = term->selstart;
+ }
+ } else {
+ /*
+ * For rectangular selection, we have a choice of
+ * _four_ places to put selanchor and selpoint: the
+ * four corners of the selection.
+ */
+ if (2*selpoint.x < term->selstart.x + term->selend.x)
+ term->selanchor.x = term->selend.x-1;
+ else
+ term->selanchor.x = term->selstart.x;
+
+ if (2*selpoint.y < term->selstart.y + term->selend.y)
+ term->selanchor.y = term->selend.y;
+ else
+ term->selanchor.y = term->selstart.y;
+ }
+ term->selstate = DRAGGING;
+ }
+ if (term->selstate != ABOUT_TO && term->selstate != DRAGGING)
+ term->selanchor = selpoint;
+ term->selstate = DRAGGING;
+ if (term->seltype == LEXICOGRAPHIC) {
+ /*
+ * For normal selection, we set (selstart,selend) to
+ * (selpoint,selanchor) in some order.
+ */
+ if (poslt(selpoint, term->selanchor)) {
+ term->selstart = selpoint;
+ term->selend = term->selanchor;
+ incpos(term->selend);
+ } else {
+ term->selstart = term->selanchor;
+ term->selend = selpoint;
+ incpos(term->selend);
+ }
+ } else {
+ /*
+ * For rectangular selection, we may need to
+ * interchange x and y coordinates (if the user has
+ * dragged in the -x and +y directions, or vice versa).
+ */
+ term->selstart.x = min(term->selanchor.x, selpoint.x);
+ term->selend.x = 1+max(term->selanchor.x, selpoint.x);
+ term->selstart.y = min(term->selanchor.y, selpoint.y);
+ term->selend.y = max(term->selanchor.y, selpoint.y);
+ }
+ sel_spread(term);
+ } else if ((bcooked == MBT_SELECT || bcooked == MBT_EXTEND) &&
+ a == MA_RELEASE) {
+ if (term->selstate == DRAGGING) {
+ /*
+ * We've completed a selection. We now transfer the
+ * data to the clipboard.
+ */
+ clipme(term, term->selstart, term->selend,
+ (term->seltype == RECTANGULAR), FALSE);
+ term->selstate = SELECTED;
+ } else
+ term->selstate = NO_SELECTION;
+ } else if (bcooked == MBT_PASTE
+ && (a == MA_CLICK
+#if MULTICLICK_ONLY_EVENT
+ || a == MA_2CLK || a == MA_3CLK
+#endif
+ )) {
+ request_paste(term->frontend);
+ }
+
+ term_update(term);
+}
+
+int format_arrow_key(char *buf, Terminal *term, int xkey, int ctrl)
+{
+ 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);
+#if 0
+ /*
+ * RDB: VT100 & VT102 manuals both state the app cursor
+ * keys only work if the app keypad is on.
+ *
+ * SGT: That may well be true, but xterm disagrees and so
+ * does at least one application, so I've #if'ed this out
+ * and the behaviour is back to PuTTY's original: app
+ * cursor and app keypad are independently switchable
+ * modes. If anyone complains about _this_ I'll have to
+ * put in a configurable option.
+ */
+ if (!term->app_keypad_keys)
+ app_flg = 0;
+#endif
+ /* Useful mapping of Ctrl-arrows */
+ if (ctrl)
+ app_flg = !app_flg;
+
+ if (app_flg)
+ p += sprintf((char *) p, "\x1BO%c", xkey);
+ else
+ p += sprintf((char *) p, "\x1B[%c", xkey);
+ }
+
+ 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;
+}
+
+static void deselect(Terminal *term)
+{
+ term->selstate = NO_SELECTION;
+ term->selstart.x = term->selstart.y = term->selend.x = term->selend.y = 0;
+}
+
+void term_deselect(Terminal *term)
+{
+ deselect(term);
+ term_update(term);
+}
+
+int term_ldisc(Terminal *term, int option)
+{
+ if (option == LD_ECHO)
+ return term->term_echoing;
+ if (option == LD_EDIT)
+ return term->term_editing;
+ return FALSE;
+}
+
+int term_data(Terminal *term, int is_stderr, const char *data, int len)
+{
+ bufchain_add(&term->inbuf, data, len);
+
+ if (!term->in_term_out) {
+ term->in_term_out = TRUE;
+ term_reset_cblink(term);
+ /*
+ * During drag-selects, we do not process terminal input,
+ * because the user will want the screen to hold still to
+ * be selected.
+ */
+ if (term->selstate != DRAGGING)
+ term_out(term);
+ term->in_term_out = FALSE;
+ }
+
+ /*
+ * term_out() always completely empties inbuf. Therefore,
+ * there's no reason at all to return anything other than zero
+ * from this function, because there _can't_ be a question of
+ * the remote side needing to wait until term_out() has cleared
+ * a backlog.
+ *
+ * This is a slightly suboptimal way to deal with SSH-2 - in
+ * principle, the window mechanism would allow us to continue
+ * to accept data on forwarded ports and X connections even
+ * while the terminal processing was going slowly - but we
+ * can't do the 100% right thing without moving the terminal
+ * processing into a separate thread, and that might hurt
+ * portability. So we manage stdout buffering the old SSH-1 way:
+ * if the terminal processing goes slowly, the whole SSH
+ * connection stops accepting data until it's ready.
+ *
+ * In practice, I can't imagine this causing serious trouble.
+ */
+ return 0;
+}
+
+/*
+ * Write untrusted data to the terminal.
+ * The only control character that should be honoured is \n (which
+ * will behave as a CRLF).
+ */
+int term_data_untrusted(Terminal *term, const char *data, int len)
+{
+ int i;
+ /* FIXME: more sophisticated checking? */
+ for (i = 0; i < len; i++) {
+ if (data[i] == '\n')
+ term_data(term, 1, "\r\n", 2);
+ else if (data[i] & 0x60)
+ term_data(term, 1, data + i, 1);
+ }
+ return 0; /* assumes that term_data() always returns 0 */
+}
+
+void term_provide_logctx(Terminal *term, void *logctx)
+{
+ term->logctx = logctx;
+}
+
+void term_set_focus(Terminal *term, int has_focus)
+{
+ term->has_focus = has_focus;
+ term_schedule_cblink(term);
+}
+
+/*
+ * Provide "auto" settings for remote tty modes, suitable for an
+ * application with a terminal window.
+ */
+char *term_get_ttymode(Terminal *term, const char *mode)
+{
+ char *val = NULL;
+ if (strcmp(mode, "ERASE") == 0) {
+ val = term->cfg.bksp_is_delete ? "^?" : "^H";
+ }
+ /* FIXME: perhaps we should set ONLCR based on cfg.lfhascr as well? */
+ /* FIXME: or ECHO and friends based on local echo state? */
+ return dupstr(val);
+}
+
+struct term_userpass_state {
+ size_t curr_prompt;
+ int done_prompt; /* printed out prompt yet? */
+ size_t pos; /* cursor position */
+};
+
+/*
+ * Process some terminal data in the course of username/password
+ * input.
+ */
+int term_get_userpass_input(Terminal *term, prompts_t *p,
+ unsigned char *in, int inlen)
+{
+ struct term_userpass_state *s = (struct term_userpass_state *)p->data;
+ if (!s) {
+ /*
+ * First call. Set some stuff up.
+ */
+ p->data = s = snew(struct term_userpass_state);
+ s->curr_prompt = 0;
+ s->done_prompt = 0;
+ /* We only print the `name' caption if we have to... */
+ if (p->name_reqd && p->name) {
+ size_t l = strlen(p->name);
+ term_data_untrusted(term, p->name, l);
+ if (p->name[l-1] != '\n')
+ term_data_untrusted(term, "\n", 1);
+ }
+ /* ...but we always print any `instruction'. */
+ if (p->instruction) {
+ size_t l = strlen(p->instruction);
+ term_data_untrusted(term, p->instruction, l);
+ if (p->instruction[l-1] != '\n')
+ term_data_untrusted(term, "\n", 1);
+ }
+ /*
+ * 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);
+ }
+ }
+
+ while (s->curr_prompt < p->n_prompts) {
+
+ prompt_t *pr = p->prompts[s->curr_prompt];
+ int finished_prompt = 0;
+
+ if (!s->done_prompt) {
+ term_data_untrusted(term, pr->prompt, strlen(pr->prompt));
+ s->done_prompt = 1;
+ s->pos = 0;
+ }
+
+ /* Breaking out here ensures that the prompt is printed even
+ * if we're now waiting for user data. */
+ if (!in || !inlen) break;
+
+ /* FIXME: should we be using local-line-editing code instead? */
+ while (!finished_prompt && inlen) {
+ char c = *in++;
+ inlen--;
+ switch (c) {
+ case 10:
+ case 13:
+ term_data(term, 0, "\r\n", 2);
+ 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;
+ case 8:
+ case 127:
+ if (s->pos > 0) {
+ if (pr->echo)
+ term_data(term, 0, "\b \b", 3);
+ s->pos--;
+ }
+ break;
+ case 21:
+ case 27:
+ while (s->pos > 0) {
+ if (pr->echo)
+ term_data(term, 0, "\b \b", 3);
+ s->pos--;
+ }
+ break;
+ case 3:
+ case 4:
+ /* Immediate abort. */
+ term_data(term, 0, "\r\n", 2);
+ sfree(s);
+ p->data = NULL;
+ return 0; /* user abort */
+ default:
+ /*
+ * 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) {
+ pr->result[s->pos++] = c;
+ if (pr->echo)
+ term_data(term, 0, &c, 1);
+ }
+ break;
+ }
+ }
+
+ }
+
+ if (s->curr_prompt < p->n_prompts) {
+ return -1; /* more data required */
+ } else {
+ sfree(s);
+ p->data = NULL;
+ return +1; /* all done */
+ }
+}
--- /dev/null
+/*
+ * Internals of the Terminal structure, for those other modules
+ * which need to look inside it. It would be nice if this could be
+ * folded back into terminal.c in future, with an abstraction layer
+ * to handle everything that other modules need to know about it;
+ * but for the moment, this will do.
+ */
+
+#ifndef PUTTY_TERMINAL_H
+#define PUTTY_TERMINAL_H
+
+#include "tree234.h"
+
+struct beeptime {
+ struct beeptime *next;
+ unsigned long ticks;
+};
+
+typedef struct {
+ int y, x;
+} pos;
+
+#ifdef OPTIMISE_SCROLL
+struct scrollregion {
+ struct scrollregion *next;
+ int topline; /* Top line of scroll region. */
+ int botline; /* Bottom line of scroll region. */
+ int lines; /* Number of lines to scroll by - +ve is forwards. */
+};
+#endif /* OPTIMISE_SCROLL */
+
+typedef struct termchar termchar;
+typedef struct termline termline;
+
+struct termchar {
+ /*
+ * Any code in terminal.c which definitely needs to be changed
+ * when extra fields are added here is labelled with a comment
+ * saying FULL-TERMCHAR.
+ */
+ unsigned long chr;
+ unsigned long attr;
+
+ /*
+ * The cc_next field is used to link multiple termchars
+ * together into a list, so as to fit more than one character
+ * into a character cell (Unicode combining characters).
+ *
+ * cc_next is a relative offset into the current array of
+ * termchars. I.e. to advance to the next character in a list,
+ * one does `tc += tc->next'.
+ *
+ * Zero means end of list.
+ */
+ int cc_next;
+};
+
+struct termline {
+ unsigned short lattr;
+ int cols; /* number of real columns on the line */
+ int size; /* number of allocated termchars
+ * (cc-lists may make this > cols) */
+ int temporary; /* TRUE if decompressed from scrollback */
+ int cc_free; /* offset to first cc in free list */
+ struct termchar *chars;
+};
+
+struct bidi_cache_entry {
+ int width;
+ struct termchar *chars;
+ int *forward, *backward; /* the permutations of line positions */
+};
+
+struct terminal_tag {
+
+ int compatibility_level;
+
+ tree234 *scrollback; /* lines scrolled off top of screen */
+ tree234 *screen; /* lines on primary screen */
+ tree234 *alt_screen; /* lines on alternate screen */
+ int disptop; /* distance scrolled back (0 or -ve) */
+ int tempsblines; /* number of lines of .scrollback that
+ can be retrieved onto the terminal
+ ("temporary scrollback") */
+
+ termline **disptext; /* buffer of text on real screen */
+ int dispcursx, dispcursy; /* location of cursor on real screen */
+ int curstype; /* type of cursor on real screen */
+
+#define VBELL_TIMEOUT (TICKSPERSEC/10) /* visual bell lasts 1/10 sec */
+
+ struct beeptime *beephead, *beeptail;
+ int nbeeps;
+ int beep_overloaded;
+ long lastbeep;
+
+#define TTYPE termchar
+#define TSIZE (sizeof(TTYPE))
+
+#ifdef OPTIMISE_SCROLL
+ struct scrollregion *scrollhead, *scrolltail;
+#endif /* OPTIMISE_SCROLL */
+
+ int default_attr, curr_attr, save_attr;
+ termchar basic_erase_char, erase_char;
+
+ bufchain inbuf; /* terminal input buffer */
+ pos curs; /* cursor */
+ pos savecurs; /* saved cursor position */
+ int marg_t, marg_b; /* scroll margins */
+ int dec_om; /* DEC origin mode flag */
+ int wrap, wrapnext; /* wrap flags */
+ int insert; /* insert-mode flag */
+ int cset; /* 0 or 1: which char set */
+ int save_cset, save_csattr; /* saved with cursor position */
+ int save_utf, save_wnext; /* saved with cursor position */
+ int rvideo; /* global reverse video flag */
+ unsigned long rvbell_startpoint; /* for ESC[?5hESC[?5l vbell */
+ int cursor_on; /* cursor enabled flag */
+ int reset_132; /* Flag ESC c resets to 80 cols */
+ int use_bce; /* Use Background coloured erase */
+ int cblinker; /* When blinking is the cursor on ? */
+ int tblinker; /* When the blinking text is on */
+ int blink_is_real; /* Actually blink blinking text */
+ int term_echoing; /* Does terminal want local echo? */
+ int term_editing; /* Does terminal want local edit? */
+ int sco_acs, save_sco_acs; /* CSI 10,11,12m -> OEM charset */
+ int vt52_bold; /* Force bold on non-bold colours */
+ int utf; /* Are we in toggleable UTF-8 mode? */
+ int utf_state; /* Is there a pending UTF-8 character */
+ int utf_char; /* and what is it so far. */
+ int utf_size; /* The size of the UTF character. */
+ int printing, only_printing; /* Are we doing ANSI printing? */
+ int print_state; /* state of print-end-sequence scan */
+ bufchain printer_buf; /* buffered data for printer */
+ printer_job *print_job;
+
+ /* ESC 7 saved state for the alternate screen */
+ pos alt_savecurs;
+ int alt_save_attr;
+ int alt_save_cset, alt_save_csattr;
+ int alt_save_utf, alt_save_wnext;
+ int alt_save_sco_acs;
+
+ int rows, cols, savelines;
+ int has_focus;
+ int in_vbell;
+ long vbell_end;
+ int app_cursor_keys, app_keypad_keys, vt52_mode;
+ int repeat_off, cr_lf_return;
+ int seen_disp_event;
+ int big_cursor;
+
+ int xterm_mouse; /* send mouse messages to host */
+ int mouse_is_down; /* used while tracking mouse buttons */
+
+ int cset_attr[2];
+
+/*
+ * Saved settings on the alternate screen.
+ */
+ int alt_x, alt_y, alt_om, alt_wrap, alt_wnext, alt_ins;
+ int alt_cset, alt_sco_acs, alt_utf;
+ int alt_t, alt_b;
+ int alt_which;
+ int alt_sblines; /* # of lines on alternate screen that should be used for scrollback. */
+
+#define ARGS_MAX 32 /* max # of esc sequence arguments */
+#define ARG_DEFAULT 0 /* if an arg isn't specified */
+#define def(a,d) ( (a) == ARG_DEFAULT ? (d) : (a) )
+ int esc_args[ARGS_MAX];
+ int esc_nargs;
+ int esc_query;
+#define ANSI(x,y) ((x)+((y)<<8))
+#define ANSI_QUE(x) ANSI(x,TRUE)
+
+#define OSC_STR_MAX 2048
+ int osc_strlen;
+ char osc_string[OSC_STR_MAX + 1];
+ int osc_w;
+
+ char id_string[1024];
+
+ unsigned char *tabs;
+
+ enum {
+ TOPLEVEL,
+ SEEN_ESC,
+ SEEN_CSI,
+ SEEN_OSC,
+ SEEN_OSC_W,
+
+ DO_CTRLS,
+
+ SEEN_OSC_P,
+ OSC_STRING, OSC_MAYBE_ST,
+ VT52_ESC,
+ VT52_Y1,
+ VT52_Y2,
+ VT52_FG,
+ VT52_BG
+ } termstate;
+
+ enum {
+ NO_SELECTION, ABOUT_TO, DRAGGING, SELECTED
+ } selstate;
+ enum {
+ LEXICOGRAPHIC, RECTANGULAR
+ } seltype;
+ enum {
+ SM_CHAR, SM_WORD, SM_LINE
+ } selmode;
+ pos selstart, selend, selanchor;
+
+ short wordness[256];
+
+ /* 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;
+
+ void (*resize_fn)(void *, int, int);
+ void *resize_ctx;
+
+ void *ldisc;
+
+ void *frontend;
+
+ 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.
+ */
+ Config cfg;
+
+ /*
+ * 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
+ * called recursively like this, it will simply add data to the
+ * end of the buffer term_out is in the process of working
+ * through.
+ */
+ int in_term_out;
+
+ /*
+ * We schedule a window update shortly after receiving terminal
+ * data. This tracks whether one is currently pending.
+ */
+ int window_update_pending;
+ long next_update;
+
+ /*
+ * Track pending blinks and tblinks.
+ */
+ int tblink_pending, cblink_pending;
+ long next_tblink, next_cblink;
+
+ /*
+ * These are buffers used by the bidi and Arabic shaping code.
+ */
+ termchar *ltemp;
+ int ltemp_size;
+ bidi_char *wcFrom, *wcTo;
+ int wcFromTo_size;
+ struct bidi_cache_entry *pre_bidi_cache, *post_bidi_cache;
+ int bidi_cache_size;
+};
+
+#define in_utf(term) ((term)->utf || (term)->ucsdata->line_codepage==CP_UTF8)
+
+#endif
--- /dev/null
+/* $Id$ */
+/*
+ * Copyright (c) 1999 Simon Tatham
+ * Copyright (c) 1999 Ben Harris
+ * All rights reserved.
+ *
+ * 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, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
+ * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+ * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+/* PuTTY test backends */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "putty.h"
+
+static const char *null_init(void *, void **, Config *, char *, int, char **,
+ int, int);
+static const char *loop_init(void *, void **, Config *, char *, int, char **,
+ int, int);
+static void null_free(void *);
+static void loop_free(void *);
+static void null_reconfig(void *, Config *);
+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);
+static const struct telnet_special *null_get_specials(void *handle);
+static int null_connected(void *);
+static int null_exitcode(void *);
+static int null_sendok(void *);
+static int null_ldisc(void *, int);
+static void null_provide_ldisc(void *, void *);
+static void null_provide_logctx(void *, void *);
+static void null_unthrottle(void *, int);
+static int null_cfg_info(void *);
+
+Backend null_backend = {
+ null_init, null_free, null_reconfig, null_send, null_sendbuffer, null_size,
+ null_special, null_get_specials, null_connected, null_exitcode, null_sendok,
+ null_ldisc, null_provide_ldisc, null_provide_logctx, null_unthrottle,
+ null_cfg_info, "null", -1, 0
+};
+
+Backend loop_backend = {
+ loop_init, loop_free, null_reconfig, loop_send, null_sendbuffer, null_size,
+ null_special, null_get_specials, null_connected, null_exitcode, null_sendok,
+ null_ldisc, null_provide_ldisc, null_provide_logctx, null_unthrottle,
+ null_cfg_info, "loop", -1, 0
+};
+
+struct loop_state {
+ Terminal *term;
+};
+
+static const char *null_init(void *frontend_handle, void **backend_handle,
+ Config *cfg, 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,
+ char **realhost, int nodelay, int keepalive) {
+ struct loop_state *st = snew(struct loop_state);
+
+ st->term = frontend_handle;
+ *backend_handle = st;
+ return NULL;
+}
+
+static void null_free(void *handle)
+{
+
+}
+
+static void loop_free(void *handle)
+{
+
+ sfree(handle);
+}
+
+static void null_reconfig(void *handle, Config *cfg) {
+
+}
+
+static int null_send(void *handle, char *buf, int len) {
+
+ return 0;
+}
+
+static int loop_send(void *handle, char *buf, int len) {
+ struct loop_state *st = handle;
+
+ return from_backend(st->term, 0, buf, len);
+}
+
+static int null_sendbuffer(void *handle) {
+
+ return 0;
+}
+
+static void null_size(void *handle, int width, int height) {
+
+}
+
+static void null_special(void *handle, Telnet_Special code) {
+
+}
+
+static const struct telnet_special *null_get_specials (void *handle) {
+
+ return NULL;
+}
+
+static int null_connected(void *handle) {
+
+ return 0;
+}
+
+static int null_exitcode(void *handle) {
+
+ return 0;
+}
+
+static int null_sendok(void *handle) {
+
+ return 1;
+}
+
+static void null_unthrottle(void *handle, int backlog) {
+
+}
+
+static int null_ldisc(void *handle, int option) {
+
+ return 0;
+}
+
+static void null_provide_ldisc (void *handle, void *ldisc) {
+
+}
+
+static void null_provide_logctx(void *handle, void *logctx) {
+
+}
+
+static int null_cfg_info(void *handle)
+{
+ return 0;
+}
+
+
+/*
+ * Emacs magic:
+ * Local Variables:
+ * c-file-style: "simon"
+ * End:
+ */
--- /dev/null
+# Generate test cases for a bignum implementation.
+
+import sys
+
+# integer square roots
+def sqrt(n):
+ d = long(n)
+ a = 0L
+ # b must start off as a power of 4 at least as large as n
+ ndigits = len(hex(long(n)))
+ b = 1L << (ndigits*4)
+ while 1:
+ a = a >> 1
+ di = 2*a + b
+ if di <= d:
+ d = d - di
+ a = a + b
+ b = b >> 2
+ if b == 0: break
+ return a
+
+# continued fraction convergents of a rational
+def confrac(n, d):
+ coeffs = [(1,0),(0,1)]
+ while d != 0:
+ i = n / d
+ n, d = d, n % d
+ coeffs.append((coeffs[-2][0]-i*coeffs[-1][0],
+ coeffs[-2][1]-i*coeffs[-1][1]))
+ return coeffs
+
+def findprod(target, dir = +1, ratio=(1,1)):
+ # Return two numbers whose product is as close as we can get to
+ # 'target', with any deviation having the sign of 'dir', and in
+ # the same approximate ratio as 'ratio'.
+
+ r = sqrt(target * ratio[0] * ratio[1])
+ a = r / ratio[1]
+ b = r / ratio[0]
+ if a*b * dir < target * dir:
+ a = a + 1
+ b = b + 1
+ assert a*b * dir >= target * dir
+
+ best = (a,b,a*b)
+
+ while 1:
+ improved = 0
+ a, b = best[:2]
+
+ coeffs = confrac(a, b)
+ for c in coeffs:
+ # a*c[0]+b*c[1] is as close as we can get it to zero. So
+ # if we replace a and b with a+c[1] and b+c[0], then that
+ # will be added to our product, along with c[0]*c[1].
+ da, db = c[1], c[0]
+
+ # Flip signs as appropriate.
+ if (a+da) * (b+db) * dir < target * dir:
+ da, db = -da, -db
+
+ # Multiply up. We want to get as close as we can to a
+ # solution of the quadratic equation in n
+ #
+ # (a + n da) (b + n db) = target
+ # => n^2 da db + n (b da + a db) + (a b - target) = 0
+ A,B,C = da*db, b*da+a*db, a*b-target
+ discrim = B^2-4*A*C
+ if discrim > 0 and A != 0:
+ root = sqrt(discrim)
+ vals = []
+ vals.append((-B + root) / (2*A))
+ vals.append((-B - root) / (2*A))
+ if root * root != discrim:
+ root = root + 1
+ vals.append((-B + root) / (2*A))
+ vals.append((-B - root) / (2*A))
+
+ for n in vals:
+ ap = a + da*n
+ bp = b + db*n
+ pp = ap*bp
+ if pp * dir >= target * dir and pp * dir < best[2]*dir:
+ best = (ap, bp, pp)
+ improved = 1
+
+ if not improved:
+ break
+
+ return best
+
+def hexstr(n):
+ s = hex(n)
+ if s[:2] == "0x": s = s[2:]
+ if s[-1:] == "L": s = s[:-1]
+ return s
+
+# Tests of multiplication which exercise the propagation of the last
+# carry to the very top of the number.
+for i in range(1,4200):
+ a, b, p = findprod((1<<i)+1, +1, (i, i*i+1))
+ print "mul", hexstr(a), hexstr(b), hexstr(p)
+ a, b, p = findprod((1<<i)+1, +1, (i, i+1))
+ print "mul", hexstr(a), hexstr(b), hexstr(p)
+
+# Simple tests of modpow.
+for i in range(64, 4097, 63):
+ modulus = sqrt(1<<(2*i-1)) | 1
+ base = sqrt(3*modulus*modulus) % modulus
+ expt = sqrt(modulus*modulus*2/5)
+ print "pow", hexstr(base), hexstr(expt), hexstr(modulus), hexstr(pow(base, expt, modulus))
+ if i <= 1024:
+ # Test even moduli, which can't be done by Montgomery.
+ modulus = modulus - 1
+ print "pow", hexstr(base), hexstr(expt), hexstr(modulus), hexstr(pow(base, expt, modulus))
--- /dev/null
+Test of most colour rendering. Omits the SCO fg and bg sequences,
+since they are destructive.
+Normal text \e[1mand bold\e[m; \e[7mreverse video \e[1mand bold\e[m
+ANSI plus bold: \e[30m0 \e[1mbold\e[m \e[31m1 \e[1mbold\e[m \e[32m2 \e[1mbold\e[m \e[33m3 \e[1mbold\e[m \e[34m4 \e[1mbold\e[m \e[35m5 \e[1mbold\e[m \e[36m6 \e[1mbold\e[m \e[37m7 \e[1mbold\e[m
+xterm bright: \e[90mfg0\e[m \e[100mbg0\e[m \e[91mfg1\e[m \e[101mbg1\e[m \e[92mfg2\e[m \e[102mbg2\e[m \e[93mfg3\e[m \e[103mbg3\e[m \e[94mfg4\e[m \e[104mbg4\e[m \e[95mfg5\e[m \e[105mbg5\e[m \e[96mfg6\e[m \e[106mbg6\e[m \e[97mfg7\e[m \e[107mbg7\e[m
+xterm 256:
+\e[38;5;0m 0\e[m\e[38;5;1m 1\e[m\e[38;5;2m 2\e[m\e[38;5;3m 3\e[m\e[38;5;4m 4\e[m\e[38;5;5m 5\e[m\e[38;5;6m 6\e[m\e[38;5;7m 7\e[m\e[38;5;8m 8\e[m\e[38;5;9m 9\e[m\e[38;5;10m 10\e[m\e[38;5;11m 11\e[m\e[38;5;12m 12\e[m\e[38;5;13m 13\e[m\e[38;5;14m 14\e[m\e[38;5;15m 15\e[m
+\e[38;5;16m 16\e[m\e[38;5;17m 17\e[m\e[38;5;18m 18\e[m\e[38;5;19m 19\e[m\e[38;5;20m 20\e[m\e[38;5;21m 21\e[m\e[38;5;22m 22\e[m\e[38;5;23m 23\e[m\e[38;5;24m 24\e[m\e[38;5;25m 25\e[m\e[38;5;26m 26\e[m\e[38;5;27m 27\e[m\e[38;5;28m 28\e[m\e[38;5;29m 29\e[m\e[38;5;30m 30\e[m\e[38;5;31m 31\e[m
+\e[38;5;32m 32\e[m\e[38;5;33m 33\e[m\e[38;5;34m 34\e[m\e[38;5;35m 35\e[m\e[38;5;36m 36\e[m\e[38;5;37m 37\e[m\e[38;5;38m 38\e[m\e[38;5;39m 39\e[m\e[38;5;40m 40\e[m\e[38;5;41m 41\e[m\e[38;5;42m 42\e[m\e[38;5;43m 43\e[m\e[38;5;44m 44\e[m\e[38;5;45m 45\e[m\e[38;5;46m 46\e[m\e[38;5;47m 47\e[m
+\e[38;5;48m 48\e[m\e[38;5;49m 49\e[m\e[38;5;50m 50\e[m\e[38;5;51m 51\e[m\e[38;5;52m 52\e[m\e[38;5;53m 53\e[m\e[38;5;54m 54\e[m\e[38;5;55m 55\e[m\e[38;5;56m 56\e[m\e[38;5;57m 57\e[m\e[38;5;58m 58\e[m\e[38;5;59m 59\e[m\e[38;5;60m 60\e[m\e[38;5;61m 61\e[m\e[38;5;62m 62\e[m\e[38;5;63m 63\e[m
+\e[38;5;64m 64\e[m\e[38;5;65m 65\e[m\e[38;5;66m 66\e[m\e[38;5;67m 67\e[m\e[38;5;68m 68\e[m\e[38;5;69m 69\e[m\e[38;5;70m 70\e[m\e[38;5;71m 71\e[m\e[38;5;72m 72\e[m\e[38;5;73m 73\e[m\e[38;5;74m 74\e[m\e[38;5;75m 75\e[m\e[38;5;76m 76\e[m\e[38;5;77m 77\e[m\e[38;5;78m 78\e[m\e[38;5;79m 79\e[m
+\e[38;5;80m 80\e[m\e[38;5;81m 81\e[m\e[38;5;82m 82\e[m\e[38;5;83m 83\e[m\e[38;5;84m 84\e[m\e[38;5;85m 85\e[m\e[38;5;86m 86\e[m\e[38;5;87m 87\e[m\e[38;5;88m 88\e[m\e[38;5;89m 89\e[m\e[38;5;90m 90\e[m\e[38;5;91m 91\e[m\e[38;5;92m 92\e[m\e[38;5;93m 93\e[m\e[38;5;94m 94\e[m\e[38;5;95m 95\e[m
+\e[38;5;96m 96\e[m\e[38;5;97m 97\e[m\e[38;5;98m 98\e[m\e[38;5;99m 99\e[m\e[38;5;100m 100\e[m\e[38;5;101m 101\e[m\e[38;5;102m 102\e[m\e[38;5;103m 103\e[m\e[38;5;104m 104\e[m\e[38;5;105m 105\e[m\e[38;5;106m 106\e[m\e[38;5;107m 107\e[m\e[38;5;108m 108\e[m\e[38;5;109m 109\e[m\e[38;5;110m 110\e[m\e[38;5;111m 111\e[m
+\e[38;5;112m 112\e[m\e[38;5;113m 113\e[m\e[38;5;114m 114\e[m\e[38;5;115m 115\e[m\e[38;5;116m 116\e[m\e[38;5;117m 117\e[m\e[38;5;118m 118\e[m\e[38;5;119m 119\e[m\e[38;5;120m 120\e[m\e[38;5;121m 121\e[m\e[38;5;122m 122\e[m\e[38;5;123m 123\e[m\e[38;5;124m 124\e[m\e[38;5;125m 125\e[m\e[38;5;126m 126\e[m\e[38;5;127m 127\e[m
+\e[38;5;128m 128\e[m\e[38;5;129m 129\e[m\e[38;5;130m 130\e[m\e[38;5;131m 131\e[m\e[38;5;132m 132\e[m\e[38;5;133m 133\e[m\e[38;5;134m 134\e[m\e[38;5;135m 135\e[m\e[38;5;136m 136\e[m\e[38;5;137m 137\e[m\e[38;5;138m 138\e[m\e[38;5;139m 139\e[m\e[38;5;140m 140\e[m\e[38;5;141m 141\e[m\e[38;5;142m 142\e[m\e[38;5;143m 143\e[m
+\e[38;5;144m 144\e[m\e[38;5;145m 145\e[m\e[38;5;146m 146\e[m\e[38;5;147m 147\e[m\e[38;5;148m 148\e[m\e[38;5;149m 149\e[m\e[38;5;150m 150\e[m\e[38;5;151m 151\e[m\e[38;5;152m 152\e[m\e[38;5;153m 153\e[m\e[38;5;154m 154\e[m\e[38;5;155m 155\e[m\e[38;5;156m 156\e[m\e[38;5;157m 157\e[m\e[38;5;158m 158\e[m\e[38;5;159m 159\e[m
+\e[38;5;160m 160\e[m\e[38;5;161m 161\e[m\e[38;5;162m 162\e[m\e[38;5;163m 163\e[m\e[38;5;164m 164\e[m\e[38;5;165m 165\e[m\e[38;5;166m 166\e[m\e[38;5;167m 167\e[m\e[38;5;168m 168\e[m\e[38;5;169m 169\e[m\e[38;5;170m 170\e[m\e[38;5;171m 171\e[m\e[38;5;172m 172\e[m\e[38;5;173m 173\e[m\e[38;5;174m 174\e[m\e[38;5;175m 175\e[m
+\e[38;5;176m 176\e[m\e[38;5;177m 177\e[m\e[38;5;178m 178\e[m\e[38;5;179m 179\e[m\e[38;5;180m 180\e[m\e[38;5;181m 181\e[m\e[38;5;182m 182\e[m\e[38;5;183m 183\e[m\e[38;5;184m 184\e[m\e[38;5;185m 185\e[m\e[38;5;186m 186\e[m\e[38;5;187m 187\e[m\e[38;5;188m 188\e[m\e[38;5;189m 189\e[m\e[38;5;190m 190\e[m\e[38;5;191m 191\e[m
+\e[38;5;192m 192\e[m\e[38;5;193m 193\e[m\e[38;5;194m 194\e[m\e[38;5;195m 195\e[m\e[38;5;196m 196\e[m\e[38;5;197m 197\e[m\e[38;5;198m 198\e[m\e[38;5;199m 199\e[m\e[38;5;200m 200\e[m\e[38;5;201m 201\e[m\e[38;5;202m 202\e[m\e[38;5;203m 203\e[m\e[38;5;204m 204\e[m\e[38;5;205m 205\e[m\e[38;5;206m 206\e[m\e[38;5;207m 207\e[m
+\e[38;5;208m 208\e[m\e[38;5;209m 209\e[m\e[38;5;210m 210\e[m\e[38;5;211m 211\e[m\e[38;5;212m 212\e[m\e[38;5;213m 213\e[m\e[38;5;214m 214\e[m\e[38;5;215m 215\e[m\e[38;5;216m 216\e[m\e[38;5;217m 217\e[m\e[38;5;218m 218\e[m\e[38;5;219m 219\e[m\e[38;5;220m 220\e[m\e[38;5;221m 221\e[m\e[38;5;222m 222\e[m\e[38;5;223m 223\e[m
+\e[38;5;224m 224\e[m\e[38;5;225m 225\e[m\e[38;5;226m 226\e[m\e[38;5;227m 227\e[m\e[38;5;228m 228\e[m\e[38;5;229m 229\e[m\e[38;5;230m 230\e[m\e[38;5;231m 231\e[m\e[38;5;232m 232\e[m\e[38;5;233m 233\e[m\e[38;5;234m 234\e[m\e[38;5;235m 235\e[m\e[38;5;236m 236\e[m\e[38;5;237m 237\e[m\e[38;5;238m 238\e[m\e[38;5;239m 239\e[m
+\e[38;5;240m 240\e[m\e[38;5;241m 241\e[m\e[38;5;242m 242\e[m\e[38;5;243m 243\e[m\e[38;5;244m 244\e[m\e[38;5;245m 245\e[m\e[38;5;246m 246\e[m\e[38;5;247m 247\e[m\e[38;5;248m 248\e[m\e[38;5;249m 249\e[m\e[38;5;250m 250\e[m\e[38;5;251m 251\e[m\e[38;5;252m 252\e[m\e[38;5;253m 253\e[m\e[38;5;254m 254\e[m\e[38;5;255m 255\e[m
--- /dev/null
+Test of line attributes:
+
+\e#3Double-height top
+\e#4Double-height bottom
+\e#5Normal text (#5)
+\e#6Double-width only
--- /dev/null
+Test of (destructive) SCO colour rendering.
+SCO fg: \e[=0F0\e[=7F \e[=1F1\e[=7F \e[=2F2\e[=7F \e[=3F3\e[=7F \e[=4F4\e[=7F \e[=5F5\e[=7F \e[=6F6\e[=7F \e[=7F7\e[=7F \e[=8F8\e[=7F \e[=9F9\e[=7F \e[=10F10\e[=7F \e[=11F11\e[=7F \e[=12F12\e[=7F \e[=13F13\e[=7F \e[=14F14\e[=7F \e[=15F15\e[=7F
+SCO bg: \e[=0G0\e[=0G \e[=1G1\e[=0G \e[=2G2\e[=0G \e[=3G3\e[=0G \e[=4G4\e[=0G \e[=5G5\e[=0G \e[=6G6\e[=0G \e[=7G7\e[=0G \e[=8G8\e[=0G \e[=9G9\e[=0G \e[=10G10\e[=0G \e[=11G11\e[=0G \e[=12G12\e[=0G \e[=13G13\e[=0G \e[=14G14\e[=0G \e[=15G15\e[=0G
--- /dev/null
+Test of UTF-8 output in a terminal emulator
+‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
+
+Some basic Unicode:
+ ∮ E⋅da = Q, n → ∞, ∑ f(i) = ∏ g(i), ∀x∈ℝ: ⌈x⌉ = −⌊−x⌋, α ∧ ¬β = ¬(¬α ∨ β),
+ ℕ ⊆ ℕ₀ ⊂ ℤ ⊂ ℚ ⊂ ℝ ⊂ ℂ, ⊥ < a ≠ b ≡ c ≤ d ≪ ⊤ ⇒ (A ⇔ B),
+
+Combining characters:
+ STARGΛ̊TE SG-1, a = v̇ = r̈, a⃑ ⊥ b⃑
+ [----------------------------|------------------------]
+ ๏ แผ่นดินฮั่นเสื่อมโทรมแสนสังเวช พระปกเกศกองบู๊กู้ขึ้นใหม่
+ สิบสองกษัตริย์ก่อนหน้าแลถัดไป สององค์ไซร้โง่เขลาเบาปัญญา
+
+Wide characters with difficult wrapping:
+ Here we go then: コンニチハ コンニチハ コンニチハ コンニチハ コンニチハ コンニチハ コンニチハ コンニチハ コンニチハ コンニチハ コンニチハ コンニチハ コンニチハ コンニチハ コンニチハ
+
+Arabic and bidirectional text:
+ (من مجمع الزوائد ومنبع الفوائد للهيثمي ، ج 1 ، ص 74-84)
+ عن \e[44mجرير\e[m \e[41mرضي\e[m الله عنه قال قال رسول الله صلى الله عليه
+ وسلم: بني الاسلام على خمس شهادة ان لا اله الا الله واقام
+Mixed LTR and RTL text: \e[44mجرير\e[m \e[41mرضي\e[m back to LTR.
+
+East Asian Ambiguous characters: ¼½¾¼½¾¼½¾¼½¾¼½¾¼½¾¼½¾¼½¾¼½¾¼½¾
--- /dev/null
+VT100 line drawing characters, actually using the VT100 escapes
+\e(B\e)0\ eooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo\ f
+
+\ elqqqqqqqqqqpoopqrssrqqqqqqqqqqwqqqqqqqqqqpoopqrssrqqqqqqqqqqk\ f
+\ ex\ f \ ex\ f \ ex\ f
+\ ex\ f ooh, swirly! \ ex\ f top right corner \ ex\ f
+\ ex\ f \ ex\ f \ ex\ f
+\ etqqqqqqqqqqpoopqrssrqqqqqqqqqqnqqqqqqqqqqpoopqrssrqqqqqqqqqqu\ f
+\ ex\ f \ ex\ f \ ex\ f
+\ ex\ f stuff down here \ ex\ f is quite inane \ ex\ f
+\ ex\ f \ ex\ f \ ex\ f
+\ emqqqqqqqqqqpoopqrssrqqqqqqqqqqvqqqqqqqqqqpoopqrssrqqqqqqqqqqj\ f
--- /dev/null
+/*
+ * Portable implementation of ltime() for any ISO-C platform where
+ * time_t behaves. (In practice, we've found that platforms such as
+ * Windows and Mac have needed their own specialised implementations.)
+ */
+
+#include <time.h>
+#include <assert.h>
+
+struct tm ltime(void)
+{
+ time_t t;
+ time(&t);
+ assert (t != ((time_t)-1));
+ return *localtime(&t);
+}
--- /dev/null
+/*
+ * timing.c
+ *
+ * This module tracks any timers set up by schedule_timer(). It
+ * keeps all the currently active timers in a list; it informs the
+ * front end of when the next timer is due to go off if that
+ * changes; and, very importantly, it tracks the context pointers
+ * passed to schedule_timer(), so that if a context is freed all
+ * the timers associated with it can be immediately annulled.
+ */
+
+#include <assert.h>
+#include <stdio.h>
+
+#include "putty.h"
+#include "tree234.h"
+
+struct timer {
+ timer_fn_t fn;
+ void *ctx;
+ long now;
+};
+
+static tree234 *timers = NULL;
+static tree234 *timer_contexts = NULL;
+static long now = 0L;
+
+static int compare_timers(void *av, void *bv)
+{
+ struct timer *a = (struct timer *)av;
+ struct timer *b = (struct timer *)bv;
+ long at = a->now - now;
+ long bt = b->now - now;
+
+ if (at < bt)
+ return -1;
+ else if (at > bt)
+ return +1;
+
+ /*
+ * Failing that, compare on the other two fields, just so that
+ * we don't get unwanted equality.
+ */
+#ifdef __LCC__
+ /* 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;
+ }
+#else
+ if (a->fn < b->fn)
+ return -1;
+ else if (a->fn > b->fn)
+ return +1;
+#endif
+
+ if (a->ctx < b->ctx)
+ return -1;
+ else if (a->ctx > b->ctx)
+ return +1;
+
+ /*
+ * Failing _that_, the two entries genuinely are equal, and we
+ * never have a need to store them separately in the tree.
+ */
+ return 0;
+}
+
+static int compare_timer_contexts(void *av, void *bv)
+{
+ char *a = (char *)av;
+ char *b = (char *)bv;
+ if (a < b)
+ return -1;
+ else if (a > b)
+ return +1;
+ return 0;
+}
+
+static void init_timers(void)
+{
+ if (!timers) {
+ timers = newtree234(compare_timers);
+ timer_contexts = newtree234(compare_timer_contexts);
+ now = GETTICKCOUNT();
+ }
+}
+
+long schedule_timer(int ticks, timer_fn_t fn, void *ctx)
+{
+ long when;
+ struct timer *t, *first;
+
+ init_timers();
+
+ when = ticks + GETTICKCOUNT();
+
+ /*
+ * 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.
+ */
+ if (when - now <= 0)
+ when = now + 1;
+
+ t = snew(struct timer);
+ t->fn = fn;
+ t->ctx = ctx;
+ t->now = when;
+
+ if (t != add234(timers, t)) {
+ sfree(t); /* identical timer already exists */
+ } else {
+ add234(timer_contexts, t->ctx);/* don't care if this fails */
+ }
+
+ first = (struct timer *)index234(timers, 0);
+ if (first == t) {
+ /*
+ * This timer is the very first on the list, so we must
+ * notify the front end.
+ */
+ timer_change_notify(first->now);
+ }
+
+ return when;
+}
+
+/*
+ * 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)
+{
+ 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;
+
+ while (1) {
+ first = (struct timer *)index234(timers, 0);
+
+ if (!first)
+ return FALSE; /* no timers remaining */
+
+ if (find234(timer_contexts, first->ctx, NULL) == NULL) {
+ /*
+ * 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) {
+ /*
+ * This timer is active and has reached its running
+ * time. Run it.
+ */
+ delpos234(timers, 0);
+ first->fn(first->ctx, first->now);
+ sfree(first);
+ } else {
+ /*
+ * This is the first still-active timer that is in the
+ * future. Return how long it has yet to go.
+ */
+ *next = first->now;
+ return TRUE;
+ }
+ }
+}
+
+/*
+ * Call to expire all timers associated with a given context.
+ */
+void expire_timer_context(void *ctx)
+{
+ init_timers();
+
+ /*
+ * We don't bother to check the return value; if the context
+ * already wasn't in the tree (presumably because no timers
+ * ever actually got scheduled for it) then that's fine and we
+ * simply don't need to do anything.
+ */
+ del234(timer_contexts, ctx);
+}
--- /dev/null
+/*
+ * tree234.c: reasonably generic counted 2-3-4 tree routines.
+ *
+ * This file is copyright 1999-2001 Simon Tatham.
+ *
+ * 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, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL SIMON TATHAM BE LIABLE FOR
+ * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+ * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#include "puttymem.h"
+#include "tree234.h"
+
+#ifdef TEST
+#define LOG(x) (printf x)
+#else
+#define LOG(x)
+#endif
+
+typedef struct node234_Tag node234;
+
+struct tree234_Tag {
+ node234 *root;
+ cmpfn234 cmp;
+};
+
+struct node234_Tag {
+ node234 *parent;
+ node234 *kids[4];
+ int counts[4];
+ void *elems[3];
+};
+
+/*
+ * Create a 2-3-4 tree.
+ */
+tree234 *newtree234(cmpfn234 cmp)
+{
+ tree234 *ret = snew(tree234);
+ LOG(("created tree %p\n", ret));
+ ret->root = NULL;
+ ret->cmp = cmp;
+ return ret;
+}
+
+/*
+ * Free a 2-3-4 tree (not including freeing the elements).
+ */
+static void freenode234(node234 * n)
+{
+ if (!n)
+ return;
+ freenode234(n->kids[0]);
+ freenode234(n->kids[1]);
+ freenode234(n->kids[2]);
+ freenode234(n->kids[3]);
+ sfree(n);
+}
+
+void freetree234(tree234 * t)
+{
+ freenode234(t->root);
+ sfree(t);
+}
+
+/*
+ * Internal function to count a node.
+ */
+static int countnode234(node234 * n)
+{
+ int count = 0;
+ int i;
+ if (!n)
+ return 0;
+ for (i = 0; i < 4; i++)
+ count += n->counts[i];
+ for (i = 0; i < 3; i++)
+ if (n->elems[i])
+ count++;
+ return count;
+}
+
+/*
+ * Count the elements in a tree.
+ */
+int count234(tree234 * t)
+{
+ if (t->root)
+ return countnode234(t->root);
+ else
+ return 0;
+}
+
+/*
+ * Add an element e to a 2-3-4 tree t. Returns e on success, or if
+ * an existing element compares equal, returns that.
+ */
+static void *add234_internal(tree234 * t, void *e, int index)
+{
+ node234 *n, **np, *left, *right;
+ void *orig_e = e;
+ int c, lcount, rcount;
+
+ LOG(("adding node %p to tree %p\n", e, t));
+ if (t->root == NULL) {
+ t->root = snew(node234);
+ t->root->elems[1] = t->root->elems[2] = NULL;
+ t->root->kids[0] = t->root->kids[1] = NULL;
+ t->root->kids[2] = t->root->kids[3] = NULL;
+ t->root->counts[0] = t->root->counts[1] = 0;
+ t->root->counts[2] = t->root->counts[3] = 0;
+ t->root->parent = NULL;
+ t->root->elems[0] = e;
+ LOG((" created root %p\n", t->root));
+ return orig_e;
+ }
+
+ n = NULL; /* placate gcc; will always be set below since t->root != NULL */
+ np = &t->root;
+ while (*np) {
+ int childnum;
+ n = *np;
+ LOG((" node %p: %p/%d [%p] %p/%d [%p] %p/%d [%p] %p/%d\n",
+ n,
+ 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]));
+ if (index >= 0) {
+ if (!n->kids[0]) {
+ /*
+ * Leaf node. We want to insert at kid position
+ * equal to the index:
+ *
+ * 0 A 1 B 2 C 3
+ */
+ childnum = index;
+ } else {
+ /*
+ * Internal node. We always descend through it (add
+ * always starts at the bottom, never in the
+ * middle).
+ */
+ do { /* this is a do ... while (0) to allow `break' */
+ if (index <= n->counts[0]) {
+ childnum = 0;
+ break;
+ }
+ index -= n->counts[0] + 1;
+ if (index <= n->counts[1]) {
+ childnum = 1;
+ break;
+ }
+ index -= n->counts[1] + 1;
+ if (index <= n->counts[2]) {
+ childnum = 2;
+ break;
+ }
+ index -= n->counts[2] + 1;
+ if (index <= n->counts[3]) {
+ childnum = 3;
+ break;
+ }
+ return NULL; /* error: index out of range */
+ } while (0);
+ }
+ } else {
+ if ((c = t->cmp(e, n->elems[0])) < 0)
+ childnum = 0;
+ else if (c == 0)
+ return n->elems[0]; /* already exists */
+ else if (n->elems[1] == NULL
+ || (c = t->cmp(e, n->elems[1])) < 0) childnum = 1;
+ else if (c == 0)
+ return n->elems[1]; /* already exists */
+ else if (n->elems[2] == NULL
+ || (c = t->cmp(e, n->elems[2])) < 0) childnum = 2;
+ else if (c == 0)
+ return n->elems[2]; /* already exists */
+ else
+ childnum = 3;
+ }
+ np = &n->kids[childnum];
+ LOG((" moving to child %d (%p)\n", childnum, *np));
+ }
+
+ /*
+ * We need to insert the new element in n at position np.
+ */
+ left = NULL;
+ lcount = 0;
+ right = NULL;
+ rcount = 0;
+ while (n) {
+ LOG((" at %p: %p/%d [%p] %p/%d [%p] %p/%d [%p] %p/%d\n",
+ n,
+ 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));
+ if (n->elems[1] == NULL) {
+ /*
+ * Insert in a 2-node; simple.
+ */
+ if (np == &n->kids[0]) {
+ LOG((" inserting on left of 2-node\n"));
+ n->kids[2] = n->kids[1];
+ n->counts[2] = n->counts[1];
+ n->elems[1] = n->elems[0];
+ n->kids[1] = right;
+ n->counts[1] = rcount;
+ n->elems[0] = e;
+ n->kids[0] = left;
+ n->counts[0] = lcount;
+ } else { /* np == &n->kids[1] */
+ LOG((" inserting on right of 2-node\n"));
+ n->kids[2] = right;
+ n->counts[2] = rcount;
+ n->elems[1] = e;
+ n->kids[1] = left;
+ n->counts[1] = lcount;
+ }
+ if (n->kids[0])
+ n->kids[0]->parent = n;
+ if (n->kids[1])
+ n->kids[1]->parent = n;
+ if (n->kids[2])
+ n->kids[2]->parent = n;
+ LOG((" done\n"));
+ break;
+ } else if (n->elems[2] == NULL) {
+ /*
+ * Insert in a 3-node; simple.
+ */
+ if (np == &n->kids[0]) {
+ LOG((" inserting on left of 3-node\n"));
+ n->kids[3] = n->kids[2];
+ n->counts[3] = n->counts[2];
+ n->elems[2] = n->elems[1];
+ n->kids[2] = n->kids[1];
+ n->counts[2] = n->counts[1];
+ n->elems[1] = n->elems[0];
+ n->kids[1] = right;
+ n->counts[1] = rcount;
+ n->elems[0] = e;
+ n->kids[0] = left;
+ n->counts[0] = lcount;
+ } else if (np == &n->kids[1]) {
+ LOG((" inserting in middle of 3-node\n"));
+ n->kids[3] = n->kids[2];
+ n->counts[3] = n->counts[2];
+ n->elems[2] = n->elems[1];
+ n->kids[2] = right;
+ n->counts[2] = rcount;
+ n->elems[1] = e;
+ n->kids[1] = left;
+ n->counts[1] = lcount;
+ } else { /* np == &n->kids[2] */
+ LOG((" inserting on right of 3-node\n"));
+ n->kids[3] = right;
+ n->counts[3] = rcount;
+ n->elems[2] = e;
+ n->kids[2] = left;
+ n->counts[2] = lcount;
+ }
+ if (n->kids[0])
+ n->kids[0]->parent = n;
+ if (n->kids[1])
+ n->kids[1]->parent = n;
+ if (n->kids[2])
+ n->kids[2]->parent = n;
+ if (n->kids[3])
+ n->kids[3]->parent = n;
+ LOG((" done\n"));
+ break;
+ } else {
+ node234 *m = snew(node234);
+ m->parent = n->parent;
+ LOG((" splitting a 4-node; created new node %p\n", m));
+ /*
+ * Insert in a 4-node; split into a 2-node and a
+ * 3-node, and move focus up a level.
+ *
+ * I don't think it matters which way round we put the
+ * 2 and the 3. For simplicity, we'll put the 3 first
+ * always.
+ */
+ if (np == &n->kids[0]) {
+ m->kids[0] = left;
+ m->counts[0] = lcount;
+ m->elems[0] = e;
+ m->kids[1] = right;
+ m->counts[1] = rcount;
+ m->elems[1] = n->elems[0];
+ m->kids[2] = n->kids[1];
+ m->counts[2] = n->counts[1];
+ e = n->elems[1];
+ n->kids[0] = n->kids[2];
+ n->counts[0] = n->counts[2];
+ n->elems[0] = n->elems[2];
+ n->kids[1] = n->kids[3];
+ n->counts[1] = n->counts[3];
+ } else if (np == &n->kids[1]) {
+ m->kids[0] = n->kids[0];
+ m->counts[0] = n->counts[0];
+ m->elems[0] = n->elems[0];
+ m->kids[1] = left;
+ m->counts[1] = lcount;
+ m->elems[1] = e;
+ m->kids[2] = right;
+ m->counts[2] = rcount;
+ e = n->elems[1];
+ n->kids[0] = n->kids[2];
+ n->counts[0] = n->counts[2];
+ n->elems[0] = n->elems[2];
+ n->kids[1] = n->kids[3];
+ n->counts[1] = n->counts[3];
+ } else if (np == &n->kids[2]) {
+ m->kids[0] = n->kids[0];
+ m->counts[0] = n->counts[0];
+ m->elems[0] = n->elems[0];
+ m->kids[1] = n->kids[1];
+ m->counts[1] = n->counts[1];
+ m->elems[1] = n->elems[1];
+ m->kids[2] = left;
+ m->counts[2] = lcount;
+ /* e = e; */
+ n->kids[0] = right;
+ n->counts[0] = rcount;
+ n->elems[0] = n->elems[2];
+ n->kids[1] = n->kids[3];
+ n->counts[1] = n->counts[3];
+ } else { /* np == &n->kids[3] */
+ m->kids[0] = n->kids[0];
+ m->counts[0] = n->counts[0];
+ m->elems[0] = n->elems[0];
+ m->kids[1] = n->kids[1];
+ m->counts[1] = n->counts[1];
+ m->elems[1] = n->elems[1];
+ m->kids[2] = n->kids[2];
+ m->counts[2] = n->counts[2];
+ n->kids[0] = left;
+ n->counts[0] = lcount;
+ n->elems[0] = e;
+ n->kids[1] = right;
+ n->counts[1] = rcount;
+ e = n->elems[2];
+ }
+ m->kids[3] = n->kids[3] = n->kids[2] = NULL;
+ m->counts[3] = n->counts[3] = n->counts[2] = 0;
+ m->elems[2] = n->elems[2] = n->elems[1] = NULL;
+ if (m->kids[0])
+ m->kids[0]->parent = m;
+ if (m->kids[1])
+ m->kids[1]->parent = m;
+ if (m->kids[2])
+ m->kids[2]->parent = m;
+ if (n->kids[0])
+ n->kids[0]->parent = n;
+ if (n->kids[1])
+ n->kids[1]->parent = n;
+ LOG((" left (%p): %p/%d [%p] %p/%d [%p] %p/%d\n", m,
+ m->kids[0], m->counts[0], m->elems[0],
+ m->kids[1], m->counts[1], m->elems[1],
+ m->kids[2], m->counts[2]));
+ LOG((" right (%p): %p/%d [%p] %p/%d\n", n,
+ n->kids[0], n->counts[0], n->elems[0],
+ n->kids[1], n->counts[1]));
+ left = m;
+ lcount = countnode234(left);
+ right = n;
+ rcount = countnode234(right);
+ }
+ if (n->parent)
+ np = (n->parent->kids[0] == n ? &n->parent->kids[0] :
+ n->parent->kids[1] == n ? &n->parent->kids[1] :
+ n->parent->kids[2] == n ? &n->parent->kids[2] :
+ &n->parent->kids[3]);
+ n = n->parent;
+ }
+
+ /*
+ * If we've come out of here by `break', n will still be
+ * non-NULL and all we need to do is go back up the tree
+ * updating counts. If we've come here because n is NULL, we
+ * need to create a new root for the tree because the old one
+ * has just split into two. */
+ if (n) {
+ while (n->parent) {
+ int count = countnode234(n);
+ int childnum;
+ childnum = (n->parent->kids[0] == n ? 0 :
+ n->parent->kids[1] == n ? 1 :
+ n->parent->kids[2] == n ? 2 : 3);
+ n->parent->counts[childnum] = count;
+ n = n->parent;
+ }
+ } else {
+ LOG((" root is overloaded, split into two\n"));
+ t->root = snew(node234);
+ t->root->kids[0] = left;
+ t->root->counts[0] = lcount;
+ t->root->elems[0] = e;
+ t->root->kids[1] = right;
+ t->root->counts[1] = rcount;
+ t->root->elems[1] = NULL;
+ t->root->kids[2] = NULL;
+ t->root->counts[2] = 0;
+ t->root->elems[2] = NULL;
+ t->root->kids[3] = NULL;
+ t->root->counts[3] = 0;
+ t->root->parent = NULL;
+ if (t->root->kids[0])
+ t->root->kids[0]->parent = t->root;
+ if (t->root->kids[1])
+ t->root->kids[1]->parent = t->root;
+ LOG((" new root is %p/%d [%p] %p/%d\n",
+ t->root->kids[0], t->root->counts[0],
+ t->root->elems[0], t->root->kids[1], t->root->counts[1]));
+ }
+
+ return orig_e;
+}
+
+void *add234(tree234 * t, void *e)
+{
+ if (!t->cmp) /* tree is unsorted */
+ return NULL;
+
+ return add234_internal(t, e, -1);
+}
+void *addpos234(tree234 * t, void *e, int index)
+{
+ if (index < 0 || /* index out of range */
+ t->cmp) /* tree is sorted */
+ return NULL; /* return failure */
+
+ return add234_internal(t, e, index); /* this checks the upper bound */
+}
+
+/*
+ * Look up the element at a given numeric index in a 2-3-4 tree.
+ * Returns NULL if the index is out of range.
+ */
+void *index234(tree234 * t, int index)
+{
+ node234 *n;
+
+ if (!t->root)
+ return NULL; /* tree is empty */
+
+ if (index < 0 || index >= countnode234(t->root))
+ return NULL; /* out of range */
+
+ n = t->root;
+
+ while (n) {
+ if (index < n->counts[0])
+ n = n->kids[0];
+ else if (index -= n->counts[0] + 1, index < 0)
+ return n->elems[0];
+ else if (index < n->counts[1])
+ n = n->kids[1];
+ else if (index -= n->counts[1] + 1, index < 0)
+ return n->elems[1];
+ else if (index < n->counts[2])
+ n = n->kids[2];
+ else if (index -= n->counts[2] + 1, index < 0)
+ return n->elems[2];
+ else
+ n = n->kids[3];
+ }
+
+ /* We shouldn't ever get here. I wonder how we did. */
+ return NULL;
+}
+
+/*
+ * Find an element e in a sorted 2-3-4 tree t. Returns NULL if not
+ * found. e is always passed as the first argument to cmp, so cmp
+ * can be an asymmetric function if desired. cmp can also be passed
+ * as NULL, in which case the compare function from the tree proper
+ * will be used.
+ */
+void *findrelpos234(tree234 * t, void *e, cmpfn234 cmp,
+ int relation, int *index)
+{
+ node234 *n;
+ void *ret;
+ int c;
+ int idx, ecount, kcount, cmpret;
+
+ if (t->root == NULL)
+ return NULL;
+
+ if (cmp == NULL)
+ cmp = t->cmp;
+
+ n = t->root;
+ /*
+ * Attempt to find the element itself.
+ */
+ idx = 0;
+ ecount = -1;
+ /*
+ * Prepare a fake `cmp' result if e is NULL.
+ */
+ cmpret = 0;
+ if (e == NULL) {
+ assert(relation == REL234_LT || relation == REL234_GT);
+ if (relation == REL234_LT)
+ cmpret = +1; /* e is a max: always greater */
+ else if (relation == REL234_GT)
+ cmpret = -1; /* e is a min: always smaller */
+ }
+ while (1) {
+ for (kcount = 0; kcount < 4; kcount++) {
+ if (kcount >= 3 || n->elems[kcount] == NULL ||
+ (c = cmpret ? cmpret : cmp(e, n->elems[kcount])) < 0) {
+ break;
+ }
+ if (n->kids[kcount])
+ idx += n->counts[kcount];
+ if (c == 0) {
+ ecount = kcount;
+ break;
+ }
+ idx++;
+ }
+ if (ecount >= 0)
+ break;
+ if (n->kids[kcount])
+ n = n->kids[kcount];
+ else
+ break;
+ }
+
+ if (ecount >= 0) {
+ /*
+ * We have found the element we're looking for. It's
+ * n->elems[ecount], at tree index idx. If our search
+ * relation is EQ, LE or GE we can now go home.
+ */
+ if (relation != REL234_LT && relation != REL234_GT) {
+ if (index)
+ *index = idx;
+ return n->elems[ecount];
+ }
+
+ /*
+ * Otherwise, we'll do an indexed lookup for the previous
+ * or next element. (It would be perfectly possible to
+ * implement these search types in a non-counted tree by
+ * going back up from where we are, but far more fiddly.)
+ */
+ if (relation == REL234_LT)
+ idx--;
+ else
+ idx++;
+ } else {
+ /*
+ * We've found our way to the bottom of the tree and we
+ * know where we would insert this node if we wanted to:
+ * we'd put it in in place of the (empty) subtree
+ * n->kids[kcount], and it would have index idx
+ *
+ * But the actual element isn't there. So if our search
+ * relation is EQ, we're doomed.
+ */
+ if (relation == REL234_EQ)
+ return NULL;
+
+ /*
+ * Otherwise, we must do an index lookup for index idx-1
+ * (if we're going left - LE or LT) or index idx (if we're
+ * going right - GE or GT).
+ */
+ if (relation == REL234_LT || relation == REL234_LE) {
+ idx--;
+ }
+ }
+
+ /*
+ * We know the index of the element we want; just call index234
+ * to do the rest. This will return NULL if the index is out of
+ * bounds, which is exactly what we want.
+ */
+ ret = index234(t, idx);
+ if (ret && index)
+ *index = idx;
+ return ret;
+}
+void *find234(tree234 * t, void *e, cmpfn234 cmp)
+{
+ return findrelpos234(t, e, cmp, REL234_EQ, NULL);
+}
+void *findrel234(tree234 * t, void *e, cmpfn234 cmp, int relation)
+{
+ return findrelpos234(t, e, cmp, relation, NULL);
+}
+void *findpos234(tree234 * t, void *e, cmpfn234 cmp, int *index)
+{
+ return findrelpos234(t, e, cmp, REL234_EQ, index);
+}
+
+/*
+ * Delete an element e in a 2-3-4 tree. Does not free the element,
+ * merely removes all links to it from the tree nodes.
+ */
+static void *delpos234_internal(tree234 * t, int index)
+{
+ node234 *n;
+ void *retval;
+ int ei = -1;
+
+ retval = 0;
+
+ n = t->root;
+ LOG(("deleting item %d from tree %p\n", index, t));
+ while (1) {
+ while (n) {
+ int ki;
+ node234 *sub;
+
+ LOG(
+ (" node %p: %p/%d [%p] %p/%d [%p] %p/%d [%p] %p/%d index=%d\n",
+ n, 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], index));
+ if (index < n->counts[0]) {
+ ki = 0;
+ } else if (index -= n->counts[0] + 1, index < 0) {
+ ei = 0;
+ break;
+ } else if (index < n->counts[1]) {
+ ki = 1;
+ } else if (index -= n->counts[1] + 1, index < 0) {
+ ei = 1;
+ break;
+ } else if (index < n->counts[2]) {
+ ki = 2;
+ } else if (index -= n->counts[2] + 1, index < 0) {
+ ei = 2;
+ break;
+ } else {
+ ki = 3;
+ }
+ /*
+ * Recurse down to subtree ki. If it has only one element,
+ * we have to do some transformation to start with.
+ */
+ LOG((" moving to subtree %d\n", ki));
+ sub = n->kids[ki];
+ if (!sub->elems[1]) {
+ LOG((" subtree has only one element!\n", ki));
+ if (ki > 0 && n->kids[ki - 1]->elems[1]) {
+ /*
+ * Case 3a, left-handed variant. Child ki has
+ * only one element, but child ki-1 has two or
+ * more. So we need to move a subtree from ki-1
+ * to ki.
+ *
+ * . C . . B .
+ * / \ -> / \
+ * [more] a A b B c d D e [more] a A b c C d D e
+ */
+ node234 *sib = n->kids[ki - 1];
+ int lastelem = (sib->elems[2] ? 2 :
+ sib->elems[1] ? 1 : 0);
+ sub->kids[2] = sub->kids[1];
+ sub->counts[2] = sub->counts[1];
+ sub->elems[1] = sub->elems[0];
+ sub->kids[1] = sub->kids[0];
+ sub->counts[1] = sub->counts[0];
+ sub->elems[0] = n->elems[ki - 1];
+ sub->kids[0] = sib->kids[lastelem + 1];
+ sub->counts[0] = sib->counts[lastelem + 1];
+ if (sub->kids[0])
+ sub->kids[0]->parent = sub;
+ n->elems[ki - 1] = sib->elems[lastelem];
+ sib->kids[lastelem + 1] = NULL;
+ sib->counts[lastelem + 1] = 0;
+ sib->elems[lastelem] = NULL;
+ n->counts[ki] = countnode234(sub);
+ LOG((" case 3a left\n"));
+ LOG(
+ (" index and left subtree count before adjustment: %d, %d\n",
+ index, n->counts[ki - 1]));
+ index += n->counts[ki - 1];
+ n->counts[ki - 1] = countnode234(sib);
+ index -= n->counts[ki - 1];
+ LOG(
+ (" index and left subtree count after adjustment: %d, %d\n",
+ index, n->counts[ki - 1]));
+ } else if (ki < 3 && n->kids[ki + 1]
+ && n->kids[ki + 1]->elems[1]) {
+ /*
+ * Case 3a, right-handed variant. ki has only
+ * one element but ki+1 has two or more. Move a
+ * subtree from ki+1 to ki.
+ *
+ * . B . . C .
+ * / \ -> / \
+ * a A b c C d D e [more] a A b B c d D e [more]
+ */
+ node234 *sib = n->kids[ki + 1];
+ int j;
+ sub->elems[1] = n->elems[ki];
+ sub->kids[2] = sib->kids[0];
+ sub->counts[2] = sib->counts[0];
+ if (sub->kids[2])
+ sub->kids[2]->parent = sub;
+ n->elems[ki] = sib->elems[0];
+ sib->kids[0] = sib->kids[1];
+ sib->counts[0] = sib->counts[1];
+ for (j = 0; j < 2 && sib->elems[j + 1]; j++) {
+ sib->kids[j + 1] = sib->kids[j + 2];
+ sib->counts[j + 1] = sib->counts[j + 2];
+ sib->elems[j] = sib->elems[j + 1];
+ }
+ sib->kids[j + 1] = NULL;
+ sib->counts[j + 1] = 0;
+ sib->elems[j] = NULL;
+ n->counts[ki] = countnode234(sub);
+ n->counts[ki + 1] = countnode234(sib);
+ LOG((" case 3a right\n"));
+ } else {
+ /*
+ * Case 3b. ki has only one element, and has no
+ * neighbour with more than one. So pick a
+ * neighbour and merge it with ki, taking an
+ * element down from n to go in the middle.
+ *
+ * . B . .
+ * / \ -> |
+ * a A b c C d a A b B c C d
+ *
+ * (Since at all points we have avoided
+ * descending to a node with only one element,
+ * we can be sure that n is not reduced to
+ * nothingness by this move, _unless_ it was
+ * the very first node, ie the root of the
+ * tree. In that case we remove the now-empty
+ * root and replace it with its single large
+ * child as shown.)
+ */
+ node234 *sib;
+ int j;
+
+ if (ki > 0) {
+ ki--;
+ index += n->counts[ki] + 1;
+ }
+ sib = n->kids[ki];
+ sub = n->kids[ki + 1];
+
+ sub->kids[3] = sub->kids[1];
+ sub->counts[3] = sub->counts[1];
+ sub->elems[2] = sub->elems[0];
+ sub->kids[2] = sub->kids[0];
+ sub->counts[2] = sub->counts[0];
+ sub->elems[1] = n->elems[ki];
+ sub->kids[1] = sib->kids[1];
+ sub->counts[1] = sib->counts[1];
+ if (sub->kids[1])
+ sub->kids[1]->parent = sub;
+ sub->elems[0] = sib->elems[0];
+ sub->kids[0] = sib->kids[0];
+ sub->counts[0] = sib->counts[0];
+ if (sub->kids[0])
+ sub->kids[0]->parent = sub;
+
+ n->counts[ki + 1] = countnode234(sub);
+
+ sfree(sib);
+
+ /*
+ * That's built the big node in sub. Now we
+ * need to remove the reference to sib in n.
+ */
+ for (j = ki; j < 3 && n->kids[j + 1]; j++) {
+ n->kids[j] = n->kids[j + 1];
+ n->counts[j] = n->counts[j + 1];
+ n->elems[j] = j < 2 ? n->elems[j + 1] : NULL;
+ }
+ n->kids[j] = NULL;
+ n->counts[j] = 0;
+ if (j < 3)
+ n->elems[j] = NULL;
+ LOG((" case 3b ki=%d\n", ki));
+
+ if (!n->elems[0]) {
+ /*
+ * The root is empty and needs to be
+ * removed.
+ */
+ LOG((" shifting root!\n"));
+ t->root = sub;
+ sub->parent = NULL;
+ sfree(n);
+ }
+ }
+ }
+ n = sub;
+ }
+ if (!retval)
+ retval = n->elems[ei];
+
+ if (ei == -1)
+ return NULL; /* although this shouldn't happen */
+
+ /*
+ * Treat special case: this is the one remaining item in
+ * the tree. n is the tree root (no parent), has one
+ * element (no elems[1]), and has no kids (no kids[0]).
+ */
+ if (!n->parent && !n->elems[1] && !n->kids[0]) {
+ LOG((" removed last element in tree\n"));
+ sfree(n);
+ t->root = NULL;
+ return retval;
+ }
+
+ /*
+ * Now we have the element we want, as n->elems[ei], and we
+ * have also arranged for that element not to be the only
+ * one in its node. So...
+ */
+
+ if (!n->kids[0] && n->elems[1]) {
+ /*
+ * Case 1. n is a leaf node with more than one element,
+ * so it's _really easy_. Just delete the thing and
+ * we're done.
+ */
+ int i;
+ LOG((" case 1\n"));
+ for (i = ei; i < 2 && n->elems[i + 1]; i++)
+ n->elems[i] = n->elems[i + 1];
+ n->elems[i] = NULL;
+ /*
+ * Having done that to the leaf node, we now go back up
+ * the tree fixing the counts.
+ */
+ while (n->parent) {
+ int childnum;
+ childnum = (n->parent->kids[0] == n ? 0 :
+ n->parent->kids[1] == n ? 1 :
+ n->parent->kids[2] == n ? 2 : 3);
+ n->parent->counts[childnum]--;
+ n = n->parent;
+ }
+ return retval; /* finished! */
+ } else if (n->kids[ei]->elems[1]) {
+ /*
+ * Case 2a. n is an internal node, and the root of the
+ * subtree to the left of e has more than one element.
+ * So find the predecessor p to e (ie the largest node
+ * in that subtree), place it where e currently is, and
+ * then start the deletion process over again on the
+ * subtree with p as target.
+ */
+ node234 *m = n->kids[ei];
+ void *target;
+ LOG((" case 2a\n"));
+ while (m->kids[0]) {
+ m = (m->kids[3] ? m->kids[3] :
+ m->kids[2] ? m->kids[2] :
+ m->kids[1] ? m->kids[1] : m->kids[0]);
+ }
+ target = (m->elems[2] ? m->elems[2] :
+ m->elems[1] ? m->elems[1] : m->elems[0]);
+ n->elems[ei] = target;
+ index = n->counts[ei] - 1;
+ n = n->kids[ei];
+ } else if (n->kids[ei + 1]->elems[1]) {
+ /*
+ * Case 2b, symmetric to 2a but s/left/right/ and
+ * s/predecessor/successor/. (And s/largest/smallest/).
+ */
+ node234 *m = n->kids[ei + 1];
+ void *target;
+ LOG((" case 2b\n"));
+ while (m->kids[0]) {
+ m = m->kids[0];
+ }
+ target = m->elems[0];
+ n->elems[ei] = target;
+ n = n->kids[ei + 1];
+ index = 0;
+ } else {
+ /*
+ * Case 2c. n is an internal node, and the subtrees to
+ * the left and right of e both have only one element.
+ * So combine the two subnodes into a single big node
+ * with their own elements on the left and right and e
+ * in the middle, then restart the deletion process on
+ * that subtree, with e still as target.
+ */
+ node234 *a = n->kids[ei], *b = n->kids[ei + 1];
+ int j;
+
+ LOG((" case 2c\n"));
+ a->elems[1] = n->elems[ei];
+ a->kids[2] = b->kids[0];
+ a->counts[2] = b->counts[0];
+ if (a->kids[2])
+ a->kids[2]->parent = a;
+ a->elems[2] = b->elems[0];
+ a->kids[3] = b->kids[1];
+ a->counts[3] = b->counts[1];
+ if (a->kids[3])
+ a->kids[3]->parent = a;
+ sfree(b);
+ n->counts[ei] = countnode234(a);
+ /*
+ * That's built the big node in a, and destroyed b. Now
+ * remove the reference to b (and e) in n.
+ */
+ for (j = ei; j < 2 && n->elems[j + 1]; j++) {
+ n->elems[j] = n->elems[j + 1];
+ n->kids[j + 1] = n->kids[j + 2];
+ n->counts[j + 1] = n->counts[j + 2];
+ }
+ n->elems[j] = NULL;
+ n->kids[j + 1] = NULL;
+ n->counts[j + 1] = 0;
+ /*
+ * It's possible, in this case, that we've just removed
+ * the only element in the root of the tree. If so,
+ * shift the root.
+ */
+ if (n->elems[0] == NULL) {
+ LOG((" shifting root!\n"));
+ t->root = a;
+ a->parent = NULL;
+ sfree(n);
+ }
+ /*
+ * Now go round the deletion process again, with n
+ * pointing at the new big node and e still the same.
+ */
+ n = a;
+ index = a->counts[0] + a->counts[1] + 1;
+ }
+ }
+}
+void *delpos234(tree234 * t, int index)
+{
+ if (index < 0 || index >= countnode234(t->root))
+ return NULL;
+ return delpos234_internal(t, index);
+}
+void *del234(tree234 * t, void *e)
+{
+ int index;
+ if (!findrelpos234(t, e, NULL, REL234_EQ, &index))
+ return NULL; /* it wasn't in there anyway */
+ return delpos234_internal(t, index); /* it's there; delete it. */
+}
+
+#ifdef TEST
+
+/*
+ * Test code for the 2-3-4 tree. This code maintains an alternative
+ * representation of the data in the tree, in an array (using the
+ * obvious and slow insert and delete functions). After each tree
+ * operation, the verify() function is called, which ensures all
+ * the tree properties are preserved:
+ * - node->child->parent always equals node
+ * - tree->root->parent always equals NULL
+ * - number of kids == 0 or number of elements + 1;
+ * - tree has the same depth everywhere
+ * - every node has at least one element
+ * - subtree element counts are accurate
+ * - any NULL kid pointer is accompanied by a zero count
+ * - in a sorted tree: ordering property between elements of a
+ * node and elements of its children is preserved
+ * and also ensures the list represented by the tree is the same
+ * list it should be. (This last check also doubly verifies the
+ * ordering properties, because the `same list it should be' is by
+ * definition correctly ordered. It also ensures all nodes are
+ * distinct, because the enum functions would get caught in a loop
+ * if not.)
+ */
+
+#include <stdarg.h>
+
+/*
+ * Error reporting function.
+ */
+void error(char *fmt, ...)
+{
+ va_list ap;
+ printf("ERROR: ");
+ va_start(ap, fmt);
+ vfprintf(stdout, fmt, ap);
+ va_end(ap);
+ printf("\n");
+}
+
+/* The array representation of the data. */
+void **array;
+int arraylen, arraysize;
+cmpfn234 cmp;
+
+/* The tree representation of the same data. */
+tree234 *tree;
+
+typedef struct {
+ int treedepth;
+ int elemcount;
+} chkctx;
+
+int chknode(chkctx * ctx, int level, node234 * node,
+ void *lowbound, void *highbound)
+{
+ int nkids, nelems;
+ int i;
+ int count;
+
+ /* Count the non-NULL kids. */
+ for (nkids = 0; nkids < 4 && node->kids[nkids]; nkids++);
+ /* Ensure no kids beyond the first NULL are non-NULL. */
+ for (i = nkids; i < 4; i++)
+ if (node->kids[i]) {
+ error("node %p: nkids=%d but kids[%d] non-NULL",
+ node, nkids, i);
+ } else if (node->counts[i]) {
+ error("node %p: kids[%d] NULL but count[%d]=%d nonzero",
+ node, i, i, node->counts[i]);
+ }
+
+ /* Count the non-NULL elements. */
+ for (nelems = 0; nelems < 3 && node->elems[nelems]; nelems++);
+ /* Ensure no elements beyond the first NULL are non-NULL. */
+ for (i = nelems; i < 3; i++)
+ if (node->elems[i]) {
+ error("node %p: nelems=%d but elems[%d] non-NULL",
+ node, nelems, i);
+ }
+
+ if (nkids == 0) {
+ /*
+ * If nkids==0, this is a leaf node; verify that the tree
+ * depth is the same everywhere.
+ */
+ if (ctx->treedepth < 0)
+ ctx->treedepth = level; /* we didn't know the depth yet */
+ else if (ctx->treedepth != level)
+ error("node %p: leaf at depth %d, previously seen depth %d",
+ node, level, ctx->treedepth);
+ } else {
+ /*
+ * If nkids != 0, then it should be nelems+1, unless nelems
+ * is 0 in which case nkids should also be 0 (and so we
+ * shouldn't be in this condition at all).
+ */
+ int shouldkids = (nelems ? nelems + 1 : 0);
+ if (nkids != shouldkids) {
+ error("node %p: %d elems should mean %d kids but has %d",
+ node, nelems, shouldkids, nkids);
+ }
+ }
+
+ /*
+ * nelems should be at least 1.
+ */
+ if (nelems == 0) {
+ error("node %p: no elems", node, nkids);
+ }
+
+ /*
+ * Add nelems to the running element count of the whole tree.
+ */
+ ctx->elemcount += nelems;
+
+ /*
+ * Check ordering property: all elements should be strictly >
+ * lowbound, strictly < highbound, and strictly < each other in
+ * sequence. (lowbound and highbound are NULL at edges of tree
+ * - both NULL at root node - and NULL is considered to be <
+ * everything and > everything. IYSWIM.)
+ */
+ if (cmp) {
+ for (i = -1; i < nelems; i++) {
+ void *lower = (i == -1 ? lowbound : node->elems[i]);
+ void *higher =
+ (i + 1 == nelems ? highbound : node->elems[i + 1]);
+ if (lower && higher && cmp(lower, higher) >= 0) {
+ error("node %p: kid comparison [%d=%s,%d=%s] failed",
+ node, i, lower, i + 1, higher);
+ }
+ }
+ }
+
+ /*
+ * Check parent pointers: all non-NULL kids should have a
+ * parent pointer coming back to this node.
+ */
+ for (i = 0; i < nkids; i++)
+ if (node->kids[i]->parent != node) {
+ error("node %p kid %d: parent ptr is %p not %p",
+ node, i, node->kids[i]->parent, node);
+ }
+
+
+ /*
+ * Now (finally!) recurse into subtrees.
+ */
+ count = nelems;
+
+ for (i = 0; i < nkids; i++) {
+ void *lower = (i == 0 ? lowbound : node->elems[i - 1]);
+ void *higher = (i >= nelems ? highbound : node->elems[i]);
+ int subcount =
+ chknode(ctx, level + 1, node->kids[i], lower, higher);
+ if (node->counts[i] != subcount) {
+ error("node %p kid %d: count says %d, subtree really has %d",
+ node, i, node->counts[i], subcount);
+ }
+ count += subcount;
+ }
+
+ return count;
+}
+
+void verify(void)
+{
+ chkctx ctx;
+ int i;
+ void *p;
+
+ ctx.treedepth = -1; /* depth unknown yet */
+ ctx.elemcount = 0; /* no elements seen yet */
+ /*
+ * Verify validity of tree properties.
+ */
+ if (tree->root) {
+ if (tree->root->parent != NULL)
+ error("root->parent is %p should be null", tree->root->parent);
+ chknode(&ctx, 0, tree->root, NULL, NULL);
+ }
+ printf("tree depth: %d\n", ctx.treedepth);
+ /*
+ * Enumerate the tree and ensure it matches up to the array.
+ */
+ for (i = 0; NULL != (p = index234(tree, i)); i++) {
+ if (i >= arraylen)
+ error("tree contains more than %d elements", arraylen);
+ if (array[i] != p)
+ error("enum at position %d: array says %s, tree says %s",
+ i, array[i], p);
+ }
+ if (ctx.elemcount != i) {
+ error("tree really contains %d elements, enum gave %d",
+ ctx.elemcount, i);
+ }
+ if (i < arraylen) {
+ error("enum gave only %d elements, array has %d", i, arraylen);
+ }
+ i = count234(tree);
+ if (ctx.elemcount != i) {
+ error("tree really contains %d elements, count234 gave %d",
+ ctx.elemcount, i);
+ }
+}
+
+void internal_addtest(void *elem, int index, void *realret)
+{
+ int i, j;
+ void *retval;
+
+ if (arraysize < arraylen + 1) {
+ arraysize = arraylen + 1 + 256;
+ array = sresize(array, arraysize, void *);
+ }
+
+ i = index;
+ /* now i points to the first element >= elem */
+ retval = elem; /* expect elem returned (success) */
+ for (j = arraylen; j > i; j--)
+ array[j] = array[j - 1];
+ array[i] = elem; /* add elem to array */
+ arraylen++;
+
+ if (realret != retval) {
+ error("add: retval was %p expected %p", realret, retval);
+ }
+
+ verify();
+}
+
+void addtest(void *elem)
+{
+ int i;
+ void *realret;
+
+ realret = add234(tree, elem);
+
+ i = 0;
+ while (i < arraylen && cmp(elem, array[i]) > 0)
+ i++;
+ if (i < arraylen && !cmp(elem, array[i])) {
+ void *retval = array[i]; /* expect that returned not elem */
+ if (realret != retval) {
+ error("add: retval was %p expected %p", realret, retval);
+ }
+ } else
+ internal_addtest(elem, i, realret);
+}
+
+void addpostest(void *elem, int i)
+{
+ void *realret;
+
+ realret = addpos234(tree, elem, i);
+
+ internal_addtest(elem, i, realret);
+}
+
+void delpostest(int i)
+{
+ int index = i;
+ void *elem = array[i], *ret;
+
+ /* i points to the right element */
+ while (i < arraylen - 1) {
+ array[i] = array[i + 1];
+ i++;
+ }
+ arraylen--; /* delete elem from array */
+
+ if (tree->cmp)
+ ret = del234(tree, elem);
+ else
+ ret = delpos234(tree, index);
+
+ if (ret != elem) {
+ error("del returned %p, expected %p", ret, elem);
+ }
+
+ verify();
+}
+
+void deltest(void *elem)
+{
+ int i;
+
+ i = 0;
+ while (i < arraylen && cmp(elem, array[i]) > 0)
+ i++;
+ if (i >= arraylen || cmp(elem, array[i]) != 0)
+ return; /* don't do it! */
+ delpostest(i);
+}
+
+/* A sample data set and test utility. Designed for pseudo-randomness,
+ * and yet repeatability. */
+
+/*
+ * This random number generator uses the `portable implementation'
+ * given in ANSI C99 draft N869. It assumes `unsigned' is 32 bits;
+ * change it if not.
+ */
+int randomnumber(unsigned *seed)
+{
+ *seed *= 1103515245;
+ *seed += 12345;
+ return ((*seed) / 65536) % 32768;
+}
+
+int mycmp(void *av, void *bv)
+{
+ char const *a = (char const *) av;
+ char const *b = (char const *) bv;
+ return strcmp(a, b);
+}
+
+#define lenof(x) ( sizeof((x)) / sizeof(*(x)) )
+
+char *strings[] = {
+ "a", "ab", "absque", "coram", "de",
+ "palam", "clam", "cum", "ex", "e",
+ "sine", "tenus", "pro", "prae",
+ "banana", "carrot", "cabbage", "broccoli", "onion", "zebra",
+ "penguin", "blancmange", "pangolin", "whale", "hedgehog",
+ "giraffe", "peanut", "bungee", "foo", "bar", "baz", "quux",
+ "murfl", "spoo", "breen", "flarn", "octothorpe",
+ "snail", "tiger", "elephant", "octopus", "warthog", "armadillo",
+ "aardvark", "wyvern", "dragon", "elf", "dwarf", "orc", "goblin",
+ "pixie", "basilisk", "warg", "ape", "lizard", "newt", "shopkeeper",
+ "wand", "ring", "amulet"
+};
+
+#define NSTR lenof(strings)
+
+int findtest(void)
+{
+ const static int rels[] = {
+ REL234_EQ, REL234_GE, REL234_LE, REL234_LT, REL234_GT
+ };
+ const static char *const relnames[] = {
+ "EQ", "GE", "LE", "LT", "GT"
+ };
+ int i, j, rel, index;
+ char *p, *ret, *realret, *realret2;
+ int lo, hi, mid, c;
+
+ for (i = 0; i < NSTR; i++) {
+ p = strings[i];
+ for (j = 0; j < sizeof(rels) / sizeof(*rels); j++) {
+ rel = rels[j];
+
+ lo = 0;
+ hi = arraylen - 1;
+ while (lo <= hi) {
+ mid = (lo + hi) / 2;
+ c = strcmp(p, array[mid]);
+ if (c < 0)
+ hi = mid - 1;
+ else if (c > 0)
+ lo = mid + 1;
+ else
+ break;
+ }
+
+ if (c == 0) {
+ if (rel == REL234_LT)
+ ret = (mid > 0 ? array[--mid] : NULL);
+ else if (rel == REL234_GT)
+ ret = (mid < arraylen - 1 ? array[++mid] : NULL);
+ else
+ ret = array[mid];
+ } else {
+ assert(lo == hi + 1);
+ if (rel == REL234_LT || rel == REL234_LE) {
+ mid = hi;
+ ret = (hi >= 0 ? array[hi] : NULL);
+ } else if (rel == REL234_GT || rel == REL234_GE) {
+ mid = lo;
+ ret = (lo < arraylen ? array[lo] : NULL);
+ } else
+ ret = NULL;
+ }
+
+ realret = findrelpos234(tree, p, NULL, rel, &index);
+ if (realret != ret) {
+ error("find(\"%s\",%s) gave %s should be %s",
+ p, relnames[j], realret, ret);
+ }
+ if (realret && index != mid) {
+ error("find(\"%s\",%s) gave %d should be %d",
+ p, relnames[j], index, mid);
+ }
+ if (realret && rel == REL234_EQ) {
+ realret2 = index234(tree, index);
+ if (realret2 != realret) {
+ error("find(\"%s\",%s) gave %s(%d) but %d -> %s",
+ p, relnames[j], realret, index, index, realret2);
+ }
+ }
+#if 0
+ printf("find(\"%s\",%s) gave %s(%d)\n", p, relnames[j],
+ realret, index);
+#endif
+ }
+ }
+
+ realret = findrelpos234(tree, NULL, NULL, REL234_GT, &index);
+ if (arraylen && (realret != array[0] || index != 0)) {
+ error("find(NULL,GT) gave %s(%d) should be %s(0)",
+ realret, index, array[0]);
+ } else if (!arraylen && (realret != NULL)) {
+ error("find(NULL,GT) gave %s(%d) should be NULL", realret, index);
+ }
+
+ realret = findrelpos234(tree, NULL, NULL, REL234_LT, &index);
+ if (arraylen
+ && (realret != array[arraylen - 1] || index != arraylen - 1)) {
+ error("find(NULL,LT) gave %s(%d) should be %s(0)", realret, index,
+ array[arraylen - 1]);
+ } else if (!arraylen && (realret != NULL)) {
+ error("find(NULL,LT) gave %s(%d) should be NULL", realret, index);
+ }
+}
+
+int main(void)
+{
+ int in[NSTR];
+ int i, j, k;
+ unsigned seed = 0;
+
+ for (i = 0; i < NSTR; i++)
+ in[i] = 0;
+ array = NULL;
+ arraylen = arraysize = 0;
+ tree = newtree234(mycmp);
+ cmp = mycmp;
+
+ verify();
+ for (i = 0; i < 10000; i++) {
+ j = randomnumber(&seed);
+ j %= NSTR;
+ printf("trial: %d\n", i);
+ if (in[j]) {
+ printf("deleting %s (%d)\n", strings[j], j);
+ deltest(strings[j]);
+ in[j] = 0;
+ } else {
+ printf("adding %s (%d)\n", strings[j], j);
+ addtest(strings[j]);
+ in[j] = 1;
+ }
+ findtest();
+ }
+
+ while (arraylen > 0) {
+ j = randomnumber(&seed);
+ j %= arraylen;
+ deltest(array[j]);
+ }
+
+ freetree234(tree);
+
+ /*
+ * Now try an unsorted tree. We don't really need to test
+ * delpos234 because we know del234 is based on it, so it's
+ * already been tested in the above sorted-tree code; but for
+ * completeness we'll use it to tear down our unsorted tree
+ * once we've built it.
+ */
+ tree = newtree234(NULL);
+ cmp = NULL;
+ verify();
+ for (i = 0; i < 1000; i++) {
+ printf("trial: %d\n", i);
+ j = randomnumber(&seed);
+ j %= NSTR;
+ k = randomnumber(&seed);
+ k %= count234(tree) + 1;
+ printf("adding string %s at index %d\n", strings[j], k);
+ addpostest(strings[j], k);
+ }
+ 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);
+ delpostest(j);
+ }
+
+ return 0;
+}
+
+#endif
--- /dev/null
+/*
+ * tree234.h: header defining functions in tree234.c.
+ *
+ * This file is copyright 1999-2001 Simon Tatham.
+ *
+ * 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, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL SIMON TATHAM BE LIABLE FOR
+ * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+ * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef TREE234_H
+#define TREE234_H
+
+/*
+ * This typedef is opaque outside tree234.c itself.
+ */
+typedef struct tree234_Tag tree234;
+
+typedef int (*cmpfn234) (void *, void *);
+
+/*
+ * Create a 2-3-4 tree. If `cmp' is NULL, the tree is unsorted, and
+ * lookups by key will fail: you can only look things up by numeric
+ * index, and you have to use addpos234() and delpos234().
+ */
+tree234 *newtree234(cmpfn234 cmp);
+
+/*
+ * Free a 2-3-4 tree (not including freeing the elements).
+ */
+void freetree234(tree234 * t);
+
+/*
+ * Add an element e to a sorted 2-3-4 tree t. Returns e on success,
+ * or if an existing element compares equal, returns that.
+ */
+void *add234(tree234 * t, void *e);
+
+/*
+ * Add an element e to an unsorted 2-3-4 tree t. Returns e on
+ * success, NULL on failure. (Failure should only occur if the
+ * index is out of range or the tree is sorted.)
+ *
+ * Index range can be from 0 to the tree's current element count,
+ * inclusive.
+ */
+void *addpos234(tree234 * t, void *e, int index);
+
+/*
+ * Look up the element at a given numeric index in a 2-3-4 tree.
+ * Returns NULL if the index is out of range.
+ *
+ * One obvious use for this function is in iterating over the whole
+ * of a tree (sorted or unsorted):
+ *
+ * for (i = 0; (p = index234(tree, i)) != NULL; i++) consume(p);
+ *
+ * or
+ *
+ * int maxcount = count234(tree);
+ * for (i = 0; i < maxcount; i++) {
+ * p = index234(tree, i);
+ * assert(p != NULL);
+ * consume(p);
+ * }
+ */
+void *index234(tree234 * t, int index);
+
+/*
+ * Find an element e in a sorted 2-3-4 tree t. Returns NULL if not
+ * found. e is always passed as the first argument to cmp, so cmp
+ * can be an asymmetric function if desired. cmp can also be passed
+ * as NULL, in which case the compare function from the tree proper
+ * will be used.
+ *
+ * Three of these functions are special cases of findrelpos234. The
+ * non-`pos' variants lack the `index' parameter: if the parameter
+ * is present and non-NULL, it must point to an integer variable
+ * which will be filled with the numeric index of the returned
+ * element.
+ *
+ * The non-`rel' variants lack the `relation' parameter. This
+ * parameter allows you to specify what relation the element you
+ * provide has to the element you're looking for. This parameter
+ * can be:
+ *
+ * REL234_EQ - find only an element that compares equal to e
+ * REL234_LT - find the greatest element that compares < e
+ * REL234_LE - find the greatest element that compares <= e
+ * REL234_GT - find the smallest element that compares > e
+ * REL234_GE - find the smallest element that compares >= e
+ *
+ * Non-`rel' variants assume REL234_EQ.
+ *
+ * If `rel' is REL234_GT or REL234_LT, the `e' parameter may be
+ * NULL. In this case, REL234_GT will return the smallest element
+ * in the tree, and REL234_LT will return the greatest. This gives
+ * an alternative means of iterating over a sorted tree, instead of
+ * using index234:
+ *
+ * // to loop forwards
+ * for (p = NULL; (p = findrel234(tree, p, NULL, REL234_GT)) != NULL ;)
+ * consume(p);
+ *
+ * // to loop backwards
+ * for (p = NULL; (p = findrel234(tree, p, NULL, REL234_LT)) != NULL ;)
+ * consume(p);
+ */
+enum {
+ REL234_EQ, REL234_LT, REL234_LE, REL234_GT, REL234_GE
+};
+void *find234(tree234 * t, void *e, cmpfn234 cmp);
+void *findrel234(tree234 * t, void *e, cmpfn234 cmp, int relation);
+void *findpos234(tree234 * t, void *e, cmpfn234 cmp, int *index);
+void *findrelpos234(tree234 * t, void *e, cmpfn234 cmp, int relation,
+ int *index);
+
+/*
+ * Delete an element e in a 2-3-4 tree. Does not free the element,
+ * merely removes all links to it from the tree nodes.
+ *
+ * delpos234 deletes the element at a particular tree index: it
+ * works on both sorted and unsorted trees.
+ *
+ * del234 deletes the element passed to it, so it only works on
+ * sorted trees. (It's equivalent to using findpos234 to determine
+ * the index of an element, and then passing that index to
+ * delpos234.)
+ *
+ * Both functions return a pointer to the element they delete, for
+ * the user to free or pass on elsewhere or whatever. If the index
+ * is out of range (delpos234) or the element is already not in the
+ * tree (del234) then they return NULL.
+ */
+void *del234(tree234 * t, void *e);
+void *delpos234(tree234 * t, int index);
+
+/*
+ * Return the total element count of a tree234.
+ */
+int count234(tree234 * t);
+
+#endif /* TREE234_H */
--- /dev/null
+# To compile this into a configure script, you need:
+# * 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
+AC_CONFIG_FILES([Makefile])
+AC_CONFIG_HEADERS([uxconfig.h:uxconfig.in])
+
+AC_PROG_INSTALL
+AC_PROG_CC
+if test "X$GCC" = Xyes; then
+ PUTTYCFLAGS="-Wall -Werror"
+else
+ PUTTYCFLAGS=""
+fi
+AC_SUBST(PUTTYCFLAGS)
+
+AC_ARG_WITH([gssapi],
+ [AS_HELP_STRING([--without-gssapi],
+ [disable GSSAPI support])],
+ [],
+ [with_gssapi=yes])
+
+WITH_GSSAPI=
+AS_IF([test "x$with_gssapi" != xno],
+ [AC_DEFINE([WITH_GSSAPI], [1], [Define if building with GSSAPI support.])])
+
+AC_CHECK_HEADERS([utmpx.h sys/select.h],,,[
+#include <sys/types.h>
+#include <utmp.h>])
+
+# 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
+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(
+ [dlopen],[dl],
+ [],
+ [AC_DEFINE([NO_LIBDL], [1], [Define if we could not find libdl.])
+ AC_CHECK_HEADERS([gssapi/gssapi.h])
+ 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_FUNCS([getaddrinfo ptsname setresuid strsignal updwtmpx])
+
+AC_OUTPUT
+
+AH_BOTTOM([
+/* Convert autoconf definitions to ones that PuTTY wants. */
+
+#ifndef HAVE_GETADDRINFO
+# define NO_IPV6
+#endif
+#ifndef HAVE_SETRESUID
+# define HAVE_NO_SETRESUID
+#endif
+#ifndef HAVE_STRSIGNAL
+# define HAVE_NO_STRSIGNAL
+#endif
+#if !defined(HAVE_UTMPX_H) || !defined(HAVE_UPDWTMPX)
+# define OMIT_UTMP
+#endif
+#ifndef HAVE_PTSNAME
+# define BSD_PTYS
+#endif
+#ifndef HAVE_SYS_SELECT_H
+# define HAVE_NO_SYS_SELECT_H
+#endif
+#ifndef HAVE_PANGO_FONT_FAMILY_IS_MONOSPACE
+# define PANGO_PRE_1POINT4
+#endif
+#ifndef HAVE_PANGO_FONT_MAP_LIST_FAMILIES
+# define PANGO_PRE_1POINT6
+#endif
+#if !defined(WITH_GSSAPI)
+# define NO_GSSAPI
+#endif
+#if !defined(NO_GSSAPI) && defined(NO_LIBDL)
+# if !defined(HAVE_GSSAPI_GSSAPI_H) || defined(NO_GSSAPI_LIB)
+# define NO_GSSAPI
+# endif
+#endif
+])
--- /dev/null
+/*
+ * gtkcfg.c - the GTK-specific parts of the PuTTY configuration
+ * box.
+ */
+
+#include <assert.h>
+#include <stdlib.h>
+
+#include "putty.h"
+#include "dialog.h"
+#include "storage.h"
+
+static void about_handler(union control *ctrl, void *dlg,
+ void *data, int event)
+{
+ if (event == EVENT_ACTION) {
+ about_box(ctrl->generic.context.p);
+ }
+}
+
+void gtk_setup_config_box(struct controlbox *b, int midsession, void *win)
+{
+ struct controlset *s, *s2;
+ union control *c;
+ int i;
+
+ if (!midsession) {
+ /*
+ * Add the About button to the standard panel.
+ */
+ s = ctrl_getset(b, "", "", "");
+ c = ctrl_pushbutton(s, "About", 'a', HELPCTX(no_help),
+ about_handler, P(win));
+ c->generic.column = 0;
+ }
+
+ /*
+ * GTK makes it rather easier to put the scrollbar on the left
+ * than Windows does!
+ */
+ s = ctrl_getset(b, "Window", "scrollback",
+ "Control the scrollback in the window");
+ ctrl_checkbox(s, "Scrollbar on left", 'l',
+ HELPCTX(no_help),
+ dlg_stdcheckbox_handler,
+ I(offsetof(Config,scrollbar_on_left)));
+ /*
+ * Really this wants to go just after `Display scrollbar'. See
+ * if we can find that control, and do some shuffling.
+ */
+ for (i = 0; i < s->ncontrols; i++) {
+ c = s->ctrls[i];
+ if (c->generic.type == CTRL_CHECKBOX &&
+ c->generic.context.i == offsetof(Config,scrollbar)) {
+ /*
+ * Control i is the scrollbar checkbox.
+ * Control s->ncontrols-1 is the scrollbar-on-left one.
+ */
+ if (i < s->ncontrols-2) {
+ c = s->ctrls[s->ncontrols-1];
+ memmove(s->ctrls+i+2, s->ctrls+i+1,
+ (s->ncontrols-i-2)*sizeof(union control *));
+ s->ctrls[i+1] = c;
+ }
+ break;
+ }
+ }
+
+ /*
+ * X requires three more fonts: bold, wide, and wide-bold; also
+ * we need the fiddly shadow-bold-offset control. This would
+ * make the Window/Appearance panel rather unwieldy and large,
+ * so I think the sensible thing here is to _move_ this
+ * controlset into a separate Window/Fonts panel!
+ */
+ s2 = ctrl_getset(b, "Window/Appearance", "font",
+ "Font settings");
+ /* Remove this controlset from b. */
+ for (i = 0; i < b->nctrlsets; i++) {
+ if (b->ctrlsets[i] == s2) {
+ memmove(b->ctrlsets+i, b->ctrlsets+i+1,
+ (b->nctrlsets-i-1) * sizeof(*b->ctrlsets));
+ b->nctrlsets--;
+ break;
+ }
+ }
+ 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)));
+ ctrl_fontsel(s, "Font used for wide (CJK) text", 'w',
+ HELPCTX(no_help),
+ dlg_stdfontsel_handler, I(offsetof(Config,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)));
+ ctrl_fontsel(s, "Font used for bold wide text", 'i',
+ HELPCTX(no_help),
+ dlg_stdfontsel_handler, I(offsetof(Config,wideboldfont)));
+ ctrl_checkbox(s, "Use shadow bold instead of bold fonts", 'u',
+ HELPCTX(no_help),
+ dlg_stdcheckbox_handler,
+ I(offsetof(Config,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));
+
+ /*
+ * Markus Kuhn feels, not totally unreasonably, that it's good
+ * for all applications to shift into UTF-8 mode if they notice
+ * that they've been started with a LANG setting dictating it,
+ * so that people don't have to keep remembering a separate
+ * UTF-8 option for every application they use. Therefore,
+ * here's an override option in the Translation panel.
+ */
+ s = ctrl_getset(b, "Window/Translation", "trans",
+ "Character set translation on received data");
+ ctrl_checkbox(s, "Override with UTF-8 if locale says so", 'l',
+ HELPCTX(translation_utf8_override),
+ dlg_stdcheckbox_handler,
+ I(offsetof(Config,utf8_override)));
+
+ 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
+ * 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)));
+ }
+}
--- /dev/null
+/*
+ * gtkcols.c - implementation of the `Columns' GTK layout container.
+ */
+
+#include "gtkcols.h"
+#include <gtk/gtk.h>
+
+static void columns_init(Columns *cols);
+static void columns_class_init(ColumnsClass *klass);
+static void columns_map(GtkWidget *widget);
+static void columns_unmap(GtkWidget *widget);
+#if !GTK_CHECK_VERSION(2,0,0)
+static void columns_draw(GtkWidget *widget, GdkRectangle *area);
+static gint columns_expose(GtkWidget *widget, GdkEventExpose *event);
+#endif
+static void columns_base_add(GtkContainer *container, GtkWidget *widget);
+static void columns_remove(GtkContainer *container, GtkWidget *widget);
+static void columns_forall(GtkContainer *container, gboolean include_internals,
+ GtkCallback callback, gpointer callback_data);
+#if !GTK_CHECK_VERSION(2,0,0)
+static gint columns_focus(GtkContainer *container, GtkDirectionType dir);
+#endif
+static GtkType columns_child_type(GtkContainer *container);
+static void columns_size_request(GtkWidget *widget, GtkRequisition *req);
+static void columns_size_allocate(GtkWidget *widget, GtkAllocation *alloc);
+
+static GtkContainerClass *parent_class = NULL;
+
+#if !GTK_CHECK_VERSION(2,0,0)
+GtkType columns_get_type(void)
+{
+ static GtkType columns_type = 0;
+
+ if (!columns_type) {
+ static const GtkTypeInfo columns_info = {
+ "Columns",
+ sizeof(Columns),
+ sizeof(ColumnsClass),
+ (GtkClassInitFunc) columns_class_init,
+ (GtkObjectInitFunc) columns_init,
+ /* reserved_1 */ NULL,
+ /* reserved_2 */ NULL,
+ (GtkClassInitFunc) NULL,
+ };
+
+ columns_type = gtk_type_unique(GTK_TYPE_CONTAINER, &columns_info);
+ }
+
+ return columns_type;
+}
+#else
+GType columns_get_type(void)
+{
+ static GType columns_type = 0;
+
+ if (!columns_type) {
+ static const GTypeInfo columns_info = {
+ sizeof(ColumnsClass),
+ NULL,
+ NULL,
+ (GClassInitFunc) columns_class_init,
+ NULL,
+ NULL,
+ sizeof(Columns),
+ 0,
+ (GInstanceInitFunc)columns_init,
+ };
+
+ columns_type = g_type_register_static(GTK_TYPE_CONTAINER, "Columns",
+ &columns_info, 0);
+ }
+
+ return columns_type;
+}
+#endif
+
+#if !GTK_CHECK_VERSION(2,0,0)
+static gint (*columns_inherited_focus)(GtkContainer *container,
+ GtkDirectionType direction);
+#endif
+
+static void columns_class_init(ColumnsClass *klass)
+{
+#if !GTK_CHECK_VERSION(2,0,0)
+ /* GtkObjectClass *object_class = (GtkObjectClass *)klass; */
+ GtkWidgetClass *widget_class = (GtkWidgetClass *)klass;
+ GtkContainerClass *container_class = (GtkContainerClass *)klass;
+#else
+ /* GObjectClass *object_class = G_OBJECT_CLASS(klass); */
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
+ GtkContainerClass *container_class = GTK_CONTAINER_CLASS(klass);
+#endif
+
+#if !GTK_CHECK_VERSION(2,0,0)
+ parent_class = gtk_type_class(GTK_TYPE_CONTAINER);
+#else
+ parent_class = g_type_class_peek_parent(klass);
+#endif
+
+ widget_class->map = columns_map;
+ widget_class->unmap = columns_unmap;
+#if !GTK_CHECK_VERSION(2,0,0)
+ widget_class->draw = columns_draw;
+ widget_class->expose_event = columns_expose;
+#endif
+ widget_class->size_request = columns_size_request;
+ widget_class->size_allocate = columns_size_allocate;
+
+ container_class->add = columns_base_add;
+ container_class->remove = columns_remove;
+ container_class->forall = columns_forall;
+ container_class->child_type = columns_child_type;
+#if !GTK_CHECK_VERSION(2,0,0)
+ /* Save the previous value of this method. */
+ if (!columns_inherited_focus)
+ columns_inherited_focus = container_class->focus;
+ container_class->focus = columns_focus;
+#endif
+}
+
+static void columns_init(Columns *cols)
+{
+ GTK_WIDGET_SET_FLAGS(cols, GTK_NO_WINDOW);
+
+ cols->children = NULL;
+ cols->spacing = 0;
+}
+
+/*
+ * These appear to be thoroughly tedious functions; the only reason
+ * we have to reimplement them at all is because we defined our own
+ * format for our GList of children...
+ */
+static void columns_map(GtkWidget *widget)
+{
+ Columns *cols;
+ ColumnsChild *child;
+ GList *children;
+
+ g_return_if_fail(widget != NULL);
+ g_return_if_fail(IS_COLUMNS(widget));
+
+ cols = COLUMNS(widget);
+ GTK_WIDGET_SET_FLAGS(cols, GTK_MAPPED);
+
+ for (children = cols->children;
+ children && (child = children->data);
+ children = children->next) {
+ if (child->widget &&
+ GTK_WIDGET_VISIBLE(child->widget) &&
+ !GTK_WIDGET_MAPPED(child->widget))
+ gtk_widget_map(child->widget);
+ }
+}
+static void columns_unmap(GtkWidget *widget)
+{
+ Columns *cols;
+ ColumnsChild *child;
+ GList *children;
+
+ g_return_if_fail(widget != NULL);
+ g_return_if_fail(IS_COLUMNS(widget));
+
+ cols = COLUMNS(widget);
+ GTK_WIDGET_UNSET_FLAGS(cols, GTK_MAPPED);
+
+ for (children = cols->children;
+ children && (child = children->data);
+ children = children->next) {
+ if (child->widget &&
+ GTK_WIDGET_VISIBLE(child->widget) &&
+ GTK_WIDGET_MAPPED(child->widget))
+ gtk_widget_unmap(child->widget);
+ }
+}
+#if !GTK_CHECK_VERSION(2,0,0)
+static void columns_draw(GtkWidget *widget, GdkRectangle *area)
+{
+ Columns *cols;
+ ColumnsChild *child;
+ GList *children;
+ GdkRectangle child_area;
+
+ g_return_if_fail(widget != NULL);
+ g_return_if_fail(IS_COLUMNS(widget));
+
+ if (GTK_WIDGET_DRAWABLE(widget)) {
+ cols = COLUMNS(widget);
+
+ for (children = cols->children;
+ children && (child = children->data);
+ children = children->next) {
+ if (child->widget &&
+ GTK_WIDGET_DRAWABLE(child->widget) &&
+ gtk_widget_intersect(child->widget, area, &child_area))
+ gtk_widget_draw(child->widget, &child_area);
+ }
+ }
+}
+static gint columns_expose(GtkWidget *widget, GdkEventExpose *event)
+{
+ Columns *cols;
+ ColumnsChild *child;
+ GList *children;
+ GdkEventExpose child_event;
+
+ g_return_val_if_fail(widget != NULL, FALSE);
+ g_return_val_if_fail(IS_COLUMNS(widget), FALSE);
+ g_return_val_if_fail(event != NULL, FALSE);
+
+ if (GTK_WIDGET_DRAWABLE(widget)) {
+ cols = COLUMNS(widget);
+ child_event = *event;
+
+ for (children = cols->children;
+ children && (child = children->data);
+ children = children->next) {
+ if (child->widget &&
+ GTK_WIDGET_DRAWABLE(child->widget) &&
+ GTK_WIDGET_NO_WINDOW(child->widget) &&
+ gtk_widget_intersect(child->widget, &event->area,
+ &child_event.area))
+ gtk_widget_event(child->widget, (GdkEvent *)&child_event);
+ }
+ }
+ return FALSE;
+}
+#endif
+
+static void columns_base_add(GtkContainer *container, GtkWidget *widget)
+{
+ Columns *cols;
+
+ g_return_if_fail(container != NULL);
+ g_return_if_fail(IS_COLUMNS(container));
+ g_return_if_fail(widget != NULL);
+
+ cols = COLUMNS(container);
+
+ /*
+ * Default is to add a new widget spanning all columns.
+ */
+ columns_add(cols, widget, 0, 0); /* 0 means ncols */
+}
+
+static void columns_remove(GtkContainer *container, GtkWidget *widget)
+{
+ Columns *cols;
+ ColumnsChild *child;
+ GtkWidget *childw;
+ GList *children;
+ gboolean was_visible;
+
+ g_return_if_fail(container != NULL);
+ g_return_if_fail(IS_COLUMNS(container));
+ g_return_if_fail(widget != NULL);
+
+ cols = COLUMNS(container);
+
+ for (children = cols->children;
+ children && (child = children->data);
+ children = children->next) {
+ if (child->widget != widget)
+ continue;
+
+ was_visible = GTK_WIDGET_VISIBLE(widget);
+ gtk_widget_unparent(widget);
+ cols->children = g_list_remove_link(cols->children, children);
+ g_list_free(children);
+ g_free(child);
+ if (was_visible)
+ gtk_widget_queue_resize(GTK_WIDGET(container));
+ break;
+ }
+
+ for (children = cols->taborder;
+ children && (childw = children->data);
+ children = children->next) {
+ if (childw != widget)
+ continue;
+
+ cols->taborder = g_list_remove_link(cols->taborder, children);
+ g_list_free(children);
+#if GTK_CHECK_VERSION(2,0,0)
+ gtk_container_set_focus_chain(container, cols->taborder);
+#endif
+ break;
+ }
+}
+
+static void columns_forall(GtkContainer *container, gboolean include_internals,
+ GtkCallback callback, gpointer callback_data)
+{
+ Columns *cols;
+ ColumnsChild *child;
+ GList *children, *next;
+
+ g_return_if_fail(container != NULL);
+ g_return_if_fail(IS_COLUMNS(container));
+ g_return_if_fail(callback != NULL);
+
+ cols = COLUMNS(container);
+
+ for (children = cols->children;
+ children && (child = children->data);
+ children = next) {
+ /*
+ * We can't wait until after the callback to assign
+ * `children = children->next', because the callback might
+ * be gtk_widget_destroy, which would remove the link
+ * `children' from the list! So instead we must get our
+ * hands on the value of the `next' pointer _before_ the
+ * callback.
+ */
+ next = children->next;
+ if (child->widget)
+ callback(child->widget, callback_data);
+ }
+}
+
+static GtkType columns_child_type(GtkContainer *container)
+{
+ return GTK_TYPE_WIDGET;
+}
+
+GtkWidget *columns_new(gint spacing)
+{
+ Columns *cols;
+
+#if !GTK_CHECK_VERSION(2,0,0)
+ cols = gtk_type_new(columns_get_type());
+#else
+ cols = g_object_new(TYPE_COLUMNS, NULL);
+#endif
+
+ cols->spacing = spacing;
+
+ return GTK_WIDGET(cols);
+}
+
+void columns_set_cols(Columns *cols, gint ncols, const gint *percentages)
+{
+ ColumnsChild *childdata;
+ gint i;
+
+ g_return_if_fail(cols != NULL);
+ g_return_if_fail(IS_COLUMNS(cols));
+ g_return_if_fail(ncols > 0);
+ g_return_if_fail(percentages != NULL);
+
+ childdata = g_new(ColumnsChild, 1);
+ childdata->widget = NULL;
+ childdata->ncols = ncols;
+ childdata->percentages = g_new(gint, ncols);
+ childdata->force_left = FALSE;
+ for (i = 0; i < ncols; i++)
+ childdata->percentages[i] = percentages[i];
+
+ cols->children = g_list_append(cols->children, childdata);
+}
+
+void columns_add(Columns *cols, GtkWidget *child,
+ gint colstart, gint colspan)
+{
+ ColumnsChild *childdata;
+
+ g_return_if_fail(cols != NULL);
+ g_return_if_fail(IS_COLUMNS(cols));
+ g_return_if_fail(child != NULL);
+ g_return_if_fail(child->parent == NULL);
+
+ childdata = g_new(ColumnsChild, 1);
+ childdata->widget = child;
+ childdata->colstart = colstart;
+ childdata->colspan = colspan;
+ childdata->force_left = FALSE;
+
+ cols->children = g_list_append(cols->children, childdata);
+ cols->taborder = g_list_append(cols->taborder, child);
+
+ gtk_widget_set_parent(child, GTK_WIDGET(cols));
+
+#if GTK_CHECK_VERSION(2,0,0)
+ gtk_container_set_focus_chain(GTK_CONTAINER(cols), cols->taborder);
+#endif
+
+ if (GTK_WIDGET_REALIZED(cols))
+ gtk_widget_realize(child);
+
+ if (GTK_WIDGET_VISIBLE(cols) && GTK_WIDGET_VISIBLE(child)) {
+ if (GTK_WIDGET_MAPPED(cols))
+ gtk_widget_map(child);
+ gtk_widget_queue_resize(child);
+ }
+}
+
+void columns_force_left_align(Columns *cols, GtkWidget *widget)
+{
+ ColumnsChild *child;
+ GList *children;
+
+ g_return_if_fail(cols != NULL);
+ g_return_if_fail(IS_COLUMNS(cols));
+ g_return_if_fail(widget != NULL);
+
+ for (children = cols->children;
+ children && (child = children->data);
+ children = children->next) {
+ if (child->widget != widget)
+ continue;
+
+ child->force_left = TRUE;
+ if (GTK_WIDGET_VISIBLE(widget))
+ gtk_widget_queue_resize(GTK_WIDGET(cols));
+ break;
+ }
+}
+
+void columns_taborder_last(Columns *cols, GtkWidget *widget)
+{
+ GtkWidget *childw;
+ GList *children;
+
+ g_return_if_fail(cols != NULL);
+ g_return_if_fail(IS_COLUMNS(cols));
+ g_return_if_fail(widget != NULL);
+
+ for (children = cols->taborder;
+ children && (childw = children->data);
+ children = children->next) {
+ if (childw != widget)
+ continue;
+
+ cols->taborder = g_list_remove_link(cols->taborder, children);
+ g_list_free(children);
+ cols->taborder = g_list_append(cols->taborder, widget);
+#if GTK_CHECK_VERSION(2,0,0)
+ gtk_container_set_focus_chain(GTK_CONTAINER(cols), cols->taborder);
+#endif
+ break;
+ }
+}
+
+#if !GTK_CHECK_VERSION(2,0,0)
+/*
+ * Override GtkContainer's focus movement so the user can
+ * explicitly specify the tab order.
+ */
+static gint columns_focus(GtkContainer *container, GtkDirectionType dir)
+{
+ Columns *cols;
+ GList *pos;
+ GtkWidget *focuschild;
+
+ g_return_val_if_fail(container != NULL, FALSE);
+ g_return_val_if_fail(IS_COLUMNS(container), FALSE);
+
+ cols = COLUMNS(container);
+
+ if (!GTK_WIDGET_DRAWABLE(cols) ||
+ !GTK_WIDGET_IS_SENSITIVE(cols))
+ return FALSE;
+
+ if (!GTK_WIDGET_CAN_FOCUS(container) &&
+ (dir == GTK_DIR_TAB_FORWARD || dir == GTK_DIR_TAB_BACKWARD)) {
+
+ focuschild = container->focus_child;
+ gtk_container_set_focus_child(container, NULL);
+
+ if (dir == GTK_DIR_TAB_FORWARD)
+ pos = cols->taborder;
+ else
+ pos = g_list_last(cols->taborder);
+
+ while (pos) {
+ GtkWidget *child = pos->data;
+
+ if (focuschild) {
+ if (focuschild == child) {
+ focuschild = NULL; /* now we can start looking in here */
+ if (GTK_WIDGET_DRAWABLE(child) &&
+ GTK_IS_CONTAINER(child) &&
+ !GTK_WIDGET_HAS_FOCUS(child)) {
+ if (gtk_container_focus(GTK_CONTAINER(child), dir))
+ return TRUE;
+ }
+ }
+ } else if (GTK_WIDGET_DRAWABLE(child)) {
+ if (GTK_IS_CONTAINER(child)) {
+ if (gtk_container_focus(GTK_CONTAINER(child), dir))
+ return TRUE;
+ } else if (GTK_WIDGET_CAN_FOCUS(child)) {
+ gtk_widget_grab_focus(child);
+ return TRUE;
+ }
+ }
+
+ if (dir == GTK_DIR_TAB_FORWARD)
+ pos = pos->next;
+ else
+ pos = pos->prev;
+ }
+
+ return FALSE;
+ } else
+ return columns_inherited_focus(container, dir);
+}
+#endif
+
+/*
+ * Now here comes the interesting bit. The actual layout part is
+ * done in the following two functions:
+ *
+ * columns_size_request() examines the list of widgets held in the
+ * Columns, and returns a requisition stating the absolute minimum
+ * size it can bear to be.
+ *
+ * columns_size_allocate() is given an allocation telling it what
+ * size the whole container is going to be, and it calls
+ * gtk_widget_size_allocate() on all of its (visible) children to
+ * set their size and position relative to the top left of the
+ * container.
+ */
+
+static void columns_size_request(GtkWidget *widget, GtkRequisition *req)
+{
+ Columns *cols;
+ ColumnsChild *child;
+ GList *children;
+ gint i, ncols, colspan, *colypos;
+ const gint *percentages;
+ static const gint onecol[] = { 100 };
+
+ g_return_if_fail(widget != NULL);
+ g_return_if_fail(IS_COLUMNS(widget));
+ g_return_if_fail(req != NULL);
+
+ cols = COLUMNS(widget);
+
+ req->width = 0;
+ req->height = cols->spacing;
+
+ ncols = 1;
+ colypos = g_new(gint, 1);
+ colypos[0] = 0;
+ percentages = onecol;
+
+ for (children = cols->children;
+ children && (child = children->data);
+ children = children->next) {
+ GtkRequisition creq;
+
+ if (!child->widget) {
+ /* Column reconfiguration. */
+ for (i = 1; i < ncols; i++) {
+ if (colypos[0] < colypos[i])
+ colypos[0] = colypos[i];
+ }
+ ncols = child->ncols;
+ percentages = child->percentages;
+ colypos = g_renew(gint, colypos, ncols);
+ for (i = 1; i < ncols; i++)
+ colypos[i] = colypos[0];
+ continue;
+ }
+
+ /* Only take visible widgets into account. */
+ if (!GTK_WIDGET_VISIBLE(child->widget))
+ continue;
+
+ gtk_widget_size_request(child->widget, &creq);
+ colspan = child->colspan ? child->colspan : ncols-child->colstart;
+
+ /*
+ * To compute width: we know that creq.width plus
+ * cols->spacing needs to equal a certain percentage of the
+ * full width of the container. So we work this value out,
+ * figure out how wide the container will need to be to
+ * make that percentage of it equal to that width, and
+ * ensure our returned width is at least that much. Very
+ * simple really.
+ */
+ {
+ int percent, thiswid, fullwid;
+
+ percent = 0;
+ for (i = 0; i < colspan; i++)
+ percent += percentages[child->colstart+i];
+
+ thiswid = creq.width + cols->spacing;
+ /*
+ * Since creq is the _minimum_ size the child needs, we
+ * must ensure that it gets _at least_ that size.
+ * Hence, when scaling thiswid up to fullwid, we must
+ * round up, which means adding percent-1 before
+ * dividing by percent.
+ */
+ fullwid = (thiswid * 100 + percent - 1) / percent;
+
+ /*
+ * The above calculation assumes every widget gets
+ * cols->spacing on the right. So we subtract
+ * cols->spacing here to account for the extra load of
+ * spacing on the right.
+ */
+ if (req->width < fullwid - cols->spacing)
+ req->width = fullwid - cols->spacing;
+ }
+
+ /*
+ * To compute height: the widget's top will be positioned
+ * at the largest y value so far reached in any of the
+ * columns it crosses. Then it will go down by creq.height
+ * plus padding; and the point it reaches at the bottom is
+ * the new y value in all those columns, and minus the
+ * padding it is also a lower bound on our own size
+ * request.
+ */
+ {
+ int topy, boty;
+
+ topy = 0;
+ for (i = 0; i < colspan; i++) {
+ if (topy < colypos[child->colstart+i])
+ topy = colypos[child->colstart+i];
+ }
+ boty = topy + creq.height + cols->spacing;
+ for (i = 0; i < colspan; i++) {
+ colypos[child->colstart+i] = boty;
+ }
+
+ if (req->height < boty - cols->spacing)
+ req->height = boty - cols->spacing;
+ }
+ }
+
+ req->width += 2*GTK_CONTAINER(cols)->border_width;
+ req->height += 2*GTK_CONTAINER(cols)->border_width;
+
+ g_free(colypos);
+}
+
+static void columns_size_allocate(GtkWidget *widget, GtkAllocation *alloc)
+{
+ Columns *cols;
+ ColumnsChild *child;
+ GList *children;
+ gint i, ncols, colspan, border, *colxpos, *colypos;
+ const gint *percentages;
+ static const gint onecol[] = { 100 };
+
+ g_return_if_fail(widget != NULL);
+ g_return_if_fail(IS_COLUMNS(widget));
+ g_return_if_fail(alloc != NULL);
+
+ cols = COLUMNS(widget);
+ widget->allocation = *alloc;
+ border = GTK_CONTAINER(cols)->border_width;
+
+ ncols = 1;
+ percentages = onecol;
+ /* colxpos gives the starting x position of each column.
+ * We supply n+1 of them, so that we can find the RH edge easily.
+ * All ending x positions are expected to be adjusted afterwards by
+ * subtracting the spacing. */
+ colxpos = g_new(gint, 2);
+ colxpos[0] = 0;
+ colxpos[1] = alloc->width - 2*border + cols->spacing;
+ /* As in size_request, colypos is the lowest y reached in each column. */
+ colypos = g_new(gint, 1);
+ colypos[0] = 0;
+
+ for (children = cols->children;
+ children && (child = children->data);
+ children = children->next) {
+ GtkRequisition creq;
+ GtkAllocation call;
+
+ if (!child->widget) {
+ gint percent;
+
+ /* Column reconfiguration. */
+ for (i = 1; i < ncols; i++) {
+ if (colypos[0] < colypos[i])
+ colypos[0] = colypos[i];
+ }
+ ncols = child->ncols;
+ percentages = child->percentages;
+ colypos = g_renew(gint, colypos, ncols);
+ for (i = 1; i < ncols; i++)
+ colypos[i] = colypos[0];
+ colxpos = g_renew(gint, colxpos, ncols + 1);
+ colxpos[0] = 0;
+ percent = 0;
+ for (i = 0; i < ncols; i++) {
+ percent += percentages[i];
+ colxpos[i+1] = (((alloc->width - 2*border) + cols->spacing)
+ * percent / 100);
+ }
+ continue;
+ }
+
+ /* Only take visible widgets into account. */
+ if (!GTK_WIDGET_VISIBLE(child->widget))
+ continue;
+
+ gtk_widget_get_child_requisition(child->widget, &creq);
+ colspan = child->colspan ? child->colspan : ncols-child->colstart;
+
+ /*
+ * Starting x position is cols[colstart].
+ * Ending x position is cols[colstart+colspan] - spacing.
+ *
+ * Unless we're forcing left, in which case the width is
+ * exactly the requisition width.
+ */
+ call.x = alloc->x + border + colxpos[child->colstart];
+ if (child->force_left)
+ call.width = creq.width;
+ else
+ call.width = (colxpos[child->colstart+colspan] -
+ colxpos[child->colstart] - cols->spacing);
+
+ /*
+ * To compute height: the widget's top will be positioned
+ * at the largest y value so far reached in any of the
+ * columns it crosses. Then it will go down by creq.height
+ * plus padding; and the point it reaches at the bottom is
+ * the new y value in all those columns.
+ */
+ {
+ int topy, boty;
+
+ topy = 0;
+ for (i = 0; i < colspan; i++) {
+ if (topy < colypos[child->colstart+i])
+ topy = colypos[child->colstart+i];
+ }
+ call.y = alloc->y + border + topy;
+ call.height = creq.height;
+ boty = topy + creq.height + cols->spacing;
+ for (i = 0; i < colspan; i++) {
+ colypos[child->colstart+i] = boty;
+ }
+ }
+
+ gtk_widget_size_allocate(child->widget, &call);
+ }
+
+ g_free(colxpos);
+ g_free(colypos);
+}
--- /dev/null
+/*
+ * gtkcols.h - header file for a columns-based widget container
+ * capable of supporting the PuTTY portable dialog box layout
+ * mechanism.
+ */
+
+#ifndef COLUMNS_H
+#define COLUMNS_H
+
+#include <gdk/gdk.h>
+#include <gtk/gtkcontainer.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#define TYPE_COLUMNS (columns_get_type())
+#define COLUMNS(obj) (GTK_CHECK_CAST((obj), TYPE_COLUMNS, Columns))
+#define COLUMNS_CLASS(klass) \
+ (GTK_CHECK_CLASS_CAST((klass), TYPE_COLUMNS, ColumnsClass))
+#define IS_COLUMNS(obj) (GTK_CHECK_TYPE((obj), TYPE_COLUMNS))
+#define IS_COLUMNS_CLASS(klass) (GTK_CHECK_CLASS_TYPE((klass), TYPE_COLUMNS))
+
+typedef struct Columns_tag Columns;
+typedef struct ColumnsClass_tag ColumnsClass;
+typedef struct ColumnsChild_tag ColumnsChild;
+
+struct Columns_tag {
+ GtkContainer container;
+ /* private after here */
+ GList *children; /* this holds ColumnsChild structures */
+ GList *taborder; /* this just holds GtkWidgets */
+ gint spacing;
+};
+
+struct ColumnsClass_tag {
+ GtkContainerClass parent_class;
+};
+
+struct ColumnsChild_tag {
+ /* If `widget' is non-NULL, this entry represents an actual widget. */
+ GtkWidget *widget;
+ gint colstart, colspan;
+ gboolean force_left; /* for recalcitrant GtkLabels */
+ /* Otherwise, this entry represents a change in the column setup. */
+ gint ncols;
+ gint *percentages;
+};
+
+GtkType columns_get_type(void);
+GtkWidget *columns_new(gint spacing);
+void columns_set_cols(Columns *cols, gint ncols, const gint *percentages);
+void columns_add(Columns *cols, GtkWidget *child,
+ gint colstart, gint colspan);
+void columns_taborder_last(Columns *cols, GtkWidget *child);
+void columns_force_left_align(Columns *cols, GtkWidget *child);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* COLUMNS_H */
--- /dev/null
+/*
+ * gtkdlg.c - GTK implementation of the PuTTY configuration box.
+ */
+
+#include <assert.h>
+#include <stdarg.h>
+#include <ctype.h>
+#include <time.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+#include <gdk/gdkx.h>
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+
+#include "gtkcols.h"
+#include "gtkfont.h"
+
+#ifdef TESTMODE
+#define PUTTY_DO_GLOBALS /* actually _define_ globals */
+#endif
+
+#include "putty.h"
+#include "storage.h"
+#include "dialog.h"
+#include "tree234.h"
+
+struct Shortcut {
+ GtkWidget *widget;
+ struct uctrl *uc;
+ int action;
+};
+
+struct Shortcuts {
+ struct Shortcut sc[128];
+};
+
+struct uctrl {
+ union control *ctrl;
+ GtkWidget *toplevel;
+ void *privdata;
+ int privdata_needs_free;
+ GtkWidget **buttons; int nbuttons; /* for radio buttons */
+ GtkWidget *entry; /* for editbox, filesel, fontsel */
+ GtkWidget *button; /* for filesel, fontsel */
+#if !GTK_CHECK_VERSION(2,4,0)
+ GtkWidget *list; /* for listbox (in GTK1), combobox (<=GTK2.3) */
+ GtkWidget *menu; /* for optionmenu (==droplist) */
+ GtkWidget *optmenu; /* also for optionmenu */
+#else
+ GtkWidget *combo; /* for combo box (either editable or not) */
+#endif
+#if GTK_CHECK_VERSION(2,0,0)
+ GtkWidget *treeview; /* for listbox (GTK2), droplist+combo (>=2.4) */
+ GtkListStore *listmodel; /* for all types of list box */
+#endif
+ GtkWidget *text; /* for text */
+ GtkWidget *label; /* for dlg_label_change */
+ GtkAdjustment *adj; /* for the scrollbar in a list box */
+ guint entrysig;
+ guint textsig;
+ int nclicks;
+};
+
+struct dlgparam {
+ tree234 *byctrl, *bywidget;
+ void *data;
+ struct { unsigned char r, g, b, ok; } coloursel_result; /* 0-255 */
+ /* `flags' are set to indicate when a GTK signal handler is being called
+ * due to automatic processing and should not flag a user event. */
+ int flags;
+ struct Shortcuts *shortcuts;
+ GtkWidget *window, *cancelbutton;
+ union control *currfocus, *lastfocus;
+#if !GTK_CHECK_VERSION(2,0,0)
+ GtkWidget *currtreeitem, **treeitems;
+ int ntreeitems;
+#endif
+ int retval;
+};
+#define FLAG_UPDATING_COMBO_LIST 1
+#define FLAG_UPDATING_LISTBOX 2
+
+enum { /* values for Shortcut.action */
+ SHORTCUT_EMPTY, /* no shortcut on this key */
+ SHORTCUT_TREE, /* focus a tree item */
+ SHORTCUT_FOCUS, /* focus the supplied widget */
+ SHORTCUT_UCTRL, /* do something sane with uctrl */
+ SHORTCUT_UCTRL_UP, /* uctrl is a draglist, move Up */
+ SHORTCUT_UCTRL_DOWN, /* uctrl is a draglist, move Down */
+};
+
+#if GTK_CHECK_VERSION(2,0,0)
+enum {
+ TREESTORE_PATH,
+ TREESTORE_PARAMS,
+ TREESTORE_NUM
+};
+#endif
+
+/*
+ * Forward references.
+ */
+static gboolean widget_focus(GtkWidget *widget, GdkEventFocus *event,
+ gpointer data);
+static void shortcut_add(struct Shortcuts *scs, GtkWidget *labelw,
+ int chr, int action, void *ptr);
+static void shortcut_highlight(GtkWidget *label, int chr);
+#if !GTK_CHECK_VERSION(2,0,0)
+static gboolean listitem_single_key(GtkWidget *item, GdkEventKey *event,
+ gpointer data);
+static gboolean listitem_multi_key(GtkWidget *item, GdkEventKey *event,
+ gpointer data);
+static gboolean listitem_button_press(GtkWidget *item, GdkEventButton *event,
+ gpointer data);
+static gboolean listitem_button_release(GtkWidget *item, GdkEventButton *event,
+ gpointer data);
+#endif
+#if !GTK_CHECK_VERSION(2,4,0)
+static void menuitem_activate(GtkMenuItem *item, gpointer data);
+#endif
+static void coloursel_ok(GtkButton *button, gpointer data);
+static void coloursel_cancel(GtkButton *button, gpointer data);
+static void window_destroy(GtkWidget *widget, gpointer data);
+int get_listitemheight(GtkWidget *widget);
+
+static int uctrl_cmp_byctrl(void *av, void *bv)
+{
+ struct uctrl *a = (struct uctrl *)av;
+ struct uctrl *b = (struct uctrl *)bv;
+ if (a->ctrl < b->ctrl)
+ return -1;
+ else if (a->ctrl > b->ctrl)
+ return +1;
+ return 0;
+}
+
+static int uctrl_cmp_byctrl_find(void *av, void *bv)
+{
+ union control *a = (union control *)av;
+ struct uctrl *b = (struct uctrl *)bv;
+ if (a < b->ctrl)
+ return -1;
+ else if (a > b->ctrl)
+ return +1;
+ return 0;
+}
+
+static int uctrl_cmp_bywidget(void *av, void *bv)
+{
+ struct uctrl *a = (struct uctrl *)av;
+ struct uctrl *b = (struct uctrl *)bv;
+ if (a->toplevel < b->toplevel)
+ return -1;
+ else if (a->toplevel > b->toplevel)
+ return +1;
+ return 0;
+}
+
+static int uctrl_cmp_bywidget_find(void *av, void *bv)
+{
+ GtkWidget *a = (GtkWidget *)av;
+ struct uctrl *b = (struct uctrl *)bv;
+ if (a < b->toplevel)
+ return -1;
+ else if (a > b->toplevel)
+ return +1;
+ return 0;
+}
+
+static void dlg_init(struct dlgparam *dp)
+{
+ dp->byctrl = newtree234(uctrl_cmp_byctrl);
+ dp->bywidget = newtree234(uctrl_cmp_bywidget);
+ dp->coloursel_result.ok = FALSE;
+ dp->window = dp->cancelbutton = NULL;
+#if !GTK_CHECK_VERSION(2,0,0)
+ dp->treeitems = NULL;
+ dp->currtreeitem = NULL;
+#endif
+ dp->flags = 0;
+ dp->currfocus = NULL;
+}
+
+static void dlg_cleanup(struct dlgparam *dp)
+{
+ struct uctrl *uc;
+
+ freetree234(dp->byctrl); /* doesn't free the uctrls inside */
+ dp->byctrl = NULL;
+ while ( (uc = index234(dp->bywidget, 0)) != NULL) {
+ del234(dp->bywidget, uc);
+ if (uc->privdata_needs_free)
+ sfree(uc->privdata);
+ sfree(uc->buttons);
+ sfree(uc);
+ }
+ freetree234(dp->bywidget);
+ dp->bywidget = NULL;
+#if !GTK_CHECK_VERSION(2,0,0)
+ sfree(dp->treeitems);
+#endif
+}
+
+static void dlg_add_uctrl(struct dlgparam *dp, struct uctrl *uc)
+{
+ add234(dp->byctrl, uc);
+ add234(dp->bywidget, uc);
+}
+
+static struct uctrl *dlg_find_byctrl(struct dlgparam *dp, union control *ctrl)
+{
+ if (!dp->byctrl)
+ return NULL;
+ return find234(dp->byctrl, ctrl, uctrl_cmp_byctrl_find);
+}
+
+static struct uctrl *dlg_find_bywidget(struct dlgparam *dp, GtkWidget *w)
+{
+ struct uctrl *ret = NULL;
+ if (!dp->bywidget)
+ return NULL;
+ do {
+ ret = find234(dp->bywidget, w, uctrl_cmp_bywidget_find);
+ if (ret)
+ return ret;
+ w = w->parent;
+ } while (w);
+ return ret;
+}
+
+void *dlg_get_privdata(union control *ctrl, void *dlg)
+{
+ struct dlgparam *dp = (struct dlgparam *)dlg;
+ struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
+ return uc->privdata;
+}
+
+void dlg_set_privdata(union control *ctrl, void *dlg, void *ptr)
+{
+ struct dlgparam *dp = (struct dlgparam *)dlg;
+ struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
+ uc->privdata = ptr;
+ uc->privdata_needs_free = FALSE;
+}
+
+void *dlg_alloc_privdata(union control *ctrl, void *dlg, size_t size)
+{
+ struct dlgparam *dp = (struct dlgparam *)dlg;
+ struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
+ /*
+ * This is an internal allocation routine, so it's allowed to
+ * use smalloc directly.
+ */
+ uc->privdata = smalloc(size);
+ uc->privdata_needs_free = FALSE;
+ return uc->privdata;
+}
+
+union control *dlg_last_focused(union control *ctrl, void *dlg)
+{
+ struct dlgparam *dp = (struct dlgparam *)dlg;
+ if (dp->currfocus != ctrl)
+ return dp->currfocus;
+ else
+ return dp->lastfocus;
+}
+
+void dlg_radiobutton_set(union control *ctrl, void *dlg, int which)
+{
+ struct dlgparam *dp = (struct dlgparam *)dlg;
+ struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
+ assert(uc->ctrl->generic.type == CTRL_RADIO);
+ assert(uc->buttons != NULL);
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(uc->buttons[which]), TRUE);
+}
+
+int dlg_radiobutton_get(union control *ctrl, void *dlg)
+{
+ struct dlgparam *dp = (struct dlgparam *)dlg;
+ struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
+ int i;
+
+ assert(uc->ctrl->generic.type == CTRL_RADIO);
+ assert(uc->buttons != NULL);
+ for (i = 0; i < uc->nbuttons; i++)
+ if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(uc->buttons[i])))
+ return i;
+ return 0; /* got to return something */
+}
+
+void dlg_checkbox_set(union control *ctrl, void *dlg, int checked)
+{
+ struct dlgparam *dp = (struct dlgparam *)dlg;
+ struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
+ assert(uc->ctrl->generic.type == CTRL_CHECKBOX);
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(uc->toplevel), checked);
+}
+
+int dlg_checkbox_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_CHECKBOX);
+ return gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(uc->toplevel));
+}
+
+void dlg_editbox_set(union control *ctrl, void *dlg, char const *text)
+{
+ struct dlgparam *dp = (struct dlgparam *)dlg;
+ struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
+ GtkWidget *entry;
+ char *tmpstring;
+ assert(uc->ctrl->generic.type == CTRL_EDITBOX);
+
+#if GTK_CHECK_VERSION(2,4,0)
+ if (uc->combo)
+ entry = gtk_bin_get_child(GTK_BIN(uc->combo));
+ else
+#endif
+ entry = uc->entry;
+
+ assert(entry != NULL);
+
+ /*
+ * GTK 2 implements gtk_entry_set_text by means of two separate
+ * operations: first delete the previous text leaving the empty
+ * string, then insert the new text. This causes two calls to
+ * 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.
+ *
+ * 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)
+{
+ 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);
+#else
+ strncpy(buffer,
+ gtk_entry_get_text
+ (GTK_ENTRY(gtk_bin_get_child(GTK_BIN(uc->combo)))),
+ length);
+#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;
+ }
+
+ assert(!"We shouldn't get here");
+}
+
+#if !GTK_CHECK_VERSION(2,4,0)
+static void container_remove_and_destroy(GtkWidget *w, gpointer data)
+{
+ GtkContainer *cont = GTK_CONTAINER(data);
+ /* gtk_container_remove will unref the widget for us; we need not. */
+ gtk_container_remove(cont, w);
+}
+#endif
+
+/* The `listbox' functions can also apply to combo boxes. */
+void dlg_listbox_clear(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 ||
+ uc->ctrl->generic.type == CTRL_LISTBOX);
+
+#if !GTK_CHECK_VERSION(2,4,0)
+ if (uc->menu) {
+ gtk_container_foreach(GTK_CONTAINER(uc->menu),
+ container_remove_and_destroy,
+ GTK_CONTAINER(uc->menu));
+ return;
+ }
+ if (uc->list) {
+ gtk_list_clear_items(GTK_LIST(uc->list), 0, -1);
+ return;
+ }
+#endif
+#if GTK_CHECK_VERSION(2,0,0)
+ if (uc->listmodel) {
+ gtk_list_store_clear(uc->listmodel);
+ return;
+ }
+#endif
+ assert(!"We shouldn't get here");
+}
+
+void dlg_listbox_del(union control *ctrl, void *dlg, int index)
+{
+ struct dlgparam *dp = (struct dlgparam *)dlg;
+ struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
+
+ assert(uc->ctrl->generic.type == CTRL_EDITBOX ||
+ uc->ctrl->generic.type == CTRL_LISTBOX);
+
+#if !GTK_CHECK_VERSION(2,4,0)
+ if (uc->menu) {
+ gtk_container_remove
+ (GTK_CONTAINER(uc->menu),
+ g_list_nth_data(GTK_MENU_SHELL(uc->menu)->children, index));
+ return;
+ }
+ if (uc->list) {
+ gtk_list_clear_items(GTK_LIST(uc->list), index, index+1);
+ return;
+ }
+#endif
+#if GTK_CHECK_VERSION(2,0,0)
+ if (uc->listmodel) {
+ GtkTreePath *path;
+ GtkTreeIter iter;
+ assert(uc->listmodel != NULL);
+ path = gtk_tree_path_new_from_indices(index, -1);
+ gtk_tree_model_get_iter(GTK_TREE_MODEL(uc->listmodel), &iter, path);
+ gtk_list_store_remove(uc->listmodel, &iter);
+ gtk_tree_path_free(path);
+ return;
+ }
+#endif
+ assert(!"We shouldn't get here");
+}
+
+void dlg_listbox_add(union control *ctrl, void *dlg, char const *text)
+{
+ dlg_listbox_addwithid(ctrl, dlg, text, 0);
+}
+
+/*
+ * Each listbox entry may have a numeric id associated with it.
+ * Note that some front ends only permit a string to be stored at
+ * each position, which means that _if_ you put two identical
+ * strings in any listbox then you MUST not assign them different
+ * IDs and expect to get meaningful results back.
+ */
+void dlg_listbox_addwithid(union control *ctrl, void *dlg,
+ char const *text, int id)
+{
+ struct dlgparam *dp = (struct dlgparam *)dlg;
+ struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
+
+ assert(uc->ctrl->generic.type == CTRL_EDITBOX ||
+ uc->ctrl->generic.type == CTRL_LISTBOX);
+
+ /*
+ * This routine is long and complicated in both GTK 1 and 2,
+ * and completely different. Sigh.
+ */
+ dp->flags |= FLAG_UPDATING_COMBO_LIST;
+
+#if !GTK_CHECK_VERSION(2,4,0)
+ if (uc->menu) {
+ /*
+ * List item in a drop-down (but non-combo) list. Tabs are
+ * ignored; we just provide a standard menu item with the
+ * text.
+ */
+ GtkWidget *menuitem = gtk_menu_item_new_with_label(text);
+
+ gtk_container_add(GTK_CONTAINER(uc->menu), menuitem);
+ gtk_widget_show(menuitem);
+
+ gtk_object_set_data(GTK_OBJECT(menuitem), "user-data",
+ GINT_TO_POINTER(id));
+ gtk_signal_connect(GTK_OBJECT(menuitem), "activate",
+ GTK_SIGNAL_FUNC(menuitem_activate), dp);
+ goto done;
+ }
+ if (uc->list && uc->entry) {
+ /*
+ * List item in a combo-box list, which means the sensible
+ * thing to do is make it a perfectly normal label. Hence
+ * tabs are disregarded.
+ */
+ GtkWidget *listitem = gtk_list_item_new_with_label(text);
+
+ gtk_container_add(GTK_CONTAINER(uc->list), listitem);
+ gtk_widget_show(listitem);
+
+ gtk_object_set_data(GTK_OBJECT(listitem), "user-data",
+ GINT_TO_POINTER(id));
+ goto done;
+ }
+#endif
+#if !GTK_CHECK_VERSION(2,0,0)
+ if (uc->list) {
+ /*
+ * List item in a non-combo-box list box. We make all of
+ * these Columns containing GtkLabels. This allows us to do
+ * the nasty force_left hack irrespective of whether there
+ * are tabs in the thing.
+ */
+ GtkWidget *listitem = gtk_list_item_new();
+ GtkWidget *cols = columns_new(10);
+ gint *percents;
+ int i, ncols;
+
+ /* Count the tabs in the text, and hence determine # of columns. */
+ ncols = 1;
+ for (i = 0; text[i]; i++)
+ if (text[i] == '\t')
+ ncols++;
+
+ assert(ncols <=
+ (uc->ctrl->listbox.ncols ? uc->ctrl->listbox.ncols : 1));
+ percents = snewn(ncols, gint);
+ percents[ncols-1] = 100;
+ for (i = 0; i < ncols-1; i++) {
+ percents[i] = uc->ctrl->listbox.percentages[i];
+ percents[ncols-1] -= percents[i];
+ }
+ columns_set_cols(COLUMNS(cols), ncols, percents);
+ sfree(percents);
+
+ for (i = 0; i < ncols; i++) {
+ int len = strcspn(text, "\t");
+ char *dup = dupprintf("%.*s", len, text);
+ GtkWidget *label;
+
+ text += len;
+ if (*text) text++;
+ label = gtk_label_new(dup);
+ sfree(dup);
+
+ columns_add(COLUMNS(cols), label, i, 1);
+ columns_force_left_align(COLUMNS(cols), label);
+ gtk_widget_show(label);
+ }
+ gtk_container_add(GTK_CONTAINER(listitem), cols);
+ gtk_widget_show(cols);
+ gtk_container_add(GTK_CONTAINER(uc->list), listitem);
+ gtk_widget_show(listitem);
+
+ if (ctrl->listbox.multisel) {
+ gtk_signal_connect(GTK_OBJECT(listitem), "key_press_event",
+ GTK_SIGNAL_FUNC(listitem_multi_key), uc->adj);
+ } else {
+ gtk_signal_connect(GTK_OBJECT(listitem), "key_press_event",
+ GTK_SIGNAL_FUNC(listitem_single_key), uc->adj);
+ }
+ gtk_signal_connect(GTK_OBJECT(listitem), "focus_in_event",
+ GTK_SIGNAL_FUNC(widget_focus), dp);
+ gtk_signal_connect(GTK_OBJECT(listitem), "button_press_event",
+ GTK_SIGNAL_FUNC(listitem_button_press), dp);
+ gtk_signal_connect(GTK_OBJECT(listitem), "button_release_event",
+ GTK_SIGNAL_FUNC(listitem_button_release), dp);
+ gtk_object_set_data(GTK_OBJECT(listitem), "user-data",
+ GINT_TO_POINTER(id));
+ goto done;
+ }
+#else
+ if (uc->listmodel) {
+ GtkTreeIter iter;
+ int i, cols;
+
+ dp->flags |= FLAG_UPDATING_LISTBOX;/* inhibit drag-list update */
+ gtk_list_store_append(uc->listmodel, &iter);
+ dp->flags &= ~FLAG_UPDATING_LISTBOX;
+ gtk_list_store_set(uc->listmodel, &iter, 0, id, -1);
+
+ /*
+ * Now go through text and divide it into columns at the tabs,
+ * as necessary.
+ */
+ cols = (uc->ctrl->generic.type == CTRL_LISTBOX ? ctrl->listbox.ncols : 1);
+ cols = cols ? cols : 1;
+ for (i = 0; i < cols; i++) {
+ int collen = strcspn(text, "\t");
+ char *tmpstr = snewn(collen+1, char);
+ memcpy(tmpstr, text, collen);
+ tmpstr[collen] = '\0';
+ gtk_list_store_set(uc->listmodel, &iter, i+1, tmpstr, -1);
+ sfree(tmpstr);
+ text += collen;
+ if (*text) text++;
+ }
+ goto done;
+ }
+#endif
+ assert(!"We shouldn't get here");
+ done:
+ dp->flags &= ~FLAG_UPDATING_COMBO_LIST;
+}
+
+int dlg_listbox_getid(union control *ctrl, void *dlg, int index)
+{
+ struct dlgparam *dp = (struct dlgparam *)dlg;
+ struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
+
+ assert(uc->ctrl->generic.type == CTRL_EDITBOX ||
+ uc->ctrl->generic.type == CTRL_LISTBOX);
+
+#if !GTK_CHECK_VERSION(2,4,0)
+ if (uc->menu || uc->list) {
+ GList *children;
+ GtkObject *item;
+
+ children = gtk_container_children(GTK_CONTAINER(uc->menu ? uc->menu :
+ uc->list));
+ item = GTK_OBJECT(g_list_nth_data(children, index));
+ g_list_free(children);
+
+ return GPOINTER_TO_INT(gtk_object_get_data(GTK_OBJECT(item),
+ "user-data"));
+ }
+#endif
+#if GTK_CHECK_VERSION(2,0,0)
+ if (uc->listmodel) {
+ GtkTreePath *path;
+ GtkTreeIter iter;
+ int ret;
+
+ path = gtk_tree_path_new_from_indices(index, -1);
+ gtk_tree_model_get_iter(GTK_TREE_MODEL(uc->listmodel), &iter, path);
+ gtk_tree_model_get(GTK_TREE_MODEL(uc->listmodel), &iter, 0, &ret, -1);
+ gtk_tree_path_free(path);
+
+ return ret;
+ }
+#endif
+ assert(!"We shouldn't get here");
+ return -1; /* placate dataflow analysis */
+}
+
+/* dlg_listbox_index returns <0 if no single element is selected. */
+int dlg_listbox_index(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 ||
+ uc->ctrl->generic.type == CTRL_LISTBOX);
+
+#if !GTK_CHECK_VERSION(2,4,0)
+ if (uc->menu || uc->list) {
+ GList *children;
+ GtkWidget *item, *activeitem;
+ int i;
+ int selected = -1;
+
+ if (uc->menu)
+ activeitem = gtk_menu_get_active(GTK_MENU(uc->menu));
+ else
+ activeitem = NULL; /* unnecessarily placate gcc */
+
+ children = gtk_container_children(GTK_CONTAINER(uc->menu ? uc->menu :
+ uc->list));
+ for (i = 0; children!=NULL && (item = GTK_WIDGET(children->data))!=NULL;
+ i++, children = children->next) {
+ if (uc->menu ? activeitem == item :
+ GTK_WIDGET_STATE(item) == GTK_STATE_SELECTED) {
+ if (selected == -1)
+ selected = i;
+ else
+ selected = -2;
+ }
+ }
+ g_list_free(children);
+ return selected < 0 ? -1 : selected;
+ }
+#else
+ if (uc->combo) {
+ /*
+ * This API function already does the right thing in the
+ * case of no current selection.
+ */
+ return gtk_combo_box_get_active(GTK_COMBO_BOX(uc->combo));
+ }
+#endif
+#if GTK_CHECK_VERSION(2,0,0)
+ if (uc->treeview) {
+ GtkTreeSelection *treesel;
+ GtkTreePath *path;
+ GtkTreeModel *model;
+ GList *sellist;
+ gint *indices;
+ int ret;
+
+ assert(uc->treeview != NULL);
+ treesel = gtk_tree_view_get_selection(GTK_TREE_VIEW(uc->treeview));
+
+ if (gtk_tree_selection_count_selected_rows(treesel) != 1)
+ return -1;
+
+ sellist = gtk_tree_selection_get_selected_rows(treesel, &model);
+
+ assert(sellist && sellist->data);
+ path = sellist->data;
+
+ if (gtk_tree_path_get_depth(path) != 1) {
+ ret = -1;
+ } else {
+ indices = gtk_tree_path_get_indices(path);
+ if (!indices) {
+ ret = -1;
+ } else {
+ ret = indices[0];
+ }
+ }
+
+ g_list_foreach(sellist, (GFunc)gtk_tree_path_free, NULL);
+ g_list_free(sellist);
+
+ return ret;
+ }
+#endif
+ assert(!"We shouldn't get here");
+ return -1; /* placate dataflow analysis */
+}
+
+int dlg_listbox_issel(union control *ctrl, void *dlg, int index)
+{
+ struct dlgparam *dp = (struct dlgparam *)dlg;
+ struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
+
+ assert(uc->ctrl->generic.type == CTRL_EDITBOX ||
+ uc->ctrl->generic.type == CTRL_LISTBOX);
+
+#if !GTK_CHECK_VERSION(2,4,0)
+ if (uc->menu || uc->list) {
+ GList *children;
+ GtkWidget *item, *activeitem;
+
+ assert(uc->ctrl->generic.type == CTRL_EDITBOX ||
+ uc->ctrl->generic.type == CTRL_LISTBOX);
+ assert(uc->menu != NULL || uc->list != NULL);
+
+ children = gtk_container_children(GTK_CONTAINER(uc->menu ? uc->menu :
+ uc->list));
+ item = GTK_WIDGET(g_list_nth_data(children, index));
+ g_list_free(children);
+
+ if (uc->menu) {
+ activeitem = gtk_menu_get_active(GTK_MENU(uc->menu));
+ return item == activeitem;
+ } else {
+ return GTK_WIDGET_STATE(item) == GTK_STATE_SELECTED;
+ }
+ }
+#else
+ if (uc->combo) {
+ /*
+ * This API function already does the right thing in the
+ * case of no current selection.
+ */
+ return gtk_combo_box_get_active(GTK_COMBO_BOX(uc->combo)) == index;
+ }
+#endif
+#if GTK_CHECK_VERSION(2,0,0)
+ if (uc->treeview) {
+ GtkTreeSelection *treesel;
+ GtkTreePath *path;
+ int ret;
+
+ assert(uc->treeview != NULL);
+ treesel = gtk_tree_view_get_selection(GTK_TREE_VIEW(uc->treeview));
+
+ path = gtk_tree_path_new_from_indices(index, -1);
+ ret = gtk_tree_selection_path_is_selected(treesel, path);
+ gtk_tree_path_free(path);
+
+ return ret;
+ }
+#endif
+ assert(!"We shouldn't get here");
+ return -1; /* placate dataflow analysis */
+}
+
+void dlg_listbox_select(union control *ctrl, void *dlg, int index)
+{
+ struct dlgparam *dp = (struct dlgparam *)dlg;
+ struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
+
+ assert(uc->ctrl->generic.type == CTRL_EDITBOX ||
+ uc->ctrl->generic.type == CTRL_LISTBOX);
+
+#if !GTK_CHECK_VERSION(2,4,0)
+ if (uc->optmenu) {
+ gtk_option_menu_set_history(GTK_OPTION_MENU(uc->optmenu), index);
+ return;
+ }
+ if (uc->list) {
+ int nitems;
+ GList *items;
+ gdouble newtop, newbot;
+
+ gtk_list_select_item(GTK_LIST(uc->list), index);
+
+ /*
+ * Scroll the list box if necessary to ensure the newly
+ * selected item is visible.
+ */
+ items = gtk_container_children(GTK_CONTAINER(uc->list));
+ nitems = g_list_length(items);
+ if (nitems > 0) {
+ int modified = FALSE;
+ g_list_free(items);
+ newtop = uc->adj->lower +
+ (uc->adj->upper - uc->adj->lower) * index / nitems;
+ newbot = uc->adj->lower +
+ (uc->adj->upper - uc->adj->lower) * (index+1) / nitems;
+ if (uc->adj->value > newtop) {
+ modified = TRUE;
+ uc->adj->value = newtop;
+ } else if (uc->adj->value < newbot - uc->adj->page_size) {
+ modified = TRUE;
+ uc->adj->value = newbot - uc->adj->page_size;
+ }
+ if (modified)
+ gtk_adjustment_value_changed(uc->adj);
+ }
+ return;
+ }
+#else
+ if (uc->combo) {
+ gtk_combo_box_set_active(GTK_COMBO_BOX(uc->combo), index);
+ return;
+ }
+#endif
+#if GTK_CHECK_VERSION(2,0,0)
+ if (uc->treeview) {
+ GtkTreeSelection *treesel;
+ GtkTreePath *path;
+
+ treesel = gtk_tree_view_get_selection(GTK_TREE_VIEW(uc->treeview));
+
+ path = gtk_tree_path_new_from_indices(index, -1);
+ gtk_tree_selection_select_path(treesel, path);
+ gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(uc->treeview),
+ path, NULL, FALSE, 0.0, 0.0);
+ gtk_tree_path_free(path);
+ return;
+ }
+#endif
+ assert(!"We shouldn't get here");
+}
+
+void dlg_text_set(union control *ctrl, void *dlg, char const *text)
+{
+ struct dlgparam *dp = (struct dlgparam *)dlg;
+ struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
+
+ assert(uc->ctrl->generic.type == CTRL_TEXT);
+ assert(uc->text != NULL);
+
+ gtk_label_set_text(GTK_LABEL(uc->text), text);
+}
+
+void dlg_label_change(union control *ctrl, void *dlg, char const *text)
+{
+ struct dlgparam *dp = (struct dlgparam *)dlg;
+ struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
+
+ switch (uc->ctrl->generic.type) {
+ case CTRL_BUTTON:
+ gtk_label_set_text(GTK_LABEL(uc->toplevel), text);
+ shortcut_highlight(uc->toplevel, ctrl->button.shortcut);
+ break;
+ case CTRL_CHECKBOX:
+ gtk_label_set_text(GTK_LABEL(uc->toplevel), text);
+ shortcut_highlight(uc->toplevel, ctrl->checkbox.shortcut);
+ break;
+ case CTRL_RADIO:
+ gtk_label_set_text(GTK_LABEL(uc->label), text);
+ shortcut_highlight(uc->label, ctrl->radio.shortcut);
+ break;
+ case CTRL_EDITBOX:
+ gtk_label_set_text(GTK_LABEL(uc->label), text);
+ shortcut_highlight(uc->label, ctrl->editbox.shortcut);
+ break;
+ case CTRL_FILESELECT:
+ gtk_label_set_text(GTK_LABEL(uc->label), text);
+ shortcut_highlight(uc->label, ctrl->fileselect.shortcut);
+ break;
+ case CTRL_FONTSELECT:
+ gtk_label_set_text(GTK_LABEL(uc->label), text);
+ shortcut_highlight(uc->label, ctrl->fontselect.shortcut);
+ break;
+ case CTRL_LISTBOX:
+ gtk_label_set_text(GTK_LABEL(uc->label), text);
+ shortcut_highlight(uc->label, ctrl->listbox.shortcut);
+ break;
+ default:
+ 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';
+}
+
+/*
+ * 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.
+ */
+void dlg_update_start(union control *ctrl, void *dlg)
+{
+ /*
+ * Apparently we can't do this at all in GTK. GtkCList supports
+ * freeze and thaw, but not GtkList. Bah.
+ */
+}
+
+void dlg_update_done(union control *ctrl, void *dlg)
+{
+ /*
+ * Apparently we can't do this at all in GTK. GtkCList supports
+ * freeze and thaw, but not GtkList. Bah.
+ */
+}
+
+void dlg_set_focus(union control *ctrl, void *dlg)
+{
+ struct dlgparam *dp = (struct dlgparam *)dlg;
+ struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
+
+ switch (ctrl->generic.type) {
+ case CTRL_CHECKBOX:
+ case CTRL_BUTTON:
+ /* Check boxes and buttons get the focus _and_ get toggled. */
+ gtk_widget_grab_focus(uc->toplevel);
+ break;
+ case CTRL_FILESELECT:
+ case CTRL_FONTSELECT:
+ case CTRL_EDITBOX:
+ if (uc->entry) {
+ /* Anything containing an edit box gets that focused. */
+ gtk_widget_grab_focus(uc->entry);
+ }
+#if GTK_CHECK_VERSION(2,4,0)
+ else if (uc->combo) {
+ /* Failing that, there'll be a combo box. */
+ gtk_widget_grab_focus(uc->combo);
+ }
+#endif
+ break;
+ case CTRL_RADIO:
+ /*
+ * Radio buttons: we find the currently selected button and
+ * focus it.
+ */
+ {
+ int i;
+ for (i = 0; i < ctrl->radio.nbuttons; i++)
+ if (gtk_toggle_button_get_active
+ (GTK_TOGGLE_BUTTON(uc->buttons[i]))) {
+ gtk_widget_grab_focus(uc->buttons[i]);
+ }
+ }
+ break;
+ case CTRL_LISTBOX:
+#if !GTK_CHECK_VERSION(2,4,0)
+ if (uc->optmenu) {
+ gtk_widget_grab_focus(uc->optmenu);
+ break;
+ }
+#else
+ if (uc->combo) {
+ gtk_widget_grab_focus(uc->combo);
+ break;
+ }
+#endif
+#if !GTK_CHECK_VERSION(2,0,0)
+ if (uc->list) {
+ /*
+ * For GTK-1 style list boxes, we tell it to focus one
+ * of its children, which appears to do the Right
+ * Thing.
+ */
+ gtk_container_focus(GTK_CONTAINER(uc->list), GTK_DIR_TAB_FORWARD);
+ break;
+ }
+#else
+ if (uc->treeview) {
+ gtk_widget_grab_focus(uc->treeview);
+ break;
+ }
+#endif
+ assert(!"We shouldn't get here");
+ break;
+ }
+}
+
+/*
+ * During event processing, you might well want to give an error
+ * indication to the user. dlg_beep() is a quick and easy generic
+ * error; dlg_error() puts up a message-box or equivalent.
+ */
+void dlg_beep(void *dlg)
+{
+ gdk_beep();
+}
+
+static void errmsg_button_clicked(GtkButton *button, gpointer data)
+{
+ gtk_widget_destroy(GTK_WIDGET(data));
+}
+
+static void set_transient_window_pos(GtkWidget *parent, GtkWidget *child)
+{
+ gint x, y, w, h, dx, dy;
+ GtkRequisition req;
+ gtk_window_set_position(GTK_WINDOW(child), GTK_WIN_POS_NONE);
+ gtk_widget_size_request(GTK_WIDGET(child), &req);
+
+ gdk_window_get_origin(GTK_WIDGET(parent)->window, &x, &y);
+ gdk_window_get_size(GTK_WIDGET(parent)->window, &w, &h);
+
+ /*
+ * One corner of the transient will be offset inwards, by 1/4
+ * of the parent window's size, from the corresponding corner
+ * of the parent window. The corner will be chosen so as to
+ * place the transient closer to the centre of the screen; this
+ * should avoid transients going off the edge of the screen on
+ * a regular basis.
+ */
+ if (x + w/2 < gdk_screen_width() / 2)
+ dx = x + w/4; /* work from left edges */
+ else
+ dx = x + 3*w/4 - req.width; /* work from right edges */
+ if (y + h/2 < gdk_screen_height() / 2)
+ dy = y + h/4; /* work from top edges */
+ else
+ dy = y + 3*h/4 - req.height; /* work from bottom edges */
+ gtk_widget_set_uposition(GTK_WIDGET(child), dx, dy);
+}
+
+void dlg_error_msg(void *dlg, char *msg)
+{
+ struct dlgparam *dp = (struct dlgparam *)dlg;
+ GtkWidget *window, *hbox, *text, *ok;
+
+ window = gtk_dialog_new();
+ text = gtk_label_new(msg);
+ gtk_misc_set_alignment(GTK_MISC(text), 0.0, 0.0);
+ hbox = gtk_hbox_new(FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(hbox), text, FALSE, FALSE, 20);
+ gtk_box_pack_start(GTK_BOX(GTK_DIALOG(window)->vbox),
+ hbox, FALSE, FALSE, 20);
+ gtk_widget_show(text);
+ gtk_widget_show(hbox);
+ gtk_window_set_title(GTK_WINDOW(window), "Error");
+ gtk_label_set_line_wrap(GTK_LABEL(text), TRUE);
+ ok = gtk_button_new_with_label("OK");
+ gtk_box_pack_end(GTK_BOX(GTK_DIALOG(window)->action_area),
+ ok, FALSE, FALSE, 0);
+ gtk_widget_show(ok);
+ GTK_WIDGET_SET_FLAGS(ok, GTK_CAN_DEFAULT);
+ gtk_window_set_default(GTK_WINDOW(window), ok);
+ gtk_signal_connect(GTK_OBJECT(ok), "clicked",
+ GTK_SIGNAL_FUNC(errmsg_button_clicked), window);
+ gtk_signal_connect(GTK_OBJECT(window), "destroy",
+ GTK_SIGNAL_FUNC(window_destroy), NULL);
+ gtk_window_set_modal(GTK_WINDOW(window), TRUE);
+ gtk_window_set_transient_for(GTK_WINDOW(window), GTK_WINDOW(dp->window));
+ set_transient_window_pos(dp->window, window);
+ gtk_widget_show(window);
+ gtk_main();
+}
+
+/*
+ * This function signals to the front end that the dialog's
+ * processing is completed, and passes an integer value (typically
+ * a success status).
+ */
+void dlg_end(void *dlg, int value)
+{
+ struct dlgparam *dp = (struct dlgparam *)dlg;
+ dp->retval = value;
+ gtk_widget_destroy(dp->window);
+}
+
+void dlg_refresh(union control *ctrl, void *dlg)
+{
+ struct dlgparam *dp = (struct dlgparam *)dlg;
+ struct uctrl *uc;
+
+ if (ctrl) {
+ if (ctrl->generic.handler != NULL)
+ ctrl->generic.handler(ctrl, dp, dp->data, EVENT_REFRESH);
+ } else {
+ int i;
+
+ for (i = 0; (uc = index234(dp->byctrl, i)) != NULL; i++) {
+ assert(uc->ctrl != NULL);
+ if (uc->ctrl->generic.handler != NULL)
+ uc->ctrl->generic.handler(uc->ctrl, dp,
+ dp->data, EVENT_REFRESH);
+ }
+ }
+}
+
+void dlg_coloursel_start(union control *ctrl, void *dlg, int r, int g, int b)
+{
+ struct dlgparam *dp = (struct dlgparam *)dlg;
+ struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
+ gdouble cvals[4];
+
+ GtkWidget *coloursel =
+ gtk_color_selection_dialog_new("Select a colour");
+ GtkColorSelectionDialog *ccs = GTK_COLOR_SELECTION_DIALOG(coloursel);
+
+ dp->coloursel_result.ok = FALSE;
+
+ gtk_window_set_modal(GTK_WINDOW(coloursel), TRUE);
+#if GTK_CHECK_VERSION(2,0,0)
+ gtk_color_selection_set_has_opacity_control(GTK_COLOR_SELECTION(ccs->colorsel), FALSE);
+#else
+ gtk_color_selection_set_opacity(GTK_COLOR_SELECTION(ccs->colorsel), FALSE);
+#endif
+ cvals[0] = r / 255.0;
+ cvals[1] = g / 255.0;
+ cvals[2] = b / 255.0;
+ cvals[3] = 1.0; /* fully opaque! */
+ gtk_color_selection_set_color(GTK_COLOR_SELECTION(ccs->colorsel), cvals);
+
+ gtk_object_set_data(GTK_OBJECT(ccs->ok_button), "user-data",
+ (gpointer)coloursel);
+ gtk_object_set_data(GTK_OBJECT(ccs->cancel_button), "user-data",
+ (gpointer)coloursel);
+ gtk_object_set_data(GTK_OBJECT(coloursel), "user-data", (gpointer)uc);
+ gtk_signal_connect(GTK_OBJECT(ccs->ok_button), "clicked",
+ GTK_SIGNAL_FUNC(coloursel_ok), (gpointer)dp);
+ gtk_signal_connect(GTK_OBJECT(ccs->cancel_button), "clicked",
+ GTK_SIGNAL_FUNC(coloursel_cancel), (gpointer)dp);
+ gtk_signal_connect_object(GTK_OBJECT(ccs->ok_button), "clicked",
+ GTK_SIGNAL_FUNC(gtk_widget_destroy),
+ (gpointer)coloursel);
+ gtk_signal_connect_object(GTK_OBJECT(ccs->cancel_button), "clicked",
+ GTK_SIGNAL_FUNC(gtk_widget_destroy),
+ (gpointer)coloursel);
+ gtk_widget_show(coloursel);
+}
+
+int dlg_coloursel_results(union control *ctrl, void *dlg,
+ int *r, int *g, int *b)
+{
+ struct dlgparam *dp = (struct dlgparam *)dlg;
+ if (dp->coloursel_result.ok) {
+ *r = dp->coloursel_result.r;
+ *g = dp->coloursel_result.g;
+ *b = dp->coloursel_result.b;
+ return 1;
+ } else
+ return 0;
+}
+
+/* ----------------------------------------------------------------------
+ * Signal handlers while the dialog box is active.
+ */
+
+static gboolean widget_focus(GtkWidget *widget, GdkEventFocus *event,
+ gpointer data)
+{
+ struct dlgparam *dp = (struct dlgparam *)data;
+ struct uctrl *uc = dlg_find_bywidget(dp, widget);
+ union control *focus;
+
+ if (uc && uc->ctrl)
+ focus = uc->ctrl;
+ else
+ focus = NULL;
+
+ if (focus != dp->currfocus) {
+ dp->lastfocus = dp->currfocus;
+ dp->currfocus = focus;
+ }
+
+ return FALSE;
+}
+
+static void button_clicked(GtkButton *button, gpointer data)
+{
+ struct dlgparam *dp = (struct dlgparam *)data;
+ struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(button));
+ uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_ACTION);
+}
+
+static void button_toggled(GtkToggleButton *tb, gpointer data)
+{
+ struct dlgparam *dp = (struct dlgparam *)data;
+ struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(tb));
+ uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_VALCHANGE);
+}
+
+static gboolean editbox_key(GtkWidget *widget, GdkEventKey *event,
+ gpointer data)
+{
+ /*
+ * GtkEntry has a nasty habit of eating the Return key, which
+ * is unhelpful since it doesn't actually _do_ anything with it
+ * (it calls gtk_widget_activate, but our edit boxes never need
+ * activating). So I catch Return before GtkEntry sees it, and
+ * pass it straight on to the parent widget. Effect: hitting
+ * Return in an edit box will now activate the default button
+ * in the dialog just like it will everywhere else.
+ */
+ if (event->keyval == GDK_Return && widget->parent != NULL) {
+ gboolean return_val;
+ gtk_signal_emit_stop_by_name(GTK_OBJECT(widget), "key_press_event");
+ gtk_signal_emit_by_name(GTK_OBJECT(widget->parent), "key_press_event",
+ event, &return_val);
+ return return_val;
+ }
+ return FALSE;
+}
+
+static void editbox_changed(GtkEditable *ed, gpointer data)
+{
+ struct dlgparam *dp = (struct dlgparam *)data;
+ if (!(dp->flags & FLAG_UPDATING_COMBO_LIST)) {
+ struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(ed));
+ uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_VALCHANGE);
+ }
+}
+
+static gboolean editbox_lostfocus(GtkWidget *ed, GdkEventFocus *event,
+ gpointer data)
+{
+ struct dlgparam *dp = (struct dlgparam *)data;
+ struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(ed));
+ uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_REFRESH);
+ return FALSE;
+}
+
+#if !GTK_CHECK_VERSION(2,0,0)
+
+/*
+ * GTK 1 list box event handlers.
+ */
+
+static gboolean listitem_key(GtkWidget *item, GdkEventKey *event,
+ gpointer data, int multiple)
+{
+ GtkAdjustment *adj = GTK_ADJUSTMENT(data);
+
+ if (event->keyval == GDK_Up || event->keyval == GDK_KP_Up ||
+ event->keyval == GDK_Down || event->keyval == GDK_KP_Down ||
+ event->keyval == GDK_Page_Up || event->keyval == GDK_KP_Page_Up ||
+ event->keyval == GDK_Page_Down || event->keyval == GDK_KP_Page_Down) {
+ /*
+ * Up, Down, PgUp or PgDn have been pressed on a ListItem
+ * in a list box. So, if the list box is single-selection:
+ *
+ * - if the list item in question isn't already selected,
+ * we simply select it.
+ * - otherwise, we find the next one (or next
+ * however-far-away) in whichever direction we're going,
+ * and select that.
+ * + in this case, we must also fiddle with the
+ * scrollbar to ensure the newly selected item is
+ * actually visible.
+ *
+ * If it's multiple-selection, we do all of the above
+ * except actually selecting anything, so we move the focus
+ * and fiddle the scrollbar to follow it.
+ */
+ GtkWidget *list = item->parent;
+
+ gtk_signal_emit_stop_by_name(GTK_OBJECT(item), "key_press_event");
+
+ if (!multiple &&
+ GTK_WIDGET_STATE(item) != GTK_STATE_SELECTED) {
+ gtk_list_select_child(GTK_LIST(list), item);
+ } else {
+ int direction =
+ (event->keyval==GDK_Up || event->keyval==GDK_KP_Up ||
+ event->keyval==GDK_Page_Up || event->keyval==GDK_KP_Page_Up)
+ ? -1 : +1;
+ int step =
+ (event->keyval==GDK_Page_Down ||
+ event->keyval==GDK_KP_Page_Down ||
+ event->keyval==GDK_Page_Up || event->keyval==GDK_KP_Page_Up)
+ ? 2 : 1;
+ int i, n;
+ GList *children, *chead;
+
+ chead = children = gtk_container_children(GTK_CONTAINER(list));
+
+ n = g_list_length(children);
+
+ if (step == 2) {
+ /*
+ * Figure out how many list items to a screenful,
+ * and adjust the step appropriately.
+ */
+ step = 0.5 + adj->page_size * n / (adj->upper - adj->lower);
+ step--; /* go by one less than that */
+ }
+
+ i = 0;
+ while (children != NULL) {
+ if (item == children->data)
+ break;
+ children = children->next;
+ i++;
+ }
+
+ while (step > 0) {
+ if (direction < 0 && i > 0)
+ children = children->prev, i--;
+ else if (direction > 0 && i < n-1)
+ children = children->next, i++;
+ step--;
+ }
+
+ if (children && children->data) {
+ if (!multiple)
+ gtk_list_select_child(GTK_LIST(list),
+ GTK_WIDGET(children->data));
+ gtk_widget_grab_focus(GTK_WIDGET(children->data));
+ gtk_adjustment_clamp_page
+ (adj,
+ adj->lower + (adj->upper-adj->lower) * i / n,
+ adj->lower + (adj->upper-adj->lower) * (i+1) / n);
+ }
+
+ g_list_free(chead);
+ }
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean listitem_single_key(GtkWidget *item, GdkEventKey *event,
+ gpointer data)
+{
+ return listitem_key(item, event, data, FALSE);
+}
+
+static gboolean listitem_multi_key(GtkWidget *item, GdkEventKey *event,
+ gpointer data)
+{
+ return listitem_key(item, event, data, TRUE);
+}
+
+static gboolean listitem_button_press(GtkWidget *item, GdkEventButton *event,
+ gpointer data)
+{
+ struct dlgparam *dp = (struct dlgparam *)data;
+ struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(item));
+ switch (event->type) {
+ default:
+ case GDK_BUTTON_PRESS: uc->nclicks = 1; break;
+ case GDK_2BUTTON_PRESS: uc->nclicks = 2; break;
+ case GDK_3BUTTON_PRESS: uc->nclicks = 3; break;
+ }
+ return FALSE;
+}
+
+static gboolean listitem_button_release(GtkWidget *item, GdkEventButton *event,
+ gpointer data)
+{
+ struct dlgparam *dp = (struct dlgparam *)data;
+ struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(item));
+ if (uc->nclicks>1) {
+ uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_ACTION);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static void list_selchange(GtkList *list, gpointer data)
+{
+ struct dlgparam *dp = (struct dlgparam *)data;
+ struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(list));
+ if (!uc) return;
+ uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_SELCHANGE);
+}
+
+static void draglist_move(struct dlgparam *dp, struct uctrl *uc, int direction)
+{
+ int index = dlg_listbox_index(uc->ctrl, dp);
+ GList *children = gtk_container_children(GTK_CONTAINER(uc->list));
+ GtkWidget *child;
+
+ if ((index < 0) ||
+ (index == 0 && direction < 0) ||
+ (index == g_list_length(children)-1 && direction > 0)) {
+ gdk_beep();
+ return;
+ }
+
+ child = g_list_nth_data(children, index);
+ gtk_widget_ref(child);
+ gtk_list_clear_items(GTK_LIST(uc->list), index, index+1);
+ g_list_free(children);
+
+ children = NULL;
+ children = g_list_append(children, child);
+ gtk_list_insert_items(GTK_LIST(uc->list), children, index + direction);
+ gtk_list_select_item(GTK_LIST(uc->list), index + direction);
+ uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_VALCHANGE);
+}
+
+static void draglist_up(GtkButton *button, gpointer data)
+{
+ struct dlgparam *dp = (struct dlgparam *)data;
+ struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(button));
+ draglist_move(dp, uc, -1);
+}
+
+static void draglist_down(GtkButton *button, gpointer data)
+{
+ struct dlgparam *dp = (struct dlgparam *)data;
+ struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(button));
+ draglist_move(dp, uc, +1);
+}
+
+#else /* !GTK_CHECK_VERSION(2,0,0) */
+
+/*
+ * GTK 2 list box event handlers.
+ */
+
+static void listbox_doubleclick(GtkTreeView *treeview, GtkTreePath *path,
+ GtkTreeViewColumn *column, gpointer data)
+{
+ struct dlgparam *dp = (struct dlgparam *)data;
+ struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(treeview));
+ if (uc)
+ uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_ACTION);
+}
+
+static void listbox_selchange(GtkTreeSelection *treeselection,
+ gpointer data)
+{
+ struct dlgparam *dp = (struct dlgparam *)data;
+ GtkTreeView *tree = gtk_tree_selection_get_tree_view(treeselection);
+ struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(tree));
+ if (uc)
+ uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_SELCHANGE);
+}
+
+struct draglist_valchange_ctx {
+ struct uctrl *uc;
+ struct dlgparam *dp;
+};
+
+static gboolean draglist_valchange(gpointer data)
+{
+ struct draglist_valchange_ctx *ctx =
+ (struct draglist_valchange_ctx *)data;
+
+ ctx->uc->ctrl->generic.handler(ctx->uc->ctrl, ctx->dp,
+ ctx->dp->data, EVENT_VALCHANGE);
+
+ sfree(ctx);
+
+ return FALSE;
+}
+
+static void listbox_reorder(GtkTreeModel *treemodel, GtkTreePath *path,
+ GtkTreeIter *iter, gpointer data)
+{
+ struct dlgparam *dp = (struct dlgparam *)data;
+ gpointer tree;
+ struct uctrl *uc;
+
+ if (dp->flags & FLAG_UPDATING_LISTBOX)
+ return; /* not a user drag operation */
+
+ tree = g_object_get_data(G_OBJECT(treemodel), "user-data");
+ uc = dlg_find_bywidget(dp, GTK_WIDGET(tree));
+ if (uc) {
+ /*
+ * We should cause EVENT_VALCHANGE on the list box, now
+ * that its rows have been reordered. However, the GTK 2
+ * docs say that at the point this signal is received the
+ * new row might not have actually been filled in yet.
+ *
+ * (So what smegging use is it then, eh? Don't suppose it
+ * occurred to you at any point that letting the
+ * application know _after_ the reordering was compelete
+ * might be helpful to someone?)
+ *
+ * To get round this, I schedule an idle function, which I
+ * hope won't be called until the main event loop is
+ * re-entered after the drag-and-drop handler has finished
+ * furtling with the list store.
+ */
+ struct draglist_valchange_ctx *ctx =
+ snew(struct draglist_valchange_ctx);
+ ctx->uc = uc;
+ ctx->dp = dp;
+ g_idle_add(draglist_valchange, ctx);
+ }
+}
+
+#endif /* !GTK_CHECK_VERSION(2,0,0) */
+
+#if !GTK_CHECK_VERSION(2,4,0)
+
+static void menuitem_activate(GtkMenuItem *item, gpointer data)
+{
+ struct dlgparam *dp = (struct dlgparam *)data;
+ GtkWidget *menushell = GTK_WIDGET(item)->parent;
+ gpointer optmenu = gtk_object_get_data(GTK_OBJECT(menushell), "user-data");
+ struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(optmenu));
+ uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_SELCHANGE);
+}
+
+#else
+
+static void droplist_selchange(GtkComboBox *combo, gpointer data)
+{
+ struct dlgparam *dp = (struct dlgparam *)data;
+ struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(combo));
+ if (uc)
+ uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_SELCHANGE);
+}
+
+#endif /* !GTK_CHECK_VERSION(2,4,0) */
+
+static void filesel_ok(GtkButton *button, gpointer data)
+{
+ /* struct dlgparam *dp = (struct dlgparam *)data; */
+ gpointer filesel = gtk_object_get_data(GTK_OBJECT(button), "user-data");
+ struct uctrl *uc = gtk_object_get_data(GTK_OBJECT(filesel), "user-data");
+ const char *name = gtk_file_selection_get_filename
+ (GTK_FILE_SELECTION(filesel));
+ gtk_entry_set_text(GTK_ENTRY(uc->entry), name);
+}
+
+static void fontsel_ok(GtkButton *button, gpointer data)
+{
+ /* struct dlgparam *dp = (struct dlgparam *)data; */
+
+#if !GTK_CHECK_VERSION(2,0,0)
+
+ gpointer fontsel = gtk_object_get_data(GTK_OBJECT(button), "user-data");
+ struct uctrl *uc = gtk_object_get_data(GTK_OBJECT(fontsel), "user-data");
+ const char *name = gtk_font_selection_dialog_get_font_name
+ (GTK_FONT_SELECTION_DIALOG(fontsel));
+ gtk_entry_set_text(GTK_ENTRY(uc->entry), name);
+
+#else
+
+ unifontsel *fontsel = (unifontsel *)gtk_object_get_data
+ (GTK_OBJECT(button), "user-data");
+ struct uctrl *uc = (struct uctrl *)fontsel->user_data;
+ char *name = unifontsel_get_name(fontsel);
+ assert(name); /* should always be ok after OK pressed */
+ gtk_entry_set_text(GTK_ENTRY(uc->entry), name);
+ sfree(name);
+
+#endif
+}
+
+static void coloursel_ok(GtkButton *button, gpointer data)
+{
+ struct dlgparam *dp = (struct dlgparam *)data;
+ gpointer coloursel = gtk_object_get_data(GTK_OBJECT(button), "user-data");
+ struct uctrl *uc = gtk_object_get_data(GTK_OBJECT(coloursel), "user-data");
+ gdouble cvals[4];
+ gtk_color_selection_get_color
+ (GTK_COLOR_SELECTION(GTK_COLOR_SELECTION_DIALOG(coloursel)->colorsel),
+ cvals);
+ dp->coloursel_result.r = (int) (255 * cvals[0]);
+ dp->coloursel_result.g = (int) (255 * cvals[1]);
+ dp->coloursel_result.b = (int) (255 * cvals[2]);
+ dp->coloursel_result.ok = TRUE;
+ uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_CALLBACK);
+}
+
+static void coloursel_cancel(GtkButton *button, gpointer data)
+{
+ struct dlgparam *dp = (struct dlgparam *)data;
+ gpointer coloursel = gtk_object_get_data(GTK_OBJECT(button), "user-data");
+ struct uctrl *uc = gtk_object_get_data(GTK_OBJECT(coloursel), "user-data");
+ dp->coloursel_result.ok = FALSE;
+ uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_CALLBACK);
+}
+
+static void filefont_clicked(GtkButton *button, gpointer data)
+{
+ struct dlgparam *dp = (struct dlgparam *)data;
+ struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(button));
+
+ if (uc->ctrl->generic.type == CTRL_FILESELECT) {
+ GtkWidget *filesel =
+ gtk_file_selection_new(uc->ctrl->fileselect.title);
+ gtk_window_set_modal(GTK_WINDOW(filesel), TRUE);
+ gtk_object_set_data
+ (GTK_OBJECT(GTK_FILE_SELECTION(filesel)->ok_button), "user-data",
+ (gpointer)filesel);
+ gtk_object_set_data(GTK_OBJECT(filesel), "user-data", (gpointer)uc);
+ gtk_signal_connect
+ (GTK_OBJECT(GTK_FILE_SELECTION(filesel)->ok_button), "clicked",
+ GTK_SIGNAL_FUNC(filesel_ok), (gpointer)dp);
+ gtk_signal_connect_object
+ (GTK_OBJECT(GTK_FILE_SELECTION(filesel)->ok_button), "clicked",
+ GTK_SIGNAL_FUNC(gtk_widget_destroy), (gpointer)filesel);
+ gtk_signal_connect_object
+ (GTK_OBJECT(GTK_FILE_SELECTION(filesel)->cancel_button), "clicked",
+ GTK_SIGNAL_FUNC(gtk_widget_destroy), (gpointer)filesel);
+ gtk_widget_show(filesel);
+ }
+
+ if (uc->ctrl->generic.type == CTRL_FONTSELECT) {
+ const gchar *fontname = gtk_entry_get_text(GTK_ENTRY(uc->entry));
+
+#if !GTK_CHECK_VERSION(2,0,0)
+
+ /*
+ * Use the GTK 1 standard font selector.
+ */
+
+ gchar *spacings[] = { "c", "m", NULL };
+ GtkWidget *fontsel =
+ gtk_font_selection_dialog_new("Select a font");
+ gtk_window_set_modal(GTK_WINDOW(fontsel), TRUE);
+ gtk_font_selection_dialog_set_filter
+ (GTK_FONT_SELECTION_DIALOG(fontsel),
+ GTK_FONT_FILTER_BASE, GTK_FONT_ALL,
+ NULL, NULL, NULL, NULL, spacings, NULL);
+ if (!gtk_font_selection_dialog_set_font_name
+ (GTK_FONT_SELECTION_DIALOG(fontsel), fontname)) {
+ /*
+ * If the font name wasn't found as it was, try opening
+ * it and extracting its FONT property. This should
+ * have the effect of mapping short aliases into true
+ * XLFDs.
+ */
+ GdkFont *font = gdk_font_load(fontname);
+ if (font) {
+ XFontStruct *xfs = GDK_FONT_XFONT(font);
+ Display *disp = GDK_FONT_XDISPLAY(font);
+ Atom fontprop = XInternAtom(disp, "FONT", False);
+ unsigned long ret;
+ gdk_font_ref(font);
+ if (XGetFontProperty(xfs, fontprop, &ret)) {
+ char *name = XGetAtomName(disp, (Atom)ret);
+ if (name)
+ gtk_font_selection_dialog_set_font_name
+ (GTK_FONT_SELECTION_DIALOG(fontsel), name);
+ }
+ gdk_font_unref(font);
+ }
+ }
+ gtk_object_set_data
+ (GTK_OBJECT(GTK_FONT_SELECTION_DIALOG(fontsel)->ok_button),
+ "user-data", (gpointer)fontsel);
+ gtk_object_set_data(GTK_OBJECT(fontsel), "user-data", (gpointer)uc);
+ gtk_signal_connect
+ (GTK_OBJECT(GTK_FONT_SELECTION_DIALOG(fontsel)->ok_button),
+ "clicked", GTK_SIGNAL_FUNC(fontsel_ok), (gpointer)dp);
+ gtk_signal_connect_object
+ (GTK_OBJECT(GTK_FONT_SELECTION_DIALOG(fontsel)->ok_button),
+ "clicked", GTK_SIGNAL_FUNC(gtk_widget_destroy),
+ (gpointer)fontsel);
+ gtk_signal_connect_object
+ (GTK_OBJECT(GTK_FONT_SELECTION_DIALOG(fontsel)->cancel_button),
+ "clicked", GTK_SIGNAL_FUNC(gtk_widget_destroy),
+ (gpointer)fontsel);
+ gtk_widget_show(fontsel);
+
+#else /* !GTK_CHECK_VERSION(2,0,0) */
+
+ /*
+ * Use the unifontsel code provided in gtkfont.c.
+ */
+
+ unifontsel *fontsel = unifontsel_new("Select a font");
+
+ gtk_window_set_modal(fontsel->window, TRUE);
+ unifontsel_set_name(fontsel, fontname);
+
+ gtk_object_set_data(GTK_OBJECT(fontsel->ok_button),
+ "user-data", (gpointer)fontsel);
+ fontsel->user_data = uc;
+ gtk_signal_connect(GTK_OBJECT(fontsel->ok_button), "clicked",
+ GTK_SIGNAL_FUNC(fontsel_ok), (gpointer)dp);
+ gtk_signal_connect_object(GTK_OBJECT(fontsel->ok_button), "clicked",
+ GTK_SIGNAL_FUNC(unifontsel_destroy),
+ (gpointer)fontsel);
+ gtk_signal_connect_object(GTK_OBJECT(fontsel->cancel_button),"clicked",
+ GTK_SIGNAL_FUNC(unifontsel_destroy),
+ (gpointer)fontsel);
+
+ gtk_widget_show(GTK_WIDGET(fontsel->window));
+
+#endif /* !GTK_CHECK_VERSION(2,0,0) */
+
+ }
+}
+
+static void label_sizealloc(GtkWidget *widget, GtkAllocation *alloc,
+ gpointer data)
+{
+ struct dlgparam *dp = (struct dlgparam *)data;
+ struct uctrl *uc = dlg_find_bywidget(dp, widget);
+
+ gtk_widget_set_usize(uc->text, alloc->width, -1);
+ gtk_label_set_text(GTK_LABEL(uc->text), uc->ctrl->generic.label);
+ gtk_signal_disconnect(GTK_OBJECT(uc->text), uc->textsig);
+}
+
+/* ----------------------------------------------------------------------
+ * This function does the main layout work: it reads a controlset,
+ * it creates the relevant GTK controls, and returns a GtkWidget
+ * containing the result. (This widget might be a title of some
+ * sort, it might be a Columns containing many controls, or it
+ * might be a GtkFrame containing a Columns; whatever it is, it's
+ * definitely a GtkWidget and should probably be added to a
+ * GtkVbox.)
+ *
+ * `win' is required for setting the default button. If it is
+ * non-NULL, all buttons created will be default-capable (so they
+ * have extra space round them for the default highlight).
+ */
+GtkWidget *layout_ctrls(struct dlgparam *dp, struct Shortcuts *scs,
+ struct controlset *s, GtkWindow *win)
+{
+ Columns *cols;
+ GtkWidget *ret;
+ int i;
+
+ if (!s->boxname && s->boxtitle) {
+ /* This controlset is a panel title. */
+ return gtk_label_new(s->boxtitle);
+ }
+
+ /*
+ * Otherwise, we expect to be laying out actual controls, so
+ * we'll start by creating a Columns for the purpose.
+ */
+ cols = COLUMNS(columns_new(4));
+ ret = GTK_WIDGET(cols);
+ gtk_widget_show(ret);
+
+ /*
+ * Create a containing frame if we have a box name.
+ */
+ if (*s->boxname) {
+ ret = gtk_frame_new(s->boxtitle); /* NULL is valid here */
+ gtk_container_set_border_width(GTK_CONTAINER(cols), 4);
+ gtk_container_add(GTK_CONTAINER(ret), GTK_WIDGET(cols));
+ gtk_widget_show(ret);
+ }
+
+ /*
+ * Now iterate through the controls themselves, create them,
+ * and add them to the Columns.
+ */
+ for (i = 0; i < s->ncontrols; i++) {
+ union control *ctrl = s->ctrls[i];
+ struct uctrl *uc;
+ int left = FALSE;
+ GtkWidget *w = NULL;
+
+ switch (ctrl->generic.type) {
+ case CTRL_COLUMNS:
+ {
+ static const int simplecols[1] = { 100 };
+ columns_set_cols(cols, ctrl->columns.ncols,
+ (ctrl->columns.percentages ?
+ ctrl->columns.percentages : simplecols));
+ }
+ continue; /* no actual control created */
+ case CTRL_TABDELAY:
+ {
+ struct uctrl *uc = dlg_find_byctrl(dp, ctrl->tabdelay.ctrl);
+ if (uc)
+ columns_taborder_last(cols, uc->toplevel);
+ }
+ continue; /* no actual control created */
+ }
+
+ uc = snew(struct uctrl);
+ uc->ctrl = ctrl;
+ uc->privdata = NULL;
+ uc->privdata_needs_free = FALSE;
+ uc->buttons = NULL;
+ uc->entry = NULL;
+#if !GTK_CHECK_VERSION(2,4,0)
+ uc->list = uc->menu = uc->optmenu = NULL;
+#else
+ uc->combo = NULL;
+#endif
+#if GTK_CHECK_VERSION(2,0,0)
+ uc->treeview = NULL;
+ uc->listmodel = NULL;
+#endif
+ uc->button = uc->text = NULL;
+ uc->label = NULL;
+ uc->nclicks = 0;
+
+ switch (ctrl->generic.type) {
+ case CTRL_BUTTON:
+ w = gtk_button_new_with_label(ctrl->generic.label);
+ if (win) {
+ GTK_WIDGET_SET_FLAGS(w, GTK_CAN_DEFAULT);
+ if (ctrl->button.isdefault)
+ gtk_window_set_default(win, w);
+ if (ctrl->button.iscancel)
+ dp->cancelbutton = w;
+ }
+ gtk_signal_connect(GTK_OBJECT(w), "clicked",
+ GTK_SIGNAL_FUNC(button_clicked), dp);
+ gtk_signal_connect(GTK_OBJECT(w), "focus_in_event",
+ GTK_SIGNAL_FUNC(widget_focus), dp);
+ shortcut_add(scs, GTK_BIN(w)->child, ctrl->button.shortcut,
+ SHORTCUT_UCTRL, uc);
+ break;
+ case CTRL_CHECKBOX:
+ w = gtk_check_button_new_with_label(ctrl->generic.label);
+ gtk_signal_connect(GTK_OBJECT(w), "toggled",
+ GTK_SIGNAL_FUNC(button_toggled), dp);
+ gtk_signal_connect(GTK_OBJECT(w), "focus_in_event",
+ GTK_SIGNAL_FUNC(widget_focus), dp);
+ shortcut_add(scs, GTK_BIN(w)->child, ctrl->checkbox.shortcut,
+ SHORTCUT_UCTRL, uc);
+ left = TRUE;
+ break;
+ case CTRL_RADIO:
+ /*
+ * Radio buttons get to go inside their own Columns, no
+ * matter what.
+ */
+ {
+ gint i, *percentages;
+ GSList *group;
+
+ w = columns_new(0);
+ if (ctrl->generic.label) {
+ GtkWidget *label = gtk_label_new(ctrl->generic.label);
+ columns_add(COLUMNS(w), label, 0, 1);
+ columns_force_left_align(COLUMNS(w), label);
+ gtk_widget_show(label);
+ shortcut_add(scs, label, ctrl->radio.shortcut,
+ SHORTCUT_UCTRL, uc);
+ uc->label = label;
+ }
+ percentages = g_new(gint, ctrl->radio.ncolumns);
+ for (i = 0; i < ctrl->radio.ncolumns; i++) {
+ percentages[i] =
+ ((100 * (i+1) / ctrl->radio.ncolumns) -
+ 100 * i / ctrl->radio.ncolumns);
+ }
+ columns_set_cols(COLUMNS(w), ctrl->radio.ncolumns,
+ percentages);
+ g_free(percentages);
+ group = NULL;
+
+ uc->nbuttons = ctrl->radio.nbuttons;
+ uc->buttons = snewn(uc->nbuttons, GtkWidget *);
+
+ for (i = 0; i < ctrl->radio.nbuttons; i++) {
+ GtkWidget *b;
+ gint colstart;
+
+ b = (gtk_radio_button_new_with_label
+ (group, ctrl->radio.buttons[i]));
+ uc->buttons[i] = b;
+ group = gtk_radio_button_group(GTK_RADIO_BUTTON(b));
+ colstart = i % ctrl->radio.ncolumns;
+ columns_add(COLUMNS(w), b, colstart,
+ (i == ctrl->radio.nbuttons-1 ?
+ ctrl->radio.ncolumns - colstart : 1));
+ columns_force_left_align(COLUMNS(w), b);
+ gtk_widget_show(b);
+ gtk_signal_connect(GTK_OBJECT(b), "toggled",
+ GTK_SIGNAL_FUNC(button_toggled), dp);
+ gtk_signal_connect(GTK_OBJECT(b), "focus_in_event",
+ GTK_SIGNAL_FUNC(widget_focus), dp);
+ if (ctrl->radio.shortcuts) {
+ shortcut_add(scs, GTK_BIN(b)->child,
+ ctrl->radio.shortcuts[i],
+ SHORTCUT_UCTRL, uc);
+ }
+ }
+ }
+ break;
+ case CTRL_EDITBOX:
+ {
+ GtkRequisition req;
+ GtkWidget *signalobject;
+
+ if (ctrl->editbox.has_list) {
+#if !GTK_CHECK_VERSION(2,4,0)
+ /*
+ * GTK 1 combo box.
+ */
+ w = gtk_combo_new();
+ gtk_combo_set_value_in_list(GTK_COMBO(w), FALSE, TRUE);
+ uc->entry = GTK_COMBO(w)->entry;
+ uc->list = GTK_COMBO(w)->list;
+ signalobject = uc->entry;
+#else
+ /*
+ * GTK 2 combo box.
+ */
+ uc->listmodel = gtk_list_store_new(2, G_TYPE_INT,
+ G_TYPE_STRING);
+ w = gtk_combo_box_entry_new_with_model
+ (GTK_TREE_MODEL(uc->listmodel), 1);
+ /* We cannot support password combo boxes. */
+ assert(!ctrl->editbox.password);
+ uc->combo = w;
+ signalobject = uc->combo;
+#endif
+ } else {
+ w = gtk_entry_new();
+ if (ctrl->editbox.password)
+ gtk_entry_set_visibility(GTK_ENTRY(w), FALSE);
+ uc->entry = w;
+ signalobject = w;
+ }
+ uc->entrysig =
+ gtk_signal_connect(GTK_OBJECT(signalobject), "changed",
+ GTK_SIGNAL_FUNC(editbox_changed), dp);
+ gtk_signal_connect(GTK_OBJECT(signalobject), "key_press_event",
+ GTK_SIGNAL_FUNC(editbox_key), dp);
+ gtk_signal_connect(GTK_OBJECT(signalobject), "focus_in_event",
+ GTK_SIGNAL_FUNC(widget_focus), dp);
+ gtk_signal_connect(GTK_OBJECT(signalobject), "focus_out_event",
+ GTK_SIGNAL_FUNC(editbox_lostfocus), dp);
+ gtk_signal_connect(GTK_OBJECT(signalobject), "focus_out_event",
+ GTK_SIGNAL_FUNC(editbox_lostfocus), dp);
+ /*
+ * Edit boxes, for some strange reason, have a minimum
+ * width of 150 in GTK 1.2. We don't want this - we'd
+ * rather the edit boxes acquired their natural width
+ * from the column layout of the rest of the box.
+ *
+ * Also, while we're here, we'll squirrel away the
+ * edit box height so we can use that to centre its
+ * label vertically beside it.
+ */
+ gtk_widget_size_request(w, &req);
+ gtk_widget_set_usize(w, 10, req.height);
+
+ if (ctrl->generic.label) {
+ GtkWidget *label, *container;
+
+ label = gtk_label_new(ctrl->generic.label);
+
+ shortcut_add(scs, label, ctrl->editbox.shortcut,
+ SHORTCUT_FOCUS, uc->entry);
+
+ container = columns_new(4);
+ if (ctrl->editbox.percentwidth == 100) {
+ columns_add(COLUMNS(container), label, 0, 1);
+ columns_force_left_align(COLUMNS(container), label);
+ columns_add(COLUMNS(container), w, 0, 1);
+ } else {
+ gint percentages[2];
+ percentages[1] = ctrl->editbox.percentwidth;
+ percentages[0] = 100 - ctrl->editbox.percentwidth;
+ columns_set_cols(COLUMNS(container), 2, percentages);
+ columns_add(COLUMNS(container), label, 0, 1);
+ columns_force_left_align(COLUMNS(container), label);
+ columns_add(COLUMNS(container), w, 1, 1);
+ /* Centre the label vertically. */
+ gtk_widget_set_usize(label, -1, req.height);
+ gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
+ }
+ gtk_widget_show(label);
+ gtk_widget_show(w);
+
+ w = container;
+ uc->label = label;
+ }
+ }
+ break;
+ case CTRL_FILESELECT:
+ case CTRL_FONTSELECT:
+ {
+ GtkWidget *ww;
+ GtkRequisition req;
+ char *browsebtn =
+ (ctrl->generic.type == CTRL_FILESELECT ?
+ "Browse..." : "Change...");
+
+ gint percentages[] = { 75, 25 };
+ w = columns_new(4);
+ columns_set_cols(COLUMNS(w), 2, percentages);
+
+ if (ctrl->generic.label) {
+ ww = gtk_label_new(ctrl->generic.label);
+ columns_add(COLUMNS(w), ww, 0, 2);
+ columns_force_left_align(COLUMNS(w), ww);
+ gtk_widget_show(ww);
+ shortcut_add(scs, ww,
+ (ctrl->generic.type == CTRL_FILESELECT ?
+ ctrl->fileselect.shortcut :
+ ctrl->fontselect.shortcut),
+ SHORTCUT_UCTRL, uc);
+ uc->label = ww;
+ }
+
+ uc->entry = ww = gtk_entry_new();
+ gtk_widget_size_request(ww, &req);
+ gtk_widget_set_usize(ww, 10, req.height);
+ columns_add(COLUMNS(w), ww, 0, 1);
+ gtk_widget_show(ww);
+
+ uc->button = ww = gtk_button_new_with_label(browsebtn);
+ columns_add(COLUMNS(w), ww, 1, 1);
+ gtk_widget_show(ww);
+
+ gtk_signal_connect(GTK_OBJECT(uc->entry), "key_press_event",
+ GTK_SIGNAL_FUNC(editbox_key), dp);
+ uc->entrysig =
+ gtk_signal_connect(GTK_OBJECT(uc->entry), "changed",
+ GTK_SIGNAL_FUNC(editbox_changed), dp);
+ gtk_signal_connect(GTK_OBJECT(uc->entry), "focus_in_event",
+ GTK_SIGNAL_FUNC(widget_focus), dp);
+ gtk_signal_connect(GTK_OBJECT(uc->button), "focus_in_event",
+ GTK_SIGNAL_FUNC(widget_focus), dp);
+ gtk_signal_connect(GTK_OBJECT(ww), "clicked",
+ GTK_SIGNAL_FUNC(filefont_clicked), dp);
+ }
+ break;
+ case CTRL_LISTBOX:
+
+#if GTK_CHECK_VERSION(2,0,0)
+ /*
+ * First construct the list data store, with the right
+ * number of columns.
+ */
+# if !GTK_CHECK_VERSION(2,4,0)
+ /* (For GTK 2.0 to 2.3, we do this for full listboxes only,
+ * because combo boxes are still done the old GTK1 way.) */
+ if (ctrl->listbox.height > 0)
+# endif
+ {
+ GType *types;
+ int i;
+ int cols;
+
+ cols = ctrl->listbox.ncols;
+ cols = cols ? cols : 1;
+ types = snewn(1 + cols, GType);
+
+ types[0] = G_TYPE_INT;
+ for (i = 0; i < cols; i++)
+ types[i+1] = G_TYPE_STRING;
+
+ uc->listmodel = gtk_list_store_newv(1 + cols, types);
+
+ sfree(types);
+ }
+#endif
+
+ /*
+ * See if it's a drop-down list (non-editable combo
+ * box).
+ */
+ if (ctrl->listbox.height == 0) {
+#if !GTK_CHECK_VERSION(2,4,0)
+ /*
+ * GTK1 and early-GTK2 option-menu style of
+ * drop-down list.
+ */
+ uc->optmenu = w = gtk_option_menu_new();
+ uc->menu = gtk_menu_new();
+ gtk_option_menu_set_menu(GTK_OPTION_MENU(w), uc->menu);
+ gtk_object_set_data(GTK_OBJECT(uc->menu), "user-data",
+ (gpointer)uc->optmenu);
+ gtk_signal_connect(GTK_OBJECT(uc->optmenu), "focus_in_event",
+ GTK_SIGNAL_FUNC(widget_focus), dp);
+#else
+ /*
+ * Late-GTK2 style using a GtkComboBox.
+ */
+ GtkCellRenderer *cr;
+
+ /*
+ * Create a non-editable GtkComboBox (that is, not
+ * its subclass GtkComboBoxEntry).
+ */
+ w = gtk_combo_box_new_with_model
+ (GTK_TREE_MODEL(uc->listmodel));
+ uc->combo = w;
+
+ /*
+ * Tell it how to render a list item (i.e. which
+ * column to look at in the list model).
+ */
+ cr = gtk_cell_renderer_text_new();
+ gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(w), cr, TRUE);
+ gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(w), cr,
+ "text", 1, NULL);
+
+ /*
+ * And tell it to notify us when the selection
+ * changes.
+ */
+ g_signal_connect(G_OBJECT(w), "changed",
+ G_CALLBACK(droplist_selchange), dp);
+#endif
+ } else {
+#if !GTK_CHECK_VERSION(2,0,0)
+ /*
+ * GTK1-style full list box.
+ */
+ uc->list = gtk_list_new();
+ if (ctrl->listbox.multisel == 2) {
+ gtk_list_set_selection_mode(GTK_LIST(uc->list),
+ GTK_SELECTION_EXTENDED);
+ } else if (ctrl->listbox.multisel == 1) {
+ gtk_list_set_selection_mode(GTK_LIST(uc->list),
+ GTK_SELECTION_MULTIPLE);
+ } else {
+ gtk_list_set_selection_mode(GTK_LIST(uc->list),
+ GTK_SELECTION_SINGLE);
+ }
+ w = gtk_scrolled_window_new(NULL, NULL);
+ gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(w),
+ uc->list);
+ gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w),
+ GTK_POLICY_NEVER,
+ GTK_POLICY_AUTOMATIC);
+ uc->adj = gtk_scrolled_window_get_vadjustment
+ (GTK_SCROLLED_WINDOW(w));
+
+ gtk_widget_show(uc->list);
+ gtk_signal_connect(GTK_OBJECT(uc->list), "selection-changed",
+ GTK_SIGNAL_FUNC(list_selchange), dp);
+ gtk_signal_connect(GTK_OBJECT(uc->list), "focus_in_event",
+ GTK_SIGNAL_FUNC(widget_focus), dp);
+
+ /*
+ * Adjust the height of the scrolled window to the
+ * minimum given by the height parameter.
+ *
+ * This piece of guesswork is a horrid hack based
+ * on looking inside the GTK 1.2 sources
+ * (specifically gtkviewport.c, which appears to be
+ * the widget which provides the border around the
+ * scrolling area). Anyone lets me know how I can
+ * do this in a way which isn't at risk from GTK
+ * upgrades, I'd be grateful.
+ */
+ {
+ int edge;
+ edge = GTK_WIDGET(uc->list)->style->klass->ythickness;
+ gtk_widget_set_usize(w, 10,
+ 2*edge + (ctrl->listbox.height *
+ get_listitemheight(w)));
+ }
+
+ if (ctrl->listbox.draglist) {
+ /*
+ * GTK doesn't appear to make it easy to
+ * implement a proper draggable list; so
+ * instead I'm just going to have to put an Up
+ * and a Down button to the right of the actual
+ * list box. Ah well.
+ */
+ GtkWidget *cols, *button;
+ static const gint percentages[2] = { 80, 20 };
+
+ cols = columns_new(4);
+ columns_set_cols(COLUMNS(cols), 2, percentages);
+ columns_add(COLUMNS(cols), w, 0, 1);
+ gtk_widget_show(w);
+ button = gtk_button_new_with_label("Up");
+ columns_add(COLUMNS(cols), button, 1, 1);
+ gtk_widget_show(button);
+ gtk_signal_connect(GTK_OBJECT(button), "clicked",
+ GTK_SIGNAL_FUNC(draglist_up), dp);
+ gtk_signal_connect(GTK_OBJECT(button), "focus_in_event",
+ GTK_SIGNAL_FUNC(widget_focus), dp);
+ button = gtk_button_new_with_label("Down");
+ columns_add(COLUMNS(cols), button, 1, 1);
+ gtk_widget_show(button);
+ gtk_signal_connect(GTK_OBJECT(button), "clicked",
+ GTK_SIGNAL_FUNC(draglist_down), dp);
+ gtk_signal_connect(GTK_OBJECT(button), "focus_in_event",
+ GTK_SIGNAL_FUNC(widget_focus), dp);
+
+ w = cols;
+ }
+#else
+ /*
+ * GTK2 treeview-based full list box.
+ */
+ GtkTreeSelection *sel;
+
+ /*
+ * Create the list box itself, its columns, and
+ * its containing scrolled window.
+ */
+ w = gtk_tree_view_new_with_model
+ (GTK_TREE_MODEL(uc->listmodel));
+ g_object_set_data(G_OBJECT(uc->listmodel), "user-data",
+ (gpointer)w);
+ gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(w), FALSE);
+ sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(w));
+ gtk_tree_selection_set_mode
+ (sel, ctrl->listbox.multisel ? GTK_SELECTION_MULTIPLE :
+ GTK_SELECTION_SINGLE);
+ uc->treeview = w;
+ gtk_signal_connect(GTK_OBJECT(w), "row-activated",
+ GTK_SIGNAL_FUNC(listbox_doubleclick), dp);
+ g_signal_connect(G_OBJECT(sel), "changed",
+ G_CALLBACK(listbox_selchange), dp);
+
+ if (ctrl->listbox.draglist) {
+ gtk_tree_view_set_reorderable(GTK_TREE_VIEW(w), TRUE);
+ g_signal_connect(G_OBJECT(uc->listmodel), "row-inserted",
+ G_CALLBACK(listbox_reorder), dp);
+ }
+
+ {
+ int i;
+ int cols;
+
+ cols = ctrl->listbox.ncols;
+ cols = cols ? cols : 1;
+ for (i = 0; i < cols; i++) {
+ GtkTreeViewColumn *column;
+ /*
+ * It appears that GTK 2 doesn't leave us any
+ * particularly sensible way to honour the
+ * "percentages" specification in the ctrl
+ * structure.
+ */
+ column = gtk_tree_view_column_new_with_attributes
+ ("heading", gtk_cell_renderer_text_new(),
+ "text", i+1, (char *)NULL);
+ gtk_tree_view_column_set_sizing
+ (column, GTK_TREE_VIEW_COLUMN_GROW_ONLY);
+ gtk_tree_view_append_column(GTK_TREE_VIEW(w), column);
+ }
+ }
+
+ {
+ GtkWidget *scroll;
+
+ scroll = gtk_scrolled_window_new(NULL, NULL);
+ gtk_scrolled_window_set_shadow_type
+ (GTK_SCROLLED_WINDOW(scroll), GTK_SHADOW_IN);
+ gtk_widget_show(w);
+ gtk_container_add(GTK_CONTAINER(scroll), w);
+ gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
+ GTK_POLICY_AUTOMATIC,
+ GTK_POLICY_ALWAYS);
+ gtk_widget_set_size_request
+ (scroll, -1,
+ ctrl->listbox.height * get_listitemheight(w));
+
+ w = scroll;
+ }
+#endif
+ }
+
+ if (ctrl->generic.label) {
+ GtkWidget *label, *container;
+ GtkRequisition req;
+
+ label = gtk_label_new(ctrl->generic.label);
+
+ shortcut_add(scs, label, ctrl->listbox.shortcut,
+ SHORTCUT_FOCUS, w);
+
+ container = columns_new(4);
+ if (ctrl->listbox.percentwidth == 100) {
+ columns_add(COLUMNS(container), label, 0, 1);
+ columns_force_left_align(COLUMNS(container), label);
+ columns_add(COLUMNS(container), w, 0, 1);
+ } else {
+ gint percentages[2];
+ percentages[1] = ctrl->listbox.percentwidth;
+ percentages[0] = 100 - ctrl->listbox.percentwidth;
+ columns_set_cols(COLUMNS(container), 2, percentages);
+ columns_add(COLUMNS(container), label, 0, 1);
+ columns_force_left_align(COLUMNS(container), label);
+ columns_add(COLUMNS(container), w, 1, 1);
+ /* Centre the label vertically. */
+ gtk_widget_size_request(w, &req);
+ gtk_widget_set_usize(label, -1, req.height);
+ gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
+ }
+ gtk_widget_show(label);
+ gtk_widget_show(w);
+
+ w = container;
+ uc->label = label;
+ }
+
+ break;
+ case CTRL_TEXT:
+ /*
+ * Wrapping text widgets don't sit well with the GTK
+ * layout model, in which widgets state a minimum size
+ * and the whole window then adjusts to the smallest
+ * size it can sensibly take given its contents. A
+ * wrapping text widget _has_ no clear minimum size;
+ * instead it has a range of possibilities. It can be
+ * one line deep but 2000 wide, or two lines deep and
+ * 1000 pixels, or three by 867, or four by 500 and so
+ * on. It can be as short as you like provided you
+ * don't mind it being wide, or as narrow as you like
+ * provided you don't mind it being tall.
+ *
+ * Therefore, it fits very badly into the layout model.
+ * Hence the only thing to do is pick a width and let
+ * it choose its own number of lines. To do this I'm
+ * going to cheat a little. All new wrapping text
+ * widgets will be created with a minimal text content
+ * "X"; then, after the rest of the dialog box is set
+ * up and its size calculated, the text widgets will be
+ * told their width and given their real text, which
+ * will cause the size to be recomputed in the y
+ * direction (because many of them will expand to more
+ * than one line).
+ */
+ uc->text = w = gtk_label_new("X");
+ gtk_misc_set_alignment(GTK_MISC(w), 0.0, 0.0);
+ gtk_label_set_line_wrap(GTK_LABEL(w), TRUE);
+ uc->textsig =
+ gtk_signal_connect(GTK_OBJECT(w), "size-allocate",
+ GTK_SIGNAL_FUNC(label_sizealloc), dp);
+ break;
+ }
+
+ assert(w != NULL);
+
+ columns_add(cols, w,
+ COLUMN_START(ctrl->generic.column),
+ COLUMN_SPAN(ctrl->generic.column));
+ if (left)
+ columns_force_left_align(cols, w);
+ gtk_widget_show(w);
+
+ uc->toplevel = w;
+ dlg_add_uctrl(dp, uc);
+ }
+
+ return ret;
+}
+
+struct selparam {
+ struct dlgparam *dp;
+ GtkNotebook *panels;
+ GtkWidget *panel;
+#if !GTK_CHECK_VERSION(2,0,0)
+ GtkWidget *treeitem;
+#else
+ int depth;
+ GtkTreePath *treepath;
+#endif
+ struct Shortcuts shortcuts;
+};
+
+#if GTK_CHECK_VERSION(2,0,0)
+static void treeselection_changed(GtkTreeSelection *treeselection,
+ gpointer data)
+{
+ struct selparam *sps = (struct selparam *)data, *sp;
+ GtkTreeModel *treemodel;
+ GtkTreeIter treeiter;
+ gint spindex;
+ gint page_num;
+
+ if (!gtk_tree_selection_get_selected(treeselection, &treemodel, &treeiter))
+ return;
+
+ gtk_tree_model_get(treemodel, &treeiter, TREESTORE_PARAMS, &spindex, -1);
+ sp = &sps[spindex];
+
+ page_num = gtk_notebook_page_num(sp->panels, sp->panel);
+ gtk_notebook_set_page(sp->panels, page_num);
+
+ dlg_refresh(NULL, sp->dp);
+
+ sp->dp->shortcuts = &sp->shortcuts;
+}
+#else
+static void treeitem_sel(GtkItem *item, gpointer data)
+{
+ struct selparam *sp = (struct selparam *)data;
+ gint page_num;
+
+ page_num = gtk_notebook_page_num(sp->panels, sp->panel);
+ gtk_notebook_set_page(sp->panels, page_num);
+
+ dlg_refresh(NULL, sp->dp);
+
+ sp->dp->shortcuts = &sp->shortcuts;
+ sp->dp->currtreeitem = sp->treeitem;
+}
+#endif
+
+static void window_destroy(GtkWidget *widget, gpointer data)
+{
+ gtk_main_quit();
+}
+
+#if !GTK_CHECK_VERSION(2,0,0)
+static int tree_grab_focus(struct dlgparam *dp)
+{
+ int i, f;
+
+ /*
+ * See if any of the treeitems has the focus.
+ */
+ f = -1;
+ for (i = 0; i < dp->ntreeitems; i++)
+ if (GTK_WIDGET_HAS_FOCUS(dp->treeitems[i])) {
+ f = i;
+ break;
+ }
+
+ if (f >= 0)
+ return FALSE;
+ else {
+ gtk_widget_grab_focus(dp->currtreeitem);
+ return TRUE;
+ }
+}
+
+gint tree_focus(GtkContainer *container, GtkDirectionType direction,
+ gpointer data)
+{
+ struct dlgparam *dp = (struct dlgparam *)data;
+
+ gtk_signal_emit_stop_by_name(GTK_OBJECT(container), "focus");
+ /*
+ * If there's a focused treeitem, we return FALSE to cause the
+ * focus to move on to some totally other control. If not, we
+ * focus the selected one.
+ */
+ return tree_grab_focus(dp);
+}
+#endif
+
+int win_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data)
+{
+ struct dlgparam *dp = (struct dlgparam *)data;
+
+ if (event->keyval == GDK_Escape && dp->cancelbutton) {
+ gtk_signal_emit_by_name(GTK_OBJECT(dp->cancelbutton), "clicked");
+ return TRUE;
+ }
+
+ if ((event->state & GDK_MOD1_MASK) &&
+ (unsigned char)event->string[0] > 0 &&
+ (unsigned char)event->string[0] <= 127) {
+ int schr = (unsigned char)event->string[0];
+ struct Shortcut *sc = &dp->shortcuts->sc[schr];
+
+ switch (sc->action) {
+ case SHORTCUT_TREE:
+#if GTK_CHECK_VERSION(2,0,0)
+ gtk_widget_grab_focus(sc->widget);
+#else
+ tree_grab_focus(dp);
+#endif
+ break;
+ case SHORTCUT_FOCUS:
+ gtk_widget_grab_focus(sc->widget);
+ break;
+ case SHORTCUT_UCTRL:
+ /*
+ * We must do something sensible with a uctrl.
+ * Precisely what this is depends on the type of
+ * control.
+ */
+ switch (sc->uc->ctrl->generic.type) {
+ case CTRL_CHECKBOX:
+ case CTRL_BUTTON:
+ /* Check boxes and buttons get the focus _and_ get toggled. */
+ gtk_widget_grab_focus(sc->uc->toplevel);
+ gtk_signal_emit_by_name(GTK_OBJECT(sc->uc->toplevel),
+ "clicked");
+ break;
+ case CTRL_FILESELECT:
+ case CTRL_FONTSELECT:
+ /* File/font selectors have their buttons pressed (ooer),
+ * and focus transferred to the edit box. */
+ gtk_signal_emit_by_name(GTK_OBJECT(sc->uc->button),
+ "clicked");
+ gtk_widget_grab_focus(sc->uc->entry);
+ break;
+ case CTRL_RADIO:
+ /*
+ * Radio buttons are fun, because they have
+ * multiple shortcuts. We must find whether the
+ * activated shortcut is the shortcut for the whole
+ * group, or for a particular button. In the former
+ * case, we find the currently selected button and
+ * focus it; in the latter, we focus-and-click the
+ * button whose shortcut was pressed.
+ */
+ if (schr == sc->uc->ctrl->radio.shortcut) {
+ int i;
+ for (i = 0; i < sc->uc->ctrl->radio.nbuttons; i++)
+ if (gtk_toggle_button_get_active
+ (GTK_TOGGLE_BUTTON(sc->uc->buttons[i]))) {
+ gtk_widget_grab_focus(sc->uc->buttons[i]);
+ }
+ } else if (sc->uc->ctrl->radio.shortcuts) {
+ int i;
+ for (i = 0; i < sc->uc->ctrl->radio.nbuttons; i++)
+ if (schr == sc->uc->ctrl->radio.shortcuts[i]) {
+ gtk_widget_grab_focus(sc->uc->buttons[i]);
+ gtk_signal_emit_by_name
+ (GTK_OBJECT(sc->uc->buttons[i]), "clicked");
+ }
+ }
+ break;
+ case CTRL_LISTBOX:
+
+#if !GTK_CHECK_VERSION(2,4,0)
+ if (sc->uc->optmenu) {
+ GdkEventButton bev;
+ gint returnval;
+
+ gtk_widget_grab_focus(sc->uc->optmenu);
+ /* Option menus don't work using the "clicked" signal.
+ * We need to manufacture a button press event :-/ */
+ bev.type = GDK_BUTTON_PRESS;
+ bev.button = 1;
+ gtk_signal_emit_by_name(GTK_OBJECT(sc->uc->optmenu),
+ "button_press_event",
+ &bev, &returnval);
+ break;
+ }
+#else
+ if (sc->uc->combo) {
+ gtk_widget_grab_focus(sc->uc->combo);
+ gtk_combo_box_popup(GTK_COMBO_BOX(sc->uc->combo));
+ break;
+ }
+#endif
+#if !GTK_CHECK_VERSION(2,0,0)
+ if (sc->uc->list) {
+ /*
+ * For GTK-1 style list boxes, we tell it to
+ * focus one of its children, which appears to
+ * do the Right Thing.
+ */
+ gtk_container_focus(GTK_CONTAINER(sc->uc->list),
+ GTK_DIR_TAB_FORWARD);
+ break;
+ }
+#else
+ if (sc->uc->treeview) {
+ gtk_widget_grab_focus(sc->uc->treeview);
+ break;
+ }
+#endif
+ assert(!"We shouldn't get here");
+ break;
+ }
+ break;
+ }
+ }
+
+ return FALSE;
+}
+
+#if !GTK_CHECK_VERSION(2,0,0)
+int tree_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data)
+{
+ struct dlgparam *dp = (struct dlgparam *)data;
+
+ if (event->keyval == GDK_Up || event->keyval == GDK_KP_Up ||
+ event->keyval == GDK_Down || event->keyval == GDK_KP_Down) {
+ int dir, i, j = -1;
+ for (i = 0; i < dp->ntreeitems; i++)
+ if (widget == dp->treeitems[i])
+ break;
+ if (i < dp->ntreeitems) {
+ if (event->keyval == GDK_Up || event->keyval == GDK_KP_Up)
+ dir = -1;
+ else
+ dir = +1;
+
+ while (1) {
+ i += dir;
+ if (i < 0 || i >= dp->ntreeitems)
+ break; /* nothing in that dir to select */
+ /*
+ * Determine if this tree item is visible.
+ */
+ {
+ GtkWidget *w = dp->treeitems[i];
+ int vis = TRUE;
+ while (w && (GTK_IS_TREE_ITEM(w) || GTK_IS_TREE(w))) {
+ if (!GTK_WIDGET_VISIBLE(w)) {
+ vis = FALSE;
+ break;
+ }
+ w = w->parent;
+ }
+ if (vis) {
+ j = i; /* got one */
+ break;
+ }
+ }
+ }
+ }
+ gtk_signal_emit_stop_by_name(GTK_OBJECT(widget),
+ "key_press_event");
+ if (j >= 0) {
+ gtk_signal_emit_by_name(GTK_OBJECT(dp->treeitems[j]), "toggle");
+ gtk_widget_grab_focus(dp->treeitems[j]);
+ }
+ return TRUE;
+ }
+
+ /*
+ * It's nice for Left and Right to expand and collapse tree
+ * branches.
+ */
+ if (event->keyval == GDK_Left || event->keyval == GDK_KP_Left) {
+ gtk_signal_emit_stop_by_name(GTK_OBJECT(widget),
+ "key_press_event");
+ gtk_tree_item_collapse(GTK_TREE_ITEM(widget));
+ return TRUE;
+ }
+ if (event->keyval == GDK_Right || event->keyval == GDK_KP_Right) {
+ gtk_signal_emit_stop_by_name(GTK_OBJECT(widget),
+ "key_press_event");
+ gtk_tree_item_expand(GTK_TREE_ITEM(widget));
+ return TRUE;
+ }
+
+ return FALSE;
+}
+#endif
+
+static void shortcut_highlight(GtkWidget *labelw, int chr)
+{
+ GtkLabel *label = GTK_LABEL(labelw);
+ gchar *currstr, *pattern;
+ int i;
+
+ gtk_label_get(label, &currstr);
+ for (i = 0; currstr[i]; i++)
+ if (tolower((unsigned char)currstr[i]) == chr) {
+ GtkRequisition req;
+
+ pattern = dupprintf("%*s_", i, "");
+
+ gtk_widget_size_request(GTK_WIDGET(label), &req);
+ gtk_label_set_pattern(label, pattern);
+ gtk_widget_set_usize(GTK_WIDGET(label), -1, req.height);
+
+ sfree(pattern);
+ break;
+ }
+}
+
+void shortcut_add(struct Shortcuts *scs, GtkWidget *labelw,
+ int chr, int action, void *ptr)
+{
+ if (chr == NO_SHORTCUT)
+ return;
+
+ chr = tolower((unsigned char)chr);
+
+ assert(scs->sc[chr].action == SHORTCUT_EMPTY);
+
+ scs->sc[chr].action = action;
+
+ if (action == SHORTCUT_FOCUS) {
+ scs->sc[chr].uc = NULL;
+ scs->sc[chr].widget = (GtkWidget *)ptr;
+ } else {
+ scs->sc[chr].widget = NULL;
+ scs->sc[chr].uc = (struct uctrl *)ptr;
+ }
+
+ shortcut_highlight(labelw, chr);
+}
+
+int get_listitemheight(GtkWidget *w)
+{
+#if !GTK_CHECK_VERSION(2,0,0)
+ GtkWidget *listitem = gtk_list_item_new_with_label("foo");
+ GtkRequisition req;
+ gtk_widget_size_request(listitem, &req);
+ gtk_object_sink(GTK_OBJECT(listitem));
+ return req.height;
+#else
+ int height;
+ GtkCellRenderer *cr = gtk_cell_renderer_text_new();
+ gtk_cell_renderer_get_size(cr, w, NULL, NULL, NULL, NULL, &height);
+ g_object_ref(G_OBJECT(cr));
+ gtk_object_sink(GTK_OBJECT(cr));
+ g_object_unref(G_OBJECT(cr));
+ return height;
+#endif
+}
+
+void set_dialog_action_area(GtkDialog *dlg, GtkWidget *w)
+{
+#if !GTK_CHECK_VERSION(2,0,0)
+
+ /*
+ * In GTK 1, laying out the buttons at the bottom of the
+ * configuration box is nice and easy, because a GtkDialog's
+ * action_area is a GtkHBox which stretches to cover the full
+ * width of the dialog. So we just put our Columns widget
+ * straight into that hbox, and it ends up just where we want
+ * it.
+ */
+ gtk_box_pack_start(GTK_BOX(dlg->action_area), w, TRUE, TRUE, 0);
+
+#else
+ /*
+ * In GTK 2, the action area is now a GtkHButtonBox and its
+ * layout behaviour seems to be different: it doesn't stretch
+ * to cover the full width of the window, but instead finds its
+ * own preferred width and right-aligns that within the window.
+ * This isn't what we want, because we have both left-aligned
+ * and right-aligned buttons coming out of the above call to
+ * layout_ctrls(), and right-aligning the whole thing will
+ * result in the former being centred and looking weird.
+ *
+ * So instead we abandon the dialog's action area completely:
+ * we gtk_widget_hide() it in the below code, and we also call
+ * gtk_dialog_set_has_separator() to remove the separator above
+ * it. We then insert our own action area into the end of the
+ * dialog's main vbox, and add our own separator above that.
+ *
+ * (Ideally, if we were a native GTK app, we would use the
+ * GtkHButtonBox's _own_ innate ability to support one set of
+ * buttons being right-aligned and one left-aligned. But to do
+ * that here, we would have to either (a) pick apart our cross-
+ * platform layout structures and treat them specially for this
+ * particular set of controls, which would be painful, or else
+ * (b) develop a special and simpler cross-platform
+ * representation for these particular controls, and introduce
+ * special-case code into all the _other_ platforms to handle
+ * it. Neither appeals. Therefore, I regretfully discard the
+ * GTKHButtonBox and go it alone.)
+ */
+
+ GtkWidget *align;
+ align = gtk_alignment_new(0, 0, 1, 1);
+ gtk_container_add(GTK_CONTAINER(align), w);
+ /*
+ * The purpose of this GtkAlignment is to provide padding
+ * around the buttons. The padding we use is twice the padding
+ * used in our GtkColumns, because we nest two GtkColumns most
+ * of the time (one separating the tree view from the main
+ * controls, and another for the main controls themselves).
+ */
+#if GTK_CHECK_VERSION(2,4,0)
+ gtk_alignment_set_padding(GTK_ALIGNMENT(align), 8, 8, 8, 8);
+#endif
+ gtk_widget_show(align);
+ gtk_box_pack_end(GTK_BOX(dlg->vbox), align, FALSE, TRUE, 0);
+ w = gtk_hseparator_new();
+ gtk_box_pack_end(GTK_BOX(dlg->vbox), w, FALSE, TRUE, 0);
+ gtk_widget_show(w);
+ 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 protcfginfo)
+{
+ GtkWidget *window, *hbox, *vbox, *cols, *label,
+ *tree, *treescroll, *panels, *panelvbox;
+ int index, level;
+ struct controlbox *ctrlbox;
+ char *path;
+#if GTK_CHECK_VERSION(2,0,0)
+ GtkTreeStore *treestore;
+ GtkCellRenderer *treerenderer;
+ GtkTreeViewColumn *treecolumn;
+ GtkTreeSelection *treeselection;
+ GtkTreeIter treeiterlevels[8];
+#else
+ GtkTreeItem *treeitemlevels[8];
+ GtkTree *treelevels[8];
+#endif
+ struct dlgparam dp;
+ struct Shortcuts scs;
+
+ struct selparam *selparams = NULL;
+ int nselparams = 0, selparamsize = 0;
+
+ dlg_init(&dp);
+
+ for (index = 0; index < lenof(scs.sc); index++) {
+ scs.sc[index].action = SHORTCUT_EMPTY;
+ }
+
+ window = gtk_dialog_new();
+
+ ctrlbox = ctrl_new_box();
+ setup_config_box(ctrlbox, midsession, cfg->protocol, protcfginfo);
+ unix_setup_config_box(ctrlbox, midsession, cfg->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);
+ gtk_container_set_border_width(GTK_CONTAINER(hbox), 10);
+ gtk_widget_show(hbox);
+ vbox = gtk_vbox_new(FALSE, 4);
+ gtk_box_pack_start(GTK_BOX(hbox), vbox, FALSE, FALSE, 0);
+ gtk_widget_show(vbox);
+ cols = columns_new(4);
+ gtk_box_pack_start(GTK_BOX(vbox), cols, FALSE, FALSE, 0);
+ gtk_widget_show(cols);
+ label = gtk_label_new("Category:");
+ columns_add(COLUMNS(cols), label, 0, 1);
+ columns_force_left_align(COLUMNS(cols), label);
+ gtk_widget_show(label);
+ treescroll = gtk_scrolled_window_new(NULL, NULL);
+#if GTK_CHECK_VERSION(2,0,0)
+ treestore = gtk_tree_store_new
+ (TREESTORE_NUM, G_TYPE_STRING, G_TYPE_INT);
+ tree = gtk_tree_view_new_with_model(GTK_TREE_MODEL(treestore));
+ gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(tree), FALSE);
+ treerenderer = gtk_cell_renderer_text_new();
+ treecolumn = gtk_tree_view_column_new_with_attributes
+ ("Label", treerenderer, "text", 0, NULL);
+ gtk_tree_view_append_column(GTK_TREE_VIEW(tree), treecolumn);
+ treeselection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree));
+ gtk_tree_selection_set_mode(treeselection, GTK_SELECTION_BROWSE);
+ gtk_container_add(GTK_CONTAINER(treescroll), tree);
+#else
+ tree = gtk_tree_new();
+ gtk_tree_set_view_mode(GTK_TREE(tree), GTK_TREE_VIEW_ITEM);
+ gtk_tree_set_selection_mode(GTK_TREE(tree), GTK_SELECTION_BROWSE);
+ gtk_signal_connect(GTK_OBJECT(tree), "focus",
+ GTK_SIGNAL_FUNC(tree_focus), &dp);
+#endif
+ gtk_signal_connect(GTK_OBJECT(tree), "focus_in_event",
+ GTK_SIGNAL_FUNC(widget_focus), &dp);
+ shortcut_add(&scs, label, 'g', SHORTCUT_TREE, tree);
+ gtk_widget_show(treescroll);
+ gtk_box_pack_start(GTK_BOX(vbox), treescroll, TRUE, TRUE, 0);
+ panels = gtk_notebook_new();
+ gtk_notebook_set_show_tabs(GTK_NOTEBOOK(panels), FALSE);
+ gtk_notebook_set_show_border(GTK_NOTEBOOK(panels), FALSE);
+ gtk_box_pack_start(GTK_BOX(hbox), panels, TRUE, TRUE, 0);
+ gtk_widget_show(panels);
+
+ panelvbox = NULL;
+ path = NULL;
+ level = 0;
+ for (index = 0; index < ctrlbox->nctrlsets; index++) {
+ struct controlset *s = ctrlbox->ctrlsets[index];
+ GtkWidget *w;
+
+ if (!*s->pathname) {
+ w = layout_ctrls(&dp, &scs, s, GTK_WINDOW(window));
+
+ set_dialog_action_area(GTK_DIALOG(window), w);
+ } else {
+ int j = path ? ctrl_path_compare(s->pathname, path) : 0;
+ if (j != INT_MAX) { /* add to treeview, start new panel */
+ char *c;
+#if GTK_CHECK_VERSION(2,0,0)
+ GtkTreeIter treeiter;
+#else
+ GtkWidget *treeitem;
+#endif
+ int first;
+
+ /*
+ * We expect never to find an implicit path
+ * component. For example, we expect never to see
+ * A/B/C followed by A/D/E, because that would
+ * _implicitly_ create A/D. All our path prefixes
+ * are expected to contain actual controls and be
+ * selectable in the treeview; so we would expect
+ * to see A/D _explicitly_ before encountering
+ * A/D/E.
+ */
+ assert(j == ctrl_path_elements(s->pathname) - 1);
+
+ c = strrchr(s->pathname, '/');
+ if (!c)
+ c = s->pathname;
+ else
+ c++;
+
+ path = s->pathname;
+
+ first = (panelvbox == NULL);
+
+ panelvbox = gtk_vbox_new(FALSE, 4);
+ gtk_widget_show(panelvbox);
+ gtk_notebook_append_page(GTK_NOTEBOOK(panels), panelvbox,
+ NULL);
+ if (first) {
+ gint page_num;
+
+ page_num = gtk_notebook_page_num(GTK_NOTEBOOK(panels),
+ panelvbox);
+ gtk_notebook_set_page(GTK_NOTEBOOK(panels), page_num);
+ }
+
+ if (nselparams >= selparamsize) {
+ selparamsize += 16;
+ selparams = sresize(selparams, selparamsize,
+ struct selparam);
+ }
+ selparams[nselparams].dp = &dp;
+ selparams[nselparams].panels = GTK_NOTEBOOK(panels);
+ selparams[nselparams].panel = panelvbox;
+ selparams[nselparams].shortcuts = scs; /* structure copy */
+
+ assert(j-1 < level);
+
+#if GTK_CHECK_VERSION(2,0,0)
+ if (j > 0)
+ /* treeiterlevels[j-1] will always be valid because we
+ * don't allow implicit path components; see above.
+ */
+ gtk_tree_store_append(treestore, &treeiter,
+ &treeiterlevels[j-1]);
+ else
+ gtk_tree_store_append(treestore, &treeiter, NULL);
+ gtk_tree_store_set(treestore, &treeiter,
+ TREESTORE_PATH, c,
+ TREESTORE_PARAMS, nselparams,
+ -1);
+ treeiterlevels[j] = treeiter;
+
+ selparams[nselparams].depth = j;
+ if (j > 0) {
+ selparams[nselparams].treepath =
+ gtk_tree_model_get_path(GTK_TREE_MODEL(treestore),
+ &treeiterlevels[j-1]);
+ /*
+ * We are going to collapse all tree branches
+ * at depth greater than 2, but not _yet_; see
+ * the comment at the call to
+ * gtk_tree_view_collapse_row below.
+ */
+ gtk_tree_view_expand_row(GTK_TREE_VIEW(tree),
+ selparams[nselparams].treepath,
+ FALSE);
+ } else {
+ selparams[nselparams].treepath = NULL;
+ }
+#else
+ treeitem = gtk_tree_item_new_with_label(c);
+ if (j > 0) {
+ if (!treelevels[j-1]) {
+ treelevels[j-1] = GTK_TREE(gtk_tree_new());
+ gtk_tree_item_set_subtree
+ (treeitemlevels[j-1],
+ GTK_WIDGET(treelevels[j-1]));
+ if (j < 2)
+ gtk_tree_item_expand(treeitemlevels[j-1]);
+ else
+ gtk_tree_item_collapse(treeitemlevels[j-1]);
+ }
+ gtk_tree_append(treelevels[j-1], treeitem);
+ } else {
+ gtk_tree_append(GTK_TREE(tree), treeitem);
+ }
+ treeitemlevels[j] = GTK_TREE_ITEM(treeitem);
+ treelevels[j] = NULL;
+
+ gtk_signal_connect(GTK_OBJECT(treeitem), "key_press_event",
+ GTK_SIGNAL_FUNC(tree_key_press), &dp);
+ gtk_signal_connect(GTK_OBJECT(treeitem), "focus_in_event",
+ GTK_SIGNAL_FUNC(widget_focus), &dp);
+
+ gtk_widget_show(treeitem);
+
+ if (first)
+ gtk_tree_select_child(GTK_TREE(tree), treeitem);
+ selparams[nselparams].treeitem = treeitem;
+#endif
+
+ level = j+1;
+ nselparams++;
+ }
+
+ w = layout_ctrls(&dp, &selparams[nselparams-1].shortcuts, s, NULL);
+ gtk_box_pack_start(GTK_BOX(panelvbox), w, FALSE, FALSE, 0);
+ gtk_widget_show(w);
+ }
+ }
+
+#if GTK_CHECK_VERSION(2,0,0)
+ {
+ GtkRequisition req;
+ int i;
+
+ /*
+ * We want our tree view to come up with all branches at
+ * depth 2 or more collapsed. However, if we start off
+ * with those branches collapsed, then the tree view's
+ * size request will be calculated based on the width of
+ * the collapsed tree. So instead we start with them all
+ * expanded; then we ask for the current size request,
+ * collapse the relevant rows, and force the width to the
+ * value we just computed. This arranges that the tree
+ * view is wide enough to have all branches expanded
+ * safely.
+ */
+
+ gtk_widget_size_request(tree, &req);
+
+ for (i = 0; i < nselparams; i++)
+ if (selparams[i].depth >= 2)
+ gtk_tree_view_collapse_row(GTK_TREE_VIEW(tree),
+ selparams[i].treepath);
+
+ gtk_widget_set_size_request(tree, req.width, -1);
+ }
+#endif
+
+#if GTK_CHECK_VERSION(2,0,0)
+ g_signal_connect(G_OBJECT(treeselection), "changed",
+ G_CALLBACK(treeselection_changed), selparams);
+#else
+ dp.ntreeitems = nselparams;
+ dp.treeitems = snewn(dp.ntreeitems, GtkWidget *);
+
+ for (index = 0; index < nselparams; index++) {
+ gtk_signal_connect(GTK_OBJECT(selparams[index].treeitem), "select",
+ GTK_SIGNAL_FUNC(treeitem_sel),
+ &selparams[index]);
+ dp.treeitems[index] = selparams[index].treeitem;
+ }
+#endif
+
+ dp.data = cfg;
+ dlg_refresh(NULL, &dp);
+
+ dp.shortcuts = &selparams[0].shortcuts;
+#if !GTK_CHECK_VERSION(2,0,0)
+ dp.currtreeitem = dp.treeitems[0];
+#endif
+ dp.lastfocus = NULL;
+ dp.retval = 0;
+ dp.window = window;
+
+ {
+ /* in gtkwin.c */
+ extern void set_window_icon(GtkWidget *window,
+ const char *const *const *icon,
+ int n_icon);
+ extern const char *const *const cfg_icon[];
+ extern const int n_cfg_icon;
+ set_window_icon(window, cfg_icon, n_cfg_icon);
+ }
+
+#if !GTK_CHECK_VERSION(2,0,0)
+ gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(treescroll),
+ tree);
+#endif
+ gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(treescroll),
+ GTK_POLICY_NEVER,
+ GTK_POLICY_AUTOMATIC);
+ gtk_widget_show(tree);
+
+ gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
+ gtk_widget_show(window);
+
+ /*
+ * Set focus into the first available control.
+ */
+ for (index = 0; index < ctrlbox->nctrlsets; index++) {
+ struct controlset *s = ctrlbox->ctrlsets[index];
+ int done = 0;
+ int j;
+
+ if (*s->pathname) {
+ for (j = 0; j < s->ncontrols; j++)
+ if (s->ctrls[j]->generic.type != CTRL_TABDELAY &&
+ s->ctrls[j]->generic.type != CTRL_COLUMNS &&
+ s->ctrls[j]->generic.type != CTRL_TEXT) {
+ dlg_set_focus(s->ctrls[j], &dp);
+ dp.lastfocus = s->ctrls[j];
+ done = 1;
+ break;
+ }
+ }
+ if (done)
+ break;
+ }
+
+ gtk_signal_connect(GTK_OBJECT(window), "destroy",
+ GTK_SIGNAL_FUNC(window_destroy), NULL);
+ gtk_signal_connect(GTK_OBJECT(window), "key_press_event",
+ GTK_SIGNAL_FUNC(win_key_press), &dp);
+
+ gtk_main();
+
+ dlg_cleanup(&dp);
+ sfree(selparams);
+
+ return dp.retval;
+}
+
+static void messagebox_handler(union control *ctrl, void *dlg,
+ void *data, int event)
+{
+ if (event == EVENT_ACTION)
+ dlg_end(dlg, ctrl->generic.context.i);
+}
+int messagebox(GtkWidget *parentwin, char *title, char *msg, int minwid, ...)
+{
+ GtkWidget *window, *w0, *w1;
+ struct controlbox *ctrlbox;
+ struct controlset *s0, *s1;
+ union control *c;
+ struct dlgparam dp;
+ struct Shortcuts scs;
+ int index, ncols;
+ va_list ap;
+
+ dlg_init(&dp);
+
+ for (index = 0; index < lenof(scs.sc); index++) {
+ scs.sc[index].action = SHORTCUT_EMPTY;
+ }
+
+ ctrlbox = ctrl_new_box();
+
+ ncols = 0;
+ va_start(ap, minwid);
+ while (va_arg(ap, char *) != NULL) {
+ ncols++;
+ (void) va_arg(ap, int); /* shortcut */
+ (void) va_arg(ap, int); /* normal/default/cancel */
+ (void) va_arg(ap, int); /* end value */
+ }
+ va_end(ap);
+
+ s0 = ctrl_getset(ctrlbox, "", "", "");
+ c = ctrl_columns(s0, 2, 50, 50);
+ c->columns.ncols = s0->ncolumns = ncols;
+ c->columns.percentages = sresize(c->columns.percentages, ncols, int);
+ for (index = 0; index < ncols; index++)
+ c->columns.percentages[index] = (index+1)*100/ncols - index*100/ncols;
+ va_start(ap, minwid);
+ index = 0;
+ while (1) {
+ char *title = va_arg(ap, char *);
+ int shortcut, type, value;
+ if (title == NULL)
+ break;
+ shortcut = va_arg(ap, int);
+ type = va_arg(ap, int);
+ value = va_arg(ap, int);
+ c = ctrl_pushbutton(s0, title, shortcut, HELPCTX(no_help),
+ messagebox_handler, I(value));
+ c->generic.column = index++;
+ if (type > 0)
+ c->button.isdefault = TRUE;
+ else if (type < 0)
+ c->button.iscancel = TRUE;
+ }
+ va_end(ap);
+
+ s1 = ctrl_getset(ctrlbox, "x", "", "");
+ ctrl_text(s1, msg, HELPCTX(no_help));
+
+ window = gtk_dialog_new();
+ gtk_window_set_title(GTK_WINDOW(window), title);
+ w0 = layout_ctrls(&dp, &scs, s0, GTK_WINDOW(window));
+ set_dialog_action_area(GTK_DIALOG(window), w0);
+ gtk_widget_show(w0);
+ w1 = layout_ctrls(&dp, &scs, s1, GTK_WINDOW(window));
+ gtk_container_set_border_width(GTK_CONTAINER(w1), 10);
+ gtk_widget_set_usize(w1, minwid+20, -1);
+ gtk_box_pack_start(GTK_BOX(GTK_DIALOG(window)->vbox),
+ w1, TRUE, TRUE, 0);
+ gtk_widget_show(w1);
+
+ dp.shortcuts = &scs;
+ dp.lastfocus = NULL;
+ dp.retval = 0;
+ dp.window = window;
+
+ gtk_window_set_modal(GTK_WINDOW(window), TRUE);
+ if (parentwin) {
+ set_transient_window_pos(parentwin, window);
+ gtk_window_set_transient_for(GTK_WINDOW(window),
+ GTK_WINDOW(parentwin));
+ } else
+ gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
+ gtk_widget_show(window);
+
+ gtk_signal_connect(GTK_OBJECT(window), "destroy",
+ GTK_SIGNAL_FUNC(window_destroy), NULL);
+ gtk_signal_connect(GTK_OBJECT(window), "key_press_event",
+ GTK_SIGNAL_FUNC(win_key_press), &dp);
+
+ gtk_main();
+
+ dlg_cleanup(&dp);
+ ctrl_free_box(ctrlbox);
+
+ return dp.retval;
+}
+
+static 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));
+ return req.width;
+}
+
+int reallyclose(void *frontend)
+{
+ char *title = dupcat(appname, " Exit Confirmation", NULL);
+ int ret = messagebox(GTK_WIDGET(get_window(frontend)),
+ title, "Are you sure you want to close this session?",
+ string_width("Most of the width of the above text"),
+ "Yes", 'y', +1, 1,
+ "No", 'n', -1, 0,
+ NULL);
+ sfree(title);
+ return ret;
+}
+
+int verify_ssh_host_key(void *frontend, char *host, int port, char *keytype,
+ char *keystr, char *fingerprint,
+ void (*callback)(void *ctx, int result), void *ctx)
+{
+ static const char absenttxt[] =
+ "The server's host key is not cached. You have no guarantee "
+ "that the server is the computer you think it is.\n"
+ "The server's %s key fingerprint is:\n"
+ "%s\n"
+ "If you trust this host, press \"Accept\" to add the key to "
+ "PuTTY's cache and carry on connecting.\n"
+ "If you want to carry on connecting just once, without "
+ "adding the key to the cache, press \"Connect Once\".\n"
+ "If you do not trust this host, press \"Cancel\" to abandon the "
+ "connection.";
+ static const char wrongtxt[] =
+ "WARNING - POTENTIAL SECURITY BREACH!\n"
+ "The server's host key does not match the one PuTTY has "
+ "cached. This means that either the server administrator "
+ "has changed the host key, or you have actually connected "
+ "to another computer pretending to be the server.\n"
+ "The new %s key fingerprint is:\n"
+ "%s\n"
+ "If you were expecting this change and trust the new key, "
+ "press \"Accept\" to update PuTTY's cache and continue connecting.\n"
+ "If you want to carry on connecting but without updating "
+ "the cache, press \"Connect Once\".\n"
+ "If you want to abandon the connection completely, press "
+ "\"Cancel\" to cancel. Pressing \"Cancel\" is the ONLY guaranteed "
+ "safe choice.";
+ char *text;
+ int ret;
+
+ /*
+ * Verify the key.
+ */
+ ret = verify_host_key(host, port, keytype, keystr);
+
+ if (ret == 0) /* success - key matched OK */
+ return 1;
+
+ text = dupprintf((ret == 2 ? wrongtxt : absenttxt), keytype, fingerprint);
+
+ ret = messagebox(GTK_WIDGET(get_window(frontend)),
+ "PuTTY Security Alert", text,
+ string_width(fingerprint),
+ "Accept", 'a', 0, 2,
+ "Connect Once", 'o', 0, 1,
+ "Cancel", 'c', -1, 0,
+ NULL);
+
+ sfree(text);
+
+ if (ret == 2) {
+ store_host_key(host, port, keytype, keystr);
+ return 1; /* continue with connection */
+ } else if (ret == 1)
+ return 1; /* continue with connection */
+ return 0; /* do not continue with connection */
+}
+
+/*
+ * Ask whether the selected algorithm is acceptable (since it was
+ * below the configured 'warn' threshold).
+ */
+int askalg(void *frontend, const char *algtype, const char *algname,
+ void (*callback)(void *ctx, int result), void *ctx)
+{
+ static const char msg[] =
+ "The first %s supported by the server is "
+ "%s, which is below the configured warning threshold.\n"
+ "Continue with connection?";
+ char *text;
+ int ret;
+
+ text = dupprintf(msg, algtype, algname);
+ ret = messagebox(GTK_WIDGET(get_window(frontend)),
+ "PuTTY Security Alert", text,
+ string_width("Continue with connection?"),
+ "Yes", 'y', 0, 1,
+ "No", 'n', 0, 0,
+ NULL);
+ sfree(text);
+
+ if (ret) {
+ return 1;
+ } else {
+ return 0;
+ }
+}
+
+void old_keyfile_warning(void)
+{
+ /*
+ * This should never happen on Unix. We hope.
+ */
+}
+
+void fatal_message_box(void *window, char *msg)
+{
+ messagebox(window, "PuTTY Fatal 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;
+ va_start(ap, p);
+ msg = dupvprintf(p, ap);
+ va_end(ap);
+ fatal_message_box(NULL, msg);
+ sfree(msg);
+ cleanup_exit(1);
+}
+
+static GtkWidget *aboutbox = NULL;
+
+static void about_close_clicked(GtkButton *button, gpointer data)
+{
+ gtk_widget_destroy(aboutbox);
+ aboutbox = NULL;
+}
+
+static void licence_clicked(GtkButton *button, gpointer data)
+{
+ char *title;
+
+ char *licence =
+ "Copyright 1997-2011 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"
+
+ "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, "
+ "and to permit persons to whom the Software is furnished to do so, "
+ "subject to the following conditions:\n\n"
+
+ "The above copyright notice and this permission notice shall be "
+ "included in all copies or substantial portions of the Software.\n\n"
+
+ "THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT "
+ "WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, "
+ "INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF "
+ "MERCHANTABILITY, FITNESS FOR A PARTICULAR "
+ "PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE "
+ "COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES "
+ "OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, "
+ "TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN "
+ "CONNECTION WITH THE SOFTWARE OR THE USE OR "
+ "OTHER DEALINGS IN THE SOFTWARE.";
+
+ title = dupcat(appname, " Licence", NULL);
+ assert(aboutbox != NULL);
+ messagebox(aboutbox, title, licence,
+ string_width("LONGISH LINE OF TEXT SO THE LICENCE"
+ " BOX ISN'T EXCESSIVELY TALL AND THIN"),
+ "OK", 'o', 1, 1, NULL);
+ sfree(title);
+}
+
+void about_box(void *window)
+{
+ GtkWidget *w;
+ char *title;
+
+ if (aboutbox) {
+ gtk_widget_grab_focus(aboutbox);
+ return;
+ }
+
+ aboutbox = gtk_dialog_new();
+ gtk_container_set_border_width(GTK_CONTAINER(aboutbox), 10);
+ title = dupcat("About ", appname, NULL);
+ gtk_window_set_title(GTK_WINDOW(aboutbox), title);
+ sfree(title);
+
+ w = gtk_button_new_with_label("Close");
+ GTK_WIDGET_SET_FLAGS(w, GTK_CAN_DEFAULT);
+ gtk_window_set_default(GTK_WINDOW(aboutbox), w);
+ gtk_box_pack_end(GTK_BOX(GTK_DIALOG(aboutbox)->action_area),
+ w, FALSE, FALSE, 0);
+ gtk_signal_connect(GTK_OBJECT(w), "clicked",
+ GTK_SIGNAL_FUNC(about_close_clicked), NULL);
+ gtk_widget_show(w);
+
+ w = gtk_button_new_with_label("View Licence");
+ GTK_WIDGET_SET_FLAGS(w, GTK_CAN_DEFAULT);
+ gtk_box_pack_end(GTK_BOX(GTK_DIALOG(aboutbox)->action_area),
+ w, FALSE, FALSE, 0);
+ gtk_signal_connect(GTK_OBJECT(w), "clicked",
+ GTK_SIGNAL_FUNC(licence_clicked), NULL);
+ gtk_widget_show(w);
+
+ w = gtk_label_new(appname);
+ gtk_box_pack_start(GTK_BOX(GTK_DIALOG(aboutbox)->vbox),
+ w, FALSE, FALSE, 0);
+ gtk_widget_show(w);
+
+ 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");
+ 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);
+ gtk_window_set_transient_for(GTK_WINDOW(aboutbox),
+ GTK_WINDOW(window));
+ gtk_widget_show(aboutbox);
+}
+
+struct eventlog_stuff {
+ GtkWidget *parentwin, *window;
+ struct controlbox *eventbox;
+ struct Shortcuts scs;
+ struct dlgparam dp;
+ union control *listctrl;
+ char **events;
+ int nevents, negsize;
+ char *seldata;
+ int sellen;
+ int ignore_selchange;
+};
+
+static void eventlog_destroy(GtkWidget *widget, gpointer data)
+{
+ struct eventlog_stuff *es = (struct eventlog_stuff *)data;
+
+ es->window = NULL;
+ sfree(es->seldata);
+ dlg_cleanup(&es->dp);
+ ctrl_free_box(es->eventbox);
+}
+static void eventlog_ok_handler(union control *ctrl, void *dlg,
+ void *data, int event)
+{
+ if (event == EVENT_ACTION)
+ dlg_end(dlg, 0);
+}
+static void eventlog_list_handler(union control *ctrl, void *dlg,
+ void *data, int event)
+{
+ struct eventlog_stuff *es = (struct eventlog_stuff *)data;
+
+ if (event == EVENT_REFRESH) {
+ int i;
+
+ dlg_update_start(ctrl, dlg);
+ dlg_listbox_clear(ctrl, dlg);
+ for (i = 0; i < es->nevents; i++) {
+ dlg_listbox_add(ctrl, dlg, es->events[i]);
+ }
+ dlg_update_done(ctrl, dlg);
+ } else if (event == EVENT_SELCHANGE) {
+ int i;
+ int selsize = 0;
+
+ /*
+ * If this SELCHANGE event is happening as a result of
+ * deliberate deselection because someone else has grabbed
+ * the selection, the last thing we want to do is pre-empt
+ * them.
+ */
+ if (es->ignore_selchange)
+ return;
+
+ /*
+ * Construct the data to use as the selection.
+ */
+ sfree(es->seldata);
+ es->seldata = NULL;
+ es->sellen = 0;
+ for (i = 0; i < es->nevents; i++) {
+ if (dlg_listbox_issel(ctrl, dlg, i)) {
+ int extralen = strlen(es->events[i]);
+
+ if (es->sellen + extralen + 2 > selsize) {
+ selsize = es->sellen + extralen + 512;
+ es->seldata = sresize(es->seldata, selsize, char);
+ }
+
+ strcpy(es->seldata + es->sellen, es->events[i]);
+ es->sellen += extralen;
+ es->seldata[es->sellen++] = '\n';
+ }
+ }
+
+ if (gtk_selection_owner_set(es->window, GDK_SELECTION_PRIMARY,
+ GDK_CURRENT_TIME)) {
+ extern GdkAtom compound_text_atom;
+
+ gtk_selection_add_target(es->window, GDK_SELECTION_PRIMARY,
+ GDK_SELECTION_TYPE_STRING, 1);
+ gtk_selection_add_target(es->window, GDK_SELECTION_PRIMARY,
+ compound_text_atom, 1);
+ }
+
+ }
+}
+
+void eventlog_selection_get(GtkWidget *widget, GtkSelectionData *seldata,
+ guint info, guint time_stamp, gpointer data)
+{
+ struct eventlog_stuff *es = (struct eventlog_stuff *)data;
+
+ gtk_selection_data_set(seldata, seldata->target, 8,
+ (unsigned char *)es->seldata, es->sellen);
+}
+
+gint eventlog_selection_clear(GtkWidget *widget, GdkEventSelection *seldata,
+ gpointer data)
+{
+ struct eventlog_stuff *es = (struct eventlog_stuff *)data;
+ struct uctrl *uc;
+
+ /*
+ * Deselect everything in the list box.
+ */
+ uc = dlg_find_byctrl(&es->dp, es->listctrl);
+ es->ignore_selchange = 1;
+#if !GTK_CHECK_VERSION(2,0,0)
+ assert(uc->list);
+ gtk_list_unselect_all(GTK_LIST(uc->list));
+#else
+ assert(uc->treeview);
+ gtk_tree_selection_unselect_all
+ (gtk_tree_view_get_selection(GTK_TREE_VIEW(uc->treeview)));
+#endif
+ es->ignore_selchange = 0;
+
+ sfree(es->seldata);
+ es->sellen = 0;
+ es->seldata = NULL;
+ return TRUE;
+}
+
+void showeventlog(void *estuff, void *parentwin)
+{
+ struct eventlog_stuff *es = (struct eventlog_stuff *)estuff;
+ GtkWidget *window, *w0, *w1;
+ GtkWidget *parent = GTK_WIDGET(parentwin);
+ struct controlset *s0, *s1;
+ union control *c;
+ int index;
+ char *title;
+
+ if (es->window) {
+ gtk_widget_grab_focus(es->window);
+ return;
+ }
+
+ dlg_init(&es->dp);
+
+ for (index = 0; index < lenof(es->scs.sc); index++) {
+ es->scs.sc[index].action = SHORTCUT_EMPTY;
+ }
+
+ es->eventbox = ctrl_new_box();
+
+ s0 = ctrl_getset(es->eventbox, "", "", "");
+ ctrl_columns(s0, 3, 33, 34, 33);
+ c = ctrl_pushbutton(s0, "Close", 'c', HELPCTX(no_help),
+ eventlog_ok_handler, P(NULL));
+ c->button.column = 1;
+ c->button.isdefault = TRUE;
+
+ s1 = ctrl_getset(es->eventbox, "x", "", "");
+ es->listctrl = c = ctrl_listbox(s1, NULL, NO_SHORTCUT, HELPCTX(no_help),
+ eventlog_list_handler, P(es));
+ c->listbox.height = 10;
+ c->listbox.multisel = 2;
+ c->listbox.ncols = 3;
+ c->listbox.percentages = snewn(3, int);
+ c->listbox.percentages[0] = 25;
+ c->listbox.percentages[1] = 10;
+ c->listbox.percentages[2] = 65;
+
+ es->window = window = gtk_dialog_new();
+ title = dupcat(appname, " Event Log", NULL);
+ gtk_window_set_title(GTK_WINDOW(window), title);
+ sfree(title);
+ w0 = layout_ctrls(&es->dp, &es->scs, s0, GTK_WINDOW(window));
+ set_dialog_action_area(GTK_DIALOG(window), w0);
+ gtk_widget_show(w0);
+ w1 = layout_ctrls(&es->dp, &es->scs, s1, GTK_WINDOW(window));
+ gtk_container_set_border_width(GTK_CONTAINER(w1), 10);
+ gtk_widget_set_usize(w1, 20 +
+ string_width("LINE OF TEXT GIVING WIDTH OF EVENT LOG"
+ " IS QUITE LONG 'COS SSH LOG ENTRIES"
+ " ARE WIDE"), -1);
+ gtk_box_pack_start(GTK_BOX(GTK_DIALOG(window)->vbox),
+ w1, TRUE, TRUE, 0);
+ gtk_widget_show(w1);
+
+ es->dp.data = es;
+ es->dp.shortcuts = &es->scs;
+ es->dp.lastfocus = NULL;
+ es->dp.retval = 0;
+ es->dp.window = window;
+
+ dlg_refresh(NULL, &es->dp);
+
+ if (parent) {
+ set_transient_window_pos(parent, window);
+ gtk_window_set_transient_for(GTK_WINDOW(window),
+ GTK_WINDOW(parent));
+ } else
+ gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
+ gtk_widget_show(window);
+
+ gtk_signal_connect(GTK_OBJECT(window), "destroy",
+ GTK_SIGNAL_FUNC(eventlog_destroy), es);
+ gtk_signal_connect(GTK_OBJECT(window), "key_press_event",
+ GTK_SIGNAL_FUNC(win_key_press), &es->dp);
+ gtk_signal_connect(GTK_OBJECT(window), "selection_get",
+ GTK_SIGNAL_FUNC(eventlog_selection_get), es);
+ gtk_signal_connect(GTK_OBJECT(window), "selection_clear_event",
+ GTK_SIGNAL_FUNC(eventlog_selection_clear), es);
+}
+
+void *eventlogstuff_new(void)
+{
+ struct eventlog_stuff *es;
+ es = snew(struct eventlog_stuff);
+ memset(es, 0, sizeof(*es));
+ return es;
+}
+
+void logevent_dlg(void *estuff, const char *string)
+{
+ struct eventlog_stuff *es = (struct eventlog_stuff *)estuff;
+
+ char timebuf[40];
+ struct tm tm;
+
+ if (es->nevents >= es->negsize) {
+ es->negsize += 64;
+ es->events = sresize(es->events, es->negsize, char *);
+ }
+
+ tm=ltime();
+ strftime(timebuf, sizeof(timebuf), "%Y-%m-%d %H:%M:%S\t", &tm);
+
+ es->events[es->nevents] = snewn(strlen(timebuf) + strlen(string) + 1, char);
+ strcpy(es->events[es->nevents], timebuf);
+ strcat(es->events[es->nevents], string);
+ if (es->window) {
+ dlg_listbox_add(es->listctrl, &es->dp, es->events[es->nevents]);
+ }
+ es->nevents++;
+}
+
+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, "
+ "append your session log to the end of it, "
+ "or disable session logging for this session.";
+ char *message;
+ char *mbtitle;
+ int mbret;
+
+ 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"),
+ "Overwrite", 'o', 1, 2,
+ "Append", 'a', 0, 1,
+ "Disable", 'd', -1, 0,
+ NULL);
+
+ sfree(message);
+ sfree(mbtitle);
+
+ return mbret;
+}
--- /dev/null
+/*
+ * Unified font management for GTK.
+ *
+ * PuTTY is willing to use both old-style X server-side bitmap
+ * fonts _and_ GTK2/Pango client-side fonts. This requires us to
+ * do a bit of work to wrap the two wildly different APIs into
+ * forms the rest of the code can switch between seamlessly, and
+ * also requires a custom font selector capable of handling both
+ * types of font.
+ */
+
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+#include <gdk/gdkx.h>
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/Xatom.h>
+
+#include "putty.h"
+#include "gtkfont.h"
+#include "tree234.h"
+
+/*
+ * Future work:
+ *
+ * - 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.
+ * 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.
+ */
+
+/*
+ * Ad-hoc vtable mechanism to allow font structures to be
+ * polymorphic.
+ *
+ * Any instance of `unifont' used in the vtable functions will
+ * actually be the first element of a larger structure containing
+ * data specific to the subtype. This is permitted by the ISO C
+ * provision that one may safely cast between a pointer to a
+ * structure and a pointer to its first element.
+ */
+
+#define FONTFLAG_CLIENTSIDE 0x0001
+#define FONTFLAG_SERVERSIDE 0x0002
+#define FONTFLAG_SERVERALIAS 0x0004
+#define FONTFLAG_NONMONOSPACED 0x0008
+
+#define FONTFLAG_SORT_MASK 0x0007 /* used to disambiguate font families */
+
+typedef void (*fontsel_add_entry)(void *ctx, const char *realfontname,
+ const char *family, const char *charset,
+ const char *style, const char *stylekey,
+ int size, int flags,
+ const struct unifont_vtable *fontclass);
+
+struct unifont_vtable {
+ /*
+ * `Methods' of the `class'.
+ */
+ unifont *(*create)(GtkWidget *widget, const char *name, int wide, int bold,
+ int shadowoffset, int shadowalways);
+ void (*destroy)(unifont *font);
+ void (*draw_text)(GdkDrawable *target, GdkGC *gc, unifont *font,
+ int x, int y, const char *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);
+ char *(*scale_fontname)(GtkWidget *widget, const char *name, int size);
+
+ /*
+ * `Static data members' of the `class'.
+ */
+ const char *prefix;
+};
+
+/* ----------------------------------------------------------------------
+ * GDK-based X11 font implementation.
+ */
+
+static void x11font_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font,
+ int x, int y, const char *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);
+static void x11font_enum_fonts(GtkWidget *widget,
+ fontsel_add_entry callback, void *callback_ctx);
+static char *x11font_canonify_fontname(GtkWidget *widget, const char *name,
+ int *size, int *flags,
+ int resolve_aliases);
+static char *x11font_scale_fontname(GtkWidget *widget, const char *name,
+ int size);
+
+struct x11font {
+ struct unifont u;
+ /*
+ * Actual font objects. We store a number of these, for
+ * automatically guessed bold and wide variants.
+ *
+ * The parallel array `allocated' indicates whether we've
+ * 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];
+ 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().
+ */
+ 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;
+ /*
+ * Data passed in to unifont_create().
+ */
+ int wide, bold, shadowoffset, shadowalways;
+};
+
+static const struct unifont_vtable x11font_vtable = {
+ x11font_create,
+ x11font_destroy,
+ 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)
+{
+ XFontStruct *xfs = GDK_FONT_XFONT(font);
+ Display *disp = GDK_FONT_XDISPLAY(font);
+ Atom fontprop = XInternAtom(disp, "FONT", False);
+ unsigned long ret;
+ if (XGetFontProperty(xfs, fontprop, &ret)) {
+ char *name = XGetAtomName(disp, (Atom)ret);
+ if (name && name[0] == '-') {
+ char *strings[13];
+ char *dupname, *extrafree = NULL, *ret;
+ char *p, *q;
+ int nstr;
+
+ p = q = dupname = dupstr(name); /* skip initial minus */
+ nstr = 0;
+
+ while (*p && nstr < lenof(strings)) {
+ if (*p == '-') {
+ *p = '\0';
+ strings[nstr++] = p+1;
+ }
+ p++;
+ }
+
+ if (nstr < lenof(strings))
+ return NULL; /* XLFD was malformed */
+
+ if (bold)
+ strings[2] = "bold";
+
+ if (wide) {
+ /* 4 is `wideness', which obviously may have changed. */
+ /* 5 is additional style, which may be e.g. `ja' or `ko'. */
+ strings[4] = strings[5] = "*";
+ strings[11] = extrafree = dupprintf("%d", 2*atoi(strings[11]));
+ }
+
+ ret = dupcat("-", strings[ 0], "-", strings[ 1], "-", strings[ 2],
+ "-", strings[ 3], "-", strings[ 4], "-", strings[ 5],
+ "-", strings[ 6], "-", strings[ 7], "-", strings[ 8],
+ "-", strings[ 9], "-", strings[10], "-", strings[11],
+ "-", strings[12], NULL);
+ sfree(extrafree);
+ sfree(dupname);
+
+ return ret;
+ }
+ }
+ return NULL;
+}
+
+static int x11_font_width(GdkFont *font, int sixteen_bit)
+{
+ if (sixteen_bit) {
+ XChar2b space;
+ space.byte1 = 0;
+ space.byte2 = '0';
+ return gdk_text_width(font, (const gchar *)&space, 2);
+ } else {
+ return gdk_char_width(font, '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;
+ 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)
+ 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;
+ variable = TRUE;
+
+ if (XGetFontProperty(xfs, charset_registry, ®istry_ret) &&
+ XGetFontProperty(xfs, charset_encoding, &encoding_ret)) {
+ char *reg, *enc;
+ reg = XGetAtomName(disp, (Atom)registry_ret);
+ enc = XGetAtomName(disp, (Atom)encoding_ret);
+ if (reg && enc) {
+ char *encoding = dupcat(reg, "-", enc, NULL);
+ pubcs = realcs = charset_from_xenc(encoding);
+
+ /*
+ * iso10646-1 is the only wide font encoding we
+ * support. In this case, we expect clients to give us
+ * UTF-8, which this module must internally convert
+ * into 16-bit Unicode.
+ */
+ if (!strcasecmp(encoding, "iso10646-1")) {
+ 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.
+ */
+ 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;
+ }
+
+ sfree(encoding);
+ }
+ }
+
+ spacing = XInternAtom(disp, "SPACING", False);
+ if (XGetFontProperty(xfs, spacing, &spacing_ret)) {
+ char *spc;
+ spc = XGetAtomName(disp, (Atom)spacing_ret);
+
+ if (spc && strchr("CcMm", spc[0]))
+ 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.height = xfont->u.ascent + xfont->u.descent;
+ xfont->u.public_charset = pubcs;
+ xfont->u.real_charset = realcs;
+ xfont->fonts[0] = font;
+ xfont->allocated[0] = TRUE;
+ xfont->sixteen_bit = sixteen_bit;
+ xfont->variable = variable;
+ xfont->wide = wide;
+ xfont->bold = bold;
+ xfont->shadowoffset = shadowoffset;
+ xfont->shadowalways = shadowalways;
+
+ for (i = 1; i < lenof(xfont->fonts); i++) {
+ xfont->fonts[i] = NULL;
+ xfont->allocated[i] = FALSE;
+ }
+
+ return (unifont *)xfont;
+}
+
+static void x11font_destroy(unifont *font)
+{
+ 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]);
+ sfree(font);
+}
+
+static void x11_alloc_subfont(struct x11font *xfont, int sfid)
+{
+ 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->allocated[sfid] = TRUE;
+ sfree(derived_name);
+}
+
+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)
+{
+ int step = clen * nchars, nsteps = 1, centre = FALSE;
+
+ if (fontvariable) {
+ /*
+ * In a variable-pitch font, we draw one character at a
+ * time, and centre it in the character cell.
+ */
+ step = clen;
+ nsteps = nchars;
+ centre = TRUE;
+ }
+
+ while (nsteps-- > 0) {
+ int X = x;
+ if (centre)
+ X += (cellwidth - gdk_text_width(font, 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);
+
+ x += cellwidth;
+ string += step;
+ }
+}
+
+static void x11font_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font,
+ int x, int y, const char *string, int len,
+ int wide, int bold, int cellwidth)
+{
+ struct x11font *xfont = (struct x11font *)font;
+ int sfid;
+ int shadowbold = FALSE;
+ int mult = (wide ? 2 : 1);
+
+ wide -= xfont->wide;
+ bold -= xfont->bold;
+
+ /*
+ * Decide which subfont we're using, and whether we have to
+ * use shadow bold.
+ */
+ if (xfont->shadowalways && bold) {
+ shadowbold = TRUE;
+ bold = 0;
+ }
+ sfid = 2 * wide + bold;
+ if (!xfont->allocated[sfid])
+ x11_alloc_subfont(xfont, sfid);
+ if (bold && !xfont->fonts[sfid]) {
+ bold = 0;
+ shadowbold = TRUE;
+ 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 */
+
+ 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.
+ */
+ 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);
+ }
+}
+
+static void x11font_enum_fonts(GtkWidget *widget,
+ fontsel_add_entry callback, void *callback_ctx)
+{
+ char **fontnames;
+ char *tmp = NULL;
+ int nnames, i, max, tmpsize;
+
+ max = 32768;
+ while (1) {
+ fontnames = XListFonts(GDK_DISPLAY(), "*", max, &nnames);
+ if (nnames >= max) {
+ XFreeFontNames(fontnames);
+ max *= 2;
+ } else
+ break;
+ }
+
+ tmpsize = 0;
+
+ for (i = 0; i < nnames; i++) {
+ if (fontnames[i][0] == '-') {
+ /*
+ * Dismember an XLFD and convert it into the format
+ * we'll be using in the font selector.
+ */
+ char *components[14];
+ char *p, *font, *style, *stylekey, *charset;
+ int j, weightkey, slantkey, setwidthkey;
+ int thistmpsize, fontsize, flags;
+
+ thistmpsize = 4 * strlen(fontnames[i]) + 256;
+ if (tmpsize < thistmpsize) {
+ tmpsize = thistmpsize;
+ tmp = sresize(tmp, tmpsize, char);
+ }
+ strcpy(tmp, fontnames[i]);
+
+ p = tmp;
+ for (j = 0; j < 14; j++) {
+ if (*p)
+ *p++ = '\0';
+ components[j] = p;
+ while (*p && *p != '-')
+ p++;
+ }
+ *p++ = '\0';
+
+ /*
+ * Font name is made up of fields 0 and 1, in reverse
+ * order with parentheses. (This is what the GTK 1.2 X
+ * font selector does, and it seems to come out
+ * looking reasonably sensible.)
+ */
+ font = p;
+ p += 1 + sprintf(p, "%s (%s)", components[1], components[0]);
+
+ /*
+ * Charset is made up of fields 12 and 13.
+ */
+ charset = p;
+ p += 1 + sprintf(p, "%s-%s", components[12], components[13]);
+
+ /*
+ * Style is a mixture of quite a lot of the fields,
+ * with some strange formatting.
+ */
+ style = p;
+ p += sprintf(p, "%s", components[2][0] ? components[2] :
+ "regular");
+ if (!g_strcasecmp(components[3], "i"))
+ p += sprintf(p, " italic");
+ else if (!g_strcasecmp(components[3], "o"))
+ p += sprintf(p, " oblique");
+ else if (!g_strcasecmp(components[3], "ri"))
+ p += sprintf(p, " reverse italic");
+ else if (!g_strcasecmp(components[3], "ro"))
+ p += sprintf(p, " reverse oblique");
+ else if (!g_strcasecmp(components[3], "ot"))
+ p += sprintf(p, " other-slant");
+ if (components[4][0] && g_strcasecmp(components[4], "normal"))
+ p += sprintf(p, " %s", components[4]);
+ if (!g_strcasecmp(components[10], "m"))
+ p += sprintf(p, " [M]");
+ if (!g_strcasecmp(components[10], "c"))
+ p += sprintf(p, " [C]");
+ if (components[5][0])
+ p += sprintf(p, " %s", components[5]);
+
+ /*
+ * Style key is the same stuff as above, but with a
+ * 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"))
+ weightkey = 0;
+ else if (!g_strncasecmp(components[2], "demi", 4) ||
+ !g_strncasecmp(components[2], "semi", 4))
+ weightkey = 1;
+ else
+ weightkey = 2;
+ if (!g_strcasecmp(components[3], "r"))
+ slantkey = 0;
+ else if (!g_strncasecmp(components[3], "r", 1))
+ slantkey = 2;
+ else
+ slantkey = 1;
+ if (!g_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",
+ weightkey,
+ (int)strlen(components[2]), components[2],
+ slantkey,
+ (int)strlen(components[3]), components[3],
+ setwidthkey,
+ (int)strlen(components[4]), components[4],
+ (int)strlen(components[10]), components[10],
+ (int)strlen(components[5]), components[5]);
+
+ assert(p - tmp < thistmpsize);
+
+ /*
+ * Size is in pixels, for our application, so we
+ * derive it directly from the pixel size field,
+ * number 6.
+ */
+ fontsize = atoi(components[6]);
+
+ /*
+ * Flags: we need to know whether this is a monospaced
+ * font, which we do by examining the spacing field
+ * again.
+ */
+ flags = FONTFLAG_SERVERSIDE;
+ if (!strchr("CcMm", components[10][0]))
+ flags |= FONTFLAG_NONMONOSPACED;
+
+ /*
+ * Not sure why, but sometimes the X server will
+ * deliver dummy font types in which fontsize comes
+ * out as zero. Filter those out.
+ */
+ if (fontsize)
+ callback(callback_ctx, fontnames[i], font, charset,
+ style, stylekey, fontsize, flags, &x11font_vtable);
+ } else {
+ /*
+ * This isn't an XLFD, so it must be an alias.
+ * Transmit it with mostly null data.
+ *
+ * It would be nice to work out if it's monospaced
+ * here, but at the moment I can't see that being
+ * anything but computationally hideous. Ah well.
+ */
+ callback(callback_ctx, fontnames[i], fontnames[i], NULL,
+ NULL, NULL, 0, FONTFLAG_SERVERALIAS, &x11font_vtable);
+ }
+ }
+ XFreeFontNames(fontnames);
+}
+
+static char *x11font_canonify_fontname(GtkWidget *widget, const char *name,
+ int *size, int *flags,
+ int resolve_aliases)
+{
+ /*
+ * When given an X11 font name to try to make sense of for a
+ * font selector, we must attempt to load it (to see if it
+ * exists), and then canonify it by extracting its FONT
+ * property, which should give its full XLFD even if what we
+ * originally had was a wildcard.
+ *
+ * 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;
+ Atom fontprop, fontprop2;
+ unsigned long ret;
+
+ if (!font)
+ 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) {
+ unsigned long fsize = 12;
+
+ fontprop2 = XInternAtom(disp, "PIXEL_SIZE", False);
+ if (XGetFontProperty(xfs, fontprop2, &fsize) && fsize > 0) {
+ *size = fsize;
+ gdk_font_unref(font);
+ if (flags) {
+ if (name[0] == '-' || resolve_aliases)
+ *flags = FONTFLAG_SERVERSIDE;
+ else
+ *flags = FONTFLAG_SERVERALIAS;
+ }
+ return dupstr(name[0] == '-' || resolve_aliases ?
+ newname : name);
+ }
+ }
+ }
+
+ gdk_font_unref(font);
+ return NULL; /* something went wrong */
+}
+
+static char *x11font_scale_fontname(GtkWidget *widget, const char *name,
+ int size)
+{
+ return NULL; /* shan't */
+}
+
+#if GTK_CHECK_VERSION(2,0,0)
+
+/* ----------------------------------------------------------------------
+ * Pango font implementation (for GTK 2 only).
+ */
+
+#if defined PANGO_PRE_1POINT4 && !defined PANGO_PRE_1POINT6
+#define PANGO_PRE_1POINT6 /* make life easier for pre-1.4 folk */
+#endif
+
+static void pangofont_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font,
+ int x, int y, const char *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 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,
+ int resolve_aliases);
+static char *pangofont_scale_fontname(GtkWidget *widget, const char *name,
+ int size);
+
+struct pangofont {
+ struct unifont u;
+ /*
+ * Pango objects.
+ */
+ PangoFontDescription *desc;
+ PangoFontset *fset;
+ /*
+ * The containing widget.
+ */
+ GtkWidget *widget;
+ /*
+ * Data passed in to unifont_create().
+ */
+ int bold, shadowoffset, shadowalways;
+};
+
+static const struct unifont_vtable pangofont_vtable = {
+ pangofont_create,
+ pangofont_destroy,
+ pangofont_draw_text,
+ pangofont_enum_fonts,
+ pangofont_canonify_fontname,
+ pangofont_scale_fontname,
+ "client",
+};
+
+/*
+ * This function is used to rigorously validate a
+ * PangoFontDescription. Later versions of Pango have a nasty
+ * habit of accepting _any_ old string as input to
+ * pango_font_description_from_string and returning a font
+ * description which can actually be used to display text, even if
+ * they have to do it by falling back to their most default font.
+ * This is doubtless helpful in some situations, but not here,
+ * because we need to know if a Pango font string actually _makes
+ * sense_ in order to fall back to treating it as an X font name
+ * if it doesn't. So we check that the font family is actually one
+ * supported by Pango.
+ */
+static int pangofont_check_desc_makes_sense(PangoContext *ctx,
+ PangoFontDescription *desc)
+{
+#ifndef PANGO_PRE_1POINT6
+ PangoFontMap *map;
+#endif
+ PangoFontFamily **families;
+ int i, nfamilies, matched;
+
+ /*
+ * Ask Pango for a list of font families, and iterate through
+ * them to see if one of them matches the family in the
+ * PangoFontDescription.
+ */
+#ifndef PANGO_PRE_1POINT6
+ map = pango_context_get_font_map(ctx);
+ if (!map)
+ return FALSE;
+ pango_font_map_list_families(map, &families, &nfamilies);
+#else
+ 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))) {
+ 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)
+{
+ 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;
+ }
+ fset = pango_font_map_load_fontset(map, ctx, desc,
+ pango_context_get_language(ctx));
+#else
+ fset = pango_context_load_fontset(ctx, desc,
+ pango_context_get_language(ctx));
+#endif
+ if (!fset) {
+ pango_font_description_free(desc);
+ return NULL;
+ }
+ metrics = pango_fontset_get_metrics(fset);
+ if (!metrics ||
+ pango_font_metrics_get_approximate_digit_width(metrics) == 0) {
+ pango_font_description_free(desc);
+ g_object_unref(fset);
+ return NULL;
+ }
+
+ pfont = snew(struct pangofont);
+ pfont->u.vt = &pangofont_vtable;
+ 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;
+ /* 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;
+ pfont->shadowalways = shadowalways;
+
+ pango_font_metrics_unref(metrics);
+
+ return (unifont *)pfont;
+}
+
+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 void pangofont_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font,
+ int x, int y, const char *string, int len,
+ int wide, int bold, int cellwidth)
+{
+ struct pangofont *pfont = (struct pangofont *)font;
+ PangoLayout *layout;
+ PangoRectangle rect;
+ int shadowbold = FALSE;
+
+ if (wide)
+ cellwidth *= 2;
+
+ y -= pfont->u.ascent;
+
+ layout = pango_layout_new(gtk_widget_get_pango_context(pfont->widget));
+ pango_layout_set_font_description(layout, pfont->desc);
+ if (bold > pfont->bold) {
+ if (pfont->shadowalways)
+ shadowbold = TRUE;
+ else {
+ PangoFontDescription *desc2 =
+ pango_font_description_copy_static(pfont->desc);
+ pango_font_description_set_weight(desc2, PANGO_WEIGHT_BOLD);
+ pango_layout_set_font_description(layout, desc2);
+ }
+ }
+
+ while (len > 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,
+ * this requires a separate text-drawing call for each
+ * character; but in the common case where the font is
+ * properly fixed-width, we can draw many characters in one
+ * go which is much faster.
+ *
+ * This still isn't really ideal. If you look at what
+ * happens in the X protocol as a result of all of this, you
+ * find - naturally enough - that each call to
+ * gdk_draw_layout() generates a separate set of X RENDER
+ * operations involving creating a picture, setting a clip
+ * rectangle, doing some drawing and undoing the whole lot.
+ * In an ideal world, we should _always_ be able to turn the
+ * contents of this loop into a single RenderCompositeGlyphs
+ * operation which internally specifies inter-character
+ * deltas to get the spacing right, which would give us full
+ * speed _even_ in the worst case of a non-fixed-width font.
+ * However, Pango's architecture and documentation are so
+ * unhelpful that I have no idea how if at all to persuade
+ * them to do that.
+ */
+
+ /*
+ * 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)
+ 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);
+ 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;
+ x += n * cellwidth;
+ }
+
+ g_object_unref(layout);
+}
+
+/*
+ * Dummy size value to be used when converting a
+ * PangoFontDescription of a scalable font to a string for
+ * internal use.
+ */
+#define PANGO_DUMMY_SIZE 12
+
+static void pangofont_enum_fonts(GtkWidget *widget, fontsel_add_entry callback,
+ void *callback_ctx)
+{
+ PangoContext *ctx;
+#ifndef PANGO_PRE_1POINT6
+ PangoFontMap *map;
+#endif
+ PangoFontFamily **families;
+ int i, nfamilies;
+
+ ctx = gtk_widget_get_pango_context(widget);
+ if (!ctx)
+ return;
+
+ /*
+ * Ask Pango for a list of font families, and iterate through
+ * them.
+ */
+#ifndef PANGO_PRE_1POINT6
+ map = pango_context_get_font_map(ctx);
+ if (!map)
+ return;
+ pango_font_map_list_families(map, &families, &nfamilies);
+#else
+ pango_context_list_families(ctx, &families, &nfamilies);
+#endif
+ for (i = 0; i < nfamilies; i++) {
+ PangoFontFamily *family = families[i];
+ const char *familyname;
+ int flags;
+ PangoFontFace **faces;
+ int j, nfaces;
+
+ /*
+ * Set up our flags for this font family, and get the name
+ * string.
+ */
+ flags = FONTFLAG_CLIENTSIDE;
+#ifndef PANGO_PRE_1POINT4
+ /*
+ * In very early versions of Pango, we can't tell
+ * monospaced fonts from non-monospaced.
+ */
+ if (!pango_font_family_is_monospace(family))
+ flags |= FONTFLAG_NONMONOSPACED;
+#endif
+ familyname = pango_font_family_get_name(family);
+
+ /*
+ * Go through the available font faces in this family.
+ */
+ pango_font_family_list_faces(family, &faces, &nfaces);
+ for (j = 0; j < nfaces; j++) {
+ PangoFontFace *face = faces[j];
+ PangoFontDescription *desc;
+ const char *facename;
+ int *sizes;
+ int k, nsizes, dummysize;
+
+ /*
+ * Get the face name string.
+ */
+ facename = pango_font_face_get_face_name(face);
+
+ /*
+ * Set up a font description with what we've got so
+ * far. We'll fill in the size field manually and then
+ * call pango_font_description_to_string() to give the
+ * full real name of the specific font.
+ */
+ desc = pango_font_face_describe(face);
+
+ /*
+ * See if this font has a list of specific sizes.
+ */
+#ifndef PANGO_PRE_1POINT4
+ pango_font_face_list_sizes(face, &sizes, &nsizes);
+#else
+ /*
+ * In early versions of Pango, that call wasn't
+ * supported; we just have to assume everything is
+ * scalable.
+ */
+ sizes = NULL;
+#endif
+ if (!sizes) {
+ /*
+ * Write a single entry with a dummy size.
+ */
+ dummysize = PANGO_DUMMY_SIZE * PANGO_SCALE;
+ sizes = &dummysize;
+ nsizes = 1;
+ }
+
+ /*
+ * If so, go through them one by one.
+ */
+ for (k = 0; k < nsizes; k++) {
+ char *fullname;
+ char stylekey[128];
+
+ pango_font_description_set_size(desc, sizes[k]);
+
+ fullname = pango_font_description_to_string(desc);
+
+ /*
+ * Construct the sorting key for font styles.
+ */
+ {
+ char *p = stylekey;
+ int n;
+
+ n = pango_font_description_get_weight(desc);
+ /* Weight: normal, then lighter, then bolder */
+ if (n <= PANGO_WEIGHT_NORMAL)
+ n = PANGO_WEIGHT_NORMAL - n;
+ p += sprintf(p, "%4d", n);
+
+ n = pango_font_description_get_style(desc);
+ p += sprintf(p, " %2d", n);
+
+ n = pango_font_description_get_stretch(desc);
+ /* Stretch: closer to normal sorts earlier */
+ n = 2 * abs(PANGO_STRETCH_NORMAL - n) +
+ (n < PANGO_STRETCH_NORMAL);
+ p += sprintf(p, " %2d", n);
+
+ n = pango_font_description_get_variant(desc);
+ p += sprintf(p, " %2d", n);
+
+ }
+
+ /*
+ * Got everything. Hand off to the callback.
+ * (The charset string is NULL, because only
+ * server-side X fonts use it.)
+ */
+ callback(callback_ctx, fullname, familyname, NULL, facename,
+ stylekey,
+ (sizes == &dummysize ? 0 : PANGO_PIXELS(sizes[k])),
+ flags, &pangofont_vtable);
+
+ g_free(fullname);
+ }
+ if (sizes != &dummysize)
+ g_free(sizes);
+
+ pango_font_description_free(desc);
+ }
+ g_free(faces);
+ }
+ g_free(families);
+}
+
+static char *pangofont_canonify_fontname(GtkWidget *widget, const char *name,
+ int *size, int *flags,
+ int resolve_aliases)
+{
+ /*
+ * When given a Pango font name to try to make sense of for a
+ * font selector, we must normalise it to PANGO_DUMMY_SIZE and
+ * extract its original size (in pixels) into the `size' field.
+ */
+ PangoContext *ctx;
+#ifndef PANGO_PRE_1POINT6
+ PangoFontMap *map;
+#endif
+ PangoFontDescription *desc;
+ PangoFontset *fset;
+ PangoFontMetrics *metrics;
+ char *newname, *retname;
+
+ 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;
+ }
+ fset = pango_font_map_load_fontset(map, ctx, desc,
+ pango_context_get_language(ctx));
+#else
+ fset = pango_context_load_fontset(ctx, desc,
+ pango_context_get_language(ctx));
+#endif
+ if (!fset) {
+ pango_font_description_free(desc);
+ return NULL;
+ }
+ metrics = pango_fontset_get_metrics(fset);
+ if (!metrics ||
+ pango_font_metrics_get_approximate_digit_width(metrics) == 0) {
+ pango_font_description_free(desc);
+ g_object_unref(fset);
+ return NULL;
+ }
+
+ *size = PANGO_PIXELS(pango_font_description_get_size(desc));
+ *flags = FONTFLAG_CLIENTSIDE;
+ pango_font_description_set_size(desc, PANGO_DUMMY_SIZE * PANGO_SCALE);
+ newname = pango_font_description_to_string(desc);
+ retname = dupstr(newname);
+ g_free(newname);
+
+ pango_font_metrics_unref(metrics);
+ pango_font_description_free(desc);
+ g_object_unref(fset);
+
+ return retname;
+}
+
+static char *pangofont_scale_fontname(GtkWidget *widget, const char *name,
+ int size)
+{
+ PangoFontDescription *desc;
+ char *newname, *retname;
+
+ desc = pango_font_description_from_string(name);
+ if (!desc)
+ return NULL;
+ pango_font_description_set_size(desc, size * PANGO_SCALE);
+ newname = pango_font_description_to_string(desc);
+ retname = dupstr(newname);
+ g_free(newname);
+ pango_font_description_free(desc);
+
+ return retname;
+}
+
+#endif /* GTK_CHECK_VERSION(2,0,0) */
+
+/* ----------------------------------------------------------------------
+ * Outermost functions which do the vtable dispatch.
+ */
+
+/*
+ * 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.)
+ */
+static const struct unifont_vtable *unifont_types[] = {
+#if GTK_CHECK_VERSION(2,0,0)
+ &pangofont_vtable,
+#endif
+ &x11font_vtable,
+};
+
+/*
+ * Function which takes a font name and processes the optional
+ * scheme prefix. Returns the tail of the font name suitable for
+ * passing to individual font scheme functions, and also provides
+ * a subrange of the unifont_types[] array above.
+ *
+ * The return values `start' and `end' denote a half-open interval
+ * in unifont_types[]; that is, the correct way to iterate over
+ * them is
+ *
+ * for (i = start; i < end; i++) {...}
+ */
+static const char *unifont_do_prefix(const char *name, int *start, int *end)
+{
+ int colonpos = strcspn(name, ":");
+ int i;
+
+ if (name[colonpos]) {
+ /*
+ * There's a colon prefix on the font name. Use it to work
+ * out which subclass to use.
+ */
+ for (i = 0; i < lenof(unifont_types); i++) {
+ if (strlen(unifont_types[i]->prefix) == colonpos &&
+ !strncmp(unifont_types[i]->prefix, name, colonpos)) {
+ *start = i;
+ *end = i+1;
+ return name + colonpos + 1;
+ }
+ }
+ /*
+ * None matched, so return an empty scheme list to prevent
+ * any scheme from being called at all.
+ */
+ *start = *end = 0;
+ return name + colonpos + 1;
+ } else {
+ /*
+ * No colon prefix, so just use all the subclasses.
+ */
+ *start = 0;
+ *end = lenof(unifont_types);
+ return name;
+ }
+}
+
+unifont *unifont_create(GtkWidget *widget, const char *name, int wide,
+ int bold, int shadowoffset, int shadowalways)
+{
+ int i, start, end;
+
+ name = unifont_do_prefix(name, &start, &end);
+
+ for (i = start; i < end; i++) {
+ unifont *ret = unifont_types[i]->create(widget, name, wide, bold,
+ shadowoffset, shadowalways);
+ if (ret)
+ return ret;
+ }
+ return NULL; /* font not found in any scheme */
+}
+
+void unifont_destroy(unifont *font)
+{
+ font->vt->destroy(font);
+}
+
+void unifont_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font,
+ int x, int y, const char *string, int len,
+ int wide, int bold, int cellwidth)
+{
+ font->vt->draw_text(target, gc, font, x, y, string, len,
+ wide, bold, cellwidth);
+}
+
+#if GTK_CHECK_VERSION(2,0,0)
+
+/* ----------------------------------------------------------------------
+ * Implementation of a unified font selector. Used on GTK 2 only;
+ * for GTK 1 we still use the standard font selector.
+ */
+
+typedef struct fontinfo fontinfo;
+
+typedef struct unifontsel_internal {
+ /* This must be the structure's first element, for cross-casting */
+ unifontsel u;
+ GtkListStore *family_model, *style_model, *size_model;
+ GtkWidget *family_list, *style_list, *size_entry, *size_list;
+ GtkWidget *filter_buttons[4];
+ GtkWidget *preview_area;
+ GdkPixmap *preview_pixmap;
+ int preview_width, preview_height;
+ GdkColor preview_fg, preview_bg;
+ int filter_flags;
+ tree234 *fonts_by_realname, *fonts_by_selorder;
+ fontinfo *selected;
+ int selsize, intendedsize;
+ int inhibit_response; /* inhibit callbacks when we change GUI controls */
+} unifontsel_internal;
+
+/*
+ * The structure held in the tree234s. All the string members are
+ * part of the same allocated area, so don't need freeing
+ * separately.
+ */
+struct fontinfo {
+ char *realname;
+ char *family, *charset, *style, *stylekey;
+ int size, flags;
+ /*
+ * Fallback sorting key, to permit multiple identical entries
+ * to exist in the selorder tree.
+ */
+ int index;
+ /*
+ * Indices mapping fontinfo structures to indices in the list
+ * boxes. sizeindex is irrelevant if the font is scalable
+ * (size==0).
+ */
+ int familyindex, styleindex, sizeindex;
+ /*
+ * The class of font.
+ */
+ const struct unifont_vtable *fontclass;
+};
+
+struct fontinfo_realname_find {
+ const char *realname;
+ int flags;
+};
+
+static int strnullcasecmp(const char *a, const char *b)
+{
+ int i;
+
+ /*
+ * If exactly one of the inputs is NULL, it compares before
+ * the other one.
+ */
+ if ((i = (!b) - (!a)) != 0)
+ return i;
+
+ /*
+ * NULL compares equal.
+ */
+ if (!a)
+ return 0;
+
+ /*
+ * Otherwise, ordinary strcasecmp.
+ */
+ return g_strcasecmp(a, b);
+}
+
+static int fontinfo_realname_compare(void *av, void *bv)
+{
+ fontinfo *a = (fontinfo *)av;
+ fontinfo *b = (fontinfo *)bv;
+ int i;
+
+ if ((i = strnullcasecmp(a->realname, b->realname)) != 0)
+ return i;
+ if ((a->flags & FONTFLAG_SORT_MASK) != (b->flags & FONTFLAG_SORT_MASK))
+ return ((a->flags & FONTFLAG_SORT_MASK) <
+ (b->flags & FONTFLAG_SORT_MASK) ? -1 : +1);
+ return 0;
+}
+
+static int fontinfo_realname_find(void *av, void *bv)
+{
+ struct fontinfo_realname_find *a = (struct fontinfo_realname_find *)av;
+ fontinfo *b = (fontinfo *)bv;
+ int i;
+
+ if ((i = strnullcasecmp(a->realname, b->realname)) != 0)
+ return i;
+ if ((a->flags & FONTFLAG_SORT_MASK) != (b->flags & FONTFLAG_SORT_MASK))
+ return ((a->flags & FONTFLAG_SORT_MASK) <
+ (b->flags & FONTFLAG_SORT_MASK) ? -1 : +1);
+ return 0;
+}
+
+static int fontinfo_selorder_compare(void *av, void *bv)
+{
+ fontinfo *a = (fontinfo *)av;
+ fontinfo *b = (fontinfo *)bv;
+ int i;
+ if ((i = strnullcasecmp(a->family, b->family)) != 0)
+ return i;
+ /*
+ * Font class comes immediately after family, so that fonts
+ * from different classes with the same family
+ */
+ if ((a->flags & FONTFLAG_SORT_MASK) != (b->flags & FONTFLAG_SORT_MASK))
+ return ((a->flags & FONTFLAG_SORT_MASK) <
+ (b->flags & FONTFLAG_SORT_MASK) ? -1 : +1);
+ if ((i = strnullcasecmp(a->charset, b->charset)) != 0)
+ return i;
+ if ((i = strnullcasecmp(a->stylekey, b->stylekey)) != 0)
+ return i;
+ if ((i = strnullcasecmp(a->style, b->style)) != 0)
+ return i;
+ if (a->size != b->size)
+ return (a->size < b->size ? -1 : +1);
+ if (a->index != b->index)
+ return (a->index < b->index ? -1 : +1);
+ return 0;
+}
+
+static void unifontsel_deselect(unifontsel_internal *fs)
+{
+ fs->selected = NULL;
+ gtk_list_store_clear(fs->style_model);
+ gtk_list_store_clear(fs->size_model);
+ gtk_widget_set_sensitive(fs->u.ok_button, FALSE);
+ gtk_widget_set_sensitive(fs->size_entry, FALSE);
+}
+
+static void unifontsel_setup_familylist(unifontsel_internal *fs)
+{
+ GtkTreeIter iter;
+ int i, listindex, minpos = -1, maxpos = -1;
+ char *currfamily = NULL;
+ int currflags = -1;
+ fontinfo *info;
+
+ gtk_list_store_clear(fs->family_model);
+ listindex = 0;
+
+ /*
+ * Search through the font tree for anything matching our
+ * current filter criteria. When we find one, add its font
+ * name to the list box.
+ */
+ for (i = 0 ;; i++) {
+ info = (fontinfo *)index234(fs->fonts_by_selorder, i);
+ /*
+ * info may be NULL if we've just run off the end of the
+ * tree. We must still do a processing pass in that
+ * situation, in case we had an unfinished font record in
+ * progress.
+ */
+ if (info && (info->flags &~ fs->filter_flags)) {
+ info->familyindex = -1;
+ continue; /* we're filtering out this font */
+ }
+ if (!info || strnullcasecmp(currfamily, info->family) ||
+ currflags != (info->flags & FONTFLAG_SORT_MASK)) {
+ /*
+ * We've either finished a family, or started a new
+ * one, or both.
+ */
+ if (currfamily) {
+ gtk_list_store_append(fs->family_model, &iter);
+ gtk_list_store_set(fs->family_model, &iter,
+ 0, currfamily, 1, minpos, 2, maxpos+1, -1);
+ listindex++;
+ }
+ if (info) {
+ minpos = i;
+ currfamily = info->family;
+ currflags = info->flags & FONTFLAG_SORT_MASK;
+ }
+ }
+ if (!info)
+ break; /* now we're done */
+ info->familyindex = listindex;
+ maxpos = i;
+ }
+
+ /*
+ * If we've just filtered out the previously selected font,
+ * deselect it thoroughly.
+ */
+ if (fs->selected && fs->selected->familyindex < 0)
+ unifontsel_deselect(fs);
+}
+
+static void unifontsel_setup_stylelist(unifontsel_internal *fs,
+ int start, int end)
+{
+ GtkTreeIter iter;
+ int i, listindex, minpos = -1, maxpos = -1, started = FALSE;
+ char *currcs = NULL, *currstyle = NULL;
+ fontinfo *info;
+
+ gtk_list_store_clear(fs->style_model);
+ listindex = 0;
+ started = FALSE;
+
+ /*
+ * Search through the font tree for anything matching our
+ * current filter criteria. When we find one, add its charset
+ * and/or style name to the list box.
+ */
+ for (i = start; i <= end; i++) {
+ if (i == end)
+ info = NULL;
+ else
+ info = (fontinfo *)index234(fs->fonts_by_selorder, i);
+ /*
+ * info may be NULL if we've just run off the end of the
+ * relevant data. We must still do a processing pass in
+ * that situation, in case we had an unfinished font
+ * record in progress.
+ */
+ if (info && (info->flags &~ fs->filter_flags)) {
+ info->styleindex = -1;
+ continue; /* we're filtering out this font */
+ }
+ if (!info || !started || strnullcasecmp(currcs, info->charset) ||
+ strnullcasecmp(currstyle, info->style)) {
+ /*
+ * We've either finished a style/charset, or started a
+ * new one, or both.
+ */
+ started = TRUE;
+ if (currstyle) {
+ gtk_list_store_append(fs->style_model, &iter);
+ gtk_list_store_set(fs->style_model, &iter,
+ 0, currstyle, 1, minpos, 2, maxpos+1,
+ 3, TRUE, -1);
+ listindex++;
+ }
+ if (info) {
+ minpos = i;
+ if (info->charset && strnullcasecmp(currcs, info->charset)) {
+ gtk_list_store_append(fs->style_model, &iter);
+ gtk_list_store_set(fs->style_model, &iter,
+ 0, info->charset, 1, -1, 2, -1,
+ 3, FALSE, -1);
+ listindex++;
+ }
+ currcs = info->charset;
+ currstyle = info->style;
+ }
+ }
+ if (!info)
+ break; /* now we're done */
+ info->styleindex = listindex;
+ maxpos = i;
+ }
+}
+
+static const int unifontsel_default_sizes[] = { 10, 12, 14, 16, 20, 24, 32 };
+
+static void unifontsel_setup_sizelist(unifontsel_internal *fs,
+ int start, int end)
+{
+ GtkTreeIter iter;
+ int i, listindex;
+ char sizetext[40];
+ fontinfo *info;
+
+ gtk_list_store_clear(fs->size_model);
+ listindex = 0;
+
+ /*
+ * Search through the font tree for anything matching our
+ * current filter criteria. When we find one, add its font
+ * name to the list box.
+ */
+ for (i = start; i < end; i++) {
+ info = (fontinfo *)index234(fs->fonts_by_selorder, i);
+ if (info->flags &~ fs->filter_flags) {
+ info->sizeindex = -1;
+ continue; /* we're filtering out this font */
+ }
+ if (info->size) {
+ sprintf(sizetext, "%d", info->size);
+ info->sizeindex = listindex;
+ gtk_list_store_append(fs->size_model, &iter);
+ gtk_list_store_set(fs->size_model, &iter,
+ 0, sizetext, 1, i, 2, info->size, -1);
+ listindex++;
+ } else {
+ int j;
+
+ assert(i == start);
+ assert(i+1 == end);
+
+ for (j = 0; j < lenof(unifontsel_default_sizes); j++) {
+ sprintf(sizetext, "%d", unifontsel_default_sizes[j]);
+ gtk_list_store_append(fs->size_model, &iter);
+ gtk_list_store_set(fs->size_model, &iter, 0, sizetext, 1, i,
+ 2, unifontsel_default_sizes[j], -1);
+ listindex++;
+ }
+ }
+ }
+}
+
+static void unifontsel_set_filter_buttons(unifontsel_internal *fs)
+{
+ int i;
+
+ for (i = 0; i < lenof(fs->filter_buttons); i++) {
+ int flagbit = GPOINTER_TO_INT(gtk_object_get_data
+ (GTK_OBJECT(fs->filter_buttons[i]),
+ "user-data"));
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(fs->filter_buttons[i]),
+ !!(fs->filter_flags & flagbit));
+ }
+}
+
+static void unifontsel_draw_preview_text(unifontsel_internal *fs)
+{
+ unifont *font;
+ char *sizename = NULL;
+ fontinfo *info = fs->selected;
+
+ if (info) {
+ sizename = info->fontclass->scale_fontname
+ (GTK_WIDGET(fs->u.window), info->realname, fs->selsize);
+ font = info->fontclass->create(GTK_WIDGET(fs->u.window),
+ sizename ? sizename : info->realname,
+ FALSE, FALSE, 0, 0);
+ } else
+ font = NULL;
+
+ if (fs->preview_pixmap) {
+ GdkGC *gc = gdk_gc_new(fs->preview_pixmap);
+ gdk_gc_set_foreground(gc, &fs->preview_bg);
+ gdk_draw_rectangle(fs->preview_pixmap, gc, 1, 0, 0,
+ fs->preview_width, fs->preview_height);
+ gdk_gc_set_foreground(gc, &fs->preview_fg);
+ if (font) {
+ /*
+ * The pangram used here is rather carefully
+ * constructed: it contains a sequence of very narrow
+ * letters (`jil') and a pair of adjacent very wide
+ * letters (`wm').
+ *
+ * If the user selects a proportional font, it will be
+ * coerced into fixed-width character cells when used
+ * in the actual terminal window. We therefore display
+ * it the same way in the preview pane, so as to show
+ * it the way it will actually be displayed - and we
+ * deliberately pick a pangram which will show the
+ * resulting miskerning at its worst.
+ *
+ * We aren't trying to sell people these fonts; we're
+ * trying to let them make an informed choice. Better
+ * that they find out the problems with using
+ * proportional fonts in terminal windows here than
+ * 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",
+ 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",
+ 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
+ * use them as matched quotes no matter what Unicode
+ * might say on the matter, so people can quickly
+ * check whether they look silly in a candidate font.
+ * The sequence #_@ is there to let people judge the
+ * suitability of the underscore as an effectively
+ * 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!?,.:;<>()[]{}\\/`'\"+*-=~#_@|%&^$",
+ 42, FALSE, FALSE, font->width);
+ }
+ gdk_gc_unref(gc);
+ gdk_window_invalidate_rect(fs->preview_area->window, NULL, FALSE);
+ }
+ if (font)
+ info->fontclass->destroy(font);
+
+ sfree(sizename);
+}
+
+static void unifontsel_select_font(unifontsel_internal *fs,
+ fontinfo *info, int size, int leftlist,
+ int size_is_explicit)
+{
+ int index;
+ int minval, maxval;
+ GtkTreePath *treepath;
+ GtkTreeIter iter;
+
+ fs->inhibit_response = TRUE;
+
+ fs->selected = info;
+ fs->selsize = size;
+ if (size_is_explicit)
+ fs->intendedsize = size;
+
+ gtk_widget_set_sensitive(fs->u.ok_button, TRUE);
+
+ /*
+ * Find the index of this fontinfo in the selorder list.
+ */
+ index = -1;
+ findpos234(fs->fonts_by_selorder, info, NULL, &index);
+ assert(index >= 0);
+
+ /*
+ * Adjust the font selector flags and redo the font family
+ * list box, if necessary.
+ */
+ if (leftlist <= 0 &&
+ (fs->filter_flags | info->flags) != fs->filter_flags) {
+ fs->filter_flags |= info->flags;
+ unifontsel_set_filter_buttons(fs);
+ unifontsel_setup_familylist(fs);
+ }
+
+ /*
+ * Find the appropriate family name and select it in the list.
+ */
+ assert(info->familyindex >= 0);
+ treepath = gtk_tree_path_new_from_indices(info->familyindex, -1);
+ gtk_tree_selection_select_path
+ (gtk_tree_view_get_selection(GTK_TREE_VIEW(fs->family_list)),
+ treepath);
+ gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(fs->family_list),
+ treepath, NULL, FALSE, 0.0, 0.0);
+ gtk_tree_model_get_iter(GTK_TREE_MODEL(fs->family_model), &iter, treepath);
+ gtk_tree_path_free(treepath);
+
+ /*
+ * Now set up the font style list.
+ */
+ gtk_tree_model_get(GTK_TREE_MODEL(fs->family_model), &iter,
+ 1, &minval, 2, &maxval, -1);
+ if (leftlist <= 1)
+ unifontsel_setup_stylelist(fs, minval, maxval);
+
+ /*
+ * Find the appropriate style name and select it in the list.
+ */
+ if (info->style) {
+ assert(info->styleindex >= 0);
+ treepath = gtk_tree_path_new_from_indices(info->styleindex, -1);
+ gtk_tree_selection_select_path
+ (gtk_tree_view_get_selection(GTK_TREE_VIEW(fs->style_list)),
+ treepath);
+ gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(fs->style_list),
+ treepath, NULL, FALSE, 0.0, 0.0);
+ gtk_tree_model_get_iter(GTK_TREE_MODEL(fs->style_model),
+ &iter, treepath);
+ gtk_tree_path_free(treepath);
+
+ /*
+ * And set up the size list.
+ */
+ gtk_tree_model_get(GTK_TREE_MODEL(fs->style_model), &iter,
+ 1, &minval, 2, &maxval, -1);
+ if (leftlist <= 2)
+ unifontsel_setup_sizelist(fs, minval, maxval);
+
+ /*
+ * Find the appropriate size, and select it in the list.
+ */
+ if (info->size) {
+ assert(info->sizeindex >= 0);
+ treepath = gtk_tree_path_new_from_indices(info->sizeindex, -1);
+ gtk_tree_selection_select_path
+ (gtk_tree_view_get_selection(GTK_TREE_VIEW(fs->size_list)),
+ treepath);
+ gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(fs->size_list),
+ treepath, NULL, FALSE, 0.0, 0.0);
+ gtk_tree_path_free(treepath);
+ size = info->size;
+ } else {
+ int j;
+ for (j = 0; j < lenof(unifontsel_default_sizes); j++)
+ if (unifontsel_default_sizes[j] == size) {
+ treepath = gtk_tree_path_new_from_indices(j, -1);
+ gtk_tree_view_set_cursor(GTK_TREE_VIEW(fs->size_list),
+ treepath, NULL, FALSE);
+ gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(fs->size_list),
+ treepath, NULL, FALSE, 0.0,
+ 0.0);
+ gtk_tree_path_free(treepath);
+ }
+ }
+
+ /*
+ * And set up the font size text entry box.
+ */
+ {
+ char sizetext[40];
+ sprintf(sizetext, "%d", size);
+ gtk_entry_set_text(GTK_ENTRY(fs->size_entry), sizetext);
+ }
+ } else {
+ if (leftlist <= 2)
+ unifontsel_setup_sizelist(fs, 0, 0);
+ gtk_entry_set_text(GTK_ENTRY(fs->size_entry), "");
+ }
+
+ /*
+ * Grey out the font size edit box if we're not using a
+ * scalable font.
+ */
+ gtk_entry_set_editable(GTK_ENTRY(fs->size_entry), fs->selected->size == 0);
+ gtk_widget_set_sensitive(fs->size_entry, fs->selected->size == 0);
+
+ unifontsel_draw_preview_text(fs);
+
+ fs->inhibit_response = FALSE;
+}
+
+static void unifontsel_button_toggled(GtkToggleButton *tb, gpointer data)
+{
+ unifontsel_internal *fs = (unifontsel_internal *)data;
+ int newstate = gtk_toggle_button_get_active(tb);
+ int newflags;
+ int flagbit = GPOINTER_TO_INT(gtk_object_get_data(GTK_OBJECT(tb),
+ "user-data"));
+
+ if (newstate)
+ newflags = fs->filter_flags | flagbit;
+ else
+ newflags = fs->filter_flags & ~flagbit;
+
+ if (fs->filter_flags != newflags) {
+ fs->filter_flags = newflags;
+ unifontsel_setup_familylist(fs);
+ }
+}
+
+static void unifontsel_add_entry(void *ctx, const char *realfontname,
+ const char *family, const char *charset,
+ const char *style, const char *stylekey,
+ int size, int flags,
+ const struct unifont_vtable *fontclass)
+{
+ unifontsel_internal *fs = (unifontsel_internal *)ctx;
+ fontinfo *info;
+ int totalsize;
+ char *p;
+
+ totalsize = sizeof(fontinfo) + strlen(realfontname) +
+ (family ? strlen(family) : 0) + (charset ? strlen(charset) : 0) +
+ (style ? strlen(style) : 0) + (stylekey ? strlen(stylekey) : 0) + 10;
+ info = (fontinfo *)smalloc(totalsize);
+ info->fontclass = fontclass;
+ p = (char *)info + sizeof(fontinfo);
+ info->realname = p;
+ strcpy(p, realfontname);
+ p += 1+strlen(p);
+ if (family) {
+ info->family = p;
+ strcpy(p, family);
+ p += 1+strlen(p);
+ } else
+ info->family = NULL;
+ if (charset) {
+ info->charset = p;
+ strcpy(p, charset);
+ p += 1+strlen(p);
+ } else
+ info->charset = NULL;
+ if (style) {
+ info->style = p;
+ strcpy(p, style);
+ p += 1+strlen(p);
+ } else
+ info->style = NULL;
+ if (stylekey) {
+ info->stylekey = p;
+ strcpy(p, stylekey);
+ p += 1+strlen(p);
+ } else
+ info->stylekey = NULL;
+ assert(p - (char *)info <= totalsize);
+ info->size = size;
+ info->flags = flags;
+ info->index = count234(fs->fonts_by_selorder);
+
+ /*
+ * It's just conceivable that a misbehaving font enumerator
+ * might tell us about the same font real name more than once,
+ * in which case we should silently drop the new one.
+ */
+ if (add234(fs->fonts_by_realname, info) != info) {
+ sfree(info);
+ return;
+ }
+ /*
+ * However, we should never get a duplicate key in the
+ * selorder tree, because the index field carefully
+ * disambiguates otherwise identical records.
+ */
+ add234(fs->fonts_by_selorder, info);
+}
+
+static fontinfo *update_for_intended_size(unifontsel_internal *fs,
+ fontinfo *info)
+{
+ fontinfo info2, *below, *above;
+ int pos;
+
+ /*
+ * Copy the info structure. This doesn't copy its dynamic
+ * string fields, but that's unimportant because all we're
+ * going to do is to adjust the size field and use it in one
+ * tree search.
+ */
+ info2 = *info;
+ info2.size = fs->intendedsize;
+
+ /*
+ * 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);
+ 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))
+ 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
+ * scale. Find out which, by NULLing out either of below and
+ * above if it differs from this one in any respect but size
+ * (and the disambiguating index field). Bear in mind, also,
+ * that either one might _already_ be NULL if we're at the
+ * extreme ends of the font list.
+ */
+ if (below) {
+ info2.size = below->size;
+ info2.index = below->index;
+ if (fontinfo_selorder_compare(&info2, below))
+ below = NULL;
+ }
+ if (above) {
+ info2.size = above->size;
+ info2.index = above->index;
+ if (fontinfo_selorder_compare(&info2, above))
+ above = NULL;
+ }
+
+ /*
+ * Now return whichever of above and below is non-NULL, if
+ * that's unambiguous.
+ */
+ if (!above)
+ return below;
+ if (!below)
+ return above;
+
+ /*
+ * And now we really do have to make a choice about whether to
+ * round up or down. We'll do it by rounding to nearest,
+ * breaking ties by rounding up.
+ */
+ if (above->size - fs->intendedsize <= fs->intendedsize - below->size)
+ return above;
+ else
+ return below;
+}
+
+static void family_changed(GtkTreeSelection *treeselection, gpointer data)
+{
+ unifontsel_internal *fs = (unifontsel_internal *)data;
+ GtkTreeModel *treemodel;
+ GtkTreeIter treeiter;
+ int minval;
+ fontinfo *info;
+
+ if (fs->inhibit_response) /* we made this change ourselves */
+ return;
+
+ if (!gtk_tree_selection_get_selected(treeselection, &treemodel, &treeiter))
+ return;
+
+ gtk_tree_model_get(treemodel, &treeiter, 1, &minval, -1);
+ info = (fontinfo *)index234(fs->fonts_by_selorder, minval);
+ info = update_for_intended_size(fs, info);
+ if (!info)
+ return; /* _shouldn't_ happen unless font list is completely funted */
+ if (!info->size)
+ fs->selsize = fs->intendedsize; /* font is scalable */
+ unifontsel_select_font(fs, info, info->size ? info->size : fs->selsize,
+ 1, FALSE);
+}
+
+static void style_changed(GtkTreeSelection *treeselection, gpointer data)
+{
+ unifontsel_internal *fs = (unifontsel_internal *)data;
+ GtkTreeModel *treemodel;
+ GtkTreeIter treeiter;
+ int minval;
+ fontinfo *info;
+
+ if (fs->inhibit_response) /* we made this change ourselves */
+ return;
+
+ if (!gtk_tree_selection_get_selected(treeselection, &treemodel, &treeiter))
+ return;
+
+ gtk_tree_model_get(treemodel, &treeiter, 1, &minval, -1);
+ if (minval < 0)
+ return; /* somehow a charset heading got clicked */
+ info = (fontinfo *)index234(fs->fonts_by_selorder, minval);
+ info = update_for_intended_size(fs, info);
+ if (!info)
+ return; /* _shouldn't_ happen unless font list is completely funted */
+ if (!info->size)
+ fs->selsize = fs->intendedsize; /* font is scalable */
+ unifontsel_select_font(fs, info, info->size ? info->size : fs->selsize,
+ 2, FALSE);
+}
+
+static void size_changed(GtkTreeSelection *treeselection, gpointer data)
+{
+ unifontsel_internal *fs = (unifontsel_internal *)data;
+ GtkTreeModel *treemodel;
+ GtkTreeIter treeiter;
+ int minval, size;
+ fontinfo *info;
+
+ if (fs->inhibit_response) /* we made this change ourselves */
+ return;
+
+ if (!gtk_tree_selection_get_selected(treeselection, &treemodel, &treeiter))
+ return;
+
+ gtk_tree_model_get(treemodel, &treeiter, 1, &minval, 2, &size, -1);
+ info = (fontinfo *)index234(fs->fonts_by_selorder, minval);
+ unifontsel_select_font(fs, info, info->size ? info->size : size, 3, TRUE);
+}
+
+static void size_entry_changed(GtkEditable *ed, gpointer data)
+{
+ unifontsel_internal *fs = (unifontsel_internal *)data;
+ const char *text;
+ int size;
+
+ if (fs->inhibit_response) /* we made this change ourselves */
+ return;
+
+ text = gtk_entry_get_text(GTK_ENTRY(ed));
+ size = atoi(text);
+
+ if (size > 0) {
+ assert(fs->selected->size == 0);
+ unifontsel_select_font(fs, fs->selected, size, 3, TRUE);
+ }
+}
+
+static void alias_resolve(GtkTreeView *treeview, GtkTreePath *path,
+ GtkTreeViewColumn *column, gpointer data)
+{
+ unifontsel_internal *fs = (unifontsel_internal *)data;
+ GtkTreeIter iter;
+ int minval, newsize;
+ fontinfo *info, *newinfo;
+ char *newname;
+
+ if (fs->inhibit_response) /* we made this change ourselves */
+ return;
+
+ gtk_tree_model_get_iter(GTK_TREE_MODEL(fs->family_model), &iter, path);
+ gtk_tree_model_get(GTK_TREE_MODEL(fs->family_model), &iter, 1,&minval, -1);
+ info = (fontinfo *)index234(fs->fonts_by_selorder, minval);
+ if (info) {
+ int flags;
+ struct fontinfo_realname_find f;
+
+ newname = info->fontclass->canonify_fontname
+ (GTK_WIDGET(fs->u.window), info->realname, &newsize, &flags, TRUE);
+
+ f.realname = newname;
+ f.flags = flags;
+ newinfo = find234(fs->fonts_by_realname, &f, fontinfo_realname_find);
+
+ sfree(newname);
+ if (!newinfo)
+ return; /* font name not in our index */
+ if (newinfo == info)
+ return; /* didn't change under canonification => not an alias */
+ unifontsel_select_font(fs, newinfo,
+ newinfo->size ? newinfo->size : newsize,
+ 1, TRUE);
+ }
+}
+
+static gint unifontsel_expose_area(GtkWidget *widget, GdkEventExpose *event,
+ gpointer data)
+{
+ unifontsel_internal *fs = (unifontsel_internal *)data;
+
+ if (fs->preview_pixmap) {
+ gdk_draw_pixmap(widget->window,
+ widget->style->fg_gc[GTK_WIDGET_STATE(widget)],
+ fs->preview_pixmap,
+ event->area.x, event->area.y,
+ event->area.x, event->area.y,
+ event->area.width, event->area.height);
+ }
+ return TRUE;
+}
+
+static gint unifontsel_configure_area(GtkWidget *widget,
+ GdkEventConfigure *event, gpointer data)
+{
+ unifontsel_internal *fs = (unifontsel_internal *)data;
+ int ox, oy, nx, ny, x, y;
+
+ /*
+ * Enlarge the pixmap, but never shrink it.
+ */
+ ox = fs->preview_width;
+ oy = fs->preview_height;
+ x = event->width;
+ y = event->height;
+ if (x > ox || y > oy) {
+ if (fs->preview_pixmap)
+ gdk_pixmap_unref(fs->preview_pixmap);
+
+ nx = (x > ox ? x : ox);
+ ny = (y > oy ? y : oy);
+ fs->preview_pixmap = gdk_pixmap_new(widget->window, nx, ny, -1);
+ fs->preview_width = nx;
+ fs->preview_height = ny;
+
+ unifontsel_draw_preview_text(fs);
+ }
+
+ gdk_window_invalidate_rect(widget->window, NULL, FALSE);
+
+ return TRUE;
+}
+
+unifontsel *unifontsel_new(const char *wintitle)
+{
+ unifontsel_internal *fs = snew(unifontsel_internal);
+ GtkWidget *table, *label, *w, *ww, *scroll;
+ GtkListStore *model;
+ GtkTreeViewColumn *column;
+ int lists_height, preview_height, font_width, style_width, size_width;
+ int i;
+
+ fs->inhibit_response = FALSE;
+ fs->selected = NULL;
+
+ {
+ /*
+ * Invent some magic size constants.
+ */
+ GtkRequisition req;
+ label = gtk_label_new("Quite Long Font Name (Foundry)");
+ gtk_widget_size_request(label, &req);
+ font_width = req.width;
+ lists_height = 14 * req.height;
+ preview_height = 5 * req.height;
+ gtk_label_set_text(GTK_LABEL(label), "Italic Extra Condensed");
+ gtk_widget_size_request(label, &req);
+ style_width = req.width;
+ gtk_label_set_text(GTK_LABEL(label), "48000");
+ gtk_widget_size_request(label, &req);
+ size_width = req.width;
+#if GTK_CHECK_VERSION(2,10,0)
+ g_object_ref_sink(label);
+ g_object_unref(label);
+#else
+ gtk_object_sink(GTK_OBJECT(label));
+#endif
+ }
+
+ /*
+ * Create the dialog box and initialise the user-visible
+ * fields in the returned structure.
+ */
+ fs->u.user_data = NULL;
+ fs->u.window = GTK_WINDOW(gtk_dialog_new());
+ gtk_window_set_title(fs->u.window, wintitle);
+ fs->u.cancel_button = gtk_dialog_add_button
+ (GTK_DIALOG(fs->u.window), GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL);
+ fs->u.ok_button = gtk_dialog_add_button
+ (GTK_DIALOG(fs->u.window), GTK_STOCK_OK, GTK_RESPONSE_OK);
+ gtk_widget_grab_default(fs->u.ok_button);
+
+ /*
+ * Now set up the internal fields, including in particular all
+ * the controls that actually allow the user to select fonts.
+ */
+ table = gtk_table_new(8, 3, FALSE);
+ gtk_widget_show(table);
+ gtk_table_set_col_spacings(GTK_TABLE(table), 8);
+#if GTK_CHECK_VERSION(2,4,0)
+ /* GtkAlignment seems to be the simplest way to put padding round things */
+ w = gtk_alignment_new(0, 0, 1, 1);
+ gtk_alignment_set_padding(GTK_ALIGNMENT(w), 8, 8, 8, 8);
+ gtk_container_add(GTK_CONTAINER(w), table);
+ gtk_widget_show(w);
+#else
+ w = table;
+#endif
+ gtk_box_pack_start(GTK_BOX(GTK_DIALOG(fs->u.window)->vbox),
+ w, TRUE, TRUE, 0);
+
+ label = gtk_label_new_with_mnemonic("_Font:");
+ gtk_widget_show(label);
+ gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
+ gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, 1, GTK_FILL, 0, 0, 0);
+
+ /*
+ * The Font list box displays only a string, but additionally
+ * stores two integers which give the limits within the
+ * tree234 of the font entries covered by this list entry.
+ */
+ model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_INT);
+ w = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
+ gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(w), FALSE);
+ gtk_label_set_mnemonic_widget(GTK_LABEL(label), w);
+ gtk_widget_show(w);
+ column = gtk_tree_view_column_new_with_attributes
+ ("Font", gtk_cell_renderer_text_new(),
+ "text", 0, (char *)NULL);
+ gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
+ gtk_tree_view_append_column(GTK_TREE_VIEW(w), column);
+ g_signal_connect(G_OBJECT(gtk_tree_view_get_selection(GTK_TREE_VIEW(w))),
+ "changed", G_CALLBACK(family_changed), fs);
+ g_signal_connect(G_OBJECT(w), "row-activated",
+ G_CALLBACK(alias_resolve), fs);
+
+ scroll = gtk_scrolled_window_new(NULL, NULL);
+ gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scroll),
+ GTK_SHADOW_IN);
+ gtk_container_add(GTK_CONTAINER(scroll), w);
+ gtk_widget_show(scroll);
+ gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
+ GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
+ gtk_widget_set_size_request(scroll, font_width, lists_height);
+ gtk_table_attach(GTK_TABLE(table), scroll, 0, 1, 1, 3, GTK_FILL,
+ GTK_EXPAND | GTK_FILL, 0, 0);
+ fs->family_model = model;
+ fs->family_list = w;
+
+ label = gtk_label_new_with_mnemonic("_Style:");
+ gtk_widget_show(label);
+ gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
+ gtk_table_attach(GTK_TABLE(table), label, 1, 2, 0, 1, GTK_FILL, 0, 0, 0);
+
+ /*
+ * The Style list box can contain insensitive elements
+ * (character set headings for server-side fonts), so we add
+ * an extra column to the list store to hold that information.
+ */
+ model = gtk_list_store_new(4, G_TYPE_STRING, G_TYPE_INT, G_TYPE_INT,
+ G_TYPE_BOOLEAN);
+ w = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
+ gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(w), FALSE);
+ gtk_label_set_mnemonic_widget(GTK_LABEL(label), w);
+ gtk_widget_show(w);
+ column = gtk_tree_view_column_new_with_attributes
+ ("Style", gtk_cell_renderer_text_new(),
+ "text", 0, "sensitive", 3, (char *)NULL);
+ gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
+ gtk_tree_view_append_column(GTK_TREE_VIEW(w), column);
+ g_signal_connect(G_OBJECT(gtk_tree_view_get_selection(GTK_TREE_VIEW(w))),
+ "changed", G_CALLBACK(style_changed), fs);
+
+ scroll = gtk_scrolled_window_new(NULL, NULL);
+ gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scroll),
+ GTK_SHADOW_IN);
+ gtk_container_add(GTK_CONTAINER(scroll), w);
+ gtk_widget_show(scroll);
+ gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
+ GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
+ gtk_widget_set_size_request(scroll, style_width, lists_height);
+ gtk_table_attach(GTK_TABLE(table), scroll, 1, 2, 1, 3, GTK_FILL,
+ GTK_EXPAND | GTK_FILL, 0, 0);
+ fs->style_model = model;
+ fs->style_list = w;
+
+ label = gtk_label_new_with_mnemonic("Si_ze:");
+ gtk_widget_show(label);
+ gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
+ gtk_table_attach(GTK_TABLE(table), label, 2, 3, 0, 1, GTK_FILL, 0, 0, 0);
+
+ /*
+ * The Size label attaches primarily to a text input box so
+ * that the user can select a size of their choice. The list
+ * of available sizes is secondary.
+ */
+ fs->size_entry = w = gtk_entry_new();
+ gtk_label_set_mnemonic_widget(GTK_LABEL(label), w);
+ gtk_widget_set_size_request(w, size_width, -1);
+ gtk_widget_show(w);
+ gtk_table_attach(GTK_TABLE(table), w, 2, 3, 1, 2, GTK_FILL, 0, 0, 0);
+ g_signal_connect(G_OBJECT(w), "changed", G_CALLBACK(size_entry_changed),
+ fs);
+
+ model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_INT);
+ w = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
+ gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(w), FALSE);
+ gtk_widget_show(w);
+ column = gtk_tree_view_column_new_with_attributes
+ ("Size", gtk_cell_renderer_text_new(),
+ "text", 0, (char *)NULL);
+ gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
+ gtk_tree_view_append_column(GTK_TREE_VIEW(w), column);
+ g_signal_connect(G_OBJECT(gtk_tree_view_get_selection(GTK_TREE_VIEW(w))),
+ "changed", G_CALLBACK(size_changed), fs);
+
+ scroll = gtk_scrolled_window_new(NULL, NULL);
+ gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scroll),
+ GTK_SHADOW_IN);
+ gtk_container_add(GTK_CONTAINER(scroll), w);
+ gtk_widget_show(scroll);
+ gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
+ GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
+ gtk_table_attach(GTK_TABLE(table), scroll, 2, 3, 2, 3, GTK_FILL,
+ GTK_EXPAND | GTK_FILL, 0, 0);
+ fs->size_model = model;
+ fs->size_list = w;
+
+ /*
+ * Preview widget.
+ */
+ fs->preview_area = gtk_drawing_area_new();
+ fs->preview_pixmap = NULL;
+ fs->preview_width = 0;
+ fs->preview_height = 0;
+ fs->preview_fg.pixel = fs->preview_bg.pixel = 0;
+ fs->preview_fg.red = fs->preview_fg.green = fs->preview_fg.blue = 0x0000;
+ fs->preview_bg.red = fs->preview_bg.green = fs->preview_bg.blue = 0xFFFF;
+ gdk_colormap_alloc_color(gdk_colormap_get_system(), &fs->preview_fg,
+ FALSE, FALSE);
+ gdk_colormap_alloc_color(gdk_colormap_get_system(), &fs->preview_bg,
+ FALSE, FALSE);
+ gtk_signal_connect(GTK_OBJECT(fs->preview_area), "expose_event",
+ GTK_SIGNAL_FUNC(unifontsel_expose_area), fs);
+ gtk_signal_connect(GTK_OBJECT(fs->preview_area), "configure_event",
+ GTK_SIGNAL_FUNC(unifontsel_configure_area), fs);
+ gtk_widget_set_size_request(fs->preview_area, 1, preview_height);
+ gtk_widget_show(fs->preview_area);
+ ww = fs->preview_area;
+ w = gtk_frame_new(NULL);
+ gtk_container_add(GTK_CONTAINER(w), ww);
+ gtk_widget_show(w);
+#if GTK_CHECK_VERSION(2,4,0)
+ ww = w;
+ /* GtkAlignment seems to be the simplest way to put padding round things */
+ w = gtk_alignment_new(0, 0, 1, 1);
+ gtk_alignment_set_padding(GTK_ALIGNMENT(w), 8, 8, 8, 8);
+ gtk_container_add(GTK_CONTAINER(w), ww);
+ gtk_widget_show(w);
+#endif
+ ww = w;
+ w = gtk_frame_new("Preview of font");
+ gtk_container_add(GTK_CONTAINER(w), ww);
+ gtk_widget_show(w);
+ gtk_table_attach(GTK_TABLE(table), w, 0, 3, 3, 4,
+ GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 8);
+
+ i = 0;
+ w = gtk_check_button_new_with_label("Show client-side fonts");
+ gtk_object_set_data(GTK_OBJECT(w), "user-data",
+ GINT_TO_POINTER(FONTFLAG_CLIENTSIDE));
+ gtk_signal_connect(GTK_OBJECT(w), "toggled",
+ GTK_SIGNAL_FUNC(unifontsel_button_toggled), fs);
+ gtk_widget_show(w);
+ fs->filter_buttons[i++] = w;
+ gtk_table_attach(GTK_TABLE(table), w, 0, 3, 4, 5, GTK_FILL, 0, 0, 0);
+ w = gtk_check_button_new_with_label("Show server-side fonts");
+ gtk_object_set_data(GTK_OBJECT(w), "user-data",
+ GINT_TO_POINTER(FONTFLAG_SERVERSIDE));
+ gtk_signal_connect(GTK_OBJECT(w), "toggled",
+ GTK_SIGNAL_FUNC(unifontsel_button_toggled), fs);
+ gtk_widget_show(w);
+ fs->filter_buttons[i++] = w;
+ gtk_table_attach(GTK_TABLE(table), w, 0, 3, 5, 6, GTK_FILL, 0, 0, 0);
+ w = gtk_check_button_new_with_label("Show server-side font aliases");
+ gtk_object_set_data(GTK_OBJECT(w), "user-data",
+ GINT_TO_POINTER(FONTFLAG_SERVERALIAS));
+ gtk_signal_connect(GTK_OBJECT(w), "toggled",
+ GTK_SIGNAL_FUNC(unifontsel_button_toggled), fs);
+ gtk_widget_show(w);
+ fs->filter_buttons[i++] = w;
+ gtk_table_attach(GTK_TABLE(table), w, 0, 3, 6, 7, GTK_FILL, 0, 0, 0);
+ w = gtk_check_button_new_with_label("Show non-monospaced fonts");
+ gtk_object_set_data(GTK_OBJECT(w), "user-data",
+ GINT_TO_POINTER(FONTFLAG_NONMONOSPACED));
+ gtk_signal_connect(GTK_OBJECT(w), "toggled",
+ GTK_SIGNAL_FUNC(unifontsel_button_toggled), fs);
+ gtk_widget_show(w);
+ fs->filter_buttons[i++] = w;
+ gtk_table_attach(GTK_TABLE(table), w, 0, 3, 7, 8, GTK_FILL, 0, 0, 0);
+
+ assert(i == lenof(fs->filter_buttons));
+ fs->filter_flags = FONTFLAG_CLIENTSIDE | FONTFLAG_SERVERSIDE |
+ FONTFLAG_SERVERALIAS;
+ unifontsel_set_filter_buttons(fs);
+
+ /*
+ * Go and find all the font names, and set up our master font
+ * list.
+ */
+ fs->fonts_by_realname = newtree234(fontinfo_realname_compare);
+ fs->fonts_by_selorder = newtree234(fontinfo_selorder_compare);
+ for (i = 0; i < lenof(unifont_types); i++)
+ unifont_types[i]->enum_fonts(GTK_WIDGET(fs->u.window),
+ unifontsel_add_entry, fs);
+
+ /*
+ * And set up the initial font names list.
+ */
+ unifontsel_setup_familylist(fs);
+
+ fs->selsize = fs->intendedsize = 13; /* random default */
+ gtk_widget_set_sensitive(fs->u.ok_button, FALSE);
+
+ return (unifontsel *)fs;
+}
+
+void unifontsel_destroy(unifontsel *fontsel)
+{
+ unifontsel_internal *fs = (unifontsel_internal *)fontsel;
+ fontinfo *info;
+
+ if (fs->preview_pixmap)
+ gdk_pixmap_unref(fs->preview_pixmap);
+
+ freetree234(fs->fonts_by_selorder);
+ while ((info = delpos234(fs->fonts_by_realname, 0)) != NULL)
+ sfree(info);
+ freetree234(fs->fonts_by_realname);
+
+ gtk_widget_destroy(GTK_WIDGET(fs->u.window));
+ sfree(fs);
+}
+
+void unifontsel_set_name(unifontsel *fontsel, const char *fontname)
+{
+ unifontsel_internal *fs = (unifontsel_internal *)fontsel;
+ int i, start, end, size, flags;
+ const char *fontname2 = NULL;
+ fontinfo *info;
+
+ /*
+ * Provide a default if given an empty or null font name.
+ */
+ if (!fontname || !*fontname)
+ fontname = "server:fixed";
+
+ /*
+ * Call the canonify_fontname function.
+ */
+ fontname = unifont_do_prefix(fontname, &start, &end);
+ for (i = start; i < end; i++) {
+ fontname2 = unifont_types[i]->canonify_fontname
+ (GTK_WIDGET(fs->u.window), fontname, &size, &flags, FALSE);
+ if (fontname2)
+ break;
+ }
+ if (i == end)
+ return; /* font name not recognised */
+
+ /*
+ * Now look up the canonified font name in our index.
+ */
+ {
+ struct fontinfo_realname_find f;
+ f.realname = fontname2;
+ f.flags = flags;
+ info = find234(fs->fonts_by_realname, &f, fontinfo_realname_find);
+ }
+
+ /*
+ * If we've found the font, and its size field is either
+ * correct or zero (the latter indicating a scalable font),
+ * then we're done. Otherwise, try looking up the original
+ * font name instead.
+ */
+ if (!info || (info->size != size && info->size != 0)) {
+ struct fontinfo_realname_find f;
+ f.realname = fontname;
+ f.flags = flags;
+
+ info = find234(fs->fonts_by_realname, &f, fontinfo_realname_find);
+ if (!info || info->size != size)
+ return; /* font name not in our index */
+ }
+
+ /*
+ * Now we've got a fontinfo structure and a font size, so we
+ * know everything we need to fill in all the fields in the
+ * dialog.
+ */
+ unifontsel_select_font(fs, info, size, 0, TRUE);
+}
+
+char *unifontsel_get_name(unifontsel *fontsel)
+{
+ unifontsel_internal *fs = (unifontsel_internal *)fontsel;
+ char *name;
+
+ if (!fs->selected)
+ return NULL;
+
+ if (fs->selected->size == 0) {
+ name = fs->selected->fontclass->scale_fontname
+ (GTK_WIDGET(fs->u.window), fs->selected->realname, fs->selsize);
+ if (name) {
+ char *ret = dupcat(fs->selected->fontclass->prefix, ":",
+ name, NULL);
+ sfree(name);
+ return ret;
+ }
+ }
+
+ return dupcat(fs->selected->fontclass->prefix, ":",
+ fs->selected->realname, NULL);
+}
+
+#endif /* GTK_CHECK_VERSION(2,0,0) */
--- /dev/null
+/*
+ * Header file for gtkfont.c. Has to be separate from unix.h
+ * because it depends on GTK data types, hence can't be included
+ * from cross-platform code (which doesn't go near GTK).
+ */
+
+#ifndef PUTTY_GTKFONT_H
+#define PUTTY_GTKFONT_H
+
+/*
+ * Exports from gtkfont.c.
+ */
+struct unifont_vtable; /* contents internal to gtkfont.c */
+typedef struct unifont {
+ const struct unifont_vtable *vt;
+ /*
+ * `Non-static data members' of the `class', accessible to
+ * external code.
+ */
+
+ /*
+ * 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;
+
+ /*
+ * Font dimensions needed by clients.
+ */
+ int width, height, ascent, descent;
+} 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 wide, int bold, int cellwidth);
+
+/*
+ * 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.
+ *
+ * (Of course, these aren't the only members; this structure is
+ * contained within a bigger one which holds data visible only to
+ * the implementation.)
+ */
+typedef struct unifontsel {
+ void *user_data; /* settable by the user */
+ GtkWindow *window;
+ GtkWidget *ok_button, *cancel_button;
+} unifontsel;
+
+unifontsel *unifontsel_new(const char *wintitle);
+void unifontsel_destroy(unifontsel *fontsel);
+void unifontsel_set_name(unifontsel *fontsel, const char *fontname);
+char *unifontsel_get_name(unifontsel *fontsel);
+
+#endif /* PUTTY_GTKFONT_H */
--- /dev/null
+/*
+ * gtkwin.c: the main code that runs a PuTTY terminal emulator and
+ * backend in a GTK window.
+ */
+
+#define _GNU_SOURCE
+
+#include <string.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+#include <stdio.h>
+#include <time.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+#include <gdk/gdkx.h>
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/Xatom.h>
+
+#define PUTTY_DO_GLOBALS /* actually _define_ globals */
+
+#include "putty.h"
+#include "terminal.h"
+#include "gtkfont.h"
+
+#define CAT2(x,y) x ## y
+#define CAT(x,y) CAT2(x,y)
+#define ASSERT(x) enum {CAT(assertion_,__LINE__) = 1 / (x)}
+
+#if GTK_CHECK_VERSION(2,0,0)
+ASSERT(sizeof(long) <= sizeof(gsize));
+#define LONG_TO_GPOINTER(l) GSIZE_TO_POINTER(l)
+#define GPOINTER_TO_LONG(p) GPOINTER_TO_SIZE(p)
+#else /* Gtk 1.2 */
+ASSERT(sizeof(long) <= sizeof(gpointer));
+#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;
+
+extern char **pty_argv; /* declared in pty.c */
+extern int use_pty_argv;
+
+/*
+ * Timers are global across all sessions (even if we were handling
+ * multiple sessions, which we aren't), so the current timer ID is
+ * a global variable.
+ */
+static guint timer_id = 0;
+
+struct gui_data {
+ GtkWidget *window, *area, *sbar;
+ GtkBox *hbox;
+ GtkAdjustment *sbar_adjust;
+ GtkWidget *menu, *specialsmenu, *specialsitem1, *specialsitem2,
+ *restartitem;
+ GtkWidget *sessionsmenu;
+ GdkPixmap *pixmap;
+ unifont *fonts[4]; /* normal, bold, wide, widebold */
+ int xpos, ypos, gotpos, gravity;
+ GdkCursor *rawcursor, *textcursor, *blankcursor, *waitcursor, *currcursor;
+ GdkColor cols[NALLCOLOURS];
+ GdkColormap *colmap;
+ wchar_t *pastein_data;
+ int direct_to_font;
+ int pastein_data_len;
+ char *pasteout_data, *pasteout_data_ctext, *pasteout_data_utf8;
+ int pasteout_data_len, pasteout_data_ctext_len, pasteout_data_utf8_len;
+ 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;
+ int alt_keycode;
+ int alt_digits;
+ char wintitle[sizeof(((Config *)0)->wintitle)];
+ char icontitle[sizeof(((Config *)0)->wintitle)];
+ int master_fd, master_func_id;
+ void *ldisc;
+ Backend *back;
+ void *backhandle;
+ Terminal *term;
+ void *logctx;
+ int exited;
+ struct unicode_data ucsdata;
+ Config cfg;
+ void *eventlogstuff;
+ char *progname, **gtkargvstart;
+ int ngtkargs;
+ guint32 input_event_time; /* Timestamp of the most recent input event. */
+ int reconfiguring;
+};
+
+struct draw_ctx {
+ GdkGC *gc;
+ struct gui_data *inst;
+};
+
+static int send_raw_mouse;
+
+static char *app_name = "pterm";
+
+static void start_backend(struct gui_data *inst);
+
+char *x_get_default(const char *key)
+{
+ return XGetDefault(GDK_DISPLAY(), app_name, key);
+}
+
+void connection_fatal(void *frontend, char *p, ...)
+{
+ struct gui_data *inst = (struct gui_data *)frontend;
+
+ 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);
+}
+
+/*
+ * Default settings that are specific to pterm.
+ */
+FontSpec platform_default_fontspec(const char *name)
+{
+ FontSpec ret;
+ if (!strcmp(name, "Font"))
+ strcpy(ret.name, "server:fixed");
+ else
+ *ret.name = '\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;
+}
+
+char *platform_default_s(const char *name)
+{
+ if (!strcmp(name, "SerialLine"))
+ return dupstr("/dev/ttyS0");
+ return NULL;
+}
+
+int platform_default_i(const char *name, int def)
+{
+ if (!strcmp(name, "CloseOnExit"))
+ return 2; /* maps to FORCE_ON after painful rearrangement :-( */
+ if (!strcmp(name, "WinNameAlways"))
+ return 0; /* X natively supports icon titles, so use 'em by default */
+ return def;
+}
+
+/* Dummy routine, only required in plink. */
+void ldisc_update(void *frontend, int echo, int edit)
+{
+}
+
+char *get_ttymode(void *frontend, const char *mode)
+{
+ struct gui_data *inst = (struct gui_data *)frontend;
+ return term_get_ttymode(inst->term, mode);
+}
+
+int from_backend(void *frontend, int is_stderr, const char *data, int len)
+{
+ struct gui_data *inst = (struct gui_data *)frontend;
+ return term_data(inst->term, is_stderr, data, len);
+}
+
+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 get_userpass_input(prompts_t *p, unsigned char *in, int inlen)
+{
+ struct gui_data *inst = (struct gui_data *)p->frontend;
+ int ret;
+ ret = cmdline_get_passwd_input(p, in, inlen);
+ if (ret == -1)
+ ret = term_get_userpass_input(inst->term, p, in, inlen);
+ return ret;
+}
+
+void logevent(void *frontend, const char *string)
+{
+ struct gui_data *inst = (struct gui_data *)frontend;
+
+ log_eventlog(inst->logctx, string);
+
+ logevent_dlg(inst->eventlogstuff, string);
+}
+
+int font_dimension(void *frontend, int which)/* 0 for width, 1 for height */
+{
+ struct gui_data *inst = (struct gui_data *)frontend;
+
+ if (which)
+ return inst->font_height;
+ else
+ return inst->font_width;
+}
+
+/*
+ * Translate a raw mouse button designation (LEFT, MIDDLE, RIGHT)
+ * into a cooked one (SELECT, EXTEND, PASTE).
+ *
+ * In Unix, this is not configurable; the X button arrangement is
+ * rock-solid across all applications, everyone has a three-button
+ * mouse or a means of faking it, and there is no need to switch
+ * buttons around at all.
+ */
+static Mouse_Button translate_button(Mouse_Button button)
+{
+ /* struct gui_data *inst = (struct gui_data *)frontend; */
+
+ if (button == MBT_LEFT)
+ return MBT_SELECT;
+ if (button == MBT_MIDDLE)
+ return MBT_PASTE;
+ if (button == MBT_RIGHT)
+ return MBT_EXTEND;
+ return 0; /* shouldn't happen */
+}
+
+/*
+ * Return the top-level GtkWindow associated with a particular
+ * front end instance.
+ */
+void *get_window(void *frontend)
+{
+ struct gui_data *inst = (struct gui_data *)frontend;
+ return inst->window;
+}
+
+/*
+ * Minimise or restore the window in response to a server-side
+ * request.
+ */
+void set_iconic(void *frontend, int iconic)
+{
+ /*
+ * GTK 1.2 doesn't know how to do this.
+ */
+#if GTK_CHECK_VERSION(2,0,0)
+ struct gui_data *inst = (struct gui_data *)frontend;
+ if (iconic)
+ gtk_window_iconify(GTK_WINDOW(inst->window));
+ else
+ gtk_window_deiconify(GTK_WINDOW(inst->window));
+#endif
+}
+
+/*
+ * Move the window in response to a server-side request.
+ */
+void move_window(void *frontend, int x, int y)
+{
+ struct gui_data *inst = (struct gui_data *)frontend;
+ /*
+ * I assume that when the GTK version of this call is available
+ * we should use it. Not sure how it differs from the GDK one,
+ * though.
+ */
+#if GTK_CHECK_VERSION(2,0,0)
+ gtk_window_move(GTK_WINDOW(inst->window), x, y);
+#else
+ gdk_window_move(inst->window->window, x, y);
+#endif
+}
+
+/*
+ * 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)
+{
+ struct gui_data *inst = (struct gui_data *)frontend;
+ if (top)
+ gdk_window_raise(inst->window->window);
+ else
+ gdk_window_lower(inst->window->window);
+}
+
+/*
+ * Refresh the window in response to a server-side request.
+ */
+void refresh_window(void *frontend)
+{
+ struct gui_data *inst = (struct gui_data *)frontend;
+ term_invalidate(inst->term);
+}
+
+/*
+ * Maximise or restore the window in response to a server-side
+ * request.
+ */
+void set_zoomed(void *frontend, int zoomed)
+{
+ /*
+ * GTK 1.2 doesn't know how to do this.
+ */
+#if GTK_CHECK_VERSION(2,0,0)
+ struct gui_data *inst = (struct gui_data *)frontend;
+ if (zoomed)
+ gtk_window_maximize(GTK_WINDOW(inst->window));
+ else
+ gtk_window_unmaximize(GTK_WINDOW(inst->window));
+#endif
+}
+
+/*
+ * Report whether the window is iconic, for terminal reports.
+ */
+int is_iconic(void *frontend)
+{
+ struct gui_data *inst = (struct gui_data *)frontend;
+ return !gdk_window_is_viewable(inst->window->window);
+}
+
+/*
+ * Report the window's position, for terminal reports.
+ */
+void get_window_pos(void *frontend, int *x, int *y)
+{
+ struct gui_data *inst = (struct gui_data *)frontend;
+ /*
+ * I assume that when the GTK version of this call is available
+ * we should use it. Not sure how it differs from the GDK one,
+ * though.
+ */
+#if GTK_CHECK_VERSION(2,0,0)
+ gtk_window_get_position(GTK_WINDOW(inst->window), x, y);
+#else
+ gdk_window_get_position(inst->window->window, x, y);
+#endif
+}
+
+/*
+ * Report the window's pixel size, for terminal reports.
+ */
+void get_window_pixels(void *frontend, int *x, int *y)
+{
+ struct gui_data *inst = (struct gui_data *)frontend;
+ /*
+ * I assume that when the GTK version of this call is available
+ * we should use it. Not sure how it differs from the GDK one,
+ * though.
+ */
+#if GTK_CHECK_VERSION(2,0,0)
+ gtk_window_get_size(GTK_WINDOW(inst->window), x, y);
+#else
+ gdk_window_get_size(inst->window->window, x, y);
+#endif
+}
+
+/*
+ * Return the window or icon title.
+ */
+char *get_window_title(void *frontend, int icon)
+{
+ struct gui_data *inst = (struct gui_data *)frontend;
+ return icon ? inst->icontitle : inst->wintitle;
+}
+
+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 (!reallyclose(inst))
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static void update_mouseptr(struct gui_data *inst)
+{
+ switch (inst->busy_status) {
+ case BUSY_NOT:
+ if (!inst->mouseptr_visible) {
+ gdk_window_set_cursor(inst->area->window, inst->blankcursor);
+ } else if (send_raw_mouse) {
+ gdk_window_set_cursor(inst->area->window, inst->rawcursor);
+ } else {
+ gdk_window_set_cursor(inst->area->window, inst->textcursor);
+ }
+ break;
+ case BUSY_WAITING: /* XXX can we do better? */
+ case BUSY_CPU:
+ /* We always display these cursors. */
+ gdk_window_set_cursor(inst->area->window, inst->waitcursor);
+ break;
+ default:
+ assert(0);
+ }
+}
+
+static void show_mouseptr(struct gui_data *inst, int show)
+{
+ if (!inst->cfg.hide_mouseptr)
+ show = 1;
+ inst->mouseptr_visible = show;
+ update_mouseptr(inst);
+}
+
+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);
+ gdk_gc_unref(gc);
+}
+
+gint configure_area(GtkWidget *widget, GdkEventConfigure *event, gpointer data)
+{
+ struct gui_data *inst = (struct gui_data *)data;
+ int w, h, need_size = 0;
+
+ /*
+ * 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;
+ if (w != inst->width || h != inst->height) {
+ inst->cfg.width = inst->width = w;
+ inst->cfg.height = inst->height = h;
+ 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);
+
+ draw_backing_rect(inst);
+
+ if (need_size && inst->term) {
+ term_size(inst->term, h, w, inst->cfg.savelines);
+ }
+
+ if (inst->term)
+ term_invalidate(inst->term);
+
+ return TRUE;
+}
+
+gint expose_area(GtkWidget *widget, GdkEventExpose *event, gpointer data)
+{
+ struct gui_data *inst = (struct gui_data *)data;
+
+ /*
+ * Pass the exposed rectangle to terminal.c, which will call us
+ * back to do the actual painting.
+ */
+ if (inst->pixmap) {
+ gdk_draw_pixmap(widget->window,
+ widget->style->fg_gc[GTK_WIDGET_STATE(widget)],
+ inst->pixmap,
+ event->area.x, event->area.y,
+ event->area.x, event->area.y,
+ event->area.width, event->area.height);
+ }
+ return TRUE;
+}
+
+#define KEY_PRESSED(k) \
+ (inst->keystate[(k) / 32] & (1 << ((k) % 32)))
+
+gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data)
+{
+ struct gui_data *inst = (struct gui_data *)data;
+ char output[256];
+ wchar_t ucsoutput[2];
+ int ucsval, start, end, special, output_charset, use_ucsoutput;
+
+ /* Remember the timestamp. */
+ inst->input_event_time = event->time;
+
+ /* By default, nothing is generated. */
+ end = start = 0;
+ special = use_ucsoutput = FALSE;
+ output_charset = CS_ISO8859_1;
+
+ /*
+ * If Alt is being released after typing an Alt+numberpad
+ * sequence, we should generate the code that was typed.
+ *
+ * Note that we only do this if more than one key was actually
+ * 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) {
+#ifdef KEY_DEBUGGING
+ 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;
+ }
+
+ if (event->type == GDK_KEY_PRESS) {
+#ifdef KEY_DEBUGGING
+ {
+ int i;
+ printf("keypress: keyval = %04x, state = %08x; string =",
+ event->keyval, event->state);
+ for (i = 0; event->string[i]; i++)
+ printf(" %02x", (unsigned char) event->string[i]);
+ printf("\n");
+ }
+#endif
+
+ /*
+ * NYI: Compose key (!!! requires Unicode faff before even trying)
+ */
+
+ /*
+ * If Alt has just been pressed, we start potentially
+ * accumulating an Alt+numberpad code. We do this by
+ * setting alt_keycode to -1 (nothing yet but plausible).
+ */
+ if ((event->keyval == GDK_Meta_L || event->keyval == GDK_Alt_L ||
+ event->keyval == GDK_Meta_R || event->keyval == GDK_Alt_R)) {
+ inst->alt_keycode = -1;
+ inst->alt_digits = 0;
+ goto done; /* this generates nothing else */
+ }
+
+ /*
+ * If we're seeing a numberpad key press with Mod1 down,
+ * consider adding it to alt_keycode if that's sensible.
+ * Anything _else_ with Mod1 down cancels any possibility
+ * of an ALT keycode: we set alt_keycode to -2.
+ */
+ if ((event->state & GDK_MOD1_MASK) && inst->alt_keycode != -2) {
+ int digit = -1;
+ switch (event->keyval) {
+ case GDK_KP_0: case GDK_KP_Insert: digit = 0; break;
+ case GDK_KP_1: case GDK_KP_End: digit = 1; break;
+ case GDK_KP_2: case GDK_KP_Down: digit = 2; break;
+ case GDK_KP_3: case GDK_KP_Page_Down: digit = 3; break;
+ case GDK_KP_4: case GDK_KP_Left: digit = 4; break;
+ case GDK_KP_5: case GDK_KP_Begin: digit = 5; break;
+ case GDK_KP_6: case GDK_KP_Right: digit = 6; break;
+ case GDK_KP_7: case GDK_KP_Home: digit = 7; break;
+ case GDK_KP_8: case GDK_KP_Up: digit = 8; break;
+ case GDK_KP_9: case GDK_KP_Page_Up: digit = 9; break;
+ }
+ if (digit < 0)
+ inst->alt_keycode = -2; /* it's invalid */
+ else {
+#ifdef KEY_DEBUGGING
+ printf("Adding digit %d to keycode %d", digit,
+ inst->alt_keycode);
+#endif
+ if (inst->alt_keycode == -1)
+ inst->alt_keycode = digit; /* one-digit code */
+ else
+ inst->alt_keycode = inst->alt_keycode * 10 + digit;
+ inst->alt_digits++;
+#ifdef KEY_DEBUGGING
+ printf(" gives new code %d\n", inst->alt_keycode);
+#endif
+ /* Having used this digit, we now do nothing more with it. */
+ goto done;
+ }
+ }
+
+ /*
+ * 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);
+ 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);
+ return TRUE;
+ }
+ if (event->keyval == GDK_Page_Down && (event->state & GDK_CONTROL_MASK)) {
+ term_scroll(inst->term, 0, +1);
+ return TRUE;
+ }
+
+ /*
+ * Neither does Shift-Ins.
+ */
+ if (event->keyval == GDK_Insert && (event->state & GDK_SHIFT_MASK)) {
+ request_paste(inst);
+ return TRUE;
+ }
+
+ special = FALSE;
+ use_ucsoutput = FALSE;
+
+ /* 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
+ * as part of a keyboard event is assumed to be in
+ * ISO-8859-1. (Seems woefully shortsighted in i18n terms,
+ * but it's true: see the man page for XLookupString(3) for
+ * confirmation.)
+ */
+ output_charset = CS_ISO8859_1;
+ strncpy(output+1, event->string, lenof(output)-1);
+#else
+ /*
+ * 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
+ * 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;
+ int wlen;
+ int ulen;
+
+ wlen = mb_to_wc(DEFAULT_CODEPAGE, 0,
+ event->string, strlen(event->string),
+ widedata, lenof(widedata)-1);
+
+ wp = widedata;
+ ulen = charset_from_unicode(&wp, &wlen, output+1, lenof(output)-2,
+ CS_UTF8, NULL, NULL, 0);
+ output[1+ulen] = '\0';
+ }
+#endif
+
+ if (!output[1] &&
+ (ucsval = keysym_to_unicode(event->keyval)) >= 0) {
+ ucsoutput[0] = '\033';
+ ucsoutput[1] = ucsval;
+ use_ucsoutput = TRUE;
+ end = 2;
+ } else {
+ output[lenof(output)-1] = '\0';
+ end = strlen(output);
+ }
+ if (event->state & GDK_MOD1_MASK) {
+ start = 0;
+ if (end == 1) end = 0;
+ } else
+ start = 1;
+
+ /* Control-` is the same as Control-\ (unless gtk has a better idea) */
+ if (!output[1] && event->keyval == '`' &&
+ (event->state & GDK_CONTROL_MASK)) {
+ output[1] = '\x1C';
+ use_ucsoutput = FALSE;
+ end = 2;
+ }
+
+ /* Control-Break sends a Break special to the backend */
+ if (event->keyval == GDK_Break &&
+ (event->state & GDK_CONTROL_MASK)) {
+ if (inst->back)
+ inst->back->special(inst->backhandle, TS_BRK);
+ return TRUE;
+ }
+
+ /* We handle Return ourselves, because it needs to be flagged as
+ * special to ldisc. */
+ if (event->keyval == GDK_Return) {
+ output[1] = '\015';
+ use_ucsoutput = FALSE;
+ end = 2;
+ special = TRUE;
+ }
+
+ /* Control-2, Control-Space and Control-@ are NUL */
+ if (!output[1] &&
+ (event->keyval == ' ' || event->keyval == '2' ||
+ event->keyval == '@') &&
+ (event->state & (GDK_SHIFT_MASK |
+ GDK_CONTROL_MASK)) == GDK_CONTROL_MASK) {
+ output[1] = '\0';
+ use_ucsoutput = FALSE;
+ end = 2;
+ }
+
+ /* Control-Shift-Space is 160 (ISO8859 nonbreaking space) */
+ if (!output[1] && event->keyval == ' ' &&
+ (event->state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK)) ==
+ (GDK_SHIFT_MASK | GDK_CONTROL_MASK)) {
+ output[1] = '\240';
+ output_charset = CS_ISO8859_1;
+ use_ucsoutput = FALSE;
+ end = 2;
+ }
+
+ /* 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';
+ 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';
+ use_ucsoutput = FALSE;
+ end = 2;
+ special = TRUE;
+ }
+
+ /* Shift-Tab is ESC [ Z */
+ if (event->keyval == GDK_ISO_Left_Tab ||
+ (event->keyval == GDK_Tab && (event->state & GDK_SHIFT_MASK))) {
+ end = 1 + sprintf(output+1, "\033[Z");
+ use_ucsoutput = FALSE;
+ }
+ /* And normal Tab is Tab, if the keymap hasn't already told us.
+ * (Curiously, at least one version of the MacOS 10.5 X server
+ * doesn't translate Tab for us. */
+ if (event->keyval == GDK_Tab && end <= 1) {
+ output[1] = '\t';
+ end = 2;
+ }
+
+ /*
+ * NetHack keypad mode.
+ */
+ if (inst->cfg.nethack_keypad) {
+ 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;
+ case GDK_KP_4: case GDK_KP_Left: keys = "hH\010"; break;
+ case GDK_KP_5: case GDK_KP_Begin: keys = "..."; break;
+ case GDK_KP_6: case GDK_KP_Right: keys = "lL\014"; break;
+ case GDK_KP_7: case GDK_KP_Home: keys = "yY\031"; break;
+ case GDK_KP_8: case GDK_KP_Up: keys = "kK\013"; break;
+ case GDK_KP_9: case GDK_KP_Page_Up: keys = "uU\025"; break;
+ }
+ if (keys) {
+ end = 2;
+ if (event->state & GDK_CONTROL_MASK)
+ output[1] = keys[2];
+ else if (event->state & GDK_SHIFT_MASK)
+ output[1] = keys[1];
+ else
+ output[1] = keys[0];
+ use_ucsoutput = FALSE;
+ goto done;
+ }
+ }
+
+ /*
+ * Application keypad mode.
+ */
+ if (inst->term->app_keypad_keys && !inst->cfg.no_applic_k) {
+ 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;
+ case GDK_KP_Subtract: xkey = 'S'; break;
+ /*
+ * Keypad + is tricky. It covers a space that would
+ * 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 (event->state & GDK_SHIFT_MASK)
+ xkey = 'l';
+ else
+ xkey = 'k';
+ } else if (event->state & GDK_SHIFT_MASK)
+ xkey = 'm';
+ else
+ xkey = 'l';
+ break;
+ case GDK_KP_Enter: xkey = 'M'; break;
+ case GDK_KP_0: case GDK_KP_Insert: xkey = 'p'; break;
+ case GDK_KP_1: case GDK_KP_End: xkey = 'q'; break;
+ case GDK_KP_2: case GDK_KP_Down: xkey = 'r'; break;
+ case GDK_KP_3: case GDK_KP_Page_Down: xkey = 's'; break;
+ case GDK_KP_4: case GDK_KP_Left: xkey = 't'; break;
+ case GDK_KP_5: case GDK_KP_Begin: xkey = 'u'; break;
+ case GDK_KP_6: case GDK_KP_Right: xkey = 'v'; break;
+ case GDK_KP_7: case GDK_KP_Home: xkey = 'w'; break;
+ case GDK_KP_8: case GDK_KP_Up: xkey = 'x'; break;
+ case GDK_KP_9: case GDK_KP_Page_Up: xkey = 'y'; break;
+ case GDK_KP_Decimal: case GDK_KP_Delete: xkey = 'n'; break;
+ }
+ if (xkey) {
+ if (inst->term->vt52_mode) {
+ if (xkey >= 'P' && xkey <= 'S')
+ end = 1 + sprintf(output+1, "\033%c", xkey);
+ else
+ end = 1 + sprintf(output+1, "\033?%c", xkey);
+ } else
+ end = 1 + sprintf(output+1, "\033O%c", xkey);
+ use_ucsoutput = FALSE;
+ goto done;
+ }
+ }
+
+ /*
+ * Next, all the keys that do tilde codes. (ESC '[' nn '~',
+ * for integer decimal nn.)
+ *
+ * We also deal with the weird ones here. Linux VCs replace F1
+ * to F5 by ESC [ [ A to ESC [ [ E. rxvt doesn't do _that_, but
+ * does replace Home and End (1~ and 4~) by ESC [ H and ESC O w
+ * respectively.
+ */
+ {
+ int code = 0;
+ switch (event->keyval) {
+ case GDK_F1:
+ code = (event->state & GDK_SHIFT_MASK ? 23 : 11);
+ break;
+ case GDK_F2:
+ code = (event->state & GDK_SHIFT_MASK ? 24 : 12);
+ break;
+ case GDK_F3:
+ code = (event->state & GDK_SHIFT_MASK ? 25 : 13);
+ break;
+ case GDK_F4:
+ code = (event->state & GDK_SHIFT_MASK ? 26 : 14);
+ break;
+ case GDK_F5:
+ code = (event->state & GDK_SHIFT_MASK ? 28 : 15);
+ break;
+ case GDK_F6:
+ code = (event->state & GDK_SHIFT_MASK ? 29 : 17);
+ break;
+ case GDK_F7:
+ code = (event->state & GDK_SHIFT_MASK ? 31 : 18);
+ break;
+ case GDK_F8:
+ code = (event->state & GDK_SHIFT_MASK ? 32 : 19);
+ break;
+ case GDK_F9:
+ code = (event->state & GDK_SHIFT_MASK ? 33 : 20);
+ break;
+ case GDK_F10:
+ code = (event->state & GDK_SHIFT_MASK ? 34 : 21);
+ break;
+ case GDK_F11:
+ code = 23;
+ break;
+ case GDK_F12:
+ code = 24;
+ break;
+ case GDK_F13:
+ code = 25;
+ break;
+ case GDK_F14:
+ code = 26;
+ break;
+ case GDK_F15:
+ code = 28;
+ break;
+ case GDK_F16:
+ code = 29;
+ break;
+ case GDK_F17:
+ code = 31;
+ break;
+ case GDK_F18:
+ code = 32;
+ break;
+ case GDK_F19:
+ code = 33;
+ break;
+ case GDK_F20:
+ code = 34;
+ break;
+ }
+ if (!(event->state & GDK_CONTROL_MASK)) switch (event->keyval) {
+ case GDK_Home: case GDK_KP_Home:
+ code = 1;
+ break;
+ case GDK_Insert: case GDK_KP_Insert:
+ code = 2;
+ break;
+ case GDK_Delete: case GDK_KP_Delete:
+ code = 3;
+ break;
+ case GDK_End: case GDK_KP_End:
+ code = 4;
+ break;
+ case GDK_Page_Up: case GDK_KP_Page_Up:
+ code = 5;
+ break;
+ 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)
+ 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 */
+ code >= 11 && code <= 34) {
+ char codes[] = "MNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz@[\\]^_`{";
+ int index = 0;
+ switch (event->keyval) {
+ case GDK_F1: index = 0; break;
+ case GDK_F2: index = 1; break;
+ case GDK_F3: index = 2; break;
+ case GDK_F4: index = 3; break;
+ case GDK_F5: index = 4; break;
+ case GDK_F6: index = 5; break;
+ case GDK_F7: index = 6; break;
+ case GDK_F8: index = 7; break;
+ case GDK_F9: index = 8; break;
+ case GDK_F10: index = 9; break;
+ case GDK_F11: index = 10; break;
+ case GDK_F12: index = 11; break;
+ }
+ if (event->state & GDK_SHIFT_MASK) index += 12;
+ 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 */
+ code >= 1 && code <= 6) {
+ char codes[] = "HL.FIG";
+ if (code == 3) {
+ output[1] = '\x7F';
+ end = 2;
+ } else {
+ 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) &&
+ code >= 11 && code <= 24) {
+ int offt = 0;
+ if (code > 15)
+ offt++;
+ if (code > 21)
+ offt++;
+ if (inst->term->vt52_mode)
+ end = 1 + sprintf(output+1,
+ "\x1B%c", code + 'P' - 11 - offt);
+ else
+ 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) {
+ 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 (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)) {
+ end = 1 + sprintf(output+1, code == 1 ? "\x1B[H" : "\x1BOw");
+ use_ucsoutput = FALSE;
+ goto done;
+ }
+ if (code) {
+ end = 1 + sprintf(output+1, "\x1B[%d~", code);
+ use_ucsoutput = FALSE;
+ goto done;
+ }
+ }
+
+ /*
+ * Cursor keys. (This includes the numberpad cursor keys,
+ * if we haven't already done them due to app keypad mode.)
+ *
+ * Here we also process un-numlocked un-appkeypadded KP5,
+ * which sends ESC [ G.
+ */
+ {
+ int xkey = 0;
+ switch (event->keyval) {
+ case GDK_Up: case GDK_KP_Up: xkey = 'A'; break;
+ case GDK_Down: case GDK_KP_Down: xkey = 'B'; break;
+ case GDK_Right: case GDK_KP_Right: xkey = 'C'; break;
+ case GDK_Left: case GDK_KP_Left: xkey = 'D'; break;
+ case GDK_Begin: case GDK_KP_Begin: xkey = 'G'; break;
+ }
+ if (xkey) {
+ end = 1 + format_arrow_key(output+1, inst->term, xkey,
+ event->state & GDK_CONTROL_MASK);
+ use_ucsoutput = FALSE;
+ goto done;
+ }
+ }
+ goto done;
+ }
+
+ done:
+
+ if (end-start > 0) {
+#ifdef KEY_DEBUGGING
+ int i;
+ printf("generating sequence:");
+ for (i = start; i < end; i++)
+ printf(" %02x", (unsigned char) output[i]);
+ printf("\n");
+#endif
+
+ if (special) {
+ /*
+ * For special control characters, the character set
+ * should never matter.
+ */
+ output[end] = '\0'; /* NUL-terminate */
+ if (inst->ldisc)
+ ldisc_send(inst->ldisc, output+start, -2, 1);
+ } else if (!inst->direct_to_font) {
+ if (!use_ucsoutput) {
+ if (inst->ldisc)
+ lpage_send(inst->ldisc, output_charset, output+start,
+ end-start, 1);
+ } else {
+ /*
+ * We generated our own Unicode key data from the
+ * keysym, so use that instead.
+ */
+ if (inst->ldisc)
+ luni_send(inst->ldisc, ucsoutput+start, end-start, 1);
+ }
+ } else {
+ /*
+ * In direct-to-font mode, we just send the string
+ * exactly as we received it.
+ */
+ if (inst->ldisc)
+ ldisc_send(inst->ldisc, output+start, end-start, 1);
+ }
+
+ show_mouseptr(inst, 0);
+ term_seen_key_event(inst->term);
+ }
+
+ return TRUE;
+}
+
+gboolean button_internal(struct gui_data *inst, guint32 timestamp,
+ GdkEventType type, guint ebutton, guint state,
+ gdouble ex, gdouble ey)
+{
+ int shift, ctrl, alt, x, y, button, act;
+
+ /* Remember the timestamp. */
+ inst->input_event_time = timestamp;
+
+ show_mouseptr(inst, 1);
+
+ if (ebutton == 4 && type == GDK_BUTTON_PRESS) {
+ term_scroll(inst->term, 0, -5);
+ return TRUE;
+ }
+ if (ebutton == 5 && type == GDK_BUTTON_PRESS) {
+ term_scroll(inst->term, 0, +5);
+ return TRUE;
+ }
+
+ shift = state & GDK_SHIFT_MASK;
+ ctrl = state & GDK_CONTROL_MASK;
+ alt = state & GDK_MOD1_MASK;
+
+ if (ebutton == 3 && ctrl) {
+ gtk_menu_popup(GTK_MENU(inst->menu), NULL, NULL, NULL, NULL,
+ ebutton, timestamp);
+ return TRUE;
+ }
+
+ if (ebutton == 1)
+ button = MBT_LEFT;
+ else if (ebutton == 2)
+ button = MBT_MIDDLE;
+ else if (ebutton == 3)
+ button = MBT_RIGHT;
+ else
+ return FALSE; /* don't even know what button! */
+
+ switch (type) {
+ case GDK_BUTTON_PRESS: act = MA_CLICK; break;
+ case GDK_BUTTON_RELEASE: act = MA_RELEASE; break;
+ 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) &&
+ 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;
+
+ term_mouse(inst->term, button, translate_button(button), act,
+ x, y, shift, ctrl, alt);
+
+ return TRUE;
+}
+
+gboolean button_event(GtkWidget *widget, GdkEventButton *event, gpointer data)
+{
+ struct gui_data *inst = (struct gui_data *)data;
+ return button_internal(inst, event->time, event->type, event->button,
+ event->state, event->x, event->y);
+}
+
+#if GTK_CHECK_VERSION(2,0,0)
+/*
+ * In GTK 2, mouse wheel events have become a new type of event.
+ * This handler translates them back into button-4 and button-5
+ * presses so that I don't have to change my old code too much :-)
+ */
+gboolean scroll_event(GtkWidget *widget, GdkEventScroll *event, gpointer data)
+{
+ struct gui_data *inst = (struct gui_data *)data;
+ guint button;
+
+ if (event->direction == GDK_SCROLL_UP)
+ button = 4;
+ else if (event->direction == GDK_SCROLL_DOWN)
+ button = 5;
+ else
+ return FALSE;
+
+ return button_internal(inst, event->time, GDK_BUTTON_PRESS,
+ button, event->state, event->x, event->y);
+}
+#endif
+
+gint motion_event(GtkWidget *widget, GdkEventMotion *event, gpointer data)
+{
+ struct gui_data *inst = (struct gui_data *)data;
+ int shift, ctrl, alt, x, y, button;
+
+ /* Remember the timestamp. */
+ inst->input_event_time = event->time;
+
+ show_mouseptr(inst, 1);
+
+ shift = event->state & GDK_SHIFT_MASK;
+ ctrl = event->state & GDK_CONTROL_MASK;
+ alt = event->state & GDK_MOD1_MASK;
+ if (event->state & GDK_BUTTON1_MASK)
+ button = MBT_LEFT;
+ else if (event->state & GDK_BUTTON2_MASK)
+ button = MBT_MIDDLE;
+ 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;
+
+ term_mouse(inst->term, button, translate_button(button), MA_DRAG,
+ x, y, shift, ctrl, alt);
+
+ return TRUE;
+}
+
+void frontend_keypress(void *handle)
+{
+ struct gui_data *inst = (struct gui_data *)handle;
+
+ /*
+ * If our child process has exited but not closed, terminate on
+ * any keypress.
+ */
+ if (inst->exited)
+ exit(0);
+}
+
+static gint idle_exit_func(gpointer data)
+{
+ struct gui_data *inst = (struct gui_data *)data;
+ int exitcode;
+
+ 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))
+ 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);
+ }
+ 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);
+}
+
+static gint timer_trigger(gpointer data)
+{
+ long now = GPOINTER_TO_LONG(data);
+ long next;
+ long ticks;
+
+ if (run_timers(now, &next)) {
+ ticks = next - GETTICKCOUNT();
+ timer_id = gtk_timeout_add(ticks > 0 ? ticks : 1, timer_trigger,
+ LONG_TO_GPOINTER(next));
+ }
+
+ /*
+ * Never let a timer resume. If we need another one, we've
+ * asked for it explicitly above.
+ */
+ return FALSE;
+}
+
+void timer_change_notify(long next)
+{
+ long ticks;
+
+ if (timer_id)
+ gtk_timeout_remove(timer_id);
+
+ ticks = next - GETTICKCOUNT();
+ if (ticks <= 0)
+ ticks = 1; /* just in case */
+
+ timer_id = gtk_timeout_add(ticks, timer_trigger,
+ LONG_TO_GPOINTER(next));
+}
+
+void fd_input_func(gpointer data, gint sourcefd, GdkInputCondition condition)
+{
+ /*
+ * We must process exceptional notifications before ordinary
+ * readability ones, or we may go straight past the urgent
+ * marker.
+ */
+ if (condition & GDK_INPUT_EXCEPTION)
+ select_result(sourcefd, 4);
+ if (condition & GDK_INPUT_READ)
+ select_result(sourcefd, 1);
+ if (condition & GDK_INPUT_WRITE)
+ select_result(sourcefd, 2);
+}
+
+void destroy(GtkWidget *widget, gpointer data)
+{
+ gtk_main_quit();
+}
+
+gint focus_event(GtkWidget *widget, GdkEventFocus *event, gpointer data)
+{
+ struct gui_data *inst = (struct gui_data *)data;
+ term_set_focus(inst->term, event->in);
+ term_update(inst->term);
+ show_mouseptr(inst, 1);
+ return FALSE;
+}
+
+void set_busy_status(void *frontend, int status)
+{
+ struct gui_data *inst = (struct gui_data *)frontend;
+ inst->busy_status = status;
+ update_mouseptr(inst);
+}
+
+/*
+ * 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;
+ send_raw_mouse = activate;
+ update_mouseptr(inst);
+}
+
+void request_resize(void *frontend, int w, int h)
+{
+ struct gui_data *inst = (struct gui_data *)frontend;
+ int large_x, large_y;
+ int offset_x, offset_y;
+ int area_x, area_y;
+ GtkRequisition inner, outer;
+
+ /*
+ * This is a heinous hack dreamed up by the gnome-terminal
+ * people to get around a limitation in gtk. The problem is
+ * that in order to set the size correctly we really need to be
+ * calling gtk_window_resize - but that needs to know the size
+ * of the _whole window_, not the drawing area. So what we do
+ * is to set an artificially huge size request on the drawing
+ * area, recompute the resulting size request on the window,
+ * and look at the difference between the two. That gives us
+ * the x and y offsets we need to translate drawing area size
+ * into window size for real, and then we call
+ * gtk_window_resize.
+ */
+
+ /*
+ * We start by retrieving the current size of the whole window.
+ * Adding a bit to _that_ will give us a value we can use as a
+ * bogus size request which guarantees to be bigger than the
+ * current size of the drawing area.
+ */
+ get_window_pixels(inst, &large_x, &large_y);
+ large_x += 32;
+ large_y += 32;
+
+#if GTK_CHECK_VERSION(2,0,0)
+ gtk_widget_set_size_request(inst->area, large_x, large_y);
+#else
+ gtk_widget_set_usize(inst->area, large_x, large_y);
+#endif
+ gtk_widget_size_request(inst->area, &inner);
+ 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;
+
+ /*
+ * 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
+ * really going to end up being.
+ */
+#if GTK_CHECK_VERSION(2,0,0)
+ gtk_widget_set_size_request(inst->area, area_x, area_y);
+ gtk_window_resize(GTK_WINDOW(inst->window),
+ area_x + offset_x, area_y + offset_y);
+#else
+ gtk_widget_set_usize(inst->area, area_x, area_y);
+ gtk_drawing_area_size(GTK_DRAWING_AREA(inst->area), area_x, area_y);
+ /*
+ * I can no longer remember what this call to
+ * gtk_container_dequeue_resize_handler is for. It was
+ * introduced in r3092 with no comment, and the commit log
+ * message was uninformative. I'm _guessing_ its purpose is to
+ * prevent gratuitous resize processing on the window given
+ * that we're about to resize it anyway, but I have no idea
+ * why that's so incredibly vital.
+ *
+ * I've tried removing the call, and nothing seems to go
+ * wrong. I've backtracked to r3092 and tried removing the
+ * call there, and still nothing goes wrong. So I'm going to
+ * adopt the working hypothesis that it's superfluous; I won't
+ * actually remove it from the GTK 1.2 code, but I won't
+ * attempt to replicate its functionality in the GTK 2 code
+ * above.
+ */
+ gtk_container_dequeue_resize_handler(GTK_CONTAINER(inst->window));
+ gdk_window_resize(inst->window->window,
+ area_x + offset_x, area_y + offset_y);
+#endif
+}
+
+static void real_palette_set(struct gui_data *inst, int n, int r, int g, int b)
+{
+ gboolean success[1];
+
+ inst->cols[n].red = r * 0x0101;
+ inst->cols[n].green = g * 0x0101;
+ inst->cols[n].blue = b * 0x0101;
+
+ gdk_colormap_free_colors(inst->colmap, inst->cols + n, 1);
+ gdk_colormap_alloc_colors(inst->colmap, inst->cols + n, 1,
+ FALSE, TRUE, success);
+ if (!success[0])
+ g_error("%s: couldn't allocate colour %d (#%02x%02x%02x)\n", appname,
+ n, r, g, b);
+}
+
+void set_window_background(struct gui_data *inst)
+{
+ if (inst->area && inst->area->window)
+ gdk_window_set_background(inst->area->window, &inst->cols[258]);
+ if (inst->window && inst->window->window)
+ gdk_window_set_background(inst->window->window, &inst->cols[258]);
+}
+
+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)
+ 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 */
+ set_window_background(inst);
+ draw_backing_rect(inst);
+ gtk_widget_queue_draw(inst->area);
+ }
+}
+
+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. */
+ 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
+ };
+ gboolean success[NALLCOLOURS];
+ int i;
+
+ assert(lenof(ww) == NCFGCOLOURS);
+
+ if (!inst->colmap) {
+ inst->colmap = gdk_colormap_get_system();
+ } 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;
+ }
+
+ for (i = 0; i < NEXTCOLOURS; i++) {
+ if (i < 216) {
+ int r = i / 36, g = (i / 6) % 6, b = i % 6;
+ inst->cols[i+16].red = r ? r * 0x2828 + 0x3737 : 0;
+ inst->cols[i+16].green = g ? g * 0x2828 + 0x3737 : 0;
+ inst->cols[i+16].blue = b ? b * 0x2828 + 0x3737 : 0;
+ } else {
+ int shade = i - 216;
+ shade = shade * 0x0a0a + 0x0808;
+ inst->cols[i+16].red = inst->cols[i+16].green =
+ inst->cols[i+16].blue = shade;
+ }
+ }
+
+ 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]);
+ }
+
+ /* Since Default Background may have changed, ensure that space
+ * between text area and window border is refreshed. */
+ set_window_background(inst);
+ if (inst->area && inst->area->window) {
+ draw_backing_rect(inst);
+ gtk_widget_queue_draw(inst->area);
+ }
+}
+
+/* Ensure that all the cut buffers exist - according to the ICCCM, we must
+ * do this before we start using cut buffers.
+ */
+void init_cutbuffers()
+{
+ unsigned char empty[] = "";
+ XChangeProperty(GDK_DISPLAY(), GDK_ROOT_WINDOW(),
+ XA_CUT_BUFFER0, XA_STRING, 8, PropModeAppend, empty, 0);
+ XChangeProperty(GDK_DISPLAY(), GDK_ROOT_WINDOW(),
+ XA_CUT_BUFFER1, XA_STRING, 8, PropModeAppend, empty, 0);
+ XChangeProperty(GDK_DISPLAY(), GDK_ROOT_WINDOW(),
+ XA_CUT_BUFFER2, XA_STRING, 8, PropModeAppend, empty, 0);
+ XChangeProperty(GDK_DISPLAY(), GDK_ROOT_WINDOW(),
+ XA_CUT_BUFFER3, XA_STRING, 8, PropModeAppend, empty, 0);
+ XChangeProperty(GDK_DISPLAY(), GDK_ROOT_WINDOW(),
+ XA_CUT_BUFFER4, XA_STRING, 8, PropModeAppend, empty, 0);
+ XChangeProperty(GDK_DISPLAY(), GDK_ROOT_WINDOW(),
+ XA_CUT_BUFFER5, XA_STRING, 8, PropModeAppend, empty, 0);
+ XChangeProperty(GDK_DISPLAY(), GDK_ROOT_WINDOW(),
+ XA_CUT_BUFFER6, XA_STRING, 8, PropModeAppend, empty, 0);
+ XChangeProperty(GDK_DISPLAY(), GDK_ROOT_WINDOW(),
+ XA_CUT_BUFFER7, XA_STRING, 8, PropModeAppend, empty, 0);
+}
+
+/* Store the data in a cut-buffer. */
+void store_cutbuffer(char * ptr, int len)
+{
+ /* ICCCM says we must rotate the buffers before storing to buffer 0. */
+ XRotateBuffers(GDK_DISPLAY(), 1);
+ XStoreBytes(GDK_DISPLAY(), ptr, len);
+}
+
+/* Retrieve data from a cut-buffer.
+ * Returned data needs to be freed with XFree().
+ */
+char * retrieve_cutbuffer(int * nbytes)
+{
+ char * ptr;
+ ptr = XFetchBytes(GDK_DISPLAY(), nbytes);
+ if (*nbytes <= 0 && ptr != 0) {
+ XFree(ptr);
+ ptr = 0;
+ }
+ return ptr;
+}
+
+void write_clip(void *frontend, wchar_t * data, int *attr, int len, int must_deselect)
+{
+ struct gui_data *inst = (struct gui_data *)frontend;
+ if (inst->pasteout_data)
+ sfree(inst->pasteout_data);
+ if (inst->pasteout_data_ctext)
+ sfree(inst->pasteout_data_ctext);
+ if (inst->pasteout_data_utf8)
+ sfree(inst->pasteout_data_utf8);
+
+ /*
+ * 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;
+ int tmplen = len;
+ XTextProperty tp;
+ char *list[1];
+
+ inst->pasteout_data_utf8 = snewn(len*6, char);
+ inst->pasteout_data_utf8_len = len*6;
+ inst->pasteout_data_utf8_len =
+ charset_from_unicode(&tmp, &tmplen, inst->pasteout_data_utf8,
+ inst->pasteout_data_utf8_len,
+ CS_UTF8, NULL, NULL, 0);
+ if (inst->pasteout_data_utf8_len == 0) {
+ sfree(inst->pasteout_data_utf8);
+ inst->pasteout_data_utf8 = NULL;
+ } else {
+ inst->pasteout_data_utf8 =
+ sresize(inst->pasteout_data_utf8,
+ inst->pasteout_data_utf8_len + 1, char);
+ inst->pasteout_data_utf8[inst->pasteout_data_utf8_len] = '\0';
+ }
+
+ /*
+ * Now let Xlib convert our UTF-8 data into compound text.
+ */
+ list[0] = inst->pasteout_data_utf8;
+ if (Xutf8TextListToTextProperty(GDK_DISPLAY(), list, 1,
+ XCompoundTextStyle, &tp) == 0) {
+ inst->pasteout_data_ctext = snewn(tp.nitems+1, char);
+ memcpy(inst->pasteout_data_ctext, tp.value, tp.nitems);
+ inst->pasteout_data_ctext_len = tp.nitems;
+ XFree(tp.value);
+ } else {
+ inst->pasteout_data_ctext = NULL;
+ inst->pasteout_data_ctext_len = 0;
+ }
+ } else {
+ inst->pasteout_data_utf8 = NULL;
+ inst->pasteout_data_utf8_len = 0;
+ inst->pasteout_data_ctext = NULL;
+ inst->pasteout_data_ctext_len = 0;
+ }
+
+ inst->pasteout_data = snewn(len*6, char);
+ inst->pasteout_data_len = len*6;
+ inst->pasteout_data_len = wc_to_mb(inst->ucsdata.line_codepage, 0,
+ data, len, inst->pasteout_data,
+ inst->pasteout_data_len,
+ NULL, NULL, NULL);
+ if (inst->pasteout_data_len == 0) {
+ sfree(inst->pasteout_data);
+ inst->pasteout_data = NULL;
+ } else {
+ inst->pasteout_data =
+ sresize(inst->pasteout_data, inst->pasteout_data_len, char);
+ }
+
+ store_cutbuffer(inst->pasteout_data, inst->pasteout_data_len);
+
+ if (gtk_selection_owner_set(inst->area, GDK_SELECTION_PRIMARY,
+ inst->input_event_time)) {
+#if GTK_CHECK_VERSION(2,0,0)
+ gtk_selection_clear_targets(inst->area, GDK_SELECTION_PRIMARY);
+#endif
+ gtk_selection_add_target(inst->area, GDK_SELECTION_PRIMARY,
+ GDK_SELECTION_TYPE_STRING, 1);
+ if (inst->pasteout_data_ctext)
+ gtk_selection_add_target(inst->area, GDK_SELECTION_PRIMARY,
+ compound_text_atom, 1);
+ if (inst->pasteout_data_utf8)
+ gtk_selection_add_target(inst->area, GDK_SELECTION_PRIMARY,
+ utf8_string_atom, 1);
+ }
+
+ if (must_deselect)
+ term_deselect(inst->term);
+}
+
+void selection_get(GtkWidget *widget, GtkSelectionData *seldata,
+ guint info, guint time_stamp, gpointer data)
+{
+ struct gui_data *inst = (struct gui_data *)data;
+ if (seldata->target == utf8_string_atom)
+ gtk_selection_data_set(seldata, seldata->target, 8,
+ (unsigned char *)inst->pasteout_data_utf8,
+ inst->pasteout_data_utf8_len);
+ else if (seldata->target == compound_text_atom)
+ gtk_selection_data_set(seldata, seldata->target, 8,
+ (unsigned char *)inst->pasteout_data_ctext,
+ inst->pasteout_data_ctext_len);
+ else
+ gtk_selection_data_set(seldata, seldata->target, 8,
+ (unsigned char *)inst->pasteout_data,
+ inst->pasteout_data_len);
+}
+
+gint selection_clear(GtkWidget *widget, GdkEventSelection *seldata,
+ gpointer data)
+{
+ struct gui_data *inst = (struct gui_data *)data;
+
+ term_deselect(inst->term);
+ if (inst->pasteout_data)
+ sfree(inst->pasteout_data);
+ if (inst->pasteout_data_ctext)
+ sfree(inst->pasteout_data_ctext);
+ if (inst->pasteout_data_utf8)
+ sfree(inst->pasteout_data_utf8);
+ inst->pasteout_data = NULL;
+ inst->pasteout_data_len = 0;
+ inst->pasteout_data_ctext = NULL;
+ inst->pasteout_data_ctext_len = 0;
+ inst->pasteout_data_utf8 = NULL;
+ inst->pasteout_data_utf8_len = 0;
+ return TRUE;
+}
+
+void request_paste(void *frontend)
+{
+ struct gui_data *inst = (struct gui_data *)frontend;
+ /*
+ * In Unix, pasting is asynchronous: all we can do at the
+ * moment is to call gtk_selection_convert(), and when the data
+ * comes back _then_ we can call term_do_paste().
+ */
+
+ if (!inst->direct_to_font) {
+ /*
+ * First we attempt to retrieve the selection as a UTF-8
+ * string (which we will convert to the correct code page
+ * before sending to the session, of course). If that
+ * fails, selection_received() will be informed and will
+ * fall back to an ordinary string.
+ */
+ gtk_selection_convert(inst->area, GDK_SELECTION_PRIMARY,
+ utf8_string_atom,
+ inst->input_event_time);
+ } else {
+ /*
+ * If we're in direct-to-font mode, we disable UTF-8
+ * pasting, and go straight to ordinary string data.
+ */
+ gtk_selection_convert(inst->area, GDK_SELECTION_PRIMARY,
+ GDK_SELECTION_TYPE_STRING,
+ inst->input_event_time);
+ }
+}
+
+gint idle_paste_func(gpointer data); /* forward ref */
+
+void selection_received(GtkWidget *widget, GtkSelectionData *seldata,
+ guint time, gpointer data)
+{
+ struct gui_data *inst = (struct gui_data *)data;
+ XTextProperty tp;
+ char **list;
+ char *text;
+ int length, count, ret;
+ int free_list_required = 0;
+ int free_required = 0;
+ int charset;
+
+ if (seldata->target == utf8_string_atom && seldata->length <= 0) {
+ /*
+ * Failed to get a UTF-8 selection string. Try compound
+ * text next.
+ */
+ gtk_selection_convert(inst->area, GDK_SELECTION_PRIMARY,
+ compound_text_atom,
+ inst->input_event_time);
+ return;
+ }
+
+ if (seldata->target == compound_text_atom && seldata->length <= 0) {
+ /*
+ * Failed to get UTF-8 or compound text. Try an ordinary
+ * string.
+ */
+ gtk_selection_convert(inst->area, GDK_SELECTION_PRIMARY,
+ GDK_SELECTION_TYPE_STRING,
+ inst->input_event_time);
+ return;
+ }
+
+ /*
+ * If we have data, but it's not of a type we can deal with,
+ * we have to ignore the data.
+ */
+ if (seldata->length > 0 &&
+ seldata->type != GDK_SELECTION_TYPE_STRING &&
+ seldata->type != compound_text_atom &&
+ seldata->type != utf8_string_atom)
+ return;
+
+ /*
+ * If we have no data, try looking in a cut buffer.
+ */
+ if (seldata->length <= 0) {
+ text = retrieve_cutbuffer(&length);
+ if (length == 0)
+ return;
+ /* Xterm is rumoured to expect Latin-1, though I havn't checked the
+ * source, so use that as a de-facto standard. */
+ charset = CS_ISO8859_1;
+ free_required = 1;
+ } else {
+ /*
+ * Convert COMPOUND_TEXT into UTF-8.
+ */
+ if (seldata->type == compound_text_atom) {
+ tp.value = seldata->data;
+ tp.encoding = (Atom) seldata->type;
+ tp.format = seldata->format;
+ tp.nitems = seldata->length;
+ ret = Xutf8TextPropertyToTextList(GDK_DISPLAY(), &tp,
+ &list, &count);
+ if (ret != 0 || count != 1) {
+ /*
+ * Compound text failed; fall back to STRING.
+ */
+ gtk_selection_convert(inst->area, GDK_SELECTION_PRIMARY,
+ GDK_SELECTION_TYPE_STRING,
+ inst->input_event_time);
+ return;
+ }
+ text = list[0];
+ length = strlen(list[0]);
+ charset = CS_UTF8;
+ free_list_required = 1;
+ } else {
+ text = (char *)seldata->data;
+ length = seldata->length;
+ charset = (seldata->type == utf8_string_atom ?
+ CS_UTF8 : inst->ucsdata.line_codepage);
+ }
+ }
+
+ if (inst->pastein_data)
+ sfree(inst->pastein_data);
+
+ inst->pastein_data = snewn(length, wchar_t);
+ inst->pastein_data_len = length;
+ inst->pastein_data_len =
+ 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;
+
+ if (p) {
+ *p = inst->pastein_data;
+ *len = inst->pastein_data_len;
+ }
+}
+
+static void set_window_titles(struct gui_data *inst)
+{
+ /*
+ * 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)
+ 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';
+ 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';
+ 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)
+ return;
+ inst->sbar_adjust->lower = 0;
+ inst->sbar_adjust->upper = total;
+ inst->sbar_adjust->value = start;
+ inst->sbar_adjust->page_size = page;
+ inst->sbar_adjust->step_increment = 1;
+ inst->sbar_adjust->page_increment = page/2;
+ inst->ignore_sbar = TRUE;
+ gtk_adjustment_changed(inst->sbar_adjust);
+ inst->ignore_sbar = FALSE;
+}
+
+void scrollbar_moved(GtkAdjustment *adj, gpointer data)
+{
+ struct gui_data *inst = (struct gui_data *)data;
+
+ if (!inst->cfg.scrollbar)
+ return;
+ if (!inst->ignore_sbar)
+ term_scroll(inst->term, 1, (int)adj->value);
+}
+
+void sys_cursor(void *frontend, int x, int y)
+{
+ /*
+ * This is meaningless under X.
+ */
+}
+
+/*
+ * This is still called when mode==BELL_VISUAL, even though the
+ * visual bell is handled entirely within terminal.c, because we
+ * may want to perform additional actions on any kind of bell (for
+ * example, taskbar flashing in Windows).
+ */
+void do_beep(void *frontend, int mode)
+{
+ if (mode == BELL_DEFAULT)
+ gdk_beep();
+}
+
+int char_width(Context ctx, int uc)
+{
+ /*
+ * Under X, any fixed-width font really _is_ fixed-width.
+ * Double-width characters will be dealt with using a separate
+ * font. For the moment we can simply return 1.
+ *
+ * FIXME: but is that also true of Pango?
+ */
+ return 1;
+}
+
+Context get_ctx(void *frontend)
+{
+ struct gui_data *inst = (struct gui_data *)frontend;
+ struct draw_ctx *dctx;
+
+ if (!inst->area->window)
+ return NULL;
+
+ dctx = snew(struct draw_ctx);
+ dctx->inst = inst;
+ dctx->gc = gdk_gc_new(inst->area->window);
+ return dctx;
+}
+
+void free_ctx(Context ctx)
+{
+ struct draw_ctx *dctx = (struct draw_ctx *)ctx;
+ /* struct gui_data *inst = dctx->inst; */
+ GdkGC *gc = dctx->gc;
+ gdk_gc_unref(gc);
+ sfree(dctx);
+}
+
+/*
+ * Draw a line of text in the window, at given character
+ * coordinates, in given attributes.
+ *
+ * We are allowed to fiddle with the contents of `text'.
+ */
+void do_text_internal(Context ctx, int x, int y, wchar_t *text, int len,
+ unsigned long attr, int lattr)
+{
+ struct draw_ctx *dctx = (struct draw_ctx *)ctx;
+ struct gui_data *inst = dctx->inst;
+ GdkGC *gc = dctx->gc;
+ int ncombining, combining;
+ int nfg, nbg, t, fontid, shadow, rlen, widefactor, bold;
+ int monochrome = gtk_widget_get_visual(inst->area)->depth == 1;
+
+ if (attr & TATTR_COMBINING) {
+ ncombining = len;
+ len = 1;
+ } else
+ ncombining = 1;
+
+ nfg = ((monochrome ? ATTR_DEFFG : (attr & ATTR_FGMASK)) >> ATTR_FGSHIFT);
+ nbg = ((monochrome ? ATTR_DEFBG : (attr & ATTR_BGMASK)) >> ATTR_BGSHIFT);
+ if (!!(attr & ATTR_REVERSE) ^ (monochrome && (attr & TATTR_ACTCURS))) {
+ t = nfg;
+ nfg = nbg;
+ nbg = t;
+ }
+ if (inst->cfg.bold_colour && (attr & ATTR_BOLD)) {
+ if (nfg < 16) nfg |= 8;
+ else if (nfg >= 256) nfg |= 1;
+ }
+ if (inst->cfg.bold_colour && (attr & ATTR_BLINK)) {
+ if (nbg < 16) nbg |= 8;
+ else if (nbg >= 256) nbg |= 1;
+ }
+ if ((attr & TATTR_ACTCURS) && !monochrome) {
+ nfg = 260;
+ nbg = 261;
+ }
+
+ fontid = shadow = 0;
+
+ if (attr & ATTR_WIDE) {
+ widefactor = 2;
+ fontid |= 2;
+ } else {
+ widefactor = 1;
+ }
+
+ if ((attr & ATTR_BOLD) && !inst->cfg.bold_colour) {
+ bold = 1;
+ fontid |= 1;
+ } else {
+ bold = 0;
+ }
+
+ if (!inst->fonts[fontid]) {
+ int i;
+ /*
+ * Fall back through font ids with subsets of this one's
+ * set bits, in order.
+ */
+ for (i = fontid; i-- > 0 ;) {
+ if (i & ~fontid)
+ continue; /* some other bit is set */
+ if (inst->fonts[i]) {
+ fontid = i;
+ break;
+ }
+ }
+ assert(inst->fonts[fontid]); /* we should at least have hit zero */
+ }
+
+ if ((lattr & LATTR_MODE) != LATTR_NORM) {
+ x *= 2;
+ if (x >= inst->term->cols)
+ return;
+ if (x + len*2*widefactor > inst->term->cols)
+ len = (inst->term->cols-x)/2/widefactor;/* trim to LH half */
+ rlen = len * 2;
+ } else
+ 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.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,
+ 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);
+ }
+
+ 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);
+ }
+
+ if ((lattr & LATTR_MODE) != LATTR_NORM) {
+ /*
+ * I can't find any plausible StretchBlt equivalent in the
+ * X server, so I'm going to do this the slow and painful
+ * way. This will involve repeated calls to
+ * gdk_draw_pixmap() to stretch the text horizontally. It's
+ * O(N^2) in time and O(N) in network bandwidth, but you
+ * 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,
+ len * widefactor * inst->font_width - i, inst->font_height);
+ }
+ len *= 2;
+ if ((lattr & LATTR_MODE) != LATTR_WIDE) {
+ int dt, db;
+ /* Now stretch vertically, in the same way. */
+ if ((lattr & LATTR_MODE) == LATTR_BOT)
+ 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),
+ len * widefactor * inst->font_width, inst->font_height-i-1);
+ }
+ }
+ }
+}
+
+void do_text(Context ctx, int x, int y, wchar_t *text, int len,
+ unsigned long attr, int lattr)
+{
+ struct draw_ctx *dctx = (struct draw_ctx *)ctx;
+ struct gui_data *inst = dctx->inst;
+ GdkGC *gc = dctx->gc;
+ int widefactor;
+
+ do_text_internal(ctx, x, y, text, len, attr, lattr);
+
+ if (attr & ATTR_WIDE) {
+ widefactor = 2;
+ } else {
+ widefactor = 1;
+ }
+
+ if ((lattr & LATTR_MODE) != LATTR_NORM) {
+ x *= 2;
+ if (x >= inst->term->cols)
+ return;
+ if (x + len*2*widefactor > inst->term->cols)
+ 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,
+ 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)
+{
+ struct draw_ctx *dctx = (struct draw_ctx *)ctx;
+ struct gui_data *inst = dctx->inst;
+ GdkGC *gc = dctx->gc;
+
+ int active, passive, widefactor;
+
+ if (attr & TATTR_PASCURS) {
+ attr &= ~TATTR_PASCURS;
+ passive = 1;
+ } else
+ passive = 0;
+ if ((attr & TATTR_ACTCURS) && inst->cfg.cursor_type != 0) {
+ attr &= ~TATTR_ACTCURS;
+ active = 1;
+ } else
+ active = 0;
+ do_text_internal(ctx, x, y, text, len, attr, lattr);
+
+ if (attr & TATTR_COMBINING)
+ len = 1;
+
+ if (attr & ATTR_WIDE) {
+ widefactor = 2;
+ } else {
+ widefactor = 1;
+ }
+
+ if ((lattr & LATTR_MODE) != LATTR_NORM) {
+ x *= 2;
+ if (x >= inst->term->cols)
+ return;
+ 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) {
+ /*
+ * 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,
+ len*widefactor*inst->font_width-1, inst->font_height-1);
+ }
+ } else {
+ int uheight;
+ int startx, starty, dx, dy, length, i;
+
+ int char_width;
+
+ 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) {
+ 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;
+ 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;
+ dx = 0;
+ dy = 1;
+ length = inst->font_height;
+ }
+
+ gdk_gc_set_foreground(gc, &inst->cols[261]);
+ if (passive) {
+ for (i = 0; i < length; i++) {
+ if (i % 2 == 0) {
+ gdk_draw_point(inst->pixmap, gc, startx, starty);
+ }
+ startx += dx;
+ starty += dy;
+ }
+ } else if (active) {
+ gdk_draw_line(inst->pixmap, gc, startx, starty,
+ 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,
+ len*widefactor*inst->font_width, inst->font_height);
+}
+
+GdkCursor *make_mouse_ptr(struct gui_data *inst, int cursor_val)
+{
+ /*
+ * Truly hideous hack: GTK doesn't allow us to set the mouse
+ * cursor foreground and background colours unless we've _also_
+ * created our own cursor from bitmaps. Therefore, I need to
+ * load the `cursor' font and draw glyphs from it on to
+ * pixmaps, in order to construct my cursors with the fg and bg
+ * I want. This is a gross hack, but it's more self-contained
+ * than linking in Xlib to find the X window handle to
+ * inst->area and calling XRecolorCursor, and it's more
+ * futureproof than hard-coding the shapes as bitmap arrays.
+ */
+ static GdkFont *cursor_font = NULL;
+ GdkPixmap *source, *mask;
+ GdkGC *gc;
+ GdkColor cfg = { 0, 65535, 65535, 65535 };
+ GdkColor cbg = { 0, 0, 0, 0 };
+ GdkColor dfg = { 1, 65535, 65535, 65535 };
+ GdkColor dbg = { 0, 0, 0, 0 };
+ GdkCursor *ret;
+ gchar text[2];
+ gint lb, rb, wid, asc, desc, w, h, x, y;
+
+ if (cursor_val == -2) {
+ gdk_font_unref(cursor_font);
+ return NULL;
+ }
+
+ if (cursor_val >= 0 && !cursor_font) {
+ cursor_font = gdk_font_load("cursor");
+ if (cursor_font)
+ gdk_font_ref(cursor_font);
+ }
+
+ /*
+ * Get the text extent of the cursor in question. We use the
+ * mask character for this, because it's typically slightly
+ * bigger than the main character.
+ */
+ if (cursor_val >= 0) {
+ text[1] = '\0';
+ text[0] = (char)cursor_val + 1;
+ gdk_string_extents(cursor_font, text, &lb, &rb, &wid, &asc, &desc);
+ w = rb-lb; h = asc+desc; x = -lb; y = asc;
+ } else {
+ w = h = 1;
+ x = y = 0;
+ }
+
+ source = gdk_pixmap_new(NULL, w, h, 1);
+ mask = gdk_pixmap_new(NULL, w, h, 1);
+
+ /*
+ * Draw the mask character on the mask pixmap.
+ */
+ gc = gdk_gc_new(mask);
+ gdk_gc_set_foreground(gc, &dbg);
+ gdk_draw_rectangle(mask, gc, 1, 0, 0, w, h);
+ if (cursor_val >= 0) {
+ text[1] = '\0';
+ text[0] = (char)cursor_val + 1;
+ gdk_gc_set_foreground(gc, &dfg);
+ gdk_draw_text(mask, cursor_font, gc, x, y, text, 1);
+ }
+ gdk_gc_unref(gc);
+
+ /*
+ * Draw the main character on the source pixmap.
+ */
+ gc = gdk_gc_new(source);
+ gdk_gc_set_foreground(gc, &dbg);
+ gdk_draw_rectangle(source, gc, 1, 0, 0, w, h);
+ if (cursor_val >= 0) {
+ text[1] = '\0';
+ text[0] = (char)cursor_val;
+ gdk_gc_set_foreground(gc, &dfg);
+ gdk_draw_text(source, cursor_font, gc, x, y, text, 1);
+ }
+ gdk_gc_unref(gc);
+
+ /*
+ * Create the cursor.
+ */
+ ret = gdk_cursor_new_from_pixmap(source, mask, &cfg, &cbg, x, y);
+
+ /*
+ * Clean up.
+ */
+ gdk_pixmap_unref(source);
+ gdk_pixmap_unref(mask);
+
+ return ret;
+}
+
+void modalfatalbox(char *p, ...)
+{
+ va_list ap;
+ fprintf(stderr, "FATAL ERROR: ");
+ va_start(ap, p);
+ vfprintf(stderr, p, ap);
+ va_end(ap);
+ fputc('\n', stderr);
+ exit(1);
+}
+
+void cmdline_error(char *p, ...)
+{
+ va_list ap;
+ fprintf(stderr, "%s: ", appname);
+ va_start(ap, p);
+ vfprintf(stderr, p, ap);
+ va_end(ap);
+ fputc('\n', stderr);
+ exit(1);
+}
+
+char *get_x_display(void *frontend)
+{
+ return gdk_get_display();
+}
+
+long get_windowid(void *frontend)
+{
+ struct gui_data *inst = (struct gui_data *)frontend;
+ return (long)GDK_WINDOW_XWINDOW(inst->area->window);
+}
+
+static void help(FILE *fp) {
+ if(fprintf(fp,
+"pterm option summary:\n"
+"\n"
+" --display DISPLAY Specify X display to use (note '--')\n"
+" -name PREFIX Prefix when looking up resources (default: pterm)\n"
+" -fn FONT Normal text font\n"
+" -fb FONT Bold text font\n"
+" -geometry GEOMETRY Position and size of window (size in characters)\n"
+" -sl LINES Number of lines of scrollback\n"
+" -fg COLOUR, -bg COLOUR Foreground/background colour\n"
+" -bfg COLOUR, -bbg COLOUR Foreground/background bold colour\n"
+" -cfg COLOUR, -bfg COLOUR Foreground/background cursor colour\n"
+" -T TITLE Window title\n"
+" -ut, +ut Do(default) or do not update utmp\n"
+" -ls, +ls Do(default) or do not make shell a login shell\n"
+" -sb, +sb Do(default) or do not display a scrollbar\n"
+" -log PATH Log all output to a file\n"
+" -nethack Map numeric keypad to hjklyubn direction keys\n"
+" -xrm RESOURCE-STRING Set an X resource\n"
+" -e COMMAND [ARGS...] Execute command (consumes all remaining args)\n"
+ ) < 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)
+{
+ int err = 0;
+ char *val;
+
+ /*
+ * Macros to make argument handling easier. Note that because
+ * they need to call `continue', they cannot be contained in
+ * the usual do {...} while (0) wrapper to make them
+ * syntactically single statements; hence it is not legal to
+ * use one of these macros as an unbraced statement between
+ * `if' and `else'.
+ */
+#define EXPECTS_ARG { \
+ if (--argc <= 0) { \
+ err = 1; \
+ fprintf(stderr, "%s: %s expects an argument\n", appname, p); \
+ continue; \
+ } else \
+ val = *++argv; \
+}
+#define SECOND_PASS_ONLY { if (!do_everything) continue; }
+
+ while (--argc > 0) {
+ char *p = *++argv;
+ int ret;
+
+ /*
+ * Shameless cheating. Debian requires all X terminal
+ * emulators to support `-T title'; but
+ * cmdline_process_param will eat -T (it means no-pty) and
+ * complain that pterm doesn't support it. So, in pterm
+ * only, we convert -T into -title.
+ */
+ 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);
+
+ if (ret == -2) {
+ cmdline_error("option \"%s\" requires an argument", p);
+ } else if (ret == 2) {
+ --argc, ++argv; /* skip next argument */
+ continue;
+ } else if (ret == 1) {
+ continue;
+ }
+
+ if (!strcmp(p, "-fn") || !strcmp(p, "-font")) {
+ EXPECTS_ARG;
+ SECOND_PASS_ONLY;
+ strncpy(cfg->font.name, val, sizeof(cfg->font.name));
+ cfg->font.name[sizeof(cfg->font.name)-1] = '\0';
+
+ } else if (!strcmp(p, "-fb")) {
+ EXPECTS_ARG;
+ SECOND_PASS_ONLY;
+ strncpy(cfg->boldfont.name, val, sizeof(cfg->boldfont.name));
+ cfg->boldfont.name[sizeof(cfg->boldfont.name)-1] = '\0';
+
+ } else if (!strcmp(p, "-fw")) {
+ EXPECTS_ARG;
+ SECOND_PASS_ONLY;
+ strncpy(cfg->widefont.name, val, sizeof(cfg->widefont.name));
+ cfg->widefont.name[sizeof(cfg->widefont.name)-1] = '\0';
+
+ } else if (!strcmp(p, "-fwb")) {
+ EXPECTS_ARG;
+ SECOND_PASS_ONLY;
+ strncpy(cfg->wideboldfont.name, val, sizeof(cfg->wideboldfont.name));
+ cfg->wideboldfont.name[sizeof(cfg->wideboldfont.name)-1] = '\0';
+
+ } 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';
+
+ } 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;
+ if (flags & HeightValue)
+ cfg->height = (int)h;
+
+ if (flags & (XValue | YValue)) {
+ inst->xpos = x;
+ inst->ypos = y;
+ inst->gotpos = TRUE;
+ inst->gravity = ((flags & XNegative ? 1 : 0) |
+ (flags & YNegative ? 2 : 0));
+ }
+
+ } else if (!strcmp(p, "-sl")) {
+ EXPECTS_ARG;
+ SECOND_PASS_ONLY;
+ cfg->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;
+
+ EXPECTS_ARG;
+ SECOND_PASS_ONLY;
+ if (!gdk_color_parse(val, &col)) {
+ err = 1;
+ fprintf(stderr, "%s: unable to parse colour \"%s\"\n",
+ appname, val);
+ } else {
+ int index;
+ index = (!strcmp(p, "-fg") ? 0 :
+ !strcmp(p, "-bg") ? 2 :
+ !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;
+ }
+
+ } else if (use_pty_argv && !strcmp(p, "-e")) {
+ /* This option swallows all further arguments. */
+ if (!do_everything)
+ break;
+
+ if (--argc > 0) {
+ int i;
+ pty_argv = snewn(argc+1, char *);
+ ++argv;
+ for (i = 0; i < argc; i++)
+ pty_argv[i] = argv[i];
+ pty_argv[argc] = NULL;
+ break; /* finished command-line processing */
+ } else
+ err = 1, fprintf(stderr, "%s: -e expects an argument\n",
+ 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';
+
+ } else if (!strcmp(p, "-log")) {
+ 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;
+
+ } else if (!strcmp(p, "-ut-") || !strcmp(p, "+ut")) {
+ SECOND_PASS_ONLY;
+ cfg->stamp_utmp = 0;
+
+ } else if (!strcmp(p, "-ut")) {
+ SECOND_PASS_ONLY;
+ cfg->stamp_utmp = 1;
+
+ } else if (!strcmp(p, "-ls-") || !strcmp(p, "+ls")) {
+ SECOND_PASS_ONLY;
+ cfg->login_shell = 0;
+
+ } else if (!strcmp(p, "-ls")) {
+ SECOND_PASS_ONLY;
+ cfg->login_shell = 1;
+
+ } else if (!strcmp(p, "-nethack")) {
+ SECOND_PASS_ONLY;
+ cfg->nethack_keypad = 1;
+
+ } else if (!strcmp(p, "-sb-") || !strcmp(p, "+sb")) {
+ SECOND_PASS_ONLY;
+ cfg->scrollbar = 0;
+
+ } else if (!strcmp(p, "-sb")) {
+ SECOND_PASS_ONLY;
+ cfg->scrollbar = 0;
+
+ } else if (!strcmp(p, "-name")) {
+ EXPECTS_ARG;
+ app_name = val;
+
+ } else if (!strcmp(p, "-xrm")) {
+ EXPECTS_ARG;
+ provide_xrm_string(val);
+
+ } else if(!strcmp(p, "-help") || !strcmp(p, "--help")) {
+ help(stdout);
+ exit(0);
+
+ } else if (!strcmp(p, "-pgpfp")) {
+ pgp_fingerprints();
+ exit(1);
+
+ } else if(p[0] != '-' && (!do_everything ||
+ process_nonoption_arg(p, cfg,
+ allow_launch))) {
+ /* do nothing */
+
+ } else {
+ err = 1;
+ fprintf(stderr, "%s: unrecognized option '%s'\n", appname, p);
+ }
+ }
+
+ return err;
+}
+
+int uxsel_input_add(int fd, int rwx) {
+ int flags = 0;
+ if (rwx & 1) flags |= GDK_INPUT_READ;
+ if (rwx & 2) flags |= GDK_INPUT_WRITE;
+ if (rwx & 4) flags |= GDK_INPUT_EXCEPTION;
+ assert(flags);
+ return gdk_input_add(fd, flags, fd_input_func, NULL);
+}
+
+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);
+ }
+ }
+
+ 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->fonts[0]->public_charset,
+ inst->cfg.vtmode);
+}
+
+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.max_width = geom.max_height = -1;
+ geom.base_width = 2*inst->cfg.window_border;
+ geom.base_height = 2*inst->cfg.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 |
+ GDK_HINT_RESIZE_INC);
+}
+
+void clear_scrollback_menuitem(GtkMenuItem *item, gpointer data)
+{
+ struct gui_data *inst = (struct gui_data *)data;
+ term_clrsb(inst->term);
+}
+
+void reset_terminal_menuitem(GtkMenuItem *item, gpointer data)
+{
+ struct gui_data *inst = (struct gui_data *)data;
+ term_pwron(inst->term, TRUE);
+ if (inst->ldisc)
+ ldisc_send(inst->ldisc, NULL, 0, 0);
+}
+
+void copy_all_menuitem(GtkMenuItem *item, gpointer data)
+{
+ struct gui_data *inst = (struct gui_data *)data;
+ term_copyall(inst->term);
+}
+
+void special_menuitem(GtkMenuItem *item, gpointer data)
+{
+ struct gui_data *inst = (struct gui_data *)data;
+ int code = GPOINTER_TO_INT(gtk_object_get_data(GTK_OBJECT(item),
+ "user-data"));
+
+ if (inst->back)
+ inst->back->special(inst->backhandle, code);
+}
+
+void about_menuitem(GtkMenuItem *item, gpointer data)
+{
+ struct gui_data *inst = (struct gui_data *)data;
+ about_box(inst->window);
+}
+
+void event_log_menuitem(GtkMenuItem *item, gpointer data)
+{
+ struct gui_data *inst = (struct gui_data *)data;
+ 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. */
+ 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;
+
+ assert(lenof(ww) == NCFGCOLOURS);
+
+ if (inst->reconfiguring)
+ return;
+ else
+ inst->reconfiguring = TRUE;
+
+ cfg2 = inst->cfg; /* structure copy */
+
+ if (do_config_box(title, &cfg2, 1,
+ inst->back?inst->back->cfg_info(inst->backhandle):0)) {
+
+ oldcfg = inst->cfg; /* structure copy */
+ inst->cfg = cfg2; /* structure copy */
+
+ /* Pass new config data to the logging module */
+ log_reconfig(inst->logctx, &cfg2);
+ /*
+ * Flush the line discipline's edit buffer in the case
+ * where local editing has just been disabled.
+ */
+ if (inst->ldisc)
+ ldisc_send(inst->ldisc, NULL, 0, 0);
+ /* Pass new config data to the terminal */
+ term_reconfig(inst->term, &cfg2);
+ /* Pass new config data to the back end */
+ if (inst->back)
+ inst->back->reconfig(inst->backhandle, &cfg2);
+
+ /*
+ * Just setting inst->cfg 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.
+ */
+ 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]);
+
+ /*
+ * If the default background has changed, we must
+ * repaint the space in between the window border
+ * and the text area.
+ */
+ if (i == 258) {
+ set_window_background(inst);
+ draw_backing_rect(inst);
+ }
+ }
+ }
+
+ /*
+ * 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)
+ gtk_widget_show(inst->sbar);
+ else
+ gtk_widget_hide(inst->sbar);
+ }
+ if (oldcfg.scrollbar_on_left != cfg2.scrollbar_on_left) {
+ gtk_box_reorder_child(inst->hbox, inst->sbar,
+ cfg2.scrollbar_on_left ? 0 : 1);
+ }
+
+ /*
+ * Change the window title, if required.
+ */
+ if (strcmp(oldcfg.wintitle, cfg2.wintitle))
+ set_title(inst, cfg2.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;
+
+ /*
+ * Resize the window.
+ */
+ if (oldcfg.width != cfg2.width || oldcfg.height != cfg2.height ||
+ oldcfg.window_border != cfg2.window_border || need_size) {
+ set_geom_hints(inst);
+ request_resize(inst, cfg2.width, cfg2.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)
+ term_size(inst->term, inst->term->rows, inst->term->cols,
+ cfg2.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);
+ }
+ sfree(title);
+ inst->reconfiguring = FALSE;
+}
+
+void fork_and_exec_self(struct gui_data *inst, int fd_to_close, ...)
+{
+ /*
+ * Re-execing ourself is not an exact science under Unix. I do
+ * the best I can by using /proc/self/exe if available and by
+ * assuming argv[0] can be found on $PATH if not.
+ *
+ * Note that we also have to reconstruct the elements of the
+ * original argv which gtk swallowed, since the user wants the
+ * new session to appear on the same X display as the old one.
+ */
+ char **args;
+ va_list ap;
+ int i, n;
+ int pid;
+
+ /*
+ * Collect the arguments with which to re-exec ourself.
+ */
+ va_start(ap, fd_to_close);
+ n = 2; /* progname and terminating NULL */
+ n += inst->ngtkargs;
+ while (va_arg(ap, char *) != NULL)
+ n++;
+ va_end(ap);
+
+ args = snewn(n, char *);
+ args[0] = inst->progname;
+ args[n-1] = NULL;
+ for (i = 0; i < inst->ngtkargs; i++)
+ args[i+1] = inst->gtkargvstart[i];
+
+ i++;
+ va_start(ap, fd_to_close);
+ while ((args[i++] = va_arg(ap, char *)) != NULL);
+ va_end(ap);
+
+ assert(i == n);
+
+ /*
+ * Do the double fork.
+ */
+ pid = fork();
+ if (pid < 0) {
+ perror("fork");
+ return;
+ }
+
+ if (pid == 0) {
+ int pid2 = fork();
+ if (pid2 < 0) {
+ perror("fork");
+ _exit(1);
+ } else if (pid2 > 0) {
+ /*
+ * First child has successfully forked second child. My
+ * Work Here Is Done. Note the use of _exit rather than
+ * exit: the latter appears to cause destroy messages
+ * to be sent to the X server. I suspect gtk uses
+ * atexit.
+ */
+ _exit(0);
+ }
+
+ /*
+ * If we reach here, we are the second child, so we now
+ * actually perform the exec.
+ */
+ if (fd_to_close >= 0)
+ close(fd_to_close);
+
+ execv("/proc/self/exe", args);
+ execvp(inst->progname, args);
+ perror("exec");
+ _exit(127);
+
+ } else {
+ int status;
+ 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
+ * into a byte stream, create a pipe, and send this byte stream
+ * to the child through the pipe.
+ */
+ int i, ret, size;
+ char *data;
+ char option[80];
+ int pipefd[2];
+
+ if (pipe(pipefd) < 0) {
+ perror("pipe");
+ return;
+ }
+
+ size = sizeof(inst->cfg);
+ 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));
+ if (use_pty_argv && pty_argv) {
+ int p = sizeof(inst->cfg);
+ 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);
+ 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)
+ i += ret;
+ if (ret < 0)
+ perror("write to pipe");
+ close(pipefd[1]);
+ sfree(data);
+}
+
+int read_dupsession_data(struct gui_data *inst, Config *cfg, char *arg)
+{
+ int fd, i, ret, size;
+ char *data;
+
+ if (sscanf(arg, "---[%d,%d]", &fd, &size) != 2) {
+ fprintf(stderr, "%s: malformed magic argument `%s'\n", appname, arg);
+ exit(1);
+ }
+
+ data = snewn(size, char);
+ i = ret = 0;
+ while (i < size && (ret = read(fd, data + i, size - i)) > 0)
+ i += ret;
+ if (ret < 0) {
+ perror("read from pipe");
+ exit(1);
+ } else if (i < size) {
+ 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)) {
+ int n = 0;
+ i = sizeof(Config);
+ while (i < size) {
+ while (i < size && data[i]) i++;
+ if (i >= size) {
+ fprintf(stderr, "%s: malformed Duplicate Session data\n",
+ appname);
+ exit(1);
+ }
+ i++;
+ n++;
+ }
+ pty_argv = snewn(n+1, char *);
+ pty_argv[n] = NULL;
+ n = 0;
+ i = sizeof(Config);
+ while (i < size) {
+ char *p = data + i;
+ while (i < size && data[i]) i++;
+ assert(i < size);
+ i++;
+ pty_argv[n++] = dupstr(p);
+ }
+ }
+
+ return 0;
+}
+
+void new_session_menuitem(GtkMenuItem *item, gpointer data)
+{
+ struct gui_data *inst = (struct gui_data *)data;
+
+ fork_and_exec_self(inst, -1, NULL);
+}
+
+void restart_session_menuitem(GtkMenuItem *item, gpointer data)
+{
+ struct gui_data *inst = (struct gui_data *)data;
+
+ if (!inst->back) {
+ logevent(inst, "----- Session restarted -----");
+ term_pwron(inst->term, FALSE);
+ start_backend(inst);
+ inst->exited = FALSE;
+ }
+}
+
+void saved_session_menuitem(GtkMenuItem *item, gpointer data)
+{
+ struct gui_data *inst = (struct gui_data *)data;
+ char *str = (char *)gtk_object_get_data(GTK_OBJECT(item), "user-data");
+
+ fork_and_exec_self(inst, -1, "-load", str, NULL);
+}
+
+void saved_session_freedata(GtkMenuItem *item, gpointer data)
+{
+ char *str = (char *)gtk_object_get_data(GTK_OBJECT(item), "user-data");
+
+ sfree(str);
+}
+
+static void update_savedsess_menu(GtkMenuItem *menuitem, gpointer data)
+{
+ struct gui_data *inst = (struct gui_data *)data;
+ struct sesslist sesslist;
+ int i;
+
+ gtk_container_foreach(GTK_CONTAINER(inst->sessionsmenu),
+ (GtkCallback)gtk_widget_destroy, NULL);
+
+ get_sesslist(&sesslist, TRUE);
+ /* skip sesslist.sessions[0] == Default Settings */
+ for (i = 1; i < sesslist.nsessions; i++) {
+ GtkWidget *menuitem =
+ gtk_menu_item_new_with_label(sesslist.sessions[i]);
+ gtk_container_add(GTK_CONTAINER(inst->sessionsmenu), menuitem);
+ gtk_widget_show(menuitem);
+ gtk_object_set_data(GTK_OBJECT(menuitem), "user-data",
+ dupstr(sesslist.sessions[i]));
+ gtk_signal_connect(GTK_OBJECT(menuitem), "activate",
+ GTK_SIGNAL_FUNC(saved_session_menuitem),
+ inst);
+ gtk_signal_connect(GTK_OBJECT(menuitem), "destroy",
+ GTK_SIGNAL_FUNC(saved_session_freedata),
+ inst);
+ }
+ if (sesslist.nsessions <= 1) {
+ GtkWidget *menuitem =
+ gtk_menu_item_new_with_label("(No sessions)");
+ gtk_widget_set_sensitive(menuitem, FALSE);
+ gtk_container_add(GTK_CONTAINER(inst->sessionsmenu), menuitem);
+ gtk_widget_show(menuitem);
+ }
+ get_sesslist(&sesslist, FALSE); /* free up */
+}
+
+void set_window_icon(GtkWidget *window, const char *const *const *icon,
+ int n_icon)
+{
+ GdkPixmap *iconpm;
+ GdkBitmap *iconmask;
+#if GTK_CHECK_VERSION(2,0,0)
+ GList *iconlist;
+ int n;
+#endif
+
+ if (!n_icon)
+ return;
+
+ gtk_widget_realize(window);
+ iconpm = gdk_pixmap_create_from_xpm_d(window->window, &iconmask,
+ NULL, (gchar **)icon[0]);
+ gdk_window_set_icon(window->window, NULL, iconpm, iconmask);
+
+#if GTK_CHECK_VERSION(2,0,0)
+ iconlist = NULL;
+ for (n = 0; n < n_icon; n++) {
+ iconlist =
+ g_list_append(iconlist,
+ gdk_pixbuf_new_from_xpm_data((const gchar **)
+ icon[n]));
+ }
+ gdk_window_set_icon_list(window->window, iconlist);
+#endif
+}
+
+void update_specials_menu(void *frontend)
+{
+ struct gui_data *inst = (struct gui_data *)frontend;
+
+ const struct telnet_special *specials;
+
+ if (inst->back)
+ specials = inst->back->get_specials(inst->backhandle);
+ else
+ specials = NULL;
+
+ /* I believe this disposes of submenus too. */
+ gtk_container_foreach(GTK_CONTAINER(inst->specialsmenu),
+ (GtkCallback)gtk_widget_destroy, NULL);
+ if (specials) {
+ int i;
+ GtkWidget *menu = inst->specialsmenu;
+ /* A lame "stack" for submenus that will do for now. */
+ GtkWidget *saved_menu = NULL;
+ int nesting = 1;
+ for (i = 0; nesting > 0; i++) {
+ GtkWidget *menuitem = NULL;
+ switch (specials[i].code) {
+ case TS_SUBMENU:
+ assert (nesting < 2);
+ saved_menu = menu; /* XXX lame stacking */
+ menu = gtk_menu_new();
+ menuitem = gtk_menu_item_new_with_label(specials[i].name);
+ gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), menu);
+ gtk_container_add(GTK_CONTAINER(saved_menu), menuitem);
+ gtk_widget_show(menuitem);
+ menuitem = NULL;
+ nesting++;
+ break;
+ case TS_EXITMENU:
+ nesting--;
+ if (nesting) {
+ menu = saved_menu; /* XXX lame stacking */
+ saved_menu = NULL;
+ }
+ break;
+ case TS_SEP:
+ menuitem = gtk_menu_item_new();
+ break;
+ default:
+ menuitem = gtk_menu_item_new_with_label(specials[i].name);
+ gtk_object_set_data(GTK_OBJECT(menuitem), "user-data",
+ GINT_TO_POINTER(specials[i].code));
+ gtk_signal_connect(GTK_OBJECT(menuitem), "activate",
+ GTK_SIGNAL_FUNC(special_menuitem), inst);
+ break;
+ }
+ if (menuitem) {
+ gtk_container_add(GTK_CONTAINER(menu), menuitem);
+ gtk_widget_show(menuitem);
+ }
+ }
+ gtk_widget_show(inst->specialsitem1);
+ gtk_widget_show(inst->specialsitem2);
+ } else {
+ gtk_widget_hide(inst->specialsitem1);
+ gtk_widget_hide(inst->specialsitem2);
+ }
+}
+
+static void start_backend(struct gui_data *inst)
+{
+ extern Backend *select_backend(Config *cfg);
+ char *realhost;
+ const char *error;
+
+ inst->back = select_backend(&inst->cfg);
+
+ 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);
+
+ if (error) {
+ char *msg = dupprintf("Unable to open connection to %s:\n%s",
+ inst->cfg.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);
+ } else {
+ char *title = make_default_wintitle(realhost);
+ set_title(inst, title);
+ set_icon(inst, 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,
+ inst);
+
+ gtk_widget_set_sensitive(inst->restartitem, FALSE);
+}
+
+int pt_main(int argc, char **argv)
+{
+ extern int cfgbox(Config *cfg);
+ struct gui_data *inst;
+
+ /*
+ * 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;
+
+ /* defer any child exit handling until we're ready to deal with
+ * it */
+ block_signal(SIGCHLD, 1);
+
+ inst->progname = argv[0];
+ /*
+ * Copy the original argv before letting gtk_init fiddle with
+ * it. It will be required later.
+ */
+ {
+ int i, oldargc;
+ inst->gtkargvstart = snewn(argc-1, char *);
+ for (i = 1; i < argc; i++)
+ inst->gtkargvstart[i-1] = dupstr(argv[i]);
+ oldargc = argc;
+ gtk_init(&argc, &argv);
+ inst->ngtkargs = oldargc - argc;
+ }
+
+ if (argc > 1 && !strncmp(argv[1], "---", 3)) {
+ read_dupsession_data(inst, &inst->cfg, argv[1]);
+ /* Splatter this argument so it doesn't clutter a ps listing */
+ memset(argv[1], 0, 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))
+ exit(1); /* pre-defaults pass to get -class */
+ do_defaults(NULL, &inst->cfg);
+ if (do_cmdline(argc, argv, 1, &allow_launch, inst, &inst->cfg))
+ exit(1); /* post-defaults, do everything */
+
+ cmdline_run_saved(&inst->cfg);
+
+ if (loaded_session)
+ allow_launch = TRUE;
+
+ if ((!allow_launch || !cfg_launchable(&inst->cfg)) &&
+ !cfgbox(&inst->cfg))
+ exit(0); /* config box hit Cancel */
+ }
+
+ if (!compound_text_atom)
+ compound_text_atom = gdk_atom_intern("COMPOUND_TEXT", FALSE);
+ if (!utf8_string_atom)
+ utf8_string_atom = gdk_atom_intern("UTF8_STRING", FALSE);
+
+ inst->area = gtk_drawing_area_new();
+
+ setup_fonts_ucs(inst);
+ 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);
+
+ /*
+ * Set up the colour map.
+ */
+ palette_reset(inst);
+
+ inst->width = inst->cfg.width;
+ inst->height = inst->cfg.height;
+
+ 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->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)
+ 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)
+ 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)
+ gtk_widget_show(inst->sbar);
+ else
+ gtk_widget_hide(inst->sbar);
+ gtk_widget_show(GTK_WIDGET(inst->hbox));
+
+ if (inst->gotpos) {
+ int x = inst->xpos, y = inst->ypos;
+ GtkRequisition req;
+ gtk_widget_size_request(GTK_WIDGET(inst->window), &req);
+ if (inst->gravity & 1) x += gdk_screen_width() - req.width;
+ if (inst->gravity & 2) y += gdk_screen_height() - req.height;
+ gtk_window_set_position(GTK_WINDOW(inst->window), GTK_WIN_POS_NONE);
+ gtk_widget_set_uposition(GTK_WIDGET(inst->window), x, y);
+ }
+
+ gtk_signal_connect(GTK_OBJECT(inst->window), "destroy",
+ GTK_SIGNAL_FUNC(destroy), inst);
+ gtk_signal_connect(GTK_OBJECT(inst->window), "delete_event",
+ GTK_SIGNAL_FUNC(delete_window), inst);
+ gtk_signal_connect(GTK_OBJECT(inst->window), "key_press_event",
+ GTK_SIGNAL_FUNC(key_event), inst);
+ gtk_signal_connect(GTK_OBJECT(inst->window), "key_release_event",
+ GTK_SIGNAL_FUNC(key_event), inst);
+ gtk_signal_connect(GTK_OBJECT(inst->window), "focus_in_event",
+ GTK_SIGNAL_FUNC(focus_event), inst);
+ gtk_signal_connect(GTK_OBJECT(inst->window), "focus_out_event",
+ GTK_SIGNAL_FUNC(focus_event), inst);
+ gtk_signal_connect(GTK_OBJECT(inst->area), "configure_event",
+ GTK_SIGNAL_FUNC(configure_area), inst);
+ gtk_signal_connect(GTK_OBJECT(inst->area), "expose_event",
+ GTK_SIGNAL_FUNC(expose_area), inst);
+ gtk_signal_connect(GTK_OBJECT(inst->area), "button_press_event",
+ GTK_SIGNAL_FUNC(button_event), inst);
+ gtk_signal_connect(GTK_OBJECT(inst->area), "button_release_event",
+ GTK_SIGNAL_FUNC(button_event), inst);
+#if GTK_CHECK_VERSION(2,0,0)
+ gtk_signal_connect(GTK_OBJECT(inst->area), "scroll_event",
+ GTK_SIGNAL_FUNC(scroll_event), inst);
+#endif
+ gtk_signal_connect(GTK_OBJECT(inst->area), "motion_notify_event",
+ GTK_SIGNAL_FUNC(motion_event), inst);
+ gtk_signal_connect(GTK_OBJECT(inst->area), "selection_received",
+ 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)
+ 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 |
+ GDK_POINTER_MOTION_MASK | GDK_BUTTON_MOTION_MASK);
+
+ {
+ extern const char *const *const main_icon[];
+ extern const int n_main_icon;
+ set_window_icon(inst->window, main_icon, n_main_icon);
+ }
+
+ gtk_widget_show(inst->window);
+
+ set_window_background(inst);
+
+ /*
+ * Set up the Ctrl+rightclick context menu.
+ */
+ {
+ GtkWidget *menuitem;
+ char *s;
+ extern const int use_event_log, new_session, saved_sessions;
+
+ inst->menu = gtk_menu_new();
+
+#define MKMENUITEM(title, func) do \
+ { \
+ menuitem = gtk_menu_item_new_with_label(title); \
+ gtk_container_add(GTK_CONTAINER(inst->menu), menuitem); \
+ gtk_widget_show(menuitem); \
+ gtk_signal_connect(GTK_OBJECT(menuitem), "activate", \
+ GTK_SIGNAL_FUNC(func), inst); \
+ } while (0)
+
+#define MKSUBMENU(title) do \
+ { \
+ menuitem = gtk_menu_item_new_with_label(title); \
+ gtk_container_add(GTK_CONTAINER(inst->menu), menuitem); \
+ gtk_widget_show(menuitem); \
+ } while (0)
+
+#define MKSEP() do \
+ { \
+ menuitem = gtk_menu_item_new(); \
+ gtk_container_add(GTK_CONTAINER(inst->menu), menuitem); \
+ gtk_widget_show(menuitem); \
+ } while (0)
+
+ if (new_session)
+ MKMENUITEM("New Session...", new_session_menuitem);
+ MKMENUITEM("Restart Session", restart_session_menuitem);
+ inst->restartitem = menuitem;
+ gtk_widget_set_sensitive(inst->restartitem, FALSE);
+ MKMENUITEM("Duplicate Session", dup_session_menuitem);
+ if (saved_sessions) {
+ inst->sessionsmenu = gtk_menu_new();
+ /* sessionsmenu will be updated when it's invoked */
+ /* XXX is this the right way to do dynamic menus in Gtk? */
+ MKMENUITEM("Saved Sessions", update_savedsess_menu);
+ gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem),
+ inst->sessionsmenu);
+ }
+ MKSEP();
+ MKMENUITEM("Change Settings...", change_settings_menuitem);
+ MKSEP();
+ if (use_event_log)
+ MKMENUITEM("Event Log", event_log_menuitem);
+ MKSUBMENU("Special Commands");
+ inst->specialsmenu = gtk_menu_new();
+ gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), inst->specialsmenu);
+ inst->specialsitem1 = menuitem;
+ MKSEP();
+ inst->specialsitem2 = menuitem;
+ gtk_widget_hide(inst->specialsitem1);
+ gtk_widget_hide(inst->specialsitem2);
+ MKMENUITEM("Clear Scrollback", clear_scrollback_menuitem);
+ MKMENUITEM("Reset Terminal", reset_terminal_menuitem);
+ MKMENUITEM("Copy All", copy_all_menuitem);
+ MKSEP();
+ s = dupcat("About ", appname, NULL);
+ MKMENUITEM(s, about_menuitem);
+ sfree(s);
+#undef MKMENUITEM
+#undef MKSUBMENU
+#undef MKSEP
+ }
+
+ inst->textcursor = make_mouse_ptr(inst, GDK_XTERM);
+ inst->rawcursor = make_mouse_ptr(inst, GDK_LEFT_PTR);
+ inst->waitcursor = make_mouse_ptr(inst, GDK_WATCH);
+ inst->blankcursor = make_mouse_ptr(inst, -1);
+ make_mouse_ptr(inst, -2); /* clean up cursor font */
+ 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);
+ term_provide_logctx(inst->term, inst->logctx);
+
+ uxsel_init();
+
+ term_size(inst->term, inst->cfg.height, inst->cfg.width, inst->cfg.savelines);
+
+ start_backend(inst);
+
+ ldisc_send(inst->ldisc, NULL, 0, 0);/* cause ldisc to notice changes */
+
+ /* now we're reday to deal with the child exit handler being
+ * called */
+ block_signal(SIGCHLD, 0);
+
+ /*
+ * Block SIGPIPE: if we attempt Duplicate Session or similar
+ * and it falls over in some way, we certainly don't want
+ * SIGPIPE terminating the main pterm/PuTTY. Note that we do
+ * this _after_ (at least pterm) forks off its child process,
+ * since the child wants SIGPIPE handled in the usual way.
+ */
+ block_signal(SIGPIPE, 1);
+
+ inst->exited = FALSE;
+
+ gtk_main();
+
+ return 0;
+}
--- /dev/null
+#ifndef PUTTY_UNIX_H
+#define PUTTY_UNIX_H
+
+#ifdef HAVE_CONFIG_H
+# include "uxconfig.h" /* Space to hide it from mkfiles.pl */
+#endif
+
+#include <stdio.h> /* for FILENAME_MAX */
+#include <stdint.h> /* C99 int types */
+#ifndef NO_LIBDL
+#include <dlfcn.h> /* Dynamic library loading */
+#endif /* NO_LIBDL */
+#include "charset.h"
+
+struct Filename {
+ char path[FILENAME_MAX];
+};
+FILE *f_open(struct Filename, char const *, int);
+
+struct FontSpec {
+ char name[256];
+};
+
+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
+
+/*
+ * Under GTK, we send MA_CLICK _and_ MA_2CLK, or MA_CLICK _and_
+ * MA_3CLK, when a button is pressed for the second or third time.
+ */
+#define MULTICLICK_ONLY_EVENT 0
+
+/*
+ * Under GTK, there is no context help available.
+ */
+#define HELPCTX(x) P(NULL)
+#define FILTER_KEY_FILES NULL /* FIXME */
+#define FILTER_DYNLIB_FILES NULL /* FIXME */
+
+/*
+ * Under X, selection data must not be NUL-terminated.
+ */
+#define SELECTION_NUL_TERMINATED 0
+
+/*
+ * Under X, copying to the clipboard terminates lines with just LF.
+ */
+#define SEL_NL { 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
+
+/*
+ * Unix-specific global flag
+ *
+ * FLAG_STDERR_TTY indicates that standard error might be a terminal and
+ * might get its configuration munged, so anything trying to output plain
+ * text (i.e. with newlines in it) will need to put it back into cooked
+ * mode first. Applications setting this flag should also call
+ * stderr_tty_init() before messing with any terminal modes, and can call
+ * premsg() before outputting text to stderr and postmsg() afterwards.
+ */
+#define FLAG_STDERR_TTY 0x1000
+
+/* Things pty.c needs from pterm.c */
+char *get_x_display(void *frontend);
+int font_dimension(void *frontend, int which);/* 0 for width, 1 for height */
+long get_windowid(void *frontend);
+
+/* 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 midsession, int protcfginfo);
+void fatal_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);
+
+/* 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);
+
+/* pterm.c needs this special function in xkeysym.c */
+int keysym_to_unicode(int keysym);
+
+/* Things uxstore.c needs from pterm.c */
+char *x_get_default(const char *key);
+
+/* Things uxstore.c provides to pterm.c */
+void provide_xrm_string(char *string);
+
+/* Things provided by uxcons.c */
+struct termios;
+void stderr_tty_init(void);
+void premsg(struct termios *);
+void postmsg(struct termios *);
+
+/* The interface used by uxsel.c */
+void uxsel_init(void);
+typedef int (*uxsel_callback_fn)(int fd, int event);
+void uxsel_set(int fd, int rwx, uxsel_callback_fn callback);
+void uxsel_del(int fd);
+int select_result(int fd, int event);
+int first_fd(int *state, int *rwx);
+int next_fd(int *state, int *rwx);
+/* The following are expected to be provided _to_ uxsel.c by the frontend */
+int uxsel_input_add(int fd, int rwx); /* returns an id */
+void uxsel_input_remove(int id);
+
+/* uxcfg.c */
+struct controlbox;
+void unix_setup_config_box(struct controlbox *b, int midsession, int protocol);
+
+/* gtkcfg.c */
+void gtk_setup_config_box(struct controlbox *b, int midsession, void *window);
+
+/*
+ * In the Unix Unicode layer, DEFAULT_CODEPAGE is a special value
+ * which causes mb_to_wc and wc_to_mb to call _libc_ rather than
+ * libcharset. That way, we can interface the various charsets
+ * supported by libcharset with the one supported by mbstowcs and
+ * wcstombs (which will be the character set in which stuff read
+ * from the command line or config files is assumed to be encoded).
+ */
+#define DEFAULT_CODEPAGE 0xFFFF
+#define CP_UTF8 CS_UTF8 /* from libcharset */
+
+#define strnicmp strncasecmp
+#define stricmp strcasecmp
+
+/* 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);
+
+/*
+ * Exports from unicode.c.
+ */
+struct unicode_data;
+int init_ucs(struct unicode_data *ucsdata, char *line_codepage,
+ int utf8_override, int font_charset, int vtmode);
+
+/*
+ * Spare function exported directly from uxnet.c.
+ */
+void *sk_getxdmdata(void *sock, int *lenp);
+
+/*
+ * General helpful Unix stuff: more helpful version of the FD_SET
+ * macro, which also handles maxfd.
+ */
+#define FD_SET_MAX(fd, max, set) do { \
+ FD_SET(fd, &set); \
+ if (max < fd + 1) max = fd + 1; \
+} while (0)
+
+/*
+ * Exports from winser.c.
+ */
+extern Backend serial_backend;
+
+#endif
--- /dev/null
+/*
+ * ux_x11.c: fetch local auth data for X forwarding.
+ */
+
+#include <ctype.h>
+#include <unistd.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#include "putty.h"
+#include "ssh.h"
+#include "network.h"
+
+void platform_get_x11_auth(struct X11Display *disp, const Config *cfg)
+{
+ char *xauthfile;
+ int needs_free;
+
+ /*
+ * Find the .Xauthority file.
+ */
+ needs_free = FALSE;
+ xauthfile = getenv("XAUTHORITY");
+ if (!xauthfile) {
+ xauthfile = getenv("HOME");
+ if (xauthfile) {
+ xauthfile = dupcat(xauthfile, "/.Xauthority", NULL);
+ needs_free = TRUE;
+ }
+ }
+
+ if (xauthfile) {
+ x11_get_auth_from_authfile(disp, xauthfile);
+ if (needs_free)
+ sfree(xauthfile);
+ }
+}
+
+const int platform_uses_x11_unix_by_default = TRUE;
--- /dev/null
+/*
+ * SSH agent client code.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <fcntl.h>
+
+#include "putty.h"
+#include "misc.h"
+#include "tree234.h"
+#include "puttymem.h"
+
+int agent_exists(void)
+{
+ if (getenv("SSH_AUTH_SOCK") != NULL)
+ return TRUE;
+ return FALSE;
+}
+
+static tree234 *agent_connections;
+struct agent_connection {
+ int fd;
+ char *retbuf;
+ char sizebuf[4];
+ int retsize, retlen;
+ void (*callback)(void *, void *, int);
+ void *callback_ctx;
+};
+static int agent_conncmp(void *av, void *bv)
+{
+ struct agent_connection *a = (struct agent_connection *) av;
+ struct agent_connection *b = (struct agent_connection *) bv;
+ if (a->fd < b->fd)
+ return -1;
+ if (a->fd > b->fd)
+ return +1;
+ return 0;
+}
+static int agent_connfind(void *av, void *bv)
+{
+ int afd = *(int *) av;
+ struct agent_connection *b = (struct agent_connection *) bv;
+ if (afd < b->fd)
+ return -1;
+ if (afd > b->fd)
+ return +1;
+ return 0;
+}
+
+static int agent_select_result(int fd, int event)
+{
+ int ret;
+ struct agent_connection *conn;
+
+ assert(event == 1); /* not selecting for anything but R */
+
+ conn = find234(agent_connections, &fd, agent_connfind);
+ if (!conn) {
+ uxsel_del(fd);
+ return 1;
+ }
+
+ ret = read(fd, conn->retbuf+conn->retlen, conn->retsize-conn->retlen);
+ if (ret <= 0) {
+ if (conn->retbuf != conn->sizebuf) sfree(conn->retbuf);
+ conn->retbuf = NULL;
+ conn->retlen = 0;
+ goto done;
+ }
+ conn->retlen += ret;
+ if (conn->retsize == 4 && conn->retlen == 4) {
+ conn->retsize = GET_32BIT(conn->retbuf);
+ 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);
+ }
+
+ if (conn->retlen < conn->retsize)
+ return 0; /* more data to come */
+
+ done:
+ /*
+ * We have now completed the agent query. Do the callback, and
+ * clean up. (Of course we don't free retbuf, since ownership
+ * of that passes to the callback.)
+ */
+ conn->callback(conn->callback_ctx, conn->retbuf, conn->retlen);
+ uxsel_del(fd);
+ close(fd);
+ del234(agent_connections, conn);
+ sfree(conn);
+ return 0;
+}
+
+int agent_query(void *in, int inlen, void **out, int *outlen,
+ void (*callback)(void *, void *, int), void *callback_ctx)
+{
+ char *name;
+ int sock;
+ struct sockaddr_un addr;
+ int done;
+ struct agent_connection *conn;
+
+ name = getenv("SSH_AUTH_SOCK");
+ if (!name)
+ goto failure;
+
+ sock = socket(PF_UNIX, SOCK_STREAM, 0);
+ if (sock < 0) {
+ perror("socket(PF_UNIX)");
+ exit(1);
+ }
+
+ cloexec(sock);
+
+ addr.sun_family = AF_UNIX;
+ strncpy(addr.sun_path, name, sizeof(addr.sun_path));
+ if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
+ close(sock);
+ goto failure;
+ }
+
+ for (done = 0; done < inlen ;) {
+ int ret = write(sock, (char *)in + done, inlen - done);
+ if (ret <= 0) {
+ close(sock);
+ goto failure;
+ }
+ done += ret;
+ }
+
+ if (!agent_connections)
+ agent_connections = newtree234(agent_conncmp);
+
+ conn = snew(struct agent_connection);
+ conn->fd = sock;
+ conn->retbuf = conn->sizebuf;
+ conn->retsize = 4;
+ conn->retlen = 0;
+ conn->callback = callback;
+ conn->callback_ctx = callback_ctx;
+ add234(agent_connections, conn);
+
+ uxsel_set(sock, 1, agent_select_result);
+ return 0;
+
+ failure:
+ *out = NULL;
+ *outlen = 0;
+ return 1;
+}
--- /dev/null
+/*
+ * uxcfg.c - the Unix-specific parts of the PuTTY configuration
+ * box.
+ */
+
+#include <assert.h>
+#include <stdlib.h>
+
+#include "putty.h"
+#include "dialog.h"
+#include "storage.h"
+
+void unix_setup_config_box(struct controlbox *b, int midsession, int protocol)
+{
+ 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.
+ */
+
+ /*
+ * On Unix, we don't have a drop-down list for the printer
+ * control.
+ */
+ s = ctrl_getset(b, "Terminal", "printing", "Remote-controlled printing");
+ assert(s->ncontrols == 1 && s->ctrls[0]->generic.type == CTRL_EDITBOX);
+ s->ctrls[0]->editbox.has_list = 0;
+
+ /*
+ * Unix supports a local-command proxy. This also means we must
+ * adjust the text on the `Telnet command' control.
+ */
+ if (!midsession) {
+ 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->radio.nbuttons++;
+ c->radio.buttons =
+ sresize(c->radio.buttons, c->radio.nbuttons, char *);
+ c->radio.buttons[c->radio.nbuttons-1] =
+ dupstr("Local");
+ c->radio.buttondata =
+ sresize(c->radio.buttondata, c->radio.nbuttons, intorptr);
+ c->radio.buttondata[c->radio.nbuttons-1] = I(PROXY_CMD);
+ break;
+ }
+ }
+
+ 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);
+ sfree(c->generic.label);
+ c->generic.label = dupstr("Telnet command, or local"
+ " proxy command");
+ break;
+ }
+ }
+ }
+
+ /*
+ * Serial back end is available on Unix. However, we have to
+ * mask out a couple of the configuration options: mark and
+ * space parity are not conveniently supported, and neither is
+ * DSR/DTR flow control.
+ */
+ if (!midsession || (protocol == PROT_SERIAL))
+ ser_setup_config_box(b, midsession, 0x07, 0x07);
+}
--- /dev/null
+/*
+ * uxcons.c: various interactive-prompt routines shared between the
+ * Unix console PuTTY tools
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <assert.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include "putty.h"
+#include "storage.h"
+#include "ssh.h"
+
+int console_batch_mode = FALSE;
+
+static void *console_logctx = NULL;
+
+static struct termios orig_termios_stderr;
+static int stderr_is_a_tty;
+
+void stderr_tty_init()
+{
+ /* Ensure that if stderr is a tty, we can get it back to a sane state. */
+ if ((flags & FLAG_STDERR_TTY) && isatty(STDERR_FILENO)) {
+ stderr_is_a_tty = TRUE;
+ tcgetattr(STDERR_FILENO, &orig_termios_stderr);
+ }
+}
+
+void premsg(struct termios *cf)
+{
+ if (stderr_is_a_tty) {
+ tcgetattr(STDERR_FILENO, cf);
+ tcsetattr(STDERR_FILENO, TCSADRAIN, &orig_termios_stderr);
+ }
+}
+void postmsg(struct termios *cf)
+{
+ if (stderr_is_a_tty)
+ tcsetattr(STDERR_FILENO, TCSADRAIN, cf);
+}
+
+/*
+ * Clean up and exit.
+ */
+void cleanup_exit(int code)
+{
+ /*
+ * Clean up.
+ */
+ sk_cleanup();
+ random_save_seed();
+ exit(code);
+}
+
+void set_busy_status(void *frontend, int status)
+{
+}
+
+void update_specials_menu(void *frontend)
+{
+}
+
+void notify_remote_exit(void *frontend)
+{
+}
+
+void timer_change_notify(long next)
+{
+}
+
+int verify_ssh_host_key(void *frontend, char *host, int port, char *keytype,
+ char *keystr, char *fingerprint,
+ void (*callback)(void *ctx, int result), void *ctx)
+{
+ int ret;
+
+ static const char absentmsg_batch[] =
+ "The server's host key is not cached. You have no guarantee\n"
+ "that the server is the computer you think it is.\n"
+ "The server's %s key fingerprint is:\n"
+ "%s\n"
+ "Connection abandoned.\n";
+ static const char absentmsg[] =
+ "The server's host key is not cached. You have no guarantee\n"
+ "that the server is the computer you think it is.\n"
+ "The server's %s key fingerprint is:\n"
+ "%s\n"
+ "If you trust this host, enter \"y\" to add the key to\n"
+ "PuTTY's cache and carry on connecting.\n"
+ "If you want to carry on connecting just once, without\n"
+ "adding the key to the cache, enter \"n\".\n"
+ "If you do not trust this host, press Return to abandon the\n"
+ "connection.\n"
+ "Store key in cache? (y/n) ";
+
+ static const char wrongmsg_batch[] =
+ "WARNING - POTENTIAL SECURITY BREACH!\n"
+ "The server's host key does not match the one PuTTY has\n"
+ "cached. This means that either the server administrator\n"
+ "has changed the host key, or you have actually connected\n"
+ "to another computer pretending to be the server.\n"
+ "The new %s key fingerprint is:\n"
+ "%s\n"
+ "Connection abandoned.\n";
+ static const char wrongmsg[] =
+ "WARNING - POTENTIAL SECURITY BREACH!\n"
+ "The server's host key does not match the one PuTTY has\n"
+ "cached. This means that either the server administrator\n"
+ "has changed the host key, or you have actually connected\n"
+ "to another computer pretending to be the server.\n"
+ "The new %s key fingerprint is:\n"
+ "%s\n"
+ "If you were expecting this change and trust the new key,\n"
+ "enter \"y\" to update PuTTY's cache and continue connecting.\n"
+ "If you want to carry on connecting but without updating\n"
+ "the cache, enter \"n\".\n"
+ "If you want to abandon the connection completely, press\n"
+ "Return to cancel. Pressing Return is the ONLY guaranteed\n"
+ "safe choice.\n"
+ "Update cached key? (y/n, Return cancels connection) ";
+
+ static const char abandoned[] = "Connection abandoned.\n";
+
+ char line[32];
+ struct termios cf;
+
+ /*
+ * Verify the key.
+ */
+ ret = verify_host_key(host, port, keytype, keystr);
+
+ if (ret == 0) /* success - key matched OK */
+ return 1;
+
+ premsg(&cf);
+ if (ret == 2) { /* key was different */
+ if (console_batch_mode) {
+ fprintf(stderr, wrongmsg_batch, keytype, fingerprint);
+ return 0;
+ }
+ fprintf(stderr, wrongmsg, keytype, fingerprint);
+ fflush(stderr);
+ }
+ if (ret == 1) { /* key was absent */
+ if (console_batch_mode) {
+ fprintf(stderr, absentmsg_batch, keytype, fingerprint);
+ return 0;
+ }
+ fprintf(stderr, absentmsg, keytype, fingerprint);
+ fflush(stderr);
+ }
+
+ {
+ struct termios oldmode, newmode;
+ tcgetattr(0, &oldmode);
+ newmode = oldmode;
+ newmode.c_lflag |= ECHO | ISIG | ICANON;
+ tcsetattr(0, TCSANOW, &newmode);
+ line[0] = '\0';
+ if (read(0, line, sizeof(line) - 1) <= 0)
+ /* handled below */;
+ tcsetattr(0, TCSANOW, &oldmode);
+ }
+
+ if (line[0] != '\0' && line[0] != '\r' && line[0] != '\n') {
+ if (line[0] == 'y' || line[0] == 'Y')
+ store_host_key(host, port, keytype, keystr);
+ postmsg(&cf);
+ return 1;
+ } else {
+ fprintf(stderr, abandoned);
+ postmsg(&cf);
+ return 0;
+ }
+}
+
+/*
+ * Ask whether the selected algorithm is acceptable (since it was
+ * below the configured 'warn' threshold).
+ */
+int askalg(void *frontend, const char *algtype, const char *algname,
+ void (*callback)(void *ctx, int result), void *ctx)
+{
+ static const char msg[] =
+ "The first %s supported by the server is\n"
+ "%s, which is below the configured warning threshold.\n"
+ "Continue with connection? (y/n) ";
+ static const char msg_batch[] =
+ "The first %s supported by the server is\n"
+ "%s, which is below the configured warning threshold.\n"
+ "Connection abandoned.\n";
+ static const char abandoned[] = "Connection abandoned.\n";
+
+ char line[32];
+ struct termios cf;
+
+ premsg(&cf);
+ if (console_batch_mode) {
+ fprintf(stderr, msg_batch, algtype, algname);
+ return 0;
+ }
+
+ fprintf(stderr, msg, algtype, algname);
+ fflush(stderr);
+
+ {
+ struct termios oldmode, newmode;
+ tcgetattr(0, &oldmode);
+ newmode = oldmode;
+ newmode.c_lflag |= ECHO | ISIG | ICANON;
+ tcsetattr(0, TCSANOW, &newmode);
+ line[0] = '\0';
+ if (read(0, line, sizeof(line) - 1) <= 0)
+ /* handled below */;
+ tcsetattr(0, TCSANOW, &oldmode);
+ }
+
+ if (line[0] == 'y' || line[0] == 'Y') {
+ postmsg(&cf);
+ return 1;
+ } else {
+ fprintf(stderr, abandoned);
+ postmsg(&cf);
+ return 0;
+ }
+}
+
+/*
+ * 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,
+ 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"
+ "append your session log to the end of it,\n"
+ "or disable session logging for this session.\n"
+ "Enter \"y\" to wipe the file, \"n\" to append to it,\n"
+ "or just press Return to disable logging.\n"
+ "Wipe the log file? (y/n, Return cancels logging) ";
+
+ static const char msgtemplate_batch[] =
+ "The session log file \"%.*s\" already exists.\n"
+ "Logging will not be enabled.\n";
+
+ char line[32];
+ struct termios cf;
+
+ premsg(&cf);
+ if (console_batch_mode) {
+ fprintf(stderr, msgtemplate_batch, FILENAME_MAX, filename.path);
+ fflush(stderr);
+ return 0;
+ }
+ fprintf(stderr, msgtemplate, FILENAME_MAX, filename.path);
+ fflush(stderr);
+
+ {
+ struct termios oldmode, newmode;
+ tcgetattr(0, &oldmode);
+ newmode = oldmode;
+ newmode.c_lflag |= ECHO | ISIG | ICANON;
+ tcsetattr(0, TCSANOW, &newmode);
+ line[0] = '\0';
+ if (read(0, line, sizeof(line) - 1) <= 0)
+ /* handled below */;
+ tcsetattr(0, TCSANOW, &oldmode);
+ }
+
+ postmsg(&cf);
+ if (line[0] == 'y' || line[0] == 'Y')
+ return 2;
+ else if (line[0] == 'n' || line[0] == 'N')
+ return 1;
+ else
+ return 0;
+}
+
+/*
+ * Warn about the obsolescent key file format.
+ *
+ * Uniquely among these functions, this one does _not_ expect a
+ * frontend handle. This means that if PuTTY is ported to a
+ * platform which requires frontend handles, this function will be
+ * an anomaly. Fortunately, the problem it addresses will not have
+ * been present on that platform, so it can plausibly be
+ * implemented as an empty function.
+ */
+void old_keyfile_warning(void)
+{
+ static const char message[] =
+ "You are loading an SSH-2 private key which has an\n"
+ "old version of the file format. This means your key\n"
+ "file is not fully tamperproof. Future versions of\n"
+ "PuTTY may stop supporting this private key format,\n"
+ "so we recommend you convert your key to the new\n"
+ "format.\n"
+ "\n"
+ "Once the key is loaded into PuTTYgen, you can perform\n"
+ "this conversion simply by saving it again.\n";
+
+ struct termios cf;
+ premsg(&cf);
+ fputs(message, stderr);
+ postmsg(&cf);
+}
+
+void console_provide_logctx(void *logctx)
+{
+ console_logctx = logctx;
+}
+
+void logevent(void *frontend, const char *string)
+{
+ struct termios cf;
+ premsg(&cf);
+ if (console_logctx)
+ log_eventlog(console_logctx, string);
+ 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
+ * 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)
+{
+ 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);
+}
+
+int console_get_userpass_input(prompts_t *p, unsigned char *in, int inlen)
+{
+ size_t curr_prompt;
+ FILE *confp = NULL;
+
+ /*
+ * 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);
+ }
+
+ if (p->n_prompts && console_batch_mode)
+ return 0;
+
+ /*
+ * 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);
+ if (p->name[l-1] != '\n')
+ console_prompt_text(&confp, "\n", 1);
+ }
+ /* ...but we always print any `instruction'. */
+ if (p->instruction) {
+ size_t l = strlen(p->instruction);
+ console_prompt_text(&confp, p->instruction, l);
+ if (p->instruction[l-1] != '\n')
+ console_prompt_text(&confp, "\n", 1);
+ }
+
+ for (curr_prompt = 0; curr_prompt < p->n_prompts; curr_prompt++) {
+
+ struct termios oldmode, newmode;
+ int i;
+ prompt_t *pr = p->prompts[curr_prompt];
+
+ tcgetattr(0, &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';
+
+ if (!pr->echo)
+ console_prompt_text(&confp, "\n", 1);
+
+ }
+
+ if (confp && confp != stderr)
+ fclose(confp);
+
+ return 1; /* success */
+}
+
+void frontend_keypress(void *handle)
+{
+ /*
+ * This is nothing but a stub, in console code.
+ */
+ return;
+}
+
+int is_interactive(void)
+{
+ return isatty(0);
+}
+
+/*
+ * X11-forwarding-related things suitable for console.
+ */
+
+char *platform_get_x_display(void) {
+ return dupstr(getenv("DISPLAY"));
+}
--- /dev/null
+/*
+ * uxgen.c: Unix implementation of get_heavy_noise() from cmdgen.c.
+ */
+
+#include <stdio.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "putty.h"
+
+char *get_random_data(int len)
+{
+ char *buf = snewn(len, char);
+ int fd;
+ int ngot, ret;
+
+ fd = open("/dev/random", O_RDONLY);
+ if (fd < 0) {
+ sfree(buf);
+ perror("puttygen: unable to open /dev/random");
+ return NULL;
+ }
+
+ ngot = 0;
+ while (ngot < len) {
+ ret = read(fd, buf+ngot, len-ngot);
+ if (ret < 0) {
+ close(fd);
+ perror("puttygen: unable to read /dev/random");
+ return NULL;
+ }
+ ngot += ret;
+ }
+
+ close(fd);
+
+ return buf;
+}
--- /dev/null
+#include "putty.h"
+#ifndef NO_GSSAPI
+#include "pgssapi.h"
+#include "sshgss.h"
+#include "sshgssc.h"
+
+/* Unix code to set up the GSSAPI library list. */
+
+#if !defined NO_LIBDL && !defined NO_GSSAPI
+
+const int ngsslibs = 4;
+const char *const gsslibnames[4] = {
+ "libgssapi (Heimdal)",
+ "libgssapi_krb5 (MIT Kerberos)",
+ "libgss (Sun)",
+ "User-specified GSSAPI library",
+};
+const struct keyvalwhere gsslibkeywords[] = {
+ { "libgssapi", 0, -1, -1 },
+ { "libgssapi_krb5", 1, -1, -1 },
+ { "libgss", 2, -1, -1 },
+ { "custom", 3, -1, -1 },
+};
+
+/*
+ * Run-time binding against a choice of GSSAPI implementations. We
+ * try loading several libraries, and produce an entry in
+ * ssh_gss_libraries[] for each one.
+ */
+
+static void gss_init(struct ssh_gss_library *lib, void *dlhandle,
+ int id, const char *msg)
+{
+ lib->id = id;
+ lib->gsslogmsg = msg;
+ lib->handle = dlhandle;
+
+#define BIND_GSS_FN(name) \
+ lib->u.gssapi.name = (t_gss_##name) dlsym(dlhandle, "gss_" #name)
+
+ BIND_GSS_FN(delete_sec_context);
+ BIND_GSS_FN(display_status);
+ BIND_GSS_FN(get_mic);
+ BIND_GSS_FN(import_name);
+ BIND_GSS_FN(init_sec_context);
+ BIND_GSS_FN(release_buffer);
+ BIND_GSS_FN(release_cred);
+ BIND_GSS_FN(release_name);
+
+#undef BIND_GSS_FN
+
+ ssh_gssapi_bind_fns(lib);
+}
+
+/* Dynamically load gssapi libs. */
+struct ssh_gss_liblist *ssh_gss_setup(const Config *cfg)
+{
+ void *gsslib;
+ struct ssh_gss_liblist *list = snew(struct ssh_gss_liblist);
+
+ list->libraries = snewn(4, struct ssh_gss_library);
+ list->nlibraries = 0;
+
+ /* Heimdal's GSSAPI Library */
+ if ((gsslib = dlopen("libgssapi.so.2", RTLD_LAZY)) != NULL)
+ gss_init(&list->libraries[list->nlibraries++], gsslib,
+ 0, "Using GSSAPI from libgssapi.so.2");
+
+ /* MIT Kerberos's GSSAPI Library */
+ if ((gsslib = dlopen("libgssapi_krb5.so.2", RTLD_LAZY)) != NULL)
+ gss_init(&list->libraries[list->nlibraries++], gsslib,
+ 1, "Using GSSAPI from libgssapi_krb5.so.2");
+
+ /* Sun's GSSAPI Library */
+ 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)
+ gss_init(&list->libraries[list->nlibraries++], gsslib,
+ 3, dupprintf("Using GSSAPI from user-specified"
+ " library '%s'", cfg->ssh_gss_custom.path));
+
+ return list;
+}
+
+void ssh_gss_cleanup(struct ssh_gss_liblist *list)
+{
+ int i;
+
+ /*
+ * dlopen and dlclose are defined to employ reference counting
+ * in the case where the same library is repeatedly dlopened, so
+ * even in a multiple-sessions-per-process context it's safe to
+ * naively dlclose everything here without worrying about
+ * destroying it under the feet of another SSH instance still
+ * using it.
+ */
+ for (i = 0; i < list->nlibraries; i++) {
+ dlclose(list->libraries[i].handle);
+ if (list->libraries[i].id == 3) {
+ /* The 'custom' id involves a dynamically allocated message.
+ * Note that we must cast away the 'const' to free it. */
+ sfree((char *)list->libraries[i].gsslogmsg);
+ }
+ }
+ sfree(list->libraries);
+ sfree(list);
+}
+
+#elif !defined NO_GSSAPI
+
+const int ngsslibs = 1;
+const char *const gsslibnames[1] = {
+ "static",
+};
+const struct keyvalwhere gsslibkeywords[] = {
+ { "static", 0, -1, -1 },
+};
+
+/*
+ * Link-time binding against GSSAPI. Here we just construct a single
+ * library structure containing pointers to the functions we linked
+ * against.
+ */
+
+#include <gssapi/gssapi.h>
+
+/* Dynamically load gssapi libs. */
+struct ssh_gss_liblist *ssh_gss_setup(const Config *cfg)
+{
+ struct ssh_gss_liblist *list = snew(struct ssh_gss_liblist);
+
+ list->libraries = snew(struct ssh_gss_library);
+ list->nlibraries = 1;
+
+ list->libraries[0].gsslogmsg = "Using statically linked GSSAPI";
+
+#define BIND_GSS_FN(name) \
+ list->libraries[0].u.gssapi.name = (t_gss_##name) gss_##name
+
+ BIND_GSS_FN(delete_sec_context);
+ BIND_GSS_FN(display_status);
+ BIND_GSS_FN(get_mic);
+ BIND_GSS_FN(import_name);
+ BIND_GSS_FN(init_sec_context);
+ BIND_GSS_FN(release_buffer);
+ BIND_GSS_FN(release_cred);
+ BIND_GSS_FN(release_name);
+
+#undef BIND_GSS_FN
+
+ ssh_gssapi_bind_fns(&list->libraries[0]);
+
+ return list;
+}
+
+void ssh_gss_cleanup(struct ssh_gss_liblist *list)
+{
+ sfree(list->libraries);
+ sfree(list);
+}
+
+#endif /* NO_LIBDL */
+
+#endif /* NO_GSSAPI */
--- /dev/null
+/*
+ * PuTTY miscellaneous Unix stuff
+ */
+
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <unistd.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <pwd.h>
+
+#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.
+ */
+ 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';
+ return ret;
+}
+
+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;
+}
+
+#ifdef DEBUG
+static FILE *debug_fp = NULL;
+
+void dputs(char *buf)
+{
+ if (!debug_fp) {
+ debug_fp = fopen("debug.log", "w");
+ }
+
+ write(1, buf, strlen(buf));
+
+ fputs(buf, debug_fp);
+ fflush(debug_fp);
+}
+#endif
+
+char *get_username(void)
+{
+ struct passwd *p;
+ uid_t uid = getuid();
+ char *user, *ret = NULL;
+
+ /*
+ * First, find who we think we are using getlogin. If this
+ * agrees with our uid, we'll go along with it. This should
+ * allow sharing of uids between several login names whilst
+ * coping correctly with people who have su'ed.
+ */
+ user = getlogin();
+ setpwent();
+ if (user)
+ p = getpwnam(user);
+ else
+ p = NULL;
+ if (p && p->pw_uid == uid) {
+ /*
+ * The result of getlogin() really does correspond to
+ * our uid. Fine.
+ */
+ ret = user;
+ } else {
+ /*
+ * If that didn't work, for whatever reason, we'll do
+ * the simpler version: look up our uid in the password
+ * file and map it straight to a name.
+ */
+ p = getpwuid(uid);
+ if (!p)
+ return NULL;
+ ret = p->pw_name;
+ }
+ endpwent();
+
+ return dupstr(ret);
+}
+
+/*
+ * Display the fingerprints of the PGP Master Keys to the user.
+ * (This is here rather than in uxcons because it's appropriate even for
+ * Unix GUI apps.)
+ */
+void pgp_fingerprints(void)
+{
+ fputs("These are the fingerprints of the PuTTY PGP Master Keys. They can\n"
+ "be used to establish a trust path from this executable to another\n"
+ "one. See the manual for more information.\n"
+ "(Note: these fingerprints have nothing to do with SSH!)\n"
+ "\n"
+ "PuTTY Master Key (RSA), 1024-bit:\n"
+ " " PGP_RSA_MASTER_KEY_FP "\n"
+ "PuTTY Master Key (DSA), 1024-bit:\n"
+ " " PGP_DSA_MASTER_KEY_FP "\n", stdout);
+}
+
+/*
+ * Set FD_CLOEXEC on a file descriptor
+ */
+int cloexec(int fd) {
+ int fdflags;
+
+ fdflags = fcntl(fd, F_GETFD);
+ if (fdflags == -1) return -1;
+ return fcntl(fd, F_SETFD, fdflags | FD_CLOEXEC);
+}
+
+FILE *f_open(struct Filename filename, char const *mode, int is_private)
+{
+ if (!is_private) {
+ 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);
+ if (fd < 0)
+ return NULL;
+ return fdopen(fd, mode);
+ }
+}
--- /dev/null
+/*
+ * Unix networking abstraction.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <netdb.h>
+#include <sys/un.h>
+
+#define DEFINE_PLUG_METHOD_MACROS
+#include "putty.h"
+#include "network.h"
+#include "tree234.h"
+
+/* Solaris needs <sys/sockio.h> for SIOCATMARK. */
+#ifndef SIOCATMARK
+#include <sys/sockio.h>
+#endif
+
+#ifndef X11_UNIX_PATH
+# define X11_UNIX_PATH "/tmp/.X11-unix/X"
+#endif
+
+/*
+ * Access to sockaddr types without breaking C strict aliasing rules.
+ */
+union sockaddr_union {
+#ifdef NO_IPV6
+ struct sockaddr_in storage;
+#else
+ struct sockaddr_storage storage;
+ struct sockaddr_in6 sin6;
+#endif
+ struct sockaddr sa;
+ struct sockaddr_in sin;
+ struct sockaddr_un su;
+};
+
+/*
+ * We used to typedef struct Socket_tag *Socket.
+ *
+ * Since we have made the networking abstraction slightly more
+ * abstract, Socket no longer means a tcp socket (it could mean
+ * an ssl socket). So now we must use Actual_Socket when we know
+ * we are talking about a tcp socket.
+ */
+typedef struct Socket_tag *Actual_Socket;
+
+/*
+ * Mutable state that goes with a SockAddr: stores information
+ * about where in the list of candidate IP(v*) addresses we've
+ * currently got to.
+ */
+typedef struct SockAddrStep_tag SockAddrStep;
+struct SockAddrStep_tag {
+#ifndef NO_IPV6
+ struct addrinfo *ai; /* steps along addr->ais */
+#endif
+ int curraddr;
+};
+
+struct Socket_tag {
+ 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;
+ int pending_error; /* in case send() returns error */
+ int listener;
+ int nodelay, keepalive; /* for connect()-type sockets */
+ int privport, port; /* and again */
+ SockAddr addr;
+ SockAddrStep step;
+ /*
+ * We sometimes need pairs of Socket structures to be linked:
+ * if we are listening on the same IPv6 and v4 port, for
+ * example. So here we define `parent' and `child' pointers to
+ * track this link.
+ */
+ Actual_Socket parent, child;
+};
+
+struct SockAddr_tag {
+ int refcount;
+ const char *error;
+ enum { UNRESOLVED, UNIX, IP } superfamily;
+#ifndef NO_IPV6
+ struct addrinfo *ais; /* Addresses IPv6 style. */
+#else
+ unsigned long *addresses; /* Addresses IPv4 style. */
+ int naddresses;
+#endif
+ char hostname[512]; /* Store an unresolved host name. */
+};
+
+/*
+ * Which address family this address belongs to. AF_INET for IPv4;
+ * AF_INET6 for IPv6; AF_UNSPEC indicates that name resolution has
+ * not been done and a simple host name is held in this SockAddr
+ * structure.
+ */
+#ifndef NO_IPV6
+#define SOCKADDR_FAMILY(addr, step) \
+ ((addr)->superfamily == UNRESOLVED ? AF_UNSPEC : \
+ (addr)->superfamily == UNIX ? AF_UNIX : \
+ (step).ai ? (step).ai->ai_family : AF_INET)
+#else
+#define SOCKADDR_FAMILY(addr, step) \
+ ((addr)->superfamily == UNRESOLVED ? AF_UNSPEC : \
+ (addr)->superfamily == UNIX ? AF_UNIX : AF_INET)
+#endif
+
+/*
+ * Start a SockAddrStep structure to step through multiple
+ * addresses.
+ */
+#ifndef NO_IPV6
+#define START_STEP(addr, step) \
+ ((step).ai = (addr)->ais, (step).curraddr = 0)
+#else
+#define START_STEP(addr, step) \
+ ((step).curraddr = 0)
+#endif
+
+static tree234 *sktree;
+
+static void uxsel_tell(Actual_Socket s);
+
+static int cmpfortree(void *av, void *bv)
+{
+ Actual_Socket a = (Actual_Socket) av, b = (Actual_Socket) bv;
+ int as = a->s, bs = b->s;
+ if (as < bs)
+ return -1;
+ if (as > bs)
+ return +1;
+ if (a < b)
+ return -1;
+ if (a > b)
+ return +1;
+ return 0;
+}
+
+static int cmpforsearch(void *av, void *bv)
+{
+ Actual_Socket b = (Actual_Socket) bv;
+ int as = *(int *)av, bs = b->s;
+ if (as < bs)
+ return -1;
+ if (as > bs)
+ return +1;
+ return 0;
+}
+
+void sk_init(void)
+{
+ sktree = newtree234(cmpfortree);
+}
+
+void sk_cleanup(void)
+{
+ Actual_Socket s;
+ int i;
+
+ if (sktree) {
+ for (i = 0; (s = index234(sktree, i)) != NULL; i++) {
+ close(s->s);
+ }
+ }
+}
+
+SockAddr sk_namelookup(const char *host, char **canonicalname, int address_family)
+{
+ SockAddr ret = snew(struct SockAddr_tag);
+#ifndef NO_IPV6
+ struct addrinfo hints;
+ int err;
+#else
+ unsigned long a;
+ struct hostent *h = NULL;
+ int n;
+#endif
+ char realhost[8192];
+
+ /* Clear the structure and default to IPv4. */
+ memset(ret, 0, sizeof(struct SockAddr_tag));
+ ret->superfamily = UNRESOLVED;
+ *realhost = '\0';
+ ret->error = NULL;
+ ret->refcount = 1;
+
+#ifndef NO_IPV6
+ hints.ai_flags = AI_CANONNAME;
+ hints.ai_family = (address_family == ADDRTYPE_IPV4 ? AF_INET :
+ address_family == ADDRTYPE_IPV6 ? AF_INET6 :
+ AF_UNSPEC);
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_protocol = 0;
+ hints.ai_addrlen = 0;
+ hints.ai_addr = NULL;
+ hints.ai_canonname = NULL;
+ hints.ai_next = NULL;
+ err = getaddrinfo(host, NULL, &hints, &ret->ais);
+ if (err != 0) {
+ ret->error = gai_strerror(err);
+ return ret;
+ }
+ ret->superfamily = IP;
+ *realhost = '\0';
+ if (ret->ais->ai_canonname != NULL)
+ strncat(realhost, ret->ais->ai_canonname, sizeof(realhost) - 1);
+ else
+ strncat(realhost, host, sizeof(realhost) - 1);
+#else
+ if ((a = inet_addr(host)) == (unsigned long)(in_addr_t)(-1)) {
+ /*
+ * Otherwise use the IPv4-only gethostbyname... (NOTE:
+ * we don't use gethostbyname as a fallback!)
+ */
+ if (ret->superfamily == UNRESOLVED) {
+ /*debug(("Resolving \"%s\" with gethostbyname() (IPv4 only)...\n", host)); */
+ if ( (h = gethostbyname(host)) )
+ ret->superfamily = IP;
+ }
+ if (ret->superfamily == UNRESOLVED) {
+ ret->error = (h_errno == HOST_NOT_FOUND ||
+ h_errno == NO_DATA ||
+ h_errno == NO_ADDRESS ? "Host does not exist" :
+ h_errno == TRY_AGAIN ?
+ "Temporary name service failure" :
+ "gethostbyname: unknown error");
+ return ret;
+ }
+ /* This way we are always sure the h->h_name is valid :) */
+ strncpy(realhost, h->h_name, sizeof(realhost));
+ for (n = 0; h->h_addr_list[n]; n++);
+ ret->addresses = snewn(n, unsigned long);
+ ret->naddresses = n;
+ for (n = 0; n < ret->naddresses; n++) {
+ memcpy(&a, h->h_addr_list[n], sizeof(a));
+ ret->addresses[n] = ntohl(a);
+ }
+ } else {
+ /*
+ * This must be a numeric IPv4 address because it caused a
+ * success return from inet_addr.
+ */
+ ret->superfamily = IP;
+ strncpy(realhost, host, sizeof(realhost));
+ ret->addresses = snew(unsigned long);
+ ret->naddresses = 1;
+ ret->addresses[0] = ntohl(a);
+ }
+#endif
+ realhost[lenof(realhost)-1] = '\0';
+ *canonicalname = snewn(1+strlen(realhost), char);
+ strcpy(*canonicalname, realhost);
+ return ret;
+}
+
+SockAddr sk_nonamelookup(const char *host)
+{
+ SockAddr ret = snew(struct SockAddr_tag);
+ ret->error = NULL;
+ ret->superfamily = UNRESOLVED;
+ strncpy(ret->hostname, host, lenof(ret->hostname));
+ ret->hostname[lenof(ret->hostname)-1] = '\0';
+#ifndef NO_IPV6
+ ret->ais = NULL;
+#else
+ ret->addresses = NULL;
+#endif
+ ret->refcount = 1;
+ return ret;
+}
+
+static int sk_nextaddr(SockAddr addr, SockAddrStep *step)
+{
+#ifndef NO_IPV6
+ if (step->ai && step->ai->ai_next) {
+ step->ai = step->ai->ai_next;
+ return TRUE;
+ } else
+ return FALSE;
+#else
+ if (step->curraddr+1 < addr->naddresses) {
+ step->curraddr++;
+ return TRUE;
+ } else {
+ return FALSE;
+ }
+#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) {
+ 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,
+ NULL, 0, NI_NUMERICHOST) != 0) {
+ buf[0] = '\0';
+ strncat(buf, "<unknown>", buflen - 1);
+ }
+#else
+ struct in_addr a;
+ SockAddrStep step;
+ START_STEP(addr, step);
+ assert(SOCKADDR_FAMILY(addr, step) == AF_INET);
+ a.s_addr = htonl(addr->addresses[0]);
+ strncpy(buf, inet_ntoa(a), buflen);
+ buf[buflen-1] = '\0';
+#endif
+ }
+}
+
+int sk_hostname_is_local(char *name)
+{
+ return !strcmp(name, "localhost") ||
+ !strcmp(name, "::1") ||
+ !strncmp(name, "127.", 4);
+}
+
+#define ipv4_is_loopback(addr) \
+ (((addr).s_addr & htonl(0xff000000)) == htonl(0x7f000000))
+
+static int sockaddr_is_loopback(struct sockaddr *sa)
+{
+ union sockaddr_union *u = (union sockaddr_union *)sa;
+ switch (u->sa.sa_family) {
+ case AF_INET:
+ return ipv4_is_loopback(u->sin.sin_addr);
+#ifndef NO_IPV6
+ case AF_INET6:
+ return IN6_IS_ADDR_LOOPBACK(&u->sin6.sin6_addr);
+#endif
+ case AF_UNIX:
+ return TRUE;
+ default:
+ return FALSE;
+ }
+}
+
+int sk_address_is_local(SockAddr addr)
+{
+ if (addr->superfamily == UNRESOLVED)
+ return 0; /* we don't know; assume not */
+ else if (addr->superfamily == UNIX)
+ return 1;
+ else {
+#ifndef NO_IPV6
+ return sockaddr_is_loopback(addr->ais->ai_addr);
+#else
+ struct in_addr a;
+ SockAddrStep step;
+ START_STEP(addr, step);
+ assert(SOCKADDR_FAMILY(addr, step) == AF_INET);
+ a.s_addr = htonl(addr->addresses[0]);
+ return ipv4_is_loopback(a);
+#endif
+ }
+}
+
+int sk_addrtype(SockAddr addr)
+{
+ SockAddrStep step;
+ int family;
+ START_STEP(addr, step);
+ family = SOCKADDR_FAMILY(addr, step);
+
+ return (family == AF_INET ? ADDRTYPE_IPV4 :
+#ifndef NO_IPV6
+ family == AF_INET6 ? ADDRTYPE_IPV6 :
+#endif
+ ADDRTYPE_NAME);
+}
+
+void sk_addrcopy(SockAddr addr, char *buf)
+{
+ SockAddrStep step;
+ int family;
+ START_STEP(addr, step);
+ family = SOCKADDR_FAMILY(addr, step);
+
+#ifndef NO_IPV6
+ if (family == AF_INET)
+ memcpy(buf, &((struct sockaddr_in *)step.ai->ai_addr)->sin_addr,
+ sizeof(struct in_addr));
+ else if (family == AF_INET6)
+ memcpy(buf, &((struct sockaddr_in6 *)step.ai->ai_addr)->sin6_addr,
+ sizeof(struct in6_addr));
+ else
+ assert(FALSE);
+#else
+ struct in_addr a;
+
+ assert(family == AF_INET);
+ a.s_addr = htonl(addr->addresses[step.curraddr]);
+ memcpy(buf, (char*) &a.s_addr, 4);
+#endif
+}
+
+void sk_addr_free(SockAddr addr)
+{
+ if (--addr->refcount > 0)
+ return;
+#ifndef NO_IPV6
+ if (addr->ais != NULL)
+ freeaddrinfo(addr->ais);
+#else
+ sfree(addr->addresses);
+#endif
+ sfree(addr);
+}
+
+SockAddr sk_addr_dup(SockAddr addr)
+{
+ addr->refcount++;
+ return addr;
+}
+
+static Plug sk_tcp_plug(Socket sock, Plug p)
+{
+ Actual_Socket s = (Actual_Socket) sock;
+ Plug ret = s->plug;
+ if (p)
+ s->plug = p;
+ return ret;
+}
+
+static void sk_tcp_flush(Socket s)
+{
+ /*
+ * We send data to the socket as soon as we can anyway,
+ * so we don't need to do anything here. :-)
+ */
+}
+
+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_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_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)
+{
+ Actual_Socket ret;
+
+ /*
+ * 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 = 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->listener = 0;
+ ret->parent = ret->child = NULL;
+ ret->addr = NULL;
+ ret->connected = 1;
+
+ ret->s = sockfd;
+
+ if (ret->s < 0) {
+ ret->error = strerror(errno);
+ return (Socket) ret;
+ }
+
+ ret->oobinline = 0;
+
+ uxsel_tell(ret);
+ add234(sktree, ret);
+
+ return (Socket) ret;
+}
+
+static int try_connect(Actual_Socket sock)
+{
+ int s;
+ union sockaddr_union u;
+ const union sockaddr_union *sa;
+ int err = 0;
+ short localport;
+ int fl, 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
+ * function, whether we changed anything or not.
+ */
+ del234(sktree, sock);
+
+ if (sock->s >= 0)
+ close(sock->s);
+
+ plug_log(sock->plug, 0, sock->addr, sock->port, NULL, 0);
+
+ /*
+ * Open socket.
+ */
+ family = SOCKADDR_FAMILY(sock->addr, sock->step);
+ assert(family != AF_UNSPEC);
+ s = socket(family, SOCK_STREAM, 0);
+ sock->s = s;
+
+ if (s < 0) {
+ err = errno;
+ goto ret;
+ }
+
+ cloexec(s);
+
+ if (sock->oobinline) {
+ int b = TRUE;
+ setsockopt(s, SOL_SOCKET, SO_OOBINLINE, (void *) &b, sizeof(b));
+ }
+
+ if (sock->nodelay) {
+ int b = TRUE;
+ setsockopt(s, IPPROTO_TCP, TCP_NODELAY, (void *) &b, sizeof(b));
+ }
+
+ if (sock->keepalive) {
+ int b = TRUE;
+ setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, (void *) &b, sizeof(b));
+ }
+
+ /*
+ * Bind to local address.
+ */
+ if (sock->privport)
+ localport = 1023; /* count from 1023 downwards */
+ else
+ localport = 0; /* just use port 0 (ie kernel picks) */
+
+ /* BSD IP stacks need sockaddr_in zeroed before filling in */
+ memset(&u,'\0',sizeof(u));
+
+ /* We don't try to bind to a local address for UNIX domain sockets. (Why
+ * do we bother doing the bind when localport == 0 anyway?) */
+ if (family != AF_UNIX) {
+ /* Loop round trying to bind */
+ while (1) {
+ int retcode;
+
+#ifndef NO_IPV6
+ if (family == AF_INET6) {
+ /* XXX use getaddrinfo to get a local address? */
+ u.sin6.sin6_family = AF_INET6;
+ u.sin6.sin6_addr = in6addr_any;
+ u.sin6.sin6_port = htons(localport);
+ retcode = bind(s, &u.sa, sizeof(u.sin6));
+ } else
+#endif
+ {
+ assert(family == AF_INET);
+ u.sin.sin_family = AF_INET;
+ u.sin.sin_addr.s_addr = htonl(INADDR_ANY);
+ u.sin.sin_port = htons(localport);
+ retcode = bind(s, &u.sa, sizeof(u.sin));
+ }
+ if (retcode >= 0) {
+ err = 0;
+ break; /* done */
+ } else {
+ err = errno;
+ if (err != EADDRINUSE) /* failed, for a bad reason */
+ break;
+ }
+
+ if (localport == 0)
+ break; /* we're only looping once */
+ localport--;
+ if (localport == 0)
+ break; /* we might have got to the end */
+ }
+
+ if (err)
+ goto ret;
+ }
+
+ /*
+ * Connect to remote address.
+ */
+ switch(family) {
+#ifndef NO_IPV6
+ case AF_INET:
+ /* XXX would be better to have got getaddrinfo() to fill in the port. */
+ ((struct sockaddr_in *)sock->step.ai->ai_addr)->sin_port =
+ htons(sock->port);
+ sa = (const union sockaddr_union *)sock->step.ai->ai_addr;
+ salen = sock->step.ai->ai_addrlen;
+ break;
+ case AF_INET6:
+ ((struct sockaddr_in *)sock->step.ai->ai_addr)->sin_port =
+ htons(sock->port);
+ sa = (const union sockaddr_union *)sock->step.ai->ai_addr;
+ salen = sock->step.ai->ai_addrlen;
+ break;
+#else
+ case AF_INET:
+ u.sin.sin_family = AF_INET;
+ u.sin.sin_addr.s_addr = htonl(sock->addr->addresses[sock->step.curraddr]);
+ u.sin.sin_port = htons((short) sock->port);
+ sa = &u;
+ salen = sizeof u.sin;
+ break;
+#endif
+ case AF_UNIX:
+ assert(sock->port == 0); /* to catch confused people */
+ assert(strlen(sock->addr->hostname) < sizeof u.su.sun_path);
+ u.su.sun_family = AF_UNIX;
+ strcpy(u.su.sun_path, sock->addr->hostname);
+ sa = &u;
+ salen = sizeof u.su;
+ break;
+
+ 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);
+
+ if ((connect(s, &(sa->sa), salen)) < 0) {
+ if ( errno != EINPROGRESS ) {
+ err = errno;
+ goto ret;
+ }
+ } else {
+ /*
+ * If we _don't_ get EWOULDBLOCK, the connect has completed
+ * and we should set the socket as connected and writable.
+ */
+ sock->connected = 1;
+ sock->writable = 1;
+ }
+
+ uxsel_tell(sock);
+
+ ret:
+
+ /*
+ * No matter what happened, put the socket back in the tree.
+ */
+ add234(sktree, sock);
+
+ if (err)
+ plug_log(sock->plug, 1, sock->addr, sock->port, strerror(err), err);
+ return err;
+}
+
+Socket sk_new(SockAddr addr, int port, int privport, int oobinline,
+ int nodelay, int keepalive, Plug plug)
+{
+ Actual_Socket ret;
+ int err;
+
+ /*
+ * 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->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->listener = 0;
+ ret->addr = addr;
+ START_STEP(ret->addr, ret->step);
+ ret->s = -1;
+ ret->oobinline = oobinline;
+ ret->nodelay = nodelay;
+ ret->keepalive = keepalive;
+ ret->privport = privport;
+ ret->port = port;
+
+ err = 0;
+ do {
+ err = try_connect(ret);
+ } while (err && sk_nextaddr(ret->addr, &ret->step));
+
+ if (err)
+ ret->error = strerror(err);
+
+ return (Socket) ret;
+}
+
+Socket sk_newlistener(char *srcaddr, int port, Plug plug, int local_host_only, int orig_address_family)
+{
+ int s;
+#ifndef NO_IPV6
+ struct addrinfo hints, *ai;
+ char portstr[6];
+#endif
+ union sockaddr_union u;
+ union sockaddr_union *addr;
+ int addrlen;
+ Actual_Socket ret;
+ int retcode;
+ int address_family;
+ int on = 1;
+
+ /*
+ * 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->frozen_readable = 0;
+ ret->localhost_only = local_host_only;
+ ret->pending_error = 0;
+ ret->parent = ret->child = NULL;
+ ret->oobpending = FALSE;
+ ret->listener = 1;
+ ret->addr = NULL;
+
+ /*
+ * Translate address_family from platform-independent constants
+ * into local reality.
+ */
+ address_family = (orig_address_family == ADDRTYPE_IPV4 ? AF_INET :
+#ifndef NO_IPV6
+ orig_address_family == ADDRTYPE_IPV6 ? AF_INET6 :
+#endif
+ AF_UNSPEC);
+
+#ifndef NO_IPV6
+ /* Let's default to IPv6.
+ * If the stack doesn't support IPv6, we will fall back to IPv4. */
+ if (address_family == AF_UNSPEC) address_family = AF_INET6;
+#else
+ /* No other choice, default to IPv4 */
+ if (address_family == AF_UNSPEC) address_family = AF_INET;
+#endif
+
+ /*
+ * Open socket.
+ */
+ s = socket(address_family, SOCK_STREAM, 0);
+
+#ifndef NO_IPV6
+ /* If the host doesn't support IPv6 try fallback to IPv4. */
+ if (s < 0 && address_family == AF_INET6) {
+ address_family = AF_INET;
+ s = socket(address_family, SOCK_STREAM, 0);
+ }
+#endif
+
+ if (s < 0) {
+ ret->error = strerror(errno);
+ return (Socket) ret;
+ }
+
+ cloexec(s);
+
+ ret->oobinline = 0;
+
+ setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (const char *)&on, sizeof(on));
+
+ retcode = -1;
+ addr = NULL; addrlen = -1; /* placate optimiser */
+
+ if (srcaddr != NULL) {
+#ifndef NO_IPV6
+ hints.ai_flags = AI_NUMERICHOST;
+ hints.ai_family = address_family;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_protocol = 0;
+ hints.ai_addrlen = 0;
+ hints.ai_addr = NULL;
+ hints.ai_canonname = NULL;
+ hints.ai_next = NULL;
+ assert(port >= 0 && port <= 99999);
+ sprintf(portstr, "%d", port);
+ retcode = getaddrinfo(srcaddr, portstr, &hints, &ai);
+ if (retcode == 0) {
+ addr = (union sockaddr_union *)ai->ai_addr;
+ addrlen = ai->ai_addrlen;
+ }
+#else
+ memset(&u,'\0',sizeof u);
+ u.sin.sin_family = AF_INET;
+ u.sin.sin_port = htons(port);
+ u.sin.sin_addr.s_addr = inet_addr(srcaddr);
+ if (u.sin.sin_addr.s_addr != (in_addr_t)(-1)) {
+ /* Override localhost_only with specified listen addr. */
+ ret->localhost_only = ipv4_is_loopback(u.sin.sin_addr);
+ }
+ addr = &u;
+ addrlen = sizeof(u.sin);
+ retcode = 0;
+#endif
+ }
+
+ if (retcode != 0) {
+ memset(&u,'\0',sizeof u);
+#ifndef NO_IPV6
+ if (address_family == AF_INET6) {
+ u.sin6.sin6_family = AF_INET6;
+ u.sin6.sin6_port = htons(port);
+ if (local_host_only)
+ u.sin6.sin6_addr = in6addr_loopback;
+ else
+ u.sin6.sin6_addr = in6addr_any;
+ addr = &u;
+ addrlen = sizeof(u.sin6);
+ } else
+#endif
+ {
+ u.sin.sin_family = AF_INET;
+ u.sin.sin_port = htons(port);
+ if (local_host_only)
+ u.sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+ else
+ u.sin.sin_addr.s_addr = htonl(INADDR_ANY);
+ addr = &u;
+ addrlen = sizeof(u.sin);
+ }
+ }
+
+ 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;
+ }
+
+#ifndef NO_IPV6
+ /*
+ * If we were given ADDRTYPE_UNSPEC, we must also create an
+ * IPv4 listening socket and link it to this one.
+ */
+ if (address_family == AF_INET6 && orig_address_family == ADDRTYPE_UNSPEC) {
+ Actual_Socket other;
+
+ other = (Actual_Socket) sk_newlistener(srcaddr, port, plug,
+ local_host_only, ADDRTYPE_IPV4);
+
+ if (other) {
+ if (!other->error) {
+ other->parent = ret;
+ ret->child = other;
+ } else {
+ /* If we couldn't create a listening socket on IPv4 as well
+ * as IPv6, we must return an error overall. */
+ close(s);
+ sfree(ret);
+ return (Socket) other;
+ }
+ }
+ }
+#endif
+
+ ret->s = s;
+
+ uxsel_tell(ret);
+ add234(sktree, ret);
+
+ return (Socket) ret;
+}
+
+static void sk_tcp_close(Socket sock)
+{
+ Actual_Socket s = (Actual_Socket) sock;
+
+ if (s->child)
+ sk_tcp_close((Socket)s->child);
+
+ uxsel_del(s->s);
+ del234(sktree, s);
+ close(s->s);
+ if (s->addr)
+ sk_addr_free(s->addr);
+ sfree(s);
+}
+
+void *sk_getxdmdata(void *sock, int *lenp)
+{
+ Actual_Socket s = (Actual_Socket) sock;
+ union sockaddr_union u;
+ socklen_t addrlen;
+ char *buf;
+ static unsigned int unix_addr = 0xFFFFFFFF;
+
+ /*
+ * We must check that this socket really _is_ an Actual_Socket.
+ */
+ if (s->fn != &tcp_fn_table)
+ return NULL; /* failure */
+
+ addrlen = sizeof(u);
+ if (getsockname(s->s, &u.sa, &addrlen) < 0)
+ return NULL;
+ switch(u.sa.sa_family) {
+ case AF_INET:
+ *lenp = 6;
+ buf = snewn(*lenp, char);
+ PUT_32BIT_MSB_FIRST(buf, ntohl(u.sin.sin_addr.s_addr));
+ PUT_16BIT_MSB_FIRST(buf+4, ntohs(u.sin.sin_port));
+ break;
+#ifndef NO_IPV6
+ case AF_INET6:
+ *lenp = 6;
+ buf = snewn(*lenp, char);
+ if (IN6_IS_ADDR_V4MAPPED(&u.sin6.sin6_addr)) {
+ memcpy(buf, u.sin6.sin6_addr.s6_addr + 12, 4);
+ PUT_16BIT_MSB_FIRST(buf+4, ntohs(u.sin6.sin6_port));
+ } else
+ /* This is stupid, but it's what XLib does. */
+ memset(buf, 0, 6);
+ break;
+#endif
+ case AF_UNIX:
+ *lenp = 6;
+ buf = snewn(*lenp, char);
+ PUT_32BIT_MSB_FIRST(buf, unix_addr--);
+ PUT_16BIT_MSB_FIRST(buf+4, getpid());
+ break;
+
+ /* XXX IPV6 */
+
+ default:
+ return NULL;
+ }
+
+ return buf;
+}
+
+/*
+ * The function which tries to send on a socket once it's deemed
+ * writable.
+ */
+void try_send(Actual_Socket s)
+{
+ while (s->sending_oob || bufchain_size(&s->output_data) > 0) {
+ int nsent;
+ int err;
+ void *data;
+ int len, urgentflag;
+
+ if (s->sending_oob) {
+ urgentflag = MSG_OOB;
+ len = s->sending_oob;
+ data = &s->oobdata;
+ } else {
+ urgentflag = 0;
+ bufchain_prefix(&s->output_data, &data, &len);
+ }
+ nsent = send(s->s, data, len, urgentflag);
+ noise_ultralight(nsent);
+ if (nsent <= 0) {
+ err = (nsent < 0 ? errno : 0);
+ if (err == EWOULDBLOCK) {
+ /*
+ * Perfectly normal: we've sent all we can for the moment.
+ */
+ s->writable = FALSE;
+ return;
+ } else {
+ /*
+ * We unfortunately can't just call plug_closing(),
+ * because it's quite likely that we're currently
+ * _in_ a call from the code we'd be calling back
+ * to, so we'd have to make half the SSH code
+ * 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;
+ return;
+ }
+ } else {
+ if (s->sending_oob) {
+ if (nsent < len) {
+ memmove(s->oobdata, s->oobdata+nsent, len-nsent);
+ s->sending_oob = len - nsent;
+ } else {
+ s->sending_oob = 0;
+ }
+ } else {
+ bufchain_consume(&s->output_data, nsent);
+ }
+ }
+ }
+ uxsel_tell(s);
+}
+
+static int sk_tcp_write(Socket sock, const char *buf, int len)
+{
+ Actual_Socket s = (Actual_Socket) sock;
+
+ /*
+ * Add the data to the buffer list on the socket.
+ */
+ bufchain_add(&s->output_data, buf, len);
+
+ /*
+ * 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);
+
+ return bufchain_size(&s->output_data);
+}
+
+static int sk_tcp_write_oob(Socket sock, const char *buf, int len)
+{
+ Actual_Socket s = (Actual_Socket) sock;
+
+ /*
+ * Replace the buffer list on the socket with the data.
+ */
+ bufchain_clear(&s->output_data);
+ assert(len <= sizeof(s->oobdata));
+ memcpy(s->oobdata, buf, len);
+ s->sending_oob = len;
+
+ /*
+ * 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);
+
+ return s->sending_oob;
+}
+
+static int net_select_result(int fd, int event)
+{
+ int ret;
+ char buf[20480]; /* nice big buffer for plenty of speed */
+ Actual_Socket s;
+ u_long atmark;
+
+ /* Find the Socket structure */
+ s = find234(sktree, &fd, cmpforsearch);
+ if (!s)
+ return 1; /* boggle */
+
+ noise_ultralight(event);
+
+ switch (event) {
+ case 4: /* exceptional */
+ if (!s->oobinline) {
+ /*
+ * On a non-oobinline socket, this indicates that we
+ * can immediately perform an OOB read and get back OOB
+ * data, which we will send to the back end with
+ * type==2 (urgent data).
+ */
+ ret = recv(s->s, buf, sizeof(buf), MSG_OOB);
+ noise_ultralight(ret);
+ if (ret <= 0) {
+ return plug_closing(s->plug,
+ ret == 0 ? "Internal networking trouble" :
+ strerror(errno), errno, 0);
+ } else {
+ /*
+ * Receiving actual data on a socket means we can
+ * stop falling back through the candidate
+ * addresses to connect to.
+ */
+ if (s->addr) {
+ sk_addr_free(s->addr);
+ s->addr = NULL;
+ }
+ return plug_receive(s->plug, 2, buf, ret);
+ }
+ break;
+ }
+
+ /*
+ * If we reach here, this is an oobinline socket, which
+ * means we should set s->oobpending and then deal with it
+ * when we get called for the readability event (which
+ * should also occur).
+ */
+ s->oobpending = TRUE;
+ break;
+ case 1: /* readable; also acceptance */
+ if (s->listener) {
+ /*
+ * On a listening socket, the readability event means a
+ * connection is ready to be accepted.
+ */
+ union sockaddr_union su;
+ socklen_t addrlen = sizeof(su);
+ 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);
+
+ if (s->localhost_only &&
+ !sockaddr_is_loopback(&su.sa)) {
+ close(t); /* someone let nonlocal through?! */
+ } else if (plug_accepting(s->plug, t)) {
+ close(t); /* denied or error */
+ }
+ break;
+ }
+
+ /*
+ * 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;
+ 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
+ * (data prior to urgent).
+ */
+ if (s->oobinline && s->oobpending) {
+ atmark = 1;
+ if (ioctl(s->s, SIOCATMARK, &atmark) == 0 && atmark)
+ s->oobpending = FALSE; /* clear this indicator */
+ } else
+ atmark = 1;
+
+ ret = recv(s->s, buf, s->oobpending ? 1 : sizeof(buf), 0);
+ noise_ultralight(ret);
+ if (ret < 0) {
+ if (errno == EWOULDBLOCK) {
+ break;
+ }
+ }
+ if (ret < 0) {
+ /*
+ * An error at this point _might_ be an error reported
+ * by a non-blocking connect(). So before we return a
+ * panic status to the user, let's just see whether
+ * that's the case.
+ */
+ int err = errno;
+ if (s->addr) {
+ plug_log(s->plug, 1, s->addr, s->port, strerror(err), err);
+ while (s->addr && sk_nextaddr(s->addr, &s->step)) {
+ err = try_connect(s);
+ }
+ }
+ if (err != 0)
+ return plug_closing(s->plug, strerror(err), err, 0);
+ } else if (0 == ret) {
+ return plug_closing(s->plug, NULL, 0, 0);
+ } else {
+ /*
+ * Receiving actual data on a socket means we can
+ * stop falling back through the candidate
+ * addresses to connect to.
+ */
+ if (s->addr) {
+ sk_addr_free(s->addr);
+ s->addr = NULL;
+ }
+ return plug_receive(s->plug, atmark ? 0 : 1, buf, ret);
+ }
+ break;
+ case 2: /* writable */
+ if (!s->connected) {
+ /*
+ * select() reports a socket as _writable_ when an
+ * asynchronous connection is completed.
+ */
+ s->connected = s->writable = 1;
+ uxsel_tell(s);
+ break;
+ } else {
+ int bufsize_before, bufsize_after;
+ s->writable = 1;
+ bufsize_before = s->sending_oob + bufchain_size(&s->output_data);
+ try_send(s);
+ bufsize_after = s->sending_oob + bufchain_size(&s->output_data);
+ if (bufsize_after < bufsize_before)
+ plug_sent(s->plug, bufsize_after);
+ }
+ break;
+ }
+
+ 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.
+ */
+const char *sk_addr_error(SockAddr addr)
+{
+ return addr->error;
+}
+static const char *sk_tcp_socket_error(Socket sock)
+{
+ Actual_Socket s = (Actual_Socket) sock;
+ return s->error;
+}
+
+static void sk_tcp_set_frozen(Socket sock, int is_frozen)
+{
+ 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 */
+ }
+ uxsel_set(s->s, rwx, net_select_result);
+}
+
+int net_service_lookup(char *service)
+{
+ struct servent *se;
+ se = getservbyname(service, NULL);
+ if (se != NULL)
+ return ntohs(se->s_port);
+ else
+ return 0;
+}
+
+char *get_hostname(void)
+{
+ int len = 128;
+ char *hostname = NULL;
+ do {
+ len *= 2;
+ hostname = sresize(hostname, len, char);
+ if ((gethostname(hostname, len) < 0) &&
+ (errno != ENAMETOOLONG)) {
+ sfree(hostname);
+ hostname = NULL;
+ break;
+ }
+ } while (strlen(hostname) >= len-1);
+ return hostname;
+}
+
+SockAddr platform_get_x11_unix_address(const char *sockpath, int displaynum)
+{
+ SockAddr ret = snew(struct SockAddr_tag);
+ int n;
+
+ memset(ret, 0, sizeof *ret);
+ ret->superfamily = UNIX;
+ /*
+ * In special circumstances (notably Mac OS X Leopard), we'll
+ * have been passed an explicit Unix socket path.
+ */
+ if (sockpath) {
+ n = snprintf(ret->hostname, sizeof ret->hostname,
+ "%s", sockpath);
+ } else {
+ n = snprintf(ret->hostname, sizeof ret->hostname,
+ "%s%d", X11_UNIX_PATH, displaynum);
+ }
+
+ if (n < 0)
+ ret->error = "snprintf failed";
+ else if (n >= sizeof ret->hostname)
+ ret->error = "X11 UNIX name too long";
+
+#ifndef NO_IPV6
+ ret->ais = NULL;
+#else
+ ret->addresses = NULL;
+ ret->naddresses = 0;
+#endif
+ ret->refcount = 1;
+ return ret;
+}
--- /dev/null
+/*
+ * Noise generation for PuTTY's cryptographic random number
+ * generator.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+
+#include "putty.h"
+#include "ssh.h"
+#include "storage.h"
+
+static int read_dev_urandom(char *buf, int len)
+{
+ int fd;
+ int ngot, ret;
+
+ fd = open("/dev/urandom", O_RDONLY);
+ if (fd < 0)
+ return 0;
+
+ ngot = 0;
+ while (ngot < len) {
+ ret = read(fd, buf+ngot, len-ngot);
+ if (ret < 0) {
+ close(fd);
+ return 0;
+ }
+ ngot += ret;
+ }
+
+ close(fd);
+
+ return 1;
+}
+
+/*
+ * This function is called once, at PuTTY startup. It will do some
+ * slightly silly things such as fetching an entire process listing
+ * and scanning /tmp, load the saved random seed from disk, and
+ * also read 32 bytes out of /dev/urandom.
+ */
+
+void noise_get_heavy(void (*func) (void *, int))
+{
+ char buf[512];
+ FILE *fp;
+ int ret;
+ int got_dev_urandom = 0;
+
+ if (read_dev_urandom(buf, 32)) {
+ got_dev_urandom = 1;
+ func(buf, 32);
+ }
+
+ fp = popen("ps -axu 2>/dev/null", "r");
+ if (fp) {
+ while ( (ret = fread(buf, 1, sizeof(buf), fp)) > 0)
+ func(buf, ret);
+ pclose(fp);
+ } else if (!got_dev_urandom) {
+ fprintf(stderr, "popen: %s\n"
+ "Unable to access fallback entropy source\n", strerror(errno));
+ exit(1);
+ }
+
+ fp = popen("ls -al /tmp 2>/dev/null", "r");
+ if (fp) {
+ while ( (ret = fread(buf, 1, sizeof(buf), fp)) > 0)
+ func(buf, ret);
+ pclose(fp);
+ } else if (!got_dev_urandom) {
+ fprintf(stderr, "popen: %s\n"
+ "Unable to access fallback entropy source\n", strerror(errno));
+ exit(1);
+ }
+
+ read_random_seed(func);
+ random_save_seed();
+}
+
+void random_save_seed(void)
+{
+ int len;
+ void *data;
+
+ if (random_active) {
+ random_get_savedata(&data, &len);
+ write_random_seed(data, len);
+ sfree(data);
+ }
+}
+
+/*
+ * This function is called every time the random pool needs
+ * stirring, and will acquire the system time.
+ */
+void noise_get_light(void (*func) (void *, int))
+{
+ struct timeval tv;
+ gettimeofday(&tv, NULL);
+ func(&tv, sizeof(tv));
+}
+
+/*
+ * This function is called on a timer, and grabs as much changeable
+ * system data as it can quickly get its hands on.
+ */
+void noise_regular(void)
+{
+ int fd;
+ int ret;
+ char buf[512];
+ struct rusage rusage;
+
+ if ((fd = open("/proc/meminfo", O_RDONLY)) >= 0) {
+ while ( (ret = read(fd, buf, sizeof(buf))) > 0)
+ random_add_noise(buf, ret);
+ close(fd);
+ }
+ if ((fd = open("/proc/stat", O_RDONLY)) >= 0) {
+ while ( (ret = read(fd, buf, sizeof(buf))) > 0)
+ random_add_noise(buf, ret);
+ close(fd);
+ }
+ getrusage(RUSAGE_SELF, &rusage);
+ random_add_noise(&rusage, sizeof(rusage));
+}
+
+/*
+ * This function is called on every keypress or mouse move, and
+ * will add the current time to the noise pool. It gets the scan
+ * code or mouse position passed in, and adds that too.
+ */
+void noise_ultralight(unsigned long data)
+{
+ struct timeval tv;
+ gettimeofday(&tv, NULL);
+ random_add_noise(&tv, sizeof(tv));
+ random_add_noise(&data, sizeof(data));
+}
--- /dev/null
+/*
+ * PLink - a command-line (stdin/stdout) variant of PuTTY.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <assert.h>
+#include <stdarg.h>
+#include <signal.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <termios.h>
+#include <pwd.h>
+#include <sys/ioctl.h>
+#include <sys/time.h>
+#ifndef HAVE_NO_SYS_SELECT_H
+#include <sys/select.h>
+#endif
+
+#define PUTTY_DO_GLOBALS /* actually _define_ globals */
+#include "putty.h"
+#include "storage.h"
+#include "tree234.h"
+
+#define MAX_STDIN_BACKLOG 4096
+
+void *logctx;
+
+static struct termios orig_termios;
+
+void fatalbox(char *p, ...)
+{
+ struct termios cf;
+ va_list ap;
+ premsg(&cf);
+ fprintf(stderr, "FATAL ERROR: ");
+ va_start(ap, p);
+ vfprintf(stderr, p, ap);
+ va_end(ap);
+ fputc('\n', stderr);
+ postmsg(&cf);
+ if (logctx) {
+ log_free(logctx);
+ logctx = NULL;
+ }
+ cleanup_exit(1);
+}
+void modalfatalbox(char *p, ...)
+{
+ struct termios cf;
+ va_list ap;
+ premsg(&cf);
+ fprintf(stderr, "FATAL ERROR: ");
+ va_start(ap, p);
+ vfprintf(stderr, p, ap);
+ va_end(ap);
+ fputc('\n', stderr);
+ postmsg(&cf);
+ if (logctx) {
+ log_free(logctx);
+ logctx = NULL;
+ }
+ cleanup_exit(1);
+}
+void connection_fatal(void *frontend, char *p, ...)
+{
+ struct termios cf;
+ va_list ap;
+ premsg(&cf);
+ fprintf(stderr, "FATAL ERROR: ");
+ va_start(ap, p);
+ vfprintf(stderr, p, ap);
+ va_end(ap);
+ fputc('\n', stderr);
+ postmsg(&cf);
+ if (logctx) {
+ log_free(logctx);
+ logctx = NULL;
+ }
+ cleanup_exit(1);
+}
+void cmdline_error(char *p, ...)
+{
+ struct termios cf;
+ va_list ap;
+ premsg(&cf);
+ fprintf(stderr, "plink: ");
+ va_start(ap, p);
+ vfprintf(stderr, p, ap);
+ va_end(ap);
+ fputc('\n', stderr);
+ postmsg(&cf);
+ exit(1);
+}
+
+static int local_tty = FALSE; /* do we have a local tty? */
+
+static Backend *back;
+static void *backhandle;
+static Config cfg;
+
+/*
+ * Default settings that are specific to pterm.
+ */
+char *platform_default_s(const char *name)
+{
+ if (!strcmp(name, "TermType"))
+ return dupstr(getenv("TERM"));
+ if (!strcmp(name, "UserName"))
+ return get_username();
+ if (!strcmp(name, "SerialLine"))
+ return dupstr("/dev/ttyS0");
+ 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;
+ if (!strcmp(name, "LogFileName"))
+ strcpy(ret.path, "putty.log");
+ else
+ *ret.path = '\0';
+ return ret;
+}
+
+char *x_get_default(const char *key)
+{
+ return NULL; /* this is a stub */
+}
+int term_ldisc(Terminal *term, int mode)
+{
+ return FALSE;
+}
+void ldisc_update(void *frontend, int echo, int edit)
+{
+ /* Update stdin read mode to reflect changes in line discipline. */
+ struct termios mode;
+
+ if (!local_tty) return;
+
+ mode = orig_termios;
+
+ if (echo)
+ mode.c_lflag |= ECHO;
+ else
+ mode.c_lflag &= ~ECHO;
+
+ if (edit) {
+ mode.c_iflag |= ICRNL;
+ mode.c_lflag |= ISIG | ICANON;
+ mode.c_oflag |= OPOST;
+ } else {
+ mode.c_iflag &= ~ICRNL;
+ mode.c_lflag &= ~(ISIG | ICANON);
+ mode.c_oflag &= ~OPOST;
+ /* Solaris sets these to unhelpful values */
+ mode.c_cc[VMIN] = 1;
+ mode.c_cc[VTIME] = 0;
+ /* FIXME: perhaps what we do with IXON/IXOFF should be an
+ * argument to ldisc_update(), to allow implementation of SSH-2
+ * "xon-xoff" and Rlogin's equivalent? */
+ mode.c_iflag &= ~IXON;
+ mode.c_iflag &= ~IXOFF;
+ }
+ /*
+ * Mark parity errors and (more important) BREAK on input. This
+ * is more complex than it need be because POSIX-2001 suggests
+ * that escaping of valid 0xff in the input stream is dependent on
+ * IGNPAR being clear even though marking of BREAK isn't. NetBSD
+ * 2.0 goes one worse and makes it dependent on INPCK too. We
+ * deal with this by forcing these flags into a useful state and
+ * then faking the state in which we found them in from_tty() if
+ * we get passed a parity or framing error.
+ */
+ mode.c_iflag = (mode.c_iflag | INPCK | PARMRK) & ~IGNPAR;
+
+ tcsetattr(STDIN_FILENO, TCSANOW, &mode);
+}
+
+/* Helper function to extract a special character from a termios. */
+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("");
+#endif
+ return dupprintf("^<%d>", c);
+}
+
+char *get_ttymode(void *frontend, const char *mode)
+{
+ /*
+ * Propagate appropriate terminal modes from the local terminal,
+ * if any.
+ */
+ if (!local_tty) return NULL;
+
+#define GET_CHAR(ourname, uxname) \
+ do { \
+ if (strcmp(mode, ourname) == 0) \
+ return get_ttychar(&orig_termios, uxname); \
+ } while(0)
+#define GET_BOOL(ourname, uxname, uxmemb, transform) \
+ do { \
+ if (strcmp(mode, ourname) == 0) { \
+ int b = (orig_termios.uxmemb & uxname) != 0; \
+ transform; \
+ return dupprintf("%d", b); \
+ } \
+ } while (0)
+
+ /*
+ * Modes that want to be the same on all terminal devices involved.
+ */
+ /* All the special characters supported by SSH */
+#if defined(VINTR)
+ GET_CHAR("INTR", VINTR);
+#endif
+#if defined(VQUIT)
+ GET_CHAR("QUIT", VQUIT);
+#endif
+#if defined(VERASE)
+ GET_CHAR("ERASE", VERASE);
+#endif
+#if defined(VKILL)
+ GET_CHAR("KILL", VKILL);
+#endif
+#if defined(VEOF)
+ GET_CHAR("EOF", VEOF);
+#endif
+#if defined(VEOL)
+ GET_CHAR("EOL", VEOL);
+#endif
+#if defined(VEOL2)
+ GET_CHAR("EOL2", VEOL2);
+#endif
+#if defined(VSTART)
+ GET_CHAR("START", VSTART);
+#endif
+#if defined(VSTOP)
+ GET_CHAR("STOP", VSTOP);
+#endif
+#if defined(VSUSP)
+ GET_CHAR("SUSP", VSUSP);
+#endif
+#if defined(VDSUSP)
+ GET_CHAR("DSUSP", VDSUSP);
+#endif
+#if defined(VREPRINT)
+ GET_CHAR("REPRINT", VREPRINT);
+#endif
+#if defined(VWERASE)
+ GET_CHAR("WERASE", VWERASE);
+#endif
+#if defined(VLNEXT)
+ GET_CHAR("LNEXT", VLNEXT);
+#endif
+#if defined(VFLUSH)
+ GET_CHAR("FLUSH", VFLUSH);
+#endif
+#if defined(VSWTCH)
+ GET_CHAR("SWTCH", VSWTCH);
+#endif
+#if defined(VSTATUS)
+ GET_CHAR("STATUS", VSTATUS);
+#endif
+#if defined(VDISCARD)
+ GET_CHAR("DISCARD", VDISCARD);
+#endif
+ /* Modes that "configure" other major modes. These should probably be
+ * considered as user preferences. */
+ /* Configuration of ICANON */
+#if defined(ECHOK)
+ GET_BOOL("ECHOK", ECHOK, c_lflag, );
+#endif
+#if defined(ECHOKE)
+ GET_BOOL("ECHOKE", ECHOKE, c_lflag, );
+#endif
+#if defined(ECHOE)
+ GET_BOOL("ECHOE", ECHOE, c_lflag, );
+#endif
+#if defined(ECHONL)
+ GET_BOOL("ECHONL", ECHONL, c_lflag, );
+#endif
+#if defined(XCASE)
+ GET_BOOL("XCASE", XCASE, c_lflag, );
+#endif
+ /* Configuration of ECHO */
+#if defined(ECHOCTL)
+ GET_BOOL("ECHOCTL", ECHOCTL, c_lflag, );
+#endif
+ /* Configuration of IXON/IXOFF */
+#if defined(IXANY)
+ GET_BOOL("IXANY", IXANY, c_iflag, );
+#endif
+ /* Configuration of OPOST */
+#if defined(OLCUC)
+ GET_BOOL("OLCUC", OLCUC, c_oflag, );
+#endif
+#if defined(ONLCR)
+ GET_BOOL("ONLCR", ONLCR, c_oflag, );
+#endif
+#if defined(OCRNL)
+ GET_BOOL("OCRNL", OCRNL, c_oflag, );
+#endif
+#if defined(ONOCR)
+ GET_BOOL("ONOCR", ONOCR, c_oflag, );
+#endif
+#if defined(ONLRET)
+ GET_BOOL("ONLRET", ONLRET, c_oflag, );
+#endif
+
+ /*
+ * Modes that want to be set in only one place, and that we have
+ * squashed locally.
+ */
+#if defined(ISIG)
+ GET_BOOL("ISIG", ISIG, c_lflag, );
+#endif
+#if defined(ICANON)
+ GET_BOOL("ICANON", ICANON, c_lflag, );
+#endif
+#if defined(ECHO)
+ GET_BOOL("ECHO", ECHO, c_lflag, );
+#endif
+#if defined(IXON)
+ GET_BOOL("IXON", IXON, c_iflag, );
+#endif
+#if defined(IXOFF)
+ GET_BOOL("IXOFF", IXOFF, c_iflag, );
+#endif
+#if defined(OPOST)
+ GET_BOOL("OPOST", OPOST, c_oflag, );
+#endif
+
+ /*
+ * We do not propagate the following modes:
+ * - Parity/serial settings, which are a local affair and don't
+ * make sense propagated over SSH's 8-bit byte-stream.
+ * IGNPAR PARMRK INPCK CS7 CS8 PARENB PARODD
+ * - Things that want to be enabled in one place that we don't
+ * squash locally.
+ * IUCLC
+ * - Status bits.
+ * PENDIN
+ * - Things I don't know what to do with. (FIXME)
+ * ISTRIP IMAXBEL NOFLSH TOSTOP IEXTEN
+ * INLCR IGNCR ICRNL
+ */
+
+#undef GET_CHAR
+#undef GET_BOOL
+
+ /* Fall through to here for unrecognised names, or ones that are
+ * unsupported on this platform */
+ return NULL;
+}
+
+void cleanup_termios(void)
+{
+ if (local_tty)
+ tcsetattr(STDIN_FILENO, TCSANOW, &orig_termios);
+}
+
+bufchain stdout_data, stderr_data;
+
+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);
+ }
+ return bufchain_size(&stdout_data) + bufchain_size(&stderr_data);
+}
+
+int from_backend(void *frontend_handle, int is_stderr,
+ const char *data, int len)
+{
+ if (is_stderr) {
+ bufchain_add(&stderr_data, data, len);
+ return try_output(TRUE);
+ } else {
+ bufchain_add(&stdout_data, data, len);
+ return try_output(FALSE);
+ }
+}
+
+int from_backend_untrusted(void *frontend_handle, const char *data, int len)
+{
+ /*
+ * 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 get_userpass_input(prompts_t *p, unsigned char *in, int inlen)
+{
+ int ret;
+ ret = cmdline_get_passwd_input(p, in, inlen);
+ if (ret == -1)
+ ret = console_get_userpass_input(p, in, inlen);
+ return ret;
+}
+
+/*
+ * Handle data from a local tty in PARMRK format.
+ */
+static void from_tty(void *vbuf, unsigned len)
+{
+ char *p, *q, *end, *buf = vbuf;
+ static enum {NORMAL, FF, FF00} state = NORMAL;
+
+ p = buf; end = buf + len;
+ while (p < end) {
+ switch (state) {
+ case NORMAL:
+ if (*p == '\xff') {
+ p++;
+ state = FF;
+ } else {
+ q = memchr(p, '\xff', end - p);
+ if (q == NULL) q = end;
+ back->send(backhandle, p, q - p);
+ p = q;
+ }
+ break;
+ case FF:
+ if (*p == '\xff') {
+ back->send(backhandle, p, 1);
+ p++;
+ state = NORMAL;
+ } else if (*p == '\0') {
+ p++;
+ state = FF00;
+ } else abort();
+ break;
+ case FF00:
+ if (*p == '\0') {
+ back->special(backhandle, TS_BRK);
+ } else {
+ /*
+ * Pretend that PARMRK wasn't set. This involves
+ * faking what INPCK and IGNPAR would have done if
+ * we hadn't overridden them. Unfortunately, we
+ * can't do this entirely correctly because INPCK
+ * distinguishes between framing and parity
+ * errors, but PARMRK format represents both in
+ * the same way. We assume that parity errors are
+ * more common than framing errors, and hence
+ * treat all input errors as being subject to
+ * INPCK.
+ */
+ if (orig_termios.c_iflag & INPCK) {
+ /* If IGNPAR is set, we throw away the character. */
+ if (!(orig_termios.c_iflag & IGNPAR)) {
+ /* PE/FE get passed on as NUL. */
+ *p = 0;
+ back->send(backhandle, p, 1);
+ }
+ } else {
+ /* INPCK not set. Assume we got a parity error. */
+ back->send(backhandle, p, 1);
+ }
+ }
+ p++;
+ state = NORMAL;
+ }
+ }
+}
+
+int signalpipe[2];
+
+void sigwinch(int signum)
+{
+ if (write(signalpipe[1], "x", 1) <= 0)
+ /* not much we can do about it */;
+}
+
+/*
+ * In Plink our selects are synchronous, so these functions are
+ * empty stubs.
+ */
+int uxsel_input_add(int fd, int rwx) { return 0; }
+void uxsel_input_remove(int id) { }
+
+/*
+ * Short description of parameters.
+ */
+static void usage(void)
+{
+ printf("PuTTY Link: command-line connection utility\n");
+ printf("%s\n", ver);
+ printf("Usage: plink [options] [user@]host [command]\n");
+ printf(" (\"host\" can also be a PuTTY saved session name)\n");
+ printf("Options:\n");
+ printf(" -V print version information and exit\n");
+ printf(" -pgpfp print PGP key fingerprints and exit\n");
+ printf(" -v show verbose messages\n");
+ printf(" -load sessname Load settings from saved session\n");
+ printf(" -ssh -telnet -rlogin -raw -serial\n");
+ printf(" force use of a particular protocol\n");
+ printf(" -P port connect to specified port\n");
+ printf(" -l user connect with specified username\n");
+ printf(" -batch disable all interactive prompts\n");
+ printf("The following options only apply to SSH connections:\n");
+ printf(" -pw passw login with specified password\n");
+ printf(" -D [listen-IP:]listen-port\n");
+ printf(" Dynamic SOCKS-based port forwarding\n");
+ printf(" -L [listen-IP:]listen-port:host:port\n");
+ printf(" Forward local port to remote address\n");
+ printf(" -R [listen-IP:]listen-port:host:port\n");
+ printf(" Forward remote port to local address\n");
+ printf(" -X -x enable / disable X11 forwarding\n");
+ printf(" -A -a enable / disable agent forwarding\n");
+ printf(" -t -T enable / disable pty allocation\n");
+ printf(" -1 -2 force use of particular protocol version\n");
+ printf(" -4 -6 force use of IPv4 or IPv6\n");
+ printf(" -C enable compression\n");
+ printf(" -i key private key file for authentication\n");
+ printf(" -noagent disable use of Pageant\n");
+ printf(" -agent enable use of Pageant\n");
+ printf(" -m file read remote command(s) from file\n");
+ printf(" -s remote command is an SSH subsystem (SSH-2 only)\n");
+ printf(" -N don't start a shell/command (SSH-2 only)\n");
+ printf(" -nc host:port\n");
+ printf(" open tunnel in place of session (SSH-2 only)\n");
+ printf(" -sercfg configuration-string (e.g. 19200,8,n,1,X)\n");
+ printf(" Specify the serial configuration (serial only)\n");
+ exit(1);
+}
+
+static void version(void)
+{
+ printf("plink: %s\n", ver);
+ exit(1);
+}
+
+int main(int argc, char **argv)
+{
+ int sending;
+ int portnumber = -1;
+ int *fdlist;
+ int fd;
+ int i, fdcount, fdsize, fdstate;
+ int connopen;
+ int exitcode;
+ int errors;
+ int use_subsystem = 0;
+ int got_host = FALSE;
+ long now;
+
+ 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;
+
+ flags = FLAG_STDERR | FLAG_STDERR_TTY;
+
+ stderr_tty_init();
+ /*
+ * Process the command line.
+ */
+ do_defaults(NULL, &cfg);
+ loaded_session = FALSE;
+ default_protocol = cfg.protocol;
+ default_port = cfg.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;
+ }
+ }
+ }
+ while (--argc) {
+ char *p = *++argv;
+ if (*p == '-') {
+ int ret = cmdline_process_param(p, (argc > 1 ? argv[1] : NULL),
+ 1, &cfg);
+ if (ret == -2) {
+ fprintf(stderr,
+ "plink: option \"%s\" requires an argument\n", p);
+ errors = 1;
+ } else if (ret == 2) {
+ --argc, ++argv;
+ } 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. */
+ use_subsystem = 1;
+ } else if (!strcmp(p, "-V")) {
+ version();
+ } else if (!strcmp(p, "-pgpfp")) {
+ pgp_fingerprints();
+ exit(1);
+ } else if (!strcmp(p, "-o")) {
+ if (argc <= 1) {
+ fprintf(stderr,
+ "plink: option \"-o\" requires an argument\n");
+ errors = 1;
+ } else {
+ --argc;
+ provide_xrm_string(*++argv);
+ }
+ } else {
+ fprintf(stderr, "plink: unknown option \"%s\"\n", p);
+ errors = 1;
+ }
+ } else if (*p) {
+ if (!cfg_launchable(&cfg) || !(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.
+ */
+ if (!strncmp(q, "telnet:", 7)) {
+ char c;
+
+ q += 7;
+ if (q[0] == '/' && q[1] == '/')
+ q += 2;
+ cfg.protocol = PROT_TELNET;
+ p = q;
+ while (*p && *p != ':' && *p != '/')
+ p++;
+ c = *p;
+ if (*p)
+ *p++ = '\0';
+ if (c == ':')
+ cfg.port = atoi(p);
+ else
+ cfg.port = -1;
+ strncpy(cfg.host, q, sizeof(cfg.host) - 1);
+ cfg.host[sizeof(cfg.host) - 1] = '\0';
+ got_host = TRUE;
+ } else {
+ char *r, *user, *host;
+ /*
+ * Before we process the [user@]host string, we
+ * first check for the presence of a protocol
+ * prefix (a protocol name followed by ",").
+ */
+ r = strchr(p, ',');
+ if (r) {
+ const Backend *b;
+ *r = '\0';
+ b = backend_from_name(p);
+ if (b) {
+ default_protocol = cfg.protocol = b->protocol;
+ portnumber = b->default_port;
+ }
+ p = r + 1;
+ }
+
+ /*
+ * A nonzero length string followed by an @ is treated
+ * as a username. (We discount an _initial_ @.) The
+ * rest of the string (or the whole string if no @)
+ * is treated as a session name and/or hostname.
+ */
+ r = strrchr(p, '@');
+ if (r == p)
+ p++, r = NULL; /* discount initial @ */
+ if (r) {
+ *r++ = '\0';
+ user = p, host = r;
+ } else {
+ user = NULL, host = p;
+ }
+
+ /*
+ * 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)) {
+ /* 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;
+ got_host = TRUE;
+ } else {
+ cfg = cfg2;
+ loaded_session = TRUE;
+ }
+ }
+
+ if (user) {
+ /* Patch in specified username. */
+ strncpy(cfg.username, user,
+ sizeof(cfg.username) - 1);
+ cfg.username[sizeof(cfg.username) - 1] = '\0';
+ }
+
+ }
+ } else {
+ char *command;
+ int cmdlen, cmdsize;
+ cmdlen = cmdsize = 0;
+ command = NULL;
+
+ while (argc) {
+ while (*p) {
+ if (cmdlen >= cmdsize) {
+ cmdsize = cmdlen + 512;
+ command = sresize(command, cmdsize, char);
+ }
+ command[cmdlen++]=*p++;
+ }
+ if (cmdlen >= cmdsize) {
+ cmdsize = cmdlen + 512;
+ command = sresize(command, cmdsize, char);
+ }
+ 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 */
+
+ break; /* done with cmdline */
+ }
+ }
+ }
+
+ if (errors)
+ return 1;
+
+ if (!cfg_launchable(&cfg) || !(got_host || loaded_session)) {
+ usage();
+ }
+
+ /*
+ * 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));
+ }
+ }
+
+ /*
+ * Perform command-line overrides on session configuration.
+ */
+ cmdline_run_saved(&cfg);
+
+ /*
+ * 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)
+ 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);
+ if (back == NULL) {
+ fprintf(stderr,
+ "Internal fault: Unsupported protocol found\n");
+ return 1;
+ }
+
+ /*
+ * Select port.
+ */
+ if (portnumber != -1)
+ cfg.port = portnumber;
+
+ /*
+ * Set up the pipe we'll use to tell us about SIGWINCH.
+ */
+ if (pipe(signalpipe) < 0) {
+ perror("pipe");
+ exit(1);
+ }
+ putty_signal(SIGWINCH, sigwinch);
+
+ 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;
+ /*
+ * Start up the connection.
+ */
+ logctx = log_init(NULL, &cfg);
+ 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);
+
+ error = back->init(NULL, &backhandle, &cfg, cfg.host, cfg.port,
+ &realhost, nodelay, cfg.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);
+ sfree(realhost);
+ }
+ connopen = 1;
+
+ /*
+ * Set up the initial console mode. We don't care if this call
+ * fails, because we know we aren't necessarily running in a
+ * console.
+ */
+ local_tty = (tcgetattr(STDIN_FILENO, &orig_termios) == 0);
+ atexit(cleanup_termios);
+ ldisc_update(NULL, 1, 1);
+ sending = FALSE;
+ now = GETTICKCOUNT();
+
+ while (1) {
+ fd_set rset, wset, xset;
+ int maxfd;
+ int rwx;
+ int ret;
+
+ FD_ZERO(&rset);
+ FD_ZERO(&wset);
+ FD_ZERO(&xset);
+ maxfd = 0;
+
+ FD_SET_MAX(signalpipe[0], maxfd, rset);
+
+ if (connopen && !sending &&
+ back->connected(backhandle) &&
+ back->sendok(backhandle) &&
+ back->sendbuffer(backhandle) < MAX_STDIN_BACKLOG) {
+ /* If we're OK to send, then try to read from stdin. */
+ FD_SET_MAX(STDIN_FILENO, maxfd, rset);
+ }
+
+ if (bufchain_size(&stdout_data) > 0) {
+ /* If we have data for stdout, try to write to stdout. */
+ FD_SET_MAX(STDOUT_FILENO, maxfd, wset);
+ }
+
+ if (bufchain_size(&stderr_data) > 0) {
+ /* If we have data for stderr, try to write to stderr. */
+ FD_SET_MAX(STDERR_FILENO, maxfd, wset);
+ }
+
+ /* Count the currently active fds. */
+ i = 0;
+ for (fd = first_fd(&fdstate, &rwx); fd >= 0;
+ fd = next_fd(&fdstate, &rwx)) i++;
+
+ /* Expand the fdlist buffer if necessary. */
+ if (i > fdsize) {
+ fdsize = i + 16;
+ fdlist = sresize(fdlist, fdsize, int);
+ }
+
+ /*
+ * Add all currently open fds to the select sets, and store
+ * them in fdlist as well.
+ */
+ fdcount = 0;
+ for (fd = first_fd(&fdstate, &rwx); fd >= 0;
+ fd = next_fd(&fdstate, &rwx)) {
+ fdlist[fdcount++] = fd;
+ if (rwx & 1)
+ FD_SET_MAX(fd, maxfd, rset);
+ if (rwx & 2)
+ FD_SET_MAX(fd, maxfd, wset);
+ if (rwx & 4)
+ FD_SET_MAX(fd, maxfd, xset);
+ }
+
+ do {
+ long next, ticks;
+ struct timeval tv, *ptv;
+
+ if (run_timers(now, &next)) {
+ ticks = next - GETTICKCOUNT();
+ if (ticks < 0) ticks = 0; /* just in case */
+ 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);
+
+ if (ret < 0) {
+ perror("select");
+ exit(1);
+ }
+
+ for (i = 0; i < fdcount; i++) {
+ fd = fdlist[i];
+ /*
+ * We must process exceptional notifications before
+ * ordinary readability ones, or we may go straight
+ * past the urgent marker.
+ */
+ if (FD_ISSET(fd, &xset))
+ select_result(fd, 4);
+ if (FD_ISSET(fd, &rset))
+ select_result(fd, 1);
+ if (FD_ISSET(fd, &wset))
+ select_result(fd, 2);
+ }
+
+ if (FD_ISSET(signalpipe[0], &rset)) {
+ 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)
+ back->size(backhandle, size.ws_col, size.ws_row);
+ }
+
+ if (FD_ISSET(STDIN_FILENO, &rset)) {
+ char buf[4096];
+ int ret;
+
+ if (connopen && back->connected(backhandle)) {
+ ret = read(STDIN_FILENO, buf, sizeof(buf));
+ if (ret < 0) {
+ perror("stdin: read");
+ exit(1);
+ } else if (ret == 0) {
+ back->special(backhandle, TS_EOF);
+ sending = FALSE; /* send nothing further after this */
+ } else {
+ if (local_tty)
+ from_tty(buf, ret);
+ else
+ back->send(backhandle, buf, ret);
+ }
+ }
+ }
+
+ if (FD_ISSET(STDOUT_FILENO, &wset)) {
+ back->unthrottle(backhandle, try_output(FALSE));
+ }
+
+ if (FD_ISSET(STDERR_FILENO, &wset)) {
+ back->unthrottle(backhandle, try_output(TRUE));
+ }
+
+ if ((!connopen || !back->connected(backhandle)) &&
+ bufchain_size(&stdout_data) == 0 &&
+ bufchain_size(&stderr_data) == 0)
+ break; /* we closed the connection */
+ }
+ exitcode = back->exitcode(backhandle);
+ if (exitcode < 0) {
+ fprintf(stderr, "Remote process exit code unavailable\n");
+ exitcode = 1; /* this is an error condition */
+ }
+ cleanup_exit(exitcode);
+ return exitcode; /* shouldn't happen, but placates gcc */
+}
--- /dev/null
+/*
+ * Printing interface for PuTTY.
+ */
+
+#include <assert.h>
+#include <stdio.h>
+#include "putty.h"
+
+struct printer_job_tag {
+ FILE *fp;
+};
+
+printer_job *printer_start_job(char *printer)
+{
+ printer_job *ret = snew(printer_job);
+ /*
+ * On Unix, we treat the printer string as the name of a
+ * command to pipe to - typically lpr, of course.
+ */
+ ret->fp = popen(printer, "w");
+ if (!ret->fp) {
+ sfree(ret);
+ ret = NULL;
+ }
+ return ret;
+}
+
+void printer_job_data(printer_job *pj, void *data, int len)
+{
+ if (!pj)
+ return;
+
+ if (fwrite(data, 1, len, pj->fp) < len)
+ /* ignore */;
+}
+
+void printer_finish_job(printer_job *pj)
+{
+ if (!pj)
+ return;
+
+ pclose(pj->fp);
+ sfree(pj);
+}
+
+/*
+ * There's no sensible way to enumerate printers under Unix, since
+ * practically any valid Unix command is a valid printer :-) So
+ * these are useless stub functions, and uxcfg.c will disable the
+ * drop-down list in the printer configurer.
+ */
+printer_enum *printer_start_enum(int *nprinters_ptr) {
+ *nprinters_ptr = 0;
+ return NULL;
+}
+char *printer_get_name(printer_enum *pe, int i) { return NULL;
+}
+void printer_finish_enum(printer_enum *pe) { }
--- /dev/null
+/*
+ * uxproxy.c: Unix implementation of platform_new_connection(),
+ * supporting an OpenSSH-like proxy command.
+ */
+
+#include <stdio.h>
+#include <assert.h>
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#define DEFINE_PLUG_METHOD_MACROS
+#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 */
+
+ int to_cmd, from_cmd; /* fds */
+
+ char *error;
+
+ Plug plug;
+
+ bufchain pending_output_data;
+ bufchain pending_input_data;
+
+ void *privptr;
+};
+
+static int localproxy_select_result(int fd, int event);
+
+/*
+ * Trees to look up the pipe fds in.
+ */
+static tree234 *localproxy_by_fromfd, *localproxy_by_tofd;
+static int localproxy_fromfd_cmp(void *av, void *bv)
+{
+ Local_Proxy_Socket a = (Local_Proxy_Socket)av;
+ Local_Proxy_Socket b = (Local_Proxy_Socket)bv;
+ if (a->from_cmd < b->from_cmd)
+ return -1;
+ if (a->from_cmd > b->from_cmd)
+ return +1;
+ return 0;
+}
+static int localproxy_fromfd_find(void *av, void *bv)
+{
+ int a = *(int *)av;
+ Local_Proxy_Socket b = (Local_Proxy_Socket)bv;
+ if (a < b->from_cmd)
+ return -1;
+ if (a > b->from_cmd)
+ return +1;
+ return 0;
+}
+static int localproxy_tofd_cmp(void *av, void *bv)
+{
+ Local_Proxy_Socket a = (Local_Proxy_Socket)av;
+ Local_Proxy_Socket b = (Local_Proxy_Socket)bv;
+ if (a->to_cmd < b->to_cmd)
+ return -1;
+ if (a->to_cmd > b->to_cmd)
+ return +1;
+ return 0;
+}
+static int localproxy_tofd_find(void *av, void *bv)
+{
+ int a = *(int *)av;
+ Local_Proxy_Socket b = (Local_Proxy_Socket)bv;
+ if (a < b->to_cmd)
+ return -1;
+ if (a > b->to_cmd)
+ return +1;
+ return 0;
+}
+
+/* basic proxy socket functions */
+
+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;
+
+ 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);
+}
+
+static int localproxy_try_send(Local_Proxy_Socket ps)
+{
+ int sent = 0;
+
+ while (bufchain_size(&ps->pending_output_data) > 0) {
+ void *data;
+ int len, ret;
+
+ bufchain_prefix(&ps->pending_output_data, &data, &len);
+ ret = write(ps->to_cmd, data, len);
+ if (ret < 0 && errno != EWOULDBLOCK) {
+ /* We're inside the Unix frontend here, so we know
+ * that the frontend handle is unnecessary. */
+ logevent(NULL, strerror(errno));
+ fatalbox("%s", strerror(errno));
+ } else if (ret <= 0) {
+ break;
+ } else {
+ bufchain_consume(&ps->pending_output_data, ret);
+ sent += ret;
+ }
+ }
+
+ if (bufchain_size(&ps->pending_output_data) == 0)
+ uxsel_del(ps->to_cmd);
+ else
+ uxsel_set(ps->to_cmd, 2, localproxy_select_result);
+
+ return sent;
+}
+
+static int sk_localproxy_write (Socket s, const char *data, int len)
+{
+ Local_Proxy_Socket ps = (Local_Proxy_Socket) s;
+
+ bufchain_add(&ps->pending_output_data, data, len);
+
+ localproxy_try_send(ps);
+
+ return bufchain_size(&ps->pending_output_data);
+}
+
+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;
+
+ if (is_frozen)
+ uxsel_del(ps->from_cmd);
+ else
+ uxsel_set(ps->from_cmd, 1, localproxy_select_result);
+}
+
+static const char * sk_localproxy_socket_error (Socket s)
+{
+ Local_Proxy_Socket ps = (Local_Proxy_Socket) s;
+ return ps->error;
+}
+
+static int localproxy_select_result(int fd, int event)
+{
+ Local_Proxy_Socket s;
+ char buf[20480];
+ int ret;
+
+ if (!(s = find234(localproxy_by_fromfd, &fd, localproxy_fromfd_find)) &&
+ !(s = find234(localproxy_by_tofd, &fd, localproxy_tofd_find)) )
+ return 1; /* boggle */
+
+ if (event == 1) {
+ assert(fd == s->from_cmd);
+ ret = read(fd, buf, sizeof(buf));
+ if (ret < 0) {
+ return plug_closing(s->plug, strerror(errno), errno, 0);
+ } else if (ret == 0) {
+ return plug_closing(s->plug, NULL, 0, 0);
+ } else {
+ return plug_receive(s->plug, 0, buf, ret);
+ }
+ } else if (event == 2) {
+ assert(fd == s->to_cmd);
+ if (localproxy_try_send(s))
+ plug_sent(s->plug, bufchain_size(&s->pending_output_data));
+ return 1;
+ }
+
+ return 1;
+}
+
+Socket platform_new_connection(SockAddr addr, char *hostname,
+ int port, int privport,
+ int oobinline, int nodelay, int keepalive,
+ Plug plug, const Config *cfg)
+{
+ 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;
+ int to_cmd_pipe[2], from_cmd_pipe[2], pid;
+
+ if (cfg->proxy_type != PROXY_CMD)
+ return NULL;
+
+ cmd = format_telnet_command(addr, port, cfg);
+
+ ret = snew(struct Socket_localproxy_tag);
+ ret->fn = &socket_fn_table;
+ ret->plug = plug;
+ ret->error = NULL;
+
+ bufchain_init(&ret->pending_input_data);
+ bufchain_init(&ret->pending_output_data);
+
+ /*
+ * Create the pipes to the proxy command, and spawn the proxy
+ * command process.
+ */
+ if (pipe(to_cmd_pipe) < 0 ||
+ pipe(from_cmd_pipe) < 0) {
+ ret->error = dupprintf("pipe: %s", strerror(errno));
+ 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));
+ 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);
+ execl("/bin/sh", "sh", "-c", cmd, (void *)NULL);
+ _exit(255);
+ }
+
+ sfree(cmd);
+
+ close(to_cmd_pipe[0]);
+ close(from_cmd_pipe[1]);
+
+ ret->to_cmd = to_cmd_pipe[1];
+ ret->from_cmd = from_cmd_pipe[0];
+
+ if (!localproxy_by_fromfd)
+ localproxy_by_fromfd = newtree234(localproxy_fromfd_cmp);
+ if (!localproxy_by_tofd)
+ localproxy_by_tofd = newtree234(localproxy_tofd_cmp);
+
+ add234(localproxy_by_fromfd, ret);
+ add234(localproxy_by_tofd, ret);
+
+ uxsel_set(ret->from_cmd, 1, localproxy_select_result);
+
+ /* We are responsible for this and don't need it any more */
+ sk_addr_free(addr);
+
+ return (Socket) ret;
+}
--- /dev/null
+/*
+ * pterm main program.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "putty.h"
+
+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)
+{
+ return &pty_backend;
+}
+
+int cfgbox(Config *cfg)
+{
+ /*
+ * 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;
+ return 1;
+}
+
+void cleanup_exit(int code)
+{
+ exit(code);
+}
+
+int process_nonoption_arg(char *arg, Config *cfg, int *allow_launch)
+{
+ return 0; /* pterm doesn't have any. */
+}
+
+char *make_default_wintitle(char *hostname)
+{
+ return dupstr("pterm");
+}
+
+int main(int argc, char **argv)
+{
+ extern int pt_main(int argc, char **argv);
+ extern void pty_pre_init(void); /* declared in pty.c */
+
+ cmdline_tooltype = TOOLTYPE_NONNETWORK;
+ default_protocol = -1;
+
+ pty_pre_init();
+
+ return pt_main(argc, argv);
+}
--- /dev/null
+/*
+ * Pseudo-tty backend for pterm.
+ */
+
+#define _GNU_SOURCE
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <signal.h>
+#include <assert.h>
+#include <fcntl.h>
+#include <termios.h>
+#include <grp.h>
+#include <utmp.h>
+#include <pwd.h>
+#include <time.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <sys/ioctl.h>
+#include <errno.h>
+
+#include "putty.h"
+#include "tree234.h"
+
+#ifndef OMIT_UTMP
+#include <utmpx.h>
+#endif
+
+#ifndef FALSE
+#define FALSE 0
+#endif
+#ifndef TRUE
+#define TRUE 1
+#endif
+
+/* updwtmpx() needs the name of the wtmp file. Try to find it. */
+#ifndef WTMPX_FILE
+#ifdef _PATH_WTMPX
+#define WTMPX_FILE _PATH_WTMPX
+#else
+#define WTMPX_FILE "/var/log/wtmpx"
+#endif
+#endif
+
+#ifndef LASTLOG_FILE
+#ifdef _PATH_LASTLOG
+#define LASTLOG_FILE _PATH_LASTLOG
+#else
+#define LASTLOG_FILE "/var/log/lastlog"
+#endif
+#endif
+
+/*
+ * Set up a default for vaguely sane systems. The idea is that if
+ * OMIT_UTMP is not defined, then at least one of the symbols which
+ * enable particular forms of utmp processing should be, if only so
+ * that a link error can warn you that you should have defined
+ * OMIT_UTMP if you didn't want any. Currently HAVE_PUTUTLINE is
+ * the only such symbol.
+ */
+#ifndef OMIT_UTMP
+#if !defined HAVE_PUTUTLINE
+#define HAVE_PUTUTLINE
+#endif
+#endif
+
+typedef struct pty_tag *Pty;
+
+/*
+ * The pty_signal_pipe, along with the SIGCHLD handler, must be
+ * process-global rather than session-specific.
+ */
+static int pty_signal_pipe[2] = { -1, -1 }; /* obviously bogus initial val */
+
+struct pty_tag {
+ Config cfg;
+ int master_fd, slave_fd;
+ void *frontend;
+ char name[FILENAME_MAX];
+ pid_t child_pid;
+ int term_width, term_height;
+ int child_dead, finished;
+ int exit_code;
+ bufchain output_data;
+};
+
+/*
+ * We store our pty backends in a tree sorted by master fd, so that
+ * when we get an uxsel notification we know which backend instance
+ * is the owner of the pty that caused it.
+ */
+static int pty_compare_by_fd(void *av, void *bv)
+{
+ Pty a = (Pty)av;
+ Pty b = (Pty)bv;
+
+ if (a->master_fd < b->master_fd)
+ return -1;
+ else if (a->master_fd > b->master_fd)
+ return +1;
+ return 0;
+}
+
+static int pty_find_by_fd(void *av, void *bv)
+{
+ int a = *(int *)av;
+ Pty b = (Pty)bv;
+
+ if (a < b->master_fd)
+ return -1;
+ else if (a > b->master_fd)
+ return +1;
+ return 0;
+}
+
+static tree234 *ptys_by_fd = NULL;
+
+/*
+ * We also have a tree sorted by child pid, so that when we wait()
+ * in response to the signal we know which backend instance is the
+ * owner of the process that caused the signal.
+ */
+static int pty_compare_by_pid(void *av, void *bv)
+{
+ Pty a = (Pty)av;
+ Pty b = (Pty)bv;
+
+ if (a->child_pid < b->child_pid)
+ return -1;
+ else if (a->child_pid > b->child_pid)
+ return +1;
+ return 0;
+}
+
+static int pty_find_by_pid(void *av, void *bv)
+{
+ pid_t a = *(pid_t *)av;
+ Pty b = (Pty)bv;
+
+ if (a < b->child_pid)
+ return -1;
+ else if (a > b->child_pid)
+ return +1;
+ return 0;
+}
+
+static tree234 *ptys_by_pid = NULL;
+
+/*
+ * If we are using pty_pre_init(), it will need to have already
+ * allocated a pty structure, which we must then return from
+ * pty_init() rather than allocating a new one. Here we store that
+ * structure between allocation and use.
+ *
+ * Note that although most of this module is entirely capable of
+ * handling multiple ptys in a single process, pty_pre_init() is
+ * fundamentally _dependent_ on there being at most one pty per
+ * process, so the normal static-data constraints don't apply.
+ *
+ * Likewise, since utmp is only used via pty_pre_init, it too must
+ * be single-instance, so we can declare utmp-related variables
+ * here.
+ */
+static Pty single_pty = NULL;
+
+#ifndef OMIT_UTMP
+static pid_t pty_utmp_helper_pid;
+static int pty_utmp_helper_pipe;
+static int pty_stamped_utmp;
+static struct utmpx utmp_entry;
+#endif
+
+/*
+ * pty_argv is a grievous hack to allow a proper argv to be passed
+ * through from the Unix command line. Again, it doesn't really
+ * make sense outside a one-pty-per-process setup.
+ */
+char **pty_argv;
+
+static void pty_close(Pty pty);
+static void pty_try_write(Pty pty);
+
+#ifndef OMIT_UTMP
+static void setup_utmp(char *ttyname, char *location)
+{
+#ifdef HAVE_LASTLOG
+ struct lastlog lastlog_entry;
+ FILE *lastlog;
+#endif
+ struct passwd *pw;
+ struct timeval tv;
+
+ pw = getpwuid(getuid());
+ memset(&utmp_entry, 0, sizeof(utmp_entry));
+ utmp_entry.ut_type = USER_PROCESS;
+ utmp_entry.ut_pid = getpid();
+ strncpy(utmp_entry.ut_line, ttyname+5, lenof(utmp_entry.ut_line));
+ strncpy(utmp_entry.ut_id, ttyname+8, lenof(utmp_entry.ut_id));
+ strncpy(utmp_entry.ut_user, pw->pw_name, lenof(utmp_entry.ut_user));
+ strncpy(utmp_entry.ut_host, location, lenof(utmp_entry.ut_host));
+ /*
+ * Apparently there are some architectures where (struct
+ * utmpx).ut_tv is not essentially struct timeval (e.g. Linux
+ * amd64). Hence the temporary.
+ */
+ gettimeofday(&tv, NULL);
+ utmp_entry.ut_tv.tv_sec = tv.tv_sec;
+ utmp_entry.ut_tv.tv_usec = tv.tv_usec;
+
+ setutxent();
+ pututxline(&utmp_entry);
+ endutxent();
+
+ updwtmpx(WTMPX_FILE, &utmp_entry);
+
+#ifdef HAVE_LASTLOG
+ memset(&lastlog_entry, 0, sizeof(lastlog_entry));
+ strncpy(lastlog_entry.ll_line, ttyname+5, lenof(lastlog_entry.ll_line));
+ strncpy(lastlog_entry.ll_host, location, lenof(lastlog_entry.ll_host));
+ time(&lastlog_entry.ll_time);
+ if ((lastlog = fopen(LASTLOG_FILE, "r+")) != NULL) {
+ fseek(lastlog, sizeof(lastlog_entry) * getuid(), SEEK_SET);
+ fwrite(&lastlog_entry, 1, sizeof(lastlog_entry), lastlog);
+ fclose(lastlog);
+ }
+#endif
+
+ pty_stamped_utmp = 1;
+
+}
+
+static void cleanup_utmp(void)
+{
+ struct timeval tv;
+
+ if (!pty_stamped_utmp)
+ return;
+
+ utmp_entry.ut_type = DEAD_PROCESS;
+ memset(utmp_entry.ut_user, 0, lenof(utmp_entry.ut_user));
+ gettimeofday(&tv, NULL);
+ utmp_entry.ut_tv.tv_sec = tv.tv_sec;
+ utmp_entry.ut_tv.tv_usec = tv.tv_usec;
+
+ updwtmpx(WTMPX_FILE, &utmp_entry);
+
+ memset(utmp_entry.ut_line, 0, lenof(utmp_entry.ut_line));
+ utmp_entry.ut_tv.tv_sec = 0;
+ utmp_entry.ut_tv.tv_usec = 0;
+
+ setutxent();
+ pututxline(&utmp_entry);
+ endutxent();
+
+ pty_stamped_utmp = 0; /* ensure we never double-cleanup */
+}
+#endif
+
+static void sigchld_handler(int signum)
+{
+ if (write(pty_signal_pipe[1], "x", 1) <= 0)
+ /* not much we can do about it */;
+}
+
+#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)
+{
+ if (pty->slave_fd < 0) {
+ pty->slave_fd = open(pty->name, O_RDWR);
+ cloexec(pty->slave_fd);
+ }
+
+ return pty->slave_fd;
+}
+
+static void pty_open_master(Pty pty)
+{
+#ifdef BSD_PTYS
+ const char chars1[] = "pqrstuvwxyz";
+ const char chars2[] = "0123456789abcdef";
+ const char *p1, *p2;
+ char master_name[20];
+ struct group *gp;
+
+ for (p1 = chars1; *p1; p1++)
+ for (p2 = chars2; *p2; p2++) {
+ sprintf(master_name, "/dev/pty%c%c", *p1, *p2);
+ pty->master_fd = open(master_name, O_RDWR);
+ if (pty->master_fd >= 0) {
+ if (geteuid() == 0 ||
+ access(master_name, R_OK | W_OK) == 0) {
+ /*
+ * We must also check at this point that we are
+ * able to open the slave side of the pty. We
+ * wouldn't want to allocate the wrong master,
+ * get all the way down to forking, and _then_
+ * find we're unable to open the slave.
+ */
+ strcpy(pty->name, master_name);
+ pty->name[5] = 't'; /* /dev/ptyXX -> /dev/ttyXX */
+
+ cloexec(pty->master_fd);
+
+ if (pty_open_slave(pty) >= 0 &&
+ access(pty->name, R_OK | W_OK) == 0)
+ goto got_one;
+ if (pty->slave_fd > 0)
+ close(pty->slave_fd);
+ pty->slave_fd = -1;
+ }
+ close(pty->master_fd);
+ }
+ }
+
+ /* If we get here, we couldn't get a tty at all. */
+ fprintf(stderr, "pterm: unable to open a pseudo-terminal device\n");
+ exit(1);
+
+ got_one:
+
+ /* 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);
+
+ if (pty->master_fd < 0) {
+ perror("/dev/ptmx: open");
+ exit(1);
+ }
+
+ if (grantpt(pty->master_fd) < 0) {
+ perror("grantpt");
+ exit(1);
+ }
+
+ if (unlockpt(pty->master_fd) < 0) {
+ perror("unlockpt");
+ exit(1);
+ }
+
+ cloexec(pty->master_fd);
+
+ 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);
+ }
+
+ if (!ptys_by_fd)
+ ptys_by_fd = newtree234(pty_compare_by_fd);
+ add234(ptys_by_fd, pty);
+}
+
+/*
+ * Pre-initialisation. This is here to get around the fact that GTK
+ * doesn't like being run in setuid/setgid programs (probably
+ * sensibly). So before we initialise GTK - and therefore before we
+ * even process the command line - we check to see if we're running
+ * set[ug]id. If so, we open our pty master _now_, chown it as
+ * necessary, and drop privileges. We can always close it again
+ * later. If we're potentially going to be doing utmp as well, we
+ * also fork off a utmp helper process and communicate with it by
+ * means of a pipe; the utmp helper will keep privileges in order
+ * to clean up utmp when we exit (i.e. when its end of our pipe
+ * closes).
+ */
+void pty_pre_init(void)
+{
+ Pty pty;
+
+#ifndef OMIT_UTMP
+ pid_t pid;
+ int pipefd[2];
+#endif
+
+ pty = single_pty = snew(struct pty_tag);
+ 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);
+ pty->master_fd = pty->slave_fd = -1;
+#ifndef OMIT_UTMP
+ 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);
+#endif
+#ifdef SIGPROF
+ putty_signal(SIGPROF, fatal_sig_handler);
+#endif
+#ifdef SIGSYS
+ putty_signal(SIGSYS, fatal_sig_handler);
+#endif
+#ifdef SIGTRAP
+ putty_signal(SIGTRAP, fatal_sig_handler);
+#endif
+#ifdef SIGVTALRM
+ putty_signal(SIGVTALRM, fatal_sig_handler);
+#endif
+#ifdef SIGXCPU
+ putty_signal(SIGXCPU, fatal_sig_handler);
+#endif
+#ifdef SIGXFSZ
+ 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
+
+ /* 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);
+#else
+ setgid(getgid());
+ setuid(getuid());
+#endif
+ }
+}
+
+int pty_real_select_result(Pty pty, int event, int status)
+{
+ char buf[4096];
+ int ret;
+ int finished = FALSE;
+
+ if (event < 0) {
+ /*
+ * We've been called because our child process did
+ * something. `status' tells us what.
+ */
+ if ((WIFEXITED(status) || WIFSIGNALED(status))) {
+ /*
+ * The primary child process died. We could keep
+ * the terminal open for remaining subprocesses to
+ * output to, but conventional wisdom seems to feel
+ * that that's the Wrong Thing for an xterm-alike,
+ * so we bail out now (though we don't necessarily
+ * _close_ the window, depending on the state of
+ * Close On Exit). This would be easy enough to
+ * change or make configurable if necessary.
+ */
+ pty->exit_code = status;
+ pty->child_dead = TRUE;
+ del234(ptys_by_pid, pty);
+ finished = TRUE;
+ }
+ } else {
+ if (event == 1) {
+
+ ret = read(pty->master_fd, buf, sizeof(buf));
+
+ /*
+ * Clean termination condition is that either ret == 0, or ret
+ * < 0 and errno == EIO. Not sure why the latter, but it seems
+ * to happen. Boo.
+ */
+ if (ret == 0 || (ret < 0 && errno == EIO)) {
+ /*
+ * We assume a clean exit if the pty has closed but the
+ * actual child process hasn't. The only way I can
+ * imagine this happening is if it detaches itself from
+ * the pty and goes daemonic - in which case the
+ * expected usage model would precisely _not_ be for
+ * the pterm window to hang around!
+ */
+ finished = TRUE;
+ if (!pty->child_dead)
+ pty->exit_code = 0;
+ } else if (ret < 0) {
+ perror("read pty master");
+ exit(1);
+ } else if (ret > 0) {
+ from_backend(pty->frontend, 0, buf, ret);
+ }
+ } else if (event == 2) {
+ /*
+ * Attempt to send data down the pty.
+ */
+ pty_try_write(pty);
+ }
+ }
+
+ if (finished && !pty->finished) {
+ uxsel_del(pty->master_fd);
+ pty_close(pty);
+ pty->master_fd = -1;
+
+ pty->finished = TRUE;
+
+ /*
+ * 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)) {
+ char message[512];
+ 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
+ sprintf(message, "\r\n[pterm: process terminated on signal"
+ " %d]\r\n", WTERMSIG(pty->exit_code));
+#else
+ sprintf(message, "\r\n[pterm: process terminated on signal"
+ " %d (%.400s)]\r\n", WTERMSIG(pty->exit_code),
+ strsignal(WTERMSIG(pty->exit_code)));
+#endif
+ from_backend(pty->frontend, 0, message, strlen(message));
+ }
+
+ notify_remote_exit(pty->frontend);
+ }
+
+ return !finished;
+}
+
+int pty_select_result(int fd, int event)
+{
+ int ret = TRUE;
+ Pty pty;
+
+ if (fd == pty_signal_pipe[0]) {
+ pid_t pid;
+ int status;
+ char c[1];
+
+ if (read(pty_signal_pipe[0], c, 1) <= 0)
+ /* ignore error */;
+ /* ignore its value; it'll be `x' */
+
+ do {
+ pid = waitpid(-1, &status, WNOHANG);
+
+ pty = find234(ptys_by_pid, &pid, pty_find_by_pid);
+
+ if (pty)
+ ret = ret && pty_real_select_result(pty, -1, status);
+ } while (pid > 0);
+ } else {
+ pty = find234(ptys_by_fd, &fd, pty_find_by_fd);
+
+ if (pty)
+ ret = ret && pty_real_select_result(pty, event, 0);
+ }
+
+ return ret;
+}
+
+static void pty_uxsel_setup(Pty pty)
+{
+ int rwx;
+
+ rwx = 1; /* always want to read from pty */
+ if (bufchain_size(&pty->output_data))
+ rwx |= 2; /* might also want to write to it */
+ uxsel_set(pty->master_fd, rwx, pty_select_result);
+
+ /*
+ * In principle this only needs calling once for all pty
+ * backend instances, but it's simplest just to call it every
+ * time; uxsel won't mind.
+ */
+ uxsel_set(pty_signal_pipe[0], 1, pty_select_result);
+}
+
+/*
+ * Called to set up the pty.
+ *
+ * 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,
+ char *host, int port, char **realhost, int nodelay,
+ int keepalive)
+{
+ int slavefd;
+ pid_t pid, pgrp;
+#ifndef NOT_X_WINDOWS /* for Mac OS X native compilation */
+ long windowid;
+#endif
+ Pty pty;
+
+ if (single_pty) {
+ pty = single_pty;
+ } else {
+ pty = snew(struct pty_tag);
+ pty->master_fd = pty->slave_fd = -1;
+#ifndef OMIT_UTMP
+ pty_stamped_utmp = FALSE;
+#endif
+ }
+
+ 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;
+
+ if (pty->master_fd < 0)
+ pty_open_master(pty);
+
+ /*
+ * Set the backspace character to be whichever of ^H and ^? is
+ * specified by bksp_is_delete.
+ */
+ {
+ struct termios attrs;
+ tcgetattr(pty->master_fd, &attrs);
+ attrs.c_cc[VERASE] = cfg->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;
+ }
+ }
+#endif
+
+#ifndef NOT_X_WINDOWS /* for Mac OS X native compilation */
+ windowid = get_windowid(pty->frontend);
+#endif
+
+ /*
+ * Fork and execute the command.
+ */
+ pid = fork();
+ if (pid < 0) {
+ perror("fork");
+ exit(1);
+ }
+
+ if (pid == 0) {
+ /*
+ * We are the child.
+ */
+
+ slavefd = pty_open_slave(pty);
+ if (slavefd < 0) {
+ perror("slave pty: open");
+ _exit(1);
+ }
+
+ close(pty->master_fd);
+ fcntl(slavefd, F_SETFD, 0); /* don't close on exec */
+ dup2(slavefd, 0);
+ dup2(slavefd, 1);
+ dup2(slavefd, 2);
+ close(slavefd);
+ setsid();
+#ifdef TIOCSCTTY
+ ioctl(0, TIOCSCTTY, 1);
+#endif
+ pgrp = getpid();
+ tcsetpgrp(0, pgrp);
+ setpgid(pgrp, pgrp);
+ close(open(pty->name, O_WRONLY, 0));
+ setpgid(pgrp, pgrp);
+ {
+ char *term_env_var = dupprintf("TERM=%s", cfg->termtype);
+ putenv(term_env_var);
+ /* We mustn't free term_env_var, as putenv links it into the
+ * environment in place.
+ */
+ }
+#ifndef NOT_X_WINDOWS /* for Mac OS X native compilation */
+ {
+ char *windowid_env_var = dupprintf("WINDOWID=%ld", windowid);
+ putenv(windowid_env_var);
+ /* We mustn't free windowid_env_var, as putenv links it into the
+ * 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);
+ putenv(varval);
+ /*
+ * We must not free varval, since putenv links it
+ * into the environment _in place_. Weird, but
+ * there we go. Memory usage will be rationalised
+ * as soon as we exec anyway.
+ */
+ }
+ }
+
+ /*
+ * SIGINT, SIGQUIT and SIGPIPE may have been set to ignored by
+ * our parent, particularly by things like sh -c 'pterm &' and
+ * some window or session managers. SIGCHLD, meanwhile, was
+ * blocked during pt_main() startup. Reverse all this for our
+ * child process.
+ */
+ putty_signal(SIGINT, SIG_DFL);
+ putty_signal(SIGQUIT, SIG_DFL);
+ putty_signal(SIGPIPE, SIG_DFL);
+ block_signal(SIGCHLD, 0);
+ if (pty_argv)
+ execvp(pty_argv[0], pty_argv);
+ else {
+ char *shell = getenv("SHELL");
+ char *shellname;
+ if (cfg->login_shell) {
+ char *p = strrchr(shell, '/');
+ shellname = snewn(2+strlen(shell), char);
+ p = p ? p+1 : shell;
+ sprintf(shellname, "-%s", p);
+ } else
+ shellname = shell;
+ execl(getenv("SHELL"), shellname, (void *)NULL);
+ }
+
+ /*
+ * If we're here, exec has gone badly foom.
+ */
+ perror("exec");
+ _exit(127);
+ } else {
+ pty->child_pid = pid;
+ pty->child_dead = FALSE;
+ pty->finished = FALSE;
+ if (pty->slave_fd > 0)
+ close(pty->slave_fd);
+ if (!ptys_by_pid)
+ ptys_by_pid = newtree234(pty_compare_by_pid);
+ add234(ptys_by_pid, pty);
+ }
+
+ if (pty_signal_pipe[0] < 0) {
+ if (pipe(pty_signal_pipe) < 0) {
+ perror("pipe");
+ exit(1);
+ }
+ cloexec(pty_signal_pipe[0]);
+ cloexec(pty_signal_pipe[1]);
+ }
+ pty_uxsel_setup(pty);
+
+ *backend_handle = pty;
+
+ *realhost = dupprintf("\0");
+
+ return NULL;
+}
+
+static void pty_reconfig(void *handle, Config *cfg)
+{
+ 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 */
+}
+
+/*
+ * Stub routine (never called in pterm).
+ */
+static void pty_free(void *handle)
+{
+ Pty pty = (Pty)handle;
+
+ /* Either of these may fail `not found'. That's fine with us. */
+ del234(ptys_by_pid, pty);
+ del234(ptys_by_fd, pty);
+
+ sfree(pty);
+}
+
+static void pty_try_write(Pty pty)
+{
+ void *data;
+ int len, ret;
+
+ assert(pty->master_fd >= 0);
+
+ while (bufchain_size(&pty->output_data) > 0) {
+ bufchain_prefix(&pty->output_data, &data, &len);
+ ret = write(pty->master_fd, data, len);
+
+ if (ret < 0 && (errno == EWOULDBLOCK)) {
+ /*
+ * We've sent all we can for the moment.
+ */
+ break;
+ }
+ if (ret < 0) {
+ perror("write pty master");
+ exit(1);
+ }
+ bufchain_consume(&pty->output_data, ret);
+ }
+
+ pty_uxsel_setup(pty);
+}
+
+/*
+ * Called to send data down the pty.
+ */
+static int pty_send(void *handle, char *buf, int len)
+{
+ Pty pty = (Pty)handle;
+
+ if (pty->master_fd < 0)
+ return 0; /* ignore all writes if fd closed */
+
+ bufchain_add(&pty->output_data, buf, len);
+ pty_try_write(pty);
+
+ return bufchain_size(&pty->output_data);
+}
+
+static void pty_close(Pty pty)
+{
+ if (pty->master_fd >= 0) {
+ close(pty->master_fd);
+ pty->master_fd = -1;
+ }
+#ifndef OMIT_UTMP
+ if (pty_utmp_helper_pipe >= 0) {
+ close(pty_utmp_helper_pipe); /* this causes utmp to be cleaned up */
+ pty_utmp_helper_pipe = -1;
+ }
+#endif
+}
+
+/*
+ * Called to query the current socket sendability status.
+ */
+static int pty_sendbuffer(void *handle)
+{
+ /* Pty pty = (Pty)handle; */
+ return 0;
+}
+
+/*
+ * Called to set the size of the window
+ */
+static void pty_size(void *handle, int width, int height)
+{
+ Pty pty = (Pty)handle;
+ struct winsize size;
+
+ pty->term_width = width;
+ pty->term_height = height;
+
+ size.ws_row = (unsigned short)pty->term_height;
+ size.ws_col = (unsigned short)pty->term_width;
+ size.ws_xpixel = (unsigned short) pty->term_width *
+ font_dimension(pty->frontend, 0);
+ size.ws_ypixel = (unsigned short) pty->term_height *
+ font_dimension(pty->frontend, 1);
+ ioctl(pty->master_fd, TIOCSWINSZ, (void *)&size);
+ return;
+}
+
+/*
+ * Send special codes.
+ */
+static void pty_special(void *handle, Telnet_Special code)
+{
+ /* Pty pty = (Pty)handle; */
+ /* Do nothing! */
+ return;
+}
+
+/*
+ * Return a list of the special codes that make sense in this
+ * protocol.
+ */
+static const struct telnet_special *pty_get_specials(void *handle)
+{
+ /* Pty pty = (Pty)handle; */
+ /*
+ * Hmm. When I get round to having this actually usable, it
+ * might be quite nice to have the ability to deliver a few
+ * well chosen signals to the child process - SIGINT, SIGTERM,
+ * SIGKILL at least.
+ */
+ return NULL;
+}
+
+static int pty_connected(void *handle)
+{
+ /* Pty pty = (Pty)handle; */
+ return TRUE;
+}
+
+static int pty_sendok(void *handle)
+{
+ /* Pty pty = (Pty)handle; */
+ return 1;
+}
+
+static void pty_unthrottle(void *handle, int backlog)
+{
+ /* Pty pty = (Pty)handle; */
+ /* do nothing */
+}
+
+static int pty_ldisc(void *handle, int option)
+{
+ /* Pty pty = (Pty)handle; */
+ return 0; /* neither editing nor echoing */
+}
+
+static void pty_provide_ldisc(void *handle, void *ldisc)
+{
+ /* Pty pty = (Pty)handle; */
+ /* This is a stub. */
+}
+
+static void pty_provide_logctx(void *handle, void *logctx)
+{
+ /* Pty pty = (Pty)handle; */
+ /* This is a stub. */
+}
+
+static int pty_exitcode(void *handle)
+{
+ Pty pty = (Pty)handle;
+ if (!pty->finished)
+ return -1; /* not dead yet */
+ else
+ return pty->exit_code;
+}
+
+static int pty_cfg_info(void *handle)
+{
+ /* Pty pty = (Pty)handle; */
+ return 0;
+}
+
+Backend pty_backend = {
+ pty_init,
+ pty_free,
+ pty_reconfig,
+ pty_send,
+ pty_sendbuffer,
+ pty_size,
+ pty_special,
+ pty_get_specials,
+ pty_connected,
+ pty_exitcode,
+ pty_sendok,
+ pty_ldisc,
+ pty_provide_ldisc,
+ pty_provide_logctx,
+ pty_unthrottle,
+ pty_cfg_info,
+ "pty",
+ -1,
+ 0
+};
--- /dev/null
+/*
+ * Unix PuTTY main program.
+ */
+
+#include <stdio.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <unistd.h>
+#include <gdk/gdk.h>
+
+#include "putty.h"
+#include "storage.h"
+
+/*
+ * Stubs to avoid uxpty.c needing to be linked in.
+ */
+const int use_pty_argv = FALSE;
+char **pty_argv; /* never used */
+
+/*
+ * Clean up and exit.
+ */
+void cleanup_exit(int code)
+{
+ /*
+ * Clean up.
+ */
+ sk_cleanup();
+ random_save_seed();
+ exit(code);
+}
+
+Backend *select_backend(Config *cfg)
+{
+ Backend *back = backend_from_proto(cfg->protocol);
+ assert(back != NULL);
+ return back;
+}
+
+int cfgbox(Config *cfg)
+{
+ char *title = dupcat(appname, " Configuration", NULL);
+ int ret = do_config_box(title, cfg, 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)
+{
+ char *p, *q = arg;
+
+ if (got_host) {
+ /*
+ * 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);
+ assert(ret == 2);
+ } else if (!strncmp(q, "telnet:", 7)) {
+ /*
+ * If the hostname starts with "telnet:",
+ * set the protocol to Telnet and process
+ * the string as a Telnet URL.
+ */
+ char c;
+
+ q += 7;
+ if (q[0] == '/' && q[1] == '/')
+ q += 2;
+ cfg->protocol = PROT_TELNET;
+ p = q;
+ while (*p && *p != ':' && *p != '/')
+ p++;
+ c = *p;
+ if (*p)
+ *p++ = '\0';
+ if (c == ':')
+ cfg->port = atoi(p);
+ else
+ cfg->port = -1;
+ strncpy(cfg->host, q, sizeof(cfg->host) - 1);
+ cfg->host[sizeof(cfg->host) - 1] = '\0';
+ got_host = 1;
+ } else {
+ /*
+ * Otherwise, treat this argument as a host name.
+ */
+ 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';
+ got_host = 1;
+ }
+ if (got_host)
+ *allow_launch = TRUE;
+ return 1;
+}
+
+char *make_default_wintitle(char *hostname)
+{
+ return dupcat(hostname, " - ", appname, NULL);
+}
+
+/*
+ * X11-forwarding-related things suitable for Gtk app.
+ */
+
+char *platform_get_x_display(void) {
+ const char *display;
+ /* Try to take account of --display and what have you. */
+ if (!(display = gdk_get_display()))
+ /* fall back to traditional method */
+ display = getenv("DISPLAY");
+ return dupstr(display);
+}
+
+int main(int argc, char **argv)
+{
+ extern int pt_main(int argc, char **argv);
+ sk_init();
+ flags = FLAG_VERBOSE | FLAG_INTERACTIVE;
+ default_protocol = be_default_protocol;
+ /* Find the appropriate default port. */
+ {
+ Backend *b = backend_from_proto(default_protocol);
+ default_port = 0; /* illegal */
+ if (b)
+ default_port = b->default_port;
+ }
+ return pt_main(argc, argv);
+}
--- /dev/null
+/*
+ * uxsel.c
+ *
+ * This module is a sort of all-purpose interchange for file
+ * descriptors. At one end it talks to uxnet.c and pty.c and
+ * anything else which might have one or more fds that need
+ * select()-type things doing to them during an extended program
+ * run; at the other end it talks to pterm.c or uxplink.c or
+ * anything else which might have its own means of actually doing
+ * those select()-type things.
+ */
+
+#include <assert.h>
+
+#include "putty.h"
+#include "tree234.h"
+
+struct fd {
+ int fd;
+ int rwx; /* 4=except 2=write 1=read */
+ uxsel_callback_fn callback;
+ int id; /* for uxsel_input_remove */
+};
+
+static tree234 *fds;
+
+static int uxsel_fd_cmp(void *av, void *bv)
+{
+ struct fd *a = (struct fd *)av;
+ struct fd *b = (struct fd *)bv;
+ if (a->fd < b->fd)
+ return -1;
+ if (a->fd > b->fd)
+ return +1;
+ return 0;
+}
+static int uxsel_fd_findcmp(void *av, void *bv)
+{
+ int *a = (int *)av;
+ struct fd *b = (struct fd *)bv;
+ if (*a < b->fd)
+ return -1;
+ if (*a > b->fd)
+ return +1;
+ return 0;
+}
+
+void uxsel_init(void)
+{
+ fds = newtree234(uxsel_fd_cmp);
+}
+
+/*
+ * Here is the interface to fd-supplying modules. They supply an
+ * fd, a set of read/write/execute states, and a callback function
+ * for when the fd satisfies one of those states. Repeated calls to
+ * uxsel_set on the same fd are perfectly legal and serve to change
+ * the rwx state (typically you only want to select an fd for
+ * writing when you actually have pending data you want to write to
+ * it!).
+ */
+
+void uxsel_set(int fd, int rwx, uxsel_callback_fn callback)
+{
+ struct fd *newfd;
+
+ uxsel_del(fd);
+
+ if (rwx) {
+ newfd = snew(struct fd);
+ newfd->fd = fd;
+ newfd->rwx = rwx;
+ newfd->callback = callback;
+ newfd->id = uxsel_input_add(fd, rwx);
+ add234(fds, newfd);
+ }
+}
+
+void uxsel_del(int fd)
+{
+ struct fd *oldfd = find234(fds, &fd, uxsel_fd_findcmp);
+ if (oldfd) {
+ uxsel_input_remove(oldfd->id);
+ del234(fds, oldfd);
+ sfree(oldfd);
+ }
+}
+
+/*
+ * And here is the interface to select-functionality-supplying
+ * modules.
+ */
+
+int next_fd(int *state, int *rwx)
+{
+ struct fd *fd;
+ fd = index234(fds, (*state)++);
+ if (fd) {
+ *rwx = fd->rwx;
+ return fd->fd;
+ } else
+ return -1;
+}
+
+int first_fd(int *state, int *rwx)
+{
+ *state = 0;
+ return next_fd(state, rwx);
+}
+
+int select_result(int fd, int event)
+{
+ struct fd *fdstruct = find234(fds, &fd, uxsel_fd_findcmp);
+ /*
+ * Apparently this can sometimes be NULL. Can't see how, but I
+ * assume it means I need to ignore the event since it's on an
+ * fd I've stopped being interested in. Sigh.
+ */
+ if (fdstruct)
+ return fdstruct->callback(fd, event);
+ else
+ return 1;
+}
--- /dev/null
+/*
+ * Serial back end (Unix-specific).
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <limits.h>
+
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <termios.h>
+
+#include "putty.h"
+#include "tree234.h"
+
+#define SERIAL_MAX_BACKLOG 4096
+
+typedef struct serial_backend_data {
+ void *frontend;
+ int fd;
+ int finished;
+ int inbufsize;
+ bufchain output_data;
+} *Serial;
+
+/*
+ * We store our serial backends in a tree sorted by fd, so that
+ * when we get an uxsel notification we know which backend instance
+ * is the owner of the serial port that caused it.
+ */
+static int serial_compare_by_fd(void *av, void *bv)
+{
+ Serial a = (Serial)av;
+ Serial b = (Serial)bv;
+
+ if (a->fd < b->fd)
+ return -1;
+ else if (a->fd > b->fd)
+ return +1;
+ return 0;
+}
+
+static int serial_find_by_fd(void *av, void *bv)
+{
+ int a = *(int *)av;
+ Serial b = (Serial)bv;
+
+ if (a < b->fd)
+ return -1;
+ else if (a > b->fd)
+ return +1;
+ return 0;
+}
+
+static tree234 *serial_by_fd = NULL;
+
+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)
+{
+ struct termios options;
+ int bflag, bval;
+ const char *str;
+ char *msg;
+
+ if (serial->fd < 0)
+ return "Unable to reconfigure already-closed serial connection";
+
+ tcgetattr(serial->fd, &options);
+
+ /*
+ * Find the appropriate baud rate flag.
+ */
+#define SETBAUD(x) (bflag = B ## x, bval = x)
+#define CHECKBAUD(x) do { if (cfg->serspeed >= x) SETBAUD(x); } while (0)
+ SETBAUD(50);
+#ifdef B75
+ CHECKBAUD(75);
+#endif
+#ifdef B110
+ CHECKBAUD(110);
+#endif
+#ifdef B134
+ CHECKBAUD(134);
+#endif
+#ifdef B150
+ CHECKBAUD(150);
+#endif
+#ifdef B200
+ CHECKBAUD(200);
+#endif
+#ifdef B300
+ CHECKBAUD(300);
+#endif
+#ifdef B600
+ CHECKBAUD(600);
+#endif
+#ifdef B1200
+ CHECKBAUD(1200);
+#endif
+#ifdef B1800
+ CHECKBAUD(1800);
+#endif
+#ifdef B2400
+ CHECKBAUD(2400);
+#endif
+#ifdef B4800
+ CHECKBAUD(4800);
+#endif
+#ifdef B9600
+ CHECKBAUD(9600);
+#endif
+#ifdef B19200
+ CHECKBAUD(19200);
+#endif
+#ifdef B38400
+ CHECKBAUD(38400);
+#endif
+#ifdef B57600
+ CHECKBAUD(57600);
+#endif
+#ifdef B76800
+ CHECKBAUD(76800);
+#endif
+#ifdef B115200
+ CHECKBAUD(115200);
+#endif
+#ifdef B153600
+ CHECKBAUD(153600);
+#endif
+#ifdef B230400
+ CHECKBAUD(230400);
+#endif
+#ifdef B307200
+ CHECKBAUD(307200);
+#endif
+#ifdef B460800
+ CHECKBAUD(460800);
+#endif
+#ifdef B500000
+ CHECKBAUD(500000);
+#endif
+#ifdef B576000
+ CHECKBAUD(576000);
+#endif
+#ifdef B921600
+ CHECKBAUD(921600);
+#endif
+#ifdef B1000000
+ CHECKBAUD(1000000);
+#endif
+#ifdef B1152000
+ CHECKBAUD(1152000);
+#endif
+#ifdef B1500000
+ CHECKBAUD(1500000);
+#endif
+#ifdef B2000000
+ CHECKBAUD(2000000);
+#endif
+#ifdef B2500000
+ CHECKBAUD(2500000);
+#endif
+#ifdef B3000000
+ CHECKBAUD(3000000);
+#endif
+#ifdef B3500000
+ CHECKBAUD(3500000);
+#endif
+#ifdef B4000000
+ CHECKBAUD(4000000);
+#endif
+#undef CHECKBAUD
+#undef SETBAUD
+ cfsetispeed(&options, bflag);
+ cfsetospeed(&options, bflag);
+ msg = dupprintf("Configuring baud rate %d", bval);
+ logevent(serial->frontend, msg);
+ sfree(msg);
+
+ options.c_cflag &= ~CSIZE;
+ switch (cfg->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);
+ logevent(serial->frontend, msg);
+ sfree(msg);
+
+ if (cfg->serstopbits >= 4) {
+ options.c_cflag |= CSTOPB;
+ } else {
+ options.c_cflag &= ~CSTOPB;
+ }
+ msg = dupprintf("Configuring %d stop bits",
+ (options.c_cflag & CSTOPB ? 2 : 1));
+ logevent(serial->frontend, msg);
+ sfree(msg);
+
+ options.c_iflag &= ~(IXON|IXOFF);
+#ifdef CRTSCTS
+ options.c_cflag &= ~CRTSCTS;
+#endif
+#ifdef CNEW_RTSCTS
+ options.c_cflag &= ~CNEW_RTSCTS;
+#endif
+ if (cfg->serflow == SER_FLOW_XONXOFF) {
+ options.c_iflag |= IXON | IXOFF;
+ str = "XON/XOFF";
+ } else if (cfg->serflow == SER_FLOW_RTSCTS) {
+#ifdef CRTSCTS
+ options.c_cflag |= CRTSCTS;
+#endif
+#ifdef CNEW_RTSCTS
+ options.c_cflag |= CNEW_RTSCTS;
+#endif
+ str = "RTS/CTS";
+ } else
+ str = "no";
+ msg = dupprintf("Configuring %s flow control", str);
+ logevent(serial->frontend, msg);
+ sfree(msg);
+
+ /* Parity */
+ if (cfg->serparity == SER_PAR_ODD) {
+ options.c_cflag |= PARENB;
+ options.c_cflag |= PARODD;
+ str = "odd";
+ } else if (cfg->serparity == SER_PAR_EVEN) {
+ options.c_cflag |= PARENB;
+ options.c_cflag &= ~PARODD;
+ str = "even";
+ } else {
+ options.c_cflag &= ~PARENB;
+ str = "no";
+ }
+ msg = dupprintf("Configuring %s parity", str);
+ logevent(serial->frontend, msg);
+ sfree(msg);
+
+ options.c_cflag |= CLOCAL | CREAD;
+ options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
+ options.c_iflag &= ~(ISTRIP | IGNCR | INLCR | ICRNL
+#ifdef IUCLC
+ | IUCLC
+#endif
+ );
+ options.c_oflag &= ~(OPOST
+#ifdef ONLCR
+ | ONLCR
+#endif
+#ifdef OCRNL
+ | OCRNL
+#endif
+#ifdef ONOCR
+ | ONOCR
+#endif
+#ifdef ONLRET
+ | ONLRET
+#endif
+ );
+ options.c_cc[VMIN] = 1;
+ options.c_cc[VTIME] = 0;
+
+ if (tcsetattr(serial->fd, TCSANOW, &options) < 0)
+ return "Unable to configure serial port";
+
+ return NULL;
+}
+
+/*
+ * Called to set up the serial connection.
+ *
+ * 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 *serial_init(void *frontend_handle, void **backend_handle,
+ Config *cfg,
+ char *host, int port, char **realhost, int nodelay,
+ int keepalive)
+{
+ Serial serial;
+ const char *err;
+
+ serial = snew(struct serial_backend_data);
+ *backend_handle = serial;
+
+ serial->frontend = frontend_handle;
+ serial->finished = FALSE;
+ serial->inbufsize = 0;
+ bufchain_init(&serial->output_data);
+
+ {
+ char *msg = dupprintf("Opening serial device %s", cfg->serline);
+ logevent(serial->frontend, msg);
+ }
+
+ serial->fd = open(cfg->serline, 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);
+ if (err)
+ return err;
+
+ *realhost = dupstr(cfg->serline);
+
+ if (!serial_by_fd)
+ serial_by_fd = newtree234(serial_compare_by_fd);
+ add234(serial_by_fd, serial);
+
+ serial_uxsel_setup(serial);
+
+ /*
+ * Specials are always available.
+ */
+ update_specials_menu(serial->frontend);
+
+ return NULL;
+}
+
+static void serial_close(Serial serial)
+{
+ if (serial->fd >= 0) {
+ close(serial->fd);
+ serial->fd = -1;
+ }
+}
+
+static void serial_free(void *handle)
+{
+ Serial serial = (Serial) handle;
+
+ serial_close(serial);
+
+ bufchain_clear(&serial->output_data);
+
+ sfree(serial);
+}
+
+static void serial_reconfig(void *handle, Config *cfg)
+{
+ Serial serial = (Serial) handle;
+
+ /*
+ * FIXME: what should we do if this returns an error?
+ */
+ serial_configure(serial, cfg);
+}
+
+static int serial_select_result(int fd, int event)
+{
+ Serial serial;
+ char buf[4096];
+ int ret;
+ int finished = FALSE;
+
+ serial = find234(serial_by_fd, &fd, serial_find_by_fd);
+
+ if (!serial)
+ return 1; /* spurious event; keep going */
+
+ if (event == 1) {
+ ret = read(serial->fd, buf, sizeof(buf));
+
+ if (ret == 0) {
+ /*
+ * Shouldn't happen on a real serial port, but I'm open
+ * to the idea that there might be two-way devices we
+ * can treat _like_ serial ports which can return EOF.
+ */
+ finished = TRUE;
+ } else if (ret < 0) {
+#ifdef EAGAIN
+ if (errno == EAGAIN)
+ return 1; /* spurious */
+#endif
+#ifdef EWOULDBLOCK
+ if (errno == EWOULDBLOCK)
+ return 1; /* spurious */
+#endif
+ perror("read serial port");
+ exit(1);
+ } else if (ret > 0) {
+ serial->inbufsize = from_backend(serial->frontend, 0, buf, ret);
+ serial_uxsel_setup(serial); /* might acquire backlog and freeze */
+ }
+ } else if (event == 2) {
+ /*
+ * Attempt to send data down the pty.
+ */
+ serial_try_write(serial);
+ }
+
+ if (finished) {
+ serial_close(serial);
+
+ serial->finished = TRUE;
+
+ notify_remote_exit(serial->frontend);
+ }
+
+ return !finished;
+}
+
+static void serial_uxsel_setup(Serial serial)
+{
+ int rwx = 0;
+
+ if (serial->inbufsize <= SERIAL_MAX_BACKLOG)
+ rwx |= 1;
+ if (bufchain_size(&serial->output_data))
+ rwx |= 2; /* might also want to write to it */
+ uxsel_set(serial->fd, rwx, serial_select_result);
+}
+
+static void serial_try_write(Serial serial)
+{
+ void *data;
+ int len, ret;
+
+ assert(serial->fd >= 0);
+
+ while (bufchain_size(&serial->output_data) > 0) {
+ bufchain_prefix(&serial->output_data, &data, &len);
+ ret = write(serial->fd, data, len);
+
+ if (ret < 0 && (errno == EWOULDBLOCK)) {
+ /*
+ * We've sent all we can for the moment.
+ */
+ break;
+ }
+ if (ret < 0) {
+ perror("write serial port");
+ exit(1);
+ }
+ bufchain_consume(&serial->output_data, ret);
+ }
+
+ serial_uxsel_setup(serial);
+}
+
+/*
+ * Called to send data down the serial connection.
+ */
+static int serial_send(void *handle, char *buf, int len)
+{
+ Serial serial = (Serial) handle;
+
+ if (serial->fd < 0)
+ return 0;
+
+ bufchain_add(&serial->output_data, buf, len);
+ serial_try_write(serial);
+
+ return bufchain_size(&serial->output_data);
+}
+
+/*
+ * Called to query the current sendability status.
+ */
+static int serial_sendbuffer(void *handle)
+{
+ Serial serial = (Serial) handle;
+ return bufchain_size(&serial->output_data);
+}
+
+/*
+ * Called to set the size of the window
+ */
+static void serial_size(void *handle, int width, int height)
+{
+ /* Do nothing! */
+ return;
+}
+
+/*
+ * Send serial special codes.
+ */
+static void serial_special(void *handle, Telnet_Special code)
+{
+ Serial serial = (Serial) handle;
+
+ if (serial->fd >= 0 && code == TS_BRK) {
+ tcsendbreak(serial->fd, 0);
+ logevent(serial->frontend, "Sending serial break at user request");
+ }
+
+ return;
+}
+
+/*
+ * Return a list of the special codes that make sense in this
+ * protocol.
+ */
+static const struct telnet_special *serial_get_specials(void *handle)
+{
+ static const struct telnet_special specials[] = {
+ {"Break", TS_BRK},
+ {NULL, TS_EXITMENU}
+ };
+ return specials;
+}
+
+static int serial_connected(void *handle)
+{
+ return 1; /* always connected */
+}
+
+static int serial_sendok(void *handle)
+{
+ return 1;
+}
+
+static void serial_unthrottle(void *handle, int backlog)
+{
+ Serial serial = (Serial) handle;
+ serial->inbufsize = backlog;
+ serial_uxsel_setup(serial);
+}
+
+static int serial_ldisc(void *handle, int option)
+{
+ /*
+ * Local editing and local echo are off by default.
+ */
+ return 0;
+}
+
+static void serial_provide_ldisc(void *handle, void *ldisc)
+{
+ /* This is a stub. */
+}
+
+static void serial_provide_logctx(void *handle, void *logctx)
+{
+ /* This is a stub. */
+}
+
+static int serial_exitcode(void *handle)
+{
+ Serial serial = (Serial) handle;
+ if (serial->fd >= 0)
+ return -1; /* still connected */
+ else
+ /* Exit codes are a meaningless concept with serial ports */
+ return INT_MAX;
+}
+
+/*
+ * cfg_info for Serial does nothing at all.
+ */
+static int serial_cfg_info(void *handle)
+{
+ return 0;
+}
+
+Backend serial_backend = {
+ serial_init,
+ serial_free,
+ serial_reconfig,
+ serial_send,
+ serial_sendbuffer,
+ serial_size,
+ serial_special,
+ serial_get_specials,
+ serial_connected,
+ serial_exitcode,
+ serial_sendok,
+ serial_ldisc,
+ serial_provide_ldisc,
+ serial_provide_logctx,
+ serial_unthrottle,
+ serial_cfg_info,
+ "serial",
+ PROT_SERIAL,
+ 0
+};
--- /dev/null
+/*
+ * uxsftp.c: the Unix-specific parts of PSFTP and PSCP.
+ */
+
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <unistd.h>
+#include <utime.h>
+#include <errno.h>
+#include <assert.h>
+#include <glob.h>
+#ifndef HAVE_NO_SYS_SELECT_H
+#include <sys/select.h>
+#endif
+
+#include "putty.h"
+#include "ssh.h"
+#include "psftp.h"
+#include "int64.h"
+
+/*
+ * In PSFTP our selects are synchronous, so these functions are
+ * empty stubs.
+ */
+int uxsel_input_add(int fd, int rwx) { return 0; }
+void uxsel_input_remove(int id) { }
+
+char *x_get_default(const char *key)
+{
+ return NULL; /* this is a stub */
+}
+
+void platform_get_x11_auth(struct X11Display *display, const Config *cfg)
+{
+ /* Do nothing, therefore no auth. */
+}
+const int platform_uses_x11_unix_by_default = TRUE;
+
+/*
+ * Default settings that are specific to PSFTP.
+ */
+char *platform_default_s(const char *name)
+{
+ return NULL;
+}
+
+int platform_default_i(const char *name, int def)
+{
+ 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;
+ if (!strcmp(name, "LogFileName"))
+ strcpy(ret.path, "putty.log");
+ else
+ *ret.path = '\0';
+ return ret;
+}
+
+char *get_ttymode(void *frontend, const char *mode) { return NULL; }
+
+int get_userpass_input(prompts_t *p, unsigned char *in, int inlen)
+{
+ int ret;
+ ret = cmdline_get_passwd_input(p, in, inlen);
+ if (ret == -1)
+ ret = console_get_userpass_input(p, in, inlen);
+ return ret;
+}
+
+/*
+ * Set local current directory. Returns NULL on success, or else an
+ * error message which must be freed after printing.
+ */
+char *psftp_lcd(char *dir)
+{
+ if (chdir(dir) < 0)
+ return dupprintf("%s: chdir: %s", dir, strerror(errno));
+ else
+ return NULL;
+}
+
+/*
+ * Get local current directory. Returns a string which must be
+ * freed.
+ */
+char *psftp_getcwd(void)
+{
+ char *buffer, *ret;
+ int size = 256;
+
+ buffer = snewn(size, char);
+ while (1) {
+ ret = getcwd(buffer, size);
+ if (ret != NULL)
+ return ret;
+ if (errno != ERANGE) {
+ sfree(buffer);
+ return dupprintf("[cwd unavailable: %s]", strerror(errno));
+ }
+ /*
+ * Otherwise, ERANGE was returned, meaning the buffer
+ * wasn't big enough.
+ */
+ size = size * 3 / 2;
+ buffer = sresize(buffer, size, char);
+ }
+}
+
+struct RFile {
+ int fd;
+};
+
+RFile *open_existing_file(char *name, uint64 *size,
+ unsigned long *mtime, unsigned long *atime)
+{
+ int fd;
+ RFile *ret;
+
+ fd = open(name, O_RDONLY);
+ if (fd < 0)
+ return NULL;
+
+ ret = snew(RFile);
+ ret->fd = fd;
+
+ if (size || mtime || atime) {
+ struct stat statbuf;
+ if (fstat(fd, &statbuf) < 0) {
+ fprintf(stderr, "%s: stat: %s\n", name, strerror(errno));
+ memset(&statbuf, 0, sizeof(statbuf));
+ }
+
+ if (size)
+ *size = uint64_make((statbuf.st_size >> 16) >> 16,
+ statbuf.st_size);
+
+ if (mtime)
+ *mtime = statbuf.st_mtime;
+
+ if (atime)
+ *atime = statbuf.st_atime;
+ }
+
+ return ret;
+}
+
+int read_from_file(RFile *f, void *buffer, int length)
+{
+ return read(f->fd, buffer, length);
+}
+
+void close_rfile(RFile *f)
+{
+ close(f->fd);
+ sfree(f);
+}
+
+struct WFile {
+ int fd;
+ char *name;
+};
+
+WFile *open_new_file(char *name)
+{
+ int fd;
+ WFile *ret;
+
+ fd = open(name, O_CREAT | O_TRUNC | O_WRONLY, 0666);
+ if (fd < 0)
+ return NULL;
+
+ ret = snew(WFile);
+ ret->fd = fd;
+ ret->name = dupstr(name);
+
+ return ret;
+}
+
+
+WFile *open_existing_wfile(char *name, uint64 *size)
+{
+ int fd;
+ WFile *ret;
+
+ fd = open(name, O_APPEND | O_WRONLY);
+ if (fd < 0)
+ return NULL;
+
+ ret = snew(WFile);
+ ret->fd = fd;
+ ret->name = dupstr(name);
+
+ if (size) {
+ struct stat statbuf;
+ if (fstat(fd, &statbuf) < 0) {
+ fprintf(stderr, "%s: stat: %s\n", name, strerror(errno));
+ memset(&statbuf, 0, sizeof(statbuf));
+ }
+
+ *size = uint64_make((statbuf.st_size >> 16) >> 16,
+ statbuf.st_size);
+ }
+
+ return ret;
+}
+
+int write_to_file(WFile *f, void *buffer, int length)
+{
+ char *p = (char *)buffer;
+ int so_far = 0;
+
+ /* Keep trying until we've really written as much as we can. */
+ while (length > 0) {
+ int ret = write(f->fd, p, length);
+
+ if (ret < 0)
+ return ret;
+
+ if (ret == 0)
+ break;
+
+ p += ret;
+ length -= ret;
+ so_far += ret;
+ }
+
+ return so_far;
+}
+
+void set_file_times(WFile *f, unsigned long mtime, unsigned long atime)
+{
+ struct utimbuf ut;
+
+ ut.actime = atime;
+ ut.modtime = mtime;
+
+ utime(f->name, &ut);
+}
+
+/* Closes and frees the WFile */
+void close_wfile(WFile *f)
+{
+ close(f->fd);
+ sfree(f->name);
+ sfree(f);
+}
+
+/* Seek offset bytes through file, from whence, where whence is
+ FROM_START, FROM_CURRENT, or FROM_END */
+int seek_file(WFile *f, uint64 offset, int whence)
+{
+ off_t fileofft;
+ int lseek_whence;
+
+ fileofft = (((off_t) offset.hi << 16) << 16) + offset.lo;
+
+ switch (whence) {
+ case FROM_START:
+ lseek_whence = SEEK_SET;
+ break;
+ case FROM_CURRENT:
+ lseek_whence = SEEK_CUR;
+ break;
+ case FROM_END:
+ lseek_whence = SEEK_END;
+ break;
+ default:
+ return -1;
+ }
+
+ return lseek(f->fd, fileofft, lseek_whence) >= 0 ? 0 : -1;
+}
+
+uint64 get_file_posn(WFile *f)
+{
+ off_t fileofft;
+ uint64 ret;
+
+ fileofft = lseek(f->fd, (off_t) 0, SEEK_CUR);
+
+ ret = uint64_make((fileofft >> 16) >> 16, fileofft);
+
+ return ret;
+}
+
+int file_type(char *name)
+{
+ struct stat statbuf;
+
+ if (stat(name, &statbuf) < 0) {
+ if (errno != ENOENT)
+ fprintf(stderr, "%s: stat: %s\n", name, strerror(errno));
+ return FILE_TYPE_NONEXISTENT;
+ }
+
+ if (S_ISREG(statbuf.st_mode))
+ return FILE_TYPE_FILE;
+
+ if (S_ISDIR(statbuf.st_mode))
+ return FILE_TYPE_DIRECTORY;
+
+ return FILE_TYPE_WEIRD;
+}
+
+struct DirHandle {
+ DIR *dir;
+};
+
+DirHandle *open_directory(char *name)
+{
+ DIR *dir;
+ DirHandle *ret;
+
+ dir = opendir(name);
+ if (!dir)
+ return NULL;
+
+ ret = snew(DirHandle);
+ ret->dir = dir;
+ return ret;
+}
+
+char *read_filename(DirHandle *dir)
+{
+ struct dirent *de;
+
+ do {
+ de = readdir(dir->dir);
+ if (de == NULL)
+ return NULL;
+ } while ((de->d_name[0] == '.' &&
+ (de->d_name[1] == '\0' ||
+ (de->d_name[1] == '.' && de->d_name[2] == '\0'))));
+
+ return dupstr(de->d_name);
+}
+
+void close_directory(DirHandle *dir)
+{
+ closedir(dir->dir);
+ sfree(dir);
+}
+
+int test_wildcard(char *name, int cmdline)
+{
+ struct stat statbuf;
+
+ if (stat(name, &statbuf) == 0) {
+ return WCTYPE_FILENAME;
+ } else if (cmdline) {
+ /*
+ * On Unix, we never need to parse wildcards coming from
+ * the command line, because the shell will have expanded
+ * them into a filename list already.
+ */
+ return WCTYPE_NONEXISTENT;
+ } else {
+ glob_t globbed;
+ int ret = WCTYPE_NONEXISTENT;
+
+ if (glob(name, GLOB_ERR, NULL, &globbed) == 0) {
+ if (globbed.gl_pathc > 0)
+ ret = WCTYPE_WILDCARD;
+ globfree(&globbed);
+ }
+
+ return ret;
+ }
+}
+
+/*
+ * Actually return matching file names for a local wildcard.
+ */
+struct WildcardMatcher {
+ glob_t globbed;
+ int i;
+};
+WildcardMatcher *begin_wildcard_matching(char *name) {
+ WildcardMatcher *ret = snew(WildcardMatcher);
+
+ if (glob(name, 0, NULL, &ret->globbed) < 0) {
+ sfree(ret);
+ return NULL;
+ }
+
+ ret->i = 0;
+
+ return ret;
+}
+char *wildcard_get_filename(WildcardMatcher *dir) {
+ if (dir->i < dir->globbed.gl_pathc) {
+ return dupstr(dir->globbed.gl_pathv[dir->i++]);
+ } else
+ return NULL;
+}
+void finish_wildcard_matching(WildcardMatcher *dir) {
+ globfree(&dir->globbed);
+ sfree(dir);
+}
+
+int vet_filename(char *name)
+{
+ if (strchr(name, '/'))
+ return FALSE;
+
+ if (name[0] == '.' && (!name[1] || (name[1] == '.' && !name[2])))
+ return FALSE;
+
+ return TRUE;
+}
+
+int create_directory(char *name)
+{
+ return mkdir(name, 0777) == 0;
+}
+
+char *dir_file_cat(char *dir, char *file)
+{
+ return dupcat(dir, "/", file, NULL);
+}
+
+/*
+ * Do a select() between all currently active network fds and
+ * optionally stdin.
+ */
+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();
+
+ fdlist = NULL;
+ fdcount = fdsize = 0;
+
+ do {
+
+ /* Count the currently active fds. */
+ i = 0;
+ for (fd = first_fd(&fdstate, &rwx); fd >= 0;
+ fd = next_fd(&fdstate, &rwx)) i++;
+
+ if (i < 1 && !no_fds_ok)
+ return -1; /* doom */
+
+ /* Expand the fdlist buffer if necessary. */
+ if (i > fdsize) {
+ fdsize = i + 16;
+ fdlist = sresize(fdlist, fdsize, int);
+ }
+
+ FD_ZERO(&rset);
+ FD_ZERO(&wset);
+ FD_ZERO(&xset);
+ maxfd = 0;
+
+ /*
+ * Add all currently open fds to the select sets, and store
+ * them in fdlist as well.
+ */
+ fdcount = 0;
+ for (fd = first_fd(&fdstate, &rwx); fd >= 0;
+ fd = next_fd(&fdstate, &rwx)) {
+ fdlist[fdcount++] = fd;
+ if (rwx & 1)
+ FD_SET_MAX(fd, maxfd, rset);
+ if (rwx & 2)
+ FD_SET_MAX(fd, maxfd, wset);
+ if (rwx & 4)
+ FD_SET_MAX(fd, maxfd, xset);
+ }
+
+ 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 */
+ 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);
+ } while (ret == 0);
+
+ if (ret < 0) {
+ perror("select");
+ exit(1);
+ }
+
+ for (i = 0; i < fdcount; i++) {
+ fd = fdlist[i];
+ /*
+ * We must process exceptional notifications before
+ * ordinary readability ones, or we may go straight
+ * past the urgent marker.
+ */
+ if (FD_ISSET(fd, &xset))
+ select_result(fd, 4);
+ if (FD_ISSET(fd, &rset))
+ select_result(fd, 1);
+ if (FD_ISSET(fd, &wset))
+ select_result(fd, 2);
+ }
+
+ sfree(fdlist);
+
+ return FD_ISSET(0, &rset) ? 1 : 0;
+}
+
+/*
+ * Wait for some network data and process it.
+ */
+int ssh_sftp_loop_iteration(void)
+{
+ return ssh_sftp_do_select(FALSE, FALSE);
+}
+
+/*
+ * Read a PSFTP command line from stdin.
+ */
+char *ssh_sftp_get_cmdline(char *prompt, int no_fds_ok)
+{
+ char *buf;
+ int buflen, bufsize, ret;
+
+ fputs(prompt, stdout);
+ fflush(stdout);
+
+ buf = NULL;
+ buflen = bufsize = 0;
+
+ while (1) {
+ ret = ssh_sftp_do_select(TRUE, no_fds_ok);
+ if (ret < 0) {
+ printf("connection died\n");
+ return NULL; /* woop woop */
+ }
+ if (ret > 0) {
+ if (buflen >= bufsize) {
+ bufsize = buflen + 512;
+ buf = sresize(buf, bufsize, char);
+ }
+ ret = read(0, buf+buflen, 1);
+ if (ret < 0) {
+ perror("read");
+ return NULL;
+ }
+ if (ret == 0) {
+ /* eof on stdin; no error, but no answer either */
+ return NULL;
+ }
+
+ if (buf[buflen++] == '\n') {
+ /* we have a full line */
+ return buf;
+ }
+ }
+ }
+}
+
+/*
+ * Main program: do platform-specific initialisation and then call
+ * psftp_main().
+ */
+int main(int argc, char *argv[])
+{
+ uxsel_init();
+ return psftp_main(argc, argv);
+}
--- /dev/null
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+/*
+ * Calling signal() is non-portable, as it varies in meaning
+ * between platforms and depending on feature macros, and has
+ * stupid semantics at least some of the time.
+ *
+ * This function provides the same interface as the libc function,
+ * but provides consistent semantics. It assumes POSIX semantics
+ * for sigaction() (so you might need to do some more work if you
+ * port to something ancient like SunOS 4)
+ */
+void (*putty_signal(int sig, void (*func)(int)))(int) {
+ struct sigaction sa;
+ struct sigaction old;
+
+ sa.sa_handler = func;
+ if(sigemptyset(&sa.sa_mask) < 0)
+ return SIG_ERR;
+ sa.sa_flags = SA_RESTART;
+ if(sigaction(sig, &sa, &old) < 0)
+ return SIG_ERR;
+ return old.sa_handler;
+}
+
+void block_signal(int sig, int block_it)
+{
+ sigset_t ss;
+
+ sigemptyset(&ss);
+ sigaddset(&ss, sig);
+ if(sigprocmask(block_it ? SIG_BLOCK : SIG_UNBLOCK, &ss, 0) < 0) {
+ perror("sigprocmask");
+ exit(1);
+ }
+}
+
+/*
+Local Variables:
+c-basic-offset:4
+comment-column:40
+End:
+*/
--- /dev/null
+/*
+ * uxstore.c: Unix-specific implementation of the interface defined
+ * in storage.h.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <errno.h>
+#include <ctype.h>
+#include <limits.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <pwd.h>
+#include "putty.h"
+#include "storage.h"
+#include "tree234.h"
+
+#ifdef PATH_MAX
+#define FNLEN PATH_MAX
+#else
+#define FNLEN 1024 /* XXX */
+#endif
+
+enum {
+ INDEX_DIR, INDEX_HOSTKEYS, INDEX_HOSTKEYS_TMP, INDEX_RANDSEED,
+ INDEX_SESSIONDIR, INDEX_SESSION,
+};
+
+static const char hex[16] = "0123456789ABCDEF";
+
+static char *mungestr(const char *in)
+{
+ char *out, *ret;
+
+ if (!in || !*in)
+ in = "Default Settings";
+
+ ret = out = snewn(3*strlen(in)+1, char);
+
+ while (*in) {
+ /*
+ * There are remarkably few punctuation characters that
+ * aren't shell-special in some way or likely to be used as
+ * separators in some file format or another! Hence we use
+ * opt-in for safe characters rather than opt-out for
+ * specific unsafe ones...
+ */
+ if (*in!='+' && *in!='-' && *in!='.' && *in!='@' && *in!='_' &&
+ !(*in >= '0' && *in <= '9') &&
+ !(*in >= 'A' && *in <= 'Z') &&
+ !(*in >= 'a' && *in <= 'z')) {
+ *out++ = '%';
+ *out++ = hex[((unsigned char) *in) >> 4];
+ *out++ = hex[((unsigned char) *in) & 15];
+ } else
+ *out++ = *in;
+ in++;
+ }
+ *out = '\0';
+ return ret;
+}
+
+static char *unmungestr(const char *in)
+{
+ char *out, *ret;
+ out = ret = snewn(strlen(in)+1, char);
+ while (*in) {
+ if (*in == '%' && in[1] && in[2]) {
+ int i, j;
+
+ i = in[1] - '0';
+ i -= (i > 9 ? 7 : 0);
+ j = in[2] - '0';
+ j -= (j > 9 ? 7 : 0);
+
+ *out++ = (i << 4) + j;
+ in += 3;
+ } else {
+ *out++ = *in++;
+ }
+ }
+ *out = '\0';
+ return ret;
+}
+
+static char *make_filename(int index, const char *subname)
+{
+ char *env, *tmp, *ret;
+
+ /*
+ * Allow override of the PuTTY configuration location, and of
+ * specific subparts of it, by means of environment variables.
+ */
+ if (index == INDEX_DIR) {
+ struct passwd *pwd;
+
+ env = getenv("PUTTYDIR");
+ if (env)
+ return dupstr(env);
+ env = getenv("HOME");
+ if (env)
+ return dupprintf("%s/.putty", env);
+ pwd = getpwuid(getuid());
+ if (pwd && pwd->pw_dir)
+ return dupprintf("%s/.putty", pwd->pw_dir);
+ return dupstr("/.putty");
+ }
+ if (index == INDEX_SESSIONDIR) {
+ env = getenv("PUTTYSESSIONS");
+ if (env)
+ return dupstr(env);
+ tmp = make_filename(INDEX_DIR, NULL);
+ ret = dupprintf("%s/sessions", tmp);
+ sfree(tmp);
+ return ret;
+ }
+ if (index == INDEX_SESSION) {
+ char *munged = mungestr(subname);
+ tmp = make_filename(INDEX_SESSIONDIR, NULL);
+ ret = dupprintf("%s/%s", tmp, munged);
+ sfree(tmp);
+ sfree(munged);
+ return ret;
+ }
+ if (index == INDEX_HOSTKEYS) {
+ env = getenv("PUTTYSSHHOSTKEYS");
+ if (env)
+ return dupstr(env);
+ tmp = make_filename(INDEX_DIR, NULL);
+ ret = dupprintf("%s/sshhostkeys", tmp);
+ sfree(tmp);
+ return ret;
+ }
+ if (index == INDEX_HOSTKEYS_TMP) {
+ tmp = make_filename(INDEX_HOSTKEYS, NULL);
+ ret = dupprintf("%s.tmp", tmp);
+ sfree(tmp);
+ return ret;
+ }
+ if (index == INDEX_RANDSEED) {
+ env = getenv("PUTTYRANDOMSEED");
+ if (env)
+ return dupstr(env);
+ tmp = make_filename(INDEX_DIR, NULL);
+ ret = dupprintf("%s/randomseed", tmp);
+ sfree(tmp);
+ return ret;
+ }
+ tmp = make_filename(INDEX_DIR, NULL);
+ ret = dupprintf("%s/ERROR", tmp);
+ sfree(tmp);
+ return ret;
+}
+
+void *open_settings_w(const char *sessionname, char **errmsg)
+{
+ char *filename;
+ FILE *fp;
+
+ *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.
+ */
+ 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);
+ }
+ sfree(filename);
+
+ filename = make_filename(INDEX_SESSION, sessionname);
+ fp = fopen(filename, "w");
+ if (!fp) {
+ *errmsg = dupprintf("Unable to create %s: %s",
+ filename, strerror(errno));
+ sfree(filename);
+ return NULL; /* can't open */
+ }
+ sfree(filename);
+ return fp;
+}
+
+void write_setting_s(void *handle, const char *key, const char *value)
+{
+ FILE *fp = (FILE *)handle;
+ fprintf(fp, "%s=%s\n", key, value);
+}
+
+void write_setting_i(void *handle, const char *key, int value)
+{
+ FILE *fp = (FILE *)handle;
+ fprintf(fp, "%s=%d\n", key, value);
+}
+
+void close_settings_w(void *handle)
+{
+ FILE *fp = (FILE *)handle;
+ fclose(fp);
+}
+
+/*
+ * Reading settings, for the moment, is done by retrieving X
+ * resources from the X display. When we introduce disk files, I
+ * think what will happen is that the X resources will override
+ * PuTTY's inbuilt defaults, but that the disk files will then
+ * override those. This isn't optimal, but it's the best I can
+ * immediately work out.
+ * FIXME: the above comment is a bit out of date. Did it happen?
+ */
+
+struct skeyval {
+ const char *key;
+ const char *value;
+};
+
+static tree234 *xrmtree = NULL;
+
+int keycmp(void *av, void *bv)
+{
+ struct skeyval *a = (struct skeyval *)av;
+ struct skeyval *b = (struct skeyval *)bv;
+ return strcmp(a->key, b->key);
+}
+
+void provide_xrm_string(char *string)
+{
+ char *p, *q, *key;
+ struct skeyval *xrms, *ret;
+
+ p = q = strchr(string, ':');
+ if (!q) {
+ fprintf(stderr, "pterm: expected a colon in resource string"
+ " \"%s\"\n", string);
+ return;
+ }
+ q++;
+ while (p > string && p[-1] != '.' && p[-1] != '*')
+ p--;
+ xrms = snew(struct skeyval);
+ key = snewn(q-p, char);
+ memcpy(key, p, q-p);
+ key[q-p-1] = '\0';
+ xrms->key = key;
+ while (*q && isspace((unsigned char)*q))
+ q++;
+ xrms->value = dupstr(q);
+
+ if (!xrmtree)
+ xrmtree = newtree234(keycmp);
+
+ ret = add234(xrmtree, xrms);
+ if (ret) {
+ /* Override an existing string. */
+ del234(xrmtree, ret);
+ add234(xrmtree, xrms);
+ }
+}
+
+const char *get_setting(const char *key)
+{
+ struct skeyval tmp, *ret;
+ tmp.key = key;
+ if (xrmtree) {
+ ret = find234(xrmtree, &tmp, NULL);
+ if (ret)
+ return ret->value;
+ }
+ return x_get_default(key);
+}
+
+void *open_settings_r(const char *sessionname)
+{
+ char *filename;
+ FILE *fp;
+ char *line;
+ tree234 *ret;
+
+ filename = make_filename(INDEX_SESSION, sessionname);
+ fp = fopen(filename, "r");
+ sfree(filename);
+ if (!fp)
+ return NULL; /* can't open */
+
+ ret = newtree234(keycmp);
+
+ while ( (line = fgetline(fp)) ) {
+ char *value = strchr(line, '=');
+ struct skeyval *kv;
+
+ if (!value)
+ continue;
+ *value++ = '\0';
+ value[strcspn(value, "\r\n")] = '\0'; /* trim trailing NL */
+
+ kv = snew(struct skeyval);
+ kv->key = dupstr(line);
+ kv->value = dupstr(value);
+ add234(ret, kv);
+
+ sfree(line);
+ }
+
+ fclose(fp);
+
+ return ret;
+}
+
+char *read_setting_s(void *handle, const char *key, char *buffer, int buflen)
+{
+ tree234 *tree = (tree234 *)handle;
+ const char *val;
+ struct skeyval tmp, *kv;
+
+ tmp.key = key;
+ if (tree != NULL &&
+ (kv = find234(tree, &tmp, NULL)) != NULL) {
+ val = kv->value;
+ assert(val != NULL);
+ } else
+ val = get_setting(key);
+
+ if (!val)
+ return NULL;
+ else {
+ strncpy(buffer, val, buflen);
+ buffer[buflen-1] = '\0';
+ return buffer;
+ }
+}
+
+int read_setting_i(void *handle, const char *key, int defvalue)
+{
+ tree234 *tree = (tree234 *)handle;
+ const char *val;
+ struct skeyval tmp, *kv;
+
+ tmp.key = key;
+ if (tree != NULL &&
+ (kv = find234(tree, &tmp, NULL)) != NULL) {
+ val = kv->value;
+ assert(val != NULL);
+ } else
+ val = get_setting(key);
+
+ if (!val)
+ return defvalue;
+ else
+ return atoi(val);
+}
+
+int read_setting_fontspec(void *handle, const char *name, FontSpec *result)
+{
+ /*
+ * 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".
+ *
+ * In GTK2 PuTTY, we have a prefix system where "client:"
+ * indicates a Pango font and "server:" an X one; existing
+ * configuration needs to be reinterpreted as having the
+ * "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))) {
+ sfree(suffname);
+ return TRUE; /* 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;
+ } else {
+ return TRUE;
+ }
+}
+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_fontspec(void *handle, const char *name, FontSpec result)
+{
+ /*
+ * 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);
+ sfree(suffname);
+}
+void write_setting_filename(void *handle, const char *name, Filename result)
+{
+ write_setting_s(handle, name, result.path);
+}
+
+void close_settings_r(void *handle)
+{
+ tree234 *tree = (tree234 *)handle;
+ struct skeyval *kv;
+
+ if (!tree)
+ return;
+
+ while ( (kv = index234(tree, 0)) != NULL) {
+ del234(tree, kv);
+ sfree((char *)kv->key);
+ sfree((char *)kv->value);
+ sfree(kv);
+ }
+
+ freetree234(tree);
+}
+
+void del_settings(const char *sessionname)
+{
+ char *filename;
+ filename = make_filename(INDEX_SESSION, sessionname);
+ unlink(filename);
+ sfree(filename);
+}
+
+void *enum_settings_start(void)
+{
+ DIR *dp;
+ char *filename;
+
+ filename = make_filename(INDEX_SESSIONDIR, NULL);
+ dp = opendir(filename);
+ sfree(filename);
+
+ return dp;
+}
+
+char *enum_settings_next(void *handle, char *buffer, int buflen)
+{
+ DIR *dp = (DIR *)handle;
+ struct dirent *de;
+ struct stat st;
+ char *fullpath;
+ int maxlen, thislen, len;
+ char *unmunged;
+
+ fullpath = make_filename(INDEX_SESSIONDIR, NULL);
+ maxlen = len = strlen(fullpath);
+
+ while ( (de = readdir(dp)) != NULL ) {
+ thislen = len + 1 + strlen(de->d_name);
+ if (maxlen < thislen) {
+ maxlen = thislen;
+ fullpath = sresize(fullpath, maxlen+1, char);
+ }
+ fullpath[len] = '/';
+ strncpy(fullpath+len+1, de->d_name, thislen - (len+1));
+ fullpath[thislen] = '\0';
+
+ if (stat(fullpath, &st) < 0 || !S_ISREG(st.st_mode))
+ continue; /* try another one */
+
+ unmunged = unmungestr(de->d_name);
+ strncpy(buffer, unmunged, buflen);
+ buffer[buflen-1] = '\0';
+ sfree(unmunged);
+ sfree(fullpath);
+ return buffer;
+ }
+
+ sfree(fullpath);
+ return NULL;
+}
+
+void enum_settings_finish(void *handle)
+{
+ DIR *dp = (DIR *)handle;
+ closedir(dp);
+}
+
+/*
+ * Lines in the host keys file are of the form
+ *
+ * type@port:hostname keydata
+ *
+ * e.g.
+ *
+ * rsa@22:foovax.example.org 0x23,0x293487364395345345....2343
+ */
+int verify_host_key(const char *hostname, int port,
+ const char *keytype, const char *key)
+{
+ FILE *fp;
+ char *filename;
+ char *line;
+ int ret;
+
+ filename = make_filename(INDEX_HOSTKEYS, NULL);
+ fp = fopen(filename, "r");
+ sfree(filename);
+ if (!fp)
+ return 1; /* key does not exist */
+
+ ret = 1;
+ while ( (line = fgetline(fp)) ) {
+ int i;
+ char *p = line;
+ char porttext[20];
+
+ line[strcspn(line, "\n")] = '\0'; /* strip trailing newline */
+
+ i = strlen(keytype);
+ if (strncmp(p, keytype, i))
+ goto done;
+ p += i;
+
+ if (*p != '@')
+ goto done;
+ p++;
+
+ sprintf(porttext, "%d", port);
+ i = strlen(porttext);
+ if (strncmp(p, porttext, i))
+ goto done;
+ p += i;
+
+ if (*p != ':')
+ goto done;
+ p++;
+
+ i = strlen(hostname);
+ if (strncmp(p, hostname, i))
+ goto done;
+ p += i;
+
+ if (*p != ' ')
+ goto done;
+ p++;
+
+ /*
+ * Found the key. Now just work out whether it's the right
+ * one or not.
+ */
+ if (!strcmp(p, key))
+ ret = 0; /* key matched OK */
+ else
+ ret = 2; /* key mismatch */
+
+ done:
+ sfree(line);
+ if (ret != 1)
+ break;
+ }
+
+ fclose(fp);
+ return ret;
+}
+
+void store_host_key(const char *hostname, int port,
+ const char *keytype, const char *key)
+{
+ 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) {
+ char *dir;
+
+ dir = make_filename(INDEX_DIR, NULL);
+ mkdir(dir, 0700);
+ sfree(dir);
+
+ wfp = fopen(tmpfilename, "w");
+ }
+ if (!wfp) {
+ sfree(tmpfilename);
+ return;
+ }
+ filename = make_filename(INDEX_HOSTKEYS, NULL);
+ rfp = fopen(filename, "r");
+
+ /*
+ * 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);
+ }
+ fclose(rfp);
+ }
+
+ /*
+ * Now add the new line at the end.
+ */
+ fputs(newtext, wfp);
+
+ fclose(wfp);
+
+ rename(tmpfilename, filename);
+
+ sfree(tmpfilename);
+ sfree(filename);
+ sfree(newtext);
+}
+
+void read_random_seed(noise_consumer_t consumer)
+{
+ int fd;
+ char *fname;
+
+ fname = make_filename(INDEX_RANDSEED, NULL);
+ fd = open(fname, O_RDONLY);
+ sfree(fname);
+ if (fd >= 0) {
+ char buf[512];
+ int ret;
+ while ( (ret = read(fd, buf, sizeof(buf))) > 0)
+ consumer(buf, ret);
+ close(fd);
+ }
+}
+
+void write_random_seed(void *data, int len)
+{
+ int fd;
+ char *fname;
+
+ fname = make_filename(INDEX_RANDSEED, NULL);
+ /*
+ * Don't truncate the random seed file if it already exists; if
+ * 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) {
+ char *dir;
+
+ dir = make_filename(INDEX_DIR, NULL);
+ mkdir(dir, 0700);
+ sfree(dir);
+
+ fd = open(fname, O_CREAT | O_WRONLY, 0600);
+ }
+
+ while (len > 0) {
+ int ret = write(fd, data, len);
+ if (ret <= 0) break;
+ len -= ret;
+ data = (char *)data + len;
+ }
+
+ close(fd);
+ sfree(fname);
+}
+
+void cleanup_all(void)
+{
+}
--- /dev/null
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <locale.h>
+#include <limits.h>
+#include <wchar.h>
+
+#include <time.h>
+
+#include "putty.h"
+#include "charset.h"
+#include "terminal.h"
+#include "misc.h"
+
+/*
+ * Unix Unicode-handling routines.
+ */
+
+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,
+ 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;
+ n++;
+ mbstr += i;
+ mblen -= i;
+ }
+
+ setlocale(LC_CTYPE, "C");
+
+ return n;
+ } else if (codepage == CS_NONE) {
+ int n = 0;
+
+ while (mblen > 0) {
+ wcstr[n] = 0xD800 | (mbstr[0] & 0xFF);
+ n++;
+ mbstr++;
+ mblen--;
+ }
+
+ return n;
+ } 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,
+ char *mbstr, int mblen, char *defchr, int *defused,
+ struct unicode_data *ucsdata)
+{
+ /* FIXME: we should remove the defused param completely... */
+ if (defused)
+ *defused = 0;
+
+ if (codepage == DEFAULT_CODEPAGE) {
+ 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;
+ memcpy(mbstr+n, output, i);
+ 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)
+ mbstr[n++] = (*wcstr & 0xFF);
+ else if (defchr)
+ mbstr[n++] = *defchr;
+ wcstr++;
+ wclen--;
+ }
+ return n;
+ } else {
+ return charset_from_unicode(&wcstr, &wclen, mbstr, mblen, codepage,
+ NULL, defchr?defchr:NULL, defchr?1:0);
+ }
+}
+
+/*
+ * Return value is TRUE if pterm is to run in direct-to-font mode.
+ */
+int init_ucs(struct unicode_data *ucsdata, char *linecharset,
+ int utf8_override, int font_charset, int vtmode)
+{
+ int i, ret = 0;
+
+ /*
+ * In the platform-independent parts of the code, font_codepage
+ * is used only for system DBCS support - which we don't
+ * support at all. So we set this to something which will never
+ * be used.
+ */
+ ucsdata->font_codepage = -1;
+
+ /*
+ * If utf8_override is set and the POSIX locale settings
+ * dictate a UTF-8 character set, then just go straight for
+ * UTF-8.
+ */
+ ucsdata->line_codepage = CS_NONE;
+ if (utf8_override) {
+ const char *s;
+ if (((s = getenv("LC_ALL")) && *s) ||
+ ((s = getenv("LC_CTYPE")) && *s) ||
+ ((s = getenv("LANG")) && *s)) {
+ if (strstr(s, "UTF-8"))
+ ucsdata->line_codepage = CS_UTF8;
+ }
+ }
+
+ /*
+ * Failing that, line_codepage should be decoded from the
+ * specification in cfg.
+ */
+ if (ucsdata->line_codepage == CS_NONE)
+ ucsdata->line_codepage = decode_codepage(linecharset);
+
+ /*
+ * If line_codepage is _still_ CS_NONE, we assume we're using
+ * the font's own encoding. This has been passed in to us, so
+ * we use that. If it's still CS_NONE after _that_ - i.e. the
+ * font we were given had an incomprehensible charset - then we
+ * fall back to using the D800 page.
+ */
+ if (ucsdata->line_codepage == CS_NONE)
+ ucsdata->line_codepage = font_charset;
+
+ if (ucsdata->line_codepage == CS_NONE)
+ ret = 1;
+
+ /*
+ * 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;
+ wchar_t wc[1];
+ int len;
+ c[0] = i;
+ p = c;
+ len = 1;
+ if (ucsdata->line_codepage == CS_NONE)
+ ucsdata->unitab_line[i] = 0xD800 | i;
+ else if (1 == charset_to_unicode(&p, &len, wc, 1,
+ ucsdata->line_codepage,
+ NULL, L"", 0))
+ ucsdata->unitab_line[i] = wc[0];
+ else
+ ucsdata->unitab_line[i] = 0xFFFD;
+ }
+
+ /*
+ * Set up unitab_xterm. This is the same as unitab_line except
+ * in the line-drawing regions, where it follows the Unicode
+ * encoding.
+ *
+ * (Note that the strange X encoding of line-drawing characters
+ * in the bottom 32 glyphs of ISO8859-1 fonts is taken care of
+ * by the font encoding, which will spot such a font and act as
+ * if it were in a variant encoding of ISO8859-1.)
+ */
+ for (i = 0; i < 256; i++) {
+ static const wchar_t unitab_xterm_std[32] = {
+ 0x2666, 0x2592, 0x2409, 0x240c, 0x240d, 0x240a, 0x00b0, 0x00b1,
+ 0x2424, 0x240b, 0x2518, 0x2510, 0x250c, 0x2514, 0x253c, 0x23ba,
+ 0x23bb, 0x2500, 0x23bc, 0x23bd, 0x251c, 0x2524, 0x2534, 0x252c,
+ 0x2502, 0x2264, 0x2265, 0x03c0, 0x2260, 0x00a3, 0x00b7, 0x0020
+ };
+ static const wchar_t unitab_xterm_poorman[32] =
+ L"*#****o~**+++++-----++++|****L. ";
+
+ const wchar_t *ptr;
+
+ if (vtmode == VT_POORMAN)
+ ptr = unitab_xterm_poorman;
+ else
+ ptr = unitab_xterm_std;
+
+ if (i >= 0x5F && i < 0x7F)
+ ucsdata->unitab_xterm[i] = ptr[i & 0x1F];
+ else
+ ucsdata->unitab_xterm[i] = ucsdata->unitab_line[i];
+ }
+
+ /*
+ * Set up unitab_scoacs. The SCO Alternate Character Set is
+ * simply CP437.
+ */
+ for (i = 0; i < 256; i++) {
+ char c[1], *p;
+ wchar_t wc[1];
+ int len;
+ c[0] = i;
+ p = c;
+ len = 1;
+ if (1 == charset_to_unicode(&p, &len, wc, 1, CS_CP437, NULL, L"", 0))
+ ucsdata->unitab_scoacs[i] = wc[0];
+ else
+ ucsdata->unitab_scoacs[i] = 0xFFFD;
+ }
+
+ /*
+ * Find the control characters in the line codepage. For
+ * direct-to-font mode using the D800 hack, we assume 00-1F and
+ * 7F are controls, but allow 80-9F through. (It's as good a
+ * guess as anything; and my bet is that half the weird fonts
+ * used in this way will be IBM or MS code pages anyway.)
+ */
+ for (i = 0; i < 256; i++) {
+ int lineval = ucsdata->unitab_line[i];
+ if (lineval < ' ' || (lineval >= 0x7F && lineval < 0xA0) ||
+ (lineval >= 0xD800 && lineval < 0xD820) || (lineval == 0xD87F))
+ ucsdata->unitab_ctrl[i] = i;
+ else
+ ucsdata->unitab_ctrl[i] = 0xFF;
+ }
+
+ return ret;
+}
+
+const char *cp_name(int codepage)
+{
+ if (codepage == CS_NONE)
+ return "Use font encoding";
+ return charset_to_localenc(codepage);
+}
+
+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)
+ return NULL;
+ return charset_to_localenc(charset);
+}
+
+int decode_codepage(char *cp_name)
+{
+ if (!*cp_name)
+ return CS_NONE; /* use font encoding */
+ return charset_from_localenc(cp_name);
+}
--- /dev/null
+/*
+ * xkeysym.c: mapping from X keysyms to Unicode values
+ *
+ * The basic idea of this is shamelessly cribbed from xterm. The
+ * actual character data is generated from Markus Kuhn's proposed
+ * redraft of the X11 keysym mapping table, using the following
+ * piece of Perl/sh code:
+
+wget -q -O - http://www.cl.cam.ac.uk/~mgk25/ucs/X11.keysyms | \
+perl -ne '/^(\d+)\s+(\d+)\s+[\d\/]+\s+U\+([\dA-Fa-f]+)/ and' \
+ -e ' do { $a{$1 * 256+ $2} = hex $3; };' \
+ -e 'END { foreach $i (sort {$a <=> $b} keys %a) {' \
+ -e ' printf " {0x%x, 0x%x},\n", $i, $a{$i} } }' \
+ -e 'BEGIN { $a{0x13a4} = 0x20ac }'
+
+ * (The BEGIN clause inserts a mapping for the Euro sign which for
+ * some reason isn't in the list but xterm supports. *shrug*.)
+ */
+
+#include "misc.h"
+
+struct keysym {
+ /*
+ * Currently nothing in here is above 0xFFFF, so I'll use
+ * `unsigned short' to save space.
+ */
+ unsigned short keysym;
+ unsigned short unicode;
+};
+
+static struct keysym keysyms[] = {
+ {0x20, 0x20},
+ {0x21, 0x21},
+ {0x22, 0x22},
+ {0x23, 0x23},
+ {0x24, 0x24},
+ {0x25, 0x25},
+ {0x26, 0x26},
+ {0x27, 0x27},
+ {0x28, 0x28},
+ {0x29, 0x29},
+ {0x2a, 0x2a},
+ {0x2b, 0x2b},
+ {0x2c, 0x2c},
+ {0x2d, 0x2d},
+ {0x2e, 0x2e},
+ {0x2f, 0x2f},
+ {0x30, 0x30},
+ {0x31, 0x31},
+ {0x32, 0x32},
+ {0x33, 0x33},
+ {0x34, 0x34},
+ {0x35, 0x35},
+ {0x36, 0x36},
+ {0x37, 0x37},
+ {0x38, 0x38},
+ {0x39, 0x39},
+ {0x3a, 0x3a},
+ {0x3b, 0x3b},
+ {0x3c, 0x3c},
+ {0x3d, 0x3d},
+ {0x3e, 0x3e},
+ {0x3f, 0x3f},
+ {0x40, 0x40},
+ {0x41, 0x41},
+ {0x42, 0x42},
+ {0x43, 0x43},
+ {0x44, 0x44},
+ {0x45, 0x45},
+ {0x46, 0x46},
+ {0x47, 0x47},
+ {0x48, 0x48},
+ {0x49, 0x49},
+ {0x4a, 0x4a},
+ {0x4b, 0x4b},
+ {0x4c, 0x4c},
+ {0x4d, 0x4d},
+ {0x4e, 0x4e},
+ {0x4f, 0x4f},
+ {0x50, 0x50},
+ {0x51, 0x51},
+ {0x52, 0x52},
+ {0x53, 0x53},
+ {0x54, 0x54},
+ {0x55, 0x55},
+ {0x56, 0x56},
+ {0x57, 0x57},
+ {0x58, 0x58},
+ {0x59, 0x59},
+ {0x5a, 0x5a},
+ {0x5b, 0x5b},
+ {0x5c, 0x5c},
+ {0x5d, 0x5d},
+ {0x5e, 0x5e},
+ {0x5f, 0x5f},
+ {0x60, 0x60},
+ {0x61, 0x61},
+ {0x62, 0x62},
+ {0x63, 0x63},
+ {0x64, 0x64},
+ {0x65, 0x65},
+ {0x66, 0x66},
+ {0x67, 0x67},
+ {0x68, 0x68},
+ {0x69, 0x69},
+ {0x6a, 0x6a},
+ {0x6b, 0x6b},
+ {0x6c, 0x6c},
+ {0x6d, 0x6d},
+ {0x6e, 0x6e},
+ {0x6f, 0x6f},
+ {0x70, 0x70},
+ {0x71, 0x71},
+ {0x72, 0x72},
+ {0x73, 0x73},
+ {0x74, 0x74},
+ {0x75, 0x75},
+ {0x76, 0x76},
+ {0x77, 0x77},
+ {0x78, 0x78},
+ {0x79, 0x79},
+ {0x7a, 0x7a},
+ {0x7b, 0x7b},
+ {0x7c, 0x7c},
+ {0x7d, 0x7d},
+ {0x7e, 0x7e},
+ {0xa0, 0xa0},
+ {0xa1, 0xa1},
+ {0xa2, 0xa2},
+ {0xa3, 0xa3},
+ {0xa4, 0xa4},
+ {0xa5, 0xa5},
+ {0xa6, 0xa6},
+ {0xa7, 0xa7},
+ {0xa8, 0xa8},
+ {0xa9, 0xa9},
+ {0xaa, 0xaa},
+ {0xab, 0xab},
+ {0xac, 0xac},
+ {0xad, 0xad},
+ {0xae, 0xae},
+ {0xaf, 0xaf},
+ {0xb0, 0xb0},
+ {0xb1, 0xb1},
+ {0xb2, 0xb2},
+ {0xb3, 0xb3},
+ {0xb4, 0xb4},
+ {0xb5, 0xb5},
+ {0xb6, 0xb6},
+ {0xb7, 0xb7},
+ {0xb8, 0xb8},
+ {0xb9, 0xb9},
+ {0xba, 0xba},
+ {0xbb, 0xbb},
+ {0xbc, 0xbc},
+ {0xbd, 0xbd},
+ {0xbe, 0xbe},
+ {0xbf, 0xbf},
+ {0xc0, 0xc0},
+ {0xc1, 0xc1},
+ {0xc2, 0xc2},
+ {0xc3, 0xc3},
+ {0xc4, 0xc4},
+ {0xc5, 0xc5},
+ {0xc6, 0xc6},
+ {0xc7, 0xc7},
+ {0xc8, 0xc8},
+ {0xc9, 0xc9},
+ {0xca, 0xca},
+ {0xcb, 0xcb},
+ {0xcc, 0xcc},
+ {0xcd, 0xcd},
+ {0xce, 0xce},
+ {0xcf, 0xcf},
+ {0xd0, 0xd0},
+ {0xd1, 0xd1},
+ {0xd2, 0xd2},
+ {0xd3, 0xd3},
+ {0xd4, 0xd4},
+ {0xd5, 0xd5},
+ {0xd6, 0xd6},
+ {0xd7, 0xd7},
+ {0xd8, 0xd8},
+ {0xd9, 0xd9},
+ {0xda, 0xda},
+ {0xdb, 0xdb},
+ {0xdc, 0xdc},
+ {0xdd, 0xdd},
+ {0xde, 0xde},
+ {0xdf, 0xdf},
+ {0xe0, 0xe0},
+ {0xe1, 0xe1},
+ {0xe2, 0xe2},
+ {0xe3, 0xe3},
+ {0xe4, 0xe4},
+ {0xe5, 0xe5},
+ {0xe6, 0xe6},
+ {0xe7, 0xe7},
+ {0xe8, 0xe8},
+ {0xe9, 0xe9},
+ {0xea, 0xea},
+ {0xeb, 0xeb},
+ {0xec, 0xec},
+ {0xed, 0xed},
+ {0xee, 0xee},
+ {0xef, 0xef},
+ {0xf0, 0xf0},
+ {0xf1, 0xf1},
+ {0xf2, 0xf2},
+ {0xf3, 0xf3},
+ {0xf4, 0xf4},
+ {0xf5, 0xf5},
+ {0xf6, 0xf6},
+ {0xf7, 0xf7},
+ {0xf8, 0xf8},
+ {0xf9, 0xf9},
+ {0xfa, 0xfa},
+ {0xfb, 0xfb},
+ {0xfc, 0xfc},
+ {0xfd, 0xfd},
+ {0xfe, 0xfe},
+ {0xff, 0xff},
+ {0x1a1, 0x104},
+ {0x1a2, 0x2d8},
+ {0x1a3, 0x141},
+ {0x1a5, 0x13d},
+ {0x1a6, 0x15a},
+ {0x1a9, 0x160},
+ {0x1aa, 0x15e},
+ {0x1ab, 0x164},
+ {0x1ac, 0x179},
+ {0x1ae, 0x17d},
+ {0x1af, 0x17b},
+ {0x1b1, 0x105},
+ {0x1b2, 0x2db},
+ {0x1b3, 0x142},
+ {0x1b5, 0x13e},
+ {0x1b6, 0x15b},
+ {0x1b7, 0x2c7},
+ {0x1b9, 0x161},
+ {0x1ba, 0x15f},
+ {0x1bb, 0x165},
+ {0x1bc, 0x17a},
+ {0x1bd, 0x2dd},
+ {0x1be, 0x17e},
+ {0x1bf, 0x17c},
+ {0x1c0, 0x154},
+ {0x1c3, 0x102},
+ {0x1c5, 0x139},
+ {0x1c6, 0x106},
+ {0x1c8, 0x10c},
+ {0x1ca, 0x118},
+ {0x1cc, 0x11a},
+ {0x1cf, 0x10e},
+ {0x1d0, 0x110},
+ {0x1d1, 0x143},
+ {0x1d2, 0x147},
+ {0x1d5, 0x150},
+ {0x1d8, 0x158},
+ {0x1d9, 0x16e},
+ {0x1db, 0x170},
+ {0x1de, 0x162},
+ {0x1e0, 0x155},
+ {0x1e3, 0x103},
+ {0x1e5, 0x13a},
+ {0x1e6, 0x107},
+ {0x1e8, 0x10d},
+ {0x1ea, 0x119},
+ {0x1ec, 0x11b},
+ {0x1ef, 0x10f},
+ {0x1f0, 0x111},
+ {0x1f1, 0x144},
+ {0x1f2, 0x148},
+ {0x1f5, 0x151},
+ {0x1f8, 0x159},
+ {0x1f9, 0x16f},
+ {0x1fb, 0x171},
+ {0x1fe, 0x163},
+ {0x1ff, 0x2d9},
+ {0x2a1, 0x126},
+ {0x2a6, 0x124},
+ {0x2a9, 0x130},
+ {0x2ab, 0x11e},
+ {0x2ac, 0x134},
+ {0x2b1, 0x127},
+ {0x2b6, 0x125},
+ {0x2b9, 0x131},
+ {0x2bb, 0x11f},
+ {0x2bc, 0x135},
+ {0x2c5, 0x10a},
+ {0x2c6, 0x108},
+ {0x2d5, 0x120},
+ {0x2d8, 0x11c},
+ {0x2dd, 0x16c},
+ {0x2de, 0x15c},
+ {0x2e5, 0x10b},
+ {0x2e6, 0x109},
+ {0x2f5, 0x121},
+ {0x2f8, 0x11d},
+ {0x2fd, 0x16d},
+ {0x2fe, 0x15d},
+ {0x3a2, 0x138},
+ {0x3a3, 0x156},
+ {0x3a5, 0x128},
+ {0x3a6, 0x13b},
+ {0x3aa, 0x112},
+ {0x3ab, 0x122},
+ {0x3ac, 0x166},
+ {0x3b3, 0x157},
+ {0x3b5, 0x129},
+ {0x3b6, 0x13c},
+ {0x3ba, 0x113},
+ {0x3bb, 0x123},
+ {0x3bc, 0x167},
+ {0x3bd, 0x14a},
+ {0x3bf, 0x14b},
+ {0x3c0, 0x100},
+ {0x3c7, 0x12e},
+ {0x3cc, 0x116},
+ {0x3cf, 0x12a},
+ {0x3d1, 0x145},
+ {0x3d2, 0x14c},
+ {0x3d3, 0x136},
+ {0x3d9, 0x172},
+ {0x3dd, 0x168},
+ {0x3de, 0x16a},
+ {0x3e0, 0x101},
+ {0x3e7, 0x12f},
+ {0x3ec, 0x117},
+ {0x3ef, 0x12b},
+ {0x3f1, 0x146},
+ {0x3f2, 0x14d},
+ {0x3f3, 0x137},
+ {0x3f9, 0x173},
+ {0x3fd, 0x169},
+ {0x3fe, 0x16b},
+ {0x47e, 0x203e},
+ {0x4a1, 0x3002},
+ {0x4a2, 0x300c},
+ {0x4a3, 0x300d},
+ {0x4a4, 0x3001},
+ {0x4a5, 0x30fb},
+ {0x4a6, 0x30f2},
+ {0x4a7, 0x30a1},
+ {0x4a8, 0x30a3},
+ {0x4a9, 0x30a5},
+ {0x4aa, 0x30a7},
+ {0x4ab, 0x30a9},
+ {0x4ac, 0x30e3},
+ {0x4ad, 0x30e5},
+ {0x4ae, 0x30e7},
+ {0x4af, 0x30c3},
+ {0x4b0, 0x30fc},
+ {0x4b1, 0x30a2},
+ {0x4b2, 0x30a4},
+ {0x4b3, 0x30a6},
+ {0x4b4, 0x30a8},
+ {0x4b5, 0x30aa},
+ {0x4b6, 0x30ab},
+ {0x4b7, 0x30ad},
+ {0x4b8, 0x30af},
+ {0x4b9, 0x30b1},
+ {0x4ba, 0x30b3},
+ {0x4bb, 0x30b5},
+ {0x4bc, 0x30b7},
+ {0x4bd, 0x30b9},
+ {0x4be, 0x30bb},
+ {0x4bf, 0x30bd},
+ {0x4c0, 0x30bf},
+ {0x4c1, 0x30c1},
+ {0x4c2, 0x30c4},
+ {0x4c3, 0x30c6},
+ {0x4c4, 0x30c8},
+ {0x4c5, 0x30ca},
+ {0x4c6, 0x30cb},
+ {0x4c7, 0x30cc},
+ {0x4c8, 0x30cd},
+ {0x4c9, 0x30ce},
+ {0x4ca, 0x30cf},
+ {0x4cb, 0x30d2},
+ {0x4cc, 0x30d5},
+ {0x4cd, 0x30d8},
+ {0x4ce, 0x30db},
+ {0x4cf, 0x30de},
+ {0x4d0, 0x30df},
+ {0x4d1, 0x30e0},
+ {0x4d2, 0x30e1},
+ {0x4d3, 0x30e2},
+ {0x4d4, 0x30e4},
+ {0x4d5, 0x30e6},
+ {0x4d6, 0x30e8},
+ {0x4d7, 0x30e9},
+ {0x4d8, 0x30ea},
+ {0x4d9, 0x30eb},
+ {0x4da, 0x30ec},
+ {0x4db, 0x30ed},
+ {0x4dc, 0x30ef},
+ {0x4dd, 0x30f3},
+ {0x4de, 0x309b},
+ {0x4df, 0x309c},
+ {0x5ac, 0x60c},
+ {0x5bb, 0x61b},
+ {0x5bf, 0x61f},
+ {0x5c1, 0x621},
+ {0x5c2, 0x622},
+ {0x5c3, 0x623},
+ {0x5c4, 0x624},
+ {0x5c5, 0x625},
+ {0x5c6, 0x626},
+ {0x5c7, 0x627},
+ {0x5c8, 0x628},
+ {0x5c9, 0x629},
+ {0x5ca, 0x62a},
+ {0x5cb, 0x62b},
+ {0x5cc, 0x62c},
+ {0x5cd, 0x62d},
+ {0x5ce, 0x62e},
+ {0x5cf, 0x62f},
+ {0x5d0, 0x630},
+ {0x5d1, 0x631},
+ {0x5d2, 0x632},
+ {0x5d3, 0x633},
+ {0x5d4, 0x634},
+ {0x5d5, 0x635},
+ {0x5d6, 0x636},
+ {0x5d7, 0x637},
+ {0x5d8, 0x638},
+ {0x5d9, 0x639},
+ {0x5da, 0x63a},
+ {0x5e0, 0x640},
+ {0x5e1, 0x641},
+ {0x5e2, 0x642},
+ {0x5e3, 0x643},
+ {0x5e4, 0x644},
+ {0x5e5, 0x645},
+ {0x5e6, 0x646},
+ {0x5e7, 0x647},
+ {0x5e8, 0x648},
+ {0x5e9, 0x649},
+ {0x5ea, 0x64a},
+ {0x5eb, 0x64b},
+ {0x5ec, 0x64c},
+ {0x5ed, 0x64d},
+ {0x5ee, 0x64e},
+ {0x5ef, 0x64f},
+ {0x5f0, 0x650},
+ {0x5f1, 0x651},
+ {0x5f2, 0x652},
+ {0x6a1, 0x452},
+ {0x6a2, 0x453},
+ {0x6a3, 0x451},
+ {0x6a4, 0x454},
+ {0x6a5, 0x455},
+ {0x6a6, 0x456},
+ {0x6a7, 0x457},
+ {0x6a8, 0x458},
+ {0x6a9, 0x459},
+ {0x6aa, 0x45a},
+ {0x6ab, 0x45b},
+ {0x6ac, 0x45c},
+ {0x6ae, 0x45e},
+ {0x6af, 0x45f},
+ {0x6b0, 0x2116},
+ {0x6b1, 0x402},
+ {0x6b2, 0x403},
+ {0x6b3, 0x401},
+ {0x6b4, 0x404},
+ {0x6b5, 0x405},
+ {0x6b6, 0x406},
+ {0x6b7, 0x407},
+ {0x6b8, 0x408},
+ {0x6b9, 0x409},
+ {0x6ba, 0x40a},
+ {0x6bb, 0x40b},
+ {0x6bc, 0x40c},
+ {0x6be, 0x40e},
+ {0x6bf, 0x40f},
+ {0x6c0, 0x44e},
+ {0x6c1, 0x430},
+ {0x6c2, 0x431},
+ {0x6c3, 0x446},
+ {0x6c4, 0x434},
+ {0x6c5, 0x435},
+ {0x6c6, 0x444},
+ {0x6c7, 0x433},
+ {0x6c8, 0x445},
+ {0x6c9, 0x438},
+ {0x6ca, 0x439},
+ {0x6cb, 0x43a},
+ {0x6cc, 0x43b},
+ {0x6cd, 0x43c},
+ {0x6ce, 0x43d},
+ {0x6cf, 0x43e},
+ {0x6d0, 0x43f},
+ {0x6d1, 0x44f},
+ {0x6d2, 0x440},
+ {0x6d3, 0x441},
+ {0x6d4, 0x442},
+ {0x6d5, 0x443},
+ {0x6d6, 0x436},
+ {0x6d7, 0x432},
+ {0x6d8, 0x44c},
+ {0x6d9, 0x44b},
+ {0x6da, 0x437},
+ {0x6db, 0x448},
+ {0x6dc, 0x44d},
+ {0x6dd, 0x449},
+ {0x6de, 0x447},
+ {0x6df, 0x44a},
+ {0x6e0, 0x42e},
+ {0x6e1, 0x410},
+ {0x6e2, 0x411},
+ {0x6e3, 0x426},
+ {0x6e4, 0x414},
+ {0x6e5, 0x415},
+ {0x6e6, 0x424},
+ {0x6e7, 0x413},
+ {0x6e8, 0x425},
+ {0x6e9, 0x418},
+ {0x6ea, 0x419},
+ {0x6eb, 0x41a},
+ {0x6ec, 0x41b},
+ {0x6ed, 0x41c},
+ {0x6ee, 0x41d},
+ {0x6ef, 0x41e},
+ {0x6f0, 0x41f},
+ {0x6f1, 0x42f},
+ {0x6f2, 0x420},
+ {0x6f3, 0x421},
+ {0x6f4, 0x422},
+ {0x6f5, 0x423},
+ {0x6f6, 0x416},
+ {0x6f7, 0x412},
+ {0x6f8, 0x42c},
+ {0x6f9, 0x42b},
+ {0x6fa, 0x417},
+ {0x6fb, 0x428},
+ {0x6fc, 0x42d},
+ {0x6fd, 0x429},
+ {0x6fe, 0x427},
+ {0x6ff, 0x42a},
+ {0x7a1, 0x386},
+ {0x7a2, 0x388},
+ {0x7a3, 0x389},
+ {0x7a4, 0x38a},
+ {0x7a5, 0x3aa},
+ {0x7a7, 0x38c},
+ {0x7a8, 0x38e},
+ {0x7a9, 0x3ab},
+ {0x7ab, 0x38f},
+ {0x7ae, 0x385},
+ {0x7af, 0x2015},
+ {0x7b1, 0x3ac},
+ {0x7b2, 0x3ad},
+ {0x7b3, 0x3ae},
+ {0x7b4, 0x3af},
+ {0x7b5, 0x3ca},
+ {0x7b6, 0x390},
+ {0x7b7, 0x3cc},
+ {0x7b8, 0x3cd},
+ {0x7b9, 0x3cb},
+ {0x7ba, 0x3b0},
+ {0x7bb, 0x3ce},
+ {0x7c1, 0x391},
+ {0x7c2, 0x392},
+ {0x7c3, 0x393},
+ {0x7c4, 0x394},
+ {0x7c5, 0x395},
+ {0x7c6, 0x396},
+ {0x7c7, 0x397},
+ {0x7c8, 0x398},
+ {0x7c9, 0x399},
+ {0x7ca, 0x39a},
+ {0x7cb, 0x39b},
+ {0x7cc, 0x39c},
+ {0x7cd, 0x39d},
+ {0x7ce, 0x39e},
+ {0x7cf, 0x39f},
+ {0x7d0, 0x3a0},
+ {0x7d1, 0x3a1},
+ {0x7d2, 0x3a3},
+ {0x7d4, 0x3a4},
+ {0x7d5, 0x3a5},
+ {0x7d6, 0x3a6},
+ {0x7d7, 0x3a7},
+ {0x7d8, 0x3a8},
+ {0x7d9, 0x3a9},
+ {0x7e1, 0x3b1},
+ {0x7e2, 0x3b2},
+ {0x7e3, 0x3b3},
+ {0x7e4, 0x3b4},
+ {0x7e5, 0x3b5},
+ {0x7e6, 0x3b6},
+ {0x7e7, 0x3b7},
+ {0x7e8, 0x3b8},
+ {0x7e9, 0x3b9},
+ {0x7ea, 0x3ba},
+ {0x7eb, 0x3bb},
+ {0x7ec, 0x3bc},
+ {0x7ed, 0x3bd},
+ {0x7ee, 0x3be},
+ {0x7ef, 0x3bf},
+ {0x7f0, 0x3c0},
+ {0x7f1, 0x3c1},
+ {0x7f2, 0x3c3},
+ {0x7f3, 0x3c2},
+ {0x7f4, 0x3c4},
+ {0x7f5, 0x3c5},
+ {0x7f6, 0x3c6},
+ {0x7f7, 0x3c7},
+ {0x7f8, 0x3c8},
+ {0x7f9, 0x3c9},
+ {0x8a1, 0x23b7},
+ {0x8a2, 0x250c},
+ {0x8a3, 0x2500},
+ {0x8a4, 0x2320},
+ {0x8a5, 0x2321},
+ {0x8a6, 0x2502},
+ {0x8a7, 0x23a1},
+ {0x8a8, 0x23a3},
+ {0x8a9, 0x23a4},
+ {0x8aa, 0x23a6},
+ {0x8ab, 0x239b},
+ {0x8ac, 0x239d},
+ {0x8ad, 0x239e},
+ {0x8ae, 0x23a0},
+ {0x8af, 0x23a8},
+ {0x8b0, 0x23ac},
+ {0x8bc, 0x2264},
+ {0x8bd, 0x2260},
+ {0x8be, 0x2265},
+ {0x8bf, 0x222b},
+ {0x8c0, 0x2234},
+ {0x8c1, 0x221d},
+ {0x8c2, 0x221e},
+ {0x8c5, 0x2207},
+ {0x8c8, 0x223c},
+ {0x8c9, 0x2243},
+ {0x8cd, 0x21d4},
+ {0x8ce, 0x21d2},
+ {0x8cf, 0x2261},
+ {0x8d6, 0x221a},
+ {0x8da, 0x2282},
+ {0x8db, 0x2283},
+ {0x8dc, 0x2229},
+ {0x8dd, 0x222a},
+ {0x8de, 0x2227},
+ {0x8df, 0x2228},
+ {0x8ef, 0x2202},
+ {0x8f6, 0x192},
+ {0x8fb, 0x2190},
+ {0x8fc, 0x2191},
+ {0x8fd, 0x2192},
+ {0x8fe, 0x2193},
+ {0x9e0, 0x25c6},
+ {0x9e1, 0x2592},
+ {0x9e2, 0x2409},
+ {0x9e3, 0x240c},
+ {0x9e4, 0x240d},
+ {0x9e5, 0x240a},
+ {0x9e8, 0x2424},
+ {0x9e9, 0x240b},
+ {0x9ea, 0x2518},
+ {0x9eb, 0x2510},
+ {0x9ec, 0x250c},
+ {0x9ed, 0x2514},
+ {0x9ee, 0x253c},
+ {0x9ef, 0x23ba},
+ {0x9f0, 0x23bb},
+ {0x9f1, 0x2500},
+ {0x9f2, 0x23bc},
+ {0x9f3, 0x23bd},
+ {0x9f4, 0x251c},
+ {0x9f5, 0x2524},
+ {0x9f6, 0x2534},
+ {0x9f7, 0x252c},
+ {0x9f8, 0x2502},
+ {0xaa1, 0x2003},
+ {0xaa2, 0x2002},
+ {0xaa3, 0x2004},
+ {0xaa4, 0x2005},
+ {0xaa5, 0x2007},
+ {0xaa6, 0x2008},
+ {0xaa7, 0x2009},
+ {0xaa8, 0x200a},
+ {0xaa9, 0x2014},
+ {0xaaa, 0x2013},
+ {0xaae, 0x2026},
+ {0xaaf, 0x2025},
+ {0xab0, 0x2153},
+ {0xab1, 0x2154},
+ {0xab2, 0x2155},
+ {0xab3, 0x2156},
+ {0xab4, 0x2157},
+ {0xab5, 0x2158},
+ {0xab6, 0x2159},
+ {0xab7, 0x215a},
+ {0xab8, 0x2105},
+ {0xabb, 0x2012},
+ {0xabc, 0x2329},
+ {0xabe, 0x232a},
+ {0xac3, 0x215b},
+ {0xac4, 0x215c},
+ {0xac5, 0x215d},
+ {0xac6, 0x215e},
+ {0xac9, 0x2122},
+ {0xaca, 0x2613},
+ {0xacc, 0x25c1},
+ {0xacd, 0x25b7},
+ {0xace, 0x25cb},
+ {0xacf, 0x25af},
+ {0xad0, 0x2018},
+ {0xad1, 0x2019},
+ {0xad2, 0x201c},
+ {0xad3, 0x201d},
+ {0xad4, 0x211e},
+ {0xad6, 0x2032},
+ {0xad7, 0x2033},
+ {0xad9, 0x271d},
+ {0xadb, 0x25ac},
+ {0xadc, 0x25c0},
+ {0xadd, 0x25b6},
+ {0xade, 0x25cf},
+ {0xadf, 0x25ae},
+ {0xae0, 0x25e6},
+ {0xae1, 0x25ab},
+ {0xae2, 0x25ad},
+ {0xae3, 0x25b3},
+ {0xae4, 0x25bd},
+ {0xae5, 0x2606},
+ {0xae6, 0x2022},
+ {0xae7, 0x25aa},
+ {0xae8, 0x25b2},
+ {0xae9, 0x25bc},
+ {0xaea, 0x261c},
+ {0xaeb, 0x261e},
+ {0xaec, 0x2663},
+ {0xaed, 0x2666},
+ {0xaee, 0x2665},
+ {0xaf0, 0x2720},
+ {0xaf1, 0x2020},
+ {0xaf2, 0x2021},
+ {0xaf3, 0x2713},
+ {0xaf4, 0x2717},
+ {0xaf5, 0x266f},
+ {0xaf6, 0x266d},
+ {0xaf7, 0x2642},
+ {0xaf8, 0x2640},
+ {0xaf9, 0x260e},
+ {0xafa, 0x2315},
+ {0xafb, 0x2117},
+ {0xafc, 0x2038},
+ {0xafd, 0x201a},
+ {0xafe, 0x201e},
+ {0xba3, 0x3c},
+ {0xba6, 0x3e},
+ {0xba8, 0x2228},
+ {0xba9, 0x2227},
+ {0xbc0, 0xaf},
+ {0xbc2, 0x22a5},
+ {0xbc3, 0x2229},
+ {0xbc4, 0x230a},
+ {0xbc6, 0x5f},
+ {0xbca, 0x2218},
+ {0xbcc, 0x2395},
+ {0xbce, 0x22a4},
+ {0xbcf, 0x25cb},
+ {0xbd3, 0x2308},
+ {0xbd6, 0x222a},
+ {0xbd8, 0x2283},
+ {0xbda, 0x2282},
+ {0xbdc, 0x22a2},
+ {0xbfc, 0x22a3},
+ {0xcdf, 0x2017},
+ {0xce0, 0x5d0},
+ {0xce1, 0x5d1},
+ {0xce2, 0x5d2},
+ {0xce3, 0x5d3},
+ {0xce4, 0x5d4},
+ {0xce5, 0x5d5},
+ {0xce6, 0x5d6},
+ {0xce7, 0x5d7},
+ {0xce8, 0x5d8},
+ {0xce9, 0x5d9},
+ {0xcea, 0x5da},
+ {0xceb, 0x5db},
+ {0xcec, 0x5dc},
+ {0xced, 0x5dd},
+ {0xcee, 0x5de},
+ {0xcef, 0x5df},
+ {0xcf0, 0x5e0},
+ {0xcf1, 0x5e1},
+ {0xcf2, 0x5e2},
+ {0xcf3, 0x5e3},
+ {0xcf4, 0x5e4},
+ {0xcf5, 0x5e5},
+ {0xcf6, 0x5e6},
+ {0xcf7, 0x5e7},
+ {0xcf8, 0x5e8},
+ {0xcf9, 0x5e9},
+ {0xcfa, 0x5ea},
+ {0xda1, 0xe01},
+ {0xda2, 0xe02},
+ {0xda3, 0xe03},
+ {0xda4, 0xe04},
+ {0xda5, 0xe05},
+ {0xda6, 0xe06},
+ {0xda7, 0xe07},
+ {0xda8, 0xe08},
+ {0xda9, 0xe09},
+ {0xdaa, 0xe0a},
+ {0xdab, 0xe0b},
+ {0xdac, 0xe0c},
+ {0xdad, 0xe0d},
+ {0xdae, 0xe0e},
+ {0xdaf, 0xe0f},
+ {0xdb0, 0xe10},
+ {0xdb1, 0xe11},
+ {0xdb2, 0xe12},
+ {0xdb3, 0xe13},
+ {0xdb4, 0xe14},
+ {0xdb5, 0xe15},
+ {0xdb6, 0xe16},
+ {0xdb7, 0xe17},
+ {0xdb8, 0xe18},
+ {0xdb9, 0xe19},
+ {0xdba, 0xe1a},
+ {0xdbb, 0xe1b},
+ {0xdbc, 0xe1c},
+ {0xdbd, 0xe1d},
+ {0xdbe, 0xe1e},
+ {0xdbf, 0xe1f},
+ {0xdc0, 0xe20},
+ {0xdc1, 0xe21},
+ {0xdc2, 0xe22},
+ {0xdc3, 0xe23},
+ {0xdc4, 0xe24},
+ {0xdc5, 0xe25},
+ {0xdc6, 0xe26},
+ {0xdc7, 0xe27},
+ {0xdc8, 0xe28},
+ {0xdc9, 0xe29},
+ {0xdca, 0xe2a},
+ {0xdcb, 0xe2b},
+ {0xdcc, 0xe2c},
+ {0xdcd, 0xe2d},
+ {0xdce, 0xe2e},
+ {0xdcf, 0xe2f},
+ {0xdd0, 0xe30},
+ {0xdd1, 0xe31},
+ {0xdd2, 0xe32},
+ {0xdd3, 0xe33},
+ {0xdd4, 0xe34},
+ {0xdd5, 0xe35},
+ {0xdd6, 0xe36},
+ {0xdd7, 0xe37},
+ {0xdd8, 0xe38},
+ {0xdd9, 0xe39},
+ {0xdda, 0xe3a},
+ {0xddf, 0xe3f},
+ {0xde0, 0xe40},
+ {0xde1, 0xe41},
+ {0xde2, 0xe42},
+ {0xde3, 0xe43},
+ {0xde4, 0xe44},
+ {0xde5, 0xe45},
+ {0xde6, 0xe46},
+ {0xde7, 0xe47},
+ {0xde8, 0xe48},
+ {0xde9, 0xe49},
+ {0xdea, 0xe4a},
+ {0xdeb, 0xe4b},
+ {0xdec, 0xe4c},
+ {0xded, 0xe4d},
+ {0xdf0, 0xe50},
+ {0xdf1, 0xe51},
+ {0xdf2, 0xe52},
+ {0xdf3, 0xe53},
+ {0xdf4, 0xe54},
+ {0xdf5, 0xe55},
+ {0xdf6, 0xe56},
+ {0xdf7, 0xe57},
+ {0xdf8, 0xe58},
+ {0xdf9, 0xe59},
+ {0xea1, 0x3131},
+ {0xea2, 0x3132},
+ {0xea3, 0x3133},
+ {0xea4, 0x3134},
+ {0xea5, 0x3135},
+ {0xea6, 0x3136},
+ {0xea7, 0x3137},
+ {0xea8, 0x3138},
+ {0xea9, 0x3139},
+ {0xeaa, 0x313a},
+ {0xeab, 0x313b},
+ {0xeac, 0x313c},
+ {0xead, 0x313d},
+ {0xeae, 0x313e},
+ {0xeaf, 0x313f},
+ {0xeb0, 0x3140},
+ {0xeb1, 0x3141},
+ {0xeb2, 0x3142},
+ {0xeb3, 0x3143},
+ {0xeb4, 0x3144},
+ {0xeb5, 0x3145},
+ {0xeb6, 0x3146},
+ {0xeb7, 0x3147},
+ {0xeb8, 0x3148},
+ {0xeb9, 0x3149},
+ {0xeba, 0x314a},
+ {0xebb, 0x314b},
+ {0xebc, 0x314c},
+ {0xebd, 0x314d},
+ {0xebe, 0x314e},
+ {0xebf, 0x314f},
+ {0xec0, 0x3150},
+ {0xec1, 0x3151},
+ {0xec2, 0x3152},
+ {0xec3, 0x3153},
+ {0xec4, 0x3154},
+ {0xec5, 0x3155},
+ {0xec6, 0x3156},
+ {0xec7, 0x3157},
+ {0xec8, 0x3158},
+ {0xec9, 0x3159},
+ {0xeca, 0x315a},
+ {0xecb, 0x315b},
+ {0xecc, 0x315c},
+ {0xecd, 0x315d},
+ {0xece, 0x315e},
+ {0xecf, 0x315f},
+ {0xed0, 0x3160},
+ {0xed1, 0x3161},
+ {0xed2, 0x3162},
+ {0xed3, 0x3163},
+ {0xed4, 0x11a8},
+ {0xed5, 0x11a9},
+ {0xed6, 0x11aa},
+ {0xed7, 0x11ab},
+ {0xed8, 0x11ac},
+ {0xed9, 0x11ad},
+ {0xeda, 0x11ae},
+ {0xedb, 0x11af},
+ {0xedc, 0x11b0},
+ {0xedd, 0x11b1},
+ {0xede, 0x11b2},
+ {0xedf, 0x11b3},
+ {0xee0, 0x11b4},
+ {0xee1, 0x11b5},
+ {0xee2, 0x11b6},
+ {0xee3, 0x11b7},
+ {0xee4, 0x11b8},
+ {0xee5, 0x11b9},
+ {0xee6, 0x11ba},
+ {0xee7, 0x11bb},
+ {0xee8, 0x11bc},
+ {0xee9, 0x11bd},
+ {0xeea, 0x11be},
+ {0xeeb, 0x11bf},
+ {0xeec, 0x11c0},
+ {0xeed, 0x11c1},
+ {0xeee, 0x11c2},
+ {0xeef, 0x316d},
+ {0xef0, 0x3171},
+ {0xef1, 0x3178},
+ {0xef2, 0x317f},
+ {0xef3, 0x3181},
+ {0xef4, 0x3184},
+ {0xef5, 0x3186},
+ {0xef6, 0x318d},
+ {0xef7, 0x318e},
+ {0xef8, 0x11eb},
+ {0xef9, 0x11f0},
+ {0xefa, 0x11f9},
+ {0xeff, 0x20a9},
+ {0x13a4, 0x20ac},
+ {0x13bc, 0x152},
+ {0x13bd, 0x153},
+ {0x13be, 0x178},
+ {0x20a0, 0x20a0},
+ {0x20a1, 0x20a1},
+ {0x20a2, 0x20a2},
+ {0x20a3, 0x20a3},
+ {0x20a4, 0x20a4},
+ {0x20a5, 0x20a5},
+ {0x20a6, 0x20a6},
+ {0x20a7, 0x20a7},
+ {0x20a8, 0x20a8},
+ {0x20aa, 0x20aa},
+ {0x20ab, 0x20ab},
+ {0x20ac, 0x20ac},
+};
+
+int keysym_to_unicode(int keysym)
+{
+ int i, j, k;
+
+ i = -1;
+ j = lenof(keysyms);
+
+ while (j - i >= 2) {
+ k = (j + i) / 2;
+ if (keysyms[k].keysym == keysym)
+ return keysyms[k].unicode;
+ else if (keysyms[k].keysym < keysym)
+ i = k;
+ else
+ j = k;
+ }
+ return -1;
+}
--- /dev/null
+/* XPM */
+static const char *const cfg_icon_0[] = {
+/* columns rows colors chars-per-pixel */
+"16 16 9 1",
+" c black",
+". c navy",
+"X c blue",
+"o c #808000",
+"O c yellow",
+"+ c #808080",
+"@ c #C0C0C0",
+"# c gray100",
+"$ c None",
+/* pixels */
+"$$$ $$$$$$$$$$$",
+"$$ OO $$$$",
+"$ +oO+###@+ $$$",
+" o #.oO.XX@+ $$$",
+" oO+.OO.XX@+ $$$",
+"$ oOOOO.XX@+ $$$",
+"$$ oooOO.X@+ $$$",
+"$$ +..oOO.@+ $$$",
+"$$ @@@+oOO++ $$",
+"$ +++++ oOO #+ $",
+" #######+oOO++ $",
+" #@@@@@++ oOO $",
+" @++++++++ oOO $",
+"$ oOO ",
+"$$$$$$$$$$$$ oO ",
+"$$$$$$$$$$$$$ $"
+};
+
+/* XPM */
+static const char *const cfg_icon_1[] = {
+/* columns rows colors chars-per-pixel */
+"32 32 9 1",
+" c black",
+". c navy",
+"X c blue",
+"o c #808000",
+"O c yellow",
+"+ c #808080",
+"@ c #C0C0C0",
+"# c gray100",
+"$ c None",
+/* pixels */
+"$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$",
+"$$$$$$ $$$$$$$$$$$$$$$$$$$$$$$$",
+"$$$$$ OO $$$$$$$$$$$$$$$$$$$$$$",
+"$$$$$ ooOO $$$$$$$$$$$$$$$$$$$$$",
+"$$$$$$ ooOO $$$$$$",
+"$$ $$$ oOO @@@@@@@@@@@@@+ $$$$$",
+"$ oO $$ oOOO @@@@@@@@@@@++ $$$$$",
+"$ oOO oOOOO #########@+++ $$$$$",
+"$$ oOOOOOOO ..........@+++ $$$$$",
+"$$ ooOOOOOOO XXXXXXXXX@+++ $$$$$",
+"$$$ ooooooOOO XXXXXXXX@+++ $$$$$",
+"$$$$ oo ooOOO XXXXXXX@+++ $$$$$",
+"$$$$$$ . ooOOO XXXXXX@+++ $$$$$",
+"$$$$$$ #.X ooOOO XXXXX@+++ $$$$$",
+"$$$$$$ #.XX ooOOO XXXX@+++ $$$$$",
+"$$$$$$ #.XXX ooOOO XXX@+++ $$$$$",
+"$$$$$$ #.XXXX ooOOO XX@+++ $$$$$",
+"$$$$$$ ####### ooOOO #@+++ $$$",
+"$$$$$ #@@@@@@@ ooOOO +++ @#+ $$",
+"$$$$ @ @++++++++ ooOOO + @#++ $$",
+"$$$ @@ ooOOO @#+++ $$",
+"$$ ############### ooOOO @+++ $$",
+"$$ #@@@@@@@@@@@@@@@ ooOOO +++ $$",
+"$$ #@@@@@@@@@@@@@@@@ ooOOO + $$$",
+"$$ #@@@@@@@@@@@@+ ooOOO $$$$",
+"$$ @++++++++++++++++++ ooOOO $$$",
+"$$$ ooOOO $$",
+"$$$$$$$$$$$$$$$$$$$$$$$$ ooO $$$",
+"$$$$$$$$$$$$$$$$$$$$$$$$$ o $$$$",
+"$$$$$$$$$$$$$$$$$$$$$$$$$$ $$$$$",
+"$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$",
+"$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$"
+};
+
+/* XPM */
+static const char *const cfg_icon_2[] = {
+/* columns rows colors chars-per-pixel */
+"48 48 9 1",
+" c black",
+". c navy",
+"X c blue",
+"o c #808000",
+"O c yellow",
+"+ c #808080",
+"@ c #C0C0C0",
+"# c gray100",
+"$ c None",
+/* pixels */
+"$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$",
+"$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$",
+"$$$$$$$$$ $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$",
+"$$$$$$$$ OO $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$",
+"$$$$$$$$ oOOOO $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$",
+"$$$$$$$$$ ooOOO $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$",
+"$$$$$$$$$$ ooOOO $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$",
+"$$$$$$$$$$$ oOOO $$$$$$$$$$",
+"$$$ $$$$$$ oOOO @@@@@@@@@@@@@@@@@@@@+ $$$$$$$$$",
+"$$ oO $$$$$ oOOOO @@@@@@@@@@@@@@@@@@++ $$$$$$$$$",
+"$$ ooO $$$ oOOOO @@@@@@@@@@@@@@@@@+++ $$$$$$$$$",
+"$$$ oOO OOOOO ################@++++ $$$$$$$$$",
+"$$$ ooOOOOOOOOOOO ++++++++++++++@+++++ $$$$$$$$$",
+"$$$ ooOOOOOOOOOOOO .............#+++++ $$$$$$$$$",
+"$$$$ oooOOOOoOOOOOO XXXXXXXXXXXX#+++++ $$$$$$$$$",
+"$$$$$ oooooooOOOOOOO XXXXXXXXXXX#+++++ $$$$$$$$$",
+"$$$$$$ oo ooOOOOOOO XXXXXXXXXX#+++++ $$$$$$$$$",
+"$$$$$$$$$ + ooOOOOOOO XXXXXXXXX#+++++ $$$$$$$$$",
+"$$$$$$$$$ #+. ooOOOOOOO XXXXXXXX#+++++ $$$$$$$$$",
+"$$$$$$$$$ #+.X ooOOOOOOO XXXXXXX#+++++ $$$$$$$$$",
+"$$$$$$$$$ #+.XX ooOOOOOOO XXXXXX#+++++ $$$$$$$$$",
+"$$$$$$$$$ #+.XXX ooOOOOOOO XXXXX#+++++ $$$$$$$$$",
+"$$$$$$$$$ #+.XXXX ooOOOOOOO XXXX#+++++ $$$$$$$$$",
+"$$$$$$$$$ #+.XXXXX ooOOOOOOO XXX#+++++ $$$$$$$$$",
+"$$$$$$$$$ #+.XXXXXX ooOOOOOOO XX#+++++ $$$$$$$$$",
+"$$$$$$$$$ #+.XXXXXXX ooOOOOOOO X#+++++ $$$$$$$$$",
+"$$$$$$$$$ #+.XXXXXXXX ooOOOOOOO #+++++ $$$$$$$$$",
+"$$$$$$$$ #@########## ooOOOOOOO +++++ $$$$$",
+"$$$$$$$ @ #@@@@@@@@@@@@ ooOOOOOOO +++ @@##+ $$$$",
+"$$$$$$ @@ #@@@@@@@@@@@@@ ooOOOOOOO + @@##++ $$$$",
+"$$$$$ @@@ @++++++++++++++ ooOOOOOOO @@##+++ $$$$",
+"$$$$ @@@@ ooOOOOOOO ##++++ $$$$",
+"$$$ ####################### ooOOOOOOO @++++ $$$$",
+"$$$ ######################## ooOOOOOOO ++++ $$$$",
+"$$$ ##@@@@@@@@@@@@@@@@@@@@@@@ ooOOOOOOO +++ $$$$",
+"$$$ ##@@@@@@@@@@@@@@@@@@@@@@@@ ooOOOOOOO ++ $$$$",
+"$$$ ##@@@@@@@@@@@@@@@@@@@@@@@@@ ooOOOOOOO $$$$$",
+"$$$ ##@@@@@@@@@@@@@@@@@@ ooOOOOOOO $$$$$",
+"$$$ @@+++++++++++++++++++++++++++ ooOOOOOOO $$$$",
+"$$$ @@++++++++++++++++++++++++++++ ooOOOOOOO $$$",
+"$$$$ ooOOOOO $$$$",
+"$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ ooOOO $$$$$",
+"$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ ooO $$$$$$",
+"$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ o $$$$$$$",
+"$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $$$$$$$$",
+"$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$",
+"$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$",
+"$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$"
+};
+
+const char *const *const cfg_icon[] = {
+ cfg_icon_0,
+ cfg_icon_1,
+ cfg_icon_2,
+};
+const int n_cfg_icon = 3;
--- /dev/null
+/* XPM */
+static const char *const main_icon_0[] = {
+/* columns rows colors chars-per-pixel */
+"16 16 6 1",
+" c black",
+". c blue",
+"X c #808080",
+"o c #C0C0C0",
+"O c gray100",
+"+ c None",
+/* pixels */
+"++++++++++++++++",
+"+++ ++++",
+"++ OOOOOOOoX +++",
+"++ O......oX +++",
+"++ O......oX +++",
+"++ O......oX +++",
+"++ O......oX +++",
+"++ O......oX +++",
+"++ ooooooooX ++",
+"+ XXXXXXXXXXOX +",
+" OOOOOOOOOOOoX +",
+" OoooooXXXXoXX +",
+" oXXXXXXXXXXX ++",
+"+ +++",
+"++++++++++++++++",
+"++++++++++++++++"
+};
+
+/* XPM */
+static const char *const main_icon_1[] = {
+/* columns rows colors chars-per-pixel */
+"32 32 7 1",
+" c black",
+". c navy",
+"X c blue",
+"o c #808080",
+"O c #C0C0C0",
+"+ c gray100",
+"@ c None",
+/* pixels */
+"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@",
+"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@",
+"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@",
+"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@",
+"@@@@@@@@@ @@@@@@",
+"@@@@@@@@ OOOOOOOOOOOOOOOOo @@@@@",
+"@@@@@@@ OOOOOOOOOOOOOOOOoo @@@@@",
+"@@@@@@ +++++++++++++++Oooo @@@@@",
+"@@@@@@ +..............Oooo @@@@@",
+"@@@@@@ +.XXXXXXXXXXXXXOooo @@@@@",
+"@@@@@@ +.XXXXXXXXXXXXXOooo @@@@@",
+"@@@@@@ +.XXXXXXXXXXXXXOooo @@@@@",
+"@@@@@@ +.XXXXXXXXXXXXXOooo @@@@@",
+"@@@@@@ +.XXXXXXXXXXXXXOooo @@@@@",
+"@@@@@@ +.XXXXXXXXXXXXXOooo @@@@@",
+"@@@@@@ +.XXXXXXXXXXXXXOooo @@@@@",
+"@@@@@@ +.XXXXXXXXXXXXXOooo @@@@@",
+"@@@@@@ +++++++++++++++Oooo @@@",
+"@@@@@ +OOOOOOOOOOOOOOooo O+o @@",
+"@@@@ O Ooooooooooooooooo O+oo @@",
+"@@@ OO O+ooo @@",
+"@@ ++++++++++++++++++++++Oooo @@",
+"@@ +OOOOOOOOOOOOOOOOOOOOOoooo @@",
+"@@ +OOOOOOOOOOOOOOOOOOOOOooo @@@",
+"@@ +OOOOOOOOOOOOo oOoo @@@@",
+"@@ Ooooooooooooooooooooooo @@@@@",
+"@@@ @@@@@@",
+"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@",
+"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@",
+"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@",
+"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@",
+"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"
+};
+
+/* XPM */
+static const char *const main_icon_2[] = {
+/* columns rows colors chars-per-pixel */
+"48 48 7 1",
+" c black",
+". c navy",
+"X c blue",
+"o c #808080",
+"O c #C0C0C0",
+"+ c gray100",
+"@ c None",
+/* pixels */
+"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@",
+"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@",
+"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@",
+"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@",
+"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@",
+"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@",
+"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@",
+"@@@@@@@@@@@@@ @@@@@@@@@@",
+"@@@@@@@@@@@@ OOOOOOOOOOOOOOOOOOOOOOOOo @@@@@@@@@",
+"@@@@@@@@@@@ OOOOOOOOOOOOOOOOOOOOOOOOoo @@@@@@@@@",
+"@@@@@@@@@@ OOOOOOOOOOOOOOOOOOOOOOOOooo @@@@@@@@@",
+"@@@@@@@@@ +++++++++++++++++++++++Ooooo @@@@@@@@@",
+"@@@@@@@@@ +oooooooooooooooooooooOooooo @@@@@@@@@",
+"@@@@@@@@@ +o....................+ooooo @@@@@@@@@",
+"@@@@@@@@@ +o.XXXXXXXXXXXXXXXXXXX+ooooo @@@@@@@@@",
+"@@@@@@@@@ +o.XXXXXXXXXXXXXXXXXXX+ooooo @@@@@@@@@",
+"@@@@@@@@@ +o.XXXXXXXXXXXXXXXXXXX+ooooo @@@@@@@@@",
+"@@@@@@@@@ +o.XXXXXXXXXXXXXXXXXXX+ooooo @@@@@@@@@",
+"@@@@@@@@@ +o.XXXXXXXXXXXXXXXXXXX+ooooo @@@@@@@@@",
+"@@@@@@@@@ +o.XXXXXXXXXXXXXXXXXXX+ooooo @@@@@@@@@",
+"@@@@@@@@@ +o.XXXXXXXXXXXXXXXXXXX+ooooo @@@@@@@@@",
+"@@@@@@@@@ +o.XXXXXXXXXXXXXXXXXXX+ooooo @@@@@@@@@",
+"@@@@@@@@@ +o.XXXXXXXXXXXXXXXXXXX+ooooo @@@@@@@@@",
+"@@@@@@@@@ +o.XXXXXXXXXXXXXXXXXXX+ooooo @@@@@@@@@",
+"@@@@@@@@@ +o.XXXXXXXXXXXXXXXXXXX+ooooo @@@@@@@@@",
+"@@@@@@@@@ +o.XXXXXXXXXXXXXXXXXXX+ooooo @@@@@@@@@",
+"@@@@@@@@@ +o.XXXXXXXXXXXXXXXXXXX+ooooo @@@@@@@@@",
+"@@@@@@@@ +O+++++++++++++++++++++ooooo @@@@@",
+"@@@@@@@ O +OOOOOOOOOOOOOOOOOOOOOOoooo OO++o @@@@",
+"@@@@@@ OO +OOOOOOOOOOOOOOOOOOOOOOooo OO++oo @@@@",
+"@@@@@ OOO Ooooooooooooooooooooooooo OO++ooo @@@@",
+"@@@@ OOOO OO++oooo @@@@",
+"@@@ ++++++++++++++++++++++++++++++++++Ooooo @@@@",
+"@@@ +++++++++++++++++++++++++++++++++Oooooo @@@@",
+"@@@ ++OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOoooooo @@@@",
+"@@@ ++OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOoooooo @@@@",
+"@@@ ++OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOooooo @@@@@",
+"@@@ ++OOOOOOOOOOOOOOOOOO oOOoooo @@@@@@",
+"@@@ OOoooooooooooooooooooooooooooooooooo @@@@@@@",
+"@@@ OOooooooooooooooooooooooooooooooooo @@@@@@@@",
+"@@@@ @@@@@@@@@",
+"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@",
+"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@",
+"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@",
+"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@",
+"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@",
+"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@",
+"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"
+};
+
+const char *const *const main_icon[] = {
+ main_icon_0,
+ main_icon_1,
+ main_icon_2,
+};
+const int n_main_icon = 3;
--- /dev/null
+/* XPM */
+static const char *const cfg_icon_0[] = {
+/* columns rows colors chars-per-pixel */
+"16 16 9 1",
+" c black",
+". c navy",
+"X c blue",
+"o c #808000",
+"O c yellow",
+"+ c #808080",
+"@ c #C0C0C0",
+"# c gray100",
+"$ c None",
+/* pixels */
+"$$$ $$ $$",
+"$$ OO #####@+ $",
+"$ $ oO #XX..@+ $",
+" o $ oO+X.O.@+ $",
+" oO OO .O.X@+ $",
+"$ oOOOOoO++@@+ $",
+"$$ oooOOoOO +++ ",
+"$ # oooOO +++++ ",
+"$ #X..ooOO +++ $",
+"$ #X.O. oOO $$",
+"$ #.O.X@ oOO $$$",
+"$ @++@@@+ oOO $$",
+"$ ++++++++ oOO $",
+" #####++++ oOO ",
+" @+++++++ $$ oO ",
+"$ $$$$ $"
+};
+
+/* XPM */
+static const char *const cfg_icon_1[] = {
+/* columns rows colors chars-per-pixel */
+"32 32 9 1",
+" c black",
+". c navy",
+"X c blue",
+"o c #808000",
+"O c yellow",
+"+ c #808080",
+"@ c #C0C0C0",
+"# c gray100",
+"$ c None",
+/* pixels */
+"$$$$$$$$$$$$$$$$ $$$$",
+"$$$$$$ $$$$$$$ @@@@@@@@@@@+ $$$",
+"$$$$$ OO $$$$ ##########@++ $$$",
+"$$$$$ ooOO $$$ #.........@++ $$$",
+"$$$$$$ ooOO $$ #.XXXXXXXX@++ $$$",
+"$$ $$$ oOO $$ #.XXXX XX@++ $$$",
+"$ oO $$ oOOO $ #.XXX O XX@++ $$$",
+"$ oOO oOOOO $ #.X O XXX@++ $$$",
+"$$ oOOOOOOO $$ #. OO XXXX@++ $$$",
+"$$ ooOOOOOOO $ # OO XXXXX@++ $$$",
+"$$$ ooooooOOO OO ######@++ $",
+"$$$$ oo ooOOO OO +++++++++ @#+ ",
+"$$$$$$ $ ooOOO @#++ ",
+"$$$$$$$$$$ ooOOO OOOO ######@++ ",
+"$$$$$ O ooOOO O @@@@@@@+++ ",
+"$$$$ @@@@@ ooOOO @@+ +@++ $",
+"$$$ ######### ooOOO +++++++++ $$",
+"$$$ #....... O ooOOO $$$",
+"$$$ #.XXXXX OO ooOOO $$$$$$$$$$",
+"$$$ #.XXXX OO @+ ooOOO $$$$$$$$$",
+"$$$ #.XXX O X@++ ooOOO $$$$$$$$",
+"$$$ #.XX O XXX@++ ooOOO $$$$$$$",
+"$$$ #.XX XXXX@++ $ ooOOO $$$$$$",
+"$$$ #.XXXXXXXX@++ $$ ooOOO $$$$$",
+"$$$ ##########@++ $ ooOOO $$$$",
+"$$ @+++++++++++ @#+ $ ooOOO $$$",
+"$ @ @#++ $$ ooOOO $$",
+" ################@++ $$$ ooO $$$",
+" #@@@@@@@@@@@@@@@+++ $$$$ o $$$$",
+" #@@@@@@@@+ +@++ $$$$$$ $$$$$",
+" @++++++++++++++++ $$$$$$$$$$$$$",
+"$ $$$$$$$$$$$$$$"
+};
+
+/* XPM */
+static const char *const cfg_icon_2[] = {
+/* columns rows colors chars-per-pixel */
+"48 48 9 1",
+" c black",
+". c navy",
+"X c blue",
+"o c #808000",
+"O c yellow",
+"+ c #808080",
+"@ c #C0C0C0",
+"# c gray100",
+"$ c None",
+/* pixels */
+"$$$$$$$$$$$$$$$$$$$$$$$$$ $$$$$",
+"$$$$$$$$$$$$$$$$$$$$$$$$ @@@@@@@@@@@@@@@@@+ $$$$",
+"$$$$$$$$$ $$$$$$$$$$$$ @@@@@@@@@@@@@@@@@++ $$$$",
+"$$$$$$$$ OO $$$$$$$$ ################@+++ $$$$",
+"$$$$$$$$ oOOOO $$$$$$$ #++++++++++++++@++++ $$$$",
+"$$$$$$$$$ ooOOO $$$$$$ #+.............#++++ $$$$",
+"$$$$$$$$$$ ooOOO $$$$$ #+.XXXXXXXXXXXX#++++ $$$$",
+"$$$$$$$$$$$ oOOO $$$$$ #+.XXXXXXXXXXXX#++++ $$$$",
+"$$$ $$$$$$ oOOO $$$$$ #+.XXXXXXX XXX#++++ $$$$",
+"$$ oO $$$$$ oOOOO $$$$ #+.XXXXXX O XXX#++++ $$$$",
+"$$ ooO $$$$ oOOOO $$$$ #+.XXXXX O XXXX#++++ $$$$",
+"$$$ oOO OOOOO $$$$$ #+.XXX O XXXXX#++++ $$$$",
+"$$$ ooOOOOOOOOOOO $$$$ #+.XX OO XXXXXX#++++ $$$$",
+"$$$ ooOOOOOOOOOOOO $$$ #+.X OO XXXXXXX#++++ $$$$",
+"$$$$ oooOOOOoOOOOOO $$ #@ OO #########++++ $",
+"$$$$$ oooooooOOOOOOO # OOO @@@@@@@@@@+++ @##+ ",
+"$$$$$$ oo ooOOOOOOO OO +++++++++++++ @##++ ",
+"$$$$$$$$$ $ ooOOOOOOO OO @##+++ ",
+"$$$$$$$$$$$$$ ooOOOOOOO ############@+++ ",
+"$$$$$$$$$$$$$$ ooOOOOOOO OOOOOO ##########@++++ ",
+"$$$$$$$$$$$$$$$ ooOOOOOOO OOO @@+ @++++ $",
+"$$$$$$$$$$$$$$$$ ooOOOOOOO O ++++++++++++++++ $$",
+"$$$$$$$$$$$$$$$ O ooOOOOOOO ++++++++++++++++ $$$",
+"$$$$$$$$$$$$$$$$ ooOOOOOOO $$$$",
+"$$$$$$$ ooOOOOOOO $$$$$$$$$$$$$$$$$$",
+"$$$$$$ @@@@@@@@@@@@ ooOOOOOOO $$$$$$$$$$$$$$$$$",
+"$$$$$ @@@@@@@@@@@@ OO ooOOOOOOO $$$$$$$$$$$$$$$$",
+"$$$$ ############ OO ooOOOOOOO $$$$$$$$$$$$$$$",
+"$$$$ #++++++++++ OO @++ ooOOOOOOO $$$$$$$$$$$$$$",
+"$$$$ #+........ OO .#+++ ooOOOOOOO $$$$$$$$$$$$$",
+"$$$$ #+.XXXXXX O XX#++++ ooOOOOOOO $$$$$$$$$$$$",
+"$$$$ #+.XXXXX O XXXX#++++ ooOOOOOOO $$$$$$$$$$$",
+"$$$$ #+.XXXX O XXXXX#++++ $ ooOOOOOOO $$$$$$$$$$",
+"$$$$ #+.XXXX XXXXXX#++++ $$ ooOOOOOOO $$$$$$$$$",
+"$$$$ #+.XXXXXXXXXXXX#++++ $$$ ooOOOOOOO $$$$$$$$",
+"$$$$ #+.XXXXXXXXXXXX#++++ $$$$ ooOOOOOOO $$$$$$$",
+"$$$$ #+.XXXXXXXXXXXX#++++ $$$$$ ooOOOOOOO $$$$$$",
+"$$$$ #+.XXXXXXXXXXXX#++++ $$$$$$ ooOOOOOOO $$$$$",
+"$$$$ #@##############++++ $$$$ ooOOOOOOO $$$$",
+"$$$ #@@@@@@@@@@@@@@@+++ @##+ $$$$ ooOOOOOOO $$$",
+"$$ @ @+++++++++++++++++ @##++ $$$$$ ooOOOOO $$$$",
+"$ @@ @##+++ $$$$$$ ooOOO $$$$$",
+" ########################@+++ $$$$$$$ ooO $$$$$$",
+" #######################@++++ $$$$$$$$ o $$$$$$$",
+" ##@@@@@@@@@@@@+ @++++ $$$$$$$$$$ $$$$$$$$",
+" @@++++++++++++++++++++++++ $$$$$$$$$$$$$$$$$$$$",
+" @@+++++++++++++++++++++++ $$$$$$$$$$$$$$$$$$$$$",
+"$ $$$$$$$$$$$$$$$$$$$$$$"
+};
+
+const char *const *const cfg_icon[] = {
+ cfg_icon_0,
+ cfg_icon_1,
+ cfg_icon_2,
+};
+const int n_cfg_icon = 3;
--- /dev/null
+/* XPM */
+static const char *const main_icon_0[] = {
+/* columns rows colors chars-per-pixel */
+"16 16 8 1",
+" c black",
+". c navy",
+"X c blue",
+"o c yellow",
+"O c #808080",
+"+ c #C0C0C0",
+"@ c gray100",
+"# c None",
+/* pixels */
+"####### ##",
+"###### @@@@@+O #",
+"###### @XX..+O #",
+"###### @X.o.+O #",
+"###### O.o.X+O #",
+"###### ooOO++O #",
+"## ooooo OOO ",
+"# @Oooooo OOOOO ",
+"# @X..oo OOOO #",
+"# @X.o.OO ##",
+"# @.o.X+O ######",
+"# +OO+++O ######",
+"# OOOOOOOO #####",
+" @@@@@OOOO #####",
+" +OOOOOOO ######",
+"# #######"
+};
+
+/* XPM */
+static const char *const main_icon_1[] = {
+/* columns rows colors chars-per-pixel */
+"32 32 8 1",
+" c black",
+". c navy",
+"X c blue",
+"o c yellow",
+"O c #808080",
+"+ c #C0C0C0",
+"@ c gray100",
+"# c None",
+/* pixels */
+"################ ####",
+"############### +++++++++++O ###",
+"############## @@@@@@@@@@+OO ###",
+"############## @.........+OO ###",
+"############## @.XXXXXXXX+OO ###",
+"############## @.XXXX XX+OO ###",
+"############## @.XXX o XX+OO ###",
+"############## @.X o XXX+OO ###",
+"############## @. oo XXXX+OO ###",
+"############## @ oo XXXXX+OO ###",
+"############## oo @@@@@@+OO #",
+"############# ooo OOOOOOOOO +@O ",
+"############ ooo +@OO ",
+"########## ooooooooo @@@@@@+OO ",
+"##### ooooooooo +++++++OOO ",
+"#### +++++ ooo ++O O+OO #",
+"### @@@@@@@@@ ooo OOOOOOOOOOO ##",
+"### @....... oo ###",
+"### @.XXXXX oo OO ##############",
+"### @.XXXX oo +OO ##############",
+"### @.XXX o X+OO ##############",
+"### @.XX o XXX+OO ##############",
+"### @.XX XXXX+OO ##############",
+"### @.XXXXXXXX+OO ##############",
+"### @@@@@@@@@@+OO ############",
+"## +OOOOOOOOOOO +@O ###########",
+"# + +@OO ###########",
+" @@@@@@@@@@@@@@@@+OO ###########",
+" @+++++++++++++++OOO ###########",
+" @++++++++O O+OO ############",
+" +OOOOOOOOOOOOOOOO #############",
+"# ##############"
+};
+
+/* XPM */
+static const char *const main_icon_2[] = {
+/* columns rows colors chars-per-pixel */
+"48 48 8 1",
+" c black",
+". c navy",
+"X c blue",
+"o c yellow",
+"O c #808080",
+"+ c #C0C0C0",
+"@ c gray100",
+"# c None",
+/* pixels */
+"######################### #####",
+"######################## +++++++++++++++++O ####",
+"####################### +++++++++++++++++OO ####",
+"###################### @@@@@@@@@@@@@@@@+OOO ####",
+"###################### @OOOOOOOOOOOOOO+OOOO ####",
+"###################### @O.............@OOOO ####",
+"###################### @O.XXXXXXXXXXXX@OOOO ####",
+"###################### @O.XXXXXXXXXXXX@OOOO ####",
+"###################### @O.XXXXXXX XXX@OOOO ####",
+"###################### @O.XXXXXX o XXX@OOOO ####",
+"###################### @O.XXXXX o XXXX@OOOO ####",
+"###################### @O.XXX o XXXXX@OOOO ####",
+"###################### @O.XX oo XXXXXX@OOOO ####",
+"###################### @O.X oo XXXXXXX@OOOO ####",
+"###################### @+ oo @@@@@@@@@OOOO #",
+"##################### @ ooo ++++++++++OOO +@@O ",
+"#################### + oo OOOOOOOOOOOOO +@@OO ",
+"################### + oo +@@OOO ",
+"################## @ ooo @@@@@@@@@@@@+OOO ",
+"################## ooooooooooo @@@@@@@@@@+OOOO ",
+"################## oooooooooo ++O +OOOO #",
+"################ oooooooooo OOOOOOOOOOOOOOOO ##",
+"############### ooooooooooo OOOOOOOOOOOOOOOO ###",
+"################ ooo ####",
+"####### oo ######################",
+"###### ++++++++++++ oo O ######################",
+"##### ++++++++++++ ooo OO ######################",
+"#### @@@@@@@@@@@@ oo OOO ######################",
+"#### @OOOOOOOOOO oo +OOOO ######################",
+"#### @O........ oo .@OOOO ######################",
+"#### @O.XXXXXX o XX@OOOO ######################",
+"#### @O.XXXXX o XXXX@OOOO ######################",
+"#### @O.XXXX o XXXXX@OOOO ######################",
+"#### @O.XXXX XXXXXX@OOOO ######################",
+"#### @O.XXXXXXXXXXXX@OOOO ######################",
+"#### @O.XXXXXXXXXXXX@OOOO ######################",
+"#### @O.XXXXXXXXXXXX@OOOO ######################",
+"#### @O.XXXXXXXXXXXX@OOOO ######################",
+"#### @+@@@@@@@@@@@@@@OOOO ###################",
+"### @+++++++++++++++OOO +@@O ##################",
+"## + +OOOOOOOOOOOOOOOOO +@@OO ##################",
+"# ++ +@@OOO ##################",
+" @@@@@@@@@@@@@@@@@@@@@@@@+OOO ##################",
+" @@@@@@@@@@@@@@@@@@@@@@@+OOOO ##################",
+" @@++++++++++++O +OOOO ###################",
+" ++OOOOOOOOOOOOOOOOOOOOOOOO ####################",
+" ++OOOOOOOOOOOOOOOOOOOOOOO #####################",
+"# ######################"
+};
+
+const char *const *const main_icon[] = {
+ main_icon_0,
+ main_icon_1,
+ main_icon_2,
+};
+const int n_main_icon = 3;
--- /dev/null
+/*
+ * PuTTY version numbering
+ */
+
+#define STR1(x) #x
+#define STR(x) STR1(x)
+
+#if defined SNAPSHOT
+
+#if defined SVN_REV
+#define SNAPSHOT_TEXT STR(SNAPSHOT) ":r" STR(SVN_REV)
+#else
+#define SNAPSHOT_TEXT STR(SNAPSHOT)
+#endif
+
+char ver[] = "Development snapshot " SNAPSHOT_TEXT;
+char sshver[] = "PuTTY-Snapshot-" SNAPSHOT_TEXT;
+
+#undef SNAPSHOT_TEXT
+
+#elif defined RELEASE
+
+char ver[] = "Release " STR(RELEASE);
+char sshver[] = "PuTTY-Release-" STR(RELEASE);
+
+#elif defined SVN_REV
+
+char ver[] = "Custom build r" STR(SVN_REV) ", " __DATE__ " " __TIME__;
+char sshver[] = "PuTTY-Custom-r" STR(SVN_REV);
+
+#else
+
+char ver[] = "Unidentified build, " __DATE__ " " __TIME__;
+char sshver[] = "PuTTY-Local: " __DATE__ " " __TIME__;
+
+#endif
+
+/*
+ * SSH local version string MUST be under 40 characters. Here's a
+ * compile time assertion to verify this.
+ */
+enum { vorpal_sword = 1 / (sizeof(sshver) <= 40) };
--- /dev/null
+/*
+ * This is an implementation of wcwidth() and wcswidth() (defined in
+ * IEEE Std 1002.1-2001) for Unicode.
+ *
+ * http://www.opengroup.org/onlinepubs/007904975/functions/wcwidth.html
+ * http://www.opengroup.org/onlinepubs/007904975/functions/wcswidth.html
+ *
+ * In fixed-width output devices, Latin characters all occupy a single
+ * "cell" position of equal width, whereas ideographic CJK characters
+ * occupy two such cells. Interoperability between terminal-line
+ * applications and (teletype-style) character terminals using the
+ * UTF-8 encoding requires agreement on which character should advance
+ * the cursor by how many cell positions. No established formal
+ * standards exist at present on which Unicode character shall occupy
+ * how many cell positions on character terminals. These routines are
+ * a first attempt of defining such behavior based on simple rules
+ * applied to data provided by the Unicode Consortium.
+ *
+ * For some graphical characters, the Unicode standard explicitly
+ * defines a character-cell width via the definition of the East Asian
+ * FullWidth (F), Wide (W), Half-width (H), and Narrow (Na) classes.
+ * In all these cases, there is no ambiguity about which width a
+ * terminal shall use. For characters in the East Asian Ambiguous (A)
+ * class, the width choice depends purely on a preference of backward
+ * compatibility with either historic CJK or Western practice.
+ * Choosing single-width for these characters is easy to justify as
+ * the appropriate long-term solution, as the CJK practice of
+ * displaying these characters as double-width comes from historic
+ * implementation simplicity (8-bit encoded characters were displayed
+ * single-width and 16-bit ones double-width, even for Greek,
+ * Cyrillic, etc.) and not any typographic considerations.
+ *
+ * Much less clear is the choice of width for the Not East Asian
+ * (Neutral) class. Existing practice does not dictate a width for any
+ * of these characters. It would nevertheless make sense
+ * typographically to allocate two character cells to characters such
+ * as for instance EM SPACE or VOLUME INTEGRAL, which cannot be
+ * represented adequately with a single-width glyph. The following
+ * routines at present merely assign a single-cell width to all
+ * neutral characters, in the interest of simplicity. This is not
+ * entirely satisfactory and should be reconsidered before
+ * establishing a formal standard in this area. At the moment, the
+ * decision which Not East Asian (Neutral) characters should be
+ * represented by double-width glyphs cannot yet be answered by
+ * applying a simple rule from the Unicode database content. Setting
+ * up a proper standard for the behavior of UTF-8 character terminals
+ * will require a careful analysis not only of each Unicode character,
+ * 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)
+ *
+ * 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.
+ *
+ * Latest version: http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c
+ */
+
+#include <wchar.h>
+
+#include "putty.h" /* for prototypes */
+
+struct interval {
+ int first;
+ int last;
+};
+
+/* auxiliary function for binary search in interval table */
+static int bisearch(wchar_t ucs, const struct interval *table, int max) {
+ int min = 0;
+ int mid;
+
+ if (ucs < table[0].first || ucs > table[max].last)
+ return 0;
+ while (max >= min) {
+ mid = (min + max) / 2;
+ if (ucs > table[mid].last)
+ min = mid + 1;
+ else if (ucs < table[mid].first)
+ max = mid - 1;
+ else
+ return 1;
+ }
+
+ return 0;
+}
+
+
+/* The following two functions define the column width of an ISO 10646
+ * character as follows:
+ *
+ * - The null character (U+0000) has a column width of 0.
+ *
+ * - Other C0/C1 control characters and DEL will lead to a return
+ * value of -1.
+ *
+ * - Non-spacing and enclosing combining characters (general
+ * category code Mn or Me in the Unicode database) have a
+ * column width of 0.
+ *
+ * - SOFT HYPHEN (U+00AD) has a column width of 1.
+ *
+ * - Other format characters (general category code Cf in the Unicode
+ * database) and ZERO WIDTH SPACE (U+200B) have a column width of 0.
+ *
+ * - Hangul Jamo medial vowels and final consonants (U+1160-U+11FF)
+ * have a column width of 0.
+ *
+ * - Spacing characters in the East Asian Wide (W) or East Asian
+ * Full-width (F) category as defined in Unicode Technical
+ * Report #11 have a column width of 2.
+ *
+ * - All remaining characters (including all printable
+ * ISO 8859-1 and WGL4 characters, Unicode control characters,
+ * etc.) have a column width of 1.
+ *
+ * This implementation assumes that wchar_t characters are encoded
+ * in ISO 10646.
+ */
+
+int mk_wcwidth(wchar_t 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 },
+ { 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 },
+ { 0x1D173, 0x1D182 }, { 0x1D185, 0x1D18B }, { 0x1D1AA, 0x1D1AD },
+ { 0xE0001, 0xE0001 }, { 0xE0020, 0xE007F }, { 0xE0100, 0xE01EF }
+ };
+
+ /* test for 8-bit control characters */
+ if (ucs == 0)
+ return 0;
+ if (ucs < 32 || (ucs >= 0x7f && ucs < 0xa0))
+ return -1;
+
+ /* binary search in table of non-spacing characters */
+ if (bisearch(ucs, combining,
+ sizeof(combining) / sizeof(struct interval) - 1))
+ return 0;
+
+ /* if we arrive here, ucs is not a combining or C0/C1 control character */
+
+ return 1 +
+ (ucs >= 0x1100 &&
+ (ucs <= 0x115f || /* Hangul Jamo init. consonants */
+ 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 >= 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 w, width = 0;
+
+ for (;*pwcs && n-- > 0; pwcs++)
+ if ((w = mk_wcwidth(*pwcs)) < 0)
+ return -1;
+ else
+ width += w;
+
+ return width;
+}
+
+
+/*
+ * The following functions are the same as mk_wcwidth() and
+ * mk_wcwidth_cjk(), 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)
+{
+ /* 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 },
+ { 0x00AA, 0x00AA }, { 0x00AE, 0x00AE }, { 0x00B0, 0x00B4 },
+ { 0x00B6, 0x00BA }, { 0x00BC, 0x00BF }, { 0x00C6, 0x00C6 },
+ { 0x00D0, 0x00D0 }, { 0x00D7, 0x00D8 }, { 0x00DE, 0x00E1 },
+ { 0x00E6, 0x00E6 }, { 0x00E8, 0x00EA }, { 0x00EC, 0x00ED },
+ { 0x00F0, 0x00F0 }, { 0x00F2, 0x00F3 }, { 0x00F7, 0x00FA },
+ { 0x00FC, 0x00FC }, { 0x00FE, 0x00FE }, { 0x0101, 0x0101 },
+ { 0x0111, 0x0111 }, { 0x0113, 0x0113 }, { 0x011B, 0x011B },
+ { 0x0126, 0x0127 }, { 0x012B, 0x012B }, { 0x0131, 0x0133 },
+ { 0x0138, 0x0138 }, { 0x013F, 0x0142 }, { 0x0144, 0x0144 },
+ { 0x0148, 0x014B }, { 0x014D, 0x014D }, { 0x0152, 0x0153 },
+ { 0x0166, 0x0167 }, { 0x016B, 0x016B }, { 0x01CE, 0x01CE },
+ { 0x01D0, 0x01D0 }, { 0x01D2, 0x01D2 }, { 0x01D4, 0x01D4 },
+ { 0x01D6, 0x01D6 }, { 0x01D8, 0x01D8 }, { 0x01DA, 0x01DA },
+ { 0x01DC, 0x01DC }, { 0x0251, 0x0251 }, { 0x0261, 0x0261 },
+ { 0x02C4, 0x02C4 }, { 0x02C7, 0x02C7 }, { 0x02C9, 0x02CB },
+ { 0x02CD, 0x02CD }, { 0x02D0, 0x02D0 }, { 0x02D8, 0x02DB },
+ { 0x02DD, 0x02DD }, { 0x02DF, 0x02DF }, { 0x0391, 0x03A1 },
+ { 0x03A3, 0x03A9 }, { 0x03B1, 0x03C1 }, { 0x03C3, 0x03C9 },
+ { 0x0401, 0x0401 }, { 0x0410, 0x044F }, { 0x0451, 0x0451 },
+ { 0x2010, 0x2010 }, { 0x2013, 0x2016 }, { 0x2018, 0x2019 },
+ { 0x201C, 0x201D }, { 0x2020, 0x2022 }, { 0x2024, 0x2027 },
+ { 0x2030, 0x2030 }, { 0x2032, 0x2033 }, { 0x2035, 0x2035 },
+ { 0x203B, 0x203B }, { 0x203E, 0x203E }, { 0x2074, 0x2074 },
+ { 0x207F, 0x207F }, { 0x2081, 0x2084 }, { 0x20AC, 0x20AC },
+ { 0x2103, 0x2103 }, { 0x2105, 0x2105 }, { 0x2109, 0x2109 },
+ { 0x2113, 0x2113 }, { 0x2116, 0x2116 }, { 0x2121, 0x2122 },
+ { 0x2126, 0x2126 }, { 0x212B, 0x212B }, { 0x2153, 0x2154 },
+ { 0x215B, 0x215E }, { 0x2160, 0x216B }, { 0x2170, 0x2179 },
+ { 0x2190, 0x2199 }, { 0x21B8, 0x21B9 }, { 0x21D2, 0x21D2 },
+ { 0x21D4, 0x21D4 }, { 0x21E7, 0x21E7 }, { 0x2200, 0x2200 },
+ { 0x2202, 0x2203 }, { 0x2207, 0x2208 }, { 0x220B, 0x220B },
+ { 0x220F, 0x220F }, { 0x2211, 0x2211 }, { 0x2215, 0x2215 },
+ { 0x221A, 0x221A }, { 0x221D, 0x2220 }, { 0x2223, 0x2223 },
+ { 0x2225, 0x2225 }, { 0x2227, 0x222C }, { 0x222E, 0x222E },
+ { 0x2234, 0x2237 }, { 0x223C, 0x223D }, { 0x2248, 0x2248 },
+ { 0x224C, 0x224C }, { 0x2252, 0x2252 }, { 0x2260, 0x2261 },
+ { 0x2264, 0x2267 }, { 0x226A, 0x226B }, { 0x226E, 0x226F },
+ { 0x2282, 0x2283 }, { 0x2286, 0x2287 }, { 0x2295, 0x2295 },
+ { 0x2299, 0x2299 }, { 0x22A5, 0x22A5 }, { 0x22BF, 0x22BF },
+ { 0x2312, 0x2312 }, { 0x2460, 0x24E9 }, { 0x24EB, 0x254B },
+ { 0x2550, 0x2573 }, { 0x2580, 0x258F }, { 0x2592, 0x2595 },
+ { 0x25A0, 0x25A1 }, { 0x25A3, 0x25A9 }, { 0x25B2, 0x25B3 },
+ { 0x25B6, 0x25B7 }, { 0x25BC, 0x25BD }, { 0x25C0, 0x25C1 },
+ { 0x25C6, 0x25C8 }, { 0x25CB, 0x25CB }, { 0x25CE, 0x25D1 },
+ { 0x25E2, 0x25E5 }, { 0x25EF, 0x25EF }, { 0x2605, 0x2606 },
+ { 0x2609, 0x2609 }, { 0x260E, 0x260F }, { 0x2614, 0x2615 },
+ { 0x261C, 0x261C }, { 0x261E, 0x261E }, { 0x2640, 0x2640 },
+ { 0x2642, 0x2642 }, { 0x2660, 0x2661 }, { 0x2663, 0x2665 },
+ { 0x2667, 0x266A }, { 0x266C, 0x266D }, { 0x266F, 0x266F },
+ { 0x273D, 0x273D }, { 0x2776, 0x277F }, { 0xE000, 0xF8FF },
+ { 0xFFFD, 0xFFFD }, { 0xF0000, 0xFFFFD }, { 0x100000, 0x10FFFD }
+ };
+
+ /* binary search in table of non-spacing characters */
+ if (bisearch(ucs, ambiguous,
+ sizeof(ambiguous) / sizeof(struct interval) - 1))
+ return 2;
+
+ return mk_wcwidth(ucs);
+}
+
+
+int mk_wcswidth_cjk(const wchar_t *pwcs, size_t n)
+{
+ int w, width = 0;
+
+ for (;*pwcs && n-- > 0; pwcs++)
+ if ((w = mk_wcwidth_cjk(*pwcs)) < 0)
+ return -1;
+ else
+ width += w;
+
+ return width;
+}
--- /dev/null
+/*
+ * Wildcard matching engine for use with SFTP-based file transfer
+ * programs (PSFTP, new-look PSCP): since SFTP has no notion of
+ * getting the remote side to do globbing (and rightly so) we have
+ * to do it locally, by retrieving all the filenames in a directory
+ * and checking each against the wildcard pattern.
+ */
+
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "putty.h"
+
+/*
+ * Definition of wildcard syntax:
+ *
+ * - * matches any sequence of characters, including zero.
+ * - ? matches exactly one character which can be anything.
+ * - [abc] matches exactly one character which is a, b or c.
+ * - [a-f] matches anything from a through f.
+ * - [^a-f] matches anything _except_ a through f.
+ * - [-_] matches - or _; [^-_] matches anything else. (The - is
+ * non-special if it occurs immediately after the opening
+ * bracket or ^.)
+ * - [a^] matches an a or a ^. (The ^ is non-special if it does
+ * _not_ occur immediately after the opening bracket.)
+ * - \*, \?, \[, \], \\ match the single characters *, ?, [, ], \.
+ * - All other characters are non-special and match themselves.
+ */
+
+/*
+ * Some notes on differences from POSIX globs (IEEE Std 1003.1, 2003 ed.):
+ * - backslashes act as escapes even within [] bracket expressions
+ * - does not support [!...] for non-matching list (POSIX are weird);
+ * NB POSIX allows [^...] as well via "A bracket expression starting
+ * with an unquoted circumflex character produces unspecified
+ * results". If we wanted to allow [!...] we might want to define
+ * [^!] as having its literal meaning (match '^' or '!').
+ * - none of the scary [[:class:]] stuff, etc
+ */
+
+/*
+ * The wildcard matching technique we use is very simple and
+ * potentially O(N^2) in running time, but I don't anticipate it
+ * being that bad in reality (particularly since N will be the size
+ * of a filename, which isn't all that much). Perhaps one day, once
+ * PuTTY has grown a regexp matcher for some other reason, I might
+ * come back and reimplement wildcards by translating them into
+ * regexps or directly into NFAs; but for the moment, in the
+ * absence of any other need for the NFA->DFA translation engine,
+ * anything more than the simplest possible wildcard matcher is
+ * vast code-size overkill.
+ *
+ * Essentially, these wildcards are much simpler than regexps in
+ * that they consist of a sequence of rigid fragments (? and [...]
+ * can never match more or less than one character) separated by
+ * asterisks. It is therefore extremely simple to look at a rigid
+ * fragment and determine whether or not it begins at a particular
+ * point in the test string; so we can search along the string
+ * until we find each fragment, then search for the next. As long
+ * as we find each fragment in the _first_ place it occurs, there
+ * will never be a danger of having to backpedal and try to find it
+ * again somewhere else.
+ */
+
+enum {
+ WC_TRAILINGBACKSLASH = 1,
+ WC_UNCLOSEDCLASS,
+ WC_INVALIDRANGE
+};
+
+/*
+ * Error reporting is done by returning various negative values
+ * from the wildcard routines. Passing any such value to wc_error
+ * will give a human-readable message.
+ */
+const char *wc_error(int value)
+{
+ value = abs(value);
+ switch (value) {
+ case WC_TRAILINGBACKSLASH:
+ return "'\' occurred at end of string (expected another character)";
+ case WC_UNCLOSEDCLASS:
+ return "expected ']' to close character class";
+ case WC_INVALIDRANGE:
+ return "character range was not terminated (']' just after '-')";
+ }
+ return "INTERNAL ERROR: unrecognised wildcard error number";
+}
+
+/*
+ * This is the routine that tests a target string to see if an
+ * initial substring of it matches a fragment. If successful, it
+ * returns 1, and advances both `fragment' and `target' past the
+ * fragment and matching substring respectively. If unsuccessful it
+ * returns zero. If the wildcard fragment suffers a syntax error,
+ * it returns <0 and the precise value indexes into wc_error.
+ */
+static int wc_match_fragment(const char **fragment, const char **target)
+{
+ const char *f, *t;
+
+ f = *fragment;
+ t = *target;
+ /*
+ * The fragment terminates at either the end of the string, or
+ * the first (unescaped) *.
+ */
+ while (*f && *f != '*' && *t) {
+ /*
+ * Extract one character from t, and one character's worth
+ * of pattern from f, and step along both. Return 0 if they
+ * fail to match.
+ */
+ if (*f == '\\') {
+ /*
+ * Backslash, which means f[1] is to be treated as a
+ * literal character no matter what it is. It may not
+ * be the end of the string.
+ */
+ if (!f[1])
+ return -WC_TRAILINGBACKSLASH; /* error */
+ if (f[1] != *t)
+ return 0; /* failed to match */
+ f += 2;
+ } else if (*f == '?') {
+ /*
+ * Question mark matches anything.
+ */
+ f++;
+ } else if (*f == '[') {
+ int invert = 0;
+ int matched = 0;
+ /*
+ * Open bracket introduces a character class.
+ */
+ f++;
+ if (*f == '^') {
+ invert = 1;
+ f++;
+ }
+ while (*f != ']') {
+ if (*f == '\\')
+ f++; /* backslashes still work */
+ if (!*f)
+ return -WC_UNCLOSEDCLASS; /* error again */
+ if (f[1] == '-') {
+ int lower, upper, ourchr;
+ lower = (unsigned char) *f++;
+ f++; /* eat the minus */
+ if (*f == ']')
+ return -WC_INVALIDRANGE; /* different error! */
+ if (*f == '\\')
+ f++; /* backslashes _still_ work */
+ if (!*f)
+ return -WC_UNCLOSEDCLASS; /* error again */
+ upper = (unsigned char) *f++;
+ ourchr = (unsigned char) *t;
+ if (lower > upper) {
+ int t = lower; lower = upper; upper = t;
+ }
+ if (ourchr >= lower && ourchr <= upper)
+ matched = 1;
+ } else {
+ matched |= (*t == *f++);
+ }
+ }
+ if (invert == matched)
+ return 0; /* failed to match character class */
+ f++; /* eat the ] */
+ } else {
+ /*
+ * Non-special character matches itself.
+ */
+ if (*f != *t)
+ return 0;
+ f++;
+ }
+ /*
+ * Now we've done that, increment t past the character we
+ * matched.
+ */
+ t++;
+ }
+ if (!*f || *f == '*') {
+ /*
+ * We have reached the end of f without finding a mismatch;
+ * so we're done. Update the caller pointers and return 1.
+ */
+ *fragment = f;
+ *target = t;
+ return 1;
+ }
+ /*
+ * Otherwise, we must have reached the end of t before we
+ * reached the end of f; so we've failed. Return 0.
+ */
+ return 0;
+}
+
+/*
+ * This is the real wildcard matching routine. It returns 1 for a
+ * successful match, 0 for an unsuccessful match, and <0 for a
+ * syntax error in the wildcard.
+ */
+int wc_match(const char *wildcard, const char *target)
+{
+ int ret;
+
+ /*
+ * Every time we see a '*' _followed_ by a fragment, we just
+ * search along the string for a location at which the fragment
+ * matches. The only special case is when we see a fragment
+ * right at the start, in which case we just call the matching
+ * routine once and give up if it fails.
+ */
+ if (*wildcard != '*') {
+ ret = wc_match_fragment(&wildcard, &target);
+ if (ret <= 0)
+ return ret; /* pass back failure or error alike */
+ }
+
+ while (*wildcard) {
+ assert(*wildcard == '*');
+ while (*wildcard == '*')
+ wildcard++;
+
+ /*
+ * It's possible we've just hit the end of the wildcard
+ * after seeing a *, in which case there's no need to
+ * bother searching any more because we've won.
+ */
+ if (!*wildcard)
+ return 1;
+
+ /*
+ * Now `wildcard' points at the next fragment. So we
+ * attempt to match it against `target', and if that fails
+ * we increment `target' and try again, and so on. When we
+ * find we're about to try matching against the empty
+ * string, we give up and return 0.
+ */
+ ret = 0;
+ while (*target) {
+ const char *save_w = wildcard, *save_t = target;
+
+ ret = wc_match_fragment(&wildcard, &target);
+
+ if (ret < 0)
+ return ret; /* syntax error */
+
+ if (ret > 0 && !*wildcard && *target) {
+ /*
+ * Final special case - literally.
+ *
+ * This situation arises when we are matching a
+ * _terminal_ fragment of the wildcard (that is,
+ * there is nothing after it, e.g. "*a"), and it
+ * has matched _too early_. For example, matching
+ * "*a" against "parka" will match the "a" fragment
+ * against the _first_ a, and then (if it weren't
+ * for this special case) matching would fail
+ * because we're at the end of the wildcard but not
+ * at the end of the target string.
+ *
+ * In this case what we must do is measure the
+ * length of the fragment in the target (which is
+ * why we saved `target'), jump straight to that
+ * distance from the end of the string using
+ * strlen, and match the same fragment again there
+ * (which is why we saved `wildcard'). Then we
+ * return whatever that operation returns.
+ */
+ target = save_t + strlen(save_t) - (target - save_t);
+ wildcard = save_w;
+ return wc_match_fragment(&wildcard, &target);
+ }
+
+ if (ret > 0)
+ break;
+ target++;
+ }
+ if (ret > 0)
+ continue;
+ return 0;
+ }
+
+ /*
+ * If we reach here, it must be because we successfully matched
+ * a fragment and then found ourselves right at the end of the
+ * wildcard. Hence, we return 1 if and only if we are also
+ * right at the end of the target.
+ */
+ return (*target ? 0 : 1);
+}
+
+/*
+ * Another utility routine that translates a non-wildcard string
+ * into its raw equivalent by removing any escaping backslashes.
+ * Expects a target string buffer of anything up to the length of
+ * the original wildcard. You can also pass NULL as the output
+ * buffer if you're only interested in the return value.
+ *
+ * Returns 1 on success, or 0 if a wildcard character was
+ * encountered. In the latter case the output string MAY not be
+ * zero-terminated and you should not use it for anything!
+ */
+int wc_unescape(char *output, const char *wildcard)
+{
+ while (*wildcard) {
+ if (*wildcard == '\\') {
+ wildcard++;
+ /* We are lenient about trailing backslashes in non-wildcards. */
+ if (*wildcard) {
+ if (output)
+ *output++ = *wildcard;
+ wildcard++;
+ }
+ } else if (*wildcard == '*' || *wildcard == '?' ||
+ *wildcard == '[' || *wildcard == ']') {
+ return 0; /* it's a wildcard! */
+ } else {
+ if (output)
+ *output++ = *wildcard;
+ wildcard++;
+ }
+ }
+ *output = '\0';
+ return 1; /* it's clean */
+}
+
+#ifdef TESTMODE
+
+struct test {
+ const char *wildcard;
+ const char *target;
+ int expected_result;
+};
+
+const struct test fragment_tests[] = {
+ /*
+ * We exhaustively unit-test the fragment matching routine
+ * itself, which should save us the need to test all its
+ * intricacies during the full wildcard tests.
+ */
+ {"abc", "abc", 1},
+ {"abc", "abd", 0},
+ {"abc", "abcd", 1},
+ {"abcd", "abc", 0},
+ {"ab[cd]", "abc", 1},
+ {"ab[cd]", "abd", 1},
+ {"ab[cd]", "abe", 0},
+ {"ab[^cd]", "abc", 0},
+ {"ab[^cd]", "abd", 0},
+ {"ab[^cd]", "abe", 1},
+ {"ab\\", "abc", -WC_TRAILINGBACKSLASH},
+ {"ab\\*", "ab*", 1},
+ {"ab\\?", "ab*", 0},
+ {"ab?", "abc", 1},
+ {"ab?", "ab", 0},
+ {"ab[", "abc", -WC_UNCLOSEDCLASS},
+ {"ab[c-", "abb", -WC_UNCLOSEDCLASS},
+ {"ab[c-]", "abb", -WC_INVALIDRANGE},
+ {"ab[c-e]", "abb", 0},
+ {"ab[c-e]", "abc", 1},
+ {"ab[c-e]", "abd", 1},
+ {"ab[c-e]", "abe", 1},
+ {"ab[c-e]", "abf", 0},
+ {"ab[e-c]", "abb", 0},
+ {"ab[e-c]", "abc", 1},
+ {"ab[e-c]", "abd", 1},
+ {"ab[e-c]", "abe", 1},
+ {"ab[e-c]", "abf", 0},
+ {"ab[^c-e]", "abb", 1},
+ {"ab[^c-e]", "abc", 0},
+ {"ab[^c-e]", "abd", 0},
+ {"ab[^c-e]", "abe", 0},
+ {"ab[^c-e]", "abf", 1},
+ {"ab[^e-c]", "abb", 1},
+ {"ab[^e-c]", "abc", 0},
+ {"ab[^e-c]", "abd", 0},
+ {"ab[^e-c]", "abe", 0},
+ {"ab[^e-c]", "abf", 1},
+ {"ab[a^]", "aba", 1},
+ {"ab[a^]", "ab^", 1},
+ {"ab[a^]", "abb", 0},
+ {"ab[^a^]", "aba", 0},
+ {"ab[^a^]", "ab^", 0},
+ {"ab[^a^]", "abb", 1},
+ {"ab[-c]", "ab-", 1},
+ {"ab[-c]", "abc", 1},
+ {"ab[-c]", "abd", 0},
+ {"ab[^-c]", "ab-", 0},
+ {"ab[^-c]", "abc", 0},
+ {"ab[^-c]", "abd", 1},
+ {"ab[\\[-\\]]", "abZ", 0},
+ {"ab[\\[-\\]]", "ab[", 1},
+ {"ab[\\[-\\]]", "ab\\", 1},
+ {"ab[\\[-\\]]", "ab]", 1},
+ {"ab[\\[-\\]]", "ab^", 0},
+ {"ab[^\\[-\\]]", "abZ", 1},
+ {"ab[^\\[-\\]]", "ab[", 0},
+ {"ab[^\\[-\\]]", "ab\\", 0},
+ {"ab[^\\[-\\]]", "ab]", 0},
+ {"ab[^\\[-\\]]", "ab^", 1},
+ {"ab[a-fA-F]", "aba", 1},
+ {"ab[a-fA-F]", "abF", 1},
+ {"ab[a-fA-F]", "abZ", 0},
+};
+
+const struct test full_tests[] = {
+ {"a", "argh", 0},
+ {"a", "ba", 0},
+ {"a", "a", 1},
+ {"a*", "aardvark", 1},
+ {"a*", "badger", 0},
+ {"*a", "park", 0},
+ {"*a", "pArka", 1},
+ {"*a", "parka", 1},
+ {"*a*", "park", 1},
+ {"*a*", "perk", 0},
+ {"?b*r?", "abracadabra", 1},
+ {"?b*r?", "abracadabr", 0},
+ {"?b*r?", "abracadabzr", 0},
+};
+
+int main(void)
+{
+ int i;
+ int fails, passes;
+
+ fails = passes = 0;
+
+ for (i = 0; i < sizeof(fragment_tests)/sizeof(*fragment_tests); i++) {
+ const char *f, *t;
+ int eret, aret;
+ f = fragment_tests[i].wildcard;
+ t = fragment_tests[i].target;
+ eret = fragment_tests[i].expected_result;
+ aret = wc_match_fragment(&f, &t);
+ if (aret != eret) {
+ printf("failed test: /%s/ against /%s/ returned %d not %d\n",
+ fragment_tests[i].wildcard, fragment_tests[i].target,
+ aret, eret);
+ fails++;
+ } else
+ passes++;
+ }
+
+ for (i = 0; i < sizeof(full_tests)/sizeof(*full_tests); i++) {
+ const char *f, *t;
+ int eret, aret;
+ f = full_tests[i].wildcard;
+ t = full_tests[i].target;
+ eret = full_tests[i].expected_result;
+ aret = wc_match(f, t);
+ if (aret != eret) {
+ printf("failed test: /%s/ against /%s/ returned %d not %d\n",
+ full_tests[i].wildcard, full_tests[i].target,
+ aret, eret);
+ fails++;
+ } else
+ passes++;
+ }
+
+ printf("passed %d, failed %d\n", passes, fails);
+
+ return 0;
+}
+
+#endif
--- /dev/null
+PuTTY README\r
+============\r
+\r
+This is the README file for the PuTTY installer distribution. If\r
+you're reading this, you've probably just run our installer and\r
+installed PuTTY on your system.\r
+\r
+What should I do next?\r
+----------------------\r
+\r
+If you want to use PuTTY to connect to other computers, or use PSFTP\r
+to transfer files, you should just be able to run them from the\r
+Start menu.\r
+\r
+If you want to use the command-line-only file transfer utility PSCP,\r
+you will probably want to put the PuTTY installation directory on\r
+your PATH. How you do this depends on your version of Windows. On\r
+Windows NT, 2000, and XP, you can set it using Control Panel > System;\r
+on Windows 95, 98, and Me, you will need to edit AUTOEXEC.BAT. Consult\r
+your Windows manuals for details.\r
+\r
+Some versions of Windows will refuse to run HTML Help files (.CHM)\r
+if they are installed on a network drive. If you have installed\r
+PuTTY on a network drive, you might want to check that the help file\r
+works properly. If not, see http://support.microsoft.com/kb/896054\r
+for information on how to solve this problem.\r
+\r
+What do I do if it doesn't work?\r
+--------------------------------\r
+\r
+The PuTTY home web site is\r
+\r
+ http://www.chiark.greenend.org.uk/~sgtatham/putty/\r
+\r
+Here you will find our list of known bugs and pending feature\r
+requests. If your problem is not listed in there, or in the FAQ, or\r
+in the manuals, read the Feedback page to find out how to report\r
+bugs to us. PLEASE read the Feedback page carefully: it is there to\r
+save you time as well as us. Do not send us one-line bug reports\r
+telling us `it doesn't work'.\r
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<!-- Do not attempt to do anything clever with this file, as some versions of
+ Windows are very sensitive to the exact format.
+ Hence, some facts below are fibs. -->
+<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
+<assemblyIdentity
+ version="0.0.0.0"
+ processorArchitecture="x86"
+ name="Pageant"
+ type="win32" />
+ <description>PuTTY SSH authentication agent</description>
+ <dependency>
+ <dependentAssembly>
+ <!-- Load Common Controls 6 instead of 5 to get WinXP native-
+ looking controls in the client area. -->
+ <assemblyIdentity type="win32"
+ name="Microsoft.Windows.Common-Controls"
+ version="6.0.0.0"
+ publicKeyToken="6595b64144ccf1df"
+ language="*"
+ processorArchitecture="x86"/>
+ </dependentAssembly>
+ </dependency>
+ <!-- Declare us to be "DPI-aware". -->
+ <asmv3:application xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
+ <asmv3:windowsSettings
+ xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">
+ <dpiAware>true</dpiAware>
+ </asmv3:windowsSettings>
+ </asmv3:application>
+</assembly>
--- /dev/null
+/*
+ * Windows resources for Pageant.
+ */
+
+#include "rcstuff.h"
+
+#define APPNAME "Pageant"
+#define APPDESC "PuTTY SSH authentication agent"
+
+200 ICON "pageant.ico"
+201 ICON "pageants.ico"
+
+210 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 "&Cancel", IDCANCEL, 80, 42, 40, 14
+END
+
+211 DIALOG DISCARDABLE 0, 0, 330, 200
+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_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
+ DEFPUSHBUTTON "&Close", IDOK, 270, 182, 50, 14
+END
+
+/* Accelerators used: cl */
+213 DIALOG DISCARDABLE 140, 40, 136, 70
+STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
+CAPTION "About Pageant"
+FONT 8, "MS Shell Dlg"
+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.",
+ 103, 10, 34, 120, 16
+END
+
+/* No accelerators used */
+214 DIALOG DISCARDABLE 50, 50, 226, 263
+STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
+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 "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
+
+ 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
+ LTEXT "publish, distribute, sublicense, and/or sell copies of the Software,", 1009, 10, 98, 206, 8
+ LTEXT "and to permit persons to whom the Software is furnished to do so,", 1010, 10, 106, 206, 8
+ LTEXT "subject to the following conditions:", 1011, 10, 114, 206, 8
+
+ LTEXT "The above copyright notice and this permission notice shall be", 1012, 10, 130, 206, 8
+ LTEXT "included in all copies or substantial portions of the Software.", 1013, 10, 138, 206, 8
+
+ LTEXT "THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT", 1014, 10, 154, 206, 8
+ LTEXT "WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,", 1015, 10, 162, 206, 8
+ LTEXT "INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF", 1016, 10, 170, 206, 8
+ LTEXT "MERCHANTABILITY, FITNESS FOR A PARTICULAR", 1017, 10, 178, 206, 8
+ LTEXT "PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE", 1018, 10, 186, 206, 8
+ LTEXT "COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES", 1019, 10, 194, 206, 8
+ LTEXT "OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,", 1020, 10, 202, 206, 8
+ LTEXT "TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN", 1021, 10, 210, 206, 8
+ LTEXT "CONNECTION WITH THE SOFTWARE OR THE USE OR", 1022, 10, 218, 206, 8
+ LTEXT "OTHER DEALINGS IN THE SOFTWARE.", 1023, 10, 226, 206, 8
+
+END
+
+#include "version.rc2"
+
+#ifndef NO_MANIFESTS
+1 RT_MANIFEST "pageant.mft"
+#endif /* NO_MANIFESTS */
--- /dev/null
+#include "rcstuff.h"
+
+#define APPNAME "Plink"
+#define APPDESC "Command-line SSH, Telnet, and Rlogin client"
+
+200 ICON "putty.ico"
+
+#include "version.rc2"
--- /dev/null
+#include "rcstuff.h"
+
+#define APPNAME "PSCP"
+#define APPDESC "Command-line SCP/SFTP client"
+
+200 ICON "pscp.ico"
+
+#include "version.rc2"
--- /dev/null
+#include "rcstuff.h"
+
+#define APPNAME "PSFTP"
+#define APPDESC "Command-line interactive SFTP client"
+
+200 ICON "pscp.ico"
+
+#include "version.rc2"
--- /dev/null
+; -*- no -*-\r
+; $Id$\r
+;\r
+; -- Inno Setup installer script for PuTTY and its related tools.\r
+; Last tested with Inno Setup 5.0.8.\r
+;\r
+; TODO for future releases:\r
+;\r
+; - It might be nice to have an option to add PSCP, Plink and PSFTP to\r
+; the PATH. See wish `installer-addpath'.\r
+;\r
+; - Maybe a "custom" installation might be useful? Hassle with\r
+; UninstallDisplayIcon, though.\r
+\r
+[Setup]\r
+AppName=PuTTY\r
+AppVerName=PuTTY version 0.61\r
+VersionInfoTextVersion=Release 0.61\r
+AppVersion=0.61\r
+VersionInfoVersion=0.61.0.0\r
+AppPublisher=Simon Tatham\r
+AppPublisherURL=http://www.chiark.greenend.org.uk/~sgtatham/putty/\r
+AppReadmeFile={app}\README.txt\r
+DefaultDirName={pf}\PuTTY\r
+DefaultGroupName=PuTTY\r
+SetupIconFile=puttyins.ico\r
+UninstallDisplayIcon={app}\putty.exe\r
+ChangesAssociations=yes\r
+;ChangesEnvironment=yes -- when PATH munging is sorted (probably)\r
+Compression=zip/9\r
+AllowNoIcons=yes\r
+\r
+[Files]\r
+; We flag all files with "restartreplace" et al primarily for the benefit\r
+; of unattended un/installations/upgrades, when the user is running one\r
+; of the apps at a time. Without it, the operation will fail noisily in\r
+; this situation.\r
+; This does mean that the user will be prompted to restart their machine\r
+; if any of the files _were_ open during installation (or, if /VERYSILENT\r
+; is used, the machine will be restarted automatically!). The /NORESTART\r
+; flag avoids this.\r
+; It might be nicer to have a "no worries, replace the file next time you\r
+; reboot" option, but the developers have no interest in adding one.\r
+; NB: apparently, using long (non-8.3) filenames with restartreplace is a\r
+; bad idea. (Not that we do.)\r
+Source: "putty.exe"; DestDir: "{app}"; Flags: promptifolder replacesameversion restartreplace uninsrestartdelete\r
+Source: "pageant.exe"; DestDir: "{app}"; Flags: promptifolder replacesameversion restartreplace uninsrestartdelete\r
+Source: "puttygen.exe"; DestDir: "{app}"; Flags: promptifolder replacesameversion restartreplace uninsrestartdelete\r
+Source: "pscp.exe"; DestDir: "{app}"; Flags: promptifolder replacesameversion restartreplace uninsrestartdelete\r
+Source: "psftp.exe"; DestDir: "{app}"; Flags: promptifolder replacesameversion restartreplace uninsrestartdelete\r
+Source: "plink.exe"; DestDir: "{app}"; Flags: promptifolder replacesameversion restartreplace uninsrestartdelete\r
+Source: "website.url"; DestDir: "{app}"; Flags: restartreplace uninsrestartdelete\r
+Source: "..\doc\putty.chm"; DestDir: "{app}"; Flags: restartreplace uninsrestartdelete\r
+Source: "..\doc\putty.hlp"; DestDir: "{app}"; Flags: restartreplace uninsrestartdelete\r
+Source: "..\doc\putty.cnt"; DestDir: "{app}"; Flags: restartreplace uninsrestartdelete\r
+Source: "..\LICENCE"; DestDir: "{app}"; Flags: restartreplace uninsrestartdelete\r
+Source: "README.txt"; DestDir: "{app}"; Flags: isreadme restartreplace uninsrestartdelete\r
+\r
+[Icons]\r
+Name: "{group}\PuTTY"; Filename: "{app}\putty.exe"\r
+; We have to fall back from the .chm to the older .hlp file on some Windows\r
+; versions.\r
+Name: "{group}\PuTTY Manual"; Filename: "{app}\putty.chm"; MinVersion: 4.1,5.0\r
+Name: "{group}\PuTTY Manual"; Filename: "{app}\putty.hlp"; OnlyBelowVersion: 4.1,5.0\r
+Name: "{group}\PuTTY Web Site"; Filename: "{app}\website.url"\r
+Name: "{group}\PSFTP"; Filename: "{app}\psftp.exe"\r
+Name: "{group}\PuTTYgen"; Filename: "{app}\puttygen.exe"\r
+Name: "{group}\Pageant"; Filename: "{app}\pageant.exe"\r
+Name: "{commondesktop}\PuTTY"; Filename: "{app}\putty.exe"; Tasks: desktopicon\common\r
+Name: "{userdesktop}\PuTTY"; Filename: "{app}\putty.exe"; Tasks: desktopicon\user\r
+; Putting this in {commonappdata} doesn't seem to work, on 98SE at least.\r
+Name: "{userappdata}\Microsoft\Internet Explorer\Quick Launch\PuTTY"; Filename: "{app}\putty.exe"; Tasks: quicklaunchicon\r
+\r
+[Tasks]\r
+Name: desktopicon; Description: "Create a &desktop icon for PuTTY"; GroupDescription: "Additional icons:"; Flags: unchecked\r
+Name: desktopicon\common; Description: "For all users"; GroupDescription: "Additional icons:"; Flags: exclusive unchecked\r
+Name: desktopicon\user; Description: "For the current user only"; GroupDescription: "Additional icons:"; Flags: exclusive unchecked\r
+Name: quicklaunchicon; Description: "Create a &Quick Launch icon for PuTTY (current user only)"; GroupDescription: "Additional icons:"; Flags: unchecked\r
+Name: associate; Description: "&Associate .PPK files (PuTTY Private Key) with Pageant and PuTTYgen"; GroupDescription: "Other tasks:"\r
+\r
+[Registry]\r
+Root: HKCR; Subkey: ".ppk"; ValueType: string; ValueName: ""; ValueData: "PuTTYPrivateKey"; Flags: uninsdeletevalue; Tasks: associate\r
+Root: HKCR; Subkey: "PuTTYPrivateKey"; ValueType: string; ValueName: ""; ValueData: "PuTTY Private Key File"; Flags: uninsdeletekey; Tasks: associate\r
+Root: HKCR; Subkey: "PuTTYPrivateKey\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\pageant.exe,0"; Tasks: associate\r
+Root: HKCR; Subkey: "PuTTYPrivateKey\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\pageant.exe"" ""%1"""; Tasks: associate\r
+Root: HKCR; Subkey: "PuTTYPrivateKey\shell\edit"; ValueType: string; ValueName: ""; ValueData: "&Edit"; Tasks: associate\r
+Root: HKCR; Subkey: "PuTTYPrivateKey\shell\edit\command"; ValueType: string; ValueName: ""; ValueData: """{app}\puttygen.exe"" ""%1"""; Tasks: associate\r
+; Add to PATH on NT-class OS?\r
+\r
+[UninstallRun]\r
+; -cleanup-during-uninstall is an undocumented option that tailors the\r
+; message displayed.\r
+; XXX: it would be nice if this task weren't run if a silent uninstall is\r
+; requested, but "skipifsilent" is disallowed.\r
+Filename: "{app}\putty.exe"; Parameters: "-cleanup-during-uninstall"; RunOnceId: "PuTTYCleanup"; StatusMsg: "Cleaning up saved sessions etc (optional)..."\r
+\r
+[Messages]\r
+; Since it's possible for the user to be asked to restart their computer,\r
+; we should override the default messages to explain exactly why, so they\r
+; can make an informed decision. (Especially as 95% of users won't need or\r
+; want to restart; see rant above.)\r
+FinishedRestartLabel=One or more [name] programs are still running. Setup will not replace these program files until you restart your computer. Would you like to restart now?\r
+; This message is popped up in a message box on a /SILENT install.\r
+FinishedRestartMessage=One or more [name] programs are still running.%nSetup will not replace these program files until you restart your computer.%n%nWould you like to restart now?\r
+; ...and this comes up if you try to uninstall.\r
+UninstalledAndNeedsRestart=One or more %1 programs are still running.%nThe program files will not be removed until your computer is restarted.%n%nWould you like to restart now?\r
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<!-- Do not attempt to do anything clever with this file, as some versions of
+ Windows are very sensitive to the exact format.
+ Hence, some facts below are fibs. -->
+<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
+<assemblyIdentity
+ version="0.0.0.0"
+ processorArchitecture="x86"
+ name="PuTTY"
+ type="win32" />
+ <description>A network client and terminal emulator</description>
+ <dependency>
+ <dependentAssembly>
+ <!-- Load Common Controls 6 instead of 5 to get WinXP native-
+ looking controls in the client area. -->
+ <assemblyIdentity type="win32"
+ name="Microsoft.Windows.Common-Controls"
+ version="6.0.0.0"
+ publicKeyToken="6595b64144ccf1df"
+ language="*"
+ processorArchitecture="x86"/>
+ </dependentAssembly>
+ </dependency>
+ <!-- Declare us to be "DPI-aware". -->
+ <asmv3:application xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
+ <asmv3:windowsSettings
+ xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">
+ <dpiAware>true</dpiAware>
+ </asmv3:windowsSettings>
+ </asmv3:application>
+</assembly>
--- /dev/null
+#include "rcstuff.h"
+
+#define APPNAME "PuTTY"
+#define APPDESC "SSH, Telnet and Rlogin client"
+
+#include "win_res.rc2"
+
+#ifndef NO_MANIFESTS
+1 RT_MANIFEST "putty.mft"
+#endif /* NO_MANIFESTS */
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<!-- Do not attempt to do anything clever with this file, as some versions of
+ Windows are very sensitive to the exact format.
+ Hence, some facts below are fibs. -->
+<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
+<assemblyIdentity
+ version="0.0.0.0"
+ processorArchitecture="x86"
+ name="PuTTYgen"
+ type="win32" />
+ <description>SSH key generator for PuTTY</description>
+ <dependency>
+ <dependentAssembly>
+ <!-- Load Common Controls 6 instead of 5 to get WinXP native-
+ looking controls in the client area. -->
+ <assemblyIdentity type="win32"
+ name="Microsoft.Windows.Common-Controls"
+ version="6.0.0.0"
+ publicKeyToken="6595b64144ccf1df"
+ language="*"
+ processorArchitecture="x86"/>
+ </dependentAssembly>
+ </dependency>
+ <!-- Declare us to be "DPI-aware". -->
+ <asmv3:application xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
+ <asmv3:windowsSettings
+ xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">
+ <dpiAware>true</dpiAware>
+ </asmv3:windowsSettings>
+ </asmv3:application>
+</assembly>
--- /dev/null
+/*
+ * Windows resources for PuTTYgen.
+ */
+
+#include "rcstuff.h"
+
+#define APPNAME "PuTTYgen"
+#define APPDESC "PuTTY SSH key generation utility"
+
+200 ICON "puttygen.ico"
+
+201 DIALOG DISCARDABLE 0, 0, 318, 270
+STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
+CAPTION "PuTTY Key Generator"
+FONT 8, "MS Shell Dlg"
+BEGIN
+END
+
+210 DIALOG DISCARDABLE 0, 0, 140, 60
+STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
+CAPTION "PuTTYgen: 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 "&Cancel", IDCANCEL, 80, 42, 40, 14
+END
+
+/* Accelerators used: cl */
+213 DIALOG DISCARDABLE 140, 40, 136, 70
+STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
+CAPTION "About PuTTYgen"
+FONT 8, "MS Shell Dlg"
+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.",
+ 103, 10, 34, 120, 16
+END
+
+/* No accelerators used */
+214 DIALOG DISCARDABLE 50, 50, 226, 263
+STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
+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 "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
+
+ 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
+ LTEXT "publish, distribute, sublicense, and/or sell copies of the Software,", 1009, 10, 98, 206, 8
+ LTEXT "and to permit persons to whom the Software is furnished to do so,", 1010, 10, 106, 206, 8
+ LTEXT "subject to the following conditions:", 1011, 10, 114, 206, 8
+
+ LTEXT "The above copyright notice and this permission notice shall be", 1012, 10, 130, 206, 8
+ LTEXT "included in all copies or substantial portions of the Software.", 1013, 10, 138, 206, 8
+
+ LTEXT "THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT", 1014, 10, 154, 206, 8
+ LTEXT "WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,", 1015, 10, 162, 206, 8
+ LTEXT "INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF", 1016, 10, 170, 206, 8
+ LTEXT "MERCHANTABILITY, FITNESS FOR A PARTICULAR", 1017, 10, 178, 206, 8
+ LTEXT "PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE", 1018, 10, 186, 206, 8
+ LTEXT "COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES", 1019, 10, 194, 206, 8
+ LTEXT "OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,", 1020, 10, 202, 206, 8
+ LTEXT "TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN", 1021, 10, 210, 206, 8
+ LTEXT "CONNECTION WITH THE SOFTWARE OR THE USE OR", 1022, 10, 218, 206, 8
+ LTEXT "OTHER DEALINGS IN THE SOFTWARE.", 1023, 10, 226, 206, 8
+
+END
+
+#include "version.rc2"
+
+#ifndef NO_MANIFESTS
+1 RT_MANIFEST "puttygen.mft"
+#endif /* NO_MANIFESTS */
--- /dev/null
+#include "rcstuff.h"
+
+#define APPNAME "PuTTYtel"
+#define APPDESC "Telnet and Rlogin client"
+
+#include "win_res.rc2"
+
+#ifndef NO_MANIFESTS
+/* FIXME */
+1 RT_MANIFEST "putty.mft"
+#endif /* NO_MANIFESTS */
--- /dev/null
+/*
+ * Miscellaneous stuff to include in all .rc files.
+ */
+
+#ifndef PUTTY_RCSTUFF_H
+#define PUTTY_RCSTUFF_H
+
+#ifdef __LCC__
+#include <win.h>
+#else
+
+/* Some compilers, like Borland, don't have winresrc.h */
+#ifndef NO_WINRESRC_H
+#ifndef MSVC4
+#include <winresrc.h>
+#else
+#include <winres.h>
+#endif
+#endif
+
+#endif /* end #ifdef __LCC__ */
+
+/* Some systems don't define this, so I do it myself if necessary */
+#ifndef TCS_MULTILINE
+#define TCS_MULTILINE 0x0200
+#endif
+
+/* Likewise */
+#ifndef RT_MANIFEST
+#define RT_MANIFEST 24
+#endif
+
+/* LCC is the offender here. */
+#ifndef VS_FF_DEBUG
+#define VS_FF_DEBUG 1
+#endif
+#ifndef VS_FF_PRERELEASE
+#define VS_FF_PRERELEASE 2
+#endif
+#ifndef VS_FF_PRIVATEBUILD
+#define VS_FF_PRIVATEBUILD 8
+#endif
+#ifndef VOS__WINDOWS32
+#define VOS__WINDOWS32 4
+#endif
+#ifndef VFT_APP
+#define VFT_APP 1
+#endif
+
+#endif /* PUTTY_RCSTUFF_H */
--- /dev/null
+/*
+ * sizetip.c - resize tips for PuTTY(tel) terminal window.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <tchar.h>
+
+#include "putty.h"
+
+static ATOM tip_class = 0;
+
+static HFONT tip_font;
+static COLORREF tip_bg;
+static COLORREF tip_text;
+
+static LRESULT CALLBACK SizeTipWndProc(HWND hWnd, UINT nMsg,
+ WPARAM wParam, LPARAM lParam)
+{
+
+ switch (nMsg) {
+ case WM_ERASEBKGND:
+ return TRUE;
+
+ case WM_PAINT:
+ {
+ HBRUSH hbr;
+ HGDIOBJ holdbr;
+ RECT cr;
+ int wtlen;
+ LPTSTR wt;
+ HDC hdc;
+
+ PAINTSTRUCT ps;
+ hdc = BeginPaint(hWnd, &ps);
+
+ SelectObject(hdc, tip_font);
+ SelectObject(hdc, GetStockObject(BLACK_PEN));
+
+ hbr = CreateSolidBrush(tip_bg);
+ holdbr = SelectObject(hdc, hbr);
+
+ GetClientRect(hWnd, &cr);
+ Rectangle(hdc, cr.left, cr.top, cr.right, cr.bottom);
+
+ wtlen = GetWindowTextLength(hWnd);
+ wt = (LPTSTR) snewn(wtlen + 1, TCHAR);
+ GetWindowText(hWnd, wt, wtlen + 1);
+
+ SetTextColor(hdc, tip_text);
+ SetBkColor(hdc, tip_bg);
+
+ TextOut(hdc, cr.left + 3, cr.top + 3, wt, wtlen);
+
+ sfree(wt);
+
+ SelectObject(hdc, holdbr);
+ DeleteObject(hbr);
+
+ EndPaint(hWnd, &ps);
+ }
+ return 0;
+
+ case WM_NCHITTEST:
+ return HTTRANSPARENT;
+
+ case WM_DESTROY:
+ DeleteObject(tip_font);
+ tip_font = NULL;
+ break;
+
+ case WM_SETTEXT:
+ {
+ LPCTSTR str = (LPCTSTR) lParam;
+ SIZE sz;
+ HDC hdc = CreateCompatibleDC(NULL);
+
+ SelectObject(hdc, tip_font);
+ GetTextExtentPoint32(hdc, str, _tcslen(str), &sz);
+
+ SetWindowPos(hWnd, NULL, 0, 0, sz.cx + 6, sz.cy + 6,
+ SWP_NOZORDER | SWP_NOMOVE | SWP_NOACTIVATE);
+ InvalidateRect(hWnd, NULL, FALSE);
+
+ DeleteDC(hdc);
+ }
+ break;
+ }
+
+ return DefWindowProc(hWnd, nMsg, wParam, lParam);
+}
+
+static HWND tip_wnd = NULL;
+static int tip_enabled = 0;
+
+void UpdateSizeTip(HWND src, int cx, int cy)
+{
+ TCHAR str[32];
+
+ if (!tip_enabled)
+ return;
+
+ if (!tip_wnd) {
+ NONCLIENTMETRICS nci;
+
+ /* First make sure the window class is registered */
+
+ if (!tip_class) {
+ WNDCLASS wc;
+ wc.style = CS_HREDRAW | CS_VREDRAW;
+ wc.lpfnWndProc = SizeTipWndProc;
+ wc.cbClsExtra = 0;
+ wc.cbWndExtra = 0;
+ wc.hInstance = hinst;
+ wc.hIcon = NULL;
+ wc.hCursor = NULL;
+ wc.hbrBackground = NULL;
+ wc.lpszMenuName = NULL;
+ wc.lpszClassName = "SizeTipClass";
+
+ tip_class = RegisterClass(&wc);
+ }
+#if 0
+ /* Default values based on Windows Standard color scheme */
+
+ tip_font = GetStockObject(SYSTEM_FONT);
+ tip_bg = RGB(255, 255, 225);
+ tip_text = RGB(0, 0, 0);
+#endif
+
+ /* Prepare other GDI objects and drawing info */
+
+ tip_bg = GetSysColor(COLOR_INFOBK);
+ tip_text = GetSysColor(COLOR_INFOTEXT);
+
+ memset(&nci, 0, sizeof(NONCLIENTMETRICS));
+ nci.cbSize = sizeof(NONCLIENTMETRICS);
+ SystemParametersInfo(SPI_GETNONCLIENTMETRICS,
+ sizeof(NONCLIENTMETRICS), &nci, 0);
+ tip_font = CreateFontIndirect(&nci.lfStatusFont);
+ }
+
+ /* Generate the tip text */
+
+ sprintf(str, "%dx%d", cx, cy);
+
+ if (!tip_wnd) {
+ HDC hdc;
+ SIZE sz;
+ RECT wr;
+ int ix, iy;
+
+ /* calculate the tip's size */
+
+ hdc = CreateCompatibleDC(NULL);
+ GetTextExtentPoint32(hdc, str, _tcslen(str), &sz);
+ DeleteDC(hdc);
+
+ GetWindowRect(src, &wr);
+
+ ix = wr.left;
+ if (ix < 16)
+ ix = 16;
+
+ iy = wr.top - sz.cy;
+ if (iy < 16)
+ iy = 16;
+
+ /* Create the tip window */
+
+ tip_wnd =
+ CreateWindowEx(WS_EX_TOOLWINDOW | WS_EX_TOPMOST,
+ MAKEINTRESOURCE(tip_class), str, WS_POPUP, ix,
+ iy, sz.cx, sz.cy, NULL, NULL, hinst, NULL);
+
+ ShowWindow(tip_wnd, SW_SHOWNOACTIVATE);
+
+ } else {
+
+ /* Tip already exists, just set the text */
+
+ SetWindowText(tip_wnd, str);
+ }
+}
+
+void EnableSizeTip(int bEnable)
+{
+ if (tip_wnd && !bEnable) {
+ DestroyWindow(tip_wnd);
+ tip_wnd = NULL;
+ }
+
+ tip_enabled = bEnable;
+}
--- /dev/null
+/*
+ * Standard Windows version information.
+ * (For inclusion in other .rc files with appropriate macro definitions.)
+ * FIXME: This file is called '.rc2' rather than '.rc' to avoid MSVC trying
+ * to compile it on its own when using the project files. Nicer solutions
+ * welcome.
+ */
+
+/*
+ * Binary versions in Windows are major.minor.build.revision. Each
+ * component is 16-bit.
+ * Here we have:
+ * major.minor
+ * PuTTY version number (e.g. 0.58). (We've made a policy decision
+ * that these will be numeric from now on.)
+ * Present in releases and snapshots (for the sake of monotonicity
+ * in version numbers).
+ * build
+ * In releases, always 0.
+ * In snapshots, nearest Subversion revision. (It shouldn't be
+ * assumed that only one binary will have a given build number, of
+ * course.)
+ * revision
+ * Reserved; always 0.
+ *
+ * Examples of these version numbers:
+ * Release: 0.58.0.0 (but 0.58 didn't have a VERSIONINFO resource)
+ * Snapshot: 0.58.6356.0 (between 0.58 and the next release)
+ * Local: 0.0.0.0
+ */
+
+/*
+ * Mechanics of version naming/numbering.
+ * (This is a ripoff of ../version.c.)
+ */
+
+#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,61
+
+#if defined SNAPSHOT
+
+/* Make SVN_REV mandatory for snapshots, to avoid issuing binary
+ * version numbers that look like full releases. */
+#ifndef SVN_REV
+#error SVN_REV not defined/nonzero for snapshot build
+#endif
+
+#define VERSION_TEXT "Development snapshot " STR(SNAPSHOT) ":r" STR(SVN_REV)
+#ifdef MODIFIED
+#define BINARY_VERSION 0,0,0,0
+#else
+#define BINARY_VERSION BASE_VERSION,SVN_REV,0
+#endif
+
+#elif defined RELEASE
+
+#define VERSION_TEXT "Release " STR(RELEASE)
+#define BINARY_VERSION BASE_VERSION,0,0
+
+#elif defined SVN_REV
+
+#define VERSION_TEXT "Custom build r" STR(SVN_REV)
+#ifdef MODIFIED
+#define BINARY_VERSION 0,0,0,0
+#else
+#define BINARY_VERSION BASE_VERSION,SVN_REV,0
+#endif
+
+#else
+
+/* We can't reliably get the same date and time as version.c, so
+ * we won't bother trying. */
+#define VERSION_TEXT "Unidentified build"
+#define BINARY_VERSION 0,0,0,0
+
+#endif
+
+/*
+ * The actual VERSIONINFO resource.
+ */
+VS_VERSION_INFO VERSIONINFO
+/* (None of this "fixed" info appears to be trivially user-visible on
+ * Win98SE. The binary version does show up on Win2K.) */
+FILEVERSION BINARY_VERSION
+PRODUCTVERSION BINARY_VERSION /* version of whole suite */
+FILEFLAGSMASK VS_FF_DEBUG | VS_FF_PRERELEASE | VS_FF_PRIVATEBUILD
+FILEFLAGS 0x0L
+#if defined DEBUG
+ | VS_FF_DEBUG
+#endif
+#if defined SNAPSHOT
+ | VS_FF_PRERELEASE
+#elif !defined RELEASE
+ | VS_FF_PRIVATEBUILD
+#endif
+FILEOS VOS__WINDOWS32
+FILETYPE VFT_APP
+FILESUBTYPE 0x0L /* n/a for VFT_APP */
+BEGIN
+ /* (On Win98SE and Win2K, we can see most of this on the Version tab
+ * in the file properties in Explorer.) */
+ BLOCK "StringFileInfo"
+ BEGIN
+ /* "lang-charset" LLLLCCCC = (UK English, Unicode) */
+ BLOCK "080904B0"
+ BEGIN
+ 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."
+#if (!defined SNAPSHOT) && (!defined RELEASE)
+ /* Only if VS_FF_PRIVATEBUILD. */
+ VALUE "PrivateBuild", VERSION_TEXT /* NBI */
+#endif
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ /* Once again -- same meanings -- apparently necessary */
+ VALUE "Translation", 0x809, 1200
+ END
+END
+
+#undef VERSION_TEXT
+#undef BASE_VERSION
+#undef BINARY_VERSION
--- /dev/null
+/*
+ * win_res.h - constants shared between win_res.rc2 and the C code.
+ */
+
+#ifndef PUTTY_WIN_RES_H
+#define PUTTY_WIN_RES_H
+
+#define IDI_MAINICON 200
+#define IDI_CFGICON 201
+
+#define IDD_MAINBOX 102
+#define IDD_LOGBOX 110
+#define IDD_ABOUTBOX 111
+#define IDD_RECONF 112
+#define IDD_LICENCEBOX 113
+
+#define IDN_LIST 1001
+#define IDN_COPY 1002
+
+#define IDA_ICON 1001
+#define IDA_TEXT1 1002
+#define IDA_VERSION 1003
+#define IDA_TEXT2 1004
+#define IDA_LICENCE 1005
+#define IDA_WEB 1006
+
+#define IDC_TAB 1001
+#define IDC_TABSTATIC1 1002
+#define IDC_TABSTATIC2 1003
+#define IDC_TABLIST 1004
+#define IDC_HELPBTN 1005
+#define IDC_ABOUT 1006
+
+#endif
--- /dev/null
+/*
+ * Windows resources shared between PuTTY and PuTTYtel, to be #include'd
+ * after defining appropriate macros.
+ * Note that many of these strings mention PuTTY. Due to restrictions in
+ * VC's handling of string concatenation, this can't easily be fixed.
+ * It's fixed up at runtime.
+ * FIXME: This file is called '.rc2' rather than '.rc' to avoid MSVC trying
+ * to compile it on its own when using the project files. Nicer solutions
+ * welcome.
+ */
+
+#include "win_res.h"
+
+IDI_MAINICON ICON "putty.ico"
+
+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"
+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", IDA_TEXT1, 10, 6, 194, 8
+ CTEXT "", IDA_VERSION, 10, 16, 194, 16
+ CTEXT "\251 1997-2011 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 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 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
+END
+
+/* No accelerators used */
+IDD_LICENCEBOX DIALOG DISCARDABLE 50, 50, 226, 263
+STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
+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 "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
+
+ 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
+ LTEXT "publish, distribute, sublicense, and/or sell copies of the Software,", 1009, 10, 98, 206, 8
+ LTEXT "and to permit persons to whom the Software is furnished to do so,", 1010, 10, 106, 206, 8
+ LTEXT "subject to the following conditions:", 1011, 10, 114, 206, 8
+
+ LTEXT "The above copyright notice and this permission notice shall be", 1012, 10, 130, 206, 8
+ LTEXT "included in all copies or substantial portions of the Software.", 1013, 10, 138, 206, 8
+
+ LTEXT "THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT", 1014, 10, 154, 206, 8
+ LTEXT "WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,", 1015, 10, 162, 206, 8
+ LTEXT "INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF", 1016, 10, 170, 206, 8
+ LTEXT "MERCHANTABILITY, FITNESS FOR A PARTICULAR", 1017, 10, 178, 206, 8
+ LTEXT "PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE", 1018, 10, 186, 206, 8
+ LTEXT "COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES", 1019, 10, 194, 206, 8
+ LTEXT "OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,", 1020, 10, 202, 206, 8
+ LTEXT "TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN", 1021, 10, 210, 206, 8
+ LTEXT "CONNECTION WITH THE SOFTWARE OR THE USE OR", 1022, 10, 218, 206, 8
+ LTEXT "OTHER DEALINGS IN THE SOFTWARE.", 1023, 10, 226, 206, 8
+
+END
+
+#include "version.rc2"
--- /dev/null
+/*
+ * wincfg.c - the Windows-specific parts of the PuTTY configuration
+ * box.
+ */
+
+#include <assert.h>
+#include <stdlib.h>
+
+#include "putty.h"
+#include "dialog.h"
+#include "storage.h"
+
+static void about_handler(union control *ctrl, void *dlg,
+ void *data, int event)
+{
+ HWND *hwndp = (HWND *)ctrl->generic.context.p;
+
+ if (event == EVENT_ACTION) {
+ modal_about_box(*hwndp);
+ }
+}
+
+static void help_handler(union control *ctrl, void *dlg,
+ void *data, int event)
+{
+ HWND *hwndp = (HWND *)ctrl->generic.context.p;
+
+ if (event == EVENT_ACTION) {
+ show_help(*hwndp);
+ }
+}
+
+static void variable_pitch_handler(union control *ctrl, void *dlg,
+ void *data, int event)
+{
+ if (event == EVENT_REFRESH) {
+ dlg_checkbox_set(ctrl, dlg, !dlg_get_fixed_pitch_flag(dlg));
+ } else if (event == EVENT_VALCHANGE) {
+ dlg_set_fixed_pitch_flag(dlg, !dlg_checkbox_get(ctrl, dlg));
+ }
+}
+
+void win_setup_config_box(struct controlbox *b, HWND *hwndp, int has_help,
+ int midsession, int protocol)
+{
+ struct controlset *s;
+ union control *c;
+ char *str;
+
+ if (!midsession) {
+ /*
+ * Add the About and Help buttons to the standard panel.
+ */
+ s = ctrl_getset(b, "", "", "");
+ c = ctrl_pushbutton(s, "About", 'a', HELPCTX(no_help),
+ about_handler, P(hwndp));
+ c->generic.column = 0;
+ if (has_help) {
+ c = ctrl_pushbutton(s, "Help", 'h', HELPCTX(no_help),
+ help_handler, P(hwndp));
+ c->generic.column = 1;
+ }
+ }
+
+ /*
+ * Full-screen mode is a Windows peculiarity; hence
+ * scrollbar_in_fullscreen is as well.
+ */
+ 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)));
+ /*
+ * 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)) {
+ /*
+ * Control i is the scrollbar checkbox.
+ * Control s->ncontrols-1 is the scrollbar-in-FS one.
+ */
+ if (i < s->ncontrols-2) {
+ c = s->ctrls[s->ncontrols-1];
+ memmove(s->ctrls+i+2, s->ctrls+i+1,
+ (s->ncontrols-i-2)*sizeof(union control *));
+ s->ctrls[i+1] = c;
+ }
+ break;
+ }
+ }
+ }
+
+ /*
+ * Windows has the AltGr key, which has various Windows-
+ * specific options.
+ */
+ 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)));
+ ctrl_checkbox(s, "Control-Alt is different from AltGr", 'd',
+ HELPCTX(keyboard_ctrlalt),
+ dlg_stdcheckbox_handler, I(offsetof(Config,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
+ * `beep' option, and add extra buttons to it.
+ *
+ * Note that although this _looks_ like a hideous hack, it's
+ * actually all above board. The well-defined interface to the
+ * per-platform dialog box code is the _data structures_ `union
+ * control', `struct controlset' and so on; so code like this
+ * that reaches into those data structures and changes bits of
+ * them is perfectly legitimate and crosses no boundaries. All
+ * the ctrl_* routines that create most of the controls are
+ * convenient shortcuts provided on the cross-platform side of
+ * the interface, and template creation code is under no actual
+ * obligation to use them.
+ */
+ s = ctrl_getset(b, "Terminal/Bell", "style", "Set the style of bell");
+ {
+ 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->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");
+ c->radio.buttons[c->radio.nbuttons-2] =
+ dupstr("Beep using the PC speaker");
+ c->radio.buttondata =
+ sresize(c->radio.buttondata, c->radio.nbuttons, intorptr);
+ c->radio.buttondata[c->radio.nbuttons-1] = I(BELL_WAVEFILE);
+ c->radio.buttondata[c->radio.nbuttons-2] = I(BELL_PCSPEAKER);
+ if (c->radio.shortcuts) {
+ c->radio.shortcuts =
+ sresize(c->radio.shortcuts, c->radio.nbuttons, char);
+ c->radio.shortcuts[c->radio.nbuttons-1] = NO_SHORTCUT;
+ c->radio.shortcuts[c->radio.nbuttons-2] = NO_SHORTCUT;
+ }
+ break;
+ }
+ }
+ }
+ 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)));
+
+ /*
+ * 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)),
+ "Disabled", I(B_IND_DISABLED),
+ "Flashing", I(B_IND_FLASH),
+ "Steady", I(B_IND_STEADY), NULL);
+
+ /*
+ * The sunken-edge border is a Windows GUI feature.
+ */
+ 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)));
+
+ /*
+ * Configurable font quality settings for Windows.
+ */
+ s = ctrl_getset(b, "Window/Appearance", "font",
+ "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)),
+ "Antialiased", I(FQ_ANTIALIASED),
+ "Non-Antialiased", I(FQ_NONANTIALIASED),
+ "ClearType", I(FQ_CLEARTYPE),
+ "Default", I(FQ_DEFAULT), NULL);
+
+ /*
+ * Cyrillic Lock is a horrid misfeature even on Windows, and
+ * the least we can do is ensure it never makes it to any other
+ * 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)));
+
+ /*
+ * On Windows we can use but not enumerate translation tables
+ * from the operating system. Briefly document this.
+ */
+ s = ctrl_getset(b, "Window/Translation", "trans",
+ "Character set translation on received data");
+ ctrl_text(s, "(Codepages supported by Windows but not listed here, "
+ "such as CP866 on many systems, can be entered manually)",
+ HELPCTX(translation_codepage));
+
+ /*
+ * Windows has the weird OEM font mode, which gives us some
+ * additional options when working with line-drawing
+ * characters.
+ */
+ str = dupprintf("Adjust how %s displays line drawing characters", appname);
+ s = ctrl_getset(b, "Window/Translation", "linedraw", str);
+ sfree(str);
+ {
+ 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->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");
+ c->radio.buttons[c->radio.nbuttons-2] =
+ dupstr("Use font in both ANSI and OEM modes");
+ c->radio.buttons[c->radio.nbuttons-1] =
+ dupstr("Use font in OEM mode only");
+ c->radio.buttondata =
+ sresize(c->radio.buttondata, c->radio.nbuttons, intorptr);
+ c->radio.buttondata[c->radio.nbuttons-3] = I(VT_XWINDOWS);
+ c->radio.buttondata[c->radio.nbuttons-2] = I(VT_OEMANSI);
+ c->radio.buttondata[c->radio.nbuttons-1] = I(VT_OEMONLY);
+ if (!c->radio.shortcuts) {
+ int j;
+ c->radio.shortcuts = snewn(c->radio.nbuttons, char);
+ for (j = 0; j < c->radio.nbuttons; j++)
+ c->radio.shortcuts[j] = NO_SHORTCUT;
+ } else {
+ c->radio.shortcuts = sresize(c->radio.shortcuts,
+ c->radio.nbuttons, char);
+ }
+ c->radio.shortcuts[c->radio.nbuttons-3] = 'x';
+ c->radio.shortcuts[c->radio.nbuttons-2] = 'b';
+ c->radio.shortcuts[c->radio.nbuttons-1] = 'e';
+ break;
+ }
+ }
+ }
+
+ /*
+ * RTF paste is Windows-specific.
+ */
+ 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)));
+
+ /*
+ * 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.
+ */
+ 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)),
+ "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
+ * bottom, so we'll just do some shuffling now we've set it
+ * up...
+ */
+ c = s->ctrls[s->ncontrols-1]; /* this should be the new control */
+ memmove(s->ctrls+1, s->ctrls, (s->ncontrols-1)*sizeof(union control *));
+ s->ctrls[0] = c;
+
+ /*
+ * Logical palettes don't even make sense anywhere except Windows.
+ */
+ 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)));
+ ctrl_checkbox(s, "Use system colours", 's',
+ HELPCTX(colours_system),
+ dlg_stdcheckbox_handler, I(offsetof(Config,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)),
+ "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);
+
+ /*
+ * Most of the Window/Behaviour stuff is there to mimic Windows
+ * conventions which PuTTY can optionally disregard. Hence,
+ * 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)));
+ ctrl_checkbox(s, "System menu appears on ALT-Space", 'y',
+ HELPCTX(behaviour_altspace),
+ dlg_stdcheckbox_handler, I(offsetof(Config,alt_space)));
+ ctrl_checkbox(s, "System menu appears on ALT alone", 'l',
+ HELPCTX(behaviour_altonly),
+ dlg_stdcheckbox_handler, I(offsetof(Config,alt_only)));
+ ctrl_checkbox(s, "Ensure window is always on top", 'e',
+ HELPCTX(behaviour_alwaysontop),
+ dlg_stdcheckbox_handler, I(offsetof(Config,alwaysontop)));
+ ctrl_checkbox(s, "Full screen on Alt-Enter", 'f',
+ HELPCTX(behaviour_altenter),
+ dlg_stdcheckbox_handler,
+ I(offsetof(Config,fullscreenonaltenter)));
+
+ /*
+ * Windows supports a local-command proxy. This also means we
+ * must adjust the text on the `Telnet command' control.
+ */
+ if (!midsession) {
+ 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->radio.nbuttons++;
+ c->radio.buttons =
+ sresize(c->radio.buttons, c->radio.nbuttons, char *);
+ c->radio.buttons[c->radio.nbuttons-1] =
+ dupstr("Local");
+ c->radio.buttondata =
+ sresize(c->radio.buttondata, c->radio.nbuttons, intorptr);
+ c->radio.buttondata[c->radio.nbuttons-1] = I(PROXY_CMD);
+ break;
+ }
+ }
+
+ 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);
+ sfree(c->generic.label);
+ c->generic.label = dupstr("Telnet command, or local"
+ " proxy command");
+ break;
+ }
+ }
+ }
+
+ /*
+ * Serial back end is available on Windows.
+ */
+ if (!midsession || (protocol == PROT_SERIAL))
+ ser_setup_config_box(b, midsession, 0x1F, 0x0F);
+
+ /*
+ * $XAUTHORITY is not reliable on Windows, so we provide a
+ * means to override it.
+ */
+ 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)));
+ }
+}
--- /dev/null
+/*
+ * wincons.c - various interactive-prompt routines shared between
+ * the Windows console PuTTY tools
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+
+#include "putty.h"
+#include "storage.h"
+#include "ssh.h"
+
+int console_batch_mode = FALSE;
+
+static void *console_logctx = NULL;
+
+/*
+ * Clean up and exit.
+ */
+void cleanup_exit(int code)
+{
+ /*
+ * Clean up.
+ */
+ sk_cleanup();
+
+ random_save_seed();
+#ifdef MSCRYPTOAPI
+ crypto_wrapup();
+#endif
+
+ exit(code);
+}
+
+void set_busy_status(void *frontend, int status)
+{
+}
+
+void notify_remote_exit(void *frontend)
+{
+}
+
+void timer_change_notify(long next)
+{
+}
+
+int verify_ssh_host_key(void *frontend, char *host, int port, char *keytype,
+ char *keystr, char *fingerprint,
+ void (*callback)(void *ctx, int result), void *ctx)
+{
+ int ret;
+ HANDLE hin;
+ DWORD savemode, i;
+
+ static const char absentmsg_batch[] =
+ "The server's host key is not cached in the registry. You\n"
+ "have no guarantee that the server is the computer you\n"
+ "think it is.\n"
+ "The server's %s key fingerprint is:\n"
+ "%s\n"
+ "Connection abandoned.\n";
+ static const char absentmsg[] =
+ "The server's host key is not cached in the registry. You\n"
+ "have no guarantee that the server is the computer you\n"
+ "think it is.\n"
+ "The server's %s key fingerprint is:\n"
+ "%s\n"
+ "If you trust this host, enter \"y\" to add the key to\n"
+ "PuTTY's cache and carry on connecting.\n"
+ "If you want to carry on connecting just once, without\n"
+ "adding the key to the cache, enter \"n\".\n"
+ "If you do not trust this host, press Return to abandon the\n"
+ "connection.\n"
+ "Store key in cache? (y/n) ";
+
+ static const char wrongmsg_batch[] =
+ "WARNING - POTENTIAL SECURITY BREACH!\n"
+ "The server's host key does not match the one PuTTY has\n"
+ "cached in the registry. This means that either the\n"
+ "server administrator has changed the host key, or you\n"
+ "have actually connected to another computer pretending\n"
+ "to be the server.\n"
+ "The new %s key fingerprint is:\n"
+ "%s\n"
+ "Connection abandoned.\n";
+ static const char wrongmsg[] =
+ "WARNING - POTENTIAL SECURITY BREACH!\n"
+ "The server's host key does not match the one PuTTY has\n"
+ "cached in the registry. This means that either the\n"
+ "server administrator has changed the host key, or you\n"
+ "have actually connected to another computer pretending\n"
+ "to be the server.\n"
+ "The new %s key fingerprint is:\n"
+ "%s\n"
+ "If you were expecting this change and trust the new key,\n"
+ "enter \"y\" to update PuTTY's cache and continue connecting.\n"
+ "If you want to carry on connecting but without updating\n"
+ "the cache, enter \"n\".\n"
+ "If you want to abandon the connection completely, press\n"
+ "Return to cancel. Pressing Return is the ONLY guaranteed\n"
+ "safe choice.\n"
+ "Update cached key? (y/n, Return cancels connection) ";
+
+ static const char abandoned[] = "Connection abandoned.\n";
+
+ char line[32];
+
+ /*
+ * Verify the key against the registry.
+ */
+ ret = verify_host_key(host, port, keytype, keystr);
+
+ if (ret == 0) /* success - key matched OK */
+ return 1;
+
+ if (ret == 2) { /* key was different */
+ if (console_batch_mode) {
+ fprintf(stderr, wrongmsg_batch, keytype, fingerprint);
+ return 0;
+ }
+ fprintf(stderr, wrongmsg, keytype, fingerprint);
+ fflush(stderr);
+ }
+ if (ret == 1) { /* key was absent */
+ if (console_batch_mode) {
+ fprintf(stderr, absentmsg_batch, keytype, fingerprint);
+ return 0;
+ }
+ fprintf(stderr, absentmsg, keytype, fingerprint);
+ fflush(stderr);
+ }
+
+ hin = GetStdHandle(STD_INPUT_HANDLE);
+ GetConsoleMode(hin, &savemode);
+ SetConsoleMode(hin, (savemode | ENABLE_ECHO_INPUT |
+ ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT));
+ ReadFile(hin, line, sizeof(line) - 1, &i, NULL);
+ SetConsoleMode(hin, savemode);
+
+ if (line[0] != '\0' && line[0] != '\r' && line[0] != '\n') {
+ if (line[0] == 'y' || line[0] == 'Y')
+ store_host_key(host, port, keytype, keystr);
+ return 1;
+ } else {
+ fprintf(stderr, abandoned);
+ return 0;
+ }
+}
+
+void update_specials_menu(void *frontend)
+{
+}
+
+/*
+ * Ask whether the selected algorithm is acceptable (since it was
+ * below the configured 'warn' threshold).
+ */
+int askalg(void *frontend, const char *algtype, const char *algname,
+ void (*callback)(void *ctx, int result), void *ctx)
+{
+ HANDLE hin;
+ DWORD savemode, i;
+
+ static const char msg[] =
+ "The first %s supported by the server is\n"
+ "%s, which is below the configured warning threshold.\n"
+ "Continue with connection? (y/n) ";
+ static const char msg_batch[] =
+ "The first %s supported by the server is\n"
+ "%s, which is below the configured warning threshold.\n"
+ "Connection abandoned.\n";
+ static const char abandoned[] = "Connection abandoned.\n";
+
+ char line[32];
+
+ if (console_batch_mode) {
+ fprintf(stderr, msg_batch, algtype, algname);
+ return 0;
+ }
+
+ fprintf(stderr, msg, algtype, algname);
+ fflush(stderr);
+
+ hin = GetStdHandle(STD_INPUT_HANDLE);
+ GetConsoleMode(hin, &savemode);
+ SetConsoleMode(hin, (savemode | ENABLE_ECHO_INPUT |
+ ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT));
+ ReadFile(hin, line, sizeof(line) - 1, &i, NULL);
+ SetConsoleMode(hin, savemode);
+
+ if (line[0] == 'y' || line[0] == 'Y') {
+ return 1;
+ } else {
+ fprintf(stderr, abandoned);
+ return 0;
+ }
+}
+
+/*
+ * 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,
+ void (*callback)(void *ctx, int result), void *ctx)
+{
+ HANDLE hin;
+ DWORD savemode, i;
+
+ static const char msgtemplate[] =
+ "The session log file \"%.*s\" already exists.\n"
+ "You can overwrite it with a new session log,\n"
+ "append your session log to the end of it,\n"
+ "or disable session logging for this session.\n"
+ "Enter \"y\" to wipe the file, \"n\" to append to it,\n"
+ "or just press Return to disable logging.\n"
+ "Wipe the log file? (y/n, Return cancels logging) ";
+
+ static const char msgtemplate_batch[] =
+ "The session log file \"%.*s\" already exists.\n"
+ "Logging will not be enabled.\n";
+
+ char line[32];
+
+ if (console_batch_mode) {
+ fprintf(stderr, msgtemplate_batch, FILENAME_MAX, filename.path);
+ fflush(stderr);
+ return 0;
+ }
+ fprintf(stderr, msgtemplate, FILENAME_MAX, filename.path);
+ fflush(stderr);
+
+ hin = GetStdHandle(STD_INPUT_HANDLE);
+ GetConsoleMode(hin, &savemode);
+ SetConsoleMode(hin, (savemode | ENABLE_ECHO_INPUT |
+ ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT));
+ ReadFile(hin, line, sizeof(line) - 1, &i, NULL);
+ SetConsoleMode(hin, savemode);
+
+ if (line[0] == 'y' || line[0] == 'Y')
+ return 2;
+ else if (line[0] == 'n' || line[0] == 'N')
+ return 1;
+ else
+ return 0;
+}
+
+/*
+ * Warn about the obsolescent key file format.
+ *
+ * Uniquely among these functions, this one does _not_ expect a
+ * frontend handle. This means that if PuTTY is ported to a
+ * platform which requires frontend handles, this function will be
+ * an anomaly. Fortunately, the problem it addresses will not have
+ * been present on that platform, so it can plausibly be
+ * implemented as an empty function.
+ */
+void old_keyfile_warning(void)
+{
+ static const char message[] =
+ "You are loading an SSH-2 private key which has an\n"
+ "old version of the file format. This means your key\n"
+ "file is not fully tamperproof. Future versions of\n"
+ "PuTTY may stop supporting this private key format,\n"
+ "so we recommend you convert your key to the new\n"
+ "format.\n"
+ "\n"
+ "Once the key is loaded into PuTTYgen, you can perform\n"
+ "this conversion simply by saving it again.\n";
+
+ fputs(message, stderr);
+}
+
+/*
+ * Display the fingerprints of the PGP Master Keys to the user.
+ */
+void pgp_fingerprints(void)
+{
+ fputs("These are the fingerprints of the PuTTY PGP Master Keys. They can\n"
+ "be used to establish a trust path from this executable to another\n"
+ "one. See the manual for more information.\n"
+ "(Note: these fingerprints have nothing to do with SSH!)\n"
+ "\n"
+ "PuTTY Master Key (RSA), 1024-bit:\n"
+ " " PGP_RSA_MASTER_KEY_FP "\n"
+ "PuTTY Master Key (DSA), 1024-bit:\n"
+ " " PGP_DSA_MASTER_KEY_FP "\n", stdout);
+}
+
+void console_provide_logctx(void *logctx)
+{
+ console_logctx = logctx;
+}
+
+void logevent(void *frontend, const char *string)
+{
+ log_eventlog(console_logctx, string);
+}
+
+static void console_data_untrusted(HANDLE hout, const char *data, int len)
+{
+ DWORD dummy;
+ /* FIXME: control-character filtering */
+ WriteFile(hout, data, len, &dummy, NULL);
+}
+
+int console_get_userpass_input(prompts_t *p, unsigned char *in, int inlen)
+{
+ HANDLE hin, hout;
+ size_t curr_prompt;
+
+ /*
+ * 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);
+ }
+
+ /*
+ * The prompts_t might contain a message to be displayed but no
+ * actual prompt. More usually, though, it will contain
+ * questions that the user needs to answer, in which case we
+ * need to ensure that we're able to get the answers.
+ */
+ if (p->n_prompts) {
+ if (console_batch_mode)
+ return 0;
+ hin = GetStdHandle(STD_INPUT_HANDLE);
+ if (hin == INVALID_HANDLE_VALUE) {
+ fprintf(stderr, "Cannot get standard input handle\n");
+ cleanup_exit(1);
+ }
+ }
+
+ /*
+ * And if we have anything to print, we need standard output.
+ */
+ if ((p->name_reqd && p->name) || p->instruction || p->n_prompts) {
+ hout = GetStdHandle(STD_OUTPUT_HANDLE);
+ if (hout == INVALID_HANDLE_VALUE) {
+ fprintf(stderr, "Cannot get standard output handle\n");
+ cleanup_exit(1);
+ }
+ }
+
+ /*
+ * Preamble.
+ */
+ /* We only print the `name' caption if we have to... */
+ if (p->name_reqd && p->name) {
+ size_t l = strlen(p->name);
+ console_data_untrusted(hout, p->name, l);
+ if (p->name[l-1] != '\n')
+ console_data_untrusted(hout, "\n", 1);
+ }
+ /* ...but we always print any `instruction'. */
+ if (p->instruction) {
+ size_t l = strlen(p->instruction);
+ console_data_untrusted(hout, p->instruction, l);
+ if (p->instruction[l-1] != '\n')
+ console_data_untrusted(hout, "\n", 1);
+ }
+
+ for (curr_prompt = 0; curr_prompt < p->n_prompts; curr_prompt++) {
+
+ DWORD savemode, newmode, i = 0;
+ 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;
+ else
+ 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);
+
+ 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);
+ }
+
+ }
+
+ return 1; /* success */
+
+}
+
+void frontend_keypress(void *handle)
+{
+ /*
+ * This is nothing but a stub, in console code.
+ */
+ return;
+}
--- /dev/null
+/*
+ * winctrls.c: routines to self-manage the controls in a dialog
+ * box.
+ */
+
+/*
+ * Possible TODO in new cross-platform config box stuff:
+ *
+ * - When lining up two controls alongside each other, I wonder if
+ * we could conveniently arrange to centre them vertically?
+ * Particularly ugly in the current setup is the `Add new
+ * forwarded port:' static next to the rather taller `Remove'
+ * button.
+ */
+
+#include <assert.h>
+#include <ctype.h>
+
+#include "putty.h"
+#include "misc.h"
+#include "dialog.h"
+
+#include <commctrl.h>
+
+#define GAPBETWEEN 3
+#define GAPWITHIN 1
+#define GAPXBOX 7
+#define GAPYBOX 4
+#define DLGWIDTH 168
+#define STATICHEIGHT 8
+#define TITLEHEIGHT 12
+#define CHECKBOXHEIGHT 8
+#define RADIOHEIGHT 8
+#define EDITHEIGHT 12
+#define LISTHEIGHT 11
+#define LISTINCREMENT 8
+#define COMBOHEIGHT 12
+#define PUSHBTNHEIGHT 14
+#define PROGBARHEIGHT 14
+
+void ctlposinit(struct ctlpos *cp, HWND hwnd,
+ int leftborder, int rightborder, int topborder)
+{
+ RECT r, r2;
+ cp->hwnd = hwnd;
+ cp->font = SendMessage(hwnd, WM_GETFONT, 0, 0);
+ cp->ypos = topborder;
+ GetClientRect(hwnd, &r);
+ r2.left = r2.top = 0;
+ r2.right = 4;
+ r2.bottom = 8;
+ MapDialogRect(hwnd, &r2);
+ cp->dlu4inpix = r2.right;
+ cp->width = (r.right * 4) / (r2.right) - 2 * GAPBETWEEN;
+ cp->xoff = leftborder;
+ cp->width -= leftborder + rightborder;
+}
+
+HWND doctl(struct ctlpos *cp, RECT r,
+ char *wclass, int wstyle, int exstyle, char *wtext, int wid)
+{
+ HWND ctl;
+ /*
+ * Note nonstandard use of RECT. This is deliberate: by
+ * transforming the width and height directly we arrange to
+ * have all supposedly same-sized controls really same-sized.
+ */
+
+ r.left += cp->xoff;
+ MapDialogRect(cp->hwnd, &r);
+
+ /*
+ * We can pass in cp->hwnd == NULL, to indicate a dry run
+ * without creating any actual controls.
+ */
+ if (cp->hwnd) {
+ ctl = CreateWindowEx(exstyle, wclass, wtext, wstyle,
+ r.left, r.top, r.right, r.bottom,
+ cp->hwnd, (HMENU) wid, hinst, NULL);
+ SendMessage(ctl, WM_SETFONT, cp->font, MAKELPARAM(TRUE, 0));
+
+ if (!strcmp(wclass, "LISTBOX")) {
+ /*
+ * Bizarre Windows bug: the list box calculates its
+ * number of lines based on the font it has at creation
+ * time, but sending it WM_SETFONT doesn't cause it to
+ * recalculate. So now, _after_ we've sent it
+ * WM_SETFONT, we explicitly resize it (to the same
+ * size it was already!) to force it to reconsider.
+ */
+ SetWindowPos(ctl, NULL, 0, 0, r.right, r.bottom,
+ SWP_NOACTIVATE | SWP_NOCOPYBITS |
+ SWP_NOMOVE | SWP_NOZORDER);
+ }
+ } else
+ ctl = NULL;
+ return ctl;
+}
+
+/*
+ * A title bar across the top of a sub-dialog.
+ */
+void bartitle(struct ctlpos *cp, char *name, int id)
+{
+ RECT r;
+
+ r.left = GAPBETWEEN;
+ r.right = cp->width;
+ r.top = cp->ypos;
+ r.bottom = STATICHEIGHT;
+ cp->ypos += r.bottom + GAPBETWEEN;
+ doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, name, id);
+}
+
+/*
+ * Begin a grouping box, with or without a group title.
+ */
+void beginbox(struct ctlpos *cp, char *name, int idbox)
+{
+ cp->boxystart = cp->ypos;
+ if (!name)
+ cp->boxystart -= STATICHEIGHT / 2;
+ if (name)
+ cp->ypos += STATICHEIGHT;
+ cp->ypos += GAPYBOX;
+ cp->width -= 2 * GAPXBOX;
+ cp->xoff += GAPXBOX;
+ cp->boxid = idbox;
+ cp->boxtext = name;
+}
+
+/*
+ * End a grouping box.
+ */
+void endbox(struct ctlpos *cp)
+{
+ RECT r;
+ cp->xoff -= GAPXBOX;
+ cp->width += 2 * GAPXBOX;
+ cp->ypos += GAPYBOX - GAPBETWEEN;
+ r.left = GAPBETWEEN;
+ r.right = cp->width;
+ r.top = cp->boxystart;
+ r.bottom = cp->ypos - cp->boxystart;
+ doctl(cp, r, "BUTTON", BS_GROUPBOX | WS_CHILD | WS_VISIBLE, 0,
+ cp->boxtext ? cp->boxtext : "", cp->boxid);
+ cp->ypos += GAPYBOX;
+}
+
+/*
+ * A static line, followed by a full-width edit box.
+ */
+void editboxfw(struct ctlpos *cp, int password, char *text,
+ int staticid, int editid)
+{
+ RECT r;
+
+ r.left = GAPBETWEEN;
+ r.right = cp->width;
+
+ if (text) {
+ r.top = cp->ypos;
+ r.bottom = STATICHEIGHT;
+ doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, text, staticid);
+ cp->ypos += STATICHEIGHT + GAPWITHIN;
+ }
+ r.top = cp->ypos;
+ r.bottom = EDITHEIGHT;
+ doctl(cp, r, "EDIT",
+ WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL |
+ (password ? ES_PASSWORD : 0),
+ WS_EX_CLIENTEDGE, "", editid);
+ cp->ypos += EDITHEIGHT + GAPBETWEEN;
+}
+
+/*
+ * A static line, followed by a full-width combo box.
+ */
+void combobox(struct ctlpos *cp, char *text, int staticid, int listid)
+{
+ RECT r;
+
+ r.left = GAPBETWEEN;
+ r.right = cp->width;
+
+ if (text) {
+ r.top = cp->ypos;
+ r.bottom = STATICHEIGHT;
+ doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, text, staticid);
+ cp->ypos += STATICHEIGHT + GAPWITHIN;
+ }
+ r.top = cp->ypos;
+ r.bottom = COMBOHEIGHT * 10;
+ doctl(cp, r, "COMBOBOX",
+ WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_VSCROLL |
+ CBS_DROPDOWN | CBS_HASSTRINGS, WS_EX_CLIENTEDGE, "", listid);
+ cp->ypos += COMBOHEIGHT + GAPBETWEEN;
+}
+
+struct radio { char *text; int id; };
+
+static void radioline_common(struct ctlpos *cp, char *text, int id,
+ int nacross, struct radio *buttons, int nbuttons)
+{
+ RECT r;
+ int group;
+ int i;
+ int j;
+
+ if (text) {
+ r.left = GAPBETWEEN;
+ r.top = cp->ypos;
+ r.right = cp->width;
+ r.bottom = STATICHEIGHT;
+ cp->ypos += r.bottom + GAPWITHIN;
+ doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, text, id);
+ }
+
+ group = WS_GROUP;
+ i = 0;
+ for (j = 0; j < nbuttons; j++) {
+ char *btext = buttons[j].text;
+ int bid = buttons[j].id;
+
+ if (i == nacross) {
+ cp->ypos += r.bottom + (nacross > 1 ? GAPBETWEEN : GAPWITHIN);
+ i = 0;
+ }
+ r.left = GAPBETWEEN + i * (cp->width + GAPBETWEEN) / nacross;
+ if (j < nbuttons-1)
+ r.right =
+ (i + 1) * (cp->width + GAPBETWEEN) / nacross - r.left;
+ else
+ r.right = cp->width - r.left;
+ r.top = cp->ypos;
+ r.bottom = RADIOHEIGHT;
+ doctl(cp, r, "BUTTON",
+ BS_NOTIFY | BS_AUTORADIOBUTTON | WS_CHILD |
+ WS_VISIBLE | WS_TABSTOP | group, 0, btext, bid);
+ group = 0;
+ i++;
+ }
+ cp->ypos += r.bottom + GAPBETWEEN;
+}
+
+/*
+ * A set of radio buttons on the same line, with a static above
+ * them. `nacross' dictates how many parts the line is divided into
+ * (you might want this not to equal the number of buttons if you
+ * needed to line up some 2s and some 3s to look good in the same
+ * panel).
+ *
+ * There's a bit of a hack in here to ensure that if nacross
+ * exceeds the actual number of buttons, the rightmost button
+ * really does get all the space right to the edge of the line, so
+ * you can do things like
+ *
+ * (*) Button1 (*) Button2 (*) ButtonWithReallyLongTitle
+ */
+void radioline(struct ctlpos *cp, char *text, int id, int nacross, ...)
+{
+ va_list ap;
+ struct radio *buttons;
+ int i, nbuttons;
+
+ va_start(ap, nacross);
+ nbuttons = 0;
+ while (1) {
+ char *btext = va_arg(ap, char *);
+ int bid;
+ if (!btext)
+ break;
+ bid = va_arg(ap, int);
+ nbuttons++;
+ }
+ va_end(ap);
+ buttons = snewn(nbuttons, struct radio);
+ va_start(ap, nacross);
+ for (i = 0; i < nbuttons; i++) {
+ buttons[i].text = va_arg(ap, char *);
+ buttons[i].id = va_arg(ap, int);
+ }
+ va_end(ap);
+ radioline_common(cp, text, id, nacross, buttons, nbuttons);
+ sfree(buttons);
+}
+
+/*
+ * A set of radio buttons on the same line, without a static above
+ * them. Otherwise just like radioline.
+ */
+void bareradioline(struct ctlpos *cp, int nacross, ...)
+{
+ va_list ap;
+ struct radio *buttons;
+ int i, nbuttons;
+
+ va_start(ap, nacross);
+ nbuttons = 0;
+ while (1) {
+ char *btext = va_arg(ap, char *);
+ int bid;
+ if (!btext)
+ break;
+ bid = va_arg(ap, int);
+ }
+ va_end(ap);
+ buttons = snewn(nbuttons, struct radio);
+ va_start(ap, nacross);
+ for (i = 0; i < nbuttons; i++) {
+ buttons[i].text = va_arg(ap, char *);
+ buttons[i].id = va_arg(ap, int);
+ }
+ va_end(ap);
+ radioline_common(cp, NULL, 0, nacross, buttons, nbuttons);
+ sfree(buttons);
+}
+
+/*
+ * A set of radio buttons on multiple lines, with a static above
+ * them.
+ */
+void radiobig(struct ctlpos *cp, char *text, int id, ...)
+{
+ va_list ap;
+ struct radio *buttons;
+ int i, nbuttons;
+
+ va_start(ap, id);
+ nbuttons = 0;
+ while (1) {
+ char *btext = va_arg(ap, char *);
+ int bid;
+ if (!btext)
+ break;
+ bid = va_arg(ap, int);
+ }
+ va_end(ap);
+ buttons = snewn(nbuttons, struct radio);
+ va_start(ap, id);
+ for (i = 0; i < nbuttons; i++) {
+ buttons[i].text = va_arg(ap, char *);
+ buttons[i].id = va_arg(ap, int);
+ }
+ va_end(ap);
+ radioline_common(cp, text, id, 1, buttons, nbuttons);
+ sfree(buttons);
+}
+
+/*
+ * A single standalone checkbox.
+ */
+void checkbox(struct ctlpos *cp, char *text, int id)
+{
+ RECT r;
+
+ r.left = GAPBETWEEN;
+ r.top = cp->ypos;
+ r.right = cp->width;
+ r.bottom = CHECKBOXHEIGHT;
+ cp->ypos += r.bottom + GAPBETWEEN;
+ doctl(cp, r, "BUTTON",
+ BS_NOTIFY | BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 0,
+ text, id);
+}
+
+/*
+ * Wrap a piece of text for a static text control. Returns the
+ * wrapped text (a malloc'ed string containing \ns), and also
+ * returns the number of lines required.
+ */
+char *staticwrap(struct ctlpos *cp, HWND hwnd, char *text, int *lines)
+{
+ HDC hdc = GetDC(hwnd);
+ int lpx = GetDeviceCaps(hdc, LOGPIXELSX);
+ int width, nlines, j;
+ INT *pwidths, nfit;
+ SIZE size;
+ char *ret, *p, *q;
+ RECT r;
+ HFONT oldfont, newfont;
+
+ ret = snewn(1+strlen(text), char);
+ p = text;
+ q = ret;
+ pwidths = snewn(1+strlen(text), INT);
+
+ /*
+ * Work out the width the text will need to fit in, by doing
+ * the same adjustment that the `statictext' function itself
+ * will perform.
+ */
+ SetMapMode(hdc, MM_TEXT); /* ensure logical units == pixels */
+ r.left = r.top = r.bottom = 0;
+ r.right = cp->width;
+ MapDialogRect(hwnd, &r);
+ width = r.right;
+
+ nlines = 1;
+
+ /*
+ * We must select the correct font into the HDC before calling
+ * GetTextExtent*, or silly things will happen.
+ */
+ newfont = (HFONT)SendMessage(hwnd, WM_GETFONT, 0, 0);
+ oldfont = SelectObject(hdc, newfont);
+
+ while (*p) {
+ if (!GetTextExtentExPoint(hdc, p, strlen(p), width,
+ &nfit, pwidths, &size) ||
+ (size_t)nfit >= strlen(p)) {
+ /*
+ * Either GetTextExtentExPoint returned failure, or the
+ * whole of the rest of the text fits on this line.
+ * Either way, we stop wrapping, copy the remainder of
+ * the input string unchanged to the output, and leave.
+ */
+ strcpy(q, p);
+ break;
+ }
+
+ /*
+ * Now we search backwards along the string from `nfit',
+ * looking for a space at which to break the line. If we
+ * don't find one at all, that's fine - we'll just break
+ * the line at `nfit'.
+ */
+ for (j = nfit; j > 0; j--) {
+ if (isspace((unsigned char)p[j])) {
+ nfit = j;
+ break;
+ }
+ }
+
+ strncpy(q, p, nfit);
+ q[nfit] = '\n';
+ q += nfit+1;
+
+ p += nfit;
+ while (*p && isspace((unsigned char)*p))
+ p++;
+
+ nlines++;
+ }
+
+ SelectObject(hdc, oldfont);
+ ReleaseDC(cp->hwnd, hdc);
+
+ if (lines) *lines = nlines;
+
+ return ret;
+}
+
+/*
+ * A single standalone static text control.
+ */
+void statictext(struct ctlpos *cp, char *text, int lines, int id)
+{
+ RECT r;
+
+ r.left = GAPBETWEEN;
+ r.top = cp->ypos;
+ r.right = cp->width;
+ r.bottom = STATICHEIGHT * lines;
+ cp->ypos += r.bottom + GAPBETWEEN;
+ doctl(cp, r, "STATIC",
+ WS_CHILD | WS_VISIBLE | SS_LEFTNOWORDWRAP,
+ 0, text, id);
+}
+
+/*
+ * An owner-drawn static text control for a panel title.
+ */
+void paneltitle(struct ctlpos *cp, int id)
+{
+ RECT r;
+
+ r.left = GAPBETWEEN;
+ r.top = cp->ypos;
+ r.right = cp->width;
+ r.bottom = TITLEHEIGHT;
+ cp->ypos += r.bottom + GAPBETWEEN;
+ doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE | SS_OWNERDRAW,
+ 0, NULL, id);
+}
+
+/*
+ * A button on the right hand side, with a static to its left.
+ */
+void staticbtn(struct ctlpos *cp, char *stext, int sid,
+ char *btext, int bid)
+{
+ const int height = (PUSHBTNHEIGHT > STATICHEIGHT ?
+ PUSHBTNHEIGHT : STATICHEIGHT);
+ RECT r;
+ int lwid, rwid, rpos;
+
+ rpos = GAPBETWEEN + 3 * (cp->width + GAPBETWEEN) / 4;
+ lwid = rpos - 2 * GAPBETWEEN;
+ rwid = cp->width + GAPBETWEEN - rpos;
+
+ r.left = GAPBETWEEN;
+ r.top = cp->ypos + (height - STATICHEIGHT) / 2;
+ r.right = lwid;
+ r.bottom = STATICHEIGHT;
+ doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, stext, sid);
+
+ r.left = rpos;
+ r.top = cp->ypos + (height - PUSHBTNHEIGHT) / 2;
+ r.right = rwid;
+ r.bottom = PUSHBTNHEIGHT;
+ doctl(cp, r, "BUTTON",
+ BS_NOTIFY | WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON,
+ 0, btext, bid);
+
+ cp->ypos += height + GAPBETWEEN;
+}
+
+/*
+ * A simple push button.
+ */
+void button(struct ctlpos *cp, char *btext, int bid, int defbtn)
+{
+ RECT r;
+
+ r.left = GAPBETWEEN;
+ r.top = cp->ypos;
+ r.right = cp->width;
+ r.bottom = PUSHBTNHEIGHT;
+
+ /* Q67655: the _dialog box_ must know which button is default
+ * as well as the button itself knowing */
+ if (defbtn && cp->hwnd)
+ SendMessage(cp->hwnd, DM_SETDEFID, bid, 0);
+
+ doctl(cp, r, "BUTTON",
+ BS_NOTIFY | WS_CHILD | WS_VISIBLE | WS_TABSTOP |
+ (defbtn ? BS_DEFPUSHBUTTON : 0) | BS_PUSHBUTTON,
+ 0, btext, bid);
+
+ cp->ypos += PUSHBTNHEIGHT + GAPBETWEEN;
+}
+
+/*
+ * Like staticbtn, but two buttons.
+ */
+void static2btn(struct ctlpos *cp, char *stext, int sid,
+ char *btext1, int bid1, char *btext2, int bid2)
+{
+ const int height = (PUSHBTNHEIGHT > STATICHEIGHT ?
+ PUSHBTNHEIGHT : STATICHEIGHT);
+ RECT r;
+ int lwid, rwid1, rwid2, rpos1, rpos2;
+
+ rpos1 = GAPBETWEEN + (cp->width + GAPBETWEEN) / 2;
+ rpos2 = GAPBETWEEN + 3 * (cp->width + GAPBETWEEN) / 4;
+ lwid = rpos1 - 2 * GAPBETWEEN;
+ rwid1 = rpos2 - rpos1 - GAPBETWEEN;
+ rwid2 = cp->width + GAPBETWEEN - rpos2;
+
+ r.left = GAPBETWEEN;
+ r.top = cp->ypos + (height - STATICHEIGHT) / 2;
+ r.right = lwid;
+ r.bottom = STATICHEIGHT;
+ doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, stext, sid);
+
+ r.left = rpos1;
+ r.top = cp->ypos + (height - PUSHBTNHEIGHT) / 2;
+ r.right = rwid1;
+ r.bottom = PUSHBTNHEIGHT;
+ doctl(cp, r, "BUTTON",
+ BS_NOTIFY | WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON,
+ 0, btext1, bid1);
+
+ r.left = rpos2;
+ r.top = cp->ypos + (height - PUSHBTNHEIGHT) / 2;
+ r.right = rwid2;
+ r.bottom = PUSHBTNHEIGHT;
+ doctl(cp, r, "BUTTON",
+ BS_NOTIFY | WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON,
+ 0, btext2, bid2);
+
+ cp->ypos += height + GAPBETWEEN;
+}
+
+/*
+ * An edit control on the right hand side, with a static to its left.
+ */
+static void staticedit_internal(struct ctlpos *cp, char *stext,
+ int sid, int eid, int percentedit,
+ int style)
+{
+ const int height = (EDITHEIGHT > STATICHEIGHT ?
+ EDITHEIGHT : STATICHEIGHT);
+ RECT r;
+ int lwid, rwid, rpos;
+
+ rpos =
+ GAPBETWEEN + (100 - percentedit) * (cp->width + GAPBETWEEN) / 100;
+ lwid = rpos - 2 * GAPBETWEEN;
+ rwid = cp->width + GAPBETWEEN - rpos;
+
+ r.left = GAPBETWEEN;
+ r.top = cp->ypos + (height - STATICHEIGHT) / 2;
+ r.right = lwid;
+ r.bottom = STATICHEIGHT;
+ doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, stext, sid);
+
+ r.left = rpos;
+ r.top = cp->ypos + (height - EDITHEIGHT) / 2;
+ r.right = rwid;
+ r.bottom = EDITHEIGHT;
+ doctl(cp, r, "EDIT",
+ WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL | style,
+ WS_EX_CLIENTEDGE, "", eid);
+
+ cp->ypos += height + GAPBETWEEN;
+}
+
+void staticedit(struct ctlpos *cp, char *stext,
+ int sid, int eid, int percentedit)
+{
+ staticedit_internal(cp, stext, sid, eid, percentedit, 0);
+}
+
+void staticpassedit(struct ctlpos *cp, char *stext,
+ int sid, int eid, int percentedit)
+{
+ staticedit_internal(cp, stext, sid, eid, percentedit, ES_PASSWORD);
+}
+
+/*
+ * A drop-down list box on the right hand side, with a static to
+ * its left.
+ */
+void staticddl(struct ctlpos *cp, char *stext,
+ int sid, int lid, int percentlist)
+{
+ const int height = (COMBOHEIGHT > STATICHEIGHT ?
+ COMBOHEIGHT : STATICHEIGHT);
+ RECT r;
+ int lwid, rwid, rpos;
+
+ rpos =
+ GAPBETWEEN + (100 - percentlist) * (cp->width + GAPBETWEEN) / 100;
+ lwid = rpos - 2 * GAPBETWEEN;
+ rwid = cp->width + GAPBETWEEN - rpos;
+
+ r.left = GAPBETWEEN;
+ r.top = cp->ypos + (height - STATICHEIGHT) / 2;
+ r.right = lwid;
+ r.bottom = STATICHEIGHT;
+ doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, stext, sid);
+
+ r.left = rpos;
+ r.top = cp->ypos + (height - EDITHEIGHT) / 2;
+ r.right = rwid;
+ r.bottom = COMBOHEIGHT*4;
+ doctl(cp, r, "COMBOBOX",
+ WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_VSCROLL |
+ CBS_DROPDOWNLIST | CBS_HASSTRINGS, WS_EX_CLIENTEDGE, "", lid);
+
+ cp->ypos += height + GAPBETWEEN;
+}
+
+/*
+ * A combo box on the right hand side, with a static to its left.
+ */
+void staticcombo(struct ctlpos *cp, char *stext,
+ int sid, int lid, int percentlist)
+{
+ const int height = (COMBOHEIGHT > STATICHEIGHT ?
+ COMBOHEIGHT : STATICHEIGHT);
+ RECT r;
+ int lwid, rwid, rpos;
+
+ rpos =
+ GAPBETWEEN + (100 - percentlist) * (cp->width + GAPBETWEEN) / 100;
+ lwid = rpos - 2 * GAPBETWEEN;
+ rwid = cp->width + GAPBETWEEN - rpos;
+
+ r.left = GAPBETWEEN;
+ r.top = cp->ypos + (height - STATICHEIGHT) / 2;
+ r.right = lwid;
+ r.bottom = STATICHEIGHT;
+ doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, stext, sid);
+
+ r.left = rpos;
+ r.top = cp->ypos + (height - EDITHEIGHT) / 2;
+ r.right = rwid;
+ r.bottom = COMBOHEIGHT*10;
+ doctl(cp, r, "COMBOBOX",
+ WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_VSCROLL |
+ CBS_DROPDOWN | CBS_HASSTRINGS, WS_EX_CLIENTEDGE, "", lid);
+
+ cp->ypos += height + GAPBETWEEN;
+}
+
+/*
+ * A static, with a full-width drop-down list box below it.
+ */
+void staticddlbig(struct ctlpos *cp, char *stext,
+ int sid, int lid)
+{
+ RECT r;
+
+ if (stext) {
+ r.left = GAPBETWEEN;
+ r.top = cp->ypos;
+ r.right = cp->width;
+ r.bottom = STATICHEIGHT;
+ doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, stext, sid);
+ cp->ypos += STATICHEIGHT;
+ }
+
+ r.left = GAPBETWEEN;
+ r.top = cp->ypos;
+ r.right = cp->width;
+ r.bottom = COMBOHEIGHT*4;
+ doctl(cp, r, "COMBOBOX",
+ WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_VSCROLL |
+ CBS_DROPDOWNLIST | CBS_HASSTRINGS, WS_EX_CLIENTEDGE, "", lid);
+ cp->ypos += COMBOHEIGHT + GAPBETWEEN;
+}
+
+/*
+ * A big multiline edit control with a static labelling it.
+ */
+void bigeditctrl(struct ctlpos *cp, char *stext,
+ int sid, int eid, int lines)
+{
+ RECT r;
+
+ if (stext) {
+ r.left = GAPBETWEEN;
+ r.top = cp->ypos;
+ r.right = cp->width;
+ r.bottom = STATICHEIGHT;
+ cp->ypos += r.bottom + GAPWITHIN;
+ doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, stext, sid);
+ }
+
+ r.left = GAPBETWEEN;
+ r.top = cp->ypos;
+ r.right = cp->width;
+ r.bottom = EDITHEIGHT + (lines - 1) * STATICHEIGHT;
+ cp->ypos += r.bottom + GAPBETWEEN;
+ doctl(cp, r, "EDIT",
+ WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_VSCROLL | ES_MULTILINE,
+ WS_EX_CLIENTEDGE, "", eid);
+}
+
+/*
+ * A list box with a static labelling it.
+ */
+void listbox(struct ctlpos *cp, char *stext,
+ int sid, int lid, int lines, int multi)
+{
+ RECT r;
+
+ if (stext != NULL) {
+ r.left = GAPBETWEEN;
+ r.top = cp->ypos;
+ r.right = cp->width;
+ r.bottom = STATICHEIGHT;
+ cp->ypos += r.bottom + GAPWITHIN;
+ doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, stext, sid);
+ }
+
+ r.left = GAPBETWEEN;
+ r.top = cp->ypos;
+ r.right = cp->width;
+ r.bottom = LISTHEIGHT + (lines - 1) * LISTINCREMENT;
+ cp->ypos += r.bottom + GAPBETWEEN;
+ doctl(cp, r, "LISTBOX",
+ WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_VSCROLL |
+ LBS_NOTIFY | LBS_HASSTRINGS | LBS_USETABSTOPS |
+ (multi ? LBS_MULTIPLESEL : 0),
+ WS_EX_CLIENTEDGE, "", lid);
+}
+
+/*
+ * A tab-control substitute when a real tab control is unavailable.
+ */
+void ersatztab(struct ctlpos *cp, char *stext, int sid, int lid, int s2id)
+{
+ const int height = (COMBOHEIGHT > STATICHEIGHT ?
+ COMBOHEIGHT : STATICHEIGHT);
+ RECT r;
+ int bigwid, lwid, rwid, rpos;
+ static const int BIGGAP = 15;
+ static const int MEDGAP = 3;
+
+ bigwid = cp->width + 2 * GAPBETWEEN - 2 * BIGGAP;
+ cp->ypos += MEDGAP;
+ rpos = BIGGAP + (bigwid + BIGGAP) / 2;
+ lwid = rpos - 2 * BIGGAP;
+ rwid = bigwid + BIGGAP - rpos;
+
+ r.left = BIGGAP;
+ r.top = cp->ypos + (height - STATICHEIGHT) / 2;
+ r.right = lwid;
+ r.bottom = STATICHEIGHT;
+ doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, stext, sid);
+
+ r.left = rpos;
+ r.top = cp->ypos + (height - COMBOHEIGHT) / 2;
+ r.right = rwid;
+ r.bottom = COMBOHEIGHT * 10;
+ doctl(cp, r, "COMBOBOX",
+ WS_CHILD | WS_VISIBLE | WS_TABSTOP |
+ CBS_DROPDOWNLIST | CBS_HASSTRINGS, WS_EX_CLIENTEDGE, "", lid);
+
+ cp->ypos += height + MEDGAP + GAPBETWEEN;
+
+ r.left = GAPBETWEEN;
+ r.top = cp->ypos;
+ r.right = cp->width;
+ r.bottom = 2;
+ doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE | SS_ETCHEDHORZ,
+ 0, "", s2id);
+}
+
+/*
+ * A static line, followed by an edit control on the left hand side
+ * and a button on the right.
+ */
+void editbutton(struct ctlpos *cp, char *stext, int sid,
+ int eid, char *btext, int bid)
+{
+ const int height = (EDITHEIGHT > PUSHBTNHEIGHT ?
+ EDITHEIGHT : PUSHBTNHEIGHT);
+ RECT r;
+ int lwid, rwid, rpos;
+
+ r.left = GAPBETWEEN;
+ r.top = cp->ypos;
+ r.right = cp->width;
+ r.bottom = STATICHEIGHT;
+ cp->ypos += r.bottom + GAPWITHIN;
+ doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, stext, sid);
+
+ rpos = GAPBETWEEN + 3 * (cp->width + GAPBETWEEN) / 4;
+ lwid = rpos - 2 * GAPBETWEEN;
+ rwid = cp->width + GAPBETWEEN - rpos;
+
+ r.left = GAPBETWEEN;
+ r.top = cp->ypos + (height - EDITHEIGHT) / 2;
+ r.right = lwid;
+ r.bottom = EDITHEIGHT;
+ doctl(cp, r, "EDIT",
+ WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL,
+ WS_EX_CLIENTEDGE, "", eid);
+
+ r.left = rpos;
+ r.top = cp->ypos + (height - PUSHBTNHEIGHT) / 2;
+ r.right = rwid;
+ r.bottom = PUSHBTNHEIGHT;
+ doctl(cp, r, "BUTTON",
+ BS_NOTIFY | WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON,
+ 0, btext, bid);
+
+ cp->ypos += height + GAPBETWEEN;
+}
+
+/*
+ * A special control for manipulating an ordered preference list
+ * (eg. for cipher selection).
+ * XXX: this is a rough hack and could be improved.
+ */
+void prefslist(struct prefslist *hdl, struct ctlpos *cp, int lines,
+ char *stext, int sid, int listid, int upbid, int dnbid)
+{
+ const static int percents[] = { 5, 75, 20 };
+ RECT r;
+ int xpos, percent = 0, i;
+ int listheight = LISTHEIGHT + (lines - 1) * LISTINCREMENT;
+ const int BTNSHEIGHT = 2*PUSHBTNHEIGHT + GAPBETWEEN;
+ int totalheight, buttonpos;
+
+ /* Squirrel away IDs. */
+ hdl->listid = listid;
+ hdl->upbid = upbid;
+ hdl->dnbid = dnbid;
+
+ /* The static label. */
+ if (stext != NULL) {
+ r.left = GAPBETWEEN;
+ r.top = cp->ypos;
+ r.right = cp->width;
+ r.bottom = STATICHEIGHT;
+ cp->ypos += r.bottom + GAPWITHIN;
+ doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, stext, sid);
+ }
+
+ if (listheight > BTNSHEIGHT) {
+ totalheight = listheight;
+ buttonpos = (listheight - BTNSHEIGHT) / 2;
+ } else {
+ totalheight = BTNSHEIGHT;
+ buttonpos = 0;
+ }
+
+ for (i=0; i<3; i++) {
+ int left, wid;
+ xpos = (cp->width + GAPBETWEEN) * percent / 100;
+ left = xpos + GAPBETWEEN;
+ percent += percents[i];
+ xpos = (cp->width + GAPBETWEEN) * percent / 100;
+ wid = xpos - left;
+
+ switch (i) {
+ case 1:
+ /* The drag list box. */
+ r.left = left; r.right = wid;
+ r.top = cp->ypos; r.bottom = listheight;
+ {
+ HWND ctl;
+ ctl = doctl(cp, r, "LISTBOX",
+ WS_CHILD | WS_VISIBLE | WS_TABSTOP |
+ WS_VSCROLL | LBS_HASSTRINGS | LBS_USETABSTOPS,
+ WS_EX_CLIENTEDGE,
+ "", listid);
+ MakeDragList(ctl);
+ }
+ break;
+
+ case 2:
+ /* The "Up" and "Down" buttons. */
+ /* XXX worry about accelerators if we have more than one
+ * prefslist on a panel */
+ r.left = left; r.right = wid;
+ r.top = cp->ypos + buttonpos; r.bottom = PUSHBTNHEIGHT;
+ doctl(cp, r, "BUTTON",
+ BS_NOTIFY | WS_CHILD | WS_VISIBLE |
+ WS_TABSTOP | BS_PUSHBUTTON,
+ 0, "&Up", upbid);
+
+ r.left = left; r.right = wid;
+ r.top = cp->ypos + buttonpos + PUSHBTNHEIGHT + GAPBETWEEN;
+ r.bottom = PUSHBTNHEIGHT;
+ doctl(cp, r, "BUTTON",
+ BS_NOTIFY | WS_CHILD | WS_VISIBLE |
+ WS_TABSTOP | BS_PUSHBUTTON,
+ 0, "&Down", dnbid);
+
+ break;
+
+ }
+ }
+
+ cp->ypos += totalheight + GAPBETWEEN;
+
+}
+
+/*
+ * Helper function for prefslist: move item in list box.
+ */
+static void pl_moveitem(HWND hwnd, int listid, int src, int dst)
+{
+ int tlen, val;
+ char *txt;
+ /* Get the item's data. */
+ tlen = SendDlgItemMessage (hwnd, listid, LB_GETTEXTLEN, src, 0);
+ txt = snewn(tlen+1, char);
+ SendDlgItemMessage (hwnd, listid, LB_GETTEXT, src, (LPARAM) txt);
+ val = SendDlgItemMessage (hwnd, listid, LB_GETITEMDATA, src, 0);
+ /* Deselect old location. */
+ SendDlgItemMessage (hwnd, listid, LB_SETSEL, FALSE, src);
+ /* Delete it at the old location. */
+ SendDlgItemMessage (hwnd, listid, LB_DELETESTRING, src, 0);
+ /* Insert it at new location. */
+ SendDlgItemMessage (hwnd, listid, LB_INSERTSTRING, dst,
+ (LPARAM) txt);
+ SendDlgItemMessage (hwnd, listid, LB_SETITEMDATA, dst,
+ (LPARAM) val);
+ /* Set selection. */
+ SendDlgItemMessage (hwnd, listid, LB_SETCURSEL, dst, 0);
+ sfree (txt);
+}
+
+int pl_itemfrompt(HWND hwnd, POINT cursor, BOOL scroll)
+{
+ int ret;
+ POINT uppoint, downpoint;
+ int updist, downdist, upitem, downitem, i;
+
+ /*
+ * Ghastly hackery to try to figure out not which
+ * _item_, but which _gap between items_, the user
+ * is pointing at. We do this by first working out
+ * which list item is under the cursor, and then
+ * working out how far the cursor would have to
+ * move up or down before the answer was different.
+ * Then we put the insertion point _above_ the
+ * current item if the upper edge is closer than
+ * the lower edge, or _below_ it if vice versa.
+ */
+ ret = LBItemFromPt(hwnd, cursor, scroll);
+ if (ret == -1)
+ return ret;
+ ret = LBItemFromPt(hwnd, cursor, FALSE);
+ updist = downdist = 0;
+ for (i = 1; i < 4096 && (!updist || !downdist); i++) {
+ uppoint = downpoint = cursor;
+ uppoint.y -= i;
+ downpoint.y += i;
+ upitem = LBItemFromPt(hwnd, uppoint, FALSE);
+ downitem = LBItemFromPt(hwnd, downpoint, FALSE);
+ if (!updist && upitem != ret)
+ updist = i;
+ if (!downdist && downitem != ret)
+ downdist = i;
+ }
+ if (downdist < updist)
+ ret++;
+ return ret;
+}
+
+/*
+ * Handler for prefslist above.
+ *
+ * Return value has bit 0 set if the dialog box procedure needs to
+ * return TRUE from handling this message; it has bit 1 set if a
+ * change may have been made in the contents of the list.
+ */
+int handle_prefslist(struct prefslist *hdl,
+ int *array, int maxmemb,
+ int is_dlmsg, HWND hwnd,
+ WPARAM wParam, LPARAM lParam)
+{
+ int i;
+ int ret = 0;
+
+ if (is_dlmsg) {
+
+ if ((int)wParam == hdl->listid) {
+ DRAGLISTINFO *dlm = (DRAGLISTINFO *)lParam;
+ int dest = 0; /* initialise to placate gcc */
+ switch (dlm->uNotification) {
+ case DL_BEGINDRAG:
+ /* Add a dummy item to make pl_itemfrompt() work
+ * better.
+ * FIXME: this causes scrollbar glitches if the count of
+ * listbox contains >= its height. */
+ hdl->dummyitem =
+ SendDlgItemMessage(hwnd, hdl->listid,
+ LB_ADDSTRING, 0, (LPARAM) "");
+
+ hdl->srcitem = LBItemFromPt(dlm->hWnd, dlm->ptCursor, TRUE);
+ hdl->dragging = 0;
+ /* XXX hack Q183115 */
+ SetWindowLongPtr(hwnd, DWLP_MSGRESULT, TRUE);
+ ret |= 1; break;
+ case DL_CANCELDRAG:
+ DrawInsert(hwnd, dlm->hWnd, -1); /* Clear arrow */
+ SendDlgItemMessage(hwnd, hdl->listid,
+ LB_DELETESTRING, hdl->dummyitem, 0);
+ hdl->dragging = 0;
+ ret |= 1; break;
+ case DL_DRAGGING:
+ hdl->dragging = 1;
+ dest = pl_itemfrompt(dlm->hWnd, dlm->ptCursor, TRUE);
+ if (dest > hdl->dummyitem) dest = hdl->dummyitem;
+ DrawInsert (hwnd, dlm->hWnd, dest);
+ if (dest >= 0)
+ SetWindowLongPtr(hwnd, DWLP_MSGRESULT, DL_MOVECURSOR);
+ else
+ SetWindowLongPtr(hwnd, DWLP_MSGRESULT, DL_STOPCURSOR);
+ ret |= 1; break;
+ case DL_DROPPED:
+ if (hdl->dragging) {
+ dest = pl_itemfrompt(dlm->hWnd, dlm->ptCursor, TRUE);
+ if (dest > hdl->dummyitem) dest = hdl->dummyitem;
+ DrawInsert (hwnd, dlm->hWnd, -1);
+ }
+ SendDlgItemMessage(hwnd, hdl->listid,
+ LB_DELETESTRING, hdl->dummyitem, 0);
+ if (hdl->dragging) {
+ hdl->dragging = 0;
+ if (dest >= 0) {
+ /* Correct for "missing" item. */
+ if (dest > hdl->srcitem) dest--;
+ pl_moveitem(hwnd, hdl->listid, hdl->srcitem, dest);
+ }
+ ret |= 2;
+ }
+ ret |= 1; break;
+ }
+ }
+
+ } else {
+
+ if (((LOWORD(wParam) == hdl->upbid) ||
+ (LOWORD(wParam) == hdl->dnbid)) &&
+ ((HIWORD(wParam) == BN_CLICKED) ||
+ (HIWORD(wParam) == BN_DOUBLECLICKED))) {
+ /* Move an item up or down the list. */
+ /* Get the current selection, if any. */
+ int selection = SendDlgItemMessage (hwnd, hdl->listid, LB_GETCURSEL, 0, 0);
+ if (selection == LB_ERR) {
+ MessageBeep(0);
+ } else {
+ int nitems;
+ /* Get the total number of items. */
+ nitems = SendDlgItemMessage (hwnd, hdl->listid, LB_GETCOUNT, 0, 0);
+ /* Should we do anything? */
+ if (LOWORD(wParam) == hdl->upbid && (selection > 0))
+ pl_moveitem(hwnd, hdl->listid, selection, selection - 1);
+ else if (LOWORD(wParam) == hdl->dnbid && (selection < nitems - 1))
+ pl_moveitem(hwnd, hdl->listid, selection, selection + 1);
+ ret |= 2;
+ }
+
+ }
+
+ }
+
+ if (array) {
+ /* Update array to match the list box. */
+ for (i=0; i < maxmemb; i++)
+ array[i] = SendDlgItemMessage (hwnd, hdl->listid, LB_GETITEMDATA,
+ i, 0);
+ }
+
+ return ret;
+}
+
+/*
+ * A progress bar (from Common Controls). We like our progress bars
+ * to be smooth and unbroken, without those ugly divisions; some
+ * older compilers may not support that, but that's life.
+ */
+void progressbar(struct ctlpos *cp, int id)
+{
+ RECT r;
+
+ r.left = GAPBETWEEN;
+ r.top = cp->ypos;
+ r.right = cp->width;
+ r.bottom = PROGBARHEIGHT;
+ cp->ypos += r.bottom + GAPBETWEEN;
+
+ doctl(cp, r, PROGRESS_CLASS, WS_CHILD | WS_VISIBLE
+#ifdef PBS_SMOOTH
+ | PBS_SMOOTH
+#endif
+ , WS_EX_CLIENTEDGE, "", id);
+}
+
+/* ----------------------------------------------------------------------
+ * Platform-specific side of portable dialog-box mechanism.
+ */
+
+/*
+ * This function takes a string, escapes all the ampersands, and
+ * places a single (unescaped) ampersand in front of the first
+ * occurrence of the given shortcut character (which may be
+ * NO_SHORTCUT).
+ *
+ * Return value is a malloc'ed copy of the processed version of the
+ * string.
+ */
+static char *shortcut_escape(const char *text, char shortcut)
+{
+ char *ret;
+ char const *p;
+ char *q;
+
+ if (!text)
+ return NULL; /* sfree won't choke on this */
+
+ ret = snewn(2*strlen(text)+1, char); /* size potentially doubles! */
+ shortcut = tolower((unsigned char)shortcut);
+
+ p = text;
+ q = ret;
+ while (*p) {
+ if (shortcut != NO_SHORTCUT &&
+ tolower((unsigned char)*p) == shortcut) {
+ *q++ = '&';
+ shortcut = NO_SHORTCUT; /* stop it happening twice */
+ } else if (*p == '&') {
+ *q++ = '&';
+ }
+ *q++ = *p++;
+ }
+ *q = '\0';
+ return ret;
+}
+
+void winctrl_add_shortcuts(struct dlgparam *dp, struct winctrl *c)
+{
+ int i;
+ for (i = 0; i < lenof(c->shortcuts); i++)
+ if (c->shortcuts[i] != NO_SHORTCUT) {
+ unsigned char s = tolower((unsigned char)c->shortcuts[i]);
+ assert(!dp->shortcuts[s]);
+ dp->shortcuts[s] = TRUE;
+ }
+}
+
+void winctrl_rem_shortcuts(struct dlgparam *dp, struct winctrl *c)
+{
+ int i;
+ for (i = 0; i < lenof(c->shortcuts); i++)
+ if (c->shortcuts[i] != NO_SHORTCUT) {
+ unsigned char s = tolower((unsigned char)c->shortcuts[i]);
+ assert(dp->shortcuts[s]);
+ dp->shortcuts[s] = FALSE;
+ }
+}
+
+static int winctrl_cmp_byctrl(void *av, void *bv)
+{
+ struct winctrl *a = (struct winctrl *)av;
+ struct winctrl *b = (struct winctrl *)bv;
+ if (a->ctrl < b->ctrl)
+ return -1;
+ else if (a->ctrl > b->ctrl)
+ return +1;
+ else
+ return 0;
+}
+static int winctrl_cmp_byid(void *av, void *bv)
+{
+ struct winctrl *a = (struct winctrl *)av;
+ struct winctrl *b = (struct winctrl *)bv;
+ if (a->base_id < b->base_id)
+ return -1;
+ else if (a->base_id > b->base_id)
+ return +1;
+ else
+ return 0;
+}
+static int winctrl_cmp_byctrl_find(void *av, void *bv)
+{
+ union control *a = (union control *)av;
+ struct winctrl *b = (struct winctrl *)bv;
+ if (a < b->ctrl)
+ return -1;
+ else if (a > b->ctrl)
+ return +1;
+ else
+ return 0;
+}
+static int winctrl_cmp_byid_find(void *av, void *bv)
+{
+ int *a = (int *)av;
+ struct winctrl *b = (struct winctrl *)bv;
+ if (*a < b->base_id)
+ return -1;
+ else if (*a >= b->base_id + b->num_ids)
+ return +1;
+ else
+ return 0;
+}
+
+void winctrl_init(struct winctrls *wc)
+{
+ wc->byctrl = newtree234(winctrl_cmp_byctrl);
+ wc->byid = newtree234(winctrl_cmp_byid);
+}
+void winctrl_cleanup(struct winctrls *wc)
+{
+ struct winctrl *c;
+
+ while ((c = index234(wc->byid, 0)) != NULL) {
+ winctrl_remove(wc, c);
+ sfree(c->data);
+ sfree(c);
+ }
+
+ freetree234(wc->byctrl);
+ freetree234(wc->byid);
+ wc->byctrl = wc->byid = NULL;
+}
+
+void winctrl_add(struct winctrls *wc, struct winctrl *c)
+{
+ struct winctrl *ret;
+ if (c->ctrl) {
+ ret = add234(wc->byctrl, c);
+ assert(ret == c);
+ }
+ ret = add234(wc->byid, c);
+ assert(ret == c);
+}
+
+void winctrl_remove(struct winctrls *wc, struct winctrl *c)
+{
+ struct winctrl *ret;
+ ret = del234(wc->byctrl, c);
+ ret = del234(wc->byid, c);
+ assert(ret == c);
+}
+
+struct winctrl *winctrl_findbyctrl(struct winctrls *wc, union control *ctrl)
+{
+ return find234(wc->byctrl, ctrl, winctrl_cmp_byctrl_find);
+}
+
+struct winctrl *winctrl_findbyid(struct winctrls *wc, int id)
+{
+ return find234(wc->byid, &id, winctrl_cmp_byid_find);
+}
+
+struct winctrl *winctrl_findbyindex(struct winctrls *wc, int index)
+{
+ return index234(wc->byid, index);
+}
+
+void winctrl_layout(struct dlgparam *dp, struct winctrls *wc,
+ struct ctlpos *cp, struct controlset *s, int *id)
+{
+ struct ctlpos columns[16];
+ int ncols, colstart, colspan;
+
+ struct ctlpos tabdelays[16];
+ union control *tabdelayed[16];
+ int ntabdelays;
+
+ struct ctlpos pos;
+
+ char shortcuts[MAX_SHORTCUTS_PER_CTRL];
+ int nshortcuts;
+ char *escaped;
+ int i, actual_base_id, base_id, num_ids;
+ void *data;
+
+ base_id = *id;
+
+ /* Start a containing box, if we have a boxname. */
+ if (s->boxname && *s->boxname) {
+ struct winctrl *c = snew(struct winctrl);
+ c->ctrl = NULL;
+ c->base_id = base_id;
+ c->num_ids = 1;
+ c->data = NULL;
+ memset(c->shortcuts, NO_SHORTCUT, lenof(c->shortcuts));
+ winctrl_add(wc, c);
+ beginbox(cp, s->boxtitle, base_id);
+ base_id++;
+ }
+
+ /* Draw a title, if we have one. */
+ if (!s->boxname && s->boxtitle) {
+ struct winctrl *c = snew(struct winctrl);
+ c->ctrl = NULL;
+ c->base_id = base_id;
+ c->num_ids = 1;
+ c->data = dupstr(s->boxtitle);
+ memset(c->shortcuts, NO_SHORTCUT, lenof(c->shortcuts));
+ winctrl_add(wc, c);
+ paneltitle(cp, base_id);
+ base_id++;
+ }
+
+ /* Initially we have just one column. */
+ ncols = 1;
+ columns[0] = *cp; /* structure copy */
+
+ /* And initially, there are no pending tab-delayed controls. */
+ ntabdelays = 0;
+
+ /* Loop over each control in the controlset. */
+ for (i = 0; i < s->ncontrols; i++) {
+ union control *ctrl = s->ctrls[i];
+
+ /*
+ * Generic processing that pertains to all control types.
+ * At the end of this if statement, we'll have produced
+ * `ctrl' (a pointer to the control we have to create, or
+ * think about creating, in this iteration of the loop),
+ * `pos' (a suitable ctlpos with which to position it), and
+ * `c' (a winctrl structure to receive details of the
+ * dialog IDs). Or we'll have done a `continue', if it was
+ * CTRL_COLUMNS and doesn't require any control creation at
+ * all.
+ */
+ if (ctrl->generic.type == CTRL_COLUMNS) {
+ assert((ctrl->columns.ncols == 1) ^ (ncols == 1));
+
+ if (ncols == 1) {
+ /*
+ * We're splitting into multiple columns.
+ */
+ int lpercent, rpercent, lx, rx, i;
+
+ ncols = ctrl->columns.ncols;
+ assert(ncols <= lenof(columns));
+ for (i = 1; i < ncols; i++)
+ columns[i] = columns[0]; /* structure copy */
+
+ lpercent = 0;
+ for (i = 0; i < ncols; i++) {
+ rpercent = lpercent + ctrl->columns.percentages[i];
+ lx = columns[i].xoff + lpercent *
+ (columns[i].width + GAPBETWEEN) / 100;
+ rx = columns[i].xoff + rpercent *
+ (columns[i].width + GAPBETWEEN) / 100;
+ columns[i].xoff = lx;
+ columns[i].width = rx - lx - GAPBETWEEN;
+ lpercent = rpercent;
+ }
+ } else {
+ /*
+ * We're recombining the various columns into one.
+ */
+ int maxy = columns[0].ypos;
+ int i;
+ for (i = 1; i < ncols; i++)
+ if (maxy < columns[i].ypos)
+ maxy = columns[i].ypos;
+ ncols = 1;
+ columns[0] = *cp; /* structure copy */
+ columns[0].ypos = maxy;
+ }
+
+ continue;
+ } else if (ctrl->generic.type == CTRL_TABDELAY) {
+ int i;
+
+ assert(!ctrl->generic.tabdelay);
+ ctrl = ctrl->tabdelay.ctrl;
+
+ for (i = 0; i < ntabdelays; i++)
+ if (tabdelayed[i] == ctrl)
+ break;
+ assert(i < ntabdelays); /* we have to have found it */
+
+ pos = tabdelays[i]; /* structure copy */
+
+ colstart = colspan = -1; /* indicate this was tab-delayed */
+
+ } else {
+ /*
+ * If it wasn't one of those, it's a genuine control;
+ * so we'll have to compute a position for it now, by
+ * checking its column span.
+ */
+ int col;
+
+ colstart = COLUMN_START(ctrl->generic.column);
+ colspan = COLUMN_SPAN(ctrl->generic.column);
+
+ pos = columns[colstart]; /* structure copy */
+ pos.width = columns[colstart+colspan-1].width +
+ (columns[colstart+colspan-1].xoff - columns[colstart].xoff);
+
+ for (col = colstart; col < colstart+colspan; col++)
+ if (pos.ypos < columns[col].ypos)
+ pos.ypos = columns[col].ypos;
+
+ /*
+ * If this control is to be tabdelayed, add it to the
+ * tabdelay list, and unset pos.hwnd to inhibit actual
+ * control creation.
+ */
+ if (ctrl->generic.tabdelay) {
+ assert(ntabdelays < lenof(tabdelays));
+ tabdelays[ntabdelays] = pos; /* structure copy */
+ tabdelayed[ntabdelays] = ctrl;
+ ntabdelays++;
+ pos.hwnd = NULL;
+ }
+ }
+
+ /* Most controls don't need anything in c->data. */
+ data = NULL;
+
+ /* And they all start off with no shortcuts registered. */
+ memset(shortcuts, NO_SHORTCUT, lenof(shortcuts));
+ nshortcuts = 0;
+
+ /* Almost all controls start at base_id. */
+ actual_base_id = base_id;
+
+ /*
+ * Now we're ready to actually create the control, by
+ * switching on its type.
+ */
+ switch (ctrl->generic.type) {
+ case CTRL_TEXT:
+ {
+ char *wrapped, *escaped;
+ int lines;
+ num_ids = 1;
+ wrapped = staticwrap(&pos, cp->hwnd,
+ ctrl->generic.label, &lines);
+ escaped = shortcut_escape(wrapped, NO_SHORTCUT);
+ statictext(&pos, escaped, lines, base_id);
+ sfree(escaped);
+ sfree(wrapped);
+ }
+ break;
+ case CTRL_EDITBOX:
+ num_ids = 2; /* static, edit */
+ escaped = shortcut_escape(ctrl->editbox.label,
+ ctrl->editbox.shortcut);
+ shortcuts[nshortcuts++] = ctrl->editbox.shortcut;
+ if (ctrl->editbox.percentwidth == 100) {
+ if (ctrl->editbox.has_list)
+ combobox(&pos, escaped,
+ base_id, base_id+1);
+ else
+ editboxfw(&pos, ctrl->editbox.password, escaped,
+ base_id, base_id+1);
+ } else {
+ if (ctrl->editbox.has_list) {
+ staticcombo(&pos, escaped, base_id, base_id+1,
+ ctrl->editbox.percentwidth);
+ } else {
+ (ctrl->editbox.password ? staticpassedit : staticedit)
+ (&pos, escaped, base_id, base_id+1,
+ ctrl->editbox.percentwidth);
+ }
+ }
+ sfree(escaped);
+ break;
+ case CTRL_RADIO:
+ num_ids = ctrl->radio.nbuttons + 1; /* label as well */
+ {
+ struct radio *buttons;
+ int i;
+
+ escaped = shortcut_escape(ctrl->radio.label,
+ ctrl->radio.shortcut);
+ shortcuts[nshortcuts++] = ctrl->radio.shortcut;
+
+ buttons = snewn(ctrl->radio.nbuttons, struct radio);
+
+ for (i = 0; i < ctrl->radio.nbuttons; i++) {
+ buttons[i].text =
+ shortcut_escape(ctrl->radio.buttons[i],
+ (char)(ctrl->radio.shortcuts ?
+ ctrl->radio.shortcuts[i] :
+ NO_SHORTCUT));
+ buttons[i].id = base_id + 1 + i;
+ if (ctrl->radio.shortcuts) {
+ assert(nshortcuts < MAX_SHORTCUTS_PER_CTRL);
+ shortcuts[nshortcuts++] = ctrl->radio.shortcuts[i];
+ }
+ }
+
+ radioline_common(&pos, escaped, base_id,
+ ctrl->radio.ncolumns,
+ buttons, ctrl->radio.nbuttons);
+
+ for (i = 0; i < ctrl->radio.nbuttons; i++) {
+ sfree(buttons[i].text);
+ }
+ sfree(buttons);
+ sfree(escaped);
+ }
+ break;
+ case CTRL_CHECKBOX:
+ num_ids = 1;
+ escaped = shortcut_escape(ctrl->checkbox.label,
+ ctrl->checkbox.shortcut);
+ shortcuts[nshortcuts++] = ctrl->checkbox.shortcut;
+ checkbox(&pos, escaped, base_id);
+ sfree(escaped);
+ break;
+ case CTRL_BUTTON:
+ escaped = shortcut_escape(ctrl->button.label,
+ ctrl->button.shortcut);
+ shortcuts[nshortcuts++] = ctrl->button.shortcut;
+ if (ctrl->button.iscancel)
+ actual_base_id = IDCANCEL;
+ num_ids = 1;
+ button(&pos, escaped, actual_base_id, ctrl->button.isdefault);
+ sfree(escaped);
+ break;
+ case CTRL_LISTBOX:
+ num_ids = 2;
+ escaped = shortcut_escape(ctrl->listbox.label,
+ ctrl->listbox.shortcut);
+ shortcuts[nshortcuts++] = ctrl->listbox.shortcut;
+ if (ctrl->listbox.draglist) {
+ data = snew(struct prefslist);
+ num_ids = 4;
+ prefslist(data, &pos, ctrl->listbox.height, escaped,
+ base_id, base_id+1, base_id+2, base_id+3);
+ shortcuts[nshortcuts++] = 'u'; /* Up */
+ shortcuts[nshortcuts++] = 'd'; /* Down */
+ } else if (ctrl->listbox.height == 0) {
+ /* Drop-down list. */
+ if (ctrl->listbox.percentwidth == 100) {
+ staticddlbig(&pos, escaped,
+ base_id, base_id+1);
+ } else {
+ staticddl(&pos, escaped, base_id,
+ base_id+1, ctrl->listbox.percentwidth);
+ }
+ } else {
+ /* Ordinary list. */
+ listbox(&pos, escaped, base_id, base_id+1,
+ ctrl->listbox.height, ctrl->listbox.multisel);
+ }
+ if (ctrl->listbox.ncols) {
+ /*
+ * This method of getting the box width is a bit of
+ * a hack; we'd do better to try to retrieve the
+ * actual width in dialog units from doctl() just
+ * before MapDialogRect. But that's going to be no
+ * fun, and this should be good enough accuracy.
+ */
+ int width = cp->width * ctrl->listbox.percentwidth;
+ int *tabarray;
+ int i, percent;
+
+ tabarray = snewn(ctrl->listbox.ncols-1, int);
+ percent = 0;
+ for (i = 0; i < ctrl->listbox.ncols-1; i++) {
+ percent += ctrl->listbox.percentages[i];
+ tabarray[i] = width * percent / 10000;
+ }
+ SendDlgItemMessage(cp->hwnd, base_id+1, LB_SETTABSTOPS,
+ ctrl->listbox.ncols-1, (LPARAM)tabarray);
+ sfree(tabarray);
+ }
+ sfree(escaped);
+ break;
+ case CTRL_FILESELECT:
+ num_ids = 3;
+ escaped = shortcut_escape(ctrl->fileselect.label,
+ ctrl->fileselect.shortcut);
+ shortcuts[nshortcuts++] = ctrl->fileselect.shortcut;
+ editbutton(&pos, escaped, base_id, base_id+1,
+ "Bro&wse...", base_id+2);
+ shortcuts[nshortcuts++] = 'w';
+ sfree(escaped);
+ break;
+ case CTRL_FONTSELECT:
+ num_ids = 3;
+ 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);
+ sfree(escaped);
+ data = snew(FontSpec);
+ break;
+ default:
+ assert(!"Can't happen");
+ num_ids = 0; /* placate gcc */
+ break;
+ }
+
+ /*
+ * Create a `struct winctrl' for this control, and advance
+ * the dialog ID counter, if it's actually been created
+ * (and isn't tabdelayed).
+ */
+ if (pos.hwnd) {
+ struct winctrl *c = snew(struct winctrl);
+
+ c->ctrl = ctrl;
+ c->base_id = actual_base_id;
+ c->num_ids = num_ids;
+ c->data = data;
+ 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;
+ }
+
+ if (colstart >= 0) {
+ /*
+ * Update the ypos in all columns crossed by this
+ * control.
+ */
+ int i;
+ for (i = colstart; i < colstart+colspan; i++)
+ columns[i].ypos = pos.ypos;
+ }
+ }
+
+ /*
+ * We've now finished laying out the controls; so now update
+ * the ctlpos and control ID that were passed in, terminate
+ * any containing box, and return.
+ */
+ for (i = 0; i < ncols; i++)
+ if (cp->ypos < columns[i].ypos)
+ cp->ypos = columns[i].ypos;
+ *id = base_id;
+
+ if (s->boxname && *s->boxname)
+ endbox(cp);
+}
+
+static void winctrl_set_focus(union control *ctrl, struct dlgparam *dp,
+ int has_focus)
+{
+ if (has_focus) {
+ if (dp->focused)
+ dp->lastfocused = dp->focused;
+ dp->focused = ctrl;
+ } else if (!has_focus && dp->focused == ctrl) {
+ dp->lastfocused = dp->focused;
+ dp->focused = NULL;
+ }
+}
+
+union control *dlg_last_focused(union control *ctrl, void *dlg)
+{
+ struct dlgparam *dp = (struct dlgparam *)dlg;
+ return dp->focused == ctrl ? dp->lastfocused : dp->focused;
+}
+
+/*
+ * The dialog-box procedure calls this function to handle Windows
+ * messages on a control we manage.
+ */
+int winctrl_handle_command(struct dlgparam *dp, UINT msg,
+ WPARAM wParam, LPARAM lParam)
+{
+ struct winctrl *c;
+ union control *ctrl;
+ int i, id, ret;
+ static UINT draglistmsg = WM_NULL;
+
+ /*
+ * Filter out pointless window messages. Our interest is in
+ * WM_COMMAND and the drag list message, and nothing else.
+ */
+ if (draglistmsg == WM_NULL)
+ draglistmsg = RegisterWindowMessage (DRAGLISTMSGSTRING);
+
+ if (msg != draglistmsg && msg != WM_COMMAND && msg != WM_DRAWITEM)
+ return 0;
+
+ /*
+ * Look up the control ID in our data.
+ */
+ c = NULL;
+ for (i = 0; i < dp->nctrltrees; i++) {
+ c = winctrl_findbyid(dp->controltrees[i], LOWORD(wParam));
+ if (c)
+ break;
+ }
+ if (!c)
+ return 0; /* we have nothing to do */
+
+ if (msg == WM_DRAWITEM) {
+ /*
+ * Owner-draw request for a panel title.
+ */
+ LPDRAWITEMSTRUCT di = (LPDRAWITEMSTRUCT) lParam;
+ HDC hdc = di->hDC;
+ RECT r = di->rcItem;
+ SIZE s;
+
+ SetMapMode(hdc, MM_TEXT); /* ensure logical units == pixels */
+
+ GetTextExtentPoint32(hdc, (char *)c->data,
+ strlen((char *)c->data), &s);
+ DrawEdge(hdc, &r, EDGE_ETCHED, BF_ADJUST | BF_RECT);
+ TextOut(hdc,
+ r.left + (r.right-r.left-s.cx)/2,
+ r.top + (r.bottom-r.top-s.cy)/2,
+ (char *)c->data, strlen((char *)c->data));
+
+ return TRUE;
+ }
+
+ ctrl = c->ctrl;
+ id = LOWORD(wParam) - c->base_id;
+
+ if (!ctrl || !ctrl->generic.handler)
+ return 0; /* nothing we can do here */
+
+ /*
+ * From here on we do not issue `return' statements until the
+ * very end of the dialog box: any event handler is entitled to
+ * ask for a colour selector, so we _must_ always allow control
+ * to reach the end of this switch statement so that the
+ * subsequent code can test dp->coloursel_wanted().
+ */
+ ret = 0;
+ dp->coloursel_wanted = FALSE;
+
+ /*
+ * Now switch on the control type and the message.
+ */
+ switch (ctrl->generic.type) {
+ case CTRL_EDITBOX:
+ if (msg == WM_COMMAND && !ctrl->editbox.has_list &&
+ (HIWORD(wParam) == EN_SETFOCUS || HIWORD(wParam) == EN_KILLFOCUS))
+ winctrl_set_focus(ctrl, dp, HIWORD(wParam) == EN_SETFOCUS);
+ if (msg == WM_COMMAND && ctrl->editbox.has_list &&
+ (HIWORD(wParam)==CBN_SETFOCUS || HIWORD(wParam)==CBN_KILLFOCUS))
+ winctrl_set_focus(ctrl, dp, HIWORD(wParam) == CBN_SETFOCUS);
+
+ if (msg == WM_COMMAND && !ctrl->editbox.has_list &&
+ HIWORD(wParam) == EN_CHANGE)
+ ctrl->generic.handler(ctrl, dp, dp->data, EVENT_VALCHANGE);
+ if (msg == WM_COMMAND &&
+ ctrl->editbox.has_list) {
+ if (HIWORD(wParam) == CBN_SELCHANGE) {
+ int index, len;
+ char *text;
+
+ index = SendDlgItemMessage(dp->hwnd, c->base_id+1,
+ CB_GETCURSEL, 0, 0);
+ len = SendDlgItemMessage(dp->hwnd, c->base_id+1,
+ CB_GETLBTEXTLEN, index, 0);
+ text = snewn(len+1, char);
+ SendDlgItemMessage(dp->hwnd, c->base_id+1, CB_GETLBTEXT,
+ index, (LPARAM)text);
+ SetDlgItemText(dp->hwnd, c->base_id+1, text);
+ sfree(text);
+ ctrl->generic.handler(ctrl, dp, dp->data, EVENT_VALCHANGE);
+ } else if (HIWORD(wParam) == CBN_EDITCHANGE) {
+ ctrl->generic.handler(ctrl, dp, dp->data, EVENT_VALCHANGE);
+ } else if (HIWORD(wParam) == CBN_KILLFOCUS) {
+ ctrl->generic.handler(ctrl, dp, dp->data, EVENT_REFRESH);
+ }
+
+ }
+ break;
+ case CTRL_RADIO:
+ if (msg == WM_COMMAND &&
+ (HIWORD(wParam) == BN_SETFOCUS || HIWORD(wParam) == BN_KILLFOCUS))
+ winctrl_set_focus(ctrl, dp, HIWORD(wParam) == BN_SETFOCUS);
+ /*
+ * We sometimes get spurious BN_CLICKED messages for the
+ * radio button that is just about to _lose_ selection, if
+ * we're switching using the arrow keys. Therefore we
+ * double-check that the button in wParam is actually
+ * checked before generating an event.
+ */
+ if (msg == WM_COMMAND &&
+ (HIWORD(wParam) == BN_CLICKED ||
+ HIWORD(wParam) == BN_DOUBLECLICKED) &&
+ IsDlgButtonChecked(dp->hwnd, LOWORD(wParam))) {
+ ctrl->generic.handler(ctrl, dp, dp->data, EVENT_VALCHANGE);
+ }
+ break;
+ case CTRL_CHECKBOX:
+ if (msg == WM_COMMAND &&
+ (HIWORD(wParam) == BN_SETFOCUS || HIWORD(wParam) == BN_KILLFOCUS))
+ winctrl_set_focus(ctrl, dp, HIWORD(wParam) == BN_SETFOCUS);
+ if (msg == WM_COMMAND &&
+ (HIWORD(wParam) == BN_CLICKED ||
+ HIWORD(wParam) == BN_DOUBLECLICKED)) {
+ ctrl->generic.handler(ctrl, dp, dp->data, EVENT_VALCHANGE);
+ }
+ break;
+ case CTRL_BUTTON:
+ if (msg == WM_COMMAND &&
+ (HIWORD(wParam) == BN_SETFOCUS || HIWORD(wParam) == BN_KILLFOCUS))
+ winctrl_set_focus(ctrl, dp, HIWORD(wParam) == BN_SETFOCUS);
+ if (msg == WM_COMMAND &&
+ (HIWORD(wParam) == BN_CLICKED ||
+ HIWORD(wParam) == BN_DOUBLECLICKED)) {
+ ctrl->generic.handler(ctrl, dp, dp->data, EVENT_ACTION);
+ }
+ break;
+ case CTRL_LISTBOX:
+ if (msg == WM_COMMAND && ctrl->listbox.height != 0 &&
+ (HIWORD(wParam)==LBN_SETFOCUS || HIWORD(wParam)==LBN_KILLFOCUS))
+ winctrl_set_focus(ctrl, dp, HIWORD(wParam) == LBN_SETFOCUS);
+ if (msg == WM_COMMAND && ctrl->listbox.height == 0 &&
+ (HIWORD(wParam)==CBN_SETFOCUS || HIWORD(wParam)==CBN_KILLFOCUS))
+ winctrl_set_focus(ctrl, dp, HIWORD(wParam) == CBN_SETFOCUS);
+ if (msg == WM_COMMAND && id >= 2 &&
+ (HIWORD(wParam) == BN_SETFOCUS || HIWORD(wParam) == BN_KILLFOCUS))
+ winctrl_set_focus(ctrl, dp, HIWORD(wParam) == BN_SETFOCUS);
+ if (ctrl->listbox.draglist) {
+ int pret;
+ pret = handle_prefslist(c->data, NULL, 0, (msg != WM_COMMAND),
+ dp->hwnd, wParam, lParam);
+ if (pret & 2)
+ ctrl->generic.handler(ctrl, dp, dp->data, EVENT_VALCHANGE);
+ ret = pret & 1;
+ } else {
+ if (msg == WM_COMMAND && HIWORD(wParam) == LBN_DBLCLK) {
+ SetCapture(dp->hwnd);
+ ctrl->generic.handler(ctrl, dp, dp->data, EVENT_ACTION);
+ } else if (msg == WM_COMMAND && HIWORD(wParam) == LBN_SELCHANGE) {
+ ctrl->generic.handler(ctrl, dp, dp->data, EVENT_SELCHANGE);
+ }
+ }
+ break;
+ case CTRL_FILESELECT:
+ if (msg == WM_COMMAND && id == 1 &&
+ (HIWORD(wParam) == EN_SETFOCUS || HIWORD(wParam) == EN_KILLFOCUS))
+ winctrl_set_focus(ctrl, dp, HIWORD(wParam) == EN_SETFOCUS);
+ if (msg == WM_COMMAND && id == 2 &&
+ (HIWORD(wParam) == BN_SETFOCUS || HIWORD(wParam) == BN_KILLFOCUS))
+ winctrl_set_focus(ctrl, dp, HIWORD(wParam) == BN_SETFOCUS);
+ if (msg == WM_COMMAND && id == 1 && HIWORD(wParam) == EN_CHANGE)
+ ctrl->generic.handler(ctrl, dp, dp->data, EVENT_VALCHANGE);
+ if (id == 2 &&
+ (msg == WM_COMMAND &&
+ (HIWORD(wParam) == BN_CLICKED ||
+ HIWORD(wParam) == BN_DOUBLECLICKED))) {
+ OPENFILENAME of;
+ char filename[FILENAME_MAX];
+
+ memset(&of, 0, sizeof(of));
+ of.hwndOwner = dp->hwnd;
+ if (ctrl->fileselect.filter)
+ of.lpstrFilter = ctrl->fileselect.filter;
+ else
+ of.lpstrFilter = "All Files (*.*)\0*\0\0\0";
+ of.lpstrCustomFilter = NULL;
+ of.nFilterIndex = 1;
+ of.lpstrFile = filename;
+ GetDlgItemText(dp->hwnd, c->base_id+1, filename, lenof(filename));
+ filename[lenof(filename)-1] = '\0';
+ of.nMaxFile = lenof(filename);
+ of.lpstrFileTitle = NULL;
+ of.lpstrTitle = ctrl->fileselect.title;
+ of.Flags = 0;
+ if (request_file(NULL, &of, FALSE, ctrl->fileselect.for_writing)) {
+ SetDlgItemText(dp->hwnd, c->base_id + 1, filename);
+ ctrl->generic.handler(ctrl, dp, dp->data, EVENT_VALCHANGE);
+ }
+ }
+ break;
+ case CTRL_FONTSELECT:
+ if (msg == WM_COMMAND && id == 2 &&
+ (HIWORD(wParam) == BN_SETFOCUS || HIWORD(wParam) == BN_KILLFOCUS))
+ winctrl_set_focus(ctrl, dp, HIWORD(wParam) == BN_SETFOCUS);
+ if (id == 2 &&
+ (msg == WM_COMMAND &&
+ (HIWORD(wParam) == BN_CLICKED ||
+ HIWORD(wParam) == BN_DOUBLECLICKED))) {
+ CHOOSEFONT cf;
+ LOGFONT lf;
+ HDC hdc;
+ FontSpec fs = *(FontSpec *)c->data;
+
+ hdc = GetDC(0);
+ 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.lfOutPrecision = OUT_DEFAULT_PRECIS;
+ lf.lfClipPrecision = CLIP_DEFAULT_PRECIS;
+ lf.lfQuality = DEFAULT_QUALITY;
+ lf.lfPitchAndFamily = FIXED_PITCH | FF_DONTCARE;
+ strncpy(lf.lfFaceName, fs.name,
+ sizeof(lf.lfFaceName) - 1);
+ lf.lfFaceName[sizeof(lf.lfFaceName) - 1] = '\0';
+
+ cf.lStructSize = sizeof(cf);
+ cf.hwndOwner = dp->hwnd;
+ 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;
+ dlg_fontsel_set(ctrl, dp, fs);
+ ctrl->generic.handler(ctrl, dp, dp->data, EVENT_VALCHANGE);
+ }
+ }
+ break;
+ }
+
+ /*
+ * If the above event handler has asked for a colour selector,
+ * now is the time to generate one.
+ */
+ if (dp->coloursel_wanted) {
+ static CHOOSECOLOR cc;
+ static DWORD custom[16] = { 0 }; /* zero initialisers */
+ cc.lStructSize = sizeof(cc);
+ cc.hwndOwner = dp->hwnd;
+ cc.hInstance = (HWND) hinst;
+ cc.lpCustColors = custom;
+ cc.rgbResult = RGB(dp->coloursel_result.r,
+ dp->coloursel_result.g,
+ dp->coloursel_result.b);
+ cc.Flags = CC_FULLOPEN | CC_RGBINIT;
+ if (ChooseColor(&cc)) {
+ dp->coloursel_result.r =
+ (unsigned char) (cc.rgbResult & 0xFF);
+ dp->coloursel_result.g =
+ (unsigned char) (cc.rgbResult >> 8) & 0xFF;
+ dp->coloursel_result.b =
+ (unsigned char) (cc.rgbResult >> 16) & 0xFF;
+ dp->coloursel_result.ok = TRUE;
+ } else
+ dp->coloursel_result.ok = FALSE;
+ ctrl->generic.handler(ctrl, dp, dp->data, EVENT_CALLBACK);
+ }
+
+ return ret;
+}
+
+/*
+ * This function can be called to produce context help on a
+ * control. Returns TRUE if it has actually launched some help.
+ */
+int winctrl_context_help(struct dlgparam *dp, HWND hwnd, int id)
+{
+ int i;
+ struct winctrl *c;
+
+ /*
+ * Look up the control ID in our data.
+ */
+ c = NULL;
+ for (i = 0; i < dp->nctrltrees; i++) {
+ c = winctrl_findbyid(dp->controltrees[i], id);
+ if (c)
+ break;
+ }
+ if (!c)
+ return 0; /* we have nothing to do */
+
+ /*
+ * This is the Windows front end, so we're allowed to assume
+ * `helpctx.p' is a context string.
+ */
+ if (!c->ctrl || !c->ctrl->generic.helpctx.p)
+ return 0; /* no help available for this ctrl */
+
+ launch_help(hwnd, c->ctrl->generic.helpctx.p);
+ return 1;
+}
+
+/*
+ * Now the various functions that the platform-independent
+ * mechanism can call to access the dialog box entries.
+ */
+
+static struct winctrl *dlg_findbyctrl(struct dlgparam *dp, union control *ctrl)
+{
+ int i;
+
+ for (i = 0; i < dp->nctrltrees; i++) {
+ struct winctrl *c = winctrl_findbyctrl(dp->controltrees[i], ctrl);
+ if (c)
+ return c;
+ }
+ return NULL;
+}
+
+void dlg_radiobutton_set(union control *ctrl, void *dlg, int whichbutton)
+{
+ struct dlgparam *dp = (struct dlgparam *)dlg;
+ struct winctrl *c = dlg_findbyctrl(dp, ctrl);
+ assert(c && c->ctrl->generic.type == CTRL_RADIO);
+ CheckRadioButton(dp->hwnd,
+ c->base_id + 1,
+ c->base_id + c->ctrl->radio.nbuttons,
+ c->base_id + 1 + whichbutton);
+}
+
+int dlg_radiobutton_get(union control *ctrl, void *dlg)
+{
+ struct dlgparam *dp = (struct dlgparam *)dlg;
+ struct winctrl *c = dlg_findbyctrl(dp, ctrl);
+ int i;
+ assert(c && c->ctrl->generic.type == CTRL_RADIO);
+ for (i = 0; i < c->ctrl->radio.nbuttons; i++)
+ if (IsDlgButtonChecked(dp->hwnd, c->base_id + 1 + i))
+ return i;
+ assert(!"No radio button was checked?!");
+ return 0;
+}
+
+void dlg_checkbox_set(union control *ctrl, void *dlg, int checked)
+{
+ struct dlgparam *dp = (struct dlgparam *)dlg;
+ struct winctrl *c = dlg_findbyctrl(dp, ctrl);
+ assert(c && c->ctrl->generic.type == CTRL_CHECKBOX);
+ CheckDlgButton(dp->hwnd, c->base_id, (checked != 0));
+}
+
+int dlg_checkbox_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_CHECKBOX);
+ return 0 != IsDlgButtonChecked(dp->hwnd, c->base_id);
+}
+
+void dlg_editbox_set(union control *ctrl, void *dlg, char const *text)
+{
+ struct dlgparam *dp = (struct dlgparam *)dlg;
+ 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)
+{
+ 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';
+}
+
+/* The `listbox' functions can also apply to combo boxes. */
+void dlg_listbox_clear(union control *ctrl, void *dlg)
+{
+ struct dlgparam *dp = (struct dlgparam *)dlg;
+ struct winctrl *c = dlg_findbyctrl(dp, ctrl);
+ int msg;
+ assert(c &&
+ (c->ctrl->generic.type == CTRL_LISTBOX ||
+ (c->ctrl->generic.type == CTRL_EDITBOX &&
+ c->ctrl->editbox.has_list)));
+ msg = (c->ctrl->generic.type==CTRL_LISTBOX && c->ctrl->listbox.height!=0 ?
+ LB_RESETCONTENT : CB_RESETCONTENT);
+ SendDlgItemMessage(dp->hwnd, c->base_id+1, msg, 0, 0);
+}
+
+void dlg_listbox_del(union control *ctrl, void *dlg, int index)
+{
+ struct dlgparam *dp = (struct dlgparam *)dlg;
+ struct winctrl *c = dlg_findbyctrl(dp, ctrl);
+ int msg;
+ assert(c &&
+ (c->ctrl->generic.type == CTRL_LISTBOX ||
+ (c->ctrl->generic.type == CTRL_EDITBOX &&
+ c->ctrl->editbox.has_list)));
+ msg = (c->ctrl->generic.type==CTRL_LISTBOX && c->ctrl->listbox.height!=0 ?
+ LB_DELETESTRING : CB_DELETESTRING);
+ SendDlgItemMessage(dp->hwnd, c->base_id+1, msg, index, 0);
+}
+
+void dlg_listbox_add(union control *ctrl, void *dlg, char const *text)
+{
+ struct dlgparam *dp = (struct dlgparam *)dlg;
+ struct winctrl *c = dlg_findbyctrl(dp, ctrl);
+ int msg;
+ assert(c &&
+ (c->ctrl->generic.type == CTRL_LISTBOX ||
+ (c->ctrl->generic.type == CTRL_EDITBOX &&
+ c->ctrl->editbox.has_list)));
+ msg = (c->ctrl->generic.type==CTRL_LISTBOX && c->ctrl->listbox.height!=0 ?
+ LB_ADDSTRING : CB_ADDSTRING);
+ SendDlgItemMessage(dp->hwnd, c->base_id+1, msg, 0, (LPARAM)text);
+}
+
+/*
+ * Each listbox entry may have a numeric id associated with it.
+ * Note that some front ends only permit a string to be stored at
+ * each position, which means that _if_ you put two identical
+ * strings in any listbox then you MUST not assign them different
+ * IDs and expect to get meaningful results back.
+ */
+void dlg_listbox_addwithid(union control *ctrl, void *dlg,
+ char const *text, int id)
+{
+ struct dlgparam *dp = (struct dlgparam *)dlg;
+ struct winctrl *c = dlg_findbyctrl(dp, ctrl);
+ int msg, msg2, index;
+ assert(c &&
+ (c->ctrl->generic.type == CTRL_LISTBOX ||
+ (c->ctrl->generic.type == CTRL_EDITBOX &&
+ c->ctrl->editbox.has_list)));
+ msg = (c->ctrl->generic.type==CTRL_LISTBOX && c->ctrl->listbox.height!=0 ?
+ LB_ADDSTRING : CB_ADDSTRING);
+ msg2 = (c->ctrl->generic.type==CTRL_LISTBOX && c->ctrl->listbox.height!=0 ?
+ LB_SETITEMDATA : CB_SETITEMDATA);
+ index = SendDlgItemMessage(dp->hwnd, c->base_id+1, msg, 0, (LPARAM)text);
+ SendDlgItemMessage(dp->hwnd, c->base_id+1, msg2, index, (LPARAM)id);
+}
+
+int dlg_listbox_getid(union control *ctrl, void *dlg, int index)
+{
+ struct dlgparam *dp = (struct dlgparam *)dlg;
+ struct winctrl *c = dlg_findbyctrl(dp, ctrl);
+ int msg;
+ assert(c && c->ctrl->generic.type == CTRL_LISTBOX);
+ msg = (c->ctrl->listbox.height != 0 ? LB_GETITEMDATA : CB_GETITEMDATA);
+ return
+ SendDlgItemMessage(dp->hwnd, c->base_id+1, msg, index, 0);
+}
+
+/* dlg_listbox_index returns <0 if no single element is selected. */
+int dlg_listbox_index(union control *ctrl, void *dlg)
+{
+ struct dlgparam *dp = (struct dlgparam *)dlg;
+ struct winctrl *c = dlg_findbyctrl(dp, ctrl);
+ int msg, ret;
+ assert(c && c->ctrl->generic.type == CTRL_LISTBOX);
+ if (c->ctrl->listbox.multisel) {
+ assert(c->ctrl->listbox.height != 0); /* not combo box */
+ ret = SendDlgItemMessage(dp->hwnd, c->base_id+1, LB_GETSELCOUNT, 0, 0);
+ if (ret == LB_ERR || ret > 1)
+ return -1;
+ }
+ msg = (c->ctrl->listbox.height != 0 ? LB_GETCURSEL : CB_GETCURSEL);
+ ret = SendDlgItemMessage(dp->hwnd, c->base_id+1, msg, 0, 0);
+ if (ret == LB_ERR)
+ return -1;
+ else
+ return ret;
+}
+
+int dlg_listbox_issel(union control *ctrl, void *dlg, int index)
+{
+ struct dlgparam *dp = (struct dlgparam *)dlg;
+ struct winctrl *c = dlg_findbyctrl(dp, ctrl);
+ assert(c && c->ctrl->generic.type == CTRL_LISTBOX &&
+ c->ctrl->listbox.multisel &&
+ c->ctrl->listbox.height != 0);
+ return
+ SendDlgItemMessage(dp->hwnd, c->base_id+1, LB_GETSEL, index, 0);
+}
+
+void dlg_listbox_select(union control *ctrl, void *dlg, int index)
+{
+ struct dlgparam *dp = (struct dlgparam *)dlg;
+ struct winctrl *c = dlg_findbyctrl(dp, ctrl);
+ int msg;
+ assert(c && c->ctrl->generic.type == CTRL_LISTBOX &&
+ !c->ctrl->listbox.multisel);
+ msg = (c->ctrl->listbox.height != 0 ? LB_SETCURSEL : CB_SETCURSEL);
+ SendDlgItemMessage(dp->hwnd, c->base_id+1, msg, index, 0);
+}
+
+void dlg_text_set(union control *ctrl, void *dlg, char const *text)
+{
+ struct dlgparam *dp = (struct dlgparam *)dlg;
+ struct winctrl *c = dlg_findbyctrl(dp, ctrl);
+ assert(c && c->ctrl->generic.type == CTRL_TEXT);
+ SetDlgItemText(dp->hwnd, c->base_id, text);
+}
+
+void dlg_label_change(union control *ctrl, void *dlg, char const *text)
+{
+ struct dlgparam *dp = (struct dlgparam *)dlg;
+ struct winctrl *c = dlg_findbyctrl(dp, ctrl);
+ char *escaped = NULL;
+ int id = -1;
+
+ assert(c);
+ switch (c->ctrl->generic.type) {
+ case CTRL_EDITBOX:
+ escaped = shortcut_escape(text, c->ctrl->editbox.shortcut);
+ id = c->base_id;
+ break;
+ case CTRL_RADIO:
+ escaped = shortcut_escape(text, c->ctrl->radio.shortcut);
+ id = c->base_id;
+ break;
+ case CTRL_CHECKBOX:
+ escaped = shortcut_escape(text, ctrl->checkbox.shortcut);
+ id = c->base_id;
+ break;
+ case CTRL_BUTTON:
+ escaped = shortcut_escape(text, ctrl->button.shortcut);
+ id = c->base_id;
+ break;
+ case CTRL_LISTBOX:
+ escaped = shortcut_escape(text, ctrl->listbox.shortcut);
+ id = c->base_id;
+ break;
+ case CTRL_FILESELECT:
+ escaped = shortcut_escape(text, ctrl->fileselect.shortcut);
+ id = c->base_id;
+ break;
+ case CTRL_FONTSELECT:
+ escaped = shortcut_escape(text, ctrl->fontselect.shortcut);
+ id = c->base_id;
+ break;
+ default:
+ assert(!"Can't happen");
+ break;
+ }
+ if (escaped) {
+ SetDlgItemText(dp->hwnd, id, escaped);
+ sfree(escaped);
+ }
+}
+
+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);
+}
+
+void dlg_filesel_get(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);
+ GetDlgItemText(dp->hwnd, c->base_id+1, fn->path, lenof(fn->path));
+ fn->path[lenof(fn->path)-1] = '\0';
+}
+
+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 */
+
+ 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"));
+ 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)
+{
+ 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 */
+}
+
+/*
+ * 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.
+ */
+void dlg_update_start(union control *ctrl, void *dlg)
+{
+ struct dlgparam *dp = (struct dlgparam *)dlg;
+ struct winctrl *c = dlg_findbyctrl(dp, ctrl);
+ if (c && c->ctrl->generic.type == CTRL_LISTBOX) {
+ SendDlgItemMessage(dp->hwnd, c->base_id+1, WM_SETREDRAW, FALSE, 0);
+ }
+}
+
+void dlg_update_done(union control *ctrl, void *dlg)
+{
+ struct dlgparam *dp = (struct dlgparam *)dlg;
+ struct winctrl *c = dlg_findbyctrl(dp, ctrl);
+ if (c && c->ctrl->generic.type == CTRL_LISTBOX) {
+ HWND hw = GetDlgItem(dp->hwnd, c->base_id+1);
+ SendMessage(hw, WM_SETREDRAW, TRUE, 0);
+ InvalidateRect(hw, NULL, TRUE);
+ }
+}
+
+void dlg_set_focus(union control *ctrl, void *dlg)
+{
+ struct dlgparam *dp = (struct dlgparam *)dlg;
+ struct winctrl *c = dlg_findbyctrl(dp, ctrl);
+ int id;
+ HWND ctl;
+ 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))
+ break;
+ /*
+ * In the theoretically-unlikely case that no button was
+ * selected, id should come out of this as 1, which is a
+ * reasonable enough choice.
+ */
+ break;
+ case CTRL_CHECKBOX: id = c->base_id; break;
+ case CTRL_BUTTON: id = c->base_id; break;
+ case CTRL_LISTBOX: id = c->base_id + 1; break;
+ case CTRL_FILESELECT: id = c->base_id + 1; break;
+ case CTRL_FONTSELECT: id = c->base_id + 2; break;
+ default: id = c->base_id; break;
+ }
+ ctl = GetDlgItem(dp->hwnd, id);
+ SetFocus(ctl);
+}
+
+/*
+ * During event processing, you might well want to give an error
+ * indication to the user. dlg_beep() is a quick and easy generic
+ * error; dlg_error() puts up a message-box or equivalent.
+ */
+void dlg_beep(void *dlg)
+{
+ /* struct dlgparam *dp = (struct dlgparam *)dlg; */
+ MessageBeep(0);
+}
+
+void dlg_error_msg(void *dlg, char *msg)
+{
+ struct dlgparam *dp = (struct dlgparam *)dlg;
+ MessageBox(dp->hwnd, msg,
+ dp->errtitle ? dp->errtitle : NULL,
+ MB_OK | MB_ICONERROR);
+}
+
+/*
+ * This function signals to the front end that the dialog's
+ * processing is completed, and passes an integer value (typically
+ * a success status).
+ */
+void dlg_end(void *dlg, int value)
+{
+ struct dlgparam *dp = (struct dlgparam *)dlg;
+ dp->ended = TRUE;
+ dp->endresult = value;
+}
+
+void dlg_refresh(union control *ctrl, void *dlg)
+{
+ struct dlgparam *dp = (struct dlgparam *)dlg;
+ int i, j;
+ struct winctrl *c;
+
+ if (!ctrl) {
+ /*
+ * Send EVENT_REFRESH to absolutely everything.
+ */
+ for (j = 0; j < dp->nctrltrees; j++) {
+ for (i = 0;
+ (c = winctrl_findbyindex(dp->controltrees[j], i)) != NULL;
+ i++) {
+ if (c->ctrl && c->ctrl->generic.handler != NULL)
+ c->ctrl->generic.handler(c->ctrl, dp,
+ dp->data, EVENT_REFRESH);
+ }
+ }
+ } else {
+ /*
+ * Send EVENT_REFRESH to a specific control.
+ */
+ if (ctrl->generic.handler != NULL)
+ ctrl->generic.handler(ctrl, dp, dp->data, EVENT_REFRESH);
+ }
+}
+
+void dlg_coloursel_start(union control *ctrl, void *dlg, int r, int g, int b)
+{
+ struct dlgparam *dp = (struct dlgparam *)dlg;
+ dp->coloursel_wanted = TRUE;
+ dp->coloursel_result.r = r;
+ dp->coloursel_result.g = g;
+ dp->coloursel_result.b = b;
+}
+
+int dlg_coloursel_results(union control *ctrl, void *dlg,
+ int *r, int *g, int *b)
+{
+ struct dlgparam *dp = (struct dlgparam *)dlg;
+ if (dp->coloursel_result.ok) {
+ *r = dp->coloursel_result.r;
+ *g = dp->coloursel_result.g;
+ *b = dp->coloursel_result.b;
+ return 1;
+ } else
+ return 0;
+}
+
+void dlg_auto_set_fixed_pitch_flag(void *dlg)
+{
+ struct dlgparam *dp = (struct dlgparam *)dlg;
+ Config *cfg = (Config *)dp->data;
+ HFONT font;
+ HDC hdc;
+ TEXTMETRIC tm;
+ int is_var;
+
+ /*
+ * 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 *'
+ * 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);
+ hdc = GetDC(NULL);
+ if (font && hdc && SelectObject(hdc, font) && 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 (is_var)
+ dp->fixed_pitch_fonts = FALSE;
+}
+
+int dlg_get_fixed_pitch_flag(void *dlg)
+{
+ struct dlgparam *dp = (struct dlgparam *)dlg;
+ return dp->fixed_pitch_fonts;
+}
+
+void dlg_set_fixed_pitch_flag(void *dlg, int flag)
+{
+ struct dlgparam *dp = (struct dlgparam *)dlg;
+ dp->fixed_pitch_fonts = flag;
+}
+
+struct perctrl_privdata {
+ union control *ctrl;
+ void *data;
+ int needs_free;
+};
+
+static int perctrl_privdata_cmp(void *av, void *bv)
+{
+ struct perctrl_privdata *a = (struct perctrl_privdata *)av;
+ struct perctrl_privdata *b = (struct perctrl_privdata *)bv;
+ if (a->ctrl < b->ctrl)
+ return -1;
+ else if (a->ctrl > b->ctrl)
+ return +1;
+ return 0;
+}
+
+void dp_init(struct dlgparam *dp)
+{
+ dp->nctrltrees = 0;
+ dp->data = NULL;
+ dp->ended = FALSE;
+ dp->focused = dp->lastfocused = NULL;
+ memset(dp->shortcuts, 0, sizeof(dp->shortcuts));
+ dp->hwnd = NULL;
+ dp->wintitle = dp->errtitle = NULL;
+ dp->privdata = newtree234(perctrl_privdata_cmp);
+ dp->fixed_pitch_fonts = TRUE;
+}
+
+void dp_add_tree(struct dlgparam *dp, struct winctrls *wc)
+{
+ assert(dp->nctrltrees < lenof(dp->controltrees));
+ dp->controltrees[dp->nctrltrees++] = wc;
+}
+
+void dp_cleanup(struct dlgparam *dp)
+{
+ struct perctrl_privdata *p;
+
+ if (dp->privdata) {
+ while ( (p = index234(dp->privdata, 0)) != NULL ) {
+ del234(dp->privdata, p);
+ if (p->needs_free)
+ sfree(p->data);
+ sfree(p);
+ }
+ freetree234(dp->privdata);
+ dp->privdata = NULL;
+ }
+ sfree(dp->wintitle);
+ sfree(dp->errtitle);
+}
+
+void *dlg_get_privdata(union control *ctrl, void *dlg)
+{
+ struct dlgparam *dp = (struct dlgparam *)dlg;
+ struct perctrl_privdata tmp, *p;
+ tmp.ctrl = ctrl;
+ p = find234(dp->privdata, &tmp, NULL);
+ if (p)
+ return p->data;
+ else
+ return NULL;
+}
+
+void dlg_set_privdata(union control *ctrl, void *dlg, void *ptr)
+{
+ struct dlgparam *dp = (struct dlgparam *)dlg;
+ struct perctrl_privdata tmp, *p;
+ tmp.ctrl = ctrl;
+ p = find234(dp->privdata, &tmp, NULL);
+ if (!p) {
+ p = snew(struct perctrl_privdata);
+ p->ctrl = ctrl;
+ p->needs_free = FALSE;
+ add234(dp->privdata, p);
+ }
+ p->data = ptr;
+}
+
+void *dlg_alloc_privdata(union control *ctrl, void *dlg, size_t size)
+{
+ struct dlgparam *dp = (struct dlgparam *)dlg;
+ struct perctrl_privdata tmp, *p;
+ tmp.ctrl = ctrl;
+ p = find234(dp->privdata, &tmp, NULL);
+ if (!p) {
+ p = snew(struct perctrl_privdata);
+ p->ctrl = ctrl;
+ p->needs_free = FALSE;
+ add234(dp->privdata, p);
+ }
+ assert(!p->needs_free);
+ p->needs_free = TRUE;
+ /*
+ * This is an internal allocation routine, so it's allowed to
+ * use smalloc directly.
+ */
+ p->data = smalloc(size);
+ return p->data;
+}
--- /dev/null
+/*
+ * windefs.c: default settings that are specific to Windows.
+ */
+
+#include "putty.h"
+
+#include <commctrl.h>
+
+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;
+}
+
+char *platform_default_s(const char *name)
+{
+ if (!strcmp(name, "SerialLine"))
+ return dupstr("COM1");
+ return NULL;
+}
+
+int platform_default_i(const char *name, int def)
+{
+ return def;
+}
--- /dev/null
+/*
+ * windlg.c - dialogs for PuTTY(tel), including the configuration dialog.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <assert.h>
+#include <ctype.h>
+#include <time.h>
+
+#include "putty.h"
+#include "ssh.h"
+#include "win_res.h"
+#include "storage.h"
+#include "dialog.h"
+
+#include <commctrl.h>
+#include <commdlg.h>
+#include <shellapi.h>
+
+#ifdef MSVC4
+#define TVINSERTSTRUCT TV_INSERTSTRUCT
+#define TVITEM TV_ITEM
+#define ICON_BIG 1
+#endif
+
+/*
+ * These are the various bits of data required to handle the
+ * portable-dialog stuff in the config box. Having them at file
+ * scope in here isn't too bad a place to put them; if we were ever
+ * to need more than one config box per process we could always
+ * shift them to a per-config-box structure stored in GWL_USERDATA.
+ */
+static struct controlbox *ctrlbox;
+/*
+ * ctrls_base holds the OK and Cancel buttons: the controls which
+ * are present in all dialog panels. ctrls_panel holds the ones
+ * which change from panel to panel.
+ */
+static struct winctrls ctrls_base, ctrls_panel;
+static struct dlgparam dp;
+
+static char **events = NULL;
+static int nevents = 0, negsize = 0;
+
+extern Config cfg; /* defined in window.c */
+
+#define PRINTER_DISABLED_STRING "None (printing disabled)"
+
+void force_normal(HWND hwnd)
+{
+ static int recurse = 0;
+
+ WINDOWPLACEMENT wp;
+
+ if (recurse)
+ return;
+ recurse = 1;
+
+ wp.length = sizeof(wp);
+ if (GetWindowPlacement(hwnd, &wp) && wp.showCmd == SW_SHOWMAXIMIZED) {
+ wp.showCmd = SW_SHOWNORMAL;
+ SetWindowPlacement(hwnd, &wp);
+ }
+ recurse = 0;
+}
+
+static int CALLBACK LogProc(HWND hwnd, UINT msg,
+ WPARAM wParam, LPARAM lParam)
+{
+ int i;
+
+ switch (msg) {
+ case WM_INITDIALOG:
+ {
+ char *str = dupprintf("%s Event Log", appname);
+ SetWindowText(hwnd, str);
+ sfree(str);
+ }
+ {
+ static int tabs[4] = { 78, 108 };
+ SendDlgItemMessage(hwnd, IDN_LIST, LB_SETTABSTOPS, 2,
+ (LPARAM) tabs);
+ }
+ for (i = 0; i < nevents; i++)
+ SendDlgItemMessage(hwnd, IDN_LIST, LB_ADDSTRING,
+ 0, (LPARAM) events[i]);
+ return 1;
+ case WM_COMMAND:
+ switch (LOWORD(wParam)) {
+ case IDOK:
+ case IDCANCEL:
+ logbox = NULL;
+ SetActiveWindow(GetParent(hwnd));
+ DestroyWindow(hwnd);
+ return 0;
+ case IDN_COPY:
+ if (HIWORD(wParam) == BN_CLICKED ||
+ HIWORD(wParam) == BN_DOUBLECLICKED) {
+ int selcount;
+ int *selitems;
+ selcount = SendDlgItemMessage(hwnd, IDN_LIST,
+ LB_GETSELCOUNT, 0, 0);
+ if (selcount == 0) { /* don't even try to copy zero items */
+ MessageBeep(0);
+ break;
+ }
+
+ selitems = snewn(selcount, int);
+ if (selitems) {
+ int count = SendDlgItemMessage(hwnd, IDN_LIST,
+ LB_GETSELITEMS,
+ selcount,
+ (LPARAM) selitems);
+ int i;
+ int size;
+ char *clipdata;
+ static unsigned char sel_nl[] = SEL_NL;
+
+ if (count == 0) { /* can't copy zero stuff */
+ MessageBeep(0);
+ break;
+ }
+
+ size = 0;
+ for (i = 0; i < count; i++)
+ size +=
+ strlen(events[selitems[i]]) + sizeof(sel_nl);
+
+ clipdata = snewn(size, char);
+ if (clipdata) {
+ char *p = clipdata;
+ for (i = 0; i < count; i++) {
+ char *q = events[selitems[i]];
+ int qlen = strlen(q);
+ memcpy(p, q, qlen);
+ p += qlen;
+ memcpy(p, sel_nl, sizeof(sel_nl));
+ p += sizeof(sel_nl);
+ }
+ write_aclip(NULL, clipdata, size, TRUE);
+ sfree(clipdata);
+ }
+ sfree(selitems);
+
+ for (i = 0; i < nevents; i++)
+ SendDlgItemMessage(hwnd, IDN_LIST, LB_SETSEL,
+ FALSE, i);
+ }
+ }
+ return 0;
+ }
+ return 0;
+ case WM_CLOSE:
+ logbox = NULL;
+ SetActiveWindow(GetParent(hwnd));
+ DestroyWindow(hwnd);
+ return 0;
+ }
+ return 0;
+}
+
+static int CALLBACK LicenceProc(HWND hwnd, UINT msg,
+ WPARAM wParam, LPARAM lParam)
+{
+ switch (msg) {
+ case WM_INITDIALOG:
+ {
+ char *str = dupprintf("%s Licence", appname);
+ SetWindowText(hwnd, str);
+ sfree(str);
+ }
+ return 1;
+ case WM_COMMAND:
+ switch (LOWORD(wParam)) {
+ case IDOK:
+ case IDCANCEL:
+ EndDialog(hwnd, 1);
+ return 0;
+ }
+ return 0;
+ case WM_CLOSE:
+ EndDialog(hwnd, 1);
+ return 0;
+ }
+ return 0;
+}
+
+static int CALLBACK AboutProc(HWND hwnd, UINT msg,
+ WPARAM wParam, LPARAM lParam)
+{
+ char *str;
+
+ switch (msg) {
+ case WM_INITDIALOG:
+ str = dupprintf("About %s", appname);
+ SetWindowText(hwnd, str);
+ sfree(str);
+ SetDlgItemText(hwnd, IDA_TEXT1, appname);
+ SetDlgItemText(hwnd, IDA_VERSION, ver);
+ return 1;
+ case WM_COMMAND:
+ switch (LOWORD(wParam)) {
+ case IDOK:
+ case IDCANCEL:
+ EndDialog(hwnd, TRUE);
+ return 0;
+ case IDA_LICENCE:
+ EnableWindow(hwnd, 0);
+ DialogBox(hinst, MAKEINTRESOURCE(IDD_LICENCEBOX),
+ hwnd, LicenceProc);
+ EnableWindow(hwnd, 1);
+ SetActiveWindow(hwnd);
+ return 0;
+
+ case IDA_WEB:
+ /* Load web browser */
+ ShellExecute(hwnd, "open",
+ "http://www.chiark.greenend.org.uk/~sgtatham/putty/",
+ 0, 0, SW_SHOWDEFAULT);
+ return 0;
+ }
+ return 0;
+ case WM_CLOSE:
+ EndDialog(hwnd, TRUE);
+ return 0;
+ }
+ return 0;
+}
+
+static int SaneDialogBox(HINSTANCE hinst,
+ LPCTSTR tmpl,
+ HWND hwndparent,
+ DLGPROC lpDialogFunc)
+{
+ WNDCLASS wc;
+ HWND hwnd;
+ MSG msg;
+ int flags;
+ int ret;
+ int gm;
+
+ wc.style = CS_DBLCLKS | CS_SAVEBITS | CS_BYTEALIGNWINDOW;
+ wc.lpfnWndProc = DefDlgProc;
+ wc.cbClsExtra = 0;
+ wc.cbWndExtra = DLGWINDOWEXTRA + 2*sizeof(LONG_PTR);
+ wc.hInstance = hinst;
+ wc.hIcon = NULL;
+ wc.hCursor = LoadCursor(NULL, IDC_ARROW);
+ wc.hbrBackground = (HBRUSH) (COLOR_BACKGROUND +1);
+ wc.lpszMenuName = NULL;
+ wc.lpszClassName = "PuTTYConfigBox";
+ RegisterClass(&wc);
+
+ hwnd = CreateDialog(hinst, tmpl, hwndparent, lpDialogFunc);
+
+ SetWindowLongPtr(hwnd, BOXFLAGS, 0); /* flags */
+ SetWindowLongPtr(hwnd, BOXRESULT, 0); /* result from SaneEndDialog */
+
+ while ((gm=GetMessage(&msg, NULL, 0, 0)) > 0) {
+ flags=GetWindowLongPtr(hwnd, BOXFLAGS);
+ if (!(flags & DF_END) && !IsDialogMessage(hwnd, &msg))
+ DispatchMessage(&msg);
+ if (flags & DF_END)
+ break;
+ }
+
+ if (gm == 0)
+ PostQuitMessage(msg.wParam); /* We got a WM_QUIT, pass it on */
+
+ ret=GetWindowLongPtr(hwnd, BOXRESULT);
+ DestroyWindow(hwnd);
+ return ret;
+}
+
+static void SaneEndDialog(HWND hwnd, int ret)
+{
+ SetWindowLongPtr(hwnd, BOXRESULT, ret);
+ SetWindowLongPtr(hwnd, BOXFLAGS, DF_END);
+}
+
+/*
+ * Null dialog procedure.
+ */
+static int CALLBACK NullDlgProc(HWND hwnd, UINT msg,
+ WPARAM wParam, LPARAM lParam)
+{
+ return 0;
+}
+
+enum {
+ IDCX_ABOUT = IDC_ABOUT,
+ IDCX_TVSTATIC,
+ IDCX_TREEVIEW,
+ IDCX_STDBASE,
+ IDCX_PANELBASE = IDCX_STDBASE + 32
+};
+
+struct treeview_faff {
+ HWND treeview;
+ HTREEITEM lastat[4];
+};
+
+static HTREEITEM treeview_insert(struct treeview_faff *faff,
+ int level, char *text, char *path)
+{
+ TVINSERTSTRUCT ins;
+ int i;
+ HTREEITEM newitem;
+ ins.hParent = (level > 0 ? faff->lastat[level - 1] : TVI_ROOT);
+ ins.hInsertAfter = faff->lastat[level];
+#if _WIN32_IE >= 0x0400 && defined NONAMELESSUNION
+#define INSITEM DUMMYUNIONNAME.item
+#else
+#define INSITEM item
+#endif
+ ins.INSITEM.mask = TVIF_TEXT | TVIF_PARAM;
+ ins.INSITEM.pszText = text;
+ ins.INSITEM.cchTextMax = strlen(text)+1;
+ ins.INSITEM.lParam = (LPARAM)path;
+ newitem = TreeView_InsertItem(faff->treeview, &ins);
+ if (level > 0)
+ TreeView_Expand(faff->treeview, faff->lastat[level - 1],
+ (level > 1 ? TVE_COLLAPSE : TVE_EXPAND));
+ faff->lastat[level] = newitem;
+ for (i = level + 1; i < 4; i++)
+ faff->lastat[i] = NULL;
+ return newitem;
+}
+
+/*
+ * Create the panelfuls of controls in the configuration box.
+ */
+static void create_controls(HWND hwnd, char *path)
+{
+ struct ctlpos cp;
+ int index;
+ int base_id;
+ struct winctrls *wc;
+
+ if (!path[0]) {
+ /*
+ * Here we must create the basic standard controls.
+ */
+ ctlposinit(&cp, hwnd, 3, 3, 235);
+ wc = &ctrls_base;
+ base_id = IDCX_STDBASE;
+ } else {
+ /*
+ * Otherwise, we're creating the controls for a particular
+ * panel.
+ */
+ ctlposinit(&cp, hwnd, 100, 3, 13);
+ wc = &ctrls_panel;
+ base_id = IDCX_PANELBASE;
+ }
+
+ for (index=-1; (index = ctrl_find_path(ctrlbox, path, index)) >= 0 ;) {
+ struct controlset *s = ctrlbox->ctrlsets[index];
+ winctrl_layout(&dp, wc, &cp, s, &base_id);
+ }
+}
+
+/*
+ * This function is the configuration box.
+ * (Being a dialog procedure, in general it returns 0 if the default
+ * dialog processing should be performed, and 1 if it should not.)
+ */
+static int CALLBACK GenericMainDlgProc(HWND hwnd, UINT msg,
+ WPARAM wParam, LPARAM lParam)
+{
+ HWND hw, treeview;
+ struct treeview_faff tvfaff;
+ int ret;
+
+ switch (msg) {
+ case WM_INITDIALOG:
+ dp.hwnd = hwnd;
+ create_controls(hwnd, ""); /* Open and Cancel buttons etc */
+ SetWindowText(hwnd, dp.wintitle);
+ SetWindowLongPtr(hwnd, GWLP_USERDATA, 0);
+ if (has_help())
+ SetWindowLongPtr(hwnd, GWL_EXSTYLE,
+ GetWindowLongPtr(hwnd, GWL_EXSTYLE) |
+ WS_EX_CONTEXTHELP);
+ else {
+ HWND item = GetDlgItem(hwnd, IDC_HELPBTN);
+ if (item)
+ DestroyWindow(item);
+ }
+ SendMessage(hwnd, WM_SETICON, (WPARAM) ICON_BIG,
+ (LPARAM) LoadIcon(hinst, MAKEINTRESOURCE(IDI_CFGICON)));
+ /*
+ * Centre the window.
+ */
+ { /* centre the window */
+ RECT rs, rd;
+
+ 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);
+ }
+
+ /*
+ * Create the tree view.
+ */
+ {
+ RECT r;
+ WPARAM font;
+ HWND tvstatic;
+
+ r.left = 3;
+ r.right = r.left + 95;
+ r.top = 3;
+ r.bottom = r.top + 10;
+ MapDialogRect(hwnd, &r);
+ tvstatic = CreateWindowEx(0, "STATIC", "Cate&gory:",
+ WS_CHILD | WS_VISIBLE,
+ r.left, r.top,
+ r.right - r.left, r.bottom - r.top,
+ hwnd, (HMENU) IDCX_TVSTATIC, hinst,
+ NULL);
+ font = SendMessage(hwnd, WM_GETFONT, 0, 0);
+ SendMessage(tvstatic, WM_SETFONT, font, MAKELPARAM(TRUE, 0));
+
+ r.left = 3;
+ r.right = r.left + 95;
+ r.top = 13;
+ r.bottom = r.top + 219;
+ MapDialogRect(hwnd, &r);
+ treeview = CreateWindowEx(WS_EX_CLIENTEDGE, WC_TREEVIEW, "",
+ WS_CHILD | WS_VISIBLE |
+ WS_TABSTOP | TVS_HASLINES |
+ TVS_DISABLEDRAGDROP | TVS_HASBUTTONS
+ | TVS_LINESATROOT |
+ TVS_SHOWSELALWAYS, r.left, r.top,
+ r.right - r.left, r.bottom - r.top,
+ hwnd, (HMENU) IDCX_TREEVIEW, hinst,
+ NULL);
+ font = SendMessage(hwnd, WM_GETFONT, 0, 0);
+ SendMessage(treeview, WM_SETFONT, font, MAKELPARAM(TRUE, 0));
+ tvfaff.treeview = treeview;
+ memset(tvfaff.lastat, 0, sizeof(tvfaff.lastat));
+ }
+
+ /*
+ * Set up the tree view contents.
+ */
+ {
+ HTREEITEM hfirst = NULL;
+ int i;
+ char *path = NULL;
+
+ for (i = 0; i < ctrlbox->nctrlsets; i++) {
+ struct controlset *s = ctrlbox->ctrlsets[i];
+ HTREEITEM item;
+ int j;
+ char *c;
+
+ if (!s->pathname[0])
+ continue;
+ j = path ? ctrl_path_compare(s->pathname, path) : 0;
+ if (j == INT_MAX)
+ continue; /* same path, nothing to add to tree */
+
+ /*
+ * We expect never to find an implicit path
+ * component. For example, we expect never to see
+ * A/B/C followed by A/D/E, because that would
+ * _implicitly_ create A/D. All our path prefixes
+ * are expected to contain actual controls and be
+ * selectable in the treeview; so we would expect
+ * to see A/D _explicitly_ before encountering
+ * A/D/E.
+ */
+ assert(j == ctrl_path_elements(s->pathname) - 1);
+
+ c = strrchr(s->pathname, '/');
+ if (!c)
+ c = s->pathname;
+ else
+ c++;
+
+ item = treeview_insert(&tvfaff, j, c, s->pathname);
+ if (!hfirst)
+ hfirst = item;
+
+ path = s->pathname;
+ }
+
+ /*
+ * Put the treeview selection on to the Session panel.
+ * This should also cause creation of the relevant
+ * controls.
+ */
+ TreeView_SelectItem(treeview, hfirst);
+ }
+
+ /*
+ * Set focus into the first available control.
+ */
+ {
+ int i;
+ struct winctrl *c;
+
+ for (i = 0; (c = winctrl_findbyindex(&ctrls_panel, i)) != NULL;
+ i++) {
+ if (c->ctrl) {
+ dlg_set_focus(c->ctrl, &dp);
+ break;
+ }
+ }
+ }
+
+ SetWindowLongPtr(hwnd, GWLP_USERDATA, 1);
+ return 0;
+ case WM_LBUTTONUP:
+ /*
+ * Button release should trigger WM_OK if there was a
+ * previous double click on the session list.
+ */
+ ReleaseCapture();
+ if (dp.ended)
+ SaneEndDialog(hwnd, dp.endresult ? 1 : 0);
+ break;
+ case WM_NOTIFY:
+ if (LOWORD(wParam) == IDCX_TREEVIEW &&
+ ((LPNMHDR) lParam)->code == TVN_SELCHANGED) {
+ HTREEITEM i =
+ TreeView_GetSelection(((LPNMHDR) lParam)->hwndFrom);
+ TVITEM item;
+ char buffer[64];
+
+ SendMessage (hwnd, WM_SETREDRAW, FALSE, 0);
+
+ item.hItem = i;
+ item.pszText = buffer;
+ item.cchTextMax = sizeof(buffer);
+ item.mask = TVIF_TEXT | TVIF_PARAM;
+ TreeView_GetItem(((LPNMHDR) lParam)->hwndFrom, &item);
+ {
+ /* Destroy all controls in the currently visible panel. */
+ int k;
+ HWND item;
+ struct winctrl *c;
+
+ while ((c = winctrl_findbyindex(&ctrls_panel, 0)) != NULL) {
+ for (k = 0; k < c->num_ids; k++) {
+ item = GetDlgItem(hwnd, c->base_id + k);
+ if (item)
+ DestroyWindow(item);
+ }
+ winctrl_rem_shortcuts(&dp, c);
+ winctrl_remove(&ctrls_panel, c);
+ sfree(c->data);
+ sfree(c);
+ }
+ }
+ create_controls(hwnd, (char *)item.lParam);
+
+ dlg_refresh(NULL, &dp); /* set up control values */
+
+ SendMessage (hwnd, WM_SETREDRAW, TRUE, 0);
+ InvalidateRect (hwnd, NULL, TRUE);
+
+ SetFocus(((LPNMHDR) lParam)->hwndFrom); /* ensure focus stays */
+ return 0;
+ }
+ break;
+ case WM_COMMAND:
+ case WM_DRAWITEM:
+ default: /* also handle drag list msg here */
+ /*
+ * Only process WM_COMMAND once the dialog is fully formed.
+ */
+ if (GetWindowLongPtr(hwnd, GWLP_USERDATA) == 1) {
+ ret = winctrl_handle_command(&dp, msg, wParam, lParam);
+ if (dp.ended && GetCapture() != hwnd)
+ SaneEndDialog(hwnd, dp.endresult ? 1 : 0);
+ } else
+ ret = 0;
+ return ret;
+ case WM_HELP:
+ if (!winctrl_context_help(&dp, hwnd,
+ ((LPHELPINFO)lParam)->iCtrlId))
+ MessageBeep(0);
+ break;
+ case WM_CLOSE:
+ quit_help(hwnd);
+ SaneEndDialog(hwnd, 0);
+ return 0;
+
+ /* Grrr Explorer will maximize Dialogs! */
+ case WM_SIZE:
+ if (wParam == SIZE_MAXIMIZED)
+ force_normal(hwnd);
+ return 0;
+
+ }
+ return 0;
+}
+
+void modal_about_box(HWND hwnd)
+{
+ EnableWindow(hwnd, 0);
+ DialogBox(hinst, MAKEINTRESOURCE(IDD_ABOUTBOX), hwnd, AboutProc);
+ EnableWindow(hwnd, 1);
+ SetActiveWindow(hwnd);
+}
+
+void show_help(HWND hwnd)
+{
+ launch_help(hwnd, NULL);
+}
+
+void defuse_showwindow(void)
+{
+ /*
+ * Work around the fact that the app's first call to ShowWindow
+ * will ignore the default in favour of the shell-provided
+ * setting.
+ */
+ {
+ HWND hwnd;
+ hwnd = CreateDialog(hinst, MAKEINTRESOURCE(IDD_ABOUTBOX),
+ NULL, NullDlgProc);
+ ShowWindow(hwnd, SW_HIDE);
+ SetActiveWindow(hwnd);
+ DestroyWindow(hwnd);
+ }
+}
+
+int do_config(void)
+{
+ int ret;
+
+ ctrlbox = ctrl_new_box();
+ setup_config_box(ctrlbox, FALSE, 0, 0);
+ win_setup_config_box(ctrlbox, &dp.hwnd, has_help(), FALSE, 0);
+ 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 Configuration", appname);
+ dp.errtitle = dupprintf("%s Error", appname);
+ dp.data = &cfg;
+ dlg_auto_set_fixed_pitch_flag(&dp);
+ dp.shortcuts['g'] = TRUE; /* the treeview: `Cate&gory' */
+
+ ret =
+ SaneDialogBox(hinst, MAKEINTRESOURCE(IDD_MAINBOX), NULL,
+ GenericMainDlgProc);
+
+ ctrl_free_box(ctrlbox);
+ winctrl_cleanup(&ctrls_panel);
+ winctrl_cleanup(&ctrls_base);
+ dp_cleanup(&dp);
+
+ return ret;
+}
+
+int do_reconfig(HWND hwnd, int protcfginfo)
+{
+ Config backup_cfg;
+ int ret;
+
+ backup_cfg = cfg; /* structure copy */
+
+ ctrlbox = ctrl_new_box();
+ setup_config_box(ctrlbox, TRUE, cfg.protocol, protcfginfo);
+ win_setup_config_box(ctrlbox, &dp.hwnd, has_help(), TRUE,
+ cfg.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;
+ dlg_auto_set_fixed_pitch_flag(&dp);
+ dp.shortcuts['g'] = TRUE; /* the treeview: `Cate&gory' */
+
+ ret = SaneDialogBox(hinst, MAKEINTRESOURCE(IDD_MAINBOX), NULL,
+ GenericMainDlgProc);
+
+ ctrl_free_box(ctrlbox);
+ winctrl_cleanup(&ctrls_base);
+ winctrl_cleanup(&ctrls_panel);
+ dp_cleanup(&dp);
+
+ if (!ret)
+ cfg = backup_cfg; /* structure copy */
+
+ return ret;
+}
+
+void logevent(void *frontend, const char *string)
+{
+ char timebuf[40];
+ struct tm tm;
+
+ log_eventlog(logctx, string);
+
+ if (nevents >= negsize) {
+ negsize += 64;
+ events = sresize(events, negsize, char *);
+ }
+
+ tm=ltime();
+ strftime(timebuf, sizeof(timebuf), "%Y-%m-%d %H:%M:%S\t", &tm);
+
+ events[nevents] = snewn(strlen(timebuf) + strlen(string) + 1, char);
+ strcpy(events[nevents], timebuf);
+ strcat(events[nevents], string);
+ if (logbox) {
+ int count;
+ SendDlgItemMessage(logbox, IDN_LIST, LB_ADDSTRING,
+ 0, (LPARAM) events[nevents]);
+ count = SendDlgItemMessage(logbox, IDN_LIST, LB_GETCOUNT, 0, 0);
+ SendDlgItemMessage(logbox, IDN_LIST, LB_SETTOPINDEX, count - 1, 0);
+ }
+ nevents++;
+}
+
+void showeventlog(HWND hwnd)
+{
+ if (!logbox) {
+ logbox = CreateDialog(hinst, MAKEINTRESOURCE(IDD_LOGBOX),
+ hwnd, LogProc);
+ ShowWindow(logbox, SW_SHOWNORMAL);
+ }
+ SetActiveWindow(logbox);
+}
+
+void showabout(HWND hwnd)
+{
+ DialogBox(hinst, MAKEINTRESOURCE(IDD_ABOUTBOX), hwnd, AboutProc);
+}
+
+int verify_ssh_host_key(void *frontend, char *host, int port, char *keytype,
+ char *keystr, char *fingerprint,
+ void (*callback)(void *ctx, int result), void *ctx)
+{
+ int ret;
+
+ static const char absentmsg[] =
+ "The server's host key is not cached in the registry. You\n"
+ "have no guarantee that the server is the computer you\n"
+ "think it is.\n"
+ "The server's %s key fingerprint is:\n"
+ "%s\n"
+ "If you trust this host, hit Yes to add the key to\n"
+ "%s's cache and carry on connecting.\n"
+ "If you want to carry on connecting just once, without\n"
+ "adding the key to the cache, hit No.\n"
+ "If you do not trust this host, hit Cancel to abandon the\n"
+ "connection.\n";
+
+ static const char wrongmsg[] =
+ "WARNING - POTENTIAL SECURITY BREACH!\n"
+ "\n"
+ "The server's host key does not match the one %s has\n"
+ "cached in the registry. This means that either the\n"
+ "server administrator has changed the host key, or you\n"
+ "have actually connected to another computer pretending\n"
+ "to be the server.\n"
+ "The new %s key fingerprint is:\n"
+ "%s\n"
+ "If you were expecting this change and trust the new key,\n"
+ "hit Yes to update %s's cache and continue connecting.\n"
+ "If you want to carry on connecting but without updating\n"
+ "the cache, hit No.\n"
+ "If you want to abandon the connection completely, hit\n"
+ "Cancel. Hitting Cancel is the ONLY guaranteed safe\n" "choice.\n";
+
+ static const char mbtitle[] = "%s Security Alert";
+
+ /*
+ * Verify the key against the registry.
+ */
+ ret = verify_host_key(host, port, keytype, keystr);
+
+ if (ret == 0) /* success - key matched OK */
+ return 1;
+ else if (ret == 2) { /* key was different */
+ int mbret;
+ char *text = dupprintf(wrongmsg, appname, keytype, fingerprint,
+ appname);
+ char *caption = dupprintf(mbtitle, appname);
+ mbret = message_box(text, caption,
+ MB_ICONWARNING | MB_YESNOCANCEL | MB_DEFBUTTON3,
+ HELPCTXID(errors_hostkey_changed));
+ assert(mbret==IDYES || mbret==IDNO || mbret==IDCANCEL);
+ sfree(text);
+ sfree(caption);
+ if (mbret == IDYES) {
+ store_host_key(host, port, keytype, keystr);
+ return 1;
+ } else if (mbret == IDNO)
+ return 1;
+ } else if (ret == 1) { /* key was absent */
+ int mbret;
+ char *text = dupprintf(absentmsg, keytype, fingerprint, appname);
+ char *caption = dupprintf(mbtitle, appname);
+ mbret = message_box(text, caption,
+ MB_ICONWARNING | MB_YESNOCANCEL | MB_DEFBUTTON3,
+ HELPCTXID(errors_hostkey_absent));
+ assert(mbret==IDYES || mbret==IDNO || mbret==IDCANCEL);
+ sfree(text);
+ sfree(caption);
+ if (mbret == IDYES) {
+ store_host_key(host, port, keytype, keystr);
+ return 1;
+ } else if (mbret == IDNO)
+ return 1;
+ }
+ return 0; /* abandon the connection */
+}
+
+/*
+ * Ask whether the selected algorithm is acceptable (since it was
+ * below the configured 'warn' threshold).
+ */
+int askalg(void *frontend, const char *algtype, const char *algname,
+ void (*callback)(void *ctx, int result), void *ctx)
+{
+ static const char mbtitle[] = "%s Security Alert";
+ static const char msg[] =
+ "The first %s supported by the server\n"
+ "is %.64s, which is below the configured\n"
+ "warning threshold.\n"
+ "Do you want to continue with this connection?\n";
+ char *message, *title;
+ int mbret;
+
+ message = dupprintf(msg, algtype, algname);
+ title = dupprintf(mbtitle, appname);
+ mbret = MessageBox(NULL, message, title,
+ MB_ICONWARNING | MB_YESNO | MB_DEFBUTTON2);
+ socket_reselect_all();
+ sfree(message);
+ sfree(title);
+ if (mbret == IDYES)
+ return 1;
+ else
+ return 0;
+}
+
+/*
+ * 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,
+ 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"
+ "append your session log to the end of it,\n"
+ "or disable session logging for this session.\n"
+ "Hit Yes to wipe the file, No to append to it,\n"
+ "or Cancel to disable logging.";
+ char *message;
+ char *mbtitle;
+ int mbret;
+
+ 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);
+
+ socket_reselect_all();
+
+ sfree(message);
+ sfree(mbtitle);
+
+ if (mbret == IDYES)
+ return 2;
+ else if (mbret == IDNO)
+ return 1;
+ else
+ return 0;
+}
+
+/*
+ * Warn about the obsolescent key file format.
+ *
+ * Uniquely among these functions, this one does _not_ expect a
+ * frontend handle. This means that if PuTTY is ported to a
+ * platform which requires frontend handles, this function will be
+ * an anomaly. Fortunately, the problem it addresses will not have
+ * been present on that platform, so it can plausibly be
+ * implemented as an empty function.
+ */
+void old_keyfile_warning(void)
+{
+ static const char mbtitle[] = "%s Key File Warning";
+ static const char message[] =
+ "You are loading an SSH-2 private key which has an\n"
+ "old version of the file format. This means your key\n"
+ "file is not fully tamperproof. Future versions of\n"
+ "%s may stop supporting this private key format,\n"
+ "so we recommend you convert your key to the new\n"
+ "format.\n"
+ "\n"
+ "You can perform this conversion by loading the key\n"
+ "into PuTTYgen and then saving it again.";
+
+ char *msg, *title;
+ msg = dupprintf(message, appname);
+ title = dupprintf(mbtitle, appname);
+
+ MessageBox(NULL, msg, title, MB_OK);
+
+ socket_reselect_all();
+
+ sfree(msg);
+ sfree(title);
+}
--- /dev/null
+/*
+ * window.c - the PuTTY(tel) main program, which runs a PuTTY terminal
+ * emulator and backend in a window.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <time.h>
+#include <limits.h>
+#include <assert.h>
+
+#ifndef NO_MULTIMON
+#define COMPILE_MULTIMON_STUBS
+#endif
+
+#define PUTTY_DO_GLOBALS /* actually _define_ globals */
+#include "putty.h"
+#include "terminal.h"
+#include "storage.h"
+#include "win_res.h"
+
+#ifndef NO_MULTIMON
+#include <multimon.h>
+#endif
+
+#include <imm.h>
+#include <commctrl.h>
+#include <richedit.h>
+#include <mmsystem.h>
+
+/* From MSDN: In the WM_SYSCOMMAND message, the four low-order bits of
+ * wParam are used by Windows, and should be masked off, so we shouldn't
+ * attempt to store information in them. Hence all these identifiers have
+ * the low 4 bits clear. Also, identifiers should < 0xF000. */
+
+#define IDM_SHOWLOG 0x0010
+#define IDM_NEWSESS 0x0020
+#define IDM_DUPSESS 0x0030
+#define IDM_RESTART 0x0040
+#define IDM_RECONF 0x0050
+#define IDM_CLRSB 0x0060
+#define IDM_RESET 0x0070
+#define IDM_HELP 0x0140
+#define IDM_ABOUT 0x0150
+#define IDM_SAVEDSESS 0x0160
+#define IDM_COPYALL 0x0170
+#define IDM_FULLSCREEN 0x0180
+#define IDM_PASTE 0x0190
+#define IDM_SPECIALSEP 0x0200
+
+#define IDM_SPECIAL_MIN 0x0400
+#define IDM_SPECIAL_MAX 0x0800
+
+#define IDM_SAVED_MIN 0x1000
+#define IDM_SAVED_MAX 0x5000
+#define MENU_SAVED_STEP 16
+/* Maximum number of sessions on saved-session submenu */
+#define MENU_SAVED_MAX ((IDM_SAVED_MAX-IDM_SAVED_MIN) / MENU_SAVED_STEP)
+
+#define WM_IGNORE_CLIP (WM_APP + 2)
+#define WM_FULLSCR_ON_MAX (WM_APP + 3)
+#define WM_AGENT_CALLBACK (WM_APP + 4)
+#define WM_GOT_CLIPDATA (WM_APP + 6)
+
+/* Needed for Chinese support and apparently not always defined. */
+#ifndef VK_PROCESSKEY
+#define VK_PROCESSKEY 0xE5
+#endif
+
+/* Mouse wheel support. */
+#ifndef WM_MOUSEWHEEL
+#define WM_MOUSEWHEEL 0x020A /* not defined in earlier SDKs */
+#endif
+#ifndef WHEEL_DELTA
+#define WHEEL_DELTA 120
+#endif
+
+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 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 int is_full_screen(void);
+static void make_full_screen(void);
+static void clear_full_screen(void);
+static void flip_full_screen(void);
+static int process_clipdata(HGLOBAL clipdata, int unicode);
+
+/* Window layout information */
+static void reset_window(int);
+static int extra_width, extra_height;
+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;
+
+static int kbd_codepage;
+
+static void *ldisc;
+static Backend *back;
+static void *backhandle;
+
+static struct unicode_data ucsdata;
+static int must_close_session, session_closed;
+static int reconfiguring = FALSE;
+
+static const struct telnet_special *specials = NULL;
+static HMENU specials_menu = NULL;
+static int n_specials = 0;
+
+static wchar_t *clipboard_contents;
+static size_t clipboard_length;
+
+#define TIMING_TIMER_ID 1234
+static long timing_next_time;
+
+static struct {
+ HMENU menu;
+} popup_menus[2];
+enum { SYSMENU, CTXMENU };
+static HMENU savedsess_menu;
+
+Config cfg; /* exported to windlg.c */
+
+static struct sesslist sesslist; /* for saved-session menu */
+
+struct agent_callback {
+ void (*callback)(void *, void *, int);
+ void *callback_ctx;
+ void *data;
+ int len;
+};
+
+#define FONT_NORMAL 0
+#define FONT_BOLD 1
+#define FONT_UNDERLINE 2
+#define FONT_BOLDUND 3
+#define FONT_WIDE 0x04
+#define FONT_HIGH 0x08
+#define FONT_NARROW 0x10
+
+#define FONT_OEM 0x20
+#define FONT_OEMBOLD 0x21
+#define FONT_OEMUND 0x22
+#define FONT_OEMBOLDUND 0x23
+
+#define FONT_MAXNO 0x2F
+#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;
+static enum {
+ UND_LINE, UND_FONT
+} und_mode;
+static int descent;
+
+#define NCFGCOLOURS 22
+#define NEXTCOLOURS 240
+#define NALLCOLOURS (NCFGCOLOURS + NEXTCOLOURS)
+static COLORREF colours[NALLCOLOURS];
+static HPALETTE pal;
+static LPLOGPALETTE logpal;
+static RGBTRIPLE defpal[NALLCOLOURS];
+
+static HBITMAP caretbm;
+
+static int dbltime, lasttime, lastact;
+static Mouse_Button lastbtn;
+
+/* this allows xterm-style mouse handling. */
+static int send_raw_mouse = 0;
+static int wheel_accumulator = 0;
+
+static int busy_status = BUSY_NOT;
+
+static char *window_name, *icon_name;
+
+static int compose_state = 0;
+
+static UINT wm_mousewheel = WM_MOUSEWHEEL;
+
+/* Dummy routine, only required in plink. */
+void ldisc_update(void *frontend, int echo, int edit)
+{
+}
+
+char *get_ttymode(void *frontend, const char *mode)
+{
+ return term_get_ttymode(term, mode);
+}
+
+static void start_backend(void)
+{
+ const char *error;
+ char msg[1024], *title;
+ char *realhost;
+ int i;
+
+ /*
+ * 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);
+ 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);
+ 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);
+ MessageBox(NULL, msg, str, MB_ICONERROR | MB_OK);
+ sfree(str);
+ exit(0);
+ }
+ window_name = icon_name = NULL;
+ if (*cfg.wintitle) {
+ title = cfg.wintitle;
+ } else {
+ sprintf(msg, "%s - %s", realhost, appname);
+ title = msg;
+ }
+ sfree(realhost);
+ set_title(NULL, title);
+ set_icon(NULL, title);
+
+ /*
+ * Connect the terminal to the backend for resize purposes.
+ */
+ term_provide_resize_fn(term, back->size, backhandle);
+
+ /*
+ * Set up a line discipline.
+ */
+ ldisc = ldisc_create(&cfg, 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
+ * as the menu item ends up not being there, we don't care
+ * whether it was us who removed it or not!)
+ */
+ 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)
+{
+ char morestuff[100];
+ int i;
+
+ session_closed = TRUE;
+ sprintf(morestuff, "%.70s (inactive)", appname);
+ set_icon(NULL, morestuff);
+ set_title(NULL, morestuff);
+
+ if (ldisc) {
+ ldisc_free(ldisc);
+ ldisc = NULL;
+ }
+ if (back) {
+ back->free(backhandle);
+ backhandle = NULL;
+ back = NULL;
+ term_provide_resize_fn(term, NULL, NULL);
+ update_specials_menu(NULL);
+ }
+
+ /*
+ * Show the Restart Session menu item. Do a precautionary
+ * delete first to ensure we never end up with more than one.
+ */
+ 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;
+ MSG msg;
+ HRESULT hr;
+ int guess_width, guess_height;
+
+ hinst = inst;
+ hwnd = NULL;
+ flags = FLAG_VERBOSE | FLAG_INTERACTIVE;
+
+ sk_init();
+
+ InitCommonControls();
+
+ /* Ensure a Maximize setting in Explorer doesn't maximise the
+ * config box. */
+ defuse_showwindow();
+
+ if (!init_winver())
+ {
+ char *str = dupprintf("%s Fatal Error", appname);
+ MessageBox(NULL, "Windows refuses to report a version",
+ str, MB_OK | MB_ICONEXCLAMATION);
+ sfree(str);
+ return 1;
+ }
+
+ /*
+ * If we're running a version of Windows that doesn't support
+ * WM_MOUSEWHEEL, find out what message number we should be
+ * using instead.
+ */
+ if (osVersion.dwMajorVersion < 4 ||
+ (osVersion.dwMajorVersion == 4 &&
+ osVersion.dwPlatformId != VER_PLATFORM_WIN32_NT))
+ wm_mousewheel = RegisterWindowMessage("MSWHEEL_ROLLMSG");
+
+ init_help();
+
+ init_flashwindow();
+
+ /*
+ * Initialize COM.
+ */
+ hr = CoInitialize(NULL);
+ if (hr != S_OK && hr != S_FALSE) {
+ char *str = dupprintf("%s Fatal Error", appname);
+ MessageBox(NULL, "Failed to initialize COM subsystem",
+ str, MB_OK | MB_ICONEXCLAMATION);
+ sfree(str);
+ return 1;
+ }
+
+ /*
+ * Process the command line.
+ */
+ {
+ char *p;
+ int got_host = 0;
+ /* 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;
+
+ default_protocol = be_default_protocol;
+ /* Find the appropriate default port. */
+ {
+ Backend *b = backend_from_proto(default_protocol);
+ default_port = 0; /* illegal */
+ if (b)
+ default_port = b->default_port;
+ }
+ cfg.logtype = LGTYP_NONE;
+
+ do_defaults(NULL, &cfg);
+
+ p = cmdline;
+
+ /*
+ * Process a couple of command-line options which are more
+ * easily dealt with before the line is broken up into words.
+ * These are the old-fashioned but convenient @sessionname and
+ * the internal-use-only &sharedmemoryhandle, neither of which
+ * are combined with anything else.
+ */
+ while (*p && isspace(*p))
+ p++;
+ if (*p == '@') {
+ /*
+ * An initial @ means that the whole of the rest of the
+ * command line should be treated as the name of a saved
+ * session, with _no quoting or escaping_. This makes it a
+ * very convenient means of automated saved-session
+ * launching, via IDM_SAVEDSESS or Windows 7 jump lists.
+ */
+ 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()) {
+ 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.
+ */
+ HANDLE filemap;
+ Config *cp;
+ if (sscanf(p + 1, "%p", &filemap) == 1 &&
+ (cp = MapViewOfFile(filemap, FILE_MAP_READ,
+ 0, 0, sizeof(Config))) != NULL) {
+ cfg = *cp;
+ UnmapViewOfFile(cp);
+ CloseHandle(filemap);
+ } else if (!do_config()) {
+ cleanup_exit(0);
+ }
+ allow_launch = TRUE;
+ } else {
+ /*
+ * Otherwise, break up the command line and deal with
+ * it sensibly.
+ */
+ int argc, i;
+ char **argv;
+
+ split_into_argv(cmdline, &argc, &argv, NULL);
+
+ for (i = 0; i < argc; i++) {
+ char *p = argv[i];
+ int ret;
+
+ ret = cmdline_process_param(p, i+1<argc?argv[i+1]:NULL,
+ 1, &cfg);
+ if (ret == -2) {
+ cmdline_error("option \"%s\" requires an argument", p);
+ } else if (ret == 2) {
+ i++; /* skip next argument */
+ } else if (ret == 1) {
+ continue; /* nothing further needs doing */
+ } else if (!strcmp(p, "-cleanup") ||
+ !strcmp(p, "-cleanup-during-uninstall")) {
+ /*
+ * `putty -cleanup'. Remove all registry
+ * entries associated with PuTTY, and also find
+ * and delete the random seed file.
+ */
+ char *s1, *s2;
+ /* Are we being invoked from an uninstaller? */
+ if (!strcmp(p, "-cleanup-during-uninstall")) {
+ s1 = dupprintf("Remove saved sessions and random seed file?\n"
+ "\n"
+ "If you hit Yes, ALL Registry entries associated\n"
+ "with %s will be removed, as well as the\n"
+ "random seed file. THIS PROCESS WILL\n"
+ "DESTROY YOUR SAVED SESSIONS.\n"
+ "(This only affects the currently logged-in user.)\n"
+ "\n"
+ "If you hit No, uninstallation will proceed, but\n"
+ "saved sessions etc will be left on the machine.",
+ appname);
+ s2 = dupprintf("%s Uninstallation", appname);
+ } else {
+ s1 = dupprintf("This procedure will remove ALL Registry entries\n"
+ "associated with %s, and will also remove\n"
+ "the random seed file. (This only affects the\n"
+ "currently logged-in user.)\n"
+ "\n"
+ "THIS PROCESS WILL DESTROY YOUR SAVED SESSIONS.\n"
+ "Are you really sure you want to continue?",
+ appname);
+ s2 = dupprintf("%s Warning", appname);
+ }
+ if (message_box(s1, s2,
+ MB_YESNO | MB_ICONWARNING | MB_DEFBUTTON2,
+ HELPCTXID(option_cleanup)) == IDYES) {
+ cleanup_all();
+ }
+ sfree(s1);
+ sfree(s2);
+ exit(0);
+ } else if (!strcmp(p, "-pgpfp")) {
+ pgp_fingerprints();
+ exit(1);
+ } else if (*p != '-') {
+ char *q = p;
+ if (got_host) {
+ /*
+ * 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", p, 1, &cfg);
+ assert(ret == 2);
+ } else if (!strncmp(q, "telnet:", 7)) {
+ /*
+ * If the hostname starts with "telnet:",
+ * set the protocol to Telnet and process
+ * the string as a Telnet URL.
+ */
+ char c;
+
+ q += 7;
+ if (q[0] == '/' && q[1] == '/')
+ q += 2;
+ cfg.protocol = PROT_TELNET;
+ p = q;
+ while (*p && *p != ':' && *p != '/')
+ p++;
+ c = *p;
+ if (*p)
+ *p++ = '\0';
+ if (c == ':')
+ cfg.port = atoi(p);
+ else
+ cfg.port = -1;
+ strncpy(cfg.host, q, sizeof(cfg.host) - 1);
+ cfg.host[sizeof(cfg.host) - 1] = '\0';
+ got_host = 1;
+ } else {
+ /*
+ * Otherwise, treat this argument as a host
+ * name.
+ */
+ while (*p && !isspace(*p))
+ p++;
+ if (*p)
+ *p++ = '\0';
+ strncpy(cfg.host, q, sizeof(cfg.host) - 1);
+ cfg.host[sizeof(cfg.host) - 1] = '\0';
+ got_host = 1;
+ }
+ } else {
+ cmdline_error("unknown option \"%s\"", p);
+ }
+ }
+ }
+
+ cmdline_run_saved(&cfg);
+
+ if (loaded_session || got_host)
+ allow_launch = TRUE;
+
+ if ((!allow_launch || !cfg_launchable(&cfg)) && !do_config()) {
+ cleanup_exit(0);
+ }
+
+ /*
+ * 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. In
+ * order to protect IPv6 address literals against this
+ * treatment, we do not do this if there's _more_ than one
+ * colon.
+ */
+ {
+ char *c = strchr(cfg.host, ':');
+
+ if (c) {
+ char *d = strchr(c+1, ':');
+ if (!d)
+ *c = '\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 (!prev) {
+ wndclass.style = 0;
+ wndclass.lpfnWndProc = WndProc;
+ wndclass.cbClsExtra = 0;
+ wndclass.cbWndExtra = 0;
+ wndclass.hInstance = inst;
+ wndclass.hIcon = LoadIcon(inst, MAKEINTRESOURCE(IDI_MAINICON));
+ wndclass.hCursor = LoadCursor(NULL, IDC_IBEAM);
+ wndclass.hbrBackground = NULL;
+ wndclass.lpszMenuName = NULL;
+ wndclass.lpszClassName = appname;
+
+ RegisterClass(&wndclass);
+ }
+
+ memset(&ucsdata, 0, sizeof(ucsdata));
+
+ cfgtopalette();
+
+ /*
+ * Guess some defaults for the window size. This all gets
+ * updated later, so we don't really care too much. However, we
+ * do want the font width/height guesses to correspond to a
+ * large font rather than a small one...
+ */
+
+ font_width = 10;
+ font_height = 20;
+ extra_width = 25;
+ extra_height = 28;
+ guess_width = extra_width + font_width * cfg.width;
+ guess_height = extra_height + font_height * cfg.height;
+ {
+ RECT r;
+ get_fullscreen_rect(&r);
+ if (guess_width > r.right - r.left)
+ guess_width = r.right - r.left;
+ if (guess_height > r.bottom - r.top)
+ guess_height = r.bottom - r.top;
+ }
+
+ {
+ int winmode = WS_OVERLAPPEDWINDOW | WS_VSCROLL;
+ int exwinmode = 0;
+ if (!cfg.scrollbar)
+ winmode &= ~(WS_VSCROLL);
+ if (cfg.resize_action == RESIZE_DISABLED)
+ winmode &= ~(WS_THICKFRAME | WS_MAXIMIZEBOX);
+ if (cfg.alwaysontop)
+ exwinmode |= WS_EX_TOPMOST;
+ if (cfg.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 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_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);
+
+ /*
+ * Correct the guesses for extra_{width,height}.
+ */
+ {
+ RECT cr, wr;
+ GetWindowRect(hwnd, &wr);
+ GetClientRect(hwnd, &cr);
+ offset_width = offset_height = cfg.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;
+ }
+
+ /*
+ * Resize the window, now we know what size we _really_ want it
+ * to be.
+ */
+ guess_width = extra_width + font_width * term->cols;
+ guess_height = extra_height + font_height * term->rows;
+ SetWindowPos(hwnd, NULL, 0, 0, guess_width, guess_height,
+ SWP_NOMOVE | SWP_NOREDRAW | SWP_NOZORDER);
+
+ /*
+ * Set up a caret bitmap, with no content.
+ */
+ {
+ char *bits;
+ int size = (font_width + 15) / 16 * 2 * font_height;
+ bits = snewn(size, char);
+ memset(bits, 0, size);
+ caretbm = CreateBitmap(font_width, font_height, 1, 1, bits);
+ sfree(bits);
+ }
+ CreateCaret(hwnd, caretbm, font_width, font_height);
+
+ /*
+ * Initialise the scroll bar.
+ */
+ {
+ SCROLLINFO si;
+
+ si.cbSize = sizeof(si);
+ si.fMask = SIF_ALL | SIF_DISABLENOSCROLL;
+ si.nMin = 0;
+ si.nMax = term->rows - 1;
+ si.nPage = term->rows;
+ si.nPos = 0;
+ SetScrollInfo(hwnd, SB_VERT, &si, FALSE);
+ }
+
+ /*
+ * Prepare the mouse handler.
+ */
+ lastact = MA_NOTHING;
+ lastbtn = MBT_NOTHING;
+ dbltime = GetDoubleClickTime();
+
+ /*
+ * Set up the session-control options on the system menu.
+ */
+ {
+ HMENU m;
+ int j;
+ char *str;
+
+ popup_menus[SYSMENU].menu = GetSystemMenu(hwnd, FALSE);
+ popup_menus[CTXMENU].menu = CreatePopupMenu();
+ AppendMenu(popup_menus[CTXMENU].menu, MF_ENABLED, IDM_PASTE, "&Paste");
+
+ savedsess_menu = CreateMenu();
+ get_sesslist(&sesslist, TRUE);
+ update_savedsess_menu();
+
+ for (j = 0; j < lenof(popup_menus); j++) {
+ m = popup_menus[j].menu;
+
+ AppendMenu(m, MF_SEPARATOR, 0, 0);
+ AppendMenu(m, MF_ENABLED, IDM_SHOWLOG, "&Event Log");
+ AppendMenu(m, MF_SEPARATOR, 0, 0);
+ AppendMenu(m, MF_ENABLED, IDM_NEWSESS, "Ne&w Session...");
+ AppendMenu(m, MF_ENABLED, IDM_DUPSESS, "&Duplicate Session");
+ AppendMenu(m, MF_POPUP | MF_ENABLED, (UINT) savedsess_menu,
+ "Sa&ved Sessions");
+ AppendMenu(m, MF_ENABLED, IDM_RECONF, "Chan&ge Settings...");
+ 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, 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);
+ sfree(str);
+ }
+ }
+
+ start_backend();
+
+ /*
+ * Set up the initial input locale.
+ */
+ set_input_locale(GetKeyboardLayout(0));
+
+ /*
+ * Finally show the window!
+ */
+ ShowWindow(hwnd, show);
+ SetForegroundWindow(hwnd);
+
+ /*
+ * Set the palette up.
+ */
+ pal = NULL;
+ logpal = NULL;
+ init_palette();
+
+ term_set_focus(term, GetForegroundWindow() == hwnd);
+ UpdateWindow(hwnd);
+
+ while (1) {
+ HANDLE *handles;
+ int nhandles, n;
+
+ handles = handle_get_events(&nhandles);
+
+ n = MsgWaitForMultipleObjects(nhandles, handles, FALSE, INFINITE,
+ 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 (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();
+ }
+
+ finished:
+ cleanup_exit(msg.wParam); /* this doesn't return... */
+ return msg.wParam; /* ... but optimiser doesn't know */
+}
+
+/*
+ * Clean up and exit.
+ */
+void cleanup_exit(int code)
+{
+ /*
+ * Clean up.
+ */
+ deinit_fonts();
+ sfree(logpal);
+ if (pal)
+ DeleteObject(pal);
+ sk_cleanup();
+
+ if (cfg.protocol == PROT_SSH) {
+ random_save_seed();
+#ifdef MSCRYPTOAPI
+ crypto_wrapup();
+#endif
+ }
+ shutdown_help();
+
+ /* Clean up COM. */
+ CoUninitialize();
+
+ exit(code);
+}
+
+/*
+ * Set up, or shut down, an AsyncSelect. Called from winnet.c.
+ */
+char *do_select(SOCKET skt, int startup)
+{
+ int msg, events;
+ if (startup) {
+ msg = WM_NETEVENT;
+ events = (FD_CONNECT | FD_READ | FD_WRITE |
+ FD_OOB | FD_CLOSE | FD_ACCEPT);
+ } else {
+ msg = events = 0;
+ }
+ if (!hwnd)
+ return "do_select(): internal error (hwnd==NULL)";
+ if (p_WSAAsyncSelect(skt, hwnd, msg, events) == SOCKET_ERROR) {
+ switch (p_WSAGetLastError()) {
+ case WSAENETDOWN:
+ return "Network is down";
+ default:
+ return "WSAAsyncSelect(): unknown error";
+ }
+ }
+ return NULL;
+}
+
+/*
+ * Refresh the saved-session submenu from `sesslist'.
+ */
+static void update_savedsess_menu(void)
+{
+ int i;
+ while (DeleteMenu(savedsess_menu, 0, MF_BYPOSITION)) ;
+ /* skip sesslist.sessions[0] == Default Settings */
+ for (i = 1;
+ i < ((sesslist.nsessions <= MENU_SAVED_MAX+1) ? sesslist.nsessions
+ : MENU_SAVED_MAX+1);
+ i++)
+ AppendMenu(savedsess_menu, MF_ENABLED,
+ IDM_SAVED_MIN + (i-1)*MENU_SAVED_STEP,
+ sesslist.sessions[i]);
+ if (sesslist.nsessions <= 1)
+ AppendMenu(savedsess_menu, MF_GRAYED, IDM_SAVED_MIN, "(No sessions)");
+}
+
+/*
+ * Update the Special Commands submenu.
+ */
+void update_specials_menu(void *frontend)
+{
+ HMENU new_menu;
+ int i, j;
+
+ if (back)
+ specials = back->get_specials(backhandle);
+ else
+ specials = NULL;
+
+ if (specials) {
+ /* We can't use Windows to provide a stack for submenus, so
+ * here's a lame "stack" that will do for now. */
+ HMENU saved_menu = NULL;
+ int nesting = 1;
+ new_menu = CreatePopupMenu();
+ for (i = 0; nesting > 0; i++) {
+ assert(IDM_SPECIAL_MIN + 0x10 * i < IDM_SPECIAL_MAX);
+ switch (specials[i].code) {
+ case TS_SEP:
+ AppendMenu(new_menu, MF_SEPARATOR, 0, 0);
+ break;
+ case TS_SUBMENU:
+ assert(nesting < 2);
+ nesting++;
+ saved_menu = new_menu; /* XXX lame stacking */
+ new_menu = CreatePopupMenu();
+ AppendMenu(saved_menu, MF_POPUP | MF_ENABLED,
+ (UINT) new_menu, specials[i].name);
+ break;
+ case TS_EXITMENU:
+ nesting--;
+ if (nesting) {
+ new_menu = saved_menu; /* XXX lame stacking */
+ saved_menu = NULL;
+ }
+ break;
+ default:
+ AppendMenu(new_menu, MF_ENABLED, IDM_SPECIAL_MIN + 0x10 * i,
+ specials[i].name);
+ break;
+ }
+ }
+ /* Squirrel the highest special. */
+ n_specials = i - 1;
+ } else {
+ new_menu = NULL;
+ n_specials = 0;
+ }
+
+ for (j = 0; j < lenof(popup_menus); j++) {
+ if (specials_menu) {
+ /* XXX does this free up all submenus? */
+ DeleteMenu(popup_menus[j].menu, (UINT)specials_menu, MF_BYCOMMAND);
+ DeleteMenu(popup_menus[j].menu, IDM_SPECIALSEP, MF_BYCOMMAND);
+ }
+ if (new_menu) {
+ InsertMenu(popup_menus[j].menu, IDM_SHOWLOG,
+ MF_BYCOMMAND | MF_POPUP | MF_ENABLED,
+ (UINT) new_menu, "S&pecial Command");
+ InsertMenu(popup_menus[j].menu, IDM_SHOWLOG,
+ MF_BYCOMMAND | MF_SEPARATOR, IDM_SPECIALSEP, 0);
+ }
+ }
+ specials_menu = new_menu;
+}
+
+static void update_mouse_pointer(void)
+{
+ LPTSTR curstype;
+ int force_visible = FALSE;
+ static int forced_visible = FALSE;
+ switch (busy_status) {
+ case BUSY_NOT:
+ if (send_raw_mouse)
+ curstype = IDC_ARROW;
+ else
+ curstype = IDC_IBEAM;
+ break;
+ case BUSY_WAITING:
+ curstype = IDC_APPSTARTING; /* this may be an abuse */
+ force_visible = TRUE;
+ break;
+ case BUSY_CPU:
+ curstype = IDC_WAIT;
+ force_visible = TRUE;
+ break;
+ default:
+ assert(0);
+ }
+ {
+ HCURSOR cursor = LoadCursor(NULL, curstype);
+ SetClassLongPtr(hwnd, GCLP_HCURSOR, (LONG_PTR)cursor);
+ SetCursor(cursor); /* force redraw of cursor at current posn */
+ }
+ if (force_visible != forced_visible) {
+ /* We want some cursor shapes to be visible always.
+ * Along with show_mouseptr(), this manages the ShowCursor()
+ * counter such that if we switch back to a non-force_visible
+ * cursor, the previous visibility state is restored. */
+ ShowCursor(force_visible);
+ forced_visible = force_visible;
+ }
+}
+
+void set_busy_status(void *frontend, int status)
+{
+ busy_status = status;
+ update_mouse_pointer();
+}
+
+/*
+ * set or clear the "raw mouse message" mode
+ */
+void set_raw_mouse_mode(void *frontend, int activate)
+{
+ activate = activate && !cfg.no_mouse_rep;
+ send_raw_mouse = activate;
+ update_mouse_pointer();
+}
+
+/*
+ * Print a message box and close the connection.
+ */
+void connection_fatal(void *frontend, char *fmt, ...)
+{
+ va_list ap;
+ char *stuff, morestuff[100];
+
+ va_start(ap, fmt);
+ stuff = dupvprintf(fmt, ap);
+ 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)
+ PostQuitMessage(1);
+ else {
+ must_close_session = TRUE;
+ }
+}
+
+/*
+ * Report an error at the command-line parsing stage.
+ */
+void cmdline_error(char *fmt, ...)
+{
+ va_list ap;
+ char *stuff, morestuff[100];
+
+ va_start(ap, fmt);
+ stuff = dupvprintf(fmt, ap);
+ va_end(ap);
+ sprintf(morestuff, "%.70s Command Line Error", appname);
+ MessageBox(hwnd, stuff, morestuff, MB_ICONERROR | MB_OK);
+ sfree(stuff);
+ exit(1);
+}
+
+/*
+ * 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;
+}
+
+/*
+ * Copy the colour palette from the configuration data into defpal.
+ * This is non-trivial because the colour indices are different.
+ */
+static void cfgtopalette(void)
+{
+ int i;
+ 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
+ };
+
+ 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];
+ }
+ 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;
+ defpal[i+16].rgbtGreen = g ? g * 40 + 55 : 0;
+ defpal[i+16].rgbtBlue = b ? b * 40 + 55 : 0;
+ } else {
+ int shade = i - 216;
+ shade = shade * 10 + 8;
+ defpal[i+16].rgbtRed = defpal[i+16].rgbtGreen =
+ defpal[i+16].rgbtBlue = shade;
+ }
+ }
+
+ /* Override with system colours if appropriate */
+ if (cfg.system_colour)
+ systopalette();
+}
+
+/*
+ * Override bit of defpal with colours from the system.
+ * (NB that this takes a copy the system colours at the time this is called,
+ * so subsequent colour scheme changes don't take effect. To fix that we'd
+ * probably want to be using GetSysColorBrush() and the like.)
+ */
+static void systopalette(void)
+{
+ int i;
+ static const struct { int nIndex; int norm; int bold; } or[] =
+ {
+ { COLOR_WINDOWTEXT, 256, 257 }, /* Default Foreground */
+ { COLOR_WINDOW, 258, 259 }, /* Default Background */
+ { COLOR_HIGHLIGHTTEXT, 260, 260 }, /* Cursor Text */
+ { COLOR_HIGHLIGHT, 261, 261 }, /* Cursor Colour */
+ };
+
+ for (i = 0; i < (sizeof(or)/sizeof(or[0])); i++) {
+ COLORREF colour = GetSysColor(or[i].nIndex);
+ defpal[or[i].norm].rgbtRed =
+ defpal[or[i].bold].rgbtRed = GetRValue(colour);
+ defpal[or[i].norm].rgbtGreen =
+ defpal[or[i].bold].rgbtGreen = GetGValue(colour);
+ defpal[or[i].norm].rgbtBlue =
+ defpal[or[i].bold].rgbtBlue = GetBValue(colour);
+ }
+}
+
+/*
+ * Set up the colour palette.
+ */
+static void init_palette(void)
+{
+ int i;
+ HDC hdc = GetDC(hwnd);
+ if (hdc) {
+ if (cfg.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)
+ - sizeof(logpal->palPalEntry)
+ + NALLCOLOURS * sizeof(PALETTEENTRY));
+ logpal->palVersion = 0x300;
+ logpal->palNumEntries = NALLCOLOURS;
+ for (i = 0; i < NALLCOLOURS; i++) {
+ logpal->palPalEntry[i].peRed = defpal[i].rgbtRed;
+ logpal->palPalEntry[i].peGreen = defpal[i].rgbtGreen;
+ logpal->palPalEntry[i].peBlue = defpal[i].rgbtBlue;
+ logpal->palPalEntry[i].peFlags = PC_NOCOLLAPSE;
+ }
+ pal = CreatePalette(logpal);
+ if (pal) {
+ SelectPalette(hdc, pal, FALSE);
+ RealizePalette(hdc);
+ SelectPalette(hdc, GetStockObject(DEFAULT_PALETTE), FALSE);
+ }
+ }
+ ReleaseDC(hwnd, hdc);
+ }
+ if (pal)
+ for (i = 0; i < NALLCOLOURS; i++)
+ colours[i] = PALETTERGB(defpal[i].rgbtRed,
+ defpal[i].rgbtGreen,
+ defpal[i].rgbtBlue);
+ else
+ for (i = 0; i < NALLCOLOURS; i++)
+ colours[i] = RGB(defpal[i].rgbtRed,
+ defpal[i].rgbtGreen, defpal[i].rgbtBlue);
+}
+
+/*
+ * This is a wrapper to ExtTextOut() to force Windows to display
+ * the precise glyphs we give it. Otherwise it would do its own
+ * bidi and Arabic shaping, and we would end up uncertain which
+ * characters it had put where.
+ */
+static void exact_textout(HDC hdc, int x, int y, CONST RECT *lprc,
+ unsigned short *lpString, UINT cbCount,
+ CONST INT *lpDx, int opaque)
+{
+#ifdef __LCC__
+ /*
+ * The LCC include files apparently don't supply the
+ * GCP_RESULTSW type, but we can make do with GCP_RESULTS
+ * proper: the differences aren't important to us (the only
+ * variable-width string parameter is one we don't use anyway).
+ */
+ GCP_RESULTS gcpr;
+#else
+ GCP_RESULTSW gcpr;
+#endif
+ char *buffer = snewn(cbCount*2+2, char);
+ char *classbuffer = snewn(cbCount, char);
+ memset(&gcpr, 0, sizeof(gcpr));
+ memset(buffer, 0, cbCount*2+2);
+ memset(classbuffer, GCPCLASS_NEUTRAL, cbCount);
+
+ gcpr.lStructSize = sizeof(gcpr);
+ gcpr.lpGlyphs = (void *)buffer;
+ gcpr.lpClass = (void *)classbuffer;
+ gcpr.nGlyphs = cbCount;
+ GetCharacterPlacementW(hdc, lpString, cbCount, 0, &gcpr,
+ FLI_MASK | GCP_CLASSIN | GCP_DIACRITIC);
+
+ ExtTextOut(hdc, x, y,
+ ETO_GLYPH_INDEX | ETO_CLIPPED | (opaque ? ETO_OPAQUE : 0),
+ lprc, buffer, cbCount, lpDx);
+}
+
+/*
+ * The exact_textout() wrapper, unfortunately, destroys the useful
+ * Windows `font linking' behaviour: automatic handling of Unicode
+ * code points not supported in this font by falling back to a font
+ * which does contain them. Therefore, we adopt a multi-layered
+ * approach: for any potentially-bidi text, we use exact_textout(),
+ * and for everything else we use a simple ExtTextOut as we did
+ * before exact_textout() was introduced.
+ */
+static void general_textout(HDC hdc, int x, int y, CONST RECT *lprc,
+ unsigned short *lpString, UINT cbCount,
+ CONST INT *lpDx, int opaque)
+{
+ int i, j, xp, xn;
+ int bkmode = 0, got_bkmode = FALSE;
+
+ xp = xn = x;
+
+ for (i = 0; i < (int)cbCount ;) {
+ int rtl = is_rtl(lpString[i]);
+
+ xn += lpDx[i];
+
+ for (j = i+1; j < (int)cbCount; j++) {
+ if (rtl != is_rtl(lpString[j]))
+ break;
+ xn += lpDx[j];
+ }
+
+ /*
+ * Now [i,j) indicates a maximal substring of lpString
+ * which should be displayed using the same textout
+ * function.
+ */
+ if (rtl) {
+ exact_textout(hdc, xp, y, lprc, lpString+i, j-i,
+ font_varpitch ? NULL : lpDx+i, opaque);
+ } else {
+ ExtTextOutW(hdc, xp, y, ETO_CLIPPED | (opaque ? ETO_OPAQUE : 0),
+ lprc, lpString+i, j-i,
+ font_varpitch ? NULL : lpDx+i);
+ }
+
+ i = j;
+ xp = xn;
+
+ bkmode = GetBkMode(hdc);
+ got_bkmode = TRUE;
+ SetBkMode(hdc, TRANSPARENT);
+ opaque = FALSE;
+ }
+
+ if (got_bkmode)
+ SetBkMode(hdc, bkmode);
+}
+
+static int get_font_width(HDC hdc, const TEXTMETRIC *tm)
+{
+ int ret;
+ /* Note that the TMPF_FIXED_PITCH bit is defined upside down :-( */
+ if (!(tm->tmPitchAndFamily & TMPF_FIXED_PITCH)) {
+ ret = tm->tmAveCharWidth;
+ } else {
+#define FIRST '0'
+#define LAST '9'
+ ABCFLOAT widths[LAST-FIRST + 1];
+ int j;
+
+ font_varpitch = TRUE;
+ font_dualwidth = TRUE;
+ if (GetCharABCWidthsFloat(hdc, FIRST, LAST, widths)) {
+ ret = 0;
+ for (j = 0; j < lenof(widths); j++) {
+ int width = (int)(0.5 + widths[j].abcfA +
+ widths[j].abcfB + widths[j].abcfC);
+ if (ret < width)
+ ret = width;
+ }
+ } else {
+ ret = tm->tmMaxCharWidth;
+ }
+#undef FIRST
+#undef LAST
+ }
+ return ret;
+}
+
+/*
+ * Initialise all the fonts we will need initially. There may be as many as
+ * three or as few as one. The other (potentially) twenty-one fonts are done
+ * if/when they are needed.
+ *
+ * We also:
+ *
+ * - check the font width and height, correcting our guesses if
+ * necessary.
+ *
+ * - verify that the bold font is the same width as the ordinary
+ * one, and engage shadow bolding if not.
+ *
+ * - verify that the underlined font is the same width as the
+ * ordinary one (manual underlining by means of line drawing can
+ * be done in a pinch).
+ */
+static void init_fonts(int pick_width, int pick_height)
+{
+ TEXTMETRIC tm;
+ CPINFO cpinfo;
+ int fontsize[3];
+ int i;
+ 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;
+ und_mode = UND_FONT;
+
+ if (cfg.font.isbold) {
+ fw_dontcare = FW_BOLD;
+ fw_bold = FW_HEAVY;
+ } else {
+ fw_dontcare = FW_DONTCARE;
+ fw_bold = FW_BOLD;
+ }
+
+ hdc = GetDC(hwnd);
+
+ if (pick_height)
+ font_height = pick_height;
+ else {
+ font_height = cfg.font.height;
+ if (font_height > 0) {
+ font_height =
+ -MulDiv(font_height, GetDeviceCaps(hdc, LOGPIXELSY), 72);
+ }
+ }
+ font_width = pick_width;
+
+#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)
+
+ f(FONT_NORMAL, cfg.font.charset, fw_dontcare, FALSE);
+
+ SelectObject(hdc, fonts[FONT_NORMAL]);
+ GetTextMetrics(hdc, &tm);
+
+ GetObject(fonts[FONT_NORMAL], sizeof(LOGFONT), &lfont);
+
+ /* Note that the TMPF_FIXED_PITCH bit is defined upside down :-( */
+ if (!(tm.tmPitchAndFamily & TMPF_FIXED_PITCH)) {
+ font_varpitch = FALSE;
+ font_dualwidth = (tm.tmAveCharWidth != tm.tmMaxCharWidth);
+ } else {
+ font_varpitch = TRUE;
+ font_dualwidth = TRUE;
+ }
+ if (pick_width == 0 || pick_height == 0) {
+ font_height = tm.tmHeight;
+ font_width = get_font_width(hdc, &tm);
+ }
+
+#ifdef RDB_DEBUG_PATCH
+ debug(23, "Primary font H=%d, AW=%d, MW=%d",
+ tm.tmHeight, tm.tmAveCharWidth, tm.tmMaxCharWidth);
+#endif
+
+ {
+ CHARSETINFO info;
+ DWORD cset = tm.tmCharSet;
+ memset(&info, 0xFF, sizeof(info));
+
+ /* !!! Yes the next line is right */
+ if (cset == OEM_CHARSET)
+ ucsdata.font_codepage = GetOEMCP();
+ else
+ if (TranslateCharsetInfo ((DWORD *) cset, &info, TCI_SRCCHARSET))
+ ucsdata.font_codepage = info.ciACP;
+ else
+ ucsdata.font_codepage = -1;
+
+ GetCPInfo(ucsdata.font_codepage, &cpinfo);
+ ucsdata.dbcs_screenfont = (cpinfo.MaxCharSize > 1);
+ }
+
+ f(FONT_UNDERLINE, cfg.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
+ * we lose the underline completely. Here we try to work
+ * out whether this is such a font, and if it is, we set a
+ * flag that causes underlines to be drawn by hand.
+ *
+ * Having tried other more sophisticated approaches (such
+ * as examining the TEXTMETRIC structure or requesting the
+ * height of a string), I think we'll do this the brute
+ * force way: we create a small bitmap, draw an underlined
+ * space on it, and test to see whether any pixels are
+ * foreground-coloured. (Since we expect the underline to
+ * go all the way across the character cell, we only search
+ * down a single column of the bitmap, half way across.)
+ */
+ {
+ HDC und_dc;
+ HBITMAP und_bm, und_oldbm;
+ int i, gotit;
+ COLORREF c;
+
+ und_dc = CreateCompatibleDC(hdc);
+ und_bm = CreateCompatibleBitmap(hdc, font_width, font_height);
+ und_oldbm = SelectObject(und_dc, und_bm);
+ SelectObject(und_dc, fonts[FONT_UNDERLINE]);
+ SetTextAlign(und_dc, TA_TOP | TA_LEFT | TA_NOUPDATECP);
+ SetTextColor(und_dc, RGB(255, 255, 255));
+ SetBkColor(und_dc, RGB(0, 0, 0));
+ SetBkMode(und_dc, OPAQUE);
+ ExtTextOut(und_dc, 0, 0, ETO_OPAQUE, NULL, " ", 1, NULL);
+ gotit = FALSE;
+ for (i = 0; i < font_height; i++) {
+ c = GetPixel(und_dc, font_width / 2, i);
+ if (c != RGB(0, 0, 0))
+ gotit = TRUE;
+ }
+ SelectObject(und_dc, und_oldbm);
+ DeleteObject(und_bm);
+ DeleteDC(und_dc);
+ if (!gotit) {
+ und_mode = UND_LINE;
+ DeleteObject(fonts[FONT_UNDERLINE]);
+ fonts[FONT_UNDERLINE] = 0;
+ }
+ }
+
+ if (bold_mode == BOLD_FONT) {
+ f(FONT_BOLD, cfg.font.charset, fw_bold, FALSE);
+ }
+#undef f
+
+ descent = tm.tmAscent + 1;
+ if (descent >= font_height)
+ descent = font_height - 1;
+
+ for (i = 0; i < 3; i++) {
+ if (fonts[i]) {
+ if (SelectObject(hdc, fonts[i]) && GetTextMetrics(hdc, &tm))
+ fontsize[i] = get_font_width(hdc, &tm) + 256 * tm.tmHeight;
+ else
+ fontsize[i] = -i;
+ } else
+ fontsize[i] = -i;
+ }
+
+ ReleaseDC(hwnd, hdc);
+
+ if (fontsize[FONT_UNDERLINE] != fontsize[FONT_NORMAL]) {
+ und_mode = UND_LINE;
+ DeleteObject(fonts[FONT_UNDERLINE]);
+ fonts[FONT_UNDERLINE] = 0;
+ }
+
+ if (bold_mode == BOLD_FONT &&
+ fontsize[FONT_BOLD] != fontsize[FONT_NORMAL]) {
+ bold_mode = BOLD_SHADOW;
+ DeleteObject(fonts[FONT_BOLD]);
+ fonts[FONT_BOLD] = 0;
+ }
+ fontflag[0] = fontflag[1] = fontflag[2] = 1;
+
+ init_ucs(&cfg, &ucsdata);
+}
+
+static void another_font(int fontno)
+{
+ int basefont;
+ int fw_dontcare, fw_bold;
+ int c, u, w, x;
+ char *s;
+
+ 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) {
+ fw_dontcare = FW_BOLD;
+ fw_bold = FW_HEAVY;
+ } else {
+ fw_dontcare = FW_DONTCARE;
+ fw_bold = FW_BOLD;
+ }
+
+ c = cfg.font.charset;
+ w = fw_dontcare;
+ u = FALSE;
+ s = cfg.font.name;
+ x = font_width;
+
+ if (fontno & FONT_WIDE)
+ x *= 2;
+ if (fontno & FONT_NARROW)
+ x = (x+1)/2;
+ if (fontno & FONT_OEM)
+ c = OEM_CHARSET;
+ if (fontno & FONT_BOLD)
+ w = fw_bold;
+ if (fontno & FONT_UNDERLINE)
+ u = TRUE;
+
+ 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),
+ DEFAULT_PITCH | FF_DONTCARE, s);
+
+ fontflag[fontno] = 1;
+}
+
+static void deinit_fonts(void)
+{
+ int i;
+ for (i = 0; i < FONT_MAXNO; i++) {
+ if (fonts[i])
+ DeleteObject(fonts[i]);
+ fonts[i] = 0;
+ fontflag[i] = 0;
+ }
+}
+
+void request_resize(void *frontend, int w, int h)
+{
+ int width, height;
+
+ /* If the window is maximized supress resizing attempts */
+ if (IsZoomed(hwnd)) {
+ if (cfg.resize_action == RESIZE_TERM)
+ return;
+ }
+
+ if (cfg.resize_action == RESIZE_DISABLED) return;
+ if (h == term->rows && w == term->cols) return;
+
+ /* Sanity checks ... */
+ {
+ static int first_time = 1;
+ static RECT ss;
+
+ switch (first_time) {
+ case 1:
+ /* Get the size of the screen */
+ if (get_fullscreen_rect(&ss))
+ /* first_time = 0 */ ;
+ else {
+ first_time = 2;
+ break;
+ }
+ case 0:
+ /* Make sure the values are sane */
+ width = (ss.right - ss.left - extra_width) / 4;
+ height = (ss.bottom - ss.top - extra_height) / 6;
+
+ if (w > width || h > height)
+ return;
+ if (w < 15)
+ w = 15;
+ if (h < 1)
+ h = 1;
+ }
+ }
+
+ term_size(term, h, w, cfg.savelines);
+
+ if (cfg.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 |
+ SWP_NOMOVE | SWP_NOZORDER);
+ } else
+ reset_window(0);
+
+ InvalidateRect(hwnd, NULL, TRUE);
+}
+
+static void reset_window(int reinit) {
+ /*
+ * This function decides how to resize or redraw when the
+ * 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;
+ RECT cr, wr;
+
+#ifdef RDB_DEBUG_PATCH
+ debug((27, "reset_window()"));
+#endif
+
+ /* Current window sizes ... */
+ GetWindowRect(hwnd, &wr);
+ GetClientRect(hwnd, &cr);
+
+ win_width = cr.right - cr.left;
+ win_height = cr.bottom - cr.top;
+
+ if (cfg.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"));
+#endif
+ deinit_fonts();
+ init_fonts(0,0);
+ }
+
+ /* Oh, looks like we're minimised */
+ if (win_width == 0 || win_height == 0)
+ return;
+
+ /* Is the window out of position ? */
+ if ( !reinit &&
+ (offset_width != (win_width-font_width*term->cols)/2 ||
+ offset_height != (win_height-font_height*term->rows)/2) ){
+ 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() -> Reposition terminal"));
+#endif
+ }
+
+ if (IsZoomed(hwnd)) {
+ /* We're fullscreen, this means we must not change the size of
+ * the window so it's the font size or the terminal itself.
+ */
+
+ 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) {
+ 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);
+#ifdef RDB_DEBUG_PATCH
+ 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) {
+ /* 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);
+ 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"));
+#endif
+ }
+ }
+ return;
+ }
+
+ /* Hmm, a force re-init means we should ignore the current window
+ * so we resize to the default font size.
+ */
+ if (reinit>0) {
+#ifdef RDB_DEBUG_PATCH
+ debug((27, "reset_window() -> Forced re-init"));
+#endif
+
+ offset_width = offset_height = cfg.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) {
+
+ /* If this is too large windows will resize it to the maximum
+ * allowed window size, we will then be back in here and resize
+ * the font or terminal to fit.
+ */
+ SetWindowPos(hwnd, NULL, 0, 0,
+ font_width*term->cols + extra_width,
+ font_height*term->rows + extra_height,
+ SWP_NOMOVE | SWP_NOZORDER);
+ }
+
+ InvalidateRect(hwnd, NULL, TRUE);
+ return;
+ }
+
+ /* 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) ||
+ reinit>0) {
+ offset_width = offset_height = cfg.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) {
+
+ static RECT ss;
+ int width, height;
+
+ get_fullscreen_rect(&ss);
+
+ 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) {
+ /* 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)
+ font_height = (ss.bottom - ss.top - extra_height)
+ / term->rows;
+
+ deinit_fonts();
+ init_fonts(font_width, font_height);
+
+ 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);
+#ifdef RDB_DEBUG_PATCH
+ debug((27, "reset_window() -> term resize to (%d,%d)",
+ height, width));
+#endif
+ }
+ }
+
+ SetWindowPos(hwnd, NULL, 0, 0,
+ font_width*term->cols + extra_width,
+ font_height*term->rows + extra_height,
+ SWP_NOMOVE | SWP_NOZORDER);
+
+ InvalidateRect(hwnd, NULL, TRUE);
+#ifdef RDB_DEBUG_PATCH
+ debug((27, "reset_window() -> window resize to (%d,%d)",
+ font_width*term->cols + extra_width,
+ font_height*term->rows + extra_height));
+#endif
+ }
+ 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) {
+
+ deinit_fonts();
+ init_fonts((win_width-cfg.window_border*2)/term->cols,
+ (win_height-cfg.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;
+
+ InvalidateRect(hwnd, NULL, TRUE);
+#ifdef RDB_DEBUG_PATCH
+ debug((25, "reset_window() -> font resize to (%d,%d)",
+ font_width, font_height));
+#endif
+ }
+}
+
+static void set_input_locale(HKL kl)
+{
+ char lbuf[20];
+
+ GetLocaleInfo(LOWORD(kl), LOCALE_IDEFAULTANSICODEPAGE,
+ lbuf, sizeof(lbuf));
+
+ kbd_codepage = atoi(lbuf);
+}
+
+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)) {
+ lastbtn = MBT_NOTHING;
+ term_mouse(term, b, translate_button(b), MA_CLICK,
+ x, y, shift, ctrl, alt);
+ return;
+ }
+
+ if (lastbtn == b && thistime - lasttime < dbltime) {
+ lastact = (lastact == MA_CLICK ? MA_2CLK :
+ lastact == MA_2CLK ? MA_3CLK :
+ lastact == MA_3CLK ? MA_CLICK : MA_NOTHING);
+ } else {
+ lastbtn = b;
+ lastact = MA_CLICK;
+ }
+ if (lastact != MA_NOTHING)
+ term_mouse(term, b, translate_button(b), lastact,
+ x, y, shift, ctrl, alt);
+ lasttime = thistime;
+}
+
+/*
+ * Translate a raw mouse button designation (LEFT, MIDDLE, RIGHT)
+ * into a cooked one (SELECT, EXTEND, PASTE).
+ */
+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;
+ if (button == MBT_RIGHT)
+ return cfg.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 (cursor_visible && !show)
+ ShowCursor(FALSE);
+ else if (!cursor_visible && show)
+ ShowCursor(TRUE);
+ cursor_visible = show;
+}
+
+static int is_alt_pressed(void)
+{
+ BYTE keystate[256];
+ int r = GetKeyboardState(keystate);
+ if (!r)
+ return FALSE;
+ if (keystate[VK_MENU] & 0x80)
+ return TRUE;
+ if (keystate[VK_RMENU] & 0x80)
+ return TRUE;
+ return FALSE;
+}
+
+static int resizing;
+
+void notify_remote_exit(void *fe)
+{
+ int exitcode;
+
+ if (!session_closed &&
+ (exitcode = back->exitcode(backhandle)) >= 0) {
+ /* 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)) {
+ PostQuitMessage(0);
+ } else {
+ must_close_session = TRUE;
+ 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)
+ MessageBox(hwnd, "Connection closed by remote host",
+ appname, MB_OK | MB_ICONINFORMATION);
+ }
+ }
+}
+
+void timer_change_notify(long next)
+{
+ long ticks = next - GETTICKCOUNT();
+ if (ticks <= 0) ticks = 1; /* just in case */
+ KillTimer(hwnd, TIMING_TIMER_ID);
+ SetTimer(hwnd, TIMING_TIMER_ID, ticks, NULL);
+ timing_next_time = next;
+}
+
+static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
+ WPARAM wParam, LPARAM lParam)
+{
+ HDC hdc;
+ 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;
+
+ switch (message) {
+ case WM_TIMER:
+ if ((UINT_PTR)wParam == TIMING_TIMER_ID) {
+ long next;
+
+ KillTimer(hwnd, TIMING_TIMER_ID);
+ if (run_timers(timing_next_time, &next)) {
+ timer_change_notify(next);
+ } else {
+ }
+ }
+ return 0;
+ case WM_CREATE:
+ break;
+ case WM_CLOSE:
+ {
+ char *str;
+ show_mouseptr(1);
+ str = dupprintf("%s Exit Confirmation", appname);
+ if (!cfg.warn_on_close || session_closed ||
+ MessageBox(hwnd,
+ "Are you sure you want to close this session?",
+ str, MB_ICONWARNING | MB_OKCANCEL | MB_DEFBUTTON1)
+ == IDOK)
+ DestroyWindow(hwnd);
+ sfree(str);
+ }
+ return 0;
+ case WM_DESTROY:
+ show_mouseptr(1);
+ PostQuitMessage(0);
+ return 0;
+ case WM_INITMENUPOPUP:
+ if ((HMENU)wParam == savedsess_menu) {
+ /* About to pop up Saved Sessions sub-menu.
+ * Refresh the session list. */
+ get_sesslist(&sesslist, FALSE); /* free */
+ get_sesslist(&sesslist, TRUE);
+ update_savedsess_menu();
+ return 0;
+ }
+ break;
+ case WM_COMMAND:
+ case WM_SYSCOMMAND:
+ switch (wParam & ~0xF) { /* low 4 bits reserved to Windows */
+ case IDM_SHOWLOG:
+ showeventlog(hwnd);
+ break;
+ case IDM_NEWSESS:
+ case IDM_DUPSESS:
+ case IDM_SAVEDSESS:
+ {
+ char b[2048];
+ char c[30], *cl;
+ int freecl = FALSE;
+ BOOL inherit_handles;
+ STARTUPINFO si;
+ PROCESS_INFORMATION pi;
+ HANDLE filemap = NULL;
+
+ if (wParam == IDM_DUPSESS) {
+ /*
+ * Allocate a file-mapping memory chunk for the
+ * config structure.
+ */
+ SECURITY_ATTRIBUTES sa;
+ Config *p;
+
+ sa.nLength = sizeof(sa);
+ sa.lpSecurityDescriptor = NULL;
+ sa.bInheritHandle = TRUE;
+ filemap = CreateFileMapping(INVALID_HANDLE_VALUE,
+ &sa,
+ PAGE_READWRITE,
+ 0, sizeof(Config), NULL);
+ if (filemap && filemap != INVALID_HANDLE_VALUE) {
+ p = (Config *) MapViewOfFile(filemap,
+ FILE_MAP_WRITE,
+ 0, 0, sizeof(Config));
+ if (p) {
+ *p = cfg; /* structure copy */
+ UnmapViewOfFile(p);
+ }
+ }
+ inherit_handles = TRUE;
+ sprintf(c, "putty &%p", filemap);
+ cl = c;
+ } else if (wParam == IDM_SAVEDSESS) {
+ unsigned int sessno = ((lParam - IDM_SAVED_MIN)
+ / MENU_SAVED_STEP) + 1;
+ if (sessno < (unsigned)sesslist.nsessions) {
+ char *session = sesslist.sessions[sessno];
+ cl = dupprintf("putty @%s", session);
+ inherit_handles = FALSE;
+ freecl = TRUE;
+ } else
+ break;
+ } else /* IDM_NEWSESS */ {
+ cl = NULL;
+ inherit_handles = FALSE;
+ }
+
+ GetModuleFileName(NULL, b, sizeof(b) - 1);
+ si.cb = sizeof(si);
+ si.lpReserved = NULL;
+ si.lpDesktop = NULL;
+ si.lpTitle = NULL;
+ si.dwFlags = 0;
+ si.cbReserved2 = 0;
+ si.lpReserved2 = NULL;
+ CreateProcess(b, cl, NULL, NULL, inherit_handles,
+ NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi);
+
+ if (filemap)
+ CloseHandle(filemap);
+ if (freecl)
+ sfree(cl);
+ }
+ break;
+ case IDM_RESTART:
+ if (!back) {
+ logevent(NULL, "----- Session restarted -----");
+ term_pwron(term, FALSE);
+ start_backend();
+ }
+
+ break;
+ case IDM_RECONF:
+ {
+ Config prev_cfg;
+ int init_lvl = 1;
+ int reconfig_result;
+
+ if (reconfiguring)
+ break;
+ else
+ reconfiguring = TRUE;
+
+ GetWindowText(hwnd, cfg.wintitle, sizeof(cfg.wintitle));
+ prev_cfg = cfg;
+
+ reconfig_result =
+ do_reconfig(hwnd, back ? back->cfg_info(backhandle) : 0);
+ reconfiguring = FALSE;
+ if (!reconfig_result)
+ break;
+
+ {
+ /* 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)
+ ? MF_GRAYED : MF_ENABLED);
+ /* Gracefully unzoom if necessary */
+ if (IsZoomed(hwnd) &&
+ (cfg.resize_action == RESIZE_DISABLED)) {
+ ShowWindow(hwnd, SW_RESTORE);
+ }
+ }
+
+ /* Pass new config data to the logging module */
+ log_reconfig(logctx, &cfg);
+
+ sfree(logpal);
+ /*
+ * Flush the line discipline's edit buffer in the
+ * case where local editing has just been disabled.
+ */
+ if (ldisc)
+ ldisc_send(ldisc, NULL, 0, 0);
+ if (pal)
+ DeleteObject(pal);
+ logpal = NULL;
+ pal = NULL;
+ cfgtopalette();
+ init_palette();
+
+ /* Pass new config data to the terminal */
+ term_reconfig(term, &cfg);
+
+ /* Pass new config data to the back end */
+ if (back)
+ back->reconfig(backhandle, &cfg);
+
+ /* 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);
+
+ /* 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) {
+ 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)
+ nexflag |= WS_EX_CLIENTEDGE;
+ else
+ nexflag &= ~(WS_EX_CLIENTEDGE);
+
+ nflg = flag;
+ if (is_full_screen() ?
+ cfg.scrollbar_in_fullscreen : cfg.scrollbar)
+ nflg |= WS_VSCROLL;
+ else
+ nflg &= ~WS_VSCROLL;
+
+ if (cfg.resize_action == RESIZE_DISABLED ||
+ is_full_screen())
+ nflg &= ~WS_THICKFRAME;
+ else
+ nflg |= WS_THICKFRAME;
+
+ if (cfg.resize_action == RESIZE_DISABLED)
+ nflg &= ~WS_MAXIMIZEBOX;
+ else
+ nflg |= WS_MAXIMIZEBOX;
+
+ if (nflg != flag || nexflag != exflag) {
+ if (nflg != flag)
+ SetWindowLongPtr(hwnd, GWL_STYLE, nflg);
+ if (nexflag != exflag)
+ SetWindowLongPtr(hwnd, GWL_EXSTYLE, nexflag);
+
+ SetWindowPos(hwnd, NULL, 0, 0, 0, 0,
+ SWP_NOACTIVATE | SWP_NOCOPYBITS |
+ SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
+ SWP_FRAMECHANGED);
+
+ init_lvl = 2;
+ }
+ }
+
+ /* Oops */
+ if (cfg.resize_action == RESIZE_DISABLED && IsZoomed(hwnd)) {
+ force_normal(hwnd);
+ init_lvl = 2;
+ }
+
+ set_title(NULL, cfg.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;
+
+ InvalidateRect(hwnd, NULL, TRUE);
+ reset_window(init_lvl);
+ net_pending_errors();
+ }
+ break;
+ case IDM_COPYALL:
+ term_copyall(term);
+ break;
+ case IDM_PASTE:
+ request_paste(NULL);
+ break;
+ case IDM_CLRSB:
+ term_clrsb(term);
+ break;
+ case IDM_RESET:
+ term_pwron(term, TRUE);
+ if (ldisc)
+ ldisc_send(ldisc, NULL, 0, 0);
+ break;
+ case IDM_ABOUT:
+ showabout(hwnd);
+ break;
+ case IDM_HELP:
+ launch_help(hwnd, NULL);
+ break;
+ case SC_MOUSEMENU:
+ /*
+ * We get this if the System menu has been activated
+ * using the mouse.
+ */
+ show_mouseptr(1);
+ break;
+ case SC_KEYMENU:
+ /*
+ * We get this if the System menu has been activated
+ * using the keyboard. This might happen from within
+ * TranslateKey, in which case it really wants to be
+ * followed by a `space' character to actually _bring
+ * the menu up_ rather than just sitting there in
+ * `ready to appear' state.
+ */
+ show_mouseptr(1); /* make sure pointer is visible */
+ if( lParam == 0 )
+ PostMessage(hwnd, WM_CHAR, ' ', 0);
+ break;
+ case IDM_FULLSCREEN:
+ flip_full_screen();
+ break;
+ default:
+ if (wParam >= IDM_SAVED_MIN && wParam < IDM_SAVED_MAX) {
+ SendMessage(hwnd, WM_SYSCOMMAND, IDM_SAVEDSESS, wParam);
+ }
+ if (wParam >= IDM_SPECIAL_MIN && wParam <= IDM_SPECIAL_MAX) {
+ int i = (wParam - IDM_SPECIAL_MIN) / 0x10;
+ /*
+ * Ensure we haven't been sent a bogus SYSCOMMAND
+ * which would cause us to reference invalid memory
+ * and crash. Perhaps I'm just too paranoid here.
+ */
+ 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))
+#define Y_POS(l) ((int)(short)HIWORD(l))
+
+#define TO_CHR_X(x) ((((x)<0 ? (x)-font_width+1 : (x))-offset_width) / font_width)
+#define TO_CHR_Y(y) ((((y)<0 ? (y)-font_height+1: (y))-offset_height) / font_height)
+ case WM_LBUTTONDOWN:
+ case WM_MBUTTONDOWN:
+ case WM_RBUTTONDOWN:
+ case WM_LBUTTONUP:
+ case WM_MBUTTONUP:
+ case WM_RBUTTONUP:
+ if (message == WM_RBUTTONDOWN &&
+ ((wParam & MK_CONTROL) || (cfg.mouse_is_xterm == 2))) {
+ POINT cursorpos;
+
+ show_mouseptr(1); /* make sure pointer is visible */
+ GetCursorPos(&cursorpos);
+ TrackPopupMenu(popup_menus[CTXMENU].menu,
+ TPM_LEFTALIGN | TPM_TOPALIGN | TPM_RIGHTBUTTON,
+ cursorpos.x, cursorpos.y,
+ 0, hwnd, NULL);
+ break;
+ }
+ {
+ int button, press;
+
+ switch (message) {
+ case WM_LBUTTONDOWN:
+ button = MBT_LEFT;
+ wParam |= MK_LBUTTON;
+ press = 1;
+ break;
+ case WM_MBUTTONDOWN:
+ button = MBT_MIDDLE;
+ wParam |= MK_MBUTTON;
+ press = 1;
+ break;
+ case WM_RBUTTONDOWN:
+ button = MBT_RIGHT;
+ wParam |= MK_RBUTTON;
+ press = 1;
+ break;
+ case WM_LBUTTONUP:
+ button = MBT_LEFT;
+ wParam &= ~MK_LBUTTON;
+ press = 0;
+ break;
+ case WM_MBUTTONUP:
+ button = MBT_MIDDLE;
+ wParam &= ~MK_MBUTTON;
+ press = 0;
+ break;
+ case WM_RBUTTONUP:
+ button = MBT_RIGHT;
+ wParam &= ~MK_RBUTTON;
+ press = 0;
+ break;
+ default:
+ button = press = 0; /* shouldn't happen */
+ }
+ show_mouseptr(1);
+ /*
+ * Special case: in full-screen mode, if the left
+ * button is clicked in the very top left corner of the
+ * window, we put up the System menu instead of doing
+ * selection.
+ */
+ {
+ char mouse_on_hotspot = 0;
+ POINT pt;
+
+ GetCursorPos(&pt);
+#ifndef NO_MULTIMON
+ {
+ HMONITOR mon;
+ MONITORINFO mi;
+
+ mon = MonitorFromPoint(pt, MONITOR_DEFAULTTONULL);
+
+ if (mon != NULL) {
+ mi.cbSize = sizeof(MONITORINFO);
+ GetMonitorInfo(mon, &mi);
+
+ if (mi.rcMonitor.left == pt.x &&
+ mi.rcMonitor.top == pt.y) {
+ mouse_on_hotspot = 1;
+ }
+ }
+ }
+#else
+ if (pt.x == 0 && pt.y == 0) {
+ mouse_on_hotspot = 1;
+ }
+#endif
+ if (is_full_screen() && press &&
+ button == MBT_LEFT && mouse_on_hotspot) {
+ SendMessage(hwnd, WM_SYSCOMMAND, SC_MOUSEMENU,
+ MAKELPARAM(pt.x, pt.y));
+ return 0;
+ }
+ }
+
+ if (press) {
+ click(button,
+ TO_CHR_X(X_POS(lParam)), TO_CHR_Y(Y_POS(lParam)),
+ wParam & MK_SHIFT, wParam & MK_CONTROL,
+ is_alt_pressed());
+ SetCapture(hwnd);
+ } else {
+ term_mouse(term, button, translate_button(button), MA_RELEASE,
+ TO_CHR_X(X_POS(lParam)),
+ TO_CHR_Y(Y_POS(lParam)), wParam & MK_SHIFT,
+ wParam & MK_CONTROL, is_alt_pressed());
+ if (!(wParam & (MK_LBUTTON | MK_MBUTTON | MK_RBUTTON)))
+ ReleaseCapture();
+ }
+ }
+ return 0;
+ case WM_MOUSEMOVE:
+ {
+ /*
+ * Windows seems to like to occasionally send MOUSEMOVE
+ * events even if the mouse hasn't moved. Don't unhide
+ * the mouse pointer in this case.
+ */
+ static WPARAM wp = 0;
+ static LPARAM lp = 0;
+ if (wParam != wp || lParam != lp ||
+ last_mousemove != WM_MOUSEMOVE) {
+ show_mouseptr(1);
+ wp = wParam; lp = lParam;
+ last_mousemove = WM_MOUSEMOVE;
+ }
+ }
+ /*
+ * Add the mouse position and message time to the random
+ * number noise.
+ */
+ noise_ultralight(lParam);
+
+ if (wParam & (MK_LBUTTON | MK_MBUTTON | MK_RBUTTON) &&
+ GetCapture() == hwnd) {
+ Mouse_Button b;
+ if (wParam & MK_LBUTTON)
+ b = MBT_LEFT;
+ else if (wParam & MK_MBUTTON)
+ b = MBT_MIDDLE;
+ else
+ b = MBT_RIGHT;
+ term_mouse(term, b, translate_button(b), MA_DRAG,
+ TO_CHR_X(X_POS(lParam)),
+ TO_CHR_Y(Y_POS(lParam)), wParam & MK_SHIFT,
+ wParam & MK_CONTROL, is_alt_pressed());
+ }
+ return 0;
+ case WM_NCMOUSEMOVE:
+ {
+ static WPARAM wp = 0;
+ static LPARAM lp = 0;
+ if (wParam != wp || lParam != lp ||
+ last_mousemove != WM_NCMOUSEMOVE) {
+ show_mouseptr(1);
+ wp = wParam; lp = lParam;
+ last_mousemove = WM_NCMOUSEMOVE;
+ }
+ }
+ noise_ultralight(lParam);
+ break;
+ case WM_IGNORE_CLIP:
+ ignore_clip = wParam; /* don't panic on DESTROYCLIPBOARD */
+ break;
+ case WM_DESTROYCLIPBOARD:
+ if (!ignore_clip)
+ term_deselect(term);
+ ignore_clip = FALSE;
+ return 0;
+ case WM_PAINT:
+ {
+ PAINTSTRUCT p;
+
+ HideCaret(hwnd);
+ hdc = BeginPaint(hwnd, &p);
+ if (pal) {
+ SelectPalette(hdc, pal, TRUE);
+ RealizePalette(hdc);
+ }
+
+ /*
+ * We have to be careful about term_paint(). It will
+ * set a bunch of character cells to INVALID and then
+ * call do_paint(), which will redraw those cells and
+ * _then mark them as done_. This may not be accurate:
+ * when painting in WM_PAINT context we are restricted
+ * to the rectangle which has just been exposed - so if
+ * that only covers _part_ of a character cell and the
+ * rest of it was already visible, that remainder will
+ * not be redrawn at all. Accordingly, we must not
+ * paint any character cell in a WM_PAINT context which
+ * already has a pending update due to terminal output.
+ * The simplest solution to this - and many, many
+ * thanks to Hung-Te Lin for working all this out - is
+ * not to do any actual painting at _all_ if there's a
+ * pending terminal update: just mark the relevant
+ * character cells as INVALID and wait for the
+ * scheduled full update to sort it out.
+ *
+ * I have a suspicion this isn't the _right_ solution.
+ * An alternative approach would be to have terminal.c
+ * separately track what _should_ be on the terminal
+ * screen and what _is_ on the terminal screen, and
+ * have two completely different types of redraw (one
+ * for full updates, which syncs the former with the
+ * terminal itself, and one for WM_PAINT which syncs
+ * the latter with the former); yet another possibility
+ * would be to have the Windows front end do what the
+ * GTK one already does, and maintain a bitmap of the
+ * current terminal appearance so that WM_PAINT becomes
+ * completely trivial. However, this should do for now.
+ */
+ term_paint(term, hdc,
+ (p.rcPaint.left-offset_width)/font_width,
+ (p.rcPaint.top-offset_height)/font_height,
+ (p.rcPaint.right-offset_width-1)/font_width,
+ (p.rcPaint.bottom-offset_height-1)/font_height,
+ !term->window_update_pending);
+
+ if (p.fErase ||
+ p.rcPaint.left < offset_width ||
+ p.rcPaint.top < offset_height ||
+ p.rcPaint.right >= offset_width + font_width*term->cols ||
+ p.rcPaint.bottom>= offset_height + font_height*term->rows)
+ {
+ HBRUSH fillcolour, oldbrush;
+ HPEN edge, oldpen;
+ fillcolour = CreateSolidBrush (
+ colours[ATTR_DEFBG>>ATTR_BGSHIFT]);
+ oldbrush = SelectObject(hdc, fillcolour);
+ edge = CreatePen(PS_SOLID, 0,
+ colours[ATTR_DEFBG>>ATTR_BGSHIFT]);
+ oldpen = SelectObject(hdc, edge);
+
+ /*
+ * Jordan Russell reports that this apparently
+ * ineffectual IntersectClipRect() call masks a
+ * Windows NT/2K bug causing strange display
+ * problems when the PuTTY window is taller than
+ * the primary monitor. It seems harmless enough...
+ */
+ IntersectClipRect(hdc,
+ p.rcPaint.left, p.rcPaint.top,
+ p.rcPaint.right, p.rcPaint.bottom);
+
+ ExcludeClipRect(hdc,
+ offset_width, offset_height,
+ offset_width+font_width*term->cols,
+ offset_height+font_height*term->rows);
+
+ Rectangle(hdc, p.rcPaint.left, p.rcPaint.top,
+ p.rcPaint.right, p.rcPaint.bottom);
+
+ /* SelectClipRgn(hdc, NULL); */
+
+ SelectObject(hdc, oldbrush);
+ DeleteObject(fillcolour);
+ SelectObject(hdc, oldpen);
+ DeleteObject(edge);
+ }
+ SelectObject(hdc, GetStockObject(SYSTEM_FONT));
+ SelectObject(hdc, GetStockObject(WHITE_PEN));
+ 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();
+ return 0;
+ case WM_SETFOCUS:
+ term_set_focus(term, TRUE);
+ CreateCaret(hwnd, caretbm, font_width, font_height);
+ ShowCaret(hwnd);
+ flash_window(0); /* stop */
+ compose_state = 0;
+ term_update(term);
+ break;
+ case WM_KILLFOCUS:
+ show_mouseptr(1);
+ term_set_focus(term, FALSE);
+ DestroyCaret();
+ caret_x = caret_y = -1; /* ensure caret is replaced next time */
+ term_update(term);
+ break;
+ case WM_ENTERSIZEMOVE:
+#ifdef RDB_DEBUG_PATCH
+ debug((27, "WM_ENTERSIZEMOVE"));
+#endif
+ EnableSizeTip(1);
+ resizing = TRUE;
+ need_backend_resize = FALSE;
+ break;
+ case WM_EXITSIZEMOVE:
+ EnableSizeTip(0);
+ resizing = FALSE;
+#ifdef RDB_DEBUG_PATCH
+ debug((27, "WM_EXITSIZEMOVE"));
+#endif
+ if (need_backend_resize) {
+ term_size(term, cfg.height, cfg.width, cfg.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())) {
+ 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 )) {
+ /*
+ * 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;
+
+ InvalidateRect(hwnd, NULL, TRUE);
+ need_backend_resize = TRUE;
+ }
+
+ width = r->right - r->left - extra_width;
+ height = r->bottom - r->top - extra_height;
+ w = (width + font_width / 2) / font_width;
+ if (w < 1)
+ w = 1;
+ h = (height + font_height / 2) / font_height;
+ if (h < 1)
+ h = 1;
+ UpdateSizeTip(hwnd, w, h);
+ ew = width - w * font_width;
+ eh = height - h * font_height;
+ if (ew != 0) {
+ if (wParam == WMSZ_LEFT ||
+ wParam == WMSZ_BOTTOMLEFT || wParam == WMSZ_TOPLEFT)
+ r->left += ew;
+ else
+ r->right -= ew;
+ }
+ if (eh != 0) {
+ if (wParam == WMSZ_TOP ||
+ wParam == WMSZ_TOPRIGHT || wParam == WMSZ_TOPLEFT)
+ r->top += eh;
+ else
+ r->bottom -= eh;
+ }
+ if (ew || eh)
+ 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;
+ LPRECT r = (LPRECT) lParam;
+
+ width = r->right - r->left - ex_width;
+ height = r->bottom - r->top - ex_height;
+
+ w = (width + term->cols/2)/term->cols;
+ h = (height + term->rows/2)/term->rows;
+ if ( r->right != r->left + w*term->cols + ex_width)
+ rv = 1;
+
+ if (wParam == WMSZ_LEFT ||
+ wParam == WMSZ_BOTTOMLEFT || wParam == WMSZ_TOPLEFT)
+ r->left = r->right - w*term->cols - ex_width;
+ else
+ r->right = r->left + w*term->cols + ex_width;
+
+ if (r->bottom != r->top + h*term->rows + ex_height)
+ rv = 1;
+
+ if (wParam == WMSZ_TOP ||
+ wParam == WMSZ_TOPRIGHT || wParam == WMSZ_TOPLEFT)
+ r->top = r->bottom - h*term->rows - ex_height;
+ else
+ r->bottom = r->top + h*term->rows + ex_height;
+
+ return rv;
+ }
+ /* break; (never reached) */
+ case WM_FULLSCR_ON_MAX:
+ fullscr_on_max = TRUE;
+ break;
+ case WM_MOVE:
+ sys_cursor_update();
+ break;
+ case WM_SIZE:
+#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":
+ (wParam == SIZE_RESTORED) ? "SIZE_RESTORED":
+ "...",
+ LOWORD(lParam), HIWORD(lParam)));
+#endif
+ if (wParam == SIZE_MINIMIZED)
+ SetWindowText(hwnd,
+ cfg.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();
+ if (processed_resize) {
+ /*
+ * Inhibit normal processing of this WM_SIZE; a
+ * secondary one was triggered just now by
+ * clear_full_screen which contained the correct
+ * client area size.
+ */
+ return 0;
+ }
+ }
+ if (wParam == SIZE_MAXIMIZED && fullscr_on_max) {
+ fullscr_on_max = FALSE;
+ processed_resize = FALSE;
+ make_full_screen();
+ if (processed_resize) {
+ /*
+ * Inhibit normal processing of this WM_SIZE; a
+ * secondary one was triggered just now by
+ * make_full_screen which contained the correct client
+ * area size.
+ */
+ return 0;
+ }
+ }
+
+ processed_resize = TRUE;
+
+ if (cfg.resize_action == RESIZE_DISABLED) {
+ /* A resize, well it better be a minimize. */
+ reset_window(-1);
+ } else {
+
+ int width, height, w, h;
+
+ 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) {
+ 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);
+ }
+ 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 (w < 1) w = 1;
+ h = (height-cfg.window_border*2) / font_height;
+ if (h < 1) h = 1;
+ term_size(term, h, w, cfg.savelines);
+ reset_window(2);
+ } else if (cfg.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 &&
+ !is_alt_pressed())) {
+ w = (width-cfg.window_border*2) / font_width;
+ if (w < 1) w = 1;
+ h = (height-cfg.window_border*2) / font_height;
+ if (h < 1) h = 1;
+
+ if (resizing) {
+ /*
+ * Don't call back->size in mid-resize. (To
+ * 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;
+ } else {
+ term_size(term, h, w, cfg.savelines);
+ }
+ } else {
+ reset_window(0);
+ }
+ }
+ sys_cursor_update();
+ return 0;
+ case WM_VSCROLL:
+ switch (LOWORD(wParam)) {
+ case SB_BOTTOM:
+ term_scroll(term, -1, 0);
+ break;
+ case SB_TOP:
+ term_scroll(term, +1, 0);
+ break;
+ case SB_LINEDOWN:
+ term_scroll(term, 0, +1);
+ break;
+ case SB_LINEUP:
+ term_scroll(term, 0, -1);
+ break;
+ case SB_PAGEDOWN:
+ term_scroll(term, 0, +term->rows / 2);
+ break;
+ case SB_PAGEUP:
+ term_scroll(term, 0, -term->rows / 2);
+ break;
+ case SB_THUMBPOSITION:
+ case SB_THUMBTRACK:
+ term_scroll(term, 1, HIWORD(wParam));
+ break;
+ }
+ break;
+ case WM_PALETTECHANGED:
+ if ((HWND) wParam != hwnd && pal != NULL) {
+ HDC hdc = get_ctx(NULL);
+ if (hdc) {
+ if (RealizePalette(hdc) > 0)
+ UpdateColors(hdc);
+ free_ctx(hdc);
+ }
+ }
+ break;
+ case WM_QUERYNEWPALETTE:
+ if (pal != NULL) {
+ HDC hdc = get_ctx(NULL);
+ if (hdc) {
+ if (RealizePalette(hdc) > 0)
+ UpdateColors(hdc);
+ free_ctx(hdc);
+ return TRUE;
+ }
+ }
+ return FALSE;
+ case WM_KEYDOWN:
+ case WM_SYSKEYDOWN:
+ case WM_KEYUP:
+ case WM_SYSKEYUP:
+ /*
+ * Add the scan code and keypress timing to the random
+ * number noise.
+ */
+ noise_ultralight(lParam);
+
+ /*
+ * We don't do TranslateMessage since it disassociates the
+ * resulting CHAR message from the KEYDOWN that sparked it,
+ * which we occasionally don't want. Instead, we process
+ * KEYDOWN, and call the Win32 translator functions so that
+ * we get the translations under _our_ control.
+ */
+ {
+ unsigned char buf[20];
+ int len;
+
+ if (wParam == VK_PROCESSKEY) { /* IME PROCESS key */
+ if (message == WM_KEYDOWN) {
+ MSG m;
+ m.hwnd = hwnd;
+ m.message = WM_KEYDOWN;
+ m.wParam = wParam;
+ m.lParam = lParam & 0xdfff;
+ TranslateMessage(&m);
+ } else break; /* pass to Windows for default processing */
+ } else {
+ 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
+ * messages. We _have_ to buffer everything
+ * we're sent.
+ */
+ term_seen_key_event(term);
+ if (ldisc)
+ 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);
+ sys_cursor_update();
+ break;
+ case WM_IME_STARTCOMPOSITION:
+ {
+ HIMC hImc = ImmGetContext(hwnd);
+ ImmSetCompositionFont(hImc, &lfont);
+ ImmReleaseContext(hwnd, hImc);
+ }
+ break;
+ case WM_IME_COMPOSITION:
+ {
+ HIMC hIMC;
+ int n;
+ char *buff;
+
+ if(osVersion.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS ||
+ osVersion.dwPlatformId == VER_PLATFORM_WIN32s) break; /* no Unicode */
+
+ if ((lParam & GCS_RESULTSTR) == 0) /* Composition unfinished. */
+ break; /* fall back to DefWindowProc */
+
+ hIMC = ImmGetContext(hwnd);
+ n = ImmGetCompositionStringW(hIMC, GCS_RESULTSTR, NULL, 0);
+
+ if (n > 0) {
+ int i;
+ buff = snewn(n, char);
+ ImmGetCompositionStringW(hIMC, GCS_RESULTSTR, buff, n);
+ /*
+ * Jaeyoun Chung reports that Korean character
+ * 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)
+ luni_send(ldisc, (unsigned short *)(buff+i), 1, 1);
+ }
+ free(buff);
+ }
+ ImmReleaseContext(hwnd, hIMC);
+ return 1;
+ }
+
+ case WM_IME_CHAR:
+ if (wParam & 0xFF00) {
+ unsigned char buf[2];
+
+ buf[1] = wParam;
+ buf[0] = wParam >> 8;
+ term_seen_key_event(term);
+ if (ldisc)
+ lpage_send(ldisc, kbd_codepage, buf, 2, 1);
+ } else {
+ char c = (unsigned char) wParam;
+ term_seen_key_event(term);
+ if (ldisc)
+ lpage_send(ldisc, kbd_codepage, &c, 1, 1);
+ }
+ return (0);
+ case WM_CHAR:
+ case WM_SYSCHAR:
+ /*
+ * Nevertheless, we are prepared to deal with WM_CHAR
+ * messages, should they crop up. So if someone wants to
+ * post the things to us as part of a macro manoeuvre,
+ * we're ready to cope.
+ */
+ {
+ char c = (unsigned char)wParam;
+ term_seen_key_event(term);
+ if (ldisc)
+ lpage_send(ldisc, CP_ACP, &c, 1, 1);
+ }
+ return 0;
+ case WM_SYSCOLORCHANGE:
+ if (cfg.system_colour) {
+ /* Refresh palette from system colours. */
+ /* XXX actually this zaps the entire palette. */
+ systopalette();
+ init_palette();
+ /* Force a repaint of the terminal window. */
+ term_invalidate(term);
+ }
+ break;
+ case WM_AGENT_CALLBACK:
+ {
+ struct agent_callback *c = (struct agent_callback *)lParam;
+ c->callback(c->callback_ctx, c->data, c->len);
+ sfree(c);
+ }
+ return 0;
+ case WM_GOT_CLIPDATA:
+ if (process_clipdata((HGLOBAL)lParam, wParam))
+ term_do_paste(term);
+ return 0;
+ default:
+ if (message == wm_mousewheel || message == WM_MOUSEWHEEL) {
+ int shift_pressed=0, control_pressed=0;
+
+ if (message == WM_MOUSEWHEEL) {
+ wheel_accumulator += (short)HIWORD(wParam);
+ shift_pressed=LOWORD(wParam) & MK_SHIFT;
+ control_pressed=LOWORD(wParam) & MK_CONTROL;
+ } else {
+ BYTE keys[256];
+ wheel_accumulator += (int)wParam;
+ if (GetKeyboardState(keys)!=0) {
+ shift_pressed=keys[VK_SHIFT]&0x80;
+ control_pressed=keys[VK_CONTROL]&0x80;
+ }
+ }
+
+ /* process events when the threshold is reached */
+ while (abs(wheel_accumulator) >= WHEEL_DELTA) {
+ int b;
+
+ /* reduce amount for next time */
+ if (wheel_accumulator > 0) {
+ b = MBT_WHEEL_UP;
+ wheel_accumulator -= WHEEL_DELTA;
+ } else if (wheel_accumulator < 0) {
+ b = MBT_WHEEL_DOWN;
+ wheel_accumulator += WHEEL_DELTA;
+ } else
+ break;
+
+ if (send_raw_mouse &&
+ !(cfg.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)) {
+ /* send a mouse-down followed by a mouse up */
+ term_mouse(term, b, translate_button(b),
+ MA_CLICK,
+ TO_CHR_X(p.x),
+ TO_CHR_Y(p.y), shift_pressed,
+ control_pressed, is_alt_pressed());
+ term_mouse(term, b, translate_button(b),
+ MA_RELEASE, TO_CHR_X(p.x),
+ TO_CHR_Y(p.y), shift_pressed,
+ control_pressed, is_alt_pressed());
+ } /* else: not sure when this can fail */
+ } else {
+ /* trigger a scroll */
+ term_scroll(term, 0,
+ b == MBT_WHEEL_UP ?
+ -term->rows / 2 : term->rows / 2);
+ }
+ }
+ return 0;
+ }
+ }
+
+ /*
+ * Any messages we don't process completely above are passed through to
+ * DefWindowProc() for default processing.
+ */
+ return DefWindowProc(hwnd, message, wParam, lParam);
+}
+
+/*
+ * Move the system caret. (We maintain one, even though it's
+ * invisible, for the benefit of blind people: apparently some
+ * helper software tracks the system caret, so we should arrange to
+ * have one.)
+ */
+void sys_cursor(void *frontend, int x, int y)
+{
+ int cx, cy;
+
+ if (!term->has_focus) return;
+
+ /*
+ * Avoid gratuitously re-updating the cursor position and IMM
+ * window if there's no actual change required.
+ */
+ cx = x * font_width + offset_width;
+ cy = y * font_height + offset_height;
+ if (cx == caret_x && cy == caret_y)
+ return;
+ caret_x = cx;
+ caret_y = cy;
+
+ sys_cursor_update();
+}
+
+static void sys_cursor_update(void)
+{
+ COMPOSITIONFORM cf;
+ HIMC hIMC;
+
+ if (!term->has_focus) return;
+
+ if (caret_x < 0 || caret_y < 0)
+ return;
+
+ SetCaretPos(caret_x, caret_y);
+
+ /* IMM calls on Win98 and beyond only */
+ if(osVersion.dwPlatformId == VER_PLATFORM_WIN32s) return; /* 3.11 */
+
+ if(osVersion.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS &&
+ osVersion.dwMinorVersion == 0) return; /* 95 */
+
+ /* we should have the IMM functions */
+ hIMC = ImmGetContext(hwnd);
+ cf.dwStyle = CFS_POINT;
+ cf.ptCurrentPos.x = caret_x;
+ cf.ptCurrentPos.y = caret_y;
+ ImmSetCompositionWindow(hIMC, &cf);
+
+ ImmReleaseContext(hwnd, hIMC);
+}
+
+/*
+ * Draw a line of text in the window, at given character
+ * coordinates, in given attributes.
+ *
+ * We are allowed to fiddle with the contents of `text'.
+ */
+void do_text_internal(Context ctx, int x, int y, wchar_t *text, int len,
+ unsigned long attr, int lattr)
+{
+ COLORREF fg, bg, t;
+ int nfg, nbg, nfont;
+ HDC hdc = ctx;
+ RECT line_box;
+ int force_manual_underline = 0;
+ int fnt_width, char_width;
+ int text_adjust = 0;
+ int xoffset = 0;
+ int maxlen, remaining, opaque;
+ static int *lpDx = NULL;
+ static int lpDx_len = 0;
+ int *lpDx_maybe;
+
+ lattr &= LATTR_MODE;
+
+ char_width = fnt_width = font_width * (1 + (lattr != LATTR_NORM));
+
+ if (attr & ATTR_WIDE)
+ char_width *= 2;
+
+ /* Only want the left half of double width lines */
+ if (lattr != LATTR_NORM && x*2 >= term->cols)
+ return;
+
+ x *= fnt_width;
+ y *= font_height;
+ x += offset_width;
+ y += offset_height;
+
+ if ((attr & TATTR_ACTCURS) && (cfg.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);
+ }
+
+ nfont = 0;
+ if (cfg.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:
+ break;
+ case LATTR_WIDE:
+ nfont |= FONT_WIDE;
+ break;
+ default:
+ nfont |= FONT_WIDE + FONT_HIGH;
+ break;
+ }
+ if (attr & ATTR_NARROW)
+ nfont |= FONT_NARROW;
+
+ /* 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;
+ break;
+ case 0xBB:
+ text_adjust = -1 * font_height / 5;
+ break;
+ case 0xBC:
+ text_adjust = font_height / 5;
+ break;
+ case 0xBD:
+ text_adjust = 2 * font_height / 5;
+ break;
+ }
+ if (lattr == LATTR_TOP || lattr == LATTR_BOT)
+ text_adjust *= 2;
+ text[0] = ucsdata.unitab_xterm['q'];
+ if (attr & ATTR_UNDER) {
+ attr &= ~ATTR_UNDER;
+ force_manual_underline = 1;
+ }
+ }
+
+ /* Anything left as an original character set is unprintable. */
+ if (DIRECT_CHAR(text[0])) {
+ int i;
+ for (i = 0; i < len; i++)
+ text[i] = 0xFFFD;
+ }
+
+ /* OEM CP */
+ 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))
+ nfont |= FONT_BOLD;
+ if (und_mode == UND_FONT && (attr & ATTR_UNDER))
+ nfont |= FONT_UNDERLINE;
+ another_font(nfont);
+ if (!fonts[nfont]) {
+ if (nfont & FONT_UNDERLINE)
+ force_manual_underline = 1;
+ /* Don't do the same for manual bold, it could be bad news. */
+
+ nfont &= ~(FONT_BOLD | FONT_UNDERLINE);
+ }
+ another_font(nfont);
+ if (!fonts[nfont])
+ nfont = FONT_NORMAL;
+ if (attr & ATTR_REVERSE) {
+ t = nfg;
+ nfg = nbg;
+ nbg = t;
+ }
+ if (bold_mode == BOLD_COLOURS && (attr & ATTR_BOLD)) {
+ if (nfg < 16) nfg |= 8;
+ else if (nfg >= 256) nfg |= 1;
+ }
+ if (bold_mode == BOLD_COLOURS && (attr & ATTR_BLINK)) {
+ if (nbg < 16) nbg |= 8;
+ else if (nbg >= 256) nbg |= 1;
+ }
+ fg = colours[nfg];
+ bg = colours[nbg];
+ SelectObject(hdc, fonts[nfont]);
+ SetTextColor(hdc, fg);
+ SetBkColor(hdc, bg);
+ if (attr & TATTR_COMBINING)
+ SetBkMode(hdc, TRANSPARENT);
+ else
+ SetBkMode(hdc, OPAQUE);
+ line_box.left = x;
+ line_box.top = y;
+ line_box.right = x + char_width * len;
+ line_box.bottom = y + font_height;
+
+ /* 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;
+
+ if (font_varpitch) {
+ /*
+ * If we're using a variable-pitch font, we unconditionally
+ * draw the glyphs one at a time and centre them in their
+ * character cells (which means in particular that we must
+ * disable the lpDx mechanism). This gives slightly odd but
+ * generally reasonable results.
+ */
+ xoffset = char_width / 2;
+ SetTextAlign(hdc, TA_TOP | TA_CENTER | TA_NOUPDATECP);
+ lpDx_maybe = NULL;
+ maxlen = 1;
+ } else {
+ /*
+ * In a fixed-pitch font, we draw the whole string in one go
+ * in the normal way.
+ */
+ xoffset = 0;
+ SetTextAlign(hdc, TA_TOP | TA_LEFT | TA_NOUPDATECP);
+ lpDx_maybe = lpDx;
+ maxlen = len;
+ }
+
+ opaque = TRUE; /* start by erasing the rectangle */
+ for (remaining = len; remaining > 0;
+ text += len, remaining -= len, x += char_width * len) {
+ 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);
+ }
+ }
+ {
+ int i;
+ for (i = 0; i < len; i++)
+ 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! */
+ /* To display on W9x I have to convert to UCS */
+ static wchar_t *uni_buf = 0;
+ static int uni_len = 0;
+ int nlen, mptr;
+ if (len > uni_len) {
+ sfree(uni_buf);
+ uni_len = len;
+ uni_buf = snewn(uni_len, wchar_t);
+ }
+
+ for(nlen = mptr = 0; mptr<len; mptr++) {
+ uni_buf[nlen] = 0xFFFD;
+ if (IsDBCSLeadByteEx(ucsdata.font_codepage,
+ (BYTE) text[mptr])) {
+ char dbcstext[2];
+ dbcstext[0] = text[mptr] & 0xFF;
+ dbcstext[1] = text[mptr+1] & 0xFF;
+ lpDx[nlen] += char_width;
+ MultiByteToWideChar(ucsdata.font_codepage, MB_USEGLYPHCHARS,
+ dbcstext, 2, uni_buf+nlen, 1);
+ mptr++;
+ }
+ else
+ {
+ char dbcstext[1];
+ dbcstext[0] = text[mptr] & 0xFF;
+ MultiByteToWideChar(ucsdata.font_codepage, MB_USEGLYPHCHARS,
+ dbcstext, 1, uni_buf+nlen, 1);
+ }
+ nlen++;
+ }
+ if (nlen <= 0)
+ return; /* Eeek! */
+
+ 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)) {
+ 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);
+ }
+
+ lpDx[0] = -1;
+ } else if (DIRECT_FONT(text[0])) {
+ static char *directbuf = NULL;
+ static int directlen = 0;
+ int i;
+ if (len > directlen) {
+ directlen = len;
+ directbuf = sresize(directbuf, directlen, char);
+ }
+
+ for (i = 0; i < len; i++)
+ directbuf[i] = text[i] & 0xFF;
+
+ 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)) {
+ 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 ...
+ * yuk.
+ *
+ * Or ... I could do a test print with "W", and use +1
+ * or -1 for this shift depending on if the leftmost
+ * column is blank...
+ */
+ ExtTextOut(hdc, x + xoffset - 1,
+ y - font_height * (lattr ==
+ LATTR_BOT) + text_adjust,
+ ETO_CLIPPED, &line_box, directbuf, len, lpDx_maybe);
+ }
+ } else {
+ /* And 'normal' unicode characters */
+ static WCHAR *wbuf = NULL;
+ static int wlen = 0;
+ int i;
+
+ if (wlen < len) {
+ sfree(wbuf);
+ wlen = len;
+ wbuf = snewn(wlen, WCHAR);
+ }
+
+ for (i = 0; i < len; i++)
+ wbuf[i] = text[i];
+
+ /* print Glyphs as they are, without Windows' Shaping*/
+ general_textout(hdc, x + xoffset,
+ 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)) {
+ SetBkMode(hdc, TRANSPARENT);
+ ExtTextOutW(hdc, x + xoffset - 1,
+ y - font_height * (lattr ==
+ LATTR_BOT) + text_adjust,
+ ETO_CLIPPED, &line_box, wbuf, len, lpDx_maybe);
+ }
+ }
+
+ /*
+ * If we're looping round again, stop erasing the background
+ * rectangle.
+ */
+ SetBkMode(hdc, TRANSPARENT);
+ opaque = FALSE;
+ }
+ if (lattr != LATTR_TOP && (force_manual_underline ||
+ (und_mode == UND_LINE
+ && (attr & ATTR_UNDER)))) {
+ HPEN oldpen;
+ int dec = descent;
+ if (lattr == LATTR_BOT)
+ dec = dec * 2 - font_height;
+
+ oldpen = SelectObject(hdc, CreatePen(PS_SOLID, 0, fg));
+ MoveToEx(hdc, x, y + dec, NULL);
+ LineTo(hdc, x + len * char_width, y + dec);
+ oldpen = SelectObject(hdc, oldpen);
+ DeleteObject(oldpen);
+ }
+}
+
+/*
+ * Wrapper that handles combining characters.
+ */
+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;
+ while (len--) {
+ 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);
+}
+
+void do_cursor(Context ctx, int x, int y, wchar_t *text, int len,
+ unsigned long attr, int lattr)
+{
+
+ int fnt_width;
+ int char_width;
+ HDC hdc = ctx;
+ int ctype = cfg.cursor_type;
+
+ lattr &= LATTR_MODE;
+
+ if ((attr & TATTR_ACTCURS) && (ctype == 0 || term->big_cursor)) {
+ if (*text != UCSWIDE) {
+ do_text(ctx, x, y, text, len, attr, lattr);
+ return;
+ }
+ ctype = 2;
+ attr |= TATTR_RIGHTCURS;
+ }
+
+ fnt_width = char_width = font_width * (1 + (lattr != LATTR_NORM));
+ if (attr & ATTR_WIDE)
+ char_width *= 2;
+ x *= fnt_width;
+ y *= font_height;
+ x += offset_width;
+ y += offset_height;
+
+ if ((attr & TATTR_PASCURS) && (ctype == 0 || term->big_cursor)) {
+ POINT pts[5];
+ HPEN oldpen;
+ pts[0].x = pts[1].x = pts[4].x = x;
+ pts[2].x = pts[3].x = x + char_width - 1;
+ pts[0].y = pts[3].y = pts[4].y = y;
+ pts[1].y = pts[2].y = y + font_height - 1;
+ oldpen = SelectObject(hdc, CreatePen(PS_SOLID, 0, colours[261]));
+ Polyline(hdc, pts, 5);
+ oldpen = SelectObject(hdc, oldpen);
+ DeleteObject(oldpen);
+ } else if ((attr & (TATTR_ACTCURS | TATTR_PASCURS)) && ctype != 0) {
+ int startx, starty, dx, dy, length, i;
+ if (ctype == 1) {
+ startx = x;
+ starty = y + descent;
+ dx = 1;
+ dy = 0;
+ length = char_width;
+ } else {
+ int xadjust = 0;
+ if (attr & TATTR_RIGHTCURS)
+ xadjust = char_width - 1;
+ startx = x + xadjust;
+ starty = y;
+ dx = 0;
+ dy = 1;
+ length = font_height;
+ }
+ if (attr & TATTR_ACTCURS) {
+ HPEN oldpen;
+ oldpen =
+ SelectObject(hdc, CreatePen(PS_SOLID, 0, colours[261]));
+ MoveToEx(hdc, startx, starty, NULL);
+ LineTo(hdc, startx + dx * length, starty + dy * length);
+ oldpen = SelectObject(hdc, oldpen);
+ DeleteObject(oldpen);
+ } else {
+ for (i = 0; i < length; i++) {
+ if (i % 2 == 0) {
+ SetPixel(hdc, startx, starty, colours[261]);
+ }
+ startx += dx;
+ starty += dy;
+ }
+ }
+ }
+}
+
+/* This function gets the actual width of a character in the normal font.
+ */
+int char_width(Context ctx, int uc) {
+ HDC hdc = ctx;
+ int ibuf = 0;
+
+ /* If the font max is the same as the font ave width then this
+ * function is a no-op.
+ */
+ if (!font_dualwidth) return 1;
+
+ switch (uc & CSET_MASK) {
+ case CSET_ASCII:
+ uc = ucsdata.unitab_line[uc & 0xFF];
+ break;
+ case CSET_LINEDRW:
+ uc = ucsdata.unitab_xterm[uc & 0xFF];
+ break;
+ case CSET_SCOACS:
+ uc = ucsdata.unitab_scoacs[uc & 0xFF];
+ break;
+ }
+ if (DIRECT_FONT(uc)) {
+ if (ucsdata.dbcs_screenfont) return 1;
+
+ /* Speedup, I know of no font where ascii is the wrong width */
+ if ((uc&~CSET_MASK) >= ' ' && (uc&~CSET_MASK)<= '~')
+ return 1;
+
+ if ( (uc & CSET_MASK) == CSET_ACP ) {
+ SelectObject(hdc, fonts[FONT_NORMAL]);
+ } else if ( (uc & CSET_MASK) == CSET_OEMCP ) {
+ another_font(FONT_OEM);
+ if (!fonts[FONT_OEM]) return 0;
+
+ SelectObject(hdc, fonts[FONT_OEM]);
+ } else
+ return 0;
+
+ if ( GetCharWidth32(hdc, uc&~CSET_MASK, uc&~CSET_MASK, &ibuf) != 1 &&
+ GetCharWidth(hdc, uc&~CSET_MASK, uc&~CSET_MASK, &ibuf) != 1)
+ return 0;
+ } else {
+ /* Speedup, I know of no font where ascii is the wrong width */
+ if (uc >= ' ' && uc <= '~') return 1;
+
+ SelectObject(hdc, fonts[FONT_NORMAL]);
+ if ( GetCharWidth32W(hdc, uc, uc, &ibuf) == 1 )
+ /* Okay that one worked */ ;
+ else if ( GetCharWidthW(hdc, uc, uc, &ibuf) == 1 )
+ /* This should work on 9x too, but it's "less accurate" */ ;
+ else
+ return 0;
+ }
+
+ ibuf += font_width / 2 -1;
+ ibuf /= font_width;
+
+ return ibuf;
+}
+
+/*
+ * 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
+ * to indicate a NUL-terminated "special" string.
+ */
+static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam,
+ unsigned char *output)
+{
+ 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;
+
+ HKL kbd_layout = GetKeyboardLayout(0);
+
+ /* keys is for ToAsciiEx. There's some ick here, see below. */
+ static WORD keys[3];
+ static int compose_char = 0;
+ static WPARAM compose_key = 0;
+
+ r = GetKeyboardState(keystate);
+ if (!r)
+ memset(keystate, 0, sizeof(keystate));
+ else {
+#if 0
+#define SHOW_TOASCII_RESULT
+ { /* Tell us all about key events */
+ static BYTE oldstate[256];
+ static int first = 1;
+ static int scan;
+ int ch;
+ if (first)
+ memcpy(oldstate, keystate, sizeof(oldstate));
+ first = 0;
+
+ if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) == KF_REPEAT) {
+ debug(("+"));
+ } else if ((HIWORD(lParam) & KF_UP)
+ && scan == (HIWORD(lParam) & 0xFF)) {
+ debug((". U"));
+ } else {
+ debug((".\n"));
+ if (wParam >= VK_F1 && wParam <= VK_F20)
+ debug(("K_F%d", wParam + 1 - VK_F1));
+ else
+ switch (wParam) {
+ case VK_SHIFT:
+ debug(("SHIFT"));
+ break;
+ case VK_CONTROL:
+ debug(("CTRL"));
+ break;
+ case VK_MENU:
+ debug(("ALT"));
+ break;
+ default:
+ debug(("VK_%02x", wParam));
+ }
+ if (message == WM_SYSKEYDOWN || message == WM_SYSKEYUP)
+ debug(("*"));
+ debug((", S%02x", scan = (HIWORD(lParam) & 0xFF)));
+
+ ch = MapVirtualKeyEx(wParam, 2, kbd_layout);
+ 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 ((keystate[VK_SHIFT] & 0x80) != 0)
+ debug((", S"));
+ if ((keystate[VK_CONTROL] & 0x80) != 0)
+ debug((", C"));
+ if ((HIWORD(lParam) & KF_EXTENDED))
+ debug((", E"));
+ if ((HIWORD(lParam) & KF_UP))
+ debug((", U"));
+ }
+
+ if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) == KF_REPEAT);
+ else if ((HIWORD(lParam) & KF_UP))
+ oldstate[wParam & 0xFF] ^= 0x80;
+ else
+ oldstate[wParam & 0xFF] ^= 0x81;
+
+ for (ch = 0; ch < 256; ch++)
+ if (oldstate[ch] != keystate[ch])
+ debug((", M%02x=%02x", ch, keystate[ch]));
+
+ memcpy(oldstate, keystate, sizeof(oldstate));
+ }
+#endif
+
+ if (wParam == VK_MENU && (HIWORD(lParam) & KF_EXTENDED)) {
+ 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))
+ && wParam == VK_NUMLOCK && !(keystate[VK_SHIFT] & 0x80)) {
+
+ wParam = VK_EXECUTE;
+
+ /* UnToggle NUMLock */
+ if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) == 0)
+ keystate[VK_NUMLOCK] ^= 1;
+ }
+
+ /* And write back the 'adjusted' state */
+ SetKeyboardState(keystate);
+ }
+
+ /* Disable Auto repeat if required */
+ if (term->repeat_off &&
+ (HIWORD(lParam) & (KF_UP | KF_REPEAT)) == KF_REPEAT)
+ return 0;
+
+ if ((HIWORD(lParam) & KF_ALTDOWN) && (keystate[VK_RMENU] & 0x80) == 0)
+ left_alt = 1;
+
+ 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)
+ keystate[VK_MENU] = 0;
+ else {
+ keystate[VK_RMENU] = 0x80;
+ left_alt = 0;
+ }
+ }
+
+ scan = (HIWORD(lParam) & (KF_UP | KF_EXTENDED | 0xFF));
+ 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) {
+ if (wParam == VK_MENU && (HIWORD(lParam) & KF_EXTENDED))
+ compose_key = wParam;
+ }
+ if (wParam == VK_APPS)
+ compose_key = wParam;
+ }
+
+ if (wParam == compose_key) {
+ 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;
+ else
+ compose_state = 0;
+ } else if (compose_state == 1 && wParam != VK_CONTROL)
+ compose_state = 0;
+
+ 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 ((HIWORD(lParam) & KF_EXTENDED) == 0) {
+ int nParam = 0;
+ switch (wParam) {
+ case VK_INSERT:
+ nParam = VK_NUMPAD0;
+ break;
+ case VK_END:
+ nParam = VK_NUMPAD1;
+ break;
+ case VK_DOWN:
+ nParam = VK_NUMPAD2;
+ break;
+ case VK_NEXT:
+ nParam = VK_NUMPAD3;
+ break;
+ case VK_LEFT:
+ nParam = VK_NUMPAD4;
+ break;
+ case VK_CLEAR:
+ nParam = VK_NUMPAD5;
+ break;
+ case VK_RIGHT:
+ nParam = VK_NUMPAD6;
+ break;
+ case VK_HOME:
+ nParam = VK_NUMPAD7;
+ break;
+ case VK_UP:
+ nParam = VK_NUMPAD8;
+ break;
+ case VK_PRIOR:
+ nParam = VK_NUMPAD9;
+ break;
+ case VK_DELETE:
+ nParam = VK_DECIMAL;
+ break;
+ }
+ if (nParam) {
+ if (keystate[VK_NUMLOCK] & 1)
+ shift_state |= 1;
+ wParam = nParam;
+ }
+ }
+ }
+
+ /* If a key is pressed and AltGr is not active */
+ if (key_down && (keystate[VK_RMENU] & 0x80) == 0 && !compose_state) {
+ /* Okay, prepare for most alts then ... */
+ if (left_alt)
+ *p++ = '\033';
+
+ /* Lets see if it's a pattern we know all about ... */
+ if (wParam == VK_PRIOR && shift_state == 1) {
+ SendMessage(hwnd, WM_VSCROLL, SB_PAGEUP, 0);
+ return 0;
+ }
+ if (wParam == VK_PRIOR && shift_state == 2) {
+ SendMessage(hwnd, WM_VSCROLL, SB_LINEUP, 0);
+ return 0;
+ }
+ if (wParam == VK_NEXT && shift_state == 1) {
+ SendMessage(hwnd, WM_VSCROLL, SB_PAGEDOWN, 0);
+ return 0;
+ }
+ if (wParam == VK_NEXT && shift_state == 2) {
+ SendMessage(hwnd, WM_VSCROLL, SB_LINEDOWN, 0);
+ return 0;
+ }
+ if ((wParam == VK_PRIOR || wParam == VK_NEXT) && shift_state == 3) {
+ term_scroll_to_selection(term, (wParam == VK_PRIOR ? 0 : 1));
+ return 0;
+ }
+ if (wParam == VK_INSERT && shift_state == 1) {
+ request_paste(NULL);
+ return 0;
+ }
+ if (left_alt && wParam == VK_F4 && cfg.alt_f4) {
+ return -1;
+ }
+ if (left_alt && wParam == VK_SPACE && cfg.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 ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) != KF_REPEAT)
+ flip_full_screen();
+ return -1;
+ }
+ /* Control-Numlock for app-keypad mode switch */
+ if (wParam == VK_PAUSE && shift_state == 2) {
+ term->app_keypad_keys ^= 1;
+ return 0;
+ }
+
+ /* Nethack keypad */
+ if (cfg.nethack_keypad && !left_alt) {
+ switch (wParam) {
+ case VK_NUMPAD1:
+ *p++ = "bB\002\002"[shift_state & 3];
+ return p - output;
+ case VK_NUMPAD2:
+ *p++ = "jJ\012\012"[shift_state & 3];
+ return p - output;
+ case VK_NUMPAD3:
+ *p++ = "nN\016\016"[shift_state & 3];
+ return p - output;
+ case VK_NUMPAD4:
+ *p++ = "hH\010\010"[shift_state & 3];
+ return p - output;
+ case VK_NUMPAD5:
+ *p++ = shift_state ? '.' : '.';
+ return p - output;
+ case VK_NUMPAD6:
+ *p++ = "lL\014\014"[shift_state & 3];
+ return p - output;
+ case VK_NUMPAD7:
+ *p++ = "yY\031\031"[shift_state & 3];
+ return p - output;
+ case VK_NUMPAD8:
+ *p++ = "kK\013\013"[shift_state & 3];
+ return p - output;
+ case VK_NUMPAD9:
+ *p++ = "uU\025\025"[shift_state & 3];
+ return p - output;
+ }
+ }
+
+ /* 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) {
+ case VK_EXECUTE:
+ xkey = 'P';
+ break;
+ case VK_DIVIDE:
+ xkey = 'Q';
+ break;
+ case VK_MULTIPLY:
+ xkey = 'R';
+ break;
+ case VK_SUBTRACT:
+ xkey = 'S';
+ break;
+ }
+ if (term->app_keypad_keys && !cfg.no_applic_k)
+ switch (wParam) {
+ case VK_NUMPAD0:
+ xkey = 'p';
+ break;
+ case VK_NUMPAD1:
+ xkey = 'q';
+ break;
+ case VK_NUMPAD2:
+ xkey = 'r';
+ break;
+ case VK_NUMPAD3:
+ xkey = 's';
+ break;
+ case VK_NUMPAD4:
+ xkey = 't';
+ break;
+ case VK_NUMPAD5:
+ xkey = 'u';
+ break;
+ case VK_NUMPAD6:
+ xkey = 'v';
+ break;
+ case VK_NUMPAD7:
+ xkey = 'w';
+ break;
+ case VK_NUMPAD8:
+ xkey = 'x';
+ break;
+ case VK_NUMPAD9:
+ xkey = 'y';
+ break;
+
+ case VK_DECIMAL:
+ xkey = 'n';
+ break;
+ case VK_ADD:
+ if (cfg.funky_type == FUNKY_XTERM) {
+ if (shift_state)
+ xkey = 'l';
+ else
+ xkey = 'k';
+ } else if (shift_state)
+ xkey = 'm';
+ else
+ xkey = 'l';
+ break;
+
+ case VK_DIVIDE:
+ if (cfg.funky_type == FUNKY_XTERM)
+ xkey = 'o';
+ break;
+ case VK_MULTIPLY:
+ if (cfg.funky_type == FUNKY_XTERM)
+ xkey = 'j';
+ break;
+ case VK_SUBTRACT:
+ if (cfg.funky_type == FUNKY_XTERM)
+ xkey = 'm';
+ break;
+
+ case VK_RETURN:
+ if (HIWORD(lParam) & KF_EXTENDED)
+ xkey = 'M';
+ break;
+ }
+ 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);
+ return p - output;
+ }
+ }
+
+ if (wParam == VK_BACK && shift_state == 0) { /* Backspace */
+ *p++ = (cfg.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++ = 0;
+ return -2;
+ }
+ if (wParam == VK_TAB && shift_state == 1) { /* Shift tab */
+ *p++ = 0x1B;
+ *p++ = '[';
+ *p++ = 'Z';
+ return p - output;
+ }
+ if (wParam == VK_SPACE && shift_state == 2) { /* Ctrl-Space */
+ *p++ = 0;
+ return p - output;
+ }
+ if (wParam == VK_SPACE && shift_state == 3) { /* Ctrl-Shift-Space */
+ *p++ = 160;
+ return p - output;
+ }
+ if (wParam == VK_CANCEL && shift_state == 2) { /* Ctrl-Break */
+ if (back)
+ back->special(backhandle, TS_BRK);
+ return 0;
+ }
+ if (wParam == VK_PAUSE) { /* Break/Pause */
+ *p++ = 26;
+ *p++ = 0;
+ return -2;
+ }
+ /* Control-2 to Control-8 are special */
+ if (shift_state == 2 && wParam >= '2' && wParam <= '8') {
+ *p++ = "\000\033\034\035\036\037\177"[wParam - '2'];
+ return p - output;
+ }
+ if (shift_state == 2 && (wParam == 0xBD || wParam == 0xBF)) {
+ *p++ = 0x1F;
+ return p - output;
+ }
+ if (shift_state == 2 && (wParam == 0xDF || wParam == 0xDC)) {
+ *p++ = 0x1C;
+ return p - output;
+ }
+ if (shift_state == 3 && wParam == 0xDE) {
+ *p++ = 0x1E; /* Ctrl-~ == Ctrl-^ in xterm at least */
+ return p - output;
+ }
+ if (shift_state == 0 && wParam == VK_RETURN && term->cr_lf_return) {
+ *p++ = '\r';
+ *p++ = '\n';
+ return p - output;
+ }
+
+ /*
+ * Next, all the keys that do tilde codes. (ESC '[' nn '~',
+ * for integer decimal nn.)
+ *
+ * We also deal with the weird ones here. Linux VCs replace F1
+ * to F5 by ESC [ [ A to ESC [ [ E. rxvt doesn't do _that_, but
+ * does replace Home and End (1~ and 4~) by ESC [ H and ESC O w
+ * respectively.
+ */
+ code = 0;
+ switch (wParam) {
+ case VK_F1:
+ code = (keystate[VK_SHIFT] & 0x80 ? 23 : 11);
+ break;
+ case VK_F2:
+ code = (keystate[VK_SHIFT] & 0x80 ? 24 : 12);
+ break;
+ case VK_F3:
+ code = (keystate[VK_SHIFT] & 0x80 ? 25 : 13);
+ break;
+ case VK_F4:
+ code = (keystate[VK_SHIFT] & 0x80 ? 26 : 14);
+ break;
+ case VK_F5:
+ code = (keystate[VK_SHIFT] & 0x80 ? 28 : 15);
+ break;
+ case VK_F6:
+ code = (keystate[VK_SHIFT] & 0x80 ? 29 : 17);
+ break;
+ case VK_F7:
+ code = (keystate[VK_SHIFT] & 0x80 ? 31 : 18);
+ break;
+ case VK_F8:
+ code = (keystate[VK_SHIFT] & 0x80 ? 32 : 19);
+ break;
+ case VK_F9:
+ code = (keystate[VK_SHIFT] & 0x80 ? 33 : 20);
+ break;
+ case VK_F10:
+ code = (keystate[VK_SHIFT] & 0x80 ? 34 : 21);
+ break;
+ case VK_F11:
+ code = 23;
+ break;
+ case VK_F12:
+ code = 24;
+ break;
+ case VK_F13:
+ code = 25;
+ break;
+ case VK_F14:
+ code = 26;
+ break;
+ case VK_F15:
+ code = 28;
+ break;
+ case VK_F16:
+ code = 29;
+ break;
+ case VK_F17:
+ code = 31;
+ break;
+ case VK_F18:
+ code = 32;
+ break;
+ case VK_F19:
+ code = 33;
+ break;
+ case VK_F20:
+ code = 34;
+ break;
+ }
+ if ((shift_state&2) == 0) switch (wParam) {
+ case VK_HOME:
+ code = 1;
+ break;
+ case VK_INSERT:
+ code = 2;
+ break;
+ case VK_DELETE:
+ code = 3;
+ break;
+ case VK_END:
+ code = 4;
+ break;
+ case VK_PRIOR:
+ code = 5;
+ break;
+ case VK_NEXT:
+ code = 6;
+ break;
+ }
+ /* Reorder edit keys to physical order */
+ if (cfg.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) {
+ char codes[] = "MNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz@[\\]^_`{";
+ int index = 0;
+ switch (wParam) {
+ case VK_F1: index = 0; break;
+ case VK_F2: index = 1; break;
+ case VK_F3: index = 2; break;
+ case VK_F4: index = 3; break;
+ case VK_F5: index = 4; break;
+ case VK_F6: index = 5; break;
+ case VK_F7: index = 6; break;
+ case VK_F8: index = 7; break;
+ case VK_F9: index = 8; break;
+ case VK_F10: index = 9; break;
+ case VK_F11: index = 10; break;
+ case VK_F12: index = 11; break;
+ }
+ 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 */
+ 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) {
+ int offt = 0;
+ if (code > 15)
+ offt++;
+ if (code > 21)
+ offt++;
+ if (term->vt52_mode)
+ p += sprintf((char *) p, "\x1B%c", code + 'P' - 11 - offt);
+ else
+ p +=
+ sprintf((char *) p, "\x1BO%c", code + 'P' - 11 - offt);
+ return p - output;
+ }
+ if (cfg.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 (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)) {
+ p += sprintf((char *) p, code == 1 ? "\x1B[H" : "\x1BOw");
+ return p - output;
+ }
+ if (code) {
+ p += sprintf((char *) p, "\x1B[%d~", code);
+ return p - output;
+ }
+
+ /*
+ * Now the remaining keys (arrows and Keypad 5. Keypad 5 for
+ * some reason seems to send VK_CLEAR to Windows...).
+ */
+ {
+ char xkey = 0;
+ switch (wParam) {
+ case VK_UP:
+ xkey = 'A';
+ break;
+ case VK_DOWN:
+ xkey = 'B';
+ break;
+ case VK_RIGHT:
+ xkey = 'C';
+ break;
+ case VK_LEFT:
+ xkey = 'D';
+ break;
+ case VK_CLEAR:
+ xkey = 'G';
+ break;
+ }
+ if (xkey) {
+ p += format_arrow_key(p, term, xkey, shift_state);
+ return p - output;
+ }
+ }
+
+ /*
+ * Finally, deal with Return ourselves. (Win95 seems to
+ * foul it up when Alt is pressed, for some reason.)
+ */
+ if (wParam == VK_RETURN) { /* Return */
+ *p++ = 0x0D;
+ *p++ = 0;
+ return -2;
+ }
+
+ if (left_alt && wParam >= VK_NUMPAD0 && wParam <= VK_NUMPAD9)
+ alt_sum = alt_sum * 10 + wParam - VK_NUMPAD0;
+ else
+ alt_sum = 0;
+ }
+
+ /* Okay we've done everything interesting; let windows deal with
+ * 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) {
+ 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) {
+ /* 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"
+ * 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 {
+ r = ToAsciiEx(wParam, scan, keystate, keys, 0, kbd_layout);
+ }
+#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]));
+ }
+ } else if (r > 0) {
+ int r1;
+ debug((", ASC("));
+ for (r1 = 0; r1 < r; r1++) {
+ debug(("%s%d", r1 ? "," : "", keys[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];
+
+ if (compose_state == 2 && (ch & 0x80) == 0 && ch > ' ') {
+ compose_char = ch;
+ compose_state++;
+ continue;
+ }
+ if (compose_state == 3 && (ch & 0x80) == 0 && ch > ' ') {
+ int nc;
+ compose_state = 0;
+
+ if ((nc = check_compose(compose_char, ch)) == -1) {
+ MessageBeep(MB_ICONHAND);
+ return 0;
+ }
+ keybuf = nc;
+ term_seen_key_event(term);
+ if (ldisc)
+ luni_send(ldisc, &keybuf, 1, 1);
+ continue;
+ }
+
+ compose_state = 0;
+
+ if (!key_down) {
+ if (alt_sum) {
+ if (in_utf(term) || ucsdata.dbcs_screenfont) {
+ keybuf = alt_sum;
+ term_seen_key_event(term);
+ if (ldisc)
+ luni_send(ldisc, &keybuf, 1, 1);
+ } else {
+ 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
+ * Windows to hold off on KEYDOWN
+ * messages. We _have_ to buffer
+ * everything we're sent.
+ */
+ term_seen_key_event(term);
+ if (ldisc)
+ ldisc_send(ldisc, &ch, 1, 1);
+ }
+ alt_sum = 0;
+ } else {
+ term_seen_key_event(term);
+ if (ldisc)
+ lpage_send(ldisc, kbd_codepage, &ch, 1, 1);
+ }
+ } else {
+ if(capsOn && ch < 0x80) {
+ WCHAR cbuf[2];
+ cbuf[0] = 27;
+ cbuf[1] = xlat_uskbd2cyrllic(ch);
+ term_seen_key_event(term);
+ if (ldisc)
+ luni_send(ldisc, cbuf+!left_alt, 1+!!left_alt, 1);
+ } else {
+ char cbuf[2];
+ cbuf[0] = '\033';
+ cbuf[1] = ch;
+ term_seen_key_event(term);
+ if (ldisc)
+ lpage_send(ldisc, kbd_codepage,
+ cbuf+!left_alt, 1+!!left_alt, 1);
+ }
+ }
+ show_mouseptr(0);
+ }
+
+ /* This is so the ALT-Numpad and dead keys work correctly. */
+ keys[0] = 0;
+
+ return p - output;
+ }
+ /* If we're definitly not building up an ALT-54321 then clear it */
+ if (!left_alt)
+ keys[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;
+ }
+
+ /*
+ * 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)
+ return 0;
+
+ return -1;
+}
+
+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))
+ 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))
+ 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)
+ return;
+
+ si.cbSize = sizeof(si);
+ si.fMask = SIF_ALL | SIF_DISABLENOSCROLL;
+ si.nMin = 0;
+ si.nMax = total - 1;
+ si.nPage = page;
+ si.nPos = start;
+ if (hwnd)
+ SetScrollInfo(hwnd, SB_VERT, &si, TRUE);
+}
+
+Context get_ctx(void *frontend)
+{
+ HDC hdc;
+ if (hwnd) {
+ hdc = GetDC(hwnd);
+ if (hdc && pal)
+ SelectPalette(hdc, pal, FALSE);
+ return hdc;
+ } else
+ return NULL;
+}
+
+void free_ctx(Context ctx)
+{
+ SelectPalette(ctx, GetStockObject(DEFAULT_PALETTE), FALSE);
+ ReleaseDC(hwnd, ctx);
+}
+
+static void real_palette_set(int n, int r, int g, int b)
+{
+ if (pal) {
+ logpal->palPalEntry[n].peRed = r;
+ logpal->palPalEntry[n].peGreen = g;
+ logpal->palPalEntry[n].peBlue = b;
+ logpal->palPalEntry[n].peFlags = PC_NOCOLLAPSE;
+ colours[n] = PALETTERGB(r, g, b);
+ SetPaletteEntries(pal, 0, NALLCOLOURS, logpal->palPalEntry);
+ } else
+ colours[n] = RGB(r, g, b);
+}
+
+void palette_set(void *frontend, int n, int r, int g, int b)
+{
+ if (n >= 16)
+ n += 256 - 16;
+ if (n > NALLCOLOURS)
+ return;
+ real_palette_set(n, r, g, b);
+ if (pal) {
+ HDC hdc = get_ctx(frontend);
+ UnrealizeObject(pal);
+ RealizePalette(hdc);
+ free_ctx(hdc);
+ } else {
+ if (n == (ATTR_DEFBG>>ATTR_BGSHIFT))
+ /* If Default Background changes, we need to ensure any
+ * space between the text area and the window border is
+ * redrawn. */
+ InvalidateRect(hwnd, NULL, TRUE);
+ }
+}
+
+void palette_reset(void *frontend)
+{
+ int i;
+
+ /* And this */
+ for (i = 0; i < NALLCOLOURS; i++) {
+ if (pal) {
+ logpal->palPalEntry[i].peRed = defpal[i].rgbtRed;
+ logpal->palPalEntry[i].peGreen = defpal[i].rgbtGreen;
+ logpal->palPalEntry[i].peBlue = defpal[i].rgbtBlue;
+ logpal->palPalEntry[i].peFlags = 0;
+ colours[i] = PALETTERGB(defpal[i].rgbtRed,
+ defpal[i].rgbtGreen,
+ defpal[i].rgbtBlue);
+ } else
+ colours[i] = RGB(defpal[i].rgbtRed,
+ defpal[i].rgbtGreen, defpal[i].rgbtBlue);
+ }
+
+ if (pal) {
+ HDC hdc;
+ SetPaletteEntries(pal, 0, NALLCOLOURS, logpal->palPalEntry);
+ hdc = get_ctx(frontend);
+ RealizePalette(hdc);
+ free_ctx(hdc);
+ } else {
+ /* Default Background may have changed. Ensure any space between
+ * text area and window border is redrawn. */
+ InvalidateRect(hwnd, NULL, TRUE);
+ }
+}
+
+void write_aclip(void *frontend, char *data, int len, int must_deselect)
+{
+ HGLOBAL clipdata;
+ void *lock;
+
+ clipdata = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, len + 1);
+ if (!clipdata)
+ return;
+ lock = GlobalLock(clipdata);
+ if (!lock)
+ return;
+ memcpy(lock, data, len);
+ ((unsigned char *) lock)[len] = 0;
+ GlobalUnlock(clipdata);
+
+ if (!must_deselect)
+ SendMessage(hwnd, WM_IGNORE_CLIP, TRUE, 0);
+
+ if (OpenClipboard(hwnd)) {
+ EmptyClipboard();
+ SetClipboardData(CF_TEXT, clipdata);
+ CloseClipboard();
+ } else
+ GlobalFree(clipdata);
+
+ if (!must_deselect)
+ SendMessage(hwnd, WM_IGNORE_CLIP, FALSE, 0);
+}
+
+/*
+ * Note: unlike write_aclip() this will not append a nul.
+ */
+void write_clip(void *frontend, wchar_t * data, int *attr, int len, int must_deselect)
+{
+ HGLOBAL clipdata, clipdata2, clipdata3;
+ int len2;
+ void *lock, *lock2, *lock3;
+
+ len2 = WideCharToMultiByte(CP_ACP, 0, data, len, 0, 0, NULL, NULL);
+
+ clipdata = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE,
+ len * sizeof(wchar_t));
+ clipdata2 = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, len2);
+
+ if (!clipdata || !clipdata2) {
+ if (clipdata)
+ GlobalFree(clipdata);
+ if (clipdata2)
+ GlobalFree(clipdata2);
+ return;
+ }
+ if (!(lock = GlobalLock(clipdata)))
+ return;
+ if (!(lock2 = GlobalLock(clipdata2)))
+ return;
+
+ memcpy(lock, data, len * sizeof(wchar_t));
+ WideCharToMultiByte(CP_ACP, 0, data, len, lock2, len2, NULL, NULL);
+
+ if (cfg.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;
+ int rtfsize = 0;
+ int multilen, blen, alen, totallen, i;
+ char before[16], after[4];
+ int fgcolour, lastfgcolour = 0;
+ int bgcolour, lastbgcolour = 0;
+ int attrBold, lastAttrBold = 0;
+ int attrUnder, lastAttrUnder = 0;
+ int palette[NALLCOLOURS];
+ int numcolours;
+
+ get_unitab(CP_ACP, unitab, 0);
+
+ rtfsize = 100 + strlen(cfg.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);
+
+ /*
+ * Add colour palette
+ * {\colortbl ;\red255\green0\blue0;\red0\green0\blue128;}
+ */
+
+ /*
+ * First - Determine all colours in use
+ * o Foregound and background colours share the same palette
+ */
+ if (attr) {
+ memset(palette, 0, sizeof(palette));
+ for (i = 0; i < (len-1); i++) {
+ fgcolour = ((attr[i] & ATTR_FGMASK) >> ATTR_FGSHIFT);
+ bgcolour = ((attr[i] & ATTR_BGMASK) >> ATTR_BGSHIFT);
+
+ if (attr[i] & ATTR_REVERSE) {
+ int tmpcolour = fgcolour; /* Swap foreground and background */
+ fgcolour = bgcolour;
+ bgcolour = tmpcolour;
+ }
+
+ if (bold_mode == BOLD_COLOURS && (attr[i] & ATTR_BOLD)) {
+ if (fgcolour < 8) /* ANSI colours */
+ fgcolour += 8;
+ else if (fgcolour >= 256) /* Default colours */
+ fgcolour ++;
+ }
+
+ if (attr[i] & ATTR_BLINK) {
+ if (bgcolour < 8) /* ANSI colours */
+ bgcolour += 8;
+ else if (bgcolour >= 256) /* Default colours */
+ bgcolour ++;
+ }
+
+ palette[fgcolour]++;
+ palette[bgcolour]++;
+ }
+
+ /*
+ * Next - Create a reduced palette
+ */
+ numcolours = 0;
+ for (i = 0; i < NALLCOLOURS; i++) {
+ if (palette[i] != 0)
+ palette[i] = ++numcolours;
+ }
+
+ /*
+ * Finally - Write the colour table
+ */
+ rtf = sresize(rtf, rtfsize + (numcolours * 25), char);
+ strcat(rtf, "{\\colortbl ;");
+ rtflen = strlen(rtf);
+
+ for (i = 0; i < NALLCOLOURS; i++) {
+ if (palette[i] != 0) {
+ rtflen += sprintf(&rtf[rtflen], "\\red%d\\green%d\\blue%d;", defpal[i].rgbtRed, defpal[i].rgbtGreen, defpal[i].rgbtBlue);
+ }
+ }
+ strcpy(&rtf[rtflen], "}");
+ rtflen ++;
+ }
+
+ /*
+ * We want to construct a piece of RTF that specifies the
+ * same Unicode text. To do this we will read back in
+ * parallel from the Unicode data in `udata' and the
+ * non-Unicode data in `tdata'. For each character in
+ * `tdata' which becomes the right thing in `udata' when
+ * looked up in `unitab', we just copy straight over from
+ * tdata. For each one that doesn't, we must WCToMB it
+ * individually and produce a \u escape sequence.
+ *
+ * It would probably be more robust to just bite the bullet
+ * and WCToMB each individual Unicode character one by one,
+ * then MBToWC each one back to see if it was an accurate
+ * translation; but that strikes me as a horrifying number
+ * of Windows API calls so I want to see if this faster way
+ * will work. If it screws up badly we can always revert to
+ * the simple and slow way.
+ */
+ while (tindex < len2 && uindex < len &&
+ tdata[tindex] && udata[uindex]) {
+ if (tindex + 1 < len2 &&
+ tdata[tindex] == '\r' &&
+ tdata[tindex+1] == '\n') {
+ tindex++;
+ uindex++;
+ }
+
+ /*
+ * Set text attributes
+ */
+ if (attr) {
+ if (rtfsize < rtflen + 64) {
+ rtfsize = rtflen + 512;
+ rtf = sresize(rtf, rtfsize, char);
+ }
+
+ /*
+ * Determine foreground and background colours
+ */
+ fgcolour = ((attr[tindex] & ATTR_FGMASK) >> ATTR_FGSHIFT);
+ bgcolour = ((attr[tindex] & ATTR_BGMASK) >> ATTR_BGSHIFT);
+
+ if (attr[tindex] & ATTR_REVERSE) {
+ int tmpcolour = fgcolour; /* Swap foreground and background */
+ fgcolour = bgcolour;
+ bgcolour = tmpcolour;
+ }
+
+ if (bold_mode == BOLD_COLOURS && (attr[tindex] & ATTR_BOLD)) {
+ if (fgcolour < 8) /* ANSI colours */
+ fgcolour += 8;
+ else if (fgcolour >= 256) /* Default colours */
+ fgcolour ++;
+ }
+
+ if (attr[tindex] & ATTR_BLINK) {
+ if (bgcolour < 8) /* ANSI colours */
+ bgcolour += 8;
+ else if (bgcolour >= 256) /* Default colours */
+ bgcolour ++;
+ }
+
+ /*
+ * Collect other attributes
+ */
+ if (bold_mode != BOLD_COLOURS)
+ attrBold = attr[tindex] & ATTR_BOLD;
+ else
+ attrBold = 0;
+
+ attrUnder = attr[tindex] & ATTR_UNDER;
+
+ /*
+ * Reverse video
+ * o If video isn't reversed, ignore colour attributes for default foregound
+ * or background.
+ * o Special case where bolded text is displayed using the default foregound
+ * and background colours - force to bolded RTF.
+ */
+ 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)
+ attrBold = ATTR_BOLD; /* Emphasize text with bold attribute */
+
+ fgcolour = -1; /* No coloring */
+ }
+ }
+
+ /*
+ * Write RTF text attributes
+ */
+ if (lastfgcolour != fgcolour) {
+ lastfgcolour = fgcolour;
+ rtflen += sprintf(&rtf[rtflen], "\\cf%d ", (fgcolour >= 0) ? palette[fgcolour] : 0);
+ }
+
+ if (lastbgcolour != bgcolour) {
+ lastbgcolour = bgcolour;
+ rtflen += sprintf(&rtf[rtflen], "\\highlight%d ", (bgcolour >= 0) ? palette[bgcolour] : 0);
+ }
+
+ if (lastAttrBold != attrBold) {
+ lastAttrBold = attrBold;
+ rtflen += sprintf(&rtf[rtflen], "%s", attrBold ? "\\b " : "\\b0 ");
+ }
+
+ if (lastAttrUnder != attrUnder) {
+ lastAttrUnder = attrUnder;
+ rtflen += sprintf(&rtf[rtflen], "%s", attrUnder ? "\\ul " : "\\ulnone ");
+ }
+ }
+
+ if (unitab[tdata[tindex]] == udata[uindex]) {
+ multilen = 1;
+ before[0] = '\0';
+ after[0] = '\0';
+ blen = alen = 0;
+ } else {
+ multilen = WideCharToMultiByte(CP_ACP, 0, unitab+uindex, 1,
+ NULL, 0, NULL, NULL);
+ if (multilen != 1) {
+ blen = sprintf(before, "{\\uc%d\\u%d", multilen,
+ udata[uindex]);
+ alen = 1; strcpy(after, "}");
+ } else {
+ blen = sprintf(before, "\\u%d", udata[uindex]);
+ alen = 0; after[0] = '\0';
+ }
+ }
+ assert(tindex + multilen <= len2);
+ totallen = blen + alen;
+ for (i = 0; i < multilen; i++) {
+ if (tdata[tindex+i] == '\\' ||
+ tdata[tindex+i] == '{' ||
+ tdata[tindex+i] == '}')
+ totallen += 2;
+ else if (tdata[tindex+i] == 0x0D || tdata[tindex+i] == 0x0A)
+ totallen += 6; /* \par\r\n */
+ else if (tdata[tindex+i] > 0x7E || tdata[tindex+i] < 0x20)
+ totallen += 4;
+ else
+ totallen++;
+ }
+
+ if (rtfsize < rtflen + totallen + 3) {
+ rtfsize = rtflen + totallen + 512;
+ rtf = sresize(rtf, rtfsize, char);
+ }
+
+ strcpy(rtf + rtflen, before); rtflen += blen;
+ for (i = 0; i < multilen; i++) {
+ if (tdata[tindex+i] == '\\' ||
+ tdata[tindex+i] == '{' ||
+ tdata[tindex+i] == '}') {
+ rtf[rtflen++] = '\\';
+ rtf[rtflen++] = tdata[tindex+i];
+ } else if (tdata[tindex+i] == 0x0D || tdata[tindex+i] == 0x0A) {
+ rtflen += sprintf(rtf+rtflen, "\\par\r\n");
+ } else if (tdata[tindex+i] > 0x7E || tdata[tindex+i] < 0x20) {
+ rtflen += sprintf(rtf+rtflen, "\\'%02x", tdata[tindex+i]);
+ } else {
+ rtf[rtflen++] = tdata[tindex+i];
+ }
+ }
+ strcpy(rtf + rtflen, after); rtflen += alen;
+
+ tindex += multilen;
+ uindex++;
+ }
+
+ rtf[rtflen++] = '}'; /* Terminate RTF stream */
+ rtf[rtflen++] = '\0';
+ rtf[rtflen++] = '\0';
+
+ clipdata3 = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, rtflen);
+ if (clipdata3 && (lock3 = GlobalLock(clipdata3)) != NULL) {
+ memcpy(lock3, rtf, rtflen);
+ GlobalUnlock(clipdata3);
+ }
+ sfree(rtf);
+ } else
+ clipdata3 = NULL;
+
+ GlobalUnlock(clipdata);
+ GlobalUnlock(clipdata2);
+
+ if (!must_deselect)
+ SendMessage(hwnd, WM_IGNORE_CLIP, TRUE, 0);
+
+ if (OpenClipboard(hwnd)) {
+ EmptyClipboard();
+ SetClipboardData(CF_UNICODETEXT, clipdata);
+ SetClipboardData(CF_TEXT, clipdata2);
+ if (clipdata3)
+ SetClipboardData(RegisterClipboardFormat(CF_RTF), clipdata3);
+ CloseClipboard();
+ } else {
+ GlobalFree(clipdata);
+ GlobalFree(clipdata2);
+ }
+
+ if (!must_deselect)
+ SendMessage(hwnd, WM_IGNORE_CLIP, FALSE, 0);
+}
+
+static DWORD WINAPI clipboard_read_threadfunc(void *param)
+{
+ HWND hwnd = (HWND)param;
+ HGLOBAL clipdata;
+
+ if (OpenClipboard(NULL)) {
+ if ((clipdata = GetClipboardData(CF_UNICODETEXT))) {
+ SendMessage(hwnd, WM_GOT_CLIPDATA, (WPARAM)1, (LPARAM)clipdata);
+ } else if ((clipdata = GetClipboardData(CF_TEXT))) {
+ SendMessage(hwnd, WM_GOT_CLIPDATA, (WPARAM)0, (LPARAM)clipdata);
+ }
+ CloseClipboard();
+ }
+
+ return 0;
+}
+
+static int process_clipdata(HGLOBAL clipdata, int unicode)
+{
+ sfree(clipboard_contents);
+ clipboard_contents = NULL;
+ clipboard_length = 0;
+
+ if (unicode) {
+ wchar_t *p = GlobalLock(clipdata);
+ wchar_t *p2;
+
+ if (p) {
+ /* Unwilling to rely on Windows having wcslen() */
+ for (p2 = p; *p2; p2++);
+ clipboard_length = p2 - p;
+ clipboard_contents = snewn(clipboard_length + 1, wchar_t);
+ memcpy(clipboard_contents, p, clipboard_length * sizeof(wchar_t));
+ clipboard_contents[clipboard_length] = L'\0';
+ return TRUE;
+ }
+ } else {
+ char *s = GlobalLock(clipdata);
+ int i;
+
+ if (s) {
+ i = MultiByteToWideChar(CP_ACP, 0, s, strlen(s) + 1, 0, 0);
+ clipboard_contents = snewn(i, wchar_t);
+ MultiByteToWideChar(CP_ACP, 0, s, strlen(s) + 1,
+ clipboard_contents, i);
+ clipboard_length = i - 1;
+ clipboard_contents[clipboard_length] = L'\0';
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+void request_paste(void *frontend)
+{
+ /*
+ * I always thought pasting was synchronous in Windows; the
+ * clipboard access functions certainly _look_ synchronous,
+ * unlike the X ones. But in fact it seems that in some
+ * situations the contents of the clipboard might not be
+ * immediately available, and the clipboard-reading functions
+ * may block. This leads to trouble if the application
+ * delivering the clipboard data has to get hold of it by -
+ * for example - talking over a network connection which is
+ * forwarded through this very PuTTY.
+ *
+ * Hence, we spawn a subthread to read the clipboard, and do
+ * our paste when it's finished. The thread will send a
+ * message back to our main window when it terminates, and
+ * that tells us it's OK to paste.
+ */
+ DWORD in_threadid; /* required for Win9x */
+ CreateThread(NULL, 0, clipboard_read_threadfunc,
+ hwnd, 0, &in_threadid);
+}
+
+void get_clip(void *frontend, wchar_t **p, int *len)
+{
+ if (p) {
+ *p = clipboard_contents;
+ *len = clipboard_length;
+ }
+}
+
+#if 0
+/*
+ * Move `lines' lines from position `from' to position `to' in the
+ * window.
+ */
+void optimised_move(void *frontend, int to, int from, int lines)
+{
+ RECT r;
+ int min, max;
+
+ min = (to < from ? to : from);
+ max = to + from - min;
+
+ r.left = offset_width;
+ r.right = offset_width + term->cols * font_width;
+ r.top = offset_height + min * font_height;
+ r.bottom = offset_height + (max + lines) * font_height;
+ ScrollWindow(hwnd, 0, (to - from) * font_height, &r, &r);
+}
+#endif
+
+/*
+ * Print a message box and perform a fatal exit.
+ */
+void fatalbox(char *fmt, ...)
+{
+ va_list ap;
+ char *stuff, morestuff[100];
+
+ va_start(ap, fmt);
+ stuff = dupvprintf(fmt, ap);
+ va_end(ap);
+ sprintf(morestuff, "%.70s Fatal Error", appname);
+ MessageBox(hwnd, stuff, morestuff, MB_ICONERROR | MB_OK);
+ sfree(stuff);
+ cleanup_exit(1);
+}
+
+/*
+ * Print a modal (Really Bad) message box and perform a fatal exit.
+ */
+void modalfatalbox(char *fmt, ...)
+{
+ va_list ap;
+ char *stuff, morestuff[100];
+
+ va_start(ap, fmt);
+ stuff = dupvprintf(fmt, ap);
+ va_end(ap);
+ sprintf(morestuff, "%.70s Fatal Error", appname);
+ MessageBox(hwnd, stuff, morestuff,
+ MB_SYSTEMMODAL | MB_ICONERROR | MB_OK);
+ sfree(stuff);
+ cleanup_exit(1);
+}
+
+DECL_WINDOWS_FUNCTION(static, BOOL, FlashWindowEx, (PFLASHWINFO));
+
+static void init_flashwindow(void)
+{
+ HMODULE user32_module = load_system32_dll("user32.dll");
+ GET_WINDOWS_FUNCTION(user32_module, FlashWindowEx);
+}
+
+static BOOL flash_window_ex(DWORD dwFlags, UINT uCount, DWORD dwTimeout)
+{
+ if (p_FlashWindowEx) {
+ FLASHWINFO fi;
+ fi.cbSize = sizeof(fi);
+ fi.hwnd = hwnd;
+ fi.dwFlags = dwFlags;
+ fi.uCount = uCount;
+ fi.dwTimeout = dwTimeout;
+ return (*p_FlashWindowEx)(&fi);
+ }
+ else
+ return FALSE; /* shrug */
+}
+
+static void flash_window(int mode);
+static long next_flash;
+static int flashing = 0;
+
+/*
+ * Timer for platforms where we must maintain window flashing manually
+ * (e.g., Win95).
+ */
+static void flash_window_timer(void *ctx, long now)
+{
+ if (flashing && now - next_flash >= 0) {
+ flash_window(1);
+ }
+}
+
+/*
+ * 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)) {
+ /* stop */
+ if (flashing) {
+ flashing = 0;
+ if (p_FlashWindowEx)
+ flash_window_ex(FLASHW_STOP, 0, 0);
+ else
+ FlashWindow(hwnd, FALSE);
+ }
+
+ } else if (mode == 2) {
+ /* start */
+ if (!flashing) {
+ flashing = 1;
+ if (p_FlashWindowEx) {
+ /* For so-called "steady" mode, we use uCount=2, which
+ * seems to be the traditional number of flashes used
+ * 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),
+ 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)) {
+ /* maintain */
+ if (flashing && !p_FlashWindowEx) {
+ FlashWindow(hwnd, TRUE); /* toggle */
+ next_flash = schedule_timer(450, flash_window_timer, hwnd);
+ }
+ }
+}
+
+/*
+ * Beep.
+ */
+void do_beep(void *frontend, int mode)
+{
+ if (mode == BELL_DEFAULT) {
+ /*
+ * For MessageBeep style bells, we want to be careful of
+ * timing, because they don't have the nice property of
+ * PlaySound bells that each one cancels the previous
+ * active one. So we limit the rate to one per 50ms or so.
+ */
+ static long lastbeep = 0;
+ long beepdiff;
+
+ beepdiff = GetTickCount() - lastbeep;
+ if (beepdiff >= 0 && beepdiff < 50)
+ return;
+ MessageBeep(MB_OK);
+ /*
+ * 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,
+ SND_ASYNC | SND_FILENAME)) {
+ char buf[sizeof(cfg.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);
+ sprintf(otherbuf, "%.70s Sound Error", appname);
+ MessageBox(hwnd, buf, otherbuf,
+ MB_OK | MB_ICONEXCLAMATION);
+ cfg.beep = BELL_DEFAULT;
+ }
+ } else if (mode == BELL_PCSPEAKER) {
+ static long lastbeep = 0;
+ long beepdiff;
+
+ beepdiff = GetTickCount() - lastbeep;
+ if (beepdiff >= 0 && beepdiff < 50)
+ return;
+
+ /*
+ * We must beep in different ways depending on whether this
+ * is a 95-series or NT-series OS.
+ */
+ if(osVersion.dwPlatformId == VER_PLATFORM_WIN32_NT)
+ Beep(800, 100);
+ else
+ MessageBeep(-1);
+ lastbeep = GetTickCount();
+ }
+ /* Otherwise, either visual bell or disabled; do nothing here */
+ if (!term->has_focus) {
+ flash_window(2); /* start */
+ }
+}
+
+/*
+ * Minimise or restore the window in response to a server-side
+ * request.
+ */
+void set_iconic(void *frontend, int iconic)
+{
+ if (IsIconic(hwnd)) {
+ if (!iconic)
+ ShowWindow(hwnd, SW_RESTORE);
+ } else {
+ if (iconic)
+ ShowWindow(hwnd, SW_MINIMIZE);
+ }
+}
+
+/*
+ * 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 ||
+ IsZoomed(hwnd))
+ return;
+
+ SetWindowPos(hwnd, NULL, x, y, 0, 0, SWP_NOSIZE | SWP_NOZORDER);
+}
+
+/*
+ * 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)
+ return; /* ignore */
+ SetWindowPos(hwnd, top ? HWND_TOP : HWND_BOTTOM, 0, 0, 0, 0,
+ SWP_NOMOVE | SWP_NOSIZE);
+}
+
+/*
+ * Refresh the window in response to a server-side request.
+ */
+void refresh_window(void *frontend)
+{
+ InvalidateRect(hwnd, NULL, TRUE);
+}
+
+/*
+ * Maximise or restore the window in response to a server-side
+ * request.
+ */
+void set_zoomed(void *frontend, int zoomed)
+{
+ if (IsZoomed(hwnd)) {
+ if (!zoomed)
+ ShowWindow(hwnd, SW_RESTORE);
+ } else {
+ if (zoomed)
+ ShowWindow(hwnd, SW_MAXIMIZE);
+ }
+}
+
+/*
+ * Report whether the window is iconic, for terminal reports.
+ */
+int is_iconic(void *frontend)
+{
+ return IsIconic(hwnd);
+}
+
+/*
+ * Report the window's position, for terminal reports.
+ */
+void get_window_pos(void *frontend, int *x, int *y)
+{
+ RECT r;
+ GetWindowRect(hwnd, &r);
+ *x = r.left;
+ *y = r.top;
+}
+
+/*
+ * Report the window's pixel size, for terminal reports.
+ */
+void get_window_pixels(void *frontend, int *x, int *y)
+{
+ RECT r;
+ GetWindowRect(hwnd, &r);
+ *x = r.right - r.left;
+ *y = r.bottom - r.top;
+}
+
+/*
+ * Return the window or icon title.
+ */
+char *get_window_title(void *frontend, int icon)
+{
+ return icon ? icon_name : window_name;
+}
+
+/*
+ * See if we're in full-screen mode.
+ */
+static int is_full_screen()
+{
+ if (!IsZoomed(hwnd))
+ return FALSE;
+ if (GetWindowLongPtr(hwnd, GWL_STYLE) & WS_CAPTION)
+ return FALSE;
+ return TRUE;
+}
+
+/* Get the rect/size of a full screen window using the nearest available
+ * monitor in multimon systems; default to something sensible if only
+ * one monitor is present. */
+static int get_fullscreen_rect(RECT * ss)
+{
+#if defined(MONITOR_DEFAULTTONEAREST) && !defined(NO_MULTIMON)
+ HMONITOR mon;
+ MONITORINFO mi;
+ mon = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
+ mi.cbSize = sizeof(mi);
+ GetMonitorInfo(mon, &mi);
+
+ /* structure copy */
+ *ss = mi.rcMonitor;
+ return TRUE;
+#else
+/* could also use code like this:
+ ss->left = ss->top = 0;
+ ss->right = GetSystemMetrics(SM_CXSCREEN);
+ ss->bottom = GetSystemMetrics(SM_CYSCREEN);
+*/
+ return GetClientRect(GetDesktopWindow(), ss);
+#endif
+}
+
+
+/*
+ * Go full-screen. This should only be called when we are already
+ * maximised.
+ */
+static void make_full_screen()
+{
+ DWORD style;
+ RECT ss;
+
+ assert(IsZoomed(hwnd));
+
+ if (is_full_screen())
+ return;
+
+ /* Remove the window furniture. */
+ style = GetWindowLongPtr(hwnd, GWL_STYLE);
+ style &= ~(WS_CAPTION | WS_BORDER | WS_THICKFRAME);
+ if (cfg.scrollbar_in_fullscreen)
+ style |= WS_VSCROLL;
+ else
+ style &= ~WS_VSCROLL;
+ SetWindowLongPtr(hwnd, GWL_STYLE, style);
+
+ /* Resize ourselves to exactly cover the nearest monitor. */
+ get_fullscreen_rect(&ss);
+ SetWindowPos(hwnd, HWND_TOP, ss.left, ss.top,
+ ss.right - ss.left,
+ ss.bottom - ss.top,
+ SWP_FRAMECHANGED);
+
+ /* We may have changed size as a result */
+
+ reset_window(0);
+
+ /* Tick the menu item in the System and context menus. */
+ {
+ int i;
+ for (i = 0; i < lenof(popup_menus); i++)
+ CheckMenuItem(popup_menus[i].menu, IDM_FULLSCREEN, MF_CHECKED);
+ }
+}
+
+/*
+ * Clear the full-screen attributes.
+ */
+static void clear_full_screen()
+{
+ DWORD oldstyle, style;
+
+ /* Reinstate the window furniture. */
+ style = oldstyle = GetWindowLongPtr(hwnd, GWL_STYLE);
+ style |= WS_CAPTION | WS_BORDER;
+ if (cfg.resize_action == RESIZE_DISABLED)
+ style &= ~WS_THICKFRAME;
+ else
+ style |= WS_THICKFRAME;
+ if (cfg.scrollbar)
+ style |= WS_VSCROLL;
+ else
+ style &= ~WS_VSCROLL;
+ if (style != oldstyle) {
+ SetWindowLongPtr(hwnd, GWL_STYLE, style);
+ SetWindowPos(hwnd, NULL, 0, 0, 0, 0,
+ SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
+ SWP_FRAMECHANGED);
+ }
+
+ /* Untick the menu item in the System and context menus. */
+ {
+ int i;
+ for (i = 0; i < lenof(popup_menus); i++)
+ CheckMenuItem(popup_menus[i].menu, IDM_FULLSCREEN, MF_UNCHECKED);
+ }
+}
+
+/*
+ * Toggle full-screen mode.
+ */
+static void flip_full_screen()
+{
+ if (is_full_screen()) {
+ ShowWindow(hwnd, SW_RESTORE);
+ } else if (IsZoomed(hwnd)) {
+ make_full_screen();
+ } else {
+ SendMessage(hwnd, WM_FULLSCR_ON_MAX, 0, 0);
+ ShowWindow(hwnd, SW_MAXIMIZE);
+ }
+}
+
+void frontend_keypress(void *handle)
+{
+ /*
+ * Keypress termination in non-Close-On-Exit mode is not
+ * currently supported in PuTTY proper, because the window
+ * always has a perfectly good Close button anyway. So we do
+ * nothing here.
+ */
+ return;
+}
+
+int from_backend(void *frontend, int is_stderr, const char *data, int len)
+{
+ return term_data(term, is_stderr, data, len);
+}
+
+int from_backend_untrusted(void *frontend, const char *data, int len)
+{
+ return term_data_untrusted(term, data, len);
+}
+
+int get_userpass_input(prompts_t *p, unsigned char *in, int inlen)
+{
+ int ret;
+ ret = cmdline_get_passwd_input(p, in, inlen);
+ if (ret == -1)
+ ret = term_get_userpass_input(term, p, in, inlen);
+ return ret;
+}
+
+void agent_schedule_callback(void (*callback)(void *, void *, int),
+ void *callback_ctx, void *data, int len)
+{
+ struct agent_callback *c = snew(struct agent_callback);
+ c->callback = callback;
+ c->callback_ctx = callback_ctx;
+ c->data = data;
+ c->len = len;
+ PostMessage(hwnd, WM_AGENT_CALLBACK, 0, (LPARAM)c);
+}
--- /dev/null
+#ifndef NO_GSSAPI
+
+#include "putty.h"
+
+#include <security.h>
+
+#include "pgssapi.h"
+#include "sshgss.h"
+#include "sshgssc.h"
+
+#include "misc.h"
+
+/* Windows code to set up the GSSAPI library list. */
+
+const int ngsslibs = 3;
+const char *const gsslibnames[3] = {
+ "MIT Kerberos GSSAPI32.DLL",
+ "Microsoft SSPI SECUR32.DLL",
+ "User-specified GSSAPI DLL",
+};
+const struct keyvalwhere gsslibkeywords[] = {
+ { "gssapi32", 0, -1, -1 },
+ { "sspi", 1, -1, -1 },
+ { "custom", 2, -1, -1 },
+};
+
+DECL_WINDOWS_FUNCTION(static, SECURITY_STATUS,
+ AcquireCredentialsHandleA,
+ (SEC_CHAR *, SEC_CHAR *, ULONG, PLUID,
+ PVOID, SEC_GET_KEY_FN, PVOID, PCredHandle, PTimeStamp));
+DECL_WINDOWS_FUNCTION(static, SECURITY_STATUS,
+ InitializeSecurityContextA,
+ (PCredHandle, PCtxtHandle, SEC_CHAR *, ULONG, ULONG,
+ ULONG, PSecBufferDesc, ULONG, PCtxtHandle,
+ PSecBufferDesc, PULONG, PTimeStamp));
+DECL_WINDOWS_FUNCTION(static, SECURITY_STATUS,
+ FreeContextBuffer,
+ (PVOID));
+DECL_WINDOWS_FUNCTION(static, SECURITY_STATUS,
+ FreeCredentialsHandle,
+ (PCredHandle));
+DECL_WINDOWS_FUNCTION(static, SECURITY_STATUS,
+ DeleteSecurityContext,
+ (PCtxtHandle));
+DECL_WINDOWS_FUNCTION(static, SECURITY_STATUS,
+ QueryContextAttributesA,
+ (PCtxtHandle, ULONG, PVOID));
+DECL_WINDOWS_FUNCTION(static, SECURITY_STATUS,
+ MakeSignature,
+ (PCtxtHandle, ULONG, PSecBufferDesc, ULONG));
+
+typedef struct winSsh_gss_ctx {
+ unsigned long maj_stat;
+ unsigned long min_stat;
+ CredHandle cred_handle;
+ CtxtHandle context;
+ PCtxtHandle context_handle;
+ TimeStamp expiry;
+} winSsh_gss_ctx;
+
+
+const Ssh_gss_buf gss_mech_krb5={9,"\x2A\x86\x48\x86\xF7\x12\x01\x02\x02"};
+
+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)
+{
+ HMODULE module;
+ HKEY regkey;
+ struct ssh_gss_liblist *list = snew(struct ssh_gss_liblist);
+
+ list->libraries = snewn(3, struct ssh_gss_library);
+ list->nlibraries = 0;
+
+ /* MIT Kerberos GSSAPI implementation */
+ /* TODO: For 64-bit builds, check for gssapi64.dll */
+ module = NULL;
+ if (RegOpenKey(HKEY_LOCAL_MACHINE, "SOFTWARE\\MIT\\Kerberos", ®key)
+ == ERROR_SUCCESS) {
+ DWORD type, size;
+ LONG ret;
+ char *buffer;
+
+ /* Find out the string length */
+ ret = RegQueryValueEx(regkey, "InstallDir", NULL, &type, NULL, &size);
+
+ if (ret == ERROR_SUCCESS && type == REG_SZ) {
+ buffer = snewn(size + 20, char);
+ ret = RegQueryValueEx(regkey, "InstallDir", NULL,
+ &type, buffer, &size);
+ if (ret == ERROR_SUCCESS && type == REG_SZ) {
+ strcat(buffer, "\\bin\\gssapi32.dll");
+ module = LoadLibrary(buffer);
+ }
+ sfree(buffer);
+ }
+ RegCloseKey(regkey);
+ }
+ if (module) {
+ struct ssh_gss_library *lib =
+ &list->libraries[list->nlibraries++];
+
+ lib->id = 0;
+ lib->gsslogmsg = "Using GSSAPI from GSSAPI32.DLL";
+ lib->handle = (void *)module;
+
+#define BIND_GSS_FN(name) \
+ lib->u.gssapi.name = (t_gss_##name) GetProcAddress(module, "gss_" #name)
+
+ BIND_GSS_FN(delete_sec_context);
+ BIND_GSS_FN(display_status);
+ BIND_GSS_FN(get_mic);
+ BIND_GSS_FN(import_name);
+ BIND_GSS_FN(init_sec_context);
+ BIND_GSS_FN(release_buffer);
+ BIND_GSS_FN(release_cred);
+ BIND_GSS_FN(release_name);
+
+#undef BIND_GSS_FN
+
+ ssh_gssapi_bind_fns(lib);
+ }
+
+ /* Microsoft SSPI Implementation */
+ module = load_system32_dll("secur32.dll");
+ if (module) {
+ struct ssh_gss_library *lib =
+ &list->libraries[list->nlibraries++];
+
+ lib->id = 1;
+ lib->gsslogmsg = "Using SSPI from SECUR32.DLL";
+ lib->handle = (void *)module;
+
+ GET_WINDOWS_FUNCTION(module, AcquireCredentialsHandleA);
+ GET_WINDOWS_FUNCTION(module, InitializeSecurityContextA);
+ GET_WINDOWS_FUNCTION(module, FreeContextBuffer);
+ GET_WINDOWS_FUNCTION(module, FreeCredentialsHandle);
+ GET_WINDOWS_FUNCTION(module, DeleteSecurityContext);
+ GET_WINDOWS_FUNCTION(module, QueryContextAttributesA);
+ GET_WINDOWS_FUNCTION(module, MakeSignature);
+
+ ssh_sspi_bind_fns(lib);
+ }
+
+ /*
+ * Custom GSSAPI DLL.
+ */
+ module = NULL;
+ if (cfg->ssh_gss_custom.path[0]) {
+ module = LoadLibrary(cfg->ssh_gss_custom.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);
+ lib->handle = (void *)module;
+
+#define BIND_GSS_FN(name) \
+ lib->u.gssapi.name = (t_gss_##name) GetProcAddress(module, "gss_" #name)
+
+ BIND_GSS_FN(delete_sec_context);
+ BIND_GSS_FN(display_status);
+ BIND_GSS_FN(get_mic);
+ BIND_GSS_FN(import_name);
+ BIND_GSS_FN(init_sec_context);
+ BIND_GSS_FN(release_buffer);
+ BIND_GSS_FN(release_cred);
+ BIND_GSS_FN(release_name);
+
+#undef BIND_GSS_FN
+
+ ssh_gssapi_bind_fns(lib);
+ }
+
+
+ return list;
+}
+
+void ssh_gss_cleanup(struct ssh_gss_liblist *list)
+{
+ int i;
+
+ /*
+ * LoadLibrary and FreeLibrary are defined to employ reference
+ * counting in the case where the same library is repeatedly
+ * loaded, so even in a multiple-sessions-per-process context
+ * (not that we currently expect ever to have such a thing on
+ * Windows) it's safe to naively FreeLibrary everything here
+ * without worrying about destroying it under the feet of
+ * another SSH instance still using it.
+ */
+ for (i = 0; i < list->nlibraries; i++) {
+ FreeLibrary((HMODULE)list->libraries[i].handle);
+ if (list->libraries[i].id == 2) {
+ /* The 'custom' id involves a dynamically allocated message.
+ * Note that we must cast away the 'const' to free it. */
+ sfree((char *)list->libraries[i].gsslogmsg);
+ }
+ }
+ sfree(list->libraries);
+ sfree(list);
+}
+
+static Ssh_gss_stat ssh_sspi_indicate_mech(struct ssh_gss_library *lib,
+ Ssh_gss_buf *mech)
+{
+ *mech = gss_mech_krb5;
+ return SSH_GSS_OK;
+}
+
+
+static Ssh_gss_stat ssh_sspi_import_name(struct ssh_gss_library *lib,
+ char *host, Ssh_gss_name *srv_name)
+{
+ char *pStr;
+
+ /* Check hostname */
+ if (host == NULL) return SSH_GSS_FAILURE;
+
+ /* copy it into form host/FQDN */
+ pStr = dupcat("host/", host, NULL);
+
+ *srv_name = (Ssh_gss_name) pStr;
+
+ return SSH_GSS_OK;
+}
+
+static Ssh_gss_stat ssh_sspi_acquire_cred(struct ssh_gss_library *lib,
+ Ssh_gss_ctx *ctx)
+{
+ winSsh_gss_ctx *winctx = snew(winSsh_gss_ctx);
+ memset(winctx, 0, sizeof(winSsh_gss_ctx));
+
+ /* prepare our "wrapper" structure */
+ winctx->maj_stat = winctx->min_stat = SEC_E_OK;
+ winctx->context_handle = NULL;
+
+ /* Specifying no principal name here means use the credentials of
+ the current logged-in user */
+
+ winctx->maj_stat = p_AcquireCredentialsHandleA(NULL,
+ "Kerberos",
+ SECPKG_CRED_OUTBOUND,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ &winctx->cred_handle,
+ &winctx->expiry);
+
+ if (winctx->maj_stat != SEC_E_OK) return SSH_GSS_FAILURE;
+
+ *ctx = (Ssh_gss_ctx) winctx;
+ return SSH_GSS_OK;
+}
+
+
+static Ssh_gss_stat ssh_sspi_init_sec_context(struct ssh_gss_library *lib,
+ Ssh_gss_ctx *ctx,
+ Ssh_gss_name srv_name,
+ int to_deleg,
+ Ssh_gss_buf *recv_tok,
+ Ssh_gss_buf *send_tok)
+{
+ winSsh_gss_ctx *winctx = (winSsh_gss_ctx *) *ctx;
+ SecBuffer wsend_tok = {send_tok->length,SECBUFFER_TOKEN,send_tok->value};
+ SecBuffer wrecv_tok = {recv_tok->length,SECBUFFER_TOKEN,recv_tok->value};
+ SecBufferDesc output_desc={SECBUFFER_VERSION,1,&wsend_tok};
+ SecBufferDesc input_desc ={SECBUFFER_VERSION,1,&wrecv_tok};
+ unsigned long flags=ISC_REQ_MUTUAL_AUTH|ISC_REQ_REPLAY_DETECT|
+ ISC_REQ_CONFIDENTIALITY|ISC_REQ_ALLOCATE_MEMORY;
+ unsigned long ret_flags=0;
+
+ /* check if we have to delegate ... */
+ if (to_deleg) flags |= ISC_REQ_DELEGATE;
+ winctx->maj_stat = p_InitializeSecurityContextA(&winctx->cred_handle,
+ winctx->context_handle,
+ (char*) srv_name,
+ flags,
+ 0, /* reserved */
+ SECURITY_NATIVE_DREP,
+ &input_desc,
+ 0, /* reserved */
+ &winctx->context,
+ &output_desc,
+ &ret_flags,
+ &winctx->expiry);
+
+ /* prepare for the next round */
+ winctx->context_handle = &winctx->context;
+ send_tok->value = wsend_tok.pvBuffer;
+ send_tok->length = wsend_tok.cbBuffer;
+
+ /* check & return our status */
+ if (winctx->maj_stat==SEC_E_OK) return SSH_GSS_S_COMPLETE;
+ if (winctx->maj_stat==SEC_I_CONTINUE_NEEDED) return SSH_GSS_S_CONTINUE_NEEDED;
+
+ return SSH_GSS_FAILURE;
+}
+
+static Ssh_gss_stat ssh_sspi_free_tok(struct ssh_gss_library *lib,
+ Ssh_gss_buf *send_tok)
+{
+ /* check input */
+ if (send_tok == NULL) return SSH_GSS_FAILURE;
+
+ /* free Windows buffer */
+ p_FreeContextBuffer(send_tok->value);
+ SSH_GSS_CLEAR_BUF(send_tok);
+
+ return SSH_GSS_OK;
+}
+
+static Ssh_gss_stat ssh_sspi_release_cred(struct ssh_gss_library *lib,
+ Ssh_gss_ctx *ctx)
+{
+ winSsh_gss_ctx *winctx= (winSsh_gss_ctx *) *ctx;
+
+ /* check input */
+ if (winctx == NULL) return SSH_GSS_FAILURE;
+
+ /* free Windows data */
+ p_FreeCredentialsHandle(&winctx->cred_handle);
+ p_DeleteSecurityContext(&winctx->context);
+
+ /* delete our "wrapper" structure */
+ sfree(winctx);
+ *ctx = (Ssh_gss_ctx) NULL;
+
+ return SSH_GSS_OK;
+}
+
+
+static Ssh_gss_stat ssh_sspi_release_name(struct ssh_gss_library *lib,
+ Ssh_gss_name *srv_name)
+{
+ char *pStr= (char *) *srv_name;
+
+ if (pStr == NULL) return SSH_GSS_FAILURE;
+ sfree(pStr);
+ *srv_name = (Ssh_gss_name) NULL;
+
+ return SSH_GSS_OK;
+}
+
+static Ssh_gss_stat ssh_sspi_display_status(struct ssh_gss_library *lib,
+ Ssh_gss_ctx ctx, Ssh_gss_buf *buf)
+{
+ winSsh_gss_ctx *winctx = (winSsh_gss_ctx *) ctx;
+ char *msg;
+
+ if (winctx == NULL) return SSH_GSS_FAILURE;
+
+ /* decode the error code */
+ switch (winctx->maj_stat) {
+ case SEC_E_OK: msg="SSPI status OK"; break;
+ case SEC_E_INVALID_HANDLE: msg="The handle passed to the function"
+ " is invalid.";
+ break;
+ case SEC_E_TARGET_UNKNOWN: msg="The target was not recognized."; break;
+ case SEC_E_LOGON_DENIED: msg="The logon failed."; break;
+ case SEC_E_INTERNAL_ERROR: msg="The Local Security Authority cannot"
+ " be contacted.";
+ break;
+ case SEC_E_NO_CREDENTIALS: msg="No credentials are available in the"
+ " security package.";
+ break;
+ case SEC_E_NO_AUTHENTICATING_AUTHORITY:
+ msg="No authority could be contacted for authentication."
+ "The domain name of the authenticating party could be wrong,"
+ " the domain could be unreachable, or there might have been"
+ " a trust relationship failure.";
+ break;
+ case SEC_E_INSUFFICIENT_MEMORY:
+ msg="One or more of the SecBufferDesc structures passed as"
+ " an OUT parameter has a buffer that is too small.";
+ break;
+ case SEC_E_INVALID_TOKEN:
+ msg="The error is due to a malformed input token, such as a"
+ " token corrupted in transit, a token"
+ " of incorrect size, or a token passed into the wrong"
+ " security package. Passing a token to"
+ " the wrong package can happen if client and server did not"
+ " negotiate the proper security package.";
+ break;
+ default:
+ msg = "Internal SSPI error";
+ break;
+ }
+
+ buf->value = dupstr(msg);
+ buf->length = strlen(buf->value);
+
+ return SSH_GSS_OK;
+}
+
+static Ssh_gss_stat ssh_sspi_get_mic(struct ssh_gss_library *lib,
+ Ssh_gss_ctx ctx, Ssh_gss_buf *buf,
+ Ssh_gss_buf *hash)
+{
+ winSsh_gss_ctx *winctx= (winSsh_gss_ctx *) ctx;
+ SecPkgContext_Sizes ContextSizes;
+ SecBufferDesc InputBufferDescriptor;
+ SecBuffer InputSecurityToken[2];
+
+ if (winctx == NULL) return SSH_GSS_FAILURE;
+
+ winctx->maj_stat = 0;
+
+ memset(&ContextSizes, 0, sizeof(ContextSizes));
+
+ winctx->maj_stat = p_QueryContextAttributesA(&winctx->context,
+ SECPKG_ATTR_SIZES,
+ &ContextSizes);
+
+ if (winctx->maj_stat != SEC_E_OK ||
+ ContextSizes.cbMaxSignature == 0)
+ return winctx->maj_stat;
+
+ InputBufferDescriptor.cBuffers = 2;
+ InputBufferDescriptor.pBuffers = InputSecurityToken;
+ InputBufferDescriptor.ulVersion = SECBUFFER_VERSION;
+ InputSecurityToken[0].BufferType = SECBUFFER_DATA;
+ InputSecurityToken[0].cbBuffer = buf->length;
+ InputSecurityToken[0].pvBuffer = buf->value;
+ InputSecurityToken[1].BufferType = SECBUFFER_TOKEN;
+ InputSecurityToken[1].cbBuffer = ContextSizes.cbMaxSignature;
+ InputSecurityToken[1].pvBuffer = snewn(ContextSizes.cbMaxSignature, char);
+
+ winctx->maj_stat = p_MakeSignature(&winctx->context,
+ 0,
+ &InputBufferDescriptor,
+ 0);
+
+ if (winctx->maj_stat == SEC_E_OK) {
+ hash->length = InputSecurityToken[1].cbBuffer;
+ hash->value = InputSecurityToken[1].pvBuffer;
+ }
+
+ return winctx->maj_stat;
+}
+
+static Ssh_gss_stat ssh_sspi_free_mic(struct ssh_gss_library *lib,
+ Ssh_gss_buf *hash)
+{
+ sfree(hash->value);
+ return SSH_GSS_OK;
+}
+
+static void ssh_sspi_bind_fns(struct ssh_gss_library *lib)
+{
+ lib->indicate_mech = ssh_sspi_indicate_mech;
+ lib->import_name = ssh_sspi_import_name;
+ lib->release_name = ssh_sspi_release_name;
+ lib->init_sec_context = ssh_sspi_init_sec_context;
+ lib->free_tok = ssh_sspi_free_tok;
+ lib->acquire_cred = ssh_sspi_acquire_cred;
+ lib->release_cred = ssh_sspi_release_cred;
+ lib->get_mic = ssh_sspi_get_mic;
+ lib->free_mic = ssh_sspi_free_mic;
+ lib->display_status = ssh_sspi_display_status;
+}
+
+#else
+
+/* Dummy function so this source file defines something if NO_GSSAPI
+ is defined. */
+
+void ssh_gss_init(void)
+{
+}
+
+#endif
--- /dev/null
+/*
+ * winhandl.c: Module to give Windows front ends the general
+ * ability to deal with consoles, pipes, serial ports, or any other
+ * type of data stream accessed through a Windows API HANDLE rather
+ * than a WinSock SOCKET.
+ *
+ * We do this by spawning a subthread to continuously try to read
+ * from the handle. Every time a read successfully returns some
+ * data, the subthread sets an event object which is picked up by
+ * the main thread, and the main thread then sets an event in
+ * return to instruct the subthread to resume reading.
+ *
+ * Output works precisely the other way round, in a second
+ * subthread. The output subthread should not be attempting to
+ * write all the time, because it hasn't always got data _to_
+ * write; so the output thread waits for an event object notifying
+ * it to _attempt_ a write, and then it sets an event in return
+ * when one completes.
+ *
+ * (It's terribly annoying having to spawn a subthread for each
+ * direction of each handle. Technically it isn't necessary for
+ * serial ports, since we could use overlapped I/O within the main
+ * thread and wait directly on the event objects in the OVERLAPPED
+ * structures. However, we can't use this trick for some types of
+ * file handle at all - for some reason Windows restricts use of
+ * OVERLAPPED to files which were opened with the overlapped flag -
+ * and so we must use threads for those. This being the case, it's
+ * simplest just to use threads for everything rather than trying
+ * to keep track of multiple completely separate mechanisms.)
+ */
+
+#include <assert.h>
+
+#include "putty.h"
+
+/* ----------------------------------------------------------------------
+ * Generic definitions.
+ */
+
+/*
+ * Maximum amount of backlog we will allow to build up on an input
+ * handle before we stop reading from it.
+ */
+#define MAX_BACKLOG 32768
+
+struct handle_generic {
+ /*
+ * Initial fields common to both handle_input and handle_output
+ * structures.
+ *
+ * The three HANDLEs are set up at initialisation time and are
+ * thereafter read-only to both main thread and subthread.
+ * `moribund' is only used by the main thread; `done' is
+ * written by the main thread before signalling to the
+ * subthread. `defunct' and `busy' are used only by the main
+ * thread.
+ */
+ 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 */
+};
+
+/* ----------------------------------------------------------------------
+ * Input threads.
+ */
+
+/*
+ * Data required by an input thread.
+ */
+struct handle_input {
+ /*
+ * 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 */
+
+ /*
+ * Data set at initialisation and then read-only.
+ */
+ int flags;
+
+ /*
+ * Data set by the input thread before signalling ev_to_main,
+ * and read by the main thread after receiving that signal.
+ */
+ char buffer[4096]; /* the data read from the handle */
+ DWORD len; /* how much data that was */
+ int readerr; /* lets us know about read errors */
+
+ /*
+ * Callback function called by this module when data arrives on
+ * an input handle.
+ */
+ handle_inputfn_t gotdata;
+};
+
+/*
+ * The actual thread procedure for an input thread.
+ */
+static DWORD WINAPI handle_input_threadfunc(void *param)
+{
+ struct handle_input *ctx = (struct handle_input *) param;
+ OVERLAPPED ovl, *povl;
+ HANDLE oev;
+ int readret, readlen;
+
+ if (ctx->flags & HANDLE_FLAG_OVERLAPPED) {
+ povl = &ovl;
+ oev = CreateEvent(NULL, TRUE, FALSE, NULL);
+ } else {
+ povl = NULL;
+ }
+
+ if (ctx->flags & HANDLE_FLAG_UNITBUFFER)
+ readlen = 1;
+ else
+ readlen = sizeof(ctx->buffer);
+
+ while (1) {
+ if (povl) {
+ memset(povl, 0, sizeof(OVERLAPPED));
+ povl->hEvent = oev;
+ }
+ readret = ReadFile(ctx->h, ctx->buffer,readlen, &ctx->len, povl);
+ if (!readret)
+ ctx->readerr = GetLastError();
+ else
+ ctx->readerr = 0;
+ if (povl && !readret && ctx->readerr == ERROR_IO_PENDING) {
+ WaitForSingleObject(povl->hEvent, INFINITE);
+ readret = GetOverlappedResult(ctx->h, povl, &ctx->len, FALSE);
+ if (!readret)
+ ctx->readerr = GetLastError();
+ else
+ ctx->readerr = 0;
+ }
+
+ if (!readret) {
+ /*
+ * Windows apparently sends ERROR_BROKEN_PIPE when a
+ * pipe we're reading from is closed normally from the
+ * writing end. This is ludicrous; if that situation
+ * isn't a natural EOF, _nothing_ is. So if we get that
+ * particular error, we pretend it's EOF.
+ */
+ if (ctx->readerr == ERROR_BROKEN_PIPE)
+ ctx->readerr = 0;
+ ctx->len = 0;
+ }
+
+ if (readret && ctx->len == 0 &&
+ (ctx->flags & HANDLE_FLAG_IGNOREEOF))
+ continue;
+
+ SetEvent(ctx->ev_to_main);
+
+ if (!ctx->len)
+ break;
+
+ WaitForSingleObject(ctx->ev_from_main, INFINITE);
+ if (ctx->done)
+ break; /* main thread told us to shut down */
+ }
+
+ if (povl)
+ CloseHandle(oev);
+
+ return 0;
+}
+
+/*
+ * This is called after a succcessful read, or from the
+ * `unthrottle' function. It decides whether or not to begin a new
+ * read operation.
+ */
+static void handle_throttle(struct handle_input *ctx, int backlog)
+{
+ if (ctx->defunct)
+ return;
+
+ /*
+ * If there's a read operation already in progress, do nothing:
+ * when that completes, we'll come back here and be in a
+ * position to make a better decision.
+ */
+ if (ctx->busy)
+ return;
+
+ /*
+ * Otherwise, we must decide whether to start a new read based
+ * on the size of the backlog.
+ */
+ if (backlog < MAX_BACKLOG) {
+ SetEvent(ctx->ev_from_main);
+ ctx->busy = TRUE;
+ }
+}
+
+/* ----------------------------------------------------------------------
+ * Output threads.
+ */
+
+/*
+ * Data required by an output thread.
+ */
+struct handle_output {
+ /*
+ * 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 */
+
+ /*
+ * Data set at initialisation and then read-only.
+ */
+ int flags;
+
+ /*
+ * Data set by the main thread before signalling ev_from_main,
+ * and read by the input thread after receiving that signal.
+ */
+ char *buffer; /* the data to write */
+ DWORD len; /* how much data there is */
+
+ /*
+ * Data set by the input thread before signalling ev_to_main,
+ * and read by the main thread after receiving that signal.
+ */
+ DWORD lenwritten; /* how much data we actually wrote */
+ int writeerr; /* return value from WriteFile */
+
+ /*
+ * Data only ever read or written by the main thread.
+ */
+ bufchain queued_data; /* data still waiting to be written */
+
+ /*
+ * Callback function called when the backlog in the bufchain
+ * drops.
+ */
+ handle_outputfn_t sentdata;
+};
+
+static DWORD WINAPI handle_output_threadfunc(void *param)
+{
+ struct handle_output *ctx = (struct handle_output *) param;
+ OVERLAPPED ovl, *povl;
+ HANDLE oev;
+ int writeret;
+
+ if (ctx->flags & HANDLE_FLAG_OVERLAPPED) {
+ povl = &ovl;
+ oev = CreateEvent(NULL, TRUE, FALSE, NULL);
+ } else {
+ povl = NULL;
+ }
+
+ while (1) {
+ WaitForSingleObject(ctx->ev_from_main, INFINITE);
+ if (ctx->done) {
+ SetEvent(ctx->ev_to_main);
+ break;
+ }
+ if (povl) {
+ memset(povl, 0, sizeof(OVERLAPPED));
+ povl->hEvent = oev;
+ }
+
+ writeret = WriteFile(ctx->h, ctx->buffer, ctx->len,
+ &ctx->lenwritten, povl);
+ if (!writeret)
+ ctx->writeerr = GetLastError();
+ else
+ ctx->writeerr = 0;
+ if (povl && !writeret && GetLastError() == ERROR_IO_PENDING) {
+ writeret = GetOverlappedResult(ctx->h, povl,
+ &ctx->lenwritten, TRUE);
+ if (!writeret)
+ ctx->writeerr = GetLastError();
+ else
+ ctx->writeerr = 0;
+ }
+
+ SetEvent(ctx->ev_to_main);
+ if (!writeret)
+ break;
+ }
+
+ if (povl)
+ CloseHandle(oev);
+
+ return 0;
+}
+
+static void handle_try_output(struct handle_output *ctx)
+{
+ void *senddata;
+ int sendlen;
+
+ if (!ctx->busy && bufchain_size(&ctx->queued_data)) {
+ bufchain_prefix(&ctx->queued_data, &senddata, &sendlen);
+ ctx->buffer = senddata;
+ ctx->len = sendlen;
+ SetEvent(ctx->ev_from_main);
+ ctx->busy = TRUE;
+ }
+}
+
+/* ----------------------------------------------------------------------
+ * Unified code handling both input and output threads.
+ */
+
+struct handle {
+ int output;
+ union {
+ struct handle_generic g;
+ struct handle_input i;
+ struct handle_output o;
+ } u;
+};
+
+static tree234 *handles_by_evtomain;
+
+static int handle_cmp_evtomain(void *av, void *bv)
+{
+ struct handle *a = (struct handle *)av;
+ struct handle *b = (struct handle *)bv;
+
+ if ((unsigned)a->u.g.ev_to_main < (unsigned)b->u.g.ev_to_main)
+ return -1;
+ else if ((unsigned)a->u.g.ev_to_main > (unsigned)b->u.g.ev_to_main)
+ return +1;
+ else
+ return 0;
+}
+
+static int handle_find_evtomain(void *av, void *bv)
+{
+ HANDLE *a = (HANDLE *)av;
+ struct handle *b = (struct handle *)bv;
+
+ if ((unsigned)*a < (unsigned)b->u.g.ev_to_main)
+ return -1;
+ else if ((unsigned)*a > (unsigned)b->u.g.ev_to_main)
+ return +1;
+ else
+ return 0;
+}
+
+struct handle *handle_input_new(HANDLE handle, handle_inputfn_t gotdata,
+ void *privdata, int flags)
+{
+ struct handle *h = snew(struct handle);
+ DWORD in_threadid; /* required for Win9x */
+
+ h->output = FALSE;
+ 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;
+ h->u.i.moribund = FALSE;
+ h->u.i.done = FALSE;
+ h->u.i.privdata = privdata;
+ h->u.i.flags = flags;
+
+ if (!handles_by_evtomain)
+ handles_by_evtomain = newtree234(handle_cmp_evtomain);
+ add234(handles_by_evtomain, h);
+
+ CreateThread(NULL, 0, handle_input_threadfunc,
+ &h->u.i, 0, &in_threadid);
+ h->u.i.busy = TRUE;
+
+ return h;
+}
+
+struct handle *handle_output_new(HANDLE handle, handle_outputfn_t sentdata,
+ void *privdata, int flags)
+{
+ struct handle *h = snew(struct handle);
+ DWORD out_threadid; /* required for Win9x */
+
+ h->output = TRUE;
+ 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.sentdata = sentdata;
+ h->u.o.flags = flags;
+
+ if (!handles_by_evtomain)
+ handles_by_evtomain = newtree234(handle_cmp_evtomain);
+ add234(handles_by_evtomain, h);
+
+ CreateThread(NULL, 0, handle_output_threadfunc,
+ &h->u.o, 0, &out_threadid);
+
+ return h;
+}
+
+int handle_write(struct handle *h, const void *data, int len)
+{
+ assert(h->output);
+ bufchain_add(&h->u.o.queued_data, data, len);
+ handle_try_output(&h->u.o);
+ return bufchain_size(&h->u.o.queued_data);
+}
+
+HANDLE *handle_get_events(int *nevents)
+{
+ HANDLE *ret;
+ struct handle *h;
+ int i, n, size;
+
+ /*
+ * Go through our tree counting the handle objects currently
+ * engaged in useful activity.
+ */
+ ret = NULL;
+ n = size = 0;
+ if (handles_by_evtomain) {
+ for (i = 0; (h = index234(handles_by_evtomain, i)) != NULL; i++) {
+ if (h->u.g.busy) {
+ if (n >= size) {
+ size += 32;
+ ret = sresize(ret, size, HANDLE);
+ }
+ ret[n++] = h->u.g.ev_to_main;
+ }
+ }
+ }
+
+ *nevents = n;
+ return ret;
+}
+
+static void handle_destroy(struct handle *h)
+{
+ if (h->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);
+}
+
+void handle_free(struct handle *h)
+{
+ /*
+ * If the handle is currently busy, we cannot immediately free
+ * it. Instead we must wait until it's finished its current
+ * operation, because otherwise the subthread will write to
+ * invalid memory after we free its context from under it.
+ */
+ assert(h && !h->u.g.moribund);
+ if (h->u.g.busy) {
+ /*
+ * Just set the moribund flag, which will be noticed next
+ * time an operation completes.
+ */
+ h->u.g.moribund = TRUE;
+ } else if (h->u.g.defunct) {
+ /*
+ * There isn't even a subthread; we can go straight to
+ * handle_destroy.
+ */
+ handle_destroy(h);
+ } else {
+ /*
+ * The subthread is alive but not busy, so we now signal it
+ * to die. Set the moribund flag to indicate that it will
+ * want destroying after that.
+ */
+ h->u.g.moribund = TRUE;
+ h->u.g.done = TRUE;
+ h->u.g.busy = TRUE;
+ SetEvent(h->u.g.ev_from_main);
+ }
+}
+
+void handle_got_event(HANDLE event)
+{
+ struct handle *h;
+
+ assert(handles_by_evtomain);
+ h = find234(handles_by_evtomain, &event, handle_find_evtomain);
+ if (!h) {
+ /*
+ * This isn't an error condition. If two or more event
+ * objects were signalled during the same select operation,
+ * and processing of the first caused the second handle to
+ * be closed, then it will sometimes happen that we receive
+ * an event notification here for a handle which is already
+ * deceased. In that situation we simply do nothing.
+ */
+ return;
+ }
+
+ if (h->u.g.moribund) {
+ /*
+ * A moribund handle is already treated as dead from the
+ * external user's point of view, so do nothing with the
+ * actual event. Just signal the thread to die if
+ * necessary, or destroy the handle if not.
+ */
+ if (h->u.g.done) {
+ handle_destroy(h);
+ } else {
+ h->u.g.done = TRUE;
+ h->u.g.busy = TRUE;
+ SetEvent(h->u.g.ev_from_main);
+ }
+ return;
+ }
+
+ if (!h->output) {
+ int backlog;
+
+ h->u.i.busy = FALSE;
+
+ /*
+ * A signal on an input handle means data has arrived.
+ */
+ if (h->u.i.len == 0) {
+ /*
+ * EOF, or (nearly equivalently) read error.
+ */
+ h->u.i.gotdata(h, NULL, -h->u.i.readerr);
+ 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 {
+ 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
+ * buffer size has decreased, or to indicate an error.
+ */
+ if (h->u.o.writeerr) {
+ /*
+ * Write error. Send a negative value to the callback,
+ * and mark the thread as defunct (because the output
+ * thread is terminating by now).
+ */
+ h->u.o.sentdata(h, -h->u.o.writeerr);
+ h->u.o.defunct = TRUE;
+ } 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);
+ }
+ }
+}
+
+void handle_unthrottle(struct handle *h, int backlog)
+{
+ assert(!h->output);
+ handle_throttle(&h->u.i, backlog);
+}
+
+int handle_backlog(struct handle *h)
+{
+ assert(h->output);
+ return bufchain_size(&h->u.o.queued_data);
+}
+
+void *handle_get_privdata(struct handle *h)
+{
+ return h->u.g.privdata;
+}
--- /dev/null
+/*
+ * winhelp.c: centralised functions to launch Windows help files,
+ * and to decide whether to use .HLP or .CHM help in any given
+ * situation.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+#include "putty.h"
+
+#ifndef NO_HTMLHELP
+#include <htmlhelp.h>
+#endif /* NO_HTMLHELP */
+
+static int requested_help;
+static char *help_path;
+static int help_has_contents;
+#ifndef NO_HTMLHELP
+DECL_WINDOWS_FUNCTION(static, HWND, HtmlHelpA, (HWND, LPCSTR, UINT, DWORD));
+static char *chm_path;
+#endif /* NO_HTMLHELP */
+
+void init_help(void)
+{
+ char b[2048], *p, *q, *r;
+ FILE *fp;
+
+ GetModuleFileName(NULL, b, sizeof(b) - 1);
+ r = b;
+ p = strrchr(b, '\\');
+ if (p && p >= r) r = p+1;
+ q = strrchr(b, ':');
+ if (q && q >= r) r = q+1;
+ strcpy(r, PUTTY_HELP_FILE);
+ if ( (fp = fopen(b, "r")) != NULL) {
+ help_path = dupstr(b);
+ fclose(fp);
+ } else
+ help_path = NULL;
+ strcpy(r, PUTTY_HELP_CONTENTS);
+ if ( (fp = fopen(b, "r")) != NULL) {
+ help_has_contents = TRUE;
+ fclose(fp);
+ } else
+ help_has_contents = FALSE;
+
+#ifndef NO_HTMLHELP
+ strcpy(r, PUTTY_CHM_FILE);
+ if ( (fp = fopen(b, "r")) != NULL) {
+ chm_path = dupstr(b);
+ fclose(fp);
+ } else
+ chm_path = NULL;
+ if (chm_path) {
+ HINSTANCE dllHH = load_system32_dll("hhctrl.ocx");
+ GET_WINDOWS_FUNCTION(dllHH, HtmlHelpA);
+ if (!p_HtmlHelpA) {
+ chm_path = NULL;
+ if (dllHH)
+ FreeLibrary(dllHH);
+ }
+ }
+#endif /* NO_HTMLHELP */
+}
+
+void shutdown_help(void)
+{
+ /* Nothing to do currently.
+ * (If we were running HTML Help single-threaded, this is where we'd
+ * call HH_UNINITIALIZE.) */
+}
+
+int has_help(void)
+{
+ /*
+ * 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
+#ifndef NO_HTMLHELP
+ || chm_path
+#endif /* NO_HTMLHELP */
+ );
+}
+
+void launch_help(HWND hwnd, const char *topic)
+{
+ if (topic) {
+ int colonpos = strcspn(topic, ":");
+
+#ifndef NO_HTMLHELP
+ if (chm_path) {
+ char *fname;
+ assert(topic[colonpos] != '\0');
+ fname = dupprintf("%s::/%s.html>main", chm_path,
+ topic + colonpos + 1);
+ p_HtmlHelpA(hwnd, fname, HH_DISPLAY_TOPIC, 0);
+ sfree(fname);
+ } else
+#endif /* NO_HTMLHELP */
+ if (help_path) {
+ char *cmd = dupprintf("JI(`',`%.*s')", colonpos, topic);
+ WinHelp(hwnd, help_path, HELP_COMMAND, (DWORD)cmd);
+ sfree(cmd);
+ }
+ } else {
+#ifndef NO_HTMLHELP
+ if (chm_path) {
+ p_HtmlHelpA(hwnd, chm_path, HH_DISPLAY_TOPIC, 0);
+ } else
+#endif /* NO_HTMLHELP */
+ if (help_path) {
+ WinHelp(hwnd, help_path,
+ help_has_contents ? HELP_FINDER : HELP_CONTENTS, 0);
+ }
+ }
+ requested_help = TRUE;
+}
+
+void quit_help(HWND hwnd)
+{
+ if (requested_help) {
+#ifndef NO_HTMLHELP
+ if (chm_path) {
+ p_HtmlHelpA(NULL, NULL, HH_CLOSE_ALL, 0);
+ } else
+#endif /* NO_HTMLHELP */
+ if (help_path) {
+ WinHelp(hwnd, help_path, HELP_QUIT, 0);
+ }
+ requested_help = FALSE;
+ }
+}
--- /dev/null
+/*
+ * winhelp.h - define Windows Help context names.
+ * Each definition has the form "winhelp-topic:halibut-topic", where:
+ * - "winhelp-topic" matches up with the \cfg{winhelp-topic} directives
+ * in the Halibut source, and is used for WinHelp;
+ * - "halibut-topic" matches up with the Halibut keywords in the source,
+ * and is used for HTML Help.
+ */
+
+/* Maximum length for WINHELP_CTX_foo strings */
+#define WINHELP_CTX_MAXLEN 80
+
+/* These are used in the cross-platform configuration dialog code. */
+
+#define HELPCTX(x) P(WINHELP_CTX_ ## x)
+
+#define WINHELP_CTX_no_help NULL
+
+#define WINHELP_CTX_session_hostname "session.hostname:config-hostname"
+#define WINHELP_CTX_session_saved "session.saved:config-saving"
+#define WINHELP_CTX_session_coe "session.coe:config-closeonexit"
+#define WINHELP_CTX_logging_main "logging.main:config-logging"
+#define WINHELP_CTX_logging_filename "logging.filename:config-logfilename"
+#define WINHELP_CTX_logging_exists "logging.exists:config-logfileexists"
+#define WINHELP_CTX_logging_flush "logging.flush:config-logflush"
+#define WINHELP_CTX_logging_ssh_omit_password "logging.ssh.omitpassword:config-logssh"
+#define WINHELP_CTX_logging_ssh_omit_data "logging.ssh.omitdata:config-logssh"
+#define WINHELP_CTX_keyboard_backspace "keyboard.backspace:config-backspace"
+#define WINHELP_CTX_keyboard_homeend "keyboard.homeend:config-homeend"
+#define WINHELP_CTX_keyboard_funkeys "keyboard.funkeys:config-funkeys"
+#define WINHELP_CTX_keyboard_appkeypad "keyboard.appkeypad:config-appkeypad"
+#define WINHELP_CTX_keyboard_appcursor "keyboard.appcursor:config-appcursor"
+#define WINHELP_CTX_keyboard_nethack "keyboard.nethack:config-nethack"
+#define WINHELP_CTX_keyboard_compose "keyboard.compose:config-compose"
+#define WINHELP_CTX_keyboard_ctrlalt "keyboard.ctrlalt:config-ctrlalt"
+#define WINHELP_CTX_features_application "features.application:config-features-application"
+#define WINHELP_CTX_features_mouse "features.mouse:config-features-mouse"
+#define WINHELP_CTX_features_resize "features.resize:config-features-resize"
+#define WINHELP_CTX_features_altscreen "features.altscreen:config-features-altscreen"
+#define WINHELP_CTX_features_retitle "features.retitle:config-features-retitle"
+#define WINHELP_CTX_features_qtitle "features.qtitle:config-features-qtitle"
+#define WINHELP_CTX_features_dbackspace "features.dbackspace:config-features-dbackspace"
+#define WINHELP_CTX_features_charset "features.charset:config-features-charset"
+#define WINHELP_CTX_features_arabicshaping "features.arabicshaping:config-features-shaping"
+#define WINHELP_CTX_features_bidi "features.bidi:config-features-bidi"
+#define WINHELP_CTX_terminal_autowrap "terminal.autowrap:config-autowrap"
+#define WINHELP_CTX_terminal_decom "terminal.decom:config-decom"
+#define WINHELP_CTX_terminal_lfhascr "terminal.lfhascr:config-crlf"
+#define WINHELP_CTX_terminal_crhaslf "terminal.crhaslf:config-lfcr"
+#define WINHELP_CTX_terminal_bce "terminal.bce:config-erase"
+#define WINHELP_CTX_terminal_blink "terminal.blink:config-blink"
+#define WINHELP_CTX_terminal_answerback "terminal.answerback:config-answerback"
+#define WINHELP_CTX_terminal_localecho "terminal.localecho:config-localecho"
+#define WINHELP_CTX_terminal_localedit "terminal.localedit:config-localedit"
+#define WINHELP_CTX_terminal_printing "terminal.printing:config-printing"
+#define WINHELP_CTX_bell_style "bell.style:config-bellstyle"
+#define WINHELP_CTX_bell_taskbar "bell.taskbar:config-belltaskbar"
+#define WINHELP_CTX_bell_overload "bell.overload:config-bellovl"
+#define WINHELP_CTX_window_size "window.size:config-winsize"
+#define WINHELP_CTX_window_resize "window.resize:config-winsizelock"
+#define WINHELP_CTX_window_scrollback "window.scrollback:config-scrollback"
+#define WINHELP_CTX_window_erased "window.erased:config-erasetoscrollback"
+#define WINHELP_CTX_behaviour_closewarn "behaviour.closewarn:config-warnonclose"
+#define WINHELP_CTX_behaviour_altf4 "behaviour.altf4:config-altf4"
+#define WINHELP_CTX_behaviour_altspace "behaviour.altspace:config-altspace"
+#define WINHELP_CTX_behaviour_altonly "behaviour.altonly:config-altonly"
+#define WINHELP_CTX_behaviour_alwaysontop "behaviour.alwaysontop:config-alwaysontop"
+#define WINHELP_CTX_behaviour_altenter "behaviour.altenter:config-fullscreen"
+#define WINHELP_CTX_appearance_cursor "appearance.cursor:config-cursor"
+#define WINHELP_CTX_appearance_font "appearance.font:config-font"
+#define WINHELP_CTX_appearance_title "appearance.title:config-title"
+#define WINHELP_CTX_appearance_hidemouse "appearance.hidemouse:config-mouseptr"
+#define WINHELP_CTX_appearance_border "appearance.border:config-winborder"
+#define WINHELP_CTX_connection_termtype "connection.termtype:config-termtype"
+#define WINHELP_CTX_connection_termspeed "connection.termspeed:config-termspeed"
+#define WINHELP_CTX_connection_username "connection.username:config-username"
+#define WINHELP_CTX_connection_username_from_env "connection.usernamefromenv:config-username-from-env"
+#define WINHELP_CTX_connection_keepalive "connection.keepalive:config-keepalive"
+#define WINHELP_CTX_connection_nodelay "connection.nodelay:config-nodelay"
+#define WINHELP_CTX_connection_ipversion "connection.ipversion:config-address-family"
+#define WINHELP_CTX_connection_tcpkeepalive "connection.tcpkeepalive:config-tcp-keepalives"
+#define WINHELP_CTX_connection_loghost "connection.loghost:config-loghost"
+#define WINHELP_CTX_proxy_type "proxy.type:config-proxy-type"
+#define WINHELP_CTX_proxy_main "proxy.main:config-proxy"
+#define WINHELP_CTX_proxy_exclude "proxy.exclude:config-proxy-exclude"
+#define WINHELP_CTX_proxy_dns "proxy.dns:config-proxy-dns"
+#define WINHELP_CTX_proxy_auth "proxy.auth:config-proxy-auth"
+#define WINHELP_CTX_proxy_command "proxy.command:config-proxy-command"
+#define WINHELP_CTX_telnet_environ "telnet.environ:config-environ"
+#define WINHELP_CTX_telnet_oldenviron "telnet.oldenviron:config-oldenviron"
+#define WINHELP_CTX_telnet_passive "telnet.passive:config-ptelnet"
+#define WINHELP_CTX_telnet_specialkeys "telnet.specialkeys:config-telnetkey"
+#define WINHELP_CTX_telnet_newline "telnet.newline:config-telnetnl"
+#define WINHELP_CTX_rlogin_localuser "rlogin.localuser:config-rlogin-localuser"
+#define WINHELP_CTX_ssh_nopty "ssh.nopty:config-ssh-pty"
+#define WINHELP_CTX_ssh_ttymodes "ssh.ttymodes:config-ttymodes"
+#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_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"
+#define WINHELP_CTX_ssh_auth_agentfwd "ssh.auth.agentfwd:config-ssh-agentfwd"
+#define WINHELP_CTX_ssh_auth_changeuser "ssh.auth.changeuser:config-ssh-changeuser"
+#define WINHELP_CTX_ssh_auth_pageant "ssh.auth.pageant:config-ssh-tryagent"
+#define WINHELP_CTX_ssh_auth_tis "ssh.auth.tis:config-ssh-tis"
+#define WINHELP_CTX_ssh_auth_ki "ssh.auth.ki:config-ssh-ki"
+#define WINHELP_CTX_ssh_gssapi "ssh.auth.gssapi:config-ssh-auth-gssapi"
+#define WINHELP_CTX_ssh_gssapi_delegation "ssh.auth.gssapi.delegation:config-ssh-auth-gssapi-delegation"
+#define WINHELP_CTX_ssh_gssapi_libraries "ssh.auth.gssapi.libraries:config-ssh-auth-gssapi-libraries"
+#define WINHELP_CTX_selection_buttons "selection.buttons:config-mouse"
+#define WINHELP_CTX_selection_shiftdrag "selection.shiftdrag:config-mouseshift"
+#define WINHELP_CTX_selection_rect "selection.rect:config-rectselect"
+#define WINHELP_CTX_selection_charclasses "selection.charclasses:config-charclasses"
+#define WINHELP_CTX_selection_linedraw "selection.linedraw:config-linedrawpaste"
+#define WINHELP_CTX_selection_rtf "selection.rtf:config-rtfpaste"
+#define WINHELP_CTX_colours_ansi "colours.ansi:config-ansicolour"
+#define WINHELP_CTX_colours_xterm256 "colours.xterm256:config-xtermcolour"
+#define WINHELP_CTX_colours_bold "colours.bold:config-boldcolour"
+#define WINHELP_CTX_colours_system "colours.system:config-syscolour"
+#define WINHELP_CTX_colours_logpal "colours.logpal:config-logpalette"
+#define WINHELP_CTX_colours_config "colours.config:config-colourcfg"
+#define WINHELP_CTX_translation_codepage "translation.codepage:config-charset"
+#define WINHELP_CTX_translation_cjk_ambig_wide "translation.cjkambigwide:config-cjk-ambig-wide"
+#define WINHELP_CTX_translation_cyrillic "translation.cyrillic:config-cyr"
+#define WINHELP_CTX_translation_linedraw "translation.linedraw:config-linedraw"
+#define WINHELP_CTX_ssh_tunnels_x11 "ssh.tunnels.x11:config-ssh-x11"
+#define WINHELP_CTX_ssh_tunnels_x11auth "ssh.tunnels.x11auth:config-ssh-x11auth"
+#define WINHELP_CTX_ssh_tunnels_xauthority "ssh.tunnels.xauthority:config-ssh-xauthority"
+#define WINHELP_CTX_ssh_tunnels_portfwd "ssh.tunnels.portfwd:config-ssh-portfwd"
+#define WINHELP_CTX_ssh_tunnels_portfwd_localhost "ssh.tunnels.portfwd.localhost:config-ssh-portfwd-localhost"
+#define WINHELP_CTX_ssh_tunnels_portfwd_ipversion "ssh.tunnels.portfwd.ipversion:config-ssh-portfwd-address-family"
+#define WINHELP_CTX_ssh_bugs_ignore1 "ssh.bugs.ignore1:config-ssh-bug-ignore1"
+#define WINHELP_CTX_ssh_bugs_plainpw1 "ssh.bugs.plainpw1:config-ssh-bug-plainpw1"
+#define WINHELP_CTX_ssh_bugs_rsa1 "ssh.bugs.rsa1:config-ssh-bug-rsa1"
+#define WINHELP_CTX_ssh_bugs_ignore2 "ssh.bugs.ignore2:config-ssh-bug-ignore2"
+#define WINHELP_CTX_ssh_bugs_hmac2 "ssh.bugs.hmac2:config-ssh-bug-hmac2"
+#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_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"
+#define WINHELP_CTX_serial_flow "serial.flow:config-serial-flow"
+
+#define WINHELP_CTX_pageant_general "pageant.general:pageant"
+#define WINHELP_CTX_pageant_keylist "pageant.keylist:pageant-mainwin-keylist"
+#define WINHELP_CTX_pageant_addkey "pageant.addkey:pageant-mainwin-addkey"
+#define WINHELP_CTX_pageant_remkey "pageant.remkey:pageant-mainwin-remkey"
+#define WINHELP_CTX_pgpfingerprints "pgpfingerprints:pgpkeys"
+#define WINHELP_CTX_puttygen_general "puttygen.general:pubkey-puttygen"
+#define WINHELP_CTX_puttygen_keytype "puttygen.keytype:puttygen-keytype"
+#define WINHELP_CTX_puttygen_bits "puttygen.bits:puttygen-strength"
+#define WINHELP_CTX_puttygen_generate "puttygen.generate:puttygen-generate"
+#define WINHELP_CTX_puttygen_fingerprint "puttygen.fingerprint:puttygen-fingerprint"
+#define WINHELP_CTX_puttygen_comment "puttygen.comment:puttygen-comment"
+#define WINHELP_CTX_puttygen_passphrase "puttygen.passphrase:puttygen-passphrase"
+#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"
+
+/* 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
+
+#define WINHELP_CTXID_no_help 0
+#define WINHELP_CTX_errors_hostkey_absent "errors.hostkey.absent:errors-hostkey-absent"
+#define WINHELP_CTXID_errors_hostkey_absent 1
+#define WINHELP_CTX_errors_hostkey_changed "errors.hostkey.changed:errors-hostkey-wrong"
+#define WINHELP_CTXID_errors_hostkey_changed 2
+#define WINHELP_CTX_errors_cantloadkey "errors.cantloadkey:errors-cant-load-key"
+#define WINHELP_CTXID_errors_cantloadkey 3
+#define WINHELP_CTX_option_cleanup "options.cleanup:using-cleanup"
+#define WINHELP_CTXID_option_cleanup 4
+#define WINHELP_CTX_pgp_fingerprints "pgpfingerprints:pgpkeys"
+#define WINHELP_CTXID_pgp_fingerprints 5
--- /dev/null
+/*
+ * winjump.c: support for Windows 7 jump lists.
+ *
+ * The Windows 7 jumplist is a customizable list defined by the
+ * application. It is persistent across application restarts: the OS
+ * maintains the list when the app is not running. The list is shown
+ * when the user right-clicks on the taskbar button of a running app
+ * or a pinned non-running application. We use the jumplist to
+ * maintain a list of recently started saved sessions, started either
+ * by doubleclicking on a saved session, or with the command line
+ * "-load" parameter.
+ *
+ * Since the jumplist is write-only: it can only be replaced and the
+ * current list cannot be read, we must maintain the contents of the
+ * list persistantly in the registry. The file winstore.h contains
+ * functions to directly manipulate these registry entries. This file
+ * contains higher level functions to manipulate the jumplist.
+ */
+
+#include <assert.h>
+
+#include "putty.h"
+#include "storage.h"
+
+#define MAX_JUMPLIST_ITEMS 30 /* PuTTY will never show more items in
+ * the jumplist than this, regardless of
+ * user preferences. */
+
+/*
+ * COM structures and functions.
+ */
+#ifndef PROPERTYKEY_DEFINED
+#define PROPERTYKEY_DEFINED
+typedef struct _tagpropertykey {
+ GUID fmtid;
+ DWORD pid;
+} PROPERTYKEY;
+#endif
+#ifndef _REFPROPVARIANT_DEFINED
+#define _REFPROPVARIANT_DEFINED
+typedef PROPVARIANT *REFPROPVARIANT;
+#endif
+/* MinGW doesn't define this yet: */
+#ifndef _PROPVARIANTINIT_DEFINED_
+#define _PROPVARIANTINIT_DEFINED_
+#define PropVariantInit(pvar) memset((pvar),0,sizeof(PROPVARIANT))
+#endif
+
+#define IID_IShellLink IID_IShellLinkA
+
+typedef struct ICustomDestinationListVtbl {
+ HRESULT ( __stdcall *QueryInterface ) (
+ /* [in] ICustomDestinationList*/ void *This,
+ /* [in] */ const GUID * const riid,
+ /* [out] */ void **ppvObject);
+
+ ULONG ( __stdcall *AddRef )(
+ /* [in] ICustomDestinationList*/ void *This);
+
+ ULONG ( __stdcall *Release )(
+ /* [in] ICustomDestinationList*/ void *This);
+
+ HRESULT ( __stdcall *SetAppID )(
+ /* [in] ICustomDestinationList*/ void *This,
+ /* [string][in] */ LPCWSTR pszAppID);
+
+ HRESULT ( __stdcall *BeginList )(
+ /* [in] ICustomDestinationList*/ void *This,
+ /* [out] */ UINT *pcMinSlots,
+ /* [in] */ const GUID * const riid,
+ /* [out] */ void **ppv);
+
+ HRESULT ( __stdcall *AppendCategory )(
+ /* [in] ICustomDestinationList*/ void *This,
+ /* [string][in] */ LPCWSTR pszCategory,
+ /* [in] IObjectArray*/ void *poa);
+
+ HRESULT ( __stdcall *AppendKnownCategory )(
+ /* [in] ICustomDestinationList*/ void *This,
+ /* [in] KNOWNDESTCATEGORY*/ int category);
+
+ HRESULT ( __stdcall *AddUserTasks )(
+ /* [in] ICustomDestinationList*/ void *This,
+ /* [in] IObjectArray*/ void *poa);
+
+ HRESULT ( __stdcall *CommitList )(
+ /* [in] ICustomDestinationList*/ void *This);
+
+ HRESULT ( __stdcall *GetRemovedDestinations )(
+ /* [in] ICustomDestinationList*/ void *This,
+ /* [in] */ const IID * const riid,
+ /* [out] */ void **ppv);
+
+ HRESULT ( __stdcall *DeleteList )(
+ /* [in] ICustomDestinationList*/ void *This,
+ /* [string][unique][in] */ LPCWSTR pszAppID);
+
+ HRESULT ( __stdcall *AbortList )(
+ /* [in] ICustomDestinationList*/ void *This);
+
+} ICustomDestinationListVtbl;
+
+typedef struct ICustomDestinationList
+{
+ ICustomDestinationListVtbl *lpVtbl;
+} ICustomDestinationList;
+
+typedef struct IObjectArrayVtbl
+{
+ HRESULT ( __stdcall *QueryInterface )(
+ /* [in] IObjectArray*/ void *This,
+ /* [in] */ const GUID * const riid,
+ /* [out] */ void **ppvObject);
+
+ ULONG ( __stdcall *AddRef )(
+ /* [in] IObjectArray*/ void *This);
+
+ ULONG ( __stdcall *Release )(
+ /* [in] IObjectArray*/ void *This);
+
+ HRESULT ( __stdcall *GetCount )(
+ /* [in] IObjectArray*/ void *This,
+ /* [out] */ UINT *pcObjects);
+
+ HRESULT ( __stdcall *GetAt )(
+ /* [in] IObjectArray*/ void *This,
+ /* [in] */ UINT uiIndex,
+ /* [in] */ const GUID * const riid,
+ /* [out] */ void **ppv);
+
+} IObjectArrayVtbl;
+
+typedef struct IObjectArray
+{
+ IObjectArrayVtbl *lpVtbl;
+} IObjectArray;
+
+typedef struct IShellLinkVtbl
+{
+ HRESULT ( __stdcall *QueryInterface )(
+ /* [in] IShellLink*/ void *This,
+ /* [in] */ const GUID * const riid,
+ /* [out] */ void **ppvObject);
+
+ ULONG ( __stdcall *AddRef )(
+ /* [in] IShellLink*/ void *This);
+
+ ULONG ( __stdcall *Release )(
+ /* [in] IShellLink*/ void *This);
+
+ HRESULT ( __stdcall *GetPath )(
+ /* [in] IShellLink*/ void *This,
+ /* [string][out] */ LPSTR pszFile,
+ /* [in] */ int cch,
+ /* [unique][out][in] */ WIN32_FIND_DATAA *pfd,
+ /* [in] */ DWORD fFlags);
+
+ HRESULT ( __stdcall *GetIDList )(
+ /* [in] IShellLink*/ void *This,
+ /* [out] LPITEMIDLIST*/ void **ppidl);
+
+ HRESULT ( __stdcall *SetIDList )(
+ /* [in] IShellLink*/ void *This,
+ /* [in] LPITEMIDLIST*/ void *pidl);
+
+ HRESULT ( __stdcall *GetDescription )(
+ /* [in] IShellLink*/ void *This,
+ /* [string][out] */ LPSTR pszName,
+ /* [in] */ int cch);
+
+ HRESULT ( __stdcall *SetDescription )(
+ /* [in] IShellLink*/ void *This,
+ /* [string][in] */ LPCSTR pszName);
+
+ HRESULT ( __stdcall *GetWorkingDirectory )(
+ /* [in] IShellLink*/ void *This,
+ /* [string][out] */ LPSTR pszDir,
+ /* [in] */ int cch);
+
+ HRESULT ( __stdcall *SetWorkingDirectory )(
+ /* [in] IShellLink*/ void *This,
+ /* [string][in] */ LPCSTR pszDir);
+
+ HRESULT ( __stdcall *GetArguments )(
+ /* [in] IShellLink*/ void *This,
+ /* [string][out] */ LPSTR pszArgs,
+ /* [in] */ int cch);
+
+ HRESULT ( __stdcall *SetArguments )(
+ /* [in] IShellLink*/ void *This,
+ /* [string][in] */ LPCSTR pszArgs);
+
+ HRESULT ( __stdcall *GetHotkey )(
+ /* [in] IShellLink*/ void *This,
+ /* [out] */ WORD *pwHotkey);
+
+ HRESULT ( __stdcall *SetHotkey )(
+ /* [in] IShellLink*/ void *This,
+ /* [in] */ WORD wHotkey);
+
+ HRESULT ( __stdcall *GetShowCmd )(
+ /* [in] IShellLink*/ void *This,
+ /* [out] */ int *piShowCmd);
+
+ HRESULT ( __stdcall *SetShowCmd )(
+ /* [in] IShellLink*/ void *This,
+ /* [in] */ int iShowCmd);
+
+ HRESULT ( __stdcall *GetIconLocation )(
+ /* [in] IShellLink*/ void *This,
+ /* [string][out] */ LPSTR pszIconPath,
+ /* [in] */ int cch,
+ /* [out] */ int *piIcon);
+
+ HRESULT ( __stdcall *SetIconLocation )(
+ /* [in] IShellLink*/ void *This,
+ /* [string][in] */ LPCSTR pszIconPath,
+ /* [in] */ int iIcon);
+
+ HRESULT ( __stdcall *SetRelativePath )(
+ /* [in] IShellLink*/ void *This,
+ /* [string][in] */ LPCSTR pszPathRel,
+ /* [in] */ DWORD dwReserved);
+
+ HRESULT ( __stdcall *Resolve )(
+ /* [in] IShellLink*/ void *This,
+ /* [unique][in] */ HWND hwnd,
+ /* [in] */ DWORD fFlags);
+
+ HRESULT ( __stdcall *SetPath )(
+ /* [in] IShellLink*/ void *This,
+ /* [string][in] */ LPCSTR pszFile);
+
+} IShellLinkVtbl;
+
+typedef struct IShellLink
+{
+ IShellLinkVtbl *lpVtbl;
+} IShellLink;
+
+typedef struct IObjectCollectionVtbl
+{
+ HRESULT ( __stdcall *QueryInterface )(
+ /* [in] IShellLink*/ void *This,
+ /* [in] */ const GUID * const riid,
+ /* [out] */ void **ppvObject);
+
+ ULONG ( __stdcall *AddRef )(
+ /* [in] IShellLink*/ void *This);
+
+ ULONG ( __stdcall *Release )(
+ /* [in] IShellLink*/ void *This);
+
+ HRESULT ( __stdcall *GetCount )(
+ /* [in] IShellLink*/ void *This,
+ /* [out] */ UINT *pcObjects);
+
+ HRESULT ( __stdcall *GetAt )(
+ /* [in] IShellLink*/ void *This,
+ /* [in] */ UINT uiIndex,
+ /* [in] */ const GUID * const riid,
+ /* [iid_is][out] */ void **ppv);
+
+ HRESULT ( __stdcall *AddObject )(
+ /* [in] IShellLink*/ void *This,
+ /* [in] */ void *punk);
+
+ HRESULT ( __stdcall *AddFromArray )(
+ /* [in] IShellLink*/ void *This,
+ /* [in] */ IObjectArray *poaSource);
+
+ HRESULT ( __stdcall *RemoveObjectAt )(
+ /* [in] IShellLink*/ void *This,
+ /* [in] */ UINT uiIndex);
+
+ HRESULT ( __stdcall *Clear )(
+ /* [in] IShellLink*/ void *This);
+
+} IObjectCollectionVtbl;
+
+typedef struct IObjectCollection
+{
+ IObjectCollectionVtbl *lpVtbl;
+} IObjectCollection;
+
+typedef struct IPropertyStoreVtbl
+{
+ HRESULT ( __stdcall *QueryInterface )(
+ /* [in] IPropertyStore*/ void *This,
+ /* [in] */ const GUID * const riid,
+ /* [iid_is][out] */ void **ppvObject);
+
+ ULONG ( __stdcall *AddRef )(
+ /* [in] IPropertyStore*/ void *This);
+
+ ULONG ( __stdcall *Release )(
+ /* [in] IPropertyStore*/ void *This);
+
+ HRESULT ( __stdcall *GetCount )(
+ /* [in] IPropertyStore*/ void *This,
+ /* [out] */ DWORD *cProps);
+
+ HRESULT ( __stdcall *GetAt )(
+ /* [in] IPropertyStore*/ void *This,
+ /* [in] */ DWORD iProp,
+ /* [out] */ PROPERTYKEY *pkey);
+
+ HRESULT ( __stdcall *GetValue )(
+ /* [in] IPropertyStore*/ void *This,
+ /* [in] */ const PROPERTYKEY * const key,
+ /* [out] */ PROPVARIANT *pv);
+
+ HRESULT ( __stdcall *SetValue )(
+ /* [in] IPropertyStore*/ void *This,
+ /* [in] */ const PROPERTYKEY * const key,
+ /* [in] */ REFPROPVARIANT propvar);
+
+ HRESULT ( __stdcall *Commit )(
+ /* [in] IPropertyStore*/ void *This);
+} IPropertyStoreVtbl;
+
+typedef struct IPropertyStore
+{
+ IPropertyStoreVtbl *lpVtbl;
+} IPropertyStore;
+
+static const CLSID CLSID_DestinationList = {
+ 0x77f10cf0, 0x3db5, 0x4966, {0xb5,0x20,0xb7,0xc5,0x4f,0xd3,0x5e,0xd6}
+};
+static const CLSID CLSID_ShellLink = {
+ 0x00021401, 0x0000, 0x0000, {0xc0,0x00,0x00,0x00,0x00,0x00,0x00,0x46}
+};
+static const CLSID CLSID_EnumerableObjectCollection = {
+ 0x2d3468c1, 0x36a7, 0x43b6, {0xac,0x24,0xd3,0xf0,0x2f,0xd9,0x60,0x7a}
+};
+static const IID IID_IObjectCollection = {
+ 0x5632b1a4, 0xe38a, 0x400a, {0x92,0x8a,0xd4,0xcd,0x63,0x23,0x02,0x95}
+};
+static const IID IID_IShellLink = {
+ 0x000214ee, 0x0000, 0x0000, {0xc0,0x00,0x00,0x00,0x00,0x00,0x00,0x46}
+};
+static const IID IID_ICustomDestinationList = {
+ 0x6332debf, 0x87b5, 0x4670, {0x90,0xc0,0x5e,0x57,0xb4,0x08,0xa4,0x9e}
+};
+static const IID IID_IObjectArray = {
+ 0x92ca9dcd, 0x5622, 0x4bba, {0xa8,0x05,0x5e,0x9f,0x54,0x1b,0xd8,0xc9}
+};
+static const IID IID_IPropertyStore = {
+ 0x886d8eeb, 0x8cf2, 0x4446, {0x8d,0x02,0xcd,0xba,0x1d,0xbd,0xcf,0x99}
+};
+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. */
+#define COMPTR(type, obj) &IID_##type, \
+ (void **)(void *)((obj) + (sizeof((obj)-(type **)(obj))) \
+ - (sizeof((obj)-(type **)(obj))))
+
+static char putty_path[2048];
+
+/*
+ * Function to make an IShellLink describing a particular PuTTY
+ * command. If 'appname' is null, the command run will be the one
+ * returned by GetModuleFileName, i.e. our own executable; if it's
+ * non-null then it will be assumed to be a filename in the same
+ * directory as our own executable, and the return value will be NULL
+ * if that file doesn't exist.
+ *
+ * If 'sessionname' is null then no command line will be passed to the
+ * program. If it's non-null, the command line will be that text
+ * prefixed with an @ (to load a PuTTY saved session).
+ *
+ * Hence, you can launch a saved session using make_shell_link(NULL,
+ * sessionname), and launch another app using e.g.
+ * make_shell_link("puttygen.exe", NULL).
+ */
+static IShellLink *make_shell_link(const char *appname,
+ const char *sessionname)
+{
+ IShellLink *ret;
+ char *app_path, *param_string, *desc_string;
+ void *psettings_tmp;
+ IPropertyStore *pPS;
+ PROPVARIANT pv;
+
+ /* Retrieve path to executable. */
+ if (!putty_path[0])
+ GetModuleFileName(NULL, putty_path, sizeof(putty_path) - 1);
+ if (appname) {
+ char *p, *q = putty_path;
+ FILE *fp;
+
+ if ((p = strrchr(q, '\\')) != NULL) q = p+1;
+ if ((p = strrchr(q, ':')) != NULL) q = p+1;
+ app_path = dupprintf("%.*s%s", (int)(q - putty_path), putty_path,
+ appname);
+ if ((fp = fopen(app_path, "r")) == NULL) {
+ sfree(app_path);
+ return NULL;
+ }
+ fclose(fp);
+ } else {
+ app_path = dupstr(putty_path);
+ }
+
+ /* Check if this is a valid session, otherwise don't add. */
+ if (sessionname) {
+ psettings_tmp = open_settings_r(sessionname);
+ if (!psettings_tmp)
+ return NULL;
+ close_settings_r(psettings_tmp);
+ }
+
+ /* Create the new item. */
+ if (!SUCCEEDED(CoCreateInstance(&CLSID_ShellLink, NULL,
+ CLSCTX_INPROC_SERVER,
+ COMPTR(IShellLink, &ret))))
+ return NULL;
+
+ /* Set path, parameters, icon and description. */
+ ret->lpVtbl->SetPath(ret, app_path);
+
+ if (sessionname) {
+ param_string = dupcat("@", sessionname, NULL);
+ } else {
+ param_string = dupstr("");
+ }
+ ret->lpVtbl->SetArguments(ret, param_string);
+ sfree(param_string);
+
+ if (sessionname) {
+ desc_string = dupcat("Connect to PuTTY session '",
+ sessionname, "'", NULL);
+ } else {
+ assert(appname);
+ desc_string = dupprintf("Run %.*s", strcspn(appname, "."), appname);
+ }
+ ret->lpVtbl->SetDescription(ret, desc_string);
+ sfree(desc_string);
+
+ ret->lpVtbl->SetIconLocation(ret, app_path, 0);
+
+ /* To set the link title, we require the property store of the link. */
+ if (SUCCEEDED(ret->lpVtbl->QueryInterface(ret,
+ COMPTR(IPropertyStore, &pPS)))) {
+ PropVariantInit(&pv);
+ pv.vt = VT_LPSTR;
+ if (sessionname) {
+ pv.pszVal = dupstr(sessionname);
+ } else {
+ assert(appname);
+ pv.pszVal = dupprintf("Run %.*s", strcspn(appname, "."), appname);
+ }
+ pPS->lpVtbl->SetValue(pPS, &PKEY_Title, &pv);
+ sfree(pv.pszVal);
+ pPS->lpVtbl->Commit(pPS);
+ pPS->lpVtbl->Release(pPS);
+ }
+
+ sfree(app_path);
+
+ return ret;
+}
+
+/* Updates jumplist from registry. */
+static void update_jumplist_from_registry(void)
+{
+ const char *piterator;
+ UINT num_items;
+ int jumplist_counter;
+ UINT nremoved;
+
+ /* Variables used by the cleanup code must be initialised to NULL,
+ * so that we don't try to free or release them if they were never
+ * set up. */
+ ICustomDestinationList *pCDL = NULL;
+ char *pjumplist_reg_entries = NULL;
+ IObjectCollection *collection = NULL;
+ IObjectArray *array = NULL;
+ IShellLink *link = NULL;
+ IObjectArray *pRemoved = NULL;
+ int need_abort = FALSE;
+
+ /*
+ * Create an ICustomDestinationList: the top-level object which
+ * deals with jump list management.
+ */
+ if (!SUCCEEDED(CoCreateInstance(&CLSID_DestinationList, NULL,
+ CLSCTX_INPROC_SERVER,
+ COMPTR(ICustomDestinationList, &pCDL))))
+ goto cleanup;
+
+ /*
+ * Call its BeginList method to start compiling a list. This gives
+ * us back 'num_items' (a hint derived from systemwide
+ * configuration about how many things to put on the list) and
+ * 'pRemoved' (user configuration about things to leave off the
+ * list).
+ */
+ if (!SUCCEEDED(pCDL->lpVtbl->BeginList(pCDL, &num_items,
+ COMPTR(IObjectArray, &pRemoved))))
+ goto cleanup;
+ need_abort = TRUE;
+ if (!SUCCEEDED(pRemoved->lpVtbl->GetCount(pRemoved, &nremoved)))
+ nremoved = 0;
+
+ /*
+ * Create an object collection to form the 'Recent Sessions'
+ * category on the jump list.
+ */
+ if (!SUCCEEDED(CoCreateInstance(&CLSID_EnumerableObjectCollection,
+ NULL, CLSCTX_INPROC_SERVER,
+ COMPTR(IObjectCollection, &collection))))
+ goto cleanup;
+
+ /*
+ * Go through the jump list entries from the registry and add each
+ * one to the collection.
+ */
+ pjumplist_reg_entries = get_jumplist_registry_entries();
+ piterator = pjumplist_reg_entries;
+ jumplist_counter = 0;
+ while (*piterator != '\0' &&
+ (jumplist_counter < min(MAX_JUMPLIST_ITEMS, (int) num_items))) {
+ link = make_shell_link(NULL, piterator);
+ if (link) {
+ UINT i;
+ int found;
+
+ /*
+ * Check that the link isn't in the user-removed list.
+ */
+ for (i = 0, found = FALSE; i < nremoved && !found; i++) {
+ IShellLink *rlink;
+ if (SUCCEEDED(pRemoved->lpVtbl->GetAt
+ (pRemoved, i, COMPTR(IShellLink, &rlink)))) {
+ char desc1[2048], desc2[2048];
+ if (SUCCEEDED(link->lpVtbl->GetDescription
+ (link, desc1, sizeof(desc1)-1)) &&
+ SUCCEEDED(rlink->lpVtbl->GetDescription
+ (rlink, desc2, sizeof(desc2)-1)) &&
+ !strcmp(desc1, desc2)) {
+ found = TRUE;
+ }
+ rlink->lpVtbl->Release(rlink);
+ }
+ }
+
+ if (!found) {
+ collection->lpVtbl->AddObject(collection, link);
+ jumplist_counter++;
+ }
+
+ link->lpVtbl->Release(link);
+ link = NULL;
+ }
+ piterator += strlen(piterator) + 1;
+ }
+ sfree(pjumplist_reg_entries);
+ pjumplist_reg_entries = NULL;
+
+ /*
+ * Get the array form of the collection we've just constructed,
+ * and put it in the jump list.
+ */
+ if (!SUCCEEDED(collection->lpVtbl->QueryInterface
+ (collection, COMPTR(IObjectArray, &array))))
+ goto cleanup;
+
+ pCDL->lpVtbl->AppendCategory(pCDL, L"Recent Sessions", array);
+
+ /*
+ * Create an object collection to form the 'Tasks' category on the
+ * jump list.
+ */
+ if (!SUCCEEDED(CoCreateInstance(&CLSID_EnumerableObjectCollection,
+ NULL, CLSCTX_INPROC_SERVER,
+ COMPTR(IObjectCollection, &collection))))
+ goto cleanup;
+
+ /*
+ * Add task entries for PuTTYgen and Pageant.
+ */
+ piterator = "Pageant.exe\0PuTTYgen.exe\0\0";
+ while (*piterator != '\0') {
+ link = make_shell_link(piterator, NULL);
+ if (link) {
+ collection->lpVtbl->AddObject(collection, link);
+ link->lpVtbl->Release(link);
+ link = NULL;
+ }
+ piterator += strlen(piterator) + 1;
+ }
+
+ /*
+ * Get the array form of the collection we've just constructed,
+ * and put it in the jump list.
+ */
+ if (!SUCCEEDED(collection->lpVtbl->QueryInterface
+ (collection, COMPTR(IObjectArray, &array))))
+ goto cleanup;
+
+ pCDL->lpVtbl->AddUserTasks(pCDL, array);
+
+ /*
+ * Now we can clean up the array and collection variables, so as
+ * to be able to reuse them.
+ */
+ array->lpVtbl->Release(array);
+ array = NULL;
+ collection->lpVtbl->Release(collection);
+ collection = NULL;
+
+ /*
+ * Create another object collection to form the user tasks
+ * category.
+ */
+ if (!SUCCEEDED(CoCreateInstance(&CLSID_EnumerableObjectCollection,
+ NULL, CLSCTX_INPROC_SERVER,
+ COMPTR(IObjectCollection, &collection))))
+ goto cleanup;
+
+ /*
+ * Get the array form of the collection we've just constructed,
+ * and put it in the jump list.
+ */
+ if (!SUCCEEDED(collection->lpVtbl->QueryInterface
+ (collection, COMPTR(IObjectArray, &array))))
+ goto cleanup;
+
+ pCDL->lpVtbl->AddUserTasks(pCDL, array);
+
+ /*
+ * Now we can clean up the array and collection variables, so as
+ * to be able to reuse them.
+ */
+ array->lpVtbl->Release(array);
+ array = NULL;
+ collection->lpVtbl->Release(collection);
+ collection = NULL;
+
+ /*
+ * Commit the jump list.
+ */
+ pCDL->lpVtbl->CommitList(pCDL);
+ need_abort = FALSE;
+
+ /*
+ * Clean up.
+ */
+ cleanup:
+ if (pRemoved) pRemoved->lpVtbl->Release(pRemoved);
+ if (pCDL && need_abort) pCDL->lpVtbl->AbortList(pCDL);
+ if (pCDL) pCDL->lpVtbl->Release(pCDL);
+ if (collection) collection->lpVtbl->Release(collection);
+ if (array) array->lpVtbl->Release(array);
+ if (link) link->lpVtbl->Release(link);
+ sfree(pjumplist_reg_entries);
+}
+
+/* Clears the entire jumplist. */
+void clear_jumplist(void)
+{
+ ICustomDestinationList *pCDL;
+
+ if (CoCreateInstance(&CLSID_DestinationList, NULL, CLSCTX_INPROC_SERVER,
+ COMPTR(ICustomDestinationList, &pCDL)) == S_OK) {
+ pCDL->lpVtbl->DeleteList(pCDL, NULL);
+ pCDL->lpVtbl->Release(pCDL);
+ }
+
+}
+
+/* Adds a saved session to the Windows 7 jumplist. */
+void add_session_to_jumplist(const char * const sessionname)
+{
+ if ((osVersion.dwMajorVersion < 6) ||
+ (osVersion.dwMajorVersion == 6 && osVersion.dwMinorVersion < 1))
+ return; /* do nothing on pre-Win7 systems */
+
+ if (add_to_jumplist_registry(sessionname) == JUMPLISTREG_OK) {
+ update_jumplist_from_registry();
+ } else {
+ /* Make sure we don't leave the jumplist dangling. */
+ clear_jumplist();
+ }
+}
+
+/* Removes a saved session from the Windows jumplist. */
+void remove_session_from_jumplist(const char * const sessionname)
+{
+ if ((osVersion.dwMajorVersion < 6) ||
+ (osVersion.dwMajorVersion == 6 && osVersion.dwMinorVersion < 1))
+ return; /* do nothing on pre-Win7 systems */
+
+ if (remove_from_jumplist_registry(sessionname) == JUMPLISTREG_OK) {
+ update_jumplist_from_registry();
+ } else {
+ /* Make sure we don't leave the jumplist dangling. */
+ clear_jumplist();
+ }
+}
--- /dev/null
+/*
+ * winmisc.c: miscellaneous Windows-specific things
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include "putty.h"
+#include <security.h>
+
+OSVERSIONINFO osVersion;
+
+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 ret;
+ strncpy(ret.path, str, sizeof(ret.path));
+ ret.path[sizeof(ret.path)-1] = '\0';
+ return ret;
+}
+
+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;
+}
+
+char *get_username(void)
+{
+ DWORD namelen;
+ char *user;
+ int got_username = FALSE;
+ DECL_WINDOWS_FUNCTION(static, BOOLEAN, GetUserNameExA,
+ (EXTENDED_NAME_FORMAT, LPSTR, PULONG));
+
+ {
+ static int tried_usernameex = FALSE;
+ if (!tried_usernameex) {
+ /* Not available on Win9x, so load dynamically */
+ HMODULE secur32 = load_system32_dll("secur32.dll");
+ GET_WINDOWS_FUNCTION(secur32, GetUserNameExA);
+ tried_usernameex = TRUE;
+ }
+ }
+
+ if (p_GetUserNameExA) {
+ /*
+ * If available, use the principal -- this avoids the problem
+ * that the local username is case-insensitive but Kerberos
+ * usernames are case-sensitive.
+ */
+
+ /* Get the length */
+ namelen = 0;
+ (void) p_GetUserNameExA(NameUserPrincipal, NULL, &namelen);
+
+ user = snewn(namelen, char);
+ got_username = p_GetUserNameExA(NameUserPrincipal, user, &namelen);
+ if (got_username) {
+ char *p = strchr(user, '@');
+ if (p) *p = 0;
+ } else {
+ sfree(user);
+ }
+ }
+
+ if (!got_username) {
+ /* Fall back to local user name */
+ namelen = 0;
+ if (GetUserName(NULL, &namelen) == FALSE) {
+ /*
+ * Apparently this doesn't work at least on Windows XP SP2.
+ * Thus assume a maximum of 256. It will fail again if it
+ * doesn't fit.
+ */
+ namelen = 256;
+ }
+
+ user = snewn(namelen, char);
+ got_username = GetUserName(user, &namelen);
+ if (!got_username) {
+ sfree(user);
+ }
+ }
+
+ return got_username ? user : NULL;
+}
+
+BOOL init_winver(void)
+{
+ ZeroMemory(&osVersion, sizeof(osVersion));
+ osVersion.dwOSVersionInfoSize = sizeof (OSVERSIONINFO);
+ return GetVersionEx ( (OSVERSIONINFO *) &osVersion);
+}
+
+HMODULE load_system32_dll(const char *libname)
+{
+ /*
+ * Wrapper function to load a DLL out of c:\windows\system32
+ * without going through the full DLL search path. (Hence no
+ * attack is possible by placing a substitute DLL earlier on that
+ * path.)
+ */
+ static char *sysdir = NULL;
+ char *fullpath;
+ HMODULE ret;
+
+ if (!sysdir) {
+ int size = 0, len;
+ do {
+ size = 3*size/2 + 512;
+ sysdir = sresize(sysdir, size, char);
+ len = GetSystemDirectory(sysdir, size);
+ } while (len >= size);
+ }
+
+ fullpath = dupcat(sysdir, "\\", libname, NULL);
+ ret = LoadLibrary(fullpath);
+ sfree(fullpath);
+ return ret;
+}
+
+#ifdef DEBUG
+static FILE *debug_fp = NULL;
+static HANDLE debug_hdl = INVALID_HANDLE_VALUE;
+static int debug_got_console = 0;
+
+void dputs(char *buf)
+{
+ DWORD dw;
+
+ if (!debug_got_console) {
+ if (AllocConsole()) {
+ debug_got_console = 1;
+ debug_hdl = GetStdHandle(STD_OUTPUT_HANDLE);
+ }
+ }
+ if (!debug_fp) {
+ debug_fp = fopen("debug.log", "w");
+ }
+
+ if (debug_hdl != INVALID_HANDLE_VALUE) {
+ WriteFile(debug_hdl, buf, strlen(buf), &dw, NULL);
+ }
+ fputs(buf, debug_fp);
+ fflush(debug_fp);
+}
+#endif
+
+#ifdef MINEFIELD
+/*
+ * Minefield - a Windows equivalent for Electric Fence
+ */
+
+#define PAGESIZE 4096
+
+/*
+ * Design:
+ *
+ * We start by reserving as much virtual address space as Windows
+ * will sensibly (or not sensibly) let us have. We flag it all as
+ * invalid memory.
+ *
+ * Any allocation attempt is satisfied by committing one or more
+ * pages, with an uncommitted page on either side. The returned
+ * memory region is jammed up against the _end_ of the pages.
+ *
+ * Freeing anything causes instantaneous decommitment of the pages
+ * involved, so stale pointers are caught as soon as possible.
+ */
+
+static int minefield_initialised = 0;
+static void *minefield_region = NULL;
+static long minefield_size = 0;
+static long minefield_npages = 0;
+static long minefield_curpos = 0;
+static unsigned short *minefield_admin = NULL;
+static void *minefield_pages = NULL;
+
+static void minefield_admin_hide(int hide)
+{
+ int access = hide ? PAGE_NOACCESS : PAGE_READWRITE;
+ VirtualProtect(minefield_admin, minefield_npages * 2, access, NULL);
+}
+
+static void minefield_init(void)
+{
+ int size;
+ int admin_size;
+ int i;
+
+ for (size = 0x40000000; size > 0; size = ((size >> 3) * 7) & ~0xFFF) {
+ minefield_region = VirtualAlloc(NULL, size,
+ MEM_RESERVE, PAGE_NOACCESS);
+ if (minefield_region)
+ break;
+ }
+ minefield_size = size;
+
+ /*
+ * Firstly, allocate a section of that to be the admin block.
+ * We'll need a two-byte field for each page.
+ */
+ minefield_admin = minefield_region;
+ minefield_npages = minefield_size / PAGESIZE;
+ admin_size = (minefield_npages * 2 + PAGESIZE - 1) & ~(PAGESIZE - 1);
+ minefield_npages = (minefield_size - admin_size) / PAGESIZE;
+ minefield_pages = (char *) minefield_region + admin_size;
+
+ /*
+ * Commit the admin region.
+ */
+ VirtualAlloc(minefield_admin, minefield_npages * 2,
+ MEM_COMMIT, PAGE_READWRITE);
+
+ /*
+ * Mark all pages as unused (0xFFFF).
+ */
+ for (i = 0; i < minefield_npages; i++)
+ minefield_admin[i] = 0xFFFF;
+
+ /*
+ * Hide the admin region.
+ */
+ minefield_admin_hide(1);
+
+ minefield_initialised = 1;
+}
+
+static void minefield_bomb(void)
+{
+ div(1, *(int *) minefield_pages);
+}
+
+static void *minefield_alloc(int size)
+{
+ int npages;
+ int pos, lim, region_end, region_start;
+ int start;
+ int i;
+
+ npages = (size + PAGESIZE - 1) / PAGESIZE;
+
+ minefield_admin_hide(0);
+
+ /*
+ * Search from current position until we find a contiguous
+ * bunch of npages+2 unused pages.
+ */
+ pos = minefield_curpos;
+ lim = minefield_npages;
+ while (1) {
+ /* Skip over used pages. */
+ while (pos < lim && minefield_admin[pos] != 0xFFFF)
+ pos++;
+ /* Count unused pages. */
+ start = pos;
+ while (pos < lim && pos - start < npages + 2 &&
+ minefield_admin[pos] == 0xFFFF)
+ pos++;
+ if (pos - start == npages + 2)
+ break;
+ /* If we've reached the limit, reset the limit or stop. */
+ if (pos >= lim) {
+ if (lim == minefield_npages) {
+ /* go round and start again at zero */
+ lim = minefield_curpos;
+ pos = 0;
+ } else {
+ minefield_admin_hide(1);
+ return NULL;
+ }
+ }
+ }
+
+ minefield_curpos = pos - 1;
+
+ /*
+ * We have npages+2 unused pages starting at start. We leave
+ * the first and last of these alone and use the rest.
+ */
+ region_end = (start + npages + 1) * PAGESIZE;
+ region_start = region_end - size;
+ /* FIXME: could align here if we wanted */
+
+ /*
+ * Update the admin region.
+ */
+ for (i = start + 2; i < start + npages + 1; i++)
+ minefield_admin[i] = 0xFFFE; /* used but no region starts here */
+ minefield_admin[start + 1] = region_start % PAGESIZE;
+
+ minefield_admin_hide(1);
+
+ VirtualAlloc((char *) minefield_pages + region_start, size,
+ MEM_COMMIT, PAGE_READWRITE);
+ return (char *) minefield_pages + region_start;
+}
+
+static void minefield_free(void *ptr)
+{
+ int region_start, i, j;
+
+ minefield_admin_hide(0);
+
+ region_start = (char *) ptr - (char *) minefield_pages;
+ i = region_start / PAGESIZE;
+ if (i < 0 || i >= minefield_npages ||
+ minefield_admin[i] != region_start % PAGESIZE)
+ minefield_bomb();
+ for (j = i; j < minefield_npages && minefield_admin[j] != 0xFFFF; j++) {
+ minefield_admin[j] = 0xFFFF;
+ }
+
+ VirtualFree(ptr, j * PAGESIZE - region_start, MEM_DECOMMIT);
+
+ minefield_admin_hide(1);
+}
+
+static int minefield_get_size(void *ptr)
+{
+ int region_start, i, j;
+
+ minefield_admin_hide(0);
+
+ region_start = (char *) ptr - (char *) minefield_pages;
+ i = region_start / PAGESIZE;
+ if (i < 0 || i >= minefield_npages ||
+ minefield_admin[i] != region_start % PAGESIZE)
+ minefield_bomb();
+ for (j = i; j < minefield_npages && minefield_admin[j] != 0xFFFF; j++);
+
+ minefield_admin_hide(1);
+
+ return j * PAGESIZE - region_start;
+}
+
+void *minefield_c_malloc(size_t size)
+{
+ if (!minefield_initialised)
+ minefield_init();
+ return minefield_alloc(size);
+}
+
+void minefield_c_free(void *p)
+{
+ if (!minefield_initialised)
+ minefield_init();
+ minefield_free(p);
+}
+
+/*
+ * realloc _always_ moves the chunk, for rapid detection of code
+ * that assumes it won't.
+ */
+void *minefield_c_realloc(void *p, size_t size)
+{
+ size_t oldsize;
+ void *q;
+ if (!minefield_initialised)
+ minefield_init();
+ q = minefield_alloc(size);
+ oldsize = minefield_get_size(p);
+ memcpy(q, p, (oldsize < size ? oldsize : size));
+ minefield_free(p);
+ return q;
+}
+
+#endif /* MINEFIELD */
--- /dev/null
+/*
+ * Windows networking abstraction.
+ *
+ * For the IPv6 code in here I am indebted to Jeroen Massar and
+ * unfix.org.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#define DEFINE_PLUG_METHOD_MACROS
+#include "putty.h"
+#include "network.h"
+#include "tree234.h"
+
+#include <ws2tcpip.h>
+
+#ifndef NO_IPV6
+const struct in6_addr in6addr_any = IN6ADDR_ANY_INIT;
+const struct in6_addr in6addr_loopback = IN6ADDR_LOOPBACK_INIT;
+#endif
+
+#define ipv4_is_loopback(addr) \
+ ((p_ntohl(addr.s_addr) & 0xFF000000L) == 0x7F000000L)
+
+/*
+ * We used to typedef struct Socket_tag *Socket.
+ *
+ * Since we have made the networking abstraction slightly more
+ * abstract, Socket no longer means a tcp socket (it could mean
+ * an ssl socket). So now we must use Actual_Socket when we know
+ * we are talking about a tcp socket.
+ */
+typedef struct Socket_tag *Actual_Socket;
+
+/*
+ * Mutable state that goes with a SockAddr: stores information
+ * about where in the list of candidate IP(v*) addresses we've
+ * currently got to.
+ */
+typedef struct SockAddrStep_tag SockAddrStep;
+struct SockAddrStep_tag {
+#ifndef NO_IPV6
+ struct addrinfo *ai; /* steps along addr->ais */
+#endif
+ int curraddr;
+};
+
+struct Socket_tag {
+ 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
+ * notification while we were frozen */
+ int localhost_only; /* for listening sockets */
+ char oobdata[1];
+ int sending_oob;
+ int oobinline, nodelay, keepalive, privport;
+ SockAddr addr;
+ SockAddrStep step;
+ int port;
+ int pending_error; /* in case send() returns error */
+ /*
+ * We sometimes need pairs of Socket structures to be linked:
+ * if we are listening on the same IPv6 and v4 port, for
+ * example. So here we define `parent' and `child' pointers to
+ * track this link.
+ */
+ Actual_Socket parent, child;
+};
+
+struct SockAddr_tag {
+ int refcount;
+ char *error;
+ int resolved;
+#ifndef NO_IPV6
+ struct addrinfo *ais; /* Addresses IPv6 style. */
+#endif
+ unsigned long *addresses; /* Addresses IPv4 style. */
+ int naddresses;
+ char hostname[512]; /* Store an unresolved host name. */
+};
+
+/*
+ * Which address family this address belongs to. AF_INET for IPv4;
+ * AF_INET6 for IPv6; AF_UNSPEC indicates that name resolution has
+ * not been done and a simple host name is held in this SockAddr
+ * structure.
+ */
+#ifndef NO_IPV6
+#define SOCKADDR_FAMILY(addr, step) \
+ (!(addr)->resolved ? AF_UNSPEC : \
+ (step).ai ? (step).ai->ai_family : AF_INET)
+#else
+#define SOCKADDR_FAMILY(addr, step) \
+ (!(addr)->resolved ? AF_UNSPEC : AF_INET)
+#endif
+
+/*
+ * Start a SockAddrStep structure to step through multiple
+ * addresses.
+ */
+#ifndef NO_IPV6
+#define START_STEP(addr, step) \
+ ((step).ai = (addr)->ais, (step).curraddr = 0)
+#else
+#define START_STEP(addr, step) \
+ ((step).curraddr = 0)
+#endif
+
+static tree234 *sktree;
+
+static int cmpfortree(void *av, void *bv)
+{
+ Actual_Socket a = (Actual_Socket) av, b = (Actual_Socket) bv;
+ unsigned long as = (unsigned long) a->s, bs = (unsigned long) b->s;
+ if (as < bs)
+ return -1;
+ if (as > bs)
+ return +1;
+ if (a < b)
+ return -1;
+ if (a > b)
+ return +1;
+ return 0;
+}
+
+static int cmpforsearch(void *av, void *bv)
+{
+ Actual_Socket b = (Actual_Socket) bv;
+ unsigned long as = (unsigned long) av, bs = (unsigned long) b->s;
+ if (as < bs)
+ return -1;
+ if (as > bs)
+ return +1;
+ return 0;
+}
+
+DECL_WINDOWS_FUNCTION(static, int, WSAStartup, (WORD, LPWSADATA));
+DECL_WINDOWS_FUNCTION(static, int, WSACleanup, (void));
+DECL_WINDOWS_FUNCTION(static, int, closesocket, (SOCKET));
+DECL_WINDOWS_FUNCTION(static, u_long, ntohl, (u_long));
+DECL_WINDOWS_FUNCTION(static, u_long, htonl, (u_long));
+DECL_WINDOWS_FUNCTION(static, u_short, htons, (u_short));
+DECL_WINDOWS_FUNCTION(static, u_short, ntohs, (u_short));
+DECL_WINDOWS_FUNCTION(static, int, gethostname, (char *, int));
+DECL_WINDOWS_FUNCTION(static, struct hostent FAR *, gethostbyname,
+ (const char FAR *));
+DECL_WINDOWS_FUNCTION(static, struct servent FAR *, getservbyname,
+ (const char FAR *, const char FAR *));
+DECL_WINDOWS_FUNCTION(static, unsigned long, inet_addr, (const char FAR *));
+DECL_WINDOWS_FUNCTION(static, char FAR *, inet_ntoa, (struct in_addr));
+DECL_WINDOWS_FUNCTION(static, int, connect,
+ (SOCKET, const struct sockaddr FAR *, int));
+DECL_WINDOWS_FUNCTION(static, int, bind,
+ (SOCKET, const struct sockaddr FAR *, int));
+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, 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));
+DECL_WINDOWS_FUNCTION(static, int, WSAIoctl,
+ (SOCKET, DWORD, LPVOID, DWORD, LPVOID, DWORD,
+ LPDWORD, LPWSAOVERLAPPED,
+ LPWSAOVERLAPPED_COMPLETION_ROUTINE));
+#ifndef NO_IPV6
+DECL_WINDOWS_FUNCTION(static, int, getaddrinfo,
+ (const char *nodename, const char *servname,
+ const struct addrinfo *hints, struct addrinfo **res));
+DECL_WINDOWS_FUNCTION(static, void, freeaddrinfo, (struct addrinfo *res));
+DECL_WINDOWS_FUNCTION(static, int, getnameinfo,
+ (const struct sockaddr FAR * sa, socklen_t salen,
+ char FAR * host, size_t hostlen, char FAR * serv,
+ size_t servlen, int flags));
+DECL_WINDOWS_FUNCTION(static, char *, gai_strerror, (int ecode));
+DECL_WINDOWS_FUNCTION(static, int, WSAAddressToStringA,
+ (LPSOCKADDR, DWORD, LPWSAPROTOCOL_INFO,
+ LPSTR, LPDWORD));
+#endif
+
+static HMODULE winsock_module = NULL;
+static WSADATA wsadata;
+#ifndef NO_IPV6
+static HMODULE winsock2_module = NULL;
+static HMODULE wship6_module = NULL;
+#endif
+
+int sk_startup(int hi, int lo)
+{
+ WORD winsock_ver;
+
+ winsock_ver = MAKEWORD(hi, lo);
+
+ if (p_WSAStartup(winsock_ver, &wsadata)) {
+ return FALSE;
+ }
+
+ if (LOBYTE(wsadata.wVersion) != LOBYTE(winsock_ver)) {
+ return FALSE;
+ }
+
+#ifdef NET_SETUP_DIAGNOSTICS
+ {
+ char buf[80];
+ sprintf(buf, "Using WinSock %d.%d", hi, lo);
+ logevent(NULL, buf);
+ }
+#endif
+ return TRUE;
+}
+
+void sk_init(void)
+{
+#ifndef NO_IPV6
+ winsock2_module =
+#endif
+ winsock_module = load_system32_dll("ws2_32.dll");
+ if (!winsock_module) {
+ winsock_module = load_system32_dll("wsock32.dll");
+ }
+ if (!winsock_module)
+ fatalbox("Unable to load any WinSock library");
+
+#ifndef NO_IPV6
+ /* Check if we have getaddrinfo in Winsock */
+ if (GetProcAddress(winsock_module, "getaddrinfo") != NULL) {
+#ifdef NET_SETUP_DIAGNOSTICS
+ logevent(NULL, "Native WinSock IPv6 support detected");
+#endif
+ GET_WINDOWS_FUNCTION(winsock_module, getaddrinfo);
+ GET_WINDOWS_FUNCTION(winsock_module, freeaddrinfo);
+ GET_WINDOWS_FUNCTION(winsock_module, getnameinfo);
+ GET_WINDOWS_FUNCTION(winsock_module, gai_strerror);
+ } else {
+ /* Fall back to wship6.dll for Windows 2000 */
+ wship6_module = load_system32_dll("wship6.dll");
+ if (wship6_module) {
+#ifdef NET_SETUP_DIAGNOSTICS
+ logevent(NULL, "WSH IPv6 support detected");
+#endif
+ GET_WINDOWS_FUNCTION(wship6_module, getaddrinfo);
+ GET_WINDOWS_FUNCTION(wship6_module, freeaddrinfo);
+ GET_WINDOWS_FUNCTION(wship6_module, getnameinfo);
+ GET_WINDOWS_FUNCTION(wship6_module, gai_strerror);
+ } else {
+#ifdef NET_SETUP_DIAGNOSTICS
+ logevent(NULL, "No IPv6 support detected");
+#endif
+ }
+ }
+ GET_WINDOWS_FUNCTION(winsock2_module, WSAAddressToStringA);
+#else
+#ifdef NET_SETUP_DIAGNOSTICS
+ logevent(NULL, "PuTTY was built without IPv6 support");
+#endif
+#endif
+
+ GET_WINDOWS_FUNCTION(winsock_module, WSAAsyncSelect);
+ GET_WINDOWS_FUNCTION(winsock_module, WSAEventSelect);
+ GET_WINDOWS_FUNCTION(winsock_module, select);
+ GET_WINDOWS_FUNCTION(winsock_module, WSAGetLastError);
+ GET_WINDOWS_FUNCTION(winsock_module, WSAEnumNetworkEvents);
+ GET_WINDOWS_FUNCTION(winsock_module, WSAStartup);
+ GET_WINDOWS_FUNCTION(winsock_module, WSACleanup);
+ GET_WINDOWS_FUNCTION(winsock_module, closesocket);
+ GET_WINDOWS_FUNCTION(winsock_module, ntohl);
+ GET_WINDOWS_FUNCTION(winsock_module, htonl);
+ GET_WINDOWS_FUNCTION(winsock_module, htons);
+ GET_WINDOWS_FUNCTION(winsock_module, ntohs);
+ GET_WINDOWS_FUNCTION(winsock_module, gethostname);
+ GET_WINDOWS_FUNCTION(winsock_module, gethostbyname);
+ GET_WINDOWS_FUNCTION(winsock_module, getservbyname);
+ GET_WINDOWS_FUNCTION(winsock_module, inet_addr);
+ GET_WINDOWS_FUNCTION(winsock_module, inet_ntoa);
+ GET_WINDOWS_FUNCTION(winsock_module, connect);
+ 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, ioctlsocket);
+ GET_WINDOWS_FUNCTION(winsock_module, accept);
+ GET_WINDOWS_FUNCTION(winsock_module, recv);
+ GET_WINDOWS_FUNCTION(winsock_module, WSAIoctl);
+
+ /* Try to get the best WinSock version we can get */
+ if (!sk_startup(2,2) &&
+ !sk_startup(2,0) &&
+ !sk_startup(1,1)) {
+ fatalbox("Unable to initialise WinSock");
+ }
+
+ sktree = newtree234(cmpfortree);
+}
+
+void sk_cleanup(void)
+{
+ Actual_Socket s;
+ int i;
+
+ if (sktree) {
+ for (i = 0; (s = index234(sktree, i)) != NULL; i++) {
+ p_closesocket(s->s);
+ }
+ freetree234(sktree);
+ sktree = NULL;
+ }
+
+ if (p_WSACleanup)
+ p_WSACleanup();
+ if (winsock_module)
+ FreeLibrary(winsock_module);
+#ifndef NO_IPV6
+ if (wship6_module)
+ FreeLibrary(wship6_module);
+#endif
+}
+
+char *winsock_error_string(int error)
+{
+ switch (error) {
+ case WSAEACCES:
+ return "Network error: Permission denied";
+ case WSAEADDRINUSE:
+ return "Network error: Address already in use";
+ case WSAEADDRNOTAVAIL:
+ return "Network error: Cannot assign requested address";
+ case WSAEAFNOSUPPORT:
+ return
+ "Network error: Address family not supported by protocol family";
+ case WSAEALREADY:
+ return "Network error: Operation already in progress";
+ case WSAECONNABORTED:
+ return "Network error: Software caused connection abort";
+ case WSAECONNREFUSED:
+ return "Network error: Connection refused";
+ case WSAECONNRESET:
+ return "Network error: Connection reset by peer";
+ case WSAEDESTADDRREQ:
+ return "Network error: Destination address required";
+ case WSAEFAULT:
+ return "Network error: Bad address";
+ case WSAEHOSTDOWN:
+ return "Network error: Host is down";
+ case WSAEHOSTUNREACH:
+ return "Network error: No route to host";
+ case WSAEINPROGRESS:
+ return "Network error: Operation now in progress";
+ case WSAEINTR:
+ return "Network error: Interrupted function call";
+ case WSAEINVAL:
+ return "Network error: Invalid argument";
+ case WSAEISCONN:
+ return "Network error: Socket is already connected";
+ case WSAEMFILE:
+ return "Network error: Too many open files";
+ case WSAEMSGSIZE:
+ return "Network error: Message too long";
+ case WSAENETDOWN:
+ return "Network error: Network is down";
+ case WSAENETRESET:
+ return "Network error: Network dropped connection on reset";
+ case WSAENETUNREACH:
+ return "Network error: Network is unreachable";
+ case WSAENOBUFS:
+ return "Network error: No buffer space available";
+ case WSAENOPROTOOPT:
+ return "Network error: Bad protocol option";
+ case WSAENOTCONN:
+ return "Network error: Socket is not connected";
+ case WSAENOTSOCK:
+ return "Network error: Socket operation on non-socket";
+ case WSAEOPNOTSUPP:
+ return "Network error: Operation not supported";
+ case WSAEPFNOSUPPORT:
+ return "Network error: Protocol family not supported";
+ case WSAEPROCLIM:
+ return "Network error: Too many processes";
+ case WSAEPROTONOSUPPORT:
+ return "Network error: Protocol not supported";
+ case WSAEPROTOTYPE:
+ return "Network error: Protocol wrong type for socket";
+ case WSAESHUTDOWN:
+ return "Network error: Cannot send after socket shutdown";
+ case WSAESOCKTNOSUPPORT:
+ return "Network error: Socket type not supported";
+ case WSAETIMEDOUT:
+ 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";
+ }
+}
+
+SockAddr sk_namelookup(const char *host, char **canonicalname,
+ int address_family)
+{
+ SockAddr ret = snew(struct SockAddr_tag);
+ unsigned long a;
+ char realhost[8192];
+ int hint_family;
+
+ /* Default to IPv4. */
+ hint_family = (address_family == ADDRTYPE_IPV4 ? AF_INET :
+#ifndef NO_IPV6
+ address_family == ADDRTYPE_IPV6 ? AF_INET6 :
+#endif
+ AF_UNSPEC);
+
+ /* Clear the structure and default to IPv4. */
+ memset(ret, 0, sizeof(struct SockAddr_tag));
+#ifndef NO_IPV6
+ ret->ais = NULL;
+#endif
+ ret->addresses = NULL;
+ ret->resolved = FALSE;
+ ret->refcount = 1;
+ *realhost = '\0';
+
+ if ((a = p_inet_addr(host)) == (unsigned long) INADDR_NONE) {
+ struct hostent *h = NULL;
+ int err;
+#ifndef NO_IPV6
+ /*
+ * Use getaddrinfo when it's available
+ */
+ if (p_getaddrinfo) {
+ struct addrinfo hints;
+#ifdef NET_SETUP_DIAGNOSTICS
+ logevent(NULL, "Using getaddrinfo() for resolving");
+#endif
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = hint_family;
+ hints.ai_flags = AI_CANONNAME;
+ if ((err = p_getaddrinfo(host, NULL, &hints, &ret->ais)) == 0)
+ ret->resolved = TRUE;
+ } else
+#endif
+ {
+#ifdef NET_SETUP_DIAGNOSTICS
+ logevent(NULL, "Using gethostbyname() for resolving");
+#endif
+ /*
+ * Otherwise use the IPv4-only gethostbyname...
+ * (NOTE: we don't use gethostbyname as a fallback!)
+ */
+ if ( (h = p_gethostbyname(host)) )
+ ret->resolved = TRUE;
+ else
+ err = p_WSAGetLastError();
+ }
+
+ if (!ret->resolved) {
+ ret->error = (err == WSAENETDOWN ? "Network is down" :
+ err == WSAHOST_NOT_FOUND ? "Host does not exist" :
+ err == WSATRY_AGAIN ? "Host not found" :
+#ifndef NO_IPV6
+ p_getaddrinfo&&p_gai_strerror ? p_gai_strerror(err) :
+#endif
+ "gethostbyname: unknown error");
+ } else {
+ ret->error = NULL;
+
+#ifndef NO_IPV6
+ /* If we got an address info use that... */
+ if (ret->ais) {
+ /* Are we in IPv4 fallback mode? */
+ /* We put the IPv4 address into the a variable so we can further-on use the IPv4 code... */
+ if (ret->ais->ai_family == AF_INET)
+ memcpy(&a,
+ (char *) &((SOCKADDR_IN *) ret->ais->
+ ai_addr)->sin_addr, sizeof(a));
+
+ if (ret->ais->ai_canonname)
+ strncpy(realhost, ret->ais->ai_canonname, lenof(realhost));
+ else
+ strncpy(realhost, host, lenof(realhost));
+ }
+ /* We used the IPv4-only gethostbyname()... */
+ else
+#endif
+ {
+ int n;
+ for (n = 0; h->h_addr_list[n]; n++);
+ ret->addresses = snewn(n, unsigned long);
+ ret->naddresses = n;
+ for (n = 0; n < ret->naddresses; n++) {
+ memcpy(&a, h->h_addr_list[n], sizeof(a));
+ ret->addresses[n] = p_ntohl(a);
+ }
+ memcpy(&a, h->h_addr, sizeof(a));
+ /* This way we are always sure the h->h_name is valid :) */
+ strncpy(realhost, h->h_name, sizeof(realhost));
+ }
+ }
+ } else {
+ /*
+ * This must be a numeric IPv4 address because it caused a
+ * success return from inet_addr.
+ */
+ ret->addresses = snewn(1, unsigned long);
+ ret->naddresses = 1;
+ ret->addresses[0] = p_ntohl(a);
+ ret->resolved = TRUE;
+ strncpy(realhost, host, sizeof(realhost));
+ }
+ realhost[lenof(realhost)-1] = '\0';
+ *canonicalname = snewn(1+strlen(realhost), char);
+ strcpy(*canonicalname, realhost);
+ return ret;
+}
+
+SockAddr sk_nonamelookup(const char *host)
+{
+ SockAddr ret = snew(struct SockAddr_tag);
+ ret->error = NULL;
+ ret->resolved = FALSE;
+#ifndef NO_IPV6
+ ret->ais = NULL;
+#endif
+ 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;
+}
+
+int sk_nextaddr(SockAddr addr, SockAddrStep *step)
+{
+#ifndef NO_IPV6
+ if (step->ai) {
+ if (step->ai->ai_next) {
+ step->ai = step->ai->ai_next;
+ return TRUE;
+ } else
+ return FALSE;
+ }
+#endif
+ if (step->curraddr+1 < addr->naddresses) {
+ step->curraddr++;
+ return TRUE;
+ } else {
+ return FALSE;
+ }
+}
+
+void sk_getaddr(SockAddr addr, char *buf, int buflen)
+{
+ SockAddrStep step;
+ START_STEP(addr, step);
+
+#ifndef NO_IPV6
+ if (step.ai) {
+ int err = 0;
+ if (p_WSAAddressToStringA) {
+ DWORD dwbuflen = buflen;
+ err = p_WSAAddressToStringA(step.ai->ai_addr, step.ai->ai_addrlen,
+ NULL, buf, &dwbuflen);
+ } else
+ err = -1;
+ if (err) {
+ strncpy(buf, addr->hostname, buflen);
+ if (!buf[0])
+ strncpy(buf, "<unknown>", buflen);
+ buf[buflen-1] = '\0';
+ }
+ } else
+#endif
+ if (SOCKADDR_FAMILY(addr, step) == AF_INET) {
+ struct in_addr a;
+ assert(addr->addresses && step.curraddr < addr->naddresses);
+ a.s_addr = p_htonl(addr->addresses[step.curraddr]);
+ strncpy(buf, p_inet_ntoa(a), buflen);
+ buf[buflen-1] = '\0';
+ } else {
+ strncpy(buf, addr->hostname, buflen);
+ buf[buflen-1] = '\0';
+ }
+}
+
+int sk_hostname_is_local(char *name)
+{
+ return !strcmp(name, "localhost") ||
+ !strcmp(name, "::1") ||
+ !strncmp(name, "127.", 4);
+}
+
+static INTERFACE_INFO local_interfaces[16];
+static int n_local_interfaces; /* 0=not yet, -1=failed, >0=number */
+
+static int ipv4_is_local_addr(struct in_addr addr)
+{
+ if (ipv4_is_loopback(addr))
+ return 1; /* loopback addresses are local */
+ if (!n_local_interfaces) {
+ SOCKET s = p_socket(AF_INET, SOCK_DGRAM, 0);
+ DWORD retbytes;
+
+ if (p_WSAIoctl &&
+ p_WSAIoctl(s, SIO_GET_INTERFACE_LIST, NULL, 0,
+ local_interfaces, sizeof(local_interfaces),
+ &retbytes, NULL, NULL) == 0)
+ n_local_interfaces = retbytes / sizeof(INTERFACE_INFO);
+ else
+ logevent(NULL, "Unable to get list of local IP addresses");
+ }
+ if (n_local_interfaces > 0) {
+ int i;
+ for (i = 0; i < n_local_interfaces; i++) {
+ SOCKADDR_IN *address =
+ (SOCKADDR_IN *)&local_interfaces[i].iiAddress;
+ if (address->sin_addr.s_addr == addr.s_addr)
+ return 1; /* this address is local */
+ }
+ }
+ return 0; /* this address is not local */
+}
+
+int sk_address_is_local(SockAddr addr)
+{
+ SockAddrStep step;
+ int family;
+ 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);
+ } else
+#endif
+ if (family == AF_INET) {
+#ifndef NO_IPV6
+ if (step.ai) {
+ return ipv4_is_local_addr(((struct sockaddr_in *)step.ai->ai_addr)
+ ->sin_addr);
+ } else
+#endif
+ {
+ struct in_addr a;
+ assert(addr->addresses && step.curraddr < addr->naddresses);
+ a.s_addr = p_htonl(addr->addresses[step.curraddr]);
+ return ipv4_is_local_addr(a);
+ }
+ } else {
+ assert(family == AF_UNSPEC);
+ return 0; /* we don't know; assume not */
+ }
+}
+
+int sk_addrtype(SockAddr addr)
+{
+ SockAddrStep step;
+ int family;
+ START_STEP(addr, step);
+ family = SOCKADDR_FAMILY(addr, step);
+
+ return (family == AF_INET ? ADDRTYPE_IPV4 :
+#ifndef NO_IPV6
+ family == AF_INET6 ? ADDRTYPE_IPV6 :
+#endif
+ ADDRTYPE_NAME);
+}
+
+void sk_addrcopy(SockAddr addr, char *buf)
+{
+ SockAddrStep step;
+ int family;
+ START_STEP(addr, step);
+ family = SOCKADDR_FAMILY(addr, step);
+
+ assert(family != AF_UNSPEC);
+#ifndef NO_IPV6
+ if (step.ai) {
+ if (family == AF_INET)
+ memcpy(buf, &((struct sockaddr_in *)step.ai->ai_addr)->sin_addr,
+ sizeof(struct in_addr));
+ else if (family == AF_INET6)
+ memcpy(buf, &((struct sockaddr_in6 *)step.ai->ai_addr)->sin6_addr,
+ sizeof(struct in6_addr));
+ else
+ assert(FALSE);
+ } else
+#endif
+ if (family == AF_INET) {
+ struct in_addr a;
+ assert(addr->addresses && step.curraddr < addr->naddresses);
+ a.s_addr = p_htonl(addr->addresses[step.curraddr]);
+ memcpy(buf, (char*) &a.s_addr, 4);
+ }
+}
+
+void sk_addr_free(SockAddr addr)
+{
+ if (--addr->refcount > 0)
+ return;
+#ifndef NO_IPV6
+ if (addr->ais && p_freeaddrinfo)
+ p_freeaddrinfo(addr->ais);
+#endif
+ if (addr->addresses)
+ sfree(addr->addresses);
+ sfree(addr);
+}
+
+SockAddr sk_addr_dup(SockAddr addr)
+{
+ addr->refcount++;
+ return addr;
+}
+
+static Plug sk_tcp_plug(Socket sock, Plug p)
+{
+ Actual_Socket s = (Actual_Socket) sock;
+ Plug ret = s->plug;
+ if (p)
+ s->plug = p;
+ return ret;
+}
+
+static void sk_tcp_flush(Socket s)
+{
+ /*
+ * We send data to the socket as soon as we can anyway,
+ * so we don't need to do anything here. :-)
+ */
+}
+
+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_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 const struct socket_function_table fn_table = {
+ sk_tcp_plug,
+ sk_tcp_close,
+ sk_tcp_write,
+ sk_tcp_write_oob,
+ sk_tcp_flush,
+ sk_tcp_set_private_ptr,
+ sk_tcp_get_private_ptr,
+ sk_tcp_set_frozen,
+ sk_tcp_socket_error
+ };
+
+ DWORD err;
+ char *errstr;
+ Actual_Socket ret;
+
+ /*
+ * Create Socket structure.
+ */
+ ret = snew(struct Socket_tag);
+ ret->fn = &fn_table;
+ ret->error = NULL;
+ 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->parent = ret->child = NULL;
+ ret->addr = NULL;
+
+ ret->s = (SOCKET)sock;
+
+ if (ret->s == INVALID_SOCKET) {
+ err = p_WSAGetLastError();
+ ret->error = winsock_error_string(err);
+ return (Socket) ret;
+ }
+
+ ret->oobinline = 0;
+
+ /* Set up a select mechanism. This could be an AsyncSelect on a
+ * window, or an EventSelect on an event object. */
+ errstr = do_select(ret->s, 1);
+ if (errstr) {
+ ret->error = errstr;
+ return (Socket) ret;
+ }
+
+ add234(sktree, ret);
+
+ return (Socket) ret;
+}
+
+static DWORD try_connect(Actual_Socket sock)
+{
+ SOCKET s;
+#ifndef NO_IPV6
+ SOCKADDR_IN6 a6;
+#endif
+ SOCKADDR_IN a;
+ DWORD err;
+ char *errstr;
+ short localport;
+ int family;
+
+ if (sock->s != INVALID_SOCKET) {
+ do_select(sock->s, 0);
+ p_closesocket(sock->s);
+ }
+
+ plug_log(sock->plug, 0, sock->addr, sock->port, NULL, 0);
+
+ /*
+ * Open socket.
+ */
+ family = SOCKADDR_FAMILY(sock->addr, sock->step);
+
+ /*
+ * 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
+ * function, whether we changed anything or not.
+ */
+ del234(sktree, sock);
+
+ s = p_socket(family, SOCK_STREAM, 0);
+ sock->s = s;
+
+ if (s == INVALID_SOCKET) {
+ err = p_WSAGetLastError();
+ sock->error = winsock_error_string(err);
+ goto ret;
+ }
+
+ if (sock->oobinline) {
+ BOOL b = TRUE;
+ p_setsockopt(s, SOL_SOCKET, SO_OOBINLINE, (void *) &b, sizeof(b));
+ }
+
+ if (sock->nodelay) {
+ BOOL b = TRUE;
+ p_setsockopt(s, IPPROTO_TCP, TCP_NODELAY, (void *) &b, sizeof(b));
+ }
+
+ if (sock->keepalive) {
+ BOOL b = TRUE;
+ p_setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, (void *) &b, sizeof(b));
+ }
+
+ /*
+ * Bind to local address.
+ */
+ if (sock->privport)
+ localport = 1023; /* count from 1023 downwards */
+ else
+ localport = 0; /* just use port 0 (ie winsock picks) */
+
+ /* Loop round trying to bind */
+ while (1) {
+ int sockcode;
+
+#ifndef NO_IPV6
+ if (family == AF_INET6) {
+ memset(&a6, 0, sizeof(a6));
+ a6.sin6_family = AF_INET6;
+ /*a6.sin6_addr = in6addr_any; */ /* == 0 done by memset() */
+ a6.sin6_port = p_htons(localport);
+ } else
+#endif
+ {
+ a.sin_family = AF_INET;
+ a.sin_addr.s_addr = p_htonl(INADDR_ANY);
+ a.sin_port = p_htons(localport);
+ }
+#ifndef NO_IPV6
+ sockcode = p_bind(s, (family == AF_INET6 ?
+ (struct sockaddr *) &a6 :
+ (struct sockaddr *) &a),
+ (family == AF_INET6 ? sizeof(a6) : sizeof(a)));
+#else
+ sockcode = p_bind(s, (struct sockaddr *) &a, sizeof(a));
+#endif
+ if (sockcode != SOCKET_ERROR) {
+ err = 0;
+ break; /* done */
+ } else {
+ err = p_WSAGetLastError();
+ if (err != WSAEADDRINUSE) /* failed, for a bad reason */
+ break;
+ }
+
+ if (localport == 0)
+ break; /* we're only looping once */
+ localport--;
+ if (localport == 0)
+ break; /* we might have got to the end */
+ }
+
+ if (err) {
+ sock->error = winsock_error_string(err);
+ goto ret;
+ }
+
+ /*
+ * Connect to remote address.
+ */
+#ifndef NO_IPV6
+ if (sock->step.ai) {
+ if (family == AF_INET6) {
+ a6.sin6_family = AF_INET6;
+ a6.sin6_port = p_htons((short) sock->port);
+ a6.sin6_addr =
+ ((struct sockaddr_in6 *) sock->step.ai->ai_addr)->sin6_addr;
+ a6.sin6_flowinfo = ((struct sockaddr_in6 *) sock->step.ai->ai_addr)->sin6_flowinfo;
+ a6.sin6_scope_id = ((struct sockaddr_in6 *) sock->step.ai->ai_addr)->sin6_scope_id;
+ } else {
+ a.sin_family = AF_INET;
+ a.sin_addr =
+ ((struct sockaddr_in *) sock->step.ai->ai_addr)->sin_addr;
+ a.sin_port = p_htons((short) sock->port);
+ }
+ } else
+#endif
+ {
+ assert(sock->addr->addresses && sock->step.curraddr < sock->addr->naddresses);
+ a.sin_family = AF_INET;
+ a.sin_addr.s_addr = p_htonl(sock->addr->addresses[sock->step.curraddr]);
+ a.sin_port = p_htons((short) sock->port);
+ }
+
+ /* Set up a select mechanism. This could be an AsyncSelect on a
+ * window, or an EventSelect on an event object. */
+ errstr = do_select(s, 1);
+ if (errstr) {
+ sock->error = errstr;
+ err = 1;
+ goto ret;
+ }
+
+ if ((
+#ifndef NO_IPV6
+ p_connect(s,
+ ((family == AF_INET6) ? (struct sockaddr *) &a6 :
+ (struct sockaddr *) &a),
+ (family == AF_INET6) ? sizeof(a6) : sizeof(a))
+#else
+ p_connect(s, (struct sockaddr *) &a, sizeof(a))
+#endif
+ ) == SOCKET_ERROR) {
+ err = p_WSAGetLastError();
+ /*
+ * We expect a potential EWOULDBLOCK here, because the
+ * chances are the front end has done a select for
+ * FD_CONNECT, so that connect() will complete
+ * asynchronously.
+ */
+ if ( err != WSAEWOULDBLOCK ) {
+ sock->error = winsock_error_string(err);
+ goto ret;
+ }
+ } else {
+ /*
+ * If we _don't_ get EWOULDBLOCK, the connect has completed
+ * and we should set the socket as writable.
+ */
+ sock->writable = 1;
+ }
+
+ err = 0;
+
+ ret:
+
+ /*
+ * No matter what happened, put the socket back in the tree.
+ */
+ add234(sktree, sock);
+
+ if (err)
+ plug_log(sock->plug, 1, sock->addr, sock->port, sock->error, err);
+ return err;
+}
+
+Socket sk_new(SockAddr addr, int port, int privport, int oobinline,
+ int nodelay, int keepalive, 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_flush,
+ sk_tcp_set_private_ptr,
+ sk_tcp_get_private_ptr,
+ sk_tcp_set_frozen,
+ sk_tcp_socket_error
+ };
+
+ Actual_Socket ret;
+ DWORD err;
+
+ /*
+ * Create Socket structure.
+ */
+ ret = snew(struct Socket_tag);
+ ret->fn = &fn_table;
+ ret->error = NULL;
+ 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->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->oobinline = oobinline;
+ ret->nodelay = nodelay;
+ ret->keepalive = keepalive;
+ ret->privport = privport;
+ ret->port = port;
+ ret->addr = addr;
+ START_STEP(ret->addr, ret->step);
+ ret->s = INVALID_SOCKET;
+
+ err = 0;
+ do {
+ err = try_connect(ret);
+ } while (err && sk_nextaddr(ret->addr, &ret->step));
+
+ return (Socket) ret;
+}
+
+Socket sk_newlistener(char *srcaddr, int port, Plug plug, int local_host_only,
+ int orig_address_family)
+{
+ static const struct socket_function_table fn_table = {
+ sk_tcp_plug,
+ sk_tcp_close,
+ sk_tcp_write,
+ sk_tcp_write_oob,
+ sk_tcp_flush,
+ sk_tcp_set_private_ptr,
+ sk_tcp_get_private_ptr,
+ sk_tcp_set_frozen,
+ sk_tcp_socket_error
+ };
+
+ SOCKET s;
+#ifndef NO_IPV6
+ SOCKADDR_IN6 a6;
+#endif
+ SOCKADDR_IN a;
+
+ DWORD err;
+ char *errstr;
+ Actual_Socket ret;
+ int retcode;
+ int on = 1;
+
+ int address_family;
+
+ /*
+ * Create Socket structure.
+ */
+ ret = snew(struct Socket_tag);
+ ret->fn = &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->frozen_readable = 0;
+ ret->localhost_only = local_host_only;
+ ret->pending_error = 0;
+ ret->parent = ret->child = NULL;
+ ret->addr = NULL;
+
+ /*
+ * Translate address_family from platform-independent constants
+ * into local reality.
+ */
+ address_family = (orig_address_family == ADDRTYPE_IPV4 ? AF_INET :
+#ifndef NO_IPV6
+ orig_address_family == ADDRTYPE_IPV6 ? AF_INET6 :
+#endif
+ AF_UNSPEC);
+
+ /*
+ * Our default, if passed the `don't care' value
+ * ADDRTYPE_UNSPEC, is to listen on IPv4. If IPv6 is supported,
+ * we will also set up a second socket listening on IPv6, but
+ * the v4 one is primary since that ought to work even on
+ * non-v6-supporting systems.
+ */
+ if (address_family == AF_UNSPEC) address_family = AF_INET;
+
+ /*
+ * Open socket.
+ */
+ s = p_socket(address_family, SOCK_STREAM, 0);
+ ret->s = s;
+
+ if (s == INVALID_SOCKET) {
+ err = p_WSAGetLastError();
+ ret->error = winsock_error_string(err);
+ return (Socket) ret;
+ }
+
+ ret->oobinline = 0;
+
+ p_setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (const char *)&on, sizeof(on));
+
+#ifndef NO_IPV6
+ if (address_family == AF_INET6) {
+ memset(&a6, 0, sizeof(a6));
+ a6.sin6_family = AF_INET6;
+ /* FIXME: srcaddr is ignored for IPv6, because I (SGT) don't
+ * know how to do it. :-)
+ * (jeroen:) saddr is specified as an address.. eg 2001:db8::1
+ * Thus we need either a parser that understands [2001:db8::1]:80
+ * style addresses and/or enhance this to understand hostnames too. */
+ if (local_host_only)
+ a6.sin6_addr = in6addr_loopback;
+ else
+ a6.sin6_addr = in6addr_any;
+ a6.sin6_port = p_htons(port);
+ } else
+#endif
+ {
+ int got_addr = 0;
+ a.sin_family = AF_INET;
+
+ /*
+ * Bind to source address. First try an explicitly
+ * specified one...
+ */
+ if (srcaddr) {
+ a.sin_addr.s_addr = p_inet_addr(srcaddr);
+ if (a.sin_addr.s_addr != INADDR_NONE) {
+ /* Override localhost_only with specified listen addr. */
+ ret->localhost_only = ipv4_is_loopback(a.sin_addr);
+ got_addr = 1;
+ }
+ }
+
+ /*
+ * ... and failing that, go with one of the standard ones.
+ */
+ if (!got_addr) {
+ if (local_host_only)
+ a.sin_addr.s_addr = p_htonl(INADDR_LOOPBACK);
+ else
+ a.sin_addr.s_addr = p_htonl(INADDR_ANY);
+ }
+
+ a.sin_port = p_htons((short)port);
+ }
+#ifndef NO_IPV6
+ retcode = p_bind(s, (address_family == AF_INET6 ?
+ (struct sockaddr *) &a6 :
+ (struct sockaddr *) &a),
+ (address_family ==
+ AF_INET6 ? sizeof(a6) : sizeof(a)));
+#else
+ retcode = p_bind(s, (struct sockaddr *) &a, sizeof(a));
+#endif
+ if (retcode != SOCKET_ERROR) {
+ err = 0;
+ } else {
+ err = p_WSAGetLastError();
+ }
+
+ if (err) {
+ p_closesocket(s);
+ ret->error = winsock_error_string(err);
+ return (Socket) ret;
+ }
+
+
+ if (p_listen(s, SOMAXCONN) == SOCKET_ERROR) {
+ p_closesocket(s);
+ ret->error = winsock_error_string(err);
+ return (Socket) ret;
+ }
+
+ /* Set up a select mechanism. This could be an AsyncSelect on a
+ * window, or an EventSelect on an event object. */
+ errstr = do_select(s, 1);
+ if (errstr) {
+ p_closesocket(s);
+ ret->error = errstr;
+ return (Socket) ret;
+ }
+
+ add234(sktree, ret);
+
+#ifndef NO_IPV6
+ /*
+ * If we were given ADDRTYPE_UNSPEC, we must also create an
+ * IPv6 listening socket and link it to this one.
+ */
+ if (address_family == AF_INET && orig_address_family == ADDRTYPE_UNSPEC) {
+ Actual_Socket other;
+
+ other = (Actual_Socket) sk_newlistener(srcaddr, port, plug,
+ local_host_only, ADDRTYPE_IPV6);
+
+ if (other) {
+ if (!other->error) {
+ other->parent = ret;
+ ret->child = other;
+ } else {
+ sfree(other);
+ }
+ }
+ }
+#endif
+
+ return (Socket) ret;
+}
+
+static void sk_tcp_close(Socket sock)
+{
+ extern char *do_select(SOCKET skt, int startup);
+ Actual_Socket s = (Actual_Socket) sock;
+
+ if (s->child)
+ sk_tcp_close((Socket)s->child);
+
+ del234(sktree, s);
+ do_select(s->s, 0);
+ p_closesocket(s->s);
+ if (s->addr)
+ sk_addr_free(s->addr);
+ sfree(s);
+}
+
+/*
+ * The function which tries to send on a socket once it's deemed
+ * writable.
+ */
+void try_send(Actual_Socket s)
+{
+ while (s->sending_oob || bufchain_size(&s->output_data) > 0) {
+ int nsent;
+ DWORD err;
+ void *data;
+ int len, urgentflag;
+
+ if (s->sending_oob) {
+ urgentflag = MSG_OOB;
+ len = s->sending_oob;
+ data = &s->oobdata;
+ } else {
+ urgentflag = 0;
+ bufchain_prefix(&s->output_data, &data, &len);
+ }
+ nsent = p_send(s->s, data, len, urgentflag);
+ noise_ultralight(nsent);
+ if (nsent <= 0) {
+ err = (nsent < 0 ? p_WSAGetLastError() : 0);
+ if ((err < WSABASEERR && nsent < 0) || err == WSAEWOULDBLOCK) {
+ /*
+ * Perfectly normal: we've sent all we can for the moment.
+ *
+ * (Some WinSock send() implementations can return
+ * <0 but leave no sensible error indication -
+ * WSAGetLastError() is called but returns zero or
+ * a small number - so we check that case and treat
+ * it just like WSAEWOULDBLOCK.)
+ */
+ s->writable = FALSE;
+ return;
+ } else if (nsent == 0 ||
+ err == WSAECONNABORTED || err == WSAECONNRESET) {
+ /*
+ * If send() returns CONNABORTED or CONNRESET, we
+ * unfortunately can't just call plug_closing(),
+ * because it's quite likely that we're currently
+ * _in_ a call from the code we'd be calling back
+ * to, so we'd have to make half the SSH code
+ * 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;
+ return;
+ } else {
+ /* We're inside the Windows frontend here, so we know
+ * that the frontend handle is unnecessary. */
+ logevent(NULL, winsock_error_string(err));
+ fatalbox("%s", winsock_error_string(err));
+ }
+ } else {
+ if (s->sending_oob) {
+ if (nsent < len) {
+ memmove(s->oobdata, s->oobdata+nsent, len-nsent);
+ s->sending_oob = len - nsent;
+ } else {
+ s->sending_oob = 0;
+ }
+ } else {
+ bufchain_consume(&s->output_data, nsent);
+ }
+ }
+ }
+}
+
+static int sk_tcp_write(Socket sock, const char *buf, int len)
+{
+ Actual_Socket s = (Actual_Socket) sock;
+
+ /*
+ * Add the data to the buffer list on the socket.
+ */
+ bufchain_add(&s->output_data, buf, len);
+
+ /*
+ * Now try sending from the start of the buffer list.
+ */
+ if (s->writable)
+ try_send(s);
+
+ return bufchain_size(&s->output_data);
+}
+
+static int sk_tcp_write_oob(Socket sock, const char *buf, int len)
+{
+ Actual_Socket s = (Actual_Socket) sock;
+
+ /*
+ * Replace the buffer list on the socket with the data.
+ */
+ bufchain_clear(&s->output_data);
+ assert(len <= sizeof(s->oobdata));
+ memcpy(s->oobdata, buf, len);
+ s->sending_oob = len;
+
+ /*
+ * Now try sending from the start of the buffer list.
+ */
+ if (s->writable)
+ try_send(s);
+
+ return s->sending_oob;
+}
+
+int select_result(WPARAM wParam, LPARAM lParam)
+{
+ int ret, open;
+ DWORD err;
+ char buf[20480]; /* nice big buffer for plenty of speed */
+ Actual_Socket s;
+ u_long atmark;
+
+ /* wParam is the socket itself */
+
+ if (wParam == 0)
+ return 1; /* boggle */
+
+ s = find234(sktree, (void *) wParam, cmpforsearch);
+ if (!s)
+ return 1; /* boggle */
+
+ if ((err = WSAGETSELECTERROR(lParam)) != 0) {
+ /*
+ * An error has occurred on this socket. Pass it to the
+ * plug.
+ */
+ if (s->addr) {
+ plug_log(s->plug, 1, s->addr, s->port,
+ winsock_error_string(err), err);
+ while (s->addr && sk_nextaddr(s->addr, &s->step)) {
+ err = try_connect(s);
+ }
+ }
+ if (err != 0)
+ return plug_closing(s->plug, winsock_error_string(err), err, 0);
+ else
+ return 1;
+ }
+
+ noise_ultralight(lParam);
+
+ switch (WSAGETSELECTEVENT(lParam)) {
+ case FD_CONNECT:
+ s->connected = s->writable = 1;
+ /*
+ * Once a socket is connected, we can stop falling
+ * back through the candidate addresses to connect
+ * to.
+ */
+ if (s->addr) {
+ sk_addr_free(s->addr);
+ s->addr = NULL;
+ }
+ break;
+ case FD_READ:
+ /* In the case the socket is still frozen, we don't even bother */
+ if (s->frozen) {
+ s->frozen_readable = 1;
+ 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
+ * (data prior to urgent).
+ */
+ if (s->oobinline) {
+ atmark = 1;
+ p_ioctlsocket(s->s, SIOCATMARK, &atmark);
+ /*
+ * Avoid checking the return value from ioctlsocket(),
+ * on the grounds that some WinSock wrappers don't
+ * support it. If it does nothing, we get atmark==1,
+ * which is equivalent to `no OOB pending', so the
+ * effect will be to non-OOB-ify any OOB data.
+ */
+ } else
+ atmark = 1;
+
+ ret = p_recv(s->s, buf, sizeof(buf), 0);
+ noise_ultralight(ret);
+ if (ret < 0) {
+ err = p_WSAGetLastError();
+ if (err == WSAEWOULDBLOCK) {
+ break;
+ }
+ }
+ if (ret < 0) {
+ return plug_closing(s->plug, winsock_error_string(err), err,
+ 0);
+ } else if (0 == ret) {
+ return plug_closing(s->plug, NULL, 0, 0);
+ } else {
+ return plug_receive(s->plug, atmark ? 0 : 1, buf, ret);
+ }
+ break;
+ case FD_OOB:
+ /*
+ * This will only happen on a non-oobinline socket. It
+ * indicates that we can immediately perform an OOB read
+ * and get back OOB data, which we will send to the back
+ * end with type==2 (urgent data).
+ */
+ ret = p_recv(s->s, buf, sizeof(buf), MSG_OOB);
+ noise_ultralight(ret);
+ if (ret <= 0) {
+ char *str = (ret == 0 ? "Internal networking trouble" :
+ winsock_error_string(p_WSAGetLastError()));
+ /* We're inside the Windows frontend here, so we know
+ * that the frontend handle is unnecessary. */
+ logevent(NULL, str);
+ fatalbox("%s", str);
+ } else {
+ return plug_receive(s->plug, 2, buf, ret);
+ }
+ break;
+ case FD_WRITE:
+ {
+ int bufsize_before, bufsize_after;
+ s->writable = 1;
+ bufsize_before = s->sending_oob + bufchain_size(&s->output_data);
+ try_send(s);
+ bufsize_after = s->sending_oob + bufchain_size(&s->output_data);
+ if (bufsize_after < bufsize_before)
+ plug_sent(s->plug, bufsize_after);
+ }
+ break;
+ case FD_CLOSE:
+ /* Signal a close on the socket. First read any outstanding data. */
+ open = 1;
+ do {
+ ret = p_recv(s->s, buf, sizeof(buf), 0);
+ if (ret < 0) {
+ err = p_WSAGetLastError();
+ if (err == WSAEWOULDBLOCK)
+ break;
+ return plug_closing(s->plug, winsock_error_string(err),
+ err, 0);
+ } else {
+ if (ret)
+ open &= plug_receive(s->plug, 0, buf, ret);
+ else
+ open &= plug_closing(s->plug, NULL, 0, 0);
+ }
+ } while (ret > 0);
+ return open;
+ case FD_ACCEPT:
+ {
+#ifdef NO_IPV6
+ struct sockaddr_in isa;
+#else
+ struct sockaddr_storage isa;
+#endif
+ int addrlen = sizeof(isa);
+ SOCKET t; /* socket of connection */
+
+ memset(&isa, 0, sizeof(isa));
+ err = 0;
+ t = p_accept(s->s,(struct sockaddr *)&isa,&addrlen);
+ if (t == INVALID_SOCKET)
+ {
+ err = p_WSAGetLastError();
+ if (err == WSATRY_AGAIN)
+ break;
+ }
+#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)) {
+ 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.
+ */
+const char *sk_addr_error(SockAddr addr)
+{
+ return addr->error;
+}
+static const char *sk_tcp_socket_error(Socket sock)
+{
+ Actual_Socket s = (Actual_Socket) sock;
+ return s->error;
+}
+
+static void sk_tcp_set_frozen(Socket sock, int is_frozen)
+{
+ Actual_Socket s = (Actual_Socket) sock;
+ if (s->frozen == is_frozen)
+ return;
+ s->frozen = is_frozen;
+ if (!is_frozen) {
+ do_select(s->s, 1);
+ if (s->frozen_readable) {
+ char c;
+ p_recv(s->s, &c, 1, MSG_PEEK);
+ }
+ }
+ s->frozen_readable = 0;
+}
+
+void socket_reselect_all(void)
+{
+ Actual_Socket s;
+ int i;
+
+ for (i = 0; (s = index234(sktree, i)) != NULL; i++) {
+ if (!s->frozen)
+ do_select(s->s, 1);
+ }
+}
+
+/*
+ * For Plink: enumerate all sockets currently active.
+ */
+SOCKET first_socket(int *state)
+{
+ Actual_Socket s;
+ *state = 0;
+ s = index234(sktree, (*state)++);
+ return s ? s->s : INVALID_SOCKET;
+}
+
+SOCKET next_socket(int *state)
+{
+ Actual_Socket s = index234(sktree, (*state)++);
+ return s ? s->s : INVALID_SOCKET;
+}
+
+extern int socket_writable(SOCKET skt)
+{
+ Actual_Socket s = find234(sktree, (void *)skt, cmpforsearch);
+
+ if (s)
+ return bufchain_size(&s->output_data) > 0;
+ else
+ return 0;
+}
+
+int net_service_lookup(char *service)
+{
+ struct servent *se;
+ se = p_getservbyname(service, NULL);
+ if (se != NULL)
+ return p_ntohs(se->s_port);
+ else
+ return 0;
+}
+
+char *get_hostname(void)
+{
+ int len = 128;
+ char *hostname = NULL;
+ do {
+ len *= 2;
+ hostname = sresize(hostname, len, char);
+ if (p_gethostname(hostname, len) < 0) {
+ sfree(hostname);
+ hostname = NULL;
+ break;
+ }
+ } while (strlen(hostname) >= (size_t)(len-1));
+ return hostname;
+}
+
+SockAddr platform_get_x11_unix_address(const char *display, int displaynum,
+ char **canonicalname)
+{
+ SockAddr ret = snew(struct SockAddr_tag);
+ memset(ret, 0, sizeof(struct SockAddr_tag));
+ ret->error = "unix sockets not supported on this platform";
+ ret->refcount = 1;
+ return ret;
+}
--- /dev/null
+/*
+ * Noise generation for PuTTY's cryptographic random number
+ * generator.
+ */
+
+#include <stdio.h>
+
+#include "putty.h"
+#include "ssh.h"
+#include "storage.h"
+
+/*
+ * 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.
+ */
+
+void noise_get_heavy(void (*func) (void *, int))
+{
+ HANDLE srch;
+ WIN32_FIND_DATA finddata;
+ DWORD pid;
+ char winpath[MAX_PATH + 3];
+
+ GetWindowsDirectory(winpath, sizeof(winpath));
+ strcat(winpath, "\\*");
+ srch = FindFirstFile(winpath, &finddata);
+ if (srch != INVALID_HANDLE_VALUE) {
+ do {
+ func(&finddata, sizeof(finddata));
+ } while (FindNextFile(srch, &finddata));
+ FindClose(srch);
+ }
+
+ pid = GetCurrentProcessId();
+ func(&pid, sizeof(pid));
+
+ read_random_seed(func);
+ /* Update the seed immediately, in case another instance uses it. */
+ random_save_seed();
+}
+
+void random_save_seed(void)
+{
+ int len;
+ void *data;
+
+ if (random_active) {
+ random_get_savedata(&data, &len);
+ write_random_seed(data, len);
+ sfree(data);
+ }
+}
+
+/*
+ * This function is called every time the random pool needs
+ * stirring, and will acquire the system time in all available
+ * forms.
+ */
+void noise_get_light(void (*func) (void *, int))
+{
+ SYSTEMTIME systime;
+ DWORD adjust[2];
+ BOOL rubbish;
+
+ GetSystemTime(&systime);
+ func(&systime, sizeof(systime));
+
+ GetSystemTimeAdjustment(&adjust[0], &adjust[1], &rubbish);
+ func(&adjust, sizeof(adjust));
+}
+
+/*
+ * This function is called on a timer, and it will monitor
+ * frequently changing quantities such as the state of physical and
+ * virtual memory, the state of the process's message queue, which
+ * window is in the foreground, which owns the clipboard, etc.
+ */
+void noise_regular(void)
+{
+ HWND w;
+ DWORD z;
+ POINT pt;
+ MEMORYSTATUS memstat;
+ FILETIME times[4];
+
+ w = GetForegroundWindow();
+ random_add_noise(&w, sizeof(w));
+ w = GetCapture();
+ random_add_noise(&w, sizeof(w));
+ w = GetClipboardOwner();
+ random_add_noise(&w, sizeof(w));
+ z = GetQueueStatus(QS_ALLEVENTS);
+ random_add_noise(&z, sizeof(z));
+
+ GetCursorPos(&pt);
+ random_add_noise(&pt, sizeof(pt));
+
+ GlobalMemoryStatus(&memstat);
+ random_add_noise(&memstat, sizeof(memstat));
+
+ GetThreadTimes(GetCurrentThread(), times, times + 1, times + 2,
+ times + 3);
+ random_add_noise(×, sizeof(times));
+ GetProcessTimes(GetCurrentProcess(), times, times + 1, times + 2,
+ times + 3);
+ random_add_noise(×, sizeof(times));
+}
+
+/*
+ * This function is called on every keypress or mouse move, and
+ * will add the current Windows time and performance monitor
+ * counter to the noise pool. It gets the scan code or mouse
+ * position passed in.
+ */
+void noise_ultralight(unsigned long data)
+{
+ DWORD wintime;
+ LARGE_INTEGER perftime;
+
+ random_add_noise(&data, sizeof(DWORD));
+
+ wintime = GetTickCount();
+ random_add_noise(&wintime, sizeof(DWORD));
+
+ if (QueryPerformanceCounter(&perftime))
+ random_add_noise(&perftime, sizeof(perftime));
+}
--- /dev/null
+/*
+ * winnojmp.c: stub jump list functions for Windows executables that
+ * don't update the jump list.
+ */
+
+void add_session_to_jumplist(const char * const sessionname) {}
+void remove_session_from_jumplist(const char * const sessionname) {}
+void clear_jumplist(void) {}
--- /dev/null
+/*
+ * PuTTY key generation front end (Windows).
+ */
+
+#include <time.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#define PUTTY_DO_GLOBALS
+
+#include "putty.h"
+#include "ssh.h"
+
+#include <commctrl.h>
+
+#ifdef MSVC4
+#define ICON_BIG 1
+#endif
+
+#define WM_DONEKEY (WM_APP + 1)
+
+#define DEFAULT_KEYSIZE 1024
+
+static char *cmdline_keyfile = NULL;
+
+/*
+ * Print a modal (Really Bad) message box and perform a fatal exit.
+ */
+void modalfatalbox(char *fmt, ...)
+{
+ va_list ap;
+ char *stuff;
+
+ va_start(ap, fmt);
+ stuff = dupvprintf(fmt, ap);
+ va_end(ap);
+ MessageBox(NULL, stuff, "PuTTYgen Fatal Error",
+ MB_SYSTEMMODAL | MB_ICONERROR | MB_OK);
+ sfree(stuff);
+ exit(1);
+}
+
+/* ----------------------------------------------------------------------
+ * Progress report code. This is really horrible :-)
+ */
+#define PROGRESSRANGE 65535
+#define MAXPHASE 5
+struct progress {
+ int nphases;
+ struct {
+ int exponential;
+ unsigned startpoint, total;
+ unsigned param, current, n; /* if exponential */
+ unsigned mult; /* if linear */
+ } phases[MAXPHASE];
+ unsigned total, divisor, range;
+ HWND progbar;
+};
+
+static void progress_update(void *param, int action, int phase, int iprogress)
+{
+ struct progress *p = (struct progress *) param;
+ unsigned progress = iprogress;
+ int position;
+
+ if (action < PROGFN_READY && p->nphases < phase)
+ p->nphases = phase;
+ switch (action) {
+ case PROGFN_INITIALISE:
+ p->nphases = 0;
+ break;
+ case PROGFN_LIN_PHASE:
+ p->phases[phase-1].exponential = 0;
+ p->phases[phase-1].mult = p->phases[phase].total / progress;
+ break;
+ case PROGFN_EXP_PHASE:
+ p->phases[phase-1].exponential = 1;
+ p->phases[phase-1].param = 0x10000 + progress;
+ p->phases[phase-1].current = p->phases[phase-1].total;
+ p->phases[phase-1].n = 0;
+ break;
+ case PROGFN_PHASE_EXTENT:
+ p->phases[phase-1].total = progress;
+ break;
+ case PROGFN_READY:
+ {
+ unsigned total = 0;
+ int i;
+ for (i = 0; i < p->nphases; i++) {
+ p->phases[i].startpoint = total;
+ total += p->phases[i].total;
+ }
+ p->total = total;
+ p->divisor = ((p->total + PROGRESSRANGE - 1) / PROGRESSRANGE);
+ p->range = p->total / p->divisor;
+ SendMessage(p->progbar, PBM_SETRANGE, 0, MAKELPARAM(0, p->range));
+ }
+ break;
+ case PROGFN_PROGRESS:
+ if (p->phases[phase-1].exponential) {
+ while (p->phases[phase-1].n < progress) {
+ p->phases[phase-1].n++;
+ p->phases[phase-1].current *= p->phases[phase-1].param;
+ p->phases[phase-1].current /= 0x10000;
+ }
+ position = (p->phases[phase-1].startpoint +
+ p->phases[phase-1].total - p->phases[phase-1].current);
+ } else {
+ position = (p->phases[phase-1].startpoint +
+ progress * p->phases[phase-1].mult);
+ }
+ SendMessage(p->progbar, PBM_SETPOS, position / p->divisor, 0);
+ break;
+ }
+}
+
+extern char ver[];
+
+#define PASSPHRASE_MAXLEN 512
+
+struct PassphraseProcStruct {
+ 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;
+ struct PassphraseProcStruct *p;
+
+ switch (msg) {
+ case WM_INITDIALOG:
+ SetForegroundWindow(hwnd);
+ SetWindowPos(hwnd, HWND_TOP, 0, 0, 0, 0,
+ SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
+
+ /*
+ * 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);
+ }
+
+ 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:
+ 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;
+}
+
+/*
+ * Prompt for a key file. Assumes the filename buffer is of size
+ * FILENAME_MAX.
+ */
+static int prompt_keyfile(HWND hwnd, char *dlgtitle,
+ char *filename, int save, int ppk)
+{
+ OPENFILENAME of;
+ memset(&of, 0, sizeof(of));
+ of.hwndOwner = hwnd;
+ if (ppk) {
+ of.lpstrFilter = "PuTTY Private Key Files (*.ppk)\0*.ppk\0"
+ "All Files (*.*)\0*\0\0\0";
+ of.lpstrDefExt = ".ppk";
+ } else {
+ of.lpstrFilter = "All Files (*.*)\0*\0\0\0";
+ }
+ of.lpstrCustomFilter = NULL;
+ of.nFilterIndex = 1;
+ of.lpstrFile = filename;
+ *filename = '\0';
+ of.nMaxFile = FILENAME_MAX;
+ of.lpstrFileTitle = NULL;
+ of.lpstrTitle = dlgtitle;
+ of.Flags = 0;
+ return request_file(NULL, &of, FALSE, save);
+}
+
+/*
+ * Dialog-box function for the Licence box.
+ */
+static int CALLBACK LicenceProc(HWND hwnd, UINT msg,
+ WPARAM wParam, LPARAM lParam)
+{
+ switch (msg) {
+ case WM_INITDIALOG:
+ /*
+ * 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);
+ }
+
+ return 1;
+ case WM_COMMAND:
+ switch (LOWORD(wParam)) {
+ case IDOK:
+ case IDCANCEL:
+ EndDialog(hwnd, 1);
+ return 0;
+ }
+ return 0;
+ case WM_CLOSE:
+ EndDialog(hwnd, 1);
+ return 0;
+ }
+ return 0;
+}
+
+/*
+ * Dialog-box function for the About box.
+ */
+static int CALLBACK AboutProc(HWND hwnd, UINT msg,
+ WPARAM wParam, LPARAM lParam)
+{
+ switch (msg) {
+ case WM_INITDIALOG:
+ /*
+ * 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);
+ }
+
+ SetDlgItemText(hwnd, 100, ver);
+ return 1;
+ case WM_COMMAND:
+ switch (LOWORD(wParam)) {
+ case IDOK:
+ case IDCANCEL:
+ EndDialog(hwnd, 1);
+ return 0;
+ case 101:
+ EnableWindow(hwnd, 0);
+ DialogBox(hinst, MAKEINTRESOURCE(214), hwnd, LicenceProc);
+ EnableWindow(hwnd, 1);
+ SetActiveWindow(hwnd);
+ return 0;
+ }
+ return 0;
+ case WM_CLOSE:
+ EndDialog(hwnd, 1);
+ return 0;
+ }
+ return 0;
+}
+
+/*
+ * Thread to generate a key.
+ */
+struct rsa_key_thread_params {
+ HWND progressbar; /* notify this with progress */
+ HWND dialog; /* notify this on completion */
+ int keysize; /* bits in key */
+ int is_dsa;
+ struct RSAKey *key;
+ struct dss_key *dsskey;
+};
+static DWORD WINAPI generate_rsa_key_thread(void *param)
+{
+ struct rsa_key_thread_params *params =
+ (struct rsa_key_thread_params *) param;
+ struct progress prog;
+ prog.progbar = params->progressbar;
+
+ progress_update(&prog, PROGFN_INITIALISE, 0, 0);
+
+ if (params->is_dsa)
+ dsa_generate(params->dsskey, params->keysize, progress_update, &prog);
+ else
+ rsa_generate(params->key, params->keysize, progress_update, &prog);
+
+ PostMessage(params->dialog, WM_DONEKEY, 0, 0);
+
+ sfree(params);
+ return 0;
+}
+
+struct MainDlgState {
+ int collecting_entropy;
+ int generation_thread_exists;
+ int key_exists;
+ int entropy_got, entropy_required, entropy_size;
+ int keysize;
+ int ssh2, is_dsa;
+ char **commentptr; /* points to key.comment or ssh2key.comment */
+ struct ssh2_userkey ssh2key;
+ unsigned *entropy;
+ struct RSAKey key;
+ struct dss_key dsskey;
+ HMENU filemenu, keymenu, cvtmenu;
+};
+
+static void hidemany(HWND hwnd, const int *ids, int hideit)
+{
+ while (*ids) {
+ ShowWindow(GetDlgItem(hwnd, *ids++), (hideit ? SW_HIDE : SW_SHOW));
+ }
+}
+
+static void setupbigedit1(HWND hwnd, int id, int idstatic, struct RSAKey *key)
+{
+ char *buffer;
+ char *dec1, *dec2;
+
+ dec1 = bignum_decimal(key->exponent);
+ dec2 = bignum_decimal(key->modulus);
+ buffer = dupprintf("%d %s %s %s", bignum_bitcount(key->modulus),
+ dec1, dec2, key->comment);
+ SetDlgItemText(hwnd, id, buffer);
+ SetDlgItemText(hwnd, idstatic,
+ "&Public key for pasting into authorized_keys file:");
+ sfree(dec1);
+ sfree(dec2);
+ sfree(buffer);
+}
+
+static void setupbigedit2(HWND hwnd, int id, int idstatic,
+ struct ssh2_userkey *key)
+{
+ unsigned char *pub_blob;
+ char *buffer, *p;
+ int pub_len;
+ int i;
+
+ pub_blob = key->alg->public_blob(key->data, &pub_len);
+ buffer = snewn(strlen(key->alg->name) + 4 * ((pub_len + 2) / 3) +
+ strlen(key->comment) + 3, char);
+ strcpy(buffer, key->alg->name);
+ p = buffer + strlen(buffer);
+ *p++ = ' ';
+ i = 0;
+ while (i < pub_len) {
+ int n = (pub_len - i < 3 ? pub_len - i : 3);
+ base64_encode_atom(pub_blob + i, n, p);
+ i += n;
+ p += 4;
+ }
+ *p++ = ' ';
+ strcpy(p, key->comment);
+ SetDlgItemText(hwnd, id, buffer);
+ SetDlgItemText(hwnd, idstatic, "&Public key for pasting into "
+ "OpenSSH authorized_keys file:");
+ sfree(pub_blob);
+ sfree(buffer);
+}
+
+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;
+ fprintf(fp, "%d %s %s %s\n",
+ bignum_bitcount(key->modulus), dec1, dec2, key->comment);
+ fclose(fp);
+ sfree(dec1);
+ sfree(dec2);
+ return 1;
+}
+
+/*
+ * Warn about the obsolescent key file format.
+ */
+void old_keyfile_warning(void)
+{
+ static const char mbtitle[] = "PuTTY Key File Warning";
+ static const char message[] =
+ "You are loading an SSH-2 private key which has an\n"
+ "old version of the file format. This means your key\n"
+ "file is not fully tamperproof. Future versions of\n"
+ "PuTTY may stop supporting this private key format,\n"
+ "so we recommend you convert your key to the new\n"
+ "format.\n"
+ "\n"
+ "Once the key is loaded into PuTTYgen, you can perform\n"
+ "this conversion simply by saving it again.";
+
+ MessageBox(NULL, message, mbtitle, MB_OK);
+}
+
+static int save_ssh2_pubkey(char *filename, struct ssh2_userkey *key)
+{
+ unsigned char *pub_blob;
+ char *p;
+ int pub_len;
+ int i, column;
+ FILE *fp;
+
+ pub_blob = key->alg->public_blob(key->data, &pub_len);
+
+ fp = fopen(filename, "wb");
+ if (!fp)
+ return 0;
+
+ fprintf(fp, "---- BEGIN SSH2 PUBLIC KEY ----\n");
+
+ fprintf(fp, "Comment: \"");
+ for (p = key->comment; *p; p++) {
+ if (*p == '\\' || *p == '\"')
+ fputc('\\', fp);
+ fputc(*p, fp);
+ }
+ fprintf(fp, "\"\n");
+
+ i = 0;
+ column = 0;
+ while (i < pub_len) {
+ char buf[5];
+ int n = (pub_len - i < 3 ? pub_len - i : 3);
+ base64_encode_atom(pub_blob + i, n, buf);
+ i += n;
+ buf[4] = '\0';
+ fputs(buf, fp);
+ if (++column >= 16) {
+ fputc('\n', fp);
+ column = 0;
+ }
+ }
+ if (column > 0)
+ fputc('\n', fp);
+
+ fprintf(fp, "---- END SSH2 PUBLIC KEY ----\n");
+ fclose(fp);
+ sfree(pub_blob);
+ return 1;
+}
+
+enum {
+ controlidstart = 100,
+ IDC_QUIT,
+ IDC_TITLE,
+ IDC_BOX_KEY,
+ IDC_NOKEY,
+ IDC_GENERATING,
+ IDC_PROGRESS,
+ IDC_PKSTATIC, IDC_KEYDISPLAY,
+ IDC_FPSTATIC, IDC_FINGERPRINT,
+ IDC_COMMENTSTATIC, IDC_COMMENTEDIT,
+ IDC_PASSPHRASE1STATIC, IDC_PASSPHRASE1EDIT,
+ IDC_PASSPHRASE2STATIC, IDC_PASSPHRASE2EDIT,
+ IDC_BOX_ACTIONS,
+ IDC_GENSTATIC, IDC_GENERATE,
+ IDC_LOADSTATIC, IDC_LOAD,
+ IDC_SAVESTATIC, IDC_SAVE, IDC_SAVEPUB,
+ IDC_BOX_PARAMS,
+ IDC_TYPESTATIC, IDC_KEYSSH1, IDC_KEYSSH2RSA, IDC_KEYSSH2DSA,
+ IDC_BITSSTATIC, IDC_BITS,
+ IDC_ABOUT,
+ IDC_GIVEHELP,
+ IDC_IMPORT, IDC_EXPORT_OPENSSH, IDC_EXPORT_SSHCOM
+};
+
+static const int nokey_ids[] = { IDC_NOKEY, 0 };
+static const int generating_ids[] = { IDC_GENERATING, IDC_PROGRESS, 0 };
+static const int gotkey_ids[] = {
+ IDC_PKSTATIC, IDC_KEYDISPLAY,
+ IDC_FPSTATIC, IDC_FINGERPRINT,
+ IDC_COMMENTSTATIC, IDC_COMMENTEDIT,
+ IDC_PASSPHRASE1STATIC, IDC_PASSPHRASE1EDIT,
+ IDC_PASSPHRASE2STATIC, IDC_PASSPHRASE2EDIT, 0
+};
+
+/*
+ * Small UI helper function to switch the state of the main dialog
+ * by enabling and disabling controls and menu items.
+ */
+void ui_set_state(HWND hwnd, struct MainDlgState *state, int status)
+{
+ int type;
+
+ switch (status) {
+ case 0: /* no key */
+ hidemany(hwnd, nokey_ids, FALSE);
+ hidemany(hwnd, generating_ids, TRUE);
+ hidemany(hwnd, gotkey_ids, TRUE);
+ EnableWindow(GetDlgItem(hwnd, IDC_GENERATE), 1);
+ EnableWindow(GetDlgItem(hwnd, IDC_LOAD), 1);
+ EnableWindow(GetDlgItem(hwnd, IDC_SAVE), 0);
+ EnableWindow(GetDlgItem(hwnd, IDC_SAVEPUB), 0);
+ EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH1), 1);
+ EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH2RSA), 1);
+ EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH2DSA), 1);
+ EnableWindow(GetDlgItem(hwnd, IDC_BITS), 1);
+ EnableMenuItem(state->filemenu, IDC_LOAD, MF_ENABLED|MF_BYCOMMAND);
+ EnableMenuItem(state->filemenu, IDC_SAVE, MF_GRAYED|MF_BYCOMMAND);
+ EnableMenuItem(state->filemenu, IDC_SAVEPUB, MF_GRAYED|MF_BYCOMMAND);
+ EnableMenuItem(state->keymenu, IDC_GENERATE, MF_ENABLED|MF_BYCOMMAND);
+ EnableMenuItem(state->keymenu, IDC_KEYSSH1, MF_ENABLED|MF_BYCOMMAND);
+ EnableMenuItem(state->keymenu, IDC_KEYSSH2RSA, MF_ENABLED|MF_BYCOMMAND);
+ EnableMenuItem(state->keymenu, IDC_KEYSSH2DSA, MF_ENABLED|MF_BYCOMMAND);
+ EnableMenuItem(state->cvtmenu, IDC_IMPORT, MF_ENABLED|MF_BYCOMMAND);
+ EnableMenuItem(state->cvtmenu, IDC_EXPORT_OPENSSH,
+ MF_GRAYED|MF_BYCOMMAND);
+ EnableMenuItem(state->cvtmenu, IDC_EXPORT_SSHCOM,
+ MF_GRAYED|MF_BYCOMMAND);
+ break;
+ case 1: /* generating key */
+ hidemany(hwnd, nokey_ids, TRUE);
+ hidemany(hwnd, generating_ids, FALSE);
+ hidemany(hwnd, gotkey_ids, TRUE);
+ EnableWindow(GetDlgItem(hwnd, IDC_GENERATE), 0);
+ EnableWindow(GetDlgItem(hwnd, IDC_LOAD), 0);
+ EnableWindow(GetDlgItem(hwnd, IDC_SAVE), 0);
+ EnableWindow(GetDlgItem(hwnd, IDC_SAVEPUB), 0);
+ EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH1), 0);
+ EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH2RSA), 0);
+ EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH2DSA), 0);
+ EnableWindow(GetDlgItem(hwnd, IDC_BITS), 0);
+ EnableMenuItem(state->filemenu, IDC_LOAD, MF_GRAYED|MF_BYCOMMAND);
+ EnableMenuItem(state->filemenu, IDC_SAVE, MF_GRAYED|MF_BYCOMMAND);
+ EnableMenuItem(state->filemenu, IDC_SAVEPUB, MF_GRAYED|MF_BYCOMMAND);
+ EnableMenuItem(state->keymenu, IDC_GENERATE, MF_GRAYED|MF_BYCOMMAND);
+ EnableMenuItem(state->keymenu, IDC_KEYSSH1, MF_GRAYED|MF_BYCOMMAND);
+ EnableMenuItem(state->keymenu, IDC_KEYSSH2RSA, MF_GRAYED|MF_BYCOMMAND);
+ EnableMenuItem(state->keymenu, IDC_KEYSSH2DSA, MF_GRAYED|MF_BYCOMMAND);
+ EnableMenuItem(state->cvtmenu, IDC_IMPORT, MF_GRAYED|MF_BYCOMMAND);
+ EnableMenuItem(state->cvtmenu, IDC_EXPORT_OPENSSH,
+ MF_GRAYED|MF_BYCOMMAND);
+ EnableMenuItem(state->cvtmenu, IDC_EXPORT_SSHCOM,
+ MF_GRAYED|MF_BYCOMMAND);
+ break;
+ case 2:
+ hidemany(hwnd, nokey_ids, TRUE);
+ hidemany(hwnd, generating_ids, TRUE);
+ hidemany(hwnd, gotkey_ids, FALSE);
+ EnableWindow(GetDlgItem(hwnd, IDC_GENERATE), 1);
+ EnableWindow(GetDlgItem(hwnd, IDC_LOAD), 1);
+ EnableWindow(GetDlgItem(hwnd, IDC_SAVE), 1);
+ EnableWindow(GetDlgItem(hwnd, IDC_SAVEPUB), 1);
+ EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH1), 1);
+ EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH2RSA), 1);
+ EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH2DSA), 1);
+ EnableWindow(GetDlgItem(hwnd, IDC_BITS), 1);
+ EnableMenuItem(state->filemenu, IDC_LOAD, MF_ENABLED|MF_BYCOMMAND);
+ EnableMenuItem(state->filemenu, IDC_SAVE, MF_ENABLED|MF_BYCOMMAND);
+ EnableMenuItem(state->filemenu, IDC_SAVEPUB, MF_ENABLED|MF_BYCOMMAND);
+ EnableMenuItem(state->keymenu, IDC_GENERATE, MF_ENABLED|MF_BYCOMMAND);
+ EnableMenuItem(state->keymenu, IDC_KEYSSH1, MF_ENABLED|MF_BYCOMMAND);
+ EnableMenuItem(state->keymenu, IDC_KEYSSH2RSA,MF_ENABLED|MF_BYCOMMAND);
+ EnableMenuItem(state->keymenu, IDC_KEYSSH2DSA,MF_ENABLED|MF_BYCOMMAND);
+ EnableMenuItem(state->cvtmenu, IDC_IMPORT, MF_ENABLED|MF_BYCOMMAND);
+ /*
+ * Enable export menu items if and only if the key type
+ * supports this kind of export.
+ */
+ type = state->ssh2 ? SSH_KEYTYPE_SSH2 : SSH_KEYTYPE_SSH1;
+#define do_export_menuitem(x,y) \
+ EnableMenuItem(state->cvtmenu, x, MF_BYCOMMAND | \
+ (import_target_type(y)==type?MF_ENABLED:MF_GRAYED))
+ do_export_menuitem(IDC_EXPORT_OPENSSH, SSH_KEYTYPE_OPENSSH);
+ do_export_menuitem(IDC_EXPORT_SSHCOM, SSH_KEYTYPE_SSHCOM);
+#undef do_export_menuitem
+ break;
+ }
+}
+
+void load_key_file(HWND hwnd, struct MainDlgState *state,
+ Filename filename, int was_import_cmd)
+{
+ char passphrase[PASSPHRASE_MAXLEN];
+ 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);
+ 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));
+ message_box(msg, "PuTTYgen Error", MB_OK | MB_ICONERROR,
+ HELPCTXID(errors_cantloadkey));
+ sfree(msg);
+ return;
+ }
+
+ if (type != SSH_KEYTYPE_SSH1 &&
+ type != SSH_KEYTYPE_SSH2) {
+ realtype = type;
+ type = import_target_type(type);
+ }
+
+ comment = NULL;
+ if (realtype == SSH_KEYTYPE_SSH1)
+ needs_pass = rsakey_encrypted(&filename, &comment);
+ else if (realtype == SSH_KEYTYPE_SSH2)
+ needs_pass =
+ ssh2_userkey_encrypted(&filename, &comment);
+ else
+ needs_pass = import_encrypted(&filename, realtype,
+ &comment);
+ pps.passphrase = passphrase;
+ pps.comment = comment;
+ do {
+ if (needs_pass) {
+ int dlgret;
+ dlgret = DialogBoxParam(hinst,
+ MAKEINTRESOURCE(210),
+ NULL, PassphraseProc,
+ (LPARAM) &pps);
+ if (!dlgret) {
+ ret = -2;
+ break;
+ }
+ } else
+ *passphrase = '\0';
+ if (type == SSH_KEYTYPE_SSH1) {
+ if (realtype == type)
+ ret = loadrsakey(&filename, &newkey1,
+ passphrase, &errmsg);
+ else
+ ret = import_ssh1(&filename, realtype,
+ &newkey1, passphrase, &errmsg);
+ } else {
+ if (realtype == type)
+ newkey2 = ssh2_load_userkey(&filename,
+ passphrase, &errmsg);
+ else
+ newkey2 = import_ssh2(&filename, realtype,
+ passphrase, &errmsg);
+ if (newkey2 == SSH2_WRONG_PASSPHRASE)
+ ret = -1;
+ else if (!newkey2)
+ ret = 0;
+ else
+ ret = 1;
+ }
+ } while (ret == -1);
+ if (comment)
+ sfree(comment);
+ if (ret == 0) {
+ char *msg = dupprintf("Couldn't load private key (%s)", errmsg);
+ message_box(msg, "PuTTYgen Error", MB_OK | MB_ICONERROR,
+ HELPCTXID(errors_cantloadkey));
+ sfree(msg);
+ } else if (ret == 1) {
+ /*
+ * Now update the key controls with all the
+ * key data.
+ */
+ {
+ SetDlgItemText(hwnd, IDC_PASSPHRASE1EDIT,
+ passphrase);
+ SetDlgItemText(hwnd, IDC_PASSPHRASE2EDIT,
+ passphrase);
+ if (type == SSH_KEYTYPE_SSH1) {
+ char buf[128];
+ char *savecomment;
+
+ state->ssh2 = FALSE;
+ state->commentptr = &state->key.comment;
+ state->key = newkey1;
+
+ /*
+ * Set the key fingerprint.
+ */
+ savecomment = state->key.comment;
+ state->key.comment = NULL;
+ rsa_fingerprint(buf, sizeof(buf),
+ &state->key);
+ state->key.comment = savecomment;
+
+ SetDlgItemText(hwnd, IDC_FINGERPRINT, buf);
+ /*
+ * Construct a decimal representation
+ * of the key, for pasting into
+ * .ssh/authorized_keys on a Unix box.
+ */
+ setupbigedit1(hwnd, IDC_KEYDISPLAY,
+ IDC_PKSTATIC, &state->key);
+ } else {
+ char *fp;
+ char *savecomment;
+
+ state->ssh2 = TRUE;
+ state->commentptr =
+ &state->ssh2key.comment;
+ state->ssh2key = *newkey2; /* structure copy */
+ sfree(newkey2);
+
+ savecomment = state->ssh2key.comment;
+ state->ssh2key.comment = NULL;
+ fp =
+ state->ssh2key.alg->
+ fingerprint(state->ssh2key.data);
+ state->ssh2key.comment = savecomment;
+
+ SetDlgItemText(hwnd, IDC_FINGERPRINT, fp);
+ sfree(fp);
+
+ setupbigedit2(hwnd, IDC_KEYDISPLAY,
+ IDC_PKSTATIC, &state->ssh2key);
+ }
+ SetDlgItemText(hwnd, IDC_COMMENTEDIT,
+ *state->commentptr);
+ }
+ /*
+ * Finally, hide the progress bar and show
+ * the key data.
+ */
+ ui_set_state(hwnd, state, 2);
+ state->key_exists = TRUE;
+
+ /*
+ * If the user has imported a foreign key
+ * using the Load command, let them know.
+ * If they've used the Import command, be
+ * silent.
+ */
+ if (realtype != type && !was_import_cmd) {
+ char msg[512];
+ sprintf(msg, "Successfully imported foreign key\n"
+ "(%s).\n"
+ "To use this key with PuTTY, you need to\n"
+ "use the \"Save private key\" command to\n"
+ "save it in PuTTY's own format.",
+ key_type_to_str(realtype));
+ MessageBox(NULL, msg, "PuTTYgen Notice",
+ MB_OK | MB_ICONINFORMATION);
+ }
+ }
+}
+
+/*
+ * Dialog-box function for the main PuTTYgen dialog box.
+ */
+static int CALLBACK MainDlgProc(HWND hwnd, UINT msg,
+ WPARAM wParam, LPARAM lParam)
+{
+ static const char generating_msg[] =
+ "Please wait while a key is generated...";
+ static const char entropy_msg[] =
+ "Please generate some randomness by moving the mouse over the blank area.";
+ struct MainDlgState *state;
+
+ switch (msg) {
+ case WM_INITDIALOG:
+ if (has_help())
+ SetWindowLongPtr(hwnd, GWL_EXSTYLE,
+ GetWindowLongPtr(hwnd, GWL_EXSTYLE) |
+ WS_EX_CONTEXTHELP);
+ else {
+ /*
+ * If we add a Help button, this is where we destroy it
+ * if the help file isn't present.
+ */
+ }
+ SendMessage(hwnd, WM_SETICON, (WPARAM) ICON_BIG,
+ (LPARAM) LoadIcon(hinst, MAKEINTRESOURCE(200)));
+
+ state = snew(struct MainDlgState);
+ state->generation_thread_exists = FALSE;
+ state->collecting_entropy = FALSE;
+ state->entropy = NULL;
+ state->key_exists = FALSE;
+ SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR) state);
+ {
+ HMENU menu, menu1;
+
+ menu = CreateMenu();
+
+ menu1 = CreateMenu();
+ AppendMenu(menu1, MF_ENABLED, IDC_LOAD, "&Load private key");
+ AppendMenu(menu1, MF_ENABLED, IDC_SAVEPUB, "Save p&ublic key");
+ AppendMenu(menu1, MF_ENABLED, IDC_SAVE, "&Save private key");
+ AppendMenu(menu1, MF_SEPARATOR, 0, 0);
+ AppendMenu(menu1, MF_ENABLED, IDC_QUIT, "E&xit");
+ AppendMenu(menu, MF_POPUP | MF_ENABLED, (UINT) menu1, "&File");
+ state->filemenu = menu1;
+
+ menu1 = CreateMenu();
+ AppendMenu(menu1, MF_ENABLED, IDC_GENERATE, "&Generate key pair");
+ AppendMenu(menu1, MF_SEPARATOR, 0, 0);
+ AppendMenu(menu1, MF_ENABLED, IDC_KEYSSH1, "SSH-&1 key (RSA)");
+ AppendMenu(menu1, MF_ENABLED, IDC_KEYSSH2RSA, "SSH-2 &RSA key");
+ AppendMenu(menu1, MF_ENABLED, IDC_KEYSSH2DSA, "SSH-2 &DSA key");
+ AppendMenu(menu, MF_POPUP | MF_ENABLED, (UINT) menu1, "&Key");
+ state->keymenu = menu1;
+
+ menu1 = CreateMenu();
+ AppendMenu(menu1, MF_ENABLED, IDC_IMPORT, "&Import key");
+ AppendMenu(menu1, MF_SEPARATOR, 0, 0);
+ AppendMenu(menu1, MF_ENABLED, IDC_EXPORT_OPENSSH,
+ "Export &OpenSSH key");
+ AppendMenu(menu1, MF_ENABLED, IDC_EXPORT_SSHCOM,
+ "Export &ssh.com key");
+ AppendMenu(menu, MF_POPUP | MF_ENABLED, (UINT) menu1,
+ "Con&versions");
+ state->cvtmenu = menu1;
+
+ menu1 = CreateMenu();
+ AppendMenu(menu1, MF_ENABLED, IDC_ABOUT, "&About");
+ if (has_help())
+ AppendMenu(menu1, MF_ENABLED, IDC_GIVEHELP, "&Help");
+ AppendMenu(menu, MF_POPUP | MF_ENABLED, (UINT) menu1, "&Help");
+
+ SetMenu(hwnd, menu);
+ }
+
+ /*
+ * 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);
+ }
+
+ {
+ struct ctlpos cp, cp2;
+
+ /* Accelerators used: acglops1rbd */
+
+ ctlposinit(&cp, hwnd, 4, 4, 4);
+ beginbox(&cp, "Key", IDC_BOX_KEY);
+ cp2 = cp;
+ statictext(&cp2, "No key.", 1, IDC_NOKEY);
+ cp2 = cp;
+ statictext(&cp2, "", 1, IDC_GENERATING);
+ progressbar(&cp2, IDC_PROGRESS);
+ bigeditctrl(&cp,
+ "&Public key for pasting into authorized_keys file:",
+ IDC_PKSTATIC, IDC_KEYDISPLAY, 5);
+ SendDlgItemMessage(hwnd, IDC_KEYDISPLAY, EM_SETREADONLY, 1, 0);
+ staticedit(&cp, "Key f&ingerprint:", IDC_FPSTATIC,
+ IDC_FINGERPRINT, 75);
+ SendDlgItemMessage(hwnd, IDC_FINGERPRINT, EM_SETREADONLY, 1,
+ 0);
+ staticedit(&cp, "Key &comment:", IDC_COMMENTSTATIC,
+ IDC_COMMENTEDIT, 75);
+ staticpassedit(&cp, "Key p&assphrase:", IDC_PASSPHRASE1STATIC,
+ IDC_PASSPHRASE1EDIT, 75);
+ staticpassedit(&cp, "C&onfirm passphrase:",
+ IDC_PASSPHRASE2STATIC, IDC_PASSPHRASE2EDIT, 75);
+ endbox(&cp);
+ beginbox(&cp, "Actions", IDC_BOX_ACTIONS);
+ staticbtn(&cp, "Generate a public/private key pair",
+ IDC_GENSTATIC, "&Generate", IDC_GENERATE);
+ staticbtn(&cp, "Load an existing private key file",
+ IDC_LOADSTATIC, "&Load", IDC_LOAD);
+ static2btn(&cp, "Save the generated key", IDC_SAVESTATIC,
+ "Save p&ublic key", IDC_SAVEPUB,
+ "&Save private key", IDC_SAVE);
+ endbox(&cp);
+ beginbox(&cp, "Parameters", IDC_BOX_PARAMS);
+ radioline(&cp, "Type of key to generate:", IDC_TYPESTATIC, 3,
+ "SSH-&1 (RSA)", IDC_KEYSSH1,
+ "SSH-2 &RSA", IDC_KEYSSH2RSA,
+ "SSH-2 &DSA", IDC_KEYSSH2DSA, NULL);
+ staticedit(&cp, "Number of &bits in a generated key:",
+ IDC_BITSSTATIC, IDC_BITS, 20);
+ endbox(&cp);
+ }
+ CheckRadioButton(hwnd, IDC_KEYSSH1, IDC_KEYSSH2DSA, IDC_KEYSSH2RSA);
+ CheckMenuRadioItem(state->keymenu, IDC_KEYSSH1, IDC_KEYSSH2DSA,
+ IDC_KEYSSH2RSA, MF_BYCOMMAND);
+ SetDlgItemInt(hwnd, IDC_BITS, DEFAULT_KEYSIZE, FALSE);
+
+ /*
+ * Initially, hide the progress bar and the key display,
+ * and show the no-key display. Also disable the Save
+ * buttons, because with no key we obviously can't save
+ * anything.
+ */
+ 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);
+
+ return 1;
+ case WM_MOUSEMOVE:
+ state = (struct MainDlgState *) GetWindowLongPtr(hwnd, GWLP_USERDATA);
+ if (state->collecting_entropy &&
+ state->entropy && state->entropy_got < state->entropy_required) {
+ state->entropy[state->entropy_got++] = lParam;
+ state->entropy[state->entropy_got++] = GetMessageTime();
+ SendDlgItemMessage(hwnd, IDC_PROGRESS, PBM_SETPOS,
+ state->entropy_got, 0);
+ if (state->entropy_got >= state->entropy_required) {
+ struct rsa_key_thread_params *params;
+ DWORD threadid;
+
+ /*
+ * Seed the entropy pool
+ */
+ random_add_heavynoise(state->entropy, state->entropy_size);
+ memset(state->entropy, 0, state->entropy_size);
+ sfree(state->entropy);
+ state->collecting_entropy = FALSE;
+
+ SetDlgItemText(hwnd, IDC_GENERATING, generating_msg);
+ SendDlgItemMessage(hwnd, IDC_PROGRESS, PBM_SETRANGE, 0,
+ MAKELPARAM(0, PROGRESSRANGE));
+ SendDlgItemMessage(hwnd, IDC_PROGRESS, PBM_SETPOS, 0, 0);
+
+ params = snew(struct rsa_key_thread_params);
+ params->progressbar = GetDlgItem(hwnd, IDC_PROGRESS);
+ params->dialog = hwnd;
+ params->keysize = state->keysize;
+ params->is_dsa = state->is_dsa;
+ params->key = &state->key;
+ params->dsskey = &state->dsskey;
+
+ if (!CreateThread(NULL, 0, generate_rsa_key_thread,
+ params, 0, &threadid)) {
+ MessageBox(hwnd, "Out of thread resources",
+ "Key generation error",
+ MB_OK | MB_ICONERROR);
+ sfree(params);
+ } else {
+ state->generation_thread_exists = TRUE;
+ }
+ }
+ }
+ break;
+ case WM_COMMAND:
+ switch (LOWORD(wParam)) {
+ case IDC_KEYSSH1:
+ case IDC_KEYSSH2RSA:
+ case IDC_KEYSSH2DSA:
+ {
+ state = (struct MainDlgState *)
+ GetWindowLongPtr(hwnd, GWLP_USERDATA);
+ if (!IsDlgButtonChecked(hwnd, LOWORD(wParam)))
+ CheckRadioButton(hwnd, IDC_KEYSSH1, IDC_KEYSSH2DSA,
+ LOWORD(wParam));
+ CheckMenuRadioItem(state->keymenu, IDC_KEYSSH1, IDC_KEYSSH2DSA,
+ LOWORD(wParam), MF_BYCOMMAND);
+ }
+ break;
+ case IDC_QUIT:
+ PostMessage(hwnd, WM_CLOSE, 0, 0);
+ break;
+ case IDC_COMMENTEDIT:
+ if (HIWORD(wParam) == EN_CHANGE) {
+ state = (struct MainDlgState *)
+ GetWindowLongPtr(hwnd, GWLP_USERDATA);
+ if (state->key_exists) {
+ HWND editctl = GetDlgItem(hwnd, IDC_COMMENTEDIT);
+ int len = GetWindowTextLength(editctl);
+ if (*state->commentptr)
+ sfree(*state->commentptr);
+ *state->commentptr = snewn(len + 1, char);
+ GetWindowText(editctl, *state->commentptr, len + 1);
+ if (state->ssh2) {
+ setupbigedit2(hwnd, IDC_KEYDISPLAY, IDC_PKSTATIC,
+ &state->ssh2key);
+ } else {
+ setupbigedit1(hwnd, IDC_KEYDISPLAY, IDC_PKSTATIC,
+ &state->key);
+ }
+ }
+ }
+ break;
+ case IDC_ABOUT:
+ EnableWindow(hwnd, 0);
+ DialogBox(hinst, MAKEINTRESOURCE(213), hwnd, AboutProc);
+ EnableWindow(hwnd, 1);
+ SetActiveWindow(hwnd);
+ return 0;
+ case IDC_GIVEHELP:
+ if (HIWORD(wParam) == BN_CLICKED ||
+ HIWORD(wParam) == BN_DOUBLECLICKED) {
+ launch_help(hwnd, WINHELP_CTX_puttygen_general);
+ }
+ return 0;
+ case IDC_GENERATE:
+ if (HIWORD(wParam) != BN_CLICKED &&
+ HIWORD(wParam) != BN_DOUBLECLICKED)
+ break;
+ state =
+ (struct MainDlgState *) GetWindowLongPtr(hwnd, GWLP_USERDATA);
+ if (!state->generation_thread_exists) {
+ BOOL ok;
+ state->keysize = GetDlgItemInt(hwnd, IDC_BITS, &ok, FALSE);
+ if (!ok)
+ state->keysize = DEFAULT_KEYSIZE;
+ /* If we ever introduce a new key type, check it here! */
+ state->ssh2 = !IsDlgButtonChecked(hwnd, IDC_KEYSSH1);
+ state->is_dsa = IsDlgButtonChecked(hwnd, IDC_KEYSSH2DSA);
+ if (state->keysize < 256) {
+ int ret = MessageBox(hwnd,
+ "PuTTYgen will not generate a key"
+ " smaller than 256 bits.\n"
+ "Key length reset to 256. Continue?",
+ "PuTTYgen Warning",
+ MB_ICONWARNING | MB_OKCANCEL);
+ if (ret != IDOK)
+ break;
+ state->keysize = 256;
+ SetDlgItemInt(hwnd, IDC_BITS, 256, FALSE);
+ }
+ ui_set_state(hwnd, state, 1);
+ SetDlgItemText(hwnd, IDC_GENERATING, entropy_msg);
+ state->key_exists = FALSE;
+ state->collecting_entropy = TRUE;
+
+ /*
+ * My brief statistical tests on mouse movements
+ * suggest that there are about 2.5 bits of
+ * randomness in the x position, 2.5 in the y
+ * position, and 1.7 in the message time, making
+ * 5.7 bits of unpredictability per mouse movement.
+ * However, other people have told me it's far less
+ * than that, so I'm going to be stupidly cautious
+ * and knock that down to a nice round 2. With this
+ * method, we require two words per mouse movement,
+ * so with 2 bits per mouse movement we expect 2
+ * bits every 2 words.
+ */
+ state->entropy_required = (state->keysize / 2) * 2;
+ state->entropy_got = 0;
+ state->entropy_size = (state->entropy_required *
+ sizeof(unsigned));
+ state->entropy = snewn(state->entropy_required, unsigned);
+
+ SendDlgItemMessage(hwnd, IDC_PROGRESS, PBM_SETRANGE, 0,
+ MAKELPARAM(0, state->entropy_required));
+ SendDlgItemMessage(hwnd, IDC_PROGRESS, PBM_SETPOS, 0, 0);
+ }
+ break;
+ case IDC_SAVE:
+ case IDC_EXPORT_OPENSSH:
+ case IDC_EXPORT_SSHCOM:
+ if (HIWORD(wParam) != BN_CLICKED)
+ break;
+ state =
+ (struct MainDlgState *) GetWindowLongPtr(hwnd, GWLP_USERDATA);
+ if (state->key_exists) {
+ char filename[FILENAME_MAX];
+ char passphrase[PASSPHRASE_MAXLEN];
+ char passphrase2[PASSPHRASE_MAXLEN];
+ int type, realtype;
+
+ if (state->ssh2)
+ realtype = SSH_KEYTYPE_SSH2;
+ else
+ realtype = SSH_KEYTYPE_SSH1;
+
+ if (LOWORD(wParam) == IDC_EXPORT_OPENSSH)
+ type = SSH_KEYTYPE_OPENSSH;
+ else if (LOWORD(wParam) == IDC_EXPORT_SSHCOM)
+ type = SSH_KEYTYPE_SSHCOM;
+ else
+ type = realtype;
+
+ if (type != realtype &&
+ import_target_type(type) != realtype) {
+ char msg[256];
+ sprintf(msg, "Cannot export an SSH-%d key in an SSH-%d"
+ " format", (state->ssh2 ? 2 : 1),
+ (state->ssh2 ? 1 : 2));
+ MessageBox(hwnd, msg,
+ "PuTTYgen Error", MB_OK | MB_ICONERROR);
+ break;
+ }
+
+ GetDlgItemText(hwnd, IDC_PASSPHRASE1EDIT,
+ passphrase, sizeof(passphrase));
+ GetDlgItemText(hwnd, IDC_PASSPHRASE2EDIT,
+ passphrase2, sizeof(passphrase2));
+ if (strcmp(passphrase, passphrase2)) {
+ MessageBox(hwnd,
+ "The two passphrases given do not match.",
+ "PuTTYgen Error", MB_OK | MB_ICONERROR);
+ break;
+ }
+ 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 (prompt_keyfile(hwnd, "Save private key as:",
+ filename, 1, (type == realtype))) {
+ int ret;
+ FILE *fp = fopen(filename, "r");
+ if (fp) {
+ char *buffer;
+ fclose(fp);
+ buffer = dupprintf("Overwrite existing file\n%s?",
+ filename);
+ ret = MessageBox(hwnd, buffer, "PuTTYgen Warning",
+ MB_YESNO | MB_ICONWARNING);
+ sfree(buffer);
+ if (ret != IDYES)
+ break;
+ }
+
+ if (state->ssh2) {
+ Filename fn = filename_from_str(filename);
+ if (type != realtype)
+ ret = export_ssh2(&fn, type, &state->ssh2key,
+ *passphrase ? passphrase : NULL);
+ else
+ ret = ssh2_save_userkey(&fn, &state->ssh2key,
+ *passphrase ? passphrase :
+ NULL);
+ } else {
+ Filename fn = filename_from_str(filename);
+ if (type != realtype)
+ ret = export_ssh1(&fn, type, &state->key,
+ *passphrase ? passphrase : NULL);
+ else
+ ret = saversakey(&fn, &state->key,
+ *passphrase ? passphrase : NULL);
+ }
+ if (ret <= 0) {
+ MessageBox(hwnd, "Unable to save key file",
+ "PuTTYgen Error", MB_OK | MB_ICONERROR);
+ }
+ }
+ }
+ break;
+ case IDC_SAVEPUB:
+ if (HIWORD(wParam) != BN_CLICKED)
+ break;
+ state =
+ (struct MainDlgState *) GetWindowLongPtr(hwnd, GWLP_USERDATA);
+ if (state->key_exists) {
+ char filename[FILENAME_MAX];
+ if (prompt_keyfile(hwnd, "Save public key as:",
+ filename, 1, 0)) {
+ int ret;
+ FILE *fp = fopen(filename, "r");
+ if (fp) {
+ char *buffer;
+ fclose(fp);
+ buffer = dupprintf("Overwrite existing file\n%s?",
+ filename);
+ ret = MessageBox(hwnd, buffer, "PuTTYgen Warning",
+ MB_YESNO | MB_ICONWARNING);
+ sfree(buffer);
+ if (ret != IDYES)
+ break;
+ }
+ if (state->ssh2) {
+ ret = save_ssh2_pubkey(filename, &state->ssh2key);
+ } else {
+ ret = save_ssh1_pubkey(filename, &state->key);
+ }
+ if (ret <= 0) {
+ MessageBox(hwnd, "Unable to save key file",
+ "PuTTYgen Error", MB_OK | MB_ICONERROR);
+ }
+ }
+ }
+ break;
+ case IDC_LOAD:
+ case IDC_IMPORT:
+ if (HIWORD(wParam) != BN_CLICKED)
+ break;
+ 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);
+ }
+ break;
+ }
+ return 0;
+ case WM_DONEKEY:
+ state = (struct MainDlgState *) GetWindowLongPtr(hwnd, GWLP_USERDATA);
+ state->generation_thread_exists = FALSE;
+ state->key_exists = TRUE;
+ SendDlgItemMessage(hwnd, IDC_PROGRESS, PBM_SETRANGE, 0,
+ MAKELPARAM(0, PROGRESSRANGE));
+ SendDlgItemMessage(hwnd, IDC_PROGRESS, PBM_SETPOS, PROGRESSRANGE, 0);
+ if (state->ssh2) {
+ if (state->is_dsa) {
+ state->ssh2key.data = &state->dsskey;
+ state->ssh2key.alg = &ssh_dss;
+ } else {
+ state->ssh2key.data = &state->key;
+ state->ssh2key.alg = &ssh_rsa;
+ }
+ state->commentptr = &state->ssh2key.comment;
+ } else {
+ state->commentptr = &state->key.comment;
+ }
+ /*
+ * Invent a comment for the key. We'll do this by including
+ * the date in it. This will be so horrifyingly ugly that
+ * the user will immediately want to change it, which is
+ * what we want :-)
+ */
+ *state->commentptr = snewn(30, char);
+ {
+ struct tm tm;
+ tm = ltime();
+ if (state->is_dsa)
+ strftime(*state->commentptr, 30, "dsa-key-%Y%m%d", &tm);
+ else
+ strftime(*state->commentptr, 30, "rsa-key-%Y%m%d", &tm);
+ }
+
+ /*
+ * Now update the key controls with all the key data.
+ */
+ {
+ char *savecomment;
+ /*
+ * Blank passphrase, initially. This isn't dangerous,
+ * because we will warn (Are You Sure?) before allowing
+ * the user to save an unprotected private key.
+ */
+ SetDlgItemText(hwnd, IDC_PASSPHRASE1EDIT, "");
+ SetDlgItemText(hwnd, IDC_PASSPHRASE2EDIT, "");
+ /*
+ * Set the comment.
+ */
+ SetDlgItemText(hwnd, IDC_COMMENTEDIT, *state->commentptr);
+ /*
+ * Set the key fingerprint.
+ */
+ savecomment = *state->commentptr;
+ *state->commentptr = NULL;
+ if (state->ssh2) {
+ char *fp;
+ fp = state->ssh2key.alg->fingerprint(state->ssh2key.data);
+ SetDlgItemText(hwnd, IDC_FINGERPRINT, fp);
+ sfree(fp);
+ } else {
+ char buf[128];
+ rsa_fingerprint(buf, sizeof(buf), &state->key);
+ SetDlgItemText(hwnd, IDC_FINGERPRINT, buf);
+ }
+ *state->commentptr = savecomment;
+ /*
+ * Construct a decimal representation of the key, for
+ * pasting into .ssh/authorized_keys or
+ * .ssh/authorized_keys2 on a Unix box.
+ */
+ if (state->ssh2) {
+ setupbigedit2(hwnd, IDC_KEYDISPLAY,
+ IDC_PKSTATIC, &state->ssh2key);
+ } else {
+ setupbigedit1(hwnd, IDC_KEYDISPLAY,
+ IDC_PKSTATIC, &state->key);
+ }
+ }
+ /*
+ * Finally, hide the progress bar and show the key data.
+ */
+ ui_set_state(hwnd, state, 2);
+ break;
+ case WM_HELP:
+ {
+ int id = ((LPHELPINFO)lParam)->iCtrlId;
+ char *topic = NULL;
+ switch (id) {
+ case IDC_GENERATING:
+ case IDC_PROGRESS:
+ case IDC_GENSTATIC:
+ case IDC_GENERATE:
+ topic = WINHELP_CTX_puttygen_generate; break;
+ case IDC_PKSTATIC:
+ case IDC_KEYDISPLAY:
+ topic = WINHELP_CTX_puttygen_pastekey; break;
+ case IDC_FPSTATIC:
+ case IDC_FINGERPRINT:
+ topic = WINHELP_CTX_puttygen_fingerprint; break;
+ case IDC_COMMENTSTATIC:
+ case IDC_COMMENTEDIT:
+ topic = WINHELP_CTX_puttygen_comment; break;
+ case IDC_PASSPHRASE1STATIC:
+ case IDC_PASSPHRASE1EDIT:
+ case IDC_PASSPHRASE2STATIC:
+ case IDC_PASSPHRASE2EDIT:
+ topic = WINHELP_CTX_puttygen_passphrase; break;
+ case IDC_LOADSTATIC:
+ case IDC_LOAD:
+ topic = WINHELP_CTX_puttygen_load; break;
+ case IDC_SAVESTATIC:
+ case IDC_SAVE:
+ topic = WINHELP_CTX_puttygen_savepriv; break;
+ case IDC_SAVEPUB:
+ topic = WINHELP_CTX_puttygen_savepub; break;
+ case IDC_TYPESTATIC:
+ case IDC_KEYSSH1:
+ case IDC_KEYSSH2RSA:
+ case IDC_KEYSSH2DSA:
+ topic = WINHELP_CTX_puttygen_keytype; break;
+ case IDC_BITSSTATIC:
+ case IDC_BITS:
+ topic = WINHELP_CTX_puttygen_bits; break;
+ case IDC_IMPORT:
+ case IDC_EXPORT_OPENSSH:
+ case IDC_EXPORT_SSHCOM:
+ topic = WINHELP_CTX_puttygen_conversions; break;
+ }
+ if (topic) {
+ launch_help(hwnd, topic);
+ } else {
+ MessageBeep(0);
+ }
+ }
+ break;
+ case WM_CLOSE:
+ state = (struct MainDlgState *) GetWindowLongPtr(hwnd, GWLP_USERDATA);
+ sfree(state);
+ quit_help(hwnd);
+ EndDialog(hwnd, 1);
+ return 0;
+ }
+ return 0;
+}
+
+void cleanup_exit(int code)
+{
+ shutdown_help();
+ exit(code);
+}
+
+int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
+{
+ int argc;
+ char **argv;
+ int ret;
+
+ InitCommonControls();
+ hinst = inst;
+ hwnd = NULL;
+
+ /*
+ * See if we can find our Help file.
+ */
+ init_help();
+
+ split_into_argv(cmdline, &argc, &argv, NULL);
+
+ if (argc > 0) {
+ if (!strcmp(argv[0], "-pgpfp")) {
+ pgp_fingerprints();
+ exit(1);
+ } else {
+ /*
+ * Assume the first argument to be a private key file, and
+ * attempt to load it.
+ */
+ cmdline_keyfile = argv[0];
+ }
+ }
+
+ random_ref();
+ ret = DialogBox(hinst, MAKEINTRESOURCE(201), NULL, MainDlgProc) != IDOK;
+
+ cleanup_exit(ret);
+ return ret; /* just in case optimiser complains */
+}
--- /dev/null
+/*
+ * Pageant: the PuTTY Authentication Agent.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <assert.h>
+#include <tchar.h>
+
+#define PUTTY_DO_GLOBALS
+
+#include "putty.h"
+#include "ssh.h"
+#include "misc.h"
+#include "tree234.h"
+
+#include <shellapi.h>
+
+#ifndef NO_SECURITY
+#include <aclapi.h>
+#ifdef DEBUG_IPC
+#define _WIN32_WINNT 0x0500 /* for ConvertSidToStringSid */
+#include <sddl.h>
+#endif
+#endif
+
+#define IDI_MAINICON 200
+#define IDI_TRAYICON 201
+
+#define WM_SYSTRAY (WM_APP + 6)
+#define WM_SYSTRAY2 (WM_APP + 7)
+
+#define AGENT_COPYDATA_ID 0x804e50ba /* random goop */
+
+/*
+ * FIXME: maybe some day we can sort this out ...
+ */
+#define AGENT_MAX_MSGLEN 8192
+
+/* From MSDN: In the WM_SYSCOMMAND message, the four low-order bits of
+ * wParam are used by Windows, and should be masked off, so we shouldn't
+ * attempt to store information in them. Hence all these identifiers have
+ * the low 4 bits clear. Also, identifiers should < 0xF000. */
+
+#define IDM_CLOSE 0x0010
+#define IDM_VIEWKEYS 0x0020
+#define IDM_ADDKEY 0x0030
+#define IDM_HELP 0x0040
+#define IDM_ABOUT 0x0050
+
+#define APPNAME "Pageant"
+
+extern char ver[];
+
+static HWND keylist;
+static HWND aboutbox;
+static HMENU systray_menu, session_menu;
+static int already_running;
+
+static char *putty_path;
+
+/* CWD for "add key" file requester. */
+static filereq *keypath = NULL;
+
+#define IDM_PUTTY 0x0060
+#define IDM_SESSIONS_BASE 0x1000
+#define IDM_SESSIONS_MAX 0x2000
+#define PUTTY_REGKEY "Software\\SimonTatham\\PuTTY\\Sessions"
+#define PUTTY_DEFAULT "Default%20Settings"
+static int initial_menuitems_count;
+
+/*
+ * Print a modal (Really Bad) message box and perform a fatal exit.
+ */
+void modalfatalbox(char *fmt, ...)
+{
+ va_list ap;
+ char *buf;
+
+ va_start(ap, fmt);
+ buf = dupvprintf(fmt, ap);
+ va_end(ap);
+ MessageBox(hwnd, buf, "Pageant Fatal Error",
+ MB_SYSTEMMODAL | MB_ICONERROR | MB_OK);
+ sfree(buf);
+ exit(1);
+}
+
+/* Un-munge session names out of the registry. */
+static void unmungestr(char *in, char *out, int outlen)
+{
+ while (*in) {
+ if (*in == '%' && in[1] && in[2]) {
+ int i, j;
+
+ i = in[1] - '0';
+ i -= (i > 9 ? 7 : 0);
+ j = in[2] - '0';
+ j -= (j > 9 ? 7 : 0);
+
+ *out++ = (i << 4) + j;
+ if (!--outlen)
+ return;
+ in += 3;
+ } else {
+ *out++ = *in++;
+ if (!--outlen)
+ return;
+ }
+ }
+ *out = '\0';
+ return;
+}
+
+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);
+static void *make_keylist2(int *length);
+static void *get_keylist1(int *length);
+static void *get_keylist2(int *length);
+
+/*
+ * We need this to link with the RSA code, because rsaencrypt()
+ * pads its data with random bytes. Since we only use rsadecrypt()
+ * and the signing functions, which are deterministic, this should
+ * never be called.
+ *
+ * If it _is_ called, there is a _serious_ problem, because it
+ * won't generate true random numbers. So we must scream, panic,
+ * and exit immediately if that should happen.
+ */
+int random_byte(void)
+{
+ MessageBox(hwnd, "Internal Error", APPNAME, MB_OK | MB_ICONERROR);
+ exit(0);
+ /* this line can't be reached but it placates MSVC's warnings :-) */
+ return 0;
+}
+
+/*
+ * Blob structure for passing to the asymmetric SSH-2 key compare
+ * function, prototyped here.
+ */
+struct blob {
+ unsigned char *blob;
+ int len;
+};
+static int cmpkeys_ssh2_asymm(void *av, void *bv);
+
+#define PASSPHRASE_MAXLEN 512
+
+struct PassphraseProcStruct {
+ char *passphrase;
+ char *comment;
+};
+
+static tree234 *passphrases = NULL;
+
+/*
+ * After processing a list of filenames, we want to forget the
+ * passphrases.
+ */
+static void forget_passphrases(void)
+{
+ while (count234(passphrases) > 0) {
+ char *pp = index234(passphrases, 0);
+ memset(pp, 0, strlen(pp));
+ delpos234(passphrases, 0);
+ free(pp);
+ }
+}
+
+/*
+ * Dialog-box function for the Licence box.
+ */
+static int CALLBACK LicenceProc(HWND hwnd, UINT msg,
+ WPARAM wParam, LPARAM lParam)
+{
+ switch (msg) {
+ case WM_INITDIALOG:
+ return 1;
+ case WM_COMMAND:
+ switch (LOWORD(wParam)) {
+ case IDOK:
+ case IDCANCEL:
+ EndDialog(hwnd, 1);
+ return 0;
+ }
+ return 0;
+ case WM_CLOSE:
+ EndDialog(hwnd, 1);
+ return 0;
+ }
+ return 0;
+}
+
+/*
+ * Dialog-box function for the About box.
+ */
+static int CALLBACK AboutProc(HWND hwnd, UINT msg,
+ WPARAM wParam, LPARAM lParam)
+{
+ switch (msg) {
+ case WM_INITDIALOG:
+ SetDlgItemText(hwnd, 100, ver);
+ return 1;
+ case WM_COMMAND:
+ switch (LOWORD(wParam)) {
+ case IDOK:
+ case IDCANCEL:
+ aboutbox = NULL;
+ DestroyWindow(hwnd);
+ return 0;
+ case 101:
+ EnableWindow(hwnd, 0);
+ DialogBox(hinst, MAKEINTRESOURCE(214), hwnd, LicenceProc);
+ EnableWindow(hwnd, 1);
+ SetActiveWindow(hwnd);
+ return 0;
+ }
+ return 0;
+ case WM_CLOSE:
+ aboutbox = NULL;
+ DestroyWindow(hwnd);
+ return 0;
+ }
+ return 0;
+}
+
+static HWND passphrase_box;
+
+/*
+ * Dialog-box function for the passphrase box.
+ */
+static int CALLBACK 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:
+ 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;
+}
+
+/*
+ * Warn about the obsolescent key file format.
+ */
+void old_keyfile_warning(void)
+{
+ static const char mbtitle[] = "PuTTY Key File Warning";
+ static const char message[] =
+ "You are loading an SSH-2 private key which has an\n"
+ "old version of the file format. This means your key\n"
+ "file is not fully tamperproof. Future versions of\n"
+ "PuTTY may stop supporting this private key format,\n"
+ "so we recommend you convert your key to the new\n"
+ "format.\n"
+ "\n"
+ "You can perform this conversion by loading the key\n"
+ "into PuTTYgen and then saving it again.";
+
+ MessageBox(NULL, message, mbtitle, MB_OK);
+}
+
+/*
+ * Update the visible key list.
+ */
+static void keylist_update(void)
+{
+ struct RSAKey *rkey;
+ struct ssh2_userkey *skey;
+ int i;
+
+ if (keylist) {
+ SendDlgItemMessage(keylist, 100, LB_RESETCONTENT, 0, 0);
+ for (i = 0; NULL != (rkey = index234(rsakeys, i)); i++) {
+ char listentry[512], *p;
+ /*
+ * Replace two spaces in the fingerprint with tabs, for
+ * nice alignment in the box.
+ */
+ strcpy(listentry, "ssh1\t");
+ p = listentry + strlen(listentry);
+ rsa_fingerprint(p, sizeof(listentry) - (p - listentry), rkey);
+ p = strchr(listentry, ' ');
+ if (p)
+ *p = '\t';
+ p = strchr(listentry, ' ');
+ if (p)
+ *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;
+ /*
+ * 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));
+ p = strchr(listentry, ' ');
+ if (p)
+ *p = '\t';
+ p = strchr(listentry, ' ');
+ if (p)
+ *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);
+ }
+ SendDlgItemMessage(keylist, 100, LB_SETCURSEL, (WPARAM) - 1, 0);
+ }
+}
+
+/*
+ * This function loads a key from a file and adds it.
+ */
+static void add_keyfile(Filename filename)
+{
+ char passphrase[PASSPHRASE_MAXLEN];
+ 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;
+
+ 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;
+ }
+
+ /*
+ * See if the key is already loaded (in the primary Pageant,
+ * which may or may not be us).
+ */
+ {
+ void *blob;
+ unsigned char *keylist, *p;
+ int i, nkeys, bloblen, keylistlen;
+
+ if (type == SSH_KEYTYPE_SSH1) {
+ 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;
+ 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;
+ }
+ /* For our purposes we want the blob prefixed with its length */
+ blob2 = snewn(bloblen+4, unsigned char);
+ PUT_32BIT(blob2, bloblen);
+ memcpy(blob2 + 4, blob, bloblen);
+ sfree(blob);
+ blob = blob2;
+
+ keylist = get_keylist2(&keylistlen);
+ }
+ if (keylist) {
+ if (keylistlen < 4) {
+ MessageBox(NULL, "Received broken key list?!", APPNAME,
+ MB_OK | MB_ICONERROR);
+ return;
+ }
+ nkeys = GET_32BIT(keylist);
+ p = keylist + 4;
+ keylistlen -= 4;
+
+ for (i = 0; i < nkeys; i++) {
+ if (!memcmp(blob, p, bloblen)) {
+ /* Key is already present; we can now leave. */
+ sfree(keylist);
+ sfree(blob);
+ return;
+ }
+ /* Now skip over public blob */
+ if (type == SSH_KEYTYPE_SSH1) {
+ int n = rsa_public_blob_len(p, keylistlen);
+ if (n < 0) {
+ MessageBox(NULL, "Received broken key list?!", APPNAME,
+ MB_OK | MB_ICONERROR);
+ return;
+ }
+ p += n;
+ keylistlen -= n;
+ } else {
+ int n;
+ if (keylistlen < 4) {
+ MessageBox(NULL, "Received broken key list?!", APPNAME,
+ MB_OK | MB_ICONERROR);
+ return;
+ }
+ n = 4 + GET_32BIT(p);
+ if (keylistlen < n) {
+ MessageBox(NULL, "Received broken key list?!", APPNAME,
+ MB_OK | MB_ICONERROR);
+ return;
+ }
+ p += n;
+ keylistlen -= n;
+ }
+ /* Now skip over comment field */
+ {
+ int n;
+ if (keylistlen < 4) {
+ MessageBox(NULL, "Received broken key list?!", APPNAME,
+ MB_OK | MB_ICONERROR);
+ return;
+ }
+ n = 4 + GET_32BIT(p);
+ if (keylistlen < n) {
+ MessageBox(NULL, "Received broken key list?!", APPNAME,
+ MB_OK | MB_ICONERROR);
+ return;
+ }
+ p += n;
+ keylistlen -= n;
+ }
+ }
+
+ sfree(keylist);
+ }
+
+ sfree(blob);
+ }
+
+ error = NULL;
+ if (type == SSH_KEYTYPE_SSH1)
+ needs_pass = rsakey_encrypted(&filename, &comment);
+ else
+ needs_pass = ssh2_userkey_encrypted(&filename, &comment);
+ attempts = 0;
+ if (type == SSH_KEYTYPE_SSH1)
+ rkey = snew(struct RSAKey);
+ pps.passphrase = passphrase;
+ pps.comment = comment;
+ original_pass = 0;
+ do {
+ if (needs_pass) {
+ /* try all the remembered passphrases first */
+ char *pp = index234(passphrases, attempts);
+ if(pp) {
+ strcpy(passphrase, pp);
+ } else {
+ int dlgret;
+ original_pass = 1;
+ dlgret = DialogBoxParam(hinst, MAKEINTRESOURCE(210),
+ NULL, PassphraseProc, (LPARAM) &pps);
+ passphrase_box = NULL;
+ if (!dlgret) {
+ if (comment)
+ sfree(comment);
+ if (type == SSH_KEYTYPE_SSH1)
+ sfree(rkey);
+ return; /* operation cancelled */
+ }
+ }
+ } else
+ *passphrase = '\0';
+ if (type == SSH_KEYTYPE_SSH1)
+ ret = loadrsakey(&filename, rkey, passphrase, &error);
+ else {
+ skey = ssh2_load_userkey(&filename, passphrase, &error);
+ if (skey == SSH2_WRONG_PASSPHRASE)
+ ret = -1;
+ else if (!skey)
+ ret = 0;
+ else
+ 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 (comment)
+ sfree(comment);
+ if (ret == 0) {
+ char *msg = dupprintf("Couldn't load private key (%s)", error);
+ message_box(msg, APPNAME, MB_OK | MB_ICONERROR,
+ HELPCTXID(errors_cantloadkey));
+ sfree(msg);
+ if (type == SSH_KEYTYPE_SSH1)
+ sfree(rkey);
+ return;
+ }
+ if (type == SSH_KEYTYPE_SSH1) {
+ if (already_running) {
+ unsigned char *request, *response;
+ void *vresponse;
+ int reqlen, clen, resplen, ret;
+
+ clen = strlen(rkey->comment);
+
+ reqlen = 4 + 1 + /* length, message type */
+ 4 + /* bit count */
+ ssh1_bignum_length(rkey->modulus) +
+ ssh1_bignum_length(rkey->exponent) +
+ ssh1_bignum_length(rkey->private_exponent) +
+ ssh1_bignum_length(rkey->iqmp) +
+ ssh1_bignum_length(rkey->p) +
+ ssh1_bignum_length(rkey->q) + 4 + clen /* comment */
+ ;
+
+ request = snewn(reqlen, unsigned char);
+
+ request[4] = SSH1_AGENTC_ADD_RSA_IDENTITY;
+ reqlen = 5;
+ PUT_32BIT(request + reqlen, bignum_bitcount(rkey->modulus));
+ reqlen += 4;
+ reqlen += ssh1_write_bignum(request + reqlen, rkey->modulus);
+ reqlen += ssh1_write_bignum(request + reqlen, rkey->exponent);
+ reqlen +=
+ ssh1_write_bignum(request + reqlen,
+ rkey->private_exponent);
+ reqlen += ssh1_write_bignum(request + reqlen, rkey->iqmp);
+ reqlen += ssh1_write_bignum(request + reqlen, rkey->p);
+ reqlen += ssh1_write_bignum(request + reqlen, rkey->q);
+ PUT_32BIT(request + reqlen, clen);
+ memcpy(request + reqlen + 4, rkey->comment, clen);
+ reqlen += 4 + clen;
+ PUT_32BIT(request, reqlen - 4);
+
+ ret = agent_query(request, reqlen, &vresponse, &resplen,
+ NULL, NULL);
+ assert(ret == 1);
+ response = vresponse;
+ if (resplen < 5 || response[4] != SSH_AGENT_SUCCESS)
+ MessageBox(NULL, "The already running Pageant "
+ "refused to add the key.", APPNAME,
+ MB_OK | MB_ICONERROR);
+
+ sfree(request);
+ sfree(response);
+ } else {
+ if (add234(rsakeys, rkey) != rkey)
+ sfree(rkey); /* already present, don't waste RAM */
+ }
+ } else {
+ if (already_running) {
+ unsigned char *request, *response;
+ void *vresponse;
+ int reqlen, alglen, clen, keybloblen, resplen, ret;
+ alglen = strlen(skey->alg->name);
+ clen = strlen(skey->comment);
+
+ keybloblen = skey->alg->openssh_fmtkey(skey->data, NULL, 0);
+
+ reqlen = 4 + 1 + /* length, message type */
+ 4 + alglen + /* algorithm name */
+ keybloblen + /* key data */
+ 4 + clen /* comment */
+ ;
+
+ request = snewn(reqlen, unsigned char);
+
+ request[4] = SSH2_AGENTC_ADD_IDENTITY;
+ reqlen = 5;
+ PUT_32BIT(request + reqlen, alglen);
+ reqlen += 4;
+ memcpy(request + reqlen, skey->alg->name, alglen);
+ reqlen += alglen;
+ reqlen += skey->alg->openssh_fmtkey(skey->data,
+ request + reqlen,
+ keybloblen);
+ PUT_32BIT(request + reqlen, clen);
+ memcpy(request + reqlen + 4, skey->comment, clen);
+ reqlen += clen + 4;
+ PUT_32BIT(request, reqlen - 4);
+
+ ret = agent_query(request, reqlen, &vresponse, &resplen,
+ NULL, NULL);
+ assert(ret == 1);
+ response = vresponse;
+ if (resplen < 5 || response[4] != SSH_AGENT_SUCCESS)
+ MessageBox(NULL, "The already running Pageant "
+ "refused to add the key.", APPNAME,
+ MB_OK | MB_ICONERROR);
+
+ sfree(request);
+ sfree(response);
+ } else {
+ if (add234(ssh2keys, skey) != skey) {
+ skey->alg->freekey(skey->data);
+ sfree(skey); /* already present, don't waste RAM */
+ }
+ }
+ }
+}
+
+/*
+ * Create an SSH-1 key list in a malloc'ed buffer; return its
+ * length.
+ */
+static void *make_keylist1(int *length)
+{
+ int i, nkeys, len;
+ struct RSAKey *key;
+ unsigned char *blob, *p, *ret;
+ int bloblen;
+
+ /*
+ * Count up the number and length of keys we hold.
+ */
+ len = 4;
+ nkeys = 0;
+ for (i = 0; NULL != (key = index234(rsakeys, i)); i++) {
+ nkeys++;
+ blob = rsa_public_blob(key, &bloblen);
+ len += bloblen;
+ sfree(blob);
+ len += 4 + strlen(key->comment);
+ }
+
+ /* Allocate the buffer. */
+ p = ret = snewn(len, unsigned char);
+ if (length) *length = len;
+
+ PUT_32BIT(p, nkeys);
+ p += 4;
+ for (i = 0; NULL != (key = index234(rsakeys, i)); i++) {
+ blob = rsa_public_blob(key, &bloblen);
+ memcpy(p, blob, bloblen);
+ p += bloblen;
+ sfree(blob);
+ PUT_32BIT(p, strlen(key->comment));
+ memcpy(p + 4, key->comment, strlen(key->comment));
+ p += 4 + strlen(key->comment);
+ }
+
+ assert(p - ret == len);
+ return ret;
+}
+
+/*
+ * Create an SSH-2 key list in a malloc'ed buffer; return its
+ * length.
+ */
+static void *make_keylist2(int *length)
+{
+ struct ssh2_userkey *key;
+ int i, len, nkeys;
+ unsigned char *blob, *p, *ret;
+ int bloblen;
+
+ /*
+ * Count up the number and length of keys we hold.
+ */
+ len = 4;
+ nkeys = 0;
+ for (i = 0; NULL != (key = index234(ssh2keys, i)); i++) {
+ nkeys++;
+ len += 4; /* length field */
+ blob = key->alg->public_blob(key->data, &bloblen);
+ len += bloblen;
+ sfree(blob);
+ len += 4 + strlen(key->comment);
+ }
+
+ /* Allocate the buffer. */
+ p = ret = snewn(len, unsigned char);
+ if (length) *length = len;
+
+ /*
+ * Packet header is the obvious five bytes, plus four
+ * bytes for the key count.
+ */
+ PUT_32BIT(p, nkeys);
+ p += 4;
+ for (i = 0; NULL != (key = index234(ssh2keys, i)); i++) {
+ blob = key->alg->public_blob(key->data, &bloblen);
+ PUT_32BIT(p, bloblen);
+ p += 4;
+ memcpy(p, blob, bloblen);
+ p += bloblen;
+ sfree(blob);
+ PUT_32BIT(p, strlen(key->comment));
+ memcpy(p + 4, key->comment, strlen(key->comment));
+ p += 4 + strlen(key->comment);
+ }
+
+ assert(p - ret == len);
+ return ret;
+}
+
+/*
+ * Acquire a keylist1 from the primary Pageant; this means either
+ * calling make_keylist1 (if that's us) or sending a message to the
+ * primary Pageant (if it's not).
+ */
+static void *get_keylist1(int *length)
+{
+ void *ret;
+
+ if (already_running) {
+ unsigned char request[5], *response;
+ void *vresponse;
+ int resplen, retval;
+ request[4] = SSH1_AGENTC_REQUEST_RSA_IDENTITIES;
+ 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)
+ return NULL;
+
+ ret = snewn(resplen-5, unsigned char);
+ memcpy(ret, response+5, resplen-5);
+ sfree(response);
+
+ if (length)
+ *length = resplen-5;
+ } else {
+ ret = make_keylist1(length);
+ }
+ return ret;
+}
+
+/*
+ * Acquire a keylist2 from the primary Pageant; this means either
+ * calling make_keylist2 (if that's us) or sending a message to the
+ * primary Pageant (if it's not).
+ */
+static void *get_keylist2(int *length)
+{
+ void *ret;
+
+ if (already_running) {
+ unsigned char request[5], *response;
+ void *vresponse;
+ int resplen, retval;
+
+ request[4] = SSH2_AGENTC_REQUEST_IDENTITIES;
+ 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)
+ return NULL;
+
+ ret = snewn(resplen-5, unsigned char);
+ memcpy(ret, response+5, resplen-5);
+ sfree(response);
+
+ if (length)
+ *length = resplen-5;
+ } else {
+ ret = make_keylist2(length);
+ }
+ return ret;
+}
+
+/*
+ * This is the main agent function that answers messages.
+ */
+static void answer_msg(void *msg)
+{
+ unsigned char *p = msg;
+ unsigned char *ret = msg;
+ unsigned char *msgend;
+ int type;
+
+ /*
+ * Get the message length.
+ */
+ msgend = p + 4 + GET_32BIT(p);
+
+ /*
+ * Get the message type.
+ */
+ if (msgend < p+5)
+ goto failure;
+ type = p[4];
+
+ p += 5;
+ switch (type) {
+ case SSH1_AGENTC_REQUEST_RSA_IDENTITIES:
+ /*
+ * Reply with SSH1_AGENT_RSA_IDENTITIES_ANSWER.
+ */
+ {
+ int len;
+ void *keylist;
+
+ ret[4] = SSH1_AGENT_RSA_IDENTITIES_ANSWER;
+ keylist = make_keylist1(&len);
+ if (len + 5 > AGENT_MAX_MSGLEN) {
+ sfree(keylist);
+ goto failure;
+ }
+ PUT_32BIT(ret, len + 1);
+ memcpy(ret + 5, keylist, len);
+ sfree(keylist);
+ }
+ break;
+ case SSH2_AGENTC_REQUEST_IDENTITIES:
+ /*
+ * Reply with SSH2_AGENT_IDENTITIES_ANSWER.
+ */
+ {
+ int len;
+ void *keylist;
+
+ ret[4] = SSH2_AGENT_IDENTITIES_ANSWER;
+ keylist = make_keylist2(&len);
+ if (len + 5 > AGENT_MAX_MSGLEN) {
+ sfree(keylist);
+ goto failure;
+ }
+ PUT_32BIT(ret, len + 1);
+ memcpy(ret + 5, keylist, len);
+ sfree(keylist);
+ }
+ break;
+ case SSH1_AGENTC_RSA_CHALLENGE:
+ /*
+ * Reply with either SSH1_AGENT_RSA_RESPONSE or
+ * SSH_AGENT_FAILURE, depending on whether we have that key
+ * or not.
+ */
+ {
+ struct RSAKey reqkey, *key;
+ Bignum challenge, response;
+ unsigned char response_source[48], response_md5[16];
+ struct MD5Context md5c;
+ int i, len;
+
+ p += 4;
+ 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)
+ goto failure;
+ p += i;
+ i = ssh1_read_bignum(p, msgend - p, &challenge);
+ if (i < 0)
+ goto failure;
+ p += i;
+ if (msgend < p+16) {
+ freebn(reqkey.exponent);
+ freebn(reqkey.modulus);
+ freebn(challenge);
+ goto failure;
+ }
+ memcpy(response_source + 32, p, 16);
+ p += 16;
+ if (msgend < p+4 ||
+ GET_32BIT(p) != 1 ||
+ (key = find234(rsakeys, &reqkey, NULL)) == NULL) {
+ freebn(reqkey.exponent);
+ freebn(reqkey.modulus);
+ freebn(challenge);
+ goto failure;
+ }
+ response = rsadecrypt(challenge, key);
+ for (i = 0; i < 32; i++)
+ 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 */
+ 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. */
+
+ /*
+ * Packet is the obvious five byte header, plus sixteen
+ * bytes of MD5.
+ */
+ len = 5 + 16;
+ PUT_32BIT(ret, len - 4);
+ ret[4] = SSH1_AGENT_RSA_RESPONSE;
+ memcpy(ret + 5, response_md5, 16);
+ }
+ break;
+ case SSH2_AGENTC_SIGN_REQUEST:
+ /*
+ * Reply with either SSH2_AGENT_SIGN_RESPONSE or
+ * SSH_AGENT_FAILURE, depending on whether we have that key
+ * or not.
+ */
+ {
+ 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);
+ 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);
+ p += 4;
+ if (msgend < p+datalen)
+ goto failure;
+ data = p;
+ key = find234(ssh2keys, &b, cmpkeys_ssh2_asymm);
+ if (!key)
+ goto failure;
+ signature = key->alg->sign(key->data, data, datalen, &siglen);
+ 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);
+ sfree(signature);
+ }
+ break;
+ case SSH1_AGENTC_ADD_RSA_IDENTITY:
+ /*
+ * Add to the list and return SSH_AGENT_SUCCESS, or
+ * SSH_AGENT_FAILURE if the key was malformed.
+ */
+ {
+ struct RSAKey *key;
+ char *comment;
+ int n, commentlen;
+
+ key = snew(struct RSAKey);
+ memset(key, 0, sizeof(struct RSAKey));
+
+ n = makekey(p, msgend - p, key, NULL, 1);
+ if (n < 0) {
+ freersakey(key);
+ sfree(key);
+ goto failure;
+ }
+ p += n;
+
+ n = makeprivate(p, msgend - p, key);
+ if (n < 0) {
+ freersakey(key);
+ sfree(key);
+ goto failure;
+ }
+ p += n;
+
+ n = ssh1_read_bignum(p, msgend - p, &key->iqmp); /* p^-1 mod q */
+ if (n < 0) {
+ freersakey(key);
+ sfree(key);
+ goto failure;
+ }
+ p += n;
+
+ n = ssh1_read_bignum(p, msgend - p, &key->p); /* p */
+ if (n < 0) {
+ freersakey(key);
+ sfree(key);
+ goto failure;
+ }
+ p += n;
+
+ n = ssh1_read_bignum(p, msgend - p, &key->q); /* q */
+ if (n < 0) {
+ freersakey(key);
+ sfree(key);
+ goto failure;
+ }
+ p += n;
+
+ if (msgend < p+4) {
+ freersakey(key);
+ sfree(key);
+ goto failure;
+ }
+ commentlen = GET_32BIT(p);
+
+ if (msgend < p+commentlen) {
+ freersakey(key);
+ sfree(key);
+ goto failure;
+ }
+
+ comment = snewn(commentlen+1, char);
+ if (comment) {
+ memcpy(comment, p + 4, commentlen);
+ comment[commentlen] = '\0';
+ key->comment = comment;
+ }
+ PUT_32BIT(ret, 1);
+ ret[4] = SSH_AGENT_FAILURE;
+ if (add234(rsakeys, key) == key) {
+ keylist_update();
+ ret[4] = SSH_AGENT_SUCCESS;
+ } else {
+ freersakey(key);
+ sfree(key);
+ }
+ }
+ break;
+ case SSH2_AGENTC_ADD_IDENTITY:
+ /*
+ * Add to the list and return SSH_AGENT_SUCCESS, or
+ * SSH_AGENT_FAILURE if the key was malformed.
+ */
+ {
+ struct ssh2_userkey *key;
+ char *comment, *alg;
+ int alglen, commlen;
+ int bloblen;
+
+
+ if (msgend < p+4)
+ goto failure;
+ alglen = GET_32BIT(p);
+ p += 4;
+ if (msgend < p+alglen)
+ goto failure;
+ alg = p;
+ p += alglen;
+
+ 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))
+ key->alg = &ssh_dss;
+ else {
+ sfree(key);
+ goto failure;
+ }
+
+ bloblen = msgend - p;
+ key->data = key->alg->openssh_createkey(&p, &bloblen);
+ if (!key->data) {
+ sfree(key);
+ goto failure;
+ }
+
+ /*
+ * p has been advanced by openssh_createkey, but
+ * certainly not _beyond_ the end of the buffer.
+ */
+ assert(p <= msgend);
+
+ if (msgend < p+4) {
+ key->alg->freekey(key->data);
+ sfree(key);
+ goto failure;
+ }
+ commlen = GET_32BIT(p);
+ p += 4;
+
+ if (msgend < p+commlen) {
+ key->alg->freekey(key->data);
+ sfree(key);
+ goto failure;
+ }
+ comment = snewn(commlen + 1, char);
+ if (comment) {
+ memcpy(comment, p, commlen);
+ comment[commlen] = '\0';
+ }
+ key->comment = comment;
+
+ PUT_32BIT(ret, 1);
+ ret[4] = SSH_AGENT_FAILURE;
+ if (add234(ssh2keys, key) == key) {
+ keylist_update();
+ ret[4] = SSH_AGENT_SUCCESS;
+ } else {
+ key->alg->freekey(key->data);
+ sfree(key->comment);
+ sfree(key);
+ }
+ }
+ break;
+ case SSH1_AGENTC_REMOVE_RSA_IDENTITY:
+ /*
+ * Remove from the list and return SSH_AGENT_SUCCESS, or
+ * perhaps SSH_AGENT_FAILURE if it wasn't in the list to
+ * start with.
+ */
+ {
+ struct RSAKey reqkey, *key;
+ int n;
+
+ n = makekey(p, msgend - p, &reqkey, NULL, 0);
+ if (n < 0)
+ goto failure;
+
+ key = find234(rsakeys, &reqkey, NULL);
+ freebn(reqkey.exponent);
+ freebn(reqkey.modulus);
+ PUT_32BIT(ret, 1);
+ ret[4] = SSH_AGENT_FAILURE;
+ if (key) {
+ del234(rsakeys, key);
+ keylist_update();
+ freersakey(key);
+ sfree(key);
+ ret[4] = SSH_AGENT_SUCCESS;
+ }
+ }
+ break;
+ case SSH2_AGENTC_REMOVE_IDENTITY:
+ /*
+ * Remove from the list and return SSH_AGENT_SUCCESS, or
+ * perhaps SSH_AGENT_FAILURE if it wasn't in the list to
+ * start with.
+ */
+ {
+ struct ssh2_userkey *key;
+ struct blob b;
+
+ if (msgend < p+4)
+ goto failure;
+ b.len = GET_32BIT(p);
+ p += 4;
+
+ if (msgend < p+b.len)
+ goto failure;
+ b.blob = p;
+ p += b.len;
+
+ key = find234(ssh2keys, &b, cmpkeys_ssh2_asymm);
+ if (!key)
+ goto failure;
+
+ PUT_32BIT(ret, 1);
+ ret[4] = SSH_AGENT_FAILURE;
+ if (key) {
+ del234(ssh2keys, key);
+ keylist_update();
+ key->alg->freekey(key->data);
+ sfree(key);
+ ret[4] = SSH_AGENT_SUCCESS;
+ }
+ }
+ break;
+ case SSH1_AGENTC_REMOVE_ALL_RSA_IDENTITIES:
+ /*
+ * Remove all SSH-1 keys. Always returns success.
+ */
+ {
+ struct RSAKey *rkey;
+
+ while ((rkey = index234(rsakeys, 0)) != NULL) {
+ del234(rsakeys, rkey);
+ freersakey(rkey);
+ sfree(rkey);
+ }
+ keylist_update();
+
+ PUT_32BIT(ret, 1);
+ ret[4] = SSH_AGENT_SUCCESS;
+ }
+ break;
+ case SSH2_AGENTC_REMOVE_ALL_IDENTITIES:
+ /*
+ * Remove all SSH-2 keys. Always returns success.
+ */
+ {
+ struct ssh2_userkey *skey;
+
+ while ((skey = index234(ssh2keys, 0)) != NULL) {
+ del234(ssh2keys, skey);
+ skey->alg->freekey(skey->data);
+ sfree(skey);
+ }
+ keylist_update();
+
+ PUT_32BIT(ret, 1);
+ ret[4] = SSH_AGENT_SUCCESS;
+ }
+ break;
+ default:
+ failure:
+ /*
+ * Unrecognised message. Return SSH_AGENT_FAILURE.
+ */
+ PUT_32BIT(ret, 1);
+ ret[4] = SSH_AGENT_FAILURE;
+ break;
+ }
+}
+
+/*
+ * Key comparison function for the 2-3-4 tree of RSA keys.
+ */
+static int cmpkeys_rsa(void *av, void *bv)
+{
+ struct RSAKey *a = (struct RSAKey *) av;
+ struct RSAKey *b = (struct RSAKey *) bv;
+ Bignum am, bm;
+ int alen, blen;
+
+ am = a->modulus;
+ bm = b->modulus;
+ /*
+ * Compare by length of moduli.
+ */
+ alen = bignum_bitcount(am);
+ blen = bignum_bitcount(bm);
+ if (alen > blen)
+ return +1;
+ else if (alen < blen)
+ return -1;
+ /*
+ * Now compare by moduli themselves.
+ */
+ alen = (alen + 7) / 8; /* byte count */
+ while (alen-- > 0) {
+ int abyte, bbyte;
+ abyte = bignum_byte(am, alen);
+ bbyte = bignum_byte(bm, alen);
+ if (abyte > bbyte)
+ return +1;
+ else if (abyte < bbyte)
+ return -1;
+ }
+ /*
+ * Give up.
+ */
+ return 0;
+}
+
+/*
+ * Key comparison function for the 2-3-4 tree of SSH-2 keys.
+ */
+static int cmpkeys_ssh2(void *av, void *bv)
+{
+ struct ssh2_userkey *a = (struct ssh2_userkey *) av;
+ struct ssh2_userkey *b = (struct ssh2_userkey *) bv;
+ int i;
+ int alen, blen;
+ unsigned char *ablob, *bblob;
+ int c;
+
+ /*
+ * Compare purely by public blob.
+ */
+ ablob = a->alg->public_blob(a->data, &alen);
+ bblob = b->alg->public_blob(b->data, &blen);
+
+ c = 0;
+ for (i = 0; i < alen && i < blen; i++) {
+ if (ablob[i] < bblob[i]) {
+ c = -1;
+ break;
+ } else if (ablob[i] > bblob[i]) {
+ c = +1;
+ break;
+ }
+ }
+ if (c == 0 && i < alen)
+ c = +1; /* a is longer */
+ if (c == 0 && i < blen)
+ c = -1; /* a is longer */
+
+ sfree(ablob);
+ sfree(bblob);
+
+ return c;
+}
+
+/*
+ * Key comparison function for looking up a blob in the 2-3-4 tree
+ * of SSH-2 keys.
+ */
+static int cmpkeys_ssh2_asymm(void *av, void *bv)
+{
+ struct blob *a = (struct blob *) av;
+ struct ssh2_userkey *b = (struct ssh2_userkey *) bv;
+ int i;
+ int alen, blen;
+ unsigned char *ablob, *bblob;
+ int c;
+
+ /*
+ * Compare purely by public blob.
+ */
+ ablob = a->blob;
+ alen = a->len;
+ bblob = b->alg->public_blob(b->data, &blen);
+
+ c = 0;
+ for (i = 0; i < alen && i < blen; i++) {
+ if (ablob[i] < bblob[i]) {
+ c = -1;
+ break;
+ } else if (ablob[i] > bblob[i]) {
+ c = +1;
+ break;
+ }
+ }
+ if (c == 0 && i < alen)
+ c = +1; /* a is longer */
+ if (c == 0 && i < blen)
+ c = -1; /* a is longer */
+
+ sfree(bblob);
+
+ return c;
+}
+
+/*
+ * Prompt for a key file to add, and add it.
+ */
+static void prompt_add_keyfile(void)
+{
+ OPENFILENAME of;
+ char *filelist = snewn(8192, char);
+
+ if (!keypath) keypath = filereq_new();
+ memset(&of, 0, sizeof(of));
+ of.hwndOwner = hwnd;
+ of.lpstrFilter = FILTER_KEY_FILES;
+ of.lpstrCustomFilter = NULL;
+ of.nFilterIndex = 1;
+ of.lpstrFile = filelist;
+ *filelist = '\0';
+ 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)
+ /* Only one filename returned? */
+ add_keyfile(filename_from_str(filelist));
+ 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));
+ sfree(filename);
+ filewalker += strlen(filewalker) + 1;
+ }
+ }
+
+ keylist_update();
+ forget_passphrases();
+ }
+ sfree(filelist);
+}
+
+/*
+ * Dialog-box function for the key list box.
+ */
+static int CALLBACK KeyListProc(HWND hwnd, UINT msg,
+ WPARAM wParam, LPARAM lParam)
+{
+ struct RSAKey *rkey;
+ struct ssh2_userkey *skey;
+
+ switch (msg) {
+ case WM_INITDIALOG:
+ /*
+ * 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);
+ }
+
+ if (has_help())
+ SetWindowLongPtr(hwnd, GWL_EXSTYLE,
+ GetWindowLongPtr(hwnd, GWL_EXSTYLE) |
+ WS_EX_CONTEXTHELP);
+ else {
+ HWND item = GetDlgItem(hwnd, 103); /* the Help button */
+ if (item)
+ DestroyWindow(item);
+ }
+
+ keylist = hwnd;
+ {
+ static int tabs[] = { 35, 60, 210 };
+ SendDlgItemMessage(hwnd, 100, LB_SETTABSTOPS,
+ sizeof(tabs) / sizeof(*tabs),
+ (LPARAM) tabs);
+ }
+ keylist_update();
+ return 0;
+ case WM_COMMAND:
+ switch (LOWORD(wParam)) {
+ case IDOK:
+ case IDCANCEL:
+ keylist = NULL;
+ DestroyWindow(hwnd);
+ return 0;
+ case 101: /* add key */
+ if (HIWORD(wParam) == BN_CLICKED ||
+ HIWORD(wParam) == BN_DOUBLECLICKED) {
+ if (passphrase_box) {
+ MessageBeep(MB_ICONERROR);
+ SetForegroundWindow(passphrase_box);
+ break;
+ }
+ prompt_add_keyfile();
+ }
+ return 0;
+ case 102: /* remove key */
+ if (HIWORD(wParam) == BN_CLICKED ||
+ HIWORD(wParam) == BN_DOUBLECLICKED) {
+ int i;
+ int rCount, sCount;
+ int *selectedArray;
+
+ /* our counter within the array of selected items */
+ int itemNum;
+
+ /* get the number of items selected in the list */
+ int numSelected =
+ SendDlgItemMessage(hwnd, 100, LB_GETSELCOUNT, 0, 0);
+
+ /* none selected? that was silly */
+ if (numSelected == 0) {
+ MessageBeep(0);
+ break;
+ }
+
+ /* get item indices in an array */
+ selectedArray = snewn(numSelected, int);
+ SendDlgItemMessage(hwnd, 100, LB_GETSELITEMS,
+ numSelected, (WPARAM)selectedArray);
+
+ itemNum = numSelected - 1;
+ rCount = count234(rsakeys);
+ sCount = count234(ssh2keys);
+
+ /* 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
+ */
+ for (i = sCount - 1; (itemNum >= 0) && (i >= 0); i--) {
+ skey = index234(ssh2keys, i);
+
+ if (selectedArray[itemNum] == rCount + i) {
+ del234(ssh2keys, skey);
+ skey->alg->freekey(skey->data);
+ sfree(skey);
+ itemNum--;
+ }
+ }
+
+ /* do the same for the rsa keys */
+ for (i = rCount - 1; (itemNum >= 0) && (i >= 0); i--) {
+ rkey = index234(rsakeys, i);
+
+ if(selectedArray[itemNum] == i) {
+ del234(rsakeys, rkey);
+ freersakey(rkey);
+ sfree(rkey);
+ itemNum--;
+ }
+ }
+
+ sfree(selectedArray);
+ keylist_update();
+ }
+ return 0;
+ case 103: /* help */
+ if (HIWORD(wParam) == BN_CLICKED ||
+ HIWORD(wParam) == BN_DOUBLECLICKED) {
+ launch_help(hwnd, WINHELP_CTX_pageant_general);
+ }
+ return 0;
+ }
+ return 0;
+ case WM_HELP:
+ {
+ int id = ((LPHELPINFO)lParam)->iCtrlId;
+ char *topic = NULL;
+ switch (id) {
+ case 100: topic = WINHELP_CTX_pageant_keylist; break;
+ case 101: topic = WINHELP_CTX_pageant_addkey; break;
+ case 102: topic = WINHELP_CTX_pageant_remkey; break;
+ }
+ if (topic) {
+ launch_help(hwnd, topic);
+ } else {
+ MessageBeep(0);
+ }
+ }
+ break;
+ case WM_CLOSE:
+ keylist = NULL;
+ DestroyWindow(hwnd);
+ return 0;
+ }
+ return 0;
+}
+
+/* Set up a system tray icon */
+static BOOL AddTrayIcon(HWND hwnd)
+{
+ BOOL res;
+ NOTIFYICONDATA tnid;
+ HICON hicon;
+
+#ifdef NIM_SETVERSION
+ tnid.uVersion = 0;
+ res = Shell_NotifyIcon(NIM_SETVERSION, &tnid);
+#endif
+
+ tnid.cbSize = sizeof(NOTIFYICONDATA);
+ 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 authentication agent)");
+
+ res = Shell_NotifyIcon(NIM_ADD, &tnid);
+
+ if (hicon) DestroyIcon(hicon);
+
+ return res;
+}
+
+/* Update the saved-sessions menu. */
+static void update_sessions(void)
+{
+ int num_entries;
+ HKEY hkey;
+ TCHAR buf[MAX_PATH + 1];
+ MENUITEMINFO mii;
+
+ int index_key, index_menu;
+
+ if (!putty_path)
+ return;
+
+ if(ERROR_SUCCESS != RegOpenKey(HKEY_CURRENT_USER, PUTTY_REGKEY, &hkey))
+ return;
+
+ for(num_entries = GetMenuItemCount(session_menu);
+ num_entries > initial_menuitems_count;
+ num_entries--)
+ RemoveMenu(session_menu, 0, MF_BYPOSITION);
+
+ index_key = 0;
+ index_menu = 0;
+
+ while(ERROR_SUCCESS == RegEnumKey(hkey, index_key, buf, MAX_PATH)) {
+ TCHAR session_name[MAX_PATH + 1];
+ unmungestr(buf, session_name, MAX_PATH);
+ if(strcmp(buf, PUTTY_DEFAULT) != 0) {
+ memset(&mii, 0, sizeof(mii));
+ mii.cbSize = sizeof(mii);
+ mii.fMask = MIIM_TYPE | MIIM_STATE | MIIM_ID;
+ mii.fType = MFT_STRING;
+ mii.fState = MFS_ENABLED;
+ mii.wID = (index_menu * 16) + IDM_SESSIONS_BASE;
+ mii.dwTypeData = session_name;
+ InsertMenuItem(session_menu, index_menu, TRUE, &mii);
+ index_menu++;
+ }
+ index_key++;
+ }
+
+ RegCloseKey(hkey);
+
+ if(index_menu == 0) {
+ mii.cbSize = sizeof(mii);
+ mii.fMask = MIIM_TYPE | MIIM_STATE;
+ mii.fType = MFT_STRING;
+ mii.fState = MFS_GRAYED;
+ mii.dwTypeData = _T("(No sessions)");
+ InsertMenuItem(session_menu, index_menu, TRUE, &mii);
+ }
+}
+
+static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
+ WPARAM wParam, LPARAM lParam)
+{
+ int ret;
+ static int menuinprogress;
+ static UINT msgTaskbarCreated = 0;
+
+ switch (message) {
+ case WM_CREATE:
+ msgTaskbarCreated = RegisterWindowMessage(_T("TaskbarCreated"));
+ break;
+ default:
+ if (message==msgTaskbarCreated) {
+ /*
+ * Explorer has been restarted, so the tray icon will
+ * have been lost.
+ */
+ AddTrayIcon(hwnd);
+ }
+ break;
+
+ case WM_SYSTRAY:
+ if (lParam == WM_RBUTTONUP) {
+ POINT cursorpos;
+ GetCursorPos(&cursorpos);
+ PostMessage(hwnd, WM_SYSTRAY2, cursorpos.x, cursorpos.y);
+ } else if (lParam == WM_LBUTTONDBLCLK) {
+ /* Run the default menu item. */
+ UINT menuitem = GetMenuDefaultItem(systray_menu, FALSE, 0);
+ if (menuitem != -1)
+ PostMessage(hwnd, WM_COMMAND, menuitem, 0);
+ }
+ break;
+ case WM_SYSTRAY2:
+ if (!menuinprogress) {
+ menuinprogress = 1;
+ update_sessions();
+ SetForegroundWindow(hwnd);
+ ret = TrackPopupMenu(systray_menu,
+ TPM_RIGHTALIGN | TPM_BOTTOMALIGN |
+ TPM_RIGHTBUTTON,
+ wParam, lParam, 0, hwnd, NULL);
+ menuinprogress = 0;
+ }
+ break;
+ case WM_COMMAND:
+ case WM_SYSCOMMAND:
+ switch (wParam & ~0xF) { /* low 4 bits reserved to Windows */
+ case IDM_PUTTY:
+ if((int)ShellExecute(hwnd, NULL, putty_path, _T(""), _T(""),
+ SW_SHOW) <= 32) {
+ MessageBox(NULL, "Unable to execute PuTTY!",
+ "Error", MB_OK | MB_ICONERROR);
+ }
+ break;
+ case IDM_CLOSE:
+ if (passphrase_box)
+ SendMessage(passphrase_box, WM_CLOSE, 0, 0);
+ SendMessage(hwnd, WM_CLOSE, 0, 0);
+ break;
+ case IDM_VIEWKEYS:
+ if (!keylist) {
+ keylist = CreateDialog(hinst, MAKEINTRESOURCE(211),
+ NULL, KeyListProc);
+ ShowWindow(keylist, SW_SHOWNORMAL);
+ }
+ /*
+ * Sometimes the window comes up minimised / hidden for
+ * no obvious reason. Prevent this. This also brings it
+ * to the front if it's already present (the user
+ * selected View Keys because they wanted to _see_ the
+ * thing).
+ */
+ SetForegroundWindow(keylist);
+ SetWindowPos(keylist, HWND_TOP, 0, 0, 0, 0,
+ SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
+ break;
+ case IDM_ADDKEY:
+ if (passphrase_box) {
+ MessageBeep(MB_ICONERROR);
+ SetForegroundWindow(passphrase_box);
+ break;
+ }
+ prompt_add_keyfile();
+ break;
+ case IDM_ABOUT:
+ if (!aboutbox) {
+ aboutbox = CreateDialog(hinst, MAKEINTRESOURCE(213),
+ NULL, AboutProc);
+ ShowWindow(aboutbox, SW_SHOWNORMAL);
+ /*
+ * Sometimes the window comes up minimised / hidden
+ * for no obvious reason. Prevent this.
+ */
+ SetForegroundWindow(aboutbox);
+ SetWindowPos(aboutbox, HWND_TOP, 0, 0, 0, 0,
+ SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
+ }
+ break;
+ case IDM_HELP:
+ launch_help(hwnd, WINHELP_CTX_pageant_general);
+ break;
+ default:
+ {
+ if(wParam >= IDM_SESSIONS_BASE && wParam <= IDM_SESSIONS_MAX) {
+ MENUITEMINFO mii;
+ TCHAR buf[MAX_PATH + 1];
+ TCHAR param[MAX_PATH + 1];
+ memset(&mii, 0, sizeof(mii));
+ mii.cbSize = sizeof(mii);
+ mii.fMask = MIIM_TYPE;
+ mii.cch = MAX_PATH;
+ mii.dwTypeData = buf;
+ GetMenuItemInfo(session_menu, wParam, FALSE, &mii);
+ strcpy(param, "@");
+ strcat(param, mii.dwTypeData);
+ if((int)ShellExecute(hwnd, NULL, putty_path, param,
+ _T(""), SW_SHOW) <= 32) {
+ MessageBox(NULL, "Unable to execute PuTTY!", "Error",
+ MB_OK | MB_ICONERROR);
+ }
+ }
+ }
+ break;
+ }
+ break;
+ case WM_DESTROY:
+ quit_help(hwnd);
+ PostQuitMessage(0);
+ return 0;
+ case WM_COPYDATA:
+ {
+ COPYDATASTRUCT *cds;
+ char *mapname;
+ void *p;
+ HANDLE filemap;
+#ifndef NO_SECURITY
+ PSID mapowner, ourself;
+ PSECURITY_DESCRIPTOR psd1 = NULL, psd2 = NULL;
+#endif
+ int ret = 0;
+
+ cds = (COPYDATASTRUCT *) lParam;
+ if (cds->dwData != AGENT_COPYDATA_ID)
+ return 0; /* not our message, mate */
+ mapname = (char *) cds->lpData;
+ if (mapname[cds->cbData - 1] != '\0')
+ return 0; /* failure to be ASCIZ! */
+#ifdef DEBUG_IPC
+ debug(("mapname is :%s:\n", mapname));
+#endif
+ filemap = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, mapname);
+#ifdef DEBUG_IPC
+ debug(("filemap is %p\n", filemap));
+#endif
+ if (filemap != NULL && filemap != INVALID_HANDLE_VALUE) {
+#ifndef NO_SECURITY
+ int rc;
+ if (has_security) {
+ if ((ourself = get_user_sid()) == NULL) {
+#ifdef DEBUG_IPC
+ debug(("couldn't get user SID\n"));
+#endif
+ return 0;
+ }
+
+ if ((rc = p_GetSecurityInfo(filemap, SE_KERNEL_OBJECT,
+ OWNER_SECURITY_INFORMATION,
+ &mapowner, NULL, NULL, NULL,
+ &psd1) != ERROR_SUCCESS)) {
+#ifdef DEBUG_IPC
+ debug(("couldn't get owner info for filemap: %d\n",
+ rc));
+#endif
+ return 0;
+ }
+#ifdef DEBUG_IPC
+ {
+ LPTSTR ours, theirs;
+ ConvertSidToStringSid(mapowner, &theirs);
+ ConvertSidToStringSid(ourself, &ours);
+ debug(("got both sids: ours=%s theirs=%s\n",
+ ours, theirs));
+ LocalFree(ours);
+ LocalFree(theirs);
+ }
+#endif
+ if (!EqualSid(mapowner, ourself))
+ return 0; /* security ID mismatch! */
+#ifdef DEBUG_IPC
+ debug(("security stuff matched\n"));
+#endif
+ LocalFree(psd1);
+ LocalFree(psd2);
+ } else {
+#ifdef DEBUG_IPC
+ debug(("security APIs not present\n"));
+#endif
+ }
+#endif
+ p = MapViewOfFile(filemap, FILE_MAP_WRITE, 0, 0, 0);
+#ifdef DEBUG_IPC
+ debug(("p is %p\n", p));
+ {
+ int i;
+ for (i = 0; i < 5; i++)
+ debug(("p[%d]=%02x\n", i,
+ ((unsigned char *) p)[i]));
+ }
+#endif
+ answer_msg(p);
+ ret = 1;
+ UnmapViewOfFile(p);
+ }
+ CloseHandle(filemap);
+ return ret;
+ }
+ }
+
+ return DefWindowProc(hwnd, message, wParam, lParam);
+}
+
+/*
+ * Fork and Exec the command in cmdline. [DBW]
+ */
+void spawn_cmd(char *cmdline, char * args, int show)
+{
+ if (ShellExecute(NULL, _T("open"), cmdline,
+ args, NULL, show) <= (HINSTANCE) 32) {
+ char *msg;
+ msg = dupprintf("Failed to run \"%.100s\", Error: %d", cmdline,
+ (int)GetLastError());
+ MessageBox(NULL, msg, APPNAME, MB_OK | MB_ICONEXCLAMATION);
+ sfree(msg);
+ }
+}
+
+/*
+ * This is a can't-happen stub, since Pageant never makes
+ * asynchronous agent requests.
+ */
+void agent_schedule_callback(void (*callback)(void *, void *, int),
+ void *callback_ctx, void *data, int len)
+{
+ assert(!"We shouldn't get here");
+}
+
+void cleanup_exit(int code)
+{
+ shutdown_help();
+ exit(code);
+}
+
+int flags = FLAG_SYNCAGENT;
+
+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;
+
+ hinst = inst;
+ hwnd = NULL;
+
+ /*
+ * Determine whether we're an NT system (should have security
+ * APIs) or a non-NT system (don't do security).
+ */
+ if (!init_winver())
+ {
+ modalfatalbox("Windows refuses to report a version");
+ }
+ if (osVersion.dwPlatformId == VER_PLATFORM_WIN32_NT) {
+ has_security = TRUE;
+ } else
+ has_security = FALSE;
+
+ if (has_security) {
+#ifndef NO_SECURITY
+ /*
+ * Attempt to get the security API we need.
+ */
+ if (!init_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;
+ }
+#else
+ MessageBox(NULL,
+ "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();
+
+ /*
+ * Look for the PuTTY binary (we will enable the saved session
+ * submenu if we find it).
+ */
+ {
+ char b[2048], *p, *q, *r;
+ FILE *fp;
+ GetModuleFileName(NULL, b, sizeof(b) - 16);
+ r = b;
+ p = strrchr(b, '\\');
+ if (p && p >= r) r = p+1;
+ q = strrchr(b, ':');
+ if (q && q >= r) r = q+1;
+ strcpy(r, "putty.exe");
+ if ( (fp = fopen(b, "r")) != NULL) {
+ putty_path = dupstr(b);
+ fclose(fp);
+ } else
+ putty_path = NULL;
+ }
+
+ /*
+ * Find out if Pageant is already running.
+ */
+ already_running = agent_exists();
+
+ /*
+ * Initialise storage for RSA keys.
+ */
+ if (!already_running) {
+ rsakeys = newtree234(cmpkeys_rsa);
+ ssh2keys = newtree234(cmpkeys_ssh2);
+ }
+
+ /*
+ * Initialise storage for short-term passphrase cache.
+ */
+ passphrases = newtree234(NULL);
+
+ /*
+ * Process the command line and add keys as listed on it.
+ */
+ 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
+ * command to be spawned.
+ */
+ if (i < argc-1)
+ command = argstart[i+1];
+ else
+ command = "";
+ break;
+ } else {
+ add_keyfile(filename_from_str(argv[i]));
+ added_keys = TRUE;
+ }
+ }
+
+ /*
+ * Forget any passphrase that we retained while going over
+ * command line keyfiles.
+ */
+ forget_passphrases();
+
+ if (command) {
+ char *args;
+ if (command[0] == '"')
+ args = strchr(++command, '"');
+ else
+ args = strchr(command, ' ');
+ if (args) {
+ *args++ = 0;
+ while(*args && isspace(*args)) args++;
+ }
+ spawn_cmd(command, args, show);
+ }
+
+ /*
+ * If Pageant was already running, we leave now. If we haven't
+ * even taken any auxiliary action (spawned a command or added
+ * keys), complain.
+ */
+ 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;
+ wndclass.lpfnWndProc = WndProc;
+ wndclass.cbClsExtra = 0;
+ wndclass.cbWndExtra = 0;
+ wndclass.hInstance = inst;
+ wndclass.hIcon = LoadIcon(inst, MAKEINTRESOURCE(IDI_MAINICON));
+ wndclass.hCursor = LoadCursor(NULL, IDC_IBEAM);
+ wndclass.hbrBackground = GetStockObject(BLACK_BRUSH);
+ wndclass.lpszMenuName = NULL;
+ wndclass.lpszClassName = APPNAME;
+
+ RegisterClass(&wndclass);
+ }
+
+ keylist = NULL;
+
+ hwnd = CreateWindow(APPNAME, APPNAME,
+ WS_OVERLAPPEDWINDOW | WS_VSCROLL,
+ CW_USEDEFAULT, CW_USEDEFAULT,
+ 100, 100, NULL, NULL, inst, NULL);
+
+ /* Set up a system tray icon */
+ AddTrayIcon(hwnd);
+
+ /* Accelerators used: nsvkxa */
+ systray_menu = CreatePopupMenu();
+ if (putty_path) {
+ session_menu = CreateMenu();
+ AppendMenu(systray_menu, MF_ENABLED, IDM_PUTTY, "&New Session");
+ AppendMenu(systray_menu, MF_POPUP | MF_ENABLED,
+ (UINT) session_menu, "&Saved Sessions");
+ AppendMenu(systray_menu, MF_SEPARATOR, 0, 0);
+ }
+ AppendMenu(systray_menu, MF_ENABLED, IDM_VIEWKEYS,
+ "&View Keys");
+ AppendMenu(systray_menu, MF_ENABLED, IDM_ADDKEY, "Add &Key");
+ AppendMenu(systray_menu, MF_SEPARATOR, 0, 0);
+ if (has_help())
+ AppendMenu(systray_menu, MF_ENABLED, IDM_HELP, "&Help");
+ AppendMenu(systray_menu, MF_ENABLED, IDM_ABOUT, "&About");
+ AppendMenu(systray_menu, MF_SEPARATOR, 0, 0);
+ AppendMenu(systray_menu, MF_ENABLED, IDM_CLOSE, "E&xit");
+ initial_menuitems_count = GetMenuItemCount(session_menu);
+
+ /* Set the default menu item. */
+ SetMenuDefaultItem(systray_menu, IDM_VIEWKEYS, FALSE);
+
+ ShowWindow(hwnd, SW_HIDE);
+
+ /*
+ * Main message loop.
+ */
+ while (GetMessage(&msg, NULL, 0, 0) == 1) {
+ if (!(IsWindow(keylist) && IsDialogMessage(keylist, &msg)) &&
+ !(IsWindow(aboutbox) && IsDialogMessage(aboutbox, &msg))) {
+ TranslateMessage(&msg);
+ DispatchMessage(&msg);
+ }
+ }
+
+ /* Clean up the system tray icon */
+ {
+ NOTIFYICONDATA tnid;
+
+ tnid.cbSize = sizeof(NOTIFYICONDATA);
+ tnid.hWnd = hwnd;
+ tnid.uID = 1;
+
+ Shell_NotifyIcon(NIM_DELETE, &tnid);
+
+ DestroyMenu(systray_menu);
+ }
+
+ if (keypath) filereq_free(keypath);
+
+ if (advapi)
+ FreeLibrary(advapi);
+
+ cleanup_exit(msg.wParam);
+ return msg.wParam; /* just in case optimiser complains */
+}
--- /dev/null
+/*
+ * Pageant client code.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "putty.h"
+
+#ifndef NO_SECURITY
+#include <aclapi.h>
+#endif
+
+#define AGENT_COPYDATA_ID 0x804e50ba /* random goop */
+#define AGENT_MAX_MSGLEN 8192
+
+int agent_exists(void)
+{
+ HWND hwnd;
+ hwnd = FindWindow("Pageant", "Pageant");
+ if (!hwnd)
+ return FALSE;
+ else
+ return TRUE;
+}
+
+/*
+ * Unfortunately, this asynchronous agent request mechanism doesn't
+ * appear to work terribly well. I'm going to comment it out for
+ * the moment, and see if I can come up with a better one :-/
+ */
+#ifdef WINDOWS_ASYNC_AGENT
+
+struct agent_query_data {
+ COPYDATASTRUCT cds;
+ unsigned char *mapping;
+ HANDLE handle;
+ char *mapname;
+ HWND hwnd;
+ void (*callback)(void *, void *, int);
+ void *callback_ctx;
+};
+
+DWORD WINAPI agent_query_thread(LPVOID param)
+{
+ struct agent_query_data *data = (struct agent_query_data *)param;
+ unsigned char *ret;
+ int id, retlen;
+
+ id = SendMessage(data->hwnd, WM_COPYDATA, (WPARAM) NULL,
+ (LPARAM) &data->cds);
+ ret = NULL;
+ if (id > 0) {
+ retlen = 4 + GET_32BIT(data->mapping);
+ ret = snewn(retlen, unsigned char);
+ if (ret) {
+ memcpy(ret, data->mapping, retlen);
+ }
+ }
+ if (!ret)
+ retlen = 0;
+ UnmapViewOfFile(data->mapping);
+ CloseHandle(data->handle);
+ sfree(data->mapname);
+
+ agent_schedule_callback(data->callback, data->callback_ctx, ret, retlen);
+
+ 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;
+ HANDLE filemap;
+ unsigned char *p, *ret;
+ int id, retlen;
+ COPYDATASTRUCT cds;
+ SECURITY_ATTRIBUTES sa, *psa;
+ PSECURITY_DESCRIPTOR psd = NULL;
+ PSID usersid = NULL;
+
+ *out = NULL;
+ *outlen = 0;
+
+ hwnd = FindWindow("Pageant", "Pageant");
+ if (!hwnd)
+ return 1; /* *out == NULL, so failure */
+ mapname = dupprintf("PageantRequest%08x", (unsigned)GetCurrentThreadId());
+
+#ifndef NO_SECURITY
+ if (advapi_initialised || init_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
+ * prompts launched as administrator should still be able to
+ * 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
+ (psd, SECURITY_DESCRIPTOR_REVISION) &&
+ p_SetSecurityDescriptorOwner(psd, usersid, FALSE)) {
+ sa.nLength = sizeof(sa);
+ sa.bInheritHandle = TRUE;
+ sa.lpSecurityDescriptor = psd;
+ psa = &sa;
+ } else {
+ LocalFree(psd);
+ psd = NULL;
+ }
+ }
+ }
+ }
+#endif /* NO_SECURITY */
+
+ filemap = CreateFileMapping(INVALID_HANDLE_VALUE, psa, PAGE_READWRITE,
+ 0, AGENT_MAX_MSGLEN, mapname);
+ if (filemap == NULL || filemap == INVALID_HANDLE_VALUE)
+ 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;
+#ifdef WINDOWS_ASYNC_AGENT
+ if (callback != NULL && !(flags & FLAG_SYNCAGENT)) {
+ /*
+ * We need an asynchronous Pageant request. Since I know of
+ * no way to stop SendMessage from blocking the thread it's
+ * called in, I see no option but to start a fresh thread.
+ * When we're done we'll PostMessage the result back to our
+ * main window, so that the callback is done in the primary
+ * thread to avoid concurrency.
+ */
+ struct agent_query_data *data = snew(struct agent_query_data);
+ DWORD threadid;
+ data->mapping = p;
+ data->handle = filemap;
+ data->mapname = mapname;
+ data->callback = callback;
+ 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(data);
+ }
+#endif
+
+ /*
+ * The user either passed a null callback (indicating that the
+ * query is required to be synchronous) or CreateThread failed.
+ * Either way, we need a synchronous request.
+ */
+ id = SendMessage(hwnd, WM_COPYDATA, (WPARAM) NULL, (LPARAM) &cds);
+ if (id > 0) {
+ retlen = 4 + GET_32BIT(p);
+ ret = snewn(retlen, unsigned char);
+ if (ret) {
+ memcpy(ret, p, retlen);
+ *out = ret;
+ *outlen = retlen;
+ }
+ }
+ UnmapViewOfFile(p);
+ CloseHandle(filemap);
+ if (psd)
+ LocalFree(psd);
+ sfree(usersid);
+ return 1;
+}
--- /dev/null
+/*
+ * PLink - a Windows command-line (stdin/stdout) variant of PuTTY.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <stdarg.h>
+
+#define PUTTY_DO_GLOBALS /* actually _define_ globals */
+#include "putty.h"
+#include "storage.h"
+#include "tree234.h"
+
+#define WM_AGENT_CALLBACK (WM_APP + 4)
+
+struct agent_callback {
+ void (*callback)(void *, void *, int);
+ void *callback_ctx;
+ void *data;
+ int len;
+};
+
+void fatalbox(char *p, ...)
+{
+ va_list ap;
+ fprintf(stderr, "FATAL ERROR: ");
+ va_start(ap, p);
+ vfprintf(stderr, p, ap);
+ va_end(ap);
+ fputc('\n', stderr);
+ if (logctx) {
+ log_free(logctx);
+ logctx = NULL;
+ }
+ cleanup_exit(1);
+}
+void modalfatalbox(char *p, ...)
+{
+ va_list ap;
+ fprintf(stderr, "FATAL ERROR: ");
+ va_start(ap, p);
+ vfprintf(stderr, p, ap);
+ va_end(ap);
+ fputc('\n', stderr);
+ if (logctx) {
+ log_free(logctx);
+ logctx = NULL;
+ }
+ cleanup_exit(1);
+}
+void connection_fatal(void *frontend, char *p, ...)
+{
+ va_list ap;
+ fprintf(stderr, "FATAL ERROR: ");
+ va_start(ap, p);
+ vfprintf(stderr, p, ap);
+ va_end(ap);
+ fputc('\n', stderr);
+ if (logctx) {
+ log_free(logctx);
+ logctx = NULL;
+ }
+ cleanup_exit(1);
+}
+void cmdline_error(char *p, ...)
+{
+ va_list ap;
+ fprintf(stderr, "plink: ");
+ va_start(ap, p);
+ vfprintf(stderr, p, ap);
+ va_end(ap);
+ fputc('\n', stderr);
+ exit(1);
+}
+
+HANDLE inhandle, outhandle, errhandle;
+struct handle *stdin_handle, *stdout_handle, *stderr_handle;
+DWORD orig_console_mode;
+int connopen;
+
+WSAEVENT netevent;
+
+static Backend *back;
+static void *backhandle;
+static Config cfg;
+
+int term_ldisc(Terminal *term, int mode)
+{
+ return FALSE;
+}
+void ldisc_update(void *frontend, int echo, int edit)
+{
+ /* Update stdin read mode to reflect changes in line discipline. */
+ DWORD mode;
+
+ mode = ENABLE_PROCESSED_INPUT;
+ if (echo)
+ mode = mode | ENABLE_ECHO_INPUT;
+ else
+ mode = mode & ~ENABLE_ECHO_INPUT;
+ if (edit)
+ mode = mode | ENABLE_LINE_INPUT;
+ else
+ mode = mode & ~ENABLE_LINE_INPUT;
+ SetConsoleMode(inhandle, mode);
+}
+
+char *get_ttymode(void *frontend, const char *mode) { return NULL; }
+
+int from_backend(void *frontend_handle, int is_stderr,
+ const char *data, int len)
+{
+ if (is_stderr) {
+ handle_write(stderr_handle, data, len);
+ } else {
+ handle_write(stdout_handle, data, len);
+ }
+
+ return handle_backlog(stdout_handle) + handle_backlog(stderr_handle);
+}
+
+int from_backend_untrusted(void *frontend_handle, const char *data, int len)
+{
+ /*
+ * 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 get_userpass_input(prompts_t *p, unsigned char *in, int inlen)
+{
+ int ret;
+ ret = cmdline_get_passwd_input(p, in, inlen);
+ if (ret == -1)
+ ret = console_get_userpass_input(p, in, inlen);
+ return ret;
+}
+
+static DWORD main_thread_id;
+
+void agent_schedule_callback(void (*callback)(void *, void *, int),
+ void *callback_ctx, void *data, int len)
+{
+ struct agent_callback *c = snew(struct agent_callback);
+ c->callback = callback;
+ c->callback_ctx = callback_ctx;
+ c->data = data;
+ c->len = len;
+ PostThreadMessage(main_thread_id, WM_AGENT_CALLBACK, 0, (LPARAM)c);
+}
+
+/*
+ * Short description of parameters.
+ */
+static void usage(void)
+{
+ printf("PuTTY Link: command-line connection utility\n");
+ printf("%s\n", ver);
+ printf("Usage: plink [options] [user@]host [command]\n");
+ printf(" (\"host\" can also be a PuTTY saved session name)\n");
+ printf("Options:\n");
+ printf(" -V print version information and exit\n");
+ printf(" -pgpfp print PGP key fingerprints and exit\n");
+ printf(" -v show verbose messages\n");
+ printf(" -load sessname Load settings from saved session\n");
+ printf(" -ssh -telnet -rlogin -raw -serial\n");
+ printf(" force use of a particular protocol\n");
+ printf(" -P port connect to specified port\n");
+ printf(" -l user connect with specified username\n");
+ printf(" -batch disable all interactive prompts\n");
+ printf("The following options only apply to SSH connections:\n");
+ printf(" -pw passw login with specified password\n");
+ printf(" -D [listen-IP:]listen-port\n");
+ printf(" Dynamic SOCKS-based port forwarding\n");
+ printf(" -L [listen-IP:]listen-port:host:port\n");
+ printf(" Forward local port to remote address\n");
+ printf(" -R [listen-IP:]listen-port:host:port\n");
+ printf(" Forward remote port to local address\n");
+ printf(" -X -x enable / disable X11 forwarding\n");
+ printf(" -A -a enable / disable agent forwarding\n");
+ printf(" -t -T enable / disable pty allocation\n");
+ printf(" -1 -2 force use of particular protocol version\n");
+ printf(" -4 -6 force use of IPv4 or IPv6\n");
+ printf(" -C enable compression\n");
+ printf(" -i key private key file for authentication\n");
+ printf(" -noagent disable use of Pageant\n");
+ printf(" -agent enable use of Pageant\n");
+ printf(" -m file read remote command(s) from file\n");
+ printf(" -s remote command is an SSH subsystem (SSH-2 only)\n");
+ printf(" -N don't start a shell/command (SSH-2 only)\n");
+ printf(" -nc host:port\n");
+ printf(" open tunnel in place of session (SSH-2 only)\n");
+ printf(" -sercfg configuration-string (e.g. 19200,8,n,1,X)\n");
+ printf(" Specify the serial configuration (serial only)\n");
+ exit(1);
+}
+
+static void version(void)
+{
+ printf("plink: %s\n", ver);
+ exit(1);
+}
+
+char *do_select(SOCKET skt, int startup)
+{
+ int events;
+ if (startup) {
+ events = (FD_CONNECT | FD_READ | FD_WRITE |
+ FD_OOB | FD_CLOSE | FD_ACCEPT);
+ } else {
+ events = 0;
+ }
+ if (p_WSAEventSelect(skt, netevent, events) == SOCKET_ERROR) {
+ switch (p_WSAGetLastError()) {
+ case WSAENETDOWN:
+ return "Network is down";
+ default:
+ return "WSAEventSelect(): unknown error";
+ }
+ }
+ return NULL;
+}
+
+int stdin_gotdata(struct handle *h, void *data, int len)
+{
+ if (len < 0) {
+ /*
+ * Special case: report read error.
+ */
+ char buf[4096];
+ FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, -len, 0,
+ buf, lenof(buf), NULL);
+ buf[lenof(buf)-1] = '\0';
+ if (buf[strlen(buf)-1] == '\n')
+ buf[strlen(buf)-1] = '\0';
+ fprintf(stderr, "Unable to read from standard input: %s\n", buf);
+ cleanup_exit(0);
+ }
+ noise_ultralight(len);
+ if (connopen && back->connected(backhandle)) {
+ if (len > 0) {
+ return back->send(backhandle, data, len);
+ } else {
+ back->special(backhandle, TS_EOF);
+ return 0;
+ }
+ } else
+ return 0;
+}
+
+void stdouterr_sent(struct handle *h, int new_backlog)
+{
+ if (new_backlog < 0) {
+ /*
+ * Special case: report write error.
+ */
+ char buf[4096];
+ FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, -new_backlog, 0,
+ buf, lenof(buf), NULL);
+ buf[lenof(buf)-1] = '\0';
+ if (buf[strlen(buf)-1] == '\n')
+ buf[strlen(buf)-1] = '\0';
+ fprintf(stderr, "Unable to write to standard %s: %s\n",
+ (h == stdout_handle ? "output" : "error"), buf);
+ cleanup_exit(0);
+ }
+ if (connopen && back->connected(backhandle)) {
+ back->unthrottle(backhandle, (handle_backlog(stdout_handle) +
+ handle_backlog(stderr_handle)));
+ }
+}
+
+int main(int argc, char **argv)
+{
+ int sending;
+ int portnumber = -1;
+ SOCKET *sklist;
+ int skcount, sksize;
+ int exitcode;
+ int errors;
+ int got_host = FALSE;
+ int use_subsystem = 0;
+ long now, next;
+
+ sklist = NULL;
+ skcount = sksize = 0;
+ /*
+ * Initialise port and protocol to sensible defaults. (These
+ * will be overridden by more or less anything.)
+ */
+ default_protocol = PROT_SSH;
+ default_port = 22;
+
+ flags = FLAG_STDERR;
+ /*
+ * Process the command line.
+ */
+ do_defaults(NULL, &cfg);
+ loaded_session = FALSE;
+ default_protocol = cfg.protocol;
+ default_port = cfg.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;
+ }
+ }
+ }
+ while (--argc) {
+ char *p = *++argv;
+ if (*p == '-') {
+ int ret = cmdline_process_param(p, (argc > 1 ? argv[1] : NULL),
+ 1, &cfg);
+ if (ret == -2) {
+ fprintf(stderr,
+ "plink: option \"%s\" requires an argument\n", p);
+ errors = 1;
+ } else if (ret == 2) {
+ --argc, ++argv;
+ } 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. */
+ use_subsystem = 1;
+ } else if (!strcmp(p, "-V")) {
+ version();
+ } 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)) {
+ char *q = p;
+ /*
+ * If the hostname starts with "telnet:", set the
+ * protocol to Telnet and process the string as a
+ * Telnet URL.
+ */
+ if (!strncmp(q, "telnet:", 7)) {
+ char c;
+
+ q += 7;
+ if (q[0] == '/' && q[1] == '/')
+ q += 2;
+ cfg.protocol = PROT_TELNET;
+ p = q;
+ while (*p && *p != ':' && *p != '/')
+ p++;
+ c = *p;
+ if (*p)
+ *p++ = '\0';
+ if (c == ':')
+ cfg.port = atoi(p);
+ else
+ cfg.port = -1;
+ strncpy(cfg.host, q, sizeof(cfg.host) - 1);
+ cfg.host[sizeof(cfg.host) - 1] = '\0';
+ got_host = TRUE;
+ } else {
+ char *r, *user, *host;
+ /*
+ * Before we process the [user@]host string, we
+ * first check for the presence of a protocol
+ * prefix (a protocol name followed by ",").
+ */
+ r = strchr(p, ',');
+ if (r) {
+ const Backend *b;
+ *r = '\0';
+ b = backend_from_name(p);
+ if (b) {
+ default_protocol = cfg.protocol = b->protocol;
+ portnumber = b->default_port;
+ }
+ p = r + 1;
+ }
+
+ /*
+ * A nonzero length string followed by an @ is treated
+ * as a username. (We discount an _initial_ @.) The
+ * rest of the string (or the whole string if no @)
+ * is treated as a session name and/or hostname.
+ */
+ r = strrchr(p, '@');
+ if (r == p)
+ p++, r = NULL; /* discount initial @ */
+ if (r) {
+ *r++ = '\0';
+ user = p, host = r;
+ } else {
+ user = NULL, host = p;
+ }
+
+ /*
+ * 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)) {
+ /* 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;
+ got_host = TRUE;
+ } else {
+ cfg = cfg2;
+ loaded_session = TRUE;
+ }
+ }
+
+ if (user) {
+ /* Patch in specified username. */
+ strncpy(cfg.username, user,
+ sizeof(cfg.username) - 1);
+ cfg.username[sizeof(cfg.username) - 1] = '\0';
+ }
+
+ }
+ } else {
+ char *command;
+ int cmdlen, cmdsize;
+ cmdlen = cmdsize = 0;
+ command = NULL;
+
+ while (argc) {
+ while (*p) {
+ if (cmdlen >= cmdsize) {
+ cmdsize = cmdlen + 512;
+ command = sresize(command, cmdsize, char);
+ }
+ command[cmdlen++]=*p++;
+ }
+ if (cmdlen >= cmdsize) {
+ cmdsize = cmdlen + 512;
+ command = sresize(command, cmdsize, char);
+ }
+ 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 */
+
+ break; /* done with cmdline */
+ }
+ }
+ }
+
+ if (errors)
+ return 1;
+
+ if (!cfg_launchable(&cfg) || !(got_host || loaded_session)) {
+ usage();
+ }
+
+ /*
+ * 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_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));
+ }
+ }
+
+ /*
+ * Perform command-line overrides on session configuration.
+ */
+ cmdline_run_saved(&cfg);
+
+ /*
+ * 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)
+ 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);
+ if (back == NULL) {
+ fprintf(stderr,
+ "Internal fault: Unsupported protocol found\n");
+ return 1;
+ }
+
+ /*
+ * Select port.
+ */
+ if (portnumber != -1)
+ cfg.port = portnumber;
+
+ sk_init();
+ if (p_WSAEventSelect == NULL) {
+ fprintf(stderr, "Plink requires WinSock 2\n");
+ return 1;
+ }
+
+ logctx = log_init(NULL, &cfg);
+ console_provide_logctx(logctx);
+
+ /*
+ * Start up the connection.
+ */
+ 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 &&
+ (GetFileType(GetStdHandle(STD_INPUT_HANDLE)) == FILE_TYPE_CHAR);
+
+ error = back->init(NULL, &backhandle, &cfg, cfg.host, cfg.port,
+ &realhost, nodelay, cfg.tcp_keepalives);
+ if (error) {
+ fprintf(stderr, "Unable to open connection:\n%s", error);
+ return 1;
+ }
+ back->provide_logctx(backhandle, logctx);
+ sfree(realhost);
+ }
+ connopen = 1;
+
+ inhandle = GetStdHandle(STD_INPUT_HANDLE);
+ outhandle = GetStdHandle(STD_OUTPUT_HANDLE);
+ errhandle = GetStdHandle(STD_ERROR_HANDLE);
+
+ /*
+ * Turn off ECHO and LINE input modes. We don't care if this
+ * call fails, because we know we aren't necessarily running in
+ * a console.
+ */
+ GetConsoleMode(inhandle, &orig_console_mode);
+ SetConsoleMode(inhandle, ENABLE_PROCESSED_INPUT);
+
+ /*
+ * Pass the output handles to the handle-handling subsystem.
+ * (The input one we leave until we're through the
+ * authentication process.)
+ */
+ stdout_handle = handle_output_new(outhandle, stdouterr_sent, NULL, 0);
+ stderr_handle = handle_output_new(errhandle, stdouterr_sent, NULL, 0);
+
+ main_thread_id = GetCurrentThreadId();
+
+ sending = FALSE;
+
+ now = GETTICKCOUNT();
+
+ while (1) {
+ int nhandles;
+ HANDLE *handles;
+ int n;
+ DWORD ticks;
+
+ if (!sending && back->sendok(backhandle)) {
+ 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 */
+ } else {
+ ticks = INFINITE;
+ }
+
+ handles = handle_get_events(&nhandles);
+ handles = sresize(handles, nhandles+1, HANDLE);
+ handles[nhandles] = netevent;
+ n = MsgWaitForMultipleObjects(nhandles+1, handles, FALSE, ticks,
+ QS_POSTMESSAGE);
+ if ((unsigned)(n - WAIT_OBJECT_0) < (unsigned)nhandles) {
+ handle_got_event(handles[n - WAIT_OBJECT_0]);
+ } else if (n == WAIT_OBJECT_0 + nhandles) {
+ WSANETWORKEVENTS things;
+ SOCKET socket;
+ extern SOCKET first_socket(int *), next_socket(int *);
+ extern int select_result(WPARAM, LPARAM);
+ int i, socketstate;
+
+ /*
+ * We must not call select_result() for any socket
+ * until we have finished enumerating within the tree.
+ * This is because select_result() may close the socket
+ * and modify the tree.
+ */
+ /* Count the active sockets. */
+ i = 0;
+ for (socket = first_socket(&socketstate);
+ socket != INVALID_SOCKET;
+ socket = next_socket(&socketstate)) i++;
+
+ /* Expand the buffer if necessary. */
+ if (i > sksize) {
+ sksize = i + 16;
+ sklist = sresize(sklist, sksize, SOCKET);
+ }
+
+ /* Retrieve the sockets into sklist. */
+ skcount = 0;
+ for (socket = first_socket(&socketstate);
+ socket != INVALID_SOCKET;
+ socket = next_socket(&socketstate)) {
+ sklist[skcount++] = socket;
+ }
+
+ /* Now we're done enumerating; go through the list. */
+ for (i = 0; i < skcount; i++) {
+ WPARAM wp;
+ socket = sklist[i];
+ wp = (WPARAM) socket;
+ if (!p_WSAEnumNetworkEvents(socket, NULL, &things)) {
+ static const struct { int bit, mask; } eventtypes[] = {
+ {FD_CONNECT_BIT, FD_CONNECT},
+ {FD_READ_BIT, FD_READ},
+ {FD_CLOSE_BIT, FD_CLOSE},
+ {FD_OOB_BIT, FD_OOB},
+ {FD_WRITE_BIT, FD_WRITE},
+ {FD_ACCEPT_BIT, FD_ACCEPT},
+ };
+ int e;
+
+ noise_ultralight(socket);
+ noise_ultralight(things.lNetworkEvents);
+
+ for (e = 0; e < lenof(eventtypes); e++)
+ if (things.lNetworkEvents & eventtypes[e].mask) {
+ LPARAM lp;
+ int err = things.iErrorCode[eventtypes[e].bit];
+ lp = WSAMAKESELECTREPLY(eventtypes[e].mask, err);
+ connopen &= select_result(wp, lp);
+ }
+ }
+ }
+ } else if (n == WAIT_OBJECT_0 + nhandles + 1) {
+ MSG msg;
+ while (PeekMessage(&msg, INVALID_HANDLE_VALUE,
+ WM_AGENT_CALLBACK, WM_AGENT_CALLBACK,
+ PM_REMOVE)) {
+ struct agent_callback *c = (struct agent_callback *)msg.lParam;
+ c->callback(c->callback_ctx, c->data, c->len);
+ sfree(c);
+ }
+ }
+
+ if (n == WAIT_TIMEOUT) {
+ now = next;
+ } else {
+ now = GETTICKCOUNT();
+ }
+
+ sfree(handles);
+
+ if (sending)
+ handle_unthrottle(stdin_handle, back->sendbuffer(backhandle));
+
+ if ((!connopen || !back->connected(backhandle)) &&
+ handle_backlog(stdout_handle) + handle_backlog(stderr_handle) == 0)
+ break; /* we closed the connection */
+ }
+ exitcode = back->exitcode(backhandle);
+ if (exitcode < 0) {
+ fprintf(stderr, "Remote process exit code unavailable\n");
+ exitcode = 1; /* this is an error condition */
+ }
+ cleanup_exit(exitcode);
+ return 0; /* placate compiler warning */
+}
--- /dev/null
+/*
+ * Printing interface for PuTTY.
+ */
+
+#include "putty.h"
+#include <winspool.h>
+
+struct printer_enum_tag {
+ int nprinters;
+ DWORD enum_level;
+ union {
+ LPPRINTER_INFO_4 i4;
+ LPPRINTER_INFO_5 i5;
+ } info;
+};
+
+struct printer_job_tag {
+ HANDLE hprinter;
+};
+
+static char *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);
+
+ /*
+ * 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,
+ &needed, &nprinters);
+
+ if (needed < 512)
+ needed = 512;
+
+ buffer = sresize(buffer, offset+needed, char);
+
+ if (EnumPrinters(param, NULL, level, buffer+offset,
+ needed, &needed, &nprinters) == 0)
+ return NULL;
+
+ *nprinters_ptr += nprinters;
+
+ return buffer;
+}
+
+printer_enum *printer_start_enum(int *nprinters_ptr)
+{
+ printer_enum *ret = snew(printer_enum);
+ char *buffer = NULL, *retval;
+
+ *nprinters_ptr = 0; /* default return value */
+ buffer = snewn(512, char);
+
+ /*
+ * Determine what enumeration level to use.
+ * When enumerating printers, we need to use PRINTER_INFO_4 on
+ * NT-class systems to avoid Windows looking too hard for them and
+ * slowing things down; and we need to avoid PRINTER_INFO_5 as
+ * we've seen network printers not show up.
+ * On 9x-class systems, PRINTER_INFO_4 isn't available and
+ * PRINTER_INFO_5 is recommended.
+ * Bletch.
+ */
+ if (osVersion.dwPlatformId != VER_PLATFORM_WIN32_NT) {
+ 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)
+ goto error;
+ else
+ buffer = retval;
+
+ switch (ret->enum_level) {
+ case 4:
+ ret->info.i4 = (LPPRINTER_INFO_4)buffer;
+ break;
+ case 5:
+ ret->info.i5 = (LPPRINTER_INFO_5)buffer;
+ break;
+ }
+ ret->nprinters = *nprinters_ptr;
+
+ return ret;
+
+ error:
+ sfree(buffer);
+ sfree(ret);
+ *nprinters_ptr = 0;
+ return NULL;
+}
+
+char *printer_get_name(printer_enum *pe, int i)
+{
+ if (!pe)
+ return NULL;
+ if (i < 0 || i >= pe->nprinters)
+ return NULL;
+ switch (pe->enum_level) {
+ case 4:
+ return pe->info.i4[i].pPrinterName;
+ case 5:
+ return pe->info.i5[i].pPrinterName;
+ default:
+ return NULL;
+ }
+}
+
+void printer_finish_enum(printer_enum *pe)
+{
+ if (!pe)
+ return;
+ switch (pe->enum_level) {
+ case 4:
+ sfree(pe->info.i4);
+ break;
+ case 5:
+ sfree(pe->info.i5);
+ break;
+ }
+ sfree(pe);
+}
+
+printer_job *printer_start_job(char *printer)
+{
+ printer_job *ret = snew(printer_job);
+ DOC_INFO_1 docinfo;
+ int jobstarted = 0, pagestarted = 0;
+
+ ret->hprinter = NULL;
+ if (!OpenPrinter(printer, &ret->hprinter, NULL))
+ goto error;
+
+ docinfo.pDocName = "PuTTY remote printer output";
+ docinfo.pOutputFile = NULL;
+ docinfo.pDatatype = "RAW";
+
+ if (!StartDocPrinter(ret->hprinter, 1, (LPSTR)&docinfo))
+ goto error;
+ jobstarted = 1;
+
+ if (!StartPagePrinter(ret->hprinter))
+ goto error;
+ pagestarted = 1;
+
+ return ret;
+
+ error:
+ if (pagestarted)
+ EndPagePrinter(ret->hprinter);
+ if (jobstarted)
+ EndDocPrinter(ret->hprinter);
+ if (ret->hprinter)
+ ClosePrinter(ret->hprinter);
+ sfree(ret);
+ return NULL;
+}
+
+void printer_job_data(printer_job *pj, void *data, int len)
+{
+ DWORD written;
+
+ if (!pj)
+ return;
+
+ WritePrinter(pj->hprinter, data, len, &written);
+}
+
+void printer_finish_job(printer_job *pj)
+{
+ if (!pj)
+ return;
+
+ EndPagePrinter(pj->hprinter);
+ EndDocPrinter(pj->hprinter);
+ ClosePrinter(pj->hprinter);
+ sfree(pj);
+}
--- /dev/null
+/*
+ * winproxy.c: Windows implementation of platform_new_connection(),
+ * supporting an OpenSSH-like proxy command via the winhandl.c
+ * mechanism.
+ */
+
+#include <stdio.h>
+#include <assert.h>
+
+#define DEFINE_PLUG_METHOD_MACROS
+#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 platform_new_connection(SockAddr addr, char *hostname,
+ int port, int privport,
+ int oobinline, int nodelay, int keepalive,
+ Plug plug, const Config *cfg)
+{
+ 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)
+ return NULL;
+
+ cmd = format_telnet_command(addr, port, cfg);
+
+ {
+ 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;
+ }
+
+ if (!CreatePipe(&cmd_from_us, &us_to_cmd, &sa, 0)) {
+ CloseHandle(us_from_cmd);
+ CloseHandle(cmd_to_us);
+ ret->error = dupprintf("Unable to create pipes for proxy command");
+ return (Socket)ret;
+ }
+
+ SetHandleInformation(us_to_cmd, HANDLE_FLAG_INHERIT, 0);
+ SetHandleInformation(us_from_cmd, HANDLE_FLAG_INHERIT, 0);
+
+ si.cb = sizeof(si);
+ si.lpReserved = NULL;
+ si.lpDesktop = NULL;
+ si.lpTitle = NULL;
+ si.dwFlags = STARTF_USESTDHANDLES;
+ si.cbReserved2 = 0;
+ si.lpReserved2 = NULL;
+ si.hStdInput = cmd_from_us;
+ si.hStdOutput = cmd_to_us;
+ si.hStdError = NULL;
+ CreateProcess(NULL, cmd, NULL, NULL, TRUE,
+ CREATE_NO_WINDOW | NORMAL_PRIORITY_CLASS,
+ NULL, NULL, &si, &pi);
+
+ 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;
+}
--- /dev/null
+/*
+ * Serial back end (Windows-specific).
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <limits.h>
+
+#include "putty.h"
+
+#define SERIAL_MAX_BACKLOG 4096
+
+typedef struct serial_backend_data {
+ HANDLE port;
+ struct handle *out, *in;
+ void *frontend;
+ int bufsize;
+ long clearbreak_time;
+ int break_in_progress;
+} *Serial;
+
+static void serial_terminate(Serial serial)
+{
+ if (serial->out) {
+ handle_free(serial->out);
+ serial->out = NULL;
+ }
+ if (serial->in) {
+ handle_free(serial->in);
+ serial->in = NULL;
+ }
+ if (serial->port != INVALID_HANDLE_VALUE) {
+ if (serial->break_in_progress)
+ ClearCommBreak(serial->port);
+ CloseHandle(serial->port);
+ serial->port = INVALID_HANDLE_VALUE;
+ }
+}
+
+static int serial_gotdata(struct handle *h, void *data, int len)
+{
+ Serial serial = (Serial)handle_get_privdata(h);
+ if (len <= 0) {
+ const char *error_msg;
+
+ /*
+ * Currently, len==0 should never happen because we're
+ * ignoring EOFs. However, it seems not totally impossible
+ * that this same back end might be usable to talk to named
+ * pipes or some other non-serial device, in which case EOF
+ * may become meaningful here.
+ */
+ if (len == 0)
+ error_msg = "End of file reading from serial device";
+ else
+ error_msg = "Error reading from serial device";
+
+ serial_terminate(serial);
+
+ notify_remote_exit(serial->frontend);
+
+ logevent(serial->frontend, error_msg);
+
+ connection_fatal(serial->frontend, "%s", error_msg);
+
+ return 0; /* placate optimiser */
+ } else {
+ return from_backend(serial->frontend, 0, data, len);
+ }
+}
+
+static void serial_sentdata(struct handle *h, int new_backlog)
+{
+ Serial serial = (Serial)handle_get_privdata(h);
+ if (new_backlog < 0) {
+ const char *error_msg = "Error writing to serial device";
+
+ serial_terminate(serial);
+
+ notify_remote_exit(serial->frontend);
+
+ logevent(serial->frontend, error_msg);
+
+ connection_fatal(serial->frontend, "%s", error_msg);
+ } else {
+ serial->bufsize = new_backlog;
+ }
+}
+
+static const char *serial_configure(Serial serial, HANDLE serport, Config *cfg)
+{
+ DCB dcb;
+ COMMTIMEOUTS timeouts;
+
+ /*
+ * Set up the serial port parameters. If we can't even
+ * GetCommState, we ignore the problem on the grounds that the
+ * user might have pointed us at some other type of two-way
+ * device instead of a serial port.
+ */
+ if (GetCommState(serport, &dcb)) {
+ char *msg;
+ const char *str;
+
+ /*
+ * Boilerplate.
+ */
+ dcb.fBinary = TRUE;
+ dcb.fDtrControl = DTR_CONTROL_ENABLE;
+ dcb.fDsrSensitivity = FALSE;
+ dcb.fTXContinueOnXoff = FALSE;
+ dcb.fOutX = FALSE;
+ dcb.fInX = FALSE;
+ dcb.fErrorChar = FALSE;
+ dcb.fNull = FALSE;
+ dcb.fRtsControl = RTS_CONTROL_ENABLE;
+ dcb.fAbortOnError = FALSE;
+ dcb.fOutxCtsFlow = FALSE;
+ dcb.fOutxDsrFlow = FALSE;
+
+ /*
+ * Configurable parameters.
+ */
+ dcb.BaudRate = cfg->serspeed;
+ msg = dupprintf("Configuring baud rate %d", cfg->serspeed);
+ logevent(serial->frontend, msg);
+ sfree(msg);
+
+ dcb.ByteSize = cfg->serdatabits;
+ msg = dupprintf("Configuring %d data bits", cfg->serdatabits);
+ logevent(serial->frontend, msg);
+ sfree(msg);
+
+ switch (cfg->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) {
+ 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;
+ }
+ msg = dupprintf("Configuring %s parity", str);
+ logevent(serial->frontend, msg);
+ sfree(msg);
+
+ switch (cfg->serflow) {
+ case SER_FLOW_NONE:
+ str = "no";
+ break;
+ case SER_FLOW_XONXOFF:
+ dcb.fOutX = dcb.fInX = TRUE;
+ str = "XON/XOFF";
+ break;
+ case SER_FLOW_RTSCTS:
+ dcb.fRtsControl = RTS_CONTROL_HANDSHAKE;
+ dcb.fOutxCtsFlow = TRUE;
+ str = "RTS/CTS";
+ break;
+ case SER_FLOW_DSRDTR:
+ dcb.fDtrControl = DTR_CONTROL_HANDSHAKE;
+ dcb.fOutxDsrFlow = TRUE;
+ str = "DSR/DTR";
+ break;
+ }
+ msg = dupprintf("Configuring %s flow control", str);
+ logevent(serial->frontend, msg);
+ sfree(msg);
+
+ if (!SetCommState(serport, &dcb))
+ return "Unable to configure serial port";
+
+ timeouts.ReadIntervalTimeout = 1;
+ timeouts.ReadTotalTimeoutMultiplier = 0;
+ timeouts.ReadTotalTimeoutConstant = 0;
+ timeouts.WriteTotalTimeoutMultiplier = 0;
+ timeouts.WriteTotalTimeoutConstant = 0;
+ if (!SetCommTimeouts(serport, &timeouts))
+ return "Unable to configure serial timeouts";
+ }
+
+ return NULL;
+}
+
+/*
+ * Called to set up the serial connection.
+ *
+ * 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 *serial_init(void *frontend_handle, void **backend_handle,
+ Config *cfg,
+ char *host, int port, char **realhost, int nodelay,
+ int keepalive)
+{
+ Serial serial;
+ HANDLE serport;
+ const char *err;
+
+ serial = snew(struct serial_backend_data);
+ serial->port = INVALID_HANDLE_VALUE;
+ serial->out = serial->in = NULL;
+ serial->bufsize = 0;
+ serial->break_in_progress = FALSE;
+ *backend_handle = serial;
+
+ serial->frontend = frontend_handle;
+
+ {
+ char *msg = dupprintf("Opening serial device %s", cfg->serline);
+ logevent(serial->frontend, msg);
+ }
+
+ {
+ /*
+ * Munge the string supplied by the user into a Windows filename.
+ *
+ * Windows supports opening a few "legacy" devices (including
+ * COM1-9) by specifying their names verbatim as a filename to
+ * open. (Thus, no files can ever have these names. See
+ * <http://msdn2.microsoft.com/en-us/library/aa365247.aspx>
+ * ("Naming a File") for the complete list of reserved names.)
+ *
+ * However, this doesn't let you get at devices COM10 and above.
+ * For that, you need to specify a filename like "\\.\COM10".
+ * This is also necessary for special serial and serial-like
+ * devices such as \\.\WCEUSBSH001. It also works for the "legacy"
+ * names, so you can do \\.\COM1 (verified as far back as Win95).
+ * See <http://msdn2.microsoft.com/en-us/library/aa363858.aspx>
+ * (CreateFile() docs).
+ *
+ * So, we believe that prepending "\\.\" should always be the
+ * Right Thing. However, just in case someone finds something to
+ * 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);
+ 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);
+ if (err)
+ return err;
+
+ serial->port = serport;
+ serial->out = handle_output_new(serport, serial_sentdata, serial,
+ HANDLE_FLAG_OVERLAPPED);
+ serial->in = handle_input_new(serport, serial_gotdata, serial,
+ HANDLE_FLAG_OVERLAPPED |
+ HANDLE_FLAG_IGNOREEOF |
+ HANDLE_FLAG_UNITBUFFER);
+
+ *realhost = dupstr(cfg->serline);
+
+ /*
+ * Specials are always available.
+ */
+ update_specials_menu(serial->frontend);
+
+ return NULL;
+}
+
+static void serial_free(void *handle)
+{
+ Serial serial = (Serial) handle;
+
+ serial_terminate(serial);
+ expire_timer_context(serial);
+ sfree(serial);
+}
+
+static void serial_reconfig(void *handle, Config *cfg)
+{
+ Serial serial = (Serial) handle;
+ const char *err;
+
+ err = serial_configure(serial, serial->port, cfg);
+
+ /*
+ * FIXME: what should we do if err returns something?
+ */
+}
+
+/*
+ * Called to send data down the serial connection.
+ */
+static int serial_send(void *handle, char *buf, int len)
+{
+ Serial serial = (Serial) handle;
+
+ if (serial->out == NULL)
+ return 0;
+
+ serial->bufsize = handle_write(serial->out, buf, len);
+ return serial->bufsize;
+}
+
+/*
+ * Called to query the current sendability status.
+ */
+static int serial_sendbuffer(void *handle)
+{
+ Serial serial = (Serial) handle;
+ return serial->bufsize;
+}
+
+/*
+ * Called to set the size of the window
+ */
+static void serial_size(void *handle, int width, int height)
+{
+ /* Do nothing! */
+ return;
+}
+
+static void serbreak_timer(void *ctx, long now)
+{
+ Serial serial = (Serial)ctx;
+
+ if (now >= serial->clearbreak_time && serial->port) {
+ ClearCommBreak(serial->port);
+ serial->break_in_progress = FALSE;
+ logevent(serial->frontend, "Finished serial break");
+ }
+}
+
+/*
+ * Send serial special codes.
+ */
+static void serial_special(void *handle, Telnet_Special code)
+{
+ Serial serial = (Serial) handle;
+
+ if (serial->port && code == TS_BRK) {
+ logevent(serial->frontend, "Starting serial break at user request");
+ SetCommBreak(serial->port);
+ /*
+ * To send a serial break on Windows, we call SetCommBreak
+ * to begin the break, then wait a bit, and then call
+ * ClearCommBreak to finish it. Hence, I must use timing.c
+ * to arrange a callback when it's time to do the latter.
+ *
+ * SUS says that a default break length must be between 1/4
+ * and 1/2 second. FreeBSD apparently goes with 2/5 second,
+ * and so will I.
+ */
+ serial->clearbreak_time =
+ schedule_timer(TICKSPERSEC * 2 / 5, serbreak_timer, serial);
+ serial->break_in_progress = TRUE;
+ }
+
+ return;
+}
+
+/*
+ * Return a list of the special codes that make sense in this
+ * protocol.
+ */
+static const struct telnet_special *serial_get_specials(void *handle)
+{
+ static const struct telnet_special specials[] = {
+ {"Break", TS_BRK},
+ {NULL, TS_EXITMENU}
+ };
+ return specials;
+}
+
+static int serial_connected(void *handle)
+{
+ return 1; /* always connected */
+}
+
+static int serial_sendok(void *handle)
+{
+ return 1;
+}
+
+static void serial_unthrottle(void *handle, int backlog)
+{
+ Serial serial = (Serial) handle;
+ if (serial->in)
+ handle_unthrottle(serial->in, backlog);
+}
+
+static int serial_ldisc(void *handle, int option)
+{
+ /*
+ * Local editing and local echo are off by default.
+ */
+ return 0;
+}
+
+static void serial_provide_ldisc(void *handle, void *ldisc)
+{
+ /* This is a stub. */
+}
+
+static void serial_provide_logctx(void *handle, void *logctx)
+{
+ /* This is a stub. */
+}
+
+static int serial_exitcode(void *handle)
+{
+ Serial serial = (Serial) handle;
+ if (serial->port != INVALID_HANDLE_VALUE)
+ return -1; /* still connected */
+ else
+ /* Exit codes are a meaningless concept with serial ports */
+ return INT_MAX;
+}
+
+/*
+ * cfg_info for Serial does nothing at all.
+ */
+static int serial_cfg_info(void *handle)
+{
+ return 0;
+}
+
+Backend serial_backend = {
+ serial_init,
+ serial_free,
+ serial_reconfig,
+ serial_send,
+ serial_sendbuffer,
+ serial_size,
+ serial_special,
+ serial_get_specials,
+ serial_connected,
+ serial_exitcode,
+ serial_sendok,
+ serial_ldisc,
+ serial_provide_ldisc,
+ serial_provide_logctx,
+ serial_unthrottle,
+ serial_cfg_info,
+ "serial",
+ PROT_SERIAL,
+ 0
+};
--- /dev/null
+/*
+ * winsftp.c: the Windows-specific parts of PSFTP and PSCP.
+ */
+
+#include <assert.h>
+
+#include "putty.h"
+#include "psftp.h"
+#include "ssh.h"
+#include "int64.h"
+
+char *get_ttymode(void *frontend, const char *mode) { return NULL; }
+
+int get_userpass_input(prompts_t *p, unsigned char *in, int inlen)
+{
+ int ret;
+ ret = cmdline_get_passwd_input(p, in, inlen);
+ if (ret == -1)
+ ret = console_get_userpass_input(p, in, inlen);
+ return ret;
+}
+
+void platform_get_x11_auth(struct X11Display *display, const Config *cfg)
+{
+ /* Do nothing, therefore no auth. */
+}
+const int platform_uses_x11_unix_by_default = TRUE;
+
+/* ----------------------------------------------------------------------
+ * File access abstraction.
+ */
+
+/*
+ * Set local current directory. Returns NULL on success, or else an
+ * error message which must be freed after printing.
+ */
+char *psftp_lcd(char *dir)
+{
+ char *ret = NULL;
+
+ if (!SetCurrentDirectory(dir)) {
+ LPVOID message;
+ int i;
+ FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
+ FORMAT_MESSAGE_FROM_SYSTEM |
+ FORMAT_MESSAGE_IGNORE_INSERTS,
+ NULL, GetLastError(),
+ MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+ (LPTSTR)&message, 0, NULL);
+ i = strcspn((char *)message, "\n");
+ ret = dupprintf("%.*s", i, (LPCTSTR)message);
+ LocalFree(message);
+ }
+
+ return ret;
+}
+
+/*
+ * Get local current directory. Returns a string which must be
+ * freed.
+ */
+char *psftp_getcwd(void)
+{
+ char *ret = snewn(256, char);
+ int len = GetCurrentDirectory(256, ret);
+ if (len > 256)
+ ret = sresize(ret, len, char);
+ GetCurrentDirectory(len, ret);
+ return ret;
+}
+
+#define TIME_POSIX_TO_WIN(t, ft) do { \
+ ULARGE_INTEGER uli; \
+ uli.QuadPart = ((ULONGLONG)(t) + 11644473600ull) * 10000000ull; \
+ (ft).dwLowDateTime = uli.LowPart; \
+ (ft).dwHighDateTime = uli.HighPart; \
+} while(0)
+#define TIME_WIN_TO_POSIX(ft, t) do { \
+ ULARGE_INTEGER uli; \
+ uli.LowPart = (ft).dwLowDateTime; \
+ uli.HighPart = (ft).dwHighDateTime; \
+ uli.QuadPart = uli.QuadPart / 10000000ull - 11644473600ull; \
+ (t) = (unsigned long) uli.QuadPart; \
+} while(0)
+
+struct RFile {
+ HANDLE h;
+};
+
+RFile *open_existing_file(char *name, uint64 *size,
+ unsigned long *mtime, unsigned long *atime)
+{
+ HANDLE h;
+ RFile *ret;
+
+ h = CreateFile(name, GENERIC_READ, FILE_SHARE_READ, NULL,
+ OPEN_EXISTING, 0, 0);
+ if (h == INVALID_HANDLE_VALUE)
+ return NULL;
+
+ ret = snew(RFile);
+ ret->h = h;
+
+ if (size)
+ size->lo=GetFileSize(h, &(size->hi));
+
+ if (mtime || atime) {
+ FILETIME actime, wrtime;
+ GetFileTime(h, NULL, &actime, &wrtime);
+ if (atime)
+ TIME_WIN_TO_POSIX(actime, *atime);
+ if (mtime)
+ TIME_WIN_TO_POSIX(wrtime, *mtime);
+ }
+
+ return ret;
+}
+
+int read_from_file(RFile *f, void *buffer, int length)
+{
+ int ret;
+ DWORD read;
+ ret = ReadFile(f->h, buffer, length, &read, NULL);
+ if (!ret)
+ return -1; /* error */
+ else
+ return read;
+}
+
+void close_rfile(RFile *f)
+{
+ CloseHandle(f->h);
+ sfree(f);
+}
+
+struct WFile {
+ HANDLE h;
+};
+
+WFile *open_new_file(char *name)
+{
+ HANDLE h;
+ WFile *ret;
+
+ h = CreateFile(name, GENERIC_WRITE, 0, NULL,
+ CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
+ if (h == INVALID_HANDLE_VALUE)
+ return NULL;
+
+ ret = snew(WFile);
+ ret->h = h;
+
+ return ret;
+}
+
+WFile *open_existing_wfile(char *name, uint64 *size)
+{
+ HANDLE h;
+ WFile *ret;
+
+ h = CreateFile(name, GENERIC_WRITE, FILE_SHARE_READ, NULL,
+ OPEN_EXISTING, 0, 0);
+ if (h == INVALID_HANDLE_VALUE)
+ return NULL;
+
+ ret = snew(WFile);
+ ret->h = h;
+
+ if (size)
+ size->lo=GetFileSize(h, &(size->hi));
+
+ return ret;
+}
+
+int write_to_file(WFile *f, void *buffer, int length)
+{
+ int ret;
+ DWORD written;
+ ret = WriteFile(f->h, buffer, length, &written, NULL);
+ if (!ret)
+ return -1; /* error */
+ else
+ return written;
+}
+
+void set_file_times(WFile *f, unsigned long mtime, unsigned long atime)
+{
+ FILETIME actime, wrtime;
+ TIME_POSIX_TO_WIN(atime, actime);
+ TIME_POSIX_TO_WIN(mtime, wrtime);
+ SetFileTime(f->h, NULL, &actime, &wrtime);
+}
+
+void close_wfile(WFile *f)
+{
+ CloseHandle(f->h);
+ sfree(f);
+}
+
+/* Seek offset bytes through file, from whence, where whence is
+ FROM_START, FROM_CURRENT, or FROM_END */
+int seek_file(WFile *f, uint64 offset, int whence)
+{
+ DWORD movemethod;
+
+ switch (whence) {
+ case FROM_START:
+ movemethod = FILE_BEGIN;
+ break;
+ case FROM_CURRENT:
+ movemethod = FILE_CURRENT;
+ break;
+ case FROM_END:
+ movemethod = FILE_END;
+ break;
+ default:
+ return -1;
+ }
+
+ SetFilePointer(f->h, offset.lo, &(offset.hi), movemethod);
+
+ if (GetLastError() != NO_ERROR)
+ return -1;
+ else
+ return 0;
+}
+
+uint64 get_file_posn(WFile *f)
+{
+ uint64 ret;
+
+ ret.hi = 0L;
+ ret.lo = SetFilePointer(f->h, 0L, &(ret.hi), FILE_CURRENT);
+
+ return ret;
+}
+
+int file_type(char *name)
+{
+ DWORD attr;
+ attr = GetFileAttributes(name);
+ /* We know of no `weird' files under Windows. */
+ if (attr == (DWORD)-1)
+ return FILE_TYPE_NONEXISTENT;
+ else if (attr & FILE_ATTRIBUTE_DIRECTORY)
+ return FILE_TYPE_DIRECTORY;
+ else
+ return FILE_TYPE_FILE;
+}
+
+struct DirHandle {
+ HANDLE h;
+ char *name;
+};
+
+DirHandle *open_directory(char *name)
+{
+ HANDLE h;
+ WIN32_FIND_DATA fdat;
+ char *findfile;
+ DirHandle *ret;
+
+ /* Enumerate files in dir `foo'. */
+ findfile = dupcat(name, "/*", NULL);
+ h = FindFirstFile(findfile, &fdat);
+ if (h == INVALID_HANDLE_VALUE)
+ return NULL;
+ sfree(findfile);
+
+ ret = snew(DirHandle);
+ ret->h = h;
+ ret->name = dupstr(fdat.cFileName);
+ return ret;
+}
+
+char *read_filename(DirHandle *dir)
+{
+ do {
+
+ if (!dir->name) {
+ WIN32_FIND_DATA fdat;
+ int ok = FindNextFile(dir->h, &fdat);
+ if (!ok)
+ return NULL;
+ else
+ dir->name = dupstr(fdat.cFileName);
+ }
+
+ assert(dir->name);
+ if (dir->name[0] == '.' &&
+ (dir->name[1] == '\0' ||
+ (dir->name[1] == '.' && dir->name[2] == '\0'))) {
+ sfree(dir->name);
+ dir->name = NULL;
+ }
+
+ } while (!dir->name);
+
+ if (dir->name) {
+ char *ret = dir->name;
+ dir->name = NULL;
+ return ret;
+ } else
+ return NULL;
+}
+
+void close_directory(DirHandle *dir)
+{
+ FindClose(dir->h);
+ if (dir->name)
+ sfree(dir->name);
+ sfree(dir);
+}
+
+int test_wildcard(char *name, int cmdline)
+{
+ HANDLE fh;
+ WIN32_FIND_DATA fdat;
+
+ /* First see if the exact name exists. */
+ if (GetFileAttributes(name) != (DWORD)-1)
+ return WCTYPE_FILENAME;
+
+ /* Otherwise see if a wildcard match finds anything. */
+ fh = FindFirstFile(name, &fdat);
+ if (fh == INVALID_HANDLE_VALUE)
+ return WCTYPE_NONEXISTENT;
+
+ FindClose(fh);
+ return WCTYPE_WILDCARD;
+}
+
+struct WildcardMatcher {
+ HANDLE h;
+ char *name;
+ char *srcpath;
+};
+
+/*
+ * Return a pointer to the portion of str that comes after the last
+ * slash (or backslash or colon, if `local' is TRUE).
+ */
+static char *stripslashes(char *str, int local)
+{
+ char *p;
+
+ if (local) {
+ p = strchr(str, ':');
+ if (p) str = p+1;
+ }
+
+ p = strrchr(str, '/');
+ if (p) str = p+1;
+
+ if (local) {
+ p = strrchr(str, '\\');
+ if (p) str = p+1;
+ }
+
+ return str;
+}
+
+WildcardMatcher *begin_wildcard_matching(char *name)
+{
+ HANDLE h;
+ WIN32_FIND_DATA fdat;
+ WildcardMatcher *ret;
+ char *last;
+
+ h = FindFirstFile(name, &fdat);
+ if (h == INVALID_HANDLE_VALUE)
+ return NULL;
+
+ ret = snew(WildcardMatcher);
+ ret->h = h;
+ ret->srcpath = dupstr(name);
+ last = stripslashes(ret->srcpath, 1);
+ *last = '\0';
+ if (fdat.cFileName[0] == '.' &&
+ (fdat.cFileName[1] == '\0' ||
+ (fdat.cFileName[1] == '.' && fdat.cFileName[2] == '\0')))
+ ret->name = NULL;
+ else
+ ret->name = dupcat(ret->srcpath, fdat.cFileName, NULL);
+
+ return ret;
+}
+
+char *wildcard_get_filename(WildcardMatcher *dir)
+{
+ while (!dir->name) {
+ WIN32_FIND_DATA fdat;
+ int ok = FindNextFile(dir->h, &fdat);
+
+ if (!ok)
+ return NULL;
+
+ if (fdat.cFileName[0] == '.' &&
+ (fdat.cFileName[1] == '\0' ||
+ (fdat.cFileName[1] == '.' && fdat.cFileName[2] == '\0')))
+ dir->name = NULL;
+ else
+ dir->name = dupcat(dir->srcpath, fdat.cFileName, NULL);
+ }
+
+ if (dir->name) {
+ char *ret = dir->name;
+ dir->name = NULL;
+ return ret;
+ } else
+ return NULL;
+}
+
+void finish_wildcard_matching(WildcardMatcher *dir)
+{
+ FindClose(dir->h);
+ if (dir->name)
+ sfree(dir->name);
+ sfree(dir->srcpath);
+ sfree(dir);
+}
+
+int vet_filename(char *name)
+{
+ if (strchr(name, '/') || strchr(name, '\\') || strchr(name, ':'))
+ return FALSE;
+
+ if (!name[strspn(name, ".")]) /* entirely composed of dots */
+ return FALSE;
+
+ return TRUE;
+}
+
+int create_directory(char *name)
+{
+ return CreateDirectory(name, NULL) != 0;
+}
+
+char *dir_file_cat(char *dir, char *file)
+{
+ return dupcat(dir, "\\", file, NULL);
+}
+
+/* ----------------------------------------------------------------------
+ * Platform-specific network handling.
+ */
+
+/*
+ * Be told what socket we're supposed to be using.
+ */
+static SOCKET sftp_ssh_socket = INVALID_SOCKET;
+static HANDLE netevent = INVALID_HANDLE_VALUE;
+char *do_select(SOCKET skt, int startup)
+{
+ int events;
+ if (startup)
+ sftp_ssh_socket = skt;
+ else
+ sftp_ssh_socket = INVALID_SOCKET;
+
+ if (p_WSAEventSelect) {
+ if (startup) {
+ events = (FD_CONNECT | FD_READ | FD_WRITE |
+ FD_OOB | FD_CLOSE | FD_ACCEPT);
+ netevent = CreateEvent(NULL, FALSE, FALSE, NULL);
+ } else {
+ events = 0;
+ }
+ if (p_WSAEventSelect(skt, netevent, events) == SOCKET_ERROR) {
+ switch (p_WSAGetLastError()) {
+ case WSAENETDOWN:
+ return "Network is down";
+ default:
+ return "WSAEventSelect(): unknown error";
+ }
+ }
+ }
+ return NULL;
+}
+extern int select_result(WPARAM, LPARAM);
+
+int do_eventsel_loop(HANDLE other_event)
+{
+ int n, nhandles, nallhandles, netindex, otherindex;
+ long next, ticks;
+ HANDLE *handles;
+ SOCKET *sklist;
+ int skcount;
+ long now = GETTICKCOUNT();
+
+ if (run_timers(now, &next)) {
+ ticks = next - GETTICKCOUNT();
+ if (ticks < 0) ticks = 0; /* just in case */
+ } else {
+ ticks = INFINITE;
+ }
+
+ handles = handle_get_events(&nhandles);
+ handles = sresize(handles, nhandles+2, HANDLE);
+ nallhandles = nhandles;
+
+ if (netevent != INVALID_HANDLE_VALUE)
+ handles[netindex = nallhandles++] = netevent;
+ else
+ netindex = -1;
+ if (other_event != INVALID_HANDLE_VALUE)
+ handles[otherindex = nallhandles++] = other_event;
+ else
+ otherindex = -1;
+
+ n = WaitForMultipleObjects(nallhandles, handles, FALSE, ticks);
+
+ if ((unsigned)(n - WAIT_OBJECT_0) < (unsigned)nhandles) {
+ handle_got_event(handles[n - WAIT_OBJECT_0]);
+ } else if (netindex >= 0 && n == WAIT_OBJECT_0 + netindex) {
+ WSANETWORKEVENTS things;
+ SOCKET socket;
+ extern SOCKET first_socket(int *), next_socket(int *);
+ extern int select_result(WPARAM, LPARAM);
+ int i, socketstate;
+
+ /*
+ * We must not call select_result() for any socket
+ * until we have finished enumerating within the
+ * tree. This is because select_result() may close
+ * the socket and modify the tree.
+ */
+ /* Count the active sockets. */
+ i = 0;
+ for (socket = first_socket(&socketstate);
+ socket != INVALID_SOCKET;
+ socket = next_socket(&socketstate)) i++;
+
+ /* Expand the buffer if necessary. */
+ sklist = snewn(i, SOCKET);
+
+ /* Retrieve the sockets into sklist. */
+ skcount = 0;
+ for (socket = first_socket(&socketstate);
+ socket != INVALID_SOCKET;
+ socket = next_socket(&socketstate)) {
+ sklist[skcount++] = socket;
+ }
+
+ /* Now we're done enumerating; go through the list. */
+ for (i = 0; i < skcount; i++) {
+ WPARAM wp;
+ socket = sklist[i];
+ wp = (WPARAM) socket;
+ if (!p_WSAEnumNetworkEvents(socket, NULL, &things)) {
+ static const struct { int bit, mask; } eventtypes[] = {
+ {FD_CONNECT_BIT, FD_CONNECT},
+ {FD_READ_BIT, FD_READ},
+ {FD_CLOSE_BIT, FD_CLOSE},
+ {FD_OOB_BIT, FD_OOB},
+ {FD_WRITE_BIT, FD_WRITE},
+ {FD_ACCEPT_BIT, FD_ACCEPT},
+ };
+ int e;
+
+ noise_ultralight(socket);
+ noise_ultralight(things.lNetworkEvents);
+
+ for (e = 0; e < lenof(eventtypes); e++)
+ if (things.lNetworkEvents & eventtypes[e].mask) {
+ LPARAM lp;
+ int err = things.iErrorCode[eventtypes[e].bit];
+ lp = WSAMAKESELECTREPLY(eventtypes[e].mask, err);
+ select_result(wp, lp);
+ }
+ }
+ }
+
+ sfree(sklist);
+ }
+
+ sfree(handles);
+
+ if (n == WAIT_TIMEOUT) {
+ now = next;
+ } else {
+ now = GETTICKCOUNT();
+ }
+
+ if (otherindex >= 0 && n == WAIT_OBJECT_0 + otherindex)
+ return 1;
+
+ return 0;
+}
+
+/*
+ * Wait for some network data and process it.
+ *
+ * We have two variants of this function. One uses select() so that
+ * it's compatible with WinSock 1. The other uses WSAEventSelect
+ * and MsgWaitForMultipleObjects, so that we can consistently use
+ * WSAEventSelect throughout; this enables us to also implement
+ * ssh_sftp_get_cmdline() using a parallel mechanism.
+ */
+int ssh_sftp_loop_iteration(void)
+{
+ if (p_WSAEventSelect == NULL) {
+ fd_set readfds;
+ int ret;
+ long now = GETTICKCOUNT();
+
+ 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;
+ struct timeval tv, *ptv;
+
+ if (run_timers(now, &next)) {
+ ticks = next - GETTICKCOUNT();
+ if (ticks <= 0)
+ ticks = 1; /* just in case */
+ tv.tv_sec = ticks / 1000;
+ tv.tv_usec = ticks % 1000 * 1000;
+ ptv = &tv;
+ } else {
+ ptv = NULL;
+ }
+
+ FD_ZERO(&readfds);
+ FD_SET(sftp_ssh_socket, &readfds);
+ ret = p_select(1, &readfds, NULL, NULL, ptv);
+
+ if (ret < 0)
+ return -1; /* doom */
+ else if (ret == 0)
+ now = next;
+ else
+ now = GETTICKCOUNT();
+
+ } while (ret == 0);
+
+ select_result((WPARAM) sftp_ssh_socket, (LPARAM) FD_READ);
+
+ return 0;
+ } else {
+ return do_eventsel_loop(INVALID_HANDLE_VALUE);
+ }
+}
+
+/*
+ * Read a command line from standard input.
+ *
+ * In the presence of WinSock 2, we can use WSAEventSelect to
+ * mediate between the socket and stdin, meaning we can send
+ * keepalives and respond to server events even while waiting at
+ * the PSFTP command prompt. Without WS2, we fall back to a simple
+ * fgets.
+ */
+struct command_read_ctx {
+ HANDLE event;
+ char *line;
+};
+
+static DWORD WINAPI command_read_thread(void *param)
+{
+ struct command_read_ctx *ctx = (struct command_read_ctx *) param;
+
+ ctx->line = fgetline(stdin);
+
+ SetEvent(ctx->event);
+
+ return 0;
+}
+
+char *ssh_sftp_get_cmdline(char *prompt, int no_fds_ok)
+{
+ int ret;
+ struct command_read_ctx actx, *ctx = &actx;
+ DWORD threadid;
+
+ fputs(prompt, stdout);
+ fflush(stdout);
+
+ if ((sftp_ssh_socket == INVALID_SOCKET && no_fds_ok) ||
+ p_WSAEventSelect == NULL) {
+ return fgetline(stdin); /* very simple */
+ }
+
+ /*
+ * Create a second thread to read from stdin. Process network
+ * and timing events until it terminates.
+ */
+ ctx->event = CreateEvent(NULL, FALSE, FALSE, NULL);
+ ctx->line = NULL;
+
+ if (!CreateThread(NULL, 0, command_read_thread,
+ ctx, 0, &threadid)) {
+ fprintf(stderr, "Unable to create command input thread\n");
+ cleanup_exit(1);
+ }
+
+ do {
+ ret = do_eventsel_loop(ctx->event);
+
+ /* Error return can only occur if netevent==NULL, and it ain't. */
+ assert(ret >= 0);
+ } while (ret == 0);
+
+ return ctx->line;
+}
+
+/* ----------------------------------------------------------------------
+ * Main program. Parse arguments etc.
+ */
+int main(int argc, char *argv[])
+{
+ int ret;
+
+ ret = psftp_main(argc, argv);
+
+ return ret;
+}
--- /dev/null
+/*
+ * winstore.c: Windows-specific implementation of the interface
+ * defined in storage.h.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <limits.h>
+#include "putty.h"
+#include "storage.h"
+
+#include <shlobj.h>
+#ifndef CSIDL_APPDATA
+#define CSIDL_APPDATA 0x001a
+#endif
+#ifndef CSIDL_LOCAL_APPDATA
+#define CSIDL_LOCAL_APPDATA 0x001c
+#endif
+
+static const char *const reg_jumplist_key = PUTTY_REG_POS "\\Jumplist";
+static const char *const reg_jumplist_value = "Recent sessions";
+static const char *const puttystr = PUTTY_REG_POS "\\Sessions";
+
+static const char hex[16] = "0123456789ABCDEF";
+
+static int tried_shgetfolderpath = FALSE;
+static HMODULE shell32_module = NULL;
+DECL_WINDOWS_FUNCTION(static, HRESULT, SHGetFolderPathA,
+ (HWND, int, HANDLE, DWORD, LPSTR));
+
+static void mungestr(const char *in, char *out)
+{
+ int candot = 0;
+
+ while (*in) {
+ if (*in == ' ' || *in == '\\' || *in == '*' || *in == '?' ||
+ *in == '%' || *in < ' ' || *in > '~' || (*in == '.'
+ && !candot)) {
+ *out++ = '%';
+ *out++ = hex[((unsigned char) *in) >> 4];
+ *out++ = hex[((unsigned char) *in) & 15];
+ } else
+ *out++ = *in;
+ in++;
+ candot = 1;
+ }
+ *out = '\0';
+ return;
+}
+
+static void unmungestr(const char *in, char *out, int outlen)
+{
+ while (*in) {
+ if (*in == '%' && in[1] && in[2]) {
+ int i, j;
+
+ i = in[1] - '0';
+ i -= (i > 9 ? 7 : 0);
+ j = in[2] - '0';
+ j -= (j > 9 ? 7 : 0);
+
+ *out++ = (i << 4) + j;
+ if (!--outlen)
+ return;
+ in += 3;
+ } else {
+ *out++ = *in++;
+ if (!--outlen)
+ return;
+ }
+ }
+ *out = '\0';
+ return;
+}
+
+void *open_settings_w(const char *sessionname, char **errmsg)
+{
+ HKEY subkey1, sesskey;
+ int ret;
+ char *p;
+
+ *errmsg = NULL;
+
+ if (!sessionname || !*sessionname)
+ sessionname = "Default Settings";
+
+ p = snewn(3 * strlen(sessionname) + 1, char);
+ mungestr(sessionname, p);
+
+ ret = RegCreateKey(HKEY_CURRENT_USER, puttystr, &subkey1);
+ if (ret != ERROR_SUCCESS) {
+ sfree(p);
+ *errmsg = dupprintf("Unable to create registry key\n"
+ "HKEY_CURRENT_USER\\%s", puttystr);
+ return NULL;
+ }
+ ret = RegCreateKey(subkey1, p, &sesskey);
+ RegCloseKey(subkey1);
+ if (ret != ERROR_SUCCESS) {
+ *errmsg = dupprintf("Unable to create registry key\n"
+ "HKEY_CURRENT_USER\\%s\\%s", puttystr, p);
+ sfree(p);
+ return NULL;
+ }
+ sfree(p);
+ return (void *) sesskey;
+}
+
+void write_setting_s(void *handle, const char *key, const char *value)
+{
+ if (handle)
+ RegSetValueEx((HKEY) handle, key, 0, REG_SZ, value,
+ 1 + strlen(value));
+}
+
+void write_setting_i(void *handle, const char *key, int value)
+{
+ if (handle)
+ RegSetValueEx((HKEY) handle, key, 0, REG_DWORD,
+ (CONST BYTE *) &value, sizeof(value));
+}
+
+void close_settings_w(void *handle)
+{
+ RegCloseKey((HKEY) handle);
+}
+
+void *open_settings_r(const char *sessionname)
+{
+ HKEY subkey1, sesskey;
+ char *p;
+
+ if (!sessionname || !*sessionname)
+ sessionname = "Default Settings";
+
+ p = snewn(3 * strlen(sessionname) + 1, char);
+ mungestr(sessionname, p);
+
+ if (RegOpenKey(HKEY_CURRENT_USER, puttystr, &subkey1) != ERROR_SUCCESS) {
+ sesskey = NULL;
+ } else {
+ if (RegOpenKey(subkey1, p, &sesskey) != ERROR_SUCCESS) {
+ sesskey = NULL;
+ }
+ RegCloseKey(subkey1);
+ }
+
+ sfree(p);
+
+ return (void *) sesskey;
+}
+
+char *read_setting_s(void *handle, const char *key, char *buffer, int buflen)
+{
+ 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;
+}
+
+int read_setting_i(void *handle, const char *key, int defvalue)
+{
+ DWORD type, val, size;
+ size = sizeof(val);
+
+ if (!handle ||
+ RegQueryValueEx((HKEY) handle, key, 0, &type,
+ (BYTE *) &val, &size) != ERROR_SUCCESS ||
+ size != sizeof(val) || type != REG_DWORD)
+ 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);
+}
+
+void close_settings_r(void *handle)
+{
+ RegCloseKey((HKEY) handle);
+}
+
+void del_settings(const char *sessionname)
+{
+ HKEY subkey1;
+ char *p;
+
+ if (RegOpenKey(HKEY_CURRENT_USER, puttystr, &subkey1) != ERROR_SUCCESS)
+ return;
+
+ p = snewn(3 * strlen(sessionname) + 1, char);
+ mungestr(sessionname, p);
+ RegDeleteKey(subkey1, p);
+ sfree(p);
+
+ RegCloseKey(subkey1);
+
+ remove_session_from_jumplist(sessionname);
+}
+
+struct enumsettings {
+ HKEY key;
+ int i;
+};
+
+void *enum_settings_start(void)
+{
+ struct enumsettings *ret;
+ HKEY key;
+
+ if (RegOpenKey(HKEY_CURRENT_USER, puttystr, &key) != ERROR_SUCCESS)
+ return NULL;
+
+ ret = snew(struct enumsettings);
+ if (ret) {
+ ret->key = key;
+ ret->i = 0;
+ }
+
+ return ret;
+}
+
+char *enum_settings_next(void *handle, char *buffer, int buflen)
+{
+ struct enumsettings *e = (struct enumsettings *) handle;
+ char *otherbuf;
+ otherbuf = snewn(3 * buflen, char);
+ if (RegEnumKey(e->key, e->i++, otherbuf, 3 * buflen) == ERROR_SUCCESS) {
+ unmungestr(otherbuf, buffer, buflen);
+ sfree(otherbuf);
+ return buffer;
+ } else {
+ sfree(otherbuf);
+ return NULL;
+ }
+}
+
+void enum_settings_finish(void *handle)
+{
+ struct enumsettings *e = (struct enumsettings *) handle;
+ RegCloseKey(e->key);
+ sfree(e);
+}
+
+static void hostkey_regname(char *buffer, const char *hostname,
+ int port, const char *keytype)
+{
+ int len;
+ strcpy(buffer, keytype);
+ strcat(buffer, "@");
+ len = strlen(buffer);
+ len += sprintf(buffer + len, "%d:", port);
+ mungestr(hostname, buffer + strlen(buffer));
+}
+
+int verify_host_key(const char *hostname, int port,
+ const char *keytype, const char *key)
+{
+ char *otherstr, *regname;
+ int len;
+ HKEY rkey;
+ DWORD readlen;
+ DWORD type;
+ int ret, compare;
+
+ len = 1 + strlen(key);
+
+ /*
+ * 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)
+ return 1; /* key does not exist in registry */
+
+ readlen = len;
+ ret = RegQueryValueEx(rkey, regname, NULL, &type, otherstr, &readlen);
+
+ if (ret != ERROR_SUCCESS && ret != ERROR_MORE_DATA &&
+ !strcmp(keytype, "rsa")) {
+ /*
+ * Key didn't exist. If the key type is RSA, we'll try
+ * another trick, which is to look up the _old_ key format
+ * under just the hostname and translate that.
+ */
+ char *justhost = regname + 1 + strcspn(regname, ":");
+ char *oldstyle = snewn(len + 10, char); /* safety margin */
+ readlen = len;
+ ret = RegQueryValueEx(rkey, justhost, NULL, &type,
+ oldstyle, &readlen);
+
+ if (ret == ERROR_SUCCESS && type == REG_SZ) {
+ /*
+ * The old format is two old-style bignums separated by
+ * a slash. An old-style bignum is made of groups of
+ * four hex digits: digits are ordered in sensible
+ * (most to least significant) order within each group,
+ * but groups are ordered in silly (least to most)
+ * order within the bignum. The new format is two
+ * ordinary C-format hex numbers (0xABCDEFG...XYZ, with
+ * A nonzero except in the special case 0x0, which
+ * doesn't appear anyway in RSA keys) separated by a
+ * comma. All hex digits are lowercase in both formats.
+ */
+ char *p = otherstr;
+ char *q = oldstyle;
+ int i, j;
+
+ for (i = 0; i < 2; i++) {
+ int ndigits, nwords;
+ *p++ = '0';
+ *p++ = 'x';
+ ndigits = strcspn(q, "/"); /* find / or end of string */
+ nwords = ndigits / 4;
+ /* now trim ndigits to remove leading zeros */
+ while (q[(ndigits - 1) ^ 3] == '0' && ndigits > 1)
+ ndigits--;
+ /* now move digits over to new string */
+ for (j = 0; j < ndigits; j++)
+ p[ndigits - 1 - j] = q[j ^ 3];
+ p += ndigits;
+ q += nwords * 4;
+ if (*q) {
+ q++; /* eat the slash */
+ *p++ = ','; /* add a comma */
+ }
+ *p = '\0'; /* terminate the string */
+ }
+
+ /*
+ * Now _if_ this key matches, we'll enter it in the new
+ * format. If not, we'll assume something odd went
+ * wrong, and hyper-cautiously do nothing.
+ */
+ if (!strcmp(otherstr, key))
+ RegSetValueEx(rkey, regname, 0, REG_SZ, otherstr,
+ strlen(otherstr) + 1);
+ }
+ }
+
+ RegCloseKey(rkey);
+
+ compare = strcmp(otherstr, key);
+
+ sfree(otherstr);
+ sfree(regname);
+
+ if (ret == ERROR_MORE_DATA ||
+ (ret == ERROR_SUCCESS && type == REG_SZ && compare))
+ return 2; /* key is different in registry */
+ else if (ret != ERROR_SUCCESS || type != REG_SZ)
+ return 1; /* key does not exist in registry */
+ else
+ return 0; /* key matched OK in registry */
+}
+
+void store_host_key(const char *hostname, int port,
+ const char *keytype, const char *key)
+{
+ char *regname;
+ HKEY rkey;
+
+ regname = snewn(3 * (strlen(hostname) + strlen(keytype)) + 15, char);
+
+ hostkey_regname(regname, hostname, port, keytype);
+
+ if (RegCreateKey(HKEY_CURRENT_USER, PUTTY_REG_POS "\\SshHostKeys",
+ &rkey) == ERROR_SUCCESS) {
+ RegSetValueEx(rkey, regname, 0, REG_SZ, key, strlen(key) + 1);
+ RegCloseKey(rkey);
+ } /* else key does not exist in registry */
+
+ sfree(regname);
+}
+
+/*
+ * Open (or delete) the random seed file.
+ */
+enum { DEL, OPEN_R, OPEN_W };
+static int try_random_seed(char const *path, int action, HANDLE *ret)
+{
+ if (action == DEL) {
+ remove(path);
+ *ret = INVALID_HANDLE_VALUE;
+ return FALSE; /* so we'll do the next ones too */
+ }
+
+ *ret = CreateFile(path,
+ action == OPEN_W ? GENERIC_WRITE : GENERIC_READ,
+ action == OPEN_W ? 0 : (FILE_SHARE_READ |
+ FILE_SHARE_WRITE),
+ NULL,
+ action == OPEN_W ? CREATE_ALWAYS : OPEN_EXISTING,
+ action == OPEN_W ? FILE_ATTRIBUTE_NORMAL : 0,
+ NULL);
+
+ return (*ret != INVALID_HANDLE_VALUE);
+}
+
+static HANDLE access_random_seed(int action)
+{
+ HKEY rkey;
+ DWORD type, size;
+ HANDLE rethandle;
+ char seedpath[2 * MAX_PATH + 10] = "\0";
+
+ /*
+ * Iterate over a selection of possible random seed paths until
+ * we find one that works.
+ *
+ * We do this iteration separately for reading and writing,
+ * meaning that we will automatically migrate random seed files
+ * if a better location becomes available (by reading from the
+ * best location in which we actually find one, and then
+ * writing to the best location in which we can _create_ one).
+ */
+
+ /*
+ * First, try the location specified by the user in the
+ * Registry, if any.
+ */
+ size = sizeof(seedpath);
+ if (RegOpenKey(HKEY_CURRENT_USER, PUTTY_REG_POS, &rkey) ==
+ ERROR_SUCCESS) {
+ int ret = RegQueryValueEx(rkey, "RandSeedFile",
+ 0, &type, seedpath, &size);
+ if (ret != ERROR_SUCCESS || type != REG_SZ)
+ seedpath[0] = '\0';
+ RegCloseKey(rkey);
+
+ if (*seedpath && try_random_seed(seedpath, action, &rethandle))
+ return rethandle;
+ }
+
+ /*
+ * Next, try the user's local Application Data directory,
+ * followed by their non-local one. This is found using the
+ * SHGetFolderPath function, which won't be present on all
+ * versions of Windows.
+ */
+ if (!tried_shgetfolderpath) {
+ /* This is likely only to bear fruit on systems with IE5+
+ * installed, or WinMe/2K+. There is some faffing with
+ * SHFOLDER.DLL we could do to try to find an equivalent
+ * on older versions of Windows if we cared enough.
+ * However, the invocation below requires IE5+ anyway,
+ * so stuff that. */
+ shell32_module = load_system32_dll("shell32.dll");
+ GET_WINDOWS_FUNCTION(shell32_module, SHGetFolderPathA);
+ tried_shgetfolderpath = TRUE;
+ }
+ if (p_SHGetFolderPathA) {
+ if (SUCCEEDED(p_SHGetFolderPathA(NULL, CSIDL_LOCAL_APPDATA,
+ NULL, SHGFP_TYPE_CURRENT, seedpath))) {
+ strcat(seedpath, "\\PUTTY.RND");
+ if (try_random_seed(seedpath, action, &rethandle))
+ return rethandle;
+ }
+
+ if (SUCCEEDED(p_SHGetFolderPathA(NULL, CSIDL_APPDATA,
+ NULL, SHGFP_TYPE_CURRENT, seedpath))) {
+ strcat(seedpath, "\\PUTTY.RND");
+ if (try_random_seed(seedpath, action, &rethandle))
+ return rethandle;
+ }
+ }
+
+ /*
+ * Failing that, try %HOMEDRIVE%%HOMEPATH% as a guess at the
+ * user's home directory.
+ */
+ {
+ int len, ret;
+
+ len =
+ GetEnvironmentVariable("HOMEDRIVE", seedpath,
+ sizeof(seedpath));
+ ret =
+ GetEnvironmentVariable("HOMEPATH", seedpath + len,
+ sizeof(seedpath) - len);
+ if (ret != 0) {
+ strcat(seedpath, "\\PUTTY.RND");
+ if (try_random_seed(seedpath, action, &rethandle))
+ return rethandle;
+ }
+ }
+
+ /*
+ * And finally, fall back to C:\WINDOWS.
+ */
+ GetWindowsDirectory(seedpath, sizeof(seedpath));
+ strcat(seedpath, "\\PUTTY.RND");
+ if (try_random_seed(seedpath, action, &rethandle))
+ return rethandle;
+
+ /*
+ * If even that failed, give up.
+ */
+ return INVALID_HANDLE_VALUE;
+}
+
+void read_random_seed(noise_consumer_t consumer)
+{
+ HANDLE seedf = access_random_seed(OPEN_R);
+
+ if (seedf != INVALID_HANDLE_VALUE) {
+ while (1) {
+ char buf[1024];
+ DWORD len;
+
+ if (ReadFile(seedf, buf, sizeof(buf), &len, NULL) && len)
+ consumer(buf, len);
+ else
+ break;
+ }
+ CloseHandle(seedf);
+ }
+}
+
+void write_random_seed(void *data, int len)
+{
+ HANDLE seedf = access_random_seed(OPEN_W);
+
+ if (seedf != INVALID_HANDLE_VALUE) {
+ DWORD lenwritten;
+
+ WriteFile(seedf, data, len, &lenwritten, NULL);
+ CloseHandle(seedf);
+ }
+}
+
+/*
+ * Internal function supporting the jump list registry code. All the
+ * functions to add, remove and read the list have substantially
+ * similar content, so this is a generalisation of all of them which
+ * transforms the list in the registry by prepending 'add' (if
+ * non-null), removing 'rem' from what's left (if non-null), and
+ * returning the resulting concatenated list of strings in 'out' (if
+ * non-null).
+ */
+static int transform_jumplist_registry
+ (const char *add, const char *rem, char **out)
+{
+ int ret;
+ HKEY pjumplist_key, psettings_tmp;
+ DWORD type;
+ int value_length;
+ char *old_value, *new_value;
+ char *piterator_old, *piterator_new, *piterator_tmp;
+
+ ret = RegCreateKeyEx(HKEY_CURRENT_USER, reg_jumplist_key, 0, NULL,
+ REG_OPTION_NON_VOLATILE, (KEY_READ | KEY_WRITE), NULL,
+ &pjumplist_key, NULL);
+ if (ret != ERROR_SUCCESS) {
+ return JUMPLISTREG_ERROR_KEYOPENCREATE_FAILURE;
+ }
+
+ /* Get current list of saved sessions in the registry. */
+ value_length = 200;
+ old_value = snewn(value_length, char);
+ ret = RegQueryValueEx(pjumplist_key, reg_jumplist_value, NULL, &type,
+ old_value, &value_length);
+ /* When the passed buffer is too small, ERROR_MORE_DATA is
+ * returned and the required size is returned in the length
+ * argument. */
+ if (ret == ERROR_MORE_DATA) {
+ sfree(old_value);
+ old_value = snewn(value_length, char);
+ ret = RegQueryValueEx(pjumplist_key, reg_jumplist_value, NULL, &type,
+ old_value, &value_length);
+ }
+
+ if (ret == ERROR_FILE_NOT_FOUND) {
+ /* Value doesn't exist yet. Start from an empty value. */
+ *old_value = '\0';
+ *(old_value + 1) = '\0';
+ } else if (ret != ERROR_SUCCESS) {
+ /* Some non-recoverable error occurred. */
+ sfree(old_value);
+ RegCloseKey(pjumplist_key);
+ return JUMPLISTREG_ERROR_VALUEREAD_FAILURE;
+ } else if (type != REG_MULTI_SZ) {
+ /* The value present in the registry has the wrong type: we
+ * try to delete it and start from an empty value. */
+ ret = RegDeleteValue(pjumplist_key, reg_jumplist_value);
+ if (ret != ERROR_SUCCESS) {
+ sfree(old_value);
+ RegCloseKey(pjumplist_key);
+ return JUMPLISTREG_ERROR_VALUEREAD_FAILURE;
+ }
+
+ *old_value = '\0';
+ *(old_value + 1) = '\0';
+ }
+
+ /* Check validity of registry data: REG_MULTI_SZ value must end
+ * with \0\0. */
+ piterator_tmp = old_value;
+ while (((piterator_tmp - old_value) < (value_length - 1)) &&
+ !(*piterator_tmp == '\0' && *(piterator_tmp+1) == '\0')) {
+ ++piterator_tmp;
+ }
+
+ if ((piterator_tmp - old_value) >= (value_length-1)) {
+ /* Invalid value. Start from an empty value. */
+ *old_value = '\0';
+ *(old_value + 1) = '\0';
+ }
+
+ /*
+ * Modify the list, if we're modifying.
+ */
+ if (add || rem) {
+ /* Walk through the existing list and construct the new list of
+ * saved sessions. */
+ new_value = snewn(value_length + (add ? strlen(add) + 1 : 0), char);
+ piterator_new = new_value;
+ piterator_old = old_value;
+
+ /* First add the new item to the beginning of the list. */
+ if (add) {
+ strcpy(piterator_new, add);
+ piterator_new += strlen(piterator_new) + 1;
+ }
+ /* Now add the existing list, taking care to leave out the removed
+ * item, if it was already in the existing list. */
+ while (*piterator_old != '\0') {
+ if (!rem || strcmp(piterator_old, rem) != 0) {
+ /* Check if this is a valid session, otherwise don't add. */
+ psettings_tmp = open_settings_r(piterator_old);
+ if (psettings_tmp != NULL) {
+ close_settings_r(psettings_tmp);
+ strcpy(piterator_new, piterator_old);
+ piterator_new += strlen(piterator_new) + 1;
+ }
+ }
+ piterator_old += strlen(piterator_old) + 1;
+ }
+ *piterator_new = '\0';
+ ++piterator_new;
+
+ /* Save the new list to the registry. */
+ ret = RegSetValueEx(pjumplist_key, reg_jumplist_value, 0, REG_MULTI_SZ,
+ new_value, piterator_new - new_value);
+
+ sfree(old_value);
+ old_value = new_value;
+ } else
+ ret = ERROR_SUCCESS;
+
+ /*
+ * Either return or free the result.
+ */
+ if (out)
+ *out = old_value;
+ else
+ sfree(old_value);
+
+ /* Clean up and return. */
+ RegCloseKey(pjumplist_key);
+
+ if (ret != ERROR_SUCCESS) {
+ return JUMPLISTREG_ERROR_VALUEWRITE_FAILURE;
+ } else {
+ return JUMPLISTREG_OK;
+ }
+}
+
+/* Adds a new entry to the jumplist entries in the registry. */
+int add_to_jumplist_registry(const char *item)
+{
+ return transform_jumplist_registry(item, item, NULL);
+}
+
+/* Removes an item from the jumplist entries in the registry. */
+int remove_from_jumplist_registry(const char *item)
+{
+ return transform_jumplist_registry(NULL, item, NULL);
+}
+
+/* Returns the jumplist entries from the registry. Caller must free
+ * the returned pointer. */
+char *get_jumplist_registry_entries (void)
+{
+ char *list_value;
+
+ if (transform_jumplist_registry(NULL,NULL,&list_value) != ERROR_SUCCESS) {
+ list_value = snewn(2, char);
+ *list_value = '\0';
+ *(list_value + 1) = '\0';
+ }
+ return list_value;
+}
+
+/*
+ * Recursively delete a registry key and everything under it.
+ */
+static void registry_recursive_remove(HKEY key)
+{
+ DWORD i;
+ char name[MAX_PATH + 1];
+ HKEY subkey;
+
+ i = 0;
+ while (RegEnumKey(key, i, name, sizeof(name)) == ERROR_SUCCESS) {
+ if (RegOpenKey(key, name, &subkey) == ERROR_SUCCESS) {
+ registry_recursive_remove(subkey);
+ RegCloseKey(subkey);
+ }
+ RegDeleteKey(key, name);
+ }
+}
+
+void cleanup_all(void)
+{
+ HKEY key;
+ int ret;
+ char name[MAX_PATH + 1];
+
+ /* ------------------------------------------------------------
+ * Wipe out the random seed file, in all of its possible
+ * locations.
+ */
+ access_random_seed(DEL);
+
+ /* ------------------------------------------------------------
+ * Ask Windows to delete any jump list information associated
+ * with this installation of PuTTY.
+ */
+ clear_jumplist();
+
+ /* ------------------------------------------------------------
+ * Destroy all registry information associated with PuTTY.
+ */
+
+ /*
+ * Open the main PuTTY registry key and remove everything in it.
+ */
+ if (RegOpenKey(HKEY_CURRENT_USER, PUTTY_REG_POS, &key) ==
+ ERROR_SUCCESS) {
+ registry_recursive_remove(key);
+ RegCloseKey(key);
+ }
+ /*
+ * Now open the parent key and remove the PuTTY main key. Once
+ * we've done that, see if the parent key has any other
+ * children.
+ */
+ if (RegOpenKey(HKEY_CURRENT_USER, PUTTY_REG_PARENT,
+ &key) == ERROR_SUCCESS) {
+ RegDeleteKey(key, PUTTY_REG_PARENT_CHILD);
+ ret = RegEnumKey(key, 0, name, sizeof(name));
+ RegCloseKey(key);
+ /*
+ * If the parent key had no other children, we must delete
+ * it in its turn. That means opening the _grandparent_
+ * key.
+ */
+ if (ret != ERROR_SUCCESS) {
+ if (RegOpenKey(HKEY_CURRENT_USER, PUTTY_REG_GPARENT,
+ &key) == ERROR_SUCCESS) {
+ RegDeleteKey(key, PUTTY_REG_GPARENT_CHILD);
+ RegCloseKey(key);
+ }
+ }
+ }
+ /*
+ * Now we're done.
+ */
+}
--- /dev/null
+/*
+ * winstuff.h: Windows-specific inter-module stuff.
+ */
+
+#ifndef PUTTY_WINSTUFF_H
+#define PUTTY_WINSTUFF_H
+
+#ifndef AUTO_WINSOCK
+#include <winsock2.h>
+#endif
+#include <windows.h>
+#include <stdio.h> /* for FILENAME_MAX */
+
+#include "tree234.h"
+
+#include "winhelp.h"
+
+struct Filename {
+ char path[FILENAME_MAX];
+};
+#define f_open(filename, mode, isprivate) ( fopen((filename).path, (mode)) )
+
+struct FontSpec {
+ char name[64];
+ int isbold;
+ int height;
+ int charset;
+};
+
+#ifndef CLEARTYPE_QUALITY
+#define CLEARTYPE_QUALITY 5
+#endif
+#define FONT_QUALITY(fq) ( \
+ (fq) == FQ_DEFAULT ? DEFAULT_QUALITY : \
+ (fq) == FQ_ANTIALIASED ? ANTIALIASED_QUALITY : \
+ (fq) == FQ_NONANTIALIASED ? NONANTIALIASED_QUALITY : \
+ CLEARTYPE_QUALITY)
+
+#define PLATFORM_IS_UTF16 /* enable UTF-16 processing when exchanging
+ * wchar_t strings with environment */
+
+/*
+ * Where we can, we use GetWindowLongPtr and friends because they're
+ * more useful on 64-bit platforms, but they're a relatively recent
+ * innovation, missing from VC++ 6 and older MinGW. Degrade nicely.
+ * (NB that on some systems, some of these things are available but
+ * not others...)
+ */
+
+#ifndef GCLP_HCURSOR
+/* GetClassLongPtr and friends */
+#undef GetClassLongPtr
+#define GetClassLongPtr GetClassLong
+#undef SetClassLongPtr
+#define SetClassLongPtr SetClassLong
+#define GCLP_HCURSOR GCL_HCURSOR
+/* GetWindowLongPtr and friends */
+#undef GetWindowLongPtr
+#define GetWindowLongPtr GetWindowLong
+#undef SetWindowLongPtr
+#define SetWindowLongPtr SetWindowLong
+#undef GWLP_USERDATA
+#define GWLP_USERDATA GWL_USERDATA
+#undef DWLP_MSGRESULT
+#define DWLP_MSGRESULT DWL_MSGRESULT
+/* Since we've clobbered the above functions, we should clobber the
+ * associated type regardless of whether it's defined. */
+#undef LONG_PTR
+#define LONG_PTR LONG
+#endif
+
+#define BOXFLAGS DLGWINDOWEXTRA
+#define BOXRESULT (DLGWINDOWEXTRA + sizeof(LONG_PTR))
+#define DF_END 0x0001
+
+/*
+ * 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
+ * in "name". If you're not sure, use this one.
+ *
+ * - GET_WINDOWS_FUNCTION_PP allows "name" to be redirected via
+ * preprocessor definitions like "#define foo bar"; this is principally
+ * intended for the ANSI/Unicode DoSomething/DoSomethingA/DoSomethingW.
+ * If your function has an argument of type "LPTSTR" or similar, this
+ * is the variant to use.
+ * (However, it can't always be used, as it trips over more complicated
+ * macro trickery such as the WspiapiGetAddrInfo wrapper for getaddrinfo.)
+ *
+ * (DECL_WINDOWS_FUNCTION works with both these variants.)
+ */
+#define DECL_WINDOWS_FUNCTION(linkage, rettype, name, params) \
+ typedef rettype (WINAPI *t_##name) params; \
+ linkage t_##name p_##name
+#define STR1(x) #x
+#define STR(x) STR1(x)
+#define GET_WINDOWS_FUNCTION_PP(module, name) \
+ (p_##name = module ? (t_##name) GetProcAddress(module, STR(name)) : NULL)
+#define GET_WINDOWS_FUNCTION(module, name) \
+ (p_##name = module ? (t_##name) GetProcAddress(module, #name) : NULL)
+
+/*
+ * Global variables. Most modules declare these `extern', but
+ * window.c will do `#define PUTTY_DO_GLOBALS' before including this
+ * module, and so will get them properly defined.
+*/
+#ifndef GLOBAL
+#ifdef PUTTY_DO_GLOBALS
+#define GLOBAL
+#else
+#define GLOBAL extern
+#endif
+#endif
+
+#ifndef DONE_TYPEDEFS
+#define DONE_TYPEDEFS
+typedef struct config_tag Config;
+typedef struct backend_tag Backend;
+typedef struct terminal_tag Terminal;
+#endif
+
+#define PUTTY_REG_POS "Software\\SimonTatham\\PuTTY"
+#define PUTTY_REG_PARENT "Software\\SimonTatham"
+#define PUTTY_REG_PARENT_CHILD "PuTTY"
+#define PUTTY_REG_GPARENT "Software"
+#define PUTTY_REG_GPARENT_CHILD "SimonTatham"
+
+/* Result values for the jumplist registry functions. */
+#define JUMPLISTREG_OK 0
+#define JUMPLISTREG_ERROR_INVALID_PARAMETER 1
+#define JUMPLISTREG_ERROR_KEYOPENCREATE_FAILURE 2
+#define JUMPLISTREG_ERROR_VALUEREAD_FAILURE 3
+#define JUMPLISTREG_ERROR_VALUEWRITE_FAILURE 4
+#define JUMPLISTREG_ERROR_INVALID_VALUE 5
+
+#define PUTTY_HELP_FILE "putty.hlp"
+#define PUTTY_CHM_FILE "putty.chm"
+#define PUTTY_HELP_CONTENTS "putty.cnt"
+
+#define GETTICKCOUNT GetTickCount
+#define CURSORBLINK GetCaretBlinkTime()
+#define TICKSPERSEC 1000 /* GetTickCount returns milliseconds */
+
+#define DEFAULT_CODEPAGE CP_ACP
+
+typedef HDC Context;
+
+typedef unsigned int uint32; /* int is 32-bits on Win32 and Win64. */
+#define PUTTY_UINT32_DEFINED
+
+#ifndef NO_GSSAPI
+/*
+ * GSS-API stuff
+ */
+#define GSS_CC CALLBACK
+/*
+typedef struct Ssh_gss_buf {
+ size_t length;
+ char *value;
+} Ssh_gss_buf;
+
+#define SSH_GSS_EMPTY_BUF (Ssh_gss_buf) {0,NULL}
+typedef void *Ssh_gss_name;
+*/
+#endif
+
+/*
+ * Window handles for the windows that can be running during a
+ * PuTTY session.
+ */
+GLOBAL HWND hwnd; /* the main terminal window */
+GLOBAL HWND logbox;
+
+/*
+ * The all-important instance handle.
+ */
+GLOBAL HINSTANCE hinst;
+
+/*
+ * Help file stuff in winhelp.c.
+ */
+void init_help(void);
+void shutdown_help(void);
+int has_help(void);
+void launch_help(HWND hwnd, const char *topic);
+void quit_help(HWND hwnd);
+
+/*
+ * The terminal and logging context are notionally local to the
+ * Windows front end, but they must be shared between window.c and
+ * windlg.c. Likewise the saved-sessions list.
+ */
+GLOBAL Terminal *term;
+GLOBAL void *logctx;
+
+#define WM_NETEVENT (WM_APP + 5)
+
+/*
+ * On Windows, we send MA_2CLK as the only event marking the second
+ * press of a mouse button. Compare unix.h.
+ */
+#define MULTICLICK_ONLY_EVENT 1
+
+/*
+ * On Windows, data written to the clipboard must be NUL-terminated.
+ */
+#define SELECTION_NUL_TERMINATED 1
+
+/*
+ * On Windows, copying to the clipboard terminates lines with CRLF.
+ */
+#define SEL_NL { 13, 10 }
+
+/*
+ * sk_getxdmdata() does not exist under Windows (not that I
+ * couldn't write it if I wanted to, but I haven't bothered), so
+ * it's a macro which always returns NULL. With any luck this will
+ * cause the compiler to notice it can optimise away the
+ * implementation of XDM-AUTHORIZATION-1 in x11fwd.c :-)
+ */
+#define sk_getxdmdata(socket, lenp) (NULL)
+
+/*
+ * File-selector filter strings used in the config box. On Windows,
+ * these strings are of exactly the type needed to go in
+ * `lpstrFilter' in an OPENFILENAME structure.
+ */
+#define FILTER_KEY_FILES ("PuTTY Private Key Files (*.ppk)\0*.ppk\0" \
+ "All Files (*.*)\0*\0\0\0")
+#define FILTER_WAVE_FILES ("Wave Files (*.wav)\0*.WAV\0" \
+ "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.
+ */
+#define TIMING_SYNC
+#define TIMING_SYNC_TICKCOUNT
+
+/*
+ * 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
+ * here they are.
+ */
+DECL_WINDOWS_FUNCTION(GLOBAL, int, WSAAsyncSelect,
+ (SOCKET, HWND, u_int, long));
+DECL_WINDOWS_FUNCTION(GLOBAL, int, WSAEventSelect,
+ (SOCKET, WSAEVENT, long));
+DECL_WINDOWS_FUNCTION(GLOBAL, int, select,
+ (int, fd_set FAR *, fd_set FAR *,
+ fd_set FAR *, const struct timeval FAR *));
+DECL_WINDOWS_FUNCTION(GLOBAL, int, WSAGetLastError, (void));
+DECL_WINDOWS_FUNCTION(GLOBAL, int, WSAEnumNetworkEvents,
+ (SOCKET, WSAEVENT, LPWSANETWORKEVENTS));
+
+extern int socket_writable(SOCKET skt);
+
+extern void socket_reselect_all(void);
+
+/*
+ * Exports from winctrls.c.
+ */
+
+struct ctlpos {
+ HWND hwnd;
+ WPARAM font;
+ int dlu4inpix;
+ int ypos, width;
+ int xoff;
+ int boxystart, boxid;
+ char *boxtext;
+};
+
+/*
+ * Exports from winutils.c.
+ */
+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);
+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.
+ */
+struct prefslist {
+ int listid, upbid, dnbid;
+ int srcitem;
+ int dummyitem;
+ int dragging;
+};
+
+/*
+ * This structure is passed to event handler functions as the `dlg'
+ * parameter, and hence is passed back to winctrls access functions.
+ */
+struct dlgparam {
+ HWND hwnd; /* the hwnd of the dialog box */
+ struct winctrls *controltrees[8]; /* can have several of these */
+ int nctrltrees;
+ char *wintitle; /* title of actual window */
+ char *errtitle; /* title of error sub-messageboxes */
+ void *data; /* data to pass in refresh events */
+ union control *focused, *lastfocused; /* which ctrl has focus now/before */
+ char shortcuts[128]; /* track which shortcuts in use */
+ int coloursel_wanted; /* has an event handler asked for
+ * a colour selector? */
+ struct { unsigned char r, g, b, ok; } coloursel_result; /* 0-255 */
+ tree234 *privdata; /* stores per-control private data */
+ int ended, endresult; /* has the dialog been ended? */
+ int fixed_pitch_fonts; /* are we constrained to fixed fonts? */
+};
+
+/*
+ * Exports from winctrls.c.
+ */
+void ctlposinit(struct ctlpos *cp, HWND hwnd,
+ int leftborder, int rightborder, int topborder);
+HWND doctl(struct ctlpos *cp, RECT r,
+ char *wclass, int wstyle, int exstyle, char *wtext, int wid);
+void bartitle(struct ctlpos *cp, char *name, int id);
+void beginbox(struct ctlpos *cp, char *name, int idbox);
+void endbox(struct ctlpos *cp);
+void editboxfw(struct ctlpos *cp, int password, char *text,
+ int staticid, int editid);
+void radioline(struct ctlpos *cp, char *text, int id, int nacross, ...);
+void bareradioline(struct ctlpos *cp, int nacross, ...);
+void radiobig(struct ctlpos *cp, char *text, int id, ...);
+void checkbox(struct ctlpos *cp, char *text, int id);
+void statictext(struct ctlpos *cp, char *text, int lines, int id);
+void staticbtn(struct ctlpos *cp, char *stext, int sid,
+ char *btext, int bid);
+void static2btn(struct ctlpos *cp, char *stext, int sid,
+ char *btext1, int bid1, char *btext2, int bid2);
+void staticedit(struct ctlpos *cp, char *stext,
+ int sid, int eid, int percentedit);
+void staticddl(struct ctlpos *cp, char *stext,
+ int sid, int lid, int percentlist);
+void combobox(struct ctlpos *cp, char *text, int staticid, int listid);
+void staticpassedit(struct ctlpos *cp, char *stext,
+ int sid, int eid, int percentedit);
+void bigeditctrl(struct ctlpos *cp, char *stext,
+ int sid, int eid, int lines);
+void ersatztab(struct ctlpos *cp, char *stext, int sid, int lid, int s2id);
+void editbutton(struct ctlpos *cp, char *stext, int sid,
+ int eid, char *btext, int bid);
+void sesssaver(struct ctlpos *cp, char *text,
+ int staticid, int editid, int listid, ...);
+void envsetter(struct ctlpos *cp, char *stext, int sid,
+ char *e1stext, int e1sid, int e1id,
+ char *e2stext, int e2sid, int e2id,
+ int listid, char *b1text, int b1id, char *b2text, int b2id);
+void charclass(struct ctlpos *cp, char *stext, int sid, int listid,
+ char *btext, int bid, int eid, char *s2text, int s2id);
+void colouredit(struct ctlpos *cp, char *stext, int sid, int listid,
+ char *btext, int bid, ...);
+void prefslist(struct prefslist *hdl, struct ctlpos *cp, int lines,
+ char *stext, int sid, int listid, int upbid, int dnbid);
+int handle_prefslist(struct prefslist *hdl,
+ int *array, int maxmemb,
+ int is_dlmsg, HWND hwnd,
+ WPARAM wParam, LPARAM lParam);
+void progressbar(struct ctlpos *cp, int id);
+void fwdsetter(struct ctlpos *cp, int listid, char *stext, int sid,
+ char *e1stext, int e1sid, int e1id,
+ char *e2stext, int e2sid, int e2id,
+ char *btext, int bid,
+ char *r1text, int r1id, char *r2text, int r2id);
+
+void dlg_auto_set_fixed_pitch_flag(void *dlg);
+int dlg_get_fixed_pitch_flag(void *dlg);
+void dlg_set_fixed_pitch_flag(void *dlg, int flag);
+
+#define MAX_SHORTCUTS_PER_CTRL 16
+
+/*
+ * This structure is what's stored for each `union control' in the
+ * portable-dialog interface.
+ */
+struct winctrl {
+ union control *ctrl;
+ /*
+ * The control may have several components at the Windows
+ * level, with different dialog IDs. To avoid needing N
+ * separate platformsidectrl structures (which could be stored
+ * separately in a tree234 so that lookup by ID worked), we
+ * impose the constraint that those IDs must be in a contiguous
+ * block.
+ */
+ int base_id;
+ int num_ids;
+ /*
+ * Remember what keyboard shortcuts were used by this control,
+ * so that when we remove it again we can take them out of the
+ * list in the dlgparam.
+ */
+ char shortcuts[MAX_SHORTCUTS_PER_CTRL];
+ /*
+ * Some controls need a piece of allocated memory in which to
+ * store temporary data about the control.
+ */
+ void *data;
+};
+/*
+ * And this structure holds a set of the above, in two separate
+ * tree234s so that it can find an item by `union control' or by
+ * dialog ID.
+ */
+struct winctrls {
+ tree234 *byctrl, *byid;
+};
+struct controlset;
+struct controlbox;
+
+void winctrl_init(struct winctrls *);
+void winctrl_cleanup(struct winctrls *);
+void winctrl_add(struct winctrls *, struct winctrl *);
+void winctrl_remove(struct winctrls *, struct winctrl *);
+struct winctrl *winctrl_findbyctrl(struct winctrls *, union control *);
+struct winctrl *winctrl_findbyid(struct winctrls *, int);
+struct winctrl *winctrl_findbyindex(struct winctrls *, int);
+void winctrl_layout(struct dlgparam *dp, struct winctrls *wc,
+ struct ctlpos *cp, struct controlset *s, int *id);
+int winctrl_handle_command(struct dlgparam *dp, UINT msg,
+ WPARAM wParam, LPARAM lParam);
+void winctrl_rem_shortcuts(struct dlgparam *dp, struct winctrl *c);
+int winctrl_context_help(struct dlgparam *dp, HWND hwnd, int id);
+
+void dp_init(struct dlgparam *dp);
+void dp_add_tree(struct dlgparam *dp, struct winctrls *tree);
+void dp_cleanup(struct dlgparam *dp);
+
+/*
+ * Exports from wincfg.c.
+ */
+void win_setup_config_box(struct controlbox *b, HWND *hwndp, int has_help,
+ int midsession, int protocol);
+
+/*
+ * Exports from windlg.c.
+ */
+void defuse_showwindow(void);
+int do_config(void);
+int do_reconfig(HWND, int);
+void showeventlog(HWND);
+void showabout(HWND);
+void force_normal(HWND hwnd);
+void modal_about_box(HWND hwnd);
+void show_help(HWND hwnd);
+
+/*
+ * Exports from winmisc.c.
+ */
+extern OSVERSIONINFO osVersion;
+BOOL init_winver(void);
+HMODULE load_system32_dll(const char *libname);
+
+/*
+ * Exports from sizetip.c.
+ */
+void UpdateSizeTip(HWND src, int cx, int cy);
+void EnableSizeTip(int bEnable);
+
+/*
+ * Exports from unicode.c.
+ */
+struct unicode_data;
+void init_ucs(Config *, struct unicode_data *);
+
+/*
+ * Exports from winhandl.c.
+ */
+#define HANDLE_FLAG_OVERLAPPED 1
+#define HANDLE_FLAG_IGNOREEOF 2
+#define HANDLE_FLAG_UNITBUFFER 4
+struct handle;
+typedef int (*handle_inputfn_t)(struct handle *h, void *data, int len);
+typedef void (*handle_outputfn_t)(struct handle *h, int new_backlog);
+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);
+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);
+
+/*
+ * 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.
+ *
+ * Also, we supply FLAG_SYNCAGENT to force agent requests to be
+ * synchronous in pscp and psftp.
+ */
+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;
+
+/*
+ * Exports from winjump.c.
+ */
+#define JUMPLIST_SUPPORTED /* suppress #defines in putty.h */
+void add_session_to_jumplist(const char * const sessionname);
+void remove_session_from_jumplist(const char * const sessionname);
+void clear_jumplist(void);
+
+/*
+ * Extra functions in winstore.c over and above the interface in
+ * storage.h.
+ *
+ * These functions manipulate the Registry section which mirrors the
+ * current Windows 7 jump list. (Because the real jump list storage is
+ * write-only, we need to keep another copy of whatever we put in it,
+ * so that we can put in a slightly modified version the next time.)
+ */
+
+/* Adds a saved session to the registry jump list mirror. 'item' is a
+ * string naming a saved session. */
+int add_to_jumplist_registry(const char *item);
+
+/* Removes an item from the registry jump list mirror. */
+int remove_from_jumplist_registry(const char *item);
+
+/* Returns the current jump list entries from the registry. Caller
+ * must free the returned pointer, which points to a contiguous
+ * sequence of NUL-terminated strings in memory, terminated with an
+ * empty one. */
+char *get_jumplist_registry_entries(void);
+
+#endif
--- /dev/null
+/*
+ * wintime.c - Avoid trouble with time() returning (time_t)-1 on Windows.
+ */
+
+#include "putty.h"
+#include <time.h>
+
+struct tm ltime(void)
+{
+ SYSTEMTIME st;
+ struct tm tm;
+
+ GetLocalTime(&st);
+ tm.tm_sec=st.wSecond;
+ tm.tm_min=st.wMinute;
+ tm.tm_hour=st.wHour;
+ tm.tm_mday=st.wDay;
+ tm.tm_mon=st.wMonth-1;
+ tm.tm_year=(st.wYear>=1900?st.wYear-1900:0);
+ tm.tm_wday=st.wDayOfWeek;
+ tm.tm_yday=-1; /* GetLocalTime doesn't tell us */
+ tm.tm_isdst=0; /* GetLocalTime doesn't tell us */
+ return tm;
+}
--- /dev/null
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <time.h>
+#include <assert.h>
+
+#include "putty.h"
+#include "terminal.h"
+#include "misc.h"
+
+/* Character conversion arrays; they are usually taken from windows,
+ * the xterm one has the four scanlines that have no unicode 2.0
+ * equivalents mapped to their unicode 3.0 locations.
+ */
+static const WCHAR unitab_xterm_std[32] = {
+ 0x2666, 0x2592, 0x2409, 0x240c, 0x240d, 0x240a, 0x00b0, 0x00b1,
+ 0x2424, 0x240b, 0x2518, 0x2510, 0x250c, 0x2514, 0x253c, 0x23ba,
+ 0x23bb, 0x2500, 0x23bc, 0x23bd, 0x251c, 0x2524, 0x2534, 0x252c,
+ 0x2502, 0x2264, 0x2265, 0x03c0, 0x2260, 0x00a3, 0x00b7, 0x0020
+};
+
+/*
+ * If the codepage is non-zero it's a window codepage, zero means use a
+ * local codepage. The name is always converted to the first of any
+ * duplicate definitions.
+ */
+
+/*
+ * Tables for ISO-8859-{1-10,13-16} derived from those downloaded
+ * 2001-10-02 from <http://www.unicode.org/Public/MAPPINGS/> -- jtn
+ * Table for ISO-8859-11 derived from same on 2002-11-18. -- bjh21
+ */
+
+/* XXX: This could be done algorithmically, but I'm not sure it's
+ * worth the hassle -- jtn */
+/* ISO/IEC 8859-1:1998 (Latin-1, "Western", "West European") */
+static const wchar_t iso_8859_1[] = {
+ 0x00A0, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7,
+ 0x00A8, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF,
+ 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7,
+ 0x00B8, 0x00B9, 0x00BA, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00BF,
+ 0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7,
+ 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF,
+ 0x00D0, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x00D7,
+ 0x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x00DE, 0x00DF,
+ 0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7,
+ 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF,
+ 0x00F0, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7,
+ 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF
+};
+
+/* ISO/IEC 8859-2:1999 (Latin-2, "Central European", "East European") */
+static const wchar_t iso_8859_2[] = {
+ 0x00A0, 0x0104, 0x02D8, 0x0141, 0x00A4, 0x013D, 0x015A, 0x00A7,
+ 0x00A8, 0x0160, 0x015E, 0x0164, 0x0179, 0x00AD, 0x017D, 0x017B,
+ 0x00B0, 0x0105, 0x02DB, 0x0142, 0x00B4, 0x013E, 0x015B, 0x02C7,
+ 0x00B8, 0x0161, 0x015F, 0x0165, 0x017A, 0x02DD, 0x017E, 0x017C,
+ 0x0154, 0x00C1, 0x00C2, 0x0102, 0x00C4, 0x0139, 0x0106, 0x00C7,
+ 0x010C, 0x00C9, 0x0118, 0x00CB, 0x011A, 0x00CD, 0x00CE, 0x010E,
+ 0x0110, 0x0143, 0x0147, 0x00D3, 0x00D4, 0x0150, 0x00D6, 0x00D7,
+ 0x0158, 0x016E, 0x00DA, 0x0170, 0x00DC, 0x00DD, 0x0162, 0x00DF,
+ 0x0155, 0x00E1, 0x00E2, 0x0103, 0x00E4, 0x013A, 0x0107, 0x00E7,
+ 0x010D, 0x00E9, 0x0119, 0x00EB, 0x011B, 0x00ED, 0x00EE, 0x010F,
+ 0x0111, 0x0144, 0x0148, 0x00F3, 0x00F4, 0x0151, 0x00F6, 0x00F7,
+ 0x0159, 0x016F, 0x00FA, 0x0171, 0x00FC, 0x00FD, 0x0163, 0x02D9
+};
+
+/* ISO/IEC 8859-3:1999 (Latin-3, "South European", "Maltese & Esperanto") */
+static const wchar_t iso_8859_3[] = {
+ 0x00A0, 0x0126, 0x02D8, 0x00A3, 0x00A4, 0xFFFD, 0x0124, 0x00A7,
+ 0x00A8, 0x0130, 0x015E, 0x011E, 0x0134, 0x00AD, 0xFFFD, 0x017B,
+ 0x00B0, 0x0127, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x0125, 0x00B7,
+ 0x00B8, 0x0131, 0x015F, 0x011F, 0x0135, 0x00BD, 0xFFFD, 0x017C,
+ 0x00C0, 0x00C1, 0x00C2, 0xFFFD, 0x00C4, 0x010A, 0x0108, 0x00C7,
+ 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF,
+ 0xFFFD, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x0120, 0x00D6, 0x00D7,
+ 0x011C, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x016C, 0x015C, 0x00DF,
+ 0x00E0, 0x00E1, 0x00E2, 0xFFFD, 0x00E4, 0x010B, 0x0109, 0x00E7,
+ 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF,
+ 0xFFFD, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x0121, 0x00F6, 0x00F7,
+ 0x011D, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x016D, 0x015D, 0x02D9
+};
+
+/* ISO/IEC 8859-4:1998 (Latin-4, "North European") */
+static const wchar_t iso_8859_4[] = {
+ 0x00A0, 0x0104, 0x0138, 0x0156, 0x00A4, 0x0128, 0x013B, 0x00A7,
+ 0x00A8, 0x0160, 0x0112, 0x0122, 0x0166, 0x00AD, 0x017D, 0x00AF,
+ 0x00B0, 0x0105, 0x02DB, 0x0157, 0x00B4, 0x0129, 0x013C, 0x02C7,
+ 0x00B8, 0x0161, 0x0113, 0x0123, 0x0167, 0x014A, 0x017E, 0x014B,
+ 0x0100, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x012E,
+ 0x010C, 0x00C9, 0x0118, 0x00CB, 0x0116, 0x00CD, 0x00CE, 0x012A,
+ 0x0110, 0x0145, 0x014C, 0x0136, 0x00D4, 0x00D5, 0x00D6, 0x00D7,
+ 0x00D8, 0x0172, 0x00DA, 0x00DB, 0x00DC, 0x0168, 0x016A, 0x00DF,
+ 0x0101, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x012F,
+ 0x010D, 0x00E9, 0x0119, 0x00EB, 0x0117, 0x00ED, 0x00EE, 0x012B,
+ 0x0111, 0x0146, 0x014D, 0x0137, 0x00F4, 0x00F5, 0x00F6, 0x00F7,
+ 0x00F8, 0x0173, 0x00FA, 0x00FB, 0x00FC, 0x0169, 0x016B, 0x02D9
+};
+
+/* ISO/IEC 8859-5:1999 (Latin/Cyrillic) */
+static const wchar_t iso_8859_5[] = {
+ 0x00A0, 0x0401, 0x0402, 0x0403, 0x0404, 0x0405, 0x0406, 0x0407,
+ 0x0408, 0x0409, 0x040A, 0x040B, 0x040C, 0x00AD, 0x040E, 0x040F,
+ 0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415, 0x0416, 0x0417,
+ 0x0418, 0x0419, 0x041A, 0x041B, 0x041C, 0x041D, 0x041E, 0x041F,
+ 0x0420, 0x0421, 0x0422, 0x0423, 0x0424, 0x0425, 0x0426, 0x0427,
+ 0x0428, 0x0429, 0x042A, 0x042B, 0x042C, 0x042D, 0x042E, 0x042F,
+ 0x0430, 0x0431, 0x0432, 0x0433, 0x0434, 0x0435, 0x0436, 0x0437,
+ 0x0438, 0x0439, 0x043A, 0x043B, 0x043C, 0x043D, 0x043E, 0x043F,
+ 0x0440, 0x0441, 0x0442, 0x0443, 0x0444, 0x0445, 0x0446, 0x0447,
+ 0x0448, 0x0449, 0x044A, 0x044B, 0x044C, 0x044D, 0x044E, 0x044F,
+ 0x2116, 0x0451, 0x0452, 0x0453, 0x0454, 0x0455, 0x0456, 0x0457,
+ 0x0458, 0x0459, 0x045A, 0x045B, 0x045C, 0x00A7, 0x045E, 0x045F
+};
+
+/* ISO/IEC 8859-6:1999 (Latin/Arabic) */
+static const wchar_t iso_8859_6[] = {
+ 0x00A0, 0xFFFD, 0xFFFD, 0xFFFD, 0x00A4, 0xFFFD, 0xFFFD, 0xFFFD,
+ 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0x060C, 0x00AD, 0xFFFD, 0xFFFD,
+ 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD,
+ 0xFFFD, 0xFFFD, 0xFFFD, 0x061B, 0xFFFD, 0xFFFD, 0xFFFD, 0x061F,
+ 0xFFFD, 0x0621, 0x0622, 0x0623, 0x0624, 0x0625, 0x0626, 0x0627,
+ 0x0628, 0x0629, 0x062A, 0x062B, 0x062C, 0x062D, 0x062E, 0x062F,
+ 0x0630, 0x0631, 0x0632, 0x0633, 0x0634, 0x0635, 0x0636, 0x0637,
+ 0x0638, 0x0639, 0x063A, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD,
+ 0x0640, 0x0641, 0x0642, 0x0643, 0x0644, 0x0645, 0x0646, 0x0647,
+ 0x0648, 0x0649, 0x064A, 0x064B, 0x064C, 0x064D, 0x064E, 0x064F,
+ 0x0650, 0x0651, 0x0652, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD,
+ 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD
+};
+
+/* ISO 8859-7:1987 (Latin/Greek) */
+static const wchar_t iso_8859_7[] = {
+ 0x00A0, 0x2018, 0x2019, 0x00A3, 0xFFFD, 0xFFFD, 0x00A6, 0x00A7,
+ 0x00A8, 0x00A9, 0xFFFD, 0x00AB, 0x00AC, 0x00AD, 0xFFFD, 0x2015,
+ 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x0384, 0x0385, 0x0386, 0x00B7,
+ 0x0388, 0x0389, 0x038A, 0x00BB, 0x038C, 0x00BD, 0x038E, 0x038F,
+ 0x0390, 0x0391, 0x0392, 0x0393, 0x0394, 0x0395, 0x0396, 0x0397,
+ 0x0398, 0x0399, 0x039A, 0x039B, 0x039C, 0x039D, 0x039E, 0x039F,
+ 0x03A0, 0x03A1, 0xFFFD, 0x03A3, 0x03A4, 0x03A5, 0x03A6, 0x03A7,
+ 0x03A8, 0x03A9, 0x03AA, 0x03AB, 0x03AC, 0x03AD, 0x03AE, 0x03AF,
+ 0x03B0, 0x03B1, 0x03B2, 0x03B3, 0x03B4, 0x03B5, 0x03B6, 0x03B7,
+ 0x03B8, 0x03B9, 0x03BA, 0x03BB, 0x03BC, 0x03BD, 0x03BE, 0x03BF,
+ 0x03C0, 0x03C1, 0x03C2, 0x03C3, 0x03C4, 0x03C5, 0x03C6, 0x03C7,
+ 0x03C8, 0x03C9, 0x03CA, 0x03CB, 0x03CC, 0x03CD, 0x03CE, 0xFFFD
+};
+
+/* ISO/IEC 8859-8:1999 (Latin/Hebrew) */
+static const wchar_t iso_8859_8[] = {
+ 0x00A0, 0xFFFD, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7,
+ 0x00A8, 0x00A9, 0x00D7, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF,
+ 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7,
+ 0x00B8, 0x00B9, 0x00F7, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0xFFFD,
+ 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD,
+ 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD,
+ 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD,
+ 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0x2017,
+ 0x05D0, 0x05D1, 0x05D2, 0x05D3, 0x05D4, 0x05D5, 0x05D6, 0x05D7,
+ 0x05D8, 0x05D9, 0x05DA, 0x05DB, 0x05DC, 0x05DD, 0x05DE, 0x05DF,
+ 0x05E0, 0x05E1, 0x05E2, 0x05E3, 0x05E4, 0x05E5, 0x05E6, 0x05E7,
+ 0x05E8, 0x05E9, 0x05EA, 0xFFFD, 0xFFFD, 0x200E, 0x200F, 0xFFFD
+};
+
+/* ISO/IEC 8859-9:1999 (Latin-5, "Turkish") */
+static const wchar_t iso_8859_9[] = {
+ 0x00A0, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7,
+ 0x00A8, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF,
+ 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7,
+ 0x00B8, 0x00B9, 0x00BA, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00BF,
+ 0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7,
+ 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF,
+ 0x011E, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x00D7,
+ 0x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x0130, 0x015E, 0x00DF,
+ 0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7,
+ 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF,
+ 0x011F, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7,
+ 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x0131, 0x015F, 0x00FF
+};
+
+/* ISO/IEC 8859-10:1998 (Latin-6, "Nordic" [Sami, Inuit, Icelandic]) */
+static const wchar_t iso_8859_10[] = {
+ 0x00A0, 0x0104, 0x0112, 0x0122, 0x012A, 0x0128, 0x0136, 0x00A7,
+ 0x013B, 0x0110, 0x0160, 0x0166, 0x017D, 0x00AD, 0x016A, 0x014A,
+ 0x00B0, 0x0105, 0x0113, 0x0123, 0x012B, 0x0129, 0x0137, 0x00B7,
+ 0x013C, 0x0111, 0x0161, 0x0167, 0x017E, 0x2015, 0x016B, 0x014B,
+ 0x0100, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x012E,
+ 0x010C, 0x00C9, 0x0118, 0x00CB, 0x0116, 0x00CD, 0x00CE, 0x00CF,
+ 0x00D0, 0x0145, 0x014C, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x0168,
+ 0x00D8, 0x0172, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x00DE, 0x00DF,
+ 0x0101, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x012F,
+ 0x010D, 0x00E9, 0x0119, 0x00EB, 0x0117, 0x00ED, 0x00EE, 0x00EF,
+ 0x00F0, 0x0146, 0x014D, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x0169,
+ 0x00F8, 0x0173, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x0138
+};
+
+/* ISO/IEC 8859-11:2001 ("Thai", "TIS620") */
+static const wchar_t iso_8859_11[] = {
+ 0x00A0, 0x0E01, 0x0E02, 0x0E03, 0x0E04, 0x0E05, 0x0E06, 0x0E07,
+ 0x0E08, 0x0E09, 0x0E0A, 0x0E0B, 0x0E0C, 0x0E0D, 0x0E0E, 0x0E0F,
+ 0x0E10, 0x0E11, 0x0E12, 0x0E13, 0x0E14, 0x0E15, 0x0E16, 0x0E17,
+ 0x0E18, 0x0E19, 0x0E1A, 0x0E1B, 0x0E1C, 0x0E1D, 0x0E1E, 0x0E1F,
+ 0x0E20, 0x0E21, 0x0E22, 0x0E23, 0x0E24, 0x0E25, 0x0E26, 0x0E27,
+ 0x0E28, 0x0E29, 0x0E2A, 0x0E2B, 0x0E2C, 0x0E2D, 0x0E2E, 0x0E2F,
+ 0x0E30, 0x0E31, 0x0E32, 0x0E33, 0x0E34, 0x0E35, 0x0E36, 0x0E37,
+ 0x0E38, 0x0E39, 0x0E3A, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0x0E3F,
+ 0x0E40, 0x0E41, 0x0E42, 0x0E43, 0x0E44, 0x0E45, 0x0E46, 0x0E47,
+ 0x0E48, 0x0E49, 0x0E4A, 0x0E4B, 0x0E4C, 0x0E4D, 0x0E4E, 0x0E4F,
+ 0x0E50, 0x0E51, 0x0E52, 0x0E53, 0x0E54, 0x0E55, 0x0E56, 0x0E57,
+ 0x0E58, 0x0E59, 0x0E5A, 0x0E5B, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD
+};
+
+/* ISO/IEC 8859-13:1998 (Latin-7, "Baltic Rim") */
+static const wchar_t iso_8859_13[] = {
+ 0x00A0, 0x201D, 0x00A2, 0x00A3, 0x00A4, 0x201E, 0x00A6, 0x00A7,
+ 0x00D8, 0x00A9, 0x0156, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00C6,
+ 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x201C, 0x00B5, 0x00B6, 0x00B7,
+ 0x00F8, 0x00B9, 0x0157, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00E6,
+ 0x0104, 0x012E, 0x0100, 0x0106, 0x00C4, 0x00C5, 0x0118, 0x0112,
+ 0x010C, 0x00C9, 0x0179, 0x0116, 0x0122, 0x0136, 0x012A, 0x013B,
+ 0x0160, 0x0143, 0x0145, 0x00D3, 0x014C, 0x00D5, 0x00D6, 0x00D7,
+ 0x0172, 0x0141, 0x015A, 0x016A, 0x00DC, 0x017B, 0x017D, 0x00DF,
+ 0x0105, 0x012F, 0x0101, 0x0107, 0x00E4, 0x00E5, 0x0119, 0x0113,
+ 0x010D, 0x00E9, 0x017A, 0x0117, 0x0123, 0x0137, 0x012B, 0x013C,
+ 0x0161, 0x0144, 0x0146, 0x00F3, 0x014D, 0x00F5, 0x00F6, 0x00F7,
+ 0x0173, 0x0142, 0x015B, 0x016B, 0x00FC, 0x017C, 0x017E, 0x2019
+};
+
+/* ISO/IEC 8859-14:1998 (Latin-8, "Celtic", "Gaelic/Welsh") */
+static const wchar_t iso_8859_14[] = {
+ 0x00A0, 0x1E02, 0x1E03, 0x00A3, 0x010A, 0x010B, 0x1E0A, 0x00A7,
+ 0x1E80, 0x00A9, 0x1E82, 0x1E0B, 0x1EF2, 0x00AD, 0x00AE, 0x0178,
+ 0x1E1E, 0x1E1F, 0x0120, 0x0121, 0x1E40, 0x1E41, 0x00B6, 0x1E56,
+ 0x1E81, 0x1E57, 0x1E83, 0x1E60, 0x1EF3, 0x1E84, 0x1E85, 0x1E61,
+ 0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7,
+ 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF,
+ 0x0174, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x1E6A,
+ 0x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x0176, 0x00DF,
+ 0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7,
+ 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF,
+ 0x0175, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x1E6B,
+ 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x0177, 0x00FF
+};
+
+/* ISO/IEC 8859-15:1999 (Latin-9 aka -0, "euro") */
+static const wchar_t iso_8859_15[] = {
+ 0x00A0, 0x00A1, 0x00A2, 0x00A3, 0x20AC, 0x00A5, 0x0160, 0x00A7,
+ 0x0161, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF,
+ 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x017D, 0x00B5, 0x00B6, 0x00B7,
+ 0x017E, 0x00B9, 0x00BA, 0x00BB, 0x0152, 0x0153, 0x0178, 0x00BF,
+ 0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7,
+ 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF,
+ 0x00D0, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x00D7,
+ 0x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x00DE, 0x00DF,
+ 0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7,
+ 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF,
+ 0x00F0, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7,
+ 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF
+};
+
+/* ISO/IEC 8859-16:2001 (Latin-10, "Balkan") */
+static const wchar_t iso_8859_16[] = {
+ 0x00A0, 0x0104, 0x0105, 0x0141, 0x20AC, 0x201E, 0x0160, 0x00A7,
+ 0x0161, 0x00A9, 0x0218, 0x00AB, 0x0179, 0x00AD, 0x017A, 0x017B,
+ 0x00B0, 0x00B1, 0x010C, 0x0142, 0x017D, 0x201D, 0x00B6, 0x00B7,
+ 0x017E, 0x010D, 0x0219, 0x00BB, 0x0152, 0x0153, 0x0178, 0x017C,
+ 0x00C0, 0x00C1, 0x00C2, 0x0102, 0x00C4, 0x0106, 0x00C6, 0x00C7,
+ 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF,
+ 0x0110, 0x0143, 0x00D2, 0x00D3, 0x00D4, 0x0150, 0x00D6, 0x015A,
+ 0x0170, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x0118, 0x021A, 0x00DF,
+ 0x00E0, 0x00E1, 0x00E2, 0x0103, 0x00E4, 0x0107, 0x00E6, 0x00E7,
+ 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF,
+ 0x0111, 0x0144, 0x00F2, 0x00F3, 0x00F4, 0x0151, 0x00F6, 0x015B,
+ 0x0171, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x0119, 0x021B, 0x00FF
+};
+
+static const wchar_t roman8[] = {
+ 0x00A0, 0x00C0, 0x00C2, 0x00C8, 0x00CA, 0x00CB, 0x00CE, 0x00CF,
+ 0x00B4, 0x02CB, 0x02C6, 0x00A8, 0x02DC, 0x00D9, 0x00DB, 0x20A4,
+ 0x00AF, 0x00DD, 0x00FD, 0x00B0, 0x00C7, 0x00E7, 0x00D1, 0x00F1,
+ 0x00A1, 0x00BF, 0x00A4, 0x00A3, 0x00A5, 0x00A7, 0x0192, 0x00A2,
+ 0x00E2, 0x00EA, 0x00F4, 0x00FB, 0x00E1, 0x00E9, 0x00F3, 0x00FA,
+ 0x00E0, 0x00E8, 0x00F2, 0x00F9, 0x00E4, 0x00EB, 0x00F6, 0x00FC,
+ 0x00C5, 0x00EE, 0x00D8, 0x00C6, 0x00E5, 0x00ED, 0x00F8, 0x00E6,
+ 0x00C4, 0x00EC, 0x00D6, 0x00DC, 0x00C9, 0x00EF, 0x00DF, 0x00D4,
+ 0x00C1, 0x00C3, 0x00E3, 0x00D0, 0x00F0, 0x00CD, 0x00CC, 0x00D3,
+ 0x00D2, 0x00D5, 0x00F5, 0x0160, 0x0161, 0x00DA, 0x0178, 0x00FF,
+ 0x00DE, 0x00FE, 0x00B7, 0x00B5, 0x00B6, 0x00BE, 0x2014, 0x00BC,
+ 0x00BD, 0x00AA, 0x00BA, 0x00AB, 0x25A0, 0x00BB, 0x00B1, 0xFFFD
+};
+
+static const wchar_t koi8_u[] = {
+ 0x2500, 0x2502, 0x250C, 0x2510, 0x2514, 0x2518, 0x251C, 0x2524,
+ 0x252C, 0x2534, 0x253C, 0x2580, 0x2584, 0x2588, 0x258C, 0x2590,
+ 0x2591, 0x2592, 0x2593, 0x2320, 0x25A0, 0x2022, 0x221A, 0x2248,
+ 0x2264, 0x2265, 0x00A0, 0x2321, 0x00B0, 0x00B2, 0x00B7, 0x00F7,
+ 0x2550, 0x2551, 0x2552, 0x0451, 0x0454, 0x2554, 0x0456, 0x0457,
+ 0x2557, 0x2558, 0x2559, 0x255A, 0x255B, 0x0491, 0x255D, 0x255E,
+ 0x255F, 0x2560, 0x2561, 0x0401, 0x0404, 0x2563, 0x0406, 0x0407,
+ 0x2566, 0x2567, 0x2568, 0x2569, 0x256A, 0x0490, 0x256C, 0x00A9,
+ 0x044E, 0x0430, 0x0431, 0x0446, 0x0434, 0x0435, 0x0444, 0x0433,
+ 0x0445, 0x0438, 0x0439, 0x043A, 0x043B, 0x043C, 0x043D, 0x043E,
+ 0x043F, 0x044F, 0x0440, 0x0441, 0x0442, 0x0443, 0x0436, 0x0432,
+ 0x044C, 0x044B, 0x0437, 0x0448, 0x044D, 0x0449, 0x0447, 0x044A,
+ 0x042E, 0x0410, 0x0411, 0x0426, 0x0414, 0x0415, 0x0424, 0x0413,
+ 0x0425, 0x0418, 0x0419, 0x041A, 0x041B, 0x041C, 0x041D, 0x041E,
+ 0x041F, 0x042F, 0x0420, 0x0421, 0x0422, 0x0423, 0x0416, 0x0412,
+ 0x042C, 0x042B, 0x0417, 0x0428, 0x042D, 0x0429, 0x0427, 0x042A
+};
+
+static const wchar_t vscii[] = {
+ 0x0000, 0x0001, 0x1EB2, 0x0003, 0x0004, 0x1EB4, 0x1EAA, 0x0007,
+ 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f,
+ 0x0010, 0x0011, 0x0012, 0x0013, 0x1EF6, 0x0015, 0x0016, 0x0017,
+ 0x0018, 0x1EF8, 0x001a, 0x001b, 0x001c, 0x001d, 0x1EF4, 0x001f,
+ 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
+ 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F,
+ 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
+ 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
+ 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
+ 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
+ 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
+ 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F,
+ 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
+ 0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F,
+ 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
+ 0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x007f,
+ 0x1EA0, 0x1EAE, 0x1EB0, 0x1EB6, 0x1EA4, 0x1EA6, 0x1EA8, 0x1EAC,
+ 0x1EBC, 0x1EB8, 0x1EBE, 0x1EC0, 0x1EC2, 0x1EC4, 0x1EC6, 0x1ED0,
+ 0x1ED2, 0x1ED4, 0x1ED6, 0x1ED8, 0x1EE2, 0x1EDA, 0x1EDC, 0x1EDE,
+ 0x1ECA, 0x1ECE, 0x1ECC, 0x1EC8, 0x1EE6, 0x0168, 0x1EE4, 0x1EF2,
+ 0x00D5, 0x1EAF, 0x1EB1, 0x1EB7, 0x1EA5, 0x1EA7, 0x1EA8, 0x1EAD,
+ 0x1EBD, 0x1EB9, 0x1EBF, 0x1EC1, 0x1EC3, 0x1EC5, 0x1EC7, 0x1ED1,
+ 0x1ED3, 0x1ED5, 0x1ED7, 0x1EE0, 0x01A0, 0x1ED9, 0x1EDD, 0x1EDF,
+ 0x1ECB, 0x1EF0, 0x1EE8, 0x1EEA, 0x1EEC, 0x01A1, 0x1EDB, 0x01AF,
+ 0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x1EA2, 0x0102, 0x1EB3, 0x1EB5,
+ 0x00C8, 0x00C9, 0x00CA, 0x1EBA, 0x00CC, 0x00CD, 0x0128, 0x1EF3,
+ 0x0110, 0x1EE9, 0x00D2, 0x00D3, 0x00D4, 0x1EA1, 0x1EF7, 0x1EEB,
+ 0x1EED, 0x00D9, 0x00DA, 0x1EF9, 0x1EF5, 0x00DD, 0x1EE1, 0x01B0,
+ 0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x1EA3, 0x0103, 0x1EEF, 0x1EAB,
+ 0x00E8, 0x00E9, 0x00EA, 0x1EBB, 0x00EC, 0x00ED, 0x0129, 0x1EC9,
+ 0x0111, 0x1EF1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x1ECF, 0x1ECD,
+ 0x1EE5, 0x00F9, 0x00FA, 0x0169, 0x1EE7, 0x00FD, 0x1EE3, 0x1EEE
+};
+
+static const wchar_t dec_mcs[] = {
+ 0x00A0, 0x00A1, 0x00A2, 0x00A3, 0xFFFD, 0x00A5, 0xFFFD, 0x00A7,
+ 0x00A4, 0x00A9, 0x00AA, 0x00AB, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD,
+ 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0xFFFD, 0x00B5, 0x00B6, 0x00B7,
+ 0xFFFD, 0x00B9, 0x00BA, 0x00BB, 0x00BC, 0x00BD, 0xFFFD, 0x00BF,
+ 0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7,
+ 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF,
+ 0xFFFD, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x0152,
+ 0x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x0178, 0xFFFD, 0x00DF,
+ 0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7,
+ 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF,
+ 0xFFFD, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x0153,
+ 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FF, 0xFFFD, 0xFFFD
+};
+
+/* Mazovia (Polish) aka CP620
+ * from "Mazowia to Unicode table", 04/24/96, Mikolaj Jedrzejak */
+static const wchar_t mazovia[] = {
+ /* Code point 0x9B is "zloty" symbol (zŽ), which is not
+ * widely used and for which there is no Unicode equivalent.
+ * One reference shows 0xA8 as U+00A7 SECTION SIGN, but we're
+ * told that's incorrect. */
+ 0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E4, 0x00E0, 0x0105, 0x00E7,
+ 0x00EA, 0x00EB, 0x00E8, 0x00EF, 0x00EE, 0x0107, 0x00C4, 0x0104,
+ 0x0118, 0x0119, 0x0142, 0x00F4, 0x00F6, 0x0106, 0x00FB, 0x00F9,
+ 0x015a, 0x00D6, 0x00DC, 0xFFFD, 0x0141, 0x00A5, 0x015b, 0x0192,
+ 0x0179, 0x017b, 0x00F3, 0x00d3, 0x0144, 0x0143, 0x017a, 0x017c,
+ 0x00BF, 0x2310, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB, 0x00BB,
+ 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556,
+ 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510,
+ 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F,
+ 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567,
+ 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B,
+ 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580,
+ 0x03B1, 0x00DF, 0x0393, 0x03C0, 0x03A3, 0x03C3, 0x00B5, 0x03C4,
+ 0x03A6, 0x0398, 0x03A9, 0x03B4, 0x221E, 0x03C6, 0x03B5, 0x2229,
+ 0x2261, 0x00B1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00F7, 0x2248,
+ 0x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x25A0, 0x00A0
+};
+
+struct cp_list_item {
+ char *name;
+ int codepage;
+ int cp_size;
+ const wchar_t *cp_table;
+};
+
+static const struct cp_list_item cp_list[] = {
+ {"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},
+ {"ISO-8859-6:1999 (Latin/Arabic)", 0, 96, iso_8859_6},
+ {"ISO-8859-7:1987 (Latin/Greek)", 0, 96, iso_8859_7},
+ {"ISO-8859-8:1999 (Latin/Hebrew)", 0, 96, iso_8859_8},
+ {"ISO-8859-9:1999 (Latin-5, Turkish)", 0, 96, iso_8859_9},
+ {"ISO-8859-10:1998 (Latin-6, Nordic)", 0, 96, iso_8859_10},
+ {"ISO-8859-11:2001 (Latin/Thai)", 0, 96, iso_8859_11},
+ {"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},
+
+ {"Win1250 (Central European)", 1250},
+ {"Win1251 (Cyrillic)", 1251},
+ {"Win1252 (Western)", 1252},
+ {"Win1253 (Greek)", 1253},
+ {"Win1254 (Turkish)", 1254},
+ {"Win1255 (Hebrew)", 1255},
+ {"Win1256 (Arabic)", 1256},
+ {"Win1257 (Baltic)", 1257},
+ {"Win1258 (Vietnamese)", 1258},
+
+ {"CP437", 437},
+ {"CP620 (Mazovia)", 0, 128, mazovia},
+ {"CP819", 28591},
+ {"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)
+{
+ int i, j;
+ int used_dtf = 0;
+ char tbuf[256];
+
+ for (i = 0; i < 256; i++)
+ tbuf[i] = i;
+
+ /* Decide on the Line and Font codepages */
+ ucsdata->line_codepage = decode_codepage(cfg->line_codepage);
+
+ if (ucsdata->font_codepage <= 0) {
+ ucsdata->font_codepage=0;
+ ucsdata->dbcs_screenfont=0;
+ }
+
+ if (cfg->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)
+ ucsdata->line_codepage = ucsdata->font_codepage;
+
+ /* Collect screen font ucs table */
+ if (ucsdata->dbcs_screenfont || ucsdata->font_codepage == 0) {
+ get_unitab(ucsdata->font_codepage, ucsdata->unitab_font, 2);
+ for (i = 128; i < 256; i++)
+ ucsdata->unitab_font[i] = (WCHAR) (CSET_ACP + i);
+ } else {
+ get_unitab(ucsdata->font_codepage, ucsdata->unitab_font, 1);
+
+ /* CP437 fonts are often broken ... */
+ if (ucsdata->font_codepage == 437)
+ ucsdata->unitab_font[0] = ucsdata->unitab_font[255] = 0xFFFF;
+ }
+ if (cfg->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)
+ 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)) {
+
+ /* For DBCS and POOR fonts force direct to font */
+ used_dtf = 1;
+ for (i = 0; i < 32; i++)
+ ucsdata->unitab_line[i] = (WCHAR) i;
+ for (i = 32; i < 256; i++)
+ ucsdata->unitab_line[i] = (WCHAR) (CSET_ACP + i);
+ ucsdata->unitab_line[127] = (WCHAR) 127;
+ } else {
+ get_unitab(ucsdata->line_codepage, ucsdata->unitab_line, 0);
+ }
+
+#if 0
+ debug(
+ ("Line cp%d, Font cp%d%s\n", ucsdata->line_codepage,
+ ucsdata->font_codepage, ucsdata->dbcs_screenfont ? " DBCS" : ""));
+
+ for (i = 0; i < 256; i += 16) {
+ for (j = 0; j < 16; j++) {
+ debug(("%04x%s", ucsdata->unitab_line[i + j], j == 15 ? "" : ","));
+ }
+ debug(("\n"));
+ }
+#endif
+
+ /* VT100 graphics - NB: Broken for non-ascii CP's */
+ memcpy(ucsdata->unitab_xterm, ucsdata->unitab_line,
+ sizeof(ucsdata->unitab_xterm));
+ memcpy(ucsdata->unitab_xterm + '`', unitab_xterm_std,
+ sizeof(unitab_xterm_std));
+ ucsdata->unitab_xterm['_'] = ' ';
+
+ /* Generate UCS ->line page table. */
+ if (ucsdata->uni_tbl) {
+ for (i = 0; i < 256; i++)
+ if (ucsdata->uni_tbl[i])
+ sfree(ucsdata->uni_tbl[i]);
+ sfree(ucsdata->uni_tbl);
+ ucsdata->uni_tbl = 0;
+ }
+ if (!used_dtf) {
+ for (i = 0; i < 256; i++) {
+ if (DIRECT_CHAR(ucsdata->unitab_line[i]))
+ continue;
+ if (DIRECT_FONT(ucsdata->unitab_line[i]))
+ continue;
+ if (!ucsdata->uni_tbl) {
+ ucsdata->uni_tbl = snewn(256, char *);
+ memset(ucsdata->uni_tbl, 0, 256 * sizeof(char *));
+ }
+ j = ((ucsdata->unitab_line[i] >> 8) & 0xFF);
+ if (!ucsdata->uni_tbl[j]) {
+ ucsdata->uni_tbl[j] = snewn(256, char);
+ memset(ucsdata->uni_tbl[j], 0, 256 * sizeof(char));
+ }
+ ucsdata->uni_tbl[j][ucsdata->unitab_line[i] & 0xFF] = i;
+ }
+ }
+
+ /* Find the line control characters. */
+ for (i = 0; i < 256; i++)
+ if (ucsdata->unitab_line[i] < ' '
+ || (ucsdata->unitab_line[i] >= 0x7F &&
+ ucsdata->unitab_line[i] < 0xA0))
+ 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)
+ 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) {
+ link_font(ucsdata->unitab_line, ucsdata->unitab_oemcp, CSET_OEMCP);
+ link_font(ucsdata->unitab_xterm, ucsdata->unitab_oemcp, CSET_OEMCP);
+ }
+
+ if (ucsdata->dbcs_screenfont &&
+ ucsdata->font_codepage != ucsdata->line_codepage) {
+ /* F***ing Microsoft fonts, Japanese and Korean codepage fonts
+ * have a currency symbol at 0x5C but their unicode value is
+ * 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) {
+ 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.";
+
+ for (i = 160; i < 256; i++)
+ if (!DIRECT_FONT(ucsdata->unitab_line[i]) &&
+ ucsdata->unitab_line[i] >= 160 &&
+ ucsdata->unitab_line[i] < 256) {
+ ucsdata->unitab_line[i] =
+ (WCHAR) (CSET_ACP +
+ poorman_latin1[ucsdata->unitab_line[i] - 160]);
+ }
+ for (i = 96; i < 127; i++)
+ if (!DIRECT_FONT(ucsdata->unitab_xterm[i]))
+ ucsdata->unitab_xterm[i] =
+ (WCHAR) (CSET_ACP + poorman_vt100[i - 96]);
+ for(i=128;i<256;i++)
+ if (!DIRECT_FONT(ucsdata->unitab_scoacs[i]))
+ ucsdata->unitab_scoacs[i] =
+ (WCHAR) (CSET_ACP + poorman_scoacs[i - 128]);
+ }
+}
+
+static void link_font(WCHAR * line_tbl, WCHAR * font_tbl, WCHAR attr)
+{
+ int font_index, line_index, i;
+ for (line_index = 0; line_index < 256; line_index++) {
+ if (DIRECT_FONT(line_tbl[line_index]))
+ continue;
+ for(i = 0; i < 256; i++) {
+ font_index = ((32 + i) & 0xFF);
+ if (line_tbl[line_index] == font_tbl[font_index]) {
+ line_tbl[line_index] = (WCHAR) (attr + font_index);
+ break;
+ }
+ }
+ }
+}
+
+wchar_t xlat_uskbd2cyrllic(int ch)
+{
+ static const wchar_t cyrtab[] = {
+ 0, 1, 2, 3, 4, 5, 6, 7,
+ 8, 9, 10, 11, 12, 13, 14, 15,
+ 16, 17, 18, 19, 20, 21, 22, 23,
+ 24, 25, 26, 27, 28, 29, 30, 31,
+ 32, 33, 0x042d, 35, 36, 37, 38, 0x044d,
+ 40, 41, 42, 0x0406, 0x0431, 0x0454, 0x044e, 0x002e,
+ 48, 49, 50, 51, 52, 53, 54, 55,
+ 56, 57, 0x0416, 0x0436, 0x0411, 0x0456, 0x042e, 0x002c,
+ 64, 0x0424, 0x0418, 0x0421, 0x0412, 0x0423, 0x0410, 0x041f,
+ 0x0420, 0x0428, 0x041e, 0x041b, 0x0414, 0x042c, 0x0422, 0x0429,
+ 0x0417, 0x0419, 0x041a, 0x042b, 0x0415, 0x0413, 0x041c, 0x0426,
+ 0x0427, 0x041d, 0x042f, 0x0445, 0x0457, 0x044a, 94, 0x0404,
+ 96, 0x0444, 0x0438, 0x0441, 0x0432, 0x0443, 0x0430, 0x043f,
+ 0x0440, 0x0448, 0x043e, 0x043b, 0x0434, 0x044c, 0x0442, 0x0449,
+ 0x0437, 0x0439, 0x043a, 0x044b, 0x0435, 0x0433, 0x043c, 0x0446,
+ 0x0447, 0x043d, 0x044f, 0x0425, 0x0407, 0x042a, 126, 127
+ };
+ return cyrtab[ch&0x7F];
+}
+
+int check_compose_internal(int first, int second, int recurse)
+{
+
+ static const struct {
+ char first, second;
+ wchar_t composed;
+ } composetbl[] = {
+ {
+ 0x2b, 0x2b, 0x0023}, {
+ 0x41, 0x41, 0x0040}, {
+ 0x28, 0x28, 0x005b}, {
+ 0x2f, 0x2f, 0x005c}, {
+ 0x29, 0x29, 0x005d}, {
+ 0x28, 0x2d, 0x007b}, {
+ 0x2d, 0x29, 0x007d}, {
+ 0x2f, 0x5e, 0x007c}, {
+ 0x21, 0x21, 0x00a1}, {
+ 0x43, 0x2f, 0x00a2}, {
+ 0x43, 0x7c, 0x00a2}, {
+ 0x4c, 0x2d, 0x00a3}, {
+ 0x4c, 0x3d, 0x20a4}, {
+ 0x58, 0x4f, 0x00a4}, {
+ 0x58, 0x30, 0x00a4}, {
+ 0x59, 0x2d, 0x00a5}, {
+ 0x59, 0x3d, 0x00a5}, {
+ 0x7c, 0x7c, 0x00a6}, {
+ 0x53, 0x4f, 0x00a7}, {
+ 0x53, 0x21, 0x00a7}, {
+ 0x53, 0x30, 0x00a7}, {
+ 0x22, 0x22, 0x00a8}, {
+ 0x43, 0x4f, 0x00a9}, {
+ 0x43, 0x30, 0x00a9}, {
+ 0x41, 0x5f, 0x00aa}, {
+ 0x3c, 0x3c, 0x00ab}, {
+ 0x2c, 0x2d, 0x00ac}, {
+ 0x2d, 0x2d, 0x00ad}, {
+ 0x52, 0x4f, 0x00ae}, {
+ 0x2d, 0x5e, 0x00af}, {
+ 0x30, 0x5e, 0x00b0}, {
+ 0x2b, 0x2d, 0x00b1}, {
+ 0x32, 0x5e, 0x00b2}, {
+ 0x33, 0x5e, 0x00b3}, {
+ 0x27, 0x27, 0x00b4}, {
+ 0x2f, 0x55, 0x00b5}, {
+ 0x50, 0x21, 0x00b6}, {
+ 0x2e, 0x5e, 0x00b7}, {
+ 0x2c, 0x2c, 0x00b8}, {
+ 0x31, 0x5e, 0x00b9}, {
+ 0x4f, 0x5f, 0x00ba}, {
+ 0x3e, 0x3e, 0x00bb}, {
+ 0x31, 0x34, 0x00bc}, {
+ 0x31, 0x32, 0x00bd}, {
+ 0x33, 0x34, 0x00be}, {
+ 0x3f, 0x3f, 0x00bf}, {
+ 0x60, 0x41, 0x00c0}, {
+ 0x27, 0x41, 0x00c1}, {
+ 0x5e, 0x41, 0x00c2}, {
+ 0x7e, 0x41, 0x00c3}, {
+ 0x22, 0x41, 0x00c4}, {
+ 0x2a, 0x41, 0x00c5}, {
+ 0x41, 0x45, 0x00c6}, {
+ 0x2c, 0x43, 0x00c7}, {
+ 0x60, 0x45, 0x00c8}, {
+ 0x27, 0x45, 0x00c9}, {
+ 0x5e, 0x45, 0x00ca}, {
+ 0x22, 0x45, 0x00cb}, {
+ 0x60, 0x49, 0x00cc}, {
+ 0x27, 0x49, 0x00cd}, {
+ 0x5e, 0x49, 0x00ce}, {
+ 0x22, 0x49, 0x00cf}, {
+ 0x2d, 0x44, 0x00d0}, {
+ 0x7e, 0x4e, 0x00d1}, {
+ 0x60, 0x4f, 0x00d2}, {
+ 0x27, 0x4f, 0x00d3}, {
+ 0x5e, 0x4f, 0x00d4}, {
+ 0x7e, 0x4f, 0x00d5}, {
+ 0x22, 0x4f, 0x00d6}, {
+ 0x58, 0x58, 0x00d7}, {
+ 0x2f, 0x4f, 0x00d8}, {
+ 0x60, 0x55, 0x00d9}, {
+ 0x27, 0x55, 0x00da}, {
+ 0x5e, 0x55, 0x00db}, {
+ 0x22, 0x55, 0x00dc}, {
+ 0x27, 0x59, 0x00dd}, {
+ 0x48, 0x54, 0x00de}, {
+ 0x73, 0x73, 0x00df}, {
+ 0x60, 0x61, 0x00e0}, {
+ 0x27, 0x61, 0x00e1}, {
+ 0x5e, 0x61, 0x00e2}, {
+ 0x7e, 0x61, 0x00e3}, {
+ 0x22, 0x61, 0x00e4}, {
+ 0x2a, 0x61, 0x00e5}, {
+ 0x61, 0x65, 0x00e6}, {
+ 0x2c, 0x63, 0x00e7}, {
+ 0x60, 0x65, 0x00e8}, {
+ 0x27, 0x65, 0x00e9}, {
+ 0x5e, 0x65, 0x00ea}, {
+ 0x22, 0x65, 0x00eb}, {
+ 0x60, 0x69, 0x00ec}, {
+ 0x27, 0x69, 0x00ed}, {
+ 0x5e, 0x69, 0x00ee}, {
+ 0x22, 0x69, 0x00ef}, {
+ 0x2d, 0x64, 0x00f0}, {
+ 0x7e, 0x6e, 0x00f1}, {
+ 0x60, 0x6f, 0x00f2}, {
+ 0x27, 0x6f, 0x00f3}, {
+ 0x5e, 0x6f, 0x00f4}, {
+ 0x7e, 0x6f, 0x00f5}, {
+ 0x22, 0x6f, 0x00f6}, {
+ 0x3a, 0x2d, 0x00f7}, {
+ 0x6f, 0x2f, 0x00f8}, {
+ 0x60, 0x75, 0x00f9}, {
+ 0x27, 0x75, 0x00fa}, {
+ 0x5e, 0x75, 0x00fb}, {
+ 0x22, 0x75, 0x00fc}, {
+ 0x27, 0x79, 0x00fd}, {
+ 0x68, 0x74, 0x00fe}, {
+ 0x22, 0x79, 0x00ff},
+ /* Unicode extras. */
+ {
+ 0x6f, 0x65, 0x0153}, {
+ 0x4f, 0x45, 0x0152},
+ /* Compose pairs from UCS */
+ {
+ 0x41, 0x2D, 0x0100}, {
+ 0x61, 0x2D, 0x0101}, {
+ 0x43, 0x27, 0x0106}, {
+ 0x63, 0x27, 0x0107}, {
+ 0x43, 0x5E, 0x0108}, {
+ 0x63, 0x5E, 0x0109}, {
+ 0x45, 0x2D, 0x0112}, {
+ 0x65, 0x2D, 0x0113}, {
+ 0x47, 0x5E, 0x011C}, {
+ 0x67, 0x5E, 0x011D}, {
+ 0x47, 0x2C, 0x0122}, {
+ 0x67, 0x2C, 0x0123}, {
+ 0x48, 0x5E, 0x0124}, {
+ 0x68, 0x5E, 0x0125}, {
+ 0x49, 0x7E, 0x0128}, {
+ 0x69, 0x7E, 0x0129}, {
+ 0x49, 0x2D, 0x012A}, {
+ 0x69, 0x2D, 0x012B}, {
+ 0x4A, 0x5E, 0x0134}, {
+ 0x6A, 0x5E, 0x0135}, {
+ 0x4B, 0x2C, 0x0136}, {
+ 0x6B, 0x2C, 0x0137}, {
+ 0x4C, 0x27, 0x0139}, {
+ 0x6C, 0x27, 0x013A}, {
+ 0x4C, 0x2C, 0x013B}, {
+ 0x6C, 0x2C, 0x013C}, {
+ 0x4E, 0x27, 0x0143}, {
+ 0x6E, 0x27, 0x0144}, {
+ 0x4E, 0x2C, 0x0145}, {
+ 0x6E, 0x2C, 0x0146}, {
+ 0x4F, 0x2D, 0x014C}, {
+ 0x6F, 0x2D, 0x014D}, {
+ 0x52, 0x27, 0x0154}, {
+ 0x72, 0x27, 0x0155}, {
+ 0x52, 0x2C, 0x0156}, {
+ 0x72, 0x2C, 0x0157}, {
+ 0x53, 0x27, 0x015A}, {
+ 0x73, 0x27, 0x015B}, {
+ 0x53, 0x5E, 0x015C}, {
+ 0x73, 0x5E, 0x015D}, {
+ 0x53, 0x2C, 0x015E}, {
+ 0x73, 0x2C, 0x015F}, {
+ 0x54, 0x2C, 0x0162}, {
+ 0x74, 0x2C, 0x0163}, {
+ 0x55, 0x7E, 0x0168}, {
+ 0x75, 0x7E, 0x0169}, {
+ 0x55, 0x2D, 0x016A}, {
+ 0x75, 0x2D, 0x016B}, {
+ 0x55, 0x2A, 0x016E}, {
+ 0x75, 0x2A, 0x016F}, {
+ 0x57, 0x5E, 0x0174}, {
+ 0x77, 0x5E, 0x0175}, {
+ 0x59, 0x5E, 0x0176}, {
+ 0x79, 0x5E, 0x0177}, {
+ 0x59, 0x22, 0x0178}, {
+ 0x5A, 0x27, 0x0179}, {
+ 0x7A, 0x27, 0x017A}, {
+ 0x47, 0x27, 0x01F4}, {
+ 0x67, 0x27, 0x01F5}, {
+ 0x4E, 0x60, 0x01F8}, {
+ 0x6E, 0x60, 0x01F9}, {
+ 0x45, 0x2C, 0x0228}, {
+ 0x65, 0x2C, 0x0229}, {
+ 0x59, 0x2D, 0x0232}, {
+ 0x79, 0x2D, 0x0233}, {
+ 0x44, 0x2C, 0x1E10}, {
+ 0x64, 0x2C, 0x1E11}, {
+ 0x47, 0x2D, 0x1E20}, {
+ 0x67, 0x2D, 0x1E21}, {
+ 0x48, 0x22, 0x1E26}, {
+ 0x68, 0x22, 0x1E27}, {
+ 0x48, 0x2C, 0x1E28}, {
+ 0x68, 0x2C, 0x1E29}, {
+ 0x4B, 0x27, 0x1E30}, {
+ 0x6B, 0x27, 0x1E31}, {
+ 0x4D, 0x27, 0x1E3E}, {
+ 0x6D, 0x27, 0x1E3F}, {
+ 0x50, 0x27, 0x1E54}, {
+ 0x70, 0x27, 0x1E55}, {
+ 0x56, 0x7E, 0x1E7C}, {
+ 0x76, 0x7E, 0x1E7D}, {
+ 0x57, 0x60, 0x1E80}, {
+ 0x77, 0x60, 0x1E81}, {
+ 0x57, 0x27, 0x1E82}, {
+ 0x77, 0x27, 0x1E83}, {
+ 0x57, 0x22, 0x1E84}, {
+ 0x77, 0x22, 0x1E85}, {
+ 0x58, 0x22, 0x1E8C}, {
+ 0x78, 0x22, 0x1E8D}, {
+ 0x5A, 0x5E, 0x1E90}, {
+ 0x7A, 0x5E, 0x1E91}, {
+ 0x74, 0x22, 0x1E97}, {
+ 0x77, 0x2A, 0x1E98}, {
+ 0x79, 0x2A, 0x1E99}, {
+ 0x45, 0x7E, 0x1EBC}, {
+ 0x65, 0x7E, 0x1EBD}, {
+ 0x59, 0x60, 0x1EF2}, {
+ 0x79, 0x60, 0x1EF3}, {
+ 0x59, 0x7E, 0x1EF8}, {
+ 0x79, 0x7E, 0x1EF9},
+ /* Compatible/possibles from UCS */
+ {
+ 0x49, 0x4A, 0x0132}, {
+ 0x69, 0x6A, 0x0133}, {
+ 0x4C, 0x4A, 0x01C7}, {
+ 0x4C, 0x6A, 0x01C8}, {
+ 0x6C, 0x6A, 0x01C9}, {
+ 0x4E, 0x4A, 0x01CA}, {
+ 0x4E, 0x6A, 0x01CB}, {
+ 0x6E, 0x6A, 0x01CC}, {
+ 0x44, 0x5A, 0x01F1}, {
+ 0x44, 0x7A, 0x01F2}, {
+ 0x64, 0x7A, 0x01F3}, {
+ 0x2E, 0x2E, 0x2025}, {
+ 0x21, 0x21, 0x203C}, {
+ 0x3F, 0x21, 0x2048}, {
+ 0x21, 0x3F, 0x2049}, {
+ 0x52, 0x73, 0x20A8}, {
+ 0x4E, 0x6F, 0x2116}, {
+ 0x53, 0x4D, 0x2120}, {
+ 0x54, 0x4D, 0x2122}, {
+ 0x49, 0x49, 0x2161}, {
+ 0x49, 0x56, 0x2163}, {
+ 0x56, 0x49, 0x2165}, {
+ 0x49, 0x58, 0x2168}, {
+ 0x58, 0x49, 0x216A}, {
+ 0x69, 0x69, 0x2171}, {
+ 0x69, 0x76, 0x2173}, {
+ 0x76, 0x69, 0x2175}, {
+ 0x69, 0x78, 0x2178}, {
+ 0x78, 0x69, 0x217A}, {
+ 0x31, 0x30, 0x2469}, {
+ 0x31, 0x31, 0x246A}, {
+ 0x31, 0x32, 0x246B}, {
+ 0x31, 0x33, 0x246C}, {
+ 0x31, 0x34, 0x246D}, {
+ 0x31, 0x35, 0x246E}, {
+ 0x31, 0x36, 0x246F}, {
+ 0x31, 0x37, 0x2470}, {
+ 0x31, 0x38, 0x2471}, {
+ 0x31, 0x39, 0x2472}, {
+ 0x32, 0x30, 0x2473}, {
+ 0x31, 0x2E, 0x2488}, {
+ 0x32, 0x2E, 0x2489}, {
+ 0x33, 0x2E, 0x248A}, {
+ 0x34, 0x2E, 0x248B}, {
+ 0x35, 0x2E, 0x248C}, {
+ 0x36, 0x2E, 0x248D}, {
+ 0x37, 0x2E, 0x248E}, {
+ 0x38, 0x2E, 0x248F}, {
+ 0x39, 0x2E, 0x2490}, {
+ 0x64, 0x61, 0x3372}, {
+ 0x41, 0x55, 0x3373}, {
+ 0x6F, 0x56, 0x3375}, {
+ 0x70, 0x63, 0x3376}, {
+ 0x70, 0x41, 0x3380}, {
+ 0x6E, 0x41, 0x3381}, {
+ 0x6D, 0x41, 0x3383}, {
+ 0x6B, 0x41, 0x3384}, {
+ 0x4B, 0x42, 0x3385}, {
+ 0x4D, 0x42, 0x3386}, {
+ 0x47, 0x42, 0x3387}, {
+ 0x70, 0x46, 0x338A}, {
+ 0x6E, 0x46, 0x338B}, {
+ 0x6D, 0x67, 0x338E}, {
+ 0x6B, 0x67, 0x338F}, {
+ 0x48, 0x7A, 0x3390}, {
+ 0x66, 0x6D, 0x3399}, {
+ 0x6E, 0x6D, 0x339A}, {
+ 0x6D, 0x6D, 0x339C}, {
+ 0x63, 0x6D, 0x339D}, {
+ 0x6B, 0x6D, 0x339E}, {
+ 0x50, 0x61, 0x33A9}, {
+ 0x70, 0x73, 0x33B0}, {
+ 0x6E, 0x73, 0x33B1}, {
+ 0x6D, 0x73, 0x33B3}, {
+ 0x70, 0x56, 0x33B4}, {
+ 0x6E, 0x56, 0x33B5}, {
+ 0x6D, 0x56, 0x33B7}, {
+ 0x6B, 0x56, 0x33B8}, {
+ 0x4D, 0x56, 0x33B9}, {
+ 0x70, 0x57, 0x33BA}, {
+ 0x6E, 0x57, 0x33BB}, {
+ 0x6D, 0x57, 0x33BD}, {
+ 0x6B, 0x57, 0x33BE}, {
+ 0x4D, 0x57, 0x33BF}, {
+ 0x42, 0x71, 0x33C3}, {
+ 0x63, 0x63, 0x33C4}, {
+ 0x63, 0x64, 0x33C5}, {
+ 0x64, 0x42, 0x33C8}, {
+ 0x47, 0x79, 0x33C9}, {
+ 0x68, 0x61, 0x33CA}, {
+ 0x48, 0x50, 0x33CB}, {
+ 0x69, 0x6E, 0x33CC}, {
+ 0x4B, 0x4B, 0x33CD}, {
+ 0x4B, 0x4D, 0x33CE}, {
+ 0x6B, 0x74, 0x33CF}, {
+ 0x6C, 0x6D, 0x33D0}, {
+ 0x6C, 0x6E, 0x33D1}, {
+ 0x6C, 0x78, 0x33D3}, {
+ 0x6D, 0x62, 0x33D4}, {
+ 0x50, 0x48, 0x33D7}, {
+ 0x50, 0x52, 0x33DA}, {
+ 0x73, 0x72, 0x33DB}, {
+ 0x53, 0x76, 0x33DC}, {
+ 0x57, 0x62, 0x33DD}, {
+ 0x66, 0x66, 0xFB00}, {
+ 0x66, 0x69, 0xFB01}, {
+ 0x66, 0x6C, 0xFB02}, {
+ 0x73, 0x74, 0xFB06}, {
+ 0, 0, 0}
+ }, *c;
+
+ int nc = -1;
+
+ for (c = composetbl; c->first; c++) {
+ if (c->first == first && c->second == second)
+ return c->composed;
+ }
+
+ if (recurse == 0) {
+ nc = check_compose_internal(second, first, 1);
+ if (nc == -1)
+ nc = check_compose_internal(toupper(first), toupper(second), 1);
+ if (nc == -1)
+ nc = check_compose_internal(toupper(second), toupper(first), 1);
+ }
+ return nc;
+}
+
+int check_compose(int first, int second)
+{
+ return check_compose_internal(first, second, 0);
+}
+
+int decode_codepage(char *cp_name)
+{
+ 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;
+ }
+
+ break_break:;
+ if (codepage != -1) {
+ if (codepage != CP_UTF8 && codepage < 65536) {
+ if (GetCPInfo(codepage, &cpinfo) == 0) {
+ codepage = -2;
+ } else if (cpinfo.MaxCharSize > 1)
+ codepage = -3;
+ }
+ }
+ if (codepage == -1 && *cp_name)
+ codepage = -2;
+ return codepage;
+}
+
+const char *cp_name(int codepage)
+{
+ const struct cp_list_item *cpi, *cpno;
+ static char buf[32];
+
+ if (codepage == -1) {
+ sprintf(buf, "Use font encoding");
+ return buf;
+ }
+
+ if (codepage > 0 && codepage < 65536)
+ sprintf(buf, "CP%03d", codepage);
+ else
+ *buf = 0;
+
+ if (codepage >= 65536) {
+ cpno = 0;
+ for (cpi = cp_list; cpi->name; cpi++)
+ if (cpi == cp_list + (codepage - 65536)) {
+ cpno = cpi;
+ break;
+ }
+ if (cpno)
+ for (cpi = cp_list; cpi->name; cpi++) {
+ if (cpno->cp_table == cpi->cp_table)
+ return cpi->name;
+ }
+ } else {
+ for (cpi = cp_list; cpi->name; cpi++) {
+ if (codepage == cpi->codepage)
+ return cpi->name;
+ }
+ }
+ return buf;
+}
+
+/*
+ * Return the nth code page in the list, for use in the GUI
+ * configurer.
+ */
+const char *cp_enumerate(int index)
+{
+ if (index < 0 || index >= lenof(cp_list))
+ return NULL;
+ return cp_list[index].name;
+}
+
+void get_unitab(int codepage, wchar_t * unitab, int ftype)
+{
+ char tbuf[4];
+ int i, max = 256, flg = MB_ERR_INVALID_CHARS;
+
+ if (ftype)
+ flg |= MB_USEGLYPHCHARS;
+ if (ftype == 2)
+ max = 128;
+
+ if (codepage == CP_UTF8) {
+ for (i = 0; i < max; i++)
+ unitab[i] = i;
+ return;
+ }
+
+ if (codepage == CP_ACP)
+ codepage = GetACP();
+ else if (codepage == CP_OEMCP)
+ codepage = GetOEMCP();
+
+ if (codepage > 0 && codepage < 65536) {
+ for (i = 0; i < max; i++) {
+ tbuf[0] = i;
+
+ if (mb_to_wc(codepage, flg, tbuf, 1, unitab + i, 1)
+ != 1)
+ unitab[i] = 0xFFFD;
+ }
+ } else {
+ int j = 256 - cp_list[codepage & 0xFFFF].cp_size;
+ for (i = 0; i < max; i++)
+ unitab[i] = i;
+ 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,
+ char *mbstr, int mblen, char *defchr, int *defused,
+ struct unicode_data *ucsdata)
+{
+ char *p;
+ int i;
+ if (ucsdata && codepage == ucsdata->line_codepage && ucsdata->uni_tbl) {
+ /* Do this by array lookup if we can. */
+ if (wclen < 0) {
+ for (wclen = 0; wcstr[wclen++] ;); /* will include the NUL */
+ }
+ for (p = mbstr, i = 0; i < wclen; i++) {
+ wchar_t ch = wcstr[i];
+ int by;
+ char *p1;
+ if (ucsdata->uni_tbl && (p1 = ucsdata->uni_tbl[(ch >> 8) & 0xFF])
+ && (by = p1[ch & 0xFF]))
+ *p++ = by;
+ else if (ch < 0x80)
+ *p++ = (char) ch;
+ else if (defchr) {
+ int j;
+ for (j = 0; defchr[j]; j++)
+ *p++ = defchr[j];
+ if (defused) *defused = 1;
+ }
+#if 1
+ else
+ *p++ = '.';
+#endif
+ assert(p - mbstr < mblen);
+ }
+ return p - mbstr;
+ } else
+ return WideCharToMultiByte(codepage, flags, wcstr, wclen,
+ mbstr, mblen, defchr, defused);
+}
+
+int mb_to_wc(int codepage, int flags, 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);
+}
--- /dev/null
+/*
+ * winutils.c: miscellaneous Windows utilities for GUI apps
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+
+#include "putty.h"
+#include "misc.h"
+
+#ifdef TESTMODE
+/* Definitions to allow this module to be compiled standalone for testing
+ * split_into_argv(). */
+#define smalloc malloc
+#define srealloc realloc
+#define sfree free
+#endif
+
+/*
+ * GetOpenFileName/GetSaveFileName tend to muck around with the process'
+ * working directory on at least some versions of Windows.
+ * Here's a wrapper that gives more control over this, and hides a little
+ * bit of other grottiness.
+ */
+
+struct filereq_tag {
+ TCHAR cwd[MAX_PATH];
+};
+
+/*
+ * `of' is expected to be initialised with most interesting fields, but
+ * this function does some administrivia. (assume `of' was memset to 0)
+ * save==1 -> GetSaveFileName; save==0 -> GetOpenFileName
+ * `state' is optional.
+ */
+BOOL request_file(filereq *state, OPENFILENAME *of, int preserve, int save)
+{
+ TCHAR cwd[MAX_PATH]; /* process CWD */
+ BOOL ret;
+
+ /* Get process CWD */
+ if (preserve) {
+ DWORD r = GetCurrentDirectory(lenof(cwd), cwd);
+ if (r == 0 || r >= lenof(cwd))
+ /* Didn't work, oh well. Stop trying to be clever. */
+ preserve = 0;
+ }
+
+ /* Open the file requester, maybe setting lpstrInitialDir */
+ {
+#ifdef OPENFILENAME_SIZE_VERSION_400
+ of->lStructSize = OPENFILENAME_SIZE_VERSION_400;
+#else
+ of->lStructSize = sizeof(*of);
+#endif
+ of->lpstrInitialDir = (state && state->cwd[0]) ? state->cwd : NULL;
+ /* Actually put up the requester. */
+ ret = save ? GetSaveFileName(of) : GetOpenFileName(of);
+ }
+
+ /* Get CWD left by requester */
+ if (state) {
+ DWORD r = GetCurrentDirectory(lenof(state->cwd), state->cwd);
+ if (r == 0 || r >= lenof(state->cwd))
+ /* Didn't work, oh well. */
+ state->cwd[0] = '\0';
+ }
+
+ /* Restore process CWD */
+ if (preserve)
+ /* If it fails, there's not much we can do. */
+ (void) SetCurrentDirectory(cwd);
+
+ return ret;
+}
+
+filereq *filereq_new(void)
+{
+ filereq *ret = snew(filereq);
+ ret->cwd[0] = '\0';
+ return ret;
+}
+
+void filereq_free(filereq *state)
+{
+ sfree(state);
+}
+
+/*
+ * Message box with optional context help.
+ */
+
+/* Callback function to launch context help. */
+static VOID CALLBACK message_box_help_callback(LPHELPINFO lpHelpInfo)
+{
+ char *context = NULL;
+#define CHECK_CTX(name) \
+ do { \
+ if (lpHelpInfo->dwContextId == WINHELP_CTXID_ ## name) \
+ context = WINHELP_CTX_ ## name; \
+ } while (0)
+ CHECK_CTX(errors_hostkey_absent);
+ CHECK_CTX(errors_hostkey_changed);
+ CHECK_CTX(errors_cantloadkey);
+ CHECK_CTX(option_cleanup);
+ CHECK_CTX(pgp_fingerprints);
+#undef CHECK_CTX
+ if (context)
+ launch_help(hwnd, context);
+}
+
+int message_box(LPCTSTR text, LPCTSTR caption, DWORD style, DWORD helpctxid)
+{
+ MSGBOXPARAMS mbox;
+
+ /*
+ * We use MessageBoxIndirect() because it allows us to specify a
+ * callback function for the Help button.
+ */
+ mbox.cbSize = sizeof(mbox);
+ /* Assumes the globals `hinst' and `hwnd' have sensible values. */
+ mbox.hInstance = hinst;
+ mbox.hwndOwner = hwnd;
+ mbox.lpfnMsgBoxCallback = &message_box_help_callback;
+ mbox.dwLanguageId = LANG_NEUTRAL;
+ mbox.lpszText = text;
+ mbox.lpszCaption = caption;
+ mbox.dwContextHelpId = helpctxid;
+ mbox.dwStyle = style;
+ if (helpctxid != 0 && has_help()) mbox.dwStyle |= MB_HELP;
+ return MessageBoxIndirect(&mbox);
+}
+
+/*
+ * Display the fingerprints of the PGP Master Keys to the user.
+ */
+void pgp_fingerprints(void)
+{
+ message_box("These are the fingerprints of the PuTTY PGP Master Keys. They can\n"
+ "be used to establish a trust path from this executable to another\n"
+ "one. See the manual for more information.\n"
+ "(Note: these fingerprints have nothing to do with SSH!)\n"
+ "\n"
+ "PuTTY Master Key (RSA), 1024-bit:\n"
+ " " PGP_RSA_MASTER_KEY_FP "\n"
+ "PuTTY Master Key (DSA), 1024-bit:\n"
+ " " PGP_DSA_MASTER_KEY_FP,
+ "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).
+ *
+ * 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
+ * at the start of the corresponding element of argv in the
+ * original command line. So if you get half way through processing
+ * your command line in argc/argv form and then decide you want to
+ * treat the rest as a raw string, you can. If you don't want to,
+ * `argstart' can be safely left NULL.
+ */
+void split_into_argv(char *cmdline, int *argc, char ***argv,
+ char ***argstart)
+{
+ char *p;
+ char *outputline, *q;
+ char **outputargv, **outputargstart;
+ int outputargc;
+
+ /*
+ * 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.
+ *
+ * - Backslashes are _only_ special when a sequence of them
+ * appear just before a double quote. In this situation,
+ * they are treated like C backslashes: so \" just gives a
+ * literal quote, \\" gives a literal backslash and then
+ * opens or closes a double-quoted segment, \\\" gives a
+ * literal backslash and then a literal quote, \\\\" gives
+ * two literal backslashes and then opens/closes a
+ * double-quoted segment, and so forth. Note that this
+ * behaviour is identical inside and outside double quotes.
+ *
+ * - Two successive double quotes become one literal double
+ * quote, but only _inside_ a double-quoted segment.
+ * Outside, they just form an empty double-quoted segment
+ * (which may cause an empty argument word).
+ *
+ * - That only leaves the interesting question of what happens
+ * when one or more backslashes precedes two or more double
+ * quotes, starting inside a double-quoted string. And the
+ * answer to that appears somewhat bizarre. Here I tabulate
+ * number of backslashes (across the top) against number of
+ * quotes (down the left), and indicate how many backslashes
+ * are output, how many quotes are output, and whether a
+ * quoted segment is open at the end of the sequence:
+ *
+ * backslashes
+ *
+ * 0 1 2 3 4
+ *
+ * 0 0,0,y | 1,0,y 2,0,y 3,0,y 4,0,y
+ * --------+-----------------------------
+ * 1 0,0,n | 0,1,y 1,0,n 1,1,y 2,0,n
+ * q 2 0,1,n | 0,1,n 1,1,n 1,1,n 2,1,n
+ * u 3 0,1,y | 0,2,n 1,1,y 1,2,n 2,1,y
+ * o 4 0,1,n | 0,2,y 1,1,n 1,2,y 2,1,n
+ * t 5 0,2,n | 0,2,n 1,2,n 1,2,n 2,2,n
+ * e 6 0,2,y | 0,3,n 1,2,y 1,3,n 2,2,y
+ * s 7 0,2,n | 0,3,y 1,2,n 1,3,y 2,2,n
+ * 8 0,3,n | 0,3,n 1,3,n 1,3,n 2,3,n
+ * 9 0,3,y | 0,4,n 1,3,y 1,4,n 2,3,y
+ * 10 0,3,n | 0,4,y 1,3,n 1,4,y 2,3,n
+ * 11 0,4,n | 0,4,n 1,4,n 1,4,n 2,4,n
+ *
+ *
+ * [Test fragment was of the form "a\\\"""b c" d.]
+ *
+ * There is very weird mod-3 behaviour going on here in the
+ * number of quotes, and it even applies when there aren't any
+ * backslashes! How ghastly.
+ *
+ * With a bit of thought, this extremely odd diagram suddenly
+ * coalesced itself into a coherent, if still ghastly, model of
+ * how things work:
+ *
+ * - As before, backslashes are only special when one or more
+ * of them appear contiguously before at least one double
+ * quote. In this situation the backslashes do exactly what
+ * you'd expect: each one quotes the next thing in front of
+ * it, so you end up with n/2 literal backslashes (if n is
+ * even) or (n-1)/2 literal backslashes and a literal quote
+ * (if n is odd). In the latter case the double quote
+ * character right after the backslashes is used up.
+ *
+ * - After that, any remaining double quotes are processed. A
+ * string of contiguous unescaped double quotes has a mod-3
+ * behaviour:
+ *
+ * * inside a quoted segment, a quote ends the segment.
+ * * _immediately_ after ending a quoted segment, a quote
+ * simply produces a literal quote.
+ * * otherwise, outside a quoted segment, a quote begins a
+ * quoted segment.
+ *
+ * So, for example, if we started inside a quoted segment
+ * then two contiguous quotes would close the segment and
+ * produce a literal quote; three would close the segment,
+ * produce a literal quote, and open a new segment. If we
+ * started outside a quoted segment, then two contiguous
+ * quotes would open and then close a segment, producing no
+ * output (but potentially creating a zero-length argument);
+ * but three quotes would open and close a segment and then
+ * produce a literal quote.
+ */
+
+ /*
+ * First deal with the simplest of all special cases: if there
+ * aren't any arguments, return 0,NULL,NULL.
+ */
+ while (*cmdline && isspace(*cmdline)) cmdline++;
+ if (!*cmdline) {
+ if (argc) *argc = 0;
+ if (argv) *argv = NULL;
+ if (argstart) *argstart = NULL;
+ return;
+ }
+
+ /*
+ * This will guaranteeably be big enough; we can realloc it
+ * down later.
+ */
+ outputline = snewn(1+strlen(cmdline), char);
+ outputargv = snewn(strlen(cmdline)+1 / 2, char *);
+ outputargstart = snewn(strlen(cmdline)+1 / 2, char *);
+
+ p = cmdline; q = outputline; outputargc = 0;
+
+ while (*p) {
+ int quote;
+
+ /* Skip whitespace searching for start of argument. */
+ while (*p && isspace(*p)) p++;
+ if (!*p) break;
+
+ /* We have an argument; start it. */
+ outputargv[outputargc] = q;
+ outputargstart[outputargc] = p;
+ outputargc++;
+ quote = 0;
+
+ /* Copy data into the argument until it's finished. */
+ while (*p) {
+ if (!quote && isspace(*p))
+ break; /* argument is finished */
+
+ if (*p == '"' || *p == '\\') {
+ /*
+ * We have a sequence of zero or more backslashes
+ * followed by a sequence of zero or more quotes.
+ * Count up how many of each, and then deal with
+ * them as appropriate.
+ */
+ int i, slashes = 0, quotes = 0;
+ while (*p == '\\') slashes++, p++;
+ while (*p == '"') quotes++, p++;
+
+ if (!quotes) {
+ /*
+ * Special case: if there are no quotes,
+ * slashes are not special at all, so just copy
+ * n slashes to the output string.
+ */
+ while (slashes--) *q++ = '\\';
+ } else {
+ /* Slashes annihilate in pairs. */
+ while (slashes >= 2) slashes -= 2, *q++ = '\\';
+
+ /* One remaining slash takes out the first quote. */
+ if (slashes) quotes--, *q++ = '"';
+
+ if (quotes > 0) {
+ /* Outside a quote segment, a quote starts one. */
+ if (!quote) quotes--, quote = 1;
+
+ /* Now we produce (n+1)/3 literal quotes... */
+ for (i = 3; i <= quotes+1; i += 3) *q++ = '"';
+
+ /* ... and end in a quote segment iff 3 divides n. */
+ quote = (quotes % 3 == 0);
+ }
+ }
+ } else {
+ *q++ = *p++;
+ }
+ }
+
+ /* At the end of an argument, just append a trailing NUL. */
+ *q++ = '\0';
+ }
+
+ outputargv = sresize(outputargv, outputargc, char *);
+ outputargstart = sresize(outputargstart, outputargc, char *);
+
+ if (argc) *argc = outputargc;
+ if (argv) *argv = outputargv; else sfree(outputargv);
+ if (argstart) *argstart = outputargstart; else sfree(outputargstart);
+}
+
+#ifdef TESTMODE
+
+const struct argv_test {
+ const char *cmdline;
+ const char *argv[10];
+} argv_tests[] = {
+ /*
+ * We generate this set of tests by invoking ourself with
+ * `-generate'.
+ */
+ {"ab c\" d", {"ab", "c d", NULL}},
+ {"a\"b c\" d", {"ab c", "d", NULL}},
+ {"a\"\"b c\" d", {"ab", "c d", NULL}},
+ {"a\"\"\"b c\" d", {"a\"b", "c d", NULL}},
+ {"a\"\"\"\"b c\" d", {"a\"b c", "d", NULL}},
+ {"a\"\"\"\"\"b c\" d", {"a\"b", "c d", NULL}},
+ {"a\"\"\"\"\"\"b c\" d", {"a\"\"b", "c d", NULL}},
+ {"a\"\"\"\"\"\"\"b c\" d", {"a\"\"b c", "d", NULL}},
+ {"a\"\"\"\"\"\"\"\"b c\" d", {"a\"\"b", "c d", NULL}},
+ {"a\\b c\" d", {"a\\b", "c d", NULL}},
+ {"a\\\"b c\" d", {"a\"b", "c d", NULL}},
+ {"a\\\"\"b c\" d", {"a\"b c", "d", NULL}},
+ {"a\\\"\"\"b c\" d", {"a\"b", "c d", NULL}},
+ {"a\\\"\"\"\"b c\" d", {"a\"\"b", "c d", NULL}},
+ {"a\\\"\"\"\"\"b c\" d", {"a\"\"b c", "d", NULL}},
+ {"a\\\"\"\"\"\"\"b c\" d", {"a\"\"b", "c d", NULL}},
+ {"a\\\"\"\"\"\"\"\"b c\" d", {"a\"\"\"b", "c d", NULL}},
+ {"a\\\"\"\"\"\"\"\"\"b c\" d", {"a\"\"\"b c", "d", NULL}},
+ {"a\\\\b c\" d", {"a\\\\b", "c d", NULL}},
+ {"a\\\\\"b c\" d", {"a\\b c", "d", NULL}},
+ {"a\\\\\"\"b c\" d", {"a\\b", "c d", NULL}},
+ {"a\\\\\"\"\"b c\" d", {"a\\\"b", "c d", NULL}},
+ {"a\\\\\"\"\"\"b c\" d", {"a\\\"b c", "d", NULL}},
+ {"a\\\\\"\"\"\"\"b c\" d", {"a\\\"b", "c d", NULL}},
+ {"a\\\\\"\"\"\"\"\"b c\" d", {"a\\\"\"b", "c d", NULL}},
+ {"a\\\\\"\"\"\"\"\"\"b c\" d", {"a\\\"\"b c", "d", NULL}},
+ {"a\\\\\"\"\"\"\"\"\"\"b c\" d", {"a\\\"\"b", "c d", NULL}},
+ {"a\\\\\\b c\" d", {"a\\\\\\b", "c d", NULL}},
+ {"a\\\\\\\"b c\" d", {"a\\\"b", "c d", NULL}},
+ {"a\\\\\\\"\"b c\" d", {"a\\\"b c", "d", NULL}},
+ {"a\\\\\\\"\"\"b c\" d", {"a\\\"b", "c d", NULL}},
+ {"a\\\\\\\"\"\"\"b c\" d", {"a\\\"\"b", "c d", NULL}},
+ {"a\\\\\\\"\"\"\"\"b c\" d", {"a\\\"\"b c", "d", NULL}},
+ {"a\\\\\\\"\"\"\"\"\"b c\" d", {"a\\\"\"b", "c d", NULL}},
+ {"a\\\\\\\"\"\"\"\"\"\"b c\" d", {"a\\\"\"\"b", "c d", NULL}},
+ {"a\\\\\\\"\"\"\"\"\"\"\"b c\" d", {"a\\\"\"\"b c", "d", NULL}},
+ {"a\\\\\\\\b c\" d", {"a\\\\\\\\b", "c d", NULL}},
+ {"a\\\\\\\\\"b c\" d", {"a\\\\b c", "d", NULL}},
+ {"a\\\\\\\\\"\"b c\" d", {"a\\\\b", "c d", NULL}},
+ {"a\\\\\\\\\"\"\"b c\" d", {"a\\\\\"b", "c d", NULL}},
+ {"a\\\\\\\\\"\"\"\"b c\" d", {"a\\\\\"b c", "d", NULL}},
+ {"a\\\\\\\\\"\"\"\"\"b c\" d", {"a\\\\\"b", "c d", NULL}},
+ {"a\\\\\\\\\"\"\"\"\"\"b c\" d", {"a\\\\\"\"b", "c d", NULL}},
+ {"a\\\\\\\\\"\"\"\"\"\"\"b c\" d", {"a\\\\\"\"b c", "d", NULL}},
+ {"a\\\\\\\\\"\"\"\"\"\"\"\"b c\" d", {"a\\\\\"\"b", "c d", NULL}},
+ {"\"ab c\" d", {"ab c", "d", NULL}},
+ {"\"a\"b c\" d", {"ab", "c d", NULL}},
+ {"\"a\"\"b c\" d", {"a\"b", "c d", NULL}},
+ {"\"a\"\"\"b c\" d", {"a\"b c", "d", NULL}},
+ {"\"a\"\"\"\"b c\" d", {"a\"b", "c d", NULL}},
+ {"\"a\"\"\"\"\"b c\" d", {"a\"\"b", "c d", NULL}},
+ {"\"a\"\"\"\"\"\"b c\" d", {"a\"\"b c", "d", NULL}},
+ {"\"a\"\"\"\"\"\"\"b c\" d", {"a\"\"b", "c d", NULL}},
+ {"\"a\"\"\"\"\"\"\"\"b c\" d", {"a\"\"\"b", "c d", NULL}},
+ {"\"a\\b c\" d", {"a\\b c", "d", NULL}},
+ {"\"a\\\"b c\" d", {"a\"b c", "d", NULL}},
+ {"\"a\\\"\"b c\" d", {"a\"b", "c d", NULL}},
+ {"\"a\\\"\"\"b c\" d", {"a\"\"b", "c d", NULL}},
+ {"\"a\\\"\"\"\"b c\" d", {"a\"\"b c", "d", NULL}},
+ {"\"a\\\"\"\"\"\"b c\" d", {"a\"\"b", "c d", NULL}},
+ {"\"a\\\"\"\"\"\"\"b c\" d", {"a\"\"\"b", "c d", NULL}},
+ {"\"a\\\"\"\"\"\"\"\"b c\" d", {"a\"\"\"b c", "d", NULL}},
+ {"\"a\\\"\"\"\"\"\"\"\"b c\" d", {"a\"\"\"b", "c d", NULL}},
+ {"\"a\\\\b c\" d", {"a\\\\b c", "d", NULL}},
+ {"\"a\\\\\"b c\" d", {"a\\b", "c d", NULL}},
+ {"\"a\\\\\"\"b c\" d", {"a\\\"b", "c d", NULL}},
+ {"\"a\\\\\"\"\"b c\" d", {"a\\\"b c", "d", NULL}},
+ {"\"a\\\\\"\"\"\"b c\" d", {"a\\\"b", "c d", NULL}},
+ {"\"a\\\\\"\"\"\"\"b c\" d", {"a\\\"\"b", "c d", NULL}},
+ {"\"a\\\\\"\"\"\"\"\"b c\" d", {"a\\\"\"b c", "d", NULL}},
+ {"\"a\\\\\"\"\"\"\"\"\"b c\" d", {"a\\\"\"b", "c d", NULL}},
+ {"\"a\\\\\"\"\"\"\"\"\"\"b c\" d", {"a\\\"\"\"b", "c d", NULL}},
+ {"\"a\\\\\\b c\" d", {"a\\\\\\b c", "d", NULL}},
+ {"\"a\\\\\\\"b c\" d", {"a\\\"b c", "d", NULL}},
+ {"\"a\\\\\\\"\"b c\" d", {"a\\\"b", "c d", NULL}},
+ {"\"a\\\\\\\"\"\"b c\" d", {"a\\\"\"b", "c d", NULL}},
+ {"\"a\\\\\\\"\"\"\"b c\" d", {"a\\\"\"b c", "d", NULL}},
+ {"\"a\\\\\\\"\"\"\"\"b c\" d", {"a\\\"\"b", "c d", NULL}},
+ {"\"a\\\\\\\"\"\"\"\"\"b c\" d", {"a\\\"\"\"b", "c d", NULL}},
+ {"\"a\\\\\\\"\"\"\"\"\"\"b c\" d", {"a\\\"\"\"b c", "d", NULL}},
+ {"\"a\\\\\\\"\"\"\"\"\"\"\"b c\" d", {"a\\\"\"\"b", "c d", NULL}},
+ {"\"a\\\\\\\\b c\" d", {"a\\\\\\\\b c", "d", NULL}},
+ {"\"a\\\\\\\\\"b c\" d", {"a\\\\b", "c d", NULL}},
+ {"\"a\\\\\\\\\"\"b c\" d", {"a\\\\\"b", "c d", NULL}},
+ {"\"a\\\\\\\\\"\"\"b c\" d", {"a\\\\\"b c", "d", NULL}},
+ {"\"a\\\\\\\\\"\"\"\"b c\" d", {"a\\\\\"b", "c d", NULL}},
+ {"\"a\\\\\\\\\"\"\"\"\"b c\" d", {"a\\\\\"\"b", "c d", NULL}},
+ {"\"a\\\\\\\\\"\"\"\"\"\"b c\" d", {"a\\\\\"\"b c", "d", NULL}},
+ {"\"a\\\\\\\\\"\"\"\"\"\"\"b c\" d", {"a\\\\\"\"b", "c d", NULL}},
+ {"\"a\\\\\\\\\"\"\"\"\"\"\"\"b c\" d", {"a\\\\\"\"\"b", "c d", NULL}},
+};
+
+int main(int argc, char **argv)
+{
+ int i, j;
+
+ if (argc > 1) {
+ /*
+ * Generation of tests.
+ *
+ * Given `-splat <args>', we print out a C-style
+ * representation of each argument (in the form "a", "b",
+ * NULL), backslash-escaping each backslash and double
+ * quote.
+ *
+ * Given `-split <string>', we first doctor `string' by
+ * turning forward slashes into backslashes, single quotes
+ * into double quotes and underscores into spaces; and then
+ * we feed the resulting string to ourself with `-splat'.
+ *
+ * Given `-generate', we concoct a variety of fun test
+ * cases, encode them in quote-safe form (mapping \, " and
+ * space to /, ' and _ respectively) and feed each one to
+ * `-split'.
+ */
+ if (!strcmp(argv[1], "-splat")) {
+ int i;
+ char *p;
+ for (i = 2; i < argc; i++) {
+ putchar('"');
+ for (p = argv[i]; *p; p++) {
+ if (*p == '\\' || *p == '"')
+ putchar('\\');
+ putchar(*p);
+ }
+ printf("\", ");
+ }
+ printf("NULL");
+ return 0;
+ }
+
+ if (!strcmp(argv[1], "-split") && argc > 2) {
+ char *str = malloc(20 + strlen(argv[0]) + strlen(argv[2]));
+ char *p, *q;
+
+ q = str + sprintf(str, "%s -splat ", argv[0]);
+ printf(" {\"");
+ for (p = argv[2]; *p; p++, q++) {
+ switch (*p) {
+ case '/': printf("\\\\"); *q = '\\'; break;
+ case '\'': printf("\\\""); *q = '"'; break;
+ case '_': printf(" "); *q = ' '; break;
+ default: putchar(*p); *q = *p; break;
+ }
+ }
+ *p = '\0';
+ printf("\", {");
+ fflush(stdout);
+
+ system(str);
+
+ printf("}},\n");
+
+ return 0;
+ }
+
+ if (!strcmp(argv[1], "-generate")) {
+ char *teststr, *p;
+ int i, initialquote, backslashes, quotes;
+
+ teststr = malloc(200 + strlen(argv[0]));
+
+ for (initialquote = 0; initialquote <= 1; initialquote++) {
+ for (backslashes = 0; backslashes < 5; backslashes++) {
+ for (quotes = 0; quotes < 9; quotes++) {
+ p = teststr + sprintf(teststr, "%s -split ", argv[0]);
+ if (initialquote) *p++ = '\'';
+ *p++ = 'a';
+ for (i = 0; i < backslashes; i++) *p++ = '/';
+ for (i = 0; i < quotes; i++) *p++ = '\'';
+ *p++ = 'b';
+ *p++ = '_';
+ *p++ = 'c';
+ *p++ = '\'';
+ *p++ = '_';
+ *p++ = 'd';
+ *p = '\0';
+
+ system(teststr);
+ }
+ }
+ }
+ return 0;
+ }
+
+ fprintf(stderr, "unrecognised option: \"%s\"\n", argv[1]);
+ return 1;
+ }
+
+ /*
+ * If we get here, we were invoked with no arguments, so just
+ * run the tests.
+ */
+
+ for (i = 0; i < lenof(argv_tests); i++) {
+ int ac;
+ char **av;
+
+ split_into_argv(argv_tests[i].cmdline, &ac, &av);
+
+ for (j = 0; j < ac && argv_tests[i].argv[j]; j++) {
+ if (strcmp(av[j], argv_tests[i].argv[j])) {
+ printf("failed test %d (|%s|) arg %d: |%s| should be |%s|\n",
+ i, argv_tests[i].cmdline,
+ j, av[j], argv_tests[i].argv[j]);
+ }
+#ifdef VERBOSE
+ else {
+ printf("test %d (|%s|) arg %d: |%s| == |%s|\n",
+ i, argv_tests[i].cmdline,
+ j, av[j], argv_tests[i].argv[j]);
+ }
+#endif
+ }
+ if (j < ac)
+ printf("failed test %d (|%s|): %d args returned, should be %d\n",
+ i, argv_tests[i].cmdline, ac, j);
+ if (argv_tests[i].argv[j])
+ printf("failed test %d (|%s|): %d args returned, should be more\n",
+ i, argv_tests[i].cmdline, ac);
+ }
+
+ return 0;
+}
+
+#endif
--- /dev/null
+/*
+ * winx11.c: fetch local auth data for X forwarding.
+ */
+
+#include <ctype.h>
+#include <assert.h>
+#include <stdlib.h>
+
+#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;
--- /dev/null
+/*
+ * Platform-independent bits of X11 forwarding.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <time.h>
+
+#include "putty.h"
+#include "ssh.h"
+#include "tree234.h"
+
+#define GET_16BIT(endian, cp) \
+ (endian=='B' ? GET_16BIT_MSB_FIRST(cp) : GET_16BIT_LSB_FIRST(cp))
+
+#define PUT_16BIT(endian, cp, val) \
+ (endian=='B' ? PUT_16BIT_MSB_FIRST(cp, val) : PUT_16BIT_LSB_FIRST(cp, val))
+
+const char *const x11_authnames[] = {
+ "", "MIT-MAGIC-COOKIE-1", "XDM-AUTHORIZATION-1"
+};
+
+struct XDMSeen {
+ unsigned int time;
+ unsigned char clientid[6];
+};
+
+struct X11Private {
+ 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 */
+ 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 peer_port;
+ void *c; /* data used by ssh.c */
+ Socket s;
+};
+
+static int xdmseen_cmp(void *a, void *b)
+{
+ struct XDMSeen *sa = a, *sb = b;
+ return sa->time > sb->time ? 1 :
+ sa->time < sb->time ? -1 :
+ memcmp(sa->clientid, sb->clientid, sizeof(sa->clientid));
+}
+
+/* Do-nothing "plug" implementation, used by x11_setup_display() when it
+ * creates a trial connection (and then immediately closes it).
+ * XXX: bit out of place here, could in principle live in a platform-
+ * independent network.c or something */
+static void dummy_plug_log(Plug p, int type, SockAddr addr, int port,
+ const char *error_msg, int error_code) { }
+static int dummy_plug_closing
+ (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 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 X11Display *disp = snew(struct X11Display);
+ char *localcopy;
+ int i;
+
+ if (!display || !*display) {
+ localcopy = platform_get_x_display();
+ if (!localcopy || !*localcopy) {
+ sfree(localcopy);
+ localcopy = dupstr(":0"); /* plausible default for any platform */
+ }
+ } else
+ localcopy = dupstr(display);
+
+ /*
+ * Parse the display name.
+ *
+ * We expect this to have one of the following forms:
+ *
+ * - the standard X format which looks like
+ * [ [ protocol '/' ] host ] ':' displaynumber [ '.' screennumber ]
+ * (X11 also permits a double colon to indicate DECnet, but
+ * that's not our problem, thankfully!)
+ *
+ * - only seen in the wild on MacOS (so far): a pathname to a
+ * Unix-domain socket, which will typically and confusingly
+ * end in ":0", and which I'm currently distinguishing from
+ * the standard scheme by noting that it starts with '/'.
+ */
+ if (localcopy[0] == '/') {
+ disp->unixsocketpath = localcopy;
+ disp->unixdomain = TRUE;
+ disp->hostname = NULL;
+ disp->displaynum = -1;
+ disp->screennum = 0;
+ disp->addr = NULL;
+ } else {
+ char *colon, *dot, *slash;
+ char *protocol, *hostname;
+
+ colon = strrchr(localcopy, ':');
+ if (!colon) {
+ sfree(disp);
+ sfree(localcopy);
+ return NULL; /* FIXME: report a specific error? */
+ }
+
+ *colon++ = '\0';
+ dot = strchr(colon, '.');
+ if (dot)
+ *dot++ = '\0';
+
+ disp->displaynum = atoi(colon);
+ if (dot)
+ disp->screennum = atoi(dot);
+ else
+ disp->screennum = 0;
+
+ protocol = NULL;
+ hostname = localcopy;
+ if (colon > localcopy) {
+ slash = strchr(localcopy, '/');
+ if (slash) {
+ *slash++ = '\0';
+ protocol = localcopy;
+ hostname = slash;
+ }
+ }
+
+ disp->hostname = *hostname ? dupstr(hostname) : NULL;
+
+ if (protocol)
+ disp->unixdomain = (!strcmp(protocol, "local") ||
+ !strcmp(protocol, "unix"));
+ else if (!*hostname || !strcmp(hostname, "unix"))
+ disp->unixdomain = platform_uses_x11_unix_by_default;
+ else
+ disp->unixdomain = FALSE;
+
+ if (!disp->hostname && !disp->unixdomain)
+ disp->hostname = dupstr("localhost");
+
+ disp->unixsocketpath = NULL;
+ disp->addr = NULL;
+
+ sfree(localcopy);
+ }
+
+ /*
+ * Look up the display hostname, if we need to.
+ */
+ if (!disp->unixdomain) {
+ const char *err;
+
+ disp->port = 6000 + disp->displaynum;
+ disp->addr = name_lookup(disp->hostname, disp->port,
+ &disp->realhost, cfg, ADDRTYPE_UNSPEC);
+
+ if ((err = sk_addr_error(disp->addr)) != NULL) {
+ sk_addr_free(disp->addr);
+ sfree(disp->hostname);
+ sfree(disp->unixsocketpath);
+ return NULL; /* FIXME: report an error */
+ }
+ }
+
+ /*
+ * Try upgrading an IP-style localhost display to a Unix-socket
+ * display (as the standard X connection libraries do).
+ */
+ if (!disp->unixdomain && sk_address_is_local(disp->addr)) {
+ SockAddr ux = platform_get_x11_unix_address(NULL, disp->displaynum);
+ const char *err = sk_addr_error(ux);
+ if (!err) {
+ /* Create trial connection to see if there is a useful Unix-domain
+ * socket */
+ const struct plug_function_table *dummy = &dummy_plug;
+ Socket s = sk_new(sk_addr_dup(ux), 0, 0, 0, 0, 0, (Plug)&dummy);
+ err = sk_socket_error(s);
+ sk_close(s);
+ }
+ if (err) {
+ sk_addr_free(ux);
+ } else {
+ sk_addr_free(disp->addr);
+ disp->unixdomain = TRUE;
+ disp->addr = ux;
+ /* Fill in the rest in a moment */
+ }
+ }
+
+ if (disp->unixdomain) {
+ if (!disp->addr)
+ disp->addr = platform_get_x11_unix_address(disp->unixsocketpath,
+ disp->displaynum);
+ if (disp->unixsocketpath)
+ disp->realhost = dupstr(disp->unixsocketpath);
+ 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);
+
+ 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);
+ 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) {
+ 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)
+ 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 */
+ t = GET_32BIT_MSB_FIRST(data+14);
+ for (i = 18; i < 24; i++)
+ if (data[i] != 0) /* zero padding wrong */
+ return "XDM-AUTHORIZATION-1 data failed check";
+ tim = time(NULL);
+ 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);
+ 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);
+ assert(seen != NULL);
+ if (t - seen->time <= XDM_MAXSKEW)
+ break;
+ sfree(delpos234(disp->xdmseen, 0));
+ }
+ }
+ /* implement other protocols here if ever required */
+ return NULL;
+}
+
+void x11_get_auth_from_authfile(struct X11Display *disp,
+ const char *authfilename)
+{
+ FILE *authfp;
+ char *buf, *ptr, *str[4];
+ int len[4];
+ int family, protocol;
+ int ideal_match = FALSE;
+ char *ourhostname = get_hostname();
+
+ /*
+ * 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
+ * in a Unix-domain-socket record (even if there isn't actually a
+ * real Unix-domain socket available, as with OpenSSH's proxy X11
+ * server).
+ *
+ * This is apparently a fudge to get round the meaninglessness of
+ * "localhost" in a shared-home-directory context -- xauth entries
+ * for Unix-domain sockets already disambiguate this by storing
+ * the *local* hostname in the conveniently-blank hostname field,
+ * but IP "localhost" records couldn't do this. So, typically, an
+ * IP "localhost" entry in the auth database isn't present and if
+ * it were it would be ignored.
+ *
+ * However, we don't entirely trust that (say) Windows X servers
+ * won't rely on a straight "localhost" entry, bad idea though
+ * that is; so if we can't find a Unix-domain-socket entry we'll
+ * fall back to an IP-based entry if we can find one.
+ */
+ int localhost = !disp->unixdomain && sk_address_is_local(disp->addr);
+
+ authfp = fopen(authfilename, "rb");
+ if (!authfp)
+ return;
+
+ /* Records in .Xauthority contain four strings of up to 64K each */
+ buf = snewn(65537 * 4, char);
+
+ while (!ideal_match) {
+ int c, i, j, match = FALSE;
+
+#define GET do { c = fgetc(authfp); if (c == EOF) goto done; c = (unsigned char)c; } while (0)
+ /* Expect a big-endian 2-byte number giving address family */
+ GET; family = c;
+ GET; family = (family << 8) | c;
+ /* Then expect four strings, each composed of a big-endian 2-byte
+ * length field followed by that many bytes of data */
+ ptr = buf;
+ for (i = 0; i < 4; i++) {
+ GET; len[i] = c;
+ GET; len[i] = (len[i] << 8) | c;
+ str[i] = ptr;
+ for (j = 0; j < len[i]; j++) {
+ GET; *ptr++ = c;
+ }
+ *ptr++ = '\0';
+ }
+#undef GET
+
+ /*
+ * Now we have a full X authority record in memory. See
+ * whether it matches the display we're trying to
+ * authenticate to.
+ *
+ * The details we've just read should be interpreted as
+ * follows:
+ *
+ * - 'family' is the network address family used to
+ * connect to the display. 0 means IPv4; 6 means IPv6;
+ * 256 means Unix-domain sockets.
+ *
+ * - str[0] is the network address itself. For IPv4 and
+ * IPv6, this is a string of binary data of the
+ * appropriate length (respectively 4 and 16 bytes)
+ * representing the address in big-endian format, e.g.
+ * 7F 00 00 01 means IPv4 localhost. For Unix-domain
+ * sockets, this is the host name of the machine on
+ * which the Unix-domain display resides (so that an
+ * .Xauthority file on a shared file system can contain
+ * authority entries for Unix-domain displays on
+ * several machines without them clashing).
+ *
+ * - str[1] is the display number. I've no idea why
+ * .Xauthority stores this as a string when it has a
+ * perfectly good integer format, but there we go.
+ *
+ * - str[2] is the authorisation method, encoded as its
+ * canonical string name (i.e. "MIT-MAGIC-COOKIE-1",
+ * "XDM-AUTHORIZATION-1" or something we don't
+ * recognise).
+ *
+ * - str[3] is the actual authorisation data, stored in
+ * binary form.
+ */
+
+ if (disp->displaynum < 0 || disp->displaynum != atoi(str[1]))
+ continue; /* not the one */
+
+ for (protocol = 1; protocol < lenof(x11_authnames); protocol++)
+ if (!strcmp(str[2], x11_authnames[protocol]))
+ break;
+ if (protocol == lenof(x11_authnames))
+ continue; /* don't recognise this protocol, look for another */
+
+ switch (family) {
+ case 0: /* IPv4 */
+ if (!disp->unixdomain &&
+ sk_addrtype(disp->addr) == ADDRTYPE_IPV4) {
+ char buf[4];
+ sk_addrcopy(disp->addr, buf);
+ if (len[0] == 4 && !memcmp(str[0], buf, 4)) {
+ match = TRUE;
+ /* If this is a "localhost" entry, note it down
+ * but carry on looking for a Unix-domain entry. */
+ ideal_match = !localhost;
+ }
+ }
+ break;
+ case 6: /* IPv6 */
+ if (!disp->unixdomain &&
+ sk_addrtype(disp->addr) == ADDRTYPE_IPV6) {
+ char buf[16];
+ sk_addrcopy(disp->addr, buf);
+ if (len[0] == 16 && !memcmp(str[0], buf, 16)) {
+ match = TRUE;
+ ideal_match = !localhost;
+ }
+ }
+ break;
+ case 256: /* Unix-domain / localhost */
+ if ((disp->unixdomain || localhost)
+ && ourhostname && !strcmp(ourhostname, str[0]))
+ /* A matching Unix-domain socket is always the best
+ * match. */
+ match = ideal_match = TRUE;
+ break;
+ }
+
+ if (match) {
+ /* Current best guess -- may be overridden if !ideal_match */
+ disp->localauthproto = protocol;
+ sfree(disp->localauthdata); /* free previous guess, if any */
+ disp->localauthdata = snewn(len[3], unsigned char);
+ memcpy(disp->localauthdata, str[3], len[3]);
+ disp->localauthdatalen = len[3];
+ }
+ }
+
+ done:
+ fclose(authfp);
+ memset(buf, 0, 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 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);
+ return 1;
+}
+
+static int x11_receive(Plug plug, int urgent, char *data, int len)
+{
+ struct X11Private *pr = (struct X11Private *) plug;
+
+ if (sshfwd_write(pr->c, data, len) > 0) {
+ pr->throttled = 1;
+ sk_set_frozen(pr->s, 1);
+ }
+
+ return 1;
+}
+
+static void x11_sent(Plug plug, int bufsize)
+{
+ struct X11Private *pr = (struct X11Private *) plug;
+
+ sshfwd_unthrottle(pr->c, bufsize);
+}
+
+/*
+ * When setting up X forwarding, we should send the screen number
+ * from the specified local display. This function extracts it from
+ * the display string.
+ */
+int x11_get_screen_number(char *display)
+{
+ int n;
+
+ n = strcspn(display, ":");
+ if (!display[n])
+ return 0;
+ n = strcspn(display, ".");
+ if (!display[n])
+ 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
+ */
+extern const char *x11_init(Socket *s, struct X11Display *disp, void *c,
+ const char *peeraddr, int peerport,
+ const Config *cfg)
+{
+ static const struct plug_function_table fn_table = {
+ x11_log,
+ x11_closing,
+ x11_receive,
+ x11_sent,
+ NULL
+ };
+
+ const char *err;
+ struct X11Private *pr;
+
+ /*
+ * 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;
+ }
+
+ /*
+ * See if we can make sense of the peer address we were given.
+ */
+ {
+ 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);
+}
+
+/*
+ * Called to send data down the raw connection.
+ */
+int x11_send(Socket s, char *data, int len)
+{
+ struct X11Private *pr;
+ if (!s)
+ 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)
+ 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;
+ /* 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);
+ }
+
+ /*
+ * 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)
+ 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;
+ }
+
+ /*
+ * After initialisation, just copy data simply.
+ */
+
+ return sk_write(s, data, len);
+}